From 9f261c882fe21b831a860a5009d79b9f6d8b4aae Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 5 Aug 2016 17:51:12 +0200 Subject: [PATCH 0001/2110] Add preliminary support for Sync (#58) --- Dockerfile | 1 + Jenkinsfile | 4 +- build.gradle | 46 +++++ .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle-plugin/build.gradle | 13 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- integration-tests/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- integration-tests/settings.gradle | 2 +- integration-tests/sync/.gitignore | 3 + integration-tests/sync/README.md | 28 +++ integration-tests/sync/build.gradle | 70 ++++++++ .../sync/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + integration-tests/sync/gradlew | 160 ++++++++++++++++++ integration-tests/sync/gradlew.bat | 90 ++++++++++ integration-tests/sync/proguard-rules.pro | 17 ++ .../realm/tests/sync/ProcessCommitTests.java | 98 +++++++++++ .../sync/src/main/AndroidManifest.xml | 12 ++ .../realm/tests/sync/model/ProcessInfo.java | 50 ++++++ .../tests/sync/service/SendOneCommit.java | 59 +++++++ .../io/realm/tests/sync/utils/Constants.java | 24 +++ .../io/realm/tests/sync/utils/HttpUtils.java | 68 ++++++++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes .../sync/test_server/package.json | 13 ++ integration-tests/sync/test_server/server.js | 67 ++++++++ integration-tests/sync/test_server/start.sh | 2 + realm-annotations/build.gradle | 13 ++ realm-transformer/build.gradle | 13 ++ realm/build.gradle | 6 +- realm/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../realm-annotations-processor/build.gradle | 13 ++ realm/realm-jni/build.gradle | 30 ++-- .../src/io_realm_internal_SharedGroup.cpp | 41 ++++- .../src/io_realm_internal_SharedGroup.h | 14 +- .../realm-jni/src/io_realm_internal_Util.cpp | 3 + .../src/io_realm_sync_SyncManager.cpp | 114 +++++++++++++ .../realm-jni/src/io_realm_sync_SyncManager.h | 29 ++++ realm/realm-jni/src/util.cpp | 3 + realm/realm-jni/src/util.hpp | 6 + realm/realm-library/build.gradle | 35 +++- .../src/androidTest/AndroidManifest.xml | 2 + .../internal/JNIImplicitTransactionsTest.java | 4 +- .../src/main/java/io/realm/BaseRealm.java | 21 ++- .../java/io/realm/RealmConfiguration.java | 71 ++++++++ .../src/main/java/io/realm/RealmQuery.java | 26 +-- .../java/io/realm/internal/SharedGroup.java | 61 ++++++- .../io/realm/internal/SharedGroupManager.java | 13 +- .../io/realm/internal/SyncSessionImpl.java | 29 ++++ .../realm/internal/async/QueryUpdateTask.java | 5 +- .../java/io/realm/sync/ManualSyncPolicy.java | 8 + .../io/realm/sync/RealtimeSyncPolicy.java | 8 + .../java/io/realm/sync/SyncConfiguration.java | 36 ++++ .../main/java/io/realm/sync/SyncManager.java | 56 ++++++ .../main/java/io/realm/sync/SyncPolicy.java | 5 + .../main/java/io/realm/sync/SyncSession.java | 6 + version.txt | 3 +- 62 files changed, 1428 insertions(+), 93 deletions(-) create mode 100644 integration-tests/sync/.gitignore create mode 100644 integration-tests/sync/README.md create mode 100644 integration-tests/sync/build.gradle create mode 100644 integration-tests/sync/gradle/wrapper/gradle-wrapper.jar create mode 100644 integration-tests/sync/gradle/wrapper/gradle-wrapper.properties create mode 100755 integration-tests/sync/gradlew create mode 100644 integration-tests/sync/gradlew.bat create mode 100644 integration-tests/sync/proguard-rules.pro create mode 100644 integration-tests/sync/src/androidTest/java/io/realm/tests/sync/ProcessCommitTests.java create mode 100644 integration-tests/sync/src/main/AndroidManifest.xml create mode 100644 integration-tests/sync/src/main/java/io/realm/tests/sync/model/ProcessInfo.java create mode 100644 integration-tests/sync/src/main/java/io/realm/tests/sync/service/SendOneCommit.java create mode 100644 integration-tests/sync/src/main/java/io/realm/tests/sync/utils/Constants.java create mode 100644 integration-tests/sync/src/main/java/io/realm/tests/sync/utils/HttpUtils.java create mode 100644 integration-tests/sync/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 integration-tests/sync/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 integration-tests/sync/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 integration-tests/sync/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 integration-tests/sync/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 integration-tests/sync/test_server/package.json create mode 100644 integration-tests/sync/test_server/server.js create mode 100755 integration-tests/sync/test_server/start.sh create mode 100644 realm/realm-jni/src/io_realm_sync_SyncManager.cpp create mode 100644 realm/realm-jni/src/io_realm_sync_SyncManager.h create mode 100644 realm/realm-library/src/main/java/io/realm/internal/SyncSessionImpl.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/ManualSyncPolicy.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/RealtimeSyncPolicy.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/SyncConfiguration.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/SyncManager.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/SyncPolicy.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/SyncSession.java diff --git a/Dockerfile b/Dockerfile index 0029aaebbf..75c1f37b07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,7 @@ RUN DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 \ build-essential \ openjdk-8-jdk-headless \ libc6:i386 libstdc++6:i386 libgcc1:i386 libncurses5:i386 libz1:i386 \ + s3cmd \ && apt-get clean # Install the Android SDK diff --git a/Jenkinsfile b/Jenkinsfile index bd62f90c5e..8998be0d00 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -17,7 +17,9 @@ try { buildEnv.inside("--privileged -v /dev/bus/usb:/dev/bus/usb -v ${env.HOME}/gradle-cache:/root/.gradle -v /root/adbkeys:/root/.android") { stage 'JVM tests' try { - gradle 'assemble check javadoc' + withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { + sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG}" + } } finally { storeJunitResults 'realm/realm-annotations-processor/build/test-results/TEST-*.xml' storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' diff --git a/build.gradle b/build.gradle index fe532a9f65..e59182c5c5 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,9 @@ task assembleRealm(type:GradleBuild) { if (project.hasProperty('buildTargetABIs')) { startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] } + if (project.hasProperty('s3cfg')) { + startParameter.projectProperties += [s3cfg: project.getProperty('s3cfg')] + } } task checkExamples(type:GradleBuild) { @@ -115,6 +118,9 @@ task installRealm(type:GradleBuild) { if (project.hasProperty('buildTargetABIs')) { startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] } + if (project.hasProperty('s3cfg')) { + startParameter.projectProperties += [s3cfg: project.getProperty('s3cfg')] + } } task assembleGradlePlugin(type:GradleBuild) { @@ -353,6 +359,46 @@ task bintrayUpload { dependsOn bintrayTransformer } +task s3Realm(type: GradleBuild) { + description = 'Publish the Realm AAR and AP to the internal S3 maven repository.' + group = 'Publishing' + buildFile = file('realm/build.gradle') + tasks = ['publish'] + if (project.hasProperty('buildTargetABIs')) { + startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] + } +} + +task s3Annotations(type: GradleBuild) { + description = 'Publish the Realm Annotations to the internal S3 maven repository.' + group = 'Publishing' + buildFile = file('realm-annotations/build.gradle') + tasks = ['publish'] +} + +task s3GradlePlugin(type: GradleBuild) { + description = 'Publish the Realm Gradle Plugin to the internal S3 maven repository.' + group = 'Publishing' + buildFile = file('gradle-plugin/build.gradle') + tasks = ['publish'] +} + +task s3Transformer(type: GradleBuild) { + description = 'Publish the Realm Transformer to the internal S3 maven repository.' + group = 'Publishing' + buildFile = file('realm-transformer/build.gradle') + tasks = ['publish'] +} + +task s3Upload { + description = 'Publish all the Realm artifacts to the internal S3 maven repository.' + group = 'Publishing' + dependsOn s3Realm + dependsOn s3Annotations + dependsOn s3GradlePlugin + dependsOn s3Transformer +} + task ojoRealm(type: GradleBuild) { description = 'Publish the Realm AAR and AP SNAPSHOT to Bintray' group = 'Publishing' diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..4f6e35c077 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 2bcb843bee..c839dac532 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -112,6 +112,19 @@ publishing { } } } + repositories { + maven { + credentials(AwsCredentials) { + accessKey project.hasProperty('s3AccessKey') ? s3AccessKey : 'noAccessKey' + secretKey project.hasProperty('s3SecretKey') ? s3SecretKey : 'noSecretKey' + } + if(project.version.endsWith('-SNAPSHOT')) { + url "s3://realm-ci-artifacts/maven/snapshots/" + } else { + url "s3://realm-ci-artifacts/maven/releases/" + } + } + } } bintray { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..4f6e35c077 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle index 272cd2af02..cd4ff9411b 100644 --- a/integration-tests/build.gradle +++ b/integration-tests/build.gradle @@ -15,8 +15,8 @@ allprojects { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' + classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' diff --git a/integration-tests/gradle/wrapper/gradle-wrapper.properties b/integration-tests/gradle/wrapper/gradle-wrapper.properties index 122a0dca2e..473ebd531b 100644 --- a/integration-tests/gradle/wrapper/gradle-wrapper.properties +++ b/integration-tests/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip diff --git a/integration-tests/settings.gradle b/integration-tests/settings.gradle index f804c6689a..1c525e6b59 100644 --- a/integration-tests/settings.gradle +++ b/integration-tests/settings.gradle @@ -1,4 +1,4 @@ -include ':optionalAPIRemoved', ':optionalAPIExists' +include ':optionalAPIRemoved', ':optionalAPIExists', ':sync' rootProject.name = 'integration-tests' diff --git a/integration-tests/sync/.gitignore b/integration-tests/sync/.gitignore new file mode 100644 index 0000000000..cfc15584e2 --- /dev/null +++ b/integration-tests/sync/.gitignore @@ -0,0 +1,3 @@ +/build +/test_server/realm-sync-server +/test_server/node_modules/ diff --git a/integration-tests/sync/README.md b/integration-tests/sync/README.md new file mode 100644 index 0000000000..e9b4862a09 --- /dev/null +++ b/integration-tests/sync/README.md @@ -0,0 +1,28 @@ +# RUNNING THE TESTSERVER + +This document describes how to configure and start the test server used by the integration tests. +This description is only temporary. We should find a better solution. + +## HOW TO + +1. Test server server needs to be started before running the integration test. + +a) Download the matching server version from S3: `s3://ealm-ci-artifacts/sync//cocoa/realm-sync-server-.zip` +b) Extract the files to `./realm-sync-server` + + +2. Start the test server + +a) Run `sh start.sh` + + +# Future plans + +The goal is to have standalone integration tests. + +This means that the test suite should be able to download and run the required server automatically. Also the above +link only points to server binaries for Mac OSX. The tests should run on any platform. + +An initial guess is that we should switch to using the node.js server instead but that still needs to be investigated. +If not we should create a gradle task that automatically downloads, unpacks and runs the Mac OS X server just like +we do for the core file. diff --git a/integration-tests/sync/build.gradle b/integration-tests/sync/build.gradle new file mode 100644 index 0000000000..bc863c60cc --- /dev/null +++ b/integration-tests/sync/build.gradle @@ -0,0 +1,70 @@ +apply plugin: 'com.android.application' +apply plugin: 'realm-android' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.3" + + defaultConfig { + applicationId "io.realm.tests.sync" + minSdkVersion 9 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + tasks.withType(JavaCompile) { + compileTask -> compileTask.dependsOn reverseNodeServerPort, reverseSyncServerPort + } +} + +task reverseNodeServerPort(type: Exec) { + def adb = android.getAdbExe()?.toString() ?: 'false' + commandLine adb, 'reverse', 'tcp:8888', 'tcp:8888' + ignoreExitValue true + doLast { + if (execResult.getExitValue() != 0) { + logger.error( + '===========================================================================\n' + + 'WARNING: Failed to automatically reverse port 8888.\n' + + 'Please reverse this port from localhost to the device or emulator being used to run the application.\n' + + 'You may need to add the appropriate flags to the command that failed:\n' + + ' adb -s DEVICE reverse tcp:8082 tcp:8082\n' + + '===========================================================================\n' + ) + } + } +} + +task reverseSyncServerPort(type: Exec) { + def adb = android.getAdbExe()?.toString() ?: 'false' + commandLine adb, 'reverse', 'tcp:7800', 'tcp:7800' + ignoreExitValue true + doLast { + if (execResult.getExitValue() != 0) { + logger.error( + '===========================================================================\n' + + 'WARNING: Failed to automatically reverse port 7800.\n' + + 'Please reverse this port from localhost to the device or emulator being used to run the application.\n' + + 'You may need to add the appropriate flags to the command that failed:\n' + + ' adb -s DEVICE reverse tcp:7800 tcp:7800\n' + + '===========================================================================\n' + ) + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.squareup.okhttp3:okhttp:3.3.1' + testCompile 'junit:junit:4.12' + androidTestCompile 'com.android.support.test:runner:0.4.1' + androidTestCompile 'com.android.support.test:rules:0.4.1' +} diff --git a/integration-tests/sync/gradle/wrapper/gradle-wrapper.jar b/integration-tests/sync/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ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 H-+%o-(+5aQ literal 0 HcmV?d00001 diff --git a/integration-tests/sync/gradle/wrapper/gradle-wrapper.properties b/integration-tests/sync/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..122a0dca2e --- /dev/null +++ b/integration-tests/sync/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Dec 28 10:00:20 PST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/integration-tests/sync/gradlew b/integration-tests/sync/gradlew new file mode 100755 index 0000000000..9d82f78915 --- /dev/null +++ b/integration-tests/sync/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/integration-tests/sync/gradlew.bat b/integration-tests/sync/gradlew.bat new file mode 100644 index 0000000000..aec99730b4 --- /dev/null +++ b/integration-tests/sync/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/integration-tests/sync/proguard-rules.pro b/integration-tests/sync/proguard-rules.pro new file mode 100644 index 0000000000..740907a636 --- /dev/null +++ b/integration-tests/sync/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/Nabil/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/integration-tests/sync/src/androidTest/java/io/realm/tests/sync/ProcessCommitTests.java b/integration-tests/sync/src/androidTest/java/io/realm/tests/sync/ProcessCommitTests.java new file mode 100644 index 0000000000..90e8b87c88 --- /dev/null +++ b/integration-tests/sync/src/androidTest/java/io/realm/tests/sync/ProcessCommitTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.tests.sync; + +import android.content.Context; +import android.content.Intent; +import android.os.Looper; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import io.realm.Realm; +import io.realm.RealmChangeListener; +import io.realm.RealmConfiguration; +import io.realm.RealmResults; +import io.realm.tests.sync.model.ProcessInfo; +import io.realm.tests.sync.service.SendOneCommit; +import io.realm.tests.sync.utils.Constants; +import io.realm.tests.sync.utils.HttpUtils; + +import static org.junit.Assert.assertNotEquals; + +@RunWith(AndroidJUnit4.class) +public class ProcessCommitTests { + HttpUtils httpUtils = new HttpUtils(); + + @Before + public void setUp () throws Exception { + httpUtils.startSyncServer(); + } + + @After + public void tearDown () throws Exception { + httpUtils.stopSyncServer(); + } + + @Test + public void expectServerCommit() throws Exception { + final CountDownLatch testFinished = new CountDownLatch(1); + ExecutorService service = Executors.newSingleThreadExecutor(); + service.submit(new Runnable() { + @Override + public void run() { + try { + Looper.prepare(); + Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final RealmConfiguration syncConfig = new RealmConfiguration + .Builder(targetContext) + .name("main_process") + .withSync(Constants.SYNC_SERVER_URL) + .syncUserToken(Constants.USER_TOKEN) + .build(); + final Realm realm = Realm.getInstance(syncConfig); + Intent intent = new Intent(targetContext, SendOneCommit.class); + targetContext.startService(intent); + + final RealmResults all = realm.where(ProcessInfo.class).findAll(); + all.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults element) { + assertNotEquals(0, all.size()); + testFinished.countDown(); + } + }); + + Looper.loop(); + + } catch (Throwable e) { + e.printStackTrace(); + } + } + }); + testFinished.await(10, TimeUnit.SECONDS); + } +} diff --git a/integration-tests/sync/src/main/AndroidManifest.xml b/integration-tests/sync/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..0c2acd3b67 --- /dev/null +++ b/integration-tests/sync/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/integration-tests/sync/src/main/java/io/realm/tests/sync/model/ProcessInfo.java b/integration-tests/sync/src/main/java/io/realm/tests/sync/model/ProcessInfo.java new file mode 100644 index 0000000000..d39312cca5 --- /dev/null +++ b/integration-tests/sync/src/main/java/io/realm/tests/sync/model/ProcessInfo.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.tests.sync.model; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + +public class ProcessInfo extends RealmObject { + private String name; + private int pid; + private long threadId; + + public int getPid() { + return pid; + } + + public void setPid(int pid) { + this.pid = pid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getThreadId() { + return threadId; + } + + public void setThreadId(long threadId) { + this.threadId = threadId; + } +} \ No newline at end of file diff --git a/integration-tests/sync/src/main/java/io/realm/tests/sync/service/SendOneCommit.java b/integration-tests/sync/src/main/java/io/realm/tests/sync/service/SendOneCommit.java new file mode 100644 index 0000000000..db408d2749 --- /dev/null +++ b/integration-tests/sync/src/main/java/io/realm/tests/sync/service/SendOneCommit.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.tests.sync.service; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.tests.sync.model.ProcessInfo; +import io.realm.tests.sync.utils.Constants; + +/** + * Open a sync Realm on a different process, then send one commit. + */ +public class SendOneCommit extends Service { + + @Override + public void onCreate() { + super.onCreate(); + final RealmConfiguration syncConfig = new RealmConfiguration + .Builder(this) + .name(SendOneCommit.class.getSimpleName()) + .withSync(Constants.SYNC_SERVER_URL) + .syncUserToken(Constants.USER_TOKEN) + .build(); + Realm realm = Realm.getInstance(syncConfig); + + realm.beginTransaction(); + ProcessInfo processInfo = realm.createObject(ProcessInfo.class); + processInfo.setName("Background"); + processInfo.setPid(android.os.Process.myPid()); + processInfo.setThreadId(Thread.currentThread().getId()); + realm.commitTransaction(); + + realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset + } + + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/Constants.java b/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/Constants.java new file mode 100644 index 0000000000..540b919eea --- /dev/null +++ b/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/Constants.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.tests.sync.utils; + +public class Constants { + // to generate a valid token run this script + // https://realmio.slack.com/files/af/F1FSVND47/generate-realm-sync-credentials.sh + public static String USER_TOKEN = "ewoJImlkZW50aXR5IjogIk5hYmlsIiwKCSJhY2Nlc3MiOiBbInVwbG9hZCIsICJkb3dubG9hZCJdLAoJImFwcF9pZCI6ICJpby5yZWFsbS50ZXN0cyIKfQo=:"; + public static String SYNC_SERVER_URL = "realm://127.0.0.1:7800/public/tests"; +} diff --git a/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/HttpUtils.java b/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/HttpUtils.java new file mode 100644 index 0000000000..1c77760837 --- /dev/null +++ b/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/HttpUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.tests.sync.utils; + +import java.io.IOException; + +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Start and Stop the node server responsible of creating a + * temp directory & start a sync server on it for each unit test. + */ +public class HttpUtils { + private final OkHttpClient client = new OkHttpClient(); + // adb reverse tcp:8888 tcp:8888 + // will forward this query to the host, running the integration test server on 8888 + private final static String START_SERVER = "http://127.0.0.1:8888/start"; + private final static String STOP_SERVER = "http://127.0.0.1:8888/stop"; + + public void startSyncServer() throws Exception { + Request request = new Request.Builder() + .url(START_SERVER) + .build(); + + Response response = client.newCall(request).execute(); + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + Headers responseHeaders = response.headers(); + for (int i = 0; i < responseHeaders.size(); i++) { + System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); + } + + System.out.println(response.body().string()); + } + + public void stopSyncServer() throws Exception { + Request request = new Request.Builder() + .url(STOP_SERVER) + .build(); + + Response response = client.newCall(request).execute(); + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + Headers responseHeaders = response.headers(); + for (int i = 0; i < responseHeaders.size(); i++) { + System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); + } + + System.out.println(response.body().string()); + } +} diff --git a/integration-tests/sync/src/main/res/mipmap-hdpi/ic_launcher.png b/integration-tests/sync/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..cde69bcccec65160d92116f20ffce4fce0b5245c GIT binary patch literal 3418 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 literal 0 HcmV?d00001 diff --git a/integration-tests/sync/src/main/res/mipmap-xhdpi/ic_launcher.png b/integration-tests/sync/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..bfa42f0e7b91d006d22352c9ff2f134e504e3c1d GIT binary patch literal 4842 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 literal 0 HcmV?d00001 diff --git a/integration-tests/sync/src/main/res/mipmap-xxhdpi/ic_launcher.png b/integration-tests/sync/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..324e72cdd7480cb983fa1bcc7ce686e51ef87fe7 GIT binary patch literal 7718 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 literal 0 HcmV?d00001 diff --git a/integration-tests/sync/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/integration-tests/sync/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..aee44e138434630332d88b1680f33c4b24c70ab3 GIT binary patch literal 10486 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 { + console.log(`stdout: ${data}`); + }); + + syncServerChildProcess.stderr.on('data', (data) => { + console.log(`stderr: ${data}`); + }); + + syncServerChildProcess.on('close', (code) => { + console.log(`child process exited with code ${code}`); + }); + } + }); + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Starting a server'); +}); + +// stop a previously started sync server +dispatcher.onGet("/stop", function(req, res) { + syncServerChildProcess.kill(); + temp.cleanupSync(); + // Do work + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Stopping the server'); +}); + +//Create and start the Http server +var server = http.createServer(handleRequest); +server.listen(PORT, function() { + console.log("Integration test server listening on: 127.0.0.1:%s", PORT); +}); diff --git a/integration-tests/sync/test_server/start.sh b/integration-tests/sync/test_server/start.sh new file mode 100755 index 0000000000..48d8ba32b2 --- /dev/null +++ b/integration-tests/sync/test_server/start.sh @@ -0,0 +1,2 @@ +npm install +node server.js ./realm-sync-server diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index 5fd63be38b..a372797de3 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -54,6 +54,19 @@ publishing { } } } + repositories { + maven { + credentials(AwsCredentials) { + accessKey project.hasProperty('s3AccessKey') ? s3AccessKey : 'noAccessKey' + secretKey project.hasProperty('s3SecretKey') ? s3SecretKey : 'noSecretKey' + } + if(project.version.endsWith('-SNAPSHOT')) { + url "s3://realm-ci-artifacts/maven/snapshots/" + } else { + url "s3://realm-ci-artifacts/maven/releases/" + } + } + } } bintray { diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index e7ebc88668..29a78de9f3 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -106,6 +106,19 @@ publishing { } } } + repositories { + maven { + credentials(AwsCredentials) { + accessKey project.hasProperty('s3AccessKey') ? s3AccessKey : 'noAccessKey' + secretKey project.hasProperty('s3SecretKey') ? s3SecretKey : 'noSecretKey' + } + if(project.version.endsWith('-SNAPSHOT')) { + url "s3://realm-ci-artifacts/maven/snapshots/" + } else { + url "s3://realm-ci-artifacts/maven/releases/" + } + } + } } bintray { diff --git a/realm/build.gradle b/realm/build.gradle index 44d2ab9c3f..1c4cdac2a9 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -6,16 +6,16 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.1.2' classpath 'de.undercouch:gradle-download-task:2.0.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4' classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.3.0' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.2' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0' classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.0.1' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" } } diff --git a/realm/gradle.properties b/realm/gradle.properties index 033fd72f9a..f3f16fcaac 100644 --- a/realm/gradle.properties +++ b/realm/gradle.properties @@ -1 +1 @@ -org.gradle.jvmargs=-Xms256m -Xmx2048m +org.gradle.jvmargs=-Xms512m -Xmx2048m diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..4f6e35c077 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index b6ca11d063..81e854e72d 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -74,6 +74,19 @@ publishing { } } } + repositories { + maven { + credentials(AwsCredentials) { + accessKey project.hasProperty('s3AccessKey') ? s3AccessKey : 'noAccessKey' + secretKey project.hasProperty('s3SecretKey') ? s3SecretKey : 'noSecretKey' + } + if(project.version.endsWith('-SNAPSHOT')) { + url "s3://realm-ci-artifacts/maven/snapshots/" + } else { + url "s3://realm-ci-artifacts/maven/releases/" + } + } + } } bintray { diff --git a/realm/realm-jni/build.gradle b/realm/realm-jni/build.gradle index eb897993a2..6add45d845 100644 --- a/realm/realm-jni/build.gradle +++ b/realm/realm-jni/build.gradle @@ -1,8 +1,8 @@ import java.security.MessageDigest -ext.coreVersion = '1.4.2' +ext.coreVersion = '0.27.0' // Sync version // empty or comment out this to disable hash checking -ext.coreSha256Hash = '1dfa2b4852b1cfccb18fd1a164cfeeea2b84f8de03f1024594837ca54840665c' +ext.coreSha256Hash = '21687bfb3bff99ef4cb6b2846bf8031bf41e9e4c4f82ef38346ff18c45be4620' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // gcc is default for the NDK. It also produces smaller binaries @@ -19,8 +19,10 @@ ext.coreArchiveDir = System.getenv("REALM_CORE_DOWNLOAD_DIR") // target ABIs to build(null means all). // To obtain the ABI of the connected device, execute "adb shell getprop ro.product.cpu.abi" ext.buildTargetAbis = project.hasProperty('buildTargetABIs') ? project.getProperty('buildTargetABIs').split(',').collect {it.trim()} : null +// The location of the s3cfg used when running s3cmd +ext.s3cfg = project.hasProperty('s3cfg') ? project.getProperty('s3cfg') : null; -def commonCflags = [ '-Os', '-std=c++11', '-Wmissing-declarations' , '-Werror'] +def commonCflags = [ '-Os', '-std=c++14', '-Wmissing-declarations' , '-Werror'] // LTO and debugging don't play well together if (!ext.debugBuild) { commonCflags += [ '-fvisibility=hidden', '-ffunction-sections', '-fdata-sections', '-flto' ] @@ -75,7 +77,8 @@ def toolchains = [ ] def allTargets = [ - new Target( name:'arm', abi:'armeabi', toolchain:toolchains.find {it.name == 'arm'}, cflags:[ '-mthumb' ] ), +// ARM support is deprecated +// new Target( name:'arm', abi:'armeabi', toolchain:toolchains.find {it.name == 'arm'}, cflags:[ '-mthumb' ] ), new Target( name:'arm-v7a', abi:'armeabi-v7a', toolchain:toolchains.find {it.name == 'arm'}, cflags:[ '-mthumb', '-march=armv7-a', '-mfloat-abi=softfp', '-mfpu=vfpv3-d16' ] ), new Target( name:'arm64', abi:'arm64-v8a', toolchain:toolchains.find {it.name == 'arm64'}, cflags:[] ), new Target( name:'mips', abi:'mips', toolchain:toolchains.find {it.name == 'mips'}, cflags:[] ), @@ -152,13 +155,15 @@ ext.coreDir = file("${buildDir}/core-${project.coreVersion}") def coreDownloaded = false -task downloadCore(group: 'build setup', description: 'Download the latest version of realm core') { +task downloadCore() { + group = 'build setup' + description = 'Download the latest version of realm core' def isHashCheckingEnabled = { return project.hasProperty('coreSha256Hash') && !project.coreSha256Hash.empty } def calcSha256Hash = {File targetFile -> - MessageDigest sha = MessageDigest.getInstance("SHA-256"); + MessageDigest sha = MessageDigest.getInstance("SHA-256") Formatter hexHash = new Formatter() sha.digest(targetFile.bytes).each { b -> hexHash.format('%02x', b) } return hexHash.toString() @@ -188,13 +193,14 @@ task downloadCore(group: 'build setup', description: 'Download the latest versio doLast { if (shouldDownloadCore()) { - download { - src "http://static.realm.io/downloads/core/realm-core-android-${project.coreVersion}.tar.gz" - dest project.coreArchiveFile - onlyIfNewer false + // CI artifacts are only available if on the internal network or VPN + def downloadUrl = "s3://realm-ci-artifacts/sync/${project.coreVersion}/android/sync-core-${project.coreVersion}.tar.gz" + + println "Downloading ${downloadUrl}" + exec { + commandLine 's3cmd', '-c', project.s3cfg, '-f', 'get', "${downloadUrl}", "${project.coreArchiveFile}" } coreDownloaded = true - if (isHashCheckingEnabled()) { def calculatedHash = calcSha256Hash(project.coreArchiveFile) if (!project.coreSha256Hash.equalsIgnoreCase(calculatedHash)) { @@ -303,7 +309,7 @@ targets.each { target -> "REALM_CFLAGS_COMMON=-Wno-variadic-macros -DREALM_HAVE_CONFIG -DPIC -I${project.coreDir}/include", "CFLAGS_ARCH=${(commonCflags + target.cflags).join(' ')}", "BASE_DENOM=${target.name}", - "REALM_LDFLAGS_COMMON=-lrealm-android-${target.name} -lstdc++ -lsupc++ -llog -L${project.coreDir} -Wl,--gc-sections -Wl,-soname,librealm-jni.so", + "REALM_LDFLAGS_COMMON=-lrealm-sync-android-${target.name} -lrealm-android-${target.name} -lstdc++ -lsupc++ -llog -lz -L${project.coreDir} -Wl,--gc-sections -Wl,-soname,librealm-jni.so", 'LIB_SUFFIX_SHARED=.so', "librealm-jni-${target.name}${getDebugExt()}.so" ] diff --git a/realm/realm-jni/src/io_realm_internal_SharedGroup.cpp b/realm/realm-jni/src/io_realm_internal_SharedGroup.cpp index 229d9a540e..432f3112f2 100644 --- a/realm/realm-jni/src/io_realm_internal_SharedGroup.cpp +++ b/realm/realm-jni/src/io_realm_internal_SharedGroup.cpp @@ -17,16 +17,24 @@ #include #include "util.hpp" - #include #include #include -#include "util.hpp" +#include +#include +#include #include "io_realm_internal_SharedGroup.h" +#include +#include +#include +#include +#include +#include using namespace std; using namespace realm; +using namespace sync; inline static bool jint_to_durability_level(JNIEnv* env, jint durability, SharedGroup::DurabilityLevel &level) { if (durability == 0) @@ -114,7 +122,9 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_createNativeWithImpli return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreateReplication + + +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreateLocalReplication (JNIEnv* env, jobject, jstring jfile_name, jbyteArray keyArray) { TR_ENTER() @@ -135,6 +145,22 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreateReplicati return 0; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreateSyncReplication + (JNIEnv* env, jobject, jstring jfile_name) +{ + TR_ENTER() + StringData file_name; + try { + JStringAccessor file_name_tmp(env, jfile_name); // throws + file_name = StringData(file_name_tmp); + std::unique_ptr hist = realm::sync::make_sync_history(file_name); + return reinterpret_cast(hist.release()); + } + CATCH_FILE(file_name) + CATCH_STD() + return 0; +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeBeginImplicit (JNIEnv* env, jobject, jlong native_ptr) { @@ -179,11 +205,15 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativePromoteToWrite } JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeCommitAndContinueAsRead - (JNIEnv *env, jobject, jlong native_ptr) + (JNIEnv *env, jobject, jlong native_ptr, jlong sync_session_ptr) { TR_ENTER_PTR(native_ptr) + Session* sync_session = SS(sync_session_ptr); try { - LangBindHelper::commit_and_continue_as_read( *SG(native_ptr) ); + SharedGroup::version_type new_version = LangBindHelper::commit_and_continue_as_read( *SG(native_ptr) ); + if (sync_session != NULL) { //sync enabled + sync_session->nonsync_transact_notify(new_version); + } } CATCH_STD() } @@ -327,3 +357,4 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeStopWaitForChang SG(native_ptr)->wait_for_change_release(); } CATCH_STD() } + diff --git a/realm/realm-jni/src/io_realm_internal_SharedGroup.h b/realm/realm-jni/src/io_realm_internal_SharedGroup.h index 2e0dd9d035..226c920c59 100644 --- a/realm/realm-jni/src/io_realm_internal_SharedGroup.h +++ b/realm/realm-jni/src/io_realm_internal_SharedGroup.h @@ -29,19 +29,27 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_createNativeWithImpli /* * Class: io_realm_internal_SharedGroup - * Method: nativeCreateReplication + * Method: nativeCreateLocalReplication * Signature: (Ljava/lang/String;[B)J */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreateReplication +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreateLocalReplication (JNIEnv *, jobject, jstring, jbyteArray); +/* + * Class: io_realm_internal_SharedGroup + * Method: nativeCreateSyncReplication + * Signature: (Ljava/lang/String;)J + */ +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreateSyncReplication + (JNIEnv *, jobject, jstring); + /* * Class: io_realm_internal_SharedGroup * Method: nativeCommitAndContinueAsRead * Signature: (J)V */ JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeCommitAndContinueAsRead - (JNIEnv *, jobject, jlong); + (JNIEnv *, jobject, jlong, jlong); /* * Class: io_realm_internal_SharedGroup diff --git a/realm/realm-jni/src/io_realm_internal_Util.cpp b/realm/realm-jni/src/io_realm_internal_Util.cpp index 49eb55f255..14cc9a5c3a 100644 --- a/realm/realm-jni/src/io_realm_internal_Util.cpp +++ b/realm/realm-jni/src/io_realm_internal_Util.cpp @@ -44,6 +44,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) return JNI_ERR; } else { + g_vm = vm; // Loading classes and constructors for later use - used by box typed fields and a few methods' return value java_lang_long = GetClass(env, "java/lang/Long"); java_lang_long_init = env->GetMethodID(java_lang_long, "", "(J)V"); @@ -51,6 +52,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); java_lang_double = GetClass(env, "java/lang/Double"); java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); + sync_manager = GetClass(env, "io/realm/sync/SyncManager"); + sync_manager_notify_handler = env->GetStaticMethodID(sync_manager, "notifyHandlers", "(Ljava/lang/String;)V"); } return JNI_VERSION_1_6; diff --git a/realm/realm-jni/src/io_realm_sync_SyncManager.cpp b/realm/realm-jni/src/io_realm_sync_SyncManager.cpp new file mode 100644 index 0000000000..d47dca5c90 --- /dev/null +++ b/realm/realm-jni/src/io_realm_sync_SyncManager.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 + +#include "io_realm_sync_SyncManager.h" +#include "util.hpp" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace realm; +using namespace sync; + +class AndroidLogger: public realm::util::RootLogger +{ +public: + void do_log(std::string msg) + { + __android_log_print(ANDROID_LOG_INFO, "[SYNC]", "> %s", msg.c_str()); + } +}; + +// maintain a reference to the threads allocated dynamically, to prevent deallocation +// after Java_io_realm_internal_SharedGroup_nativeStartSession completes. +// To be released later, maybe on JNI_OnUnload +std::thread* sync_client_thread; +JNIEnv* sync_client_env; + +JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateClient + (JNIEnv *env, jclass) +{ + TR_ENTER() + try { + AndroidLogger* base_logger = new AndroidLogger();//FIXME find a way to delete it when we delete the client + + sync::Client::Config config; + config.logger = base_logger; + config.reconnect = sync::Client::Reconnect::immediately; + + sync::Client* m_sync_client = new sync::Client(config); + sync_client_thread = new std::thread([m_sync_client](){ + //Attaching thread to Java so we can perform JNI calls + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_6; + args.name = NULL; // java thread a name + args.group = NULL; // java thread group + g_vm->AttachCurrentThread(&sync_client_env, &args); + + m_sync_client->run(); + }); + + return reinterpret_cast(m_sync_client); + + } CATCH_STD() + return 0; +} + +JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateSession + (JNIEnv *env, jclass, jlong clientPointer, jstring realmPath, jstring serverUrl, jstring userToken) +{ + TR_ENTER() + Client* sync_client = SC(clientPointer); + if (sync_client == NULL) { + return 0; + } + try { + const char *token_tmp = env->GetStringUTFChars(userToken, NULL); + std::string user_token(token_tmp); + env->ReleaseStringUTFChars(userToken, token_tmp); + + const char *path_tmp = env->GetStringUTFChars(realmPath, NULL); + std::string path(path_tmp); + env->ReleaseStringUTFChars(realmPath, path_tmp); + + JStringAccessor server_url_tmp(env, serverUrl); // throws + StringData server_url = StringData(server_url_tmp); + + Session* sync_session = new Session(*sync_client, path); + + std::function sync_transact_callback = [path](Session::version_type) { + sync_client_env->CallStaticVoidMethod(sync_manager, sync_manager_notify_handler, sync_client_env->NewStringUTF(path.c_str()));//REALM_CHANGE + }; + sync_session->set_sync_transact_callback(sync_transact_callback); + sync_session->bind(server_url, user_token); + return reinterpret_cast(sync_session); + } CATCH_STD() + return 0; +} + diff --git a/realm/realm-jni/src/io_realm_sync_SyncManager.h b/realm/realm-jni/src/io_realm_sync_SyncManager.h new file mode 100644 index 0000000000..71c3a2b3ce --- /dev/null +++ b/realm/realm-jni/src/io_realm_sync_SyncManager.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class io_realm_sync_SyncManager */ + +#ifndef _Included_io_realm_sync_SyncManager +#define _Included_io_realm_sync_SyncManager +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: io_realm_sync_SyncManager + * Method: syncCreateClient + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateClient + (JNIEnv *, jclass); + +/* + * Class: io_realm_sync_SyncManager + * Method: syncCreateSession + * Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)J + */ +JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateSession + (JNIEnv *, jclass, jlong, jstring, jstring, jstring); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/realm/realm-jni/src/util.cpp b/realm/realm-jni/src/util.cpp index 6d8a7dd58a..2a1051a89c 100644 --- a/realm/realm-jni/src/util.cpp +++ b/realm/realm-jni/src/util.cpp @@ -29,12 +29,15 @@ using namespace realm; using namespace realm::util; // Caching classes and constructors for boxed types. +JavaVM* g_vm; jclass java_lang_long; jmethodID java_lang_long_init; jclass java_lang_float; jmethodID java_lang_float_init; jclass java_lang_double; jmethodID java_lang_double_init; +jclass sync_manager; +jmethodID sync_manager_notify_handler; void ConvertException(JNIEnv* env, const char *file, int line) { diff --git a/realm/realm-jni/src/util.hpp b/realm/realm-jni/src/util.hpp index 947b0dfae9..b3d896cde9 100644 --- a/realm/realm-jni/src/util.hpp +++ b/realm/realm-jni/src/util.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "io_realm_internal_Util.h" @@ -112,6 +113,8 @@ std::string num_to_string(T pNumber) #define SG(ptr) reinterpret_cast(ptr) #define CH(ptr) reinterpret_cast(ptr) #define HO(T, ptr) reinterpret_cast* >(ptr) +#define SC(ptr) reinterpret_cast(ptr) +#define SS(ptr) reinterpret_cast(ptr) // Exception handling enum ExceptionKind { @@ -647,12 +650,15 @@ class JniBooleanArray { jint m_releaseMode; }; +extern JavaVM* g_vm; extern jclass java_lang_long; extern jmethodID java_lang_long_init; extern jclass java_lang_float; extern jmethodID java_lang_float_init; extern jclass java_lang_double; extern jmethodID java_lang_double_init; +extern jclass sync_manager; +extern jmethodID sync_manager_notify_handler; inline jobject NewLong(JNIEnv* env, int64_t value) { diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 7bd8082fba..21fee02c64 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -217,6 +217,32 @@ install { } } +publishing { + publications { + realmPublication(MavenPublication) { + groupId 'io.realm' + artifactId 'realm-android-library' + version project.version + artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-release.aar") + artifact sourcesJar + artifact javadocJar + } + } + repositories { + maven { + credentials(AwsCredentials) { + accessKey project.hasProperty('s3AccessKey') ? s3AccessKey : 'noAccessKey' + secretKey project.hasProperty('s3SecretKey') ? s3SecretKey : 'noSecretKey' + } + if(project.version.endsWith('-SNAPSHOT')) { + url "s3://realm-ci-artifacts/maven/snapshots/" + } else { + url "s3://realm-ci-artifacts/maven/releases/" + } + } + } +} + bintray { user = project.hasProperty('bintrayUser') ? bintrayUser : 'noUser' key = project.hasProperty('bintrayKey') ? bintrayKey : 'noKey' @@ -224,7 +250,7 @@ bintray { dryRun = false publish = false - configurations = ['archives'] + configurations = ['realmPublication'] pkg { repo = 'maven' @@ -249,14 +275,9 @@ artifactory { maven = true } defaults { - publishConfigs('archives') + publishConfigs('realmPublication') publishPom = true publishIvy = false } } } - -artifacts { - archives javadocJar - archives sourcesJar -} diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index 357f8f7ef8..a44988ce92 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -3,6 +3,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> + + diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIImplicitTransactionsTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIImplicitTransactionsTest.java index bbfcbbf011..a599eeb5e5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIImplicitTransactionsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIImplicitTransactionsTest.java @@ -46,7 +46,7 @@ private void deleteFile() { public void testImplicitTransactions() { deleteFile(); - SharedGroup sg = new SharedGroup(testFile, true, SharedGroup.Durability.FULL, null); // TODO: try with encryption + SharedGroup sg = new SharedGroup(testFile, true, false, SharedGroup.Durability.FULL, null); // TODO: try with encryption // Create a table WriteTransaction wt = sg.beginWrite(); @@ -76,7 +76,7 @@ public void testImplicitTransactions() { public void testCannotUseClosedImplicitTransaction() { deleteFile(); - SharedGroup sg = new SharedGroup(testFile, true, SharedGroup.Durability.FULL, null); + SharedGroup sg = new SharedGroup(testFile, true, false, SharedGroup.Durability.FULL, null); WriteTransaction wt = sg.beginWrite(); if (!wt.hasTable("test")) { Table table = wt.getTable("test"); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 6565dbae6e..9c9bfd8862 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -52,7 +52,7 @@ * @see io.realm.DynamicRealm */ @SuppressWarnings("WeakerAccess") -abstract class BaseRealm implements Closeable { +public abstract class BaseRealm implements Closeable { protected static final long UNVERSIONED = -1; private static final String INCORRECT_THREAD_CLOSE_MESSAGE = "Realm access from incorrect thread. Realm instance can only be closed on the thread it was created."; @@ -64,17 +64,17 @@ abstract class BaseRealm implements Closeable { "Changing Realm data can only be done from inside a transaction."; // Map between a Handler and the canonical path to a Realm file - protected static final Map handlers = new ConcurrentHashMap(); + public static final Map handlers = new ConcurrentHashMap(); // Thread pool for all async operations (Query & transaction) static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); final long threadId; protected RealmConfiguration configuration; - protected SharedGroupManager sharedGroupManager; + public SharedGroupManager sharedGroupManager; RealmSchema schema; - Handler handler; - HandlerController handlerController; + public Handler handler; + public HandlerController handlerController; static { //noinspection ConstantConditions @@ -84,9 +84,9 @@ abstract class BaseRealm implements Closeable { protected BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; + this.handlerController = new HandlerController(this); this.sharedGroupManager = new SharedGroupManager(configuration); this.schema = new RealmSchema(this, sharedGroupManager.getTransaction()); - this.handlerController = new HandlerController(this); if (handlerController.isAutoRefreshAvailable()) { setAutoRefresh(true); @@ -105,7 +105,14 @@ protected BaseRealm(RealmConfiguration configuration) { * @throws IllegalStateException if called from a non-Looper thread. */ public void setAutoRefresh(boolean autoRefresh) { - checkIfValid(); + setAutoRefresh(autoRefresh, true); + } + + private void setAutoRefresh(boolean autoRefresh, boolean performCheck) { + if (performCheck) { + checkIfValid(); + } + handlerController.checkCanBeAutoRefreshed(); if (autoRefresh && !handlerController.isAutoRefreshEnabled()) { // Switch it on handler = new Handler(handlerController); diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 4f7a8c031a..2952dfff2d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -17,6 +17,7 @@ package io.realm; import android.content.Context; +import android.os.Handler; import android.text.TextUtils; import java.io.File; @@ -25,6 +26,8 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -97,6 +100,8 @@ public final class RealmConfiguration { private final RxObservableFactory rxObservableFactory; private final Realm.Transaction initialDataTransaction; private final WeakReference contextWeakRef; + private final String syncServerUrl; + private final String syncUserToken; private RealmConfiguration(Builder builder) { this.realmFolder = builder.folder; @@ -112,6 +117,8 @@ private RealmConfiguration(Builder builder) { this.rxObservableFactory = builder.rxFactory; this.initialDataTransaction = builder.initialDataTransaction; this.contextWeakRef = builder.contextWeakRef; + this.syncServerUrl = builder.syncServerUrl; + this.syncUserToken = builder.syncUserToken; } public File getRealmFolder() { @@ -213,6 +220,33 @@ public RxObservableFactory getRxFactory() { return rxObservableFactory; } + /** + * Checks if server side synchronization is enabled for this Realm. + * + * @return {@code true} if synchronisation is enabled, {@code false} otherwise. + */ + public boolean isSyncEnabled() { + return syncServerUrl != null && syncServerUrl.length() > 0; + } + + /** + * Returns the server side URL used to sync this Realm across devices. + * + * @return URL of the Realm Sync server. + */ + public String getSyncServerUrl() { + return syncServerUrl; + } + + /** + * Returns the predefined user token for server side synchronization or {@code null} if no user is predefined. + * + * @return The user token for Realm Sync of {@code null} if no token is defined. + */ + public String getSyncUserToken() { + return syncUserToken; + } + @Override public boolean equals(Object obj) { if (this == obj) return true; @@ -231,9 +265,18 @@ public boolean equals(Object obj) { //noinspection SimplifiableIfStatement if (rxObservableFactory != null ? !rxObservableFactory.equals(that.rxObservableFactory) : that.rxObservableFactory != null) return false; if (initialDataTransaction != null ? !initialDataTransaction.equals(that.initialDataTransaction) : that.initialDataTransaction != null) return false; + if (syncServerUrl == null ? that.syncServerUrl != null : !syncServerUrl.equals(that.syncServerUrl)) { + return false; + } + if (syncUserToken == null ? that.syncUserToken != null : !syncUserToken.equals(that.syncUserToken)) { + return false; + } + return schemaMediator.equals(that.schemaMediator); } + + @Override public int hashCode() { int result = realmFolder.hashCode(); @@ -247,6 +290,8 @@ public int hashCode() { result = 31 * result + durability.hashCode(); result = 31 * result + (rxObservableFactory != null ? rxObservableFactory.hashCode() : 0); result = 31 * result + (initialDataTransaction != null ? initialDataTransaction.hashCode() : 0); + result = 31 * result + (syncServerUrl != null ? syncServerUrl.hashCode() : 0); + result = 31 * result + (syncUserToken != null ? syncUserToken.hashCode() : 0); return result; } @@ -359,6 +404,8 @@ public static final class Builder { private WeakReference contextWeakRef; private RxObservableFactory rxFactory; private Realm.Transaction initialDataTransaction; + private String syncServerUrl; + private String syncUserToken; /** * Creates an instance of the Builder for the RealmConfiguration. @@ -611,6 +658,30 @@ public Builder assetFile(Context context, final String assetFile) { return this; } + /** + * Enable server side synchronization for this Realm. The URL should point to an endpoint exposed by a + * Realm Sync server. + * + * @param serverUrl Realm Sync server url. + */ + public Builder withSync(String serverUrl) { + syncServerUrl = serverUrl; + return this; + } + + /** + * Sets the default user token to be used with Realm Sync. + * + * @param userToken User token identifying the user connecting to Realm Sync. + */ + public Builder syncUserToken(String userToken) { + if (userToken == null || userToken.equals("")) { + throw new IllegalArgumentException("Non-empty user token required"); + } + syncUserToken = userToken; + return this; + } + private void addModule(Object module) { if (module != null) { checkModule(module); diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 7f0494b826..529e0adb3f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1170,10 +1170,7 @@ public Long call() throws Exception { SharedGroup sharedGroup = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedGroup = new SharedGroup(realmConfiguration); long handoverTableViewPointer = query. findDistinctWithHandover(sharedGroup.getNativePointer(), @@ -1478,10 +1475,7 @@ public Long call() throws Exception { SharedGroup sharedGroup = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedGroup = new SharedGroup(realmConfiguration); // Run the query & handover the table view for the caller thread // Note: the handoverQueryPointer contains the versionID needed by the SG in order @@ -1596,11 +1590,7 @@ public Long call() throws Exception { SharedGroup sharedGroup = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); - + sharedGroup = new SharedGroup(realmConfiguration); long columnIndex = getColumnIndexForSort(fieldName); // run the query & handover the table view for the caller thread @@ -1771,10 +1761,7 @@ public Long call() throws Exception { SharedGroup sharedGroup = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedGroup = new SharedGroup(realmConfiguration); // run the query & handover the table view for the caller thread long handoverTableViewPointer = query.findAllMultiSortedWithHandover(sharedGroup.getNativePointer(), @@ -1911,10 +1898,7 @@ public Long call() throws Exception { SharedGroup sharedGroup = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedGroup = new SharedGroup(realmConfiguration); long handoverRowPointer = query.findWithHandover(sharedGroup.getNativePointer(), sharedGroup.getNativeReplicationPointer(), handoverQueryPointer); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedGroup.java b/realm/realm-library/src/main/java/io/realm/internal/SharedGroup.java index da20dd4114..378939aa2f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedGroup.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedGroup.java @@ -20,11 +20,13 @@ import java.io.IOError; import java.util.concurrent.TimeUnit; +import io.realm.RealmConfiguration; import io.realm.exceptions.IncompatibleLockFileException; import io.realm.exceptions.RealmError; import io.realm.exceptions.RealmIOException; import io.realm.internal.async.BadVersionException; import io.realm.internal.log.RealmLog; +import io.realm.sync.SyncManager; public class SharedGroup implements Closeable { @@ -43,6 +45,8 @@ public class SharedGroup implements Closeable { private final String path; private long nativePtr; +// private long syncClientPtr = 0;s + private long sessionPtr = 0; private long nativeReplicationPtr; private boolean implicitTransactionsEnabled = false; private boolean activeTransaction; @@ -68,10 +72,47 @@ public SharedGroup(String databaseFile) { checkNativePtrNotZero(); } - public SharedGroup(String canonicalPath, boolean enableImplicitTransactions, Durability durability, byte[] key) { + /** + * Constructs a new shared group using implicit transactions. + * + * @param config RealmConfiguration to create the SharedGroup for. + */ + public SharedGroup(RealmConfiguration config) { + String canonicalPath = config.getPath(); + boolean syncEnabled = config.isSyncEnabled(); + byte[] encryptionKey = config.getEncryptionKey(); + Durability durability = config.getDurability(); + + if (syncEnabled) { + nativeReplicationPtr = nativeCreateSyncReplication(canonicalPath); + } else { + nativeReplicationPtr = nativeCreateLocalReplication(canonicalPath, encryptionKey); + } + nativePtr = openSharedGroupOrFail(nativeReplicationPtr, canonicalPath, durability, encryptionKey); + implicitTransactionsEnabled = true; + context = new Context(); + path = canonicalPath; + checkNativePtrNotZero(); + + if (syncEnabled) { + //TODO client is thread-safe & it should be global & reused across different RealmConfiguration + sessionPtr = SyncManager.getSession(config.getSyncUserToken(), config.getPath(), config.getSyncServerUrl()); + } + } + + // TODO Remove this? Explicit transactions are not supported anyway? + public SharedGroup(String canonicalPath, + boolean enableImplicitTransactions, + boolean enableSync, + Durability durability, + byte[] key) { if (enableImplicitTransactions) { - nativeReplicationPtr = nativeCreateReplication(canonicalPath, key); - nativePtr = openSharedGroupOrFail(durability, key); + if (enableSync) { + nativeReplicationPtr = nativeCreateSyncReplication(canonicalPath); + } else { + nativeReplicationPtr = nativeCreateLocalReplication(canonicalPath, key); + } + nativePtr = openSharedGroupOrFail(nativeReplicationPtr, canonicalPath, durability, key); implicitTransactionsEnabled = true; } else { nativePtr = nativeCreate(canonicalPath, Durability.FULL.value, CREATE_FILE_YES, DISABLE_REPLICATION, key); @@ -81,7 +122,7 @@ public SharedGroup(String canonicalPath, boolean enableImplicitTransactions, Dur checkNativePtrNotZero(); } - private long openSharedGroupOrFail(Durability durability, byte[] key) { + private long openSharedGroupOrFail(long nativeReplicationPtr, String path, Durability durability, byte[] key) { // We have anecdotal evidence that on some versions of Android it is possible for two versions of an app // to exist in two processes during an app upgrade. This is problematic since the lock file might not be // compatible across two versions of Android. See https://github.com/realm/realm-java/issues/2459. If this @@ -146,7 +187,7 @@ void promoteToWrite() { } void commitAndContinueAsRead() { - nativeCommitAndContinueAsRead(nativePtr); + nativeCommitAndContinueAsRead(nativePtr, sessionPtr); } void rollbackAndContinueAsRead() { @@ -207,6 +248,7 @@ void endRead() { activeTransaction = false; } + //FIXME close session (delete sessionPtr pointer) public void close() { synchronized (context) { if (nativePtr != 0) { @@ -363,10 +405,11 @@ public void stopWaitForChange() { nativeStopWaitForChange(nativePtr); } - private native long createNativeWithImplicitTransactions(long nativeReplicationPtr, - int durability, byte[] key); - private native long nativeCreateReplication(String databaseFile, byte[] key); - private native void nativeCommitAndContinueAsRead(long nativePtr); + private native long createNativeWithImplicitTransactions(long nativeReplicationPtr, int durability, byte[] key); + private native long nativeCreateLocalReplication(String databaseFile, byte[] key); + private native long nativeCreateSyncReplication(String databaseFile); + private native long nativeCommitAndContinueAsRead(long nativePtr, long sessionPtr); + private native long nativeBeginImplicit(long nativePtr); private native void nativeReserve(long nativePtr, long bytes); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedGroupManager.java b/realm/realm-library/src/main/java/io/realm/internal/SharedGroupManager.java index 7eac1c3ef1..ebe38f90cb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedGroupManager.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedGroupManager.java @@ -42,11 +42,7 @@ public class SharedGroupManager implements Closeable { * Creates a new instance of the FileWrapper for the given configuration on this thread. */ public SharedGroupManager(RealmConfiguration configuration) { - this.sharedGroup = new SharedGroup( - configuration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - configuration.getDurability(), - configuration.getEncryptionKey()); + this.sharedGroup = new SharedGroup(configuration); this.transaction = sharedGroup.beginImplicitTransaction(); } @@ -170,11 +166,8 @@ public static boolean compact(RealmConfiguration configuration) { SharedGroup sharedGroup = null; boolean result = false; try { - sharedGroup = new SharedGroup( - configuration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - SharedGroup.Durability.FULL, - configuration.getEncryptionKey()); + // TODO: Fail if in-memory only? + sharedGroup = new SharedGroup(configuration); result = sharedGroup.compact(); } catch (Exception e) { RealmLog.i(e.getMessage()); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SyncSessionImpl.java b/realm/realm-library/src/main/java/io/realm/internal/SyncSessionImpl.java new file mode 100644 index 0000000000..c5263b4694 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/SyncSessionImpl.java @@ -0,0 +1,29 @@ +package io.realm.internal; + +import io.realm.sync.SyncConfiguration; +import io.realm.sync.SyncSession; + +public class SyncSessionImpl implements SyncSession { + + private final SyncConfiguration config; + private final long nativeSyncSessionPtr; + + public SyncSessionImpl(SyncConfiguration config, long nativeSyncSessionPtr) { + this.config = config; + this.nativeSyncSessionPtr = nativeSyncSessionPtr; + } + + @Override + public void start() { + // nativeStartSync(nativeSyncSessionPtr); + } + + @Override + public void stop() { + nativeStopSync(nativeSyncSessionPtr); + } + + private native void nativeStartSync(long nativeSyncSessionPtr); + //TODO just delete the pointer nativeSyncSessionPtr to stop syncing + private native void nativeStopSync(long nativeSyncSessionPtr); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java index a7be9d941b..6f8365cf32 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java @@ -71,10 +71,7 @@ public static Builder.RealmConfigurationStep newBuilder() { public void run() { SharedGroup sharedGroup = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedGroup = new SharedGroup(realmConfiguration); Result result; boolean updateSuccessful; diff --git a/realm/realm-library/src/main/java/io/realm/sync/ManualSyncPolicy.java b/realm/realm-library/src/main/java/io/realm/sync/ManualSyncPolicy.java new file mode 100644 index 0000000000..9a2e378838 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/ManualSyncPolicy.java @@ -0,0 +1,8 @@ +package io.realm.sync; + +public class ManualSyncPolicy implements SyncPolicy { + @Override + public void apply(SyncSession session) { + // Zzzzz.... + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/RealtimeSyncPolicy.java b/realm/realm-library/src/main/java/io/realm/sync/RealtimeSyncPolicy.java new file mode 100644 index 0000000000..b887329032 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/RealtimeSyncPolicy.java @@ -0,0 +1,8 @@ +package io.realm.sync; + +public class RealtimeSyncPolicy implements SyncPolicy { + @Override + public void apply(SyncSession session) { + session.start(); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/sync/SyncConfiguration.java new file mode 100644 index 0000000000..20a3b1b832 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/SyncConfiguration.java @@ -0,0 +1,36 @@ +package io.realm.sync; + +import io.realm.RealmConfiguration; + +public class SyncConfiguration { + + private final SyncPolicy syncPolicy; + private final String userToken; + final RealmConfiguration configuration; + + public RealmConfiguration getConfiguration() { + return configuration; + } + + public String getServer() { + return server; + } + + private final String server; + + //TODO have a builder + public SyncConfiguration(RealmConfiguration configuration, String server) { + this.configuration = configuration; + this.server = server; + this.syncPolicy = new RealtimeSyncPolicy(); + this.userToken = "boom"; + } + + public SyncPolicy getSyncPolicy() { + return syncPolicy; + } + + public String getUserToken() { + return userToken; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/SyncManager.java b/realm/realm-library/src/main/java/io/realm/sync/SyncManager.java new file mode 100644 index 0000000000..fd285d571d --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/SyncManager.java @@ -0,0 +1,56 @@ +package io.realm.sync; + +import android.os.Handler; + +import java.util.HashMap; +import java.util.Map; + +import io.realm.BaseRealm; +import io.realm.internal.log.RealmLog; + +public final class SyncManager { + private static volatile long syncClientPointer = 0; + private final static Map SYNC_SESSIONS = new HashMap(); + + public synchronized static long getSession(final String userToken, final String path, final String serverUrl) { + if (syncClientPointer == 0) { + // client event loop is not created for this token + // we create 1 client per user token + syncClientPointer = syncCreateClient(); + } + + // check if the session is not already available for the provided RealmConfiguration + Long syncSessionPointer = SYNC_SESSIONS.get(path); + if (syncSessionPointer == null) { + syncSessionPointer = syncCreateSession(syncClientPointer, path, serverUrl, userToken); + } + + SYNC_SESSIONS.put(path, syncSessionPointer); + return syncSessionPointer; + } + + public static void notifyHandlers(String path) { + + for (Map.Entry handlerIntegerEntry : BaseRealm.handlers.entrySet()) { + Handler handler = handlerIntegerEntry.getKey(); + String realmPath = handlerIntegerEntry.getValue(); + + // For all other threads, use the Handler + // Note there is a race condition with handler.hasMessages() and handler.sendEmptyMessage() + // as the target thread consumes messages at the same time. In this case it is not a problem as worst + // case we end up with two REALM_CHANGED messages in the queue. + if ( + realmPath.equals(path) // It's the right realm + && !handler.hasMessages(14930352) // HandlerController.REALM_CHANGED The right message + && handler.getLooper().getThread().isAlive() // HandlerController.REALM_CHANGED The receiving thread is alive + && !handler.sendEmptyMessage(14930352)) { + RealmLog.w("Cannot update Looper threads when the Looper has quit. Use realm.setAutoRefresh(false) " + + "to prevent this."); + } + } + } + + private static native long syncCreateClient(); + private static native long syncCreateSession(long clientPointer, String path, String serverUrl, String userToken); + +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/SyncPolicy.java b/realm/realm-library/src/main/java/io/realm/sync/SyncPolicy.java new file mode 100644 index 0000000000..f4e8753b5e --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/SyncPolicy.java @@ -0,0 +1,5 @@ +package io.realm.sync; + +public interface SyncPolicy { + void apply(SyncSession session); +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/SyncSession.java b/realm/realm-library/src/main/java/io/realm/sync/SyncSession.java new file mode 100644 index 0000000000..b406297c7d --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/SyncSession.java @@ -0,0 +1,6 @@ +package io.realm.sync; + +public interface SyncSession { + void start(); + void stop(); +} diff --git a/version.txt b/version.txt index 468e6c357b..a14d320fba 100644 --- a/version.txt +++ b/version.txt @@ -1 +1,2 @@ -1.2.0-SNAPSHOT +0.27.0-SNAPSHOT + From 95e252bce2f121587c465f94b69b377ea33f8944 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 21 Aug 2016 12:21:29 +0200 Subject: [PATCH 0002/2110] Bump to latest sync release and disable Werror --- realm/realm-jni/build.gradle | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/realm/realm-jni/build.gradle b/realm/realm-jni/build.gradle index d6091dc3fb..ca88f2260d 100644 --- a/realm/realm-jni/build.gradle +++ b/realm/realm-jni/build.gradle @@ -1,8 +1,8 @@ import java.security.MessageDigest -ext.coreVersion = '0.27.0' // Sync version +ext.coreVersion = '0.28.0' // Sync version // empty or comment out this to disable hash checking -ext.coreSha256Hash = '21687bfb3bff99ef4cb6b2846bf8031bf41e9e4c4f82ef38346ff18c45be4620' +ext.coreSha256Hash = 'e4d8ed7342824a1574449700b16cd36f663f4d6768fc09c1aca986f19e27162b' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // gcc is default for the NDK. It also produces smaller binaries @@ -22,7 +22,8 @@ ext.buildTargetAbis = project.hasProperty('buildTargetABIs') ? project.getProper // The location of the s3cfg used when running s3cmd ext.s3cfg = project.hasProperty('s3cfg') ? project.getProperty('s3cfg') : null; -def commonCflags = [ '-Os', '-std=c++14', '-Wmissing-declarations' , '-Werror', '-fsigned-char'] +// Bad bad CM, but disabling '-Werror' for now +def commonCflags = [ '-Os', '-std=c++14', '-Wmissing-declarations' , '-fsigned-char'] // LTO and debugging don't play well together if (!ext.debugBuild) { commonCflags += [ '-fvisibility=hidden', '-ffunction-sections', '-fdata-sections', '-flto' ] From 34a12a0216d737125101df66d0dabb260970f571 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 23 Aug 2016 13:24:44 +0900 Subject: [PATCH 0003/2110] update setup instruction in README.md (#75) * add set-up instruction of 's3cmd' to README.md use ~/.s3cfg as default configutration. * revert the change of core archive filename --- README.md | 2 ++ realm/realm-jni/build.gradle | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a24fa16f7..aedabe16b3 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ Prerequisites: * Make sure `make` is available in your `$PATH` * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. + * Download & install s3cmd (`brew install s3cmd` on Mac, `sudo apt-get install s3cmd` on Ubuntu). + * Get `.s3cfg` file and put it in your home directory. If you'd like to put it other location, add `s3cfg=` in `~/.gradle/gradle.properties`. * Download & install the Android SDK **Build-Tools 24.0.0**, **Android N (API 24)** (for example through Android Studio’s **Android SDK Manager**) * Download the **Android NDK (= r10e)** for [OS X](http://dl.google.com/android/ndk/android-ndk-r10e-darwin-x86_64.bin) or [Linux](http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin). * Or you can use [Hombrew-versions](https://github.com/Homebrew/homebrew-versions) to install Android NDK for Mac: diff --git a/realm/realm-jni/build.gradle b/realm/realm-jni/build.gradle index ca88f2260d..fc14ddc742 100644 --- a/realm/realm-jni/build.gradle +++ b/realm/realm-jni/build.gradle @@ -214,7 +214,11 @@ task downloadCore() { println "Downloading ${downloadUrl}" exec { - commandLine 's3cmd', '-c', project.s3cfg, '-f', 'get', "${downloadUrl}", "${project.coreArchiveFile}" + if (project.s3cfg) { + commandLine 's3cmd', '-c', project.s3cfg , '-f', 'get', "${downloadUrl}", "${project.coreArchiveFile}" + } else { + commandLine 's3cmd', '-f', 'get', "${downloadUrl}", "${project.coreArchiveFile}" + } } coreDownloaded = true if (isHashCheckingEnabled()) { From 5058549f867d16024bee7c562c71cd6b21ec0717 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 23 Aug 2016 16:29:43 +0800 Subject: [PATCH 0004/2110] Remove improper incompatibleLockFile test (#3324) This test is not constructed properly. It tries to modify the lock file while the Realm is still opened. This would cause some undefined behaviour in core. Especially when we are using the generic external_commit_helper which is based on SharedGroup::wait_for_change() the helper thread will freeze because of polling hangs forever. Just remove the test, core should ensure the proper exception thrown and we have other tests to ensure the exception conversion right. --- .../androidTest/java/io/realm/RealmTests.java | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index f73eff9771..02679eb1e6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3428,24 +3428,4 @@ public void run(Realm realm) { TestHelper.awaitOrFail(bgRealmFished); assertFalse(bgRealmChangeResult.get()); } - - @Test - public void incompatibleLockFile() throws IOException { - // Replace .lock file with a corrupted one - File lockFile = new File(realmConfig.getPath() + ".lock"); - assertTrue(lockFile.exists()); - FileOutputStream fooStream = new FileOutputStream(lockFile, false); - fooStream.write("Boom".getBytes()); - fooStream.close(); - - try { - // This will try to open a second SharedGroup which should fail when the .lock file is corrupt - DynamicRealm.getInstance(realm.getConfiguration()); - fail(); - } catch (RealmError expected) { - assertTrue(expected.getMessage().contains("Info size doesn't match")); - } finally { - lockFile.delete(); - } - } } From ffac1d30b939f1f907071d4c73a1dcfd5a68de0e Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Tue, 23 Aug 2016 14:02:25 +0200 Subject: [PATCH 0005/2110] Allow CI builds not running as root (#3335) --- Dockerfile | 23 +++++++++++++++-------- Jenkinsfile | 2 +- realm/realm-jni/build.gradle | 16 +++++++++------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0029aaebbf..3c0cc94473 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,9 +26,10 @@ RUN DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 \ && apt-get clean # Install the Android SDK -RUN cd /opt && wget -q https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz -O android-sdk.tgz -RUN cd /opt && tar -xvzf android-sdk.tgz -RUN cd /opt && rm -f android-sdk.tgz +RUN cd /opt && \ + wget -q https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz -O android-sdk.tgz && \ + tar -xvzf android-sdk.tgz && \ + rm -f android-sdk.tgz # Grab what's needed in the SDK # ↓ updates tools to at least 25.1.7, but that prints 'Nothing was installed' (so I don't check the outputs). @@ -39,8 +40,14 @@ RUN echo y | android update sdk --no-ui --all --filter extra-android-m2repositor RUN echo y | android update sdk --no-ui --all --filter android-24 | grep 'package installed' # Install the NDK -RUN mkdir /opt/android-ndk-tmp -RUN cd /opt/android-ndk-tmp && wget -q http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin -O android-ndk.bin -RUN cd /opt/android-ndk-tmp && chmod a+x ./android-ndk.bin && ./android-ndk.bin -RUN cd /opt/android-ndk-tmp && mv ./android-ndk-r10e /opt/android-ndk -RUN rm -rf /opt/android-ndk-tmp +RUN mkdir /opt/android-ndk-tmp && \ + cd /opt/android-ndk-tmp && \ + wget -q http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin -O android-ndk.bin && \ + chmod a+x ./android-ndk.bin && \ + ./android-ndk.bin && \ + mv android-ndk-r10e /opt/android-ndk && \ + rm -rf /opt/android-ndk-tmp && \ + chmod -R a+rX /opt/android-ndk + +# Make the SDK universally readable +RUN chmod -R a+rX /opt/android-sdk-linux diff --git a/Jenkinsfile b/Jenkinsfile index be329c01d2..291c183c5a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,7 +14,7 @@ try { stage 'Docker build' def buildEnv = docker.build 'realm-java:snapshot' - buildEnv.inside("--privileged -v /dev/bus/usb:/dev/bus/usb -v ${env.HOME}/gradle-cache:/root/.gradle -v /root/adbkeys:/root/.android") { + buildEnv.inside("-e HOME=/tmp -e _JAVA_OPTIONS=-Duser.home=/tmp --privileged -v /dev/bus/usb:/dev/bus/usb -v ${env.HOME}/gradle-cache:/tmp/.gradle -v ${env.HOME}/.android:/tmp/.android") { stage 'JVM tests' try { gradle 'assemble check javadoc' diff --git a/realm/realm-jni/build.gradle b/realm/realm-jni/build.gradle index b7d0fc5331..78bb43842b 100644 --- a/realm/realm-jni/build.gradle +++ b/realm/realm-jni/build.gradle @@ -122,27 +122,29 @@ def getNdk() { if (!System.env.NDK_HOME) { throw new GradleException('The NDK_HOME environment variable is not set.') } - def ndkDir = file(System.env.NDK_HOME) + def ndkDir = new File(System.env.NDK_HOME) if (!ndkDir.directory) { throw new GradleException('The path provided in the NDK_HOME environment variable is not a folder.') } def detectedNdkVersion - if (file("${ndkDir}/RELEASE.TXT").file) { - detectedNdkVersion = file("${ndkDir}/RELEASE.TXT").text.trim().split()[0].split('-')[0] - } else if (file("${ndkDir}/source.properties").file) { - def reader = file("${ndkDir}/source.properties").newReader() + def releaseFile = new File(ndkDir, 'RELEASE.TXT') + def propertyFile = new File(ndkDir, 'source.properties') + if (releaseFile.isFile()) { + detectedNdkVersion = releaseFile.text.trim().split()[0].split('-')[0] + } else if (propertyFile.isFile()) { + def reader = propertyFile.newReader() try { def props = new Properties() props.load(reader) detectedNdkVersion = props.get('Pkg.Revision') if (detectedNdkVersion == null) { - throw new GradleException("failed to obtain ndk version information from ${ndkDir}/source.properties") + throw new GradleException("Failed to obtain the NDK version information from ${ndkDir}/source.properties") } } finally { reader.close() } } else { - throw new GradleException('The path provided in the NDK_HOME environment variable does not seem to be an Android NDK.') + throw new GradleException("Neither ${releaseFile.getAbsolutePath()} nor ${propertyFile.getAbsolutePath()} is a file.") } //noinspection GroovyVariableNotAssigned if (detectedNdkVersion != ndkVersion) { From 708296a5f67b08b222c6962f7851eea14226f55c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 23 Aug 2016 21:44:19 +0800 Subject: [PATCH 0006/2110] Move JNI build to cmake (#2960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * This requires AS 2.2 beta2+ to build. * Also NDK_HOME needs to be set. * The com.android.tools.build:gradle:2.2.0-beta2 requires java8 * The JNI header files are not maintained in the repo, instead, it will be generated from classes directly before native build. project realm-jni is not needed anymore. All native build is handled by CMake which will be supported by Android gradle 2.2.0+. We were using CMake 3.4 shipped by Android SDK manager when started this PR. Now the version bumped to 3.6. Unfortunately google decided to rewrite a whole new ⁠android.toolchain.cmake which has a few bugs and doesn't work with NDK r10e. So we ship a copy of ⁠android.toolchain.cmake from CMake 3.4 (the one from https://github.com/taka-no-me/android-cmake) with some modifications to make it work with the cmake android gradle changes (https://android.googlesource.com/platform/external/cmake/+/bb48c9e0c45765f8dd57c814b3e3a689ddebd2e1). --- .gitignore | 7 +- CHANGELOG.md | 6 + Dockerfile | 18 +- README.md | 15 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- realm.properties | 2 +- realm/build.gradle | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/realm-jni/build.gradle | 374 ---- realm/realm-jni/generate-jni-headers.sh | 18 - realm/realm-jni/generic.mk | 1971 ----------------- realm/realm-jni/project.mk | 64 - realm/realm-jni/src/Makefile | 6 - .../src/io_realm_internal_CheckedRow.h | 197 -- realm/realm-jni/src/io_realm_internal_Group.h | 155 -- .../src/io_realm_internal_LinkView.h | 149 -- .../src/io_realm_internal_SharedGroup.h | 201 -- realm/realm-jni/src/io_realm_internal_Table.h | 759 ------- .../src/io_realm_internal_TableQuery.h | 605 ----- .../src/io_realm_internal_TableView.h | 527 ----- .../src/io_realm_internal_UncheckedRow.h | 245 -- realm/realm-jni/src/io_realm_internal_Util.h | 45 - realm/realm-library/build.gradle | 176 +- .../realm-library/src/main/cpp/CMakeLists.txt | 109 + .../src/main/cpp/android.toolchain.cmake | 1703 ++++++++++++++ .../cpp}/io_realm_internal_CheckedRow.cpp | 0 .../src/main/cpp}/io_realm_internal_Group.cpp | 0 .../main/cpp}/io_realm_internal_LinkView.cpp | 0 .../cpp}/io_realm_internal_SharedGroup.cpp | 0 .../src/main/cpp}/io_realm_internal_Table.cpp | 0 .../cpp}/io_realm_internal_TableQuery.cpp | 0 .../main/cpp}/io_realm_internal_TableView.cpp | 0 .../cpp}/io_realm_internal_UncheckedRow.cpp | 0 .../src/main/cpp}/io_realm_internal_Util.cpp | 0 .../src/main/cpp}/java_lang_List_Util.cpp | 0 .../src/main/cpp}/java_lang_List_Util.hpp | 0 .../src/main/cpp}/mem_usage.cpp | 0 .../src/main/cpp}/mem_usage.hpp | 0 .../src/main/cpp}/tablebase_tpl.hpp | 0 .../src/main/cpp}/utf8.hpp | 0 .../src/main/cpp}/util.cpp | 0 .../src/main/cpp}/util.hpp | 0 realm/settings.gradle | 1 - 44 files changed, 2028 insertions(+), 5337 deletions(-) delete mode 100644 realm/realm-jni/build.gradle delete mode 100755 realm/realm-jni/generate-jni-headers.sh delete mode 100644 realm/realm-jni/generic.mk delete mode 100644 realm/realm-jni/project.mk delete mode 100644 realm/realm-jni/src/Makefile delete mode 100644 realm/realm-jni/src/io_realm_internal_CheckedRow.h delete mode 100644 realm/realm-jni/src/io_realm_internal_Group.h delete mode 100644 realm/realm-jni/src/io_realm_internal_LinkView.h delete mode 100644 realm/realm-jni/src/io_realm_internal_SharedGroup.h delete mode 100644 realm/realm-jni/src/io_realm_internal_Table.h delete mode 100644 realm/realm-jni/src/io_realm_internal_TableQuery.h delete mode 100644 realm/realm-jni/src/io_realm_internal_TableView.h delete mode 100644 realm/realm-jni/src/io_realm_internal_UncheckedRow.h delete mode 100644 realm/realm-jni/src/io_realm_internal_Util.h create mode 100644 realm/realm-library/src/main/cpp/CMakeLists.txt create mode 100644 realm/realm-library/src/main/cpp/android.toolchain.cmake rename realm/{realm-jni/src => realm-library/src/main/cpp}/io_realm_internal_CheckedRow.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/io_realm_internal_Group.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/io_realm_internal_LinkView.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/io_realm_internal_SharedGroup.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/io_realm_internal_Table.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/io_realm_internal_TableQuery.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/io_realm_internal_TableView.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/io_realm_internal_UncheckedRow.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/io_realm_internal_Util.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/java_lang_List_Util.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/java_lang_List_Util.hpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/mem_usage.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/mem_usage.hpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/tablebase_tpl.hpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/utf8.hpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/util.cpp (100%) rename realm/{realm-jni/src => realm-library/src/main/cpp}/util.hpp (100%) diff --git a/.gitignore b/.gitignore index a72b66b484..22df2e0956 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Gradle build artifacts build realm/build -realm-jni/build # Gradle cache .gradle @@ -46,3 +45,9 @@ distribution/RealmGridViewExample/app/src distribution/RealmIntroExample/app/src distribution/RealmMigrationExample/app/src +# Generated JNI headers +realm/realm-library/src/main/cpp/jni_include +# Downloaded core +realm/realm-library/distribution +# Cmake output +realm/realm-library/.externalNativeBuild diff --git a/CHANGELOG.md b/CHANGELOG.md index bacad0d716..b0adb808f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.2.1 + +### Internal + +* Move JNI build to CMake. + ## 1.2.0 ### Bug fixes diff --git a/Dockerfile b/Dockerfile index 3c0cc94473..f3c06ffa5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,8 @@ ENV LC_ALL "en_US.UTF-8" # Set the environment variables ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64 ENV ANDROID_HOME /opt/android-sdk-linux -ENV NDK_HOME /opt/android-ndk +# Need by cmake +ENV ANDROID_NDK_HOME /opt/android-ndk ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} @@ -27,9 +28,9 @@ RUN DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 \ # Install the Android SDK RUN cd /opt && \ - wget -q https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz -O android-sdk.tgz && \ - tar -xvzf android-sdk.tgz && \ - rm -f android-sdk.tgz + wget -q https://dl.google.com/android/repository/tools_r25.1.7-linux.zip -O android-tools-linux.zip && \ + unzip android-tools-linux.zip -d ${ANDROID_HOME} && \ + rm -f android-tools-linux.zip # Grab what's needed in the SDK # ↓ updates tools to at least 25.1.7, but that prints 'Nothing was installed' (so I don't check the outputs). @@ -49,5 +50,12 @@ RUN mkdir /opt/android-ndk-tmp && \ rm -rf /opt/android-ndk-tmp && \ chmod -R a+rX /opt/android-ndk +# Install cmake +RUN mkdir /opt/cmake-tmp && \ + cd /opt/cmake-tmp && \ + wget -q https://dl.google.com/android/repository/cmake-3.6.3133135-linux-x86_64.zip -O cmake-linux.zip && \ + unzip cmake-linux.zip -d ${ANDROID_HOME}/cmake && \ + rm -rf /opt/cmake-tmp + # Make the SDK universally readable -RUN chmod -R a+rX /opt/android-sdk-linux +RUN chmod -R a+rX ${ANDROID_HOME} diff --git a/README.md b/README.md index 6a24fa16f7..791a4f8cc6 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,11 @@ In case you don't want to use the precompiled version, you can build Realm yours Prerequisites: - * Make sure `make` is available in your `$PATH` + * Make sure `make` is available in your `$PATH`. * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install the Android SDK **Build-Tools 24.0.0**, **Android N (API 24)** (for example through Android Studio’s **Android SDK Manager**) + * Download & install the Android SDK **Build-Tools 24.0.0**, **Android N (API 24)** (for example through Android Studio’s **Android SDK Manager**). * Download the **Android NDK (= r10e)** for [OS X](http://dl.google.com/android/ndk/android-ndk-r10e-darwin-x86_64.bin) or [Linux](http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin). + * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * Or you can use [Hombrew-versions](https://github.com/Homebrew/homebrew-versions) to install Android NDK for Mac: ``` @@ -72,14 +73,20 @@ Prerequisites: ``` export ANDROID_HOME=~/Library/Android/sdk - export NDK_HOME=/usr/local/Cellar/android-ndk-r10e/r10e + export ANDROID_NDK_HOME=/usr/local/Cellar/android-ndk-r10e/r10e + ``` + + * If you want to build with Android Studio, `ndk.dir` has to be defined in the `realm/local.properties` as well. + + ``` + ndk.dir=/usr/local/Cellar/android-ndk-r10e/r10e ``` * If you are using OS X, you'd be better to add following lines to `~/.profile` (or `~/.zprofile` if the login shell is `zsh`) in order for Android Studio to see those environment variables. ``` launchctl setenv ANDROID_HOME "$ANDROID_HOME" - launchctl setenv NDK_HOME "$NDK_HOME" + launchctl setenv ANDROID_NDK_HOME "$ANDROID_NDK_HOME" ``` * And if you'd like to specify the location to store the archives of Realm's core, set `REALM_CORE_DOWNLOAD_DIR` environment variable. It enables you to keep core's archive when executing `git clean -xfd`. diff --git a/build.gradle b/build.gradle index 43db7f348b..228c2813a0 100644 --- a/build.gradle +++ b/build.gradle @@ -213,7 +213,7 @@ task distributionJniUnstrippedPackage(type:Zip) { archiveName = "realm-java-jni-libs-unstripped-${currentVersion}.zip" destinationDir = file("${buildDir}/outputs/distribution") - from("realm/realm-jni/build/outputs/jniLibs-unstripped") { + from("realm/realm-library/build/outputs/jniLibs-unstripped") { include '**/*.so' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/realm.properties b/realm.properties index 60d64cf12c..1be5af0639 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=2.7 +gradleVersion=2.14.1 ndkVersion=r10e diff --git a/realm/build.gradle b/realm/build.gradle index 61e092c997..3be5c2794e 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -6,10 +6,10 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' - classpath 'de.undercouch:gradle-download-task:2.0.0' + classpath 'com.android.tools.build:gradle:2.2.0-beta2' + classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' classpath 'com.novoda:gradle-android-command-plugin:1.3.0' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.2' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0' diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/realm/realm-jni/build.gradle b/realm/realm-jni/build.gradle deleted file mode 100644 index 78bb43842b..0000000000 --- a/realm/realm-jni/build.gradle +++ /dev/null @@ -1,374 +0,0 @@ -import java.security.MessageDigest - -ext.coreVersion = '1.5.1' -// empty or comment out this to disable hash checking -ext.coreSha256Hash = 'a034d3250c820a15126721142d168a2ac4a12223b75bb324958ca2a70442720d' -ext.forceDownloadCore = - project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false -// gcc is default for the NDK. It also produces smaller binaries -ext.clang = project.hasProperty('clang') ? project.getProperty('clang').toBoolean() : false -// Build with debug symbols -ext.debugBuild = project.hasProperty('debugBuild') ? project.getProperty('debugBuild').toBoolean() : false -// Strip the symbols from the so file or not. If debugBuild is true, this one will be always false. -ext.stripSymbols = project.hasProperty('stripSymbols') ? project.getProperty('stripSymbols').toBoolean() : true -// Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from -// core source code. -ext.coreSourcePath = project.hasProperty('coreSourcePath') ? project.getProperty('coreSourcePath') : null -// The location of core archive. -ext.coreArchiveDir = System.getenv("REALM_CORE_DOWNLOAD_DIR") -// target ABIs to build(null means all). -// To obtain the ABI of the connected device, execute "adb shell getprop ro.product.cpu.abi" -ext.buildTargetAbis = project.hasProperty('buildTargetABIs') ? project.getProperty('buildTargetABIs').split(',').collect {it.trim()} : null - -def commonCflags = [ '-Os', '-std=c++14', '-Wmissing-declarations' , '-Werror', '-fsigned-char'] -// LTO and debugging don't play well together -if (!ext.debugBuild) { - commonCflags += [ '-fvisibility=hidden', '-ffunction-sections', '-fdata-sections', '-flto' ] -} - -enum Compiler { - GCC, CLANG -} - -// Unfortunately the NDK has no consistency when it comes to naming. -// This Class holds all the different names used and some more information -class Toolchain { - // The standard name: arm, arm64, mips, x86 - String name - - // The name used when generating the standalone toolchain - String fullName - - // The prefix commands use. i.e. arm-linux-androideabi-gcc - String commandPrefix - - // Which version of each compiler to use - Map version - - // The first Android platform to support this toolchain - int platform -} - -// This class describes the specific target -class Target { - // The name of the target. This is used for the task names - String name - - // The name of the abi. It is also the name of the folder where the Android Gradle plugin - // expects to find the shared library - String abi - - // The toolchain associated to this target - Toolchain toolchain - - // The CFLAGS specific to this target - List cflags -} - -// We are using gcc 4.9 for all architectures -def toolchains = [ - new Toolchain( name:'arm', fullName:'arm-linux-androideabi', commandPrefix:'arm-linux-androideabi', version:[ (Compiler.GCC):'4.9', (Compiler.CLANG):'3.5' ], platform:8 ), - new Toolchain( name:'arm64', fullName:'aarch64-linux-android', commandPrefix:'aarch64-linux-android', version:[ (Compiler.GCC):'4.9', (Compiler.CLANG):'3.5' ], platform:21 ), - new Toolchain( name:'mips', fullName:'mipsel-linux-android', commandPrefix:'mipsel-linux-android', version:[ (Compiler.GCC):'4.9', (Compiler.CLANG):'3.5' ], platform:9 ), - new Toolchain( name:'x86', fullName:'x86', commandPrefix:'i686-linux-android', version:[ (Compiler.GCC):'4.9', (Compiler.CLANG):'3.5' ], platform:9 ), - new Toolchain( name:'x86_64', fullName:'x86_64', commandPrefix:'x86_64-linux-android', version:[ (Compiler.GCC):'4.9', (Compiler.CLANG):'3.5' ], platform:21 ) -] - -def allTargets = [ - new Target( name:'arm', abi:'armeabi', toolchain:toolchains.find {it.name == 'arm'}, cflags:[ '-mthumb' ] ), - new Target( name:'arm-v7a', abi:'armeabi-v7a', toolchain:toolchains.find {it.name == 'arm'}, cflags:[ '-mthumb', '-march=armv7-a', '-mfloat-abi=softfp', '-mfpu=vfpv3-d16' ] ), - new Target( name:'arm64', abi:'arm64-v8a', toolchain:toolchains.find {it.name == 'arm64'}, cflags:[] ), - new Target( name:'mips', abi:'mips', toolchain:toolchains.find {it.name == 'mips'}, cflags:[] ), - new Target( name:'x86', abi:'x86', toolchain:toolchains.find {it.name == 'x86'}, cflags:[] ), - new Target( name:'x86_64', abi:'x86_64', toolchain:toolchains.find {it.name == 'x86_64'}, cflags:[] ) -] - -def targets -if (ext.buildTargetAbis == null) { - targets = allTargets; -} else { - targets = ext.buildTargetAbis.collect { targetAbi -> - def target = allTargets.find {it.abi == targetAbi} - if (!target) { - throw new GradleException("Warning: no target ABIs found for '${targetAbi}'." + - " Please check 'buildTargetABIs' property." + - " Supprted ABIs are ${allTargets.collect {it.abi}. join(', ')}.") - } - return target - } -} - -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'de.undercouch:gradle-download-task:2.0.0' - } -} - -apply plugin: 'de.undercouch.download' - -if (ext.debugBuild) { - // Debug build should never strip symbols - ext.stripSymbols = false -} -if (ext.coreSourcePath) { - // Run the "sh build.sh get-version" to get the core version. - ext.coreVersion = "sh build.sh get-version".execute([], file(coreSourcePath)).text.trim() -} - -def getNdk() { - if (!System.env.NDK_HOME) { - throw new GradleException('The NDK_HOME environment variable is not set.') - } - def ndkDir = new File(System.env.NDK_HOME) - if (!ndkDir.directory) { - throw new GradleException('The path provided in the NDK_HOME environment variable is not a folder.') - } - def detectedNdkVersion - def releaseFile = new File(ndkDir, 'RELEASE.TXT') - def propertyFile = new File(ndkDir, 'source.properties') - if (releaseFile.isFile()) { - detectedNdkVersion = releaseFile.text.trim().split()[0].split('-')[0] - } else if (propertyFile.isFile()) { - def reader = propertyFile.newReader() - try { - def props = new Properties() - props.load(reader) - detectedNdkVersion = props.get('Pkg.Revision') - if (detectedNdkVersion == null) { - throw new GradleException("Failed to obtain the NDK version information from ${ndkDir}/source.properties") - } - } finally { - reader.close() - } - } else { - throw new GradleException("Neither ${releaseFile.getAbsolutePath()} nor ${propertyFile.getAbsolutePath()} is a file.") - } - //noinspection GroovyVariableNotAssigned - if (detectedNdkVersion != ndkVersion) { - throw new GradleException("Your NDK version: ${detectedNdkVersion}. Realm JNI should be compiled with the version ${ndkVersion} of NDK.") - } - return ndkDir -} - -def getStrippedExt() { - return stripSymbols ? "-stripped" : "" -} - -def getDebugExt() { - return debugBuild ? "-dbg" : "" -} - -if (!ext.coreArchiveDir) { - ext.coreArchiveDir = ".." -} -ext.coreArchiveFile = rootProject.file("${ext.coreArchiveDir}/core-android-${project.coreVersion}.tar.gz") -ext.coreDir = file("${buildDir}/core-${project.coreVersion}") - -def coreDownloaded = false - -task downloadCore(group: 'build setup', description: 'Download the latest version of realm core') { - def isHashCheckingEnabled = { - return project.hasProperty('coreSha256Hash') && !project.coreSha256Hash.empty - } - - def calcSha256Hash = {File targetFile -> - MessageDigest sha = MessageDigest.getInstance("SHA-256"); - Formatter hexHash = new Formatter() - sha.digest(targetFile.bytes).each { b -> hexHash.format('%02x', b) } - return hexHash.toString() - } - - def shouldDownloadCore = { - if (!project.coreArchiveFile.exists()) { - return true - } - if (project.forceDownloadCore) { - return true; - } - if (!isHashCheckingEnabled()) { - println "Skipping hash check(empty \'coreSha256Hash\')." - return false - } - - def calculatedHash = calcSha256Hash(project.coreArchiveFile) - if (project.coreSha256Hash.equalsIgnoreCase(calculatedHash)) { - return false - } - - println "Existing archive hash mismatch(Expected: ${project.coreSha256Hash.toLowerCase()}" + - " but got ${calculatedHash.toLowerCase()}). Download new version." - return true - } - - doLast { - if (shouldDownloadCore()) { - download { - src "http://static.realm.io/downloads/core/realm-core-android-${project.coreVersion}.tar.gz" - dest project.coreArchiveFile - onlyIfNewer false - } - coreDownloaded = true - - if (isHashCheckingEnabled()) { - def calculatedHash = calcSha256Hash(project.coreArchiveFile) - if (!project.coreSha256Hash.equalsIgnoreCase(calculatedHash)) { - throw new GradleException("Invalid checksum for file '" + - "${project.coreArchiveFile.getName()}'. Expected " + - "${project.coreSha256Hash.toLowerCase()} but got " + - "${calculatedHash.toLowerCase()}."); - } - } else { - println 'Skipping hash check(empty \'coreSha256Hash\').' - } - } - } -} - -task compileCore(group: 'build setup', description: 'Compile the core library from source code') { - // Build the library from core source code - doFirst { - if (!coreSourcePath) { - throw new GradleException('The coreSourcePath is not set.') - } - exec { - workingDir = coreSourcePath - commandLine = [ - "bash", - "build.sh", - "build-android" - ] - } - } - - // Copy the core tar ball - doLast { - copy { - from "${coreSourcePath}/realm-core-android-${coreVersion}.tar.gz" - into project.coreArchiveFile.parent - rename "realm-core-android-${coreVersion}.tar.gz", "core-android-${coreVersion}.tar.gz" - } - } -} - -task deployCore(group: 'build setup', description: 'Deploy the latest version of realm core') { - dependsOn { - coreSourcePath ? compileCore : downloadCore - } - - outputs.upToDateWhen { - // Clean up the coreDir if it is newly downloaded or compiled from source - if (coreDownloaded || coreSourcePath) { - return false - } - - return project.coreDir.exists() - } - - doLast { - exec { - commandLine = [ - 'rm', - '-rf', - project.coreDir.getAbsolutePath() - ] - } - copy { - from tarTree(project.coreArchiveFile) - into project.coreDir - } - exec { - commandLine = [ 'git', 'clean', '-xfd', "${projectDir}/src" ] - } - } -} - -toolchains.each { toolchain -> - def ndkDir = getNdk() - task "generateNdkToolchain${toolchain.name.capitalize()}"(type: Exec) { - group 'build setup' - description "Generate the NDK standalone toolchain for the ${toolchain.name.capitalize()} platform" - outputs.dir file("${buildDir}/standalone-toolchains/${toolchain.name}") - commandLine = [ - "bash", - "${ndkDir}/build/tools/make-standalone-toolchain.sh", - "--platform=android-${toolchain.platform}", - "--install-dir=${buildDir}/standalone-toolchains/${toolchain.name}", - "--toolchain=${toolchain.fullName}-${clang?'clang'+toolchain.version[Compiler.CLANG]:toolchain.version[Compiler.GCC]}" - ] - } -} - -targets.each { target -> - task "buildAndroidJni${target.name.capitalize()}"(type: Exec) { - group 'build' - description "Build the Android JNI shared library for the ${target.name.capitalize()} platform" - dependsOn deployCore - dependsOn "generateNdkToolchain${target.toolchain.name.capitalize()}" - environment PATH: "${buildDir}/standalone-toolchains/${target.toolchain.name}/bin:${System.env.PATH}" - environment CC: "${target.toolchain.commandPrefix}-${clang?'clang':'gcc'}" - environment STRIP: "${target.toolchain.commandPrefix}-strip -o librealm-jni-${target.name}-stripped.so" - environment REALM_ANDROID: '1' - commandLine = [ - 'make', - "-j${Runtime.getRuntime().availableProcessors() * 2}", - "-l${Runtime.getRuntime().availableProcessors()}", - '-C', "${projectDir}/src", - "CC_IS=${clang?'clang':'gcc'}", - "REALM_CFLAGS_COMMON=-Wno-variadic-macros -DREALM_HAVE_CONFIG -DPIC -I${project.coreDir}/include", - "CFLAGS_ARCH=${(commonCflags + target.cflags).join(' ')}", - "BASE_DENOM=${target.name}", - "REALM_LDFLAGS_COMMON=-lrealm-android-${target.name} -lstdc++ -lsupc++ -llog -L${project.coreDir} -Wl,--gc-sections -Wl,-soname,librealm-jni.so", - 'LIB_SUFFIX_SHARED=.so', - "librealm-jni-${target.name}${getDebugExt()}.so" - ] - } - - task "copyAndroidJni${target.name.capitalize()}"(dependsOn: "buildAndroidJni${target.name.capitalize()}") << { - copy { - from "${projectDir}/src/librealm-jni-${target.name}${getDebugExt()}${getStrippedExt()}.so" - into "${projectDir}/../realm-library/src/main/jniLibs/${target.abi}" - rename "librealm-jni-${target.name}${getDebugExt()}${getStrippedExt()}.so", 'librealm-jni.so' - } - - // Store the unstripped version - copy { - from "${projectDir}/src/librealm-jni-${target.name}${getDebugExt()}.so" - into "${buildDir}/outputs/jniLibs-unstripped/${target.abi}" - rename "librealm-jni-${target.name}${getDebugExt()}.so", 'librealm-jni.so' - } - } -} - -task buildAndroidJni(group: 'build', description: 'Build the Android JNI shared library for all the supported platforms') { - targets.each { target -> - dependsOn "copyAndroidJni${target.name.capitalize()}" - } -} - -task clean(type: Delete) { - outputs.upToDateWhen { - project.hasProperty('dontCleanJniFiles') - } - - delete project.buildDir - - delete fileTree(dir: "${projectDir}/../realm-library/src/main/jniLibs/", include: '**/librealm-jni*.so') - delete fileTree(dir: "${projectDir}/src/", include: '**/librealm-jni*-stripped.so') - - doLast { - targets.each { target -> - exec { - commandLine = [ - 'make', - '-C', "${projectDir}/src", - "BASE_DENOM=${target.name}", - 'LIB_SUFFIX_SHARED=.so', - 'clean' - ] - } - } - } -} diff --git a/realm/realm-jni/generate-jni-headers.sh b/realm/realm-jni/generate-jni-headers.sh deleted file mode 100755 index 692757c4c8..0000000000 --- a/realm/realm-jni/generate-jni-headers.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# -# Generate header file for JNI calls -# -# Assumption: the .java files have been compiled -# 1. build project using Android Studio (or gradle); unit tests will probably fail due to missing native methods -# 2. run this script -# 3. rebuild project using Android Studio (or gradle) - -# Setting up -CLASSDIR="$(pwd)/../realm-library/build/intermediates/classes/release/" -JNIDIR="$(pwd)/src" - -# Generate the headers -(cd "$CLASSDIR" && javah -jni -classpath "$CLASSDIR" -d "$JNIDIR" io.realm.internal.Group io.realm.internal.LinkView io.realm.internal.Row io.realm.internal.SharedGroup io.realm.internal.Table io.realm.internal.TableQuery io.realm.internal.TableView io.realm.internal.Util) - -# Remove "empty" header files (they have 13 lines) -wc -l "$JNIDIR"/*.h | grep " 13 " | awk '{print $2}' | xargs rm -f diff --git a/realm/realm-jni/generic.mk b/realm/realm-jni/generic.mk deleted file mode 100644 index 9b0d6fdf55..0000000000 --- a/realm/realm-jni/generic.mk +++ /dev/null @@ -1,1971 +0,0 @@ -# Generic makefile that captures some of the ideas of GNU Automake, -# especially with respect to naming targets. -# -# Author: Kristian Spangsege -# -# Version: 1.0.2 -# -# This makefile requires GNU Make. It has been tested with version -# 3.81, and it is known to work well on both Linux and OS X. -# -# -# Major goals -# ----------- -# -# clean ........ Delete targets and other files that are produced -# while building. -# -# build (default) Build convenience libraries (`noinst_LIBRARIES`) -# plus everything that `install-only` wants to install -# (when disregarding `INSTALL_FILTER`). If necessary, -# the convenience libraries will also be built in -# 'debug' mode. -# -# install ...... Same as `build` followed by `install-only`. -# -# uninstall .... Uninstall everything that `install` would install. -# -# install-only . Installs `HEADERS`, `LIBRARIES`, `PROGRAMS`, and -# `DEV_PROGRAMS`. Whether static libraries and/or -# 'debug' mode versions are also installed depends on -# various configuration parameters. Note that -# `INSTALL_FILTER` can be used to select a subset of -# the above. -# -# ### Selective building -# -# release ...... Builds `LIBRARIES`, `noinst_LIBRARIES`, `PROGRAMS`, -# `DEV_PROGRAMS`, and `noinst_PROGRAMS` in 'release' -# mode. -# -# nodebug ...... Builds everything that `release` does, plus static -# versions installable libraries (`LIBRARIES`) in -# 'release' mode. -# -# debug ........ Builds everything that `release` does, but in 'debug' -# mode. -# -# cover ........ Builds everything that `release` does, but in 'code -# coverage' mode. -# -# everything ... Builds `LIBRARIES`, `noinst_LIBRARIES`, -# `check_LIBRARIES`, `PROGRAMS`, `DEV_PROGRAMS`, and -# `noinst_PROGRAMS` in both 'release' and 'debug' mode. -# -# ### Testing -# -# check ......... Build `LIBRARIES`, `noinst_LIBRARIES`, -# `check_LIBRARIES`, `PROGRAMS`, and `check_PROGRAMS` -# in 'release' mode, then run all `check_PROGRAMS`. -# -# check-debug ... Same as `check`, but in 'debug' mode. -# -# check-cover ... Same as `check`, but in 'code coverage' mode. -# -# memcheck, memcheck-debug Same as `check` and `check-debug` -# respectively, but runs each program under Valgrind. -# -# check-norun, check-debug-norun, check-cover-norun Same as `check`, -# `check-debug`, and `check-cover` respectively, but -# stop after building. -# -# -# Building installable programs and libraries -# ------------------------------------------- -# -# Here is an example of a complete `Makefile` that uses `generic.mk` -# to build a program called `myprog` out of two source files called -# `foo.cpp` and `bar.cpp`: -# -# bin_PROGRAMS = myprog -# myprog_SOURCES = foo.cpp bar.cpp -# include generic.mk -# -# The `bin` in `bin_PROGRAMS` means that your program will be -# installed in the directory specified by the `bindir` variable which -# is set to `/usr/local/bin` by default. This can be overridden by -# setting `prefix`, `exec_prefix`, or `bindir`. -# -# Note: You can place `generic.mk` anywhere you like inside your -# project, but you must always refer to it by a relative path, and if -# you have multiple `Makefile`s in multiple directories, they must all -# refer to the same `generic.mk`. -# -# Here is how to build a library: -# -# include_HEADERS = foo.hpp -# lib_LIBRARIES = libfoo.a -# libfoo_a_SOURCES = libfoo.cpp -# -# Again, the `lib` prefix in `lib_LIBRARIES` means that your library -# will be installed in the directory specified by the `libdir` -# variable which is typically set to `/usr/local/lib` by default. The -# exact default path depends on the chosen installation prefix as well -# as platform policies, for example, on a 64 bit Fedora, it will be -# `/usr/local/lib64`. This can be overridden by setting `prefix`, -# `exec_prefix`, or `libdir`. -# -# The `lib` prefix in `libfoo.a` is mandatory for all installed -# libraries. The `.a` suffix is mandatory for both installed an -# non-installed libraries. The actual extension of the installed -# library is not necessarily going to be `.a`. For a shared library on -# Linux, it will be `.so` by default. The important point is that the -# specified library name is a logical name that is mapped to one or -# more physical names by `generic.mk`. -# -# Note that `.` is replaced by `_` when referring to `libfoo.a` in -# `libfoo_a_SOURCES`. In general, when a target (program or library) -# name is used as part of a variable name, any character that is -# neither alphanumeric nor an underscore, is converted to an -# underscore. -# -# Installed libraries are generally accompanied by one or more headers -# to be included by applications that use the library. Such headers -# must be listed in the `include_HEADERS` variable. Headers are -# installed in `/usr/local/include` by default, but this can be -# changed by setting the `prefix` or `includedir` variable. Note also -# that headers can be installed in a subdirectory of -# `/usr/local/include` or even into a multi-level hierarchy of -# subdirectories (see the 'Subdirectories' section for more on this). -# -# To build more than one program, or more than one library, simply -# list all of them in `bin_PROGRAMS` or `lib_LIBRARIES`. For -# example: -# -# bin_PROGRAMS = hilbert banach -# hilbert_SOURCES = ... -# banach_SOURCES = ... -# -# Here is how to build a library as well as a program that uses the -# library: -# -# lib_LIBRARIES = libmyparser.a -# bin_PROGRAMS = parser -# libmyparser_a_SOURCES = myparser.c -# parser_SOURCES = parser.c -# parser_LIBS = libmyparser.a -# -# I you have two libraries, and one depends on the other: -# -# lib_LIBRARIES = libfoo.a libbar.a -# libbar_a_LIBS = libfoo.a -# -# The installation directory for programs, libraries, and headers is -# determined by the primary prefix being used. Note that `PROGRAMS`, -# `LIBRARIES`, and `HEADERS` are primaries, and that `bin` is a -# primary prefix in `bin_PROGRAMS`, for example. The following primary -# prefixes are supported directly by `generic.mk`: -# -# Prefix Variable Installation directory Default value -# ---------------------------------------------------------------------------- -# bin bindir $(exec_prefix)/bin (/usr/local/bin) -# sbin sbindir $(exec_prefix)/sbin (/usr/local/sbin) -# lib libdir $(exec_prefix)/lib (/usr/local/lib) (*1) -# libexec libexecdir $(exec_prefix)/libexec (/usr/local/libexec) -# include includedir $(prefix)/include (/usr/local/include) -# subinclude includedir $(prefix)/include/... (/usr/local/include/...) (*2) -# -# (*1) The actual default value depends on the platform. -# (*2) Only available when `INCLUDE_ROOT` is specified. -# -# You can also install a program, a library, or a header into a -# non-default directory by defining a custom primary prefix. This is -# usefull when you want (for other purposes) to maintain the default -# values of the standard prefixes. Here is an example: -# -# EXTRA_PRIMARY_PREFIXES = libhome -# libhomedir = /usr/lib/mydeamon/bin -# libhome_PROGRAMS = mydaemon -# -# When doing 'filtered installs' (using `make install -# INSTALL_FILTER=...`) there is a distinction between two categories -# of programs, ordinary programs and 'developer programs'. When a -# project that provides a library gets distributed in compiled form, -# it is customary to offer two packages, the main one, that provides -# the shared library, and a secondary one that provides the header -# files. Some such projects offer programs that are packaged together -# with the shared library, and other programs that are packaged -# together with the headers. The latter category is what we refer to -# as 'developer programs' when working with filtered installs. -# -# To mark a program as a 'developer program' use the special primary -# prefix 'DEV' as in the following example: -# -# DEV_PROGRAMS = mylib-config -# mylib_config_SOURCES = ... -# -# These programs are installed into the same directory as -# `bin_PROGRAMS`. -# -# -# Convenience libraries -# --------------------- -# -# A convenience library is one that is not installed, but gets linked -# statically into programs that are built as part of the same -# project. Convenience libraries are created by using the special -# primary prefix `noinst`, for example: -# -# noinst_LIBRARIES = util.a -# bin_PROGRAMS = foo -# foo_SOURCES = foo.cpp -# foo_LIBS = util.a -# -# Note that in contrast to installed libraries, names of convenience -# libraries are not required to have `lib` as a prefix, but the `.a` -# suffix is still mandatory. Additionally, convenience library names -# do not have to be unique. Indeed, it is valid for a program to be -# linked against two convenience libraries of the same name, as long -# as they reside in different subdirectories within the -# project. Installed libraries, on the other hand, need to have -# system-wide unique names. -# -# It is an error to list a convenience library as a dependency of -# another convenience library or as a dependency of an installed -# library. Only programs can be declared to depend on convenience -# libraries. -# -# A convenience library such as `util.a` can be made to depend on -# project-local installed libraries by listing them in the -# `util_a_LIBS` variable. This can be done because code in `util.a` -# depends on those other libraries, or it can be done simply to avoid -# specifying them repeatedly for multiple programs. On top of that, it -# is possible to attach a set of extra linker flags to a convenience -# library, to be used when linking programs against it. Such flags are -# listed in `util_a_LDFLAGS`. This can be used, for example, to -# specify linking against system libraries or other separately -# installed libraries. -# -# -# Installed programs -# ------------------ -# -# If a program, that is supposed to be installed, is linked against a -# locally built shared library, then `generic.mk` will pass the -# appropriate `-rpath` option to the program linker, such that the -# dynamic linker can find the library in its installed -# location. Unfortunately this does not enable the program to find the -# locally built library, and therefore it will generally not be -# possible to execute the program until after the library is -# installed. -# -# To work around this problem, `generic.mk` will create an extra -# version of the program, where it sets the `RPATH` in such a way that -# the (yet to be installed) library can be found. The name of the -# extra version is constructed by appending `-noinst` to the name of -# the regular version. The extra 'noinst' version is created only for -# testing purposes, and it will not be included during -# installation. It should be noted that the extra 'noinst' version is -# created only for programs that are linked against locally built, -# shared libraries. -# -# The extra 'noinst' versions of installed programs, as well as test -# programs and programs declared using the special primary prefix -# `noinst`, are all configured with relative `RPATH`s. This means that -# they will continue to work even when the project is relocated to a -# different directory, as long as the internal directory structure -# remains the same. -# -# Note that the standard installation procedure, that places targets -# in system directories according to category (`/usr/local/bin`, -# `/usr/local/lib`, ...), does not in general preserve the relative -# paths between targets with respect to how they occur in your project -# directory. Further more, the standard installation procedure is -# based upon the idea that the final installed location of targets is -# specified and fixed at build time. -# -# As a special option, `generic.mk` can be asked to completely disable -# its support for installation, and instead link all programs as if -# they had been declared as 'noinst' programs in the first place. This -# mode also disables the creation of the extra 'noinst' versions (as -# they would be redundant), and it will disable shared library -# versioning, that is, it will build each library as if no version was -# specified for it (see 'Library versioning' below). This mode is -# enabled by setting the environment variable `ENABLE_NOINST_BUILD` to -# a non-empty value. Be sure to do a `make clean` when you switch -# between 'noinst' and regular mode. -# -# -# Programs that are not installed -# ------------------------------- -# -# Sometimes it is desirable to build a program that is not supposed to -# be installed when running `make install`. One reason could be that -# the program is used only for testing. Such programs are created by -# using the special primary prefix `noinst`, for example: -# -# noinst_PROGRAMS = performance -# -# There is another related category of programs called 'test programs' -# that are both built and executed when running `make test`. These -# programs are created by using the `check` primary prefix, and are -# also not installed: -# -# check_PROGRAMS = test_foo test_bar -# -# It is also possible to create a convenience library that is built -# only when 'test programs' are built. List libraries of this kind in -# `check_LIBRARIES`. -# -# -# Subdirectories -# -------------- -# -# In larger projects it is desirable to organize the source files into -# multiple subdirectories. This can be done in two ways, using a -# single `Makefile` or using multiple `Makefile`s. When using a single -# `Makefile`, refer to the source files using relative paths as -# follows: -# -# myprog_SOURCES = foo/alpha.cpp bar/beta.cpp -# -# The alternative is to use multiple `Makefile`s. This requires one or -# more subdirectories each one with an extra subordinate -# `Makefile`. The top-level `Makefile` must then use the `SUBDIRS` -# variable to list each of the involved subdirectories. When there is -# a dependency between two subdirectories, the top-level `Makefile` -# must declare this. Here is an example: -# -# Makefile: -# SUBDIRS = foo bar -# bar_DEPS = foo -# include generic.mk -# -# foo/Makefile: -# lib_LIBRARIES = util.a -# util_a_SOURCES = ... -# include ../generic.mk -# -# bar/Makefile: -# bin_PROGRAMS = myprog -# myprog_SOURCES = ... -# myprog_LIBS = ../foo/util.a -# -# To declare that a subdirectory `foo` depends on stuff in the current -# directory (presumably libraries), include `.` in `foo_DEPS`. To -# declare that the current directory depends on stuff in a -# subdirectory, list that subdirectory in the `DIR_DEPS` variable as -# in the following example: -# -# Makefile: -# SUBDIRS = util -# DIR_DEPS = util -# bin_PROGRAMS = myprog -# myprog_SOURCES = ... -# myprog_LIBS = util/util.a -# include generic.mk -# -# util/Makefile: -# noinst_LIBRARIES = util.a -# util_a_SOURCES = ... -# include ../generic.mk -# -# FIXME: Mention `PASSIVE_SUBDIRS` (such directories are cleaned but -# not otherwise included during recursive `make` invocations). -# -# -# Compiler and linker flags -# ------------------------- -# -# Extra compiler and linker flags can be specified for each target -# (program or library): -# -# bin_PROGRAMS = myprog -# myprog_SOURCES = foo.cpp bar.cpp -# myprog_CFLAGS = -Wno-long-long -# myprog_LDFLAGS = -lparser -# -# Compiler flags can also be specified for individual object files, -# for example, to add flags just to the compilation of `foo.o`: -# -# foo_o_CFLAGS = -I/opt/parser-1.5/include -# -# Compiler and linker flags can be specified for all targets in a -# directory (the directory containing the `Makefile`) as follows: -# -# DIR_CFLAGS = ... -# DIR_LDFLAGS = ... -# -# In a project that consists of multiple subprojects (each one in its -# own subdirectory and with its own `Makefile`,) compiler and linker -# flags can be specified for all targets in the project by setting -# `PROJECT_CFLAGS` and `PROJECT_LDFLAGS` in `project.mk`: -# -# PROJECT_CFLAGS = ... -# PROJECT_LDFLAGS = ... -# -# All these compiler and linker flag specifications are additive. -# -# -# Debug and coverage analysis modes -# --------------------------------- -# -# foo_o_CFLAGS_OPTIM -# foo_o_CFLAGS_DEBUG -# foo_o_CFLAGS_COVER -# -# -# Library versioning -# ------------------ -# -# lib_LIBRARIES = libmyparser.a -# libmyparser_a_VERSION = 4:0:0 -# -# Format: CURRENT[:REVISION[:AGE]] -# -# At each new public release: -# If the interface has changed at all: -# Increment CURRENT and reset REVISION to zero -# Let COMPAT be the least number such that the new library (in -# its binary form) can be used as a drop-in replacement for -# all previous releases whose CURRENT is greater than or equal -# to COMPAT -# If COMPAT + AGE < CURRENT: -# Increment AGE -# Else: -# Reset AGE to zero -# Else: -# Increment REVISION -# -# The meaning of this version string is identical to the one defined -# by GNU Libtool. See also -# http://www.gnu.org/software/libtool/manual/libtool.html#Libtool-versioning -# -# -# Generated sources -# ----------------- -# -# FIXME: Describe `GENERATED_SOURCES`. -# -# -# Configuration variables -# ----------------------- -# -# All variables listed in the section CONFIG VARIABLES are available -# for modification in `project.mk`, and they may also be overridden on -# the command line. For example, to enable POSIX Threads and disable -# automatic dependency tracking, you could do this: -# -# make CFLAGS_PTHREADS="-pthreads" CFLAGS_AUTODEP="" -# -# If CFLAGS is specified in the environment or on the command line, it -# will replace the value of CFLAGS_GENERAL. Similarly with LDFLAGS and -# ARFLAGS. -# -# If EXTRA_CFLAGS is specified on the command line, its contents will -# be added to CFLAGS_GENERAL. Similarly with LDFLAGS. -# -# If CC, CXX, OCC, OCXX, LD, and AR are specified in the environment -# or on the command line, their values will be respected. -# -# NOTE: When you change the configuration specified via the -# environment or the command line from one invocation of make to the -# next, you should always start with a 'make clean'. MAKE does this -# automatically when you change `project.mk`. -# -# If `CC` is neither specified in the environment nor on the command -# line, `generic.mk` will look for a number of well-known compilers -# (GCC, Clang), and set `CC` accordingly. If CXX or OCC is neither -# specified in the environment nor on the command line, it will be set -# to whatever `CC` is set to. Likewise, if `OCXX` is neither specified -# in the environment nor on the command line, it will be set to -# whatever `OCC` is set to. If `LD` or `AR` is neither specified in -# the environment nor on the command line, `generic.mk` will attempt -# to derive their values from `CC`. -# -# A number of variables are provided to query about the -# detected/specified tool chain: -# -# If `generic.mk` can identify the contents of `CC` as GCC or Clang, -# it sets `CC_IS` to `gcc` or `clang` respectively, and -# `CC_IS_GCC_LIKE` to `yes`. Otherwise it sets both `CC_IS` and -# `CC_IS_GCC_LIKE` to empty strings. Equivalent variables exist for -# `CXX`, `OCC`, `OCXX`, and `LD`. -# -# Additionally, if `IS_CC`, `IS_CXX`, `IS_OCC`, and `IS_OCXX` are all -# equal, then `COMPILER_IS` is set to that value (`gcc` or -# `clang`). Likewise, if all four are GCC-like, then -# `COMPILER_IS_GCC_LIKE` is set to `yes`. -# -# In general, `generic.mk` can identify a compiler or linker as GCC if -# its name (when arguments are stripped away and path is removed) is -# `gcc` or `g++`, or begins with `gcc-` or `g++-`. Likewise with Clang -# if the name is `clang` or `clang++`. -# -# When `COMPILER_IS_GCC_LIKE` is true (not empty), `generic.mk` will -# add a number of sensible GCC-like compiler flags for optimization, -# debugging, profiling, header dependency tracking, and -# more. Likewise, if `LD_IS_GCC_LIKE` is not empty, extra linker flags -# will be added. -# -# If you set `CC` to something that is GCC-like, but is not -# automatically identified as such (e.g. `arm-linux-androideabi-gcc`), -# you can manually override `CC_IS` (or any of the other classifying -# variables) on the `make` command line. When done right, this will -# fix "chained" classification variables, and reenable the automatic -# addition of extra GCC-like compiler/linker flags. -# -# -# Technicalities -# -------------- -# -# Project local files and directories mentioned in variables passed to -# `generic.mk` as part of specifying target, source, or subdirectory -# paths, must consist entirely of letters, digits, `_`, `-`, and `.` -# (all from the ASCII character set). In particular, spaces are not -# allowed. When variable names are constructed from paths, `/`, `-`, -# and `.` are folded to `_`. -# -# The same restriction applies to all installation directories -# (`bindir`, `libdir` `includedir`, etc.). -# -# On the other hand, the value of `DESTDIR` may contain any graphical -# characters from the ASCII character set as well as SPACE and TAB. -# -# Except when you are building in 'code coverage' mode, the absolute -# path to the root of your project may contain any graphical -# characters from ASCII as well as SPACE and TAB. However, when you -# are building in 'code coverage' mode, your project root path must -# adhere to the same restrictions that apply to project local target -# paths passed to `generic.mk`. - - - -# CONFIG VARIABLES - -# The relative path to the root of the include tree. If specified, a -# corresponding include option (`-I`) is added to the compiler command -# line for all object file targets in the project, and the primary -# prefix `subinclude` becomes available in Makefiles contained inside -# the specified directory. -INCLUDE_ROOT = - -CFLAGS_OPTIM = -DNDEBUG -CFLAGS_DEBUG = -CFLAGS_COVER = -CFLAGS_SHARED = -CFLAGS_PTHREADS = -CFLAGS_GENERAL = -CFLAGS_C = -CFLAGS_CXX = -CFLAGS_OBJC = -CFLAGS_ARCH = -CFLAGS_INCLUDE = -CFLAGS_AUTODEP = -LDFLAGS_OPTIM = $(filter-out -D%,$(CFLAGS_OPTIM)) -LDFLAGS_DEBUG = $(filter-out -D%,$(CFLAGS_DEBUG)) -LDFLAGS_COVER = $(filter-out -D%,$(CFLAGS_COVER)) -LDFLAGS_SHARED = -LDFLAGS_PTHREADS = $(CFLAGS_PTHREADS) -LDFLAGS_GENERAL = -LDFLAGS_ARCH = $(CFLAGS_ARCH) -ARFLAGS_GENERAL = csr - -PROJECT_CFLAGS = -PROJECT_CFLAGS_OPTIM = -PROJECT_CFLAGS_DEBUG = -PROJECT_CFLAGS_COVER = -PROJECT_LDFLAGS = -PROJECT_LDFLAGS_OPTIM = -PROJECT_LDFLAGS_DEBUG = -PROJECT_LDFLAGS_COVER = - -LIB_SUFFIX_STATIC = .a -LIB_SUFFIX_SHARED = .so -LIB_SUFFIX_LIBDEPS = .libdeps - -ifneq ($(filter undefined environment,$(origin PROG_SUFFIX)),) -PROG_SUFFIX = -endif - -BASE_DENOM = -OBJ_DENOM_SHARED = .pic -OBJ_DENOM_OPTIM = -OBJ_DENOM_DEBUG = .dbg -OBJ_DENOM_COVER = .cov -LIB_DENOM_OPTIM = -LIB_DENOM_DEBUG = -dbg -LIB_DENOM_COVER = -cov -PROG_DENOM_OPTIM = -PROG_DENOM_DEBUG = -dbg -PROG_DENOM_COVER = -cov - -# When set to an empty value, 'make install' will not install the -# static versions of the libraries mentioned in lib_LIBRARIES, and a -# plain 'make' will not even build them. When set to a nonempty value, -# the opposite is true. -ENABLE_INSTALL_STATIC_LIBS = - -# When set to an empty value, 'make install' will not install the -# debug versions of the libraries mentioned in lib_LIBRARIES, and a -# plain 'make' will not even build them. When set to a nonempty value, -# the opposite is true. -ENABLE_INSTALL_DEBUG_LIBS = - -# When set to an empty value, 'make install' will not install the -# debug versions of the programs mentioned in bin_PROGRAMS, and a -# plain 'make' will not even build them. When set to a nonempty value, -# the opposite is true. -ENABLE_INSTALL_DEBUG_PROGS = - -# Use this if you want to install only a subset of what is usually -# installed. For example, to produce a separate binary and development -# package for a library product, you can run 'make install -# INSTALL_FILTER=shared-libs,progs' for the binary package and 'make -# install INSTALL_FILTER=static-libs,dev-progs,headers' for the -# development package. This filter affects uninstallation the same way -# it affects installation. -INSTALL_FILTER = shared-libs,static-libs,progs,dev-progs,headers - -# Installation (GNU style) -prefix = /usr/local -exec_prefix = $(prefix) -bindir = $(exec_prefix)/bin -sbindir = $(exec_prefix)/sbin -libdir = $(if $(USE_LIB64),$(exec_prefix)/lib64,$(exec_prefix)/lib) -libexecdir = $(exec_prefix)/libexec -includedir = $(prefix)/include -INSTALL = install -INSTALL_DIR = $(INSTALL) -d -INSTALL_DATA = $(INSTALL) -m 644 -INSTALL_LIBRARY = $(INSTALL) -m 644 -INSTALL_PROGRAM = $(INSTALL) - -VALGRIND ?= valgrind -VALGRIND_FLAGS ?= --quiet --track-origins=yes --leak-check=yes --leak-resolution=low - -# Alternative filesystem root for installation -DESTDIR = - - - -# UTILITY CONSTANTS AND FUNCTIONS - -EMPTY := -SPACE := $(EMPTY) $(EMPTY) -COMMA := , -APOSTROPHE := $(patsubst "%",%,"'") - -define NEWLINE -$(EMPTY) -$(EMPTY) -endef - -define TAB - $(EMPTY) -endef - -NL_TAB := $(NEWLINE)$(TAB) - -IDENTITY = $(1) - -IS_EQUAL_TO = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) - -# ARGS: prefix, string -IS_PREFIX_OF = $(findstring .$(call IS_PREFIX_OF_1,$(1)),.$(call IS_PREFIX_OF_1,$(2))) -IS_PREFIX_OF_1 = $(subst .,:d:,$(subst :,:c:,$(1))) - -COND_PREPEND = $(if $(2),$(1)$(2)) -COND_APPEND = $(if $(1),$(1)$(2)) - -LIST_CONCAT = $(if $(and $(1),$(2)),$(1) $(2),$(1)$(2)) -LIST_REVERSE = $(if $(1),$(call LIST_CONCAT,$(call LIST_REVERSE,$(wordlist 2,$(words $(1)),$(1))),$(firstword $(1)))) - -# ARGS: predicate, list, optional_predicate_arg -STABLE_PARTITION = $(call STABLE_PARTITION_1,$(1),$(strip $(2)),$(3)) -STABLE_PARTITION_1 = $(if $(2),$(call STABLE_PARTITION_2,$(1),$(wordlist 2,$(words $(2)),$(2)),$(3),$(4),$(5),$(word 1,$(2))),$(strip $(4) $(5))) -STABLE_PARTITION_2 = $(if $(call $(1),$(6),$(3)),$(call STABLE_PARTITION_1,$(1),$(2),$(3),$(4) $(6),$(5)),$(call STABLE_PARTITION_1,$(1),$(2),$(3),$(4),$(5) $(6))) - -# ARGS: predicate, list, optional_predicate_arg -# Expands to first entry that satisfies the predicate, or the empty string if no entry satsifies it. -FIND = $(call FIND_1,$(1),$(strip $(2)),$(3)) -FIND_1 = $(if $(2),$(call FIND_2,$(1),$(2),$(3),$(word 1,$(2)))) -FIND_2 = $(if $(call $(1),$(4),$(3)),$(4),$(call FIND_1,$(1),$(wordlist 2,$(words $(2)),$(2)),$(3))) - -# ARGS: func, init_accum, list -FOLD_LEFT = $(call FOLD_LEFT_1,$(1),$(2),$(strip $(3))) -FOLD_LEFT_1 = $(if $(3),$(call FOLD_LEFT_1,$(1),$(call $(1),$(2),$(word 1,$(3))),$(wordlist 2,$(words $(3)),$(3))),$(2)) - -# ARGS: list_1, list_2 -UNION = $(call FOLD_LEFT,UNION_1,$(1),$(2)) -UNION_1 = $(if $(call FIND,IS_EQUAL_TO,$(1),$(2)),$(1),$(if $(1),$(1) $(2),$(2))) - -# ARGS: list -REMOVE_DUPES = $(call UNION,,$(1)) - -# ARGS: predicate, list, optional_predicate_arg -FILTER = $(call FILTER_1,$(1),$(strip $(2)),$(3)) -FILTER_1 = $(if $(2),$(call FILTER_1,$(1),$(wordlist 2,$(words $(2)),$(2)),$(3),$(call LIST_CONCAT,$(4),$(if $(call $(1),$(word 1,$(2)),$(3)),$(word 1,$(2))))),$(4)) - -# ARGS: list -REMOVE_PREFIXES = $(call FILTER,REMOVE_PREFIXES_1,$(1),$(1)) -REMOVE_PREFIXES_1 = $(if $(call FIND,REMOVE_PREFIXES_2,$(2),$(1)),,x) -REMOVE_PREFIXES_2 = $(if $(call IS_EQUAL_TO,$(2),$(1)),,$(call IS_PREFIX_OF,$(2),$(1))) - -HIDE_SPACE = $(subst $(TAB),:t:,$(subst $(SPACE),:s:,$(subst :,:c:,$(1)))) -UNHIDE_SPACE = $(subst :c:,:,$(subst :s:,$(SPACE),$(subst :t:,$(TAB),$(1)))) - -# If `a` and `b` are relative or absolute paths (without a final -# slash), and `b` points to a directory, then PATH_DIFF(a,b) expands -# to the relative path from `b` to `a`. If abspath(a) and abspath(b) -# are the same path, then PATH_DIFF(a,b) expands to the empty string. -PATH_DIFF = $(call PATH_DIFF_2,$(call PATH_DIFF_1,$(1)),$(call PATH_DIFF_1,$(2))) -PATH_DIFF_1 = $(subst /,$(SPACE),$(abspath $(call HIDE_SPACE,$(if $(filter /%,$(1)),$(1),$(abspath .)/$(1))))) -PATH_DIFF_2 = $(if $(and $(1),$(2),$(call IS_EQUAL_TO,$(word 1,$(1)),$(word 1,$(2)))),$(call PATH_DIFF_2,$(wordlist 2,$(words $(1)),$(1)),$(wordlist 2,$(words $(2)),$(2))),$(call UNHIDE_SPACE,$(subst $(SPACE),/,$(strip $(patsubst %,..,$(2)) $(1))))) - -# If `p` is already an absolute path, or if `optional_abs_base` is not -# specified, then `MAKE_ABS_PATH(p, optional_abs_base)` expands to -# `abspath(p)`. Otherwise, `optional_abs_base` must be an absolute -# path, and this function expands to -# `abspath(optional_abs_base+'/'+p)`. As opposed to the built-in -# function `abspath()`, this function properly handles paths that -# contain spaces. -MAKE_ABS_PATH = $(call UNHIDE_SPACE,$(abspath $(call HIDE_SPACE,$(if $(filter /%,$(1)),$(1),$(or $(2),$(abspath .))/$(1))))) - -# If `p` and `base` are paths, then MAKE_REL_PATH(p,base) expands to -# the relative path from abspath(base) to abspath(p). If the two paths -# are equal, it expands to `.`. If `base` is unspecified, it defaults -# to `.`. -MAKE_REL_PATH = $(or $(call PATH_DIFF,$(1),$(or $(2),.)),.) - -IS_SAME_PATH_AS = $(call IS_EQUAL_TO,$(call CANON_PATH_HIDE_SPACE,$(1)),$(call CANON_PATH_HIDE_SPACE,$(2))) -IS_PATH_CONTAINED_IN = $(call IS_PREFIX_OF,$(call CANON_PATH_HIDE_SPACE,$(2))/,$(call CANON_PATH_HIDE_SPACE,$(1))) -CANON_PATH_HIDE_SPACE = $(abspath $(call HIDE_SPACE,$(patsubst %/,%,$(if $(filter /%,$(1)),$(1),$(abspath .)/$(1))))) - -# Only a `*` is recognized, and at most one is allowed per component -# of the wildcard path. Matching is guaranteed to fail if any -# component of the wildcard path has more than one star and the -# non-wildcard path has no stars in it. -# -# ARGS: wildcard_path, path -WILDCARD_PATH_MATCH = $(and $(call WILDCARD_PATH_MATCH_1,$(dir $(1)),$(dir $(2))),$(filter $(subst *,%,$(subst %,\%,$(notdir $(1)))),$(notdir $(2)))) -WILDCARD_PATH_MATCH_1 = $(if $(filter-out / ./,$(1) $(2)),$(if $(filter / ./,$(1) $(2)),,$(call WILDCARD_PATH_MATCH,$(patsubst %/,%,$(1)),$(patsubst %/,%,$(2)))),$(filter // ././,$(1)$(2))) - -# ARGS: wildcard_paths, paths -WILDCARD_PATHS_FILTER_OUT = $(foreach x,$(2),$(if $(strip $(foreach y,$(1),$(call WILDCARD_PATH_MATCH,$(y),$(x)))),,$(x))) - -# Escape space, tab, and the following 21 characters using backslashes: !"#$&'()*;<>?[\]`{|}~ -SHELL_ESCAPE = $(shell printf '%s\n' '$(call SHELL_ESCAPE_1,$(1))' | sed $(SHELL_ESCAPE_2)) -SHELL_ESCAPE_1 = $(subst $(APOSTROPHE),$(APOSTROPHE)\$(APOSTROPHE)$(APOSTROPHE),$(1)) -SHELL_ESCAPE_2 = 's/\([]$(TAB)$(SPACE)!"\#$$&'\''()*;<>?[\`{|}~]\)/\\\1/g' - -HAVE_CMD = $(shell which $(word 1,$(1))) - -# ARGS: command, prefix_to_class_map -# Returns empty if identification fails -IDENT_CMD = $(call IDENT_CMD_1,$(notdir $(word 1,$(1))),$(2)) -IDENT_CMD_1 = $(word 1,$(foreach x,$(2),$(call IDENT_CMD_2,$(1),$(subst :,$(SPACE),$(x))))) -IDENT_CMD_2 = $(call IDENT_CMD_3,$(1),$(word 1,$(2)),$(word 2,$(2))) -IDENT_CMD_3 = $(if $(call IS_PREFIX_OF,$(2)-,$(1)-),$(3)) - -# ARGS: command, subsitutions -# Returns empty if mapping fails -MAP_CMD = $(call MAP_CMD_1,$(word 1,$(1)),$(wordlist 2,$(words $(1)),$(1)),$(2)) -MAP_CMD_1 = $(call MAP_CMD_2,$(if $(findstring /,$(1)),$(dir $(1))),$(notdir $(1)),$(2),$(3)) -MAP_CMD_2 = $(call MAP_CMD_3,$(1),$(word 1,$(foreach x,$(4),$(call MAP_CMD_4,$(x),$(2)))),$(3)) -MAP_CMD_3 = $(if $(2),$(call LIST_CONCAT,$(1)$(2),$(3))) -MAP_CMD_4 = $(call MAP_CMD_5,$(subst :,$(SPACE),$(1)),$(2)) -MAP_CMD_5 = $(call MAP_CMD_6,-$(word 1,$(1))-,-$(word 2,$(1))-,-$(2)-) -MAP_CMD_6 = $(if $(findstring $(1),$(3)),$(call MAP_CMD_7,$(patsubst -%-,%,$(subst $(1),$(2),$(3))))) -MAP_CMD_7 = $(if $(call HAVE_CMD,$(1)),$(1)) - -CAT_OPT_FILE = $(shell cat $(1) 2>/dev/null) - -# Library for non-negative integer arithmetic. -# -# Note: It is an error if a numeric (unencoded) argument is greater -# than 65536. -# -# This implementation is an adaptation of John Graham-Cumming's work at -# http://www.cmcrossroads.com/article/learning-gnu-make-functions-arithmetic -INT_ADD = $(call INT_DEC,$(call INT_ADD_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2)))) -INT_SUB = $(call INT_DEC,$(call INT_SUB_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2)))) -INT_MUL = $(call INT_DEC,$(call INT_MUL_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2)))) -INT_DIV = $(call INT_DEC,$(call INT_DIV_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2)))) -INT_MAX = $(call INT_DEC,$(call INT_MAX_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2)))) -INT_MIN = $(call INT_DEC,$(call INT_MIN_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2)))) -INT_EQ = $(call INT_EQ_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2))) -INT_NE = $(call INT_NE_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2))) -INT_GT = $(call INT_GT_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2))) -INT_LT = $(call INT_LT_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2))) -INT_GTE = $(call INT_GTE_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2))) -INT_LTE = $(call INT_LTE_E,$(call INT_ENC,$(1)),$(call INT_ENC,$(2))) -INT_ADD_E = $(1) $(2) -INT_SUB_E = $(if $(call INT_GTE_E,$(1),$(2)),$(filter-out xx,$(join $(1),$(2))),$(error Subtraction underflow)) -INT_MUL_E = $(foreach a,$(1),$(2)) -INT_DIV_E = $(if $(filter-out $(words $(2)),0),$(call INT_DIV_2,$(1),$(2)),$(error Division by zero)) -INT_DIV_2 = $(if $(call INT_GTE_E,$(1),$(2)),x $(call INT_DIV_2,$(call INT_SUB_E,$(1),$(2)),$(2))) -INT_MAX_E = $(subst xx,x,$(join $(1),$(2))) -INT_MIN_E = $(subst xx,x,$(filter xx,$(join $(1),$(2)))) -INT_EQ_E = $(filter $(words $(1)),$(words $(2))) -INT_NE_E = $(filter-out $(words $(1)),$(words $(2))) -INT_GT_E = $(filter-out $(words $(2)),$(words $(call INT_MAX_E,$(1),$(2)))) -INT_LT_E = $(filter-out $(words $(1)),$(words $(call INT_MAX_E,$(1),$(2)))) -INT_GTE_E = $(call INT_GT_E,$(1),$(2))$(call INT_EQ_E,$(1),$(2)) -INT_LTE_E = $(call INT_LT_E,$(1),$(2))$(call INT_EQ_E,$(1),$(2)) -# More efficient increment / decrement -INT_INC_E = $(1) x -INT_DEC_E = $(wordlist 2,$(words $(1)),$(1)) -# More efficient double / halve -INT_DBL_E = $(1) $(1) -INT_HLV_E = $(subst xx,x,$(filter-out xy x y,$(join $(1),$(foreach a,$(1),y x)))) -# Encode / decode -INT_DEC = $(words $(1)) -INT_ENC = $(wordlist 1,$(1),$(INT_65536)) -INT_16 := x x x x x x x x x x x x x x x -INT_65536 := $(foreach a,$(INT_16),$(foreach b,$(INT_16),$(foreach c,$(INT_16),$(INT_16)))) - - - -# PLATFORM SPECIFICS - -OS := $(shell uname) -ARCH := $(shell uname -m) - -ifeq ($(OS),Darwin) - LIB_SUFFIX_SHARED = .dylib -endif - -USE_LIB64 = -ifeq ($(OS),Linux) - IS_64BIT = $(filter x86_64 ia64,$(ARCH)) - ifneq ($(IS_64BIT),) - ifeq ($(shell [ -e /etc/redhat-release -o -e /etc/SuSE-release ] && echo yes),yes) - USE_LIB64 = 1 - else ifneq ($(shell [ -e /etc/system-release ] && grep Amazon /etc/system-release),) - USE_LIB64 = 1 - endif - endif -endif - - - -# SETUP A GCC-LIKE TOOL CHAIN IF POSSIBLE - -# If CC is not specified, search PATH for these compilers in the -# specified order. -ifeq ($(OS),Darwin) - COMPILER_DETECT_LIST = clang llvm-gcc gcc -else - COMPILER_DETECT_LIST = gcc clang -endif - -# Compiler identification. Maps command prefix to compiler class. -COMPILER_IDENT_MAP = gcc:gcc g++:gcc llvm-gcc:gcc llvm-g++:gcc clang:clang clang++:clang - -# Compiler classes that are mostly like GCC. -GCC_LIKE_COMPILERS = gcc clang - -# How to map C compiler to corresponding C++ linker. -CC_TO_CXXL_MAP = gcc:g++ g++:g++ clang:clang++ clang++:clang++ - -# How to map C compiler to corresponding archiver (static libraries). -CC_TO_AR_MAP = gcc:gcc-ar g++:gcc-ar gcc:ar g++:ar clang:clang-ar clang++:clang-ar clang:ar clang++:ar - -DETECT_COMPILER = $(call FIND,HAVE_CMD,$(COMPILER_DETECT_LIST)) -IDENT_COMPILER = $(call IDENT_CMD,$(1),$(COMPILER_IDENT_MAP)) -CLASS_IS_GCC_LIKE = $(if $(filter $(GCC_LIKE_COMPILERS),$(1)),yes) - -# C compiler -CC_SPECIFIED := $(filter-out undefined default,$(origin CC)) -ifeq ($(CC_SPECIFIED),) - # CC was not specified - X := $(call DETECT_COMPILER) - ifneq ($(X),) - CC := $(X) - endif -endif -CC_IS := $(call IDENT_COMPILER,$(CC)) -CC_IS_GCC_LIKE := $(call CLASS_IS_GCC_LIKE,$(CC_IS)) - -# C++ compiler -CXX_SPECIFIED := $(filter-out undefined default,$(origin CXX)) -ifeq ($(CXX_SPECIFIED),) - # CXX was not specified - CXX := $(CC) - CXX_IS := $(CC_IS) - CXX_IS_GCC_LIKE := $(CC_IS_GCC_LIKE) -else - CXX_IS := $(call IDENT_COMPILER,$(CXX)) - CXX_IS_GCC_LIKE := $(call CLASS_IS_GCC_LIKE,$(CXX_IS)) -endif - -# Objective-C compiler -OCC_SPECIFIED := $(filter-out undefined default,$(origin OCC)) -ifeq ($(OCC_SPECIFIED),) - # OCC was not specified - OCC := $(CC) - OCC_IS := $(CC_IS) - OCC_IS_GCC_LIKE := $(CC_IS_GCC_LIKE) -else - OCC_IS := $(call IDENT_COMPILER,$(OCC)) - OCC_IS_GCC_LIKE := $(call CLASS_IS_GCC_LIKE,$(OCC_IS)) -endif - -# Objective-C++ compiler -OCXX_SPECIFIED := $(filter-out undefined default,$(origin OCXX)) -ifeq ($(OCXX_SPECIFIED),) - # OCXX was not specified - OCXX := $(OCC) - OCXX_IS := $(OCC_IS) - OCXX_IS_GCC_LIKE := $(OCC_IS_GCC_LIKE) -else - OCXX_IS := $(call IDENT_COMPILER,$(OCXX)) - OCXX_IS_GCC_LIKE := $(call CLASS_IS_GCC_LIKE,$(OCXX_IS)) -endif - -COMPILER_IS = $(if $(word 2,$(call REMOVE_DUPES,x$(CC_IS) x$(CXX_IS) x$(OCC_IS) x$(OCXX_IS))),,$(CC_IS)) -COMPILER_IS_GCC_LIKE := $(and $(CC_IS_GCC_LIKE),$(CXX_IS_GCC_LIKE),$(OCC_IS_GCC_LIKE),$(OCXX_IS_GCC_LIKE)) - -ifneq ($(COMPILER_IS_GCC_LIKE),) - CFLAGS_OPTIM = -O3 -DNDEBUG - CFLAGS_DEBUG = -ggdb - CFLAGS_COVER = --coverage - CFLAGS_SHARED = -fPIC -DPIC - CFLAGS_GENERAL = -Wall - CFLAGS_AUTODEP = -MMD -MP -endif - -# Linker -X := $(EMPTY) -LD_SPECIFIED = $(filter-out undefined default,$(origin LD)) -ifeq ($(LD_SPECIFIED),) - # LD was not specified - ifneq ($(CC_IS_GCC_LIKE),) - X := $(call MAP_CMD,$(CC),$(CC_TO_CXXL_MAP)) - ifneq ($(X),) - LD := $(X) - LD_IS := $(CC_IS) - LD_IS_GCC_LIKE := yes - endif - endif -endif -ifeq ($(X),) - LD_IS := $(call IDENT_COMPILER,$(LD)) - LD_IS_GCC_LIKE := $(call CLASS_IS_GCC_LIKE,$(LD_IS)) -endif -ifneq ($(LD_IS_GCC_LIKE),) - LDFLAGS_SHARED = -shared -endif - -# Archiver (static libraries) -AR_SPECIFIED = $(filter-out undefined default,$(origin AR)) -ifeq ($(AR_SPECIFIED),) - # AR was not specified - ifneq ($(CC_IS_GCC_LIKE),) - X := $(call MAP_CMD,$(CC),$(CC_TO_AR_MAP)) - ifneq ($(X),) - AR := $(X) - endif - endif -endif - - - -# LOAD PROJECT SPECIFIC CONFIGURATION - -EXTRA_CFLAGS = -EXTRA_LDFLAGS = - -GENERIC_MK := $(lastword $(MAKEFILE_LIST)) -GENERIC_MK_DIR := $(patsubst %/,%,$(dir $(GENERIC_MK))) -PROJECT_MK := $(GENERIC_MK_DIR)/project.mk -DEP_MAKEFILES := Makefile $(GENERIC_MK) -ifneq ($(wildcard $(PROJECT_MK)),) - DEP_MAKEFILES += $(PROJECT_MK) - include $(PROJECT_MK) -endif - -ifneq ($(INCLUDE_ROOT),) - REL_INCLUDE_ROOT := $(call MAKE_REL_PATH,$(dir $(GENERIC_MK))/$(INCLUDE_ROOT)) -endif - -ROOT_INC_FLAG := $(EMPTY) -ROOT_INC_FLAG_COVER := $(EMPTY) -ifneq ($(REL_INCLUDE_ROOT),) - ROOT_INC_FLAG += -I$(REL_INCLUDE_ROOT) - ROOT_INC_FLAG_COVER += -I$(call MAKE_ABS_PATH,$(REL_INCLUDE_ROOT)) -endif - - - -# SETUP BUILD COMMANDS - -CFLAGS_SPECIFIED := $(filter-out undefined default,$(origin CFLAGS)) -LDFLAGS_SPECIFIED := $(filter-out undefined default,$(origin LDFLAGS)) -ARFLAGS_SPECIFIED := $(filter-out undefined default,$(origin ARFLAGS)) -ifneq ($(CFLAGS_SPECIFIED),) -CFLAGS_GENERAL = $(CFLAGS) -endif -ifneq ($(LDFLAGS_SPECIFIED),) -LDFLAGS_GENERAL = $(LDFLAGS) -endif -ifneq ($(ARFLAGS_SPECIFIED),) -ARFLAGS_GENERAL = $(ARFLAGS) -endif -CFLAGS_GENERAL += $(EXTRA_CFLAGS) -LDFLAGS_GENERAL += $(EXTRA_LDFLAGS) - -CC_STATIC_OPTIM = $(CC) $(CFLAGS_OPTIM) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -CC_SHARED_OPTIM = $(CC) $(CFLAGS_OPTIM) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -CC_STATIC_DEBUG = $(CC) $(CFLAGS_DEBUG) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -CC_SHARED_DEBUG = $(CC) $(CFLAGS_DEBUG) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -CC_STATIC_COVER = $(CC) $(CFLAGS_COVER) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(ROOT_INC_FLAG_COVER) $(CFLAGS_GENERAL) -CC_SHARED_COVER = $(CC) $(CFLAGS_COVER) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(ROOT_INC_FLAG_COVER) $(CFLAGS_GENERAL) - -CXX_STATIC_OPTIM = $(CXX) $(CFLAGS_OPTIM) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -CXX_SHARED_OPTIM = $(CXX) $(CFLAGS_OPTIM) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -CXX_STATIC_DEBUG = $(CXX) $(CFLAGS_DEBUG) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -CXX_SHARED_DEBUG = $(CXX) $(CFLAGS_DEBUG) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -CXX_STATIC_COVER = $(CXX) $(CFLAGS_COVER) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(ROOT_INC_FLAG_COVER) $(CFLAGS_GENERAL) -CXX_SHARED_COVER = $(CXX) $(CFLAGS_COVER) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(ROOT_INC_FLAG_COVER) $(CFLAGS_GENERAL) - -OCC_STATIC_OPTIM = $(OCC) $(CFLAGS_OPTIM) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(CFLAGS_OBJC) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -OCC_SHARED_OPTIM = $(OCC) $(CFLAGS_OPTIM) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(CFLAGS_OBJC) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -OCC_STATIC_DEBUG = $(OCC) $(CFLAGS_DEBUG) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(CFLAGS_OBJC) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -OCC_SHARED_DEBUG = $(OCC) $(CFLAGS_DEBUG) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(CFLAGS_OBJC) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -OCC_STATIC_COVER = $(OCC) $(CFLAGS_COVER) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(CFLAGS_OBJC) $(ROOT_INC_FLAG_COVER) $(CFLAGS_GENERAL) -OCC_SHARED_COVER = $(OCC) $(CFLAGS_COVER) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_C) $(CFLAGS_OBJC) $(ROOT_INC_FLAG_COVER) $(CFLAGS_GENERAL) - -OCXX_STATIC_OPTIM = $(OCXX) $(CFLAGS_OPTIM) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(CFLAGS_OBJC) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -OCXX_SHARED_OPTIM = $(OCXX) $(CFLAGS_OPTIM) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(CFLAGS_OBJC) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -OCXX_STATIC_DEBUG = $(OCXX) $(CFLAGS_DEBUG) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(CFLAGS_OBJC) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -OCXX_SHARED_DEBUG = $(OCXX) $(CFLAGS_DEBUG) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(CFLAGS_OBJC) $(ROOT_INC_FLAG) $(CFLAGS_GENERAL) -OCXX_STATIC_COVER = $(OCXX) $(CFLAGS_COVER) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(CFLAGS_OBJC) $(ROOT_INC_FLAG_COVER) $(CFLAGS_GENERAL) -OCXX_SHARED_COVER = $(OCXX) $(CFLAGS_COVER) $(CFLAGS_SHARED) $(CFLAGS_PTHREADS) $(CFLAGS_CXX) $(CFLAGS_OBJC) $(ROOT_INC_FLAG_COVER) $(CFLAGS_GENERAL) - -CFLAGS_OTHER = $(CFLAGS_ARCH) $(CFLAGS_INCLUDE) $(CFLAGS_AUTODEP) - -LD_LIB_OPTIM = $(LD) $(LDFLAGS_SHARED) $(LDFLAGS_OPTIM) $(LDFLAGS_PTHREADS) $(LDFLAGS_GENERAL) -LD_LIB_DEBUG = $(LD) $(LDFLAGS_SHARED) $(LDFLAGS_DEBUG) $(LDFLAGS_PTHREADS) $(LDFLAGS_GENERAL) -LD_LIB_COVER = $(LD) $(LDFLAGS_SHARED) $(LDFLAGS_COVER) $(LDFLAGS_PTHREADS) $(LDFLAGS_GENERAL) -LD_PROG_OPTIM = $(LD) $(LDFLAGS_OPTIM) $(LDFLAGS_PTHREADS) $(LDFLAGS_GENERAL) -LD_PROG_DEBUG = $(LD) $(LDFLAGS_DEBUG) $(LDFLAGS_PTHREADS) $(LDFLAGS_GENERAL) -LD_PROG_COVER = $(LD) $(LDFLAGS_COVER) $(LDFLAGS_PTHREADS) $(LDFLAGS_GENERAL) - - - -BASE_DENOM_2 := $(if $(BASE_DENOM),-$(BASE_DENOM)) -SUFFIX_OBJ_STATIC_OPTIM := $(BASE_DENOM_2)$(OBJ_DENOM_OPTIM).o -SUFFIX_OBJ_SHARED_OPTIM := $(BASE_DENOM_2)$(OBJ_DENOM_OPTIM)$(OBJ_DENOM_SHARED).o -SUFFIX_OBJ_STATIC_DEBUG := $(BASE_DENOM_2)$(OBJ_DENOM_DEBUG).o -SUFFIX_OBJ_SHARED_DEBUG := $(BASE_DENOM_2)$(OBJ_DENOM_DEBUG)$(OBJ_DENOM_SHARED).o -SUFFIX_OBJ_STATIC_COVER := $(BASE_DENOM_2)$(OBJ_DENOM_COVER).o -SUFFIX_OBJ_SHARED_COVER := $(BASE_DENOM_2)$(OBJ_DENOM_COVER)$(OBJ_DENOM_SHARED).o -SUFFIX_LIB_STATIC_OPTIM := $(BASE_DENOM_2)$(LIB_DENOM_OPTIM)$(LIB_SUFFIX_STATIC) -SUFFIX_LIB_SHARED_OPTIM := $(BASE_DENOM_2)$(LIB_DENOM_OPTIM)$(LIB_SUFFIX_SHARED) -SUFFIX_LIB_STATIC_DEBUG := $(BASE_DENOM_2)$(LIB_DENOM_DEBUG)$(LIB_SUFFIX_STATIC) -SUFFIX_LIB_SHARED_DEBUG := $(BASE_DENOM_2)$(LIB_DENOM_DEBUG)$(LIB_SUFFIX_SHARED) -SUFFIX_LIB_STATIC_COVER := $(BASE_DENOM_2)$(LIB_DENOM_COVER)$(LIB_SUFFIX_STATIC) -SUFFIX_LIB_SHARED_COVER := $(BASE_DENOM_2)$(LIB_DENOM_COVER)$(LIB_SUFFIX_SHARED) -SUFFIX_PROG_OPTIM := $(BASE_DENOM_2)$(PROG_DENOM_OPTIM)$(PROG_SUFFIX) -SUFFIX_PROG_DEBUG := $(BASE_DENOM_2)$(PROG_DENOM_DEBUG)$(PROG_SUFFIX) -SUFFIX_PROG_COVER := $(BASE_DENOM_2)$(PROG_DENOM_COVER)$(PROG_SUFFIX) - -GET_FLAGS = $($(1)) $($(1)_$(2)) -FOLD_TARGET = $(subst /,_,$(subst .,_,$(subst -,_,$(1)))) -GET_LIBRARY_STEM = $(patsubst %.a,%,$(1)) -GET_OBJECTS_FOR_TARGET = $(addsuffix $(2),$(basename $($(call FOLD_TARGET,$(1))_SOURCES))) -GET_LDFLAGS_FOR_TARGET = $(foreach x,PROJECT DIR $(call FOLD_TARGET,$(1)),$(call GET_FLAGS,$(x)_LDFLAGS,$(2))) -GET_DEPS_FOR_TARGET = $($(call FOLD_TARGET,$(1))_DEPS) -GET_LIBRARY_VERSION = $(call GET_LIBRARY_VERSION_2,$(strip $($(call FOLD_TARGET,$(1))_VERSION))) -GET_LIBRARY_VERSION_2 = $(if $(1),$(wordlist 1,3,$(subst :, ,$(1):0:0))) - -PRIMARIES := HEADERS LIBRARIES PROGRAMS -PRIMARY_PREFIXES := bin sbin lib libexec include -INCLUDE_SUBDIR := - -USING_SUBINCLUDE := $(strip $(foreach x,$(PRIMARIES) $(PRIMARIES)_EXTRA_UNINSTALL,$(subinclude_$(x))$(nobase_subinclude_$(x)))) -ifneq ($(USING_SUBINCLUDE),) -ifeq ($(REL_INCLUDE_ROOT),) -$(error Cannot determine installation directory for `subinclude` when `INCLUDE_ROOT` is unspecified) -endif -INSIDE_INCLUDE_ROOT := $(or $(call IS_SAME_PATH_AS,.,$(REL_INCLUDE_ROOT)),$(call IS_PATH_CONTAINED_IN,.,$(REL_INCLUDE_ROOT))) -ifeq ($(INSIDE_INCLUDE_ROOT),) -$(error Cannot determine installation directory for `subinclude` from outside `INCLUDE_ROOT`) -endif -PRIMARY_PREFIXES += subinclude -INCLUDE_SUBDIR := $(call PATH_DIFF,.,$(REL_INCLUDE_ROOT)) -endif - -PRIMARY_PREFIXES += $(EXTRA_PRIMARY_PREFIXES) - -# ARGS: primary_prefix -GET_INSTALL_DIR = $(if $(filter subinclude,$(1)),$(call GET_ROOT_INSTALL_DIR,include)$(call COND_PREPEND,/,$(INCLUDE_SUBDIR)),$(call GET_ROOT_INSTALL_DIR,$(1))) -GET_ROOT_INSTALL_DIR = $(if $($(1)dir),$(patsubst %/,%,$($(1)dir)),$(error Variable `$(1)dir` was not specified)) - -# ARGS: folded_lib_target, install_dir -define RECORD_LIB_INSTALL_DIR -GMK_INSTALL_DIR_$(1) = $(2) -endef - -# ARGS: primary_prefix, install_dir -RECORD_LIB_INSTALL_DIRS = \ -$(foreach x,$($(1)_LIBRARIES),$(eval $(call RECORD_LIB_INSTALL_DIR,$(call FOLD_TARGET,$(x)),$(2))))\ -$(foreach x,$(nobase_$(1)_LIBRARIES),$(eval $(call RECORD_LIB_INSTALL_DIR,$(call FOLD_TARGET,$(x)),$(patsubst %/,%,$(dir $(2)/$(x)))))) - -$(foreach x,$(PRIMARY_PREFIXES),$(call RECORD_LIB_INSTALL_DIRS,$(x),$(call GET_INSTALL_DIR,$(x)))) - -# ARGS: installable_lib_target -GET_INSTALL_DIR_FOR_LIB_TARGET = $(value GMK_INSTALL_DIR_$(call FOLD_TARGET,$(1))) - -INST_PROGRAMS := $(strip $(foreach x,$(PRIMARY_PREFIXES),$($(x)_PROGRAMS) $(nobase_$(x)_PROGRAMS))) -INST_LIBRARIES := $(strip $(foreach x,$(PRIMARY_PREFIXES),$($(x)_LIBRARIES) $(nobase_$(x)_LIBRARIES))) - -LIBRARIES := $(INST_LIBRARIES) $(noinst_LIBRARIES) $(check_LIBRARIES) -PROGRAMS := $(INST_PROGRAMS) $(DEV_PROGRAMS) $(noinst_PROGRAMS) $(check_PROGRAMS) - -SOURCE_DIRS := $(patsubst ././,./,$(patsubst %,./%,$(call REMOVE_DUPES,$(dir $(foreach x,$(LIBRARIES) $(PROGRAMS),$($(call FOLD_TARGET,$(x))_SOURCES)))))) - -OBJECTS_STATIC_OPTIM := $(foreach x,$(LIBRARIES) $(PROGRAMS),$(call GET_OBJECTS_FOR_TARGET,$(x),$(SUFFIX_OBJ_STATIC_OPTIM))) -OBJECTS_SHARED_OPTIM := $(foreach x,$(LIBRARIES),$(call GET_OBJECTS_FOR_TARGET,$(x),$(SUFFIX_OBJ_SHARED_OPTIM))) -OBJECTS_STATIC_DEBUG := $(foreach x,$(LIBRARIES) $(PROGRAMS),$(call GET_OBJECTS_FOR_TARGET,$(x),$(SUFFIX_OBJ_STATIC_DEBUG))) -OBJECTS_SHARED_DEBUG := $(foreach x,$(LIBRARIES),$(call GET_OBJECTS_FOR_TARGET,$(x),$(SUFFIX_OBJ_SHARED_DEBUG))) -OBJECTS_STATIC_COVER := $(foreach x,$(LIBRARIES) $(PROGRAMS),$(call GET_OBJECTS_FOR_TARGET,$(x),$(SUFFIX_OBJ_STATIC_COVER))) -OBJECTS_SHARED_COVER := $(foreach x,$(LIBRARIES),$(call GET_OBJECTS_FOR_TARGET,$(x),$(SUFFIX_OBJ_SHARED_COVER))) -OBJECTS := $(sort $(OBJECTS_STATIC_OPTIM) $(OBJECTS_SHARED_OPTIM) $(OBJECTS_STATIC_DEBUG) $(OBJECTS_SHARED_DEBUG) $(OBJECTS_STATIC_COVER) $(OBJECTS_SHARED_COVER)) - -TARGETS_LIB_STATIC_OPTIM := $(foreach x,$(INST_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_STATIC_OPTIM)) -TARGETS_LIB_SHARED_OPTIM := $(foreach x,$(INST_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_SHARED_OPTIM)) -TARGETS_LIB_STATIC_DEBUG := $(foreach x,$(INST_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_STATIC_DEBUG)) -TARGETS_LIB_SHARED_DEBUG := $(foreach x,$(INST_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_SHARED_DEBUG)) -TARGETS_LIB_STATIC_COVER := $(foreach x,$(INST_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_STATIC_COVER)) -TARGETS_LIB_SHARED_COVER := $(foreach x,$(INST_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_SHARED_COVER)) -TARGETS_INST_LIB_LIBDEPS := $(foreach x,$(INST_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(LIB_SUFFIX_LIBDEPS)) -TARGETS_NOINST_LIB_OPTIM := $(foreach x,$(noinst_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_STATIC_OPTIM)) -TARGETS_NOINST_LIB_DEBUG := $(foreach x,$(noinst_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_STATIC_DEBUG)) -TARGETS_NOINST_LIB_COVER := $(foreach x,$(noinst_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_STATIC_COVER)) -TARGETS_NOINST_LIB_LIBDEPS := $(foreach x,$(noinst_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(LIB_SUFFIX_LIBDEPS)) -TARGETS_CHECK_LIB_OPTIM := $(foreach x,$(check_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_STATIC_OPTIM)) -TARGETS_CHECK_LIB_DEBUG := $(foreach x,$(check_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_STATIC_DEBUG)) -TARGETS_CHECK_LIB_COVER := $(foreach x,$(check_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(SUFFIX_LIB_STATIC_COVER)) -TARGETS_CHECK_LIB_LIBDEPS := $(foreach x,$(check_LIBRARIES),$(call GET_LIBRARY_STEM,$(x))$(LIB_SUFFIX_LIBDEPS)) -TARGETS_PROG_OPTIM := $(foreach x,$(INST_PROGRAMS),$(x)$(SUFFIX_PROG_OPTIM)) -TARGETS_PROG_DEBUG := $(foreach x,$(INST_PROGRAMS),$(x)$(SUFFIX_PROG_DEBUG)) -TARGETS_PROG_COVER := $(foreach x,$(INST_PROGRAMS),$(x)$(SUFFIX_PROG_COVER)) -TARGETS_DEV_PROG_OPTIM := $(foreach x,$(DEV_PROGRAMS),$(x)$(SUFFIX_PROG_OPTIM)) -TARGETS_DEV_PROG_DEBUG := $(foreach x,$(DEV_PROGRAMS),$(x)$(SUFFIX_PROG_DEBUG)) -TARGETS_NOINST_PROG_OPTIM := $(foreach x,$(noinst_PROGRAMS),$(x)$(SUFFIX_PROG_OPTIM)) -TARGETS_NOINST_PROG_DEBUG := $(foreach x,$(noinst_PROGRAMS),$(x)$(SUFFIX_PROG_DEBUG)) -TARGETS_NOINST_PROG_COVER := $(foreach x,$(noinst_PROGRAMS),$(x)$(SUFFIX_PROG_COVER)) -TARGETS_CHECK_PROG_OPTIM := $(foreach x,$(check_PROGRAMS),$(x)$(SUFFIX_PROG_OPTIM)) -TARGETS_CHECK_PROG_DEBUG := $(foreach x,$(check_PROGRAMS),$(x)$(SUFFIX_PROG_DEBUG)) -TARGETS_CHECK_PROG_COVER := $(foreach x,$(check_PROGRAMS),$(x)$(SUFFIX_PROG_COVER)) - -TARGETS_BUILD := -ifneq ($(ENABLE_INSTALL_STATIC_LIBS),) -TARGETS_BUILD += $(TARGETS_LIB_STATIC_OPTIM) -endif -TARGETS_BUILD += $(TARGETS_LIB_SHARED_OPTIM) -ifneq ($(or $(ENABLE_INSTALL_DEBUG_LIBS),$(ENABLE_INSTALL_DEBUG_PROGS)),) -TARGETS_BUILD += $(TARGETS_LIB_SHARED_DEBUG) -endif -TARGETS_BUILD += $(TARGETS_INST_LIB_LIBDEPS) -TARGETS_BUILD += $(TARGETS_NOINST_LIB_OPTIM) -ifneq ($(ENABLE_INSTALL_DEBUG_PROGS),) -TARGETS_BUILD += $(TARGETS_NOINST_LIB_DEBUG) -endif -TARGETS_BUILD += $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS_BUILD += $(TARGETS_PROG_OPTIM) -ifneq ($(ENABLE_INSTALL_DEBUG_PROGS),) -TARGETS_BUILD += $(TARGETS_PROG_DEBUG) -endif -TARGETS_BUILD += $(TARGETS_DEV_PROG_OPTIM) $(TARGETS_DEV_PROG_DEBUG) $(TARGETS_NOINST_PROG_OPTIM) - -TARGETS_RELEASE := $(TARGETS_LIB_SHARED_OPTIM) $(TARGETS_INST_LIB_LIBDEPS) -TARGETS_RELEASE += $(TARGETS_NOINST_LIB_OPTIM) $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS_RELEASE += $(TARGETS_PROG_OPTIM) $(TARGETS_DEV_PROG_OPTIM) $(TARGETS_NOINST_PROG_OPTIM) -TARGETS_NODEBUG := $(TARGETS_LIB_STATIC_OPTIM) $(TARGETS_LIB_SHARED_OPTIM) $(TARGETS_INST_LIB_LIBDEPS) -TARGETS_NODEBUG += $(TARGETS_NOINST_LIB_OPTIM) $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS_NODEBUG += $(TARGETS_PROG_OPTIM) $(TARGETS_DEV_PROG_OPTIM) $(TARGETS_NOINST_PROG_OPTIM) -TARGETS_DEBUG := $(TARGETS_LIB_SHARED_DEBUG) $(TARGETS_INST_LIB_LIBDEPS) -TARGETS_DEBUG += $(TARGETS_NOINST_LIB_DEBUG) $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS_DEBUG += $(TARGETS_PROG_DEBUG) $(TARGETS_DEV_PROG_DEBUG) $(TARGETS_NOINST_PROG_DEBUG) -TARGETS_COVER := $(TARGETS_LIB_SHARED_COVER) $(TARGETS_INST_LIB_LIBDEPS) -TARGETS_COVER += $(TARGETS_NOINST_LIB_COVER) $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS_COVER += $(TARGETS_PROG_COVER) $(TARGETS_NOINST_PROG_COVER) -TARGETS_CHECK := $(TARGETS_LIB_SHARED_OPTIM) $(TARGETS_INST_LIB_LIBDEPS) -TARGETS_CHECK += $(TARGETS_NOINST_LIB_OPTIM) $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS_CHECK += $(TARGETS_CHECK_LIB_OPTIM) $(TARGETS_CHECK_LIB_LIBDEPS) -TARGETS_CHECK += $(TARGETS_PROG_OPTIM) $(TARGETS_CHECK_PROG_OPTIM) -TARGETS_CHECK_DEBUG := $(TARGETS_LIB_SHARED_DEBUG) $(TARGETS_INST_LIB_LIBDEPS) -TARGETS_CHECK_DEBUG += $(TARGETS_NOINST_LIB_DEBUG) $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS_CHECK_DEBUG += $(TARGETS_CHECK_LIB_DEBUG) $(TARGETS_CHECK_LIB_LIBDEPS) -TARGETS_CHECK_DEBUG += $(TARGETS_PROG_DEBUG) $(TARGETS_CHECK_PROG_DEBUG) -TARGETS_CHECK_COVER := $(TARGETS_LIB_SHARED_COVER) $(TARGETS_INST_LIB_LIBDEPS) -TARGETS_CHECK_COVER += $(TARGETS_NOINST_LIB_COVER) $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS_CHECK_COVER += $(TARGETS_CHECK_LIB_COVER) $(TARGETS_CHECK_LIB_LIBDEPS) -TARGETS_CHECK_COVER += $(TARGETS_PROG_COVER) $(TARGETS_CHECK_PROG_COVER) - -TARGETS_EVERYTHING := $(TARGETS_LIB_STATIC_OPTIM) $(TARGETS_LIB_SHARED_OPTIM) -TARGETS_EVERYTHING += $(TARGETS_LIB_SHARED_DEBUG) $(TARGETS_INST_LIB_LIBDEPS) -TARGETS_EVERYTHING += $(TARGETS_NOINST_LIB_OPTIM) $(TARGETS_NOINST_LIB_DEBUG) $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS_EVERYTHING += $(TARGETS_CHECK_LIB_OPTIM) $(TARGETS_CHECK_LIB_DEBUG) $(TARGETS_CHECK_LIB_LIBDEPS) -TARGETS_EVERYTHING += $(TARGETS_PROG_OPTIM) $(TARGETS_PROG_DEBUG) -TARGETS_EVERYTHING += $(TARGETS_DEV_PROG_OPTIM) $(TARGETS_DEV_PROG_DEBUG) -TARGETS_EVERYTHING += $(TARGETS_NOINST_PROG_OPTIM) $(TARGETS_NOINST_PROG_DEBUG) -TARGETS_EVERYTHING += $(TARGETS_CHECK_PROG_OPTIM) $(TARGETS_CHECK_PROG_DEBUG) - -TARGETS_LIB_STATIC := $(TARGETS_LIB_STATIC_OPTIM) $(TARGETS_LIB_STATIC_DEBUG) $(TARGETS_LIB_STATIC_COVER) -TARGETS_LIB_SHARED := $(TARGETS_LIB_SHARED_OPTIM) $(TARGETS_LIB_SHARED_DEBUG) $(TARGETS_LIB_SHARED_COVER) -TARGETS_NOINST_LIB := $(TARGETS_NOINST_LIB_OPTIM) $(TARGETS_NOINST_LIB_DEBUG) $(TARGETS_NOINST_LIB_COVER) -TARGETS_CHECK_LIB := $(TARGETS_CHECK_LIB_OPTIM) $(TARGETS_CHECK_LIB_DEBUG) $(TARGETS_CHECK_LIB_COVER) -TARGETS_PROG := $(TARGETS_PROG_OPTIM) $(TARGETS_PROG_DEBUG) $(TARGETS_PROG_COVER) -TARGETS_DEV_PROG := $(TARGETS_DEV_PROG_OPTIM) $(TARGETS_DEV_PROG_DEBUG) -TARGETS_NOINST_PROG := $(TARGETS_NOINST_PROG_OPTIM) $(TARGETS_NOINST_PROG_DEBUG) $(TARGETS_NOINST_PROG_COVER) -TARGETS_CHECK_PROG := $(TARGETS_CHECK_PROG_OPTIM) $(TARGETS_CHECK_PROG_DEBUG) $(TARGETS_CHECK_PROG_COVER) -TARGETS_PROG_ALL := $(foreach x,$(TARGETS_PROG) $(TARGETS_DEV_PROG),$(x) $(x)-noinst) $(TARGETS_NOINST_PROG) $(TARGETS_CHECK_PROG) - -# ARGS: real_local_path, version -GET_SHARED_LIB_ALIASES = $(1) - -ifeq ($(OS),Linux) - -GET_SHARED_LIB_ALIASES = $(if $(2),$(call GET_SHARED_LIB_ALIASES_2,$(1),$(call MAP_SHARED_LIB_VERSION,$(2))),$(1)) -GET_SHARED_LIB_ALIASES_2 = $(1) $(1).$(word 1,$(2)) $(1).$(word 2,$(2)) - -MAP_SHARED_LIB_VERSION = $(call MAP_SHARED_LIB_VERSION_2,$(word 1,$(1)),$(word 2,$(1)),$(word 3,$(1))) -MAP_SHARED_LIB_VERSION_2 = $(call MAP_SHARED_LIB_VERSION_3,$(call INT_SUB,$(1),$(3)),$(3),$(2)) -MAP_SHARED_LIB_VERSION_3 = $(1) $(1).$(2).$(3) - -endif - -ifeq ($(OS),Darwin) - -GET_SHARED_LIB_ALIASES = $(if $(2),$(1) $(word 1,$(call MAP_SHARED_LIB_VERSION,$(1),$(2))),$(1)) - -MAP_SHARED_LIB_VERSION = $(call MAP_SHARED_LIB_VERSION_2,$(1),$(word 1,$(2)),$(word 2,$(2)),$(word 3,$(2))) -MAP_SHARED_LIB_VERSION_2 = $(call MAP_SHARED_LIB_VERSION_3,$(1),$(call INT_SUB,$(2),$(4)),$(call INT_ADD,$(2),1),$(3)) -MAP_SHARED_LIB_VERSION_3 = $(patsubst %.dylib,%,$(1)).$(2).dylib $(3) $(3).$(4) - -endif - -TARGETS_LIB_SHARED_ALIASES = $(foreach x,$(INST_LIBRARIES),$(foreach y,OPTIM DEBUG COVER,$(call TARGETS_LIB_SHARED_ALIASES_1,$(x),$(SUFFIX_LIB_SHARED_$(y))))) -TARGETS_LIB_SHARED_ALIASES_1 = $(call GET_SHARED_LIB_ALIASES,$(call GET_LIBRARY_STEM,$(1))$(2),$(call GET_LIBRARY_VERSION,$(1))) - -TARGETS := $(TARGETS_LIB_STATIC) $(TARGETS_LIB_SHARED_ALIASES) $(TARGETS_INST_LIB_LIBDEPS) -TARGETS += $(TARGETS_NOINST_LIB) $(TARGETS_NOINST_LIB_LIBDEPS) -TARGETS += $(TARGETS_CHECK_LIB) $(TARGETS_CHECK_LIB_LIBDEPS) $(TARGETS_PROG_ALL) - -RECURSIVE_GOALS = build release nodebug debug cover everything clean install-only uninstall \ -check-norun check-debug-norun check-cover-norun check check-debug check-cover memcheck memcheck-debug - -.DEFAULT_GOAL := - -.PHONY: all -all: build - -build/local: $(TARGETS_BUILD) -release/local: $(TARGETS_RELEASE) -nodebug/local: $(TARGETS_NODEBUG) -debug/local: $(TARGETS_DEBUG) -cover/local: $(TARGETS_COVER) -everything/local: $(TARGETS_EVERYTHING) -check-norun/local: $(TARGETS_CHECK) -check-debug-norun/local: $(TARGETS_CHECK_DEBUG) -check-cover-norun/local: $(TARGETS_CHECK_COVER) - - -# Update everything if any makefile or any generated source has changed -$(GENERATED_SOURCES) $(OBJECTS) $(TARGETS): $(DEP_MAKEFILES) -$(OBJECTS): $(GENERATED_SOURCES) - -# Disable all suffix rules and some interfering implicit pattern rules -.SUFFIXES: -%: %.o -%: %.c -%: %.cpp - - - -# SUBPROJECTS - -# ARGS: recursive_goal, subdirs_for_goal, subdir_deps_for_this_dir -define RECURSIVE_GOAL_RULES -.PHONY: $(1) $(1)/this-dir $(1)/local -$(1): $(1)/this-dir $(patsubst %,$(1)/subdir/%,$(2)) -ifeq ($(strip $(3)),) -$(1)/this-dir: $(1)/local -else -$(1)/this-dir: - @$$(MAKE) --no-print-directory $(1)/local -endif -endef - -# ARGS: recursive_goal, subdir_dep_for_this_dir -define SUBDIR_DEP_FOR_THIS_DIR_RULE -$(1)/this-dir: $(1)/subdir/$(2) -endef - -# ARGS: recursive_goal, subdir -define RECURSIVE_GOAL_SUBDIR_RULES -.PHONY: $(1)/subdir/$(2) -ifeq ($(1),build) -$(1)/subdir/$(2): - @$$(MAKE) -w -C $(2) -else -$(1)/subdir/$(2): - @$$(MAKE) -w -C $(2) $(1) -endif -endef - -# ARGS: recursive_goal, subdir, dep -define SUBDIR_DEP_RULE -ifeq ($(3),.) -$(1)/subdir/$(2): $(1)/this-dir -else -$(1)/subdir/$(2): $(1)/subdir/$(3) -endif -endef - -AVAIL_PASSIVE_SUBDIRS := $(foreach x,$(PASSIVE_SUBDIRS),$(if $(realpath $(x)),$(x))) - -# ARGS: recursive_goal -GET_SUBDIRS_FOR_RECURSIVE_GOAL = $(if $(filter clean,$(1)),$(SUBDIRS) $(AVAIL_PASSIVE_SUBDIRS),$(SUBDIRS)) - -# ARGS: recursive_goal -GET_SUBDIR_DEPS_FOR_THIS_DIR = $(if $(filter clean install-only,$(1)),,$(if $(filter uninstall,$(1)),$(SUBDIRS),$(DIR_DEPS))) - -# ARGS: recursive_goal, subdir -GET_SUBDIR_DEPS = $(if $(filter clean uninstall,$(1)),,$(if $(filter install-only,$(1)),.,$($(call FOLD_TARGET,$(2))_DEPS))) - -# ARGS: recursive_goal, subdir, deps -EVAL_RECURSIVE_GOAL_SUBDIR_RULES = \ -$(eval $(call RECURSIVE_GOAL_SUBDIR_RULES,$(1),$(2)))\ -$(foreach x,$(call GET_SUBDIR_DEPS,$(1),$(2)),$(eval $(call SUBDIR_DEP_RULE,$(1),$(2),$(x)))) - -# ARGS: recursive_goal, subdir_deps_for_this_dir -EVAL_RECURSIVE_GOAL_RULES = \ -$(eval $(call RECURSIVE_GOAL_RULES,$(1),$(call GET_SUBDIRS_FOR_RECURSIVE_GOAL,$(1)),$(2)))\ -$(foreach x,$(2),$(eval $(call SUBDIR_DEP_FOR_THIS_DIR_RULE,$(1),$(x))))\ -$(foreach x,$(SUBDIRS) $(PASSIVE_SUBDIRS),$(call EVAL_RECURSIVE_GOAL_SUBDIR_RULES,$(1),$(x))) - -$(foreach x,$(RECURSIVE_GOALS),$(call EVAL_RECURSIVE_GOAL_RULES,$(x),$(call GET_SUBDIR_DEPS_FOR_THIS_DIR,$(x)))) - - - -# CLEANING - -GET_CLEAN_FILES = $(strip $(call WILDCARD_PATHS_FILTER_OUT,$(EXTRA_CLEAN),$(foreach x,$(SOURCE_DIRS),$(foreach y,*.d *.o *.gcno *.gcda,$(patsubst ./%,%,$(x))$(y))) $(TARGETS)) $(EXTRA_CLEAN)) - -ifneq ($(word 1,$(or $(SOURCE_DIRS),$(TARGETS),$(EXTRA_CLEAN))),) -define CLEANING_RULES -clean/local: - $$(RM) $$(call GET_CLEAN_FILES) -endef -$(eval $(CLEANING_RULES)) -endif - - - -# INSTALL / UNINSTALL - -.PHONY: install -install: build - @$(MAKE) install-only - -HAS_STAR = $(call HAS_STAR_1,$(subst *,x$(SPACE)x,$(subst $(SPACE),x,$(1)))) -HAS_STAR_1 = $(wordlist 2,$(words $(1)),$(1)) - -CHECK_WILDCARD = $(if $(call HAS_STAR,$(word 1,$(subst /,$(SPACE),$(1)))),$(error For your safety, uninstallation wildcards are not allowed to appear at the root level of the target installation directory [$(1)])) -CHECK_WILDCARDS = $(foreach x,$(PRIMARIES),$(foreach y,$(notdir $($(1)_$(x)_EXTRA_UNINSTALL)) $(nobase_$(1)_$(x)_EXTRA_UNINSTALL),$(call CHECK_WILDCARD,$(2)$(y)))) -$(foreach x,$(PRIMARY_PREFIXES),$(call CHECK_WILDCARDS,$(x),$(if $(filter subinclude,$(x)),$(call COND_APPEND,$(INCLUDE_SUBDIR),/)))) - -DESTDIR_2 := $(call SHELL_ESCAPE,$(call UNHIDE_SPACE,$(patsubst %/,%,$(call HIDE_SPACE,$(value DESTDIR))))) - -# ARGS: install_dir, real_local_paths -INSTALL_RECIPE_FILES = $(NL_TAB)$$(INSTALL_DATA) $(2) $$(DESTDIR_2)$(1) -UNINSTALL_RECIPE_FILES = $(NL_TAB)$$(RM) $(foreach x,$(2),$$(DESTDIR_2)$(1)/$(x)) - -# ARGS: install_dir, real_local_paths -INSTALL_RECIPE_LIBS = $(NL_TAB)$$(INSTALL_LIBRARY) $(2) $$(DESTDIR_2)$(1) - -# ARGS: real_local_path, version -INSTALL_FILES_VERSIONED_LIB = $(1) - -# ARGS: install_dir, real_local_path, version -INSTALL_RECIPE_VERSIONED_LIB = $(INSTALL_RECIPE_LIBS) - -ifeq ($(OS),Linux) -INSTALL_FILES_VERSIONED_LIB = $(if $(2),$(call INSTALL_FILES_VERSIONED_LIB_1,$(1),$(call MAP_SHARED_LIB_VERSION,$(2))),$(1)) -INSTALL_FILES_VERSIONED_LIB_1 = $(1) $(1).$(word 1,$(2)) $(1).$(word 2,$(2)) -INSTALL_RECIPE_VERSIONED_LIB = $(if $(3),$(call INSTALL_RECIPE_VERSIONED_LIB_1,$(1),$(2),$(call MAP_SHARED_LIB_VERSION,$(3))),$(INSTALL_RECIPE_LIBS)) -INSTALL_RECIPE_VERSIONED_LIB_1 = $(call INSTALL_RECIPE_VERSIONED_LIB_2,$(1),$(2),$(2).$(word 1,$(3)),$(2).$(word 2,$(3))) -INSTALL_RECIPE_VERSIONED_LIB_2 = $(call INSTALL_RECIPE_LIBS,$(1),$(4))$(NL_TAB)cd $$(DESTDIR_2)$(1) && ln -s -f $(notdir $(4)) $(notdir $(3)) && ln -s -f $(notdir $(3)) $(notdir $(2)) -endif - -ifeq ($(OS),Darwin) -INSTALL_FILES_VERSIONED_LIB = $(if $(2),$(1) $(word 1,$(call MAP_SHARED_LIB_VERSION,$(1),$(2))),$(1)) -INSTALL_RECIPE_VERSIONED_LIB = $(if $(3),$(call INSTALL_RECIPE_VERSIONED_LIB_1,$(1),$(2),$(word 1,$(call MAP_SHARED_LIB_VERSION,$(2),$(3)))),$(INSTALL_RECIPE_LIBS)) -INSTALL_RECIPE_VERSIONED_LIB_1 = $(call INSTALL_RECIPE_LIBS,$(1),$(3))$(NL_TAB)cd $$(DESTDIR_2)$(1) && ln -s -f $(notdir $(3)) $(notdir $(2)) -endif - -INST_STATIC_LIB_SUFFICES := -INST_SHARED_LIB_SUFFICES := -INST_PROG_SUFFICES := -ifneq ($(ENABLE_INSTALL_STATIC_LIBS),) -INST_STATIC_LIB_SUFFICES += +$(SUFFIX_LIB_STATIC_OPTIM) -endif -INST_SHARED_LIB_SUFFICES += +$(SUFFIX_LIB_SHARED_OPTIM) -ifneq ($(ENABLE_INSTALL_DEBUG_LIBS),) -INST_SHARED_LIB_SUFFICES += +$(SUFFIX_LIB_SHARED_DEBUG) -endif -INST_PROG_SUFFICES += +$(SUFFIX_PROG_OPTIM) -ifneq ($(ENABLE_INSTALL_DEBUG_PROGS),) -INST_PROG_SUFFICES += +$(SUFFIX_PROG_DEBUG) -endif - -# ARGS: abstract_targets -INSTALL_FILES_STATIC_LIBS = $(foreach x,$(1),$(foreach y,$(INST_STATIC_LIB_SUFFICES),$(call GET_LIBRARY_STEM,$(x))$(patsubst +%,%,$(y)))) -INSTALL_FILES_SHARED_LIBS = $(foreach x,$(1),$(foreach y,$(INST_SHARED_LIB_SUFFICES),$(call INSTALL_FILES_VERSIONED_LIB,$(call GET_LIBRARY_STEM,$(x))$(patsubst +%,%,$(y)),$(call GET_LIBRARY_VERSION,$(x))))) -INSTALL_FILES_PROGRAMS = $(foreach x,$(1),$(foreach y,$(INST_PROG_SUFFICES),$(x)$(patsubst +%,%,$(y)))) - -# ARGS: install_dir, abstract_targets -INSTALL_RECIPE_STATIC_LIBS = $(call INSTALL_RECIPE_STATIC_LIBS_1,$(1),$(call INSTALL_FILES_STATIC_LIBS,$(2))) -INSTALL_RECIPE_STATIC_LIBS_1 = $(if $(2),$(call INSTALL_RECIPE_LIBS,$(1),$(2))) -INSTALL_RECIPE_SHARED_LIBS = $(foreach x,$(2),$(foreach y,$(INST_SHARED_LIB_SUFFICES),$(call INSTALL_RECIPE_VERSIONED_LIB,$(1),$(call GET_LIBRARY_STEM,$(x))$(patsubst +%,%,$(y)),$(call GET_LIBRARY_VERSION,$(x)))$(NEWLINE))) -INSTALL_RECIPE_PROGRAMS = $(NL_TAB)$$(INSTALL_PROGRAM) $(call INSTALL_FILES_PROGRAMS,$(2)) $$(DESTDIR_2)$(1) - -# ARGS: primary, is_for_uninstall -GET_INSTALL_DIRS = $(foreach x,$(PRIMARY_PREFIXES),$(if $(filter subinclude,$(x)),$(call GET_INSTALL_DIRS_1,$(call GET_ROOT_INSTALL_DIR,include),$(call COND_APPEND,$(INCLUDE_SUBDIR),/),$(x),$(1),$(2)),$(call GET_INSTALL_DIRS_1,$(call GET_ROOT_INSTALL_DIR,$(x)),,$(x),$(1),$(2)))) - -# ARGS: root_install_dir, opt_include_subdir_slash, primary_prefix, primary, is_for_uninstall -GET_INSTALL_DIRS_1 = $(call GET_INSTALL_DIRS_2,$(1),$(2),$(notdir $($(3)_$(4))) $(nobase_$(3)_$(4)),$(5)) - -# ARGS: root_install_dir, opt_include_subdir_slash, nobase_targets, is_for_uninstall -GET_INSTALL_DIRS_2 = $(foreach x,$(call REMOVE_DUPES,$(patsubst %/,%,$(dir $(addprefix $(2),$(3))))),$(if $(filter .,$(x)),$(if $(4),,$(1)),$(foreach y,$(call SUBDIR_PARENT_EXPAND,$(x)),$(1)/$(y)))) - -SUBDIR_PARENT_EXPAND = $(if $(filter-out ./,$(dir $(1))),$(call SUBDIR_PARENT_EXPAND,$(patsubst %/,%,$(dir $(1)))) $(1),$(1)) - -# ARGS: primary, get_recipes -GET_INSTALL_RECIPES = $(foreach x,$(PRIMARY_PREFIXES),$(call GET_INSTALL_RECIPES_1,$(2),$(call GET_INSTALL_DIR,$(x)),$(strip $($(x)_$(1))),$(nobase_$(x)_$(1)))) - -# ARGS: get_recipes, install_dir, abstract_targets, nobase_targets -GET_INSTALL_RECIPES_1 = $(if $(3),$(call $(1),$(2),$(3))$(NEWLINE)) $(foreach x,$(call REMOVE_DUPES,$(dir $(4))),$(call $(1),$(2)$(patsubst %/,/%,$(filter-out ./,$(x))),$(strip $(foreach y,$(4),$(if $(call IS_EQUAL_TO,$(dir $(y)),$(x)),$(y)))))$(NEWLINE)) - -# ARGS: primary, get_files -GET_UNINSTALL_RECIPES = $(foreach x,$(PRIMARY_PREFIXES),$(call GET_UNINSTALL_RECIPES_1,$(call GET_INSTALL_DIR,$(x)),$(2),$($(x)_$(1)),$(nobase_$(x)_$(1)),$(call REMOVE_DUPES,$(notdir $($(x)_$(1)_EXTRA_UNINSTALL)) $(nobase_$(x)_$(1)_EXTRA_UNINSTALL)))) - -# ARGS: install_dir, get_files, abstract_targets, nobase_targets, extra_uninstall -GET_UNINSTALL_RECIPES_1 = $(call GET_UNINSTALL_RECIPES_2,$(1),$(strip $(call WILDCARD_PATHS_FILTER_OUT,$(5),$(notdir $(call $(2),$(3))) $(call $(2),$(4))) $(5))) - -# ARGS: install_dir, uninstall_paths -GET_UNINSTALL_RECIPES_2 = $(if $(2), $(call UNINSTALL_RECIPE_FILES,$(1),$(2))$(NEWLINE)) - -INSTALL_DIRS := -UNINSTALL_DIRS := -EXTRA_UNINSTALL_DIRS := -INSTALL_RECIPES := -UNINSTALL_RECIPES := - -INSTALL_FILTER_2 := $(subst $(COMMA),$(SPACE),$(INSTALL_FILTER)) - -ifneq ($(filter headers,$(INSTALL_FILTER_2)),) -INSTALL_DIRS += $(call GET_INSTALL_DIRS,HEADERS) -UNINSTALL_DIRS += $(call GET_INSTALL_DIRS,HEADERS,x) -EXTRA_UNINSTALL_DIRS += $(call GET_INSTALL_DIRS,HEADERS_EXTRA_UNINSTALL,x) -INSTALL_RECIPES += $(call GET_INSTALL_RECIPES,HEADERS,INSTALL_RECIPE_FILES)$(NEWLINE) -UNINSTALL_RECIPES += $(call GET_UNINSTALL_RECIPES,HEADERS,IDENTITY)$(NEWLINE) -endif -ifneq ($(filter static-libs,$(INSTALL_FILTER_2)),) -INSTALL_DIRS += $(call GET_INSTALL_DIRS,LIBRARIES) -UNINSTALL_DIRS += $(call GET_INSTALL_DIRS,LIBRARIES,x) -EXTRA_UNINSTALL_DIRS += $(call GET_INSTALL_DIRS,LIBRARIES_EXTRA_UNINSTALL,x) -INSTALL_RECIPES += $(call GET_INSTALL_RECIPES,LIBRARIES,INSTALL_RECIPE_STATIC_LIBS)$(NEWLINE) -UNINSTALL_RECIPES += $(call GET_UNINSTALL_RECIPES,LIBRARIES,INSTALL_FILES_STATIC_LIBS)$(NEWLINE) -endif -ifneq ($(filter shared-libs,$(INSTALL_FILTER_2)),) -INSTALL_DIRS += $(call GET_INSTALL_DIRS,LIBRARIES) -UNINSTALL_DIRS += $(call GET_INSTALL_DIRS,LIBRARIES,x) -EXTRA_UNINSTALL_DIRS += $(call GET_INSTALL_DIRS,LIBRARIES_EXTRA_UNINSTALL,x) -INSTALL_RECIPES += $(call GET_INSTALL_RECIPES,LIBRARIES,INSTALL_RECIPE_SHARED_LIBS)$(NEWLINE) -UNINSTALL_RECIPES += $(call GET_UNINSTALL_RECIPES,LIBRARIES,INSTALL_FILES_SHARED_LIBS)$(NEWLINE) -endif -ifneq ($(filter progs,$(INSTALL_FILTER_2)),) -INSTALL_DIRS += $(call GET_INSTALL_DIRS,PROGRAMS) -UNINSTALL_DIRS += $(call GET_INSTALL_DIRS,PROGRAMS,x) -EXTRA_UNINSTALL_DIRS += $(call GET_INSTALL_DIRS,PROGRAMS_EXTRA_UNINSTALL,x) -INSTALL_RECIPES += $(call GET_INSTALL_RECIPES,PROGRAMS,INSTALL_RECIPE_PROGRAMS)$(NEWLINE) -UNINSTALL_RECIPES += $(call GET_UNINSTALL_RECIPES,PROGRAMS,INSTALL_FILES_PROGRAMS)$(NEWLINE) -endif -ifneq ($(and $(filter dev-progs,$(INSTALL_FILTER_2)),$(strip $(DEV_PROGRAMS))),) -INSTALL_DIRS += $(call GET_ROOT_INSTALL_DIR,bin) -INSTALL_RECIPES += $(call INSTALL_RECIPE_PROGRAMS,$(call GET_ROOT_INSTALL_DIR,bin),$(DEV_PROGRAMS))$(NEWLINE) -UNINSTALL_RECIPES += $(call UNINSTALL_RECIPE_FILES,$(call GET_ROOT_INSTALL_DIR,bin),$(call INSTALL_FILES_PROGRAMS,$(DEV_PROGRAMS)))$(NEWLINE) -endif - -# ARGS: paths, extra_paths -FILTER_UNINSTALL_DIRS = $(call FOLD_LEFT,FILTER_UNINSTALL_DIRS_1,$(1) $(2),$(2)) -FILTER_UNINSTALL_DIRS_1 = $(call REMOVE_DUPES,$(foreach x,$(1),$(if $(call WILDCARD_PATH_MATCH,$(2),$(x)),$(2),$(x)))) - -INSTALL_DIR_RECIPES := $(if $(strip $(INSTALL_DIRS)),$(NL_TAB)$$(INSTALL_DIR) $(foreach x,$(call REMOVE_PREFIXES,$(patsubst %,%/,$(call REMOVE_DUPES,$(INSTALL_DIRS)))),$$(DESTDIR_2)$(x))$(NEWLINE)) -UNINSTALL_DIR_RECIPES := $(foreach x,$(call LIST_REVERSE,$(call FILTER_UNINSTALL_DIRS,$(call REMOVE_DUPES,$(UNINSTALL_DIRS)),$(call REMOVE_DUPES,$(EXTRA_UNINSTALL_DIRS)))),$(NL_TAB)-rmdir $$(DESTDIR_2)$(x)/$(NEWLINE)) - -define INSTALL_RULES -install-only/local:$(INSTALL_DIR_RECIPES)$(INSTALL_RECIPES) -uninstall/local:$(UNINSTALL_RECIPES)$(UNINSTALL_DIR_RECIPES) -endef - -ifeq ($(ENABLE_NOINST_BUILD),) -$(eval $(INSTALL_RULES)) -endif - - -# TESTING (A.K.A CHECKING) - -define CHECK_RULES - -check/local: $(TARGETS_CHECK) -$(foreach x,$(TARGETS_CHECK_PROG_OPTIM),$(NL_TAB)./$(x)$(NEWLINE)) - -check-debug/local: $(TARGETS_CHECK_DEBUG) -$(foreach x,$(TARGETS_CHECK_PROG_DEBUG),$(NL_TAB)./$(x)$(NEWLINE)) - -memcheck/local: $(TARGETS_CHECK) -$(foreach x,$(TARGETS_CHECK_PROG_OPTIM),$(NL_TAB)$$(VALGRIND) $$(VALGRIND_FLAGS) --error-exitcode=1 ./$(x) --no-error-exitcode$(NEWLINE)) - -memcheck-debug/local: $(TARGETS_CHECK_DEBUG) -$(foreach x,$(TARGETS_CHECK_PROG_DEBUG),$(NL_TAB)$$(VALGRIND) $$(VALGRIND_FLAGS) --error-exitcode=1 ./$(x) --no-error-exitcode$(NEWLINE)) - -ifneq ($(strip $(or $(SOURCE_DIRS),$(TARGETS_CHECK_COVER))),) -check-cover/local: $(TARGETS_CHECK_COVER) -$(if $(SOURCE_DIRS),$(NL_TAB)$$(RM) $(foreach x,$(SOURCE_DIRS),$(patsubst ./%,%,$(x))*.gcda)) -$(foreach x,$(TARGETS_CHECK_PROG_COVER),$(NL_TAB)-./$(x)$(NEWLINE)) -endif - -endef - -$(eval $(CHECK_RULES)) - - - -# LINKING PROGRAMS - -# ARGS: origin_pattern, target_pattern, list -FILTER_PATSUBST = $(patsubst $(1),$(2),$(filter $(1),$(3))) - -# ARGS: patterns, list -FILTER_UNPACK = $(foreach x,$(2),$(call FILTER_UNPACK_1,$(call FIND,FILTER_UNPACK_2,$(1),$(x)),$(x))) -FILTER_UNPACK_1 = $(and $(1),$(patsubst $(1),%,$(2))) -FILTER_UNPACK_2 = $(filter $(1),$(2)) - -# ARGS: func, patterns, list, optional_arg -PATTERN_UNPACK_MAP = $(foreach x,$(3),$(call PATTERN_UNPACK_MAP_1,$(1),$(call FIND,PATTERN_UNPACK_MAP_2,$(2),$(x)),$(x),$(4))) -# ARGS: func, optional_matching_pattern, entry, optional_arg -PATTERN_UNPACK_MAP_1 = $(if $(2),$(patsubst %,$(2),$(call $(1),$(patsubst $(2),%,$(3)),$(4))),$(3)) -PATTERN_UNPACK_MAP_2 = $(filter $(1),$(2)) - -MANGLE_LIBREF = $(subst /,_s,$(subst .,_d,$(subst -,_e,$(subst _,_u,$(1))))) - -# Expand the contents of the `target_LIBS` variable for the specified -# target. The target must either be a program or an installed -# library. Relative paths in the output will be expressed relative to -# the directory holding the local `Makefile`. -# -# Output for each convenience library `x/y/libfoo.a`: -# -# noinst:x/y/libfoo libdeps:x/y/libfoo.libdeps -# ldflag-opt:flag... ldflag-dbg:flag... ldflag-cov:flag... -# -# For each installed library `x/y/libfoo.a` referenced directly or in -# two steps via a convenience library: -# -# inst:x/y/libfoo libdeps:x/y/libfoo.libdeps dir:x/y lib:foo -# -# For each installed library `x/y/libfoo.a` referenced directly or -# indirectly in any number of steps, and installed as -# `/foo/bar/libfoo.so`: -# -# rpath:/foo/bar rpath-noinst:x/y -# -# ARGS: abstract_target -EXPAND_INST_LIB_LIBREFS = $(call EXPAND_LIBREFS,$(1),rpath:$(call GET_INSTALL_DIR_FOR_LIB_TARGET,$(1)) rpath-noinst:$(patsubst %/,%,$(dir $(1)))) -# ARGS: abstract_target, initial_elems -EXPAND_LIBREFS = $(call REMOVE_DUPES,$(2) $(foreach x,$($(call FOLD_TARGET,$(1))_LIBS),$(call EXPAND_LIBREF,$(x)))) -# ARGS: libref -EXPAND_LIBREF = $(call EXPAND_LIBREF_1,$(1),$(call MANGLE_LIBREF,$(1))) -EXPAND_LIBREF_1 = $(if $(GMK_CELR_$(2)),,$(eval GMK_ELR_$(2) := $$(call EXPAND_LIBREF_2,$(1))$(NEWLINE)GMK_CELR_$(2) = x))$(GMK_ELR_$(2)) -EXPAND_LIBREF_2 = $(call EXPAND_LIBREF_3,$(call GET_LIBRARY_STEM,$(x)),$(call READ_LIBDEPS,$(x))) -# ARGS: libref_stem, libref_libdeps_contents -EXPAND_LIBREF_3 = $(if $(filter noinst,$(2)),$(call EXPAND_LIBREF_NOINST,$(1),$(2)),$(call EXPAND_LIBREF_INST,$(1),$(2))) -EXPAND_LIBREF_NOINST = $(call EXPAND_LIBREF_4,noinst:$(1) libdeps:$(1)$(LIB_SUFFIX_LIBDEPS) $(filter-out noinst,$(2))) -EXPAND_LIBREF_INST = $(call EXPAND_LIBREF_4,lib:$(1).a libdeps:$(1)$(LIB_SUFFIX_LIBDEPS) $(2)) -# ARGS: partially_expanded_libdeps -EXPAND_LIBREF_4 = $(foreach x,$(1),$(if $(filter lib:%,$(x)),$(call EXPAND_LIBREF_5,$(call GET_LIBRARY_STEM,$(patsubst lib:%,%,$(x)))),$(x))) -# ARGS: nested_libref_stem -EXPAND_LIBREF_5 = $(call EXPAND_LIBREF_6,$(1),$(dir $(1)),$(notdir $(1))) -# ARGS: nested_libref_stem, dir_part, nondir_part -EXPAND_LIBREF_6 = inst:$(1) dir:$(patsubst %/,%,$(2)) $(patsubst lib%,lib:%,$(3)) - -# Read the contents of the `.libdeps` file for the specified library -# and translate relative paths such that they are expressed relative -# to the directory holding the local `Makefile`. For referenced -# libraries defined in the local `Makefile`, the contents needs to be -# computed "on the fly" because the `.libdeps` file may not yet be up -# to date. -# -# ARGS: abstract_libref -READ_LIBDEPS = $(if $(call IS_LOCAL_NOINST_LIB,$(1)),$(call MAKE_NOINST_LIBDEPS,$(1)),$(if $(call IS_LOCAL_INST_LIB,$(1)),$(call MAKE_INST_LIBDEPS,$(1)),$(call READ_LIBDEPS_1,$(1)))) -READ_LIBDEPS_1 = $(call PATTERN_UNPACK_MAP,READ_LIBDEPS_2,lib:% rpath-noinst:%,$(call CAT_OPT_FILE,$(call GET_LIBRARY_STEM,$(1))$(LIB_SUFFIX_LIBDEPS)),$(dir $(1))) -READ_LIBDEPS_2 = $(call MAKE_REL_PATH,$(2)$(1)) -# Is the specified library one that is defined in the local Makefile? -IS_LOCAL_INST_LIB = $(call FIND,IS_SAME_PATH_AS,$(INST_LIBRARIES),$(1)) -IS_LOCAL_NOINST_LIB = $(call FIND,IS_SAME_PATH_AS,$(noinst_LIBRARIES) $(check_LIBRARIES),$(1)) - -# Quote elements for shell and translate relative paths such that they -# become relative to the specified target directory. It is assumed -# that the relative paths are currently relative to the current -# working directory. -# -# ARGS: libdeps_contents, target_dir -EXPORT_LIBDEPS = $(foreach x,$(call PATTERN_UNPACK_MAP,EXPORT_LIBDEPS_1,lib:% rpath-noinst:%,$(1),$(2)),$(call SHELL_ESCAPE,$(x))) -EXPORT_LIBDEPS_1 = $(call MAKE_REL_PATH,$(1),$(2)) - -# Compute what is almost the contents to be placed in the `.libdeps` -# file for the specified library. The only thing that sets it apart -# from what must ultimately be placed in the file, is that all -# relative paths in the output of this function will be expressed -# relative to the directory holding the local `Makefile`, and not -# relative to the directory holding the `.libdeps` file (in case they -# differ). -# -# ARGS: abstract_target -MAKE_INST_LIBDEPS = $(call EXTRACT_INST_LIB_LIBDEPS,$(call EXPAND_INST_LIB_LIBREFS,$(1))) -MAKE_NOINST_LIBDEPS = $(strip noinst $(call MAKE_NOINST_LIBDEPS_1,$(1)) $(call MAKE_NOINST_LIBDEPS_2,$(1))) -MAKE_NOINST_LIBDEPS_1 = $(foreach x,$($(call FOLD_TARGET,$(1))_LIBS),lib:$(x) $(call READ_LIBDEPS,$(x))) -MAKE_NOINST_LIBDEPS_2 = $(call MAKE_NOINST_LIBDEPS_3,$(1)) $(call MAKE_NOINST_LIBDEPS_4,$(1)) $(call MAKE_NOINST_LIBDEPS_5,$(1)) -MAKE_NOINST_LIBDEPS_3 = $(foreach x,$(call GET_FLAGS,$(call FOLD_TARGET,$(1))_LDFLAGS,OPTIM),ldflag-opt:$(x)) -MAKE_NOINST_LIBDEPS_4 = $(foreach x,$(call GET_FLAGS,$(call FOLD_TARGET,$(1))_LDFLAGS,DEBUG),ldflag-dbg:$(x)) -MAKE_NOINST_LIBDEPS_5 = $(foreach x,$(call GET_FLAGS,$(call FOLD_TARGET,$(1))_LDFLAGS,COVER),ldflag-cov:$(x)) - -# ARGS: expanded_librefs -EXTRACT_INST_LIB_LIBDEPS = $(filter rpath:% rpath-noinst:%,$(1)) - -# Add library name qualification, and select the appropriate set of -# linker flags for the specified compilation mode. -# -# ARGS: expanded_librefs, compile_mode -FINALIZE_EXPANDED_LIBREFS = $(call SELECT_LDFLAGS_$(2),$(call QUALIFY_LIBREFS,$(1),$(2))) -QUALIFY_LIBREFS = $(call QUALIFY_LIBREFS_1,$(1),$(SUFFIX_LIB_STATIC_$(2)),$(SUFFIX_LIB_SHARED_$(2)),$(BASE_DENOM_2)$(LIB_DENOM_$(2))) -QUALIFY_LIBREFS_1 = $(patsubst noinst:%,noinst:%$(2),$(patsubst inst:%,inst:%$(3),$(patsubst lib:%,lib:%$(4),$(1)))) -SELECT_LDFLAGS_OPTIM = $(patsubst ldflag-opt:%,ldflag:%,$(filter-out ldflag-dbg:% ldflag-cov:%,$(1))) -SELECT_LDFLAGS_DEBUG = $(patsubst ldflag-dbg:%,ldflag:%,$(filter-out ldflag-opt:% ldflag-cov:%,$(1))) -SELECT_LDFLAGS_COVER = $(patsubst ldflag-cov:%,ldflag:%,$(filter-out ldflag-opt:% ldflag-dbg:%,$(1))) - -# ARGS: abstract_target -GET_LIBREFS_DEP_INFO = $(call GET_LIBREFS_DEP_INFO_1,$(foreach x,$($(call FOLD_TARGET,$(1))_LIBS),$(call EXPAND_LIBREF,$(x)))) -GET_LIBREFS_DEP_INFO_1 = $(filter noinst:% inst:% libdeps:%,$(1)) $(if $(filter rpath-noinst:%,$(1)),noinst_rpath) - -# ARGS: librefs_dep_info, compile_mode -FINALIZE_LIBREFS_DEP_INFO = $(call FILTER_UNPACK,noinst:% inst:% libdeps:%,$(call QUALIFY_LIBREFS,$(1),$(2))) - -# ARGS: finalized_expanded_librefs -LDFLAGS_FROM_LIBREFS = $(call FILTER_PATSUBST,noinst:%,%,$(1)) $(call FILTER_PATSUBST,lib:%,-l%,$(1)) $(call FILTER_PATSUBST,dir:%,-L%,$(1)) $(call FILTER_PATSUBST,ldflag:%,%,$(1)) -RPATHS_FROM_LIBREFS = $(NOINST_RPATHS_FROM_LIBREFS) -ifeq ($(ENABLE_NOINST_BUILD),) -RPATHS_FROM_LIBREFS = $(foreach x,$(call FILTER_PATSUBST,rpath:%,%,$(1)),-Wl,-rpath,$(x)) -endif -NOINST_RPATHS_FROM_LIBREFS = $(foreach x,$(call FILTER_PATSUBST,rpath-noinst:%,%,$(1)),-Wl,-rpath,\$$ORIGIN$(if $(call IS_EQUAL_TO,$(x),.),,/$(x))) -ifeq ($(OS),Darwin) -NOINST_RPATHS_FROM_LIBREFS = $(foreach x,$(call FILTER_PATSUBST,rpath-noinst:%,%,$(1)),-Wl,-rpath,@loader_path/$(x)) -endif - -# ARGS: target, objects, abstract_target, compile_mode -NOINST_PROG_RECIPE = $(call NOINST_PROG_RECIPE_1,$(1),$(2),$(3),$(4),$(call FINALIZE_EXPANDED_LIBREFS,$(call EXPAND_LIBREFS,$(3)),$(4))) -NOINST_PROG_RECIPE_1 = $(call LIST_CONCAT,$(strip $(LD_PROG_$(4)) $(2) $(call LDFLAGS_FROM_LIBREFS,$(5)) $(call GET_LDFLAGS_FOR_TARGET,$(3),$(4)) $(LDFLAGS_ARCH)),$(call NOINST_RPATHS_FROM_LIBREFS,$(5))) -o $(1) - -INST_PROG_RECIPE = $(call INST_PROG_RECIPE_1,$(1),$(2),$(3),$(4),$(call FINALIZE_EXPANDED_LIBREFS,$(call EXPAND_LIBREFS,$(3)),$(4))) -INST_PROG_RECIPE_1 = $(strip $(LD_PROG_$(4)) $(2) $(call LDFLAGS_FROM_LIBREFS,$(5)) $(call GET_LDFLAGS_FOR_TARGET,$(3),$(4)) $(LDFLAGS_ARCH) $(call RPATHS_FROM_LIBREFS,$(5))) -o $(1) - -# ARGS: target, objects, deps, abstract_target, compile_mode, has_noinst_rpaths -define NOINST_PROG_RULES -$(1): $(2) $(3) - $$(call NOINST_PROG_RECIPE,$(1),$(2),$(4),$(5)) -endef -define INST_PROG_RULES -ifeq ($(if $(ENABLE_NOINST_BUILD),,$(6)),) -$(1): $(2) $(3) - $$(call INST_PROG_RECIPE,$(1),$(2),$(4),$(5)) -else -$(1) $(1)-noinst: $(2) $(3) - $$(call INST_PROG_RECIPE,$(1),$(2),$(4),$(5)) - $$(call NOINST_PROG_RECIPE,$(1)-noinst,$(2),$(4),$(5)) -endif -endef - -# ARGS: abstract_target, abstract_objects, librefs_dep_info, extra_deps, compile_mode, prog_type -EVAL_PROG_RULES_3 = $(eval $(call $(6)_PROG_RULES,$(1)$(SUFFIX_PROG_$(5)),$(patsubst %.o,%$(SUFFIX_OBJ_STATIC_$(5)),$(2)),$(call FINALIZE_LIBREFS_DEP_INFO,$(3),$(5)) $(call GET_DEPS_FOR_TARGET,$(1)),$(1),$(5),$(filter noinst_rpath,$(3)))) - -EVAL_PROG_RULES_2 = $(foreach x,OPTIM DEBUG COVER,$(call EVAL_PROG_RULES_3,$(1),$(2),$(3),$(4),$(x),$(5))) - -EVAL_PROG_RULES_1 = $(call EVAL_PROG_RULES_2,$(1),$(call GET_OBJECTS_FOR_TARGET,$(1),.o),$(call GET_LIBREFS_DEP_INFO,$(1)),$(call GET_DEPS_FOR_TARGET,$(1)),$(2)) - -$(foreach x,$(noinst_PROGRAMS) $(check_PROGRAMS),$(call EVAL_PROG_RULES_1,$(x),NOINST)) -$(foreach x,$(INST_PROGRAMS) $(DEV_PROGRAMS),$(call EVAL_PROG_RULES_1,$(x),INST)) - - - -# CREATING/LINKING LIBRARIES - -# For each library `libfoo.a` (installed or uninstalled) a 'libdeps' -# file called `libfoo.libdeps` is also created. This file contains a -# space-separated list of entries of various different kinds needed -# when linking project-local targets against the library. The order of -# entries is immaterial. -# -# -# If `libinst.a` is an installed library, then `libinst.libdeps` -# contains a number of `rpath:` and `noninst-rpath:` entries. The -# `rpath:` entries are used in `-rpath` flags when linking installed -# programs against `libinst.a`. The `rpath-noinst:` entries are -# similar, but they are used when linking programs that are not -# installed (i.e., those that can be executed before `libinst.a` is -# installed). While the paths specified by the `rpath:` entries are -# absolute, the paths specified by the `noninst-rpath:` entries are -# always relative to the directory containing the 'libdeps' file. -# -# First of all, `libinst.libdeps` contains an `rpath:` and a -# `rpath-noinst:` entry for itself. For instance: -# -# rpath:/usr/local/lib rpath-noinst:. -# -# Further more, `libinst.libdeps` contains an `rpath:` and a -# `rpath-noinst:` entry for each installed library `libxxx.a`, that -# `libinst.a` depends on, and which is also part of this project, -# unless those entries would lead to duplicates. This is true even -# when `libxxx.a` is an indirect dependency of `libinst.a` -# (transitivity). For example, if `libxxx.a` is an dependency of -# `libyyy.a` and `libyyy.a` is a dependency of `libinst.a`, then -# `libxxx.a` is an indirect dependency of `libinst.a`. Let us assume -# that `libinst.a`, `libxxx.a`, and `libyyy.a` are located in -# subdirectories `inst`, `xxx`, and `yyy` respectively, and all are -# installed in `/usr/local/lib`, then `libinst.libdeps` will contain -# -# rpath:/usr/local/lib rpath-noinst:. rpath-noinst:../xxx -# rpath-noinst:../yyy -# -# Had they all been located in the same directory, `libinst.libdeps` -# would instead contain -# -# rpath:/usr/local/lib rpath-noinst:. -# -# -# If `libconv.a` is a convenience library (not installed), then -# `libconv.libdeps` contains a `noinst` entry that identifies it as a -# convenience library from the point of view of `Makefile`s in other -# subdirectories. Apart from that, it contains a `lib:` entry for each -# installed project-local library that `libconv.a` directly depends -# on, and it contains the union of the contents of the 'libdeps' files -# associated with each of those `lib:` entries with relative paths -# transformed as necessary. Aa with `noninst-rpath:`, the paths -# specified by the `lib:` entries are always relative to the directory -# containing the 'libdeps' file. For example, if `libconv.a` depends -# on `libinst.a`, and `libconv.a` is located in the root directory of -# the project, and the installed libraries are located in distinct -# subdirectories as described in an example above, then -# `libconv.libdeps` will contain -# -# noinst lib:inst/libinst.a rpath:/usr/local/lib rpath-noinst:inst -# rpath-noinst:xxx rpath-noinst:yyy -# -# Note how the relative paths in the `rpath-noinst:` entries have been -# transformed such that they are now relative to the root directory. -# -# When extra linker flags are attached to a convenience library, those -# flags will also be carried in the 'libdeps' file. For example, -# `libconv.libdeps` might contain -# -# ldflag-opt:-lmagic ldflag-opt:-L/opt/magic/lib -# ldflag-dbg:-lmagic ldflag-dbg:-L/opt/magic-debug/lib -# ldflag-cov:-lmagic ldflag-cov:-L/opt/magic-debug/lib -# -# The `ldflag-opt:` entries are used when compiling in optimized -# (default) mode, while the `ldflag-dbg:` and the `ldflag-cov:` -# entries are used when compiling in debug and coverage modes -# respectively. - -# ARGS: target, objects, extra_deps -define STATIC_LIBRARY_RULE -$(1): $(2) $(3) - $$(RM) $(1) - $$(strip $$(AR) $$(ARFLAGS_GENERAL) $(1) $(2)) -endef - -# ARGS: real_local_path, objects, finalized_expanded_librefs, extra_deps, link_cmd, ldflags, lib_version -SHARED_LIBRARY_RULE_HELPER = $(call SHARED_LIBRARY_RULE,$(1),$(2) $(call FILTER_UNPACK,inst:% libdeps:%,$(3)) $(4),$(5) $(2) $(call LDFLAGS_FROM_LIBREFS,$(3)) $(6) $$(LDFLAGS_ARCH),$(if $(ENABLE_NOINST_BUILD),,$(7))) - -# ARGS: qual_lib_name, deps, cmd, version -SHARED_LIBRARY_RULE = $(SHARED_LIBRARY_RULE_DEFAULT) -define SHARED_LIBRARY_RULE_DEFAULT -$(1): $(2) - $$(strip $(3)) -o $(1) - $(STRIP) -s $(1) -endef - -ifeq ($(OS),Linux) - -# ARGS: qual_lib_name, deps, cmd, version -SHARED_LIBRARY_RULE = $(if $(4),$(call SHARED_LIBRARY_RULE_VER,$(1),$(2),$(3),$(call MAP_SHARED_LIB_VERSION,$(4))),$(SHARED_LIBRARY_RULE_DEFAULT)) - -# ARGS: qual_lib_name, deps, cmd, mapped_version -SHARED_LIBRARY_RULE_VER = $(call SHARED_LIBRARY_RULE_VER_2,$(1),$(2),$(3),$(word 1,$(4)),$(word 2,$(4))) - -# ARGS: qual_lib_name, deps, cmd, major_version, full_version -define SHARED_LIBRARY_RULE_VER_2 -$(1) $(1).$(4) $(1).$(5): $(2) - $$(strip $(3) -Wl,-soname,$(notdir $(1).$(4))) -o $(1).$(5) - ln -s -f $(notdir $(1).$(5)) $(1).$(4) - ln -s -f $(notdir $(1).$(4)) $(1) -endef - -endif - -ifeq ($(OS),Darwin) - -# See http://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html - -# ARGS: qual_lib_name, deps, cmd, version -SHARED_LIBRARY_RULE = $(if $(4),$(call SHARED_LIBRARY_RULE_VER,$(1),$(2),$(3),$(call MAP_SHARED_LIB_VERSION,$(1),$(4))),$(SHARED_LIBRARY_RULE_DEFAULT)) - -# ARGS: qual_lib_name, deps, cmd, mapped_version -SHARED_LIBRARY_RULE_VER = $(call SHARED_LIBRARY_RULE_VER_2,$(1),$(2),$(3),$(word 1,$(4)),$(word 2,$(4)),$(word 3,$(4))) - -# ARGS: qual_lib_name, deps, cmd, qual_lib_name_with_version, compatibility_version, current_version -define SHARED_LIBRARY_RULE_VER_2 -$(1) $(4): $(2) - $$(strip $(3) -install_name @rpath/$(notdir $(4)) -compatibility_version $(5) -current_version $(6)) -o $(4) - ln -s -f $(notdir $(4)) $(1) -endef - -endif - -# ARGS: target_stem_path, contents, deps -define LIBDEPS_RULE -$(1)$$(LIB_SUFFIX_LIBDEPS): $(3) $$(DEP_MAKEFILES) - echo $$(call EXPORT_LIBDEPS,$(2),$(dir $(1))) >$(1)$$(LIB_SUFFIX_LIBDEPS) -endef - -# ARGS: abstract_target, extra_deps -define NOINST_LIB_RULES -$(call STATIC_LIBRARY_RULE,$(call GET_LIBRARY_STEM,$(1))$(SUFFIX_LIB_STATIC_OPTIM),$(call GET_OBJECTS_FOR_TARGET,$(1),$(SUFFIX_OBJ_STATIC_OPTIM)),$(3)) -$(call STATIC_LIBRARY_RULE,$(call GET_LIBRARY_STEM,$(1))$(SUFFIX_LIB_STATIC_DEBUG),$(call GET_OBJECTS_FOR_TARGET,$(1),$(SUFFIX_OBJ_STATIC_DEBUG)),$(3)) -$(call STATIC_LIBRARY_RULE,$(call GET_LIBRARY_STEM,$(1))$(SUFFIX_LIB_STATIC_COVER),$(call GET_OBJECTS_FOR_TARGET,$(1),$(SUFFIX_OBJ_STATIC_COVER)),$(3)) -$(call LIBDEPS_RULE,$(call GET_LIBRARY_STEM,$(1)),$(call MAKE_NOINST_LIBDEPS,$(1)),$(foreach x,$($(call FOLD_TARGET,$(1))_LIBS),$(call GET_LIBRARY_STEM,$(x))$(LIB_SUFFIX_LIBDEPS))) -endef - -# ARGS: abstract_target, expanded_librefs, extra_deps -define INST_LIB_RULES -$(call STATIC_LIBRARY_RULE,$(call GET_LIBRARY_STEM,$(1))$(SUFFIX_LIB_STATIC_OPTIM),$(call GET_OBJECTS_FOR_TARGET,$(1),$(SUFFIX_OBJ_STATIC_OPTIM)),$(3)) -$(call STATIC_LIBRARY_RULE,$(call GET_LIBRARY_STEM,$(1))$(SUFFIX_LIB_STATIC_DEBUG),$(call GET_OBJECTS_FOR_TARGET,$(1),$(SUFFIX_OBJ_STATIC_DEBUG)),$(3)) -$(call STATIC_LIBRARY_RULE,$(call GET_LIBRARY_STEM,$(1))$(SUFFIX_LIB_STATIC_COVER),$(call GET_OBJECTS_FOR_TARGET,$(1),$(SUFFIX_OBJ_STATIC_COVER)),$(3)) -$(call SHARED_LIBRARY_RULE_HELPER,$(call GET_LIBRARY_STEM,$(1))$(SUFFIX_LIB_SHARED_OPTIM),$(call GET_OBJECTS_FOR_TARGET,$(1),$(SUFFIX_OBJ_SHARED_OPTIM)),$(call FINALIZE_EXPANDED_LIBREFS,$(2),OPTIM),$(3),$$(LD_LIB_OPTIM),$(call GET_LDFLAGS_FOR_TARGET,$(1),OPTIM),$(call GET_LIBRARY_VERSION,$(1))) -$(call SHARED_LIBRARY_RULE_HELPER,$(call GET_LIBRARY_STEM,$(1))$(SUFFIX_LIB_SHARED_DEBUG),$(call GET_OBJECTS_FOR_TARGET,$(1),$(SUFFIX_OBJ_SHARED_DEBUG)),$(call FINALIZE_EXPANDED_LIBREFS,$(2),DEBUG),$(3),$$(LD_LIB_DEBUG),$(call GET_LDFLAGS_FOR_TARGET,$(1),DEBUG),$(call GET_LIBRARY_VERSION,$(1))) -$(call SHARED_LIBRARY_RULE_HELPER,$(call GET_LIBRARY_STEM,$(1))$(SUFFIX_LIB_SHARED_COVER),$(call GET_OBJECTS_FOR_TARGET,$(1),$(SUFFIX_OBJ_SHARED_COVER)),$(call FINALIZE_EXPANDED_LIBREFS,$(2),COVER),$(3),$$(LD_LIB_COVER),$(call GET_LDFLAGS_FOR_TARGET,$(1),COVER),$(call GET_LIBRARY_VERSION,$(1))) -$(call LIBDEPS_RULE,$(call GET_LIBRARY_STEM,$(1)),$(call EXTRACT_INST_LIB_LIBDEPS,$(2)),$(call FILTER_PATSUBST,libdeps:%,%,$(2))) -endef - -define LIBRARY_RULES -$(foreach x,$(noinst_LIBRARIES) $(check_LIBRARIES),$(NEWLINE)$(call NOINST_LIB_RULES,$(x),$(call GET_DEPS_FOR_TARGET,$(x)))$(NEWLINE)) -$(foreach x,$(INST_LIBRARIES),$(NEWLINE)$(call INST_LIB_RULES,$(x),$(call EXPAND_INST_LIB_LIBREFS,$(x)),$(call GET_DEPS_FOR_TARGET,$(x)))$(NEWLINE)) -endef - -$(eval $(LIBRARY_RULES)) - - - -# FLEX AND BISON - -%.flex.cpp %.flex.hpp: %.flex $(DEP_MAKEFILES) - flex --outfile=$*.flex.cpp --header-file=$*.flex.hpp $< - -%.bison.cpp %.bison.hpp: %.bison $(DEP_MAKEFILES) - bison --output=$*.bison.cpp --defines=$*.bison.hpp $< - - - -# COMPILING + AUTOMATIC DEPENDENCIES - -$(foreach x,$(LIBRARIES) $(PROGRAMS),$(foreach y,$(call GET_OBJECTS_FOR_TARGET,$(x),.o),$(eval GMK_TARGETS_$(call FOLD_TARGET,$(y)) += $(x)))) - -GET_CFLAGS_FOR_TARGET = $(foreach x,PROJECT DIR $(foreach y,$(GMK_TARGETS_$(call FOLD_TARGET,$(1))) $(1),$(call FOLD_TARGET,$(y))),$(call GET_FLAGS,$(x)_CFLAGS,$(2))) - -%$(SUFFIX_OBJ_STATIC_OPTIM): %.c - $(strip $(CC_STATIC_OPTIM) $(call GET_CFLAGS_FOR_TARGET,$*.o,OPTIM) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_STATIC_OPTIM): %.cpp - $(strip $(CXX_STATIC_OPTIM) $(call GET_CFLAGS_FOR_TARGET,$*.o,OPTIM) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_SHARED_OPTIM): %.c - $(strip $(CC_SHARED_OPTIM) $(call GET_CFLAGS_FOR_TARGET,$*.o,OPTIM) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_SHARED_OPTIM): %.cpp - $(strip $(CXX_SHARED_OPTIM) $(call GET_CFLAGS_FOR_TARGET,$*.o,OPTIM) $(CFLAGS_OTHER)) -c $< -o $@ - - -%$(SUFFIX_OBJ_STATIC_DEBUG): %.c - $(strip $(CC_STATIC_DEBUG) $(call GET_CFLAGS_FOR_TARGET,$*.o,DEBUG) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_STATIC_DEBUG): %.cpp - $(strip $(CXX_STATIC_DEBUG) $(call GET_CFLAGS_FOR_TARGET,$*.o,DEBUG) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_SHARED_DEBUG): %.c - $(strip $(CC_SHARED_DEBUG) $(call GET_CFLAGS_FOR_TARGET,$*.o,DEBUG) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_SHARED_DEBUG): %.cpp - $(strip $(CXX_SHARED_DEBUG) $(call GET_CFLAGS_FOR_TARGET,$*.o,DEBUG) $(CFLAGS_OTHER)) -c $< -o $@ - - -%$(SUFFIX_OBJ_STATIC_COVER): %.c - $(strip $(CC_STATIC_COVER) $(call GET_CFLAGS_FOR_TARGET,$*.o,COVER) $(CFLAGS_OTHER)) -c $(abspath $<) -o $(abspath $@) - -%$(SUFFIX_OBJ_STATIC_COVER): %.cpp - $(strip $(CXX_STATIC_COVER) $(call GET_CFLAGS_FOR_TARGET,$*.o,COVER) $(CFLAGS_OTHER)) -c $(abspath $<) -o $(abspath $@) - -%$(SUFFIX_OBJ_SHARED_COVER): %.c - $(strip $(CC_SHARED_COVER) $(call GET_CFLAGS_FOR_TARGET,$*.o,COVER) $(CFLAGS_OTHER)) -c $(abspath $<) -o $(abspath $@) - -%$(SUFFIX_OBJ_SHARED_COVER): %.cpp - $(strip $(CXX_SHARED_COVER) $(call GET_CFLAGS_FOR_TARGET,$*.o,COVER) $(CFLAGS_OTHER)) -c $(abspath $<) -o $(abspath $@) - - - -%$(SUFFIX_OBJ_STATIC_OPTIM): %.m - $(strip $(OCC_STATIC_OPTIM) $(call GET_CFLAGS_FOR_TARGET,$*.o,OPTIM) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_STATIC_OPTIM): %.mm - $(strip $(OCXX_STATIC_OPTIM) $(call GET_CFLAGS_FOR_TARGET,$*.o,OPTIM) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_SHARED_OPTIM): %.m - $(strip $(OCC_SHARED_OPTIM) $(call GET_CFLAGS_FOR_TARGET,$*.o,OPTIM) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_SHARED_OPTIM): %.mm - $(strip $(OCXX_SHARED_OPTIM) $(call GET_CFLAGS_FOR_TARGET,$*.o,OPTIM) $(CFLAGS_OTHER)) -c $< -o $@ - - -%$(SUFFIX_OBJ_STATIC_DEBUG): %.m - $(strip $(OCC_STATIC_DEBUG) $(call GET_CFLAGS_FOR_TARGET,$*.o,DEBUG) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_STATIC_DEBUG): %.mm - $(strip $(OCXX_STATIC_DEBUG) $(call GET_CFLAGS_FOR_TARGET,$*.o,DEBUG) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_SHARED_DEBUG): %.m - $(strip $(OCC_SHARED_DEBUG) $(call GET_CFLAGS_FOR_TARGET,$*.o,DEBUG) $(CFLAGS_OTHER)) -c $< -o $@ - -%$(SUFFIX_OBJ_SHARED_DEBUG): %.mm - $(strip $(OCXX_SHARED_DEBUG) $(call GET_CFLAGS_FOR_TARGET,$*.o,DEBUG) $(CFLAGS_OTHER)) -c $< -o $@ - - -%$(SUFFIX_OBJ_STATIC_COVER): %.m - $(strip $(OCC_STATIC_COVER) $(call GET_CFLAGS_FOR_TARGET,$*.o,COVER) $(CFLAGS_OTHER)) -c $(abspath $<) -o $(abspath $@) - -%$(SUFFIX_OBJ_STATIC_COVER): %.mm - $(strip $(OCXX_STATIC_COVER) $(call GET_CFLAGS_FOR_TARGET,$*.o,COVER) $(CFLAGS_OTHER)) -c $(abspath $<) -o $(abspath $@) - -%$(SUFFIX_OBJ_SHARED_COVER): %.m - $(strip $(OCC_SHARED_COVER) $(call GET_CFLAGS_FOR_TARGET,$*.o,COVER) $(CFLAGS_OTHER)) -c $(abspath $<) -o $(abspath $@) - -%$(SUFFIX_OBJ_SHARED_COVER): %.mm - $(strip $(OCXX_SHARED_COVER) $(call GET_CFLAGS_FOR_TARGET,$*.o,COVER) $(CFLAGS_OTHER)) -c $(abspath $<) -o $(abspath $@) - - --include $(OBJECTS:.o=.d) diff --git a/realm/realm-jni/project.mk b/realm/realm-jni/project.mk deleted file mode 100644 index cf9b0e6b95..0000000000 --- a/realm/realm-jni/project.mk +++ /dev/null @@ -1,64 +0,0 @@ -ENABLE_INSTALL_DEBUG_LIBS = 1 - -# Construct fat binaries on Darwin when using Clang -ifneq ($(REALM_ENABLE_FAT_BINARIES),) - ifeq ($(OS),Darwin) - ifeq ($(COMPILER_IS),clang) - CFLAGS_ARCH += -arch i386 -arch x86_64 - endif - endif -endif - -ifeq ($(OS),Darwin) - CFLAGS_ARCH += -mmacosx-version-min=10.8 -stdlib=libc++ -endif - -# FIXME: '-fno-elide-constructors' currently causes Realm to fail -#CFLAGS_DEBUG += -fno-elide-constructors -CFLAGS_PTHREADS += -pthread -CFLAGS_GENERAL += -Wextra -ansi -pedantic -Wno-long-long - -# Avoid a warning from Clang when linking on OS X. By default, -# `LDFLAGS_PTHREADS` inherits its value from `CFLAGS_PTHREADS`, so we -# have to override that with an empty value. -ifeq ($(OS),Darwin) - ifeq ($(LD_IS),clang) - LDFLAGS_PTHREADS = $(EMPTY) - endif -endif - -# Load dynamic configuration -#ifeq ($(NO_CONFIG_MK),) - #CONFIG_MK = $(GENERIC_MK_DIR)/config.mk - #DEP_MAKEFILES += $(CONFIG_MK) - #include $(CONFIG_MK) - LIB_SUFFIX_SHARED = $(JNI_SUFFIX) - #EXTRA_PRIMARY_PREFIXES = jni - #jnidir = $(JNI_INSTALL_DIR) -#endif - -ifeq ($(REALM_ANDROID),) - REALM_LDFLAGS += -llog - CFLAGS_INCLUDE += $(JAVA_CFLAGS) - ifneq ($(REALM_ENABLE_MEM_USAGE),) - PROJECT_CFLAGS += -DREALM_ENABLE_MEM_USAGE - ifeq ($(shell pkg-config libprocps --exists 2>/dev/null && echo yes),yes) - PROCPS_CFLAGS := $(shell pkg-config libprocps --cflags) - PROCPS_LDFLAGS := $(shell pkg-config libprocps --libs) - PROJECT_CFLAGS += $(PROCPS_CFLAGS) - PROJECT_LDFLAGS += $(PROCPS_LDFLAGS) - else - PROJECT_LDFLAGS += -lproc - endif - endif -else - PROJECT_CFLAGS += -DANDROID - CFLAGS_OPTIM = -Os -DNDEBUG -endif - -PROJECT_CFLAGS_OPTIM += ${REALM_CFLAGS_COMMON} $(REALM_CFLAGS) -PROJECT_CFLAGS_DEBUG += ${REALM_CFLAGS_COMMON} $(REALM_CFLAGS_DBG) -PROJECT_CFLAGS_COVER += ${REALM_CFLAGS_COMMON} $(REALM_CFLAGS_DBG) -PROJECT_LDFLAGS_OPTIM += $(REALM_LDFLAGS_COMMON) $(REALM_LDFLAGS) -PROJECT_LDFLAGS_DEBUG += $(REALM_LDFLAGS_COMMON) $(REALM_LDFLAGS_DBG) -PROJECT_LDFLAGS_COVER += $(REALM_LDFLAGS_COMMON) $(REALM_LDFLAGS_DBG) diff --git a/realm/realm-jni/src/Makefile b/realm/realm-jni/src/Makefile deleted file mode 100644 index 4a8eabdd10..0000000000 --- a/realm/realm-jni/src/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -lib_LIBRARIES = librealm-jni.a - -JNI_SOURCES := $(wildcard *.cpp) -librealm_jni_a_SOURCES = $(JNI_SOURCES) - -include ../generic.mk diff --git a/realm/realm-jni/src/io_realm_internal_CheckedRow.h b/realm/realm-jni/src/io_realm_internal_CheckedRow.h deleted file mode 100644 index 8674f9947c..0000000000 --- a/realm/realm-jni/src/io_realm_internal_CheckedRow.h +++ /dev/null @@ -1,197 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_internal_CheckedRow */ - -#ifndef _Included_io_realm_internal_CheckedRow -#define _Included_io_realm_internal_CheckedRow -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetColumnCount - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnCount - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetColumnName - * Signature: (JJ)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnName - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetColumnIndex - * Signature: (JLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnIndex - (JNIEnv *, jobject, jlong, jstring); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetColumnType - * Signature: (JJ)I - */ -JNIEXPORT jint JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnType - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetLong - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLong - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetBoolean - * Signature: (JJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeGetBoolean - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetFloat - * Signature: (JJ)F - */ -JNIEXPORT jfloat JNICALL Java_io_realm_internal_CheckedRow_nativeGetFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetDouble - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_CheckedRow_nativeGetDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetTimestamp - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetTimestamp - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetString - * Signature: (JJ)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetString - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeIsNullLink - * Signature: (JJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeIsNullLink - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetByteArray - * Signature: (JJ)[B - */ -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_CheckedRow_nativeGetByteArray - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetLinkView - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLinkView - (JNIEnv *, jclass, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeSetLong - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetLong - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeSetBoolean - * Signature: (JJZ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetBoolean - (JNIEnv *, jobject, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeSetFloat - * Signature: (JJF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetFloat - (JNIEnv *, jobject, jlong, jlong, jfloat); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeGetLink - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLink - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeSetDouble - * Signature: (JJD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetDouble - (JNIEnv *, jobject, jlong, jlong, jdouble); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeSetTimestamp - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetTimestamp - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeSetString - * Signature: (JJLjava/lang/String;)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetString - (JNIEnv *, jobject, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeSetByteArray - * Signature: (JJ[B)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetByteArray - (JNIEnv *, jobject, jlong, jlong, jbyteArray); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeSetLink - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetLink - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_CheckedRow - * Method: nativeNullifyLink - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeNullifyLink - (JNIEnv *, jobject, jlong, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-jni/src/io_realm_internal_Group.h b/realm/realm-jni/src/io_realm_internal_Group.h deleted file mode 100644 index 0099bfec7c..0000000000 --- a/realm/realm-jni/src/io_realm_internal_Group.h +++ /dev/null @@ -1,155 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_internal_Group */ - -#ifndef _Included_io_realm_internal_Group -#define _Included_io_realm_internal_Group -#ifdef __cplusplus -extern "C" { -#endif -#undef io_realm_internal_Group_MODE_READONLY -#define io_realm_internal_Group_MODE_READONLY 0L -#undef io_realm_internal_Group_MODE_READWRITE -#define io_realm_internal_Group_MODE_READWRITE 1L -#undef io_realm_internal_Group_MODE_READWRITE_NOCREATE -#define io_realm_internal_Group_MODE_READWRITE_NOCREATE 2L -/* - * Class: io_realm_internal_Group - * Method: nativeRemoveTable - * Signature: (JLjava/lang/String;)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeRemoveTable - (JNIEnv *, jobject, jlong, jstring); - -/* - * Class: io_realm_internal_Group - * Method: nativeRenameTable - * Signature: (JLjava/lang/String;Ljava/lang/String;)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeRenameTable - (JNIEnv *, jobject, jlong, jstring, jstring); - -/* - * Class: io_realm_internal_Group - * Method: createNative - * Signature: ()J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_createNative__ - (JNIEnv *, jobject); - -/* - * Class: io_realm_internal_Group - * Method: createNative - * Signature: (Ljava/lang/String;I)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_createNative__Ljava_lang_String_2I - (JNIEnv *, jobject, jstring, jint); - -/* - * Class: io_realm_internal_Group - * Method: createNative - * Signature: ([B)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_createNative___3B - (JNIEnv *, jobject, jbyteArray); - -/* - * Class: io_realm_internal_Group - * Method: createNative - * Signature: (Ljava/nio/ByteBuffer;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_createNative__Ljava_nio_ByteBuffer_2 - (JNIEnv *, jobject, jobject); - -/* - * Class: io_realm_internal_Group - * Method: nativeClose - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeClose - (JNIEnv *, jclass, jlong); - -/* - * Class: io_realm_internal_Group - * Method: nativeSize - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_nativeSize - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Group - * Method: nativeGetTableName - * Signature: (JI)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_Group_nativeGetTableName - (JNIEnv *, jobject, jlong, jint); - -/* - * Class: io_realm_internal_Group - * Method: nativeHasTable - * Signature: (JLjava/lang/String;)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Group_nativeHasTable - (JNIEnv *, jobject, jlong, jstring); - -/* - * Class: io_realm_internal_Group - * Method: nativeWriteToFile - * Signature: (JLjava/lang/String;[B)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeWriteToFile - (JNIEnv *, jobject, jlong, jstring, jbyteArray); - -/* - * Class: io_realm_internal_Group - * Method: nativeGetTableNativePtr - * Signature: (JLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_nativeGetTableNativePtr - (JNIEnv *, jobject, jlong, jstring); - -/* - * Class: io_realm_internal_Group - * Method: nativeWriteToMem - * Signature: (J)[B - */ -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_Group_nativeWriteToMem - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Group - * Method: nativeToJson - * Signature: (J)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_Group_nativeToJson - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Group - * Method: nativeCommit - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeCommit - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Group - * Method: nativeToString - * Signature: (J)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_Group_nativeToString - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Group - * Method: nativeIsEmpty - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Group_nativeIsEmpty - (JNIEnv *, jobject, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-jni/src/io_realm_internal_LinkView.h b/realm/realm-jni/src/io_realm_internal_LinkView.h deleted file mode 100644 index 7c9e93e61e..0000000000 --- a/realm/realm-jni/src/io_realm_internal_LinkView.h +++ /dev/null @@ -1,149 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_internal_LinkView */ - -#ifndef _Included_io_realm_internal_LinkView -#define _Included_io_realm_internal_LinkView -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: io_realm_internal_LinkView - * Method: nativeClose - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeClose - (JNIEnv *, jclass, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeGetRow - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetRow - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeGetTargetRowIndex - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetRowIndex - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeAdd - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeAdd - (JNIEnv *, jclass, jlong, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeInsert - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeInsert - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeSet - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeSet - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeMove - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeMove - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeRemove - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemove - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeClear - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeClear - (JNIEnv *, jclass, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeSize - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeSize - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeIsEmpty - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsEmpty - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeWhere - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeWhere - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeIsAttached - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsAttached - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeFind - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeFind - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeRemoveTargetRow - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveTargetRow - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeRemoveAllTargetRows - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveAllTargetRows - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_LinkView - * Method: nativeGetTargetTable - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable - (JNIEnv *, jobject, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-jni/src/io_realm_internal_SharedGroup.h b/realm/realm-jni/src/io_realm_internal_SharedGroup.h deleted file mode 100644 index 2e0dd9d035..0000000000 --- a/realm/realm-jni/src/io_realm_internal_SharedGroup.h +++ /dev/null @@ -1,201 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_internal_SharedGroup */ - -#ifndef _Included_io_realm_internal_SharedGroup -#define _Included_io_realm_internal_SharedGroup -#ifdef __cplusplus -extern "C" { -#endif -#undef io_realm_internal_SharedGroup_IMPLICIT_TRANSACTION -#define io_realm_internal_SharedGroup_IMPLICIT_TRANSACTION 1L -#undef io_realm_internal_SharedGroup_EXPLICIT_TRANSACTION -#define io_realm_internal_SharedGroup_EXPLICIT_TRANSACTION 0L -#undef io_realm_internal_SharedGroup_CREATE_FILE_YES -#define io_realm_internal_SharedGroup_CREATE_FILE_YES 0L -#undef io_realm_internal_SharedGroup_CREATE_FILE_NO -#define io_realm_internal_SharedGroup_CREATE_FILE_NO 1L -#undef io_realm_internal_SharedGroup_ENABLE_REPLICATION -#define io_realm_internal_SharedGroup_ENABLE_REPLICATION 1L -#undef io_realm_internal_SharedGroup_DISABLE_REPLICATION -#define io_realm_internal_SharedGroup_DISABLE_REPLICATION 0L -/* - * Class: io_realm_internal_SharedGroup - * Method: createNativeWithImplicitTransactions - * Signature: (JI[B)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_createNativeWithImplicitTransactions - (JNIEnv *, jobject, jlong, jint, jbyteArray); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeCreateReplication - * Signature: (Ljava/lang/String;[B)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreateReplication - (JNIEnv *, jobject, jstring, jbyteArray); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeCommitAndContinueAsRead - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeCommitAndContinueAsRead - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeBeginImplicit - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeBeginImplicit - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeReserve - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeReserve - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeHasChanged - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedGroup_nativeHasChanged - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeBeginRead - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeBeginRead - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeEndRead - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeEndRead - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeBeginWrite - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeBeginWrite - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeCommit - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeCommit - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeRollback - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeRollback - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeCreate - * Signature: (Ljava/lang/String;IZZ[B)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreate - (JNIEnv *, jobject, jstring, jint, jboolean, jboolean, jbyteArray); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeCompact - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedGroup_nativeCompact - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeClose - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeClose - (JNIEnv *, jclass, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeCloseReplication - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeCloseReplication - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeRollbackAndContinueAsRead - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeRollbackAndContinueAsRead - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeGetVersionID - * Signature: (J)[J - */ -JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedGroup_nativeGetVersionID - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeWaitForChange - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedGroup_nativeWaitForChange - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeStopWaitForChange - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeStopWaitForChange - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeAdvanceRead - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeAdvanceRead - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativeAdvanceReadToVersion - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeAdvanceReadToVersion - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_SharedGroup - * Method: nativePromoteToWrite - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativePromoteToWrite - (JNIEnv *, jobject, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-jni/src/io_realm_internal_Table.h b/realm/realm-jni/src/io_realm_internal_Table.h deleted file mode 100644 index 7f0218941d..0000000000 --- a/realm/realm-jni/src/io_realm_internal_Table.h +++ /dev/null @@ -1,759 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_internal_Table */ - -#ifndef _Included_io_realm_internal_Table -#define _Included_io_realm_internal_Table -#ifdef __cplusplus -extern "C" { -#endif -#undef io_realm_internal_Table_TABLE_MAX_LENGTH -#define io_realm_internal_Table_TABLE_MAX_LENGTH 56L -#undef io_realm_internal_Table_INFINITE -#define io_realm_internal_Table_INFINITE -1LL -#undef io_realm_internal_Table_INTEGER_DEFAULT_VALUE -#define io_realm_internal_Table_INTEGER_DEFAULT_VALUE 0LL -#undef io_realm_internal_Table_NULLABLE -#define io_realm_internal_Table_NULLABLE 1L -#undef io_realm_internal_Table_NOT_NULLABLE -#define io_realm_internal_Table_NOT_NULLABLE 0L -#undef io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX -#define io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX 0LL -#undef io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX -#define io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX 1LL -#undef io_realm_internal_Table_NO_PRIMARY_KEY -#define io_realm_internal_Table_NO_PRIMARY_KEY -2LL -#undef io_realm_internal_Table_DEBUG -#define io_realm_internal_Table_DEBUG 0L -/* - * Class: io_realm_internal_Table - * Method: createNative - * Signature: ()J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_createNative - (JNIEnv *, jobject); - -/* - * Class: io_realm_internal_Table - * Method: nativeClose - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClose - (JNIEnv *, jclass, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeIsValid - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeIsRootTable - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsRootTable - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeAddColumn - * Signature: (JILjava/lang/String;Z)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumn - (JNIEnv *, jobject, jlong, jint, jstring, jboolean); - -/* - * Class: io_realm_internal_Table - * Method: nativeAddColumnLink - * Signature: (JILjava/lang/String;J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumnLink - (JNIEnv *, jobject, jlong, jint, jstring, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeRenameColumn - * Signature: (JJLjava/lang/String;)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRenameColumn - (JNIEnv *, jobject, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_Table - * Method: nativeRemoveColumn - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveColumn - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeIsColumnNullable - * Signature: (JJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeConvertColumnToNullable - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullable - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeConvertColumnToNotNullable - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNullable - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeSize - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSize - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeClear - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetColumnCount - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnCount - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetColumnName - * Signature: (JJ)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetColumnName - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetColumnIndex - * Signature: (JLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnIndex - (JNIEnv *, jobject, jlong, jstring); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetColumnType - * Signature: (JJ)I - */ -JNIEXPORT jint JNICALL Java_io_realm_internal_Table_nativeGetColumnType - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeRemove - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemove - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeRemoveLast - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveLast - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeMoveLastOver - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMoveLastOver - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeAddEmptyRow - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddEmptyRow - (JNIEnv *, jclass, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetSortedView - * Signature: (JJZ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedView - (JNIEnv *, jobject, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetSortedViewMulti - * Signature: (J[J[Z)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti - (JNIEnv *, jobject, jlong, jlongArray, jbooleanArray); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetLong - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLong - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetBoolean - * Signature: (JJJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeGetBoolean - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetFloat - * Signature: (JJJ)F - */ -JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeGetFloat - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetDouble - * Signature: (JJJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeGetDouble - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetTimestamp - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetTimestamp - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetString - * Signature: (JJJ)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetString - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetByteArray - * Signature: (JJJ)[B - */ -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_Table_nativeGetByteArray - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetLink - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLink - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetLinkView - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLinkView - (JNIEnv *, jclass, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetLinkTarget - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLinkTarget - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetRowPtr - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetRowPtr - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetLong - * Signature: (JJJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLong - (JNIEnv *, jclass, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetBoolean - * Signature: (JJJZ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean - (JNIEnv *, jclass, jlong, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetFloat - * Signature: (JJJF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetFloat - (JNIEnv *, jclass, jlong, jlong, jlong, jfloat); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetDouble - * Signature: (JJJD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetDouble - (JNIEnv *, jclass, jlong, jlong, jlong, jdouble); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetTimestamp - * Signature: (JJJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp - (JNIEnv *, jclass, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetString - * Signature: (JJJLjava/lang/String;)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString - (JNIEnv *, jclass, jlong, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetNull - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull - (JNIEnv *, jclass, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetByteArray - * Signature: (JJJ[B)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray - (JNIEnv *, jclass, jlong, jlong, jlong, jbyteArray); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetLink - * Signature: (JJJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLink - (JNIEnv *, jclass, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeSetPrimaryKey - * Signature: (JJLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey - (JNIEnv *, jobject, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_Table - * Method: nativeMigratePrimaryKeyTableIfNeeded - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeAddSearchIndex - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeAddSearchIndex - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeRemoveSearchIndex - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveSearchIndex - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeHasSearchIndex - * Signature: (JJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSearchIndex - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeIsNullLink - * Signature: (JJJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNullLink - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeNullifyLink - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeNullifyLink - (JNIEnv *, jclass, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeSumInt - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSumInt - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeMaximumInt - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMaximumInt - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeMinimumInt - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMinimumInt - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeAverageInt - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageInt - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeSumFloat - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeSumFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeMaximumFloat - * Signature: (JJ)F - */ -JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeMaximumFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeMinimumFloat - * Signature: (JJ)F - */ -JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeMinimumFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeAverageFloat - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeSumDouble - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeSumDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeMaximumDouble - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeMaximumDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeMinimumDouble - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeMinimumDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeAverageDouble - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeMaximumTimestamp - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMaximumTimestamp - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeMinimumTimestamp - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMinimumTimestamp - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeCountLong - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountLong - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeCountFloat - * Signature: (JJF)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountFloat - (JNIEnv *, jobject, jlong, jlong, jfloat); - -/* - * Class: io_realm_internal_Table - * Method: nativeCountDouble - * Signature: (JJD)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountDouble - (JNIEnv *, jobject, jlong, jlong, jdouble); - -/* - * Class: io_realm_internal_Table - * Method: nativeCountString - * Signature: (JJLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountString - (JNIEnv *, jobject, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_Table - * Method: nativeWhere - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeWhere - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindFirstInt - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstInt - (JNIEnv *, jclass, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindFirstBool - * Signature: (JJZ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstBool - (JNIEnv *, jobject, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindFirstFloat - * Signature: (JJF)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstFloat - (JNIEnv *, jobject, jlong, jlong, jfloat); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindFirstDouble - * Signature: (JJD)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstDouble - (JNIEnv *, jobject, jlong, jlong, jdouble); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindFirstTimestamp - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstTimestamp - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindFirstString - * Signature: (JJLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstString - (JNIEnv *, jclass, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindFirstNull - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull - (JNIEnv *, jclass, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindAllInt - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllInt - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindAllBool - * Signature: (JJZ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllBool - (JNIEnv *, jobject, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindAllFloat - * Signature: (JJF)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllFloat - (JNIEnv *, jobject, jlong, jlong, jfloat); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindAllDouble - * Signature: (JJD)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllDouble - (JNIEnv *, jobject, jlong, jlong, jdouble); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindAllTimestamp - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllTimestamp - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeFindAllString - * Signature: (JJLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllString - (JNIEnv *, jobject, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_Table - * Method: nativeLowerBoundInt - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeLowerBoundInt - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeUpperBoundInt - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeUpperBoundInt - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativePivot - * Signature: (JJJIJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativePivot - (JNIEnv *, jobject, jlong, jlong, jlong, jint, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetDistinctView - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetDistinctView - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeGetName - * Signature: (J)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetName - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeOptimize - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeOptimize - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeToJson - * Signature: (J)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeToJson - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeHasSameSchema - * Signature: (JJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_Table - * Method: nativeVersion - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeVersion - (JNIEnv *, jobject, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-jni/src/io_realm_internal_TableQuery.h b/realm/realm-jni/src/io_realm_internal_TableQuery.h deleted file mode 100644 index 129da77a12..0000000000 --- a/realm/realm-jni/src/io_realm_internal_TableQuery.h +++ /dev/null @@ -1,605 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_internal_TableQuery */ - -#ifndef _Included_io_realm_internal_TableQuery -#define _Included_io_realm_internal_TableQuery -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: io_realm_internal_TableQuery - * Method: nativeClose - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeClose - (JNIEnv *, jclass, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeValidateQuery - * Signature: (J)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_TableQuery_nativeValidateQuery - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeTableview - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeTableview - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGroup - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGroup - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeEndGroup - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndGroup - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeOr - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeOr - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeNot - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNot - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeEqual - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JJ - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeNotEqual - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JJ - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGreater - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JJ - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGreaterEqual - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JJ - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeLess - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JJ - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeLessEqual - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JJ - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeBetween - * Signature: (J[JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JJJ - (JNIEnv *, jobject, jlong, jlongArray, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeEqual - * Signature: (J[JF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JF - (JNIEnv *, jobject, jlong, jlongArray, jfloat); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeNotEqual - * Signature: (J[JF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JF - (JNIEnv *, jobject, jlong, jlongArray, jfloat); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGreater - * Signature: (J[JF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JF - (JNIEnv *, jobject, jlong, jlongArray, jfloat); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGreaterEqual - * Signature: (J[JF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JF - (JNIEnv *, jobject, jlong, jlongArray, jfloat); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeLess - * Signature: (J[JF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JF - (JNIEnv *, jobject, jlong, jlongArray, jfloat); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeLessEqual - * Signature: (J[JF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JF - (JNIEnv *, jobject, jlong, jlongArray, jfloat); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeBetween - * Signature: (J[JFF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JFF - (JNIEnv *, jobject, jlong, jlongArray, jfloat, jfloat); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeEqual - * Signature: (J[JD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JD - (JNIEnv *, jobject, jlong, jlongArray, jdouble); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeNotEqual - * Signature: (J[JD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JD - (JNIEnv *, jobject, jlong, jlongArray, jdouble); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGreater - * Signature: (J[JD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JD - (JNIEnv *, jobject, jlong, jlongArray, jdouble); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGreaterEqual - * Signature: (J[JD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JD - (JNIEnv *, jobject, jlong, jlongArray, jdouble); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeLess - * Signature: (J[JD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JD - (JNIEnv *, jobject, jlong, jlongArray, jdouble); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeLessEqual - * Signature: (J[JD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JD - (JNIEnv *, jobject, jlong, jlongArray, jdouble); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeBetween - * Signature: (J[JDD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JDD - (JNIEnv *, jobject, jlong, jlongArray, jdouble, jdouble); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeEqual - * Signature: (J[JZ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JZ - (JNIEnv *, jobject, jlong, jlongArray, jboolean); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeEqualTimestamp - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualTimestamp - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeNotEqualTimestamp - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualTimestamp - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGreaterTimestamp - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterTimestamp - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGreaterEqualTimestamp - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualTimestamp - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeLessTimestamp - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessTimestamp - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeLessEqualTimestamp - * Signature: (J[JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualTimestamp - (JNIEnv *, jobject, jlong, jlongArray, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeBetweenTimestamp - * Signature: (J[JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenTimestamp - (JNIEnv *, jobject, jlong, jlongArray, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeEqual - * Signature: (J[J[B)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3B - (JNIEnv *, jobject, jlong, jlongArray, jbyteArray); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeNotEqual - * Signature: (J[J[B)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3B - (JNIEnv *, jobject, jlong, jlongArray, jbyteArray); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeEqual - * Signature: (J[JLjava/lang/String;Z)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JLjava_lang_String_2Z - (JNIEnv *, jobject, jlong, jlongArray, jstring, jboolean); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeNotEqual - * Signature: (J[JLjava/lang/String;Z)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JLjava_lang_String_2Z - (JNIEnv *, jobject, jlong, jlongArray, jstring, jboolean); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeBeginsWith - * Signature: (J[JLjava/lang/String;Z)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBeginsWith - (JNIEnv *, jobject, jlong, jlongArray, jstring, jboolean); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeEndsWith - * Signature: (J[JLjava/lang/String;Z)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndsWith - (JNIEnv *, jobject, jlong, jlongArray, jstring, jboolean); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeContains - * Signature: (J[JLjava/lang/String;Z)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeContains - (JNIEnv *, jobject, jlong, jlongArray, jstring, jboolean); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeIsEmpty - * Signature: (J[J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty - (JNIEnv *, jobject, jlong, jlongArray); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeFind - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFind - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeFindAll - * Signature: (JJJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll - (JNIEnv *, jobject, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeSumInt - * Signature: (JJJJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeSumInt - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeMaximumInt - * Signature: (JJJJJ)Ljava/lang/Long; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumInt - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeMinimumInt - * Signature: (JJJJJ)Ljava/lang/Long; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumInt - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeAverageInt - * Signature: (JJJJJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageInt - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeSumFloat - * Signature: (JJJJJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumFloat - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeMaximumFloat - * Signature: (JJJJJ)Ljava/lang/Float; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumFloat - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeMinimumFloat - * Signature: (JJJJJ)Ljava/lang/Float; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumFloat - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeAverageFloat - * Signature: (JJJJJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageFloat - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeSumDouble - * Signature: (JJJJJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumDouble - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeMaximumDouble - * Signature: (JJJJJ)Ljava/lang/Double; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumDouble - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeMinimumDouble - * Signature: (JJJJJ)Ljava/lang/Double; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumDouble - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeAverageDouble - * Signature: (JJJJJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageDouble - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeMaximumTimestamp - * Signature: (JJJJJ)Ljava/lang/Long; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumTimestamp - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeMinimumTimestamp - * Signature: (JJJJJ)Ljava/lang/Long; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumTimestamp - (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeIsNull - * Signature: (J[J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull - (JNIEnv *, jobject, jlong, jlongArray); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeIsNotNull - * Signature: (J[J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull - (JNIEnv *, jobject, jlong, jlongArray); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeCount - * Signature: (JJJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeCount - (JNIEnv *, jobject, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeRemove - * Signature: (JJJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeRemove - (JNIEnv *, jobject, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeImportHandoverTableViewIntoSharedGroup - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverTableViewIntoSharedGroup - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeHandoverQuery - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeFindAllSortedWithHandover - * Signature: (JJJJJJZ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllSortedWithHandover - (JNIEnv *, jclass, jlong, jlong, jlong, jlong, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeFindAllWithHandover - * Signature: (JJJJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllWithHandover - (JNIEnv *, jclass, jlong, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeGetDistinctViewWithHandover - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetDistinctViewWithHandover - (JNIEnv *, jclass, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeFindWithHandover - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindWithHandover - (JNIEnv *, jclass, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeFindAllMultiSortedWithHandover - * Signature: (JJJJJ[J[Z)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllMultiSortedWithHandover - (JNIEnv *, jclass, jlong, jlong, jlong, jlong, jlong, jlongArray, jbooleanArray); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeImportHandoverRowIntoSharedGroup - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRowIntoSharedGroup - (JNIEnv *, jclass, jlong, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeCloseQueryHandover - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeCloseQueryHandover - (JNIEnv *, jclass, jlong); - -/* - * Class: io_realm_internal_TableQuery - * Method: nativeBatchUpdateQueries - * Signature: (J[J[[J[[J[[Z)[J - */ -JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdateQueries - (JNIEnv *, jclass, jlong, jlongArray, jobjectArray, jobjectArray, jobjectArray); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-jni/src/io_realm_internal_TableView.h b/realm/realm-jni/src/io_realm_internal_TableView.h deleted file mode 100644 index fb1cfb53fe..0000000000 --- a/realm/realm-jni/src/io_realm_internal_TableView.h +++ /dev/null @@ -1,527 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_internal_TableView */ - -#ifndef _Included_io_realm_internal_TableView -#define _Included_io_realm_internal_TableView -#ifdef __cplusplus -extern "C" { -#endif -#undef io_realm_internal_TableView_DEBUG -#define io_realm_internal_TableView_DEBUG 0L -/* - * Class: io_realm_internal_TableView - * Method: nativeClose - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeClose - (JNIEnv *, jclass, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSize - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSize - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetSourceRowIndex - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetSourceRowIndex - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetColumnCount - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetColumnCount - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetColumnName - * Signature: (JJ)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_TableView_nativeGetColumnName - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetColumnIndex - * Signature: (JLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetColumnIndex - (JNIEnv *, jobject, jlong, jstring); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetColumnType - * Signature: (JJ)I - */ -JNIEXPORT jint JNICALL Java_io_realm_internal_TableView_nativeGetColumnType - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetLong - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetLong - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetBoolean - * Signature: (JJJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_TableView_nativeGetBoolean - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetFloat - * Signature: (JJJ)F - */ -JNIEXPORT jfloat JNICALL Java_io_realm_internal_TableView_nativeGetFloat - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetDouble - * Signature: (JJJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeGetDouble - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetTimestamp - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetTimestamp - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetString - * Signature: (JJJ)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_TableView_nativeGetString - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetByteArray - * Signature: (JJJ)[B - */ -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_TableView_nativeGetByteArray - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeGetLink - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetLink - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSetLong - * Signature: (JJJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetLong - (JNIEnv *, jobject, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSetBoolean - * Signature: (JJJZ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetBoolean - (JNIEnv *, jobject, jlong, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSetFloat - * Signature: (JJJF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetFloat - (JNIEnv *, jobject, jlong, jlong, jlong, jfloat); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSetDouble - * Signature: (JJJD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetDouble - (JNIEnv *, jobject, jlong, jlong, jlong, jdouble); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSetTimestampValue - * Signature: (JJJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetTimestampValue - (JNIEnv *, jobject, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSetString - * Signature: (JJJLjava/lang/String;)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetString - (JNIEnv *, jobject, jlong, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSetByteArray - * Signature: (JJJ[B)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetByteArray - (JNIEnv *, jobject, jlong, jlong, jlong, jbyteArray); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSetLink - * Signature: (JJJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetLink - (JNIEnv *, jobject, jlong, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeIsNullLink - * Signature: (JJJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_TableView_nativeIsNullLink - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeNullifyLink - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeNullifyLink - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeClear - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeClear - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeRemoveRow - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeRemoveRow - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindFirstInt - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstInt - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindFirstBool - * Signature: (JJZ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstBool - (JNIEnv *, jobject, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindFirstFloat - * Signature: (JJF)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstFloat - (JNIEnv *, jobject, jlong, jlong, jfloat); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindFirstDouble - * Signature: (JJD)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstDouble - (JNIEnv *, jobject, jlong, jlong, jdouble); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindFirstDate - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstDate - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindFirstString - * Signature: (JJLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstString - (JNIEnv *, jobject, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindAllInt - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllInt - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindAllBool - * Signature: (JJZ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllBool - (JNIEnv *, jobject, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindAllFloat - * Signature: (JJF)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllFloat - (JNIEnv *, jobject, jlong, jlong, jfloat); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindAllDouble - * Signature: (JJD)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllDouble - (JNIEnv *, jobject, jlong, jlong, jdouble); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindAllDate - * Signature: (JJJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllDate - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindBySourceNdx - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindBySourceNdx - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSumInt - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSumInt - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeFindAllString - * Signature: (JJLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllString - (JNIEnv *, jobject, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_TableView - * Method: nativeMaximumInt - * Signature: (JJ)Ljava/lang/Long; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMaximumInt - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeMinimumInt - * Signature: (JJ)Ljava/lang/Long; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMinimumInt - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeAverageInt - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeAverageInt - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSumFloat - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeSumFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeMaximumFloat - * Signature: (JJ)Ljava/lang/Float; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMaximumFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeMinimumFloat - * Signature: (JJ)Ljava/lang/Float; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMinimumFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeAverageFloat - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeAverageFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSumDouble - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeSumDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeMaximumDouble - * Signature: (JJ)Ljava/lang/Double; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMaximumDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeMinimumDouble - * Signature: (JJ)Ljava/lang/Double; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMinimumDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeAverageDouble - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeAverageDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeMaximumTimestamp - * Signature: (JJ)Ljava/lang/Long; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMaximumTimestamp - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeMinimumTimestamp - * Signature: (JJ)Ljava/lang/Long; - */ -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMinimumTimestamp - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSort - * Signature: (JJZ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSort - (JNIEnv *, jobject, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSortMulti - * Signature: (J[J[Z)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSortMulti - (JNIEnv *, jobject, jlong, jlongArray, jbooleanArray); - -/* - * Class: io_realm_internal_TableView - * Method: createNativeTableView - * Signature: (Lio/realm/internal/Table;J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_createNativeTableView - (JNIEnv *, jobject, jobject, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeToJson - * Signature: (J)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_TableView_nativeToJson - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeWhere - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeWhere - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativePivot - * Signature: (JJJIJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativePivot - (JNIEnv *, jobject, jlong, jlong, jlong, jint, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeDistinct - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeDistinct - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSyncIfNeeded - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSyncIfNeeded - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_TableView - * Method: nativeDistinctMulti - * Signature: (J[J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeDistinctMulti - (JNIEnv *, jobject, jlong, jlongArray); - -/* - * Class: io_realm_internal_TableView - * Method: nativeSync - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSync - (JNIEnv *, jobject, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-jni/src/io_realm_internal_UncheckedRow.h b/realm/realm-jni/src/io_realm_internal_UncheckedRow.h deleted file mode 100644 index 267be9e485..0000000000 --- a/realm/realm-jni/src/io_realm_internal_UncheckedRow.h +++ /dev/null @@ -1,245 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_internal_UncheckedRow */ - -#ifndef _Included_io_realm_internal_UncheckedRow -#define _Included_io_realm_internal_UncheckedRow -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetColumnCount - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetColumnName - * Signature: (JJ)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnName - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetColumnIndex - * Signature: (JLjava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex - (JNIEnv *, jobject, jlong, jstring); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetColumnType - * Signature: (JJ)I - */ -JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetIndex - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetIndex - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetLong - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLong - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetBoolean - * Signature: (JJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeGetBoolean - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetFloat - * Signature: (JJ)F - */ -JNIEXPORT jfloat JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFloat - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetDouble - * Signature: (JJ)D - */ -JNIEXPORT jdouble JNICALL Java_io_realm_internal_UncheckedRow_nativeGetDouble - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetTimestamp - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetTimestamp - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetString - * Signature: (JJ)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetString - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeIsNullLink - * Signature: (JJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNullLink - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetByteArray - * Signature: (JJ)[B - */ -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteArray - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetLinkView - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLinkView - (JNIEnv *, jclass, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeSetLong - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLong - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeSetBoolean - * Signature: (JJZ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetBoolean - (JNIEnv *, jobject, jlong, jlong, jboolean); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeSetFloat - * Signature: (JJF)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetFloat - (JNIEnv *, jobject, jlong, jlong, jfloat); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeGetLink - * Signature: (JJ)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLink - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeSetDouble - * Signature: (JJD)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDouble - (JNIEnv *, jobject, jlong, jlong, jdouble); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeSetTimestamp - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetTimestamp - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeSetString - * Signature: (JJLjava/lang/String;)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetString - (JNIEnv *, jobject, jlong, jlong, jstring); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeSetByteArray - * Signature: (JJ[B)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray - (JNIEnv *, jobject, jlong, jlong, jbyteArray); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeSetLink - * Signature: (JJJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLink - (JNIEnv *, jobject, jlong, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeNullifyLink - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeClose - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeClose - (JNIEnv *, jclass, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeIsAttached - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsAttached - (JNIEnv *, jobject, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeHasColumn - * Signature: (JLjava/lang/String;)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeHasColumn - (JNIEnv *, jobject, jlong, jstring); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeIsNull - * Signature: (JJ)Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull - (JNIEnv *, jobject, jlong, jlong); - -/* - * Class: io_realm_internal_UncheckedRow - * Method: nativeSetNull - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetNull - (JNIEnv *, jobject, jlong, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-jni/src/io_realm_internal_Util.h b/realm/realm-jni/src/io_realm_internal_Util.h deleted file mode 100644 index 8a41d65d19..0000000000 --- a/realm/realm-jni/src/io_realm_internal_Util.h +++ /dev/null @@ -1,45 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_internal_Util */ - -#ifndef _Included_io_realm_internal_Util -#define _Included_io_realm_internal_Util -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: io_realm_internal_Util - * Method: nativeGetMemUsage - * Signature: ()J - */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Util_nativeGetMemUsage - (JNIEnv *, jclass); - -/* - * Class: io_realm_internal_Util - * Method: nativeSetDebugLevel - * Signature: (I)V - */ -JNIEXPORT void JNICALL Java_io_realm_internal_Util_nativeSetDebugLevel - (JNIEnv *, jclass, jint); - -/* - * Class: io_realm_internal_Util - * Method: nativeGetTablePrefix - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeGetTablePrefix - (JNIEnv *, jclass); - -/* - * Class: io_realm_internal_Util - * Method: nativeTestcase - * Signature: (IZJ)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeTestcase - (JNIEnv *, jclass, jint, jboolean, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index d3127e906f..502601c1d3 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -1,3 +1,5 @@ +import java.security.MessageDigest + apply plugin: 'com.android.library' apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'com.github.dcendents.android-maven' @@ -8,6 +10,24 @@ apply plugin: 'findbugs' apply plugin: 'pmd' apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' +apply plugin: 'de.undercouch.download' + +ext.coreVersion = '1.5.1' +// empty or comment out this to disable hash checking +ext.coreSha256Hash = 'a034d3250c820a15126721142d168a2ac4a12223b75bb324958ca2a70442720d' +ext.forceDownloadCore = + project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false +// Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from +// core source code. +ext.coreSourcePath = project.hasProperty('coreSourcePath') ? project.getProperty('coreSourcePath') : null +// The location of core archive. +ext.coreArchiveDir = System.getenv("REALM_CORE_DOWNLOAD_DIR") +if (!ext.coreArchiveDir) { + ext.coreArchiveDir = ".." +} +ext.coreArchiveFile = rootProject.file("${ext.coreArchiveDir}/core-android-${project.coreVersion}.tar.gz") +ext.coreDistributionDir = file("${projectDir}/distribution/realm-core/") +ext.coreDir = file("${project.coreDistributionDir.getAbsolutePath()}/core-${project.coreVersion}") android { compileSdkVersion 24 @@ -19,6 +39,24 @@ android { project.archivesBaseName = "realm-android-library" consumerProguardFiles 'proguard-rules.pro' testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + arguments "-DREALM_CORE_DIST_DIR:STRING=${project.coreDir.getAbsolutePath()}", + // FIXME: + // This is copied from https://dl.google.com/android/repository/cmake-3.4.2909474-linux-x86_64.zip + // because of the android.toolchain.cmake shipped with Android SDK CMake 3.6 doesn't work with our + // JNI build currently (lack of lto linking support). + // This file should be removed and use the one from Android SDK cmake package when it supports lto. + "-DCMAKE_TOOLCHAIN_FILE=${project.file('src/main/cpp/android.toolchain.cmake').path}" + abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips' + } + } + } + + externalNativeBuild { + cmake { + path 'src/main/cpp/CMakeLists.txt' + } } buildTypes { @@ -109,10 +147,8 @@ task javadocJar(type: Jar, dependsOn: javadoc) { from javadoc.destinationDir } -preBuild.dependsOn ':realm-jni:buildAndroidJni' - task findbugs(type: FindBugs) { - dependsOn assembleDebug + dependsOn assemble group = 'Verification' ignoreFailures = false @@ -259,3 +295,137 @@ artifacts { archives javadocJar archives sourcesJar } + + +def coreDownloaded = false + +task downloadCore(group: 'build setup', description: 'Download the latest version of Realm Core') { + def isHashCheckingEnabled = { + return project.hasProperty('coreSha256Hash') && !project.coreSha256Hash.empty + } + + def calcSha256Hash = {File targetFile -> + MessageDigest sha = MessageDigest.getInstance("SHA-256"); + Formatter hexHash = new Formatter() + sha.digest(targetFile.bytes).each { b -> hexHash.format('%02x', b) } + return hexHash.toString() + } + + def shouldDownloadCore = { + if (!project.coreArchiveFile.exists()) { + return true + } + if (project.forceDownloadCore) { + return true; + } + if (!isHashCheckingEnabled()) { + println "Skipping hash check(empty \'coreSha256Hash\')." + return false + } + + def calculatedHash = calcSha256Hash(project.coreArchiveFile) + if (project.coreSha256Hash.equalsIgnoreCase(calculatedHash)) { + return false + } + + println "Existing archive hash mismatch (Expected: ${project.coreSha256Hash.toLowerCase()}" + + " but got ${calculatedHash.toLowerCase()}). Download new version." + return true + } + + doLast { + if (shouldDownloadCore()) { + download { + src "http://static.realm.io/downloads/core/realm-core-android-${project.coreVersion}.tar.gz" + dest project.coreArchiveFile + onlyIfNewer false + } + coreDownloaded = true + + if (isHashCheckingEnabled()) { + def calculatedHash = calcSha256Hash(project.coreArchiveFile) + if (!project.coreSha256Hash.equalsIgnoreCase(calculatedHash)) { + throw new GradleException("Invalid checksum for file '" + + "${project.coreArchiveFile.getName()}'. Expected " + + "${project.coreSha256Hash.toLowerCase()} but got " + + "${calculatedHash.toLowerCase()}."); + } + } else { + println 'Skipping hash check (empty \'coreSha256Hash\').' + } + } + } +} + +task compileCore(group: 'build setup', description: 'Compile the core library from source code') { + // Build the library from core source code + doFirst { + if (!coreSourcePath) { + throw new GradleException('The coreSourcePath is not set.') + } + exec { + workingDir = coreSourcePath + commandLine = [ + "bash", + "build.sh", + "build-android" + ] + } + } + + // Copy the core tar ball + doLast { + copy { + from "${coreSourcePath}/realm-core-android-${coreVersion}.tar.gz" + into project.coreArchiveFile.parent + rename "realm-core-android-${coreVersion}.tar.gz", "core-android-${coreVersion}.tar.gz" + } + } +} + +task deployCore(group: 'build setup', description: 'Deploy the latest version of Realm Core') { + dependsOn { + coreSourcePath ? compileCore : downloadCore + } + + outputs.upToDateWhen { + // Clean up the coreDir if it is newly downloaded or compiled from source + if (coreDownloaded || coreSourcePath) { + return false + } + + return project.coreDir.exists() + } + + doLast { + // Delete all files to avoid multiple copies of the same header file in Android Studio. + exec { + commandLine = [ + 'rm', + '-rf', + project.coreDistributionDir.getAbsolutePath() + ] + } + copy { + from tarTree(project.coreArchiveFile) + into project.coreDir + } + } +} + +preBuild.dependsOn deployCore + +if (project.hasProperty('dontCleanJniFiles')) { + project.afterEvaluate { + tasks.all { task -> + if (task.name.startsWith('externalNativeBuildClean')) { + task.enabled = false + } + } + } +} else { + task cleanExternalBuildFiles(type: Delete) { + delete project.file('.externalNativeBuild') + } + clean.dependsOn cleanExternalBuildFiles +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000..23ee4bf2f9 --- /dev/null +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -0,0 +1,109 @@ +cmake_minimum_required(VERSION 3.6.0) + +# find javah +find_package(Java COMPONENTS Development) +if (NOT Java_Development_FOUND) + if (DEFINED ENV{JAVA_HOME} AND EXISTS "$ENV{JAVA_HOME}/bin/javah") + set(Java_JAVAH_EXECUTABLE "$ENV{JAVA_HOME}/bin/javah") + elseif (EXISTS "/usr/bin/javah") + set(Java_JAVAH_EXECUTABLE "/usr/bin/javah") + else() + message(FATAL_ERROR "Cannot find javah") + endif() +endif() +include (UseJava) + +set(CMAKE_VERBOSE_MAKEFILE ON) +# Generate compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if (CMAKE_BUILD_TYPE STREQUAL "Release") + set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/classes/release/) +else() + set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/classes/debug/) +endif() + +create_javah(TARGET jni_headers + CLASSES io.realm.internal.Table io.realm.internal.TableView + io.realm.internal.CheckedRow io.realm.internal.SharedGroup io.realm.internal.Group + io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow + io.realm.internal.TableQuery + + CLASSPATH ${classes_PATH} + OUTPUT_DIR ${CMAKE_SOURCE_DIR}/jni_include + DEPENDS ${classes_PATH} +) + +# TODO: Ideally the debug build should link with core's debug build. But core dbg lib has +# some compile options problems with arm, especially with macro REALM_DEBUG. Link to core +# dbg for debug build when that gets solved. +# We always link to the non-dbg version of core libs for now. +# This means only JNI part has debugging symbols with debug build. +# Debugging with core source code will also be done though anther approach -- compiling the core +# with cmake inside android project. +# Configure import realm core lib +set(core_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-android-${ANDROID_ABI}.a) +# Workaround for old core's funny ABI nicknames +if (NOT EXISTS ${core_lib_PATH}) + if (ARMEABI) + set(core_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-android-arm.a) + elseif (ARMEABI_V7A) + set(core_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-android-arm-v7a.a) + elseif (ARM64_V8A) + set(core_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-android-arm64.a) + else() + message(FATAL_ERROR "Cannot find core lib file: ${core_lib_PATH}") + endif() +endif() + +add_library(lib_realm_core STATIC IMPORTED) +set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_PATH}) + +# build application's shared lib +include_directories(${REALM_CORE_DIST_DIR}/include + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/jni_include) + +# Set compile flags +set(ANDROID_STL "gnustl_static") + +if (ARMEABI) + set(ABI_CXX_FLAGS "-mthumb") +elseif (ARMEABI_V7A) + set(ABI_CXX_FLAGS "-mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16") +endif() + +#FIXME uninitialized is reported by query_expression.hpp:1070 +# d.init(ValueBase::m_from_link_list, ValueBase::m_values, D{}); +#FIXME maybe-uninitialized is reported by table_view.cpp:272:15: +# 'best.m_nanoseconds' was declared here +set(WARNING_CXX_FLAGS "-Werror -Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros \ +-Wno-missing-field-initializers -Wmissing-declarations -Wno-error=uninitialized -Wno-error=maybe-uninitialized") +set(REALM_COMMON_CXX_FLAGS "-DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14") +set(CMAKE_CXX_FLAGS_RELEASE "-Os -DNDEBUG -flto") +#-ggdb doesn't play well with -flto +set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -Os -DNDEBUG") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${REALM_COMMON_CXX_FLAGS} ${WARNING_CXX_FLAGS} ${ABI_CXX_FLAGS}") + +# Set link flags +set(REALM_LINKER_FLAGS "") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${REALM_LINKER_FLAGS}") + +file(GLOB jni_SRC + "*.cpp" +) +add_library(realm-jni SHARED ${jni_SRC}) +add_dependencies(realm-jni jni_headers) +# -latomic is not set by default for mips. See https://code.google.com/p/android/issues/detail?id=182094 +target_link_libraries(realm-jni log android atomic lib_realm_core) + +# Strip the release so files and backup the unstripped versions +if (CMAKE_BUILD_TYPE STREQUAL "Release") + set(unstripped_SO_DIR "${CMAKE_SOURCE_DIR}/../../../build/outputs/jniLibs-unstripped/${ANDROID_ABI}") + add_custom_command(TARGET realm-jni + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${unstripped_SO_DIR} + COMMAND ${CMAKE_COMMAND} -E copy $ ${unstripped_SO_DIR} + COMMAND ${CMAKE_STRIP} $) +endif() + diff --git a/realm/realm-library/src/main/cpp/android.toolchain.cmake b/realm/realm-library/src/main/cpp/android.toolchain.cmake new file mode 100644 index 0000000000..86046dfa45 --- /dev/null +++ b/realm/realm-library/src/main/cpp/android.toolchain.cmake @@ -0,0 +1,1703 @@ +# Copyright (c) 2010-2011, Ethan Rublee +# Copyright (c) 2011-2014, Andrey Kamaev +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# ------------------------------------------------------------------------------ +# Android CMake toolchain file, for use with the Android NDK r5-r10d +# Requires cmake 2.6.3 or newer (2.8.9 or newer is recommended). +# See home page: https://github.com/taka-no-me/android-cmake +# +# Usage Linux: +# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk +# $ mkdir build && cd build +# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake .. +# $ make -j8 +# +# Usage Windows: +# You need native port of make to build your project. +# Android NDK r7 (and newer) already has make.exe on board. +# For older NDK you have to install it separately. +# For example, this one: http://gnuwin32.sourceforge.net/packages/make.htm +# +# $ SET ANDROID_NDK=C:\absolute\path\to\the\android-ndk +# $ mkdir build && cd build +# $ cmake.exe -G"MinGW Makefiles" +# -DCMAKE_TOOLCHAIN_FILE=path\to\the\android.toolchain.cmake +# -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%\prebuilt\windows\bin\make.exe" .. +# $ cmake.exe --build . +# +# +# Options (can be set as cmake parameters: -D=): +# ANDROID_NDK=/opt/android-ndk - path to the NDK root. +# Can be set as environment variable. Can be set only at first cmake run. +# +# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary +# Interface (ABI). This option nearly matches to the APP_ABI variable +# used by ndk-build tool from Android NDK. +# +# Possible targets are: +# "armeabi" - ARMv5TE based CPU with software floating point operations +# "armeabi-v7a" - ARMv7 based devices with hardware FPU instructions +# this ABI target is used by default +# "armeabi-v7a with NEON" - same as armeabi-v7a, but +# sets NEON as floating-point unit +# "armeabi-v7a with VFPV3" - same as armeabi-v7a, but +# sets VFPV3 as floating-point unit (has 32 registers instead of 16) +# "armeabi-v6 with VFP" - tuned for ARMv6 processors having VFP +# "x86" - IA-32 instruction set +# "mips" - MIPS32 instruction set +# +# 64-bit ABIs for NDK r10 and newer: +# "arm64-v8a" - ARMv8 AArch64 instruction set +# "x86_64" - Intel64 instruction set (r1) +# "mips64" - MIPS64 instruction set (r6) +# +# ANDROID_NATIVE_API_LEVEL=android-9 - level of Android API compile for. +# Option is read-only when standalone toolchain is used. +# Note: building for "android-L" requires explicit configuration. +# +# ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.9 - the name of compiler +# toolchain to be used. The list of possible values depends on the NDK +# version. For NDK r10c the possible values are: +# +# * aarch64-linux-android-4.9 +# * aarch64-linux-android-clang3.4 +# * aarch64-linux-android-clang3.5 +# * arm-linux-androideabi-4.6 +# * arm-linux-androideabi-4.8 +# * arm-linux-androideabi-4.9 (default) +# * arm-linux-androideabi-clang3.4 +# * arm-linux-androideabi-clang3.5 +# * mips64el-linux-android-4.9 +# * mips64el-linux-android-clang3.4 +# * mips64el-linux-android-clang3.5 +# * mipsel-linux-android-4.6 +# * mipsel-linux-android-4.8 +# * mipsel-linux-android-4.9 +# * mipsel-linux-android-clang3.4 +# * mipsel-linux-android-clang3.5 +# * x86-4.6 +# * x86-4.8 +# * x86-4.9 +# * x86-clang3.4 +# * x86-clang3.5 +# * x86_64-4.9 +# * x86_64-clang3.4 +# * x86_64-clang3.5 +# +# ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions +# instead of Thumb. Is not available for "armeabi-v6 with VFP" +# (is forced to be ON) ABI. +# +# ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker +# errors even if they are not used. +# +# ANDROID_SO_UNDEFINED=OFF - set ON to allow undefined symbols in shared +# libraries. Automatically turned for NDK r5x and r6x due to GLESv2 +# problems. +# +# ANDROID_STL=gnustl_static - specify the runtime to use. +# +# Possible values are: +# none -> Do not configure the runtime. +# system -> Use the default minimal system C++ runtime library. +# Implies -fno-rtti -fno-exceptions. +# Is not available for standalone toolchain. +# system_re -> Use the default minimal system C++ runtime library. +# Implies -frtti -fexceptions. +# Is not available for standalone toolchain. +# gabi++_static -> Use the GAbi++ runtime as a static library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7 and newer. +# Is not available for standalone toolchain. +# gabi++_shared -> Use the GAbi++ runtime as a shared library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7 and newer. +# Is not available for standalone toolchain. +# stlport_static -> Use the STLport runtime as a static library. +# Implies -fno-rtti -fno-exceptions for NDK before r7. +# Implies -frtti -fno-exceptions for NDK r7 and newer. +# Is not available for standalone toolchain. +# stlport_shared -> Use the STLport runtime as a shared library. +# Implies -fno-rtti -fno-exceptions for NDK before r7. +# Implies -frtti -fno-exceptions for NDK r7 and newer. +# Is not available for standalone toolchain. +# gnustl_static -> Use the GNU STL as a static library. +# Implies -frtti -fexceptions. +# gnustl_shared -> Use the GNU STL as a shared library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7b and newer. +# Silently degrades to gnustl_static if not available. +# +# ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on +# chosen runtime. If disabled, then the user is responsible for settings +# these options. +# +# What?: +# android-cmake toolchain searches for NDK/toolchain in the following order: +# ANDROID_NDK - cmake parameter +# ANDROID_NDK - environment variable +# ANDROID_STANDALONE_TOOLCHAIN - cmake parameter +# ANDROID_STANDALONE_TOOLCHAIN - environment variable +# ANDROID_NDK - default locations +# ANDROID_STANDALONE_TOOLCHAIN - default locations +# +# Make sure to do the following in your scripts: +# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}" ) +# SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}" ) +# The flags will be prepopulated with critical flags, so don't loose them. +# Also be aware that toolchain also sets configuration-specific compiler +# flags and linker flags. +# +# ANDROID and BUILD_ANDROID will be set to true, you may test any of these +# variables to make necessary Android-specific configuration changes. +# +# Also ARMEABI or ARMEABI_V7A or X86 or MIPS or ARM64_V8A or X86_64 or MIPS64 +# will be set true, mutually exclusive. NEON option will be set true +# if VFP is set to NEON. +# +# ------------------------------------------------------------------------------ + +# FIXME: +# This is copied from https://dl.google.com/android/repository/cmake-3.4.2909474-linux-x86_64.zip +# because of the android.toolchain.cmake shipped with Android SDK CMake 3.6 doesn't work with our +# JNI build currently (lack of lto linking support.). Two modifications are made to avoid warnings +# with CMake 3.6 -- disable CMAKE_FORCE_CXX_COMPILER & CMAKE_FORCE_C_COMPILER. +# This file should be removed and use the one from Android SDK cmake package when it supports lto. + +cmake_minimum_required( VERSION 2.6.3 ) + +if( DEFINED CMAKE_CROSSCOMPILING ) + # subsequent toolchain loading is not really needed + return() +endif() + +if( CMAKE_TOOLCHAIN_FILE ) + # touch toolchain variable to suppress "unused variable" warning +endif() + +# inherit settings in recursive loads +get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE ) +if( _CMAKE_IN_TRY_COMPILE ) + include( "${CMAKE_CURRENT_SOURCE_DIR}/../android.toolchain.config.cmake" OPTIONAL ) +endif() + +# this one is important +if( CMAKE_VERSION VERSION_GREATER "3.0.99" ) + set( CMAKE_SYSTEM_NAME Android ) +else() + set( CMAKE_SYSTEM_NAME Linux ) +endif() + +# this one not so much +set( CMAKE_SYSTEM_VERSION 1 ) + +# rpath makes low sense for Android +set( CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "" ) +set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." ) + +# NDK search paths +set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10d -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" ) +if( NOT DEFINED ANDROID_NDK_SEARCH_PATHS ) + if( CMAKE_HOST_WIN32 ) + file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS ) + set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}" "$ENV{SystemDrive}/NVPACK" ) + else() + file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS ) + set( ANDROID_NDK_SEARCH_PATHS /opt "${ANDROID_NDK_SEARCH_PATHS}/NVPACK" ) + endif() +endif() +if( NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) + set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain ) +endif() + +# known ABIs +set( ANDROID_SUPPORTED_ABIS_arm "armeabi-v7a;armeabi;armeabi-v7a with NEON;armeabi-v7a with VFPV3;armeabi-v6 with VFP" ) +set( ANDROID_SUPPORTED_ABIS_arm64 "arm64-v8a" ) +set( ANDROID_SUPPORTED_ABIS_x86 "x86" ) +set( ANDROID_SUPPORTED_ABIS_x86_64 "x86_64" ) +set( ANDROID_SUPPORTED_ABIS_mips "mips" ) +set( ANDROID_SUPPORTED_ABIS_mips64 "mips64" ) + +# API level defaults +set( ANDROID_DEFAULT_NDK_API_LEVEL 9 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_arm64 21 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_x86 9 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_x86_64 21 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_mips 9 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_mips64 21 ) + + +macro( __LIST_FILTER listvar regex ) + if( ${listvar} ) + foreach( __val ${${listvar}} ) + if( __val MATCHES "${regex}" ) + list( REMOVE_ITEM ${listvar} "${__val}" ) + endif() + endforeach() + endif() +endmacro() + +macro( __INIT_VARIABLE var_name ) + set( __test_path 0 ) + foreach( __var ${ARGN} ) + if( __var STREQUAL "PATH" ) + set( __test_path 1 ) + break() + endif() + endforeach() + + if( __test_path AND NOT EXISTS "${${var_name}}" ) + unset( ${var_name} CACHE ) + endif() + + if( " ${${var_name}}" STREQUAL " " ) + set( __values 0 ) + foreach( __var ${ARGN} ) + if( __var STREQUAL "VALUES" ) + set( __values 1 ) + elseif( NOT __var STREQUAL "PATH" ) + if( __var MATCHES "^ENV_.*$" ) + string( REPLACE "ENV_" "" __var "${__var}" ) + set( __value "$ENV{${__var}}" ) + elseif( DEFINED ${__var} ) + set( __value "${${__var}}" ) + elseif( __values ) + set( __value "${__var}" ) + else() + set( __value "" ) + endif() + + if( NOT " ${__value}" STREQUAL " " AND (NOT __test_path OR EXISTS "${__value}") ) + set( ${var_name} "${__value}" ) + break() + endif() + endif() + endforeach() + unset( __value ) + unset( __values ) + endif() + + if( __test_path ) + file( TO_CMAKE_PATH "${${var_name}}" ${var_name} ) + endif() + unset( __test_path ) +endmacro() + +macro( __DETECT_NATIVE_API_LEVEL _var _path ) + set( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" ) + file( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" ) + if( NOT __apiFileContent ) + message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." ) + endif() + string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" ) + unset( __apiFileContent ) + unset( __ndkApiLevelRegex ) +endmacro() + +macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root ) + if( EXISTS "${_root}" ) + file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" ) + __LIST_FILTER( __gccExePath "^[.].*" ) + list( LENGTH __gccExePath __gccExePathsCount ) + if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "Could not determine machine name for compiler from ${_root}" ) + set( ${_var} "" ) + else() + get_filename_component( __gccExeName "${__gccExePath}" NAME_WE ) + string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" ) + endif() + unset( __gccExePath ) + unset( __gccExePathsCount ) + unset( __gccExeName ) + else() + set( ${_var} "" ) + endif() +endmacro() + + +# fight against cygwin +set( ANDROID_FORBID_SYGWIN TRUE CACHE BOOL "Prevent cmake from working under cygwin and using cygwin tools") +mark_as_advanced( ANDROID_FORBID_SYGWIN ) +if( ANDROID_FORBID_SYGWIN ) + if( CYGWIN ) + message( FATAL_ERROR "Android NDK and android-cmake toolchain are not welcome Cygwin. It is unlikely that this cmake toolchain will work under cygwin. But if you want to try then you can set cmake variable ANDROID_FORBID_SYGWIN to FALSE and rerun cmake." ) + endif() + + if( CMAKE_HOST_WIN32 ) + # remove cygwin from PATH + set( __new_path "$ENV{PATH}") + __LIST_FILTER( __new_path "cygwin" ) + set(ENV{PATH} "${__new_path}") + unset(__new_path) + endif() +endif() + + +# detect current host platform +if( NOT DEFINED ANDROID_NDK_HOST_X64 AND (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64" OR CMAKE_HOST_APPLE) ) + set( ANDROID_NDK_HOST_X64 1 CACHE BOOL "Try to use 64-bit compiler toolchain" ) + mark_as_advanced( ANDROID_NDK_HOST_X64 ) +endif() + +set( TOOL_OS_SUFFIX "" ) +if( CMAKE_HOST_APPLE ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "darwin-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "darwin-x86" ) +elseif( CMAKE_HOST_WIN32 ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "windows-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "windows" ) + set( TOOL_OS_SUFFIX ".exe" ) +elseif( CMAKE_HOST_UNIX ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "linux-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "linux-x86" ) +else() + message( FATAL_ERROR "Cross-compilation on your platform is not supported by this cmake toolchain" ) +endif() + +if( NOT ANDROID_NDK_HOST_X64 ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) +endif() + +# see if we have path to Android NDK +if( NOT ANDROID_NDK AND NOT ANDROID_STANDALONE_TOOLCHAIN ) + __INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK ) +endif() +if( NOT ANDROID_NDK ) + # see if we have path to Android standalone toolchain + __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN ) + + if( NOT ANDROID_STANDALONE_TOOLCHAIN ) + #try to find Android NDK in one of the the default locations + set( __ndkSearchPaths ) + foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} ) + foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} ) + list( APPEND __ndkSearchPaths "${__ndkSearchPath}/android-ndk${suffix}" ) + endforeach() + endforeach() + __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} ) + unset( __ndkSearchPaths ) + + if( ANDROID_NDK ) + message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" ) + message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" ) + else() + #try to find Android standalone toolchain in one of the the default locations + __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) + + if( ANDROID_STANDALONE_TOOLCHAIN ) + message( STATUS "Using default path for standalone toolchain ${ANDROID_STANDALONE_TOOLCHAIN}" ) + message( STATUS " If you prefer to use a different location, please define the variable: ANDROID_STANDALONE_TOOLCHAIN" ) + endif( ANDROID_STANDALONE_TOOLCHAIN ) + endif( ANDROID_NDK ) + endif( NOT ANDROID_STANDALONE_TOOLCHAIN ) +endif( NOT ANDROID_NDK ) + +# remember found paths +if( ANDROID_NDK ) + get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE ) + set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE ) + set( BUILD_WITH_ANDROID_NDK True ) + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT" ) + file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX "r[0-9]+[a-z]?" ) + string( REGEX MATCH "r([0-9]+)([a-z]?)" ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) + else() + set( ANDROID_NDK_RELEASE "r1x" ) + set( ANDROID_NDK_RELEASE_FULL "unreleased" ) + endif() + string( REGEX REPLACE "r([0-9]+)([a-z]?)" "\\1*1000" ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE}" ) + string( FIND " abcdefghijklmnopqastuvwxyz" "${CMAKE_MATCH_2}" __ndkReleaseLetterNum ) + math( EXPR ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE_NUM}+${__ndkReleaseLetterNum}" ) +elseif( ANDROID_STANDALONE_TOOLCHAIN ) + get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE ) + # try to detect change + if( CMAKE_AR ) + string( LENGTH "${ANDROID_STANDALONE_TOOLCHAIN}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidStandaloneToolchainPreviousPath ) + if( NOT __androidStandaloneToolchainPreviousPath STREQUAL ANDROID_STANDALONE_TOOLCHAIN ) + message( FATAL_ERROR "It is not possible to change path to the Android standalone toolchain on subsequent run." ) + endif() + unset( __androidStandaloneToolchainPreviousPath ) + unset( __length ) + endif() + set( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" CACHE INTERNAL "Path of the Android standalone toolchain" FORCE ) + set( BUILD_WITH_STANDALONE_TOOLCHAIN True ) +else() + list(GET ANDROID_NDK_SEARCH_PATHS 0 ANDROID_NDK_SEARCH_PATH) + message( FATAL_ERROR "Could not find neither Android NDK nor Android standalone toolchain. + You should either set an environment variable: + export ANDROID_NDK=~/my-android-ndk + or + export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain + or put the toolchain or NDK in the default path: + sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}/android-ndk + sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" ) +endif() + +# android NDK layout +if( BUILD_WITH_ANDROID_NDK ) + if( NOT DEFINED ANDROID_NDK_LAYOUT ) + # try to automatically detect the layout + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT") + set( ANDROID_NDK_LAYOUT "RELEASE" ) + elseif( EXISTS "${ANDROID_NDK}/../../linux-x86/toolchain/" ) + set( ANDROID_NDK_LAYOUT "LINARO" ) + elseif( EXISTS "${ANDROID_NDK}/../../gcc/" ) + set( ANDROID_NDK_LAYOUT "ANDROID" ) + endif() + endif() + set( ANDROID_NDK_LAYOUT "${ANDROID_NDK_LAYOUT}" CACHE STRING "The inner layout of NDK" ) + mark_as_advanced( ANDROID_NDK_LAYOUT ) + if( ANDROID_NDK_LAYOUT STREQUAL "LINARO" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../${ANDROID_NDK_HOST_SYSTEM_NAME}/toolchain" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + elseif( ANDROID_NDK_LAYOUT STREQUAL "ANDROID" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../gcc/${ANDROID_NDK_HOST_SYSTEM_NAME}/arm" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + else() # ANDROID_NDK_LAYOUT STREQUAL "RELEASE" + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/toolchains" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME2}" ) + endif() + get_filename_component( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK_TOOLCHAINS_PATH}" ABSOLUTE ) + + # try to detect change of NDK + if( CMAKE_AR ) + string( LENGTH "${ANDROID_NDK_TOOLCHAINS_PATH}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) + if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK_TOOLCHAINS_PATH ) + message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. + " ) + endif() + unset( __androidNdkPreviousPath ) + unset( __length ) + endif() +endif() + + +# get all the details about standalone toolchain +if( BUILD_WITH_STANDALONE_TOOLCHAIN ) + __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" ) + set( ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + set( __availableToolchains "standalone" ) + __DETECT_TOOLCHAIN_MACHINE_NAME( __availableToolchainMachines "${ANDROID_STANDALONE_TOOLCHAIN}" ) + if( NOT __availableToolchainMachines ) + message( FATAL_ERROR "Could not determine machine name of your toolchain. Probably your Android standalone toolchain is broken." ) + endif() + if( __availableToolchainMachines MATCHES x86_64 ) + set( __availableToolchainArchs "x86_64" ) + elseif( __availableToolchainMachines MATCHES i686 ) + set( __availableToolchainArchs "x86" ) + elseif( __availableToolchainMachines MATCHES aarch64 ) + set( __availableToolchainArchs "arm64" ) + elseif( __availableToolchainMachines MATCHES arm ) + set( __availableToolchainArchs "arm" ) + elseif( __availableToolchainMachines MATCHES mips64el ) + set( __availableToolchainArchs "mips64" ) + elseif( __availableToolchainMachines MATCHES mipsel ) + set( __availableToolchainArchs "mips" ) + endif() + execute_process( COMMAND "${ANDROID_STANDALONE_TOOLCHAIN}/bin/${__availableToolchainMachines}-gcc${TOOL_OS_SUFFIX}" -dumpversion + OUTPUT_VARIABLE __availableToolchainCompilerVersions OUTPUT_STRIP_TRAILING_WHITESPACE ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?" __availableToolchainCompilerVersions "${__availableToolchainCompilerVersions}" ) + if( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/bin/clang${TOOL_OS_SUFFIX}" ) + list( APPEND __availableToolchains "standalone-clang" ) + list( APPEND __availableToolchainMachines ${__availableToolchainMachines} ) + list( APPEND __availableToolchainArchs ${__availableToolchainArchs} ) + list( APPEND __availableToolchainCompilerVersions ${__availableToolchainCompilerVersions} ) + endif() +endif() + +macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __toolchain_subpath ) + foreach( __toolchain ${${__availableToolchainsLst}} ) + if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}${__toolchain_subpath}" ) + SET( __toolchainVersionRegex "^TOOLCHAIN_VERSION[\t ]+:=[\t ]+(.*)$" ) + FILE( STRINGS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}/setup.mk" __toolchainVersionStr REGEX "${__toolchainVersionRegex}" ) + if( __toolchainVersionStr ) + string( REGEX REPLACE "${__toolchainVersionRegex}" "\\1" __toolchainVersionStr "${__toolchainVersionStr}" ) + string( REGEX REPLACE "-clang3[.][0-9]$" "-${__toolchainVersionStr}" __gcc_toolchain "${__toolchain}" ) + else() + string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" ) + endif() + unset( __toolchainVersionStr ) + unset( __toolchainVersionRegex ) + else() + set( __gcc_toolchain "${__toolchain}" ) + endif() + __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK_TOOLCHAINS_PATH}/${__gcc_toolchain}${__toolchain_subpath}" ) + if( __machine ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9x]+)?$" __version "${__gcc_toolchain}" ) + if( __machine MATCHES x86_64 ) + set( __arch "x86_64" ) + elseif( __machine MATCHES i686 ) + set( __arch "x86" ) + elseif( __machine MATCHES aarch64 ) + set( __arch "arm64" ) + elseif( __machine MATCHES arm ) + set( __arch "arm" ) + elseif( __machine MATCHES mips64el ) + set( __arch "mips64" ) + elseif( __machine MATCHES mipsel ) + set( __arch "mips" ) + else() + set( __arch "" ) + endif() + #message("machine: !${__machine}!\narch: !${__arch}!\nversion: !${__version}!\ntoolchain: !${__toolchain}!\n") + if (__arch) + list( APPEND __availableToolchainMachines "${__machine}" ) + list( APPEND __availableToolchainArchs "${__arch}" ) + list( APPEND __availableToolchainCompilerVersions "${__version}" ) + list( APPEND ${__availableToolchainsVar} "${__toolchain}" ) + endif() + endif() + unset( __gcc_toolchain ) + endforeach() +endmacro() + +# get all the details about NDK +if( BUILD_WITH_ANDROID_NDK ) + file( GLOB ANDROID_SUPPORTED_NATIVE_API_LEVELS RELATIVE "${ANDROID_NDK}/platforms" "${ANDROID_NDK}/platforms/android-*" ) + string( REPLACE "android-" "" ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_SUPPORTED_NATIVE_API_LEVELS}" ) + set( __availableToolchains "" ) + set( __availableToolchainMachines "" ) + set( __availableToolchainArchs "" ) + set( __availableToolchainCompilerVersions "" ) + if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_TOOLCHAIN_NAME}/" ) + # do not go through all toolchains if we know the name + set( __availableToolchainsLst "${ANDROID_TOOLCHAIN_NAME}" ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) + if( __availableToolchains ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) + endif() + endif() + endif() + if( NOT __availableToolchains ) + file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" ) + if( __availableToolchainsLst ) + list(SORT __availableToolchainsLst) # we need clang to go after gcc + endif() + __LIST_FILTER( __availableToolchainsLst "^[.]" ) + __LIST_FILTER( __availableToolchainsLst "llvm" ) + __LIST_FILTER( __availableToolchainsLst "renderscript" ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) + if( __availableToolchains ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) + endif() + endif() + endif() + if( NOT __availableToolchains ) + message( FATAL_ERROR "Could not find any working toolchain in the NDK. Probably your Android NDK is broken." ) + endif() +endif() + +# build list of available ABIs +set( ANDROID_SUPPORTED_ABIS "" ) +set( __uniqToolchainArchNames ${__availableToolchainArchs} ) +list( REMOVE_DUPLICATES __uniqToolchainArchNames ) +list( SORT __uniqToolchainArchNames ) +foreach( __arch ${__uniqToolchainArchNames} ) + list( APPEND ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${__arch}} ) +endforeach() +unset( __uniqToolchainArchNames ) +if( NOT ANDROID_SUPPORTED_ABIS ) + message( FATAL_ERROR "No one of known Android ABIs is supported by this cmake toolchain." ) +endif() + +# choose target ABI +__INIT_VARIABLE( ANDROID_ABI VALUES ${ANDROID_SUPPORTED_ABIS} ) +# verify that target ABI is supported +list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx ) +if( __androidAbiIdx EQUAL -1 ) + string( REPLACE ";" "\", \"" PRINTABLE_ANDROID_SUPPORTED_ABIS "${ANDROID_SUPPORTED_ABIS}" ) + message( FATAL_ERROR "Specified ANDROID_ABI = \"${ANDROID_ABI}\" is not supported by this cmake toolchain or your NDK/toolchain. + Supported values are: \"${PRINTABLE_ANDROID_SUPPORTED_ABIS}\" + " ) +endif() +unset( __androidAbiIdx ) + +# set target ABI options +if( ANDROID_ABI STREQUAL "x86" ) + set( X86 true ) + set( ANDROID_NDK_ABI_NAME "x86" ) + set( ANDROID_ARCH_NAME "x86" ) + set( ANDROID_LLVM_TRIPLE "i686-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "i686" ) +elseif( ANDROID_ABI STREQUAL "x86_64" ) + set( X86 true ) + set( X86_64 true ) + set( ANDROID_NDK_ABI_NAME "x86_64" ) + set( ANDROID_ARCH_NAME "x86_64" ) + set( CMAKE_SYSTEM_PROCESSOR "x86_64" ) + set( ANDROID_LLVM_TRIPLE "x86_64-none-linux-android" ) +elseif( ANDROID_ABI STREQUAL "mips64" ) + set( MIPS64 true ) + set( ANDROID_NDK_ABI_NAME "mips64" ) + set( ANDROID_ARCH_NAME "mips64" ) + set( ANDROID_LLVM_TRIPLE "mips64el-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "mips64" ) +elseif( ANDROID_ABI STREQUAL "mips" ) + set( MIPS true ) + set( ANDROID_NDK_ABI_NAME "mips" ) + set( ANDROID_ARCH_NAME "mips" ) + set( ANDROID_LLVM_TRIPLE "mipsel-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "mips" ) +elseif( ANDROID_ABI STREQUAL "arm64-v8a" ) + set( ARM64_V8A true ) + set( ANDROID_NDK_ABI_NAME "arm64-v8a" ) + set( ANDROID_ARCH_NAME "arm64" ) + set( ANDROID_LLVM_TRIPLE "aarch64-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "aarch64" ) + set( VFPV3 true ) + set( NEON true ) +elseif( ANDROID_ABI STREQUAL "armeabi" ) + set( ARMEABI true ) + set( ANDROID_NDK_ABI_NAME "armeabi" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv5te" ) +elseif( ANDROID_ABI STREQUAL "armeabi-v6 with VFP" ) + set( ARMEABI_V6 true ) + set( ANDROID_NDK_ABI_NAME "armeabi" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv6" ) + # need always fallback to older platform + set( ARMEABI true ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a") + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a with VFPV3" ) + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) + set( VFPV3 true ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a with NEON" ) + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) + set( VFPV3 true ) + set( NEON true ) +else() + message( SEND_ERROR "Unknown ANDROID_ABI=\"${ANDROID_ABI}\" is specified." ) +endif() + +if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" ) + # really dirty hack + # it is not possible to change CMAKE_SYSTEM_PROCESSOR after the first run... + file( APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" "SET(CMAKE_SYSTEM_PROCESSOR \"${CMAKE_SYSTEM_PROCESSOR}\")\n" ) +endif() + +if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 ) + __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD VALUES OFF ) + set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE ) + mark_as_advanced( ANDROID_FORCE_ARM_BUILD ) +else() + unset( ANDROID_FORCE_ARM_BUILD CACHE ) +endif() + +# choose toolchain +if( ANDROID_TOOLCHAIN_NAME ) + list( FIND __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" __toolchainIdx ) + if( __toolchainIdx EQUAL -1 ) + list( SORT __availableToolchains ) + string( REPLACE ";" "\n * " toolchains_list "${__availableToolchains}" ) + set( toolchains_list " * ${toolchains_list}") + message( FATAL_ERROR "Specified toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is missing in your NDK or broken. Please verify that your NDK is working or select another compiler toolchain. +To configure the toolchain set CMake variable ANDROID_TOOLCHAIN_NAME to one of the following values:\n${toolchains_list}\n" ) + endif() + list( GET __availableToolchainArchs ${__toolchainIdx} __toolchainArch ) + if( NOT __toolchainArch STREQUAL ANDROID_ARCH_NAME ) + message( SEND_ERROR "Selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is not able to compile binaries for the \"${ANDROID_ARCH_NAME}\" platform." ) + endif() +else() + set( __toolchainIdx -1 ) + set( __applicableToolchains "" ) + set( __toolchainMaxVersion "0.0.0" ) + list( LENGTH __availableToolchains __availableToolchainsCount ) + math( EXPR __availableToolchainsCount "${__availableToolchainsCount}-1" ) + foreach( __idx RANGE ${__availableToolchainsCount} ) + list( GET __availableToolchainArchs ${__idx} __toolchainArch ) + if( __toolchainArch STREQUAL ANDROID_ARCH_NAME ) + list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion ) + string( REPLACE "x" "99" __toolchainVersion "${__toolchainVersion}") + if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion ) + set( __toolchainMaxVersion "${__toolchainVersion}" ) + set( __toolchainIdx ${__idx} ) + endif() + endif() + endforeach() + unset( __availableToolchainsCount ) + unset( __toolchainMaxVersion ) + unset( __toolchainVersion ) +endif() +unset( __toolchainArch ) +if( __toolchainIdx EQUAL -1 ) + message( FATAL_ERROR "No one of available compiler toolchains is able to compile for ${ANDROID_ARCH_NAME} platform." ) +endif() +list( GET __availableToolchains ${__toolchainIdx} ANDROID_TOOLCHAIN_NAME ) +list( GET __availableToolchainMachines ${__toolchainIdx} ANDROID_TOOLCHAIN_MACHINE_NAME ) +list( GET __availableToolchainCompilerVersions ${__toolchainIdx} ANDROID_COMPILER_VERSION ) + +unset( __toolchainIdx ) +unset( __availableToolchains ) +unset( __availableToolchainMachines ) +unset( __availableToolchainArchs ) +unset( __availableToolchainCompilerVersions ) + +# choose native API level +__INIT_VARIABLE( ANDROID_NATIVE_API_LEVEL ENV_ANDROID_NATIVE_API_LEVEL ANDROID_API_LEVEL ENV_ANDROID_API_LEVEL ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME} ANDROID_DEFAULT_NDK_API_LEVEL ) +string( REPLACE "android-" "" ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" ) +string( STRIP "${ANDROID_NATIVE_API_LEVEL}" ANDROID_NATIVE_API_LEVEL ) +# adjust API level +set( __real_api_level ${ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME}} ) +foreach( __level ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + if( (__level LESS ANDROID_NATIVE_API_LEVEL OR __level STREQUAL ANDROID_NATIVE_API_LEVEL) AND NOT __level LESS __real_api_level ) + set( __real_api_level ${__level} ) + endif() +endforeach() +if( __real_api_level AND NOT ANDROID_NATIVE_API_LEVEL STREQUAL __real_api_level ) + message( STATUS "Adjusting Android API level 'android-${ANDROID_NATIVE_API_LEVEL}' to 'android-${__real_api_level}'") + set( ANDROID_NATIVE_API_LEVEL ${__real_api_level} ) +endif() +unset(__real_api_level) +# validate +list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_NATIVE_API_LEVEL}" __levelIdx ) +if( __levelIdx EQUAL -1 ) + message( SEND_ERROR "Specified Android native API level 'android-${ANDROID_NATIVE_API_LEVEL}' is not supported by your NDK/toolchain." ) +else() + if( BUILD_WITH_ANDROID_NDK ) + __DETECT_NATIVE_API_LEVEL( __realApiLevel "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}/usr/include/android/api-level.h" ) + if( NOT __realApiLevel EQUAL ANDROID_NATIVE_API_LEVEL AND NOT __realApiLevel GREATER 9000 ) + message( SEND_ERROR "Specified Android API level (${ANDROID_NATIVE_API_LEVEL}) does not match to the level found (${__realApiLevel}). Probably your copy of NDK is broken." ) + endif() + unset( __realApiLevel ) + endif() + set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE ) + set( CMAKE_ANDROID_API ${ANDROID_NATIVE_API_LEVEL} ) + if( CMAKE_VERSION VERSION_GREATER "2.8" ) + list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS ) + set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + endif() +endif() +unset( __levelIdx ) + + +# remember target ABI +set( ANDROID_ABI "${ANDROID_ABI}" CACHE STRING "The target ABI for Android. If arm, then armeabi-v7a is recommended for hardware floating point." FORCE ) +if( CMAKE_VERSION VERSION_GREATER "2.8" ) + list( SORT ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME} ) + set_property( CACHE ANDROID_ABI PROPERTY STRINGS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME}} ) +endif() + + +# runtime choice (STL, rtti, exceptions) +if( NOT ANDROID_STL ) + set( ANDROID_STL gnustl_static ) +endif() +set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" ) +set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" ) +mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES ) + +if( BUILD_WITH_ANDROID_NDK ) + if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared)$") + message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". +The possible values are: + none -> Do not configure the runtime. + system -> Use the default minimal system C++ runtime library. + system_re -> Same as system but with rtti and exceptions. + gabi++_static -> Use the GAbi++ runtime as a static library. + gabi++_shared -> Use the GAbi++ runtime as a shared library. + stlport_static -> Use the STLport runtime as a static library. + stlport_shared -> Use the STLport runtime as a shared library. + gnustl_static -> (default) Use the GNU STL as a static library. + gnustl_shared -> Use the GNU STL as a shared library. +" ) + endif() +elseif( BUILD_WITH_STANDALONE_TOOLCHAIN ) + if( NOT "${ANDROID_STL}" MATCHES "^(none|gnustl_static|gnustl_shared)$") + message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". +The possible values are: + none -> Do not configure the runtime. + gnustl_static -> (default) Use the GNU STL as a static library. + gnustl_shared -> Use the GNU STL as a shared library. +" ) + endif() +endif() + +unset( ANDROID_RTTI ) +unset( ANDROID_EXCEPTIONS ) +unset( ANDROID_STL_INCLUDE_DIRS ) +unset( __libstl ) +unset( __libsupcxx ) + +if( NOT _CMAKE_IN_TRY_COMPILE AND ANDROID_NDK_RELEASE STREQUAL "r7b" AND ARMEABI_V7A AND NOT VFPV3 AND ANDROID_STL MATCHES "gnustl" ) + message( WARNING "The GNU STL armeabi-v7a binaries from NDK r7b can crash non-NEON devices. The files provided with NDK r7b were not configured properly, resulting in crashes on Tegra2-based devices and others when trying to use certain floating-point functions (e.g., cosf, sinf, expf). +You are strongly recommended to switch to another NDK release. +" ) +endif() + +if( NOT _CMAKE_IN_TRY_COMPILE AND X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" ) + message( WARNING "The x86 system header file from NDK r6 has incorrect definition for ptrdiff_t. You are recommended to upgrade to a newer NDK release or manually patch the header: +See https://android.googlesource.com/platform/development.git f907f4f9d4e56ccc8093df6fee54454b8bcab6c2 + diff --git a/ndk/platforms/android-9/arch-x86/include/machine/_types.h b/ndk/platforms/android-9/arch-x86/include/machine/_types.h + index 5e28c64..65892a1 100644 + --- a/ndk/platforms/android-9/arch-x86/include/machine/_types.h + +++ b/ndk/platforms/android-9/arch-x86/include/machine/_types.h + @@ -51,7 +51,11 @@ typedef long int ssize_t; + #endif + #ifndef _PTRDIFF_T + #define _PTRDIFF_T + -typedef long ptrdiff_t; + +# ifdef __ANDROID__ + + typedef int ptrdiff_t; + +# else + + typedef long ptrdiff_t; + +# endif + #endif +" ) +endif() + + +# setup paths and STL for standalone toolchain +if( BUILD_WITH_STANDALONE_TOOLCHAIN ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) + set( ANDROID_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot" ) + + if( NOT ANDROID_STL STREQUAL "none" ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/include/c++/${ANDROID_COMPILER_VERSION}" ) + if( NOT EXISTS "${ANDROID_STL_INCLUDE_DIRS}" ) + # old location ( pre r8c ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}" ) + endif() + if( ARMEABI_V7A AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/bits" ) + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb/bits" ) + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb" ) + else() + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" ) + endif() + # always search static GNU STL to get the location of libsupc++.a + if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb" ) + elseif( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb" ) + elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib" ) + endif() + if( __libstl ) + set( __libsupcxx "${__libstl}/libsupc++.a" ) + set( __libstl "${__libstl}/libstdc++.a" ) + endif() + if( NOT EXISTS "${__libsupcxx}" ) + message( FATAL_ERROR "The required libstdsupc++.a is missing in your standalone toolchain. + Usually it happens because of bug in make-standalone-toolchain.sh script from NDK r7, r7b and r7c. + You need to either upgrade to newer NDK or manually copy + $ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a + to + ${__libsupcxx} + " ) + endif() + if( ANDROID_STL STREQUAL "gnustl_shared" ) + if( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) + elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) + endif() + endif() + endif() +endif() + +# clang +if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" ) + set( ANDROID_COMPILER_IS_CLANG 1 ) + execute_process( COMMAND "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/clang${TOOL_OS_SUFFIX}" --version OUTPUT_VARIABLE ANDROID_CLANG_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) + string( REGEX MATCH "[0-9]+[.][0-9]+" ANDROID_CLANG_VERSION "${ANDROID_CLANG_VERSION}") +elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" ) + string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}") + string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-${ANDROID_COMPILER_VERSION}" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) + if( NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}/bin/clang${TOOL_OS_SUFFIX}" ) + message( FATAL_ERROR "Could not find the Clang compiler driver" ) + endif() + set( ANDROID_COMPILER_IS_CLANG 1 ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) +else() + set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) + unset( ANDROID_COMPILER_IS_CLANG CACHE ) +endif() + +string( REPLACE "." "" _clang_name "clang${ANDROID_CLANG_VERSION}" ) +if( NOT EXISTS "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" ) + set( _clang_name "clang" ) +endif() + + +# setup paths and STL for NDK +if( BUILD_WITH_ANDROID_NDK ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" ) + + if( ANDROID_STL STREQUAL "none" ) + # do nothing + elseif( ANDROID_STL STREQUAL "system" ) + set( ANDROID_RTTI OFF ) + set( ANDROID_EXCEPTIONS OFF ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) + elseif( ANDROID_STL STREQUAL "system_re" ) + set( ANDROID_RTTI ON ) + set( ANDROID_EXCEPTIONS ON ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) + elseif( ANDROID_STL MATCHES "gabi" ) + if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 + message( FATAL_ERROR "gabi++ is not available in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.") + endif() + set( ANDROID_RTTI ON ) + set( ANDROID_EXCEPTIONS OFF ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/gabi++/include" ) + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gabi++/libs/${ANDROID_NDK_ABI_NAME}/libgabi++_static.a" ) + elseif( ANDROID_STL MATCHES "stlport" ) + if( NOT ANDROID_NDK_RELEASE_NUM LESS 8004 ) # before r8d + set( ANDROID_EXCEPTIONS ON ) + else() + set( ANDROID_EXCEPTIONS OFF ) + endif() + if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 + set( ANDROID_RTTI OFF ) + else() + set( ANDROID_RTTI ON ) + endif() + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/stlport/stlport" ) + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/stlport/libs/${ANDROID_NDK_ABI_NAME}/libstlport_static.a" ) + elseif( ANDROID_STL MATCHES "gnustl" ) + set( ANDROID_EXCEPTIONS ON ) + set( ANDROID_RTTI ON ) + if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) + if( ARMEABI_V7A AND ANDROID_COMPILER_VERSION VERSION_EQUAL "4.7" AND ANDROID_NDK_RELEASE STREQUAL "r8d" ) + # gnustl binary for 4.7 compiler is buggy :( + # TODO: look for right fix + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.6" ) + else() + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) + endif() + else() + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" ) + endif() + set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" "${__libstl}/include/backward" ) + if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) + set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) + else() + set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" ) + endif() + else() + message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" ) + endif() + # find libsupc++.a - rtti & exceptions + if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r8b or newer + if( NOT EXISTS "${__libsupcxx}" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r7-r8 + endif() + if( NOT EXISTS "${__libsupcxx}" ) # before r7 + if( ARMEABI_V7A ) + if( ANDROID_FORCE_ARM_BUILD ) + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" ) + else() + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" ) + endif() + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD ) + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" ) + else() + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" ) + endif() + endif() + if( NOT EXISTS "${__libsupcxx}") + message( ERROR "Could not find libsupc++.a for a chosen platform. Either your NDK is not supported or is broken.") + endif() + endif() +endif() + + +# case of shared STL linkage +if( ANDROID_STL MATCHES "shared" AND DEFINED __libstl ) + string( REPLACE "_static.a" "_shared.so" __libstl "${__libstl}" ) + # TODO: check if .so file exists before the renaming +endif() + + +# ccache support +__INIT_VARIABLE( _ndk_ccache NDK_CCACHE ENV_NDK_CCACHE ) +if( _ndk_ccache ) + if( DEFINED NDK_CCACHE AND NOT EXISTS NDK_CCACHE ) + unset( NDK_CCACHE CACHE ) + endif() + find_program( NDK_CCACHE "${_ndk_ccache}" DOC "The path to ccache binary") +else() + unset( NDK_CCACHE CACHE ) +endif() +unset( _ndk_ccache ) + + +# setup the cross-compiler +if( NOT CMAKE_C_COMPILER ) + if( NDK_CCACHE AND NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) + set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" ) + set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" ) + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + else() + set( CMAKE_C_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + endif() + else() + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + else() + set( CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler" ) + set( CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler" ) + endif() + endif() + set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" ) + set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" ) + if( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" ) + # Use gcc-ar if we have it for better LTO support. + set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) + else() + set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) + endif() + set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" ) + set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" ) + set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" ) + set( CMAKE_OBJDUMP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump" ) + set( CMAKE_RANLIB "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib" ) +endif() + +set( _CMAKE_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_MACHINE_NAME}-" ) +if( CMAKE_VERSION VERSION_LESS 2.8.5 ) + set( CMAKE_ASM_COMPILER_ARG1 "-c" ) +endif() +if( APPLE ) + find_program( CMAKE_INSTALL_NAME_TOOL NAMES install_name_tool ) + if( NOT CMAKE_INSTALL_NAME_TOOL ) + message( FATAL_ERROR "Could not find install_name_tool, please check your installation." ) + endif() + mark_as_advanced( CMAKE_INSTALL_NAME_TOOL ) +endif() + +# Force set compilers because standard identification works badly for us +include( CMakeForceCompiler ) +# CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU ) +if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER_ID Clang ) +endif() +set( CMAKE_C_PLATFORM_ID Linux ) +if( X86_64 OR MIPS64 OR ARM64_V8A ) + set( CMAKE_C_SIZEOF_DATA_PTR 8 ) +else() + set( CMAKE_C_SIZEOF_DATA_PTR 4 ) +endif() +set( CMAKE_C_HAS_ISYSROOT 1 ) +set( CMAKE_C_COMPILER_ABI ELF ) +# CMAKE_FORCE_CXX_COMPILER( "${CMAKE_CXX_COMPILER}" GNU ) +if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_CXX_COMPILER_ID Clang) +endif() +set( CMAKE_CXX_PLATFORM_ID Linux ) +set( CMAKE_CXX_SIZEOF_DATA_PTR ${CMAKE_C_SIZEOF_DATA_PTR} ) +set( CMAKE_CXX_HAS_ISYSROOT 1 ) +set( CMAKE_CXX_COMPILER_ABI ELF ) +set( CMAKE_CXX_SOURCE_FILE_EXTENSIONS cc cp cxx cpp CPP c++ C ) +# force ASM compiler (required for CMake < 2.8.5) +set( CMAKE_ASM_COMPILER_ID_RUN TRUE ) +set( CMAKE_ASM_COMPILER_ID GNU ) +set( CMAKE_ASM_COMPILER_WORKS TRUE ) +set( CMAKE_ASM_COMPILER_FORCED TRUE ) +set( CMAKE_COMPILER_IS_GNUASM 1) +set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm ) + +foreach( lang C CXX ASM ) + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_CLANG_VERSION} ) + else() + set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_COMPILER_VERSION} ) + endif() +endforeach() + +# flags and definitions +remove_definitions( -DANDROID ) +add_definitions( -DANDROID ) + +if( ANDROID_SYSROOT MATCHES "[ ;\"]" ) + if( CMAKE_HOST_WIN32 ) + # try to convert path to 8.3 form + file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "@echo %~s1" ) + execute_process( COMMAND "$ENV{ComSpec}" /c "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "${ANDROID_SYSROOT}" + OUTPUT_VARIABLE __path OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE __result ERROR_QUIET ) + if( __result EQUAL 0 ) + file( TO_CMAKE_PATH "${__path}" ANDROID_SYSROOT ) + set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) + else() + set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) + endif() + else() + set( ANDROID_CXX_FLAGS "'--sysroot=${ANDROID_SYSROOT}'" ) + endif() + if( NOT _CMAKE_IN_TRY_COMPILE ) + # quotes can break try_compile and compiler identification + message(WARNING "Path to your Android NDK (or toolchain) has non-alphanumeric symbols.\nThe build might be broken.\n") + endif() +else() + set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) +endif() + +# NDK flags +if (ARM64_V8A ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) + endif() +elseif( ARMEABI OR ARMEABI_V7A) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 ) + set( ANDROID_CXX_FLAGS_RELEASE "-mthumb -fomit-frame-pointer -fno-strict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -finline-limit=64" ) + endif() + else() + # always compile ARMEABI_V6 in arm mode; otherwise there is no difference from ARMEABI + set( ANDROID_CXX_FLAGS_RELEASE "-marm -fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) + endif() + endif() +elseif( X86 OR X86_64 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) + endif() + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) +elseif( MIPS OR MIPS64 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-strict-aliasing -finline-functions -funwind-tables -fmessage-length=0" ) + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers" ) + set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) + endif() +elseif() + set( ANDROID_CXX_FLAGS_RELEASE "" ) + set( ANDROID_CXX_FLAGS_DEBUG "" ) +endif() + +set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" ) # good/necessary when porting desktop libraries + +if( NOT X86 AND NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "-Wno-psabi ${ANDROID_CXX_FLAGS}" ) +endif() + +if( NOT ANDROID_COMPILER_VERSION VERSION_LESS "4.6" ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -no-canonical-prefixes" ) # see https://android-review.googlesource.com/#/c/47564/ +endif() + +# ABI-specific flags +if( ARMEABI_V7A ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv7-a -mfloat-abi=softfp" ) + if( NEON ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=neon" ) + elseif( VFPV3 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3" ) + else() + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3-d16" ) + endif() +elseif( ARMEABI_V6 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv6 -mfloat-abi=softfp -mfpu=vfp" ) # vfp == vfpv2 +elseif( ARMEABI ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" ) +endif() + +if( ANDROID_STL MATCHES "gnustl" AND (EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}") ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +else() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +endif() + +# STL +if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) + if( EXISTS "${__libstl}" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libstl}\"" ) + endif() + if( EXISTS "${__libsupcxx}" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) + # C objects: + set( CMAKE_C_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_C_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_C_LINK_EXECUTABLE " -o " ) + set( CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) + set( CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) + set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) + endif() + if( ANDROID_STL MATCHES "gnustl" ) + if( NOT EXISTS "${ANDROID_LIBM_PATH}" ) + set( ANDROID_LIBM_PATH -lm ) + endif() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} ${ANDROID_LIBM_PATH}" ) + endif() +endif() + +# variables controlling optional build flags +if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 + # libGLESv2.so in NDK's prior to r7 refers to missing external symbols. + # So this flag option is required for all projects using OpenGL from native. + __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES ON ) +else() + __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF ) +endif() +__INIT_VARIABLE( ANDROID_NO_UNDEFINED VALUES ON ) +__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON ) +__INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON ) +__INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON ) +__INIT_VARIABLE( ANDROID_RELRO VALUES ON ) + +set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" ) +set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Put each function in separate section and enable garbage collection of unused input sections at link time" ) +set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker" ) +set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" ) +mark_as_advanced( ANDROID_NO_UNDEFINED ANDROID_SO_UNDEFINED ANDROID_FUNCTION_LEVEL_LINKING ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO ) + +# linker flags +set( ANDROID_LINKER_FLAGS "" ) + +if( ARMEABI_V7A ) + # this is *required* to use the following linker flags that routes around + # a CPU bug in some Cortex-A8 implementations: + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--fix-cortex-a8" ) +endif() + +if( ANDROID_NO_UNDEFINED ) + if( MIPS ) + # there is some sysroot-related problem in mips linker... + if( NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined -Wl,-rpath-link,${ANDROID_SYSROOT}/usr/lib" ) + endif() + else() + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) + endif() +endif() + +if( ANDROID_SO_UNDEFINED ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-allow-shlib-undefined" ) +endif() + +if( ANDROID_FUNCTION_LEVEL_LINKING ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fdata-sections -ffunction-sections" ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--gc-sections" ) +endif() + +if( ANDROID_COMPILER_VERSION VERSION_EQUAL "4.6" ) + if( ANDROID_GOLD_LINKER AND (CMAKE_HOST_UNIX OR ANDROID_NDK_RELEASE_NUM GREATER 8002) AND (ARMEABI OR ARMEABI_V7A OR X86) ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=gold" ) + elseif( ANDROID_NDK_RELEASE_NUM GREATER 8002 ) # after r8b + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=bfd" ) + elseif( ANDROID_NDK_RELEASE STREQUAL "r8b" AND ARMEABI AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "The default bfd linker from arm GCC 4.6 toolchain can fail with 'unresolvable R_ARM_THM_CALL relocation' error message. See https://code.google.com/p/android/issues/detail?id=35342 + On Linux and OS X host platform you can workaround this problem using gold linker (default). + Rerun cmake with -DANDROID_GOLD_LINKER=ON option in case of problems. +" ) + endif() +endif() # version 4.6 + +if( ANDROID_NOEXECSTACK ) + if( ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Xclang -mnoexecstack" ) + else() + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Wa,--noexecstack" ) + endif() + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,noexecstack" ) +endif() + +if( ANDROID_RELRO ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now" ) +endif() + +if( ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "-target ${ANDROID_LLVM_TRIPLE} -Qunused-arguments ${ANDROID_CXX_FLAGS}" ) + if( BUILD_WITH_ANDROID_NDK ) + set( ANDROID_CXX_FLAGS "-gcc-toolchain ${ANDROID_TOOLCHAIN_ROOT} ${ANDROID_CXX_FLAGS}" ) + endif() +endif() + +# cache flags +set( CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags" ) +set( CMAKE_C_FLAGS "" CACHE STRING "c flags" ) +set( CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c++ Release flags" ) +set( CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c Release flags" ) +set( CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c++ Debug flags" ) +set( CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c Debug flags" ) +set( CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id" CACHE STRING "shared linker flags" ) +set( CMAKE_MODULE_LINKER_FLAGS "-Wl,--build-id" CACHE STRING "module linker flags" ) +set( CMAKE_EXE_LINKER_FLAGS "-Wl,--build-id -Wl,-z,nocopyreloc" CACHE STRING "executable linker flags" ) + +# put flags to cache (for debug purpose only) +set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS}" CACHE INTERNAL "Android specific c/c++ flags" ) +set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE}" CACHE INTERNAL "Android specific c/c++ Release flags" ) +set( ANDROID_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG}" CACHE INTERNAL "Android specific c/c++ Debug flags" ) +set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}" CACHE INTERNAL "Android specific c/c++ linker flags" ) + +# finish flags +set( CMAKE_CXX_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_CXX_FLAGS}" ) +set( CMAKE_C_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_C_FLAGS}" ) +set( CMAKE_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_CXX_FLAGS_RELEASE}" ) +set( CMAKE_C_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_C_FLAGS_RELEASE}" ) +set( CMAKE_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_DEBUG}" ) +set( CMAKE_C_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_C_FLAGS_DEBUG}" ) +set( CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" ) +set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" ) +set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" ) + +if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) + set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) + set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) + set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) +endif() + +# pie/pic +if( NOT (ANDROID_NATIVE_API_LEVEL LESS 16) AND (NOT DEFINED ANDROID_APP_PIE OR ANDROID_APP_PIE) AND (CMAKE_VERSION VERSION_GREATER 2.8.8) ) + set( CMAKE_POSITION_INDEPENDENT_CODE TRUE ) + set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie") +else() + set( CMAKE_POSITION_INDEPENDENT_CODE FALSE ) + set( CMAKE_CXX_FLAGS "-fpic ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fpic ${CMAKE_C_FLAGS}" ) +endif() + +# configure rtti +if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES ) + if( ANDROID_RTTI ) + set( CMAKE_CXX_FLAGS "-frtti ${CMAKE_CXX_FLAGS}" ) + else() + set( CMAKE_CXX_FLAGS "-fno-rtti ${CMAKE_CXX_FLAGS}" ) + endif() +endif() + +# configure exceptios +if( DEFINED ANDROID_EXCEPTIONS AND ANDROID_STL_FORCE_FEATURES ) + if( ANDROID_EXCEPTIONS ) + set( CMAKE_CXX_FLAGS "-fexceptions ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fexceptions ${CMAKE_C_FLAGS}" ) + else() + set( CMAKE_CXX_FLAGS "-fno-exceptions ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fno-exceptions ${CMAKE_C_FLAGS}" ) + endif() +endif() + +# global includes and link directories +include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} ) +get_filename_component(__android_install_path "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" ABSOLUTE) # avoid CMP0015 policy warning +link_directories( "${__android_install_path}" ) + +# detect if need link crtbegin_so.o explicitly +if( NOT DEFINED ANDROID_EXPLICIT_CRT_LINK ) + set( __cmd "${CMAKE_CXX_CREATE_SHARED_LIBRARY}" ) + string( REPLACE "" "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_CXX_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_SHARED_LINKER_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "-shared" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain_crtlink_test.so" __cmd "${__cmd}" ) + string( REPLACE "" "\"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + separate_arguments( __cmd ) + foreach( __var ANDROID_NDK ANDROID_NDK_TOOLCHAINS_PATH ANDROID_STANDALONE_TOOLCHAIN ) + if( ${__var} ) + set( __tmp "${${__var}}" ) + separate_arguments( __tmp ) + string( REPLACE "${__tmp}" "${${__var}}" __cmd "${__cmd}") + endif() + endforeach() + string( REPLACE "'" "" __cmd "${__cmd}" ) + string( REPLACE "\"" "" __cmd "${__cmd}" ) + execute_process( COMMAND ${__cmd} RESULT_VARIABLE __cmd_result OUTPUT_QUIET ERROR_QUIET ) + if( __cmd_result EQUAL 0 ) + set( ANDROID_EXPLICIT_CRT_LINK ON ) + else() + set( ANDROID_EXPLICIT_CRT_LINK OFF ) + endif() +endif() + +if( ANDROID_EXPLICIT_CRT_LINK ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) +endif() + +# setup output directories +set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) + +if( DEFINED LIBRARY_OUTPUT_PATH_ROOT + OR EXISTS "${CMAKE_SOURCE_DIR}/AndroidManifest.xml" + OR (EXISTS "${CMAKE_SOURCE_DIR}/../AndroidManifest.xml" AND EXISTS "${CMAKE_SOURCE_DIR}/../jni/") ) + set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "Root for binaries output, set this to change where Android libs are installed to" ) + if( NOT _CMAKE_IN_TRY_COMPILE ) + if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" ) + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" ) + else() + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" ) + endif() + set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for Android libs" ) + endif() +endif() + +# copy shaed stl library to build directory +if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" AND DEFINED LIBRARY_OUTPUT_PATH ) + get_filename_component( __libstlname "${__libstl}" NAME ) + execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess ) + if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}") + message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" ) + endif() + unset( __fileCopyProcess ) + unset( __libstlname ) +endif() + + +# set these global flags for cmake client scripts to change behavior +set( ANDROID True ) +set( BUILD_ANDROID True ) + +# where is the target environment +set( CMAKE_FIND_ROOT_PATH "${ANDROID_TOOLCHAIN_ROOT}/bin" "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" "${ANDROID_SYSROOT}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_PREFIX}/share" ) + +# only search for libraries and includes in the ndk toolchain +set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) + + +# macro to find packages on the host OS +macro( find_host_package ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) + if( CMAKE_HOST_WIN32 ) + SET( WIN32 1 ) + SET( UNIX ) + elseif( CMAKE_HOST_APPLE ) + SET( APPLE 1 ) + SET( UNIX ) + endif() + find_package( ${ARGN} ) + SET( WIN32 ) + SET( APPLE ) + SET( UNIX 1 ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +endmacro() + + +# macro to find programs on the host OS +macro( find_host_program ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) + if( CMAKE_HOST_WIN32 ) + SET( WIN32 1 ) + SET( UNIX ) + elseif( CMAKE_HOST_APPLE ) + SET( APPLE 1 ) + SET( UNIX ) + endif() + find_program( ${ARGN} ) + SET( WIN32 ) + SET( APPLE ) + SET( UNIX 1 ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +endmacro() + + +# export toolchain settings for the try_compile() command +if( NOT _CMAKE_IN_TRY_COMPILE ) + set( __toolchain_config "") + foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN + ANDROID_NDK_HOST_X64 + ANDROID_NDK + ANDROID_NDK_LAYOUT + ANDROID_STANDALONE_TOOLCHAIN + ANDROID_TOOLCHAIN_NAME + ANDROID_ABI + ANDROID_NATIVE_API_LEVEL + ANDROID_STL + ANDROID_STL_FORCE_FEATURES + ANDROID_FORCE_ARM_BUILD + ANDROID_NO_UNDEFINED + ANDROID_SO_UNDEFINED + ANDROID_FUNCTION_LEVEL_LINKING + ANDROID_GOLD_LINKER + ANDROID_NOEXECSTACK + ANDROID_RELRO + ANDROID_LIBM_PATH + ANDROID_EXPLICIT_CRT_LINK + ANDROID_APP_PIE + ) + if( DEFINED ${__var} ) + if( ${__var} MATCHES " ") + set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" ) + else() + set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" ) + endif() + endif() + endforeach() + file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android.toolchain.config.cmake" "${__toolchain_config}" ) + unset( __toolchain_config ) +endif() + + +# force cmake to produce / instead of \ in build commands for Ninja generator +if( CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_HOST_WIN32 ) + # it is a bad hack after all + # CMake generates Ninja makefiles with UNIX paths only if it thinks that we are going to build with MinGW + set( CMAKE_COMPILER_IS_MINGW TRUE ) # tell CMake that we are MinGW + set( CMAKE_CROSSCOMPILING TRUE ) # stop recursion + enable_language( C ) + enable_language( CXX ) + # unset( CMAKE_COMPILER_IS_MINGW ) # can't unset because CMake does not convert back-slashes in response files without it + unset( MINGW ) +endif() + +# Variables need by cmAndroidGradleBuild to generate android_gradle_build.json +set(CMAKE_ANDROID_ARCH_ABI ${ANDROID_ABI}) + + +# Variables controlling behavior or set by cmake toolchain: +# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips", "arm64-v8a", "x86_64", "mips64" +# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14,15,16,17,18,19,21 (depends on NDK version) +# ANDROID_STL : gnustl_static/gnustl_shared/stlport_static/stlport_shared/gabi++_static/gabi++_shared/system_re/system/none +# ANDROID_FORBID_SYGWIN : ON/OFF +# ANDROID_NO_UNDEFINED : ON/OFF +# ANDROID_SO_UNDEFINED : OFF/ON (default depends on NDK version) +# ANDROID_FUNCTION_LEVEL_LINKING : ON/OFF +# ANDROID_GOLD_LINKER : ON/OFF +# ANDROID_NOEXECSTACK : ON/OFF +# ANDROID_RELRO : ON/OFF +# ANDROID_FORCE_ARM_BUILD : ON/OFF +# ANDROID_STL_FORCE_FEATURES : ON/OFF +# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` +# Can be set only at the first run: +# ANDROID_NDK : path to your NDK install +# NDK_CCACHE : path to your ccache executable +# ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain +# ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) +# ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID) +# LIBRARY_OUTPUT_PATH_ROOT : +# ANDROID_STANDALONE_TOOLCHAIN +# +# Primary read-only variables: +# ANDROID : always TRUE +# ARMEABI : TRUE for arm v6 and older devices +# ARMEABI_V6 : TRUE for arm v6 +# ARMEABI_V7A : TRUE for arm v7a +# ARM64_V8A : TRUE for arm64-v8a +# NEON : TRUE if NEON unit is enabled +# VFPV3 : TRUE if VFP version 3 is enabled +# X86 : TRUE if configured for x86 +# X86_64 : TRUE if configured for x86_64 +# MIPS : TRUE if configured for mips +# MIPS64 : TRUE if configured for mips64 +# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used +# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used +# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform +# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86", "mips", "arm64-v8a", "x86_64", "mips64" depending on ANDROID_ABI +# ANDROID_NDK_RELEASE : from r5 to r10d; set only for NDK +# ANDROID_NDK_RELEASE_NUM : numeric ANDROID_NDK_RELEASE version (1000*major+minor) +# ANDROID_ARCH_NAME : "arm", "x86", "mips", "arm64", "x86_64", "mips64" depending on ANDROID_ABI +# ANDROID_SYSROOT : path to the compiler sysroot +# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform +# ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used +# +# Secondary (less stable) read-only variables: +# ANDROID_COMPILER_VERSION : GCC version used (not Clang version) +# ANDROID_CLANG_VERSION : version of clang compiler if clang is used +# ANDROID_CXX_FLAGS : C/C++ compiler flags required by Android platform +# ANDROID_SUPPORTED_ABIS : list of currently allowed values for ANDROID_ABI +# ANDROID_TOOLCHAIN_MACHINE_NAME : "arm-linux-androideabi", "arm-eabi" or "i686-android-linux" +# ANDROID_TOOLCHAIN_ROOT : path to the top level of toolchain (standalone or placed inside NDK) +# ANDROID_CLANG_TOOLCHAIN_ROOT : path to clang tools +# ANDROID_SUPPORTED_NATIVE_API_LEVELS : list of native API levels found inside NDK +# ANDROID_STL_INCLUDE_DIRS : stl include paths +# ANDROID_RTTI : if rtti is enabled by the runtime +# ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime +# ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used +# +# Defaults: +# ANDROID_DEFAULT_NDK_API_LEVEL +# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH} +# ANDROID_NDK_SEARCH_PATHS +# ANDROID_SUPPORTED_ABIS_${ARCH} +# ANDROID_SUPPORTED_NDK_VERSIONS diff --git a/realm/realm-jni/src/io_realm_internal_CheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp similarity index 100% rename from realm/realm-jni/src/io_realm_internal_CheckedRow.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp diff --git a/realm/realm-jni/src/io_realm_internal_Group.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Group.cpp similarity index 100% rename from realm/realm-jni/src/io_realm_internal_Group.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_Group.cpp diff --git a/realm/realm-jni/src/io_realm_internal_LinkView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp similarity index 100% rename from realm/realm-jni/src/io_realm_internal_LinkView.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp diff --git a/realm/realm-jni/src/io_realm_internal_SharedGroup.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedGroup.cpp similarity index 100% rename from realm/realm-jni/src/io_realm_internal_SharedGroup.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_SharedGroup.cpp diff --git a/realm/realm-jni/src/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp similarity index 100% rename from realm/realm-jni/src/io_realm_internal_Table.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp diff --git a/realm/realm-jni/src/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp similarity index 100% rename from realm/realm-jni/src/io_realm_internal_TableQuery.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp diff --git a/realm/realm-jni/src/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp similarity index 100% rename from realm/realm-jni/src/io_realm_internal_TableView.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp diff --git a/realm/realm-jni/src/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp similarity index 100% rename from realm/realm-jni/src/io_realm_internal_UncheckedRow.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp diff --git a/realm/realm-jni/src/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp similarity index 100% rename from realm/realm-jni/src/io_realm_internal_Util.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp diff --git a/realm/realm-jni/src/java_lang_List_Util.cpp b/realm/realm-library/src/main/cpp/java_lang_List_Util.cpp similarity index 100% rename from realm/realm-jni/src/java_lang_List_Util.cpp rename to realm/realm-library/src/main/cpp/java_lang_List_Util.cpp diff --git a/realm/realm-jni/src/java_lang_List_Util.hpp b/realm/realm-library/src/main/cpp/java_lang_List_Util.hpp similarity index 100% rename from realm/realm-jni/src/java_lang_List_Util.hpp rename to realm/realm-library/src/main/cpp/java_lang_List_Util.hpp diff --git a/realm/realm-jni/src/mem_usage.cpp b/realm/realm-library/src/main/cpp/mem_usage.cpp similarity index 100% rename from realm/realm-jni/src/mem_usage.cpp rename to realm/realm-library/src/main/cpp/mem_usage.cpp diff --git a/realm/realm-jni/src/mem_usage.hpp b/realm/realm-library/src/main/cpp/mem_usage.hpp similarity index 100% rename from realm/realm-jni/src/mem_usage.hpp rename to realm/realm-library/src/main/cpp/mem_usage.hpp diff --git a/realm/realm-jni/src/tablebase_tpl.hpp b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp similarity index 100% rename from realm/realm-jni/src/tablebase_tpl.hpp rename to realm/realm-library/src/main/cpp/tablebase_tpl.hpp diff --git a/realm/realm-jni/src/utf8.hpp b/realm/realm-library/src/main/cpp/utf8.hpp similarity index 100% rename from realm/realm-jni/src/utf8.hpp rename to realm/realm-library/src/main/cpp/utf8.hpp diff --git a/realm/realm-jni/src/util.cpp b/realm/realm-library/src/main/cpp/util.cpp similarity index 100% rename from realm/realm-jni/src/util.cpp rename to realm/realm-library/src/main/cpp/util.cpp diff --git a/realm/realm-jni/src/util.hpp b/realm/realm-library/src/main/cpp/util.hpp similarity index 100% rename from realm/realm-jni/src/util.hpp rename to realm/realm-library/src/main/cpp/util.hpp diff --git a/realm/settings.gradle b/realm/settings.gradle index 82a197b850..4540b8669d 100644 --- a/realm/settings.gradle +++ b/realm/settings.gradle @@ -1,4 +1,3 @@ // Realm projects include 'realm-library' include 'realm-annotations-processor' -include 'realm-jni' From 2050a9b89c9a6f8c11f7aa1336038e57324d6260 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 25 Aug 2016 11:42:09 +0200 Subject: [PATCH 0007/2110] Add isManaged() to RealmObject/RealmCollection (#3341) * isValid() returns true for unmanaged object and collection. * Add isManaged() to RealmObject and RealmCollection --- CHANGELOG.md | 12 +++- .../processor/RealmProxyClassGenerator.java | 4 +- .../io/realm/AllTypesRealmProxy.java | 4 +- .../io/realm/NullTypesRealmProxy.java | 2 +- .../java/io/realm/CollectionTests.java | 2 +- .../io/realm/DynamicRealmObjectTests.java | 5 ++ .../io/realm/ManagedRealmCollectionTests.java | 8 +++ .../OrderedRealmCollectionIteratorTests.java | 1 + .../java/io/realm/RealmAsyncQueryTests.java | 3 +- .../java/io/realm/RealmListTests.java | 3 +- .../java/io/realm/RealmObjectTests.java | 17 +++++- .../UnManagedOrderedRealmCollectionTests.java | 2 +- .../realm/UnManagedRealmCollectionTests.java | 10 +++- .../java/io/realm/entities/CustomMethods.java | 2 +- .../java/io/realm/DynamicRealmObject.java | 9 ++- .../src/main/java/io/realm/Realm.java | 4 +- .../main/java/io/realm/RealmCollection.java | 21 ++++++- .../src/main/java/io/realm/RealmList.java | 17 ++++-- .../src/main/java/io/realm/RealmObject.java | 56 +++++++++++++++++-- .../src/main/java/io/realm/RealmResults.java | 14 ++++- 20 files changed, 163 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0adb808f2..cac4791627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,18 @@ +## 2.0.0 + +### Breaking Changes + +* `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). + +### Enhancements + +* Added `realmObject.isManaged()`, `RealmObject.isManaged(obj)` and `RealmCollection.isManaged()` (#3101). + ## 1.2.1 ### Internal -* Move JNI build to CMake. +* Moved JNI build to CMake. ## 1.2.0 diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index cc4f726bf0..7ccba2a307 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -276,7 +276,7 @@ private void emitAccessors(JavaWriter writer) throws IOException { writer.emitStatement("proxyState.getRow$realm().nullifyLink(%s)", fieldIndexVariableReference(field)); writer.emitStatement("return"); writer.endControlFlow(); - writer.beginControlFlow("if (!RealmObject.isValid(value))"); + writer.beginControlFlow("if (!(RealmObject.isManaged(value) && RealmObject.isValid(value)))"); writer.emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")"); writer.endControlFlow(); writer.beginControlFlow("if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())"); @@ -315,7 +315,7 @@ private void emitAccessors(JavaWriter writer) throws IOException { writer.emitStatement("return"); writer.endControlFlow(); writer.beginControlFlow("for (RealmModel linkedObject : (RealmList) value)"); - writer.beginControlFlow("if (!RealmObject.isValid(linkedObject))"); + writer.beginControlFlow("if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject)))"); writer.emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must be a valid managed object.\")"); writer.endControlFlow(); writer.beginControlFlow("if (((RealmObjectProxy)linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 1412974f42..3c3bfba9d3 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -196,7 +196,7 @@ static final class AllTypesColumnInfo extends ColumnInfo { proxyState.getRow$realm().nullifyLink(columnInfo.columnObjectIndex); return; } - if (!RealmObject.isValid(value)) { + if (!(RealmObject.isManaged(value) && RealmObject.isValid(value))) { throw new IllegalArgumentException("'value' is not a valid managed object."); } if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { @@ -225,7 +225,7 @@ static final class AllTypesColumnInfo extends ColumnInfo { return; } for (RealmModel linkedObject : (RealmList) value) { - if (!RealmObject.isValid(linkedObject)) { + if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject))) { throw new IllegalArgumentException("Each element of 'value' must be a valid managed object."); } if (((RealmObjectProxy)linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 4d62f6ef04..24a5b2949d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -482,7 +482,7 @@ static final class NullTypesColumnInfo extends ColumnInfo { proxyState.getRow$realm().nullifyLink(columnInfo.fieldObjectNullIndex); return; } - if (!RealmObject.isValid(value)) { + if (!(RealmObject.isManaged(value) && RealmObject.isValid(value))) { throw new IllegalArgumentException("'value' is not a valid managed object."); } if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index d147fd591a..064d318d57 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -49,7 +49,7 @@ protected enum ManagedCollection { // Enumerate all methods from the RealmCollection interface that depend on Realm API's. protected enum RealmCollectionMethod { - WHERE, MIN, MAX, SUM, AVERAGE, MIN_DATE, MAX_DATE, DELETE_ALL_FROM_REALM, IS_VALID + WHERE, MIN, MAX, SUM, AVERAGE, MIN_DATE, MAX_DATE, DELETE_ALL_FROM_REALM, IS_VALID, IS_MANAGED } // Enumerate all methods from the Collection interface diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index d9153010a2..580a59e9be 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -122,6 +122,11 @@ public void constructor_deletedObjectThrows() { new DynamicRealmObject(typedObj); } + @Test (expected = IllegalArgumentException.class) + public void constructor_unmanagedObjectThrows() { + new DynamicRealmObject(new AllTypes()); + } + // Test that all getters fail if given invalid field name @Test public void typedGetter_illegalFieldNameThrows() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 0812e5f972..3fc90f93c0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -522,6 +522,7 @@ public void realmMethods_invalidFieldNames() { case WHERE: case DELETE_ALL_FROM_REALM: case IS_VALID: + case IS_MANAGED: continue; default: @@ -553,6 +554,7 @@ public void realmMethods_invalidFieldType() { case WHERE: case DELETE_ALL_FROM_REALM: case IS_VALID: + case IS_MANAGED: continue; default: @@ -636,6 +638,11 @@ public void isValid_realmClosed() { assertFalse(collection.isValid()); } + @Test + public void isManaged() { + assertTrue(collection.isManaged()); + } + @Test public void contains_deletedRealmObject() { AllJavaTypes obj = collection.iterator().next(); @@ -717,6 +724,7 @@ public Boolean call() throws Exception { case MAX_DATE: collection.maxDate(AllJavaTypes.FIELD_DATE); break; case DELETE_ALL_FROM_REALM: collection.deleteAllFromRealm(); break; case IS_VALID: collection.isValid(); break; + case IS_MANAGED: collection.isManaged(); return true; } return false; } catch (IllegalStateException ignored) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index 7fbed86aca..e35a649c82 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -724,6 +724,7 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification_managedColl case MIN_DATE: case MAX_DATE: case IS_VALID: + case IS_MANAGED: realm.cancelTransaction(); continue; default: diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index b36526ddaf..1bc0da4b20 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -365,7 +365,8 @@ public void unmanagedObjectAsyncBehaviour() { dog.setAge(10); assertTrue(dog.isLoaded()); - assertFalse(dog.isValid()); + assertTrue(dog.isValid()); + assertFalse(dog.isManaged()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 1230a7c761..7dc7177144 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -120,7 +120,7 @@ public void constructor_unmanaged_null() { public void isValid_unmanagedMode() { //noinspection MismatchedQueryAndUpdateOfCollection RealmList list = new RealmList(); - assertFalse(list.isValid()); + assertTrue(list.isValid()); } @Test @@ -724,6 +724,7 @@ public void realmMethods_onDeletedLinkView() { case MAX_DATE: results.maxDate(CyclicType.FIELD_DATE); break; case DELETE_ALL_FROM_REALM: results.deleteAllFromRealm(); break; case IS_VALID: continue; // Does not throw + case IS_MANAGED: continue; // Does not throw } fail(method + " should have thrown an Exception."); } catch (IllegalStateException ignored) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 7c64ba48cd..aa0ecba5e1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -933,7 +933,7 @@ public void classNameConflictsWithFrameworkClass() { @Test public void isValid_unmanagedObject() { AllTypes allTypes = new AllTypes(); - assertFalse(allTypes.isValid()); + assertTrue(allTypes.isValid()); } @Test @@ -1227,6 +1227,21 @@ public void isValid() { assertFalse(dog.isValid()); } + @Test + public void isManaged_managedObject() { + realm.beginTransaction(); + Dog dog = realm.createObject(Dog.class); + realm.commitTransaction(); + + assertTrue(dog.isManaged()); + } + + @Test + public void isManaged_unmanagedObject() { + Dog dog = new Dog(); + assertFalse(dog.isManaged()); + } + // Test NaN value on float and double columns @Test public void float_double_NaN() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java index d2d2bc41f9..ea515284b7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java @@ -173,7 +173,7 @@ public void load() { @Test public void isValid() { - assertFalse(collection.isValid()); + assertTrue(collection.isValid()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java index 70f6aab05d..d74ed31181 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java @@ -99,7 +99,8 @@ public void unsupportedMethods_unManagedCollections() { case DELETE_ALL_FROM_REALM: collection.deleteAllFromRealm(); break; // Supported methods - case IS_VALID: assertFalse(collection.isValid()); continue; + case IS_VALID: assertTrue(collection.isValid()); continue; + case IS_MANAGED: assertFalse(collection.isManaged()); continue; } fail(method + " should have thrown an exception."); } catch (UnsupportedOperationException ignored) { @@ -119,7 +120,12 @@ public void load() { @Test public void isValid() { - assertFalse(collection.isValid()); + assertTrue(collection.isValid()); + } + + @Test + public void isManaged() { + assertFalse(collection.isManaged()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/CustomMethods.java b/realm/realm-library/src/androidTest/java/io/realm/entities/CustomMethods.java index a81d653aad..66ad9a11c8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/CustomMethods.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/CustomMethods.java @@ -42,7 +42,7 @@ public boolean equals(Object o) { return reverseEquals; } CustomMethods other = (CustomMethods) o; - if (isValid() == other.isValid() && other.name.equals(name)) { + if (isManaged() == other.isManaged() && other.name.equals(name)) { return !reverseEquals; } else { return reverseEquals; diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 6f277ceb9b..ff8cb5a461 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -49,9 +49,14 @@ public DynamicRealmObject(RealmModel obj) { throw new IllegalArgumentException("The object is already a DynamicRealmObject: " + obj); } - if (!RealmObject.isValid(obj)) { + if (!RealmObject.isManaged(obj)) { throw new IllegalArgumentException("An object managed by Realm must be provided. This " + - "is an unmanaged object or it was deleted."); + "is an unmanaged object."); + } + + if (!RealmObject.isValid(obj)) { + throw new IllegalArgumentException("A valid object managed by Realm must be provided. " + + "This object was deleted."); } RealmObjectProxy proxy = (RealmObjectProxy) obj; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index b0bca8a249..1cfe512f1d 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1319,8 +1319,8 @@ private void checkValidObjectForDetach(E realmObject) { if (realmObject == null) { throw new IllegalArgumentException("Null objects cannot be copied from Realm."); } - if (!RealmObject.isValid(realmObject)) { - throw new IllegalArgumentException("RealmObject is not valid, so it cannot be copied."); + if (!(RealmObject.isManaged(realmObject) && RealmObject.isValid(realmObject))) { + throw new IllegalArgumentException("Only valid managed objects can be copied from Realm."); } if (realmObject instanceof DynamicRealmObject) { throw new IllegalArgumentException("DynamicRealmObject cannot be copied from Realm."); diff --git a/realm/realm-library/src/main/java/io/realm/RealmCollection.java b/realm/realm-library/src/main/java/io/realm/RealmCollection.java index 6278272dc2..722d1253d3 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCollection.java @@ -139,13 +139,28 @@ public interface RealmCollection extends Collection { boolean load(); /** - * Checks if the collection is still valid to use e.g. the {@link io.realm.Realm} instance hasn't - * been closed. + * Checks if the collection is still valid to use, i.e., the {@link io.realm.Realm} instance hasn't been closed. It + * will always return {@code true} for an unmanaged collection. * - * @return {@code true} if still valid to use, {@code false} otherwise. + * @return {@code true} if it is still valid to use or an unmanaged collection, {@code false} otherwise. */ boolean isValid(); + /** + * Checks if the collection is managed by Realm. A managed collection is just a wrapper around the data in the + * underlying Realm file. On Looper threads, a managed collection will be live-updated so it always points to the + * latest data. Managed collections are thread confined so that they cannot be accessed from other threads than the + * one that created them. + *

+ * + * If this method returns {@code false}, the collection is unmanaged. An unmanaged collection is just a normal java + * collection, so it will not be live updated. + *

+ * + * @return {@code true} if this is a managed {@link RealmCollection}, {@code false} otherwise. + */ + boolean isManaged(); + /** * Tests whether this {@code Collection} contains the specified object. Returns * {@code true} if and only if at least one element {@code elem} in this diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index d6397ef789..9ba427a4c4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -114,19 +114,26 @@ public RealmList(E... objects) { } /** - * Checks if the {@link RealmList} is managed by Realm and contains valid data i.e., the {@link io.realm.Realm} - * instance hasn't been closed. - * - * @return {@code true} if still valid to use, {@code false} otherwise or if it's an unmanaged list. + * {@inheritDoc} */ public boolean isValid() { + if (realm == null) { + return true; + } //noinspection SimplifiableIfStatement - if (realm == null || realm.isClosed()) { + if (realm.isClosed()) { return false; } return isAttached(); } + /** + * {@inheritDoc} + */ + public boolean isManaged() { + return realm != null; + } + private boolean isAttached() { return view != null && view.isAttached(); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index b621f26571..ed03ec6e4b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -114,7 +114,7 @@ public static void deleteFromRealm(E object) { /** * Checks if the RealmObject is still valid to use i.e., the RealmObject hasn't been deleted nor has the - * {@link io.realm.Realm} been closed. It will always return {@code false} for unmanaged objects. + * {@link io.realm.Realm} been closed. It will always return {@code true} for unmanaged objects. *

+ * + * If this method returns {@code false}, the object is unmanaged. An unmanaged object is just a normal Java object, + * so it can be parsed freely across threads, but the data in the object is not connected to the underlying Realm, + * so it will not be live updated. + *

+ * + * It is possible to create a managed object from an unmanaged object by using + * {@link Realm#copyToRealm(RealmModel)}. An unmanaged object can be created from a managed object by using + * {@link Realm#copyFromRealm(RealmModel)}. + * + * @return {@code true} if the object is managed, {@code false} if it is unmanaged. + */ + public boolean isManaged() { + return isManaged(this); + } + + /** + * Checks if this object is managed by Realm. A managed object is just a wrapper around the data in the underlying + * Realm file. On Looper threads, a managed object will be live-updated so it always points to the latest data. It + * is possible to register a change listener using {@link #addChangeListener(RealmModel, RealmChangeListener)} to be + * notified when changes happen. Managed objects are thread confined so that they cannot be accessed from other threads + * than the one that created them. + *

+ * + * If this method returns {@code false}, the object is unmanaged. An unmanaged object is just a normal Java object, + * so it can be parsed freely across threads, but the data in the object is not connected to the underlying Realm, + * so it will not be live updated. + *

+ * + * It is possible to create a managed object from an unmanaged object by using + * {@link Realm#copyToRealm(RealmModel)}. An unmanaged object can be created from a managed object by using + * {@link Realm#copyFromRealm(RealmModel)}. + * + * @return {@code true} if the object is managed, {@code false} if it is unmanaged. + */ + public static boolean isManaged(E object) { + return object instanceof RealmObjectProxy; + } + /** * Makes an asynchronous query blocking. This will also trigger any registered listeners. *

diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index e09d96904e..a4d4eca6f7 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -72,7 +72,7 @@ public final class RealmResults extends AbstractList im private final static String NOT_SUPPORTED_MESSAGE = "This method is not supported by RealmResults."; - BaseRealm realm; + final BaseRealm realm; Class classSpec; // Return type String className; // Class name used by DynamicRealmObjects private TableOrView table = null; @@ -158,7 +158,17 @@ TableOrView getTable() { * {@inheritDoc} */ public boolean isValid() { - return realm != null && !realm.isClosed(); + return !realm.isClosed(); + } + + /** + * A {@link RealmResults} is always a managed collection. + * + * @return {@code true}. + * @see RealmCollection#isManaged() + */ + public boolean isManaged() { + return true; } /** From 66b6baf0d2134df925d4c079805c199e6c3c92b3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 26 Aug 2016 17:51:40 +0800 Subject: [PATCH 0008/2110] Integrate Object Store [PART2] - SharedRealm (#3031) This simplified our code base a lot. Basically all APIs we need from SharedGroup/Group are wrapped in the SharedRealm. So we can just remove those classes. But we do need a few APIs from SharedGroup which is not supplied by SharedRealm because of async queries. Currently we expose those in a friend class of ShareRealm, see realm/realm-object-store#141 We are still managing Realm caches in Java although there are mechanism in OS to do the same thing. The major reason is we have method like deleteRealm needs information from cache to check if all Realm instances are closed. The ShareRealm is actually a std::shared_ptr. We hold the pointer to the std::shared_ptr in Java. Another fundamental change is that we used to have separated SharedGroups for DynamicRealm and typed Realm in the same thread. But now they are using different SharedRealm but actually the different SharedRealms are point to the same SharedGroup. And some other code cleanup. --- .gitmodules | 3 + CHANGELOG.md | 1 + Jenkinsfile | 2 + README.md | 18 +- realm/config/findbugs/findbugs-filter.xml | 25 - .../processor/RealmProxyClassGenerator.java | 76 +-- .../RealmProxyMediatorGenerator.java | 10 +- .../io/realm/AllTypesRealmProxy.java | 104 ++-- .../io/realm/BooleansRealmProxy.java | 46 +- .../io/realm/NullTypesRealmProxy.java | 160 +++--- .../io/realm/RealmDefaultModuleMediator.java | 10 +- .../resources/io/realm/SimpleRealmProxy.java | 34 +- realm/realm-library/build.gradle | 3 +- .../java/io/realm/RealmAsyncQueryTests.java | 4 +- .../java/io/realm/RealmCacheTests.java | 12 +- .../java/io/realm/RealmInMemoryTest.java | 2 +- .../androidTest/java/io/realm/RealmTests.java | 38 +- .../java/io/realm/internal/JNICloseTest.java | 120 +---- .../internal/JNIImplicitTransactionsTest.java | 98 ---- .../java/io/realm/internal/JNILinkTest.java | 48 +- ...NI_nativeTests.java => JNINativeTest.java} | 28 +- .../java/io/realm/internal/JNIQueryTest.java | 12 - .../java/io/realm/internal/JNITableTest.java | 209 +++----- .../io/realm/internal/JNITransactions.java | 462 ------------------ .../java/io/realm/internal/PivotTest.java | 23 - .../io/realm/internal/SharedRealmTests.java | 154 ++++++ .../realm-library/src/main/cpp/CMakeLists.txt | 17 +- .../src/main/cpp/io_realm_internal_Group.cpp | 292 ----------- .../cpp/io_realm_internal_SharedGroup.cpp | 329 ------------- .../cpp/io_realm_internal_SharedRealm.cpp | 371 ++++++++++++++ .../src/main/cpp/io_realm_internal_Table.cpp | 44 +- .../main/cpp/io_realm_internal_TableQuery.cpp | 228 ++++----- .../main/cpp/io_realm_internal_TestUtil.cpp | 127 +++++ .../src/main/cpp/io_realm_internal_Util.cpp | 103 ---- realm/realm-library/src/main/cpp/object-store | 1 + realm/realm-library/src/main/cpp/util.cpp | 21 +- realm/realm-library/src/main/cpp/util.hpp | 121 ++--- .../src/main/java/io/realm/BaseRealm.java | 74 +-- .../src/main/java/io/realm/DynamicRealm.java | 3 +- .../main/java/io/realm/HandlerController.java | 18 +- .../src/main/java/io/realm/ProxyState.java | 2 +- .../src/main/java/io/realm/Realm.java | 6 +- .../java/io/realm/RealmConfiguration.java | 14 +- .../main/java/io/realm/RealmObjectSchema.java | 13 +- .../src/main/java/io/realm/RealmQuery.java | 136 +++--- .../src/main/java/io/realm/RealmResults.java | 4 +- .../src/main/java/io/realm/RealmSchema.java | 41 +- .../main/java/io/realm/internal/Context.java | 8 - .../main/java/io/realm/internal/Group.java | 289 ----------- .../realm/internal/ImplicitTransaction.java | 94 ---- .../main/java/io/realm/internal/LinkView.java | 2 +- .../io/realm/internal/RealmProxyMediator.java | 6 +- .../java/io/realm/internal/SharedGroup.java | 394 --------------- .../io/realm/internal/SharedGroupManager.java | 193 -------- .../java/io/realm/internal/SharedRealm.java | 294 +++++++++++ .../main/java/io/realm/internal/Table.java | 206 ++------ .../java/io/realm/internal/TableOrView.java | 2 - .../java/io/realm/internal/TableQuery.java | 84 ++-- .../java/io/realm/internal/TableView.java | 16 +- .../{ReadTransaction.java => TestUtil.java} | 26 +- .../java/io/realm/internal/UncheckedRow.java | 2 +- .../src/main/java/io/realm/internal/Util.java | 37 -- .../io/realm/internal/WriteTransaction.java | 52 -- .../realm/internal/async/QueryUpdateTask.java | 30 +- .../internal/modules/CompositeMediator.java | 10 +- .../internal/modules/FilterableMediator.java | 10 +- 66 files changed, 1793 insertions(+), 3629 deletions(-) create mode 100644 .gitmodules delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/JNIImplicitTransactionsTest.java rename realm/realm-library/src/androidTest/java/io/realm/internal/{JNI_nativeTests.java => JNINativeTest.java} (51%) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/JNITransactions.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java delete mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_Group.cpp delete mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_SharedGroup.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp create mode 160000 realm/realm-library/src/main/cpp/object-store delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/Group.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/ImplicitTransaction.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/SharedGroup.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/SharedGroupManager.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java rename realm/realm-library/src/main/java/io/realm/internal/{ReadTransaction.java => TestUtil.java} (55%) delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/WriteTransaction.java diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..35b419c520 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "realm/realm-library/src/main/cpp/object-store"] + path = realm/realm-library/src/main/cpp/object-store + url = https://github.com/realm/realm-object-store.git diff --git a/CHANGELOG.md b/CHANGELOG.md index cac4791627..026e296c64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Breaking Changes * `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). +* armeabi is not supported anymore. ### Enhancements diff --git a/Jenkinsfile b/Jenkinsfile index 291c183c5a..4c10941ce9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,6 +11,8 @@ try { checkout scm // Make sure not to delete the folder that Jenkins allocates to store scripts sh 'git clean -ffdx -e .????????' + // Update submodule for object-store + sh 'git submodule update --init --force' stage 'Docker build' def buildEnv = docker.build 'realm-java:snapshot' diff --git a/README.md b/README.md index 791a4f8cc6..9f0eeec413 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ See [version.txt](version.txt) for the latest version number. In case you don't want to use the precompiled version, you can build Realm yourself from source. -Prerequisites: +### Prerequisites * Make sure `make` is available in your `$PATH`. * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. @@ -101,6 +101,22 @@ Prerequisites: launchctl setenv REALM_CORE_DOWNLOAD_DIR "$REALM_CORE_DOWNLOAD_DIR" ``` +### Download sources + +You can download the source code of Realm Java by using git. Since realm-java has git submodules, use `--recursive` when cloning the repository. + +``` +git clone git@github.com:realm/realm-java.git --recursive +``` + +or + +``` +git clone https://github.com/realm/realm-java.git --recursive +``` + +### Build + Once you have completed all the pre-requisites building Realm is done with a simple command ``` diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index 37ab1ae7e9..b6503d5337 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -7,21 +7,6 @@ - - - - - - - - - - - - - - - @@ -42,16 +27,6 @@ - - - - - - - - - - diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 7ccba2a307..13c8972f66 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -70,7 +70,7 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("io.realm.internal.RealmObjectProxy"); imports.add("io.realm.internal.Table"); imports.add("io.realm.internal.TableOrView"); - imports.add("io.realm.internal.ImplicitTransaction"); + imports.add("io.realm.internal.SharedRealm"); imports.add("io.realm.internal.LinkView"); imports.add("io.realm.internal.android.JsonUtils"); imports.add("java.io.IOException"); @@ -345,10 +345,10 @@ private void emitInitTableMethod(JavaWriter writer) throws IOException { "Table", // Return type "initTable", // Method name EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "ImplicitTransaction", "transaction"); // Argument type & argument name + "SharedRealm", "sharedRealm"); // Argument type & argument name - writer.beginControlFlow("if (!transaction.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); - writer.emitStatement("Table table = transaction.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); + writer.beginControlFlow("if (!sharedRealm.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); + writer.emitStatement("Table table = sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); // For each field generate corresponding table index constant for (VariableElement field : metadata.getFields()) { @@ -367,17 +367,17 @@ private void emitInitTableMethod(JavaWriter writer) throws IOException { Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeCanonicalName), fieldName, nullableFlag); } else if (Utils.isRealmModel(field)) { - writer.beginControlFlow("if (!transaction.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName); - writer.emitStatement("%s%s.initTable(transaction)", fieldTypeSimpleName, Constants.PROXY_SUFFIX); + writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName); + writer.emitStatement("%s%s.initTable(sharedRealm)", fieldTypeSimpleName, Constants.PROXY_SUFFIX); writer.endControlFlow(); - writer.emitStatement("table.addColumnLink(RealmFieldType.OBJECT, \"%s\", transaction.getTable(\"%s%s\"))", + writer.emitStatement("table.addColumnLink(RealmFieldType.OBJECT, \"%s\", sharedRealm.getTable(\"%s%s\"))", fieldName, Constants.TABLE_PREFIX, fieldTypeSimpleName); } else if (Utils.isRealmList(field)) { String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); - writer.beginControlFlow("if (!transaction.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName); - writer.emitStatement("%s.initTable(transaction)", Utils.getProxyClassName(genericTypeSimpleName)); + writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName); + writer.emitStatement("%s.initTable(sharedRealm)", Utils.getProxyClassName(genericTypeSimpleName)); writer.endControlFlow(); - writer.emitStatement("table.addColumnLink(RealmFieldType.LIST, \"%s\", transaction.getTable(\"%s%s\"))", + writer.emitStatement("table.addColumnLink(RealmFieldType.LIST, \"%s\", sharedRealm.getTable(\"%s%s\"))", fieldName, Constants.TABLE_PREFIX, genericTypeSimpleName); } } @@ -396,7 +396,7 @@ private void emitInitTableMethod(JavaWriter writer) throws IOException { writer.emitStatement("return table"); writer.endControlFlow(); - writer.emitStatement("return transaction.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); + writer.emitStatement("return sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); writer.endMethod(); writer.emitEmptyLine(); } @@ -406,14 +406,14 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { columnInfoClassName(), // Return type "validateTable", // Method name EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "ImplicitTransaction", "transaction"); // Argument type & argument name + "SharedRealm", "sharedRealm"); // Argument type & argument name - writer.beginControlFlow("if (transaction.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); - writer.emitStatement("Table table = transaction.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); + writer.beginControlFlow("if (sharedRealm.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); + writer.emitStatement("Table table = sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); // verify number of columns writer.beginControlFlow("if (table.getColumnCount() != " + metadata.getFields().size() + ")"); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Field count does not match - expected %d but was \" + table.getColumnCount())", + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Field count does not match - expected %d but was \" + table.getColumnCount())", metadata.getFields().size()); writer.endControlFlow(); @@ -425,7 +425,7 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.emitEmptyLine(); // create an instance of ColumnInfo - writer.emitStatement("final %1$s columnInfo = new %1$s(transaction.getPath(), table)", columnInfoClassName()); + writer.emitStatement("final %1$s columnInfo = new %1$s(sharedRealm.getPath(), table)", columnInfoClassName()); writer.emitEmptyLine(); // For each field verify there is a corresponding @@ -438,13 +438,13 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { if (Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeQualifiedName)) { // make sure types align writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Missing field '%s' in existing Realm file. " + + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing field '%s' in existing Realm file. " + "Either remove field or migrate using io.realm.internal.Table.addColumn()." + "\")", fieldName); writer.endControlFlow(); writer.beginControlFlow("if (columnTypes.get(\"%s\") != %s)", fieldName, Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeQualifiedName)); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Invalid type '%s' for field '%s' in existing Realm file.\")", + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid type '%s' for field '%s' in existing Realm file.\")", fieldTypeSimpleName, fieldName); writer.endControlFlow(); @@ -453,19 +453,19 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.beginControlFlow("if (!table.isColumnNullable(%s))", fieldIndexVariableReference(field)); // Check if the existing PrimaryKey does support null value for String, Byte, Short, Integer, & Long if (field.equals(metadata.getPrimaryKey())) { - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath()," + + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + "\"@PrimaryKey field '%s' does not support null values in the existing Realm file. " + "Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required.\")", fieldName); // nullability check for boxed types } else if (Utils.isBoxedType(fieldTypeQualifiedName)) { - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath()," + + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + "\"Field '%s' does not support null values in the existing Realm file. " + "Either set @Required, use the primitive type for field '%s' " + "or migrate using RealmObjectSchema.setNullable().\")", fieldName, fieldName); } else { - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath()," + + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + " \"Field '%s' is required. Either set @Required to field '%s' " + "or migrate using RealmObjectSchema.setNullable().\")", fieldName, fieldName); @@ -484,12 +484,12 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { } else { writer.beginControlFlow("if (table.isColumnNullable(%s))", fieldIndexVariableReference(field)); if (Utils.isPrimitiveType(fieldTypeQualifiedName)) { - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath()," + + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + " \"Field '%s' does support null values in the existing Realm file. " + "Use corresponding boxed type for field '%s' or migrate using RealmObjectSchema.setNullable().\")", fieldName, fieldName); } else { - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath()," + + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + " \"Field '%s' does support null values in the existing Realm file. " + "Remove @Required or @PrimaryKey from field '%s' or migrate using RealmObjectSchema.setNullable().\")", fieldName, fieldName); @@ -501,56 +501,56 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { // Validate @PrimaryKey if (field.equals(metadata.getPrimaryKey())) { writer.beginControlFlow("if (table.getPrimaryKey() != table.getColumnIndex(\"%s\"))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Primary key not defined for field '%s' in existing Realm file. Add @PrimaryKey.\")", fieldName); + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Primary key not defined for field '%s' in existing Realm file. Add @PrimaryKey.\")", fieldName); writer.endControlFlow(); } // Validate @Index if (metadata.getIndexedFields().contains(field)) { writer.beginControlFlow("if (!table.hasSearchIndex(table.getColumnIndex(\"%s\")))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Index not defined for field '%s' in existing Realm file. " + + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Index not defined for field '%s' in existing Realm file. " + "Either set @Index or migrate using io.realm.internal.Table.removeSearchIndex().\")", fieldName); writer.endControlFlow(); } } else if (Utils.isRealmModel(field)) { // Links writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Missing field '%s' in existing Realm file. " + + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing field '%s' in existing Realm file. " + "Either remove field or migrate using io.realm.internal.Table.addColumn().\")", fieldName); writer.endControlFlow(); writer.beginControlFlow("if (columnTypes.get(\"%s\") != RealmFieldType.OBJECT)", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Invalid type '%s' for field '%s'\")", + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid type '%s' for field '%s'\")", fieldTypeSimpleName, fieldName); writer.endControlFlow(); - writer.beginControlFlow("if (!transaction.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Missing class '%s%s' for field '%s'\")", + writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName); + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing class '%s%s' for field '%s'\")", Constants.TABLE_PREFIX, fieldTypeSimpleName, fieldName); writer.endControlFlow(); - writer.emitStatement("Table table_%d = transaction.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, fieldTypeSimpleName); + writer.emitStatement("Table table_%d = sharedRealm.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, fieldTypeSimpleName); writer.beginControlFlow("if (!table.getLinkTarget(%s).hasSameSchema(table_%d))", fieldIndexVariableReference(field), fieldIndex); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Invalid RealmObject for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid RealmObject for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", fieldName, fieldIndexVariableReference(field), fieldIndex); writer.endControlFlow(); } else if (Utils.isRealmList(field)) { // Link Lists String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Missing field '%s'\")", fieldName); + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing field '%s'\")", fieldName); writer.endControlFlow(); writer.beginControlFlow("if (columnTypes.get(\"%s\") != RealmFieldType.LIST)", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Invalid type '%s' for field '%s'\")", + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid type '%s' for field '%s'\")", genericTypeSimpleName, fieldName); writer.endControlFlow(); - writer.beginControlFlow("if (!transaction.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Missing class '%s%s' for field '%s'\")", + writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName); + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing class '%s%s' for field '%s'\")", Constants.TABLE_PREFIX, genericTypeSimpleName, fieldName); writer.endControlFlow(); - writer.emitStatement("Table table_%d = transaction.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, genericTypeSimpleName); + writer.emitStatement("Table table_%d = sharedRealm.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, genericTypeSimpleName); writer.beginControlFlow("if (!table.getLinkTarget(%s).hasSameSchema(table_%d))", fieldIndexVariableReference(field), fieldIndex); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"Invalid RealmList type for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid RealmList type for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", fieldName, fieldIndexVariableReference(field), fieldIndex); writer.endControlFlow(); } @@ -560,7 +560,7 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.emitStatement("return %s", "columnInfo"); writer.nextControlFlow("else"); - writer.emitStatement("throw new RealmMigrationNeededException(transaction.getPath(), \"The '%s' class is missing from the schema for this Realm.\")", metadata.getSimpleClassName()); + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"The '%s' class is missing from the schema for this Realm.\")", metadata.getSimpleClassName()); writer.endControlFlow(); writer.endMethod(); writer.emitEmptyLine(); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index 091253a819..e78a679736 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -74,7 +74,7 @@ public void generate() throws IOException { "java.util.Iterator", "java.util.Collection", "io.realm.internal.ColumnInfo", - "io.realm.internal.ImplicitTransaction", + "io.realm.internal.SharedRealm", "io.realm.internal.RealmObjectProxy", "io.realm.internal.RealmProxyMediator", "io.realm.internal.Table", @@ -129,12 +129,12 @@ private void emitCreateTableMethod(JavaWriter writer) throws IOException { "Table", "createTable", EnumSet.of(Modifier.PUBLIC), - "Class", "clazz", "ImplicitTransaction", "transaction" + "Class", "clazz", "SharedRealm", "sharedRealm" ); emitMediatorSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return %s.initTable(transaction)", qualifiedProxyClasses.get(i)); + writer.emitStatement("return %s.initTable(sharedRealm)", qualifiedProxyClasses.get(i)); } }, writer); writer.endMethod(); @@ -147,12 +147,12 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { "ColumnInfo", "validateTable", EnumSet.of(Modifier.PUBLIC), - "Class", "clazz", "ImplicitTransaction", "transaction" + "Class", "clazz", "SharedRealm", "sharedRealm" ); emitMediatorSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return %s.validateTable(transaction)", qualifiedProxyClasses.get(i)); + writer.emitStatement("return %s.validateTable(sharedRealm)", qualifiedProxyClasses.get(i)); } }, writer); writer.endMethod(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 3c3bfba9d3..ca2884f09c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -6,9 +6,9 @@ import io.realm.RealmFieldType; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; -import io.realm.internal.ImplicitTransaction; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; @@ -235,9 +235,9 @@ static final class AllTypesColumnInfo extends ColumnInfo { } } - public static Table initTable(ImplicitTransaction transaction) { - if (!transaction.hasTable("class_AllTypes")) { - Table table = transaction.getTable("class_AllTypes"); + public static Table initTable(SharedRealm sharedRealm) { + if (!sharedRealm.hasTable("class_AllTypes")) { + Table table = sharedRealm.getTable("class_AllTypes"); table.addColumn(RealmFieldType.STRING, "columnString", Table.NULLABLE); table.addColumn(RealmFieldType.INTEGER, "columnLong", Table.NOT_NULLABLE); table.addColumn(RealmFieldType.FLOAT, "columnFloat", Table.NOT_NULLABLE); @@ -245,132 +245,132 @@ public static Table initTable(ImplicitTransaction transaction) { table.addColumn(RealmFieldType.BOOLEAN, "columnBoolean", Table.NOT_NULLABLE); table.addColumn(RealmFieldType.DATE, "columnDate", Table.NOT_NULLABLE); table.addColumn(RealmFieldType.BINARY, "columnBinary", Table.NOT_NULLABLE); - if (!transaction.hasTable("class_AllTypes")) { - AllTypesRealmProxy.initTable(transaction); + if (!sharedRealm.hasTable("class_AllTypes")) { + AllTypesRealmProxy.initTable(sharedRealm); } - table.addColumnLink(RealmFieldType.OBJECT, "columnObject", transaction.getTable("class_AllTypes")); - if (!transaction.hasTable("class_AllTypes")) { - AllTypesRealmProxy.initTable(transaction); + table.addColumnLink(RealmFieldType.OBJECT, "columnObject", sharedRealm.getTable("class_AllTypes")); + if (!sharedRealm.hasTable("class_AllTypes")) { + AllTypesRealmProxy.initTable(sharedRealm); } - table.addColumnLink(RealmFieldType.LIST, "columnRealmList", transaction.getTable("class_AllTypes")); + table.addColumnLink(RealmFieldType.LIST, "columnRealmList", sharedRealm.getTable("class_AllTypes")); table.addSearchIndex(table.getColumnIndex("columnString")); table.setPrimaryKey("columnString"); return table; } - return transaction.getTable("class_AllTypes"); + return sharedRealm.getTable("class_AllTypes"); } - public static AllTypesColumnInfo validateTable(ImplicitTransaction transaction) { - if (transaction.hasTable("class_AllTypes")) { - Table table = transaction.getTable("class_AllTypes"); + public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm) { + if (sharedRealm.hasTable("class_AllTypes")) { + Table table = sharedRealm.getTable("class_AllTypes"); if (table.getColumnCount() != 9) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field count does not match - expected 9 but was " + table.getColumnCount()); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count does not match - expected 9 but was " + table.getColumnCount()); } Map columnTypes = new HashMap(); for (long i = 0; i < 9; i++) { columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } - final AllTypesColumnInfo columnInfo = new AllTypesColumnInfo(transaction.getPath(), table); + final AllTypesColumnInfo columnInfo = new AllTypesColumnInfo(sharedRealm.getPath(), table); if (!columnTypes.containsKey("columnString")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'columnString' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnString' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("columnString") != RealmFieldType.STRING) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'String' for field 'columnString' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'columnString' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.columnStringIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(),"@PrimaryKey field 'columnString' does not support null values in the existing Realm file. Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required."); + throw new RealmMigrationNeededException(sharedRealm.getPath(),"@PrimaryKey field 'columnString' does not support null values in the existing Realm file. Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required."); } if (table.getPrimaryKey() != table.getColumnIndex("columnString")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Primary key not defined for field 'columnString' in existing Realm file. Add @PrimaryKey."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary key not defined for field 'columnString' in existing Realm file. Add @PrimaryKey."); } if (!table.hasSearchIndex(table.getColumnIndex("columnString"))) { - throw new RealmMigrationNeededException(transaction.getPath(), "Index not defined for field 'columnString' in existing Realm file. Either set @Index or migrate using io.realm.internal.Table.removeSearchIndex()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Index not defined for field 'columnString' in existing Realm file. Either set @Index or migrate using io.realm.internal.Table.removeSearchIndex()."); } if (!columnTypes.containsKey("columnLong")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'columnLong' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnLong' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("columnLong") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'long' for field 'columnLong' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'long' for field 'columnLong' in existing Realm file."); } if (table.isColumnNullable(columnInfo.columnLongIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'columnLong' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnLong' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnLong' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnLong' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("columnFloat")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'columnFloat' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnFloat' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("columnFloat") != RealmFieldType.FLOAT) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'float' for field 'columnFloat' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'float' for field 'columnFloat' in existing Realm file."); } if (table.isColumnNullable(columnInfo.columnFloatIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'columnFloat' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnFloat' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnFloat' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnFloat' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("columnDouble")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'columnDouble' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnDouble' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("columnDouble") != RealmFieldType.DOUBLE) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'double' for field 'columnDouble' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'double' for field 'columnDouble' in existing Realm file."); } if (table.isColumnNullable(columnInfo.columnDoubleIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'columnDouble' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnDouble' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnDouble' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnDouble' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("columnBoolean")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'columnBoolean' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnBoolean' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("columnBoolean") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'boolean' for field 'columnBoolean' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'columnBoolean' in existing Realm file."); } if (table.isColumnNullable(columnInfo.columnBooleanIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'columnBoolean' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnBoolean' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnBoolean' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnBoolean' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("columnDate")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'columnDate' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnDate' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("columnDate") != RealmFieldType.DATE) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Date' for field 'columnDate' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Date' for field 'columnDate' in existing Realm file."); } if (table.isColumnNullable(columnInfo.columnDateIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'columnDate' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'columnDate' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnDate' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'columnDate' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("columnBinary")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'columnBinary' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnBinary' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("columnBinary") != RealmFieldType.BINARY) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'byte[]' for field 'columnBinary' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'byte[]' for field 'columnBinary' in existing Realm file."); } if (table.isColumnNullable(columnInfo.columnBinaryIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'columnBinary' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'columnBinary' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnBinary' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'columnBinary' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("columnObject")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'columnObject' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnObject' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("columnObject") != RealmFieldType.OBJECT) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'AllTypes' for field 'columnObject'"); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'AllTypes' for field 'columnObject'"); } - if (!transaction.hasTable("class_AllTypes")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing class 'class_AllTypes' for field 'columnObject'"); + if (!sharedRealm.hasTable("class_AllTypes")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing class 'class_AllTypes' for field 'columnObject'"); } - Table table_7 = transaction.getTable("class_AllTypes"); + Table table_7 = sharedRealm.getTable("class_AllTypes"); if (!table.getLinkTarget(columnInfo.columnObjectIndex).hasSameSchema(table_7)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid RealmObject for field 'columnObject': '" + table.getLinkTarget(columnInfo.columnObjectIndex).getName() + "' expected - was '" + table_7.getName() + "'"); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid RealmObject for field 'columnObject': '" + table.getLinkTarget(columnInfo.columnObjectIndex).getName() + "' expected - was '" + table_7.getName() + "'"); } if (!columnTypes.containsKey("columnRealmList")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'columnRealmList'"); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnRealmList'"); } if (columnTypes.get("columnRealmList") != RealmFieldType.LIST) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'AllTypes' for field 'columnRealmList'"); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'AllTypes' for field 'columnRealmList'"); } - if (!transaction.hasTable("class_AllTypes")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing class 'class_AllTypes' for field 'columnRealmList'"); + if (!sharedRealm.hasTable("class_AllTypes")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing class 'class_AllTypes' for field 'columnRealmList'"); } - Table table_8 = transaction.getTable("class_AllTypes"); + Table table_8 = sharedRealm.getTable("class_AllTypes"); if (!table.getLinkTarget(columnInfo.columnRealmListIndex).hasSameSchema(table_8)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid RealmList type for field 'columnRealmList': '" + table.getLinkTarget(columnInfo.columnRealmListIndex).getName() + "' expected - was '" + table_8.getName() + "'"); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid RealmList type for field 'columnRealmList': '" + table.getLinkTarget(columnInfo.columnRealmListIndex).getName() + "' expected - was '" + table_8.getName() + "'"); } return columnInfo; } else { - throw new RealmMigrationNeededException(transaction.getPath(), "The 'AllTypes' class is missing from the schema for this Realm."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'AllTypes' class is missing from the schema for this Realm."); } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index de1dc8239f..65d911e7f7 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -6,9 +6,9 @@ import io.realm.RealmFieldType; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; -import io.realm.internal.ImplicitTransaction; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; @@ -113,9 +113,9 @@ static final class BooleansColumnInfo extends ColumnInfo { proxyState.getRow$realm().setBoolean(columnInfo.anotherBooleanIndex, value); } - public static Table initTable(ImplicitTransaction transaction) { - if (!transaction.hasTable("class_Booleans")) { - Table table = transaction.getTable("class_Booleans"); + public static Table initTable(SharedRealm sharedRealm) { + if (!sharedRealm.hasTable("class_Booleans")) { + Table table = sharedRealm.getTable("class_Booleans"); table.addColumn(RealmFieldType.BOOLEAN, "done", Table.NOT_NULLABLE); table.addColumn(RealmFieldType.BOOLEAN, "isReady", Table.NOT_NULLABLE); table.addColumn(RealmFieldType.BOOLEAN, "mCompleted", Table.NOT_NULLABLE); @@ -123,61 +123,61 @@ public static Table initTable(ImplicitTransaction transaction) { table.setPrimaryKey(""); return table; } - return transaction.getTable("class_Booleans"); + return sharedRealm.getTable("class_Booleans"); } - public static BooleansColumnInfo validateTable(ImplicitTransaction transaction) { - if (transaction.hasTable("class_Booleans")) { - Table table = transaction.getTable("class_Booleans"); + public static BooleansColumnInfo validateTable(SharedRealm sharedRealm) { + if (sharedRealm.hasTable("class_Booleans")) { + Table table = sharedRealm.getTable("class_Booleans"); if (table.getColumnCount() != 4) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field count does not match - expected 4 but was " + table.getColumnCount()); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count does not match - expected 4 but was " + table.getColumnCount()); } Map columnTypes = new HashMap(); for (long i = 0; i < 4; i++) { columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } - final BooleansColumnInfo columnInfo = new BooleansColumnInfo(transaction.getPath(), table); + final BooleansColumnInfo columnInfo = new BooleansColumnInfo(sharedRealm.getPath(), table); if (!columnTypes.containsKey("done")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'done' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'done' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("done") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'boolean' for field 'done' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'done' in existing Realm file."); } if (table.isColumnNullable(columnInfo.doneIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'done' does support null values in the existing Realm file. Use corresponding boxed type for field 'done' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'done' does support null values in the existing Realm file. Use corresponding boxed type for field 'done' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("isReady")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'isReady' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'isReady' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("isReady") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'boolean' for field 'isReady' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'isReady' in existing Realm file."); } if (table.isColumnNullable(columnInfo.isReadyIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'isReady' does support null values in the existing Realm file. Use corresponding boxed type for field 'isReady' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'isReady' does support null values in the existing Realm file. Use corresponding boxed type for field 'isReady' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("mCompleted")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'mCompleted' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'mCompleted' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("mCompleted") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'boolean' for field 'mCompleted' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'mCompleted' in existing Realm file."); } if (table.isColumnNullable(columnInfo.mCompletedIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'mCompleted' does support null values in the existing Realm file. Use corresponding boxed type for field 'mCompleted' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'mCompleted' does support null values in the existing Realm file. Use corresponding boxed type for field 'mCompleted' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("anotherBoolean")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'anotherBoolean' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'anotherBoolean' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("anotherBoolean") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'boolean' for field 'anotherBoolean' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'anotherBoolean' in existing Realm file."); } if (table.isColumnNullable(columnInfo.anotherBooleanIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'anotherBoolean' does support null values in the existing Realm file. Use corresponding boxed type for field 'anotherBoolean' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'anotherBoolean' does support null values in the existing Realm file. Use corresponding boxed type for field 'anotherBoolean' or migrate using RealmObjectSchema.setNullable()."); } return columnInfo; } else { - throw new RealmMigrationNeededException(transaction.getPath(), "The 'Booleans' class is missing from the schema for this Realm."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'Booleans' class is missing from the schema for this Realm."); } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 24a5b2949d..9e57fa4e90 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -6,9 +6,9 @@ import io.realm.RealmFieldType; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; -import io.realm.internal.ImplicitTransaction; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; @@ -491,9 +491,9 @@ static final class NullTypesColumnInfo extends ColumnInfo { proxyState.getRow$realm().setLink(columnInfo.fieldObjectNullIndex, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex()); } - public static Table initTable(ImplicitTransaction transaction) { - if (!transaction.hasTable("class_NullTypes")) { - Table table = transaction.getTable("class_NullTypes"); + public static Table initTable(SharedRealm sharedRealm) { + if (!sharedRealm.hasTable("class_NullTypes")) { + Table table = sharedRealm.getTable("class_NullTypes"); table.addColumn(RealmFieldType.STRING, "fieldStringNotNull", Table.NOT_NULLABLE); table.addColumn(RealmFieldType.STRING, "fieldStringNull", Table.NULLABLE); table.addColumn(RealmFieldType.BOOLEAN, "fieldBooleanNotNull", Table.NOT_NULLABLE); @@ -514,225 +514,225 @@ public static Table initTable(ImplicitTransaction transaction) { table.addColumn(RealmFieldType.DOUBLE, "fieldDoubleNull", Table.NULLABLE); table.addColumn(RealmFieldType.DATE, "fieldDateNotNull", Table.NOT_NULLABLE); table.addColumn(RealmFieldType.DATE, "fieldDateNull", Table.NULLABLE); - if (!transaction.hasTable("class_NullTypes")) { - NullTypesRealmProxy.initTable(transaction); + if (!sharedRealm.hasTable("class_NullTypes")) { + NullTypesRealmProxy.initTable(sharedRealm); } - table.addColumnLink(RealmFieldType.OBJECT, "fieldObjectNull", transaction.getTable("class_NullTypes")); + table.addColumnLink(RealmFieldType.OBJECT, "fieldObjectNull", sharedRealm.getTable("class_NullTypes")); table.setPrimaryKey(""); return table; } - return transaction.getTable("class_NullTypes"); + return sharedRealm.getTable("class_NullTypes"); } - public static NullTypesColumnInfo validateTable(ImplicitTransaction transaction) { - if (transaction.hasTable("class_NullTypes")) { - Table table = transaction.getTable("class_NullTypes"); + public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm) { + if (sharedRealm.hasTable("class_NullTypes")) { + Table table = sharedRealm.getTable("class_NullTypes"); if (table.getColumnCount() != 21) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field count does not match - expected 21 but was " + table.getColumnCount()); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count does not match - expected 21 but was " + table.getColumnCount()); } Map columnTypes = new HashMap(); for (long i = 0; i < 21; i++) { columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } - final NullTypesColumnInfo columnInfo = new NullTypesColumnInfo(transaction.getPath(), table); + final NullTypesColumnInfo columnInfo = new NullTypesColumnInfo(sharedRealm.getPath(), table); if (!columnTypes.containsKey("fieldStringNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldStringNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldStringNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldStringNotNull") != RealmFieldType.STRING) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'String' for field 'fieldStringNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'fieldStringNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldStringNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldStringNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldStringNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldStringNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldStringNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldStringNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldStringNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldStringNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldStringNull") != RealmFieldType.STRING) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'String' for field 'fieldStringNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'fieldStringNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldStringNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldStringNull' is required. Either set @Required to field 'fieldStringNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldStringNull' is required. Either set @Required to field 'fieldStringNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldBooleanNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldBooleanNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBooleanNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldBooleanNotNull") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Boolean' for field 'fieldBooleanNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Boolean' for field 'fieldBooleanNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldBooleanNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldBooleanNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldBooleanNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldBooleanNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldBooleanNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldBooleanNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldBooleanNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBooleanNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldBooleanNull") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Boolean' for field 'fieldBooleanNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Boolean' for field 'fieldBooleanNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldBooleanNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(),"Field 'fieldBooleanNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldBooleanNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldBooleanNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldBooleanNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldBytesNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldBytesNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBytesNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldBytesNotNull") != RealmFieldType.BINARY) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'byte[]' for field 'fieldBytesNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'byte[]' for field 'fieldBytesNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldBytesNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldBytesNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldBytesNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldBytesNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldBytesNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldBytesNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldBytesNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBytesNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldBytesNull") != RealmFieldType.BINARY) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'byte[]' for field 'fieldBytesNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'byte[]' for field 'fieldBytesNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldBytesNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldBytesNull' is required. Either set @Required to field 'fieldBytesNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldBytesNull' is required. Either set @Required to field 'fieldBytesNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldByteNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldByteNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldByteNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldByteNotNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Byte' for field 'fieldByteNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Byte' for field 'fieldByteNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldByteNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldByteNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldByteNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldByteNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldByteNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldByteNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldByteNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldByteNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldByteNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Byte' for field 'fieldByteNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Byte' for field 'fieldByteNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldByteNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(),"Field 'fieldByteNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldByteNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldByteNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldByteNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldShortNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldShortNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldShortNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldShortNotNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Short' for field 'fieldShortNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Short' for field 'fieldShortNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldShortNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldShortNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldShortNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldShortNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldShortNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldShortNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldShortNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldShortNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldShortNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Short' for field 'fieldShortNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Short' for field 'fieldShortNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldShortNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(),"Field 'fieldShortNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldShortNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldShortNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldShortNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldIntegerNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldIntegerNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldIntegerNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldIntegerNotNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Integer' for field 'fieldIntegerNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Integer' for field 'fieldIntegerNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldIntegerNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldIntegerNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldIntegerNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldIntegerNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldIntegerNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldIntegerNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldIntegerNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldIntegerNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldIntegerNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Integer' for field 'fieldIntegerNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Integer' for field 'fieldIntegerNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldIntegerNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(),"Field 'fieldIntegerNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldIntegerNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldIntegerNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldIntegerNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldLongNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldLongNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldLongNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldLongNotNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Long' for field 'fieldLongNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Long' for field 'fieldLongNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldLongNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldLongNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldLongNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldLongNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldLongNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldLongNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldLongNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldLongNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldLongNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Long' for field 'fieldLongNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Long' for field 'fieldLongNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldLongNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(),"Field 'fieldLongNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldLongNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldLongNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldLongNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldFloatNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldFloatNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldFloatNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldFloatNotNull") != RealmFieldType.FLOAT) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Float' for field 'fieldFloatNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Float' for field 'fieldFloatNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldFloatNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldFloatNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldFloatNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldFloatNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldFloatNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldFloatNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldFloatNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldFloatNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldFloatNull") != RealmFieldType.FLOAT) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Float' for field 'fieldFloatNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Float' for field 'fieldFloatNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldFloatNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(),"Field 'fieldFloatNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldFloatNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldFloatNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldFloatNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldDoubleNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldDoubleNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDoubleNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldDoubleNotNull") != RealmFieldType.DOUBLE) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Double' for field 'fieldDoubleNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Double' for field 'fieldDoubleNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldDoubleNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldDoubleNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldDoubleNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldDoubleNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldDoubleNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldDoubleNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldDoubleNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDoubleNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldDoubleNull") != RealmFieldType.DOUBLE) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Double' for field 'fieldDoubleNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Double' for field 'fieldDoubleNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldDoubleNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(),"Field 'fieldDoubleNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldDoubleNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldDoubleNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldDoubleNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldDateNotNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldDateNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDateNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldDateNotNull") != RealmFieldType.DATE) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Date' for field 'fieldDateNotNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Date' for field 'fieldDateNotNull' in existing Realm file."); } if (table.isColumnNullable(columnInfo.fieldDateNotNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldDateNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldDateNotNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldDateNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldDateNotNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldDateNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldDateNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDateNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldDateNull") != RealmFieldType.DATE) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'Date' for field 'fieldDateNull' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Date' for field 'fieldDateNull' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.fieldDateNullIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'fieldDateNull' is required. Either set @Required to field 'fieldDateNull' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldDateNull' is required. Either set @Required to field 'fieldDateNull' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("fieldObjectNull")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'fieldObjectNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldObjectNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("fieldObjectNull") != RealmFieldType.OBJECT) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'NullTypes' for field 'fieldObjectNull'"); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'NullTypes' for field 'fieldObjectNull'"); } - if (!transaction.hasTable("class_NullTypes")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing class 'class_NullTypes' for field 'fieldObjectNull'"); + if (!sharedRealm.hasTable("class_NullTypes")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing class 'class_NullTypes' for field 'fieldObjectNull'"); } - Table table_20 = transaction.getTable("class_NullTypes"); + Table table_20 = sharedRealm.getTable("class_NullTypes"); if (!table.getLinkTarget(columnInfo.fieldObjectNullIndex).hasSameSchema(table_20)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid RealmObject for field 'fieldObjectNull': '" + table.getLinkTarget(columnInfo.fieldObjectNullIndex).getName() + "' expected - was '" + table_20.getName() + "'"); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid RealmObject for field 'fieldObjectNull': '" + table.getLinkTarget(columnInfo.fieldObjectNullIndex).getName() + "' expected - was '" + table_20.getName() + "'"); } return columnInfo; } else { - throw new RealmMigrationNeededException(transaction.getPath(), "The 'NullTypes' class is missing from the schema for this Realm."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'NullTypes' class is missing from the schema for this Realm."); } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index dacfbdf3f1..8b9a712837 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -3,9 +3,9 @@ import android.util.JsonReader; import io.realm.internal.ColumnInfo; -import io.realm.internal.ImplicitTransaction; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import java.io.IOException; import java.util.Collection; @@ -30,22 +30,22 @@ class DefaultRealmModuleMediator extends RealmProxyMediator { } @Override - public Table createTable(Class clazz, ImplicitTransaction transaction) { + public Table createTable(Class clazz, SharedRealm sharedRealm) { checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { - return io.realm.AllTypesRealmProxy.initTable(transaction); + return io.realm.AllTypesRealmProxy.initTable(sharedRealm); } else { throw getMissingProxyClassException(clazz); } } @Override - public ColumnInfo validateTable(Class clazz, ImplicitTransaction transaction) { + public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm) { checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { - return io.realm.AllTypesRealmProxy.validateTable(transaction); + return io.realm.AllTypesRealmProxy.validateTable(sharedRealm); } else { throw getMissingProxyClassException(clazz); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 942bbc2d2d..fd245ceba8 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -6,9 +6,9 @@ import io.realm.RealmFieldType; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; -import io.realm.internal.ImplicitTransaction; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; @@ -85,51 +85,51 @@ static final class SimpleColumnInfo extends ColumnInfo { proxyState.getRow$realm().setLong(columnInfo.ageIndex, value); } - public static Table initTable(ImplicitTransaction transaction) { - if (!transaction.hasTable("class_Simple")) { - Table table = transaction.getTable("class_Simple"); + public static Table initTable(SharedRealm sharedRealm) { + if (!sharedRealm.hasTable("class_Simple")) { + Table table = sharedRealm.getTable("class_Simple"); table.addColumn(RealmFieldType.STRING, "name", Table.NULLABLE); table.addColumn(RealmFieldType.INTEGER, "age", Table.NOT_NULLABLE); table.setPrimaryKey(""); return table; } - return transaction.getTable("class_Simple"); + return sharedRealm.getTable("class_Simple"); } - public static SimpleColumnInfo validateTable(ImplicitTransaction transaction) { - if (transaction.hasTable("class_Simple")) { - Table table = transaction.getTable("class_Simple"); + public static SimpleColumnInfo validateTable(SharedRealm sharedRealm) { + if (sharedRealm.hasTable("class_Simple")) { + Table table = sharedRealm.getTable("class_Simple"); if (table.getColumnCount() != 2) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field count does not match - expected 2 but was " + table.getColumnCount()); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count does not match - expected 2 but was " + table.getColumnCount()); } Map columnTypes = new HashMap(); for (long i = 0; i < 2; i++) { columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } - final SimpleColumnInfo columnInfo = new SimpleColumnInfo(transaction.getPath(), table); + final SimpleColumnInfo columnInfo = new SimpleColumnInfo(sharedRealm.getPath(), table); if (!columnTypes.containsKey("name")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'name' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'name' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("name") != RealmFieldType.STRING) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'String' for field 'name' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'name' in existing Realm file."); } if (!table.isColumnNullable(columnInfo.nameIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'name' is required. Either set @Required to field 'name' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'name' is required. Either set @Required to field 'name' or migrate using RealmObjectSchema.setNullable()."); } if (!columnTypes.containsKey("age")) { - throw new RealmMigrationNeededException(transaction.getPath(), "Missing field 'age' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'age' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } if (columnTypes.get("age") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(transaction.getPath(), "Invalid type 'int' for field 'age' in existing Realm file."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'int' for field 'age' in existing Realm file."); } if (table.isColumnNullable(columnInfo.ageIndex)) { - throw new RealmMigrationNeededException(transaction.getPath(), "Field 'age' does support null values in the existing Realm file. Use corresponding boxed type for field 'age' or migrate using RealmObjectSchema.setNullable()."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'age' does support null values in the existing Realm file. Use corresponding boxed type for field 'age' or migrate using RealmObjectSchema.setNullable()."); } return columnInfo; } else { - throw new RealmMigrationNeededException(transaction.getPath(), "The 'Simple' class is missing from the schema for this Realm."); + throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'Simple' class is missing from the schema for this Realm."); } } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 502601c1d3..2e122d003c 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -48,7 +48,8 @@ android { // JNI build currently (lack of lto linking support). // This file should be removed and use the one from Android SDK cmake package when it supports lto. "-DCMAKE_TOOLCHAIN_FILE=${project.file('src/main/cpp/android.toolchain.cmake').path}" - abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips' + // armeabi is not supported anymore. + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a', 'mips' } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 1bc0da4b20..9c5260ea0b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -1871,8 +1871,8 @@ public void onChange(AllTypes object) { public void run() { Realm bgRealm = Realm.getInstance(looperThread.realmConfiguration); // Advancing the Realm without generating notifications - bgRealm.sharedGroupManager.promoteToWrite(); - bgRealm.sharedGroupManager.commitAndContinueAsRead(); + bgRealm.sharedRealm.beginTransaction(); + bgRealm.sharedRealm.commitTransaction(); Realm.asyncTaskExecutor.resume(); bgRealm.close(); signalClosedRealm.countDown(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index abbc3a37dc..2368a216eb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -263,9 +263,9 @@ public void releaseCacheInOneThread() { Realm realmA = RealmCache.createRealmOrGetFromCache(defaultConfig, Realm.class); Realm realmB = RealmCache.createRealmOrGetFromCache(defaultConfig, Realm.class); RealmCache.release(realmA); - assertNotNull(realmA.sharedGroupManager); + assertNotNull(realmA.sharedRealm); RealmCache.release(realmB); - assertNull(realmB.sharedGroupManager); + assertNull(realmB.sharedRealm); // No crash but warning in the log RealmCache.release(realmB); @@ -275,9 +275,9 @@ public void releaseCacheInOneThread() { DynamicRealm dynamicRealmB = RealmCache.createRealmOrGetFromCache(defaultConfig, DynamicRealm.class); RealmCache.release(dynamicRealmA); - assertNotNull(dynamicRealmA.sharedGroupManager); + assertNotNull(dynamicRealmA.sharedRealm); RealmCache.release(dynamicRealmB); - assertNull(dynamicRealmB.sharedGroupManager); + assertNull(dynamicRealmB.sharedRealm); // No crash but warning in the log RealmCache.release(dynamicRealmB); @@ -285,8 +285,8 @@ public void releaseCacheInOneThread() { realmA = RealmCache.createRealmOrGetFromCache(defaultConfig, Realm.class); dynamicRealmA = RealmCache.createRealmOrGetFromCache(defaultConfig, DynamicRealm.class); RealmCache.release(realmA); - assertNull(realmA.sharedGroupManager); + assertNull(realmA.sharedRealm); RealmCache.release(dynamicRealmA); - assertNull(realmA.sharedGroupManager); + assertNull(realmA.sharedRealm); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index a2ed08b3e1..8057162511 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -127,7 +127,7 @@ public void testDelete() { } // Test if an in-memory Realm can be written to disk with/without encryption - public void testWriteCopyTo() throws IOException { + public void testWriteCopyTo() { byte[] key = TestHelper.getRandomKey(); String fileName = IDENTIFIER + ".realm"; String encFileName = IDENTIFIER + ".enc.realm"; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 02679eb1e6..11d1d0b858 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -89,6 +89,7 @@ import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmIOException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; +import io.realm.internal.SharedRealm; import io.realm.internal.log.RealmLog; import io.realm.objectid.NullPrimaryKey; import io.realm.rule.RunInLooperThread; @@ -208,7 +209,7 @@ public void getInstance_writeProtectedFile() throws IOException { assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); - thrown.expect(RealmIOException.class); + thrown.expect(IllegalArgumentException.class); Realm.getInstance(new RealmConfiguration.Builder(folder).name(REALM_FILE).build()); } @@ -221,7 +222,7 @@ public void getInstance_writeProtectedFileWithContext() throws IOException { assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); - thrown.expect(RealmIOException.class); + thrown.expect(IllegalArgumentException.class); Realm.getInstance(new RealmConfiguration.Builder(context, folder).name(REALM_FILE).build()); } @@ -509,7 +510,7 @@ public void nestedTransaction() { realm.beginTransaction(); fail(); } catch (IllegalStateException e) { - assertEquals("Nested transactions are not allowed. Use commitTransaction() after each beginTransaction().", e.getMessage()); + assertTrue(e.getMessage().startsWith("The Realm is already in a write transaction")); } realm.commitTransaction(); } @@ -663,13 +664,14 @@ public void cancelTransaction() { @Test public void executeTransaction_null() { + SharedRealm.VersionID oldVersion = realm.sharedRealm.getVersionID(); try { realm.executeTransaction(null); fail("null transaction should throw"); } catch (IllegalArgumentException ignored) { - } - assertFalse(realm.hasChanged()); + SharedRealm.VersionID newVersion = realm.sharedRealm.getVersionID(); + assertEquals(oldVersion, newVersion); } @Test @@ -1845,11 +1847,7 @@ public void writeEncryptedCopyTo() throws Exception { // Write encrypted copy from a unencrypted Realm File destination = new File(encryptedRealmConfig.getPath()); - try { - realm.writeEncryptedCopyTo(destination, encryptedRealmConfig.getEncryptionKey()); - } catch (Exception e) { - fail(e.getMessage()); - } + realm.writeEncryptedCopyTo(destination, encryptedRealmConfig.getEncryptionKey()); Realm encryptedRealm = null; try { @@ -1859,11 +1857,7 @@ public void writeEncryptedCopyTo() throws Exception { assertEquals(TEST_DATA_SIZE, encryptedRealm.where(AllTypes.class).count()); destination = new File(reEncryptedRealmConfig.getPath()); - try { - encryptedRealm.writeEncryptedCopyTo(destination, reEncryptedRealmConfig.getEncryptionKey()); - } catch (Exception e) { - fail(e.getMessage()); - } + encryptedRealm.writeEncryptedCopyTo(destination, reEncryptedRealmConfig.getEncryptionKey()); // Verify re-encrypted copy Realm reEncryptedRealm = null; @@ -1881,11 +1875,7 @@ public void writeEncryptedCopyTo() throws Exception { // Write non-encrypted copy from the encrypted version destination = new File(decryptedRealmConfig.getPath()); - try { - encryptedRealm.writeEncryptedCopyTo(destination, null); - } catch (Exception e) { - fail(e.getMessage()); - } + encryptedRealm.writeEncryptedCopyTo(destination, null); // Verify decrypted Realm and cleanup Realm decryptedRealm = null; @@ -1910,6 +1900,14 @@ public void writeEncryptedCopyTo() throws Exception { } } + @Test + public void writeEncryptedCopyTo_wrongKeyLength() { + byte[] wrongLengthKey = new byte[42]; + File destination = new File(configFactory.getRoot(), "wrong_key.realm"); + thrown.expect(IllegalArgumentException.class); + realm.writeEncryptedCopyTo(destination, wrongLengthKey); + } + @Test public void deleteRealm_failures() { final String OTHER_REALM_NAME = "yetAnotherRealm.realm"; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java index d9e8991f9e..d7c477cd7a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java @@ -18,86 +18,11 @@ import android.test.AndroidTestCase; -import java.io.Closeable; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - import io.realm.RealmFieldType; import io.realm.TestHelper; -// Tables get detached - public class JNICloseTest extends AndroidTestCase { - public void testCloseable() { - - String testFile = new File( - this.getContext().getFilesDir(), - "closeableTest.realm").toString(); - File f = new File(testFile); - if (f.exists()) { - boolean result = f.delete(); - if (!result) { - fail(); - } - } - - List resources = new ArrayList(); - - SharedGroup sg = new SharedGroup(testFile); - resources.add(sg); - - WriteTransaction wt = sg.beginWrite(); - resources.add(wt); - try { - Table t = wt.getTable("test"); - resources.add(t); - t.addColumn(RealmFieldType.STRING, "StringColumn"); - - t.add("abc"); - t.add("cba"); - - wt.commit(); - - } catch(Throwable t) { - wt.rollback(); - } finally { - for (Closeable c : resources) { - try { - c.close(); - } catch(java.io.IOException e) { - e.printStackTrace(); - } - } - } - } - - public void testShouldCloseTable() throws Throwable { - Table table = new Table(); - table.close(); - - try { table.size(); fail("Table is closed"); } catch (IllegalStateException e) { } - try { table.getColumnCount(); fail("Table is closed"); } catch (IllegalStateException e) { } - try { table.addColumn(RealmFieldType.STRING, ""); fail("Table is closed"); } catch (IllegalStateException e) { } - - // TODO: Test all methods... - } - - // TODO: Much more testing needed. - // Verify that methods make exceptions when Tables are invalidated. - // Verify subtables are invalidated when table is changed/updated in any way. - // Check that Group.close works - - public void testShouldCloseGroup() { - - Group group = new Group(); - group.close(); - - try { group.getTable("t"); fail("Group is closed"); } catch (IllegalStateException e) { } - try { group.size(); fail("Group is closed"); } catch (IllegalStateException e) { } - } - /** * Make sure, that it's possible to use the query on a closed table */ @@ -108,8 +33,7 @@ public void testQueryAccessibleAfterTableClose() throws Throwable{ table.setLong(5, i, i); TableQuery query = table.where(); // Closes the table, it _should_ be allowed to access the query thereafter - table.close(); - table = null; + Table.nativeClose(table.nativePtr); Table table2 = TestHelper.getTableWithAllColumnTypes(); table2.addEmptyRows(10); for (int i=0; i tableNames = Arrays.asList( - "ChatList", "Drafts", "Member", "Message", "Notifs", "NotifyLink", "PopularPost", - "Post", "Tags", "Threads", "User"); - - configFactory.copyRealmFromAssets(context, "0841_pk_migration.realm", "default.realm"); - SharedGroup db = new SharedGroup(new File(configFactory.getRoot(), - Realm.DEFAULT_REALM_NAME).getAbsolutePath(), SharedGroup.Durability.FULL, null); - - ImplicitTransaction tr = db.beginImplicitTransaction(); - // To trigger migratePrimaryKeyTableIfNeeded. - tr.getTable("class_ChatList").getPrimaryKey(); - - Table table = tr.getTable("pk"); - for (int i = 0; i < table.size(); i++) { - UncheckedRow row = table.getUncheckedRow(i); - // io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX 0LL - assertTrue(tableNames.contains(row.getString(0))); - } - db.close(); - } - - // Test if toString() returns a correct PrimaryKey field description from a Table - public void testTableToStringWithPrimaryKey() { - Table t = getTableWithStringPrimaryKey(); - t.addColumn(RealmFieldType.INTEGER, "intCol"); - t.addColumn(RealmFieldType.BOOLEAN, "boolCol"); - - t.add("s1", 1, true); - t.add("s2", 2, false); - - String expected = "The Table has 'colName' field as a PrimaryKey, and contains 3 columns: colName, intCol, boolCol. And 2 rows."; - assertEquals(expected, t.toString()); - } -} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java index 6b5aa39009..1ee36c4c23 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java @@ -59,27 +59,4 @@ public void testPivotTable(){ try { t.pivot(colIndexHired, colIndexAge, PivotType.SUM); fail("Group by not a String column"); } catch (UnsupportedOperationException e) { } try { t.pivot(colIndexSex, colIndexHired, PivotType.SUM); fail("Aggregation not an int column"); } catch (UnsupportedOperationException e) { } } - - - public void testPivotTableView(){ - - TableView view = t.getSortedView(colIndexAge); - - Table resultCount = view.pivot(colIndexSex, colIndexAge, PivotType.COUNT); - assertEquals(2, resultCount.size()); - assertEquals(25000, resultCount.getLong(1, 0)); - assertEquals(25000, resultCount.getLong(1, 1)); - - Table resultMin = view.pivot(colIndexSex, colIndexAge, PivotType.MIN); - assertEquals(20, resultMin.getLong(1, 0)); - assertEquals(21, resultMin.getLong(1, 1)); - - Table resultMax = view.pivot(colIndexSex, colIndexAge, PivotType.MAX); - assertEquals(38, resultMax.getLong(1, 0)); - assertEquals(39, resultMax.getLong(1, 1)); - - - try { view.pivot(colIndexHired, colIndexAge, PivotType.SUM); fail("Group by not a String column"); } catch (UnsupportedOperationException e) { } - try { view.pivot(colIndexSex, colIndexHired, PivotType.SUM); fail("Aggregation not an int column"); } catch (UnsupportedOperationException e) { } - } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java new file mode 100644 index 0000000000..2fd93db7ce --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java @@ -0,0 +1,154 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm.internal; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import io.realm.RealmConfiguration; +import io.realm.exceptions.RealmError; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class SharedRealmTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private SharedRealm sharedRealm; + + @Before + public void setUp() { + RealmConfiguration config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); + } + + @After + public void tearDown() { + sharedRealm.close(); + } + + @Test + public void getVersionID() { + SharedRealm.VersionID versionID1 = sharedRealm.getVersionID(); + sharedRealm.beginTransaction(); + sharedRealm.commitTransaction(); + SharedRealm.VersionID versionID2 = sharedRealm.getVersionID(); + assertFalse(versionID1.equals(versionID2)); + } + + @Test + public void hasTable() { + assertFalse(sharedRealm.hasTable("MyTable")); + sharedRealm.beginTransaction(); + sharedRealm.getTable("MyTable"); + sharedRealm.commitTransaction(); + assertTrue(sharedRealm.hasTable("MyTable")); + } + + @Test(expected = IllegalStateException.class) + public void getTable_createNotInTransactionThrows() { + sharedRealm.getTable("NON-EXISTING"); + } + + @Test + public void getTable() { + assertFalse(sharedRealm.hasTable("MyTable")); + sharedRealm.beginTransaction(); + sharedRealm.getTable("MyTable"); + sharedRealm.commitTransaction(); + assertTrue(sharedRealm.hasTable("MyTable")); + + // Table is existing, no need transaction to create it + sharedRealm.getTable("MyTable"); + } + + @Test + public void isInTransaction() { + assertFalse(sharedRealm.isInTransaction()); + sharedRealm.beginTransaction(); + assertTrue(sharedRealm.isInTransaction()); + sharedRealm.cancelTransaction(); + assertFalse(sharedRealm.isInTransaction()); + } + + @Test + public void removeTable() { + sharedRealm.beginTransaction(); + sharedRealm.getTable("TableToRemove"); + assertTrue(sharedRealm.hasTable("TableToRemove")); + sharedRealm.removeTable("TableToRemove"); + assertFalse(sharedRealm.hasTable("TableToRemove")); + sharedRealm.commitTransaction(); + } + + @Test + public void removeTable_notInTransactionThrows() { + sharedRealm.beginTransaction(); + sharedRealm.getTable("TableToRemove"); + sharedRealm.commitTransaction(); + thrown.expect(IllegalStateException.class); + sharedRealm.removeTable("TableToRemove"); + } + + @Test + public void removeTable_tableNotExist() { + sharedRealm.beginTransaction(); + assertFalse(sharedRealm.hasTable("TableToRemove")); + thrown.expect(RealmError.class); + sharedRealm.removeTable("TableToRemove"); + sharedRealm.cancelTransaction(); + } + + @Test + public void renameTable() { + sharedRealm.beginTransaction(); + sharedRealm.getTable("OldTable"); + assertTrue(sharedRealm.hasTable("OldTable")); + sharedRealm.renameTable("OldTable", "NewTable"); + assertFalse(sharedRealm.hasTable("OldTable")); + assertTrue(sharedRealm.hasTable("NewTable")); + sharedRealm.commitTransaction(); + } + + @Test + public void renameTable_notInTransactionThrows() { + sharedRealm.beginTransaction(); + sharedRealm.getTable("OldTable"); + sharedRealm.commitTransaction(); + thrown.expect(IllegalStateException.class); + sharedRealm.renameTable("OldTable", "NewTable"); + } + + @Test + public void renameTable_tableNotExist() { + sharedRealm.beginTransaction(); + assertFalse(sharedRealm.hasTable("TableToRemove")); + thrown.expect(RealmError.class); + sharedRealm.renameTable("TableToRemove", "newName"); + sharedRealm.cancelTransaction(); + } +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 23ee4bf2f9..b4f9210420 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -24,10 +24,9 @@ else() endif() create_javah(TARGET jni_headers - CLASSES io.realm.internal.Table io.realm.internal.TableView - io.realm.internal.CheckedRow io.realm.internal.SharedGroup io.realm.internal.Group + CLASSES io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow - io.realm.internal.TableQuery + io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil CLASSPATH ${classes_PATH} OUTPUT_DIR ${CMAKE_SOURCE_DIR}/jni_include @@ -62,9 +61,9 @@ set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_PAT # build application's shared lib include_directories(${REALM_CORE_DIST_DIR}/include ${CMAKE_SOURCE_DIR} - ${CMAKE_SOURCE_DIR}/jni_include) + ${CMAKE_SOURCE_DIR}/jni_include + ${CMAKE_SOURCE_DIR}/object-store/src) -# Set compile flags set(ANDROID_STL "gnustl_static") if (ARMEABI) @@ -92,7 +91,13 @@ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${REALM_LINKER_FLAGS file(GLOB jni_SRC "*.cpp" ) -add_library(realm-jni SHARED ${jni_SRC}) +file(GLOB objectstore_SRC + "object-store/src/*.cpp" + "object-store/src/impl/*.cpp" + "object-store/src/impl/android/*.cpp" + "object-store/src/util/*.cpp" +) +add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC}) add_dependencies(realm-jni jni_headers) # -latomic is not set by default for mips. See https://code.google.com/p/android/issues/detail?id=182094 target_link_libraries(realm-jni log android atomic lib_realm_core) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Group.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Group.cpp deleted file mode 100644 index f916bae98d..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Group.cpp +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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 - -#include "util.hpp" -#include "io_realm_internal_Group.h" - -using namespace realm; -using std::string; - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_createNative__( - JNIEnv*, jobject) -{ - TR_ENTER() - Group *ptr = new Group(); - TR("Group::createNative(): %p.", VOID_PTR(ptr)) - return reinterpret_cast(ptr); -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_createNative__Ljava_lang_String_2I( - JNIEnv* env, jobject, jstring jFileName, jint mode) -{ - TR_ENTER() - - Group* pGroup = 0; - StringData file_name; - try { - JStringAccessor file_name_tmp(env, jFileName); // throws - file_name = StringData(file_name_tmp); - Group::OpenMode openmode; - switch (mode) { - case 0: openmode = Group::mode_ReadOnly; break; - case 1: openmode = Group::mode_ReadWrite; break; - case 2: openmode = Group::mode_ReadWriteNoCreate; break; - default: - TR("Invalid mode: %d", mode) - ThrowException(env, IllegalArgument, "Group(): Invalid mode parameter."); - return 0; - } - - pGroup = new Group(file_name, NULL, openmode); - - TR("group: %p", VOID_PTR(pGroup)) - return reinterpret_cast(pGroup); - } - CATCH_FILE(file_name) - CATCH_STD() - - // Failed - cleanup - if (pGroup) - delete pGroup; - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_createNative___3B( - JNIEnv* env, jobject, jbyteArray jData) -{ - TR_ENTER() - // Copy the group buffer given - jsize byteArrayLength = env->GetArrayLength(jData); - if (byteArrayLength == 0) - return 0; - jbyte* buf = static_cast(malloc(S(byteArrayLength)*sizeof(jbyte))); - if (!buf) { - ThrowException(env, OutOfMemory, "copying the group buffer."); - return 0; - } - env->GetByteArrayRegion(jData, 0, byteArrayLength, buf); - - TR("%d bytes.", byteArrayLength) - Group* pGroup = 0; - try { - pGroup = new Group(BinaryData(reinterpret_cast(buf), S(byteArrayLength)), true); - TR("groupPtr: %p", VOID_PTR(pGroup)) - return reinterpret_cast(pGroup); - } - CATCH_FILE("memory-buffer") - CATCH_STD() - - // Failed - cleanup - if (buf) - free(buf); - return 0; -} - -// FIXME: Remove this method? It's dangerous to not own the group data... -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_createNative__Ljava_nio_ByteBuffer_2( - JNIEnv* env, jobject, jobject jByteBuffer) -{ - TR_ENTER() - BinaryData bin; - if (!GetBinaryData(env, jByteBuffer, bin)) - return 0; - TR("%" PRId64 " bytes.", S64(bin.size())) - - Group* pGroup = 0; - try { - pGroup = new Group(BinaryData(bin.data(), bin.size()), false); - } - CATCH_FILE("memory-buffer") - CATCH_STD() - - TR("%p", VOID_PTR(pGroup)) - return reinterpret_cast(pGroup); -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeClose( - JNIEnv*, jclass, jlong nativeGroupPtr) -{ - TR_ENTER_PTR(nativeGroupPtr) - delete G(nativeGroupPtr); -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_nativeSize( - JNIEnv*, jobject, jlong nativeGroupPtr) -{ - TR_ENTER_PTR(nativeGroupPtr) - return static_cast( G(nativeGroupPtr)->size() ); // noexcept -} - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Group_nativeHasTable( - JNIEnv* env, jobject, jlong nativeGroupPtr, jstring jTableName) -{ - TR_ENTER_PTR(nativeGroupPtr) - try { - JStringAccessor tableName(env, jTableName); // throws - return G(nativeGroupPtr)->has_table(tableName); - } CATCH_STD() - return false; -} - -JNIEXPORT jstring JNICALL Java_io_realm_internal_Group_nativeGetTableName( - JNIEnv* env, jobject, jlong nativeGroupPtr, jint index) -{ - TR_ENTER_PTR(nativeGroupPtr) - try { - return to_jstring(env, G(nativeGroupPtr)->get_table_name(index)); - } CATCH_STD() - return 0; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeRemoveTable( - JNIEnv* env, jobject, jlong nativeGroupPtr, jstring name) -{ - TR_ENTER_PTR(nativeGroupPtr) - try { - JStringAccessor table_name(env, name); - G(nativeGroupPtr)->remove_table(table_name); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeRenameTable( - JNIEnv* env, jobject, jlong nativeGroupPtr, jstring oldName, jstring newName) -{ - TR_ENTER_PTR(nativeGroupPtr) - try { - JStringAccessor old_name(env, oldName); - JStringAccessor new_name(env, newName); - G(nativeGroupPtr)->rename_table(old_name, new_name); - } CATCH_STD() -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Group_nativeGetTableNativePtr( - JNIEnv *env, jobject, jlong nativeGroupPtr, jstring name) -{ - TR_ENTER_PTR(nativeGroupPtr) - try { - JStringAccessor tableName(env, name); // throws - Table* pTable = LangBindHelper::get_or_add_table(*G(nativeGroupPtr), tableName); - return (jlong)pTable; - } CATCH_STD() - return 0; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeWriteToFile( - JNIEnv* env, jobject, jlong nativeGroupPtr, jstring jFileName, jbyteArray keyArray) -{ - TR_ENTER_PTR(nativeGroupPtr) - StringData file_name; - KeyBuffer key(env, keyArray); - try { - JStringAccessor file_name_tmp(env, jFileName); // throws - file_name = StringData(file_name_tmp); -#ifdef REALM_ENABLE_ENCRYPTION - G(nativeGroupPtr)->write(file_name, key.data()); -#else - G(nativeGroupPtr)->write(file_name); -#endif - } - CATCH_FILE(file_name) - CATCH_STD() -} - -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_Group_nativeWriteToMem( - JNIEnv* env, jobject, jlong nativeGroupPtr) -{ - TR_ENTER_PTR(nativeGroupPtr) - BinaryData buffer; - char* bufPtr = 0; - try { - buffer = G(nativeGroupPtr)->write_to_mem(); // throws - bufPtr = const_cast(buffer.data()); - // Copy the data to Java array, so Java owns it. - jbyteArray jArray = 0; - if (buffer.size() <= MAX_JSIZE) { - jsize jlen = static_cast(buffer.size()); - jArray = env->NewByteArray(jlen); - if (jArray) - // Copy data to Byte[] - env->SetByteArrayRegion(jArray, 0, jlen, reinterpret_cast(bufPtr)); - // SetByteArrayRegion() may throw ArrayIndexOutOfBoundsException - logic error - } - if (!jArray) { - ThrowException(env, IndexOutOfBounds, "Group too big to copy and write."); - } - free(bufPtr); - return jArray; - } - CATCH_STD() - if (bufPtr) - free(bufPtr); - return 0; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Group_nativeCommit( - JNIEnv*, jobject, jlong nativeGroupPtr) -{ - TR_ENTER() - G(nativeGroupPtr)->commit(); -} - - -JNIEXPORT jstring JNICALL Java_io_realm_internal_Group_nativeToJson( - JNIEnv* env, jobject, jlong nativeGroupPtr) -{ - Group* grp = G(nativeGroupPtr); - - try { - // Write group to string in JSON format - std::ostringstream ss; - ss.sync_with_stdio(false); // for performance - grp->to_json(ss); - const std::string str = ss.str(); - return to_jstring(env, str); - } CATCH_STD() - return 0; -} - -JNIEXPORT jstring JNICALL Java_io_realm_internal_Group_nativeToString( - JNIEnv* env, jobject, jlong nativeGroupPtr) -{ - Group* grp = G(nativeGroupPtr); - try { - // Write group to string - std::ostringstream ss; - ss.sync_with_stdio(false); // for performance - grp->to_string(ss); - const std::string str = ss.str(); - return to_jstring(env, str); - } CATCH_STD() - return 0; -} - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Group_nativeIsEmpty( - JNIEnv*, jobject, jlong nativeGroupPtr) -{ - Group* grp = G(nativeGroupPtr); - const size_t table_prefix_length = TABLE_PREFIX.length(); - - for (size_t i = 0; i < grp->size(); ++i) { - ConstTableRef table = grp->get_table(i); - const string table_name = table->get_name(); - if (table_name.compare(0, table_prefix_length, TABLE_PREFIX) == 0 && !table->is_empty()) { - return false; - } - } - return true; -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedGroup.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedGroup.cpp deleted file mode 100644 index 229d9a540e..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedGroup.cpp +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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 - -#include "util.hpp" - -#include -#include -#include - -#include "util.hpp" -#include "io_realm_internal_SharedGroup.h" - -using namespace std; -using namespace realm; - -inline static bool jint_to_durability_level(JNIEnv* env, jint durability, SharedGroup::DurabilityLevel &level) { - if (durability == 0) - level = SharedGroup::durability_Full; - else if (durability == 1) - level = SharedGroup::durability_MemOnly; - else if (durability == 2) -#ifdef _WIN32 - level = SharedGroup::durability_Full; // For Windows, use Full instead of Async -#else - level = SharedGroup::durability_Async; -#endif - else { - ThrowException(env, UnsupportedOperation, "Unsupported durability."); - return false; - } - - return true; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreate( - JNIEnv* env, jobject, jstring jfile_name, jint durability, jboolean no_create, jboolean enable_replication, jbyteArray keyArray) -{ - TR_ENTER() - StringData file_name; - - SharedGroup* db = 0; - try { - JStringAccessor file_name_tmp(env, jfile_name); // throws - file_name = StringData(file_name_tmp); - - if (enable_replication) { -#ifdef REALM_ENABLE_REPLICATION - ThrowException(env, UnsupportedOperation, - "Replication is not currently supported by the Java language binding."); -// db = new SharedGroup(SharedGroup::replication_tag(), *file_name_ptr ? file_name_ptr : 0); -#else - ThrowException(env, UnsupportedOperation, - "Replication was disabled in the native library at compile time."); -#endif - } - else { - SharedGroup::DurabilityLevel level; - // Exception thrown for wrong durability value - if (!jint_to_durability_level(env, durability, level)) { - return 0; - } - - KeyBuffer key(env, keyArray); -#ifdef REALM_ENABLE_ENCRYPTION - db = new SharedGroup(file_name, no_create != 0, level, key.data()); -#else - db = new SharedGroup(file_name, no_create != 0, level); -#endif - } - return reinterpret_cast(db); - } - CATCH_FILE(file_name) - CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_createNativeWithImplicitTransactions - (JNIEnv* env, jobject, jlong native_replication_ptr, jint durability, jbyteArray keyArray) -{ - TR_ENTER() - - SharedGroup::DurabilityLevel level; - // Exception thrown for wrong durability value - if (!jint_to_durability_level(env, durability, level)) { - return 0; - } - - try { - KeyBuffer key(env, keyArray); -#ifdef REALM_ENABLE_ENCRYPTION - SharedGroup* db = new SharedGroup(*CH(native_replication_ptr), level, key.data()); -#else - SharedGroup* db = new SharedGroup(*CH(native_replication_ptr), level); -#endif - return reinterpret_cast(db); - } - CATCH_FILE() - CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeCreateReplication - (JNIEnv* env, jobject, jstring jfile_name, jbyteArray keyArray) -{ - TR_ENTER() - StringData file_name; - try { - JStringAccessor file_name_tmp(env, jfile_name); // throws - file_name = StringData(file_name_tmp); - KeyBuffer key(env, keyArray); -#ifdef REALM_ENABLE_ENCRYPTION - std::unique_ptr hist = make_client_history(file_name, key.data()); -#else - std::unique_ptr hist = make_client_history(file_name); -#endif - return reinterpret_cast(hist.release()); - } - CATCH_FILE(file_name) - CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeBeginImplicit - (JNIEnv* env, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - Group& group = const_cast(SG(native_ptr)->begin_read()); - return reinterpret_cast(&group); - } - CATCH_STD() - return 0; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeAdvanceRead -(JNIEnv *env, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - LangBindHelper::advance_read(*SG(native_ptr)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeAdvanceReadToVersion -(JNIEnv *env, jobject, jlong native_ptr, jlong version, jlong index) -{ - TR_ENTER_PTR(native_ptr) - try { - SharedGroup::VersionID versionId(version, index); - LangBindHelper::advance_read(*SG(native_ptr), versionId); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativePromoteToWrite - (JNIEnv *env, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - LangBindHelper::promote_to_write(*SG(native_ptr)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeCommitAndContinueAsRead - (JNIEnv *env, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - LangBindHelper::commit_and_continue_as_read( *SG(native_ptr) ); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeCloseReplication - (JNIEnv *, jobject, jlong native_replication_ptr) -{ - TR_ENTER_PTR(native_replication_ptr) - delete CH(native_replication_ptr); -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeClose( - JNIEnv*, jclass, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - delete SG(native_ptr); -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeReserve( - JNIEnv *env, jobject, jlong native_ptr, jlong bytes) -{ - TR_ENTER_PTR(native_ptr) - if (bytes <= 0) { - ThrowException(env, UnsupportedOperation, "number of bytes must be > 0."); - return; - } - - try { - SG(native_ptr)->reserve(S(bytes)); - } - CATCH_STD() -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeBeginRead( - JNIEnv* env, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - const Group& group = SG(native_ptr)->begin_read(); - return reinterpret_cast(&group); - } - CATCH_STD() - return 0; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeEndRead( - JNIEnv *, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - SG(native_ptr)->end_read(); // noexcept -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedGroup_nativeBeginWrite( - JNIEnv* env, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - Group& group = SG(native_ptr)->begin_write(); - return reinterpret_cast(&group); - } - CATCH_STD() - return 0; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeCommit( - JNIEnv*, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - SG(native_ptr)->commit(); // noexcept -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeRollback( - JNIEnv*, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - SG(native_ptr)->rollback(); // noexcept -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeRollbackAndContinueAsRead( - JNIEnv *, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - LangBindHelper::rollback_and_continue_as_read(*SG(native_ptr)); -} - - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedGroup_nativeHasChanged - (JNIEnv *, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - return SG(native_ptr)->has_changed(); // noexcept -} - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedGroup_nativeCompact( - JNIEnv* env, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - return SG(native_ptr)->compact(); // throws - } - CATCH_FILE() - CATCH_STD() - return false; -} - -JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedGroup_nativeGetVersionID - (JNIEnv *env, jobject, jlong native_ptr) -{ - TR_ENTER() - SharedGroup::VersionID version_id = SG(native_ptr)->get_version_of_current_transaction(); - - jlong version_array [2]; - version_array[0] = static_cast(version_id.version); - version_array[1] = static_cast(version_id.index); - - jlongArray version_data = env->NewLongArray(2); - if (version_data == NULL) { - ThrowException(env, OutOfMemory, "Could not allocate memory to return versionID."); - return NULL; - } - env->SetLongArrayRegion(version_data, 0, 2, version_array); - - return version_data; -} - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedGroup_nativeWaitForChange - (JNIEnv *env, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - return static_cast(SG(native_ptr)->wait_for_change()); - } CATCH_STD() - return false; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedGroup_nativeStopWaitForChange - (JNIEnv *env, jobject, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - SG(native_ptr)->wait_for_change_release(); - } CATCH_STD() -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp new file mode 100644 index 0000000000..9136decb2f --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -0,0 +1,371 @@ +#include +#include "io_realm_internal_SharedRealm.h" + +#include "shared_realm.hpp" +#include "util.hpp" + +using namespace realm; + +static_assert(SchemaMode::Automatic == + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_AUTOMATIC), ""); +static_assert(SchemaMode::ReadOnly == + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_READONLY), ""); +static_assert(SchemaMode::ResetFile == + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_RESET_FILE), ""); +static_assert(SchemaMode::Additive == + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_ADDITIVE), ""); +static_assert(SchemaMode::Manual == + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_MANUAL), ""); + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstring realm_path, jbyteArray key, + jbyte schema_mode, jboolean in_memory, jboolean cache, jboolean disable_format_upgrade, + jboolean auto_change_notification) +{ + TR_ENTER() + + try { + JStringAccessor path(env, realm_path); // throws + JniByteArray key_array(env, key); + Realm::Config *config = new Realm::Config(); + config->path = path; + config->encryption_key = key_array; + config->schema_mode = static_cast(schema_mode); + config->in_memory = in_memory; + config->cache = cache; + config->disable_format_upgrade = disable_format_upgrade; + config->automatic_change_notifications = auto_change_notification; + return reinterpret_cast(config); + } CATCH_STD() + + return static_cast(NULL); +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv *, jclass, jlong config_ptr) +{ + TR_ENTER_PTR(config_ptr) + + auto config = reinterpret_cast(config_ptr); + delete config; +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr) +{ + TR_ENTER_PTR(config_ptr) + + auto config = reinterpret_cast(config_ptr); + try { + auto shared_realm = Realm::get_shared_realm(*config); + return reinterpret_cast(new SharedRealm(std::move(shared_realm))); + } CATCH_STD() + return static_cast(NULL); +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv *, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto ptr = reinterpret_cast(shared_realm_ptr); + delete ptr; +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + shared_realm->begin_transaction(); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + shared_realm->commit_transaction(); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + shared_realm->cancel_transaction(); + } CATCH_STD() +} + + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv *, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return static_cast(shared_realm->is_in_transaction()); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv *env, jclass , jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + return reinterpret_cast(&shared_realm->read_group()); + } CATCH_STD() + + return static_cast(NULL); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + return static_cast(ObjectStore::get_schema_version(shared_realm->read_group())); + } CATCH_STD() + + // FIXME: Use constant value + return -1; +} + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + return static_cast(ObjectStore::is_empty(shared_realm->read_group())); + } CATCH_STD() + return JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeRefresh__J(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + shared_realm->refresh(); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeRefresh__JJJ(JNIEnv *env, jclass, jlong shared_realm_ptr, jlong version, + jlong index) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + SharedGroup::VersionID version_id(static_cast(version), + static_cast(index)); + try { + using rf = realm::_impl::RealmFriend; + auto& shared_group = rf::get_shared_group(*shared_realm); + LangBindHelper::advance_read(shared_group, version_id); + } CATCH_STD() +} + +JNIEXPORT jlongArray JNICALL +Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + using rf = realm::_impl::RealmFriend; + SharedGroup::VersionID version_id = rf::get_shared_group(*shared_realm).get_version_of_current_transaction(); + + jlong version_array[2]; + version_array[0] = static_cast(version_id.version); + version_array[1] = static_cast(version_id.index); + + jlongArray version_data = env->NewLongArray(2); + if (version_data == NULL) { + ThrowException(env, OutOfMemory, "Could not allocate memory to return versionID."); + return NULL; + } + env->SetLongArrayRegion(version_data, 0, 2, version_array); + + return version_data; + } CATCH_STD () + + return NULL; +} + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv *, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return static_cast(shared_realm->is_closed()); +} + + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) +{ + TR_ENTER_PTR(shared_realm_ptr) + + try { + JStringAccessor name(env, table_name); // throws + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + if (!shared_realm->read_group().has_table(name) && !shared_realm->is_in_transaction()) { + std::ostringstream ss; + ss << "Class " << name << " doesn't exist and the shared Realm is not in transaction."; + ThrowException(env, IllegalState, ss.str()); + return static_cast(NULL); + } + Table* pTable = LangBindHelper::get_or_add_table(shared_realm->read_group(), name); + return reinterpret_cast(pTable); + } CATCH_STD() + + return static_cast(NULL); +} + +JNIEXPORT jstring JNICALL +Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv *env, jclass, jlong shared_realm_ptr, jint index) +{ + + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + return to_jstring(env, shared_realm->read_group().get_table_name(static_cast(index))); + } CATCH_STD() + return NULL; +} + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + JStringAccessor name(env, table_name); + return static_cast(shared_realm->read_group().has_table(name)); + } CATCH_STD() + return JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv *env, jclass, jlong shared_realm_ptr, + jstring old_table_name, jstring new_table_name) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + JStringAccessor old_name(env, old_table_name); + if (!shared_realm->is_in_transaction()) { + std::ostringstream ss; + ss << "Class " << old_name << " cannot be removed when the realm is not in transaction."; + ThrowException(env, IllegalState, ss.str()); + return; + } + JStringAccessor new_name(env, new_table_name); + shared_realm->read_group().rename_table(old_name, new_name); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + JStringAccessor name(env, table_name); + if (!shared_realm->is_in_transaction()) { + std::ostringstream ss; + ss << "Class " << name << " cannot be removed when the realm is not in transaction."; + ThrowException(env, IllegalState, ss.str()); + return; + } + shared_realm->read_group().remove_table(name); + } CATCH_STD() +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + return static_cast(shared_realm->read_group().size()); + } CATCH_STD() + + return 0; +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring path, + jbyteArray key) +{ + TR_ENTER_PTR(shared_realm_ptr); + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + JStringAccessor path_str(env, path); + JniByteArray key_buffer(env, key); + shared_realm->write_copy(path_str, key_buffer); + } CATCH_STD() +} + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr); + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + using rf = realm::_impl::RealmFriend; + return static_cast(rf::get_shared_group(*shared_realm).wait_for_change()); + } CATCH_STD() + + return JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + + TR_ENTER_PTR(shared_realm_ptr); + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + using rf = realm::_impl::RealmFriend; + rf::get_shared_group(*shared_realm).wait_for_change_release(); + } CATCH_STD() +} + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr); + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + return static_cast(shared_realm->compact()); + } CATCH_STD() + + return JNI_FALSE; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index c62da6716e..d74bd51398 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -393,13 +393,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull } CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsRootTable - (JNIEnv *, jobject, jlong nativeTablePtr) -{ - //If the spec is shared, it is a subtable, and this method will return false - return !TBL(nativeTablePtr)->has_shared_type(); -} - JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSize( JNIEnv* env, jobject, jlong nativeTablePtr) { @@ -1272,31 +1265,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetDistinctView( } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedView( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jboolean ascending) -{ - Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) - return 0; - int colType = pTable->get_column_type( S(columnIndex) ); - switch (colType) { - case type_Int: - case type_Bool: - case type_String: - case type_Double: - case type_Float: - case type_Timestamp: - try { - TableView* pTableView = new TableView( pTable->get_sorted_view(S(columnIndex), ascending != 0 ? true : false) ); - return reinterpret_cast(pTableView); - } CATCH_STD() - default: - ThrowException(env, IllegalArgument, "Sort is only support on String, Date, boolean, byte, short, int, long and their boxed variants."); - return 0; - } - return 0; -} - JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti( JNIEnv *env, jobject, jlong nativeTablePtr, jlongArray columnIndices, jbooleanArray ascending) { @@ -1351,16 +1319,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti( return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeOptimize( - JNIEnv* env, jobject, jlong nativeTablePtr) -{ - if (!TABLE_VALID(env, TBL(nativeTablePtr))) - return; - try { - TBL(nativeTablePtr)->optimize(); - } CATCH_STD() -} - JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetName( JNIEnv *env, jobject, jlong nativeTablePtr) { @@ -1537,7 +1495,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTable const size_t CLASS_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX; const size_t FIELD_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX; - Group* group = G(groupNativePtr); + auto group = reinterpret_cast(groupNativePtr); Table* pk_table = TBL(privateKeyTableNativePtr); // Fix wrong types (string, int) -> (string, string) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index b1913cb615..25d6da6a68 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include "util.hpp" #include "io_realm_internal_TableQuery.h" @@ -84,7 +86,7 @@ static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& indicesArray return table_ref; } -static jlong findAllWithHandover(JNIEnv* env, jlong bgSharedGroupPtr, std::unique_ptr query, jlong start, jlong end, jlong limit) +static jlong findAllWithHandover(JNIEnv* env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong start, jlong end, jlong limit) { TR_ENTER() TableRef table = query.get()->get_table(); @@ -96,13 +98,14 @@ static jlong findAllWithHandover(JNIEnv* env, jlong bgSharedGroupPtr, std::uniqu TableView tableView(query->find_all(S(start), S(end), S(limit))); // handover the result - std::unique_ptr> handover = SG( - bgSharedGroupPtr)->export_for_handover(tableView, MutableSourcePayload::Move); + auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); + using rf = realm::_impl::RealmFriend; + auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(tableView, MutableSourcePayload::Move); return reinterpret_cast(handover.release()); } static jlong getDistinctViewWithHandover - (JNIEnv *env, jlong bgSharedGroupPtr, std::unique_ptr query, jlong columnIndex) + (JNIEnv *env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong columnIndex) { TableRef table = query->get_table(); if (!QUERY_VALID(env, query.get()) || @@ -117,8 +120,10 @@ static jlong getDistinctViewWithHandover TableView tableView(table->get_distinct_view(S(columnIndex)) ); // handover the result - std::unique_ptr> handover = SG( - bgSharedGroupPtr)->export_for_handover(tableView, MutableSourcePayload::Move); + auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); + using rf = realm::_impl::RealmFriend; + auto handover = rf::get_shared_group(*sharedRealm).export_for_handover( + tableView, MutableSourcePayload::Move); return reinterpret_cast(handover.release()); } default: @@ -129,7 +134,7 @@ static jlong getDistinctViewWithHandover } static jlong findAllSortedWithHandover - (JNIEnv *env, jlong bgSharedGroupPtr, std::unique_ptr query, jlong start, jlong end, jlong limit, jlong columnIndex, jboolean ascending) + (JNIEnv *env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong start, jlong end, jlong limit, jlong columnIndex, jboolean ascending) { TableRef table = query->get_table(); @@ -161,72 +166,75 @@ static jlong findAllSortedWithHandover } // handover the result - std::unique_ptr > handover = SG(bgSharedGroupPtr)->export_for_handover(tableView, MutableSourcePayload::Move); + auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); + using rf = realm::_impl::RealmFriend; + auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(tableView, MutableSourcePayload::Move); return reinterpret_cast(handover.release()); } static jlong findAllMultiSortedWithHandover - (JNIEnv *env, jlong bgSharedGroupPtr, std::unique_ptr query, jlong start, jlong end, jlong limit, jlongArray columnIndices, jbooleanArray ascending) + (JNIEnv *env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong start, jlong end, jlong limit, jlongArray columnIndices, jbooleanArray ascending) { - JniLongArray long_arr(env, columnIndices); - JniBooleanArray bool_arr(env, ascending); - jsize arr_len = long_arr.len(); - jsize asc_len = bool_arr.len(); + JniLongArray long_arr(env, columnIndices); + JniBooleanArray bool_arr(env, ascending); + jsize arr_len = long_arr.len(); + jsize asc_len = bool_arr.len(); - if (arr_len == 0) { - ThrowException(env, IllegalArgument, "You must provide at least one field name."); - return 0; - } - if (asc_len == 0) { - ThrowException(env, IllegalArgument, "You must provide at least one sort order."); - return 0; - } - if (arr_len != asc_len) { - ThrowException(env, IllegalArgument, "Number of fields and sort orders do not match."); - return 0; - } + if (arr_len == 0) { + ThrowException(env, IllegalArgument, "You must provide at least one field name."); + return 0; + } + if (asc_len == 0) { + ThrowException(env, IllegalArgument, "You must provide at least one sort order."); + return 0; + } + if (arr_len != asc_len) { + ThrowException(env, IllegalArgument, "Number of fields and sort orders do not match."); + return 0; + } - TableRef table = query->get_table(); + TableRef table = query->get_table(); - if (!QUERY_VALID(env, query.get()) || !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) { - return 0; - } + if (!QUERY_VALID(env, query.get()) || !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) { + return 0; + } - // run the query - TableView tableView( query->find_all(S(start), S(end), S(limit)) ); + // run the query + TableView tableView( query->find_all(S(start), S(end), S(limit)) ); - // sorting the results - std::vector> indices; - std::vector ascendings; - for (int i = 0; i < arr_len; ++i) { - if (!COL_INDEX_VALID(env, &tableView, long_arr[i])) { - return -1; - } - int colType = tableView.get_column_type( S(long_arr[i]) ); - switch (colType) { - case type_Bool: - case type_Int: - case type_Float: - case type_Double: - case type_String: - case type_Timestamp: - indices.push_back(std::vector { S(long_arr[i]) }); - ascendings.push_back( B(bool_arr[i]) ); - break; - default: - ThrowException(env, IllegalArgument, ERR_SORT_NOT_SUPPORTED); - return 0; - } + // sorting the results + std::vector> indices; + std::vector ascendings; + for (int i = 0; i < arr_len; ++i) { + if (!COL_INDEX_VALID(env, &tableView, long_arr[i])) { + return -1; } + int colType = tableView.get_column_type( S(long_arr[i]) ); + switch (colType) { + case type_Bool: + case type_Int: + case type_Float: + case type_Double: + case type_String: + case type_Timestamp: + indices.push_back(std::vector { S(long_arr[i]) }); + ascendings.push_back( B(bool_arr[i]) ); + break; + default: + ThrowException(env, IllegalArgument, ERR_SORT_NOT_SUPPORTED); + return 0; + } + } - tableView.sort(SortDescriptor(*table, indices, ascendings)); + tableView.sort(SortDescriptor(*table, indices, ascendings)); - // handover the result - std::unique_ptr > handover = SG(bgSharedGroupPtr)->export_for_handover(tableView, MutableSourcePayload::Move); - return reinterpret_cast(handover.release()); + // handover the result + auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); + using rf = realm::_impl::RealmFriend; + auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(tableView, MutableSourcePayload::Move); + return reinterpret_cast(handover.release()); } - template Query numeric_link_equal(TableRef tbl, jlong columnIndex, javatype value) { return tbl->column(size_t(columnIndex)) == cpptype(value); @@ -1066,9 +1074,9 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFind( return -1; } -// Returns a pointer to query on the worker SharedGroup or throw a BadVersion if the SharedGroup version required +// Returns a pointer to query on the worker SharedRealm or throw a BadVersion if the SharedRealm version required // for the handover is no longer available. -static std::unique_ptr handoverQueryToWorker(jlong bgSharedGroupPtr, jlong queryPtr, bool advanceToLatestVersion) +static std::unique_ptr handoverQueryToWorker(jlong bgSharedRealmPtr, jlong queryPtr, bool advanceToLatestVersion) { SharedGroup::Handover *handoverQueryPtr = HO(Query, queryPtr); std::unique_ptr> handoverQuery(handoverQueryPtr); @@ -1076,19 +1084,13 @@ static std::unique_ptr handoverQueryToWorker(jlong bgSharedGroupPtr, jlon // The Handover object doesn't prevent a SharedGroup version from no longer being accessible. In rare // cases this means that the version in the Handover object is invalid and Realm Core will throw a // BadVersion as result. - realm::SharedGroup* sg = SG(bgSharedGroupPtr); - if (sg->get_transact_stage() != SharedGroup::transact_Reading) { - // if the SharedGroup is not in Read Transaction, we position it at the same version as the handover - sg->begin_read(handoverQuery->version); - } else if (sg->get_version_of_current_transaction() != handoverQuery->version) { - sg->end_read(); - sg->begin_read(handoverQuery->version); - } - - std::unique_ptr query = sg->import_from_handover(std::move(handoverQuery)); + auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); + using rf = realm::_impl::RealmFriend; + rf::read_group_to(*sharedRealm, handoverQuery->version); + auto query = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverQuery)); if (advanceToLatestVersion) { - LangBindHelper::advance_read(*sg); + sharedRealm->refresh(); } return query; @@ -1096,11 +1098,11 @@ static std::unique_ptr handoverQueryToWorker(jlong bgSharedGroupPtr, jlon // queryPtr would be owned and released by this function JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindWithHandover( - JNIEnv* env, jclass, jlong bgSharedGroupPtr, jlong queryPtr, jlong fromTableRow) + JNIEnv* env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong fromTableRow) { TR_ENTER() try { - std::unique_ptr query = handoverQueryToWorker(bgSharedGroupPtr, queryPtr, false); // throws + std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, false); // throws TableRef table = query->get_table(); if (!QUERY_VALID(env, query.get())) { @@ -1120,8 +1122,9 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindWithHandover } else { // handover the result Row row = (*table)[r]; - std::unique_ptr> handover = SG( - bgSharedGroupPtr)->export_for_handover(row); + auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); + using rf = realm::_impl::RealmFriend; + auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(row); return reinterpret_cast(handover.release()); } @@ -1148,12 +1151,12 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll( // queryPtr would be owned and released by this function JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllWithHandover - (JNIEnv* env, jclass, jlong bgSharedGroupPtr, jlong queryPtr, jlong start, jlong end, jlong limit) + (JNIEnv* env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit) { TR_ENTER() try { - std::unique_ptr query = handoverQueryToWorker(bgSharedGroupPtr, queryPtr, true); // throws - return findAllWithHandover(env, bgSharedGroupPtr, std::move(query), start, end, limit); + std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws + return findAllWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit); } CATCH_STD() return 0; } @@ -1165,7 +1168,7 @@ enum query_type {QUERY_TYPE_FIND_ALL = 0, QUERY_TYPE_DISTINCT = 4, QUERY_TYPE_FI // batch update of async queries JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdateQueries - (JNIEnv *env, jclass, jlong bgSharedGroupPtr, + (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlongArray handover_queries_array /*list of handover queries*/, jobjectArray query_param_matrix /*type & params of the query to be updated*/, jobjectArray multi_sorted_indices_matrix, @@ -1188,27 +1191,24 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate // The Handover object doesn't prevent a SharedGroup version from no longer being accessible. In rare // cases this means that the version in the Handover object is invalid and Realm Core will throw a // BadVersion as result. - realm::SharedGroup* sg = SG(bgSharedGroupPtr); - if (sg->get_transact_stage() != SharedGroup::transact_Reading) { - sg->begin_read(handoverQuery->version); - } else if (sg->get_version_of_current_transaction() != handoverQuery->version) { - sg->end_read(); - sg->begin_read(handoverQuery->version); - } + auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); + using rf = realm::_impl::RealmFriend; + rf::read_group_to(*sharedRealm, handoverQuery->version); std::vector> queries(number_of_queries); // import the first query - queries[0] = sg->import_from_handover(std::move(handoverQuery)); + queries[0] = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverQuery)); // import the rest of the queries for (size_t i = 1; i < number_of_queries; ++i) { std::unique_ptr> handoverQuery(HO(Query, handover_queries_pointer_array[i])); - queries[i] = sg->import_from_handover(std::move(handoverQuery)); + using rf = realm::_impl::RealmFriend; + queries[i] = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverQuery)); } // Step2: Bring the queries into the latest shared group version - LangBindHelper::advance_read(*sg); + sharedRealm->refresh(); // Step3: Run & export the queries against the latest shared group for (size_t i = 0; i < number_of_queries; ++i) { @@ -1218,7 +1218,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate exported_handover_tableview_array[i] = findAllWithHandover (env, - bgSharedGroupPtr, + bgSharedRealmPtr, std::move(queries[i]), query_param_array[1]/*start*/, query_param_array[2]/*end*/, @@ -1229,7 +1229,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate exported_handover_tableview_array[i] = getDistinctViewWithHandover (env, - bgSharedGroupPtr, + bgSharedRealmPtr, std::move(queries[i]), query_param_array[1]/*columnIndex*/); break; @@ -1238,7 +1238,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate exported_handover_tableview_array[i] = findAllSortedWithHandover (env, - bgSharedGroupPtr, + bgSharedRealmPtr, std::move(queries[i]), query_param_array[1]/*start*/, query_param_array[2]/*end*/, @@ -1255,7 +1255,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate exported_handover_tableview_array[i] = findAllMultiSortedWithHandover (env, - bgSharedGroupPtr, + bgSharedRealmPtr, std::move(queries[i]), query_param_array[1]/*start*/, query_param_array[2]/*end*/, @@ -1285,35 +1285,35 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetDistinctViewWithHandover - (JNIEnv *env, jclass, jlong bgSharedGroupPtr, jlong queryPtr, jlong columnIndex) + (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong columnIndex) { TR_ENTER() try { - std::unique_ptr query = handoverQueryToWorker(bgSharedGroupPtr, queryPtr, true); // throws - return getDistinctViewWithHandover(env, bgSharedGroupPtr, std::move(query), columnIndex); + std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws + return getDistinctViewWithHandover(env, bgSharedRealmPtr, std::move(query), columnIndex); } CATCH_STD() return 0; } JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllSortedWithHandover - (JNIEnv *env, jclass, jlong bgSharedGroupPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlong columnIndex, jboolean ascending) + (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlong columnIndex, jboolean ascending) { TR_ENTER() try { - std::unique_ptr query = handoverQueryToWorker(bgSharedGroupPtr, queryPtr, true); // throws - return findAllSortedWithHandover(env, bgSharedGroupPtr, std::move(query), start, end, limit, columnIndex, ascending); + std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws + return findAllSortedWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit, columnIndex, ascending); } CATCH_STD() return 0; } JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllMultiSortedWithHandover - (JNIEnv *env, jclass, jlong bgSharedGroupPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlongArray columnIndices, jbooleanArray ascending) + (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlongArray columnIndices, jbooleanArray ascending) { TR_ENTER() try { - // import the handover query pointer using the background SharedGroup - std::unique_ptr query = handoverQueryToWorker(bgSharedGroupPtr, queryPtr, true); // throws - return findAllMultiSortedWithHandover(env, bgSharedGroupPtr, std::move(query), start, end, limit,columnIndices, ascending); + // import the handover query pointer using the background SharedRealm + std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws + return findAllMultiSortedWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit,columnIndices, ascending); } CATCH_STD() return 0; } @@ -1714,9 +1714,10 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverTa std::unique_ptr> handoverTableView(handoverTableViewPtr); try { // import_from_handover will free (delete) the handover - if (SG(callerSharedGrpPtr)->is_attached()) { - std::unique_ptr tableView = SG(callerSharedGrpPtr)->import_from_handover( - std::move(handoverTableView)); + auto sharedRealm = *(reinterpret_cast(callerSharedGrpPtr)); + if (!sharedRealm->is_closed()) { + using rf = realm::_impl::RealmFriend; + auto tableView = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverTableView)); return reinterpret_cast(tableView.release()); } else { ThrowException(env, RuntimeError, ERR_IMPORT_CLOSED_REALM); @@ -1734,9 +1735,10 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRo try { // import_from_handover will free (delete) the handover - if (SG(callerSharedGrpPtr)->is_attached()) { - std::unique_ptr row = SG(callerSharedGrpPtr)->import_from_handover( - std::move(handoverRow)); + auto sharedRealm = *(reinterpret_cast(callerSharedGrpPtr)); + if (!sharedRealm->is_closed()) { + using rf = realm::_impl::RealmFriend; + auto row = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverRow)); return reinterpret_cast(row.release()); } else { ThrowException(env, RuntimeError, ERR_IMPORT_CLOSED_REALM); @@ -1746,15 +1748,17 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRo } JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery - (JNIEnv* env, jobject, jlong bgSharedGroupPtr, jlong nativeQueryPtr) + (JNIEnv* env, jobject, jlong bgSharedRealmPtr, jlong nativeQueryPtr) { TR_ENTER_PTR(nativeQueryPtr) Query* pQuery = Q(nativeQueryPtr); if (!QUERY_VALID(env, pQuery)) return 0; try { - std::unique_ptr > handoverQueryPtr = SG(bgSharedGroupPtr)->export_for_handover(*pQuery, ConstSourcePayload::Copy); - return reinterpret_cast(handoverQueryPtr.release()); + auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); + using rf = realm::_impl::RealmFriend; + auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(*pQuery, ConstSourcePayload::Copy); + return reinterpret_cast(handover.release()); } CATCH_STD() return 0; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp new file mode 100644 index 0000000000..d14615f5b6 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp @@ -0,0 +1,127 @@ +#include "io_realm_internal_TestUtil.h" +#include "util.hpp" + +static jstring throwOrGetExpectedMessage(JNIEnv *env, jlong testcase, bool should_throw); + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_TestUtil_getMaxExceptionNumber(JNIEnv *, jclass) +{ + return ExceptionKindMax; +} + +JNIEXPORT jstring JNICALL +Java_io_realm_internal_TestUtil_getExpectedMessage(JNIEnv *env, jclass, jlong exception_kind) +{ + return throwOrGetExpectedMessage(env, exception_kind, false); +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_TestUtil_testThrowExceptions(JNIEnv *env, jclass, jlong exception_kind) +{ + throwOrGetExpectedMessage(env, exception_kind, true); +} + +static jstring +throwOrGetExpectedMessage(JNIEnv *env, jlong testcase, bool should_throw) +{ + std::string expect; + + switch (ExceptionKind(testcase)) { + case ClassNotFound: + expect = "java.lang.ClassNotFoundException: Class 'parm1' could not be located."; + if (should_throw) + ThrowException(env, ClassNotFound, "parm1", "parm2"); + break; + case NoSuchField: + expect = "java.lang.NoSuchFieldException: Field 'parm2' could not be located in class io.realm.parm1"; + if (should_throw) + ThrowException(env, NoSuchField, "parm1", "parm2"); + break; + case NoSuchMethod: + expect = "java.lang.NoSuchMethodException: Method 'parm2' could not be located in class io.realm.parm1"; + if (should_throw) + ThrowException(env, NoSuchMethod, "parm1", "parm2"); + break; + case IllegalArgument: + expect = "java.lang.IllegalArgumentException: Illegal Argument: parm1"; + if (should_throw) + ThrowException(env, IllegalArgument, "parm1", "parm2"); + break; + case IOFailed: + expect = "io.realm.exceptions.RealmIOException: Failed to open parm1. parm2"; + if (should_throw) + ThrowException(env, IOFailed, "parm1", "parm2"); + break; + case FileNotFound: + expect = "io.realm.exceptions.RealmIOException: File not found: parm1."; + if (should_throw) + ThrowException(env, FileNotFound, "parm1", "parm2"); + break; + case FileAccessError: + expect = "io.realm.exceptions.RealmIOException: Failed to access: parm1. parm2"; + if (should_throw) + ThrowException(env, FileAccessError, "parm1", "parm2"); + break; + case IndexOutOfBounds: + expect = "java.lang.ArrayIndexOutOfBoundsException: parm1"; + if (should_throw) + ThrowException(env, IndexOutOfBounds, "parm1", "parm2"); + break; + case TableInvalid: + expect = "java.lang.IllegalStateException: Illegal State: parm1"; + if (should_throw) + ThrowException(env, TableInvalid, "parm1", "parm2"); + break; + case UnsupportedOperation: + expect = "java.lang.UnsupportedOperationException: parm1"; + if (should_throw) + ThrowException(env, UnsupportedOperation, "parm1", "parm2"); + break; + case OutOfMemory: + expect = "io.realm.internal.OutOfMemoryError: parm1 parm2"; + if (should_throw) + ThrowException(env, OutOfMemory, "parm1", "parm2"); + break; + case FatalError: + expect = "io.realm.exceptions.RealmError: Unrecoverable error. parm1"; + if (should_throw) + ThrowException(env, FatalError, "parm1", "parm2"); + break; + case RuntimeError: + expect = "java.lang.RuntimeException: parm1"; + if (should_throw) + ThrowException(env, RuntimeError, "parm1", "parm2"); + break; + case RowInvalid: + expect = "java.lang.IllegalStateException: Illegal State: parm1"; + if (should_throw) + ThrowException(env, RowInvalid, "parm1", "parm2"); + break; + case CrossTableLink: + expect = "java.lang.IllegalStateException: This class is referenced by other classes. Remove those fields first before removing this class."; + if (should_throw) + ThrowException(env, CrossTableLink, "parm1"); + break; + case BadVersion: + expect = "io.realm.internal.async.BadVersionException: parm1"; + if (should_throw) + ThrowException(env, BadVersion, "parm1", "parm2"); + break; + case LockFileError: + expect = "io.realm.exceptions.IncompatibleLockFileException: parm1"; + if (should_throw) + ThrowException(env, LockFileError, "parm1", "parm2"); + break; + case IllegalState: + expect = "java.lang.IllegalStateException: parm1"; + if (should_throw) + ThrowException(env, IllegalState, "parm1"); + break; + default: + break; + } + if (should_throw) { + return NULL; + } + return to_jstring(env, expect); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 49eb55f255..88b09a9cb9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -85,106 +85,3 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeGetTablePrefix( realm::StringData sd(TABLE_PREFIX); return to_jstring(env, sd); } - -// -------------------------- Testcases for exception handling - -JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeTestcase( - JNIEnv *env, jclass, jint testcase, jboolean dotest, jlong) -{ - string expect; - - switch (ExceptionKind(testcase)) { - case ClassNotFound: - expect = "java.lang.ClassNotFoundException: Class 'parm1' could not be located."; - if (dotest) - ThrowException(env, ClassNotFound, "parm1", "parm2"); - break; - case NoSuchField: - expect = "java.lang.NoSuchFieldException: Field 'parm2' could not be located in class io.realm.parm1"; - if (dotest) - ThrowException(env, NoSuchField, "parm1", "parm2"); - break; - case NoSuchMethod: - expect = "java.lang.NoSuchMethodException: Method 'parm2' could not be located in class io.realm.parm1"; - if (dotest) - ThrowException(env, NoSuchMethod, "parm1", "parm2"); - break; - case IllegalArgument: - expect = "java.lang.IllegalArgumentException: Illegal Argument: parm1"; - if (dotest) - ThrowException(env, IllegalArgument, "parm1", "parm2"); - break; - case IOFailed: - expect = "io.realm.exceptions.RealmIOException: Failed to open parm1. parm2"; - if (dotest) - ThrowException(env, IOFailed, "parm1", "parm2"); - break; - case FileNotFound: - expect = "io.realm.exceptions.RealmIOException: File not found: parm1."; - if (dotest) - ThrowException(env, FileNotFound, "parm1", "parm2"); - break; - case FileAccessError: - expect = "io.realm.exceptions.RealmIOException: Failed to access: parm1. parm2"; - if (dotest) - ThrowException(env, FileAccessError, "parm1", "parm2"); - break; - case IndexOutOfBounds: - expect = "java.lang.ArrayIndexOutOfBoundsException: parm1"; - if (dotest) - ThrowException(env, IndexOutOfBounds, "parm1", "parm2"); - break; - case TableInvalid: - expect = "java.lang.IllegalStateException: Illegal State: parm1"; - if (dotest) - ThrowException(env, TableInvalid, "parm1", "parm2"); - break; - case UnsupportedOperation: - expect = "java.lang.UnsupportedOperationException: parm1"; - if (dotest) - ThrowException(env, UnsupportedOperation, "parm1", "parm2"); - break; - case OutOfMemory: - expect = "io.realm.internal.OutOfMemoryError: parm1 parm2"; - if (dotest) - ThrowException(env, OutOfMemory, "parm1", "parm2"); - break; - case FatalError: - expect = "io.realm.exceptions.RealmError: Unrecoverable error. parm1"; - if (dotest) - ThrowException(env, FatalError, "parm1", "parm2"); - break; - case RuntimeError: - expect = "java.lang.RuntimeException: parm1"; - if (dotest) - ThrowException(env, RuntimeError, "parm1", "parm2"); - break; - case RowInvalid: - expect = "java.lang.IllegalStateException: Illegal State: parm1"; - if (dotest) - ThrowException(env, RowInvalid, "parm1", "parm2"); - break; - case CrossTableLink: - expect = "java.lang.IllegalStateException: This class is referenced by other classes. Remove those fields first before removing this class."; - if (dotest) - ThrowException(env, CrossTableLink, "parm1"); - break; - case BadVersion: - expect = "io.realm.internal.async.BadVersionException: parm1"; - if (dotest) - ThrowException(env, BadVersion, "parm1", "parm2"); - break; - case LockFileError: - expect = "io.realm.exceptions.IncompatibleLockFileException: parm1"; - if (dotest) - ThrowException(env, LockFileError, "parm1", "parm2"); - break; - - - } - if (dotest) { - return NULL; - } - return to_jstring(env, expect); -} - diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store new file mode 160000 index 0000000000..4663837974 --- /dev/null +++ b/realm/realm-library/src/main/cpp/object-store @@ -0,0 +1 @@ +Subproject commit 4663837974a46fa2b34f85362b5c86b5a1d3437a diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 6d8a7dd58a..b5af9f0d4b 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -23,6 +23,7 @@ #include "util.hpp" #include "io_realm_internal_Util.h" +#include "shared_realm.hpp" using namespace std; using namespace realm; @@ -58,8 +59,16 @@ void ConvertException(JNIEnv* env, const char *file, int line) ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalArgument, ss.str()); } - catch (File::AccessError& e) { - ss << e.what() << " path: " << e.get_path() << " in " << file << " line " << line; + catch (RealmFileException& e) { + ss << e.what() << " in " << file << " line " << line; + ThrowException(env, IllegalArgument, ss.str()); + } + catch (InvalidTransactionException& e) { + ss << e.what() << " in " << file << " line " << line; + ThrowException(env, IllegalState, ss.str()); + } + catch (InvalidEncryptionKeyException& e) { + ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalArgument, ss.str()); } catch (exception& e) { @@ -167,6 +176,14 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla message = classStr; break; + case IllegalState: + jExceptionClass = env->FindClass("java/lang/IllegalStateException"); + message = classStr; + break; + // Should never get here. + case ExceptionKindMax: + default: + break; } if (jExceptionClass != NULL) { env->ThrowNew(jExceptionClass, message.c_str()); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 4f0a6e01a3..8213a63b76 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -56,29 +56,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved); #define STRINGIZE(x) STRINGIZE_DETAIL(x) // Exception handling - -#define CATCH_FILE(fileName) \ - catch (InvalidDatabase&) { \ - ThrowException(env, IllegalArgument, "Invalid format of Realm file."); \ - } \ - catch (util::File::PermissionDenied& e) { \ - ThrowException(env, IOFailed, string(fileName), \ - std::string(e.what()) + " path: " + e.get_path()); \ - } \ - catch (util::File::NotFound& e) { \ - ThrowException(env, FileNotFound, string(fileName), \ - std::string(e.what()) + " path: " + e.get_path()); \ - } \ - catch (util::File::AccessError& e) { \ - ThrowException(env, FileAccessError, string(fileName), \ - std::string(e.what()) + " path: " + e.get_path()); \ - } \ - catch (realm::IncompatibleLockFile& e) { \ - ThrowException(env, LockFileError, std::string(e.what())); \ - } \ - - - #define CATCH_STD() \ catch (...) { \ ConvertException(env, __FILE__, __LINE__); \ @@ -93,8 +70,6 @@ std::string num_to_string(T pNumber) } -#define MAX_JLONG 0x7FFFFFFFFFFFFFFFLL -#define MIN_JLONG -0x8000000000000000LL #define MAX_JINT 0x7FFFFFFFL #define MAX_JSIZE MAX_JINT @@ -107,32 +82,33 @@ std::string num_to_string(T pNumber) #define TV(x) reinterpret_cast(x) #define LV(x) reinterpret_cast(x) #define Q(x) reinterpret_cast(x) -#define G(x) reinterpret_cast(x) #define ROW(x) reinterpret_cast(x) -#define SG(ptr) reinterpret_cast(ptr) -#define CH(ptr) reinterpret_cast(ptr) #define HO(T, ptr) reinterpret_cast* >(ptr) // Exception handling +// FIXME: RowInvalid and IllegalState both throw IllegalStateException, maybe remove the RowInvalid. enum ExceptionKind { ClassNotFound = 0, - NoSuchField = 1, - NoSuchMethod = 2, - IllegalArgument = 3, - IOFailed = 4, - FileNotFound = 5, - FileAccessError = 6, - IndexOutOfBounds = 7, - TableInvalid = 8, - UnsupportedOperation = 9, - OutOfMemory = 10, - FatalError = 11, - RuntimeError = 12, - RowInvalid = 13, - CrossTableLink = 15, - BadVersion = 16, - LockFileError = 17 -// NOTE!!!!: Please also add test cases to Util.java when introducing a new exception kind. + NoSuchField, + NoSuchMethod, + IllegalArgument, + IOFailed, + FileNotFound, + FileAccessError, + IndexOutOfBounds, + TableInvalid, + UnsupportedOperation, + OutOfMemory, + FatalError, + RuntimeError, + RowInvalid, + CrossTableLink, + BadVersion, + LockFileError, + IllegalState, + // NOTE!!!!: Please also add test cases to io_realm_internal_TestUtil when introducing a + // new exception kind. + ExceptionKindMax // Always keep this as the last one! }; void ConvertException(JNIEnv* env, const char *file, int line); @@ -520,45 +496,18 @@ class JStringAccessor { } } -private: - bool m_is_null; - std::unique_ptr m_data; - std::size_t m_size; -}; - -class KeyBuffer { -public: - KeyBuffer(JNIEnv* env, jbyteArray arr) - : m_env(env) - , m_array(arr) - , m_ptr(0) + operator std::string() const noexcept { -#ifdef REALM_ENABLE_ENCRYPTION - if (arr) { - if (env->GetArrayLength(m_array) != 64) - ThrowException(env, UnsupportedOperation, "Encryption key must be exactly 64 bytes."); - m_ptr = env->GetByteArrayElements(m_array, NULL); + if (m_is_null) { + return std::string(); } -#else - if (arr) - ThrowException(env, UnsupportedOperation, - "Encryption was disabled in the native library at compile time."); -#endif - } - - const char *data() const { - return reinterpret_cast(m_ptr); - } - - ~KeyBuffer() { - if (m_ptr) - m_env->ReleaseByteArrayElements(m_array, m_ptr, JNI_ABORT); + return std::string(m_data.get(), m_size); } private: - JNIEnv* m_env; - jbyteArray m_array; - jbyte* m_ptr; + bool m_is_null; + std::unique_ptr m_data; + std::size_t m_size; }; class JniLongArray { @@ -638,6 +587,20 @@ class JniByteArray { return m_array[index]; } + inline operator realm::BinaryData() const noexcept { + return realm::BinaryData(reinterpret_cast(m_array), m_arrayLength); + } + + inline operator std::vector() const noexcept { + if (m_array == nullptr) { + return {}; + } + + std::vector v(m_arrayLength); + std::copy_n(m_array, v.size(), v.begin()); + return v; + } + inline void updateOnRelease() noexcept { m_releaseMode = 0; diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index e1775d8dd6..26b9723fdf 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -35,7 +35,7 @@ import io.realm.internal.HandlerControllerConstants; import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; -import io.realm.internal.SharedGroupManager; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.android.DebugAndroidLogger; @@ -70,7 +70,8 @@ abstract class BaseRealm implements Closeable { final long threadId; protected RealmConfiguration configuration; - protected SharedGroupManager sharedGroupManager; + protected SharedRealm sharedRealm; + RealmSchema schema; Handler handler; HandlerController handlerController; @@ -83,8 +84,9 @@ abstract class BaseRealm implements Closeable { protected BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; - this.sharedGroupManager = new SharedGroupManager(configuration); - this.schema = new RealmSchema(this, sharedGroupManager.getTransaction()); + + this.sharedRealm = SharedRealm.getInstance(configuration); + this.schema = new RealmSchema(this); this.handlerController = new HandlerController(this); if (handlerController.isAutoRefreshAvailable()) { @@ -131,7 +133,7 @@ public boolean isAutoRefresh() { */ public boolean isInTransaction() { checkIfValid(); - return !sharedGroupManager.isImmutable(); + return sharedRealm.isInTransaction(); } protected void addListener(RealmChangeListener listener) { @@ -226,9 +228,8 @@ protected void removeHandler() { * the last transaction was committed. * * @param destination file to save the Realm to. - * @throws java.io.IOException if any write operation fails. */ - public void writeCopyTo(File destination) throws java.io.IOException { + public void writeCopyTo(File destination) { writeEncryptedCopyTo(destination, null); } @@ -243,15 +244,14 @@ public void writeCopyTo(File destination) throws java.io.IOException { * * @param destination file to save the Realm to. * @param key a 64-byte encryption key. - * @throws java.io.IOException if any write operation fails. * @throws IllegalArgumentException if destination argument is null. */ - public void writeEncryptedCopyTo(File destination, byte[] key) throws java.io.IOException { + public void writeEncryptedCopyTo(File destination, byte[] key) { if (destination == null) { throw new IllegalArgumentException("The destination argument cannot be null"); } checkIfValid(); - sharedGroupManager.copyToFile(destination, key); + sharedRealm.writeCopy(destination, key); } /** @@ -271,10 +271,10 @@ public boolean waitForChange() { if (Looper.myLooper() != null) { throw new IllegalStateException("Cannot wait for changes inside a Looper thread. Use RealmChangeListeners instead."); } - boolean hasChanged = sharedGroupManager.getSharedGroup().waitForChange(); + boolean hasChanged = sharedRealm.waitForChange(); if (hasChanged) { // Since this Realm instance has been waiting for change, advance realm & refresh realm. - sharedGroupManager.advanceRead(); + sharedRealm.refresh(); handlerController.refreshSynchronousTableViews(); } return hasChanged; @@ -294,10 +294,10 @@ public void stopWaitForChange() { @Override public void onCall() { // Check if the Realm instance has been closed - if (sharedGroupManager == null || !sharedGroupManager.isOpen() || sharedGroupManager.getSharedGroup().isClosed()) { + if (sharedRealm == null || sharedRealm.isClosed()) { throw new IllegalStateException(BaseRealm.CLOSED_REALM_MESSAGE); } - sharedGroupManager.getSharedGroup().stopWaitForChange(); + sharedRealm.stopWaitForChange(); } }); } @@ -334,7 +334,7 @@ public void onCall() { */ public void beginTransaction() { checkIfValid(); - sharedGroupManager.promoteToWrite(); + sharedRealm.beginTransaction(); } /** @@ -364,7 +364,7 @@ void commitAsyncTransaction() { */ void commitTransaction(boolean notifyLocalThread, boolean notifyOtherThreads) { checkIfValid(); - sharedGroupManager.commitAndContinueAsRead(); + sharedRealm.commitTransaction(); for (Map.Entry handlerIntegerEntry : handlers.entrySet()) { Handler handler = handlerIntegerEntry.getKey(); @@ -432,15 +432,14 @@ void commitTransaction(boolean notifyLocalThread, boolean notifyOtherThreads) { */ public void cancelTransaction() { checkIfValid(); - sharedGroupManager.rollbackAndContinueAsRead(); + sharedRealm.cancelTransaction(); } /** * Checks if a Realm's underlying resources are still available or not getting accessed from the wrong thread. */ protected void checkIfValid() { - // Check if the Realm instance has been closed - if (sharedGroupManager == null || !sharedGroupManager.isOpen()) { + if (sharedRealm == null || sharedRealm.isClosed()) { throw new IllegalStateException(BaseRealm.CLOSED_REALM_MESSAGE); } @@ -450,6 +449,12 @@ protected void checkIfValid() { } } + protected void checkIfInTransaction() { + if (!sharedRealm.isInTransaction()) { + throw new IllegalStateException("Changing Realm data can only be done from inside a transaction."); + } + } + /** * Check if the Realm is valid and in a transaction. */ @@ -484,11 +489,7 @@ public RealmConfiguration getConfiguration() { * @return the schema version for the Realm file backing this Realm. */ public long getVersion() { - if (!sharedGroupManager.hasTable(Table.METADATA_TABLE_NAME)) { - return UNVERSIONED; - } - Table metadataTable = sharedGroupManager.getTable(Table.METADATA_TABLE_NAME); - return metadataTable.getLong(0, 0); + return sharedRealm.getSchemaVersion(); } /** @@ -512,9 +513,9 @@ public void close() { * Closes the Realm instances and all its resources without checking the {@link RealmCache}. */ void doClose() { - if (sharedGroupManager != null) { - sharedGroupManager.close(); - sharedGroupManager = null; + if (sharedRealm != null) { + sharedRealm.close(); + sharedRealm = null; } if (handler != null) { removeHandler(); @@ -532,7 +533,7 @@ public boolean isClosed() { throw new IllegalStateException(INCORRECT_THREAD_MESSAGE); } - return sharedGroupManager == null || !sharedGroupManager.isOpen(); + return sharedRealm == null || sharedRealm.isClosed(); } /** @@ -542,16 +543,12 @@ public boolean isClosed() { */ public boolean isEmpty() { checkIfValid(); - return sharedGroupManager.getTransaction().isObjectTablesEmpty(); - } - - boolean hasChanged() { - return sharedGroupManager.hasChanged(); + return sharedRealm.isEmpty(); } // package protected so unit tests can access it void setVersion(long version) { - Table metadataTable = sharedGroupManager.getTable(Table.METADATA_TABLE_NAME); + Table metadataTable = sharedRealm.getTable(Table.METADATA_TABLE_NAME); if (metadataTable.getColumnCount() == 0) { metadataTable.addColumn(RealmFieldType.INTEGER, "version"); metadataTable.addEmptyRow(); @@ -693,11 +690,14 @@ public void onResult(int count) { * @return {@code true} if compaction succeeded, {@code false} otherwise. */ static boolean compactRealm(final RealmConfiguration configuration) { + // FIXME: Move this check to OS? if (configuration.getEncryptionKey() != null) { throw new IllegalArgumentException("Cannot currently compact an encrypted Realm."); } - - return SharedGroupManager.compact(configuration); + SharedRealm sharedRealm = SharedRealm.getInstance(configuration); + Boolean result = sharedRealm.compact(); + sharedRealm.close(); + return result; } /** @@ -764,7 +764,7 @@ public void onResult(int count) { @Override protected void finalize() throws Throwable { - if (sharedGroupManager != null && sharedGroupManager.isOpen()) { + if (sharedRealm != null && !sharedRealm.isClosed()) { RealmLog.w("Remember to call close() on all Realm instances. " + "Realm " + configuration.getPath() + " is being finalized without being closed, " + "this can lead to running out of native memory." diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 474e070020..ec9d324e98 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -109,7 +109,7 @@ public DynamicRealmObject createObject(String className, Object primaryKeyValue) */ public RealmQuery where(String className) { checkIfValid(); - if (!sharedGroupManager.hasTable(Table.TABLE_PREFIX + className)) { + if (!sharedRealm.hasTable(Table.TABLE_PREFIX + className)) { throw new IllegalArgumentException("Class does not exist in the Realm and cannot be queried: " + className); } return RealmQuery.createDynamicQuery(this, className); @@ -144,6 +144,7 @@ public void addChangeListener(RealmChangeListener listener) { */ public void delete(String className) { checkIfValid(); + checkIfInTransaction(); schema.getTable(className).clear(); } diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java index 0663ba24d9..58733f306e 100644 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java @@ -37,7 +37,7 @@ import io.realm.internal.IdentitySet; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SharedGroup; +import io.realm.internal.SharedRealm; import io.realm.internal.async.BadVersionException; import io.realm.internal.async.QueryUpdateTask; import io.realm.internal.log.RealmLog; @@ -108,8 +108,8 @@ public boolean handleMessage(Message message) { // aware when this threads handler is removed before they send messages to it. We don't wish to synchronize // access to the handlers as they are the prime mean of notifying about updates. Instead we make sure // that if a message does slip though (however unlikely), it will not try to update a SharedGroup that no - // longer exists. `sharedGroupManager` will only be null if a Realm is really closed. - if (realm.sharedGroupManager != null) { + // longer exists. `sharedRealm` will only be null if a Realm is really closed. + if (realm.sharedRealm != null) { QueryUpdateTask.Result result; switch (message.what) { @@ -157,7 +157,7 @@ public boolean handleMessage(Message message) { */ public void handleAsyncTransactionCompleted(Runnable onSuccess) { // Same reason as handleMessage() - if (realm.sharedGroupManager != null) { + if (realm.sharedRealm != null) { if (onSuccess != null) { pendingOnSuccessAsyncTransactionCallbacks.add(onSuccess); } @@ -442,7 +442,7 @@ private void realmChanged(boolean localCommit) { // localCommit && threadContainsAsyncQueries (this is the case the warning above is about) // localCommit && !threadContainsAsyncQueries // !localCommit && !threadContainsAsyncQueries - realm.sharedGroupManager.advanceRead(); + realm.sharedRealm.refresh(); List> resultsToBeNotified = new ArrayList>(); collectAsyncRealmResultsCallbacks(resultsToBeNotified); @@ -462,7 +462,7 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) { RealmLog.d("[COMPLETED_ASYNC_REALM_RESULTS "+ weakRealmResults + "] realm:"+ HandlerController.this + " RealmResults GC'd ignore results"); } else { - SharedGroup.VersionID callerVersionID = realm.sharedGroupManager.getVersion(); + SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID(); int compare = callerVersionID.compareTo(result.versionID); if (compare == 0) { // if the RealmResults is empty (has not completed yet) then use the value @@ -520,7 +520,7 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) { } private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) { - SharedGroup.VersionID callerVersionID = realm.sharedGroupManager.getVersion(); + SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID(); int compare = callerVersionID.compareTo(result.versionID); if (compare > 0) { // if the caller thread is more advanced than the worker thread, it means it did a local commit. @@ -542,7 +542,7 @@ private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) { // (advanceRead to the latest version may cause a version mismatch error) preventing us // from importing correctly the handover table view try { - realm.sharedGroupManager.advanceRead(result.versionID); + realm.sharedRealm.refresh(result.versionID); } catch (BadVersionException e) { // The version comparison above should have ensured that that the Caller version is less than the // Worker version. In that case it should always be safe to advance_read. @@ -601,7 +601,7 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) { RealmObjectProxy proxy = realmObjectWeakReference.get(); if (proxy != null) { - SharedGroup.VersionID callerVersionID = realm.sharedGroupManager.getVersion(); + SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID(); int compare = callerVersionID.compareTo(result.versionID); // we always query on the same version // only two use cases could happen 1. we're on the same version or 2. the caller has advanced in the meanwhile diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 96049817d8..73f8e5f405 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -132,7 +132,7 @@ public ProxyState(Class clazzName, E model) { } else if (!isCompleted || row == Row.EMPTY_ROW) { isCompleted = true; - long nativeRowPointer = TableQuery.nativeImportHandoverRowIntoSharedGroup(handoverRowPointer, realm.sharedGroupManager.getNativePointer()); + long nativeRowPointer = TableQuery.importHandoverRow(handoverRowPointer, realm.sharedRealm); Table table = getTable(); this.row = table.getUncheckedRowByPointer(nativeRowPointer); }// else: already loaded query no need to import again the pointer diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 1cfe512f1d..c338db9db7 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -275,9 +275,9 @@ private static void initializeRealm(Realm realm) { for (Class modelClass : modelClasses) { // Create and validate table if (version == UNVERSIONED) { - mediator.createTable(modelClass, realm.sharedGroupManager.getTransaction()); + mediator.createTable(modelClass, realm.sharedRealm); } - columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedGroupManager.getTransaction())); + columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm)); } realm.schema.columnIndices = new ColumnIndices(columnInfoMap); @@ -1168,7 +1168,7 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, fin " and you provided a callback, we need a Handler to invoke your callback"); } - // We need to use the same configuration to open a background SharedGroup (i.e Realm) + // We need to use the same configuration to open a background SharedRealm (i.e Realm) // to perform the transaction final RealmConfiguration realmConfiguration = getConfiguration(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 4f7a8c031a..23df1a6274 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -34,7 +34,7 @@ import io.realm.exceptions.RealmException; import io.realm.internal.RealmCore; import io.realm.internal.RealmProxyMediator; -import io.realm.internal.SharedGroup; +import io.realm.internal.SharedRealm; import io.realm.internal.modules.CompositeMediator; import io.realm.internal.modules.FilterableMediator; import io.realm.rx.RealmObservableFactory; @@ -92,7 +92,7 @@ public final class RealmConfiguration { private final long schemaVersion; private final RealmMigration migration; private final boolean deleteRealmIfMigrationNeeded; - private final SharedGroup.Durability durability; + private final SharedRealm.Durability durability; private final RealmProxyMediator schemaMediator; private final RxObservableFactory rxObservableFactory; private final Realm.Transaction initialDataTransaction; @@ -138,7 +138,7 @@ public boolean shouldDeleteRealmIfMigrationNeeded() { return deleteRealmIfMigrationNeeded; } - public SharedGroup.Durability getDurability() { + public SharedRealm.Durability getDurability() { return durability; } @@ -353,7 +353,7 @@ public static final class Builder { private long schemaVersion; private RealmMigration migration; private boolean deleteRealmIfMigrationNeeded; - private SharedGroup.Durability durability; + private SharedRealm.Durability durability; private HashSet modules = new HashSet(); private HashSet> debugSchema = new HashSet>(); private WeakReference contextWeakRef; @@ -424,7 +424,7 @@ private void initializeBuilder(File folder) { this.schemaVersion = 0; this.migration = null; this.deleteRealmIfMigrationNeeded = false; - this.durability = SharedGroup.Durability.FULL; + this.durability = SharedRealm.Durability.FULL; if (DEFAULT_MODULE != null) { this.modules.add(DEFAULT_MODULE); } @@ -521,7 +521,7 @@ public Builder inMemory() { throw new RealmException("Realm can not use in-memory configuration if asset file is present."); } - this.durability = SharedGroup.Durability.MEM_ONLY; + this.durability = SharedRealm.Durability.MEM_ONLY; return this; } @@ -598,7 +598,7 @@ public Builder assetFile(Context context, final String assetFile) { if (TextUtils.isEmpty(assetFile)) { throw new IllegalArgumentException("A non-empty asset file path must be provided"); } - if (durability == SharedGroup.Durability.MEM_ONLY) { + if (durability == SharedRealm.Durability.MEM_ONLY) { throw new RealmException("Realm can not use in-memory configuration if asset file is present."); } if (this.deleteRealmIfMigrationNeeded) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index ac48a48f19..189963b558 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -17,7 +17,6 @@ package io.realm; import io.realm.annotations.Required; -import io.realm.internal.ImplicitTransaction; import io.realm.internal.Table; import io.realm.internal.TableOrView; @@ -68,7 +67,6 @@ public final class RealmObjectSchema { private final BaseRealm realm; final Table table; - private final ImplicitTransaction transaction; private final Map columnIndices; /** @@ -80,7 +78,6 @@ public final class RealmObjectSchema { */ RealmObjectSchema(BaseRealm realm, Table table, Map columnIndices) { this.realm = realm; - this.transaction = realm.sharedGroupManager.getTransaction(); this.table = table; this.columnIndices = columnIndices; } @@ -114,7 +111,7 @@ public RealmObjectSchema setClassName(String className) { if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { throw new IllegalArgumentException("Class name is to long. Limit is 56 characters: \'" + className + "\' (" + Integer.toString(className.length()) + ")"); } - if (transaction.hasTable(internalTableName)) { + if (realm.sharedRealm.hasTable(internalTableName)) { throw new IllegalArgumentException("Class already exists: " + className); } // in case this table has a primary key, we need to transfer it after renaming the table. @@ -125,13 +122,13 @@ public RealmObjectSchema setClassName(String className) { pkField = getPrimaryKey(); table.setPrimaryKey(null); } - transaction.renameTable(table.getName(), internalTableName); + realm.sharedRealm.renameTable(table.getName(), internalTableName); if (pkField != null && !pkField.isEmpty()) { try { table.setPrimaryKey(pkField); } catch (Exception e) { // revert the table name back when something goes wrong - transaction.renameTable(table.getName(), oldTableName); + realm.sharedRealm.renameTable(table.getName(), oldTableName); throw e; } } @@ -192,7 +189,7 @@ public RealmObjectSchema addField(String fieldName, Class fieldType, FieldAtt public RealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema) { checkLegalName(fieldName); checkFieldNameIsAvailable(fieldName); - table.addColumnLink(RealmFieldType.OBJECT, fieldName, transaction.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); + table.addColumnLink(RealmFieldType.OBJECT, fieldName, realm.sharedRealm.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); return this; } @@ -207,7 +204,7 @@ public RealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema public RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema) { checkLegalName(fieldName); checkFieldNameIsAvailable(fieldName); - table.addColumnLink(RealmFieldType.LIST, fieldName, transaction.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); + table.addColumnLink(RealmFieldType.LIST, fieldName, realm.sharedRealm.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); return this; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index afbe21db21..1e4019113b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -32,7 +32,7 @@ import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SharedGroup; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.TableQuery; @@ -1365,13 +1365,13 @@ public RealmResults distinctAsync(String fieldName) { final WeakReference weakHandler = getWeakReferenceHandler(); // handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedGroupManager.getNativePointer()); + final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); // save query arguments (for future update) argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_DISTINCT); argumentsHolder.columnIndex = columnIndex; - // we need to use the same configuration to open a background SharedGroup (i.e Realm) + // we need to use the same configuration to open a background SharedRealm (i.e Realm) // to perform the query final RealmConfiguration realmConfiguration = realm.getConfiguration(); @@ -1391,35 +1391,31 @@ public RealmResults distinctAsync(String fieldName) { @Override public Long call() throws Exception { if (!Thread.currentThread().isInterrupted()) { - SharedGroup sharedGroup = null; + SharedRealm sharedRealm = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); - - long handoverTableViewPointer = query. - findDistinctWithHandover(sharedGroup.getNativePointer(), - sharedGroup.getNativeReplicationPointer(), + sharedRealm = SharedRealm.getInstance(realmConfiguration); + + long handoverTableViewPointer = TableQuery. + findDistinctWithHandover(sharedRealm, handoverQueryPointer, columnIndex); QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); - result.versionID = sharedGroup.getVersion(); - closeSharedGroupAndSendMessageToHandler(sharedGroup, + result.versionID = sharedRealm.getVersionID(); + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result); return handoverTableViewPointer; } catch (Throwable e) { RealmLog.e(e.getMessage(), e); - closeSharedGroupAndSendMessageToHandler(sharedGroup, + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); } finally { - if (sharedGroup != null && !sharedGroup.isClosed()) { - sharedGroup.close(); + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); } } } else { @@ -1674,12 +1670,12 @@ public RealmResults findAllAsync() { final WeakReference weakHandler = getWeakReferenceHandler(); // handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedGroupManager.getNativePointer()); + final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); // save query arguments (for future update) argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_ALL); - // we need to use the same configuration to open a background SharedGroup (i.e Realm) + // we need to use the same configuration to open a background SharedRealm (i.e Realm) // to perform the query final RealmConfiguration realmConfiguration = realm.getConfiguration(); @@ -1699,23 +1695,21 @@ public RealmResults findAllAsync() { @Override public Long call() throws Exception { if (!Thread.currentThread().isInterrupted()) { - SharedGroup sharedGroup = null; + SharedRealm sharedRealm = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedRealm = SharedRealm.getInstance(realmConfiguration); // Run the query & handover the table view for the caller thread // Note: the handoverQueryPointer contains the versionID needed by the SG in order // to import it. - long handoverTableViewPointer = query.findAllWithHandover(sharedGroup.getNativePointer(), sharedGroup.getNativeReplicationPointer(), handoverQueryPointer); + long handoverTableViewPointer = TableQuery.findAllWithHandover(sharedRealm, + handoverQueryPointer); QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); - result.versionID = sharedGroup.getVersion(); - closeSharedGroupAndSendMessageToHandler(sharedGroup, + result.versionID = sharedRealm.getVersionID(); + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result); return handoverTableViewPointer; @@ -1727,12 +1721,11 @@ public Long call() throws Exception { } catch (Throwable e) { RealmLog.e(e.getMessage(), e); - closeSharedGroupAndSendMessageToHandler(sharedGroup, + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); - } finally { - if (sharedGroup != null && !sharedGroup.isClosed()) { - sharedGroup.close(); + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); } } } else { @@ -1797,9 +1790,9 @@ public RealmResults findAllSortedAsync(final String fieldName, final Sort sor final WeakReference weakHandler = getWeakReferenceHandler(); // handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedGroupManager.getNativePointer()); + final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - // we need to use the same configuration to open a background SharedGroup to perform the query + // we need to use the same configuration to open a background SharedRealm to perform the query final RealmConfiguration realmConfiguration = realm.getConfiguration(); RealmResults realmResults; @@ -1817,24 +1810,21 @@ public RealmResults findAllSortedAsync(final String fieldName, final Sort sor @Override public Long call() throws Exception { if (!Thread.currentThread().isInterrupted()) { - SharedGroup sharedGroup = null; + SharedRealm sharedRealm = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedRealm = SharedRealm.getInstance(realmConfiguration); long columnIndex = getColumnIndexForSort(fieldName); // run the query & handover the table view for the caller thread - long handoverTableViewPointer = query.findAllSortedWithHandover(sharedGroup.getNativePointer(), - sharedGroup.getNativeReplicationPointer(), handoverQueryPointer, columnIndex, sortOrder); + long handoverTableViewPointer = TableQuery.findAllSortedWithHandover(sharedRealm, + handoverQueryPointer, columnIndex, sortOrder); QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); - result.versionID = sharedGroup.getVersion(); - closeSharedGroupAndSendMessageToHandler(sharedGroup, + result.versionID = sharedRealm.getVersionID(); + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result); return handoverTableViewPointer; @@ -1845,12 +1835,12 @@ public Long call() throws Exception { } catch (Throwable e) { RealmLog.e(e.getMessage(), e); - closeSharedGroupAndSendMessageToHandler(sharedGroup, + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); } finally { - if (sharedGroup != null && !sharedGroup.isClosed()) { - sharedGroup.close(); + if (sharedRealm!= null && !sharedRealm.isClosed()) { + sharedRealm.close(); } } } else { @@ -1960,9 +1950,9 @@ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sort final WeakReference weakHandler = getWeakReferenceHandler(); // Handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedGroupManager.getNativePointer()); + final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - // We need to use the same configuration to open a background SharedGroup to perform the query + // We need to use the same configuration to open a background SharedRealm to perform the query final RealmConfiguration realmConfiguration = realm.getConfiguration(); final long indices[] = new long[fieldNames.length]; @@ -1992,22 +1982,19 @@ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sort @Override public Long call() throws Exception { if (!Thread.currentThread().isInterrupted()) { - SharedGroup sharedGroup = null; + SharedRealm sharedRealm = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedRealm = SharedRealm.getInstance(realmConfiguration); // run the query & handover the table view for the caller thread - long handoverTableViewPointer = query.findAllMultiSortedWithHandover(sharedGroup.getNativePointer(), - sharedGroup.getNativeReplicationPointer(), handoverQueryPointer, indices, sortOrders); + long handoverTableViewPointer = TableQuery.findAllMultiSortedWithHandover(sharedRealm, + handoverQueryPointer, indices, sortOrders); QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); - result.versionID = sharedGroup.getVersion(); - closeSharedGroupAndSendMessageToHandler(sharedGroup, + result.versionID = sharedRealm.getVersionID(); + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result); return handoverTableViewPointer; @@ -2018,12 +2005,12 @@ public Long call() throws Exception { } catch (Throwable e) { RealmLog.e(e.getMessage(), e); - closeSharedGroupAndSendMessageToHandler(sharedGroup, + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); } finally { - if (sharedGroup != null && !sharedGroup.isClosed()) { - sharedGroup.close(); + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); } } } else { @@ -2106,7 +2093,7 @@ public E findFirstAsync() { final WeakReference weakHandler = getWeakReferenceHandler(); // handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedGroupManager.getNativePointer()); + final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); // save query arguments (for future update) argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_FIRST); @@ -2132,16 +2119,12 @@ public E findFirstAsync() { @Override public Long call() throws Exception { if (!Thread.currentThread().isInterrupted()) { - SharedGroup sharedGroup = null; + SharedRealm sharedRealm = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedRealm = SharedRealm.getInstance(realmConfiguration); - long handoverRowPointer = query.findWithHandover(sharedGroup.getNativePointer(), - sharedGroup.getNativeReplicationPointer(), handoverQueryPointer); + long handoverRowPointer = TableQuery.findWithHandover(sharedRealm, handoverQueryPointer); if (handoverRowPointer == 0) { // empty row realm.handlerController.addToEmptyAsyncRealmObject(realmObjectWeakReference, RealmQuery.this); realm.handlerController.removeFromAsyncRealmObject(realmObjectWeakReference); @@ -2149,8 +2132,8 @@ public Long call() throws Exception { QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmObjectResponse(); result.updatedRow.put(realmObjectWeakReference, handoverRowPointer); - result.versionID = sharedGroup.getVersion(); - closeSharedGroupAndSendMessageToHandler(sharedGroup, + result.versionID = sharedRealm.getVersionID(); + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT, result); return handoverRowPointer; @@ -2158,12 +2141,12 @@ public Long call() throws Exception { } catch (Throwable e) { RealmLog.e(e.getMessage(), e); // handler can't throw a checked exception need to wrap it into unchecked Exception - closeSharedGroupAndSendMessageToHandler(sharedGroup, + closeSharedRealmAndSendMessageToHandler(sharedRealm, weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); } finally { - if (sharedGroup != null && !sharedGroup.isClosed()) { - sharedGroup.close(); + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); } } } else { @@ -2202,9 +2185,10 @@ private WeakReference getWeakReferenceHandler() { // The shared group needs to be closed before sending the message to other threads to avoid timing problems. // eg.: The other thread wants to delete Realm when getting notified. - private void closeSharedGroupAndSendMessageToHandler(SharedGroup sharedGroup, WeakReference weakHandler, int what, Object obj) { - if (sharedGroup != null) { - sharedGroup.close(); + private void closeSharedRealmAndSendMessageToHandler(SharedRealm sharedRealm, WeakReference weakHandler, + int what, Object obj) { + if (sharedRealm != null) { + sharedRealm.close(); } Handler handler = weakHandler.get(); if (handler != null && handler.getLooper().getThread().isAlive()) { @@ -2264,6 +2248,6 @@ public ArgumentsHolder getArgument() { * @return the exported handover pointer for this RealmQuery. */ long handoverQueryPointer() { - return query.handoverQuery(realm.sharedGroupManager.getNativePointer()); + return query.handoverQuery(realm.sharedRealm); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index a4d4eca6f7..f2416101e0 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -847,7 +847,7 @@ public void set(E object) { */ void swapTableViewPointer(long handoverTableViewPointer) { try { - table = query.importHandoverTableView(handoverTableViewPointer, realm.sharedGroupManager.getNativePointer()); + table = query.importHandoverTableView(handoverTableViewPointer, realm.sharedRealm); asyncQueryCompleted = true; } catch (BadVersionException e) { throw new IllegalStateException("Caller and Worker Realm should have been at the same version"); @@ -913,7 +913,7 @@ private boolean onAsyncQueryCompleted() { // this may fail with BadVersionException if the caller and/or the worker thread // are not in sync. COMPLETED_ASYNC_REALM_RESULTS will be fired by the worker thread // this should handle more complex use cases like retry, ignore etc - table = query.importHandoverTableView(tvHandover, realm.sharedGroupManager.getNativePointer()); + table = query.importHandoverTableView(tvHandover, realm.sharedRealm); asyncQueryCompleted = true; notifyChangeListeners(true); } catch (Exception e) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index f2c723ad31..65ce99b506 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -23,7 +23,6 @@ import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; -import io.realm.internal.ImplicitTransaction; import io.realm.internal.Table; import io.realm.internal.Util; @@ -49,16 +48,14 @@ public final class RealmSchema { // Caches Class Strings to their Schema object private final Map dynamicClassToSchema = new HashMap(); - private final ImplicitTransaction transaction; private final BaseRealm realm; ColumnIndices columnIndices; // Cached field look up /** * Creates a wrapper to easily manipulate the current schema of a Realm. */ - RealmSchema(BaseRealm realm, ImplicitTransaction transaction) { + RealmSchema(BaseRealm realm) { this.realm = realm; - this.transaction = transaction; } /** @@ -71,8 +68,8 @@ public final class RealmSchema { public RealmObjectSchema get(String className) { checkEmpty(className, EMPTY_STRING_MSG); String internalClassName = TABLE_PREFIX + className; - if (transaction.hasTable(internalClassName)) { - Table table = transaction.getTable(internalClassName); + if (realm.sharedRealm.hasTable(internalClassName)) { + Table table = realm.sharedRealm.getTable(internalClassName); RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); return new RealmObjectSchema(realm, table, columnIndices); } else { @@ -86,14 +83,14 @@ public RealmObjectSchema get(String className) { * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ public Set getAll() { - int tableCount = (int) transaction.size(); + int tableCount = (int) realm.sharedRealm.size(); Set schemas = new LinkedHashSet<>(tableCount); for (int i = 0; i < tableCount; i++) { - String tableName = transaction.getTableName(i); + String tableName = realm.sharedRealm.getTableName(i); if (Table.isMetaTable(tableName)) { continue; } - Table table = transaction.getTable(tableName); + Table table = realm.sharedRealm.getTable(tableName); RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); schemas.add(new RealmObjectSchema(realm, table, columnIndices)); } @@ -112,10 +109,10 @@ public RealmObjectSchema create(String className) { if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { throw new IllegalArgumentException("Class name is to long. Limit is 57 characters: " + className.length()); } - if (transaction.hasTable(internalTableName)) { + if (realm.sharedRealm.hasTable(internalTableName)) { throw new IllegalArgumentException("Class already exists: " + className); } - Table table = transaction.getTable(internalTableName); + Table table = realm.sharedRealm.getTable(internalTableName); RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); return new RealmObjectSchema(realm, table, columnIndices); } @@ -134,7 +131,7 @@ public void remove(String className) { if (table.hasPrimaryKey()) { table.setPrimaryKey(null); } - transaction.removeTable(internalTableName); + realm.sharedRealm.removeTable(internalTableName); } /** @@ -150,7 +147,7 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { String oldInternalName = TABLE_PREFIX + oldClassName; String newInternalName = TABLE_PREFIX + newClassName; checkHasTable(oldClassName, "Cannot rename class because it doesn't exist in this Realm: " + oldClassName); - if (transaction.hasTable(newInternalName)) { + if (realm.sharedRealm.hasTable(newInternalName)) { throw new IllegalArgumentException(oldClassName + " cannot be renamed because the new class already exists: " + newClassName); } @@ -162,8 +159,8 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { oldTable.setPrimaryKey(null); } - transaction.renameTable(oldInternalName, newInternalName); - Table table = transaction.getTable(newInternalName); + realm.sharedRealm.renameTable(oldInternalName, newInternalName); + Table table = realm.sharedRealm.getTable(newInternalName); // Set the primary key for the new class if necessary if (pkField != null) { @@ -181,7 +178,7 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { * @return {@code true} if the class already exists. {@code false} otherwise. */ public boolean contains(String className) { - return transaction.hasTable(Table.TABLE_PREFIX + className); + return realm.sharedRealm.hasTable(Table.TABLE_PREFIX + className); } private void checkEmpty(String str, String error) { @@ -192,7 +189,7 @@ private void checkEmpty(String str, String error) { private void checkHasTable(String className, String errorMsg) { String internalTableName = TABLE_PREFIX + className; - if (!transaction.hasTable(internalTableName)) { + if (!realm.sharedRealm.hasTable(internalTableName)) { throw new IllegalArgumentException(errorMsg); } } @@ -209,10 +206,10 @@ Table getTable(String className) { className = Table.TABLE_PREFIX + className; Table table = dynamicClassToTable.get(className); if (table == null) { - if (!transaction.hasTable(className)) { + if (!realm.sharedRealm.hasTable(className)) { throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); } - table = transaction.getTable(className); + table = realm.sharedRealm.getTable(className); dynamicClassToTable.put(className, table); } return table; @@ -227,7 +224,7 @@ Table getTable(Class clazz) { table = classToTable.get(originalClass); } if (table == null) { - table = transaction.getTable(realm.configuration.getSchemaMediator().getTableName(originalClass)); + table = realm.sharedRealm.getTable(realm.configuration.getSchemaMediator().getTableName(originalClass)); classToTable.put(originalClass, table); } if (isProxyClass(originalClass, clazz)) { @@ -268,10 +265,10 @@ RealmObjectSchema getSchemaForClass(String className) { className = Table.TABLE_PREFIX + className; RealmObjectSchema dynamicSchema = dynamicClassToSchema.get(className); if (dynamicSchema == null) { - if (!transaction.hasTable(className)) { + if (!realm.sharedRealm.hasTable(className)) { throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); } - Table table = transaction.getTable(className); + Table table = realm.sharedRealm.getTable(className); RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); dynamicSchema = new RealmObjectSchema(realm, table, columnIndices); dynamicClassToSchema.put(className, dynamicSchema); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Context.java b/realm/realm-library/src/main/java/io/realm/internal/Context.java index 8d241d0043..66b236a786 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Context.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Context.java @@ -133,14 +133,6 @@ public void asyncDisposeQuery(long nativePointer) { } } - public void asyncDisposeGroup(long nativePointer) { - Group.nativeClose(nativePointer); - } - - public void asyncDisposeSharedGroup(long nativePointer) { - SharedGroup.nativeClose(nativePointer); - } - protected void finalize() throws Throwable { synchronized (this) { isFinalized = true; diff --git a/realm/realm-library/src/main/java/io/realm/internal/Group.java b/realm/realm-library/src/main/java/io/realm/internal/Group.java deleted file mode 100644 index d02022fdec..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/Group.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * This class is used to serialize tables to either disk or memory. It consists of a collection of tables. - */ -public class Group implements Closeable { - - // Below values must match the values in realm::group::OpenMode in C++ - public static final int MODE_READONLY = 0; // Open in read-only mode. Fail if the file does not already exist. - public static final int MODE_READWRITE = 1; // Open in read/write mode. Create the file if it doesn't exist. - public static final int MODE_READWRITE_NOCREATE = 2; // Open in read/write mode. Fail if the file does not already exist. - - protected long nativePtr; - protected boolean immutable; - private final Context context; - - private void checkNativePtrNotZero() { - if (this.nativePtr == 0) - // FIXME: It is wrong to assume that a null pointer means 'out - // of memory'. An out of memory condition in - // createNative() must be handled by having createNative() - // throw OutOfMemoryError. - throw new OutOfMemoryError("Out of native memory."); - } - - public Group() { - this.immutable = false; - this.context = new Context(); - this.nativePtr = createNative(); - checkNativePtrNotZero(); - } - - public Group(String filepath, int mode) { - this.immutable = (mode == MODE_READONLY); - this.context = new Context(); - this.nativePtr = createNative(filepath, mode); - checkNativePtrNotZero(); - } - - public Group(String filepath) { - this(filepath, MODE_READONLY); - } - - public Group(File file) { - this(file.getAbsolutePath(), file.canWrite() ? MODE_READWRITE : MODE_READONLY); - } - - public Group(byte[] data) { - this.immutable = false; - this.context = new Context(); - if (data != null) { - this.nativePtr = createNative(data); - checkNativePtrNotZero(); - } else { - throw new IllegalArgumentException(); - } - } - - public Group(ByteBuffer buffer) { - this.immutable = false; - this.context = new Context(); - if (buffer != null) { - this.nativePtr = createNative(buffer); - checkNativePtrNotZero(); - } else { - throw new IllegalArgumentException(); - } - } - - Group(Context context, long nativePointer, boolean immutable) { - this.context = context; - this.nativePtr = nativePointer; - this.immutable = immutable; - } - - // If close() is called, no penalty is paid for delayed disposal - // via the context - public void close() { - synchronized (context) { - if (nativePtr != 0) { - nativeClose(nativePtr); - nativePtr = 0; - } - } - } - - /** - * Checks if a group has been closed and can no longer be used. - * - * @return {@code true} if closed, {@code false} otherwise. - */ - boolean isClosed() { - return nativePtr == 0; - } - - protected void finalize() { - synchronized (context) { - if (nativePtr != 0) { - context.asyncDisposeGroup(nativePtr); - nativePtr = 0; // Set to 0 if finalize is called before close() for some reason - } - } - } - - private void verifyGroupIsValid() { - if (nativePtr == 0) { - throw new IllegalStateException("Illegal to call methods on a closed Group."); - } - } - - public long size() { - verifyGroupIsValid(); - return nativeSize(nativePtr); - } - - public boolean isEmpty(){ - return size() == 0; - } - - /** - * Checks whether {@link Table} exists in the Group. - * - * @param name the name of the {@link Table}. - * @return {@code true} if the table exists, otherwise {@code false}. - */ - public boolean hasTable(String name) { - verifyGroupIsValid(); - return name != null && nativeHasTable(nativePtr, name); - } - - public String getTableName(int index) { - verifyGroupIsValid(); - long cnt = size(); - if (index < 0 || index >= cnt) { - throw new IndexOutOfBoundsException( - "Table index argument is out of range. possible range is [0, " - + (cnt - 1) + "]"); - } - return nativeGetTableName(nativePtr, index); - } - - /** - * Removes a table from the group and delete all data. - */ - public void removeTable(String name) { - nativeRemoveTable(nativePtr, name); - } - - native void nativeRemoveTable(long nativeGroupPtr, String tableName); - - /** - * Renames a table - */ - public void renameTable(String oldName, String newName) { - nativeRenameTable(nativePtr, oldName, newName); - } - - native void nativeRenameTable(long nativeGroupPtr, String oldName, String newName); - - /** - * Returns a table with the specified name. - * - * @param name the name of the {@link Table}. - * @return the {@link Table} if it exists, otherwise create it. - */ - public Table getTable(String name) { - verifyGroupIsValid(); - if (name == null || name.isEmpty()) { - throw new IllegalArgumentException("Invalid name. Name must be a non-empty String."); - } - if (immutable && !hasTable(name)) { - throw new IllegalStateException("Requested table is not in this Realm. " + - "Creating it requires a transaction: " + name); - } - - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); - long nativeTablePointer = nativeGetTableNativePtr(nativePtr, name); - try { - // Copy context reference from parent - return new Table(context, this, nativeTablePointer); - } catch (RuntimeException e) { - Table.nativeClose(nativeTablePointer); - throw e; - } - } - - /** - * Serializes the group to the specific file on the disk using encryption. - * - * @param file a File object representing the file. - * @param key A 64 bytes long byte array containing the key to the encrypted Realm file. Can be null if encryption - * is not required. - * @throws IOException. - */ - public void writeToFile(File file, byte[] key) throws IOException { - verifyGroupIsValid(); - if (file.isFile() && file.exists()) { - throw new IllegalArgumentException("The destination file must not exist"); - } - if (key != null && key.length != 64) { - throw new IllegalArgumentException("Realm AES keys must be 64 bytes long"); - } - - nativeWriteToFile(nativePtr, file.getAbsolutePath(), key); - } - - /** - * Serializes the group to a memory buffer. The byte[] is owned by the JVM. - * - * @return the binary array of the serialized group. - */ - public byte[] writeToMem() { - verifyGroupIsValid(); - return nativeWriteToMem(nativePtr); - } - - /* - * Checks if the Group contains any objects. It only checks for "class_" tables or non-metadata tables, e.g. this - * return true if the "pk" table contained information. - * - * @return {@code true} if empty, @{code false} otherwise. - */ - public boolean isObjectTablesEmpty() { - return nativeIsEmpty(nativePtr); - } - -/* - * TODO: Find a way to release the malloc'ed native memory automatically - - public ByteBuffer writeToByteBuffer() { - verifyGroupIsValid(); - return nativeWriteToByteBuffer(nativePtr); - } - - protected native ByteBuffer nativeWriteToByteBuffer(long nativeGroupPtr); -*/ - - public void commit() { - verifyGroupIsValid(); - nativeCommit(nativePtr); - } - - public String toJson() { - return nativeToJson(nativePtr); - } - - public String toString() { - return nativeToString(nativePtr); - } - - - protected native long createNative(); - protected native long createNative(String filepath, int value); - protected native long createNative(byte[] data); - protected native long createNative(ByteBuffer buffer); - protected static native void nativeClose(long nativeGroupPtr); - protected native long nativeSize(long nativeGroupPtr); - protected native String nativeGetTableName(long nativeGroupPtr, int index); - protected native boolean nativeHasTable(long nativeGroupPtr, String name); - protected native void nativeWriteToFile(long nativeGroupPtr, String fileName, byte[] keyArray) throws IOException; - protected native long nativeGetTableNativePtr(long nativeGroupPtr, String name); - protected native byte[] nativeWriteToMem(long nativeGroupPtr); - protected native String nativeToJson(long nativeGroupPtr); - protected native void nativeCommit(long nativeGroupPtr); - protected native String nativeToString(long nativeGroupPtr); - protected native boolean nativeIsEmpty(long nativeGroupPtr); -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/ImplicitTransaction.java b/realm/realm-library/src/main/java/io/realm/internal/ImplicitTransaction.java deleted file mode 100644 index cd6a3c62eb..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/ImplicitTransaction.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import io.realm.internal.async.BadVersionException; - -public class ImplicitTransaction extends Group { - - private final SharedGroup parent; - - public ImplicitTransaction(Context context, SharedGroup sharedGroup, long nativePtr) { - super(context, nativePtr, true); - parent = sharedGroup; - } - - /** - * Positions the shared group to the latest version. - */ - public void advanceRead() { - assertNotClosed(); - parent.advanceRead(); - } - - /** - * Positions the shared group at the specified version. - * - * @param versionID version of the shared group. - */ - public void advanceRead(SharedGroup.VersionID versionID) throws BadVersionException { - assertNotClosed(); - parent.advanceRead(versionID); - } - - public void promoteToWrite() { - assertNotClosed(); - if (!immutable) { - throw new IllegalStateException("Nested transactions are not allowed. Use commitTransaction() after each beginTransaction()."); - } - immutable = false; - parent.promoteToWrite(); - } - - public void commitAndContinueAsRead() { - assertNotClosed(); - if (immutable) { - throw new IllegalStateException("Not inside a transaction."); - } - parent.commitAndContinueAsRead(); - immutable = true; - } - - public void endRead() { - assertNotClosed(); - parent.endRead(); - } - - public void rollbackAndContinueAsRead() { - assertNotClosed(); - if (immutable) { - throw new IllegalStateException("Not inside a transaction."); - } - parent.rollbackAndContinueAsRead(); - immutable = true; - } - - private void assertNotClosed() { - if (isClosed() || parent.isClosed()) { - throw new IllegalStateException("Cannot use ImplicitTransaction after it or its parent has been closed."); - } - } - - /** - * Returns the absolute path to the Realm file backing this transaction. - */ - public String getPath() { - return parent.getPath(); - } - - protected void finalize() {} // Nullify the actions of Group.finalize() -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index 2a92a7ba92..d05a4a51dd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -154,7 +154,7 @@ public Table getTargetTable() { long nativeTablePointer = nativeGetTargetTable(nativePointer); try { // Copy context reference from parent - return new Table(context, this.parent, nativeTablePointer); + return new Table(this.parent, nativeTablePointer); } catch (RuntimeException e) { Table.nativeClose(nativeTablePointer); throw e; diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index 0b1256a1ed..e348a15129 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -48,16 +48,16 @@ public abstract class RealmProxyMediator { * @param clazz the {@link RealmObject} model class to create backing table for. * @param transaction the read transaction for the Realm to create table in. */ - public abstract Table createTable(Class clazz, ImplicitTransaction transaction); + public abstract Table createTable(Class clazz, SharedRealm sharedRealm); /** * Validates the backing table in Realm for the given RealmObject class. * * @param clazz the {@link RealmObject} model class to validate. - * @param transaction the read transaction for the Realm to validate against. + * @param sharedRealm the read transaction for the Realm to validate against. * @return the field indices map. */ - public abstract ColumnInfo validateTable(Class clazz, ImplicitTransaction transaction); + public abstract ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm); /** * Returns a map of non-obfuscated object field names to their internal Realm name. diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedGroup.java b/realm/realm-library/src/main/java/io/realm/internal/SharedGroup.java deleted file mode 100644 index da20dd4114..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedGroup.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import java.io.Closeable; -import java.io.IOError; -import java.util.concurrent.TimeUnit; - -import io.realm.exceptions.IncompatibleLockFileException; -import io.realm.exceptions.RealmError; -import io.realm.exceptions.RealmIOException; -import io.realm.internal.async.BadVersionException; -import io.realm.internal.log.RealmLog; - -public class SharedGroup implements Closeable { - - // Keep these public so we can ask users to experiment with these values if needed. - // Should be locked down as soon as possible. - public static long[] INCREMENTAL_BACKOFF_MS = new long[] {1, 10, 20, 50, 100, 200, 400}; // Will keep re-using last value until LIMIT is hit - public static long INCREMENTAL_BACKOFF_LIMIT_MS = 3000; - - public static final boolean IMPLICIT_TRANSACTION = true; - public static final boolean EXPLICIT_TRANSACTION = false; - - private static final boolean CREATE_FILE_YES = false; - private static final boolean CREATE_FILE_NO = true; - private static final boolean ENABLE_REPLICATION = true; - private static final boolean DISABLE_REPLICATION = false; - - private final String path; - private long nativePtr; - private long nativeReplicationPtr; - private boolean implicitTransactionsEnabled = false; - private boolean activeTransaction; - private final Context context; - - public enum Durability { - FULL(0), - MEM_ONLY(1); - //ASYNC(2); // TODO: re-enable when possible - - final int value; - - Durability(int value) { - this.value = value; - } - } - - // TODO Only used by Unit tests. Remove? - public SharedGroup(String databaseFile) { - context = new Context(); - path = databaseFile; - nativePtr = nativeCreate(databaseFile, Durability.FULL.value, CREATE_FILE_YES, DISABLE_REPLICATION, null); - checkNativePtrNotZero(); - } - - public SharedGroup(String canonicalPath, boolean enableImplicitTransactions, Durability durability, byte[] key) { - if (enableImplicitTransactions) { - nativeReplicationPtr = nativeCreateReplication(canonicalPath, key); - nativePtr = openSharedGroupOrFail(durability, key); - implicitTransactionsEnabled = true; - } else { - nativePtr = nativeCreate(canonicalPath, Durability.FULL.value, CREATE_FILE_YES, DISABLE_REPLICATION, key); - } - context = new Context(); - path = canonicalPath; - checkNativePtrNotZero(); - } - - private long openSharedGroupOrFail(Durability durability, byte[] key) { - // We have anecdotal evidence that on some versions of Android it is possible for two versions of an app - // to exist in two processes during an app upgrade. This is problematic since the lock file might not be - // compatible across two versions of Android. See https://github.com/realm/realm-java/issues/2459. If this - // happens we assume the overlap is really small so instead of failing outright we retry using incremental - // backoff. - int i = 0; - final long start = System.nanoTime(); - RuntimeException lastError = null; - while (TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS) < INCREMENTAL_BACKOFF_LIMIT_MS) { - try { - long nativePtr = createNativeWithImplicitTransactions(nativeReplicationPtr, durability.value, key); - if (i > 0) { - RealmLog.w("IncompatibleLockFile was detected. Error was resolved after " + i + " retries"); - } - return nativePtr; - } catch (IncompatibleLockFileException e) { - i++; - lastError = e; - try { - Thread.sleep(getSleepTime(i)); - RealmLog.d("Waiting for another process to release the Realm file: " + path); - } catch (InterruptedException ignored) { - RealmLog.d("Waiting for Realm to open interrupted: " + path); - } - } - } - - throw new RealmError("Could not open the Realm file: " + lastError.getMessage()); - } - - // Returns the time to sleep before retrying opening the SharedGroup. - private static long getSleepTime(int tries) { - if (INCREMENTAL_BACKOFF_MS == null) { - return 0; - } else { - if (tries > INCREMENTAL_BACKOFF_MS.length) { - return INCREMENTAL_BACKOFF_MS[INCREMENTAL_BACKOFF_MS.length - 1]; - } else { - return INCREMENTAL_BACKOFF_MS[tries - 1]; - } - } - } - - // TODO Only used by Unit tests. Remove? - public SharedGroup(String canonicalPath, Durability durability, byte[] key) { - path = canonicalPath; - context = new Context(); - nativePtr = nativeCreate(canonicalPath, durability.value, false, false, key); - checkNativePtrNotZero(); - } - - void advanceRead() { - nativeAdvanceRead(nativePtr); - } - - void advanceRead(VersionID versionID) throws BadVersionException { - nativeAdvanceReadToVersion(nativePtr, versionID.version, versionID.index); - } - - void promoteToWrite() { - nativePromoteToWrite(nativePtr); - } - - void commitAndContinueAsRead() { - nativeCommitAndContinueAsRead(nativePtr); - } - - void rollbackAndContinueAsRead() { - nativeRollbackAndContinueAsRead(nativePtr); - } - - public ImplicitTransaction beginImplicitTransaction() { - if (activeTransaction) { - throw new IllegalStateException( - "Can't beginImplicitTransaction() during another active transaction"); - } - long nativeGroupPtr = nativeBeginImplicit(nativePtr); - ImplicitTransaction transaction = new ImplicitTransaction(context, this, nativeGroupPtr); - activeTransaction = true; - return transaction; - } - - public WriteTransaction beginWrite() { - if (activeTransaction) - throw new IllegalStateException( - "Can't beginWrite() during another active transaction"); - // FIXME: throw from nativeMethod in case of error - - long nativeWritePtr = nativeBeginWrite(nativePtr); - try { - // Copy context reference from parent - WriteTransaction t = new WriteTransaction(context, this, nativeWritePtr); - activeTransaction = true; - return t; - } catch (RuntimeException e) { - Group.nativeClose(nativeWritePtr); - throw e; - } - } - - public ReadTransaction beginRead() { - if (activeTransaction) - throw new IllegalStateException("Can't beginRead() during another active transaction"); - // FIXME: throw from nativeMethod in case of error - - long nativeReadPtr = nativeBeginRead(nativePtr); - try { - // Copy context reference from parent - ReadTransaction t = new ReadTransaction(context, this, nativeReadPtr); - activeTransaction = true; - return t; - } catch (RuntimeException e) { - Group.nativeClose(nativeReadPtr); - throw e; - } - } - - void endRead() { - if (isClosed()) - throw new IllegalStateException("Can't endRead() on closed group. " + - "ReadTransaction is invalid."); - nativeEndRead(nativePtr); - activeTransaction = false; - } - - public void close() { - synchronized (context) { - if (nativePtr != 0) { - nativeClose(nativePtr); - nativePtr = 0; - if (implicitTransactionsEnabled && nativeReplicationPtr != 0) { - nativeCloseReplication(nativeReplicationPtr); - nativeReplicationPtr = 0; - } - } - } - } - - protected void finalize() { - synchronized (context) { - if (nativePtr != 0) { - context.asyncDisposeSharedGroup(nativePtr); - nativePtr = 0; // Set to 0 if finalize is called before close() for some reason - if (implicitTransactionsEnabled && nativeReplicationPtr != 0) { - nativeCloseReplication(nativeReplicationPtr); - nativeReplicationPtr = 0; - } - } - } - } - - void commit() { - if (isClosed()) - throw new IllegalStateException( - "Can't commit() on closed group. WriteTransaction is invalid."); - nativeCommit(nativePtr); - activeTransaction = false; - } - - void rollback() { - if (isClosed()) - throw new IllegalStateException( - "Can't rollback() on closed group. WriteTransaction is invalid."); - nativeRollback(nativePtr); - activeTransaction = false; - } - - public boolean isClosed() { - return nativePtr == 0; - } - - public boolean hasChanged() { - return nativeHasChanged(nativePtr); - } - - public void reserve(long bytes) { - nativeReserve(nativePtr, bytes); - } - - /** - * Compacts a shared group. This will block access to the shared group until done. - * - * @return {@code true} if compaction succeeded, {@code false} otherwise. - * @throws RuntimeException if using this within either a read or or write transaction. - */ - public boolean compact() { - return nativeCompact(nativePtr); - } - - /** - * Returns the absolute path to the file backing this SharedGroup. - * - * @return the canonical path to the Realm file. - */ - public String getPath() { - return path; - } - - private void checkNativePtrNotZero() { - if (this.nativePtr == 0) { - throw new IOError(new RealmIOException("Realm could not be opened")); - } - } - - public long getNativePointer () { - return nativePtr; - } - - public long getNativeReplicationPointer () { - return nativeReplicationPtr; - } - - public VersionID getVersion () { - long[] versionId = nativeGetVersionID (nativePtr); - return new VersionID (versionId[0], versionId[1]); - - } - - public static class VersionID implements Comparable { - final long version; - final long index; - - VersionID(long version, long index) { - this.version = version; - this.index = index; - } - - @Override - public int compareTo(VersionID another) { - if (version > another.version) { - return 1; - } else if (version < another.version) { - return -1; - } else { - return 0; - } - } - - @Override - public String toString() { - return "VersionID{" + - "version=" + version + - ", index=" + index + - '}'; - } - - @Override - public boolean equals(Object object) { - if (this == object) return true; - if (object == null || getClass() != object.getClass()) return false; - if (!super.equals(object)) return false; - - VersionID versionID = (VersionID) object; - return (version == versionID.version && index == versionID.index); - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (int) (version ^ (version >>> 32)); - result = 31 * result + (int) (index ^ (index >>> 32)); - return result; - } - } - - /** - * Waits for change committed by {@link SharedGroup} in other Thread. - * - * @return {@code true} if successfully detects change, {@code false} no change has been detected otherwise. - */ - public boolean waitForChange() { - return nativeWaitForChange(nativePtr); - } - - /** - * Stops waiting for change. - */ - public void stopWaitForChange() { - nativeStopWaitForChange(nativePtr); - } - - private native long createNativeWithImplicitTransactions(long nativeReplicationPtr, - int durability, byte[] key); - private native long nativeCreateReplication(String databaseFile, byte[] key); - private native void nativeCommitAndContinueAsRead(long nativePtr); - private native long nativeBeginImplicit(long nativePtr); - - private native void nativeReserve(long nativePtr, long bytes); - private native boolean nativeHasChanged(long nativePtr); - private native long nativeBeginRead(long nativePtr); - private native void nativeEndRead(long nativePtr); - private native long nativeBeginWrite(long nativePtr); - private native void nativeCommit(long nativePtr); - private native void nativeRollback(long nativePtr); - private native long nativeCreate(String databaseFile, - int durabilityValue, - boolean dontCreateFile, - boolean enableReplication, - byte[] key); - private native boolean nativeCompact(long nativePtr); - protected static native void nativeClose(long nativePtr); - private native void nativeCloseReplication(long nativeReplicationPtr); - private native void nativeRollbackAndContinueAsRead(long nativePtr); - private native long[] nativeGetVersionID (long nativePtr); - private native boolean nativeWaitForChange(long nativePtr); - private native void nativeStopWaitForChange(long nativePtr); - private native void nativeAdvanceRead(long nativePtr); - private native void nativeAdvanceReadToVersion(long nativePtr, long version, long index) throws BadVersionException; - private native void nativePromoteToWrite(long nativePtr); -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedGroupManager.java b/realm/realm-library/src/main/java/io/realm/internal/SharedGroupManager.java deleted file mode 100644 index 7eac1c3ef1..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedGroupManager.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; - -import io.realm.RealmConfiguration; -import io.realm.internal.async.BadVersionException; -import io.realm.internal.log.RealmLog; - -/** - * This class wraps access to a given Realm file on a single thread including its {@link SharedGroup} and - * {@link ImplicitTransaction}. By nature this means that this class is not thread safe and should only be used from the - * thread that created it. - * - * Realm is a MVCC database (Multiversion concurrency control), which means that multiple versions of the data might - * exist in the same file. By default the file is always opened on the latest version and it is possible to advance to - * the latest version by calling {@link #advanceRead()}. - */ -public class SharedGroupManager implements Closeable { - - private SharedGroup sharedGroup; - private ImplicitTransaction transaction; - - /** - * Creates a new instance of the FileWrapper for the given configuration on this thread. - */ - public SharedGroupManager(RealmConfiguration configuration) { - this.sharedGroup = new SharedGroup( - configuration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - configuration.getDurability(), - configuration.getEncryptionKey()); - this.transaction = sharedGroup.beginImplicitTransaction(); - } - - /** - * Closes the underlying {@link SharedGroup} and free any native resources. - */ - @Override - public void close() { - sharedGroup.close(); - sharedGroup = null; - transaction = null; - } - - /** - * Checks if the Realm file is accessible. - * - * @return {@code true} if the file is open and data can be accessed, {@code false} otherwise. - */ - public boolean isOpen() { - return sharedGroup != null; - } - - /** - * Advances the Realm file to the latest version. - */ - public void advanceRead() { - transaction.advanceRead(); - } - - /** - * Advances the Realm file to the given version. - */ - public void advanceRead(SharedGroup.VersionID version) throws BadVersionException { - transaction.advanceRead(version); - } - - - // Public because of migrations. Gets the full table name. Prefix will not be added. - // TODO Remove when new Migration API is introduced. - public Table getTable(String tableName) { - return transaction.getTable(tableName); - } - - /** - * Checks if a Realm file can be advanced to a newer version. - */ - public boolean hasChanged() { - return sharedGroup.hasChanged(); - } - - /** - * Returns the version for the SharedGroup. - */ - public SharedGroup.VersionID getVersion() { - return sharedGroup.getVersion(); - } - - /** - * Makes the file writable. This will block all other threads and processes from making it writable as well. - */ - public void promoteToWrite() { - transaction.promoteToWrite(); - } - - /** - * Commits any pending changes to the file and return to read-only mode. - */ - public void commitAndContinueAsRead() { - transaction.commitAndContinueAsRead(); - } - - /** - * Rollbacks any changes to the file since it was made writable and continue in read-only mode. - */ - public void rollbackAndContinueAsRead() { - transaction.rollbackAndContinueAsRead(); - } - - /** - * Checks if a given table exists. - * - * @return {code true} if the table exists. {@code false} otherwise. - */ - public boolean hasTable(String tableName) { - return transaction.hasTable(tableName); - } - - /** - * Writes a copy of this Realm file to another location. - */ - public void copyToFile(File destination, byte[] key) throws IOException { - transaction.writeToFile(destination, key); - } - - /** - * Returns a reference to current {@link SharedGroup}. - */ - public SharedGroup getSharedGroup() { - return sharedGroup; - } - - /** - * Returns a reference to the current {@link ImplicitTransaction}. - */ - public ImplicitTransaction getTransaction() { - return transaction; - } - - /** - * Returns if the Realm is currently not in a transaction. - */ - public boolean isImmutable() { - return transaction.immutable; - } - - /** - * Compacts a Realm file. It cannot be open when calling this method. - * Returns true if compaction succeeded, false otherwise. - */ - public static boolean compact(RealmConfiguration configuration) { - SharedGroup sharedGroup = null; - boolean result = false; - try { - sharedGroup = new SharedGroup( - configuration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - SharedGroup.Durability.FULL, - configuration.getEncryptionKey()); - result = sharedGroup.compact(); - } catch (Exception e) { - RealmLog.i(e.getMessage()); - return false; - } finally { - if (sharedGroup != null) { - sharedGroup.close(); - } - } - return result; - } - - public long getNativePointer() { - return sharedGroup.getNativePointer(); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java new file mode 100644 index 0000000000..34f674e44b --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -0,0 +1,294 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import java.io.Closeable; +import java.io.File; + +import io.realm.RealmConfiguration; +import io.realm.internal.async.BadVersionException; + +public final class SharedRealm implements Closeable { + + public enum Durability { + FULL(0), + MEM_ONLY(1); + + final int value; + + Durability(int value) { + this.value = value; + } + } + + // Public for static checking in JNI + @SuppressWarnings("WeakerAccess") + public static final byte SCHEMA_MODE_VALUE_AUTOMATIC = 0; + @SuppressWarnings("WeakerAccess") + public static final byte SCHEMA_MODE_VALUE_READONLY = 1; + @SuppressWarnings("WeakerAccess") + public static final byte SCHEMA_MODE_VALUE_RESET_FILE = 2; + @SuppressWarnings("WeakerAccess") + public static final byte SCHEMA_MODE_VALUE_ADDITIVE = 3; + @SuppressWarnings("WeakerAccess") + public static final byte SCHEMA_MODE_VALUE_MANUAL = 4; + @SuppressWarnings("WeakerAccess") + public enum SchemaMode { + SCHEMA_MODE_AUTOMATIC(SCHEMA_MODE_VALUE_AUTOMATIC), + SCHEMA_MODE_READONLY(SCHEMA_MODE_VALUE_READONLY), + SCHEMA_MODE_RESET_FILE(SCHEMA_MODE_VALUE_RESET_FILE), + SCHEMA_MODE_ADDITIVE(SCHEMA_MODE_VALUE_ADDITIVE), + SCHEMA_MODE_MANUAL(SCHEMA_MODE_VALUE_MANUAL); + + final byte value; + SchemaMode(byte value) { + this .value = value; + } + } + + public static class VersionID implements Comparable { + final long version; + final long index; + + VersionID(long version, long index) { + this.version = version; + this.index = index; + } + + @Override + public int compareTo(@SuppressWarnings("NullableProblems") VersionID another) { + if (another == null) { + throw new IllegalArgumentException("Version cannot be compared to a null value."); + } + if (version > another.version) { + return 1; + } else if (version < another.version) { + return -1; + } else { + return 0; + } + } + + @Override + public String toString() { + return "VersionID{" + + "version=" + version + + ", index=" + index + + '}'; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + + VersionID versionID = (VersionID) object; + return (version == versionID.version && index == versionID.index); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (int) (version ^ (version >>> 32)); + result = 31 * result + (int) (index ^ (index >>> 32)); + return result; + } + } + + private long nativePtr; + private RealmConfiguration configuration; + final Context context; + + private SharedRealm(long nativePtr, RealmConfiguration configuration) { + this.nativePtr = nativePtr; + this.configuration = configuration; + context = new Context(); + } + + public static SharedRealm getInstance(RealmConfiguration config) { + long nativeConfigPtr = nativeCreateConfig( + config.getPath(), + config.getEncryptionKey(), + SchemaMode.SCHEMA_MODE_MANUAL.value, + config.getDurability() == Durability.MEM_ONLY, + false, + false, + false); + try { + return new SharedRealm(nativeGetSharedRealm(nativeConfigPtr), config); + } finally { + nativeCloseConfig(nativeConfigPtr); + } + } + + long getNativePtr() { + return nativePtr; + } + + public void beginTransaction() { + nativeBeginTransaction(nativePtr); + } + + public void commitTransaction() { + nativeCommitTransaction(nativePtr); + } + + public void cancelTransaction() { + nativeCancelTransaction(nativePtr); + } + + public boolean isInTransaction() { + return nativeIsInTransaction(nativePtr); + } + + public long getSchemaVersion() { + return nativeGetVersion(nativePtr); + } + + // FIXME: This should be removed, migratePrimaryKeyTableIfNeeded is using it which should be in Object Store instead? + long getGroupNative() { + return nativeReadGroup(nativePtr); + } + + public boolean hasTable(String name) { + return nativeHasTable(nativePtr, name); + } + + public Table getTable(String name) { + return new Table(this, nativeGetTable(nativePtr, name)); + } + + public void renameTable(String oldName, String newName) { + nativeRenameTable(nativePtr, oldName, newName); + } + + public void removeTable(String name) { + nativeRemoveTable(nativePtr, name); + } + + public String getTableName(int index) { + return nativeGetTableName(nativePtr, index); + } + + public long size() { + return nativeSize(nativePtr); + } + + public String getPath() { + return configuration.getPath(); + } + + public boolean isEmpty() { + return nativeIsEmpty(nativePtr); + } + + public void refresh() { + nativeRefresh(nativePtr); + } + + public void refresh(SharedRealm.VersionID version) throws BadVersionException { + // FIXME: This will have a different behaviour compared to refresh to the latest version. + // In the JNI this will just advance read the corresponding SharedGroup to the specific version without notifier + // or transact log observer involved. Before we use notification & fine grained notification from OS, it is not + // a problem. + nativeRefresh(nativePtr, version.version, version.index); + } + + public SharedRealm.VersionID getVersionID() { + long[] versionId = nativeGetVersionID (nativePtr); + return new SharedRealm.VersionID(versionId[0], versionId[1]); + } + + public boolean isClosed() { + return nativePtr == 0 || nativeIsClosed(nativePtr); + } + + public void writeCopy(File file, byte[] key) { + if (file.isFile() && file.exists()) { + throw new IllegalArgumentException("The destination file must not exist"); + } + nativeWriteCopy(nativePtr, file.getAbsolutePath(), key); + } + + public boolean waitForChange() { + return nativeWaitForChange(nativePtr); + } + + public void stopWaitForChange() { + nativeStopWaitForChange(nativePtr); + } + + public boolean compact() { + return nativeCompact(nativePtr); + } + + @Override + public void close() { + synchronized (context) { + if (nativePtr != 0) { + nativeCloseSharedRealm(nativePtr); + nativePtr = 0; + } + } + } + + @Override + protected void finalize() throws Throwable { + synchronized (context) { + close(); + // FIXME: Below is the original implementation of SharedGroup.finalize(). + // And actually Context.asyncDisposeSharedGroup will simply call nativeClose which is not asyc at all. + // IMO since this implemented Closeable already, it makes no sense to implement finalize. + // Just keep the logic the same for now and make nativeClose private. Rethink about this when cleaning + // up finalizers. + //context.asyncDisposeSharedRealm(nativePtr); + } + super.finalize(); + } + + private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory, + boolean cache, boolean disableFormatUpgrade, + boolean autoChangeNotification); + private static native void nativeCloseConfig(long nativeConfigPtr); + private static native long nativeGetSharedRealm(long nativeConfigPtr); + private static native void nativeCloseSharedRealm(long nativeSharedRealmPtr); + private static native boolean nativeIsClosed(long nativeSharedRealmPtr); + private static native void nativeBeginTransaction(long nativeSharedRealmPtr); + private static native void nativeCommitTransaction(long nativeSharedRealmPtr); + private static native void nativeCancelTransaction(long nativeSharedRealmPtr); + private static native boolean nativeIsInTransaction(long nativeSharedRealmPtr); + private static native long nativeGetVersion(long nativeSharedRealmPtr); + private static native long nativeReadGroup(long nativeSharedRealmPtr); + private static native boolean nativeIsEmpty(long nativeSharedRealmPtr); + private static native void nativeRefresh(long nativeSharedRealmPtr); + private static native void nativeRefresh(long nativeSharedRealmPtr, long version, long index); + private static native long[] nativeGetVersionID(long nativeSharedRealmPtr); + private static native long nativeGetTable(long nativeSharedRealmPtr, String tableName); + private static native String nativeGetTableName(long nativeSharedRealmPtr, int index); + private static native boolean nativeHasTable(long nativeSharedRealmPtr, String tableName); + private static native void nativeRenameTable(long nativeSharedRealmPtr, String oldTableName, String newTableName); + private static native void nativeRemoveTable(long nativeSharedRealmPtr, String tableName); + private static native long nativeSize(long nativeSharedRealmPtr); + private static native void nativeWriteCopy(long nativeSharedRealmPtr, String path, byte[] key); + private static native boolean nativeWaitForChange(long nativeSharedRealmPtr); + private static native void nativeStopWaitForChange(long nativeSharedRealmPtr); + private static native boolean nativeCompact(long nativeSharedRealmPtr); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 84b8696654..5b3c1bab4a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -16,15 +16,12 @@ package io.realm.internal; -import java.io.Closeable; import java.util.Date; -import java.util.concurrent.atomic.AtomicInteger; import io.realm.RealmFieldType; import io.realm.Sort; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; -import io.realm.internal.log.RealmLog; /** @@ -32,12 +29,14 @@ * (define/insert/delete/update) a table has. All the native communications to the Realm C++ library are also handled by * this class. */ -public class Table implements TableOrView, TableSchema, Closeable { +public class Table implements TableOrView, TableSchema { public static final int TABLE_MAX_LENGTH = 56; // Max length of class names without prefix public static final String TABLE_PREFIX = Util.getTablePrefix(); public static final long INFINITE = -1; + @SuppressWarnings("WeakerAccess") public static final String STRING_DEFAULT_VALUE = ""; + @SuppressWarnings("WeakerAccess") public static final long INTEGER_DEFAULT_VALUE = 0; public static final String METADATA_TABLE_NAME = "metadata"; public static final boolean NULLABLE = true; @@ -50,16 +49,11 @@ public class Table implements TableOrView, TableSchema, Closeable { private static final long PRIMARY_KEY_FIELD_COLUMN_INDEX = 1; private static final long NO_PRIMARY_KEY = -2; - protected long nativePtr; - protected final Object parent; + long nativePtr; private final Context context; + private final SharedRealm sharedRealm; private long cachedPrimaryKeyColumnIndex = NO_MATCH; - // test: - protected int tableNo; - private static final boolean DEBUG = false; - static AtomicInteger tableCount = new AtomicInteger(0); - static { RealmCore.loadLibrary(); } @@ -69,7 +63,6 @@ public class Table implements TableOrView, TableSchema, Closeable { * allowed only for empty tables. It creates a native reference of the object and keeps a reference to it. */ public Table() { - this.parent = null; // No parent in free-standing table this.context = new Context(); // Native methods work will be initialized here. Generated classes will // have nothing to do with the native functions. Generated Java Table @@ -78,20 +71,17 @@ public Table() { if (nativePtr == 0) { throw new java.lang.OutOfMemoryError("Out of native memory."); } - if (DEBUG) { - tableNo = tableCount.incrementAndGet(); - RealmLog.d("====== New Tablebase " + tableNo + " : ptr = " + nativePtr); - } + sharedRealm = null; } - Table(Context context, Object parent, long nativePointer) { - this.context = context; - this.parent = parent; + Table(Table parent, long nativePointer) { + this(parent.sharedRealm, nativePointer); + } + + Table(SharedRealm sharedRealm, long nativePointer) { + this.context = sharedRealm.context; + this.sharedRealm = sharedRealm; this.nativePtr = nativePointer; - if (DEBUG) { - tableNo = tableCount.incrementAndGet(); - RealmLog.d("===== New Tablebase(ptr) " + tableNo + " : ptr = " + nativePtr); - } } @Override @@ -103,34 +93,16 @@ public long getNativeTablePointer() { return nativePtr; } - // If close() is called, no penalty is paid for delayed disposal - // via the context @Override - public void close() { + protected void finalize() throws Throwable { synchronized (context) { if (nativePtr != 0) { - nativeClose(nativePtr); - if (DEBUG) { - tableCount.decrementAndGet(); - RealmLog.d("==== CLOSE " + tableNo + " ptr= " + nativePtr + " remaining " + tableCount.get()); - } - nativePtr = 0; - } - } - } - - @Override - protected void finalize() { - synchronized (context) { - if (nativePtr != 0) { - boolean isRoot = (parent == null); - context.asyncDisposeTable(nativePtr, isRoot); + // Don't dispose the table immediately if it is created from a SharedRealm to avoid long run finalizer. + context.asyncDisposeTable(nativePtr, sharedRealm == null); nativePtr = 0; // Set to 0 if finalize is called before close() for some reason } } - if (DEBUG) { - RealmLog.d("==== FINALIZE " + tableNo + "..."); - } + super.finalize(); } /* @@ -139,7 +111,6 @@ protected void finalize() { * You can no longer perform any actions on the table, and if done anyway, an exception is thrown. * The only method you can call is 'isValid()'. */ - public boolean isValid() { return nativePtr != 0 && nativeIsValid(nativePtr); } @@ -227,8 +198,8 @@ public void removeColumn(long columnIndex) { * * @param columnIndex the column index to be renamed. * @param newName a new name replacing the old column name. - * @throws {@link IllegalArgumentException} if {@code newFieldName} is an empty string, or exceeds field name length limit. - * @throws {@link IllegalStateException} if a PrimaryKey column name could not be found in the meta table, but {@link #getPrimaryKey()} returns an index. + * @throws IllegalArgumentException if {@code newFieldName} is an empty string, or exceeds field name length limit. + * @throws IllegalStateException if a PrimaryKey column name could not be found in the meta table, but {@link #getPrimaryKey()} returns an index. */ @Override public void renameColumn(long columnIndex, String newName) { @@ -247,6 +218,10 @@ public void renameColumn(long columnIndex, String newName) { try { String className = tableNameToClassName(getName()); Table pkTable = getPrimaryKeyTable(); + if (pkTable == null) { + throw new IllegalStateException( + "Table is not created from a SharedRealm, primary key is not available"); + } long pkRowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, className); if (pkRowIndex != NO_MATCH) { pkTable.setString(PRIMARY_KEY_FIELD_COLUMN_INDEX, pkRowIndex, newName); @@ -484,6 +459,7 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { return rowIndex; } + @SuppressWarnings("WeakerAccess") public long addEmptyRows(long rows) { checkImmutable(); if (rows < 1) { @@ -592,48 +568,6 @@ private boolean isPrimaryKeyColumn(long columnIndex) { return columnIndex == getPrimaryKey(); } - /** - * Returns a view sorted by the specified column and order. - * - * @param columnIndex the column index. - * @param sortOrder the sort order. - * @return a sorted view. - */ - public TableView getSortedView(long columnIndex, Sort sortOrder){ - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); - long nativeViewPtr = nativeGetSortedView(nativePtr, columnIndex, sortOrder.getValue()); - try { - return new TableView(this.context, this, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } - } - - /** - * Returns a view sorted by the specified column by the default order. - * - * @param columnIndex the column index. - * @return a sorted view. - */ - public TableView getSortedView(long columnIndex) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); - long nativeViewPtr = nativeGetSortedView(nativePtr, columnIndex, true); - return new TableView(this.context, this, nativeViewPtr); - } - - public TableView getSortedView(long columnIndices[], Sort sortOrders[]) { - context.executeDelayedDisposal(); - boolean[] nativeSortOrder = new boolean[sortOrders.length]; - for (int i = 0; i < sortOrders.length; i++) { - nativeSortOrder[i] = sortOrders[i].getValue(); - } - long nativeViewPtr = nativeGetSortedViewMulti(nativePtr, columnIndices, nativeSortOrder); - return new TableView(this.context, this, nativeViewPtr); - } - /** * Returns the column index for the primary key. * @@ -667,7 +601,7 @@ public long getPrimaryKey() { * @param columnIndex the index of column in the table. * @return {@code true} if column is a primary key, {@code false} otherwise. */ - public boolean isPrimaryKey(long columnIndex) { + private boolean isPrimaryKey(long columnIndex) { return columnIndex >= 0 && columnIndex == getPrimaryKey(); } @@ -768,22 +702,6 @@ public String getString(long columnIndex, long rowIndex) { return nativeGetString(nativePtr, columnIndex, rowIndex); } - /** - * Gets the value of a (binary) cell. - * - * @param columnIndex 0 based index value of the cell column. - * @param rowIndex 0 based index value of the cell row. - * @return value of the particular cell. - */ - /* - @Override - public ByteBuffer getBinaryByteBuffer(long columnIndex, long rowIndex) { - return nativeGetByteBuffer(nativePtr, columnIndex, rowIndex); - } - - protected native ByteBuffer nativeGetByteBuffer(long nativeTablePtr, long columnIndex, long rowIndex); - */ - @Override public byte[] getBinaryByteArray(long columnIndex, long rowIndex) { return nativeGetByteArray(nativePtr, columnIndex, rowIndex); @@ -799,7 +717,7 @@ public Table getLinkTarget(long columnIndex) { long nativeTablePointer = nativeGetLinkTarget(nativePtr, columnIndex); try { // Copy context reference from parent - return new Table(context, this.parent, nativeTablePointer); + return new Table(this.sharedRealm, nativeTablePointer); } catch (RuntimeException e) { Table.nativeClose(nativeTablePointer); @@ -898,30 +816,6 @@ public void setString(long columnIndex, long rowIndex, String value) { } } - /** - * Sets the value for a (binary) cell. - * - * @param columnIndex column index of the cell. - * @param rowIndex row index of the cell. - * @param data the ByteBuffer must be allocated with {@code ByteBuffer.allocateDirect(len)}. - */ - - /* - @Override - public void setBinaryByteBuffer(long columnIndex, long rowIndex, ByteBuffer data) { - if (immutable) throwImmutable(); - if (data == null) - throw new IllegalArgumentException("Null array"); - if (data.isDirect()) - nativeSetByteBuffer(nativePtr, columnIndex, rowIndex, data); - else - throw new RuntimeException("Currently ByteBuffer must be allocateDirect()."); // FIXME: support other than allocateDirect - } - - protected native void nativeSetByteBuffer(long nativeTablePtr, long columnIndex, long rowIndex, ByteBuffer data); - */ - - @Override public void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data) { checkImmutable(); @@ -948,7 +842,7 @@ public void removeSearchIndex(long columnIndex) { * * @param columnName the name of the field that will function primary key. "" or {@code null} will remove any * previous set magic key. - * @throws {@link io.realm.exceptions.RealmException} if it is not possible to set the primary key due to the column + * @throws io.realm.exceptions.RealmException if it is not possible to set the primary key due to the column * not having distinct values (i.e. violating the primary key constraint). */ public void setPrimaryKey(String columnName) { @@ -964,17 +858,15 @@ public void setPrimaryKey(long columnIndex) { } private Table getPrimaryKeyTable() { - Group group = getTableGroup(); - if (group == null) { + if (sharedRealm == null) { return null; } - - Table pkTable = group.getTable(PRIMARY_KEY_TABLE_NAME); + Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); if (pkTable.getColumnCount() == 0) { pkTable.addColumn(RealmFieldType.STRING, PRIMARY_KEY_CLASS_COLUMN_NAME); pkTable.addColumn(RealmFieldType.STRING, PRIMARY_KEY_FIELD_COLUMN_NAME); } else { - migratePrimaryKeyTableIfNeeded(group, pkTable); + migratePrimaryKeyTableIfNeeded(sharedRealm.getGroupNative(), pkTable); } return pkTable; @@ -996,19 +888,8 @@ private void invalidateCachedPrimaryKeyIndex() { * This will remove the prefix "class_" from all table names in the pk_column * Any database created on Realm-Java 0.84.1 and below will have this error. */ - private void migratePrimaryKeyTableIfNeeded(Group group, Table pkTable) { - nativeMigratePrimaryKeyTableIfNeeded(group.nativePtr, pkTable.nativePtr); - } - - // Recursively look at parents until either a Group or null is found - Group getTableGroup() { - if (parent instanceof Group) { - return (Group) parent; - } else if (parent instanceof Table) { - return ((Table) parent).getTableGroup(); - } else { - return null; // Free table - } + private void migratePrimaryKeyTableIfNeeded(long groupNativePtr, Table pkTable) { + nativeMigratePrimaryKeyTableIfNeeded(groupNativePtr, pkTable.nativePtr); } public boolean hasSearchIndex(long columnIndex) { @@ -1024,13 +905,10 @@ public void nullifyLink(long columnIndex, long rowIndex) { } boolean isImmutable() { - if (!(parent instanceof Table)) { - return parent != null && ((Group) parent).immutable; - } else { - return ((Table)parent).isImmutable(); - } + return sharedRealm != null && !sharedRealm.isInTransaction(); } + // This checking should be moved to SharedRealm level void checkImmutable() { if (isImmutable()) { throwImmutable(); @@ -1328,13 +1206,6 @@ public String getName() { return nativeGetName(nativePtr); } - - // Optimize - public void optimize() { - checkImmutable(); - nativeOptimize(nativePtr); - } - @Override public String toJson() { return nativeToJson(nativePtr); @@ -1351,7 +1222,7 @@ public String toString() { } if (hasPrimaryKey()) { String pkFieldName = getColumnName(getPrimaryKey()); - stringBuilder.append("has \'" + pkFieldName + "\' field as a PrimaryKey, and "); + stringBuilder.append("has \'").append(pkFieldName).append("\' field as a PrimaryKey, and "); } stringBuilder.append("contains "); stringBuilder.append(columnCount); @@ -1420,9 +1291,9 @@ public static String tableNameToClassName(String tableName) { } protected native long createNative(); + // Free the underlying table ref. It is important that the nativeTablePtr become a invalid pointer after return. static native void nativeClose(long nativeTablePtr); private native boolean nativeIsValid(long nativeTablePtr); - private native boolean nativeIsRootTable(long nativeTablePtr); private native long nativeAddColumn(long nativeTablePtr, int type, String name, boolean isNullable); private native long nativeAddColumnLink(long nativeTablePtr, int type, String name, long targetTablePtr); private native void nativeRenameColumn(long nativeTablePtr, long columnIndex, String name); @@ -1440,7 +1311,6 @@ public static String tableNameToClassName(String tableName) { private native void nativeRemoveLast(long nativeTablePtr); private native void nativeMoveLastOver(long nativeTablePtr, long rowIndex); public static native long nativeAddEmptyRow(long nativeTablePtr, long rows); - private native long nativeGetSortedView(long nativeTableViewPtr, long columnIndex, boolean ascending); private native long nativeGetSortedViewMulti(long nativeTableViewPtr, long[] columnIndices, boolean[] ascending); private native long nativeGetLong(long nativeTablePtr, long columnIndex, long rowIndex); private native boolean nativeGetBoolean(long nativeTablePtr, long columnIndex, long rowIndex); @@ -1499,14 +1369,14 @@ public static String tableNameToClassName(String tableName) { private native long nativeFindAllBool(long nativePtr, long columnIndex, boolean value); private native long nativeFindAllFloat(long nativePtr, long columnIndex, float value); private native long nativeFindAllDouble(long nativePtr, long columnIndex, double value); - private native long nativeFindAllTimestamp(long nativePtr, long columnIndex, long dateTimeValue); + // FIXME: Disabled in cpp code, see comments there + // private native long nativeFindAllTimestamp(long nativePtr, long columnIndex, long dateTimeValue); private native long nativeFindAllString(long nativePtr, long columnIndex, String value); private native long nativeLowerBoundInt(long nativePtr, long columnIndex, long value); private native long nativeUpperBoundInt(long nativePtr, long columnIndex, long value); private native void nativePivot(long nativeTablePtr, long stringCol, long intCol, int pivotType, long resultPtr); private native long nativeGetDistinctView(long nativePtr, long columnIndex); private native String nativeGetName(long nativeTablePtr); - private native void nativeOptimize(long nativeTablePtr); private native String nativeToJson(long nativeTablePtr); private native boolean nativeHasSameSchema(long thisTable, long otherTable); private native long nativeVersion(long nativeTablePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java b/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java index 9157aa37ed..ff39eb50ef 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java @@ -37,8 +37,6 @@ public interface TableOrView { */ Table getTable(); - void close(); - /** * Returns the number of entries of the table/view. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 7d550ea226..cb6bd376c1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -433,16 +433,13 @@ public long find() { /** * Performs a find query then handover the resulted Row (ready to be imported by another thread/shared_group). * - * @param bgSharedGroupPtr current shared_group from which to operate the query. - * @param nativeReplicationPtr replication pointer associated with the shared_group. + * @param sharedRealm current {@link SharedRealm }from which to operate the query. * @param ptrQuery query to run the the find against. * @return pointer to the handover result (table_view). */ - public long findWithHandover(long bgSharedGroupPtr, long nativeReplicationPtr, long ptrQuery) { - validateQuery(); + public static long findWithHandover(SharedRealm sharedRealm, long ptrQuery) { // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); - return nativeFindWithHandover(bgSharedGroupPtr, ptrQuery, 0); + return nativeFindWithHandover(sharedRealm.getNativePtr(), ptrQuery, 0); } public TableView findAll(long start, long end, long limit) { @@ -476,45 +473,39 @@ public TableView findAll() { // handover find* methods // this will use a background SharedGroup to import the query (using the handover object) // run the query, and return the table view to the caller SharedGroup using the handover object. - public long findAllWithHandover(long bgSharedGroupPtr, long nativeReplicationPtr, long ptrQuery) throws BadVersionException { - validateQuery(); - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); - return nativeFindAllWithHandover(bgSharedGroupPtr, ptrQuery, 0, Table.INFINITE, Table.INFINITE); + public static long findAllWithHandover(SharedRealm sharedRealm, long ptrQuery) throws BadVersionException { + return nativeFindAllWithHandover(sharedRealm.getNativePtr(), ptrQuery, 0, Table.INFINITE, Table.INFINITE); } - public long findDistinctWithHandover(long bgSharedGroupPtr, long nativeReplicationPtr, long ptrQuery, long columnIndex) throws BadVersionException { - validateQuery(); - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); - return nativeGetDistinctViewWithHandover(bgSharedGroupPtr, ptrQuery, columnIndex); + public static long findDistinctWithHandover(SharedRealm sharedRealm, long ptrQuery, long columnIndex) throws BadVersionException { + return nativeGetDistinctViewWithHandover(sharedRealm.getNativePtr(), ptrQuery, columnIndex); } - public long findAllSortedWithHandover(long bgSharedGroupPtr, long nativeReplicationPtr, long ptrQuery, long columnIndex, Sort sortOrder) throws BadVersionException { - validateQuery(); - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); - return nativeFindAllSortedWithHandover(bgSharedGroupPtr, ptrQuery, 0, Table.INFINITE, Table.INFINITE, columnIndex, sortOrder.getValue()); + public static long findAllSortedWithHandover(SharedRealm sharedRealm, long ptrQuery, long columnIndex, Sort sortOrder) throws BadVersionException { + return nativeFindAllSortedWithHandover(sharedRealm.getNativePtr(), ptrQuery, 0, Table.INFINITE, Table.INFINITE, columnIndex, sortOrder.getValue()); } - public long findAllMultiSortedWithHandover(long bgSharedGroupPtr, long nativeReplicationPtr, long ptrQuery, long[] columnIndices, Sort[] sortOrders) throws BadVersionException { - validateQuery(); - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); + public static long findAllMultiSortedWithHandover(SharedRealm sharedRealm, long ptrQuery, long[] columnIndices, Sort[] sortOrders) throws BadVersionException { boolean[] ascendings = getNativeSortOrderValues(sortOrders); - return nativeFindAllMultiSortedWithHandover(bgSharedGroupPtr, ptrQuery, 0, Table.INFINITE, Table.INFINITE, columnIndices, ascendings); + return nativeFindAllMultiSortedWithHandover(sharedRealm.getNativePtr(), ptrQuery, 0, Table.INFINITE, Table.INFINITE, columnIndices, ascendings); } + public static long[] batchUpdateQueries(SharedRealm sharedRealm, long[] handoverQueries, long[][] parameters, + long[][] queriesParameters, boolean[][] multiSortOrder) + throws BadVersionException { + return nativeBatchUpdateQueries(sharedRealm.getNativePtr(), handoverQueries, parameters, queriesParameters, + multiSortOrder); + } /** * Imports a TableView from a worker thread to the caller thread. * * @param handoverPtr pointer to the handover object - * @param callerSharedGroupPtr pointer to the SharedGroup on the caller thread. + * @param sharedRealm the SharedRealm on the caller thread. * @return the TableView on the caller thread. * @throws BadVersionException if the worker thread and caller thread are not at the same version. */ - public TableView importHandoverTableView(long handoverPtr, long callerSharedGroupPtr) throws BadVersionException { - long nativeTvPtr = nativeImportHandoverTableViewIntoSharedGroup(handoverPtr, callerSharedGroupPtr); + public TableView importHandoverTableView(long handoverPtr, SharedRealm sharedRealm) throws BadVersionException { + long nativeTvPtr = nativeImportHandoverTableViewIntoSharedGroup(handoverPtr, sharedRealm.getNativePtr()); try { return new TableView(this.context, this.table, nativeTvPtr); } catch (RuntimeException e) { @@ -525,14 +516,25 @@ public TableView importHandoverTableView(long handoverPtr, long callerSharedGrou } } + /** + * Imports a row from a worker thread to the caller thread. + * + * @param handoverRowPtr pointer to the handover row object + * @param sharedRealm the SharedRealm on the caller thread. + * @return the row pointer on the caller thread. + */ + public static long importHandoverRow(long handoverRowPtr, SharedRealm sharedRealm) { + return nativeImportHandoverRowIntoSharedGroup(handoverRowPtr, sharedRealm.getNativePtr()); + } + /** * Handovers the query, so it can be used by other SharedGroup (in different thread) * - * @param callerSharedGroupPtr native pointer to the SharedGroup holding the query + * @param sharedRealm the SharedGroup holding the query * @return native pointer to the handover query */ - public long handoverQuery(long callerSharedGroupPtr) { - return nativeHandoverQuery(callerSharedGroupPtr, nativePtr); + public long handoverQuery(SharedRealm sharedRealm) { + return nativeHandoverQuery(sharedRealm.getNativePtr(), nativePtr); } // @@ -807,14 +809,14 @@ private void throwImmutable() { private native void nativeIsNotNull(long nativePtr, long columnIndices[]); private native long nativeCount(long nativeQueryPtr, long start, long end, long limit); private native long nativeRemove(long nativeQueryPtr, long start, long end, long limit); - private native long nativeImportHandoverTableViewIntoSharedGroup(long handoverTableViewPtr, long callerSharedGroupPtr) throws BadVersionException; - private native long nativeHandoverQuery(long callerSharedGroupPtr, long nativeQueryPtr); - public static native long nativeFindAllSortedWithHandover(long bgSharedGroupPtr, long nativeQueryPtr, long start, long end, long limit, long columnIndex, boolean ascending) throws BadVersionException; - public static native long nativeFindAllWithHandover(long bgSharedGroupPtr, long nativeQueryPtr, long start, long end, long limit) throws BadVersionException; - public static native long nativeGetDistinctViewWithHandover(long bgSharedGroupPtr, long nativeQueryPtr, long columnIndex) throws BadVersionException; - public static native long nativeFindWithHandover(long bgSharedGroupPtr, long nativeQueryPtr, long fromTableRow); - public static native long nativeFindAllMultiSortedWithHandover(long bgSharedGroupPtr, long nativeQueryPtr, long start, long end, long limit, long[] columnIndices, boolean[] ascending) throws BadVersionException; - public static native long nativeImportHandoverRowIntoSharedGroup(long handoverRowPtr, long callerSharedGroupPtr); + private native long nativeImportHandoverTableViewIntoSharedGroup(long handoverTableViewPtr, long callerSharedRealmPtr) throws BadVersionException; + private native long nativeHandoverQuery(long callerSharedRealmPtr, long nativeQueryPtr); + private static native long nativeFindAllSortedWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long start, long end, long limit, long columnIndex, boolean ascending) throws BadVersionException; + private static native long nativeFindAllWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long start, long end, long limit) throws BadVersionException; + private static native long nativeGetDistinctViewWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long columnIndex) throws BadVersionException; + private static native long nativeFindWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long fromTableRow); + private static native long nativeFindAllMultiSortedWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long start, long end, long limit, long[] columnIndices, boolean[] ascending) throws BadVersionException; + private static native long nativeImportHandoverRowIntoSharedGroup(long handoverRowPtr, long callerSharedRealmPtr); public static native void nativeCloseQueryHandover(long nativePtr); - public static native long[] nativeBatchUpdateQueries(long bgSharedGroupPtr, long[] handoverQueries, long[][] parameters, long[][] queriesParameters, boolean[][] multiSortOrder) throws BadVersionException; + private static native long[] nativeBatchUpdateQueries(long bgSharedRealmPtr, long[] handoverQueries, long[][] parameters, long[][] queriesParameters, boolean[][] multiSortOrder) throws BadVersionException; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableView.java b/realm/realm-library/src/main/java/io/realm/internal/TableView.java index df97a3fb06..db245e3fe6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableView.java @@ -31,7 +31,7 @@ * The view doesn't copy data from the table, but contains merely a list of row-references into the original table * with the real data. */ -public class TableView implements TableOrView, Closeable { +public class TableView implements TableOrView { private static final boolean DEBUG = false; //true; // Don't convert this into local variable and don't remove this. // Core requests TableView to hold the Query reference. @@ -74,20 +74,6 @@ public Table getTable() { return parent; } - @Override - public void close() { - synchronized (context) { - if (nativePtr != 0) { - nativeClose(nativePtr); - - if (DEBUG) { - RealmLog.d("==== TableView CLOSE, ptr= " + nativePtr); - } - nativePtr = 0; - } - } - } - @Override protected void finalize() { synchronized (context) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ReadTransaction.java b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java similarity index 55% rename from realm/realm-library/src/main/java/io/realm/internal/ReadTransaction.java rename to realm/realm-library/src/main/java/io/realm/internal/TestUtil.java index dac99bc040..4af5a15360 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ReadTransaction.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2016 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,15 @@ package io.realm.internal; -public class ReadTransaction extends Group { +class TestUtil { - private final SharedGroup db; - - ReadTransaction(Context context, SharedGroup db, long nativePointer) { - super(context, nativePointer, true); // make Group immutable - this.db = db; - } - - public void endRead() { - db.endRead(); - } - - @Override - public void close() { - db.endRead(); + static { + // Any internal class with static native methods that uses Realm Core must load the Realm Core library + // themselves as it otherwise might not have been loaded. + RealmCore.loadLibrary(); } - protected void finalize() {} // Nullify the actions of Group.finalize() + public native static long getMaxExceptionNumber(); + public native static String getExpectedMessage(long exceptionKind); + public native static void testThrowExceptions(long exceptionKind); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index d2d012f466..b867d42d6b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -24,7 +24,7 @@ * Wrapper around a Row in Realm Core. * * IMPORTANT: All access to methods using this class are non-checking. Safety guarantees are given by the annotation - * processor and {@link RealmProxyMediator#validateTable(Class, ImplicitTransaction)} which is called before the typed + * processor and {@link RealmProxyMediator#validateTable(Class, SharedRealm)} which is called before the typed * API can be used. * * For low-level access to Row data where error checking is required, use {@link CheckedRow}. diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 8d24bfe3b1..f6eb85b851 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -48,43 +48,6 @@ public static String getTablePrefix() { } static native String nativeGetTablePrefix(); - - // Testcases run in nativeCode - public enum Testcase { - Exception_ClassNotFound(0), - Exception_NoSuchField(1), - Exception_NoSuchMethod(2), - Exception_IllegalArgument(3), - Exception_IOFailed(4), - Exception_FileNotFound(5), - Exception_FileAccessError(6), - Exception_IndexOutOfBounds(7), - Exception_TableInvalid(8), - Exception_UnsupportedOperation(9), - Exception_OutOfMemory(10), - Exception_FatalError(11), - Exception_RuntimeError(12), - Exception_RowInvalid(13), - Exception_EncryptionNotSupported(14), - Exception_CrossTableLink(15), - Exception_BadVersion(16), - Exception_IncompatibleLockFile(17); - - private final int nativeTestcase; - Testcase(int nativeValue) { - this.nativeTestcase = nativeValue; - } - - public String expectedResult(long parm1) { - return nativeTestcase(nativeTestcase, false, parm1); - } - public String execute(long parm1) { - return nativeTestcase(nativeTestcase, true, parm1); - } - } - - static native String nativeTestcase(int testcase, boolean dotest, long parm1); - /** * Normalizes a input class to it's original RealmObject class so it is transparent whether or not the input class * was a RealmProxy class. diff --git a/realm/realm-library/src/main/java/io/realm/internal/WriteTransaction.java b/realm/realm-library/src/main/java/io/realm/internal/WriteTransaction.java deleted file mode 100644 index af3fae4a20..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/WriteTransaction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -public class WriteTransaction extends Group { - - private final SharedGroup db; - private boolean committed; - - public void commit() { - if (!committed) { - db.commit(); - committed = true; - } - else { - throw new IllegalStateException("You can only commit once after a WriteTransaction has been made."); - } - } - - public void rollback() { - db.rollback(); - } - - @Override - public void close() { - if (!committed) { - rollback(); - } - } - - WriteTransaction(Context context,SharedGroup db, long nativePtr) { - super(context, nativePtr, false); // Group is mutable - this.db = db; - committed = false; - } - - protected void finalize() {} // Nullify the actions of Group.finalize() -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java index a7be9d941b..f8f00fef87 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java @@ -28,7 +28,7 @@ import io.realm.RealmResults; import io.realm.internal.HandlerControllerConstants; import io.realm.internal.RealmObjectProxy; -import io.realm.internal.SharedGroup; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableQuery; import io.realm.internal.log.RealmLog; @@ -69,31 +69,28 @@ public static Builder.RealmConfigurationStep newBuilder() { @Override public void run() { - SharedGroup sharedGroup = null; + SharedRealm sharedRealm = null; try { - sharedGroup = new SharedGroup(realmConfiguration.getPath(), - SharedGroup.IMPLICIT_TRANSACTION, - realmConfiguration.getDurability(), - realmConfiguration.getEncryptionKey()); + sharedRealm = SharedRealm.getInstance(realmConfiguration); Result result; boolean updateSuccessful; if (updateMode == MODE_UPDATE_REALM_RESULTS) { result = Result.newRealmResultsResponse(); AlignedQueriesParameters alignedParameters = prepareQueriesParameters(); - long[] handoverTableViewPointer = TableQuery.nativeBatchUpdateQueries(sharedGroup.getNativePointer(), + long[] handoverTableViewPointer = TableQuery.batchUpdateQueries(sharedRealm, alignedParameters.handoverQueries, alignedParameters.queriesParameters, alignedParameters.multiSortColumnIndices, alignedParameters.multiSortOrder); swapPointers(result, handoverTableViewPointer); updateSuccessful = true; - result.versionID = sharedGroup.getVersion(); + result.versionID = sharedRealm.getVersionID(); } else { result = Result.newRealmObjectResponse(); - updateSuccessful = updateRealmObjectQuery(sharedGroup, result); - result.versionID = sharedGroup.getVersion(); + updateSuccessful = updateRealmObjectQuery(sharedRealm, result); + result.versionID = sharedRealm.getVersionID(); } Handler handler = callerHandler.get(); @@ -114,8 +111,8 @@ public void run() { } } finally { - if (sharedGroup != null) { - sharedGroup.close(); + if (sharedRealm != null) { + sharedRealm.close(); } } } @@ -184,13 +181,12 @@ private void swapPointers(Result result, long[] handoverTableViewPointer) { } } - private boolean updateRealmObjectQuery(SharedGroup sharedGroup, Result result) { + private boolean updateRealmObjectQuery(SharedRealm sharedRealm, Result result) { if (!isTaskCancelled()) { switch (realmObjectEntry.queryArguments.type) { case ArgumentsHolder.TYPE_FIND_FIRST: { - long handoverRowPointer = TableQuery. - nativeFindWithHandover(sharedGroup.getNativePointer(), - realmObjectEntry.handoverQueryPointer, 0); + long handoverRowPointer = TableQuery.findWithHandover(sharedRealm, + realmObjectEntry.handoverQueryPointer); result.updatedRow.put(realmObjectEntry.element, handoverRowPointer); break; } @@ -217,7 +213,7 @@ private boolean isAliveHandler(Handler handler) { public static class Result { public IdentityHashMap>, Long> updatedTableViews; public IdentityHashMap, Long> updatedRow; - public SharedGroup.VersionID versionID; + public SharedRealm.VersionID versionID; public static Result newRealmResultsResponse() { Result result = new Result(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index 662f8f732f..bbddbd6cea 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -32,9 +32,9 @@ import io.realm.Realm; import io.realm.RealmModel; import io.realm.internal.ColumnInfo; -import io.realm.internal.ImplicitTransaction; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.Util; @@ -58,15 +58,15 @@ public CompositeMediator(RealmProxyMediator... mediators) { } @Override - public Table createTable(Class clazz, ImplicitTransaction transaction) { + public Table createTable(Class clazz, SharedRealm sharedRealm) { RealmProxyMediator mediator = getMediator(clazz); - return mediator.createTable(clazz, transaction); + return mediator.createTable(clazz, sharedRealm); } @Override - public ColumnInfo validateTable(Class clazz, ImplicitTransaction transaction) { + public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm) { RealmProxyMediator mediator = getMediator(clazz); - return mediator.validateTable(clazz, transaction); + return mediator.validateTable(clazz, sharedRealm); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index 8d233a3e91..17db19b6d9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -32,9 +32,9 @@ import io.realm.Realm; import io.realm.RealmModel; import io.realm.internal.ColumnInfo; -import io.realm.internal.ImplicitTransaction; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.Util; @@ -73,15 +73,15 @@ public RealmProxyMediator getOriginalMediator() { } @Override - public Table createTable(Class clazz, ImplicitTransaction transaction) { + public Table createTable(Class clazz, SharedRealm sharedRealm) { checkSchemaHasClass(clazz); - return originalMediator.createTable(clazz, transaction); + return originalMediator.createTable(clazz, sharedRealm); } @Override - public ColumnInfo validateTable(Class clazz, ImplicitTransaction transaction) { + public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm) { checkSchemaHasClass(clazz); - return originalMediator.validateTable(clazz, transaction); + return originalMediator.validateTable(clazz, sharedRealm); } @Override From 814882be51f553c7b036390f7fa2158abc557a1f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 25 Aug 2016 19:47:23 +0800 Subject: [PATCH 0009/2110] Remove unused exception conversions --- .../src/main/cpp/io_realm_internal_Table.cpp | 3 +- .../main/cpp/io_realm_internal_TableView.cpp | 4 +- .../main/cpp/io_realm_internal_TestUtil.cpp | 45 ----------------- .../src/main/cpp/java_lang_List_Util.cpp | 46 ------------------ .../src/main/cpp/java_lang_List_Util.hpp | 33 ------------- realm/realm-library/src/main/cpp/util.cpp | 48 +------------------ realm/realm-library/src/main/cpp/util.hpp | 14 +----- 7 files changed, 7 insertions(+), 186 deletions(-) delete mode 100644 realm/realm-library/src/main/cpp/java_lang_List_Util.cpp delete mode 100644 realm/realm-library/src/main/cpp/java_lang_List_Util.hpp diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index d74bd51398..272dd33ec8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -18,7 +18,6 @@ #include "util.hpp" #include "io_realm_internal_Table.h" -#include "java_lang_List_Util.hpp" #include "tablebase_tpl.hpp" using namespace std; @@ -1545,7 +1544,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeVersion( bool valid = (TBL(nativeTablePtr) != NULL); if (valid) { if (!TBL(nativeTablePtr)->is_attached()) { - ThrowException(env, TableInvalid, "The Realm has been closed and is no longer accessible."); + ThrowException(env, IllegalState, "The Realm has been closed and is no longer accessible."); return 0; } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp index ba1bfc78b2..d4837d7465 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp @@ -29,7 +29,7 @@ inline bool view_valid_and_in_sync(JNIEnv* env, jlong nativeViewPtr) { bool valid = (TV(nativeViewPtr) != NULL); if (valid) { if (!TV(nativeViewPtr)->is_attached()) { - ThrowException(env, TableInvalid, "The Realm has been closed and is no longer accessible."); + ThrowException(env, IllegalState, "The Realm has been closed and is no longer accessible."); return false; } // depends_on_deleted_linklist() will return true if and only if the current TableView was created from a @@ -951,7 +951,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSyncIfNeeded( bool valid = (TV(nativeViewPtr) != NULL); if (valid) { if (!TV(nativeViewPtr)->is_attached()) { - ThrowException(env, TableInvalid, "The Realm has been closed and is no longer accessible."); + ThrowException(env, IllegalState, "The Realm has been closed and is no longer accessible."); return 0; } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp index d14615f5b6..0529180b94 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp @@ -32,46 +32,16 @@ throwOrGetExpectedMessage(JNIEnv *env, jlong testcase, bool should_throw) if (should_throw) ThrowException(env, ClassNotFound, "parm1", "parm2"); break; - case NoSuchField: - expect = "java.lang.NoSuchFieldException: Field 'parm2' could not be located in class io.realm.parm1"; - if (should_throw) - ThrowException(env, NoSuchField, "parm1", "parm2"); - break; - case NoSuchMethod: - expect = "java.lang.NoSuchMethodException: Method 'parm2' could not be located in class io.realm.parm1"; - if (should_throw) - ThrowException(env, NoSuchMethod, "parm1", "parm2"); - break; case IllegalArgument: expect = "java.lang.IllegalArgumentException: Illegal Argument: parm1"; if (should_throw) ThrowException(env, IllegalArgument, "parm1", "parm2"); break; - case IOFailed: - expect = "io.realm.exceptions.RealmIOException: Failed to open parm1. parm2"; - if (should_throw) - ThrowException(env, IOFailed, "parm1", "parm2"); - break; - case FileNotFound: - expect = "io.realm.exceptions.RealmIOException: File not found: parm1."; - if (should_throw) - ThrowException(env, FileNotFound, "parm1", "parm2"); - break; - case FileAccessError: - expect = "io.realm.exceptions.RealmIOException: Failed to access: parm1. parm2"; - if (should_throw) - ThrowException(env, FileAccessError, "parm1", "parm2"); - break; case IndexOutOfBounds: expect = "java.lang.ArrayIndexOutOfBoundsException: parm1"; if (should_throw) ThrowException(env, IndexOutOfBounds, "parm1", "parm2"); break; - case TableInvalid: - expect = "java.lang.IllegalStateException: Illegal State: parm1"; - if (should_throw) - ThrowException(env, TableInvalid, "parm1", "parm2"); - break; case UnsupportedOperation: expect = "java.lang.UnsupportedOperationException: parm1"; if (should_throw) @@ -92,26 +62,11 @@ throwOrGetExpectedMessage(JNIEnv *env, jlong testcase, bool should_throw) if (should_throw) ThrowException(env, RuntimeError, "parm1", "parm2"); break; - case RowInvalid: - expect = "java.lang.IllegalStateException: Illegal State: parm1"; - if (should_throw) - ThrowException(env, RowInvalid, "parm1", "parm2"); - break; - case CrossTableLink: - expect = "java.lang.IllegalStateException: This class is referenced by other classes. Remove those fields first before removing this class."; - if (should_throw) - ThrowException(env, CrossTableLink, "parm1"); - break; case BadVersion: expect = "io.realm.internal.async.BadVersionException: parm1"; if (should_throw) ThrowException(env, BadVersion, "parm1", "parm2"); break; - case LockFileError: - expect = "io.realm.exceptions.IncompatibleLockFileException: parm1"; - if (should_throw) - ThrowException(env, LockFileError, "parm1", "parm2"); - break; case IllegalState: expect = "java.lang.IllegalStateException: parm1"; if (should_throw) diff --git a/realm/realm-library/src/main/cpp/java_lang_List_Util.cpp b/realm/realm-library/src/main/cpp/java_lang_List_Util.cpp deleted file mode 100644 index d1a1853b28..0000000000 --- a/realm/realm-library/src/main/cpp/java_lang_List_Util.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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 "util.hpp" -#include "java_lang_List_Util.hpp" - -jint java_lang_List_size(JNIEnv* env, jobject jList) -{ - // WARNING: do not cache these methods, list class may be different based on the object jlist - jclass jListClass = env->GetObjectClass(jList); - if (jListClass == NULL) - return 0; - jmethodID jListSizeMethodId = env->GetMethodID(jListClass, "size", "()I"); - if (jListSizeMethodId == NULL) { - ThrowException(env, NoSuchMethod, "jList", "size"); - return 0; - } - return env->CallIntMethod(jList, jListSizeMethodId); -} - -jobject java_lang_List_get(JNIEnv* env, jobject jList, jint index) -{ - // WARNING: do not cache these methods/classes, list class may be different based on the object jlist - jclass jListClass = env->GetObjectClass(jList); - if (jListClass == NULL) - return NULL; - jmethodID jListGetMethodId = env->GetMethodID(jListClass, "get", "(I)Ljava/lang/Object;"); - if (jListGetMethodId == NULL) { - ThrowException(env, NoSuchMethod, "jList", "get"); - return NULL; - } - return env->CallObjectMethod(jList, jListGetMethodId, index); -} diff --git a/realm/realm-library/src/main/cpp/java_lang_List_Util.hpp b/realm/realm-library/src/main/cpp/java_lang_List_Util.hpp deleted file mode 100644 index a360a247b4..0000000000 --- a/realm/realm-library/src/main/cpp/java_lang_List_Util.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -#ifndef JAVA_LANG_LIST_UTIL_H -#define JAVA_LANG_LIST_UTIL_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -jint java_lang_List_size(JNIEnv* env, jobject jList); -jobject java_lang_List_get(JNIEnv* env, jobject jList, jint index); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index b5af9f0d4b..136056fae1 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -49,7 +49,7 @@ void ConvertException(JNIEnv* env, const char *file, int line) } catch (CrossTableLinkTarget& e) { ss << e.what() << " in " << file << " line " << line; - ThrowException(env, CrossTableLink, ss.str()); + ThrowException(env, IllegalState, ss.str()); } catch (SharedGroup::BadVersion& e) { ss << e.what() << " in " << file << " line " << line; @@ -96,41 +96,11 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla message = "Class '" + classStr + "' could not be located."; break; - case NoSuchField: - jExceptionClass = env->FindClass("java/lang/NoSuchFieldException"); - message = "Field '" + itemStr + "' could not be located in class io.realm." + classStr; - break; - - case NoSuchMethod: - jExceptionClass = env->FindClass("java/lang/NoSuchMethodException"); - message = "Method '" + itemStr + "' could not be located in class io.realm." + classStr; - break; - case IllegalArgument: jExceptionClass = env->FindClass("java/lang/IllegalArgumentException"); message = "Illegal Argument: " + classStr; break; - case TableInvalid: - jExceptionClass = env->FindClass("java/lang/IllegalStateException"); - message = "Illegal State: " + classStr; - break; - - case IOFailed: - jExceptionClass = env->FindClass("io/realm/exceptions/RealmIOException"); - message = "Failed to open " + classStr + ". " + itemStr; - break; - - case FileNotFound: - jExceptionClass = env->FindClass("io/realm/exceptions/RealmIOException"); - message = "File not found: " + classStr + "."; - break; - - case FileAccessError: - jExceptionClass = env->FindClass("io/realm/exceptions/RealmIOException"); - message = "Failed to access: " + classStr + ". " + itemStr; - break; - case IndexOutOfBounds: jExceptionClass = env->FindClass("java/lang/ArrayIndexOutOfBoundsException"); message = classStr; @@ -156,30 +126,16 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla message = classStr; break; - case RowInvalid: - jExceptionClass = env->FindClass("java/lang/IllegalStateException"); - message = "Illegal State: " + classStr; - break; - - case CrossTableLink: - jExceptionClass = env->FindClass("java/lang/IllegalStateException"); - message = "This class is referenced by other classes. Remove those fields first before removing this class."; - break; - case BadVersion: jExceptionClass = env->FindClass("io/realm/internal/async/BadVersionException"); message = classStr; break; - case LockFileError: - jExceptionClass = env->FindClass("io/realm/exceptions/IncompatibleLockFileException"); - message = classStr; - break; - case IllegalState: jExceptionClass = env->FindClass("java/lang/IllegalStateException"); message = classStr; break; + // Should never get here. case ExceptionKindMax: default: diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 8213a63b76..346595751b 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -86,25 +86,15 @@ std::string num_to_string(T pNumber) #define HO(T, ptr) reinterpret_cast* >(ptr) // Exception handling -// FIXME: RowInvalid and IllegalState both throw IllegalStateException, maybe remove the RowInvalid. enum ExceptionKind { ClassNotFound = 0, - NoSuchField, - NoSuchMethod, IllegalArgument, - IOFailed, - FileNotFound, - FileAccessError, IndexOutOfBounds, - TableInvalid, UnsupportedOperation, OutOfMemory, FatalError, RuntimeError, - RowInvalid, - CrossTableLink, BadVersion, - LockFileError, IllegalState, // NOTE!!!!: Please also add test cases to io_realm_internal_TestUtil when introducing a // new exception kind. @@ -220,7 +210,7 @@ inline bool TableIsValid(JNIEnv* env, T* objPtr) } if (!valid) { TR_ERR("Table %p is no longer attached!", VOID_PTR(objPtr)) - ThrowException(env, TableInvalid, "Table is no longer valid to operate on."); + ThrowException(env, IllegalState, "Table is no longer valid to operate on."); } return valid; } @@ -230,7 +220,7 @@ inline bool RowIsValid(JNIEnv* env, realm::Row* rowPtr) bool valid = (rowPtr != NULL && rowPtr->is_attached()); if (!valid) { TR_ERR("Row %p is no longer attached!", VOID_PTR(rowPtr)) - ThrowException(env, RowInvalid, "Object is no longer valid to operate on. Was it deleted by another thread?"); + ThrowException(env, IllegalState, "Object is no longer valid to operate on. Was it deleted by another thread?"); } return valid; } From 99740e5726012d33aa5c4e7633d0be495d889408 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 25 Aug 2016 22:12:37 +0800 Subject: [PATCH 0010/2110] Add RealmFileException to replace RealmIOException and IncompatibleLockFileException. Also it is mapped to the same name exception in ObjectStore to give user a detailed kind of file exception. --- CHANGELOG.md | 3 + .../java/io/realm/RealmCacheTests.java | 7 +- .../io/realm/RealmConfigurationTests.java | 5 +- .../java/io/realm/RealmInMemoryTest.java | 6 +- .../androidTest/java/io/realm/RealmTests.java | 18 +-- .../main/cpp/io_realm_internal_TestUtil.cpp | 3 + realm/realm-library/src/main/cpp/util.cpp | 38 +++++- realm/realm-library/src/main/cpp/util.hpp | 3 + .../src/main/java/io/realm/BaseRealm.java | 5 + .../src/main/java/io/realm/DynamicRealm.java | 4 +- .../src/main/java/io/realm/Realm.java | 15 ++- .../src/main/java/io/realm/RealmCache.java | 25 ++-- .../IncompatibleLockFileException.java | 35 ------ .../realm/exceptions/RealmFileException.java | 115 ++++++++++++++++++ .../io/realm/exceptions/RealmIOException.java | 42 ------- .../java/io/realm/internal/SharedRealm.java | 8 ++ 16 files changed, 224 insertions(+), 108 deletions(-) delete mode 100644 realm/realm-library/src/main/java/io/realm/exceptions/IncompatibleLockFileException.java create mode 100644 realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java delete mode 100644 realm/realm-library/src/main/java/io/realm/exceptions/RealmIOException.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 026e296c64..678e47647f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ * `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). * armeabi is not supported anymore. +* Added new `RealmFileException`. + - `IncompatibleLockFileException` has been removed and replaced by `RealmFileException` with kind `INCOMPATIBLE_LOCK_FILE`. + - `RealmIOExcpetion` has been removed and replaced by `RealmFileException`. ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index 2368a216eb..dcec37ea70 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -31,6 +31,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; +import io.realm.exceptions.RealmFileException; import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; @@ -105,7 +106,8 @@ public void getInstanceClearsCacheWhenFailed() { realm.close(); try { Realm.getInstance(configB); // Try to open with key 2 - } catch (IllegalArgumentException ignored) { + } catch (RealmFileException expected) { + assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR); // Delete Realm so key 2 works. This should work as a Realm shouldn't be cached // if initialization failed. assertTrue(Realm.deleteRealm(configA)); @@ -152,7 +154,8 @@ public void dontCacheWrongConfigurations() throws IOException { try { Realm.getInstance(wrongConfig); fail(); - } catch (IllegalArgumentException ignored) { + } catch (RealmFileException expected) { + assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR); } // Try again with proper key diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 3b58dcce74..1accf93f07 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -43,7 +43,7 @@ import io.realm.entities.HumanModule; import io.realm.entities.Owner; import io.realm.exceptions.RealmException; -import io.realm.exceptions.RealmIOException; +import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.modules.CompositeMediator; import io.realm.internal.modules.FilterableMediator; @@ -839,7 +839,8 @@ public void assetFileFakeFile() { try { Realm.getInstance(configuration); fail(); - } catch (RealmIOException ignored) { + } catch (RealmFileException expected) { + assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index 8057162511..c1a572e9c6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit; import io.realm.entities.Dog; +import io.realm.exceptions.RealmFileException; public class RealmInMemoryTest extends AndroidTestCase { @@ -165,8 +166,9 @@ public void testWriteCopyTo() { .encryptionKey(TestHelper.getRandomKey(42)) .build(); Realm.getInstance(wrongKeyConf); - fail("Realm.getInstance should fail with illegal argument"); - } catch (IllegalArgumentException ignored) { + fail("Realm.getInstance should fail with RealmFileException"); + } catch (RealmFileException expected) { + assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 11d1d0b858..1eb985e3f5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -40,7 +40,6 @@ import org.junit.runner.RunWith; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -85,9 +84,8 @@ import io.realm.entities.PrimaryKeyRequiredAsBoxedShort; import io.realm.entities.PrimaryKeyRequiredAsString; import io.realm.entities.StringOnly; -import io.realm.exceptions.RealmError; import io.realm.exceptions.RealmException; -import io.realm.exceptions.RealmIOException; +import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.internal.SharedRealm; import io.realm.internal.log.RealmLog; @@ -209,8 +207,11 @@ public void getInstance_writeProtectedFile() throws IOException { assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); - thrown.expect(IllegalArgumentException.class); - Realm.getInstance(new RealmConfiguration.Builder(folder).name(REALM_FILE).build()); + try { + Realm.getInstance(new RealmConfiguration.Builder(folder).name(REALM_FILE).build()); + } catch (RealmFileException expected) { + assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); + } } @Test @@ -222,8 +223,11 @@ public void getInstance_writeProtectedFileWithContext() throws IOException { assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); - thrown.expect(IllegalArgumentException.class); - Realm.getInstance(new RealmConfiguration.Builder(context, folder).name(REALM_FILE).build()); + try { + Realm.getInstance(new RealmConfiguration.Builder(context, folder).name(REALM_FILE).build()); + } catch (RealmFileException expected) { + assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); + } } @Test diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp index 0529180b94..674412dca7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp @@ -72,6 +72,9 @@ throwOrGetExpectedMessage(JNIEnv *env, jlong testcase, bool should_throw) if (should_throw) ThrowException(env, IllegalState, "parm1"); break; + // FIXME: This is difficult to test right now. Need to refactor the test. + // See https://github.com/realm/realm-java/issues/3348 + // case RealmFileError: default: break; } diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 136056fae1..1318605529 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -23,6 +23,7 @@ #include "util.hpp" #include "io_realm_internal_Util.h" +#include "io_realm_internal_SharedRealm.h" #include "shared_realm.hpp" using namespace std; @@ -37,6 +38,8 @@ jmethodID java_lang_float_init; jclass java_lang_double; jmethodID java_lang_double_init; +void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind); + void ConvertException(JNIEnv* env, const char *file, int line) { ostringstream ss; @@ -61,7 +64,7 @@ void ConvertException(JNIEnv* env, const char *file, int line) } catch (RealmFileException& e) { ss << e.what() << " in " << file << " line " << line; - ThrowException(env, IllegalArgument, ss.str()); + ThrowRealmFileException(env, ss.str(), e.kind()); } catch (InvalidTransactionException& e) { ss << e.what() << " in " << file << " line " << line; @@ -152,6 +155,39 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla env->DeleteLocalRef(jExceptionClass); } +void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind) +{ + jclass cls = env->FindClass("io/realm/exceptions/RealmFileException"); + + jmethodID constructor = env->GetMethodID(cls, "", "(BLjava/lang/String;)V"); + jbyte kind_code; + switch (kind) { + case realm::RealmFileException::Kind::AccessError: + kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_ACCESS_ERROR; + break; + case realm::RealmFileException::Kind::PermissionDenied: + kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_PERMISSION_DENIED; + break; + case realm::RealmFileException::Kind::Exists: + kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_EXISTS; + break; + case realm::RealmFileException::Kind::NotFound: + kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_NOT_FOUND; + break; + case realm::RealmFileException::Kind::IncompatibleLockFile: + kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_IMCOMPATIBLE_LOCK_FILE; + break; + case realm::RealmFileException::Kind::FormatUpgradeRequired: + kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED; + break; + } + jstring jstr = env->NewStringUTF(message.c_str()); + jobject exception = env->NewObject(cls, constructor, kind_code, jstr); + env->Throw(reinterpret_cast(exception)); + env->DeleteLocalRef(cls); + env->DeleteLocalRef(exception); +} + jclass GetClass(JNIEnv* env, const char* classStr) { jclass localRefClass = env->FindClass(classStr); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 346595751b..255cf6372b 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -87,6 +87,8 @@ std::string num_to_string(T pNumber) // Exception handling enum ExceptionKind { + // FIXME: This is not something should be exposed to java, ClassNotFound is something we should + // crash hard in native code and fix it. ClassNotFound = 0, IllegalArgument, IndexOutOfBounds, @@ -96,6 +98,7 @@ enum ExceptionKind { RuntimeError, BadVersion, IllegalState, + RealmFileError, // NOTE!!!!: Please also add test cases to io_realm_internal_TestUtil when introducing a // new exception kind. ExceptionKindMax // Always keep this as the last one! diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 26b9723fdf..08a8e0eb6d 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.HandlerControllerConstants; import io.realm.internal.InvalidRow; @@ -228,6 +229,8 @@ protected void removeHandler() { * the last transaction was committed. * * @param destination file to save the Realm to. + * @throws RealmFileException if an error happened when accessing the underlying Realm file or writing to the + * destination file. */ public void writeCopyTo(File destination) { writeEncryptedCopyTo(destination, null); @@ -245,6 +248,8 @@ public void writeCopyTo(File destination) { * @param destination file to save the Realm to. * @param key a 64-byte encryption key. * @throws IllegalArgumentException if destination argument is null. + * @throws RealmFileException if an error happened when accessing the underlying Realm file or writing to the + * destination file. */ public void writeEncryptedCopyTo(File destination, byte[] key) { if (destination == null) { diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index ec9d324e98..d7abb24362 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -19,7 +19,7 @@ import android.app.IntentService; import io.realm.exceptions.RealmException; -import io.realm.exceptions.RealmIOException; +import io.realm.exceptions.RealmFileException; import io.realm.internal.Table; import io.realm.internal.log.RealmLog; import rx.Observable; @@ -57,7 +57,7 @@ private DynamicRealm(RealmConfiguration configuration) { * * @return the DynamicRealm defined by the configuration. * @see RealmConfiguration for details on how to configure a Realm. - * @throws RealmIOException if an error happened when accessing the underlying Realm file. + * @throws RealmFileException if an error happened when accessing the underlying Realm file. * @throws IllegalArgumentException if {@code configuration} argument is {@code null}. */ public static DynamicRealm getInstance(RealmConfiguration configuration) { diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index c338db9db7..806995544e 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -19,7 +19,6 @@ import android.annotation.TargetApi; import android.app.IntentService; import android.os.Build; -import android.os.Looper; import android.util.JsonReader; import org.json.JSONArray; @@ -43,10 +42,8 @@ import java.util.Set; import java.util.concurrent.Future; -import io.realm.RealmObject; -import io.realm.RealmQuery; import io.realm.exceptions.RealmException; -import io.realm.exceptions.RealmIOException; +import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; @@ -151,7 +148,7 @@ public Observable asObservable() { * @throws java.lang.NullPointerException if no default configuration has been defined. * @throws RealmMigrationNeededException if no migration has been provided by the default configuration and the * RealmObject classes or version has has changed so a migration is required. - * @throws RealmIOException if an error happened when accessing the underlying Realm file. + * @throws RealmFileException if an error happened when accessing the underlying Realm file. */ public static Realm getDefaultInstance() { if (defaultConfiguration == null) { @@ -167,7 +164,7 @@ public static Realm getDefaultInstance() { * @return an instance of the Realm class * @throws RealmMigrationNeededException if no migration has been provided by the configuration and the RealmObject * classes or version has has changed so a migration is required. - * @throws RealmIOException if an error happened when accessing the underlying Realm file. + * @throws RealmFileException if an error happened when accessing the underlying Realm file. * @throws IllegalArgumentException if a null {@link RealmConfiguration} is provided. * @see RealmConfiguration for details on how to configure a Realm. */ @@ -221,7 +218,7 @@ static Realm createInstance(RealmConfiguration configuration, ColumnIndices colu migrateRealm(configuration); } catch (FileNotFoundException fileNotFoundException) { // Should never happen - throw new RealmIOException(fileNotFoundException); + throw new RealmFileException(RealmFileException.Kind.NOT_FOUND, fileNotFoundException); } } @@ -1389,7 +1386,9 @@ static String getCanonicalPath(File realmFile) { try { return realmFile.getCanonicalPath(); } catch (IOException e) { - throw new RealmIOException("Could not resolve the canonical path to the Realm file: " + realmFile.getAbsolutePath()); + throw new RealmFileException(RealmFileException.Kind.ACCESS_ERROR, + "Could not resolve the canonical path to the Realm file: " + realmFile.getAbsolutePath(), + e); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 824f7fb7ed..49e10e1922 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -24,7 +24,7 @@ import java.util.HashMap; import java.util.Map; -import io.realm.exceptions.RealmIOException; +import io.realm.exceptions.RealmFileException; import io.realm.internal.ColumnIndices; import io.realm.internal.log.RealmLog; @@ -275,7 +275,7 @@ static synchronized void invokeWithGlobalRefCount(RealmConfiguration configurati } /** - * Runs the callback function with synchronization on {@class RealmCache}. + * Runs the callback function with synchronization on {@link RealmCache}. * * @param callback the callback will be executed. */ @@ -288,9 +288,10 @@ static synchronized void invokeWithLock(Callback0 callback) { * Copy is performed only at the first time when there is no Realm database file. * * @param configuration configuration object for Realm instance. - * @throws IOException if copying the file fails. + * @throws RealmFileException if copying the file fails. */ private static void copyAssetFileIfNeeded(RealmConfiguration configuration) { + IOException exceptionWhenClose = null; if (configuration.hasAssetFile()) { File realmFile = new File(configuration.getRealmFolder(), configuration.getRealmFileName()); if (realmFile.exists()) { @@ -302,7 +303,8 @@ private static void copyAssetFileIfNeeded(RealmConfiguration configuration) { try { inputStream = configuration.getAssetFile(); if (inputStream == null) { - throw new RealmIOException("Invalid input stream to asset file."); + throw new RealmFileException(RealmFileException.Kind.ACCESS_ERROR, + "Invalid input stream to asset file."); } outputStream = new FileOutputStream(realmFile); @@ -312,23 +314,32 @@ private static void copyAssetFileIfNeeded(RealmConfiguration configuration) { outputStream.write(buf, 0, bytesRead); } } catch (IOException e) { - throw new RealmIOException("Could not resolve the path to the Realm asset file.", e); + throw new RealmFileException(RealmFileException.Kind.ACCESS_ERROR, + "Could not resolve the path to the Realm asset file.", e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { - // Ignore this exception because any significant errors should already have been handled + exceptionWhenClose = e; } } if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { - throw new RealmIOException("Invalid output stream to " + realmFile.getPath(), e); + // Ignore this one if there was an exception when close inputStream. + if (exceptionWhenClose == null) { + exceptionWhenClose = e; + } } } } + + // No other exception has been thrown, only the exception when close. So, throw it. + if (exceptionWhenClose != null) { + throw new RealmFileException(RealmFileException.Kind.ACCESS_ERROR, exceptionWhenClose); + } } } } diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/IncompatibleLockFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/IncompatibleLockFileException.java deleted file mode 100644 index d1135bef12..0000000000 --- a/realm/realm-library/src/main/java/io/realm/exceptions/IncompatibleLockFileException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.exceptions; - -import io.realm.internal.Keep; - -/** - * Triggered from the JNI level when there was something wrong with the lock file. - * This can happen if two different versions of Realm tries to access the same file concurrently. - */ -@Keep -public class IncompatibleLockFileException extends RealmIOException { - - public IncompatibleLockFileException(String detailMessage) { - super(detailMessage); - } - - public IncompatibleLockFileException(String detailMessage, Throwable exception) { - super(detailMessage, exception); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java new file mode 100644 index 0000000000..a12f94368a --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm.exceptions; + +import io.realm.internal.Keep; +import io.realm.internal.SharedRealm; + +/** + * Class for reporting problems when accessing the Realm related files. + */ +@Keep +public class RealmFileException extends RuntimeException { + /** + * The specific kind of this {@link RealmFileException}. + */ + public enum Kind { + /** + * Thrown for any I/O related exception scenarios when a Realm is opened. + */ + ACCESS_ERROR, + /** + * Thrown if the user does not have permission to open or create the specified file in the specified access + * mode when the Realm is opened. + */ + PERMISSION_DENIED, + /** + * Thrown if the destination file exists but it is not supposed to. + */ + EXISTS, + /** + * Thrown if the relevant file cannot be found. + */ + NOT_FOUND, + /** + * Thrown if the database file is currently open in another process which cannot share with the current process + * due to an architecture mismatch. + */ + INCOMPATIBLE_LOCK_FILE, + /** + * Thrown if the file needs to be upgraded to a new format, but upgrades have been explicitly disabled. + */ + FORMAT_UPGRADE_REQUIRED; + + // Created from byte values by JNI. + static Kind getKind(byte value) { + switch (value) { + case SharedRealm.FILE_EXCEPTION_KIND_ACCESS_ERROR: + return ACCESS_ERROR; + case SharedRealm.FILE_EXCEPTION_KIND_PERMISSION_DENIED: + return PERMISSION_DENIED; + case SharedRealm.FILE_EXCEPTION_KIND_EXISTS: + return EXISTS; + case SharedRealm.FILE_EXCEPTION_KIND_NOT_FOUND: + return NOT_FOUND; + case SharedRealm.FILE_EXCEPTION_KIND_IMCOMPATIBLE_LOCK_FILE: + return INCOMPATIBLE_LOCK_FILE; + case SharedRealm.FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED: + return FORMAT_UPGRADE_REQUIRED; + default: + throw new RuntimeException("Unknown value for RealmFileException kind."); + } + } + } + + private final Kind kind; + + // Called by JNI + @SuppressWarnings("unused") + public RealmFileException(byte value, String message) { + super(message); + kind = Kind.getKind(value); + } + + public RealmFileException(Kind kind, String message) { + super(message); + this.kind = kind; + } + + public RealmFileException(Kind kind, Throwable cause) { + super(cause); + this.kind = kind; + } + + public RealmFileException(Kind kind, String message, Throwable cause) { + super(message, cause); + this.kind = kind; + } + + /** + * Gets the {@link #kind} of this exception. + * + * @return the {@link #kind} of this exception. + */ + public Kind getKind() { + return kind; + } + + @Override + public String toString() { + return String.format("%s Kind: %s.", super.toString(), kind); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmIOException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmIOException.java deleted file mode 100644 index 3d59ed049e..0000000000 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmIOException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.exceptions; - -import io.realm.internal.Keep; - -/** - * Class for reporting problems with Realm files. - */ -@Keep -public class RealmIOException extends RuntimeException { - - public RealmIOException(Throwable cause) { - super(cause); - } - - public RealmIOException() { - } - - public RealmIOException(String message) { - super(message); - } - - public RealmIOException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 34f674e44b..4f0c9755b7 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -24,6 +24,14 @@ public final class SharedRealm implements Closeable { + // Const value for RealmFileException conversion + public static final byte FILE_EXCEPTION_KIND_ACCESS_ERROR = 0; + public static final byte FILE_EXCEPTION_KIND_PERMISSION_DENIED = 1; + public static final byte FILE_EXCEPTION_KIND_EXISTS = 2; + public static final byte FILE_EXCEPTION_KIND_NOT_FOUND = 3; + public static final byte FILE_EXCEPTION_KIND_IMCOMPATIBLE_LOCK_FILE = 4; + public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 5; + public enum Durability { FULL(0), MEM_ONLY(1); From 0ed5fed22d3f8f5b8f5487cba76f6ba33e274481 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 26 Aug 2016 23:13:31 +0900 Subject: [PATCH 0011/2110] revive the version check of ndk (#3354) * revive the version check of ndk in order not to waste our time by using wrong version of NDK. * minor fix * minor fix * move 'checkNdk(ndkPathInLocalProperties)' to ensure the existance of the directory when obtainig canonical path --- realm/realm-library/build.gradle | 66 ++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 2e122d003c..e2f3cb2634 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -430,3 +430,69 @@ if (project.hasProperty('dontCleanJniFiles')) { } clean.dependsOn cleanExternalBuildFiles } + +project.afterEvaluate { + android.libraryVariants.all { variant -> + variant.externalNativeBuildTasks[0].dependsOn(checkNdk) + } +} + +task checkNdk() << { + def ndkPathInEnvVariable = System.env.ANDROID_NDK_HOME + if (!ndkPathInEnvVariable) { + throw new GradleException("The environment variable 'ANDROID_NDK_HOME' must be set.") + } + checkNdk(ndkPathInEnvVariable) + + def localPropFile = rootProject.file('local.properties') + if (!localPropFile.exists()) { + // we can skip the checks since 'ANDROID_NDK_HOME' will be used instead. + } else { + def String ndkPathInLocalProperties = getValueFromPropertiesFile(localPropFile, 'ndk.dir') + if (!ndkPathInLocalProperties) { + throw new GradleException("'ndk.dir' must be set in ${localPropFile.getAbsolutePath()}.") + } + checkNdk(ndkPathInLocalProperties) + if (new File(ndkPathInLocalProperties).getCanonicalPath() + != new File(ndkPathInEnvVariable).getCanonicalPath()) { + throw new GradleException( + "The value of environment variable 'ANDROID_NDK_HOME' (${ndkPathInEnvVariable}) and" + + " 'ndk.dir' in 'local.properties' (${ndkPathInLocalProperties}) " + + ' must point the same directory.') + } + } +} + +def checkNdk(String ndkPath) { + def detectedNdkVersion + def releaseFile = new File(ndkPath, 'RELEASE.TXT') + def propertyFile = new File(ndkPath, 'source.properties') + if (releaseFile.isFile()) { + detectedNdkVersion = releaseFile.text.trim().split()[0].split('-')[0] + } else if (propertyFile.isFile()) { + detectedNdkVersion = getValueFromPropertiesFile(propertyFile, 'Pkg.Revision') + if (detectedNdkVersion == null) { + throw new GradleException("Failed to obtain the NDK version information from ${ndkPath}/source.properties") + } + } else { + throw new GradleException("Neither ${releaseFile.getAbsolutePath()} nor ${propertyFile.getAbsolutePath()} is a file.") + } + if (detectedNdkVersion != project.ndkVersion) { + throw new GradleException("Your NDK version: ${detectedNdkVersion}." + +" Realm JNI must be compiled with the version ${project.ndkVersion} of NDK.") + } +} + +def getValueFromPropertiesFile(File propFile, String key) { + if (!propFile.isFile() || !propFile.canRead()) { + return null + } + def prop = new Properties() + def reader = propFile.newReader() + try { + prop.load(reader) + } finally { + reader.close() + } + return prop.get(key) +} From 12b3afd343f293f8c008e8dca62f0e892b50a915 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 29 Aug 2016 11:49:44 +0800 Subject: [PATCH 0012/2110] revive the version check of ndk (#3354) (#3360) * revive the version check of ndk in order not to waste our time by using wrong version of NDK. * minor fix * minor fix * move 'checkNdk(ndkPathInLocalProperties)' to ensure the existance of the directory when obtainig canonical path --- realm/realm-library/build.gradle | 66 ++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 502601c1d3..87d8e41f0d 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -429,3 +429,69 @@ if (project.hasProperty('dontCleanJniFiles')) { } clean.dependsOn cleanExternalBuildFiles } + +project.afterEvaluate { + android.libraryVariants.all { variant -> + variant.externalNativeBuildTasks[0].dependsOn(checkNdk) + } +} + +task checkNdk() << { + def ndkPathInEnvVariable = System.env.ANDROID_NDK_HOME + if (!ndkPathInEnvVariable) { + throw new GradleException("The environment variable 'ANDROID_NDK_HOME' must be set.") + } + checkNdk(ndkPathInEnvVariable) + + def localPropFile = rootProject.file('local.properties') + if (!localPropFile.exists()) { + // we can skip the checks since 'ANDROID_NDK_HOME' will be used instead. + } else { + def String ndkPathInLocalProperties = getValueFromPropertiesFile(localPropFile, 'ndk.dir') + if (!ndkPathInLocalProperties) { + throw new GradleException("'ndk.dir' must be set in ${localPropFile.getAbsolutePath()}.") + } + checkNdk(ndkPathInLocalProperties) + if (new File(ndkPathInLocalProperties).getCanonicalPath() + != new File(ndkPathInEnvVariable).getCanonicalPath()) { + throw new GradleException( + "The value of environment variable 'ANDROID_NDK_HOME' (${ndkPathInEnvVariable}) and" + + " 'ndk.dir' in 'local.properties' (${ndkPathInLocalProperties}) " + + ' must point the same directory.') + } + } +} + +def checkNdk(String ndkPath) { + def detectedNdkVersion + def releaseFile = new File(ndkPath, 'RELEASE.TXT') + def propertyFile = new File(ndkPath, 'source.properties') + if (releaseFile.isFile()) { + detectedNdkVersion = releaseFile.text.trim().split()[0].split('-')[0] + } else if (propertyFile.isFile()) { + detectedNdkVersion = getValueFromPropertiesFile(propertyFile, 'Pkg.Revision') + if (detectedNdkVersion == null) { + throw new GradleException("Failed to obtain the NDK version information from ${ndkPath}/source.properties") + } + } else { + throw new GradleException("Neither ${releaseFile.getAbsolutePath()} nor ${propertyFile.getAbsolutePath()} is a file.") + } + if (detectedNdkVersion != project.ndkVersion) { + throw new GradleException("Your NDK version: ${detectedNdkVersion}." + +" Realm JNI must be compiled with the version ${project.ndkVersion} of NDK.") + } +} + +def getValueFromPropertiesFile(File propFile, String key) { + if (!propFile.isFile() || !propFile.canRead()) { + return null + } + def prop = new Properties() + def reader = propFile.newReader() + try { + prop.load(reader) + } finally { + reader.close() + } + return prop.get(key) +} From 8d2e3da163bbab092ca6c2980a7367c84dbad5f2 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 30 Aug 2016 13:28:55 +0900 Subject: [PATCH 0013/2110] Fix a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11. (#3364) fixes #3356 --- CHANGELOG.md | 4 ++++ .../java/io/realm/processor/RealmProxyClassGenerator.java | 3 +++ .../src/test/resources/io/realm/AllTypesRealmProxy.java | 3 +++ .../src/test/resources/io/realm/BooleansRealmProxy.java | 3 +++ .../src/test/resources/io/realm/NullTypesRealmProxy.java | 3 +++ .../src/test/resources/io/realm/SimpleRealmProxy.java | 3 +++ 6 files changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0adb808f2..a0925e3aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ * Move JNI build to CMake. +### Bug fixes + +* Fixed a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11 (#3356). + ## 1.2.0 ### Bug fixes diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index cc4f726bf0..ed6359f858 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -62,6 +62,8 @@ public void generate() throws IOException, UnsupportedOperationException { .emitEmptyLine(); ArrayList imports = new ArrayList(); + imports.add("android.annotation.TargetApi"); + imports.add("android.os.Build"); imports.add("android.util.JsonReader"); imports.add("android.util.JsonToken"); imports.add("io.realm.RealmFieldType"); @@ -1553,6 +1555,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer.emitAnnotation("SuppressWarnings", "\"cast\""); + writer.emitAnnotation("TargetApi", "Build.VERSION_CODES.HONEYCOMB"); writer.beginMethod( qualifiedClassName, "createUsingJsonStream", diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 1412974f42..0eb564cd6c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -1,6 +1,8 @@ package io.realm; +import android.annotation.TargetApi; +import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; import io.realm.RealmFieldType; @@ -490,6 +492,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON } @SuppressWarnings("cast") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { some.test.AllTypes obj = realm.createObject(some.test.AllTypes.class); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index de1dc8239f..4a9fc6a693 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -1,6 +1,8 @@ package io.realm; +import android.annotation.TargetApi; +import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; import io.realm.RealmFieldType; @@ -225,6 +227,7 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON } @SuppressWarnings("cast") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { some.test.Booleans obj = realm.createObject(some.test.Booleans.class); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 4d62f6ef04..04286fee6e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -1,6 +1,8 @@ package io.realm; +import android.annotation.TargetApi; +import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; import io.realm.RealmFieldType; @@ -910,6 +912,7 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO } @SuppressWarnings("cast") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { some.test.NullTypes obj = realm.createObject(some.test.NullTypes.class); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 942bbc2d2d..00d1ea7bb9 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -1,6 +1,8 @@ package io.realm; +import android.annotation.TargetApi; +import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; import io.realm.RealmFieldType; @@ -163,6 +165,7 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb } @SuppressWarnings("cast") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { some.test.Simple obj = realm.createObject(some.test.Simple.class); From 1983d7dca31fa23d3623bad44beb5ad3dea084c8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 30 Aug 2016 12:48:07 +0800 Subject: [PATCH 0014/2110] Build with sync core --- realm/realm-library/build.gradle | 20 ++++++++----- .../realm-library/src/main/cpp/CMakeLists.txt | 29 ++++++++++++++++--- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 78f3dc3ace..eb3d8a7394 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '1.5.1' +ext.coreVersion = '0.28.0' // empty or comment out this to disable hash checking -ext.coreSha256Hash = 'a034d3250c820a15126721142d168a2ac4a12223b75bb324958ca2a70442720d' +ext.coreSha256Hash = 'e4d8ed7342824a1574449700b16cd36f663f4d6768fc09c1aca986f19e27162b' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from @@ -326,13 +326,15 @@ artifacts { def coreDownloaded = false -task downloadCore(group: 'build setup', description: 'Download the latest version of Realm Core') { +task downloadCore() { + group = 'build setup' + description = 'Download the latest version of Realm Core' def isHashCheckingEnabled = { return project.hasProperty('coreSha256Hash') && !project.coreSha256Hash.empty } def calcSha256Hash = {File targetFile -> - MessageDigest sha = MessageDigest.getInstance("SHA-256"); + MessageDigest sha = MessageDigest.getInstance("SHA-256") Formatter hexHash = new Formatter() sha.digest(targetFile.bytes).each { b -> hexHash.format('%02x', b) } return hexHash.toString() @@ -362,10 +364,12 @@ task downloadCore(group: 'build setup', description: 'Download the latest versio doLast { if (shouldDownloadCore()) { - download { - src "http://static.realm.io/downloads/core/realm-core-android-${project.coreVersion}.tar.gz" - dest project.coreArchiveFile - onlyIfNewer false + // CI artifacts are only available if on the internal network or VPN + def downloadUrl = "s3://realm-ci-artifacts/sync/${project.coreVersion}/android/sync-core-${project.coreVersion}.tar.gz" + + println "Downloading ${downloadUrl}" + exec { + commandLine 's3cmd', '-c', project.s3cfg, '-f', 'get', "${downloadUrl}", "${project.coreArchiveFile}" } coreDownloaded = true diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index b4f9210420..f6be360036 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -27,6 +27,7 @@ create_javah(TARGET jni_headers CLASSES io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil + io.realm.sync.SyncManager CLASSPATH ${classes_PATH} OUTPUT_DIR ${CMAKE_SOURCE_DIR}/jni_include @@ -58,6 +59,23 @@ endif() add_library(lib_realm_core STATIC IMPORTED) set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_PATH}) +# Sync static library +set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-${ANDROID_ABI}.a) +# Workaround for old core's funny ABI nicknames +if (NOT EXISTS ${sync_lib_PATH}) + if (ARMEABI) + set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm.a) + elseif (ARMEABI_V7A) + set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm-v7a.a) + elseif (ARM64_V8A) + set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm64.a) + else() + message(FATAL_ERROR "Cannot find core lib file: ${core_lib_PATH}") + endif() +endif() +add_library(lib_realm_sync STATIC IMPORTED) +set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_PATH}) + # build application's shared lib include_directories(${REALM_CORE_DIST_DIR}/include ${CMAKE_SOURCE_DIR} @@ -65,6 +83,8 @@ include_directories(${REALM_CORE_DIST_DIR}/include ${CMAKE_SOURCE_DIR}/object-store/src) set(ANDROID_STL "gnustl_static") +set(ANDROID_NO_UNDEFINED OFF) +set(ANDROID_SO_UNDEFINED ON) if (ARMEABI) set(ABI_CXX_FLAGS "-mthumb") @@ -76,16 +96,16 @@ endif() # d.init(ValueBase::m_from_link_list, ValueBase::m_values, D{}); #FIXME maybe-uninitialized is reported by table_view.cpp:272:15: # 'best.m_nanoseconds' was declared here -set(WARNING_CXX_FLAGS "-Werror -Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros \ +set(WARNING_CXX_FLAGS "-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros \ -Wno-missing-field-initializers -Wmissing-declarations -Wno-error=uninitialized -Wno-error=maybe-uninitialized") -set(REALM_COMMON_CXX_FLAGS "-DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14") +set(REALM_COMMON_CXX_FLAGS "-DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14 -fsigned-char") set(CMAKE_CXX_FLAGS_RELEASE "-Os -DNDEBUG -flto") #-ggdb doesn't play well with -flto set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -Os -DNDEBUG") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${REALM_COMMON_CXX_FLAGS} ${WARNING_CXX_FLAGS} ${ABI_CXX_FLAGS}") # Set link flags -set(REALM_LINKER_FLAGS "") +set(REALM_LINKER_FLAGS "-lz") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${REALM_LINKER_FLAGS}") file(GLOB jni_SRC @@ -100,7 +120,8 @@ file(GLOB objectstore_SRC add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC}) add_dependencies(realm-jni jni_headers) # -latomic is not set by default for mips. See https://code.google.com/p/android/issues/detail?id=182094 -target_link_libraries(realm-jni log android atomic lib_realm_core) +# FIXME: The order matters! lib_realm_sync needs to be in front of lib_realm_core!! Find out why!! +target_link_libraries(realm-jni log android atomic lib_realm_sync lib_realm_core) # Strip the release so files and backup the unstripped versions if (CMAKE_BUILD_TYPE STREQUAL "Release") From 9a258999556767eb184a9391da92eac5ffa3c2cd Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 30 Aug 2016 10:35:12 +0200 Subject: [PATCH 0015/2110] Remove deprecated constructor + add directory() (#3357) This commit simplifies the RealmConfiguration constructors and also ensures that we always have an Android context. It does so by now only having the`RealmConfiguration.Builder(context)` constructor. Custom file locations are now supported through the `directory()` builder method. This also made it possible to simply `assetFile(Context, location)` to only `assetFile(location)`. Having the Context means that we are now able to access system services and other framework classes without exposing any Android functionality in any potential interface (which will be needed to support Realm on the JVM). --- CHANGELOG.md | 5 +- .../java/io/realm/RealmCacheTests.java | 4 +- .../io/realm/RealmConfigurationTests.java | 229 +++++++++++------- .../java/io/realm/RealmObjectTests.java | 2 +- .../androidTest/java/io/realm/RealmTests.java | 35 +-- .../androidTest/java/io/realm/TestHelper.java | 5 +- .../rule/TestRealmConfigurationFactory.java | 20 +- .../benchmarks/config/BenchmarkConfig.java | 4 +- .../src/main/java/io/realm/BaseRealm.java | 8 +- .../src/main/java/io/realm/RealmCache.java | 2 +- .../java/io/realm/RealmConfiguration.java | 102 ++++---- 11 files changed, 219 insertions(+), 197 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4a88c363e..c74a0969ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,13 @@ * Added new `RealmFileException`. - `IncompatibleLockFileException` has been removed and replaced by `RealmFileException` with kind `INCOMPATIBLE_LOCK_FILE`. - `RealmIOExcpetion` has been removed and replaced by `RealmFileException`. +* Removed `RealmConfiguration.Builder(Context, File)` and `RealmConfiguration.Builder(File)` constructors. +* `RealmConfiguration.Builder.assetFile(Context, String)` has been renamed to `RealmConfiguration.Builder.assetFile(String)`. ### Enhancements * Added `realmObject.isManaged()`, `RealmObject.isManaged(obj)` and `RealmCollection.isManaged()` (#3101). - -## 1.2.1 +* Added `RealmConfiguration.Builder.directory(File)`. ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index dcec37ea70..c8213e7d9c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -182,7 +182,7 @@ public void deletingRealmAlsoClearsConfigurationCache() throws IOException { // 1. Write a copy of the encrypted Realm to a new file Realm testRealm = Realm.getInstance(config); - File copiedRealm = new File(config.getRealmFolder(), "encrypted-copy.realm"); + File copiedRealm = new File(config.getRealmDirectory(), "encrypted-copy.realm"); if (copiedRealm.exists()) { assertTrue(copiedRealm.delete()); } @@ -193,7 +193,7 @@ public void deletingRealmAlsoClearsConfigurationCache() throws IOException { Realm.deleteRealm(config); // 3. Rename the new file to the old file name. - assertTrue(copiedRealm.renameTo(new File(config.getRealmFolder(), REALM_NAME))); + assertTrue(copiedRealm.renameTo(new File(config.getRealmDirectory(), REALM_NAME))); // 4. Try to open the file again with the new password // If the configuration cache wasn't cleared this would fail as we would detect two diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 1accf93f07..3237aab39d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -20,6 +20,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import android.content.Context; @@ -70,11 +71,16 @@ public class RealmConfigurationTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - RealmConfiguration defaultConfig; - Realm realm; + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Context context; + private RealmConfiguration defaultConfig; + private Realm realm; @Before public void setUp() { + context = InstrumentationRegistry.getTargetContext(); defaultConfig = configFactory.createConfiguration(); } @@ -121,40 +127,42 @@ public void getInstance_nullConfigThrows() { } @Test - public void constructBuilder_nullDirThrows() { + public void constructBuilder_nullNameThrows() { try { - new RealmConfiguration.Builder((File) null).build(); + new RealmConfiguration.Builder(context).name(null); fail(); } catch (IllegalArgumentException ignored) { } } @Test - public void constructBuilder_createSubFoldersThrows() { - File folder = new File(configFactory.getRoot() + "/subfolder1/subfolder2/"); + public void constructBuilder_emptyNameThrows() { try { - new RealmConfiguration.Builder(folder).build(); - fail("Assuming that sub folders are created automatically should fail."); + new RealmConfiguration.Builder(context).name(""); + fail(); } catch (IllegalArgumentException ignored) { } } + @Test(expected = IllegalArgumentException.class) + public void directory_null() { + new RealmConfiguration.Builder(context).directory(null); + } + @Test - public void constructBuilder_nullNameThrows() { - try { - new RealmConfiguration.Builder(configFactory.getRoot()).name(null).build(); - fail(); - } catch (IllegalArgumentException ignored) { - } + public void directory_writeProtectedDir() { + File dir = new File("/"); + thrown.expect(IllegalArgumentException.class); + new RealmConfiguration.Builder(context).directory(dir); } @Test - public void constructBuilder_emptyNameThrows() { - try { - new RealmConfiguration.Builder(configFactory.getRoot()).name("").build(); - fail(); - } catch (IllegalArgumentException ignored) { - } + public void directory_dirIsAFile() throws IOException { + File dir = configFactory.getRoot(); + File file = new File(dir, "dummyfile"); + assertTrue(file.createNewFile()); + thrown.expect(IllegalArgumentException.class); + new RealmConfiguration.Builder(context).directory(file); } @Test @@ -173,7 +181,7 @@ public void getInstance_idForHashCollision() { @Test public void constructBuilder_nullKeyThrows() { try { - new RealmConfiguration.Builder(configFactory.getRoot()).encryptionKey(null).build(); + new RealmConfiguration.Builder(context).encryptionKey(null); fail(); } catch (IllegalArgumentException ignored) { } @@ -188,7 +196,7 @@ public void constructBuilder_wrongKeyLengthThrows() { }; for (byte[] key : wrongKeys) { try { - new RealmConfiguration.Builder(configFactory.getRoot()).encryptionKey(key).build(); + new RealmConfiguration.Builder(context).encryptionKey(key); fail("Key with length " + key.length + " should throw an exception"); } catch (IllegalArgumentException ignored) { } @@ -198,7 +206,7 @@ public void constructBuilder_wrongKeyLengthThrows() { @Test public void constructBuilder_negativeVersionThrows() { try { - new RealmConfiguration.Builder(configFactory.getRoot()).schemaVersion(-1).build(); + new RealmConfiguration.Builder(context).schemaVersion(-1); fail(); } catch (IllegalArgumentException ignored) { } @@ -206,14 +214,19 @@ public void constructBuilder_negativeVersionThrows() { @Test public void constructBuilder_versionLessThanDiscVersionThrows() { - realm = Realm.getInstance(new RealmConfiguration.Builder(configFactory.getRoot()).schemaVersion(42).build()); + realm = Realm.getInstance(new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .schemaVersion(42) + .build()); realm.close(); int[] wrongVersions = new int[] { 0, 1, 41 }; for (int version : wrongVersions) { try { - realm = Realm.getInstance(new RealmConfiguration.Builder(configFactory.getRoot()) - .schemaVersion(version).build()); + realm = Realm.getInstance(new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .schemaVersion(version) + .build()); fail("Version " + version + " should throw an exception"); } catch (IllegalArgumentException ignored) { } @@ -223,14 +236,20 @@ public void constructBuilder_versionLessThanDiscVersionThrows() { @Test public void constructBuilder_versionEqualWhenSchemaChangesThrows() { // Create initial Realm - RealmConfiguration config = new RealmConfiguration.Builder(configFactory.getRoot()) - .schemaVersion(42).schema(Dog.class).build(); + RealmConfiguration config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .schemaVersion(42) + .schema(Dog.class) + .build(); Realm.getInstance(config).close(); // Create new instance with a configuration containing another schema try { - config = new RealmConfiguration.Builder(configFactory.getRoot()) - .schemaVersion(42).schema(AllTypesPrimaryKey.class).build(); + config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .schemaVersion(42) + .schema(AllTypesPrimaryKey.class) + .build(); realm = Realm.getInstance(config); fail("A migration should be required"); } catch (RealmMigrationNeededException ignored) { @@ -239,7 +258,10 @@ public void constructBuilder_versionEqualWhenSchemaChangesThrows() { @Test public void customSchemaDontIncludeLinkedClasses() { - RealmConfiguration config = new RealmConfiguration.Builder(configFactory.getRoot()).schema(Dog.class).build(); + RealmConfiguration config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .schema(Dog.class) + .build(); realm = Realm.getInstance(config); try { assertEquals(3, realm.getTable(Owner.class).getColumnCount()); @@ -251,7 +273,7 @@ public void customSchemaDontIncludeLinkedClasses() { @Test public void migration_nullThrows() { try { - new RealmConfiguration.Builder(configFactory.getRoot()).migration(null).build(); + new RealmConfiguration.Builder(context).migration(null).build(); fail(); } catch (IllegalArgumentException ignored) { } @@ -261,14 +283,14 @@ public void migration_nullThrows() { public void modules_nonRealmModulesThrows() { // Test first argument try { - new RealmConfiguration.Builder(configFactory.getRoot()).modules(new Object()); + new RealmConfiguration.Builder(context).modules(new Object()); fail(); } catch (IllegalArgumentException ignored) { } // Test second argument try { - new RealmConfiguration.Builder(configFactory.getRoot()).modules(Realm.getDefaultModule(), new Object()); + new RealmConfiguration.Builder(context).modules(Realm.getDefaultModule(), new Object()); fail(); } catch (IllegalArgumentException ignored) { } @@ -276,8 +298,10 @@ public void modules_nonRealmModulesThrows() { @Test public void modules() { - RealmConfiguration realmConfig = new RealmConfiguration.Builder(configFactory.getRoot()) - .modules(Realm.getDefaultModule(), (Object) null).build(); + RealmConfiguration realmConfig = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .modules(Realm.getDefaultModule(), (Object) null) + .build(); realm = Realm.getInstance(realmConfig); assertNotNull(realm.getTable(AllTypes.class)); } @@ -298,7 +322,8 @@ public void getInstance() { @Test public void standardSetup() { - RealmConfiguration config = new RealmConfiguration.Builder(configFactory.getRoot()) + RealmConfiguration config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .name("foo.realm") .encryptionKey(TestHelper.getRandomKey()) .schemaVersion(42) @@ -320,7 +345,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { @Test public void deleteRealmIfMigrationNeeded() { // Populate v0 of a Realm with an object - RealmConfiguration config = new RealmConfiguration.Builder(configFactory.getRoot()) + RealmConfiguration config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .schema(Dog.class) .schemaVersion(0) .build(); @@ -333,7 +359,8 @@ public void deleteRealmIfMigrationNeeded() { realm.close(); // Change schema and verify that Realm has been cleared - config = new RealmConfiguration.Builder(configFactory.getRoot()) + config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .schema(Owner.class, Dog.class) .schemaVersion(1) .deleteRealmIfMigrationNeeded() @@ -350,8 +377,8 @@ public void deleteRealmIfMigrationNeeded_failsWhenAssetFileProvided() { RealmConfiguration.Builder builder = new RealmConfiguration.Builder(context); try { builder - .assetFile(context, "asset_file.realm") - .deleteRealmIfMigrationNeeded(); + .assetFile("asset_file.realm") + .deleteRealmIfMigrationNeeded(); fail(); } catch (IllegalStateException expected) { assertEquals("Realm cannot clear its schema when previously configured to use an asset file by calling assetFile().", @@ -367,8 +394,10 @@ public void upgradeVersionWithNoMigration() { // Version upgrades should always require a migration. try { - realm = Realm.getInstance(new RealmConfiguration.Builder(configFactory.getRoot()) - .schemaVersion(42).build()); + realm = Realm.getInstance(new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .schemaVersion(42) + .build()); fail(); } catch (RealmMigrationNeededException ignored) { } @@ -376,35 +405,37 @@ public void upgradeVersionWithNoMigration() { @Test public void equals() { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()).build(); + RealmConfiguration config1 = new RealmConfiguration.Builder(context).build(); + RealmConfiguration config2 = new RealmConfiguration.Builder(context).build(); assertTrue(config1.equals(config2)); } @Test public void equalsWhenRxJavaUnavailable() { // test for https://github.com/realm/realm-java/issues/2416 - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()).build(); + RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); TestHelper.emulateRxJavaUnavailable(config1); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()).build(); + RealmConfiguration config2 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); TestHelper.emulateRxJavaUnavailable(config2); assertTrue(config1.equals(config2)); } @Test public void hashCode_Test() { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()).build(); + RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); + RealmConfiguration config2 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); assertEquals(config1.hashCode(), config2.hashCode()); } @Test public void equals_withCustomModules() { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()) + RealmConfiguration config1 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .modules(new HumanModule(), new AnimalModule()) .build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()) + RealmConfiguration config2 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .modules(new AnimalModule(), new HumanModule()) .build(); @@ -413,10 +444,12 @@ public void equals_withCustomModules() { @Test public void hashCode_withCustomModules() { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()) + RealmConfiguration config1 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .modules(new HumanModule(), new AnimalModule()) .build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()) + RealmConfiguration config2 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .modules(new AnimalModule(), new HumanModule()) .build(); @@ -425,10 +458,12 @@ public void hashCode_withCustomModules() { @Test public void hashCode_withDifferentRxObservableFactory() { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()) + RealmConfiguration config1 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .rxFactory(new RealmObservableFactory()) .build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()) + RealmConfiguration config2 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .rxFactory(new RealmObservableFactory() { @Override public int hashCode() { @@ -442,8 +477,8 @@ public int hashCode() { @Test public void equals_configurationsReturnCachedRealm() { - Realm realm1 = Realm.getInstance(new RealmConfiguration.Builder(configFactory.getRoot()).build()); - Realm realm2 = Realm.getInstance(new RealmConfiguration.Builder(configFactory.getRoot()).build()); + Realm realm1 = Realm.getInstance(new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build()); + Realm realm2 = Realm.getInstance(new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build()); try { assertEquals(realm1, realm2); } finally { @@ -454,8 +489,8 @@ public void equals_configurationsReturnCachedRealm() { @Test public void schemaVersion_differentVersionsThrows() { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()).schemaVersion(1).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()).schemaVersion(2).build(); + RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).schemaVersion(1).build(); + RealmConfiguration config2 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).schemaVersion(2).build(); Realm realm1 = Realm.getInstance(config1); try { @@ -469,10 +504,14 @@ public void schemaVersion_differentVersionsThrows() { @Test public void encryptionKey_differentEncryptionKeysThrows() { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()) - .encryptionKey(TestHelper.getRandomKey()).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()) - .encryptionKey(TestHelper.getRandomKey()).build(); + RealmConfiguration config1 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .encryptionKey(TestHelper.getRandomKey()) + .build(); + RealmConfiguration config2 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .encryptionKey(TestHelper.getRandomKey()) + .build(); Realm realm1 = Realm.getInstance(config1); try { @@ -486,9 +525,12 @@ public void encryptionKey_differentEncryptionKeysThrows() { @Test public void schema_differentSchemasThrows() { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()) - .schema(AllTypes.class).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()) + RealmConfiguration config1 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .schema(AllTypes.class) + .build(); + RealmConfiguration config2 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) .schema(CyclicType.class).build(); Realm realm1 = Realm.getInstance(config1); @@ -504,8 +546,13 @@ public void schema_differentSchemasThrows() { // Creating Realm instances with same name but different durabilities is not allowed. @Test public void inMemory_differentDurabilityThrows() { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()).inMemory().build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.getRoot()).build(); + RealmConfiguration config1 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .inMemory() + .build(); + RealmConfiguration config2 = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .build(); // Create In-memory Realm first. Realm realm1 = Realm.getInstance(config1); @@ -533,8 +580,8 @@ public void inMemory_differentDurabilityThrows() { // It is allowed to create multiple Realm with same name but in different directory @Test public void constructBuilder_differentDirSameName() throws IOException { - RealmConfiguration config1 = new RealmConfiguration.Builder(configFactory.getRoot()).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(configFactory.newFolder()).build(); + RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); + RealmConfiguration config2 = new RealmConfiguration.Builder(context).directory(configFactory.newFolder()).build(); Realm realm1 = Realm.getInstance(config1); Realm realm2 = Realm.getInstance(config2); @@ -547,7 +594,10 @@ public void encryptionKey_keyStorage() throws Exception { // Generate a key and use it in a RealmConfiguration byte[] oldKey = TestHelper.getRandomKey(12345); byte[] key = oldKey; - RealmConfiguration config = new RealmConfiguration.Builder(configFactory.getRoot()).encryptionKey(key).build(); + RealmConfiguration config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .encryptionKey(key) + .build(); // Generate a different key and assign it to the same variable byte[] newKey = TestHelper.getRandomKey(67890); @@ -577,8 +627,10 @@ public void modelClassesForDefaultMediator() throws Exception { @Test public void modelClasses_forGeneratedMediator() throws Exception { - final RealmConfiguration config = new RealmConfiguration.Builder(configFactory.getRoot()) - .modules(new HumanModule()).build(); + final RealmConfiguration config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .modules(new HumanModule()) + .build(); assertTrue(config.getSchemaMediator() instanceof HumanModuleMediator); final Set> realmClasses = config.getRealmObjectClasses(); @@ -597,8 +649,10 @@ public void modelClasses_forGeneratedMediator() throws Exception { @Test public void modelClasses_forCompositeMediator() throws Exception { - final RealmConfiguration config = new RealmConfiguration.Builder(configFactory.getRoot()) - .modules(new HumanModule(), new AnimalModule()).build(); + final RealmConfiguration config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .modules(new HumanModule(), new AnimalModule()) + .build(); assertTrue(config.getSchemaMediator() instanceof CompositeMediator); final Set> realmClasses = config.getRealmObjectClasses(); @@ -618,8 +672,10 @@ public void modelClasses_forCompositeMediator() throws Exception { @Test public void modelClasses_forFilterableMediator() throws Exception { //noinspection unchecked - final RealmConfiguration config = new RealmConfiguration.Builder(configFactory.getRoot()) - .schema(AllTypes.class, CatOwner.class).build(); + final RealmConfiguration config = new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .schema(AllTypes.class, CatOwner.class) + .build(); assertTrue(config.getSchemaMediator() instanceof FilterableMediator); final Set> realmClasses = config.getRealmObjectClasses(); @@ -800,15 +856,14 @@ public void initialDataTransactionAssetFile() throws IOException { @Test public void assetFileNullAndEmptyFileName() { - Context context = InstrumentationRegistry.getInstrumentation().getContext(); try { - new RealmConfiguration.Builder(context).assetFile(context, null).build(); + new RealmConfiguration.Builder(context).assetFile(null).build(); fail(); } catch (IllegalArgumentException ignored) { } try { - new RealmConfiguration.Builder(context).assetFile(context, "").build(); + new RealmConfiguration.Builder(context).assetFile("").build(); fail(); } catch (IllegalArgumentException ignored) { } @@ -816,13 +871,11 @@ public void assetFileNullAndEmptyFileName() { @Test public void assetFileWithInMemoryConfig() { - Context context = InstrumentationRegistry.getInstrumentation().getContext(); - // Ensure that there is no data Realm.deleteRealm(new RealmConfiguration.Builder(context).build()); try { - new RealmConfiguration.Builder(context).assetFile(context, "asset_file.realm").inMemory().build(); + new RealmConfiguration.Builder(context).assetFile("asset_file.realm").inMemory().build(); fail(); } catch (RealmException ignored) { } @@ -830,12 +883,10 @@ public void assetFileWithInMemoryConfig() { @Test public void assetFileFakeFile() { - Context context = InstrumentationRegistry.getInstrumentation().getContext(); - // Ensure that there is no data Realm.deleteRealm(new RealmConfiguration.Builder(context).build()); - RealmConfiguration configuration = new RealmConfiguration.Builder(context).assetFile(context, "no_file").build(); + RealmConfiguration configuration = new RealmConfiguration.Builder(context).assetFile("no_file").build(); try { Realm.getInstance(configuration); fail(); @@ -846,15 +897,13 @@ public void assetFileFakeFile() { @Test public void assetFileValidFile() throws IOException { - Context context = InstrumentationRegistry.getInstrumentation().getContext(); - // Ensure that there is no data Realm.deleteRealm(new RealmConfiguration.Builder(context).build()); RealmConfiguration configuration = new RealmConfiguration .Builder(context) .modules(new AssetFileModule()) - .assetFile(context, "asset_file.realm") + .assetFile("asset_file.realm") .build(); Realm.deleteRealm(configuration); @@ -888,7 +937,7 @@ public void assetFile_failsWhenDeleteRealmIfMigrationNeededConfigured() { try { builder .deleteRealmIfMigrationNeeded() - .assetFile(context, "asset_file.realm"); + .assetFile("asset_file.realm"); fail(); } catch (IllegalStateException expected) { assertEquals("Realm cannot use an asset file when previously configured to clear its schema in migration by calling deleteRealmIfMigrationNeeded().", diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index aa0ecba5e1..662f1d446f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -523,7 +523,7 @@ public void execute(Realm realm) { realm_differentName.close(); } - // Check the hash code of the object from a Realm in different folder. + // Check the hash code of the object from a Realm in different directory. RealmConfiguration realmConfig_differentPath = configFactory.createConfiguration( "anotherDir", realmConfig.getRealmFileName()); Realm realm_differentPath = Realm.getInstance(realmConfig_differentPath); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 1eb985e3f5..816f4fba5a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -174,30 +174,6 @@ private void populateTestRealm() { populateTestRealm(realm, TEST_DATA_SIZE); } - @Test(expected = IllegalArgumentException.class) - public void getInstance_nullDir() { - Realm.getInstance(new RealmConfiguration.Builder((File) null).build()); - } - - @Test - public void getInstance_writeProtectedDir() { - File folder = new File("/"); - thrown.expect(IllegalArgumentException.class); - Realm.getInstance(new RealmConfiguration.Builder(folder).build()); - } - - @Test(expected = IllegalArgumentException.class) - public void getInstance_nullContextWithCustomDirThrows() { - Realm.getInstance(new RealmConfiguration.Builder((Context) null, configFactory.getRoot()).build()); - } - - @Test - public void getInstance_writeProtectedDirWithContext() { - File folder = new File("/"); - thrown.expect(IllegalArgumentException.class); - Realm.getInstance(new RealmConfiguration.Builder(context, folder).build()); - } - @Test public void getInstance_writeProtectedFile() throws IOException { String REALM_FILE = "readonly.realm"; @@ -208,7 +184,10 @@ public void getInstance_writeProtectedFile() throws IOException { assertTrue(realmFile.setWritable(false)); try { - Realm.getInstance(new RealmConfiguration.Builder(folder).name(REALM_FILE).build()); + Realm.getInstance(new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + .directory(folder) + .name(REALM_FILE) + .build()); } catch (RealmFileException expected) { assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); } @@ -224,7 +203,7 @@ public void getInstance_writeProtectedFileWithContext() throws IOException { assertTrue(realmFile.setWritable(false)); try { - Realm.getInstance(new RealmConfiguration.Builder(context, folder).name(REALM_FILE).build()); + Realm.getInstance(new RealmConfiguration.Builder(context).directory(folder).name(REALM_FILE).build()); } catch (RealmFileException expected) { assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); } @@ -1980,7 +1959,9 @@ public void deleteRealm() throws InterruptedException { File tempDirRenamed = new File(configFactory.getRoot(), "delete_test_dir_2"); assertTrue(tempDir.mkdir()); - final RealmConfiguration configuration = new RealmConfiguration.Builder(tempDir).build(); + final RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + .directory(tempDir) + .build(); final CountDownLatch bgThreadReadyLatch = new CountDownLatch(1); final CountDownLatch readyToCloseLatch = new CountDownLatch(1); diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 94d2d2e460..6b34702235 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -19,6 +19,7 @@ import android.content.Context; import android.content.res.AssetManager; import android.os.Looper; +import android.support.test.InstrumentationRegistry; import android.util.Log; import org.junit.Assert; @@ -382,7 +383,9 @@ public static RealmConfiguration createConfiguration(Context context, String nam */ @Deprecated public static RealmConfiguration createConfiguration(File dir, String name, byte[] key) { - RealmConfiguration.Builder config = new RealmConfiguration.Builder(dir).name(name); + RealmConfiguration.Builder config = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + .directory(dir) + .name(name); if (key != null) { config.encryptionKey(key); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index 7930566faf..b646cc7257 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.res.AssetManager; +import android.support.test.InstrumentationRegistry; import org.junit.rules.TemporaryFolder; import org.junit.runner.Description; @@ -88,13 +89,14 @@ protected void after() { throw e; } } finally { - // This will delete the temp folder. + // This will delete the temp directory. super.after(); } } public RealmConfiguration createConfiguration() { - RealmConfiguration configuration = new RealmConfiguration.Builder(getRoot()) + RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + .directory(getRoot()) .build(); configurations.add(configuration); @@ -104,7 +106,8 @@ public RealmConfiguration createConfiguration() { public RealmConfiguration createConfiguration(String subDir, String name) { final File folder = new File(getRoot(), subDir); assertTrue(folder.mkdirs()); - RealmConfiguration configuration = new RealmConfiguration.Builder(folder) + RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + .directory(folder) .name(name) .build(); @@ -113,7 +116,8 @@ public RealmConfiguration createConfiguration(String subDir, String name) { } public RealmConfiguration createConfiguration(String name) { - RealmConfiguration configuration = new RealmConfiguration.Builder(getRoot()) + RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + .directory(getRoot()) .name(name) .build(); @@ -122,7 +126,8 @@ public RealmConfiguration createConfiguration(String name) { } public RealmConfiguration createConfiguration(String name, byte[] key) { - RealmConfiguration configuration = new RealmConfiguration.Builder(getRoot()) + RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + .directory(getRoot()) .name(name) .encryptionKey(key) .build(); @@ -132,14 +137,15 @@ public RealmConfiguration createConfiguration(String name, byte[] key) { } public RealmConfiguration.Builder createConfigurationBuilder() { - return new RealmConfiguration.Builder(getRoot()); + return new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()).directory(getRoot()); } // Copies a Realm file from assets to temp dir public void copyRealmFromAssets(Context context, String realmPath, String newName) throws IOException { // Delete the existing file before copy - RealmConfiguration configToDelete = new RealmConfiguration.Builder(getRoot()) + RealmConfiguration configToDelete = new RealmConfiguration.Builder(context) + .directory(getRoot()) .name(newName) .build(); Realm.deleteRealm(configToDelete); diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java index 47a1136b30..da65aab166 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java @@ -31,13 +31,13 @@ public class BenchmarkConfig { public static SpannerConfig getConfiguration(String className) { - // Document folder is located at: /sdcard/realm-benchmarks + // Document directory is located at: /sdcard/realm-benchmarks // Benchmarks results should be saved in /results/.json // Baseline data should be found in /baselines/.json // Custom CSV files should be found in /csv/.csv File externalDocuments = new File(Environment.getExternalStorageDirectory(), "realm-benchmarks"); if (!externalDocuments.exists() && !externalDocuments.mkdir()) { - throw new RuntimeException("Could not create benchmark folder: " + externalDocuments); + throw new RuntimeException("Could not create benchmark directory: " + externalDocuments); } File resultsDir = new File(externalDocuments, "results"); File baselineDir = new File(externalDocuments, "baselines"); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 08a8e0eb6d..3bcd4e79d5 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -665,12 +665,12 @@ public void onResult(int count) { } String canonicalPath = configuration.getPath(); - File realmFolder = configuration.getRealmFolder(); + File realmFolder = configuration.getRealmDirectory(); String realmFileName = configuration.getRealmFileName(); File managementFolder = new File(realmFolder, realmFileName + management); - // delete files in management folder and the folder - // there is no subfolders in the management folder + // delete files in management directory and the directory + // there is no subfolders in the management directory File[] files = managementFolder.listFiles(); if (files != null) { for (File file : files) { @@ -679,7 +679,7 @@ public void onResult(int count) { } realmDeleted.set(realmDeleted.get() && managementFolder.delete()); - // delete specific files in root folder + // delete specific files in root directory realmDeleted.set(realmDeleted.get() && deletes(canonicalPath, realmFolder, realmFileName)); } }); diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 49e10e1922..99648ee4c1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -293,7 +293,7 @@ static synchronized void invokeWithLock(Callback0 callback) { private static void copyAssetFileIfNeeded(RealmConfiguration configuration) { IOException exceptionWhenClose = null; if (configuration.hasAssetFile()) { - File realmFile = new File(configuration.getRealmFolder(), configuration.getRealmFileName()); + File realmFile = new File(configuration.getRealmDirectory(), configuration.getRealmFileName()); if (realmFile.exists()) { return; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 23df1a6274..d9b42c51b2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -30,6 +30,7 @@ import java.util.HashSet; import java.util.Set; +import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.RealmCore; @@ -84,7 +85,7 @@ public final class RealmConfiguration { } } - private final File realmFolder; + private final File realmDirectory; private final String realmFileName; private final String canonicalPath; private final String assetFilePath; @@ -99,9 +100,9 @@ public final class RealmConfiguration { private final WeakReference contextWeakRef; private RealmConfiguration(Builder builder) { - this.realmFolder = builder.folder; + this.realmDirectory = builder.directory; this.realmFileName = builder.fileName; - this.canonicalPath = Realm.getCanonicalPath(new File(realmFolder, realmFileName)); + this.canonicalPath = Realm.getCanonicalPath(new File(realmDirectory, realmFileName)); this.assetFilePath = builder.assetFilePath; this.key = builder.key; this.schemaVersion = builder.schemaVersion; @@ -114,8 +115,8 @@ private RealmConfiguration(Builder builder) { this.contextWeakRef = builder.contextWeakRef; } - public File getRealmFolder() { - return realmFolder; + public File getRealmDirectory() { + return realmDirectory; } public String getRealmFileName() { @@ -222,7 +223,7 @@ public boolean equals(Object obj) { if (schemaVersion != that.schemaVersion) return false; if (deleteRealmIfMigrationNeeded != that.deleteRealmIfMigrationNeeded) return false; - if (!realmFolder.equals(that.realmFolder)) return false; + if (!realmDirectory.equals(that.realmDirectory)) return false; if (!realmFileName.equals(that.realmFileName)) return false; if (!canonicalPath.equals(that.canonicalPath)) return false; if (!Arrays.equals(key, that.key)) return false; @@ -236,7 +237,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - int result = realmFolder.hashCode(); + int result = realmDirectory.hashCode(); result = 31 * result + realmFileName.hashCode(); result = 31 * result + canonicalPath.hashCode(); result = 31 * result + (key != null ? Arrays.hashCode(key) : 0); @@ -304,7 +305,7 @@ private static RealmProxyMediator getModuleMediator(String fullyQualifiedModuleC public String toString() { //noinspection StringBufferReplaceableByString StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("realmFolder: ").append(realmFolder.toString()); + stringBuilder.append("realmDirectory: ").append(realmDirectory.toString()); stringBuilder.append("\n"); stringBuilder.append("realmFileName : ").append(realmFileName); stringBuilder.append("\n"); @@ -346,7 +347,7 @@ private static synchronized boolean isRxJavaAvailable() { * RealmConfiguration.Builder used to construct instances of a RealmConfiguration in a fluent manner. */ public static final class Builder { - private File folder; + private File directory; private String fileName; private String assetFilePath; private byte[] key; @@ -360,20 +361,6 @@ public static final class Builder { private RxObservableFactory rxFactory; private Realm.Transaction initialDataTransaction; - /** - * Creates an instance of the Builder for the RealmConfiguration. - * The Realm file will be saved in the provided folder. - * - * @param folder the folder to save Realm file in. Folder must be writable. - * @throws IllegalArgumentException if folder doesn't exist or isn't writable. - * @deprecated Please use {@link #Builder(Context, File)} instead. - */ - @Deprecated - public Builder(File folder) { - RealmCore.loadLibrary(); - initializeBuilder(folder); - } - /** * Creates an instance of the Builder for the RealmConfiguration. *

@@ -388,37 +375,13 @@ public Builder(Context context) { throw new IllegalArgumentException("A non-null Context must be provided"); } RealmCore.loadLibrary(context); - initializeBuilder(context.getFilesDir()); - } - - /** - * Creates an instance of the Builder for the RealmConfiguration. - *

- * The Realm file will be saved in the provided folder, and it might require additional permissions. - * - * @param context the Android application context. - * @param folder the folder to save Realm file in. Folder must be writable. - * @throws IllegalArgumentException if folder doesn't exist or isn't writable. - */ - public Builder(Context context, File folder) { - if (context == null) { - throw new IllegalArgumentException("A non-null Context must be provided"); - } - RealmCore.loadLibrary(context); - initializeBuilder(folder); + initializeBuilder(context); } // Setup builder in its initial state - private void initializeBuilder(File folder) { - if (folder == null || !folder.isDirectory()) { - throw new IllegalArgumentException(("An existing folder must be provided. " + - "Yours was " + (folder != null ? folder.getAbsolutePath() : "null"))); - } - if (!folder.canWrite()) { - throw new IllegalArgumentException("Folder is not writable: " + folder.getAbsolutePath()); - } - - this.folder = folder; + private void initializeBuilder(Context context) { + this.contextWeakRef = new WeakReference(context); + this.directory = context.getFilesDir(); this.fileName = Realm.DEFAULT_REALM_NAME; this.key = null; this.schemaVersion = 0; @@ -431,7 +394,7 @@ private void initializeBuilder(File folder) { } /** - * Sets the filename for the Realm. + * Sets the filename for the Realm file. */ public Builder name(String filename) { if (filename == null || filename.isEmpty()) { @@ -442,6 +405,30 @@ public Builder name(String filename) { return this; } + /** + * Specify the directory where the Realm file will be saved. The default value is {@code context.getFiles()}. + * If the directory does not exist, it will be created. + * + * @param directory the directory to save the Realm file in. Directory must be writable. + * @throws IllegalArgumentException if {@code directory} is null, not writable or a file. + */ + public Builder directory(File directory) { + if (directory == null) { + throw new IllegalArgumentException("Non-null 'dir' required."); + } + if (directory.isFile()) { + throw new IllegalArgumentException("'dir' is a file, not a directory: " + directory.getAbsolutePath() + "."); + } + if (!directory.exists() && !directory.mkdirs()) { + throw new IllegalArgumentException("Could not create the specified directory: " + directory.getAbsolutePath() + "."); + } + if (!directory.canWrite()) { + throw new IllegalArgumentException("Realm directory is not writable: " + directory.getAbsolutePath() + "."); + } + this.directory = directory; + return this; + } + /** * Sets the 64 bit key used to encrypt and decrypt the Realm file. */ @@ -493,11 +480,11 @@ public Builder migration(RealmMigration migration) { * with the new Realm schema. * *

This cannot be configured to have an asset file at the same time by calling - * {@link #assetFile(Context, String)} as the provided asset file will be deleted in migrations. + * {@link #assetFile(String)} as the provided asset file will be deleted in migrations. * *

WARNING! This will result in loss of data. * - * @throws IllegalStateException if configured to use an asset file by calling {@link #assetFile(Context, String)} previously. + * @throws IllegalStateException if configured to use an asset file by calling {@link #assetFile(String)} previously. */ public Builder deleteRealmIfMigrationNeeded() { if (this.assetFilePath != null && this.assetFilePath.length() != 0) { @@ -587,14 +574,10 @@ public Builder initialData(Realm.Transaction transaction) { *

* WARNING: This could potentially be a lengthy operation and should ideally be done on a background thread. * - * @param context Android application context. * @param assetFile path to the asset database file. * @throws IllegalStateException if this is configured to clear its schema by calling {@link #deleteRealmIfMigrationNeeded()}. */ - public Builder assetFile(Context context, final String assetFile) { - if (context == null) { - throw new IllegalArgumentException("A non-null Context must be provided"); - } + public Builder assetFile(final String assetFile) { if (TextUtils.isEmpty(assetFile)) { throw new IllegalArgumentException("A non-empty asset file path must be provided"); } @@ -605,7 +588,6 @@ public Builder assetFile(Context context, final String assetFile) { throw new IllegalStateException("Realm cannot use an asset file when previously configured to clear its schema in migration by calling deleteRealmIfMigrationNeeded()."); } - this.contextWeakRef = new WeakReference<>(context); this.assetFilePath = assetFile; return this; From ac2ec86b77c44c77354e72ebd7ca5682ffd570f1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 30 Aug 2016 21:35:18 +0800 Subject: [PATCH 0016/2110] DeleteLocalRef when the ref is created in loop (#3366) Add wrapper class for JNI local reference to delete the local ref after using it. This is reported by user on helpscout: https://secure.helpscout.net/conversation/244053233/6163/?folderId=366141 And some useful explanation can be found: http://stackoverflow.com/questions/24289724/jni-deletelocalref-clarification Normally the local ref doesn't have to be deleted since they will be cleaned up when program returns to Java from native code. Using it in a loop is obvious a corner case: the size of local ref table is relatively small (512 on Android). To avoid it, the local ref should be deleted when using it in a loop. --- CHANGELOG.md | 1 + .../java/io/realm/RealmAsyncQueryTests.java | 54 +++++++++++++++++++ .../main/cpp/io_realm_internal_TableQuery.cpp | 5 +- realm/realm-library/src/main/cpp/util.hpp | 26 ++++++++- 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0925e3aec..77b60993f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Bug fixes * Fixed a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11 (#3356). +* Fixed a potential crash when there were lots of async queries waiting in the queue. ## 1.2.0 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index b36526ddaf..1c6b78eb23 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -27,14 +27,17 @@ import org.junit.runner.RunWith; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Date; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import dk.ilios.spanner.All; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AnnotationIndexTypes; @@ -2111,6 +2114,57 @@ public void run() { realm.close(); } + // This test reproduce the issue in https://secure.helpscout.net/conversation/244053233/6163/?folderId=366141 + // First it creates 512 async queries, then trigger a transaction to make the queries gets update with + // nativeBatchUpdateQueries. It should not exceed the limits of local ref map size in JNI. + @Test + @RunTestInLooperThread + public void batchUpdate_localRefIsDeletedInLoopOfNativeBatchUpdateQueries() { + final Realm realm = looperThread.realm; + // For Android, the size of local ref map is 512. Use 1024 for more pressure. + final int TEST_COUNT = 1024; + final AtomicBoolean updatesTriggered = new AtomicBoolean(false); + // The first time onChange gets called for every results. + final AtomicInteger firstOnChangeCounter = new AtomicInteger(0); + // The second time onChange gets called for every results which is triggered by the transaction. + final AtomicInteger secondOnChangeCounter = new AtomicInteger(0); + + final RealmChangeListener> listener = new RealmChangeListener>() { + @Override + public void onChange(RealmResults element) { + if (updatesTriggered.get()) { + // Step 4: Test finished after all results's onChange gets called the 2nd time. + int count = secondOnChangeCounter.addAndGet(1); + if (count == TEST_COUNT) { + realm.removeAllChangeListeners(); + looperThread.testComplete(); + } + } else { + int count = firstOnChangeCounter.addAndGet(1); + if (count == TEST_COUNT) { + // Step 3: Commit the transaction to trigger queries updates. + updatesTriggered.set(true); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class); + } + }); + } else { + // Step 2: Create 2nd - TEST_COUNT queries. + RealmResults results = realm.where(AllTypes.class).findAllAsync(); + results.addChangeListener(this); + looperThread.keepStrongReference.add(results); + } + } + } + }; + // Step 1. Create first async to kick the test start. + RealmResults results = realm.where(AllTypes.class).findAllAsync(); + results.addChangeListener(listener); + looperThread.keepStrongReference.add(results); + } + // *** Helper methods *** private void populateTestRealm(final Realm testRealm, int objects) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index b1913cb615..2cd3e9c7a8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -1212,7 +1212,10 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate // Step3: Run & export the queries against the latest shared group for (size_t i = 0; i < number_of_queries; ++i) { - JniLongArray query_param_array(env, (jlongArray) env->GetObjectArrayElement(query_param_matrix, i)); + // Delete the local ref since we might have a long loop + JniLocalRef local_ref(env, (jlongArray) env->GetObjectArrayElement(query_param_matrix, i)); + JniLongArray query_param_array(env, local_ref); + switch (query_param_array[0]) { // 0, index of the type of query, the next indicies are parameters case QUERY_TYPE_FIND_ALL: {// nativeFindAllWithHandover exported_handover_tableview_array[i] = diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 4f0a6e01a3..5ea8cdc8d0 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -551,8 +551,9 @@ class KeyBuffer { } ~KeyBuffer() { - if (m_ptr) + if (m_ptr) { m_env->ReleaseByteArrayElements(m_array, m_ptr, JNI_ABORT); + } } private: @@ -696,6 +697,29 @@ class JniBooleanArray { jint m_releaseMode; }; +// Wraps jobject and automatically calls DeleteLocalRef when this object is destroyed. +// DeleteLocalRef is not necessary to be called in most cases since all local references will be cleaned up when the +// program returns to Java from native. But if the LocaRef is created in a loop, consider to use this class to wrap it +// because the size of local reference table is relative small (512 on Android). +template +class JniLocalRef { +public: + JniLocalRef(JNIEnv* env, T obj) : m_jobject(obj), m_env(env) {}; + ~JniLocalRef() + { + m_env->DeleteLocalRef(m_jobject); + } + + inline operator T() const noexcept + { + return m_jobject; + } + +private: + T m_jobject; + JNIEnv* m_env; +}; + extern jclass java_lang_long; extern jmethodID java_lang_long_init; extern jclass java_lang_float; From 8a6ad03232d680d2a21fc9dd1affb7b3170f0f6d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 30 Aug 2016 15:37:29 +0800 Subject: [PATCH 0017/2110] Build with OS sync support --- .gitmodules | 2 +- Jenkinsfile | 14 +++++++++++--- .../src/main/cpp/io_realm_internal_SharedRealm.cpp | 10 +++++++++- realm/realm-library/src/main/cpp/object-store | 2 +- .../main/java/io/realm/internal/SharedRealm.java | 7 +++++-- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/.gitmodules b/.gitmodules index 35b419c520..4e9399aee9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "realm/realm-library/src/main/cpp/object-store"] path = realm/realm-library/src/main/cpp/object-store - url = https://github.com/realm/realm-object-store.git + url = git@github.com:realm/realm-object-store-private.git diff --git a/Jenkinsfile b/Jenkinsfile index e00abd5c85..8adcc47138 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,11 +8,19 @@ try { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { stage 'SCM' - checkout scm + checkout([ + $class: 'GitSCM', + branches: scm.branches, + gitTool: 'native git', + extensions: scm.extensions + [[$class: 'CleanCheckout']], + userRemoteConfigs: scm.userRemoteConfigs + ]) + sshagent(['realm-ci-ssh']) { + sh 'git submodule sync' + sh 'git submodule update --init --recursive' + } // Make sure not to delete the folder that Jenkins allocates to store scripts sh 'git clean -ffdx -e .????????' - // Update submodule for object-store - sh 'git submodule update --init --force' stage 'Docker build' def buildEnv = docker.build 'realm-java:snapshot' diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 9136decb2f..6915552739 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -3,6 +3,7 @@ #include "shared_realm.hpp" #include "util.hpp" +#include "sync_config.hpp" using namespace realm; @@ -20,7 +21,7 @@ static_assert(SchemaMode::Manual == JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstring realm_path, jbyteArray key, jbyte schema_mode, jboolean in_memory, jboolean cache, jboolean disable_format_upgrade, - jboolean auto_change_notification) + jboolean auto_change_notification, jstring sync_server_url, jstring sync_user_token) { TR_ENTER() @@ -35,6 +36,13 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri config->cache = cache; config->disable_format_upgrade = disable_format_upgrade; config->automatic_change_notifications = auto_change_notification; + if (sync_server_url) { + JStringAccessor url(env, sync_server_url); + JStringAccessor token(env, sync_user_token); + config->sync_config = std::make_shared(); + config->sync_config->user_tag = token; + config->sync_config->realm_url = url; + } return reinterpret_cast(config); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 4663837974..decd4a0693 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 4663837974a46fa2b34f85362b5c86b5a1d3437a +Subproject commit decd4a0693cda3b4e07e13982e74a0e7e7c4d21b diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 4f0c9755b7..895b2252dd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -139,7 +139,9 @@ public static SharedRealm getInstance(RealmConfiguration config) { config.getDurability() == Durability.MEM_ONLY, false, false, - false); + false, + null, + null); try { return new SharedRealm(nativeGetSharedRealm(nativeConfigPtr), config); } finally { @@ -274,7 +276,8 @@ protected void finalize() throws Throwable { private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory, boolean cache, boolean disableFormatUpgrade, - boolean autoChangeNotification); + boolean autoChangeNotification, + String syncServerURL, String syncUserToken); private static native void nativeCloseConfig(long nativeConfigPtr); private static native long nativeGetSharedRealm(long nativeConfigPtr); private static native void nativeCloseSharedRealm(long nativeSharedRealmPtr); From 429eaf8e8c2620aff1c75a9aecb71ac08f79965e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 1 Sep 2016 16:02:25 +0800 Subject: [PATCH 0018/2110] Integrate Object Store [PART4] - OS notifications (#3370) * Use OS's notification mechanism to notify threads. * Create RealmNotificer interface for decouple Android related handler logic. * Create AndroidNotifier for the handler notifications. The major change of this PR is about the timing. The notifications are not sent immediately after transaction committed. Instead, there is a dedicated thread monitoring changes and notify others when changes happen. The known problem is for every RealmConfiguration, a monitor thread will be created which is not ideal for app which is using multiple RealmConfiguration. There are different implementations for the monitoring thread in OS. For Android, we can choose from generic which is based on the core's wait_for_change() and android which is used by dotnet based on the named pipe. To align with dotnet, we are using the named pipe for now which also enables notifications between realm-java and realm-dotnet. --- .../java/io/realm/NotificationsTest.java | 18 -- .../androidTest/java/io/realm/RealmTests.java | 36 +--- .../cpp/io_realm_internal_SharedRealm.cpp | 11 +- .../src/main/cpp/java_binding_context.cpp | 60 +++++++ .../src/main/cpp/java_binding_context.hpp | 66 +++++++ .../main/java/io/realm/AndroidNotifier.java | 169 ++++++++++++++++++ .../src/main/java/io/realm/BaseRealm.java | 121 ++----------- .../main/java/io/realm/HandlerController.java | 12 +- .../src/main/java/io/realm/Realm.java | 22 ++- .../src/main/java/io/realm/RealmQuery.java | 88 +++++---- .../java/io/realm/internal/RealmNotifier.java | 65 +++++++ .../java/io/realm/internal/SharedRealm.java | 19 +- .../realm/internal/async/QueryUpdateTask.java | 74 ++++---- 13 files changed, 514 insertions(+), 247 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/java_binding_context.cpp create mode 100644 realm/realm-library/src/main/cpp/java_binding_context.hpp create mode 100644 realm/realm-library/src/main/java/io/realm/AndroidNotifier.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 6de7246d95..b7268783cc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -317,24 +317,6 @@ public void run() { assertTrue(result); } - @Test - @UiThreadTest - public void handlerNotRemovedToSoon() { - RealmConfiguration realmConfig = configFactory.createConfiguration("private-realm"); - Realm.deleteRealm(realmConfig); - Realm instance1 = Realm.getInstance(realmConfig); - Realm instance2 = Realm.getInstance(realmConfig); - assertEquals(instance1.getPath(), instance2.getPath()); - assertNotNull(instance1.handler); - - // If multiple instances are open on the same thread, don't remove handler on that thread - // until last instance is closed. - instance2.close(); - assertNotNull(instance1.handler); - instance1.close(); - assertNull(instance1.handler); - } - @Test @RunTestInLooperThread public void commitTransaction_delayChangeListenerOnSameThread() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 816f4fba5a..03e4e1544a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -243,38 +243,6 @@ public void checkIfValid() { realm = null; } - @Test - @UiThreadTest - public void internalRealmChangedHandlersRemoved() { - realm.close(); // Clear handler created by testRealm in setUp() - assertEquals(0, Realm.getHandlers().size()); - final String REALM_NAME = "test-internalhandlers"; - RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME); - Realm.deleteRealm(realmConfig); - - // Open and close first instance of a Realm - Realm realm = null; - try { - realm = Realm.getInstance(realmConfig); - assertFalse(this.realm == realm); - assertEquals(1, Realm.getHandlers().size()); - realm.close(); - - // All Realms closed. No handlers should be alive. - assertEquals(0, Realm.getHandlers().size()); - - // Open instance the 2nd time. Old handler should now be gone - realm = Realm.getInstance(realmConfig); - assertEquals(1, Realm.getHandlers().size()); - realm.close(); - - } finally { - if (realm != null) { - realm.close(); - } - } - } - @Test public void getInstance() { assertNotNull("Realm.getInstance unexpectedly returns null", realm); @@ -2002,7 +1970,9 @@ public void run() { assertTrue(Realm.deleteRealm(configuration)); // Directory should be empty now - assertEquals(0, tempDir.listFiles().length); + // FIXME: .note file is the named pipe for OS android notification. Just don't delete it until we figure out + // one single daemon thread for notification. + assertEquals(/*0*/1, tempDir.listFiles().length); } // Test that all methods that require a transaction (ie. any function that mutates Realm data) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 9136decb2f..97789266b5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -1,10 +1,13 @@ -#include #include "io_realm_internal_SharedRealm.h" +#include "object_store.hpp" #include "shared_realm.hpp" + +#include "java_binding_context.hpp" #include "util.hpp" using namespace realm; +using namespace realm::_impl; static_assert(SchemaMode::Automatic == static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_AUTOMATIC), ""); @@ -51,13 +54,16 @@ Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv *, jclass, jlong con } JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr) +Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr, jobject notifier) { TR_ENTER_PTR(config_ptr) auto config = reinterpret_cast(config_ptr); try { auto shared_realm = Realm::get_shared_realm(*config); + shared_realm->m_binding_context = JavaBindingContext::create(env, notifier); + // advance_read needs to be handled by Java because of async query. + shared_realm->set_auto_refresh(false); return reinterpret_cast(new SharedRealm(std::move(shared_realm))); } CATCH_STD() return static_cast(NULL); @@ -347,7 +353,6 @@ Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv *env, jclass, jlon JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp new file mode 100644 index 0000000000..b8954caeb6 --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 "java_binding_context.hpp" + +#include "util/format.hpp" + +using namespace realm; +using namespace realm::_impl; + +JavaBindingContext::JavaBindingContext(const ConcreteJavaBindContext& concrete_context) + : m_local_jni_env(concrete_context.jni_env) +{ + jint ret = m_local_jni_env->GetJavaVM(&m_jvm); + if (ret != 0) { + throw std::runtime_error(util::format("Failed to get Java vm. Error: %d", ret)); + } + if (concrete_context.java_notifier) { + m_java_notifier = m_local_jni_env->NewWeakGlobalRef(concrete_context.java_notifier); + jclass cls = m_local_jni_env->GetObjectClass(m_java_notifier); + m_notify_by_other_method = m_local_jni_env->GetMethodID(cls, "notifyCommitByOtherThread", "()V"); + } else { + m_java_notifier = nullptr; + } +} + +JavaBindingContext::~JavaBindingContext() +{ + if (m_java_notifier) { + // Always try to attach here since this may be called in the finalizer/phantom thread where m_local_jni_env + // should not be used on. No need to call DetachCurrentThread since this thread should always be created by + // JVM. + JNIEnv *env; + m_jvm->AttachCurrentThread(&env, nullptr); + env->DeleteWeakGlobalRef(m_java_notifier); + } +} + +void JavaBindingContext::changes_available() +{ + jobject notifier = m_local_jni_env->NewLocalRef(m_java_notifier); + if (notifier) { + m_local_jni_env->CallVoidMethod(m_java_notifier, m_notify_by_other_method); + m_local_jni_env->DeleteLocalRef(notifier); + } +} + diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp new file mode 100644 index 0000000000..ee96a000a5 --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +#ifndef JAVA_BINDING_CONTEXT_HPP +#define JAVA_BINDING_CONTEXT_HPP + +#include +#include + +#include "binding_context.hpp" + +namespace realm { + +namespace _impl { + +// Binding context which will be called from OS. +class JavaBindingContext final : public BindingContext { +private: + struct ConcreteJavaBindContext { + JNIEnv* jni_env; + jobject java_notifier; + explicit ConcreteJavaBindContext(JNIEnv* env, jobject notifier) + :jni_env(env), java_notifier(notifier) { } + }; + + // The JNIEnv for the thread which creates the Realm. This should only be used on the current thread. + JNIEnv* m_local_jni_env; + // All methods should be called from the thread which creates the realm except the destructor which might be + // called from finalizer/phantom daemon. So we need a jvm pointer to create JNIEnv there if needed. + JavaVM* m_jvm; + // A weak global ref to the implementation of RealmNotifier + // Java should hold a strong ref to it as long as the SharedRealm lives + jobject m_java_notifier; + // Method IDs from RealmNotifier implementation. Cache them as member vars. + jmethodID m_notify_by_other_method; + +public: + virtual ~JavaBindingContext(); + virtual void changes_available(); + + JavaBindingContext(const ConcreteJavaBindContext&); + static inline std::unique_ptr create(JNIEnv* env, jobject notifier) + { + return std::make_unique(ConcreteJavaBindContext{env, notifier}); + }; +}; + +} // namespace _impl + +} // namespace realm + +#endif + diff --git a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java new file mode 100644 index 0000000000..3f8d577b6d --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java @@ -0,0 +1,169 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import io.realm.internal.HandlerControllerConstants; +import io.realm.internal.RealmNotifier; +import io.realm.internal.async.QueryUpdateTask; +import io.realm.internal.log.RealmLog; + +/** + * Implementation of {@link RealmNotifier} for Android based on {@link Handler}. + */ +// FIXME: Please move me to the io.realm.internal when HandlerController is there. +class AndroidNotifier implements RealmNotifier { + private Handler handler; + + public AndroidNotifier(HandlerController handlerController) { + if (isAutoRefreshAvailable()) { + handler = new Handler(handlerController); + } + } + + // Called by Java when transaction committed to send LOCAL_COMMIT to current thread's handler. + @Override + public void notifyCommitByLocalThread() { + if (handler == null) { + return; + } + + // Force any updates on the current thread to the front the queue. Doing this is mostly + // relevant on the UI thread where it could otherwise process a motion event before the + // REALM_CHANGED event. This could in turn cause a UI component like ListView to crash. See + // https://github.com/realm/realm-android-adapters/issues/11 for such a case. + // Other Looper threads could process similar events. For that reason all looper threads will + // prioritize local commits. + // + // If a user is doing commits inside a RealmChangeListener this can cause the Looper thread to get + // event starved as it only starts handling Realm events instead. This is an acceptable risk as + // that behaviour indicate a user bug. Previously this would be hidden as the UI would still + // be responsive. + Message msg = Message.obtain(); + msg.what = HandlerControllerConstants.LOCAL_COMMIT; + if (!handler.hasMessages(HandlerControllerConstants.LOCAL_COMMIT)) { + handler.removeMessages(HandlerControllerConstants.REALM_CHANGED); + handler.sendMessageAtFrontOfQueue(msg); + } + } + + // This is called by OS when other thread/process changes the Realm. + // This is getting called on the same thread which created the Realm. + // FIXME: The whole calling routine is twisted and needs to be rewritten in the near future. + // |---------------------------------------------------------------+--------------+------------------------------------------------| + // | Thread A | Thread B | Daemon Thread | + // |---------------------------------------------------------------+--------------+------------------------------------------------| + // | | Make changes | | + // | | | Detect and notify thread A through JNI ALooper | + // | Call OS's Realm::notify() from OS's ALooper callback | | | + // | Realm::notify() calls JavaBindingContext:change_available() | | | + // | change_available calls into this method to send REALM_CHANGED | | | + // |---------------------------------------------------------------+--------------+------------------------------------------------| + @Override + public void notifyCommitByOtherThread() { + if (handler == null) { + return; + } + + // Note there is a race condition with handler.hasMessages() and handler.sendEmptyMessage() + // as the target thread consumes messages at the same time. In this case it is not a problem as worst + // case we end up with two REALM_CHANGED messages in the queue. + boolean messageHandled = true; + if (!handler.hasMessages(HandlerControllerConstants.REALM_CHANGED) && + !handler.hasMessages(HandlerControllerConstants.LOCAL_COMMIT)) { + messageHandled = handler.sendEmptyMessage(HandlerControllerConstants.REALM_CHANGED); + } + if (!messageHandled) { + RealmLog.w("Cannot update Looper threads when the Looper has quit. Use realm.setAutoRefresh(false) " + + "to prevent this."); + } + } + + @Override + public void post(Runnable runnable) { + Looper looper = handler.getLooper(); + if (looper.getThread().isAlive()) { // The receiving thread is alive + handler.post(runnable); + } + } + + @Override + public boolean isValid() { + return handler != null; + } + + @Override + public void close() { + if (handler != null) { + handler.removeCallbacksAndMessages(null); + handler = null; + } + } + + @Override + public void completeAsyncResults(QueryUpdateTask.Result result) { + Looper looper = handler.getLooper(); + if (looper.getThread().isAlive()) { // The receiving thread is alive + handler.obtainMessage(HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result).sendToTarget(); + } + } + + @Override + public void completeAsyncObject(QueryUpdateTask.Result result) { + Looper looper = handler.getLooper(); + if (looper.getThread().isAlive()) { // The receiving thread is alive + handler.obtainMessage(HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT, result).sendToTarget(); + } + } + + @Override + public void throwBackgroundException(Throwable throwable) { + Looper looper = handler.getLooper(); + if (looper.getThread().isAlive()) { // The receiving thread is alive + handler.obtainMessage( + HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(throwable)).sendToTarget(); + } + } + + @Override + public void completeUpdateAsyncQueries(QueryUpdateTask.Result result) { + Looper looper = handler.getLooper(); + if (looper.getThread().isAlive()) { // The receiving thread is alive + handler.obtainMessage(HandlerControllerConstants.COMPLETED_UPDATE_ASYNC_QUERIES, result).sendToTarget(); + } + } + + private static boolean isAutoRefreshAvailable() { + return (Looper.myLooper() != null && !isIntentServiceThread()); + } + + private static boolean isIntentServiceThread() { + // Tries to determine if a thread is an IntentService thread. No public API can detect this, + // so use the thread name as a heuristic: + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/IntentService.java#108 + String threadName = Thread.currentThread().getName(); + return threadName != null && threadName.startsWith("IntentService["); + } + + // For testing purpose only. Should be removed ideally. + public void setHandler(Handler handler) { + this.handler = handler; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 3bcd4e79d5..4880ce8217 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -18,7 +18,6 @@ import android.os.Handler; import android.os.Looper; -import android.os.Message; import com.getkeepsafe.relinker.BuildConfig; @@ -27,13 +26,10 @@ import java.io.FileNotFoundException; import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; -import io.realm.internal.HandlerControllerConstants; import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; import io.realm.internal.SharedRealm; @@ -63,9 +59,6 @@ abstract class BaseRealm implements Closeable { private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; - // Map between a Handler and the canonical path to a Realm file - protected static final Map handlers = new ConcurrentHashMap(); - // Thread pool for all async operations (Query & transaction) static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); @@ -74,7 +67,6 @@ abstract class BaseRealm implements Closeable { protected SharedRealm sharedRealm; RealmSchema schema; - Handler handler; HandlerController handlerController; static { @@ -86,9 +78,9 @@ protected BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; - this.sharedRealm = SharedRealm.getInstance(configuration); - this.schema = new RealmSchema(this); this.handlerController = new HandlerController(this); + this.sharedRealm = SharedRealm.getInstance(configuration, new AndroidNotifier(this.handlerController)); + this.schema = new RealmSchema(this); if (handlerController.isAutoRefreshAvailable()) { setAutoRefresh(true); @@ -109,12 +101,6 @@ protected BaseRealm(RealmConfiguration configuration) { public void setAutoRefresh(boolean autoRefresh) { checkIfValid(); handlerController.checkCanBeAutoRefreshed(); - if (autoRefresh && !handlerController.isAutoRefreshEnabled()) { // Switch it on - handler = new Handler(handlerController); - handlers.put(handler, configuration.getPath()); - } else if (!autoRefresh && handlerController.isAutoRefreshEnabled() && handler != null) { // Switch it off - removeHandler(); - } handlerController.setAutoRefresh(autoRefresh); } @@ -204,21 +190,9 @@ public void removeAllChangeListeners() { // WARNING: If this method is used after calling any async method, the old handler will still be used. // package private, for test purpose only void setHandler(Handler handler) { - // remove the old one - handlers.remove(this.handler); - handlers.put(handler, configuration.getPath()); - this.handler = handler; + ((AndroidNotifier)sharedRealm.realmNotifier).setHandler(handler); } - /** - * Removes and stops the current thread handler as gracefully as possible. - */ - protected void removeHandler() { - handlers.remove(handler); - // Warning: This only clears the Looper queue. Handler.Callback is not removed. - handler.removeCallbacksAndMessages(null); - this.handler = null; - } /** * Writes a compacted copy of the Realm to the given destination File. @@ -349,81 +323,23 @@ public void beginTransaction() { * changes from this commit. */ public void commitTransaction() { - commitTransaction(true, true); + commitTransaction(true); } /** - * Commits an async transaction. This will not trigger any REALM_CHANGED events. Caller is responsible for handling - * that. - */ - void commitAsyncTransaction() { - commitTransaction(false, false); - } - - /** - * Commits transaction, runs the given runnable and then sends notifications. The runnable is useful to meet some - * timing conditions like the async transaction. In async transaction, the background Realm has to be closed before - * other threads see the changes to majoyly avoid the flaky tests. + * Commits transaction and sends notifications to local thread. * - * @param notifyLocalThread set to {@code false} to prevent this commit from triggering thread local change listeners. + * @param notifyLocalThread set to {@code false} to prevent this commit from triggering thread local change + * listeners. */ - void commitTransaction(boolean notifyLocalThread, boolean notifyOtherThreads) { + void commitTransaction(boolean notifyLocalThread) { checkIfValid(); sharedRealm.commitTransaction(); - for (Map.Entry handlerIntegerEntry : handlers.entrySet()) { - Handler handler = handlerIntegerEntry.getKey(); - String realmPath = handlerIntegerEntry.getValue(); - - // Sometimes we don't want to notify the local thread about commits, e.g. creating a completely new Realm - // file will make a commit in order to create the schema. Users should not be notified about that. - if (!notifyLocalThread && handler.equals(this.handler)) { - continue; - } - - // Sometimes we don't want to notify other threads about changes because we need a custom message, e.g. when - // doing async transactions. - if (!notifyOtherThreads && !handler.equals(this.handler)) { - continue; - } - - // For all other threads, use the Handler - // Note there is a race condition with handler.hasMessages() and handler.sendEmptyMessage() - // as the target thread consumes messages at the same time. In this case it is not a problem as worst - // case we end up with two REALM_CHANGED messages in the queue. - Looper looper = handler.getLooper(); - if (realmPath.equals(configuration.getPath()) // It's the right realm - && looper.getThread().isAlive()) { // The receiving thread is alive - - boolean messageHandled = true; - if (looper == Looper.myLooper()) { - // Force any updates on the current thread to the front the queue. Doing this is mostly - // relevant on the UI thread where it could otherwise process a motion event before the - // REALM_CHANGED event. This could in turn cause a UI component like ListView to crash. See - // https://github.com/realm/realm-android-adapters/issues/11 for such a case. - // Other Looper threads could process similar events. For that reason all looper threads will - // prioritize local commits. - // - // If a user is doing commits inside a RealmChangeListener this can cause the Looper thread to get - // event starved as it only starts handling Realm events instead. This is an acceptable risk as - // that behaviour indicate a user bug. Previously this would be hidden as the UI would still - // be responsive. - Message msg = Message.obtain(); - msg.what = HandlerControllerConstants.LOCAL_COMMIT; - if (!handler.hasMessages(HandlerControllerConstants.LOCAL_COMMIT)) { - handler.removeMessages(HandlerControllerConstants.REALM_CHANGED); - messageHandled = handler.sendMessageAtFrontOfQueue(msg); - } - } else { - if (!handler.hasMessages(HandlerControllerConstants.REALM_CHANGED)) { - messageHandled = handler.sendEmptyMessage(HandlerControllerConstants.REALM_CHANGED); - } - } - if (!messageHandled) { - RealmLog.w("Cannot update Looper threads when the Looper has quit. Use realm.setAutoRefresh(false) " + - "to prevent this."); - } - } + // Sometimes we don't want to notify the local thread about commits, e.g. creating a completely new Realm + // file will make a commit in order to create the schema. Users should not be notified about that. + if (notifyLocalThread) { + sharedRealm.realmNotifier.notifyCommitByLocalThread(); } } @@ -522,9 +438,6 @@ void doClose() { sharedRealm.close(); sharedRealm = null; } - if (handler != null) { - removeHandler(); - } } /** @@ -561,11 +474,6 @@ void setVersion(long version) { metadataTable.setLong(0, 0, version); } - // Return all handlers registered for this Realm - static Map getHandlers() { - return handlers; - } - /** * Returns the schema for this Realm. * @@ -767,6 +675,11 @@ public void onResult(int count) { } } + // Return true if this Realm can receive notifications. + boolean hasValidNotifier() { + return sharedRealm.realmNotifier != null && sharedRealm.realmNotifier.isValid(); + } + @Override protected void finalize() throws Throwable { if (sharedRealm != null && !sharedRealm.isClosed()) { diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java index 58733f306e..911b46fc92 100644 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java @@ -274,7 +274,8 @@ private void updateAsyncEmptyRealmObject() { .addObject(next.getKey(), next.getValue().handoverQueryPointer(), next.getValue().getArgument()) - .sendToHandler(realm.handler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT) + .sendToNotifier(realm.sharedRealm.realmNotifier, + QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_OBJECT) .build()); } else { @@ -415,7 +416,8 @@ private void updateAsyncQueries() { } if (realmResultsQueryStep != null) { QueryUpdateTask queryUpdateTask = realmResultsQueryStep - .sendToHandler(realm.handler, HandlerControllerConstants.COMPLETED_UPDATE_ASYNC_QUERIES) + .sendToNotifier(realm.sharedRealm.realmNotifier, + QueryUpdateTask.NotifyEvent.COMPLETE_UPDATE_ASYNC_QUERIES) .build(); updateAsyncQueriesTask = Realm.asyncTaskExecutor.submitQueryUpdate(queryUpdateTask); } @@ -498,7 +500,8 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) { .add(weakRealmResults, query.handoverQueryPointer(), query.getArgument()) - .sendToHandler(realm.handler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS) + .sendToNotifier(realm.sharedRealm.realmNotifier, + QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS) .build(); Realm.asyncTaskExecutor.submitQueryUpdate(queryUpdateTask); @@ -640,7 +643,8 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) { .addObject(realmObjectWeakReference, realmQuery.handoverQueryPointer(), realmQuery.getArgument()) - .sendToHandler(realm.handler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT) + .sendToNotifier(realm.sharedRealm.realmNotifier, + QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_OBJECT) .build(); Realm.asyncTaskExecutor.submitQueryUpdate(queryUpdateTask); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 806995544e..570943cb3a 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -286,7 +286,7 @@ private static void initializeRealm(Realm realm) { } } finally { if (commitNeeded) { - realm.commitTransaction(false, true); + realm.commitTransaction(false); } else { realm.cancelTransaction(); } @@ -1160,7 +1160,7 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, fin // If the user provided a Callback then we make sure, the current Realm has a Handler // we can use to deliver the result - if ((onSuccess != null || onError != null) && handler == null) { + if ((onSuccess != null || onError != null) && !hasValidNotifier()) { throw new IllegalStateException("Your Realm is opened from a thread without a Looper" + " and you provided a callback, we need a Handler to invoke your callback"); } @@ -1184,10 +1184,10 @@ public void run() { transaction.execute(bgRealm); if (!Thread.currentThread().isInterrupted()) { - bgRealm.commitAsyncTransaction(); - // The bgRealm needs to be closed before posting the REALM_CHANGED event to the caller's handler - // to avoid currency problems. This is currently guaranteed by posting - // handleAsyncTransactionCompleted below. + // No need to send change notification to the work thread. + bgRealm.commitTransaction(false); + // The bgRealm needs to be closed before post event to caller's handler to avoid concurrency + // problem. This is currently guaranteed by posting handleAsyncTransactionCompleted below. bgRealm.close(); transactionCommitted = true; } @@ -1205,13 +1205,11 @@ public void run() { final Throwable backgroundException = exception[0]; // Send response as the final step to ensure the bg thread quit before others get the response! - if (handler != null - && !Thread.currentThread().isInterrupted() - && handler.getLooper().getThread().isAlive()) { + if (hasValidNotifier() && !Thread.currentThread().isInterrupted()) { if (transactionCommitted) { // This will be treated like a special REALM_CHANGED event - handler.post(new Runnable() { + sharedRealm.realmNotifier.post(new Runnable() { @Override public void run() { handlerController.handleAsyncTransactionCompleted(onSuccess != null ? new Runnable() { @@ -1227,14 +1225,14 @@ public void run() { // Send errors directly to the looper, so they don't get intercepted by the HandlerController. if (backgroundException != null) { if (onError != null) { - handler.post(new Runnable() { + sharedRealm.realmNotifier.post(new Runnable() { @Override public void run() { onError.onError(backgroundException); } }); } else { - handler.post(new Runnable() { + sharedRealm.realmNotifier.post(new Runnable() { @Override public void run() { if (backgroundException instanceof RuntimeException) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 1e4019113b..b6d6b0d546 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -17,8 +17,6 @@ package io.realm; -import android.os.Handler; - import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Date; @@ -28,8 +26,8 @@ import java.util.concurrent.Future; import io.realm.annotations.Required; -import io.realm.internal.HandlerControllerConstants; import io.realm.internal.LinkView; +import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -1362,7 +1360,7 @@ public RealmResults distinct(String fieldName) { public RealmResults distinctAsync(String fieldName) { checkQueryIsNotReused(); final long columnIndex = getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); - final WeakReference weakHandler = getWeakReferenceHandler(); + final WeakReference weakNotifier = getWeakReferenceNotifier(); // handover the query (to be used by a worker thread) final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); @@ -1404,15 +1402,14 @@ public Long call() throws Exception { QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result); + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS, result); return handoverTableViewPointer; } catch (Throwable e) { RealmLog.e(e.getMessage(), e); - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); - + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); } finally { if (sharedRealm != null && !sharedRealm.isClosed()) { sharedRealm.close(); @@ -1667,7 +1664,7 @@ public RealmResults findAll() { */ public RealmResults findAllAsync() { checkQueryIsNotReused(); - final WeakReference weakHandler = getWeakReferenceHandler(); + final WeakReference weakNotifier = getWeakReferenceNotifier(); // handover the query (to be used by a worker thread) final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); @@ -1709,8 +1706,8 @@ public Long call() throws Exception { QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result); + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS, result); return handoverTableViewPointer; @@ -1721,8 +1718,8 @@ public Long call() throws Exception { } catch (Throwable e) { RealmLog.e(e.getMessage(), e); - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); } finally { if (sharedRealm != null && !sharedRealm.isClosed()) { sharedRealm.close(); @@ -1787,7 +1784,7 @@ public RealmResults findAllSortedAsync(final String fieldName, final Sort sor argumentsHolder.sortOrder = sortOrder; argumentsHolder.columnIndex = columnIndex; - final WeakReference weakHandler = getWeakReferenceHandler(); + final WeakReference weakNotifier = getWeakReferenceNotifier(); // handover the query (to be used by a worker thread) final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); @@ -1824,8 +1821,8 @@ public Long call() throws Exception { QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result); + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS, result); return handoverTableViewPointer; } catch (BadVersionException e) { @@ -1835,8 +1832,8 @@ public Long call() throws Exception { } catch (Throwable e) { RealmLog.e(e.getMessage(), e); - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); } finally { if (sharedRealm!= null && !sharedRealm.isClosed()) { @@ -1947,7 +1944,7 @@ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sort return findAllSortedAsync(fieldNames[0], sortOrders[0]); } else { - final WeakReference weakHandler = getWeakReferenceHandler(); + final WeakReference weakNotifier = getWeakReferenceNotifier(); // Handover the query (to be used by a worker thread) final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); @@ -1994,8 +1991,8 @@ public Long call() throws Exception { QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result); + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS, result); return handoverTableViewPointer; } catch (BadVersionException e) { @@ -2005,9 +2002,8 @@ public Long call() throws Exception { } catch (Throwable e) { RealmLog.e(e.getMessage(), e); - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); - + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); } finally { if (sharedRealm != null && !sharedRealm.isClosed()) { sharedRealm.close(); @@ -2090,7 +2086,7 @@ public E findFirst() { */ public E findFirstAsync() { checkQueryIsNotReused(); - final WeakReference weakHandler = getWeakReferenceHandler(); + final WeakReference weakNotifier = getWeakReferenceNotifier(); // handover the query (to be used by a worker thread) final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); @@ -2133,17 +2129,16 @@ public Long call() throws Exception { QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmObjectResponse(); result.updatedRow.put(realmObjectWeakReference, handoverRowPointer); result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT, result); + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_OBJECT, result); return handoverRowPointer; } catch (Throwable e) { RealmLog.e(e.getMessage(), e); // handler can't throw a checked exception need to wrap it into unchecked Exception - closeSharedRealmAndSendMessageToHandler(sharedRealm, - weakHandler, HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)); - + closeSharedRealmAndSendEventToNotifier(sharedRealm, + weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); } finally { if (sharedRealm != null && !sharedRealm.isClosed()) { sharedRealm.close(); @@ -2175,24 +2170,39 @@ private void checkSortParameters(String fieldNames[], final Sort[] sortOrders) { } } - private WeakReference getWeakReferenceHandler() { - if (realm.handler == null) { + private WeakReference getWeakReferenceNotifier() { + if (realm.sharedRealm.realmNotifier == null || !realm.sharedRealm.realmNotifier.isValid()) { throw new IllegalStateException("Your Realm is opened from a thread without a Looper." + " Async queries need a Handler to send results of your query"); } - return new WeakReference(realm.handler); // use caller Realm's Looper + return new WeakReference(realm.sharedRealm.realmNotifier); // use caller Realm's Looper } // The shared group needs to be closed before sending the message to other threads to avoid timing problems. // eg.: The other thread wants to delete Realm when getting notified. - private void closeSharedRealmAndSendMessageToHandler(SharedRealm sharedRealm, WeakReference weakHandler, - int what, Object obj) { + private void closeSharedRealmAndSendEventToNotifier(SharedRealm sharedRealm, + WeakReference weakNotifier, + QueryUpdateTask.NotifyEvent event, Object obj) { if (sharedRealm != null) { sharedRealm.close(); } - Handler handler = weakHandler.get(); - if (handler != null && handler.getLooper().getThread().isAlive()) { - handler.obtainMessage(what, obj).sendToTarget(); + + RealmNotifier notifier = weakNotifier.get(); + if (notifier!= null) { + switch (event) { + case COMPLETE_ASYNC_RESULTS: + notifier.completeAsyncResults((QueryUpdateTask.Result)obj); + break; + case COMPLETE_ASYNC_OBJECT: + notifier.completeAsyncObject((QueryUpdateTask.Result)obj); + break; + case THROW_BACKGROUND_EXCEPTION: + notifier.throwBackgroundException((Throwable)obj); + break; + default: + // Should not get here. + throw new IllegalStateException(String.format("%s is not handled here.", event)); + } } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java new file mode 100644 index 0000000000..d3464b2027 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import io.realm.internal.async.QueryUpdateTask; + +/** + * This interface needs to be implemented by Java and pass to Realm Object Store in order to get notifications when + * other thread/process changes the Realm file. + */ +@Keep +public interface RealmNotifier { + /** + * This is called from Java when the changes have been made on the same thread. + */ + void notifyCommitByLocalThread(); + + /** + * This is called in Realm Object Store's JavaBindingContext::changes_available. + * This is getting called on the same thread which created this Realm when the same Realm file has been changed by + * other thread. The changes on the same thread should not trigger this call. + */ + @SuppressWarnings("unused") + void notifyCommitByOtherThread(); + + /** + * Post a runnable to be run in the next event loop on the thread which creates the corresponding Realm. + * + * @param runnable to be posted. + */ + void post(Runnable runnable); + + /** + * Is the current notifier valid? eg. Notifier created on non-looper thread cannot be notified. + * + * @return {@code true} if the thread which owns this notifier can be notified. Otherwise {@code false} + */ + boolean isValid(); + + /** + * Called when close SharedRealm to clean up any event left in to queue. + */ + void close(); + + // FIXME: These are for decoupling handler from async query. Async query needs refactor to either adapt the OS or + // abstract the logic from Android handlers. + void completeAsyncResults(QueryUpdateTask.Result result); + void completeAsyncObject(QueryUpdateTask.Result result); + void throwBackgroundException(Throwable throwable); + void completeUpdateAsyncQueries(QueryUpdateTask.Result result); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 4f0c9755b7..8e225ecff1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -68,6 +68,9 @@ public enum SchemaMode { } } + // JNI will only hold a weak global ref to this. + public final RealmNotifier realmNotifier; + public static class VersionID implements Comparable { final long version; final long index; @@ -125,13 +128,18 @@ public int hashCode() { private RealmConfiguration configuration; final Context context; - private SharedRealm(long nativePtr, RealmConfiguration configuration) { + private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotifier notifier) { this.nativePtr = nativePtr; this.configuration = configuration; + this.realmNotifier = notifier; context = new Context(); } public static SharedRealm getInstance(RealmConfiguration config) { + return getInstance(config, null); + } + + public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier realmNotifier) { long nativeConfigPtr = nativeCreateConfig( config.getPath(), config.getEncryptionKey(), @@ -139,9 +147,9 @@ public static SharedRealm getInstance(RealmConfiguration config) { config.getDurability() == Durability.MEM_ONLY, false, false, - false); + true); try { - return new SharedRealm(nativeGetSharedRealm(nativeConfigPtr), config); + return new SharedRealm(nativeGetSharedRealm(nativeConfigPtr, realmNotifier), config, realmNotifier); } finally { nativeCloseConfig(nativeConfigPtr); } @@ -250,6 +258,9 @@ public boolean compact() { @Override public void close() { + if (realmNotifier != null) { + realmNotifier.close(); + } synchronized (context) { if (nativePtr != 0) { nativeCloseSharedRealm(nativePtr); @@ -276,7 +287,7 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte boolean cache, boolean disableFormatUpgrade, boolean autoChangeNotification); private static native void nativeCloseConfig(long nativeConfigPtr); - private static native long nativeGetSharedRealm(long nativeConfigPtr); + private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier); private static native void nativeCloseSharedRealm(long nativeSharedRealmPtr); private static native boolean nativeIsClosed(long nativeSharedRealmPtr); private static native void nativeBeginTransaction(long nativeSharedRealmPtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java index f8f00fef87..f106ef3191 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java @@ -16,8 +16,6 @@ package io.realm.internal.async; -import android.os.Handler; - import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.IdentityHashMap; @@ -26,7 +24,7 @@ import io.realm.RealmConfiguration; import io.realm.RealmModel; import io.realm.RealmResults; -import io.realm.internal.HandlerControllerConstants; +import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.SharedRealm; import io.realm.internal.Table; @@ -37,6 +35,14 @@ * Manages the update of async queries. */ public class QueryUpdateTask implements Runnable { + + public enum NotifyEvent { + COMPLETE_ASYNC_RESULTS, + COMPLETE_ASYNC_OBJECT, + COMPLETE_UPDATE_ASYNC_QUERIES, + THROW_BACKGROUND_EXCEPTION, + } + // true if updating RealmResults, false if updating RealmObject, can't mix both // the builder pattern will prevent this. private final static int MODE_UPDATE_REALM_RESULTS = 0; @@ -46,21 +52,21 @@ public class QueryUpdateTask implements Runnable { private RealmConfiguration realmConfiguration; private List realmResultsEntries; private Builder.QueryEntry realmObjectEntry; - private WeakReference callerHandler; - private int message; + private WeakReference callerNotifier; + private NotifyEvent event; private QueryUpdateTask (int mode, RealmConfiguration realmConfiguration, List listOfRealmResults, Builder.QueryEntry realmObject, - WeakReference handler, - int message) { + WeakReference notifier, + NotifyEvent event) { this.updateMode = mode; this.realmConfiguration = realmConfiguration; this.realmResultsEntries = listOfRealmResults; this.realmObjectEntry = realmObject; - this.callerHandler = handler; - this.message = message; + this.callerNotifier = notifier; + this.event = event; } public static Builder.RealmConfigurationStep newBuilder() { @@ -93,9 +99,21 @@ public void run() { result.versionID = sharedRealm.getVersionID(); } - Handler handler = callerHandler.get(); - if (updateSuccessful && !isTaskCancelled() && isAliveHandler(handler)) { - handler.obtainMessage(message, result).sendToTarget(); + RealmNotifier notifier = callerNotifier.get(); + if (updateSuccessful && !isTaskCancelled() && notifier != null) { + switch (event) { + case COMPLETE_ASYNC_RESULTS: + notifier.completeAsyncResults(result); + break; + case COMPLETE_ASYNC_OBJECT: + notifier.completeAsyncObject(result); + break; + case COMPLETE_UPDATE_ASYNC_QUERIES: + notifier.completeUpdateAsyncQueries(result); + break; + default: + throw new IllegalStateException(String.format("%s is not handled here.", event)); + } } } catch (BadVersionException e) { @@ -105,9 +123,9 @@ public void run() { } catch (Throwable e) { RealmLog.e(e.getMessage(), e); - Handler handler = callerHandler.get(); - if (handler != null && handler.getLooper().getThread().isAlive()) { - handler.obtainMessage(HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(e)).sendToTarget(); + RealmNotifier notifier = callerNotifier.get(); + if (notifier!= null) { + notifier.throwBackgroundException(e); } } finally { @@ -205,10 +223,6 @@ private boolean isTaskCancelled() { return Thread.currentThread().isInterrupted(); } - private boolean isAliveHandler(Handler handler) { - return handler != null && handler.getLooper().getThread().isAlive(); - } - // result of the async query public static class Result { public IdentityHashMap>, Long> updatedTableViews; @@ -241,13 +255,13 @@ private static class AlignedQueriesParameters { .realmConfiguration(null, null) .add(null, 0, null) .add(null, 0, null) - .sendToHandler(null, 0) + .sendToNotifier(null, 0) .build(); QueryUpdateTask task2 = QueryUpdateTask.newBuilder() .realmConfiguration(null, null) .addObject(null, 0, null) - .sendToHandler(null, 0) + .sendToNotifier(null, 0) .build(); */ public static class Builder { @@ -268,11 +282,11 @@ public interface RealmResultsQueryStep { RealmResultsQueryStep add(WeakReference> weakReference, long handoverQueryPointer, ArgumentsHolder queryArguments); - BuilderStep sendToHandler(Handler handler, int message); + BuilderStep sendToNotifier(RealmNotifier notifier, NotifyEvent event); } public interface HandlerStep { - BuilderStep sendToHandler (Handler handler, int message); + BuilderStep sendToNotifier(RealmNotifier notifier, NotifyEvent event); } public interface BuilderStep { @@ -283,8 +297,8 @@ private static class Steps implements RealmConfigurationStep, UpdateQueryStep, R private RealmConfiguration realmConfiguration; private List realmResultsEntries; private QueryEntry realmObjectEntry; - private WeakReference callerHandler; - private int message; + private WeakReference callerNotifier; + private NotifyEvent event; @Override public UpdateQueryStep realmConfiguration(RealmConfiguration realmConfiguration) { @@ -313,9 +327,9 @@ public HandlerStep addObject(WeakReference weakReference, } @Override - public BuilderStep sendToHandler(Handler handler, int message) { - this.callerHandler = new WeakReference(handler); - this.message = message; + public BuilderStep sendToNotifier(RealmNotifier notifier, NotifyEvent event) { + this.callerNotifier = new WeakReference(notifier); + this.event = event; return this; } @@ -326,8 +340,8 @@ public QueryUpdateTask build() { realmConfiguration, realmResultsEntries, realmObjectEntry, - callerHandler, - message); + callerNotifier, + event); } } From 5969277be4e51e3cc061f9727be73c8872787c55 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 1 Sep 2016 10:59:09 +0200 Subject: [PATCH 0019/2110] RealmLog (#3368) Moved RealmLog to the public API. Routes all log events through it, also from native code. --- CHANGELOG.md | 9 +- .../unittesting/ExampleActivityTest.java | 15 +- .../unittesting/ExampleRealmTest.java | 6 +- .../java/io/realm/DynamicRealmTests.java | 2 +- .../java/io/realm/NotificationsTest.java | 8 +- .../java/io/realm/RealmAsyncQueryTests.java | 5 +- .../androidTest/java/io/realm/RealmTests.java | 2 +- .../androidTest/java/io/realm/TestHelper.java | 103 +++--- .../realm-library/src/main/cpp/CMakeLists.txt | 1 + .../main/cpp/io_realm_internal_LinkView.cpp | 34 +- .../cpp/io_realm_internal_SharedRealm.cpp | 58 ++-- .../src/main/cpp/io_realm_internal_Table.cpp | 8 +- .../main/cpp/io_realm_internal_TableQuery.cpp | 30 +- .../main/cpp/io_realm_internal_TableView.cpp | 6 +- .../main/cpp/io_realm_internal_TestUtil.cpp | 2 +- .../cpp/io_realm_internal_UncheckedRow.cpp | 66 ++-- .../src/main/cpp/io_realm_internal_Util.cpp | 30 +- realm/realm-library/src/main/cpp/util.cpp | 6 +- realm/realm-library/src/main/cpp/util.hpp | 90 +++--- .../main/java/io/realm/AndroidNotifier.java | 4 +- .../src/main/java/io/realm/BaseRealm.java | 18 +- .../src/main/java/io/realm/DynamicRealm.java | 4 +- .../main/java/io/realm/HandlerController.java | 52 +-- .../src/main/java/io/realm/ProxyState.java | 5 +- .../src/main/java/io/realm/Realm.java | 6 +- .../src/main/java/io/realm/RealmCache.java | 4 +- .../src/main/java/io/realm/RealmQuery.java | 18 +- .../src/main/java/io/realm/RealmResults.java | 4 +- .../java/io/realm/internal/TableView.java | 3 - .../realm/internal/android/AndroidLogger.java | 141 -------- .../internal/android/DebugAndroidLogger.java | 29 -- .../android/ReleaseAndroidLogger.java | 29 -- .../realm/internal/async/QueryUpdateTask.java | 6 +- .../java/io/realm/internal/log/Logger.java | 33 -- .../java/io/realm/internal/log/RealmLog.java | 123 ------- .../main/java/io/realm/log/AndroidLogger.java | 150 +++++++++ .../src/main/java/io/realm/log/LogLevel.java | 71 +++++ .../src/main/java/io/realm/log/Logger.java | 90 ++++++ .../src/main/java/io/realm/log/RealmLog.java | 301 ++++++++++++++++++ 39 files changed, 936 insertions(+), 636 deletions(-) delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/android/AndroidLogger.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/android/DebugAndroidLogger.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/android/ReleaseAndroidLogger.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/log/Logger.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/log/RealmLog.java create mode 100644 realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java create mode 100644 realm/realm-library/src/main/java/io/realm/log/LogLevel.java create mode 100644 realm/realm-library/src/main/java/io/realm/log/Logger.java create mode 100644 realm/realm-library/src/main/java/io/realm/log/RealmLog.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 251aeffd91..79f359596d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,16 +14,17 @@ * Added `realmObject.isManaged()`, `RealmObject.isManaged(obj)` and `RealmCollection.isManaged()` (#3101). * Added `RealmConfiguration.Builder.directory(File)`. - -### Internal - -* Moved JNI build to CMake. +* `RealmLog` has been moved to the public API. It is now possible to control which events Realm emit to Logcat. See the `RealmLog` class for more details. ### Bug fixes * Fixed a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11 (#3356). * Fixed a potential crash when there were lots of async queries waiting in the queue. +### Internal + +* Moved JNI build to CMake. + ## 1.2.0 ### Bug fixes diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java index 4e23a70df5..c4b4d8403c 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java @@ -23,8 +23,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; +import org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricGradleTestRunner; @@ -41,6 +44,9 @@ import io.realm.RealmResults; import io.realm.examples.unittesting.model.Person; import io.realm.internal.RealmCore; +import io.realm.internal.Util; +import io.realm.log.Logger; +import io.realm.log.RealmLog; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -60,7 +66,8 @@ @RunWith(RobolectricGradleTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) -@PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class}) +@SuppressStaticInitializationFor("io.realm.internal.Util") +@PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class}) public class ExampleActivityTest { // Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock @@ -74,10 +81,11 @@ public class ExampleActivityTest { @Before public void setup() throws Exception { - // Setup Realm to be mocked + // Setup Realm to be mocked. The order of these matters + mockStatic(RealmCore.class); + mockStatic(RealmLog.class); mockStatic(Realm.class); mockStatic(RealmConfiguration.class); - mockStatic(RealmCore.class); // Create the mock final Realm mockRealm = mock(Realm.class); @@ -89,6 +97,7 @@ public void setup() throws Exception { doNothing().when(RealmCore.class); RealmCore.loadLibrary(any(Context.class)); + // TODO: Mock the RealmConfiguration's constructor. If the RealmConfiguration.Builder.build can be mocked, this // is not necessary anymore. whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig); diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java index e7be410e33..1c12d4114d 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java @@ -24,6 +24,7 @@ import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.annotation.Config; @@ -32,6 +33,7 @@ import io.realm.examples.unittesting.model.Dog; import io.realm.examples.unittesting.repository.DogRepository; import io.realm.examples.unittesting.repository.DogRepositoryImpl; +import io.realm.log.RealmLog; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -45,7 +47,8 @@ @RunWith(RobolectricGradleTestRunner.class) @Config(constants = BuildConfig.class, sdk = 19) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) -@PrepareForTest({Realm.class}) +@SuppressStaticInitializationFor("io.realm.internal.Util") +@PrepareForTest({Realm.class, RealmLog.class}) public class ExampleRealmTest { // Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock @@ -55,6 +58,7 @@ public class ExampleRealmTest { @Before public void setup() { + mockStatic(RealmLog.class); mockStatic(Realm.class); Realm mockRealm = PowerMockito.mock(Realm.class); diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 52afe5073c..70a8ff2494 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -41,7 +41,7 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.internal.HandlerControllerConstants; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index b7268783cc..ac29db2afc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -33,9 +33,7 @@ import org.junit.runner.RunWith; import java.lang.ref.WeakReference; -import java.util.Map; import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -48,8 +46,8 @@ import io.realm.entities.AllTypes; import io.realm.entities.Dog; -import io.realm.internal.log.Logger; -import io.realm.internal.log.RealmLog; +import io.realm.log.Logger; +import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -1179,7 +1177,7 @@ public void warnIfMixingSyncWritesAndAsyncQueries() { final AtomicBoolean warningLogged = new AtomicBoolean(false); final TestHelper.TestLogger testLogger = new TestHelper.TestLogger() { @Override - public void w(String message) { + public void warn(Throwable t, String message, Object... args) { assertTrue(message.contains("Mixing asynchronous queries with local writes should be avoided.")); warningLogged.set(true); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index a495f87cf1..14f5b6f372 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -48,7 +48,8 @@ import io.realm.internal.HandlerControllerConstants; import io.realm.internal.RealmObjectProxy; import io.realm.internal.async.RealmThreadPoolExecutor; -import io.realm.internal.log.RealmLog; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -174,7 +175,7 @@ public void onChange(Realm object) { @Test @RunTestInLooperThread public void executeTransactionAsync_exceptionHandling() throws Throwable { - final TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); + final TestHelper.TestLogger testLogger = new TestHelper.TestLogger(LogLevel.DEBUG); RealmLog.add(testLogger); final Realm realm = looperThread.realm; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 03e4e1544a..610bc316da 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -88,7 +88,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.internal.SharedRealm; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; import io.realm.objectid.NullPrimaryKey; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 6b34702235..e171990d9c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -57,9 +57,12 @@ import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.async.RealmThreadPoolExecutor; -import io.realm.internal.log.Logger; +import io.realm.log.AndroidLogger; +import io.realm.log.LogLevel; +import io.realm.log.Logger; import io.realm.rule.TestRealmConfigurationFactory; +import static android.R.id.message; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; @@ -172,7 +175,7 @@ public static byte[] getRandomKey(long seed) { * @return Logger implementation */ public static Logger getFailureLogger(final int failureLevel) { - return new Logger() { + return new AndroidLogger(Log.VERBOSE) { private void failIfEqualOrAbove(int logLevel, int failureLevel) { if (logLevel >= failureLevel) { @@ -181,54 +184,35 @@ private void failIfEqualOrAbove(int logLevel, int failureLevel) { } @Override - public void v(String message) { + public void trace(Throwable t, String message, Object... args) { failIfEqualOrAbove(Log.VERBOSE, failureLevel); } @Override - public void v(String message, Throwable t) { - failIfEqualOrAbove(Log.VERBOSE, failureLevel); - } - - @Override - public void d(String message) { - failIfEqualOrAbove(Log.DEBUG, failureLevel); - } - - @Override - public void d(String message, Throwable t) { + public void debug(Throwable t, String message, Object... args) { failIfEqualOrAbove(Log.DEBUG, failureLevel); } @Override - public void i(String message) { + public void info(Throwable t, String message, Object... args) { failIfEqualOrAbove(Log.INFO, failureLevel); } @Override - public void i(String message, Throwable t) { - failIfEqualOrAbove(Log.INFO, failureLevel); - } - - @Override - public void w(String message) { - failIfEqualOrAbove(Log.WARN, failureLevel); - } - - @Override - public void w(String message, Throwable t) { + public void warn(Throwable t, String message, Object... args) { failIfEqualOrAbove(Log.WARN, failureLevel); } @Override - public void e(String message) { + public void error(Throwable t, String message, Object... args) { failIfEqualOrAbove(Log.ERROR, failureLevel); } @Override - public void e(String message, Throwable t) { + public void fatal(Throwable t, String message, Object... args) { failIfEqualOrAbove(Log.ERROR, failureLevel); } + }; } @@ -246,62 +230,67 @@ public static String getRandomString(int length) { */ public static class TestLogger implements Logger { + private final int minimumLevel; public String message; public Throwable throwable; - @Override - public void v(String message) { - this.message = message; - } - - @Override - public void v(String message, Throwable t) { - this.message = message; - this.throwable = t; + public TestLogger() { + this(LogLevel.DEBUG); } - @Override - public void d(String message) { - this.message = message; + public TestLogger(int minimumLevel) { + this.minimumLevel = minimumLevel; } @Override - public void d(String message, Throwable t) { - this.message = message; - this.throwable = t; + public int getMinimumNativeDebugLevel() { + return minimumLevel; } @Override - public void i(String message) { - this.message = message; + public void trace(Throwable t, String message, Object... args) { + if (minimumLevel <= LogLevel.TRACE) { + this.message = (message != null) ? String.format(message, args) : null; + this.throwable = t; + } } @Override - public void i(String message, Throwable t) { - this.message = message; - this.throwable = t; + public void debug(Throwable t, String message, Object... args) { + if (minimumLevel <= LogLevel.DEBUG) { + this.message = (message != null) ? String.format(message, args) : null; + this.throwable = t; + } } @Override - public void w(String message) { - this.message = message; + public void info(Throwable t, String message, Object... args) { + if (minimumLevel <= LogLevel.INFO) { + this.message = (message != null) ? String.format(message, args) : null; + this.throwable = t; + } } @Override - public void w(String message, Throwable t) { - this.message = message; + public void warn(Throwable t, String message, Object... args) { + this.message = (message != null) ? String.format(message, args) : null; this.throwable = t; } @Override - public void e(String message) { - this.message = message; + public void error(Throwable t, String message, Object... args) { + if (minimumLevel <= LogLevel.ERROR) { + this.message = (message != null) ? String.format(message, args) : null; + this.throwable = t; + } } @Override - public void e(String message, Throwable t) { - this.message = message; - this.throwable = t; + public void fatal(Throwable t, String message, Object... args) { + if (minimumLevel <= LogLevel.FATAL) { + this.message = (message != null) ? String.format(message, args) : null; + this.throwable = t; + } } } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index b4f9210420..e1e1f1075a 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -27,6 +27,7 @@ create_javah(TARGET jni_headers CLASSES io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil + io.realm.log.LogLevel CLASSPATH ${classes_PATH} OUTPUT_DIR ${CMAKE_SOURCE_DIR}/jni_include diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp index a2aaee63f6..94481b65ef 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp @@ -29,7 +29,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeClose JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetRow (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return -1; @@ -46,7 +46,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetRow JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetRowIndex (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return -1; @@ -62,7 +62,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetRowIndex JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeAdd (JNIEnv* env, jclass, jlong nativeLinkViewPtr, jlong rowIndex) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); try { LinkViewRef lvr = *lv; @@ -74,7 +74,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeAdd JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeInsert (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos, jlong rowIndex) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); try { LinkViewRef lvr = *lv; @@ -86,7 +86,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeInsert JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeSet (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos, jlong rowIndex) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return; @@ -101,7 +101,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeSet JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeMove (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong old_pos, jlong new_pos) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -120,7 +120,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeMove JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemove (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return; @@ -135,7 +135,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemove JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeClear (JNIEnv* env, jclass, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -148,7 +148,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeSize (JNIEnv* env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -161,7 +161,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeSize JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsEmpty (JNIEnv* env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -173,7 +173,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsEmpty JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeWhere (JNIEnv *env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -186,7 +186,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeWhere JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsAttached (JNIEnv *env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -198,7 +198,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsAttached JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeFind (JNIEnv *env, jobject, jlong nativeLinkViewPtr, jlong targetRowIndex) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -214,7 +214,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeFind JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveAllTargetRows (JNIEnv *env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) try { LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -223,9 +223,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveAllTargetRows } JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable - (JNIEnv*, jobject, jlong nativeLinkViewPtr) + (JNIEnv* env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -238,7 +238,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveTargetRow (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) { - TR_ENTER_PTR(nativeLinkViewPtr) + TR_ENTER_PTR(env, nativeLinkViewPtr) LinkViewRef* lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 97789266b5..6a753754aa 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -25,7 +25,7 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri jbyte schema_mode, jboolean in_memory, jboolean cache, jboolean disable_format_upgrade, jboolean auto_change_notification) { - TR_ENTER() + TR_ENTER(env) try { JStringAccessor path(env, realm_path); // throws @@ -45,9 +45,9 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri } JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv *, jclass, jlong config_ptr) +Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv* env, jclass, jlong config_ptr) { - TR_ENTER_PTR(config_ptr) + TR_ENTER_PTR(env, config_ptr) auto config = reinterpret_cast(config_ptr); delete config; @@ -56,7 +56,7 @@ Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv *, jclass, jlong con JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr, jobject notifier) { - TR_ENTER_PTR(config_ptr) + TR_ENTER_PTR(env, config_ptr) auto config = reinterpret_cast(config_ptr); try { @@ -70,9 +70,9 @@ Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlo } JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv *, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto ptr = reinterpret_cast(shared_realm_ptr); delete ptr; @@ -81,7 +81,7 @@ Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv *, jclass, jlon JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -92,7 +92,7 @@ Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv *env, jclass, j JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -103,7 +103,7 @@ Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv *env, jclass, JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -113,9 +113,9 @@ Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv *env, jclass, JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv *, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); return static_cast(shared_realm->is_in_transaction()); @@ -124,7 +124,7 @@ Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv *, jclass, jlong JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv *env, jclass , jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -137,7 +137,7 @@ Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv *env, jclass , jlong s JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -151,7 +151,7 @@ Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong s JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -163,7 +163,7 @@ Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv *env, jclass, jlong shar JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRefresh__J(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -175,7 +175,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRefresh__JJJ(JNIEnv *env, jclass, jlong shared_realm_ptr, jlong version, jlong index) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); SharedGroup::VersionID version_id(static_cast(version), @@ -190,7 +190,7 @@ Java_io_realm_internal_SharedRealm_nativeRefresh__JJJ(JNIEnv *env, jclass, jlong JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -215,9 +215,9 @@ Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong } JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv *, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); return static_cast(shared_realm->is_closed()); @@ -227,7 +227,7 @@ Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv *, jclass, jlong shared JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) try { JStringAccessor name(env, table_name); // throws @@ -249,7 +249,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv *env, jclass, jlong shared_realm_ptr, jint index) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -261,7 +261,7 @@ Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv *env, jclass, jlong JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -275,7 +275,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring old_table_name, jstring new_table_name) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -294,7 +294,7 @@ Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv *env, jclass, jlong JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -312,7 +312,7 @@ Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv *env, jclass, jlong JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) + TR_ENTER_PTR(env, shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -326,7 +326,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring path, jbyteArray key) { - TR_ENTER_PTR(shared_realm_ptr); + TR_ENTER_PTR(env, shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -339,7 +339,7 @@ Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv *env, jclass, jlong sh JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr); + TR_ENTER_PTR(env, shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -353,7 +353,7 @@ Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv *env, jclass, jlon JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr); + TR_ENTER_PTR(env, shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -365,7 +365,7 @@ Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr); + TR_ENTER_PTR(env, shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 272dd33ec8..94953a1346 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1356,15 +1356,15 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid( } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClose( - JNIEnv*, jclass, jlong nativeTablePtr) + JNIEnv* env, jclass, jlong nativeTablePtr) { - TR_ENTER_PTR(nativeTablePtr) + TR_ENTER_PTR(env, nativeTablePtr) LangBindHelper::unbind_table_ptr(TBL(nativeTablePtr)); } JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_createNative(JNIEnv *env, jobject) { - TR_ENTER() + TR_ENTER(env) try { return reinterpret_cast(LangBindHelper::new_table()); } CATCH_STD() @@ -1532,7 +1532,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTable } JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema - (JNIEnv *, jobject, jlong thisTablePtr, jlong otherTablePtr) + (JNIEnv*, jobject, jlong thisTablePtr, jlong otherTablePtr) { return *TBL(thisTablePtr)->get_descriptor() == *TBL(otherTablePtr)->get_descriptor(); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 0100902978..4d8859e62f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -47,8 +47,8 @@ const char* ERR_IMPORT_CLOSED_REALM = "Can not import results from a closed Real const char* ERR_SORT_NOT_SUPPORTED = "Sort is not supported on binary data, object references and RealmList"; //------------------------------------------------------- -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeClose(JNIEnv *, jclass, jlong nativeQueryPtr) { - TR_ENTER_PTR(nativeQueryPtr) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeClose(JNIEnv* env, jclass, jlong nativeQueryPtr) { + TR_ENTER_PTR(env, nativeQueryPtr) delete Q(nativeQueryPtr); } @@ -88,7 +88,7 @@ static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& indicesArray static jlong findAllWithHandover(JNIEnv* env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong start, jlong end, jlong limit) { - TR_ENTER() + TR_ENTER(env) TableRef table = query.get()->get_table(); if (!QUERY_VALID(env, query.get()) || !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) { @@ -1100,7 +1100,7 @@ static std::unique_ptr handoverQueryToWorker(jlong bgSharedRealmPtr, jlon JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindWithHandover( JNIEnv* env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong fromTableRow) { - TR_ENTER() + TR_ENTER(env) try { std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, false); // throws TableRef table = query->get_table(); @@ -1136,7 +1136,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindWithHandover JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll( JNIEnv* env, jobject, jlong nativeQueryPtr, jlong start, jlong end, jlong limit) { - TR_ENTER() + TR_ENTER(env) Query* query = Q(nativeQueryPtr); TableRef table = query->get_table(); if (!QUERY_VALID(env, query) || @@ -1153,7 +1153,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll( JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllWithHandover (JNIEnv* env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit) { - TR_ENTER() + TR_ENTER(env) try { std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws return findAllWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit); @@ -1174,7 +1174,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate jobjectArray multi_sorted_indices_matrix, jobjectArray multi_sorted_order_matrix) { - TR_ENTER() + TR_ENTER(env) try { JniLongArray handover_queries_pointer_array(env, handover_queries_array); @@ -1290,7 +1290,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetDistinctViewWithHandover (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong columnIndex) { - TR_ENTER() + TR_ENTER(env) try { std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws return getDistinctViewWithHandover(env, bgSharedRealmPtr, std::move(query), columnIndex); @@ -1301,7 +1301,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetDistinctViewW JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllSortedWithHandover (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlong columnIndex, jboolean ascending) { - TR_ENTER() + TR_ENTER(env) try { std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws return findAllSortedWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit, columnIndex, ascending); @@ -1312,7 +1312,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllSortedWit JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllMultiSortedWithHandover (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlongArray columnIndices, jbooleanArray ascending) { - TR_ENTER() + TR_ENTER(env) try { // import the handover query pointer using the background SharedRealm std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws @@ -1712,7 +1712,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull( JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverTableViewIntoSharedGroup (JNIEnv *env, jobject, jlong handoverPtr, jlong callerSharedGrpPtr) { - TR_ENTER_PTR(handoverPtr) + TR_ENTER_PTR(env, handoverPtr) SharedGroup::Handover *handoverTableViewPtr = HO(TableView, handoverPtr); std::unique_ptr> handoverTableView(handoverTableViewPtr); try { @@ -1732,7 +1732,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverTa JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRowIntoSharedGroup (JNIEnv *env, jclass, jlong handoverPtr, jlong callerSharedGrpPtr) { - TR_ENTER_PTR(handoverPtr) + TR_ENTER_PTR(env, handoverPtr) SharedGroup::Handover *handoverRowPtr = HO(Row, handoverPtr); std::unique_ptr> handoverRow(handoverRowPtr); @@ -1753,7 +1753,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRo JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery (JNIEnv* env, jobject, jlong bgSharedRealmPtr, jlong nativeQueryPtr) { - TR_ENTER_PTR(nativeQueryPtr) + TR_ENTER_PTR(env, nativeQueryPtr) Query* pQuery = Q(nativeQueryPtr); if (!QUERY_VALID(env, pQuery)) return 0; @@ -1768,9 +1768,9 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeCloseQueryHandover - (JNIEnv *, jclass, jlong nativeHandoverQuery) + (JNIEnv* env, jclass, jlong nativeHandoverQuery) { - TR_ENTER_PTR(nativeHandoverQuery) + TR_ENTER_PTR(env, nativeHandoverQuery) delete HO(Query, nativeHandoverQuery); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp index d4837d7465..65866dc126 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp @@ -616,9 +616,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllString( !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_String)) return 0; JStringAccessor value2(env, value); // throws - TR("nativeFindAllString(col %" PRId64 ", string '%s') ", S64(columnIndex), StringData(value2).data()) TableView* pResultView = new TableView( TV(nativeViewPtr)->find_all_string( S(columnIndex), value2) ); - TR("-- resultview size=%" PRId64 ".", S64(pResultView->size())) return reinterpret_cast(pResultView); } CATCH_STD() return 0; @@ -934,7 +932,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_TableView_nativeToJson( JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeWhere( JNIEnv *env, jobject, jlong nativeViewPtr) { - TR_ENTER_PTR(nativeViewPtr) + TR_ENTER_PTR(env, nativeViewPtr) try { if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) return 0; @@ -964,7 +962,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSyncIfNeeded( JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindBySourceNdx (JNIEnv *env, jobject, jlong nativeViewPtr, jlong sourceIndex) { - TR_ENTER_PTR(nativeViewPtr); + TR_ENTER_PTR(env, nativeViewPtr); try { if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !ROW_INDEX_VALID(env, &(TV(nativeViewPtr)->get_parent()), sourceIndex)) return -1; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp index 674412dca7..a5b34275c4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp @@ -4,7 +4,7 @@ static jstring throwOrGetExpectedMessage(JNIEnv *env, jlong testcase, bool should_throw); JNIEXPORT jlong JNICALL -Java_io_realm_internal_TestUtil_getMaxExceptionNumber(JNIEnv *, jclass) +Java_io_realm_internal_TestUtil_getMaxExceptionNumber(JNIEnv*, jclass) { return ExceptionKindMax; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 908ef25fe4..5f2fc6c1d7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -20,9 +20,9 @@ using namespace realm; JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount - (JNIEnv *, jobject, jlong nativeRowPtr) + (JNIEnv *env, jobject, jlong nativeRowPtr) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW(nativeRowPtr)->is_attached()) return 0; @@ -32,7 +32,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnName (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -45,7 +45,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnNam JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex (JNIEnv* env, jobject, jlong nativeRowPtr, jstring columnName) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW(nativeRowPtr)->is_attached()) return 0; @@ -57,16 +57,16 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex } JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType - (JNIEnv*, jobject, jlong nativeRowPtr, jlong columnIndex) + (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) return static_cast( ROW(nativeRowPtr)->get_column_type( S(columnIndex)) ); // noexcept } JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetIndex (JNIEnv* env, jobject, jlong nativeRowPtr) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -76,7 +76,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetIndex JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLong (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -86,7 +86,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLong JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeGetBoolean (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -96,7 +96,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeGetBoolean JNIEXPORT jfloat JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFloat (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -106,7 +106,7 @@ JNIEXPORT jfloat JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFloat JNIEXPORT jdouble JNICALL Java_io_realm_internal_UncheckedRow_nativeGetDouble (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -116,7 +116,7 @@ JNIEXPORT jdouble JNICALL Java_io_realm_internal_UncheckedRow_nativeGetDouble JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetTimestamp (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -126,7 +126,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetTimestamp JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetString (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -140,7 +140,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetString JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteArray (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -163,7 +163,7 @@ JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteAr JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLink (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -176,7 +176,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLink JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNullLink (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -186,7 +186,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNullLink JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLinkView (JNIEnv* env, jclass, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -197,7 +197,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLinkView JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLong (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jlong value) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -209,7 +209,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLong JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetBoolean (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jboolean value) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -221,7 +221,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetBoolean JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetFloat (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jfloat value) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -233,7 +233,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetFloat JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDouble (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jdouble value) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -245,7 +245,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDouble JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetTimestamp (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jlong value) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -257,7 +257,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetTimestamp JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetString (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jstring value) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -274,7 +274,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetString JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jbyteArray value) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -307,7 +307,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLink (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jlong value) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -319,7 +319,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLink JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -329,16 +329,16 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeClose - (JNIEnv *, jclass, jlong nativeRowPtr) + (JNIEnv* env, jclass, jlong nativeRowPtr) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) delete ROW(nativeRowPtr); } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsAttached - (JNIEnv *, jobject, jlong nativeRowPtr) + (JNIEnv* env, jobject, jlong nativeRowPtr) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) return ROW(nativeRowPtr)->is_attached(); } @@ -350,14 +350,14 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeHasColumn } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull - (JNIEnv *, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { + TR_ENTER_PTR(env, nativeRowPtr) return ROW(nativeRowPtr)->is_null(columnIndex); } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetNull (JNIEnv *env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(nativeRowPtr) + TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; if (!TBL_AND_COL_NULLABLE(env, ROW(nativeRowPtr)->get_table(), columnIndex)) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 88b09a9cb9..2641cc5ca5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -19,9 +19,9 @@ #include #include -#include "util.hpp" +#include "io_realm_log_LogLevel.h" #include "mem_usage.hpp" -#include "io_realm_internal_Util.h" +#include "util.hpp" using std::string; @@ -32,7 +32,13 @@ using std::string; // used by logging int trace_level = 0; -const char* log_tag = "REALM"; +jclass realmlog_class; +jmethodID log_trace; +jmethodID log_debug; +jmethodID log_info; +jmethodID log_warn; +jmethodID log_error; +jmethodID log_fatal; const string TABLE_PREFIX("class_"); @@ -51,6 +57,13 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); java_lang_double = GetClass(env, "java/lang/Double"); java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); + realmlog_class = GetClass(env, "io/realm/log/RealmLog"); + log_trace = env->GetStaticMethodID(realmlog_class, "trace", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_debug = env->GetStaticMethodID(realmlog_class, "debug", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_info = env->GetStaticMethodID(realmlog_class, "info", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_warn = env->GetStaticMethodID(realmlog_class, "warn", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_error = env->GetStaticMethodID(realmlog_class, "error", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_fatal = env->GetStaticMethodID(realmlog_class, "fatal", "(Ljava/lang/String;[Ljava/lang/Object;)V"); } return JNI_VERSION_1_6; @@ -71,6 +84,17 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) JNIEXPORT void JNICALL Java_io_realm_internal_Util_nativeSetDebugLevel(JNIEnv*, jclass, jint level) { + /** + * level should match one of the levels defined in LogLevel.java + * ALL = 1 + * TRACE = 2 + * DEBUG = 3 + * INFO = 4 + * WARN = 5 + * ERROR = 6 + * FATAL = 7 + * OFF = 8 + */ trace_level = level; } diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 1318605529..f1bba74329 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -91,7 +91,7 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla string message; jclass jExceptionClass = NULL; - TR_ERR("jni: ThrowingException %d, %s, %s.", exception, classStr.c_str(), itemStr.c_str()) + TR_ERR(env, "jni: ThrowingException %d, %s, %s.", exception, classStr.c_str(), itemStr.c_str()) switch (exception) { case ClassNotFound: @@ -146,10 +146,10 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla } if (jExceptionClass != NULL) { env->ThrowNew(jExceptionClass, message.c_str()); - TR_ERR("Exception has been throw: %s", message.c_str()) + TR_ERR(env, "Exception has been throw: %s", message.c_str()) } else { - TR_ERR("ERROR: Couldn't throw exception.") + TR_ERR(env, "ERROR: Couldn't throw exception.", NULL) } env->DeleteLocalRef(jExceptionClass); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 043f74469f..ad3de760d1 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -34,7 +34,7 @@ #include #include "io_realm_internal_Util.h" - +#include "io_realm_log_LogLevel.h" #define TRACE 1 // disable for performance #define CHECK_PARAMETERS 1 // Check all parameters in API and throw exceptions in java if invalid @@ -114,33 +114,49 @@ jclass GetClass(JNIEnv* env, const char* classStr); // Debug trace extern int trace_level; -extern const char* log_tag; +extern jclass realmlog_class; +extern jmethodID log_trace; +extern jmethodID log_debug; +extern jmethodID log_info; +extern jmethodID log_warn; +extern jmethodID log_error; +extern jmethodID log_fatal; + + +// Inspired by From http://www.netmite.com/android/mydroid/system/core/liblog/logd_write.c +inline void log_message(JNIEnv *env, jmethodID log_method, const char *msg, ...) +{ + // Check if a exception has already bee cast. In that case trying to log anything will crash. + if (env->ExceptionCheck()) { + return; + } + + va_list ap; + char buf[1024]; // Max logcat line length + va_start(ap, msg); + // Do formatting in C++. I gave up trying to send C++ variadic arguments back as Java var args. + vsnprintf(buf, 1024, msg, ap); + va_end(ap); + + jstring log_message = env->NewStringUTF(buf); + env->CallStaticVoidMethod(realmlog_class, log_method, log_message, NULL); + env->DeleteLocalRef(log_message); +} #if TRACE - #if defined(ANDROID) - #include - #define LOG_DEBUG ANDROID_LOG_DEBUG - #define TR_ENTER() if (trace_level >= 1) { __android_log_print(ANDROID_LOG_DEBUG, log_tag, " --> %s", __FUNCTION__); } else {} - #define TR_ENTER_PTR(ptr) if (trace_level >= 1) { __android_log_print(ANDROID_LOG_DEBUG, log_tag, " --> %s %" PRId64, __FUNCTION__, static_cast(ptr)); } else {} - #define TR(...) if (trace_level >= 2) { __android_log_print(ANDROID_LOG_DEBUG, log_tag, __VA_ARGS__); } else {} - #define TR_ERR(...) if (trace_level >= 0) { __android_log_print(ANDROID_LOG_DEBUG, log_tag, __VA_ARGS__); } else {} - #define TR_LEAVE() if (trace_level >= 3) { __android_log_print(ANDROID_LOG_DEBUG, log_tag, " <-- %s", __FUNCTION__); } else {} - #else // ANDROID - #define TR_ENTER() - #define TR_ENTER_PTR(ptr) - #define TR(...) - #define TR_ERR(...) - #define TR_LEAVE() - #endif + #define TR_ENTER(env) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, " --> %s", __FUNCTION__); } else {} + #define TR_ENTER_PTR(env, ptr) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, " --> %s %" PRId64, __FUNCTION__, static_cast(ptr)); } else {} + #define TR(env, msg, ...) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, msg, __VA_ARGS__)); } else {} + #define TR_ERR(env, msg, ...) if (trace_level <= io_realm_log_LogLevel_ERROR) { log_message(env, log_error, msg, __VA_ARGS__); } else {} + #define TR_LEAVE(env) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, " <-- %s", __FUNCTION__); } else {} #else // TRACE - these macros must be empty - #define TR_ENTER() - #define TR_ENTER_PTR(ptr) - #define TR(...) - #define TR_ERR(...) - #define TR_LEAVE() + #define TR_ENTER(env) + #define TR_ENTER_PTR(env, ptr) + #define TR(env, msg, ...) + #define TR_ERR(env, msg, ...) + #define TR_LEAVE(env) #endif - // Check parameters #define TABLE_VALID(env,ptr) TableIsValid(env, ptr) @@ -166,7 +182,7 @@ extern const char* log_tag; #define TBL_AND_INDEX_AND_TYPE_VALID(env,ptr,col,row,type) TblIndexAndTypeValid(env, ptr, col, row, type) #define TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env,ptr,col,row,type) TblIndexAndTypeInsertValid(env, ptr, col, row, type) -#define ROW_AND_COL_INDEX_AND_TYPE_VALID(env,ptr,col, type) RowColIndexAndTypeValid(env, ptr, col, type) +#define ROW_AND_COL_INDEX_AND_TYPE_VALID(env,ptr,col,type) RowColIndexAndTypeValid(env, ptr, col, type) #define ROW_AND_COL_INDEX_VALID(env,ptr,col) RowColIndexValid(env, ptr, col) #else @@ -212,7 +228,7 @@ inline bool TableIsValid(JNIEnv* env, T* objPtr) } if (!valid) { - TR_ERR("Table %p is no longer attached!", VOID_PTR(objPtr)) + TR_ERR(env, "Table %p is no longer attached!", VOID_PTR(objPtr)) ThrowException(env, IllegalState, "Table is no longer valid to operate on."); } return valid; @@ -222,7 +238,7 @@ inline bool RowIsValid(JNIEnv* env, realm::Row* rowPtr) { bool valid = (rowPtr != NULL && rowPtr->is_attached()); if (!valid) { - TR_ERR("Row %p is no longer attached!", VOID_PTR(rowPtr)) + TR_ERR(env, "Row %p is no longer attached!", VOID_PTR(rowPtr)) ThrowException(env, IllegalState, "Object is no longer valid to operate on. Was it deleted by another thread?"); } return valid; @@ -236,29 +252,29 @@ bool RowIndexesValid(JNIEnv* env, T* pTable, jlong startIndex, jlong endIndex, j if (endIndex == -1) endIndex = maxIndex; if (startIndex < 0) { - TR_ERR("startIndex %" PRId64 " < 0 - invalid!", S64(startIndex)) + TR_ERR(env, "startIndex %" PRId64 " < 0 - invalid!", S64(startIndex)) ThrowException(env, IndexOutOfBounds, "startIndex < 0."); return false; } if (realm::util::int_greater_than(startIndex, maxIndex)) { - TR_ERR("startIndex %" PRId64 " > %" PRId64 " - invalid!", S64(startIndex), S64(maxIndex)) + TR_ERR(env, "startIndex %" PRId64 " > %" PRId64 " - invalid!", S64(startIndex), S64(maxIndex)) ThrowException(env, IndexOutOfBounds, "startIndex > available rows."); return false; } if (realm::util::int_greater_than(endIndex, maxIndex)) { - TR_ERR("endIndex %" PRId64 " > %" PRId64 " - invalid!", S64(endIndex), S64(maxIndex)) + TR_ERR(env, "endIndex %" PRId64 " > %" PRId64 " - invalid!", S64(endIndex), S64(maxIndex)) ThrowException(env, IndexOutOfBounds, "endIndex > available rows."); return false; } if (startIndex > endIndex) { - TR_ERR("startIndex %" PRId64 " > endIndex %" PRId64 " - invalid!", S64(startIndex), S64(endIndex)) + TR_ERR(env, "startIndex %" PRId64 " > endIndex %" PRId64 " - invalid!", S64(startIndex), S64(endIndex)) ThrowException(env, IndexOutOfBounds, "startIndex > endIndex."); return false; } if (range != -1 && range < 0) { - TR_ERR("range %" PRId64 " < 0 - invalid!", S64(range)) + TR_ERR(env, "range %" PRId64 " < 0 - invalid!", S64(range)) ThrowException(env, IndexOutOfBounds, "range < 0."); return false; } @@ -278,7 +294,7 @@ inline bool RowIndexValid(JNIEnv* env, T pTable, jlong rowIndex, bool offset=fal size -= 1; bool rowErr = realm::util::int_greater_than_or_equal(rowIndex, size); if (rowErr) { - TR_ERR("rowIndex %" PRId64 " > %" PRId64 " - invalid!", S64(rowIndex), S64(size)) + TR_ERR(env, "rowIndex %" PRId64 " > %" PRId64 " - invalid!", S64(rowIndex), S64(size)) ThrowException(env, IndexOutOfBounds, "rowIndex > available rows: " + num_to_string(rowIndex) + " > " + num_to_string(size)); @@ -305,7 +321,7 @@ inline bool ColIndexValid(JNIEnv* env, T* pTable, jlong columnIndex) } bool colErr = realm::util::int_greater_than_or_equal(columnIndex, pTable->get_column_count()); if (colErr) { - TR_ERR("columnIndex %" PRId64 " > %" PRId64 " - invalid!", S64(columnIndex), S64(pTable->get_column_count())) + TR_ERR(env, "columnIndex %" PRId64 " > %" PRId64 " - invalid!", S64(columnIndex), S64(pTable->get_column_count())) ThrowException(env, IndexOutOfBounds, "columnIndex > available columns."); } return !colErr; @@ -347,7 +363,7 @@ inline bool TblIndexInsertValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong return false; bool rowErr = realm::util::int_greater_than(rowIndex, pTable->size()+1); if (rowErr) { - TR_ERR("rowIndex %" PRId64 " > %" PRId64 " - invalid!", S64(rowIndex), S64(pTable->size())) + TR_ERR(env, "rowIndex %" PRId64 " > %" PRId64 " - invalid!", S64(rowIndex), S64(pTable->size())) ThrowException(env, IndexOutOfBounds, "rowIndex " + num_to_string(rowIndex) + " > available rows " + num_to_string(pTable->size()) + "."); @@ -361,7 +377,7 @@ inline bool TypeValid(JNIEnv* env, T* pTable, jlong columnIndex, int expectColTy size_t col = static_cast(columnIndex); int colType = pTable->get_column_type(col); if (colType != expectColType) { - TR_ERR("Expected columnType %d, but got %d.", expectColType, pTable->get_column_type(col)) + TR_ERR(env, "Expected columnType %d, but got %d.", expectColType, pTable->get_column_type(col)) ThrowException(env, IllegalArgument, "ColumnType invalid."); return false; } @@ -377,7 +393,7 @@ inline bool TypeIsLinkLike(JNIEnv* env, T* pTable, jlong columnIndex) return true; } - TR_ERR("Expected columnType %d or %d, but got %d", realm::type_Link, realm::type_LinkList, colType) + TR_ERR(env, "Expected columnType %d or %d, but got %d", realm::type_Link, realm::type_LinkList, colType) ThrowException(env, IllegalArgument, "ColumnType invalid: expected type_Link or type_LinkList"); return false; } @@ -400,7 +416,7 @@ inline bool ColIsNullable(JNIEnv* env, T* pTable, jlong columnIndex) return true; } - TR_ERR("Expected nullable column type") + TR_ERR(env, "Expected nullable column type", NULL) ThrowException(env, IllegalArgument, "This field is not nullable."); return false; } diff --git a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java index 3f8d577b6d..b3f881bfa4 100644 --- a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java @@ -23,7 +23,7 @@ import io.realm.internal.HandlerControllerConstants; import io.realm.internal.RealmNotifier; import io.realm.internal.async.QueryUpdateTask; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; /** * Implementation of {@link RealmNotifier} for Android based on {@link Handler}. @@ -91,7 +91,7 @@ public void notifyCommitByOtherThread() { messageHandled = handler.sendEmptyMessage(HandlerControllerConstants.REALM_CHANGED); } if (!messageHandled) { - RealmLog.w("Cannot update Looper threads when the Looper has quit. Use realm.setAutoRefresh(false) " + + RealmLog.warn("Cannot update Looper threads when the Looper has quit. Use realm.setAutoRefresh(false) " + "to prevent this."); } } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 4880ce8217..cdd0c35dd4 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -18,8 +18,7 @@ import android.os.Handler; import android.os.Looper; - -import com.getkeepsafe.relinker.BuildConfig; +import android.util.Log; import java.io.Closeable; import java.io.File; @@ -35,10 +34,9 @@ import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; -import io.realm.internal.android.DebugAndroidLogger; -import io.realm.internal.android.ReleaseAndroidLogger; import io.realm.internal.async.RealmThreadPoolExecutor; -import io.realm.internal.log.RealmLog; +import io.realm.log.AndroidLogger; +import io.realm.log.RealmLog; import rx.Observable; /** @@ -71,7 +69,7 @@ abstract class BaseRealm implements Closeable { static { //noinspection ConstantConditions - RealmLog.add(BuildConfig.DEBUG ? new DebugAndroidLogger() : new ReleaseAndroidLogger()); + RealmLog.add(BuildConfig.DEBUG ? new AndroidLogger(Log.DEBUG) : new AndroidLogger(Log.WARN)); } protected BaseRealm(RealmConfiguration configuration) { @@ -550,7 +548,7 @@ static private boolean deletes(String canonicalPath, File rootFolder, String rea boolean deleteResult = fileToDelete.delete(); if (!deleteResult) { realmDeleted.set(false); - RealmLog.w("Could not delete the file " + fileToDelete); + RealmLog.warn("Could not delete the file %s", fileToDelete); } } } @@ -683,9 +681,9 @@ boolean hasValidNotifier() { @Override protected void finalize() throws Throwable { if (sharedRealm != null && !sharedRealm.isClosed()) { - RealmLog.w("Remember to call close() on all Realm instances. " + - "Realm " + configuration.getPath() + " is being finalized without being closed, " + - "this can lead to running out of native memory." + RealmLog.warn("Remember to call close() on all Realm instances. " + + "Realm %s is being finalized without being closed, " + + "this can lead to running out of native memory.", configuration.getPath() ); } super.finalize(); diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index d7abb24362..20671389cd 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -21,7 +21,7 @@ import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.internal.Table; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; import rx.Observable; /** @@ -169,7 +169,7 @@ public void executeTransaction(Transaction transaction) { if (isInTransaction()) { cancelTransaction(); } else { - RealmLog.w("Could not cancel transaction, not currently in a transaction."); + RealmLog.warn("Could not cancel transaction, not currently in a transaction."); } throw e; } diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java index 911b46fc92..3a04ddff21 100644 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java @@ -40,7 +40,11 @@ import io.realm.internal.SharedRealm; import io.realm.internal.async.BadVersionException; import io.realm.internal.async.QueryUpdateTask; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; + +import static android.R.attr.version; +import static io.realm.internal.HandlerControllerConstants.LOCAL_COMMIT; +import static io.realm.internal.HandlerControllerConstants.REALM_CHANGED; /** * Centralises all Handler callbacks, including updating async queries and refreshing the Realm. @@ -113,9 +117,9 @@ public boolean handleMessage(Message message) { QueryUpdateTask.Result result; switch (message.what) { - case HandlerControllerConstants.LOCAL_COMMIT: - case HandlerControllerConstants.REALM_CHANGED: - realmChanged(message.what == HandlerControllerConstants.LOCAL_COMMIT); + case LOCAL_COMMIT: + case REALM_CHANGED: + realmChanged(message.what == LOCAL_COMMIT); break; case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: @@ -382,9 +386,9 @@ private void updateAsyncQueries() { // try to cancel any pending update since we're submitting a new one anyway updateAsyncQueriesTask.cancel(true); Realm.asyncTaskExecutor.getQueue().remove(updateAsyncQueriesTask); - RealmLog.d("REALM_CHANGED realm:" + HandlerController.this + " cancelling pending COMPLETED_UPDATE_ASYNC_QUERIES updates"); + RealmLog.trace("REALM_CHANGED realm: %s cancelling pending COMPLETED_UPDATE_ASYNC_QUERIES updates", HandlerController.this); } - RealmLog.d("REALM_CHANGED realm:"+ HandlerController.this + " updating async queries, total: " + asyncRealmResults.size()); + RealmLog.trace("REALM_CHANGED realm: %s updating async queries, total: %d", HandlerController.this, asyncRealmResults.size()); // prepare a QueryUpdateTask to current async queries in this thread QueryUpdateTask.Builder.UpdateQueryStep updateQueryStep = QueryUpdateTask.newBuilder() .realmConfiguration(realm.getConfiguration()); @@ -424,13 +428,13 @@ private void updateAsyncQueries() { } private void realmChanged(boolean localCommit) { - RealmLog.d((localCommit ? "LOCAL_COMMIT" : "REALM_CHANGED") + " : realm:" + HandlerController.this); + RealmLog.debug("%s : %s", (localCommit ? "LOCAL_COMMIT" : "REALM_CHANGED"), HandlerController.this); deleteWeakReferences(); boolean threadContainsAsyncQueries = threadContainsAsyncQueries(); // Mixing local transactions and async queries has unavoidable race conditions if (localCommit && threadContainsAsyncQueries) { - RealmLog.w("Mixing asynchronous queries with local writes should be avoided. " + + RealmLog.warn("Mixing asynchronous queries with local writes should be avoided. " + "Realm will convert any async queries to synchronous in order to remain consistent. Use " + "asynchronous writes instead. You can read more here: " + "https://realm.io/docs/java/latest/#asynchronous-transactions"); @@ -461,8 +465,8 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) { RealmResults realmResults = weakRealmResults.get(); if (realmResults == null) { asyncRealmResults.remove(weakRealmResults); - RealmLog.d("[COMPLETED_ASYNC_REALM_RESULTS "+ weakRealmResults + "] realm:"+ HandlerController.this + " RealmResults GC'd ignore results"); - + RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] realm: %s RealmResults GC'd ignore results", + weakRealmResults, HandlerController.this); } else { SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID(); int compare = callerVersionID.compareTo(result.versionID); @@ -470,14 +474,16 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) { // if the RealmResults is empty (has not completed yet) then use the value // otherwise a task (grouped update) has already updated this RealmResults if (!realmResults.isLoaded()) { - RealmLog.d("[COMPLETED_ASYNC_REALM_RESULTS "+ weakRealmResults + "] , realm:"+ HandlerController.this + " same versions, using results (RealmResults is not loaded)"); + RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] , realm: %s same versions, using results (RealmResults is not loaded)", + weakRealmResults, HandlerController.this); // swap pointer realmResults.swapTableViewPointer(result.updatedTableViews.get(weakRealmResults)); // notify callbacks realmResults.syncIfNeeded(); realmResults.notifyChangeListeners(false); } else { - RealmLog.d("[COMPLETED_ASYNC_REALM_RESULTS "+ weakRealmResults + "] , realm:"+ HandlerController.this + " ignoring result the RealmResults (is already loaded)"); + RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] , realm: %s ignoring result the RealmResults (is already loaded)", + weakRealmResults, HandlerController.this); } } else if (compare > 0) { @@ -492,7 +498,7 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) { if (!realmResults.isLoaded()) { // UC2 // UC covered by this test: RealmAsyncQueryTests#testFindAllAsyncRetry - RealmLog.d("[COMPLETED_ASYNC_REALM_RESULTS " + weakRealmResults + "] , realm:"+ HandlerController.this + " caller is more advanced & RealmResults is not loaded, rerunning the query against the latest version"); + RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s ] , %s caller is more advanced & RealmResults is not loaded, rerunning the query against the latest version", weakRealmResults, HandlerController.this); RealmQuery query = asyncRealmResults.get(weakRealmResults); QueryUpdateTask queryUpdateTask = QueryUpdateTask.newBuilder() @@ -508,7 +514,7 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) { } else { // UC covered by this test: RealmAsyncQueryTests#testFindAllCallerIsAdvanced - RealmLog.d("[COMPLETED_ASYNC_REALM_RESULTS "+ weakRealmResults + "] , realm:"+ HandlerController.this + " caller is more advanced & RealmResults is loaded ignore the outdated result"); + RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] , %s caller is more advanced & RealmResults is loaded ignore the outdated result", weakRealmResults, HandlerController.this); } } else { @@ -516,7 +522,7 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) { // no need to rerun the query, since we're going to receive the update signal // & batch update all async queries including this one // UC covered by this test: RealmAsyncQueryTests#testFindAllCallerThreadBehind - RealmLog.d("[COMPLETED_ASYNC_REALM_RESULTS "+ weakRealmResults + "] , realm:"+ HandlerController.this + " caller thread behind worker thread, ignore results (a batch update will update everything including this query)"); + RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] , %s caller thread behind worker thread, ignore results (a batch update will update everything including this query)", weakRealmResults, HandlerController.this); } } } @@ -529,7 +535,7 @@ private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) { // if the caller thread is more advanced than the worker thread, it means it did a local commit. // This should also have put a REALM_CHANGED event on the Looper queue, so ignoring this result should // be safe as all async queries will be rerun when processing the REALM_CHANGED event. - RealmLog.d("COMPLETED_UPDATE_ASYNC_QUERIES realm:" + HandlerController.this + " caller is more advanced, Looper will updates queries"); + RealmLog.trace("COMPLETED_UPDATE_ASYNC_QUERIES %s caller is more advanced, Looper will updates queries", HandlerController.this); } else { // We're behind or on the same version as the worker thread @@ -540,7 +546,7 @@ private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) { // imperative TV, they will not rerun if the SharedGroup advance // UC covered by this test: RealmAsyncQueryTests#testFindAllCallerThreadBehind - RealmLog.d("COMPLETED_UPDATE_ASYNC_QUERIES realm:"+ HandlerController.this + " caller is behind advance_read"); + RealmLog.trace("COMPLETED_UPDATE_ASYNC_QUERIES %s caller is behind advance_read", HandlerController.this); // refresh the Realm to the version provided by the worker thread // (advanceRead to the latest version may cause a version mismatch error) preventing us // from importing correctly the handover table view @@ -570,7 +576,7 @@ private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) { realmResults.syncIfNeeded(); resultsToBeNotified.add(realmResults); - RealmLog.d("COMPLETED_UPDATE_ASYNC_QUERIES realm:"+ HandlerController.this + " updating RealmResults " + weakRealmResults); + RealmLog.trace("COMPLETED_UPDATE_ASYNC_QUERIES updating RealmResults %s", HandlerController.this, weakRealmResults); } } collectSyncRealmResultsCallbacks(resultsToBeNotified); @@ -622,13 +628,15 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) { // the caller has advanced we need to // retry against the current version of the caller if it's still empty if (RealmObject.isValid(proxy)) { // already completed & has a valid pointer no need to re-run - RealmLog.d("[COMPLETED_ASYNC_REALM_OBJECT "+ proxy + "] , realm:" + HandlerController.this - + " RealmObject is already loaded, just notify it."); + RealmLog.trace("[COMPLETED_ASYNC_REALM_OBJECT %s], realm: %s. " + + "RealmObject is already loaded, just notify it", + realm, HandlerController.this); proxy.realmGet$proxyState().notifyChangeListeners$realm(); } else { - RealmLog.d("[COMPLETED_ASYNC_REALM_OBJECT " + proxy + "] , realm:" + HandlerController.this - + " RealmObject is not loaded yet. Rerun the query."); + RealmLog.trace("[COMPLETED_ASYNC_REALM_OBJECT %s, realm: %s. " + + "RealmObject is not loaded yet. Rerun the query.", + proxy, HandlerController.this); Object value = realmObjects.get(realmObjectWeakReference); RealmQuery realmQuery; if (value == null || value == NO_REALM_QUERY) { // this is a retry of an empty RealmObject diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 73f8e5f405..f06b5f11b8 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -20,11 +20,10 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; -import io.realm.internal.InvalidRow; import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.TableQuery; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; /** * This implements {@code RealmObjectProxy} interface, to eliminate copying logic between @@ -114,7 +113,7 @@ public ProxyState(Class clazzName, E model) { isCompleted = true; } } catch (Exception e) { - RealmLog.d(e.getMessage()); + RealmLog.debug(e); return false; } return true; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 570943cb3a..53f8ff4b55 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -50,7 +50,7 @@ import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; import rx.Observable; /** @@ -1089,7 +1089,7 @@ public void executeTransaction(Transaction transaction) { if (isInTransaction()) { cancelTransaction(); } else { - RealmLog.w("Could not cancel transaction, not currently in a transaction."); + RealmLog.warn("Could not cancel transaction, not currently in a transaction."); } throw e; } @@ -1198,7 +1198,7 @@ public void run() { if (bgRealm.isInTransaction()) { bgRealm.cancelTransaction(); } else if (exception[0] != null) { - RealmLog.w("Could not cancel transaction, not currently in a transaction."); + RealmLog.warn("Could not cancel transaction, not currently in a transaction."); } bgRealm.close(); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 99648ee4c1..0a24a113bd 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -26,7 +26,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.internal.ColumnIndices; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; /** * To cache {@link Realm}, {@link DynamicRealm} instances and related resources. @@ -176,7 +176,7 @@ static synchronized void release(BaseRealm realm) { } if (refCount <= 0) { - RealmLog.w("Realm " + canonicalPath + " has been closed already."); + RealmLog.warn("%s has been closed already.", canonicalPath); return; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index b6d6b0d546..ed64e04ef9 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -38,7 +38,7 @@ import io.realm.internal.async.ArgumentsHolder; import io.realm.internal.async.BadVersionException; import io.realm.internal.async.QueryUpdateTask; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; /** * A RealmQuery encapsulates a query on a {@link io.realm.Realm} or a {@link io.realm.RealmResults} using the Builder @@ -1407,7 +1407,7 @@ public Long call() throws Exception { return handoverTableViewPointer; } catch (Throwable e) { - RealmLog.e(e.getMessage(), e); + RealmLog.error(e); closeSharedRealmAndSendEventToNotifier(sharedRealm, weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); } finally { @@ -1713,11 +1713,11 @@ public Long call() throws Exception { } catch (BadVersionException e) { // In some rare race conditions, this can happen. In that case, just ignore the error. - RealmLog.d("findAllAsync handover could not complete due to a BadVersionException. " + + RealmLog.debug("findAllAsync handover could not complete due to a BadVersionException. " + "Retry is scheduled by a REALM_CHANGED event."); } catch (Throwable e) { - RealmLog.e(e.getMessage(), e); + RealmLog.error(e); closeSharedRealmAndSendEventToNotifier(sharedRealm, weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); } finally { @@ -1827,11 +1827,11 @@ public Long call() throws Exception { return handoverTableViewPointer; } catch (BadVersionException e) { // In some rare race conditions, this can happen. In that case, just ignore the error. - RealmLog.d("findAllSortedAsync handover could not complete due to a BadVersionException. " + + RealmLog.debug("findAllSortedAsync handover could not complete due to a BadVersionException. " + "Retry is scheduled by a REALM_CHANGED event."); } catch (Throwable e) { - RealmLog.e(e.getMessage(), e); + RealmLog.error(e); closeSharedRealmAndSendEventToNotifier(sharedRealm, weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); @@ -1997,11 +1997,11 @@ public Long call() throws Exception { return handoverTableViewPointer; } catch (BadVersionException e) { // In some rare race conditions, this can happen. In that case, just ignore the error. - RealmLog.d("findAllSortedAsync handover could not complete due to a BadVersionException. " + + RealmLog.debug("findAllSortedAsync handover could not complete due to a BadVersionException. " + "Retry is scheduled by a REALM_CHANGED event."); } catch (Throwable e) { - RealmLog.e(e.getMessage(), e); + RealmLog.error(e); closeSharedRealmAndSendEventToNotifier(sharedRealm, weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); } finally { @@ -2135,7 +2135,7 @@ public Long call() throws Exception { return handoverRowPointer; } catch (Throwable e) { - RealmLog.e(e.getMessage(), e); + RealmLog.error(e); // handler can't throw a checked exception need to wrap it into unchecked Exception closeSharedRealmAndSendEventToNotifier(sharedRealm, weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index f2416101e0..090302a6c2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -38,7 +38,7 @@ import io.realm.internal.TableQuery; import io.realm.internal.TableView; import io.realm.internal.async.BadVersionException; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; import rx.Observable; /** @@ -917,7 +917,7 @@ private boolean onAsyncQueryCompleted() { asyncQueryCompleted = true; notifyChangeListeners(true); } catch (Exception e) { - RealmLog.d(e.getMessage()); + RealmLog.debug(e.getMessage()); return false; } return true; diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableView.java b/realm/realm-library/src/main/java/io/realm/internal/TableView.java index db245e3fe6..fa3ce44194 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableView.java @@ -16,13 +16,11 @@ package io.realm.internal; -import java.io.Closeable; import java.util.Date; import java.util.List; import io.realm.RealmFieldType; import io.realm.Sort; -import io.realm.internal.log.RealmLog; /** * This class represents a view of a particular table. We can think of a tableview as a subset of a table. It contains @@ -32,7 +30,6 @@ * with the real data. */ public class TableView implements TableOrView { - private static final boolean DEBUG = false; //true; // Don't convert this into local variable and don't remove this. // Core requests TableView to hold the Query reference. @SuppressWarnings({"unused"}) diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidLogger.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidLogger.java deleted file mode 100644 index 196b0450b3..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidLogger.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android; - -import android.util.Log; - -import io.realm.internal.log.Logger; -import io.realm.internal.log.RealmLog; - -public class AndroidLogger implements Logger { - - private static final int LOG_ENTRY_MAX_LENGTH = 4000; - private int minimumLogLevel = RealmLog.VERBOSE; - private String logTag = "REALM"; - - /** - * Manually sets a logging tag. - * - * @param tag Logging tag to use for all subsequent logging calls. - */ - public void setTag(String tag) { - logTag = tag; - } - - /** - * Overrides the provided logger behavior and only log if log entry has a level equal or higher. - * - * @param logLevel the minimum log level to report. - */ - public void setMinimumLogLevel(int logLevel) { - minimumLogLevel = logLevel; - } - - // Inspired by https://github.com/JakeWharton/timber/blob/master/timber/src/main/java/timber/log/Timber.java - private void log(int logLevel, String message, Throwable t) { - if (logLevel < minimumLogLevel) { - return; - } - if (message == null || message.length() == 0) { - if (t != null) { - message = Log.getStackTraceString(t); - } else { - return; // Don't log if message is null and there is no throwable - } - } else if (t != null) { - message += "\n" + Log.getStackTraceString(t); - } - - if (message.length() < 4000) { - Log.println(logLevel, logTag, message); - } else { - logMessageIgnoringLimit(logLevel, logTag, message); - } - } - - /** - * Inspired by: - * http://stackoverflow.com/questions/8888654/android-set-max-length-of-logcat-messages - * https://github.com/jakubkrolewski/timber/blob/feature/logging_long_messages/timber/src/main/java/timber/log/Timber.java - */ - private void logMessageIgnoringLimit(int logLevel, String tag, String message) { - while (message.length() != 0) { - int nextNewLineIndex = message.indexOf('\n'); - int chunkLength = nextNewLineIndex != -1 ? nextNewLineIndex : message.length(); - chunkLength = Math.min(chunkLength, LOG_ENTRY_MAX_LENGTH); - String messageChunk = message.substring(0, chunkLength); - Log.println(logLevel, tag, messageChunk); - - if (nextNewLineIndex != -1 && nextNewLineIndex == chunkLength) { - // Don't print out the \n twice. - message = message.substring(chunkLength + 1); - } else { - message = message.substring(chunkLength); - } - } - } - - @Override - public void v(String message) { - log(RealmLog.VERBOSE, message, null); - } - - @Override - public void v(String message, Throwable t) { - log(RealmLog.VERBOSE, message, t); - } - - @Override - public void d(String message) { - log(RealmLog.DEBUG, message, null); - } - - @Override - public void d(String message, Throwable t) { - log(RealmLog.DEBUG, message, t); - } - - @Override - public void i(String message) { - log(RealmLog.INFO, message, null); - } - - @Override - public void i(String message, Throwable t) { - log(RealmLog.INFO, message, t); - } - - @Override - public void w(String message) { - log(RealmLog.WARN, message, null); - } - - @Override - public void w(String message, Throwable t) { - log(RealmLog.WARN, message, t); - } - - @Override - public void e(String message) { - log(RealmLog.ERROR, message, null); - } - - @Override - public void e(String message, Throwable t) { - log(RealmLog.ERROR, message, t); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/DebugAndroidLogger.java b/realm/realm-library/src/main/java/io/realm/internal/android/DebugAndroidLogger.java deleted file mode 100644 index bf2de63996..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/android/DebugAndroidLogger.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android; - -import io.realm.internal.log.RealmLog; - -/** - * RealmLogger for Android debug builds. This logs everything as default. - */ -public class DebugAndroidLogger extends AndroidLogger { - - public DebugAndroidLogger() { - setMinimumLogLevel(RealmLog.VERBOSE); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/ReleaseAndroidLogger.java b/realm/realm-library/src/main/java/io/realm/internal/android/ReleaseAndroidLogger.java deleted file mode 100644 index ce7e28966f..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/android/ReleaseAndroidLogger.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android; - -import io.realm.internal.log.RealmLog; - -/** - * This is the RealmLogger used by Realm in Release builds. It only logs warnings and errors by default. - */ -public class ReleaseAndroidLogger extends AndroidLogger { - - public ReleaseAndroidLogger() { - setMinimumLogLevel(RealmLog.WARN); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java index f106ef3191..b711d45de9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java @@ -29,7 +29,7 @@ import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableQuery; -import io.realm.internal.log.RealmLog; +import io.realm.log.RealmLog; /** * Manages the update of async queries. @@ -118,11 +118,11 @@ public void run() { } catch (BadVersionException e) { // In some rare race conditions, this can happen. In that case, just ignore the error. - RealmLog.d("Query update task could not complete due to a BadVersionException. " + + RealmLog.debug("Query update task could not complete due to a BadVersionException. " + "Retry is scheduled by a REALM_CHANGED event."); } catch (Throwable e) { - RealmLog.e(e.getMessage(), e); + RealmLog.error(e); RealmNotifier notifier = callerNotifier.get(); if (notifier!= null) { notifier.throwBackgroundException(e); diff --git a/realm/realm-library/src/main/java/io/realm/internal/log/Logger.java b/realm/realm-library/src/main/java/io/realm/internal/log/Logger.java deleted file mode 100644 index 50eba1905c..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/log/Logger.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal.log; - -/** - * Interface for Realm logger implementations. - */ -public interface Logger { - void v(String message); - void v(String message, Throwable t); - void d(String message); - void d(String message, Throwable t); - void i(String message); - void i(String message, Throwable t); - void w(String message); - void w(String message, Throwable t); - void e(String message); - void e(String message, Throwable t); -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/internal/log/RealmLog.java deleted file mode 100644 index 5c1ef9f3d4..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/log/RealmLog.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal.log; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Logger implementation for Realm. This can be used to transparently change logging behavior between Android and Java. - * - * This class supports adding multiple logger implementations. - */ -public final class RealmLog { - - // Log levels - public static final int VERBOSE = 2; - public static final int DEBUG = 3; - public static final int INFO = 4; - public static final int WARN = 5; - public static final int ERROR = 6; - public static final int ASSERT = 7; - public static final int NONE = 8; - - private static final List LOGGERS = new CopyOnWriteArrayList(); - - /** - * Adds a logger implementation. - * - * @param logger the reference to a {@link Logger} implementation. - */ - public static void add(Logger logger) { - if (logger == null) { - throw new IllegalArgumentException("A non-null logger has to be provided"); - } - LOGGERS.add(logger); - } - - /** - * Removes a current logger implementation. - * - * @param logger. - */ - public static void remove(Logger logger) { - if (logger == null) { - throw new IllegalArgumentException("A non-null logger has to be provided"); - } - LOGGERS.remove(logger); - } - - public static void v(String message) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).v(message); - } - } - - public static void v(String message, Throwable t) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).v(message, t); - } - } - - public static void d(String message) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).d(message); - } - } - - public static void d(String message, Throwable t) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).d(message, t); - } - } - - public static void i(String message) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).i(message); - } - } - - public static void i(String message, Throwable t) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).i(message, t); - } - } - - public static void w(String message) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).w(message); - } - } - - public static void w(String message, Throwable t) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).w(message, null); - } - } - - public static void e(String message) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).e(message); - } - } - - public static void e(String message, Throwable t) { - for (int i = 0; i < LOGGERS.size(); i++) { - LOGGERS.get(i).v(message, t); - } - } -} diff --git a/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java b/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java new file mode 100644 index 0000000000..9f460aeeed --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java @@ -0,0 +1,150 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.log; + +import android.util.Log; + +import static android.util.Log.getStackTraceString; + +/** + * Logger implementation outputting to Android LogCat. + * Androids {@link Log}levels are mapped to Realm {@link LogLevel}s using the following table: + * + * + * + * + * + * + * + * + * + * + * + * + *
{@link LogLevel#ALL}{@link Log#VERBOSE}{@link LogLevel#TRACE}{@link Log#VERBOSE}{@link LogLevel#DEBUG}{@link Log#DEBUG}{@link LogLevel#INFO}{@link Log#INFO}{@link LogLevel#WARN}{@link Log#WARN}{@link LogLevel#ERROR}{@link Log#ERROR}{@link LogLevel#FATAL}{@link Log#ERROR}{@link LogLevel#OFF}Not supported. Remove the logger instead.
+ */ +public class AndroidLogger implements Logger { + + private static final int LOG_ENTRY_MAX_LENGTH = 4000; + private final int minimumLogLevel; + private volatile String logTag = "REALM"; + + /** + * Creates an logger that outputs to logcat. + * + * @param androidLogLevel Android log level + */ + public AndroidLogger(int androidLogLevel) { + if (androidLogLevel < Log.VERBOSE || androidLogLevel > Log.ASSERT) { + throw new IllegalArgumentException("Unknown android log level: " + androidLogLevel); + } + minimumLogLevel = androidLogLevel; + } + + /** + * Sets the logging tag used when outputting to LogCat. The default value is "REALM". + * + * @param tag Logging tag to use for all subsequent logging calls. + */ + public void setTag(String tag) { + logTag = tag; + } + + @Override + public int getMinimumNativeDebugLevel() { + // Map Android log level to Realms log levels + switch (minimumLogLevel) { + case Log.VERBOSE: return LogLevel.TRACE; + case Log.DEBUG: return LogLevel.DEBUG; + case Log.INFO: return LogLevel.INFO; + case Log.WARN: return LogLevel.WARN; + case Log.ERROR: return LogLevel.ERROR; + case Log.ASSERT: return LogLevel.FATAL; + default: + throw new IllegalStateException("Unknown log level: " + minimumLogLevel); + } + } + + // Inspired by https://github.com/JakeWharton/timber/blob/master/timber/src/main/java/timber/log/Timber.java + private void log(int androidLogLevel, Throwable t, String message, Object... args) { + if (androidLogLevel < minimumLogLevel) { + return; + } + if (message == null) { + if (t == null) { + return; // Ignore event if message is null and there's no throwable. + } + message = getStackTraceString(t); + } else { + if (args != null && args.length > 0) { + message = String.format(message, args); + } + if (t != null) { + message += "\n" + getStackTraceString(t); + } + } + + // Message fit one line. Just print and exit + if (message.length() < LOG_ENTRY_MAX_LENGTH) { + Log.println(androidLogLevel, logTag, message); + return; + } + + // Message does not fit one line. + // Split by line, then ensure each line can fit into Log's maximum length. + for (int i = 0, length = message.length(); i < length; i++) { + int newline = message.indexOf('\n', i); + newline = newline != -1 ? newline : length; + do { + int end = Math.min(newline, i + LOG_ENTRY_MAX_LENGTH); + String part = message.substring(i, end); + Log.println(androidLogLevel, logTag, part); + i = end; + } while (i < newline); + } + } + + @Override + public void trace(Throwable throwable, String message, Object... args) { + log(Log.VERBOSE, throwable, message, args); + } + + @Override + public void debug(Throwable throwable, String message, Object... args) { + log(Log.DEBUG, throwable, message, args); + } + + @Override + public void info(Throwable throwable, String message, Object... args) { + log(Log.INFO, throwable, message, args); + } + + @Override + public void warn(Throwable throwable, String message, Object... args) { + log(Log.WARN, throwable, message, args); + } + + @Override + public void error(Throwable throwable, String message, Object... args) { + log(Log.ERROR, throwable, message, args); + } + + @Override + public void fatal(Throwable throwable, String message, Object... args) { + log(Log.ASSERT, throwable, message, args); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/log/LogLevel.java b/realm/realm-library/src/main/java/io/realm/log/LogLevel.java new file mode 100644 index 0000000000..8333656322 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/log/LogLevel.java @@ -0,0 +1,71 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.log; + +/** + * The Log levels defined and used by Realm when logging events in the API. + * + * Realm uses the log levels defined by Log4J: + * https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Level.html + * + * @see RealmLog#add(Logger) + */ +public class LogLevel { + + /** + * The ALL has the lowest possible rank and is intended to turn on all logging. + */ + public static final int ALL = 1; + + /** + * The TRACE level designates finer-grained informational events than DEBUG. + */ + public static final int TRACE = 2; + + /** + * The DEBUG level designates fine-grained informational events that are mostly useful to debug an application. + */ + public static final int DEBUG = 3; + + /** + * The INFO level designates informational messages that highlight the progress of the application at + * coarse-grained level. + */ + public static final int INFO = 4; + + /** + * The WARN level designates potentially harmful situations. + */ + public static final int WARN = 5; + + /** + * The ERROR level designates error events that might still allow the application to continue running. + */ + public static final int ERROR = 6; + + /** + * The FATAL level designates very severe error events that will presumably lead the application to abort. + */ + public static final int FATAL = 7; + + /** + * The OFF has the highest possible rank and is intended to turn off logging. + */ + public static final int OFF = 8; +} + + diff --git a/realm/realm-library/src/main/java/io/realm/log/Logger.java b/realm/realm-library/src/main/java/io/realm/log/Logger.java new file mode 100644 index 0000000000..7da472b4a8 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/log/Logger.java @@ -0,0 +1,90 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.log; + +/** + * Interface for custom loggers that can be registered at {@link RealmLog#add(Logger)}. + * The different log levels are described in {@link LogLevel}. + */ +public interface Logger { + + /** + * Defines which {@link LogLevel} events this logger cares about from the native components. + *

+ * If multiple loggers are registered, the minimum value among all loggers is used. + *

+ * Note that sending log events from the native layer is relatively expensive, so only set this value to events + * that are truly useful. + * + * @return the minimum {@link LogLevel} native events this logger cares about. + */ + int getMinimumNativeDebugLevel(); + + /** + * Handles a {@link LogLevel#TRACE} event. + * + * @param throwable optional exception to log. + * @param message optional additional message. + * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. + */ + void trace(Throwable throwable, String message, Object... args); + + /** + * Handles a {@link LogLevel#DEBUG} event. + * + * @param throwable optional exception to log. + * @param message optional additional message. + * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. + */ + void debug(Throwable throwable, String message, Object... args); + + /** + * Handles an {@link LogLevel#INFO} event. + * + * @param throwable optional exception to log. + * @param message optional additional message. + * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. + */ + void info(Throwable throwable, String message, Object... args); + + /** + * Handles a {@link LogLevel#WARN} event. + * + * @param throwable optional exception to log. + * @param message optional additional message. + * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. + */ + void warn(Throwable throwable, String message, Object... args); + + /** + * Handles an {@link LogLevel#ERROR} event. + * + * @param throwable optional exception to log. + * @param message optional additional message. + * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. + */ + void error(Throwable throwable, String message, Object... args); + + /** + * Handles a {@link LogLevel#FATAL} event. + * + * @param throwable optional exception to log. + * @param message optional additional message. + * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. + */ + void fatal(Throwable throwable, String message, Object... args); +} diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java new file mode 100644 index 0000000000..f83173214a --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -0,0 +1,301 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.log; + +import java.util.ArrayList; +import java.util.List; + +import io.realm.internal.Keep; +import io.realm.internal.Util; + +/** + * Global logger used by all Realm components. + * Custom loggers can be added by registering classes implementing {@link Logger}. + */ +@Keep +public final class RealmLog { + + private static final Logger[] NO_LOGGERS = new Logger[0]; + + // All of the below should be modified together under under a lock on LOGGERS. + private static final List LOGGERS = new ArrayList<>(); + private static volatile Logger[] loggersAsArray = NO_LOGGERS; + private static int minimumNativeLogLevel = Integer.MAX_VALUE; + + /** + * Adds a logger implementation that will be notified on log events. + * + * @param logger the reference to a {@link Logger} implementation. + */ + public static void add(Logger logger) { + if (logger == null) { + throw new IllegalArgumentException("A non-null logger has to be provided"); + } + synchronized (LOGGERS) { + LOGGERS.add(logger); + int minimumLogLevel = logger.getMinimumNativeDebugLevel(); + if (minimumLogLevel < minimumNativeLogLevel) { + setMinimumNativeDebugLevel(minimumLogLevel); + } + loggersAsArray = LOGGERS.toArray(new Logger[LOGGERS.size()]); + } + } + + private static void setMinimumNativeDebugLevel(int nativeDebugLevel) { + minimumNativeLogLevel = nativeDebugLevel; + Util.setDebugLevel(nativeDebugLevel); // Log level for Realm Core + } + + /** + * Removes the given logger if it is currently added. + * + * @return {@code true} if the logger was removed, {@code false} otherwise. + */ + public static boolean remove(Logger logger) { + if (logger == null) { + throw new IllegalArgumentException("A non-null logger has to be provided"); + } + synchronized (LOGGERS) { + LOGGERS.remove(logger); + int newMinLevel = Integer.MAX_VALUE; + for (int i = 0; i < LOGGERS.size(); i++) { + int logMin = LOGGERS.get(i).getMinimumNativeDebugLevel(); + if (logMin < newMinLevel) { + newMinLevel = logMin; + } + } + setMinimumNativeDebugLevel(newMinLevel); + loggersAsArray = LOGGERS.toArray(new Logger[LOGGERS.size()]); + } + return true; + } + + /** + * Remove all loggers. + */ + public static void clear() { + synchronized (LOGGERS) { + LOGGERS.clear(); + setMinimumNativeDebugLevel(Integer.MAX_VALUE); + loggersAsArray = NO_LOGGERS; + } + } + + /** + * Logs a {@link LogLevel#TRACE} exception. + * + * @param throwable exception to log. + */ + public static void trace(Throwable throwable) { + trace(throwable, null); + } + + /** + * Logs a {@link LogLevel#TRACE} event. + * + * @param message message to log. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void trace(String message, Object... args) { + trace(null, message, args); + } + + /** + * Logs a {@link LogLevel#TRACE} event. + * + * @param throwable optional exception to log. + * @param message optional message. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void trace(Throwable throwable, String message, Object... args) { + Logger[] loggers = loggersAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < loggers.length; i++) { + loggers[i].trace(throwable, message, args); + } + } + + /** + * Logs a {@link LogLevel#DEBUG} exception. + * + * @param throwable exception to log. + */ + public static void debug(Throwable throwable) { + debug(throwable, null); + } + + /** + * Logs a {@link LogLevel#DEBUG} event. + * + * @param message message to log. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void debug(String message, Object... args) { + debug(null, message, args); + } + + /** + * Logs a {@link LogLevel#DEBUG} event. + * + * @param throwable optional exception to log. + * @param message optional message. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void debug(Throwable throwable, String message, Object... args) { + Logger[] loggers = loggersAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < loggers.length; i++) { + loggers[i].debug(throwable, message, args); + } + } + + /** + * Logs an {@link LogLevel#INFO} exception. + * + * @param throwable exception to log. + */ + public static void info(Throwable throwable) { + info(throwable, null); + } + + /** + * Logs an {@link LogLevel#INFO} event. + * + * @param message message to log. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void info(String message, Object... args) { + info(null, message, args); + } + + /** + * Logs an {@link LogLevel#INFO} event. + * + * @param throwable optional exception to log. + * @param message optional message. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void info(Throwable throwable, String message, Object... args) { + Logger[] loggers = loggersAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < loggers.length; i++) { + loggers[i].info(throwable, message, args); + } + } + + /** + * Logs a {@link LogLevel#WARN} exception. + * + * @param throwable exception to log. + */ + public static void warn(Throwable throwable) { + warn(throwable, null); + } + + /** + * Logs a {@link LogLevel#WARN} event. + * + * @param message message to log. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void warn(String message, Object... args) { + warn(null, message, args); + } + + /** + * Logs a {@link LogLevel#WARN} event. + * + * @param throwable optional exception to log. + * @param message optional message. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void warn(Throwable throwable, String message, Object... args) { + Logger[] loggers = loggersAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < loggers.length; i++) { + loggers[i].warn(throwable, message, args); + } + } + + /** + * Logs an {@link LogLevel#ERROR} exception. + * + * @param throwable exception to log. + */ + public static void error(Throwable throwable) { + error(throwable, null); + } + + /** + * Logs an {@link LogLevel#ERROR} event. + * + * @param message message to log. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void error(String message, Object... args) { + error(null, message, args); + } + + /** + * Logs an {@link LogLevel#ERROR} event. + * + * @param throwable optional exception to log. + * @param message optional message. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void error(Throwable throwable, String message, Object... args) { + Logger[] loggers = loggersAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < loggers.length; i++) { + loggers[i].error(throwable, message, args); + } + } + + /** + * Logs a {@link LogLevel#FATAL} exception. + * + * @param throwable exception to log. + */ + public static void fatal(Throwable throwable) { + fatal(throwable, null); + } + + /** + * Logs an {@link LogLevel#FATAL} event. + * + * @param message message to log. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void fatal(String message, Object... args) { + fatal(null, message, args); + } + + /** + * Logs a {@link LogLevel#FATAL} event. + * + * @param throwable optional exception to log. + * @param message optional message. + * @param args optional args used to format the message using {@link String#format(String, Object...)}. + */ + public static void fatal(Throwable throwable, String message, Object... args) { + Logger[] loggers = loggersAsArray; + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < loggers.length; i++) { + loggers[i].fatal(throwable, message, args); + } + } +} From 2f233742fd8c02f7de8320caec1cf516ddad74b9 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 2 Sep 2016 01:03:41 +0800 Subject: [PATCH 0020/2110] Use createObject(class, primaryKey) in test (#3377) Since Realm.createObject(class) will be deprecated on master for Classes with primary key defined, fix the test cases which use it first to avoid more conflicts when merging. --- .../java/io/realm/BulkInsertTests.java | 8 +++--- .../java/io/realm/CollectionTests.java | 2 +- .../io/realm/DynamicRealmObjectTests.java | 25 +++++++++-------- .../ManagedOrderedRealmCollectionTests.java | 2 +- .../io/realm/ManagedRealmCollectionTests.java | 2 +- .../java/io/realm/RealmAnnotationTests.java | 27 +++++++------------ .../io/realm/RealmChangeListenerTests.java | 4 +-- .../java/io/realm/RealmCollectionTests.java | 2 +- .../java/io/realm/RealmMigrationTests.java | 4 +-- .../java/io/realm/RealmObjectTests.java | 8 +++--- .../java/io/realm/RealmQueryTests.java | 3 +-- .../androidTest/java/io/realm/RealmTests.java | 10 +++---- 12 files changed, 43 insertions(+), 54 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java index 0d64918ed8..b2adb4d7e5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java @@ -833,8 +833,8 @@ public void insertManagedObjectWillNotDuplicate() { @Test public void insertOrUpdate_collectionOfManagedObjects() { realm.beginTransaction(); - AllTypesPrimaryKey allTypes = realm.createObject(AllTypesPrimaryKey.class); - allTypes.getColumnRealmList().add(realm.createObject(DogPrimaryKey.class)); + AllTypesPrimaryKey allTypes = realm.createObject(AllTypesPrimaryKey.class, 0); + allTypes.getColumnRealmList().add(realm.createObject(DogPrimaryKey.class, 0)); realm.commitTransaction(); assertEquals(1, allTypes.getColumnRealmList().size()); @@ -856,8 +856,8 @@ public void insertOrUpdate_collectionOfManagedObjects() { @Test public void insertOrUpdate_shouldNotClearRealmList() { realm.beginTransaction(); - AllTypesPrimaryKey allTypes = realm.createObject(AllTypesPrimaryKey.class); - allTypes.getColumnRealmList().add(realm.createObject(DogPrimaryKey.class)); + AllTypesPrimaryKey allTypes = realm.createObject(AllTypesPrimaryKey.class, 0); + allTypes.getColumnRealmList().add(realm.createObject(DogPrimaryKey.class, 0)); realm.commitTransaction(); assertEquals(1, allTypes.getColumnRealmList().size()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index d147fd591a..cabf34b078 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -200,7 +200,7 @@ protected OrderedRealmCollection createStringCollection(Realm real return realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_STRING); case MANAGED_REALMLIST: - AllJavaTypes first = realm.createObject(AllJavaTypes.class); + AllJavaTypes first = realm.createObject(AllJavaTypes.class, 0); first.setFieldString(args[0]); first.getFieldList().add(first); for (int i = 1; i < args.length; i++) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index d9153010a2..27ad6336ad 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -68,7 +68,7 @@ public void setUp() { RealmConfiguration realmConfig = configFactory.createConfiguration(); realm = Realm.getInstance(realmConfig); realm.beginTransaction(); - typedObj = realm.createObject(AllJavaTypes.class); + typedObj = realm.createObject(AllJavaTypes.class, 0); typedObj.setFieldString("str"); typedObj.setFieldShort((short) 1); typedObj.setFieldInt(1); @@ -247,7 +247,7 @@ private void callSetter(SupportedType type, List fieldNames) { @Test public void typedGettersAndSetters() { realm.beginTransaction(); - AllJavaTypes obj = realm.createObject(AllJavaTypes.class); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { for (SupportedType type : SupportedType.values()) { @@ -311,7 +311,7 @@ public void typedGettersAndSetters() { @Test public void setter_null() { realm.beginTransaction(); - NullTypes obj = realm.createObject(NullTypes.class); + NullTypes obj = realm.createObject(NullTypes.class, 0); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { for (SupportedType type : SupportedType.values()) { @@ -384,7 +384,7 @@ public void setter_null() { @Test public void setter_nullOnRequiredFieldsThrows() { realm.beginTransaction(); - NullTypes obj = realm.createObject(NullTypes.class); + NullTypes obj = realm.createObject(NullTypes.class, 0); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { for (SupportedType type : SupportedType.values()) { @@ -418,7 +418,7 @@ public void setter_nullOnRequiredFieldsThrows() { @Test public void typedSetter_null() { realm.beginTransaction(); - NullTypes obj = realm.createObject(NullTypes.class); + NullTypes obj = realm.createObject(NullTypes.class, 0); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { for (SupportedType type : SupportedType.values()) { @@ -478,7 +478,7 @@ public void setObject_differentType() { @Test public void setObject_wrongTypeThrows() { realm.beginTransaction(); - AllJavaTypes obj = realm.createObject(AllJavaTypes.class); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); Dog otherObj = realm.createObject(Dog.class); DynamicRealmObject dynamicObj = new DynamicRealmObject(obj); DynamicRealmObject dynamicWrongType = new DynamicRealmObject(otherObj); @@ -611,8 +611,7 @@ public void untypedSetter_listWrongTypeThrows() { @Test public void untypedSetter_listMixedTypesThrows() { realm.beginTransaction(); - AllJavaTypes obj1 = realm.createObject(AllJavaTypes.class); - obj1.setFieldLong(2); + AllJavaTypes obj1 = realm.createObject(AllJavaTypes.class, 2); CyclicType obj2 = realm.createObject(CyclicType.class); RealmList list = new RealmList(); @@ -644,7 +643,7 @@ public void getList() { @Test public void untypedGetterSetter() { realm.beginTransaction(); - AllJavaTypes obj = realm.createObject(AllJavaTypes.class); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { for (SupportedType type : SupportedType.values()) { @@ -713,7 +712,7 @@ public void untypedGetterSetter() { @Test public void untypedSetter_usingStringConversion() { realm.beginTransaction(); - AllJavaTypes obj = realm.createObject(AllJavaTypes.class); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { for (SupportedType type : SupportedType.values()) { @@ -767,7 +766,7 @@ public void untypedSetter_usingStringConversion() { @Test public void untypedSetter_illegalImplicitConversionThrows() { realm.beginTransaction(); - AllJavaTypes obj = realm.createObject(AllJavaTypes.class); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { for (SupportedType type : SupportedType.values()) { @@ -827,7 +826,7 @@ public void isNull_nullNotSupportedField() { @Test public void isNull_true() { realm.beginTransaction(); - AllJavaTypes obj = realm.createObject(AllJavaTypes.class); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); realm.commitTransaction(); assertTrue(new DynamicRealmObject(obj).isNull(AllJavaTypes.FIELD_OBJECT)); @@ -913,7 +912,7 @@ public void toString_test() { @Test public void toString_nullValues() { dynamicRealm.beginTransaction(); - final DynamicRealmObject obj = dynamicRealm.createObject(NullTypes.CLASS_NAME); + final DynamicRealmObject obj = dynamicRealm.createObject(NullTypes.CLASS_NAME, 0); dynamicRealm.commitTransaction(); String str = obj.toString(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index 2f19f3c5a5..c903d110c7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -170,7 +170,7 @@ private OrderedRealmCollection createEmptyCollection(Realm realm, Man switch (collectionClass) { case MANAGED_REALMLIST: realm.beginTransaction(); - NullTypes obj = realm.createObject(NullTypes.class); + NullTypes obj = realm.createObject(NullTypes.class, 0); realm.commitTransaction(); return obj.getFieldListNull(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 0812e5f972..aa50c1f62f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -139,7 +139,7 @@ private OrderedRealmCollection createEmptyCollection(Realm realm, Man switch (collectionClass) { case MANAGED_REALMLIST: realm.beginTransaction(); - NullTypes obj = realm.createObject(NullTypes.class); + NullTypes obj = realm.createObject(NullTypes.class, 0); realm.commitTransaction(); return obj.getFieldListNull(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java index 81c4938f31..588ca1fe74 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java @@ -50,7 +50,7 @@ public void setUp() { RealmConfiguration realmConfig = configFactory.createConfiguration(); realm = Realm.getInstance(realmConfig); realm.beginTransaction(); - AnnotationTypes object = realm.createObject(AnnotationTypes.class); + AnnotationTypes object = realm.createObject(AnnotationTypes.class, 0); object.setNotIndexString("String 1"); object.setIndexString("String 2"); object.setIgnoreString("String 3"); @@ -102,9 +102,8 @@ public void index() { public void primaryKey_migration_long() { realm.beginTransaction(); for (int i = 1; i <= 2; i++) { - PrimaryKeyAsString obj = realm.createObject(PrimaryKeyAsString.class); + PrimaryKeyAsString obj = realm.createObject(PrimaryKeyAsString.class, "String" + i); obj.setId(i); - obj.setName("String" + i); } Table table = realm.getTable(PrimaryKeyAsString.class); @@ -118,9 +117,8 @@ public void primaryKey_migration_long() { public void primaryKey_migration_longDuplicateValues() { realm.beginTransaction(); for (int i = 1; i <= 2; i++) { - PrimaryKeyAsString obj = realm.createObject(PrimaryKeyAsString.class); + PrimaryKeyAsString obj = realm.createObject(PrimaryKeyAsString.class, "String" + i); obj.setId(1); // Create duplicate values - obj.setName("String" + i); } Table table = realm.getTable(PrimaryKeyAsString.class); @@ -139,8 +137,7 @@ public void primaryKey_migration_longDuplicateValues() { public void primaryKey_migration_string() { realm.beginTransaction(); for (int i = 1; i <= 2; i++) { - PrimaryKeyAsLong obj = realm.createObject(PrimaryKeyAsLong.class); - obj.setId(i); + PrimaryKeyAsLong obj = realm.createObject(PrimaryKeyAsLong.class, i); obj.setName("String" + i); } @@ -155,8 +152,7 @@ public void primaryKey_migration_string() { public void primaryKey_migration_stringDuplicateValues() { realm.beginTransaction(); for (int i = 1; i <= 2; i++) { - PrimaryKeyAsLong obj = realm.createObject(PrimaryKeyAsLong.class); - obj.setId(i); + PrimaryKeyAsLong obj = realm.createObject(PrimaryKeyAsLong.class, i); obj.setName("String"); // Create duplicate values } @@ -175,7 +171,7 @@ public void primaryKey_migration_stringDuplicateValues() { public void primaryKey_checkPrimaryKeyOnCreate() { realm.beginTransaction(); try { - realm.createObject(AnnotationTypes.class); + realm.createObject(AnnotationTypes.class, 0); fail("Two empty objects cannot be created on the same table if a primary key is defined"); } catch (RealmPrimaryKeyConstraintException ignored) { } finally { @@ -187,8 +183,7 @@ public void primaryKey_checkPrimaryKeyOnCreate() { @Test public void primaryKey_defaultStringValue() { realm.beginTransaction(); - PrimaryKeyAsString str = realm.createObject(PrimaryKeyAsString.class); - str.setName(""); + realm.createObject(PrimaryKeyAsString.class, ""); realm.commitTransaction(); } @@ -196,7 +191,7 @@ public void primaryKey_defaultStringValue() { @Test public void primaryKey_defaultLongValue() { realm.beginTransaction(); - PrimaryKeyAsLong str = realm.createObject(PrimaryKeyAsLong.class); + PrimaryKeyAsLong str = realm.createObject(PrimaryKeyAsLong.class, 0); str.setId(0); realm.commitTransaction(); } @@ -205,10 +200,8 @@ public void primaryKey_defaultLongValue() { public void primaryKey_errorOnInsertingSameObject() { try { realm.beginTransaction(); - AnnotationTypes obj1 = realm.createObject(AnnotationTypes.class); - obj1.setId(1); - AnnotationTypes obj2 = realm.createObject(AnnotationTypes.class); - obj2.setId(1); + realm.createObject(AnnotationTypes.class, 1); + realm.createObject(AnnotationTypes.class, 1); fail("Inserting two objects with same primary key should fail"); } catch (RealmPrimaryKeyConstraintException ignored) { } finally { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java index 23f08c5aec..b058f3357a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java @@ -127,7 +127,7 @@ public void onChange(RealmResults result) { }); realm.beginTransaction(); - AllTypesRealmModel model = realm.createObject(AllTypesRealmModel.class); + AllTypesRealmModel model = realm.createObject(AllTypesRealmModel.class, 0); model.columnString = "data 1"; realm.commitTransaction(); } @@ -160,7 +160,7 @@ public void onChange(Cat object) { public void returnedRealmModelIsNotNull() { Realm realm = looperThread.realm; realm.beginTransaction(); - AllTypesRealmModel model = realm.createObject(AllTypesRealmModel.class); + AllTypesRealmModel model = realm.createObject(AllTypesRealmModel.class, 0); realm.commitTransaction(); looperThread.keepStrongReference.add(model); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java index 4daa4e751c..1fcc0c64df 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java @@ -137,7 +137,7 @@ private OrderedRealmCollection createEmptyCollection(Realm realm, Col switch (collectionClass) { case MANAGED_REALMLIST: realm.beginTransaction(); - NullTypes obj = realm.createObject(NullTypes.class); + NullTypes obj = realm.createObject(NullTypes.class, 0); realm.commitTransaction(); return obj.getFieldListNull(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index ccfacdc351..cbba8cc04c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -515,9 +515,9 @@ private void createObjectsWithOldPrimaryKey(final String className, final boolea realm.executeTransaction(new DynamicRealm.Transaction() { @Override public void execute(DynamicRealm realm) { - realm.createObject(className).setString(MigrationPrimaryKey.FIELD_PRIMARY, "12"); + realm.createObject(className, "12"); if (insertNullValue) { - realm.createObject(className).setString(MigrationPrimaryKey.FIELD_PRIMARY, null); + realm.createObject(className, null); } } }); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 7c64ba48cd..f704c2d817 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -971,7 +971,7 @@ public void isValid_managedObject() { @Test public void set_get_nullOnNullableFields() { realm.beginTransaction(); - NullTypes nullTypes = realm.createObject(NullTypes.class); + NullTypes nullTypes = realm.createObject(NullTypes.class, 0); // 1 String nullTypes.setFieldStringNull(null); // 2 Bytes @@ -1024,7 +1024,7 @@ public void get_set_nonNullValueOnNullableFields() { final byte[] testBytes = new byte[] {42}; final Date testDate = newDate(2000, 1, 1); realm.beginTransaction(); - NullTypes nullTypes = realm.createObject(NullTypes.class); + NullTypes nullTypes = realm.createObject(NullTypes.class, 0); // 1 String nullTypes.setFieldStringNull(testString); // 2 Bytes @@ -1075,7 +1075,7 @@ public void get_set_nonNullValueOnNullableFields() { public void set_nullValuesToNonNullableFields() { try { realm.beginTransaction(); - NullTypes nullTypes = realm.createObject(NullTypes.class); + NullTypes nullTypes = realm.createObject(NullTypes.class, 0); // 1 String try { nullTypes.setFieldStringNotNull(null); @@ -1144,7 +1144,7 @@ public void set_nullValuesToNonNullableFields() { @Test public void defaultValuesForNewObject() { realm.beginTransaction(); - NullTypes nullTypes = realm.createObject(NullTypes.class); + NullTypes nullTypes = realm.createObject(NullTypes.class, 0); realm.commitTransaction(); assertNotNull(nullTypes); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 75e05b4340..8cf83d61a9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2547,8 +2547,7 @@ public void execute(Realm realm) { // Crash with i == 1000, 500, 100, 89, 85, 84 // Doesn't crash for i == 10, 50, 75, 82, 83 for (int i = 0; i < 84; i++) { - AllJavaTypes obj = realm.createObject(AllJavaTypes.class); - obj.setFieldLong(i + 1); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, i + 1); obj.setFieldBoolean(i % 2 == 0); obj.setFieldObject(obj); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 02679eb1e6..e15d495a79 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -1243,7 +1243,7 @@ public void copyToRealm_convertsNullToDefaultValue() { @Test public void copyToRealm_primaryKeyIsSetDirectly() { realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class); + realm.createObject(OwnerPrimaryKey.class, 0); realm.copyToRealm(new OwnerPrimaryKey(1, "Foo")); realm.commitTransaction(); assertEquals(2, realm.where(OwnerPrimaryKey.class).count()); @@ -1323,9 +1323,8 @@ public void copyToRealm_doNotCopyReferencedObjectIfManaged() { realm.beginTransaction(); // Child object is managed by Realm - CyclicTypePrimaryKey childObj = realm.createObject(CyclicTypePrimaryKey.class); + CyclicTypePrimaryKey childObj = realm.createObject(CyclicTypePrimaryKey.class, 1); childObj.setName("Child"); - childObj.setId(1); // Parent object is an unmanaged object CyclicTypePrimaryKey parentObj = new CyclicTypePrimaryKey(2); @@ -1737,7 +1736,7 @@ public void copyToRealmOrUpdate_objectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); realm.beginTransaction(); - final OwnerPrimaryKey ownerPrimaryKey = realm.createObject(OwnerPrimaryKey.class); + final OwnerPrimaryKey ownerPrimaryKey = realm.createObject(OwnerPrimaryKey.class, 0); realm.commitTransaction(); new Thread(new Runnable() { @@ -1949,8 +1948,7 @@ public void setter_updateField() throws Exception { realm.beginTransaction(); // Create an owner with two dogs - OwnerPrimaryKey owner = realm.createObject(OwnerPrimaryKey.class); - owner.setId(1); + OwnerPrimaryKey owner = realm.createObject(OwnerPrimaryKey.class, 1); owner.setName("Jack"); Dog rex = realm.createObject(Dog.class); rex.setName("Rex"); From cda77fce53c22bff31a6ce567abc6dbeef3029d9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 3 Sep 2016 14:04:26 +0200 Subject: [PATCH 0021/2110] Fix logging --- .../realm-library/src/main/cpp/io_realm_sync_SyncManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_sync_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_sync_SyncManager.cpp index d47dca5c90..50e04ad5ad 100644 --- a/realm/realm-library/src/main/cpp/io_realm_sync_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_sync_SyncManager.cpp @@ -54,7 +54,7 @@ JNIEnv* sync_client_env; JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateClient (JNIEnv *env, jclass) { - TR_ENTER() + TR_ENTER(env) try { AndroidLogger* base_logger = new AndroidLogger();//FIXME find a way to delete it when we delete the client @@ -83,7 +83,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateClient JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateSession (JNIEnv *env, jclass, jlong clientPointer, jstring realmPath, jstring serverUrl, jstring userToken) { - TR_ENTER() + TR_ENTER(env) Client* sync_client = SC(clientPointer); if (sync_client == NULL) { return 0; From 43e2e6ad1348718cf6e8d2d55c5d79f80b39fc64 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 5 Sep 2016 11:53:52 +0900 Subject: [PATCH 0022/2110] fix Javadoc documentation about encryption key length (64 bit -> 64 bytes) (#3390) fixes #3389 --- .../src/main/java/io/realm/RealmConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 4f7a8c031a..10523e62e1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -443,7 +443,7 @@ public Builder name(String filename) { } /** - * Sets the 64 bit key used to encrypt and decrypt the Realm file. + * Sets the {@value io.realm.RealmConfiguration#KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file. */ public Builder encryptionKey(byte[] key) { if (key == null) { From 65ee5a2014c7fe71b10542c9726495e83a899bfb Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 5 Sep 2016 14:30:00 +0900 Subject: [PATCH 0023/2110] transform accesses to other model fields in model's constructors. (#3392) transform access to other model fields in model's constructors. fixes #3361 --- CHANGELOG.md | 1 + .../realm/transformer/BytecodeModifier.groovy | 32 +++++-- .../transformer/BytecodeModifierTest.groovy | 92 ++++++++++++++----- 3 files changed, 96 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b60993f3..2c80af7c89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Fixed a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11 (#3356). * Fixed a potential crash when there were lots of async queries waiting in the queue. +* Fixed a bug causing the Realm Transformer to not transform field access in the model's constructors (#3361). ## 1.2.0 diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index b927ef5b38..b9e388c828 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -66,11 +66,10 @@ class BytecodeModifier { !behavior.name.startsWith('realmGet$') && !behavior.name.startsWith('realmSet$') ) || ( - behavior instanceof CtConstructor && - !modelClasses.contains(clazz) + behavior instanceof CtConstructor ) ) { - behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior)) + behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior, modelClasses.contains(clazz))) } } } @@ -94,11 +93,18 @@ class BytecodeModifier { final List managedFields final CtClass ctClass final CtBehavior behavior + final boolean isModelClass + final boolean isInConstructor - FieldAccessToAccessorConverter(List managedFields, CtClass ctClass, CtBehavior behavior) { + FieldAccessToAccessorConverter(List managedFields, + CtClass ctClass, + CtBehavior behavior, + boolean isModelClass) { this.managedFields = managedFields this.ctClass = ctClass this.behavior = behavior + this.isModelClass = isModelClass + this.isInConstructor = behavior instanceof CtConstructor } @Override @@ -112,9 +118,23 @@ class BytecodeModifier { logger.info " Methods: ${ctClass.declaredMethods}" def fieldName = fieldAccess.fieldName if (fieldAccess.isReader()) { - fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') + if (isInConstructor && isModelClass) { + // work around https://github.com/realm/realm-java/issues/2536 + // '$0' is the object that owns target field. + // 'this' is the instance where the constructor belongs. + fieldAccess.replace('$_ = ($0 == this) ? $0.' + fieldName + ' : $0.realmGet$' + fieldName + '();') + } else { + fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') + } } else if (fieldAccess.isWriter()) { - fieldAccess.replace('$0.realmSet$' + fieldName + '($1);') + if (isInConstructor && isModelClass) { + // work around https://github.com/realm/realm-java/issues/2536 + // '$0' is the object that owns target field. + // 'this' is the instance where the constructor belongs. + fieldAccess.replace('if ($0 == this) {$0.' + fieldName + ' = $1;} else { $0.realmSet$' + fieldName + '($1);}') + } else { + fieldAccess.replace('$0.realmSet$' + fieldName + '($1);') + } } } } diff --git a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy index 65609cd5df..6750f2b273 100644 --- a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy +++ b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy @@ -79,7 +79,7 @@ class BytecodeModifierTest extends Specification { def "UseRealmAccessors"() { setup: 'generate an empty class' def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('testClass') + def ctClass = classPool.makeClass('TestClass') and: 'add a field' def ctField = new CtField(CtClass.intType, 'age', ctClass) @@ -95,21 +95,11 @@ class BytecodeModifierTest extends Specification { when: 'the field use is replaced by the accessor' BytecodeModifier.useRealmAccessors(ctClass, [ctField], []) - then: 'the field is not used in the method anymore' - def methodInfo = ctMethod.getMethodInfo() - def codeAttribute = methodInfo.getCodeAttribute() - def fieldIsUsed = false - for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { - int index = ci.next(); - int op = ci.byteAt(index); - if (op == Opcode.GETFIELD) { - fieldIsUsed = true - } - } - !fieldIsUsed + then: 'the field is not used and getter is called in the method ' + !isFieldRead(ctMethod) && hasMethodCall(ctMethod) } - def "UseRealmAccessorsInNonDefaultConstructor"() { + def "UseRealmAccessors_fieldAccessInModelConstructorIsTransformed"() { setup: 'generate an empty class' def classPool = ClassPool.getDefault() def ctClass = classPool.makeClass('TestClass') @@ -122,27 +112,83 @@ class BytecodeModifierTest extends Specification { def ctMethod = CtNewMethod.make('private void setupAge(int age) { this.age = age; }', ctClass) ctClass.addMethod(ctMethod) - and: 'add a constructor that uses the method' - def ctConstructor = CtNewConstructor.make('public TestClass(int age) { setupAge(age); }', ctClass) - ctClass.addConstructor(ctConstructor) + and: 'add a default constructor that uses the method' + def ctDefaultConstructor = CtNewConstructor.make('public TestClass() { int myAge = this.age; }', ctClass) + ctClass.addConstructor(ctDefaultConstructor) + + and: 'add a non-default constructor that uses the method' + def ctNonDefaultConstructor = CtNewConstructor.make('public TestClass(TestClass other) { int otherAge = other.age; }', ctClass) + ctClass.addConstructor(ctNonDefaultConstructor) and: 'realm accessors are added' BytecodeModifier.addRealmAccessors(ctClass) when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(ctClass, [ctField], []) + BytecodeModifier.useRealmAccessors(ctClass, [ctField], [ctClass]) + + then: 'the field is still used and also getter is called in the constructor' + // to work around https://github.com/realm/realm-java/issues/2536 , field access is not removed + isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && + isFieldRead(ctNonDefaultConstructor) && hasMethodCall(ctNonDefaultConstructor) + } + + def "UseRealmAccessors_fieldAccessInNonModelConstructorIsTransformed"() { + setup: 'generate an empty class' + def classPool = ClassPool.getDefault() + def ctClass = classPool.makeClass('TestClass') + + and: 'add a field' + def ctField = new CtField(CtClass.intType, 'age', ctClass) + ctClass.addField(ctField) + + and: 'add a method that sets such field' + def ctMethod = CtNewMethod.make('private void setupAge(int age) { this.age = age; }', ctClass) + ctClass.addMethod(ctMethod) + + and: 'add a default constructor that uses the method' + def ctDefaultConstructor = CtNewConstructor.make('public TestClass() { int myAge = this.age; }', ctClass) + ctClass.addConstructor(ctDefaultConstructor) + + and: 'add a non-default constructor that uses the method' + def ctNonDefaultConstructor = CtNewConstructor.make('public TestClass(TestClass other) { int otherAge = other.age; }', ctClass) + ctClass.addConstructor(ctNonDefaultConstructor) + + and: 'realm accessors are added' + BytecodeModifier.addRealmAccessors(ctClass) + + when: 'the field use is replaced by the accessor' + BytecodeModifier.useRealmAccessors(ctClass, [ctField], [/* no ctClass in model class list*/]) then: 'the field is not used in the method anymore' - def methodInfo = ctMethod.getMethodInfo() + !isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && + !isFieldRead(ctNonDefaultConstructor) && hasMethodCall(ctNonDefaultConstructor) + } + + private static def isFieldRead(CtBehavior behavior) { + def methodInfo = behavior.getMethodInfo() def codeAttribute = methodInfo.getCodeAttribute() - def fieldIsUsed = false + + for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { + int index = ci.next(); + int op = ci.byteAt(index); + if (op == Opcode.GETFIELD) { + return true + } + } + return false + } + + private static def hasMethodCall(CtBehavior behavior) { + def methodInfo = behavior.getMethodInfo() + def codeAttribute = methodInfo.getCodeAttribute() + for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { int index = ci.next(); int op = ci.byteAt(index); - if (op == Opcode.PUTFIELD) { - fieldIsUsed = true + if (op == Opcode.INVOKEVIRTUAL) { + return true } } - !fieldIsUsed + return false } } From dda715900232b3dd0a77a49b3ee1eb8bed572e6c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 5 Sep 2016 15:56:48 +0800 Subject: [PATCH 0024/2110] Create object without setting PK is not allowed (#3379) * Create object without setting PK is not allowed * createObject must be called with PK value when creating a object with PK defined. * Creating objects from JSON must have have corresponding PK defined in the JSON object. Known issue: The default values for creating object from JSONStream will be differnt from those created by createObject. Default values from default constructor VS default values from core. This has to be addressed by #777 --- CHANGELOG.md | 6 + .../java/io/realm/processor/Constants.java | 2 + .../realm/processor/RealmJsonTypeHelper.java | 24 ++- .../processor/RealmProxyClassGenerator.java | 17 +- .../io/realm/AllTypesRealmProxy.java | 11 +- .../io/realm/BooleansRealmProxy.java | 3 +- .../io/realm/NullTypesRealmProxy.java | 3 +- .../resources/io/realm/SimpleRealmProxy.java | 3 +- .../java/io/realm/DynamicRealmTests.java | 7 + .../realm/RealmJsonAbsentPrimaryKeyTests.java | 161 ++++++++++++++++++ .../realm/RealmJsonNullPrimaryKeyTests.java | 22 +-- .../java/io/realm/RealmJsonTests.java | 9 +- .../java/io/realm/RealmModelTests.java | 3 +- .../androidTest/java/io/realm/RealmTests.java | 6 + .../java/io/realm/internal/JNITableTest.java | 1 - .../src/main/java/io/realm/DynamicRealm.java | 5 + .../src/main/java/io/realm/Realm.java | 77 +++++---- .../main/java/io/realm/internal/Table.java | 31 ++-- 18 files changed, 305 insertions(+), 86 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 79f359596d..e88c2b114b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 2.0.0 +### Known issues + +* When creating a `RealmObject` from a JSON stream, it will take the default values defined by its default constructor for those fields that are not defined in the JSON object. This behaviour is different from other APIs when creating `RealmObject`s. + ### Breaking Changes * `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). @@ -9,6 +13,8 @@ - `RealmIOExcpetion` has been removed and replaced by `RealmFileException`. * Removed `RealmConfiguration.Builder(Context, File)` and `RealmConfiguration.Builder(File)` constructors. * `RealmConfiguration.Builder.assetFile(Context, String)` has been renamed to `RealmConfiguration.Builder.assetFile(String)`. +* Object with primary key is now required to define it when the object is created. This means that `Realm.createObject(Class)` and `DynamicRealm.createObject(String)` now throws `RealmException` if they are used to create an object with a primary key field. Use `Realm.createObject(Class, Object)` or `DynamicRealm.createObject(String, Object)` instead. +* Importing from JSON without the primary key field defined in the JSON object now throws `IllegalArgumentException`. ### Enhancements diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java index 68b2f11ca9..592aec97b3 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java @@ -28,6 +28,8 @@ public class Constants { public static final String DEFAULT_MODULE_CLASS_NAME = "DefaultRealmModule"; static final String STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE = "throw new IllegalArgumentException(\"Trying to set non-nullable field '%s' to null.\")"; + static final String STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON = + "throw new IllegalArgumentException(\"JSON object doesn't have the primary key field '%s'.\")"; static final Map JAVA_TO_REALM_TYPES; static { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java index cd63ab09a3..c54e00e149 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java @@ -68,7 +68,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String - fieldType, JavaWriter writer) + fieldType, JavaWriter writer, boolean isPrimaryKey) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") @@ -110,7 +110,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String - fieldType, JavaWriter writer) + fieldType, JavaWriter writer, boolean isPrimaryKey) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") @@ -183,11 +183,16 @@ public static void emitFillRealmListWithJsonValue(String interfaceName, String g } - public static void emitFillJavaTypeFromStream(String interfaceName, String setter, String fieldName, String + public static void emitFillJavaTypeFromStream(String interfaceName, ClassMetaData metaData, String fieldName, String fieldType, JavaWriter writer) throws IOException { + String setter = metaData.getSetter(fieldName); + boolean isPrimaryKey = false; + if (metaData.hasPrimaryKey() && metaData.getPrimaryKey().getSimpleName().toString().equals(fieldName)) { + isPrimaryKey = true; + } if (JAVA_TO_JSON_TYPES.containsKey(fieldType)) { JAVA_TO_JSON_TYPES.get(fieldType).emitStreamTypeConversion(interfaceName, setter, fieldName, fieldType, - writer); + writer, isPrimaryKey); } } @@ -211,6 +216,7 @@ public static void emitFillRealmListFromStream(String interfaceName, String gett .emitStatement("reader.skipValue()") .emitStatement("((%s) obj).%s(null)", interfaceName, setter) .nextControlFlow("else") + .emitStatement("((%s) obj).%s(new RealmList<%s>())", interfaceName, setter, fieldTypeCanonicalName) .emitStatement("reader.beginArray()") .beginControlFlow("while (reader.hasNext())") .emitStatement("%s item = %s.createUsingJsonStream(realm, reader)", fieldTypeCanonicalName, proxyClass) @@ -264,7 +270,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer) + JavaWriter writer, boolean isPrimaryKey) throws IOException { String statementSetNullOrThrow; if (Utils.isPrimitiveType(fieldType)) { @@ -281,6 +287,9 @@ public void emitStreamTypeConversion(String interfaceName, String setter, String .nextControlFlow("else") .emitStatement("((%s) obj).%s((%s) reader.next%s())", interfaceName, setter, castType, jsonType) .endControlFlow(); + if (isPrimaryKey) { + writer.emitStatement("jsonHasPrimaryKey = true"); + } } @Override @@ -299,8 +308,7 @@ public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass, jsonType, fieldName) .endControlFlow() .nextControlFlow("else") - .emitStatement("obj = (%1$s) realm.createObject(%2$s.class)", - qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass) + .emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) .endControlFlow(); } } @@ -309,7 +317,7 @@ private interface JsonToRealmFieldTypeConverter { void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, JavaWriter writer) throws IOException; void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer) throws IOException; + JavaWriter writer, boolean isPrimaryKey) throws IOException; void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) throws IOException; diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 8d1e445e09..83c2d43e9a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -1553,6 +1553,10 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep writer.emitEmptyLine(); } + // FIXME: Since we need to check the PK in stream before create an object, this is now using copyToRealm instead of + // createObject() to avoid parse the stream twice. This brings a problem that the default value behaviour is + // different from those which are using the createObject. And it needs to be addressed by + // https://github.com/realm/realm-java/issues/777 private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.emitAnnotation("TargetApi", "Build.VERSION_CODES.HONEYCOMB"); @@ -1563,7 +1567,10 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { Arrays.asList("Realm", "realm", "JsonReader", "reader"), Collections.singletonList("IOException")); - writer.emitStatement("%s obj = realm.createObject(%s.class)",qualifiedClassName, qualifiedClassName); + if (metadata.hasPrimaryKey()) { + writer.emitStatement("boolean jsonHasPrimaryKey = false"); + } + writer.emitStatement("%s obj = new %s()", qualifiedClassName, qualifiedClassName); writer.emitStatement("reader.beginObject()"); writer.beginControlFlow("while (reader.hasNext())"); writer.emitStatement("String name = reader.nextName()"); @@ -1601,7 +1608,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { } else { RealmJsonTypeHelper.emitFillJavaTypeFromStream( interfaceName, - metadata.getSetter(fieldName), + metadata, fieldName, qualifiedFieldType, writer @@ -1616,6 +1623,12 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { } writer.endControlFlow(); writer.emitStatement("reader.endObject()"); + if (metadata.hasPrimaryKey()) { + writer.beginControlFlow("if (!jsonHasPrimaryKey)"); + writer.emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.getPrimaryKey()); + writer.endControlFlow(); + } + writer.emitStatement("obj = realm.copyToRealm(obj)"); writer.emitStatement("return obj"); writer.endMethod(); writer.emitEmptyLine(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index bdec992709..cca4d4a861 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -411,7 +411,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON obj = (io.realm.AllTypesRealmProxy) realm.createObject(some.test.AllTypes.class, json.getString("columnString")); } } else { - obj = (io.realm.AllTypesRealmProxy) realm.createObject(some.test.AllTypes.class); + throw new IllegalArgumentException("JSON object doesn't have the primary key field 'columnString'."); } } if (json.has("columnString")) { @@ -495,7 +495,8 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.AllTypes obj = realm.createObject(some.test.AllTypes.class); + boolean jsonHasPrimaryKey = false; + some.test.AllTypes obj = new some.test.AllTypes(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -506,6 +507,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } else { ((AllTypesRealmProxyInterface) obj).realmSet$columnString((String) reader.nextString()); } + jsonHasPrimaryKey = true; } else if (name.equals("columnLong")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); @@ -566,6 +568,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r reader.skipValue(); ((AllTypesRealmProxyInterface) obj).realmSet$columnRealmList(null); } else { + ((AllTypesRealmProxyInterface) obj).realmSet$columnRealmList(new RealmList()); reader.beginArray(); while (reader.hasNext()) { some.test.AllTypes item = AllTypesRealmProxy.createUsingJsonStream(realm, reader); @@ -578,6 +581,10 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } } reader.endObject(); + if (!jsonHasPrimaryKey) { + throw new IllegalArgumentException("JSON object doesn't have the primary key field 'columnString'."); + } + obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index f04ecb0b32..6f555eb3f6 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -230,7 +230,7 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.Booleans obj = realm.createObject(some.test.Booleans.class); + some.test.Booleans obj = new some.test.Booleans(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -267,6 +267,7 @@ public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader r } } reader.endObject(); + obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 9500a86e0c..e4faa4691d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -915,7 +915,7 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.NullTypes obj = realm.createObject(some.test.NullTypes.class); + some.test.NullTypes obj = new some.test.NullTypes(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -1082,6 +1082,7 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader } } reader.endObject(); + obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index b4b5aca12a..cf863899ca 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -168,7 +168,7 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.Simple obj = realm.createObject(some.test.Simple.class); + some.test.Simple obj = new some.test.Simple(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -191,6 +191,7 @@ public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader rea } } reader.endObject(); + obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 70a8ff2494..9821c20c12 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -40,6 +40,7 @@ import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; +import io.realm.exceptions.RealmException; import io.realm.internal.HandlerControllerConstants; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; @@ -233,6 +234,12 @@ public void createObject_illegalPrimaryKeyValue() { realm.createObject(DogPrimaryKey.CLASS_NAME, "bar"); } + @Test(expected = RealmException.class) + public void createObject_absentPrimaryKeyThrows() { + realm.beginTransaction(); + realm.createObject(DogPrimaryKey.CLASS_NAME); + } + @Test public void where() { realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java new file mode 100644 index 0000000000..c985be7e62 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.util.Arrays; + +import io.realm.entities.PrimaryKeyAsBoxedByte; +import io.realm.entities.PrimaryKeyAsBoxedInteger; +import io.realm.entities.PrimaryKeyAsBoxedLong; +import io.realm.entities.PrimaryKeyAsBoxedShort; +import io.realm.entities.PrimaryKeyAsString; +import io.realm.rule.TestRealmConfigurationFactory; + +@RunWith(Parameterized.class) +public class RealmJsonAbsentPrimaryKeyTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + protected Realm realm; + + @Before + public void setUp() { + RealmConfiguration realmConfig = configFactory.createConfiguration(); + realm = Realm.getInstance(realmConfig); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + // parameters for testing absent primary key value. PrimaryKey field is absent. + @Parameterized.Parameters + public static Iterable data() { + return Arrays.asList(new Object[][]{ + {PrimaryKeyAsBoxedByte.class, "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, + {PrimaryKeyAsBoxedShort.class, "{ \"name\":\"KeyValueTestIsFun\" }"}, + {PrimaryKeyAsBoxedInteger.class, "{ \"name\":\"FunValueTestIsKey\" }"}, + {PrimaryKeyAsBoxedLong.class, "{ \"name\":\"NameAsBoxedLong-!\" }"}, + {PrimaryKeyAsString.class, "{ \"id\":2429214 }"} + }); + } + + final private Class clazz; + final private String jsonString; + + public RealmJsonAbsentPrimaryKeyTests(Class clazz, String jsonString) { + this.jsonString = jsonString; + this.clazz = clazz; + } + + // Testing absent primary key value for createObjectFromJson() + @Test + public void createObjectFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createObjectFromJson(clazz, new JSONObject(jsonString)); + realm.commitTransaction(); + } + + // Testing absent primary key value for createOrUpdateObjectFromJson() + @Test + public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createOrUpdateObjectFromJson(clazz, new JSONObject(jsonString)); + realm.commitTransaction(); + } + + // Testing absent primary key value for createAllFromJson() + @Test + public void createAllFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { + JSONArray jsonArray = new JSONArray(); + jsonArray.put(new JSONObject(jsonString)); + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createAllFromJson(clazz, jsonArray); + realm.commitTransaction(); + } + + // Testing absent primary key value for createOrUpdateAllFromJson() + @Test + public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { + JSONArray jsonArray = new JSONArray(); + jsonArray.put(new JSONObject(jsonString)); + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createOrUpdateAllFromJson(clazz, jsonArray); + realm.commitTransaction(); + } + + // Testing absent primary key value for createObjectFromJson() stream version + @Test + public void createObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createObjectFromJson(clazz, TestHelper.stringToStream(jsonString)); + realm.commitTransaction(); + } + + // Testing absent primary key value for createOrUpdateObjectFromJson() stream version + @Test + public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createOrUpdateObjectFromJson(clazz, TestHelper.stringToStream(jsonString)); + realm.commitTransaction(); + } + + // Testing absent primary key value for createAllFromJson() stream version + @Test + public void createAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + JSONArray jsonArray = new JSONArray(); + jsonArray.put(new JSONObject(jsonString)); + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createAllFromJson(clazz, TestHelper.stringToStream(jsonArray.toString())); + realm.commitTransaction(); + } + + // Testing absent primary key value for createOrUpdateAllFromJson() stream version + @Test + public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + JSONArray jsonArray = new JSONArray(); + jsonArray.put(new JSONObject(jsonString)); + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createOrUpdateAllFromJson(clazz, TestHelper.stringToStream(jsonArray.toString())); + realm.commitTransaction(); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java index c5482eb151..f0345f07bd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java @@ -57,7 +57,7 @@ public void tearDown() { } } - // parameters for testing null primary key value. PrimaryKey field is explicitly null or absent. + // parameters for testing null primary key value. PrimaryKey field is explicitly null @Parameterized.Parameters public static Iterable data() { return Arrays.asList(new Object[][]{ @@ -65,12 +65,7 @@ public static Iterable data() { {PrimaryKeyAsBoxedShort.class, "YouBetItIsNullKey", "{ \"id\":null, \"name\":\"YouBetItIsNullKey\" }"}, {PrimaryKeyAsBoxedInteger.class, "Gosh Didnt KnowIt", "{ \"id\":null, \"name\":\"Gosh Didnt KnowIt\" }"}, {PrimaryKeyAsBoxedLong.class, "?YOUNOWKNOWRIGHT?", "{ \"id\":null, \"name\":\"?YOUNOWKNOWRIGHT?\" }"}, - {PrimaryKeyAsBoxedByte.class, "HaHaHaHaHaHaHaHaH", "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, - {PrimaryKeyAsBoxedShort.class, "KeyValueTestIsFun", "{ \"name\":\"KeyValueTestIsFun\" }"}, - {PrimaryKeyAsBoxedInteger.class, "FunValueTestIsKey", "{ \"name\":\"FunValueTestIsKey\" }"}, - {PrimaryKeyAsBoxedLong.class, "NameAsBoxedLong-!", "{ \"name\":\"NameAsBoxedLong-!\" }"}, {PrimaryKeyAsString.class, "4299121", "{ \"name\":null, \"id\":4299121 }"}, - {PrimaryKeyAsString.class, "2429214", "{ \"id\":2429214 }"} }); } @@ -84,9 +79,9 @@ public RealmJsonNullPrimaryKeyTests(Class clazz, String s this.clazz = clazz; } - // Testing null or absent primary key value for createObjectFromJson() + // Testing null primary key value for createObjectFromJson() @Test - public void createObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObject() throws JSONException { + public void createObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONException { realm.beginTransaction(); realm.createObjectFromJson(clazz, new JSONObject(jsonString)); realm.commitTransaction(); @@ -107,9 +102,9 @@ public void createObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObject() thro } } - // Testing null or absent primary key value for createOrUpdateObjectFromJson() + // Testing null primary key value for createOrUpdateObjectFromJson() @Test - public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObject() throws JSONException { + public void createOrUpdateObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONException { realm.beginTransaction(); realm.createOrUpdateObjectFromJson(clazz, new JSONObject(jsonString)); realm.commitTransaction(); @@ -130,11 +125,11 @@ public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObjec } } - // Testing null or absent primary key value for createObject() -> createOrUpdateObjectFromJson() + // Testing null primary key value for createObject() -> createOrUpdateObjectFromJson() @Test - public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_updateFromJsonObject() throws JSONException { + public void createOrUpdateObjectFromJson_primaryKey_isNull_updateFromJsonObject() throws JSONException { realm.beginTransaction(); - realm.createObject(clazz); // name = null, id = 0 + realm.createObject(clazz, null); // name = null, id =null realm.createOrUpdateObjectFromJson(clazz, new JSONObject(jsonString)); realm.commitTransaction(); @@ -144,7 +139,6 @@ public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_updateFromJso assertEquals(1, results.size()); assertEquals(Long.valueOf(secondaryFieldValue).longValue(), results.first().getId()); assertEquals(null, results.first().getName()); - // PrimaryKeyAsNumber } else { RealmResults results = realm.where(clazz).findAll(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index fbcc2fdcf8..1e697d5f43 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -409,6 +409,7 @@ public void createObjectFromJson_jsonException() throws JSONException { @Test public void createObjectFromJson_respectIgnoredFields() throws JSONException { JSONObject json = new JSONObject(); + json.put("id", 0); json.put("indexString", "Foo"); json.put("notIndexString", "Bar"); json.put("ignoreString", "Baz"); @@ -763,7 +764,7 @@ public void createOrUpdateObjectFromJson_inputStream() throws IOException { public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException { InputStream stream = TestHelper.stringToStream("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class); // id = 0 + realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 realm.createOrUpdateObjectFromJson(OwnerPrimaryKey.class, stream); realm.commitTransaction(); @@ -961,7 +962,7 @@ public void createOrUpdateObjectFromJson_invalidJsonObject() throws JSONExceptio public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromJsonObject() throws JSONException { JSONObject newObject = new JSONObject("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class); // id = 0 + realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 realm.createOrUpdateObjectFromJson(OwnerPrimaryKey.class, newObject); realm.commitTransaction(); @@ -1367,7 +1368,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromJsonObject() throws JSONException { JSONObject newObject = new JSONObject("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class); // id = 0 + realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 realm.createObjectFromJson(OwnerPrimaryKey.class, newObject); realm.commitTransaction(); @@ -1393,7 +1394,7 @@ public void createObjectFromJson_objectNullClass() throws JSONException { public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException { InputStream stream = TestHelper.stringToStream("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class); // id = 0 + realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 realm.createObjectFromJson(OwnerPrimaryKey.class, stream); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index d634c2a2da..b4f48e94fc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -101,8 +101,7 @@ public void createObject() { for (int i = 1; i < 43; i++) { // using i = 0 as PK will crash subsequent createObject // since createObject uses default values realm.beginTransaction(); - AllTypesRealmModel allTypesRealmModel = realm.createObject(AllTypesRealmModel.class); - allTypesRealmModel.columnLong = i; + realm.createObject(AllTypesRealmModel.class, i); realm.commitTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 975b7aed87..d4bcf79876 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -2087,6 +2087,12 @@ public void createObject_cannotCreateDynamicRealmObject() { } } + @Test(expected = RealmException.class) + public void createObject_absentPrimaryKeyThrows() { + realm.beginTransaction(); + realm.createObject(DogPrimaryKey.class); + } + @Test public void createObjectWithPrimaryKey() { realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index b62c1c7ff6..5df16b658b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -28,7 +28,6 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmFieldType; -import io.realm.Sort; import io.realm.TestHelper; import io.realm.rule.TestRealmConfigurationFactory; diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 20671389cd..3445f9cac2 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -77,6 +77,11 @@ public static DynamicRealm getInstance(RealmConfiguration configuration) { public DynamicRealmObject createObject(String className) { checkIfValid(); Table table = schema.getTable(className); + // Check and throw the exception earlier for a better exception message. + if (table.hasPrimaryKey()) { + throw new RealmException(String.format("'%s' has a primary key, use" + + " 'createObject(String, Object)' instead.", className)); + } long rowIndex = table.addEmptyRow(); return get(DynamicRealmObject.class, className, rowIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 53f8ff4b55..b25b493486 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -296,13 +296,14 @@ private static void initializeRealm(Realm realm) { /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. *

- * JSON properties with {@code null} values will map to the default value for the data type in Realm and unknown properties - * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} - * field will be set to the default value for that type. + * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the + * JSON object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm objects to create. * @param json an array where each JSONObject must map to the specified class. * @throws RealmException if mapping from JSON fails. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. */ public void createAllFromJson(Class clazz, JSONArray json) { if (clazz == null || json == null) { @@ -327,8 +328,9 @@ public void createAllFromJson(Class clazz, JSONArray j * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json array with object data. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if unable to map JSON. * @see #createAllFromJson(Class, org.json.JSONArray) */ @@ -348,13 +350,14 @@ public void createOrUpdateAllFromJson(Class clazz, JSO /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. - * JSON properties with {@code null} values will map to the default value for the data type in Realm and unknown properties - * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field - * will be set to the default value for that type. + * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the + * JSON object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm objects to create. * @param json the JSON array as a String where each object can map to the specified class. * @throws RealmException if mapping from JSON fails. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. */ public void createAllFromJson(Class clazz, String json) { if (clazz == null || json == null || json.length() == 0) { @@ -380,9 +383,10 @@ public void createAllFromJson(Class clazz, String json * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json string with an array of JSON objects. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. * @throws RealmException if unable to create a JSON array from the json string. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @see #createAllFromJson(Class, String) */ public void createOrUpdateAllFromJson(Class clazz, String json) { @@ -403,13 +407,14 @@ public void createOrUpdateAllFromJson(Class clazz, Str /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. - * JSON properties with {@code null} value will map to the default value for the data type in Realm and unknown properties - * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field - * will be set to the default value for that type. + * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the + * JSON object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm objects created. * @param inputStream the JSON array as a InputStream. All objects in the array must be of the specified class. * @throws RealmException if mapping from JSON fails. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws IOException if something was wrong with the input stream. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) @@ -439,8 +444,9 @@ public void createAllFromJson(Class clazz, InputStream * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the InputStream with a list of object data in JSON format. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if unable to read JSON. * @see #createOrUpdateAllFromJson(Class, java.io.InputStream) */ @@ -471,14 +477,15 @@ public void createOrUpdateAllFromJson(Class clazz, Inp /** * Creates a Realm object pre-filled with data from a JSON object. This must be done inside a transaction. JSON - * properties with {@code null} values will map to the default value for the data type in Realm and unknown properties will - * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will - * be set to the default value for that type. + * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON + * object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm object to create. * @param json the JSONObject with object data. * @return created object or {@code null} if no JSON data was provided. * @throws RealmException if the mapping from JSON fails. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @see #createOrUpdateObjectFromJson(Class, org.json.JSONObject) */ public E createObjectFromJson(Class clazz, JSONObject json) { @@ -502,8 +509,9 @@ public E createObjectFromJson(Class clazz, JSONObject * @param clazz Type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json {@link org.json.JSONObject} with object data. * @return created or updated {@link io.realm.RealmObject}. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if JSON data cannot be mapped. * @see #createObjectFromJson(Class, org.json.JSONObject) */ @@ -522,14 +530,15 @@ public E createOrUpdateObjectFromJson(Class clazz, JSO /** * Creates a Realm object pre-filled with data from a JSON object. This must be done inside a transaction. JSON - * properties with {@code null} values will map to the default value for the data type in Realm and unknown properties will - * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will - * be set to the default value for that type. + * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON + * object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm object to create. * @param json the JSON string with object data. * @return created object or {@code null} if JSON string was empty or null. * @throws RealmException if mapping to json failed. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. */ public E createObjectFromJson(Class clazz, String json) { if (clazz == null || json == null || json.length() == 0) { @@ -556,8 +565,9 @@ public E createObjectFromJson(Class clazz, String json * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json string with object data in JSON format. * @return created or updated {@link io.realm.RealmObject}. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if JSON object cannot be mapped from the string parameter. * @see #createObjectFromJson(Class, String) */ @@ -579,14 +589,15 @@ public E createOrUpdateObjectFromJson(Class clazz, Str /** * Creates a Realm object pre-filled with data from a JSON object. This must be done inside a transaction. JSON - * properties with {@code null} value will map to the default value for the data type in Realm and unknown properties will - * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will - * be set to the default value for that type. + * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON + * object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm object to create. * @param inputStream the JSON object data as a InputStream. * @return created object or {@code null} if JSON string was empty or null. * @throws RealmException if the mapping from JSON failed. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws IOException if something went wrong with the input stream. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) @@ -633,8 +644,9 @@ public E createObjectFromJson(Class clazz, InputStream * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the {@link InputStream} with object data in JSON format. * @return created or updated {@link io.realm.RealmObject}. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if failure to read JSON. * @see #createObjectFromJson(Class, java.io.InputStream) */ @@ -675,6 +687,11 @@ private Scanner getFullStringScanner(InputStream in) { public E createObject(Class clazz) { checkIfValid(); Table table = schema.getTable(clazz); + // Check and throw the exception earlier for a better exception message. + if (table.hasPrimaryKey()) { + throw new RealmException(String.format("'%s' has a primary key, use" + + " 'createObject(Class, Object)' instead.", Table.tableNameToClassName(table.getName()))); + } long rowIndex = table.addEmptyRow(); return get(clazz, rowIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 5b3c1bab4a..8e4dd14de2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -19,7 +19,6 @@ import java.util.Date; import io.realm.RealmFieldType; -import io.realm.Sort; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; @@ -373,27 +372,16 @@ public void moveLastOver(long rowIndex) { nativeMoveLastOver(nativePtr, rowIndex); } + /** + * Add an empty row to the table which doesn't have a primary key defined. + *

+ * NOTE: To add a table with a primary key defined, use {@link #addEmptyRowWithPrimaryKey(Object)} instead. This + * won't check if this table has a primary key. + * + * @return row index. + */ public long addEmptyRow() { checkImmutable(); - if (hasPrimaryKey()) { - long primaryKeyColumnIndex = getPrimaryKey(); - RealmFieldType type = getColumnType(primaryKeyColumnIndex); - switch (type) { - case STRING: - if (findFirstString(primaryKeyColumnIndex, STRING_DEFAULT_VALUE) != NO_MATCH) { - throwDuplicatePrimaryKeyException(STRING_DEFAULT_VALUE); - } - break; - case INTEGER: - if (findFirstLong(primaryKeyColumnIndex, INTEGER_DEFAULT_VALUE) != NO_MATCH) { - throwDuplicatePrimaryKeyException(INTEGER_DEFAULT_VALUE); - } - break; - default: - throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); - } - } - return nativeAddEmptyRow(nativePtr, 1); } @@ -479,6 +467,9 @@ public long addEmptyRows(long rows) { * * @param values values. * @return the row index of the appended row. + * @deprecated Remove this functions since it doesn't seem to be useful. And this function does deal with tables + * withprimary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row + * added. */ protected long add(Object... values) { long rowIndex = addEmptyRow(); From ea82c605e022b1a71d663abcd721ca62eace9e58 Mon Sep 17 00:00:00 2001 From: Lars Grefer Date: Tue, 6 Sep 2016 08:55:17 +0200 Subject: [PATCH 0025/2110] Use https in javadoc links (#3380) --- realm/realm-library/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 87d8e41f0d..32416d0ab8 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -127,9 +127,9 @@ task javadoc(type: Javadoc) { locale = 'en_US' overview = 'src/overview.html' - links "http://docs.oracle.com/javase/7/docs/api/" + links "https://docs.oracle.com/javase/7/docs/api/" links "http://reactivex.io/RxJava/javadoc/" - linksOffline "http://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" + linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" } exclude '**/internal/**' exclude '**/BuildConfig.java' From 953bc209ca24274955094cb2520c6f0e0e604083 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Sep 2016 16:19:06 +0800 Subject: [PATCH 0026/2110] Add branch strategy to the CONTRIBUTING doc. (#3405) --- CONTRIBUTING.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 117abc2ef9..e25a9af5f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -182,3 +182,19 @@ public RealmQuery equalTo(String fieldName, String fieldValue, boolean caseSe Above is based on the official guidelines from Oracle regarding Javadoc: http://www.oracle.com/technetwork/articles/java/index-137868.html +### Branch Strategy + +We have two branches for shared development: `master` and `releases`. We make releases from each. + +`master`: + +* The `master` branch is where major/minor versions are released from. +* It is for new features and/or breaking changes. + +`releases`: + +* The releases branch is where patch versions are released from. +* It is mainly for bug fixes. +* Every commit is automatically merged to `master`. +* Minor changes (e.g. to documentation, tests, and the build system) may not affect end users but should still be merged to `releases` to avoid diverging too far from `master` and to reduce the likelihood of merge conflicts. + From ddc5a58f276525b784a5af4e857f90812baa790c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 8 Sep 2016 12:00:59 +0200 Subject: [PATCH 0027/2110] Public Sync API (#73) --- CHANGELOG.md | 1 + examples/build.gradle | 2 +- examples/encryptionExample/lint.xml | 1 + examples/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 4 +- examples/gridViewExample/lint.xml | 1 + examples/introExample/lint.xml | 1 + examples/jsonExample/lint.xml | 1 + examples/kotlinExample/lint.xml | 1 + examples/migrationExample/lint.xml | 1 + examples/moduleExample/app/build.gradle | 5 +- examples/moduleExample/app/lint.xml | 1 + examples/moduleExample/library/lint.xml | 1 + examples/newsreaderExample/lint.xml | 1 + examples/rxJavaExample/lint.xml | 1 + examples/threadExample/lint.xml | 1 + .../examples/threads/AsyncQueryFragment.java | 4 +- examples/unitTestExample/lint.xml | 1 + .../unittesting/ExampleActivityTest.java | 3 + gradle/wrapper/gradle-wrapper.properties | 2 +- realm/build.gradle | 2 +- .../realm-jni/src/io_realm_sync_SyncManager.h | 29 -- realm/realm-library/build.gradle | 1 + .../java/io/realm/RealmAsyncQueryTests.java | 2 - .../androidTest/java/io/realm/TestHelper.java | 1 - .../objectserver/SyncConfigurationTests.java | 266 +++++++++++ .../src/main/AndroidManifest.xml | 14 + .../realm-library/src/main/cpp/CMakeLists.txt | 3 +- .../cpp/io_realm_internal_SharedRealm.cpp | 16 + .../src/main/cpp/io_realm_internal_Util.cpp | 3 +- .../cpp/io_realm_objectserver_Session.cpp | 112 +++++ .../cpp/io_realm_objectserver_SyncManager.cpp | 114 +++++ .../main/cpp/io_realm_sync_SyncManager.cpp | 114 ----- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/cpp/objectserver_shared.hpp | 80 ++++ realm/realm-library/src/main/cpp/util.cpp | 4 +- realm/realm-library/src/main/cpp/util.hpp | 8 +- .../src/main/java/io/realm/BaseRealm.java | 6 +- .../main/java/io/realm/HandlerController.java | 1 - .../src/main/java/io/realm/Realm.java | 14 +- .../main/java/io/realm/RealmAsyncTask.java | 15 +- .../src/main/java/io/realm/RealmCache.java | 13 +- .../java/io/realm/RealmConfiguration.java | 95 +--- .../java/io/realm/internal/RealmCore.java | 2 + .../java/io/realm/internal/SharedRealm.java | 33 +- .../io/realm/internal/SyncSessionImpl.java | 29 -- .../src/main/java/io/realm/internal/Util.java | 66 +++ .../async/RealmThreadPoolExecutor.java | 10 + .../src/main/java/io/realm/log/RealmLog.java | 5 + .../objectserver/AuthenticatingState.java | 110 +++++ .../io/realm/objectserver/BindingState.java | 57 +++ .../io/realm/objectserver/BoundState.java | 108 +++++ .../io/realm/objectserver/Credentials.java | 163 +++++++ .../java/io/realm/objectserver/ErrorCode.java | 147 ++++++ .../java/io/realm/objectserver/FsmAction.java | 33 ++ .../java/io/realm/objectserver/FsmState.java | 86 ++++ .../io/realm/objectserver/InitialState.java | 44 ++ .../realm/objectserver/ObjectServerError.java | 80 ++++ .../java/io/realm/objectserver/Session.java | 349 ++++++++++++++ .../io/realm/objectserver/SessionState.java | 31 ++ .../io/realm/objectserver/StoppedState.java | 64 +++ .../realm/objectserver/SyncConfiguration.java | 442 ++++++++++++++++++ .../io/realm/objectserver/SyncManager.java | 134 ++++++ .../io/realm/objectserver/UnboundState.java | 49 ++ .../main/java/io/realm/objectserver/User.java | 338 ++++++++++++++ .../java/io/realm/objectserver/UserStore.java | 78 ++++ .../android/SharedPrefsUserStore.java | 158 +++++++ .../internal/ObjectServerFacade.java | 64 +++ .../objectserver/internal/SessionStore.java | 71 +++ .../io/realm/objectserver/internal/Token.java | 109 +++++ .../internal/network/AuthenticateRequest.java | 156 +++++++ .../network/AuthenticateResponse.java | 147 ++++++ .../network/AuthenticationServer.java | 35 ++ .../network/NetworkStateReceiver.java | 79 ++++ .../network/OkHttpAuthenticationServer.java | 85 ++++ .../internal/network/RefreshResponse.java | 48 ++ .../syncpolicy/AutomaticSyncPolicy.java | 69 +++ .../internal/syncpolicy/SyncPolicy.java | 84 ++++ .../java/io/realm/sync/ManualSyncPolicy.java | 8 - .../io/realm/sync/RealtimeSyncPolicy.java | 8 - .../java/io/realm/sync/SyncConfiguration.java | 36 -- .../main/java/io/realm/sync/SyncManager.java | 30 -- .../main/java/io/realm/sync/SyncPolicy.java | 5 - .../main/java/io/realm/sync/SyncSession.java | 6 - 84 files changed, 4219 insertions(+), 386 deletions(-) create mode 100644 examples/gradle.properties delete mode 100644 realm/realm-jni/src/io_realm_sync_SyncManager.h create mode 100644 realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp delete mode 100644 realm/realm-library/src/main/cpp/io_realm_sync_SyncManager.cpp create mode 100644 realm/realm-library/src/main/cpp/objectserver_shared.hpp delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/SyncSessionImpl.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/AuthenticatingState.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/BindingState.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/FsmAction.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/FsmState.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/InitialState.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/Session.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/StoppedState.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/UnboundState.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/User.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/NetworkStateReceiver.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/ManualSyncPolicy.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/RealtimeSyncPolicy.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/SyncConfiguration.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/SyncManager.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/SyncPolicy.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/SyncSession.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 79f359596d..9da928f1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ ### Internal * Updated Realm Core to 1.4.2. +* Improved sorting speed. ## 1.1.0 diff --git a/examples/build.gradle b/examples/build.gradle index 47be4e4ca1..c7ab4ca759 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -16,7 +16,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.0-rc1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' diff --git a/examples/encryptionExample/lint.xml b/examples/encryptionExample/lint.xml index 0666c5455c..6793b0702b 100644 --- a/examples/encryptionExample/lint.xml +++ b/examples/encryptionExample/lint.xml @@ -6,4 +6,5 @@ + diff --git a/examples/gradle.properties b/examples/gradle.properties new file mode 100644 index 0000000000..4a9594aeec --- /dev/null +++ b/examples/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx2048M \ No newline at end of file diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 4f6e35c077..63bd7405be 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 05 14:18:17 CET 2016 +#Tue Aug 23 09:07:37 CEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/examples/gridViewExample/lint.xml b/examples/gridViewExample/lint.xml index 3af2534ba6..6a9810cdcb 100644 --- a/examples/gridViewExample/lint.xml +++ b/examples/gridViewExample/lint.xml @@ -6,4 +6,5 @@ + diff --git a/examples/introExample/lint.xml b/examples/introExample/lint.xml index 3af2534ba6..6a9810cdcb 100644 --- a/examples/introExample/lint.xml +++ b/examples/introExample/lint.xml @@ -6,4 +6,5 @@ + diff --git a/examples/jsonExample/lint.xml b/examples/jsonExample/lint.xml index 6a7edc9890..a443370a1a 100644 --- a/examples/jsonExample/lint.xml +++ b/examples/jsonExample/lint.xml @@ -5,4 +5,5 @@ + diff --git a/examples/kotlinExample/lint.xml b/examples/kotlinExample/lint.xml index cc4d461aee..7d530f741e 100644 --- a/examples/kotlinExample/lint.xml +++ b/examples/kotlinExample/lint.xml @@ -5,4 +5,5 @@ + diff --git a/examples/migrationExample/lint.xml b/examples/migrationExample/lint.xml index 1d3dbb0011..1f5e37cb86 100644 --- a/examples/migrationExample/lint.xml +++ b/examples/migrationExample/lint.xml @@ -4,4 +4,5 @@ + diff --git a/examples/moduleExample/app/build.gradle b/examples/moduleExample/app/build.gradle index 957f094945..c8e16e60d3 100644 --- a/examples/moduleExample/app/build.gradle +++ b/examples/moduleExample/app/build.gradle @@ -25,9 +25,8 @@ android { buildTypes { release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), - 'proguard-rules.pro' + minifyEnabled false // FIXME Why is this suddenly broken? + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } } diff --git a/examples/moduleExample/app/lint.xml b/examples/moduleExample/app/lint.xml index 1d3dbb0011..1f5e37cb86 100644 --- a/examples/moduleExample/app/lint.xml +++ b/examples/moduleExample/app/lint.xml @@ -4,4 +4,5 @@ + diff --git a/examples/moduleExample/library/lint.xml b/examples/moduleExample/library/lint.xml index 3af2534ba6..6a9810cdcb 100644 --- a/examples/moduleExample/library/lint.xml +++ b/examples/moduleExample/library/lint.xml @@ -6,4 +6,5 @@ + diff --git a/examples/newsreaderExample/lint.xml b/examples/newsreaderExample/lint.xml index 1d3dbb0011..1f5e37cb86 100644 --- a/examples/newsreaderExample/lint.xml +++ b/examples/newsreaderExample/lint.xml @@ -4,4 +4,5 @@ + diff --git a/examples/rxJavaExample/lint.xml b/examples/rxJavaExample/lint.xml index cc4d461aee..7d530f741e 100644 --- a/examples/rxJavaExample/lint.xml +++ b/examples/rxJavaExample/lint.xml @@ -5,4 +5,5 @@ + diff --git a/examples/threadExample/lint.xml b/examples/threadExample/lint.xml index 3af2534ba6..6a9810cdcb 100644 --- a/examples/threadExample/lint.xml +++ b/examples/threadExample/lint.xml @@ -6,4 +6,5 @@ + diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java index 6d730bce93..8eafdfe4e5 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java @@ -178,7 +178,9 @@ public View getView(int i, View view, ViewGroup viewGroup) { view.setTag(viewHolder); } ViewHolder vh = (ViewHolder) view.getTag(); - vh.text.setText(view.getResources().getString(R.string.coordinate, getItem(i).getX(),getItem(i).getY())); + vh.text.setText(view.getResources().getString(R.string.coordinate, + Integer.toString(getItem(i).getX()), + Integer.toString(getItem(i).getY()))); return view; } diff --git a/examples/unitTestExample/lint.xml b/examples/unitTestExample/lint.xml index 1d3dbb0011..1f5e37cb86 100644 --- a/examples/unitTestExample/lint.xml +++ b/examples/unitTestExample/lint.xml @@ -4,4 +4,5 @@ + diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java index c4b4d8403c..cbe494678e 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java @@ -19,6 +19,7 @@ import android.content.Context; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -174,6 +175,7 @@ public void setup() throws Exception { } + @Ignore("FIXME: Some problems mocking OKHttp") @Test public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { doCallRealMethod().when(mockRealm).executeTransaction(Mockito.any(Realm.Transaction.class)); @@ -220,6 +222,7 @@ public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { * Have to verify the transaction execution in a different test because * of a problem with Powermock: https://github.com/jayway/powermock/issues/649 */ + @Ignore("FIXME: Some problems mocking OKHttp") @Test public void shouldBeAbleToVerifyTransactionCalls() { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f71002edb7..bca8323670 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Tue Jan 05 14:18:17 CET 2016 +#Wed Aug 24 21:03:36 CEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/realm/build.gradle b/realm/build.gradle index 13acda3bcb..6dfe2d069d 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0-beta2' + classpath 'com.android.tools.build:gradle:2.2.0-rc1' classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' diff --git a/realm/realm-jni/src/io_realm_sync_SyncManager.h b/realm/realm-jni/src/io_realm_sync_SyncManager.h deleted file mode 100644 index 71c3a2b3ce..0000000000 --- a/realm/realm-jni/src/io_realm_sync_SyncManager.h +++ /dev/null @@ -1,29 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class io_realm_sync_SyncManager */ - -#ifndef _Included_io_realm_sync_SyncManager -#define _Included_io_realm_sync_SyncManager -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: io_realm_sync_SyncManager - * Method: syncCreateClient - * Signature: ()J - */ -JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateClient - (JNIEnv *, jclass); - -/* - * Class: io_realm_sync_SyncManager - * Method: syncCreateSession - * Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateSession - (JNIEnv *, jclass, jlong, jstring, jstring, jstring); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index e56cd819ad..01678728ff 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -96,6 +96,7 @@ repositories { dependencies { provided 'io.reactivex:rxjava:1.1.0' compile "io.realm:realm-annotations:${version}" + compile 'com.squareup.okhttp3:okhttp:3.4.1' // Should be moved to a seperate library compile 'com.getkeepsafe.relinker:relinker:1.2.1' androidTestCompile 'io.reactivex:rxjava:1.1.0' diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 14f5b6f372..1536742e6a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -27,10 +27,8 @@ import org.junit.runner.RunWith; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Date; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.CountDownLatch; diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 9b2a5fe28c..fcb666fd02 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -62,7 +62,6 @@ import io.realm.log.Logger; import io.realm.rule.TestRealmConfigurationFactory; -import static android.R.id.message; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java new file mode 100644 index 0000000000..8d307badff --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java @@ -0,0 +1,266 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.Locale; +import java.util.UUID; + +import io.realm.objectserver.internal.Token; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.TestRealmConfigurationFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class SyncConfigurationTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + @Rule + public final TemporaryFolder tempFolder = new TemporaryFolder(); + + private Context context; + + @Before + public void setUp() { + context = InstrumentationRegistry.getContext(); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void user() { +// new SyncConfiguration.Builder(context); + // Check that user can be added + // That the default local path is correct + } + + @Test + public void user_invalidUserThrows() { + SyncConfiguration.Builder builder = new SyncConfiguration.Builder(context); + + try { + builder.user(null); + } catch (IllegalArgumentException ignore) { + } + + User user = createTestUser(0); // Create user that has expired credentials + try { + builder.user(user); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void serverUrl_setsFolderAndFileName() { + User user = User.createLocal(); + String[][] validUrls = { + // , , + { "realm://objectserver.realm.io/~/default", "realm-object-server/" + user.getIdentifier(), "default" }, + { "realm://objectserver.realm.io/~/sub/default", "realm-object-server/" + user.getIdentifier() + "/sub", "default" } + }; + + for (String[] validUrl : validUrls) { + String serverUrl = validUrl[0]; + String expectedFolder = validUrl[1]; + String expectedFileName = validUrl[2]; + + SyncConfiguration config = new SyncConfiguration.Builder(context) + .serverUrl(serverUrl) + .user(user) + .build(); + + assertEquals(new File(context.getFilesDir(), expectedFolder), config.getRealmDirectory()); + assertEquals(expectedFileName, config.getRealmFileName()); + } + } + + @Test + public void serverUrl_invalidUrlThrows() { + String[] invalidUrls = { + null, +// TODO Should these two fail? +// "objectserver.realm.io/~/default", // Missing protocol. TODO Should we just default to one? +// "/~/default", // Missing server + "realm://objectserver.realm.io/~/default.realm", // Ending with .realm + "realm://objectserver.realm.io/<~>/default.realm", // Invalid chars <> + "realm://objectserver.realm.io/~/default.realm/", // Ending with / + }; + + SyncConfiguration.Builder builder = new SyncConfiguration.Builder(context); + for (String invalidUrl : invalidUrls) { + try { + builder.serverUrl(invalidUrl); + fail(invalidUrl + " should have failed."); + } catch (IllegalArgumentException ignore) { + } + } + } + + @Test + public void userAndServerUrlRequired() { + SyncConfiguration.Builder builder; + + // Both missing + builder = new SyncConfiguration.Builder(context); + try { + builder.build(); + } catch (IllegalStateException ignore) { + } + + builder = new SyncConfiguration.Builder(context); + try { + builder.user(createTestUser(Long.MAX_VALUE)).build(); + } catch (IllegalStateException ignore) { + } + + // user missing + builder = new SyncConfiguration.Builder(context); + try { + builder.serverUrl("realm://foo.bar/~/default").build(); + } catch (IllegalStateException ignore) { + } + } + + @Test + public void errorHandler() { + SyncConfiguration.Builder builder; + builder = new SyncConfiguration.Builder(context) + .user(User.createLocal()) + .serverUrl("realm://objectserver.realm.io/default"); + + Session.ErrorHandler errorHandler = new Session.ErrorHandler() { + @Override + public void onError(Session session, ObjectServerError error) { + + } + }; + + SyncConfiguration config = builder.errorHandler(errorHandler).build(); + assertEquals(errorHandler, config.getErrorHandler()); + } + + @Test + public void errorHandler_fromSyncManager() { + // Set default error handler + Session.ErrorHandler errorHandler = new Session.ErrorHandler() { + @Override + public void onError(Session session, ObjectServerError error) { + + } + }; + SyncManager.setDefaultSessionErrorHandler(errorHandler); + + // Create configuration using the default handler + SyncConfiguration config = new SyncConfiguration.Builder(context) + .user(User.createLocal()) + .serverUrl("realm://objectserver.realm.io/default") + .build(); + assertEquals(errorHandler, config.getErrorHandler()); + SyncManager.setDefaultSessionErrorHandler(null); + } + + + @Test + public void errorHandler_nullThrows() { + SyncConfiguration.Builder builder; + builder = new SyncConfiguration.Builder(context) + .user(User.createLocal()) + .serverUrl("realm://objectserver.realm.io/default"); + + try { + builder.errorHandler(null); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void syncPolicy() { + + } + + @Test + public void syncPolicy_nullThrows() { +// User user = User.createLocal(); +// user.add(con); +// + } + +// @Ignore("Only used for quick testing without needing to spin up a full integration test") +// @Test +// @RunTestInLooperThread +// public void basicIntegrationTest2() { +// User.loginAsync(Credentials.fromUsernamePassword("cm", "test", false), "http://192.168.1.21:8080/auth", new User.Callback() { +// @Override +// public void onSuccess(User user) { +// SyncConfiguration config = new SyncConfiguration.Builder(context) +// .user(user) +// .serverUrl("realm://192.168.1.21/~/default") +// .build(); +// Realm realm = Realm.getInstance(config); +// realm.beginTransaction(); +// realm.commitTransaction(); +// } +// +// @Override +// public void onError(ObjectServerError error) { +// fail(error.toString()); +// } +// }); +// } + + private User createTestUser(long expires) { + JSONObject obj = new JSONObject(); + try { + obj.put("identifier", UUID.randomUUID().toString()); + JSONObject token = new JSONObject(); + JSONArray perms = new JSONArray(); // Grant all permissions + for (int i = 0; i < Token.Permission.values().length; i++) { + perms.put(Token.Permission.values()[i].toString().toLowerCase(Locale.US)); + } + token.put("access", perms); + token.put("token", UUID.randomUUID().toString()); + token.put("expires", expires); + obj.put("refreshToken", token); + obj.put("authUrl", "http://dummy.org/auth"); + return User.fromJson(obj.toString()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } +} diff --git a/realm/realm-library/src/main/AndroidManifest.xml b/realm/realm-library/src/main/AndroidManifest.xml index 6c57d744f5..0e8fc314f7 100644 --- a/realm/realm-library/src/main/AndroidManifest.xml +++ b/realm/realm-library/src/main/AndroidManifest.xml @@ -2,4 +2,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 9f3d83e75e..b880c40d86 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -27,7 +27,8 @@ create_javah(TARGET jni_headers CLASSES io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil - io.realm.sync.SyncManager io.realm.log.LogLevel + io.realm.objectserver.SyncManager io.realm.objectserver.Session + io.realm.log.LogLevel CLASSPATH ${classes_PATH} OUTPUT_DIR ${CMAKE_SOURCE_DIR}/jni_include diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 494ddaa6b0..b55c0af109 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -382,3 +382,19 @@ Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv *env, jclass, jlong shar return JNI_FALSE; } + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, jlong sharedRealmPtr) +{ + TR_ENTER_PTR(env, sharedRealmPtr) + + auto shared_realm = *(reinterpret_cast(sharedRealmPtr)); + try { + using rf = realm::_impl::RealmFriend; + auto& shared_group = rf::get_shared_group(*shared_realm); + return LangBindHelper::get_version_of_latest_snapshot(shared_group); + } CATCH_STD () + return 0; +} + + diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index fea81c2477..6dc3da3862 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -58,8 +58,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); java_lang_double = GetClass(env, "java/lang/Double"); java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); - sync_manager = GetClass(env, "io/realm/sync/SyncManager"); - sync_manager_notify_handler = env->GetStaticMethodID(sync_manager, "notifyHandlers", "(Ljava/lang/String;)V"); + sync_manager = GetClass(env, "io/realm/objectserver/SyncManager"); realmlog_class = GetClass(env, "io/realm/log/RealmLog"); log_trace = env->GetStaticMethodID(realmlog_class, "trace", "(Ljava/lang/String;[Ljava/lang/Object;)V"); log_debug = env->GetStaticMethodID(realmlog_class, "debug", "(Ljava/lang/String;[Ljava/lang/Object;)V"); diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp b/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp new file mode 100644 index 0000000000..480d49978a --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 + +#include "io_realm_objectserver_Session.h" +#include "objectserver_shared.hpp" +#include "util.hpp" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace realm; +using namespace sync; + + +JNIEXPORT jlong JNICALL Java_io_realm_objectserver_Session_nativeCreateSession + (JNIEnv *env, jobject obj, jstring localRealmPath) +{ + TR_ENTER(env) + try { + Client* sync_client = &SyncManager::shared().get_sync_client()->client; + if (sync_client == NULL) { + return 0; + } + + JStringAccessor local_path(env, localRealmPath); + JniSession* jni_session = new JniSession(sync_client, local_path, obj, env); + return reinterpret_cast(jni_session); + } CATCH_STD() + return 0; +} + +JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeBind + (JNIEnv *env, jobject, jlong sessionPointer, jstring remoteUrl, jstring accessToken) +{ + TR_ENTER(env) + try { + auto *session_wrapper = reinterpret_cast(sessionPointer); + + const char *token_tmp = env->GetStringUTFChars(accessToken, NULL); + std::string access_token(token_tmp); + env->ReleaseStringUTFChars(accessToken, token_tmp); + + JStringAccessor url_tmp(env, remoteUrl); // throws + StringData remote_url = StringData(url_tmp); + + // Bind the local Realm to the remote one + session_wrapper->get_session()->bind(remote_url, access_token); + } CATCH_STD() +} + + +JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeUnbind + (JNIEnv *env, jobject, jlong sessionPointer) +{ + TR_ENTER(env) + JniSession* session = SS(sessionPointer); + delete session; // TODO Can we avoid killing the session here? +} + +JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeRefresh + (JNIEnv *env, jobject, jlong sessionPointer, jstring accessToken) +{ + TR_ENTER(env) + try { + JniSession* session_wrapper = SS(sessionPointer); + + JStringAccessor token_tmp(env, accessToken); // throws + StringData access_token = StringData(token_tmp); + + session_wrapper->get_session()->refresh(access_token); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_objectserver_Session_nativeNotifyCommitHappened + (JNIEnv *env, jobject, jlong sessionPointer, jlong version) +{ + TR_ENTER(env) + try { + JniSession* session_wrapper = SS(sessionPointer); + session_wrapper->get_session()->nonsync_transact_notify(version); + } CATCH_STD() +} + + diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp new file mode 100644 index 0000000000..6e7154f3a0 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 + +#include "io_realm_objectserver_SyncManager.h" +#include "objectserver_shared.hpp" +#include "util.hpp" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace realm; +using namespace sync; + +class AndroidLogger: public realm::util::RootLogger +{ +public: + void do_log(std::string msg) + { + // Figure out how to log properly. We need level/code/message + // Think it has been fixed in later versions of Core + log_message(sync_client_env, log_debug, msg.c_str()); + } +}; + +struct AndroidLoggerFactory : public realm::SyncLoggerFactory { + std::unique_ptr make_logger(realm::util::Logger::Level level) { + auto logger = std::make_unique(); + logger->set_level_threshold(level); + return std::move(logger); + } +} s_logger_factory; + +// Object Server global vars, see objectserver_shared.hpp +std::thread* sync_client_thread; +JNIEnv* sync_client_env; + +JNIEXPORT void JNICALL Java_io_realm_objectserver_SyncManager_nativeInitializeSyncClient + (JNIEnv *env, jclass) +{ + TR_ENTER(env) + try { + // Prepare Sync Client. It will be created on demand + + SyncLoginFunction loginDelegate = [=](const Realm::Config&) { + // Ignore this for now. We are handling this manually. + }; + + SyncClientReadyFunction clientReadyDelegate = [=](const realm::sync::Client&) { + //Attaching thread to Java so we can perform JNI calls + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_6; + args.name = NULL; // java thread a name + args.group = NULL; // java thread group + g_vm->AttachCurrentThread(&sync_client_env, &args); + }; + + SyncManager& sync_manager = SyncManager::shared(); + sync_manager.set_login_function(loginDelegate); + sync_manager.set_logger_factory(s_logger_factory); + sync_manager.set_log_level(util::Logger::Level::warn); + sync_manager.set_client_ready_callback(clientReadyDelegate); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_objectserver_SyncManager_nativeSetSyncClientLogLevel(JNIEnv* env, jclass, jint logLevel) +{ + util::Logger::Level native_log_level; + bool valid_log_level = true; + switch(logLevel) { + case io_realm_log_LogLevel_ALL: native_log_level = util::Logger::Level::all; break; + case io_realm_log_LogLevel_TRACE: native_log_level = util::Logger::Level::trace; break; + case io_realm_log_LogLevel_DEBUG: native_log_level = util::Logger::Level::debug; break; + case io_realm_log_LogLevel_INFO: native_log_level = util::Logger::Level::info; break; + case io_realm_log_LogLevel_WARN: native_log_level = util::Logger::Level::warn; break; + case io_realm_log_LogLevel_ERROR: native_log_level = util::Logger::Level::error; break; + case io_realm_log_LogLevel_FATAL: native_log_level = util::Logger::Level::fatal; break; + case io_realm_log_LogLevel_OFF: native_log_level = util::Logger::Level::off; break; + default: + valid_log_level = false; + ThrowException(env, IllegalArgument, "Invalid log level: " + logLevel); + } + if (valid_log_level) { + realm::SyncManager::shared().set_log_level(native_log_level); + } +} + + diff --git a/realm/realm-library/src/main/cpp/io_realm_sync_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_sync_SyncManager.cpp deleted file mode 100644 index 50e04ad5ad..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_sync_SyncManager.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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 - -#include "io_realm_sync_SyncManager.h" -#include "util.hpp" -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace realm; -using namespace sync; - -class AndroidLogger: public realm::util::RootLogger -{ -public: - void do_log(std::string msg) - { - __android_log_print(ANDROID_LOG_INFO, "[SYNC]", "> %s", msg.c_str()); - } -}; - -// maintain a reference to the threads allocated dynamically, to prevent deallocation -// after Java_io_realm_internal_SharedGroup_nativeStartSession completes. -// To be released later, maybe on JNI_OnUnload -std::thread* sync_client_thread; -JNIEnv* sync_client_env; - -JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateClient - (JNIEnv *env, jclass) -{ - TR_ENTER(env) - try { - AndroidLogger* base_logger = new AndroidLogger();//FIXME find a way to delete it when we delete the client - - sync::Client::Config config; - config.logger = base_logger; - config.reconnect = sync::Client::Reconnect::immediately; - - sync::Client* m_sync_client = new sync::Client(config); - sync_client_thread = new std::thread([m_sync_client](){ - //Attaching thread to Java so we can perform JNI calls - JavaVMAttachArgs args; - args.version = JNI_VERSION_1_6; - args.name = NULL; // java thread a name - args.group = NULL; // java thread group - g_vm->AttachCurrentThread(&sync_client_env, &args); - - m_sync_client->run(); - }); - - return reinterpret_cast(m_sync_client); - - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_sync_SyncManager_syncCreateSession - (JNIEnv *env, jclass, jlong clientPointer, jstring realmPath, jstring serverUrl, jstring userToken) -{ - TR_ENTER(env) - Client* sync_client = SC(clientPointer); - if (sync_client == NULL) { - return 0; - } - try { - const char *token_tmp = env->GetStringUTFChars(userToken, NULL); - std::string user_token(token_tmp); - env->ReleaseStringUTFChars(userToken, token_tmp); - - const char *path_tmp = env->GetStringUTFChars(realmPath, NULL); - std::string path(path_tmp); - env->ReleaseStringUTFChars(realmPath, path_tmp); - - JStringAccessor server_url_tmp(env, serverUrl); // throws - StringData server_url = StringData(server_url_tmp); - - Session* sync_session = new Session(*sync_client, path); - - std::function sync_transact_callback = [path](Session::version_type) { - sync_client_env->CallStaticVoidMethod(sync_manager, sync_manager_notify_handler, sync_client_env->NewStringUTF(path.c_str()));//REALM_CHANGE - }; - sync_session->set_sync_transact_callback(sync_transact_callback); - sync_session->bind(server_url, user_token); - return reinterpret_cast(sync_session); - } CATCH_STD() - return 0; -} - diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index decd4a0693..2f222fea53 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit decd4a0693cda3b4e07e13982e74a0e7e7c4d21b +Subproject commit 2f222fea53d1a6d17d6144c1412a751b92cfa080 diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp new file mode 100644 index 0000000000..5104ab2bcf --- /dev/null +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -0,0 +1,80 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +#ifndef REALM_OBJECTSERVER_SHARED_HPP +#define REALM_OBJECTSERVER_SHARED_HPP + +#include +#include +#include + +#include +#include +#include +#include + +#include "util.hpp" + +// maintain a reference to the threads allocated dynamically, to prevent deallocation +// after Java_io_realm_internal_SharedGroup_nativeStartSession completes. +// To be released later, maybe on JNI_OnUnload +extern std::thread* sync_client_thread; +extern JNIEnv* sync_client_env; + + +// Wrapper class for realm::Session. This allows us to manage the C++ session and callback lifecycle correctly. +// TODO Use OS SyncSession instead +class JniSession { + +public: + JniSession() = delete; + JniSession(realm::sync::Client* sync_client, std::string local_realm_path, jobject java_session_obj, JNIEnv* env) + { + // Get the coordinator for the given path, or null if there is none + m_sync_session = new realm::sync::Session(*sync_client, local_realm_path); + m_global_obj_ref = env->NewGlobalRef(java_session_obj); + jobject global_obj_ref_tmp(m_global_obj_ref); + auto sync_transact_callback = [local_realm_path](realm::sync::Session::version_type) { + auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator(realm::StringData(local_realm_path)); + if (coordinator) { + coordinator->notify_others(); + } + }; + auto error_handler = [&, global_obj_ref_tmp](int error_code, std::string message) { + std::string log = num_to_string(error_code) + " " + message.c_str(); + log_message(sync_client_env, log_debug, log.c_str()); + }; + m_sync_session->set_sync_transact_callback(sync_transact_callback); + m_sync_session->set_error_handler(std::move(error_handler)); + } + + inline realm::sync::Session* get_session() const noexcept + { + return m_sync_session; + } + + ~JniSession() + { + sync_client_env->DeleteGlobalRef(m_global_obj_ref); + delete m_sync_session; + } + +private: + realm::sync::Session* m_sync_session; + jobject m_global_obj_ref; +}; + +#endif // REALM_OBJECTSERVER_SHARED_HPP diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 260795abb2..7755e7d942 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -39,7 +39,9 @@ jmethodID java_lang_float_init; jclass java_lang_double; jmethodID java_lang_double_init; jclass sync_manager; -jmethodID sync_manager_notify_handler; +jmethodID sync_manager_notify_error_handler; +jclass session_class_ref; +jmethodID session_error_handler; void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index e091e55013..7a402f4c2d 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -86,7 +86,7 @@ std::string num_to_string(T pNumber) #define ROW(x) reinterpret_cast(x) #define HO(T, ptr) reinterpret_cast* >(ptr) #define SC(ptr) reinterpret_cast(ptr) -#define SS(ptr) reinterpret_cast(ptr) +#define SS(ptr) reinterpret_cast(ptr) // Exception handling enum ExceptionKind { @@ -701,8 +701,12 @@ extern jclass java_lang_float; extern jmethodID java_lang_float_init; extern jclass java_lang_double; extern jmethodID java_lang_double_init; + +// FIXME Move to own library extern jclass sync_manager; -extern jmethodID sync_manager_notify_handler; +extern jmethodID sync_manager_notify_error_handler; +extern jclass session_class_ref; +extern jmethodID session_error_handler; inline jobject NewLong(JNIEnv* env, int64_t value) { diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 9f9b3b70c5..15d0192dc9 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -20,6 +20,8 @@ import android.os.Looper; import android.util.Log; +import com.getkeepsafe.relinker.BuildConfig; + import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; @@ -37,6 +39,7 @@ import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; +import io.realm.objectserver.internal.ObjectServerFacade; import rx.Observable; /** @@ -57,7 +60,7 @@ public abstract class BaseRealm implements Closeable { private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; - // Thread pool for all async operations (Query & transaction) + // Thread pool for all async operations (Query / transaction / network requests) static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); final long threadId; @@ -333,6 +336,7 @@ public void commitTransaction() { void commitTransaction(boolean notifyLocalThread) { checkIfValid(); sharedRealm.commitTransaction(); + ObjectServerFacade.notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); // Sometimes we don't want to notify the local thread about commits, e.g. creating a completely new Realm // file will make a commit in order to create the schema. Users should not be notified about that. diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java index 3a04ddff21..071dcc6b77 100644 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java @@ -42,7 +42,6 @@ import io.realm.internal.async.QueryUpdateTask; import io.realm.log.RealmLog; -import static android.R.attr.version; import static io.realm.internal.HandlerControllerConstants.LOCAL_COMMIT; import static io.realm.internal.HandlerControllerConstants.REALM_CHANGED; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 53f8ff4b55..9f294b8af4 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -25,7 +25,6 @@ import org.json.JSONException; import org.json.JSONObject; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -1266,7 +1265,7 @@ public void run() { } }); - return new RealmAsyncTask(pendingTransaction); + return new RealmAsyncTask(pendingTransaction, asyncTaskExecutor); } /** @@ -1379,17 +1378,6 @@ public static boolean compactRealm(RealmConfiguration configuration) { return BaseRealm.compactRealm(configuration); } - // Get the canonical path for a given file - static String getCanonicalPath(File realmFile) { - try { - return realmFile.getCanonicalPath(); - } catch (IOException e) { - throw new RealmFileException(RealmFileException.Kind.ACCESS_ERROR, - "Could not resolve the canonical path to the Realm file: " + realmFile.getAbsolutePath(), - e); - } - } - Table getTable(Class clazz) { return schema.getTable(clazz); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java b/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java index aa53c0ce8e..cc2809d512 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java +++ b/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java @@ -16,7 +16,9 @@ package io.realm; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; /** * Represents a pending asynchronous Realm transaction. @@ -26,18 +28,21 @@ * caller's thread callback). */ public final class RealmAsyncTask { - private final Future pendingQuery; + private final Future pendingTask; + private final ThreadPoolExecutor service; private volatile boolean isCancelled = false; - RealmAsyncTask(Future pendingQuery) { - this.pendingQuery = pendingQuery; + // FIXME This shouldn't be public + public RealmAsyncTask(Future pendingTask, ThreadPoolExecutor service) { + this.pendingTask = pendingTask; + this.service = service; } /** * Attempts to cancel execution of this transaction (if it hasn't already completed or previously cancelled). */ public void cancel() { - pendingQuery.cancel(true); + pendingTask.cancel(true); isCancelled = true; // From "Java Threads": By Scott Oaks & Henry Wong @@ -49,7 +54,7 @@ public void cancel() { // first thread is attempting to purge the queue the attempt to purge // the queue fails and the cancelled object remain in the queue. // A better way to cancel objects with thread pools is to use the remove() - Realm.asyncTaskExecutor.getQueue().remove(pendingQuery); + service.getQueue().remove(pendingTask); } /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 0a24a113bd..8e2b865a72 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -27,6 +27,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.internal.ColumnIndices; import io.realm.log.RealmLog; +import io.realm.objectserver.internal.ObjectServerFacade; /** * To cache {@link Realm}, {@link DynamicRealm} instances and related resources. @@ -152,6 +153,11 @@ static synchronized E createRealmOrGetFromCache(RealmConfi @SuppressWarnings("unchecked") E realm = (E) refAndCount.localRealm.get(); + + // Notify SyncPolicy that the Realm has been opened for the first time + if (refAndCount.globalCount == 1) { + ObjectServerFacade.realmOpened(configuration); + } return realm; } @@ -207,13 +213,16 @@ static synchronized void release(BaseRealm realm) { for (RealmCacheType type : RealmCacheType.values()) { totalRefCount += cache.refAndCountMap.get(type).globalCount; } + + // No more local reference to this Realm in current thread, close the instance. + realm.doClose(); + // No more instance of typed Realm and dynamic Realm. Remove the configuration from cache. if (totalRefCount == 0) { cachesMap.remove(canonicalPath); + ObjectServerFacade.realmClosed(realm.getConfiguration()); } - // No more local reference to this Realm in current thread, close the instance. - realm.doClose(); } else { refAndCount.localCount.set(refCount); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index d059064734..0bfa172f4a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -17,7 +17,6 @@ package io.realm; import android.content.Context; -import android.os.Handler; import android.text.TextUtils; import java.io.File; @@ -26,8 +25,6 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -36,6 +33,7 @@ import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; +import io.realm.exceptions.RealmFileException; import io.realm.internal.RealmCore; import io.realm.internal.RealmProxyMediator; import io.realm.internal.SharedRealm; @@ -64,7 +62,7 @@ *

  • It has its schema version set to 0.
  • * */ -public final class RealmConfiguration { +public class RealmConfiguration { public static final String DEFAULT_REALM_NAME = "default.realm"; public static final int KEY_LENGTH = 64; @@ -101,13 +99,11 @@ public final class RealmConfiguration { private final RxObservableFactory rxObservableFactory; private final Realm.Transaction initialDataTransaction; private final WeakReference contextWeakRef; - private final String syncServerUrl; - private final String syncUserToken; - private RealmConfiguration(Builder builder) { + protected RealmConfiguration(Builder builder) { this.realmDirectory = builder.directory; this.realmFileName = builder.fileName; - this.canonicalPath = Realm.getCanonicalPath(new File(realmDirectory, realmFileName)); + this.canonicalPath = getCanonicalPath(new File(realmDirectory, realmFileName)); this.assetFilePath = builder.assetFilePath; this.key = builder.key; this.schemaVersion = builder.schemaVersion; @@ -118,8 +114,6 @@ private RealmConfiguration(Builder builder) { this.rxObservableFactory = builder.rxFactory; this.initialDataTransaction = builder.initialDataTransaction; this.contextWeakRef = builder.contextWeakRef; - this.syncServerUrl = builder.syncServerUrl; - this.syncUserToken = builder.syncUserToken; } public File getRealmDirectory() { @@ -221,33 +215,6 @@ public RxObservableFactory getRxFactory() { return rxObservableFactory; } - /** - * Checks if server side synchronization is enabled for this Realm. - * - * @return {@code true} if synchronisation is enabled, {@code false} otherwise. - */ - public boolean isSyncEnabled() { - return syncServerUrl != null && syncServerUrl.length() > 0; - } - - /** - * Returns the server side URL used to sync this Realm across devices. - * - * @return URL of the Realm Sync server. - */ - public String getSyncServerUrl() { - return syncServerUrl; - } - - /** - * Returns the predefined user token for server side synchronization or {@code null} if no user is predefined. - * - * @return The user token for Realm Sync of {@code null} if no token is defined. - */ - public String getSyncUserToken() { - return syncUserToken; - } - @Override public boolean equals(Object obj) { if (this == obj) return true; @@ -266,12 +233,6 @@ public boolean equals(Object obj) { //noinspection SimplifiableIfStatement if (rxObservableFactory != null ? !rxObservableFactory.equals(that.rxObservableFactory) : that.rxObservableFactory != null) return false; if (initialDataTransaction != null ? !initialDataTransaction.equals(that.initialDataTransaction) : that.initialDataTransaction != null) return false; - if (syncServerUrl == null ? that.syncServerUrl != null : !syncServerUrl.equals(that.syncServerUrl)) { - return false; - } - if (syncUserToken == null ? that.syncUserToken != null : !syncUserToken.equals(that.syncUserToken)) { - return false; - } return schemaMediator.equals(that.schemaMediator); } @@ -291,8 +252,6 @@ public int hashCode() { result = 31 * result + durability.hashCode(); result = 31 * result + (rxObservableFactory != null ? rxObservableFactory.hashCode() : 0); result = 31 * result + (initialDataTransaction != null ? initialDataTransaction.hashCode() : 0); - result = 31 * result + (syncServerUrl != null ? syncServerUrl.hashCode() : 0); - result = 31 * result + (syncUserToken != null ? syncUserToken.hashCode() : 0); return result; } @@ -388,10 +347,28 @@ private static synchronized boolean isRxJavaAvailable() { return rxJavaAvailable; } + public Context getContext() { + return contextWeakRef.get(); + } + + // Get the canonical path for a given file + protected static String getCanonicalPath(File realmFile) { + try { + return realmFile.getCanonicalPath(); + } catch (IOException e) { + throw new RealmFileException(RealmFileException.Kind.ACCESS_ERROR, + "Could not resolve the canonical path to the Realm file: " + realmFile.getAbsolutePath(), + e); + } + } + /** * RealmConfiguration.Builder used to construct instances of a RealmConfiguration in a fluent manner. */ - public static final class Builder { + public static class Builder { + /** + * IMPORTANT: When adding any new methods to this class also add them to ObjectServerConfiguration.Builder + */ private File directory; private String fileName; private String assetFilePath; @@ -405,8 +382,6 @@ public static final class Builder { private WeakReference contextWeakRef; private RxObservableFactory rxFactory; private Realm.Transaction initialDataTransaction; - private String syncServerUrl; - private String syncUserToken; /** * Creates an instance of the Builder for the RealmConfiguration. @@ -640,30 +615,6 @@ public Builder assetFile(final String assetFile) { return this; } - /** - * Enable server side synchronization for this Realm. The URL should point to an endpoint exposed by a - * Realm Sync server. - * - * @param serverUrl Realm Sync server url. - */ - public Builder withSync(String serverUrl) { - syncServerUrl = serverUrl; - return this; - } - - /** - * Sets the default user token to be used with Realm Sync. - * - * @param userToken User token identifying the user connecting to Realm Sync. - */ - public Builder syncUserToken(String userToken) { - if (userToken == null || userToken.equals("")) { - throw new IllegalArgumentException("Non-empty user token required"); - } - syncUserToken = userToken; - return this; - } - private void addModule(Object module) { if (module != null) { checkModule(module); diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java index b89e49e9d3..750ca7d0b6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java @@ -21,7 +21,9 @@ import com.getkeepsafe.relinker.ReLinker; import java.io.File; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.util.Locale; /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 9254d2637e..67245556ee 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -21,6 +21,8 @@ import io.realm.RealmConfiguration; import io.realm.internal.async.BadVersionException; +import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.internal.ObjectServerFacade; public final class SharedRealm implements Closeable { @@ -66,14 +68,18 @@ public enum SchemaMode { SchemaMode(byte value) { this .value = value; } + + public byte getNativeValue() { + return value; + } } // JNI will only hold a weak global ref to this. public final RealmNotifier realmNotifier; public static class VersionID implements Comparable { - final long version; - final long index; + public final long version; + public final long index; VersionID(long version, long index) { this.version = version; @@ -140,16 +146,22 @@ public static SharedRealm getInstance(RealmConfiguration config) { } public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier realmNotifier) { + String[] userAndServer = ObjectServerFacade.getUserAndServerUrl(config); + String rosServerUrl = userAndServer[0]; + String rosUserToken = userAndServer[1]; + boolean enable_caching = false; // Handled in Java currently + boolean disableFormatUpgrade = false; // TODO Double negatives :/ + boolean autoChangeNotifications = true; long nativeConfigPtr = nativeCreateConfig( config.getPath(), config.getEncryptionKey(), - SchemaMode.SCHEMA_MODE_MANUAL.value, + rosServerUrl != null ? SchemaMode.SCHEMA_MODE_ADDITIVE.getNativeValue() : SchemaMode.SCHEMA_MODE_MANUAL.getNativeValue(), config.getDurability() == Durability.MEM_ONLY, - false, - false, - true, - null, - null); + enable_caching, + disableFormatUpgrade, + autoChangeNotifications, + rosServerUrl, + rosUserToken); try { return new SharedRealm(nativeGetSharedRealm(nativeConfigPtr, realmNotifier), config, realmNotifier); } finally { @@ -235,6 +247,10 @@ public SharedRealm.VersionID getVersionID() { return new SharedRealm.VersionID(versionId[0], versionId[1]); } + public long getLastSnapshotVersion() { + return nativeGetSnapshotVersion(nativePtr); + } + public boolean isClosed() { return nativePtr == 0 || nativeIsClosed(nativePtr); } @@ -298,6 +314,7 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte private static native void nativeCancelTransaction(long nativeSharedRealmPtr); private static native boolean nativeIsInTransaction(long nativeSharedRealmPtr); private static native long nativeGetVersion(long nativeSharedRealmPtr); + private static native long nativeGetSnapshotVersion(long nativeSharedRealmPtr); private static native long nativeReadGroup(long nativeSharedRealmPtr); private static native boolean nativeIsEmpty(long nativeSharedRealmPtr); private static native void nativeRefresh(long nativeSharedRealmPtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SyncSessionImpl.java b/realm/realm-library/src/main/java/io/realm/internal/SyncSessionImpl.java deleted file mode 100644 index c5263b4694..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/SyncSessionImpl.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.realm.internal; - -import io.realm.sync.SyncConfiguration; -import io.realm.sync.SyncSession; - -public class SyncSessionImpl implements SyncSession { - - private final SyncConfiguration config; - private final long nativeSyncSessionPtr; - - public SyncSessionImpl(SyncConfiguration config, long nativeSyncSessionPtr) { - this.config = config; - this.nativeSyncSessionPtr = nativeSyncSessionPtr; - } - - @Override - public void start() { - // nativeStartSync(nativeSyncSessionPtr); - } - - @Override - public void stop() { - nativeStopSync(nativeSyncSessionPtr); - } - - private native void nativeStartSync(long nativeSyncSessionPtr); - //TODO just delete the pointer nativeSyncSessionPtr to stop syncing - private native void nativeStopSync(long nativeSyncSessionPtr); -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index f6eb85b851..d4178fc008 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -16,6 +16,11 @@ package io.realm.internal; +import android.os.Build; + +import java.io.PrintWriter; +import java.io.StringWriter; + import io.realm.RealmModel; import io.realm.RealmObject; @@ -64,4 +69,65 @@ public static Class getOriginalModelClass(ClassGets the stack trace from a Throwable as a String.

    + * + *

    The result of this method vary by JDK version as this method + * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. + * On JDK1.3 and earlier, the cause exception will not be shown + * unless the specified throwable alters printStackTrace.

    + * + * @param throwable the Throwable to be examined + * @return the stack trace as generated by the exception's + * printStackTrace(PrintWriter) method + * + * Credit: https://commons.apache.org/proper/commons-lang/apidocs/src-html/org/apache/commons/lang3/exception/ExceptionUtils.html + */ + public static String getStackTrace(final Throwable throwable) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + throwable.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + // Credit: http://stackoverflow.com/questions/2799097/how-can-i-detect-when-an-android-application-is-running-in-the-emulator + public static boolean isEmulator() { + return Build.FINGERPRINT.startsWith("generic") + || Build.FINGERPRINT.startsWith("unknown") + || Build.MODEL.contains("google_sdk") + || Build.MODEL.contains("Emulator") + || Build.MODEL.contains("Android SDK built for x86") + || Build.MANUFACTURER.contains("Genymotion") + || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) + || "google_sdk".equals(Build.PRODUCT); + } + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java index 482cbdc1b6..7f251618a2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java @@ -92,6 +92,16 @@ public Future submitQuery(Callable task) { return super.submit(new BgPriorityCallable(task)); } + /** + * Submits a runnable for executing a network request. + * + * @param task the task to submit + * @return a future representing pending completion of the task + */ + public Future submitNetworkRequest(Runnable task) { + return super.submit(new BgPriorityRunnable(task)); + } + /** * Method invoked prior to executing the given Runnable to pause execution of the thread. * diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index f83173214a..59532dac07 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -21,6 +21,8 @@ import io.realm.internal.Keep; import io.realm.internal.Util; +import io.realm.objectserver.SyncManager; +import io.realm.objectserver.internal.ObjectServerFacade; /** * Global logger used by all Realm components. @@ -58,6 +60,9 @@ public static void add(Logger logger) { private static void setMinimumNativeDebugLevel(int nativeDebugLevel) { minimumNativeLogLevel = nativeDebugLevel; Util.setDebugLevel(nativeDebugLevel); // Log level for Realm Core + if (ObjectServerFacade.SYNC_AVAILABLE) { + SyncManager.setLogLevel(nativeDebugLevel); + } } /** diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticatingState.java b/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticatingState.java new file mode 100644 index 0000000000..f7fad18c54 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticatingState.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import io.realm.objectserver.internal.network.NetworkStateReceiver; + +/** + * AUTHENTICATING State. This step is needed if the user does not have proper access or credentials to access this + * Realm when attempting to bind it. This can happen in 3 ways: + * + *
      + *
    1. + * Refresh token has expired: + * This effectively means the user has been logged out from the Realm Object Server and credentials has + * to be re-verified on the Authentication Server. Since this involves creating a new User object object, + * this session will be stopped and and error reported. + *
    2. + *
    3. + * Access token has expired: + * This state will automatically refresh it and retry binding the Realm. + *
    4. + *
    5. + * Access token does not exists: + * This state means the user has logged in, but not yet gained a specific access token for this Realm. + * This state will automatically fetch the access token and retry binding the Realm. + *
    6. + *
    + */ +class AuthenticatingState extends FsmState { + + @Override + public void onEnterState() { + if (NetworkStateReceiver.isOnline(session.configuration.getContext())) { + authenticate(session); + } else { + // Wait for connection to become available, before trying again. + // The Session might potentially stay in this state for the lifetime of the application. + // This is acceptable. + session.networkListener = new NetworkStateReceiver.ConnectionListener() { + @Override + public void onChange(boolean connectionAvailable) { + if (connectionAvailable) { + authenticate(session); + NetworkStateReceiver.removeListener(this); + } + } + }; + NetworkStateReceiver.addListener(session.networkListener); + } + } + + @Override + public void onExitState() { + // Abort any current network request. + if (session.networkRequest != null) { + session.networkRequest.cancel(); + session.networkRequest = null; + } + + // Release listener if we were waiting for network to become available. + if (session.networkListener != null) { + NetworkStateReceiver.removeListener(session.networkListener); + session.networkListener = null; + } + } + + private synchronized void authenticate(final Session session) { + session.authenticateRealm(new Runnable() { + @Override + public void run() { + gotoNextState(SessionState.BINDING); + } + }, new Session.ErrorHandler() { + @Override + public void onError(Session session, ObjectServerError error) { + // FIXME For critical errors, got directly to STOPPED + gotoNextState(SessionState.UNBOUND); + } + }); + } + + @Override + public void onBind() { + gotoNextState(SessionState.BINDING); // Equivalent to forcing a retry + } + + @Override + public void onUnbind() { + gotoNextState(SessionState.UNBOUND); // Treat this as user wanting to exit a binding in progress. + } + + @Override + public void onStop() { + gotoNextState(SessionState.STOPPED); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/BindingState.java b/realm/realm-library/src/main/java/io/realm/objectserver/BindingState.java new file mode 100644 index 0000000000..ae1e81497f --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/BindingState.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +/** + * BINDING State. After bind() is called, this state will attempt to bind the local Realm to the remote. This is an + * asynchronous operation that must be able to be interrupted. + */ +class BindingState extends FsmState { + + @Override + public void onEnterState() { + if (session.isAuthenticated(session.configuration)) { + // FIXME How to handle errors? + session.bindWithTokens(); + gotoNextState(SessionState.BOUND); + } else { + // Not access token available. We need to authenticateUser first. + gotoNextState(SessionState.AUTHENTICATING); + } + } + + @Override + public void onExitState() { + // TODO Abort any async stuff going on, possible in `session.bindWithTokens()` + } + + @Override + public void onBind() { + gotoNextState(SessionState.BINDING); // Will trigger a retry. + } + + @Override + public void onUnbind() { + gotoNextState(SessionState.UNBOUND); + } + + @Override + public void onError(ObjectServerError error) { + // Ignore all errors. This is just a transient state. We are not bound yet, and any error should not + // happen until we are BOUND. + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java b/realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java new file mode 100644 index 0000000000..ef375186eb --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +/** + * BOUND State. At this state the local Realm is bound to the remote Realm and changes is sent in both + * directions immediately. + */ +class BoundState extends FsmState { + + @Override + public void onEnterState() { + // Do nothing. If everything is setup correctly. We should now be synchronizing any changes + // between the local and remote Realm. + } + + @Override + public void onExitState() { + session.stopNativeSession(); + } + + @Override + public void onUnbind() { + gotoNextState(SessionState.UNBOUND); + } + + @Override + public void onStop() { + gotoNextState(SessionState.STOPPED); + } + + @Override + public void onError(ObjectServerError error) { + switch(error.errorCode()) { + // Auth protocol errors (should not happen). If credentials are being replaced + case IO_EXCEPTION: + case JSON_EXCEPTION: + case REALM_PROBLEM: + case INVALID_PARAMETERS: + case MISSING_PARAMETERS: + case INVALID_CREDENTIALS: + case UNKNOWN_ACCOUNT: + case EXISTING_ACCOUNT: + case ACCESS_DENIED: + case INVALID_REFRESH_TOKEN: + case EXPIRED_REFRESH_TOKEN: + case INTERNAL_SERVER_ERROR: + throw new IllegalStateException("Authentication protocol errors should not happen: " + error.toString()); + + // Ignore Network client errors (irrelevant) + // FIXME: Not accurate: https://github.com/realm/realm-sync/issues/659 How should these be handled? + case CONNECTION_CLOSED: + case OTHER_ERROR: + case UNKNOWN_MESSAGE: + case BAD_SYNTAX: + case LIMITS_EXCEEDED: + case WRONG_PROTOCOL_VERSION: + case BAD_SESSION_IDENT: + case REUSE_OF_SESSION_IDENT: + case BOUND_IN_OTHER_SESSION: + case BAD_MESSAGE_ORDER: + return; + + // Session errors: + // FIXME: Which of these are just INFO and which can we actually do something about? Right now treat all as fatal + case SESSION_CLOSED: + case OTHER_SESSION_ERROR: + gotoNextState(SessionState.STOPPED); + break; + + case TOKEN_EXPIRED: + // Only known case we can actually work around. + // Trigger a rebind which will cause access token to be refreshed. + gotoNextState(SessionState.BINDING); + break; + + case BAD_AUTHENTICATION: + case ILLEGAL_REALM_PATH: + case NO_SUCH_PATH: + case PERMISSION_DENIED: + case BAD_SERVER_FILE_IDENT: + case BAD_CLIENT_FILE_IDENT: + case BAD_SERVER_VERSION: + case BAD_CLIENT_VERSION: + case DIVERGING_HISTORIES: + case BAD_CHANGESET: + gotoNextState(SessionState.STOPPED); + break; + + default: + throw new IllegalArgumentException("Unknown error code:" + error.errorCode()); + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java b/realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java new file mode 100644 index 0000000000..835e3e600c --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java @@ -0,0 +1,163 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import java.util.UUID; + +/** + * Credentials represent a login with a 3rd party login provider in an OAuth2 login flow, and are used by the Realm + * Object Server to verify the user and grant access. + *

    + * Logging into the Realm Object Server consists of the following steps: + *

      + *
    1. + * Login to 3rd party like Facebook, Google or Twitter. The result is usually an Authorization Grant that must be + * saved in a {@link Credentials} object of the proper type, e.g {@link Credentials#fromFacebook(String)} for a + * Facebook login. + *
    2. + *
    3. + * Authenticate a {@link User} through the Realm Object Server using these credentials. Once authenticated + * a Realm Object Server user is returned. This user can then be attached to a {@link SyncConfiguration}, which + * will make it possible to synchronize data between the local and remote Realm. + *

      + * It is possible to persist the user object using e.g. the {@link UserStore} so logging + * into e.g Facebook is only required the first time the app is used. + *

    4. + *
    + * + *
    + * {@code
    + * // Example
    + *
    + * Credentials credentials = Credentials.fromFacebook(getFacebookToken());
    + * boolean createUser = true;
    + * User.authenticateUser(credentials, new URL("http://objectserver.realm.io/auth", new User.Callback() {
    + *     \@Override
    + *     public void onSuccess(User user) {
    + *          userStore.saveUser("key", user)
    + *          // User is now authenticated and be be used to open Realms.
    + *     }
    + *
    + *     \@Override
    + *     public void onError(ObjectServerError error) {
    + *
    + *     }
    + * });
    + * }
    + * 
    + */ +public class Credentials { + + private LoginType loginType; + private String field1; + private String field2; + private final boolean createUser; + + // Factory constructors + + /** + * Creates credentials for a local user that is only known by this device. + * Loosing these credentials or the User once it has been authenticated means that the data stored in + * the Realm cannot be recovered. + * + * @see Tutorial showing how to authenticateUser using local credentials + */ + public static Credentials createLocal() { + return new Credentials(LoginType.LOCAL, UUID.randomUUID().toString()); + } + + /** + * Creates a credentials token based on a login with username and password. + * + * @see Tutorial showing how to authenticateUser using username and password + */ + public static Credentials fromUsernamePassword(String username, String password, boolean createUser) { + return new Credentials(LoginType.USERNAME_PASSWORD, username, password, createUser); + } + + /** + * Creates a credentials token based on a Facebook login. + * + * @see Tutorial showing how to authenticateUser using the Facebook SDK + */ + public static Credentials fromFacebook(String facebookToken) { + return new Credentials(LoginType.FACEBOOK, facebookToken); + } + + private Credentials(LoginType type, String token) { + this.loginType = type; + this.field1 = token; + this.createUser = false; + } + + private Credentials(LoginType usernamePassword, String username, String password, boolean createUser) { + this.loginType = LoginType.USERNAME_PASSWORD; + this.field1 = username; + this.field2 = password; + this.createUser = createUser; + } + + /** + * Returns the type of login used to createFrom these credentials. + * It is used by the authentication server to determine how these credentials should be validated. + * + * @return the login type. + */ + public LoginType getLoginType() { + return loginType; + } + + /** + * Returns the data in field 1. The type of information in this field will depend on the login type. + * + * @return the value of field1 of for these credentials. + */ + public String getField1() { + return field1; + } + + /** + * Returns the data in field 2. The type of information in this field will depend on the login type. + * + * @return the value of field2 of for these credentials. + */ + public String getField2() { + return field2; + } + + + /** + * Returns {@code true} if a User should be created based on these credentials. + * If the user already exists, this will fail. + * + * @return {@code true} if the user should be created on the Realm Object Server, {@code false} if it already exists. + */ + public boolean shouldCreateUser() { + return createUser; + } + + /** + * Enumeration of the different types of supported authentication method. + */ + public enum LoginType { + FACEBOOK, + TWITTER, + GOOGLE, + USERNAME_PASSWORD, + LOCAL + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java b/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java new file mode 100644 index 0000000000..e1930a6686 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java @@ -0,0 +1,147 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +public enum ErrorCode { + + // See https://github.com/realm/realm-sync/issues/585 + // See https://github.com/realm/realm-sync/blob/master/doc/protocol.md + + // Realm Java errors (0-49) + + UNKNOWN(-1), // Catch-all + IO_EXCEPTION(0, Category.RECOVERABLE), // Some IO error while either contacting the server or reading the response + JSON_EXCEPTION(1), // JSON input could not be parsed correctly + + // Realm Authentication Server response errors (50 - 99) + + REALM_PROBLEM(50), + INVALID_PARAMETERS(51), + MISSING_PARAMETERS(52), + INVALID_CREDENTIALS(53), + UNKNOWN_ACCOUNT(54), + EXISTING_ACCOUNT(55), + ACCESS_DENIED(56), + INVALID_REFRESH_TOKEN(57), + EXPIRED_REFRESH_TOKEN(58), + INTERNAL_SERVER_ERROR(59), + + // Realm Object Server errors (100 - 199) + + // Connection level and protocol errors + + CONNECTION_CLOSED(100, Category.INFO), // Connection closed (no error) + OTHER_ERROR(101, Category.INFO), // Other connection level error + UNKNOWN_MESSAGE(102, Category.INFO), // Unknown type of input message + BAD_SYNTAX(103, Category.INFO), // Bad syntax in input message head + LIMITS_EXCEEDED(104, Category.INFO), // Limits exceeded in input message + WRONG_PROTOCOL_VERSION(105, Category.INFO), // Wrong protocol version (CLIENT) + BAD_SESSION_IDENT(106, Category.INFO), // Bad session identifier in input message + REUSE_OF_SESSION_IDENT(107, Category.INFO), // Overlapping reuse of session identifier (BIND) + BOUND_IN_OTHER_SESSION(108, Category.INFO), // Client file bound in other session (IDENT) + BAD_MESSAGE_ORDER(109, Category.INFO), // Bad input message order + + // Session level errors (200 - 299) + SESSION_CLOSED(200, Category.RECOVERABLE), // Session closed (no error) + OTHER_SESSION_ERROR(201, Category.RECOVERABLE), // Other session level error + TOKEN_EXPIRED(202, Category.RECOVERABLE), // Access token expired + + // Session fatal: Auth wrong. Cannot be fixed without a new User/SyncConfiguration. + BAD_AUTHENTICATION(203), // Bad user authentication (BIND, REFRESH) + ILLEGAL_REALM_PATH(204), // Illegal Realm path (BIND) + NO_SUCH_PATH(205), // No such Realm (BIND) + PERMISSION_DENIED(206), // Permission denied (BIND, REFRESH) + + // Fatal: Wrong server/client versions. Trying to sync incompatible files or corrupted. + BAD_SERVER_FILE_IDENT(207), // Bad server file identifier (IDENT) + BAD_CLIENT_FILE_IDENT(208), // Bad client file identifier (IDENT) + BAD_SERVER_VERSION(209), // Bad server version (IDENT, UPLOAD) + BAD_CLIENT_VERSION(210), // Bad client version (IDENT, UPLOAD) + DIVERGING_HISTORIES(211), // Diverging histories (IDENT) + BAD_CHANGESET(212); // Bad changeset (UPLOAD) + + private final int code; + private final Category category; + + ErrorCode(int errorCode) { + this(errorCode, Category.FATAL); + } + + ErrorCode(int errorCode, Category category) { + this.code = errorCode; + this.category = category; + } + + @Override + public String toString() { + return super.toString() + "(" + code + ")"; + } + + public int errorCode() { + return code; + } + + + /** + * Returns the category of the error. + *

    + * Errors come in 3 categories: FATAL, RECOVERABLE, and INFO. + *

    + * FATAL: The session cannot be recovered and needs to be re-created. A likely cause is that the User does not + * have access to this Realm. Check that the {@link SyncConfiguration} is correct. + *

    + * RECOVERABLE: The session is paused until given additional information. Most likely cause is an expired access + * token or similar. + *

    + * INFO: The underlying sync client will automatically try to recover from this. + * + * @return the severity of the error. + */ + public Category getCategory() { + return category; + } + + public static ErrorCode fromInt(int errorCode) { + ErrorCode[] errorCodes = values(); + for (int i = 0; i < errorCodes.length; i++) { + ErrorCode error = errorCodes[i]; + if (error.errorCode() == errorCode) { + return error; + } + } + throw new IllegalArgumentException("Unknown error code: " + errorCode); + } + + public static ErrorCode fromAuthError(String type) { + switch(type) { + case "https://realm.io/docs/object-server/problems/invalid-credentials" : return ErrorCode.INVALID_CREDENTIALS; + case "https://realm.io/docs/object-server/problems/unknown-account" : return ErrorCode.UNKNOWN_ACCOUNT; + case "https://realm.io/docs/object-server/problems/existing-account" : return ErrorCode.EXISTING_ACCOUNT; + case "https://realm.io/docs/object-server/problems/access-denied" : return ErrorCode.ACCESS_DENIED; + case "https://realm.io/docs/object-server/problems/expired-refresh-token" : return ErrorCode.EXPIRED_REFRESH_TOKEN; + case "https://realm.io/docs/object-server/problems/internal-server-error" : return ErrorCode.INTERNAL_SERVER_ERROR; + default: + throw new IllegalArgumentException("Unknown error: " + type); + } + } + +public enum Category { + FATAL, // Abort session as soon as possible + RECOVERABLE, // Still possible to recover the session by either rebinding or providing the required information. + INFO // Just FYI. The underlying network client will automatically try to recover. + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/FsmAction.java b/realm/realm-library/src/main/java/io/realm/objectserver/FsmAction.java new file mode 100644 index 0000000000..178ab77f03 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/FsmAction.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +/** + * As {@link Session} is modeled as a state machine, this interface describe all + * possible actions in that machine. + * + * All states should implement this so all possible permutations of state/actions are covered. + * + * TODO Move this to the Object Store + */ +interface FsmAction { + void onStart(); + void onBind(); + void onUnbind(); + void onStop(); + void onError(ObjectServerError error); +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/FsmState.java b/realm/realm-library/src/main/java/io/realm/objectserver/FsmState.java new file mode 100644 index 0000000000..eb1262c632 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/FsmState.java @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +/** + * Abstract class containing shared logic for all {@link Session} states. All states must extend this class as it + * contains the logic for entering and leaving states. + * + * TODO Move this to the Object Store + */ +abstract class FsmState implements FsmAction { + + volatile Session session; // This is non-null when this state is active. + private boolean exiting; // TODO: Remind me again what race condition necessitated this. + + /** + * Entry into the state. This method is also responsible for executing any asynchronous work + * this state might run. + * + * This should only be called from {@link Session}. + */ + public void entry(Session session) { + this.session = session; + this.exiting = false; + onEnterState(); + } + + /** + * Called just before leaving the state. Once this method is called no more state changes can be triggered from + * this state until {@link #entry(Session)} has been called again. + * + * This should only be called from {@link Session}. + */ + public void exit() { + exiting = true; + onExitState(); + } + + public void gotoNextState(SessionState state) { + if (!exiting) { + session.nextState(state); + } + } + + protected abstract void onEnterState(); + protected abstract void onExitState(); + + @Override + public void onStart() { + // Do nothing + } + + @Override + public void onBind() { + // Do nothing + } + + @Override + public void onUnbind() { + // Do nothing + } + + @Override + public void onStop() { + // Do nothing + } + + @Override + public void onError(ObjectServerError error) { + gotoNextState(SessionState.STOPPED); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/InitialState.java b/realm/realm-library/src/main/java/io/realm/objectserver/InitialState.java new file mode 100644 index 0000000000..ce624e05c2 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/InitialState.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +/** + * INITIAL State. Starting point for the Session Finite-State-Machine. + */ +class InitialState extends FsmState { + + @Override + public void onEnterState() { + // Do nothing. We start here + } + + @Override + protected void onExitState() { + // Do nothing. Right now the underlying Realm Core session cannot bound/unbind multiple times, so instead + // we create a new session object each time the Session becomes unbound. + } + + @Override + public void onStart() { + gotoNextState(SessionState.UNBOUND); + } + + @Override + public void onError(ObjectServerError error) { + // Ignore all errors at this state. None of them would have any impact. + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java b/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java new file mode 100644 index 0000000000..0b197aaf41 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import io.realm.internal.Util; + +/** + * This class is a wrapper for all errors happening when communicating with the Realm Object Server. + * This include both exceptions and protocol errors. + * + * Only {@link #errorCode()} is guaranteed to be set. If the error was caused by an underlying exception + * {@link #errorMessage()} is {@code null} and {@link #exception()} is set, while if the error was a protocol error + * {@link #errorMessage()} is set and {@link #exception()} is null. + * + * @see io.realm.objectserver.ErrorCode for a list of possible errors. + */ +public class ObjectServerError extends RuntimeException { + + private final ErrorCode error; + private final String errorMessage; + private final Throwable exception; + + public ObjectServerError(ErrorCode errorCode, String errorMessage) { + this(errorCode, errorMessage, null); + } + + public ObjectServerError(ErrorCode errorCode, Throwable exception) { + this(errorCode, null, exception); + } + + public ObjectServerError(ErrorCode errorCode, String errorMessage, Throwable exception) { + this.error = errorCode; + this.errorMessage = errorMessage; + this.exception = exception; + } + + public ErrorCode errorCode() { + return error; + } + + public String errorMessage() { + return errorMessage; + } + + public Throwable exception() { + return exception; + } + + public ErrorCode.Category category() { + return error.getCategory(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(errorCode().toString()); + if (errorMessage != null) { + sb.append('\n'); + sb.append(errorMessage); + } + if (exception != null) { + sb.append('\n'); + sb.append(Util.getStackTrace(exception)); + } + return sb.toString(); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/Session.java b/realm/realm-library/src/main/java/io/realm/objectserver/Session.java new file mode 100644 index 0000000000..d1f5acbb57 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/Session.java @@ -0,0 +1,349 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import java.util.HashMap; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import io.realm.RealmAsyncTask; +import io.realm.internal.Keep; +import io.realm.internal.Util; +import io.realm.objectserver.internal.syncpolicy.SyncPolicy; +import io.realm.objectserver.internal.Token; +import io.realm.objectserver.internal.network.AuthenticateResponse; +import io.realm.objectserver.internal.network.AuthenticationServer; +import io.realm.objectserver.internal.network.NetworkStateReceiver; +import io.realm.log.RealmLog; + +/** + * This class controls the connection to a Realm Object Server for one Realm. + *

    + * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening + * a Realm instance. Once a session has been created it will continue to exist until explicitly closed or the + * underlying Realm file is deleted. + *

    + * It is normally not necessary to interact directly with a session. That should be done by the {@link SyncPolicy} + * defined using {@link io.realm.objectserver.SyncConfiguration.Builder#syncPolicy(SyncPolicy)}. + *

    + * A session has a lifecycle consisting of the following states: + *

    + *

      + *
    1. + * INITIAL Initial state when creating the Session object. No connections to the object server have been + * made yet. At this point it is possible to register any relevant error and event listeners. Calling + * {@link #start()} will cause the session to become unbound and notify the {@link SyncPolicy} that the + * session is ready by calling {@link SyncPolicy#onSessionCreated(Session)}. + *
    2. + *
    3. + * UNBOUND When a session is unbound, no synchronization between the local and remote Realm is happening. + * Call {@link #bind()} to start synchronizing changes. + *
    4. + *
    5. + * BINDING A session is in the process of binding a local Realm to a remote one. Calling {@link #unbind()} + * at this stage, will cancel the process. If binding fails, the session will revert to being unbound and the error + * will be reported to the error handler. + * + * During binding, if a users access has expired, the session will be AUTHENTICATING. During this state, + * Realm will automatically try to acquire new valid credentials. If this succeed BINDING will + * automatically be resumed, if not, the session will become UNBOUND and an appropriate error reported. + *
    6. + *
    7. + * BOUND A bound session has an active connection to the remote Realm and will synchronize any changes + * immediately. + *
    8. + *
    9. + * STOPPED The session has been stopped and no longer work. A new session will be created the next time + * either the Realm is opened or {@link SyncManager#getSession(SyncConfiguration)} is called. + *
    10. + *
    + * + * This object is thread safe. + * + * @see io.realm.objectserver.SyncConfiguration.Builder#syncPolicy(SyncPolicy) + */ +@Keep +public final class Session { + + private final HashMap FSM = new HashMap(); + + // Variables used by the FSM + final SyncConfiguration configuration; + final AuthenticationServer authServer; + private final ErrorHandler errorHandler; + public long nativeSessionPointer; + final User user; + RealmAsyncTask networkRequest; + NetworkStateReceiver.ConnectionListener networkListener; + private SyncPolicy syncPolicy; + + // Keeping track of currrent FSM state + SessionState currentStateDescription; + FsmState currentState; + + /** + /** + * Creates a new Object Server Session + * + * @param syncConfiguration Sync configuration defining this session + * @param authServer Authentication server used to refresh credentials if needed + * @param policy Sync Policy to use by this Session. + */ + public Session(SyncConfiguration syncConfiguration, AuthenticationServer authServer, SyncPolicy policy) { + this.configuration = syncConfiguration; + this.user = configuration.getUser(); + this.authServer = authServer; + this.errorHandler = configuration.getErrorHandler(); + this.syncPolicy = policy; + setupStateMachine(); + } + + private void setupStateMachine() { + FSM.put(SessionState.INITIAL, new InitialState()); + FSM.put(SessionState.UNBOUND, new UnboundState()); + FSM.put(SessionState.BINDING, new BindingState()); + FSM.put(SessionState.AUTHENTICATING, new AuthenticatingState()); + FSM.put(SessionState.BOUND, new BoundState()); + FSM.put(SessionState.STOPPED, new StoppedState()); + RealmLog.debug("Session started: " + configuration.getServerUrl()); + currentState = FSM.get(SessionState.INITIAL); + currentState.entry(this); + } + + // Goto the next state. The FsmState classes are responsible for calling this method as a reaction to a FsmAction + // being called or an internal action triggering a state transition. + void nextState(SessionState nextStateDescription) { + currentState.exit(); + FsmState nextState = FSM.get(nextStateDescription); + if (nextState == null) { + throw new IllegalStateException("No state was configured to handle: " + nextStateDescription); + } + RealmLog.debug("Session[%s]: %s -> %s", configuration.getServerUrl(), currentStateDescription, nextStateDescription); + currentStateDescription = nextStateDescription; + currentState = nextState; + nextState.entry(this); + } + + /** + * Starts the session. This will cause the session to come unbound. {@link #bind()} must be called to + * actually start synchronizing data. + */ + public synchronized void start() { + currentState.onStart(); + } + + /** + * Stops the session. The session can no longer be used. + */ + public synchronized void stop() { + currentState.onStop(); + } + + /** + * Binds the local Realm to the remote Realm. Once bound, changes to either the local or Remote Realm will be + * synchronized immediately. + * + * While this method will return immediately, binding a Realm is not guaranteed to succeed. Possible reasons for + * failure could be either if the device is offline or credentials have expired. Binding is an asynchronous + * operation and all errors will be sent first to {@link SyncPolicy#onError(Session, ObjectServerError)} and if the + * SyncPolicy didn't handle it, to the {@link ErrorHandler} defined by + * {@link SyncConfiguration.Builder#errorHandler(ErrorHandler)}. + */ + public synchronized void bind() { + currentState.onBind(); + } + + /** + * Stops a local Realm from synchronizing changes with the remote Realm. + * + * It is possible to call {@link #bind()} again after a Realm has been unbound. + */ + public synchronized void unbind() { + currentState.onUnbind(); + } + + + /** + * // FIXME This method shouldn't be public + * Notify the session that an error has occurred. + * @param error the kind of err + */ + public synchronized void onError(ObjectServerError error) { + currentState.onError(error); // FSM needs to respond to the error first, before notifying the User + if (errorHandler != null) { + errorHandler.onError(this, error); + } + } + + // Called from Session.cpp and SyncMaanger + // This callback will happen on the thread running the Sync Client. + void notifySessionError(int errorCode, String errorMessage) { + ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); + onError(error); + } + + /** + * Checks if the local Realm is bound to the remote Realm and can synchronize any changes happening on either + * side. + * + * @return {@code true} if the local Realm is bound to the remote Realm, {@code false} otherwise. + */ + public boolean isBound() { + return currentStateDescription == SessionState.BOUND; + } + + // + // Package protected methods used by the FSM states to manipulate session variables. + // + + // Create a native session. The session abstraction in Realm Core doesn't support multiple calls to bind()/unbind() + // yet, so the Java SyncSession must manually create/and close the native sessions as needed. + void createNativeSession() { + nativeSessionPointer = nativeCreateSession(configuration.getPath()); + } + + void stopNativeSession() { + if (nativeSessionPointer != 0) { + nativeUnbind(nativeSessionPointer); + nativeSessionPointer = 0; + } + } + + // Bind with proper access tokens + // Access tokens are presumed to be present and valid at this point + void bindWithTokens() { + Token accessToken = user.getAccessToken(configuration.getServerUrl()); + if (accessToken == null) { + throw new IllegalStateException("User '" + user.toString() + "' does not have an access token for " + + configuration.getServerUrl()); + } + nativeBind(nativeSessionPointer, configuration.getServerUrl().toString(), accessToken.value()); + } + + // Authenticate by getting access tokens for the specific Realm + void authenticateRealm(final Runnable onSuccess, final Session.ErrorHandler errorHandler) { + if (networkRequest != null) { + networkRequest.cancel(); + } + // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { + @Override + public void run() { + int attempt = 0; + boolean success; + ObjectServerError error = null; + while (true) { + attempt++; + long sleep = Util.calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + return; // Abort authentication if interrupted. + } + } + + AuthenticateResponse response = authServer.authenticateRealm( + user.getRefreshToken(), + configuration.getServerUrl(), + user.getAuthenticationUrl() + ); + if (response.isValid()) { + user.addAccessToken(configuration.getServerUrl(), response.getAccessToken()); + success = true; + break; + } else { + // Only retry in case of IO exceptions, since that might be network timeouts etc. + // All other errors indicate a bigger problem, so stop trying to authenticate and + // unbind + ObjectServerError responseError = response.getError(); + if (responseError.errorCode() != ErrorCode.IO_EXCEPTION) { + success = false; + error = responseError; + break; + } + } + } + + if (success) { + onSuccess.run(); + } else { + errorHandler.onError(Session.this, error); + } + } + }); + networkRequest = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); + } + + public boolean isAuthenticated(SyncConfiguration configuration) { + Token token = user.getAccessToken(configuration.getServerUrl()); + return token != null && token.expiresMs() > System.currentTimeMillis(); + } + + public SyncConfiguration getConfiguration() { + return configuration; + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (currentStateDescription != SessionState.STOPPED) { + RealmLog.warn("Session was not closed before being finalized. This is a potential resource leak."); + stop(); + } + } + + private native long nativeCreateSession(String localRealmPath); + private native void nativeBind(long nativeSessionPointer, String remoteRealmUrl, String userToken); + private native void nativeUnbind(long nativeSessionPointer); + private native void nativeRefresh(long nativeSessionPointer, String userToken); + private native void nativeNotifyCommitHappened(long sessionPointer, long version); + + /** + * FIXME: Find a way to keep this out of the public API. Could probably happen as part of moving everything to the + * Object Store. + * + * Notify session that a commit on the device has happened. + */ + public void notifyCommit(long version) { + if (isBound()) { + nativeNotifyCommitHappened(nativeSessionPointer, version); + } + } + + public SyncPolicy getSyncPolicy() { + return syncPolicy; + } + + /** + * Interface used by both the Object Server network client and sessions to report back errors. + * + * @see SyncManager#setDefaultSessionErrorHandler(ErrorHandler) + * @see io.realm.objectserver.SyncConfiguration.Builder#errorHandler(ErrorHandler) + */ + public interface ErrorHandler { + /** + * Callback for errors on this session object. + * Only errors with an ID between 0-99 and 200-299 and will be reported here. + * + * @param session {@link Session} this error happened on. + * @param error type of error. + */ + void onError(Session session, ObjectServerError error); + } +} + diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java b/realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java new file mode 100644 index 0000000000..d1764b75ee --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +/** + * Enum describing the various states the Session Finite-State-Machine can be in. + */ +enum SessionState { + INITIAL, // Initial starting state + UNBOUND, // Start done, Realm is unbound. + BINDING, // bind() has been called. Can take a while. + AUTHENTICATING, // Trying to authenticate credentials. Can take a while. + BOUND, // Local realm was successfully bound to the remote Realm. Changes are being synchronized. + STOPPED // Terminal state. Session can no longer be used. +} + + diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/StoppedState.java b/realm/realm-library/src/main/java/io/realm/objectserver/StoppedState.java new file mode 100644 index 0000000000..0871f454fc --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/StoppedState.java @@ -0,0 +1,64 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +/** + * STOPPED State. This is the final state for a {@link Session}. After this, all actions will throw an + * {@link IllegalStateException}. + */ +class StoppedState extends FsmState { + + @Override + public void onEnterState() { + session.stopNativeSession(); + session.configuration.getSyncPolicy().onSessionStopped(session); + } + + @Override + protected void onExitState() { + // Cannot exit this state + } + + @Override + public void onStart() { + // To harsh to to throw here as any SyncPolicy might not have been made aware + // that the Session is stopped. Just ignore the call instead. + } + + @Override + public void onBind() { + // To harsh to to throw here as any SyncPolicy might not have been made aware + // that the Session is stopped. Just ignore the call instead. + } + + @Override + public void onUnbind() { + // To harsh to to throw here as any SyncPolicy might not have been made aware + // that the Session is stopped. Just ignore the call instead. + } + + @Override + public void onStop() { + // To harsh to to throw here as any SyncPolicy might not have been made aware + // that the Session is stopped. Just ignore the call instead. + } + + @Override + public void onError(ObjectServerError error) { + // Ignore all errors at this state. None of them would have any impact. + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java new file mode 100644 index 0000000000..742c812549 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java @@ -0,0 +1,442 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import android.content.Context; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.RealmMigration; +import io.realm.objectserver.internal.syncpolicy.AutomaticSyncPolicy; +import io.realm.objectserver.internal.syncpolicy.SyncPolicy; +import io.realm.rx.RxObservableFactory; + +/** + * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm + * Object Server. + *

    + * A valid {@link User} is required to create a SyncConfiguration. See {@link Credentials} and + * {@link User#loginAsync(Credentials, String, User.Callback)} for more information on + * how to get a user object. + *

    + * A minimal SyncConfiguration can look like this: + *

    + * {@code
    + * SyncConfiguration config = new SyncConfiguration.Builder(context)
    + *   .serverUrl("realm://objectserver.realm.io/~/default")
    + *   .user(myUser)
    + *   .build();
    + * }
    + * 
    + * + * Realms created using a {@link SyncConfiguration} is accessed normally using {@link Realm#getInstance(RealmConfiguration)} + * and can also be stored using {@link Realm#setDefaultConfiguration(RealmConfiguration)}. + * + * TODO Need to expand this section I think + */ +public final class SyncConfiguration extends RealmConfiguration { + + private final File realmDirectory; + private final String realmFileName; + private final String canonicalPath; + private final URI serverUrl; + private final User user; + private final SyncPolicy syncPolicy; + private final Session.ErrorHandler errorHandler; + + private SyncConfiguration(Builder builder) { + super(builder); + if (builder.serverUrl == null || builder.user == null) { + throw new IllegalStateException("serverUrl() and user() are both required."); + } + + // Check if the user has an identifier, if not, it cannot use /~/. + if (builder.serverUrl.toString().contains("/~/") && builder.user.getIdentifier() == null) { + throw new IllegalStateException("The serverUrl contained a /~/, but the user does not have an identifier," + + " most likely because it hasn't been authenticated yet or have been created directly from an" + + " access token. Use a path without /~/."); + } + + this.user = builder.user; + this.serverUrl = getFullServerUrl(builder.serverUrl, user.getIdentifier()); + this.syncPolicy = builder.syncPolicy; + this.errorHandler = builder.errorHandler; + + // Determine location on disk + // Use the serverUrl + user to create a unique filepath unless it has been explicitly overridden. + // // + File rootDir = builder.overrideDefaultFolder ? super.getRealmDirectory() : builder.defaultFolder; + String realmPath = getServerPath(serverUrl); + this.realmDirectory = new File(rootDir, realmPath); + // Create the folder on disk (if needed) + if (!realmDirectory.exists() && !realmDirectory.mkdirs()) { + throw new IllegalStateException("Could not create directory for saving the Realm: " + realmDirectory); + } + this.realmFileName = builder.overrideDefaultLocalFileName ? super.getRealmFileName() : builder.defaultLocalFileName; + this.canonicalPath = getCanonicalPath(new File(realmDirectory, realmFileName)); + } + + static URI getFullServerUrl(URI serverUrl, String userIdentifier) { + try { + return new URI(serverUrl.toString().replace("/~/", "/" + userIdentifier + "/")); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Could not replace '/~/' with a valid user ID.", e); + } + } + + // Extract the full server path, minus the file name + private String getServerPath(URI serverUrl) { + String path = serverUrl.getPath(); + int endIndex = path.lastIndexOf("/"); + if (endIndex == -1 ) { + return path; + } else if (endIndex == 0) { + return path.substring(1); + } else { + return path.substring(1, endIndex); // Also strip leading / + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + SyncConfiguration that = (SyncConfiguration) o; + + if (realmDirectory != null ? !realmDirectory.equals(that.realmDirectory) : that.realmDirectory != null) return false; + if (realmFileName != null ? !realmFileName.equals(that.realmFileName) : that.realmFileName != null) return false; + if (canonicalPath != null ? !canonicalPath.equals(that.canonicalPath) : that.canonicalPath != null) return false; + if (serverUrl != null ? !serverUrl.equals(that.serverUrl) : that.serverUrl != null) return false; + if (user != null ? !user.equals(that.user) : that.user != null) return false; + if (syncPolicy != null ? !syncPolicy.equals(that.syncPolicy) : that.syncPolicy != null) return false; + return errorHandler != null ? errorHandler.equals(that.errorHandler) : that.errorHandler == null; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (realmDirectory != null ? realmDirectory.hashCode() : 0); + result = 31 * result + (realmFileName != null ? realmFileName.hashCode() : 0); + result = 31 * result + (canonicalPath != null ? canonicalPath.hashCode() : 0); + result = 31 * result + (serverUrl != null ? serverUrl.hashCode() : 0); + result = 31 * result + (user != null ? user.hashCode() : 0); + result = 31 * result + (syncPolicy != null ? syncPolicy.hashCode() : 0); + result = 31 * result + (errorHandler != null ? errorHandler.hashCode() : 0); + return result; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + // TODO + return stringBuilder.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public File getRealmDirectory() { + return this.realmDirectory; + } + + /** + * {@inheritDoc} + */ + @Override + public String getRealmFileName() { + return this.realmFileName; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPath() { + return this.canonicalPath; + } + + // Keeping this package protected for now. The API might still be subject to change. + SyncPolicy getSyncPolicy() { + return syncPolicy; + } + + public User getUser() { + return user; + } + + /** + * Returns the fully disambiguated URI for the remote Realm, i.e. any {@code /~/} placeholder has been replaced + * by the proper user ID. + * + * @return {@link URI} identifying the remote Realm this local Realm is synchronized with. + */ + public URI getServerUrl() { + return serverUrl; + } + + public Session.ErrorHandler getErrorHandler() { + return errorHandler; + } + + /** + * ReplicationConfiguration.Builder used to construct instances of a ReplicationConfiguration in a fluent manner. + */ + public static final class Builder extends RealmConfiguration.Builder { + + private Context context; + private URI serverUrl; + private User user = null; + private SyncPolicy syncPolicy = new AutomaticSyncPolicy(); + private Session.ErrorHandler errorHandler = SyncManager.defaultSessionErrorHandler; + private boolean overrideDefaultFolder = false; + private boolean overrideDefaultLocalFileName = false; + private File defaultFolder; + private String defaultLocalFileName; + + /** + * {@inheritDoc} + */ + public Builder(Context context) { + super(context); + this.context = context; + } + + /** + * Sets the local filename for the Realm. + * This will override the default name defined by the {@link #serverUrl(String)} + * + * @param name name of the local file on disk. + */ + @Override + public Builder name(String name) { + super.name(name); + this.overrideDefaultLocalFileName = true; + return this; + } + + /** + * Sets the local root directory where synchronized Realm files can be saved. + * + * Synchronized Realms will not be saved directly in the provided directory, but instead in a + * subfolder that matches the path defined by {@link #serverUrl(String)}. As Realm server URLs are unique + * this means that multiple users can save their Realms on disk without the risk of them overriding each other. + * + * The default location is {@code context.getFilesDir()}. + * + * @param dir directory on disk where the Realm file can be saved. + * @throws IllegalArgumentException if the directory is not valid. + */ + public Builder directory(File dir) { + super.directory(dir); + overrideDefaultFolder = true; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Builder encryptionKey(byte[] key) { + super.encryptionKey(key); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Builder schemaVersion(long schemaVersion) { + super.schemaVersion(schemaVersion); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Builder deleteRealmIfMigrationNeeded() { + super.deleteRealmIfMigrationNeeded(); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public SyncConfiguration.Builder inMemory() { + super.inMemory(); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Builder modules(Object baseModule, Object... additionalModules) { + super.modules(baseModule, additionalModules); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public SyncConfiguration.Builder rxFactory(RxObservableFactory factory) { + super.rxFactory(factory); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Builder initialData(Realm.Transaction transaction) { + super.initialData(transaction); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Builder assetFile(String assetFile) { + super.assetFile(assetFile); + return this; + } + + /** + * Manual migrations are not supported (yet) for Realms that can be synced using the Realm Object Server + * Only additive changes are allowed, and these will be detected and applied automatically. + * + * @throws IllegalArgumentException always. + */ + @Override + public Builder migration(RealmMigration migration) { + throw new IllegalArgumentException("Migrations are not supported for Realms that can be synchronized using the Realm Mobile Platform"); + } + + /** + * Enable server side synchronization for this Realm. The name should be a unique URL that identifies the Realm. + * {@code /~/} can be used as a placeholder for a user ID in case the Realm should only be available to one + * user, e.g. {@code "realm://objectserver.realm.io/~/default"} + * + * The `/~/` will automatically be replaced with the user ID when creating the {@link SyncConfiguration}. + * + * The URL also defines the local location on the device. The default location of a synchronized Realm file is + * {@code /data/data//files/realm-object-server//}. + * + * This behaviour can be overwritten using {@link #name(String)} and {@link #directory(File)}. + * + * @param url URL identifying the Realm. + * @throws IllegalArgumentException if the URL is not valid. + */ + public Builder serverUrl(String url) { + if (url == null) { + throw new IllegalArgumentException("Non-null 'url' required."); + } + try { + this.serverUrl = new URI(url); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid url: " + url, e); + } + + // Detect last path segment as it is the default file name + String path = serverUrl.getPath(); + if (path == null) { + throw new IllegalArgumentException("Invalid url: " + url); + } + + String[] pathSegments = path.split("/"); + this.defaultLocalFileName = pathSegments[pathSegments.length - 1]; + + // Validate filename + // TODO Lift this restriction on the Object Server + if (defaultLocalFileName.endsWith(".realm")) { + throw new IllegalArgumentException("The URL must not end with '.realm': " + url); + } + + return this; + } + + /** + * Set the user for this Realm. An authenticated {@link User} is required to open any Realm managed by a + * Realm Object Server. + * + * @param user {@link User} who wants to access this Realm. + */ + public Builder user(User user) { + if (user == null) { + throw new IllegalArgumentException("Non-null `user` required."); + } + if (!user.isAuthenticated()) { + throw new IllegalArgumentException("User not authenticated or authentication expired. User ID: " + user.getIdentifier()); + } + + this.defaultFolder = new File(context.getFilesDir(), "realm-object-server"); + this.user = user; + return this; + } + + /** + * Sets the sync policy used to control when changes should be synchronized with the remote Realm. + * The default policy is {@link AutomaticSyncPolicy}. + * + * @param syncPolicy policy to use. + * + * @see Session + */ + Builder syncPolicy(SyncPolicy syncPolicy) { + // TODO: Decide if we should launch with this as package protected since the sync notification API might + // change quite a bit. + this.syncPolicy = syncPolicy; + return this; + } + + /** + * Sets the error handler used by this configuration. + * This will override any handler set by calling {@link SyncManager#setDefaultSessionErrorHandler(Session.ErrorHandler)}. + * + * Only errors not handled by the defined {@link SyncPolicy} will be reported to this error handler. + * + * @param errorHandler error handler used to report back errors when communicating with the Realm Object Server. + * @throws IllegalArgumentException if {@code null} is given as an error handler. + */ + public Builder errorHandler(Session.ErrorHandler errorHandler) { + if (errorHandler == null) { + throw new IllegalArgumentException("Non-null 'errorHandler' required."); + } + this.errorHandler = errorHandler; + return this; + } + + /** + * Creates the RealmConfiguration based on the builder parameters. + * + * @return the created {@link SyncConfiguration}. + */ + public SyncConfiguration build() { + return new SyncConfiguration(this); + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java new file mode 100644 index 0000000000..3a1171c9f7 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java @@ -0,0 +1,134 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import io.realm.internal.Keep; +import io.realm.internal.RealmCore; +import io.realm.objectserver.internal.SessionStore; +import io.realm.objectserver.internal.network.AuthenticationServer; +import io.realm.objectserver.internal.network.OkHttpAuthenticationServer; +import io.realm.log.RealmLog; + +/** + * The SyncManager is the central controller for interacting with the Realm Object Server. + * It handles the creation of {@link Session}s and it is possible to configure session defaults and the underlying + * network client using this class. + * + * // TODO Rewrite this section. + */ +@Keep +public final class SyncManager { + + public static final String APP_ID = "foo"; // FIXME Find a way to get an application ID + // Thread pool used when doing network requests against the Realm Authentication Server. + // FIXME Set proper parameters + public static final ThreadPoolExecutor NETWORK_POOL_EXECUTOR = new ThreadPoolExecutor( + 10, 10, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(100)); + + private static final Session.ErrorHandler SESSION_NO_OP_ERROR_HANDLER = new Session.ErrorHandler() { + @Override + public void onError(Session session, ObjectServerError error) { + String errorMsg = String.format("Session Error[%s]: %s", + session.getConfiguration().getServerUrl(), + error.toString()); + switch (error.errorCode().getCategory()) { + case FATAL: + RealmLog.error(errorMsg); + break; + case RECOVERABLE: + RealmLog.info(errorMsg); + break; + case INFO: + RealmLog.debug(errorMsg); + break; + } + } + }; + + // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. + // Right now it just lives and dies together with the process. + private static volatile AuthenticationServer authServer = new OkHttpAuthenticationServer(); + static volatile Session.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; + + static { + RealmCore.loadLibrary(); + nativeInitializeSyncClient(); + } + + /** + * Sets the default error handler used by all {@link SyncConfiguration} objects when they are created. + * + * @param errorHandler the default error handler used when interacting with a Realm managed by a Realm Object Server. + */ + public static void setDefaultSessionErrorHandler(Session.ErrorHandler errorHandler) { + if (errorHandler == null) { + defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; + } else { + defaultSessionErrorHandler = errorHandler; + } + } + + /** + * Gets any cached {@link Session} for the given {@link SyncConfiguration} or create a new one if + * no one exists. + * + * @param syncConfiguration configuration object for the synchronized Realm. + * @return the {@link Session} for the specified Realm. + */ + public static synchronized Session getSession(SyncConfiguration syncConfiguration) { + return SessionStore.getSession(syncConfiguration); + } + + public static AuthenticationServer getAuthServer() { + return authServer; + } + + /** + * TODO Internal only? Developers can also use this to inject stubs. + * TODO Find a better method name. + *

    + * Sets the auth server implementation used when validating credentials. + */ + static void setAuthServerImpl(AuthenticationServer authServerImpl) { + authServer = authServerImpl; + } + + // This is called from SyncManager.cpp from the worker thread the Sync Client is running on + // Right now Core doesn't send these errors to the proper session, so instead we need to notify all sessions + // from here. This can be removed once better error propagation is implemented in Sync Core. + private static void notifyErrorHandler(int errorCode, String errorMessage) { + ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); + for (Session session : SessionStore.getSession()) { + session.onError(error); + } + } + + /** + * Sets the log level for the underlying + * @param logLevel + */ + public static void setLogLevel(int logLevel) { + nativeSetSyncClientLogLevel(logLevel); + } + + private static native void nativeInitializeSyncClient(); + private static native void nativeSetSyncClientLogLevel(int logLevel); +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/UnboundState.java b/realm/realm-library/src/main/java/io/realm/objectserver/UnboundState.java new file mode 100644 index 0000000000..89b1a33588 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/UnboundState.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +/** + * UNBOUND State. This is the default state after a session has been started and no attempt at binding the local Realm + * has been made. + */ +class UnboundState extends FsmState { + + @Override + public void onEnterState() { + // We can enter this state from multiple states which might have had an active session. + // In those cases cleanup any old native session + session.stopNativeSession(); + + // Create the native session so it is ready to be bound. + session.createNativeSession(); + } + + @Override + protected void onExitState() { + // Do nothing. + } + + @Override + public void onBind() { + gotoNextState(SessionState.BINDING); + } + + @Override + public void onError(ObjectServerError error) { + // Ignore all errors at this state. None of them would have any impact. + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/User.java b/realm/realm-library/src/main/java/io/realm/objectserver/User.java new file mode 100644 index 0000000000..bf3e2a176c --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/User.java @@ -0,0 +1,338 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import io.realm.RealmAsyncTask; +import io.realm.internal.IOException; +import io.realm.internal.Util; +import io.realm.objectserver.internal.Token; +import io.realm.objectserver.internal.network.AuthenticateResponse; +import io.realm.objectserver.internal.network.AuthenticationServer; +import io.realm.objectserver.internal.network.RefreshResponse; +import io.realm.log.RealmLog; + +/** + * This class represents a user on the Realm Object Server. + * + * + * TODO Rewrite this section + */ +public class User { + + // Time left on current refresh token, when we want to begin refreshing it. + // Failing to refresh it before it expires, will result in the user getting logged out. + private static RealmAsyncTask authenticateTask; + private RealmAsyncTask refreshTask; + + private final String identifier; + private Token refreshToken; + private URL authentificationUrl; + private Map accessTokens = new HashMap(); + + /** + * Creates a User only known to this device. + * @return + */ + public static User createLocal() { + Token token = new Token(UUID.randomUUID().toString(), Long.MAX_VALUE, Token.Permission.values()); + return new User(UUID.randomUUID().toString(), token, null); + } + + /** + * Load a user that has previously been serialized using {@link #toJson()}. + * + * @param user JSON string representing the user. + * + * @return the user object. + * @throws IllegalArgumentException if the JSON couldn't be converted to a valid {@link User} object. + */ + public static User fromJson(String user) { + try { + JSONObject obj = new JSONObject(user); + String id = obj.getString("identifier"); + Token refreshToken = Token.from(obj.getJSONObject("refreshToken")); + URL authUrl = new URL(obj.getString("authUrl")); + // FIXME: Add support for storing access tokens as well + return new User(id, refreshToken, authUrl); + } catch (JSONException e) { + throw new IllegalArgumentException("Could not parse user json: " + user, e); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("URL in JSON not valid: " + user, e); + } + } + + /** + * Creates a user from an existing token. This user is automatically consider validated by the device, but the + * Realm Object Server might determine that the token has expired or no longer is valid. + * + * This should only be used when debugging or testing. In most other cases the user object obtained from a + * {@link #loginAsync(Credentials, String, Callback)} should be saved and reused. This can e.g. be done using a + * {@link UserStore}. + * + * @param token token to represent user. + */ + public static User fromToken(String token) { + // Define a user with unlimited access. Object Server will reject any invalid access anyway. + return new User(null, new Token(token, Long.MAX_VALUE, Token.Permission.values()), null); + } + + public static User login(final Credentials credentials, final URL authentificationUrl) + throws ObjectServerError { + return null; // TODO + } + + /** + * Login the user on the Realm Object Server + * + * @param credentials credentials to use + * @param authenticationUrl URL to authenticateUser against + * @param callback callback when login has completed or failed. This callback will always happen on the UI thread. + * @throws IllegalArgumentException + */ + // FIXME Return task that can be canceled + public static RealmAsyncTask loginAsync(final Credentials credentials, final String authenticationUrl, final Callback callback) { + final URL authUrl; + try { + authUrl = new URL(authenticationUrl); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e); + } + if (Looper.myLooper() == null) { + throw new IllegalStateException("Asynchronous login is only possible from looper threads."); + } + + final Handler handler = new Handler(Looper.myLooper()); + + final AuthenticationServer server = SyncManager.getAuthServer(); + Future authenticateRequest = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { + @Override + public void run() { + // Don't retry authenticateUser requests. The app might want to respond to errors. + try { + AuthenticateResponse result = server.authenticateUser(credentials, authUrl, credentials.shouldCreateUser()); + if (result.isValid()) { + User user = new User(result.getIdentifier(), result.getRefreshToken(), authUrl); + postSuccess(user); + } else { + postError(result.getError()); + } + } catch (IOException e) { + postError(new ObjectServerError(ErrorCode.IO_EXCEPTION, e)); + } + } + + private void postError(final ObjectServerError error) { + RealmLog.info("Failed authenticating user.\n%s", error); + if (callback != null) { + handler.post(new Runnable() { + @Override + public void run() { + callback.onError(error); + } + }); + } + } + + private void postSuccess(final User user) { + RealmLog.info("Succeeded authenticating user.\n%s", user); + if (callback != null) { + handler.post(new Runnable() { + @Override + public void run() { + callback.onSuccess(user); + } + }); + } + } + }); + authenticateTask = new RealmAsyncTask(authenticateRequest, SyncManager.NETWORK_POOL_EXECUTOR); + return authenticateTask; + } + + private User(String identifier, Token refreshToken, URL authenticationUrl) { + this.identifier = identifier; + this.authentificationUrl = authenticationUrl; + setRefreshToken(refreshToken); + } + + void setRefreshToken(final Token refreshToken) { + if (refreshTask != null) { + refreshTask.cancel(); + refreshTask = null; + } + this.refreshToken = refreshToken; + + if (authentificationUrl == null) { + return; + } + // Schedule a refresh. This method cannot fail, but will continue retrying until either the app is killed + // or the attempt was successful. + // TODO Consider combining refresh across all users? + final long expire = refreshToken.expiresMs(); + final AuthenticationServer server = SyncManager.getAuthServer(); + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { + @Override + public void run() { + long timeToExpiration = System.currentTimeMillis() - expire; + if (timeToExpiration > 0) { + SystemClock.sleep(timeToExpiration); + } + + int attempt = 0; + while (!Thread.interrupted()) { + attempt++; + long sleep = Util.calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + return; // Abort authentication if interrupted. + } + } + try { + RefreshResponse result = server.refresh(refreshToken.value(), authentificationUrl); + if (result.isValid()) { + setRefreshToken(result.getRefreshToken()); + break; + } else { + // FIXME: Log to session events instead + RealmLog.warn("Refreshing login failed: " + result.getErrorCode() + " : " + result.getErrorMessage()); + } + } catch (IOException e) { + // FIXME: Log to session events instead. + RealmLog.info("Refreshing login failed: " + e.toString()); + } + } + } + }); + refreshTask = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); + } + + /** + * Returns true if the User is authenticated by the Realm Object Server. Being authenticated means that the + * user is know by the Realm Object Server, but nothing about which Realms that user might have access to and with + * what kind of permissions. + */ + public boolean isAuthenticated() { + return refreshToken != null && refreshToken.expiresMs() > System.currentTimeMillis(); + } + + public void logout() { + // TODO Stop any session + // TODO Clear all tokens + } + + /** + * Returns a JSON token representing this user. + * + * Possession of this JSON token can potentially grant access to data stored on the Realm Object Server, so it + * should be treated as sensitive data. + * + * @return JSON string representing this user. It can be converted back into a real user object using + * {@link #fromJson(String)}. + * + * @see #fromJson(String) + */ + public String toJson() { + JSONObject obj = new JSONObject(); + try { + obj.put("identifier", identifier); + obj.put("refreshToken", refreshToken.toJson()); + obj.put("authUrl", authentificationUrl); + // FIXME: Add support for storing access tokens as well + return obj.toString(); + } catch (JSONException e) { + throw new RuntimeException("Could not convert User to JSON", e); + } + } + + public String getIdentifier() { + return identifier; + } + + /** + * Return the access token for the given Realm or {@code null} if no token exists. + */ + Token getAccessToken(URI serverUrl) { + return accessTokens.get(serverUrl); + } + + void addAccessToken(URI uri, Token accessToken) { + accessTokens.put(uri, accessToken); + } + + /** + * Adds an access token to this user. + *

    + * An access token is a token granting access to one remote Realm. They are normally fetched transparently when + * opening a Realm, but using this method it is possible to add tokens upfront if they have been fetched or + * created manually. + * + * @param uri {@link java.net.URI} pointing to a remote Realm. + * @param accessToken + */ + void addAccessToken(URI uri, String accessToken) { + // TODO Currently package protected as we will be unifying the tokens shortly, so each user only has one + // access token that can be used everywhere. Permissions/access are then fully handled by the Object Server. + if (uri == null || accessToken == null) { + throw new IllegalArgumentException("Non-null 'uri' and 'accessToken' required."); + } + uri = SyncConfiguration.getFullServerUrl(uri, identifier); + + // Optimistically create a long-lived token with all permissions. If this is incorrect the Object Server + // will reject it anyway. If tokens are added manually it is up to the user to ensure they are also used + // correctly. + addAccessToken(uri, new Token(accessToken, Long.MAX_VALUE, Token.Permission.values())); + } + + + URL getAuthenticationUrl() { + return authentificationUrl; + } + + // TODO Figure out how to make this non-public + public Token getRefreshToken() { + return refreshToken; + } + + @Override + public String toString() { + return super.toString(); + // FIXME Print representation of user, but be careful about printing anything sensitive + } + + public interface Callback { + void onSuccess(User user); + void onError(ObjectServerError error); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java b/realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java new file mode 100644 index 0000000000..96a21e91ae --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import io.realm.objectserver.android.SharedPrefsUserStore; + +/** + * Interface for describing how a given user object can be persisted and retrieved again. + * + * @see SharedPrefsUserStore + */ +public interface UserStore { + + /** + * Saves a User object under the given key. If another user already exists, it will be replaced. + * + * @param key Key used to store the User. The same key is used to retrieve it again + * @param user User object to store. + */ + boolean save(String key, User user); + + /** + * Saves a User object under the given key. If another user already exists, it will be replaced. + * + * @param key + * @param user + */ + void saveAsync(String key, User user); + + /** + * TODO + * @param key + * @param user + */ + void saveASync(String key, User user, Callback callback); + + /** + * TODO + * @param key + */ + User load(String key); + + /** + * TODO + * @param key + */ + void loadAsync(String key, Callback callback); + + + /** + * Interface responsible for handling the result of asynchronously saving or loading the user. + */ + interface Callback { + /** + * User was successfully saved or loaded. + */ + void onSuccess(User user); + + /** + * The user could not be saved or loaded. + */ + void onError(Throwable t); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java b/realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java new file mode 100644 index 0000000000..7d89453a96 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java @@ -0,0 +1,158 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.android; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.Executor; + +import io.realm.log.RealmLog; +import io.realm.objectserver.User; +import io.realm.objectserver.UserStore; + +/** + * A User Store backed by a SharedPreferences file. + */ +public class SharedPrefsUserStore implements UserStore { + + public static final Executor THREAD_POOL; + private final SharedPreferences sp; + private Handler handler = new Handler(Looper.getMainLooper()); + + static { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + THREAD_POOL = AsyncTask.THREAD_POOL_EXECUTOR; + } else { + throw new UnsupportedOperationException("FIXME: Not supported yet. Realm.asyncTaskExecutor must be public first"); + // THREAD_POOL = Realm.asyncTaskExecutor; // FIXME Do this better + } + } + + public SharedPrefsUserStore(Context context) { + sp = context.getSharedPreferences("realm_object_server_users", Context.MODE_PRIVATE); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean save(String key, User user) { + SharedPreferences.Editor editor = sp.edit(); + editor.putString(key, user.toJson()); + return editor.commit(); + } + + /** + * {@inheritDoc} + */ + @Override + public void saveAsync(String key, User user) { + saveASync(key, user, null); + } + + /** + * {@inheritDoc} + */ + @Override + public void saveASync(final String key, final User user, final Callback callback) { + THREAD_POOL.execute(new Runnable() { + @Override + public void run() { + boolean success; + Throwable error = null; + try { + success = save(key, user); + if (!success) { + error = new RuntimeException("Could not save key"); + } + } catch (Exception e) { + success = false; + error = e; + RealmLog.error("Failed to save user", e); + } + if (callback != null) { + final boolean finalSuccess = success; + final Throwable finalError = error; + handler.post(new Runnable() { + @Override + public void run() { + if (finalSuccess) { + callback.onSuccess(user); + } else { + callback.onError(finalError); + } + } + }); + } + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public User load(String key) { + String userData = sp.getString(key, ""); + if (userData.equals("")) { + return null; + } + return User.fromJson(userData); + } + + /** + * {@inheritDoc} + */ + @Override + public void loadAsync(final String key, final Callback callback) { + THREAD_POOL.execute(new Runnable() { + @Override + public void run() { + User user = null; + Throwable error = null; + try { + user = load(key); + if (user == null) { + error = new RuntimeException("Could not load user:" + key); + } + } catch (Exception e) { + error = e; + RealmLog.error("Failed to save user", e); + } + if (callback != null) { + final User finalUser = user; + final Throwable finalError = error; + handler.post(new Runnable() { + @Override + public void run() { + if (finalUser != null) { + callback.onSuccess(finalUser); + } else { + callback.onError(finalError); + } + } + }); + } + } + }); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java new file mode 100644 index 0000000000..1726c13e50 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java @@ -0,0 +1,64 @@ +package io.realm.objectserver.internal; + +import io.realm.RealmConfiguration; +import io.realm.objectserver.Session; +import io.realm.objectserver.SyncConfiguration; + +/** + * Class acting as an mediator between the basic Realm APIs and the Object Server APIs. + * This breaks the cyclic dependency between ObjectServer and Realm code. + * + * TODO Move this class into a `common` module that both realm-library and objectserver-library depends on. + */ +public class ObjectServerFacade { + + public static final boolean SYNC_AVAILABLE; + + static { + boolean syncAvailable; + try { + Class.forName("io.realm.objectserver.SyncManager"); + syncAvailable = true; + } catch (ClassNotFoundException e) { + syncAvailable = false; + } + SYNC_AVAILABLE = syncAvailable; + } + /** + * Notify the session for this configuration that a local commit was made. + */ + public static void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { + if (SYNC_AVAILABLE && configuration instanceof SyncConfiguration) { + Session session = SessionStore.getSession((SyncConfiguration) configuration); + session.notifyCommit(lastSnapshotVersion); + } + } + + public static void realmClosed(RealmConfiguration configuration) { + if (SYNC_AVAILABLE && configuration instanceof SyncConfiguration) { + SyncConfiguration syncConfig = (SyncConfiguration) configuration; + Session session = SessionStore.getSession(syncConfig); + session.getSyncPolicy().onRealmClosed(session); + } + } + + public static void realmOpened(RealmConfiguration configuration) { + if (SYNC_AVAILABLE && configuration instanceof SyncConfiguration) { + SyncConfiguration syncConfig = (SyncConfiguration) configuration; + Session session = SessionStore.getSession(syncConfig); + session.getSyncPolicy().onRealmOpened(session); + } + + } + + public static String[] getUserAndServerUrl(RealmConfiguration config) { + if (SYNC_AVAILABLE && config instanceof SyncConfiguration) { + SyncConfiguration syncConfig = (SyncConfiguration) config; + String rosServerUrl = syncConfig.getServerUrl().toString(); + String rosUserToken = syncConfig.getUser().getRefreshToken().value(); + return new String[] {rosServerUrl, rosUserToken}; + } else { + return new String[2]; + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java new file mode 100644 index 0000000000..3fbe7b088e --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java @@ -0,0 +1,71 @@ +package io.realm.objectserver.internal; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import io.realm.objectserver.Session; +import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.SyncManager; +import io.realm.objectserver.internal.syncpolicy.AutomaticSyncPolicy; + +/** + * Private class for keeping track of sessions. + * If {@link io.realm.objectserver.Session} moves into the public API at some point, this class can be folded into + * {@link io.realm.objectserver.SyncManager}; + */ +public class SessionStore { + + // Map of between a local Realm path and any associated sessionInfo + private static HashMap sessions = new HashMap(); + + + /** + * Gets any cached {@link Session} for the given {@link SyncConfiguration} or create a new one if + * no one exists. + * + * @param syncConfiguration configuration object for the synchronized Realm. + * @return the {@link Session} for the specified Realm. + */ + public static synchronized Session getSession(SyncConfiguration syncConfiguration) { + if (syncConfiguration == null) { + throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); + } + + String localPath = syncConfiguration.getPath(); + Session session = sessions.get(localPath); + if (session == null) { + session = new Session(syncConfiguration, SyncManager.getAuthServer(), new AutomaticSyncPolicy()); + session.getSyncPolicy().onSessionCreated(session); + sessions.put(localPath, session); + } + + return session; + } + + /** + * Removes a session. Should only be once it has been closed. + */ + static synchronized void removeSession(Session session) { + if (session == null) { + return; + } + + Iterator> it = sessions.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + if (entry.getValue().equals(session)) { + it.remove(); + break; + } + } + } + + /** + * Returns a list of all sessions being tracked. + */ + public static Collection getSession() { + return sessions.values(); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java new file mode 100644 index 0000000000..99bcaeb304 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java @@ -0,0 +1,109 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Arrays; +import java.util.Locale; + +/** + * This class represents a value from the Realm Authentication Server. + */ +public class Token { + + private final String value; + private final long expires; + private final Permission[] permissions; + + public static Token from(JSONObject token) throws JSONException { + String value = token.getString("token"); + long expires = token.getLong("expires"); + Permission[] permissions; + JSONArray access = token.getJSONArray("access"); + if (access != null) { + permissions = new Permission[access.length()]; + for (int i = 0; i < access.length(); i++) { + try { + permissions[i] = Permission.valueOf(access.getString(i)); + } catch (IllegalArgumentException e) { + permissions[i] = Permission.UNKNOWN; + } + } + } else { + permissions = new Permission[0]; + } + + return new Token(value, expires, permissions); + } + + public Token(String value, long expires, Permission... permissions) { + this.value = value; + this.expires = expires; + this.permissions = Arrays.copyOf(permissions, permissions.length); + } + + public String value() { + return value; + } + + /** + * Returns when this token expiresSec. Timestamp is in UTC seconds. + */ + public long expiresSec() { + return expires; + } + + public long expiresMs() { + long expiresMs = expires * 1000; + if (expiresMs < expires) { + return Long.MAX_VALUE; // Overflow + } else { + return expiresMs; + } + } + + public Permission[] permissions() { + return Arrays.copyOf(permissions, permissions.length); + } + + public String toJson() { + JSONObject obj = new JSONObject(); + try { + obj.put("token", value); + obj.put("expires", expires); + JSONArray perms = new JSONArray(); + for (int i = 0; i < permissions.length; i++) { + perms.put(permissions[i].toString().toLowerCase(Locale.US)); + } + obj.put("access", perms); + return obj.toString(); + } catch (JSONException e) { + throw new RuntimeException("Could not convert Token to JSON.", e); + } + } + + public enum Permission { + UNKNOWN, + UPLOAD, + DOWNLOAD, + REFRESH, + MANAGE; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java new file mode 100644 index 0000000000..5522d66056 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import io.realm.objectserver.internal.Token; +import io.realm.objectserver.Credentials; +import io.realm.objectserver.SyncManager; + +/** + * This class encapsulates a request to authenticate a user on the Realm Authentication Server. It is responsible for + * constructing the JSON understood by the Realm Authentication Server. + */ +public class AuthenticateRequest { + + private final Provider provider; + private final String data; + private final String appId; + private final Map userInfo; + private final String path; + + /** + * Generates a proper login request for a new user. + */ + public static AuthenticateRequest fromCredentials(Credentials credentials, boolean createUser) { + if (credentials == null) { + throw new IllegalArgumentException("Non-null credentials required."); + } + Provider provider; + String data; + String appId = SyncManager.APP_ID; + Map userInfo = new HashMap(); + userInfo.put("register", createUser); + + switch (credentials.getLoginType()) { + case FACEBOOK: + provider = Provider.FACEBOOK; + data = credentials.getField1(); + break; + case USERNAME_PASSWORD: + provider = Provider.PASSWORD; + data = credentials.getField1(); + userInfo.put("password", credentials.getField2()); + break; + default: + throw new IllegalArgumentException("Login type not supported: " + credentials.getLoginType()); + } + + return new AuthenticateRequest(provider, data, appId, null, userInfo); + } + + /** + * Authenticate access to a given Realm using an already logged in user. + * + * @param refreshToken Users refresh token + * @param path Path of the Realm to gain access to. + */ + public static AuthenticateRequest fromRefreshToken(Token refreshToken, URI path) { + // Authenticate a given Realm path using an already logged in user. + return new AuthenticateRequest(Provider.REALM, + refreshToken.value(), + SyncManager.APP_ID, + path.getPath(), + Collections.emptyMap() + ); + } + + /** + * Create an admin user request. Admin access gives access to all Realms. Admin access is disabled if the + * Authentication Server is in production mode. + */ + public static AuthenticateRequest adminAccess() { + return debug("admin", null); + } + + /** + * Creates an debug user request. Debug users are automatically logged into the Realm Authentication Server, and + * will always be granted access. Debug users are disabled if the Authentication Server is in production mode. + */ + public static AuthenticateRequest debug(String username, String path) { + return new AuthenticateRequest( + Provider.DEBUG, + username, + SyncManager.APP_ID, + path, + Collections.emptyMap() + ); + } + + private AuthenticateRequest(Provider provider, String data, String appId, String path, Map userInfo) { + this.provider = provider; + this.data = data; + this.appId = appId; + this.path = path; + this.userInfo = userInfo; + } + + /** + * Converts the request into a JSON payload. + */ + public String toJson() { + JSONObject request = new JSONObject(); + try { + request.put("provider", provider.getProvider()); + request.put("data", data); + request.put("app_id", appId); + if (path != null) { + request.put("path", path); + } + request.put("user_info", new JSONObject(userInfo)); + } catch (JSONException e) { + throw new RuntimeException(e); + } + + return request.toString(); + } + + private enum Provider { + REALM("realm"), // Used if you already have a valid refresh token + DEBUG("debug"), // Will always succeed + PASSWORD("password"), // password/username login + FACEBOOK("facebook"); // facebook login + + private final String provider; + + Provider(String provider) { + this.provider = provider; + } + + public String getProvider() { + return provider; + } + } + +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java new file mode 100644 index 0000000000..cfd9822369 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java @@ -0,0 +1,147 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import io.realm.log.RealmLog; +import io.realm.objectserver.ErrorCode; +import io.realm.objectserver.internal.Token; +import io.realm.objectserver.ObjectServerError; +import okhttp3.Response; + +/** + * This class represents the response for a authenticate request. + */ +public class AuthenticateResponse { + + private final ObjectServerError error; + private final String identifier; + private final String path; + private final String appId; + private final Token accessToken; + private final Token refreshToken; + + /** + * Helper method for creating the proper Authenticate response. This method will set the appropriate error + * depending on any HTTP response codes or IO errors. + */ + public static AuthenticateResponse createFrom(Response response) { + String serverResponse; + try { + serverResponse = response.body().string(); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new AuthenticateResponse(error); + } + RealmLog.debug("Authenticate response: " + serverResponse); + if (response.code() != 200) { + try { + JSONObject obj = new JSONObject(serverResponse); + String type = obj.getString("type"); + String hint = obj.optString("hint", null); + ErrorCode errorCode = ErrorCode.fromAuthError(type); + ObjectServerError error = new ObjectServerError(errorCode, hint); + return new AuthenticateResponse(error); + } catch (JSONException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, "Server failed with " + + response.code() + ", but could not parse error.", e); + return new AuthenticateResponse(error); + } + } else { + return new AuthenticateResponse(serverResponse); + } + } + + /** + * Create a unsuccessful authentication response. This should only happen in case of network / IO problems. + */ + public AuthenticateResponse(ObjectServerError error) { + this.error = error; + this.identifier = null; + this.path = null; + this.appId = null; + this.accessToken = null; + this.refreshToken = null; + } + + /** + * Parse a valid (200) server response. It might still result in a unsuccessful authentication attempt, if the + * JSON response could not be parsed correctly. + */ + public AuthenticateResponse(String serverResponse) { + ObjectServerError error; + String identifier; + String path; + String appId; + Token accessToken; + Token refreshToken; + try { + JSONObject obj = new JSONObject(serverResponse); + identifier = obj.getString("identity"); + path = obj.optString("path"); + appId = obj.optString("app_id"); // FIXME No longer sent? + accessToken = obj.has("token") ? Token.from(obj) : null; + refreshToken = obj.has("refresh") ? Token.from(obj.getJSONObject("refresh")) : null; + error = null; + } catch (JSONException ex) { + identifier = null; + path = null; + appId = null; + accessToken = null; + refreshToken = null; + error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, ex); + } + this.identifier = identifier; + this.path = path; + this.appId = appId; + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.error = error; + } + + public boolean isValid() { + return (error == null); + } + + public ObjectServerError getError() { + return error; + } + + public String getIdentifier() { + return identifier; + } + + public String getPath() { + return path; + } + + public String getAppId() { + return appId; + } + + public Token getAccessToken() { + return accessToken; + } + + public Token getRefreshToken() { + return refreshToken; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java new file mode 100644 index 0000000000..eb17182e6f --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.network; + +import java.net.URI; +import java.net.URL; + +import io.realm.objectserver.internal.Token; +import io.realm.objectserver.Credentials; + +/** + * Interface for handling communication with the Realm Object Server. + * + * Note, any implementation of this class is not responsible for handling retries or error handling, it is + * only responsible for executing a given network request. + */ +public interface AuthenticationServer { + AuthenticateResponse authenticateUser(Credentials credentials, URL authenticationUrl, boolean createUser); + AuthenticateResponse authenticateRealm(Token refreshToken, URI path, URL authenticationUrl); + RefreshResponse refresh(String token, URL authenticationUrl); +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/NetworkStateReceiver.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/NetworkStateReceiver.java new file mode 100644 index 0000000000..2a79eceea1 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/NetworkStateReceiver.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.network; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import io.realm.internal.Util; + +/** + * This class is responsible for keeping track of system events related to the network so it can delegate them to + * interested parties. + */ +public class NetworkStateReceiver extends BroadcastReceiver { + + private static List listeners = new CopyOnWriteArrayList(); + + /** + * Add a listener to be notified about any network changes. + * This method is thread safe. + *

    + * IMPORTANT: Not removing it again will result in major leaks. + */ + public static void addListener(ConnectionListener listener) { + listeners.add(listener); + } + + /** + * Removes a network listener. + * This method is thread safe. + */ + public static synchronized void removeListener(ConnectionListener listener) { + listeners.remove(listener); + } + + /** + * Attempt to detect if a device is online and can transmit or receive data. + * This method is thread safe. + *

    + * The Emulator is always considered online, as `getActiveNetworkInfo()` does not report the correct value. + */ + public static boolean isOnline(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + return ((networkInfo != null && networkInfo.isConnectedOrConnecting()) || Util.isEmulator()); + } + + + public void onReceive(Context context, Intent intent) { + boolean connected = isOnline(context); + for (ConnectionListener listener : listeners) { + listener.onChange(connected); + } + } + + public interface ConnectionListener { + void onChange(boolean connectionAvailable); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java new file mode 100644 index 0000000000..2185496d1b --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.network; + +import java.net.URI; +import java.net.URL; +import java.util.concurrent.TimeUnit; + +import io.realm.internal.Util; +import io.realm.objectserver.ErrorCode; +import io.realm.objectserver.internal.Token; +import io.realm.objectserver.Credentials; +import io.realm.objectserver.ObjectServerError; +import okhttp3.Call; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class OkHttpAuthenticationServer implements AuthenticationServer { + + public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + + private final OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build(); + + /** + * Authenticate the given credentials on the specified Realm Authentication Server. + */ + @Override + public AuthenticateResponse authenticateUser(Credentials credentials, URL authenticationUrl, boolean createUser) { + try { + String requestBody = AuthenticateRequest.fromCredentials(credentials, createUser).toJson(); + return authenticate(authenticationUrl, requestBody); + } catch (Exception e) { + return new AuthenticateResponse(new ObjectServerError(ErrorCode.OTHER_ERROR, Util.getStackTrace(e))); + } + } + + @Override + public AuthenticateResponse authenticateRealm(Token refreshToken, URI path, URL authenticationUrl) { + try { + String requestBody = AuthenticateRequest.fromRefreshToken(refreshToken, path).toJson(); + return authenticate(authenticationUrl, requestBody); + } catch (Exception e) { + return new AuthenticateResponse(new ObjectServerError(ErrorCode.UNKNOWN, e)); + } + } + + @Override + public RefreshResponse refresh(String token, URL authenticationUrl) { + throw new UnsupportedOperationException("FIXME"); + } + + private AuthenticateResponse authenticate(URL authenticationUrl, String requestBody) throws Exception { + Request request = new Request.Builder() + .url(authenticationUrl) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .addHeader("Connection", "close") // See https://github.com/square/okhttp/issues/2363 + .post(RequestBody.create(JSON, requestBody)) + .build(); + Call call = client.newCall(request); + Response response = call.execute(); + return AuthenticateResponse.createFrom(response); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java new file mode 100644 index 0000000000..56f98f5f32 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.network; + +import io.realm.objectserver.ErrorCode; +import io.realm.objectserver.internal.Token; + +public class RefreshResponse { + private Token refreshToken = null; + private ErrorCode errorCode = null; + private String errorMessage = null; + + public RefreshResponse(Token refreshToken, ErrorCode errorCode, String errorMessage) { + this.refreshToken = refreshToken; + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + public boolean isValid() { + return false; + } + + public Token getRefreshToken() { + return refreshToken; + } + + public ErrorCode getErrorCode() { + return errorCode; + } + + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java new file mode 100644 index 0000000000..fcbf81da27 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.syncpolicy; + +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.Session; + +/** + * This SyncPolicy will automatically start synchronizing changes to a Realm as soon as it is opened. + * // TODO Figure out how to close connection once all changes have been uploaded. + */ +public class AutomaticSyncPolicy implements SyncPolicy { + + @Override + public void onRealmOpened(Session session) { + session.bind(); // Bind Realm first time it is opened. + } + + @Override + public void onRealmClosed(Session session) { + // TODO Sync need to expose callback when there is no more local changes + // For now just keep the session open. + } + + @Override + public void onSessionCreated(Session session) { + session.start(); + } + + @Override + public void onSessionStopped(Session session) { + // Do nothing + } + + @Override + public boolean onError(Session session, ObjectServerError error) { + switch(error.category()) { + case FATAL: + return false; // Report all fatal errors to the user + case INFO: + return true; // Ignore all INFO errors + case RECOVERABLE: + rebind(session); + return true; + default: + return false; + } + } + + private void rebind(Session session) { + // FIXME: Do not rebind uncritically. Figure out a good strategy for this. + // See https://realmio.slack.com/archives/sync-core/p1472415880000002 + session.bind(); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java new file mode 100644 index 0000000000..3bb5a064fb --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.syncpolicy; + +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.Session; + +/** + * Interface describing a given synchronization policy with the Realm Object Server. + *

    + * The sole purpose of classes implementing this interface is to call {@link Session#bind()} and {@link Session#unbind()} + * as needed, which will control when changes are synchronized between a local and remote Realm. + * + * The SyncPolicy is not responsible for managing the lifecycle of the {@link Session} in general. So any + * implementation of this class should avoid calling {@link Session#stop()} and {@link Session#start()}. + * + * If a session is stopped, {@link Session#unbind()} is automatically called and any further calls to + * {@link Session#bind()} and {@link Session#unbind()} are ignored. {@link #onSessionStopped(Session)} ()} will then be + * called so the sync policy have a chance to clean up any resources it might be using. + */ +// TODO: Still experimental API. We need to figure out exactly which events we expose and how +// TODO: Should we keep this protected for now? +public interface SyncPolicy { + + /** + * Called when the session object is created. At this point it is possible to register any relevant error and event + * listeners in either the Android framework or for the session itself. + * + * {@link Session#start()} will be automatically called after this method. + * + * @param session the {@link Session} just created. It has not yet been started. + */ + void onSessionCreated(Session session); + + /** + * The {@link Session} has been stopped and will ignore any further calls to {@link Session#bind()} and + * {@link Session#unbind()}. All external resources should be cleaned up. + * + * @param session {@link Session} that has been stopped. + */ + void onSessionStopped(Session session); + + /** + * Called the first time a Realm is opened on any thread. + * + * @param session {@link Session} associated with this Realm. + */ + void onRealmOpened(Session session); + + /** + * Called when the last Realm instance across all threads have been closed. + * + * @param session {@link Session} associated with this Realm. + */ + void onRealmClosed(Session session); + + /** + * Called if an error occurred in the underlying session. In many cases this has caused the session to become + * unbound. + * + * @param error {@link io.realm.objectserver.ObjectServerError} object describing the error. + * @return {@code true} if the error was handled, or {@code false} if it should be propagated further out to the + * SyncConfigurations error handler. + * + * This method is always called from a background thread, never the UI thread. + * + * @see io.realm.objectserver.SyncConfiguration.Builder#errorHandler(Session.ErrorHandler) + */ + boolean onError(Session session, ObjectServerError error); +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/ManualSyncPolicy.java b/realm/realm-library/src/main/java/io/realm/sync/ManualSyncPolicy.java deleted file mode 100644 index 9a2e378838..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/ManualSyncPolicy.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.realm.sync; - -public class ManualSyncPolicy implements SyncPolicy { - @Override - public void apply(SyncSession session) { - // Zzzzz.... - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/RealtimeSyncPolicy.java b/realm/realm-library/src/main/java/io/realm/sync/RealtimeSyncPolicy.java deleted file mode 100644 index b887329032..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/RealtimeSyncPolicy.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.realm.sync; - -public class RealtimeSyncPolicy implements SyncPolicy { - @Override - public void apply(SyncSession session) { - session.start(); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/sync/SyncConfiguration.java deleted file mode 100644 index 20a3b1b832..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/SyncConfiguration.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.realm.sync; - -import io.realm.RealmConfiguration; - -public class SyncConfiguration { - - private final SyncPolicy syncPolicy; - private final String userToken; - final RealmConfiguration configuration; - - public RealmConfiguration getConfiguration() { - return configuration; - } - - public String getServer() { - return server; - } - - private final String server; - - //TODO have a builder - public SyncConfiguration(RealmConfiguration configuration, String server) { - this.configuration = configuration; - this.server = server; - this.syncPolicy = new RealtimeSyncPolicy(); - this.userToken = "boom"; - } - - public SyncPolicy getSyncPolicy() { - return syncPolicy; - } - - public String getUserToken() { - return userToken; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/SyncManager.java b/realm/realm-library/src/main/java/io/realm/sync/SyncManager.java deleted file mode 100644 index 9d3a72475e..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/SyncManager.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.realm.sync; - -import java.util.HashMap; -import java.util.Map; - -public final class SyncManager { - private static volatile long syncClientPointer = 0; - private final static Map SYNC_SESSIONS = new HashMap(); - - public synchronized static long getSession(final String userToken, final String path, final String serverUrl) { - if (syncClientPointer == 0) { - // client event loop is not created for this token - // we create 1 client per user token - syncClientPointer = syncCreateClient(); - } - - // check if the session is not already available for the provided RealmConfiguration - Long syncSessionPointer = SYNC_SESSIONS.get(path); - if (syncSessionPointer == null) { - syncSessionPointer = syncCreateSession(syncClientPointer, path, serverUrl, userToken); - } - - SYNC_SESSIONS.put(path, syncSessionPointer); - return syncSessionPointer; - } - - private static native long syncCreateClient(); - private static native long syncCreateSession(long clientPointer, String path, String serverUrl, String userToken); - -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/SyncPolicy.java b/realm/realm-library/src/main/java/io/realm/sync/SyncPolicy.java deleted file mode 100644 index f4e8753b5e..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/SyncPolicy.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.realm.sync; - -public interface SyncPolicy { - void apply(SyncSession session); -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/SyncSession.java b/realm/realm-library/src/main/java/io/realm/sync/SyncSession.java deleted file mode 100644 index b406297c7d..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/SyncSession.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.realm.sync; - -public interface SyncSession { - void start(); - void stop(); -} From c237fc3065ea4813dda4fe3280582263e4482924 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 8 Sep 2016 20:51:16 +0900 Subject: [PATCH 0028/2110] fix javadoc comments in io.realm.internal.RealmProxyMediator --- .../src/main/java/io/realm/internal/RealmProxyMediator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index e348a15129..f13ad8671c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -46,7 +46,7 @@ public abstract class RealmProxyMediator { * Creates the backing table in Realm for the given RealmObject class. * * @param clazz the {@link RealmObject} model class to create backing table for. - * @param transaction the read transaction for the Realm to create table in. + * @param sharedRealm the wrapper object of underlying native database. */ public abstract Table createTable(Class clazz, SharedRealm sharedRealm); @@ -54,7 +54,7 @@ public abstract class RealmProxyMediator { * Validates the backing table in Realm for the given RealmObject class. * * @param clazz the {@link RealmObject} model class to validate. - * @param sharedRealm the read transaction for the Realm to validate against. + * @param sharedRealm the wrapper object of underlying native database to validate against. * @return the field indices map. */ public abstract ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm); From 5e13e225046d230613cf5cecea8aaf39f8cd0f01 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Sep 2016 21:25:45 +0800 Subject: [PATCH 0029/2110] Ignore callingOrdersOfListeners for now (#3416) since it becomes a flaky test after #3370. --- .../src/androidTest/java/io/realm/NotificationsTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index ac29db2afc..90625fc4f8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -28,6 +28,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -954,6 +955,7 @@ public void onChange(Realm element) { }); } + // FIXME check if the SharedRealm Changed in handleAsyncTransactionCompleted and reenable this test. // We precisely depend on the order of triggering change listeners right now. // So it should be: // 1. Synced object listener @@ -964,6 +966,7 @@ public void onChange(Realm element) { // If this case fails on your code, think twice before changing the test! // https://github.com/realm/realm-java/issues/2408 is related to this test! @Test + @Ignore("Listener on Realm might be trigger more times, ignore for now") @RunTestInLooperThread public void callingOrdersOfListeners() { final Realm realm = looperThread.realm; From e10cffd143ce1f91c7f1227aaee2ff134844c60e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Sep 2016 11:54:59 +0800 Subject: [PATCH 0030/2110] Update core to 2.0.0-rc4 (#3384) * And with some code cleanup. * Throw an runtime exception when input java bytes array cannot be read. * Update Object Store to solve the breaking change caused failure. See https://github.com/realm/realm-object-store/pull/158 * Use '-O2' instead '-Os' since it seems a gcc bug hangs encryption releated tests with '-Os' enabled in JNI build. --- CHANGELOG.md | 1 + Jenkinsfile | 1 + realm/realm-library/build.gradle | 4 +- .../java/io/realm/RealmQueryTests.java | 3 ++ .../androidTest/java/io/realm/SortTest.java | 32 ++++++------- .../realm-library/src/main/cpp/CMakeLists.txt | 6 ++- .../main/cpp/io_realm_internal_LinkView.cpp | 6 +-- .../src/main/cpp/io_realm_internal_Table.cpp | 11 ++--- .../main/cpp/io_realm_internal_TableQuery.cpp | 9 ++-- .../main/cpp/io_realm_internal_TableView.cpp | 4 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/cpp/tablebase_tpl.hpp | 22 --------- realm/realm-library/src/main/cpp/util.hpp | 12 ++++- .../src/main/java/io/realm/RealmQuery.java | 48 ++++++++----------- .../src/main/java/io/realm/RealmResults.java | 17 ++++--- .../main/java/io/realm/internal/LinkView.java | 9 ++-- .../java/io/realm/internal/TableQuery.java | 15 +++--- 17 files changed, 90 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68fbc27dfc..642f6c0f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ### Internal * Moved JNI build to CMake. +* Updated Realm Core to 2.0.0-rc4. ## 1.2.0 diff --git a/Jenkinsfile b/Jenkinsfile index 4c10941ce9..f263677e77 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,6 +12,7 @@ try { // Make sure not to delete the folder that Jenkins allocates to store scripts sh 'git clean -ffdx -e .????????' // Update submodule for object-store + sh 'git submodule sync' sh 'git submodule update --init --force' stage 'Docker build' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 458c4040c6..d7da9bdc9a 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '1.5.1' +ext.coreVersion = '2.0.0-rc4' // empty or comment out this to disable hash checking -ext.coreSha256Hash = 'a034d3250c820a15126721142d168a2ac4a12223b75bb324958ca2a70442720d' +ext.coreSha256Hash = '760d8e889b8d678da36f63be2a49924969bfc8370176696ee977659d59677717' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 8cf83d61a9..8bea9267cf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2249,6 +2249,9 @@ public void resultOfTableViewQuery() { populateTestRealm(); final RealmResults results = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 3L).findAll(); + assertEquals(1, results.size()); + assertEquals("test data 3", results.first().getColumnString()); + final RealmQuery tableViewQuery = results.where(); assertEquals("test data 3", tableViewQuery.findAll().first().getColumnString()); assertEquals("test data 3", tableViewQuery.findFirst().getColumnString()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 396e9cdf91..bb2f61f4cb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -153,19 +153,19 @@ private void checkSortTwoFieldsStringAscendingIntAscending(RealmResults results) { @@ -179,19 +179,19 @@ private void checkSortTwoFieldsIntString(RealmResults results) { assertEquals("Adam", results.get(0).getColumnString()); assertEquals(4, results.get(0).getColumnLong()); - assertEquals(2, ((TableView) results.getTable()).getSourceRowIndex(0)); + assertEquals(2, ((TableView) results.getTableOrView()).getSourceRowIndex(0)); assertEquals("Brian", results.get(1).getColumnString()); assertEquals(4, results.get(1).getColumnLong()); - assertEquals(1, ((TableView) results.getTable()).getSourceRowIndex(1)); + assertEquals(1, ((TableView) results.getTableOrView()).getSourceRowIndex(1)); assertEquals("Adam", results.get(2).getColumnString()); assertEquals(5, results.get(2).getColumnLong()); - assertEquals(0, ((TableView) results.getTable()).getSourceRowIndex(2)); + assertEquals(0, ((TableView) results.getTableOrView()).getSourceRowIndex(2)); assertEquals("Adam", results.get(3).getColumnString()); assertEquals(5, results.get(3).getColumnLong()); - assertEquals(3, ((TableView) results.getTable()).getSourceRowIndex(3)); + assertEquals(3, ((TableView) results.getTableOrView()).getSourceRowIndex(3)); } private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -205,19 +205,19 @@ private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -231,19 +231,19 @@ private void checkSortTwoFieldsStringAscendingIntDescending(RealmResultsget( S(pos) ).get_index(); + return lvr->get(S(linkViewIndex)).get_index(); } CATCH_STD() return 0; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 94953a1346..719d6e59a7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -712,15 +712,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray( if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) return; try { - if (dataArray == NULL) { - if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { + if (dataArray == NULL && !TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { return; - } - TBL(nativeTablePtr)->set_binary(S(columnIndex), S(rowIndex), BinaryData()); - } - else { - tbl_nativeDoByteArray(&Table::set_binary, TBL(nativeTablePtr), env, columnIndex, rowIndex, dataArray); } + + JniByteArray byteAccessor(env, dataArray); + TBL(nativeTablePtr)->set_binary(S(columnIndex), S(rowIndex), byteAccessor); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 4d8859e62f..2f038e1d69 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include #include "util.hpp" @@ -1613,15 +1612,13 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeCount( } JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeRemove( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlong start, jlong end, jlong limit) + JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) + if (!QUERY_VALID(env, pQuery)) return 0; try { - return pQuery->remove(S(start), S(end), S(limit)); + return pQuery->remove(); } CATCH_STD() return 0; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp index 65866dc126..b4d561be94 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp @@ -397,7 +397,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetByteArray( if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Binary)) return; - tbl_nativeDoByteArray(&TableView::set_binary, TV(nativeViewPtr), env, columnIndex, rowIndex, byteArray); + + JniByteArray bytesAccessor(env, byteArray); + TV(nativeViewPtr)->set_binary(S(columnIndex), S(rowIndex), bytesAccessor); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 4663837974..9ea6895190 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 4663837974a46fa2b34f85362b5c86b5a1d3437a +Subproject commit 9ea6895190ea38951d4498f4e124e806efca62b0 diff --git a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp index a9ca249173..bf7ea21178 100644 --- a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp +++ b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp @@ -41,26 +41,4 @@ jbyteArray tbl_GetByteArray(JNIEnv* env, jlong nativeTablePtr, jlong columnIndex } } -template -void tbl_nativeDoByteArray(M doBinary, T* pTable, JNIEnv* env, jlong columnIndex, jlong rowIndex, jbyteArray dataArray) -{ - jbyte* bytePtr = env->GetByteArrayElements(dataArray, NULL); - if (!bytePtr) { - ThrowException(env, IllegalArgument, "doByteArray"); - return; - } - size_t dataLen = S(env->GetArrayLength(dataArray)); - (pTable->*doBinary)( S(columnIndex), S(rowIndex), realm::BinaryData(reinterpret_cast(bytePtr), dataLen)); - env->ReleaseByteArrayElements(dataArray, bytePtr, 0); -} - - -template -void tbl_nativeDoBinary(M doBinary, T* pTable, JNIEnv* env, jlong columnIndex, jlong rowIndex, jobject byteBuffer) -{ - realm::BinaryData bin; - if (GetBinaryData(env, byteBuffer, bin)) - (pTable->*doBinary)( S(columnIndex), S(rowIndex), bin); -} - #endif // REALM_JNI_TABLEBASE_TPL_HPP diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index ad3de760d1..9bdad05fa4 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -28,10 +28,12 @@ #include #include -#include -#include #include #include +#include +#include + +#include #include "io_realm_internal_Util.h" #include "io_realm_log_LogLevel.h" @@ -440,6 +442,8 @@ inline bool TblColIndexAndLinkOrLinkList(JNIEnv* env, T* pTable, jlong columnInd && TypeIsLinkLike(env, pTable, columnIndex); } +// FIXME Usually this is called after TBL_AND_INDEX_AND_TYPE_VALID which will validate Table as well. +// Try to avoid duplicated checks to improve performance. template inline bool TblColIndexAndNullable(JNIEnv* env, T* pTable, jlong columnIndex) { return TableIsValid(env, pTable) @@ -572,6 +576,10 @@ class JniByteArray { , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) , m_array(javaArray == NULL ? NULL : env->GetByteArrayElements(javaArray, NULL)) , m_releaseMode(JNI_ABORT) { + if (m_javaArray != nullptr && m_array == nullptr) { + // javaArray is not null but GetByteArrayElements returns null, something is really wrong. + throw std::runtime_error(realm::util::format("GetByteArrayElements failed on byte array %x", m_javaArray)); + } } ~JniByteArray() diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index ed64e04ef9..0ecf8c1b88 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -65,7 +65,7 @@ public final class RealmQuery { private String className; private TableOrView table; private RealmObjectSchema schema; - private LinkView view; + private LinkView linkView; private TableQuery query; private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; @@ -136,7 +136,7 @@ private RealmQuery(Realm realm, Class clazz) { this.clazz = clazz; this.schema = realm.schema.getSchemaForClass(clazz); this.table = schema.table; - this.view = null; + this.linkView = null; this.query = table.where(); } @@ -144,18 +144,18 @@ private RealmQuery(RealmResults queryResults, Class clazz) { this.realm = queryResults.realm; this.clazz = clazz; this.schema = realm.schema.getSchemaForClass(clazz); - this.table = queryResults.getTable(); - this.view = null; - this.query = queryResults.getTable().where(); + this.table = queryResults.getTableOrView(); + this.linkView = null; + this.query = this.table.where(); } - private RealmQuery(BaseRealm realm, LinkView view, Class clazz) { + private RealmQuery(BaseRealm realm, LinkView linkView, Class clazz) { this.realm = realm; this.clazz = clazz; - this.query = view.where(); - this.view = view; this.schema = realm.schema.getSchemaForClass(clazz); this.table = schema.table; + this.linkView = linkView; + this.query = linkView.where(); } private RealmQuery(BaseRealm realm, String className) { @@ -171,16 +171,16 @@ private RealmQuery(RealmResults queryResults, String classNa this.className = className; this.schema = realm.schema.getSchemaForClass(className); this.table = schema.table; - this.query = queryResults.getTable().where(); + this.query = queryResults.getTableOrView().where(); } - private RealmQuery(BaseRealm realm, LinkView view, String className) { + private RealmQuery(BaseRealm realm, LinkView linkView, String className) { this.realm = realm; this.className = className; - this.query = view.where(); - this.view = view; this.schema = realm.schema.getSchemaForClass(className); this.table = schema.table; + this.linkView = linkView; + this.query = linkView.where(); } /** @@ -194,8 +194,8 @@ public boolean isValid() { return false; } - if (view != null) { - return view.isAttached(); + if (linkView != null) { + return linkView.isAttached(); } return table != null && table.getTable().isValid(); } @@ -2064,9 +2064,9 @@ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, */ public E findFirst() { checkQueryIsNotReused(); - long sourceRowIndex = getSourceRowIndexForFirstObject(); - if (sourceRowIndex >= 0) { - E realmObject = realm.get(clazz, className, sourceRowIndex); + long tableRowIndex = getSourceRowIndexForFirstObject(); + if (tableRowIndex >= 0) { + E realmObject = realm.get(clazz, className, tableRowIndex); return realmObject; } else { return null; @@ -2217,19 +2217,9 @@ private void checkQueryIsNotReused() { } private long getSourceRowIndexForFirstObject() { - long rowIndex = this.query.find(); - if (rowIndex < 0) { - return rowIndex; - } - if (this.view != null) { - return view.getTargetRowIndex(rowIndex); - } else if (table instanceof TableView){ - return ((TableView) table).getSourceRowIndex(rowIndex); - } else { - return rowIndex; - } + long tableRowIndex = this.query.find(); + return tableRowIndex; } - // Get the column index for sorting related functions. A proper exception will be thrown if the field doesn't exist // or it belongs to the child object. private long getColumnIndexForSort(String fieldName) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 090302a6c2..ea37900e37 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -146,7 +146,7 @@ private RealmResults(BaseRealm realm, TableOrView table, String className) { this.currentTableViewVersion = table.syncIfNeeded(); } - TableOrView getTable() { + TableOrView getTableOrView() { if (table == null) { return realm.schema.getTable(classSpec); } else { @@ -177,7 +177,6 @@ public boolean isManaged() { @Override public RealmQuery where() { realm.checkIfValid(); - return RealmQuery.createQueryFromResult(this); } @@ -211,7 +210,7 @@ public boolean contains(Object object) { public E get(int location) { E obj; realm.checkIfValid(); - TableOrView table = getTable(); + TableOrView table = getTableOrView(); if (table instanceof TableView) { obj = realm.get(classSpec, className, ((TableView) table).getSourceRowIndex(location)); } else { @@ -252,7 +251,7 @@ public E last() { @Override public void deleteFromRealm(int location) { realm.checkIfValid(); - TableOrView table = getTable(); + TableOrView table = getTableOrView(); table.remove(location); } @@ -263,7 +262,7 @@ public void deleteFromRealm(int location) { public boolean deleteAllFromRealm() { realm.checkIfValid(); if (size() > 0) { - TableOrView table = getTable(); + TableOrView table = getTableOrView(); table.clear(); return true; } else { @@ -382,7 +381,7 @@ public int size() { if (!isLoaded()) { return 0; } else { - long size = getTable().size(); + long size = getTableOrView().size(); return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; } } @@ -509,7 +508,7 @@ public RealmResults distinct(String fieldName) { realm.checkIfValid(); long columnIndex = RealmQuery.getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); - TableOrView tableOrView = getTable(); + TableOrView tableOrView = getTableOrView(); if (tableOrView instanceof Table) { this.table = ((Table) tableOrView).getDistinctView(columnIndex); } else { @@ -618,7 +617,7 @@ public boolean retainAll(Collection collection) { public boolean deleteLastFromRealm() { realm.checkIfValid(); if (size() > 0) { - TableOrView table = getTable(); + TableOrView table = getTableOrView(); table.removeLast(); return true; } else { @@ -648,7 +647,7 @@ void syncIfNeeded() { @Override public boolean deleteFirstFromRealm() { if (size() > 0) { - TableOrView table = getTable(); + TableOrView table = getTableOrView(); table.removeFirst(); return true; } else { diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index d05a4a51dd..6ba77cc800 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -62,8 +62,11 @@ public CheckedRow getCheckedRow(long index) { return CheckedRow.get(context, this, index); } - public long getTargetRowIndex(long pos) { - return nativeGetTargetRowIndex(nativePointer, pos); + /** + * Returns the row index in the underlying table. + */ + public long getTargetRowIndex(long linkViewIndex) { + return nativeGetTargetRowIndex(nativePointer, linkViewIndex); } public void add(long rowIndex) { @@ -169,7 +172,7 @@ private void checkImmutable() { public static native void nativeClose(long nativeLinkViewPtr); native long nativeGetRow(long nativeLinkViewPtr, long pos); - private native long nativeGetTargetRowIndex(long nativeLinkViewPtr, long pos); + private native long nativeGetTargetRowIndex(long nativeLinkViewPtr, long linkViewIndex); public static native void nativeAdd(long nativeLinkViewPtr, long rowIndex); private native void nativeInsert(long nativeLinkViewPtr, long pos, long rowIndex); private native void nativeSet(long nativeLinkViewPtr, long pos, long rowIndex); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index cb6bd376c1..5060ad4e23 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -420,11 +420,15 @@ public TableQuery isNotEmpty(long[] columnIndices) { // Searching methods. + @Deprecated // Doesn't seem to be used public long find(long fromTableRow) { validateQuery(); return nativeFind(nativePtr, fromTableRow); } + /** + * Returns the table row index for the first element matching the query. + */ public long find() { validateQuery(); return nativeFind(nativePtr, 0); @@ -717,17 +721,10 @@ public long count() { return nativeCount(nativePtr, 0, Table.INFINITE, Table.INFINITE); } - // Deletion. - public long remove(long start, long end) { - validateQuery(); - if (table.isImmutable()) throwImmutable(); - return nativeRemove(nativePtr, start, end, Table.INFINITE); - } - public long remove() { validateQuery(); if (table.isImmutable()) throwImmutable(); - return nativeRemove(nativePtr, 0, Table.INFINITE, Table.INFINITE); + return nativeRemove(nativePtr); } /** @@ -808,7 +805,7 @@ private void throwImmutable() { private native void nativeIsNull(long nativePtr, long columnIndices[]); private native void nativeIsNotNull(long nativePtr, long columnIndices[]); private native long nativeCount(long nativeQueryPtr, long start, long end, long limit); - private native long nativeRemove(long nativeQueryPtr, long start, long end, long limit); + private native long nativeRemove(long nativeQueryPtr); private native long nativeImportHandoverTableViewIntoSharedGroup(long handoverTableViewPtr, long callerSharedRealmPtr) throws BadVersionException; private native long nativeHandoverQuery(long callerSharedRealmPtr, long nativeQueryPtr); private static native long nativeFindAllSortedWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long start, long end, long limit, long columnIndex, boolean ascending) throws BadVersionException; From eef3ab5ed03687da309a06058168e44538157bac Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 9 Sep 2016 12:38:30 +0200 Subject: [PATCH 0031/2110] Added proper javadoc and tests for not allowing setting manual migrations. --- .../objectserver/SyncConfigurationTests.java | 32 +++++++++++++++---- .../realm/objectserver/SyncConfiguration.java | 4 +-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java index 8d307badff..79f1b0e0c7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java @@ -31,11 +31,17 @@ import org.junit.runner.RunWith; import java.io.File; +import java.net.URL; import java.util.Locale; import java.util.UUID; +import io.realm.DynamicRealm; +import io.realm.Realm; +import io.realm.RealmMigration; +import io.realm.objectserver.android.SharedPrefsUserStore; import io.realm.objectserver.internal.Token; import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; @@ -210,17 +216,29 @@ public void errorHandler_nullThrows() { } @Test - public void syncPolicy() { + public void migration_alwaysThrows() { + SyncConfiguration.Builder builder; + builder = new SyncConfiguration.Builder(context) + .user(User.createLocal()) + .serverUrl("realm://objectserver.realm.io/default"); - } + try { + builder.migration(null); + } catch (IllegalArgumentException ignore) { + } - @Test - public void syncPolicy_nullThrows() { -// User user = User.createLocal(); -// user.add(con); -// + try { + builder.migration(new RealmMigration() { + @Override + public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { + // Nothing + } + }); + } catch (IllegalArgumentException ignore) { + } } + // @Ignore("Only used for quick testing without needing to spin up a full integration test") // @Test // @RunTestInLooperThread diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java index 742c812549..428de0569e 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java @@ -326,14 +326,14 @@ public Builder assetFile(String assetFile) { } /** - * Manual migrations are not supported (yet) for Realms that can be synced using the Realm Object Server + * Manual migrations are not supported for Realms that can be synced using the Realm Object Server * Only additive changes are allowed, and these will be detected and applied automatically. * * @throws IllegalArgumentException always. */ @Override public Builder migration(RealmMigration migration) { - throw new IllegalArgumentException("Migrations are not supported for Realms that can be synchronized using the Realm Mobile Platform"); + throw new IllegalArgumentException("Manual migrations are not supported for Realms that can be synchronized using the Realm Object Server"); } /** From 973d7ca117b3b861178e5c6725f06988bbc5e53f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Sep 2016 19:23:36 +0800 Subject: [PATCH 0032/2110] Call set_xxx_unique for primary keys Call set_xxx_unique for primary keys --- .../processor/RealmProxyClassGenerator.java | 11 ++--- .../io/realm/AllTypesRealmProxy.java | 20 ++-------- .../src/main/cpp/io_realm_internal_Table.cpp | 29 ++++++++++++++ .../main/java/io/realm/internal/Table.java | 40 ++++++++++++++----- 4 files changed, 68 insertions(+), 32 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 83c2d43e9a..5eff1c6043 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -1124,15 +1124,11 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP } writer.beginControlFlow("if (rowIndex == TableOrView.NO_MATCH)"); - writer.emitStatement("rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1)"); if (Utils.isString(metadata.getPrimaryKey())) { - writer.beginControlFlow("if (primaryKeyValue != null)"); - writer.emitStatement("Table.nativeSetString(tableNativePtr, pkColumnIndex, rowIndex, (String)primaryKeyValue)"); - writer.endControlFlow(); + writer.emitStatement("rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false)"); } else { - writer.beginControlFlow("if (primaryKeyValue != null)"); - writer.emitStatement("Table.nativeSetLong(tableNativePtr, pkColumnIndex, rowIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter); - writer.endControlFlow(); + writer.emitStatement("rowIndex = table.addEmptyRowWithPrimaryKey(((%s) object).%s(), false)", + interfaceName, primaryKeyGetter); } if (throwIfPrimaryKeyDuplicate) { @@ -1632,6 +1628,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer.emitStatement("return obj"); writer.endMethod(); writer.emitEmptyLine(); + } private String columnInfoClassName() { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index cca4d4a861..c5c43b8825 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -690,10 +690,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == TableOrView.NO_MATCH) { - rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); - if (primaryKeyValue != null) { - Table.nativeSetString(tableNativePtr, pkColumnIndex, rowIndex, (String)primaryKeyValue); - } + rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } @@ -820,10 +814,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == TableOrView.NO_MATCH) { - rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); - if (primaryKeyValue != null) { - Table.nativeSetString(tableNativePtr, pkColumnIndex, rowIndex, (String)primaryKeyValue); - } + rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); } cache.put(object, rowIndex); Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong()); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 719d6e59a7..0f1eb70bed 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -638,6 +638,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLong( } CATCH_STD() } +JNIEXPORT void JNICALL +Java_io_realm_internal_Table_nativeSetLongUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, + jlong rowIndex, jlong value) +{ + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) + return; + try { + TBL(nativeTablePtr)->set_int_unique( S(columnIndex), S(rowIndex), value); + } CATCH_STD() +} + JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean( JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jboolean value) { @@ -684,6 +695,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString( } CATCH_STD() } +JNIEXPORT void JNICALL +Java_io_realm_internal_Table_nativeSetStringUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, + jlong rowIndex, jstring value) +{ + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) + return; + try { + if (value == NULL) { + if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { + return; + } + } + JStringAccessor value2(env, value); // throws + // FIXME: Check if we need to call set_null_unique when core support it. + TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), value2); + } CATCH_STD() +} + JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp( JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong timestampValue) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 8e4dd14de2..b831ef5ea5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -385,9 +385,30 @@ public long addEmptyRow() { return nativeAddEmptyRow(nativePtr, 1); } + /** + * Add an empty row to the table and set the primary key with the given value. Equivalent to call + * {@link #addEmptyRowWithPrimaryKey(Object, boolean)} with {@code validation = true}. + * + * @param primaryKeyValue the primary key value + * @return the row index. + */ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { - checkImmutable(); - checkHasPrimaryKey(); + return addEmptyRowWithPrimaryKey(primaryKeyValue, true); + } + + /** + * Add an empty row to the table and set the primary key with the given value. + * + * @param primaryKeyValue the primary key value. + * @param validation set to {@code false} to skip all validations. This is currently used by bulk insert which + * has its own validations. + * @return the row index. + */ + public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation) { + if (validation) { + checkImmutable(); + checkHasPrimaryKey(); + } long primaryKeyColumnIndex = getPrimaryKey(); RealmFieldType type = getColumnType(primaryKeyColumnIndex); @@ -399,11 +420,12 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { switch (type) { case STRING: case INTEGER: - if (findFirstNull(primaryKeyColumnIndex) != NO_MATCH) { + if (validation && findFirstNull(primaryKeyColumnIndex) != NO_MATCH) { throwDuplicatePrimaryKeyException("null"); } rowIndex = nativeAddEmptyRow(nativePtr, 1); row = getUncheckedRow(rowIndex); + // FIXME: Use core's set_null_unique when core supports it. row.setNull(primaryKeyColumnIndex); break; @@ -417,12 +439,11 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { if (!(primaryKeyValue instanceof String)) { throw new IllegalArgumentException("Primary key value is not a String: " + primaryKeyValue); } - if (findFirstString(primaryKeyColumnIndex, (String) primaryKeyValue) != NO_MATCH) { + if (validation && findFirstString(primaryKeyColumnIndex, (String) primaryKeyValue) != NO_MATCH) { throwDuplicatePrimaryKeyException(primaryKeyValue); } rowIndex = nativeAddEmptyRow(nativePtr, 1); - row = getUncheckedRow(rowIndex); - row.setString(primaryKeyColumnIndex, (String) primaryKeyValue); + nativeSetStringUnique(nativePtr, primaryKeyColumnIndex, rowIndex, (String) primaryKeyValue); break; case INTEGER: @@ -432,12 +453,11 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { } catch (RuntimeException e) { throw new IllegalArgumentException("Primary key value is not a long: " + primaryKeyValue); } - if (findFirstLong(primaryKeyColumnIndex, pkValue) != NO_MATCH) { + if (validation && findFirstLong(primaryKeyColumnIndex, pkValue) != NO_MATCH) { throwDuplicatePrimaryKeyException(pkValue); } rowIndex = nativeAddEmptyRow(nativePtr, 1); - row = getUncheckedRow(rowIndex); - row.setLong(primaryKeyColumnIndex, pkValue); + nativeSetLongUnique(nativePtr, primaryKeyColumnIndex, rowIndex, pkValue); break; default: @@ -1315,11 +1335,13 @@ public static String tableNameToClassName(String tableName) { private native long nativeGetLinkTarget(long nativePtr, long columnIndex); native long nativeGetRowPtr(long nativePtr, long index); public static native void nativeSetLong(long nativeTablePtr, long columnIndex, long rowIndex, long value); + public static native void nativeSetLongUnique(long nativeTablePtr, long columnIndex, long rowIndex, long value); public static native void nativeSetBoolean(long nativeTablePtr, long columnIndex, long rowIndex, boolean value); public static native void nativeSetFloat(long nativeTablePtr, long columnIndex, long rowIndex, float value); public static native void nativeSetDouble(long nativeTablePtr, long columnIndex, long rowIndex, double value); public static native void nativeSetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex, long dateTimeValue); public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value); + public static native void nativeSetStringUnique(long nativeTablePtr, long columnIndex, long rowIndex, String value); public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex); public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data); public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value); From b6d809dcf54eaa33c4b07b837068e89c9a03849f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 9 Sep 2016 16:53:05 +0200 Subject: [PATCH 0033/2110] Revert "Merge branch 'master' into master-sync" This reverts commit 9bcdd318e9a32ad70abc3f1f6be942a492dde470, reversing changes made to eef3ab5ed03687da309a06058168e44538157bac. --- CHANGELOG.md | 8 - .../realm/transformer/BytecodeModifier.groovy | 32 +--- .../transformer/BytecodeModifierTest.groovy | 92 +++------- .../java/io/realm/processor/Constants.java | 2 - .../realm/processor/RealmJsonTypeHelper.java | 24 +-- .../processor/RealmProxyClassGenerator.java | 28 +-- .../io/realm/AllTypesRealmProxy.java | 31 ++-- .../io/realm/BooleansRealmProxy.java | 3 +- .../io/realm/NullTypesRealmProxy.java | 3 +- .../resources/io/realm/SimpleRealmProxy.java | 3 +- realm/realm-library/build.gradle | 8 +- .../java/io/realm/DynamicRealmTests.java | 7 - .../java/io/realm/NotificationsTest.java | 3 - .../realm/RealmJsonAbsentPrimaryKeyTests.java | 161 ------------------ .../realm/RealmJsonNullPrimaryKeyTests.java | 22 ++- .../java/io/realm/RealmJsonTests.java | 9 +- .../java/io/realm/RealmModelTests.java | 3 +- .../java/io/realm/RealmQueryTests.java | 3 - .../androidTest/java/io/realm/RealmTests.java | 6 - .../androidTest/java/io/realm/SortTest.java | 32 ++-- .../java/io/realm/internal/JNITableTest.java | 1 + .../realm-library/src/main/cpp/CMakeLists.txt | 6 +- .../main/cpp/io_realm_internal_LinkView.cpp | 6 +- .../src/main/cpp/io_realm_internal_Table.cpp | 40 +---- .../main/cpp/io_realm_internal_TableQuery.cpp | 9 +- .../main/cpp/io_realm_internal_TableView.cpp | 4 +- .../src/main/cpp/tablebase_tpl.hpp | 22 +++ realm/realm-library/src/main/cpp/util.hpp | 12 +- .../src/main/java/io/realm/DynamicRealm.java | 5 - .../src/main/java/io/realm/Realm.java | 77 ++++----- .../java/io/realm/RealmConfiguration.java | 1 - .../src/main/java/io/realm/RealmQuery.java | 48 +++--- .../src/main/java/io/realm/RealmResults.java | 17 +- .../main/java/io/realm/internal/LinkView.java | 9 +- .../io/realm/internal/RealmProxyMediator.java | 4 +- .../main/java/io/realm/internal/Table.java | 71 ++++---- .../java/io/realm/internal/TableQuery.java | 15 +- 37 files changed, 262 insertions(+), 565 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c3641c6010..9da928f1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,5 @@ ## 2.0.0 -### Known issues - -* When creating a `RealmObject` from a JSON stream, it will take the default values defined by its default constructor for those fields that are not defined in the JSON object. This behaviour is different from other APIs when creating `RealmObject`s. - ### Breaking Changes * `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). @@ -13,8 +9,6 @@ - `RealmIOExcpetion` has been removed and replaced by `RealmFileException`. * Removed `RealmConfiguration.Builder(Context, File)` and `RealmConfiguration.Builder(File)` constructors. * `RealmConfiguration.Builder.assetFile(Context, String)` has been renamed to `RealmConfiguration.Builder.assetFile(String)`. -* Object with primary key is now required to define it when the object is created. This means that `Realm.createObject(Class)` and `DynamicRealm.createObject(String)` now throws `RealmException` if they are used to create an object with a primary key field. Use `Realm.createObject(Class, Object)` or `DynamicRealm.createObject(String, Object)` instead. -* Importing from JSON without the primary key field defined in the JSON object now throws `IllegalArgumentException`. ### Enhancements @@ -26,12 +20,10 @@ * Fixed a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11 (#3356). * Fixed a potential crash when there were lots of async queries waiting in the queue. -* Fixed a bug causing the Realm Transformer to not transform field access in the model's constructors (#3361). ### Internal * Moved JNI build to CMake. -* Updated Realm Core to 2.0.0-rc4. ## 1.2.0 diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index b9e388c828..b927ef5b38 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -66,10 +66,11 @@ class BytecodeModifier { !behavior.name.startsWith('realmGet$') && !behavior.name.startsWith('realmSet$') ) || ( - behavior instanceof CtConstructor + behavior instanceof CtConstructor && + !modelClasses.contains(clazz) ) ) { - behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior, modelClasses.contains(clazz))) + behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior)) } } } @@ -93,18 +94,11 @@ class BytecodeModifier { final List managedFields final CtClass ctClass final CtBehavior behavior - final boolean isModelClass - final boolean isInConstructor - FieldAccessToAccessorConverter(List managedFields, - CtClass ctClass, - CtBehavior behavior, - boolean isModelClass) { + FieldAccessToAccessorConverter(List managedFields, CtClass ctClass, CtBehavior behavior) { this.managedFields = managedFields this.ctClass = ctClass this.behavior = behavior - this.isModelClass = isModelClass - this.isInConstructor = behavior instanceof CtConstructor } @Override @@ -118,23 +112,9 @@ class BytecodeModifier { logger.info " Methods: ${ctClass.declaredMethods}" def fieldName = fieldAccess.fieldName if (fieldAccess.isReader()) { - if (isInConstructor && isModelClass) { - // work around https://github.com/realm/realm-java/issues/2536 - // '$0' is the object that owns target field. - // 'this' is the instance where the constructor belongs. - fieldAccess.replace('$_ = ($0 == this) ? $0.' + fieldName + ' : $0.realmGet$' + fieldName + '();') - } else { - fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') - } + fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') } else if (fieldAccess.isWriter()) { - if (isInConstructor && isModelClass) { - // work around https://github.com/realm/realm-java/issues/2536 - // '$0' is the object that owns target field. - // 'this' is the instance where the constructor belongs. - fieldAccess.replace('if ($0 == this) {$0.' + fieldName + ' = $1;} else { $0.realmSet$' + fieldName + '($1);}') - } else { - fieldAccess.replace('$0.realmSet$' + fieldName + '($1);') - } + fieldAccess.replace('$0.realmSet$' + fieldName + '($1);') } } } diff --git a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy index 6750f2b273..65609cd5df 100644 --- a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy +++ b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy @@ -79,7 +79,7 @@ class BytecodeModifierTest extends Specification { def "UseRealmAccessors"() { setup: 'generate an empty class' def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('TestClass') + def ctClass = classPool.makeClass('testClass') and: 'add a field' def ctField = new CtField(CtClass.intType, 'age', ctClass) @@ -95,44 +95,21 @@ class BytecodeModifierTest extends Specification { when: 'the field use is replaced by the accessor' BytecodeModifier.useRealmAccessors(ctClass, [ctField], []) - then: 'the field is not used and getter is called in the method ' - !isFieldRead(ctMethod) && hasMethodCall(ctMethod) - } - - def "UseRealmAccessors_fieldAccessInModelConstructorIsTransformed"() { - setup: 'generate an empty class' - def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('TestClass') - - and: 'add a field' - def ctField = new CtField(CtClass.intType, 'age', ctClass) - ctClass.addField(ctField) - - and: 'add a method that sets such field' - def ctMethod = CtNewMethod.make('private void setupAge(int age) { this.age = age; }', ctClass) - ctClass.addMethod(ctMethod) - - and: 'add a default constructor that uses the method' - def ctDefaultConstructor = CtNewConstructor.make('public TestClass() { int myAge = this.age; }', ctClass) - ctClass.addConstructor(ctDefaultConstructor) - - and: 'add a non-default constructor that uses the method' - def ctNonDefaultConstructor = CtNewConstructor.make('public TestClass(TestClass other) { int otherAge = other.age; }', ctClass) - ctClass.addConstructor(ctNonDefaultConstructor) - - and: 'realm accessors are added' - BytecodeModifier.addRealmAccessors(ctClass) - - when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(ctClass, [ctField], [ctClass]) - - then: 'the field is still used and also getter is called in the constructor' - // to work around https://github.com/realm/realm-java/issues/2536 , field access is not removed - isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && - isFieldRead(ctNonDefaultConstructor) && hasMethodCall(ctNonDefaultConstructor) + then: 'the field is not used in the method anymore' + def methodInfo = ctMethod.getMethodInfo() + def codeAttribute = methodInfo.getCodeAttribute() + def fieldIsUsed = false + for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { + int index = ci.next(); + int op = ci.byteAt(index); + if (op == Opcode.GETFIELD) { + fieldIsUsed = true + } + } + !fieldIsUsed } - def "UseRealmAccessors_fieldAccessInNonModelConstructorIsTransformed"() { + def "UseRealmAccessorsInNonDefaultConstructor"() { setup: 'generate an empty class' def classPool = ClassPool.getDefault() def ctClass = classPool.makeClass('TestClass') @@ -145,50 +122,27 @@ class BytecodeModifierTest extends Specification { def ctMethod = CtNewMethod.make('private void setupAge(int age) { this.age = age; }', ctClass) ctClass.addMethod(ctMethod) - and: 'add a default constructor that uses the method' - def ctDefaultConstructor = CtNewConstructor.make('public TestClass() { int myAge = this.age; }', ctClass) - ctClass.addConstructor(ctDefaultConstructor) - - and: 'add a non-default constructor that uses the method' - def ctNonDefaultConstructor = CtNewConstructor.make('public TestClass(TestClass other) { int otherAge = other.age; }', ctClass) - ctClass.addConstructor(ctNonDefaultConstructor) + and: 'add a constructor that uses the method' + def ctConstructor = CtNewConstructor.make('public TestClass(int age) { setupAge(age); }', ctClass) + ctClass.addConstructor(ctConstructor) and: 'realm accessors are added' BytecodeModifier.addRealmAccessors(ctClass) when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(ctClass, [ctField], [/* no ctClass in model class list*/]) + BytecodeModifier.useRealmAccessors(ctClass, [ctField], []) then: 'the field is not used in the method anymore' - !isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && - !isFieldRead(ctNonDefaultConstructor) && hasMethodCall(ctNonDefaultConstructor) - } - - private static def isFieldRead(CtBehavior behavior) { - def methodInfo = behavior.getMethodInfo() + def methodInfo = ctMethod.getMethodInfo() def codeAttribute = methodInfo.getCodeAttribute() - - for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { - int index = ci.next(); - int op = ci.byteAt(index); - if (op == Opcode.GETFIELD) { - return true - } - } - return false - } - - private static def hasMethodCall(CtBehavior behavior) { - def methodInfo = behavior.getMethodInfo() - def codeAttribute = methodInfo.getCodeAttribute() - + def fieldIsUsed = false for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { int index = ci.next(); int op = ci.byteAt(index); - if (op == Opcode.INVOKEVIRTUAL) { - return true + if (op == Opcode.PUTFIELD) { + fieldIsUsed = true } } - return false + !fieldIsUsed } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java index 592aec97b3..68b2f11ca9 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java @@ -28,8 +28,6 @@ public class Constants { public static final String DEFAULT_MODULE_CLASS_NAME = "DefaultRealmModule"; static final String STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE = "throw new IllegalArgumentException(\"Trying to set non-nullable field '%s' to null.\")"; - static final String STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON = - "throw new IllegalArgumentException(\"JSON object doesn't have the primary key field '%s'.\")"; static final Map JAVA_TO_REALM_TYPES; static { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java index c54e00e149..cd63ab09a3 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java @@ -68,7 +68,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String - fieldType, JavaWriter writer, boolean isPrimaryKey) + fieldType, JavaWriter writer) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") @@ -110,7 +110,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String - fieldType, JavaWriter writer, boolean isPrimaryKey) + fieldType, JavaWriter writer) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") @@ -183,16 +183,11 @@ public static void emitFillRealmListWithJsonValue(String interfaceName, String g } - public static void emitFillJavaTypeFromStream(String interfaceName, ClassMetaData metaData, String fieldName, String + public static void emitFillJavaTypeFromStream(String interfaceName, String setter, String fieldName, String fieldType, JavaWriter writer) throws IOException { - String setter = metaData.getSetter(fieldName); - boolean isPrimaryKey = false; - if (metaData.hasPrimaryKey() && metaData.getPrimaryKey().getSimpleName().toString().equals(fieldName)) { - isPrimaryKey = true; - } if (JAVA_TO_JSON_TYPES.containsKey(fieldType)) { JAVA_TO_JSON_TYPES.get(fieldType).emitStreamTypeConversion(interfaceName, setter, fieldName, fieldType, - writer, isPrimaryKey); + writer); } } @@ -216,7 +211,6 @@ public static void emitFillRealmListFromStream(String interfaceName, String gett .emitStatement("reader.skipValue()") .emitStatement("((%s) obj).%s(null)", interfaceName, setter) .nextControlFlow("else") - .emitStatement("((%s) obj).%s(new RealmList<%s>())", interfaceName, setter, fieldTypeCanonicalName) .emitStatement("reader.beginArray()") .beginControlFlow("while (reader.hasNext())") .emitStatement("%s item = %s.createUsingJsonStream(realm, reader)", fieldTypeCanonicalName, proxyClass) @@ -270,7 +264,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer, boolean isPrimaryKey) + JavaWriter writer) throws IOException { String statementSetNullOrThrow; if (Utils.isPrimitiveType(fieldType)) { @@ -287,9 +281,6 @@ public void emitStreamTypeConversion(String interfaceName, String setter, String .nextControlFlow("else") .emitStatement("((%s) obj).%s((%s) reader.next%s())", interfaceName, setter, castType, jsonType) .endControlFlow(); - if (isPrimaryKey) { - writer.emitStatement("jsonHasPrimaryKey = true"); - } } @Override @@ -308,7 +299,8 @@ public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass, jsonType, fieldName) .endControlFlow() .nextControlFlow("else") - .emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) + .emitStatement("obj = (%1$s) realm.createObject(%2$s.class)", + qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass) .endControlFlow(); } } @@ -317,7 +309,7 @@ private interface JsonToRealmFieldTypeConverter { void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, JavaWriter writer) throws IOException; void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer, boolean isPrimaryKey) throws IOException; + JavaWriter writer) throws IOException; void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) throws IOException; diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 5eff1c6043..8d1e445e09 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -1124,11 +1124,15 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP } writer.beginControlFlow("if (rowIndex == TableOrView.NO_MATCH)"); + writer.emitStatement("rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1)"); if (Utils.isString(metadata.getPrimaryKey())) { - writer.emitStatement("rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false)"); + writer.beginControlFlow("if (primaryKeyValue != null)"); + writer.emitStatement("Table.nativeSetString(tableNativePtr, pkColumnIndex, rowIndex, (String)primaryKeyValue)"); + writer.endControlFlow(); } else { - writer.emitStatement("rowIndex = table.addEmptyRowWithPrimaryKey(((%s) object).%s(), false)", - interfaceName, primaryKeyGetter); + writer.beginControlFlow("if (primaryKeyValue != null)"); + writer.emitStatement("Table.nativeSetLong(tableNativePtr, pkColumnIndex, rowIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter); + writer.endControlFlow(); } if (throwIfPrimaryKeyDuplicate) { @@ -1549,10 +1553,6 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep writer.emitEmptyLine(); } - // FIXME: Since we need to check the PK in stream before create an object, this is now using copyToRealm instead of - // createObject() to avoid parse the stream twice. This brings a problem that the default value behaviour is - // different from those which are using the createObject. And it needs to be addressed by - // https://github.com/realm/realm-java/issues/777 private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.emitAnnotation("TargetApi", "Build.VERSION_CODES.HONEYCOMB"); @@ -1563,10 +1563,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { Arrays.asList("Realm", "realm", "JsonReader", "reader"), Collections.singletonList("IOException")); - if (metadata.hasPrimaryKey()) { - writer.emitStatement("boolean jsonHasPrimaryKey = false"); - } - writer.emitStatement("%s obj = new %s()", qualifiedClassName, qualifiedClassName); + writer.emitStatement("%s obj = realm.createObject(%s.class)",qualifiedClassName, qualifiedClassName); writer.emitStatement("reader.beginObject()"); writer.beginControlFlow("while (reader.hasNext())"); writer.emitStatement("String name = reader.nextName()"); @@ -1604,7 +1601,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { } else { RealmJsonTypeHelper.emitFillJavaTypeFromStream( interfaceName, - metadata, + metadata.getSetter(fieldName), fieldName, qualifiedFieldType, writer @@ -1619,16 +1616,9 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { } writer.endControlFlow(); writer.emitStatement("reader.endObject()"); - if (metadata.hasPrimaryKey()) { - writer.beginControlFlow("if (!jsonHasPrimaryKey)"); - writer.emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.getPrimaryKey()); - writer.endControlFlow(); - } - writer.emitStatement("obj = realm.copyToRealm(obj)"); writer.emitStatement("return obj"); writer.endMethod(); writer.emitEmptyLine(); - } private String columnInfoClassName() { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index c5c43b8825..bdec992709 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -411,7 +411,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON obj = (io.realm.AllTypesRealmProxy) realm.createObject(some.test.AllTypes.class, json.getString("columnString")); } } else { - throw new IllegalArgumentException("JSON object doesn't have the primary key field 'columnString'."); + obj = (io.realm.AllTypesRealmProxy) realm.createObject(some.test.AllTypes.class); } } if (json.has("columnString")) { @@ -495,8 +495,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - boolean jsonHasPrimaryKey = false; - some.test.AllTypes obj = new some.test.AllTypes(); + some.test.AllTypes obj = realm.createObject(some.test.AllTypes.class); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -507,7 +506,6 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } else { ((AllTypesRealmProxyInterface) obj).realmSet$columnString((String) reader.nextString()); } - jsonHasPrimaryKey = true; } else if (name.equals("columnLong")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); @@ -568,7 +566,6 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r reader.skipValue(); ((AllTypesRealmProxyInterface) obj).realmSet$columnRealmList(null); } else { - ((AllTypesRealmProxyInterface) obj).realmSet$columnRealmList(new RealmList()); reader.beginArray(); while (reader.hasNext()) { some.test.AllTypes item = AllTypesRealmProxy.createUsingJsonStream(realm, reader); @@ -581,10 +578,6 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } } reader.endObject(); - if (!jsonHasPrimaryKey) { - throw new IllegalArgumentException("JSON object doesn't have the primary key field 'columnString'."); - } - obj = realm.copyToRealm(obj); return obj; } @@ -690,7 +683,10 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == TableOrView.NO_MATCH) { - rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); + rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); + if (primaryKeyValue != null) { + Table.nativeSetString(tableNativePtr, pkColumnIndex, rowIndex, (String)primaryKeyValue); + } } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } @@ -814,7 +813,10 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == TableOrView.NO_MATCH) { - rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); + rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); + if (primaryKeyValue != null) { + Table.nativeSetString(tableNativePtr, pkColumnIndex, rowIndex, (String)primaryKeyValue); + } } cache.put(object, rowIndex); Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong()); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 6f555eb3f6..f04ecb0b32 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -230,7 +230,7 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.Booleans obj = new some.test.Booleans(); + some.test.Booleans obj = realm.createObject(some.test.Booleans.class); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -267,7 +267,6 @@ public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader r } } reader.endObject(); - obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index e4faa4691d..9500a86e0c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -915,7 +915,7 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.NullTypes obj = new some.test.NullTypes(); + some.test.NullTypes obj = realm.createObject(some.test.NullTypes.class); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -1082,7 +1082,6 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader } } reader.endObject(); - obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index cf863899ca..b4b5aca12a 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -168,7 +168,7 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.Simple obj = new some.test.Simple(); + some.test.Simple obj = realm.createObject(some.test.Simple.class); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -191,7 +191,6 @@ public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader rea } } reader.endObject(); - obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 0d7ffbe2fd..01678728ff 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '1.0.0-beta-31' +ext.coreVersion = '0.28.0' // empty or comment out this to disable hash checking -ext.coreSha256Hash = 'd922d14c5770429aafe57d76b95ae57a77da8625dd1a723d5518e032d167d114' +ext.coreSha256Hash = 'e4d8ed7342824a1574449700b16cd36f663f4d6768fc09c1aca986f19e27162b' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from @@ -129,9 +129,9 @@ task javadoc(type: Javadoc) { locale = 'en_US' overview = 'src/overview.html' - links "https://docs.oracle.com/javase/7/docs/api/" + links "http://docs.oracle.com/javase/7/docs/api/" links "http://reactivex.io/RxJava/javadoc/" - linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" + linksOffline "http://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" } exclude '**/internal/**' exclude '**/BuildConfig.java' diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 9821c20c12..70a8ff2494 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -40,7 +40,6 @@ import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; -import io.realm.exceptions.RealmException; import io.realm.internal.HandlerControllerConstants; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; @@ -234,12 +233,6 @@ public void createObject_illegalPrimaryKeyValue() { realm.createObject(DogPrimaryKey.CLASS_NAME, "bar"); } - @Test(expected = RealmException.class) - public void createObject_absentPrimaryKeyThrows() { - realm.beginTransaction(); - realm.createObject(DogPrimaryKey.CLASS_NAME); - } - @Test public void where() { realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 90625fc4f8..ac29db2afc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -28,7 +28,6 @@ import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -955,7 +954,6 @@ public void onChange(Realm element) { }); } - // FIXME check if the SharedRealm Changed in handleAsyncTransactionCompleted and reenable this test. // We precisely depend on the order of triggering change listeners right now. // So it should be: // 1. Synced object listener @@ -966,7 +964,6 @@ public void onChange(Realm element) { // If this case fails on your code, think twice before changing the test! // https://github.com/realm/realm-java/issues/2408 is related to this test! @Test - @Ignore("Listener on Realm might be trigger more times, ignore for now") @RunTestInLooperThread public void callingOrdersOfListeners() { final Realm realm = looperThread.realm; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java deleted file mode 100644 index c985be7e62..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.io.IOException; -import java.util.Arrays; - -import io.realm.entities.PrimaryKeyAsBoxedByte; -import io.realm.entities.PrimaryKeyAsBoxedInteger; -import io.realm.entities.PrimaryKeyAsBoxedLong; -import io.realm.entities.PrimaryKeyAsBoxedShort; -import io.realm.entities.PrimaryKeyAsString; -import io.realm.rule.TestRealmConfigurationFactory; - -@RunWith(Parameterized.class) -public class RealmJsonAbsentPrimaryKeyTests { - @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - protected Realm realm; - - @Before - public void setUp() { - RealmConfiguration realmConfig = configFactory.createConfiguration(); - realm = Realm.getInstance(realmConfig); - } - - @After - public void tearDown() { - if (realm != null) { - realm.close(); - } - } - - // parameters for testing absent primary key value. PrimaryKey field is absent. - @Parameterized.Parameters - public static Iterable data() { - return Arrays.asList(new Object[][]{ - {PrimaryKeyAsBoxedByte.class, "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, - {PrimaryKeyAsBoxedShort.class, "{ \"name\":\"KeyValueTestIsFun\" }"}, - {PrimaryKeyAsBoxedInteger.class, "{ \"name\":\"FunValueTestIsKey\" }"}, - {PrimaryKeyAsBoxedLong.class, "{ \"name\":\"NameAsBoxedLong-!\" }"}, - {PrimaryKeyAsString.class, "{ \"id\":2429214 }"} - }); - } - - final private Class clazz; - final private String jsonString; - - public RealmJsonAbsentPrimaryKeyTests(Class clazz, String jsonString) { - this.jsonString = jsonString; - this.clazz = clazz; - } - - // Testing absent primary key value for createObjectFromJson() - @Test - public void createObjectFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { - realm.beginTransaction(); - thrown.expect(IllegalArgumentException.class); - realm.createObjectFromJson(clazz, new JSONObject(jsonString)); - realm.commitTransaction(); - } - - // Testing absent primary key value for createOrUpdateObjectFromJson() - @Test - public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { - realm.beginTransaction(); - thrown.expect(IllegalArgumentException.class); - realm.createOrUpdateObjectFromJson(clazz, new JSONObject(jsonString)); - realm.commitTransaction(); - } - - // Testing absent primary key value for createAllFromJson() - @Test - public void createAllFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { - JSONArray jsonArray = new JSONArray(); - jsonArray.put(new JSONObject(jsonString)); - realm.beginTransaction(); - thrown.expect(IllegalArgumentException.class); - realm.createAllFromJson(clazz, jsonArray); - realm.commitTransaction(); - } - - // Testing absent primary key value for createOrUpdateAllFromJson() - @Test - public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { - JSONArray jsonArray = new JSONArray(); - jsonArray.put(new JSONObject(jsonString)); - realm.beginTransaction(); - thrown.expect(IllegalArgumentException.class); - realm.createOrUpdateAllFromJson(clazz, jsonArray); - realm.commitTransaction(); - } - - // Testing absent primary key value for createObjectFromJson() stream version - @Test - public void createObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { - realm.beginTransaction(); - thrown.expect(IllegalArgumentException.class); - realm.createObjectFromJson(clazz, TestHelper.stringToStream(jsonString)); - realm.commitTransaction(); - } - - // Testing absent primary key value for createOrUpdateObjectFromJson() stream version - @Test - public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { - realm.beginTransaction(); - thrown.expect(IllegalArgumentException.class); - realm.createOrUpdateObjectFromJson(clazz, TestHelper.stringToStream(jsonString)); - realm.commitTransaction(); - } - - // Testing absent primary key value for createAllFromJson() stream version - @Test - public void createAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { - JSONArray jsonArray = new JSONArray(); - jsonArray.put(new JSONObject(jsonString)); - realm.beginTransaction(); - thrown.expect(IllegalArgumentException.class); - realm.createAllFromJson(clazz, TestHelper.stringToStream(jsonArray.toString())); - realm.commitTransaction(); - } - - // Testing absent primary key value for createOrUpdateAllFromJson() stream version - @Test - public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { - JSONArray jsonArray = new JSONArray(); - jsonArray.put(new JSONObject(jsonString)); - realm.beginTransaction(); - thrown.expect(IllegalArgumentException.class); - realm.createOrUpdateAllFromJson(clazz, TestHelper.stringToStream(jsonArray.toString())); - realm.commitTransaction(); - } -} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java index f0345f07bd..c5482eb151 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java @@ -57,7 +57,7 @@ public void tearDown() { } } - // parameters for testing null primary key value. PrimaryKey field is explicitly null + // parameters for testing null primary key value. PrimaryKey field is explicitly null or absent. @Parameterized.Parameters public static Iterable data() { return Arrays.asList(new Object[][]{ @@ -65,7 +65,12 @@ public static Iterable data() { {PrimaryKeyAsBoxedShort.class, "YouBetItIsNullKey", "{ \"id\":null, \"name\":\"YouBetItIsNullKey\" }"}, {PrimaryKeyAsBoxedInteger.class, "Gosh Didnt KnowIt", "{ \"id\":null, \"name\":\"Gosh Didnt KnowIt\" }"}, {PrimaryKeyAsBoxedLong.class, "?YOUNOWKNOWRIGHT?", "{ \"id\":null, \"name\":\"?YOUNOWKNOWRIGHT?\" }"}, + {PrimaryKeyAsBoxedByte.class, "HaHaHaHaHaHaHaHaH", "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, + {PrimaryKeyAsBoxedShort.class, "KeyValueTestIsFun", "{ \"name\":\"KeyValueTestIsFun\" }"}, + {PrimaryKeyAsBoxedInteger.class, "FunValueTestIsKey", "{ \"name\":\"FunValueTestIsKey\" }"}, + {PrimaryKeyAsBoxedLong.class, "NameAsBoxedLong-!", "{ \"name\":\"NameAsBoxedLong-!\" }"}, {PrimaryKeyAsString.class, "4299121", "{ \"name\":null, \"id\":4299121 }"}, + {PrimaryKeyAsString.class, "2429214", "{ \"id\":2429214 }"} }); } @@ -79,9 +84,9 @@ public RealmJsonNullPrimaryKeyTests(Class clazz, String s this.clazz = clazz; } - // Testing null primary key value for createObjectFromJson() + // Testing null or absent primary key value for createObjectFromJson() @Test - public void createObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONException { + public void createObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObject() throws JSONException { realm.beginTransaction(); realm.createObjectFromJson(clazz, new JSONObject(jsonString)); realm.commitTransaction(); @@ -102,9 +107,9 @@ public void createObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONE } } - // Testing null primary key value for createOrUpdateObjectFromJson() + // Testing null or absent primary key value for createOrUpdateObjectFromJson() @Test - public void createOrUpdateObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONException { + public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObject() throws JSONException { realm.beginTransaction(); realm.createOrUpdateObjectFromJson(clazz, new JSONObject(jsonString)); realm.commitTransaction(); @@ -125,11 +130,11 @@ public void createOrUpdateObjectFromJson_primaryKey_isNull_fromJsonObject() thro } } - // Testing null primary key value for createObject() -> createOrUpdateObjectFromJson() + // Testing null or absent primary key value for createObject() -> createOrUpdateObjectFromJson() @Test - public void createOrUpdateObjectFromJson_primaryKey_isNull_updateFromJsonObject() throws JSONException { + public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_updateFromJsonObject() throws JSONException { realm.beginTransaction(); - realm.createObject(clazz, null); // name = null, id =null + realm.createObject(clazz); // name = null, id = 0 realm.createOrUpdateObjectFromJson(clazz, new JSONObject(jsonString)); realm.commitTransaction(); @@ -139,6 +144,7 @@ public void createOrUpdateObjectFromJson_primaryKey_isNull_updateFromJsonObject( assertEquals(1, results.size()); assertEquals(Long.valueOf(secondaryFieldValue).longValue(), results.first().getId()); assertEquals(null, results.first().getName()); + // PrimaryKeyAsNumber } else { RealmResults results = realm.where(clazz).findAll(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index 1e697d5f43..fbcc2fdcf8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -409,7 +409,6 @@ public void createObjectFromJson_jsonException() throws JSONException { @Test public void createObjectFromJson_respectIgnoredFields() throws JSONException { JSONObject json = new JSONObject(); - json.put("id", 0); json.put("indexString", "Foo"); json.put("notIndexString", "Bar"); json.put("ignoreString", "Baz"); @@ -764,7 +763,7 @@ public void createOrUpdateObjectFromJson_inputStream() throws IOException { public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException { InputStream stream = TestHelper.stringToStream("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 + realm.createObject(OwnerPrimaryKey.class); // id = 0 realm.createOrUpdateObjectFromJson(OwnerPrimaryKey.class, stream); realm.commitTransaction(); @@ -962,7 +961,7 @@ public void createOrUpdateObjectFromJson_invalidJsonObject() throws JSONExceptio public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromJsonObject() throws JSONException { JSONObject newObject = new JSONObject("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 + realm.createObject(OwnerPrimaryKey.class); // id = 0 realm.createOrUpdateObjectFromJson(OwnerPrimaryKey.class, newObject); realm.commitTransaction(); @@ -1368,7 +1367,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromJsonObject() throws JSONException { JSONObject newObject = new JSONObject("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 + realm.createObject(OwnerPrimaryKey.class); // id = 0 realm.createObjectFromJson(OwnerPrimaryKey.class, newObject); realm.commitTransaction(); @@ -1394,7 +1393,7 @@ public void createObjectFromJson_objectNullClass() throws JSONException { public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException { InputStream stream = TestHelper.stringToStream("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 + realm.createObject(OwnerPrimaryKey.class); // id = 0 realm.createObjectFromJson(OwnerPrimaryKey.class, stream); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index b4f48e94fc..d634c2a2da 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -101,7 +101,8 @@ public void createObject() { for (int i = 1; i < 43; i++) { // using i = 0 as PK will crash subsequent createObject // since createObject uses default values realm.beginTransaction(); - realm.createObject(AllTypesRealmModel.class, i); + AllTypesRealmModel allTypesRealmModel = realm.createObject(AllTypesRealmModel.class); + allTypesRealmModel.columnLong = i; realm.commitTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 8bea9267cf..8cf83d61a9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2249,9 +2249,6 @@ public void resultOfTableViewQuery() { populateTestRealm(); final RealmResults results = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 3L).findAll(); - assertEquals(1, results.size()); - assertEquals("test data 3", results.first().getColumnString()); - final RealmQuery tableViewQuery = results.where(); assertEquals("test data 3", tableViewQuery.findAll().first().getColumnString()); assertEquals("test data 3", tableViewQuery.findFirst().getColumnString()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index d4bcf79876..975b7aed87 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -2087,12 +2087,6 @@ public void createObject_cannotCreateDynamicRealmObject() { } } - @Test(expected = RealmException.class) - public void createObject_absentPrimaryKeyThrows() { - realm.beginTransaction(); - realm.createObject(DogPrimaryKey.class); - } - @Test public void createObjectWithPrimaryKey() { realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index bb2f61f4cb..396e9cdf91 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -153,19 +153,19 @@ private void checkSortTwoFieldsStringAscendingIntAscending(RealmResults results) { @@ -179,19 +179,19 @@ private void checkSortTwoFieldsIntString(RealmResults results) { assertEquals("Adam", results.get(0).getColumnString()); assertEquals(4, results.get(0).getColumnLong()); - assertEquals(2, ((TableView) results.getTableOrView()).getSourceRowIndex(0)); + assertEquals(2, ((TableView) results.getTable()).getSourceRowIndex(0)); assertEquals("Brian", results.get(1).getColumnString()); assertEquals(4, results.get(1).getColumnLong()); - assertEquals(1, ((TableView) results.getTableOrView()).getSourceRowIndex(1)); + assertEquals(1, ((TableView) results.getTable()).getSourceRowIndex(1)); assertEquals("Adam", results.get(2).getColumnString()); assertEquals(5, results.get(2).getColumnLong()); - assertEquals(0, ((TableView) results.getTableOrView()).getSourceRowIndex(2)); + assertEquals(0, ((TableView) results.getTable()).getSourceRowIndex(2)); assertEquals("Adam", results.get(3).getColumnString()); assertEquals(5, results.get(3).getColumnLong()); - assertEquals(3, ((TableView) results.getTableOrView()).getSourceRowIndex(3)); + assertEquals(3, ((TableView) results.getTable()).getSourceRowIndex(3)); } private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -205,19 +205,19 @@ private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -231,19 +231,19 @@ private void checkSortTwoFieldsStringAscendingIntDescending(RealmResultsget(S(linkViewIndex)).get_index(); + return lvr->get( S(pos) ).get_index(); } CATCH_STD() return 0; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 0f1eb70bed..94953a1346 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -638,17 +638,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLong( } CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_Table_nativeSetLongUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, - jlong rowIndex, jlong value) -{ - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) - return; - try { - TBL(nativeTablePtr)->set_int_unique( S(columnIndex), S(rowIndex), value); - } CATCH_STD() -} - JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean( JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jboolean value) { @@ -695,24 +684,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString( } CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_Table_nativeSetStringUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, - jlong rowIndex, jstring value) -{ - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) - return; - try { - if (value == NULL) { - if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { - return; - } - } - JStringAccessor value2(env, value); // throws - // FIXME: Check if we need to call set_null_unique when core support it. - TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), value2); - } CATCH_STD() -} - JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp( JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong timestampValue) { @@ -741,12 +712,15 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray( if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) return; try { - if (dataArray == NULL && !TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { + if (dataArray == NULL) { + if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { return; + } + TBL(nativeTablePtr)->set_binary(S(columnIndex), S(rowIndex), BinaryData()); + } + else { + tbl_nativeDoByteArray(&Table::set_binary, TBL(nativeTablePtr), env, columnIndex, rowIndex, dataArray); } - - JniByteArray byteAccessor(env, dataArray); - TBL(nativeTablePtr)->set_binary(S(columnIndex), S(rowIndex), byteAccessor); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 2f038e1d69..4d8859e62f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include "util.hpp" @@ -1612,13 +1613,15 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeCount( } JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeRemove( - JNIEnv* env, jobject, jlong nativeQueryPtr) + JNIEnv* env, jobject, jlong nativeQueryPtr, jlong start, jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) + Table* pTable = pQuery->get_table().get(); + if (!QUERY_VALID(env, pQuery) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) return 0; try { - return pQuery->remove(); + return pQuery->remove(S(start), S(end), S(limit)); } CATCH_STD() return 0; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp index b4d561be94..65866dc126 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp @@ -397,9 +397,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetByteArray( if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Binary)) return; - - JniByteArray bytesAccessor(env, byteArray); - TV(nativeViewPtr)->set_binary(S(columnIndex), S(rowIndex), bytesAccessor); + tbl_nativeDoByteArray(&TableView::set_binary, TV(nativeViewPtr), env, columnIndex, rowIndex, byteArray); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp index bf7ea21178..a9ca249173 100644 --- a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp +++ b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp @@ -41,4 +41,26 @@ jbyteArray tbl_GetByteArray(JNIEnv* env, jlong nativeTablePtr, jlong columnIndex } } +template +void tbl_nativeDoByteArray(M doBinary, T* pTable, JNIEnv* env, jlong columnIndex, jlong rowIndex, jbyteArray dataArray) +{ + jbyte* bytePtr = env->GetByteArrayElements(dataArray, NULL); + if (!bytePtr) { + ThrowException(env, IllegalArgument, "doByteArray"); + return; + } + size_t dataLen = S(env->GetArrayLength(dataArray)); + (pTable->*doBinary)( S(columnIndex), S(rowIndex), realm::BinaryData(reinterpret_cast(bytePtr), dataLen)); + env->ReleaseByteArrayElements(dataArray, bytePtr, 0); +} + + +template +void tbl_nativeDoBinary(M doBinary, T* pTable, JNIEnv* env, jlong columnIndex, jlong rowIndex, jobject byteBuffer) +{ + realm::BinaryData bin; + if (GetBinaryData(env, byteBuffer, bin)) + (pTable->*doBinary)( S(columnIndex), S(rowIndex), bin); +} + #endif // REALM_JNI_TABLEBASE_TPL_HPP diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 8cbd3e37cb..7a402f4c2d 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -28,14 +28,12 @@ #include #include -#include -#include #include #include +#include +#include #include -#include - #include "io_realm_internal_Util.h" #include "io_realm_log_LogLevel.h" @@ -445,8 +443,6 @@ inline bool TblColIndexAndLinkOrLinkList(JNIEnv* env, T* pTable, jlong columnInd && TypeIsLinkLike(env, pTable, columnIndex); } -// FIXME Usually this is called after TBL_AND_INDEX_AND_TYPE_VALID which will validate Table as well. -// Try to avoid duplicated checks to improve performance. template inline bool TblColIndexAndNullable(JNIEnv* env, T* pTable, jlong columnIndex) { return TableIsValid(env, pTable) @@ -579,10 +575,6 @@ class JniByteArray { , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) , m_array(javaArray == NULL ? NULL : env->GetByteArrayElements(javaArray, NULL)) , m_releaseMode(JNI_ABORT) { - if (m_javaArray != nullptr && m_array == nullptr) { - // javaArray is not null but GetByteArrayElements returns null, something is really wrong. - throw std::runtime_error(realm::util::format("GetByteArrayElements failed on byte array %x", m_javaArray)); - } } ~JniByteArray() diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 3445f9cac2..20671389cd 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -77,11 +77,6 @@ public static DynamicRealm getInstance(RealmConfiguration configuration) { public DynamicRealmObject createObject(String className) { checkIfValid(); Table table = schema.getTable(className); - // Check and throw the exception earlier for a better exception message. - if (table.hasPrimaryKey()) { - throw new RealmException(String.format("'%s' has a primary key, use" + - " 'createObject(String, Object)' instead.", className)); - } long rowIndex = table.addEmptyRow(); return get(DynamicRealmObject.class, className, rowIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 204d4ded3b..9f294b8af4 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -295,14 +295,13 @@ private static void initializeRealm(Realm realm) { /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. *

    - * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the - * JSON object the {@link RealmObject} field will be set to the default value for that type. + * JSON properties with {@code null} values will map to the default value for the data type in Realm and unknown properties + * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} + * field will be set to the default value for that type. * * @param clazz type of Realm objects to create. * @param json an array where each JSONObject must map to the specified class. * @throws RealmException if mapping from JSON fails. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. */ public void createAllFromJson(Class clazz, JSONArray json) { if (clazz == null || json == null) { @@ -327,9 +326,8 @@ public void createAllFromJson(Class clazz, JSONArray j * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json array with object data. - * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. + * @throws java.lang.IllegalArgumentException if trying to update a class without a + * {@link io.realm.annotations.PrimaryKey}. * @throws RealmException if unable to map JSON. * @see #createAllFromJson(Class, org.json.JSONArray) */ @@ -349,14 +347,13 @@ public void createOrUpdateAllFromJson(Class clazz, JSO /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. - * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the - * JSON object the {@link RealmObject} field will be set to the default value for that type. + * JSON properties with {@code null} values will map to the default value for the data type in Realm and unknown properties + * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field + * will be set to the default value for that type. * * @param clazz type of Realm objects to create. * @param json the JSON array as a String where each object can map to the specified class. * @throws RealmException if mapping from JSON fails. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. */ public void createAllFromJson(Class clazz, String json) { if (clazz == null || json == null || json.length() == 0) { @@ -382,10 +379,9 @@ public void createAllFromJson(Class clazz, String json * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json string with an array of JSON objects. - * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws java.lang.IllegalArgumentException if trying to update a class without a + * {@link io.realm.annotations.PrimaryKey}. * @throws RealmException if unable to create a JSON array from the json string. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @see #createAllFromJson(Class, String) */ public void createOrUpdateAllFromJson(Class clazz, String json) { @@ -406,14 +402,13 @@ public void createOrUpdateAllFromJson(Class clazz, Str /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. - * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the - * JSON object the {@link RealmObject} field will be set to the default value for that type. + * JSON properties with {@code null} value will map to the default value for the data type in Realm and unknown properties + * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field + * will be set to the default value for that type. * * @param clazz type of Realm objects created. * @param inputStream the JSON array as a InputStream. All objects in the array must be of the specified class. * @throws RealmException if mapping from JSON fails. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws IOException if something was wrong with the input stream. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) @@ -443,9 +438,8 @@ public void createAllFromJson(Class clazz, InputStream * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the InputStream with a list of object data in JSON format. - * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. + * @throws java.lang.IllegalArgumentException if trying to update a class without a + * {@link io.realm.annotations.PrimaryKey}. * @throws RealmException if unable to read JSON. * @see #createOrUpdateAllFromJson(Class, java.io.InputStream) */ @@ -476,15 +470,14 @@ public void createOrUpdateAllFromJson(Class clazz, Inp /** * Creates a Realm object pre-filled with data from a JSON object. This must be done inside a transaction. JSON - * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON - * object the {@link RealmObject} field will be set to the default value for that type. + * properties with {@code null} values will map to the default value for the data type in Realm and unknown properties will + * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will + * be set to the default value for that type. * * @param clazz type of Realm object to create. * @param json the JSONObject with object data. * @return created object or {@code null} if no JSON data was provided. * @throws RealmException if the mapping from JSON fails. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @see #createOrUpdateObjectFromJson(Class, org.json.JSONObject) */ public E createObjectFromJson(Class clazz, JSONObject json) { @@ -508,9 +501,8 @@ public E createObjectFromJson(Class clazz, JSONObject * @param clazz Type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json {@link org.json.JSONObject} with object data. * @return created or updated {@link io.realm.RealmObject}. - * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. + * @throws java.lang.IllegalArgumentException if trying to update a class without a + * {@link io.realm.annotations.PrimaryKey}. * @throws RealmException if JSON data cannot be mapped. * @see #createObjectFromJson(Class, org.json.JSONObject) */ @@ -529,15 +521,14 @@ public E createOrUpdateObjectFromJson(Class clazz, JSO /** * Creates a Realm object pre-filled with data from a JSON object. This must be done inside a transaction. JSON - * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON - * object the {@link RealmObject} field will be set to the default value for that type. + * properties with {@code null} values will map to the default value for the data type in Realm and unknown properties will + * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will + * be set to the default value for that type. * * @param clazz type of Realm object to create. * @param json the JSON string with object data. * @return created object or {@code null} if JSON string was empty or null. * @throws RealmException if mapping to json failed. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. */ public E createObjectFromJson(Class clazz, String json) { if (clazz == null || json == null || json.length() == 0) { @@ -564,9 +555,8 @@ public E createObjectFromJson(Class clazz, String json * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json string with object data in JSON format. * @return created or updated {@link io.realm.RealmObject}. - * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. + * @throws java.lang.IllegalArgumentException if trying to update a class without a + * {@link io.realm.annotations.PrimaryKey}. * @throws RealmException if JSON object cannot be mapped from the string parameter. * @see #createObjectFromJson(Class, String) */ @@ -588,15 +578,14 @@ public E createOrUpdateObjectFromJson(Class clazz, Str /** * Creates a Realm object pre-filled with data from a JSON object. This must be done inside a transaction. JSON - * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON - * object the {@link RealmObject} field will be set to the default value for that type. + * properties with {@code null} value will map to the default value for the data type in Realm and unknown properties will + * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will + * be set to the default value for that type. * * @param clazz type of Realm object to create. * @param inputStream the JSON object data as a InputStream. * @return created object or {@code null} if JSON string was empty or null. * @throws RealmException if the mapping from JSON failed. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws IOException if something went wrong with the input stream. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) @@ -643,9 +632,8 @@ public E createObjectFromJson(Class clazz, InputStream * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the {@link InputStream} with object data in JSON format. * @return created or updated {@link io.realm.RealmObject}. - * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. - * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding - * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. + * @throws java.lang.IllegalArgumentException if trying to update a class without a + * {@link io.realm.annotations.PrimaryKey}. * @throws RealmException if failure to read JSON. * @see #createObjectFromJson(Class, java.io.InputStream) */ @@ -686,11 +674,6 @@ private Scanner getFullStringScanner(InputStream in) { public E createObject(Class clazz) { checkIfValid(); Table table = schema.getTable(clazz); - // Check and throw the exception earlier for a better exception message. - if (table.hasPrimaryKey()) { - throw new RealmException(String.format("'%s' has a primary key, use" + - " 'createObject(Class, Object)' instead.", Table.tableNameToClassName(table.getName()))); - } long rowIndex = table.addEmptyRow(); return get(clazz, rowIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 43c382f3c6..0bfa172f4a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -453,7 +453,6 @@ public Builder directory(File directory) { /** * Sets the 64 bit key used to encrypt and decrypt the Realm file. - * Sets the {@value io.realm.RealmConfiguration#KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file. */ public Builder encryptionKey(byte[] key) { if (key == null) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 0ecf8c1b88..ed64e04ef9 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -65,7 +65,7 @@ public final class RealmQuery { private String className; private TableOrView table; private RealmObjectSchema schema; - private LinkView linkView; + private LinkView view; private TableQuery query; private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; @@ -136,7 +136,7 @@ private RealmQuery(Realm realm, Class clazz) { this.clazz = clazz; this.schema = realm.schema.getSchemaForClass(clazz); this.table = schema.table; - this.linkView = null; + this.view = null; this.query = table.where(); } @@ -144,18 +144,18 @@ private RealmQuery(RealmResults queryResults, Class clazz) { this.realm = queryResults.realm; this.clazz = clazz; this.schema = realm.schema.getSchemaForClass(clazz); - this.table = queryResults.getTableOrView(); - this.linkView = null; - this.query = this.table.where(); + this.table = queryResults.getTable(); + this.view = null; + this.query = queryResults.getTable().where(); } - private RealmQuery(BaseRealm realm, LinkView linkView, Class clazz) { + private RealmQuery(BaseRealm realm, LinkView view, Class clazz) { this.realm = realm; this.clazz = clazz; + this.query = view.where(); + this.view = view; this.schema = realm.schema.getSchemaForClass(clazz); this.table = schema.table; - this.linkView = linkView; - this.query = linkView.where(); } private RealmQuery(BaseRealm realm, String className) { @@ -171,16 +171,16 @@ private RealmQuery(RealmResults queryResults, String classNa this.className = className; this.schema = realm.schema.getSchemaForClass(className); this.table = schema.table; - this.query = queryResults.getTableOrView().where(); + this.query = queryResults.getTable().where(); } - private RealmQuery(BaseRealm realm, LinkView linkView, String className) { + private RealmQuery(BaseRealm realm, LinkView view, String className) { this.realm = realm; this.className = className; + this.query = view.where(); + this.view = view; this.schema = realm.schema.getSchemaForClass(className); this.table = schema.table; - this.linkView = linkView; - this.query = linkView.where(); } /** @@ -194,8 +194,8 @@ public boolean isValid() { return false; } - if (linkView != null) { - return linkView.isAttached(); + if (view != null) { + return view.isAttached(); } return table != null && table.getTable().isValid(); } @@ -2064,9 +2064,9 @@ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, */ public E findFirst() { checkQueryIsNotReused(); - long tableRowIndex = getSourceRowIndexForFirstObject(); - if (tableRowIndex >= 0) { - E realmObject = realm.get(clazz, className, tableRowIndex); + long sourceRowIndex = getSourceRowIndexForFirstObject(); + if (sourceRowIndex >= 0) { + E realmObject = realm.get(clazz, className, sourceRowIndex); return realmObject; } else { return null; @@ -2217,9 +2217,19 @@ private void checkQueryIsNotReused() { } private long getSourceRowIndexForFirstObject() { - long tableRowIndex = this.query.find(); - return tableRowIndex; + long rowIndex = this.query.find(); + if (rowIndex < 0) { + return rowIndex; + } + if (this.view != null) { + return view.getTargetRowIndex(rowIndex); + } else if (table instanceof TableView){ + return ((TableView) table).getSourceRowIndex(rowIndex); + } else { + return rowIndex; + } } + // Get the column index for sorting related functions. A proper exception will be thrown if the field doesn't exist // or it belongs to the child object. private long getColumnIndexForSort(String fieldName) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index ea37900e37..090302a6c2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -146,7 +146,7 @@ private RealmResults(BaseRealm realm, TableOrView table, String className) { this.currentTableViewVersion = table.syncIfNeeded(); } - TableOrView getTableOrView() { + TableOrView getTable() { if (table == null) { return realm.schema.getTable(classSpec); } else { @@ -177,6 +177,7 @@ public boolean isManaged() { @Override public RealmQuery where() { realm.checkIfValid(); + return RealmQuery.createQueryFromResult(this); } @@ -210,7 +211,7 @@ public boolean contains(Object object) { public E get(int location) { E obj; realm.checkIfValid(); - TableOrView table = getTableOrView(); + TableOrView table = getTable(); if (table instanceof TableView) { obj = realm.get(classSpec, className, ((TableView) table).getSourceRowIndex(location)); } else { @@ -251,7 +252,7 @@ public E last() { @Override public void deleteFromRealm(int location) { realm.checkIfValid(); - TableOrView table = getTableOrView(); + TableOrView table = getTable(); table.remove(location); } @@ -262,7 +263,7 @@ public void deleteFromRealm(int location) { public boolean deleteAllFromRealm() { realm.checkIfValid(); if (size() > 0) { - TableOrView table = getTableOrView(); + TableOrView table = getTable(); table.clear(); return true; } else { @@ -381,7 +382,7 @@ public int size() { if (!isLoaded()) { return 0; } else { - long size = getTableOrView().size(); + long size = getTable().size(); return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; } } @@ -508,7 +509,7 @@ public RealmResults distinct(String fieldName) { realm.checkIfValid(); long columnIndex = RealmQuery.getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); - TableOrView tableOrView = getTableOrView(); + TableOrView tableOrView = getTable(); if (tableOrView instanceof Table) { this.table = ((Table) tableOrView).getDistinctView(columnIndex); } else { @@ -617,7 +618,7 @@ public boolean retainAll(Collection collection) { public boolean deleteLastFromRealm() { realm.checkIfValid(); if (size() > 0) { - TableOrView table = getTableOrView(); + TableOrView table = getTable(); table.removeLast(); return true; } else { @@ -647,7 +648,7 @@ void syncIfNeeded() { @Override public boolean deleteFirstFromRealm() { if (size() > 0) { - TableOrView table = getTableOrView(); + TableOrView table = getTable(); table.removeFirst(); return true; } else { diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index 6ba77cc800..d05a4a51dd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -62,11 +62,8 @@ public CheckedRow getCheckedRow(long index) { return CheckedRow.get(context, this, index); } - /** - * Returns the row index in the underlying table. - */ - public long getTargetRowIndex(long linkViewIndex) { - return nativeGetTargetRowIndex(nativePointer, linkViewIndex); + public long getTargetRowIndex(long pos) { + return nativeGetTargetRowIndex(nativePointer, pos); } public void add(long rowIndex) { @@ -172,7 +169,7 @@ private void checkImmutable() { public static native void nativeClose(long nativeLinkViewPtr); native long nativeGetRow(long nativeLinkViewPtr, long pos); - private native long nativeGetTargetRowIndex(long nativeLinkViewPtr, long linkViewIndex); + private native long nativeGetTargetRowIndex(long nativeLinkViewPtr, long pos); public static native void nativeAdd(long nativeLinkViewPtr, long rowIndex); private native void nativeInsert(long nativeLinkViewPtr, long pos, long rowIndex); private native void nativeSet(long nativeLinkViewPtr, long pos, long rowIndex); diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index f13ad8671c..e348a15129 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -46,7 +46,7 @@ public abstract class RealmProxyMediator { * Creates the backing table in Realm for the given RealmObject class. * * @param clazz the {@link RealmObject} model class to create backing table for. - * @param sharedRealm the wrapper object of underlying native database. + * @param transaction the read transaction for the Realm to create table in. */ public abstract Table createTable(Class clazz, SharedRealm sharedRealm); @@ -54,7 +54,7 @@ public abstract class RealmProxyMediator { * Validates the backing table in Realm for the given RealmObject class. * * @param clazz the {@link RealmObject} model class to validate. - * @param sharedRealm the wrapper object of underlying native database to validate against. + * @param sharedRealm the read transaction for the Realm to validate against. * @return the field indices map. */ public abstract ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index b831ef5ea5..5b3c1bab4a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -19,6 +19,7 @@ import java.util.Date; import io.realm.RealmFieldType; +import io.realm.Sort; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; @@ -372,43 +373,33 @@ public void moveLastOver(long rowIndex) { nativeMoveLastOver(nativePtr, rowIndex); } - /** - * Add an empty row to the table which doesn't have a primary key defined. - *

    - * NOTE: To add a table with a primary key defined, use {@link #addEmptyRowWithPrimaryKey(Object)} instead. This - * won't check if this table has a primary key. - * - * @return row index. - */ public long addEmptyRow() { checkImmutable(); + if (hasPrimaryKey()) { + long primaryKeyColumnIndex = getPrimaryKey(); + RealmFieldType type = getColumnType(primaryKeyColumnIndex); + switch (type) { + case STRING: + if (findFirstString(primaryKeyColumnIndex, STRING_DEFAULT_VALUE) != NO_MATCH) { + throwDuplicatePrimaryKeyException(STRING_DEFAULT_VALUE); + } + break; + case INTEGER: + if (findFirstLong(primaryKeyColumnIndex, INTEGER_DEFAULT_VALUE) != NO_MATCH) { + throwDuplicatePrimaryKeyException(INTEGER_DEFAULT_VALUE); + } + break; + default: + throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); + } + } + return nativeAddEmptyRow(nativePtr, 1); } - /** - * Add an empty row to the table and set the primary key with the given value. Equivalent to call - * {@link #addEmptyRowWithPrimaryKey(Object, boolean)} with {@code validation = true}. - * - * @param primaryKeyValue the primary key value - * @return the row index. - */ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { - return addEmptyRowWithPrimaryKey(primaryKeyValue, true); - } - - /** - * Add an empty row to the table and set the primary key with the given value. - * - * @param primaryKeyValue the primary key value. - * @param validation set to {@code false} to skip all validations. This is currently used by bulk insert which - * has its own validations. - * @return the row index. - */ - public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation) { - if (validation) { - checkImmutable(); - checkHasPrimaryKey(); - } + checkImmutable(); + checkHasPrimaryKey(); long primaryKeyColumnIndex = getPrimaryKey(); RealmFieldType type = getColumnType(primaryKeyColumnIndex); @@ -420,12 +411,11 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation switch (type) { case STRING: case INTEGER: - if (validation && findFirstNull(primaryKeyColumnIndex) != NO_MATCH) { + if (findFirstNull(primaryKeyColumnIndex) != NO_MATCH) { throwDuplicatePrimaryKeyException("null"); } rowIndex = nativeAddEmptyRow(nativePtr, 1); row = getUncheckedRow(rowIndex); - // FIXME: Use core's set_null_unique when core supports it. row.setNull(primaryKeyColumnIndex); break; @@ -439,11 +429,12 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation if (!(primaryKeyValue instanceof String)) { throw new IllegalArgumentException("Primary key value is not a String: " + primaryKeyValue); } - if (validation && findFirstString(primaryKeyColumnIndex, (String) primaryKeyValue) != NO_MATCH) { + if (findFirstString(primaryKeyColumnIndex, (String) primaryKeyValue) != NO_MATCH) { throwDuplicatePrimaryKeyException(primaryKeyValue); } rowIndex = nativeAddEmptyRow(nativePtr, 1); - nativeSetStringUnique(nativePtr, primaryKeyColumnIndex, rowIndex, (String) primaryKeyValue); + row = getUncheckedRow(rowIndex); + row.setString(primaryKeyColumnIndex, (String) primaryKeyValue); break; case INTEGER: @@ -453,11 +444,12 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation } catch (RuntimeException e) { throw new IllegalArgumentException("Primary key value is not a long: " + primaryKeyValue); } - if (validation && findFirstLong(primaryKeyColumnIndex, pkValue) != NO_MATCH) { + if (findFirstLong(primaryKeyColumnIndex, pkValue) != NO_MATCH) { throwDuplicatePrimaryKeyException(pkValue); } rowIndex = nativeAddEmptyRow(nativePtr, 1); - nativeSetLongUnique(nativePtr, primaryKeyColumnIndex, rowIndex, pkValue); + row = getUncheckedRow(rowIndex); + row.setLong(primaryKeyColumnIndex, pkValue); break; default: @@ -487,9 +479,6 @@ public long addEmptyRows(long rows) { * * @param values values. * @return the row index of the appended row. - * @deprecated Remove this functions since it doesn't seem to be useful. And this function does deal with tables - * withprimary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row - * added. */ protected long add(Object... values) { long rowIndex = addEmptyRow(); @@ -1335,13 +1324,11 @@ public static String tableNameToClassName(String tableName) { private native long nativeGetLinkTarget(long nativePtr, long columnIndex); native long nativeGetRowPtr(long nativePtr, long index); public static native void nativeSetLong(long nativeTablePtr, long columnIndex, long rowIndex, long value); - public static native void nativeSetLongUnique(long nativeTablePtr, long columnIndex, long rowIndex, long value); public static native void nativeSetBoolean(long nativeTablePtr, long columnIndex, long rowIndex, boolean value); public static native void nativeSetFloat(long nativeTablePtr, long columnIndex, long rowIndex, float value); public static native void nativeSetDouble(long nativeTablePtr, long columnIndex, long rowIndex, double value); public static native void nativeSetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex, long dateTimeValue); public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value); - public static native void nativeSetStringUnique(long nativeTablePtr, long columnIndex, long rowIndex, String value); public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex); public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data); public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 5060ad4e23..cb6bd376c1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -420,15 +420,11 @@ public TableQuery isNotEmpty(long[] columnIndices) { // Searching methods. - @Deprecated // Doesn't seem to be used public long find(long fromTableRow) { validateQuery(); return nativeFind(nativePtr, fromTableRow); } - /** - * Returns the table row index for the first element matching the query. - */ public long find() { validateQuery(); return nativeFind(nativePtr, 0); @@ -721,10 +717,17 @@ public long count() { return nativeCount(nativePtr, 0, Table.INFINITE, Table.INFINITE); } + // Deletion. + public long remove(long start, long end) { + validateQuery(); + if (table.isImmutable()) throwImmutable(); + return nativeRemove(nativePtr, start, end, Table.INFINITE); + } + public long remove() { validateQuery(); if (table.isImmutable()) throwImmutable(); - return nativeRemove(nativePtr); + return nativeRemove(nativePtr, 0, Table.INFINITE, Table.INFINITE); } /** @@ -805,7 +808,7 @@ private void throwImmutable() { private native void nativeIsNull(long nativePtr, long columnIndices[]); private native void nativeIsNotNull(long nativePtr, long columnIndices[]); private native long nativeCount(long nativeQueryPtr, long start, long end, long limit); - private native long nativeRemove(long nativeQueryPtr); + private native long nativeRemove(long nativeQueryPtr, long start, long end, long limit); private native long nativeImportHandoverTableViewIntoSharedGroup(long handoverTableViewPtr, long callerSharedRealmPtr) throws BadVersionException; private native long nativeHandoverQuery(long callerSharedRealmPtr, long nativeQueryPtr); private static native long nativeFindAllSortedWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long start, long end, long limit, long columnIndex, boolean ascending) throws BadVersionException; From 1b53fbfeffb416c57d09042804d095e3da8294df Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 13 Sep 2016 17:40:40 +0900 Subject: [PATCH 0034/2110] Invalidate schema cache when the schema version of Realm is changed by other process (#3409). invalidate schema cache when the schema version of Realm is changed by other process. Now schema cache referred by Realm instance is not shared and global schema cache is introduced instead. --- CHANGELOG.md | 2 + .../processor/RealmProxyClassGenerator.java | 65 ++++++-- .../RealmProxyMediatorGenerator.java | 7 +- .../io/realm/AllTypesRealmProxy.java | 68 ++++++--- .../io/realm/BooleansRealmProxy.java | 48 ++++-- .../io/realm/NullTypesRealmProxy.java | 118 +++++++++------ .../io/realm/RealmDefaultModuleMediator.java | 4 +- .../resources/io/realm/SimpleRealmProxy.java | 40 +++-- .../java/io/realm/ColumnIndicesTests.java | 121 +++++++++++++++ .../java/io/realm/ColumnInfoTests.java | 142 ++++++++++++++++++ .../io/realm/RealmProxyMediatorTests.java | 110 ++++++++++++++ .../androidTest/java/io/realm/RealmTests.java | 48 +++++- .../java/io/realm/entities/Cat.java | 8 + .../io/realm/internal/SharedRealmTests.java | 82 +++++++++- .../src/main/java/io/realm/BaseRealm.java | 21 ++- .../src/main/java/io/realm/Realm.java | 65 ++++++-- .../src/main/java/io/realm/RealmCache.java | 82 +++++++++- .../src/main/java/io/realm/RealmSchema.java | 4 - .../java/io/realm/internal/ColumnIndices.java | 45 +++++- .../java/io/realm/internal/ColumnInfo.java | 40 ++++- .../io/realm/internal/RealmProxyMediator.java | 6 +- .../java/io/realm/internal/SharedRealm.java | 49 +++++- .../internal/modules/CompositeMediator.java | 5 +- .../internal/modules/FilterableMediator.java | 5 +- 24 files changed, 1031 insertions(+), 154 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 642f6c0f61..5d51538356 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,14 @@ * `RealmConfiguration.Builder.assetFile(Context, String)` has been renamed to `RealmConfiguration.Builder.assetFile(String)`. * Object with primary key is now required to define it when the object is created. This means that `Realm.createObject(Class)` and `DynamicRealm.createObject(String)` now throws `RealmException` if they are used to create an object with a primary key field. Use `Realm.createObject(Class, Object)` or `DynamicRealm.createObject(String, Object)` instead. * Importing from JSON without the primary key field defined in the JSON object now throws `IllegalArgumentException`. +* Now `Realm.beginTransaction()`, `Realm.executeTransaction()` and `Realm.waitForChange()` throw `RealmMigrationNeededException` if a remote process introduces an incompatible schema changes (#3409). ### Enhancements * Added `realmObject.isManaged()`, `RealmObject.isManaged(obj)` and `RealmCollection.isManaged()` (#3101). * Added `RealmConfiguration.Builder.directory(File)`. * `RealmLog` has been moved to the public API. It is now possible to control which events Realm emit to Logcat. See the `RealmLog` class for more details. +* Typed `RealmObject`s can now continue to access their fields properly even though the schema was changed while the Realm was open (#3409). ### Bug fixes diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 5eff1c6043..e0101026c7 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -66,7 +66,6 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("android.os.Build"); imports.add("android.util.JsonReader"); imports.add("android.util.JsonToken"); - imports.add("io.realm.RealmFieldType"); imports.add("io.realm.exceptions.RealmMigrationNeededException"); imports.add("io.realm.internal.ColumnInfo"); imports.add("io.realm.internal.RealmObjectProxy"); @@ -75,6 +74,7 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("io.realm.internal.SharedRealm"); imports.add("io.realm.internal.LinkView"); imports.add("io.realm.internal.android.JsonUtils"); + imports.add("io.realm.log.RealmLog"); imports.add("java.io.IOException"); imports.add("java.util.ArrayList"); imports.add("java.util.Collections"); @@ -135,13 +135,14 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { columnInfoClassName(), // full qualified name of the item to generate "class", // the type of the item EnumSet.of(Modifier.STATIC, Modifier.FINAL), // modifiers to apply - "ColumnInfo") // base class + "ColumnInfo", // base class + "Cloneable") // interfaces .emitEmptyLine(); // fields for (VariableElement variableElement : metadata.getFields()) { writer.emitField("long", columnIndexVarName(variableElement), - EnumSet.of(Modifier.PUBLIC, Modifier.FINAL)); + EnumSet.of(Modifier.PUBLIC)); } writer.emitEmptyLine(); @@ -157,13 +158,44 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { writer.emitStatement("this.%s = getValidColumnIndex(path, table, \"%s\", \"%s\")", columnIndexVarName, simpleClassName, columnName); writer.emitStatement("indicesMap.put(\"%s\", this.%s)", columnName, columnIndexVarName); - writer.emitEmptyLine(); } + writer.emitEmptyLine(); writer.emitStatement("setIndicesMap(indicesMap)"); writer.endConstructor(); + writer.emitEmptyLine(); - writer.endType(); + // copyColumnInfoFrom method + writer.emitAnnotation("Override"); + writer.beginMethod( + "void", // return type + "copyColumnInfoFrom", // method name + EnumSet.of(Modifier.PUBLIC, Modifier.FINAL), // modifiers + "ColumnInfo", "other"); // parameters + { + writer.emitStatement("final %1$s otherInfo = (%1$s) other", columnInfoClassName()); + + // copy field values + for (VariableElement variableElement : metadata.getFields()) { + writer.emitStatement("this.%1$s = otherInfo.%1$s", columnIndexVarName(variableElement)); + } + writer.emitEmptyLine(); + writer.emitStatement("setIndicesMap(otherInfo.getIndicesMap())"); + } + writer.endMethod(); writer.emitEmptyLine(); + + // clone method + writer.emitAnnotation("Override"); + writer.beginMethod( + columnInfoClassName(), // return type + "clone", // method name + EnumSet.of(Modifier.PUBLIC, Modifier.FINAL)) // modifiers + // method body + .emitStatement("return (%1$s) super.clone()", columnInfoClassName()) + .endMethod() + .emitEmptyLine(); + + writer.endType(); } private void emitClassFields(JavaWriter writer) throws IOException { @@ -405,18 +437,29 @@ private void emitInitTableMethod(JavaWriter writer) throws IOException { private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.beginMethod( - columnInfoClassName(), // Return type - "validateTable", // Method name + columnInfoClassName(), // Return type + "validateTable", // Method name EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "SharedRealm", "sharedRealm"); // Argument type & argument name + "SharedRealm", "sharedRealm", // Argument type & argument name + "boolean", "allowExtraColumns"); writer.beginControlFlow("if (sharedRealm.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); writer.emitStatement("Table table = sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); // verify number of columns - writer.beginControlFlow("if (table.getColumnCount() != " + metadata.getFields().size() + ")"); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Field count does not match - expected %d but was \" + table.getColumnCount())", - metadata.getFields().size()); + writer.emitStatement("final long columnCount = table.getColumnCount()"); + writer.beginControlFlow("if (columnCount != %d)", metadata.getFields().size()); + writer.beginControlFlow("if (columnCount < %d)", metadata.getFields().size()); + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Field count is less than expected - expected %d but was \" + columnCount)", + metadata.getFields().size()); + writer.endControlFlow(); + writer.beginControlFlow("if (allowExtraColumns)"); + writer.emitStatement("RealmLog.debug(\"Field count is more than expected - expected %d but was %%1$d\", columnCount)", + metadata.getFields().size()); + writer.nextControlFlow("else"); + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Field count is more than expected - expected %d but was \" + columnCount)", + metadata.getFields().size()); + writer.endControlFlow(); writer.endControlFlow(); // create type dictionary for lookup diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index e78a679736..8c759f78e4 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -147,12 +147,15 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { "ColumnInfo", "validateTable", EnumSet.of(Modifier.PUBLIC), - "Class", "clazz", "SharedRealm", "sharedRealm" + "Class", "clazz", // Argument type & argument name + "SharedRealm", "sharedRealm", + "boolean", "allowExtraColumns" ); emitMediatorSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return %s.validateTable(sharedRealm)", qualifiedProxyClasses.get(i)); + writer.emitStatement("return %s.validateTable(sharedRealm, allowExtraColumns)", + qualifiedProxyClasses.get(i)); } }, writer); writer.endMethod(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index c5c43b8825..2f04a79cfd 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -5,7 +5,6 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; -import io.realm.RealmFieldType; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -14,6 +13,7 @@ import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; +import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -29,51 +29,65 @@ public class AllTypesRealmProxy extends some.test.AllTypes implements RealmObjectProxy, AllTypesRealmProxyInterface { - static final class AllTypesColumnInfo extends ColumnInfo { + static final class AllTypesColumnInfo extends ColumnInfo + implements Cloneable { - public final long columnStringIndex; - public final long columnLongIndex; - public final long columnFloatIndex; - public final long columnDoubleIndex; - public final long columnBooleanIndex; - public final long columnDateIndex; - public final long columnBinaryIndex; - public final long columnObjectIndex; - public final long columnRealmListIndex; + public long columnStringIndex; + public long columnLongIndex; + public long columnFloatIndex; + public long columnDoubleIndex; + public long columnBooleanIndex; + public long columnDateIndex; + public long columnBinaryIndex; + public long columnObjectIndex; + public long columnRealmListIndex; AllTypesColumnInfo(String path, Table table) { final Map indicesMap = new HashMap(9); this.columnStringIndex = getValidColumnIndex(path, table, "AllTypes", "columnString"); indicesMap.put("columnString", this.columnStringIndex); - this.columnLongIndex = getValidColumnIndex(path, table, "AllTypes", "columnLong"); indicesMap.put("columnLong", this.columnLongIndex); - this.columnFloatIndex = getValidColumnIndex(path, table, "AllTypes", "columnFloat"); indicesMap.put("columnFloat", this.columnFloatIndex); - this.columnDoubleIndex = getValidColumnIndex(path, table, "AllTypes", "columnDouble"); indicesMap.put("columnDouble", this.columnDoubleIndex); - this.columnBooleanIndex = getValidColumnIndex(path, table, "AllTypes", "columnBoolean"); indicesMap.put("columnBoolean", this.columnBooleanIndex); - this.columnDateIndex = getValidColumnIndex(path, table, "AllTypes", "columnDate"); indicesMap.put("columnDate", this.columnDateIndex); - this.columnBinaryIndex = getValidColumnIndex(path, table, "AllTypes", "columnBinary"); indicesMap.put("columnBinary", this.columnBinaryIndex); - this.columnObjectIndex = getValidColumnIndex(path, table, "AllTypes", "columnObject"); indicesMap.put("columnObject", this.columnObjectIndex); - this.columnRealmListIndex = getValidColumnIndex(path, table, "AllTypes", "columnRealmList"); indicesMap.put("columnRealmList", this.columnRealmListIndex); setIndicesMap(indicesMap); } - } + @Override + public final void copyColumnInfoFrom(ColumnInfo other) { + final AllTypesColumnInfo otherInfo = (AllTypesColumnInfo) other; + this.columnStringIndex = otherInfo.columnStringIndex; + this.columnLongIndex = otherInfo.columnLongIndex; + this.columnFloatIndex = otherInfo.columnFloatIndex; + this.columnDoubleIndex = otherInfo.columnDoubleIndex; + this.columnBooleanIndex = otherInfo.columnBooleanIndex; + this.columnDateIndex = otherInfo.columnDateIndex; + this.columnBinaryIndex = otherInfo.columnBinaryIndex; + this.columnObjectIndex = otherInfo.columnObjectIndex; + this.columnRealmListIndex = otherInfo.columnRealmListIndex; + + setIndicesMap(otherInfo.getIndicesMap()); + } + + @Override + public final AllTypesColumnInfo clone() { + return (AllTypesColumnInfo) super.clone(); + } + + } private final AllTypesColumnInfo columnInfo; private final ProxyState proxyState; private RealmList columnRealmListRealmList; @@ -262,11 +276,19 @@ public static Table initTable(SharedRealm sharedRealm) { return sharedRealm.getTable("class_AllTypes"); } - public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm) { + public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { if (sharedRealm.hasTable("class_AllTypes")) { Table table = sharedRealm.getTable("class_AllTypes"); - if (table.getColumnCount() != 9) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count does not match - expected 9 but was " + table.getColumnCount()); + final long columnCount = table.getColumnCount(); + if (columnCount != 9) { + if (columnCount < 9) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 9 but was " + columnCount); + } + if (allowExtraColumns) { + RealmLog.debug("Field count is more than expected - expected 9 but was %1$d", columnCount); + } else { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 9 but was " + columnCount); + } } Map columnTypes = new HashMap(); for (long i = 0; i < 9; i++) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 6f555eb3f6..3d6f35d867 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -5,7 +5,6 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; -import io.realm.RealmFieldType; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -14,6 +13,7 @@ import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; +import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -29,31 +29,45 @@ public class BooleansRealmProxy extends some.test.Booleans implements RealmObjectProxy, BooleansRealmProxyInterface { - static final class BooleansColumnInfo extends ColumnInfo { + static final class BooleansColumnInfo extends ColumnInfo + implements Cloneable { - public final long doneIndex; - public final long isReadyIndex; - public final long mCompletedIndex; - public final long anotherBooleanIndex; + public long doneIndex; + public long isReadyIndex; + public long mCompletedIndex; + public long anotherBooleanIndex; BooleansColumnInfo(String path, Table table) { final Map indicesMap = new HashMap(4); this.doneIndex = getValidColumnIndex(path, table, "Booleans", "done"); indicesMap.put("done", this.doneIndex); - this.isReadyIndex = getValidColumnIndex(path, table, "Booleans", "isReady"); indicesMap.put("isReady", this.isReadyIndex); - this.mCompletedIndex = getValidColumnIndex(path, table, "Booleans", "mCompleted"); indicesMap.put("mCompleted", this.mCompletedIndex); - this.anotherBooleanIndex = getValidColumnIndex(path, table, "Booleans", "anotherBoolean"); indicesMap.put("anotherBoolean", this.anotherBooleanIndex); setIndicesMap(indicesMap); } - } + @Override + public final void copyColumnInfoFrom(ColumnInfo other) { + final BooleansColumnInfo otherInfo = (BooleansColumnInfo) other; + this.doneIndex = otherInfo.doneIndex; + this.isReadyIndex = otherInfo.isReadyIndex; + this.mCompletedIndex = otherInfo.mCompletedIndex; + this.anotherBooleanIndex = otherInfo.anotherBooleanIndex; + + setIndicesMap(otherInfo.getIndicesMap()); + } + + @Override + public final BooleansColumnInfo clone() { + return (BooleansColumnInfo) super.clone(); + } + + } private final BooleansColumnInfo columnInfo; private final ProxyState proxyState; private static final List FIELD_NAMES; @@ -128,11 +142,19 @@ public static Table initTable(SharedRealm sharedRealm) { return sharedRealm.getTable("class_Booleans"); } - public static BooleansColumnInfo validateTable(SharedRealm sharedRealm) { + public static BooleansColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { if (sharedRealm.hasTable("class_Booleans")) { Table table = sharedRealm.getTable("class_Booleans"); - if (table.getColumnCount() != 4) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count does not match - expected 4 but was " + table.getColumnCount()); + final long columnCount = table.getColumnCount(); + if (columnCount != 4) { + if (columnCount < 4) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 4 but was " + columnCount); + } + if (allowExtraColumns) { + RealmLog.debug("Field count is more than expected - expected 4 but was %1$d", columnCount); + } else { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 4 but was " + columnCount); + } } Map columnTypes = new HashMap(); for (long i = 0; i < 4; i++) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index e4faa4691d..8c0bb734ea 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -5,7 +5,6 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; -import io.realm.RealmFieldType; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -14,6 +13,7 @@ import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; +import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -29,99 +29,113 @@ public class NullTypesRealmProxy extends some.test.NullTypes implements RealmObjectProxy, NullTypesRealmProxyInterface { - static final class NullTypesColumnInfo extends ColumnInfo { - - public final long fieldStringNotNullIndex; - public final long fieldStringNullIndex; - public final long fieldBooleanNotNullIndex; - public final long fieldBooleanNullIndex; - public final long fieldBytesNotNullIndex; - public final long fieldBytesNullIndex; - public final long fieldByteNotNullIndex; - public final long fieldByteNullIndex; - public final long fieldShortNotNullIndex; - public final long fieldShortNullIndex; - public final long fieldIntegerNotNullIndex; - public final long fieldIntegerNullIndex; - public final long fieldLongNotNullIndex; - public final long fieldLongNullIndex; - public final long fieldFloatNotNullIndex; - public final long fieldFloatNullIndex; - public final long fieldDoubleNotNullIndex; - public final long fieldDoubleNullIndex; - public final long fieldDateNotNullIndex; - public final long fieldDateNullIndex; - public final long fieldObjectNullIndex; + static final class NullTypesColumnInfo extends ColumnInfo + implements Cloneable { + + public long fieldStringNotNullIndex; + public long fieldStringNullIndex; + public long fieldBooleanNotNullIndex; + public long fieldBooleanNullIndex; + public long fieldBytesNotNullIndex; + public long fieldBytesNullIndex; + public long fieldByteNotNullIndex; + public long fieldByteNullIndex; + public long fieldShortNotNullIndex; + public long fieldShortNullIndex; + public long fieldIntegerNotNullIndex; + public long fieldIntegerNullIndex; + public long fieldLongNotNullIndex; + public long fieldLongNullIndex; + public long fieldFloatNotNullIndex; + public long fieldFloatNullIndex; + public long fieldDoubleNotNullIndex; + public long fieldDoubleNullIndex; + public long fieldDateNotNullIndex; + public long fieldDateNullIndex; + public long fieldObjectNullIndex; NullTypesColumnInfo(String path, Table table) { final Map indicesMap = new HashMap(21); this.fieldStringNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldStringNotNull"); indicesMap.put("fieldStringNotNull", this.fieldStringNotNullIndex); - this.fieldStringNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldStringNull"); indicesMap.put("fieldStringNull", this.fieldStringNullIndex); - this.fieldBooleanNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldBooleanNotNull"); indicesMap.put("fieldBooleanNotNull", this.fieldBooleanNotNullIndex); - this.fieldBooleanNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldBooleanNull"); indicesMap.put("fieldBooleanNull", this.fieldBooleanNullIndex); - this.fieldBytesNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldBytesNotNull"); indicesMap.put("fieldBytesNotNull", this.fieldBytesNotNullIndex); - this.fieldBytesNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldBytesNull"); indicesMap.put("fieldBytesNull", this.fieldBytesNullIndex); - this.fieldByteNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldByteNotNull"); indicesMap.put("fieldByteNotNull", this.fieldByteNotNullIndex); - this.fieldByteNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldByteNull"); indicesMap.put("fieldByteNull", this.fieldByteNullIndex); - this.fieldShortNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldShortNotNull"); indicesMap.put("fieldShortNotNull", this.fieldShortNotNullIndex); - this.fieldShortNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldShortNull"); indicesMap.put("fieldShortNull", this.fieldShortNullIndex); - this.fieldIntegerNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldIntegerNotNull"); indicesMap.put("fieldIntegerNotNull", this.fieldIntegerNotNullIndex); - this.fieldIntegerNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldIntegerNull"); indicesMap.put("fieldIntegerNull", this.fieldIntegerNullIndex); - this.fieldLongNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldLongNotNull"); indicesMap.put("fieldLongNotNull", this.fieldLongNotNullIndex); - this.fieldLongNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldLongNull"); indicesMap.put("fieldLongNull", this.fieldLongNullIndex); - this.fieldFloatNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldFloatNotNull"); indicesMap.put("fieldFloatNotNull", this.fieldFloatNotNullIndex); - this.fieldFloatNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldFloatNull"); indicesMap.put("fieldFloatNull", this.fieldFloatNullIndex); - this.fieldDoubleNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldDoubleNotNull"); indicesMap.put("fieldDoubleNotNull", this.fieldDoubleNotNullIndex); - this.fieldDoubleNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldDoubleNull"); indicesMap.put("fieldDoubleNull", this.fieldDoubleNullIndex); - this.fieldDateNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldDateNotNull"); indicesMap.put("fieldDateNotNull", this.fieldDateNotNullIndex); - this.fieldDateNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldDateNull"); indicesMap.put("fieldDateNull", this.fieldDateNullIndex); - this.fieldObjectNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldObjectNull"); indicesMap.put("fieldObjectNull", this.fieldObjectNullIndex); setIndicesMap(indicesMap); } - } + @Override + public final void copyColumnInfoFrom(ColumnInfo other) { + final NullTypesColumnInfo otherInfo = (NullTypesColumnInfo) other; + this.fieldStringNotNullIndex = otherInfo.fieldStringNotNullIndex; + this.fieldStringNullIndex = otherInfo.fieldStringNullIndex; + this.fieldBooleanNotNullIndex = otherInfo.fieldBooleanNotNullIndex; + this.fieldBooleanNullIndex = otherInfo.fieldBooleanNullIndex; + this.fieldBytesNotNullIndex = otherInfo.fieldBytesNotNullIndex; + this.fieldBytesNullIndex = otherInfo.fieldBytesNullIndex; + this.fieldByteNotNullIndex = otherInfo.fieldByteNotNullIndex; + this.fieldByteNullIndex = otherInfo.fieldByteNullIndex; + this.fieldShortNotNullIndex = otherInfo.fieldShortNotNullIndex; + this.fieldShortNullIndex = otherInfo.fieldShortNullIndex; + this.fieldIntegerNotNullIndex = otherInfo.fieldIntegerNotNullIndex; + this.fieldIntegerNullIndex = otherInfo.fieldIntegerNullIndex; + this.fieldLongNotNullIndex = otherInfo.fieldLongNotNullIndex; + this.fieldLongNullIndex = otherInfo.fieldLongNullIndex; + this.fieldFloatNotNullIndex = otherInfo.fieldFloatNotNullIndex; + this.fieldFloatNullIndex = otherInfo.fieldFloatNullIndex; + this.fieldDoubleNotNullIndex = otherInfo.fieldDoubleNotNullIndex; + this.fieldDoubleNullIndex = otherInfo.fieldDoubleNullIndex; + this.fieldDateNotNullIndex = otherInfo.fieldDateNotNullIndex; + this.fieldDateNullIndex = otherInfo.fieldDateNullIndex; + this.fieldObjectNullIndex = otherInfo.fieldObjectNullIndex; + + setIndicesMap(otherInfo.getIndicesMap()); + } + + @Override + public final NullTypesColumnInfo clone() { + return (NullTypesColumnInfo) super.clone(); + } + + } private final NullTypesColumnInfo columnInfo; private final ProxyState proxyState; private static final List FIELD_NAMES; @@ -526,11 +540,19 @@ public static Table initTable(SharedRealm sharedRealm) { return sharedRealm.getTable("class_NullTypes"); } - public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm) { + public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { if (sharedRealm.hasTable("class_NullTypes")) { Table table = sharedRealm.getTable("class_NullTypes"); - if (table.getColumnCount() != 21) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count does not match - expected 21 but was " + table.getColumnCount()); + final long columnCount = table.getColumnCount(); + if (columnCount != 21) { + if (columnCount < 21) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 21 but was " + columnCount); + } + if (allowExtraColumns) { + RealmLog.debug("Field count is more than expected - expected 21 but was %1$d", columnCount); + } else { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 21 but was " + columnCount); + } } Map columnTypes = new HashMap(); for (long i = 0; i < 21; i++) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 8b9a712837..5938c43b0f 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -41,11 +41,11 @@ public Table createTable(Class clazz, SharedRealm sharedRe } @Override - public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm) { + public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm, boolean allowExtraColumns) { checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { - return io.realm.AllTypesRealmProxy.validateTable(sharedRealm); + return io.realm.AllTypesRealmProxy.validateTable(sharedRealm, allowExtraColumns); } else { throw getMissingProxyClassException(clazz); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index cf863899ca..1fedc38f07 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -5,7 +5,6 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; -import io.realm.RealmFieldType; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -14,6 +13,7 @@ import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; +import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -29,23 +29,37 @@ public class SimpleRealmProxy extends some.test.Simple implements RealmObjectProxy, SimpleRealmProxyInterface { - static final class SimpleColumnInfo extends ColumnInfo { + static final class SimpleColumnInfo extends ColumnInfo + implements Cloneable { - public final long nameIndex; - public final long ageIndex; + public long nameIndex; + public long ageIndex; SimpleColumnInfo(String path, Table table) { final Map indicesMap = new HashMap(2); this.nameIndex = getValidColumnIndex(path, table, "Simple", "name"); indicesMap.put("name", this.nameIndex); - this.ageIndex = getValidColumnIndex(path, table, "Simple", "age"); indicesMap.put("age", this.ageIndex); setIndicesMap(indicesMap); } - } + @Override + public final void copyColumnInfoFrom(ColumnInfo other) { + final SimpleColumnInfo otherInfo = (SimpleColumnInfo) other; + this.nameIndex = otherInfo.nameIndex; + this.ageIndex = otherInfo.ageIndex; + + setIndicesMap(otherInfo.getIndicesMap()); + } + + @Override + public final SimpleColumnInfo clone() { + return (SimpleColumnInfo) super.clone(); + } + + } private final SimpleColumnInfo columnInfo; private final ProxyState proxyState; private static final List FIELD_NAMES; @@ -98,11 +112,19 @@ public static Table initTable(SharedRealm sharedRealm) { return sharedRealm.getTable("class_Simple"); } - public static SimpleColumnInfo validateTable(SharedRealm sharedRealm) { + public static SimpleColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { if (sharedRealm.hasTable("class_Simple")) { Table table = sharedRealm.getTable("class_Simple"); - if (table.getColumnCount() != 2) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count does not match - expected 2 but was " + table.getColumnCount()); + final long columnCount = table.getColumnCount(); + if (columnCount != 2) { + if (columnCount < 2) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 2 but was " + columnCount); + } + if (allowExtraColumns) { + RealmLog.debug("Field count is more than expected - expected 2 but was %1$d", columnCount); + } else { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 2 but was " + columnCount); + } } Map columnTypes = new HashMap(); for (long i = 0; i < 2; i++) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java new file mode 100644 index 0000000000..0205aa80ce --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.annotation.NonNull; +import android.support.test.runner.AndroidJUnit4; + +import com.google.common.collect.ImmutableMap; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import io.realm.entities.Cat; +import io.realm.entities.Dog; +import io.realm.internal.ColumnIndices; +import io.realm.internal.ColumnInfo; +import io.realm.internal.RealmProxyMediator; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotSame; +import static junit.framework.Assert.assertSame; +import static org.junit.Assert.assertNotEquals; + +@RunWith(AndroidJUnit4.class) +public class ColumnIndicesTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Realm realm; + private RealmProxyMediator mediator; + + @Before + public void setUp() { + RealmConfiguration config = configFactory.createConfiguration(); + realm = Realm.getInstance(config); + mediator = config.getSchemaMediator(); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + @NonNull + private ColumnIndices create(long schemaVersion) { + final CatRealmProxy.CatColumnInfo catColumnInfo; + final DogRealmProxy.DogColumnInfo dogColumnInfo; + catColumnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + dogColumnInfo = (DogRealmProxy.DogColumnInfo) mediator.validateTable(Dog.class, realm.sharedRealm, false); + + return new ColumnIndices(schemaVersion, + ImmutableMap., ColumnInfo>of( + Cat.class, catColumnInfo, + Dog.class, dogColumnInfo)); + } + + @Test + public void copyDeeply() { + final long schemaVersion = 100; + + final ColumnIndices columnIndices = create(schemaVersion); + final ColumnIndices deepCopy = columnIndices.clone(); + + assertEquals(schemaVersion, deepCopy.getSchemaVersion()); + assertEquals(columnIndices.getColumnIndex(Cat.class, Cat.FIELD_NAME), + deepCopy.getColumnIndex(Cat.class, Cat.FIELD_NAME)); + assertEquals(columnIndices.getColumnIndex(Dog.class, Dog.FIELD_AGE), + deepCopy.getColumnIndex(Dog.class, Dog.FIELD_AGE)); + + // check if those are different instance. + assertNotSame(columnIndices, deepCopy); + assertNotSame(columnIndices.getColumnInfo(Cat.class), deepCopy.getColumnInfo(Cat.class)); + assertNotSame(columnIndices.getColumnInfo(Dog.class), deepCopy.getColumnInfo(Dog.class)); + } + + @Test + public void copyFrom() { + final long sourceSchemaVersion = 101; + final long targetSchemaVersion = 100; + + final ColumnIndices source = create(sourceSchemaVersion); + final ColumnIndices target = create(targetSchemaVersion); + + final CatRealmProxy.CatColumnInfo catColumnInfoInSource = (CatRealmProxy.CatColumnInfo) source.getColumnInfo(Cat.class); + final CatRealmProxy.CatColumnInfo catColumnInfoInTarget = (CatRealmProxy.CatColumnInfo) target.getColumnInfo(Cat.class); + + catColumnInfoInSource.nameIndex++; + + // check preconditions + assertNotEquals(catColumnInfoInSource.nameIndex, catColumnInfoInTarget.nameIndex); + assertNotSame(catColumnInfoInSource.getIndicesMap(), catColumnInfoInTarget.getIndicesMap()); + + target.copyFrom(source, mediator); + + assertEquals(sourceSchemaVersion, target.getSchemaVersion()); + assertEquals(catColumnInfoInSource.nameIndex, catColumnInfoInTarget.nameIndex); + assertSame(catColumnInfoInSource.getIndicesMap(), catColumnInfoInTarget.getIndicesMap()); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java new file mode 100644 index 0000000000..ae20b233a3 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java @@ -0,0 +1,142 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import io.realm.entities.Cat; +import io.realm.internal.RealmProxyMediator; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotSame; +import static junit.framework.Assert.assertSame; + +@RunWith(AndroidJUnit4.class) +public class ColumnInfoTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Realm realm; + + @Before + public void setUp() { + RealmConfiguration config = configFactory.createConfiguration(); + realm = Realm.getInstance(config); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + @Test + public void copyColumnInfoFrom_checkIndex() { + final RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); + final CatRealmProxy.CatColumnInfo sourceColumnInfo, targetColumnInfo; + sourceColumnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + targetColumnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + + // check precondition + assertNotSame(sourceColumnInfo, targetColumnInfo); + assertNotSame(sourceColumnInfo.getIndicesMap(), targetColumnInfo.getIndicesMap()); + + sourceColumnInfo.nameIndex = 1; + sourceColumnInfo.ageIndex = 2; + sourceColumnInfo.heightIndex = 3; + sourceColumnInfo.weightIndex = 4; + sourceColumnInfo.hasTailIndex = 5; + sourceColumnInfo.birthdayIndex = 6; + sourceColumnInfo.ownerIndex = 7; + sourceColumnInfo.scaredOfDogIndex = 8; + + targetColumnInfo.nameIndex = 0; + targetColumnInfo.ageIndex = 0; + targetColumnInfo.heightIndex = 0; + targetColumnInfo.weightIndex = 0; + targetColumnInfo.hasTailIndex = 0; + targetColumnInfo.birthdayIndex = 0; + targetColumnInfo.ownerIndex = 0; + targetColumnInfo.scaredOfDogIndex = 0; + + targetColumnInfo.copyColumnInfoFrom(sourceColumnInfo); + + assertEquals(1, targetColumnInfo.nameIndex); + assertEquals(2, targetColumnInfo.ageIndex); + assertEquals(3, targetColumnInfo.heightIndex); + assertEquals(4, targetColumnInfo.weightIndex); + assertEquals(5, targetColumnInfo.hasTailIndex); + assertEquals(6, targetColumnInfo.birthdayIndex); + assertEquals(7, targetColumnInfo.ownerIndex); + assertEquals(8, targetColumnInfo.scaredOfDogIndex); + + // current implementation shares the indices map. + assertSame(sourceColumnInfo.getIndicesMap(), targetColumnInfo.getIndicesMap()); + } + + @Test + public void clone_hasSameValue() { + final RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); + final CatRealmProxy.CatColumnInfo columnInfo; + columnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + + columnInfo.nameIndex = 1; + columnInfo.ageIndex = 2; + columnInfo.heightIndex = 3; + columnInfo.weightIndex = 4; + columnInfo.hasTailIndex = 5; + columnInfo.birthdayIndex = 6; + columnInfo.ownerIndex = 7; + columnInfo.scaredOfDogIndex = 8; + + CatRealmProxy.CatColumnInfo copy = columnInfo.clone(); + + // modify original object + columnInfo.nameIndex = 0; + columnInfo.ageIndex = 0; + columnInfo.heightIndex = 0; + columnInfo.weightIndex = 0; + columnInfo.hasTailIndex = 0; + columnInfo.birthdayIndex = 0; + columnInfo.ownerIndex = 0; + columnInfo.scaredOfDogIndex = 0; + + assertNotSame(columnInfo, copy); + + assertEquals(1, copy.nameIndex); + assertEquals(2, copy.ageIndex); + assertEquals(3, copy.heightIndex); + assertEquals(4, copy.weightIndex); + assertEquals(5, copy.hasTailIndex); + assertEquals(6, copy.birthdayIndex); + assertEquals(7, copy.ownerIndex); + assertEquals(8, copy.scaredOfDogIndex); + + // current implementation shares the indices map between copies. + assertSame(columnInfo.getIndicesMap(), copy.getIndicesMap()); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java new file mode 100644 index 0000000000..9facd69f7f --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +import io.realm.entities.Cat; +import io.realm.internal.RealmProxyMediator; +import io.realm.rule.TestRealmConfigurationFactory; + +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class RealmProxyMediatorTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Realm realm; + + @Before + public void setUp() { + RealmConfiguration config = configFactory.createConfiguration(); + realm = Realm.getInstance(config); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + @Test + public void validateTable_noDuplicateIndexInIndexFields() { + RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); + CatRealmProxy.CatColumnInfo columnInfo; + columnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + + final Set indexSet = new HashSet<>(); + int indexCount = 0; + + indexSet.add(columnInfo.nameIndex); + indexCount++; + indexSet.add(columnInfo.ageIndex); + indexCount++; + indexSet.add(columnInfo.heightIndex); + indexCount++; + indexSet.add(columnInfo.weightIndex); + indexCount++; + indexSet.add(columnInfo.hasTailIndex); + indexCount++; + indexSet.add(columnInfo.birthdayIndex); + indexCount++; + indexSet.add(columnInfo.ownerIndex); + indexCount++; + indexSet.add(columnInfo.scaredOfDogIndex); + indexCount++; + + assertEquals(indexCount, indexSet.size()); + } + + @Test + public void validateTable_noDuplicateIndexInIndicesMap() { + RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); + CatRealmProxy.CatColumnInfo columnInfo; + columnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + + final Set indexSet = new HashSet<>(); + int indexCount = 0; + + // get index for each field and then put into set + for (Field field : Cat.class.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + indexSet.add(columnInfo.getIndicesMap().get(field.getName())); + indexCount++; + } + + assertEquals("if no duplicates, size of set equals to field count.", + indexCount, indexSet.size()); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index d4bcf79876..b8e431ea7e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -22,7 +22,6 @@ import android.os.Looper; import android.os.SystemClock; import android.support.test.InstrumentationRegistry; -import android.support.test.annotation.UiThreadTest; import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; @@ -88,6 +87,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.internal.SharedRealm; +import io.realm.internal.Table; import io.realm.log.RealmLog; import io.realm.objectid.NullPrimaryKey; import io.realm.rule.RunInLooperThread; @@ -99,6 +99,7 @@ import static io.realm.internal.test.ExtraTests.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; @@ -3385,4 +3386,49 @@ public void run(Realm realm) { TestHelper.awaitOrFail(bgRealmFished); assertFalse(bgRealmChangeResult.get()); } + + @Test + public void schemaIndexCacheIsUpdatedAfterSchemaChange() { + final CatRealmProxy.CatColumnInfo catColumnInfo; + catColumnInfo = (CatRealmProxy.CatColumnInfo) realm.schema.columnIndices.getColumnInfo(Cat.class); + + final long nameIndex = catColumnInfo.nameIndex; + final AtomicLong nameIndexNew = new AtomicLong(-1L); + + // change column index of "name" + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final Table catTable = realm.getSchema().getTable(Cat.CLASS_NAME); + final long nameIndex = catTable.getColumnIndex(Cat.FIELD_NAME); + catTable.removeColumn(nameIndex); + final long newIndex = catTable.addColumn(RealmFieldType.STRING, + Cat.FIELD_NAME, true); + + realm.setVersion(realm.getConfiguration().getSchemaVersion() + 1); + + nameIndexNew.set(newIndex); + } + }); + // we need ↓ to update index cache if the schema version was changed in the same thread. + realm.sharedRealm.invokeSchemaChangeListenerIfSchemaChanged(); + + // check if the index was changed + assertNotEquals(nameIndex, nameIndexNew); + + // check if index in the ColumnInfo is updated + assertEquals(nameIndexNew.get(), catColumnInfo.nameIndex); + assertEquals(nameIndexNew.get(), (long) catColumnInfo.getIndicesMap().get(Cat.FIELD_NAME)); + + // check by actual get and set + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final Cat cat = realm.createObject(Cat.class); + cat.setName("pochi"); + } + }); + //noinspection ConstantConditions + assertEquals("pochi", realm.where(Cat.class).findFirst().getName()); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/Cat.java b/realm/realm-library/src/androidTest/java/io/realm/entities/Cat.java index 03e961f055..b9e218f3e2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/Cat.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/Cat.java @@ -23,6 +23,14 @@ public class Cat extends RealmObject { public static final String CLASS_NAME = "Cat"; + public static final String FIELD_NAME = "name"; + public static final String FIELD_AGE = "age"; + public static final String FIELD_HEIGHT = "height"; + public static final String FIELD_WEIGHT = "weight"; + public static final String FIELD_HAS_TAIL = "hasTail"; + public static final String FIELD_BIRTHDAY = "birthday"; + public static final String FIELD_OWNER = "owner"; + public static final String FIELD_SCARED_OF_DOG = "scaredOfDog"; private String name; private long age; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java index 2fd93db7ce..9dab44e638 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java @@ -24,10 +24,14 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + import io.realm.RealmConfiguration; import io.realm.exceptions.RealmError; import io.realm.rule.TestRealmConfigurationFactory; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -38,11 +42,12 @@ public class SharedRealmTests { @Rule public final ExpectedException thrown = ExpectedException.none(); + private RealmConfiguration config; private SharedRealm sharedRealm; @Before public void setUp() { - RealmConfiguration config = configFactory.createConfiguration(); + config = configFactory.createConfiguration(); sharedRealm = SharedRealm.getInstance(config); } @@ -151,4 +156,79 @@ public void renameTable_tableNotExist() { sharedRealm.renameTable("TableToRemove", "newName"); sharedRealm.cancelTransaction(); } + + @Test + public void beginTransaction_SchemaVersionListener() { + final AtomicBoolean listenerCalled = new AtomicBoolean(false); + final AtomicLong schemaVersionFromListener = new AtomicLong(-1L); + + sharedRealm.close(); + sharedRealm = SharedRealm.getInstance(config, null, new SharedRealm.SchemaVersionListener() { + @Override + public void onSchemaVersionChanged(long currentVersion) { + listenerCalled.set(true); + schemaVersionFromListener.set(currentVersion); + } + }); + + final long before = sharedRealm.getSchemaVersion(); + + sharedRealm.beginTransaction(); + try { + // listener is not called if there was no schema change + assertFalse(listenerCalled.get()); + + // change the schema version + sharedRealm.setSchemaVersion(before + 1); + } finally { + sharedRealm.commitTransaction(); + } + + // listener is not yet called + assertFalse(listenerCalled.get()); + + sharedRealm.beginTransaction(); + try { + assertTrue(listenerCalled.get()); + assertEquals(before + 1, schemaVersionFromListener.get()); + } finally { + sharedRealm.cancelTransaction(); + } + } + + @Test + public void refresh_SchemaVersionListener() { + final AtomicBoolean listenerCalled = new AtomicBoolean(false); + final AtomicLong schemaVersionFromListener = new AtomicLong(-1L); + + sharedRealm.close(); + sharedRealm = SharedRealm.getInstance(config, null, new SharedRealm.SchemaVersionListener() { + @Override + public void onSchemaVersionChanged(long currentVersion) { + listenerCalled.set(true); + schemaVersionFromListener.set(currentVersion); + } + }); + + final long before = sharedRealm.getSchemaVersion(); + + sharedRealm.refresh(); + // listener is not called if there was no schema change + assertFalse(listenerCalled.get()); + + sharedRealm.beginTransaction(); + try { + // change the schema version + sharedRealm.setSchemaVersion(before + 1); + } finally { + sharedRealm.commitTransaction(); + } + + // listener is not yet called + assertFalse(listenerCalled.get()); + + sharedRealm.refresh(); + assertTrue(listenerCalled.get()); + assertEquals(before + 1, schemaVersionFromListener.get()); + } } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index cdd0c35dd4..861c482b0a 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -77,7 +77,14 @@ protected BaseRealm(RealmConfiguration configuration) { this.configuration = configuration; this.handlerController = new HandlerController(this); - this.sharedRealm = SharedRealm.getInstance(configuration, new AndroidNotifier(this.handlerController)); + this.sharedRealm = SharedRealm.getInstance(configuration, new AndroidNotifier(this.handlerController), + !(this instanceof Realm) ? null : + new SharedRealm.SchemaVersionListener() { + @Override + public void onSchemaVersionChanged(long currentVersion) { + RealmCache.updateSchemaCache((Realm) BaseRealm.this); + } + }); this.schema = new RealmSchema(this); if (handlerController.isAutoRefreshAvailable()) { @@ -239,6 +246,8 @@ public void writeEncryptedCopyTo(File destination, byte[] key) { * @return {@code true} if the Realm was updated to the latest version, {@code false} if it was * cancelled by calling stopWaitForChange. * @throws IllegalStateException if calling this from within a transaction or from a Looper thread. + * @throws RealmMigrationNeededException on typed {@link Realm} if the latest version contains + * incompatible schema changes. */ public boolean waitForChange() { checkIfValid(); @@ -308,6 +317,9 @@ public void onCall() { *

    * Notice: it is not possible to nest transactions. If you start a transaction within a transaction an exception is * thrown. + * + * @throws RealmMigrationNeededException on typed {@link Realm} if the latest version contains + * incompatible schema changes. */ public void beginTransaction() { checkIfValid(); @@ -464,12 +476,7 @@ public boolean isEmpty() { // package protected so unit tests can access it void setVersion(long version) { - Table metadataTable = sharedRealm.getTable(Table.METADATA_TABLE_NAME); - if (metadataTable.getColumnCount() == 0) { - metadataTable.addColumn(RealmFieldType.INTEGER, "version"); - metadataTable.addEmptyRow(); - } - metadataTable.setLong(0, 0, version); + sharedRealm.setSchemaVersion(version); } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index b25b493486..048c62fe57 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -201,14 +201,15 @@ public static void removeDefaultConfiguration() { * Creates a {@link Realm} instance without checking the existence in the {@link RealmCache}. * * @param configuration {@link RealmConfiguration} used to create the Realm. - * @param columnIndices if this is not {@code null}, the {@link BaseRealm#schema#columnIndices} will be - * initialized to it. Otherwise, {@link BaseRealm#schema#columnIndices} will be populated from - * the Realm file. + * @param globalCacheArray if this is not {@code null} and contains an entry for current schema version, + * the {@link BaseRealm#schema#columnIndices} will be initialized with the copy of + * the entry. Otherwise, {@link BaseRealm#schema#columnIndices} will be populated + * from the Realm file. * @return a {@link Realm} instance. */ - static Realm createInstance(RealmConfiguration configuration, ColumnIndices columnIndices) { + static Realm createInstance(RealmConfiguration configuration, ColumnIndices[] globalCacheArray) { try { - return createAndValidate(configuration, columnIndices); + return createAndValidate(configuration, globalCacheArray); } catch (RealmMigrationNeededException e) { if (configuration.shouldDeleteRealmIfMigrationNeeded()) { @@ -222,14 +223,15 @@ static Realm createInstance(RealmConfiguration configuration, ColumnIndices colu } } - return createAndValidate(configuration, columnIndices); + return createAndValidate(configuration, globalCacheArray); } } - static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices columnIndices) { + static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices[] globalCacheArray) { Realm realm = new Realm(configuration); long currentVersion = realm.getVersion(); long requiredVersion = configuration.getSchemaVersion(); + final ColumnIndices columnIndices = RealmCache.findColumnIndices(globalCacheArray, requiredVersion); if (currentVersion != UNVERSIONED && currentVersion < requiredVersion && columnIndices == null) { realm.doClose(); throw new RealmMigrationNeededException(configuration.getPath(), String.format("Realm on disk need to migrate from v%s to v%s", currentVersion, requiredVersion)); @@ -248,7 +250,8 @@ static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices c throw e; } } else { - realm.schema.columnIndices = columnIndices; + // copy global cache as a Realm local indices cache + realm.schema.columnIndices = columnIndices.clone(); } return realm; @@ -274,9 +277,11 @@ private static void initializeRealm(Realm realm) { if (version == UNVERSIONED) { mediator.createTable(modelClass, realm.sharedRealm); } - columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm)); + columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } - realm.schema.columnIndices = new ColumnIndices(columnInfoMap); + realm.schema.columnIndices = new ColumnIndices( + (version == UNVERSIONED) ? realm.configuration.getSchemaVersion() : version, + columnInfoMap); if (version == UNVERSIONED) { final Transaction transaction = realm.getConfiguration().getInitialDataTransaction(); @@ -1092,6 +1097,7 @@ public void addChangeListener(RealmChangeListener listener) { * * @param transaction the {@link io.realm.Realm.Transaction} to execute. * @throws IllegalArgumentException if the {@code transaction} is {@code null}. + * @throws RealmMigrationNeededException if the latest version contains incompatible schema changes. */ public void executeTransaction(Transaction transaction) { if (transaction == null) { @@ -1411,6 +1417,45 @@ Table getTable(Class clazz) { return schema.getTable(clazz); } + /** + * Updates own schema cache. + * + * @param globalCacheArray global cache of column indices. If it contains an entry for current + * schema version, this method only copies the indices information in the entry. + * @return newly created indices information for current schema version. Or {@code null} if + * {@code globalCacheArray} already contains the entry for current schema version. + */ + ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) { + final long currentSchemaVersion = sharedRealm.getSchemaVersion(); + final long cacheSchemaVersion = schema.columnIndices.getSchemaVersion(); + if (currentSchemaVersion == cacheSchemaVersion) { + return null; + } + + ColumnIndices createdGlobalCache = null; + final RealmProxyMediator mediator = getConfiguration().getSchemaMediator(); + ColumnIndices cacheForCurrentVersion = RealmCache.findColumnIndices(globalCacheArray, + currentSchemaVersion); + if (cacheForCurrentVersion == null) { + // not found in global cache. create it. + final Set> modelClasses = mediator.getModelClasses(); + final Map, ColumnInfo> map; + map = new HashMap, ColumnInfo>(modelClasses.size()); + try { + for (Class clazz : modelClasses) { + final ColumnInfo columnInfo = mediator.validateTable(clazz, sharedRealm, true); + map.put(clazz, columnInfo); + } + } catch (RealmMigrationNeededException e) { + throw e; + } + + cacheForCurrentVersion = createdGlobalCache = new ColumnIndices(currentSchemaVersion, map); + } + schema.columnIndices.copyFrom(cacheForCurrentVersion, mediator); + return createdGlobalCache; + } + /** * Returns the default Realm module. This module contains all Realm classes in the current project, but not those * from library or project dependencies. Realm classes in these should be exposed using their own module. diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 0a24a113bd..dd7cd65632 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -74,7 +74,8 @@ static RealmCacheType valueOf(Class clazz) { // Column indices are cached to speed up opening typed Realm. If a Realm instance is created in one thread, creating // Realm instances in other threads doesn't have to initialize the column indices again. - private ColumnIndices typedColumnIndices; + private static final int MAX_ENTRIES_IN_TYPED_COLUMN_INDICES_ARRAY = 4; + private final ColumnIndices[] typedColumnIndicesArray = new ColumnIndices[MAX_ENTRIES_IN_TYPED_COLUMN_INDICES_ARRAY]; // Realm path will be used as the key to store different RealmCaches. Different Realm configurations with same path // are not allowed and an exception will be thrown when trying to add it to the cache map. @@ -123,7 +124,7 @@ static synchronized E createRealmOrGetFromCache(RealmConfi if (realmClass == Realm.class) { // RealmMigrationNeededException might be thrown here. - realm = Realm.createInstance(configuration, cache.typedColumnIndices); + realm = Realm.createInstance(configuration, cache.typedColumnIndicesArray); } else if (realmClass == DynamicRealm.class) { realm = DynamicRealm.createInstance(configuration); } else { @@ -143,7 +144,9 @@ static synchronized E createRealmOrGetFromCache(RealmConfi Integer refCount = refAndCount.localCount.get(); if (refCount == 0) { if (realmClass == Realm.class && refAndCount.globalCount == 0) { - cache.typedColumnIndices = refAndCount.localRealm.get().schema.columnIndices; + final BaseRealm realm = refAndCount.localRealm.get(); + // store a copy of local ColumnIndices as a global cache. + RealmCache.storeColumnIndices(cache.typedColumnIndicesArray, realm.schema.columnIndices.clone()); } // This is the first instance in current thread, increase the global count. refAndCount.globalCount++; @@ -200,7 +203,7 @@ static synchronized void release(BaseRealm realm) { // Clear the column indices cache if needed if (realm instanceof Realm && refAndCount.globalCount == 0) { // All typed Realm instances of this file are cleared from cache - cache.typedColumnIndices = null; + Arrays.fill(cache.typedColumnIndicesArray, null); } int totalRefCount = 0; @@ -274,6 +277,30 @@ static synchronized void invokeWithGlobalRefCount(RealmConfiguration configurati callback.onResult(totalRefCount); } + /** + * Updates the schema cache in the typed Realm for {@code pathOfRealm}. + * + * @param realm the instance that contains the schema cache to be updated. + */ + static synchronized void updateSchemaCache(Realm realm) { + final RealmCache cache = cachesMap.get(realm.getPath()); + if (cache == null) { + // Called during initialization. just skip it. + return; + } + final RefAndCount refAndCount = cache.refAndCountMap.get(RealmCacheType.TYPED_REALM); + if (refAndCount.localRealm.get() == null) { + // Called during initialization. just skip it. + // We can reach here if the DynamicRealm instance is initialized first. + return; + } + final ColumnIndices[] globalCacheArray = cache.typedColumnIndicesArray; + final ColumnIndices createdCacheEntry = realm.updateSchemaCache(globalCacheArray); + if (createdCacheEntry != null) { + RealmCache.storeColumnIndices(globalCacheArray, createdCacheEntry); + } + } + /** * Runs the callback function with synchronization on {@link RealmCache}. * @@ -342,4 +369,51 @@ private static void copyAssetFileIfNeeded(RealmConfiguration configuration) { } } } + + /** + * Finds an entry for specified schema version in the array. + * + * @param array target array of schema cache. + * @param schemaVersion requested version of the schema. + * @return {@link ColumnIndices} instance for specified schema version. {@code null} if not found. + */ + public static ColumnIndices findColumnIndices(ColumnIndices[] array, long schemaVersion) { + for (int i = array.length - 1; 0 <= i; i--) { + final ColumnIndices candidate = array[i]; + if (candidate != null && candidate.getSchemaVersion() == schemaVersion) { + return candidate; + } + } + return null; + } + + /** + * Stores the schema cache to the array. + *

    + * If the {@code array} has an empty slot ({@code == null}), this method stores + * the {@code columnIndices} to it. Otherwise, the entry of the oldest schema version is + * replaced. + * + * @param array target array. + * @param columnIndices the item to be stored into the {@code array}. + * @return the index in the {@code array} where the {@code columnIndices} was stored. + */ + private static int storeColumnIndices(ColumnIndices[] array, ColumnIndices columnIndices) { + long oldestSchemaVersion = Long.MAX_VALUE; + int candidateIndex = -1; + for (int i = array.length - 1; 0 <= i; i--) { + if (array[i] == null) { + array[i] = columnIndices; + return i; + } + + ColumnIndices target = array[i]; + if (target.getSchemaVersion() <= oldestSchemaVersion) { + oldestSchemaVersion = target.getSchemaVersion(); + candidateIndex = i; + } + } + array[candidateIndex] = columnIndices; + return candidateIndex; + } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 65ce99b506..5033abc5b4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -276,10 +276,6 @@ RealmObjectSchema getSchemaForClass(String className) { return dynamicSchema; } - void setColumnIndices(ColumnIndices columnIndices) { - this.columnIndices = columnIndices; - } - static String getSchemaForTable(Table table) { return table.getName().substring(Table.TABLE_PREFIX.length()); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java index 97e2934c3c..0e89e649ed 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java @@ -16,6 +16,7 @@ package io.realm.internal; +import java.util.HashMap; import java.util.Map; import io.realm.RealmModel; @@ -23,14 +24,19 @@ /** * Utility class used to cache the mapping between object field names and their column indices. */ -public class ColumnIndices { +public final class ColumnIndices implements Cloneable { + private long schemaVersion; + private Map, ColumnInfo> classes; - private final Map, ColumnInfo> classes; - - public ColumnIndices(Map, ColumnInfo> classes) { + public ColumnIndices(long schemaVersion, Map, ColumnInfo> classes) { + this.schemaVersion = schemaVersion; this.classes = classes; } + public long getSchemaVersion() { + return schemaVersion; + } + /** * Returns {@link ColumnInfo} for the given class or {@code null} if no mapping exists. */ @@ -50,4 +56,35 @@ public long getColumnIndex(Class clazz, String fieldName) return -1; } } + + @Override + public ColumnIndices clone() { + try { + final ColumnIndices clone = (ColumnIndices) super.clone(); + clone.classes = duplicateColumnInfoMap(); + return clone; + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + private Map, ColumnInfo> duplicateColumnInfoMap() { + final Map, ColumnInfo> copy = new HashMap<>(); + for (Map.Entry, ColumnInfo> entry : classes.entrySet()) { + copy.put(entry.getKey(), entry.getValue().clone()); + } + return copy; + } + + public void copyFrom(ColumnIndices other, RealmProxyMediator mediator) { + for (Map.Entry, ColumnInfo> entry : classes.entrySet()) { + final ColumnInfo otherColumnInfo = other.getColumnInfo(entry.getKey()); + if (otherColumnInfo == null) { + throw new IllegalStateException("Failed to copy ColumnIndices cache: " + + Table.tableNameToClassName(mediator.getTableName(entry.getKey()))); + } + entry.getValue().copyColumnInfoFrom(otherColumnInfo); + } + this.schemaVersion = other.schemaVersion; + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java index 1bd57372f8..3846930296 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java @@ -16,12 +16,11 @@ package io.realm.internal; -import java.util.Collections; import java.util.Map; import io.realm.exceptions.RealmMigrationNeededException; -public class ColumnInfo { +public abstract class ColumnInfo implements Cloneable { private Map indicesMap; protected final long getValidColumnIndex(String realmPath, Table table, @@ -34,11 +33,40 @@ protected final long getValidColumnIndex(String realmPath, Table table, return columnIndex; } - protected final void setIndicesMap(Map indicesMap) { - this.indicesMap = Collections.unmodifiableMap(indicesMap); - } - + /** + * Returns a map from column name to column index. + * + * @return a map from column name to column index. Do not modify returned map because it may be + * shared among other {@link ColumnInfo} instances. + */ public Map getIndicesMap() { return indicesMap; } + + protected final void setIndicesMap(Map indicesMap) { + this.indicesMap = indicesMap; + } + + /** + * Copies the column index value from other {@link ColumnInfo} object. + * + * @param other The class of {@code other} must be exactly the same as this instance. + * It must not be {@code null}. + * @throws IllegalArgumentException if {@code other} has different class than this. + */ + public abstract void copyColumnInfoFrom(ColumnInfo other); + + /** + * Returns a shallow copy of this instance. + * + * @return shallow copy. + */ + @Override + public ColumnInfo clone() { + try { + return (ColumnInfo) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + }; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index f13ad8671c..84231559a3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -55,9 +55,13 @@ public abstract class RealmProxyMediator { * * @param clazz the {@link RealmObject} model class to validate. * @param sharedRealm the wrapper object of underlying native database to validate against. + * @param allowExtraColumns if {@code} false, {@link io.realm.exceptions.RealmMigrationNeededException} + * is thrown when the column count it more than expected. * @return the field indices map. */ - public abstract ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm); + public abstract ColumnInfo validateTable(Class clazz, + SharedRealm sharedRealm, + boolean allowExtraColumns); /** * Returns a map of non-obfuscated object field names to their internal Realm name. diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 8e225ecff1..728bcc53e4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -20,6 +20,7 @@ import java.io.File; import io.realm.RealmConfiguration; +import io.realm.RealmFieldType; import io.realm.internal.async.BadVersionException; public final class SharedRealm implements Closeable { @@ -124,22 +125,32 @@ public int hashCode() { } } + public interface SchemaVersionListener { + void onSchemaVersionChanged(long currentVersion); + } + private long nativePtr; private RealmConfiguration configuration; final Context context; + private long lastSchemaVersion; + private final SchemaVersionListener schemaChangeListener; - private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotifier notifier) { + private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotifier notifier, + SchemaVersionListener schemaVersionListener) { this.nativePtr = nativePtr; this.configuration = configuration; this.realmNotifier = notifier; + this.schemaChangeListener = schemaVersionListener; context = new Context(); + this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion(); } public static SharedRealm getInstance(RealmConfiguration config) { - return getInstance(config, null); + return getInstance(config, null, null); } - public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier realmNotifier) { + public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier realmNotifier, + SchemaVersionListener schemaVersionListener) { long nativeConfigPtr = nativeCreateConfig( config.getPath(), config.getEncryptionKey(), @@ -149,7 +160,11 @@ public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier r false, true); try { - return new SharedRealm(nativeGetSharedRealm(nativeConfigPtr, realmNotifier), config, realmNotifier); + return new SharedRealm( + nativeGetSharedRealm(nativeConfigPtr, realmNotifier), + config, + realmNotifier, + schemaVersionListener); } finally { nativeCloseConfig(nativeConfigPtr); } @@ -161,6 +176,7 @@ long getNativePtr() { public void beginTransaction() { nativeBeginTransaction(nativePtr); + invokeSchemaChangeListenerIfSchemaChanged(); } public void commitTransaction() { @@ -175,6 +191,16 @@ public boolean isInTransaction() { return nativeIsInTransaction(nativePtr); } + public void setSchemaVersion(long schemaVersion) { + // FIXME migrate to ObjectStore + Table metadataTable = getTable(Table.METADATA_TABLE_NAME); + if (metadataTable.getColumnCount() == 0) { + metadataTable.addColumn(RealmFieldType.INTEGER, "version"); + metadataTable.addEmptyRow(); + } + metadataTable.setLong(0, 0, schemaVersion); + } + public long getSchemaVersion() { return nativeGetVersion(nativePtr); } @@ -218,6 +244,7 @@ public boolean isEmpty() { public void refresh() { nativeRefresh(nativePtr); + invokeSchemaChangeListenerIfSchemaChanged(); } public void refresh(SharedRealm.VersionID version) throws BadVersionException { @@ -226,6 +253,7 @@ public void refresh(SharedRealm.VersionID version) throws BadVersionException { // or transact log observer involved. Before we use notification & fine grained notification from OS, it is not // a problem. nativeRefresh(nativePtr, version.version, version.index); + invokeSchemaChangeListenerIfSchemaChanged(); } public SharedRealm.VersionID getVersionID() { @@ -283,6 +311,19 @@ protected void finalize() throws Throwable { super.finalize(); } + public void invokeSchemaChangeListenerIfSchemaChanged() { + if (schemaChangeListener == null) { + return; + } + + final long before = lastSchemaVersion; + final long current = getSchemaVersion(); + if (current != before) { + lastSchemaVersion = current; + schemaChangeListener.onSchemaVersionChanged(current); + } + } + private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory, boolean cache, boolean disableFormatUpgrade, boolean autoChangeNotification); diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index bbddbd6cea..71f258cb21 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -64,9 +64,10 @@ public Table createTable(Class clazz, SharedRealm sharedRe } @Override - public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm) { + public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm, + boolean allowExtraColumns) { RealmProxyMediator mediator = getMediator(clazz); - return mediator.validateTable(clazz, sharedRealm); + return mediator.validateTable(clazz, sharedRealm, allowExtraColumns); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index 17db19b6d9..29d24d2f6c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -79,9 +79,10 @@ public Table createTable(Class clazz, SharedRealm sharedRe } @Override - public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm) { + public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm, + boolean allowExtraColumns) { checkSchemaHasClass(clazz); - return originalMediator.validateTable(clazz, sharedRealm); + return originalMediator.validateTable(clazz, sharedRealm, allowExtraColumns); } @Override From f1e16959c550eefd93c279f1d538ccdd649783b8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Sep 2016 11:37:15 +0200 Subject: [PATCH 0035/2110] Upgrade to beta-33 / 2.0.0-rc4 (#90) --- CHANGELOG.md | 8 + .../realm/transformer/BytecodeModifier.groovy | 32 +++- .../transformer/BytecodeModifierTest.groovy | 92 +++++++--- .../java/io/realm/processor/Constants.java | 2 + .../realm/processor/RealmJsonTypeHelper.java | 24 ++- .../processor/RealmProxyClassGenerator.java | 28 ++- .../io/realm/AllTypesRealmProxy.java | 31 ++-- .../io/realm/BooleansRealmProxy.java | 3 +- .../io/realm/NullTypesRealmProxy.java | 3 +- .../resources/io/realm/SimpleRealmProxy.java | 3 +- realm/realm-library/build.gradle | 14 +- .../java/io/realm/DynamicRealmTests.java | 7 + .../java/io/realm/NotificationsTest.java | 3 + .../realm/RealmJsonAbsentPrimaryKeyTests.java | 161 ++++++++++++++++++ .../realm/RealmJsonNullPrimaryKeyTests.java | 22 +-- .../java/io/realm/RealmJsonTests.java | 9 +- .../java/io/realm/RealmModelTests.java | 3 +- .../java/io/realm/RealmQueryTests.java | 3 + .../androidTest/java/io/realm/RealmTests.java | 6 + .../androidTest/java/io/realm/SortTest.java | 32 ++-- .../java/io/realm/internal/JNITableTest.java | 1 - .../objectserver/SyncConfigurationTests.java | 37 +--- .../realm-library/src/main/cpp/CMakeLists.txt | 6 +- .../main/cpp/io_realm_internal_LinkView.cpp | 6 +- .../src/main/cpp/io_realm_internal_Table.cpp | 40 ++++- .../main/cpp/io_realm_internal_TableQuery.cpp | 9 +- .../main/cpp/io_realm_internal_TableView.cpp | 4 +- .../cpp/io_realm_objectserver_Session.cpp | 3 +- .../cpp/io_realm_objectserver_SyncManager.cpp | 21 ++- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/cpp/objectserver_shared.hpp | 4 +- .../src/main/cpp/tablebase_tpl.hpp | 22 --- realm/realm-library/src/main/cpp/util.hpp | 12 +- .../src/main/java/io/realm/DynamicRealm.java | 5 + .../src/main/java/io/realm/Realm.java | 77 +++++---- .../java/io/realm/RealmConfiguration.java | 1 + .../src/main/java/io/realm/RealmQuery.java | 48 +++--- .../src/main/java/io/realm/RealmResults.java | 17 +- .../main/java/io/realm/internal/LinkView.java | 9 +- .../io/realm/internal/RealmProxyMediator.java | 4 +- .../main/java/io/realm/internal/Table.java | 71 ++++---- .../java/io/realm/internal/TableQuery.java | 15 +- .../io/realm/objectserver/BoundState.java | 6 +- .../java/io/realm/objectserver/ErrorCode.java | 40 ++--- .../realm/objectserver/ObjectServerError.java | 19 +++ .../java/io/realm/objectserver/Session.java | 14 +- .../realm/objectserver/SyncConfiguration.java | 2 +- .../main/java/io/realm/objectserver/User.java | 13 +- .../io/realm/objectserver/internal/Token.java | 38 +++-- .../network/AuthenticateResponse.java | 37 +--- version.txt | 2 +- 51 files changed, 676 insertions(+), 395 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 9da928f1df..c3641c6010 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 2.0.0 +### Known issues + +* When creating a `RealmObject` from a JSON stream, it will take the default values defined by its default constructor for those fields that are not defined in the JSON object. This behaviour is different from other APIs when creating `RealmObject`s. + ### Breaking Changes * `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). @@ -9,6 +13,8 @@ - `RealmIOExcpetion` has been removed and replaced by `RealmFileException`. * Removed `RealmConfiguration.Builder(Context, File)` and `RealmConfiguration.Builder(File)` constructors. * `RealmConfiguration.Builder.assetFile(Context, String)` has been renamed to `RealmConfiguration.Builder.assetFile(String)`. +* Object with primary key is now required to define it when the object is created. This means that `Realm.createObject(Class)` and `DynamicRealm.createObject(String)` now throws `RealmException` if they are used to create an object with a primary key field. Use `Realm.createObject(Class, Object)` or `DynamicRealm.createObject(String, Object)` instead. +* Importing from JSON without the primary key field defined in the JSON object now throws `IllegalArgumentException`. ### Enhancements @@ -20,10 +26,12 @@ * Fixed a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11 (#3356). * Fixed a potential crash when there were lots of async queries waiting in the queue. +* Fixed a bug causing the Realm Transformer to not transform field access in the model's constructors (#3361). ### Internal * Moved JNI build to CMake. +* Updated Realm Core to 2.0.0-rc4. ## 1.2.0 diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index b927ef5b38..b9e388c828 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -66,11 +66,10 @@ class BytecodeModifier { !behavior.name.startsWith('realmGet$') && !behavior.name.startsWith('realmSet$') ) || ( - behavior instanceof CtConstructor && - !modelClasses.contains(clazz) + behavior instanceof CtConstructor ) ) { - behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior)) + behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior, modelClasses.contains(clazz))) } } } @@ -94,11 +93,18 @@ class BytecodeModifier { final List managedFields final CtClass ctClass final CtBehavior behavior + final boolean isModelClass + final boolean isInConstructor - FieldAccessToAccessorConverter(List managedFields, CtClass ctClass, CtBehavior behavior) { + FieldAccessToAccessorConverter(List managedFields, + CtClass ctClass, + CtBehavior behavior, + boolean isModelClass) { this.managedFields = managedFields this.ctClass = ctClass this.behavior = behavior + this.isModelClass = isModelClass + this.isInConstructor = behavior instanceof CtConstructor } @Override @@ -112,9 +118,23 @@ class BytecodeModifier { logger.info " Methods: ${ctClass.declaredMethods}" def fieldName = fieldAccess.fieldName if (fieldAccess.isReader()) { - fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') + if (isInConstructor && isModelClass) { + // work around https://github.com/realm/realm-java/issues/2536 + // '$0' is the object that owns target field. + // 'this' is the instance where the constructor belongs. + fieldAccess.replace('$_ = ($0 == this) ? $0.' + fieldName + ' : $0.realmGet$' + fieldName + '();') + } else { + fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') + } } else if (fieldAccess.isWriter()) { - fieldAccess.replace('$0.realmSet$' + fieldName + '($1);') + if (isInConstructor && isModelClass) { + // work around https://github.com/realm/realm-java/issues/2536 + // '$0' is the object that owns target field. + // 'this' is the instance where the constructor belongs. + fieldAccess.replace('if ($0 == this) {$0.' + fieldName + ' = $1;} else { $0.realmSet$' + fieldName + '($1);}') + } else { + fieldAccess.replace('$0.realmSet$' + fieldName + '($1);') + } } } } diff --git a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy index 65609cd5df..6750f2b273 100644 --- a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy +++ b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy @@ -79,7 +79,7 @@ class BytecodeModifierTest extends Specification { def "UseRealmAccessors"() { setup: 'generate an empty class' def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('testClass') + def ctClass = classPool.makeClass('TestClass') and: 'add a field' def ctField = new CtField(CtClass.intType, 'age', ctClass) @@ -95,21 +95,11 @@ class BytecodeModifierTest extends Specification { when: 'the field use is replaced by the accessor' BytecodeModifier.useRealmAccessors(ctClass, [ctField], []) - then: 'the field is not used in the method anymore' - def methodInfo = ctMethod.getMethodInfo() - def codeAttribute = methodInfo.getCodeAttribute() - def fieldIsUsed = false - for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { - int index = ci.next(); - int op = ci.byteAt(index); - if (op == Opcode.GETFIELD) { - fieldIsUsed = true - } - } - !fieldIsUsed + then: 'the field is not used and getter is called in the method ' + !isFieldRead(ctMethod) && hasMethodCall(ctMethod) } - def "UseRealmAccessorsInNonDefaultConstructor"() { + def "UseRealmAccessors_fieldAccessInModelConstructorIsTransformed"() { setup: 'generate an empty class' def classPool = ClassPool.getDefault() def ctClass = classPool.makeClass('TestClass') @@ -122,27 +112,83 @@ class BytecodeModifierTest extends Specification { def ctMethod = CtNewMethod.make('private void setupAge(int age) { this.age = age; }', ctClass) ctClass.addMethod(ctMethod) - and: 'add a constructor that uses the method' - def ctConstructor = CtNewConstructor.make('public TestClass(int age) { setupAge(age); }', ctClass) - ctClass.addConstructor(ctConstructor) + and: 'add a default constructor that uses the method' + def ctDefaultConstructor = CtNewConstructor.make('public TestClass() { int myAge = this.age; }', ctClass) + ctClass.addConstructor(ctDefaultConstructor) + + and: 'add a non-default constructor that uses the method' + def ctNonDefaultConstructor = CtNewConstructor.make('public TestClass(TestClass other) { int otherAge = other.age; }', ctClass) + ctClass.addConstructor(ctNonDefaultConstructor) and: 'realm accessors are added' BytecodeModifier.addRealmAccessors(ctClass) when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(ctClass, [ctField], []) + BytecodeModifier.useRealmAccessors(ctClass, [ctField], [ctClass]) + + then: 'the field is still used and also getter is called in the constructor' + // to work around https://github.com/realm/realm-java/issues/2536 , field access is not removed + isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && + isFieldRead(ctNonDefaultConstructor) && hasMethodCall(ctNonDefaultConstructor) + } + + def "UseRealmAccessors_fieldAccessInNonModelConstructorIsTransformed"() { + setup: 'generate an empty class' + def classPool = ClassPool.getDefault() + def ctClass = classPool.makeClass('TestClass') + + and: 'add a field' + def ctField = new CtField(CtClass.intType, 'age', ctClass) + ctClass.addField(ctField) + + and: 'add a method that sets such field' + def ctMethod = CtNewMethod.make('private void setupAge(int age) { this.age = age; }', ctClass) + ctClass.addMethod(ctMethod) + + and: 'add a default constructor that uses the method' + def ctDefaultConstructor = CtNewConstructor.make('public TestClass() { int myAge = this.age; }', ctClass) + ctClass.addConstructor(ctDefaultConstructor) + + and: 'add a non-default constructor that uses the method' + def ctNonDefaultConstructor = CtNewConstructor.make('public TestClass(TestClass other) { int otherAge = other.age; }', ctClass) + ctClass.addConstructor(ctNonDefaultConstructor) + + and: 'realm accessors are added' + BytecodeModifier.addRealmAccessors(ctClass) + + when: 'the field use is replaced by the accessor' + BytecodeModifier.useRealmAccessors(ctClass, [ctField], [/* no ctClass in model class list*/]) then: 'the field is not used in the method anymore' - def methodInfo = ctMethod.getMethodInfo() + !isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && + !isFieldRead(ctNonDefaultConstructor) && hasMethodCall(ctNonDefaultConstructor) + } + + private static def isFieldRead(CtBehavior behavior) { + def methodInfo = behavior.getMethodInfo() def codeAttribute = methodInfo.getCodeAttribute() - def fieldIsUsed = false + + for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { + int index = ci.next(); + int op = ci.byteAt(index); + if (op == Opcode.GETFIELD) { + return true + } + } + return false + } + + private static def hasMethodCall(CtBehavior behavior) { + def methodInfo = behavior.getMethodInfo() + def codeAttribute = methodInfo.getCodeAttribute() + for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { int index = ci.next(); int op = ci.byteAt(index); - if (op == Opcode.PUTFIELD) { - fieldIsUsed = true + if (op == Opcode.INVOKEVIRTUAL) { + return true } } - !fieldIsUsed + return false } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java index 68b2f11ca9..592aec97b3 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java @@ -28,6 +28,8 @@ public class Constants { public static final String DEFAULT_MODULE_CLASS_NAME = "DefaultRealmModule"; static final String STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE = "throw new IllegalArgumentException(\"Trying to set non-nullable field '%s' to null.\")"; + static final String STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON = + "throw new IllegalArgumentException(\"JSON object doesn't have the primary key field '%s'.\")"; static final Map JAVA_TO_REALM_TYPES; static { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java index cd63ab09a3..c54e00e149 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java @@ -68,7 +68,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String - fieldType, JavaWriter writer) + fieldType, JavaWriter writer, boolean isPrimaryKey) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") @@ -110,7 +110,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String - fieldType, JavaWriter writer) + fieldType, JavaWriter writer, boolean isPrimaryKey) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") @@ -183,11 +183,16 @@ public static void emitFillRealmListWithJsonValue(String interfaceName, String g } - public static void emitFillJavaTypeFromStream(String interfaceName, String setter, String fieldName, String + public static void emitFillJavaTypeFromStream(String interfaceName, ClassMetaData metaData, String fieldName, String fieldType, JavaWriter writer) throws IOException { + String setter = metaData.getSetter(fieldName); + boolean isPrimaryKey = false; + if (metaData.hasPrimaryKey() && metaData.getPrimaryKey().getSimpleName().toString().equals(fieldName)) { + isPrimaryKey = true; + } if (JAVA_TO_JSON_TYPES.containsKey(fieldType)) { JAVA_TO_JSON_TYPES.get(fieldType).emitStreamTypeConversion(interfaceName, setter, fieldName, fieldType, - writer); + writer, isPrimaryKey); } } @@ -211,6 +216,7 @@ public static void emitFillRealmListFromStream(String interfaceName, String gett .emitStatement("reader.skipValue()") .emitStatement("((%s) obj).%s(null)", interfaceName, setter) .nextControlFlow("else") + .emitStatement("((%s) obj).%s(new RealmList<%s>())", interfaceName, setter, fieldTypeCanonicalName) .emitStatement("reader.beginArray()") .beginControlFlow("while (reader.hasNext())") .emitStatement("%s item = %s.createUsingJsonStream(realm, reader)", fieldTypeCanonicalName, proxyClass) @@ -264,7 +270,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer) + JavaWriter writer, boolean isPrimaryKey) throws IOException { String statementSetNullOrThrow; if (Utils.isPrimitiveType(fieldType)) { @@ -281,6 +287,9 @@ public void emitStreamTypeConversion(String interfaceName, String setter, String .nextControlFlow("else") .emitStatement("((%s) obj).%s((%s) reader.next%s())", interfaceName, setter, castType, jsonType) .endControlFlow(); + if (isPrimaryKey) { + writer.emitStatement("jsonHasPrimaryKey = true"); + } } @Override @@ -299,8 +308,7 @@ public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass, jsonType, fieldName) .endControlFlow() .nextControlFlow("else") - .emitStatement("obj = (%1$s) realm.createObject(%2$s.class)", - qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass) + .emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) .endControlFlow(); } } @@ -309,7 +317,7 @@ private interface JsonToRealmFieldTypeConverter { void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, JavaWriter writer) throws IOException; void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer) throws IOException; + JavaWriter writer, boolean isPrimaryKey) throws IOException; void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) throws IOException; diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 8d1e445e09..5eff1c6043 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -1124,15 +1124,11 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP } writer.beginControlFlow("if (rowIndex == TableOrView.NO_MATCH)"); - writer.emitStatement("rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1)"); if (Utils.isString(metadata.getPrimaryKey())) { - writer.beginControlFlow("if (primaryKeyValue != null)"); - writer.emitStatement("Table.nativeSetString(tableNativePtr, pkColumnIndex, rowIndex, (String)primaryKeyValue)"); - writer.endControlFlow(); + writer.emitStatement("rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false)"); } else { - writer.beginControlFlow("if (primaryKeyValue != null)"); - writer.emitStatement("Table.nativeSetLong(tableNativePtr, pkColumnIndex, rowIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter); - writer.endControlFlow(); + writer.emitStatement("rowIndex = table.addEmptyRowWithPrimaryKey(((%s) object).%s(), false)", + interfaceName, primaryKeyGetter); } if (throwIfPrimaryKeyDuplicate) { @@ -1553,6 +1549,10 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep writer.emitEmptyLine(); } + // FIXME: Since we need to check the PK in stream before create an object, this is now using copyToRealm instead of + // createObject() to avoid parse the stream twice. This brings a problem that the default value behaviour is + // different from those which are using the createObject. And it needs to be addressed by + // https://github.com/realm/realm-java/issues/777 private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.emitAnnotation("TargetApi", "Build.VERSION_CODES.HONEYCOMB"); @@ -1563,7 +1563,10 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { Arrays.asList("Realm", "realm", "JsonReader", "reader"), Collections.singletonList("IOException")); - writer.emitStatement("%s obj = realm.createObject(%s.class)",qualifiedClassName, qualifiedClassName); + if (metadata.hasPrimaryKey()) { + writer.emitStatement("boolean jsonHasPrimaryKey = false"); + } + writer.emitStatement("%s obj = new %s()", qualifiedClassName, qualifiedClassName); writer.emitStatement("reader.beginObject()"); writer.beginControlFlow("while (reader.hasNext())"); writer.emitStatement("String name = reader.nextName()"); @@ -1601,7 +1604,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { } else { RealmJsonTypeHelper.emitFillJavaTypeFromStream( interfaceName, - metadata.getSetter(fieldName), + metadata, fieldName, qualifiedFieldType, writer @@ -1616,9 +1619,16 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { } writer.endControlFlow(); writer.emitStatement("reader.endObject()"); + if (metadata.hasPrimaryKey()) { + writer.beginControlFlow("if (!jsonHasPrimaryKey)"); + writer.emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.getPrimaryKey()); + writer.endControlFlow(); + } + writer.emitStatement("obj = realm.copyToRealm(obj)"); writer.emitStatement("return obj"); writer.endMethod(); writer.emitEmptyLine(); + } private String columnInfoClassName() { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index bdec992709..c5c43b8825 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -411,7 +411,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON obj = (io.realm.AllTypesRealmProxy) realm.createObject(some.test.AllTypes.class, json.getString("columnString")); } } else { - obj = (io.realm.AllTypesRealmProxy) realm.createObject(some.test.AllTypes.class); + throw new IllegalArgumentException("JSON object doesn't have the primary key field 'columnString'."); } } if (json.has("columnString")) { @@ -495,7 +495,8 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.AllTypes obj = realm.createObject(some.test.AllTypes.class); + boolean jsonHasPrimaryKey = false; + some.test.AllTypes obj = new some.test.AllTypes(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -506,6 +507,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } else { ((AllTypesRealmProxyInterface) obj).realmSet$columnString((String) reader.nextString()); } + jsonHasPrimaryKey = true; } else if (name.equals("columnLong")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); @@ -566,6 +568,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r reader.skipValue(); ((AllTypesRealmProxyInterface) obj).realmSet$columnRealmList(null); } else { + ((AllTypesRealmProxyInterface) obj).realmSet$columnRealmList(new RealmList()); reader.beginArray(); while (reader.hasNext()) { some.test.AllTypes item = AllTypesRealmProxy.createUsingJsonStream(realm, reader); @@ -578,6 +581,10 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } } reader.endObject(); + if (!jsonHasPrimaryKey) { + throw new IllegalArgumentException("JSON object doesn't have the primary key field 'columnString'."); + } + obj = realm.copyToRealm(obj); return obj; } @@ -683,10 +690,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == TableOrView.NO_MATCH) { - rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); - if (primaryKeyValue != null) { - Table.nativeSetString(tableNativePtr, pkColumnIndex, rowIndex, (String)primaryKeyValue); - } + rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } @@ -813,10 +814,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == TableOrView.NO_MATCH) { - rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); - if (primaryKeyValue != null) { - Table.nativeSetString(tableNativePtr, pkColumnIndex, rowIndex, (String)primaryKeyValue); - } + rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); } cache.put(object, rowIndex); Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong()); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index f04ecb0b32..6f555eb3f6 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -230,7 +230,7 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.Booleans obj = realm.createObject(some.test.Booleans.class); + some.test.Booleans obj = new some.test.Booleans(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -267,6 +267,7 @@ public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader r } } reader.endObject(); + obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 9500a86e0c..e4faa4691d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -915,7 +915,7 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.NullTypes obj = realm.createObject(some.test.NullTypes.class); + some.test.NullTypes obj = new some.test.NullTypes(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -1082,6 +1082,7 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader } } reader.endObject(); + obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index b4b5aca12a..cf863899ca 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -168,7 +168,7 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { - some.test.Simple obj = realm.createObject(some.test.Simple.class); + some.test.Simple obj = new some.test.Simple(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -191,6 +191,7 @@ public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader rea } } reader.endObject(); + obj = realm.copyToRealm(obj); return obj; } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 01678728ff..25483f4de4 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '0.28.0' +ext.coreVersion = '1.0.0-beta-33.0' // empty or comment out this to disable hash checking -ext.coreSha256Hash = 'e4d8ed7342824a1574449700b16cd36f663f4d6768fc09c1aca986f19e27162b' +ext.coreSha256Hash = 'cb86996a80a41b074cfd5162dee85ca0b799dc5fa92e23fd0de33938ed61dbdd' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from @@ -25,7 +25,7 @@ ext.coreArchiveDir = System.getenv("REALM_CORE_DOWNLOAD_DIR") if (!ext.coreArchiveDir) { ext.coreArchiveDir = ".." } -ext.coreArchiveFile = rootProject.file("${ext.coreArchiveDir}/core-android-${project.coreVersion}.tar.gz") +ext.coreArchiveFile = rootProject.file("${ext.coreArchiveDir}/realm-sync-android-${project.coreVersion}.tar.gz") ext.coreDistributionDir = file("${projectDir}/distribution/realm-core/") ext.coreDir = file("${project.coreDistributionDir.getAbsolutePath()}/core-${project.coreVersion}") @@ -129,9 +129,9 @@ task javadoc(type: Javadoc) { locale = 'en_US' overview = 'src/overview.html' - links "http://docs.oracle.com/javase/7/docs/api/" + links "https://docs.oracle.com/javase/7/docs/api/" links "http://reactivex.io/RxJava/javadoc/" - linksOffline "http://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" + linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" } exclude '**/internal/**' exclude '**/BuildConfig.java' @@ -366,7 +366,7 @@ task downloadCore() { doLast { if (shouldDownloadCore()) { // CI artifacts are only available if on the internal network or VPN - def downloadUrl = "s3://realm-ci-artifacts/sync/${project.coreVersion}/android/sync-core-${project.coreVersion}.tar.gz" + def downloadUrl = "s3://realm-ci-artifacts/sync/${project.coreVersion}/android/realm-sync-android-${project.coreVersion}.tar.gz" println "Downloading ${downloadUrl}" exec { @@ -410,7 +410,7 @@ task compileCore(group: 'build setup', description: 'Compile the core library fr copy { from "${coreSourcePath}/realm-core-android-${coreVersion}.tar.gz" into project.coreArchiveFile.parent - rename "realm-core-android-${coreVersion}.tar.gz", "core-android-${coreVersion}.tar.gz" + rename "realm-core-android-${coreVersion}.tar.gz", "realm-sync-android-${coreVersion}.tar.gz" } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 70a8ff2494..9821c20c12 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -40,6 +40,7 @@ import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; +import io.realm.exceptions.RealmException; import io.realm.internal.HandlerControllerConstants; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; @@ -233,6 +234,12 @@ public void createObject_illegalPrimaryKeyValue() { realm.createObject(DogPrimaryKey.CLASS_NAME, "bar"); } + @Test(expected = RealmException.class) + public void createObject_absentPrimaryKeyThrows() { + realm.beginTransaction(); + realm.createObject(DogPrimaryKey.CLASS_NAME); + } + @Test public void where() { realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index ac29db2afc..90625fc4f8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -28,6 +28,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -954,6 +955,7 @@ public void onChange(Realm element) { }); } + // FIXME check if the SharedRealm Changed in handleAsyncTransactionCompleted and reenable this test. // We precisely depend on the order of triggering change listeners right now. // So it should be: // 1. Synced object listener @@ -964,6 +966,7 @@ public void onChange(Realm element) { // If this case fails on your code, think twice before changing the test! // https://github.com/realm/realm-java/issues/2408 is related to this test! @Test + @Ignore("Listener on Realm might be trigger more times, ignore for now") @RunTestInLooperThread public void callingOrdersOfListeners() { final Realm realm = looperThread.realm; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java new file mode 100644 index 0000000000..c985be7e62 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.util.Arrays; + +import io.realm.entities.PrimaryKeyAsBoxedByte; +import io.realm.entities.PrimaryKeyAsBoxedInteger; +import io.realm.entities.PrimaryKeyAsBoxedLong; +import io.realm.entities.PrimaryKeyAsBoxedShort; +import io.realm.entities.PrimaryKeyAsString; +import io.realm.rule.TestRealmConfigurationFactory; + +@RunWith(Parameterized.class) +public class RealmJsonAbsentPrimaryKeyTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + protected Realm realm; + + @Before + public void setUp() { + RealmConfiguration realmConfig = configFactory.createConfiguration(); + realm = Realm.getInstance(realmConfig); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + // parameters for testing absent primary key value. PrimaryKey field is absent. + @Parameterized.Parameters + public static Iterable data() { + return Arrays.asList(new Object[][]{ + {PrimaryKeyAsBoxedByte.class, "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, + {PrimaryKeyAsBoxedShort.class, "{ \"name\":\"KeyValueTestIsFun\" }"}, + {PrimaryKeyAsBoxedInteger.class, "{ \"name\":\"FunValueTestIsKey\" }"}, + {PrimaryKeyAsBoxedLong.class, "{ \"name\":\"NameAsBoxedLong-!\" }"}, + {PrimaryKeyAsString.class, "{ \"id\":2429214 }"} + }); + } + + final private Class clazz; + final private String jsonString; + + public RealmJsonAbsentPrimaryKeyTests(Class clazz, String jsonString) { + this.jsonString = jsonString; + this.clazz = clazz; + } + + // Testing absent primary key value for createObjectFromJson() + @Test + public void createObjectFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createObjectFromJson(clazz, new JSONObject(jsonString)); + realm.commitTransaction(); + } + + // Testing absent primary key value for createOrUpdateObjectFromJson() + @Test + public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createOrUpdateObjectFromJson(clazz, new JSONObject(jsonString)); + realm.commitTransaction(); + } + + // Testing absent primary key value for createAllFromJson() + @Test + public void createAllFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { + JSONArray jsonArray = new JSONArray(); + jsonArray.put(new JSONObject(jsonString)); + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createAllFromJson(clazz, jsonArray); + realm.commitTransaction(); + } + + // Testing absent primary key value for createOrUpdateAllFromJson() + @Test + public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException { + JSONArray jsonArray = new JSONArray(); + jsonArray.put(new JSONObject(jsonString)); + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createOrUpdateAllFromJson(clazz, jsonArray); + realm.commitTransaction(); + } + + // Testing absent primary key value for createObjectFromJson() stream version + @Test + public void createObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createObjectFromJson(clazz, TestHelper.stringToStream(jsonString)); + realm.commitTransaction(); + } + + // Testing absent primary key value for createOrUpdateObjectFromJson() stream version + @Test + public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createOrUpdateObjectFromJson(clazz, TestHelper.stringToStream(jsonString)); + realm.commitTransaction(); + } + + // Testing absent primary key value for createAllFromJson() stream version + @Test + public void createAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + JSONArray jsonArray = new JSONArray(); + jsonArray.put(new JSONObject(jsonString)); + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createAllFromJson(clazz, TestHelper.stringToStream(jsonArray.toString())); + realm.commitTransaction(); + } + + // Testing absent primary key value for createOrUpdateAllFromJson() stream version + @Test + public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + JSONArray jsonArray = new JSONArray(); + jsonArray.put(new JSONObject(jsonString)); + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.createOrUpdateAllFromJson(clazz, TestHelper.stringToStream(jsonArray.toString())); + realm.commitTransaction(); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java index c5482eb151..f0345f07bd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java @@ -57,7 +57,7 @@ public void tearDown() { } } - // parameters for testing null primary key value. PrimaryKey field is explicitly null or absent. + // parameters for testing null primary key value. PrimaryKey field is explicitly null @Parameterized.Parameters public static Iterable data() { return Arrays.asList(new Object[][]{ @@ -65,12 +65,7 @@ public static Iterable data() { {PrimaryKeyAsBoxedShort.class, "YouBetItIsNullKey", "{ \"id\":null, \"name\":\"YouBetItIsNullKey\" }"}, {PrimaryKeyAsBoxedInteger.class, "Gosh Didnt KnowIt", "{ \"id\":null, \"name\":\"Gosh Didnt KnowIt\" }"}, {PrimaryKeyAsBoxedLong.class, "?YOUNOWKNOWRIGHT?", "{ \"id\":null, \"name\":\"?YOUNOWKNOWRIGHT?\" }"}, - {PrimaryKeyAsBoxedByte.class, "HaHaHaHaHaHaHaHaH", "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, - {PrimaryKeyAsBoxedShort.class, "KeyValueTestIsFun", "{ \"name\":\"KeyValueTestIsFun\" }"}, - {PrimaryKeyAsBoxedInteger.class, "FunValueTestIsKey", "{ \"name\":\"FunValueTestIsKey\" }"}, - {PrimaryKeyAsBoxedLong.class, "NameAsBoxedLong-!", "{ \"name\":\"NameAsBoxedLong-!\" }"}, {PrimaryKeyAsString.class, "4299121", "{ \"name\":null, \"id\":4299121 }"}, - {PrimaryKeyAsString.class, "2429214", "{ \"id\":2429214 }"} }); } @@ -84,9 +79,9 @@ public RealmJsonNullPrimaryKeyTests(Class clazz, String s this.clazz = clazz; } - // Testing null or absent primary key value for createObjectFromJson() + // Testing null primary key value for createObjectFromJson() @Test - public void createObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObject() throws JSONException { + public void createObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONException { realm.beginTransaction(); realm.createObjectFromJson(clazz, new JSONObject(jsonString)); realm.commitTransaction(); @@ -107,9 +102,9 @@ public void createObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObject() thro } } - // Testing null or absent primary key value for createOrUpdateObjectFromJson() + // Testing null primary key value for createOrUpdateObjectFromJson() @Test - public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObject() throws JSONException { + public void createOrUpdateObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONException { realm.beginTransaction(); realm.createOrUpdateObjectFromJson(clazz, new JSONObject(jsonString)); realm.commitTransaction(); @@ -130,11 +125,11 @@ public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_fromJsonObjec } } - // Testing null or absent primary key value for createObject() -> createOrUpdateObjectFromJson() + // Testing null primary key value for createObject() -> createOrUpdateObjectFromJson() @Test - public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_updateFromJsonObject() throws JSONException { + public void createOrUpdateObjectFromJson_primaryKey_isNull_updateFromJsonObject() throws JSONException { realm.beginTransaction(); - realm.createObject(clazz); // name = null, id = 0 + realm.createObject(clazz, null); // name = null, id =null realm.createOrUpdateObjectFromJson(clazz, new JSONObject(jsonString)); realm.commitTransaction(); @@ -144,7 +139,6 @@ public void createOrUpdateObjectFromJson_primaryKey_isNullOrAbsent_updateFromJso assertEquals(1, results.size()); assertEquals(Long.valueOf(secondaryFieldValue).longValue(), results.first().getId()); assertEquals(null, results.first().getName()); - // PrimaryKeyAsNumber } else { RealmResults results = realm.where(clazz).findAll(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index fbcc2fdcf8..1e697d5f43 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -409,6 +409,7 @@ public void createObjectFromJson_jsonException() throws JSONException { @Test public void createObjectFromJson_respectIgnoredFields() throws JSONException { JSONObject json = new JSONObject(); + json.put("id", 0); json.put("indexString", "Foo"); json.put("notIndexString", "Bar"); json.put("ignoreString", "Baz"); @@ -763,7 +764,7 @@ public void createOrUpdateObjectFromJson_inputStream() throws IOException { public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException { InputStream stream = TestHelper.stringToStream("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class); // id = 0 + realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 realm.createOrUpdateObjectFromJson(OwnerPrimaryKey.class, stream); realm.commitTransaction(); @@ -961,7 +962,7 @@ public void createOrUpdateObjectFromJson_invalidJsonObject() throws JSONExceptio public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromJsonObject() throws JSONException { JSONObject newObject = new JSONObject("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class); // id = 0 + realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 realm.createOrUpdateObjectFromJson(OwnerPrimaryKey.class, newObject); realm.commitTransaction(); @@ -1367,7 +1368,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromJsonObject() throws JSONException { JSONObject newObject = new JSONObject("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class); // id = 0 + realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 realm.createObjectFromJson(OwnerPrimaryKey.class, newObject); realm.commitTransaction(); @@ -1393,7 +1394,7 @@ public void createObjectFromJson_objectNullClass() throws JSONException { public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException { InputStream stream = TestHelper.stringToStream("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); - realm.createObject(OwnerPrimaryKey.class); // id = 0 + realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 realm.createObjectFromJson(OwnerPrimaryKey.class, stream); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index d634c2a2da..b4f48e94fc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -101,8 +101,7 @@ public void createObject() { for (int i = 1; i < 43; i++) { // using i = 0 as PK will crash subsequent createObject // since createObject uses default values realm.beginTransaction(); - AllTypesRealmModel allTypesRealmModel = realm.createObject(AllTypesRealmModel.class); - allTypesRealmModel.columnLong = i; + realm.createObject(AllTypesRealmModel.class, i); realm.commitTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 8cf83d61a9..8bea9267cf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2249,6 +2249,9 @@ public void resultOfTableViewQuery() { populateTestRealm(); final RealmResults results = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 3L).findAll(); + assertEquals(1, results.size()); + assertEquals("test data 3", results.first().getColumnString()); + final RealmQuery tableViewQuery = results.where(); assertEquals("test data 3", tableViewQuery.findAll().first().getColumnString()); assertEquals("test data 3", tableViewQuery.findFirst().getColumnString()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 975b7aed87..d4bcf79876 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -2087,6 +2087,12 @@ public void createObject_cannotCreateDynamicRealmObject() { } } + @Test(expected = RealmException.class) + public void createObject_absentPrimaryKeyThrows() { + realm.beginTransaction(); + realm.createObject(DogPrimaryKey.class); + } + @Test public void createObjectWithPrimaryKey() { realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 396e9cdf91..bb2f61f4cb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -153,19 +153,19 @@ private void checkSortTwoFieldsStringAscendingIntAscending(RealmResults results) { @@ -179,19 +179,19 @@ private void checkSortTwoFieldsIntString(RealmResults results) { assertEquals("Adam", results.get(0).getColumnString()); assertEquals(4, results.get(0).getColumnLong()); - assertEquals(2, ((TableView) results.getTable()).getSourceRowIndex(0)); + assertEquals(2, ((TableView) results.getTableOrView()).getSourceRowIndex(0)); assertEquals("Brian", results.get(1).getColumnString()); assertEquals(4, results.get(1).getColumnLong()); - assertEquals(1, ((TableView) results.getTable()).getSourceRowIndex(1)); + assertEquals(1, ((TableView) results.getTableOrView()).getSourceRowIndex(1)); assertEquals("Adam", results.get(2).getColumnString()); assertEquals(5, results.get(2).getColumnLong()); - assertEquals(0, ((TableView) results.getTable()).getSourceRowIndex(2)); + assertEquals(0, ((TableView) results.getTableOrView()).getSourceRowIndex(2)); assertEquals("Adam", results.get(3).getColumnString()); assertEquals(5, results.get(3).getColumnLong()); - assertEquals(3, ((TableView) results.getTable()).getSourceRowIndex(3)); + assertEquals(3, ((TableView) results.getTableOrView()).getSourceRowIndex(3)); } private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -205,19 +205,19 @@ private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -231,19 +231,19 @@ private void checkSortTwoFieldsStringAscendingIntDescending(RealmResultsget( S(pos) ).get_index(); + return lvr->get(S(linkViewIndex)).get_index(); } CATCH_STD() return 0; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 94953a1346..0f1eb70bed 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -638,6 +638,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLong( } CATCH_STD() } +JNIEXPORT void JNICALL +Java_io_realm_internal_Table_nativeSetLongUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, + jlong rowIndex, jlong value) +{ + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) + return; + try { + TBL(nativeTablePtr)->set_int_unique( S(columnIndex), S(rowIndex), value); + } CATCH_STD() +} + JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean( JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jboolean value) { @@ -684,6 +695,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString( } CATCH_STD() } +JNIEXPORT void JNICALL +Java_io_realm_internal_Table_nativeSetStringUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, + jlong rowIndex, jstring value) +{ + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) + return; + try { + if (value == NULL) { + if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { + return; + } + } + JStringAccessor value2(env, value); // throws + // FIXME: Check if we need to call set_null_unique when core support it. + TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), value2); + } CATCH_STD() +} + JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp( JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong timestampValue) { @@ -712,15 +741,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray( if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) return; try { - if (dataArray == NULL) { - if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { + if (dataArray == NULL && !TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { return; - } - TBL(nativeTablePtr)->set_binary(S(columnIndex), S(rowIndex), BinaryData()); - } - else { - tbl_nativeDoByteArray(&Table::set_binary, TBL(nativeTablePtr), env, columnIndex, rowIndex, dataArray); } + + JniByteArray byteAccessor(env, dataArray); + TBL(nativeTablePtr)->set_binary(S(columnIndex), S(rowIndex), byteAccessor); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 4d8859e62f..2f038e1d69 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include #include "util.hpp" @@ -1613,15 +1612,13 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeCount( } JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeRemove( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlong start, jlong end, jlong limit) + JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) + if (!QUERY_VALID(env, pQuery)) return 0; try { - return pQuery->remove(S(start), S(end), S(limit)); + return pQuery->remove(); } CATCH_STD() return 0; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp index 65866dc126..b4d561be94 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp @@ -397,7 +397,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetByteArray( if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Binary)) return; - tbl_nativeDoByteArray(&TableView::set_binary, TV(nativeViewPtr), env, columnIndex, rowIndex, byteArray); + + JniByteArray bytesAccessor(env, byteArray); + TV(nativeViewPtr)->set_binary(S(columnIndex), S(rowIndex), bytesAccessor); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp b/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp index 480d49978a..6821fc73c1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp @@ -21,7 +21,6 @@ #include "util.hpp" #include #include -#include #include #include @@ -50,7 +49,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_objectserver_Session_nativeCreateSession } JStringAccessor local_path(env, localRealmPath); - JniSession* jni_session = new JniSession(sync_client, local_path, obj, env); + JniSession* jni_session = new JniSession(env, sync_client, local_path, obj); return reinterpret_cast(jni_session); } CATCH_STD() return 0; diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp index 6e7154f3a0..c82b36b7a8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp @@ -21,7 +21,6 @@ #include "util.hpp" #include #include -#include #include #include @@ -40,11 +39,23 @@ using namespace sync; class AndroidLogger: public realm::util::RootLogger { public: - void do_log(std::string msg) + void do_log(Level level, std::string msg) { - // Figure out how to log properly. We need level/code/message - // Think it has been fixed in later versions of Core - log_message(sync_client_env, log_debug, msg.c_str()); + jmethodID log_method; + switch (level) { + case Level::trace: log_method = log_trace; break; + case Level::debug: log_method = log_debug; break; + case Level::detail: log_method = log_debug; break; + case Level::info: log_method = log_info; break; + case Level::warn: log_method = log_warn; break; + case Level::error: log_method = log_error; break; + case Level::fatal: log_method = log_fatal; break; + case Level::all: + case Level::off: + ThrowException(sync_client_env, IllegalArgument, "Unknown logger argument: " + num_to_string(level)); + return; + } + log_message(sync_client_env, log_method, msg.c_str()); } }; diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 2f222fea53..b11ef9c8b7 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 2f222fea53d1a6d17d6144c1412a751b92cfa080 +Subproject commit b11ef9c8b799f0f09059789ab8711853ce31b85f diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 5104ab2bcf..b15208b799 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -41,13 +41,13 @@ class JniSession { public: JniSession() = delete; - JniSession(realm::sync::Client* sync_client, std::string local_realm_path, jobject java_session_obj, JNIEnv* env) + JniSession(JNIEnv* env, realm::sync::Client* sync_client, std::string local_realm_path, jobject java_session_obj) { // Get the coordinator for the given path, or null if there is none m_sync_session = new realm::sync::Session(*sync_client, local_realm_path); m_global_obj_ref = env->NewGlobalRef(java_session_obj); jobject global_obj_ref_tmp(m_global_obj_ref); - auto sync_transact_callback = [local_realm_path](realm::sync::Session::version_type) { + auto sync_transact_callback = [local_realm_path](realm::VersionID, realm::VersionID) { auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator(realm::StringData(local_realm_path)); if (coordinator) { coordinator->notify_others(); diff --git a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp index a9ca249173..bf7ea21178 100644 --- a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp +++ b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp @@ -41,26 +41,4 @@ jbyteArray tbl_GetByteArray(JNIEnv* env, jlong nativeTablePtr, jlong columnIndex } } -template -void tbl_nativeDoByteArray(M doBinary, T* pTable, JNIEnv* env, jlong columnIndex, jlong rowIndex, jbyteArray dataArray) -{ - jbyte* bytePtr = env->GetByteArrayElements(dataArray, NULL); - if (!bytePtr) { - ThrowException(env, IllegalArgument, "doByteArray"); - return; - } - size_t dataLen = S(env->GetArrayLength(dataArray)); - (pTable->*doBinary)( S(columnIndex), S(rowIndex), realm::BinaryData(reinterpret_cast(bytePtr), dataLen)); - env->ReleaseByteArrayElements(dataArray, bytePtr, 0); -} - - -template -void tbl_nativeDoBinary(M doBinary, T* pTable, JNIEnv* env, jlong columnIndex, jlong rowIndex, jobject byteBuffer) -{ - realm::BinaryData bin; - if (GetBinaryData(env, byteBuffer, bin)) - (pTable->*doBinary)( S(columnIndex), S(rowIndex), bin); -} - #endif // REALM_JNI_TABLEBASE_TPL_HPP diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 7a402f4c2d..8cbd3e37cb 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -28,12 +28,14 @@ #include #include -#include -#include #include #include +#include +#include #include +#include + #include "io_realm_internal_Util.h" #include "io_realm_log_LogLevel.h" @@ -443,6 +445,8 @@ inline bool TblColIndexAndLinkOrLinkList(JNIEnv* env, T* pTable, jlong columnInd && TypeIsLinkLike(env, pTable, columnIndex); } +// FIXME Usually this is called after TBL_AND_INDEX_AND_TYPE_VALID which will validate Table as well. +// Try to avoid duplicated checks to improve performance. template inline bool TblColIndexAndNullable(JNIEnv* env, T* pTable, jlong columnIndex) { return TableIsValid(env, pTable) @@ -575,6 +579,10 @@ class JniByteArray { , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) , m_array(javaArray == NULL ? NULL : env->GetByteArrayElements(javaArray, NULL)) , m_releaseMode(JNI_ABORT) { + if (m_javaArray != nullptr && m_array == nullptr) { + // javaArray is not null but GetByteArrayElements returns null, something is really wrong. + throw std::runtime_error(realm::util::format("GetByteArrayElements failed on byte array %x", m_javaArray)); + } } ~JniByteArray() diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 20671389cd..3445f9cac2 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -77,6 +77,11 @@ public static DynamicRealm getInstance(RealmConfiguration configuration) { public DynamicRealmObject createObject(String className) { checkIfValid(); Table table = schema.getTable(className); + // Check and throw the exception earlier for a better exception message. + if (table.hasPrimaryKey()) { + throw new RealmException(String.format("'%s' has a primary key, use" + + " 'createObject(String, Object)' instead.", className)); + } long rowIndex = table.addEmptyRow(); return get(DynamicRealmObject.class, className, rowIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 9f294b8af4..204d4ded3b 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -295,13 +295,14 @@ private static void initializeRealm(Realm realm) { /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. *

    - * JSON properties with {@code null} values will map to the default value for the data type in Realm and unknown properties - * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} - * field will be set to the default value for that type. + * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the + * JSON object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm objects to create. * @param json an array where each JSONObject must map to the specified class. * @throws RealmException if mapping from JSON fails. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. */ public void createAllFromJson(Class clazz, JSONArray json) { if (clazz == null || json == null) { @@ -326,8 +327,9 @@ public void createAllFromJson(Class clazz, JSONArray j * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json array with object data. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if unable to map JSON. * @see #createAllFromJson(Class, org.json.JSONArray) */ @@ -347,13 +349,14 @@ public void createOrUpdateAllFromJson(Class clazz, JSO /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. - * JSON properties with {@code null} values will map to the default value for the data type in Realm and unknown properties - * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field - * will be set to the default value for that type. + * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the + * JSON object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm objects to create. * @param json the JSON array as a String where each object can map to the specified class. * @throws RealmException if mapping from JSON fails. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. */ public void createAllFromJson(Class clazz, String json) { if (clazz == null || json == null || json.length() == 0) { @@ -379,9 +382,10 @@ public void createAllFromJson(Class clazz, String json * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json string with an array of JSON objects. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. * @throws RealmException if unable to create a JSON array from the json string. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @see #createAllFromJson(Class, String) */ public void createOrUpdateAllFromJson(Class clazz, String json) { @@ -402,13 +406,14 @@ public void createOrUpdateAllFromJson(Class clazz, Str /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. - * JSON properties with {@code null} value will map to the default value for the data type in Realm and unknown properties - * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field - * will be set to the default value for that type. + * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the + * JSON object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm objects created. * @param inputStream the JSON array as a InputStream. All objects in the array must be of the specified class. * @throws RealmException if mapping from JSON fails. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws IOException if something was wrong with the input stream. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) @@ -438,8 +443,9 @@ public void createAllFromJson(Class clazz, InputStream * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the InputStream with a list of object data in JSON format. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if unable to read JSON. * @see #createOrUpdateAllFromJson(Class, java.io.InputStream) */ @@ -470,14 +476,15 @@ public void createOrUpdateAllFromJson(Class clazz, Inp /** * Creates a Realm object pre-filled with data from a JSON object. This must be done inside a transaction. JSON - * properties with {@code null} values will map to the default value for the data type in Realm and unknown properties will - * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will - * be set to the default value for that type. + * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON + * object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm object to create. * @param json the JSONObject with object data. * @return created object or {@code null} if no JSON data was provided. * @throws RealmException if the mapping from JSON fails. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @see #createOrUpdateObjectFromJson(Class, org.json.JSONObject) */ public E createObjectFromJson(Class clazz, JSONObject json) { @@ -501,8 +508,9 @@ public E createObjectFromJson(Class clazz, JSONObject * @param clazz Type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json {@link org.json.JSONObject} with object data. * @return created or updated {@link io.realm.RealmObject}. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if JSON data cannot be mapped. * @see #createObjectFromJson(Class, org.json.JSONObject) */ @@ -521,14 +529,15 @@ public E createOrUpdateObjectFromJson(Class clazz, JSO /** * Creates a Realm object pre-filled with data from a JSON object. This must be done inside a transaction. JSON - * properties with {@code null} values will map to the default value for the data type in Realm and unknown properties will - * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will - * be set to the default value for that type. + * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON + * object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm object to create. * @param json the JSON string with object data. * @return created object or {@code null} if JSON string was empty or null. * @throws RealmException if mapping to json failed. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. */ public E createObjectFromJson(Class clazz, String json) { if (clazz == null || json == null || json.length() == 0) { @@ -555,8 +564,9 @@ public E createObjectFromJson(Class clazz, String json * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json string with object data in JSON format. * @return created or updated {@link io.realm.RealmObject}. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if JSON object cannot be mapped from the string parameter. * @see #createObjectFromJson(Class, String) */ @@ -578,14 +588,15 @@ public E createOrUpdateObjectFromJson(Class clazz, Str /** * Creates a Realm object pre-filled with data from a JSON object. This must be done inside a transaction. JSON - * properties with {@code null} value will map to the default value for the data type in Realm and unknown properties will - * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will - * be set to the default value for that type. + * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON + * object the {@link RealmObject} field will be set to the default value for that type. * * @param clazz type of Realm object to create. * @param inputStream the JSON object data as a InputStream. * @return created object or {@code null} if JSON string was empty or null. * @throws RealmException if the mapping from JSON failed. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws IOException if something went wrong with the input stream. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) @@ -632,8 +643,9 @@ public E createObjectFromJson(Class clazz, InputStream * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the {@link InputStream} with object data in JSON format. * @return created or updated {@link io.realm.RealmObject}. - * @throws java.lang.IllegalArgumentException if trying to update a class without a - * {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. + * @throws IllegalArgumentException if the JSON object doesn't have a primary key property but the corresponding + * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws RealmException if failure to read JSON. * @see #createObjectFromJson(Class, java.io.InputStream) */ @@ -674,6 +686,11 @@ private Scanner getFullStringScanner(InputStream in) { public E createObject(Class clazz) { checkIfValid(); Table table = schema.getTable(clazz); + // Check and throw the exception earlier for a better exception message. + if (table.hasPrimaryKey()) { + throw new RealmException(String.format("'%s' has a primary key, use" + + " 'createObject(Class, Object)' instead.", Table.tableNameToClassName(table.getName()))); + } long rowIndex = table.addEmptyRow(); return get(clazz, rowIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 0bfa172f4a..43c382f3c6 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -453,6 +453,7 @@ public Builder directory(File directory) { /** * Sets the 64 bit key used to encrypt and decrypt the Realm file. + * Sets the {@value io.realm.RealmConfiguration#KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file. */ public Builder encryptionKey(byte[] key) { if (key == null) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index ed64e04ef9..0ecf8c1b88 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -65,7 +65,7 @@ public final class RealmQuery { private String className; private TableOrView table; private RealmObjectSchema schema; - private LinkView view; + private LinkView linkView; private TableQuery query; private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; @@ -136,7 +136,7 @@ private RealmQuery(Realm realm, Class clazz) { this.clazz = clazz; this.schema = realm.schema.getSchemaForClass(clazz); this.table = schema.table; - this.view = null; + this.linkView = null; this.query = table.where(); } @@ -144,18 +144,18 @@ private RealmQuery(RealmResults queryResults, Class clazz) { this.realm = queryResults.realm; this.clazz = clazz; this.schema = realm.schema.getSchemaForClass(clazz); - this.table = queryResults.getTable(); - this.view = null; - this.query = queryResults.getTable().where(); + this.table = queryResults.getTableOrView(); + this.linkView = null; + this.query = this.table.where(); } - private RealmQuery(BaseRealm realm, LinkView view, Class clazz) { + private RealmQuery(BaseRealm realm, LinkView linkView, Class clazz) { this.realm = realm; this.clazz = clazz; - this.query = view.where(); - this.view = view; this.schema = realm.schema.getSchemaForClass(clazz); this.table = schema.table; + this.linkView = linkView; + this.query = linkView.where(); } private RealmQuery(BaseRealm realm, String className) { @@ -171,16 +171,16 @@ private RealmQuery(RealmResults queryResults, String classNa this.className = className; this.schema = realm.schema.getSchemaForClass(className); this.table = schema.table; - this.query = queryResults.getTable().where(); + this.query = queryResults.getTableOrView().where(); } - private RealmQuery(BaseRealm realm, LinkView view, String className) { + private RealmQuery(BaseRealm realm, LinkView linkView, String className) { this.realm = realm; this.className = className; - this.query = view.where(); - this.view = view; this.schema = realm.schema.getSchemaForClass(className); this.table = schema.table; + this.linkView = linkView; + this.query = linkView.where(); } /** @@ -194,8 +194,8 @@ public boolean isValid() { return false; } - if (view != null) { - return view.isAttached(); + if (linkView != null) { + return linkView.isAttached(); } return table != null && table.getTable().isValid(); } @@ -2064,9 +2064,9 @@ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, */ public E findFirst() { checkQueryIsNotReused(); - long sourceRowIndex = getSourceRowIndexForFirstObject(); - if (sourceRowIndex >= 0) { - E realmObject = realm.get(clazz, className, sourceRowIndex); + long tableRowIndex = getSourceRowIndexForFirstObject(); + if (tableRowIndex >= 0) { + E realmObject = realm.get(clazz, className, tableRowIndex); return realmObject; } else { return null; @@ -2217,19 +2217,9 @@ private void checkQueryIsNotReused() { } private long getSourceRowIndexForFirstObject() { - long rowIndex = this.query.find(); - if (rowIndex < 0) { - return rowIndex; - } - if (this.view != null) { - return view.getTargetRowIndex(rowIndex); - } else if (table instanceof TableView){ - return ((TableView) table).getSourceRowIndex(rowIndex); - } else { - return rowIndex; - } + long tableRowIndex = this.query.find(); + return tableRowIndex; } - // Get the column index for sorting related functions. A proper exception will be thrown if the field doesn't exist // or it belongs to the child object. private long getColumnIndexForSort(String fieldName) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 090302a6c2..ea37900e37 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -146,7 +146,7 @@ private RealmResults(BaseRealm realm, TableOrView table, String className) { this.currentTableViewVersion = table.syncIfNeeded(); } - TableOrView getTable() { + TableOrView getTableOrView() { if (table == null) { return realm.schema.getTable(classSpec); } else { @@ -177,7 +177,6 @@ public boolean isManaged() { @Override public RealmQuery where() { realm.checkIfValid(); - return RealmQuery.createQueryFromResult(this); } @@ -211,7 +210,7 @@ public boolean contains(Object object) { public E get(int location) { E obj; realm.checkIfValid(); - TableOrView table = getTable(); + TableOrView table = getTableOrView(); if (table instanceof TableView) { obj = realm.get(classSpec, className, ((TableView) table).getSourceRowIndex(location)); } else { @@ -252,7 +251,7 @@ public E last() { @Override public void deleteFromRealm(int location) { realm.checkIfValid(); - TableOrView table = getTable(); + TableOrView table = getTableOrView(); table.remove(location); } @@ -263,7 +262,7 @@ public void deleteFromRealm(int location) { public boolean deleteAllFromRealm() { realm.checkIfValid(); if (size() > 0) { - TableOrView table = getTable(); + TableOrView table = getTableOrView(); table.clear(); return true; } else { @@ -382,7 +381,7 @@ public int size() { if (!isLoaded()) { return 0; } else { - long size = getTable().size(); + long size = getTableOrView().size(); return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; } } @@ -509,7 +508,7 @@ public RealmResults distinct(String fieldName) { realm.checkIfValid(); long columnIndex = RealmQuery.getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); - TableOrView tableOrView = getTable(); + TableOrView tableOrView = getTableOrView(); if (tableOrView instanceof Table) { this.table = ((Table) tableOrView).getDistinctView(columnIndex); } else { @@ -618,7 +617,7 @@ public boolean retainAll(Collection collection) { public boolean deleteLastFromRealm() { realm.checkIfValid(); if (size() > 0) { - TableOrView table = getTable(); + TableOrView table = getTableOrView(); table.removeLast(); return true; } else { @@ -648,7 +647,7 @@ void syncIfNeeded() { @Override public boolean deleteFirstFromRealm() { if (size() > 0) { - TableOrView table = getTable(); + TableOrView table = getTableOrView(); table.removeFirst(); return true; } else { diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index d05a4a51dd..6ba77cc800 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -62,8 +62,11 @@ public CheckedRow getCheckedRow(long index) { return CheckedRow.get(context, this, index); } - public long getTargetRowIndex(long pos) { - return nativeGetTargetRowIndex(nativePointer, pos); + /** + * Returns the row index in the underlying table. + */ + public long getTargetRowIndex(long linkViewIndex) { + return nativeGetTargetRowIndex(nativePointer, linkViewIndex); } public void add(long rowIndex) { @@ -169,7 +172,7 @@ private void checkImmutable() { public static native void nativeClose(long nativeLinkViewPtr); native long nativeGetRow(long nativeLinkViewPtr, long pos); - private native long nativeGetTargetRowIndex(long nativeLinkViewPtr, long pos); + private native long nativeGetTargetRowIndex(long nativeLinkViewPtr, long linkViewIndex); public static native void nativeAdd(long nativeLinkViewPtr, long rowIndex); private native void nativeInsert(long nativeLinkViewPtr, long pos, long rowIndex); private native void nativeSet(long nativeLinkViewPtr, long pos, long rowIndex); diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index e348a15129..f13ad8671c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -46,7 +46,7 @@ public abstract class RealmProxyMediator { * Creates the backing table in Realm for the given RealmObject class. * * @param clazz the {@link RealmObject} model class to create backing table for. - * @param transaction the read transaction for the Realm to create table in. + * @param sharedRealm the wrapper object of underlying native database. */ public abstract Table createTable(Class clazz, SharedRealm sharedRealm); @@ -54,7 +54,7 @@ public abstract class RealmProxyMediator { * Validates the backing table in Realm for the given RealmObject class. * * @param clazz the {@link RealmObject} model class to validate. - * @param sharedRealm the read transaction for the Realm to validate against. + * @param sharedRealm the wrapper object of underlying native database to validate against. * @return the field indices map. */ public abstract ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 5b3c1bab4a..b831ef5ea5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -19,7 +19,6 @@ import java.util.Date; import io.realm.RealmFieldType; -import io.realm.Sort; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; @@ -373,33 +372,43 @@ public void moveLastOver(long rowIndex) { nativeMoveLastOver(nativePtr, rowIndex); } + /** + * Add an empty row to the table which doesn't have a primary key defined. + *

    + * NOTE: To add a table with a primary key defined, use {@link #addEmptyRowWithPrimaryKey(Object)} instead. This + * won't check if this table has a primary key. + * + * @return row index. + */ public long addEmptyRow() { checkImmutable(); - if (hasPrimaryKey()) { - long primaryKeyColumnIndex = getPrimaryKey(); - RealmFieldType type = getColumnType(primaryKeyColumnIndex); - switch (type) { - case STRING: - if (findFirstString(primaryKeyColumnIndex, STRING_DEFAULT_VALUE) != NO_MATCH) { - throwDuplicatePrimaryKeyException(STRING_DEFAULT_VALUE); - } - break; - case INTEGER: - if (findFirstLong(primaryKeyColumnIndex, INTEGER_DEFAULT_VALUE) != NO_MATCH) { - throwDuplicatePrimaryKeyException(INTEGER_DEFAULT_VALUE); - } - break; - default: - throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); - } - } - return nativeAddEmptyRow(nativePtr, 1); } + /** + * Add an empty row to the table and set the primary key with the given value. Equivalent to call + * {@link #addEmptyRowWithPrimaryKey(Object, boolean)} with {@code validation = true}. + * + * @param primaryKeyValue the primary key value + * @return the row index. + */ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { - checkImmutable(); - checkHasPrimaryKey(); + return addEmptyRowWithPrimaryKey(primaryKeyValue, true); + } + + /** + * Add an empty row to the table and set the primary key with the given value. + * + * @param primaryKeyValue the primary key value. + * @param validation set to {@code false} to skip all validations. This is currently used by bulk insert which + * has its own validations. + * @return the row index. + */ + public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation) { + if (validation) { + checkImmutable(); + checkHasPrimaryKey(); + } long primaryKeyColumnIndex = getPrimaryKey(); RealmFieldType type = getColumnType(primaryKeyColumnIndex); @@ -411,11 +420,12 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { switch (type) { case STRING: case INTEGER: - if (findFirstNull(primaryKeyColumnIndex) != NO_MATCH) { + if (validation && findFirstNull(primaryKeyColumnIndex) != NO_MATCH) { throwDuplicatePrimaryKeyException("null"); } rowIndex = nativeAddEmptyRow(nativePtr, 1); row = getUncheckedRow(rowIndex); + // FIXME: Use core's set_null_unique when core supports it. row.setNull(primaryKeyColumnIndex); break; @@ -429,12 +439,11 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { if (!(primaryKeyValue instanceof String)) { throw new IllegalArgumentException("Primary key value is not a String: " + primaryKeyValue); } - if (findFirstString(primaryKeyColumnIndex, (String) primaryKeyValue) != NO_MATCH) { + if (validation && findFirstString(primaryKeyColumnIndex, (String) primaryKeyValue) != NO_MATCH) { throwDuplicatePrimaryKeyException(primaryKeyValue); } rowIndex = nativeAddEmptyRow(nativePtr, 1); - row = getUncheckedRow(rowIndex); - row.setString(primaryKeyColumnIndex, (String) primaryKeyValue); + nativeSetStringUnique(nativePtr, primaryKeyColumnIndex, rowIndex, (String) primaryKeyValue); break; case INTEGER: @@ -444,12 +453,11 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { } catch (RuntimeException e) { throw new IllegalArgumentException("Primary key value is not a long: " + primaryKeyValue); } - if (findFirstLong(primaryKeyColumnIndex, pkValue) != NO_MATCH) { + if (validation && findFirstLong(primaryKeyColumnIndex, pkValue) != NO_MATCH) { throwDuplicatePrimaryKeyException(pkValue); } rowIndex = nativeAddEmptyRow(nativePtr, 1); - row = getUncheckedRow(rowIndex); - row.setLong(primaryKeyColumnIndex, pkValue); + nativeSetLongUnique(nativePtr, primaryKeyColumnIndex, rowIndex, pkValue); break; default: @@ -479,6 +487,9 @@ public long addEmptyRows(long rows) { * * @param values values. * @return the row index of the appended row. + * @deprecated Remove this functions since it doesn't seem to be useful. And this function does deal with tables + * withprimary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row + * added. */ protected long add(Object... values) { long rowIndex = addEmptyRow(); @@ -1324,11 +1335,13 @@ public static String tableNameToClassName(String tableName) { private native long nativeGetLinkTarget(long nativePtr, long columnIndex); native long nativeGetRowPtr(long nativePtr, long index); public static native void nativeSetLong(long nativeTablePtr, long columnIndex, long rowIndex, long value); + public static native void nativeSetLongUnique(long nativeTablePtr, long columnIndex, long rowIndex, long value); public static native void nativeSetBoolean(long nativeTablePtr, long columnIndex, long rowIndex, boolean value); public static native void nativeSetFloat(long nativeTablePtr, long columnIndex, long rowIndex, float value); public static native void nativeSetDouble(long nativeTablePtr, long columnIndex, long rowIndex, double value); public static native void nativeSetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex, long dateTimeValue); public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value); + public static native void nativeSetStringUnique(long nativeTablePtr, long columnIndex, long rowIndex, String value); public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex); public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data); public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index cb6bd376c1..5060ad4e23 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -420,11 +420,15 @@ public TableQuery isNotEmpty(long[] columnIndices) { // Searching methods. + @Deprecated // Doesn't seem to be used public long find(long fromTableRow) { validateQuery(); return nativeFind(nativePtr, fromTableRow); } + /** + * Returns the table row index for the first element matching the query. + */ public long find() { validateQuery(); return nativeFind(nativePtr, 0); @@ -717,17 +721,10 @@ public long count() { return nativeCount(nativePtr, 0, Table.INFINITE, Table.INFINITE); } - // Deletion. - public long remove(long start, long end) { - validateQuery(); - if (table.isImmutable()) throwImmutable(); - return nativeRemove(nativePtr, start, end, Table.INFINITE); - } - public long remove() { validateQuery(); if (table.isImmutable()) throwImmutable(); - return nativeRemove(nativePtr, 0, Table.INFINITE, Table.INFINITE); + return nativeRemove(nativePtr); } /** @@ -808,7 +805,7 @@ private void throwImmutable() { private native void nativeIsNull(long nativePtr, long columnIndices[]); private native void nativeIsNotNull(long nativePtr, long columnIndices[]); private native long nativeCount(long nativeQueryPtr, long start, long end, long limit); - private native long nativeRemove(long nativeQueryPtr, long start, long end, long limit); + private native long nativeRemove(long nativeQueryPtr); private native long nativeImportHandoverTableViewIntoSharedGroup(long handoverTableViewPtr, long callerSharedRealmPtr) throws BadVersionException; private native long nativeHandoverQuery(long callerSharedRealmPtr, long nativeQueryPtr); private static native long nativeFindAllSortedWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long start, long end, long limit, long columnIndex, boolean ascending) throws BadVersionException; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java b/realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java index ef375186eb..15027bb737 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java @@ -46,19 +46,17 @@ public void onStop() { @Override public void onError(ObjectServerError error) { switch(error.errorCode()) { - // Auth protocol errors (should not happen). If credentials are being replaced + // FIXME: Regenerate this + // Auth protocol errors (should not happen). case IO_EXCEPTION: case JSON_EXCEPTION: - case REALM_PROBLEM: case INVALID_PARAMETERS: case MISSING_PARAMETERS: case INVALID_CREDENTIALS: case UNKNOWN_ACCOUNT: case EXISTING_ACCOUNT: case ACCESS_DENIED: - case INVALID_REFRESH_TOKEN: case EXPIRED_REFRESH_TOKEN: - case INTERNAL_SERVER_ERROR: throw new IllegalStateException("Authentication protocol errors should not happen: " + error.toString()); // Ignore Network client errors (irrelevant) diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java b/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java index e1930a6686..ac857cc59e 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java @@ -27,19 +27,6 @@ public enum ErrorCode { IO_EXCEPTION(0, Category.RECOVERABLE), // Some IO error while either contacting the server or reading the response JSON_EXCEPTION(1), // JSON input could not be parsed correctly - // Realm Authentication Server response errors (50 - 99) - - REALM_PROBLEM(50), - INVALID_PARAMETERS(51), - MISSING_PARAMETERS(52), - INVALID_CREDENTIALS(53), - UNKNOWN_ACCOUNT(54), - EXISTING_ACCOUNT(55), - ACCESS_DENIED(56), - INVALID_REFRESH_TOKEN(57), - EXPIRED_REFRESH_TOKEN(58), - INTERNAL_SERVER_ERROR(59), - // Realm Object Server errors (100 - 199) // Connection level and protocol errors @@ -72,7 +59,19 @@ public enum ErrorCode { BAD_SERVER_VERSION(209), // Bad server version (IDENT, UPLOAD) BAD_CLIENT_VERSION(210), // Bad client version (IDENT, UPLOAD) DIVERGING_HISTORIES(211), // Diverging histories (IDENT) - BAD_CHANGESET(212); // Bad changeset (UPLOAD) + BAD_CHANGESET(212), // Bad changeset (UPLOAD) + + // 300 - 599 Standard HTTP error codes + + // Realm Authentication Server response errors (600 - 699) + + INVALID_PARAMETERS(601), + MISSING_PARAMETERS(602), + INVALID_CREDENTIALS(611), + UNKNOWN_ACCOUNT(612), + EXISTING_ACCOUNT(613), + ACCESS_DENIED(614), + EXPIRED_REFRESH_TOKEN(615); private final int code; private final Category category; @@ -126,19 +125,6 @@ public static ErrorCode fromInt(int errorCode) { throw new IllegalArgumentException("Unknown error code: " + errorCode); } - public static ErrorCode fromAuthError(String type) { - switch(type) { - case "https://realm.io/docs/object-server/problems/invalid-credentials" : return ErrorCode.INVALID_CREDENTIALS; - case "https://realm.io/docs/object-server/problems/unknown-account" : return ErrorCode.UNKNOWN_ACCOUNT; - case "https://realm.io/docs/object-server/problems/existing-account" : return ErrorCode.EXISTING_ACCOUNT; - case "https://realm.io/docs/object-server/problems/access-denied" : return ErrorCode.ACCESS_DENIED; - case "https://realm.io/docs/object-server/problems/expired-refresh-token" : return ErrorCode.EXPIRED_REFRESH_TOKEN; - case "https://realm.io/docs/object-server/problems/internal-server-error" : return ErrorCode.INTERNAL_SERVER_ERROR; - default: - throw new IllegalArgumentException("Unknown error: " + type); - } - } - public enum Category { FATAL, // Abort session as soon as possible RECOVERABLE, // Still possible to recover the session by either rebinding or providing the required information. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java b/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java index 0b197aaf41..f66ef6f31a 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java @@ -42,12 +42,31 @@ public ObjectServerError(ErrorCode errorCode, Throwable exception) { this(errorCode, null, exception); } + /** + * Generic error happening that could happen anywhere. + * + * @param errorCode + * @param errorMessage + * @param exception + */ public ObjectServerError(ErrorCode errorCode, String errorMessage, Throwable exception) { this.error = errorCode; this.errorMessage = errorMessage; this.exception = exception; } + /** + * Errors happening while trying to authenticate a user. + * + * @param errorCode + * @param title + * @param hint + * @param type + */ + public ObjectServerError(ErrorCode errorCode, String title, String hint, String type) { + this(errorCode, String.format("%s : %s (%s)", title, hint, type), null); + } + public ErrorCode errorCode() { return error; } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/Session.java b/realm/realm-library/src/main/java/io/realm/objectserver/Session.java index d1f5acbb57..9dfbc340b3 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/Session.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/Session.java @@ -37,8 +37,8 @@ * a Realm instance. Once a session has been created it will continue to exist until explicitly closed or the * underlying Realm file is deleted. *

    - * It is normally not necessary to interact directly with a session. That should be done by the {@link SyncPolicy} - * defined using {@link io.realm.objectserver.SyncConfiguration.Builder#syncPolicy(SyncPolicy)}. + * It is normally not necessary to interact directly with a session. That should be done by the {@code SyncPolicy} + * defined using {@code io.realm.objectserver.SyncConfiguration.Builder#syncPolicy(SyncPolicy)}. *

    * A session has a lifecycle consisting of the following states: *

    @@ -46,8 +46,8 @@ *

  • * INITIAL Initial state when creating the Session object. No connections to the object server have been * made yet. At this point it is possible to register any relevant error and event listeners. Calling - * {@link #start()} will cause the session to become unbound and notify the {@link SyncPolicy} that the - * session is ready by calling {@link SyncPolicy#onSessionCreated(Session)}. + * {@link #start()} will cause the session to become unbound and notify the {@code SyncPolicy} that the + * session is ready by calling {@code SyncPolicy#onSessionCreated(Session)}. *
  • *
  • * UNBOUND When a session is unbound, no synchronization between the local and remote Realm is happening. @@ -68,13 +68,11 @@ *
  • *
  • * STOPPED The session has been stopped and no longer work. A new session will be created the next time - * either the Realm is opened or {@link SyncManager#getSession(SyncConfiguration)} is called. + * either the Realm is opened or {@code SyncManager#getSession(SyncConfiguration)} is called. *
  • * * * This object is thread safe. - * - * @see io.realm.objectserver.SyncConfiguration.Builder#syncPolicy(SyncPolicy) */ @Keep public final class Session { @@ -159,7 +157,7 @@ public synchronized void stop() { * * While this method will return immediately, binding a Realm is not guaranteed to succeed. Possible reasons for * failure could be either if the device is offline or credentials have expired. Binding is an asynchronous - * operation and all errors will be sent first to {@link SyncPolicy#onError(Session, ObjectServerError)} and if the + * operation and all errors will be sent first to {@code SyncPolicy#onError(Session, ObjectServerError)} and if the * SyncPolicy didn't handle it, to the {@link ErrorHandler} defined by * {@link SyncConfiguration.Builder#errorHandler(ErrorHandler)}. */ diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java index 428de0569e..fb25cd232b 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java @@ -417,7 +417,7 @@ Builder syncPolicy(SyncPolicy syncPolicy) { * Sets the error handler used by this configuration. * This will override any handler set by calling {@link SyncManager#setDefaultSessionErrorHandler(Session.ErrorHandler)}. * - * Only errors not handled by the defined {@link SyncPolicy} will be reported to this error handler. + * Only errors not handled by the defined {@code SyncPolicy} will be reported to this error handler. * * @param errorHandler error handler used to report back errors when communicating with the Realm Object Server. * @throws IllegalArgumentException if {@code null} is given as an error handler. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/User.java b/realm/realm-library/src/main/java/io/realm/objectserver/User.java index bf3e2a176c..e6445edf6e 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/User.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/User.java @@ -64,10 +64,9 @@ public class User { * @return */ public static User createLocal() { - Token token = new Token(UUID.randomUUID().toString(), Long.MAX_VALUE, Token.Permission.values()); + Token token = new Token(UUID.randomUUID().toString(), null, null, Long.MAX_VALUE, Token.Permission.values()); return new User(UUID.randomUUID().toString(), token, null); } - /** * Load a user that has previously been serialized using {@link #toJson()}. * @@ -79,11 +78,10 @@ public static User createLocal() { public static User fromJson(String user) { try { JSONObject obj = new JSONObject(user); - String id = obj.getString("identifier"); Token refreshToken = Token.from(obj.getJSONObject("refreshToken")); URL authUrl = new URL(obj.getString("authUrl")); // FIXME: Add support for storing access tokens as well - return new User(id, refreshToken, authUrl); + return new User(refreshToken.identity(), refreshToken, authUrl); } catch (JSONException e) { throw new IllegalArgumentException("Could not parse user json: " + user, e); } catch (MalformedURLException e) { @@ -101,9 +99,10 @@ public static User fromJson(String user) { * * @param token token to represent user. */ + // FIXME Align with Cocoa on naming public static User fromToken(String token) { // Define a user with unlimited access. Object Server will reject any invalid access anyway. - return new User(null, new Token(token, Long.MAX_VALUE, Token.Permission.values()), null); + return new User(null, new Token(token, null, null, Long.MAX_VALUE, Token.Permission.values()), null); } public static User login(final Credentials credentials, final URL authentificationUrl) @@ -141,7 +140,7 @@ public void run() { try { AuthenticateResponse result = server.authenticateUser(credentials, authUrl, credentials.shouldCreateUser()); if (result.isValid()) { - User user = new User(result.getIdentifier(), result.getRefreshToken(), authUrl); + User user = new User(result.getRefreshToken().identity(), result.getRefreshToken(), authUrl); postSuccess(user); } else { postError(result.getError()); @@ -312,7 +311,7 @@ void addAccessToken(URI uri, String accessToken) { // Optimistically create a long-lived token with all permissions. If this is incorrect the Object Server // will reject it anyway. If tokens are added manually it is up to the user to ensure they are also used // correctly. - addAccessToken(uri, new Token(accessToken, Long.MAX_VALUE, Token.Permission.values())); + addAccessToken(uri, new Token(accessToken, null, uri.toString(), Long.MAX_VALUE, Token.Permission.values())); } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java index 99bcaeb304..7814773d91 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java @@ -29,14 +29,19 @@ public class Token { private final String value; - private final long expires; + private final long expiresSec; private final Permission[] permissions; + private final String identity; + private final String path; public static Token from(JSONObject token) throws JSONException { String value = token.getString("token"); - long expires = token.getLong("expires"); + JSONObject tokenData = token.getJSONObject("token_data"); + String identity = tokenData.getString("identity"); + String path = tokenData.optString("path"); + long expiresSec = tokenData.getLong("expires"); Permission[] permissions; - JSONArray access = token.getJSONArray("access"); + JSONArray access = tokenData.getJSONArray("access"); if (access != null) { permissions = new Permission[access.length()]; for (int i = 0; i < access.length(); i++) { @@ -50,12 +55,14 @@ public static Token from(JSONObject token) throws JSONException { permissions = new Permission[0]; } - return new Token(value, expires, permissions); + return new Token(value, identity, path, expiresSec, permissions); } - public Token(String value, long expires, Permission... permissions) { + public Token(String value, String identity, String path, long expiresSec, Permission[] permissions) { this.value = value; - this.expires = expires; + this.identity = identity; + this.path = path; + this.expiresSec = expiresSec; this.permissions = Arrays.copyOf(permissions, permissions.length); } @@ -63,17 +70,24 @@ public String value() { return value; } + public String identity() { return identity; } + + public String path() { return path; } + /** - * Returns when this token expiresSec. Timestamp is in UTC seconds. + * Returns when this token expires. Timestamp is in UTC seconds. */ public long expiresSec() { - return expires; + return expiresSec; } + /** + * Returns when this token expires. Timestamp is in UTC milliseconds. + */ public long expiresMs() { - long expiresMs = expires * 1000; - if (expiresMs < expires) { - return Long.MAX_VALUE; // Overflow + long expiresMs = expiresSec * 1000; + if (expiresMs < expiresSec) { + return Long.MAX_VALUE; // Prevent overflow } else { return expiresMs; } @@ -87,7 +101,7 @@ public String toJson() { JSONObject obj = new JSONObject(); try { obj.put("token", value); - obj.put("expires", expires); + obj.put("expires", expiresSec); JSONArray perms = new JSONArray(); for (int i = 0; i < permissions.length; i++) { perms.put(permissions[i].toString().toLowerCase(Locale.US)); diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java index cfd9822369..d2695a828b 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java @@ -33,9 +33,6 @@ public class AuthenticateResponse { private final ObjectServerError error; - private final String identifier; - private final String path; - private final String appId; private final Token accessToken; private final Token refreshToken; @@ -57,8 +54,9 @@ public static AuthenticateResponse createFrom(Response response) { JSONObject obj = new JSONObject(serverResponse); String type = obj.getString("type"); String hint = obj.optString("hint", null); - ErrorCode errorCode = ErrorCode.fromAuthError(type); - ObjectServerError error = new ObjectServerError(errorCode, hint); + String title = obj.optString("title", null); + ErrorCode errorCode = ErrorCode.fromInt(obj.optInt("code", -1)); + ObjectServerError error = new ObjectServerError(errorCode, title, hint, type); return new AuthenticateResponse(error); } catch (JSONException e) { ObjectServerError error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, "Server failed with " + @@ -75,9 +73,6 @@ public static AuthenticateResponse createFrom(Response response) { */ public AuthenticateResponse(ObjectServerError error) { this.error = error; - this.identifier = null; - this.path = null; - this.appId = null; this.accessToken = null; this.refreshToken = null; } @@ -95,23 +90,15 @@ public AuthenticateResponse(String serverResponse) { Token refreshToken; try { JSONObject obj = new JSONObject(serverResponse); - identifier = obj.getString("identity"); - path = obj.optString("path"); - appId = obj.optString("app_id"); // FIXME No longer sent? - accessToken = obj.has("token") ? Token.from(obj) : null; - refreshToken = obj.has("refresh") ? Token.from(obj.getJSONObject("refresh")) : null; + accessToken = obj.has("accessToken") ? Token.from(obj.getJSONObject("accessToken")) : null; + refreshToken = obj.has("refreshToken") ? Token.from(obj.getJSONObject("refreshToken")) : null; error = null; } catch (JSONException ex) { - identifier = null; - path = null; - appId = null; accessToken = null; refreshToken = null; error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, ex); } - this.identifier = identifier; - this.path = path; - this.appId = appId; + this.accessToken = accessToken; this.refreshToken = refreshToken; this.error = error; @@ -125,18 +112,6 @@ public ObjectServerError getError() { return error; } - public String getIdentifier() { - return identifier; - } - - public String getPath() { - return path; - } - - public String getAppId() { - return appId; - } - public Token getAccessToken() { return accessToken; } diff --git a/version.txt b/version.txt index c55fc9b3d3..cecea37b79 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.28.0-SNAPSHOT +1.0.0-beta-33.0-SNAPSHOT From da9aeda1a85e0f15b16cca7a9e1b59c8fd25edc8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 13 Sep 2016 22:35:25 -0500 Subject: [PATCH 0036/2110] Disallow changing PK after object created (#3418) Thrown an exception if changing the pk after the object creation. --- CHANGELOG.md | 3 +- .../java/io/realm/annotations/PrimaryKey.java | 7 +- .../java/io/realm/processor/Constants.java | 3 + .../processor/RealmProxyClassGenerator.java | 42 ++++-- .../io/realm/AllTypesRealmProxy.java | 14 +- .../java/io/realm/BulkInsertTests.java | 10 +- .../java/io/realm/CollectionTests.java | 4 +- .../io/realm/DynamicRealmObjectTests.java | 121 +++++++++++++++++- .../OrderedRealmCollectionIteratorTests.java | 2 +- .../java/io/realm/RealmAnnotationTests.java | 17 --- .../java/io/realm/RealmModelTests.java | 10 +- .../java/io/realm/RealmObjectTests.java | 11 ++ .../java/io/realm/RealmQueryTests.java | 8 +- .../androidTest/java/io/realm/RealmTests.java | 8 +- .../java/io/realm/entities/AllJavaTypes.java | 21 ++- .../realm/entities/PrimaryKeyAsBoxedLong.java | 1 + .../io/realm/entities/PrimaryKeyAsByte.java | 3 + .../realm/entities/PrimaryKeyAsInteger.java | 3 + .../io/realm/entities/PrimaryKeyAsLong.java | 3 + .../io/realm/entities/PrimaryKeyAsShort.java | 3 + .../entities/pojo/AllTypesRealmModel.java | 1 + .../src/main/java/io/realm/DynamicRealm.java | 3 +- .../java/io/realm/DynamicRealmObject.java | 23 ++++ 23 files changed, 243 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d51538356..8f800aee67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ * `RealmConfiguration.Builder.assetFile(Context, String)` has been renamed to `RealmConfiguration.Builder.assetFile(String)`. * Object with primary key is now required to define it when the object is created. This means that `Realm.createObject(Class)` and `DynamicRealm.createObject(String)` now throws `RealmException` if they are used to create an object with a primary key field. Use `Realm.createObject(Class, Object)` or `DynamicRealm.createObject(String, Object)` instead. * Importing from JSON without the primary key field defined in the JSON object now throws `IllegalArgumentException`. -* Now `Realm.beginTransaction()`, `Realm.executeTransaction()` and `Realm.waitForChange()` throw `RealmMigrationNeededException` if a remote process introduces an incompatible schema changes (#3409). +* Now `Realm.beginTransaction()`, `Realm.executeTransaction()` and `Realm.waitForChange()` throw `RealmMigrationNeededException` if a remote process introduces incompatible schema changes (#3409). +* The primary key value of an object can no longer be changed after the object was created. Instead a new object must be created and all fields copied over. ### Enhancements diff --git a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java index ddef303a14..b9e263cf4e 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java +++ b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java @@ -25,10 +25,11 @@ * The @PrimaryKey annotation will mark a field as a primary key inside Realm. Only one field in a * RealmObject class can have this annotation, and the field should uniquely identify the object. * Trying to insert an object with an existing primary key will result in an - * {@link io.realm.exceptions.RealmPrimaryKeyConstraintException}. - * + * {@link io.realm.exceptions.RealmPrimaryKeyConstraintException}. Primary key cannot be changed + * after the object created. + *

    * Primary keys also count as having the {@link Index} annotation. - * + *

    * It is allowed to apply this annotation on the following primitive types: byte, short, int, and long. * String, Byte, Short, Integer, and Long are also allowed, and further permitted to have {@code null} * as a primary key value. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java index 592aec97b3..791f81df9c 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java @@ -30,6 +30,9 @@ public class Constants { "throw new IllegalArgumentException(\"Trying to set non-nullable field '%s' to null.\")"; static final String STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON = "throw new IllegalArgumentException(\"JSON object doesn't have the primary key field '%s'.\")"; + static final String STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED = + "throw new io.realm.exceptions.RealmException(\"Primary key field '%s' cannot be changed after object" + + " was created.\")"; static final Map JAVA_TO_REALM_TYPES; static { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index e0101026c7..5b129fed9c 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -271,21 +271,26 @@ private void emitAccessors(JavaWriter writer) throws IOException { writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); // Although setting null value for String and bytes[] can be handled by the JNI code, we still generate the same code here. // Compared with getter, null value won't trigger more native calls in setter which is relatively cheaper. - if (metadata.isNullable(field)) { - writer.beginControlFlow("if (value == null)") - .emitStatement("proxyState.getRow$realm().setNull(%s)", fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); - } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { - // Same reason, throw IAE earlier. - writer - .beginControlFlow("if (value == null)") - .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) - .endControlFlow(); + if (field.equals(metadata.getPrimaryKey())) { + // Primary key is not allowed to be changed after object created. + writer.emitStatement(Constants.STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED, fieldName); + } else { + if (metadata.isNullable(field)) { + writer.beginControlFlow("if (value == null)") + .emitStatement("proxyState.getRow$realm().setNull(%s)", fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow(); + } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { + // Same reason, throw IAE earlier. + writer + .beginControlFlow("if (value == null)") + .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + .endControlFlow(); + } + writer.emitStatement( + "proxyState.getRow$realm().set%s(%s, value)", + realmType, fieldIndexVariableReference(field)); } - writer.emitStatement( - "proxyState.getRow$realm().set%s(%s, value)", - realmType, fieldIndexVariableReference(field)); writer.endMethod(); } else if (Utils.isRealmModel(field)) { /** @@ -1212,6 +1217,11 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { String setter = metadata.getSetter(fieldName); String getter = metadata.getGetter(fieldName); + if (field.equals(metadata.getPrimaryKey())) { + // PK has been set when creating object. + continue; + } + if (Utils.isRealmModel(field)) { writer .emitEmptyLine() @@ -1556,6 +1566,10 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String qualifiedFieldType = field.asType().toString(); + if (field.equals(metadata.getPrimaryKey())) { + // Primary key has already been set when adding new row or finding the existing row. + continue; + } if (Utils.isRealmModel(field)) { RealmJsonTypeHelper.emitFillRealmObjectWithJsonValue( interfaceName, diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 2f04a79cfd..49d3817280 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -119,11 +119,7 @@ public final AllTypesColumnInfo clone() { public void realmSet$columnString(String value) { proxyState.getRealm$realm().checkIfValid(); - if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.columnStringIndex); - return; - } - proxyState.getRow$realm().setString(columnInfo.columnStringIndex, value); + throw new io.realm.exceptions.RealmException("Primary key field 'columnString' cannot be changed after object was created."); } @SuppressWarnings("cast") @@ -436,13 +432,6 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON throw new IllegalArgumentException("JSON object doesn't have the primary key field 'columnString'."); } } - if (json.has("columnString")) { - if (json.isNull("columnString")) { - ((AllTypesRealmProxyInterface) obj).realmSet$columnString(null); - } else { - ((AllTypesRealmProxyInterface) obj).realmSet$columnString((String) json.getString("columnString")); - } - } if (json.has("columnLong")) { if (json.isNull("columnLong")) { throw new IllegalArgumentException("Trying to set non-nullable field 'columnLong' to null."); @@ -658,7 +647,6 @@ public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, } else { some.test.AllTypes realmObject = realm.createObject(some.test.AllTypes.class, ((AllTypesRealmProxyInterface) newObject).realmGet$columnString()); cache.put(newObject, (RealmObjectProxy) realmObject); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnString(((AllTypesRealmProxyInterface) newObject).realmGet$columnString()); ((AllTypesRealmProxyInterface) realmObject).realmSet$columnLong(((AllTypesRealmProxyInterface) newObject).realmGet$columnLong()); ((AllTypesRealmProxyInterface) realmObject).realmSet$columnFloat(((AllTypesRealmProxyInterface) newObject).realmGet$columnFloat()); ((AllTypesRealmProxyInterface) realmObject).realmSet$columnDouble(((AllTypesRealmProxyInterface) newObject).realmGet$columnDouble()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java index b2adb4d7e5..4ae12cc5c7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java @@ -17,7 +17,6 @@ package io.realm; import android.support.test.runner.AndroidJUnit4; -import android.util.Log; import org.junit.After; import org.junit.Before; @@ -88,6 +87,7 @@ public void tearDown() { public void insert() { AllJavaTypes obj = new AllJavaTypes(); obj.setFieldIgnored("cookie"); + obj.setFieldId(42); obj.setFieldLong(42); obj.setFieldString("obj1"); @@ -98,6 +98,7 @@ public void insert() { AllJavaTypes allTypes = new AllJavaTypes(); allTypes.setFieldString("String"); + allTypes.setFieldId(1L); allTypes.setFieldLong(1L); allTypes.setFieldFloat(1F); allTypes.setFieldDouble(1D); @@ -765,6 +766,7 @@ public void insert_listWithNullElement() { @Test public void insertOrUpdate_managedObject() { AllJavaTypes obj = new AllJavaTypes(); + obj.setFieldId(42); obj.setFieldIgnored("cookie"); obj.setFieldLong(42); obj.setFieldString("obj1"); @@ -808,11 +810,11 @@ public void insertOrUpdate_linkingManagedToUnmanagedObject() { realm.insertOrUpdate(unmanagedObject); realm.commitTransaction(); - AllJavaTypes first = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_LONG, 8).findFirst(); + AllJavaTypes first = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_ID, 8).findFirst(); assertNotNull(first); - assertEquals(8, first.getFieldLong(), 0); + assertEquals(8, first.getFieldId(), 0); assertNotNull(first.getFieldObject()); - assertEquals(42, first.getFieldObject().getFieldLong()); + assertEquals(42, first.getFieldObject().getFieldId()); assertEquals(2, realm.where(AllJavaTypes.class).count()); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index 90a33806df..1ef8aad27b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -94,8 +94,8 @@ protected void populateRealm(Realm realm, int objects) { } // Add all items to the RealmList on the first object - AllJavaTypes firstObj = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_LONG, 0).findFirst(); - RealmResults listData = realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + AllJavaTypes firstObj = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_ID, 0).findFirst(); + RealmResults listData = realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_ID, Sort.ASCENDING); RealmList list = firstObj.getFieldList(); for (int i = 0; i < listData.size(); i++) { list.add(listData.get(i)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index a934214a9c..14c9e278bb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -37,6 +37,15 @@ import io.realm.entities.Dog; import io.realm.entities.NullTypes; import io.realm.entities.Owner; +import io.realm.entities.PrimaryKeyAsBoxedByte; +import io.realm.entities.PrimaryKeyAsBoxedInteger; +import io.realm.entities.PrimaryKeyAsBoxedLong; +import io.realm.entities.PrimaryKeyAsBoxedShort; +import io.realm.entities.PrimaryKeyAsByte; +import io.realm.entities.PrimaryKeyAsInteger; +import io.realm.entities.PrimaryKeyAsLong; +import io.realm.entities.PrimaryKeyAsShort; +import io.realm.entities.PrimaryKeyAsString; import io.realm.exceptions.RealmException; import io.realm.rule.TestRealmConfigurationFactory; @@ -68,7 +77,7 @@ public void setUp() { RealmConfiguration realmConfig = configFactory.createConfiguration(); realm = Realm.getInstance(realmConfig); realm.beginTransaction(); - typedObj = realm.createObject(AllJavaTypes.class, 0); + typedObj = realm.createObject(AllJavaTypes.class, 1); typedObj.setFieldString("str"); typedObj.setFieldShort((short) 1); typedObj.setFieldInt(1); @@ -226,6 +235,50 @@ public void typedSetter_wrongUnderlyingTypeThrows() { } } + private void callSetterOnPrimaryKey(String className, DynamicRealmObject object) { + switch (className) { + case PrimaryKeyAsByte.CLASS_NAME: + object.setByte(PrimaryKeyAsByte.FIELD_ID, (byte) 42); + break; + case PrimaryKeyAsShort.CLASS_NAME: + object.setShort(PrimaryKeyAsShort.FIELD_ID, (short) 42); + break; + case PrimaryKeyAsInteger.CLASS_NAME: + object.setInt(PrimaryKeyAsInteger.FIELD_ID, 42); + break; + case PrimaryKeyAsLong.CLASS_NAME: + object.setLong(PrimaryKeyAsLong.FIELD_ID, 42); + break; + case PrimaryKeyAsString.CLASS_NAME: + object.setString(PrimaryKeyAsString.FIELD_PRIMARY_KEY, "42"); + break; + default: + fail(); + } + } + + @Test + public void typedSetter_changePrimaryKeyThrows() { + final String[] primaryKeyClasses = {PrimaryKeyAsByte.CLASS_NAME, PrimaryKeyAsShort.CLASS_NAME, + PrimaryKeyAsInteger.CLASS_NAME, PrimaryKeyAsLong.CLASS_NAME, PrimaryKeyAsString.CLASS_NAME}; + for (String pkClass : primaryKeyClasses) { + dynamicRealm.beginTransaction(); + DynamicRealmObject object; + if (pkClass.equals(PrimaryKeyAsString.CLASS_NAME)) { + object = dynamicRealm.createObject(pkClass, ""); + } else { + object = dynamicRealm.createObject(pkClass, 0); + } + + try { + callSetterOnPrimaryKey(pkClass, object); + fail(); + } catch (IllegalArgumentException ignored) { + } + dynamicRealm.cancelTransaction(); + } + } + // Helper method for calling setters with different field names private void callSetter(SupportedType type, List fieldNames) { for (String fieldName : fieldNames) { @@ -466,6 +519,29 @@ public void typedSetter_null() { } } + @Test + public void setNull_changePrimaryKeyThrows() { + final String[] primaryKeyClasses = {PrimaryKeyAsBoxedByte.CLASS_NAME, PrimaryKeyAsBoxedShort.CLASS_NAME, + PrimaryKeyAsBoxedInteger.CLASS_NAME, PrimaryKeyAsBoxedLong.CLASS_NAME, PrimaryKeyAsString.CLASS_NAME}; + for (String pkClass : primaryKeyClasses) { + dynamicRealm.beginTransaction(); + DynamicRealmObject object; + boolean isStringPK = pkClass.equals(PrimaryKeyAsString.CLASS_NAME); + if (isStringPK) { + object = dynamicRealm.createObject(pkClass, ""); + } else { + object = dynamicRealm.createObject(pkClass, 0); + } + + try { + object.setNull(isStringPK ? PrimaryKeyAsString.FIELD_PRIMARY_KEY : "id"); + fail(); + } catch (IllegalArgumentException ignored) { + } + dynamicRealm.cancelTransaction(); + } + } + @Test public void setObject_differentType() { realm.beginTransaction(); @@ -784,7 +860,7 @@ public void untypedSetter_illegalImplicitConversionThrows() { dObj.set(AllJavaTypes.FIELD_INT, "foo"); break; case LONG: - dObj.set(AllJavaTypes.FIELD_LONG, "foo"); + dObj.set(AllJavaTypes.FIELD_ID, "foo"); break; case FLOAT: dObj.set(AllJavaTypes.FIELD_FLOAT, "foo"); @@ -823,6 +899,38 @@ public void untypedSetter_illegalImplicitConversionThrows() { } } + private void testChangePrimaryKeyThroughUntypedSetter(String value) { + final String[] primaryKeyClasses = {PrimaryKeyAsBoxedByte.CLASS_NAME, PrimaryKeyAsBoxedShort.CLASS_NAME, + PrimaryKeyAsBoxedInteger.CLASS_NAME, PrimaryKeyAsBoxedLong.CLASS_NAME, PrimaryKeyAsString.CLASS_NAME}; + for (String pkClass : primaryKeyClasses) { + dynamicRealm.beginTransaction(); + DynamicRealmObject object; + boolean isStringPK = pkClass.equals(PrimaryKeyAsString.CLASS_NAME); + if (isStringPK) { + object = dynamicRealm.createObject(pkClass, ""); + } else { + object = dynamicRealm.createObject(pkClass, 0); + } + + try { + object.set(isStringPK ? PrimaryKeyAsString.FIELD_PRIMARY_KEY : "id", value); + fail(); + } catch (IllegalArgumentException ignored) { + } + dynamicRealm.cancelTransaction(); + } + } + + @Test + public void untypedSetter_setValue_changePrimaryKeyThrows() { + testChangePrimaryKeyThroughUntypedSetter("42"); + } + + @Test + public void untypedSetter_setNull_changePrimaryKeyThrows() { + testChangePrimaryKeyThroughUntypedSetter(null); + } + @Test public void isNull_nullNotSupportedField() { assertFalse(dObjTyped.isNull(AllJavaTypes.FIELD_INT)); @@ -844,10 +952,10 @@ public void isNull_false() { @Test public void getFieldNames() { - String[] expectedKeys = {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_SHORT, AllJavaTypes.FIELD_INT, - AllJavaTypes.FIELD_LONG, AllJavaTypes.FIELD_BYTE, AllJavaTypes.FIELD_FLOAT, AllJavaTypes.FIELD_DOUBLE, - AllJavaTypes.FIELD_BOOLEAN, AllJavaTypes.FIELD_DATE, AllJavaTypes.FIELD_BINARY, - AllJavaTypes.FIELD_OBJECT, AllJavaTypes.FIELD_LIST}; + String[] expectedKeys = {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID, AllJavaTypes.FIELD_LONG, + AllJavaTypes.FIELD_SHORT, AllJavaTypes.FIELD_INT, AllJavaTypes.FIELD_BYTE, AllJavaTypes.FIELD_FLOAT, + AllJavaTypes.FIELD_DOUBLE, AllJavaTypes.FIELD_BOOLEAN, AllJavaTypes.FIELD_DATE, + AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_OBJECT, AllJavaTypes.FIELD_LIST}; String[] keys = dObjTyped.getFieldNames(); assertArrayEquals(expectedKeys, keys); } @@ -935,6 +1043,7 @@ public void toString_nullValues() { assertTrue(str.contains(NullTypes.FIELD_LIST_NULL + ":RealmList[0]")); } + public void testExceptionMessage() { // test for https://github.com/realm/realm-java/issues/2141 realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index e35a649c82..0643ba9316 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -126,7 +126,7 @@ private void appendElementToCollection(Realm realm, CollectionClass collection) } private void createNewObject() { - Number currentMax = realm.where(AllJavaTypes.class).max(AllJavaTypes.FIELD_LONG); + Number currentMax = realm.where(AllJavaTypes.class).max(AllJavaTypes.FIELD_ID); long nextId = 0; if (currentMax != null) { nextId = currentMax.longValue() + 1; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java index 588ca1fe74..6677482d9c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java @@ -179,23 +179,6 @@ public void primaryKey_checkPrimaryKeyOnCreate() { } } - // It should be allowed to override the primary key value with the same value - @Test - public void primaryKey_defaultStringValue() { - realm.beginTransaction(); - realm.createObject(PrimaryKeyAsString.class, ""); - realm.commitTransaction(); - } - - // It should be allowed to override the primary key value with the same value - @Test - public void primaryKey_defaultLongValue() { - realm.beginTransaction(); - PrimaryKeyAsLong str = realm.createObject(PrimaryKeyAsLong.class, 0); - str.setId(0); - realm.commitTransaction(); - } - @Test public void primaryKey_errorOnInsertingSameObject() { try { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index b4f48e94fc..4c1a04a037 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -45,6 +45,7 @@ import static io.realm.internal.test.ExtraTests.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; // tests API methods when using a model class implementing RealmModel instead @@ -153,6 +154,7 @@ public void execute(Realm realm) { assertEquals(1, realm.where(AllTypesRealmModel.class).count()); AllTypesRealmModel obj = realm.where(AllTypesRealmModel.class).findFirst(); + assertNotNull(obj); assertEquals("Foo", obj.columnString); } @@ -164,6 +166,7 @@ public void createOrUpdateAllFromJson() throws IOException { assertEquals(1, realm.where(AllTypesRealmModel.class).count()); AllTypesRealmModel obj = realm.where(AllTypesRealmModel.class).findFirst(); + assertNotNull(obj); assertEquals("Bar", obj.columnString); assertEquals(2.23F, obj.columnFloat, 0.000000001); assertEquals(2.234D, obj.columnDouble, 0.000000001); @@ -206,12 +209,13 @@ public void dynamicObject() { populateTestRealm(realm, TEST_DATA_SIZE); AllTypesRealmModel typedObj = realm.where(AllTypesRealmModel.class).findFirst(); + assertNotNull(typedObj); DynamicRealmObject dObj = new DynamicRealmObject(typedObj); realm.beginTransaction(); - dObj.setLong(AllTypesRealmModel.FIELD_LONG, 42L); - assertEquals(42, dObj.getLong(AllTypesRealmModel.FIELD_LONG)); - assertEquals(42, typedObj.columnLong); + dObj.setByte(AllTypesRealmModel.FIELD_BYTE, (byte) 42); + assertEquals(42, dObj.getLong(AllTypesRealmModel.FIELD_BYTE)); + assertEquals(42, typedObj.columnByte); dObj.setBlob(AllTypesRealmModel.FIELD_BINARY, new byte[]{1, 2, 3}); Assert.assertArrayEquals(new byte[]{1, 2, 3}, dObj.getBlob(AllTypesRealmModel.FIELD_BINARY)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 59f475902b..0224d6496c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -40,6 +40,7 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; +import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.ConflictingFieldName; @@ -48,6 +49,7 @@ import io.realm.entities.Dog; import io.realm.entities.NullTypes; import io.realm.entities.StringAndInt; +import io.realm.exceptions.RealmException; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.Table; @@ -1572,6 +1574,15 @@ public void setter_nullValueInNullableField() { assertNull(realm.where(NullTypes.class).findFirst().getFieldDateNull()); } + @Test + public void setter_changePrimaryKeyThrows() { + realm.beginTransaction(); + AllJavaTypes allJavaTypes = realm.createObject(AllJavaTypes.class, 42); + thrown.expect(RealmException.class); + allJavaTypes.setFieldId(111); + realm.cancelTransaction(); + } + @Test @RunTestInLooperThread public void addChangeListener_throwOnAddingNullListenerFromLooperThread() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 8bea9267cf..d23e6796c1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2314,7 +2314,7 @@ private void createIsEmptyDataSet(Realm realm) { realm.beginTransaction(); AllJavaTypes emptyValues = new AllJavaTypes(); - emptyValues.setFieldLong(1); + emptyValues.setFieldId(1); emptyValues.setFieldString(""); emptyValues.setFieldBinary(new byte[0]); emptyValues.setFieldObject(emptyValues); @@ -2322,7 +2322,7 @@ private void createIsEmptyDataSet(Realm realm) { realm.copyToRealm(emptyValues); AllJavaTypes nonEmpty = new AllJavaTypes(); - nonEmpty.setFieldLong(2); + nonEmpty.setFieldId(2); nonEmpty.setFieldString("Foo"); nonEmpty.setFieldBinary(new byte[]{1, 2, 3}); nonEmpty.setFieldObject(nonEmpty); @@ -2437,7 +2437,7 @@ private void createIsNotEmptyDataSet(Realm realm) { realm.beginTransaction(); AllJavaTypes emptyValues = new AllJavaTypes(); - emptyValues.setFieldLong(1); + emptyValues.setFieldId(1); emptyValues.setFieldString(""); emptyValues.setFieldBinary(new byte[0]); emptyValues.setFieldObject(emptyValues); @@ -2445,7 +2445,7 @@ private void createIsNotEmptyDataSet(Realm realm) { realm.copyToRealm(emptyValues); AllJavaTypes notEmpty = new AllJavaTypes(); - notEmpty.setFieldLong(2); + notEmpty.setFieldId(2); notEmpty.setFieldString("Foo"); notEmpty.setFieldBinary(new byte[]{1, 2, 3}); notEmpty.setFieldObject(notEmpty); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index b8e431ea7e..0a4220768f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -526,13 +526,15 @@ public Boolean call() throws Exception { realm.createAllFromJson(AllTypes.class, "[{}]"); break; case METHOD_CREATE_OR_UPDATE_ALL_FROM_JSON: - realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, "[{\"columnLong\":1}]"); + realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, "[{\"columnLong\":1," + + " \"columnBoolean\": true}]"); break; case METHOD_CREATE_FROM_JSON: realm.createObjectFromJson(AllTypes.class, "{}"); break; case METHOD_CREATE_OR_UPDATE_FROM_JSON: - realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, "{\"columnLong\":1}"); + realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, "{\"columnLong\":1," + + " \"columnBoolean\": true}"); break; case METHOD_INSERT_COLLECTION: realm.insert(Arrays.asList(new AllTypes(), new AllTypes())); @@ -2099,7 +2101,7 @@ public void createObjectWithPrimaryKey() { realm.beginTransaction(); AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 42); assertEquals(1, realm.where(AllJavaTypes.class).count()); - assertEquals(42, obj.getFieldLong()); + assertEquals(42, obj.getFieldId()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java index 2a5308d913..515eea403c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java @@ -32,6 +32,7 @@ public class AllJavaTypes extends RealmObject { public static String FIELD_SHORT = "fieldShort"; public static String FIELD_INT = "fieldInt"; public static String FIELD_LONG = "fieldLong"; + public static String FIELD_ID = "fieldId"; public static String FIELD_BYTE = "fieldByte"; public static String FIELD_FLOAT = "fieldFloat"; public static String FIELD_DOUBLE = "fieldDouble"; @@ -46,9 +47,10 @@ public class AllJavaTypes extends RealmObject { @Ignore private String fieldIgnored; @Index private String fieldString; + @PrimaryKey private long fieldId; + private long fieldLong; private short fieldShort; private int fieldInt; - @PrimaryKey private long fieldLong; private byte fieldByte; private float fieldFloat; private double fieldDouble; @@ -63,6 +65,7 @@ public AllJavaTypes() { } public AllJavaTypes(long fieldLong) { + this.fieldId = fieldLong; this.fieldLong = fieldLong; } @@ -90,6 +93,14 @@ public void setFieldShort(short fieldShort) { this.fieldShort = fieldShort; } + public long getFieldLong() { + return fieldLong; + } + + public void setFieldLong(long fieldLong) { + this.fieldLong = fieldLong; + } + public int getFieldInt() { return fieldInt; } @@ -98,12 +109,12 @@ public void setFieldInt(int fieldInt) { this.fieldInt = fieldInt; } - public long getFieldLong() { - return fieldLong; + public long getFieldId() { + return fieldId; } - public void setFieldLong(long fieldLong) { - this.fieldLong = fieldLong; + public void setFieldId(long fieldId) { + this.fieldId = fieldId; } public byte getFieldByte() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedLong.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedLong.java index 6ad4929040..839681ba72 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedLong.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedLong.java @@ -24,6 +24,7 @@ public class PrimaryKeyAsBoxedLong extends RealmObject implements NullPrimaryKey public static final String CLASS_NAME = "PrimaryKeyAsBoxedLong"; public static final String FIELD_PRIMARY_KEY = "id"; + public static final String FIELD_NAME = "name"; @PrimaryKey private Long id; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsByte.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsByte.java index a7474a743c..95eeb8aa88 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsByte.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsByte.java @@ -21,6 +21,9 @@ public class PrimaryKeyAsByte extends RealmObject { + public static final String CLASS_NAME = "PrimaryKeyAsByte"; + public static final String FIELD_ID = "id"; + @PrimaryKey private byte id; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java index 4b12691d84..c54aa7b6fa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java @@ -21,6 +21,9 @@ public class PrimaryKeyAsInteger extends RealmObject { + public static final String CLASS_NAME = "PrimaryKeyAsInteger"; + public static final String FIELD_ID = "id"; + @PrimaryKey private int id; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsLong.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsLong.java index 9b803899ad..19fd734383 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsLong.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsLong.java @@ -21,6 +21,9 @@ public class PrimaryKeyAsLong extends RealmObject { + public static final String CLASS_NAME = "PrimaryKeyAsLong"; + public static final String FIELD_ID = "id"; + @PrimaryKey private long id; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsShort.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsShort.java index b2a28c2213..092d256dd4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsShort.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsShort.java @@ -21,6 +21,9 @@ public class PrimaryKeyAsShort extends RealmObject { + public static final String CLASS_NAME = "PrimaryKeyAsShort"; + public static final String FIELD_ID = "id"; + @PrimaryKey private short id; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java b/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java index 6e87305827..4998ef8bb7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java @@ -29,6 +29,7 @@ public class AllTypesRealmModel implements RealmModel { public static final String CLASS_NAME = "AllTypesRealmModel"; public static final String FIELD_LONG = "columnLong"; + public static final String FIELD_BYTE = "columnByte"; public static final String FIELD_DOUBLE = "columnDouble"; public static final String FIELD_STRING = "columnString"; public static final String FIELD_BINARY = "columnBinary"; diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 3445f9cac2..8e3f35b77f 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -100,8 +100,7 @@ public DynamicRealmObject createObject(String className) { public DynamicRealmObject createObject(String className, Object primaryKeyValue) { Table table = schema.getTable(className); long index = table.addEmptyRowWithPrimaryKey(primaryKeyValue); - DynamicRealmObject dynamicRealmObject = new DynamicRealmObject(this, table.getCheckedRow(index)); - return dynamicRealmObject; + return new DynamicRealmObject(this, table.getCheckedRow(index)); } /** diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index ff8cb5a461..fb0c8a340a 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.Locale; +import io.realm.exceptions.RealmException; import io.realm.internal.CheckedRow; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; @@ -363,6 +364,7 @@ public String[] getFieldNames() { * @throws IllegalArgumentException if field name doesn't exist or if the input value cannot be converted * to the appropriate input type. * @throws NumberFormatException if a String based number cannot be converted properly. + * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ @SuppressWarnings("unchecked") public void set(String fieldName, Object value) { @@ -445,8 +447,10 @@ public void setBoolean(String fieldName, boolean value) { * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't an integer field. + * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setShort(String fieldName, short value) { + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); } @@ -457,8 +461,10 @@ public void setShort(String fieldName, short value) { * @param fieldName field name to update. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't an integer field. + * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setInt(String fieldName, int value) { + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); } @@ -469,8 +475,10 @@ public void setInt(String fieldName, int value) { * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't an integer field. + * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setLong(String fieldName, long value) { + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); } @@ -481,8 +489,10 @@ public void setLong(String fieldName, long value) { * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't an integer field. + * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setByte(String fieldName, byte value) { + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); } @@ -517,8 +527,10 @@ public void setDouble(String fieldName, double value) { * @param fieldName field name. * @param value value to insert. * @throws IllegalArgumentException if field name doesn't exist or field isn't a String field. + * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setString(String fieldName, String value) { + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setString(columnIndex, value); } @@ -632,6 +644,7 @@ public void setList(String fieldName, RealmList list) { * * @param fieldName field name. * @throws IllegalArgumentException if field name doesn't exist, or the field isn't nullable. + * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setNull(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); @@ -639,6 +652,7 @@ public void setNull(String fieldName) { if (type == RealmFieldType.OBJECT) { proxyState.getRow$realm().nullifyLink(columnIndex); } else { + checkIsPrimaryKey(fieldName); proxyState.getRow$realm().setNull(columnIndex); } } @@ -794,4 +808,13 @@ public String toString() { public ProxyState realmGet$proxyState() { return proxyState; } + + // Checks if the given field is primary key field. Throws if it is a PK field. + private void checkIsPrimaryKey(String fieldName) { + RealmObjectSchema objectSchema = proxyState.getRealm$realm().getSchema().getSchemaForClass(getType()); + if (objectSchema.hasPrimaryKey() && objectSchema.getPrimaryKey().equals(fieldName)) { + throw new IllegalArgumentException(String.format( + "Primary key field '%s' cannot be changed after object was created.", fieldName)); + } + } } From 5740ad865d5b9d84b7f8710df2aa44de08069877 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 14 Sep 2016 15:56:12 +0900 Subject: [PATCH 0037/2110] use ObjectStore::set_schema_version() to change the schema version instead of Java local logic (#3424) --- .../main/cpp/io_realm_internal_SharedRealm.cpp | 18 ++++++++++++++++++ realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/RealmSchema.java | 2 +- .../java/io/realm/internal/SharedRealm.java | 9 ++------- .../src/main/java/io/realm/internal/Table.java | 7 +++---- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 6a753754aa..90d9b6b0eb 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -148,6 +148,24 @@ Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong s return -1; } +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv *env, jclass, jlong shared_realm_ptr, jlong version) +{ + TR_ENTER_PTR(env, shared_realm_ptr) + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + if (!shared_realm->is_in_transaction()) { + std::ostringstream ss; + ss << "Cannot set schema version when the realm is not in transaction."; + ThrowException(env, IllegalState, ss.str()); + return; + } + + ObjectStore::set_schema_version(shared_realm->read_group(), static_cast(version)); + } CATCH_STD() +} + JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv *env, jclass, jlong shared_realm_ptr) { diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 9ea6895190..4100b8fc40 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 9ea6895190ea38951d4498f4e124e806efca62b0 +Subproject commit 4100b8fc407176cdf09e6f394bd111595a6412aa diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 5033abc5b4..0780595495 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -87,7 +87,7 @@ public Set getAll() { Set schemas = new LinkedHashSet<>(tableCount); for (int i = 0; i < tableCount; i++) { String tableName = realm.sharedRealm.getTableName(i); - if (Table.isMetaTable(tableName)) { + if (!Table.isModelTable(tableName)) { continue; } Table table = realm.sharedRealm.getTable(tableName); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 728bcc53e4..8fa0cf6f22 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -192,13 +192,7 @@ public boolean isInTransaction() { } public void setSchemaVersion(long schemaVersion) { - // FIXME migrate to ObjectStore - Table metadataTable = getTable(Table.METADATA_TABLE_NAME); - if (metadataTable.getColumnCount() == 0) { - metadataTable.addColumn(RealmFieldType.INTEGER, "version"); - metadataTable.addEmptyRow(); - } - metadataTable.setLong(0, 0, schemaVersion); + nativeSetVersion(nativePtr, schemaVersion); } public long getSchemaVersion() { @@ -336,6 +330,7 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte private static native void nativeCancelTransaction(long nativeSharedRealmPtr); private static native boolean nativeIsInTransaction(long nativeSharedRealmPtr); private static native long nativeGetVersion(long nativeSharedRealmPtr); + private static native void nativeSetVersion(long nativeSharedRealmPtr, long version); private static native long nativeReadGroup(long nativeSharedRealmPtr); private static native boolean nativeIsEmpty(long nativeSharedRealmPtr); private static native void nativeRefresh(long nativeSharedRealmPtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index b831ef5ea5..db2ba7db16 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -37,7 +37,6 @@ public class Table implements TableOrView, TableSchema { public static final String STRING_DEFAULT_VALUE = ""; @SuppressWarnings("WeakerAccess") public static final long INTEGER_DEFAULT_VALUE = 0; - public static final String METADATA_TABLE_NAME = "metadata"; public static final boolean NULLABLE = true; public static final boolean NOT_NULLABLE = false; @@ -1277,10 +1276,10 @@ public boolean hasSameSchema(Table table) { } /** - * Checks if a given table name is a meta-table, i.e. a table used by Realm to track its internal state. + * Checks if a given table name is a name for a model table. */ - public static boolean isMetaTable(String tableName) { - return (tableName.equals(METADATA_TABLE_NAME) || tableName.equals(PRIMARY_KEY_TABLE_NAME)); + public static boolean isModelTable(String tableName) { + return tableName.startsWith(TABLE_PREFIX); } /** From 074fbd78d08f116197aca38465e6f6d48967d915 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 14 Sep 2016 11:49:50 +0200 Subject: [PATCH 0038/2110] Fix threading error when deleting global refs. (#93) --- .../src/main/cpp/io_realm_objectserver_Session.cpp | 1 + realm/realm-library/src/main/cpp/objectserver_shared.hpp | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp b/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp index 6821fc73c1..efabb2d315 100644 --- a/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp @@ -80,6 +80,7 @@ JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeUnbind { TR_ENTER(env) JniSession* session = SS(sessionPointer); + session->close(env); delete session; // TODO Can we avoid killing the session here? } diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index b15208b799..9e44aa5bad 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -66,9 +66,14 @@ class JniSession { return m_sync_session; } + // Call this just before destroying the object to release JNI ressources. + inline void close(JNIEnv* env) + { + env->DeleteGlobalRef(m_global_obj_ref); + } + ~JniSession() { - sync_client_env->DeleteGlobalRef(m_global_obj_ref); delete m_sync_session; } From ec254346484cc6851cfe71c61b1f17285b2bea00 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 14 Sep 2016 05:00:45 -0500 Subject: [PATCH 0039/2110] Error in exector task and fix JSON field in auth (#94) --- .../main/java/io/realm/objectserver/User.java | 2 ++ .../network/AuthenticateResponse.java | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/User.java b/realm/realm-library/src/main/java/io/realm/objectserver/User.java index e6445edf6e..230998dc45 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/User.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/User.java @@ -147,6 +147,8 @@ public void run() { } } catch (IOException e) { postError(new ObjectServerError(ErrorCode.IO_EXCEPTION, e)); + } catch (Throwable e) { + postError(new ObjectServerError(ErrorCode.UNKNOWN, e)); } } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java index d2695a828b..0416786a63 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java @@ -32,6 +32,9 @@ */ public class AuthenticateResponse { + private static final String JSON_FIELD_ACCESS_TOKEN = "access_token"; + private static final String JSON_FIELD_REFRESH_TOKEN = "refresh_token"; + private final ObjectServerError error; private final Token accessToken; private final Token refreshToken; @@ -40,7 +43,7 @@ public class AuthenticateResponse { * Helper method for creating the proper Authenticate response. This method will set the appropriate error * depending on any HTTP response codes or IO errors. */ - public static AuthenticateResponse createFrom(Response response) { + static AuthenticateResponse createFrom(Response response) { String serverResponse; try { serverResponse = response.body().string(); @@ -69,33 +72,33 @@ public static AuthenticateResponse createFrom(Response response) { } /** - * Create a unsuccessful authentication response. This should only happen in case of network / IO problems. + * Creates a unsuccessful authentication response. This should only happen in case of network / IO problems. */ - public AuthenticateResponse(ObjectServerError error) { + AuthenticateResponse(ObjectServerError error) { this.error = error; this.accessToken = null; this.refreshToken = null; } /** - * Parse a valid (200) server response. It might still result in a unsuccessful authentication attempt, if the + * Parses a valid (200) server response. It might still result in a unsuccessful authentication attempt, if the * JSON response could not be parsed correctly. */ - public AuthenticateResponse(String serverResponse) { + private AuthenticateResponse(String serverResponse) { ObjectServerError error; - String identifier; - String path; - String appId; Token accessToken; Token refreshToken; try { JSONObject obj = new JSONObject(serverResponse); - accessToken = obj.has("accessToken") ? Token.from(obj.getJSONObject("accessToken")) : null; - refreshToken = obj.has("refreshToken") ? Token.from(obj.getJSONObject("refreshToken")) : null; + accessToken = obj.has(JSON_FIELD_ACCESS_TOKEN) ? + Token.from(obj.getJSONObject(JSON_FIELD_ACCESS_TOKEN)) : null; + refreshToken = obj.has(JSON_FIELD_REFRESH_TOKEN) ? + Token.from(obj.getJSONObject(JSON_FIELD_REFRESH_TOKEN)) : null; error = null; } catch (JSONException ex) { accessToken = null; refreshToken = null; + //noinspection ThrowableInstanceNeverThrown error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, ex); } From e5ca398799f509bfcc8106437436a85be1f2fb72 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 14 Sep 2016 19:28:30 +0900 Subject: [PATCH 0040/2110] added thread check to Realm#createObject(Class,Object) (#3436) * added thread check to Realm#createObject(Class,Object) * added test --- .../src/androidTest/java/io/realm/RealmTests.java | 4 ++++ realm/realm-library/src/main/java/io/realm/Realm.java | 1 + 2 files changed, 5 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index e15d495a79..a0e7282415 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -521,6 +521,7 @@ private enum Method { METHOD_DELETE_TYPE, METHOD_DELETE_ALL, METHOD_CREATE_OBJECT, + METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY, METHOD_COPY_TO_REALM, METHOD_COPY_TO_REALM_OR_UPDATE, METHOD_CREATE_ALL_FROM_JSON, @@ -563,6 +564,9 @@ public Boolean call() throws Exception { case METHOD_CREATE_OBJECT: realm.createObject(AllTypes.class); break; + case METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY: + realm.createObject(AllJavaTypes.class, 1L); + break; case METHOD_COPY_TO_REALM: realm.copyToRealm(new AllTypes()); break; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index b0bca8a249..55565157a4 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -697,6 +697,7 @@ public E createObject(Class clazz) { * expected value. */ public E createObject(Class clazz, Object primaryKeyValue) { + checkIfValid(); Table table = schema.getTable(clazz); long rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue); return get(clazz, rowIndex); From 35e1a5d90c93e50e19680a9dfed4be55be07537d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 14 Sep 2016 12:45:24 +0200 Subject: [PATCH 0041/2110] Remove local user from the API (#86) Removes the local user. We are not going to ship with that concept. It will be added later. --- .../objectserver/SyncConfigurationTests.java | 17 ++++++++--------- .../main/java/io/realm/objectserver/User.java | 8 -------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java index df6142648c..eb5fc17f8f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java @@ -31,17 +31,13 @@ import org.junit.runner.RunWith; import java.io.File; -import java.net.URL; import java.util.Locale; import java.util.UUID; import io.realm.DynamicRealm; -import io.realm.Realm; import io.realm.RealmMigration; -import io.realm.objectserver.android.SharedPrefsUserStore; import io.realm.objectserver.internal.Token; import io.realm.rule.RunInLooperThread; -import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; @@ -94,7 +90,7 @@ public void user_invalidUserThrows() { @Test public void serverUrl_setsFolderAndFileName() { - User user = User.createLocal(); + User user = createTestUser(); String[][] validUrls = { // , , { "realm://objectserver.realm.io/~/default", "realm-object-server/" + user.getIdentifier(), "default" }, @@ -167,7 +163,7 @@ public void userAndServerUrlRequired() { public void errorHandler() { SyncConfiguration.Builder builder; builder = new SyncConfiguration.Builder(context) - .user(User.createLocal()) + .user(createTestUser()) .serverUrl("realm://objectserver.realm.io/default"); Session.ErrorHandler errorHandler = new Session.ErrorHandler() { @@ -194,7 +190,7 @@ public void onError(Session session, ObjectServerError error) { // Create configuration using the default handler SyncConfiguration config = new SyncConfiguration.Builder(context) - .user(User.createLocal()) + .user(createTestUser()) .serverUrl("realm://objectserver.realm.io/default") .build(); assertEquals(errorHandler, config.getErrorHandler()); @@ -206,7 +202,7 @@ public void onError(Session session, ObjectServerError error) { public void errorHandler_nullThrows() { SyncConfiguration.Builder builder; builder = new SyncConfiguration.Builder(context) - .user(User.createLocal()) + .user(createTestUser()) .serverUrl("realm://objectserver.realm.io/default"); try { @@ -219,7 +215,7 @@ public void errorHandler_nullThrows() { public void migration_alwaysThrows() { SyncConfiguration.Builder builder; builder = new SyncConfiguration.Builder(context) - .user(User.createLocal()) + .user(createTestUser()) .serverUrl("realm://objectserver.realm.io/default"); try { @@ -237,6 +233,9 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { } catch (IllegalArgumentException ignore) { } } + private User createTestUser() { + return createTestUser(Long.MAX_VALUE); + } private User createTestUser(long expires) { JSONObject obj = new JSONObject(); diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/User.java b/realm/realm-library/src/main/java/io/realm/objectserver/User.java index 230998dc45..a760ca690e 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/User.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/User.java @@ -59,14 +59,6 @@ public class User { private URL authentificationUrl; private Map accessTokens = new HashMap(); - /** - * Creates a User only known to this device. - * @return - */ - public static User createLocal() { - Token token = new Token(UUID.randomUUID().toString(), null, null, Long.MAX_VALUE, Token.Permission.values()); - return new User(UUID.randomUUID().toString(), token, null); - } /** * Load a user that has previously been serialized using {@link #toJson()}. * From 80a3b74f6353e1234a07af2ad7829b888848a5c1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 14 Sep 2016 14:31:13 +0200 Subject: [PATCH 0042/2110] Object Server Example App (#80) --- examples/objectServerExample/build.gradle | 36 ++++ examples/objectServerExample/lint.xml | 10 ++ .../src/main/AndroidManifest.xml | 24 +++ .../objectserver/CounterActivity.java | 163 ++++++++++++++++++ .../examples/objectserver/LoginActivity.java | 144 ++++++++++++++++ .../examples/objectserver/MyApplication.java | 37 ++++ .../objectserver/model/CRDTCounter.java | 50 ++++++ .../objectserver/model/CounterOperation.java | 27 +++ .../ic_exit_to_app_white_24dp.png | Bin 0 -> 364 bytes .../ic_exit_to_app_white_24dp.png | Bin 0 -> 444 bytes .../src/main/res/drawable/button_counter.xml | 6 + .../src/main/res/drawable/logo.png | Bin 0 -> 16078 bytes .../src/main/res/layout/activity_counter.xml | 37 ++++ .../src/main/res/layout/activity_login.xml | 67 +++++++ .../src/main/res/menu/menu_counter.xml | 11 ++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4906 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2968 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7076 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11165 bytes .../src/main/res/values-w820dp/dimens.xml | 6 + .../src/main/res/values/dimens.xml | 5 + .../src/main/res/values/realm_colors.xml | 23 +++ .../src/main/res/values/strings.xml | 4 + .../src/main/res/values/styles.xml | 11 ++ examples/settings.gradle | 1 + 25 files changed, 662 insertions(+) create mode 100644 examples/objectServerExample/build.gradle create mode 100644 examples/objectServerExample/lint.xml create mode 100644 examples/objectServerExample/src/main/AndroidManifest.xml create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CounterOperation.java create mode 100644 examples/objectServerExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png create mode 100644 examples/objectServerExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png create mode 100644 examples/objectServerExample/src/main/res/drawable/button_counter.xml create mode 100755 examples/objectServerExample/src/main/res/drawable/logo.png create mode 100644 examples/objectServerExample/src/main/res/layout/activity_counter.xml create mode 100644 examples/objectServerExample/src/main/res/layout/activity_login.xml create mode 100644 examples/objectServerExample/src/main/res/menu/menu_counter.xml create mode 100755 examples/objectServerExample/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100755 examples/objectServerExample/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100755 examples/objectServerExample/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100755 examples/objectServerExample/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 examples/objectServerExample/src/main/res/values-w820dp/dimens.xml create mode 100644 examples/objectServerExample/src/main/res/values/dimens.xml create mode 100644 examples/objectServerExample/src/main/res/values/realm_colors.xml create mode 100644 examples/objectServerExample/src/main/res/values/strings.xml create mode 100644 examples/objectServerExample/src/main/res/values/styles.xml diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle new file mode 100644 index 0000000000..81259e4c98 --- /dev/null +++ b/examples/objectServerExample/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'com.android.application' +apply plugin: 'android-command' +apply plugin: 'realm-android' + +android { + compileSdkVersion rootProject.sdkVersion + buildToolsVersion rootProject.buildTools + + defaultConfig { + applicationId 'io.realm.examples.objectserver' + targetSdkVersion rootProject.sdkVersion + minSdkVersion 15 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + } + debug { + minifyEnabled false + } + } + + command { + events 2000 + } +} + +dependencies { + compile 'com.android.support:support-v4:24.2.0' + compile 'com.android.support:design:24.2.0' + compile 'com.jakewharton:butterknife:8.3.0' + apt 'com.jakewharton:butterknife-compiler:8.3.0' +} diff --git a/examples/objectServerExample/lint.xml b/examples/objectServerExample/lint.xml new file mode 100644 index 0000000000..6a9810cdcb --- /dev/null +++ b/examples/objectServerExample/lint.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/objectServerExample/src/main/AndroidManifest.xml b/examples/objectServerExample/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..45e5d52c1c --- /dev/null +++ b/examples/objectServerExample/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java new file mode 100644 index 0000000000..cc1595f7cf --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -0,0 +1,163 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.examples.objectserver; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import io.realm.Realm; +import io.realm.RealmChangeListener; +import io.realm.RealmResults; +import io.realm.examples.objectserver.model.CRDTCounter; +import io.realm.examples.objectserver.model.CounterOperation; +import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.User; + +public class CounterActivity extends AppCompatActivity { + + private Realm realm; + private RealmResults counter; + + @BindView(R.id.text_counter) TextView counterView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_counter); + ButterKnife.bind(this); + + // Check if we have a valid user, otherwise redirect to login + User user = MyApplication.CURRENT_USER; + if (user == null) { + gotoLoginActivity(); + } + } + + @Override + protected void onStart() { + super.onStart(); + if (MyApplication.CURRENT_USER != null) { + // Create a RealmConfiguration for our user + SyncConfiguration config = new SyncConfiguration.Builder(this) + .initialData(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(CRDTCounter.class, 1); + } + }) + .user(MyApplication.CURRENT_USER) + .serverUrl("realm://" + MyApplication.OBJECT_SERVER_IP + "/~/default") + .build(); + + // This will automatically sync all changes in the background for as long as the Realm is open + realm = Realm.getInstance(config); + + // FIXME Looks like PrimaryKey and lists are not working correctly yet +// counter = realm.where(CRDTCounter.class).findFirstAsync(); +// counter.addChangeListener(new RealmChangeListener() { +// @Override +// public void onChange(CRDTCounter counter) { +// if (counter.isValid()) { +// counterView.setText(String.format(Locale.US, "%d", counter.getCount())); +// } else { +// counterView.setText("-"); +// } +// } +// }); + + counter = realm.where(CounterOperation.class).findAllAsync(); + counter.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults result) { + // FIXME Why isn't this triggered when the DB is opened? + Number sum = result.sum("adjustment"); + if (sum != null) { + counterView.setText(Long.toString(sum.longValue())); + } else { + counterView.setText("0"); + } + } + }); + counterView.setText("0"); + } + } + + @Override + protected void onStop() { + super.onStop(); + closeRealm(); + } + + private void closeRealm() { + if (realm != null && !realm.isClosed()) { + realm.close(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_counter, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case R.id.action_logout: + MyApplication.CURRENT_USER.logout(); + closeRealm(); + gotoLoginActivity(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + @OnClick(R.id.upper) + public void incrementCounter() { + adjustCounter(new CounterOperation(1)); + } + + @OnClick(R.id.lower) + public void decrementCounter() { + adjustCounter(new CounterOperation(-1)); + } + + private void adjustCounter(final CounterOperation ops) { + // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI + // thread is HIGHLY discouraged as it might block longer than intended. Only use async transactions. + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.copyToRealm(ops); + } + }); + } + + private void gotoLoginActivity() { + Intent intent = new Intent(this, LoginActivity.class); + startActivity(intent); + } +} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java new file mode 100644 index 0000000000..adbc6fa529 --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -0,0 +1,144 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.examples.objectserver; + +import android.app.ProgressDialog; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.realm.objectserver.Credentials; +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.User; +import io.realm.objectserver.UserStore; + +public class LoginActivity extends AppCompatActivity { + + private UserStore userStore = MyApplication.USER_STORE; + + @BindView(R.id.input_username) EditText username; + @BindView(R.id.input_password) EditText password; + @BindView(R.id.button_login) Button loginButton; + @BindView(R.id.button_create) Button createUserButton; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + ButterKnife.bind(this); + loginButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + login(false); + } + }); + createUserButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + login(true); + } + }); + } + + public void login(boolean createUser) { + if (!validate()) { + onLoginFailed("Invalid username or password"); + return; + } + + createUserButton.setEnabled(false); + loginButton.setEnabled(false); + + final ProgressDialog progressDialog = new ProgressDialog(LoginActivity.this); + progressDialog.setIndeterminate(true); + progressDialog.setMessage("Authenticating..."); + progressDialog.show(); + + String username = this.username.getText().toString(); + String password = this.password.getText().toString(); + + Credentials creds = Credentials.fromUsernamePassword(username, password, createUser); + String authUrl = "http://" + MyApplication.OBJECT_SERVER_IP + ":8080/auth"; + User.Callback callback = new User.Callback() { + @Override + public void onSuccess(User user) { + progressDialog.dismiss(); + userStore.saveAsync(MyApplication.APP_USER_KEY, user); // TODO Use Async + MyApplication.CURRENT_USER = user; + onLoginSuccess(); + } + + @Override + public void onError(ObjectServerError error) { + progressDialog.dismiss(); + String errorMsg; + switch (error.errorCode()) { + case UNKNOWN_ACCOUNT: + errorMsg = "Account does not exists."; + break; + case INVALID_CREDENTIALS: + errorMsg = "User name and password does not match"; + break; + default: + errorMsg = error.toString(); + } + onLoginFailed(errorMsg); + } + }; + + User.loginAsync(creds, authUrl, callback); + } + + @Override + public void onBackPressed() { + // Disable going back to the MainActivity + moveTaskToBack(true); + } + + public void onLoginSuccess() { + loginButton.setEnabled(true); + createUserButton.setEnabled(true); + finish(); + } + + public void onLoginFailed(String errorMsg) { + loginButton.setEnabled(true); + createUserButton.setEnabled(true); + Toast.makeText(getBaseContext(), errorMsg, Toast.LENGTH_LONG).show(); + } + + public boolean validate() { + boolean valid = true; + String email = username.getText().toString(); + String password = this.password.getText().toString(); + + if (email.isEmpty()) { + valid = false; + } + + if (password.isEmpty()) { + valid = false; + } + + return valid; + } +} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java new file mode 100644 index 0000000000..008ca06030 --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.examples.objectserver; + +import android.app.Application; + +import io.realm.objectserver.User; +import io.realm.objectserver.UserStore; +import io.realm.objectserver.android.SharedPrefsUserStore; + +public class MyApplication extends Application { + + public static final String OBJECT_SERVER_IP = "192.168.104.22"; + public static final String APP_USER_KEY = "defaultAppUser"; + public static UserStore USER_STORE; + public static User CURRENT_USER = null; + + @Override + public void onCreate() { + super.onCreate(); + USER_STORE = new SharedPrefsUserStore(this); + } +} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java new file mode 100644 index 0000000000..93096e3ac6 --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.examples.objectserver.model; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + +/** + * Counter class that is eventually consistent. Two devices can simultaneous increment this and eventually reach + * the same value. + * + * @see Conflict Free Replicated Data Structures + */ +public class CRDTCounter extends RealmObject { + + @PrimaryKey + private long id; + private RealmList operations; + + public CRDTCounter() { + // Required by Realm + } + + public CRDTCounter(long id) { + this.id = id; + } + + public long getCount() { + return operations.where().sum("adjustment").longValue(); + } + + public void add(long val) { + operations.add(new CounterOperation(val)); + } +} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CounterOperation.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CounterOperation.java new file mode 100644 index 0000000000..53342f648d --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CounterOperation.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.examples.objectserver.model; + +import io.realm.RealmObject; + +public class CounterOperation extends RealmObject { + public long adjustment; + public CounterOperation() {}; + public CounterOperation(long adjustment) { + this.adjustment = adjustment; + } +} diff --git a/examples/objectServerExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png b/examples/objectServerExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c04fe6e0e39ff126795317e64eeb057ec5f628b5 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXoKNz-a90;uuoF`1Yow*Wm;a*N5g_ zG0jR#*7>yvl`e>gzB?;8+OKMqZbdnPCS+0Q*8dhG2_$nP9Lq)n|fwV zOW!whv8{egkZ`-0%q>mX;_HQrnzF29_6BDz5DQ<+cI#2r^2WLX(~Jd$hKG--YB-(} zy7Iv#m@mVT|BFd#gIEnChYI(LH~q>Jd=?ZlG^XrHF}Lz$_r1N=VN+9+bz=%Et2T(p z1`*OAqF?Fd{RNzyD$E`Vk&ad?YXagn&0BV3{$BYn0oQs9gRd=4j=gsH>%KLQk9$3k z=-Y7V;Tis28gD1uzi>OgnPt*<*FdFS%b)3ozW|-Wd29N&%!i`k(Yz(StCzA_JrZ`$ z5Ba5jj-UDYi~vW+v!d2)&oUBDPwEPA_ysrZgQu&X%Q~lo FCIHRMl<)um literal 0 HcmV?d00001 diff --git a/examples/objectServerExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png b/examples/objectServerExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..27a9d7b05ab3990cc2476d892407323546b4b1bf GIT binary patch literal 444 zcmV;t0Ymei(==F%5gteOz$~KpL5PR=bU3n!-bZqYdN!{rW&5%kLT>UZ5<7>v4A;mD0E;p4zNe1-59_UojU4# zK!Z}-e82^zPWgZqrLOsae$~X&&j14qFu;J)0EtvG;2D5aDqzKTsYJkvpQX|OFZ4?# z0iO7+R1V;d_W(fv5Fi8qfe8=<0C7SbAV3fR1PB6v06_o{ASeLhgg8Ky5C=?ADjfi+ zbO5B%0gw#vBNSQRe@6d)7887t39K&)Sn#3zV=l110wA!y1|YD$K48IT$^X>f2i6xj z*b1yK00^uv0(dO_aJM4qAFt;T^e;FD38~aez7$yB*Q~ziHoyP_3^1Vh7hlgPb<79s zP->kIsOeCt<6#J({{sGoY7Ah;0fk=qmjUPz8ai%S$ELr2?zvFSmJ?T|?#hvM@7d1> m=#K#g7+`<_1{h#KV7>r3kQ{Z*xKsuJ0000 + + + + + diff --git a/examples/objectServerExample/src/main/res/drawable/logo.png b/examples/objectServerExample/src/main/res/drawable/logo.png new file mode 100755 index 0000000000000000000000000000000000000000..91826a7567c331b45474d439ca1b15fdd6f86feb GIT binary patch literal 16078 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 + + + + + + + + + + + + + diff --git a/examples/objectServerExample/src/main/res/layout/activity_login.xml b/examples/objectServerExample/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000000..142ad539e1 --- /dev/null +++ b/examples/objectServerExample/src/main/res/layout/activity_login.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/objectServerExample/src/main/res/menu/menu_counter.xml b/examples/objectServerExample/src/main/res/menu/menu_counter.xml new file mode 100644 index 0000000000..858fd2e7e8 --- /dev/null +++ b/examples/objectServerExample/src/main/res/menu/menu_counter.xml @@ -0,0 +1,11 @@ +

    + + diff --git a/examples/objectServerExample/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/objectServerExample/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..58303aff5b97f3c5a1757573ef73347d3475e16c GIT binary patch literal 4906 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 O0000 literal 0 HcmV?d00001 diff --git a/examples/objectServerExample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/objectServerExample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..eb9ece04b26b69f1d98f9294716e8c982a4577b9 GIT binary patch literal 11165 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 vL;xs~^hI0s(5?>@cEQ$g3}{%|s>uHXFw5o4 + + 64dp + diff --git a/examples/objectServerExample/src/main/res/values/dimens.xml b/examples/objectServerExample/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..47c8224673 --- /dev/null +++ b/examples/objectServerExample/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/examples/objectServerExample/src/main/res/values/realm_colors.xml b/examples/objectServerExample/src/main/res/values/realm_colors.xml new file mode 100644 index 0000000000..aada8ea195 --- /dev/null +++ b/examples/objectServerExample/src/main/res/values/realm_colors.xml @@ -0,0 +1,23 @@ + + + // Grays + #1C233F + #9A9BA5 + #b1b3bf + #EBEBF2 + + // Orb colors + #39477F + #59569E + #9A59A5 + #D34CA3 + #F25192 + #F77C88 + #FC9F95 + #FCC397 + + // Material adjustments + #d64881 + #dadada + + \ No newline at end of file diff --git a/examples/objectServerExample/src/main/res/values/strings.xml b/examples/objectServerExample/src/main/res/values/strings.xml new file mode 100644 index 0000000000..10e43bd0a9 --- /dev/null +++ b/examples/objectServerExample/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Object Server Example + diff --git a/examples/objectServerExample/src/main/res/values/styles.xml b/examples/objectServerExample/src/main/res/values/styles.xml new file mode 100644 index 0000000000..333a4944af --- /dev/null +++ b/examples/objectServerExample/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/examples/settings.gradle b/examples/settings.gradle index 9b18f1e779..d9922ed59e 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -11,6 +11,7 @@ include 'threadExample' include 'unitTestExample' include 'newsreaderExample' include 'rxJavaExample' +include 'objectServerExample' rootProject.name = 'realm-examples' From b681793a788a349966d3d3fe181c2b663c134b0d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 15 Sep 2016 13:05:58 +0200 Subject: [PATCH 0043/2110] SyncConfiguration Builder now only contains allowed options (#87) --- .../objectserver/SyncConfigurationTests.java | 30 +- .../java/io/realm/RealmConfiguration.java | 68 ++-- .../realm/objectserver/SyncConfiguration.java | 365 +++++++++++------- .../main/java/io/realm/objectserver/User.java | 2 +- 4 files changed, 268 insertions(+), 197 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java index eb5fc17f8f..fde38e84a4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java @@ -34,8 +34,6 @@ import java.util.Locale; import java.util.UUID; -import io.realm.DynamicRealm; -import io.realm.RealmMigration; import io.realm.objectserver.internal.Token; import io.realm.rule.RunInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -201,9 +199,7 @@ public void onError(Session session, ObjectServerError error) { @Test public void errorHandler_nullThrows() { SyncConfiguration.Builder builder; - builder = new SyncConfiguration.Builder(context) - .user(createTestUser()) - .serverUrl("realm://objectserver.realm.io/default"); + builder = new SyncConfiguration.Builder(context); try { builder.errorHandler(null); @@ -211,32 +207,10 @@ public void errorHandler_nullThrows() { } } - @Test - public void migration_alwaysThrows() { - SyncConfiguration.Builder builder; - builder = new SyncConfiguration.Builder(context) - .user(createTestUser()) - .serverUrl("realm://objectserver.realm.io/default"); - - try { - builder.migration(null); - } catch (IllegalArgumentException ignore) { - } - - try { - builder.migration(new RealmMigration() { - @Override - public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { - // Nothing - } - }); - } catch (IllegalArgumentException ignore) { - } - } private User createTestUser() { return createTestUser(Long.MAX_VALUE); } - + private User createTestUser(long expires) { JSONObject obj = new JSONObject(); try { diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 43c382f3c6..3756124df5 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -100,20 +100,34 @@ public class RealmConfiguration { private final Realm.Transaction initialDataTransaction; private final WeakReference contextWeakRef; - protected RealmConfiguration(Builder builder) { - this.realmDirectory = builder.directory; - this.realmFileName = builder.fileName; - this.canonicalPath = getCanonicalPath(new File(realmDirectory, realmFileName)); - this.assetFilePath = builder.assetFilePath; - this.key = builder.key; - this.schemaVersion = builder.schemaVersion; - this.deleteRealmIfMigrationNeeded = builder.deleteRealmIfMigrationNeeded; - this.migration = builder.migration; - this.durability = builder.durability; - this.schemaMediator = createSchemaMediator(builder); - this.rxObservableFactory = builder.rxFactory; - this.initialDataTransaction = builder.initialDataTransaction; - this.contextWeakRef = builder.contextWeakRef; + // We need to enumerate all parameters since SyncConfiguration and RealmConfiguration supports different + // subsets of them. + protected RealmConfiguration(File realmDirectory, + String realmFileName, + String canonicalPath, + String assetFilePath, + byte[] key, + long schemaVersion, + RealmMigration migration, + boolean deleteRealmIfMigrationNeeded, + SharedRealm.Durability durability, + RealmProxyMediator schemaMediator, + RxObservableFactory rxObservableFactory, + Realm.Transaction initialDataTransaction, + WeakReference contextWeakRef) { + this.realmDirectory = realmDirectory; + this.realmFileName = realmFileName; + this.canonicalPath = canonicalPath; + this.assetFilePath = assetFilePath; + this.key = key; + this.schemaVersion = schemaVersion; + this.migration = migration; + this.deleteRealmIfMigrationNeeded = deleteRealmIfMigrationNeeded; + this.durability = durability; + this.schemaMediator = schemaMediator; + this.rxObservableFactory = rxObservableFactory; + this.initialDataTransaction = initialDataTransaction; + this.contextWeakRef = contextWeakRef; } public File getRealmDirectory() { @@ -257,10 +271,8 @@ public int hashCode() { } // Creates the mediator that defines the current schema - private RealmProxyMediator createSchemaMediator(Builder builder) { - - Set modules = builder.modules; - Set> debugSchema = builder.debugSchema; + protected static RealmProxyMediator createSchemaMediator(Set modules, + Set> debugSchema) { // If using debug schema, use special mediator if (debugSchema.size() > 0) { @@ -366,9 +378,7 @@ protected static String getCanonicalPath(File realmFile) { * RealmConfiguration.Builder used to construct instances of a RealmConfiguration in a fluent manner. */ public static class Builder { - /** - * IMPORTANT: When adding any new methods to this class also add them to ObjectServerConfiguration.Builder - */ + // IMPORTANT: When adding any new methods to this class also add them to SyncConfiguration. private File directory; private String fileName; private String assetFilePath; @@ -651,7 +661,21 @@ public RealmConfiguration build() { if (rxFactory == null && isRxJavaAvailable()) { rxFactory = new RealmObservableFactory(); } - return new RealmConfiguration(this); + + return new RealmConfiguration(directory, + fileName, + getCanonicalPath(new File(directory, fileName)), + assetFilePath, + key, + schemaVersion, + migration, + deleteRealmIfMigrationNeeded, + durability, + createSchemaMediator(modules, debugSchema), + rxFactory, + initialDataTransaction, + contextWeakRef + ); } private void checkModule(Object module) { diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java index fb25cd232b..2f81735d66 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java @@ -19,25 +19,33 @@ import android.content.Context; import java.io.File; +import java.lang.ref.WeakReference; import java.net.URI; import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashSet; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmMigration; +import io.realm.RealmModel; +import io.realm.annotations.RealmModule; +import io.realm.internal.RealmProxyMediator; +import io.realm.internal.SharedRealm; import io.realm.objectserver.internal.syncpolicy.AutomaticSyncPolicy; import io.realm.objectserver.internal.syncpolicy.SyncPolicy; +import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; /** * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm * Object Server. *

    - * A valid {@link User} is required to create a SyncConfiguration. See {@link Credentials} and + * A valid {@link User} is required to create a {@link SyncConfiguration}. See {@link Credentials} and * {@link User#loginAsync(Credentials, String, User.Callback)} for more information on * how to get a user object. *

    - * A minimal SyncConfiguration can look like this: + * A minimal {@link SyncConfiguration} can look like this: *

      * {@code
      * SyncConfiguration config = new SyncConfiguration.Builder(context)
    @@ -47,54 +55,66 @@
      * }
      * 
    * - * Realms created using a {@link SyncConfiguration} is accessed normally using {@link Realm#getInstance(RealmConfiguration)} - * and can also be stored using {@link Realm#setDefaultConfiguration(RealmConfiguration)}. + * Synchronized Realms only support additive migrations which can be detected automatically, so the following + * builder options are not accessible compared to a normal Realm: * - * TODO Need to expand this section I think + *
      + *
    • {@code deleteRealmIfMigrationNeeded()}
    • + *
    • {@code schemaVersion(long version)}
    • + *
    • {@code migration(Migration)}
    • + *
    + * + * Synchronized Realms are created by using {@link Realm#getInstance(RealmConfiguration)} and + * {@link Realm#getDefaultInstance()} like normal unsynchronized Realms. */ public final class SyncConfiguration extends RealmConfiguration { - private final File realmDirectory; - private final String realmFileName; - private final String canonicalPath; private final URI serverUrl; private final User user; private final SyncPolicy syncPolicy; private final Session.ErrorHandler errorHandler; - private SyncConfiguration(Builder builder) { - super(builder); - if (builder.serverUrl == null || builder.user == null) { - throw new IllegalStateException("serverUrl() and user() are both required."); - } - - // Check if the user has an identifier, if not, it cannot use /~/. - if (builder.serverUrl.toString().contains("/~/") && builder.user.getIdentifier() == null) { - throw new IllegalStateException("The serverUrl contained a /~/, but the user does not have an identifier," + - " most likely because it hasn't been authenticated yet or have been created directly from an" + - " access token. Use a path without /~/."); - } - - this.user = builder.user; - this.serverUrl = getFullServerUrl(builder.serverUrl, user.getIdentifier()); - this.syncPolicy = builder.syncPolicy; - this.errorHandler = builder.errorHandler; - - // Determine location on disk - // Use the serverUrl + user to create a unique filepath unless it has been explicitly overridden. - // // - File rootDir = builder.overrideDefaultFolder ? super.getRealmDirectory() : builder.defaultFolder; - String realmPath = getServerPath(serverUrl); - this.realmDirectory = new File(rootDir, realmPath); - // Create the folder on disk (if needed) - if (!realmDirectory.exists() && !realmDirectory.mkdirs()) { - throw new IllegalStateException("Could not create directory for saving the Realm: " + realmDirectory); - } - this.realmFileName = builder.overrideDefaultLocalFileName ? super.getRealmFileName() : builder.defaultLocalFileName; - this.canonicalPath = getCanonicalPath(new File(realmDirectory, realmFileName)); + private SyncConfiguration(File directory, + String filename, + String canonicalPath, + String assetFilePath, + byte[] key, + long schemaVersion, + RealmMigration migration, + boolean deleteRealmIfMigrationNeeded, + SharedRealm.Durability durability, + RealmProxyMediator schemaMediator, + RxObservableFactory rxFactory, + Realm.Transaction initialDataTransaction, + WeakReference context, + User user, + URI serverUrl, + SyncPolicy syncPolicy, + Session.ErrorHandler errorHandler + ) { + super(directory, + filename, + canonicalPath, + assetFilePath, + key, + schemaVersion, + migration, + deleteRealmIfMigrationNeeded, + durability, + schemaMediator, + rxFactory, + initialDataTransaction, + context + ); + + this.user = user; + this.serverUrl = serverUrl; + this.syncPolicy = syncPolicy; + this.errorHandler = errorHandler; } - static URI getFullServerUrl(URI serverUrl, String userIdentifier) { + + static URI resolveServerUrl(URI serverUrl, String userIdentifier) { try { return new URI(serverUrl.toString().replace("/~/", "/" + userIdentifier + "/")); } catch (URISyntaxException e) { @@ -103,7 +123,7 @@ static URI getFullServerUrl(URI serverUrl, String userIdentifier) { } // Extract the full server path, minus the file name - private String getServerPath(URI serverUrl) { + private static String getServerPath(URI serverUrl) { String path = serverUrl.getPath(); int endIndex = path.lastIndexOf("/"); if (endIndex == -1 ) { @@ -123,9 +143,6 @@ public boolean equals(Object o) { SyncConfiguration that = (SyncConfiguration) o; - if (realmDirectory != null ? !realmDirectory.equals(that.realmDirectory) : that.realmDirectory != null) return false; - if (realmFileName != null ? !realmFileName.equals(that.realmFileName) : that.realmFileName != null) return false; - if (canonicalPath != null ? !canonicalPath.equals(that.canonicalPath) : that.canonicalPath != null) return false; if (serverUrl != null ? !serverUrl.equals(that.serverUrl) : that.serverUrl != null) return false; if (user != null ? !user.equals(that.user) : that.user != null) return false; if (syncPolicy != null ? !syncPolicy.equals(that.syncPolicy) : that.syncPolicy != null) return false; @@ -135,9 +152,6 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + (realmDirectory != null ? realmDirectory.hashCode() : 0); - result = 31 * result + (realmFileName != null ? realmFileName.hashCode() : 0); - result = 31 * result + (canonicalPath != null ? canonicalPath.hashCode() : 0); result = 31 * result + (serverUrl != null ? serverUrl.hashCode() : 0); result = 31 * result + (user != null ? user.hashCode() : 0); result = 31 * result + (syncPolicy != null ? syncPolicy.hashCode() : 0); @@ -152,30 +166,6 @@ public String toString() { return stringBuilder.toString(); } - /** - * {@inheritDoc} - */ - @Override - public File getRealmDirectory() { - return this.realmDirectory; - } - - /** - * {@inheritDoc} - */ - @Override - public String getRealmFileName() { - return this.realmFileName; - } - - /** - * {@inheritDoc} - */ - @Override - public String getPath() { - return this.canonicalPath; - } - // Keeping this package protected for now. The API might still be subject to change. SyncPolicy getSyncPolicy() { return syncPolicy; @@ -202,35 +192,51 @@ public Session.ErrorHandler getErrorHandler() { /** * ReplicationConfiguration.Builder used to construct instances of a ReplicationConfiguration in a fluent manner. */ - public static final class Builder extends RealmConfiguration.Builder { + public static final class Builder { private Context context; + private File directory; + private boolean overrideDefaultFolder = false; + private String fileName; + private boolean overrideDefaultLocalFileName = false; + private byte[] key; + private HashSet modules = new HashSet(); + private HashSet> debugSchema = new HashSet>(); + private RxObservableFactory rxFactory; + private Realm.Transaction initialDataTransaction; private URI serverUrl; private User user = null; private SyncPolicy syncPolicy = new AutomaticSyncPolicy(); private Session.ErrorHandler errorHandler = SyncManager.defaultSessionErrorHandler; - private boolean overrideDefaultFolder = false; - private boolean overrideDefaultLocalFileName = false; private File defaultFolder; private String defaultLocalFileName; + private SharedRealm.Durability durability = SharedRealm.Durability.FULL; /** - * {@inheritDoc} + * Creates an instance of the Builder for the SyncConfiguration. + *

    + * This will use the app's own internal directory for storing the Realm file. This does not require any + * additional permissions. The default location is {@code /data/data//files/realm-object-server}, + * but can change depending on vendor implementations of Android. + * + * @param context the Android application context. */ public Builder(Context context) { - super(context); this.context = context; + this.defaultFolder = new File(context.getFilesDir(), "realm-object-server"); } /** * Sets the local filename for the Realm. * This will override the default name defined by the {@link #serverUrl(String)} * - * @param name name of the local file on disk. + * @param filename name of the local file on disk. */ - @Override - public Builder name(String name) { - super.name(name); + public Builder name(String filename) { + if (filename == null || filename.isEmpty()) { + throw new IllegalArgumentException("A non-empty filename must be provided"); + } + this.fileName = filename; this.overrideDefaultLocalFileName = true; return this; } @@ -244,98 +250,108 @@ public Builder name(String name) { * * The default location is {@code context.getFilesDir()}. * - * @param dir directory on disk where the Realm file can be saved. + * @param directory directory on disk where the Realm file can be saved. * @throws IllegalArgumentException if the directory is not valid. */ - public Builder directory(File dir) { - super.directory(dir); + public Builder directory(File directory) { + if (directory == null) { + throw new IllegalArgumentException("Non-null 'directory' required."); + } + if (directory.isFile()) { + throw new IllegalArgumentException("'directory' is a file, not a directory: " + + directory.getAbsolutePath() + "."); + } + if (!directory.exists() && !directory.mkdirs()) { + throw new IllegalArgumentException("Could not create the specified directory: " + + directory.getAbsolutePath() + "."); + } + if (!directory.canWrite()) { + throw new IllegalArgumentException("Realm directory is not writable: " + + directory.getAbsolutePath() + "."); + } + this.directory = directory; overrideDefaultFolder = true; return this; } /** - * {@inheritDoc} + * Sets the 64 bit key used to encrypt and decrypt the Realm file. + * Sets the {@value io.realm.RealmConfiguration#KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file. */ - @Override public Builder encryptionKey(byte[] key) { - super.encryptionKey(key); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public Builder schemaVersion(long schemaVersion) { - super.schemaVersion(schemaVersion); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public Builder deleteRealmIfMigrationNeeded() { - super.deleteRealmIfMigrationNeeded(); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public SyncConfiguration.Builder inMemory() { - super.inMemory(); + if (key == null) { + throw new IllegalArgumentException("A non-null key must be provided"); + } + if (key.length != KEY_LENGTH) { + throw new IllegalArgumentException(String.format("The provided key must be %s bytes. Yours was: %s", + KEY_LENGTH, key.length)); + } + this.key = Arrays.copyOf(key, key.length); return this; } /** - * {@inheritDoc} + * Replaces the existing module(s) with one or more {@link RealmModule}s. Using this method will replace the + * current schema for this Realm with the schema defined by the provided modules. + *

    + * A reference to the default Realm module containing all Realm classes in the project (but not dependencies), + * can be found using {@link Realm#getDefaultModule()}. Combining the schema from the app project and a library + * dependency is thus done using the following code: + *

    + * {@code builder.modules(Realm.getDefaultMode(), new MyLibraryModule()); } + *

    + * @param baseModule the first Realm module (required). + * @param additionalModules the additional Realm modules + * @throws IllegalArgumentException if any of the modules don't have the {@link RealmModule} annotation. + * @see Realm#getDefaultModule() */ - @Override public Builder modules(Object baseModule, Object... additionalModules) { - super.modules(baseModule, additionalModules); + modules.clear(); + addModule(baseModule); + if (additionalModules != null) { + for (Object module : additionalModules) { + addModule(module); + } + } return this; } /** - * {@inheritDoc} + * Sets the {@link RxObservableFactory} used to create Rx Observables from Realm objects. + * The default factory is {@link RealmObservableFactory}. + * + * @param factory factory to use. */ - @Override - public SyncConfiguration.Builder rxFactory(RxObservableFactory factory) { - super.rxFactory(factory); + public Builder rxFactory(RxObservableFactory factory) { + rxFactory = factory; return this; } /** - * {@inheritDoc} + * Sets the initial data in {@link io.realm.Realm}. This transaction will be executed only for the first time + * when database file is created or while migrating the data when + * {@link RealmConfiguration.Builder#deleteRealmIfMigrationNeeded()} is set. + * + * @param transaction transaction to execute. */ - @Override public Builder initialData(Realm.Transaction transaction) { - super.initialData(transaction); + initialDataTransaction = transaction; return this; } /** - * {@inheritDoc} + * Setting this will create an in-memory Realm instead of saving it to disk. In-memory Realms might still use + * disk space if memory is running low, but all files created by an in-memory Realm will be deleted when the + * Realm is closed. + *

    + * Note that because in-memory Realms are not persisted, you must be sure to hold on to at least one non-closed + * reference to the in-memory Realm object with the specific name as long as you want the data to last. */ - @Override - public Builder assetFile(String assetFile) { - super.assetFile(assetFile); + public Builder inMemory() { + this.durability = SharedRealm.Durability.MEM_ONLY; return this; } - /** - * Manual migrations are not supported for Realms that can be synced using the Realm Object Server - * Only additive changes are allowed, and these will be detected and applied automatically. - * - * @throws IllegalArgumentException always. - */ - @Override - public Builder migration(RealmMigration migration) { - throw new IllegalArgumentException("Manual migrations are not supported for Realms that can be synchronized using the Realm Object Server"); - } - /** * Enable server side synchronization for this Realm. The name should be a unique URL that identifies the Realm. * {@code /~/} can be used as a placeholder for a user ID in case the Realm should only be available to one @@ -390,16 +406,15 @@ public Builder user(User user) { throw new IllegalArgumentException("Non-null `user` required."); } if (!user.isAuthenticated()) { - throw new IllegalArgumentException("User not authenticated or authentication expired. User ID: " + user.getIdentifier()); + throw new IllegalArgumentException("User not authenticated or authentication expired. User ID: " + + user.getIdentifier()); } - - this.defaultFolder = new File(context.getFilesDir(), "realm-object-server"); this.user = user; return this; } /** - * Sets the sync policy used to control when changes should be synchronized with the remote Realm. + * Sets the {@link SyncPolicy} used to control when changes should be synchronized with the remote Realm. * The default policy is {@link AutomaticSyncPolicy}. * * @param syncPolicy policy to use. @@ -407,15 +422,14 @@ public Builder user(User user) { * @see Session */ Builder syncPolicy(SyncPolicy syncPolicy) { - // TODO: Decide if we should launch with this as package protected since the sync notification API might - // change quite a bit. + // Package protected until SyncPolicy API is more stable. this.syncPolicy = syncPolicy; return this; } /** - * Sets the error handler used by this configuration. - * This will override any handler set by calling {@link SyncManager#setDefaultSessionErrorHandler(Session.ErrorHandler)}. + * Sets the error handler used by this configuration. This will override any handler set by calling + * {@link SyncManager#setDefaultSessionErrorHandler(Session.ErrorHandler)}. * * Only errors not handled by the defined {@code SyncPolicy} will be reported to this error handler. * @@ -436,7 +450,66 @@ public Builder errorHandler(Session.ErrorHandler errorHandler) { * @return the created {@link SyncConfiguration}. */ public SyncConfiguration build() { - return new SyncConfiguration(this); + if (serverUrl == null || user == null) { + throw new IllegalStateException("serverUrl() and user() are both required."); + } + + // Check if the user has an identifier, if not, it cannot use /~/. + if (serverUrl.toString().contains("/~/") && user.getIdentifier() == null) { + throw new IllegalStateException("The serverUrl contains a /~/, but the user does not have an identity." + + " Most likely it hasn't been authenticated yet or has been created directly from an" + + " access token. Use a path without /~/."); + } + + // Determine location on disk + // Use the serverUrl + user to create a unique filepath unless it has been explicitly overridden. + // // + URI resolvedServerUrl = resolveServerUrl(serverUrl, user.getIdentifier()); + File rootDir = overrideDefaultFolder ? directory : defaultFolder; + String realmPathFromRootDir = getServerPath(resolvedServerUrl); + File realmFileDirectory = new File(rootDir, realmPathFromRootDir); + // Create the folder on disk (if needed) + if (!realmFileDirectory.exists() && !realmFileDirectory.mkdirs()) { + throw new IllegalStateException("Could not create directory for saving the Realm: " + realmFileDirectory); + } + String realmFileName = overrideDefaultLocalFileName ? fileName : defaultLocalFileName; + + return new SyncConfiguration( + // Realm Configuration options + realmFileDirectory, + realmFileName, + getCanonicalPath(new File(realmFileDirectory, realmFileName)), + null, // assetFile not supported by Sync. See https://github.com/realm/realm-sync/issues/241 + key, + -1, // Schema version not supported + null, // Custom migrations not supported + false, // MigrationNeededException is never thrown + durability, + createSchemaMediator(modules, debugSchema), + rxFactory, + initialDataTransaction, + new WeakReference(context), + + // Sync Configuration specific + user, + resolvedServerUrl, + syncPolicy, + errorHandler + ); + } + + private void addModule(Object module) { + if (module != null) { + checkModule(module); + modules.add(module); + } + } + + private void checkModule(Object module) { + if (!module.getClass().isAnnotationPresent(RealmModule.class)) { + throw new IllegalArgumentException(module.getClass().getCanonicalName() + " is not a RealmModule. " + + "Add @RealmModule to the class definition."); + } } } } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/User.java b/realm/realm-library/src/main/java/io/realm/objectserver/User.java index a760ca690e..24a62db29b 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/User.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/User.java @@ -300,7 +300,7 @@ void addAccessToken(URI uri, String accessToken) { if (uri == null || accessToken == null) { throw new IllegalArgumentException("Non-null 'uri' and 'accessToken' required."); } - uri = SyncConfiguration.getFullServerUrl(uri, identifier); + uri = SyncConfiguration.resolveServerUrl(uri, identifier); // Optimistically create a long-lived token with all permissions. If this is incorrect the Object Server // will reject it anyway. If tokens are added manually it is up to the user to ensure they are also used From 59359eb9c3c458daca826a5e0132a6e06f037152 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 15 Sep 2016 16:41:41 +0200 Subject: [PATCH 0044/2110] Align the public Session with Cocoa (#95) --- .../objectserver/SyncConfigurationTests.java | 31 +- .../io/realm/objectserver/SyncTestUtils.java | 40 ++ .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- ...alm_objectserver_internal_SyncSession.cpp} | 12 +- .../java/io/realm/objectserver/Session.java | 305 ++------------ .../io/realm/objectserver/SessionState.java | 2 +- .../realm/objectserver/SyncConfiguration.java | 12 +- .../io/realm/objectserver/SyncManager.java | 23 +- .../main/java/io/realm/objectserver/User.java | 168 ++------ .../{ => internal}/AuthenticatingState.java | 9 +- .../{ => internal}/BindingState.java | 5 +- .../{ => internal}/BoundState.java | 5 +- .../{ => internal}/FsmAction.java | 6 +- .../objectserver/{ => internal}/FsmState.java | 18 +- .../{ => internal}/InitialState.java | 5 +- .../internal/ObjectServerFacade.java | 15 +- .../objectserver/internal/SessionStore.java | 65 ++- .../{ => internal}/StoppedState.java | 8 +- .../objectserver/internal/SyncSession.java | 373 ++++++++++++++++++ .../realm/objectserver/internal/SyncUser.java | 209 ++++++++++ .../realm/objectserver/internal/SyncUtil.java | 21 + .../{ => internal}/UnboundState.java | 6 +- .../syncpolicy/AutomaticSyncPolicy.java | 14 +- .../internal/syncpolicy/SyncPolicy.java | 44 ++- 24 files changed, 857 insertions(+), 541 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java rename realm/realm-library/src/main/cpp/{io_realm_objectserver_Session.cpp => io_realm_objectserver_internal_SyncSession.cpp} (86%) rename realm/realm-library/src/main/java/io/realm/objectserver/{ => internal}/AuthenticatingState.java (92%) rename realm/realm-library/src/main/java/io/realm/objectserver/{ => internal}/BindingState.java (93%) rename realm/realm-library/src/main/java/io/realm/objectserver/{ => internal}/BoundState.java (96%) rename realm/realm-library/src/main/java/io/realm/objectserver/{ => internal}/FsmAction.java (83%) rename realm/realm-library/src/main/java/io/realm/objectserver/{ => internal}/FsmState.java (76%) rename realm/realm-library/src/main/java/io/realm/objectserver/{ => internal}/InitialState.java (90%) rename realm/realm-library/src/main/java/io/realm/objectserver/{ => internal}/StoppedState.java (87%) create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUtil.java rename realm/realm-library/src/main/java/io/realm/objectserver/{ => internal}/UnboundState.java (88%) diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java index fde38e84a4..522f6bca8c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java @@ -38,6 +38,7 @@ import io.realm.rule.RunInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; +import static io.realm.objectserver.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -91,8 +92,8 @@ public void serverUrl_setsFolderAndFileName() { User user = createTestUser(); String[][] validUrls = { // , , - { "realm://objectserver.realm.io/~/default", "realm-object-server/" + user.getIdentifier(), "default" }, - { "realm://objectserver.realm.io/~/sub/default", "realm-object-server/" + user.getIdentifier() + "/sub", "default" } + { "realm://objectserver.realm.io/~/default", "realm-object-server/" + user.getIdentity(), "default" }, + { "realm://objectserver.realm.io/~/sub/default", "realm-object-server/" + user.getIdentity() + "/sub", "default" } }; for (String[] validUrl : validUrls) { @@ -207,30 +208,4 @@ public void errorHandler_nullThrows() { } } - private User createTestUser() { - return createTestUser(Long.MAX_VALUE); - } - - private User createTestUser(long expires) { - JSONObject obj = new JSONObject(); - try { - JSONObject token = new JSONObject(); - token.put("token", UUID.randomUUID().toString()); - JSONObject tokenData = new JSONObject(); - JSONArray perms = new JSONArray(); // Grant all permissions - for (int i = 0; i < Token.Permission.values().length; i++) { - perms.put(Token.Permission.values()[i].toString().toLowerCase(Locale.US)); - } - tokenData.put("identity", UUID.randomUUID().toString()); - tokenData.put("path", null); - tokenData.put("expires", expires); - tokenData.put("access", perms); - token.put("token_data", tokenData); - obj.put("refreshToken", token); - obj.put("authUrl", "http://dummy.org/auth"); - return User.fromJson(obj.toString()); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java new file mode 100644 index 0000000000..8b985da518 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java @@ -0,0 +1,40 @@ +package io.realm.objectserver; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Locale; +import java.util.UUID; + +import io.realm.objectserver.internal.Token; + +public class SyncTestUtils { + + public static User createTestUser() { + return createTestUser(Long.MAX_VALUE); + } + + public static User createTestUser(long expires) { + JSONObject obj = new JSONObject(); + try { + JSONObject token = new JSONObject(); + token.put("token", UUID.randomUUID().toString()); + JSONObject tokenData = new JSONObject(); + JSONArray perms = new JSONArray(); // Grant all permissions + for (int i = 0; i < Token.Permission.values().length; i++) { + perms.put(Token.Permission.values()[i].toString().toLowerCase(Locale.US)); + } + tokenData.put("identity", UUID.randomUUID().toString()); + tokenData.put("path", null); + tokenData.put("expires", expires); + tokenData.put("access", perms); + token.put("token_data", tokenData); + obj.put("refreshToken", token); + obj.put("authUrl", "http://dummy.org/auth"); + return User.fromJson(obj.toString()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 91423934d9..fe152c6146 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -27,7 +27,7 @@ create_javah(TARGET jni_headers CLASSES io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil - io.realm.objectserver.SyncManager io.realm.objectserver.Session + io.realm.objectserver.SyncManager io.realm.objectserver.internal.SyncSession io.realm.log.LogLevel CLASSPATH ${classes_PATH} diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp b/realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp similarity index 86% rename from realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp rename to realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp index efabb2d315..18c50639bf 100644 --- a/realm/realm-library/src/main/cpp/io_realm_objectserver_Session.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp @@ -16,7 +16,7 @@ #include -#include "io_realm_objectserver_Session.h" +#include "io_realm_objectserver_internal_SyncSession.h" #include "objectserver_shared.hpp" #include "util.hpp" #include @@ -38,7 +38,7 @@ using namespace realm; using namespace sync; -JNIEXPORT jlong JNICALL Java_io_realm_objectserver_Session_nativeCreateSession +JNIEXPORT jlong JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeCreateSession (JNIEnv *env, jobject obj, jstring localRealmPath) { TR_ENTER(env) @@ -55,7 +55,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_objectserver_Session_nativeCreateSession return 0; } -JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeBind +JNIEXPORT void JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeBind (JNIEnv *env, jobject, jlong sessionPointer, jstring remoteUrl, jstring accessToken) { TR_ENTER(env) @@ -75,7 +75,7 @@ JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeBind } -JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeUnbind +JNIEXPORT void JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeUnbind (JNIEnv *env, jobject, jlong sessionPointer) { TR_ENTER(env) @@ -84,7 +84,7 @@ JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeUnbind delete session; // TODO Can we avoid killing the session here? } -JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeRefresh +JNIEXPORT void JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeRefresh (JNIEnv *env, jobject, jlong sessionPointer, jstring accessToken) { TR_ENTER(env) @@ -99,7 +99,7 @@ JNIEXPORT void JNICALL Java_io_realm_objectserver_Session_nativeRefresh } JNIEXPORT void JNICALL -Java_io_realm_objectserver_Session_nativeNotifyCommitHappened +Java_io_realm_objectserver_internal_SyncSession_nativeNotifyCommitHappened (JNIEnv *env, jobject, jlong sessionPointer, jlong version) { TR_ENTER(env) diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/Session.java b/realm/realm-library/src/main/java/io/realm/objectserver/Session.java index 9dfbc340b3..61ad97ba89 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/Session.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/Session.java @@ -16,327 +16,94 @@ package io.realm.objectserver; -import java.util.HashMap; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.net.URI; -import io.realm.RealmAsyncTask; import io.realm.internal.Keep; -import io.realm.internal.Util; -import io.realm.objectserver.internal.syncpolicy.SyncPolicy; -import io.realm.objectserver.internal.Token; -import io.realm.objectserver.internal.network.AuthenticateResponse; -import io.realm.objectserver.internal.network.AuthenticationServer; -import io.realm.objectserver.internal.network.NetworkStateReceiver; import io.realm.log.RealmLog; +import io.realm.objectserver.internal.SyncSession; /** - * This class controls the connection to a Realm Object Server for one Realm. + * This class represents the connection to the Realm Object Server for one {@link SyncConfiguration}. *

    * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening - * a Realm instance. Once a session has been created it will continue to exist until explicitly closed or the - * underlying Realm file is deleted. + * a Realm instance using that configuration. Once a session has been created it will continue to exist until the app + * is closed or the {@link SyncConfiguration} is no longer used. *

    - * It is normally not necessary to interact directly with a session. That should be done by the {@code SyncPolicy} - * defined using {@code io.realm.objectserver.SyncConfiguration.Builder#syncPolicy(SyncPolicy)}. + * A session is fully controlled by Realm, but can provide additional information in case of errors. + * It is passed along in all {@link Session.ErrorHandler}s. *

    - * A session has a lifecycle consisting of the following states: - *

    - *

      - *
    1. - * INITIAL Initial state when creating the Session object. No connections to the object server have been - * made yet. At this point it is possible to register any relevant error and event listeners. Calling - * {@link #start()} will cause the session to become unbound and notify the {@code SyncPolicy} that the - * session is ready by calling {@code SyncPolicy#onSessionCreated(Session)}. - *
    2. - *
    3. - * UNBOUND When a session is unbound, no synchronization between the local and remote Realm is happening. - * Call {@link #bind()} to start synchronizing changes. - *
    4. - *
    5. - * BINDING A session is in the process of binding a local Realm to a remote one. Calling {@link #unbind()} - * at this stage, will cancel the process. If binding fails, the session will revert to being unbound and the error - * will be reported to the error handler. - * - * During binding, if a users access has expired, the session will be AUTHENTICATING. During this state, - * Realm will automatically try to acquire new valid credentials. If this succeed BINDING will - * automatically be resumed, if not, the session will become UNBOUND and an appropriate error reported. - *
    6. - *
    7. - * BOUND A bound session has an active connection to the remote Realm and will synchronize any changes - * immediately. - *
    8. - *
    9. - * STOPPED The session has been stopped and no longer work. A new session will be created the next time - * either the Realm is opened or {@code SyncManager#getSession(SyncConfiguration)} is called. - *
    10. - *
    - * * This object is thread safe. + * + * @see SessionState */ @Keep public final class Session { - private final HashMap FSM = new HashMap(); - - // Variables used by the FSM - final SyncConfiguration configuration; - final AuthenticationServer authServer; - private final ErrorHandler errorHandler; - public long nativeSessionPointer; - final User user; - RealmAsyncTask networkRequest; - NetworkStateReceiver.ConnectionListener networkListener; - private SyncPolicy syncPolicy; - - // Keeping track of currrent FSM state - SessionState currentStateDescription; - FsmState currentState; - - /** - /** - * Creates a new Object Server Session - * - * @param syncConfiguration Sync configuration defining this session - * @param authServer Authentication server used to refresh credentials if needed - * @param policy Sync Policy to use by this Session. - */ - public Session(SyncConfiguration syncConfiguration, AuthenticationServer authServer, SyncPolicy policy) { - this.configuration = syncConfiguration; - this.user = configuration.getUser(); - this.authServer = authServer; - this.errorHandler = configuration.getErrorHandler(); - this.syncPolicy = policy; - setupStateMachine(); - } + private final SyncSession syncSession; - private void setupStateMachine() { - FSM.put(SessionState.INITIAL, new InitialState()); - FSM.put(SessionState.UNBOUND, new UnboundState()); - FSM.put(SessionState.BINDING, new BindingState()); - FSM.put(SessionState.AUTHENTICATING, new AuthenticatingState()); - FSM.put(SessionState.BOUND, new BoundState()); - FSM.put(SessionState.STOPPED, new StoppedState()); - RealmLog.debug("Session started: " + configuration.getServerUrl()); - currentState = FSM.get(SessionState.INITIAL); - currentState.entry(this); - } - - // Goto the next state. The FsmState classes are responsible for calling this method as a reaction to a FsmAction - // being called or an internal action triggering a state transition. - void nextState(SessionState nextStateDescription) { - currentState.exit(); - FsmState nextState = FSM.get(nextStateDescription); - if (nextState == null) { - throw new IllegalStateException("No state was configured to handle: " + nextStateDescription); - } - RealmLog.debug("Session[%s]: %s -> %s", configuration.getServerUrl(), currentStateDescription, nextStateDescription); - currentStateDescription = nextStateDescription; - currentState = nextState; - nextState.entry(this); + Session(SyncSession rosSession) { + this.syncSession = rosSession; } /** - * Starts the session. This will cause the session to come unbound. {@link #bind()} must be called to - * actually start synchronizing data. - */ - public synchronized void start() { - currentState.onStart(); - } - - /** - * Stops the session. The session can no longer be used. - */ - public synchronized void stop() { - currentState.onStop(); - } - - /** - * Binds the local Realm to the remote Realm. Once bound, changes to either the local or Remote Realm will be - * synchronized immediately. + * Returns the {@link SyncConfiguration} that is responsible for controlling this session. * - * While this method will return immediately, binding a Realm is not guaranteed to succeed. Possible reasons for - * failure could be either if the device is offline or credentials have expired. Binding is an asynchronous - * operation and all errors will be sent first to {@code SyncPolicy#onError(Session, ObjectServerError)} and if the - * SyncPolicy didn't handle it, to the {@link ErrorHandler} defined by - * {@link SyncConfiguration.Builder#errorHandler(ErrorHandler)}. + * @return SyncConfiguration that defines and controls this session. */ - public synchronized void bind() { - currentState.onBind(); + public SyncConfiguration getConfiguration() { + return syncSession.getConfiguration(); } /** - * Stops a local Realm from synchronizing changes with the remote Realm. + * Returns the {@link User} defined by the {@link SyncConfiguration} that is used to connect to the + * Realm Object Server. * - * It is possible to call {@link #bind()} again after a Realm has been unbound. + * @return {@link User} used to authenticate the session on the Realm Object Server. */ - public synchronized void unbind() { - currentState.onUnbind(); + public User getUser() { + return syncSession.getConfiguration().getUser(); } - /** - * // FIXME This method shouldn't be public - * Notify the session that an error has occurred. - * @param error the kind of err + * Returns the {@link URI} describing the remote Realm which this session connects to and synchronizes changes with. + * + * @return {@link URI} describing the remote Realm. */ - public synchronized void onError(ObjectServerError error) { - currentState.onError(error); // FSM needs to respond to the error first, before notifying the User - if (errorHandler != null) { - errorHandler.onError(this, error); - } - } - - // Called from Session.cpp and SyncMaanger - // This callback will happen on the thread running the Sync Client. - void notifySessionError(int errorCode, String errorMessage) { - ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); - onError(error); + public URI getServerUrl() { + return syncSession.getConfiguration().getServerUrl(); } /** - * Checks if the local Realm is bound to the remote Realm and can synchronize any changes happening on either - * side. + * Returns the state of this session. * - * @return {@code true} if the local Realm is bound to the remote Realm, {@code false} otherwise. + * @return The current {@link SessionState} for this session. */ - public boolean isBound() { - return currentStateDescription == SessionState.BOUND; - } - - // - // Package protected methods used by the FSM states to manipulate session variables. - // - - // Create a native session. The session abstraction in Realm Core doesn't support multiple calls to bind()/unbind() - // yet, so the Java SyncSession must manually create/and close the native sessions as needed. - void createNativeSession() { - nativeSessionPointer = nativeCreateSession(configuration.getPath()); - } - - void stopNativeSession() { - if (nativeSessionPointer != 0) { - nativeUnbind(nativeSessionPointer); - nativeSessionPointer = 0; - } + public SessionState getState() { + return syncSession.getState(); } - // Bind with proper access tokens - // Access tokens are presumed to be present and valid at this point - void bindWithTokens() { - Token accessToken = user.getAccessToken(configuration.getServerUrl()); - if (accessToken == null) { - throw new IllegalStateException("User '" + user.toString() + "' does not have an access token for " - + configuration.getServerUrl()); - } - nativeBind(nativeSessionPointer, configuration.getServerUrl().toString(), accessToken.value()); - } - - // Authenticate by getting access tokens for the specific Realm - void authenticateRealm(final Runnable onSuccess, final Session.ErrorHandler errorHandler) { - if (networkRequest != null) { - networkRequest.cancel(); - } - // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { - @Override - public void run() { - int attempt = 0; - boolean success; - ObjectServerError error = null; - while (true) { - attempt++; - long sleep = Util.calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); - if (sleep > 0) { - try { - Thread.sleep(sleep); - } catch (InterruptedException e) { - return; // Abort authentication if interrupted. - } - } - - AuthenticateResponse response = authServer.authenticateRealm( - user.getRefreshToken(), - configuration.getServerUrl(), - user.getAuthenticationUrl() - ); - if (response.isValid()) { - user.addAccessToken(configuration.getServerUrl(), response.getAccessToken()); - success = true; - break; - } else { - // Only retry in case of IO exceptions, since that might be network timeouts etc. - // All other errors indicate a bigger problem, so stop trying to authenticate and - // unbind - ObjectServerError responseError = response.getError(); - if (responseError.errorCode() != ErrorCode.IO_EXCEPTION) { - success = false; - error = responseError; - break; - } - } - } - - if (success) { - onSuccess.run(); - } else { - errorHandler.onError(Session.this, error); - } - } - }); - networkRequest = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); - } - - public boolean isAuthenticated(SyncConfiguration configuration) { - Token token = user.getAccessToken(configuration.getServerUrl()); - return token != null && token.expiresMs() > System.currentTimeMillis(); - } - - public SyncConfiguration getConfiguration() { - return configuration; + SyncSession getSyncSession() { + return syncSession; } @Override protected void finalize() throws Throwable { super.finalize(); - if (currentStateDescription != SessionState.STOPPED) { + if (syncSession.getState() != SessionState.STOPPED) { RealmLog.warn("Session was not closed before being finalized. This is a potential resource leak."); - stop(); + syncSession.stop(); } } - private native long nativeCreateSession(String localRealmPath); - private native void nativeBind(long nativeSessionPointer, String remoteRealmUrl, String userToken); - private native void nativeUnbind(long nativeSessionPointer); - private native void nativeRefresh(long nativeSessionPointer, String userToken); - private native void nativeNotifyCommitHappened(long sessionPointer, long version); - - /** - * FIXME: Find a way to keep this out of the public API. Could probably happen as part of moving everything to the - * Object Store. - * - * Notify session that a commit on the device has happened. - */ - public void notifyCommit(long version) { - if (isBound()) { - nativeNotifyCommitHappened(nativeSessionPointer, version); - } - } - - public SyncPolicy getSyncPolicy() { - return syncPolicy; - } - /** - * Interface used by both the Object Server network client and sessions to report back errors. + * Interface used to report any session errors. * * @see SyncManager#setDefaultSessionErrorHandler(ErrorHandler) * @see io.realm.objectserver.SyncConfiguration.Builder#errorHandler(ErrorHandler) */ public interface ErrorHandler { /** - * Callback for errors on this session object. - * Only errors with an ID between 0-99 and 200-299 and will be reported here. + * Callback for errors on a session object. * * @param session {@link Session} this error happened on. * @param error type of error. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java b/realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java index d1764b75ee..a82d17e4ca 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java @@ -19,7 +19,7 @@ /** * Enum describing the various states the Session Finite-State-Machine can be in. */ -enum SessionState { +public enum SessionState { INITIAL, // Initial starting state UNBOUND, // Start done, Realm is unbound. BINDING, // bind() has been called. Can take a while. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java index 2f81735d66..3e6a3b4506 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java @@ -28,6 +28,7 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmMigration; +import io.realm.objectserver.internal.SyncUtil; import io.realm.RealmModel; import io.realm.annotations.RealmModule; import io.realm.internal.RealmProxyMediator; @@ -37,6 +38,8 @@ import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; +import static io.realm.objectserver.internal.SyncUtil.getFullServerUrl; + /** * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm * Object Server. @@ -405,9 +408,8 @@ public Builder user(User user) { if (user == null) { throw new IllegalArgumentException("Non-null `user` required."); } - if (!user.isAuthenticated()) { - throw new IllegalArgumentException("User not authenticated or authentication expired. User ID: " - + user.getIdentifier()); + if (!user.getSyncUser().isAuthenticated()) { + throw new IllegalArgumentException("User not authenticated or authentication expired. User ID: " + user.getIdentity()); } this.user = user; return this; @@ -455,7 +457,7 @@ public SyncConfiguration build() { } // Check if the user has an identifier, if not, it cannot use /~/. - if (serverUrl.toString().contains("/~/") && user.getIdentifier() == null) { + if (serverUrl.toString().contains("/~/") && user.getIdentity() == null) { throw new IllegalStateException("The serverUrl contains a /~/, but the user does not have an identity." + " Most likely it hasn't been authenticated yet or has been created directly from an" + " access token. Use a path without /~/."); @@ -464,7 +466,7 @@ public SyncConfiguration build() { // Determine location on disk // Use the serverUrl + user to create a unique filepath unless it has been explicitly overridden. // // - URI resolvedServerUrl = resolveServerUrl(serverUrl, user.getIdentifier()); + URI resolvedServerUrl = resolveServerUrl(serverUrl, user.getIdentity()); File rootDir = overrideDefaultFolder ? directory : defaultFolder; String realmPathFromRootDir = getServerPath(resolvedServerUrl); File realmFileDirectory = new File(rootDir, realmPathFromRootDir); diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java index 3a1171c9f7..b3b556d6b6 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java @@ -22,6 +22,7 @@ import io.realm.internal.Keep; import io.realm.internal.RealmCore; +import io.realm.objectserver.internal.SyncSession; import io.realm.objectserver.internal.SessionStore; import io.realm.objectserver.internal.network.AuthenticationServer; import io.realm.objectserver.internal.network.OkHttpAuthenticationServer; @@ -94,7 +95,24 @@ public static void setDefaultSessionErrorHandler(Session.ErrorHandler errorHandl * @return the {@link Session} for the specified Realm. */ public static synchronized Session getSession(SyncConfiguration syncConfiguration) { - return SessionStore.getSession(syncConfiguration); + if (syncConfiguration == null) { + throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); + } + + if (SessionStore.hasSession(syncConfiguration)) { + return SessionStore.getPublicSession(syncConfiguration); + } else { + SyncSession internalSession = new SyncSession( + syncConfiguration, + authServer, + syncConfiguration.getUser().getSyncUser(), + syncConfiguration.getSyncPolicy(), + syncConfiguration.getErrorHandler() + ); + Session publicSession = new Session(internalSession); + SessionStore.addSession(publicSession, internalSession); + return publicSession; + } } public static AuthenticationServer getAuthServer() { @@ -116,7 +134,7 @@ static void setAuthServerImpl(AuthenticationServer authServerImpl) { // from here. This can be removed once better error propagation is implemented in Sync Core. private static void notifyErrorHandler(int errorCode, String errorMessage) { ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); - for (Session session : SessionStore.getSession()) { + for (SyncSession session : SessionStore.getAllSessions()) { session.onError(error); } } @@ -125,6 +143,7 @@ private static void notifyErrorHandler(int errorCode, String errorMessage) { * Sets the log level for the underlying * @param logLevel */ + // FIXME Remove from the public API. This is controlled by Logger#minimumNativeLogLevel public static void setLogLevel(int logLevel) { nativeSetSyncClientLogLevel(logLevel); } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/User.java b/realm/realm-library/src/main/java/io/realm/objectserver/User.java index 24a62db29b..8ed9293a98 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/User.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/User.java @@ -18,46 +18,34 @@ import android.os.Handler; import android.os.Looper; -import android.os.SystemClock; import org.json.JSONException; import org.json.JSONObject; import java.net.MalformedURLException; -import java.net.URI; import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import io.realm.RealmAsyncTask; import io.realm.internal.IOException; -import io.realm.internal.Util; +import io.realm.objectserver.internal.SyncUser; import io.realm.objectserver.internal.Token; import io.realm.objectserver.internal.network.AuthenticateResponse; import io.realm.objectserver.internal.network.AuthenticationServer; -import io.realm.objectserver.internal.network.RefreshResponse; import io.realm.log.RealmLog; /** * This class represents a user on the Realm Object Server. - * - * * TODO Rewrite this section */ public class User { - // Time left on current refresh token, when we want to begin refreshing it. - // Failing to refresh it before it expires, will result in the user getting logged out. private static RealmAsyncTask authenticateTask; - private RealmAsyncTask refreshTask; + private final SyncUser syncUser; - private final String identifier; - private Token refreshToken; - private URL authentificationUrl; - private Map accessTokens = new HashMap(); + private User(SyncUser user) { + this.syncUser = user; + } /** * Load a user that has previously been serialized using {@link #toJson()}. @@ -73,7 +61,7 @@ public static User fromJson(String user) { Token refreshToken = Token.from(obj.getJSONObject("refreshToken")); URL authUrl = new URL(obj.getString("authUrl")); // FIXME: Add support for storing access tokens as well - return new User(refreshToken.identity(), refreshToken, authUrl); + return new User(new SyncUser(refreshToken.identity(), refreshToken, authUrl)); } catch (JSONException e) { throw new IllegalArgumentException("Could not parse user json: " + user, e); } catch (MalformedURLException e) { @@ -90,13 +78,18 @@ public static User fromJson(String user) { * {@link UserStore}. * * @param token token to represent user. + * @see #getAccessToken() + * */ // FIXME Align with Cocoa on naming public static User fromToken(String token) { // Define a user with unlimited access. Object Server will reject any invalid access anyway. - return new User(null, new Token(token, null, null, Long.MAX_VALUE, Token.Permission.values()), null); + Token refreshToken = new Token(token, null, null, Long.MAX_VALUE, Token.Permission.values()); + SyncUser internalUser = new SyncUser(null, refreshToken, null); + return new User(internalUser); } + // FIXME Javadoc public static User login(final Credentials credentials, final URL authentificationUrl) throws ObjectServerError { return null; // TODO @@ -132,7 +125,7 @@ public void run() { try { AuthenticateResponse result = server.authenticateUser(credentials, authUrl, credentials.shouldCreateUser()); if (result.isValid()) { - User user = new User(result.getRefreshToken().identity(), result.getRefreshToken(), authUrl); + User user = new User(new SyncUser(result.getRefreshToken().identity(), result.getRefreshToken(), authUrl)); postSuccess(user); } else { postError(result.getError()); @@ -172,74 +165,6 @@ public void run() { return authenticateTask; } - private User(String identifier, Token refreshToken, URL authenticationUrl) { - this.identifier = identifier; - this.authentificationUrl = authenticationUrl; - setRefreshToken(refreshToken); - } - - void setRefreshToken(final Token refreshToken) { - if (refreshTask != null) { - refreshTask.cancel(); - refreshTask = null; - } - this.refreshToken = refreshToken; - - if (authentificationUrl == null) { - return; - } - // Schedule a refresh. This method cannot fail, but will continue retrying until either the app is killed - // or the attempt was successful. - // TODO Consider combining refresh across all users? - final long expire = refreshToken.expiresMs(); - final AuthenticationServer server = SyncManager.getAuthServer(); - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { - @Override - public void run() { - long timeToExpiration = System.currentTimeMillis() - expire; - if (timeToExpiration > 0) { - SystemClock.sleep(timeToExpiration); - } - - int attempt = 0; - while (!Thread.interrupted()) { - attempt++; - long sleep = Util.calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); - if (sleep > 0) { - try { - Thread.sleep(sleep); - } catch (InterruptedException e) { - return; // Abort authentication if interrupted. - } - } - try { - RefreshResponse result = server.refresh(refreshToken.value(), authentificationUrl); - if (result.isValid()) { - setRefreshToken(result.getRefreshToken()); - break; - } else { - // FIXME: Log to session events instead - RealmLog.warn("Refreshing login failed: " + result.getErrorCode() + " : " + result.getErrorMessage()); - } - } catch (IOException e) { - // FIXME: Log to session events instead. - RealmLog.info("Refreshing login failed: " + e.toString()); - } - } - } - }); - refreshTask = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); - } - - /** - * Returns true if the User is authenticated by the Realm Object Server. Being authenticated means that the - * user is know by the Realm Object Server, but nothing about which Realms that user might have access to and with - * what kind of permissions. - */ - public boolean isAuthenticated() { - return refreshToken != null && refreshToken.expiresMs() > System.currentTimeMillis(); - } - public void logout() { // TODO Stop any session // TODO Clear all tokens @@ -257,65 +182,27 @@ public void logout() { * @see #fromJson(String) */ public String toJson() { - JSONObject obj = new JSONObject(); - try { - obj.put("identifier", identifier); - obj.put("refreshToken", refreshToken.toJson()); - obj.put("authUrl", authentificationUrl); - // FIXME: Add support for storing access tokens as well - return obj.toString(); - } catch (JSONException e) { - throw new RuntimeException("Could not convert User to JSON", e); - } - } - - public String getIdentifier() { - return identifier; + return syncUser.toJson(); } /** - * Return the access token for the given Realm or {@code null} if no token exists. + * Returns the identity or key of this user on the Realm Object Server. + * + * @return Identity of the user on the Realm Object Server. If the user has logged out or the login has expired + * {@code null} is returned. */ - Token getAccessToken(URI serverUrl) { - return accessTokens.get(serverUrl); - } - - void addAccessToken(URI uri, Token accessToken) { - accessTokens.put(uri, accessToken); + public String getIdentity() { + return syncUser.getIdentity(); } /** - * Adds an access token to this user. - *

    - * An access token is a token granting access to one remote Realm. They are normally fetched transparently when - * opening a Realm, but using this method it is possible to add tokens upfront if they have been fetched or - * created manually. + * Returns this user's access token. This is the users credential for accessing the Realm Object Server and should + * be treated as sensitive data. * - * @param uri {@link java.net.URI} pointing to a remote Realm. - * @param accessToken + * @return The user's access token. If this user has logged out or the login has expired {@code null} is returned. */ - void addAccessToken(URI uri, String accessToken) { - // TODO Currently package protected as we will be unifying the tokens shortly, so each user only has one - // access token that can be used everywhere. Permissions/access are then fully handled by the Object Server. - if (uri == null || accessToken == null) { - throw new IllegalArgumentException("Non-null 'uri' and 'accessToken' required."); - } - uri = SyncConfiguration.resolveServerUrl(uri, identifier); - - // Optimistically create a long-lived token with all permissions. If this is incorrect the Object Server - // will reject it anyway. If tokens are added manually it is up to the user to ensure they are also used - // correctly. - addAccessToken(uri, new Token(accessToken, null, uri.toString(), Long.MAX_VALUE, Token.Permission.values())); - } - - - URL getAuthenticationUrl() { - return authentificationUrl; - } - - // TODO Figure out how to make this non-public - public Token getRefreshToken() { - return refreshToken; + public String getAccessToken() { + return syncUser.getRefreshToken().value(); } @Override @@ -324,6 +211,11 @@ public String toString() { // FIXME Print representation of user, but be careful about printing anything sensitive } + // Expose internal representation for other package protected classes + SyncUser getSyncUser() { + return syncUser; + } + public interface Callback { void onSuccess(User user); void onError(ObjectServerError error); diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticatingState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java similarity index 92% rename from realm/realm-library/src/main/java/io/realm/objectserver/AuthenticatingState.java rename to realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java index f7fad18c54..0ac116e2ee 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticatingState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm.objectserver.internal; +import io.realm.objectserver.*; import io.realm.objectserver.internal.network.NetworkStateReceiver; /** @@ -78,15 +79,15 @@ public void onExitState() { } } - private synchronized void authenticate(final Session session) { + private synchronized void authenticate(final SyncSession session) { session.authenticateRealm(new Runnable() { @Override public void run() { gotoNextState(SessionState.BINDING); } - }, new Session.ErrorHandler() { + }, new io.realm.objectserver.Session.ErrorHandler() { @Override - public void onError(Session session, ObjectServerError error) { + public void onError(io.realm.objectserver.Session session, ObjectServerError error) { // FIXME For critical errors, got directly to STOPPED gotoNextState(SessionState.UNBOUND); } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/BindingState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/BindingState.java similarity index 93% rename from realm/realm-library/src/main/java/io/realm/objectserver/BindingState.java rename to realm/realm-library/src/main/java/io/realm/objectserver/internal/BindingState.java index ae1e81497f..1fa3ac17c8 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/BindingState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/BindingState.java @@ -14,7 +14,10 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm.objectserver.internal; + +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.SessionState; /** * BINDING State. After bind() is called, this state will attempt to bind the local Realm to the remote. This is an diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java similarity index 96% rename from realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java rename to realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java index 15027bb737..5eb347afa2 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/BoundState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java @@ -14,7 +14,10 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm.objectserver.internal; + +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.SessionState; /** * BOUND State. At this state the local Realm is bound to the remote Realm and changes is sent in both diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/FsmAction.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmAction.java similarity index 83% rename from realm/realm-library/src/main/java/io/realm/objectserver/FsmAction.java rename to realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmAction.java index 178ab77f03..781b653953 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/FsmAction.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmAction.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm.objectserver.internal; + +import io.realm.objectserver.*; /** - * As {@link Session} is modeled as a state machine, this interface describe all + * As {@link io.realm.objectserver.Session} is modeled as a state machine, this interface describe all * possible actions in that machine. * * All states should implement this so all possible permutations of state/actions are covered. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/FsmState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java similarity index 76% rename from realm/realm-library/src/main/java/io/realm/objectserver/FsmState.java rename to realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java index eb1262c632..8460a58964 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/FsmState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java @@ -14,26 +14,28 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm.objectserver.internal; + +import io.realm.objectserver.*; /** - * Abstract class containing shared logic for all {@link Session} states. All states must extend this class as it + * Abstract class containing shared logic for all {@link io.realm.objectserver.Session} states. All states must extend this class as it * contains the logic for entering and leaving states. * * TODO Move this to the Object Store */ abstract class FsmState implements FsmAction { - volatile Session session; // This is non-null when this state is active. + volatile SyncSession session; // This is non-null when this state is active. private boolean exiting; // TODO: Remind me again what race condition necessitated this. /** * Entry into the state. This method is also responsible for executing any asynchronous work * this state might run. * - * This should only be called from {@link Session}. + * This should only be called from {@link io.realm.objectserver.Session}. */ - public void entry(Session session) { + public void entry(SyncSession session) { this.session = session; this.exiting = false; onEnterState(); @@ -41,9 +43,9 @@ public void entry(Session session) { /** * Called just before leaving the state. Once this method is called no more state changes can be triggered from - * this state until {@link #entry(Session)} has been called again. - * - * This should only be called from {@link Session}. + * this state until {@link #entry(io.realm.objectserver.Session)} has been called again. + *

    + * This should only be called from {@link io.realm.objectserver.Session}. */ public void exit() { exiting = true; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/InitialState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/InitialState.java similarity index 90% rename from realm/realm-library/src/main/java/io/realm/objectserver/InitialState.java rename to realm/realm-library/src/main/java/io/realm/objectserver/internal/InitialState.java index ce624e05c2..a5d173e6b4 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/InitialState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/InitialState.java @@ -14,7 +14,10 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm.objectserver.internal; + +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.SessionState; /** * INITIAL State. Starting point for the Session Finite-State-Machine. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java index 1726c13e50..7951004d49 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java @@ -3,6 +3,7 @@ import io.realm.RealmConfiguration; import io.realm.objectserver.Session; import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.SyncManager; /** * Class acting as an mediator between the basic Realm APIs and the Object Server APIs. @@ -29,33 +30,33 @@ public class ObjectServerFacade { */ public static void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { if (SYNC_AVAILABLE && configuration instanceof SyncConfiguration) { - Session session = SessionStore.getSession((SyncConfiguration) configuration); + Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); + SyncSession session = SessionStore.getPrivateSession(publicSession); session.notifyCommit(lastSnapshotVersion); } } public static void realmClosed(RealmConfiguration configuration) { if (SYNC_AVAILABLE && configuration instanceof SyncConfiguration) { - SyncConfiguration syncConfig = (SyncConfiguration) configuration; - Session session = SessionStore.getSession(syncConfig); + Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); + SyncSession session = SessionStore.getPrivateSession(publicSession); session.getSyncPolicy().onRealmClosed(session); } } public static void realmOpened(RealmConfiguration configuration) { if (SYNC_AVAILABLE && configuration instanceof SyncConfiguration) { - SyncConfiguration syncConfig = (SyncConfiguration) configuration; - Session session = SessionStore.getSession(syncConfig); + Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); + SyncSession session = SessionStore.getPrivateSession(publicSession); session.getSyncPolicy().onRealmOpened(session); } - } public static String[] getUserAndServerUrl(RealmConfiguration config) { if (SYNC_AVAILABLE && config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; String rosServerUrl = syncConfig.getServerUrl().toString(); - String rosUserToken = syncConfig.getUser().getRefreshToken().value(); + String rosUserToken = syncConfig.getUser().getAccessToken(); return new String[] {rosServerUrl, rosUserToken}; } else { return new String[2]; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java index 3fbe7b088e..786720cc0d 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java @@ -7,46 +7,18 @@ import io.realm.objectserver.Session; import io.realm.objectserver.SyncConfiguration; -import io.realm.objectserver.SyncManager; -import io.realm.objectserver.internal.syncpolicy.AutomaticSyncPolicy; /** * Private class for keeping track of sessions. - * If {@link io.realm.objectserver.Session} moves into the public API at some point, this class can be folded into - * {@link io.realm.objectserver.SyncManager}; + * If {@link io.realm.objectserver.Session} and {@link SyncSession} are combined at some point, this class can + * be folded into {@link io.realm.objectserver.SyncManager}; */ public class SessionStore { // Map of between a local Realm path and any associated sessionInfo private static HashMap sessions = new HashMap(); + private static HashMap privateSessions = new HashMap(); - - /** - * Gets any cached {@link Session} for the given {@link SyncConfiguration} or create a new one if - * no one exists. - * - * @param syncConfiguration configuration object for the synchronized Realm. - * @return the {@link Session} for the specified Realm. - */ - public static synchronized Session getSession(SyncConfiguration syncConfiguration) { - if (syncConfiguration == null) { - throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); - } - - String localPath = syncConfiguration.getPath(); - Session session = sessions.get(localPath); - if (session == null) { - session = new Session(syncConfiguration, SyncManager.getAuthServer(), new AutomaticSyncPolicy()); - session.getSyncPolicy().onSessionCreated(session); - sessions.put(localPath, session); - } - - return session; - } - - /** - * Removes a session. Should only be once it has been closed. - */ static synchronized void removeSession(Session session) { if (session == null) { return; @@ -62,10 +34,31 @@ static synchronized void removeSession(Session session) { } } - /** - * Returns a list of all sessions being tracked. - */ - public static Collection getSession() { - return sessions.values(); + public static synchronized void addSession(Session publicSession, SyncSession internalSession) { + String localPath = publicSession.getConfiguration().getPath(); + sessions.put(localPath, publicSession); + privateSessions.put(localPath, internalSession); + } + + public static synchronized boolean hasSession(SyncConfiguration config) { + String localPath = config.getPath(); + return sessions.containsKey(localPath); } + + public static synchronized Session getPublicSession(SyncConfiguration config) { + String localPath = config.getPath(); + return sessions.get(localPath); + } + + public static synchronized SyncSession getPrivateSession(Session session) { + String localPath = session.getConfiguration().getPath(); + return privateSessions.get(localPath); + } + + public static Collection getAllSessions() { + return privateSessions.values(); + } + } + + diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/StoppedState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/StoppedState.java similarity index 87% rename from realm/realm-library/src/main/java/io/realm/objectserver/StoppedState.java rename to realm/realm-library/src/main/java/io/realm/objectserver/internal/StoppedState.java index 0871f454fc..82d9a036e8 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/StoppedState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/StoppedState.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm.objectserver.internal; + +import io.realm.objectserver.*; /** - * STOPPED State. This is the final state for a {@link Session}. After this, all actions will throw an + * STOPPED State. This is the final state for a {@link io.realm.objectserver.Session}. After this, all actions will throw an * {@link IllegalStateException}. */ class StoppedState extends FsmState { @@ -25,7 +27,7 @@ class StoppedState extends FsmState { @Override public void onEnterState() { session.stopNativeSession(); - session.configuration.getSyncPolicy().onSessionStopped(session); + session.getSyncPolicy().onSessionStopped(session); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java new file mode 100644 index 0000000000..16ad2500c1 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java @@ -0,0 +1,373 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal; + +import java.net.URI; +import java.util.HashMap; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import io.realm.RealmAsyncTask; +import io.realm.internal.Keep; +import io.realm.internal.Util; +import io.realm.log.RealmLog; +import io.realm.objectserver.ErrorCode; +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.Session; +import io.realm.objectserver.SessionState; +import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.SyncManager; +import io.realm.objectserver.User; +import io.realm.objectserver.internal.network.AuthenticateResponse; +import io.realm.objectserver.internal.network.AuthenticationServer; +import io.realm.objectserver.internal.network.NetworkStateReceiver; +import io.realm.objectserver.internal.syncpolicy.SyncPolicy; + +/** + * Internal class describing a Realm Object Server Session. + * There is currently a split between the public {@link io.realm.objectserver.Session} and this class. + * This class is intended as a wrapper around Object Stores Sync Session, but it is not that yet. + *

    + * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening + * a Realm instance. Once a session has been created it will continue to exist until explicitly closed or the + * underlying Realm file is deleted. + *

    + * It is normally not necessary to interact directly with a session. That should be done by the {@code SyncPolicy} + * defined using {@code io.realm.objectserver.SyncConfiguration.Builder#syncPolicy(SyncPolicy)}. + *

    + * A session has a lifecycle consisting of the following states: + *

    + *

    + *
  • + * INITIAL Initial state when creating the Session object. No connections to the object server have been + * made yet. At this point it is possible to register any relevant error and event listeners. Calling + * {@link #start()} will cause the session to become UNBOUND and notify the {@code SyncPolicy} that the + * session is ready by calling {@code SyncPolicy#onSessionCreated(Session)}. + *
  • + *
  • + * UNBOUND When a session is unbound, no synchronization between the local and remote Realm is happening. + * Call {@link #bind()} to start synchronizing changes. + *
  • + *
  • + * BINDING A session is in the process of binding a local Realm to a remote one. Calling {@link #unbind()} + * at this stage, will cancel the process. If binding fails, the session will revert to being unbound and the error + * will be reported to the error handler. + *
  • + *
  • + * AUTHENTICATING During binding, if a users access has expired, the session will be AUTHENTICATING. + * During this state, Realm will automatically try to acquire new valid credentials. If this succeed BINDING + * will automatically be resumed, if not, the session will become UNBOUND or STOPPED and an + * appropriate error reported. + *
  • + *
  • + * BOUND A bound session has an active connection to the remote Realm and will synchronize any changes + * immediately. + *
  • + *
  • + * STOPPED The session are in an unrecoverable state. Check the error log for additional information, but + * the type of errors are usually wrong credentials for the Realm being accessed or a mismatching Object Server. + * Most problems can be solved by creating a new {@link SyncConfiguration} with a new {@code serverUrl} and + * {@code user}. + *
  • + *
    + * + * This object is thread safe. + */ +@Keep +public final class SyncSession { + + private final HashMap FSM = new HashMap(); + + // Variables used by the FSM + final SyncConfiguration configuration; + private final AuthenticationServer authServer; + private final Session.ErrorHandler errorHandler; + private long nativeSessionPointer; + private final SyncUser user; + RealmAsyncTask networkRequest; + NetworkStateReceiver.ConnectionListener networkListener; + private SyncPolicy syncPolicy; + + // Keeping track of current FSM state + private SessionState currentStateDescription; + private FsmState currentState; + private Session userSession; + + /** + * Creates a new Object Server Session. + * + * @param syncConfiguration Sync configuration defining this session + * @param authServer Authentication server used to refresh credentials if needed + * @param policy Sync Policy to use by this Session. + */ + public SyncSession(SyncConfiguration syncConfiguration, + AuthenticationServer authServer, + SyncUser user, + SyncPolicy policy, + Session.ErrorHandler errorHandler) { + this.configuration = syncConfiguration; + this.user = user; + this.authServer = authServer; + this.errorHandler = errorHandler; + this.syncPolicy = policy; + setupStateMachine(); + } + + private void setupStateMachine() { + FSM.put(SessionState.INITIAL, new InitialState()); + FSM.put(SessionState.UNBOUND, new UnboundState()); + FSM.put(SessionState.BINDING, new BindingState()); + FSM.put(SessionState.AUTHENTICATING, new AuthenticatingState()); + FSM.put(SessionState.BOUND, new BoundState()); + FSM.put(SessionState.STOPPED, new StoppedState()); + RealmLog.debug("Session started: " + configuration.getServerUrl()); + currentState = FSM.get(SessionState.INITIAL); + currentState.entry(this); + } + + // Goto the next state. The FsmState classes are responsible for calling this method as a reaction to a FsmAction + // being called or an internal action triggering a state transition. + void nextState(SessionState nextStateDescription) { + currentState.exit(); + FsmState nextState = FSM.get(nextStateDescription); + if (nextState == null) { + throw new IllegalStateException("No state was configured to handle: " + nextStateDescription); + } + RealmLog.debug("Session[%s]: %s -> %s", configuration.getServerUrl(), currentStateDescription, nextStateDescription); + currentStateDescription = nextStateDescription; + currentState = nextState; + nextState.entry(this); + } + + /** + * Starts the session. This will cause the session to come unbound. {@link #bind()} must be called to + * actually start synchronizing data. + */ + public synchronized void start() { + currentState.onStart(); + } + + /** + * Stops the session. The session can no longer be used. + */ + public synchronized void stop() { + currentState.onStop(); + } + + /** + * Binds the local Realm to the remote Realm. Once bound, changes to either the local or Remote Realm will be + * synchronized immediately. + *

    + * While this method will return immediately, binding a Realm is not guaranteed to succeed. Possible reasons for + * failure could be either if the device is offline or credentials have expired. Binding is an asynchronous + * operation and all errors will be sent first to {@code SyncPolicy#onError(Session, ObjectServerError)} and if the + * SyncPolicy doesn't handle it, to the {@link Session.ErrorHandler} defined by + * {@link SyncConfiguration.Builder#errorHandler(Session.ErrorHandler)}. + */ + public synchronized void bind() { + currentState.onBind(); + } + + /** + * Stops a local Realm from synchronizing changes with the remote Realm. + *

    + * It is possible to call {@link #bind()} again after a Realm has been unbound. + */ + public synchronized void unbind() { + currentState.onUnbind(); + } + + /** + * Notify the session that an error has occurred. + * + * @param error the kind of err + */ + public synchronized void onError(ObjectServerError error) { + currentState.onError(error); // FSM needs to respond to the error first, before notifying the User + if (errorHandler != null) { + errorHandler.onError(this.getUserSession(), error); + } + } + + // Called from Session.cpp and SyncMaanger + // This callback will happen on the thread running the Sync Client. + void notifySessionError(int errorCode, String errorMessage) { + ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); + onError(error); + } + + /** + * Checks if the local Realm is bound to the remote Realm and can synchronize any changes happening on either + * sides. + * + * @return {@code true} if the local Realm is bound to the remote Realm, {@code false} otherwise. + */ + boolean isBound() { + return currentStateDescription == SessionState.BOUND; + } + + // + // Package protected methods used by the FSM states to manipulate session variables. + // + + // Create a native session. The session abstraction in Realm Core doesn't support multiple calls to bind()/unbind() + // yet, so the Java SyncSession must manually create/and close the native sessions as needed. + void createNativeSession() { + nativeSessionPointer = nativeCreateSession(configuration.getPath()); + } + + void stopNativeSession() { + if (nativeSessionPointer != 0) { + nativeUnbind(nativeSessionPointer); + nativeSessionPointer = 0; + } + } + + // Bind with proper access tokens + // Access tokens are presumed to be present and valid at this point + void bindWithTokens() { + Token accessToken = user.getAccessToken(configuration.getServerUrl()); + if (accessToken == null) { + throw new IllegalStateException("User '" + user.toString() + "' does not have an access token for " + + configuration.getServerUrl()); + } + nativeBind(nativeSessionPointer, configuration.getServerUrl().toString(), accessToken.value()); + } + + // Authenticate by getting access tokens for the specific Realm + void authenticateRealm(final Runnable onSuccess, final Session.ErrorHandler errorHandler) { + if (networkRequest != null) { + networkRequest.cancel(); + } + // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { + @Override + public void run() { + int attempt = 0; + boolean success; + ObjectServerError error = null; + while (true) { + attempt++; + long sleep = Util.calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + return; // Abort authentication if interrupted. + } + } + + AuthenticateResponse response = authServer.authenticateRealm( + user.getRefreshToken(), + configuration.getServerUrl(), + user.getAuthenticationUrl() + ); + if (response.isValid()) { + user.addAccessToken(configuration.getServerUrl(), response.getAccessToken()); + success = true; + break; + } else { + // Only retry in case of IO exceptions, since that might be network timeouts etc. + // All other errors indicate a bigger problem, so stop trying to authenticate and + // unbind + ObjectServerError responseError = response.getError(); + if (responseError.errorCode() != ErrorCode.IO_EXCEPTION) { + success = false; + error = responseError; + break; + } + } + } + + if (success) { + onSuccess.run(); + } else { + errorHandler.onError(getUserSession(), error); + } + } + }); + networkRequest = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); + } + + boolean isAuthenticated(SyncConfiguration configuration) { + return user.isAuthenticated(configuration); + } + + /** + * Returns the {@link SyncConfiguration} that is responsible for controlling this session. + * + * @return SyncConfiguration that defines and controls this session. + */ + public SyncConfiguration getConfiguration() { + return configuration; + } + + /** + * Returns the {@link User} defined by the {@link SyncConfiguration} that is used to connect to the + * Realm Object Server. + * + * @return {@link User} used to authenticate the session on the Realm Object Server. + */ + public User getUser() { + return configuration.getUser(); + } + + /** + * Returns the {@link URI} describing the remote Realm this session connects to and synchronizes changes with. + * + * @return {@link URI} describing the remote Realm. + */ + public URI getServerUrl() { + return configuration.getServerUrl(); + } + + /** + * Returns the state of this session. + * + * @return The current {@link SessionState} for this session. + */ + public SessionState getState() { + return currentStateDescription; + } + + /** + * FIXME: Find a way to keep this out of the public API. Could probably happen as part of moving everything to the + * Object Store. + * + * Notify session that a commit on the device has happened. + */ + void notifyCommit(long version) { + if (isBound()) { + nativeNotifyCommitHappened(nativeSessionPointer, version); + } + } + + SyncPolicy getSyncPolicy() { + return syncPolicy; + } + + Session getUserSession() { + return userSession; + } + + private native long nativeCreateSession(String localRealmPath); + private native void nativeBind(long nativeSessionPointer, String remoteRealmUrl, String userToken); + private native void nativeUnbind(long nativeSessionPointer); + private native void nativeRefresh(long nativeSessionPointer, String userToken); + private native void nativeNotifyCommitHappened(long sessionPointer, long version); +} + diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java new file mode 100644 index 0000000000..b1e5c24467 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java @@ -0,0 +1,209 @@ +package io.realm.objectserver.internal;/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +import android.os.SystemClock; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.URI; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import io.realm.RealmAsyncTask; +import io.realm.internal.IOException; +import io.realm.internal.Util; +import io.realm.log.RealmLog; +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.SyncManager; +import io.realm.objectserver.User; +import io.realm.objectserver.internal.network.AuthenticationServer; +import io.realm.objectserver.internal.network.RefreshResponse; + +/** + * Internal representation of a user on the Realm Object Server. + * The public API is defined by {@link User}. + */ +public class SyncUser { + + // Time left on current refresh token, when we want to begin refreshing it. + // Failing to refresh it before it expires, will result in the user getting logged out. + private RealmAsyncTask refreshTask; + + private final String identifier; + private Token refreshToken; + private URL authentificationUrl; + private Map accessTokens = new HashMap(); + + /** + * Create a new Realm Object Server User + */ + public SyncUser(String identifier, Token refreshToken, URL authenticationUrl) { + this.identifier = identifier; + this.authentificationUrl = authenticationUrl; + setRefreshToken(refreshToken); + } + + public void setRefreshToken(final Token refreshToken) { + if (refreshTask != null) { + refreshTask.cancel(); + refreshTask = null; + } + this.refreshToken = refreshToken; + + if (authentificationUrl == null) { + return; + } + // Schedule a refresh. This method cannot fail, but will continue retrying until either the app is killed + // or the attempt was successful. + // TODO Consider combining refresh across all users? + final long expire = refreshToken.expiresMs(); + final AuthenticationServer server = SyncManager.getAuthServer(); + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { + @Override + public void run() { + long timeToExpiration = System.currentTimeMillis() - expire; + if (timeToExpiration > 0) { + SystemClock.sleep(timeToExpiration); + } + + int attempt = 0; + while (!Thread.interrupted()) { + attempt++; + long sleep = Util.calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + return; // Abort authentication if interrupted. + } + } + try { + RefreshResponse result = server.refresh(refreshToken.value(), authentificationUrl); + if (result.isValid()) { + setRefreshToken(result.getRefreshToken()); + break; + } else { + // FIXME: Log to session events instead + RealmLog.warn("Refreshing login failed: " + result.getErrorCode() + " : " + result.getErrorMessage()); + } + } catch (IOException e) { + // FIXME: Log to session events instead. + RealmLog.info("Refreshing login failed: " + e.toString()); + } + } + } + }); + refreshTask = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); + } + + /** + * Returns {@code true} if the user is logged into the Realm Object Server. If this method returns {@code true it + * means that the user has valid credentials that have not expired. + *

    + * The user might still be logged out by the Realm Object Server which will not be detected before the user + * tries to actively synchronize a Realm. If a logged out user tries to synchronize a Realm, errors will be reported + * to the {@link io.realm.objectserver.Session.ErrorHandler} defined by + * {@link io.realm.objectserver.SyncConfiguration.Builder#errorHandler}. + * + * @return {@code true} if the User is considered logged into the Realm Object Server, {@code false} otherwise. + */ + public boolean isAuthenticated() { + return refreshToken != null && refreshToken.expiresMs() > System.currentTimeMillis(); + } + + /** + * Checks if the user has access to the given Realm. Being authenticated means that the + * user is know by the Realm Object Server and have been granted access to the given Realm. + * + * Authenticating will happen automatically as part of opening a Realm. + */ + public boolean isAuthenticated(SyncConfiguration configuration) { + Token token = getAccessToken(configuration.getServerUrl()); + return token != null && token.expiresMs() > System.currentTimeMillis(); + } + + public void logout() { + // TODO Stop any session + // TODO Clear all tokens + } + + public String toJson() { + JSONObject obj = new JSONObject(); + try { + obj.put("identifier", identifier); + obj.put("refreshToken", refreshToken.toJson()); + obj.put("authUrl", authentificationUrl); + // FIXME: Add support for storing access tokens as well + return obj.toString(); + } catch (JSONException e) { + throw new RuntimeException("Could not convert User to JSON", e); + } + } + + public String getIdentity() { + return identifier; + } + + public Token getAccessToken(URI serverUrl) { + return accessTokens.get(serverUrl); + } + + public void addAccessToken(URI uri, Token accessToken) { + accessTokens.put(uri, accessToken); + } + + /** + * Adds an access token to this user. + *

    + * An access token is a token granting access to one remote Realm. Access Tokens are normally fetched transparently + * when opening a Realm, but using this method it is possible to add tokens upfront if they have been fetched or + * created manually. + * + * @param uri {@link java.net.URI} pointing to a remote Realm. + * @param accessToken + */ + public void addAccessToken(URI uri, String accessToken) { + // TODO Currently package protected as we will be unifying the tokens shortly, so each user only has one + // access token that can be used everywhere. Permissions/access are then fully handled by the Object Server. + if (uri == null || accessToken == null) { + throw new IllegalArgumentException("Non-null 'uri' and 'accessToken' required."); + } + uri = SyncUtil.getFullServerUrl(uri, identifier); + + // Optimistically create a long-lived token with all permissions. If this is incorrect the Object Server + // will reject it anyway. If tokens are added manually it is up to the user to ensure they are also used + // correctly. + addAccessToken(uri, new Token(accessToken, null, uri.toString(), Long.MAX_VALUE, Token.Permission.values())); + } + + URL getAuthenticationUrl() { + return authentificationUrl; + } + + public Token getRefreshToken() { + return refreshToken; + } + + public interface Callback { + void onSuccess(User user); + void onError(ObjectServerError error); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUtil.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUtil.java new file mode 100644 index 0000000000..675bcc7be0 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUtil.java @@ -0,0 +1,21 @@ +package io.realm.objectserver.internal; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Helper class for Object Server classes. + */ +public class SyncUtil { + + /** + * Fully resolve an URL so all placeholder objects are replaced with the user identity. + */ + public static URI getFullServerUrl(URI serverUrl, String userIdentity) { + try { + return new URI(serverUrl.toString().replace("/~/", "/" + userIdentity + "/")); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Could not replace '/~/' with a valid user ID.", e); + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/UnboundState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/UnboundState.java similarity index 88% rename from realm/realm-library/src/main/java/io/realm/objectserver/UnboundState.java rename to realm/realm-library/src/main/java/io/realm/objectserver/internal/UnboundState.java index 89b1a33588..f2045abc3e 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/UnboundState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/UnboundState.java @@ -14,7 +14,11 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm.objectserver.internal; + +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.SessionState; +import io.realm.objectserver.internal.FsmState; /** * UNBOUND State. This is the default state after a session has been started and no attempt at binding the local Realm diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java index fcbf81da27..eabb6b644a 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java @@ -17,7 +17,7 @@ package io.realm.objectserver.internal.syncpolicy; import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.Session; +import io.realm.objectserver.internal.SyncSession; /** * This SyncPolicy will automatically start synchronizing changes to a Realm as soon as it is opened. @@ -26,28 +26,28 @@ public class AutomaticSyncPolicy implements SyncPolicy { @Override - public void onRealmOpened(Session session) { + public void onRealmOpened(SyncSession session) { session.bind(); // Bind Realm first time it is opened. } @Override - public void onRealmClosed(Session session) { + public void onRealmClosed(SyncSession session) { // TODO Sync need to expose callback when there is no more local changes // For now just keep the session open. } @Override - public void onSessionCreated(Session session) { + public void onSessionCreated(SyncSession session) { session.start(); } @Override - public void onSessionStopped(Session session) { + public void onSessionStopped(SyncSession session) { // Do nothing } @Override - public boolean onError(Session session, ObjectServerError error) { + public boolean onError(SyncSession session, ObjectServerError error) { switch(error.category()) { case FATAL: return false; // Report all fatal errors to the user @@ -61,7 +61,7 @@ public boolean onError(Session session, ObjectServerError error) { } } - private void rebind(Session session) { + private void rebind(SyncSession session) { // FIXME: Do not rebind uncritically. Figure out a good strategy for this. // See https://realmio.slack.com/archives/sync-core/p1472415880000002 session.bind(); diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java index 3bb5a064fb..873092f090 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java @@ -18,55 +18,59 @@ import io.realm.objectserver.ObjectServerError; import io.realm.objectserver.Session; +import io.realm.objectserver.internal.SyncSession; /** * Interface describing a given synchronization policy with the Realm Object Server. *

    - * The sole purpose of classes implementing this interface is to call {@link Session#bind()} and {@link Session#unbind()} - * as needed, which will control when changes are synchronized between a local and remote Realm. + * The sole purpose of classes implementing this interface is to call {@link SyncSession#bind()} and + * {@link SyncSession#unbind()} as needed, which will control when changes are synchronized between a local and + * remote Realm. * - * The SyncPolicy is not responsible for managing the lifecycle of the {@link Session} in general. So any - * implementation of this class should avoid calling {@link Session#stop()} and {@link Session#start()}. + * The SyncPolicy is not responsible for managing the lifecycle of the {@link SyncSession} in general. So any + * implementation of this class should avoid calling {@link SyncSession#stop()} and + * {@link SyncSession#start()}. * - * If a session is stopped, {@link Session#unbind()} is automatically called and any further calls to - * {@link Session#bind()} and {@link Session#unbind()} are ignored. {@link #onSessionStopped(Session)} ()} will then be - * called so the sync policy have a chance to clean up any resources it might be using. + * If a session is stopped, {@link SyncSession#unbind()} is automatically called and any further calls to + * {@link SyncSession#bind()} and {@link SyncSession#unbind()} are ignored. + * {@link #onSessionStopped(SyncSession)} ()} will then be called so the sync policy have a chance to clean up + * any resources it might be using. */ -// TODO: Still experimental API. We need to figure out exactly which events we expose and how -// TODO: Should we keep this protected for now? +// Internal until we are sure this is the API we want public interface SyncPolicy { /** * Called when the session object is created. At this point it is possible to register any relevant error and event * listeners in either the Android framework or for the session itself. * - * {@link Session#start()} will be automatically called after this method. + * {@link SyncSession#start()} will be automatically called after this method. * * @param session the {@link Session} just created. It has not yet been started. */ - void onSessionCreated(Session session); + void onSessionCreated(SyncSession session); /** - * The {@link Session} has been stopped and will ignore any further calls to {@link Session#bind()} and - * {@link Session#unbind()}. All external resources should be cleaned up. + * The {@link SyncSession} has been stopped and will ignore any further calls to + * {@link SyncSession#bind()} and {@link SyncSession#unbind()}. All external resources should be + * cleaned up. * - * @param session {@link Session} that has been stopped. + * @param session {@link SyncSession} that has been stopped. */ - void onSessionStopped(Session session); + void onSessionStopped(SyncSession session); /** * Called the first time a Realm is opened on any thread. * - * @param session {@link Session} associated with this Realm. + * @param session {@link SyncSession} associated with this Realm. */ - void onRealmOpened(Session session); + void onRealmOpened(SyncSession session); /** * Called when the last Realm instance across all threads have been closed. * - * @param session {@link Session} associated with this Realm. + * @param session {@link SyncSession} associated with this Realm. */ - void onRealmClosed(Session session); + void onRealmClosed(SyncSession session); /** * Called if an error occurred in the underlying session. In many cases this has caused the session to become @@ -80,5 +84,5 @@ public interface SyncPolicy { * * @see io.realm.objectserver.SyncConfiguration.Builder#errorHandler(Session.ErrorHandler) */ - boolean onError(Session session, ObjectServerError error); + boolean onError(SyncSession session, ObjectServerError error); } From 2d6e0869d455c57c9d19da3981d393969741979a Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 16 Sep 2016 14:37:58 +0200 Subject: [PATCH 0045/2110] Supporting both additive and manual schema modes (#91) * Adding very thin wrappers for Object Store's ObjectSchema and Property. * Adding method for building object schema is proxy classes * Adding rudimentary support for Object Store schemas. Using ObjectStore::update_schema() to update schema for the additive mode. * Disallowing destructive schema changes in additive mode. --- .../io/realm/processor/ClassMetaData.java | 23 +++ .../processor/RealmProxyClassGenerator.java | 51 ++++++ .../RealmProxyMediatorGenerator.java | 20 +++ .../io/realm/AllTypesRealmProxy.java | 25 +++ .../io/realm/BooleansRealmProxy.java | 14 ++ .../io/realm/NullTypesRealmProxy.java | 34 ++++ .../io/realm/RealmDefaultModuleMediator.java | 12 ++ .../resources/io/realm/SimpleRealmProxy.java | 12 ++ .../io/realm/objectserver/SchemaTests.java | 147 ++++++++++++++++++ .../realm-library/src/main/cpp/CMakeLists.txt | 1 + .../src/main/cpp/io_realm_Property.cpp | 76 +++++++++ .../main/cpp/io_realm_RealmObjectSchema.cpp | 97 ++++++++++++ .../src/main/cpp/io_realm_RealmSchema.cpp | 73 +++++++++ .../cpp/io_realm_internal_SharedRealm.cpp | 12 ++ realm/realm-library/src/main/cpp/util.hpp | 4 + .../src/main/java/io/realm/BaseRealm.java | 3 + .../src/main/java/io/realm/Property.java | 57 +++++++ .../src/main/java/io/realm/Realm.java | 53 +++++-- .../main/java/io/realm/RealmObjectSchema.java | 104 ++++++++++++- .../src/main/java/io/realm/RealmSchema.java | 132 ++++++++++++---- .../io/realm/internal/RealmProxyMediator.java | 13 +- .../java/io/realm/internal/SharedRealm.java | 7 +- .../internal/modules/CompositeMediator.java | 8 + .../internal/modules/FilterableMediator.java | 7 + .../realm/objectserver/SyncConfiguration.java | 3 + 25 files changed, 940 insertions(+), 48 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_Property.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp create mode 100644 realm/realm-library/src/main/java/io/realm/Property.java diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index e13bbc4626..972a0f6314 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -373,6 +373,29 @@ public boolean isNullable(VariableElement variableElement) { return nullableFields.contains(variableElement); } + /** + * Checks if a VariableElement is indexed. + * + * @param variableElement the element/field + * @return {@code true} if a VariableElement is indexed, {@code false} otherwise. + */ + public boolean isIndexed(VariableElement variableElement) { + return indexedFields.contains(variableElement); + } + + /** + * Checks if a VariableElement is a primary key. + * + * @param variableElement the element/field + * @return {@code true} if a VariableElement is primary key, {@code false} otherwise. + */ + public boolean isPrimaryKey(VariableElement variableElement) { + if (primaryKey == null) { + return false; + } + return primaryKey.equals(variableElement); + } + private boolean isValidPrimaryKeyType(TypeMirror type) { for (TypeMirror validType : validPrimaryKeyTypes) { if (typeUtils.isAssignable(type, validType)) { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 5eff1c6043..614a8d4301 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -67,6 +67,8 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("android.util.JsonReader"); imports.add("android.util.JsonToken"); imports.add("io.realm.RealmFieldType"); + imports.add("io.realm.RealmObjectSchema"); + imports.add("io.realm.RealmSchema"); imports.add("io.realm.exceptions.RealmMigrationNeededException"); imports.add("io.realm.internal.ColumnInfo"); imports.add("io.realm.internal.RealmObjectProxy"); @@ -106,6 +108,7 @@ public void generate() throws IOException, UnsupportedOperationException { emitClassFields(writer); emitConstructor(writer); emitAccessors(writer); + emitCreateRealmObjectSchemaMethod(writer); emitInitTableMethod(writer); emitValidateTableMethod(writer); emitGetTableNameMethod(writer); @@ -342,6 +345,54 @@ private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOExce writer.emitEmptyLine(); } + private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOException { + writer.beginMethod( + "RealmObjectSchema", // Return type + "createRealmObjectSchema", // Method name + EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers + "RealmSchema", "realmSchema"); // Argument type & argument name + + writer.beginControlFlow("if (!realmSchema.contains(\"" + this.simpleClassName + "\"))"); + writer.emitStatement("RealmObjectSchema realmObjectSchema = realmSchema.create(\"%s\")", this.simpleClassName); + + // For each field generate corresponding table index constant + for (VariableElement field : metadata.getFields()) { + String fieldName = field.getSimpleName().toString(); + String fieldTypeCanonicalName = field.asType().toString(); + String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); + + if (Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeCanonicalName)) { + String nullableFlag = (metadata.isNullable(field) ? "!" : "") + "Property.REQUIRED"; + String indexedFlag = (metadata.isIndexed(field) ? "" : "!") + "Property.INDEXED"; + String primaryKeyFlag = (metadata.isPrimaryKey(field) ? "" : "!") + "Property.PRIMARY_KEY"; + writer.emitStatement("realmObjectSchema.add(new Property(\"%s\", %s, %s, %s, %s))", + fieldName, + Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeCanonicalName), + primaryKeyFlag, + indexedFlag, + nullableFlag); + } else if (Utils.isRealmModel(field)) { + writer.beginControlFlow("if (!realmSchema.contains(\"" + fieldTypeSimpleName + "\"))"); + writer.emitStatement("%s%s.createRealmObjectSchema(realmSchema)", fieldTypeSimpleName, Constants.PROXY_SUFFIX); + writer.endControlFlow(); + writer.emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\")))", + fieldName, fieldTypeSimpleName); + } else if (Utils.isRealmList(field)) { + String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); + writer.beginControlFlow("if (!realmSchema.contains(\"" + genericTypeSimpleName +"\"))"); + writer.emitStatement("%s%s.createRealmObjectSchema(realmSchema)", genericTypeSimpleName, Constants.PROXY_SUFFIX); + writer.endControlFlow(); + writer.emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\")))", + fieldName, genericTypeSimpleName); + } + } + writer.emitStatement("return realmObjectSchema"); + writer.endControlFlow(); + writer.emitStatement("return realmSchema.get(\"" + this.simpleClassName + "\")"); + writer.endMethod(); + writer.emitEmptyLine(); + } + private void emitInitTableMethod(JavaWriter writer) throws IOException { writer.beginMethod( "Table", // Return type diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index e78a679736..cce7d21c7d 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -78,6 +78,7 @@ public void generate() throws IOException { "io.realm.internal.RealmObjectProxy", "io.realm.internal.RealmProxyMediator", "io.realm.internal.Table", + "io.realm.RealmObjectSchema", "org.json.JSONException", "org.json.JSONObject" ); @@ -94,6 +95,7 @@ public void generate() throws IOException { emitFields(writer); emitCreateTableMethod(writer); + emitCreateRealmObjectSchema(writer); emitValidateTableMethod(writer); emitGetFieldNamesMethod(writer); emitGetTableNameMethod(writer); @@ -123,6 +125,24 @@ private void emitFields(JavaWriter writer) throws IOException { writer.emitEmptyLine(); } + private void emitCreateRealmObjectSchema(JavaWriter writer) throws IOException { + writer.emitAnnotation("Override"); + writer.beginMethod( + "RealmObjectSchema", + "createRealmObjectSchema", + EnumSet.of(Modifier.PUBLIC), + "Class", "clazz", "RealmSchema", "realmSchema" + ); + emitMediatorSwitch(new ProxySwitchStatement() { + @Override + public void emitStatement(int i, JavaWriter writer) throws IOException { + writer.emitStatement("return %s.createRealmObjectSchema(realmSchema)", qualifiedProxyClasses.get(i)); + } + }, writer); + writer.endMethod(); + writer.emitEmptyLine(); + } + private void emitCreateTableMethod(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); writer.beginMethod( diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index c5c43b8825..887452be52 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -6,6 +6,8 @@ import android.util.JsonReader; import android.util.JsonToken; import io.realm.RealmFieldType; +import io.realm.RealmObjectSchema; +import io.realm.RealmSchema; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -237,6 +239,29 @@ static final class AllTypesColumnInfo extends ColumnInfo { } } + public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { + if (!realmSchema.contains("AllTypes")) { + RealmObjectSchema realmObjectSchema = realmSchema.create("AllTypes"); + realmObjectSchema.add(new Property("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("columnDouble", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("columnBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + if (!realmSchema.contains("AllTypes")) { + AllTypesRealmProxy.createRealmObjectSchema(realmSchema); + } + realmObjectSchema.add(new Property("columnObject", RealmFieldType.OBJECT, realmSchema.get("AllTypes"))); + if (!realmSchema.contains("AllTypes")) { + AllTypesRealmProxy.createRealmObjectSchema(realmSchema); + } + realmObjectSchema.add(new Property("columnRealmList", RealmFieldType.LIST, realmSchema.get("AllTypes"))); + return realmObjectSchema; + } + return realmSchema.get("AllTypes"); + } + public static Table initTable(SharedRealm sharedRealm) { if (!sharedRealm.hasTable("class_AllTypes")) { Table table = sharedRealm.getTable("class_AllTypes"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 6f555eb3f6..ea864542cc 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -6,6 +6,8 @@ import android.util.JsonReader; import android.util.JsonToken; import io.realm.RealmFieldType; +import io.realm.RealmObjectSchema; +import io.realm.RealmSchema; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -115,6 +117,18 @@ static final class BooleansColumnInfo extends ColumnInfo { proxyState.getRow$realm().setBoolean(columnInfo.anotherBooleanIndex, value); } + public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { + if (!realmSchema.contains("Booleans")) { + RealmObjectSchema realmObjectSchema = realmSchema.create("Booleans"); + realmObjectSchema.add(new Property("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("anotherBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + return realmObjectSchema; + } + return realmSchema.get("Booleans"); + } + public static Table initTable(SharedRealm sharedRealm) { if (!sharedRealm.hasTable("class_Booleans")) { Table table = sharedRealm.getTable("class_Booleans"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index e4faa4691d..52b30c5ce3 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -6,6 +6,8 @@ import android.util.JsonReader; import android.util.JsonToken; import io.realm.RealmFieldType; +import io.realm.RealmObjectSchema; +import io.realm.RealmSchema; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -493,6 +495,38 @@ static final class NullTypesColumnInfo extends ColumnInfo { proxyState.getRow$realm().setLink(columnInfo.fieldObjectNullIndex, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex()); } + public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { + if (!realmSchema.contains("NullTypes")) { + RealmObjectSchema realmObjectSchema = realmSchema.create("NullTypes"); + realmObjectSchema.add(new Property("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldBooleanNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldBytesNotNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldBytesNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldByteNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldByteNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldShortNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldShortNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldIntegerNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldIntegerNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldLongNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldLongNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldFloatNotNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldFloatNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldDoubleNotNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldDoubleNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add(new Property("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + if (!realmSchema.contains("NullTypes")) { + NullTypesRealmProxy.createRealmObjectSchema(realmSchema); + } + realmObjectSchema.add(new Property("fieldObjectNull", RealmFieldType.OBJECT, realmSchema.get("NullTypes"))); + return realmObjectSchema; + } + return realmSchema.get("NullTypes"); + } + public static Table initTable(SharedRealm sharedRealm) { if (!sharedRealm.hasTable("class_NullTypes")) { Table table = sharedRealm.getTable("class_NullTypes"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 8b9a712837..1fe12ab673 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -2,6 +2,7 @@ import android.util.JsonReader; +import io.realm.RealmObjectSchema; import io.realm.internal.ColumnInfo; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; @@ -40,6 +41,17 @@ public Table createTable(Class clazz, SharedRealm sharedRe } } + @Override + public RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema realmSchema) { + checkClass(clazz); + + if (clazz.equals(some.test.AllTypes.class)) { + return io.realm.AllTypesRealmProxy.createRealmObjectSchema(realmSchema); + } else { + throw getMissingProxyClassException(clazz); + } + } + @Override public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm) { checkClass(clazz); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index cf863899ca..5163187160 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -6,6 +6,8 @@ import android.util.JsonReader; import android.util.JsonToken; import io.realm.RealmFieldType; +import io.realm.RealmObjectSchema; +import io.realm.RealmSchema; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -87,6 +89,16 @@ static final class SimpleColumnInfo extends ColumnInfo { proxyState.getRow$realm().setLong(columnInfo.ageIndex, value); } + public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { + if (!realmSchema.contains("Simple")) { + RealmObjectSchema realmObjectSchema = realmSchema.create("Simple"); + realmObjectSchema.add(new Property("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add(new Property("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + return realmObjectSchema; + } + return realmSchema.get("Simple"); + } + public static Table initTable(SharedRealm sharedRealm) { if (!sharedRealm.hasTable("class_Simple")) { Table table = sharedRealm.getTable("class_Simple"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java new file mode 100644 index 0000000000..20647dfb3e --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java @@ -0,0 +1,147 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import io.realm.Realm; +import io.realm.entities.StringOnly; +import io.realm.rule.TestRealmConfigurationFactory; + +import static io.realm.objectserver.SyncTestUtils.createTestUser; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static junit.framework.TestCase.assertFalse; + +@RunWith(AndroidJUnit4.class) +public class SchemaTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Context context; + private User user; + private SyncConfiguration config; + + @Before + public void setUp() { + context = InstrumentationRegistry.getContext(); + User user = createTestUser(); + config = new SyncConfiguration.Builder(context) + .user(user) + .serverUrl("realm://objectserver.realm.io/~/default") + .build(); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void getInstance() { + Realm realm = Realm.getInstance(config); + assertFalse(realm.isClosed()); + realm.close(); + assertTrue(realm.isClosed()); + } + + @Test + public void createObject() { + Realm realm = Realm.getInstance(config); + realm.beginTransaction(); + assertTrue(realm.getSchema().contains("StringOnly")); + StringOnly stringOnly= realm.createObject(StringOnly.class); + stringOnly.setChars("TEST"); + realm.commitTransaction(); + assertEquals(1, realm.where(StringOnly.class).count()); + realm.close(); + } + + @Test + public void disallow_removeClass() { + Realm realm = Realm.getInstance(config); + String className = "StringOnly"; + realm.beginTransaction(); + assertTrue(realm.getSchema().contains(className)); + thrown.expect(IllegalArgumentException.class); + realm.getSchema().remove(className); + realm.cancelTransaction(); + realm.close(); + } + + @Test + public void allow_createClass() { + Realm realm = Realm.getInstance(config); + String className = "Dogplace"; + realm.beginTransaction(); + realm.getSchema().create("Dogplace"); + realm.commitTransaction(); + assertTrue(realm.getSchema().contains(className)); + realm.close(); + } + + @Test + public void disallow_renameClass() { + Realm realm = Realm.getInstance(config); + String className = "StringOnly"; + realm.beginTransaction(); + thrown.expect(IllegalArgumentException.class); + realm.getSchema().rename(className, "Dogplace"); + realm.cancelTransaction(); + assertTrue(realm.getSchema().contains(className)); + realm.close(); + } + + @Test + public void disallow_removeField() { + Realm realm = Realm.getInstance(config); + String className = "StringOnly"; + String fieldName = "chars"; + realm.beginTransaction(); + assertTrue(realm.getSchema().get(className).hasField(fieldName)); + thrown.expect(IllegalArgumentException.class); + realm.getSchema().get(className).removeField(fieldName); + realm.cancelTransaction(); + realm.close(); + } + + @Test + public void allow_addField() { + String className = "StringOnly"; + Realm realm = Realm.getInstance(config); + + realm.beginTransaction(); + realm.getSchema().get(className).addField("foo", String.class); + realm.commitTransaction(); + + assertTrue(realm.getSchema().get(className).hasField("foo")); + + realm.close(); + } +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index fe152c6146..d88e67cf39 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -29,6 +29,7 @@ create_javah(TARGET jni_headers io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.objectserver.SyncManager io.realm.objectserver.internal.SyncSession io.realm.log.LogLevel + io.realm.Property io.realm.RealmSchema io.realm.RealmObjectSchema CLASSPATH ${classes_PATH} OUTPUT_DIR ${CMAKE_SOURCE_DIR}/jni_include diff --git a/realm/realm-library/src/main/cpp/io_realm_Property.cpp b/realm/realm-library/src/main/cpp/io_realm_Property.cpp new file mode 100644 index 0000000000..a59b0cd890 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_Property.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 +#include "io_realm_Property.h" + +#include +#include +#include + +#include "util.hpp" + +using namespace realm; + +JNIEXPORT jlong JNICALL +Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2IZZZ(JNIEnv *env, jclass, jstring name_, + jint type, jboolean is_primary, jboolean is_indexed, + jboolean is_nullable) { + TR_ENTER(env) + try { + JStringAccessor str(env, name_); + PropertyType p_type = static_cast(static_cast(type)); + std::unique_ptr property(new Property(str, p_type, "", "", to_bool(is_primary), to_bool(is_indexed), to_bool(is_nullable))); + if (to_bool(is_indexed) && !property->is_indexable()) { + throw std::invalid_argument( + "This field cannot be indexed - Only String/byte/short/int/long/boolean/Date fields are supported."); + } + if (to_bool(is_primary) && p_type != PropertyType::Int && p_type != PropertyType::String) { + std::string typ = property->type_string(); + throw std::invalid_argument("Invalid primary key type: " + typ); + } + return reinterpret_cast(property.release()); + } + CATCH_STD() + return 0; +} + +JNIEXPORT jlong JNICALL +Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2ILjava_lang_String_2(JNIEnv *env, jclass, + jstring name_, jint type, + jstring linkedToName_) { + TR_ENTER(env) + try { + JStringAccessor name(env, name_); + JStringAccessor link_name(env, linkedToName_); + PropertyType p_type = static_cast(static_cast(type)); + bool is_nullable = (p_type == PropertyType::Object); + std::unique_ptr property(new Property(name, p_type, link_name, "", false, false, is_nullable)); + return reinterpret_cast(property.release()); + } + CATCH_STD() + return 0; +} + +JNIEXPORT void JNICALL +Java_io_realm_Property_nativeClose(JNIEnv *env, jclass, jlong property_ptr) { + TR_ENTER_PTR(env, property_ptr) + try { + Property *property = reinterpret_cast(property_ptr); + delete property; + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp new file mode 100644 index 0000000000..984960bbcd --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 +#include "io_realm_RealmObjectSchema.h" + +#include +#include + +#include "util.hpp" +using namespace realm; + +JNIEXPORT jlong JNICALL +Java_io_realm_RealmObjectSchema_nativeCreateRealmObjectSchema(JNIEnv *env, jclass, jstring className_) { + TR_ENTER(env) + try { + JStringAccessor name(env, className_); + ObjectSchema *object_schema = new ObjectSchema(); + object_schema->name = name; + return reinterpret_cast(object_schema); + } + CATCH_STD() + return 0; +} + +JNIEXPORT void JNICALL +Java_io_realm_RealmObjectSchema_nativeClose(JNIEnv *env, jclass, jlong native_ptr) { + TR_ENTER_PTR(env, native_ptr) + try { + ObjectSchema* object_schema = reinterpret_cast(native_ptr); + delete object_schema; + } + CATCH_STD() +} + + +JNIEXPORT void JNICALL +Java_io_realm_RealmObjectSchema_nativeAddProperty(JNIEnv *env, jclass, jlong native_ptr, jlong property_ptr) { + TR_ENTER_PTR(env, native_ptr) + try { + ObjectSchema* object_schema = reinterpret_cast(native_ptr); + Property* property = reinterpret_cast(property_ptr); + object_schema->persisted_properties.push_back(*property); + if (property->is_primary) { + object_schema->primary_key = property->name; + } + } + CATCH_STD() +} + +JNIEXPORT jstring JNICALL +Java_io_realm_RealmObjectSchema_nativeGetClassName(JNIEnv *env, jclass, jlong nativePtr) { + TR_ENTER_PTR(env, nativePtr) + try { + ObjectSchema* object_schema = reinterpret_cast(nativePtr); + auto name = object_schema->name; + return to_jstring(env, name); + } + CATCH_STD() +} + +JNIEXPORT jlongArray JNICALL +Java_io_realm_RealmObjectSchema_nativeGetProperties(JNIEnv *env, jclass type, jlong nativePtr) { + TR_ENTER_PTR(env, nativePtr) + try { + ObjectSchema* object_schema = reinterpret_cast(nativePtr); + size_t size = object_schema->persisted_properties.size(); + jlongArray native_ptr_array = env->NewLongArray(static_cast(size)); + jlong* tmp = new jlong[size]; + auto it = object_schema->persisted_properties.begin(); + size_t index = 0; + while (it != object_schema->persisted_properties.end()) { + Property property = *it; + tmp[index] = reinterpret_cast(new Property(std::move(property))); + ++index; + ++it; + } + env->SetLongArrayRegion(native_ptr_array, 0, static_cast(size), tmp); + delete tmp; + return native_ptr_array; + } + CATCH_STD() +} + diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp new file mode 100644 index 0000000000..2a35f58a8d --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 +#include "io_realm_RealmSchema.h" + +#include +#include +#include + +#include "util.hpp" +using namespace realm; + + +JNIEXPORT jlong JNICALL +Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv *env, jclass, jlongArray objectSchemaPtrs_) { + TR_ENTER(env) + try { + std::vector object_schemas; + JniLongArray array(env, objectSchemaPtrs_); + for (jsize i = 0; i < array.len(); ++i) { + ObjectSchema object_schema = *reinterpret_cast(array[i]); + object_schemas.push_back(std::move(object_schema)); + } + auto *schema = new Schema(object_schemas); + return reinterpret_cast(schema); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_RealmSchema_nativeClose(JNIEnv *env, jclass, jlong nativePtr) { + TR_ENTER_PTR(env, nativePtr) + Schema* schema = reinterpret_cast(nativePtr); + delete schema; +} + +JNIEXPORT jlongArray JNICALL +Java_io_realm_RealmSchema_nativeGetAll(JNIEnv *env, jclass, jlong nativePtr) { + TR_ENTER_PTR(env, nativePtr) + try { + Schema* schema = reinterpret_cast(nativePtr); + size_t size = schema->size(); + jlongArray native_ptr_array = env->NewLongArray(static_cast(size)); + jlong* tmp = new jlong[size]; + auto it = schema->begin(); + size_t index = 0; + while (it != schema->end()) { + auto object_schema = *it; + tmp[index] = reinterpret_cast(new ObjectSchema(std::move(object_schema))); + ++index; + ++it; + } + env->SetLongArrayRegion(native_ptr_array, 0, static_cast(size), tmp); + delete tmp; + return native_ptr_array; + } + CATCH_STD() +} + diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index b55c0af109..5e0d40001b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -397,4 +397,16 @@ Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, return 0; } +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv *env, jclass, jlong nativePtr, + jlong nativeSchemaPtr, jlong version) { + TR_ENTER(env) + try { + auto shared_realm = *(reinterpret_cast(nativePtr)); + auto *schema = reinterpret_cast(nativeSchemaPtr); + shared_realm->update_schema(*schema, static_cast(version)); + } + CATCH_STD() +} + diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 8cbd3e37cb..bed13c338a 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -751,4 +751,8 @@ inline realm::Timestamp from_milliseconds(jlong milliseconds) extern const std::string TABLE_PREFIX; +static inline bool to_bool(jboolean b) { + return b == JNI_TRUE; +} + #endif // REALM_JAVA_UTIL_HPP diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 15d0192dc9..ae345d9857 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -440,6 +440,9 @@ void doClose() { sharedRealm.close(); sharedRealm = null; } + if (schema != null) { + schema.close(); + } } /** diff --git a/realm/realm-library/src/main/java/io/realm/Property.java b/realm/realm-library/src/main/java/io/realm/Property.java new file mode 100644 index 0000000000..82fb4a0655 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/Property.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm; + + +/** + * Class for handling properties/fields. + */ + +public class Property { + public static boolean PRIMARY_KEY = true; + public static boolean REQUIRED = true; + public static boolean INDEXED = true; + + private final long nativePtr; + + public Property(String name, RealmFieldType type, boolean isPrimary, boolean isIndexed, boolean isRequired) { + this.nativePtr = nativeCreateProperty(name, type.getNativeValue(), isPrimary, isIndexed, !isRequired); + } + + public Property(String name, RealmFieldType type, RealmObjectSchema linkedTo) { + String linkedToName = linkedTo.getClassName(); + this.nativePtr = nativeCreateProperty(name, type.getNativeValue(), linkedToName); + } + + protected Property(long nativePtr) { + this.nativePtr = nativePtr; + } + + protected long getNativePtr() { + return nativePtr; + } + + public void close() { + if (nativePtr != 0) { + nativeClose(nativePtr); + } + } + + private static native long nativeCreateProperty(String name, int type, boolean isPrimary, boolean isIndexed, boolean isNullable); + private static native long nativeCreateProperty(String name, int type, String linkedToName); + private static native void nativeClose(long nativePtr); +} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 204d4ded3b..ca8a5f04b4 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -50,6 +50,8 @@ import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; import io.realm.log.RealmLog; +import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.internal.ObjectServerFacade; import rx.Observable; /** @@ -257,37 +259,58 @@ static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices c private static void initializeRealm(Realm realm) { long version = realm.getVersion(); boolean commitNeeded = false; + boolean syncAvailable = ObjectServerFacade.SYNC_AVAILABLE && realm.configuration instanceof SyncConfiguration; + try { - realm.beginTransaction(); - if (version == UNVERSIONED) { - commitNeeded = true; - realm.setVersion(realm.configuration.getSchemaVersion()); + if (!syncAvailable) { + realm.beginTransaction(); + if (version == UNVERSIONED) { + commitNeeded = true; + realm.setVersion(realm.configuration.getSchemaVersion()); + } } RealmProxyMediator mediator = realm.configuration.getSchemaMediator(); final Set> modelClasses = mediator.getModelClasses(); final Map, ColumnInfo> columnInfoMap; columnInfoMap = new HashMap, ColumnInfo>(modelClasses.size()); + ArrayList realmObjectSchemas = new ArrayList<>(); + RealmSchema realmSchemaCache = new RealmSchema(); for (Class modelClass : modelClasses) { // Create and validate table - if (version == UNVERSIONED) { + if (version == UNVERSIONED && !syncAvailable) { mediator.createTable(modelClass, realm.sharedRealm); } - columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm)); + if (syncAvailable) { + RealmObjectSchema realmObjectSchema = mediator.createRealmObjectSchema(modelClass, realmSchemaCache); + realmObjectSchemas.add(realmObjectSchema); + } else { + columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm)); + } + } + if (syncAvailable) { + RealmSchema schema = new RealmSchema(realmObjectSchemas); + // Assumption: when SyncConfiguration then additive schema update mode + realm.sharedRealm.updateSchema(schema, version); + for (Class modelClass : modelClasses) { + columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm)); + } } realm.schema.columnIndices = new ColumnIndices(columnInfoMap); - if (version == UNVERSIONED) { + if (version == UNVERSIONED && !syncAvailable) { final Transaction transaction = realm.getConfiguration().getInitialDataTransaction(); if (transaction != null) { transaction.execute(realm); } } } finally { - if (commitNeeded) { - realm.commitTransaction(false); - } else { - realm.cancelTransaction(); + if (!syncAvailable) { + if (commitNeeded) { + realm.commitTransaction(false); + } else { + realm.cancelTransaction(); + } } } } @@ -1010,7 +1033,7 @@ public List copyFromRealm(Iterable realmObjects, in * The copied object(s) are all detached from Realm and they will no longer be automatically updated. This means * that the copied objects might contain data that are no longer consistent with other managed Realm objects. *

    - * *WARNING*: Any changes to copied objects can be merged back into Realm using + * *WARNING*: Any changes to copied objects can be merged back into Realm using * {@link #copyToRealmOrUpdate(RealmModel)}, but all fields will be overridden, not just those that were changed. * This includes references to other objects, and can potentially override changes made by other threads. * @@ -1031,9 +1054,9 @@ public E copyFromRealm(E realmObject) { * The copied object(s) are all detached from Realm and they will no longer be automatically updated. This means * that the copied objects might contain data that are no longer consistent with other managed Realm objects. *

    - * *WARNING*: Any changes to copied objects can be merged back into Realm using - * {@link #copyToRealmOrUpdate(RealmModel)}, but all fields will be overridden, not just those that were changed. - * This includes references to other objects even though they might be {@code null} due to {@code maxDepth} being + * *WARNING*: Any changes to copied objects can be merged back into Realm using + * {@link #copyToRealmOrUpdate(RealmModel)}, but all fields will be overridden, not just those that were changed. + * This includes references to other objects even though they might be {@code null} due to {@code maxDepth} being * reached. This can also potentially override changes made by other threads. * * @param realmObject {@link RealmObject} to copy. diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 189963b558..e04f557ec3 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -16,10 +16,6 @@ package io.realm; -import io.realm.annotations.Required; -import io.realm.internal.Table; -import io.realm.internal.TableOrView; - import java.util.Arrays; import java.util.Collection; import java.util.Date; @@ -28,6 +24,12 @@ import java.util.Map; import java.util.Set; +import io.realm.annotations.Required; +import io.realm.internal.Table; +import io.realm.internal.TableOrView; +import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.internal.ObjectServerFacade; + /** * Class for interacting with the schema for a given RealmObject class. This makes it possible to * add, delete or change the fields for given class. @@ -68,6 +70,7 @@ public final class RealmObjectSchema { private final BaseRealm realm; final Table table; private final Map columnIndices; + private final long nativePtr; /** * Creates a schema object for a given Realm class. @@ -80,6 +83,47 @@ public final class RealmObjectSchema { this.realm = realm; this.table = table; this.columnIndices = columnIndices; + this.nativePtr = 0; + } + + /** + * Creates a schema object using object store. This constructor is intended to be used by + * the validation of schema, object schemas and prorperties through the object store. Even though the constructor + * is public, there is never a purpose which justifies calling it! + * + * @param className name of the class + */ + RealmObjectSchema(String className) { + this.realm = null; + this.table = null; + this.columnIndices = null; + this.nativePtr = nativeCreateRealmObjectSchema(className); + } + + protected RealmObjectSchema(long nativePtr) { + this.realm = null; + this.table = null; + this.columnIndices = null; + this.nativePtr = nativePtr; + } + + /** + * Closes/frees native resource. Even though the method is public, there is never a purpose which justifies calling + * it! + */ + public void close() { + if (nativePtr != 0) { + Set properties = getProperties(); + for (Property property : properties) { + property.close(); + } + nativeClose(nativePtr); + } + } + + + protected long getNativePtr() { + return nativePtr; } /** @@ -93,7 +137,11 @@ public final class RealmObjectSchema { * @return the name of the RealmObject class represented by this schema. */ public String getClassName() { - return table.getName().substring(Table.TABLE_PREFIX.length()); + if (realm == null) { + return nativeGetClassName(nativePtr); + } else { + return table.getName().substring(Table.TABLE_PREFIX.length()); + } } /** @@ -105,6 +153,7 @@ public String getClassName() { * @see RealmSchema#rename(String, String) */ public RealmObjectSchema setClassName(String className) { + checkNotInSync(); // renaming a table is not permitted checkEmpty(className); String internalTableName = Table.TABLE_PREFIX + className; //FIXME : when core implements class name length check, please remove. @@ -208,6 +257,34 @@ public RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema o return this; } + /** + * Adds a property to an object schema. This method should only be used by proxy classes to set up a schema. + * + * @param property the property to add. + * @return the updated schema. + * @throws IllegalArgumentException if the method is called after opening a Realm. + */ + protected RealmObjectSchema add(Property property) { + if (realm != null && nativePtr == 0) { + throw new IllegalArgumentException("Don't use this method."); + } + nativeAddProperty(nativePtr, property.getNativePtr()); + return this; + } + + private Set getProperties() { + if (realm == null) { + long[] ptrs = nativeGetProperties(nativePtr); + Set properties = new LinkedHashSet<>(ptrs.length); + for (int i = 0; i < ptrs.length; i++) { + properties.add(new Property(ptrs[i])); + } + return properties; + } else { + throw new IllegalArgumentException("Not possible"); + } + } + /** * Removes a field from the class. * @@ -216,6 +293,7 @@ public RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema o * @throws IllegalArgumentException if field name doesn't exist. */ public RealmObjectSchema removeField(String fieldName) { + checkNotInSync(); // destructive modification of a schema is not permitted checkLegalName(fieldName); if (!hasField(fieldName)) { throw new IllegalStateException(fieldName + " does not exist."); @@ -237,6 +315,7 @@ public RealmObjectSchema removeField(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or if the new field name already exists. */ public RealmObjectSchema renameField(String currentFieldName, String newFieldName) { + checkNotInSync(); // destructive modification of a schema is not permitted checkLegalName(currentFieldName); checkFieldExists(currentFieldName); checkLegalName(newFieldName); @@ -302,6 +381,7 @@ public boolean hasIndex(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or the field doesn't have an index. */ public RealmObjectSchema removeIndex(String fieldName) { + checkNotInSync(); // destructive modifications are not permitted checkLegalName(fieldName); checkFieldExists(fieldName); long columnIndex = getColumnIndex(fieldName); @@ -344,6 +424,7 @@ public RealmObjectSchema addPrimaryKey(String fieldName) { * @throws IllegalArgumentException if the class doesn't have a primary key defined. */ public RealmObjectSchema removePrimaryKey() { + checkNotInSync(); // destructive modifications are not permitted if (!table.hasPrimaryKey()) { throw new IllegalStateException(getClassName() + " doesn't have a primary key."); } @@ -582,6 +663,13 @@ private void checkEmpty(String str) { } } + private void checkNotInSync() { + // FIXME: similar method found in RealmSchema. + if (ObjectServerFacade.SYNC_AVAILABLE && realm.configuration instanceof SyncConfiguration) { + throw new IllegalArgumentException("You cannot perform changes to a schema. Please update app and restart."); + } + } + /** * Returns the column indices for the given field name. If a linked field is defined, the column index for * each field is returned. @@ -775,4 +863,10 @@ public Collection values() { throw new UnsupportedOperationException(); } } + + static native long nativeCreateRealmObjectSchema(String className); + static native void nativeAddProperty(long nativePtr, long nativePropertyPtr); + static native long[] nativeGetProperties(long nativePtr); + static native void nativeClose(long nativePtr); + static native String nativeGetClassName(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 65ce99b506..2084601883 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -16,6 +16,7 @@ package io.realm; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -25,6 +26,8 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.Table; import io.realm.internal.Util; +import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.internal.ObjectServerFacade; /** * Class for interacting with the Realm schema using a dynamic API. This makes it possible @@ -49,6 +52,7 @@ public final class RealmSchema { private final Map dynamicClassToSchema = new HashMap(); private final BaseRealm realm; + private long nativePtr; ColumnIndices columnIndices; // Cached field look up /** @@ -56,6 +60,43 @@ public final class RealmSchema { */ RealmSchema(BaseRealm realm) { this.realm = realm; + this.nativePtr = 0; + } + + /** + * Creates a wrappor to easily manipulate Object Store schemas. This constructor should only be called by + * proxy classes during validation of schema. + */ + RealmSchema() { + // This is the case where the schema is created from the proxy classes. + // dynamicClassToSchema is used to keep track of which model classes have been processed. + this.realm = null; + this.nativePtr = 0; + // TODO: create a Object Store realm::Schema object and store the native pointer + } + + + RealmSchema(ArrayList realmObjectSchemas) { + long list[] = new long[realmObjectSchemas.size()]; + for (int i = 0; i < realmObjectSchemas.size(); i++) { + list[i] = realmObjectSchemas.get(i).getNativePtr(); + } + this.nativePtr = nativeCreateFromList(list); + this.realm = null; + } + + public long getNativePtr() { + return this.nativePtr; + } + + public void close() { + if (nativePtr != 0) { + Set schemas = getAll(); + for (RealmObjectSchema schema : schemas) { + schema.close(); + } + nativeClose(nativePtr); + } } /** @@ -67,13 +108,21 @@ public final class RealmSchema { */ public RealmObjectSchema get(String className) { checkEmpty(className, EMPTY_STRING_MSG); - String internalClassName = TABLE_PREFIX + className; - if (realm.sharedRealm.hasTable(internalClassName)) { - Table table = realm.sharedRealm.getTable(internalClassName); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - return new RealmObjectSchema(realm, table, columnIndices); + if (realm == null) { + if (contains(className)) { + return dynamicClassToSchema.get(className); + } else { + return null; + } } else { - return null; + String internalClassName = TABLE_PREFIX + className; + if (realm.sharedRealm.hasTable(internalClassName)) { + Table table = realm.sharedRealm.getTable(internalClassName); + RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); + return new RealmObjectSchema(realm, table, columnIndices); + } else { + return null; + } } } @@ -83,18 +132,27 @@ public RealmObjectSchema get(String className) { * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ public Set getAll() { - int tableCount = (int) realm.sharedRealm.size(); - Set schemas = new LinkedHashSet<>(tableCount); - for (int i = 0; i < tableCount; i++) { - String tableName = realm.sharedRealm.getTableName(i); - if (Table.isMetaTable(tableName)) { - continue; + if (realm == null) { + long[] ptrs = nativeGetAll(nativePtr); + Set schemas = new LinkedHashSet<>(ptrs.length); + for (int i = 0; i < ptrs.length; i++) { + schemas.add(new RealmObjectSchema(ptrs[i])); } - Table table = realm.sharedRealm.getTable(tableName); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - schemas.add(new RealmObjectSchema(realm, table, columnIndices)); + return schemas; + } else { + int tableCount = (int) realm.sharedRealm.size(); + Set schemas = new LinkedHashSet<>(tableCount); + for (int i = 0; i < tableCount; i++) { + String tableName = realm.sharedRealm.getTableName(i); + if (Table.isMetaTable(tableName)) { + continue; + } + Table table = realm.sharedRealm.getTable(tableName); + RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); + schemas.add(new RealmObjectSchema(realm, table, columnIndices)); + } + return schemas; } - return schemas; } /** @@ -104,17 +162,24 @@ public Set getAll() { * @return a Realm schema object for that class. */ public RealmObjectSchema create(String className) { + // adding a class is always permitted checkEmpty(className, EMPTY_STRING_MSG); - String internalTableName = TABLE_PREFIX + className; - if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { - throw new IllegalArgumentException("Class name is to long. Limit is 57 characters: " + className.length()); - } - if (realm.sharedRealm.hasTable(internalTableName)) { - throw new IllegalArgumentException("Class already exists: " + className); + if (realm == null) { + RealmObjectSchema realmObjectSchema = new RealmObjectSchema(className); + dynamicClassToSchema.put(className, realmObjectSchema); + return realmObjectSchema; + } else { + String internalTableName = TABLE_PREFIX + className; + if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { + throw new IllegalArgumentException("Class name is to long. Limit is 57 characters: " + className.length()); + } + if (realm.sharedRealm.hasTable(internalTableName)) { + throw new IllegalArgumentException("Class already exists: " + className); + } + Table table = realm.sharedRealm.getTable(internalTableName); + RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); + return new RealmObjectSchema(realm, table, columnIndices); } - Table table = realm.sharedRealm.getTable(internalTableName); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - return new RealmObjectSchema(realm, table, columnIndices); } /** @@ -124,6 +189,7 @@ public RealmObjectSchema create(String className) { * @param className name of the class to remove. */ public void remove(String className) { + checkNotInSync(); // destructive modifications are not permitted checkEmpty(className, EMPTY_STRING_MSG); String internalTableName = TABLE_PREFIX + className; checkHasTable(className, "Cannot remove class because it is not in this Realm: " + className); @@ -142,6 +208,7 @@ public void remove(String className) { * @return a schema object for renamed class. */ public RealmObjectSchema rename(String oldClassName, String newClassName) { + checkNotInSync(); // destructive modifications are not permitted checkEmpty(oldClassName, "Class names cannot be empty or null"); checkEmpty(newClassName, "Class names cannot be empty or null"); String oldInternalName = TABLE_PREFIX + oldClassName; @@ -178,9 +245,18 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { * @return {@code true} if the class already exists. {@code false} otherwise. */ public boolean contains(String className) { - return realm.sharedRealm.hasTable(Table.TABLE_PREFIX + className); + if (realm == null) { + return dynamicClassToSchema.containsKey(className); + } else { + return realm.sharedRealm.hasTable(Table.TABLE_PREFIX + className); + } } + private void checkNotInSync() { + if (ObjectServerFacade.SYNC_AVAILABLE && realm.configuration instanceof SyncConfiguration) { + throw new IllegalArgumentException("You cannot perform changes to a schema. Please update app and restart."); + } + } private void checkEmpty(String str, String error) { if (str == null || str.isEmpty()) { throw new IllegalArgumentException(error); @@ -283,4 +359,8 @@ void setColumnIndices(ColumnIndices columnIndices) { static String getSchemaForTable(Table table) { return table.getName().substring(Table.TABLE_PREFIX.length()); } + + static native long nativeCreateFromList(long[] objectSchemaPtrs); + static native void nativeClose(long nativePtr); + static native long[] nativeGetAll(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index f13ad8671c..a8af6f0972 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -29,6 +29,8 @@ import io.realm.Realm; import io.realm.RealmModel; import io.realm.RealmObject; +import io.realm.RealmObjectSchema; +import io.realm.RealmSchema; import io.realm.exceptions.RealmException; /** @@ -42,6 +44,15 @@ */ public abstract class RealmProxyMediator { + /** + * Create a object schema for the given RealmObject class. + * + * @param clazz the {@link RealmObject} model class to create object schema for. + * @param realmSchema the {@link RealmSchema} to associate the object schema with. + * @return The object schema. + */ + public abstract RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema realmSchema); + /** * Creates the backing table in Realm for the given RealmObject class. * @@ -153,7 +164,7 @@ public abstract class RealmProxyMediator { * @param clazz the type of {@link RealmObject} * @param realm the reference to {@link Realm} where to create the object. * @param json the JSON data - * @param update {@code true} if Realm should try to update a existing object. This requires that the RealmObject + * @param update {@code true} if Realm should try to update a existing object. This requires that the RealmObject * class has a @PrimaryKey. * @return RealmObject that has been created or updated. * @throws JSONException if the JSON mapping doesn't match the expected class. diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 67245556ee..426855a7fa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -20,8 +20,8 @@ import java.io.File; import io.realm.RealmConfiguration; +import io.realm.RealmSchema; import io.realm.internal.async.BadVersionException; -import io.realm.objectserver.SyncConfiguration; import io.realm.objectserver.internal.ObjectServerFacade; public final class SharedRealm implements Closeable { @@ -274,6 +274,10 @@ public boolean compact() { return nativeCompact(nativePtr); } + public void updateSchema(RealmSchema schema, long version) { + nativeUpdateSchema(nativePtr, schema.getNativePtr(), version); + } + @Override public void close() { if (realmNotifier != null) { @@ -330,4 +334,5 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte private static native boolean nativeWaitForChange(long nativeSharedRealmPtr); private static native void nativeStopWaitForChange(long nativeSharedRealmPtr); private static native boolean nativeCompact(long nativeSharedRealmPtr); + private static native void nativeUpdateSchema(long nativePtr, long nativeSchemaPtr, long version); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index bbddbd6cea..a0c83a0e48 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -31,6 +31,8 @@ import io.realm.Realm; import io.realm.RealmModel; +import io.realm.RealmObjectSchema; +import io.realm.RealmSchema; import io.realm.internal.ColumnInfo; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; @@ -57,6 +59,12 @@ public CompositeMediator(RealmProxyMediator... mediators) { this.mediators = Collections.unmodifiableMap(tempMediators); } + @Override + public RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema schema) { + RealmProxyMediator mediator = getMediator(clazz); + return mediator.createRealmObjectSchema(clazz, schema); + } + @Override public Table createTable(Class clazz, SharedRealm sharedRealm) { RealmProxyMediator mediator = getMediator(clazz); diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index 17db19b6d9..d12b9819fe 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -31,6 +31,8 @@ import io.realm.Realm; import io.realm.RealmModel; +import io.realm.RealmObjectSchema; +import io.realm.RealmSchema; import io.realm.internal.ColumnInfo; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; @@ -72,6 +74,11 @@ public RealmProxyMediator getOriginalMediator() { return originalMediator; } + @Override + public RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema schema) { + checkSchemaHasClass(clazz); + return originalMediator.createRealmObjectSchema(clazz, schema); + } @Override public Table createTable(Class clazz, SharedRealm sharedRealm) { checkSchemaHasClass(clazz); diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java index 3e6a3b4506..263c080489 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java @@ -227,6 +227,9 @@ public static final class Builder { public Builder(Context context) { this.context = context; this.defaultFolder = new File(context.getFilesDir(), "realm-object-server"); + if (Realm.getDefaultModule() != null) { + this.modules.add(Realm.getDefaultModule()); + } } /** From 7b8413baa344008af10e87bcf9648f685c755e28 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Sep 2016 07:46:08 -0500 Subject: [PATCH 0046/2110] Remove dependencies from OS sync (#98) Done due to bugs encountered when implementing the Object Store abstraction. We will add it later together with the Session implementation. --- .../cpp/io_realm_internal_SharedRealm.cpp | 2 + .../cpp/io_realm_objectserver_SyncManager.cpp | 93 +++++++++---------- ...ealm_objectserver_internal_SyncSession.cpp | 8 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/cpp/objectserver_shared.hpp | 44 ++++----- .../io/realm/objectserver/SyncManager.java | 12 +++ 6 files changed, 84 insertions(+), 77 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 5e0d40001b..079220856b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -45,6 +45,8 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri config->sync_config = std::make_shared(); config->sync_config->user_tag = token; config->sync_config->realm_url = url; + // FIXME: Sync session is handled by java now. Remove this when adapt to OS sync implementation. + config->sync_config->create_session = false; } return reinterpret_cast(config); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp index c82b36b7a8..e70103185b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp @@ -16,31 +16,33 @@ #include -#include "io_realm_objectserver_SyncManager.h" -#include "objectserver_shared.hpp" -#include "util.hpp" -#include -#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include + +#include "objectserver_shared.hpp" + +#include "io_realm_objectserver_SyncManager.h" using namespace realm; -using namespace sync; +using namespace realm::sync; -class AndroidLogger: public realm::util::RootLogger +std::unique_ptr sync_client; + +class AndroidLogger: public util::RootLogger { public: void do_log(Level level, std::string msg) { + // FIXME Sync only calls the logger from the thread running the client, so it should + // be safe to store the env when starting the thread. + JNIEnv *env; + g_vm->AttachCurrentThread(&env, nullptr); jmethodID log_method; switch (level) { case Level::trace: log_method = log_trace; break; @@ -52,50 +54,51 @@ class AndroidLogger: public realm::util::RootLogger case Level::fatal: log_method = log_fatal; break; case Level::all: case Level::off: - ThrowException(sync_client_env, IllegalArgument, "Unknown logger argument: " + num_to_string(level)); + ThrowException(env, IllegalArgument, + util::format("Unknown logger argument: %s.", util::Logger::get_level_prefix(level))); return; } - log_message(sync_client_env, log_method, msg.c_str()); + log_message(env, log_method, msg.c_str()); } + static AndroidLogger& shared() noexcept; }; +// Not used by now struct AndroidLoggerFactory : public realm::SyncLoggerFactory { - std::unique_ptr make_logger(realm::util::Logger::Level level) { + std::unique_ptr make_logger(util::Logger::Level level) { auto logger = std::make_unique(); logger->set_level_threshold(level); - return std::move(logger); + return std::unique_ptr(std::move(logger)); } } s_logger_factory; -// Object Server global vars, see objectserver_shared.hpp -std::thread* sync_client_thread; -JNIEnv* sync_client_env; +// TODO: Move to a better place & not needed after moving to OS +AndroidLogger& AndroidLogger::shared() noexcept { + static AndroidLogger logger; + return logger; +} JNIEXPORT void JNICALL Java_io_realm_objectserver_SyncManager_nativeInitializeSyncClient (JNIEnv *env, jclass) { TR_ENTER(env) - try { - // Prepare Sync Client. It will be created on demand + if (sync_client) return; - SyncLoginFunction loginDelegate = [=](const Realm::Config&) { - // Ignore this for now. We are handling this manually. - }; + try { + AndroidLogger::shared().set_level_threshold(util::Logger::Level::warn); - SyncClientReadyFunction clientReadyDelegate = [=](const realm::sync::Client&) { - //Attaching thread to Java so we can perform JNI calls - JavaVMAttachArgs args; - args.version = JNI_VERSION_1_6; - args.name = NULL; // java thread a name - args.group = NULL; // java thread group - g_vm->AttachCurrentThread(&sync_client_env, &args); - }; + sync::Client::Config config; + config.logger = &AndroidLogger::shared(); + sync_client = std::make_unique(std::move(config)); // Throws + // FIXME setup error handler for client + } CATCH_STD() +} - SyncManager& sync_manager = SyncManager::shared(); - sync_manager.set_login_function(loginDelegate); - sync_manager.set_logger_factory(s_logger_factory); - sync_manager.set_log_level(util::Logger::Level::warn); - sync_manager.set_client_ready_callback(clientReadyDelegate); +// Create the thread from java side to avoid some strange errors when native throws. +JNIEXPORT void JNICALL +Java_io_realm_objectserver_SyncManager_nativeRunClient(JNIEnv *env, jclass) { + try { + sync_client->run(); } CATCH_STD() } @@ -103,7 +106,6 @@ JNIEXPORT void JNICALL Java_io_realm_objectserver_SyncManager_nativeSetSyncClientLogLevel(JNIEnv* env, jclass, jint logLevel) { util::Logger::Level native_log_level; - bool valid_log_level = true; switch(logLevel) { case io_realm_log_LogLevel_ALL: native_log_level = util::Logger::Level::all; break; case io_realm_log_LogLevel_TRACE: native_log_level = util::Logger::Level::trace; break; @@ -114,12 +116,9 @@ Java_io_realm_objectserver_SyncManager_nativeSetSyncClientLogLevel(JNIEnv* env, case io_realm_log_LogLevel_FATAL: native_log_level = util::Logger::Level::fatal; break; case io_realm_log_LogLevel_OFF: native_log_level = util::Logger::Level::off; break; default: - valid_log_level = false; ThrowException(env, IllegalArgument, "Invalid log level: " + logLevel); + return; } - if (valid_log_level) { - realm::SyncManager::shared().set_log_level(native_log_level); - } + // FIXME: This call is not thread safe. Switch to OS implementation to make it thread safe. + AndroidLogger::shared().set_level_threshold(native_log_level); } - - diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp index 18c50639bf..5222166a05 100644 --- a/realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp @@ -31,7 +31,6 @@ #include #include #include -#include using namespace std; using namespace realm; @@ -43,13 +42,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeCr { TR_ENTER(env) try { - Client* sync_client = &SyncManager::shared().get_sync_client()->client; - if (sync_client == NULL) { - return 0; - } - JStringAccessor local_path(env, localRealmPath); - JniSession* jni_session = new JniSession(env, sync_client, local_path, obj); + JniSession* jni_session = new JniSession(env, local_path, obj); return reinterpret_cast(jni_session); } CATCH_STD() return 0; diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index b11ef9c8b7..6b6012cb2d 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit b11ef9c8b799f0f09059789ab8711853ce31b85f +Subproject commit 6b6012cb2d603e20bf82e19c453af0f9bf997894 diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 9e44aa5bad..7d9d8249bd 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #ifndef REALM_OBJECTSERVER_SHARED_HPP #define REALM_OBJECTSERVER_SHARED_HPP @@ -25,15 +24,10 @@ #include #include #include +#include #include "util.hpp" -// maintain a reference to the threads allocated dynamically, to prevent deallocation -// after Java_io_realm_internal_SharedGroup_nativeStartSession completes. -// To be released later, maybe on JNI_OnUnload -extern std::thread* sync_client_thread; -extern JNIEnv* sync_client_env; - // Wrapper class for realm::Session. This allows us to manage the C++ session and callback lifecycle correctly. // TODO Use OS SyncSession instead @@ -41,24 +35,30 @@ class JniSession { public: JniSession() = delete; - JniSession(JNIEnv* env, realm::sync::Client* sync_client, std::string local_realm_path, jobject java_session_obj) + JniSession(JNIEnv* env, std::string local_realm_path, jobject java_session_obj) { + // FIXME: This doesn't look good. Temp solution before moving to OS sync. + extern std::unique_ptr sync_client; // Get the coordinator for the given path, or null if there is none m_sync_session = new realm::sync::Session(*sync_client, local_realm_path); - m_global_obj_ref = env->NewGlobalRef(java_session_obj); - jobject global_obj_ref_tmp(m_global_obj_ref); - auto sync_transact_callback = [local_realm_path](realm::VersionID, realm::VersionID) { - auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator(realm::StringData(local_realm_path)); - if (coordinator) { - coordinator->notify_others(); - } - }; - auto error_handler = [&, global_obj_ref_tmp](int error_code, std::string message) { - std::string log = num_to_string(error_code) + " " + message.c_str(); - log_message(sync_client_env, log_debug, log.c_str()); - }; - m_sync_session->set_sync_transact_callback(sync_transact_callback); - m_sync_session->set_error_handler(std::move(error_handler)); + m_global_obj_ref = env->NewGlobalRef(java_session_obj); + jobject global_obj_ref_tmp(m_global_obj_ref); + auto sync_transact_callback = [local_realm_path](realm::VersionID, realm::VersionID) { + auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator( + realm::StringData(local_realm_path)); + if (coordinator) { + coordinator->notify_others(); + } + }; + auto error_handler = [&, global_obj_ref_tmp](int error_code, std::string message) { + // FIXME: Simplify this by moving log_message to AndroidLogger + JNIEnv *local_env; + g_vm->AttachCurrentThread(&env, nullptr); + std::string log = num_to_string(error_code) + " " + message.c_str(); + log_message(local_env, log_debug, log.c_str()); + }; + m_sync_session->set_sync_transact_callback(sync_transact_callback); + m_sync_session->set_error_handler(std::move(error_handler)); } inline realm::sync::Session* get_session() const noexcept diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java index b3b556d6b6..e068cec4bb 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java @@ -68,10 +68,21 @@ public void onError(Session session, ObjectServerError error) { // Right now it just lives and dies together with the process. private static volatile AuthenticationServer authServer = new OkHttpAuthenticationServer(); static volatile Session.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; + @SuppressWarnings("FieldCanBeLocal") + private static Thread clientThread; static { RealmCore.loadLibrary(); nativeInitializeSyncClient(); + // Create the client thread in java to avoid strange problem when error happens. And anyway we need to attach + // to the jvm for logger. + clientThread = new Thread(new Runnable() { + @Override + public void run() { + nativeRunClient(); + } + }, "RealmSyncClient"); + clientThread.start(); } /** @@ -150,4 +161,5 @@ public static void setLogLevel(int logLevel) { private static native void nativeInitializeSyncClient(); private static native void nativeSetSyncClientLogLevel(int logLevel); + private static native void nativeRunClient(); } From 48197be15f754cefa55eefb8794f01f202d58fa0 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 16 Sep 2016 21:57:12 +0900 Subject: [PATCH 0047/2110] Allow to specify default value of the field in model's constructor (#3397) * Allow to call its accessors, and replace its field accesses with accessor calls in model's constructor. fixes #777 fixes #2536 * use field instead of checking transaction * fix a bug that acceptDefaultValue is not set correctly * reject default values when the getter of a model creates other model object * add simple test for default value * supports default value of model field * supports default value of RealmList fields * add tests for assignment in constructor and setter in constructor * update javadoc comments of createObject * always ignores the default value of primary key if the object is managed * update javadoc * add a test for default values handling in copyToRealm(). the last assertion of RealmTests.copyToRealm_defaultValuesAreIgnored() is failing now. * refactor tests * use isPrimaryKey() * fix a bug that unexpected realm object is created by default value of RealmModel/RealmList fields * remove extra ';' from generated code * add more tests for default value * fix tests * fix a bug that creates unexpected objects * rename internal methods * update changelog * update CHANGELOG * review comments * update CHANGELOG * added a description of how proxy object should be created in the Javadoc comment of RealmProcessor --- CHANGELOG.md | 7 +- .../realm/transformer/BytecodeModifier.groovy | 29 +- .../realm/transformer/RealmTransformer.groovy | 2 +- .../transformer/BytecodeModifierTest.groovy | 39 +- .../io/realm/processor/ClassMetaData.java | 13 + .../realm/processor/RealmJsonTypeHelper.java | 4 +- .../io/realm/processor/RealmProcessor.java | 22 + .../processor/RealmProxyClassGenerator.java | 194 +++++++-- .../RealmProxyMediatorGenerator.java | 16 +- .../io/realm/AllTypesRealmProxy.java | 224 ++++++++++- .../io/realm/BooleansRealmProxy.java | 93 ++++- .../io/realm/NullTypesRealmProxy.java | 376 +++++++++++++++++- .../io/realm/RealmDefaultModuleMediator.java | 19 +- .../resources/io/realm/SimpleRealmProxy.java | 59 ++- .../java/io/realm/RealmAsyncQueryTests.java | 3 - .../java/io/realm/RealmJsonTests.java | 222 ++++++++++- .../java/io/realm/RealmResultsTests.java | 78 ++++ .../androidTest/java/io/realm/RealmTests.java | 310 +++++++++++++++ .../androidTest/java/io/realm/TestHelper.java | 58 +++ .../entities/DefaultValueConstructor.java | 227 +++++++++++ .../realm/entities/DefaultValueOfField.java | 213 ++++++++++ .../io/realm/entities/DefaultValueSetter.java | 231 +++++++++++ ...maryKeyWithNoPrimaryKeyObjectRelation.java | 9 +- .../io/realm/entities/RandomPrimaryKey.java | 54 +++ .../src/main/java/io/realm/BaseRealm.java | 80 +++- .../src/main/java/io/realm/DynamicRealm.java | 2 +- .../java/io/realm/DynamicRealmObject.java | 26 +- .../src/main/java/io/realm/ProxyState.java | 31 ++ .../src/main/java/io/realm/Realm.java | 50 ++- .../main/java/io/realm/RealmObjectSchema.java | 2 +- .../src/main/java/io/realm/RealmQuery.java | 11 +- .../io/realm/internal/RealmProxyMediator.java | 13 +- .../internal/modules/CompositeMediator.java | 10 +- .../internal/modules/FilterableMediator.java | 10 +- 34 files changed, 2546 insertions(+), 191 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueConstructor.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOfField.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueSetter.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/RandomPrimaryKey.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f800aee67..3acce90210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,5 @@ ## 2.0.0 -### Known issues - -* When creating a `RealmObject` from a JSON stream, it will take the default values defined by its default constructor for those fields that are not defined in the JSON object. This behaviour is different from other APIs when creating `RealmObject`s. - ### Breaking Changes * `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). @@ -17,6 +13,8 @@ * Importing from JSON without the primary key field defined in the JSON object now throws `IllegalArgumentException`. * Now `Realm.beginTransaction()`, `Realm.executeTransaction()` and `Realm.waitForChange()` throw `RealmMigrationNeededException` if a remote process introduces incompatible schema changes (#3409). * The primary key value of an object can no longer be changed after the object was created. Instead a new object must be created and all fields copied over. +* Now `Realm.createObject(Class)` and `Realm.createObject(Class,Object)` take the values from the model's fields and default constructor. `DynamicRealm` does not take these default values (#777). +* When `Realm.create*FromJson()`s create a new `RealmObject`, now they take the default values defined by the field itself and its default constructor for those fields that are not defined in the JSON object. ### Enhancements @@ -30,6 +28,7 @@ * Fixed a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11 (#3356). * Fixed a potential crash when there were lots of async queries waiting in the queue. * Fixed a bug causing the Realm Transformer to not transform field access in the model's constructors (#3361). +* Fixed a bug causing the `NullPointerException` when calling getters/setters in the model's constructors (#2536). ### Internal diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index b9e388c828..ce1fc39801 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -57,7 +57,7 @@ class BytecodeModifier { * @param clazz The CtClass to modify * @param managedFields List of fields whose access should be replaced */ - public static void useRealmAccessors(CtClass clazz, List managedFields, List modelClasses) { + public static void useRealmAccessors(CtClass clazz, List managedFields) { clazz.getDeclaredBehaviors().each { behavior -> logger.info " Behavior: ${behavior.name}" if ( @@ -69,7 +69,7 @@ class BytecodeModifier { behavior instanceof CtConstructor ) ) { - behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior, modelClasses.contains(clazz))) + behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior)) } } } @@ -93,18 +93,13 @@ class BytecodeModifier { final List managedFields final CtClass ctClass final CtBehavior behavior - final boolean isModelClass - final boolean isInConstructor FieldAccessToAccessorConverter(List managedFields, CtClass ctClass, - CtBehavior behavior, - boolean isModelClass) { + CtBehavior behavior) { this.managedFields = managedFields this.ctClass = ctClass this.behavior = behavior - this.isModelClass = isModelClass - this.isInConstructor = behavior instanceof CtConstructor } @Override @@ -118,23 +113,9 @@ class BytecodeModifier { logger.info " Methods: ${ctClass.declaredMethods}" def fieldName = fieldAccess.fieldName if (fieldAccess.isReader()) { - if (isInConstructor && isModelClass) { - // work around https://github.com/realm/realm-java/issues/2536 - // '$0' is the object that owns target field. - // 'this' is the instance where the constructor belongs. - fieldAccess.replace('$_ = ($0 == this) ? $0.' + fieldName + ' : $0.realmGet$' + fieldName + '();') - } else { - fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') - } + fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') } else if (fieldAccess.isWriter()) { - if (isInConstructor && isModelClass) { - // work around https://github.com/realm/realm-java/issues/2536 - // '$0' is the object that owns target field. - // 'this' is the instance where the constructor belongs. - fieldAccess.replace('if ($0 == this) {$0.' + fieldName + ' = $1;} else { $0.realmSet$' + fieldName + '($1);}') - } else { - fieldAccess.replace('$0.realmSet$' + fieldName + '($1);') - } + fieldAccess.replace('$0.realmSet$' + fieldName + '($1);') } } } diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index d58fb39b7c..ba85db84ab 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -137,7 +137,7 @@ class RealmTransformer extends Transform { inputClassNames.each { logger.info " Modifying class ${it}" def ctClass = classPool.getCtClass(it) - BytecodeModifier.useRealmAccessors(ctClass, allManagedFields, allModelClasses) + BytecodeModifier.useRealmAccessors(ctClass, allManagedFields) ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) } diff --git a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy index 6750f2b273..6a8b6ee34d 100644 --- a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy +++ b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy @@ -93,13 +93,13 @@ class BytecodeModifierTest extends Specification { BytecodeModifier.addRealmAccessors(ctClass) when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(ctClass, [ctField], []) + BytecodeModifier.useRealmAccessors(ctClass, [ctField]) then: 'the field is not used and getter is called in the method ' !isFieldRead(ctMethod) && hasMethodCall(ctMethod) } - def "UseRealmAccessors_fieldAccessInModelConstructorIsTransformed"() { + def "UseRealmAccessors_fieldAccessConstructorIsTransformed"() { setup: 'generate an empty class' def classPool = ClassPool.getDefault() def ctClass = classPool.makeClass('TestClass') @@ -124,40 +124,7 @@ class BytecodeModifierTest extends Specification { BytecodeModifier.addRealmAccessors(ctClass) when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(ctClass, [ctField], [ctClass]) - - then: 'the field is still used and also getter is called in the constructor' - // to work around https://github.com/realm/realm-java/issues/2536 , field access is not removed - isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && - isFieldRead(ctNonDefaultConstructor) && hasMethodCall(ctNonDefaultConstructor) - } - - def "UseRealmAccessors_fieldAccessInNonModelConstructorIsTransformed"() { - setup: 'generate an empty class' - def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('TestClass') - - and: 'add a field' - def ctField = new CtField(CtClass.intType, 'age', ctClass) - ctClass.addField(ctField) - - and: 'add a method that sets such field' - def ctMethod = CtNewMethod.make('private void setupAge(int age) { this.age = age; }', ctClass) - ctClass.addMethod(ctMethod) - - and: 'add a default constructor that uses the method' - def ctDefaultConstructor = CtNewConstructor.make('public TestClass() { int myAge = this.age; }', ctClass) - ctClass.addConstructor(ctDefaultConstructor) - - and: 'add a non-default constructor that uses the method' - def ctNonDefaultConstructor = CtNewConstructor.make('public TestClass(TestClass other) { int otherAge = other.age; }', ctClass) - ctClass.addConstructor(ctNonDefaultConstructor) - - and: 'realm accessors are added' - BytecodeModifier.addRealmAccessors(ctClass) - - when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(ctClass, [ctField], [/* no ctClass in model class list*/]) + BytecodeModifier.useRealmAccessors(ctClass, [ctField]) then: 'the field is not used in the method anymore' !isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index e13bbc4626..646a12790d 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -373,6 +373,19 @@ public boolean isNullable(VariableElement variableElement) { return nullableFields.contains(variableElement); } + /** + * Checks if a VariableElement is a primary key. + * + * @param variableElement the element/field + * @return {@code true} if a VariableElement is primary key, {@code false} otherwise. + */ + public boolean isPrimaryKey(VariableElement variableElement) { + if (primaryKey == null) { + return false; + } + return primaryKey.equals(variableElement); + } + private boolean isValidPrimaryKeyType(TypeMirror type) { for (TypeMirror validType : validPrimaryKeyTypes) { if (typeUtils.isAssignable(type, validType)) { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java index c54e00e149..af9402ab21 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java @@ -301,10 +301,10 @@ public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, writer .beginControlFlow("if (json.has(\"%s\"))", fieldName) .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - .emitStatement("obj = (%1$s) realm.createObject(%2$s.class, null)", + .emitStatement("obj = (%1$s) realm.createObjectInternal(%2$s.class, null, true, excludeFields)", qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass) .nextControlFlow("else") - .emitStatement("obj = (%1$s) realm.createObject(%2$s.class, json.get%3$s(\"%4$s\"))", + .emitStatement("obj = (%1$s) realm.createObjectInternal(%2$s.class, json.get%3$s(\"%4$s\"), true, excludeFields)", qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass, jsonType, fieldName) .endControlFlow() .nextControlFlow("else") diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index 1e437f8bcf..59f14327d7 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -90,6 +90,28 @@ *

  • Each time a static helper method is needed, Realm can now delegate these method calls to the appropriate * Mediator which in turn will delegate the method call to the appropriate RealmObjectProxy class.
  • * + * + *

    CREATING A MANAGED RealmObject

    + * + * To allow to specify default values by model's constructor or direct field assignment, + * the flow of creating the proxy object is a bit complicated. This section illustrates + * how proxy object should be created. + * + *
      + *
    1. Get the thread local {@code io.realm.BaseRealm.RealmObjectContext} instance by {@code BaseRealm.objectContext.get()}
    2. + *
    3. Set the object context information to the {@code RealmObjectContext} those should be set to the creating proxy object.
    4. + *
    5. Create proxy object ({@code new io.realm.FooRealmProxy()}).
    6. + *
    7. Set the object context information to the created proxy when the first access of its accessors (or in its constructor if accessors are not used in the model's constructor).
    8. + *
    9. Clear the object context information in the thread local {@code io.realm.BaseRealm.RealmObjectContext} instance by calling {@code + * #clear()} method.
    10. + *
    + * + * The reason of this complicated step is that we can't pass these context information + * via the constructor of the proxy. It's because the constructor of the proxy is executed + * after the constructor of the model class. The access to the fields in the model's + * constructor happens before the assignment of the context information to the 'proxyState'. + * This will cause the {@link NullPointerException} if getters/setter is accessed in the model's + * constructor (see https://github.com/realm/realm-java/issues/2536 ). */ @SupportedAnnotationTypes({ "io.realm.annotations.RealmClass", diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 5b129fed9c..a9cbc092cd 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -105,6 +105,7 @@ public void generate() throws IOException, UnsupportedOperationException { emitClassFields(writer); emitConstructor(writer); + emitInjectContextMethod(writer); emitAccessors(writer); emitInitTableMethod(writer); emitValidateTableMethod(writer); @@ -199,8 +200,8 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { } private void emitClassFields(JavaWriter writer) throws IOException { - writer.emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE, Modifier.FINAL)); - writer.emitField("ProxyState", "proxyState", EnumSet.of(Modifier.PRIVATE, Modifier.FINAL)); + writer.emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE)); + writer.emitField("ProxyState", "proxyState", EnumSet.of(Modifier.PRIVATE)); for (VariableElement variableElement : metadata.getFields()) { if (Utils.isRealmList(variableElement)) { @@ -222,17 +223,19 @@ private void emitClassFields(JavaWriter writer) throws IOException { private void emitConstructor(JavaWriter writer) throws IOException { // FooRealmProxy(ColumnInfo) - writer.beginConstructor(EnumSet.noneOf(Modifier.class), "ColumnInfo", "columnInfo"); - writer.emitStatement("this.columnInfo = (%s) columnInfo", columnInfoClassName()); - writer.emitStatement("this.proxyState = new ProxyState(%s.class, this)", qualifiedClassName); + writer.beginConstructor(EnumSet.noneOf(Modifier.class)); + writer.beginControlFlow("if (proxyState == null)") + .emitStatement("injectObjectContext()") + .endControlFlow(); + writer.emitStatement("proxyState.setConstructionFinished()"); writer.endConstructor(); writer.emitEmptyLine(); } private void emitAccessors(JavaWriter writer) throws IOException { for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); - String fieldTypeCanonicalName = field.asType().toString(); + final String fieldName = field.getSimpleName().toString(); + final String fieldTypeCanonicalName = field.asType().toString(); if (Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeCanonicalName)) { /** @@ -243,6 +246,7 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Getter writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); + emitCodeForInjectingObjectContext(writer, field, false, metadata.isPrimaryKey(field)); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here. @@ -254,7 +258,7 @@ private void emitAccessors(JavaWriter writer) throws IOException { // For Boxed types, this should be the corresponding primitive types. Others remain the same. String castingBackType; - if (Utils.isBoxedType(field.asType().toString())) { + if (Utils.isBoxedType(fieldTypeCanonicalName)) { Types typeUtils = processingEnvironment.getTypeUtils(); castingBackType = typeUtils.unboxedType(field.asType()).toString(); } else { @@ -268,10 +272,11 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Setter writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); + emitCodeForInjectingObjectContext(writer, field, true, metadata.isPrimaryKey(field)); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); // Although setting null value for String and bytes[] can be handled by the JNI code, we still generate the same code here. // Compared with getter, null value won't trigger more native calls in setter which is relatively cheaper. - if (field.equals(metadata.getPrimaryKey())) { + if (metadata.isPrimaryKey(field)) { // Primary key is not allowed to be changed after object created. writer.emitStatement(Constants.STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED, fieldName); } else { @@ -299,17 +304,19 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Getter writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); + emitCodeForInjectingObjectContext(writer, field, false, metadata.isPrimaryKey(field)); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.beginControlFlow("if (proxyState.getRow$realm().isNullLink(%s))", fieldIndexVariableReference(field)); writer.emitStatement("return null"); writer.endControlFlow(); - writer.emitStatement("return proxyState.getRealm$realm().get(%s.class, proxyState.getRow$realm().getLink(%s))", + writer.emitStatement("return proxyState.getRealm$realm().get(%s.class, proxyState.getRow$realm().getLink(%s), false, Collections.emptyList())", fieldTypeCanonicalName, fieldIndexVariableReference(field)); writer.endMethod(); writer.emitEmptyLine(); // Setter writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); + emitCodeForInjectingObjectContext(writer, field, true, metadata.isPrimaryKey(field)); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.beginControlFlow("if (value == null)"); writer.emitStatement("proxyState.getRow$realm().nullifyLink(%s)", fieldIndexVariableReference(field)); @@ -331,6 +338,7 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Getter writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); + emitCodeForInjectingObjectContext(writer, field, false, metadata.isPrimaryKey(field)); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.emitSingleLineComment("use the cached value if available"); writer.beginControlFlow("if (" + fieldName + "RealmList != null)"); @@ -348,6 +356,7 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Setter writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); + emitCodeForInjectingObjectContext(writer, field, true, metadata.isPrimaryKey(field)); writer.emitStatement("LinkView links = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)); writer.emitStatement("links.clear()"); writer.beginControlFlow("if (value == null)"); @@ -371,6 +380,82 @@ private void emitAccessors(JavaWriter writer) throws IOException { } } + private void emitCodeForInjectingObjectContext(JavaWriter writer, VariableElement field, boolean isSetter, boolean isPrimaryKey) throws IOException { + // if invoked from model's constructor, inject BaseRealm and Row + writer.beginControlFlow("if (proxyState == null)"); + { + writer.emitSingleLineComment("Called from model's constructor. Inject context."); + writer.emitStatement("injectObjectContext()"); + } + writer.endControlFlow(); + writer.emitEmptyLine(); + + if (isSetter) { + writer.beginControlFlow("if (proxyState.isUnderConstruction())"); + { + if (isPrimaryKey) { + writer.emitSingleLineComment("default value of the primary key is always ignored."); + writer.emitStatement("return"); + } else { + writer.beginControlFlow("if (!proxyState.getAcceptDefaultValue$realm())") + .emitStatement("return") + .endControlFlow(); + if (Utils.isRealmModel(field)) { + // check excludeFields + writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", + field.getSimpleName().toString()) + .emitStatement("return") + .endControlFlow(); + writer.beginControlFlow("if (value != null && !RealmObject.isManaged(value))") + .emitStatement("value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value)") + .endControlFlow(); + } else if (Utils.isRealmList(field)) { + // check excludeFields + writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", + field.getSimpleName().toString()) + .emitStatement("return") + .endControlFlow(); + final String modelFqcn = Utils.getGenericTypeQualifiedName(field); + writer.beginControlFlow("if (value != null && !value.isManaged())") + .emitStatement("final Realm realm = (Realm) proxyState.getRealm$realm()") + .emitStatement("final RealmList<%1$s> original = value", modelFqcn) + .emitStatement("value = new RealmList<%1$s>()", modelFqcn) + .beginControlFlow("for (%1$s item : original)", modelFqcn) + .beginControlFlow("if (item == null || RealmObject.isManaged(item))") + .emitStatement("value.add(item)") + .nextControlFlow("else") + .emitStatement("value.add(realm.copyToRealm(item))") + .endControlFlow() + .endControlFlow() + .endControlFlow(); + } + } + } + writer.endControlFlow() + .emitEmptyLine(); + } + } + + private void emitInjectContextMethod(JavaWriter writer) throws IOException { + writer.beginMethod( + "void", // Return type + "injectObjectContext", // Method name + EnumSet.of(Modifier.PRIVATE) // Modifiers + ); // Argument type & argument name + + writer.emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()"); + writer.emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()); + writer.emitStatement("this.proxyState = new ProxyState(%1$s.class, this)", qualifiedClassName); + writer.emitStatement("proxyState.setRealm$realm(context.getRealm())"); + writer.emitStatement("proxyState.setRow$realm(context.getRow())"); + writer.emitStatement("proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue())"); + writer.emitStatement("proxyState.setExcludeFields$realm(context.getExcludeFields())"); + + writer.endMethod(); + writer.emitEmptyLine(); + } + + private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); writer.beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)); @@ -502,7 +587,7 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { if (metadata.isNullable(field)) { writer.beginControlFlow("if (!table.isColumnNullable(%s))", fieldIndexVariableReference(field)); // Check if the existing PrimaryKey does support null value for String, Byte, Short, Integer, & Long - if (field.equals(metadata.getPrimaryKey())) { + if (metadata.isPrimaryKey(field)) { writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + "\"@PrimaryKey field '%s' does not support null values in the existing Realm file. " + "Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required.\")", @@ -523,7 +608,7 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.endControlFlow(); } else { // check before migrating a nullable field containing null value to not-nullable PrimaryKey field for Realm version 0.89+ - if (field.equals(metadata.getPrimaryKey())) { + if (metadata.isPrimaryKey(field)) { writer .beginControlFlow("if (table.isColumnNullable(%s) && table.findFirstNull(%s) != TableOrView.NO_MATCH)", fieldIndexVariableReference(field), fieldIndexVariableReference(field)) @@ -549,7 +634,7 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { } // Validate @PrimaryKey - if (field.equals(metadata.getPrimaryKey())) { + if (metadata.isPrimaryKey(field)) { writer.beginControlFlow("if (table.getPrimaryKey() != table.getColumnIndex(\"%s\"))", fieldName); writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Primary key not defined for field '%s' in existing Realm file. Add @PrimaryKey.\")", fieldName); writer.endControlFlow(); @@ -650,6 +735,8 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { .emitStatement("return object") .endControlFlow(); + writer.emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()"); + writer.emitStatement("RealmObjectProxy cachedRealmObject = cache.get(object)"); writer.beginControlFlow("if (cachedRealmObject != null)") .emitStatement("return (%s) cachedRealmObject", qualifiedClassName) @@ -695,12 +782,16 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer .beginControlFlow("if (rowIndex != TableOrView.NO_MATCH)") - .emitStatement("realmObject = new %s(realm.schema.getColumnInfo(%s.class))", - qualifiedGeneratedClassName, - qualifiedClassName) - .emitStatement("((RealmObjectProxy)realmObject).realmGet$proxyState().setRealm$realm(realm)") - .emitStatement("((RealmObjectProxy)realmObject).realmGet$proxyState().setRow$realm(table.getUncheckedRow(rowIndex))") - .emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") + .beginControlFlow("try") + .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex)," + + " realm.schema.getColumnInfo(%s.class)," + + " false, Collections. emptyList())", qualifiedClassName) + .emitStatement("realmObject = new %s()", qualifiedGeneratedClassName) + .emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") + .nextControlFlow("finally") + .emitStatement("objectContext.clear()") + .endControlFlow() + .nextControlFlow("else") .emitStatement("canUpdate = false") .endControlFlow(); @@ -1204,11 +1295,13 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { .emitStatement("return (%s) cachedRealmObject", qualifiedClassName) .nextControlFlow("else"); + writer.emitSingleLineComment("rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields."); if (metadata.hasPrimaryKey()) { - writer.emitStatement("%s realmObject = realm.createObject(%s.class, ((%s) newObject).%s())", + writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, ((%s) newObject).%s(), false, Collections.emptyList())", qualifiedClassName, qualifiedClassName, interfaceName, metadata.getPrimaryKeyGetter()); } else { - writer.emitStatement("%s realmObject = realm.createObject(%s.class)", qualifiedClassName, qualifiedClassName); + writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, false, Collections.emptyList())", + qualifiedClassName, qualifiedClassName); } writer.emitStatement("cache.put(newObject, (RealmObjectProxy) realmObject)"); for (VariableElement field : metadata.getFields()) { @@ -1217,7 +1310,7 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { String setter = metadata.getSetter(fieldName); String getter = metadata.getGetter(fieldName); - if (field.equals(metadata.getPrimaryKey())) { + if (metadata.isPrimaryKey(field)) { // PK has been set when creating object. continue; } @@ -1498,7 +1591,7 @@ private void emitEqualsMethod(JavaWriter writer) throws IOException { writer.emitEmptyLine(); writer.emitStatement("String path = proxyState.getRealm$realm().getPath()"); writer.emitStatement("String otherPath = %s.proxyState.getRealm$realm().getPath()", otherObjectVarName); - writer.emitStatement("if (path != null ? !path.equals(otherPath) : otherPath != null) return false;"); + writer.emitStatement("if (path != null ? !path.equals(otherPath) : otherPath != null) return false"); writer.emitEmptyLine(); writer.emitStatement("String tableName = proxyState.getRow$realm().getTable().getName()"); writer.emitStatement("String otherTableName = %s.proxyState.getRow$realm().getTable().getName()", otherObjectVarName); @@ -1511,7 +1604,6 @@ private void emitEqualsMethod(JavaWriter writer) throws IOException { writer.emitEmptyLine(); } - private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOException { writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.beginMethod( @@ -1521,8 +1613,17 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep Arrays.asList("Realm", "realm", "JSONObject", "json", "boolean", "update"), Collections.singletonList("JSONException")); + final int modelOrListCount = countModelOrListFields(metadata.getFields()); + if (modelOrListCount == 0) { + writer.emitStatement("final List excludeFields = Collections. emptyList()"); + } else { + writer.emitStatement("final List excludeFields = new ArrayList(%1$d)", + modelOrListCount); + } if (!metadata.hasPrimaryKey()) { - writer.emitStatement("%s obj = realm.createObject(%s.class)", qualifiedClassName, qualifiedClassName); + buildExcludeFieldsList(writer, metadata.getFields()); + writer.emitStatement("%s obj = realm.createObjectInternal(%s.class, true, excludeFields)", + qualifiedClassName, qualifiedClassName); } else { String pkType = Utils.isString(metadata.getPrimaryKey()) ? "String" : "Long"; writer @@ -1548,14 +1649,20 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep } writer .beginControlFlow("if (rowIndex != TableOrView.NO_MATCH)") - .emitStatement("obj = new %s(realm.schema.getColumnInfo(%s.class))", - qualifiedGeneratedClassName, qualifiedClassName) - .emitStatement("((RealmObjectProxy)obj).realmGet$proxyState().setRealm$realm(realm)") - .emitStatement("((RealmObjectProxy)obj).realmGet$proxyState().setRow$realm(table.getUncheckedRow(rowIndex))") + .emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") + .beginControlFlow("try") + .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex)," + + " realm.schema.getColumnInfo(%s.class)," + + " false, Collections. emptyList())", qualifiedClassName) + .emitStatement("obj = new %s()", qualifiedGeneratedClassName) + .nextControlFlow("finally") + .emitStatement("objectContext.clear()") + .endControlFlow() .endControlFlow() .endControlFlow(); writer.beginControlFlow("if (obj == null)"); + buildExcludeFieldsList(writer, metadata.getFields()); String primaryKeyFieldType = metadata.getPrimaryKey().asType().toString(); String primaryKeyFieldName = metadata.getPrimaryKey().getSimpleName().toString(); RealmJsonTypeHelper.emitCreateObjectWithPrimaryKeyValue(qualifiedClassName, qualifiedGeneratedClassName, @@ -1566,7 +1673,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String qualifiedFieldType = field.asType().toString(); - if (field.equals(metadata.getPrimaryKey())) { + if (metadata.isPrimaryKey(field)) { // Primary key has already been set when adding new row or finding the existing row. continue; } @@ -1606,10 +1713,19 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep writer.emitEmptyLine(); } - // FIXME: Since we need to check the PK in stream before create an object, this is now using copyToRealm instead of - // createObject() to avoid parse the stream twice. This brings a problem that the default value behaviour is - // different from those which are using the createObject. And it needs to be addressed by - // https://github.com/realm/realm-java/issues/777 + private void buildExcludeFieldsList(JavaWriter writer, List fields) throws IOException { + for (VariableElement field : fields) { + if (Utils.isRealmModel(field) || Utils.isRealmList(field)) { + final String fieldName = field.getSimpleName().toString(); + writer.beginControlFlow("if (json.has(\"%1$s\"))", fieldName) + .emitStatement("excludeFields.add(\"%1$s\")", fieldName) + .endControlFlow(); + } + } + } + + // Since we need to check the PK in stream before creating the object, this is now using copyToRealm + // instead of createObject() to avoid parsing the stream twice. private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.emitAnnotation("TargetApi", "Build.VERSION_CODES.HONEYCOMB"); @@ -1699,4 +1815,14 @@ private String columnIndexVarName(VariableElement variableElement) { private String fieldIndexVariableReference(VariableElement variableElement) { return "columnInfo." + columnIndexVarName(variableElement); } + + private static int countModelOrListFields(List fields) { + int count = 0; + for (VariableElement f : fields) { + if (Utils.isRealmModel(f) || Utils.isRealmList(f)) { + count++; + } + } + return count; + } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index 8c759f78e4..69f14ae529 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -77,6 +77,7 @@ public void generate() throws IOException { "io.realm.internal.SharedRealm", "io.realm.internal.RealmObjectProxy", "io.realm.internal.RealmProxyMediator", + "io.realm.internal.Row", "io.realm.internal.Table", "org.json.JSONException", "org.json.JSONObject" @@ -204,14 +205,25 @@ private void emitNewInstanceMethod(JavaWriter writer) throws IOException { " E", "newInstance", EnumSet.of(Modifier.PUBLIC), - "Class", "clazz", "ColumnInfo", "columnInfo" + "Class", "clazz", + "Object", "baseRealm", + "Row", "row", + "ColumnInfo", "columnInfo", + "boolean", "acceptDefaultValue", + "List", "excludeFields" ); + writer.emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()"); + writer.beginControlFlow("try") + .emitStatement("objectContext.set((BaseRealm) baseRealm, row, columnInfo, acceptDefaultValue, excludeFields)"); emitMediatorSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return clazz.cast(new %s(columnInfo))", qualifiedProxyClasses.get(i)); + writer.emitStatement("return clazz.cast(new %s())", qualifiedProxyClasses.get(i)); } }, writer); + writer.nextControlFlow("finally") + .emitStatement("objectContext.clear()") + .endControlFlow(); writer.endMethod(); writer.emitEmptyLine(); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 49d3817280..17697142ad 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -88,8 +88,8 @@ public final AllTypesColumnInfo clone() { } } - private final AllTypesColumnInfo columnInfo; - private final ProxyState proxyState; + private AllTypesColumnInfo columnInfo; + private ProxyState proxyState; private RealmList columnRealmListRealmList; private static final List FIELD_NAMES; static { @@ -106,73 +106,180 @@ public final AllTypesColumnInfo clone() { FIELD_NAMES = Collections.unmodifiableList(fieldNames); } - AllTypesRealmProxy(ColumnInfo columnInfo) { - this.columnInfo = (AllTypesColumnInfo) columnInfo; + AllTypesRealmProxy() { + if (proxyState == null) { + injectObjectContext(); + } + proxyState.setConstructionFinished(); + } + + private void injectObjectContext() { + final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); + this.columnInfo = (AllTypesColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.AllTypes.class, this); + proxyState.setRealm$realm(context.getRealm()); + proxyState.setRow$realm(context.getRow()); + proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); + proxyState.setExcludeFields$realm(context.getExcludeFields()); } @SuppressWarnings("cast") public String realmGet$columnString() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.columnStringIndex); } public void realmSet$columnString(String value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + // default value of the primary key is always ignored. + return; + } + proxyState.getRealm$realm().checkIfValid(); throw new io.realm.exceptions.RealmException("Primary key field 'columnString' cannot be changed after object was created."); } @SuppressWarnings("cast") public long realmGet$columnLong() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (long) proxyState.getRow$realm().getLong(columnInfo.columnLongIndex); } public void realmSet$columnLong(long value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); proxyState.getRow$realm().setLong(columnInfo.columnLongIndex, value); } @SuppressWarnings("cast") public float realmGet$columnFloat() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (float) proxyState.getRow$realm().getFloat(columnInfo.columnFloatIndex); } public void realmSet$columnFloat(float value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); proxyState.getRow$realm().setFloat(columnInfo.columnFloatIndex, value); } @SuppressWarnings("cast") public double realmGet$columnDouble() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (double) proxyState.getRow$realm().getDouble(columnInfo.columnDoubleIndex); } public void realmSet$columnDouble(double value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); proxyState.getRow$realm().setDouble(columnInfo.columnDoubleIndex, value); } @SuppressWarnings("cast") public boolean realmGet$columnBoolean() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.columnBooleanIndex); } public void realmSet$columnBoolean(boolean value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); proxyState.getRow$realm().setBoolean(columnInfo.columnBooleanIndex, value); } @SuppressWarnings("cast") public Date realmGet$columnDate() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.columnDateIndex); } public void realmSet$columnDate(Date value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'columnDate' to null."); @@ -182,11 +289,27 @@ public final AllTypesColumnInfo clone() { @SuppressWarnings("cast") public byte[] realmGet$columnBinary() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.columnBinaryIndex); } public void realmSet$columnBinary(byte[] value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'columnBinary' to null."); @@ -195,14 +318,36 @@ public final AllTypesColumnInfo clone() { } public some.test.AllTypes realmGet$columnObject() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNullLink(columnInfo.columnObjectIndex)) { return null; } - return proxyState.getRealm$realm().get(some.test.AllTypes.class, proxyState.getRow$realm().getLink(columnInfo.columnObjectIndex)); + return proxyState.getRealm$realm().get(some.test.AllTypes.class, proxyState.getRow$realm().getLink(columnInfo.columnObjectIndex), false, Collections.emptyList()); } public void realmSet$columnObject(some.test.AllTypes value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnObject")) { + return; + } + if (value != null && !RealmObject.isManaged(value)) { + value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value); + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().nullifyLink(columnInfo.columnObjectIndex); @@ -218,6 +363,11 @@ public final AllTypesColumnInfo clone() { } public RealmList realmGet$columnRealmList() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); // use the cached value if available if (columnRealmListRealmList != null) { @@ -231,6 +381,32 @@ public final AllTypesColumnInfo clone() { public void realmSet$columnRealmList(RealmList value) { proxyState.getRealm$realm().checkIfValid(); + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnRealmList")) { + return; + } + if (value != null && !value.isManaged()) { + final Realm realm = (Realm) proxyState.getRealm$realm(); + final RealmList original = value; + value = new RealmList(); + for (some.test.AllTypes item : original) { + if (item == null || RealmObject.isManaged(item)) { + value.add(item); + } else { + value.add(realm.copyToRealm(item)); + } + } + } + } + LinkView links = proxyState.getRow$realm().getLinkList(columnInfo.columnRealmListIndex); links.clear(); if (value == null) { @@ -405,6 +581,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { + final List excludeFields = new ArrayList(2); some.test.AllTypes obj = null; if (update) { Table table = realm.getTable(some.test.AllTypes.class); @@ -416,17 +593,27 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON rowIndex = table.findFirstString(pkColumnIndex, json.getString("columnString")); } if (rowIndex != TableOrView.NO_MATCH) { - obj = new io.realm.AllTypesRealmProxy(realm.schema.getColumnInfo(some.test.AllTypes.class)); - ((RealmObjectProxy)obj).realmGet$proxyState().setRealm$realm(realm); - ((RealmObjectProxy)obj).realmGet$proxyState().setRow$realm(table.getUncheckedRow(rowIndex)); + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + try { + objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.schema.getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); + obj = new io.realm.AllTypesRealmProxy(); + } finally { + objectContext.clear(); + } } } if (obj == null) { + if (json.has("columnObject")) { + excludeFields.add("columnObject"); + } + if (json.has("columnRealmList")) { + excludeFields.add("columnRealmList"); + } if (json.has("columnString")) { if (json.isNull("columnString")) { - obj = (io.realm.AllTypesRealmProxy) realm.createObject(some.test.AllTypes.class, null); + obj = (io.realm.AllTypesRealmProxy) realm.createObjectInternal(some.test.AllTypes.class, null, true, excludeFields); } else { - obj = (io.realm.AllTypesRealmProxy) realm.createObject(some.test.AllTypes.class, json.getString("columnString")); + obj = (io.realm.AllTypesRealmProxy) realm.createObjectInternal(some.test.AllTypes.class, json.getString("columnString"), true, excludeFields); } } else { throw new IllegalArgumentException("JSON object doesn't have the primary key field 'columnString'."); @@ -606,6 +793,7 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (some.test.AllTypes) cachedRealmObject; @@ -623,10 +811,13 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob rowIndex = table.findFirstString(pkColumnIndex, value); } if (rowIndex != TableOrView.NO_MATCH) { - realmObject = new io.realm.AllTypesRealmProxy(realm.schema.getColumnInfo(some.test.AllTypes.class)); - ((RealmObjectProxy)realmObject).realmGet$proxyState().setRealm$realm(realm); - ((RealmObjectProxy)realmObject).realmGet$proxyState().setRow$realm(table.getUncheckedRow(rowIndex)); - cache.put(object, (RealmObjectProxy) realmObject); + try { + objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.schema.getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); + realmObject = new io.realm.AllTypesRealmProxy(); + cache.put(object, (RealmObjectProxy) realmObject); + } finally { + objectContext.clear(); + } } else { canUpdate = false; } @@ -645,7 +836,8 @@ public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, if (cachedRealmObject != null) { return (some.test.AllTypes) cachedRealmObject; } else { - some.test.AllTypes realmObject = realm.createObject(some.test.AllTypes.class, ((AllTypesRealmProxyInterface) newObject).realmGet$columnString()); + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.AllTypes realmObject = realm.createObjectInternal(some.test.AllTypes.class, ((AllTypesRealmProxyInterface) newObject).realmGet$columnString(), false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); ((AllTypesRealmProxyInterface) realmObject).realmSet$columnLong(((AllTypesRealmProxyInterface) newObject).realmGet$columnLong()); ((AllTypesRealmProxyInterface) realmObject).realmSet$columnFloat(((AllTypesRealmProxyInterface) newObject).realmGet$columnFloat()); @@ -1094,7 +1286,7 @@ public boolean equals(Object o) { String path = proxyState.getRealm$realm().getPath(); String otherPath = aAllTypes.proxyState.getRealm$realm().getPath(); - if (path != null ? !path.equals(otherPath) : otherPath != null) return false;; + if (path != null ? !path.equals(otherPath) : otherPath != null) return false; String tableName = proxyState.getRow$realm().getTable().getName(); String otherTableName = aAllTypes.proxyState.getRow$realm().getTable().getName(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 3d6f35d867..7621195151 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -68,8 +68,8 @@ public final BooleansColumnInfo clone() { } } - private final BooleansColumnInfo columnInfo; - private final ProxyState proxyState; + private BooleansColumnInfo columnInfo; + private ProxyState proxyState; private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -80,51 +80,127 @@ public final BooleansColumnInfo clone() { FIELD_NAMES = Collections.unmodifiableList(fieldNames); } - BooleansRealmProxy(ColumnInfo columnInfo) { - this.columnInfo = (BooleansColumnInfo) columnInfo; + BooleansRealmProxy() { + if (proxyState == null) { + injectObjectContext(); + } + proxyState.setConstructionFinished(); + } + + private void injectObjectContext() { + final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); + this.columnInfo = (BooleansColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.Booleans.class, this); + proxyState.setRealm$realm(context.getRealm()); + proxyState.setRow$realm(context.getRow()); + proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); + proxyState.setExcludeFields$realm(context.getExcludeFields()); } @SuppressWarnings("cast") public boolean realmGet$done() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.doneIndex); } public void realmSet$done(boolean value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); proxyState.getRow$realm().setBoolean(columnInfo.doneIndex, value); } @SuppressWarnings("cast") public boolean realmGet$isReady() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.isReadyIndex); } public void realmSet$isReady(boolean value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); proxyState.getRow$realm().setBoolean(columnInfo.isReadyIndex, value); } @SuppressWarnings("cast") public boolean realmGet$mCompleted() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.mCompletedIndex); } public void realmSet$mCompleted(boolean value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); proxyState.getRow$realm().setBoolean(columnInfo.mCompletedIndex, value); } @SuppressWarnings("cast") public boolean realmGet$anotherBoolean() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.anotherBooleanIndex); } public void realmSet$anotherBoolean(boolean value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); proxyState.getRow$realm().setBoolean(columnInfo.anotherBooleanIndex, value); } @@ -216,7 +292,8 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { - some.test.Booleans obj = realm.createObject(some.test.Booleans.class); + final List excludeFields = Collections. emptyList(); + some.test.Booleans obj = realm.createObjectInternal(some.test.Booleans.class, true, excludeFields); if (json.has("done")) { if (json.isNull("done")) { throw new IllegalArgumentException("Trying to set non-nullable field 'done' to null."); @@ -300,6 +377,7 @@ public static some.test.Booleans copyOrUpdate(Realm realm, some.test.Booleans ob if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (some.test.Booleans) cachedRealmObject; @@ -313,7 +391,8 @@ public static some.test.Booleans copy(Realm realm, some.test.Booleans newObject, if (cachedRealmObject != null) { return (some.test.Booleans) cachedRealmObject; } else { - some.test.Booleans realmObject = realm.createObject(some.test.Booleans.class); + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.Booleans realmObject = realm.createObjectInternal(some.test.Booleans.class, false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); ((BooleansRealmProxyInterface) realmObject).realmSet$done(((BooleansRealmProxyInterface) newObject).realmGet$done()); ((BooleansRealmProxyInterface) realmObject).realmSet$isReady(((BooleansRealmProxyInterface) newObject).realmGet$isReady()); @@ -475,7 +554,7 @@ public boolean equals(Object o) { String path = proxyState.getRealm$realm().getPath(); String otherPath = aBooleans.proxyState.getRealm$realm().getPath(); - if (path != null ? !path.equals(otherPath) : otherPath != null) return false;; + if (path != null ? !path.equals(otherPath) : otherPath != null) return false; String tableName = proxyState.getRow$realm().getTable().getName(); String otherTableName = aBooleans.proxyState.getRow$realm().getTable().getName(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 8c0bb734ea..b8b1322464 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -136,8 +136,8 @@ public final NullTypesColumnInfo clone() { } } - private final NullTypesColumnInfo columnInfo; - private final ProxyState proxyState; + private NullTypesColumnInfo columnInfo; + private ProxyState proxyState; private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -165,18 +165,46 @@ public final NullTypesColumnInfo clone() { FIELD_NAMES = Collections.unmodifiableList(fieldNames); } - NullTypesRealmProxy(ColumnInfo columnInfo) { - this.columnInfo = (NullTypesColumnInfo) columnInfo; + NullTypesRealmProxy() { + if (proxyState == null) { + injectObjectContext(); + } + proxyState.setConstructionFinished(); + } + + private void injectObjectContext() { + final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); + this.columnInfo = (NullTypesColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.NullTypes.class, this); + proxyState.setRealm$realm(context.getRealm()); + proxyState.setRow$realm(context.getRow()); + proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); + proxyState.setExcludeFields$realm(context.getExcludeFields()); } @SuppressWarnings("cast") public String realmGet$fieldStringNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNotNullIndex); } public void realmSet$fieldStringNotNull(String value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldStringNotNull' to null."); @@ -186,11 +214,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public String realmGet$fieldStringNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNullIndex); } public void realmSet$fieldStringNull(String value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldStringNullIndex); @@ -201,11 +245,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Boolean realmGet$fieldBooleanNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.fieldBooleanNotNullIndex); } public void realmSet$fieldBooleanNotNull(Boolean value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldBooleanNotNull' to null."); @@ -215,6 +275,11 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Boolean realmGet$fieldBooleanNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldBooleanNullIndex)) { return null; @@ -223,6 +288,17 @@ public final NullTypesColumnInfo clone() { } public void realmSet$fieldBooleanNull(Boolean value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldBooleanNullIndex); @@ -233,11 +309,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public byte[] realmGet$fieldBytesNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNotNullIndex); } public void realmSet$fieldBytesNotNull(byte[] value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldBytesNotNull' to null."); @@ -247,11 +339,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public byte[] realmGet$fieldBytesNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNullIndex); } public void realmSet$fieldBytesNull(byte[] value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldBytesNullIndex); @@ -262,11 +370,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Byte realmGet$fieldByteNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (byte) proxyState.getRow$realm().getLong(columnInfo.fieldByteNotNullIndex); } public void realmSet$fieldByteNotNull(Byte value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldByteNotNull' to null."); @@ -276,6 +400,11 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Byte realmGet$fieldByteNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldByteNullIndex)) { return null; @@ -284,6 +413,17 @@ public final NullTypesColumnInfo clone() { } public void realmSet$fieldByteNull(Byte value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldByteNullIndex); @@ -294,11 +434,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Short realmGet$fieldShortNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (short) proxyState.getRow$realm().getLong(columnInfo.fieldShortNotNullIndex); } public void realmSet$fieldShortNotNull(Short value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldShortNotNull' to null."); @@ -308,6 +464,11 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Short realmGet$fieldShortNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldShortNullIndex)) { return null; @@ -316,6 +477,17 @@ public final NullTypesColumnInfo clone() { } public void realmSet$fieldShortNull(Short value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldShortNullIndex); @@ -326,11 +498,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Integer realmGet$fieldIntegerNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (int) proxyState.getRow$realm().getLong(columnInfo.fieldIntegerNotNullIndex); } public void realmSet$fieldIntegerNotNull(Integer value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldIntegerNotNull' to null."); @@ -340,6 +528,11 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Integer realmGet$fieldIntegerNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldIntegerNullIndex)) { return null; @@ -348,6 +541,17 @@ public final NullTypesColumnInfo clone() { } public void realmSet$fieldIntegerNull(Integer value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldIntegerNullIndex); @@ -358,11 +562,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Long realmGet$fieldLongNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (long) proxyState.getRow$realm().getLong(columnInfo.fieldLongNotNullIndex); } public void realmSet$fieldLongNotNull(Long value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldLongNotNull' to null."); @@ -372,6 +592,11 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Long realmGet$fieldLongNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldLongNullIndex)) { return null; @@ -380,6 +605,17 @@ public final NullTypesColumnInfo clone() { } public void realmSet$fieldLongNull(Long value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldLongNullIndex); @@ -390,11 +626,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Float realmGet$fieldFloatNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (float) proxyState.getRow$realm().getFloat(columnInfo.fieldFloatNotNullIndex); } public void realmSet$fieldFloatNotNull(Float value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldFloatNotNull' to null."); @@ -404,6 +656,11 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Float realmGet$fieldFloatNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldFloatNullIndex)) { return null; @@ -412,6 +669,17 @@ public final NullTypesColumnInfo clone() { } public void realmSet$fieldFloatNull(Float value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldFloatNullIndex); @@ -422,11 +690,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Double realmGet$fieldDoubleNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (double) proxyState.getRow$realm().getDouble(columnInfo.fieldDoubleNotNullIndex); } public void realmSet$fieldDoubleNotNull(Double value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDoubleNotNull' to null."); @@ -436,6 +720,11 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Double realmGet$fieldDoubleNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldDoubleNullIndex)) { return null; @@ -444,6 +733,17 @@ public final NullTypesColumnInfo clone() { } public void realmSet$fieldDoubleNull(Double value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldDoubleNullIndex); @@ -454,11 +754,27 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Date realmGet$fieldDateNotNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.fieldDateNotNullIndex); } public void realmSet$fieldDateNotNull(Date value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDateNotNull' to null."); @@ -468,6 +784,11 @@ public final NullTypesColumnInfo clone() { @SuppressWarnings("cast") public Date realmGet$fieldDateNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldDateNullIndex)) { return null; @@ -476,6 +797,17 @@ public final NullTypesColumnInfo clone() { } public void realmSet$fieldDateNull(Date value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.fieldDateNullIndex); @@ -485,14 +817,36 @@ public final NullTypesColumnInfo clone() { } public some.test.NullTypes realmGet$fieldObjectNull() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNullLink(columnInfo.fieldObjectNullIndex)) { return null; } - return proxyState.getRealm$realm().get(some.test.NullTypes.class, proxyState.getRow$realm().getLink(columnInfo.fieldObjectNullIndex)); + return proxyState.getRealm$realm().get(some.test.NullTypes.class, proxyState.getRow$realm().getLink(columnInfo.fieldObjectNullIndex), false, Collections.emptyList()); } public void realmSet$fieldObjectNull(some.test.NullTypes value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldObjectNull")) { + return; + } + if (value != null && !RealmObject.isManaged(value)) { + value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value); + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().nullifyLink(columnInfo.fieldObjectNullIndex); @@ -771,7 +1125,11 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { - some.test.NullTypes obj = realm.createObject(some.test.NullTypes.class); + final List excludeFields = new ArrayList(1); + if (json.has("fieldObjectNull")) { + excludeFields.add("fieldObjectNull"); + } + some.test.NullTypes obj = realm.createObjectInternal(some.test.NullTypes.class, true, excludeFields); if (json.has("fieldStringNotNull")) { if (json.isNull("fieldStringNotNull")) { ((NullTypesRealmProxyInterface) obj).realmSet$fieldStringNotNull(null); @@ -1115,6 +1473,7 @@ public static some.test.NullTypes copyOrUpdate(Realm realm, some.test.NullTypes if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (some.test.NullTypes) cachedRealmObject; @@ -1128,7 +1487,8 @@ public static some.test.NullTypes copy(Realm realm, some.test.NullTypes newObjec if (cachedRealmObject != null) { return (some.test.NullTypes) cachedRealmObject; } else { - some.test.NullTypes realmObject = realm.createObject(some.test.NullTypes.class); + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.NullTypes realmObject = realm.createObjectInternal(some.test.NullTypes.class, false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldStringNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldStringNotNull()); ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldStringNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldStringNull()); @@ -1829,7 +2189,7 @@ public boolean equals(Object o) { String path = proxyState.getRealm$realm().getPath(); String otherPath = aNullTypes.proxyState.getRealm$realm().getPath(); - if (path != null ? !path.equals(otherPath) : otherPath != null) return false;; + if (path != null ? !path.equals(otherPath) : otherPath != null) return false; String tableName = proxyState.getRow$realm().getTable().getName(); String otherTableName = aNullTypes.proxyState.getRow$realm().getTable().getName(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 5938c43b0f..0edbe6cb1f 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -5,6 +5,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import java.io.IOException; @@ -74,13 +75,19 @@ public String getTableName(Class clazz) { } @Override - public E newInstance(Class clazz, ColumnInfo columnInfo) { - checkClass(clazz); + public E newInstance(Class clazz, Object baseRealm, Row row, ColumnInfo columnInfo, boolean acceptDefaultValue, List excludeFields) { + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + try { + objectContext.set((BaseRealm) baseRealm, row, columnInfo, acceptDefaultValue, excludeFields); + checkClass(clazz); - if (clazz.equals(some.test.AllTypes.class)) { - return clazz.cast(new io.realm.AllTypesRealmProxy(columnInfo)); - } else { - throw getMissingProxyClassException(clazz); + if (clazz.equals(some.test.AllTypes.class)) { + return clazz.cast(new io.realm.AllTypesRealmProxy()); + } else { + throw getMissingProxyClassException(clazz); + } + } finally { + objectContext.clear(); } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 1fedc38f07..c25a606f1f 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -60,8 +60,8 @@ public final SimpleColumnInfo clone() { } } - private final SimpleColumnInfo columnInfo; - private final ProxyState proxyState; + private SimpleColumnInfo columnInfo; + private ProxyState proxyState; private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -70,18 +70,46 @@ public final SimpleColumnInfo clone() { FIELD_NAMES = Collections.unmodifiableList(fieldNames); } - SimpleRealmProxy(ColumnInfo columnInfo) { - this.columnInfo = (SimpleColumnInfo) columnInfo; + SimpleRealmProxy() { + if (proxyState == null) { + injectObjectContext(); + } + proxyState.setConstructionFinished(); + } + + private void injectObjectContext() { + final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); + this.columnInfo = (SimpleColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.Simple.class, this); + proxyState.setRealm$realm(context.getRealm()); + proxyState.setRow$realm(context.getRow()); + proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); + proxyState.setExcludeFields$realm(context.getExcludeFields()); } @SuppressWarnings("cast") public String realmGet$name() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.nameIndex); } public void realmSet$name(String value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); if (value == null) { proxyState.getRow$realm().setNull(columnInfo.nameIndex); @@ -92,11 +120,27 @@ public final SimpleColumnInfo clone() { @SuppressWarnings("cast") public int realmGet$age() { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + proxyState.getRealm$realm().checkIfValid(); return (int) proxyState.getRow$realm().getLong(columnInfo.ageIndex); } public void realmSet$age(int value) { + if (proxyState == null) { + // Called from model's constructor. Inject context. + injectObjectContext(); + } + + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + } + proxyState.getRealm$realm().checkIfValid(); proxyState.getRow$realm().setLong(columnInfo.ageIndex, value); } @@ -168,7 +212,8 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { - some.test.Simple obj = realm.createObject(some.test.Simple.class); + final List excludeFields = Collections. emptyList(); + some.test.Simple obj = realm.createObjectInternal(some.test.Simple.class, true, excludeFields); if (json.has("name")) { if (json.isNull("name")) { ((SimpleRealmProxyInterface) obj).realmSet$name(null); @@ -224,6 +269,7 @@ public static some.test.Simple copyOrUpdate(Realm realm, some.test.Simple object if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (some.test.Simple) cachedRealmObject; @@ -237,7 +283,8 @@ public static some.test.Simple copy(Realm realm, some.test.Simple newObject, boo if (cachedRealmObject != null) { return (some.test.Simple) cachedRealmObject; } else { - some.test.Simple realmObject = realm.createObject(some.test.Simple.class); + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.Simple realmObject = realm.createObjectInternal(some.test.Simple.class, false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); ((SimpleRealmProxyInterface) realmObject).realmSet$name(((SimpleRealmProxyInterface) newObject).realmGet$name()); ((SimpleRealmProxyInterface) realmObject).realmSet$age(((SimpleRealmProxyInterface) newObject).realmGet$age()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 14f5b6f372..9485c8f454 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -27,17 +27,14 @@ import org.junit.runner.RunWith; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Date; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import dk.ilios.spanner.All; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AnnotationIndexTypes; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index 1e697d5f43..2f1219acd6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -19,8 +19,11 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import android.text.TextUtils; import android.util.Base64; +import com.google.gson.internal.bind.util.ISO8601Utils; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -42,10 +45,12 @@ import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.AnnotationTypes; +import io.realm.entities.DefaultValueOfField; import io.realm.entities.Dog; import io.realm.entities.NoPrimaryKeyNullTypes; import io.realm.entities.NullTypes; import io.realm.entities.OwnerPrimaryKey; +import io.realm.entities.RandomPrimaryKey; import io.realm.exceptions.RealmException; import io.realm.rule.TestRealmConfigurationFactory; @@ -363,6 +368,220 @@ public void createAllFromJson_jsonArray() throws JSONException { assertEquals(1, realm.where(Dog.class).equalTo("name", "Fido-3").findAll().size()); } + @Test + public void createFromJson_respectDefaultValues() throws JSONException { + final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1; + + // Step 1: Prepare almost empty JSON + final JSONObject json = new JSONObject(); + json.put(DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, fieldLongPrimaryKeyValue); + + // Step 2: Update with almost empty JSONObject + realm.beginTransaction(); + final DefaultValueOfField managedObj = realm.createOrUpdateObjectFromJson(DefaultValueOfField.class, json); + realm.commitTransaction(); + + // Step 3: Check that default values are applied + assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE, + managedObj.getFieldIgnored()); + assertEquals(DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE, managedObj.getFieldString()); + assertFalse(TextUtils.isEmpty(managedObj.getFieldRandomString())); + assertEquals(DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE, managedObj.getFieldShort()); + assertEquals(DefaultValueOfField.FIELD_INT_DEFAULT_VALUE, managedObj.getFieldInt()); + assertEquals(fieldLongPrimaryKeyValue, managedObj.getFieldLongPrimaryKey()); + assertEquals(DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE, managedObj.getFieldLong()); + assertEquals(DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE, managedObj.getFieldByte()); + assertEquals(DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE, managedObj.getFieldFloat(), 0f); + assertEquals(DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE, managedObj.getFieldDouble(), 0d); + assertEquals(DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE, managedObj.isFieldBoolean()); + assertEquals(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE, managedObj.getFieldDate()); + assertTrue(Arrays.equals(DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE, managedObj.getFieldBinary())); + assertEquals(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE, managedObj.getFieldObject().getFieldInt()); + assertEquals(1, managedObj.getFieldList().size()); + assertEquals(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE, managedObj.getFieldList().first().getFieldInt()); + + // make sure that excess object by default value is not created. + assertEquals(2, realm.where(RandomPrimaryKey.class).count()); + } + + @Test + public void createFromJson_defaultValuesAreIgnored() throws JSONException { + final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1; + + // Step 1: Prepare JSON + final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"; + final String fieldStringValue = DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified"; + final String fieldRandomStringValue = "non-random"; + final short fieldShortValue = (short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1); + final int fieldIntValue = DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1; + final long fieldLongValue = DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1; + final byte fieldByteValue = (byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1); + final float fieldFloatValue = DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1; + final double fieldDoubleValue = DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1; + final boolean fieldBooleanValue = !DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE; + final Date fieldDateValue = new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1); + final byte[] fieldBinaryValue = {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}; + final int fieldObjectIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1; + final int fieldListIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2; + + final JSONObject json = new JSONObject(); + json.put(DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, fieldLongPrimaryKeyValue); + json.put(DefaultValueOfField.FIELD_IGNORED, fieldIgnoredValue); + json.put(DefaultValueOfField.FIELD_STRING, fieldStringValue); + json.put(DefaultValueOfField.FIELD_RANDOM_STRING, fieldRandomStringValue); + json.put(DefaultValueOfField.FIELD_SHORT, fieldShortValue); + json.put(DefaultValueOfField.FIELD_INT, fieldIntValue); + json.put(DefaultValueOfField.FIELD_LONG, fieldLongValue); + json.put(DefaultValueOfField.FIELD_BYTE, fieldByteValue); + json.put(DefaultValueOfField.FIELD_FLOAT, fieldFloatValue); + json.put(DefaultValueOfField.FIELD_DOUBLE, fieldDoubleValue); + json.put(DefaultValueOfField.FIELD_BOOLEAN, fieldBooleanValue); + json.put(DefaultValueOfField.FIELD_DATE, ISO8601Utils.format(fieldDateValue, true)); + json.put(DefaultValueOfField.FIELD_BINARY, Base64.encodeToString(fieldBinaryValue, Base64.DEFAULT)); + // value for 'fieldObject' + final JSONObject fieldObjectJson = new JSONObject(); + fieldObjectJson.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY, "pk of fieldObject"); + fieldObjectJson.put(RandomPrimaryKey.FIELD_INT, fieldObjectIntValue); + json.put(DefaultValueOfField.FIELD_OBJECT, fieldObjectJson); + // value for 'fieldList' + final JSONArray fieldListArrayJson = new JSONArray(); + final JSONObject fieldListItem0Json = new JSONObject(); + fieldListItem0Json.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY, "pk1 of fieldList"); + fieldListItem0Json.put(RandomPrimaryKey.FIELD_INT, fieldListIntValue); + fieldListArrayJson.put(fieldListItem0Json); + final JSONObject fieldListItem1Json = new JSONObject(); + fieldListItem1Json.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY, "pk2 of fieldList"); + fieldListItem1Json.put(RandomPrimaryKey.FIELD_INT, fieldListIntValue + 1); + fieldListArrayJson.put(fieldListItem1Json); + json.put(DefaultValueOfField.FIELD_LIST, fieldListArrayJson); + + // Step 3: Update with JSONObject + realm.beginTransaction(); + final DefaultValueOfField managedObj = realm.createOrUpdateObjectFromJson(DefaultValueOfField.class, json); + realm.commitTransaction(); + + // Step 4: Check that properly created + assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE/*not fieldIgnoredValue*/, + managedObj.getFieldIgnored()); + assertEquals(fieldStringValue, managedObj.getFieldString()); + assertEquals(fieldRandomStringValue, managedObj.getFieldRandomString()); + assertEquals(fieldShortValue, managedObj.getFieldShort()); + assertEquals(fieldIntValue, managedObj.getFieldInt()); + assertEquals(fieldLongPrimaryKeyValue, managedObj.getFieldLongPrimaryKey()); + assertEquals(fieldLongValue, managedObj.getFieldLong()); + assertEquals(fieldByteValue, managedObj.getFieldByte()); + assertEquals(fieldFloatValue, managedObj.getFieldFloat(), 0f); + assertEquals(fieldDoubleValue, managedObj.getFieldDouble(), 0d); + assertEquals(fieldBooleanValue, managedObj.isFieldBoolean()); + assertEquals(fieldDateValue, managedObj.getFieldDate()); + assertTrue(Arrays.equals(fieldBinaryValue, managedObj.getFieldBinary())); + assertEquals(fieldObjectJson.getString(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY), + managedObj.getFieldObject().getFieldRandomPrimaryKey()); + assertEquals(fieldObjectIntValue, managedObj.getFieldObject().getFieldInt()); + assertEquals(2, managedObj.getFieldList().size()); + assertEquals(fieldListItem0Json.get(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY), + managedObj.getFieldList().get(0).getFieldRandomPrimaryKey()); + assertEquals(fieldListIntValue, managedObj.getFieldList().get(0).getFieldInt()); + assertEquals(fieldListItem1Json.get(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY), + managedObj.getFieldList().get(1).getFieldRandomPrimaryKey()); + assertEquals(fieldListIntValue + 1, managedObj.getFieldList().get(1).getFieldInt()); + + // make sure that excess object by default value is not created. + assertEquals(3, realm.where(RandomPrimaryKey.class).count()); + } + + @Test + public void updateFromJson_defaultValuesAreIgnored() throws JSONException { + final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1; + + // Step 1: Create an object with default values + final DefaultValueOfField original; + realm.beginTransaction(); { + original = realm.createObject(DefaultValueOfField.class, fieldLongPrimaryKeyValue); + } + realm.commitTransaction(); + + // Step 2: Prepare JSON + final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"; + final String fieldStringValue = DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified"; + final String fieldRandomStringValue = "non-random"; + final short fieldShortValue = (short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1); + final int fieldIntValue = DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1; + final long fieldLongValue = DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1; + final byte fieldByteValue = (byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1); + final float fieldFloatValue = DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1; + final double fieldDoubleValue = DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1; + final boolean fieldBooleanValue = !DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE; + final Date fieldDateValue = new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1); + final byte[] fieldBinaryValue = {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}; + final int fieldObjectIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1; + final int fieldListIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2; + + final JSONObject json = new JSONObject(); + json.put(DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, fieldLongPrimaryKeyValue); + json.put(DefaultValueOfField.FIELD_IGNORED, fieldIgnoredValue); + json.put(DefaultValueOfField.FIELD_STRING, fieldStringValue); + json.put(DefaultValueOfField.FIELD_RANDOM_STRING, fieldRandomStringValue); + json.put(DefaultValueOfField.FIELD_SHORT, fieldShortValue); + json.put(DefaultValueOfField.FIELD_INT, fieldIntValue); + json.put(DefaultValueOfField.FIELD_LONG, fieldLongValue); + json.put(DefaultValueOfField.FIELD_BYTE, fieldByteValue); + json.put(DefaultValueOfField.FIELD_FLOAT, fieldFloatValue); + json.put(DefaultValueOfField.FIELD_DOUBLE, fieldDoubleValue); + json.put(DefaultValueOfField.FIELD_BOOLEAN, fieldBooleanValue); + json.put(DefaultValueOfField.FIELD_DATE, ISO8601Utils.format(fieldDateValue, true)); + json.put(DefaultValueOfField.FIELD_BINARY, Base64.encodeToString(fieldBinaryValue, Base64.DEFAULT)); + // value for 'fieldObject' + final JSONObject fieldObjectJson = new JSONObject(); + fieldObjectJson.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY, + original.getFieldObject().getFieldRandomPrimaryKey()); + fieldObjectJson.put(RandomPrimaryKey.FIELD_INT, fieldObjectIntValue); + json.put(DefaultValueOfField.FIELD_OBJECT, fieldObjectJson); + // value for 'fieldList' + final JSONArray fieldListArrayJson = new JSONArray(); + final JSONObject fieldListItem0Json = new JSONObject(); // to be added + fieldListItem0Json.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY, "unique value"); + fieldListItem0Json.put(RandomPrimaryKey.FIELD_INT, fieldListIntValue); + fieldListArrayJson.put(fieldListItem0Json); + final JSONObject fieldListItem1Json = new JSONObject(); // to be updated + fieldListItem1Json.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY, + original.getFieldList().first().getFieldRandomPrimaryKey()); + fieldListItem1Json.put(RandomPrimaryKey.FIELD_INT, fieldListIntValue + 1); + fieldListArrayJson.put(fieldListItem1Json); + json.put(DefaultValueOfField.FIELD_LIST, fieldListArrayJson); + + // Step 3: Update with JSONObject + realm.beginTransaction(); + final DefaultValueOfField managedObj = realm.createOrUpdateObjectFromJson(DefaultValueOfField.class, json); + realm.commitTransaction(); + + // Step 4: Check that properly updated + assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE/*not fieldIgnoredValue*/, + managedObj.getFieldIgnored()); + assertEquals(fieldStringValue, managedObj.getFieldString()); + assertEquals(fieldRandomStringValue, managedObj.getFieldRandomString()); + assertEquals(fieldShortValue, managedObj.getFieldShort()); + assertEquals(fieldIntValue, managedObj.getFieldInt()); + assertEquals(fieldLongPrimaryKeyValue, managedObj.getFieldLongPrimaryKey()); + assertEquals(fieldLongValue, managedObj.getFieldLong()); + assertEquals(fieldByteValue, managedObj.getFieldByte()); + assertEquals(fieldFloatValue, managedObj.getFieldFloat(), 0f); + assertEquals(fieldDoubleValue, managedObj.getFieldDouble(), 0d); + assertEquals(fieldBooleanValue, managedObj.isFieldBoolean()); + assertEquals(fieldDateValue, managedObj.getFieldDate()); + assertTrue(Arrays.equals(fieldBinaryValue, managedObj.getFieldBinary())); + assertEquals(fieldObjectIntValue, managedObj.getFieldObject().getFieldInt()); + assertEquals(2, managedObj.getFieldList().size()); + assertEquals("unique value", managedObj.getFieldList().get(0).getFieldRandomPrimaryKey()); + assertEquals(fieldListIntValue, managedObj.getFieldList().get(0).getFieldInt()); + assertEquals(fieldListItem1Json.get(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY), + managedObj.getFieldList().get(1).getFieldRandomPrimaryKey()); + assertEquals(fieldListIntValue + 1, managedObj.getFieldList().get(1).getFieldInt()); + + // make sure that excess object by default value is not created. + assertEquals(3/* 2 updated + 1 added*/, realm.where(RandomPrimaryKey.class).count()); + } + // Test if Json object doesn't have the field, then the field should have default value. @Test public void createObjectFromJson_noValues() throws JSONException { @@ -396,6 +615,7 @@ public void createObjectFromJson_jsonException() throws JSONException { realm.beginTransaction(); try { realm.createObjectFromJson(AllTypes.class, json); + fail(); } catch (RealmException ignored) { } finally { realm.commitTransaction(); @@ -1141,7 +1361,7 @@ public void createObjectFromJson_updateNullTypesJSONWithNulls() throws IOExcepti RealmResults nullTypesRealmResults = realm.where(NullTypes.class).findAll(); assertEquals(2, nullTypesRealmResults.size()); - checkNullableValuesAreNotNull(nullTypesRealmResults.first()); + checkNullableValuesAreNotNull(nullTypesRealmResults.where().equalTo("id", 1).findFirst()); // Update object with id 1, nullable fields should have null values JSONArray array = new JSONArray(json); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index cf2ea4fd8b..cbce2dfd79 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -27,6 +27,7 @@ import org.junit.runner.RunWith; import org.mockito.Mockito; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; @@ -37,9 +38,11 @@ import io.realm.entities.AllTypes; import io.realm.entities.AnnotationIndexTypes; import io.realm.entities.CyclicType; +import io.realm.entities.DefaultValueOfField; import io.realm.entities.Dog; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.Owner; +import io.realm.entities.RandomPrimaryKey; import io.realm.entities.StringOnly; import io.realm.internal.Table; import io.realm.rule.RunInLooperThread; @@ -1037,4 +1040,79 @@ public void deleteAndDeleteAll() { assertEquals(0, realm.where(StringOnly.class).findAll().size()); } + + @Test + public void syncQuery_defaultValuesAreIgnored() { + final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"; + final String fieldStringValue = DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified"; + final String fieldRandomStringValue = "non-random"; + final short fieldShortValue = (short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1); + final int fieldIntValue = DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1; + final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1; + final long fieldLongValue = DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1; + final byte fieldByteValue = (byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1); + final float fieldFloatValue = DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1; + final double fieldDoubleValue = DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1; + final boolean fieldBooleanValue = !DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE; + final Date fieldDateValue = new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1); + final byte[] fieldBinaryValue = {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}; + final int fieldObjectIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1; + final int fieldListIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2; + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final DefaultValueOfField obj = new DefaultValueOfField(); + obj.setFieldIgnored(fieldIgnoredValue); + obj.setFieldString(fieldStringValue); + obj.setFieldRandomString(fieldRandomStringValue); + obj.setFieldShort(fieldShortValue); + obj.setFieldInt(fieldIntValue); + obj.setFieldLongPrimaryKey(fieldLongPrimaryKeyValue); + obj.setFieldLong(fieldLongValue); + obj.setFieldByte(fieldByteValue); + obj.setFieldFloat(fieldFloatValue); + obj.setFieldDouble(fieldDoubleValue); + obj.setFieldBoolean(fieldBooleanValue); + obj.setFieldDate(fieldDateValue); + obj.setFieldBinary(fieldBinaryValue); + + final RandomPrimaryKey fieldObjectValue = new RandomPrimaryKey(); + fieldObjectValue.setFieldInt(fieldObjectIntValue); + obj.setFieldObject(fieldObjectValue); + + final RealmList list = new RealmList<>(); + final RandomPrimaryKey listItem = new RandomPrimaryKey(); + listItem.setFieldInt(fieldListIntValue); + list.add(listItem); + obj.setFieldList(list); + + realm.copyToRealm(obj); + } + }); + + final RealmResults result = realm.where(DefaultValueOfField.class) + .equalTo(DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, + fieldLongPrimaryKeyValue).findAll(); + + final DefaultValueOfField obj = result.first(); + + assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE/*not fieldIgnoredValue*/, + obj.getFieldIgnored()); + assertEquals(fieldStringValue, obj.getFieldString()); + assertEquals(fieldRandomStringValue, obj.getFieldRandomString()); + assertEquals(fieldShortValue, obj.getFieldShort()); + assertEquals(fieldIntValue, obj.getFieldInt()); + assertEquals(fieldLongPrimaryKeyValue, obj.getFieldLongPrimaryKey()); + assertEquals(fieldLongValue, obj.getFieldLong()); + assertEquals(fieldByteValue, obj.getFieldByte()); + assertEquals(fieldFloatValue, obj.getFieldFloat(), 0f); + assertEquals(fieldDoubleValue, obj.getFieldDouble(), 0d); + assertEquals(fieldBooleanValue, obj.isFieldBoolean()); + assertEquals(fieldDateValue, obj.getFieldDate()); + assertTrue(Arrays.equals(fieldBinaryValue, obj.getFieldBinary())); + assertEquals(fieldObjectIntValue, obj.getFieldObject().getFieldInt()); + assertEquals(1, obj.getFieldList().size()); + assertEquals(fieldListIntValue, obj.getFieldList().first().getFieldInt()); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index e4de786a32..9f44948320 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -63,6 +63,9 @@ import io.realm.entities.Cat; import io.realm.entities.CyclicType; import io.realm.entities.CyclicTypePrimaryKey; +import io.realm.entities.DefaultValueConstructor; +import io.realm.entities.DefaultValueOfField; +import io.realm.entities.DefaultValueSetter; import io.realm.entities.Dog; import io.realm.entities.DogPrimaryKey; import io.realm.entities.NoPrimaryKeyNullTypes; @@ -82,6 +85,7 @@ import io.realm.entities.PrimaryKeyRequiredAsBoxedLong; import io.realm.entities.PrimaryKeyRequiredAsBoxedShort; import io.realm.entities.PrimaryKeyRequiredAsString; +import io.realm.entities.RandomPrimaryKey; import io.realm.entities.StringOnly; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; @@ -96,6 +100,8 @@ import io.realm.util.ExceptionHolder; import io.realm.util.RealmThread; +import static io.realm.TestHelper.testNoObjectFound; +import static io.realm.TestHelper.testOneObjectFound; import static io.realm.internal.test.ExtraTests.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -2266,6 +2272,310 @@ public void createObjectWithPrimaryKey_nullDuplicated() { realm.cancelTransaction(); } + @Test + public void createObject_defaultValueFromModelField() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + // create a DefaultValueOfField with non-default primary key value + realm.createObject(DefaultValueOfField.class, + DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); + } + }); + final String createdRandomString = DefaultValueOfField.lastRandomStringValue; + + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_STRING, + DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_RANDOM_STRING, createdRandomString); + testOneObjectFound(realm, DefaultValueOfField.class,DefaultValueOfField.FIELD_SHORT, + DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_INT, + DefaultValueOfField.FIELD_INT_DEFAULT_VALUE); + // default value for pk must be ignored + testNoObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, + DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, + DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_LONG, + DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_BYTE, + DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_FLOAT, + DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_DOUBLE, + DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_BOOLEAN, + DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_DATE, + DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_BINARY, + DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_OBJECT + "." + RandomPrimaryKey.FIELD_INT, + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueOfField.class, + DefaultValueOfField.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + } + + @Test + public void createObject_defaultValueFromModelConstructor() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + // create a DefaultValueConstructor with non-default primary key value + realm.createObject(DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); + } + }); + final String createdRandomString = DefaultValueConstructor.lastRandomStringValue; + + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_STRING, + DefaultValueConstructor.FIELD_STRING_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_RANDOM_STRING, + createdRandomString); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_SHORT, + DefaultValueConstructor.FIELD_SHORT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_INT, + DefaultValueConstructor.FIELD_INT_DEFAULT_VALUE);; + // default value for pk must be ignored + testNoObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, + DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, + DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_LONG, + DefaultValueConstructor.FIELD_LONG_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_BYTE, + DefaultValueConstructor.FIELD_BYTE_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_FLOAT, + DefaultValueConstructor.FIELD_FLOAT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_DOUBLE, + DefaultValueConstructor.FIELD_DOUBLE_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_BOOLEAN, + DefaultValueConstructor.FIELD_BOOLEAN_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_DATE, DefaultValueConstructor.FIELD_DATE_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_BINARY, + DefaultValueConstructor.FIELD_BINARY_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_OBJECT + "." + RandomPrimaryKey.FIELD_INT, + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueConstructor.class, + DefaultValueConstructor.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + } + + @Test + public void createObject_defaultValueSetterInConstructor() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + // create a DefaultValueSetter with non-default primary key value + realm.createObject(DefaultValueSetter.class, + DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); + } + }); + final String createdRandomString = DefaultValueSetter.lastRandomStringValue; + + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_STRING, + DefaultValueSetter.FIELD_STRING_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_RANDOM_STRING, + createdRandomString); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_SHORT, + DefaultValueSetter.FIELD_SHORT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_INT, + DefaultValueSetter.FIELD_INT_DEFAULT_VALUE); + // default value for pk must be ignored + testNoObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, + DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, + DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_LONG, + DefaultValueSetter.FIELD_LONG_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_BYTE, + DefaultValueSetter.FIELD_BYTE_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_FLOAT, + DefaultValueSetter.FIELD_FLOAT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_DOUBLE, + DefaultValueSetter.FIELD_DOUBLE_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_BOOLEAN, + DefaultValueSetter.FIELD_BOOLEAN_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_DATE, + DefaultValueSetter.FIELD_DATE_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_BINARY, + DefaultValueSetter.FIELD_BINARY_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_OBJECT + "." + RandomPrimaryKey.FIELD_INT, + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + testOneObjectFound(realm, DefaultValueSetter.class, + DefaultValueSetter.FIELD_LIST+ "." + RandomPrimaryKey.FIELD_INT, + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); + } + + @Test + public void copyToRealm_defaultValuesAreIgnored() { + final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"; + final String fieldStringValue = DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified"; + final String fieldRandomStringValue = "non-random"; + final short fieldShortValue = (short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1); + final int fieldIntValue = DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1; + final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1; + final long fieldLongValue = DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1; + final byte fieldByteValue = (byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1); + final float fieldFloatValue = DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1; + final double fieldDoubleValue = DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1; + final boolean fieldBooleanValue = !DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE; + final Date fieldDateValue = new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1); + final byte[] fieldBinaryValue = {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}; + final int fieldObjectIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1; + final int fieldListIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2; + + final DefaultValueOfField managedObj; + realm.beginTransaction(); { + final DefaultValueOfField obj = new DefaultValueOfField(); + obj.setFieldIgnored(fieldIgnoredValue); + obj.setFieldString(fieldStringValue); + obj.setFieldRandomString(fieldRandomStringValue); + obj.setFieldShort(fieldShortValue); + obj.setFieldInt(fieldIntValue); + obj.setFieldLongPrimaryKey(fieldLongPrimaryKeyValue); + obj.setFieldLong(fieldLongValue); + obj.setFieldByte(fieldByteValue); + obj.setFieldFloat(fieldFloatValue); + obj.setFieldDouble(fieldDoubleValue); + obj.setFieldBoolean(fieldBooleanValue); + obj.setFieldDate(fieldDateValue); + obj.setFieldBinary(fieldBinaryValue); + + final RandomPrimaryKey fieldObjectValue = new RandomPrimaryKey(); + fieldObjectValue.setFieldInt(fieldObjectIntValue); + obj.setFieldObject(fieldObjectValue); + + final RealmList list = new RealmList<>(); + final RandomPrimaryKey listItem = new RandomPrimaryKey(); + listItem.setFieldInt(fieldListIntValue); + list.add(listItem); + obj.setFieldList(list); + + managedObj = realm.copyToRealm(obj); + } + realm.commitTransaction(); + + assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE/*not fieldIgnoredValue*/, + managedObj.getFieldIgnored()); + assertEquals(fieldStringValue, managedObj.getFieldString()); + assertEquals(fieldRandomStringValue, managedObj.getFieldRandomString()); + assertEquals(fieldShortValue, managedObj.getFieldShort()); + assertEquals(fieldIntValue, managedObj.getFieldInt()); + assertEquals(fieldLongPrimaryKeyValue, managedObj.getFieldLongPrimaryKey()); + assertEquals(fieldLongValue, managedObj.getFieldLong()); + assertEquals(fieldByteValue, managedObj.getFieldByte()); + assertEquals(fieldFloatValue, managedObj.getFieldFloat(), 0f); + assertEquals(fieldDoubleValue, managedObj.getFieldDouble(), 0d); + assertEquals(fieldBooleanValue, managedObj.isFieldBoolean()); + assertEquals(fieldDateValue, managedObj.getFieldDate()); + assertTrue(Arrays.equals(fieldBinaryValue, managedObj.getFieldBinary())); + assertEquals(fieldObjectIntValue, managedObj.getFieldObject().getFieldInt()); + assertEquals(1, managedObj.getFieldList().size()); + assertEquals(fieldListIntValue, managedObj.getFieldList().first().getFieldInt()); + + // make sure that excess object by default value is not created. + assertEquals(2, realm.where(RandomPrimaryKey.class).count()); + } + + @Test + public void copyFromRealm_defaultValuesAreIgnored() { + final DefaultValueOfField managedObj; + realm.beginTransaction(); { + final DefaultValueOfField obj = new DefaultValueOfField(); + obj.setFieldIgnored(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"); + obj.setFieldString(DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified"); + obj.setFieldRandomString("non-random"); + obj.setFieldShort((short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1)); + obj.setFieldInt(DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1); + obj.setFieldLongPrimaryKey(DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1); + obj.setFieldLong(DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1); + obj.setFieldByte((byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1)); + obj.setFieldFloat(DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1); + obj.setFieldDouble(DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1); + obj.setFieldBoolean(!DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE); + obj.setFieldDate(new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1)); + obj.setFieldBinary(new byte[] {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}); + + final RandomPrimaryKey fieldObjectValue = new RandomPrimaryKey(); + fieldObjectValue.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); + obj.setFieldObject(fieldObjectValue); + + final RealmList list = new RealmList<>(); + final RandomPrimaryKey listItem = new RandomPrimaryKey(); + listItem.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2); + list.add(listItem); + obj.setFieldList(list); + + managedObj = realm.copyToRealm(obj); + } + realm.commitTransaction(); + + final DefaultValueOfField copy = realm.copyFromRealm(managedObj); + + assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE, copy.getFieldIgnored()); + assertEquals(managedObj.getFieldString(), copy.getFieldString()); + assertEquals(managedObj.getFieldRandomString(), copy.getFieldRandomString()); + assertEquals(managedObj.getFieldShort(), copy.getFieldShort()); + assertEquals(managedObj.getFieldInt(), copy.getFieldInt()); + assertEquals(managedObj.getFieldLongPrimaryKey(), copy.getFieldLongPrimaryKey()); + assertEquals(managedObj.getFieldLong(), copy.getFieldLong()); + assertEquals(managedObj.getFieldByte(), copy.getFieldByte()); + assertEquals(managedObj.getFieldFloat(), copy.getFieldFloat(), 0f); + assertEquals(managedObj.getFieldDouble(), copy.getFieldDouble(), 0d); + assertEquals(managedObj.isFieldBoolean(), copy.isFieldBoolean()); + assertEquals(managedObj.getFieldDate(), copy.getFieldDate()); + assertTrue(Arrays.equals(managedObj.getFieldBinary(), copy.getFieldBinary())); + assertEquals(managedObj.getFieldObject().getFieldInt(), copy.getFieldObject().getFieldInt()); + assertEquals(1, copy.getFieldList().size()); + assertEquals(managedObj.getFieldList().first().getFieldInt(), copy.getFieldList().first().getFieldInt()); + } + // Test close Realm in another thread different from where it is created. @Test public void close_differentThread() throws InterruptedException { diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index e171990d9c..cade67abf7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -937,6 +937,64 @@ public static RealmResults newRealmResults( } } + public static void testNoObjectFound( + Realm realm, + Class clazz, + String fieldName, Object value) { + testObjectCount(realm, 0L, clazz, fieldName, value); + } + + public static void testOneObjectFound( + Realm realm, + Class clazz, + String fieldName, Object value) { + testObjectCount(realm, 1L, clazz, fieldName, value); + } + + public static void testObjectCount( + Realm realm, + long expectedCount, + Class clazz, + String fieldName, Object value) { + final RealmQuery query; + switch (value.getClass().getSimpleName()) { + case "String": + query = realm.where(clazz).equalTo(fieldName, (String) value); + break; + case "Byte": + query = realm.where(clazz).equalTo(fieldName, (Byte) value); + break; + case "Short": + query = realm.where(clazz).equalTo(fieldName, (Short) value); + break; + case "Integer": + query = realm.where(clazz).equalTo(fieldName, (Integer) value); + break; + case "Long": + query = realm.where(clazz).equalTo(fieldName, (Long) value); + break; + case "Float": + query = realm.where(clazz).equalTo(fieldName, (Float) value); + break; + case "Double": + query = realm.where(clazz).equalTo(fieldName, (Double) value); + break; + case "Boolean": + query = realm.where(clazz).equalTo(fieldName, (Boolean) value); + break; + case "Date": + query = realm.where(clazz).equalTo(fieldName, (Date) value); + break; + case "byte[]": + query = realm.where(clazz).equalTo(fieldName, (byte[]) value); + break; + default: + throw new AssertionError("unknown type: " + value.getClass().getSimpleName()); + } + + assertEquals(expectedCount, query.count()); + } + /** * Replaces the current thread executor with a another one for testing. * WARNING: This method should only be called before any async tasks have been started. diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueConstructor.java b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueConstructor.java new file mode 100644 index 0000000000..4aed4562b7 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueConstructor.java @@ -0,0 +1,227 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import java.util.Date; +import java.util.UUID; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.Ignore; +import io.realm.annotations.PrimaryKey; + +public class DefaultValueConstructor extends RealmObject { + + public static final String CLASS_NAME = "DefaultValueOfField"; + public static String FIELD_IGNORED = "fieldIgnored"; + public static String FIELD_RANDOM_STRING = "fieldRandomString"; + public static String FIELD_STRING = "fieldString"; + public static String FIELD_SHORT = "fieldShort"; + public static String FIELD_INT = "fieldInt"; + public static String FIELD_LONG_PRIMARY_KEY = "fieldLongPrimaryKey"; + public static String FIELD_LONG = "fieldLong"; + public static String FIELD_BYTE = "fieldByte"; + public static String FIELD_FLOAT = "fieldFloat"; + public static String FIELD_DOUBLE = "fieldDouble"; + public static String FIELD_BOOLEAN = "fieldBoolean"; + public static String FIELD_DATE = "fieldDate"; + public static String FIELD_BINARY = "fieldBinary"; + public static String FIELD_OBJECT = "fieldObject"; + public static String FIELD_LIST = "fieldList"; + + + public static String FIELD_IGNORED_DEFAULT_VALUE = "ignored"; + public static String FIELD_STRING_DEFAULT_VALUE = "defaultString"; + public static short FIELD_SHORT_DEFAULT_VALUE = 1234; + public static int FIELD_INT_DEFAULT_VALUE = 123456; + public static long FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE = 2L * Integer.MAX_VALUE; + public static long FIELD_LONG_DEFAULT_VALUE = 3L * Integer.MAX_VALUE; + public static byte FIELD_BYTE_DEFAULT_VALUE = 100; + public static float FIELD_FLOAT_DEFAULT_VALUE = 0.5f; + public static double FIELD_DOUBLE_DEFAULT_VALUE = 0.25; + public static boolean FIELD_BOOLEAN_DEFAULT_VALUE = true; + public static Date FIELD_DATE_DEFAULT_VALUE = new Date(1473691826000L /*2016/9/12 23:56:26 JST*/); + public static byte[] FIELD_BINARY_DEFAULT_VALUE = new byte[] {123, -100, 0, 2}; + public static RandomPrimaryKey FIELD_OBJECT_DEFAULT_VALUE; + public static RealmList FIELD_LIST_DEFAULT_VALUE; + + static { + FIELD_OBJECT_DEFAULT_VALUE = new RandomPrimaryKey(); + FIELD_LIST_DEFAULT_VALUE = new RealmList(); + FIELD_LIST_DEFAULT_VALUE.add(new RandomPrimaryKey()); + } + + public static String lastRandomStringValue; + + @Ignore private String fieldIgnored; + private String fieldString; + private String fieldRandomString; + private short fieldShort; + private int fieldInt; + @PrimaryKey private long fieldLongPrimaryKey; + private long fieldLong; + private byte fieldByte; + private float fieldFloat; + private double fieldDouble; + private boolean fieldBoolean; + private Date fieldDate; + private byte[] fieldBinary; + private RandomPrimaryKey fieldObject; + private RealmList fieldList; + + public DefaultValueConstructor() { + fieldIgnored = FIELD_IGNORED_DEFAULT_VALUE; + fieldString = FIELD_STRING_DEFAULT_VALUE; + fieldRandomString = lastRandomStringValue = UUID.randomUUID().toString(); + fieldShort = FIELD_SHORT_DEFAULT_VALUE; + fieldInt = FIELD_INT_DEFAULT_VALUE; + fieldLongPrimaryKey = FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE; + fieldLong = FIELD_LONG_DEFAULT_VALUE; + fieldByte = FIELD_BYTE_DEFAULT_VALUE; + fieldFloat = FIELD_FLOAT_DEFAULT_VALUE; + fieldDouble = FIELD_DOUBLE_DEFAULT_VALUE; + fieldBoolean = FIELD_BOOLEAN_DEFAULT_VALUE; + fieldDate = FIELD_DATE_DEFAULT_VALUE; + fieldBinary = FIELD_BINARY_DEFAULT_VALUE; + fieldObject = FIELD_OBJECT_DEFAULT_VALUE; + fieldList = FIELD_LIST_DEFAULT_VALUE; + } + + public DefaultValueConstructor(long fieldLong) { + this.fieldLong = fieldLong; + } + + public String getFieldIgnored() { + return fieldIgnored; + } + + public void setFieldIgnored(String fieldIgnored) { + this.fieldIgnored = fieldIgnored; + } + + public String getFieldString() { + return fieldString; + } + + public void setFieldString(String fieldString) { + this.fieldString = fieldString; + } + + public String getFieldRandomString() { + return fieldRandomString; + } + + public void setFieldRandomString(String fieldRandomString) { + this.fieldRandomString = fieldRandomString; + } + + public short getFieldShort() { + return fieldShort; + } + + public void setFieldShort(short fieldShort) { + this.fieldShort = fieldShort; + } + + public int getFieldInt() { + return fieldInt; + } + + public void setFieldInt(int fieldInt) { + this.fieldInt = fieldInt; + } + + public long getFieldLongPrimaryKey() { + return fieldLongPrimaryKey; + } + + public void setFieldLongPrimaryKey(long fieldLongPrimaryKey) { + this.fieldLongPrimaryKey = fieldLongPrimaryKey; + } + + public long getFieldLong() { + return fieldLong; + } + + public void setFieldLong(long fieldLong) { + this.fieldLong = fieldLong; + } + + public byte getFieldByte() { + return fieldByte; + } + + public void setFieldByte(byte fieldByte) { + this.fieldByte = fieldByte; + } + + public float getFieldFloat() { + return fieldFloat; + } + + public void setFieldFloat(float fieldFloat) { + this.fieldFloat = fieldFloat; + } + + public double getFieldDouble() { + return fieldDouble; + } + + public void setFieldDouble(double fieldDouble) { + this.fieldDouble = fieldDouble; + } + + public boolean isFieldBoolean() { + return fieldBoolean; + } + + public void setFieldBoolean(boolean fieldBoolean) { + this.fieldBoolean = fieldBoolean; + } + + public Date getFieldDate() { + return fieldDate; + } + + public void setFieldDate(Date fieldDate) { + this.fieldDate = fieldDate; + } + + public byte[] getFieldBinary() { + return fieldBinary; + } + + public void setFieldBinary(byte[] fieldBinary) { + this.fieldBinary = fieldBinary; + } + + public RandomPrimaryKey getFieldObject() { + return fieldObject; + } + + public void setFieldObject(RandomPrimaryKey fieldObject) { + this.fieldObject = fieldObject; + } + + public RealmList getFieldList() { + return fieldList; + } + + public void setFieldList(RealmList fieldList) { + this.fieldList = fieldList; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOfField.java b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOfField.java new file mode 100644 index 0000000000..0378e64817 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOfField.java @@ -0,0 +1,213 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import java.util.Date; +import java.util.UUID; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.Ignore; +import io.realm.annotations.PrimaryKey; + +public class DefaultValueOfField extends RealmObject { + + public static final String CLASS_NAME = "DefaultValueOfField"; + public static String FIELD_IGNORED = "fieldIgnored"; + public static String FIELD_RANDOM_STRING = "fieldRandomString"; + public static String FIELD_STRING = "fieldString"; + public static String FIELD_SHORT = "fieldShort"; + public static String FIELD_INT = "fieldInt"; + public static String FIELD_LONG_PRIMARY_KEY = "fieldLongPrimaryKey"; + public static String FIELD_LONG = "fieldLong"; + public static String FIELD_BYTE = "fieldByte"; + public static String FIELD_FLOAT = "fieldFloat"; + public static String FIELD_DOUBLE = "fieldDouble"; + public static String FIELD_BOOLEAN = "fieldBoolean"; + public static String FIELD_DATE = "fieldDate"; + public static String FIELD_BINARY = "fieldBinary"; + public static String FIELD_OBJECT = "fieldObject"; + public static String FIELD_LIST = "fieldList"; + + + public static String FIELD_IGNORED_DEFAULT_VALUE = "ignored"; + public static String FIELD_STRING_DEFAULT_VALUE = "defaultString"; + public static short FIELD_SHORT_DEFAULT_VALUE = 1234; + public static int FIELD_INT_DEFAULT_VALUE = 123456; + public static long FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE = 2L * Integer.MAX_VALUE; + public static long FIELD_LONG_DEFAULT_VALUE = 3L * Integer.MAX_VALUE; + public static byte FIELD_BYTE_DEFAULT_VALUE = 100; + public static float FIELD_FLOAT_DEFAULT_VALUE = 0.5f; + public static double FIELD_DOUBLE_DEFAULT_VALUE = 0.25; + public static boolean FIELD_BOOLEAN_DEFAULT_VALUE = true; + public static Date FIELD_DATE_DEFAULT_VALUE = new Date(1473691826000L /*2016/9/12 23:56:26 JST*/); + public static byte[] FIELD_BINARY_DEFAULT_VALUE = new byte[] {123, -100, 0, 2}; + public static RandomPrimaryKey FIELD_OBJECT_DEFAULT_VALUE; + public static RealmList FIELD_LIST_DEFAULT_VALUE; + + static { + FIELD_OBJECT_DEFAULT_VALUE = new RandomPrimaryKey(); + FIELD_LIST_DEFAULT_VALUE = new RealmList(); + FIELD_LIST_DEFAULT_VALUE.add(new RandomPrimaryKey()); + } + + public static String lastRandomStringValue; + + @Ignore private String fieldIgnored = FIELD_IGNORED_DEFAULT_VALUE; + private String fieldString = FIELD_STRING_DEFAULT_VALUE; + private String fieldRandomString = lastRandomStringValue = UUID.randomUUID().toString(); + private short fieldShort = FIELD_SHORT_DEFAULT_VALUE; + private int fieldInt = FIELD_INT_DEFAULT_VALUE; + @PrimaryKey private long fieldLongPrimaryKey = FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE; + private long fieldLong = FIELD_LONG_DEFAULT_VALUE; + private byte fieldByte = FIELD_BYTE_DEFAULT_VALUE; + private float fieldFloat = FIELD_FLOAT_DEFAULT_VALUE; + private double fieldDouble = FIELD_DOUBLE_DEFAULT_VALUE; + private boolean fieldBoolean = FIELD_BOOLEAN_DEFAULT_VALUE; + private Date fieldDate = FIELD_DATE_DEFAULT_VALUE; + private byte[] fieldBinary = FIELD_BINARY_DEFAULT_VALUE; + private RandomPrimaryKey fieldObject = FIELD_OBJECT_DEFAULT_VALUE; + private RealmList fieldList = FIELD_LIST_DEFAULT_VALUE; + + public DefaultValueOfField() { + + } + + public DefaultValueOfField(long fieldLong) { + this.fieldLong = fieldLong; + } + + public String getFieldIgnored() { + return fieldIgnored; + } + + public void setFieldIgnored(String fieldIgnored) { + this.fieldIgnored = fieldIgnored; + } + + public String getFieldString() { + return fieldString; + } + + public void setFieldString(String fieldString) { + this.fieldString = fieldString; + } + + public String getFieldRandomString() { + return fieldRandomString; + } + + public void setFieldRandomString(String fieldRandomString) { + this.fieldRandomString = fieldRandomString; + } + + public short getFieldShort() { + return fieldShort; + } + + public void setFieldShort(short fieldShort) { + this.fieldShort = fieldShort; + } + + public int getFieldInt() { + return fieldInt; + } + + public void setFieldInt(int fieldInt) { + this.fieldInt = fieldInt; + } + + public long getFieldLongPrimaryKey() { + return fieldLongPrimaryKey; + } + + public void setFieldLongPrimaryKey(long fieldLongPrimaryKey) { + this.fieldLongPrimaryKey = fieldLongPrimaryKey; + } + + public long getFieldLong() { + return fieldLong; + } + + public void setFieldLong(long fieldLong) { + this.fieldLong = fieldLong; + } + + public byte getFieldByte() { + return fieldByte; + } + + public void setFieldByte(byte fieldByte) { + this.fieldByte = fieldByte; + } + + public float getFieldFloat() { + return fieldFloat; + } + + public void setFieldFloat(float fieldFloat) { + this.fieldFloat = fieldFloat; + } + + public double getFieldDouble() { + return fieldDouble; + } + + public void setFieldDouble(double fieldDouble) { + this.fieldDouble = fieldDouble; + } + + public boolean isFieldBoolean() { + return fieldBoolean; + } + + public void setFieldBoolean(boolean fieldBoolean) { + this.fieldBoolean = fieldBoolean; + } + + public Date getFieldDate() { + return fieldDate; + } + + public void setFieldDate(Date fieldDate) { + this.fieldDate = fieldDate; + } + + public byte[] getFieldBinary() { + return fieldBinary; + } + + public void setFieldBinary(byte[] fieldBinary) { + this.fieldBinary = fieldBinary; + } + + public RandomPrimaryKey getFieldObject() { + return fieldObject; + } + + public void setFieldObject(RandomPrimaryKey fieldObject) { + this.fieldObject = fieldObject; + } + + public RealmList getFieldList() { + return fieldList; + } + + public void setFieldList(RealmList fieldList) { + this.fieldList = fieldList; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueSetter.java b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueSetter.java new file mode 100644 index 0000000000..e4583a9dc0 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueSetter.java @@ -0,0 +1,231 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import java.util.Date; +import java.util.UUID; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.Ignore; +import io.realm.annotations.PrimaryKey; + +public class DefaultValueSetter extends RealmObject { + + public static final String CLASS_NAME = "DefaultValueOfField"; + public static String FIELD_IGNORED = "fieldIgnored"; + public static String FIELD_RANDOM_STRING = "fieldRandomString"; + public static String FIELD_STRING = "fieldString"; + public static String FIELD_SHORT = "fieldShort"; + public static String FIELD_INT = "fieldInt"; + public static String FIELD_LONG_PRIMARY_KEY = "fieldLongPrimaryKey"; + public static String FIELD_LONG = "fieldLong"; + public static String FIELD_BYTE = "fieldByte"; + public static String FIELD_FLOAT = "fieldFloat"; + public static String FIELD_DOUBLE = "fieldDouble"; + public static String FIELD_BOOLEAN = "fieldBoolean"; + public static String FIELD_DATE = "fieldDate"; + public static String FIELD_BINARY = "fieldBinary"; + public static String FIELD_OBJECT = "fieldObject"; + public static String FIELD_LIST = "fieldList"; + + + public static String FIELD_IGNORED_DEFAULT_VALUE = "ignored"; + public static String FIELD_STRING_DEFAULT_VALUE = "defaultString"; + public static short FIELD_SHORT_DEFAULT_VALUE = 1234; + public static int FIELD_INT_DEFAULT_VALUE = 123456; + public static long FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE = 2L * Integer.MAX_VALUE; + public static long FIELD_LONG_DEFAULT_VALUE = 3L * Integer.MAX_VALUE; + public static byte FIELD_BYTE_DEFAULT_VALUE = 100; + public static float FIELD_FLOAT_DEFAULT_VALUE = 0.5f; + public static double FIELD_DOUBLE_DEFAULT_VALUE = 0.25; + public static boolean FIELD_BOOLEAN_DEFAULT_VALUE = true; + public static Date FIELD_DATE_DEFAULT_VALUE = new Date(1473691826000L /*2016/9/12 23:56:26 JST*/); + public static byte[] FIELD_BINARY_DEFAULT_VALUE = new byte[] {123, -100, 0, 2}; + public static RandomPrimaryKey FIELD_OBJECT_DEFAULT_VALUE; + public static RealmList FIELD_LIST_DEFAULT_VALUE; + + static { + FIELD_OBJECT_DEFAULT_VALUE = new RandomPrimaryKey(); + FIELD_LIST_DEFAULT_VALUE = new RealmList(); + FIELD_LIST_DEFAULT_VALUE.add(new RandomPrimaryKey()); + } + + public static String lastRandomStringValue; + + @Ignore private String fieldIgnored; + private String fieldString; + private String fieldRandomString; + private short fieldShort; + private int fieldInt; + @PrimaryKey private long fieldLongPrimaryKey; + private long fieldLong; + private byte fieldByte; + private float fieldFloat; + private double fieldDouble; + private boolean fieldBoolean; + private Date fieldDate; + private byte[] fieldBinary; + private RandomPrimaryKey fieldObject; + private RealmList fieldList; + + public DefaultValueSetter() { + setFieldIgnored(FIELD_IGNORED_DEFAULT_VALUE); + setFieldString(FIELD_STRING_DEFAULT_VALUE); + setFieldRandomString(lastRandomStringValue = UUID.randomUUID().toString()); + setFieldShort(FIELD_SHORT_DEFAULT_VALUE); + setFieldInt(FIELD_INT_DEFAULT_VALUE); + setFieldLongPrimaryKey(FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); + setFieldLong(FIELD_LONG_DEFAULT_VALUE); + setFieldByte(FIELD_BYTE_DEFAULT_VALUE); + setFieldFloat(FIELD_FLOAT_DEFAULT_VALUE); + setFieldDouble(FIELD_DOUBLE_DEFAULT_VALUE); + setFieldBoolean(FIELD_BOOLEAN_DEFAULT_VALUE); + setFieldDate(FIELD_DATE_DEFAULT_VALUE); + setFieldBinary(FIELD_BINARY_DEFAULT_VALUE); + setFieldObject(FIELD_OBJECT_DEFAULT_VALUE); + setFieldList(FIELD_LIST_DEFAULT_VALUE); + + final RandomPrimaryKey listItem2 = new RandomPrimaryKey(); + listItem2.setFieldInt(listItem2.getFieldInt() + 1); + getFieldList().add(listItem2); + } + + public DefaultValueSetter(long fieldLong) { + this.fieldLong = fieldLong; + } + + public String getFieldIgnored() { + return fieldIgnored; + } + + public void setFieldIgnored(String fieldIgnored) { + this.fieldIgnored = fieldIgnored; + } + + public String getFieldString() { + return fieldString; + } + + public void setFieldString(String fieldString) { + this.fieldString = fieldString; + } + + public String getFieldRandomString() { + return fieldRandomString; + } + + public void setFieldRandomString(String fieldRandomString) { + this.fieldRandomString = fieldRandomString; + } + + public short getFieldShort() { + return fieldShort; + } + + public void setFieldShort(short fieldShort) { + this.fieldShort = fieldShort; + } + + public int getFieldInt() { + return fieldInt; + } + + public void setFieldInt(int fieldInt) { + this.fieldInt = fieldInt; + } + + public long getFieldLongPrimaryKey() { + return fieldLongPrimaryKey; + } + + public void setFieldLongPrimaryKey(long fieldLongPrimaryKey) { + this.fieldLongPrimaryKey = fieldLongPrimaryKey; + } + + public long getFieldLong() { + return fieldLong; + } + + public void setFieldLong(long fieldLong) { + this.fieldLong = fieldLong; + } + + public byte getFieldByte() { + return fieldByte; + } + + public void setFieldByte(byte fieldByte) { + this.fieldByte = fieldByte; + } + + public float getFieldFloat() { + return fieldFloat; + } + + public void setFieldFloat(float fieldFloat) { + this.fieldFloat = fieldFloat; + } + + public double getFieldDouble() { + return fieldDouble; + } + + public void setFieldDouble(double fieldDouble) { + this.fieldDouble = fieldDouble; + } + + public boolean isFieldBoolean() { + return fieldBoolean; + } + + public void setFieldBoolean(boolean fieldBoolean) { + this.fieldBoolean = fieldBoolean; + } + + public Date getFieldDate() { + return fieldDate; + } + + public void setFieldDate(Date fieldDate) { + this.fieldDate = fieldDate; + } + + public byte[] getFieldBinary() { + return fieldBinary; + } + + public void setFieldBinary(byte[] fieldBinary) { + this.fieldBinary = fieldBinary; + } + + public RandomPrimaryKey getFieldObject() { + return fieldObject; + } + + public void setFieldObject(RandomPrimaryKey fieldObject) { + this.fieldObject = fieldObject; + } + + public RealmList getFieldList() { + return fieldList; + } + + public void setFieldList(RealmList fieldList) { + this.fieldList = fieldList; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyWithNoPrimaryKeyObjectRelation.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyWithNoPrimaryKeyObjectRelation.java index cb10b36f94..0206a2974c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyWithNoPrimaryKeyObjectRelation.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyWithNoPrimaryKeyObjectRelation.java @@ -20,12 +20,19 @@ import io.realm.annotations.PrimaryKey; public class PrimaryKeyWithNoPrimaryKeyObjectRelation extends RealmObject { + public static final String CLASS_NAME = "PrimaryKeyWithNoPrimaryKeyObjectRelation"; + public static final String FIELD_COLUMN_STRING = "columnString"; + public static final String FIELD_COLUMN_REALM_OBJECT_NO_PK = "columnRealmObjectNoPK"; + public static final String FIELD_COLUMN_INT = "columnInt"; + + public static final int FIELD_COLUMN_INT_DEFAULT_VALUE = 8; + @PrimaryKey private String columnString; private AllTypes columnRealmObjectNoPK; - private int columnInt = 8; + private int columnInt = FIELD_COLUMN_INT_DEFAULT_VALUE; public String getColumnString() { return columnString; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/RandomPrimaryKey.java b/realm/realm-library/src/androidTest/java/io/realm/entities/RandomPrimaryKey.java new file mode 100644 index 0000000000..d8d434e1ab --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/RandomPrimaryKey.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import java.util.UUID; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + +public class RandomPrimaryKey extends RealmObject { + + public static final String CLASS_NAME = "RandomPrimaryKey"; + public static String FIELD_RANDOM_PRIMARY_KEY = "fieldRandomPrimaryKey"; + public static String FIELD_INT = "fieldInt"; + + + public static int FIELD_INT_DEFAULT_VALUE = 1357924; + + @PrimaryKey private String fieldRandomPrimaryKey = UUID.randomUUID().toString(); + private int fieldInt = FIELD_INT_DEFAULT_VALUE; + + public RandomPrimaryKey() { + } + + public String getFieldRandomPrimaryKey() { + return fieldRandomPrimaryKey; + } + + public void setFieldRandomPrimaryKey(String fieldRandomPrimaryKey) { + this.fieldRandomPrimaryKey = fieldRandomPrimaryKey; + } + + public int getFieldInt() { + return fieldInt; + } + + public void setFieldInt(int fieldInt) { + this.fieldInt = fieldInt; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 861c482b0a..4e5e35272a 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -24,6 +24,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -32,6 +33,8 @@ import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; import io.realm.internal.SharedRealm; +import io.realm.internal.ColumnInfo; +import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.async.RealmThreadPoolExecutor; @@ -488,40 +491,37 @@ public RealmSchema getSchema() { return schema; } - E get(Class clazz, long rowIndex) { + E get(Class clazz, long rowIndex, boolean acceptDefaultValue, List excludeFields) { Table table = schema.getTable(clazz); UncheckedRow row = table.getUncheckedRow(rowIndex); - E result = configuration.getSchemaMediator().newInstance(clazz, schema.getColumnInfo(clazz)); + E result = configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz), + acceptDefaultValue, excludeFields); RealmObjectProxy proxy = (RealmObjectProxy) result; - proxy.realmGet$proxyState().setRow$realm(row); - proxy.realmGet$proxyState().setRealm$realm(this); proxy.realmGet$proxyState().setTableVersion$realm(); - return result; } // Used by RealmList/RealmResults // Invariant: if dynamicClassName != null -> clazz == DynamicRealmObject E get(Class clazz, String dynamicClassName, long rowIndex) { - Table table; + final Table table = (dynamicClassName != null) ? schema.getTable(dynamicClassName) : schema.getTable(clazz); + E result; if (dynamicClassName != null) { - table = schema.getTable(dynamicClassName); @SuppressWarnings("unchecked") - E dynamicObj = (E) new DynamicRealmObject(); + E dynamicObj = (E) new DynamicRealmObject(this, + (rowIndex != Table.NO_MATCH) ? table.getUncheckedRow(rowIndex) : InvalidRow.INSTANCE, + false); result = dynamicObj; } else { - table = schema.getTable(clazz); - result = configuration.getSchemaMediator().newInstance(clazz, schema.getColumnInfo(clazz)); + result = configuration.getSchemaMediator().newInstance(clazz, this, + (rowIndex != Table.NO_MATCH) ? table.getUncheckedRow(rowIndex) : InvalidRow.INSTANCE, + schema.getColumnInfo(clazz), false, Collections. emptyList()); } RealmObjectProxy proxy = (RealmObjectProxy) result; - proxy.realmGet$proxyState().setRealm$realm(this); if (rowIndex != Table.NO_MATCH) { - proxy.realmGet$proxyState().setRow$realm(table.getUncheckedRow(rowIndex)); proxy.realmGet$proxyState().setTableVersion$realm(); - } else { - proxy.realmGet$proxyState().setRow$realm(InvalidRow.INSTANCE); } return result; @@ -701,4 +701,56 @@ protected interface MigrationCallback { void migrationComplete(); } + public static final class RealmObjectContext { + private BaseRealm realm; + private Row row; + private ColumnInfo columnInfo; + private boolean acceptDefaultValue; + private List excludeFields; + + public void set(BaseRealm realm, Row row, ColumnInfo columnInfo, + boolean acceptDefaultValue, List excludeFields) { + this.realm = realm; + this.row = row; + this.columnInfo = columnInfo; + this.acceptDefaultValue = acceptDefaultValue; + this.excludeFields = excludeFields; + } + + public BaseRealm getRealm() { + return realm; + } + + public Row getRow() { + return row; + } + + public ColumnInfo getColumnInfo() { + return columnInfo; + } + + public boolean getAcceptDefaultValue() { + return acceptDefaultValue; + } + + public List getExcludeFields() { + return excludeFields; + } + + public void clear() { + realm = null; + row = null; + columnInfo = null; + acceptDefaultValue = false; + excludeFields = null; + } + } + static final class ThreadLocalRealmObjectContext extends ThreadLocal { + @Override + protected RealmObjectContext initialValue() { + return new RealmObjectContext(); + } + } + + public static final ThreadLocalRealmObjectContext objectContext = new ThreadLocalRealmObjectContext(); } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 8e3f35b77f..4af7ad524b 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -100,7 +100,7 @@ public DynamicRealmObject createObject(String className) { public DynamicRealmObject createObject(String className, Object primaryKeyValue) { Table table = schema.getTable(className); long index = table.addEmptyRowWithPrimaryKey(primaryKeyValue); - return new DynamicRealmObject(this, table.getCheckedRow(index)); + return new DynamicRealmObject(this, table.getCheckedRow(index), false); } /** diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index fb0c8a340a..88ec5cd14e 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -64,20 +64,28 @@ public DynamicRealmObject(RealmModel obj) { Row row = proxy.realmGet$proxyState().getRow$realm(); proxyState.setRealm$realm(proxy.realmGet$proxyState().getRealm$realm()); proxyState.setRow$realm(((UncheckedRow) row).convertToChecked()); + proxyState.setConstructionFinished(); } - // Create a dynamic object. Only used internally - DynamicRealmObject() { - - } - - DynamicRealmObject(BaseRealm realm, Row row) { + DynamicRealmObject(BaseRealm realm, Row row, boolean convertTocheckedRow) { proxyState.setRealm$realm(realm); - proxyState.setRow$realm((row instanceof CheckedRow) ? (CheckedRow) row : ((UncheckedRow) row).convertToChecked()); + if (convertTocheckedRow) { + proxyState.setRow$realm((row instanceof CheckedRow) ? (CheckedRow) row : ((UncheckedRow) row).convertToChecked()); + } else { + proxyState.setRow$realm(row); + } + proxyState.setConstructionFinished(); } - DynamicRealmObject(String className) { + DynamicRealmObject(String className, BaseRealm realm, Row row, boolean convertTocheckedRow) { proxyState.setClassName(className); + proxyState.setRealm$realm(realm); + if (convertTocheckedRow) { + proxyState.setRow$realm((row instanceof CheckedRow) ? (CheckedRow) row : ((UncheckedRow) row).convertToChecked()); + } else { + proxyState.setRow$realm(row); + } + proxyState.setConstructionFinished(); } /** @@ -279,7 +287,7 @@ public DynamicRealmObject getObject(String fieldName) { } else { long linkRowIndex = proxyState.getRow$realm().getLink(columnIndex); CheckedRow linkRow = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getCheckedRow(linkRowIndex); - return new DynamicRealmObject(proxyState.getRealm$realm(), linkRow); + return new DynamicRealmObject(proxyState.getRealm$realm(), linkRow, false); } } diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index f06b5f11b8..af3d807c7a 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -34,8 +34,13 @@ public final class ProxyState { private String className; private Class clazzName; + // true only while executing the constructor of the enclosing proxy object + private boolean underConstruction = true; + private Row row; private BaseRealm realm; + private boolean acceptDefaultValue; + private List excludeFields; private final List> listeners = new CopyOnWriteArrayList>(); private Future pendingQuery; @@ -86,6 +91,22 @@ public ProxyState(Class clazzName, E model) { this.row = row; } + public boolean getAcceptDefaultValue$realm() { + return acceptDefaultValue; + } + + public void setAcceptDefaultValue$realm(boolean acceptDefaultValue) { + this.acceptDefaultValue = acceptDefaultValue; + } + + public List getExcludeFields$realm() { + return excludeFields; + } + + public void setExcludeFields$realm(List excludeFields) { + this.excludeFields = excludeFields; + } + public Object getPendingQuery$realm() { return pendingQuery; } @@ -177,6 +198,16 @@ public void setClassName(String className) { this.className = className; } + public boolean isUnderConstruction() { + return underConstruction; + } + + public void setConstructionFinished() { + underConstruction = false; + // only used while construction. + excludeFields = null; + } + private Table getTable () { if (className != null) { return getRealm$realm().schema.getTable(className); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index bf52d176e6..eded91e118 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -34,6 +34,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; @@ -684,13 +685,34 @@ private Scanner getFullStringScanner(InputStream in) { /** * Instantiates and adds a new object to the Realm. + *

    + * This method is only available for model classes with no @PrimaryKey annotation. + * If you like to create an object that has a primary key, use {@link #createObject(Class, Object)} + * or {@link #copyToRealm(RealmModel)} instead. * * @param clazz the Class of the object to create. * @return the new object. - * @throws RealmException if an object cannot be created. + * @throws RealmException if the primary key is defined in the model class or an object cannot be created. + * @see #createObject(Class, Object) */ public E createObject(Class clazz) { checkIfValid(); + return createObjectInternal(clazz, true, Collections. emptyList()); + } + + /** + * Same as {@link #createObject(Class)} but this does not check the thread. + * + * @param clazz the Class of the object to create. + * @param acceptDefaultValue if {@code true}, default value of the object will be applied and + * if {@code false}, it will be ignored. + * @return the new object. + * @throws RealmException if the primary key is defined in the model class or an object cannot be created. + */ + // called from proxy classes + E createObjectInternal(Class clazz, + boolean acceptDefaultValue, + List excludeFields) { Table table = schema.getTable(clazz); // Check and throw the exception earlier for a better exception message. if (table.hasPrimaryKey()) { @@ -698,7 +720,7 @@ public E createObject(Class clazz) { " 'createObject(Class, Object)' instead.", Table.tableNameToClassName(table.getName()))); } long rowIndex = table.addEmptyRow(); - return get(clazz, rowIndex); + return get(clazz, rowIndex, acceptDefaultValue, excludeFields); } /** @@ -706,20 +728,40 @@ public E createObject(Class clazz) { *

    * If the value violates the primary key constraint, no object will be added and a {@link RealmException} will be * thrown. + * The default value for primary key provided by the model class will be ignored. * * @param clazz the Class of the object to create. * @param primaryKeyValue value for the primary key field. * @return the new object. * @throws RealmException if object could not be created due to the primary key being invalid. - * @throws IllegalStateException if the model clazz does not have an primary key defined. + * @throws IllegalStateException if the model class does not have an primary key defined. * @throws IllegalArgumentException if the {@code primaryKeyValue} doesn't have a value that can be converted to the * expected value. */ public E createObject(Class clazz, Object primaryKeyValue) { checkIfValid(); + return createObjectInternal(clazz, primaryKeyValue, true, Collections. emptyList()); + } + + /** + * Same as {@link #createObject(Class, Object)} but this does not check the thread. + * + * @param clazz the Class of the object to create. + * @param primaryKeyValue value for the primary key field. + * @param acceptDefaultValue if {@code true}, default value of the object will be applied and + * if {@code false}, it will be ignored. + * @return the new object. + * @throws RealmException if object could not be created due to the primary key being invalid. + * @throws IllegalStateException if the model class does not have an primary key defined. + * @throws IllegalArgumentException if the {@code primaryKeyValue} doesn't have a value that can be converted to the + */ + // called from proxy classes + E createObjectInternal(Class clazz, Object primaryKeyValue, + boolean acceptDefaultValue, + List excludeFields) { Table table = schema.getTable(clazz); long rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue); - return get(clazz, rowIndex); + return get(clazz, rowIndex, acceptDefaultValue, excludeFields); } /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 189963b558..cd41670a83 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -492,7 +492,7 @@ public RealmObjectSchema transform(Function function) { if (function != null) { long size = table.size(); for (long i = 0; i < size; i++) { - function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i))); + function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i), false)); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 0ecf8c1b88..d50cad7172 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -19,6 +19,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; @@ -2101,15 +2102,15 @@ public E findFirstAsync() { final E result; if (isDynamicQuery()) { //noinspection unchecked - result = (E) new DynamicRealmObject(className); + result = (E) new DynamicRealmObject(className, realm, Row.EMPTY_ROW, false); } else { - result = realm.getConfiguration().getSchemaMediator().newInstance(clazz, realm.getSchema().getColumnInfo(clazz)); + result = realm.getConfiguration().getSchemaMediator().newInstance( + clazz, realm, Row.EMPTY_ROW, realm.getSchema().getColumnInfo(clazz), + false, Collections.emptyList()); } - RealmObjectProxy proxy = (RealmObjectProxy) result; + final RealmObjectProxy proxy = (RealmObjectProxy) result; final WeakReference realmObjectWeakReference = realm.handlerController.addToAsyncRealmObject(proxy, this); - proxy.realmGet$proxyState().setRealm$realm(realm); - proxy.realmGet$proxyState().setRow$realm(Row.EMPTY_ROW); final Future pendingQuery = Realm.asyncTaskExecutor.submitQuery(new Callable() { @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index 84231559a3..b0ec6a9bfa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -85,10 +85,19 @@ public abstract ColumnInfo validateTable(Class clazz, * Creates a new instance of an {@link RealmObjectProxy} for the given RealmObject class. * * @param clazz the {@link RealmObject} to create {@link RealmObjectProxy} for. - * @param columnInfo the {@link ColumnInfo} object for the RealmObject class of {@code E}. + * @param acceptDefaultValue {@code true} to accept the values set in the constructor, {@code false} otherwise. + * @param excludeFields the column names whose default value will be ignored if the {@code acceptDefaultValue} + * is {@code true}. Only {@link io.realm.RealmModel} and {@link io.realm.RealmList} + * column will respect this. + * No effects if the {@code acceptDefaultValue} is {@code false}. * @return created {@link RealmObjectProxy} object. */ - public abstract E newInstance(Class clazz, ColumnInfo columnInfo); + public abstract E newInstance(Class clazz, + Object baseRealm, + Row row, + ColumnInfo columnInfo, + boolean acceptDefaultValue, + List excludeFields); /** * Returns the list of RealmObject classes that can be saved in this Realm. diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index 71f258cb21..4b94846c22 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -34,6 +34,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.Util; @@ -83,9 +84,14 @@ public String getTableName(Class clazz) { } @Override - public E newInstance(Class clazz, ColumnInfo columnInfo) { + public E newInstance(Class clazz, + Object baseRealm, + Row row, + ColumnInfo columnInfo, + boolean acceptDefaultValue, + List excludeFields) { RealmProxyMediator mediator = getMediator(clazz); - return mediator.newInstance(clazz, columnInfo); + return mediator.newInstance(clazz, baseRealm, row, columnInfo, acceptDefaultValue, excludeFields); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index 29d24d2f6c..af48ac87da 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -34,6 +34,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.Util; @@ -98,9 +99,14 @@ public String getTableName(Class clazz) { } @Override - public E newInstance(Class clazz, ColumnInfo columnInfo) { + public E newInstance(Class clazz, + Object baseRealm, + Row row, + ColumnInfo columnInfo, + boolean acceptDefaultValue, + List excludeFields) { checkSchemaHasClass(clazz); - return originalMediator.newInstance(clazz, columnInfo); + return originalMediator.newInstance(clazz, baseRealm, row, columnInfo, acceptDefaultValue, excludeFields); } @Override From 4397969a2702e3c9d8a2f261cd2008e003b5a620 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 16 Sep 2016 22:05:39 +0900 Subject: [PATCH 0048/2110] update CHANGELOG (#3445) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3acce90210..3f62ff6c1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ * Importing from JSON without the primary key field defined in the JSON object now throws `IllegalArgumentException`. * Now `Realm.beginTransaction()`, `Realm.executeTransaction()` and `Realm.waitForChange()` throw `RealmMigrationNeededException` if a remote process introduces incompatible schema changes (#3409). * The primary key value of an object can no longer be changed after the object was created. Instead a new object must be created and all fields copied over. -* Now `Realm.createObject(Class)` and `Realm.createObject(Class,Object)` take the values from the model's fields and default constructor. `DynamicRealm` does not take these default values (#777). +* Now `Realm.createObject(Class)` and `Realm.createObject(Class,Object)` take the values from the model's fields and default constructor. Creating objects through the `DynamicRealm` does not use these values (#777). * When `Realm.create*FromJson()`s create a new `RealmObject`, now they take the default values defined by the field itself and its default constructor for those fields that are not defined in the JSON object. ### Enhancements From 99efe673165fdc4dff5967ae9e91f1bb88f81fea Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 16 Sep 2016 15:47:52 +0200 Subject: [PATCH 0049/2110] fix merge mistakes --- realm/realm-library/src/main/java/io/realm/Realm.java | 4 ++-- realm/realm-library/src/main/java/io/realm/RealmSchema.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 3c75ccac7f..2683c6eb12 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -289,7 +289,7 @@ private static void initializeRealm(Realm realm) { RealmObjectSchema realmObjectSchema = mediator.createRealmObjectSchema(modelClass, realmSchemaCache); realmObjectSchemas.add(realmObjectSchema); } else { - columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm), false); + columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } } if (syncAvailable) { @@ -297,7 +297,7 @@ private static void initializeRealm(Realm realm) { // Assumption: when SyncConfiguration then additive schema update mode realm.sharedRealm.updateSchema(schema, version); for (Class modelClass : modelClasses) { - columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm),false); + columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } } realm.schema.columnIndices = new ColumnIndices( diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 94154d54e4..177872e97a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -144,7 +144,7 @@ public Set getAll() { Set schemas = new LinkedHashSet<>(tableCount); for (int i = 0; i < tableCount; i++) { String tableName = realm.sharedRealm.getTableName(i); - if (Table.isMetaTable(tableName)) { + if (Table.isModelTable(tableName)) { continue; } Table table = realm.sharedRealm.getTable(tableName); From 28b99d39d80180d86e327f9de7129e350f7dc758 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 16 Sep 2016 16:35:58 +0200 Subject: [PATCH 0050/2110] Fix unit tests. --- .../src/androidTest/java/io/realm/TestHelper.java | 3 +-- realm/realm-library/src/main/java/io/realm/RealmSchema.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index f183e3a6c0..6df974f3e3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -64,7 +64,6 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; -import static org.junit.Assert.assertEquals; public class TestHelper { @@ -848,7 +847,7 @@ public static void populateForDistinctFieldsOrder(Realm realm, long numberOfBloc } public static void awaitOrFail(CountDownLatch latch) { - awaitOrFail(latch, 7); + awaitOrFail(latch, 1000); } public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 177872e97a..27795005ba 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -144,7 +144,7 @@ public Set getAll() { Set schemas = new LinkedHashSet<>(tableCount); for (int i = 0; i < tableCount; i++) { String tableName = realm.sharedRealm.getTableName(i); - if (Table.isModelTable(tableName)) { + if (!Table.isModelTable(tableName)) { continue; } Table table = realm.sharedRealm.getTable(tableName); From 0b812ea382c66279fe638eebea680ab4a089e8ed Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 16 Sep 2016 20:29:11 +0200 Subject: [PATCH 0051/2110] Cleaned up User/Credential API (#99) --- .../examples/objectserver/LoginActivity.java | 2 +- .../androidTest/java/io/realm/TestHelper.java | 2 +- .../realm/objectserver/CredentialsTests.java | 103 +++++++++++ realm/realm-library/src/main/cpp/object-store | 2 +- .../io/realm/objectserver/Credentials.java | 174 +++++++++++------- .../main/java/io/realm/objectserver/User.java | 105 +++++------ .../internal/network/AuthenticateRequest.java | 73 +------- .../network/AuthenticationServer.java | 2 +- .../network/OkHttpAuthenticationServer.java | 4 +- 9 files changed, 270 insertions(+), 197 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/objectserver/CredentialsTests.java diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java index adbc6fa529..bd32a9bb2b 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -76,7 +76,7 @@ public void login(boolean createUser) { String username = this.username.getText().toString(); String password = this.password.getText().toString(); - Credentials creds = Credentials.fromUsernamePassword(username, password, createUser); + Credentials creds = Credentials.usernamePassword(username, password, createUser); String authUrl = "http://" + MyApplication.OBJECT_SERVER_IP + ":8080/auth"; User.Callback callback = new User.Callback() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 6df974f3e3..aca740d079 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -847,7 +847,7 @@ public static void populateForDistinctFieldsOrder(Realm realm, long numberOfBloc } public static void awaitOrFail(CountDownLatch latch) { - awaitOrFail(latch, 1000); + awaitOrFail(latch, 7); } public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/CredentialsTests.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/CredentialsTests.java new file mode 100644 index 0000000000..87c755029d --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/CredentialsTests.java @@ -0,0 +1,103 @@ +package io.realm.objectserver; +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class CredentialsTests { + + // See https://github.com/realm/realm-sync-services/blob/master/doc/index.apib for a description of the fields + // needed by each identity provider. + + @Test + public void getUserInfo_isUnmodifiable() { + Credentials creds = Credentials.custom("foo", "bar", null); + Map userInfo = creds.getUserInfo(); + try { + userInfo.put("boom", null); + fail(); + } catch (UnsupportedOperationException ignored) { + } + } + + @Test + public void facebook() { + Credentials creds = Credentials.facebook("foo"); + + assertEquals(Credentials.IdentityProvider.FACEBOOK, creds.getIdentityProvider()); + assertEquals("foo", creds.getUserIdentifier()); + assertTrue(creds.getUserInfo().isEmpty()); + } + + @Test + public void facebook_invalidInput() { + String[] invalidInput = { null, ""}; + for (String input : invalidInput) { + try { + Credentials.facebook(input); + fail(input + " should have failed"); + } catch (IllegalArgumentException ignored) { + } + } + } + + @Test + public void usernamePassword() { + Credentials creds = Credentials.usernamePassword("foo", "bar", true); + assertEquals("foo", creds.getUserIdentifier()); + Map userInfo = creds.getUserInfo(); + + assertEquals(Credentials.IdentityProvider.USERNAME_PASSWORD, creds.getIdentityProvider()); + assertEquals("bar", userInfo.get("password")); + assertTrue((Boolean) userInfo.get("register")); + } + + // Only validate username. All passwords are allowed + @Test + public void usernamePassword_invalidUserName() { + String[] invalidInput = { null, ""}; + for (String input : invalidInput) { + try { + Credentials.usernamePassword(input, "bar", true); + fail(input + " should have failed"); + } catch (IllegalArgumentException ignored) { + } + } + } + + @Test + public void custom() { + Map userInfo = new HashMap(); + userInfo.put("custom", "property"); + Credentials creds = Credentials.custom("customProvider", "foo", userInfo); + + assertEquals("foo", creds.getUserIdentifier()); + assertEquals("customProvider", creds.getIdentityProvider()); + assertEquals(1, creds.getUserInfo().size()); + assertEquals("property", creds.getUserInfo().get("custom")); + } +} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index b11ef9c8b7..6b6012cb2d 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit b11ef9c8b799f0f09059789ab8711853ce31b85f +Subproject commit 6b6012cb2d603e20bf82e19c453af0f9bf997894 diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java b/realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java index 835e3e600c..5fca741d3e 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java @@ -16,22 +16,25 @@ package io.realm.objectserver; -import java.util.UUID; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; /** * Credentials represent a login with a 3rd party login provider in an OAuth2 login flow, and are used by the Realm * Object Server to verify the user and grant access. *

    - * Logging into the Realm Object Server consists of the following steps: + * Logging into the Object Server consists of the following steps: *

      *
    1. * Login to 3rd party like Facebook, Google or Twitter. The result is usually an Authorization Grant that must be - * saved in a {@link Credentials} object of the proper type, e.g {@link Credentials#fromFacebook(String)} for a + * saved in a {@link Credentials} object of the proper type, e.g {@link Credentials#facebook(String)} for a * Facebook login. *
    2. *
    3. - * Authenticate a {@link User} through the Realm Object Server using these credentials. Once authenticated - * a Realm Object Server user is returned. This user can then be attached to a {@link SyncConfiguration}, which + * Authenticate a {@link User} through the Object Server using these credentials. Once authenticated + * an Object Server user is returned. This user can then be attached to a {@link SyncConfiguration}, which * will make it possible to synchronize data between the local and remote Realm. *

      * It is possible to persist the user object using e.g. the {@link UserStore} so logging @@ -43,12 +46,10 @@ * {@code * // Example * - * Credentials credentials = Credentials.fromFacebook(getFacebookToken()); - * boolean createUser = true; - * User.authenticateUser(credentials, new URL("http://objectserver.realm.io/auth", new User.Callback() { + * Credentials credentials = Credentials.facebook(getFacebookToken()); + * User.login(credentials, "http://objectserver.realm.io/auth", new User.Callback() { * \@Override * public void onSuccess(User user) { - * userStore.saveUser("key", user) * // User is now authenticated and be be used to open Realms. * } * @@ -62,102 +63,139 @@ */ public class Credentials { - private LoginType loginType; - private String field1; - private String field2; - private final boolean createUser; + private String identityProvider; + private String userIdentifier; + private Map userInfo; // Factory constructors /** - * Creates credentials for a local user that is only known by this device. - * Loosing these credentials or the User once it has been authenticated means that the data stored in - * the Realm cannot be recovered. + * Creates credentials based on a login with username and password. These credentials will only be verified + * by the Object Server. * - * @see Tutorial showing how to authenticateUser using local credentials + * @param username username of the user + * @param password the users password + * @param createUser {@code true} if the user should be created, {@code false} otherwise. It is not possible to + * create a user twice when logging in, so this flag should only be set to {@code true} the first + * time a users log in. + * @return a set of credentials that can be used to log into the Object Server using + * {@link User#loginAsync(Credentials, String, User.Callback)}. */ - public static Credentials createLocal() { - return new Credentials(LoginType.LOCAL, UUID.randomUUID().toString()); + public static Credentials usernamePassword(String username, String password, boolean createUser) { + if (username == null || username.equals("")) { + throw new IllegalArgumentException("Non-null 'username' required."); + } + Map userInfo = new HashMap(); + userInfo.put("register", createUser); + userInfo.put("password", password); + return new Credentials(IdentityProvider.USERNAME_PASSWORD, username, userInfo); } /** - * Creates a credentials token based on a login with username and password. + * Creates credentials based on a Facebook login. * - * @see Tutorial showing how to authenticateUser using username and password + * @param facebookToken a facebook userIdentifier acquired by logging into Facebook. + * @return a set of credentials that can be used to log into the Object Server using + * {@link User#loginAsync(Credentials, String, User.Callback)}. */ - public static Credentials fromUsernamePassword(String username, String password, boolean createUser) { - return new Credentials(LoginType.USERNAME_PASSWORD, username, password, createUser); + public static Credentials facebook(String facebookToken) { + if (facebookToken == null || facebookToken.equals("")) { + throw new IllegalArgumentException("Non-null 'facebookToken' required."); + } + return new Credentials(IdentityProvider.FACEBOOK, facebookToken, null); } /** - * Creates a credentials token based on a Facebook login. + * Creates a custom set of credentials. The behaviour will depend on the type of {@code identityProvider} and + * {@code userInfo} used. * - * @see Tutorial showing how to authenticateUser using the Facebook SDK + * @param identityProvider provider used to verify the credentials. + * @param userIdentifier String identifying the user. Usually a username of userIdentifier. + * @param userInfo data describing the user further or {@code null} if the user does not have any extra data. The + * data will be serialized to JSON, so all values must be mappable to a valid JSON data type. Custom + * classes will be converted using {@code toString()}. + * @return a set of credentials that can be used to log into the Object Server using + * {@link User#loginAsync(Credentials, String, User.Callback)}. */ - public static Credentials fromFacebook(String facebookToken) { - return new Credentials(LoginType.FACEBOOK, facebookToken); + public static Credentials custom(String identityProvider, String userIdentifier, Map userInfo) { + if (identityProvider == null || identityProvider.equals("")) { + throw new IllegalArgumentException("Non-null 'identityProvider' required."); + } + if (userIdentifier == null || userIdentifier.equals("")) { + throw new IllegalArgumentException("Non-null 'userIdentifier' required."); + } + if (userInfo == null) { + userInfo = new HashMap(); + } + return new Credentials(identityProvider, userIdentifier, userInfo); } - private Credentials(LoginType type, String token) { - this.loginType = type; - this.field1 = token; - this.createUser = false; - } - - private Credentials(LoginType usernamePassword, String username, String password, boolean createUser) { - this.loginType = LoginType.USERNAME_PASSWORD; - this.field1 = username; - this.field2 = password; - this.createUser = createUser; + private Credentials(String identityProvider, String token, Map userInfo) { + this.identityProvider = identityProvider; + this.userIdentifier = token; + this.userInfo = (userInfo == null) ? new HashMap() : userInfo; } /** - * Returns the type of login used to createFrom these credentials. - * It is used by the authentication server to determine how these credentials should be validated. + * Returns the provider used by the Object Server to validate these credentials. * * @return the login type. */ - public LoginType getLoginType() { - return loginType; - } - - /** - * Returns the data in field 1. The type of information in this field will depend on the login type. - * - * @return the value of field1 of for these credentials. - */ - public String getField1() { - return field1; + public String getIdentityProvider() { + return identityProvider; } /** - * Returns the data in field 2. The type of information in this field will depend on the login type. + * Returns a String that identifies the user. The value will depend on the type of {@link IdentityProvider} used. * - * @return the value of field2 of for these credentials. + * @return a String identifying the user. */ - public String getField2() { - return field2; + public String getUserIdentifier() { + return userIdentifier; } - /** - * Returns {@code true} if a User should be created based on these credentials. - * If the user already exists, this will fail. + * Returns any custom user information associated with this credential. + * The type of information will depend on the type of {@link io.realm.objectserver.Credentials.IdentityProvider} + * used. * - * @return {@code true} if the user should be created on the Realm Object Server, {@code false} if it already exists. + * @return a map of additional information about the user. */ - public boolean shouldCreateUser() { - return createUser; + public Map getUserInfo() { + return Collections.unmodifiableMap(userInfo); } /** - * Enumeration of the different types of supported authentication method. + * Enumeration of the different types of identity providers. An identity provider is the entity responsible for + * verifying that a given credential is valid. */ - public enum LoginType { - FACEBOOK, - TWITTER, - GOOGLE, - USERNAME_PASSWORD, - LOCAL + public static final class IdentityProvider { + /** + * Any credentials verified by the debug identity provider will always be considered valid. + * It is only available if configured on the Object Server, and it is disabled by default. + */ + public static final String DEBUG = "debug"; + + /** + * Credentials will be verified by Facebook. + */ + public static final String FACEBOOK = "facebook"; + + /** + * Credentials will be verified by Google. + */ + public static final String GOOGLE = "google"; + + /** + * Credentials will be verified by Twitter. + */ + public static final String TWITTER = "twitter"; + + /** + * Credentials will be verified by the Object Server. + * + * @see #usernamePassword(String, String, boolean) + */ + public static final String USERNAME_PASSWORD = "password"; } } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/User.java b/realm/realm-library/src/main/java/io/realm/objectserver/User.java index 8ed9293a98..3bea6c079f 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/User.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/User.java @@ -25,6 +25,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; import io.realm.RealmAsyncTask; import io.realm.internal.IOException; @@ -40,7 +41,6 @@ */ public class User { - private static RealmAsyncTask authenticateTask; private final SyncUser syncUser; private User(SyncUser user) { @@ -70,75 +70,70 @@ public static User fromJson(String user) { } /** - * Creates a user from an existing token. This user is automatically consider validated by the device, but the - * Realm Object Server might determine that the token has expired or no longer is valid. + * Login the user on the Realm Object Server. This is done synchronously, so calling this method on the Android + * UI thread will always crash. A logged in user is required to be able to create a {@link SyncConfiguration}. * - * This should only be used when debugging or testing. In most other cases the user object obtained from a - * {@link #loginAsync(Credentials, String, Callback)} should be saved and reused. This can e.g. be done using a - * {@link UserStore}. - * - * @param token token to represent user. - * @see #getAccessToken() + * @param credentials credentials to use. + * @param authenticationUrl Server that can authenticate against. + * @throws ObjectServerError if the login failed. * + * @see io.realm.objectserver.SyncConfiguration.Builder#user(User) */ - // FIXME Align with Cocoa on naming - public static User fromToken(String token) { - // Define a user with unlimited access. Object Server will reject any invalid access anyway. - Token refreshToken = new Token(token, null, null, Long.MAX_VALUE, Token.Permission.values()); - SyncUser internalUser = new SyncUser(null, refreshToken, null); - return new User(internalUser); - } + public static User login(final Credentials credentials, final String authenticationUrl) throws ObjectServerError { + final URL authUrl; + try { + authUrl = new URL(authenticationUrl); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e); + } - // FIXME Javadoc - public static User login(final Credentials credentials, final URL authentificationUrl) - throws ObjectServerError { - return null; // TODO + final AuthenticationServer server = SyncManager.getAuthServer(); + try { + AuthenticateResponse result = server.authenticateUser(credentials, authUrl); + if (result.isValid()) { + SyncUser syncUser = new SyncUser(result.getRefreshToken().identity(), result.getRefreshToken(), authUrl); + User user = new User(syncUser); + RealmLog.info("Succeeded authenticating user.\n%s", user); + return user; + } else { + RealmLog.info("Failed authenticating user.\n%s", result.getError()); + throw result.getError(); + } + } catch (IOException e) { + throw new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + } catch (Throwable e) { + throw new ObjectServerError(ErrorCode.UNKNOWN, e); + } } /** - * Login the user on the Realm Object Server + * Login the user on the Realm Object Server. A logged in user is required to be able to create a + * {@link SyncConfiguration}. * - * @param credentials credentials to use - * @param authenticationUrl URL to authenticateUser against + * @param credentials credentials to use. + * @param authenticationUrl Server that can authenticate against. * @param callback callback when login has completed or failed. This callback will always happen on the UI thread. - * @throws IllegalArgumentException + * + * @see io.realm.objectserver.SyncConfiguration.Builder#user(User) */ - // FIXME Return task that can be canceled public static RealmAsyncTask loginAsync(final Credentials credentials, final String authenticationUrl, final Callback callback) { - final URL authUrl; - try { - authUrl = new URL(authenticationUrl); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e); - } if (Looper.myLooper() == null) { throw new IllegalStateException("Asynchronous login is only possible from looper threads."); } - final Handler handler = new Handler(Looper.myLooper()); - - final AuthenticationServer server = SyncManager.getAuthServer(); - Future authenticateRequest = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { + ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; + Future authenticateRequest = networkPoolExecutor.submit(new Runnable() { @Override public void run() { - // Don't retry authenticateUser requests. The app might want to respond to errors. try { - AuthenticateResponse result = server.authenticateUser(credentials, authUrl, credentials.shouldCreateUser()); - if (result.isValid()) { - User user = new User(new SyncUser(result.getRefreshToken().identity(), result.getRefreshToken(), authUrl)); - postSuccess(user); - } else { - postError(result.getError()); - } - } catch (IOException e) { - postError(new ObjectServerError(ErrorCode.IO_EXCEPTION, e)); - } catch (Throwable e) { - postError(new ObjectServerError(ErrorCode.UNKNOWN, e)); + User user = login(credentials, authenticationUrl); + postSuccess(user); + } catch (ObjectServerError e) { + postError(e); } } private void postError(final ObjectServerError error) { - RealmLog.info("Failed authenticating user.\n%s", error); if (callback != null) { handler.post(new Runnable() { @Override @@ -150,7 +145,6 @@ public void run() { } private void postSuccess(final User user) { - RealmLog.info("Succeeded authenticating user.\n%s", user); if (callback != null) { handler.post(new Runnable() { @Override @@ -161,8 +155,8 @@ public void run() { } } }); - authenticateTask = new RealmAsyncTask(authenticateRequest, SyncManager.NETWORK_POOL_EXECUTOR); - return authenticateTask; + + return new RealmAsyncTask(authenticateRequest, networkPoolExecutor); } public void logout() { @@ -186,7 +180,8 @@ public String toJson() { } /** - * Returns the identity or key of this user on the Realm Object Server. + * Returns the identity of this user on the Realm Object Server. The identity is a guaranteed to be unique + * among all users on the Realm Object Server. * * @return Identity of the user on the Realm Object Server. If the user has logged out or the login has expired * {@code null} is returned. @@ -205,12 +200,6 @@ public String getAccessToken() { return syncUser.getRefreshToken().value(); } - @Override - public String toString() { - return super.toString(); - // FIXME Print representation of user, but be careful about printing anything sensitive - } - // Expose internal representation for other package protected classes SyncUser getSyncUser() { return syncUser; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java index 5522d66056..17a7c08d56 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java @@ -21,7 +21,6 @@ import java.net.URI; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import io.realm.objectserver.internal.Token; @@ -34,7 +33,7 @@ */ public class AuthenticateRequest { - private final Provider provider; + private final String provider; private final String data; private final String appId; private final Map userInfo; @@ -43,30 +42,14 @@ public class AuthenticateRequest { /** * Generates a proper login request for a new user. */ - public static AuthenticateRequest fromCredentials(Credentials credentials, boolean createUser) { + public static AuthenticateRequest fromCredentials(Credentials credentials) { if (credentials == null) { throw new IllegalArgumentException("Non-null credentials required."); } - Provider provider; - String data; + String provider = credentials.getIdentityProvider(); + String data = credentials.getUserIdentifier(); + Map userInfo = credentials.getUserInfo(); String appId = SyncManager.APP_ID; - Map userInfo = new HashMap(); - userInfo.put("register", createUser); - - switch (credentials.getLoginType()) { - case FACEBOOK: - provider = Provider.FACEBOOK; - data = credentials.getField1(); - break; - case USERNAME_PASSWORD: - provider = Provider.PASSWORD; - data = credentials.getField1(); - userInfo.put("password", credentials.getField2()); - break; - default: - throw new IllegalArgumentException("Login type not supported: " + credentials.getLoginType()); - } - return new AuthenticateRequest(provider, data, appId, null, userInfo); } @@ -78,7 +61,7 @@ public static AuthenticateRequest fromCredentials(Credentials credentials, boole */ public static AuthenticateRequest fromRefreshToken(Token refreshToken, URI path) { // Authenticate a given Realm path using an already logged in user. - return new AuthenticateRequest(Provider.REALM, + return new AuthenticateRequest("realm", refreshToken.value(), SyncManager.APP_ID, path.getPath(), @@ -86,29 +69,7 @@ public static AuthenticateRequest fromRefreshToken(Token refreshToken, URI path) ); } - /** - * Create an admin user request. Admin access gives access to all Realms. Admin access is disabled if the - * Authentication Server is in production mode. - */ - public static AuthenticateRequest adminAccess() { - return debug("admin", null); - } - - /** - * Creates an debug user request. Debug users are automatically logged into the Realm Authentication Server, and - * will always be granted access. Debug users are disabled if the Authentication Server is in production mode. - */ - public static AuthenticateRequest debug(String username, String path) { - return new AuthenticateRequest( - Provider.DEBUG, - username, - SyncManager.APP_ID, - path, - Collections.emptyMap() - ); - } - - private AuthenticateRequest(Provider provider, String data, String appId, String path, Map userInfo) { + private AuthenticateRequest(String provider, String data, String appId, String path, Map userInfo) { this.provider = provider; this.data = data; this.appId = appId; @@ -122,7 +83,7 @@ private AuthenticateRequest(Provider provider, String data, String appId, String public String toJson() { JSONObject request = new JSONObject(); try { - request.put("provider", provider.getProvider()); + request.put("provider", provider); request.put("data", data); request.put("app_id", appId); if (path != null) { @@ -135,22 +96,4 @@ public String toJson() { return request.toString(); } - - private enum Provider { - REALM("realm"), // Used if you already have a valid refresh token - DEBUG("debug"), // Will always succeed - PASSWORD("password"), // password/username login - FACEBOOK("facebook"); // facebook login - - private final String provider; - - Provider(String provider) { - this.provider = provider; - } - - public String getProvider() { - return provider; - } - } - } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java index eb17182e6f..58594e2507 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java @@ -29,7 +29,7 @@ * only responsible for executing a given network request. */ public interface AuthenticationServer { - AuthenticateResponse authenticateUser(Credentials credentials, URL authenticationUrl, boolean createUser); + AuthenticateResponse authenticateUser(Credentials credentials, URL authenticationUrl); AuthenticateResponse authenticateRealm(Token refreshToken, URI path, URL authenticationUrl); RefreshResponse refresh(String token, URL authenticationUrl); } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java index 2185496d1b..6a4ba6a4ba 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java @@ -46,9 +46,9 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { * Authenticate the given credentials on the specified Realm Authentication Server. */ @Override - public AuthenticateResponse authenticateUser(Credentials credentials, URL authenticationUrl, boolean createUser) { + public AuthenticateResponse authenticateUser(Credentials credentials, URL authenticationUrl) { try { - String requestBody = AuthenticateRequest.fromCredentials(credentials, createUser).toJson(); + String requestBody = AuthenticateRequest.fromCredentials(credentials).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { return new AuthenticateResponse(new ObjectServerError(ErrorCode.OTHER_ERROR, Util.getStackTrace(e))); From 604e67a4096b7fc79b9d6f3d12de226a620b31fb Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 16 Sep 2016 22:06:50 +0200 Subject: [PATCH 0052/2110] Better error handling (#102) * Better error handling in Session states. * AutomaticSyncPolicy now tries to be smart about when to abort. Better Javadoc. --- .../examples/objectserver/LoginActivity.java | 4 +- .../java/io/realm/objectserver/ErrorCode.java | 59 +++++++-------- .../realm/objectserver/ObjectServerError.java | 71 ++++++++++++++----- .../io/realm/objectserver/SyncManager.java | 5 +- .../internal/AuthenticatingState.java | 29 ++++---- .../objectserver/internal/BoundState.java | 68 +++--------------- .../realm/objectserver/internal/FsmState.java | 18 +++-- .../objectserver/internal/SyncSession.java | 9 ++- .../network/AuthenticateResponse.java | 3 +- .../syncpolicy/AutomaticSyncPolicy.java | 46 ++++++++---- 10 files changed, 164 insertions(+), 148 deletions(-) diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java index bd32a9bb2b..5c92889432 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -31,6 +31,8 @@ import io.realm.objectserver.User; import io.realm.objectserver.UserStore; +import static io.realm.objectserver.ErrorCode.UNKNOWN_ACCOUNT; + public class LoginActivity extends AppCompatActivity { private UserStore userStore = MyApplication.USER_STORE; @@ -91,7 +93,7 @@ public void onSuccess(User user) { public void onError(ObjectServerError error) { progressDialog.dismiss(); String errorMsg; - switch (error.errorCode()) { + switch (error.getErrorCode()) { case UNKNOWN_ACCOUNT: errorMsg = "Account does not exists."; break; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java b/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java index ac857cc59e..326c717886 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java @@ -16,31 +16,30 @@ package io.realm.objectserver; +/** + * This class enumerate all potential errors related to using the Object Server or synchronizing data. + */ public enum ErrorCode { - // See https://github.com/realm/realm-sync/issues/585 // See https://github.com/realm/realm-sync/blob/master/doc/protocol.md // Realm Java errors (0-49) - UNKNOWN(-1), // Catch-all IO_EXCEPTION(0, Category.RECOVERABLE), // Some IO error while either contacting the server or reading the response JSON_EXCEPTION(1), // JSON input could not be parsed correctly // Realm Object Server errors (100 - 199) - - // Connection level and protocol errors - - CONNECTION_CLOSED(100, Category.INFO), // Connection closed (no error) - OTHER_ERROR(101, Category.INFO), // Other connection level error - UNKNOWN_MESSAGE(102, Category.INFO), // Unknown type of input message - BAD_SYNTAX(103, Category.INFO), // Bad syntax in input message head - LIMITS_EXCEEDED(104, Category.INFO), // Limits exceeded in input message - WRONG_PROTOCOL_VERSION(105, Category.INFO), // Wrong protocol version (CLIENT) - BAD_SESSION_IDENT(106, Category.INFO), // Bad session identifier in input message - REUSE_OF_SESSION_IDENT(107, Category.INFO), // Overlapping reuse of session identifier (BIND) - BOUND_IN_OTHER_SESSION(108, Category.INFO), // Client file bound in other session (IDENT) - BAD_MESSAGE_ORDER(109, Category.INFO), // Bad input message order + // Connection level and protocol errors. + CONNECTION_CLOSED(100), // Connection closed (no error) + OTHER_ERROR(101), // Other connection level error + UNKNOWN_MESSAGE(102), // Unknown type of input message + BAD_SYNTAX(103), // Bad syntax in input message head + LIMITS_EXCEEDED(104), // Limits exceeded in input message + WRONG_PROTOCOL_VERSION(105), // Wrong protocol version (CLIENT) + BAD_SESSION_IDENT(106), // Bad session identifier in input message + REUSE_OF_SESSION_IDENT(107), // Overlapping reuse of session identifier (BIND) + BOUND_IN_OTHER_SESSION(108), // Client file bound in other session (IDENT) + BAD_MESSAGE_ORDER(109), // Bad input message order // Session level errors (200 - 299) SESSION_CLOSED(200, Category.RECOVERABLE), // Session closed (no error) @@ -53,7 +52,7 @@ public enum ErrorCode { NO_SUCH_PATH(205), // No such Realm (BIND) PERMISSION_DENIED(206), // Permission denied (BIND, REFRESH) - // Fatal: Wrong server/client versions. Trying to sync incompatible files or corrupted. + // Fatal: Wrong server/client versions. Trying to sync incompatible files or the file was corrupted. BAD_SERVER_FILE_IDENT(207), // Bad server file identifier (IDENT) BAD_CLIENT_FILE_IDENT(208), // Bad client file identifier (IDENT) BAD_SERVER_VERSION(209), // Bad server version (IDENT, UPLOAD) @@ -61,10 +60,9 @@ public enum ErrorCode { DIVERGING_HISTORIES(211), // Diverging histories (IDENT) BAD_CHANGESET(212), // Bad changeset (UPLOAD) - // 300 - 599 Standard HTTP error codes + // 300 - 599 Reserved for Standard HTTP error codes // Realm Authentication Server response errors (600 - 699) - INVALID_PARAMETERS(601), MISSING_PARAMETERS(602), INVALID_CREDENTIALS(611), @@ -90,23 +88,27 @@ public String toString() { return super.toString() + "(" + code + ")"; } - public int errorCode() { + /** + * Returns the numerical value for this error code. + * + * @return the error code as an unique {@code int} value. + */ + public int intValue() { return code; } - /** - * Returns the category of the error. + * Returns the getCategory of the error. *

      - * Errors come in 3 categories: FATAL, RECOVERABLE, and INFO. + * Errors come in 2 categories: FATAL, RECOVERABLE *

      * FATAL: The session cannot be recovered and needs to be re-created. A likely cause is that the User does not - * have access to this Realm. Check that the {@link SyncConfiguration} is correct. + * have access to this Realm. Check that the {@link SyncConfiguration} is correct. Any fatal error will cause + * the session to be become {@link SessionState#STOPPED}. *

      - * RECOVERABLE: The session is paused until given additional information. Most likely cause is an expired access - * token or similar. + * RECOVERABLE: Temporary error. The session becomes {@link SessionState#UNBOUND}, but will automatically try to + * recover as soon as possible. *

      - * INFO: The underlying sync client will automatically try to recover from this. * * @return the severity of the error. */ @@ -118,7 +120,7 @@ public static ErrorCode fromInt(int errorCode) { ErrorCode[] errorCodes = values(); for (int i = 0; i < errorCodes.length; i++) { ErrorCode error = errorCodes[i]; - if (error.errorCode() == errorCode) { + if (error.intValue() == errorCode) { return error; } } @@ -127,7 +129,6 @@ public static ErrorCode fromInt(int errorCode) { public enum Category { FATAL, // Abort session as soon as possible - RECOVERABLE, // Still possible to recover the session by either rebinding or providing the required information. - INFO // Just FYI. The underlying network client will automatically try to recover. + RECOVERABLE // Still possible to recover the session by either rebinding or providing the required information. } } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java b/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java index f66ef6f31a..27f061864c 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java @@ -22,9 +22,9 @@ * This class is a wrapper for all errors happening when communicating with the Realm Object Server. * This include both exceptions and protocol errors. * - * Only {@link #errorCode()} is guaranteed to be set. If the error was caused by an underlying exception - * {@link #errorMessage()} is {@code null} and {@link #exception()} is set, while if the error was a protocol error - * {@link #errorMessage()} is set and {@link #exception()} is null. + * Only {@link #getErrorCode()} is guaranteed to contain a value. If the error was caused by an underlying exception + * {@link #getErrorMessage()} is {@code null} and {@link #getException()} is set, while if the error was a protocol error + * {@link #getErrorMessage()} is set and {@link #getException()} is null. * * @see io.realm.objectserver.ErrorCode for a list of possible errors. */ @@ -34,10 +34,22 @@ public class ObjectServerError extends RuntimeException { private final String errorMessage; private final Throwable exception; + /** + * Create an error caused by an error in the protocol when communicating with the Object Server. + * + * @param errorCode error code for this type of error. + * @param errorMessage detailed error message. + */ public ObjectServerError(ErrorCode errorCode, String errorMessage) { - this(errorCode, errorMessage, null); + this(errorCode, errorMessage, (Throwable) null); } + /** + * Create an error caused by an an exception when communicating with the Object Server. + * + * @param errorCode error code for this type of error. + * @param exception underlying exception causing this error. + */ public ObjectServerError(ErrorCode errorCode, Throwable exception) { this(errorCode, null, exception); } @@ -45,9 +57,9 @@ public ObjectServerError(ErrorCode errorCode, Throwable exception) { /** * Generic error happening that could happen anywhere. * - * @param errorCode - * @param errorMessage - * @param exception + * @param errorCode error code for this type of error. + * @param errorMessage detailed error message. + * @param exception underlying exception if the error was caused by this. */ public ObjectServerError(ErrorCode errorCode, String errorMessage, Throwable exception) { this.error = errorCode; @@ -58,34 +70,57 @@ public ObjectServerError(ErrorCode errorCode, String errorMessage, Throwable exc /** * Errors happening while trying to authenticate a user. * - * @param errorCode - * @param title - * @param hint - * @param type + * @param errorCode error code for this type of error. + * @param title Title for this type of error. + * @param hint a hint for resolving the error. */ - public ObjectServerError(ErrorCode errorCode, String title, String hint, String type) { - this(errorCode, String.format("%s : %s (%s)", title, hint, type), null); + public ObjectServerError(ErrorCode errorCode, String title, String hint) { + this(errorCode, (hint != null) ? title + " : " + hint : title, (Throwable) null); } - public ErrorCode errorCode() { + /** + * Returns the error code uniquely identifying this type of error. + * + * @return the error code identifying the type of error. + * @see ErrorCode + */ + public ErrorCode getErrorCode() { return error; } - public String errorMessage() { + /** + * Returns a more detailed error message about the cause of this error. + * + * @return a detailed error message or {@code null} if one was not available. + */ + public String getErrorMessage() { return errorMessage; } - public Throwable exception() { + /** + * Returns the underlying exception causing this error, if any. + * + * @return the underlying exception causing this error, or {@code null} if not caused by an exception. + */ + public Throwable getException() { return exception; } - public ErrorCode.Category category() { + /** + * Returns the {@link io.realm.objectserver.ErrorCode.Category} category for this error. + * Errors that are {@link io.realm.objectserver.ErrorCode.Category#RECOVERABLE} mean that it is still possible for a + * given {@link Session} to resume synchronization. {@link io.realm.objectserver.ErrorCode.Category#FATAL} errors + * means that session has stopped and cannot be recovered. + * + * @return the error category. + */ + public ErrorCode.Category getCategory() { return error.getCategory(); } @Override public String toString() { - StringBuilder sb = new StringBuilder(errorCode().toString()); + StringBuilder sb = new StringBuilder(getErrorCode().toString()); if (errorMessage != null) { sb.append('\n'); sb.append(errorMessage); diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java index e068cec4bb..917e98cb7f 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java @@ -50,16 +50,13 @@ public void onError(Session session, ObjectServerError error) { String errorMsg = String.format("Session Error[%s]: %s", session.getConfiguration().getServerUrl(), error.toString()); - switch (error.errorCode().getCategory()) { + switch (error.getErrorCode().getCategory()) { case FATAL: RealmLog.error(errorMsg); break; case RECOVERABLE: RealmLog.info(errorMsg); break; - case INFO: - RealmLog.debug(errorMsg); - break; } } }; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java index 0ac116e2ee..a52d2f39f6 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java @@ -79,21 +79,6 @@ public void onExitState() { } } - private synchronized void authenticate(final SyncSession session) { - session.authenticateRealm(new Runnable() { - @Override - public void run() { - gotoNextState(SessionState.BINDING); - } - }, new io.realm.objectserver.Session.ErrorHandler() { - @Override - public void onError(io.realm.objectserver.Session session, ObjectServerError error) { - // FIXME For critical errors, got directly to STOPPED - gotoNextState(SessionState.UNBOUND); - } - }); - } - @Override public void onBind() { gotoNextState(SessionState.BINDING); // Equivalent to forcing a retry @@ -108,4 +93,18 @@ public void onUnbind() { public void onStop() { gotoNextState(SessionState.STOPPED); } + + private synchronized void authenticate(final SyncSession session) { + session.authenticateRealm(new Runnable() { + @Override + public void run() { + gotoNextState(SessionState.BINDING); + } + }, new Session.ErrorHandler() { + @Override + public void onError(Session s, ObjectServerError error) { + session.onError(error); + } + }); + } } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java index 5eb347afa2..d03632dae0 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java @@ -16,6 +16,7 @@ package io.realm.objectserver.internal; +import io.realm.objectserver.ErrorCode; import io.realm.objectserver.ObjectServerError; import io.realm.objectserver.SessionState; @@ -33,7 +34,7 @@ public void onEnterState() { @Override public void onExitState() { - session.stopNativeSession(); + // Do nothing. Entry states will stop the session if needed. } @Override @@ -48,62 +49,15 @@ public void onStop() { @Override public void onError(ObjectServerError error) { - switch(error.errorCode()) { - // FIXME: Regenerate this - // Auth protocol errors (should not happen). - case IO_EXCEPTION: - case JSON_EXCEPTION: - case INVALID_PARAMETERS: - case MISSING_PARAMETERS: - case INVALID_CREDENTIALS: - case UNKNOWN_ACCOUNT: - case EXISTING_ACCOUNT: - case ACCESS_DENIED: - case EXPIRED_REFRESH_TOKEN: - throw new IllegalStateException("Authentication protocol errors should not happen: " + error.toString()); - - // Ignore Network client errors (irrelevant) - // FIXME: Not accurate: https://github.com/realm/realm-sync/issues/659 How should these be handled? - case CONNECTION_CLOSED: - case OTHER_ERROR: - case UNKNOWN_MESSAGE: - case BAD_SYNTAX: - case LIMITS_EXCEEDED: - case WRONG_PROTOCOL_VERSION: - case BAD_SESSION_IDENT: - case REUSE_OF_SESSION_IDENT: - case BOUND_IN_OTHER_SESSION: - case BAD_MESSAGE_ORDER: - return; - - // Session errors: - // FIXME: Which of these are just INFO and which can we actually do something about? Right now treat all as fatal - case SESSION_CLOSED: - case OTHER_SESSION_ERROR: - gotoNextState(SessionState.STOPPED); - break; - - case TOKEN_EXPIRED: - // Only known case we can actually work around. - // Trigger a rebind which will cause access token to be refreshed. - gotoNextState(SessionState.BINDING); - break; - - case BAD_AUTHENTICATION: - case ILLEGAL_REALM_PATH: - case NO_SUCH_PATH: - case PERMISSION_DENIED: - case BAD_SERVER_FILE_IDENT: - case BAD_CLIENT_FILE_IDENT: - case BAD_SERVER_VERSION: - case BAD_CLIENT_VERSION: - case DIVERGING_HISTORIES: - case BAD_CHANGESET: - gotoNextState(SessionState.STOPPED); - break; - - default: - throw new IllegalArgumentException("Unknown error code:" + error.errorCode()); + // If a Realms access token has expired, trigger a rebind. If the user is still valid it will automatically + // refresh it. + if (error.getErrorCode() == ErrorCode.TOKEN_EXPIRED) { + gotoNextState(SessionState.BINDING); + } else { + switch (error.getCategory()) { + case FATAL: gotoNextState(SessionState.STOPPED); break; + case RECOVERABLE: gotoNextState(SessionState.UNBOUND); break; + } } } } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java index 8460a58964..833e282191 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java @@ -16,11 +16,12 @@ package io.realm.objectserver.internal; -import io.realm.objectserver.*; +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.SessionState; /** - * Abstract class containing shared logic for all {@link io.realm.objectserver.Session} states. All states must extend this class as it - * contains the logic for entering and leaving states. + * Abstract class containing shared logic for all {@link io.realm.objectserver.Session} states. All states must extend + * this class as it contains the logic for entering and leaving states. * * TODO Move this to the Object Store */ @@ -43,7 +44,7 @@ public void entry(SyncSession session) { /** * Called just before leaving the state. Once this method is called no more state changes can be triggered from - * this state until {@link #entry(io.realm.objectserver.Session)} has been called again. + * this state until {@link #entry(SyncSession)} has been called again. *

      * This should only be called from {@link io.realm.objectserver.Session}. */ @@ -83,6 +84,13 @@ public void onStop() { @Override public void onError(ObjectServerError error) { - gotoNextState(SessionState.STOPPED); + switch(error.getCategory()) { + case FATAL: + gotoNextState(SessionState.STOPPED); + break; + case RECOVERABLE: + gotoNextState(SessionState.UNBOUND); + break; + } } } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java index 16ad2500c1..85db11b919 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java @@ -90,6 +90,8 @@ @Keep public final class SyncSession { + private static final long MAX_DELAY_MS = TimeUnit.MINUTES.toMillis(5); + private final HashMap FSM = new HashMap(); // Variables used by the FSM @@ -262,7 +264,7 @@ public void run() { ObjectServerError error = null; while (true) { attempt++; - long sleep = Util.calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); + long sleep = Util.calculateExponentialDelay(attempt - 1, MAX_DELAY_MS); if (sleep > 0) { try { Thread.sleep(sleep); @@ -285,7 +287,7 @@ public void run() { // All other errors indicate a bigger problem, so stop trying to authenticate and // unbind ObjectServerError responseError = response.getError(); - if (responseError.errorCode() != ErrorCode.IO_EXCEPTION) { + if (responseError.getErrorCode() != ErrorCode.IO_EXCEPTION) { success = false; error = responseError; break; @@ -345,9 +347,6 @@ public SessionState getState() { } /** - * FIXME: Find a way to keep this out of the public API. Could probably happen as part of moving everything to the - * Object Store. - * * Notify session that a commit on the device has happened. */ void notifyCommit(long version) { diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java index 0416786a63..0455ac4049 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java @@ -55,11 +55,10 @@ static AuthenticateResponse createFrom(Response response) { if (response.code() != 200) { try { JSONObject obj = new JSONObject(serverResponse); - String type = obj.getString("type"); String hint = obj.optString("hint", null); String title = obj.optString("title", null); ErrorCode errorCode = ErrorCode.fromInt(obj.optInt("code", -1)); - ObjectServerError error = new ObjectServerError(errorCode, title, hint, type); + ObjectServerError error = new ObjectServerError(errorCode, title, hint); return new AuthenticateResponse(error); } catch (JSONException e) { ObjectServerError error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, "Server failed with " + diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java index eabb6b644a..5941452ba1 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java @@ -19,12 +19,16 @@ import io.realm.objectserver.ObjectServerError; import io.realm.objectserver.internal.SyncSession; +import static java.lang.System.currentTimeMillis; + /** * This SyncPolicy will automatically start synchronizing changes to a Realm as soon as it is opened. - * // TODO Figure out how to close connection once all changes have been uploaded. */ public class AutomaticSyncPolicy implements SyncPolicy { + private Long lastError = null; + private int recurringErrors = 0; + @Override public void onRealmOpened(SyncSession session) { session.bind(); // Bind Realm first time it is opened. @@ -32,8 +36,8 @@ public void onRealmOpened(SyncSession session) { @Override public void onRealmClosed(SyncSession session) { - // TODO Sync need to expose callback when there is no more local changes - // For now just keep the session open. + // TODO In order to preserve resources we should ideally close the session as well, but first + // we want to make sure that all local changes have been synchronized to the remote Realm. } @Override @@ -48,22 +52,40 @@ public void onSessionStopped(SyncSession session) { @Override public boolean onError(SyncSession session, ObjectServerError error) { - switch(error.category()) { + switch(error.getCategory()) { case FATAL: return false; // Report all fatal errors to the user - case INFO: - return true; // Ignore all INFO errors case RECOVERABLE: - rebind(session); - return true; + return rebind(session); default: return false; } } - private void rebind(SyncSession session) { - // FIXME: Do not rebind uncritically. Figure out a good strategy for this. - // See https://realmio.slack.com/archives/sync-core/p1472415880000002 - session.bind(); + /** + * Returns {@code true} if we decide to rebind, {@code false} if the error was determined to no longer be solvable. + */ + private boolean rebind(SyncSession session) { + // Track all calls to rebind(). If some error reported as RECOVERABLE keeps happening, we need to abort to + // prevent run-away sessions. Right now we treat an error as recurring if it happens within 3 seconds of each + // other. After 5 of such errors we terminate the session. + // + // Standard IO errors are already handled using incremental backoff by e.g the AUTHENTICATING state, so + // re-occurring errors at this level are more serious. + long now = System.currentTimeMillis(); + if (lastError - now < 3000) { + recurringErrors++; + } else { + recurringErrors = 1; + } + lastError = now; + + if (recurringErrors == 5) { + session.stop(); // Abort session, some error that should be temporary keeps happening. + return false; + } else { + session.bind(); + return true; + } } } From eab94bef8b2a0b7b88ec3dd71802cfe2cbc14ebb Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 19 Sep 2016 13:43:49 +0900 Subject: [PATCH 0053/2110] add a task to clean jni header files when .so file is cleaned (#3450) --- realm/realm-library/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 32416d0ab8..fa3940a90c 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -424,6 +424,11 @@ if (project.hasProperty('dontCleanJniFiles')) { } } } else { + task cleanJniHeaders(type: Delete) { + delete project.file('src/main/cpp/jni_include') + } + clean.dependsOn cleanJniHeaders + task cleanExternalBuildFiles(type: Delete) { delete project.file('.externalNativeBuild') } From cf2e66bd067aee4901b60c3654fca91b583d1bfa Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 19 Sep 2016 14:58:50 +0900 Subject: [PATCH 0054/2110] update javadoc comments of JSON APIs in Realm class to mention available API level. (#3456) --- realm/realm-library/src/main/java/io/realm/Realm.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 55565157a4..609b4dc261 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -409,6 +409,8 @@ public void createOrUpdateAllFromJson(Class clazz, Str * JSON properties with {@code null} value will map to the default value for the data type in Realm and unknown properties * will be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field * will be set to the default value for that type. + *

      + * This API is only available in API level 11 or later. * * @param clazz type of Realm objects created. * @param inputStream the JSON array as a InputStream. All objects in the array must be of the specified class. @@ -439,6 +441,8 @@ public void createAllFromJson(Class clazz, InputStream * If updating a {@link RealmObject} and a field is not found in the JSON object, that field will not be updated. * If a new {@link RealmObject} is created and a field is not found in the JSON object, that field will be assigned * the default value for the field type. + *

      + * This API is only available in API level 11 or later. * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the InputStream with a list of object data in JSON format. @@ -585,6 +589,8 @@ public E createOrUpdateObjectFromJson(Class clazz, Str * properties with {@code null} value will map to the default value for the data type in Realm and unknown properties will * be ignored. If a {@link RealmObject} field is not present in the JSON object the {@link RealmObject} field will * be set to the default value for that type. + *

      + * This API is only available in API level 11 or later. * * @param clazz type of Realm object to create. * @param inputStream the JSON object data as a InputStream. @@ -632,6 +638,8 @@ public E createObjectFromJson(Class clazz, InputStream * {@link RealmObject} and a field is not found in the JSON object, that field will not be updated. If a new * {@link RealmObject} is created and a field is not found in the JSON object, that field will be assigned the * default value for the field type. + *

      + * This API is only available in API level 11 or later. * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the {@link InputStream} with object data in JSON format. From d7361125735ec44dd1794070b173ce794e2f98a2 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 19 Sep 2016 15:00:48 +0900 Subject: [PATCH 0055/2110] fix unstable test (#3449) --- .../src/androidTest/java/io/realm/RealmAsyncQueryTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 1c6b78eb23..e61a93c76c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -1125,7 +1125,7 @@ public void findAllSortedAsync_batchUpdate() { public boolean onInterceptInMessage(int what) { switch (what) { case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: { - if (numberOfIntercept.incrementAndGet() == 1) { + if (numberOfIntercept.incrementAndGet() == 2 /* 2 queries are both completed */) { // 6. The first time the async queries complete we start an update from // another background thread. This will cause queries to rerun when the // background thread notifies this thread. From 5295af6d9ed30ac4a1476b39c10e3bf97e441317 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 19 Sep 2016 15:28:58 +0900 Subject: [PATCH 0056/2110] resurrect buildTargetABIs properties support (#3454) --- realm/realm-library/build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index fa3940a90c..50edef0b50 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -48,7 +48,11 @@ android { // JNI build currently (lack of lto linking support). // This file should be removed and use the one from Android SDK cmake package when it supports lto. "-DCMAKE_TOOLCHAIN_FILE=${project.file('src/main/cpp/android.toolchain.cmake').path}" - abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips' + if (!project.hasProperty('android.injected.build.abi') && project.hasProperty('buildTargetABIs')) { + abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*')) + } else { + abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips' + } } } } From 5730886208f1067f2ebfb138592cce9e20e56c45 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Mon, 19 Sep 2016 09:47:05 +0200 Subject: [PATCH 0057/2110] Only report to Slack on failure (#3461) --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f263677e77..de5d3ebddd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -75,16 +75,16 @@ try { buildSuccess = false throw e } finally { - if (['master', 'releases'].contains(env.BRANCH_NAME)) { + if (['master', 'releases'].contains(env.BRANCH_NAME) && !buildSuccess) { node { withCredentials([[$class: 'StringBinding', credentialsId: 'slack-java-url', variable: 'SLACK_URL']]) { def payload = JsonOutput.toJson([ username: 'Mr. Jenkins', icon_emoji: ':jenkins:', attachments: [[ - 'title': "The ${env.BRANCH_NAME} branch is ${buildSuccess?'healthy.':'broken!'}", + 'title': "The ${env.BRANCH_NAME} branch is broken!", 'text': "<${env.BUILD_URL}|Click here> to check the build.", - 'color': "${buildSuccess?'good':'danger'}" + 'color': "danger" ]] ]) sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" From c472f10c1a410b6611ab8afa8f3a50bf331ad456 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 19 Sep 2016 12:13:37 +0200 Subject: [PATCH 0058/2110] Logout and Userstore (#104) --- examples/build.gradle | 2 +- .../objectserver/CounterActivity.java | 14 +- .../examples/objectserver/LoginActivity.java | 4 - .../examples/objectserver/MyApplication.java | 13 +- .../androidTest/java/io/realm/RealmTests.java | 53 ++++ .../io/realm/objectserver/SchemaTests.java | 44 +-- .../io/realm/objectserver/SyncTestUtils.java | 48 ++-- .../java/io/realm/objectserver/UserTests.java | 36 +++ .../src/main/java/io/realm/BaseRealm.java | 42 +-- .../src/main/java/io/realm/Realm.java | 30 +++ .../src/main/java/io/realm/RealmCache.java | 13 + .../src/main/java/io/realm/internal/Util.java | 73 +++-- .../objectserver/AuthenticationListener.java | 36 +++ .../realm/objectserver/SyncConfiguration.java | 59 ++-- .../io/realm/objectserver/SyncManager.java | 69 +++++ .../main/java/io/realm/objectserver/User.java | 145 +++++++++- .../java/io/realm/objectserver/UserStore.java | 65 ++--- .../android/SharedPrefsUserStore.java | 145 ++++------ .../objectserver/internal/SyncSession.java | 74 ++--- .../realm/objectserver/internal/SyncUser.java | 255 ++++++++++++------ .../io/realm/objectserver/internal/Token.java | 42 ++- .../internal/network/AuthServerResponse.java | 64 +++++ .../network/AuthenticateResponse.java | 28 +- .../network/AuthenticationServer.java | 4 +- .../network/ExponentialBackoffTask.java | 105 ++++++++ .../internal/network/LogoutRequest.java | 32 +++ .../internal/network/LogoutResponse.java | 80 ++++++ .../network/OkHttpAuthenticationServer.java | 6 + .../internal/network/RefreshResponse.java | 27 +- 29 files changed, 1152 insertions(+), 456 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/objectserver/UserTests.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/AuthenticationListener.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthServerResponse.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/ExponentialBackoffTask.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutRequest.java create mode 100644 realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutResponse.java diff --git a/examples/build.gradle b/examples/build.gradle index c7ab4ca759..0488d75dc1 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -16,7 +16,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0-rc1' + classpath 'com.android.tools.build:gradle:2.2.0-rc2' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index cc1595f7cf..b0eee51e19 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -32,12 +32,14 @@ import io.realm.examples.objectserver.model.CRDTCounter; import io.realm.examples.objectserver.model.CounterOperation; import io.realm.objectserver.SyncConfiguration; +import io.realm.objectserver.SyncManager; import io.realm.objectserver.User; public class CounterActivity extends AppCompatActivity { private Realm realm; private RealmResults counter; + private User user; @BindView(R.id.text_counter) TextView counterView; @@ -48,8 +50,7 @@ protected void onCreate(Bundle savedInstanceState) { ButterKnife.bind(this); // Check if we have a valid user, otherwise redirect to login - User user = MyApplication.CURRENT_USER; - if (user == null) { + if (User.currentUser() == null) { gotoLoginActivity(); } } @@ -57,7 +58,8 @@ protected void onCreate(Bundle savedInstanceState) { @Override protected void onStart() { super.onStart(); - if (MyApplication.CURRENT_USER != null) { + if (User.currentUser() != null) { + user = User.currentUser(); // Create a RealmConfiguration for our user SyncConfiguration config = new SyncConfiguration.Builder(this) .initialData(new Realm.Transaction() { @@ -66,7 +68,7 @@ public void execute(Realm realm) { realm.createObject(CRDTCounter.class, 1); } }) - .user(MyApplication.CURRENT_USER) + .user(user) .serverUrl("realm://" + MyApplication.OBJECT_SERVER_IP + "/~/default") .build(); @@ -74,6 +76,7 @@ public void execute(Realm realm) { realm = Realm.getInstance(config); // FIXME Looks like PrimaryKey and lists are not working correctly yet + // FIXME Also need support for the `setDefault` instruction for this to make sense. // counter = realm.where(CRDTCounter.class).findFirstAsync(); // counter.addChangeListener(new RealmChangeListener() { // @Override @@ -107,6 +110,7 @@ public void onChange(RealmResults result) { protected void onStop() { super.onStop(); closeRealm(); + user = null; } private void closeRealm() { @@ -125,8 +129,8 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case R.id.action_logout: - MyApplication.CURRENT_USER.logout(); closeRealm(); + user.logout(); gotoLoginActivity(); return true; diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java index 5c92889432..25d3d76826 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -35,8 +35,6 @@ public class LoginActivity extends AppCompatActivity { - private UserStore userStore = MyApplication.USER_STORE; - @BindView(R.id.input_username) EditText username; @BindView(R.id.input_password) EditText password; @BindView(R.id.button_login) Button loginButton; @@ -84,8 +82,6 @@ public void login(boolean createUser) { @Override public void onSuccess(User user) { progressDialog.dismiss(); - userStore.saveAsync(MyApplication.APP_USER_KEY, user); // TODO Use Async - MyApplication.CURRENT_USER = user; onLoginSuccess(); } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java index 008ca06030..57981c629b 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java @@ -17,21 +17,22 @@ package io.realm.examples.objectserver; import android.app.Application; +import android.util.Log; +import io.realm.log.AndroidLogger; +import io.realm.log.RealmLog; +import io.realm.objectserver.SyncManager; import io.realm.objectserver.User; import io.realm.objectserver.UserStore; import io.realm.objectserver.android.SharedPrefsUserStore; public class MyApplication extends Application { - public static final String OBJECT_SERVER_IP = "192.168.104.22"; - public static final String APP_USER_KEY = "defaultAppUser"; - public static UserStore USER_STORE; - public static User CURRENT_USER = null; - + public static final String OBJECT_SERVER_IP = "192.168.1.3"; @Override public void onCreate() { super.onCreate(); - USER_STORE = new SharedPrefsUserStore(this); + RealmLog.add(new AndroidLogger(Log.VERBOSE)); + SyncManager.setUserStore(new SharedPrefsUserStore(this)); // Temporary until we can find a way to inject Context. } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 9f44948320..f590270e28 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3747,4 +3747,57 @@ public void execute(Realm realm) { //noinspection ConstantConditions assertEquals("pochi", realm.where(Cat.class).findFirst().getName()); } + + @Test + public void getGlobalInstanceCount() { + final CountDownLatch bgDone = new CountDownLatch(1); + + final RealmConfiguration config = configFactory.createConfiguration("globalCountTest"); + assertEquals(0, Realm.getGlobalInstanceCount(config)); + + // Open thread local Realm + Realm realm = Realm.getInstance(config); + assertEquals(1, Realm.getGlobalInstanceCount(config)); + + // Open thread local DynamicRealm + DynamicRealm dynRealm = DynamicRealm.getInstance(config); + assertEquals(2, Realm.getGlobalInstanceCount(config)); + + // Open Realm in another thread + new Thread(new Runnable() { + @Override + public void run() { + Realm realm = Realm.getInstance(config); + assertEquals(3, Realm.getGlobalInstanceCount(config)); + realm.close(); + assertEquals(2, Realm.getGlobalInstanceCount(config)); + bgDone.countDown(); + } + }).start(); + + TestHelper.awaitOrFail(bgDone); + dynRealm.close(); + assertEquals(1, Realm.getGlobalInstanceCount(config)); + realm.close(); + assertEquals(0, Realm.getGlobalInstanceCount(config)); + } + + @Test + public void getLocalInstanceCount() { + final RealmConfiguration config = configFactory.createConfiguration("localInstanceCount"); + assertEquals(0, Realm.getGlobalInstanceCount(config)); + + // Open thread local Realm + Realm realm = Realm.getInstance(config); + assertEquals(1, Realm.getGlobalInstanceCount(config)); + + // Open thread local DynamicRealm + DynamicRealm dynRealm = DynamicRealm.getInstance(config); + assertEquals(2, Realm.getGlobalInstanceCount(config)); + + dynRealm.close(); + assertEquals(1, Realm.getGlobalInstanceCount(config)); + realm.close(); + assertEquals(0, Realm.getGlobalInstanceCount(config)); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java index 20647dfb3e..ccc3dfe023 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java @@ -36,17 +36,14 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) public class SchemaTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - @Rule - public final ExpectedException thrown = ExpectedException.none(); - private Context context; - private User user; private SyncConfiguration config; @Before @@ -61,6 +58,7 @@ public void setUp() { @After public void tearDown() throws Exception { + Realm.deleteRealm(config); } @Test @@ -89,10 +87,14 @@ public void disallow_removeClass() { String className = "StringOnly"; realm.beginTransaction(); assertTrue(realm.getSchema().contains(className)); - thrown.expect(IllegalArgumentException.class); - realm.getSchema().remove(className); - realm.cancelTransaction(); - realm.close(); + try { + realm.getSchema().remove(className); + fail(); + } catch (IllegalArgumentException ignored) { + } finally { + realm.cancelTransaction(); + realm.close(); + } } @Test @@ -111,11 +113,15 @@ public void disallow_renameClass() { Realm realm = Realm.getInstance(config); String className = "StringOnly"; realm.beginTransaction(); - thrown.expect(IllegalArgumentException.class); - realm.getSchema().rename(className, "Dogplace"); - realm.cancelTransaction(); - assertTrue(realm.getSchema().contains(className)); - realm.close(); + try { + realm.getSchema().rename(className, "Dogplace"); + fail(); + } catch (IllegalArgumentException ignored) { + } finally { + realm.cancelTransaction(); + assertTrue(realm.getSchema().contains(className)); + realm.close(); + } } @Test @@ -125,10 +131,14 @@ public void disallow_removeField() { String fieldName = "chars"; realm.beginTransaction(); assertTrue(realm.getSchema().get(className).hasField(fieldName)); - thrown.expect(IllegalArgumentException.class); - realm.getSchema().get(className).removeField(fieldName); - realm.cancelTransaction(); - realm.close(); + try { + realm.getSchema().get(className).removeField(fieldName); + fail(); + } catch (IllegalArgumentException ignored) { + } finally { + realm.cancelTransaction(); + realm.close(); + } } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java index 8b985da518..6bbb930b93 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java @@ -1,37 +1,55 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + package io.realm.objectserver; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.Locale; import java.util.UUID; +import io.realm.objectserver.internal.SyncUser; import io.realm.objectserver.internal.Token; public class SyncTestUtils { + public static String USER_TOKEN = UUID.randomUUID().toString(); + public static String REALM_TOKEN = UUID.randomUUID().toString(); + public static User createTestUser() { return createTestUser(Long.MAX_VALUE); } public static User createTestUser(long expires) { + Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); + Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); + SyncUser.AccessDescription desc = new SyncUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); + JSONObject obj = new JSONObject(); try { - JSONObject token = new JSONObject(); - token.put("token", UUID.randomUUID().toString()); - JSONObject tokenData = new JSONObject(); - JSONArray perms = new JSONArray(); // Grant all permissions - for (int i = 0; i < Token.Permission.values().length; i++) { - perms.put(Token.Permission.values()[i].toString().toLowerCase(Locale.US)); - } - tokenData.put("identity", UUID.randomUUID().toString()); - tokenData.put("path", null); - tokenData.put("expires", expires); - tokenData.put("access", perms); - token.put("token_data", tokenData); - obj.put("refreshToken", token); - obj.put("authUrl", "http://dummy.org/auth"); + JSONArray realmList = new JSONArray(); + JSONObject realmDesc = new JSONObject(); + realmDesc.put("uri", "realm://objectserver.realm.io/default"); + realmDesc.put("description", desc.toJson()); + realmList.put(realmDesc); + + obj.put("authUrl", "http://objectserver.realm.io/auth"); + obj.put("userToken", userToken.toJson()); + obj.put("realms", realmList); return User.fromJson(obj.toString()); } catch (JSONException e) { throw new RuntimeException(e); diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/UserTests.java b/realm/realm-library/src/androidTest/java/io/realm/objectserver/UserTests.java new file mode 100644 index 0000000000..ce5ae3552f --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/objectserver/UserTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static io.realm.objectserver.SyncTestUtils.createTestUser; +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class UserTests { + + @Test + public void toAndFromJson() { + User user1 = createTestUser(); + User user2 = User.fromJson(user1.toJson()); + assertEquals(user1, user2); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 0500421875..af9af702f9 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -39,6 +39,7 @@ import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; +import io.realm.internal.Util; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; @@ -546,36 +547,11 @@ public void deleteAll() { } } - static private boolean deletes(String canonicalPath, File rootFolder, String realmFileName) { - final AtomicBoolean realmDeleted = new AtomicBoolean(true); - - List filesToDelete = Arrays.asList( - new File(rootFolder, realmFileName), - new File(rootFolder, realmFileName + ".lock"), - // Old core log file naming styles - new File(rootFolder, realmFileName + ".log_a"), - new File(rootFolder, realmFileName + ".log_b"), - new File(rootFolder, realmFileName + ".log"), - new File(canonicalPath)); - for (File fileToDelete : filesToDelete) { - if (fileToDelete.exists()) { - boolean deleteResult = fileToDelete.delete(); - if (!deleteResult) { - realmDeleted.set(false); - RealmLog.warn("Could not delete the file %s", fileToDelete); - } - } - } - return realmDeleted.get(); - } - /** * Deletes the Realm file defined by the given configuration. */ static boolean deleteRealm(final RealmConfiguration configuration) { - final String management = ".management"; final AtomicBoolean realmDeleted = new AtomicBoolean(true); - RealmCache.invokeWithGlobalRefCount(configuration, new RealmCache.Callback() { @Override public void onResult(int count) { @@ -587,23 +563,9 @@ public void onResult(int count) { String canonicalPath = configuration.getPath(); File realmFolder = configuration.getRealmDirectory(); String realmFileName = configuration.getRealmFileName(); - File managementFolder = new File(realmFolder, realmFileName + management); - - // delete files in management directory and the directory - // there is no subfolders in the management directory - File[] files = managementFolder.listFiles(); - if (files != null) { - for (File file : files) { - realmDeleted.set(realmDeleted.get() && file.delete()); - } - } - realmDeleted.set(realmDeleted.get() && managementFolder.delete()); - - // delete specific files in root directory - realmDeleted.set(realmDeleted.get() && deletes(canonicalPath, realmFolder, realmFileName)); + realmDeleted.set(Util.deleteRealm(canonicalPath, realmFolder, realmFileName)); } }); - return realmDeleted.get(); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 2683c6eb12..4dab4c38fa 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -41,6 +41,7 @@ import java.util.Scanner; import java.util.Set; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; @@ -1538,6 +1539,35 @@ public static Object getDefaultModule() { } } + /** + * Returns the current number of open Realm instances across all threads that are using this configuration. + * This includes both dynamic and normal Realms. + * + * @param configuration the {@link io.realm.RealmConfiguration} for the Realm. + * @return number of open Realm instances across all threads. + */ + public static int getGlobalInstanceCount(RealmConfiguration configuration) { + final AtomicInteger globalCount = new AtomicInteger(0); + RealmCache.invokeWithGlobalRefCount(configuration, new RealmCache.Callback() { + @Override + public void onResult(int count) { + globalCount.set(count); + } + }); + return globalCount.get(); + } + + /** + * Returns the current number of open Realm instances on the thread calling this method. This include both + * dynamic and normal Realms. + * + * @param configuration the {@link io.realm.RealmConfiguration} for the Realm. + * @return number of open Realm instances across all threads. + */ + public static int getLocalInstanceCount(RealmConfiguration configuration) { + return RealmCache.getLocalThreadCount(configuration); + } + /** * Encapsulates a Realm transaction. *

      diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index a60ee2f1b8..0d7ec8c99d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -379,6 +379,19 @@ private static void copyAssetFileIfNeeded(RealmConfiguration configuration) { } } + static int getLocalThreadCount(RealmConfiguration configuration) { + RealmCache cache = cachesMap.get(configuration.getPath()); + if (cache == null) { + return 0; + } else { + int totalRefCount = 0; + for (RealmCacheType type : RealmCacheType.values()) { + totalRefCount += cache.refAndCountMap.get(type).localCount.get(); + } + return totalRefCount; + } + } + /** * Finds an entry for specified schema version in the array. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index d4178fc008..a611cf6378 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -18,11 +18,16 @@ import android.os.Build; +import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import io.realm.RealmModel; import io.realm.RealmObject; +import io.realm.log.RealmLog; public class Util { @@ -70,32 +75,6 @@ public static Class getOriginalModelClass(ClassGets the stack trace from a Throwable as a String.

      @@ -130,4 +109,46 @@ public static boolean isEmulator() { || "google_sdk".equals(Build.PRODUCT); } + public static boolean deleteRealm(String canonicalPath, File realmFolder, String realmFileName) { + boolean realmDeleted = true; + final String management = ".management"; + File managementFolder = new File(realmFolder, realmFileName + management); + + // delete files in management directory and the directory + // there is no subfolders in the management directory + File[] files = managementFolder.listFiles(); + if (files != null) { + for (File file : files) { + realmDeleted = realmDeleted && file.delete(); + } + } + realmDeleted = realmDeleted && managementFolder.delete(); + + // delete specific files in root directory + return realmDeleted && deletes(canonicalPath, realmFolder, realmFileName); + } + + private static boolean deletes(String canonicalPath, File rootFolder, String realmFileName) { + final AtomicBoolean realmDeleted = new AtomicBoolean(true); + + List filesToDelete = Arrays.asList( + new File(rootFolder, realmFileName), + new File(rootFolder, realmFileName + ".lock"), + // Old core log file naming styles + new File(rootFolder, realmFileName + ".log_a"), + new File(rootFolder, realmFileName + ".log_b"), + new File(rootFolder, realmFileName + ".log"), + new File(canonicalPath)); + for (File fileToDelete : filesToDelete) { + if (fileToDelete.exists()) { + boolean deleteResult = fileToDelete.delete(); + if (!deleteResult) { + realmDeleted.set(false); + RealmLog.warn("Could not delete the file %s", fileToDelete); + } + } + } + return realmDeleted.get(); + } + } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticationListener.java b/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticationListener.java new file mode 100644 index 0000000000..8a7934d286 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticationListener.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +/** + * Interface describing events related to Users and their authentication + */ +public interface AuthenticationListener { + /** + * A user was logged into the Object Server + * + * @param user {@link User} that is now logged in. + */ + void loggedIn(User user); + + /** + * A user was successfully logged out from the Object Server. + * + * @param user {@link User} that was successfully logged out. + */ + void loggedOut(User user); +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java index 263c080489..198d6cb4b3 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java @@ -28,7 +28,6 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmMigration; -import io.realm.objectserver.internal.SyncUtil; import io.realm.RealmModel; import io.realm.annotations.RealmModule; import io.realm.internal.RealmProxyMediator; @@ -38,8 +37,6 @@ import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; -import static io.realm.objectserver.internal.SyncUtil.getFullServerUrl; - /** * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm * Object Server. @@ -76,6 +73,7 @@ public final class SyncConfiguration extends RealmConfiguration { private final User user; private final SyncPolicy syncPolicy; private final Session.ErrorHandler errorHandler; + private final boolean deleteRealmOnLogout; private SyncConfiguration(File directory, String filename, @@ -93,7 +91,8 @@ private SyncConfiguration(File directory, User user, URI serverUrl, SyncPolicy syncPolicy, - Session.ErrorHandler errorHandler + Session.ErrorHandler errorHandler, + boolean deleteRealmOnLogout ) { super(directory, filename, @@ -114,6 +113,7 @@ private SyncConfiguration(File directory, this.serverUrl = serverUrl; this.syncPolicy = syncPolicy; this.errorHandler = errorHandler; + this.deleteRealmOnLogout = deleteRealmOnLogout; } @@ -146,19 +146,22 @@ public boolean equals(Object o) { SyncConfiguration that = (SyncConfiguration) o; - if (serverUrl != null ? !serverUrl.equals(that.serverUrl) : that.serverUrl != null) return false; - if (user != null ? !user.equals(that.user) : that.user != null) return false; - if (syncPolicy != null ? !syncPolicy.equals(that.syncPolicy) : that.syncPolicy != null) return false; - return errorHandler != null ? errorHandler.equals(that.errorHandler) : that.errorHandler == null; + if (deleteRealmOnLogout != that.deleteRealmOnLogout) return false; + if (!serverUrl.equals(that.serverUrl)) return false; + if (!user.equals(that.user)) return false; + if (!syncPolicy.equals(that.syncPolicy)) return false; + return errorHandler.equals(that.errorHandler); + } @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + (serverUrl != null ? serverUrl.hashCode() : 0); - result = 31 * result + (user != null ? user.hashCode() : 0); - result = 31 * result + (syncPolicy != null ? syncPolicy.hashCode() : 0); - result = 31 * result + (errorHandler != null ? errorHandler.hashCode() : 0); + result = 31 * result + serverUrl.hashCode(); + result = 31 * result + user.hashCode(); + result = 31 * result + syncPolicy.hashCode(); + result = 31 * result + errorHandler.hashCode(); + result = 31 * result + (deleteRealmOnLogout ? 1 : 0); return result; } @@ -193,7 +196,17 @@ public Session.ErrorHandler getErrorHandler() { } /** - * ReplicationConfiguration.Builder used to construct instances of a ReplicationConfiguration in a fluent manner. + * Returns {@code true} if the Realm file must be deleted once the {@link User} owning it logs out. + * + * @return {@code true} if the Realm file must be deleted if the {@link User} logs out. {@code false} if the file + * is allowed to remain behind. + */ + public boolean shouldDeleteRealmOnLogout() { + return deleteRealmOnLogout; + } + + /** + * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ public static final class Builder { @@ -214,6 +227,7 @@ public static final class Builder { private File defaultFolder; private String defaultLocalFileName; private SharedRealm.Durability durability = SharedRealm.Durability.FULL; + private boolean deleteRealmOnLogout = false; /** * Creates an instance of the Builder for the SyncConfiguration. @@ -411,8 +425,8 @@ public Builder user(User user) { if (user == null) { throw new IllegalArgumentException("Non-null `user` required."); } - if (!user.getSyncUser().isAuthenticated()) { - throw new IllegalArgumentException("User not authenticated or authentication expired. User ID: " + user.getIdentity()); + if (!user.isValid()) { + throw new IllegalArgumentException("User not authenticated or authentication expired."); } this.user = user; return this; @@ -449,6 +463,18 @@ public Builder errorHandler(Session.ErrorHandler errorHandler) { return this; } + /** + * Setting this will cause the local Realm file used to synchronize changes to be deleted if the {@link User} + * defined by {@link #user(User)} logs out from the device using {@link User#logout()}. + * + * The default behaviour is that the Realm file is allowed to stay behind, making it faster for users to log in + * again and have access to their data faster. + */ + public Builder deleteRealmOnLogout() { + this.deleteRealmOnLogout = true; + return this; + } + /** * Creates the RealmConfiguration based on the builder parameters. * @@ -499,7 +525,8 @@ public SyncConfiguration build() { user, resolvedServerUrl, syncPolicy, - errorHandler + errorHandler, + deleteRealmOnLogout ); } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java index 917e98cb7f..f3f16b25b2 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java @@ -17,11 +17,13 @@ package io.realm.objectserver; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import io.realm.internal.Keep; import io.realm.internal.RealmCore; +import io.realm.objectserver.android.SharedPrefsUserStore; import io.realm.objectserver.internal.SyncSession; import io.realm.objectserver.internal.SessionStore; import io.realm.objectserver.internal.network.AuthenticationServer; @@ -61,9 +63,14 @@ public void onError(Session session, ObjectServerError error) { } }; + private static CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList(); + // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. // Right now it just lives and dies together with the process. private static volatile AuthenticationServer authServer = new OkHttpAuthenticationServer(); + private static volatile UserStore userStore; // FIXME: Set to a default once we merge global init + + static volatile Session.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; @SuppressWarnings("FieldCanBeLocal") private static Thread clientThread; @@ -82,6 +89,45 @@ public void run() { clientThread.start(); } + /** + * Set the {@link UserStore} used by the Realm Object Server to save user information. + * If no Userstore is specified {@link User#currentUser()} will always return {@code null}. + * + * @param userStore {@link UserStore} to use. + */ + public static void setUserStore(UserStore userStore) { + if (userStore == null) { + throw new IllegalArgumentException("Non-null 'userStore' required."); + } + SyncManager.userStore = userStore; + } + + /** + * Sets a global authentication listener that will be notified about User events like + * login and logout. + * + * @param listener listener to register. + * @throws IllegalArgumentException if {@code listener} is {@code null}. + */ + public static void addAuthenticationListener(AuthenticationListener listener) { + if (listener == null) { + throw new IllegalArgumentException("Non-null 'listener' required."); + } + authListeners.add(listener); + } + + /** + * Removes the provided global authentication listener. + * + * @param listener listener to remove. + */ + public static void removeAuthenticationListener(AuthenticationListener listener) { + if (listener == null) { + return; + } + authListeners.remove(listener); + } + /** * Sets the default error handler used by all {@link SyncConfiguration} objects when they are created. * @@ -119,6 +165,8 @@ public static synchronized Session getSession(SyncConfiguration syncConfiguratio ); Session publicSession = new Session(internalSession); SessionStore.addSession(publicSession, internalSession); + syncConfiguration.getUser().getSyncUser().addSession(publicSession); + syncConfiguration.getSyncPolicy().onSessionCreated(internalSession); return publicSession; } } @@ -137,6 +185,12 @@ static void setAuthServerImpl(AuthenticationServer authServerImpl) { authServer = authServerImpl; } + + // Return the currently configured User store. + static UserStore getUserStore() { + return userStore; + } + // This is called from SyncManager.cpp from the worker thread the Sync Client is running on // Right now Core doesn't send these errors to the proper session, so instead we need to notify all sessions // from here. This can be removed once better error propagation is implemented in Sync Core. @@ -147,6 +201,20 @@ private static void notifyErrorHandler(int errorCode, String errorMessage) { } } + // Notify listeners that a user logged in + static void notifyUserLoggedIn(User user) { + for (AuthenticationListener authListener : authListeners) { + authListener.loggedIn(user); + } + } + + // Notify listeners that a user logged out successfully + static void notifyUserLoggedOut(User user) { + for (AuthenticationListener authListener : authListeners) { + authListener.loggedOut(user); + } + } + /** * Sets the log level for the underlying * @param logLevel @@ -159,4 +227,5 @@ public static void setLogLevel(int logLevel) { private static native void nativeInitializeSyncClient(); private static native void nativeSetSyncClientLogLevel(int logLevel); private static native void nativeRunClient(); + } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/User.java b/realm/realm-library/src/main/java/io/realm/objectserver/User.java index 3bea6c079f..f5d210843d 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/User.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/User.java @@ -19,21 +19,30 @@ import android.os.Handler; import android.os.Looper; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.io.File; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; +import java.util.Collection; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; +import io.realm.Realm; import io.realm.RealmAsyncTask; import io.realm.internal.IOException; +import io.realm.internal.Util; import io.realm.objectserver.internal.SyncUser; import io.realm.objectserver.internal.Token; import io.realm.objectserver.internal.network.AuthenticateResponse; import io.realm.objectserver.internal.network.AuthenticationServer; import io.realm.log.RealmLog; +import io.realm.objectserver.internal.network.ExponentialBackoffTask; +import io.realm.objectserver.internal.network.LogoutResponse; /** * This class represents a user on the Realm Object Server. @@ -47,6 +56,15 @@ private User(SyncUser user) { this.syncUser = user; } + /** + * Returns the last user that has logged in that hasn't logged out yet. + * + * @return last {@link User} that have logged in that is still valid. + */ + public static User currentUser() { + return SyncManager.getUserStore().get(UserStore.CURRENT_USER_KEY); + } + /** * Load a user that has previously been serialized using {@link #toJson()}. * @@ -58,14 +76,23 @@ private User(SyncUser user) { public static User fromJson(String user) { try { JSONObject obj = new JSONObject(user); - Token refreshToken = Token.from(obj.getJSONObject("refreshToken")); URL authUrl = new URL(obj.getString("authUrl")); - // FIXME: Add support for storing access tokens as well - return new User(new SyncUser(refreshToken.identity(), refreshToken, authUrl)); + Token refreshToken = Token.from(obj.getJSONObject("userToken")); + SyncUser syncUser = new SyncUser(refreshToken, authUrl); + JSONArray realmTokens = obj.getJSONArray("realms"); + for (int i = 0; i < realmTokens.length(); i++) { + JSONObject token = realmTokens.getJSONObject(i); + URI uri = new URI(token.getString("uri")); + SyncUser.AccessDescription realmDesc = SyncUser.AccessDescription.fromJson(token.getJSONObject("description")); + syncUser.addRealm(uri, realmDesc); + } + return new User(syncUser); } catch (JSONException e) { throw new IllegalArgumentException("Could not parse user json: " + user, e); } catch (MalformedURLException e) { throw new IllegalArgumentException("URL in JSON not valid: " + user, e); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("URI is not valid: " + user, e); } } @@ -91,9 +118,11 @@ public static User login(final Credentials credentials, final String authenticat try { AuthenticateResponse result = server.authenticateUser(credentials, authUrl); if (result.isValid()) { - SyncUser syncUser = new SyncUser(result.getRefreshToken().identity(), result.getRefreshToken(), authUrl); + SyncUser syncUser = new SyncUser(result.getRefreshToken(), authUrl); User user = new User(syncUser); RealmLog.info("Succeeded authenticating user.\n%s", user); + SyncManager.getUserStore().put(UserStore.CURRENT_USER_KEY, user); + SyncManager.notifyUserLoggedIn(user); return user; } else { RealmLog.info("Failed authenticating user.\n%s", result.getError()); @@ -159,9 +188,78 @@ public void run() { return new RealmAsyncTask(authenticateRequest, networkPoolExecutor); } + /** + * Log the user out of the Object Server. Once the Object Server has confirmed the logout any registered + * {@link AuthenticationListener} will be notified and user credentials will be deleted from this device. + *

      + * Any Realms owned by the user will be deleted if {@link SyncConfiguration.Builder#deleteRealmOnLogout()} is + * also set. + * + * @throws IllegalStateException if any Realms owned by this user is still open. They should be closed before + * logging out. + */ public void logout() { - // TODO Stop any session - // TODO Clear all tokens + // Acquire lock to prevent users creating new instances + synchronized (Realm.class) { + if (!syncUser.isLoggedIn()) { + return; // Already logged out + } + + // Ensure that we can log out. If any Realm file is still open we should abort before doing anything + // else. + Collection sessions = syncUser.getSessions(); + for (Session session : sessions) { + SyncConfiguration config = session.getConfiguration(); + if (Realm.getGlobalInstanceCount(config) > 0) { + throw new IllegalStateException("A Realm controlled by this user is still open. Close all Realms " + + "before logging out: " + config.getPath()); + } + } + + // Stop all active sessions immediately. If we waited until after talking to the server + // there is a high chance errors would be reported from the Sync Client first which would + // be confusing. + for (Session session : sessions) { + session.getSyncSession().stop(); + } + + final AuthenticationServer server = SyncManager.getAuthServer(); + ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; + networkPoolExecutor.submit(new ExponentialBackoffTask() { + + @Override + protected LogoutResponse execute() { + return server.logout(User.this, syncUser.getAuthenticationUrl()); + } + + @Override + protected void onSuccess(LogoutResponse response) { + // Remove all local tokens, preventing further connections. + syncUser.clearTokens(); + + if (User.this.equals(User.currentUser())) { + SyncManager.getUserStore().remove(UserStore.CURRENT_USER_KEY); + } + + // Delete all Realms if needed. + for (SyncUser.AccessDescription desc : syncUser.getRealms()) { + if (desc.deleteOnLogout) { + File realmFile = new File(desc.localPath); + if (realmFile.exists() && !Util.deleteRealm(desc.localPath, realmFile.getParentFile(), realmFile.getName())) { + RealmLog.error("Could not delete Realm when user logged out: " + desc.localPath); + } + } + } + + SyncManager.notifyUserLoggedOut(User.this); + } + + @Override + protected void onError(LogoutResponse response) { + RealmLog.error("Failed to log user out.\n" + response.getError().toString()); + } + }); + } } /** @@ -179,6 +277,22 @@ public String toJson() { return syncUser.toJson(); } + /** + * Returns {@code true} if the user is logged into the Realm Object Server. If this method returns {@code true} it + * means that the user has valid credentials that have not expired. + *

      + * The user might still be have been logged out by the Realm Object Server which will not be detected before the + * user tries to actively synchronize a Realm. If a logged out user tries to synchronize a Realm, an error will be + * reported to the {@link io.realm.objectserver.Session.ErrorHandler} defined by + * {@link io.realm.objectserver.SyncConfiguration.Builder#errorHandler(Session.ErrorHandler)}. + * + * @return {@code true} if the User is logged into the Realm Object Server, {@code false} otherwise. + */ + public boolean isValid() { + Token userToken = getSyncUser().getUserToken(); + return syncUser.isLoggedIn() && userToken != null && userToken.expiresMs() > System.currentTimeMillis(); + } + /** * Returns the identity of this user on the Realm Object Server. The identity is a guaranteed to be unique * among all users on the Realm Object Server. @@ -197,7 +311,24 @@ public String getIdentity() { * @return The user's access token. If this user has logged out or the login has expired {@code null} is returned. */ public String getAccessToken() { - return syncUser.getRefreshToken().value(); + Token userToken = syncUser.getUserToken(); + return (userToken != null) ? userToken.value() : null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + User user = (User) o; + + return syncUser.equals(user.syncUser); + + } + + @Override + public int hashCode() { + return syncUser.hashCode(); } // Expose internal representation for other package protected classes diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java b/realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java index 96a21e91ae..1904bf1e9f 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java @@ -16,63 +16,52 @@ package io.realm.objectserver; +import java.util.Collection; + import io.realm.objectserver.android.SharedPrefsUserStore; /** - * Interface for describing how a given user object can be persisted and retrieved again. + * Interface for classes responsible for saving and retrieving Object Server users again. + *

      + * Any implementation of a User Store is expected to not perform lengthy blocking operations as it might + * be called on the Main Thread. All implementations of this interface should be thread safe. * + * @see SyncManager#setUserStore(UserStore) * @see SharedPrefsUserStore */ public interface UserStore { - /** - * Saves a User object under the given key. If another user already exists, it will be replaced. - * - * @param key Key used to store the User. The same key is used to retrieve it again - * @param user User object to store. - */ - boolean save(String key, User user); + String CURRENT_USER_KEY = "realm$currentUser"; /** - * Saves a User object under the given key. If another user already exists, it will be replaced. + * Saves a {@link User} object under the given key. If another user already exists, it will be replaced. + * + * @param key key used to store the User. + * @param user {@link User} object to store. + * @return The previous user saved with this key or {@code null} if no user was replaced. * - * @param key - * @param user - */ - void saveAsync(String key, User user); - - /** - * TODO - * @param key - * @param user */ - void saveASync(String key, User user, Callback callback); + User put(String key, User user); /** - * TODO - * @param key + * Retrieves the {@link User} with the given key. + * + * @param key {@link User} saved under the given key or {@code null} if no user exists for that key. */ - User load(String key); + User get(String key); /** - * TODO - * @param key + * Removes the user with the given key from the store. + * + * @param key key for the user to remove. + * @return {@link User} that was removed or {@code null} if no user matched the key. */ - void loadAsync(String key, Callback callback); - + User remove(String key); /** - * Interface responsible for handling the result of asynchronously saving or loading the user. + * Returns a collection of all users saved in the User store. + * + * @return Collection of all users. If no users exist, an empty collection is returned. */ - interface Callback { - /** - * User was successfully saved or loaded. - */ - void onSuccess(User user); - - /** - * The user could not be saved or loaded. - */ - void onError(Throwable t); - } + Collection allUsers(); } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java b/realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java index 7d89453a96..2b06c351d4 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java @@ -18,14 +18,11 @@ import android.content.Context; import android.content.SharedPreferences; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import java.util.concurrent.Executor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; -import io.realm.log.RealmLog; import io.realm.objectserver.User; import io.realm.objectserver.UserStore; @@ -34,18 +31,8 @@ */ public class SharedPrefsUserStore implements UserStore { - public static final Executor THREAD_POOL; private final SharedPreferences sp; - private Handler handler = new Handler(Looper.getMainLooper()); - - static { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - THREAD_POOL = AsyncTask.THREAD_POOL_EXECUTOR; - } else { - throw new UnsupportedOperationException("FIXME: Not supported yet. Realm.asyncTaskExecutor must be public first"); - // THREAD_POOL = Realm.asyncTaskExecutor; // FIXME Do this better - } - } + private User cachedCurrentUser; // Keep a quick reference to the current user public SharedPrefsUserStore(Context context) { sp = context.getSharedPreferences("realm_object_server_users", Context.MODE_PRIVATE); @@ -55,104 +42,76 @@ public SharedPrefsUserStore(Context context) { * {@inheritDoc} */ @Override - public boolean save(String key, User user) { + public User put(String key, User user) { + String previousUser = sp.getString(key, null); SharedPreferences.Editor editor = sp.edit(); editor.putString(key, user.toJson()); - return editor.commit(); - } + // Optimistically save. If the user isn't saved due to a process crash it isn't dangerous. + editor.apply(); - /** - * {@inheritDoc} - */ - @Override - public void saveAsync(String key, User user) { - saveASync(key, user, null); + if (UserStore.CURRENT_USER_KEY.equals(key)) { + cachedCurrentUser = user; + } + + if (previousUser != null) { + return User.fromJson(previousUser); + } else { + return null; + } } /** * {@inheritDoc} */ @Override - public void saveASync(final String key, final User user, final Callback callback) { - THREAD_POOL.execute(new Runnable() { - @Override - public void run() { - boolean success; - Throwable error = null; - try { - success = save(key, user); - if (!success) { - error = new RuntimeException("Could not save key"); - } - } catch (Exception e) { - success = false; - error = e; - RealmLog.error("Failed to save user", e); - } - if (callback != null) { - final boolean finalSuccess = success; - final Throwable finalError = error; - handler.post(new Runnable() { - @Override - public void run() { - if (finalSuccess) { - callback.onSuccess(user); - } else { - callback.onError(finalError); - } - } - }); - } - } - }); + public User get(String key) { + if (key == UserStore.CURRENT_USER_KEY && cachedCurrentUser != null) { + return cachedCurrentUser; + } + + String userData = sp.getString(key, ""); + if (userData.equals("")) { + return null; + } + + User user = User.fromJson(userData); + if (UserStore.CURRENT_USER_KEY.equals(key)) { + cachedCurrentUser = user; + } + return user; } /** * {@inheritDoc} */ @Override - public User load(String key) { - String userData = sp.getString(key, ""); - if (userData.equals("")) { + public User remove(String key) { + String currentUser = sp.getString(key, null); + SharedPreferences.Editor editor = sp.edit(); + editor.putString(key, null); + editor.apply(); + + if (UserStore.CURRENT_USER_KEY.equals(key) && cachedCurrentUser != null) { + cachedCurrentUser = null; + } + + if (currentUser != null) { + return User.fromJson(currentUser); + } else { return null; } - return User.fromJson(userData); } /** * {@inheritDoc} */ @Override - public void loadAsync(final String key, final Callback callback) { - THREAD_POOL.execute(new Runnable() { - @Override - public void run() { - User user = null; - Throwable error = null; - try { - user = load(key); - if (user == null) { - error = new RuntimeException("Could not load user:" + key); - } - } catch (Exception e) { - error = e; - RealmLog.error("Failed to save user", e); - } - if (callback != null) { - final User finalUser = user; - final Throwable finalError = error; - handler.post(new Runnable() { - @Override - public void run() { - if (finalUser != null) { - callback.onSuccess(finalUser); - } else { - callback.onError(finalError); - } - } - }); - } - } - }); + public Collection allUsers() { + Map all = sp.getAll(); + ArrayList users = new ArrayList(all.size()); + for (Object userJson : all.values()) { + users.add(User.fromJson((String) userJson)); + } + return users; } } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java index 85db11b919..01551b1266 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java @@ -19,11 +19,9 @@ import java.net.URI; import java.util.HashMap; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import io.realm.RealmAsyncTask; import io.realm.internal.Keep; -import io.realm.internal.Util; import io.realm.log.RealmLog; import io.realm.objectserver.ErrorCode; import io.realm.objectserver.ObjectServerError; @@ -34,6 +32,7 @@ import io.realm.objectserver.User; import io.realm.objectserver.internal.network.AuthenticateResponse; import io.realm.objectserver.internal.network.AuthenticationServer; +import io.realm.objectserver.internal.network.ExponentialBackoffTask; import io.realm.objectserver.internal.network.NetworkStateReceiver; import io.realm.objectserver.internal.syncpolicy.SyncPolicy; @@ -90,8 +89,6 @@ @Keep public final class SyncSession { - private static final long MAX_DELAY_MS = TimeUnit.MINUTES.toMillis(5); - private final HashMap FSM = new HashMap(); // Variables used by the FSM @@ -256,55 +253,38 @@ void authenticateRealm(final Runnable onSuccess, final Session.ErrorHandler erro networkRequest.cancel(); } // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { @Override - public void run() { - int attempt = 0; - boolean success; - ObjectServerError error = null; - while (true) { - attempt++; - long sleep = Util.calculateExponentialDelay(attempt - 1, MAX_DELAY_MS); - if (sleep > 0) { - try { - Thread.sleep(sleep); - } catch (InterruptedException e) { - return; // Abort authentication if interrupted. - } - } - - AuthenticateResponse response = authServer.authenticateRealm( - user.getRefreshToken(), - configuration.getServerUrl(), - user.getAuthenticationUrl() - ); - if (response.isValid()) { - user.addAccessToken(configuration.getServerUrl(), response.getAccessToken()); - success = true; - break; - } else { - // Only retry in case of IO exceptions, since that might be network timeouts etc. - // All other errors indicate a bigger problem, so stop trying to authenticate and - // unbind - ObjectServerError responseError = response.getError(); - if (responseError.getErrorCode() != ErrorCode.IO_EXCEPTION) { - success = false; - error = responseError; - break; - } - } - } - - if (success) { - onSuccess.run(); - } else { - errorHandler.onError(getUserSession(), error); - } + protected AuthenticateResponse execute() { + return authServer.authenticateRealm( + user.getUserToken(), + configuration.getServerUrl(), + user.getAuthenticationUrl() + ); + } + + @Override + protected void onSuccess(AuthenticateResponse response) { + SyncUser.AccessDescription desc = new SyncUser.AccessDescription( + response.getAccessToken(), + configuration.getPath(), + configuration.shouldDeleteRealmOnLogout() + ); + user.addRealm(configuration.getServerUrl(), desc); + onSuccess.run(); + } + + @Override + protected void onError(AuthenticateResponse response) { + errorHandler.onError(getUserSession(), response.getError()); } }); networkRequest = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); } + /** + * Checks if a user has valid credentials for accessing this Realm. + */ boolean isAuthenticated(SyncConfiguration configuration) { return user.isAuthenticated(configuration); } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java index b1e5c24467..b09540bb5f 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java @@ -16,25 +16,27 @@ import android.os.SystemClock; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.net.URI; import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import io.realm.RealmAsyncTask; -import io.realm.internal.IOException; -import io.realm.internal.Util; -import io.realm.log.RealmLog; -import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.Session; import io.realm.objectserver.SyncConfiguration; import io.realm.objectserver.SyncManager; import io.realm.objectserver.User; import io.realm.objectserver.internal.network.AuthenticationServer; +import io.realm.objectserver.internal.network.ExponentialBackoffTask; import io.realm.objectserver.internal.network.RefreshResponse; /** @@ -43,92 +45,59 @@ */ public class SyncUser { - // Time left on current refresh token, when we want to begin refreshing it. - // Failing to refresh it before it expires, will result in the user getting logged out. - private RealmAsyncTask refreshTask; + // Time left on current refresh token, before we want to begin refreshing it. + // Failing to refresh it before it expires, will result in the user no longer being valid, and not being able + // to synchronize changes. It will still be possible to open Realms and read their data. + private final long REFRESH_WINDOW_MS = TimeUnit.SECONDS.toMillis(5); - private final String identifier; + private final String identity; private Token refreshToken; - private URL authentificationUrl; - private Map accessTokens = new HashMap(); + private URL authenticationUrl; + private Map realms = new HashMap(); + private List sessions = new ArrayList(); + private RealmAsyncTask refreshTask; + private boolean loggedIn; /** * Create a new Realm Object Server User */ - public SyncUser(String identifier, Token refreshToken, URL authenticationUrl) { - this.identifier = identifier; - this.authentificationUrl = authenticationUrl; + public SyncUser(Token refreshToken, URL authenticationUrl) { + this.identity = refreshToken.identity(); + this.authenticationUrl = authenticationUrl; setRefreshToken(refreshToken); + this.loggedIn = true; } public void setRefreshToken(final Token refreshToken) { - if (refreshTask != null) { - refreshTask.cancel(); - refreshTask = null; - } - this.refreshToken = refreshToken; + this.refreshToken = refreshToken; // Replace any existing token. TODO re-save the user with latest token. - if (authentificationUrl == null) { - return; - } // Schedule a refresh. This method cannot fail, but will continue retrying until either the app is killed // or the attempt was successful. - // TODO Consider combining refresh across all users? final long expire = refreshToken.expiresMs(); final AuthenticationServer server = SyncManager.getAuthServer(); - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new Runnable() { + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { @Override - public void run() { + protected RefreshResponse execute() { long timeToExpiration = System.currentTimeMillis() - expire; - if (timeToExpiration > 0) { + if (timeToExpiration - REFRESH_WINDOW_MS > 0) { SystemClock.sleep(timeToExpiration); } + return server.refresh(refreshToken.value(), authenticationUrl); + } + + @Override + protected void onSuccess(RefreshResponse response) { + setRefreshToken(response.getRefreshToken()); + } + + @Override + protected void onError(RefreshResponse response) { - int attempt = 0; - while (!Thread.interrupted()) { - attempt++; - long sleep = Util.calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); - if (sleep > 0) { - try { - Thread.sleep(sleep); - } catch (InterruptedException e) { - return; // Abort authentication if interrupted. - } - } - try { - RefreshResponse result = server.refresh(refreshToken.value(), authentificationUrl); - if (result.isValid()) { - setRefreshToken(result.getRefreshToken()); - break; - } else { - // FIXME: Log to session events instead - RealmLog.warn("Refreshing login failed: " + result.getErrorCode() + " : " + result.getErrorMessage()); - } - } catch (IOException e) { - // FIXME: Log to session events instead. - RealmLog.info("Refreshing login failed: " + e.toString()); - } - } } }); refreshTask = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); } - /** - * Returns {@code true} if the user is logged into the Realm Object Server. If this method returns {@code true it - * means that the user has valid credentials that have not expired. - *

      - * The user might still be logged out by the Realm Object Server which will not be detected before the user - * tries to actively synchronize a Realm. If a logged out user tries to synchronize a Realm, errors will be reported - * to the {@link io.realm.objectserver.Session.ErrorHandler} defined by - * {@link io.realm.objectserver.SyncConfiguration.Builder#errorHandler}. - * - * @return {@code true} if the User is considered logged into the Realm Object Server, {@code false} otherwise. - */ - public boolean isAuthenticated() { - return refreshToken != null && refreshToken.expiresMs() > System.currentTimeMillis(); - } - /** * Checks if the user has access to the given Realm. Being authenticated means that the * user is know by the Realm Object Server and have been granted access to the given Realm. @@ -140,18 +109,19 @@ public boolean isAuthenticated(SyncConfiguration configuration) { return token != null && token.expiresMs() > System.currentTimeMillis(); } - public void logout() { - // TODO Stop any session - // TODO Clear all tokens - } - public String toJson() { JSONObject obj = new JSONObject(); try { - obj.put("identifier", identifier); - obj.put("refreshToken", refreshToken.toJson()); - obj.put("authUrl", authentificationUrl); - // FIXME: Add support for storing access tokens as well + obj.put("authUrl", authenticationUrl); + obj.put("userToken", refreshToken.toJson()); + JSONArray realmList = new JSONArray(); + for (Map.Entry entry : realms.entrySet()) { + JSONObject token = new JSONObject(); + token.put("uri", entry.getKey().toString()); + token.put("description", entry.getValue().toJson()); + realmList.put(token); + } + obj.put("realms", realmList); return obj.toString(); } catch (JSONException e) { throw new RuntimeException("Could not convert User to JSON", e); @@ -159,15 +129,21 @@ public String toJson() { } public String getIdentity() { - return identifier; + return identity; } public Token getAccessToken(URI serverUrl) { - return accessTokens.get(serverUrl); + AccessDescription accessDescription = realms.get(serverUrl); + return (accessDescription != null) ? accessDescription.accessToken : null; } - public void addAccessToken(URI uri, Token accessToken) { - accessTokens.put(uri, accessToken); + public void addRealm(URI uri, AccessDescription description) { + realms.put(uri, description); + } + + // When a session is started, add it to the user so it can be tracked + public void addSession(Session session) { + sessions.add(session); } /** @@ -180,30 +156,131 @@ public void addAccessToken(URI uri, Token accessToken) { * @param uri {@link java.net.URI} pointing to a remote Realm. * @param accessToken */ - public void addAccessToken(URI uri, String accessToken) { - // TODO Currently package protected as we will be unifying the tokens shortly, so each user only has one - // access token that can be used everywhere. Permissions/access are then fully handled by the Object Server. + public void addRealm(URI uri, String accessToken, String localPath, boolean deleteOnLogout) { if (uri == null || accessToken == null) { throw new IllegalArgumentException("Non-null 'uri' and 'accessToken' required."); } - uri = SyncUtil.getFullServerUrl(uri, identifier); + uri = SyncUtil.getFullServerUrl(uri, identity); // Optimistically create a long-lived token with all permissions. If this is incorrect the Object Server // will reject it anyway. If tokens are added manually it is up to the user to ensure they are also used // correctly. - addAccessToken(uri, new Token(accessToken, null, uri.toString(), Long.MAX_VALUE, Token.Permission.values())); + Token token = new Token(accessToken, null, uri.toString(), Long.MAX_VALUE, Token.Permission.values()); + addRealm(uri, new AccessDescription(token, localPath, deleteOnLogout)); } - URL getAuthenticationUrl() { - return authentificationUrl; + public URL getAuthenticationUrl() { + return authenticationUrl; } - public Token getRefreshToken() { + public Token getUserToken() { return refreshToken; } - public interface Callback { - void onSuccess(User user); - void onError(ObjectServerError error); + public List getSessions() { + return sessions; + } + + public void clearTokens() { + realms.clear(); + refreshToken = null; + if (refreshTask != null) { + refreshTask.cancel(); + refreshTask = null; + } + } + + public boolean isLoggedIn() { + return loggedIn; + } + + // Local Logout means that the user is no longer able to create new sync configurations, + // nor synchronize changes + public void localLogout() { + loggedIn = false; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SyncUser syncUser = (SyncUser) o; + + if (!identity.equals(syncUser.identity)) return false; + if (!refreshToken.equals(syncUser.refreshToken)) return false; + if (!authenticationUrl.toString().equals(syncUser.authenticationUrl.toString())) return false; + return realms.equals(syncUser.realms); + + } + + @Override + public int hashCode() { + int result = identity.hashCode(); + result = 31 * result + refreshToken.hashCode(); + result = 31 * result + authenticationUrl.toString().hashCode(); + result = 31 * result + realms.hashCode(); + return result; + } + + public Collection getRealms() { + return realms.values(); + } + + // Wrapper for all Realm data needed by a User that might get serialized. + public static class AccessDescription { + public Token accessToken; + public String localPath; + public boolean deleteOnLogout; + + public AccessDescription(Token accessToken, String localPath, boolean deleteOnLogout) { + this.accessToken = accessToken; + this.localPath = localPath; + this.deleteOnLogout = deleteOnLogout; + } + + public static AccessDescription fromJson(JSONObject json) { + try { + Token token = Token.from(json.getJSONObject("accessToken")); + String localPath = json.getString("localPath"); + boolean deleteOnLogout = json.getBoolean("deleteOnLogout"); + return new AccessDescription(token, localPath, deleteOnLogout); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public JSONObject toJson() { + try { + JSONObject obj = new JSONObject(); + obj.put("accessToken", accessToken.toJson()); + obj.put("localPath", localPath); + obj.put("deleteOnLogout", deleteOnLogout); + return obj; + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AccessDescription that = (AccessDescription) o; + + if (deleteOnLogout != that.deleteOnLogout) return false; + if (!accessToken.equals(that.accessToken)) return false; + return localPath.equals(that.localPath); + + } + + @Override + public int hashCode() { + int result = accessToken.hashCode(); + result = 31 * result + localPath.hashCode(); + result = 31 * result + (deleteOnLogout ? 1 : 0); + return result; + } } } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java index 7814773d91..ca1d3ae3af 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java @@ -63,7 +63,11 @@ public Token(String value, String identity, String path, long expiresSec, Permis this.identity = identity; this.path = path; this.expiresSec = expiresSec; - this.permissions = Arrays.copyOf(permissions, permissions.length); + if (permissions != null) { + this.permissions = Arrays.copyOf(permissions, permissions.length); + } else { + this.permissions = new Permission[0]; + } } public String value() { @@ -97,22 +101,50 @@ public Permission[] permissions() { return Arrays.copyOf(permissions, permissions.length); } - public String toJson() { + public JSONObject toJson() { JSONObject obj = new JSONObject(); try { obj.put("token", value); - obj.put("expires", expiresSec); + JSONObject tokenData = new JSONObject(); + tokenData.put("identity", identity); + tokenData.put("path", path); + tokenData.put("expires", expiresSec); JSONArray perms = new JSONArray(); for (int i = 0; i < permissions.length; i++) { perms.put(permissions[i].toString().toLowerCase(Locale.US)); } - obj.put("access", perms); - return obj.toString(); + tokenData.put("access", perms); + obj.put("token_data", tokenData); + return obj; } catch (JSONException e) { throw new RuntimeException("Could not convert Token to JSON.", e); } } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Token token = (Token) o; + + if (expiresSec != token.expiresSec) return false; + if (!value.equals(token.value)) return false; + if (!Arrays.equals(permissions, token.permissions)) return false; + if (!identity.equals(token.identity)) return false; + return path != null ? path.equals(token.path) : token.path == null; + } + + @Override + public int hashCode() { + int result = value.hashCode(); + result = 31 * result + (int) (expiresSec ^ (expiresSec >>> 32)); + result = 31 * result + Arrays.hashCode(permissions); + result = 31 * result + identity.hashCode(); + result = 31 * result + (path != null ? path.hashCode() : 0); + return result; + } + public enum Permission { UNKNOWN, UPLOAD, diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthServerResponse.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthServerResponse.java new file mode 100644 index 0000000000..d38b73a447 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthServerResponse.java @@ -0,0 +1,64 @@ +package io.realm.objectserver.internal.network; +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +import org.json.JSONException; +import org.json.JSONObject; + +import io.realm.objectserver.ErrorCode; +import io.realm.objectserver.ObjectServerError; + +/** + * Base class for all response types from the Realm Authentication Server. + */ +public class AuthServerResponse { + + protected ObjectServerError error; + + /** + * Checks if this response was valid. + */ + public boolean isValid() { + return (error == null); + } + + /** + * If {@link #isValid()} returns {@code false}, this method must return the error causing this. + */ + public ObjectServerError getError() { + return error; + } + + protected void setError(ObjectServerError error) { + this.error = error; + } + + // Parse an http error form the Auth server. + // The server returns errors following https://tools.ietf.org/html/rfc7807 with an extra "code" field + // for Realm specific error codes. + public static ObjectServerError createError(String response, int httpErrorCode) { + try { + JSONObject obj = new JSONObject(response); + String title = obj.optString("title", null); + String hint = obj.optString("hint", null); + ErrorCode errorCode = ErrorCode.fromInt(obj.optInt("code", -1)); + return new ObjectServerError(errorCode, title, hint); + } catch (JSONException e) { + return new ObjectServerError(ErrorCode.JSON_EXCEPTION, "Server failed with " + + httpErrorCode + ", but could not parse error.", e); + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java index 0455ac4049..c610b747d3 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java @@ -30,12 +30,11 @@ /** * This class represents the response for a authenticate request. */ -public class AuthenticateResponse { +public class AuthenticateResponse extends AuthServerResponse { private static final String JSON_FIELD_ACCESS_TOKEN = "access_token"; private static final String JSON_FIELD_REFRESH_TOKEN = "refresh_token"; - private final ObjectServerError error; private final Token accessToken; private final Token refreshToken; @@ -53,18 +52,7 @@ static AuthenticateResponse createFrom(Response response) { } RealmLog.debug("Authenticate response: " + serverResponse); if (response.code() != 200) { - try { - JSONObject obj = new JSONObject(serverResponse); - String hint = obj.optString("hint", null); - String title = obj.optString("title", null); - ErrorCode errorCode = ErrorCode.fromInt(obj.optInt("code", -1)); - ObjectServerError error = new ObjectServerError(errorCode, title, hint); - return new AuthenticateResponse(error); - } catch (JSONException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, "Server failed with " + - response.code() + ", but could not parse error.", e); - return new AuthenticateResponse(error); - } + return new AuthenticateResponse(AuthServerResponse.createError(serverResponse, response.code())); } else { return new AuthenticateResponse(serverResponse); } @@ -74,7 +62,7 @@ static AuthenticateResponse createFrom(Response response) { * Creates a unsuccessful authentication response. This should only happen in case of network / IO problems. */ AuthenticateResponse(ObjectServerError error) { - this.error = error; + setError(error); this.accessToken = null; this.refreshToken = null; } @@ -101,17 +89,9 @@ private AuthenticateResponse(String serverResponse) { error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, ex); } + setError(error); this.accessToken = accessToken; this.refreshToken = refreshToken; - this.error = error; - } - - public boolean isValid() { - return (error == null); - } - - public ObjectServerError getError() { - return error; } public Token getAccessToken() { diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java index 58594e2507..4740b8e9af 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java @@ -19,11 +19,12 @@ import java.net.URI; import java.net.URL; +import io.realm.objectserver.User; import io.realm.objectserver.internal.Token; import io.realm.objectserver.Credentials; /** - * Interface for handling communication with the Realm Object Server. + * Interface for handling communication with Realm Object Servers. * * Note, any implementation of this class is not responsible for handling retries or error handling, it is * only responsible for executing a given network request. @@ -32,4 +33,5 @@ public interface AuthenticationServer { AuthenticateResponse authenticateUser(Credentials credentials, URL authenticationUrl); AuthenticateResponse authenticateRealm(Token refreshToken, URI path, URL authenticationUrl); RefreshResponse refresh(String token, URL authenticationUrl); + LogoutResponse logout(User user, URL authenticationUrl); } diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/ExponentialBackoffTask.java new file mode 100644 index 0000000000..59858aaf72 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/ExponentialBackoffTask.java @@ -0,0 +1,105 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.network; + +import java.util.concurrent.TimeUnit; + +import io.realm.objectserver.ErrorCode; + +/** + * Abstracts the concept of running an network task with incremental backoff. It will run forever until interrupted. + */ +public abstract class ExponentialBackoffTask implements Runnable { + + // Task to perform + protected abstract T execute(); + + // Check if the task was successful + protected boolean isSuccess(T result) { + return result.isValid(); + } + + // Return true if based on the task result that this task will never complete + protected boolean shouldAbortTask(T response) { + // Only retry in case of IO exceptions, since that might be network timeouts etc. + // All other errors indicate a bigger problem, so just stop the task. + if (!response.isValid()) { + return response.getError().getErrorCode() != ErrorCode.IO_EXCEPTION; + } else { + return false; + } + } + + // Callback when task is have succeeded + protected abstract void onSuccess(T response); + + // Callback when task has failed + protected abstract void onError(T response); + + @Override + public void run() { + int attempt = 0; + while (true) { + attempt++; + long sleep = calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + return; // Abort if interrupted + } + } + T response = execute(); + + if (isSuccess(response)) { + onSuccess(response); + break; + } else { + if (shouldAbortTask(response)) { + onError(response); + break; + } + } + } + } + + private static long calculateExponentialDelay(int failedAttempts, long maxDelayInMs) { + // https://en.wikipedia.org/wiki/Exponential_backoff + //Attempt = FailedAttempts + 1 + //Attempt 1 0s 0s + //Attempt 2 2s 2s + //Attempt 3 4s 4s + //Attempt 4 8s 8s + //Attempt 5 16s 16s + //Attempt 6 32s 32s + //Attempt 7 64s 1m 4s + //Attempt 8 128s 2m 8s + //Attempt 9 256s 4m 16s + //Attempt 10 512 8m 32s + //Attempt 11 1024 17m 4s + //Attempt 12 2048 34m 8s + //Attempt 13 4096 1h 8m 16s + //Attempt 14 8192 2h 16m 32s + //Attempt 15 16384 4h 33m 4s + double SCALE = 1.0D; // Scale the exponential backoff + double delayInMs = ((Math.pow(2.0D, failedAttempts) - 1d) / 2.0D) * 1000 * SCALE; + + // Just use maximum back-off value. We are not afraid of many threads using this value + // to trigger at once. + return maxDelayInMs < delayInMs ? maxDelayInMs : (long) delayInMs; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutRequest.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutRequest.java new file mode 100644 index 0000000000..1f62755ff8 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutRequest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.network; + +import io.realm.objectserver.User; + +/** + * This class encapsulates a request to logout a user on the Realm Authentication Server. It is responsible for + * constructing the JSON understood by the Realm Authentication Server. + */ +public class LogoutRequest { + // TODO Endpoint not finished yet + + LogoutRequest fromUser(User user) { + return new LogoutRequest(); + } + +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutResponse.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutResponse.java new file mode 100644 index 0000000000..d723f47289 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutResponse.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import io.realm.log.RealmLog; +import io.realm.objectserver.ErrorCode; +import io.realm.objectserver.ObjectServerError; +import io.realm.objectserver.internal.Token; +import okhttp3.Response; + +/** + * This class represents the response for a logout request. + */ +public class LogoutResponse extends AuthServerResponse { + + private final ObjectServerError error; + + /** + * Helper method for creating the proper Authenticate response. This method will set the appropriate error + * depending on any HTTP response codes or IO errors. + */ + static LogoutResponse createFrom(Response response) { + String serverResponse; + try { + serverResponse = response.body().string(); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new LogoutResponse(error); + } + RealmLog.debug("Authenticate response: " + serverResponse); + if (response.code() != 200) { + return new LogoutResponse(AuthServerResponse.createError(serverResponse, response.code())); + } else { + return new LogoutResponse(serverResponse); + } + } + + /** + * Creates a unsuccessful authentication response. This should only happen in case of network / IO problems. + */ + private LogoutResponse(ObjectServerError error) { + this.error = error; + } + + /** + * Parses a valid (200) server response. + */ + private LogoutResponse(String serverResponse) { + this.error = null; + // TODO endpoint not finalized + } + + public boolean isValid() { +// return (error == null); + return true; + } + + public ObjectServerError getError() { + return error; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java index 6a4ba6a4ba..113ee969d5 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java @@ -22,6 +22,7 @@ import io.realm.internal.Util; import io.realm.objectserver.ErrorCode; +import io.realm.objectserver.User; import io.realm.objectserver.internal.Token; import io.realm.objectserver.Credentials; import io.realm.objectserver.ObjectServerError; @@ -70,6 +71,11 @@ public RefreshResponse refresh(String token, URL authenticationUrl) { throw new UnsupportedOperationException("FIXME"); } + @Override + public LogoutResponse logout(User user, URL authenticationUrl) { + throw new UnsupportedOperationException("FIXME"); + } + private AuthenticateResponse authenticate(URL authenticationUrl, String requestBody) throws Exception { Request request = new Request.Builder() .url(authenticationUrl) diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java index 56f98f5f32..0eaf52c04d 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java +++ b/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java @@ -16,33 +16,16 @@ package io.realm.objectserver.internal.network; -import io.realm.objectserver.ErrorCode; import io.realm.objectserver.internal.Token; +import okhttp3.Response; -public class RefreshResponse { - private Token refreshToken = null; - private ErrorCode errorCode = null; - private String errorMessage = null; +public class RefreshResponse extends AuthServerResponse { - public RefreshResponse(Token refreshToken, ErrorCode errorCode, String errorMessage) { - this.refreshToken = refreshToken; - this.errorCode = errorCode; - this.errorMessage = errorMessage; - } - - public boolean isValid() { - return false; + public RefreshResponse(Response response) { + // FIXME Parse refresh result } public Token getRefreshToken() { - return refreshToken; - } - - public ErrorCode getErrorCode() { - return errorCode; - } - - public String getErrorMessage() { - return errorMessage; + return null; } } From 566c24f34e73732f38a88a32203921dde599072f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 19 Sep 2016 13:32:14 +0200 Subject: [PATCH 0059/2110] Introduce global init (#3457) Realm now uses a global init function instead of Context on the RealmConfiguration.Builder --- CHANGELOG.md | 3 +- .../src/main/AndroidManifest.xml | 9 ++-- .../EncryptionExampleActivity.java | 2 +- .../encryptionexample/MyApplication.java | 30 +++++++++++ .../src/main/AndroidManifest.xml | 1 + .../GridViewExampleActivity.java | 2 +- .../examples/realmgridview/MyApplication.java | 30 +++++++++++ .../introExample/src/main/AndroidManifest.xml | 1 + .../examples/intro/IntroExampleActivity.java | 6 +-- .../realm/examples/intro/MyApplication.java | 31 +++++++++++ .../jsonExample/src/main/AndroidManifest.xml | 1 + .../examples/json/JsonExampleActivity.java | 2 +- .../io/realm/examples/json/MyApplication.java | 30 +++++++++++ .../src/main/AndroidManifest.xml | 1 + .../examples/kotlin/KotlinExampleActivity.kt | 6 +-- .../io/realm/examples/kotlin/MyApplication.kt | 30 +++++++++++ .../src/main/AndroidManifest.xml | 1 + .../MigrationExampleActivity.java | 18 +++---- .../realmmigrationexample/MyApplication.java | 30 +++++++++++ .../app/src/main/AndroidManifest.xml | 1 + .../appmodules/ModulesExampleActivity.java | 8 +-- .../examples/appmodules/MyApplication.java | 30 +++++++++++ .../io/realm/examples/librarymodules/Zoo.java | 6 +-- .../newsreader/NewsReaderApplication.java | 3 +- .../realm/examples/rxjava/MyApplication.java | 3 +- .../realm/examples/threads/MyApplication.java | 3 +- .../examples/unittesting/ExampleActivity.java | 8 ++- .../unittesting/ExampleActivityTest.java | 14 ++--- .../io/realm/RealmConfigurationTests.java | 10 ---- .../rule/TestRealmConfigurationFactory.java | 13 ++--- .../realm/services/RemoteProcessService.java | 11 +++- .../io/realm/benchmarks/RealmBenchmarks.java | 5 +- .../benchmarks/RealmObjectReadBenchmarks.java | 3 +- .../RealmObjectWriteBenchmarks.java | 4 +- .../benchmarks/RealmQueryBenchmarks.java | 2 +- .../benchmarks/RealmResultsBenchmarks.java | 3 +- .../src/main/java/io/realm/BaseRealm.java | 8 ++- .../src/main/java/io/realm/Realm.java | 53 ++++++++++++++++++- .../java/io/realm/RealmConfiguration.java | 21 +++----- .../java/io/realm/internal/RealmCore.java | 30 ----------- .../main/java/io/realm/internal/Table.java | 4 -- .../main/java/io/realm/internal/TestUtil.java | 6 --- .../src/main/java/io/realm/internal/Util.java | 6 --- 43 files changed, 349 insertions(+), 140 deletions(-) create mode 100644 examples/encryptionExample/src/main/java/io/realm/examples/encryptionexample/MyApplication.java create mode 100644 examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/MyApplication.java create mode 100644 examples/introExample/src/main/java/io/realm/examples/intro/MyApplication.java create mode 100644 examples/jsonExample/src/main/java/io/realm/examples/json/MyApplication.java create mode 100644 examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/MyApplication.kt create mode 100644 examples/migrationExample/src/main/java/io/realm/examples/realmmigrationexample/MyApplication.java create mode 100644 examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/MyApplication.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f62ff6c1f..33c3bb1160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,13 @@ ### Breaking Changes +* It is now required to call `Realm.init(Context)` before calling any other Realm API. +* Removed `RealmConfiguration.Builder(Context)`, `RealmConfiguration.Builder(Context, File)` and `RealmConfiguration.Builder(File)` constructors. * `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). * armeabi is not supported anymore. * Added new `RealmFileException`. - `IncompatibleLockFileException` has been removed and replaced by `RealmFileException` with kind `INCOMPATIBLE_LOCK_FILE`. - `RealmIOExcpetion` has been removed and replaced by `RealmFileException`. -* Removed `RealmConfiguration.Builder(Context, File)` and `RealmConfiguration.Builder(File)` constructors. * `RealmConfiguration.Builder.assetFile(Context, String)` has been renamed to `RealmConfiguration.Builder.assetFile(String)`. * Object with primary key is now required to define it when the object is created. This means that `Realm.createObject(Class)` and `DynamicRealm.createObject(String)` now throws `RealmException` if they are used to create an object with a primary key field. Use `Realm.createObject(Class, Object)` or `DynamicRealm.createObject(String, Object)` instead. * Importing from JSON without the primary key field defined in the JSON object now throws `IllegalArgumentException`. diff --git a/examples/encryptionExample/src/main/AndroidManifest.xml b/examples/encryptionExample/src/main/AndroidManifest.xml index b3bb0e1028..00f9f6c7be 100644 --- a/examples/encryptionExample/src/main/AndroidManifest.xml +++ b/examples/encryptionExample/src/main/AndroidManifest.xml @@ -1,8 +1,9 @@ - + - - + + diff --git a/examples/encryptionExample/src/main/java/io/realm/examples/encryptionexample/EncryptionExampleActivity.java b/examples/encryptionExample/src/main/java/io/realm/examples/encryptionexample/EncryptionExampleActivity.java index edf66fdaef..d40a95ce1a 100644 --- a/examples/encryptionExample/src/main/java/io/realm/examples/encryptionexample/EncryptionExampleActivity.java +++ b/examples/encryptionExample/src/main/java/io/realm/examples/encryptionexample/EncryptionExampleActivity.java @@ -42,7 +42,7 @@ protected void onCreate(Bundle savedInstanceState) { // * http://nelenkov.blogspot.dk/2012/05/storing-application-secrets-in-androids.html byte[] key = new byte[64]; new SecureRandom().nextBytes(key); - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this) + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() .encryptionKey(key) .build(); diff --git a/examples/encryptionExample/src/main/java/io/realm/examples/encryptionexample/MyApplication.java b/examples/encryptionExample/src/main/java/io/realm/examples/encryptionexample/MyApplication.java new file mode 100644 index 0000000000..83fc8be016 --- /dev/null +++ b/examples/encryptionExample/src/main/java/io/realm/examples/encryptionexample/MyApplication.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.examples.encryptionexample; + +import android.app.Application; + +import io.realm.Realm; + +public class MyApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + Realm.init(this); + } +} diff --git a/examples/gridViewExample/src/main/AndroidManifest.xml b/examples/gridViewExample/src/main/AndroidManifest.xml index 1bf1d8752c..45615afd2b 100644 --- a/examples/gridViewExample/src/main/AndroidManifest.xml +++ b/examples/gridViewExample/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="io.realm.examples.realmgridview"> diff --git a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java index 246bafadfc..1d2cb4a177 100644 --- a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java +++ b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java @@ -58,19 +58,19 @@ protected void onCreate(Bundle savedInstanceState) { // The default Realm instance implicitly knows about all classes in the realmModuleAppExample Android Studio // module. This does not include the classes from the realmModuleLibraryExample AS module so a Realm using this // configuration would know about the following classes: { Cow, Pig, Snake, Spider } - RealmConfiguration defaultConfig = new RealmConfiguration.Builder(this).build(); + RealmConfiguration defaultConfig = new RealmConfiguration.Builder().build(); // It is possible to extend the default schema by adding additional Realm modules using modules(). This can // also be Realm modules from libraries. The below Realm contains the following classes: { Cow, Pig, Snake, // Spider, Cat, Dog } - RealmConfiguration farmAnimalsConfig = new RealmConfiguration.Builder(this) + RealmConfiguration farmAnimalsConfig = new RealmConfiguration.Builder() .name("farm.realm") .modules(Realm.getDefaultModule(), new DomesticAnimalsModule()) .build(); // Or you can completely replace the default schema. // This Realm contains the following classes: { Elephant, Lion, Zebra, Snake, Spider } - RealmConfiguration exoticAnimalsConfig = new RealmConfiguration.Builder(this) + RealmConfiguration exoticAnimalsConfig = new RealmConfiguration.Builder() .name("exotic.realm") .modules(new ZooAnimalsModule(), new CreepyAnimalsModule()) .build(); @@ -144,7 +144,7 @@ public void execute(Realm realm) { // And Realms in library projects are independent from Realms in the app code showStatus("Interacting with library code that uses Realm internally"); int animals = 5; - Zoo libraryZoo = new Zoo(this); + Zoo libraryZoo = new Zoo(); libraryZoo.open(); showStatus("Adding animals: " + animals); libraryZoo.addAnimals(5); diff --git a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/MyApplication.java b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/MyApplication.java new file mode 100644 index 0000000000..61afd4ba36 --- /dev/null +++ b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/MyApplication.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.examples.appmodules; + +import android.app.Application; + +import io.realm.Realm; + +public class MyApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + Realm.init(this); + } +} diff --git a/examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/Zoo.java b/examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/Zoo.java index f33c90cbbd..b7d68f2eb6 100644 --- a/examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/Zoo.java +++ b/examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/Zoo.java @@ -32,9 +32,9 @@ public class Zoo { private final RealmConfiguration realmConfig; private Realm realm; - public Zoo(Context context) { - realmConfig = new RealmConfiguration.Builder(context) // Beware this is the app context - .name("library.zoo.realm") // So always use a unique name + public Zoo() { + realmConfig = new RealmConfiguration.Builder() // The app is responsible for calling `Realm.init(Context)` + .name("library.zoo.realm") // So always use a unique name .modules(new AllAnimalsModule()) // Always use explicit modules in library projects .build(); diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/NewsReaderApplication.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/NewsReaderApplication.java index d8f575dd1a..77674d9c2b 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/NewsReaderApplication.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/NewsReaderApplication.java @@ -44,7 +44,8 @@ public void handleError(Throwable e) { }); // Configure default configuration for Realm - RealmConfiguration realmConfig = new RealmConfiguration.Builder(this).build(); + Realm.init(this); + RealmConfiguration realmConfig = new RealmConfiguration.Builder().build(); Realm.setDefaultConfiguration(realmConfig); } diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java index 22e5e2b6db..9ac0f17b60 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java @@ -45,7 +45,8 @@ public class MyApplication extends Application { public void onCreate() { super.onCreate(); context = this; - RealmConfiguration config = new RealmConfiguration.Builder(this).build(); + Realm.init(this); + RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.deleteRealm(config); Realm.setDefaultConfiguration(config); createTestData(); diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/MyApplication.java b/examples/threadExample/src/main/java/io/realm/examples/threads/MyApplication.java index 7ced58b6c8..a043477ecf 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/MyApplication.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/MyApplication.java @@ -28,7 +28,8 @@ public void onCreate() { super.onCreate(); // Configure Realm for the application - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build(); + Realm.init(this); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build(); Realm.deleteRealm(realmConfiguration); // Clean slate Realm.setDefaultConfiguration(realmConfiguration); // Make this Realm the default } diff --git a/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java b/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java index b918cdd6f4..479037fabb 100644 --- a/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java +++ b/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java @@ -36,19 +36,17 @@ public class ExampleActivity extends Activity { private LinearLayout rootLayout = null; private Realm realm; - private static RealmConfiguration realmConfig; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Realm.init(getApplicationContext()); setContentView(R.layout.activity_example); rootLayout = ((LinearLayout) findViewById(R.id.container)); rootLayout.removeAllViews(); - // Create Realm configuration if it doesn't exist. - realmConfig = new RealmConfiguration.Builder(this).build(); // Open the default Realm for the UI thread. - realm = Realm.getInstance(realmConfig); + realm = Realm.getDefaultInstance(); // Clean up from previous run cleanUp(); @@ -160,7 +158,7 @@ public void execute(Realm realm) { private String complexQuery() { String status = "\n\nPerforming complex Query operation..."; - Realm realm = Realm.getInstance(realmConfig); + Realm realm = Realm.getDefaultInstance(); status += "\nNumber of people in the DB: " + realm.where(Person.class).count(); // Find all persons where age between 1 and 99 and name begins with "J". diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java index c4b4d8403c..c380633bd0 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java @@ -31,6 +31,7 @@ import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.util.ActivityController; @@ -86,6 +87,7 @@ public void setup() throws Exception { mockStatic(RealmLog.class); mockStatic(Realm.class); mockStatic(RealmConfiguration.class); + Realm.init(RuntimeEnvironment.application); // Create the mock final Realm mockRealm = mock(Realm.class); @@ -103,7 +105,7 @@ public void setup() throws Exception { whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig); // Anytime getInstance is called with any configuration, then return the mockRealm - when(Realm.getInstance(any(RealmConfiguration.class))).thenReturn(mockRealm); + when(Realm.getDefaultInstance()).thenReturn(mockRealm); // Anytime we ask Realm to create a Person, return a new instance. when(mockRealm.createObject(Person.class)).thenReturn(new Person()); @@ -179,15 +181,14 @@ public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { doCallRealMethod().when(mockRealm).executeTransaction(Mockito.any(Realm.Transaction.class)); // Create activity - ActivityController controller = - Robolectric.buildActivity(ExampleActivity.class).setup(); + ActivityController controller = Robolectric.buildActivity(ExampleActivity.class).setup(); ExampleActivity activity = controller.get(); assertThat(activity.getTitle().toString(), is("Unit Test Example")); // Verify that two Realm.getInstance() calls took place. verifyStatic(times(2)); - Realm.getInstance(any(RealmConfiguration.class)); + Realm.getDefaultInstance(); // verify that we have four begin and commit transaction calls // Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649 @@ -224,15 +225,14 @@ public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { public void shouldBeAbleToVerifyTransactionCalls() { // Create activity - ActivityController controller = - Robolectric.buildActivity(ExampleActivity.class).setup(); + ActivityController controller = Robolectric.buildActivity(ExampleActivity.class).setup(); ExampleActivity activity = controller.get(); assertThat(activity.getTitle().toString(), is("Unit Test Example")); // Verify that two Realm.getInstance() calls took place. verifyStatic(times(2)); - Realm.getInstance(any(RealmConfiguration.class)); + Realm.getDefaultInstance(); // verify that we have four begin and commit transaction calls // Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 3237aab39d..effdab153d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -107,16 +107,6 @@ public void setDefaultConfiguration_nullThrows() throws NoSuchFieldException, Il } } - @Test - public void getDefaultInstance_nullThrows() throws NoSuchFieldException, IllegalAccessException { - clearDefaultConfiguration(); - try { - Realm.getDefaultInstance(); - fail(); - } catch (NullPointerException ignored) { - } - } - @Test public void getInstance_nullConfigThrows() { try { diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index b646cc7257..cb8a1c62a8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -71,6 +71,7 @@ public void evaluate() throws Throwable { @Override protected void before() throws Throwable { super.before(); + Realm.init(InstrumentationRegistry.getTargetContext()); } @Override @@ -95,7 +96,7 @@ protected void after() { } public RealmConfiguration createConfiguration() { - RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + RealmConfiguration configuration = new RealmConfiguration.Builder() .directory(getRoot()) .build(); @@ -106,7 +107,7 @@ public RealmConfiguration createConfiguration() { public RealmConfiguration createConfiguration(String subDir, String name) { final File folder = new File(getRoot(), subDir); assertTrue(folder.mkdirs()); - RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + RealmConfiguration configuration = new RealmConfiguration.Builder() .directory(folder) .name(name) .build(); @@ -116,7 +117,7 @@ public RealmConfiguration createConfiguration(String subDir, String name) { } public RealmConfiguration createConfiguration(String name) { - RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + RealmConfiguration configuration = new RealmConfiguration.Builder() .directory(getRoot()) .name(name) .build(); @@ -126,7 +127,7 @@ public RealmConfiguration createConfiguration(String name) { } public RealmConfiguration createConfiguration(String name, byte[] key) { - RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + RealmConfiguration configuration = new RealmConfiguration.Builder() .directory(getRoot()) .name(name) .encryptionKey(key) @@ -137,14 +138,14 @@ public RealmConfiguration createConfiguration(String name, byte[] key) { } public RealmConfiguration.Builder createConfigurationBuilder() { - return new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()).directory(getRoot()); + return new RealmConfiguration.Builder().directory(getRoot()); } // Copies a Realm file from assets to temp dir public void copyRealmFromAssets(Context context, String realmPath, String newName) throws IOException { // Delete the existing file before copy - RealmConfiguration configToDelete = new RealmConfiguration.Builder(context) + RealmConfiguration configToDelete = new RealmConfiguration.Builder() .directory(getRoot()) .name(newName) .build(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java index a29895fffb..7a841817d0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java +++ b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java @@ -24,6 +24,7 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.util.Log; import java.util.HashMap; import java.util.Map; @@ -79,6 +80,12 @@ public RemoteProcessService() { thiz = this; } + @Override + public void onCreate() { + super.onCreate(); + Realm.init(this); + } + @Override public IBinder onBind(Intent intent) { return messenger.getBinder(); @@ -121,7 +128,7 @@ private static String currentLine() { @Override void run() { - thiz.testRealm = Realm.getInstance(new RealmConfiguration.Builder(thiz).build()); + thiz.testRealm = Realm.getInstance(new RealmConfiguration.Builder().build()); int expected = 1; long got = thiz.testRealm.where(AllTypes.class).count(); if (expected == got) { @@ -137,7 +144,7 @@ void run() { @Override void run() { - thiz.testRealm = Realm.getInstance(new RealmConfiguration.Builder(thiz).build()); + thiz.testRealm = Realm.getInstance(new RealmConfiguration.Builder().build()); thiz.testRealm.close(); response(null); Runtime.getRuntime().exit(0); diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmBenchmarks.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmBenchmarks.java index dd23f1f917..cf307a1b5b 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmBenchmarks.java +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmBenchmarks.java @@ -43,8 +43,9 @@ public class RealmBenchmarks { @BeforeExperiment public void before() { - coldConfig = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()).name("cold").build(); - RealmConfiguration config = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()).build(); + Realm.init(InstrumentationRegistry.getTargetContext()); + coldConfig = new RealmConfiguration.Builder().name("cold").build(); + RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.deleteRealm(coldConfig); Realm.deleteRealm(config); realm = Realm.getInstance(config); diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java index e26c003cd4..30719457ff 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java @@ -42,7 +42,8 @@ public class RealmObjectReadBenchmarks { @BeforeExperiment public void before() { - RealmConfiguration config = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()).build(); + Realm.init(InstrumentationRegistry.getTargetContext()); + RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.deleteRealm(config); realm = Realm.getInstance(config); realm.executeTransaction(new Realm.Transaction() { diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java index 73df749ab6..ff8be605c7 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java @@ -16,8 +16,6 @@ package io.realm.benchmarks; -import android.support.test.InstrumentationRegistry; - import org.junit.runner.RunWith; import dk.ilios.spanner.AfterExperiment; @@ -42,7 +40,7 @@ public class RealmObjectWriteBenchmarks { @BeforeExperiment public void before() { - RealmConfiguration config = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()).build(); + RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.deleteRealm(config); realm = Realm.getInstance(config); realm.beginTransaction(); diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmQueryBenchmarks.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmQueryBenchmarks.java index 9117d55d0a..8487c9d668 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmQueryBenchmarks.java +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmQueryBenchmarks.java @@ -45,7 +45,7 @@ public class RealmQueryBenchmarks { @BeforeExperiment public void before() { - RealmConfiguration config = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()).build(); + RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.deleteRealm(config); realm = Realm.getInstance(config); realm.beginTransaction(); diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmResultsBenchmarks.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmResultsBenchmarks.java index 5a25e95ee0..f4cee113b9 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmResultsBenchmarks.java +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmResultsBenchmarks.java @@ -45,7 +45,8 @@ public class RealmResultsBenchmarks { @BeforeExperiment public void before() { - RealmConfiguration config = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()).build(); + Realm.init(InstrumentationRegistry.getTargetContext()); + RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.deleteRealm(config); realm = Realm.getInstance(config); realm.beginTransaction(); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 4e5e35272a..d3cf9a78de 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -16,6 +16,7 @@ package io.realm; +import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -60,6 +61,8 @@ abstract class BaseRealm implements Closeable { private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; + volatile static Context applicationContext; + // Thread pool for all async operations (Query & transaction) static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); @@ -70,11 +73,6 @@ abstract class BaseRealm implements Closeable { RealmSchema schema; HandlerController handlerController; - static { - //noinspection ConstantConditions - RealmLog.add(BuildConfig.DEBUG ? new AndroidLogger(Log.DEBUG) : new AndroidLogger(Log.WARN)); - } - protected BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 7fe7d4aed7..de51d80733 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -18,8 +18,12 @@ import android.annotation.TargetApi; import android.app.IntentService; +import android.content.Context; import android.os.Build; import android.util.JsonReader; +import android.util.Log; + +import com.getkeepsafe.relinker.BuildConfig; import org.json.JSONArray; import org.json.JSONException; @@ -48,9 +52,11 @@ import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; +import io.realm.internal.RealmCore; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; +import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; import rx.Observable; @@ -141,6 +147,51 @@ public Observable asObservable() { return configuration.getRxFactory().from(this); } + /** + * Initializes the Realm library and creates a default configuration that is ready to use. It is required to call + * this method before interacting with any other of the Realm API's. + * + * A good place is in an {@link android.app.Application} subclass: + *

      +     * {@code
      +     * public class MyApplication extends Application {
      +     *   \@Override
      +     *   public void onCreate() {
      +     *     super.onCreate();
      +     *     Realm.init(this);
      +     *   }
      +     * }
      +     * }
      +     * 
      + * + * Remember to register it in the {@code AndroidManifest.xml} file: + *
      +     * {@code
      +     * 
      +     * 
      +     * 
      +     *   // ...
      +     * 
      +     * 
      +     * }
      +     * 
      + * + * @param context the Application Context. + * @throws IllegalArgumentException if a {@code null} context is provided. + * @see #getDefaultInstance() + */ + public static synchronized void init(Context context) { + if (BaseRealm.applicationContext == null) { + if (context == null) { + throw new IllegalArgumentException("Non-null context required."); + } + RealmCore.loadLibrary(context); + RealmLog.add(BuildConfig.DEBUG ? new AndroidLogger(Log.DEBUG) : new AndroidLogger(Log.WARN)); + defaultConfiguration = new RealmConfiguration.Builder(context).build(); + BaseRealm.applicationContext = context.getApplicationContext(); + } + } + /** * Realm static constructor that returns the Realm instance defined by the {@link io.realm.RealmConfiguration} set * by {@link #setDefaultConfiguration(RealmConfiguration)} @@ -153,7 +204,7 @@ public Observable asObservable() { */ public static Realm getDefaultInstance() { if (defaultConfiguration == null) { - throw new NullPointerException("No default RealmConfiguration was found. Call setDefaultConfiguration() first"); + throw new IllegalStateException("Call `Realm.init(Context)` before calling this method."); } return RealmCache.createRealmOrGetFromCache(defaultConfiguration, Realm.class); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 2be71eb4ad..630a50c1a1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -97,7 +97,6 @@ public final class RealmConfiguration { private final RealmProxyMediator schemaMediator; private final RxObservableFactory rxObservableFactory; private final Realm.Transaction initialDataTransaction; - private final WeakReference contextWeakRef; private RealmConfiguration(Builder builder) { this.realmDirectory = builder.directory; @@ -112,7 +111,6 @@ private RealmConfiguration(Builder builder) { this.schemaMediator = createSchemaMediator(builder); this.rxObservableFactory = builder.rxFactory; this.initialDataTransaction = builder.initialDataTransaction; - this.contextWeakRef = builder.contextWeakRef; } public File getRealmDirectory() { @@ -177,12 +175,7 @@ boolean hasAssetFile() { * @throws IOException if copying the file fails. */ InputStream getAssetFile() throws IOException { - Context context = contextWeakRef.get(); - if (context != null) { - return context.getAssets().open(assetFilePath); - } else { - throw new IllegalArgumentException("Context should not be null. Use Application Context instead of Activity Context."); - } + return BaseRealm.applicationContext.getAssets().open(assetFilePath); } /** @@ -357,7 +350,6 @@ public static final class Builder { private SharedRealm.Durability durability; private HashSet modules = new HashSet(); private HashSet> debugSchema = new HashSet>(); - private WeakReference contextWeakRef; private RxObservableFactory rxFactory; private Realm.Transaction initialDataTransaction; @@ -367,12 +359,14 @@ public static final class Builder { * This will use the app's own internal directory for storing the Realm file. This does not require any * additional permissions. The default location is {@code /data/data//files}, but can * change depending on vendor implementations of Android. - * - * @param context the Android application context. */ - public Builder(Context context) { + public Builder() { + this(BaseRealm.applicationContext); + } + + Builder(Context context) { if (context == null) { - throw new IllegalArgumentException("A non-null Context must be provided"); + throw new IllegalStateException("Call `Realm.init(Context)` before creating a RealmConfiguration"); } RealmCore.loadLibrary(context); initializeBuilder(context); @@ -380,7 +374,6 @@ public Builder(Context context) { // Setup builder in its initial state private void initializeBuilder(Context context) { - this.contextWeakRef = new WeakReference(context); this.directory = context.getFilesDir(); this.fileName = Realm.DEFAULT_REALM_NAME; this.key = null; diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java index b89e49e9d3..72e043dc1d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java @@ -41,36 +41,6 @@ public static boolean osIsWindows() { return (os.contains("win")); } - /** - * Loads the .so file. This method is useful for static blocks as it does not rely on access to a Context. - * - * Although loadLibrary is synchronized internally from AOSP 4.3, for compatibility reasons, - * KEEP synchronized here for old devices! - */ - public static synchronized void loadLibrary() { - if (libraryIsLoaded) { - // The java native should ensure only load the lib once, but we met some problems before. - // So keep the flag. - return; - } - - if (osIsWindows()) { - loadLibraryWindows(); - } - else { - String jnilib; - String debug = System.getenv("REALM_JAVA_DEBUG"); - if (debug == null || debug.isEmpty()) { - jnilib = "realm-jni"; - } - else { - jnilib = "realm-jni-dbg"; - } - System.loadLibrary(jnilib); - } - libraryIsLoaded = true; - } - /** * Loads the .so file. Typically, the .so file is installed and can be found by System.loadLibrary() but * can be damaged or missing. This happens for the Android installer, especially when apps are installed diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index db2ba7db16..ad5c39f35b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -52,10 +52,6 @@ public class Table implements TableOrView, TableSchema { private final SharedRealm sharedRealm; private long cachedPrimaryKeyColumnIndex = NO_MATCH; - static { - RealmCore.loadLibrary(); - } - /** * Constructs a Table base object. It can be used to register columns in this table. Registering into table is * allowed only for empty tables. It creates a native reference of the object and keeps a reference to it. diff --git a/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java index 4af5a15360..275cecfb04 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java @@ -18,12 +18,6 @@ class TestUtil { - static { - // Any internal class with static native methods that uses Realm Core must load the Realm Core library - // themselves as it otherwise might not have been loaded. - RealmCore.loadLibrary(); - } - public native static long getMaxExceptionNumber(); public native static String getExpectedMessage(long exceptionKind); public native static void testThrowExceptions(long exceptionKind); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index f6eb85b851..107350f769 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -21,12 +21,6 @@ public class Util { - static { - // Any internal class with static native methods that uses Realm Core must load the Realm Core library - // themselves as it otherwise might not have been loaded. - RealmCore.loadLibrary(); - } - public static long getNativeMemUsage() { return nativeGetMemUsage(); } From fa938b8cccbcbb99122fdebdc1505f230f7a9d16 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 19 Sep 2016 09:51:57 -0500 Subject: [PATCH 0060/2110] Move classes from objectserver to its super (#113) Normal classes: io/realm/objectserver -> io/realm io/realm/objectserver/internal -> io/realm/internal/objectserver io/realm/objectserver/internal/network -> io/realm/internal/network io/realm/objectserver/internal/syncpolicy -> io/realm/internal/syncpolicy Tests classes: io/realm/objectserver -> io/realm io/realm/objectserver/SyncTestUtils.java -> io/realm/util/SyncTestUtils.java --- .../objectserver/CounterActivity.java | 6 ++-- .../examples/objectserver/LoginActivity.java | 10 +++---- .../examples/objectserver/MyApplication.java | 8 ++--- .../{objectserver => }/CredentialsTests.java | 2 +- .../realm/{objectserver => }/SchemaTests.java | 8 ++--- .../SyncConfigurationTests.java | 20 +++++-------- .../realm/{objectserver => }/UserTests.java | 4 +-- .../{objectserver => util}/SyncTestUtils.java | 7 +++-- .../src/main/AndroidManifest.xml | 2 +- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- ...ncManager.cpp => io_realm_SyncManager.cpp} | 8 ++--- .../src/main/cpp/io_realm_internal_Util.cpp | 2 +- ...alm_internal_objectserver_SyncSession.cpp} | 12 ++++---- .../AuthenticationListener.java | 2 +- .../src/main/java/io/realm/BaseRealm.java | 3 +- .../realm/{objectserver => }/Credentials.java | 5 ++-- .../realm/{objectserver => }/ErrorCode.java | 2 +- .../{objectserver => }/ObjectServerError.java | 10 +++---- .../src/main/java/io/realm/Realm.java | 3 +- .../src/main/java/io/realm/RealmCache.java | 2 +- .../main/java/io/realm/RealmObjectSchema.java | 3 +- .../src/main/java/io/realm/RealmSchema.java | 3 +- .../io/realm/{objectserver => }/Session.java | 6 ++-- .../{objectserver => }/SessionState.java | 2 +- .../{objectserver => }/SyncConfiguration.java | 11 ++----- .../realm/{objectserver => }/SyncManager.java | 11 ++++--- .../io/realm/{objectserver => }/User.java | 24 +++++++-------- .../realm/{objectserver => }/UserStore.java | 4 +-- .../android/SharedPrefsUserStore.java | 6 ++-- .../java/io/realm/internal/SharedRealm.java | 3 +- .../internal/network/AuthServerResponse.java | 6 ++-- .../internal/network/AuthenticateRequest.java | 8 ++--- .../network/AuthenticateResponse.java | 8 ++--- .../network/AuthenticationServer.java | 8 ++--- .../network/ExponentialBackoffTask.java | 4 +-- .../internal/network/LogoutRequest.java | 4 +-- .../internal/network/LogoutResponse.java | 10 ++----- .../network/NetworkStateReceiver.java | 2 +- .../network/OkHttpAuthenticationServer.java | 12 ++++---- .../internal/network/RefreshResponse.java | 4 +-- .../objectserver}/AuthenticatingState.java | 8 +++-- .../objectserver}/BindingState.java | 6 ++-- .../objectserver}/BoundState.java | 8 ++--- .../objectserver}/FsmAction.java | 7 +++-- .../objectserver}/FsmState.java | 13 ++++---- .../objectserver}/InitialState.java | 6 ++-- .../objectserver}/ObjectServerFacade.java | 10 +++---- .../objectserver}/SessionStore.java | 11 +++---- .../objectserver}/StoppedState.java | 7 +++-- .../objectserver}/SyncSession.java | 30 +++++++++---------- .../objectserver}/SyncUser.java | 16 +++++----- .../objectserver}/SyncUtil.java | 2 +- .../objectserver}/Token.java | 2 +- .../objectserver}/UnboundState.java | 7 ++--- .../syncpolicy/AutomaticSyncPolicy.java | 8 ++--- .../internal/syncpolicy/SyncPolicy.java | 13 ++++---- .../src/main/java/io/realm/log/RealmLog.java | 4 +-- 57 files changed, 197 insertions(+), 218 deletions(-) rename realm/realm-library/src/androidTest/java/io/realm/{objectserver => }/CredentialsTests.java (99%) rename realm/realm-library/src/androidTest/java/io/realm/{objectserver => }/SchemaTests.java (95%) rename realm/realm-library/src/androidTest/java/io/realm/{objectserver => }/SyncConfigurationTests.java (91%) rename realm/realm-library/src/androidTest/java/io/realm/{objectserver => }/UserTests.java (91%) rename realm/realm-library/src/androidTest/java/io/realm/{objectserver => util}/SyncTestUtils.java (93%) rename realm/realm-library/src/main/cpp/{io_realm_objectserver_SyncManager.cpp => io_realm_SyncManager.cpp} (93%) rename realm/realm-library/src/main/cpp/{io_realm_objectserver_internal_SyncSession.cpp => io_realm_internal_objectserver_SyncSession.cpp} (88%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/AuthenticationListener.java (97%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/Credentials.java (98%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/ErrorCode.java (99%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/ObjectServerError.java (90%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/Session.java (95%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/SessionState.java (97%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/SyncConfiguration.java (98%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/SyncManager.java (96%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/User.java (94%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/UserStore.java (95%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/android/SharedPrefsUserStore.java (96%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/AuthServerResponse.java (93%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/AuthenticateRequest.java (94%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/AuthenticateResponse.java (95%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/AuthenticationServer.java (87%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/ExponentialBackoffTask.java (97%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/LogoutRequest.java (91%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/LogoutResponse.java (90%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/NetworkStateReceiver.java (98%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/OkHttpAuthenticationServer.java (92%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/network/RefreshResponse.java (89%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/AuthenticatingState.java (95%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/BindingState.java (93%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/BoundState.java (91%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/FsmAction.java (83%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/FsmState.java (85%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/InitialState.java (90%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/ObjectServerFacade.java (91%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/SessionStore.java (86%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/StoppedState.java (90%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/SyncSession.java (93%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/SyncUser.java (96%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/SyncUtil.java (93%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/Token.java (99%) rename realm/realm-library/src/main/java/io/realm/{objectserver/internal => internal/objectserver}/UnboundState.java (89%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/syncpolicy/AutomaticSyncPolicy.java (93%) rename realm/realm-library/src/main/java/io/realm/{objectserver => }/internal/syncpolicy/SyncPolicy.java (89%) diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 7afc98947a..c7ce08f8d5 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -31,9 +31,9 @@ import io.realm.RealmResults; import io.realm.examples.objectserver.model.CRDTCounter; import io.realm.examples.objectserver.model.CounterOperation; -import io.realm.objectserver.SyncConfiguration; -import io.realm.objectserver.SyncManager; -import io.realm.objectserver.User; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.User; public class CounterActivity extends AppCompatActivity { diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java index 25d3d76826..bd02d2d2fe 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -26,12 +26,12 @@ import butterknife.BindView; import butterknife.ButterKnife; -import io.realm.objectserver.Credentials; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.User; -import io.realm.objectserver.UserStore; +import io.realm.Credentials; +import io.realm.ObjectServerError; +import io.realm.User; +import io.realm.UserStore; -import static io.realm.objectserver.ErrorCode.UNKNOWN_ACCOUNT; +import static io.realm.ErrorCode.UNKNOWN_ACCOUNT; public class LoginActivity extends AppCompatActivity { diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java index 57981c629b..02d9ad8186 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java @@ -21,10 +21,10 @@ import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; -import io.realm.objectserver.SyncManager; -import io.realm.objectserver.User; -import io.realm.objectserver.UserStore; -import io.realm.objectserver.android.SharedPrefsUserStore; +import io.realm.SyncManager; +import io.realm.User; +import io.realm.UserStore; +import io.realm.android.SharedPrefsUserStore; public class MyApplication extends Application { diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/CredentialsTests.java b/realm/realm-library/src/androidTest/java/io/realm/CredentialsTests.java similarity index 99% rename from realm/realm-library/src/androidTest/java/io/realm/objectserver/CredentialsTests.java rename to realm/realm-library/src/androidTest/java/io/realm/CredentialsTests.java index 87c755029d..e406e78fec 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/CredentialsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CredentialsTests.java @@ -1,4 +1,4 @@ -package io.realm.objectserver; +package io.realm; /* * Copyright 2016 Realm Inc. * diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/SchemaTests.java similarity index 95% rename from realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java rename to realm/realm-library/src/androidTest/java/io/realm/SchemaTests.java index ccc3dfe023..8394607544 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SchemaTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import android.content.Context; @@ -25,14 +25,12 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; -import io.realm.Realm; import io.realm.entities.StringOnly; import io.realm.rule.TestRealmConfigurationFactory; +import io.realm.util.SyncTestUtils; -import static io.realm.objectserver.SyncTestUtils.createTestUser; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertFalse; @@ -49,7 +47,7 @@ public class SchemaTests { @Before public void setUp() { context = InstrumentationRegistry.getContext(); - User user = createTestUser(); + User user = SyncTestUtils.createTestUser(); config = new SyncConfiguration.Builder(context) .user(user) .serverUrl("realm://objectserver.realm.io/~/default") diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java similarity index 91% rename from realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java rename to realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java index 522f6bca8c..24d1fc39b1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java @@ -14,15 +14,12 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -31,14 +28,11 @@ import org.junit.runner.RunWith; import java.io.File; -import java.util.Locale; -import java.util.UUID; -import io.realm.objectserver.internal.Token; import io.realm.rule.RunInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; +import io.realm.util.SyncTestUtils; -import static io.realm.objectserver.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -80,7 +74,7 @@ public void user_invalidUserThrows() { } catch (IllegalArgumentException ignore) { } - User user = createTestUser(0); // Create user that has expired credentials + User user = SyncTestUtils.createTestUser(0); // Create user that has expired credentials try { builder.user(user); } catch (IllegalArgumentException ignore) { @@ -89,7 +83,7 @@ public void user_invalidUserThrows() { @Test public void serverUrl_setsFolderAndFileName() { - User user = createTestUser(); + User user = SyncTestUtils.createTestUser(); String[][] validUrls = { // , , { "realm://objectserver.realm.io/~/default", "realm-object-server/" + user.getIdentity(), "default" }, @@ -146,7 +140,7 @@ public void userAndServerUrlRequired() { builder = new SyncConfiguration.Builder(context); try { - builder.user(createTestUser(Long.MAX_VALUE)).build(); + builder.user(SyncTestUtils.createTestUser(Long.MAX_VALUE)).build(); } catch (IllegalStateException ignore) { } @@ -162,7 +156,7 @@ public void userAndServerUrlRequired() { public void errorHandler() { SyncConfiguration.Builder builder; builder = new SyncConfiguration.Builder(context) - .user(createTestUser()) + .user(SyncTestUtils.createTestUser()) .serverUrl("realm://objectserver.realm.io/default"); Session.ErrorHandler errorHandler = new Session.ErrorHandler() { @@ -189,7 +183,7 @@ public void onError(Session session, ObjectServerError error) { // Create configuration using the default handler SyncConfiguration config = new SyncConfiguration.Builder(context) - .user(createTestUser()) + .user(SyncTestUtils.createTestUser()) .serverUrl("realm://objectserver.realm.io/default") .build(); assertEquals(errorHandler, config.getErrorHandler()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/UserTests.java b/realm/realm-library/src/androidTest/java/io/realm/UserTests.java similarity index 91% rename from realm/realm-library/src/androidTest/java/io/realm/objectserver/UserTests.java rename to realm/realm-library/src/androidTest/java/io/realm/UserTests.java index ce5ae3552f..d91b850a64 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/UserTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/UserTests.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import static io.realm.objectserver.SyncTestUtils.createTestUser; +import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; @RunWith(AndroidJUnit4.class) diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java b/realm/realm-library/src/androidTest/java/io/realm/util/SyncTestUtils.java similarity index 93% rename from realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java rename to realm/realm-library/src/androidTest/java/io/realm/util/SyncTestUtils.java index 6bbb930b93..6a28b0e529 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/objectserver/SyncTestUtils.java +++ b/realm/realm-library/src/androidTest/java/io/realm/util/SyncTestUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm.util; import org.json.JSONArray; import org.json.JSONException; @@ -22,8 +22,9 @@ import java.util.UUID; -import io.realm.objectserver.internal.SyncUser; -import io.realm.objectserver.internal.Token; +import io.realm.User; +import io.realm.internal.objectserver.SyncUser; +import io.realm.internal.objectserver.Token; public class SyncTestUtils { diff --git a/realm/realm-library/src/main/AndroidManifest.xml b/realm/realm-library/src/main/AndroidManifest.xml index 0e8fc314f7..5de8dda36c 100644 --- a/realm/realm-library/src/main/AndroidManifest.xml +++ b/realm/realm-library/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ - + diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index d88e67cf39..3916cf6977 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -27,7 +27,7 @@ create_javah(TARGET jni_headers CLASSES io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil - io.realm.objectserver.SyncManager io.realm.objectserver.internal.SyncSession + io.realm.SyncManager io.realm.internal.objectserver.SyncSession io.realm.log.LogLevel io.realm.Property io.realm.RealmSchema io.realm.RealmObjectSchema diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp similarity index 93% rename from realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp rename to realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index e70103185b..c397fea617 100644 --- a/realm/realm-library/src/main/cpp/io_realm_objectserver_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -27,7 +27,7 @@ #include "objectserver_shared.hpp" -#include "io_realm_objectserver_SyncManager.h" +#include "io_realm_SyncManager.h" using namespace realm; using namespace realm::sync; @@ -78,7 +78,7 @@ AndroidLogger& AndroidLogger::shared() noexcept { return logger; } -JNIEXPORT void JNICALL Java_io_realm_objectserver_SyncManager_nativeInitializeSyncClient +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient (JNIEnv *env, jclass) { TR_ENTER(env) @@ -96,14 +96,14 @@ JNIEXPORT void JNICALL Java_io_realm_objectserver_SyncManager_nativeInitializeSy // Create the thread from java side to avoid some strange errors when native throws. JNIEXPORT void JNICALL -Java_io_realm_objectserver_SyncManager_nativeRunClient(JNIEnv *env, jclass) { +Java_io_realm_SyncManager_nativeRunClient(JNIEnv *env, jclass) { try { sync_client->run(); } CATCH_STD() } JNIEXPORT void JNICALL -Java_io_realm_objectserver_SyncManager_nativeSetSyncClientLogLevel(JNIEnv* env, jclass, jint logLevel) +Java_io_realm_SyncManager_nativeSetSyncClientLogLevel(JNIEnv* env, jclass, jint logLevel) { util::Logger::Level native_log_level; switch(logLevel) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 6dc3da3862..84e7a3fe5a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -58,7 +58,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); java_lang_double = GetClass(env, "java/lang/Double"); java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); - sync_manager = GetClass(env, "io/realm/objectserver/SyncManager"); + sync_manager = GetClass(env, "io/realm/SyncManager"); realmlog_class = GetClass(env, "io/realm/log/RealmLog"); log_trace = env->GetStaticMethodID(realmlog_class, "trace", "(Ljava/lang/String;[Ljava/lang/Object;)V"); log_debug = env->GetStaticMethodID(realmlog_class, "debug", "(Ljava/lang/String;[Ljava/lang/Object;)V"); diff --git a/realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_SyncSession.cpp similarity index 88% rename from realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_objectserver_SyncSession.cpp index 5222166a05..f790f75da0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_objectserver_internal_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_SyncSession.cpp @@ -16,7 +16,7 @@ #include -#include "io_realm_objectserver_internal_SyncSession.h" +#include "io_realm_internal_objectserver_SyncSession.h" #include "objectserver_shared.hpp" #include "util.hpp" #include @@ -37,7 +37,7 @@ using namespace realm; using namespace sync; -JNIEXPORT jlong JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeCreateSession +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeCreateSession (JNIEnv *env, jobject obj, jstring localRealmPath) { TR_ENTER(env) @@ -49,7 +49,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeCr return 0; } -JNIEXPORT void JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeBind +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeBind (JNIEnv *env, jobject, jlong sessionPointer, jstring remoteUrl, jstring accessToken) { TR_ENTER(env) @@ -69,7 +69,7 @@ JNIEXPORT void JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeBin } -JNIEXPORT void JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeUnbind +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeUnbind (JNIEnv *env, jobject, jlong sessionPointer) { TR_ENTER(env) @@ -78,7 +78,7 @@ JNIEXPORT void JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeUnb delete session; // TODO Can we avoid killing the session here? } -JNIEXPORT void JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeRefresh +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeRefresh (JNIEnv *env, jobject, jlong sessionPointer, jstring accessToken) { TR_ENTER(env) @@ -93,7 +93,7 @@ JNIEXPORT void JNICALL Java_io_realm_objectserver_internal_SyncSession_nativeRef } JNIEXPORT void JNICALL -Java_io_realm_objectserver_internal_SyncSession_nativeNotifyCommitHappened +Java_io_realm_internal_objectserver_SyncSession_nativeNotifyCommitHappened (JNIEnv *env, jobject, jlong sessionPointer, jlong version) { TR_ENTER(env) diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticationListener.java b/realm/realm-library/src/main/java/io/realm/AuthenticationListener.java similarity index 97% rename from realm/realm-library/src/main/java/io/realm/objectserver/AuthenticationListener.java rename to realm/realm-library/src/main/java/io/realm/AuthenticationListener.java index 8a7934d286..d7a2de6f73 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/AuthenticationListener.java +++ b/realm/realm-library/src/main/java/io/realm/AuthenticationListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; /** * Interface describing events related to Users and their authentication diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index d391b52520..388801da60 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -26,7 +26,6 @@ import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -44,7 +43,7 @@ import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; -import io.realm.objectserver.internal.ObjectServerFacade; +import io.realm.internal.objectserver.ObjectServerFacade; import rx.Observable; /** diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java b/realm/realm-library/src/main/java/io/realm/Credentials.java similarity index 98% rename from realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java rename to realm/realm-library/src/main/java/io/realm/Credentials.java index 5fca741d3e..975f6286c8 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/Credentials.java +++ b/realm/realm-library/src/main/java/io/realm/Credentials.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.Map; /** @@ -156,7 +155,7 @@ public String getUserIdentifier() { /** * Returns any custom user information associated with this credential. - * The type of information will depend on the type of {@link io.realm.objectserver.Credentials.IdentityProvider} + * The type of information will depend on the type of {@link Credentials.IdentityProvider} * used. * * @return a map of additional information about the user. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java b/realm/realm-library/src/main/java/io/realm/ErrorCode.java similarity index 99% rename from realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java rename to realm/realm-library/src/main/java/io/realm/ErrorCode.java index 326c717886..d3b1ca29bb 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/ErrorCode.java +++ b/realm/realm-library/src/main/java/io/realm/ErrorCode.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; /** * This class enumerate all potential errors related to using the Object Server or synchronizing data. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java b/realm/realm-library/src/main/java/io/realm/ObjectServerError.java similarity index 90% rename from realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java rename to realm/realm-library/src/main/java/io/realm/ObjectServerError.java index 27f061864c..fed409b9d2 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/ObjectServerError.java +++ b/realm/realm-library/src/main/java/io/realm/ObjectServerError.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import io.realm.internal.Util; @@ -26,7 +26,7 @@ * {@link #getErrorMessage()} is {@code null} and {@link #getException()} is set, while if the error was a protocol error * {@link #getErrorMessage()} is set and {@link #getException()} is null. * - * @see io.realm.objectserver.ErrorCode for a list of possible errors. + * @see ErrorCode for a list of possible errors. */ public class ObjectServerError extends RuntimeException { @@ -107,9 +107,9 @@ public Throwable getException() { } /** - * Returns the {@link io.realm.objectserver.ErrorCode.Category} category for this error. - * Errors that are {@link io.realm.objectserver.ErrorCode.Category#RECOVERABLE} mean that it is still possible for a - * given {@link Session} to resume synchronization. {@link io.realm.objectserver.ErrorCode.Category#FATAL} errors + * Returns the {@link ErrorCode.Category} category for this error. + * Errors that are {@link ErrorCode.Category#RECOVERABLE} mean that it is still possible for a + * given {@link Session} to resume synchronization. {@link ErrorCode.Category#FATAL} errors * means that session has stopped and cannot be recovered. * * @return the error category. diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 5a8697e203..1e49eba190 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -58,8 +58,7 @@ import io.realm.internal.Table; import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; -import io.realm.objectserver.SyncConfiguration; -import io.realm.objectserver.internal.ObjectServerFacade; +import io.realm.internal.objectserver.ObjectServerFacade; import rx.Observable; /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 0d7ec8c99d..fb60662c0a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -27,7 +27,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.internal.ColumnIndices; import io.realm.log.RealmLog; -import io.realm.objectserver.internal.ObjectServerFacade; +import io.realm.internal.objectserver.ObjectServerFacade; /** * To cache {@link Realm}, {@link DynamicRealm} instances and related resources. diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index df1ed4f051..71919125eb 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -27,8 +27,7 @@ import io.realm.annotations.Required; import io.realm.internal.Table; import io.realm.internal.TableOrView; -import io.realm.objectserver.SyncConfiguration; -import io.realm.objectserver.internal.ObjectServerFacade; +import io.realm.internal.objectserver.ObjectServerFacade; /** * Class for interacting with the schema for a given RealmObject class. This makes it possible to diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 27795005ba..3aa0f832ec 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -26,8 +26,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.Table; import io.realm.internal.Util; -import io.realm.objectserver.SyncConfiguration; -import io.realm.objectserver.internal.ObjectServerFacade; +import io.realm.internal.objectserver.ObjectServerFacade; /** * Class for interacting with the Realm schema using a dynamic API. This makes it possible diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/Session.java b/realm/realm-library/src/main/java/io/realm/Session.java similarity index 95% rename from realm/realm-library/src/main/java/io/realm/objectserver/Session.java rename to realm/realm-library/src/main/java/io/realm/Session.java index 61ad97ba89..94de8ea5a5 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/Session.java +++ b/realm/realm-library/src/main/java/io/realm/Session.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import java.net.URI; import io.realm.internal.Keep; import io.realm.log.RealmLog; -import io.realm.objectserver.internal.SyncSession; +import io.realm.internal.objectserver.SyncSession; /** * This class represents the connection to the Realm Object Server for one {@link SyncConfiguration}. @@ -99,7 +99,7 @@ protected void finalize() throws Throwable { * Interface used to report any session errors. * * @see SyncManager#setDefaultSessionErrorHandler(ErrorHandler) - * @see io.realm.objectserver.SyncConfiguration.Builder#errorHandler(ErrorHandler) + * @see SyncConfiguration.Builder#errorHandler(ErrorHandler) */ public interface ErrorHandler { /** diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java b/realm/realm-library/src/main/java/io/realm/SessionState.java similarity index 97% rename from realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java rename to realm/realm-library/src/main/java/io/realm/SessionState.java index a82d17e4ca..d3e167eae7 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SessionState.java +++ b/realm/realm-library/src/main/java/io/realm/SessionState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; /** * Enum describing the various states the Session Finite-State-Machine can be in. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java similarity index 98% rename from realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java rename to realm/realm-library/src/main/java/io/realm/SyncConfiguration.java index 3f2cf5ed43..a6891d9ffa 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import android.content.Context; @@ -24,16 +24,11 @@ import java.util.Arrays; import java.util.HashSet; -import io.realm.BaseRealm; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.RealmMigration; -import io.realm.RealmModel; import io.realm.annotations.RealmModule; import io.realm.internal.RealmProxyMediator; import io.realm.internal.SharedRealm; -import io.realm.objectserver.internal.syncpolicy.AutomaticSyncPolicy; -import io.realm.objectserver.internal.syncpolicy.SyncPolicy; +import io.realm.internal.syncpolicy.AutomaticSyncPolicy; +import io.realm.internal.syncpolicy.SyncPolicy; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java b/realm/realm-library/src/main/java/io/realm/SyncManager.java similarity index 96% rename from realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java rename to realm/realm-library/src/main/java/io/realm/SyncManager.java index 9900c5f22e..ce61081bfc 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/SyncManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; @@ -23,11 +23,10 @@ import io.realm.internal.Keep; import io.realm.internal.RealmCore; -import io.realm.objectserver.android.SharedPrefsUserStore; -import io.realm.objectserver.internal.SyncSession; -import io.realm.objectserver.internal.SessionStore; -import io.realm.objectserver.internal.network.AuthenticationServer; -import io.realm.objectserver.internal.network.OkHttpAuthenticationServer; +import io.realm.internal.objectserver.SyncSession; +import io.realm.internal.objectserver.SessionStore; +import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.OkHttpAuthenticationServer; import io.realm.log.RealmLog; /** diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/User.java b/realm/realm-library/src/main/java/io/realm/User.java similarity index 94% rename from realm/realm-library/src/main/java/io/realm/objectserver/User.java rename to realm/realm-library/src/main/java/io/realm/User.java index f5d210843d..14cba000ea 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/User.java +++ b/realm/realm-library/src/main/java/io/realm/User.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import android.os.Handler; import android.os.Looper; @@ -32,17 +32,15 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; -import io.realm.Realm; -import io.realm.RealmAsyncTask; import io.realm.internal.IOException; import io.realm.internal.Util; -import io.realm.objectserver.internal.SyncUser; -import io.realm.objectserver.internal.Token; -import io.realm.objectserver.internal.network.AuthenticateResponse; -import io.realm.objectserver.internal.network.AuthenticationServer; +import io.realm.internal.objectserver.SyncUser; +import io.realm.internal.objectserver.Token; +import io.realm.internal.network.AuthenticateResponse; +import io.realm.internal.network.AuthenticationServer; import io.realm.log.RealmLog; -import io.realm.objectserver.internal.network.ExponentialBackoffTask; -import io.realm.objectserver.internal.network.LogoutResponse; +import io.realm.internal.network.ExponentialBackoffTask; +import io.realm.internal.network.LogoutResponse; /** * This class represents a user on the Realm Object Server. @@ -104,7 +102,7 @@ public static User fromJson(String user) { * @param authenticationUrl Server that can authenticate against. * @throws ObjectServerError if the login failed. * - * @see io.realm.objectserver.SyncConfiguration.Builder#user(User) + * @see SyncConfiguration.Builder#user(User) */ public static User login(final Credentials credentials, final String authenticationUrl) throws ObjectServerError { final URL authUrl; @@ -143,7 +141,7 @@ public static User login(final Credentials credentials, final String authenticat * @param authenticationUrl Server that can authenticate against. * @param callback callback when login has completed or failed. This callback will always happen on the UI thread. * - * @see io.realm.objectserver.SyncConfiguration.Builder#user(User) + * @see SyncConfiguration.Builder#user(User) */ public static RealmAsyncTask loginAsync(final Credentials credentials, final String authenticationUrl, final Callback callback) { if (Looper.myLooper() == null) { @@ -283,8 +281,8 @@ public String toJson() { *

      * The user might still be have been logged out by the Realm Object Server which will not be detected before the * user tries to actively synchronize a Realm. If a logged out user tries to synchronize a Realm, an error will be - * reported to the {@link io.realm.objectserver.Session.ErrorHandler} defined by - * {@link io.realm.objectserver.SyncConfiguration.Builder#errorHandler(Session.ErrorHandler)}. + * reported to the {@link Session.ErrorHandler} defined by + * {@link SyncConfiguration.Builder#errorHandler(Session.ErrorHandler)}. * * @return {@code true} if the User is logged into the Realm Object Server, {@code false} otherwise. */ diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java b/realm/realm-library/src/main/java/io/realm/UserStore.java similarity index 95% rename from realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java rename to realm/realm-library/src/main/java/io/realm/UserStore.java index 1904bf1e9f..c55aaceaf8 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/UserStore.java +++ b/realm/realm-library/src/main/java/io/realm/UserStore.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import java.util.Collection; -import io.realm.objectserver.android.SharedPrefsUserStore; +import io.realm.android.SharedPrefsUserStore; /** * Interface for classes responsible for saving and retrieving Object Server users again. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java b/realm/realm-library/src/main/java/io/realm/android/SharedPrefsUserStore.java similarity index 96% rename from realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java rename to realm/realm-library/src/main/java/io/realm/android/SharedPrefsUserStore.java index 2b06c351d4..64653bd87a 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/android/SharedPrefsUserStore.java +++ b/realm/realm-library/src/main/java/io/realm/android/SharedPrefsUserStore.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver.android; +package io.realm.android; import android.content.Context; import android.content.SharedPreferences; @@ -23,8 +23,8 @@ import java.util.Collection; import java.util.Map; -import io.realm.objectserver.User; -import io.realm.objectserver.UserStore; +import io.realm.User; +import io.realm.UserStore; /** * A User Store backed by a SharedPreferences file. diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 03c2921051..eca1c49e27 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -21,9 +21,8 @@ import io.realm.RealmConfiguration; import io.realm.RealmSchema; -import io.realm.RealmFieldType; import io.realm.internal.async.BadVersionException; -import io.realm.objectserver.internal.ObjectServerFacade; +import io.realm.internal.objectserver.ObjectServerFacade; public final class SharedRealm implements Closeable { diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthServerResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthServerResponse.java similarity index 93% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthServerResponse.java rename to realm/realm-library/src/main/java/io/realm/internal/network/AuthServerResponse.java index d38b73a447..158f27f659 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthServerResponse.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthServerResponse.java @@ -1,4 +1,4 @@ -package io.realm.objectserver.internal.network; +package io.realm.internal.network; /* * Copyright 2016 Realm Inc. * @@ -18,8 +18,8 @@ import org.json.JSONException; import org.json.JSONObject; -import io.realm.objectserver.ErrorCode; -import io.realm.objectserver.ObjectServerError; +import io.realm.ErrorCode; +import io.realm.ObjectServerError; /** * Base class for all response types from the Realm Authentication Server. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java similarity index 94% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java rename to realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java index 17a7c08d56..0e946b4715 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateRequest.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver.internal.network; +package io.realm.internal.network; import org.json.JSONException; import org.json.JSONObject; @@ -23,9 +23,9 @@ import java.util.Collections; import java.util.Map; -import io.realm.objectserver.internal.Token; -import io.realm.objectserver.Credentials; -import io.realm.objectserver.SyncManager; +import io.realm.internal.objectserver.Token; +import io.realm.Credentials; +import io.realm.SyncManager; /** * This class encapsulates a request to authenticate a user on the Realm Authentication Server. It is responsible for diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java similarity index 95% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java rename to realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java index c610b747d3..a5a1c7f06c 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver.internal.network; +package io.realm.internal.network; import org.json.JSONException; import org.json.JSONObject; @@ -22,9 +22,9 @@ import java.io.IOException; import io.realm.log.RealmLog; -import io.realm.objectserver.ErrorCode; -import io.realm.objectserver.internal.Token; -import io.realm.objectserver.ObjectServerError; +import io.realm.ErrorCode; +import io.realm.internal.objectserver.Token; +import io.realm.ObjectServerError; import okhttp3.Response; /** diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java similarity index 87% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java rename to realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java index 4740b8e9af..4c15c64def 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.realm.objectserver.internal.network; +package io.realm.internal.network; import java.net.URI; import java.net.URL; -import io.realm.objectserver.User; -import io.realm.objectserver.internal.Token; -import io.realm.objectserver.Credentials; +import io.realm.User; +import io.realm.internal.objectserver.Token; +import io.realm.Credentials; /** * Interface for handling communication with Realm Object Servers. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/main/java/io/realm/internal/network/ExponentialBackoffTask.java similarity index 97% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/ExponentialBackoffTask.java rename to realm/realm-library/src/main/java/io/realm/internal/network/ExponentialBackoffTask.java index 59858aaf72..aa613c8357 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/ExponentialBackoffTask.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/ExponentialBackoffTask.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.realm.objectserver.internal.network; +package io.realm.internal.network; import java.util.concurrent.TimeUnit; -import io.realm.objectserver.ErrorCode; +import io.realm.ErrorCode; /** * Abstracts the concept of running an network task with incremental backoff. It will run forever until interrupted. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutRequest.java b/realm/realm-library/src/main/java/io/realm/internal/network/LogoutRequest.java similarity index 91% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutRequest.java rename to realm/realm-library/src/main/java/io/realm/internal/network/LogoutRequest.java index 1f62755ff8..3514369931 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutRequest.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/LogoutRequest.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.realm.objectserver.internal.network; +package io.realm.internal.network; -import io.realm.objectserver.User; +import io.realm.User; /** * This class encapsulates a request to logout a user on the Realm Authentication Server. It is responsible for diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/LogoutResponse.java similarity index 90% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutResponse.java rename to realm/realm-library/src/main/java/io/realm/internal/network/LogoutResponse.java index d723f47289..92557af144 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/LogoutResponse.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/LogoutResponse.java @@ -14,17 +14,13 @@ * limitations under the License. */ -package io.realm.objectserver.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; +package io.realm.internal.network; import java.io.IOException; import io.realm.log.RealmLog; -import io.realm.objectserver.ErrorCode; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.internal.Token; +import io.realm.ErrorCode; +import io.realm.ObjectServerError; import okhttp3.Response; /** diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/NetworkStateReceiver.java b/realm/realm-library/src/main/java/io/realm/internal/network/NetworkStateReceiver.java similarity index 98% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/NetworkStateReceiver.java rename to realm/realm-library/src/main/java/io/realm/internal/network/NetworkStateReceiver.java index 2a79eceea1..0e4e9a31d2 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/NetworkStateReceiver.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/NetworkStateReceiver.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver.internal.network; +package io.realm.internal.network; import android.content.BroadcastReceiver; import android.content.Context; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java similarity index 92% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java rename to realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 113ee969d5..d1cdd84421 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package io.realm.objectserver.internal.network; +package io.realm.internal.network; import java.net.URI; import java.net.URL; import java.util.concurrent.TimeUnit; import io.realm.internal.Util; -import io.realm.objectserver.ErrorCode; -import io.realm.objectserver.User; -import io.realm.objectserver.internal.Token; -import io.realm.objectserver.Credentials; -import io.realm.objectserver.ObjectServerError; +import io.realm.ErrorCode; +import io.realm.User; +import io.realm.internal.objectserver.Token; +import io.realm.Credentials; +import io.realm.ObjectServerError; import okhttp3.Call; import okhttp3.MediaType; import okhttp3.OkHttpClient; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java similarity index 89% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java rename to realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java index 0eaf52c04d..0c88d0b034 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/network/RefreshResponse.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.realm.objectserver.internal.network; +package io.realm.internal.network; -import io.realm.objectserver.internal.Token; +import io.realm.internal.objectserver.Token; import okhttp3.Response; public class RefreshResponse extends AuthServerResponse { diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java similarity index 95% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java index 3b74b2fead..402d36cc6e 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/AuthenticatingState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java @@ -14,11 +14,13 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; import io.realm.BaseRealm; -import io.realm.objectserver.*; -import io.realm.objectserver.internal.network.NetworkStateReceiver; +import io.realm.ObjectServerError; +import io.realm.Session; +import io.realm.SessionState; +import io.realm.internal.network.NetworkStateReceiver; /** * AUTHENTICATING State. This step is needed if the user does not have proper access or credentials to access this diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/BindingState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/BindingState.java similarity index 93% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/BindingState.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/BindingState.java index 1fa3ac17c8..83b6c9c168 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/BindingState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/BindingState.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.SessionState; +import io.realm.ObjectServerError; +import io.realm.SessionState; /** * BINDING State. After bind() is called, this state will attempt to bind the local Realm to the remote. This is an diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/BoundState.java similarity index 91% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/BoundState.java index d03632dae0..6ee9a85b81 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/BoundState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/BoundState.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; -import io.realm.objectserver.ErrorCode; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.SessionState; +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.SessionState; /** * BOUND State. At this state the local Realm is bound to the remote Realm and changes is sent in both diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmAction.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmAction.java similarity index 83% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmAction.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmAction.java index 781b653953..dafc865d72 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmAction.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmAction.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; -import io.realm.objectserver.*; +import io.realm.ObjectServerError; +import io.realm.Session; /** - * As {@link io.realm.objectserver.Session} is modeled as a state machine, this interface describe all + * As {@link Session} is modeled as a state machine, this interface describe all * possible actions in that machine. * * All states should implement this so all possible permutations of state/actions are covered. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmState.java similarity index 85% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmState.java index 833e282191..bbde585bfd 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/FsmState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmState.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.SessionState; +import io.realm.Session; +import io.realm.ObjectServerError; +import io.realm.SessionState; /** - * Abstract class containing shared logic for all {@link io.realm.objectserver.Session} states. All states must extend + * Abstract class containing shared logic for all {@link Session} states. All states must extend * this class as it contains the logic for entering and leaving states. * * TODO Move this to the Object Store @@ -34,7 +35,7 @@ abstract class FsmState implements FsmAction { * Entry into the state. This method is also responsible for executing any asynchronous work * this state might run. * - * This should only be called from {@link io.realm.objectserver.Session}. + * This should only be called from {@link Session}. */ public void entry(SyncSession session) { this.session = session; @@ -46,7 +47,7 @@ public void entry(SyncSession session) { * Called just before leaving the state. Once this method is called no more state changes can be triggered from * this state until {@link #entry(SyncSession)} has been called again. *

      - * This should only be called from {@link io.realm.objectserver.Session}. + * This should only be called from {@link Session}. */ public void exit() { exiting = true; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/InitialState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/InitialState.java similarity index 90% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/InitialState.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/InitialState.java index a5d173e6b4..ed13957250 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/InitialState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/InitialState.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.SessionState; +import io.realm.ObjectServerError; +import io.realm.SessionState; /** * INITIAL State. Starting point for the Session Finite-State-Machine. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/ObjectServerFacade.java similarity index 91% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/ObjectServerFacade.java index 7951004d49..bcac71164e 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/ObjectServerFacade.java @@ -1,9 +1,9 @@ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; import io.realm.RealmConfiguration; -import io.realm.objectserver.Session; -import io.realm.objectserver.SyncConfiguration; -import io.realm.objectserver.SyncManager; +import io.realm.Session; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; /** * Class acting as an mediator between the basic Realm APIs and the Object Server APIs. @@ -18,7 +18,7 @@ public class ObjectServerFacade { static { boolean syncAvailable; try { - Class.forName("io.realm.objectserver.SyncManager"); + Class.forName("io.realm.SyncManager"); syncAvailable = true; } catch (ClassNotFoundException e) { syncAvailable = false; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SessionStore.java similarity index 86% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/SessionStore.java index 786720cc0d..43d4409a33 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SessionStore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SessionStore.java @@ -1,17 +1,18 @@ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import io.realm.objectserver.Session; -import io.realm.objectserver.SyncConfiguration; +import io.realm.Session; +import io.realm.SyncManager; +import io.realm.SyncConfiguration; /** * Private class for keeping track of sessions. - * If {@link io.realm.objectserver.Session} and {@link SyncSession} are combined at some point, this class can - * be folded into {@link io.realm.objectserver.SyncManager}; + * If {@link Session} and {@link SyncSession} are combined at some point, this class can + * be folded into {@link SyncManager}; */ public class SessionStore { diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/StoppedState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/StoppedState.java similarity index 90% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/StoppedState.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/StoppedState.java index 82d9a036e8..2928c0e9c3 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/StoppedState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/StoppedState.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; -import io.realm.objectserver.*; +import io.realm.ObjectServerError; +import io.realm.Session; /** - * STOPPED State. This is the final state for a {@link io.realm.objectserver.Session}. After this, all actions will throw an + * STOPPED State. This is the final state for a {@link Session}. After this, all actions will throw an * {@link IllegalStateException}. */ class StoppedState extends FsmState { diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java similarity index 93% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java index 01551b1266..7a42631302 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncSession.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; import java.net.URI; import java.util.HashMap; @@ -23,22 +23,22 @@ import io.realm.RealmAsyncTask; import io.realm.internal.Keep; import io.realm.log.RealmLog; -import io.realm.objectserver.ErrorCode; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.Session; -import io.realm.objectserver.SessionState; -import io.realm.objectserver.SyncConfiguration; -import io.realm.objectserver.SyncManager; -import io.realm.objectserver.User; -import io.realm.objectserver.internal.network.AuthenticateResponse; -import io.realm.objectserver.internal.network.AuthenticationServer; -import io.realm.objectserver.internal.network.ExponentialBackoffTask; -import io.realm.objectserver.internal.network.NetworkStateReceiver; -import io.realm.objectserver.internal.syncpolicy.SyncPolicy; +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.Session; +import io.realm.SessionState; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.User; +import io.realm.internal.network.AuthenticateResponse; +import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.ExponentialBackoffTask; +import io.realm.internal.network.NetworkStateReceiver; +import io.realm.internal.syncpolicy.SyncPolicy; /** * Internal class describing a Realm Object Server Session. - * There is currently a split between the public {@link io.realm.objectserver.Session} and this class. + * There is currently a split between the public {@link Session} and this class. * This class is intended as a wrapper around Object Stores Sync Session, but it is not that yet. *

      * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening @@ -46,7 +46,7 @@ * underlying Realm file is deleted. *

      * It is normally not necessary to interact directly with a session. That should be done by the {@code SyncPolicy} - * defined using {@code io.realm.objectserver.SyncConfiguration.Builder#syncPolicy(SyncPolicy)}. + * defined using {@code io.realm.SyncConfiguration.Builder#syncPolicy(SyncPolicy)}. *

      * A session has a lifecycle consisting of the following states: *

      diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java similarity index 96% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java index b09540bb5f..fcbcb96e2b 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUser.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java @@ -1,4 +1,4 @@ -package io.realm.objectserver.internal;/* +package io.realm.internal.objectserver;/* * Copyright 2016 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,13 +31,13 @@ import java.util.concurrent.TimeUnit; import io.realm.RealmAsyncTask; -import io.realm.objectserver.Session; -import io.realm.objectserver.SyncConfiguration; -import io.realm.objectserver.SyncManager; -import io.realm.objectserver.User; -import io.realm.objectserver.internal.network.AuthenticationServer; -import io.realm.objectserver.internal.network.ExponentialBackoffTask; -import io.realm.objectserver.internal.network.RefreshResponse; +import io.realm.Session; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.User; +import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.ExponentialBackoffTask; +import io.realm.internal.network.RefreshResponse; /** * Internal representation of a user on the Realm Object Server. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUtil.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUtil.java similarity index 93% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUtil.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUtil.java index 675bcc7be0..397fb96976 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/SyncUtil.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUtil.java @@ -1,4 +1,4 @@ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; import java.net.URI; import java.net.URISyntaxException; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/Token.java similarity index 99% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/Token.java index ca1d3ae3af..78f0acfb04 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/Token.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/Token.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; import org.json.JSONArray; import org.json.JSONException; diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/UnboundState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/UnboundState.java similarity index 89% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/UnboundState.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/UnboundState.java index f2045abc3e..3471357544 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/UnboundState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/UnboundState.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package io.realm.objectserver.internal; +package io.realm.internal.objectserver; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.SessionState; -import io.realm.objectserver.internal.FsmState; +import io.realm.ObjectServerError; +import io.realm.SessionState; /** * UNBOUND State. This is the default state after a session has been started and no attempt at binding the local Realm diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java b/realm/realm-library/src/main/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java similarity index 93% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java rename to realm/realm-library/src/main/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java index 5941452ba1..4fd66809c5 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/AutomaticSyncPolicy.java +++ b/realm/realm-library/src/main/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java @@ -14,12 +14,10 @@ * limitations under the License. */ -package io.realm.objectserver.internal.syncpolicy; +package io.realm.internal.syncpolicy; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.internal.SyncSession; - -import static java.lang.System.currentTimeMillis; +import io.realm.ObjectServerError; +import io.realm.internal.objectserver.SyncSession; /** * This SyncPolicy will automatically start synchronizing changes to a Realm as soon as it is opened. diff --git a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java b/realm/realm-library/src/main/java/io/realm/internal/syncpolicy/SyncPolicy.java similarity index 89% rename from realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java rename to realm/realm-library/src/main/java/io/realm/internal/syncpolicy/SyncPolicy.java index 873092f090..2cae128bb9 100644 --- a/realm/realm-library/src/main/java/io/realm/objectserver/internal/syncpolicy/SyncPolicy.java +++ b/realm/realm-library/src/main/java/io/realm/internal/syncpolicy/SyncPolicy.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package io.realm.objectserver.internal.syncpolicy; +package io.realm.internal.syncpolicy; -import io.realm.objectserver.ObjectServerError; -import io.realm.objectserver.Session; -import io.realm.objectserver.internal.SyncSession; +import io.realm.ObjectServerError; +import io.realm.Session; +import io.realm.SyncConfiguration; +import io.realm.internal.objectserver.SyncSession; /** * Interface describing a given synchronization policy with the Realm Object Server. @@ -76,13 +77,13 @@ public interface SyncPolicy { * Called if an error occurred in the underlying session. In many cases this has caused the session to become * unbound. * - * @param error {@link io.realm.objectserver.ObjectServerError} object describing the error. + * @param error {@link ObjectServerError} object describing the error. * @return {@code true} if the error was handled, or {@code false} if it should be propagated further out to the * SyncConfigurations error handler. * * This method is always called from a background thread, never the UI thread. * - * @see io.realm.objectserver.SyncConfiguration.Builder#errorHandler(Session.ErrorHandler) + * @see SyncConfiguration.Builder#errorHandler(Session.ErrorHandler) */ boolean onError(SyncSession session, ObjectServerError error); } diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index 59532dac07..3693c3db0c 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -21,8 +21,8 @@ import io.realm.internal.Keep; import io.realm.internal.Util; -import io.realm.objectserver.SyncManager; -import io.realm.objectserver.internal.ObjectServerFacade; +import io.realm.SyncManager; +import io.realm.internal.objectserver.ObjectServerFacade; /** * Global logger used by all Realm components. From 7526eaae66e5cef4fa9e205ce64b246a73d0deb5 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 19 Sep 2016 20:15:57 +0200 Subject: [PATCH 0061/2110] If serverUrl is too long (>255 characters), MD5 of the URL is used instead (#100) * If serverUrl is too long (>256 characters for path, >255 for file name), MD5 of the URL is used instead. --- .../java/io/realm/SyncConfigurationTests.java | 47 ++++++++++++-- .../main/java/io/realm/SyncConfiguration.java | 61 ++++++++++++++++++- 2 files changed, 101 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java index 24d1fc39b1..9a2456daf0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java @@ -31,9 +31,11 @@ import io.realm.rule.RunInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; -import io.realm.util.SyncTestUtils; +import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -74,7 +76,7 @@ public void user_invalidUserThrows() { } catch (IllegalArgumentException ignore) { } - User user = SyncTestUtils.createTestUser(0); // Create user that has expired credentials + User user = createTestUser(0); // Create user that has expired credentials try { builder.user(user); } catch (IllegalArgumentException ignore) { @@ -83,7 +85,7 @@ public void user_invalidUserThrows() { @Test public void serverUrl_setsFolderAndFileName() { - User user = SyncTestUtils.createTestUser(); + User user = createTestUser(); String[][] validUrls = { // , , { "realm://objectserver.realm.io/~/default", "realm-object-server/" + user.getIdentity(), "default" }, @@ -127,6 +129,39 @@ public void serverUrl_invalidUrlThrows() { } } + private String makeServerUrl(int len) { + StringBuilder builder = new StringBuilder("realm://objectserver.realm.io/~/"); + for (int i = 0; i < len; i++) { + builder.append('A'); + } + return builder.toString(); + } + + @Test + public void serverUrl_length() { + int[] lengths = {1, SyncConfiguration.MAX_FILE_NAME_LENGTH - 1, + SyncConfiguration.MAX_FILE_NAME_LENGTH, SyncConfiguration.MAX_FILE_NAME_LENGTH + 1, 1000}; + + for (int len : lengths) { + SyncConfiguration.Builder builder = new SyncConfiguration.Builder(context) + .serverUrl(makeServerUrl(len)) + .user(createTestUser()); + + SyncConfiguration config = builder.build(); + assertTrue("Length: " + len, config.getRealmFileName().length() <= SyncConfiguration.MAX_FILE_NAME_LENGTH); + assertTrue("Length: " + len, config.getPath().length() <= SyncConfiguration.MAX_FULL_PATH_LENGTH); + } + } + + @Test + public void serverUrl_invalidChars() { + SyncConfiguration.Builder builder = new SyncConfiguration.Builder(context) + .serverUrl("realm://objectserver.realm.io/~/?") + .user(createTestUser()); + SyncConfiguration config = builder.build(); + assertFalse(config.getRealmFileName().contains("?")); + } + @Test public void userAndServerUrlRequired() { SyncConfiguration.Builder builder; @@ -140,7 +175,7 @@ public void userAndServerUrlRequired() { builder = new SyncConfiguration.Builder(context); try { - builder.user(SyncTestUtils.createTestUser(Long.MAX_VALUE)).build(); + builder.user(createTestUser(Long.MAX_VALUE)).build(); } catch (IllegalStateException ignore) { } @@ -156,7 +191,7 @@ public void userAndServerUrlRequired() { public void errorHandler() { SyncConfiguration.Builder builder; builder = new SyncConfiguration.Builder(context) - .user(SyncTestUtils.createTestUser()) + .user(createTestUser()) .serverUrl("realm://objectserver.realm.io/default"); Session.ErrorHandler errorHandler = new Session.ErrorHandler() { @@ -183,7 +218,7 @@ public void onError(Session session, ObjectServerError error) { // Create configuration using the default handler SyncConfiguration config = new SyncConfiguration.Builder(context) - .user(SyncTestUtils.createTestUser()) + .user(createTestUser()) .serverUrl("realm://objectserver.realm.io/default") .build(); assertEquals(errorHandler, config.getErrorHandler()); diff --git a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java index a6891d9ffa..d97ed9572f 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java @@ -19,12 +19,16 @@ import android.content.Context; import java.io.File; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashSet; import io.realm.annotations.RealmModule; +import io.realm.exceptions.RealmException; import io.realm.internal.RealmProxyMediator; import io.realm.internal.SharedRealm; import io.realm.internal.syncpolicy.AutomaticSyncPolicy; @@ -64,6 +68,12 @@ */ public final class SyncConfiguration extends RealmConfiguration { + // The FAT file system has limitations of length. Also, not all characters are permitted. + // https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + public static final int MAX_FULL_PATH_LENGTH = 256; + public static final int MAX_FILE_NAME_LENGTH = 255; + private static final char[] INVALID_CHARS = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'}; + private final URI serverUrl; private final User user; private final SyncPolicy syncPolicy; @@ -380,6 +390,11 @@ public Builder inMemory() { * * This behaviour can be overwritten using {@link #name(String)} and {@link #directory(File)}. * + * Many Android devices are using FAT32 file systems. FAT32 file systems have a limitation that + * file name cannot be longer than 255 characters. Moreover, the entire URL should not exceed 256 characters. + * If file name and underlying path are too long to handle for FAT32, a shorter unique name will be generated. + * See also @{link https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx}. + * * @param url URL identifying the Realm. * @throws IllegalArgumentException if the URL is not valid. */ @@ -459,6 +474,22 @@ public Builder errorHandler(Session.ErrorHandler errorHandler) { return this; } + private String MD5(String in) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] buf = digest.digest(in.getBytes("UTF-8")); + StringBuilder builder = new StringBuilder(); + for (byte b : buf) { + builder.append(String.format("%02X", b)); + } + return builder.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RealmException(e.getMessage()); + } catch (UnsupportedEncodingException e) { + throw new RealmException(e.getMessage()); + } + } + /** * Setting this will cause the local Realm file used to synchronize changes to be deleted if the {@link User} * defined by {@link #user(User)} logs out from the device using {@link User#logout()}. @@ -495,11 +526,39 @@ public SyncConfiguration build() { File rootDir = overrideDefaultFolder ? directory : defaultFolder; String realmPathFromRootDir = getServerPath(resolvedServerUrl); File realmFileDirectory = new File(rootDir, realmPathFromRootDir); + + String realmFileName = overrideDefaultLocalFileName ? fileName : defaultLocalFileName; + String fullPathName = realmFileDirectory.getAbsolutePath() + File.pathSeparator + realmFileName; + // full path must not exceed 256 characters (on FAT) + if (fullPathName.length() > MAX_FULL_PATH_LENGTH) { + // path is too long, so we make the file name shorter + realmFileName = MD5(realmFileName); + fullPathName = realmFileDirectory.getAbsolutePath() + File.pathSeparator + realmFileName; + if (fullPathName.length() > MAX_FULL_PATH_LENGTH) { + // use rootDir/userIdentify as directory instead as it is shorter + realmFileDirectory = new File(rootDir, user.getIdentity()); + fullPathName = realmFileDirectory.getAbsolutePath() + File.pathSeparator + realmFileName; + if (fullPathName.length() > MAX_FULL_PATH_LENGTH) { // we are out of ideas + throw new IllegalStateException(String.format("Full path name must not exceed %d characters: %s", + MAX_FULL_PATH_LENGTH, fullPathName)); + } + } + } + + if (realmFileName.length() > MAX_FILE_NAME_LENGTH) { + throw new IllegalStateException(String.format("File name exceed %d characters: %d", MAX_FILE_NAME_LENGTH, + realmFileName.length())); + } + + // substitute invalid characters + for (char c : INVALID_CHARS) { + realmFileName = realmFileName.replace(c, '_'); + } + // Create the folder on disk (if needed) if (!realmFileDirectory.exists() && !realmFileDirectory.mkdirs()) { throw new IllegalStateException("Could not create directory for saving the Realm: " + realmFileDirectory); } - String realmFileName = overrideDefaultLocalFileName ? fileName : defaultLocalFileName; return new SyncConfiguration( // Realm Configuration options From 52ed654b1d25064539e844beb72d886b0243e3ea Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 19 Sep 2016 20:21:27 +0200 Subject: [PATCH 0062/2110] New name for core distribution package. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 22df2e0956..7d38506f03 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ local.properties # Core core core-* +realm-sync-android-* # Android Studio .idea From 465e05f0e7c4bea788974a9ff5ff54b891fe430f Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 19 Sep 2016 20:25:55 +0200 Subject: [PATCH 0063/2110] New name for core distribution package. (#119) From 9e0c3436ef9f25ba94195286e675e4b620199118 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 19 Sep 2016 23:27:27 -0500 Subject: [PATCH 0064/2110] Sync facade to make spliting lib possible (#116) * Sync facade to make spliting lib possible * Add class SyncObjectServerFacade which will only exist in the sync lib. * Check if SyncObjectServerFacade exists and create an singleton instance. * Empty implementations for base ObjectServerFacade. * Add RealmConfiguration.isSyncConfiguration to make the checks faster. * Other cleanups. Close #112 --- .../src/main/java/io/realm/BaseRealm.java | 20 ++++-- .../src/main/java/io/realm/Realm.java | 3 +- .../src/main/java/io/realm/RealmCache.java | 7 +- .../java/io/realm/RealmConfiguration.java | 5 ++ .../main/java/io/realm/RealmObjectSchema.java | 18 ++--- .../src/main/java/io/realm/RealmSchema.java | 10 +-- .../main/java/io/realm/SyncConfiguration.java | 5 ++ .../io/realm/internal/ObjectServerFacade.java | 58 +++++++++++++++++ .../java/io/realm/internal/SharedRealm.java | 5 +- .../objectserver/ObjectServerFacade.java | 65 ------------------- .../objectserver/SyncObjectServerFacade.java | 60 +++++++++++++++++ .../src/main/java/io/realm/log/RealmLog.java | 6 +- 12 files changed, 159 insertions(+), 103 deletions(-) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/objectserver/ObjectServerFacade.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 388801da60..2836c6c170 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -19,9 +19,6 @@ import android.content.Context; import android.os.Handler; import android.os.Looper; -import android.util.Log; - -import com.getkeepsafe.relinker.BuildConfig; import java.io.Closeable; import java.io.File; @@ -41,9 +38,8 @@ import io.realm.internal.UncheckedRow; import io.realm.internal.Util; import io.realm.internal.async.RealmThreadPoolExecutor; -import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; -import io.realm.internal.objectserver.ObjectServerFacade; +import io.realm.internal.ObjectServerFacade; import rx.Observable; /** @@ -77,6 +73,7 @@ public abstract class BaseRealm implements Closeable { RealmSchema schema; HandlerController handlerController; + protected BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; @@ -350,7 +347,8 @@ public void commitTransaction() { void commitTransaction(boolean notifyLocalThread) { checkIfValid(); sharedRealm.commitTransaction(); - ObjectServerFacade.notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); + ObjectServerFacade.getFacade(configuration.isSyncConfiguration()) + .notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); // Sometimes we don't want to notify the local thread about commits, e.g. creating a completely new Realm // file will make a commit in order to create the schema. Users should not be notified about that. @@ -401,6 +399,16 @@ protected void checkIfValidAndInTransaction() { } } + /** + * Check if the Realm is not built with a SyncRealmConfiguration + */ + void checkNotInSync() { + if (configuration.isSyncConfiguration()) { + throw new IllegalArgumentException("You cannot perform changes to a schema. " + + "Please update app and restart."); + } + } + /** * Returns the canonical path to where this Realm is persisted on disk. * diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 1e49eba190..5c7cbeb302 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -58,7 +58,6 @@ import io.realm.internal.Table; import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; -import io.realm.internal.objectserver.ObjectServerFacade; import rx.Observable; /** @@ -314,7 +313,7 @@ static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices[] private static void initializeRealm(Realm realm) { long version = realm.getVersion(); boolean commitNeeded = false; - boolean syncAvailable = ObjectServerFacade.SYNC_AVAILABLE && realm.configuration instanceof SyncConfiguration; + boolean syncAvailable = realm.configuration.isSyncConfiguration(); try { if (!syncAvailable) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index fb60662c0a..002b09f214 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -27,7 +27,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.internal.ColumnIndices; import io.realm.log.RealmLog; -import io.realm.internal.objectserver.ObjectServerFacade; +import io.realm.internal.ObjectServerFacade; /** * To cache {@link Realm}, {@link DynamicRealm} instances and related resources. @@ -159,7 +159,7 @@ static synchronized E createRealmOrGetFromCache(RealmConfi // Notify SyncPolicy that the Realm has been opened for the first time if (refAndCount.globalCount == 1) { - ObjectServerFacade.realmOpened(configuration); + ObjectServerFacade.getFacade(configuration.isSyncConfiguration()).realmOpened(configuration); } return realm; } @@ -223,7 +223,8 @@ static synchronized void release(BaseRealm realm) { // No more instance of typed Realm and dynamic Realm. Remove the configuration from cache. if (totalRefCount == 0) { cachesMap.remove(canonicalPath); - ObjectServerFacade.realmClosed(realm.getConfiguration()); + ObjectServerFacade.getFacade(realm.getConfiguration().isSyncConfiguration()) + .realmClosed(realm.getConfiguration()); } } else { diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 28c6745603..06775f7abc 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -360,6 +360,11 @@ protected static String getCanonicalPath(File realmFile) { } } + // Check if this configuration is a SyncConfiguration instance. + boolean isSyncConfiguration() { + return false; + } + /** * RealmConfiguration.Builder used to construct instances of a RealmConfiguration in a fluent manner. */ diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 71919125eb..8c7e6b07d1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -27,7 +27,6 @@ import io.realm.annotations.Required; import io.realm.internal.Table; import io.realm.internal.TableOrView; -import io.realm.internal.objectserver.ObjectServerFacade; /** * Class for interacting with the schema for a given RealmObject class. This makes it possible to @@ -152,7 +151,7 @@ public String getClassName() { * @see RealmSchema#rename(String, String) */ public RealmObjectSchema setClassName(String className) { - checkNotInSync(); // renaming a table is not permitted + realm.checkNotInSync(); // renaming a table is not permitted checkEmpty(className); String internalTableName = Table.TABLE_PREFIX + className; //FIXME : when core implements class name length check, please remove. @@ -292,7 +291,7 @@ private Set getProperties() { * @throws IllegalArgumentException if field name doesn't exist. */ public RealmObjectSchema removeField(String fieldName) { - checkNotInSync(); // destructive modification of a schema is not permitted + realm.checkNotInSync(); // destructive modification of a schema is not permitted checkLegalName(fieldName); if (!hasField(fieldName)) { throw new IllegalStateException(fieldName + " does not exist."); @@ -314,7 +313,7 @@ public RealmObjectSchema removeField(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or if the new field name already exists. */ public RealmObjectSchema renameField(String currentFieldName, String newFieldName) { - checkNotInSync(); // destructive modification of a schema is not permitted + realm.checkNotInSync(); // destructive modification of a schema is not permitted checkLegalName(currentFieldName); checkFieldExists(currentFieldName); checkLegalName(newFieldName); @@ -380,7 +379,7 @@ public boolean hasIndex(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or the field doesn't have an index. */ public RealmObjectSchema removeIndex(String fieldName) { - checkNotInSync(); // destructive modifications are not permitted + realm.checkNotInSync(); // destructive modifications are not permitted checkLegalName(fieldName); checkFieldExists(fieldName); long columnIndex = getColumnIndex(fieldName); @@ -423,7 +422,7 @@ public RealmObjectSchema addPrimaryKey(String fieldName) { * @throws IllegalArgumentException if the class doesn't have a primary key defined. */ public RealmObjectSchema removePrimaryKey() { - checkNotInSync(); // destructive modifications are not permitted + realm.checkNotInSync(); // destructive modifications are not permitted if (!table.hasPrimaryKey()) { throw new IllegalStateException(getClassName() + " doesn't have a primary key."); } @@ -662,13 +661,6 @@ private void checkEmpty(String str) { } } - private void checkNotInSync() { - // FIXME: similar method found in RealmSchema. - if (ObjectServerFacade.SYNC_AVAILABLE && realm.configuration instanceof SyncConfiguration) { - throw new IllegalArgumentException("You cannot perform changes to a schema. Please update app and restart."); - } - } - /** * Returns the column indices for the given field name. If a linked field is defined, the column index for * each field is returned. diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 3aa0f832ec..99264bf6b1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -26,7 +26,6 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.Table; import io.realm.internal.Util; -import io.realm.internal.objectserver.ObjectServerFacade; /** * Class for interacting with the Realm schema using a dynamic API. This makes it possible @@ -188,7 +187,7 @@ public RealmObjectSchema create(String className) { * @param className name of the class to remove. */ public void remove(String className) { - checkNotInSync(); // destructive modifications are not permitted + realm.checkNotInSync(); // destructive modifications are not permitted checkEmpty(className, EMPTY_STRING_MSG); String internalTableName = TABLE_PREFIX + className; checkHasTable(className, "Cannot remove class because it is not in this Realm: " + className); @@ -207,7 +206,7 @@ public void remove(String className) { * @return a schema object for renamed class. */ public RealmObjectSchema rename(String oldClassName, String newClassName) { - checkNotInSync(); // destructive modifications are not permitted + realm.checkNotInSync(); // destructive modifications are not permitted checkEmpty(oldClassName, "Class names cannot be empty or null"); checkEmpty(newClassName, "Class names cannot be empty or null"); String oldInternalName = TABLE_PREFIX + oldClassName; @@ -251,11 +250,6 @@ public boolean contains(String className) { } } - private void checkNotInSync() { - if (ObjectServerFacade.SYNC_AVAILABLE && realm.configuration instanceof SyncConfiguration) { - throw new IllegalArgumentException("You cannot perform changes to a schema. Please update app and restart."); - } - } private void checkEmpty(String str, String error) { if (str == null || str.isEmpty()) { throw new IllegalArgumentException(error); diff --git a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java index d97ed9572f..80698a599f 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java @@ -208,6 +208,11 @@ public boolean shouldDeleteRealmOnLogout() { return deleteRealmOnLogout; } + @Override + boolean isSyncConfiguration() { + return true; + } + /** * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java new file mode 100644 index 0000000000..331ea7de1f --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -0,0 +1,58 @@ +package io.realm.internal; + +import io.realm.RealmConfiguration; +import io.realm.exceptions.RealmException; + +/** + * Class acting as an mediator between the basic Realm APIs and the Object Server APIs. + * This breaks the cyclic dependency between ObjectServer and Realm code. + */ +public class ObjectServerFacade { + + private final static ObjectServerFacade nonSyncFacade = new ObjectServerFacade(); + private static ObjectServerFacade syncFacade = null; + + static { + //noinspection TryWithIdenticalCatches + try { + Class syncFacadeClass = Class.forName("io.realm.internal.objectserver.SyncObjectServerFacade"); + syncFacade = (ObjectServerFacade) syncFacadeClass.newInstance(); + } catch (ClassNotFoundException ignored) { + } catch (InstantiationException e) { + throw new RealmException("Failed to init SyncObjectServerFacade", e); + } catch (IllegalAccessException e) { + throw new RealmException("Failed to init SyncObjectServerFacade", e); + } + } + + /** + * Notify the session for this configuration that a local commit was made. + */ + public void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { + } + + public void realmClosed(RealmConfiguration configuration) { + } + + public void realmOpened(RealmConfiguration configuration) { + } + + public String[] getUserAndServerUrl(RealmConfiguration config) { + return new String[2]; + } + + public static ObjectServerFacade getFacade(boolean needSyncFacade) { + if (needSyncFacade) { + return syncFacade; + } + return nonSyncFacade; + } + + // Returns a SyncObjectServerFacade instance if the class exists. Otherwise returns a non-sync one. + static ObjectServerFacade getSyncFacadeIfPossible() { + if (syncFacade != null) { + return syncFacade; + } + return nonSyncFacade; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index eca1c49e27..347807c0b5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -22,7 +22,6 @@ import io.realm.RealmConfiguration; import io.realm.RealmSchema; import io.realm.internal.async.BadVersionException; -import io.realm.internal.objectserver.ObjectServerFacade; public final class SharedRealm implements Closeable { @@ -76,6 +75,7 @@ public byte getNativeValue() { // JNI will only hold a weak global ref to this. public final RealmNotifier realmNotifier; + public final ObjectServerFacade objectServerFacade; public static class VersionID implements Comparable { public final long version; @@ -148,6 +148,7 @@ private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotif this.schemaChangeListener = schemaVersionListener; context = new Context(); this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion(); + objectServerFacade = null; } public static SharedRealm getInstance(RealmConfiguration config) { @@ -156,7 +157,7 @@ public static SharedRealm getInstance(RealmConfiguration config) { public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier realmNotifier, SchemaVersionListener schemaVersionListener) { - String[] userAndServer = ObjectServerFacade.getUserAndServerUrl(config); + String[] userAndServer = ObjectServerFacade.getSyncFacadeIfPossible().getUserAndServerUrl(config); String rosServerUrl = userAndServer[0]; String rosUserToken = userAndServer[1]; boolean enable_caching = false; // Handled in Java currently diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/ObjectServerFacade.java deleted file mode 100644 index bcac71164e..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/ObjectServerFacade.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.realm.internal.objectserver; - -import io.realm.RealmConfiguration; -import io.realm.Session; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; - -/** - * Class acting as an mediator between the basic Realm APIs and the Object Server APIs. - * This breaks the cyclic dependency between ObjectServer and Realm code. - * - * TODO Move this class into a `common` module that both realm-library and objectserver-library depends on. - */ -public class ObjectServerFacade { - - public static final boolean SYNC_AVAILABLE; - - static { - boolean syncAvailable; - try { - Class.forName("io.realm.SyncManager"); - syncAvailable = true; - } catch (ClassNotFoundException e) { - syncAvailable = false; - } - SYNC_AVAILABLE = syncAvailable; - } - /** - * Notify the session for this configuration that a local commit was made. - */ - public static void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { - if (SYNC_AVAILABLE && configuration instanceof SyncConfiguration) { - Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); - SyncSession session = SessionStore.getPrivateSession(publicSession); - session.notifyCommit(lastSnapshotVersion); - } - } - - public static void realmClosed(RealmConfiguration configuration) { - if (SYNC_AVAILABLE && configuration instanceof SyncConfiguration) { - Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); - SyncSession session = SessionStore.getPrivateSession(publicSession); - session.getSyncPolicy().onRealmClosed(session); - } - } - - public static void realmOpened(RealmConfiguration configuration) { - if (SYNC_AVAILABLE && configuration instanceof SyncConfiguration) { - Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); - SyncSession session = SessionStore.getPrivateSession(publicSession); - session.getSyncPolicy().onRealmOpened(session); - } - } - - public static String[] getUserAndServerUrl(RealmConfiguration config) { - if (SYNC_AVAILABLE && config instanceof SyncConfiguration) { - SyncConfiguration syncConfig = (SyncConfiguration) config; - String rosServerUrl = syncConfig.getServerUrl().toString(); - String rosUserToken = syncConfig.getUser().getAccessToken(); - return new String[] {rosServerUrl, rosUserToken}; - } else { - return new String[2]; - } - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java new file mode 100644 index 0000000000..5740a201ad --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java @@ -0,0 +1,60 @@ +package io.realm.internal.objectserver; + + +import io.realm.RealmConfiguration; +import io.realm.Session; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.internal.ObjectServerFacade; + +@SuppressWarnings("unused") // Used through reflection. See ObjectServerFacade +public class SyncObjectServerFacade extends ObjectServerFacade { + + private static final String WRONG_TYPE_OF_CONFIGURATION = + "'configuration' has to be an instance of 'SyncConfiguration'."; + + @Override + public void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { + if (configuration instanceof SyncConfiguration) { + Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); + SyncSession session = SessionStore.getPrivateSession(publicSession); + session.notifyCommit(lastSnapshotVersion); + } else { + throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); + } + } + + @Override + public void realmClosed(RealmConfiguration configuration) { + if (configuration instanceof SyncConfiguration) { + Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); + SyncSession session = SessionStore.getPrivateSession(publicSession); + session.getSyncPolicy().onRealmClosed(session); + } else { + throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); + } + } + + @Override + public void realmOpened(RealmConfiguration configuration) { + if (configuration instanceof SyncConfiguration) { + Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); + SyncSession session = SessionStore.getPrivateSession(publicSession); + session.getSyncPolicy().onRealmOpened(session); + } else { + throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); + } + } + + @Override + public String[] getUserAndServerUrl(RealmConfiguration config) { + if (config instanceof SyncConfiguration) { + SyncConfiguration syncConfig = (SyncConfiguration) config; + String rosServerUrl = syncConfig.getServerUrl().toString(); + String rosUserToken = syncConfig.getUser().getAccessToken(); + return new String[] {rosServerUrl, rosUserToken}; + } else { + return new String[2]; + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index 3693c3db0c..6389196faa 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -22,7 +22,7 @@ import io.realm.internal.Keep; import io.realm.internal.Util; import io.realm.SyncManager; -import io.realm.internal.objectserver.ObjectServerFacade; +import io.realm.internal.ObjectServerFacade; /** * Global logger used by all Realm components. @@ -59,10 +59,8 @@ public static void add(Logger logger) { private static void setMinimumNativeDebugLevel(int nativeDebugLevel) { minimumNativeLogLevel = nativeDebugLevel; + // FIXME: Use same log level setting for normal Realm and Sync Realm. Util.setDebugLevel(nativeDebugLevel); // Log level for Realm Core - if (ObjectServerFacade.SYNC_AVAILABLE) { - SyncManager.setLogLevel(nativeDebugLevel); - } } /** From c18170146484594f8c0f2cd983b3abd8c168e986 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 20 Sep 2016 07:46:43 +0200 Subject: [PATCH 0065/2110] Bump to build tools 2.2.0 (#3467) --- examples/build.gradle | 2 +- examples/gradle/wrapper/gradle-wrapper.properties | 2 +- .../java/io/realm/examples/threads/AsyncQueryFragment.java | 2 +- examples/threadExample/src/main/res/values/strings.xml | 2 +- gradle-plugin/gradle/wrapper/gradle-wrapper.properties | 2 +- .../src/test/groovy/io/realm/gradle/PluginTest.groovy | 4 ++-- realm-annotations/gradle/wrapper/gradle-wrapper.properties | 2 +- realm-transformer/gradle/wrapper/gradle-wrapper.properties | 2 +- realm/build.gradle | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 47be4e4ca1..64b863d9f8 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -16,7 +16,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java index 6d730bce93..3fdde6744e 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java @@ -178,7 +178,7 @@ public View getView(int i, View view, ViewGroup viewGroup) { view.setTag(viewHolder); } ViewHolder vh = (ViewHolder) view.getTag(); - vh.text.setText(view.getResources().getString(R.string.coordinate, getItem(i).getX(),getItem(i).getY())); + vh.text.setText(view.getResources().getString(R.string.coordinate, getItem(i).getX(), getItem(i).getY())); return view; } diff --git a/examples/threadExample/src/main/res/values/strings.xml b/examples/threadExample/src/main/res/values/strings.xml index b1480bd6cc..240767c073 100644 --- a/examples/threadExample/src/main/res/values/strings.xml +++ b/examples/threadExample/src/main/res/values/strings.xml @@ -13,7 +13,7 @@ Object Passing Passing to Intent Service Passing To Receiver - [X= %1$s Y= %2$s] + [X= %1$d Y= %2$d] Start diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index d73ef15836..c99d4f53b4 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -53,7 +53,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } @@ -78,7 +78,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/realm/build.gradle b/realm/build.gradle index 3be5c2794e..429b20fbe0 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0-beta2' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' From 838e403b885c4c41fe23214c1f910e26bf6db71c Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 20 Sep 2016 09:19:35 +0200 Subject: [PATCH 0066/2110] Simple URL validation (#114) Simple URL validation --- .../java/io/realm/SyncConfigurationTests.java | 6 ++++ .../main/java/io/realm/SyncConfiguration.java | 31 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java index 9a2456daf0..18f507397e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java @@ -115,8 +115,14 @@ public void serverUrl_invalidUrlThrows() { // "objectserver.realm.io/~/default", // Missing protocol. TODO Should we just default to one? // "/~/default", // Missing server "realm://objectserver.realm.io/~/default.realm", // Ending with .realm + "realm://objectserver.realm.io/~/default.realm.lock", // Ending with .realm.lock + "realm://objectserver.realm.io/~/default.realm.management", // Ending with .realm.management "realm://objectserver.realm.io/<~>/default.realm", // Invalid chars <> "realm://objectserver.realm.io/~/default.realm/", // Ending with / + "realm://objectserver.realm.io/~/Αθήνα", // Non-ascii + "realm://objectserver.realm.io/~/foo/../bar", // .. is not allowed + "realm://objectserver.realm.io/~/foo/./bar", // . is not allowed + "http://objectserver.realm.io/~/default", // wrong scheme }; SyncConfiguration.Builder builder = new SyncConfiguration.Builder(context); diff --git a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java index 80698a599f..9b6c044fc7 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java @@ -26,7 +26,8 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashSet; - +import java.util.regex.Matcher; +import java.util.regex.Pattern; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.RealmProxyMediator; @@ -235,6 +236,8 @@ public static final class Builder { private String defaultLocalFileName; private SharedRealm.Durability durability = SharedRealm.Durability.FULL; private boolean deleteRealmOnLogout = false; + private final Pattern pattern = Pattern.compile("^[A-Za-z0-9_\\-\\.]+$"); // for checking serverUrl + /** * Creates an instance of the Builder for the SyncConfiguration. @@ -413,6 +416,12 @@ public Builder serverUrl(String url) { throw new IllegalArgumentException("Invalid url: " + url, e); } + // scheme must be realm or realms + String scheme = serverUrl.getScheme(); + if (!scheme.equals("realm") && !scheme.equals("realms")) { + throw new IllegalArgumentException("Invalid scheme: " + scheme); + } + // Detect last path segment as it is the default file name String path = serverUrl.getPath(); if (path == null) { @@ -420,12 +429,28 @@ public Builder serverUrl(String url) { } String[] pathSegments = path.split("/"); + for (int i = 1; i < pathSegments.length; i++) { + String segment = pathSegments[i]; + if (segment.equals("~")) { + continue; + } + if (segment.equals("..") || segment.equals(".")) { + throw new IllegalArgumentException("The URL has an invalid segment: " + segment); + } + Matcher m = pattern.matcher(segment); + if (!m.matches()) { + throw new IllegalArgumentException("The URL must only contain characters 0-9, a-z, A-Z, ., _, and -: " + segment); + } + } + this.defaultLocalFileName = pathSegments[pathSegments.length - 1]; // Validate filename // TODO Lift this restriction on the Object Server - if (defaultLocalFileName.endsWith(".realm")) { - throw new IllegalArgumentException("The URL must not end with '.realm': " + url); + if (defaultLocalFileName.endsWith(".realm") + || defaultLocalFileName.endsWith(".realm.lock") + || defaultLocalFileName.endsWith(".realm.management")) { + throw new IllegalArgumentException("The URL must not end with '.realm', '.realm.lock' or '.realm.management: " + url); } return this; From 0f19798cbb4868329a406638521d78ad91624f15 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 20 Sep 2016 11:02:26 +0200 Subject: [PATCH 0067/2110] Setting the server port if not specified by user. --- .../java/io/realm/SyncConfigurationTests.java | 19 +++++++++++++++++++ .../main/java/io/realm/SyncConfiguration.java | 19 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java index 18f507397e..00d690d475 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java @@ -28,6 +28,8 @@ import org.junit.runner.RunWith; import java.io.File; +import java.util.HashMap; +import java.util.Map; import io.realm.rule.RunInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -168,6 +170,23 @@ public void serverUrl_invalidChars() { assertFalse(config.getRealmFileName().contains("?")); } + @Test + public void serverUrl_port() { + Map urlPort = new HashMap(); + urlPort.put("realm://objectserver.realm.io/~/default", 80); + urlPort.put("realms://objectserver.realm.io/~/default", 443); + urlPort.put("realm://objectserver.realm.io:8080/~/default", 8080); + urlPort.put("realms://objectserver.realm.io:2443/~/default", 2443); + + for (String url : urlPort.keySet()) { + SyncConfiguration config = new SyncConfiguration.Builder(context) + .serverUrl(url) + .user(createTestUser()) + .build(); + assertEquals(urlPort.get(url).intValue(), config.getServerUrl().getPort()); + } + } + @Test public void userAndServerUrlRequired() { SyncConfiguration.Builder builder; diff --git a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java index 9b6c044fc7..957f09ebbf 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java @@ -410,8 +410,10 @@ public Builder serverUrl(String url) { if (url == null) { throw new IllegalArgumentException("Non-null 'url' required."); } + + URI serverUrl; try { - this.serverUrl = new URI(url); + serverUrl = new URI(url); } catch (URISyntaxException e) { throw new IllegalArgumentException("Invalid url: " + url, e); } @@ -422,6 +424,15 @@ public Builder serverUrl(String url) { throw new IllegalArgumentException("Invalid scheme: " + scheme); } + // set port if not set by user + int port; + int currentPort = serverUrl.getPort(); + if (currentPort == -1) { + port = scheme.equals("realm") ? 80 : 443; + } else { + port = currentPort; + } + // Detect last path segment as it is the default file name String path = serverUrl.getPath(); if (path == null) { @@ -453,6 +464,12 @@ public Builder serverUrl(String url) { throw new IllegalArgumentException("The URL must not end with '.realm', '.realm.lock' or '.realm.management: " + url); } + try { + this.serverUrl = new URI(scheme, serverUrl.getUserInfo(), serverUrl.getHost(), + port, serverUrl.getPath(), serverUrl.getQuery(), serverUrl.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot reconstruct url: " + url, e); + } return this; } From 4b8901fd4b6e8eba13c7c8b06f17b04cbeb589a1 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 20 Sep 2016 12:19:52 +0200 Subject: [PATCH 0068/2110] Setting the server port if not specified by user. (#121) Setting the server port if not specified by user. --- .../androidTest/java/io/realm/SyncConfigurationTests.java | 4 ++-- .../src/main/java/io/realm/SyncConfiguration.java | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java index 00d690d475..457637cb66 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java @@ -173,8 +173,8 @@ public void serverUrl_invalidChars() { @Test public void serverUrl_port() { Map urlPort = new HashMap(); - urlPort.put("realm://objectserver.realm.io/~/default", 80); - urlPort.put("realms://objectserver.realm.io/~/default", 443); + urlPort.put("realm://objectserver.realm.io/~/default", SyncConfiguration.PORT_REALM); + urlPort.put("realms://objectserver.realm.io/~/default", SyncConfiguration.PORT_REALMS); urlPort.put("realm://objectserver.realm.io:8080/~/default", 8080); urlPort.put("realms://objectserver.realm.io:2443/~/default", 2443); diff --git a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java index 957f09ebbf..e8cb2b0ad7 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java @@ -69,6 +69,9 @@ */ public final class SyncConfiguration extends RealmConfiguration { + public static final int PORT_REALM = 80; + public static final int PORT_REALMS = 443; + // The FAT file system has limitations of length. Also, not all characters are permitted. // https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx public static final int MAX_FULL_PATH_LENGTH = 256; @@ -428,7 +431,7 @@ public Builder serverUrl(String url) { int port; int currentPort = serverUrl.getPort(); if (currentPort == -1) { - port = scheme.equals("realm") ? 80 : 443; + port = scheme.equals("realm") ? PORT_REALM : PORT_REALMS; } else { port = currentPort; } From e59095296d8f71572087196d71f1cfc83573933a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 20 Sep 2016 13:41:24 +0200 Subject: [PATCH 0069/2110] Make Sync API take advantage of global init (#115) --- .../objectserver/CounterActivity.java | 7 +- realm/config/findbugs/findbugs-filter.xml | 5 + .../java/io/realm/SchemaTests.java | 8 +- .../java/io/realm/SyncConfigurationTests.java | 73 ++---- .../src/main/java/io/realm/ObjectServer.java | 31 +++ .../src/main/java/io/realm/Realm.java | 2 + .../main/java/io/realm/SyncConfiguration.java | 223 +++++++++--------- .../src/main/java/io/realm/SyncManager.java | 23 +- .../src/main/java/io/realm/User.java | 4 - .../io/realm/internal/ObjectServerFacade.java | 19 +- .../SyncObjectServerFacade.java | 32 ++- .../internal/objectserver/SyncSession.java | 6 +- 12 files changed, 230 insertions(+), 203 deletions(-) create mode 100644 realm/realm-library/src/main/java/io/realm/ObjectServer.java rename realm/realm-library/src/main/java/io/realm/internal/{objectserver => }/SyncObjectServerFacade.java (64%) diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index c7ce08f8d5..4ba665ff4a 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -37,10 +37,13 @@ public class CounterActivity extends AppCompatActivity { + private static final String REALM_URL = "realm://" + MyApplication.OBJECT_SERVER_IP + "/~/default"; + private Realm realm; private RealmResults counter; private User user; + @BindView(R.id.text_counter) TextView counterView; @Override @@ -61,15 +64,13 @@ protected void onStart() { if (User.currentUser() != null) { user = User.currentUser(); // Create a RealmConfiguration for our user - SyncConfiguration config = new SyncConfiguration.Builder() + SyncConfiguration config = new SyncConfiguration.Builder(user, REALM_URL) .initialData(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(CRDTCounter.class, 1); } }) - .user(user) - .serverUrl("realm://" + MyApplication.OBJECT_SERVER_IP + "/~/default") .build(); // This will automatically sync all changes in the background for as long as the Realm is open diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index b6503d5337..2911aec28b 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -27,6 +27,11 @@ + + + + + diff --git a/realm/realm-library/src/androidTest/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/SchemaTests.java index 8394607544..8dc0c1eaa9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SchemaTests.java @@ -17,7 +17,6 @@ package io.realm; -import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; @@ -41,17 +40,12 @@ public class SchemaTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - private Context context; private SyncConfiguration config; @Before public void setUp() { - context = InstrumentationRegistry.getContext(); User user = SyncTestUtils.createTestUser(); - config = new SyncConfiguration.Builder(context) - .user(user) - .serverUrl("realm://objectserver.realm.io/~/default") - .build(); + config = new SyncConfiguration.Builder(user, "realm://objectserver.realm.io/~/default").build(); } @After diff --git a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java index 457637cb66..81685ee378 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java @@ -71,16 +71,14 @@ public void user() { @Test public void user_invalidUserThrows() { - SyncConfiguration.Builder builder = new SyncConfiguration.Builder(context); - try { - builder.user(null); + new SyncConfiguration.Builder(null, "realm://ros.realm.io/default"); } catch (IllegalArgumentException ignore) { } User user = createTestUser(0); // Create user that has expired credentials try { - builder.user(user); + new SyncConfiguration.Builder(user, "realm://ros.realm.io/default"); } catch (IllegalArgumentException ignore) { } } @@ -99,10 +97,7 @@ public void serverUrl_setsFolderAndFileName() { String expectedFolder = validUrl[1]; String expectedFileName = validUrl[2]; - SyncConfiguration config = new SyncConfiguration.Builder(context) - .serverUrl(serverUrl) - .user(user) - .build(); + SyncConfiguration config = new SyncConfiguration.Builder(user, serverUrl).build(); assertEquals(new File(context.getFilesDir(), expectedFolder), config.getRealmDirectory()); assertEquals(expectedFileName, config.getRealmFileName()); @@ -127,10 +122,9 @@ public void serverUrl_invalidUrlThrows() { "http://objectserver.realm.io/~/default", // wrong scheme }; - SyncConfiguration.Builder builder = new SyncConfiguration.Builder(context); for (String invalidUrl : invalidUrls) { try { - builder.serverUrl(invalidUrl); + new SyncConfiguration.Builder(createTestUser(), invalidUrl); fail(invalidUrl + " should have failed."); } catch (IllegalArgumentException ignore) { } @@ -151,11 +145,7 @@ public void serverUrl_length() { SyncConfiguration.MAX_FILE_NAME_LENGTH, SyncConfiguration.MAX_FILE_NAME_LENGTH + 1, 1000}; for (int len : lengths) { - SyncConfiguration.Builder builder = new SyncConfiguration.Builder(context) - .serverUrl(makeServerUrl(len)) - .user(createTestUser()); - - SyncConfiguration config = builder.build(); + SyncConfiguration config = new SyncConfiguration.Builder(createTestUser(), makeServerUrl(len)).build(); assertTrue("Length: " + len, config.getRealmFileName().length() <= SyncConfiguration.MAX_FILE_NAME_LENGTH); assertTrue("Length: " + len, config.getPath().length() <= SyncConfiguration.MAX_FULL_PATH_LENGTH); } @@ -163,9 +153,7 @@ public void serverUrl_length() { @Test public void serverUrl_invalidChars() { - SyncConfiguration.Builder builder = new SyncConfiguration.Builder(context) - .serverUrl("realm://objectserver.realm.io/~/?") - .user(createTestUser()); + SyncConfiguration.Builder builder = new SyncConfiguration.Builder(createTestUser(), "realm://objectserver.realm.io/~/?"); SyncConfiguration config = builder.build(); assertFalse(config.getRealmFileName().contains("?")); } @@ -179,53 +167,20 @@ public void serverUrl_port() { urlPort.put("realms://objectserver.realm.io:2443/~/default", 2443); for (String url : urlPort.keySet()) { - SyncConfiguration config = new SyncConfiguration.Builder(context) - .serverUrl(url) - .user(createTestUser()) - .build(); + SyncConfiguration config = new SyncConfiguration.Builder(createTestUser(), url).build(); assertEquals(urlPort.get(url).intValue(), config.getServerUrl().getPort()); } } - @Test - public void userAndServerUrlRequired() { - SyncConfiguration.Builder builder; - - // Both missing - builder = new SyncConfiguration.Builder(context); - try { - builder.build(); - } catch (IllegalStateException ignore) { - } - - builder = new SyncConfiguration.Builder(context); - try { - builder.user(createTestUser(Long.MAX_VALUE)).build(); - } catch (IllegalStateException ignore) { - } - - // user missing - builder = new SyncConfiguration.Builder(context); - try { - builder.serverUrl("realm://foo.bar/~/default").build(); - } catch (IllegalStateException ignore) { - } - } - @Test public void errorHandler() { - SyncConfiguration.Builder builder; - builder = new SyncConfiguration.Builder(context) - .user(createTestUser()) - .serverUrl("realm://objectserver.realm.io/default"); - + SyncConfiguration.Builder builder = new SyncConfiguration.Builder(createTestUser(), "realm://objectserver.realm.io/default"); Session.ErrorHandler errorHandler = new Session.ErrorHandler() { @Override public void onError(Session session, ObjectServerError error) { } }; - SyncConfiguration config = builder.errorHandler(errorHandler).build(); assertEquals(errorHandler, config.getErrorHandler()); } @@ -242,10 +197,9 @@ public void onError(Session session, ObjectServerError error) { SyncManager.setDefaultSessionErrorHandler(errorHandler); // Create configuration using the default handler - SyncConfiguration config = new SyncConfiguration.Builder(context) - .user(createTestUser()) - .serverUrl("realm://objectserver.realm.io/default") - .build(); + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); assertEquals(errorHandler, config.getErrorHandler()); SyncManager.setDefaultSessionErrorHandler(null); } @@ -253,8 +207,9 @@ public void onError(Session session, ObjectServerError error) { @Test public void errorHandler_nullThrows() { - SyncConfiguration.Builder builder; - builder = new SyncConfiguration.Builder(context); + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration.Builder builder = new SyncConfiguration.Builder(user, url); try { builder.errorHandler(null); diff --git a/realm/realm-library/src/main/java/io/realm/ObjectServer.java b/realm/realm-library/src/main/java/io/realm/ObjectServer.java new file mode 100644 index 0000000000..b8599292c8 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/ObjectServer.java @@ -0,0 +1,31 @@ +package io.realm; + +import android.content.Context; +import android.content.pm.PackageInfo; + +import io.realm.android.SharedPrefsUserStore; +import io.realm.internal.Keep; + +/** + * Internal initializer class for the Object Server. + * Use to keep the `SyncManager` free from Android dependencies + */ +@SuppressWarnings("unused") +@Keep +class ObjectServer { + + public static void init(Context context) { + // Setup AppID + String appId = "unknown"; + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + appId = pi.packageName; + } catch (Exception ignore) { + } + + // Configure default UserStore + UserStore userStore = new SharedPrefsUserStore(context); + + SyncManager.init(appId, userStore); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 5c7cbeb302..4bc0c25fe9 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -52,6 +52,7 @@ import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; +import io.realm.internal.ObjectServerFacade; import io.realm.internal.RealmCore; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; @@ -188,6 +189,7 @@ public static synchronized void init(Context context) { RealmCore.loadLibrary(context); RealmLog.add(BuildConfig.DEBUG ? new AndroidLogger(Log.DEBUG) : new AndroidLogger(Log.WARN)); defaultConfiguration = new RealmConfiguration.Builder(context).build(); + ObjectServerFacade.getSyncFacadeIfPossible().init(context); BaseRealm.applicationContext = context.getApplicationContext(); } } diff --git a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java index e8cb2b0ad7..7d964edea9 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java @@ -245,15 +245,34 @@ public static final class Builder { /** * Creates an instance of the Builder for the SyncConfiguration. *

      - * This will use the app's own internal directory for storing the Realm file. This does not require any - * additional permissions. The default location is {@code /data/data//files/realm-object-server}, - * but can change depending on vendor implementations of Android. + * Opening a synchronized Realm requires a valid user and an unique URL that identifies that Realm. In URL's, + * {@code /~/} can be used as a placeholder for a user ID in case the Realm should only be available to one + * user, e.g. {@code "realm://objectserver.realm.io/~/default"} + *

      + * The URL cannot end with {@code .realm}. + *

      + * The `/~/` will automatically be replaced with the user ID when creating the {@link SyncConfiguration}. + *

      + * The URL also defines the local location on disk. The default location of a synchronized Realm file is + * {@code /data/data//files/realm-object-server//}, but this behaviour + * can be overwritten using {@link #name(String)} and {@link #directory(File)}. + *

      + * Many Android devices are using FAT32 file systems. FAT32 file systems have a limitation that + * file name cannot be longer than 255 characters. Moreover, the entire URL should not exceed 256 characters. + * If file name and underlying path are too long to handle for FAT32, a shorter unique name will be generated. + * See also @{link https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx}. + * + * @param user Set the user for this Realm. An authenticated {@link User} is required to open any Realm managed + * by a Realm Object Server. + * @param url URL identifying the Realm. + * + * @see User#isValid() */ - public Builder() { - this(BaseRealm.applicationContext); + public Builder(User user, String url) { + this(BaseRealm.applicationContext, user, url); } - Builder(Context context) { + Builder(Context context, User user, String url) { if (context == null) { throw new IllegalStateException("Call `Realm.init(Context)` before creating a SyncConfiguration"); } @@ -261,11 +280,89 @@ public Builder() { if (Realm.getDefaultModule() != null) { this.modules.add(Realm.getDefaultModule()); } + + validateAndSet(user); + validateAndSet(url); + } + + private void validateAndSet(User user) { + if (user == null) { + throw new IllegalArgumentException("Non-null `user` required."); + } + if (!user.isValid()) { + throw new IllegalArgumentException("User not authenticated or authentication expired."); + } + this.user = user; + } + + private void validateAndSet(String url) { + if (url == null) { + throw new IllegalArgumentException("Non-null 'url' required."); + } + + try { + serverUrl = new URI(url); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid url: " + url, e); + } + + // scheme must be realm or realms + String scheme = serverUrl.getScheme(); + if (!scheme.equals("realm") && !scheme.equals("realms")) { + throw new IllegalArgumentException("Invalid scheme: " + scheme); + } + + // set port if not set by user + int port; + int currentPort = serverUrl.getPort(); + if (currentPort == -1) { + port = scheme.equals("realm") ? PORT_REALM : PORT_REALMS; + } else { + port = currentPort; + } + + // Detect last path segment as it is the default file name + String path = serverUrl.getPath(); + if (path == null) { + throw new IllegalArgumentException("Invalid url: " + url); + } + + String[] pathSegments = path.split("/"); + for (int i = 1; i < pathSegments.length; i++) { + String segment = pathSegments[i]; + if (segment.equals("~")) { + continue; + } + if (segment.equals("..") || segment.equals(".")) { + throw new IllegalArgumentException("The URL has an invalid segment: " + segment); + } + Matcher m = pattern.matcher(segment); + if (!m.matches()) { + throw new IllegalArgumentException("The URL must only contain characters 0-9, a-z, A-Z, ., _, and -: " + segment); + } + } + + this.defaultLocalFileName = pathSegments[pathSegments.length - 1]; + + // Validate filename + // TODO Lift this restriction on the Object Server + if (defaultLocalFileName.endsWith(".realm") + || defaultLocalFileName.endsWith(".realm.lock") + || defaultLocalFileName.endsWith(".realm.management")) { + throw new IllegalArgumentException("The URL must not end with '.realm', '.realm.lock' or '.realm.management: " + url); + } + + try { + this.serverUrl = new URI(scheme, serverUrl.getUserInfo(), serverUrl.getHost(), + port, serverUrl.getPath(), serverUrl.getQuery(), serverUrl.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot reconstruct url: " + url, e); + } } /** * Sets the local filename for the Realm. - * This will override the default name defined by the {@link #serverUrl(String)} + * This will override the default name defined by the the Realm URL. * * @param filename name of the local file on disk. */ @@ -282,7 +379,7 @@ public Builder name(String filename) { * Sets the local root directory where synchronized Realm files can be saved. * * Synchronized Realms will not be saved directly in the provided directory, but instead in a - * subfolder that matches the path defined by {@link #serverUrl(String)}. As Realm server URLs are unique + * subfolder that matches the path defined by Realm URL. As Realm server URLs are unique * this means that multiple users can save their Realms on disk without the risk of them overriding each other. * * The default location is {@code context.getFilesDir()}. @@ -389,110 +486,6 @@ public Builder inMemory() { return this; } - /** - * Enable server side synchronization for this Realm. The name should be a unique URL that identifies the Realm. - * {@code /~/} can be used as a placeholder for a user ID in case the Realm should only be available to one - * user, e.g. {@code "realm://objectserver.realm.io/~/default"} - * - * The `/~/` will automatically be replaced with the user ID when creating the {@link SyncConfiguration}. - * - * The URL also defines the local location on the device. The default location of a synchronized Realm file is - * {@code /data/data//files/realm-object-server//}. - * - * This behaviour can be overwritten using {@link #name(String)} and {@link #directory(File)}. - * - * Many Android devices are using FAT32 file systems. FAT32 file systems have a limitation that - * file name cannot be longer than 255 characters. Moreover, the entire URL should not exceed 256 characters. - * If file name and underlying path are too long to handle for FAT32, a shorter unique name will be generated. - * See also @{link https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx}. - * - * @param url URL identifying the Realm. - * @throws IllegalArgumentException if the URL is not valid. - */ - public Builder serverUrl(String url) { - if (url == null) { - throw new IllegalArgumentException("Non-null 'url' required."); - } - - URI serverUrl; - try { - serverUrl = new URI(url); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid url: " + url, e); - } - - // scheme must be realm or realms - String scheme = serverUrl.getScheme(); - if (!scheme.equals("realm") && !scheme.equals("realms")) { - throw new IllegalArgumentException("Invalid scheme: " + scheme); - } - - // set port if not set by user - int port; - int currentPort = serverUrl.getPort(); - if (currentPort == -1) { - port = scheme.equals("realm") ? PORT_REALM : PORT_REALMS; - } else { - port = currentPort; - } - - // Detect last path segment as it is the default file name - String path = serverUrl.getPath(); - if (path == null) { - throw new IllegalArgumentException("Invalid url: " + url); - } - - String[] pathSegments = path.split("/"); - for (int i = 1; i < pathSegments.length; i++) { - String segment = pathSegments[i]; - if (segment.equals("~")) { - continue; - } - if (segment.equals("..") || segment.equals(".")) { - throw new IllegalArgumentException("The URL has an invalid segment: " + segment); - } - Matcher m = pattern.matcher(segment); - if (!m.matches()) { - throw new IllegalArgumentException("The URL must only contain characters 0-9, a-z, A-Z, ., _, and -: " + segment); - } - } - - this.defaultLocalFileName = pathSegments[pathSegments.length - 1]; - - // Validate filename - // TODO Lift this restriction on the Object Server - if (defaultLocalFileName.endsWith(".realm") - || defaultLocalFileName.endsWith(".realm.lock") - || defaultLocalFileName.endsWith(".realm.management")) { - throw new IllegalArgumentException("The URL must not end with '.realm', '.realm.lock' or '.realm.management: " + url); - } - - try { - this.serverUrl = new URI(scheme, serverUrl.getUserInfo(), serverUrl.getHost(), - port, serverUrl.getPath(), serverUrl.getQuery(), serverUrl.getFragment()); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Cannot reconstruct url: " + url, e); - } - return this; - } - - /** - * Set the user for this Realm. An authenticated {@link User} is required to open any Realm managed by a - * Realm Object Server. - * - * @param user {@link User} who wants to access this Realm. - */ - public Builder user(User user) { - if (user == null) { - throw new IllegalArgumentException("Non-null `user` required."); - } - if (!user.isValid()) { - throw new IllegalArgumentException("User not authenticated or authentication expired."); - } - this.user = user; - return this; - } - /** * Sets the {@link SyncPolicy} used to control when changes should be synchronized with the remote Realm. * The default policy is {@link AutomaticSyncPolicy}. @@ -542,10 +535,10 @@ private String MD5(String in) { /** * Setting this will cause the local Realm file used to synchronize changes to be deleted if the {@link User} - * defined by {@link #user(User)} logs out from the device using {@link User#logout()}. + * owning this Realm logs out from the device using {@link User#logout()}. * - * The default behaviour is that the Realm file is allowed to stay behind, making it faster for users to log in - * again and have access to their data faster. + * The default behaviour is that the Realm file is allowed to stay behind, making it possible for users to log + * in again and have access to their data faster. */ public Builder deleteRealmOnLogout() { this.deleteRealmOnLogout = true; diff --git a/realm/realm-library/src/main/java/io/realm/SyncManager.java b/realm/realm-library/src/main/java/io/realm/SyncManager.java index ce61081bfc..5230a2541f 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/SyncManager.java @@ -22,11 +22,10 @@ import java.util.concurrent.TimeUnit; import io.realm.internal.Keep; -import io.realm.internal.RealmCore; -import io.realm.internal.objectserver.SyncSession; -import io.realm.internal.objectserver.SessionStore; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.OkHttpAuthenticationServer; +import io.realm.internal.objectserver.SessionStore; +import io.realm.internal.objectserver.SyncSession; import io.realm.log.RealmLog; /** @@ -39,7 +38,11 @@ @Keep public final class SyncManager { - public static final String APP_ID = "foo"; // FIXME Find a way to get an application ID + /** + * APP ID sent to the Realm Object Server. Is automatically initialized to the package name for the app. + */ + public static String APP_ID = null; + // Thread pool used when doing network requests against the Realm Authentication Server. // FIXME Set proper parameters public static final ThreadPoolExecutor NETWORK_POOL_EXECUTOR = new ThreadPoolExecutor( @@ -74,11 +77,15 @@ public void onError(Session session, ObjectServerError error) { @SuppressWarnings("FieldCanBeLocal") private static Thread clientThread; - // FIXME This should be called by a method in Realm.init() otherwise the ClassLoader might call this first. - static { + // Called from SyncObjectServerFacade using reflection + @SuppressWarnings("unused") + static void init(String appId, UserStore userStore) { + + // Initialize underlying Sync Network Client nativeInitializeSyncClient(); - // Create the client thread in java to avoid strange problem when error happens. And anyway we need to attach - // to the jvm for logger. + + // Create the client thread in java to avoid problems when exceptions are being thrown. We need to attach + // any thread to the JVM anyway in order to send back log events. clientThread = new Thread(new Runnable() { @Override public void run() { diff --git a/realm/realm-library/src/main/java/io/realm/User.java b/realm/realm-library/src/main/java/io/realm/User.java index 14cba000ea..8b89636c4e 100644 --- a/realm/realm-library/src/main/java/io/realm/User.java +++ b/realm/realm-library/src/main/java/io/realm/User.java @@ -101,8 +101,6 @@ public static User fromJson(String user) { * @param credentials credentials to use. * @param authenticationUrl Server that can authenticate against. * @throws ObjectServerError if the login failed. - * - * @see SyncConfiguration.Builder#user(User) */ public static User login(final Credentials credentials, final String authenticationUrl) throws ObjectServerError { final URL authUrl; @@ -140,8 +138,6 @@ public static User login(final Credentials credentials, final String authenticat * @param credentials credentials to use. * @param authenticationUrl Server that can authenticate against. * @param callback callback when login has completed or failed. This callback will always happen on the UI thread. - * - * @see SyncConfiguration.Builder#user(User) */ public static RealmAsyncTask loginAsync(final Credentials credentials, final String authenticationUrl, final Callback callback) { if (Looper.myLooper() == null) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 331ea7de1f..5422735708 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -1,5 +1,7 @@ package io.realm.internal; +import android.content.Context; + import io.realm.RealmConfiguration; import io.realm.exceptions.RealmException; @@ -15,7 +17,7 @@ public class ObjectServerFacade { static { //noinspection TryWithIdenticalCatches try { - Class syncFacadeClass = Class.forName("io.realm.internal.objectserver.SyncObjectServerFacade"); + Class syncFacadeClass = Class.forName("io.realm.internal.SyncObjectServerFacade"); syncFacade = (ObjectServerFacade) syncFacadeClass.newInstance(); } catch (ClassNotFoundException ignored) { } catch (InstantiationException e) { @@ -25,15 +27,28 @@ public class ObjectServerFacade { } } + /** + * Initialize the Object Server library + * @param context + */ + public void init(Context context) { + } + /** * Notify the session for this configuration that a local commit was made. */ public void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { } + /** + * The first instance of this Realm was opened. + */ public void realmClosed(RealmConfiguration configuration) { } + /** + * The last instance of this Realm was closed. + */ public void realmOpened(RealmConfiguration configuration) { } @@ -49,7 +64,7 @@ public static ObjectServerFacade getFacade(boolean needSyncFacade) { } // Returns a SyncObjectServerFacade instance if the class exists. Otherwise returns a non-sync one. - static ObjectServerFacade getSyncFacadeIfPossible() { + public static ObjectServerFacade getSyncFacadeIfPossible() { if (syncFacade != null) { return syncFacade; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/SyncObjectServerFacade.java similarity index 64% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java rename to realm/realm-library/src/main/java/io/realm/internal/SyncObjectServerFacade.java index 5740a201ad..7ea800c166 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SyncObjectServerFacade.java @@ -1,18 +1,46 @@ -package io.realm.internal.objectserver; +package io.realm.internal; +import android.content.Context; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + import io.realm.RealmConfiguration; import io.realm.Session; import io.realm.SyncConfiguration; import io.realm.SyncManager; -import io.realm.internal.ObjectServerFacade; +import io.realm.exceptions.RealmException; +import io.realm.internal.objectserver.SessionStore; +import io.realm.internal.objectserver.SyncSession; @SuppressWarnings("unused") // Used through reflection. See ObjectServerFacade +@Keep public class SyncObjectServerFacade extends ObjectServerFacade { private static final String WRONG_TYPE_OF_CONFIGURATION = "'configuration' has to be an instance of 'SyncConfiguration'."; + @Override + public void init(Context context) { + // Trying to keep things out the public API is no fun :/ + // Just use reflection on init. It is a one-time method call so should be acceptable. + try { + Class syncManager = Class.forName("io.realm.ObjectServer"); + Method method = syncManager.getDeclaredMethod("init", Context.class); + method.setAccessible(true); + method.invoke(null, context); + } catch (NoSuchMethodException e) { + throw new RealmException("Could not initialize the Realm Object Server", e); + } catch (InvocationTargetException e) { + throw new RealmException("Could not initialize the Realm Object Server", e); + } catch (IllegalAccessException e) { + throw new RealmException("Could not initialize the Realm Object Server", e); + } catch (ClassNotFoundException e) { + throw new RealmException("Could not initialize the Realm Object Server", e); + } + } + @Override public void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { if (configuration instanceof SyncConfiguration) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java index 7a42631302..49cbf98da3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java @@ -329,17 +329,17 @@ public SessionState getState() { /** * Notify session that a commit on the device has happened. */ - void notifyCommit(long version) { + public void notifyCommit(long version) { if (isBound()) { nativeNotifyCommitHappened(nativeSessionPointer, version); } } - SyncPolicy getSyncPolicy() { + public SyncPolicy getSyncPolicy() { return syncPolicy; } - Session getUserSession() { + public Session getUserSession() { return userSession; } From 7ff00fe2cc07f83eec79c18852b675f03465c4ef Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 20 Sep 2016 14:54:06 +0200 Subject: [PATCH 0070/2110] Fix example. User store only returns the user if it is valid. --- .../examples/objectserver/CounterActivity.java | 6 +++--- .../examples/objectserver/MyApplication.java | 16 +++++++++------- .../src/main/java/io/realm/SyncManager.java | 13 +++++++------ .../src/main/java/io/realm/User.java | 6 ++++-- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 4ba665ff4a..4cfce022d5 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -37,7 +37,7 @@ public class CounterActivity extends AppCompatActivity { - private static final String REALM_URL = "realm://" + MyApplication.OBJECT_SERVER_IP + "/~/default"; + private static final String REALM_URL = "realm://" + MyApplication.OBJECT_SERVER_IP + ":7800/~/default"; private Realm realm; private RealmResults counter; @@ -61,8 +61,8 @@ protected void onCreate(Bundle savedInstanceState) { @Override protected void onStart() { super.onStart(); - if (User.currentUser() != null) { - user = User.currentUser(); + user = User.currentUser(); + if (user != null) { // Create a RealmConfiguration for our user SyncConfiguration config = new SyncConfiguration.Builder(user, REALM_URL) .initialData(new Realm.Transaction() { diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java index 02d9ad8186..05d7567940 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java @@ -19,20 +19,22 @@ import android.app.Application; import android.util.Log; +import io.realm.Realm; import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; -import io.realm.SyncManager; -import io.realm.User; -import io.realm.UserStore; -import io.realm.android.SharedPrefsUserStore; public class MyApplication extends Application { - public static final String OBJECT_SERVER_IP = "192.168.1.3"; + public static final String OBJECT_SERVER_IP = "192.168.104.22"; @Override public void onCreate() { super.onCreate(); - RealmLog.add(new AndroidLogger(Log.VERBOSE)); - SyncManager.setUserStore(new SharedPrefsUserStore(this)); // Temporary until we can find a way to inject Context. + Realm.init(this); + + // Enable full log output when debugging + if (BuildConfig.DEBUG) { + RealmLog.clear(); + RealmLog.add(new AndroidLogger(Log.VERBOSE)); + } } } diff --git a/realm/realm-library/src/main/java/io/realm/SyncManager.java b/realm/realm-library/src/main/java/io/realm/SyncManager.java index 5230a2541f..41d8ca5711 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/SyncManager.java @@ -70,29 +70,30 @@ public void onError(Session session, ObjectServerError error) { // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. // Right now it just lives and dies together with the process. private static volatile AuthenticationServer authServer = new OkHttpAuthenticationServer(); - private static volatile UserStore userStore; // FIXME: Set to a default once we merge global init - + private static volatile UserStore userStore; static volatile Session.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; @SuppressWarnings("FieldCanBeLocal") private static Thread clientThread; - // Called from SyncObjectServerFacade using reflection - @SuppressWarnings("unused") + // Initialize the SyncManager static void init(String appId, UserStore userStore) { + SyncManager.APP_ID = appId; + SyncManager.userStore = userStore; + // Initialize underlying Sync Network Client nativeInitializeSyncClient(); // Create the client thread in java to avoid problems when exceptions are being thrown. We need to attach // any thread to the JVM anyway in order to send back log events. - clientThread = new Thread(new Runnable() { + SyncManager.clientThread = new Thread(new Runnable() { @Override public void run() { nativeRunClient(); } }, "RealmSyncClient"); - clientThread.start(); + SyncManager.clientThread.start(); } /** diff --git a/realm/realm-library/src/main/java/io/realm/User.java b/realm/realm-library/src/main/java/io/realm/User.java index 8b89636c4e..85a9a08e18 100644 --- a/realm/realm-library/src/main/java/io/realm/User.java +++ b/realm/realm-library/src/main/java/io/realm/User.java @@ -55,12 +55,14 @@ private User(SyncUser user) { } /** - * Returns the last user that has logged in that hasn't logged out yet. + * Returns the last user that has logged in that are still valid. + * A user is invalidated when it logs out or its access tokens expire. * * @return last {@link User} that have logged in that is still valid. */ public static User currentUser() { - return SyncManager.getUserStore().get(UserStore.CURRENT_USER_KEY); + User user = SyncManager.getUserStore().get(UserStore.CURRENT_USER_KEY); + return (user != null && user.isValid()) ? user : null; } /** From b06db736871b5ecd382a50cb8c44eb163cd00741 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 20 Sep 2016 19:04:01 +0200 Subject: [PATCH 0071/2110] Removing FIXMEs and create them as Github issues. (#130) --- realm/config/findbugs/findbugs-filter.xml | 5 +++++ .../src/main/cpp/io_realm_internal_SharedRealm.cpp | 3 +-- realm/realm-library/src/main/cpp/objectserver_shared.hpp | 3 +-- .../src/main/java/io/realm/AndroidNotifier.java | 3 +-- realm/realm-library/src/main/java/io/realm/BaseRealm.java | 4 ++-- .../realm-library/src/main/java/io/realm/RealmAsyncTask.java | 2 -- .../src/main/java/io/realm/RealmObjectSchema.java | 1 - realm/realm-library/src/main/java/io/realm/SyncManager.java | 1 - .../realm/internal/network/OkHttpAuthenticationServer.java | 4 ++-- .../main/java/io/realm/internal/network/RefreshResponse.java | 1 - realm/realm-library/src/main/java/io/realm/log/RealmLog.java | 3 --- 11 files changed, 12 insertions(+), 18 deletions(-) diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index 2911aec28b..e2c1a8b2e1 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -32,6 +32,11 @@ + + + + + diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 00c0b9344a..1792920f4b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -154,8 +154,7 @@ Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong s return static_cast(ObjectStore::get_schema_version(shared_realm->read_group())); } CATCH_STD() - // FIXME: Use constant value - return -1; + return static_cast(ObjectStore::NotVersioned); } JNIEXPORT void JNICALL diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 7d9d8249bd..d066d3653c 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -30,14 +30,13 @@ // Wrapper class for realm::Session. This allows us to manage the C++ session and callback lifecycle correctly. -// TODO Use OS SyncSession instead +// TODO Use OS SyncSession instead - https://github.com/realm/realm-java-private/issues/123 class JniSession { public: JniSession() = delete; JniSession(JNIEnv* env, std::string local_realm_path, jobject java_session_obj) { - // FIXME: This doesn't look good. Temp solution before moving to OS sync. extern std::unique_ptr sync_client; // Get the coordinator for the given path, or null if there is none m_sync_session = new realm::sync::Session(*sync_client, local_realm_path); diff --git a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java index b3f881bfa4..8e2ddc26d8 100644 --- a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java @@ -28,7 +28,6 @@ /** * Implementation of {@link RealmNotifier} for Android based on {@link Handler}. */ -// FIXME: Please move me to the io.realm.internal when HandlerController is there. class AndroidNotifier implements RealmNotifier { private Handler handler; @@ -66,7 +65,7 @@ public void notifyCommitByLocalThread() { // This is called by OS when other thread/process changes the Realm. // This is getting called on the same thread which created the Realm. - // FIXME: The whole calling routine is twisted and needs to be rewritten in the near future. + // https://github.com/realm/realm-java-private/issues/127 // |---------------------------------------------------------------+--------------+------------------------------------------------| // | Thread A | Thread B | Daemon Thread | // |---------------------------------------------------------------+--------------+------------------------------------------------| diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 2836c6c170..e4b8faeb5c 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -61,7 +61,7 @@ public abstract class BaseRealm implements Closeable { "Changing Realm data can only be done from inside a transaction."; // Thread pool for all async operations (Query & transaction) - public volatile static Context applicationContext; // FIXME Make package protected once all sync code moves to io.realm + public volatile static Context applicationContext; // Thread pool for all async operations (Query & transaction) static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); @@ -583,7 +583,7 @@ public void onResult(int count) { * @return {@code true} if compaction succeeded, {@code false} otherwise. */ static boolean compactRealm(final RealmConfiguration configuration) { - // FIXME: Move this check to OS? + // https://github.com/realm/realm-java/issues/1033 if (configuration.getEncryptionKey() != null) { throw new IllegalArgumentException("Cannot currently compact an encrypted Realm."); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java b/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java index cc2809d512..e2733a7c66 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java +++ b/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java @@ -16,7 +16,6 @@ package io.realm; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; @@ -32,7 +31,6 @@ public final class RealmAsyncTask { private final ThreadPoolExecutor service; private volatile boolean isCancelled = false; - // FIXME This shouldn't be public public RealmAsyncTask(Future pendingTask, ThreadPoolExecutor service) { this.pendingTask = pendingTask; this.service = service; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 8c7e6b07d1..9e92389494 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -154,7 +154,6 @@ public RealmObjectSchema setClassName(String className) { realm.checkNotInSync(); // renaming a table is not permitted checkEmpty(className); String internalTableName = Table.TABLE_PREFIX + className; - //FIXME : when core implements class name length check, please remove. if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { throw new IllegalArgumentException("Class name is to long. Limit is 56 characters: \'" + className + "\' (" + Integer.toString(className.length()) + ")"); } diff --git a/realm/realm-library/src/main/java/io/realm/SyncManager.java b/realm/realm-library/src/main/java/io/realm/SyncManager.java index 41d8ca5711..ca496efeb1 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/SyncManager.java @@ -226,7 +226,6 @@ static void notifyUserLoggedOut(User user) { * Sets the log level for the underlying * @param logLevel */ - // FIXME Remove from the public API. This is controlled by Logger#minimumNativeLogLevel public static void setLogLevel(int logLevel) { nativeSetSyncClientLogLevel(logLevel); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java index d1cdd84421..3218dc72b3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -68,12 +68,12 @@ public AuthenticateResponse authenticateRealm(Token refreshToken, URI path, URL @Override public RefreshResponse refresh(String token, URL authenticationUrl) { - throw new UnsupportedOperationException("FIXME"); + throw new UnsupportedOperationException("Not yet implemented"); } @Override public LogoutResponse logout(User user, URL authenticationUrl) { - throw new UnsupportedOperationException("FIXME"); + throw new UnsupportedOperationException("Not yet implemented"); } private AuthenticateResponse authenticate(URL authenticationUrl, String requestBody) throws Exception { diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java index 0c88d0b034..24559721bb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java @@ -22,7 +22,6 @@ public class RefreshResponse extends AuthServerResponse { public RefreshResponse(Response response) { - // FIXME Parse refresh result } public Token getRefreshToken() { diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index 6389196faa..f83173214a 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -21,8 +21,6 @@ import io.realm.internal.Keep; import io.realm.internal.Util; -import io.realm.SyncManager; -import io.realm.internal.ObjectServerFacade; /** * Global logger used by all Realm components. @@ -59,7 +57,6 @@ public static void add(Logger logger) { private static void setMinimumNativeDebugLevel(int nativeDebugLevel) { minimumNativeLogLevel = nativeDebugLevel; - // FIXME: Use same log level setting for normal Realm and Sync Realm. Util.setDebugLevel(nativeDebugLevel); // Log level for Realm Core } From 4ca57c35eeeed0eb79482f52b20413f5b0623809 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 20 Sep 2016 23:19:15 -0500 Subject: [PATCH 0072/2110] Make RealmAsyncTask into an interface (#133) * Make RealmAsyncTask into an interface and move the implementation into internal package. Then we can create the async task in different internal packages without exposing the constructor to outside. Part of fix for #125 --- .../src/main/java/io/realm/Realm.java | 3 +- .../main/java/io/realm/RealmAsyncTask.java | 34 ++--------- .../src/main/java/io/realm/User.java | 3 +- .../internal/async/RealmAsyncTaskImpl.java | 59 +++++++++++++++++++ .../internal/objectserver/SyncSession.java | 3 +- .../realm/internal/objectserver/SyncUser.java | 3 +- 6 files changed, 71 insertions(+), 34 deletions(-) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 4bc0c25fe9..c549ae6fb3 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -57,6 +57,7 @@ import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; +import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; import rx.Observable; @@ -1414,7 +1415,7 @@ public void run() { } }); - return new RealmAsyncTask(pendingTransaction, asyncTaskExecutor); + return new RealmAsyncTaskImpl(pendingTransaction, asyncTaskExecutor); } /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java b/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java index e2733a7c66..28fceabc74 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java +++ b/realm/realm-library/src/main/java/io/realm/RealmAsyncTask.java @@ -16,9 +16,6 @@ package io.realm; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; - /** * Represents a pending asynchronous Realm transaction. *

      @@ -26,41 +23,18 @@ * case of a configuration change for example (to avoid memory leak, as the transaction will post the result to the * caller's thread callback). */ -public final class RealmAsyncTask { - private final Future pendingTask; - private final ThreadPoolExecutor service; - private volatile boolean isCancelled = false; - - public RealmAsyncTask(Future pendingTask, ThreadPoolExecutor service) { - this.pendingTask = pendingTask; - this.service = service; - } +public interface RealmAsyncTask { /** * Attempts to cancel execution of this transaction (if it hasn't already completed or previously cancelled). */ - public void cancel() { - pendingTask.cancel(true); - isCancelled = true; - - // From "Java Threads": By Scott Oaks & Henry Wong - // cancelled tasks are never executed, but may - // accumulate in work queues, which may causes a memory leak - // if the task hold references (to an enclosing class for example) - // we can use purge() but one caveat applies: if a second thread attempts to add - // something to the pool (using the execute() method) at the same time the - // first thread is attempting to purge the queue the attempt to purge - // the queue fails and the cancelled object remain in the queue. - // A better way to cancel objects with thread pools is to use the remove() - service.getQueue().remove(pendingTask); - } + void cancel(); /** * Checks whether an attempt to cancel the transaction was performed. * * @return {@code true} if {@link #cancel()} has already been called, {@code false} otherwise. */ - public boolean isCancelled() { - return isCancelled; - } + boolean isCancelled(); } + diff --git a/realm/realm-library/src/main/java/io/realm/User.java b/realm/realm-library/src/main/java/io/realm/User.java index 85a9a08e18..1cf9af3a83 100644 --- a/realm/realm-library/src/main/java/io/realm/User.java +++ b/realm/realm-library/src/main/java/io/realm/User.java @@ -34,6 +34,7 @@ import io.realm.internal.IOException; import io.realm.internal.Util; +import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.objectserver.SyncUser; import io.realm.internal.objectserver.Token; import io.realm.internal.network.AuthenticateResponse; @@ -181,7 +182,7 @@ public void run() { } }); - return new RealmAsyncTask(authenticateRequest, networkPoolExecutor); + return new RealmAsyncTaskImpl(authenticateRequest, networkPoolExecutor); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java new file mode 100644 index 0000000000..d523c9ae28 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.async; + +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; + +import io.realm.RealmAsyncTask; + +public final class RealmAsyncTaskImpl implements RealmAsyncTask { + private final Future pendingTask; + private final ThreadPoolExecutor service; + private volatile boolean isCancelled = false; + + public RealmAsyncTaskImpl(Future pendingTask, ThreadPoolExecutor service) { + this.pendingTask = pendingTask; + this.service = service; + } + + /** + * {@inheritDoc} + */ + public void cancel() { + pendingTask.cancel(true); + isCancelled = true; + + // From "Java Threads": By Scott Oaks & Henry Wong + // cancelled tasks are never executed, but may + // accumulate in work queues, which may causes a memory leak + // if the task hold references (to an enclosing class for example) + // we can use purge() but one caveat applies: if a second thread attempts to add + // something to the pool (using the execute() method) at the same time the + // first thread is attempting to purge the queue the attempt to purge + // the queue fails and the cancelled object remain in the queue. + // A better way to cancel objects with thread pools is to use the remove() + service.getQueue().remove(pendingTask); + } + + /** + * {@inheritDoc} + */ + public boolean isCancelled() { + return isCancelled; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java index 49cbf98da3..79fc9ae3b9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java @@ -22,6 +22,7 @@ import io.realm.RealmAsyncTask; import io.realm.internal.Keep; +import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; import io.realm.ErrorCode; import io.realm.ObjectServerError; @@ -279,7 +280,7 @@ protected void onError(AuthenticateResponse response) { errorHandler.onError(getUserSession(), response.getError()); } }); - networkRequest = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); + networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java index fcbcb96e2b..2e9d8450b3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java @@ -35,6 +35,7 @@ import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.User; +import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.RefreshResponse; @@ -95,7 +96,7 @@ protected void onError(RefreshResponse response) { } }); - refreshTask = new RealmAsyncTask(task, SyncManager.NETWORK_POOL_EXECUTOR); + refreshTask = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); } /** From 0e86b2bbcbcf6786a4354e45040608132ed67aba Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 20 Sep 2016 07:46:43 +0200 Subject: [PATCH 0073/2110] Bump to build tools 2.2.0 (#3467) --- examples/build.gradle | 2 +- examples/gradle/wrapper/gradle-wrapper.properties | 2 +- .../java/io/realm/examples/threads/AsyncQueryFragment.java | 2 +- examples/threadExample/src/main/res/values/strings.xml | 2 +- gradle-plugin/gradle/wrapper/gradle-wrapper.properties | 2 +- .../src/test/groovy/io/realm/gradle/PluginTest.groovy | 4 ++-- realm-annotations/gradle/wrapper/gradle-wrapper.properties | 2 +- realm-transformer/gradle/wrapper/gradle-wrapper.properties | 2 +- realm/build.gradle | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 47be4e4ca1..64b863d9f8 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -16,7 +16,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java index 6d730bce93..3fdde6744e 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java @@ -178,7 +178,7 @@ public View getView(int i, View view, ViewGroup viewGroup) { view.setTag(viewHolder); } ViewHolder vh = (ViewHolder) view.getTag(); - vh.text.setText(view.getResources().getString(R.string.coordinate, getItem(i).getX(),getItem(i).getY())); + vh.text.setText(view.getResources().getString(R.string.coordinate, getItem(i).getX(), getItem(i).getY())); return view; } diff --git a/examples/threadExample/src/main/res/values/strings.xml b/examples/threadExample/src/main/res/values/strings.xml index b1480bd6cc..240767c073 100644 --- a/examples/threadExample/src/main/res/values/strings.xml +++ b/examples/threadExample/src/main/res/values/strings.xml @@ -13,7 +13,7 @@ Object Passing Passing to Intent Service Passing To Receiver - [X= %1$s Y= %2$s] + [X= %1$d Y= %2$d] Start diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index d73ef15836..c99d4f53b4 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -53,7 +53,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } @@ -78,7 +78,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 587246a1a4..f71002edb7 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/realm/build.gradle b/realm/build.gradle index 3be5c2794e..429b20fbe0 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0-beta2' + classpath 'com.android.tools.build:gradle:2.2.0' classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' From fd51cd14a9582554dfe3ea7e586f842dcd4c86b1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 20 Sep 2016 14:27:19 +0800 Subject: [PATCH 0074/2110] Update to gradle 3.0 --- Jenkinsfile | 2 +- build.gradle | 2 +- examples/gradle/wrapper/gradle-wrapper.jar | Bin 53638 -> 53324 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- examples/gradlew | 46 +++-- examples/gradlew.bat | 8 +- gradle-plugin/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 51017 -> 53324 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- gradle-plugin/gradlew | 52 ++--- gradle-plugin/gradlew.bat | 8 +- gradle/wrapper/gradle-wrapper.jar | Bin 53638 -> 53324 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 46 +++-- gradlew.bat | 8 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 53638 -> 53324 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- realm-annotations/gradlew | 46 +++-- realm-annotations/gradlew.bat | 180 +++++++++--------- .../gradle/wrapper/gradle-wrapper.jar | Bin 53638 -> 53324 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- realm-transformer/gradlew | 46 +++-- realm-transformer/gradlew.bat | 180 +++++++++--------- realm/build.gradle | 2 +- realm/config/pmd/ruleset.xml | 6 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 53638 -> 53324 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- realm/gradlew | 46 +++-- realm/gradlew.bat | 8 +- realm/realm-library/build.gradle | 1 - 30 files changed, 364 insertions(+), 349 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 291c183c5a..117d3c1764 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -19,7 +19,7 @@ try { try { gradle 'assemble check javadoc' } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/TEST-*.xml' + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' step([$class: 'LintPublisher']) } diff --git a/build.gradle b/build.gradle index 228c2813a0..215e8e8d6b 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ def currentVersion = file("${projectDir}/version.txt").text.trim(); def props = new Properties() props.load(new FileInputStream("${rootDir}/realm.properties")) props.each { key, val -> - project.set(key, val) + project.ext.set(key, val) } task assembleAnnotations(type:GradleBuild) { diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index e8c6bf7bb47dff6b81c2cf7a349eb7e912c9fbe2..3baa851b28c65f87dd36a6748e1a85cf360c1301 100644 GIT binary patch delta 5494 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 MSIUeM4P@v40p5%%-2eap delta 5701 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 bKi@x~80mdC-3;tBodo3HFj3mK9J%^G#jsYK diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index f71002edb7..f930473763 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 05 14:18:17 CET 2016 +#Tue Sep 20 14:25:59 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip diff --git a/examples/gradlew b/examples/gradlew index 97fac783e1..27309d9231 100755 --- a/examples/gradlew +++ b/examples/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then diff --git a/examples/gradlew.bat b/examples/gradlew.bat index aec99730b4..f6d5974e72 100644 --- a/examples/gradlew.bat +++ b/examples/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 2bcb843bee..eaad561c34 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'com.jfrog.bintray' def props = new Properties() props.load(new FileInputStream("${rootDir}/../realm.properties")) props.each { key, val -> - project.set(key, val) + project.ext.set(key, val) } repositories { diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index b7612167031001b7b84baf2a959e8ea8ad03c011..3baa851b28c65f87dd36a6748e1a85cf360c1301 100644 GIT binary patch delta 28971 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

      NA&SbtvYF-$add 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 HKmYwdM#_19 diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index f71002edb7..4912622457 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 05 14:18:17 CET 2016 +#Tue Sep 20 14:21:04 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip diff --git a/gradle-plugin/gradlew b/gradle-plugin/gradlew index 91a7e269e1..27309d9231 100755 --- a/gradle-plugin/gradlew +++ b/gradle-plugin/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` diff --git a/gradle-plugin/gradlew.bat b/gradle-plugin/gradlew.bat index aec99730b4..f6d5974e72 100644 --- a/gradle-plugin/gradlew.bat +++ b/gradle-plugin/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e8c6bf7bb47dff6b81c2cf7a349eb7e912c9fbe2..3baa851b28c65f87dd36a6748e1a85cf360c1301 100644 GIT binary patch delta 5494 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 MSIUeM4P@v40p5%%-2eap delta 5701 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 bKi@x~80mdC-3;tBodo3HFj3mK9J%^G#jsYK diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f71002edb7..bc6b7c4622 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 05 14:18:17 CET 2016 +#Tue Sep 20 14:03:29 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip diff --git a/gradlew b/gradlew index 97fac783e1..27309d9231 100755 --- a/gradlew +++ b/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then diff --git a/gradlew.bat b/gradlew.bat index aec99730b4..f6d5974e72 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index e8c6bf7bb47dff6b81c2cf7a349eb7e912c9fbe2..3baa851b28c65f87dd36a6748e1a85cf360c1301 100644 GIT binary patch delta 5494 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 MSIUeM4P@v40p5%%-2eap delta 5701 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 bKi@x~80mdC-3;tBodo3HFj3mK9J%^G#jsYK diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index f71002edb7..a18e4af3f9 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 05 14:18:17 CET 2016 +#Tue Sep 20 14:22:29 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip diff --git a/realm-annotations/gradlew b/realm-annotations/gradlew index 97fac783e1..27309d9231 100755 --- a/realm-annotations/gradlew +++ b/realm-annotations/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then diff --git a/realm-annotations/gradlew.bat b/realm-annotations/gradlew.bat index 8a0b282aa6..f6d5974e72 100644 --- a/realm-annotations/gradlew.bat +++ b/realm-annotations/gradlew.bat @@ -1,90 +1,90 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index e8c6bf7bb47dff6b81c2cf7a349eb7e912c9fbe2..3baa851b28c65f87dd36a6748e1a85cf360c1301 100644 GIT binary patch delta 5494 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 MSIUeM4P@v40p5%%-2eap delta 5701 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 bKi@x~80mdC-3;tBodo3HFj3mK9J%^G#jsYK diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index f71002edb7..e9bd4ba64f 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 05 14:18:17 CET 2016 +#Tue Sep 20 14:33:05 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip diff --git a/realm-transformer/gradlew b/realm-transformer/gradlew index 97fac783e1..27309d9231 100755 --- a/realm-transformer/gradlew +++ b/realm-transformer/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then diff --git a/realm-transformer/gradlew.bat b/realm-transformer/gradlew.bat index 8a0b282aa6..f6d5974e72 100644 --- a/realm-transformer/gradlew.bat +++ b/realm-transformer/gradlew.bat @@ -1,90 +1,90 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/realm/build.gradle b/realm/build.gradle index 429b20fbe0..2e0d21424d 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -23,7 +23,7 @@ allprojects { def props = new Properties() props.load(new FileInputStream("${rootDir}/../realm.properties")) props.each { key, val -> - project.set(key, val) + project.ext.set(key, val) } group = 'io.realm' diff --git a/realm/config/pmd/ruleset.xml b/realm/config/pmd/ruleset.xml index a3596aa24e..c777aa8319 100644 --- a/realm/config/pmd/ruleset.xml +++ b/realm/config/pmd/ruleset.xml @@ -8,10 +8,6 @@ Realm PMD ruleset - - - - - \ No newline at end of file + diff --git a/realm/gradle/wrapper/gradle-wrapper.jar b/realm/gradle/wrapper/gradle-wrapper.jar index e8c6bf7bb47dff6b81c2cf7a349eb7e912c9fbe2..3baa851b28c65f87dd36a6748e1a85cf360c1301 100644 GIT binary patch delta 5494 zcmZWs2{=^k`yRuf82ebJ(b)I3Qg&rmk*(}ojioHvLZk_$vZOl65+Y=&tb;z07&91K zRLH(%Em6su{%2-<_09ixuIpUqnP=|je(vXa-}9dLjm@R+$fjpAKS9s17Xo2shA zGrq?r4kte~)KoWgArOeoy`xtA0_k%C-vUYx*F#P>6x@?2x2B*AyvFK!$Fd zVpOt01EEycb%q(L%MX@IbyXsmxfrQFS+)Tv8<`0c-Z6Hc0Rqw518{PxVjZj;PV?*> zHc=Huk?Ic_JLFYecd%467RSl(h#{cj%=yj>!Wj}bV}mB!Oz1AIZrZz`JQrdvvURC; zy-!hUO^94GDjG8rneHQDDt-=nM@D?9YN+Zr+u7Vo(xI!nbun^|kQXhDUQn9HUpgt9 zy3#0`cyS}!^^BQ_<*S@=Uo0$W?@XjuQy!m%nu2k;6u}g2EoTz;oU=WwfK%2sdGg`# z^iw`>?P208%Q{KI7T4x6(WP-cSbFrOsOkZGpUdGpU6Z{{B7`3&r+E{D9t}pyKj`hy zmzo)fO=D&`WNPO@>^bRaaKimk)aD-ip$uK|kDOIZv z6k_ZG1jLqOn^piY~VThfT)8ITlFlcz3-FL`d{l!qufFH4^hSxWqyXEb{%5;;O zPM$&TTB~`4Dq1Sf+crl)H2)^k-gnQ><>`L6PhYHyZ96wN`0!c->ptaob_L@iCzZ;( zM6npRaLJMaLHvRFppkc>SOMco?vL^#!6Y?%+p+gkY!;V@22pqJ{}R39=Y2;U&`H)N z+4-63T^@uc8Z;|ZK0JA5KR!i4ZFQb&O?jW&ywp_0kzeT9+)s3p#qo~5{}8Xil||hy znDu!i%zFk!PNu$2O|F^>kGhprV67>YQvC83AA^0;TH1#LCQJE3C1Ga+s9_rpS0o6jmL_ih|i8)^eT=5ZM$BXKrO@M>`?VQ{0Hh zlLrxxr=sE%m-C}bt*>AcFETKm8nuMVC#TpRmCsM~_2Vg(9ojy;XnvUvv)CTAH$yEX z`De0z*rc?qXiQu1$+Je#4{K+$N!DU2j!}x$LzTKT+h{}Yv))%PNCIs#oeQ;o`c(J!8%RLJ=6c7!iic}*{dGERhwR$cJiFpdZUKrJF~W<#vjhgw=rjQ z_cl|2n$D(jY`~#jQFgR}#wWY50JhHQ@?_S{HW#$r;;iQ_9D~{&i+z&vZd{ddoC7#* zpRi+)c4Or>8O~QhAKFt)dQ?kjgr!`0aRB)2eK@d(k^bn>BWH5bDJ zS%R4^FOGNdR&vFwiX5Bpe@ws1YW3ExMFM^IhToq09`U=ddhDaqiR@bv8^^wOMx-D4 zp82)oQO)n2?#16wf41KV6PgKn4@z3h-xwy`m&U^dvTQ6Kd@;4Nl{v25jCE}_v&-Xq zQsy3V)_j5#Vi5aC#*g5Sa!~eZ$IdR7OKI=NOD?zZYv15A*u=$kw{CyrH=7DNaK)M6 zi*UI$8Luq1Y{}!o^+~aP8KL~+^u5=-gnsuOL!PmONeAUC`^GqD6^&L#q+Ux(x|~^w zMCh3N`_$q}_#{nRsyeIUys;1EVD^0#tPxKNHSSDEsb0E#)h96g!lm7FSSk1y-D1v-udrmUVNnEPY=uKv7TbephQBnorr z=1UZBDJy+&*Z_az+`|J&j|^fYM3T}U&O2Ma%|bbz;YgSIR1^|Ch)YN#VQ13a6c@Y= z^tM^n-A4|)3;M(k!-2xBf)gRaQ?E$F6{~?C%MJ$BzEUO@9xginuBUwZYX9UHuSWm1 z@8#(}?u*h`4_tqzE}_pLGXM^ey7~I+%!}J7}`3riuWEWvGyFQ3V`iBr&->z@V>9sf^Ge_Y8Hz`-{He2jwBYz z_m`oPX6}9}&%Fy5KhYK4v6ZpP)GCkfl1)kWt2c8>djb{q5Ajl#mmi6HM%edGArDwwN=bfkP zAF6CKhaS>o%CxB!AcPU*X5bF^B!gMWb!mY%ul2O&hATnv29EiZm$~EH8m0c_E3$}& zKBXAD(S_k@QJJf`6WE&d%(yY{b^4vciBs#9(F*JX5}oeEPTju1#OUnJc&UWR(r7QE z#ueVQGjHDwWR}{N{B!>EMrG&|Yv~#8Gi|0mWlG#nPnW#h({RYJ`RB8Q)TXiJP3MU%^TIDKb0ud@HMI^o}U9@ZbFXL!dzt zQ@`euY1<`;+K5Ul##U}H?75dUUQ=owXpXeav+P<_(7F!v6JZ!JG~||8xYJ51U%PI2WPpsewibRVqZdFUuTQqCM7y$o|%(L zk#_?hMEJy`&{GTb8Hlb4YdGmy)`IvQ<*uV>-7pUHle~@N>qHY!jHL1# z=ey`R;7OcKP@R0;>zA2-W#t2}LtIV7qQr$HlmrI06}5^oE*A8j#`SZM@?$SBcjv{v zQ_)w54aq5K#k%w$*}jNWTk0{{*duOE8L0-}RJ9Jk#dgI{Y}K~>oF}f$hxeLKnPd1` zY%E76mW<&}8ms={922P+sTkGchNpy0-|7uCmGKQC(7{@`p;QsA=ZLG1ojnu=!xki} z6z)E>DqDv*38`@BecNNn@iY@3cHi=P`jrgm0g~elRV;hnt+M(!Zk3FT^ZJu9=%0;W z4~Z|MxI;(r55M`oFNcSz@|;TlcE50fZd%jF_ZuT@^y_j)2I}_X+M7&)!2?ow&a z=5XbkNz3|Jrj4`~Xn96{-7Rs&^n2=?oN^I!L(}5ycePLWO4pPGZ~GwVlU7HMsn!F2 zUtdg)z?~&86CdPR|ZNO)_D-ao=7 zjc@X`)lq{23oKeT1Ux)RQb2(JL~4LnLLM+Mg&S?f9zQkW&7S3k@(EClI$e79kh92& zqTNKO$@p*aEN(-IcPkh~OsPiKh1^AWBJv7ut}$LgDnY$ct0FiSRD z5u;wZn}-%x1;QgAs@b|x*W69x?m9BimZg#qf;aE7EV;a{`@lrG(IF zZt7MadyoZ2weF}QSg2Nk-p{>ME5~ej_ec~7zn`s%$yzzVCg?t4Q&hzM+`*hH0XLr4V>T48$zA@zo|s9$l?OSrbuy5sCc>3N zw#lYPYL(-e1P@wE%t^#y8{@6tO>a6 zCd)uJDhu6iLOIFS0T=hAr=ZL^@RkC~6T|=vriO|^yYE1$max~{t_AnLPe+N<;q&_4 z!UTcb6@3k~g+%y)UR@p#Gcq97<0S zTK%RhC>08U6oZ?gU7_8m%JI@CyCJa^j=O1RDv$04%e?H~_5J$CY8Pi+cb}e%EQ_$;fodwsn|Rk!J%Pl!yNBc^2@0bCC8x3zWT4 zIZU2TAQ1#qNV&ix=kCP;`E@K4v@ZsFD*l&@90BZWM5;IL{^=R$hVgO#9}Jo1UsiCC zb}usPXYzhf_WyHwMPod2LDUEE7X23vK5eFE80lFqD zAu?w6vu#i@_}>tCi_l;$GXTm&ULdXsNdYnxg^xIbWELQqJPq(13@C3z080c$V5~`q z0!B?b4G~}v$R^m)gEZj1%oMOHvk+k4%t`@wz?}{6fy^7=nA0)~u~EQgY)GIKPX+%y z=|dn&G|bx^6!0=$nzAMUg3UdN;{x1lk)XV&KzR!i zig5)xTLh`Um%!hEE1=XR0Cc+{0j*Y6s^7E~3Eh7VIEImt`|r#pC$iMtBUlIy_ZnET z=ASg=nY$MQ28aUY?)!jW{}RF5kWf}nAg5Fsu=U~vmO7|v=S1P(jKF@J0Ev`oNY>AT zBq*S)O_Wj=&B{juyyFd&`{)3#+o_BwZ{G4wkZlN*b%X|37DP$E9nA43{q)%nk`o}< z4_s4qXu!Q8lp6TM_WxlP32(E%555d(aPV+P1Lg>)Xgw9d3cMmxWn}m`b{Bw!J^i17 zp21RNylCHTeOEgYIu!*xdM*O6c5qROjX$R1a|H}$0fW_PGQGMDXm=P>$0n`=2~CQp z$ZYFSruue3pVl4FCjw}8QpsG{wR>~H8l}M+Y2V?wY)Y`g6R!UmT%#V3&-cNg46t6> zpy6DK{Pj+LiqtTPNo!YdDM7M8AR07M8=ivG$%HB^vI_|fdj{61@TWmEUkIAOt0iED zzXK}CV9Y82Hp7!#0}U{soYMBP3U=~@rO46i;hk9kyLJVXLpv#ZDk#McbW2iz07N5= z+~RGJRgH!fQ3Igg8c}s$c#DM2y`*-jmXkaasD2XY*Lg+p@9B}C5Ym32{xagCKD-7$ MSIUeM4P@v40p5%%-2eap delta 5701 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 bKi@x~80mdC-3;tBodo3HFj3mK9J%^G#jsYK diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index f71002edb7..897f3bf902 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 05 14:18:17 CET 2016 +#Tue Sep 20 14:03:59 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip diff --git a/realm/gradlew b/realm/gradlew index 97fac783e1..27309d9231 100755 --- a/realm/gradlew +++ b/realm/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then diff --git a/realm/gradlew.bat b/realm/gradlew.bat index aec99730b4..f6d5974e72 100644 --- a/realm/gradlew.bat +++ b/realm/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 50edef0b50..7a5f841a45 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -179,7 +179,6 @@ task pmd(type: Pmd) { source = fileTree('src/main/java') ruleSetFiles = files("${projectDir}/../config/pmd/ruleset.xml") - ruleSets = [] // This needs to be here to remove the default checks reports { xml.enabled = false From 17889ee7e4425db8efe5338da6e21fd36813a7cf Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 21 Sep 2016 12:35:08 +0200 Subject: [PATCH 0075/2110] Added support for refresh (#134) * Refresh implemented properly. Renamed AuthServer interface so it is more readable. * Fix bugs in unit tests and SharedPrefsUserStore * User store now returns null if user expired + unit tests. Added logging if refreshing fails. --- .../androidTest/java/io/realm/UserTests.java | 94 +++++++++++++++++++ .../java/io/realm/util/SyncTestUtils.java | 23 +++++ .../src/main/java/io/realm/SyncManager.java | 4 - .../src/main/java/io/realm/User.java | 12 ++- .../realm/android/SharedPrefsUserStore.java | 2 +- .../internal/network/AuthenticateRequest.java | 5 +- .../network/AuthenticateResponse.java | 19 +++- .../network/AuthenticationServer.java | 30 +++++- .../network/OkHttpAuthenticationServer.java | 27 +++--- .../internal/network/RefreshResponse.java | 30 ------ .../internal/objectserver/SyncSession.java | 2 +- .../realm/internal/objectserver/SyncUser.java | 23 +++-- 12 files changed, 202 insertions(+), 69 deletions(-) delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/UserTests.java b/realm/realm-library/src/androidTest/java/io/realm/UserTests.java index d91b850a64..01df4e1724 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/UserTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/UserTests.java @@ -16,21 +16,115 @@ package io.realm; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.net.URL; +import java.util.concurrent.TimeUnit; + +import io.realm.android.SharedPrefsUserStore; +import io.realm.internal.network.AuthenticateResponse; +import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.objectserver.Token; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.util.SyncTestUtils; import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; @RunWith(AndroidJUnit4.class) public class UserTests { + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + @Test public void toAndFromJson() { User user1 = createTestUser(); User user2 = User.fromJson(user1.toJson()); assertEquals(user1, user2); } + + // Tests that the UserStore does not return users that have expired + @Test + public void currentUser_returnsNullIfUserExpired() { + // Add an expired user to the user store + UserStore userStore = new SharedPrefsUserStore(InstrumentationRegistry.getContext()); + SyncManager.setUserStore(userStore); + userStore.put(UserStore.CURRENT_USER_KEY, SyncTestUtils.createTestUser(Long.MIN_VALUE)); + + // Invalid users should not be returned when asking the for the current user + assertNull(User.currentUser()); + } + + // Tests that the user store returns the last user to login + @Test + public void currentUser_returnsUserAfterLogin() { + AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + when(authServer.loginUser(any(Credentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(Long.MAX_VALUE)); + + User user = User.login(Credentials.facebook("foo"), "http://bar.com/auth"); + assertEquals(user, User.currentUser()); + } + + // Tests that if a user logs in, the refreshToken is refreshed before it expires. + @RunTestInLooperThread + @Test + public void login_refreshWhenExpiring() { + // Setup server responses + // Expires in 30 seconds and Refresh starts 30 seconds before it expires. This should trigger a refresh + // immediately. + long expires = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30); + AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + when(authServer.loginUser(any(Credentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(expires)); + when(authServer.refreshUser(any(Token.class), any(URL.class))).then(new Answer() { + @Override + public AuthenticateResponse answer(InvocationOnMock invocation) throws Throwable { + looperThread.testComplete(); + return SyncTestUtils.createRefreshResponse(); + } + }); + + // Login (which will trigger a refreshUser) + SyncManager.setAuthServerImpl(authServer); + User.login(Credentials.facebook("foo"), "http://bar.com/auth"); + } + + // Tests that if a user is loaded from storage, it will still be refreshed when expiring. + @RunTestInLooperThread + @Test + public void currentUser_refreshWhenExpiring() { + // Setup + // Expires in 30 seconds and Refresh starts 30 seconds before it expires. This should trigger a refresh + // immediately. + long expires = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30); + AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + when(authServer.refreshUser(any(Token.class), any(URL.class))).then(new Answer() { + @Override + public AuthenticateResponse answer(InvocationOnMock invocation) throws Throwable { + looperThread.testComplete(); + return SyncTestUtils.createRefreshResponse(); + } + }); + + SyncManager.setUserStore(new SharedPrefsUserStore(InstrumentationRegistry.getContext())); + SyncManager.setAuthServerImpl(authServer); + User testUser = SyncTestUtils.createTestUser(expires); + SyncManager.getUserStore().put(UserStore.CURRENT_USER_KEY, testUser); + + // Load user from storage. This should also trigger a refresh when the user expires + User user = User.currentUser(); + assertEquals(testUser, user); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTest/java/io/realm/util/SyncTestUtils.java index 6a28b0e529..2fdd291cad 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTest/java/io/realm/util/SyncTestUtils.java @@ -23,6 +23,7 @@ import java.util.UUID; import io.realm.User; +import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.objectserver.SyncUser; import io.realm.internal.objectserver.Token; @@ -56,4 +57,26 @@ public static User createTestUser(long expires) { throw new RuntimeException(e); } } + + public static AuthenticateResponse createLoginResponse(long expires) { + try { + Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); + JSONObject response = new JSONObject(); + response.put("refresh_token", userToken.toJson()); + return AuthenticateResponse.from(response.toString()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public static AuthenticateResponse createRefreshResponse() { + try { + Token userToken = new Token(USER_TOKEN, "JohnDoe", null, Long.MAX_VALUE, null); + JSONObject response = new JSONObject(); + response.put("refresh_token", userToken.toJson()); + return AuthenticateResponse.from(response.toString()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/SyncManager.java b/realm/realm-library/src/main/java/io/realm/SyncManager.java index ca496efeb1..5172e1c1f2 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/SyncManager.java @@ -183,16 +183,12 @@ public static AuthenticationServer getAuthServer() { } /** - * TODO Internal only? Developers can also use this to inject stubs. - * TODO Find a better method name. - *

      * Sets the auth server implementation used when validating credentials. */ static void setAuthServerImpl(AuthenticationServer authServerImpl) { authServer = authServerImpl; } - // Return the currently configured User store. static UserStore getUserStore() { return userStore; diff --git a/realm/realm-library/src/main/java/io/realm/User.java b/realm/realm-library/src/main/java/io/realm/User.java index 1cf9af3a83..87bef8c0c5 100644 --- a/realm/realm-library/src/main/java/io/realm/User.java +++ b/realm/realm-library/src/main/java/io/realm/User.java @@ -63,7 +63,11 @@ private User(SyncUser user) { */ public static User currentUser() { User user = SyncManager.getUserStore().get(UserStore.CURRENT_USER_KEY); - return (user != null && user.isValid()) ? user : null; + if (user != null && user.isValid()) { + user.getSyncUser().scheduleRefresh(); + return user; + } + return null; } /** @@ -78,8 +82,8 @@ public static User fromJson(String user) { try { JSONObject obj = new JSONObject(user); URL authUrl = new URL(obj.getString("authUrl")); - Token refreshToken = Token.from(obj.getJSONObject("userToken")); - SyncUser syncUser = new SyncUser(refreshToken, authUrl); + Token userToken = Token.from(obj.getJSONObject("userToken")); + SyncUser syncUser = new SyncUser(userToken, authUrl); JSONArray realmTokens = obj.getJSONArray("realms"); for (int i = 0; i < realmTokens.length(); i++) { JSONObject token = realmTokens.getJSONObject(i); @@ -115,7 +119,7 @@ public static User login(final Credentials credentials, final String authenticat final AuthenticationServer server = SyncManager.getAuthServer(); try { - AuthenticateResponse result = server.authenticateUser(credentials, authUrl); + AuthenticateResponse result = server.loginUser(credentials, authUrl); if (result.isValid()) { SyncUser syncUser = new SyncUser(result.getRefreshToken(), authUrl); User user = new User(syncUser); diff --git a/realm/realm-library/src/main/java/io/realm/android/SharedPrefsUserStore.java b/realm/realm-library/src/main/java/io/realm/android/SharedPrefsUserStore.java index 64653bd87a..eab709cf07 100644 --- a/realm/realm-library/src/main/java/io/realm/android/SharedPrefsUserStore.java +++ b/realm/realm-library/src/main/java/io/realm/android/SharedPrefsUserStore.java @@ -65,7 +65,7 @@ public User put(String key, User user) { */ @Override public User get(String key) { - if (key == UserStore.CURRENT_USER_KEY && cachedCurrentUser != null) { + if (UserStore.CURRENT_USER_KEY.equals(key) && cachedCurrentUser != null) { return cachedCurrentUser; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java index 0e946b4715..34e97bfa94 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java @@ -57,14 +57,13 @@ public static AuthenticateRequest fromCredentials(Credentials credentials) { * Authenticate access to a given Realm using an already logged in user. * * @param refreshToken Users refresh token - * @param path Path of the Realm to gain access to. */ - public static AuthenticateRequest fromRefreshToken(Token refreshToken, URI path) { + public static AuthenticateRequest fromRefreshToken(Token refreshToken) { // Authenticate a given Realm path using an already logged in user. return new AuthenticateRequest("realm", refreshToken.value(), SyncManager.APP_ID, - path.getPath(), + refreshToken.path(), Collections.emptyMap() ); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java index a5a1c7f06c..a7c4c0f2f7 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java @@ -42,7 +42,7 @@ public class AuthenticateResponse extends AuthServerResponse { * Helper method for creating the proper Authenticate response. This method will set the appropriate error * depending on any HTTP response codes or IO errors. */ - static AuthenticateResponse createFrom(Response response) { + public static AuthenticateResponse from(Response response) { String serverResponse; try { serverResponse = response.body().string(); @@ -58,10 +58,24 @@ static AuthenticateResponse createFrom(Response response) { } } + /** + * Helper method for creating the response from a JSON string. + */ + public static AuthenticateResponse from(String json) { + return new AuthenticateResponse(json); + } + + /** + * Helper method for creating a failed response. + */ + public static AuthenticateResponse from(ObjectServerError error) { + return new AuthenticateResponse(error); + } + /** * Creates a unsuccessful authentication response. This should only happen in case of network / IO problems. */ - AuthenticateResponse(ObjectServerError error) { + private AuthenticateResponse(ObjectServerError error) { setError(error); this.accessToken = null; this.refreshToken = null; @@ -101,4 +115,5 @@ public Token getAccessToken() { public Token getRefreshToken() { return refreshToken; } + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java index 4c15c64def..8a71a37aaa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java @@ -19,9 +19,9 @@ import java.net.URI; import java.net.URL; +import io.realm.Credentials; import io.realm.User; import io.realm.internal.objectserver.Token; -import io.realm.Credentials; /** * Interface for handling communication with Realm Object Servers. @@ -30,8 +30,30 @@ * only responsible for executing a given network request. */ public interface AuthenticationServer { - AuthenticateResponse authenticateUser(Credentials credentials, URL authenticationUrl); - AuthenticateResponse authenticateRealm(Token refreshToken, URI path, URL authenticationUrl); - RefreshResponse refresh(String token, URL authenticationUrl); + /** + * Login a User on the Object Server. This will create a "UserToken" (Currently called RefreshToken) that acts as + * the users credentials. + */ + AuthenticateResponse loginUser(Credentials credentials, URL authenticationUrl); + + /** + * Requests access to a specific Realm. Only users with a valid user token can ask for permission to a remote Realm. + * Permission to a Realm is granted through an "AccessToken". Each Realm have their own access token, and all + * tokens should be managed by {@link User}. + */ + AuthenticateResponse loginToRealm(Token userToken, URI serverUrl, URL authenticationUrl); + + /** + * When the Object Server returns the user token, it also sends a timestamp for when the token expires. + * Before it expires, the client should try to refresh the token, effectively keeping the user logged in on the + * Object Server. Failing to do so will cause a "soft logout", where the User will have limited access rights. + */ + AuthenticateResponse refreshUser(Token userToken, URL authenticationUrl); + + /** + * Logs out the user on the Object Server by invalidating the refresh token. Each device should be given their + * own refresh token, but if the refresh token for some reason was shared or stolen all these devices will be + * logged out as well. + */ LogoutResponse logout(User user, URL authenticationUrl); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 3218dc72b3..137718674f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -20,12 +20,12 @@ import java.net.URL; import java.util.concurrent.TimeUnit; -import io.realm.internal.Util; +import io.realm.Credentials; import io.realm.ErrorCode; +import io.realm.ObjectServerError; import io.realm.User; +import io.realm.internal.Util; import io.realm.internal.objectserver.Token; -import io.realm.Credentials; -import io.realm.ObjectServerError; import okhttp3.Call; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -47,28 +47,33 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { * Authenticate the given credentials on the specified Realm Authentication Server. */ @Override - public AuthenticateResponse authenticateUser(Credentials credentials, URL authenticationUrl) { + public AuthenticateResponse loginUser(Credentials credentials, URL authenticationUrl) { try { String requestBody = AuthenticateRequest.fromCredentials(credentials).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { - return new AuthenticateResponse(new ObjectServerError(ErrorCode.OTHER_ERROR, Util.getStackTrace(e))); + return AuthenticateResponse.from(new ObjectServerError(ErrorCode.OTHER_ERROR, Util.getStackTrace(e))); } } @Override - public AuthenticateResponse authenticateRealm(Token refreshToken, URI path, URL authenticationUrl) { + public AuthenticateResponse loginToRealm(Token refreshToken, URI serverUrl, URL authenticationUrl) { try { - String requestBody = AuthenticateRequest.fromRefreshToken(refreshToken, path).toJson(); + String requestBody = AuthenticateRequest.fromRefreshToken(refreshToken).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { - return new AuthenticateResponse(new ObjectServerError(ErrorCode.UNKNOWN, e)); + return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); } } @Override - public RefreshResponse refresh(String token, URL authenticationUrl) { - throw new UnsupportedOperationException("Not yet implemented"); + public AuthenticateResponse refreshUser(Token userToken, URL authenticationUrl) { + try { + String requestBody = AuthenticateRequest.fromRefreshToken(userToken).toJson(); + return authenticate(authenticationUrl, requestBody); + } catch (Exception e) { + return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); + } } @Override @@ -86,6 +91,6 @@ private AuthenticateResponse authenticate(URL authenticationUrl, String requestB .build(); Call call = client.newCall(request); Response response = call.execute(); - return AuthenticateResponse.createFrom(response); + return AuthenticateResponse.from(response); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java deleted file mode 100644 index 24559721bb..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/network/RefreshResponse.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import io.realm.internal.objectserver.Token; -import okhttp3.Response; - -public class RefreshResponse extends AuthServerResponse { - - public RefreshResponse(Response response) { - } - - public Token getRefreshToken() { - return null; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java index 79fc9ae3b9..196ca7fd8e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java @@ -257,7 +257,7 @@ void authenticateRealm(final Runnable onSuccess, final Session.ErrorHandler erro Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { @Override protected AuthenticateResponse execute() { - return authServer.authenticateRealm( + return authServer.loginToRealm( user.getUserToken(), configuration.getServerUrl(), user.getAuthenticationUrl() diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java index 2e9d8450b3..e4a4ff0420 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java @@ -35,10 +35,11 @@ import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.User; +import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.ExponentialBackoffTask; -import io.realm.internal.network.RefreshResponse; +import io.realm.log.RealmLog; /** * Internal representation of a user on the Realm Object Server. @@ -71,29 +72,33 @@ public SyncUser(Token refreshToken, URL authenticationUrl) { public void setRefreshToken(final Token refreshToken) { this.refreshToken = refreshToken; // Replace any existing token. TODO re-save the user with latest token. + scheduleRefresh(); + } - // Schedule a refresh. This method cannot fail, but will continue retrying until either the app is killed - // or the attempt was successful. + // Schedule a refresh. This method cannot fail, but will continue retrying until either the app is killed + // or the attempt was successful. + // We should probably optimize this. See https://github.com/realm/realm-java-private/issues/140 + public void scheduleRefresh() { final long expire = refreshToken.expiresMs(); final AuthenticationServer server = SyncManager.getAuthServer(); - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { @Override - protected RefreshResponse execute() { + protected AuthenticateResponse execute() { long timeToExpiration = System.currentTimeMillis() - expire; if (timeToExpiration - REFRESH_WINDOW_MS > 0) { SystemClock.sleep(timeToExpiration); } - return server.refresh(refreshToken.value(), authenticationUrl); + return server.refreshUser(refreshToken, authenticationUrl); } @Override - protected void onSuccess(RefreshResponse response) { + protected void onSuccess(AuthenticateResponse response) { setRefreshToken(response.getRefreshToken()); } @Override - protected void onError(RefreshResponse response) { - + protected void onError(AuthenticateResponse response) { + RealmLog.warn("Failed refreshing a user.\n" + response.getError().toString()); } }); refreshTask = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); From 554572a5e83f7e931e99038390299425a12d22df Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 21 Sep 2016 07:27:35 -0500 Subject: [PATCH 0076/2110] Update core to 2.0.0-rc7 (#3473) * Call set_null_unique on PK * Use set_string_unique for null string on PK --- CHANGELOG.md | 2 +- realm/realm-library/build.gradle | 4 ++-- .../src/main/cpp/io_realm_internal_Table.cpp | 24 ++++++++++++++++--- .../main/java/io/realm/internal/Table.java | 15 ++++++------ 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33c3bb1160..5e25fb2053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ ### Internal * Moved JNI build to CMake. -* Updated Realm Core to 2.0.0-rc4. +* Updated Realm Core to 2.0.0-rc7. ## 1.2.0 diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ebc11370e2..b42f048b50 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '2.0.0-rc4' +ext.coreVersion = '2.0.0-rc7' // empty or comment out this to disable hash checking -ext.coreSha256Hash = '760d8e889b8d678da36f63be2a49924969bfc8370176696ee977659d59677717' +ext.coreSha256Hash = '5707af75cd3624505d687c5fa31ccb6a61fe754f72ff6c79d98a342af3b9942b' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 0f1eb70bed..5f8c54dbd6 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -706,10 +706,11 @@ Java_io_realm_internal_Table_nativeSetStringUnique(JNIEnv *env, jclass, jlong na if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { return; } + TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), null{}); + } else { + JStringAccessor value2(env, value); // throws + TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), value2); } - JStringAccessor value2(env, value); // throws - // FIXME: Check if we need to call set_null_unique when core support it. - TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), value2); } CATCH_STD() } @@ -765,6 +766,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull( } CATCH_STD() } +JNIEXPORT void JNICALL +Java_io_realm_internal_Table_nativeSetNullUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, + jlong rowIndex) +{ + Table* pTable = TBL(nativeTablePtr); + if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) + return; + if (!TBL_AND_ROW_INDEX_VALID(env, pTable, rowIndex)) + return; + if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) + return; + try { + pTable->set_null_unique(S(columnIndex), S(rowIndex)); + } CATCH_STD() +} + + JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetRowPtr (JNIEnv* env, jobject, jlong nativeTablePtr, jlong index) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index ad5c39f35b..a0c5c5ebaf 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -33,10 +33,6 @@ public class Table implements TableOrView, TableSchema { public static final int TABLE_MAX_LENGTH = 56; // Max length of class names without prefix public static final String TABLE_PREFIX = Util.getTablePrefix(); public static final long INFINITE = -1; - @SuppressWarnings("WeakerAccess") - public static final String STRING_DEFAULT_VALUE = ""; - @SuppressWarnings("WeakerAccess") - public static final long INTEGER_DEFAULT_VALUE = 0; public static final boolean NULLABLE = true; public static final boolean NOT_NULLABLE = false; @@ -408,7 +404,6 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation long primaryKeyColumnIndex = getPrimaryKey(); RealmFieldType type = getColumnType(primaryKeyColumnIndex); long rowIndex; - UncheckedRow row; // Add with primary key initially set if (primaryKeyValue == null) { @@ -419,9 +414,11 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation throwDuplicatePrimaryKeyException("null"); } rowIndex = nativeAddEmptyRow(nativePtr, 1); - row = getUncheckedRow(rowIndex); - // FIXME: Use core's set_null_unique when core supports it. - row.setNull(primaryKeyColumnIndex); + if (type == RealmFieldType.STRING) { + nativeSetStringUnique(nativePtr, primaryKeyColumnIndex, rowIndex, null); + } else { + nativeSetNullUnique(nativePtr, primaryKeyColumnIndex, rowIndex); + } break; default: @@ -1338,6 +1335,8 @@ public static String tableNameToClassName(String tableName) { public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value); public static native void nativeSetStringUnique(long nativeTablePtr, long columnIndex, long rowIndex, String value); public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex); + // Use nativeSetStringUnique(null) for String column! + public static native void nativeSetNullUnique(long nativeTablePtr, long columnIndex, long rowIndex); public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data); public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value); private native long nativeSetPrimaryKey(long privateKeyTableNativePtr, long nativePtr, String columnName); From 63d082809b696a5c7f168e39a7b62ed01d2d23ae Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 21 Sep 2016 22:37:52 +0900 Subject: [PATCH 0077/2110] add default value instruction support (#3462) * add default value support to Table class * Table#isNull() and TableView#isNull() * use default value feature * added a test to check if nullified link can be overwritten by default value * removed duplicate thread check when constructing proxy objects (and small bugfix in setter of the list). * added thread check * reflect review comments * reflect comment in JNI code --- .../processor/RealmProxyClassGenerator.java | 204 +++++--- .../io/realm/AllTypesRealmProxy.java | 105 ++-- .../io/realm/BooleansRealmProxy.java | 45 +- .../io/realm/NullTypesRealmProxy.java | 393 +++++++++----- .../resources/io/realm/SimpleRealmProxy.java | 31 +- .../java/io/realm/DynamicRealmTests.java | 2 + .../androidTest/java/io/realm/RealmTests.java | 11 + .../DefaultValueOverwriteNullLink.java | 62 +++ .../java/io/realm/internal/JNICloseTest.java | 5 +- .../java/io/realm/internal/JNILinkTest.java | 2 +- .../java/io/realm/internal/JNITableTest.java | 493 ++++++++++++++++-- .../io/realm/internal/JNITableViewTest.java | 141 +++++ .../java/io/realm/internal/JNIViewTest.java | 4 +- .../src/main/cpp/io_realm_internal_Table.cpp | 42 +- .../main/cpp/io_realm_internal_TableView.cpp | 11 + .../src/main/java/io/realm/Realm.java | 10 + .../main/java/io/realm/internal/Table.java | 88 ++-- .../java/io/realm/internal/TableOrView.java | 20 +- .../java/io/realm/internal/TableView.java | 31 +- 19 files changed, 1334 insertions(+), 366 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOverwriteNullLink.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index a9cbc092cd..1247b5d2a4 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -69,6 +69,7 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("io.realm.exceptions.RealmMigrationNeededException"); imports.add("io.realm.internal.ColumnInfo"); imports.add("io.realm.internal.RealmObjectProxy"); + imports.add("io.realm.internal.Row"); imports.add("io.realm.internal.Table"); imports.add("io.realm.internal.TableOrView"); imports.add("io.realm.internal.SharedRealm"); @@ -232,8 +233,8 @@ private void emitConstructor(JavaWriter writer) throws IOException { writer.emitEmptyLine(); } - private void emitAccessors(JavaWriter writer) throws IOException { - for (VariableElement field : metadata.getFields()) { + private void emitAccessors(final JavaWriter writer) throws IOException { + for (final VariableElement field : metadata.getFields()) { final String fieldName = field.getSimpleName().toString(); final String fieldTypeCanonicalName = field.asType().toString(); @@ -241,12 +242,12 @@ private void emitAccessors(JavaWriter writer) throws IOException { /** * Primitives and boxed types */ - String realmType = Constants.JAVA_TO_REALM_TYPES.get(fieldTypeCanonicalName); + final String realmType = Constants.JAVA_TO_REALM_TYPES.get(fieldTypeCanonicalName); // Getter writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); - emitCodeForInjectingObjectContext(writer, field, false, metadata.isPrimaryKey(field)); + emitCodeForInjectingObjectContext(writer); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here. @@ -272,7 +273,30 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Setter writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForInjectingObjectContext(writer, field, true, metadata.isPrimaryKey(field)); + emitCodeForInjectingObjectContext(writer); + emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { + @Override + public void emit(JavaWriter writer) throws IOException { + // set value as default value + writer.emitStatement("final Row row = proxyState.getRow$realm()"); + + if (metadata.isNullable(field)) { + writer.beginControlFlow("if (value == null)") + .emitStatement("row.getTable().setNull(%s, row.getIndex(), true)", + fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow(); + } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { + writer.beginControlFlow("if (value == null)") + .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + .endControlFlow(); + } + writer.emitStatement( + "row.getTable().set%s(%s, row.getIndex(), value, true)", + realmType, fieldIndexVariableReference(field)); + writer.emitStatement("return"); + } + }); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); // Although setting null value for String and bytes[] can be handled by the JNI code, we still generate the same code here. // Compared with getter, null value won't trigger more native calls in setter which is relatively cheaper. @@ -304,7 +328,7 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Getter writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); - emitCodeForInjectingObjectContext(writer, field, false, metadata.isPrimaryKey(field)); + emitCodeForInjectingObjectContext(writer); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.beginControlFlow("if (proxyState.getRow$realm().isNullLink(%s))", fieldIndexVariableReference(field)); writer.emitStatement("return null"); @@ -316,7 +340,37 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Setter writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForInjectingObjectContext(writer, field, true, metadata.isPrimaryKey(field)); + emitCodeForInjectingObjectContext(writer); + emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { + @Override + public void emit(JavaWriter writer) throws IOException { + // check excludeFields + writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", + field.getSimpleName().toString()) + .emitStatement("return") + .endControlFlow(); + writer.beginControlFlow("if (value != null && !RealmObject.isManaged(value))") + .emitStatement("value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value)") + .endControlFlow(); + + // set value as default value + writer.emitStatement("final Row row = proxyState.getRow$realm()"); + writer.beginControlFlow("if (value == null)") + .emitSingleLineComment("Table#nullifyLink() does not support default value. Just using Row.") + .emitStatement("row.nullifyLink(%s)", fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow(); + writer.beginControlFlow("if (!RealmObject.isValid(value))") + .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") + .endControlFlow(); + writer.beginControlFlow("if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") + .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") + .endControlFlow(); + writer.emitStatement("row.getTable().setLink(%s, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true)", + fieldIndexVariableReference(field)); + writer.emitStatement("return"); + } + }); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.beginControlFlow("if (value == null)"); writer.emitStatement("proxyState.getRow$realm().nullifyLink(%s)", fieldIndexVariableReference(field)); @@ -338,7 +392,7 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Getter writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); - emitCodeForInjectingObjectContext(writer, field, false, metadata.isPrimaryKey(field)); + emitCodeForInjectingObjectContext(writer); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.emitSingleLineComment("use the cached value if available"); writer.beginControlFlow("if (" + fieldName + "RealmList != null)"); @@ -355,8 +409,33 @@ private void emitAccessors(JavaWriter writer) throws IOException { // Setter writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); + emitCodeForInjectingObjectContext(writer); + emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { + @Override + public void emit(JavaWriter writer) throws IOException { + // check excludeFields + writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", + field.getSimpleName().toString()) + .emitStatement("return") + .endControlFlow(); + final String modelFqcn = Utils.getGenericTypeQualifiedName(field); + writer.beginControlFlow("if (value != null && !value.isManaged())") + .emitStatement("final Realm realm = (Realm) proxyState.getRealm$realm()") + .emitStatement("final RealmList<%1$s> original = value", modelFqcn) + .emitStatement("value = new RealmList<%1$s>()", modelFqcn) + .beginControlFlow("for (%1$s item : original)", modelFqcn) + .beginControlFlow("if (item == null || RealmObject.isManaged(item))") + .emitStatement("value.add(item)") + .nextControlFlow("else") + .emitStatement("value.add(realm.copyToRealm(item))") + .endControlFlow() + .endControlFlow() + .endControlFlow(); + + // LinkView currently does not support default value feature. Just fallback to normal code. + } + }); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - emitCodeForInjectingObjectContext(writer, field, true, metadata.isPrimaryKey(field)); writer.emitStatement("LinkView links = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)); writer.emitStatement("links.clear()"); writer.beginControlFlow("if (value == null)"); @@ -380,7 +459,7 @@ private void emitAccessors(JavaWriter writer) throws IOException { } } - private void emitCodeForInjectingObjectContext(JavaWriter writer, VariableElement field, boolean isSetter, boolean isPrimaryKey) throws IOException { + private void emitCodeForInjectingObjectContext(JavaWriter writer) throws IOException { // if invoked from model's constructor, inject BaseRealm and Row writer.beginControlFlow("if (proxyState == null)"); { @@ -389,51 +468,26 @@ private void emitCodeForInjectingObjectContext(JavaWriter writer, VariableElemen } writer.endControlFlow(); writer.emitEmptyLine(); + } - if (isSetter) { - writer.beginControlFlow("if (proxyState.isUnderConstruction())"); - { - if (isPrimaryKey) { - writer.emitSingleLineComment("default value of the primary key is always ignored."); - writer.emitStatement("return"); - } else { - writer.beginControlFlow("if (!proxyState.getAcceptDefaultValue$realm())") - .emitStatement("return") - .endControlFlow(); - if (Utils.isRealmModel(field)) { - // check excludeFields - writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", - field.getSimpleName().toString()) - .emitStatement("return") - .endControlFlow(); - writer.beginControlFlow("if (value != null && !RealmObject.isManaged(value))") - .emitStatement("value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value)") - .endControlFlow(); - } else if (Utils.isRealmList(field)) { - // check excludeFields - writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", - field.getSimpleName().toString()) - .emitStatement("return") - .endControlFlow(); - final String modelFqcn = Utils.getGenericTypeQualifiedName(field); - writer.beginControlFlow("if (value != null && !value.isManaged())") - .emitStatement("final Realm realm = (Realm) proxyState.getRealm$realm()") - .emitStatement("final RealmList<%1$s> original = value", modelFqcn) - .emitStatement("value = new RealmList<%1$s>()", modelFqcn) - .beginControlFlow("for (%1$s item : original)", modelFqcn) - .beginControlFlow("if (item == null || RealmObject.isManaged(item))") - .emitStatement("value.add(item)") - .nextControlFlow("else") - .emitStatement("value.add(realm.copyToRealm(item))") - .endControlFlow() - .endControlFlow() - .endControlFlow(); - } - } - } - writer.endControlFlow() - .emitEmptyLine(); + private interface CodeEmitter { + void emit(JavaWriter writer) throws IOException; + } + + private void emitCodeForUnderConstruction(JavaWriter writer, boolean isPrimaryKey, + CodeEmitter defaultValueCodeEmitter) throws IOException { + writer.beginControlFlow("if (proxyState.isUnderConstruction())"); + if (isPrimaryKey) { + writer.emitSingleLineComment("default value of the primary key is always ignored."); + writer.emitStatement("return"); + } else { + writer.beginControlFlow("if (!proxyState.getAcceptDefaultValue$realm())") + .emitStatement("return") + .endControlFlow(); + defaultValueCodeEmitter.emit(writer); } + writer.endControlFlow(); + writer.emitEmptyLine(); } private void emitInjectContextMethod(JavaWriter writer) throws IOException { @@ -817,7 +871,7 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam || "int".equals(fieldType) || "short".equals(fieldType) || "byte".equals(fieldType)) { - writer.emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s())", fieldName, interfaceName, getter); + writer.emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); } else if ("java.lang.Long".equals(fieldType) || "java.lang.Integer".equals(fieldType) @@ -826,52 +880,52 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer .emitStatement("Number %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue())", fieldName, getter); + .emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter); if (isUpdate) { writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName); + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); } writer.endControlFlow(); } else if ("double".equals(fieldType)) { - writer.emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s())", fieldName, interfaceName, getter); + writer.emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); } else if("java.lang.Double".equals(fieldType)) { writer .emitStatement("Double %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, %s)", fieldName, getter); + .emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName); + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); } writer.endControlFlow(); } else if ("float".equals(fieldType)) { - writer.emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s())", fieldName, interfaceName, getter); + writer.emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); } else if ("java.lang.Float".equals(fieldType)) { writer .emitStatement("Float %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, %s)", fieldName, getter); + .emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName); + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); } writer.endControlFlow(); } else if ("boolean".equals(fieldType)) { - writer.emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s())", fieldName, interfaceName, getter); + writer.emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); } else if ("java.lang.Boolean".equals(fieldType)) { writer .emitStatement("Boolean %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, %s)", fieldName, getter); + .emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName); + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); } writer.endControlFlow(); @@ -879,10 +933,10 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer .emitStatement("byte[] %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sIndex, rowIndex, %s)", fieldName, getter); + .emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName); + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); } writer.endControlFlow(); @@ -891,10 +945,10 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer .emitStatement("java.util.Date %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.getTime())", fieldName, getter); + .emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.getTime(), false)", fieldName, getter); if (isUpdate) { writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName); + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); } writer.endControlFlow(); @@ -902,10 +956,10 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer .emitStatement("String %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sIndex, rowIndex, %s)", fieldName, getter); + .emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName); + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); } writer.endControlFlow(); } else { @@ -954,7 +1008,7 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { Utils.getProxyClassSimpleName(field), fieldName) .endControlFlow() - .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s)", fieldName) + .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .endControlFlow(); } else if (Utils.isRealmList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); @@ -1032,7 +1086,7 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { Utils.getProxyClassSimpleName(field), fieldName) .endControlFlow() - .emitStatement("table.setLink(columnInfo.%1$sIndex, rowIndex, cache%1$s)", fieldName) + .emitStatement("table.setLink(columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .endControlFlow(); } else if (Utils.isRealmList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); @@ -1106,7 +1160,7 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { fieldName, Utils.getProxyClassSimpleName(field)) .endControlFlow() - .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s)", fieldName) + .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) @@ -1187,7 +1241,7 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException fieldName, Utils.getProxyClassSimpleName(field)) .endControlFlow() - .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s)", fieldName) + .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 17697142ad..833187116f 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -9,6 +9,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableOrView; @@ -170,6 +171,9 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + row.getTable().setLong(columnInfo.columnLongIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -197,6 +201,9 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + row.getTable().setFloat(columnInfo.columnFloatIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -224,6 +231,9 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + row.getTable().setDouble(columnInfo.columnDoubleIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -251,6 +261,9 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + row.getTable().setBoolean(columnInfo.columnBooleanIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -278,6 +291,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'columnDate' to null."); + } + row.getTable().setDate(columnInfo.columnDateIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -308,6 +327,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'columnBinary' to null."); + } + row.getTable().setBinaryByteArray(columnInfo.columnBinaryIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -346,6 +371,20 @@ private void injectObjectContext() { if (value != null && !RealmObject.isManaged(value)) { value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value); } + final Row row = proxyState.getRow$realm(); + if (value == null) { + // Table#nullifyLink() does not support default value. Just use Row. + row.nullifyLink(columnInfo.columnObjectIndex); + return; + } + if (!RealmObject.isValid(value)) { + throw new IllegalArgumentException("'value' is not a valid managed object."); + } + if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { + throw new IllegalArgumentException("'value' belongs to a different Realm."); + } + row.getTable().setLink(columnInfo.columnObjectIndex, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -380,7 +419,6 @@ private void injectObjectContext() { } public void realmSet$columnRealmList(RealmList value) { - proxyState.getRealm$realm().checkIfValid(); if (proxyState == null) { // Called from model's constructor. Inject context. injectObjectContext(); @@ -407,6 +445,7 @@ private void injectObjectContext() { } } + proxyState.getRealm$realm().checkIfValid(); LinkView links = proxyState.getRow$realm().getLinkList(columnInfo.columnRealmListIndex); links.clear(); if (value == null) { @@ -897,17 +936,17 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); @@ -961,17 +1000,17 @@ public static void insert(Realm realm, Iterator objects, M Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong()); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnFloat()); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnDouble()); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnBoolean()); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnBoolean(), false); java.util.Date realmGet$columnDate = ((AllTypesRealmProxyInterface)object).realmGet$columnDate(); if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime()); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); } byte[] realmGet$columnBinary = ((AllTypesRealmProxyInterface)object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary); + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); } some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) object).realmGet$columnObject(); @@ -980,7 +1019,7 @@ public static void insert(Realm realm, Iterator objects, M if (cachecolumnObject == null) { cachecolumnObject = AllTypesRealmProxy.insert(realm, columnObjectObj, cache); } - table.setLink(columnInfo.columnObjectIndex, rowIndex, cachecolumnObject); + table.setLink(columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); } RealmList columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); @@ -1019,21 +1058,21 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); } cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong()); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnFloat()); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnDouble()); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnBoolean()); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnBoolean(), false); java.util.Date realmGet$columnDate = ((AllTypesRealmProxyInterface)object).realmGet$columnDate(); if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime()); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnDateIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.columnDateIndex, rowIndex, false); } byte[] realmGet$columnBinary = ((AllTypesRealmProxyInterface)object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary); + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, false); } some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) object).realmGet$columnObject(); @@ -1111,7 +1150,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cachecolumnObject == null) { cachecolumnObject = AllTypesRealmProxy.insertOrUpdate(realm, columnObjectObj, cache); } - Table.nativeSetLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex, cachecolumnObject); + Table.nativeSetLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); } else { Table.nativeNullifyLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 7621195151..cfbde4a0ac 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -9,6 +9,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableOrView; @@ -118,6 +119,9 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + row.getTable().setBoolean(columnInfo.doneIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -145,6 +149,9 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + row.getTable().setBoolean(columnInfo.isReadyIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -172,6 +179,9 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + row.getTable().setBoolean(columnInfo.mCompletedIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -199,6 +209,9 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + row.getTable().setBoolean(columnInfo.anotherBooleanIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -411,10 +424,10 @@ public static long insert(Realm realm, some.test.Booleans object, Map objects, M } long rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done()); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady()); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$mCompleted()); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$anotherBoolean()); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$anotherBoolean(), false); } } } @@ -449,10 +462,10 @@ public static long insertOrUpdate(Realm realm, some.test.Booleans object, Map ob } long rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done()); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady()); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$mCompleted()); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$anotherBoolean()); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$anotherBoolean(), false); } } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index b8b1322464..582b025120 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -9,6 +9,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableOrView; @@ -203,6 +204,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldStringNotNull' to null."); + } + row.getTable().setString(columnInfo.fieldStringNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -233,6 +240,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldStringNullIndex, row.getIndex(), true); + return; + } + row.getTable().setString(columnInfo.fieldStringNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -264,6 +278,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldBooleanNotNull' to null."); + } + row.getTable().setBoolean(columnInfo.fieldBooleanNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -297,6 +317,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldBooleanNullIndex, row.getIndex(), true); + return; + } + row.getTable().setBoolean(columnInfo.fieldBooleanNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -328,6 +355,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldBytesNotNull' to null."); + } + row.getTable().setBinaryByteArray(columnInfo.fieldBytesNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -358,6 +391,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldBytesNullIndex, row.getIndex(), true); + return; + } + row.getTable().setBinaryByteArray(columnInfo.fieldBytesNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -389,6 +429,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldByteNotNull' to null."); + } + row.getTable().setLong(columnInfo.fieldByteNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -422,6 +468,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldByteNullIndex, row.getIndex(), true); + return; + } + row.getTable().setLong(columnInfo.fieldByteNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -453,6 +506,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldShortNotNull' to null."); + } + row.getTable().setLong(columnInfo.fieldShortNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -486,6 +545,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldShortNullIndex, row.getIndex(), true); + return; + } + row.getTable().setLong(columnInfo.fieldShortNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -517,6 +583,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldIntegerNotNull' to null."); + } + row.getTable().setLong(columnInfo.fieldIntegerNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -550,6 +622,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldIntegerNullIndex, row.getIndex(), true); + return; + } + row.getTable().setLong(columnInfo.fieldIntegerNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -581,6 +660,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldLongNotNull' to null."); + } + row.getTable().setLong(columnInfo.fieldLongNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -614,6 +699,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldLongNullIndex, row.getIndex(), true); + return; + } + row.getTable().setLong(columnInfo.fieldLongNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -645,6 +737,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldFloatNotNull' to null."); + } + row.getTable().setFloat(columnInfo.fieldFloatNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -678,6 +776,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldFloatNullIndex, row.getIndex(), true); + return; + } + row.getTable().setFloat(columnInfo.fieldFloatNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -709,6 +814,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDoubleNotNull' to null."); + } + row.getTable().setDouble(columnInfo.fieldDoubleNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -742,6 +853,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldDoubleNullIndex, row.getIndex(), true); + return; + } + row.getTable().setDouble(columnInfo.fieldDoubleNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -773,6 +891,12 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDateNotNull' to null."); + } + row.getTable().setDate(columnInfo.fieldDateNotNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -806,6 +930,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldDateNullIndex, row.getIndex(), true); + return; + } + row.getTable().setDate(columnInfo.fieldDateNullIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -845,6 +976,20 @@ private void injectObjectContext() { if (value != null && !RealmObject.isManaged(value)) { value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value); } + final Row row = proxyState.getRow$realm(); + if (value == null) { + // Table#nullifyLink() does not support default value. Just use Row. + row.nullifyLink(columnInfo.fieldObjectNullIndex); + return; + } + if (!RealmObject.isValid(value)) { + throw new IllegalArgumentException("'value' is not a valid managed object."); + } + if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { + throw new IllegalArgumentException("'value' belongs to a different Realm."); + } + row.getTable().setLink(columnInfo.fieldObjectNullIndex, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -1537,83 +1682,83 @@ public static long insert(Realm realm, some.test.NullTypes object, Map objects, M cache.put(object, rowIndex); String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); } String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); } Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); } Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); } byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); } byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); } Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); } Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); } Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); } Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); } Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); } Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); } Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); } Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); } Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); } Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); } Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); } Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); } java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime()); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); } java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime()); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); } some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); @@ -1728,7 +1873,7 @@ public static void insert(Realm realm, Iterator objects, M if (cachefieldObjectNull == null) { cachefieldObjectNull = NullTypesRealmProxy.insert(realm, fieldObjectNullObj, cache); } - table.setLink(columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull); + table.setLink(columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); } } } @@ -1745,123 +1890,123 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map ob cache.put(object, rowIndex); String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, false); } String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, false); } Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, false); } Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, false); } byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, false); } byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, false); } Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, false); } Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, false); } Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, false); } Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, false); } Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, false); } Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, false); } Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, false); } Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue()); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, false); } Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, false); } Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, false); } Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, false); } Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, false); } java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime()); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, false); } java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime()); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, false); } some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); @@ -2018,7 +2163,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cachefieldObjectNull == null) { cachefieldObjectNull = NullTypesRealmProxy.insertOrUpdate(realm, fieldObjectNullObj, cache); } - Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull); + Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); } else { Table.nativeNullifyLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index c25a606f1f..19d2db6f8c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -9,6 +9,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.TableOrView; @@ -108,6 +109,13 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.nameIndex, row.getIndex(), true); + return; + } + row.getTable().setString(columnInfo.nameIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -139,6 +147,9 @@ private void injectObjectContext() { if (!proxyState.getAcceptDefaultValue$realm()) { return; } + final Row row = proxyState.getRow$realm(); + row.getTable().setLong(columnInfo.ageIndex, row.getIndex(), value, true); + return; } proxyState.getRealm$realm().checkIfValid(); @@ -303,9 +314,9 @@ public static long insert(Realm realm, some.test.Simple object, Map objects, M cache.put(object, rowIndex); String realmGet$name = ((SimpleRealmProxyInterface)object).realmGet$name(); if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name); + Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface)object).realmGet$age()); + Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface)object).realmGet$age(), false); } } } @@ -343,11 +354,11 @@ public static long insertOrUpdate(Realm realm, some.test.Simple object, Map ob cache.put(object, rowIndex); String realmGet$name = ((SimpleRealmProxyInterface)object).realmGet$name(); if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name); + Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.nameIndex, rowIndex); + Table.nativeSetNull(tableNativePtr, columnInfo.nameIndex, rowIndex, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface)object).realmGet$age()); + Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface)object).realmGet$age(), false); } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 9821c20c12..378a28b1d1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -382,6 +382,7 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); + looperThread.keepStrongReference.add(allTypes); } @Test @@ -406,6 +407,7 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); + looperThread.keepStrongReference.add(allTypes); } // Initialize a Dynamic Realm used by the *Async tests and keep it ref in the looperThread. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 9f44948320..933d9d7e95 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -65,6 +65,7 @@ import io.realm.entities.CyclicTypePrimaryKey; import io.realm.entities.DefaultValueConstructor; import io.realm.entities.DefaultValueOfField; +import io.realm.entities.DefaultValueOverwriteNullLink; import io.realm.entities.DefaultValueSetter; import io.realm.entities.Dog; import io.realm.entities.DogPrimaryKey; @@ -2330,6 +2331,16 @@ public void execute(Realm realm) { RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); } + @Test + public void createObject_overwriteNullifiedLinkWithDefaultValue() { + final DefaultValueOverwriteNullLink created; + realm.beginTransaction(); + created = realm.createObject(DefaultValueOverwriteNullLink.class); + realm.commitTransaction(); + + assertEquals(created.getExpectedKeyOfFieldObject(), created.getFieldObject().getFieldRandomPrimaryKey()); + } + @Test public void createObject_defaultValueFromModelConstructor() { realm.executeTransaction(new Realm.Transaction() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOverwriteNullLink.java b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOverwriteNullLink.java new file mode 100644 index 0000000000..e6f673de3a --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOverwriteNullLink.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import java.util.Date; +import java.util.UUID; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.Ignore; +import io.realm.annotations.PrimaryKey; + +public class DefaultValueOverwriteNullLink extends RealmObject { + + public static final String CLASS_NAME = "DefaultValueOverwriteNullLink"; + public static String FIELD_OBJECT = "fieldObject"; + public static String EXPECTED_KEY_OF_FIELD_OBJECT = "expectedKeyOfFieldObject"; + + private RandomPrimaryKey fieldObject; + private String expectedKeyOfFieldObject; + + public DefaultValueOverwriteNullLink() { + final RandomPrimaryKey firstDefaultValue = new RandomPrimaryKey(); + final RandomPrimaryKey secondDefaultValue = new RandomPrimaryKey(); + + expectedKeyOfFieldObject = secondDefaultValue.getFieldRandomPrimaryKey(); + + fieldObject = firstDefaultValue; + fieldObject = null; + fieldObject = secondDefaultValue; + } + + public RandomPrimaryKey getFieldObject() { + return fieldObject; + } + + public void setFieldObject(RandomPrimaryKey fieldObject) { + this.fieldObject = fieldObject; + } + + public String getExpectedKeyOfFieldObject() { + return expectedKeyOfFieldObject; + } + + public void setExpectedKeyOfFieldObject(String expectedKeyOfFieldObject) { + this.expectedKeyOfFieldObject = expectedKeyOfFieldObject; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java index d7c477cd7a..5cbffdd653 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java @@ -18,7 +18,6 @@ import android.test.AndroidTestCase; -import io.realm.RealmFieldType; import io.realm.TestHelper; public class JNICloseTest extends AndroidTestCase { @@ -30,14 +29,14 @@ public void testQueryAccessibleAfterTableClose() throws Throwable{ Table table = TestHelper.getTableWithAllColumnTypes(); table.addEmptyRows(10); for (long i=0; i> columnInfoList = Arrays.asList( + new Pair(RealmFieldType.STRING, "string value"), + new Pair(RealmFieldType.INTEGER, 100L), + new Pair(RealmFieldType.BOOLEAN, true), + new Pair(RealmFieldType.BINARY, new byte[]{123}), + new Pair(RealmFieldType.DATE, new Date(123456)), + new Pair(RealmFieldType.FLOAT, 1.234f), + new Pair(RealmFieldType.DOUBLE, Math.PI), + new Pair(RealmFieldType.OBJECT, 0L) + // currently, LIST does not support default value + //new Pair(RealmFieldType.LIST, ) + ); + + for (Pair columnInfo : columnInfoList) { + final RealmFieldType type = columnInfo.first; + if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { + table.addColumnLink(type, type.name().toLowerCase(Locale.ENGLISH) + "Col", table); + } else { + table.addColumn(type, type.name().toLowerCase(Locale.ENGLISH) + "Col"); + } + } + + sharedRealm.beginTransaction(); + table.addEmptyRow(); + + ListIterator> it = columnInfoList.listIterator(); + for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { + Pair columnInfo = it.next(); + final RealmFieldType type = columnInfo.first; + final Object value = columnInfo.second; + + switch (type) { + case STRING: + table.setString(columnIndex, 0, (String) value, true); + assertEquals(value, table.getString(columnIndex, 0)); + break; + case INTEGER: + table.setLong(columnIndex, 0, (long) value, true); + assertEquals(value, table.getLong(columnIndex, 0)); + break; + case BOOLEAN: + table.setBoolean(columnIndex, 0, (boolean) value, true); + assertEquals(value, table.getBoolean(columnIndex, 0)); + break; + case BINARY: + table.setBinaryByteArray(columnIndex, 0, (byte[]) value, true); + assertTrue(Arrays.equals((byte[]) value, table.getBinaryByteArray(columnIndex, 0))); + break; + case DATE: + table.setDate(columnIndex, 0, (Date) value, true); + assertEquals(value, table.getDate(columnIndex, 0)); + break; + case FLOAT: + table.setFloat(columnIndex, 0, (float) value, true); + assertEquals(value, table.getFloat(columnIndex, 0)); + break; + case DOUBLE: + table.setDouble(columnIndex, 0, (double) value, true); + assertEquals(value, table.getDouble(columnIndex, 0)); + break; + case OBJECT: + table.setLink(columnIndex, 0, (long) value, true); + assertEquals(value, table.getLink(columnIndex, 0)); + break; + default: + throw new RuntimeException("unexpected field type: " + type); + } + } + sharedRealm.commitTransaction(); + + // check if the value can be read after committing transaction + it = columnInfoList.listIterator(); + for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { + Pair columnInfo = it.next(); + final RealmFieldType type = columnInfo.first; + final Object value = columnInfo.second; + + switch (type) { + case STRING: + assertEquals(value, table.getString(columnIndex, 0)); + break; + case INTEGER: + assertEquals(value, table.getLong(columnIndex, 0)); + break; + case BOOLEAN: + assertEquals(value, table.getBoolean(columnIndex, 0)); + break; + case BINARY: + assertTrue(Arrays.equals((byte[]) value, table.getBinaryByteArray(columnIndex, 0))); + break; + case DATE: + assertEquals(value, table.getDate(columnIndex, 0)); + break; + case FLOAT: + assertEquals(value, table.getFloat(columnIndex, 0)); + break; + case DOUBLE: + assertEquals(value, table.getDouble(columnIndex, 0)); + break; + case OBJECT: + assertEquals(value, table.getLink(columnIndex, 0)); + break; + default: + throw new RuntimeException("unexpected field type: " + type); + } + } + + } finally { + sharedRealm.close(); + } + } + + @Test + public void defaultValue_setMultipleTimes() { + // t is not used in this test + t = null; + final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); + //noinspection TryFinallyCanBeTryWithResources + try { + sharedRealm.beginTransaction(); + final Table table = sharedRealm.getTable(Table.TABLE_PREFIX + "DefaultValueTest"); + sharedRealm.commitTransaction(); + + List> columnInfoList = Arrays.asList( + new Pair(RealmFieldType.STRING, new String[] {"string value1", "string value2"}), + new Pair(RealmFieldType.INTEGER, new Long[] {100L, 102L}), + new Pair(RealmFieldType.BOOLEAN, new Boolean[] {false, true}), + new Pair(RealmFieldType.BINARY, new byte[][] {new byte[]{123}, new byte[]{-123}}), + new Pair(RealmFieldType.DATE, new Date[] {new Date(123456), new Date(13579)}), + new Pair(RealmFieldType.FLOAT, new Float[] {1.234f, 100f}), + new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}), + new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L}) + // currently, LIST does not support default value + //new Pair(RealmFieldType.LIST, ) + ); + + for (Pair columnInfo : columnInfoList) { + final RealmFieldType type = columnInfo.first; + if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { + table.addColumnLink(type, type.name().toLowerCase(Locale.ENGLISH) + "Col", table); + } else { + table.addColumn(type, type.name().toLowerCase(Locale.ENGLISH) + "Col"); + } + } + + sharedRealm.beginTransaction(); + table.addEmptyRow(); + table.addEmptyRow(); // for link field update + + ListIterator> it = columnInfoList.listIterator(); + for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { + Pair columnInfo = it.next(); + final RealmFieldType type = columnInfo.first; + final Object value1 = ((Object[]) columnInfo.second)[0]; + final Object value2 = ((Object[]) columnInfo.second)[1]; + + switch (type) { + case STRING: + table.setString(columnIndex, 0, (String) value1, true); + table.setString(columnIndex, 0, (String) value2, true); + assertEquals(value2, table.getString(columnIndex, 0)); + break; + case INTEGER: + table.setLong(columnIndex, 0, (long) value1, true); + table.setLong(columnIndex, 0, (long) value2, true); + assertEquals(value2, table.getLong(columnIndex, 0)); + break; + case BOOLEAN: + table.setBoolean(columnIndex, 0, (boolean) value1, true); + table.setBoolean(columnIndex, 0, (boolean) value2, true); + assertEquals(value2, table.getBoolean(columnIndex, 0)); + break; + case BINARY: + table.setBinaryByteArray(columnIndex, 0, (byte[]) value1, true); + table.setBinaryByteArray(columnIndex, 0, (byte[]) value2, true); + assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnIndex, 0))); + break; + case DATE: + table.setDate(columnIndex, 0, (Date) value1, true); + table.setDate(columnIndex, 0, (Date) value2, true); + assertEquals(value2, table.getDate(columnIndex, 0)); + break; + case FLOAT: + table.setFloat(columnIndex, 0, (float) value1, true); + table.setFloat(columnIndex, 0, (float) value2, true); + assertEquals(value2, table.getFloat(columnIndex, 0)); + break; + case DOUBLE: + table.setDouble(columnIndex, 0, (double) value1, true); + table.setDouble(columnIndex, 0, (double) value2, true); + assertEquals(value2, table.getDouble(columnIndex, 0)); + break; + case OBJECT: + table.setLink(columnIndex, 0, (long) value1, true); + table.setLink(columnIndex, 0, (long) value2, true); + assertEquals(value2, table.getLink(columnIndex, 0)); + break; + default: + throw new RuntimeException("unexpected field type: " + type); + } + } + sharedRealm.commitTransaction(); + + // check if the value can be read after committing transaction + it = columnInfoList.listIterator(); + for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { + Pair columnInfo = it.next(); + final RealmFieldType type = columnInfo.first; + final Object value2 = ((Object[]) columnInfo.second)[1]; + + switch (type) { + case STRING: + assertEquals(value2, table.getString(columnIndex, 0)); + break; + case INTEGER: + assertEquals(value2, table.getLong(columnIndex, 0)); + break; + case BOOLEAN: + assertEquals(value2, table.getBoolean(columnIndex, 0)); + break; + case BINARY: + assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnIndex, 0))); + break; + case DATE: + assertEquals(value2, table.getDate(columnIndex, 0)); + break; + case FLOAT: + assertEquals(value2, table.getFloat(columnIndex, 0)); + break; + case DOUBLE: + assertEquals(value2, table.getDouble(columnIndex, 0)); + break; + case OBJECT: + assertEquals(value2, table.getLink(columnIndex, 0)); + break; + default: + throw new RuntimeException("unexpected field type: " + type); + } + } + } finally { + sharedRealm.close(); + } + } + + @Test + public void defaultValue_overwrittenByNonDefault() { + // t is not used in this test + t = null; + final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); + //noinspection TryFinallyCanBeTryWithResources + try { + sharedRealm.beginTransaction(); + final Table table = sharedRealm.getTable(Table.TABLE_PREFIX + "DefaultValueTest"); + sharedRealm.commitTransaction(); + + List> columnInfoList = Arrays.asList( + new Pair(RealmFieldType.STRING, new String[] {"string value1", "string value2"}), + new Pair(RealmFieldType.INTEGER, new Long[] {100L, 102L}), + new Pair(RealmFieldType.BOOLEAN, new Boolean[] {false, true}), + new Pair(RealmFieldType.BINARY, new byte[][] {new byte[]{123}, new byte[]{-123}}), + new Pair(RealmFieldType.DATE, new Date[] {new Date(123456), new Date(13579)}), + new Pair(RealmFieldType.FLOAT, new Float[] {1.234f, 100f}), + new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}), + new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L}) + // currently, LIST does not support default value + //new Pair(RealmFieldType.LIST, ) + ); + + for (Pair columnInfo : columnInfoList) { + final RealmFieldType type = columnInfo.first; + if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { + table.addColumnLink(type, type.name().toLowerCase(Locale.ENGLISH) + "Col", table); + } else { + table.addColumn(type, type.name().toLowerCase(Locale.ENGLISH) + "Col"); + } + } + + sharedRealm.beginTransaction(); + table.addEmptyRow(); + table.addEmptyRow(); // for link field update + + // set as default + ListIterator> it = columnInfoList.listIterator(); + for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { + Pair columnInfo = it.next(); + final RealmFieldType type = columnInfo.first; + final Object value1 = ((Object[]) columnInfo.second)[0]; + + switch (type) { + case STRING: + table.setString(columnIndex, 0, (String) value1, true); + break; + case INTEGER: + table.setLong(columnIndex, 0, (long) value1, true); + break; + case BOOLEAN: + table.setBoolean(columnIndex, 0, (boolean) value1, true); + break; + case BINARY: + table.setBinaryByteArray(columnIndex, 0, (byte[]) value1, true); + break; + case DATE: + table.setDate(columnIndex, 0, (Date) value1, true); + break; + case FLOAT: + table.setFloat(columnIndex, 0, (float) value1, true); + break; + case DOUBLE: + table.setDouble(columnIndex, 0, (double) value1, true); + break; + case OBJECT: + table.setLink(columnIndex, 0, (long) value1, true); + break; + default: + throw new RuntimeException("unexpected field type: " + type); + } + } + sharedRealm.commitTransaction(); + + // update as non default + sharedRealm.beginTransaction(); + it = columnInfoList.listIterator(); + for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { + Pair columnInfo = it.next(); + final RealmFieldType type = columnInfo.first; + final Object value2 = ((Object[]) columnInfo.second)[1]; + + switch (type) { + case STRING: + table.setString(columnIndex, 0, (String) value2, false); + assertEquals(value2, table.getString(columnIndex, 0)); + break; + case INTEGER: + table.setLong(columnIndex, 0, (long) value2, false); + assertEquals(value2, table.getLong(columnIndex, 0)); + break; + case BOOLEAN: + table.setBoolean(columnIndex, 0, (boolean) value2, false); + assertEquals(value2, table.getBoolean(columnIndex, 0)); + break; + case BINARY: + table.setBinaryByteArray(columnIndex, 0, (byte[]) value2, false); + assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnIndex, 0))); + break; + case DATE: + table.setDate(columnIndex, 0, (Date) value2, false); + assertEquals(value2, table.getDate(columnIndex, 0)); + break; + case FLOAT: + table.setFloat(columnIndex, 0, (float) value2, false); + assertEquals(value2, table.getFloat(columnIndex, 0)); + break; + case DOUBLE: + table.setDouble(columnIndex, 0, (double) value2, false); + assertEquals(value2, table.getDouble(columnIndex, 0)); + break; + case OBJECT: + table.setLink(columnIndex, 0, (long) value2, false); + assertEquals(value2, table.getLink(columnIndex, 0)); + break; + default: + throw new RuntimeException("unexpected field type: " + type); + } + } + sharedRealm.commitTransaction(); + + // check if the value was overwritten + it = columnInfoList.listIterator(); + for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { + Pair columnInfo = it.next(); + final RealmFieldType type = columnInfo.first; + final Object value2 = ((Object[]) columnInfo.second)[1]; + + switch (type) { + case STRING: + assertEquals(value2, table.getString(columnIndex, 0)); + break; + case INTEGER: + assertEquals(value2, table.getLong(columnIndex, 0)); + break; + case BOOLEAN: + assertEquals(value2, table.getBoolean(columnIndex, 0)); + break; + case BINARY: + assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnIndex, 0))); + break; + case DATE: + assertEquals(value2, table.getDate(columnIndex, 0)); + break; + case FLOAT: + assertEquals(value2, table.getFloat(columnIndex, 0)); + break; + case DOUBLE: + assertEquals(value2, table.getDouble(columnIndex, 0)); + break; + case OBJECT: + assertEquals(value2, table.getLink(columnIndex, 0)); + break; + default: + throw new RuntimeException("unexpected field type: " + type); + } + } + } finally { + sharedRealm.close(); + } + } } + diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java new file mode 100644 index 0000000000..e1088af833 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import io.realm.RealmFieldType; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class JNITableViewTest { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + private static final String TABLE_NAME = Table.TABLE_PREFIX + "JNITableViewTest"; + private static final int ROW_COUNT = 10; + + private static final List FIELDS = Arrays.asList( + RealmFieldType.INTEGER, + RealmFieldType.BOOLEAN, + RealmFieldType.STRING, + RealmFieldType.BINARY, + RealmFieldType.DATE, + RealmFieldType.FLOAT, + RealmFieldType.DOUBLE); + private static final long INTEGER_COLUMN_INDEX = 0; + private static final long STRING_COLUMN_INDEX = 2; + + private SharedRealm sharedRealm; + + private Table table; + + @Before + public void setUp() { + sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); + sharedRealm.beginTransaction(); + try { + table = sharedRealm.getTable(TABLE_NAME); + + for (RealmFieldType field : FIELDS) { + final long index = table.addColumn(field, field.name().toLowerCase(Locale.ENGLISH) + "Column"); + table.convertColumnToNullable(index); + } + + for (int i = 0; i < ROW_COUNT; i++) { + table.add(i, true, "abcd", new byte[]{123, -123}, new Date(12345), 1.234f, 3.446d); + } + } finally { + sharedRealm.commitTransaction(); + } + } + + @Test + public void setNull() { + TableQuery query = table.where(); + for (int i = 0; i < ROW_COUNT; i++) { + if (isOdd(i)) { + query = query.or().equalTo(new long[]{INTEGER_COLUMN_INDEX}, (long) i); + } + } + final TableView oddRows = query.findAll(); + + sharedRealm.beginTransaction(); + for (int i = 0; i < oddRows.size(); i++) { + oddRows.setNull(STRING_COLUMN_INDEX, i, false); + } + sharedRealm.commitTransaction(); + + // check if TableView#setNull() worked as expected + for (int i = 0; i < table.size(); i++) { + assertEquals("index: " + i, isOdd(i), table.isNull(STRING_COLUMN_INDEX, i)); + } + } + + @Test + public void isNull() { + + sharedRealm.beginTransaction(); + for (int i = 0; i < table.size(); i++) { + if (isOdd(i)) { + table.setNull(STRING_COLUMN_INDEX, i, false); + } + } + sharedRealm.commitTransaction(); + + TableQuery query = table.where(); + for (int i = 0; i < ROW_COUNT; i++) { + if (isOdd(i)) { + query = query.or().equalTo(new long[]{INTEGER_COLUMN_INDEX}, (long) i); + } + } + final TableView oddRows = query.findAll(); + for (int i = 0; i < oddRows.size(); i++) { + assertEquals("index: " + i, true, oddRows.isNull(STRING_COLUMN_INDEX, i)); + } + + query = table.where(); + for (int i = 0; i < ROW_COUNT; i++) { + if (isEven(i)) { + query = query.or().equalTo(new long[]{INTEGER_COLUMN_INDEX}, (long) i); + } + } + final TableView evenRows = query.findAll(); + for (int i = 0; i < evenRows.size(); i++) { + assertEquals("index: " + i, false, evenRows.isNull(STRING_COLUMN_INDEX, i)); + } + } + + private static boolean isEven(int i) { + return i % 2 == 0; + } + private static boolean isOdd(int i) { + return i % 2 == 1; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java index 9a3d5669e7..661e6c1a6d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java @@ -131,7 +131,7 @@ public void testSetBinary() { byte[] arr2 = new byte[] {1,2,3, 4, 5}; - view.setBinaryByteArray(0, 0, arr2); + view.setBinaryByteArray(0, 0, arr2, false); MoreAsserts.assertEquals(arr2, view.getBinaryByteArray(0, 0)); } @@ -372,7 +372,7 @@ public void testViewShouldInvalidate() { assertEquals(1, view.size()); // access view after change in value is ok - t.setLong(0, 0, 3); + t.setLong(0, 0, 3, false); accessingViewOk(view); // access view after additions to table must fail diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 5f8c54dbd6..626f4a3a54 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -615,26 +615,31 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLinkTarget return 0; } +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNull + (JNIEnv*, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +{ + return TBL(nativeTablePtr)->is_null( S(columnIndex), S(rowIndex)) ? JNI_TRUE : JNI_FALSE; // noexcept +} // ----------------- Set cell JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLink - (JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong targetRowIndex) + (JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong targetRowIndex, jboolean isDefault) { if (!TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) return; try { - TBL(nativeTablePtr)->set_link( S(columnIndex), S(rowIndex), S(targetRowIndex)); + TBL(nativeTablePtr)->set_link( S(columnIndex), S(rowIndex), S(targetRowIndex), B(isDefault)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLong( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong value) + JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong value, jboolean isDefault) { if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) return; try { - TBL(nativeTablePtr)->set_int( S(columnIndex), S(rowIndex), value); + TBL(nativeTablePtr)->set_int( S(columnIndex), S(rowIndex), value, B(isDefault)); } CATCH_STD() } @@ -650,37 +655,37 @@ Java_io_realm_internal_Table_nativeSetLongUnique(JNIEnv *env, jclass, jlong nati } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jboolean value) + JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jboolean value, jboolean isDefault) { if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Bool)) return; try { - TBL(nativeTablePtr)->set_bool( S(columnIndex), S(rowIndex), value == JNI_TRUE ? true : false); + TBL(nativeTablePtr)->set_bool( S(columnIndex), S(rowIndex), B(value), B(isDefault)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetFloat( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jfloat value) + JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jfloat value, jboolean isDefault) { if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Float)) return; try { - TBL(nativeTablePtr)->set_float( S(columnIndex), S(rowIndex), value); + TBL(nativeTablePtr)->set_float( S(columnIndex), S(rowIndex), value, B(isDefault)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetDouble( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jdouble value) + JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jdouble value, jboolean isDefault) { if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Double)) return; try { - TBL(nativeTablePtr)->set_double( S(columnIndex), S(rowIndex), value); + TBL(nativeTablePtr)->set_double( S(columnIndex), S(rowIndex), value, B(isDefault)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jstring value) + JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jstring value, jboolean isDefault) { if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) return; @@ -691,7 +696,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString( } } JStringAccessor value2(env, value); // throws - TBL(nativeTablePtr)->set_string( S(columnIndex), S(rowIndex), value2); + TBL(nativeTablePtr)->set_string( S(columnIndex), S(rowIndex), value2, B(isDefault)); } CATCH_STD() } @@ -715,12 +720,13 @@ Java_io_realm_internal_Table_nativeSetStringUnique(JNIEnv *env, jclass, jlong na } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong timestampValue) + JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong timestampValue, jboolean isDefault) { if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Timestamp)) return; try { - TBL(nativeTablePtr)->set_timestamp( S(columnIndex), S(rowIndex), from_milliseconds(timestampValue)); + TBL(nativeTablePtr)->set_timestamp( S(columnIndex), S(rowIndex), from_milliseconds(timestampValue), + B(isDefault)); } CATCH_STD() } @@ -737,7 +743,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteBuffer( */ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jbyteArray dataArray) + JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jbyteArray dataArray, jboolean isDefault) { if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) return; @@ -747,12 +753,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray( } JniByteArray byteAccessor(env, dataArray); - TBL(nativeTablePtr)->set_binary(S(columnIndex), S(rowIndex), byteAccessor); + TBL(nativeTablePtr)->set_binary(S(columnIndex), S(rowIndex), byteAccessor, B(isDefault)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) + JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jboolean isDefault) { Table* pTable = TBL(nativeTablePtr); if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) @@ -762,7 +768,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull( if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) return; try { - pTable->set_null(S(columnIndex), S(rowIndex)); + pTable->set_null(S(columnIndex), S(rowIndex), B(isDefault)); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp index b4d561be94..7c4b5aee76 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp @@ -317,6 +317,17 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetLink return TV(nativeViewPtr)->get_link( S(columnIndex), S(rowIndex)); // noexcept } +JNIEXPORT jboolean JNICALL Java_io_realm_internal_TableView_nativeIsNull + (JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) +{ + try { + if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) + return 0; + return TV(nativeViewPtr)->get_parent().is_null( S(columnIndex), TV(nativeViewPtr)->get_source_ndx(S(rowIndex))) ? JNI_TRUE : JNI_FALSE; // noexcept + } CATCH_STD() + return 0; +} + // Setters JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetLong( diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index de51d80733..4e6f2eed7b 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -366,6 +366,7 @@ public void createAllFromJson(Class clazz, JSONArray j if (clazz == null || json == null) { return; } + checkIfValid(); for (int i = 0; i < json.length(); i++) { try { @@ -395,6 +396,7 @@ public void createOrUpdateAllFromJson(Class clazz, JSO if (clazz == null || json == null) { return; } + checkIfValid(); checkHasPrimaryKey(clazz); for (int i = 0; i < json.length(); i++) { try { @@ -450,6 +452,7 @@ public void createOrUpdateAllFromJson(Class clazz, Str if (clazz == null || json == null || json.length() == 0) { return; } + checkIfValid(); checkHasPrimaryKey(clazz); JSONArray arr; @@ -481,6 +484,7 @@ public void createAllFromJson(Class clazz, InputStream if (clazz == null || inputStream == null) { return; } + checkIfValid(); JsonReader reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8")); try { @@ -516,6 +520,7 @@ public void createOrUpdateAllFromJson(Class clazz, Inp if (clazz == null || in == null) { return; } + checkIfValid(); checkHasPrimaryKey(clazz); // As we need the primary key value we have to first parse the entire input stream as in the general @@ -553,6 +558,7 @@ public E createObjectFromJson(Class clazz, JSONObject if (clazz == null || json == null) { return null; } + checkIfValid(); try { return configuration.getSchemaMediator().createOrUpdateUsingJsonObject(clazz, this, json, false); @@ -580,6 +586,7 @@ public E createOrUpdateObjectFromJson(Class clazz, JSO if (clazz == null || json == null) { return null; } + checkIfValid(); checkHasPrimaryKey(clazz); try { E realmObject = configuration.getSchemaMediator().createOrUpdateUsingJsonObject(clazz, this, json, true); @@ -636,6 +643,7 @@ public E createOrUpdateObjectFromJson(Class clazz, Str if (clazz == null || json == null || json.length() == 0) { return null; } + checkIfValid(); checkHasPrimaryKey(clazz); JSONObject obj; @@ -668,6 +676,7 @@ public E createObjectFromJson(Class clazz, InputStream if (clazz == null || inputStream == null) { return null; } + checkIfValid(); E realmObject; Table table = schema.getTable(clazz); if (table.hasPrimaryKey()) { @@ -720,6 +729,7 @@ public E createOrUpdateObjectFromJson(Class clazz, Inp if (clazz == null || in == null) { return null; } + checkIfValid(); checkHasPrimaryKey(clazz); // As we need the primary key value we have to first parse the entire input stream as in the general diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index a0c5c5ebaf..c99ace2268 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -214,7 +214,7 @@ public void renameColumn(long columnIndex, String newName) { } long pkRowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, className); if (pkRowIndex != NO_MATCH) { - pkTable.setString(PRIMARY_KEY_FIELD_COLUMN_INDEX, pkRowIndex, newName); + pkTable.setString(PRIMARY_KEY_FIELD_COLUMN_INDEX, pkRowIndex, newName, false); } else { throw new IllegalStateException("Non-existent PrimaryKey column cannot be renamed"); } @@ -520,43 +520,43 @@ protected long add(Object... values) { Object value = values[(int)columnIndex]; switch (colTypes[(int)columnIndex]) { case BOOLEAN: - nativeSetBoolean(nativePtr, columnIndex, rowIndex, (Boolean)value); + nativeSetBoolean(nativePtr, columnIndex, rowIndex, (Boolean)value, false); break; case INTEGER: if (value == null) { checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); - nativeSetNull(nativePtr, columnIndex, rowIndex); + nativeSetNull(nativePtr, columnIndex, rowIndex, false); } else { long intValue = ((Number) value).longValue(); checkIntValueIsLegal(columnIndex, rowIndex, intValue); - nativeSetLong(nativePtr, columnIndex, rowIndex, intValue); + nativeSetLong(nativePtr, columnIndex, rowIndex, intValue, false); } break; case FLOAT: - nativeSetFloat(nativePtr, columnIndex, rowIndex, (Float) value); + nativeSetFloat(nativePtr, columnIndex, rowIndex, (Float) value, false); break; case DOUBLE: - nativeSetDouble(nativePtr, columnIndex, rowIndex, (Double) value); + nativeSetDouble(nativePtr, columnIndex, rowIndex, (Double) value, false); break; case STRING: if (value == null) { checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); - nativeSetNull(nativePtr, columnIndex, rowIndex); + nativeSetNull(nativePtr, columnIndex, rowIndex, false); } else { String stringValue = (String) value; checkStringValueIsLegal(columnIndex, rowIndex, stringValue); - nativeSetString(nativePtr, columnIndex, rowIndex, (String) value); + nativeSetString(nativePtr, columnIndex, rowIndex, (String) value, false); } break; case DATE: if (value == null) throw new IllegalArgumentException("Null Date is not allowed."); - nativeSetTimestamp(nativePtr, columnIndex, rowIndex, ((Date) value).getTime()); + nativeSetTimestamp(nativePtr, columnIndex, rowIndex, ((Date) value).getTime(), false); break; case BINARY: if (value == null) throw new IllegalArgumentException("Null Array is not allowed"); - nativeSetByteArray(nativePtr, columnIndex, rowIndex, (byte[])value); + nativeSetByteArray(nativePtr, columnIndex, rowIndex, (byte[])value, false); break; case UNSUPPORTED_MIXED: case UNSUPPORTED_TABLE: @@ -710,6 +710,7 @@ public byte[] getBinaryByteArray(long columnIndex, long rowIndex) { return nativeGetByteArray(nativePtr, columnIndex, rowIndex); } + @Override public long getLink(long columnIndex, long rowIndex) { return nativeGetLink(nativePtr, columnIndex, rowIndex); } @@ -728,6 +729,11 @@ public Table getLinkTarget(long columnIndex) { } } + @Override + public boolean isNull(long columnIndex, long rowIndex) { + return nativeIsNull(nativePtr, columnIndex, rowIndex); + } + /** * Returns a non-checking Row. Incorrect use of this Row will cause a hard core crash. * If error checking is required, use {@link #getCheckedRow(long)} instead. @@ -768,36 +774,36 @@ public CheckedRow getCheckedRow(long index) { // @Override - public void setLong(long columnIndex, long rowIndex, long value) { + public void setLong(long columnIndex, long rowIndex, long value, boolean isDefault) { checkImmutable(); checkIntValueIsLegal(columnIndex, rowIndex, value); - nativeSetLong(nativePtr, columnIndex, rowIndex, value); + nativeSetLong(nativePtr, columnIndex, rowIndex, value, isDefault); } @Override - public void setBoolean(long columnIndex, long rowIndex, boolean value) { + public void setBoolean(long columnIndex, long rowIndex, boolean value, boolean isDefault) { checkImmutable(); - nativeSetBoolean(nativePtr, columnIndex, rowIndex, value); + nativeSetBoolean(nativePtr, columnIndex, rowIndex, value, isDefault); } @Override - public void setFloat(long columnIndex, long rowIndex, float value) { + public void setFloat(long columnIndex, long rowIndex, float value, boolean isDefault) { checkImmutable(); - nativeSetFloat(nativePtr, columnIndex, rowIndex, value); + nativeSetFloat(nativePtr, columnIndex, rowIndex, value, isDefault); } @Override - public void setDouble(long columnIndex, long rowIndex, double value) { + public void setDouble(long columnIndex, long rowIndex, double value, boolean isDefault) { checkImmutable(); - nativeSetDouble(nativePtr, columnIndex, rowIndex, value); + nativeSetDouble(nativePtr, columnIndex, rowIndex, value, isDefault); } @Override - public void setDate(long columnIndex, long rowIndex, Date date) { + public void setDate(long columnIndex, long rowIndex, Date date, boolean isDefault) { if (date == null) throw new IllegalArgumentException("Null Date is not allowed."); checkImmutable(); - nativeSetTimestamp(nativePtr, columnIndex, rowIndex, date.getTime()); + nativeSetTimestamp(nativePtr, columnIndex, rowIndex, date.getTime(), isDefault); } /** @@ -808,26 +814,33 @@ public void setDate(long columnIndex, long rowIndex, Date date) { * @param value a String value to set in the cell. */ @Override - public void setString(long columnIndex, long rowIndex, String value) { + public void setString(long columnIndex, long rowIndex, String value, boolean isDefault) { checkImmutable(); if (value == null) { checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); - nativeSetNull(nativePtr, columnIndex, rowIndex); + nativeSetNull(nativePtr, columnIndex, rowIndex, isDefault); } else { checkStringValueIsLegal(columnIndex, rowIndex, value); - nativeSetString(nativePtr, columnIndex, rowIndex, value); + nativeSetString(nativePtr, columnIndex, rowIndex, value, isDefault); } } @Override - public void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data) { + public void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data, boolean isDefault) { + checkImmutable(); + nativeSetByteArray(nativePtr, columnIndex, rowIndex, data, isDefault); + } + + @Override + public void setLink(long columnIndex, long rowIndex, long value, boolean isDefault) { checkImmutable(); - nativeSetByteArray(nativePtr, columnIndex, rowIndex, data); + nativeSetLink(nativePtr, columnIndex, rowIndex, value, isDefault); } - public void setLink(long columnIndex, long rowIndex, long value) { + public void setNull(long columnIndex, long rowIndex, boolean isDefault) { checkImmutable(); - nativeSetLink(nativePtr, columnIndex, rowIndex, value); + checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); + nativeSetNull(nativePtr, columnIndex, rowIndex, isDefault); } public void addSearchIndex(long columnIndex) { @@ -899,10 +912,12 @@ public boolean hasSearchIndex(long columnIndex) { return nativeHasSearchIndex(nativePtr, columnIndex); } + @Override public boolean isNullLink(long columnIndex, long rowIndex) { return nativeIsNullLink(nativePtr, columnIndex, rowIndex); } + @Override public void nullifyLink(long columnIndex, long rowIndex) { nativeNullifyLink(nativePtr, columnIndex, rowIndex); } @@ -1325,20 +1340,21 @@ public static String tableNameToClassName(String tableName) { private native long nativeGetLink(long nativePtr, long columnIndex, long rowIndex); public static native long nativeGetLinkView(long nativePtr, long columnIndex, long rowIndex); private native long nativeGetLinkTarget(long nativePtr, long columnIndex); + private native boolean nativeIsNull(long nativePtr, long columnIndex, long rowIndex); native long nativeGetRowPtr(long nativePtr, long index); - public static native void nativeSetLong(long nativeTablePtr, long columnIndex, long rowIndex, long value); + public static native void nativeSetLong(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); public static native void nativeSetLongUnique(long nativeTablePtr, long columnIndex, long rowIndex, long value); - public static native void nativeSetBoolean(long nativeTablePtr, long columnIndex, long rowIndex, boolean value); - public static native void nativeSetFloat(long nativeTablePtr, long columnIndex, long rowIndex, float value); - public static native void nativeSetDouble(long nativeTablePtr, long columnIndex, long rowIndex, double value); - public static native void nativeSetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex, long dateTimeValue); - public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value); + public static native void nativeSetBoolean(long nativeTablePtr, long columnIndex, long rowIndex, boolean value, boolean isDefault); + public static native void nativeSetFloat(long nativeTablePtr, long columnIndex, long rowIndex, float value, boolean isDefault); + public static native void nativeSetDouble(long nativeTablePtr, long columnIndex, long rowIndex, double value, boolean isDefault); + public static native void nativeSetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex, long dateTimeValue, boolean isDefault); + public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value, boolean isDefault); public static native void nativeSetStringUnique(long nativeTablePtr, long columnIndex, long rowIndex, String value); - public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex); + public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex, boolean isDefault); // Use nativeSetStringUnique(null) for String column! public static native void nativeSetNullUnique(long nativeTablePtr, long columnIndex, long rowIndex); - public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data); - public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value); + public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data, boolean isDefault); + public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); private native long nativeSetPrimaryKey(long privateKeyTableNativePtr, long nativePtr, String columnName); private native void nativeMigratePrimaryKeyTableIfNeeded(long groupNativePtr, long primaryKeyTableNativePtr); private native void nativeAddSearchIndex(long nativePtr, long columnIndex); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java b/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java index ff39eb50ef..58404ee5bf 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java @@ -151,7 +151,7 @@ public interface TableOrView { * @param rowIndex * @param value */ - void setLong(long columnIndex, long rowIndex, long value); + void setLong(long columnIndex, long rowIndex, long value, boolean isDefault); /** * Sets the boolean value of a cell identified by the columnIndex and the rowIndex of that cell. @@ -160,7 +160,7 @@ public interface TableOrView { * @param rowIndex * @param value */ - void setBoolean(long columnIndex, long rowIndex, boolean value); + void setBoolean(long columnIndex, long rowIndex, boolean value, boolean isDefault); /** * Sets the float value of a cell identified by the columnIndex and the rowIndex of that cell. @@ -169,7 +169,7 @@ public interface TableOrView { * @param rowIndex * @param value */ - void setFloat(long columnIndex, long rowIndex, float value); + void setFloat(long columnIndex, long rowIndex, float value, boolean isDefault); /** * Sets the double value of a cell identified by the columnIndex and the rowIndex of that cell. @@ -178,7 +178,7 @@ public interface TableOrView { * @param rowIndex * @param value */ - void setDouble(long columnIndex, long rowIndex, double value); + void setDouble(long columnIndex, long rowIndex, double value, boolean isDefault); /** * Sets the string value of a particular cell of the table/view identified by the columnIndex and the rowIndex of @@ -188,7 +188,7 @@ public interface TableOrView { * @param rowIndex * @param value */ - void setString(long columnIndex, long rowIndex, String value); + void setString(long columnIndex, long rowIndex, String value, boolean isDefault); /** * Sets the binary value for a particular cell identified by the rowIndex and columnIndex of the cell. @@ -199,9 +199,9 @@ public interface TableOrView { */ //void setBinaryByteBuffer(long columnIndex, long rowIndex, ByteBuffer data); - void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data); + void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data, boolean isDefault); - void setDate(long columnIndex, long rowIndex, Date date); + void setDate(long columnIndex, long rowIndex, Date date, boolean isDefault); boolean isNullLink(long columnIndex, long rowIndex); @@ -214,7 +214,11 @@ public interface TableOrView { * @param rowIndex * @param value */ - void setLink(long columnIndex, long rowIndex, long value); + void setLink(long columnIndex, long rowIndex, long value, boolean isDefault); + + void setNull(long columnIndex, long rowIndex, boolean isDefault); + + boolean isNull(long columnIndex, long rowIndex); long sumLong(long columnIndex); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableView.java b/realm/realm-library/src/main/java/io/realm/internal/TableView.java index fa3ce44194..746e206b8d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableView.java @@ -253,6 +253,11 @@ public long getLink(long columnIndex, long rowIndex){ return nativeGetLink(nativePtr, columnIndex, rowIndex); } + @Override + public boolean isNull(long columnIndex, long rowIndex) { + return nativeIsNull(nativePtr, columnIndex, rowIndex); + } + // Methods for setting values. /** @@ -263,7 +268,7 @@ public long getLink(long columnIndex, long rowIndex){ * @param value the value. */ @Override - public void setLong(long columnIndex, long rowIndex, long value){ + public void setLong(long columnIndex, long rowIndex, long value, boolean isDefault){ if (parent.isImmutable()) throwImmutable(); nativeSetLong(nativePtr, columnIndex, rowIndex, value); } @@ -276,7 +281,7 @@ public void setLong(long columnIndex, long rowIndex, long value){ * @param value the value. */ @Override - public void setBoolean(long columnIndex, long rowIndex, boolean value){ + public void setBoolean(long columnIndex, long rowIndex, boolean value, boolean isDefault){ if (parent.isImmutable()) throwImmutable(); nativeSetBoolean(nativePtr, columnIndex, rowIndex, value); } @@ -289,7 +294,7 @@ public void setBoolean(long columnIndex, long rowIndex, boolean value){ * @param value the value. */ @Override - public void setFloat(long columnIndex, long rowIndex, float value){ + public void setFloat(long columnIndex, long rowIndex, float value, boolean isDefault){ if (parent.isImmutable()) throwImmutable(); nativeSetFloat(nativePtr, columnIndex, rowIndex, value); } @@ -302,7 +307,7 @@ public void setFloat(long columnIndex, long rowIndex, float value){ * @param value the value. */ @Override - public void setDouble(long columnIndex, long rowIndex, double value){ + public void setDouble(long columnIndex, long rowIndex, double value, boolean isDefault){ if (parent.isImmutable()) throwImmutable(); nativeSetDouble(nativePtr, columnIndex, rowIndex, value); } @@ -315,7 +320,7 @@ public void setDouble(long columnIndex, long rowIndex, double value){ * @param value the value. */ @Override - public void setDate(long columnIndex, long rowIndex, Date value){ + public void setDate(long columnIndex, long rowIndex, Date value, boolean isDefault){ if (parent.isImmutable()) throwImmutable(); nativeSetTimestampValue(nativePtr, columnIndex, rowIndex, value.getTime()); } @@ -328,7 +333,7 @@ public void setDate(long columnIndex, long rowIndex, Date value){ * @param value the value. */ @Override - public void setString(long columnIndex, long rowIndex, String value){ + public void setString(long columnIndex, long rowIndex, String value, boolean isDefault){ if (parent.isImmutable()) throwImmutable(); nativeSetString(nativePtr, columnIndex, rowIndex, value); } @@ -351,20 +356,29 @@ public void setBinaryByteBuffer(long columnIndex, long rowIndex, ByteBuffer data */ @Override - public void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data){ + public void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data, boolean isDefault){ if (parent.isImmutable()) throwImmutable(); nativeSetByteArray(nativePtr, columnIndex, rowIndex, data); } - public void setLink(long columnIndex, long rowIndex, long value){ + @Override + public void setLink(long columnIndex, long rowIndex, long value, boolean isDefault){ if (parent.isImmutable()) throwImmutable(); nativeSetLink(nativePtr, columnIndex, rowIndex, value); } + @Override + public void setNull(long columnIndex, long rowIndex, boolean isDefault) { + if (parent.isImmutable()) throwImmutable(); + getTable().setNull(columnIndex, getSourceRowIndex(rowIndex), isDefault); + } + + @Override public boolean isNullLink(long columnIndex, long rowIndex) { return nativeIsNullLink(nativePtr, columnIndex, rowIndex); } + @Override public void nullifyLink(long columnIndex, long rowIndex) { nativeNullifyLink(nativePtr, columnIndex, rowIndex); } @@ -788,6 +802,7 @@ public long syncIfNeeded() { private native String nativeGetString(long nativeViewPtr, long columnIndex, long rowIndex); private native byte[] nativeGetByteArray(long nativePtr, long columnIndex, long rowIndex); private native long nativeGetLink(long nativeViewPtr, long columnIndex, long rowIndex); + private native boolean nativeIsNull(long nativePtr, long columnIndex, long rowIndex); private native void nativeSetLong(long nativeViewPtr, long columnIndex, long rowIndex, long value); private native void nativeSetBoolean(long nativeViewPtr, long columnIndex, long rowIndex, boolean value); private native void nativeSetFloat(long nativeViewPtr, long columnIndex, long rowIndex, float value); From 84ae10590f41a6c9928eee7db2114a4103642406 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 21 Sep 2016 16:19:31 +0200 Subject: [PATCH 0078/2110] Updating documentation (#141) --- .../src/main/java/io/realm/Credentials.java | 23 ++--- .../src/main/java/io/realm/Session.java | 6 +- .../main/java/io/realm/SyncConfiguration.java | 86 +++++++++++-------- .../src/main/java/io/realm/SyncManager.java | 10 ++- .../src/main/java/io/realm/User.java | 43 ++++++---- .../internal/network/AuthServerResponse.java | 19 +++- .../internal/network/AuthenticateRequest.java | 2 +- .../network/AuthenticateResponse.java | 14 ++- .../network/AuthenticationServer.java | 4 +- .../realm/internal/network/LogoutRequest.java | 2 +- .../internal/network/LogoutResponse.java | 24 +++++- .../network/NetworkStateReceiver.java | 9 +- .../objectserver/AuthenticatingState.java | 18 ++-- .../internal/objectserver/BindingState.java | 4 +- .../internal/objectserver/BoundState.java | 2 +- .../internal/objectserver/FsmAction.java | 5 +- .../realm/internal/objectserver/FsmState.java | 2 - .../internal/objectserver/SyncSession.java | 25 +++--- 18 files changed, 187 insertions(+), 111 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/Credentials.java b/realm/realm-library/src/main/java/io/realm/Credentials.java index 975f6286c8..8cf749593a 100644 --- a/realm/realm-library/src/main/java/io/realm/Credentials.java +++ b/realm/realm-library/src/main/java/io/realm/Credentials.java @@ -24,20 +24,20 @@ * Credentials represent a login with a 3rd party login provider in an OAuth2 login flow, and are used by the Realm * Object Server to verify the user and grant access. *

      - * Logging into the Object Server consists of the following steps: + * Logging into the Realm Object Server consists of the following steps: *

        *
      1. - * Login to 3rd party like Facebook, Google or Twitter. The result is usually an Authorization Grant that must be - * saved in a {@link Credentials} object of the proper type, e.g {@link Credentials#facebook(String)} for a + * Log in to 3rd party provider (Facebook, Google or Twitter). The result is usually an Authorization Grant that must be + * saved in a {@link Credentials} object of the proper type e.g., {@link Credentials#facebook(String)} for a * Facebook login. *
      2. *
      3. - * Authenticate a {@link User} through the Object Server using these credentials. Once authenticated - * an Object Server user is returned. This user can then be attached to a {@link SyncConfiguration}, which + * Authenticate a {@link User} through the Object Server using these credentials. Once authenticated, + * an Object Server user is returned. Then this user can be attached to a {@link SyncConfiguration}, which * will make it possible to synchronize data between the local and remote Realm. *

        - * It is possible to persist the user object using e.g. the {@link UserStore} so logging - * into e.g Facebook is only required the first time the app is used. + * It is possible to persist the user object e.g., using the {@link UserStore}. That means, logging + * into an OAuth2 provider is only required the first time the app is used. *

      4. *
      * @@ -72,13 +72,14 @@ public class Credentials { * Creates credentials based on a login with username and password. These credentials will only be verified * by the Object Server. * - * @param username username of the user - * @param password the users password + * @param username username of the user. + * @param password the users password. * @param createUser {@code true} if the user should be created, {@code false} otherwise. It is not possible to * create a user twice when logging in, so this flag should only be set to {@code true} the first * time a users log in. * @return a set of credentials that can be used to log into the Object Server using * {@link User#loginAsync(Credentials, String, User.Callback)}. + * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static Credentials usernamePassword(String username, String password, boolean createUser) { if (username == null || username.equals("")) { @@ -95,7 +96,8 @@ public static Credentials usernamePassword(String username, String password, boo * * @param facebookToken a facebook userIdentifier acquired by logging into Facebook. * @return a set of credentials that can be used to log into the Object Server using - * {@link User#loginAsync(Credentials, String, User.Callback)}. + * {@link User#loginAsync(Credentials, String, User.Callback)} + * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static Credentials facebook(String facebookToken) { if (facebookToken == null || facebookToken.equals("")) { @@ -115,6 +117,7 @@ public static Credentials facebook(String facebookToken) { * classes will be converted using {@code toString()}. * @return a set of credentials that can be used to log into the Object Server using * {@link User#loginAsync(Credentials, String, User.Callback)}. + * @throws IllegalArgumentException if any parameter is either {@code null} or empty. */ public static Credentials custom(String identityProvider, String userIdentifier, Map userInfo) { if (identityProvider == null || identityProvider.equals("")) { diff --git a/realm/realm-library/src/main/java/io/realm/Session.java b/realm/realm-library/src/main/java/io/realm/Session.java index 94de8ea5a5..66253ce7a3 100644 --- a/realm/realm-library/src/main/java/io/realm/Session.java +++ b/realm/realm-library/src/main/java/io/realm/Session.java @@ -26,7 +26,7 @@ * This class represents the connection to the Realm Object Server for one {@link SyncConfiguration}. *

      * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening - * a Realm instance using that configuration. Once a session has been created it will continue to exist until the app + * a Realm instance using that configuration. Once a session has been created, it will continue to exist until the app * is closed or the {@link SyncConfiguration} is no longer used. *

      * A session is fully controlled by Realm, but can provide additional information in case of errors. @@ -46,7 +46,7 @@ public final class Session { } /** - * Returns the {@link SyncConfiguration} that is responsible for controlling this session. + * Returns the {@link SyncConfiguration} that is responsible for controlling the session. * * @return SyncConfiguration that defines and controls this session. */ @@ -76,7 +76,7 @@ public URI getServerUrl() { /** * Returns the state of this session. * - * @return The current {@link SessionState} for this session. + * @return the current {@link SessionState} for this session. */ public SessionState getState() { return syncSession.getState(); diff --git a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java index 7d964edea9..8e4510737a 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java @@ -45,7 +45,7 @@ * {@link User#loginAsync(Credentials, String, User.Callback)} for more information on * how to get a user object. *

      - * A minimal {@link SyncConfiguration} can look like this: + * A minimal {@link SyncConfiguration} can be found below. *

        * {@code
        * SyncConfiguration config = new SyncConfiguration.Builder(context)
      @@ -55,8 +55,8 @@
        * }
        * 
      * - * Synchronized Realms only support additive migrations which can be detected automatically, so the following - * builder options are not accessible compared to a normal Realm: + * Synchronized Realms only support additive migrations which can be detected and performed automatically, so + * the following builder options are not accessible compared to a normal Realm: * *
        *
      • {@code deleteRealmIfMigrationNeeded()}
      • @@ -65,7 +65,7 @@ *
      * * Synchronized Realms are created by using {@link Realm#getInstance(RealmConfiguration)} and - * {@link Realm#getDefaultInstance()} like normal unsynchronized Realms. + * {@link Realm#getDefaultInstance()} like ordinary unsynchronized Realms. */ public final class SyncConfiguration extends RealmConfiguration { @@ -184,12 +184,17 @@ SyncPolicy getSyncPolicy() { return syncPolicy; } + /** + * Returns the user. + * + * @return the user. + */ public User getUser() { return user; } /** - * Returns the fully disambiguated URI for the remote Realm, i.e. any {@code /~/} placeholder has been replaced + * Returns the fully disambiguated URI for the remote Realm i.e., the {@code /~/} placeholder has been replaced * by the proper user ID. * * @return {@link URI} identifying the remote Realm this local Realm is synchronized with. @@ -245,31 +250,31 @@ public static final class Builder { /** * Creates an instance of the Builder for the SyncConfiguration. *

      - * Opening a synchronized Realm requires a valid user and an unique URL that identifies that Realm. In URL's, + * Opening a synchronized Realm requires a valid user and an unique URI that identifies that Realm. In URIs, * {@code /~/} can be used as a placeholder for a user ID in case the Realm should only be available to one - * user, e.g. {@code "realm://objectserver.realm.io/~/default"} + * user e.g., {@code "realm://objectserver.realm.io/~/default"}. *

      - * The URL cannot end with {@code .realm}. + * The URL cannot end with {@code .realm}, {@code .realm.lock} or {@code .realm.management}. *

      - * The `/~/` will automatically be replaced with the user ID when creating the {@link SyncConfiguration}. + * The {@code /~/} will automatically be replaced with the user ID when creating the {@link SyncConfiguration}. *

      - * The URL also defines the local location on disk. The default location of a synchronized Realm file is - * {@code /data/data//files/realm-object-server//}, but this behaviour + * Moreover, the URI defines the local location on disk. The default location of a synchronized Realm file is + * {@code /data/data//files/realm-object-server//}, but this behavior * can be overwritten using {@link #name(String)} and {@link #directory(File)}. *

      * Many Android devices are using FAT32 file systems. FAT32 file systems have a limitation that - * file name cannot be longer than 255 characters. Moreover, the entire URL should not exceed 256 characters. + * file names cannot be longer than 255 characters. Moreover, the entire URI should not exceed 256 characters. * If file name and underlying path are too long to handle for FAT32, a shorter unique name will be generated. * See also @{link https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx}. * - * @param user Set the user for this Realm. An authenticated {@link User} is required to open any Realm managed + * @param user the user for this Realm. An authenticated {@link User} is required to open any Realm managed * by a Realm Object Server. - * @param url URL identifying the Realm. + * @param uri URI identifying the Realm. * * @see User#isValid() */ - public Builder(User user, String url) { - this(BaseRealm.applicationContext, user, url); + public Builder(User user, String uri) { + this(BaseRealm.applicationContext, user, uri); } Builder(Context context, User user, String url) { @@ -295,15 +300,15 @@ private void validateAndSet(User user) { this.user = user; } - private void validateAndSet(String url) { - if (url == null) { - throw new IllegalArgumentException("Non-null 'url' required."); + private void validateAndSet(String uri) { + if (uri == null) { + throw new IllegalArgumentException("Non-null 'uri' required."); } try { - serverUrl = new URI(url); + serverUrl = new URI(uri); } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid url: " + url, e); + throw new IllegalArgumentException("Invalid URI: " + uri, e); } // scheme must be realm or realms @@ -324,7 +329,7 @@ private void validateAndSet(String url) { // Detect last path segment as it is the default file name String path = serverUrl.getPath(); if (path == null) { - throw new IllegalArgumentException("Invalid url: " + url); + throw new IllegalArgumentException("Invalid URI: " + uri); } String[] pathSegments = path.split("/"); @@ -334,11 +339,11 @@ private void validateAndSet(String url) { continue; } if (segment.equals("..") || segment.equals(".")) { - throw new IllegalArgumentException("The URL has an invalid segment: " + segment); + throw new IllegalArgumentException("The URI has an invalid segment: " + segment); } Matcher m = pattern.matcher(segment); if (!m.matches()) { - throw new IllegalArgumentException("The URL must only contain characters 0-9, a-z, A-Z, ., _, and -: " + segment); + throw new IllegalArgumentException("The URI must only contain characters 0-9, a-z, A-Z, ., _, and -: " + segment); } } @@ -349,22 +354,23 @@ private void validateAndSet(String url) { if (defaultLocalFileName.endsWith(".realm") || defaultLocalFileName.endsWith(".realm.lock") || defaultLocalFileName.endsWith(".realm.management")) { - throw new IllegalArgumentException("The URL must not end with '.realm', '.realm.lock' or '.realm.management: " + url); + throw new IllegalArgumentException("The URI must not end with '.realm', '.realm.lock' or '.realm.management: " + uri); } try { this.serverUrl = new URI(scheme, serverUrl.getUserInfo(), serverUrl.getHost(), port, serverUrl.getPath(), serverUrl.getQuery(), serverUrl.getFragment()); } catch (URISyntaxException e) { - throw new IllegalArgumentException("Cannot reconstruct url: " + url, e); + throw new IllegalArgumentException("Cannot reconstruct URI: " + uri, e); } } /** - * Sets the local filename for the Realm. - * This will override the default name defined by the the Realm URL. + * Sets the local file name for the Realm. + * This will override the default name defined by the Realm URL. * * @param filename name of the local file on disk. + * @throws IllegalArgumentException if file name is {@code null} or empty. */ public Builder name(String filename) { if (filename == null || filename.isEmpty()) { @@ -377,11 +383,12 @@ public Builder name(String filename) { /** * Sets the local root directory where synchronized Realm files can be saved. - * + *

      * Synchronized Realms will not be saved directly in the provided directory, but instead in a - * subfolder that matches the path defined by Realm URL. As Realm server URLs are unique - * this means that multiple users can save their Realms on disk without the risk of them overriding each other. - * + * subfolder that matches the path defined by Realm URI. As Realm server URIs are unique + * this means that multiple users can save their Realms on disk without the risk of them overwriting + * each other files. + *

      * The default location is {@code context.getFilesDir()}. * * @param directory directory on disk where the Realm file can be saved. @@ -409,8 +416,10 @@ public Builder directory(File directory) { } /** - * Sets the 64 bit key used to encrypt and decrypt the Realm file. * Sets the {@value io.realm.RealmConfiguration#KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file. + * + * @param key the encryption key. + * @throws IllegalArgumentException if key is invalid. */ public Builder encryptionKey(byte[] key) { if (key == null) { @@ -462,8 +471,8 @@ public Builder rxFactory(RxObservableFactory factory) { } /** - * Sets the initial data in {@link io.realm.Realm}. This transaction will be executed only for the first time - * when database file is created or while migrating the data when + * Sets the initial data in {@link io.realm.Realm}. This transaction will be executed only the first time + * the Realm file is opened (created) or while migrating the data if * {@link RealmConfiguration.Builder#deleteRealmIfMigrationNeeded()} is set. * * @param transaction transaction to execute. @@ -503,7 +512,7 @@ Builder syncPolicy(SyncPolicy syncPolicy) { /** * Sets the error handler used by this configuration. This will override any handler set by calling * {@link SyncManager#setDefaultSessionErrorHandler(Session.ErrorHandler)}. - * + *

      * Only errors not handled by the defined {@code SyncPolicy} will be reported to this error handler. * * @param errorHandler error handler used to report back errors when communicating with the Realm Object Server. @@ -536,8 +545,8 @@ private String MD5(String in) { /** * Setting this will cause the local Realm file used to synchronize changes to be deleted if the {@link User} * owning this Realm logs out from the device using {@link User#logout()}. - * - * The default behaviour is that the Realm file is allowed to stay behind, making it possible for users to log + *

      + * The default behavior is that the Realm file is allowed to stay behind, making it possible for users to log * in again and have access to their data faster. */ public Builder deleteRealmOnLogout() { @@ -549,6 +558,7 @@ public Builder deleteRealmOnLogout() { * Creates the RealmConfiguration based on the builder parameters. * * @return the created {@link SyncConfiguration}. + * @throws IllegalStateException if the configuration parameters are invalid or inconsistent. */ public SyncConfiguration build() { if (serverUrl == null || user == null) { diff --git a/realm/realm-library/src/main/java/io/realm/SyncManager.java b/realm/realm-library/src/main/java/io/realm/SyncManager.java index 5172e1c1f2..c6a7fbeeb5 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/main/java/io/realm/SyncManager.java @@ -32,8 +32,12 @@ * The SyncManager is the central controller for interacting with the Realm Object Server. * It handles the creation of {@link Session}s and it is possible to configure session defaults and the underlying * network client using this class. + *

      + * Through the SyncManager, it is possible to add authentication listeners. An authentication listener will + * response to events like user logging in or out. + *

      + * Default error handling for any {@link SyncConfiguration} can be added using the SyncManager. * - * // TODO Rewrite this section. */ @Keep public final class SyncManager { @@ -101,6 +105,7 @@ public void run() { * If no Userstore is specified {@link User#currentUser()} will always return {@code null}. * * @param userStore {@link UserStore} to use. + * @throws IllegalArgumentException if {@code userStore} is {@code null}. */ public static void setUserStore(UserStore userStore) { if (userStore == null) { @@ -154,6 +159,7 @@ public static void setDefaultSessionErrorHandler(Session.ErrorHandler errorHandl * * @param syncConfiguration configuration object for the synchronized Realm. * @return the {@link Session} for the specified Realm. + * @throws IllegalArgumentException if syncConfiguration is {@code null}. */ public static synchronized Session getSession(SyncConfiguration syncConfiguration) { if (syncConfiguration == null) { @@ -219,7 +225,7 @@ static void notifyUserLoggedOut(User user) { } /** - * Sets the log level for the underlying + * Sets the log level for the underlying. * @param logLevel */ public static void setLogLevel(int logLevel) { diff --git a/realm/realm-library/src/main/java/io/realm/User.java b/realm/realm-library/src/main/java/io/realm/User.java index 87bef8c0c5..8414517787 100644 --- a/realm/realm-library/src/main/java/io/realm/User.java +++ b/realm/realm-library/src/main/java/io/realm/User.java @@ -44,8 +44,15 @@ import io.realm.internal.network.LogoutResponse; /** - * This class represents a user on the Realm Object Server. - * TODO Rewrite this section + * This class represents a user on the Realm Object Server. The credentials are provided by various 3rd party + * providers (Facebook, Google, etc.). + *

      + * A user can log in to the Realm Object Server, and if access is granted, it is possible to synchronize the local + * and the remote Realm. Moreover, synchronization is halted when the user is logged out. + *

      + * It is possible to persist a user. By retrieving a user, there is no need to log in to the 3rd party provider again. + * Persisting a user between sessions, the user's credentials are stored locally on the device, and should be treated + * as sensitive data. */ public class User { @@ -56,10 +63,11 @@ private User(SyncUser user) { } /** - * Returns the last user that has logged in that are still valid. - * A user is invalidated when it logs out or its access tokens expire. + * Returns the last user that has logged in and who is still valid. + * A user is invalidated when he/she logs out or the user's access token expire. * - * @return last {@link User} that have logged in that is still valid. + * @return last {@link User} that has logged in and who is still valid. {@code null} if no current user or user has + * been invalidated. */ public static User currentUser() { User user = SyncManager.getUserStore().get(UserStore.CURRENT_USER_KEY); @@ -71,7 +79,7 @@ public static User currentUser() { } /** - * Load a user that has previously been serialized using {@link #toJson()}. + * Loads a user that has previously been serialized using {@link #toJson()}. * * @param user JSON string representing the user. * @@ -102,12 +110,13 @@ public static User fromJson(String user) { } /** - * Login the user on the Realm Object Server. This is done synchronously, so calling this method on the Android + * Logs in the user to the Realm Object Server. This is done synchronously, so calling this method on the Android * UI thread will always crash. A logged in user is required to be able to create a {@link SyncConfiguration}. * * @param credentials credentials to use. - * @param authenticationUrl Server that can authenticate against. + * @param authenticationUrl server that can authenticate against. * @throws ObjectServerError if the login failed. + * @throws IllegalArgumentException if the URL is malformed. */ public static User login(final Credentials credentials, final String authenticationUrl) throws ObjectServerError { final URL authUrl; @@ -139,12 +148,14 @@ public static User login(final Credentials credentials, final String authenticat } /** - * Login the user on the Realm Object Server. A logged in user is required to be able to create a + * Logs in the user to the Realm Object Server. A logged in user is required to be able to create a * {@link SyncConfiguration}. * * @param credentials credentials to use. - * @param authenticationUrl Server that can authenticate against. - * @param callback callback when login has completed or failed. This callback will always happen on the UI thread. + * @param authenticationUrl server that the user is authenticated against. + * @param callback callback when login has completed or failed. The callback will always happen on the same thread + * as this this method is called on. + * @throws IllegalArgumentException if not on a Looper thread. */ public static RealmAsyncTask loginAsync(final Credentials credentials, final String authenticationUrl, final Callback callback) { if (Looper.myLooper() == null) { @@ -190,7 +201,7 @@ public void run() { } /** - * Log the user out of the Object Server. Once the Object Server has confirmed the logout any registered + * Logs out the user from the Realm Object Server. Once the Object Server has confirmed the logout any registered * {@link AuthenticationListener} will be notified and user credentials will be deleted from this device. *

      * Any Realms owned by the user will be deleted if {@link SyncConfiguration.Builder#deleteRealmOnLogout()} is @@ -265,7 +276,7 @@ protected void onError(LogoutResponse response) { /** * Returns a JSON token representing this user. - * + *

      * Possession of this JSON token can potentially grant access to data stored on the Realm Object Server, so it * should be treated as sensitive data. * @@ -280,7 +291,7 @@ public String toJson() { /** * Returns {@code true} if the user is logged into the Realm Object Server. If this method returns {@code true} it - * means that the user has valid credentials that have not expired. + * implies that the user has valid credentials that have not expired. *

      * The user might still be have been logged out by the Realm Object Server which will not be detected before the * user tries to actively synchronize a Realm. If a logged out user tries to synchronize a Realm, an error will be @@ -298,7 +309,7 @@ public boolean isValid() { * Returns the identity of this user on the Realm Object Server. The identity is a guaranteed to be unique * among all users on the Realm Object Server. * - * @return Identity of the user on the Realm Object Server. If the user has logged out or the login has expired + * @return identity of the user on the Realm Object Server. If the user has logged out or the login has expired * {@code null} is returned. */ public String getIdentity() { @@ -309,7 +320,7 @@ public String getIdentity() { * Returns this user's access token. This is the users credential for accessing the Realm Object Server and should * be treated as sensitive data. * - * @return The user's access token. If this user has logged out or the login has expired {@code null} is returned. + * @return the user's access token. If this user has logged out or the login has expired {@code null} is returned. */ public String getAccessToken() { Token userToken = syncUser.getUserToken(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthServerResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthServerResponse.java index 158f27f659..cad9ce933a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/AuthServerResponse.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthServerResponse.java @@ -30,13 +30,17 @@ public class AuthServerResponse { /** * Checks if this response was valid. + * + * @return {@code true} if valid, {@code false} otherwise. */ public boolean isValid() { return (error == null); } /** - * If {@link #isValid()} returns {@code false}, this method must return the error causing this. + * If {@link #isValid()} returns {@code false}, this method will return the error causing this. + * + * @return the error. */ public ObjectServerError getError() { return error; @@ -46,9 +50,16 @@ protected void setError(ObjectServerError error) { this.error = error; } - // Parse an http error form the Auth server. - // The server returns errors following https://tools.ietf.org/html/rfc7807 with an extra "code" field - // for Realm specific error codes. + + + /** + * Parse an HTTP error from a Realm Authentication Server. The server returns errors following + * https://tools.ietf.org/html/rfc7807 with an extra "code" field for Realm specific error codes. + * + * @param response the server response. + * @param httpErrorCode the HTTP error code. + * @return an server error. + */ public static ObjectServerError createError(String response, int httpErrorCode) { try { JSONObject obj = new JSONObject(response); diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java index 34e97bfa94..590599de2a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java @@ -56,7 +56,7 @@ public static AuthenticateRequest fromCredentials(Credentials credentials) { /** * Authenticate access to a given Realm using an already logged in user. * - * @param refreshToken Users refresh token + * @param refreshToken user's refresh token. */ public static AuthenticateRequest fromRefreshToken(Token refreshToken) { // Authenticate a given Realm path using an already logged in user. diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java index a7c4c0f2f7..18619e3635 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java @@ -28,7 +28,7 @@ import okhttp3.Response; /** - * This class represents the response for a authenticate request. + * This class represents the response for an authenticate request. */ public class AuthenticateResponse extends AuthServerResponse { @@ -41,6 +41,9 @@ public class AuthenticateResponse extends AuthServerResponse { /** * Helper method for creating the proper Authenticate response. This method will set the appropriate error * depending on any HTTP response codes or IO errors. + * + * @param response the HTTP response. + * @return an authenticate response. */ public static AuthenticateResponse from(Response response) { String serverResponse; @@ -73,7 +76,10 @@ public static AuthenticateResponse from(ObjectServerError error) { } /** - * Creates a unsuccessful authentication response. This should only happen in case of network / IO problems. + * Creates an unsuccessful authentication response. This should only happen in case of network or I/O related + * issues. + * + * @param error the network or I/O error. */ private AuthenticateResponse(ObjectServerError error) { setError(error); @@ -82,8 +88,10 @@ private AuthenticateResponse(ObjectServerError error) { } /** - * Parses a valid (200) server response. It might still result in a unsuccessful authentication attempt, if the + * Parses a valid (200) server response. It might still result in an unsuccessful authentication attempt, if the * JSON response could not be parsed correctly. + * + * @param serverResponse the server response. */ private AuthenticateResponse(String serverResponse) { ObjectServerError error; diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java index 8a71a37aaa..07e21dbe59 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java @@ -25,8 +25,8 @@ /** * Interface for handling communication with Realm Object Servers. - * - * Note, any implementation of this class is not responsible for handling retries or error handling, it is + *

      + * Note, no implementation of this class is responsible for handling retries or error handling. It is * only responsible for executing a given network request. */ public interface AuthenticationServer { diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/LogoutRequest.java b/realm/realm-library/src/main/java/io/realm/internal/network/LogoutRequest.java index 3514369931..62cb9b095a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/LogoutRequest.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/LogoutRequest.java @@ -19,7 +19,7 @@ import io.realm.User; /** - * This class encapsulates a request to logout a user on the Realm Authentication Server. It is responsible for + * This class encapsulates a request to log out a user on the Realm Authentication Server. It is responsible for * constructing the JSON understood by the Realm Authentication Server. */ public class LogoutRequest { diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/LogoutResponse.java b/realm/realm-library/src/main/java/io/realm/internal/network/LogoutResponse.java index 92557af144..1ee07bb51b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/LogoutResponse.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/LogoutResponse.java @@ -24,7 +24,7 @@ import okhttp3.Response; /** - * This class represents the response for a logout request. + * This class represents the response for a log out request. */ public class LogoutResponse extends AuthServerResponse { @@ -32,7 +32,10 @@ public class LogoutResponse extends AuthServerResponse { /** * Helper method for creating the proper Authenticate response. This method will set the appropriate error - * depending on any HTTP response codes or IO errors. + * depending on any HTTP response codes or I/O errors. + * + * @param response the server response. + * @return the log out response. */ static LogoutResponse createFrom(Response response) { String serverResponse; @@ -51,7 +54,10 @@ static LogoutResponse createFrom(Response response) { } /** - * Creates a unsuccessful authentication response. This should only happen in case of network / IO problems. + * Creates an unsuccessful authentication response. This should only happen in case of network or I/O + * related issues. + * + * @param error an authentication response error. */ private LogoutResponse(ObjectServerError error) { this.error = error; @@ -59,17 +65,29 @@ private LogoutResponse(ObjectServerError error) { /** * Parses a valid (200) server response. + * + * @param serverResponse the server response. */ private LogoutResponse(String serverResponse) { this.error = null; // TODO endpoint not finalized } + /** + * Checks if response was valid. + * + * @return {@code true} if valid. + */ public boolean isValid() { // return (error == null); return true; } + /** + * Returns the error. + * + * @return the error. + */ public ObjectServerError getError() { return error; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/NetworkStateReceiver.java b/realm/realm-library/src/main/java/io/realm/internal/network/NetworkStateReceiver.java index 0e4e9a31d2..5fa68176a9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/network/NetworkStateReceiver.java +++ b/realm/realm-library/src/main/java/io/realm/internal/network/NetworkStateReceiver.java @@ -40,6 +40,8 @@ public class NetworkStateReceiver extends BroadcastReceiver { * This method is thread safe. *

      * IMPORTANT: Not removing it again will result in major leaks. + * + * @param listener the listener. */ public static void addListener(ConnectionListener listener) { listeners.add(listener); @@ -48,6 +50,8 @@ public static void addListener(ConnectionListener listener) { /** * Removes a network listener. * This method is thread safe. + * + * @param listener the listener. */ public static synchronized void removeListener(ConnectionListener listener) { listeners.remove(listener); @@ -57,7 +61,10 @@ public static synchronized void removeListener(ConnectionListener listener) { * Attempt to detect if a device is online and can transmit or receive data. * This method is thread safe. *

      - * The Emulator is always considered online, as `getActiveNetworkInfo()` does not report the correct value. + * An emulator is always considered online, as `getActiveNetworkInfo()` does not report the correct value. + * + * @param context an Android context. + * @return {@code true} if device is online, otherwise {@code false}. */ public static boolean isOnline(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java index 402d36cc6e..d05c9a3ef4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java @@ -23,24 +23,24 @@ import io.realm.internal.network.NetworkStateReceiver; /** - * AUTHENTICATING State. This step is needed if the user does not have proper access or credentials to access this - * Realm when attempting to bind it. This can happen in 3 ways: + * AUTHENTICATING State. This step is needed if the user does not have proper access or credentials to access the + * Realm when attempting to bind it. Reasons for not having proper access or invalid credentials include: * *

        *
      1. * Refresh token has expired: - * This effectively means the user has been logged out from the Realm Object Server and credentials has - * to be re-verified on the Authentication Server. Since this involves creating a new User object object, - * this session will be stopped and and error reported. + * This effectively means the user has been logged out from the Realm Object Server and credentials have + * to be re-verified by the Authentication Server. Since verification involves creating a new User object, + * this session will be stopped and an error reported. *
      2. *
      3. * Access token has expired: - * This state will automatically refresh it and retry binding the Realm. + * In this case, the token is automatically refreshed and will retry binding the Realm. *
      4. *
      5. - * Access token does not exists: - * This state means the user has logged in, but not yet gained a specific access token for this Realm. - * This state will automatically fetch the access token and retry binding the Realm. + * Access token does not exist: + * This state means the user has logged in, but not yet gained a specific access token for the Realm. + * The access token will automatically be fetched and binding the Realm is retried. *
      6. *
      */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/BindingState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/BindingState.java index 83b6c9c168..fbd3a14f5b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/BindingState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/BindingState.java @@ -20,8 +20,8 @@ import io.realm.SessionState; /** - * BINDING State. After bind() is called, this state will attempt to bind the local Realm to the remote. This is an - * asynchronous operation that must be able to be interrupted. + * BINDING State. After {@code bind()} is called, the state will attempt to bind the local Realm to the remote. This is an + * asynchronous operation which must be interruptible. */ class BindingState extends FsmState { diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/BoundState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/BoundState.java index 6ee9a85b81..a941440867 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/BoundState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/BoundState.java @@ -21,7 +21,7 @@ import io.realm.SessionState; /** - * BOUND State. At this state the local Realm is bound to the remote Realm and changes is sent in both + * BOUND State. In this state the local Realm is bound to the remote Realm and changes are sent in both * directions immediately. */ class BoundState extends FsmState { diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmAction.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmAction.java index dafc865d72..bf6c001d12 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmAction.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmAction.java @@ -22,10 +22,9 @@ /** * As {@link Session} is modeled as a state machine, this interface describe all * possible actions in that machine. + *

      + * All states should implement this interface so all possible permutations of state/actions are covered. * - * All states should implement this so all possible permutations of state/actions are covered. - * - * TODO Move this to the Object Store */ interface FsmAction { void onStart(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmState.java index bbde585bfd..726ede49d0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmState.java @@ -23,8 +23,6 @@ /** * Abstract class containing shared logic for all {@link Session} states. All states must extend * this class as it contains the logic for entering and leaving states. - * - * TODO Move this to the Object Store */ abstract class FsmState implements FsmAction { diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java index 196ca7fd8e..212c60fe89 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java @@ -40,13 +40,13 @@ /** * Internal class describing a Realm Object Server Session. * There is currently a split between the public {@link Session} and this class. - * This class is intended as a wrapper around Object Stores Sync Session, but it is not that yet. + * This class is intended as a wrapper for Object Store's Sync Session, but it is not that yet. *

      * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening - * a Realm instance. Once a session has been created it will continue to exist until explicitly closed or the + * a Realm instance. Once a session has been created, it will continue to exist until explicitly closed or the * underlying Realm file is deleted. *

      - * It is normally not necessary to interact directly with a session. That should be done by the {@code SyncPolicy} + * It is typically not necessary to interact directly with a session. The interaction should be done by the {@code SyncPolicy} * defined using {@code io.realm.SyncConfiguration.Builder#syncPolicy(SyncPolicy)}. *

      * A session has a lifecycle consisting of the following states: @@ -54,22 +54,22 @@ *

      *
    4. * INITIAL Initial state when creating the Session object. No connections to the object server have been - * made yet. At this point it is possible to register any relevant error and event listeners. Calling + * created yet. At this point it is possible to register any relevant error and event listeners. Calling * {@link #start()} will cause the session to become UNBOUND and notify the {@code SyncPolicy} that the * session is ready by calling {@code SyncPolicy#onSessionCreated(Session)}. *
    5. *
    6. - * UNBOUND When a session is unbound, no synchronization between the local and remote Realm is happening. + * UNBOUND When a session is unbound, no synchronization between the local and remote Realm is taking place. * Call {@link #bind()} to start synchronizing changes. *
    7. *
    8. * BINDING A session is in the process of binding a local Realm to a remote one. Calling {@link #unbind()} - * at this stage, will cancel the process. If binding fails, the session will revert to being unbound and the error + * at this stage, will cancel the process. If binding fails, the session will revert to being INBOUND and an error * will be reported to the error handler. *
    9. *
    10. * AUTHENTICATING During binding, if a users access has expired, the session will be AUTHENTICATING. - * During this state, Realm will automatically try to acquire new valid credentials. If this succeed BINDING + * During this state, Realm will automatically try to acquire new valid credentials. If it succeed BINDING * will automatically be resumed, if not, the session will become UNBOUND or STOPPED and an * appropriate error reported. *
    11. @@ -79,7 +79,7 @@ * *
    12. * STOPPED The session are in an unrecoverable state. Check the error log for additional information, but - * the type of errors are usually wrong credentials for the Realm being accessed or a mismatching Object Server. + * the type of errors is usually wrong credentials for the Realm being accessed or a mismatching Object Server. * Most problems can be solved by creating a new {@link SyncConfiguration} with a new {@code serverUrl} and * {@code user}. *
    13. @@ -154,7 +154,7 @@ void nextState(SessionState nextStateDescription) { } /** - * Starts the session. This will cause the session to come unbound. {@link #bind()} must be called to + * Starts the session. This will cause the session to come UNBOUND. {@link #bind()} must be called to * actually start synchronizing data. */ public synchronized void start() { @@ -173,7 +173,7 @@ public synchronized void stop() { * synchronized immediately. *

      * While this method will return immediately, binding a Realm is not guaranteed to succeed. Possible reasons for - * failure could be either if the device is offline or credentials have expired. Binding is an asynchronous + * failure could be if the device is offline or credentials have expired. Binding is an asynchronous * operation and all errors will be sent first to {@code SyncPolicy#onError(Session, ObjectServerError)} and if the * SyncPolicy doesn't handle it, to the {@link Session.ErrorHandler} defined by * {@link SyncConfiguration.Builder#errorHandler(Session.ErrorHandler)}. @@ -285,6 +285,9 @@ protected void onError(AuthenticateResponse response) { /** * Checks if a user has valid credentials for accessing this Realm. + * + * @param configuration the configuration. + * @return {@code true} if credentials are valid, {@code false} otherwise. */ boolean isAuthenticated(SyncConfiguration configuration) { return user.isAuthenticated(configuration); @@ -329,6 +332,8 @@ public SessionState getState() { /** * Notify session that a commit on the device has happened. + * + * @param version the commit number/version. */ public void notifyCommit(long version) { if (isBound()) { From 10838784955387a8f9760055b1b896deedcb6aed Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 21 Sep 2016 09:51:41 -0500 Subject: [PATCH 0079/2110] Make BaseRealm package protected again (#143) and move SyncObjectServerFacade to internal/objectserver. --- .../src/main/java/io/realm/BaseRealm.java | 4 ++-- .../io/realm/internal/ObjectServerFacade.java | 2 +- .../objectserver/AuthenticatingState.java | 3 +-- .../SyncObjectServerFacade.java | 18 +++++++++++++++--- 4 files changed, 19 insertions(+), 8 deletions(-) rename realm/realm-library/src/main/java/io/realm/internal/{ => objectserver}/SyncObjectServerFacade.java (85%) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index e4b8faeb5c..56cf037e8e 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -49,7 +49,7 @@ * @see io.realm.DynamicRealm */ @SuppressWarnings("WeakerAccess") -public abstract class BaseRealm implements Closeable { +abstract class BaseRealm implements Closeable { protected static final long UNVERSIONED = -1; private static final String INCORRECT_THREAD_CLOSE_MESSAGE = "Realm access from incorrect thread. Realm instance can only be closed on the thread it was created."; @@ -61,7 +61,7 @@ public abstract class BaseRealm implements Closeable { "Changing Realm data can only be done from inside a transaction."; // Thread pool for all async operations (Query & transaction) - public volatile static Context applicationContext; + volatile static Context applicationContext; // Thread pool for all async operations (Query & transaction) static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 5422735708..c750708828 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -17,7 +17,7 @@ public class ObjectServerFacade { static { //noinspection TryWithIdenticalCatches try { - Class syncFacadeClass = Class.forName("io.realm.internal.SyncObjectServerFacade"); + Class syncFacadeClass = Class.forName("io.realm.internal.objectserver.SyncObjectServerFacade"); syncFacade = (ObjectServerFacade) syncFacadeClass.newInstance(); } catch (ClassNotFoundException ignored) { } catch (InstantiationException e) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java index d05c9a3ef4..e742799177 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java @@ -16,7 +16,6 @@ package io.realm.internal.objectserver; -import io.realm.BaseRealm; import io.realm.ObjectServerError; import io.realm.Session; import io.realm.SessionState; @@ -48,7 +47,7 @@ class AuthenticatingState extends FsmState { @Override public void onEnterState() { - if (NetworkStateReceiver.isOnline(BaseRealm.applicationContext)) { + if (NetworkStateReceiver.isOnline(SyncObjectServerFacade.getApplicationContext())) { authenticate(session); } else { // Wait for connection to become available, before trying again. diff --git a/realm/realm-library/src/main/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java similarity index 85% rename from realm/realm-library/src/main/java/io/realm/internal/SyncObjectServerFacade.java rename to realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java index 7ea800c166..5b925e9fef 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java @@ -1,6 +1,7 @@ -package io.realm.internal; +package io.realm.internal.objectserver; +import android.annotation.SuppressLint; import android.content.Context; import java.lang.reflect.InvocationTargetException; @@ -11,8 +12,8 @@ import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.exceptions.RealmException; -import io.realm.internal.objectserver.SessionStore; -import io.realm.internal.objectserver.SyncSession; +import io.realm.internal.Keep; +import io.realm.internal.ObjectServerFacade; @SuppressWarnings("unused") // Used through reflection. See ObjectServerFacade @Keep @@ -20,12 +21,16 @@ public class SyncObjectServerFacade extends ObjectServerFacade { private static final String WRONG_TYPE_OF_CONFIGURATION = "'configuration' has to be an instance of 'SyncConfiguration'."; + @SuppressLint("StaticFieldLeak") // + private static Context applicationContext; @Override public void init(Context context) { // Trying to keep things out the public API is no fun :/ // Just use reflection on init. It is a one-time method call so should be acceptable. + //noinspection TryWithIdenticalCatches try { + // FIXME: Reflection can be avoided by moving some functions of SyncManager and ObjectServer out of public Class syncManager = Class.forName("io.realm.ObjectServer"); Method method = syncManager.getDeclaredMethod("init", Context.class); method.setAccessible(true); @@ -39,6 +44,9 @@ public void init(Context context) { } catch (ClassNotFoundException e) { throw new RealmException("Could not initialize the Realm Object Server", e); } + if (applicationContext == null) { + applicationContext = context; + } } @Override @@ -85,4 +93,8 @@ public String[] getUserAndServerUrl(RealmConfiguration config) { return new String[2]; } } + + static Context getApplicationContext() { + return applicationContext; + } } From 86b575bde4281037f3fda9fb941a1440c9bf0afa Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 21 Sep 2016 23:26:35 +0200 Subject: [PATCH 0080/2110] Fix s3uploads missing dependencies --- realm/realm-library/build.gradle | 13 +++++++++++++ .../realm-library/src/main/java/io/realm/User.java | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index fcb4080a47..a0160478fc 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -267,6 +267,19 @@ publishing { artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-release.aar") artifact sourcesJar artifact javadocJar + + //The publication doesn't know about our dependencies, so we have to manually add them to the pom + pom.withXml { + def dependenciesNode = asNode().appendNode('dependencies') + + //Iterate over the compile dependencies (we don't want the test ones), adding a node for each + configurations.compile.allDependencies.each { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + } + } } } repositories { diff --git a/realm/realm-library/src/main/java/io/realm/User.java b/realm/realm-library/src/main/java/io/realm/User.java index 8414517787..be2a111e7c 100644 --- a/realm/realm-library/src/main/java/io/realm/User.java +++ b/realm/realm-library/src/main/java/io/realm/User.java @@ -46,10 +46,10 @@ /** * This class represents a user on the Realm Object Server. The credentials are provided by various 3rd party * providers (Facebook, Google, etc.). - *

      + *

      * A user can log in to the Realm Object Server, and if access is granted, it is possible to synchronize the local * and the remote Realm. Moreover, synchronization is halted when the user is logged out. - *

      + *

      * It is possible to persist a user. By retrieving a user, there is no need to log in to the 3rd party provider again. * Persisting a user between sessions, the user's credentials are stored locally on the device, and should be treated * as sensitive data. From e7cad3811e4a6b9238f1ffda57ca64551a78af9a Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 22 Sep 2016 13:28:21 +0900 Subject: [PATCH 0081/2110] fix duplicate check of accessors when adding them (#3477) --- CHANGELOG.md | 1 + .../realm/transformer/BytecodeModifier.groovy | 4 +- .../transformer/BytecodeModifierTest.groovy | 62 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c80af7c89..bd7a429b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Fixed a lint error in proxy classes when the 'minSdkVersion' of user's project is smaller than 11 (#3356). * Fixed a potential crash when there were lots of async queries waiting in the queue. * Fixed a bug causing the Realm Transformer to not transform field access in the model's constructors (#3361). +* Fixed a bug causing a build failure when the Realm Transformer adds accessors to a model class that was already transformed in other project (#3469). ## 1.2.0 diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index b9e388c828..187dba7a03 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -41,10 +41,10 @@ class BytecodeModifier { def methods = clazz.getDeclaredMethods()*.name clazz.declaredFields.each { CtField field -> if (!Modifier.isStatic(field.getModifiers()) && !field.hasAnnotation(Ignore.class)) { - if (!methods.contains("realmGet\$${field.name}")) { + if (!methods.contains("realmGet\$${field.name}".toString())) { clazz.addMethod(CtNewMethod.getter("realmGet\$${field.name}", field)) } - if (!methods.contains("realmSet\$${field.name}")) { + if (!methods.contains("realmSet\$${field.name}".toString())) { clazz.addMethod(CtNewMethod.setter("realmSet\$${field.name}", field)) } } diff --git a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy index 6750f2b273..eb3e5d02e1 100644 --- a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy +++ b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy @@ -52,6 +52,68 @@ class BytecodeModifierTest extends Specification { } } + // https://github.com/realm/realm-java/issues/3469 + def "AddRealmAccessors_duplicateSetter"() { + setup: 'generate an empty class' + def classPool = ClassPool.getDefault() + def ctClass = classPool.makeClass('testClass') + + and: 'add a field' + def ctField = new CtField(CtClass.intType, 'age', ctClass) + ctClass.addField(ctField) + + and: 'add a setter' + def setter = CtNewMethod.setter('realmSet$age', ctField) + ctClass.addMethod(setter) + + when: 'addRealmAccessors is called' + BytecodeModifier.addRealmAccessors(ctClass) + + then: 'a getter for the field is generated' + def ctMethods = ctClass.getDeclaredMethods() + def methodNames = ctMethods.name + methodNames.contains('realmGet$age') + + and: 'the setter is not changed' + ctMethods.find {it.name.equals('realmSet$age')} == setter + + and: 'the accessors are public' + ctMethods.each { + it.getModifiers() == Modifier.PUBLIC + } + } + + // https://github.com/realm/realm-java/issues/3469 + def "AddRealmAccessors_duplicateGetter"() { + setup: 'generate an empty class' + def classPool = ClassPool.getDefault() + def ctClass = classPool.makeClass('testClass') + + and: 'add a field' + def ctField = new CtField(CtClass.intType, 'age', ctClass) + ctClass.addField(ctField) + + and: 'add a getter' + def getter = CtNewMethod.getter('realmGet$age', ctField) + ctClass.addMethod(getter) + + when: 'addRealmAccessors is called' + BytecodeModifier.addRealmAccessors(ctClass) + + then: 'a setter for the field is generated' + def ctMethods = ctClass.getDeclaredMethods() + def methodNames = ctMethods.name + methodNames.contains('realmSet$age') + + and: 'the getter is not changed' + ctMethods.find {it.name.equals('realmGet$age')} == getter + + and: 'the accessors are public' + ctMethods.each { + it.getModifiers() == Modifier.PUBLIC + } + } + def "AddRealmAccessors_IgnoreAnnotation"() { setup: 'generate an empty class' def classPool = ClassPool.getDefault() From 4ef1f46c218ce01b9a60bc731b080da7bf98dd5e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 22 Sep 2016 03:23:55 -0500 Subject: [PATCH 0082/2110] Add cause to RealmMigrationNeededException (#3482) --- CHANGELOG.md | 4 ++++ .../io/realm/RealmConfigurationTests.java | 4 +++- .../src/main/java/io/realm/BaseRealm.java | 6 +++-- .../src/main/java/io/realm/Realm.java | 22 ++++++++++++++++--- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7a429b67..a68ad60276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ * Fixed a bug causing the Realm Transformer to not transform field access in the model's constructors (#3361). * Fixed a bug causing a build failure when the Realm Transformer adds accessors to a model class that was already transformed in other project (#3469). +### Enhancements + +* A `RealmMigrationNeededException` will be thrown with a cause to show the detailed message when a migration is needed and the migration block is not in the `RealmConfiguration`. + ## 1.2.0 ### Bug fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 3b58dcce74..065a2da8fd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -370,7 +370,9 @@ public void upgradeVersionWithNoMigration() { realm = Realm.getInstance(new RealmConfiguration.Builder(configFactory.getRoot()) .schemaVersion(42).build()); fail(); - } catch (RealmMigrationNeededException ignored) { + } catch (RealmMigrationNeededException expected) { + // And it should come with a cause. + assertNotNull(expected.getCause()); } } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index e1775d8dd6..13c7f1ac93 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -706,15 +706,17 @@ static boolean compactRealm(final RealmConfiguration configuration) { * @param configuration configuration for the Realm that should be migrated. * @param migration if set, this migration block will override what is set in {@link RealmConfiguration}. * @param callback callback for specific Realm type behaviors. + * @param cause which triggers this migration. * @throws FileNotFoundException if the Realm file doesn't exist. */ protected static void migrateRealm(final RealmConfiguration configuration, final RealmMigration migration, - final MigrationCallback callback) throws FileNotFoundException { + final MigrationCallback callback, final RealmMigrationNeededException cause) + throws FileNotFoundException { if (configuration == null) { throw new IllegalArgumentException("RealmConfiguration must be provided"); } if (migration == null && configuration.getMigration() == null) { - throw new RealmMigrationNeededException(configuration.getPath(), "RealmMigration must be provided"); + throw new RealmMigrationNeededException(configuration.getPath(), "RealmMigration must be provided", cause); } final AtomicBoolean fileNotFound = new AtomicBoolean(false); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 609b4dc261..fbbfbbd7d9 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -218,7 +218,7 @@ static Realm createInstance(RealmConfiguration configuration, ColumnIndices colu deleteRealm(configuration); } else { try { - migrateRealm(configuration); + migrateRealm(configuration, e); } catch (FileNotFoundException fileNotFoundException) { // Should never happen throw new RealmIOException(fileNotFoundException); @@ -1344,7 +1344,23 @@ private void checkValidObjectForDetach(E realmObject) { * @throws FileNotFoundException if the Realm file doesn't exist. */ public static void migrateRealm(RealmConfiguration configuration) throws FileNotFoundException { - migrateRealm(configuration, null); + migrateRealm(configuration, (RealmMigration) null); + } + + /** + * Called when migration needed in the Realm initialization. + * + * @param configuration {@link RealmConfiguration} + * @param cause which triggers this migration. + * @throws FileNotFoundException if the Realm file doesn't exist. + */ + private static void migrateRealm(final RealmConfiguration configuration, final RealmMigrationNeededException cause) + throws FileNotFoundException { + BaseRealm.migrateRealm(configuration, null, new MigrationCallback() { + @Override + public void migrationComplete() { + } + }, cause); } /** @@ -1361,7 +1377,7 @@ public static void migrateRealm(RealmConfiguration configuration, RealmMigration @Override public void migrationComplete() { } - }); + }, null); } /** From 293466620929df597dc7efbebac9f965d6056eab Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 22 Sep 2016 12:06:43 +0200 Subject: [PATCH 0083/2110] Adding twiiter and google. Adding unit tests. (#152) --- .../java/io/realm/CredentialsTests.java | 45 +++++++++++++++++++ .../src/main/java/io/realm/Credentials.java | 30 +++++++++++++ 2 files changed, 75 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTest/java/io/realm/CredentialsTests.java index e406e78fec..c464499403 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CredentialsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CredentialsTests.java @@ -53,6 +53,24 @@ public void facebook() { assertTrue(creds.getUserInfo().isEmpty()); } + @Test + public void google() { + Credentials creds = Credentials.google("foo"); + + assertEquals(Credentials.IdentityProvider.GOOGLE, creds.getIdentityProvider()); + assertEquals("foo", creds.getUserIdentifier()); + assertTrue(creds.getUserInfo().isEmpty()); + } + + @Test + public void twitter() { + Credentials creds = Credentials.twitter("foo"); + + assertEquals(Credentials.IdentityProvider.TWITTER, creds.getIdentityProvider()); + assertEquals("foo", creds.getUserIdentifier()); + assertTrue(creds.getUserInfo().isEmpty()); + } + @Test public void facebook_invalidInput() { String[] invalidInput = { null, ""}; @@ -89,6 +107,19 @@ public void usernamePassword_invalidUserName() { } } + @Test + public void custom_invalidUserName() { + Map userInfo = new HashMap<>(); + userInfo.put("custom", "property"); + for (String username : new String[]{null, ""}) { + try { + Credentials.custom("facebook", username, userInfo); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + } + @Test public void custom() { Map userInfo = new HashMap(); @@ -100,4 +131,18 @@ public void custom() { assertEquals(1, creds.getUserInfo().size()); assertEquals("property", creds.getUserInfo().get("custom")); } + + @Test + public void custom_invalidProvider() { + Map userInfo = new HashMap<>(); + userInfo.put("custom", "property"); + + for (String provider : new String[]{null, ""}) { + try { + Credentials.custom(null, "foo", userInfo); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/Credentials.java b/realm/realm-library/src/main/java/io/realm/Credentials.java index 8cf749593a..144a935a39 100644 --- a/realm/realm-library/src/main/java/io/realm/Credentials.java +++ b/realm/realm-library/src/main/java/io/realm/Credentials.java @@ -106,6 +106,36 @@ public static Credentials facebook(String facebookToken) { return new Credentials(IdentityProvider.FACEBOOK, facebookToken, null); } + /** + * Creates credentials based on a Google login. + * + * @param googleToken a google userIdentifier acquired by logging into Google. + * @return a set of credentials that can be used to log into the Object Server using + * {@link User#loginAsync(Credentials, String, User.Callback)} + * @throws IllegalArgumentException if user name is either {@code null} or empty. + */ + public static Credentials google(String googleToken) { + if (googleToken == null || googleToken.equals("")) { + throw new IllegalArgumentException("Non-null 'googleToken' required."); + } + return new Credentials(IdentityProvider.GOOGLE, googleToken, null); + } + + /** + * Creates credentials based on a Twitter login. + * + * @param twitterToken a google userIdentifier acquired by logging into Twitter. + * @return a set of credentials that can be used to log into the Object Server using + * {@link User#loginAsync(Credentials, String, User.Callback)} + * @throws IllegalArgumentException if user name is either {@code null} or empty. + */ + public static Credentials twitter(String twitterToken) { + if (twitterToken == null || twitterToken.equals("")) { + throw new IllegalArgumentException("Non-null 'twitterToken' required."); + } + return new Credentials(IdentityProvider.TWITTER, twitterToken, null); + } + /** * Creates a custom set of credentials. The behaviour will depend on the type of {@code identityProvider} and * {@code userInfo} used. From 80587a130b2426414974eb7f3e249cd8a7471d91 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 22 Sep 2016 20:52:32 +0200 Subject: [PATCH 0084/2110] Unit tests for SyncConfiguration (#155) * Unit tests for SyncConfiguration * Enable initialData for sync --- .../java/io/realm/SyncConfigurationTests.java | 157 ++++++++++++++++++ .../src/main/java/io/realm/Realm.java | 9 +- .../main/java/io/realm/SyncConfiguration.java | 6 +- 3 files changed, 167 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java index 81685ee378..8d8200b74d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java @@ -24,19 +24,24 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.Map; +import io.realm.entities.StringOnly; import io.realm.rule.RunInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -51,6 +56,9 @@ public class SyncConfigurationTests { @Rule public final TemporaryFolder tempFolder = new TemporaryFolder(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private Context context; @Before @@ -217,4 +225,153 @@ public void errorHandler_nullThrows() { } } + @Test + public void equals() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config = new SyncConfiguration.Builder(user, url) + .build(); + assertTrue(config.equals(config)); + } + + @Test + public void not_equals_same() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config1 = new SyncConfiguration.Builder(user, url).build(); + SyncConfiguration config2 = new SyncConfiguration.Builder(user, url).build(); + + assertFalse(config1.equals(config2)); + } + + @Test + public void equals_not() { + User user = createTestUser(); + String url1 = "realm://objectserver.realm.io/default1"; + String url2 = "realm://objectserver.realm.io/default2"; + SyncConfiguration config1 = new SyncConfiguration.Builder(user, url1).build(); + SyncConfiguration config2 = new SyncConfiguration.Builder(user, url2).build(); + assertFalse(config1.equals(config2)); + } + + @Test + public void hashCode_equal() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config = new SyncConfiguration.Builder(user, url) + .build(); + + assertEquals(config.hashCode(), config.hashCode()); + } + + @Test + public void hashCode_notEquals() { + User user = createTestUser(); + String url1 = "realm://objectserver.realm.io/default1"; + String url2 = "realm://objectserver.realm.io/default2"; + SyncConfiguration config1 = new SyncConfiguration.Builder(user, url1).build(); + SyncConfiguration config2 = new SyncConfiguration.Builder(user, url2).build(); + assertNotEquals(config1.hashCode(), config2.hashCode()); + } + + @Test + public void get_syncSpecificValues() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + assertTrue(user.equals(config.getUser())); + assertEquals("realm://objectserver.realm.io:80/default", config.getServerUrl().toString()); + assertFalse(config.shouldDeleteRealmOnLogout()); + assertTrue(config.isSyncConfiguration()); + } + + @Test + public void encryption() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config = new SyncConfiguration.Builder(user, url) + .encryptionKey(TestHelper.getRandomKey()) + .build(); + assertNotNull(config.getEncryptionKey()); + } + + @Test(expected = IllegalArgumentException.class) + public void encryption_invalid_null() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + + new SyncConfiguration.Builder(user, url).encryptionKey(null); + } + + @Test(expected = IllegalArgumentException.class) + public void encryption_invalid_wrong_length() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + + new SyncConfiguration.Builder(user, url).encryptionKey(new byte[]{1, 2, 3}); + } + + @Test(expected = IllegalArgumentException.class) + public void directory_null() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + new SyncConfiguration.Builder(user, url).directory(null); + } + + @Test(expected = IllegalArgumentException.class) + public void directory_writeProtectedDir() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + + File dir = new File("/"); + new SyncConfiguration.Builder(user, url).directory(dir); + } + + @Test + public void directory_dirIsAFile() throws IOException { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + + File dir = configFactory.getRoot(); + File file = new File(dir, "dummyfile"); + assertTrue(file.createNewFile()); + thrown.expect(IllegalArgumentException.class); + new SyncConfiguration.Builder(user, url).directory(file); + file.delete(); // clean up + } + + @Test + public void deleteOnLogout() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + + SyncConfiguration config = new SyncConfiguration.Builder(user, url) + .deleteRealmOnLogout() + .build(); + assertTrue(config.shouldDeleteRealmOnLogout()); + } + + @Test + public void initialData() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + + SyncConfiguration config = new SyncConfiguration.Builder(user, url) + .initialData(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + StringOnly stringOnly = realm.createObject(StringOnly.class); + stringOnly.setChars("TEST 42"); + } + }) + .build(); + + assertNotNull(config.getInitialDataTransaction()); + + Realm realm = Realm.getInstance(config); + RealmResults results = realm.where(StringOnly.class).findAll(); + assertEquals(1, results.size()); + assertEquals("TEST 42", results.first().getChars()); + realm.close(); + } } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index c549ae6fb3..a10443a75e 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -357,11 +357,16 @@ private static void initializeRealm(Realm realm) { (version == UNVERSIONED) ? realm.configuration.getSchemaVersion() : version, columnInfoMap); - if (version == UNVERSIONED && !syncAvailable) { + if (version == UNVERSIONED) { final Transaction transaction = realm.getConfiguration().getInitialDataTransaction(); if (transaction != null) { - transaction.execute(realm); + if (syncAvailable) { + realm.executeTransaction(transaction); + } else { + transaction.execute(realm); + } } + } } finally { if (!syncAvailable) { diff --git a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java index 8e4510737a..1600cd2ec5 100644 --- a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java @@ -157,8 +157,8 @@ public boolean equals(Object o) { if (!serverUrl.equals(that.serverUrl)) return false; if (!user.equals(that.user)) return false; if (!syncPolicy.equals(that.syncPolicy)) return false; - return errorHandler.equals(that.errorHandler); - + if (!errorHandler.equals(that.errorHandler)) return false; + return true; } @Override @@ -166,9 +166,9 @@ public int hashCode() { int result = super.hashCode(); result = 31 * result + serverUrl.hashCode(); result = 31 * result + user.hashCode(); + result = 31 * result + (deleteRealmOnLogout ? 1 : 0); result = 31 * result + syncPolicy.hashCode(); result = 31 * result + errorHandler.hashCode(); - result = 31 * result + (deleteRealmOnLogout ? 1 : 0); return result; } From 9f288df235a504203bdaf9ed7f0a5fb179122712 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Thu, 22 Sep 2016 21:44:23 +0200 Subject: [PATCH 0085/2110] Use product flavors to create libraries with and without object server (#142) * Preliminary work on product flavors to split base from object-server * Implement the Gradle plugin extension * Create JNI headers for each build * Separate native lib for sync * All JNI source files stay in main/cpp. * Add REALM_SYNC to control specific sync code. * Don't link to sync lib when compiling common Realm. * Wrong abiFilters * Disable lto It cause some miserable failures on linking which I have no idea to fix. It looks like https://sourceware.org/ml/binutils/2004-08/msg00187.html According to my last test, using lto saves 30KB for stripped armeabi-v7a so lib. * Get the classes.jar file from the flavor location * Don't load SyncManager in JNI in the base flavor * Collect the test results from the new location * Only depend on okhttp for the objectServer flavor * Rename and change default value of DSL field * Fix the field name in the plugin * Add the dependency after evaluation * After evaluation is deprecated * Use the DependencyResolutionListener callback to add the dependency * Imports --- Jenkinsfile | 2 +- build.gradle | 8 +- examples/objectServerExample/build.gradle | 4 + .../main/groovy/io/realm/gradle/Realm.groovy | 19 ++- .../realm/gradle/RealmPluginExtension.groovy | 21 ++++ .../realm-annotations-processor/build.gradle | 2 +- realm/realm-library/build.gradle | 45 +++++-- .../java/io/realm/CredentialsTests.java | 0 .../java/io/realm/SchemaTests.java | 0 .../java/io/realm/SyncConfigurationTests.java | 0 .../java/io/realm/UserTests.java | 0 .../java/io/realm/util/SyncTestUtils.java | 0 .../realm-library/src/main/cpp/CMakeLists.txt | 86 +++++++++---- .../cpp/io_realm_internal_SharedRealm.cpp | 4 + .../src/main/cpp/io_realm_internal_Util.cpp | 1 - realm/realm-library/src/main/cpp/object-store | 2 +- .../cpp/io_realm_internal_Util.cpp | 113 ++++++++++++++++++ .../java/io/realm/AuthenticationListener.java | 0 .../java/io/realm/Credentials.java | 0 .../java/io/realm/ErrorCode.java | 0 .../java/io/realm/ObjectServer.java | 0 .../java/io/realm/ObjectServerError.java | 0 .../java/io/realm/Session.java | 0 .../java/io/realm/SessionState.java | 0 .../java/io/realm/SyncConfiguration.java | 0 .../java/io/realm/SyncManager.java | 0 .../java/io/realm/User.java | 0 .../java/io/realm/UserStore.java | 0 .../realm/android/SharedPrefsUserStore.java | 0 .../internal}/SyncObjectServerFacade.java | 0 .../internal/network/AuthServerResponse.java | 0 .../internal/network/AuthenticateRequest.java | 0 .../network/AuthenticateResponse.java | 0 .../network/AuthenticationServer.java | 0 .../network/ExponentialBackoffTask.java | 0 .../realm/internal/network/LogoutRequest.java | 0 .../internal/network/LogoutResponse.java | 0 .../network/NetworkStateReceiver.java | 0 .../network/OkHttpAuthenticationServer.java | 0 .../objectserver/AuthenticatingState.java | 0 .../internal/objectserver/BindingState.java | 0 .../internal/objectserver/BoundState.java | 0 .../internal/objectserver/FsmAction.java | 0 .../realm/internal/objectserver/FsmState.java | 0 .../internal/objectserver/InitialState.java | 0 .../internal/objectserver/SessionStore.java | 0 .../internal/objectserver/StoppedState.java | 0 .../internal/objectserver/SyncSession.java | 0 .../realm/internal/objectserver/SyncUser.java | 0 .../realm/internal/objectserver/SyncUtil.java | 0 .../io/realm/internal/objectserver/Token.java | 0 .../internal/objectserver/UnboundState.java | 0 .../syncpolicy/AutomaticSyncPolicy.java | 0 .../realm/internal/syncpolicy/SyncPolicy.java | 0 54 files changed, 267 insertions(+), 40 deletions(-) create mode 100644 gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy rename realm/realm-library/src/{androidTest => androidTestobjectServer}/java/io/realm/CredentialsTests.java (100%) rename realm/realm-library/src/{androidTest => androidTestobjectServer}/java/io/realm/SchemaTests.java (100%) rename realm/realm-library/src/{androidTest => androidTestobjectServer}/java/io/realm/SyncConfigurationTests.java (100%) rename realm/realm-library/src/{androidTest => androidTestobjectServer}/java/io/realm/UserTests.java (100%) rename realm/realm-library/src/{androidTest => androidTestobjectServer}/java/io/realm/util/SyncTestUtils.java (100%) create mode 100644 realm/realm-library/src/objectServer/cpp/io_realm_internal_Util.cpp rename realm/realm-library/src/{main => objectServer}/java/io/realm/AuthenticationListener.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/Credentials.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/ErrorCode.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/ObjectServer.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/ObjectServerError.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/Session.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/SessionState.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/SyncConfiguration.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/SyncManager.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/User.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/UserStore.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/android/SharedPrefsUserStore.java (100%) rename realm/realm-library/src/{main/java/io/realm/internal/objectserver => objectServer/java/io/realm/internal}/SyncObjectServerFacade.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/network/AuthServerResponse.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/network/AuthenticateRequest.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/network/AuthenticateResponse.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/network/AuthenticationServer.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/network/ExponentialBackoffTask.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/network/LogoutRequest.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/network/LogoutResponse.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/network/NetworkStateReceiver.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/network/OkHttpAuthenticationServer.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/AuthenticatingState.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/BindingState.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/BoundState.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/FsmAction.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/FsmState.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/InitialState.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/SessionStore.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/StoppedState.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/SyncSession.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/SyncUser.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/SyncUtil.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/Token.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/objectserver/UnboundState.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java (100%) rename realm/realm-library/src/{main => objectServer}/java/io/realm/internal/syncpolicy/SyncPolicy.java (100%) diff --git a/Jenkinsfile b/Jenkinsfile index 43de09cba5..03c31f729f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -60,7 +60,7 @@ try { archiveLog = false; } finally { stopLogCatCollector(backgroundPid, archiveLog) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/TEST-*.xml' + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' } // TODO: add support for running monkey on the example apps diff --git a/build.gradle b/build.gradle index b0103c89c6..0f99add0a1 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ task installAnnotations(type:GradleBuild) { group = 'Install' description = 'Install the jar realm-annotations into mavenLocal()' buildFile = file('realm-annotations/build.gradle') - tasks = ['install'] + tasks = ['publishToMavenLocal'] } task assembleTransformer(type:GradleBuild) { @@ -44,7 +44,7 @@ task installTransformer(type:GradleBuild) { description = 'Install the jar realm-transformer into mavenLocal()' dependsOn installAnnotations buildFile = file('realm-transformer/build.gradle') - tasks = ['install'] + tasks = ['publishToMavenLocal'] } task assembleRealm(type:GradleBuild) { @@ -105,7 +105,7 @@ task installRealm(type:GradleBuild) { description = 'Install the artifacts of Realm libraries into mavenLocal()' dependsOn installTransformer buildFile = file('realm/build.gradle') - tasks = ['install'] + tasks = ['publishToMavenLocal'] if (project.hasProperty('buildTargetABIs')) { startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] } @@ -129,7 +129,7 @@ task installGradlePlugin(type:GradleBuild) { dependsOn installRealm dependsOn installTransformer buildFile = file('gradle-plugin/build.gradle') - tasks = ['install'] + tasks = ['publishToMavenLocal'] } task installRealmJava(type:Task) { diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 81259e4c98..51de41a750 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -28,6 +28,10 @@ android { } } +realm { + syncEnabled = true +} + dependencies { compile 'com.android.support:support-v4:24.2.0' compile 'com.android.support:design:24.2.0' diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index c533258ce2..8847a46826 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -23,6 +23,8 @@ import io.realm.transformer.RealmTransformer import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.artifacts.DependencyResolutionListener +import org.gradle.api.artifacts.ResolvableDependencies class Realm implements Plugin { @@ -39,6 +41,8 @@ class Realm implements Plugin { throw new GradleException('Realm gradle plugin only supports android gradle plugin 1.5.0 or later.') } + project.extensions.create('realm', RealmPluginExtension) + def usesKotlinPlugin = project.plugins.findPlugin('kotlin-android') != null def usesAptPlugin = project.plugins.findPlugin('com.neenbedankt.android-apt') != null @@ -50,7 +54,6 @@ class Realm implements Plugin { project.android.registerTransform(new RealmTransformer(project)) project.repositories.add(project.getRepositories().jcenter()) - project.dependencies.add("compile", "io.realm:realm-android-library:${Version.VERSION}") project.dependencies.add("compile", "io.realm:realm-annotations:${Version.VERSION}") if (isKaptProject) { project.dependencies.add("kapt", "io.realm:realm-annotations:${Version.VERSION}") @@ -61,6 +64,20 @@ class Realm implements Plugin { project.dependencies.add("androidTestApt", "io.realm:realm-annotations:${Version.VERSION}") project.dependencies.add("androidTestApt", "io.realm:realm-annotations-processor:${Version.VERSION}") } + + // Using afterEvaluate is now deprecated so this callback is used instead + def compileDeps = project.getConfigurations().getByName("compile").getDependencies() + project.getGradle().addListener(new DependencyResolutionListener() { + @Override + void beforeResolve(ResolvableDependencies resolvableDependencies) { + def suffix = project.realm.syncEnabled?'-object-server':'' + compileDeps.add(project.getDependencies().create("io.realm:realm-android-library${suffix}:${Version.VERSION}")) + project.getGradle().removeListener(this) + } + + @Override + void afterResolve(ResolvableDependencies resolvableDependencies) {} + }) } private static boolean isTransformAvailable() { diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy new file mode 100644 index 0000000000..46577c5646 --- /dev/null +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy @@ -0,0 +1,21 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.gradle + +class RealmPluginExtension { + boolean syncEnabled = false +} \ No newline at end of file diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 81e854e72d..6c2c3cd3cd 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -11,7 +11,7 @@ dependencies { compile group:'com.squareup', name:'javawriter', version:'2.5.0' compile "io.realm:realm-annotations:${version}" - testCompile files('../realm-library/build/intermediates/bundles/release/classes.jar') // Java projects cannot depend on AAR files + testCompile files('../realm-library/build/intermediates/bundles/base/release/classes.jar') // Java projects cannot depend on AAR files testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it testCompile group:'junit', name:'junit', version:'4.12' testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index a0160478fc..83adceb7b6 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -84,6 +84,23 @@ android { lintOptions { abortOnError false } + + productFlavors { + base { + externalNativeBuild { + cmake { + arguments "-DREALM_FLAVOR=base" + } + } + } + objectServer { + externalNativeBuild { + cmake { + arguments "-DREALM_FLAVOR=objectServer" + } + } + } + } } @@ -99,10 +116,12 @@ repositories { dependencies { provided 'io.reactivex:rxjava:1.1.0' + compile "io.realm:realm-annotations:${version}" - compile 'com.squareup.okhttp3:okhttp:3.4.1' // Should be moved to a seperate library compile 'com.getkeepsafe.relinker:relinker:1.2.1' + objectServerCompile 'com.squareup.okhttp3:okhttp:3.4.1' // Should be moved to a seperate library + androidTestCompile 'io.reactivex:rxjava:1.1.0' androidTestCompile 'com.android.support:support-annotations:24.0.0' androidTestCompile 'com.android.support.test:runner:0.5' @@ -116,11 +135,13 @@ dependencies { } task sourcesJar(type: Jar) { + from android.sourceSets.objectServer.java.srcDirs from android.sourceSets.main.java.srcDirs classifier = 'sources' } task javadoc(type: Javadoc) { + source android.sourceSets.objectServer.java.srcDirs source android.sourceSets.main.java.srcDirs source "../../realm-annotations/src/main/java" classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) @@ -260,11 +281,19 @@ install { publishing { publications { - realmPublication(MavenPublication) { + basePublication(MavenPublication) { groupId 'io.realm' artifactId 'realm-android-library' version project.version - artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-release.aar") + artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-base-release.aar") + artifact sourcesJar + artifact javadocJar + } + objectServerPublication(MavenPublication) { + groupId 'io.realm' + artifactId 'realm-android-library-object-server' + version project.version + artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-objectServer-release.aar") artifact sourcesJar artifact javadocJar @@ -304,7 +333,7 @@ bintray { dryRun = false publish = false - configurations = ['realmPublication'] + configurations = ['basePublication', 'objectServerPublication'] pkg { repo = 'maven' @@ -329,7 +358,7 @@ artifactory { maven = true } defaults { - publishConfigs('realmPublication') + publishConfigs('basePublication', 'objectServerPublication') publishPom = true publishIvy = false } @@ -462,6 +491,7 @@ task deployCore(group: 'build setup', description: 'Deploy the latest version of } } +publishToMavenLocal.dependsOn assemble preBuild.dependsOn deployCore if (project.hasProperty('dontCleanJniFiles')) { @@ -473,11 +503,6 @@ if (project.hasProperty('dontCleanJniFiles')) { } } } else { - task cleanJniHeaders(type: Delete) { - delete project.file('src/main/cpp/jni_include') - } - clean.dependsOn cleanJniHeaders - task cleanExternalBuildFiles(type: Delete) { delete project.file('.externalNativeBuild') } diff --git a/realm/realm-library/src/androidTest/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/CredentialsTests.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/CredentialsTests.java rename to realm/realm-library/src/androidTestobjectServer/java/io/realm/CredentialsTests.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SchemaTests.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/SchemaTests.java rename to realm/realm-library/src/androidTestobjectServer/java/io/realm/SchemaTests.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/SyncConfigurationTests.java rename to realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/UserTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/UserTests.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/UserTests.java rename to realm/realm-library/src/androidTestobjectServer/java/io/realm/UserTests.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/util/SyncTestUtils.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/util/SyncTestUtils.java rename to realm/realm-library/src/androidTestobjectServer/java/io/realm/util/SyncTestUtils.java diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 3916cf6977..04a18efac8 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -17,23 +17,33 @@ set(CMAKE_VERBOSE_MAKEFILE ON) # Generate compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -if (CMAKE_BUILD_TYPE STREQUAL "Release") - set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/classes/release/) +# Set flag build_SYNC +if (REALM_FLAVOR STREQUAL base) + set(build_SYNC OFF) else() - set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/classes/debug/) + set(build_SYNC ON) endif() +# Generate JNI header files. Each build has its own JNI header in its build_dir/jni_include. +string(TOLOWER ${CMAKE_BUILD_TYPE} build_type_FOLDER) +set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/classes/${REALM_FLAVOR}/${build_type_FOLDER}/) +set(classes_LIST + io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow + io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow + io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil + io.realm.log.LogLevel io.realm.Property io.realm.RealmSchema io.realm.RealmObjectSchema +) +set(jni_headers_PATH ${PROJECT_BINARY_DIR}/jni_include) +if (build_SYNC) + list(APPEND classes_LIST + io.realm.SyncManager io.realm.internal.objectserver.SyncSession) +endif() create_javah(TARGET jni_headers - CLASSES io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow - io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow - io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil - io.realm.SyncManager io.realm.internal.objectserver.SyncSession - io.realm.log.LogLevel - io.realm.Property io.realm.RealmSchema io.realm.RealmObjectSchema - - CLASSPATH ${classes_PATH} - OUTPUT_DIR ${CMAKE_SOURCE_DIR}/jni_include - DEPENDS ${classes_PATH} + CLASSES ${classes_LIST} + + CLASSPATH ${classes_PATH} + OUTPUT_DIR ${jni_headers_PATH} + DEPENDS ${classes_PATH} ) # TODO: Ideally the debug build should link with core's debug build. But core dbg lib has @@ -81,7 +91,7 @@ set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_PAT # build application's shared lib include_directories(${REALM_CORE_DIST_DIR}/include ${CMAKE_SOURCE_DIR} - ${CMAKE_SOURCE_DIR}/jni_include + ${jni_headers_PATH} ${CMAKE_SOURCE_DIR}/object-store/src) set(ANDROID_STL "gnustl_static") @@ -101,31 +111,65 @@ endif() set(WARNING_CXX_FLAGS "-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros \ -Wno-missing-field-initializers -Wmissing-declarations -Wno-error=uninitialized -Wno-error=maybe-uninitialized") set(REALM_COMMON_CXX_FLAGS "-DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14 -fsigned-char") +if (build_SYNC) + set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_SYNC") +endif() # There might be an issue with -Os of ndk gcc 4.9. It will hang the encryption related tests. # And this issue doesn't seem to impact the core compiling. -set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG -flto") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") #-ggdb doesn't play well with -flto set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -Og -DNDEBUG") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${REALM_COMMON_CXX_FLAGS} ${WARNING_CXX_FLAGS} ${ABI_CXX_FLAGS}") # Set link flags -set(REALM_LINKER_FLAGS "-lz") +set(REALM_LINKER_FLAGS "") +if (build_SYNC) + set(REALM_LINKER_FLAGS "${REALM_LINKER_FLAGS} -lz") +endif() set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${REALM_LINKER_FLAGS}") +# JNI source files file(GLOB jni_SRC "*.cpp" ) +# Those source file are only needed for sync. +if (NOT build_SYNC) + list(REMOVE_ITEM jni_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectserver_SyncSession.cpp) +endif() + +# Object Store source files file(GLOB objectstore_SRC - "object-store/src/*.cpp" - "object-store/src/impl/*.cpp" + "object-store/src/collection_notifications.cpp" + "object-store/src/object_schema.cpp" + "object-store/src/object_store.cpp" + "object-store/src/schema.cpp" + "object-store/src/index_set.cpp" + "object-store/src/shared_realm.cpp" + "object-store/src/impl/realm_coordinator.cpp" + "object-store/src/impl/collection_notifier.cpp" + "object-store/src/impl/collection_change_builder.cpp" + "object-store/src/impl/transact_log_handler.cpp" + "object-store/src/impl/weak_realm_notifier.cpp" "object-store/src/impl/android/*.cpp" - "object-store/src/util/*.cpp" -) -add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC}) + "object-store/src/util/*.cpp") +# Sync needed Object Store files +if (build_SYNC) + file(GLOB objectstore_sync_SRC + "object-store/src/sync_manager.cpp" + "object-store/src/impl/sync_session.cpp") +endif() + +add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) add_dependencies(realm-jni jni_headers) # -latomic is not set by default for mips. See https://code.google.com/p/android/issues/detail?id=182094 +if (build_SYNC) # FIXME: The order matters! lib_realm_sync needs to be in front of lib_realm_core!! Find out why!! target_link_libraries(realm-jni log android atomic lib_realm_sync lib_realm_core) +else() +target_link_libraries(realm-jni log android atomic lib_realm_core) +endif() # Strip the release so files and backup the unstripped versions if (CMAKE_BUILD_TYPE STREQUAL "Release") diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 1792920f4b..39b3eefb97 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -5,7 +5,9 @@ #include "java_binding_context.hpp" #include "util.hpp" +#ifdef REALM_SYNC #include "sync_config.hpp" +#endif using namespace realm; using namespace realm::_impl; @@ -39,6 +41,7 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri config->cache = cache; config->disable_format_upgrade = disable_format_upgrade; config->automatic_change_notifications = auto_change_notification; +#ifdef REALM_SYNC if (sync_server_url) { JStringAccessor url(env, sync_server_url); JStringAccessor token(env, sync_user_token); @@ -48,6 +51,7 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri // FIXME: Sync session is handled by java now. Remove this when adapt to OS sync implementation. config->sync_config->create_session = false; } +#endif return reinterpret_cast(config); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 84e7a3fe5a..067ce9c8e6 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -58,7 +58,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); java_lang_double = GetClass(env, "java/lang/Double"); java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); - sync_manager = GetClass(env, "io/realm/SyncManager"); realmlog_class = GetClass(env, "io/realm/log/RealmLog"); log_trace = env->GetStaticMethodID(realmlog_class, "trace", "(Ljava/lang/String;[Ljava/lang/Object;)V"); log_debug = env->GetStaticMethodID(realmlog_class, "debug", "(Ljava/lang/String;[Ljava/lang/Object;)V"); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 6b6012cb2d..b297230bde 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 6b6012cb2d603e20bf82e19c453af0f9bf997894 +Subproject commit b297230bde4370301948055014453323898198fe diff --git a/realm/realm-library/src/objectServer/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/objectServer/cpp/io_realm_internal_Util.cpp new file mode 100644 index 0000000000..84e7a3fe5a --- /dev/null +++ b/realm/realm-library/src/objectServer/cpp/io_realm_internal_Util.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2014 Realm Inc. + * + * 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 + +#include +#include + +#include "io_realm_log_LogLevel.h" +#include "mem_usage.hpp" +#include "util.hpp" + +using std::string; + +//#define USE_VLD +#if defined(_MSC_VER) && defined(_DEBUG) && defined(USE_VLD) + #include "C:\\Program Files (x86)\\Visual Leak Detector\\include\\vld.h" +#endif + +// used by logging +int trace_level = 0; +jclass realmlog_class; +jmethodID log_trace; +jmethodID log_debug; +jmethodID log_info; +jmethodID log_warn; +jmethodID log_error; +jmethodID log_fatal; + +const string TABLE_PREFIX("class_"); + + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) +{ + JNIEnv* env; + if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { + return JNI_ERR; + } + else { + g_vm = vm; + // Loading classes and constructors for later use - used by box typed fields and a few methods' return value + java_lang_long = GetClass(env, "java/lang/Long"); + java_lang_long_init = env->GetMethodID(java_lang_long, "", "(J)V"); + java_lang_float = GetClass(env, "java/lang/Float"); + java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); + java_lang_double = GetClass(env, "java/lang/Double"); + java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); + sync_manager = GetClass(env, "io/realm/SyncManager"); + realmlog_class = GetClass(env, "io/realm/log/RealmLog"); + log_trace = env->GetStaticMethodID(realmlog_class, "trace", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_debug = env->GetStaticMethodID(realmlog_class, "debug", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_info = env->GetStaticMethodID(realmlog_class, "info", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_warn = env->GetStaticMethodID(realmlog_class, "warn", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_error = env->GetStaticMethodID(realmlog_class, "error", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + log_fatal = env->GetStaticMethodID(realmlog_class, "fatal", "(Ljava/lang/String;[Ljava/lang/Object;)V"); + } + + return JNI_VERSION_1_6; +} + +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) +{ + JNIEnv* env; + if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { + return; + } + else { + env->DeleteGlobalRef(java_lang_long); + env->DeleteGlobalRef(java_lang_float); + env->DeleteGlobalRef(java_lang_double); + } +} + +JNIEXPORT void JNICALL Java_io_realm_internal_Util_nativeSetDebugLevel(JNIEnv*, jclass, jint level) +{ + /** + * level should match one of the levels defined in LogLevel.java + * ALL = 1 + * TRACE = 2 + * DEBUG = 3 + * INFO = 4 + * WARN = 5 + * ERROR = 6 + * FATAL = 7 + * OFF = 8 + */ + trace_level = level; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_Util_nativeGetMemUsage(JNIEnv*, jclass) +{ + return GetMemUsage(); +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeGetTablePrefix( + JNIEnv* env, jclass) +{ + realm::StringData sd(TABLE_PREFIX); + return to_jstring(env, sd); +} diff --git a/realm/realm-library/src/main/java/io/realm/AuthenticationListener.java b/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/AuthenticationListener.java rename to realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java diff --git a/realm/realm-library/src/main/java/io/realm/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/Credentials.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/Credentials.java rename to realm/realm-library/src/objectServer/java/io/realm/Credentials.java diff --git a/realm/realm-library/src/main/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/ErrorCode.java rename to realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java diff --git a/realm/realm-library/src/main/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/ObjectServer.java rename to realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java diff --git a/realm/realm-library/src/main/java/io/realm/ObjectServerError.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/ObjectServerError.java rename to realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java diff --git a/realm/realm-library/src/main/java/io/realm/Session.java b/realm/realm-library/src/objectServer/java/io/realm/Session.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/Session.java rename to realm/realm-library/src/objectServer/java/io/realm/Session.java diff --git a/realm/realm-library/src/main/java/io/realm/SessionState.java b/realm/realm-library/src/objectServer/java/io/realm/SessionState.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/SessionState.java rename to realm/realm-library/src/objectServer/java/io/realm/SessionState.java diff --git a/realm/realm-library/src/main/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/SyncConfiguration.java rename to realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java diff --git a/realm/realm-library/src/main/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/SyncManager.java rename to realm/realm-library/src/objectServer/java/io/realm/SyncManager.java diff --git a/realm/realm-library/src/main/java/io/realm/User.java b/realm/realm-library/src/objectServer/java/io/realm/User.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/User.java rename to realm/realm-library/src/objectServer/java/io/realm/User.java diff --git a/realm/realm-library/src/main/java/io/realm/UserStore.java b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/UserStore.java rename to realm/realm-library/src/objectServer/java/io/realm/UserStore.java diff --git a/realm/realm-library/src/main/java/io/realm/android/SharedPrefsUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/android/SharedPrefsUserStore.java rename to realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncObjectServerFacade.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthServerResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/network/AuthServerResponse.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateRequest.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/network/AuthenticateResponse.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/network/AuthenticationServer.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/network/ExponentialBackoffTask.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/LogoutRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/network/LogoutRequest.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/LogoutResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/network/LogoutResponse.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/NetworkStateReceiver.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/network/NetworkStateReceiver.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/network/OkHttpAuthenticationServer.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/AuthenticatingState.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/BindingState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BindingState.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/BindingState.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BindingState.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/BoundState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/BoundState.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmAction.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmAction.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/FsmState.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/InitialState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/InitialState.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/InitialState.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/InitialState.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SessionStore.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/SessionStore.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/StoppedState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/StoppedState.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncSession.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUser.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUtil.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUtil.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/SyncUtil.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUtil.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/Token.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/Token.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectserver/UnboundState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/UnboundState.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/objectserver/UnboundState.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/UnboundState.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java diff --git a/realm/realm-library/src/main/java/io/realm/internal/syncpolicy/SyncPolicy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java similarity index 100% rename from realm/realm-library/src/main/java/io/realm/internal/syncpolicy/SyncPolicy.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java From 2394e2673e93c5c72928f3f349fe387ac401eb84 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 23 Sep 2016 08:53:20 +0200 Subject: [PATCH 0086/2110] Moving logging to a stage where response has been parsed (#158) --- .../realm/internal/network/AuthenticateResponse.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index 18619e3635..e3fbc8c3b4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -53,7 +53,6 @@ public static AuthenticateResponse from(Response response) { ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); return new AuthenticateResponse(error); } - RealmLog.debug("Authenticate response: " + serverResponse); if (response.code() != 200) { return new AuthenticateResponse(AuthServerResponse.createError(serverResponse, response.code())); } else { @@ -82,6 +81,7 @@ public static AuthenticateResponse from(ObjectServerError error) { * @param error the network or I/O error. */ private AuthenticateResponse(ObjectServerError error) { + RealmLog.debug("AuthenticateResponse. Error " + error.getErrorMessage()); setError(error); this.accessToken = null; this.refreshToken = null; @@ -97,6 +97,7 @@ private AuthenticateResponse(String serverResponse) { ObjectServerError error; Token accessToken; Token refreshToken; + String message; try { JSONObject obj = new JSONObject(serverResponse); accessToken = obj.has(JSON_FIELD_ACCESS_TOKEN) ? @@ -104,13 +105,19 @@ private AuthenticateResponse(String serverResponse) { refreshToken = obj.has(JSON_FIELD_REFRESH_TOKEN) ? Token.from(obj.getJSONObject(JSON_FIELD_REFRESH_TOKEN)) : null; error = null; + if (accessToken == null) { + message = "accessToken = null"; + } else { + message = String.format("Identity %s; Path %s", accessToken.identity(), accessToken.path()); + } } catch (JSONException ex) { accessToken = null; refreshToken = null; //noinspection ThrowableInstanceNeverThrown error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, ex); + message = String.format("Error %s", error.getErrorMessage()); } - + RealmLog.debug("AuthenticateResponse. " + message); setError(error); this.accessToken = accessToken; this.refreshToken = refreshToken; From 1a73742f853b4f9df463fb494f32cb2ec22b7b93 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 23 Sep 2016 10:04:20 +0200 Subject: [PATCH 0087/2110] Use latest Sync RC --- realm/realm-library/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index fe3e15444b..85b98eba1f 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '1.0.0-beta-36.0' +ext.coreVersion = '1.0.0-beta-37.0-rc' // empty or comment out this to disable hash checking -ext.coreSha256Hash = 'a283d8e7d2430976289a3ae5132c1c7811e2048ffb568d5e12c49ea545bcf272' +ext.coreSha256Hash = '613ce968fd951295ca31d68feade17215d5c753c0beba62c818796dd5256e9f8' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from From 7e17887d0945acfab6dfb5c787ae2c731d82b843 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 23 Sep 2016 15:34:49 +0200 Subject: [PATCH 0088/2110] Fix local POM not being generated properly. (#162) --- realm/build.gradle | 2 +- realm/realm-library/build.gradle | 50 +++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/realm/build.gradle b/realm/build.gradle index 8b8dd41c0c..70fd1cd30b 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -9,7 +9,7 @@ buildscript { classpath 'com.android.tools.build:gradle:2.2.0' classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.novoda:gradle-android-command-plugin:1.3.0' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.2' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 85b98eba1f..ad88ae343d 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -116,12 +116,9 @@ repositories { dependencies { provided 'io.reactivex:rxjava:1.1.0' - compile "io.realm:realm-annotations:${version}" compile 'com.getkeepsafe.relinker:relinker:1.2.1' - - objectServerCompile 'com.squareup.okhttp3:okhttp:3.4.1' // Should be moved to a seperate library - + objectServerCompile 'com.squareup.okhttp3:okhttp:3.4.1' androidTestCompile 'io.reactivex:rxjava:1.1.0' androidTestCompile 'com.android.support:support-annotations:24.0.0' androidTestCompile 'com.android.support.test:runner:0.5' @@ -130,7 +127,6 @@ dependencies { androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'com.opencsv:opencsv:3.4' androidTestCompile 'dk.ilios:spanner:0.6.0' - androidTestApt project(':realm-annotations-processor') } @@ -278,6 +274,34 @@ install { } } +// The publications doesn't know about our AAR dependencies, so we have to manually add them to the pom +// Credit: http://stackoverflow.com/questions/24743562/gradle-not-including-dependencies-in-published-pom-xml +def createPomDependencies(configurationNames) { + return { + def dependenciesNode = asNode().appendNode('dependencies') + configurationNames.each { configurationName -> + configurations[configurationName].allDependencies.each { + if (it.group != null && it.name != null) { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + + //If there are any exclusions in dependency + if (it.excludeRules.size() > 0) { + def exclusionsNode = dependencyNode.appendNode('exclusions') + it.excludeRules.each { rule -> + def exclusionNode = exclusionsNode.appendNode('exclusion') + exclusionNode.appendNode('groupId', rule.group) + exclusionNode.appendNode('artifactId', rule.module) + } + } + } + } + } + } +} + publishing { publications { basePublication(MavenPublication) { @@ -287,7 +311,10 @@ publishing { artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-base-release.aar") artifact sourcesJar artifact javadocJar + + pom.withXml(createPomDependencies(["baseCompile", "compile"])) } + objectServerPublication(MavenPublication) { groupId 'io.realm' artifactId 'realm-android-library-object-server' @@ -296,18 +323,7 @@ publishing { artifact sourcesJar artifact javadocJar - //The publication doesn't know about our dependencies, so we have to manually add them to the pom - pom.withXml { - def dependenciesNode = asNode().appendNode('dependencies') - - //Iterate over the compile dependencies (we don't want the test ones), adding a node for each - configurations.compile.allDependencies.each { - def dependencyNode = dependenciesNode.appendNode('dependency') - dependencyNode.appendNode('groupId', it.group) - dependencyNode.appendNode('artifactId', it.name) - dependencyNode.appendNode('version', it.version) - } - } + pom.withXml(createPomDependencies(["objectServerCompile", "compile"])) } } repositories { From a0b5ca465a32674d5d116e2e6058dc6a97d1c063 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Sep 2016 09:58:53 -0500 Subject: [PATCH 0089/2110] Upgrade ReLinker to 1.2.2 (#3487) --- CHANGELOG.md | 1 + realm/realm-library/build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a68ad60276..9229e3fc12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Internal * Move JNI build to CMake. +* Upgrade ReLinker to 1.2.2. ### Bug fixes diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 7a5f841a45..e994083131 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -99,7 +99,7 @@ repositories { dependencies { provided 'io.reactivex:rxjava:1.1.0' compile "io.realm:realm-annotations:${version}" - compile 'com.getkeepsafe.relinker:relinker:1.2.1' + compile 'com.getkeepsafe.relinker:relinker:1.2.2' androidTestCompile 'io.reactivex:rxjava:1.1.0' androidTestCompile 'com.android.support:support-annotations:24.0.0' From 332d7b7ce585f55f9655ebd56577bf91c8fb1ff7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 23 Sep 2016 17:05:03 +0200 Subject: [PATCH 0090/2110] Upgrade to latest sync release (#165) --- realm/realm-library/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ad88ae343d..5e8135f351 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '1.0.0-beta-37.0-rc' +ext.coreVersion = '1.0.0-beta-37.0' // empty or comment out this to disable hash checking -ext.coreSha256Hash = '613ce968fd951295ca31d68feade17215d5c753c0beba62c818796dd5256e9f8' +ext.coreSha256Hash = '495cd28446e57f4565780dd83a859e4cbc0d60907844f902ae4f5233dc47a8ba' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from From b26226c27dc8a4dcbd113b70b4da8f42917f6206 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 23 Sep 2016 17:51:43 +0200 Subject: [PATCH 0091/2110] Tests for Session and SyncManager (#163) --- .../java/io/realm/SessionTests.java | 79 ++++++++ .../java/io/realm/SyncManagerTests.java | 174 ++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 realm/realm-library/src/androidTestobjectServer/java/io/realm/SessionTests.java create mode 100644 realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncManagerTests.java diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SessionTests.java new file mode 100644 index 0000000000..8554b2280e --- /dev/null +++ b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SessionTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.OkHttpAuthenticationServer; +import io.realm.internal.objectserver.SyncSession; +import io.realm.rule.TestRealmConfigurationFactory; + +import static io.realm.util.SyncTestUtils.createTestUser; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(AndroidJUnit4.class) +public class SessionTests { + + private static String REALM_URI = "realm://objectserver.realm.io/~/default"; + + private Context context; + private AuthenticationServer authServer; + private SyncConfiguration configuration; + private User user; + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @Before + public void setUp() { + context = InstrumentationRegistry.getContext(); + user = createTestUser(); + authServer = new OkHttpAuthenticationServer(); + configuration = new SyncConfiguration.Builder(user, REALM_URI).build(); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void get_syncValues() { + SyncSession internalSession = new SyncSession( + configuration, + authServer, + configuration.getUser().getSyncUser(), + configuration.getSyncPolicy(), + configuration.getErrorHandler() + ); + Session session = new Session(internalSession); + + assertEquals("realm://objectserver.realm.io:80/JohnDoe/default", session.getServerUrl().toString()); + assertEquals(user, session.getUser()); + assertEquals(configuration, session.getConfiguration()); + assertNull(session.getState()); + } +} diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncManagerTests.java new file mode 100644 index 0000000000..1deaf9b2db --- /dev/null +++ b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncManagerTests.java @@ -0,0 +1,174 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.util.Collection; +import java.util.Set; + +import io.realm.rule.TestRealmConfigurationFactory; + +import static io.realm.util.SyncTestUtils.createTestUser; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class SyncManagerTests { + + private Context context; + private UserStore userStore; + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Before + public void setUp() { + context = InstrumentationRegistry.getContext(); + userStore = new UserStore() { + @Override + public User put(String key, User user) { + return null; + } + + @Override + public User get(String key) { + return null; + } + + @Override + public User remove(String key) { + return null; + } + + @Override + public Collection allUsers() { + return null; + } + }; + } + + @After + public void tearDown() { + } + + @Test + public void init() { + // Realm.init() calls SyncManager.init() wihich will start a thread for the sync client + boolean found = false; + Set threads = Thread.getAllStackTraces().keySet(); + for (Thread thread : threads) { + if (thread.getName().equals("RealmSyncClient")) { + found = true; + break; + } + } + assertTrue(found); + } + + @Test + public void set_userStore() { + SyncManager.setUserStore(userStore); + assertTrue(userStore.equals(SyncManager.getUserStore())); + } + + @Test(expected = IllegalArgumentException.class) + public void set_userStore_null() { + SyncManager.setUserStore(null); + } + + @Test + public void authListener() { + User user = createTestUser(); + final int[] counter = {0, 0}; + + AuthenticationListener authenticationListener = new AuthenticationListener() { + @Override + public void loggedIn(User user) { + counter[0]++; + } + + @Override + public void loggedOut(User user) { + counter[1]++; + } + }; + + SyncManager.addAuthenticationListener(authenticationListener); + SyncManager.notifyUserLoggedIn(user); + SyncManager.notifyUserLoggedOut(user); + assertEquals(1, counter[0]); + assertEquals(1, counter[1]); + } + + @Test(expected = IllegalArgumentException.class) + public void authListener_null() { + SyncManager.addAuthenticationListener(null); + } + + @Test + public void authListener_remove() { + User user = createTestUser(); + final int[] counter = {0, 0}; + + AuthenticationListener authenticationListener = new AuthenticationListener() { + @Override + public void loggedIn(User user) { + counter[0]++; + } + + @Override + public void loggedOut(User user) { + counter[1]++; + } + }; + + SyncManager.addAuthenticationListener(authenticationListener); + + SyncManager.removeAuthenticationListener(authenticationListener); + + SyncManager.notifyUserLoggedIn(user); + SyncManager.notifyUserLoggedOut(user); + + // no listener to update counters + assertEquals(0, counter[0]); + assertEquals(0, counter[1]); + } + + @Test + public void session() { + User user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config = new SyncConfiguration.Builder(user, url) + .build(); + + Session session = SyncManager.getSession(config); + assertEquals(user, session.getUser()); // see also SessionTests + } +} From 3befe85ab7c2db405e1f54e1d119562e14940724 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 23 Sep 2016 21:02:33 +0200 Subject: [PATCH 0092/2110] Fixed bug in path when getting access tokens for Realms (#157) --- .../objectserver/CounterActivity.java | 5 +- .../io/realm/AuthenticateRequestTests.java | 57 +++++++++++++++++++ .../src/main/java/io/realm/Realm.java | 4 +- .../src/objectServer/java/io/realm/User.java | 1 - .../internal/network/AuthenticateRequest.java | 24 +++++--- .../network/OkHttpAuthenticationServer.java | 6 +- .../objectserver/AuthenticatingState.java | 3 + .../realm/internal/objectserver/SyncUser.java | 49 ---------------- 8 files changed, 85 insertions(+), 64 deletions(-) create mode 100644 realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 4cfce022d5..229d41b25e 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -68,7 +68,10 @@ protected void onStart() { .initialData(new Realm.Transaction() { @Override public void execute(Realm realm) { - realm.createObject(CRDTCounter.class, 1); + // Workaround for initialData right now https://github.com/realm/realm-java-private/issues/164 + if (realm.isEmpty()) { + realm.createObject(CRDTCounter.class, 1); + } } }) .build(); diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java new file mode 100644 index 0000000000..bc274b886a --- /dev/null +++ b/realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java @@ -0,0 +1,57 @@ +package io.realm; + + +import android.support.test.runner.AndroidJUnit4; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.URI; +import java.net.URISyntaxException; + +import io.realm.internal.network.AuthenticateRequest; +import io.realm.internal.objectserver.Token; +import io.realm.util.SyncTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +@RunWith(AndroidJUnit4.class) +public class AuthenticateRequestTests { + + // Tests based on the schemas described here: https://github.com/realm/realm-sync-services/blob/master/doc/index.apib + + @Test + public void realmLogin() throws URISyntaxException, JSONException { + Token t = SyncTestUtils.createTestUser().getSyncUser().getUserToken(); + AuthenticateRequest request = AuthenticateRequest.realmLogin(t, new URI("realm://objectserver/" + t.value() + "/default")); + + JSONObject obj = new JSONObject(request.toJson()); + assertEquals("/" + t.value() + "/default", obj.get("path")); + assertEquals(t.value(), obj.get("data")); + assertEquals("realm", obj.get("provider")); + } + + @Test + public void userLogin() throws URISyntaxException, JSONException { + AuthenticateRequest request = AuthenticateRequest.userLogin(Credentials.facebook("foo")); + + JSONObject obj = new JSONObject(request.toJson()); + assertFalse(obj.has("path")); + assertEquals("foo", obj.get("data")); + assertEquals("facebook", obj.get("provider")); + } + + @Test + public void userRefresh() throws URISyntaxException, JSONException { + Token t = SyncTestUtils.createTestUser().getSyncUser().getUserToken(); + AuthenticateRequest request = AuthenticateRequest.userRefresh(t); + + JSONObject obj = new JSONObject(request.toJson()); + assertFalse(obj.has("path")); + assertEquals(t.value(), obj.get("data")); + assertEquals("realm", obj.get("provider")); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index d3dc9349af..1830681b20 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -23,8 +23,6 @@ import android.util.JsonReader; import android.util.Log; -import com.getkeepsafe.relinker.BuildConfig; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -188,7 +186,7 @@ public static synchronized void init(Context context) { throw new IllegalArgumentException("Non-null context required."); } RealmCore.loadLibrary(context); - RealmLog.add(BuildConfig.DEBUG ? new AndroidLogger(Log.DEBUG) : new AndroidLogger(Log.WARN)); + RealmLog.add(io.realm.BuildConfig.DEBUG ? new AndroidLogger(Log.DEBUG) : new AndroidLogger(Log.WARN)); defaultConfiguration = new RealmConfiguration.Builder(context).build(); ObjectServerFacade.getSyncFacadeIfPossible().init(context); BaseRealm.applicationContext = context.getApplicationContext(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/User.java b/realm/realm-library/src/objectServer/java/io/realm/User.java index be2a111e7c..439ea59494 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/User.java @@ -72,7 +72,6 @@ private User(SyncUser user) { public static User currentUser() { User user = SyncManager.getUserStore().get(UserStore.CURRENT_USER_KEY); if (user != null && user.isValid()) { - user.getSyncUser().scheduleRefresh(); return user; } return null; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java index 590599de2a..cd2a3f015d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java @@ -42,7 +42,7 @@ public class AuthenticateRequest { /** * Generates a proper login request for a new user. */ - public static AuthenticateRequest fromCredentials(Credentials credentials) { + public static AuthenticateRequest userLogin(Credentials credentials) { if (credentials == null) { throw new IllegalArgumentException("Non-null credentials required."); } @@ -54,16 +54,26 @@ public static AuthenticateRequest fromCredentials(Credentials credentials) { } /** - * Authenticate access to a given Realm using an already logged in user. - * - * @param refreshToken user's refresh token. + * Generates a request for refreshing a user token. */ - public static AuthenticateRequest fromRefreshToken(Token refreshToken) { + public static AuthenticateRequest userRefresh(Token userToken) { + return new AuthenticateRequest("realm", + userToken.value(), + SyncManager.APP_ID, + null, + Collections.emptyMap() + ); + } + + /** + * Generates a request for accessing a Realm + */ + public static AuthenticateRequest realmLogin(Token userToken, URI serverUrl) { // Authenticate a given Realm path using an already logged in user. return new AuthenticateRequest("realm", - refreshToken.value(), + userToken.value(), SyncManager.APP_ID, - refreshToken.path(), + serverUrl.getPath(), Collections.emptyMap() ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 137718674f..919c1eddea 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -49,7 +49,7 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { @Override public AuthenticateResponse loginUser(Credentials credentials, URL authenticationUrl) { try { - String requestBody = AuthenticateRequest.fromCredentials(credentials).toJson(); + String requestBody = AuthenticateRequest.userLogin(credentials).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { return AuthenticateResponse.from(new ObjectServerError(ErrorCode.OTHER_ERROR, Util.getStackTrace(e))); @@ -59,7 +59,7 @@ public AuthenticateResponse loginUser(Credentials credentials, URL authenticatio @Override public AuthenticateResponse loginToRealm(Token refreshToken, URI serverUrl, URL authenticationUrl) { try { - String requestBody = AuthenticateRequest.fromRefreshToken(refreshToken).toJson(); + String requestBody = AuthenticateRequest.realmLogin(refreshToken, serverUrl).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); @@ -69,7 +69,7 @@ public AuthenticateResponse loginToRealm(Token refreshToken, URI serverUrl, URL @Override public AuthenticateResponse refreshUser(Token userToken, URL authenticationUrl) { try { - String requestBody = AuthenticateRequest.fromRefreshToken(userToken).toJson(); + String requestBody = AuthenticateRequest.userRefresh(userToken).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java index e742799177..efbb49c224 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java @@ -20,6 +20,7 @@ import io.realm.Session; import io.realm.SessionState; import io.realm.internal.network.NetworkStateReceiver; +import io.realm.log.RealmLog; /** * AUTHENTICATING State. This step is needed if the user does not have proper access or credentials to access the @@ -100,11 +101,13 @@ private synchronized void authenticate(final SyncSession session) { session.authenticateRealm(new Runnable() { @Override public void run() { + RealmLog.debug("Session[%s]: Access token acquired", session.getConfiguration().getPath()); gotoNextState(SessionState.BINDING); } }, new Session.ErrorHandler() { @Override public void onError(Session s, ObjectServerError error) { + RealmLog.debug("Session[%s]: Failed to get access token (%d)", session.getConfiguration().getPath(), error.getErrorCode()); session.onError(error); } }); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java index e4a4ff0420..67f95fb3e0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java @@ -14,8 +14,6 @@ * limitations under the License. */ -import android.os.SystemClock; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -27,19 +25,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import io.realm.RealmAsyncTask; import io.realm.Session; import io.realm.SyncConfiguration; -import io.realm.SyncManager; import io.realm.User; -import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.internal.network.AuthenticationServer; -import io.realm.internal.network.ExponentialBackoffTask; -import io.realm.log.RealmLog; /** * Internal representation of a user on the Realm Object Server. @@ -47,17 +38,11 @@ */ public class SyncUser { - // Time left on current refresh token, before we want to begin refreshing it. - // Failing to refresh it before it expires, will result in the user no longer being valid, and not being able - // to synchronize changes. It will still be possible to open Realms and read their data. - private final long REFRESH_WINDOW_MS = TimeUnit.SECONDS.toMillis(5); - private final String identity; private Token refreshToken; private URL authenticationUrl; private Map realms = new HashMap(); private List sessions = new ArrayList(); - private RealmAsyncTask refreshTask; private boolean loggedIn; /** @@ -72,36 +57,6 @@ public SyncUser(Token refreshToken, URL authenticationUrl) { public void setRefreshToken(final Token refreshToken) { this.refreshToken = refreshToken; // Replace any existing token. TODO re-save the user with latest token. - scheduleRefresh(); - } - - // Schedule a refresh. This method cannot fail, but will continue retrying until either the app is killed - // or the attempt was successful. - // We should probably optimize this. See https://github.com/realm/realm-java-private/issues/140 - public void scheduleRefresh() { - final long expire = refreshToken.expiresMs(); - final AuthenticationServer server = SyncManager.getAuthServer(); - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { - @Override - protected AuthenticateResponse execute() { - long timeToExpiration = System.currentTimeMillis() - expire; - if (timeToExpiration - REFRESH_WINDOW_MS > 0) { - SystemClock.sleep(timeToExpiration); - } - return server.refreshUser(refreshToken, authenticationUrl); - } - - @Override - protected void onSuccess(AuthenticateResponse response) { - setRefreshToken(response.getRefreshToken()); - } - - @Override - protected void onError(AuthenticateResponse response) { - RealmLog.warn("Failed refreshing a user.\n" + response.getError().toString()); - } - }); - refreshTask = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); } /** @@ -190,10 +145,6 @@ public List getSessions() { public void clearTokens() { realms.clear(); refreshToken = null; - if (refreshTask != null) { - refreshTask.cancel(); - refreshTask = null; - } } public boolean isLoggedIn() { From 4f67489d48a3ee075e0b130c91cbda4b2b88002a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 23 Sep 2016 22:08:36 +0200 Subject: [PATCH 0093/2110] Upgrade to latest core+sync release (#167) --- realm/realm-library/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 5e8135f351..fb4a7ca8bd 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '1.0.0-beta-37.0' +ext.coreVersion = '1.0.0-beta-37.1' // empty or comment out this to disable hash checking -ext.coreSha256Hash = '495cd28446e57f4565780dd83a859e4cbc0d60907844f902ae4f5233dc47a8ba' +ext.coreSha256Hash = '226270a563fddc7e8512d650e644b9863cce7f4fba58d51b638007fe3ad7ccb9' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from From 99554991e6ed0c45a2582b582221cd0609a93c9d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 23 Sep 2016 22:18:47 +0200 Subject: [PATCH 0094/2110] Bumped version to match expected release number --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index cecea37b79..420ca2e62c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.0-beta-33.0-SNAPSHOT +2.0.0-BETA1-SNAPSHOT \ No newline at end of file From 3b00c5fe262a95062c123b2b8e2900bff4a629bb Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 23 Sep 2016 22:59:27 +0100 Subject: [PATCH 0095/2110] Nh/android key store (#138) * Add a crypto capability to store a Token/User securely. --- .../objectserver/CounterActivity.java | 1 - .../secureTokenAndroidKeyStore/build.gradle | 37 ++ examples/secureTokenAndroidKeyStore/lint.xml | 5 + .../proguard-rules.pro | 17 + .../src/main/AndroidManifest.xml | 15 + .../MainActivity.java | 136 ++++++++ .../src/main/res/layout/activity_main.xml | 18 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4906 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2968 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7076 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11165 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 16078 bytes .../src/main/res/values/colors.xml | 8 + .../src/main/res/values/strings.xml | 5 + .../src/main/res/values/styles.xml | 11 + examples/settings.gradle | 1 + .../src/main/res/values-w820dp/dimens.xml | 6 - .../java/io/realm/android/UserStoreTest.java | 70 ++++ .../java/io/realm/SyncConfiguration.java | 1 - .../io/realm/android/SecureUserStore.java | 162 +++++++++ .../internal/android/crypto/CipherClient.java | 103 ++++++ .../android/crypto/CipherFactory.java | 53 +++ .../internal/android/crypto/SyncCrypto.java | 33 ++ .../android/crypto/SyncCryptoFactory.java | 44 +++ .../crypto/api_18/SyncCryptoApi18Impl.java | 316 ++++++++++++++++++ .../crypto/api_legacy/SyncCryptoLegacy.java | 262 +++++++++++++++ .../android/crypto/ciper/CipherJB.java | 31 ++ .../android/crypto/ciper/CipherLegacy.java | 31 ++ .../android/crypto/ciper/CipherMM.java | 31 ++ .../internal/android/crypto/misc/Base64.java | 31 ++ .../android/crypto/misc/PRNGFixes.java | 94 ++++++ .../realm/internal/objectserver/SyncUser.java | 4 +- 32 files changed, 1517 insertions(+), 9 deletions(-) create mode 100644 examples/secureTokenAndroidKeyStore/build.gradle create mode 100644 examples/secureTokenAndroidKeyStore/lint.xml create mode 100644 examples/secureTokenAndroidKeyStore/proguard-rules.pro create mode 100644 examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml create mode 100644 examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java create mode 100644 examples/secureTokenAndroidKeyStore/src/main/res/layout/activity_main.xml create mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 examples/secureTokenAndroidKeyStore/src/main/res/values/colors.xml create mode 100644 examples/secureTokenAndroidKeyStore/src/main/res/values/strings.xml create mode 100644 examples/secureTokenAndroidKeyStore/src/main/res/values/styles.xml delete mode 100644 examples/threadExample/src/main/res/values-w820dp/dimens.xml create mode 100644 realm/realm-library/src/androidTestobjectServer/java/io/realm/android/UserStoreTest.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherFactory.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCrypto.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCryptoFactory.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_18/SyncCryptoApi18Impl.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_legacy/SyncCryptoLegacy.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherJB.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherLegacy.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherMM.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/Base64.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/PRNGFixes.java diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 229d41b25e..5537ca1d65 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -43,7 +43,6 @@ public class CounterActivity extends AppCompatActivity { private RealmResults counter; private User user; - @BindView(R.id.text_counter) TextView counterView; @Override diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle new file mode 100644 index 0000000000..7c3f9f7f96 --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'com.android.application' +apply plugin: 'realm-android' + +android { + compileSdkVersion 24 + buildToolsVersion "24.0.0" + + defaultConfig { + applicationId "examples.realm.io.securetokenandroidkeystore" + minSdkVersion 9 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:24.2.0' + testCompile 'junit:junit:4.12' +} + +realm { + syncEnabled = true +} \ No newline at end of file diff --git a/examples/secureTokenAndroidKeyStore/lint.xml b/examples/secureTokenAndroidKeyStore/lint.xml new file mode 100644 index 0000000000..5f242796c5 --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/lint.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/secureTokenAndroidKeyStore/proguard-rules.pro b/examples/secureTokenAndroidKeyStore/proguard-rules.pro new file mode 100644 index 0000000000..740907a636 --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/Nabil/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml b/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..89f0a5e281 --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java b/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java new file mode 100644 index 0000000000..c570b81c2f --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java @@ -0,0 +1,136 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package examples.io.realm.securetokenandroidkeystore.securetokenandroidkeystore; + +import android.os.Bundle; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.widget.TextView; + +import com.example.securetokenandroidkeystore.R; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.security.KeyStoreException; +import java.util.UUID; + +import io.realm.Realm; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.User; +import io.realm.android.SecureUserStore; +import io.realm.internal.android.crypto.CipherClient; +import io.realm.internal.objectserver.SyncUser; +import io.realm.internal.objectserver.Token; + +/** + * Activity responsible of unlocking the KeyStore + * before using the {@link io.realm.android.SecureUserStore} to encrypt + * the Token we get from the session + */ +public class MainActivity extends AppCompatActivity { + private CipherClient cryptoClient; + private TextView txtKeystoreState; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + txtKeystoreState = (TextView) findViewById(R.id.txtLabelKeyStore); + + try { + cryptoClient = new CipherClient(this); + if (cryptoClient.isKeystoreUnlocked()) { + buildSyncConf(); + keystoreUnlockedMessage(); + } else { + cryptoClient.unlockKeystore(); + } + } catch (KeyStoreException e) { + e.printStackTrace(); + } + } + + @Override + protected void onResume() { + super.onResume(); + try { + // We return to the app after the KeyStore is unlocked or not. + if (cryptoClient.isKeystoreUnlocked()) { + buildSyncConf(); + keystoreUnlockedMessage (); + } else { + keystoreLockedMessage (); + } + } catch (KeyStoreException e) { + e.printStackTrace(); + } + } + + // build SyncConfiguration with a user store to store encrypted Token. + private void buildSyncConf () { + try { + SyncManager.setUserStore(new SecureUserStore(MainActivity.this)); + // the rest of Sync logic ... + User user = createTestUser(0); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); + Realm realm = Realm.getInstance(secureConfig); + // ... + + } catch (KeyStoreException e) { + e.printStackTrace(); + } + } + // Helpers + private final static String USER_TOKEN = UUID.randomUUID().toString(); + private final static String REALM_TOKEN = UUID.randomUUID().toString(); + + private static User createTestUser(long expires) { + Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); + Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); + SyncUser.AccessDescription desc = new SyncUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); + + JSONObject obj = new JSONObject(); + try { + JSONArray realmList = new JSONArray(); + JSONObject realmDesc = new JSONObject(); + realmDesc.put("uri", "realm://objectserver.realm.io/default"); + realmDesc.put("description", desc.toJson()); + realmList.put(realmDesc); + + obj.put("authUrl", "http://objectserver.realm.io/auth"); + obj.put("userToken", userToken.toJson()); + obj.put("realms", realmList); + return User.fromJson(obj.toString()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + private void keystoreLockedMessage () { + txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorLocked)); + txtKeystoreState.setText(R.string.locked_text); + } + + private void keystoreUnlockedMessage () { + txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorActivated)); + txtKeystoreState.setText(R.string.unlocked_text); + } +} + diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/layout/activity_main.xml b/examples/secureTokenAndroidKeyStore/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..eff5fd5e7b --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/secureTokenAndroidKeyStore/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..58303aff5b97f3c5a1757573ef73347d3475e16c GIT binary patch literal 4906 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 O0000 literal 0 HcmV?d00001 diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..eb9ece04b26b69f1d98f9294716e8c982a4577b9 GIT binary patch literal 11165 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 + + #3F51B5 + #303F9F + #FF4081 + #00CC00 + #CC0000 + diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/values/strings.xml b/examples/secureTokenAndroidKeyStore/src/main/res/values/strings.xml new file mode 100644 index 0000000000..d028dcda8a --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + secureTokenAndroidKeyStore + Key Store Locked/Uninitialised:\nYou can not encrypt the Token + Key Store Unlocked:\nYou can encrypt the Token + diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/values/styles.xml b/examples/secureTokenAndroidKeyStore/src/main/res/values/styles.xml new file mode 100644 index 0000000000..daa2a5c2f0 --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/examples/settings.gradle b/examples/settings.gradle index d9922ed59e..0f9f5242bd 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -1,3 +1,4 @@ +include 'secureTokenAndroidKeyStore' include 'encryptionExample' include 'gridViewExample' include 'introExample' diff --git a/examples/threadExample/src/main/res/values-w820dp/dimens.xml b/examples/threadExample/src/main/res/values-w820dp/dimens.xml deleted file mode 100644 index 63fc816444..0000000000 --- a/examples/threadExample/src/main/res/values-w820dp/dimens.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 64dp - diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/android/UserStoreTest.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/android/UserStoreTest.java new file mode 100644 index 0000000000..b55d740044 --- /dev/null +++ b/realm/realm-library/src/androidTestobjectServer/java/io/realm/android/UserStoreTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.android; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.security.KeyStoreException; + +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.User; +import io.realm.UserStore; +import io.realm.android.SecureUserStore; +import io.realm.rule.TestRealmConfigurationFactory; + +import static io.realm.util.SyncTestUtils.createTestUser; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(AndroidJUnit4.class) +public class UserStoreTest { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + private Realm realm; + + @Before + public void setUp() { + RealmConfiguration realmConfig = configFactory.createConfiguration(); + realm = Realm.getInstance(realmConfig); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + @Test + public void encrypt_decrypt_UsingAndroidKeyStoreUserStore() throws KeyStoreException { + User user = createTestUser(); + UserStore userStore = new SecureUserStore(InstrumentationRegistry.getTargetContext()); + User savedUser = userStore.put("crypted_entry", user); + assertNull(savedUser); + User decrypted_entry = userStore.get("crypted_entry"); + assertEquals(user, decrypted_entry); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 1600cd2ec5..765cfaee56 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -123,7 +123,6 @@ private SyncConfiguration(File directory, this.deleteRealmOnLogout = deleteRealmOnLogout; } - static URI resolveServerUrl(URI serverUrl, String userIdentifier) { try { return new URI(serverUrl.toString().replace("/~/", "/" + userIdentifier + "/")); diff --git a/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java new file mode 100644 index 0000000000..7aedf01db3 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java @@ -0,0 +1,162 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.android; + +import android.content.Context; +import android.content.SharedPreferences; + +import java.security.KeyStoreException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import io.realm.User; +import io.realm.UserStore; +import io.realm.internal.android.crypto.CipherClient; + +/** + * Encrypt and decrypt the token ({@link User}) using Android built in KeyStore capabilities. + * According to the Android API this picks the right algorithm to perfom the operations. + * Prior to API 18 there were no AndroidKeyStore API, but the UNIX deamon existed to it's possible + * with the help of this code: https://github.com/nelenkov/android-keystore. + * + * On API > = 18, we generate an AES key to encrypt we then generate and uses the RSA key inside the KeyStore + * to encrypt the AES key that we store along the encrypted data inside a private {@link android.content.SharedPreferences}. + * + * This throws a {@link java.security.KeyStoreException} in case of an error or KeyStore being unvailable (unlocked). + * + * See also: io.realm.internal.android.crypto.class.CipherClient + * @see Android KeyStore + */ +public class SecureUserStore implements UserStore { + private static final String REALM_OBJECT_SERVER_USERS = "realm_object_server_users"; + private final CipherClient cipherClient; + private final SharedPreferences sp; + private User cachedCurrentUser; // Keep a quick reference to the current user + + public SecureUserStore(final Context context) throws KeyStoreException { + cipherClient = new CipherClient(context); + sp = context.getSharedPreferences(REALM_OBJECT_SERVER_USERS, Context.MODE_PRIVATE); + } + + /** + * Store user as serialised and encrypted (Json), inside the private {@link android.content.SharedPreferences}. + * @param key the {@link android.content.SharedPreferences} key. + * @param user we want to save. + * @return The previous user saved with this key or {@code null} if no user was replaced. + */ + @Override + public User put(String key, User user) { + String previousUser = sp.getString(key, null); + SharedPreferences.Editor editor = sp.edit(); + String userSerialisedAndEncrypted; + try { + userSerialisedAndEncrypted = cipherClient.encrypt(user.toJson()); + } catch (KeyStoreException e) { + e.printStackTrace(); + return null; + } + editor.putString(key, userSerialisedAndEncrypted); + // Optimistically save. If the user isn't saved due to a process crash it isn't dangerous. + editor.apply(); + + if (UserStore.CURRENT_USER_KEY.equals(key)) { + cachedCurrentUser = user; + } + if (previousUser != null) { + try { + String userSerialisedAndDecrypted = cipherClient.decrypt(previousUser); + return User.fromJson(userSerialisedAndDecrypted); + } catch (KeyStoreException e) { + e.printStackTrace(); + return null; + } + } else { + return null; + } + } + + /** + * Retrieves the {@link User} by decrypting first the serialised Json. + * @param key the {@link android.content.SharedPreferences} key. + * @return the {@link User} with the given key. + */ + @Override + public User get(String key) { + if (key.equals(UserStore.CURRENT_USER_KEY) && cachedCurrentUser != null) { + return cachedCurrentUser; + } + + String userData = sp.getString(key, ""); + if (userData.equals("")) { + return null; + } + + try { + String userSerialisedAndDecrypted = cipherClient.decrypt(userData); + User user = User.fromJson(userSerialisedAndDecrypted); + if (UserStore.CURRENT_USER_KEY.equals(key)) { + cachedCurrentUser = user; + } + return user; + } catch (KeyStoreException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public User remove(String key) { + String currentUser = sp.getString(key, null); + SharedPreferences.Editor editor = sp.edit(); + editor.putString(key, null); + editor.apply(); + + if (UserStore.CURRENT_USER_KEY.equals(key) && cachedCurrentUser != null) { + cachedCurrentUser = null; + } + + if (currentUser != null) { + try { + String userSerialisedAndDecrypted = cipherClient.decrypt(currentUser); + return User.fromJson(userSerialisedAndDecrypted); + } catch (KeyStoreException e) { + e.printStackTrace(); + return null; + } + } else { + return null; + } + } + + @Override + public Collection allUsers() { + Map all = sp.getAll(); + ArrayList users = new ArrayList(all.size()); + for (Object userJson : all.values()) { + String userSerialisedAndDecrypted = null; + try { + userSerialisedAndDecrypted = cipherClient.decrypt((String) userJson); + } catch (KeyStoreException e) { + e.printStackTrace(); + // returning null will probably penalise the other Users + } + users.add(User.fromJson(userSerialisedAndDecrypted)); + } + return users; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java new file mode 100644 index 0000000000..9253072b2d --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java @@ -0,0 +1,103 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto; + +import android.content.Context; + +import java.security.KeyStoreException; + +/** + * A Helper to use the crypto API, it allows encryption/decryption and has methods to help test if the KeyStore is locked and help unlocked it. + * This hides the complexity of different Android API to achieve those operations. + * + * This support Android API 9 and forwards. + * This cipher uses the KeyStore provided by Android, hence we to need to be sure that the KeyStore is available + * before doing any {@link #encrypt(String)}/{@link #decrypt(String)} by calling {@link #isKeystoreUnlocked()} then + * {@link #unlockKeystore()}, note that the latter will open the system {@link android.app.Activity} to set a passowrd/PIN/Pattern required + * to unlock the sceen & the KeyStore. + */ +public class CipherClient { + private SyncCrypto syncCrypto; + + public CipherClient(Context context) throws KeyStoreException { + syncCrypto = SyncCryptoFactory.get(context); + } + + /** + * Takes some plain text {@link String} and return the encrypted version + * of this {@link String} using the Android Key Store. + * + * @param user represents the Token of a {@link io.realm.User}. + * @return the encrypted Token. + * @throws KeyStoreException in case the Key Store is locked or other error. + */ + public String encrypt(String user) throws KeyStoreException { + if (syncCrypto.is_keystore_unlocked()) { + try { + syncCrypto.create_key(); + String encrypted = syncCrypto.encrypt(user); + return encrypted; + } catch (KeyStoreException ex) { + throw new KeyStoreException(ex); + } + } else { + throw new KeyStoreException("Trying to use SecureUserStore without an unlocked KeyStore"); + } + } + + /** + * Takes a previously {@link #encrypt(String)} to decrypted it + * using the Android Key Store. + * + * @param user_encrypted represents the encrypted Token of a {@link io.realm.User}. + * @return the decrypted Token. + * @throws KeyStoreException in case the KeyStore is locked or other error. + */ + public String decrypt(String user_encrypted) throws KeyStoreException { + if (syncCrypto.is_keystore_unlocked()) { + try { + String decrypted = syncCrypto.decrypt(user_encrypted); + return decrypted; + } catch (KeyStoreException ex) { + throw new KeyStoreException(ex); + } + } else { + throw new KeyStoreException("Trying to use SecureUserStore without an unlocked KeyStore"); + } + } + + + /** + * Checks whether the Android KeyStore is available. + * This should be called before {@link #encrypt(String)} or {@link #decrypt(String)} as those need the KeyStore unlocked. + * @return {@code true} if the Android KeyStore in unlocked. + * @throws KeyStoreException in case of error. + */ + public boolean isKeystoreUnlocked () throws KeyStoreException { + return syncCrypto.is_keystore_unlocked(); + } + + /** + * Helps unlock the KeyStore this will launch the appropriate {@link android.content.Intent} + * to start the platform system {@link android.app.Activity} to create/unlock the KeyStore. + * + * @throws KeyStoreException in case of error. + */ + public void unlockKeystore () throws KeyStoreException { + syncCrypto.unlock_keystore(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherFactory.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherFactory.java new file mode 100644 index 0000000000..453f35c56a --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto; + +import android.os.Build; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; + +import io.realm.internal.android.crypto.ciper.CipherJB; +import io.realm.internal.android.crypto.ciper.CipherLegacy; +import io.realm.internal.android.crypto.ciper.CipherMM; + + +/** + * Return an appropriate {@link Cipher} given the version of Android. + * Ex: on API 23 OpenSSL is replaced by BoringSSL. + */ +public class CipherFactory { + + private static final boolean IS_JB43 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; + private static final boolean IS_MM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + private static final boolean IS_GINGERBREAD = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; + + public static Cipher get() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { + if (IS_MM) { + return CipherMM.get(); + } else if (IS_JB43) { + return CipherJB.get(); + } else if (IS_GINGERBREAD) { + return CipherLegacy.get(); + } else { + throw new IllegalArgumentException("Not supported yet"); + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCrypto.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCrypto.java new file mode 100644 index 0000000000..cd3c63beeb --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCrypto.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto; + +import java.security.KeyStoreException; + +/** + * Define methods that Android API should expose regardless of the API version. + */ +public interface SyncCrypto { + String encrypt(String plainText) throws KeyStoreException; + String decrypt(String cipherText) throws KeyStoreException; + void create_key() throws KeyStoreException; + + // User is responsible of unlocking the keystore, we expose these methods as + // a helper. + boolean is_keystore_unlocked() throws KeyStoreException; + void unlock_keystore() throws KeyStoreException; +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCryptoFactory.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCryptoFactory.java new file mode 100644 index 0000000000..9741aa73bf --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCryptoFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto; + +import android.content.Context; +import android.os.Build; + +import java.security.KeyStoreException; + +import io.realm.internal.android.crypto.api_18.SyncCryptoApi18Impl; +import io.realm.internal.android.crypto.api_legacy.SyncCryptoLegacy; + +/** + * Return an appropriate {@link SyncCrypto} given the version of Android. + */ +public class SyncCryptoFactory { + + private static final boolean IS_JB43 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; + private static final boolean IS_GINGERBREAD = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; + + public static SyncCrypto get (Context context) throws KeyStoreException { + if (IS_JB43) { + return new SyncCryptoApi18Impl(context); + } else if (IS_GINGERBREAD) { + return new SyncCryptoLegacy(context); + } else { + throw new KeyStoreException("Unknown android version"); + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_18/SyncCryptoApi18Impl.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_18/SyncCryptoApi18Impl.java new file mode 100644 index 0000000000..1551241c98 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_18/SyncCryptoApi18Impl.java @@ -0,0 +1,316 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto.api_18; + +import android.annotation.TargetApi; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.security.KeyPairGeneratorSpec; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyPairGenerator; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Calendar; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import javax.security.auth.x500.X500Principal; + + +import io.realm.internal.android.crypto.CipherFactory; +import io.realm.internal.android.crypto.SyncCrypto; +import io.realm.internal.android.crypto.misc.Base64; +import io.realm.internal.android.crypto.misc.PRNGFixes; + +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; + +/** + * Implements {@link SyncCrypto} methods for API 18 (after the Android KeyStore public API). + */ +public class SyncCryptoApi18Impl implements SyncCrypto { + private java.security.KeyStore keyStore; + private Context context; + private String alias = "Realm"; + private static String X500Principal = "CN=Sync, O=Realm"; + private final static String DELIMITER = "]"; + + public static final String UNLOCK_ACTION = "com.android.credentials.UNLOCK"; + + private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; + + public SyncCryptoApi18Impl (Context context) throws KeyStoreException { + PRNGFixes.apply(); + this.context = context; + try { + keyStore = java.security.KeyStore.getInstance(ANDROID_KEYSTORE); + keyStore.load(null); + } catch (KeyStoreException e) { + e.printStackTrace(); + throw new KeyStoreException(e); + } catch (CertificateException e) { + e.printStackTrace(); + throw new KeyStoreException(e); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + throw new KeyStoreException(e); + } catch (IOException e) { + e.printStackTrace(); + throw new KeyStoreException(e); + } + } + + @Override + public String encrypt(String plainText) throws KeyStoreException { + try { + SecretKey key = generateAESKey(); + byte[] encrypted = encryptedUsingAESKey(key, plainText); + byte[] encryptedKey = encryptAESKeyUsingRSA(key); + // append with AES enc with RSA + return String.format("%s%s%s", Base64.to(encryptedKey), DELIMITER, + Base64.to(encrypted)); + } catch (Exception e) { + throw new KeyStoreException(e); + } + } + + @Override + public String decrypt(String cipherText) throws KeyStoreException { + try { + String[] fields = cipherText.split(DELIMITER); + if (fields.length != 2) { + throw new IllegalArgumentException("Invalid encrypted text format"); + } + + byte[] aesEncWithRSA = Base64.from(fields[0]); + byte[] encToken = Base64.from(fields[1]); + + // decrypt AES using RSA + SecretKey key = decrypytAESKeyUsingRSA(aesEncWithRSA); + + // decrypt Token using decrypted AES + return decryptedUsingAESKey(key, encToken); + } catch (Exception e) { + throw new KeyStoreException(e); + } + } + + @Override + public boolean is_keystore_unlocked() throws KeyStoreException { + try { + Class keyStoreClass = Class.forName("android.security.KeyStore"); + Method getInstanceMethod = keyStoreClass.getMethod("getInstance"); + Object invoke = getInstanceMethod.invoke(null); + + Method isUnlockedMethod = keyStoreClass.getMethod("isUnlocked"); + boolean isUnlocked = (boolean)isUnlockedMethod.invoke(invoke); + return isUnlocked; + } catch (ClassNotFoundException e) { + throw new KeyStoreException(e); + } catch (NoSuchMethodException e) { + throw new KeyStoreException(e); + } catch (IllegalAccessException e) { + throw new KeyStoreException(e); + } catch (InvocationTargetException e) { + throw new KeyStoreException(e); + } + } + + @Override + public void unlock_keystore() throws KeyStoreException { + try { + Intent intent = new Intent(UNLOCK_ACTION); + intent.addFlags(FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + throw new KeyStoreException(e); + } + } + + @TargetApi(18) + public void create_key() throws KeyStoreException { + try { + // Create new key. + // Avoid a known bug in Api 23 where we need names in KeyStore to be unique + // http://stackoverflow.com/questions/23977407/android-4-3-keystore-chain-null-while-trying-to-retrieve-keys + if (keyStore.containsAlias(alias)) { + keyStore.deleteEntry(alias); + } + Calendar start = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + end.add(Calendar.YEAR, 1); + KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) + .setAlias(alias) + .setSubject(new X500Principal(X500Principal)) + .setSerialNumber(BigInteger.ONE) + .setStartDate(start.getTime()) + .setEndDate(end.getTime()) + .build(); + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", + "AndroidKeyStore"); + generator.initialize(spec); + generator.generateKeyPair(); + } catch (Exception e) { + throw new KeyStoreException(e); + } + } + + private SecretKey generateAESKey() throws NoSuchAlgorithmException { + // Generate a 256-bit key + final int outputKeyLength = 256; + + SecureRandom secureRandom = new SecureRandom(); + // Do *not* seed secureRandom! Automatically seeded from system entropy. + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(outputKeyLength, secureRandom); + SecretKey key = keyGenerator.generateKey(); + return key; + } + + private byte[] encryptedUsingAESKey(SecretKey key, String plainText) throws KeyStoreException { + try { + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, key); + return cipher.doFinal(plainText.getBytes("UTF-8")); + } catch (NoSuchAlgorithmException e) { + throw new KeyStoreException(e); + } catch (NoSuchPaddingException e) { + throw new KeyStoreException(e); + } catch (BadPaddingException e) { + throw new KeyStoreException(e); + } catch (UnsupportedEncodingException e) { + throw new KeyStoreException(e); + } catch (IllegalBlockSizeException e) { + throw new KeyStoreException(e); + } catch (InvalidKeyException e) { + throw new KeyStoreException(e); + } + } + + private String decryptedUsingAESKey(SecretKey key, byte[] cipherText) throws KeyStoreException { + try { + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] encrypted = cipher.doFinal(cipherText); + return new String(encrypted, "UTF-8"); + } catch (NoSuchAlgorithmException e) { + throw new KeyStoreException(e); + } catch (NoSuchPaddingException e) { + throw new KeyStoreException(e); + } catch (BadPaddingException e) { + throw new KeyStoreException(e); + } catch (UnsupportedEncodingException e) { + throw new KeyStoreException(e); + } catch (IllegalBlockSizeException e) { + throw new KeyStoreException(e); + } catch (InvalidKeyException e) { + throw new KeyStoreException(e); + } + } + + private byte[] encryptAESKeyUsingRSA(SecretKey key) throws KeyStoreException { + try { + java.security.KeyStore.PrivateKeyEntry privateKeyEntry = (java.security.KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null); + RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey(); + + Cipher cipher = CipherFactory.get(); + + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher); + cipherOutputStream.write(key.getEncoded()); + cipherOutputStream.close(); + + return outputStream.toByteArray(); + } catch (NoSuchPaddingException e) { + throw new KeyStoreException(e); + } catch (NoSuchAlgorithmException e) { + throw new KeyStoreException(e); + } catch (NoSuchProviderException e) { + throw new KeyStoreException(e); + } catch (InvalidKeyException e) { + throw new KeyStoreException(e); + } catch (KeyStoreException e) { + throw new KeyStoreException(e); + } catch (UnrecoverableEntryException e) { + throw new KeyStoreException(e); + } catch (IOException e) { + throw new KeyStoreException(e); + } + } + + private SecretKeySpec decrypytAESKeyUsingRSA(byte[] aesEncKey) throws KeyStoreException { + try { + java.security.KeyStore.PrivateKeyEntry privateKeyEntry = (java.security.KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null); + Cipher cipher = CipherFactory.get(); + cipher.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey()); + CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(aesEncKey), cipher); + + ArrayList values = new ArrayList<>(); + int nextByte; + while ((nextByte = cipherInputStream.read()) != -1) { + values.add((byte)nextByte); + } + + final byte[] bytes = new byte[values.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = values.get(i).byteValue(); + } + + SecretKeySpec originalKey = new SecretKeySpec(bytes, "AES"); + return originalKey; + } catch (NoSuchPaddingException e) { + throw new KeyStoreException(e); + } catch (NoSuchAlgorithmException e) { + throw new KeyStoreException(e); + } catch (NoSuchProviderException e) { + throw new KeyStoreException(e); + } catch (UnsupportedEncodingException e) { + throw new KeyStoreException(e); + } catch (IOException e) { + throw new KeyStoreException(e); + } catch (InvalidKeyException e) { + throw new KeyStoreException(e); + } catch (UnrecoverableEntryException e) { + throw new KeyStoreException(e); + } catch (KeyStoreException e) { + throw new KeyStoreException(e); + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_legacy/SyncCryptoLegacy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_legacy/SyncCryptoLegacy.java new file mode 100644 index 0000000000..de8c5836fc --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_legacy/SyncCryptoLegacy.java @@ -0,0 +1,262 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto.api_legacy; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.KeyStoreException; +import java.security.SecureRandom; +import java.util.ArrayList; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + + +import io.realm.internal.android.crypto.CipherFactory; +import io.realm.internal.android.crypto.SyncCrypto; +import io.realm.internal.android.crypto.misc.Base64; +import io.realm.internal.android.crypto.misc.PRNGFixes; + +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; + +/** + * Implements {@link SyncCrypto} methods for API 9 to 18 (pre Android KeyStore public API). + */ +public class SyncCryptoLegacy implements SyncCrypto { + private Context context; + private SecretKey key; + private String alias = "Realm"; + private int mError = NO_ERROR; + private SecureRandom random = new SecureRandom(); + + private static final String UNLOCK_ACTION = "android.credentials.UNLOCK"; + + // ResponseCodes + private static final int NO_ERROR = 1; + private static final int LOCKED = 2; + private static final int UNINITIALIZED = 3; + private static final int PROTOCOL_ERROR = 5; + + // States + private enum State { + UNLOCKED, LOCKED, UNINITIALIZED + }; + + private static final LocalSocketAddress sAddress = new LocalSocketAddress( + "keystore", LocalSocketAddress.Namespace.RESERVED); + private final static int KEY_LENGTH = 256; + private final static String DELIMITER = "]"; + + public SyncCryptoLegacy (Context context) throws KeyStoreException { + PRNGFixes.apply(); + this.context = context; + } + + @Override + public String encrypt(String plainText) throws KeyStoreException { + try { + Cipher cipher = CipherFactory.get(); + + byte[] iv = generateIv(cipher.getBlockSize()); + IvParameterSpec ivParams = new IvParameterSpec(iv); + cipher.init(Cipher.ENCRYPT_MODE, key, ivParams); + byte[] cipherText = cipher.doFinal(plainText.getBytes("UTF-8")); + + return String.format("%s%s%s", Base64.to(iv), DELIMITER, + Base64.to(cipherText)); + } catch (GeneralSecurityException e) { + throw new KeyStoreException(e); + } catch (UnsupportedEncodingException e) { + throw new KeyStoreException(e); + } + } + + @Override + public String decrypt(String cipherText) throws KeyStoreException { + byte[] keyBytes = get(alias); + if (keyBytes == null) { + return null; + } + SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); + + try { + String[] fields = cipherText.split(DELIMITER); + if (fields.length != 2) { + throw new IllegalArgumentException("Invalid encrypted text format"); + } + + byte[] iv = Base64.from(fields[0]); + byte[] cipherBytes = Base64.from(fields[1]); + Cipher cipher = CipherFactory.get(); + IvParameterSpec ivParams = new IvParameterSpec(iv); + cipher.init(Cipher.DECRYPT_MODE, key, ivParams); + byte[] plaintext = cipher.doFinal(cipherBytes); + return new String(plaintext, "UTF-8"); + } catch (GeneralSecurityException e) { + throw new KeyStoreException(e); + } catch (UnsupportedEncodingException e) { + throw new KeyStoreException(e); + } + } + + @Override + public boolean is_keystore_unlocked() throws KeyStoreException { + return state() == State.UNLOCKED; + } + + @Override + public void unlock_keystore() throws KeyStoreException { + try { + Intent intent = new Intent(UNLOCK_ACTION); + intent.addFlags(FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + throw new KeyStoreException(e); + } + } + + @Override + public void create_key() throws KeyStoreException { + try { + KeyGenerator kg = KeyGenerator.getInstance("AES"); + kg.init(KEY_LENGTH); + key = kg.generateKey(); + + boolean success = put(getBytes(alias), key.getEncoded()); + if (!success) { + throw new KeyStoreException("Keystore error"); + } + } catch (Exception e) { + throw new KeyStoreException(e); + } + } + + private State state() throws KeyStoreException { + execute('t'); + switch (mError) { + case NO_ERROR: + return State.UNLOCKED; + case LOCKED: + return State.LOCKED; + case UNINITIALIZED: + return State.UNINITIALIZED; + default: + throw new KeyStoreException("" + mError); + } + } + + private byte[] get(byte[] key) { + ArrayList values = execute('g', key); + return (values == null || values.isEmpty()) ? null : values.get(0); + } + + private byte[] get(String key) { + return get(getBytes(key)); + } + + private boolean put(byte[] key, byte[] value) { + execute('i', key, value); + return mError == NO_ERROR; + } + + private ArrayList execute(int code, byte[]... parameters) { + mError = PROTOCOL_ERROR; + + for (byte[] parameter : parameters) { + if (parameter == null || parameter.length > 65535) { + return null; + } + } + + LocalSocket socket = new LocalSocket(); + try { + socket.connect(sAddress); + + OutputStream out = socket.getOutputStream(); + out.write(code); + for (byte[] parameter : parameters) { + out.write(parameter.length >> 8); + out.write(parameter.length); + out.write(parameter); + } + out.flush(); + socket.shutdownOutput(); + + InputStream in = socket.getInputStream(); + if ((code = in.read()) != NO_ERROR) { + if (code != -1) { + mError = code; + } + return null; + } + + ArrayList values = new ArrayList(); + while (true) { + int i, j; + if ((i = in.read()) == -1) { + break; + } + if ((j = in.read()) == -1) { + return null; + } + byte[] value = new byte[i << 8 | j]; + for (i = 0; i < value.length; i += j) { + if ((j = in.read(value, i, value.length - i)) == -1) { + return null; + } + } + values.add(value); + } + mError = NO_ERROR; + return values; + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + socket.close(); + } catch (IOException e) { + } + } + return null; + } + + private static byte[] getBytes(String string) { + try { + return string.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private byte[] generateIv(int length) { + byte[] b = new byte[length]; + random.nextBytes(b); + return b; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherJB.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherJB.java new file mode 100644 index 0000000000..cc4ee8bbd7 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherJB.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto.ciper; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +import javax.crypto.NoSuchPaddingException; + +/** + * Return a {@link javax.crypto.Cipher} that works for the API 18. + */ +public class CipherJB { + public static javax.crypto.Cipher get() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { + return javax.crypto.Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL"); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherLegacy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherLegacy.java new file mode 100644 index 0000000000..d34c728a7f --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherLegacy.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto.ciper; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +import javax.crypto.NoSuchPaddingException; + +/** + * Return a {@link javax.crypto.Cipher} that works for the legacy API 9 to 18. + */ +public class CipherLegacy { + public static javax.crypto.Cipher get() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { + return javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding"); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherMM.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherMM.java new file mode 100644 index 0000000000..3f10d15205 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherMM.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto.ciper; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +import javax.crypto.NoSuchPaddingException; + +/** + * Return a {@link javax.crypto.Cipher} that works for API > 23. + */ +public class CipherMM { + public static javax.crypto.Cipher get() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { + return javax.crypto.Cipher.getInstance("RSA/ECB/PKCS1Padding"); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/Base64.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/Base64.java new file mode 100644 index 0000000000..19ad363ef1 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/Base64.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal.android.crypto.misc; + +/** + * Base64 helper methods. + */ +public class Base64 { + public static String to(byte[] bytes) { + return android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP); + } + + public static byte[] from(String base64) { + return android.util.Base64.decode(base64, android.util.Base64.NO_WRAP); + } + +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/PRNGFixes.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/PRNGFixes.java new file mode 100644 index 0000000000..6ddddefc58 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/PRNGFixes.java @@ -0,0 +1,94 @@ +package io.realm.internal.android.crypto.misc; + +import android.os.Build; +import android.os.Process; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +// Based on http://android-developers.blogspot.jp/2013/08/some-securerandom-thoughts.html +public class PRNGFixes { + + private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); + + private PRNGFixes() { + } + + public static void apply() { + applyOpenSSLFix(); + } + + public static void applyOpenSSLFix() throws SecurityException { + if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + || (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2)) { + // No need to apply the fix + return; + } + + try { + // Mix in the device- and invocation-specific seed. + Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_seed", byte[].class) + .invoke(null, generateSeed()); + + // Mix output of Linux PRNG into OpenSSL's PRNG + int bytesRead = (Integer) Class + .forName( + "org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_load_file", String.class, long.class) + .invoke(null, "/dev/urandom", 1024); + if (bytesRead != 1024) { + throw new IOException( + "Unexpected number of bytes read from Linux PRNG: " + + bytesRead); + } + } catch (Exception e) { + throw new SecurityException("Failed to seed OpenSSL PRNG", e); + } + } + + private static byte[] generateSeed() { + try { + ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); + DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); + seedBufferOut.writeLong(System.currentTimeMillis()); + seedBufferOut.writeLong(System.nanoTime()); + seedBufferOut.writeInt(Process.myPid()); + seedBufferOut.writeInt(Process.myUid()); + seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); + seedBufferOut.close(); + return seedBuffer.toByteArray(); + } catch (IOException e) { + throw new SecurityException("Failed to generate seed", e); + } + } + + private static String getDeviceSerialNumber() { + // We're using the Reflection API because Build.SERIAL is only available + // since API Level 9 (Gingerbread, Android 2.3). + try { + return (String) Build.class.getField("SERIAL").get(null); + } catch (Exception ignored) { + return null; + } + } + + private static byte[] getBuildFingerprintAndDeviceSerial() { + StringBuilder result = new StringBuilder(); + String fingerprint = Build.FINGERPRINT; + if (fingerprint != null) { + result.append(fingerprint); + } + String serial = getDeviceSerialNumber(); + if (serial != null) { + result.append(serial); + } + try { + return result.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 encoding not supported"); + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java index 67f95fb3e0..83e6185b55 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java @@ -1,4 +1,4 @@ -package io.realm.internal.objectserver;/* +/* * Copyright 2016 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,8 @@ * limitations under the License. */ +package io.realm.internal.objectserver; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; From 43219b6c6979339542c9dbe0b157050c99bcb9bd Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Sat, 24 Sep 2016 21:53:55 +0200 Subject: [PATCH 0096/2110] initialData must only be triggered once (#166) --- .../java/io/realm/SyncConfigurationTests.java | 12 +++++++++--- .../src/main/cpp/io_realm_internal_SharedRealm.cpp | 2 -- .../realm-library/src/main/java/io/realm/Realm.java | 6 ++++++ .../java/io/realm/SyncConfiguration.java | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java index 8d8200b74d..77dd7abf9d 100644 --- a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java @@ -368,10 +368,16 @@ public void execute(Realm realm) { assertNotNull(config.getInitialDataTransaction()); - Realm realm = Realm.getInstance(config); - RealmResults results = realm.where(StringOnly.class).findAll(); + // open the first time - initialData must be triggered + Realm realm1 = Realm.getInstance(config); + RealmResults results = realm1.where(StringOnly.class).findAll(); assertEquals(1, results.size()); assertEquals("TEST 42", results.first().getChars()); - realm.close(); + realm1.close(); + + // open the second time - initialData must not be triggered + Realm realm2 = Realm.getInstance(config); + assertEquals(1, realm2.where(StringOnly.class).count()); + realm2.close(); } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 39b3eefb97..9c00c09fc1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -157,8 +157,6 @@ Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong s try { return static_cast(ObjectStore::get_schema_version(shared_realm->read_group())); } CATCH_STD() - - return static_cast(ObjectStore::NotVersioned); } JNIEXPORT void JNICALL diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 1830681b20..3c92dc8662 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -360,6 +360,12 @@ private static void initializeRealm(Realm realm) { if (transaction != null) { if (syncAvailable) { realm.executeTransaction(transaction); + realm.executeTransaction(new Transaction() { + @Override + public void execute(Realm realm) { + realm.setVersion(realm.configuration.getSchemaVersion()); + } + }); } else { transaction.execute(realm); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 765cfaee56..e548ecd20a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -619,7 +619,7 @@ public SyncConfiguration build() { getCanonicalPath(new File(realmFileDirectory, realmFileName)), null, // assetFile not supported by Sync. See https://github.com/realm/realm-sync/issues/241 key, - -1, // Schema version not supported + 0, null, // Custom migrations not supported false, // MigrationNeededException is never thrown durability, From 9155cb1f3f06609d57d2350bccbc8f318bcaf77e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 24 Sep 2016 22:24:08 +0200 Subject: [PATCH 0097/2110] Fix errors from login being wrapped in an UNKNOWN code. (#168) --- .../io/realm/AuthenticateRequestTests.java | 21 +++++++++++++++++++ .../java/io/realm/util/SyncTestUtils.java | 6 ++++++ .../src/objectServer/java/io/realm/User.java | 6 +++--- .../network/OkHttpAuthenticationServer.java | 2 +- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java index bc274b886a..458bb29cba 100644 --- a/realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java @@ -7,16 +7,22 @@ import org.json.JSONObject; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import io.realm.internal.network.AuthenticateRequest; +import io.realm.internal.network.AuthenticationServer; import io.realm.internal.objectserver.Token; import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; @RunWith(AndroidJUnit4.class) public class AuthenticateRequestTests { @@ -54,4 +60,19 @@ public void userRefresh() throws URISyntaxException, JSONException { assertEquals(t.value(), obj.get("data")); assertEquals("realm", obj.get("provider")); } + + + @Test + public void errorsNotWrapped() { + AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + when(authServer.loginUser(any(Credentials.class), any(URL.class))).thenReturn(SyncTestUtils.createErrorResponse(ErrorCode.ACCESS_DENIED)); + SyncManager.setAuthServerImpl(authServer); + + try { + User.login(Credentials.facebook("foo"), "http://foo.bar/auth"); + fail(); + } catch (ObjectServerError e) { + assertEquals(ErrorCode.ACCESS_DENIED, e.getErrorCode()); + } + } } diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/util/SyncTestUtils.java index 2fdd291cad..dda3944952 100644 --- a/realm/realm-library/src/androidTestobjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestobjectServer/java/io/realm/util/SyncTestUtils.java @@ -22,6 +22,8 @@ import java.util.UUID; +import io.realm.ErrorCode; +import io.realm.ObjectServerError; import io.realm.User; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.objectserver.SyncUser; @@ -79,4 +81,8 @@ public static AuthenticateResponse createRefreshResponse() { throw new RuntimeException(e); } } + + public static AuthenticateResponse createErrorResponse(ErrorCode code) { + return AuthenticateResponse.from(new ObjectServerError(code, "dummy")); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/User.java b/realm/realm-library/src/objectServer/java/io/realm/User.java index 439ea59494..2f84203c48 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/User.java @@ -126,6 +126,7 @@ public static User login(final Credentials credentials, final String authenticat } final AuthenticationServer server = SyncManager.getAuthServer(); + ObjectServerError error; try { AuthenticateResponse result = server.loginUser(credentials, authUrl); if (result.isValid()) { @@ -137,13 +138,12 @@ public static User login(final Credentials credentials, final String authenticat return user; } else { RealmLog.info("Failed authenticating user.\n%s", result.getError()); - throw result.getError(); + error = result.getError(); } - } catch (IOException e) { - throw new ObjectServerError(ErrorCode.IO_EXCEPTION, e); } catch (Throwable e) { throw new ObjectServerError(ErrorCode.UNKNOWN, e); } + throw error; } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 919c1eddea..2b5ae1e25a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -52,7 +52,7 @@ public AuthenticateResponse loginUser(Credentials credentials, URL authenticatio String requestBody = AuthenticateRequest.userLogin(credentials).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { - return AuthenticateResponse.from(new ObjectServerError(ErrorCode.OTHER_ERROR, Util.getStackTrace(e))); + return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); } } From 4bdd5cd58fdacfed812bc5017b2139032418cfde Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 25 Sep 2016 07:17:10 +0200 Subject: [PATCH 0098/2110] Upgraded to 37.2 (#169) --- realm/realm-library/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index fb4a7ca8bd..ad74c26b56 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '1.0.0-beta-37.1' +ext.coreVersion = '1.0.0-beta-37.2' // empty or comment out this to disable hash checking -ext.coreSha256Hash = '226270a563fddc7e8512d650e644b9863cce7f4fba58d51b638007fe3ad7ccb9' +ext.coreSha256Hash = '7c8bd6bf952aff39ae56dace9674b951005cf6f320bc2266cbe7a71af73f4e2b' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from From 163071215317458917e1a0f6cba9ecba38ca8da3 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 26 Sep 2016 09:27:13 +0200 Subject: [PATCH 0099/2110] Let sync client handle default port (#176) --- .../java/io/realm/SyncConfigurationTests.java | 4 ++-- .../java/io/realm/SyncConfiguration.java | 16 ---------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java index 77dd7abf9d..4a57ddec19 100644 --- a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java @@ -169,8 +169,8 @@ public void serverUrl_invalidChars() { @Test public void serverUrl_port() { Map urlPort = new HashMap(); - urlPort.put("realm://objectserver.realm.io/~/default", SyncConfiguration.PORT_REALM); - urlPort.put("realms://objectserver.realm.io/~/default", SyncConfiguration.PORT_REALMS); + urlPort.put("realm://objectserver.realm.io/~/default", -1); // default port - handled by sync client + urlPort.put("realms://objectserver.realm.io/~/default", -1); // default port - handled by sync client urlPort.put("realm://objectserver.realm.io:8080/~/default", 8080); urlPort.put("realms://objectserver.realm.io:2443/~/default", 2443); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index e548ecd20a..a28b2af5b4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -316,15 +316,6 @@ private void validateAndSet(String uri) { throw new IllegalArgumentException("Invalid scheme: " + scheme); } - // set port if not set by user - int port; - int currentPort = serverUrl.getPort(); - if (currentPort == -1) { - port = scheme.equals("realm") ? PORT_REALM : PORT_REALMS; - } else { - port = currentPort; - } - // Detect last path segment as it is the default file name String path = serverUrl.getPath(); if (path == null) { @@ -355,13 +346,6 @@ private void validateAndSet(String uri) { || defaultLocalFileName.endsWith(".realm.management")) { throw new IllegalArgumentException("The URI must not end with '.realm', '.realm.lock' or '.realm.management: " + uri); } - - try { - this.serverUrl = new URI(scheme, serverUrl.getUserInfo(), serverUrl.getHost(), - port, serverUrl.getPath(), serverUrl.getQuery(), serverUrl.getFragment()); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Cannot reconstruct URI: " + uri, e); - } } /** From d34a58358096f914a6b9c9dde649a3a594584f2e Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Mon, 26 Sep 2016 09:34:50 +0200 Subject: [PATCH 0100/2110] Let Mixpanel track the version of sync being used (#161) * Add support for sync to Mixpanel Also moves the version and SHA256 of sync in the dependencies.list file This consolidates with the other repos in the org * Be aware of whether sync is enabled or not * Align naming style --- dependencies.list | 2 ++ .../main/groovy/io/realm/gradle/Realm.groovy | 2 +- realm-transformer/build.gradle | 7 ++++++- .../realm/transformer/RealmTransformer.groovy | 9 +++++---- .../io/realm/transformer/RealmAnalytics.java | 17 ++++++----------- .../src/main/templates/Version.java | 1 + realm/realm-library/build.gradle | 7 +++++-- 7 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 dependencies.list diff --git a/dependencies.list b/dependencies.list new file mode 100644 index 0000000000..7c0830698e --- /dev/null +++ b/dependencies.list @@ -0,0 +1,2 @@ +REALM_SYNC_VERSION=1.0.0-beta-37.2 +REALM_SYNC_SHA256=7c8bd6bf952aff39ae56dace9674b951005cf6f320bc2266cbe7a71af73f4e2b \ No newline at end of file diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 8847a46826..d84e9b8622 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -52,7 +52,6 @@ class Realm implements Plugin { project.plugins.apply(AndroidAptPlugin) } - project.android.registerTransform(new RealmTransformer(project)) project.repositories.add(project.getRepositories().jcenter()) project.dependencies.add("compile", "io.realm:realm-annotations:${Version.VERSION}") if (isKaptProject) { @@ -70,6 +69,7 @@ class Realm implements Plugin { project.getGradle().addListener(new DependencyResolutionListener() { @Override void beforeResolve(ResolvableDependencies resolvableDependencies) { + project.android.registerTransform(new RealmTransformer(project)) def suffix = project.realm.syncEnabled?'-object-server':'' compileDeps.add(project.getDependencies().create("io.realm:realm-android-library${suffix}:${Version.VERSION}")) project.getGradle().removeListener(this) diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 5fbfb9219f..2dc0dadb96 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -18,6 +18,11 @@ apply plugin: 'com.jfrog.bintray' group = 'io.realm' version = file("${projectDir}/../version.txt").text.trim(); +def properties = new Properties() +properties.load(new FileInputStream("${projectDir}/../dependencies.list")) + +def syncVersion = properties.getProperty('REALM_SYNC_VERSION') + sourceCompatibility = '1.6' targetCompatibility = '1.6' @@ -59,7 +64,7 @@ import org.apache.tools.ant.filters.ReplaceTokens task generateVersionClass(type: Copy) { from 'src/main/templates/Version.java' into 'build/generated-src/main/java/io/realm/transformer' - filter(ReplaceTokens, tokens: [version: version]) + filter(ReplaceTokens, tokens: [version: version, syncVersion: syncVersion]) outputs.upToDateWhen { false } } diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index ba85db84ab..0704cb2a26 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -146,7 +146,7 @@ class RealmTransformer extends Transform { def toc = System.currentTimeMillis() logger.info "Realm Transform time: ${toc-tic} milliseconds" - sendAnalytics(inputs, inputModelClasses) + this.sendAnalytics(inputs, inputModelClasses) } /** @@ -154,7 +154,7 @@ class RealmTransformer extends Transform { * @param inputs the inputs provided by the Transform API * @param inputModelClasses a list of ctClasses describing the Realm models */ - private static sendAnalytics(Collection inputs, List inputModelClasses) { + private sendAnalytics(Collection inputs, List inputModelClasses) { def containsKotlin = false inputs.each { it.directoryInputs.each { @@ -177,7 +177,8 @@ class RealmTransformer extends Transform { def env = System.getenv() def disableAnalytics = env["REALM_DISABLE_ANALYTICS"] if (disableAnalytics == null || disableAnalytics != "true") { - def analytics = RealmAnalytics.getInstance(packages as Set, containsKotlin) + boolean sync = project?.realm?.syncEnabled != null && project.realm.syncEnabled + def analytics = new RealmAnalytics(packages as Set, containsKotlin, sync) analytics.execute() } } @@ -246,7 +247,7 @@ class RealmTransformer extends Transform { // The jar might not using File.separatorChar as the path separator. So we just replace both `\` and // `/`. It depends on how the jar file was created. // See http://stackoverflow.com/questions/13846000/file-separators-of-path-name-of-zipentry - def className = path.substring(0, path.length() - SdkConstants.DOT_CLASS.length()) + String className = path.substring(0, path.length() - SdkConstants.DOT_CLASS.length()) .replace('/' as char , '.' as char) .replace('\\' as char , '.' as char) classNames.add(className) diff --git a/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java b/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java index bd1709453b..18dd7b827b 100644 --- a/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java +++ b/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java @@ -19,11 +19,9 @@ import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; -import java.net.NetworkInterface; import java.net.SocketException; import java.net.URL; import java.security.NoSuchAlgorithmException; -import java.util.Enumeration; import java.util.Set; // Asynchronously submits build information to Realm when the annotation @@ -69,6 +67,7 @@ public class RealmAnalytics { + " \"Anonymized Bundle ID\": \"%APP_ID%\",\n" + " \"Binding\": \"java\",\n" + " \"Language\": \"%LANGUAGE%\",\n" + + " \"Sync Version\": %SYNC_VERSION%,\n" + " \"Realm Version\": \"%REALM_VERSION%\",\n" + " \"Host OS Type\": \"%OS_TYPE%\",\n" + " \"Host OS Version\": \"%OS_VERSION%\",\n" @@ -80,17 +79,12 @@ public class RealmAnalytics { private Set packages; private boolean usesKotlin; + private boolean usesSync; - private RealmAnalytics(Set packages, boolean usesKotlin) { + public RealmAnalytics(Set packages, boolean usesKotlin, boolean usesSync) { this.packages = packages; this.usesKotlin = usesKotlin; - } - - public static RealmAnalytics getInstance(Set packages, boolean usesKotlin) { - if (instance == null) { - instance = new RealmAnalytics(packages, usesKotlin); - } - return instance; + this.usesSync = usesSync; } private void send() { @@ -135,7 +129,8 @@ public String generateJson() throws SocketException, NoSuchAlgorithmException { .replaceAll("%TOKEN%", TOKEN) .replaceAll("%USER_ID%", ComputerIdentifierGenerator.get()) .replaceAll("%APP_ID%", getAnonymousAppId()) - .replaceAll("%LANGUAGE%", usesKotlin?"kotlin":"java") + .replaceAll("%LANGUAGE%", usesKotlin ? "kotlin" : "java") + .replaceAll("%SYNC_VERSION%", usesSync ? "\"" + Version.SYNC_VERSION + "\"": "null") .replaceAll("%REALM_VERSION%", Version.VERSION) .replaceAll("%OS_TYPE%", System.getProperty("os.name")) .replaceAll("%OS_VERSION%", System.getProperty("os.version")); diff --git a/realm-transformer/src/main/templates/Version.java b/realm-transformer/src/main/templates/Version.java index e1d6208e55..9239f52d3e 100644 --- a/realm-transformer/src/main/templates/Version.java +++ b/realm-transformer/src/main/templates/Version.java @@ -2,4 +2,5 @@ public class Version { public static final String VERSION = "@version@"; + public static final String SYNC_VERSION = "@syncVersion@"; } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ad74c26b56..0d3833e499 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,12 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '1.0.0-beta-37.2' +def properties = new Properties() +properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) + +ext.coreVersion = properties.getProperty('REALM_SYNC_VERSION') // empty or comment out this to disable hash checking -ext.coreSha256Hash = '7c8bd6bf952aff39ae56dace9674b951005cf6f320bc2266cbe7a71af73f4e2b' +ext.coreSha256Hash = properties.getProperty('REALM_SYNC_SHA256') ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from From dd64bbd8a7cf61ebd508b77f0e61911616d80bb9 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Mon, 26 Sep 2016 09:36:47 +0200 Subject: [PATCH 0101/2110] Perform the publishing to Bintray using curl (#173) * Perform the publishing to Bintray using curl The Bintray Gradle plugin does not support to publish to multiple repos from one project * Use a default value for missing bintray credentials --- realm/realm-library/build.gradle | 96 ++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 0d3833e499..2987477664 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -5,7 +5,6 @@ apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'maven-publish' apply plugin: 'com.jfrog.artifactory' -apply plugin: 'com.jfrog.bintray' apply plugin: 'findbugs' apply plugin: 'pmd' apply plugin: 'checkstyle' @@ -344,28 +343,6 @@ publishing { } } -bintray { - user = project.hasProperty('bintrayUser') ? bintrayUser : 'noUser' - key = project.hasProperty('bintrayKey') ? bintrayKey : 'noKey' - - dryRun = false - publish = false - - configurations = ['basePublication', 'objectServerPublication'] - - pkg { - repo = 'maven' - name = 'realm-android-library' - desc = 'Realm for Android' - websiteUrl = 'http://realm.io' - issueTrackerUrl = 'https://github.com/realm/realm-java/issues' - vcsUrl = 'https://github.com/realm/realm-java.git' - licenses = ['Apache-2.0'] - labels = ['android', 'realm'] - publicDownloadNumbers = false - } -} - artifactory { contextUrl = 'https://oss.jfrog.org/artifactory' publish { @@ -559,6 +536,79 @@ task checkNdk() << { } } +android.productFlavors.all { flavor -> + def librarySuffix = flavor.name == 'base' ? '' : '-object-server' + def userName = project.findProperty('bintrayUser') ?: 'noUser' + def accessKey = project.findProperty('bintrayKey') ?: 'noKey' + + task("bintrayAar${flavor.name.capitalize()}", type: Exec) { + dependsOn "assemble${flavor.name.capitalize()}" + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/outputs/aar/realm-android-library-${flavor.name}-release.aar", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}.aar?publish=0" + } + + task("bintraySources${flavor.name.capitalize()}", type: Exec) { + dependsOn sourcesJar + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-android-library-${project.version}-sources.jar", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-sources.jar?publish=0" + } + + task("bintrayJavadoc${flavor.name.capitalize()}", type: Exec) { + dependsOn javadocJar + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-android-library-${project.version}-javadoc.jar", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-javadoc.jar?publish=0" + } + + task("bintrayPom${flavor.name.capitalize()}", type: Exec) { + dependsOn "publish${flavor.name.capitalize()}PublicationPublicationToMavenLocal" + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/publications/${flavor.name}Publication/pom-default.xml", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}.pom?publish=0" + } + + task("bintray${flavor.name.capitalize()}") { + dependsOn "bintrayAar${flavor.name.capitalize()}" + dependsOn "bintraySources${flavor.name.capitalize()}" + dependsOn "bintrayJavadoc${flavor.name.capitalize()}" + dependsOn "bintrayPom${flavor.name.capitalize()}" + group = 'Publishing' + } +} + +task bintrayUpload() { + android.productFlavors.all { flavor -> + dependsOn "bintray${flavor.name.capitalize()}" + } + group = 'Publishing' +} + def checkNdk(String ndkPath) { def detectedNdkVersion def releaseFile = new File(ndkPath, 'RELEASE.TXT') From eaf6483f059063fd29e87c872b7563dfeddf2f90 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 26 Sep 2016 12:27:13 +0200 Subject: [PATCH 0102/2110] Upgrade to final 1.0.0-BETA --- dependencies.list | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.list b/dependencies.list index 7c0830698e..ab9c452f27 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,2 +1,2 @@ -REALM_SYNC_VERSION=1.0.0-beta-37.2 -REALM_SYNC_SHA256=7c8bd6bf952aff39ae56dace9674b951005cf6f320bc2266cbe7a71af73f4e2b \ No newline at end of file +REALM_SYNC_VERSION=1.0.0-BETA-1.0 +REALM_SYNC_SHA256=c2eacb3dcfcdf41e8f19a1fcb40e51927d883d6898a36d732f848e85b3aba25e \ No newline at end of file From 070cf094688958b270aee42568f6a926e8e4d9a3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 26 Sep 2016 13:01:33 +0200 Subject: [PATCH 0103/2110] Upgrade to Core 2.0.0 (#3494) --- CHANGELOG.md | 4 ++-- realm/realm-library/build.gradle | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea347efc67..d5f22ca956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,8 +35,8 @@ ### Internal * Moved JNI build to CMake. -* Updated Realm Core to 2.0.0-rc7. -* Upgrade ReLinker to 1.2.2. +* Updated Realm Core to 2.0.0. +* Updated ReLinker to 1.2.2. ### Enhancements diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 877786000b..cb5981df86 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -12,9 +12,9 @@ apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' -ext.coreVersion = '2.0.0-rc7' +ext.coreVersion = '2.0.0' // empty or comment out this to disable hash checking -ext.coreSha256Hash = '5707af75cd3624505d687c5fa31ccb6a61fe754f72ff6c79d98a342af3b9942b' +ext.coreSha256Hash = 'e8f3c7c0573cfc202749b9d9b374b080b24812689e9ccd4c3e8ba2c6dee7146e' ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from From ced0c96db2e02a32da574081af553c69b5f8b0f9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 26 Sep 2016 13:05:31 +0200 Subject: [PATCH 0104/2110] Using a custom tag (@Beta) in javadoc. Add annotation @Beta to public classes. (#178) --- .../main/java/io/realm/annotations/Beta.java | 33 +++++++++++++++++++ realm/realm-library/build.gradle | 7 ++++ .../java/io/realm/AuthenticationListener.java | 4 +++ .../java/io/realm/Credentials.java | 4 +++ .../objectServer/java/io/realm/ErrorCode.java | 4 +++ .../java/io/realm/ObjectServer.java | 3 ++ .../java/io/realm/ObjectServerError.java | 3 ++ .../objectServer/java/io/realm/Session.java | 3 ++ .../java/io/realm/SessionState.java | 4 +++ .../java/io/realm/SyncConfiguration.java | 4 +++ .../java/io/realm/SyncManager.java | 3 ++ .../src/objectServer/java/io/realm/User.java | 10 +++--- .../objectServer/java/io/realm/UserStore.java | 3 ++ .../network/AuthenticateResponse.java | 4 +-- .../internal/network/LogoutResponse.java | 2 +- .../network/OkHttpAuthenticationServer.java | 1 - 16 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 realm-annotations/src/main/java/io/realm/annotations/Beta.java diff --git a/realm-annotations/src/main/java/io/realm/annotations/Beta.java b/realm-annotations/src/main/java/io/realm/annotations/Beta.java new file mode 100644 index 0000000000..f35dd4da23 --- /dev/null +++ b/realm-annotations/src/main/java/io/realm/annotations/Beta.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is added to classes, constructors or methods which are considered in beta phase. + * It indicates that any public interface can change without prior announcements. + * Moreover, classes, constructors, and methods annotated as beta are not considered at production + * quality, and should be used with care. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface Beta { +} diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 2987477664..504bce7192 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -138,6 +138,11 @@ task sourcesJar(type: Jar) { classifier = 'sources' } +def betaTag = 'Beta:a:

      This software is considered in beta phase. ' + + 'It indicates that any public interface can change without prior announcements. ' + + 'Moreover, classes, constructors, and methods annotated as beta are not ' + + 'considered at production quality, and should be used with care.
      ' + task javadoc(type: Javadoc) { source android.sourceSets.objectServer.java.srcDirs source android.sourceSets.main.java.srcDirs @@ -155,6 +160,8 @@ task javadoc(type: Javadoc) { links "https://docs.oracle.com/javase/7/docs/api/" links "http://reactivex.io/RxJava/javadoc/" linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" + + tags = [ betaTag ] } exclude '**/internal/**' exclude '**/BuildConfig.java' diff --git a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java b/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java index d7a2de6f73..7fbed146cc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java @@ -16,9 +16,13 @@ package io.realm; +import io.realm.annotations.Beta; + /** + * @Beta * Interface describing events related to Users and their authentication */ +@Beta public interface AuthenticationListener { /** * A user was logged into the Object Server diff --git a/realm/realm-library/src/objectServer/java/io/realm/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/Credentials.java index 144a935a39..2f03ad6163 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/Credentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/Credentials.java @@ -20,7 +20,10 @@ import java.util.HashMap; import java.util.Map; +import io.realm.annotations.Beta; + /** + * @Beta * Credentials represent a login with a 3rd party login provider in an OAuth2 login flow, and are used by the Realm * Object Server to verify the user and grant access. *

      @@ -60,6 +63,7 @@ * } * */ +@Beta public class Credentials { private String identityProvider; diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index d3b1ca29bb..17e0365396 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -16,9 +16,13 @@ package io.realm; +import io.realm.annotations.Beta; + /** + * @Beta * This class enumerate all potential errors related to using the Object Server or synchronizing data. */ +@Beta public enum ErrorCode { // See https://github.com/realm/realm-sync/blob/master/doc/protocol.md diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index b8599292c8..069f32b2a1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -4,14 +4,17 @@ import android.content.pm.PackageInfo; import io.realm.android.SharedPrefsUserStore; +import io.realm.annotations.Beta; import io.realm.internal.Keep; /** + * @Beta * Internal initializer class for the Object Server. * Use to keep the `SyncManager` free from Android dependencies */ @SuppressWarnings("unused") @Keep +@Beta class ObjectServer { public static void init(Context context) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java index fed409b9d2..bd2f0f467f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java @@ -16,9 +16,11 @@ package io.realm; +import io.realm.annotations.Beta; import io.realm.internal.Util; /** + * @Beta * This class is a wrapper for all errors happening when communicating with the Realm Object Server. * This include both exceptions and protocol errors. * @@ -28,6 +30,7 @@ * * @see ErrorCode for a list of possible errors. */ +@Beta public class ObjectServerError extends RuntimeException { private final ErrorCode error; diff --git a/realm/realm-library/src/objectServer/java/io/realm/Session.java b/realm/realm-library/src/objectServer/java/io/realm/Session.java index 66253ce7a3..a5c44e257b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/Session.java +++ b/realm/realm-library/src/objectServer/java/io/realm/Session.java @@ -18,11 +18,13 @@ import java.net.URI; +import io.realm.annotations.Beta; import io.realm.internal.Keep; import io.realm.log.RealmLog; import io.realm.internal.objectserver.SyncSession; /** + * @Beta * This class represents the connection to the Realm Object Server for one {@link SyncConfiguration}. *

      * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening @@ -37,6 +39,7 @@ * @see SessionState */ @Keep +@Beta public final class Session { private final SyncSession syncSession; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SessionState.java b/realm/realm-library/src/objectServer/java/io/realm/SessionState.java index d3e167eae7..9ede02c3fc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SessionState.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SessionState.java @@ -16,9 +16,13 @@ package io.realm; +import io.realm.annotations.Beta; + /** + * @Beta * Enum describing the various states the Session Finite-State-Machine can be in. */ +@Beta public enum SessionState { INITIAL, // Initial starting state UNBOUND, // Start done, Realm is unbound. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index a28b2af5b4..e6eeebbca3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -28,6 +28,8 @@ import java.util.HashSet; import java.util.regex.Matcher; import java.util.regex.Pattern; + +import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.RealmProxyMediator; @@ -38,6 +40,7 @@ import io.realm.rx.RxObservableFactory; /** + * @Beta * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm * Object Server. *

      @@ -67,6 +70,7 @@ * Synchronized Realms are created by using {@link Realm#getInstance(RealmConfiguration)} and * {@link Realm#getDefaultInstance()} like ordinary unsynchronized Realms. */ +@Beta public final class SyncConfiguration extends RealmConfiguration { public static final int PORT_REALM = 80; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index c6a7fbeeb5..56dfad50e2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -21,6 +21,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import io.realm.annotations.Beta; import io.realm.internal.Keep; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.OkHttpAuthenticationServer; @@ -29,6 +30,7 @@ import io.realm.log.RealmLog; /** + * @Beta * The SyncManager is the central controller for interacting with the Realm Object Server. * It handles the creation of {@link Session}s and it is possible to configure session defaults and the underlying * network client using this class. @@ -40,6 +42,7 @@ * */ @Keep +@Beta public final class SyncManager { /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/User.java b/realm/realm-library/src/objectServer/java/io/realm/User.java index 2f84203c48..41515d765c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/User.java @@ -32,18 +32,19 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; -import io.realm.internal.IOException; +import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.internal.objectserver.SyncUser; -import io.realm.internal.objectserver.Token; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.network.AuthenticationServer; -import io.realm.log.RealmLog; import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.LogoutResponse; +import io.realm.internal.objectserver.SyncUser; +import io.realm.internal.objectserver.Token; +import io.realm.log.RealmLog; /** + * @Beta * This class represents a user on the Realm Object Server. The credentials are provided by various 3rd party * providers (Facebook, Google, etc.). *

      @@ -54,6 +55,7 @@ * Persisting a user between sessions, the user's credentials are stored locally on the device, and should be treated * as sensitive data. */ +@Beta public class User { private final SyncUser syncUser; diff --git a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java index c55aaceaf8..140d013d0a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java @@ -19,8 +19,10 @@ import java.util.Collection; import io.realm.android.SharedPrefsUserStore; +import io.realm.annotations.Beta; /** + * @Beta * Interface for classes responsible for saving and retrieving Object Server users again. *

      * Any implementation of a User Store is expected to not perform lengthy blocking operations as it might @@ -29,6 +31,7 @@ * @see SyncManager#setUserStore(UserStore) * @see SharedPrefsUserStore */ +@Beta public interface UserStore { String CURRENT_USER_KEY = "realm$currentUser"; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index e3fbc8c3b4..75e31a0818 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -21,10 +21,10 @@ import java.io.IOException; -import io.realm.log.RealmLog; import io.realm.ErrorCode; -import io.realm.internal.objectserver.Token; import io.realm.ObjectServerError; +import io.realm.internal.objectserver.Token; +import io.realm.log.RealmLog; import okhttp3.Response; /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java index 1ee07bb51b..5439f9f769 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java @@ -18,9 +18,9 @@ import java.io.IOException; -import io.realm.log.RealmLog; import io.realm.ErrorCode; import io.realm.ObjectServerError; +import io.realm.log.RealmLog; import okhttp3.Response; /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 2b5ae1e25a..2cac097162 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -24,7 +24,6 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.User; -import io.realm.internal.Util; import io.realm.internal.objectserver.Token; import okhttp3.Call; import okhttp3.MediaType; From d8377f2bed46dc7814bd777878c393c4a1626a28 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 27 Sep 2016 04:41:55 +0900 Subject: [PATCH 0105/2110] Fix unstable test (#3495) --- .../src/androidTest/java/io/realm/DynamicRealmTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 52afe5073c..a0b248acba 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -515,6 +515,8 @@ public void onChange(RealmResults object) { signalCallbackDone.run(); } }); + looperThread.keepStrongReference.add(realmResults1); + looperThread.keepStrongReference.add(realmResults2); } @Test From 6f639f881173d8843828cd36a987fb8b93fa1e48 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 26 Sep 2016 22:21:16 +0200 Subject: [PATCH 0106/2110] Prepare release version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 420ca2e62c..155b9cb2e9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.0-BETA1-SNAPSHOT \ No newline at end of file +2.0.0-SNAPSHOT \ No newline at end of file From b1bc66aa9d001e04fd56e898b61d5bf0210e6713 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 26 Sep 2016 20:51:31 -0500 Subject: [PATCH 0107/2110] Fix some native compiling warnings (#185) Close #184 --- .../src/main/cpp/io_realm_RealmObjectSchema.cpp | 6 +++++- realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp | 2 ++ realm/realm-library/src/main/cpp/java_binding_context.hpp | 7 ++++++- realm/realm-library/src/main/cpp/objectserver_shared.hpp | 8 ++++++-- realm/realm-library/src/main/cpp/util.cpp | 3 ++- realm/realm-library/src/main/cpp/util.hpp | 4 +++- 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp index 984960bbcd..5e3233e9a0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp @@ -70,10 +70,12 @@ Java_io_realm_RealmObjectSchema_nativeGetClassName(JNIEnv *env, jclass, jlong na return to_jstring(env, name); } CATCH_STD() + + return NULL; } JNIEXPORT jlongArray JNICALL -Java_io_realm_RealmObjectSchema_nativeGetProperties(JNIEnv *env, jclass type, jlong nativePtr) { +Java_io_realm_RealmObjectSchema_nativeGetProperties(JNIEnv *env, jclass, jlong nativePtr) { TR_ENTER_PTR(env, nativePtr) try { ObjectSchema* object_schema = reinterpret_cast(nativePtr); @@ -93,5 +95,7 @@ Java_io_realm_RealmObjectSchema_nativeGetProperties(JNIEnv *env, jclass type, jl return native_ptr_array; } CATCH_STD() + + return NULL; } diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp index 2a35f58a8d..8cb7f7daf1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp @@ -39,6 +39,7 @@ Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv *env, jclass, jlongArray o return reinterpret_cast(schema); } CATCH_STD() + return 0; } JNIEXPORT void JNICALL @@ -69,5 +70,6 @@ Java_io_realm_RealmSchema_nativeGetAll(JNIEnv *env, jclass, jlong nativePtr) { return native_ptr_array; } CATCH_STD() + return NULL; } diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index ee96a000a5..a058691b3e 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -51,7 +51,12 @@ class JavaBindingContext final : public BindingContext { virtual ~JavaBindingContext(); virtual void changes_available(); - JavaBindingContext(const ConcreteJavaBindContext&); + explicit JavaBindingContext(const ConcreteJavaBindContext&); + JavaBindingContext(const JavaBindingContext&) = delete; + JavaBindingContext& operator=(const JavaBindingContext&) = delete; + JavaBindingContext(JavaBindingContext&&) = delete; + JavaBindingContext& operator=(JavaBindingContext&&) = delete; + static inline std::unique_ptr create(JNIEnv* env, jobject notifier) { return std::make_unique(ConcreteJavaBindContext{env, notifier}); diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index d066d3653c..acdd15103a 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -34,7 +34,11 @@ class JniSession { public: - JniSession() = delete; + JniSession(const JniSession&) = delete; + JniSession& operator=(const JniSession&) = delete; + JniSession(JniSession&&) = delete; + JniSession& operator=(JniSession&&) = delete; + JniSession(JNIEnv* env, std::string local_realm_path, jobject java_session_obj) { extern std::unique_ptr sync_client; @@ -52,7 +56,7 @@ class JniSession { auto error_handler = [&, global_obj_ref_tmp](int error_code, std::string message) { // FIXME: Simplify this by moving log_message to AndroidLogger JNIEnv *local_env; - g_vm->AttachCurrentThread(&env, nullptr); + g_vm->AttachCurrentThread(&local_env, nullptr); std::string log = num_to_string(error_code) + " " + message.c_str(); log_message(local_env, log_debug, log.c_str()); }; diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 7755e7d942..2c571bdc0f 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -154,7 +154,7 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla TR_ERR(env, "Exception has been throw: %s", message.c_str()) } else { - TR_ERR(env, "ERROR: Couldn't throw exception.", NULL) + TR_ERR_NO_VA_ARG(env, "ERROR: Couldn't throw exception.") } env->DeleteLocalRef(jExceptionClass); @@ -165,6 +165,7 @@ void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::Rea jclass cls = env->FindClass("io/realm/exceptions/RealmFileException"); jmethodID constructor = env->GetMethodID(cls, "", "(BLjava/lang/String;)V"); + // Initial value to suppress gcc warning. jbyte kind_code; switch (kind) { case realm::RealmFileException::Kind::AccessError: diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index bed13c338a..7cadba4466 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -153,12 +153,14 @@ inline void log_message(JNIEnv *env, jmethodID log_method, const char *msg, ...) #define TR_ENTER_PTR(env, ptr) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, " --> %s %" PRId64, __FUNCTION__, static_cast(ptr)); } else {} #define TR(env, msg, ...) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, msg, __VA_ARGS__)); } else {} #define TR_ERR(env, msg, ...) if (trace_level <= io_realm_log_LogLevel_ERROR) { log_message(env, log_error, msg, __VA_ARGS__); } else {} + #define TR_ERR_NO_VA_ARG(env, msg) if (trace_level <= io_realm_log_LogLevel_ERROR) { log_message(env, log_error, msg); } else {} #define TR_LEAVE(env) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, " <-- %s", __FUNCTION__); } else {} #else // TRACE - these macros must be empty #define TR_ENTER(env) #define TR_ENTER_PTR(env, ptr) #define TR(env, msg, ...) #define TR_ERR(env, msg, ...) + #define TR_ERR_NO_VA_ARG(env, msg) #define TR_LEAVE(env) #endif @@ -421,7 +423,7 @@ inline bool ColIsNullable(JNIEnv* env, T* pTable, jlong columnIndex) return true; } - TR_ERR(env, "Expected nullable column type", NULL) + TR_ERR_NO_VA_ARG(env, "Expected nullable column type") ThrowException(env, IllegalArgument, "This field is not nullable."); return false; } From f67aa457c6feae49c9afbc7de9f652960f31dab9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 27 Sep 2016 03:52:40 +0200 Subject: [PATCH 0108/2110] Upgrade to latest Sync Core with latest SSL vulnerability fixed (#187) --- dependencies.list | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.list b/dependencies.list index ab9c452f27..d3f62f9ee1 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,2 +1,2 @@ -REALM_SYNC_VERSION=1.0.0-BETA-1.0 -REALM_SYNC_SHA256=c2eacb3dcfcdf41e8f19a1fcb40e51927d883d6898a36d732f848e85b3aba25e \ No newline at end of file +REALM_SYNC_VERSION=1.0.0-BETA-1.1 +REALM_SYNC_SHA256=1524721b35cffcfc14e1609ea42ebe65f87e134542dbe5164de347bf5586e789 \ No newline at end of file From ae1da2c2a01fe43abc9c22182c6ec36cc3f4b010 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 27 Sep 2016 06:36:34 +0200 Subject: [PATCH 0109/2110] Updated changelog (#188) --- CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d02e6aab5..4627bcd181 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ ## 2.0.0 +This release introduces support for the Realm Mobile Platform! +See for an overview of these great new features. + ### Breaking Changes +* Files written by Realm 2.0 cannot be read by 1.x or earlier versions. Old files can still be opened. * It is now required to call `Realm.init(Context)` before calling any other Realm API. * Removed `RealmConfiguration.Builder(Context)`, `RealmConfiguration.Builder(Context, File)` and `RealmConfiguration.Builder(File)` constructors. * `isValid()` now always returns `true` instead of `false` for unmanaged `RealmObject` and `RealmList`. This puts it in line with the behaviour of the Cocoa and .NET API's (#3101). @@ -23,6 +27,8 @@ * Added `RealmConfiguration.Builder.directory(File)`. * `RealmLog` has been moved to the public API. It is now possible to control which events Realm emit to Logcat. See the `RealmLog` class for more details. * Typed `RealmObject`s can now continue to access their fields properly even though the schema was changed while the Realm was open (#3409). +* A `RealmMigrationNeededException` will be thrown with a cause to show the detailed message when a migration is needed and the migration block is not in the `RealmConfiguration`. + ### Bug fixes @@ -38,10 +44,6 @@ * Updated Realm Core to 2.0.0. * Updated ReLinker to 1.2.2. -### Enhancements - -* A `RealmMigrationNeededException` will be thrown with a cause to show the detailed message when a migration is needed and the migration block is not in the `RealmConfiguration`. - ## 1.2.0 ### Bug fixes From 23915d221f3e49e45bdc653b066f28b0ed2ff0b6 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 26 Sep 2016 22:56:26 +0800 Subject: [PATCH 0110/2110] Rename androidTestobjectServer to androidTestObjectServer --- .../java/io/realm/AuthenticateRequestTests.java | 0 .../java/io/realm/CredentialsTests.java | 0 .../java/io/realm/SchemaTests.java | 0 .../java/io/realm/SessionTests.java | 0 .../java/io/realm/SyncConfigurationTests.java | 0 .../java/io/realm/SyncManagerTests.java | 0 .../java/io/realm/UserTests.java | 0 .../java/io/realm/android/UserStoreTest.java | 0 .../java/io/realm/util/SyncTestUtils.java | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename realm/realm-library/src/{androidTestobjectServer => androidTestObjectServer}/java/io/realm/AuthenticateRequestTests.java (100%) rename realm/realm-library/src/{androidTestobjectServer => androidTestObjectServer}/java/io/realm/CredentialsTests.java (100%) rename realm/realm-library/src/{androidTestobjectServer => androidTestObjectServer}/java/io/realm/SchemaTests.java (100%) rename realm/realm-library/src/{androidTestobjectServer => androidTestObjectServer}/java/io/realm/SessionTests.java (100%) rename realm/realm-library/src/{androidTestobjectServer => androidTestObjectServer}/java/io/realm/SyncConfigurationTests.java (100%) rename realm/realm-library/src/{androidTestobjectServer => androidTestObjectServer}/java/io/realm/SyncManagerTests.java (100%) rename realm/realm-library/src/{androidTestobjectServer => androidTestObjectServer}/java/io/realm/UserTests.java (100%) rename realm/realm-library/src/{androidTestobjectServer => androidTestObjectServer}/java/io/realm/android/UserStoreTest.java (100%) rename realm/realm-library/src/{androidTestobjectServer => androidTestObjectServer}/java/io/realm/util/SyncTestUtils.java (100%) diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java similarity index 100% rename from realm/realm-library/src/androidTestobjectServer/java/io/realm/AuthenticateRequestTests.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java similarity index 100% rename from realm/realm-library/src/androidTestobjectServer/java/io/realm/CredentialsTests.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java similarity index 100% rename from realm/realm-library/src/androidTestobjectServer/java/io/realm/SchemaTests.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java similarity index 100% rename from realm/realm-library/src/androidTestobjectServer/java/io/realm/SessionTests.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java similarity index 100% rename from realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncConfigurationTests.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java similarity index 100% rename from realm/realm-library/src/androidTestobjectServer/java/io/realm/SyncManagerTests.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/UserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java similarity index 100% rename from realm/realm-library/src/androidTestobjectServer/java/io/realm/UserTests.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/android/UserStoreTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java similarity index 100% rename from realm/realm-library/src/androidTestobjectServer/java/io/realm/android/UserStoreTest.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java diff --git a/realm/realm-library/src/androidTestobjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java similarity index 100% rename from realm/realm-library/src/androidTestobjectServer/java/io/realm/util/SyncTestUtils.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java From a4f75bd60addda14bd317c2b9677acacca75f1ca Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 26 Sep 2016 22:59:25 +0800 Subject: [PATCH 0111/2110] Call SyncManager.notifyErrorHandler --- .../src/main/cpp/io_realm_SyncManager.cpp | 29 ++++++++++++++++--- realm/realm-library/src/main/cpp/util.cpp | 2 -- realm/realm-library/src/main/cpp/util.hpp | 2 -- .../java/io/realm/SyncManager.java | 1 + 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index c397fea617..f513c03a37 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -73,13 +73,28 @@ struct AndroidLoggerFactory : public realm::SyncLoggerFactory { } s_logger_factory; // TODO: Move to a better place & not needed after moving to OS -AndroidLogger& AndroidLogger::shared() noexcept { +AndroidLogger& AndroidLogger::shared() noexcept +{ static AndroidLogger logger; return logger; } +static jclass sync_manager = nullptr; +static jmethodID sync_manager_notify_error_handler = nullptr; + +static void error_handler(int error_code, std::string message) +{ + JNIEnv* env; + if (g_vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { + throw std::runtime_error("JVM is not attached to this thread. Called in error_handler."); + } + + env->CallStaticVoidMethod(sync_manager, + sync_manager_notify_error_handler, error_code, env->NewStringUTF(message.c_str())); +} + JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient - (JNIEnv *env, jclass) + (JNIEnv *env, jclass sync_manager_class) { TR_ENTER(env) if (sync_client) return; @@ -90,13 +105,19 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient sync::Client::Config config; config.logger = &AndroidLogger::shared(); sync_client = std::make_unique(std::move(config)); // Throws - // FIXME setup error handler for client + + // This function should only be called once, so below is safe. + sync_manager = sync_manager_class; + sync_manager_notify_error_handler = env->GetStaticMethodID(sync_manager, + "notifyErrorHandler", "(ILjava/lang/String;)V"); + sync_client->set_error_handler(error_handler); } CATCH_STD() } // Create the thread from java side to avoid some strange errors when native throws. JNIEXPORT void JNICALL -Java_io_realm_SyncManager_nativeRunClient(JNIEnv *env, jclass) { +Java_io_realm_SyncManager_nativeRunClient(JNIEnv *env, jclass) +{ try { sync_client->run(); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 2c571bdc0f..eb00c1afe2 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -38,8 +38,6 @@ jclass java_lang_float; jmethodID java_lang_float_init; jclass java_lang_double; jmethodID java_lang_double_init; -jclass sync_manager; -jmethodID sync_manager_notify_error_handler; jclass session_class_ref; jmethodID session_error_handler; diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 7cadba4466..9db3763c60 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -713,8 +713,6 @@ extern jclass java_lang_double; extern jmethodID java_lang_double_init; // FIXME Move to own library -extern jclass sync_manager; -extern jmethodID sync_manager_notify_error_handler; extern jclass session_class_ref; extern jmethodID session_error_handler; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 56dfad50e2..01891b667d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -206,6 +206,7 @@ static UserStore getUserStore() { // This is called from SyncManager.cpp from the worker thread the Sync Client is running on // Right now Core doesn't send these errors to the proper session, so instead we need to notify all sessions // from here. This can be removed once better error propagation is implemented in Sync Core. + @SuppressWarnings("unused") private static void notifyErrorHandler(int errorCode, String errorMessage) { ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); for (SyncSession session : SessionStore.getAllSessions()) { From ceeef062dffd714438cfc176e999b0624c03d53e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 27 Sep 2016 12:22:02 +0800 Subject: [PATCH 0112/2110] Update object store --- realm/realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../src/main/cpp/io_realm_internal_SharedRealm.cpp | 5 ++--- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 04a18efac8..39c456f1da 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -158,7 +158,7 @@ file(GLOB objectstore_SRC if (build_SYNC) file(GLOB objectstore_sync_SRC "object-store/src/sync_manager.cpp" - "object-store/src/impl/sync_session.cpp") + "object-store/src/sync_session.cpp") endif() add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 9c00c09fc1..30fca15276 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -1,3 +1,4 @@ +#include #include "io_realm_internal_SharedRealm.h" #include "object_store.hpp" @@ -45,9 +46,7 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri if (sync_server_url) { JStringAccessor url(env, sync_server_url); JStringAccessor token(env, sync_user_token); - config->sync_config = std::make_shared(); - config->sync_config->user_tag = token; - config->sync_config->realm_url = url; + config->sync_config = std::make_shared(token, url, nullptr, SyncSessionStopPolicy::Immediately); // FIXME: Sync session is handled by java now. Remove this when adapt to OS sync implementation. config->sync_config->create_session = false; } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index b297230bde..a0ab785896 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit b297230bde4370301948055014453323898198fe +Subproject commit a0ab785896b3e362e9703c798b8db729a49c9fda From 866f78ed6d8873d8e40cb658e554bb9df0cb9783 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 27 Sep 2016 02:07:11 -0500 Subject: [PATCH 0113/2110] Use set_string_unique to set primary key (#3488) * Migrate PK table when get 1st Realm instance * migratePrimaryKeyTableIfNeeded will be called when the first time Realm instance gets opened. * Get miss-deleted tests case for pk table back. * Update Object Store --- .../io/realm/internal/PrimaryKeyTests.java | 257 ++++++++++++++++++ .../src/main/cpp/io_realm_internal_Table.cpp | 50 +++- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/RealmCache.java | 15 + .../main/java/io/realm/internal/Table.java | 32 ++- 5 files changed, 344 insertions(+), 12 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java new file mode 100644 index 0000000000..3ba437ac44 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -0,0 +1,257 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import io.realm.RealmConfiguration; +import io.realm.RealmFieldType; +import io.realm.exceptions.RealmError; +import io.realm.exceptions.RealmException; +import io.realm.exceptions.RealmPrimaryKeyConstraintException; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class PrimaryKeyTests { + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + private android.content.Context context; + private RealmConfiguration config; + private SharedRealm sharedRealm; + + @Before + public void setUp() throws Exception { + config = configFactory.createConfiguration(); + context = InstrumentationRegistry.getInstrumentation().getContext(); + } + + @After + public void tearDown() { + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); + } + } + + private Table getTableWithStringPrimaryKey() { + sharedRealm = SharedRealm.getInstance(config); + sharedRealm.beginTransaction(); + Table t = sharedRealm.getTable("TestTable"); + long column = t.addColumn(RealmFieldType.STRING, "colName", true); + t.addSearchIndex(column); + t.setPrimaryKey("colName"); + return t; + } + + private Table getTableWithIntegerPrimaryKey() { + sharedRealm = SharedRealm.getInstance(config); + sharedRealm.beginTransaction(); + Table t = sharedRealm.getTable("TestTable"); + long column = t.addColumn(RealmFieldType.INTEGER, "colName"); + t.addSearchIndex(column); + t.setPrimaryKey("colName"); + return t; + } + + // Test that primary key constraints are actually removed + @Test + public void removingPrimaryKeyRemovesConstraint_typeSetters() { + RealmConfiguration config = configFactory.createConfigurationBuilder() + .name("removeConstraints").build(); + SharedRealm sharedRealm = SharedRealm.getInstance(config); + + sharedRealm.beginTransaction(); + Table tbl = sharedRealm.getTable("EmployeeTable"); + tbl.addColumn(RealmFieldType.STRING, "name"); + tbl.setPrimaryKey("name"); + + // Create first entry with name "Foo" + tbl.setString(0, tbl.addEmptyRow(), "Foo", false); + + long rowIndex = tbl.addEmptyRow(); + try { + tbl.setString(0, rowIndex, "Foo", false); // Try to create 2nd entry with name Foo + } catch (RealmPrimaryKeyConstraintException e1) { + tbl.setPrimaryKey(""); // Primary key check worked, now remove it and try again. + try { + tbl.setString(0, rowIndex, "Foo", false); + return; + } catch (RealmException e2) { + fail("Primary key not removed"); + } + } + + fail("Primary key not enforced."); + sharedRealm.close(); + } + + @Test + public void addEmptyRowWithPrimaryKeyWrongTypeStringThrows() { + Table t = getTableWithStringPrimaryKey(); + try { + t.addEmptyRowWithPrimaryKey(42); + fail(); + } catch (IllegalArgumentException ignored) { + } + sharedRealm.cancelTransaction(); + } + + @Test + public void addEmptyRowWithPrimaryKeyNullString() { + Table t = getTableWithStringPrimaryKey(); + t.addEmptyRowWithPrimaryKey(null); + assertEquals(1, t.size()); + sharedRealm.cancelTransaction(); + } + + @Test + public void addEmptyRowWithPrimaryKeyWrongTypeIntegerThrows() { + Table t = getTableWithIntegerPrimaryKey(); + try { + t.addEmptyRowWithPrimaryKey("Foo"); + fail(); + } catch (IllegalArgumentException ignored) { + } + sharedRealm.cancelTransaction(); + } + + @Test + public void addEmptyRowWithPrimaryKeyString() { + Table t = getTableWithStringPrimaryKey(); + long rowIndex = t.addEmptyRowWithPrimaryKey("Foo"); + assertEquals(1, t.size()); + assertEquals("Foo", t.getUncheckedRow(rowIndex).getString(0)); + sharedRealm.cancelTransaction(); + } + + @Test + public void addEmptyRowWithPrimaryKeyLong() { + Table t = getTableWithIntegerPrimaryKey(); + long rowIndex = t.addEmptyRowWithPrimaryKey(42); + assertEquals(1, t.size()); + assertEquals(42L, t.getUncheckedRow(rowIndex).getLong(0)); + sharedRealm.cancelTransaction(); + } + + @Test + public void migratePrimaryKeyTableIfNeeded_first() throws IOException { + configFactory.copyRealmFromAssets(context, "080_annotationtypes.realm", "default.realm"); + sharedRealm = SharedRealm.getInstance(config); + sharedRealm.beginTransaction(); + assertTrue(Table.migratePrimaryKeyTableIfNeeded(sharedRealm)); + sharedRealm.commitTransaction(); + Table t = sharedRealm.getTable("class_AnnotationTypes"); + assertTrue(t.hasPrimaryKey()); + assertEquals(t.getColumnIndex("id"), t.getPrimaryKey()); + assertEquals(RealmFieldType.STRING, sharedRealm.getTable("pk").getColumnType(0)); + } + + @Test + public void migratePrimaryKeyTableIfNeeded_second() throws IOException { + configFactory.copyRealmFromAssets(context, "0841_annotationtypes.realm", "default.realm"); + sharedRealm = SharedRealm.getInstance(config); + sharedRealm.beginTransaction(); + assertTrue(Table.migratePrimaryKeyTableIfNeeded(sharedRealm)); + sharedRealm.commitTransaction(); + Table t = sharedRealm.getTable("class_AnnotationTypes"); + assertTrue(t.hasPrimaryKey()); + assertEquals(t.getColumnIndex("id"), t.getPrimaryKey()); + assertEquals("AnnotationTypes", sharedRealm.getTable("pk").getString(0, 0)); + } + + // See https://github.com/realm/realm-java/issues/1775 . + // Before 0.84.2, pk table added prefix "class_" to every class's name. + // After 0.84.2, the pk table should be migrated automatically to remove the "class_". + // In 0.84.2, the class names in pk table has been renamed to some incorrect names like "Thclass", "Mclass", + // "NClass", "Meclass" and etc.. + // The 0841_pk_migration.realm is made to produce the issue. + @Test + public void migratePrimaryKeyTableIfNeeded_primaryKeyTableMigratedWithRightName() throws IOException { + List tableNames = Arrays.asList( + "ChatList", "Drafts", "Member", "Message", "Notifs", "NotifyLink", "PopularPost", + "Post", "Tags", "Threads", "User"); + + configFactory.copyRealmFromAssets(context, "0841_pk_migration.realm", "default.realm"); + sharedRealm = SharedRealm.getInstance(config); + sharedRealm.beginTransaction(); + assertTrue(Table.migratePrimaryKeyTableIfNeeded(sharedRealm)); + sharedRealm.commitTransaction(); + + Table table = sharedRealm.getTable("pk"); + for (int i = 0; i < table.size(); i++) { + UncheckedRow row = table.getUncheckedRow(i); + // io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX 0LL + assertTrue(tableNames.contains(row.getString(0))); + } + } + + // PK table's column 'pk_table' needs search index in order to use set_string_unique. + // See https://github.com/realm/realm-java/pull/3488 + @Test + public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { + sharedRealm = SharedRealm.getInstance(config); + sharedRealm.beginTransaction(); + Table table = sharedRealm.getTable("TestTable"); + long column = table.addColumn(RealmFieldType.INTEGER, "PKColumn"); + table.addSearchIndex(column); + table.setPrimaryKey(column); + sharedRealm.commitTransaction(); + + assertEquals(table.getPrimaryKey(), table.getColumnIndex("PKColumn")); + // Now we have a pk table with search index. + + sharedRealm.beginTransaction(); + Table pkTable = sharedRealm.getTable("pk"); + long classColumn = pkTable.getColumnIndex("pk_table"); + pkTable.removeSearchIndex(classColumn); + + // Try to add a pk for another table + Table table2 = sharedRealm.getTable("TestTable2"); + long column2 = table2.addColumn(RealmFieldType.INTEGER, "PKColumn"); + table2.addSearchIndex(column2); + try { + table2.setPrimaryKey(column2); + } catch (RealmError ignored) { + // Column has no search index + } + + assertFalse(pkTable.hasSearchIndex(classColumn)); + + Table.migratePrimaryKeyTableIfNeeded(sharedRealm); + assertTrue(pkTable.hasSearchIndex(classColumn)); + // Now it works. + table2.addSearchIndex(column2); + sharedRealm.cancelTransaction(); + } +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 626f4a3a54..31bbca5e1b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1502,7 +1502,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey( // No primary key is currently set if (check_valid_primary_key_column(env, table, new_primary_key_column_name)) { row_index = pk_table->add_empty_row(); - pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX, row_index, table_name); + pk_table->set_string_unique(io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX, row_index, table_name); pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, new_primary_key_column_name); } } @@ -1535,17 +1535,23 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey( // - All Realms created by Cocoa and used by Realm-android up to 0.84.1 // - All Realms created by Realm-Android 0.84.1 and below // See https://github.com/realm/realm-java/issues/1703 +// +// 3> PK table's column 'pk_table' needs search index in order to use set_string_unique. +// This affects: +// - All Realms created by Cocoa and used by Realm-java before 2.0.0 +// See https://github.com/realm/realm-java/pull/3488 // This methods converts the old (wrong) table format (string, integer) to the right (string,string) format and strips // any class names in the col[0] of their "class_" prefix -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded - (JNIEnv*, jobject, jlong groupNativePtr, jlong privateKeyTableNativePtr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded + (JNIEnv*, jclass, jlong groupNativePtr, jlong privateKeyTableNativePtr) { const size_t CLASS_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX; const size_t FIELD_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX; auto group = reinterpret_cast(groupNativePtr); Table* pk_table = TBL(privateKeyTableNativePtr); + jboolean changed = JNI_FALSE; // Fix wrong types (string, int) -> (string, string) if (pk_table->get_column_type(FIELD_COLUMN_INDEX) == type_Int) { @@ -1566,6 +1572,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTable // The column index for the renamed column will then be the same as the deleted old column pk_table->remove_column(FIELD_COLUMN_INDEX); pk_table->rename_column(pk_table->get_column_index(tmp_col_name), StringData("pk_property")); + changed = JNI_TRUE; } // If needed remove "class_" prefix from class names @@ -1577,8 +1584,45 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTable std::string str(table_name.substr(TABLE_PREFIX.length())); StringData sd(str); pk_table->set_string(CLASS_COLUMN_INDEX, row_ndx, sd); + changed = JNI_TRUE; + } + } + + // From realm-java 2.0.0, pk table's class column requires a search index. + if (!pk_table->has_search_index(CLASS_COLUMN_INDEX)) { + pk_table->add_search_index(CLASS_COLUMN_INDEX); + changed = JNI_TRUE; + } + return changed; +} + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_Table_nativePrimaryKeyTableNeedsMigration(JNIEnv *, jclass, jlong primaryKeyTableNativePtr) +{ + + const size_t CLASS_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX; + const size_t FIELD_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX; + + Table* pk_table = TBL(primaryKeyTableNativePtr); + + // Fix wrong types (string, int) -> (string, string) + if (pk_table->get_column_type(FIELD_COLUMN_INDEX) == type_Int) { + return JNI_TRUE; + } + + // If needed remove "class_" prefix from class names + size_t number_of_rows = pk_table->size(); + for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { + StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); + if (table_name.begins_with(TABLE_PREFIX)) { + return JNI_TRUE; } } + // From realm-java 2.0.0, pk table's class column requires a search index. + if (!pk_table->has_search_index(CLASS_COLUMN_INDEX)) { + return JNI_TRUE; + } + return JNI_FALSE; } JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 4100b8fc40..feed85b287 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 4100b8fc407176cdf09e6f394bd111595a6412aa +Subproject commit feed85b287f6822743825a033ddcf8c6253440fa diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index dd7cd65632..e439408843 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -26,6 +26,8 @@ import io.realm.exceptions.RealmFileException; import io.realm.internal.ColumnIndices; +import io.realm.internal.SharedRealm; +import io.realm.internal.Table; import io.realm.log.RealmLog; /** @@ -117,6 +119,19 @@ static synchronized E createRealmOrGetFromCache(RealmConfi RefAndCount refAndCount = cache.refAndCountMap.get(RealmCacheType.valueOf(realmClass)); + if (refAndCount.globalCount == 0) { + SharedRealm sharedRealm = SharedRealm.getInstance(configuration); + if (Table.primaryKeyTableNeedsMigration(sharedRealm)) { + sharedRealm.beginTransaction(); + if (Table.migratePrimaryKeyTableIfNeeded(sharedRealm)) { + sharedRealm.commitTransaction(); + } else { + sharedRealm.cancelTransaction(); + } + } + sharedRealm.close(); + } + if (refAndCount.localRealm.get() == null) { // Create a new local Realm instance BaseRealm realm; diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index c99ace2268..88ba67d577 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -214,7 +214,7 @@ public void renameColumn(long columnIndex, String newName) { } long pkRowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, className); if (pkRowIndex != NO_MATCH) { - pkTable.setString(PRIMARY_KEY_FIELD_COLUMN_INDEX, pkRowIndex, newName, false); + nativeSetString(pkTable.nativePtr, PRIMARY_KEY_FIELD_COLUMN_INDEX, pkRowIndex, newName, false); } else { throw new IllegalStateException("Non-existent PrimaryKey column cannot be renamed"); } @@ -879,10 +879,10 @@ private Table getPrimaryKeyTable() { } Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); if (pkTable.getColumnCount() == 0) { - pkTable.addColumn(RealmFieldType.STRING, PRIMARY_KEY_CLASS_COLUMN_NAME); + checkImmutable(); + long columnIndex = pkTable.addColumn(RealmFieldType.STRING, PRIMARY_KEY_CLASS_COLUMN_NAME); + pkTable.addSearchIndex(columnIndex); pkTable.addColumn(RealmFieldType.STRING, PRIMARY_KEY_FIELD_COLUMN_NAME); - } else { - migratePrimaryKeyTableIfNeeded(sharedRealm.getGroupNative(), pkTable); } return pkTable; @@ -904,8 +904,23 @@ private void invalidateCachedPrimaryKeyIndex() { * This will remove the prefix "class_" from all table names in the pk_column * Any database created on Realm-Java 0.84.1 and below will have this error. */ - private void migratePrimaryKeyTableIfNeeded(long groupNativePtr, Table pkTable) { - nativeMigratePrimaryKeyTableIfNeeded(groupNativePtr, pkTable.nativePtr); + public static boolean migratePrimaryKeyTableIfNeeded(SharedRealm sharedRealm) { + if (sharedRealm == null || !sharedRealm.isInTransaction()) { + throwImmutable(); + } + if (!sharedRealm.hasTable(PRIMARY_KEY_TABLE_NAME)) { + return false; + } + Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); + return nativeMigratePrimaryKeyTableIfNeeded(sharedRealm.getGroupNative(), pkTable.nativePtr); + } + + public static boolean primaryKeyTableNeedsMigration(SharedRealm sharedRealm) { + if (!sharedRealm.hasTable(PRIMARY_KEY_TABLE_NAME)) { + return false; + } + Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); + return nativePrimaryKeyTableNeedsMigration(pkTable.nativePtr); } public boolean hasSearchIndex(long columnIndex) { @@ -1266,7 +1281,7 @@ public long syncIfNeeded() { throw new RuntimeException("Not supported for tables"); } - private void throwImmutable() { + private static void throwImmutable() { throw new IllegalStateException("Changing Realm data can only be done from inside a transaction."); } @@ -1356,7 +1371,8 @@ public static String tableNameToClassName(String tableName) { public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data, boolean isDefault); public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); private native long nativeSetPrimaryKey(long privateKeyTableNativePtr, long nativePtr, String columnName); - private native void nativeMigratePrimaryKeyTableIfNeeded(long groupNativePtr, long primaryKeyTableNativePtr); + private static native boolean nativeMigratePrimaryKeyTableIfNeeded(long groupNativePtr, long primaryKeyTableNativePtr); + private static native boolean nativePrimaryKeyTableNeedsMigration(long primaryKeyTableNativePtr); private native void nativeAddSearchIndex(long nativePtr, long columnIndex); private native void nativeRemoveSearchIndex(long nativePtr, long columnIndex); private native boolean nativeHasSearchIndex(long nativePtr, long columnIndex); From 33322d2824852dfdc44b6e6097c97bb95e8b8ad8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 27 Sep 2016 02:23:38 -0500 Subject: [PATCH 0114/2110] Disable SyncConfig.Builder.deleteRealmOnLogout (#139) See https://github.com/realm/realm-core/issues/2165 --- .../objectServer/java/io/realm/SyncConfiguration.java | 2 ++ .../src/objectServer/java/io/realm/User.java | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index e6eeebbca3..e930e14c27 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -536,10 +536,12 @@ private String MD5(String in) { * The default behavior is that the Realm file is allowed to stay behind, making it possible for users to log * in again and have access to their data faster. */ + /* FIXME: Disable this API since we cannot support it without https://github.com/realm/realm-core/issues/2165 public Builder deleteRealmOnLogout() { this.deleteRealmOnLogout = true; return this; } + */ /** * Creates the RealmConfiguration based on the builder parameters. diff --git a/realm/realm-library/src/objectServer/java/io/realm/User.java b/realm/realm-library/src/objectServer/java/io/realm/User.java index 41515d765c..cab8285653 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/User.java @@ -204,13 +204,15 @@ public void run() { /** * Logs out the user from the Realm Object Server. Once the Object Server has confirmed the logout any registered * {@link AuthenticationListener} will be notified and user credentials will be deleted from this device. - *

      - * Any Realms owned by the user will be deleted if {@link SyncConfiguration.Builder#deleteRealmOnLogout()} is - * also set. * * @throws IllegalStateException if any Realms owned by this user is still open. They should be closed before * logging out. */ + /* FIXME: Add this back to the javadoc when enable SyncConfiguration.Builder#deleteRealmOnLogout() +

      + Any Realms owned by the user will be deleted if {@link SyncConfiguration.Builder#deleteRealmOnLogout()} is + also set. + */ public void logout() { // Acquire lock to prevent users creating new instances synchronized (Realm.class) { @@ -256,6 +258,8 @@ protected void onSuccess(LogoutResponse response) { // Delete all Realms if needed. for (SyncUser.AccessDescription desc : syncUser.getRealms()) { + // FIXME: This will always be false since SyncConfiguration.Builder.deleteRealmOnLogout() is + // disabled. Make sure this works for Realm opened in the client thread/other processes. if (desc.deleteOnLogout) { File realmFile = new File(desc.localPath); if (realmFile.exists() && !Util.deleteRealm(desc.localPath, realmFile.getParentFile(), realmFile.getName())) { From 0f22b6a7d0affce4d6bf876ad26ec9767eb50e54 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 27 Sep 2016 09:45:42 +0200 Subject: [PATCH 0115/2110] Update ObjectServerExample (#190) --- examples/objectServerExample/README.md | 14 +++++ examples/objectServerExample/build.gradle | 33 ++++++++++-- .../objectserver/CounterActivity.java | 53 ++++++------------- .../examples/objectserver/LoginActivity.java | 2 +- .../examples/objectserver/MyApplication.java | 1 - 5 files changed, 62 insertions(+), 41 deletions(-) create mode 100644 examples/objectServerExample/README.md diff --git a/examples/objectServerExample/README.md b/examples/objectServerExample/README.md new file mode 100644 index 0000000000..f9ec43ab2d --- /dev/null +++ b/examples/objectServerExample/README.md @@ -0,0 +1,14 @@ +# Using this example + +This example shows a minimal example on how to connect to and use the +Realm Object Server to synchronize changes between devices. + +The example will assume that the Object Server is running on the machine +building the example and the IP address will automatically be injected +into the build configuration. + +If this for some reasons does not work, please insert the IP Address into +the `build.gradle` accordingly. + +To read more about the Realm Object Server and how to deploy it, see +https://realm.io/news/introducing-realm-mobile-platform/ diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 51de41a750..dd0e37e1ae 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -2,6 +2,26 @@ apply plugin: 'com.android.application' apply plugin: 'android-command' apply plugin: 'realm-android' +// Credit: http://jeremie-martinez.com/2015/05/05/inject-host-gradle/ +def getIP() { + InetAddress result = null; + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + Enumeration addresses = interfaces.nextElement().getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + if (!address.isLoopbackAddress()) { + if (address.isSiteLocalAddress()) { + return address.getHostAddress(); + } else if (result == null) { + result = address; + } + } + } + } + return (result != null ? result : InetAddress.getLocalHost()).getHostAddress(); +} + android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools @@ -15,11 +35,18 @@ android { } buildTypes { - release { - minifyEnabled false - } + // This will automatically try to detect the IP address of the machine + // building the example. It is assumed that this machine is also running + // the Object Server. If not, replace 'host' with the IP of the machine + // hosting the server. In some cases the wrong IP address will also + // be detected. In that case also insert the IP address manually. + def host = getIP() debug { + buildConfigField "String", "OBJECT_SERVER_IP", "\"${host}\"" + } + release { minifyEnabled false + buildConfigField "String", "OBJECT_SERVER_IP", "\"${host}\"" } } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 5537ca1d65..accbf13284 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -23,24 +23,23 @@ import android.view.MenuItem; import android.widget.TextView; +import java.util.Locale; + import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import io.realm.Realm; import io.realm.RealmChangeListener; -import io.realm.RealmResults; -import io.realm.examples.objectserver.model.CRDTCounter; -import io.realm.examples.objectserver.model.CounterOperation; import io.realm.SyncConfiguration; -import io.realm.SyncManager; import io.realm.User; +import io.realm.examples.objectserver.model.CRDTCounter; public class CounterActivity extends AppCompatActivity { - private static final String REALM_URL = "realm://" + MyApplication.OBJECT_SERVER_IP + ":7800/~/default"; + private static final String REALM_URL = "realm://" + BuildConfig.OBJECT_SERVER_IP + ":9080/~/default"; private Realm realm; - private RealmResults counter; + private CRDTCounter counter; private User user; @BindView(R.id.text_counter) TextView counterView; @@ -67,10 +66,7 @@ protected void onStart() { .initialData(new Realm.Transaction() { @Override public void execute(Realm realm) { - // Workaround for initialData right now https://github.com/realm/realm-java-private/issues/164 - if (realm.isEmpty()) { - realm.createObject(CRDTCounter.class, 1); - } + realm.createObject(CRDTCounter.class, 1); } }) .build(); @@ -78,30 +74,14 @@ public void execute(Realm realm) { // This will automatically sync all changes in the background for as long as the Realm is open realm = Realm.getInstance(config); - // FIXME Looks like PrimaryKey and lists are not working correctly yet - // FIXME Also need support for the `setDefault` instruction for this to make sense. -// counter = realm.where(CRDTCounter.class).findFirstAsync(); -// counter.addChangeListener(new RealmChangeListener() { -// @Override -// public void onChange(CRDTCounter counter) { -// if (counter.isValid()) { -// counterView.setText(String.format(Locale.US, "%d", counter.getCount())); -// } else { -// counterView.setText("-"); -// } -// } -// }); - - counter = realm.where(CounterOperation.class).findAllAsync(); - counter.addChangeListener(new RealmChangeListener>() { + counter = realm.where(CRDTCounter.class).findFirstAsync(); + counter.addChangeListener(new RealmChangeListener() { @Override - public void onChange(RealmResults result) { - // FIXME Why isn't this triggered when the DB is opened? - Number sum = result.sum("adjustment"); - if (sum != null) { - counterView.setText(Long.toString(sum.longValue())); + public void onChange(CRDTCounter counter) { + if (counter.isValid()) { + counterView.setText(String.format(Locale.US, "%d", counter.getCount())); } else { - counterView.setText("0"); + counterView.setText("-"); } } }); @@ -144,21 +124,22 @@ public boolean onOptionsItemSelected(MenuItem item) { @OnClick(R.id.upper) public void incrementCounter() { - adjustCounter(new CounterOperation(1)); + adjustCounter(1); } @OnClick(R.id.lower) public void decrementCounter() { - adjustCounter(new CounterOperation(-1)); + adjustCounter(-1); } - private void adjustCounter(final CounterOperation ops) { + private void adjustCounter(final int adjustment) { // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI // thread is HIGHLY discouraged as it might block longer than intended. Only use async transactions. realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { - realm.copyToRealm(ops); + CRDTCounter counter = realm.where(CRDTCounter.class).findFirst(); + counter.add(adjustment); } }); } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java index bd02d2d2fe..7018e8bd66 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -77,7 +77,7 @@ public void login(boolean createUser) { String password = this.password.getText().toString(); Credentials creds = Credentials.usernamePassword(username, password, createUser); - String authUrl = "http://" + MyApplication.OBJECT_SERVER_IP + ":8080/auth"; + String authUrl = "http://" + BuildConfig.OBJECT_SERVER_IP + ":9080/auth"; User.Callback callback = new User.Callback() { @Override public void onSuccess(User user) { diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java index 05d7567940..45050d8d89 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java @@ -25,7 +25,6 @@ public class MyApplication extends Application { - public static final String OBJECT_SERVER_IP = "192.168.104.22"; @Override public void onCreate() { super.onCreate(); From f039099d7d40505cae1b39f2a8d09914705fe6c8 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 27 Sep 2016 10:12:20 +0200 Subject: [PATCH 0116/2110] Updating license (#192) --- LICENSE | 36 +++++++----------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/LICENSE b/LICENSE index 62e2f9e364..f2d098fd9c 100644 --- a/LICENSE +++ b/LICENSE @@ -4,7 +4,7 @@ TABLE OF CONTENTS 2. Realm Components 3. Export Compliance -------------------------------------------------------------------------------- +1. ------------------------------------------------------------------------------- Apache License Version 2.0, January 2004 @@ -183,31 +183,7 @@ TABLE OF CONTENTS END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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. - +2. ------------------------------------------------------------------------------- REALM COMPONENTS @@ -215,11 +191,11 @@ This software contains components with separate copyright and license terms. Your use of these components is subject to the terms and conditions of the following licenses. -For the Realm Core component +For the Realm Platform Extensions component - Realm Core Binary License + Realm Platform Extensions License - Copyright (c) 2011-2015 Realm Inc All rights reserved + Copyright (c) 2011-2014 Realm Inc All rights reserved Redistribution and use in binary form, with or without modification, is permitted provided that the following conditions are met: @@ -250,6 +226,8 @@ For the Realm Core component ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +3. ------------------------------------------------------------------------------- + EXPORT COMPLIANCE You understand that the Software may contain cryptographic functions that may be From 1d7ae8038fbf965034428aa5e4bd119682d8a7c9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 27 Sep 2016 10:21:20 +0200 Subject: [PATCH 0117/2110] Update LICENSE (#193) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f2d098fd9c..273b4d5f7b 100644 --- a/LICENSE +++ b/LICENSE @@ -195,7 +195,7 @@ For the Realm Platform Extensions component Realm Platform Extensions License - Copyright (c) 2011-2014 Realm Inc All rights reserved + Copyright (c) 2011-2016 Realm Inc All rights reserved Redistribution and use in binary form, with or without modification, is permitted provided that the following conditions are met: From 713ea641dfa655b3e9bf4a3aca9958362f1add41 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 27 Sep 2016 04:47:22 -0500 Subject: [PATCH 0118/2110] Fix tests (#194) * Remove unused tests. * Fix tests with default port[80]. * One failed test caused by wrong mocked JSON string. --- .../java/io/realm/SessionTests.java | 2 +- .../java/io/realm/SyncConfigurationTests.java | 4 +- .../java/io/realm/UserTests.java | 52 +------------------ 3 files changed, 6 insertions(+), 52 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 8554b2280e..1fe1112e48 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -71,7 +71,7 @@ public void get_syncValues() { ); Session session = new Session(internalSession); - assertEquals("realm://objectserver.realm.io:80/JohnDoe/default", session.getServerUrl().toString()); + assertEquals("realm://objectserver.realm.io/JohnDoe/default", session.getServerUrl().toString()); assertEquals(user, session.getUser()); assertEquals(configuration, session.getConfiguration()); assertNull(session.getState()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 4a57ddec19..8cd5e59097 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -280,7 +280,7 @@ public void get_syncSpecificValues() { String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); assertTrue(user.equals(config.getUser())); - assertEquals("realm://objectserver.realm.io:80/default", config.getServerUrl().toString()); + assertEquals("realm://objectserver.realm.io/default", config.getServerUrl().toString()); assertFalse(config.shouldDeleteRealmOnLogout()); assertTrue(config.isSyncConfiguration()); } @@ -340,6 +340,7 @@ public void directory_dirIsAFile() throws IOException { file.delete(); // clean up } + /* FIXME: deleteRealmOnLogout is not supported by now @Test public void deleteOnLogout() { User user = createTestUser(); @@ -350,6 +351,7 @@ public void deleteOnLogout() { .build(); assertTrue(config.shouldDeleteRealmOnLogout()); } + */ @Test public void initialData() { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java index 01df4e1724..4cf3601779 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java @@ -69,6 +69,7 @@ public void currentUser_returnsNullIfUserExpired() { } // Tests that the user store returns the last user to login + /* FIXME: This test fails because of wrong JSON string. @Test public void currentUser_returnsUserAfterLogin() { AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); @@ -77,54 +78,5 @@ public void currentUser_returnsUserAfterLogin() { User user = User.login(Credentials.facebook("foo"), "http://bar.com/auth"); assertEquals(user, User.currentUser()); } - - // Tests that if a user logs in, the refreshToken is refreshed before it expires. - @RunTestInLooperThread - @Test - public void login_refreshWhenExpiring() { - // Setup server responses - // Expires in 30 seconds and Refresh starts 30 seconds before it expires. This should trigger a refresh - // immediately. - long expires = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30); - AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); - when(authServer.loginUser(any(Credentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(expires)); - when(authServer.refreshUser(any(Token.class), any(URL.class))).then(new Answer() { - @Override - public AuthenticateResponse answer(InvocationOnMock invocation) throws Throwable { - looperThread.testComplete(); - return SyncTestUtils.createRefreshResponse(); - } - }); - - // Login (which will trigger a refreshUser) - SyncManager.setAuthServerImpl(authServer); - User.login(Credentials.facebook("foo"), "http://bar.com/auth"); - } - - // Tests that if a user is loaded from storage, it will still be refreshed when expiring. - @RunTestInLooperThread - @Test - public void currentUser_refreshWhenExpiring() { - // Setup - // Expires in 30 seconds and Refresh starts 30 seconds before it expires. This should trigger a refresh - // immediately. - long expires = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30); - AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); - when(authServer.refreshUser(any(Token.class), any(URL.class))).then(new Answer() { - @Override - public AuthenticateResponse answer(InvocationOnMock invocation) throws Throwable { - looperThread.testComplete(); - return SyncTestUtils.createRefreshResponse(); - } - }); - - SyncManager.setUserStore(new SharedPrefsUserStore(InstrumentationRegistry.getContext())); - SyncManager.setAuthServerImpl(authServer); - User testUser = SyncTestUtils.createTestUser(expires); - SyncManager.getUserStore().put(UserStore.CURRENT_USER_KEY, testUser); - - // Load user from storage. This should also trigger a refresh when the user expires - User user = User.currentUser(); - assertEquals(testUser, user); - } + */ } From 1062d2462eaf841c215d7962dea979f65032fb5a Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Tue, 27 Sep 2016 12:51:33 +0200 Subject: [PATCH 0119/2110] Use https for the submodule (#195) --- .gitmodules | 2 +- Jenkinsfile | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index 4e9399aee9..35b419c520 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "realm/realm-library/src/main/cpp/object-store"] path = realm/realm-library/src/main/cpp/object-store - url = git@github.com:realm/realm-object-store-private.git + url = https://github.com/realm/realm-object-store.git diff --git a/Jenkinsfile b/Jenkinsfile index c54298ac86..7b511a21e4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,10 +15,8 @@ try { extensions: scm.extensions + [[$class: 'CleanCheckout']], userRemoteConfigs: scm.userRemoteConfigs ]) - sshagent(['realm-ci-ssh']) { - sh 'git submodule sync' - sh 'git submodule update --init --recursive' - } + sh 'git submodule sync' + sh 'git submodule update --init --recursive' // Make sure not to delete the folder that Jenkins allocates to store scripts sh 'git clean -ffdx -e .????????' From 1f19a05f820c1e43a9a0e38d8e32b0d96920df7f Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Tue, 27 Sep 2016 13:24:02 +0200 Subject: [PATCH 0120/2110] Release v2.0.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 155b9cb2e9..359a5b952d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.0-SNAPSHOT \ No newline at end of file +2.0.0 \ No newline at end of file From a56463af4e742697861ca2796af2930a0156b4a3 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Tue, 27 Sep 2016 13:24:03 +0200 Subject: [PATCH 0121/2110] Prepare next release v2.0.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 359a5b952d..5e38773b3d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.0 \ No newline at end of file +2.0.1-SNAPSHOT \ No newline at end of file From bf5ed071d28a0e5b2b13895e1f26cf783103cc76 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 28 Sep 2016 18:04:33 +0900 Subject: [PATCH 0122/2110] move SyncObjectServerFacade.java to objectserver directory --- .../SyncObjectServerFacade.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) rename realm/realm-library/src/objectServer/java/io/realm/internal/{ => objectserver}/SyncObjectServerFacade.java (83%) diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java similarity index 83% rename from realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java index 5b925e9fef..b8d10af862 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java @@ -1,5 +1,20 @@ -package io.realm.internal.objectserver; +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm.internal.objectserver; import android.annotation.SuppressLint; import android.content.Context; @@ -15,7 +30,7 @@ import io.realm.internal.Keep; import io.realm.internal.ObjectServerFacade; -@SuppressWarnings("unused") // Used through reflection. See ObjectServerFacade +@SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep public class SyncObjectServerFacade extends ObjectServerFacade { @@ -45,7 +60,7 @@ public void init(Context context) { throw new RealmException("Could not initialize the Realm Object Server", e); } if (applicationContext == null) { - applicationContext = context; + applicationContext = context; } } @@ -88,7 +103,7 @@ public String[] getUserAndServerUrl(RealmConfiguration config) { SyncConfiguration syncConfig = (SyncConfiguration) config; String rosServerUrl = syncConfig.getServerUrl().toString(); String rosUserToken = syncConfig.getUser().getAccessToken(); - return new String[] {rosServerUrl, rosUserToken}; + return new String[]{rosServerUrl, rosUserToken}; } else { return new String[2]; } From 729d0ce13b0e043462eb66b637d6652540a89ffb Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Wed, 28 Sep 2016 11:47:39 +0200 Subject: [PATCH 0123/2110] Append the version number to the name of all the artifacts uploaded to Bintray (#3512) --- realm/realm-library/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 8a8158f7be..484c3e7308 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -558,7 +558,7 @@ android.productFlavors.all { flavor -> "${buildDir}/outputs/aar/realm-android-library-${flavor.name}-release.aar", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}.aar?publish=0" + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-{project.version}.aar?publish=0" } task("bintraySources${flavor.name.capitalize()}", type: Exec) { @@ -571,7 +571,7 @@ android.productFlavors.all { flavor -> "${buildDir}/libs/realm-android-library-${project.version}-sources.jar", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-sources.jar?publish=0" + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-sources-{project.version}.jar?publish=0" } task("bintrayJavadoc${flavor.name.capitalize()}", type: Exec) { @@ -584,7 +584,7 @@ android.productFlavors.all { flavor -> "${buildDir}/libs/realm-android-library-${project.version}-javadoc.jar", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-javadoc.jar?publish=0" + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-javadoc-{project.version}.jar?publish=0" } task("bintrayPom${flavor.name.capitalize()}", type: Exec) { @@ -597,7 +597,7 @@ android.productFlavors.all { flavor -> "${buildDir}/publications/${flavor.name}Publication/pom-default.xml", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}.pom?publish=0" + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-{project.version}.pom?publish=0" } task("bintray${flavor.name.capitalize()}") { From 9de7182c6214d316ce965ca81a19e19b71daafc4 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Wed, 28 Sep 2016 14:25:43 +0200 Subject: [PATCH 0124/2110] Add support for ccache (#3518) --- Dockerfile | 29 ++++++++++++++++++++--------- Jenkinsfile | 2 +- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index b607664cc2..a632ba67e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,18 +13,29 @@ ENV ANDROID_HOME /opt/android-sdk-linux ENV ANDROID_NDK_HOME /opt/android-ndk ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} +ENV NDK_CCACHE /usr/bin/ccache -# Install the JDK -# We are going to need some 32 bit binaries because aapt requires it -# file is need by the script that creates NDK toolchains +# The 32 bit binaries because aapt requires it +# `file` is need by the script that creates NDK toolchains +# Keep the packages in alphabetical order to make it easy to avoid duplication RUN DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 \ && apt-get update -qq \ - && apt-get install -y file git curl wget zip unzip \ - bsdmainutils \ - build-essential \ - openjdk-8-jdk-headless \ - libc6:i386 libstdc++6:i386 libgcc1:i386 libncurses5:i386 libz1:i386 \ - s3cmd \ + && apt-get install -y bsdmainutils \ + build-essential \ + ccache \ + curl \ + file \ + git \ + libc6:i386 \ + libgcc1:i386 \ + libncurses5:i386 \ + libstdc++6:i386 \ + libz1:i386 \ + openjdk-8-jdk-headless \ + s3cmd \ + unzip \ + wget \ + zip \ && apt-get clean # Install the Android SDK diff --git a/Jenkinsfile b/Jenkinsfile index 7b511a21e4..5ae8ce94fd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,7 +22,7 @@ try { stage 'Docker build' def buildEnv = docker.build 'realm-java:snapshot' - buildEnv.inside("-e HOME=/tmp -e _JAVA_OPTIONS=-Duser.home=/tmp --privileged -v /dev/bus/usb:/dev/bus/usb -v ${env.HOME}/gradle-cache:/tmp/.gradle -v ${env.HOME}/.android:/tmp/.android") { + buildEnv.inside("-e HOME=/tmp -e _JAVA_OPTIONS=-Duser.home=/tmp --privileged -v /dev/bus/usb:/dev/bus/usb -v ${env.HOME}/gradle-cache:/tmp/.gradle -v ${env.HOME}/.android:/tmp/.android -v ${env.HOME}/ccache:/tmp/.ccache") { stage 'JVM tests' try { withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { From 0c34f8ce9c5c31e18bdb9c2a4850d7cfbd675943 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 28 Sep 2016 15:42:56 +0200 Subject: [PATCH 0125/2110] Compacting encrypted Realms is working. (#3520) --- CHANGELOG.md | 6 +++++ .../androidTest/java/io/realm/RealmTests.java | 25 +++++++++---------- .../src/main/java/io/realm/BaseRealm.java | 4 --- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4627bcd181..cdede1b26f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.1 + +### Enhancement + +* `Realm.compactRealm()` works for encrypted Realms. + ## 2.0.0 This release introduces support for the Realm Mobile Platform! diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index a9205744ba..fa5f8f67e5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -982,27 +982,26 @@ public void compactRealm_encryptedEmptyRealm() { RealmConfiguration realmConfig = configFactory.createConfiguration("enc.realm", TestHelper.getRandomKey()); Realm realm = Realm.getInstance(realmConfig); realm.close(); - // TODO: remove try/catch block when compacting encrypted Realms is supported - try { - assertTrue(Realm.compactRealm(realmConfig)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertTrue(Realm.compactRealm(realmConfig)); + realm = Realm.getInstance(realmConfig); + assertFalse(realm.isClosed()); + assertTrue(realm.isEmpty()); + realm.close(); } @Test public void compactRealm_encryptedPopulatedRealm() { + final int DATA_SIZE = 100; RealmConfiguration realmConfig = configFactory.createConfiguration("enc.realm", TestHelper.getRandomKey()); Realm realm = Realm.getInstance(realmConfig); - populateTestRealm(realm, 100); + populateTestRealm(realm, DATA_SIZE); + realm.close(); + assertTrue(Realm.compactRealm(realmConfig)); + realm = Realm.getInstance(realmConfig); + assertFalse(realm.isClosed()); + assertEquals(DATA_SIZE, realm.where(AllTypes.class).count()); realm.close(); - // TODO: remove try/catch block when compacting encrypted Realms is supported - try { - assertTrue(Realm.compactRealm(realmConfig)); - fail(); - } catch (IllegalArgumentException expected) { - } } @Test diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index cc1d89dd79..d3dc068a43 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -583,10 +583,6 @@ public void onResult(int count) { * @return {@code true} if compaction succeeded, {@code false} otherwise. */ static boolean compactRealm(final RealmConfiguration configuration) { - // https://github.com/realm/realm-java/issues/1033 - if (configuration.getEncryptionKey() != null) { - throw new IllegalArgumentException("Cannot currently compact an encrypted Realm."); - } SharedRealm sharedRealm = SharedRealm.getInstance(configuration); Boolean result = sharedRealm.compact(); sharedRealm.close(); From 23b387fa2fad408898be111370f9818b9ffd3fa6 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 28 Sep 2016 18:04:33 +0900 Subject: [PATCH 0126/2110] move SyncObjectServerFacade.java to objectserver directory --- .../SyncObjectServerFacade.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) rename realm/realm-library/src/objectServer/java/io/realm/internal/{ => objectserver}/SyncObjectServerFacade.java (83%) diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java similarity index 83% rename from realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java index 5b925e9fef..b8d10af862 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java @@ -1,5 +1,20 @@ -package io.realm.internal.objectserver; +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm.internal.objectserver; import android.annotation.SuppressLint; import android.content.Context; @@ -15,7 +30,7 @@ import io.realm.internal.Keep; import io.realm.internal.ObjectServerFacade; -@SuppressWarnings("unused") // Used through reflection. See ObjectServerFacade +@SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep public class SyncObjectServerFacade extends ObjectServerFacade { @@ -45,7 +60,7 @@ public void init(Context context) { throw new RealmException("Could not initialize the Realm Object Server", e); } if (applicationContext == null) { - applicationContext = context; + applicationContext = context; } } @@ -88,7 +103,7 @@ public String[] getUserAndServerUrl(RealmConfiguration config) { SyncConfiguration syncConfig = (SyncConfiguration) config; String rosServerUrl = syncConfig.getServerUrl().toString(); String rosUserToken = syncConfig.getUser().getAccessToken(); - return new String[] {rosServerUrl, rosUserToken}; + return new String[]{rosServerUrl, rosUserToken}; } else { return new String[2]; } From b97ac1cb6747aa52915d96fd29654854944e33f9 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 29 Sep 2016 01:24:14 +0900 Subject: [PATCH 0127/2110] change master version to 2.1.0-SNAPSHOT (#3525) --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdede1b26f..92a80ddff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.0.1 +## 2.1.0 ### Enhancement diff --git a/version.txt b/version.txt index 5e38773b3d..19d5f5f9c6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.1-SNAPSHOT \ No newline at end of file +2.1.0-SNAPSHOT \ No newline at end of file From 8b1574b6588a0f99df411338164ff10f39f03adb Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 29 Sep 2016 02:16:29 +0900 Subject: [PATCH 0128/2110] move registering NetworkStateReceiver to objectServer flavor (#3509) * move registering NetworkStateReceiver to objectServer flavor * added CHANGELOG --- CHANGELOG.md | 7 +++++++ realm/realm-library/src/main/AndroidManifest.xml | 14 -------------- .../src/objectServer/AndroidManifest.xml | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 realm/realm-library/src/objectServer/AndroidManifest.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 4627bcd181..d6b44c9178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.0.1 + +### Bug fixes + +* Fixed a bug that `android.net.conn.CONNECTIVITY_CHANGE` broadcast caused `RuntimeException` if sync extension was disabled (#3505). + + ## 2.0.0 This release introduces support for the Realm Mobile Platform! diff --git a/realm/realm-library/src/main/AndroidManifest.xml b/realm/realm-library/src/main/AndroidManifest.xml index 5de8dda36c..6c57d744f5 100644 --- a/realm/realm-library/src/main/AndroidManifest.xml +++ b/realm/realm-library/src/main/AndroidManifest.xml @@ -2,18 +2,4 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/realm/realm-library/src/objectServer/AndroidManifest.xml b/realm/realm-library/src/objectServer/AndroidManifest.xml new file mode 100644 index 0000000000..0f07d36378 --- /dev/null +++ b/realm/realm-library/src/objectServer/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file From c4f68035eb6b44471f6271b9236e8b3676ce3c75 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 29 Sep 2016 14:07:31 +0900 Subject: [PATCH 0129/2110] use Context.registerReceiver() instead of AndroidManifest.xml to register NetworkStateReceiver for android.net.conn.CONNECTIVITY_CHANGE broadcast. (#3529) On Android 7 device, android.net.conn.CONNECTIVITY_CHANGE is not deliverd to receivers registerd by AndroidManifest.xml. See https://developer.android.com/topic/performance/background-optimization.html#connectivity-action fixes #3511 --- CHANGELOG.md | 1 + .../io/realm/internal/ObjectServerFacade.java | 16 ++++++++++++++++ .../src/objectServer/AndroidManifest.xml | 7 ------- .../objectServer/java/io/realm/ObjectServer.java | 16 ++++++++++++++++ .../internal/objectserver/SessionStore.java | 16 ++++++++++++++++ .../objectserver/SyncObjectServerFacade.java | 6 ++++++ .../io/realm/internal/objectserver/SyncUtil.java | 16 ++++++++++++++++ 7 files changed, 71 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6b44c9178..ff031b203c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug fixes * Fixed a bug that `android.net.conn.CONNECTIVITY_CHANGE` broadcast caused `RuntimeException` if sync extension was disabled (#3505). +* Fixed a bug that `android.net.conn.CONNECTIVITY_CHANGE` was not delivered on Android 7 devices. ## 2.0.0 diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index c750708828..8bb11bf554 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + package io.realm.internal; import android.content.Context; diff --git a/realm/realm-library/src/objectServer/AndroidManifest.xml b/realm/realm-library/src/objectServer/AndroidManifest.xml index 0f07d36378..0a7a9b5299 100644 --- a/realm/realm-library/src/objectServer/AndroidManifest.xml +++ b/realm/realm-library/src/objectServer/AndroidManifest.xml @@ -5,11 +5,4 @@ - - - - - - - \ No newline at end of file diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index 069f32b2a1..38b0c0c1cb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + package io.realm; import android.content.Context; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java index 43d4409a33..6bbffe5ae8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + package io.realm.internal.objectserver; import java.util.Collection; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java index b8d10af862..141fc35fe0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java @@ -18,6 +18,8 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.content.IntentFilter; +import android.net.ConnectivityManager; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -29,6 +31,7 @@ import io.realm.exceptions.RealmException; import io.realm.internal.Keep; import io.realm.internal.ObjectServerFacade; +import io.realm.internal.network.NetworkStateReceiver; @SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep @@ -61,6 +64,9 @@ public void init(Context context) { } if (applicationContext == null) { applicationContext = context; + + applicationContext.registerReceiver(new NetworkStateReceiver(), + new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUtil.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUtil.java index 397fb96976..9d6b8f1e23 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUtil.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUtil.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + package io.realm.internal.objectserver; import java.net.URI; From 1d77c7f54f9b97986d68248f5759cf7f31bef72c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 29 Sep 2016 04:03:44 -0500 Subject: [PATCH 0130/2110] Accelerate build with ccache and lcache (#3523) See https://github.com/beeender/lcache for source code for lcache. It is very simple right now. It identify if the link target is cached by using checksums from command line + checksums of all input files. The realm-library gradle check those paths from project properties first , then the system env. So those can be set in the ~/.gradle/gradle.properties like: ccachePath=/usr/bin/ccache lcachePath=/usr/bin/lcache Or by system ENVs like: NDK_CCACHE=/usr/bin/ccache NDK_LCACHE=/usr/bin/lcache --- realm/realm-library/build.gradle | 4 ++++ realm/realm-library/src/main/cpp/CMakeLists.txt | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 484c3e7308..484c3f430a 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -30,6 +30,8 @@ if (!ext.coreArchiveDir) { ext.coreArchiveFile = rootProject.file("${ext.coreArchiveDir}/realm-sync-android-${project.coreVersion}.tar.gz") ext.coreDistributionDir = file("${projectDir}/distribution/realm-core/") ext.coreDir = file("${project.coreDistributionDir.getAbsolutePath()}/core-${project.coreVersion}") +ext.ccachePath = project.findProperty('ccachePath') ?: System.getenv('NDK_CCACHE') +ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE') android { compileSdkVersion 24 @@ -50,6 +52,8 @@ android { // JNI build currently (lack of lto linking support). // This file should be removed and use the one from Android SDK cmake package when it supports lto. "-DCMAKE_TOOLCHAIN_FILE=${project.file('src/main/cpp/android.toolchain.cmake').path}" + if (project.ccachePath) arguments "-DNDK_CCACHE=$project.ccachePath" + if (project.lcachePath) arguments "-DNDK_LCACHE=$project.lcachePath" if (!project.hasProperty('android.injected.build.abi') && project.hasProperty('buildTargetABIs')) { abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*')) } else { diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 39c456f1da..957d90fda2 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -17,6 +17,11 @@ set(CMAKE_VERBOSE_MAKEFILE ON) # Generate compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# Setup lcache +if(NDK_LCACHE) + set(CMAKE_CXX_CREATE_SHARED_LIBRARY "${NDK_LCACHE} ${CMAKE_CXX_CREATE_SHARED_LIBRARY}") +endif() + # Set flag build_SYNC if (REALM_FLAVOR STREQUAL base) set(build_SYNC OFF) @@ -180,4 +185,3 @@ if (CMAKE_BUILD_TYPE STREQUAL "Release") COMMAND ${CMAKE_COMMAND} -E copy $ ${unstripped_SO_DIR} COMMAND ${CMAKE_STRIP} $) endif() - From a0955726c0065f59c1bb808b9d8afb194d3b3027 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Fri, 30 Sep 2016 10:26:02 +0200 Subject: [PATCH 0131/2110] Add support for the multiple flavors to the metrics collection stage (#3527) * Add support for the multiple flavors to the metrics collection stage * Fix a failed escape * Fix another failed escape * Forgot some renames * Put stages in blocks * Scopes matter * Avoid one @NonCPS * Minimize the amount of non-serializable code * Fix typo * Remove debugging comments --- Jenkinsfile | 165 ++++++++++++++++++++++++++++------------------------ 1 file changed, 90 insertions(+), 75 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5ae8ce94fd..5c3dfe4ccf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,69 +7,78 @@ try { node('android') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { - stage 'SCM' - checkout([ - $class: 'GitSCM', - branches: scm.branches, - gitTool: 'native git', - extensions: scm.extensions + [[$class: 'CleanCheckout']], - userRemoteConfigs: scm.userRemoteConfigs - ]) - sh 'git submodule sync' - sh 'git submodule update --init --recursive' - // Make sure not to delete the folder that Jenkins allocates to store scripts - sh 'git clean -ffdx -e .????????' - - stage 'Docker build' - def buildEnv = docker.build 'realm-java:snapshot' + stage('SCM') { + checkout([ + $class: 'GitSCM', + branches: scm.branches, + gitTool: 'native git', + extensions: scm.extensions + [[$class: 'CleanCheckout']], + userRemoteConfigs: scm.userRemoteConfigs + ]) + sh 'git submodule sync' + sh 'git submodule update --init --recursive' + // Make sure not to delete the folder that Jenkins allocates to store scripts + sh 'git clean -ffdx -e .????????' + } + + def buildEnv + stage('Docker build') { + buildEnv = docker.build 'realm-java:snapshot' + } + buildEnv.inside("-e HOME=/tmp -e _JAVA_OPTIONS=-Duser.home=/tmp --privileged -v /dev/bus/usb:/dev/bus/usb -v ${env.HOME}/gradle-cache:/tmp/.gradle -v ${env.HOME}/.android:/tmp/.android -v ${env.HOME}/ccache:/tmp/.ccache") { - stage 'JVM tests' - try { - withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG}" + stage('JVM tests') { + try { + withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { + sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG}" + } + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) } - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) } - stage 'Static code analysis' - try { - gradle('realm', 'findbugs pmd checkstyle') - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) - step([$class: 'CheckStylePublisher', - canComputeNew: false, - defaultEncoding: '', - healthy: '', - pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - unHealthy: '' - ]) + stage('Static code analysis') { + try { + gradle('realm', 'findbugs pmd checkstyle') + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) + step([$class: 'CheckStylePublisher', + canComputeNew: false, + defaultEncoding: '', + healthy: '', + pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', + unHealthy: '' + ]) + } } - stage 'Run instrumented tests' - boolean archiveLog = true - String backgroundPid - try { - backgroundPid = startLogCatCollector() - gradle('realm', 'connectedUnitTests') - archiveLog = false; - } finally { - stopLogCatCollector(backgroundPid, archiveLog) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + stage('Run instrumented tests') { + boolean archiveLog = true + String backgroundPid + try { + backgroundPid = startLogCatCollector() + gradle('realm', 'connectedUnitTests') + archiveLog = false; + } finally { + stopLogCatCollector(backgroundPid, archiveLog) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } } // TODO: add support for running monkey on the example apps if (env.BRANCH_NAME == 'master') { - stage 'Collect metrics' - collectAarMetrics() + stage('Collect metrics') { + collectAarMetrics() + } - stage 'Publish to OJO' - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + stage('Publish to OJO') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + } } } } @@ -121,16 +130,16 @@ def stopLogCatCollector(String backgroundPid, boolean archiveLog) { sh 'rm logcat.txt ' } -def sendMetrics(String metric, String value) { +def sendMetrics(String metricName, String metricValue, Map tags) { + def tagsString = getTagsString(tags) withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: '5b8ad2d9-61a4-43b5-b4df-b8ff6b1f16fa', passwordVariable: 'influx_pass', usernameVariable: 'influx_user']]) { - sh "curl -i -XPOST 'https://greatscott-pinheads-70.c.influxdb.com:8086/write?db=realm' --data-binary '${metric} value=${value}i' --user '${env.influx_user}:${env.influx_pass}'" + sh "curl -i -XPOST 'https://greatscott-pinheads-70.c.influxdb.com:8086/write?db=realm' --data-binary '${metricName},${tagsString} value=${metricValue}i' --user '${env.influx_user}:${env.influx_pass}'" } } -def sendTaggedMetric(String metric, String value, String tagName, String tagValue) { - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: '5b8ad2d9-61a4-43b5-b4df-b8ff6b1f16fa', passwordVariable: 'influx_pass', usernameVariable: 'influx_user']]) { - sh "curl -i -XPOST 'https://greatscott-pinheads-70.c.influxdb.com:8086/write?db=realm' --data-binary '${metric},${tagName}=${tagValue} value=${value}i' --user '${env.influx_user}:${env.influx_pass}'" - } +@NonCPS +def getTagsString(Map tags) { + return tags.collect { k,v -> "$k=$v" }.join(',') } def storeJunitResults(String path) { @@ -141,24 +150,30 @@ def storeJunitResults(String path) { } def collectAarMetrics() { - sh '''set -xe - cd realm/realm-library/build/outputs/aar - unzip realm-android-library-release.aar -d unzipped - find $ANDROID_HOME -name dx | sort -r | head -n 1 > dx - $(cat dx) --dex --output=temp.dex unzipped/classes.jar - cat temp.dex | head -c 92 | tail -c 4 | hexdump -e '1/4 "%d"' > methods - ''' - - sendMetrics('methods', readFile('realm/realm-library/build/outputs/aar/methods')) - - def aarFile = findFiles(glob: 'realm/realm-library/build/outputs/aar/realm-android-library-release.aar')[0] - sendMetrics('aar_size', aarFile.length as String) - - def soFiles = findFiles(glob: 'realm/realm-library/build/outputs/aar/unzipped/jni/*/librealm-jni.so') - for (int i = 0; i < soFiles.length; i++) { - def abiName = soFiles[i].path.tokenize('/')[-2] - def libSize = soFiles[i].length as String - sendTaggedMetric('abi_size', libSize, 'type', abiName) + def flavors = ['base', 'objectServer'] + for (def i = 0; i < flavors.size(); i++) { + def flavor = flavors[i] + sh """set -xe + cd realm/realm-library/build/outputs/aar + unzip realm-android-library-${flavor}-release.aar -d unzipped${flavor} + find \$ANDROID_HOME -name dx | sort -r | head -n 1 > dx + \$(cat dx) --dex --output=temp${flavor}.dex unzipped${flavor}/classes.jar + cat temp${flavor}.dex | head -c 92 | tail -c 4 | hexdump -e '1/4 \"%d\"' > methods${flavor} + """ + + def methods = readFile("realm/realm-library/build/outputs/aar/methods${flavor}") + sendMetrics('methods', methods, ['flavor':flavor]) + + def aarFile = findFiles(glob: "realm/realm-library/build/outputs/aar/realm-android-library-${flavor}-release.aar")[0] + sendMetrics('aar_size', aarFile.length as String, ['flavor':flavor]) + + def soFiles = findFiles(glob: "realm/realm-library/build/outputs/aar/unzipped${flavor}/jni/*/librealm-jni.so") + for (def j = 0; j < soFiles.size(); j++) { + def soFile = soFiles[j] + def abiName = soFile.path.tokenize('/')[-2] + def libSize = soFile.length as String + sendMetrics('abi_size', libSize, ['flavor':flavor, 'type':abiName]) + } } } From 06fee2d30c941c0ac7dd73455793a13b633d70b7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 4 Oct 2016 05:38:26 +0100 Subject: [PATCH 0132/2110] Clean .so files that were created by old build script (#3542) --- realm/realm-library/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 484c3f430a..47de10fc0f 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -511,6 +511,8 @@ if (project.hasProperty('dontCleanJniFiles')) { } else { task cleanExternalBuildFiles(type: Delete) { delete project.file('.externalNativeBuild') + // Clean .so files that were created by old build script (realm/realm-jni/build.gradle). + delete project.file('src/main/jniLibs') } clean.dependsOn cleanExternalBuildFiles } From 91a9b1cedb8872558634591a95d7aa881b4cab9b Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 4 Oct 2016 14:42:04 +0900 Subject: [PATCH 0133/2110] fixed a bug causing ConcurrentModificationException on some Gradle 3.1 environment (#3547) * stop using DependencyResolutionListener in Realm gradle plugin to avoid ConcurrentModificationException in some environment. * update README * update README * clean up the code --- CHANGELOG.md | 1 + .../main/groovy/io/realm/gradle/Realm.groovy | 20 +++---------- .../realm/gradle/RealmPluginExtension.groovy | 28 +++++++++++++++++-- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff031b203c..b2afd7dcb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fixed a bug that `android.net.conn.CONNECTIVITY_CHANGE` broadcast caused `RuntimeException` if sync extension was disabled (#3505). * Fixed a bug that `android.net.conn.CONNECTIVITY_CHANGE` was not delivered on Android 7 devices. +* Fixed a bug causing the `ConcurrentModificationException` while building an application (#3501). ## 2.0.0 diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index d84e9b8622..f3557026e5 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -41,7 +41,8 @@ class Realm implements Plugin { throw new GradleException('Realm gradle plugin only supports android gradle plugin 1.5.0 or later.') } - project.extensions.create('realm', RealmPluginExtension) + def syncEnabledDefault = false + project.extensions.create('realm', RealmPluginExtension, project, syncEnabledDefault) def usesKotlinPlugin = project.plugins.findPlugin('kotlin-android') != null def usesAptPlugin = project.plugins.findPlugin('com.neenbedankt.android-apt') != null @@ -52,6 +53,8 @@ class Realm implements Plugin { project.plugins.apply(AndroidAptPlugin) } + project.android.registerTransform(new RealmTransformer(project)) + project.repositories.add(project.getRepositories().jcenter()) project.dependencies.add("compile", "io.realm:realm-annotations:${Version.VERSION}") if (isKaptProject) { @@ -63,21 +66,6 @@ class Realm implements Plugin { project.dependencies.add("androidTestApt", "io.realm:realm-annotations:${Version.VERSION}") project.dependencies.add("androidTestApt", "io.realm:realm-annotations-processor:${Version.VERSION}") } - - // Using afterEvaluate is now deprecated so this callback is used instead - def compileDeps = project.getConfigurations().getByName("compile").getDependencies() - project.getGradle().addListener(new DependencyResolutionListener() { - @Override - void beforeResolve(ResolvableDependencies resolvableDependencies) { - project.android.registerTransform(new RealmTransformer(project)) - def suffix = project.realm.syncEnabled?'-object-server':'' - compileDeps.add(project.getDependencies().create("io.realm:realm-android-library${suffix}:${Version.VERSION}")) - project.getGradle().removeListener(this) - } - - @Override - void afterResolve(ResolvableDependencies resolvableDependencies) {} - }) } private static boolean isTransformAvailable() { diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy index 46577c5646..ef1f21e97d 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy @@ -16,6 +16,30 @@ package io.realm.gradle +import org.gradle.api.Project + class RealmPluginExtension { - boolean syncEnabled = false -} \ No newline at end of file + private Project project + def boolean syncEnabled + + RealmPluginExtension(Project project, boolean syncEnabledDefault) { + this.project = project + setSyncEnabled(syncEnabledDefault) + } + + void setSyncEnabled(value) { + this.syncEnabled = value; + + // remove realm android library first + project.getConfigurations().getByName("compile").getDependencies().removeIf() { + if (it.group != 'io.realm') { + return false + } + return it.name.startsWith('realm-android-library') + } + + // then add again + def artifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" + project.dependencies.add("compile", "io.realm:${artifactName}:${Version.VERSION}") + } +} From a3a25f6a4498dcf260ab6b19bba0147e9cc320eb Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 4 Oct 2016 16:06:40 +0900 Subject: [PATCH 0134/2110] Use CheckedRow when creating a DynamicRealm Object. (#3551) And this removes a check of UncheckedRow in some constructors of DynamicRealmObject since CheckedRow is never passed to it. --- .../src/main/java/io/realm/BaseRealm.java | 8 +++---- .../src/main/java/io/realm/DynamicRealm.java | 4 ++-- .../java/io/realm/DynamicRealmObject.java | 21 +++++++------------ .../main/java/io/realm/RealmObjectSchema.java | 2 +- .../src/main/java/io/realm/RealmQuery.java | 2 +- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index cc1d89dd79..345c8a6735 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -518,14 +518,14 @@ E get(Class clazz, long rowIndex, boolean acceptDefaul // Used by RealmList/RealmResults // Invariant: if dynamicClassName != null -> clazz == DynamicRealmObject E get(Class clazz, String dynamicClassName, long rowIndex) { - final Table table = (dynamicClassName != null) ? schema.getTable(dynamicClassName) : schema.getTable(clazz); + final boolean isDynamicRealmObject = dynamicClassName != null; + final Table table = isDynamicRealmObject ? schema.getTable(dynamicClassName) : schema.getTable(clazz); E result; - if (dynamicClassName != null) { + if (isDynamicRealmObject) { @SuppressWarnings("unchecked") E dynamicObj = (E) new DynamicRealmObject(this, - (rowIndex != Table.NO_MATCH) ? table.getUncheckedRow(rowIndex) : InvalidRow.INSTANCE, - false); + (rowIndex != Table.NO_MATCH) ? table.getCheckedRow(rowIndex) : InvalidRow.INSTANCE); result = dynamicObj; } else { result = configuration.getSchemaMediator().newInstance(clazz, this, diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 4af7ad524b..9b529e0f24 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -95,12 +95,12 @@ public DynamicRealmObject createObject(String className) { * @throws RealmException if object could not be created due to the primary key being invalid. * @throws IllegalStateException if the model clazz does not have an primary key defined. * @throws IllegalArgumentException if the {@code primaryKeyValue} doesn't have a value that can be converted to the - * expectd value. + * expected value. */ public DynamicRealmObject createObject(String className, Object primaryKeyValue) { Table table = schema.getTable(className); long index = table.addEmptyRowWithPrimaryKey(primaryKeyValue); - return new DynamicRealmObject(this, table.getCheckedRow(index), false); + return new DynamicRealmObject(this, table.getCheckedRow(index)); } /** diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 88ec5cd14e..f48a0a0872 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -32,6 +32,7 @@ * Class that wraps a normal RealmObject in order to allow dynamic access instead of a typed interface. * Using a DynamicRealmObject is slower than using the regular RealmObject class. */ +@SuppressWarnings("WeakerAccess") public final class DynamicRealmObject extends RealmObject implements RealmObjectProxy { private final ProxyState proxyState = new ProxyState(this); @@ -67,24 +68,18 @@ public DynamicRealmObject(RealmModel obj) { proxyState.setConstructionFinished(); } - DynamicRealmObject(BaseRealm realm, Row row, boolean convertTocheckedRow) { + // row must not be an instance of UncheckedRow + DynamicRealmObject(BaseRealm realm, Row row) { proxyState.setRealm$realm(realm); - if (convertTocheckedRow) { - proxyState.setRow$realm((row instanceof CheckedRow) ? (CheckedRow) row : ((UncheckedRow) row).convertToChecked()); - } else { - proxyState.setRow$realm(row); - } + proxyState.setRow$realm(row); proxyState.setConstructionFinished(); } - DynamicRealmObject(String className, BaseRealm realm, Row row, boolean convertTocheckedRow) { + // row must not be an instance of UncheckedRow + DynamicRealmObject(String className, BaseRealm realm, Row row) { proxyState.setClassName(className); proxyState.setRealm$realm(realm); - if (convertTocheckedRow) { - proxyState.setRow$realm((row instanceof CheckedRow) ? (CheckedRow) row : ((UncheckedRow) row).convertToChecked()); - } else { - proxyState.setRow$realm(row); - } + proxyState.setRow$realm(row); proxyState.setConstructionFinished(); } @@ -287,7 +282,7 @@ public DynamicRealmObject getObject(String fieldName) { } else { long linkRowIndex = proxyState.getRow$realm().getLink(columnIndex); CheckedRow linkRow = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getCheckedRow(linkRowIndex); - return new DynamicRealmObject(proxyState.getRealm$realm(), linkRow, false); + return new DynamicRealmObject(proxyState.getRealm$realm(), linkRow); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 9e92389494..febc7e89db 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -570,7 +570,7 @@ public RealmObjectSchema transform(Function function) { if (function != null) { long size = table.size(); for (long i = 0; i < size; i++) { - function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i), false)); + function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i))); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index d50cad7172..566a050d44 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -2102,7 +2102,7 @@ public E findFirstAsync() { final E result; if (isDynamicQuery()) { //noinspection unchecked - result = (E) new DynamicRealmObject(className, realm, Row.EMPTY_ROW, false); + result = (E) new DynamicRealmObject(className, realm, Row.EMPTY_ROW); } else { result = realm.getConfiguration().getSchemaMediator().newInstance( clazz, realm, Row.EMPTY_ROW, realm.getSchema().getColumnInfo(clazz), From 734c9ffab549db689994a1493f608e8089c469f4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 4 Oct 2016 10:10:38 +0200 Subject: [PATCH 0135/2110] distinctAsync now respects other query parameters. (#3539) --- CHANGELOG.md | 8 +++--- .../java/io/realm/RealmAsyncQueryTests.java | 25 +++++++++++++++++++ .../main/cpp/io_realm_internal_TableQuery.cpp | 3 ++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2afd7dcb1..e5fe0e3b80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,10 @@ ### Bug fixes -* Fixed a bug that `android.net.conn.CONNECTIVITY_CHANGE` broadcast caused `RuntimeException` if sync extension was disabled (#3505). -* Fixed a bug that `android.net.conn.CONNECTIVITY_CHANGE` was not delivered on Android 7 devices. -* Fixed a bug causing the `ConcurrentModificationException` while building an application (#3501). - +* `android.net.conn.CONNECTIVITY_CHANGE` broadcast caused `RuntimeException` if sync extension was disabled (#3505). +* `android.net.conn.CONNECTIVITY_CHANGE` was not delivered on Android 7 devices. +* `distinctAsync` did not respect other query parameters (#3537). +* `ConcurrentModificationException` from Gradle when building an application (#3501). ## 2.0.0 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index dc17dab814..dc200d9493 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -1620,6 +1620,31 @@ public void onChange(RealmResults object) { }); } + @Test + @RunTestInLooperThread() + public void distinctAsync_rememberQueryParams() { + final Realm realm = looperThread.realm; + realm.beginTransaction(); + final int TEST_SIZE = 10; + for (int i = 0; i < TEST_SIZE; i++) { + realm.createObject(AllJavaTypes.class, i); + } + realm.commitTransaction(); + + RealmResults results = realm.where(AllJavaTypes.class) + .notEqualTo(AllJavaTypes.FIELD_ID, TEST_SIZE / 2) + .distinctAsync(AllJavaTypes.FIELD_ID); + + results.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults results) { + assertEquals(TEST_SIZE - 1, results.size()); + assertEquals(0, results.where().equalTo(AllJavaTypes.FIELD_ID, TEST_SIZE / 2).count()); + looperThread.testComplete(); + } + }); + } + @Test @RunTestInLooperThread public void distinctAsync_notIndexedFields() throws Throwable { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 2f038e1d69..d5ca832903 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -116,7 +116,8 @@ static jlong getDistinctViewWithHandover case type_Int: case type_Timestamp: case type_String: { - TableView tableView(table->get_distinct_view(S(columnIndex)) ); + TableView tableView(query->find_all()); + tableView.distinct(S(columnIndex)); // handover the result auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); From 8f10e7952a11b5f0bbf1ce5f26c44384a5992df9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 4 Oct 2016 12:35:33 +0200 Subject: [PATCH 0136/2110] Download core from public location. Upgrade to latest sync beta. (#3531) --- CHANGELOG.md | 4 ++++ dependencies.list | 4 ++-- realm/realm-library/build.gradle | 10 ++++------ realm/realm-library/src/main/cpp/object-store | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5fe0e3b80..cfc36dff9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ * `distinctAsync` did not respect other query parameters (#3537). * `ConcurrentModificationException` from Gradle when building an application (#3501). +## Internal + +* Upgraded to Realm Core 2.1.0 / Realm Sync 2.0-BETA + ## 2.0.0 This release introduces support for the Realm Mobile Platform! diff --git a/dependencies.list b/dependencies.list index d3f62f9ee1..a7943dc708 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,2 +1,2 @@ -REALM_SYNC_VERSION=1.0.0-BETA-1.1 -REALM_SYNC_SHA256=1524721b35cffcfc14e1609ea42ebe65f87e134542dbe5164de347bf5586e789 \ No newline at end of file +REALM_SYNC_VERSION=1.0.0-BETA-2.0 +REALM_SYNC_SHA256=c7eb59576b28373283e94dafe42737015657cb0deac435a66c28fc74629ce721 diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 484c3e7308..259e2debc6 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -413,12 +413,10 @@ task downloadCore() { doLast { if (shouldDownloadCore()) { - // CI artifacts are only available if on the internal network or VPN - def downloadUrl = "s3://realm-ci-artifacts/sync/${project.coreVersion}/android/realm-sync-android-${project.coreVersion}.tar.gz" - - println "Downloading ${downloadUrl}" - exec { - commandLine 's3cmd', '-c', project.s3cfg, '-f', 'get', "${downloadUrl}", "${project.coreArchiveFile}" + download { + src "http://static.realm.io/downloads/sync/realm-sync-android-${project.coreVersion}.tar.gz" + dest project.coreArchiveFile + onlyIfNewer false } coreDownloaded = true diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index a0ab785896..c5135a5935 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit a0ab785896b3e362e9703c798b8db729a49c9fda +Subproject commit c5135a5935765fa8993fc1b796ef5ed7da609cdf From 1e5e15357291754d0ceb3a266b3020eced75ad59 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 4 Oct 2016 21:23:20 +0900 Subject: [PATCH 0137/2110] Fix native crash in DynamicRealmObject#setList() (#3550) --- .../io/realm/DynamicRealmObjectTests.java | 97 +++++++++++++------ .../main/cpp/io_realm_internal_CheckedRow.cpp | 2 +- .../cpp/io_realm_internal_UncheckedRow.cpp | 2 +- .../java/io/realm/DynamicRealmObject.java | 62 ++++++++---- .../java/io/realm/internal/CheckedRow.java | 2 +- .../java/io/realm/internal/UncheckedRow.java | 2 +- 6 files changed, 114 insertions(+), 53 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 14c9e278bb..9386beb7cb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -151,7 +151,12 @@ public void typedGetter_illegalFieldNameThrows() { // of failing values. Only difference is the wrong type column has to be different. List args = (type == SupportedType.STRING) ? stringArguments : arguments; try { - callGetter(type, args); + callGetter(dObjTyped, type, args); + fail(); + } catch (IllegalArgumentException ignored) { + } + try { + callGetter(dObjDynamic, type, args); fail(); } catch (IllegalArgumentException ignored) { } @@ -164,9 +169,19 @@ public void typedGetter_wrongUnderlyingTypeThrows() { try { // Make sure we hit the wrong underlying type for all types. if (type == SupportedType.DOUBLE) { - callGetter(type, Arrays.asList(AllJavaTypes.FIELD_STRING)); + callGetter(dObjTyped, type, Arrays.asList(AllJavaTypes.FIELD_STRING)); + } else { + callGetter(dObjTyped, type, Arrays.asList(AllJavaTypes.FIELD_DOUBLE)); + } + fail(type + " failed to throw."); + } catch (IllegalArgumentException ignored) { + } + try { + // Make sure we hit the wrong underlying type for all types. + if (type == SupportedType.DOUBLE) { + callGetter(dObjDynamic, type, Arrays.asList(AllJavaTypes.FIELD_STRING)); } else { - callGetter(type, Arrays.asList(AllJavaTypes.FIELD_DOUBLE)); + callGetter(dObjDynamic, type, Arrays.asList(AllJavaTypes.FIELD_DOUBLE)); } fail(type + " failed to throw."); } catch (IllegalArgumentException ignored) { @@ -175,21 +190,21 @@ public void typedGetter_wrongUnderlyingTypeThrows() { } // Helper method for calling getters with different field names - private void callGetter(SupportedType type, List fieldNames) { + private static void callGetter(DynamicRealmObject target, SupportedType type, List fieldNames) { for (String fieldName : fieldNames) { switch (type) { - case BOOLEAN: dObjTyped.getBoolean(fieldName); break; - case SHORT: dObjTyped.getShort(fieldName); break; - case INT: dObjTyped.getInt(fieldName); break; - case LONG: dObjTyped.getLong(fieldName); break; - case BYTE: dObjTyped.getByte(fieldName); break; - case FLOAT: dObjTyped.getFloat(fieldName); break; - case DOUBLE: dObjTyped.getDouble(fieldName); break; - case STRING: dObjTyped.getString(fieldName); break; - case BINARY: dObjTyped.getBlob(fieldName); break; - case DATE: dObjTyped.getDate(fieldName); break; - case OBJECT: dObjTyped.getObject(fieldName); break; - case LIST: dObjTyped.getList(fieldName); break; + case BOOLEAN: target.getBoolean(fieldName); break; + case SHORT: target.getShort(fieldName); break; + case INT: target.getInt(fieldName); break; + case LONG: target.getLong(fieldName); break; + case BYTE: target.getByte(fieldName); break; + case FLOAT: target.getFloat(fieldName); break; + case DOUBLE: target.getDouble(fieldName); break; + case STRING: target.getString(fieldName); break; + case BINARY: target.getBlob(fieldName); break; + case DATE: target.getDate(fieldName); break; + case OBJECT: target.getObject(fieldName); break; + case LIST: target.getList(fieldName); break; default: fail(); } @@ -209,7 +224,12 @@ public void typedSetter_illegalFieldNameThrows() { for (SupportedType type : SupportedType.values()) { List args = (type == SupportedType.STRING) ? stringArguments : arguments; try { - callSetter(type, args); + callSetter(dObjTyped, type, args); + fail(); + } catch (IllegalArgumentException ignored) { + } + try { + callSetter(dObjDynamic, type, args); fail(); } catch (IllegalArgumentException ignored) { } @@ -223,15 +243,28 @@ public void typedSetter_wrongUnderlyingTypeThrows() { try { // Make sure we hit the wrong underlying type for all types. if (type == SupportedType.STRING) { - callSetter(type, Arrays.asList(AllJavaTypes.FIELD_BOOLEAN)); + callSetter(dObjTyped, type, Arrays.asList(AllJavaTypes.FIELD_BOOLEAN)); } else { - callSetter(type, Arrays.asList(AllJavaTypes.FIELD_STRING)); + callSetter(dObjTyped, type, Arrays.asList(AllJavaTypes.FIELD_STRING)); } fail(); } catch (IllegalArgumentException ignored) { } finally { realm.cancelTransaction(); } + dynamicRealm.beginTransaction(); + try { + // Make sure we hit the wrong underlying type for all types. + if (type == SupportedType.STRING) { + callSetter(dObjDynamic, type, Arrays.asList(AllJavaTypes.FIELD_BOOLEAN)); + } else { + callSetter(dObjDynamic, type, Arrays.asList(AllJavaTypes.FIELD_STRING)); + } + fail(); + } catch (IllegalArgumentException ignored) { + } finally { + dynamicRealm.cancelTransaction(); + } } } @@ -280,21 +313,21 @@ public void typedSetter_changePrimaryKeyThrows() { } // Helper method for calling setters with different field names - private void callSetter(SupportedType type, List fieldNames) { + private static void callSetter(DynamicRealmObject target, SupportedType type, List fieldNames) { for (String fieldName : fieldNames) { switch (type) { - case BOOLEAN: dObjTyped.setBoolean(fieldName, false); break; - case SHORT: dObjTyped.setShort(fieldName, (short) 1); break; - case INT: dObjTyped.setInt(fieldName, 1); break; - case LONG: dObjTyped.setLong(fieldName, 1L); break; - case BYTE: dObjTyped.setByte(fieldName, (byte) 4); break; - case FLOAT: dObjTyped.setFloat(fieldName, 1.23f); break; - case DOUBLE: dObjTyped.setDouble(fieldName, 1.23d); break; - case STRING: dObjTyped.setString(fieldName, "foo"); break; - case BINARY: dObjTyped.setBlob(fieldName, new byte[]{}); break; - case DATE: dObjTyped.getDate(fieldName); break; - case OBJECT: dObjTyped.setObject(fieldName, null); break; - case LIST: dObjTyped.setList(fieldName, null); break; + case BOOLEAN: target.setBoolean(fieldName, false); break; + case SHORT: target.setShort(fieldName, (short) 1); break; + case INT: target.setInt(fieldName, 1); break; + case LONG: target.setLong(fieldName, 1L); break; + case BYTE: target.setByte(fieldName, (byte) 4); break; + case FLOAT: target.setFloat(fieldName, 1.23f); break; + case DOUBLE: target.setDouble(fieldName, 1.23d); break; + case STRING: target.setString(fieldName, "foo"); break; + case BINARY: target.setBlob(fieldName, new byte[]{}); break; + case DATE: target.getDate(fieldName); break; + case OBJECT: target.setObject(fieldName, null); target.setObject(fieldName, target); break; + case LIST: target.setList(fieldName, new RealmList()); break; default: fail(); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp index e9116d021b..b419a697f6 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp @@ -147,7 +147,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeIsNullLink } JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLinkView - (JNIEnv* env, jclass obj, jlong nativeRowPtr, jlong columnIndex) + (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) { if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_LinkList)) return 0; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 5f2fc6c1d7..8bde98aeed 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -184,7 +184,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNullLink } JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLinkView - (JNIEnv* env, jclass, jlong nativeRowPtr, jlong columnIndex) + (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(env, nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index f48a0a0872..95c4ae2014 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -124,8 +124,12 @@ public E get(String fieldName) { */ public boolean getBoolean(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - checkFieldType(fieldName, columnIndex, RealmFieldType.BOOLEAN); - return proxyState.getRow$realm().getBoolean(columnIndex); + try { + return proxyState.getRow$realm().getBoolean(columnIndex); + } catch (IllegalArgumentException e) { + checkFieldType(fieldName, columnIndex, RealmFieldType.BOOLEAN); + throw e; + } } /** @@ -171,8 +175,12 @@ public short getShort(String fieldName) { */ public long getLong(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - checkFieldType(fieldName, columnIndex, RealmFieldType.INTEGER); - return proxyState.getRow$realm().getLong(columnIndex); + try { + return proxyState.getRow$realm().getLong(columnIndex); + } catch (IllegalArgumentException e) { + checkFieldType(fieldName, columnIndex, RealmFieldType.INTEGER); + throw e; + } } /** @@ -203,8 +211,12 @@ public byte getByte(String fieldName) { */ public float getFloat(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - checkFieldType(fieldName, columnIndex, RealmFieldType.FLOAT); - return proxyState.getRow$realm().getFloat(columnIndex); + try { + return proxyState.getRow$realm().getFloat(columnIndex); + } catch (IllegalArgumentException e) { + checkFieldType(fieldName, columnIndex, RealmFieldType.FLOAT); + throw e; + } } /** @@ -220,8 +232,12 @@ public float getFloat(String fieldName) { */ public double getDouble(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - checkFieldType(fieldName, columnIndex, RealmFieldType.DOUBLE); - return proxyState.getRow$realm().getDouble(columnIndex); + try { + return proxyState.getRow$realm().getDouble(columnIndex); + } catch (IllegalArgumentException e) { + checkFieldType(fieldName, columnIndex, RealmFieldType.DOUBLE); + throw e; + } } /** @@ -233,8 +249,12 @@ public double getDouble(String fieldName) { */ public byte[] getBlob(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - checkFieldType(fieldName, columnIndex, RealmFieldType.BINARY); - return proxyState.getRow$realm().getBinaryByteArray(columnIndex); + try { + return proxyState.getRow$realm().getBinaryByteArray(columnIndex); + } catch (IllegalArgumentException e) { + checkFieldType(fieldName, columnIndex, RealmFieldType.BINARY); + throw e; + } } /** @@ -246,8 +266,12 @@ public byte[] getBlob(String fieldName) { */ public String getString(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - checkFieldType(fieldName, columnIndex, RealmFieldType.STRING); - return proxyState.getRow$realm().getString(columnIndex); + try { + return proxyState.getRow$realm().getString(columnIndex); + } catch (IllegalArgumentException e) { + checkFieldType(fieldName, columnIndex, RealmFieldType.STRING); + throw e; + } } /** @@ -295,10 +319,14 @@ public DynamicRealmObject getObject(String fieldName) { */ public RealmList getList(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - checkFieldType(fieldName, columnIndex, RealmFieldType.LIST); - LinkView linkView = proxyState.getRow$realm().getLinkList(columnIndex); - String className = RealmSchema.getSchemaForTable(linkView.getTargetTable()); - return new RealmList(className, linkView, proxyState.getRealm$realm()); + try { + LinkView linkView = proxyState.getRow$realm().getLinkList(columnIndex); + String className = RealmSchema.getSchemaForTable(linkView.getTargetTable()); + return new RealmList(className, linkView, proxyState.getRealm$realm()); + } catch (IllegalArgumentException e) { + checkFieldType(fieldName, columnIndex, RealmFieldType.LIST); + throw e; + } } /** @@ -688,7 +716,7 @@ private void checkFieldType(String fieldName, long columnIndex, RealmFieldType e expectedIndefiniteVowel = "n"; } String columnTypeIndefiniteVowel = ""; - if (expectedType == RealmFieldType.INTEGER || expectedType == RealmFieldType.OBJECT) { + if (columnType == RealmFieldType.INTEGER || columnType == RealmFieldType.OBJECT) { columnTypeIndefiniteVowel = "n"; } throw new IllegalArgumentException(String.format("'%s' is not a%s '%s', but a%s '%s'.", diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index 42c89a20c1..7d10954838 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -124,7 +124,7 @@ public void setNull(long columnIndex) { protected native String nativeGetString(long nativePtr, long columnIndex); protected native boolean nativeIsNullLink(long nativeRowPtr, long columnIndex); protected native byte[] nativeGetByteArray(long nativePtr, long columnIndex); - public static native long nativeGetLinkView(long nativePtr, long columnIndex); + protected native long nativeGetLinkView(long nativePtr, long columnIndex); protected native void nativeSetLong(long nativeRowPtr, long columnIndex, long value); protected native void nativeSetBoolean(long nativeRowPtr, long columnIndex, boolean value); protected native void nativeSetFloat(long nativeRowPtr, long columnIndex, float value); diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index b867d42d6b..6e4bb8e9a4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -296,7 +296,7 @@ public boolean hasColumn(String fieldName) { protected native String nativeGetString(long nativePtr, long columnIndex); protected native boolean nativeIsNullLink(long nativeRowPtr, long columnIndex); protected native byte[] nativeGetByteArray(long nativePtr, long columnIndex); - public static native long nativeGetLinkView(long nativePtr, long columnIndex); + protected native long nativeGetLinkView(long nativePtr, long columnIndex); protected native void nativeSetLong(long nativeRowPtr, long columnIndex, long value); protected native void nativeSetBoolean(long nativeRowPtr, long columnIndex, boolean value); protected native void nativeSetFloat(long nativeRowPtr, long columnIndex, float value); From 3cfa572cf1c7e7dd0bfe779a3e3d273507181630 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 4 Oct 2016 20:25:39 +0200 Subject: [PATCH 0138/2110] Fix examples --- .../io/realm/examples/intro/IntroExampleActivity.java | 6 ++---- .../io/realm/examples/kotlin/KotlinExampleActivity.kt | 9 +++------ .../main/kotlin/io/realm/examples/kotlin/model/Person.kt | 7 ++++--- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java index 684960e753..ba58b50372 100644 --- a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java +++ b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java @@ -24,7 +24,6 @@ import android.widget.TextView; import io.realm.Realm; -import io.realm.RealmConfiguration; import io.realm.RealmResults; import io.realm.Sort; import io.realm.examples.intro.model.Cat; @@ -37,7 +36,6 @@ public class IntroExampleActivity extends Activity { private LinearLayout rootLayout = null; private Realm realm; - private RealmConfiguration realmConfig; @Override protected void onCreate(Bundle savedInstanceState) { @@ -148,7 +146,7 @@ private String complexReadWrite() { // Open the default realm. All threads must use it's own reference to the realm. // Those can not be transferred across threads. - Realm realm = Realm.getInstance(realmConfig); + Realm realm = Realm.getDefaultInstance(); // Add ten persons in one transaction realm.executeTransaction(new Realm.Transaction() { @@ -204,7 +202,7 @@ public void execute(Realm realm) { private String complexQuery() { String status = "\n\nPerforming complex Query operation..."; - Realm realm = Realm.getInstance(realmConfig); + Realm realm = Realm.getDefaultInstance(); status += "\nNumber of persons: " + realm.where(Person.class).count(); // Find all persons where age between 7 and 9 and name begins with "Person". diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index f519715041..60ab821878 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -23,7 +23,6 @@ import android.widget.LinearLayout import android.widget.TextView import io.realm.Realm import io.realm.Sort -import io.realm.RealmConfiguration import io.realm.examples.kotlin.model.Cat import io.realm.examples.kotlin.model.Dog import io.realm.examples.kotlin.model.Person @@ -40,7 +39,6 @@ class KotlinExampleActivity : Activity() { private var rootLayout: LinearLayout by Delegates.notNull() private var realm: Realm by Delegates.notNull() - private var realmConfig: RealmConfiguration by Delegates.notNull() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -96,8 +94,7 @@ class KotlinExampleActivity : Activity() { // All writes must be wrapped in a transaction to facilitate safe multi threading realm.executeTransaction { // Add a person - var person = realm.createObject(Person::class.java) - person.id = 1 + var person = realm.createObject(Person::class.java, 1) person.name = "Young Person" person.age = 14 } @@ -137,7 +134,7 @@ class KotlinExampleActivity : Activity() { // Open the default realm. All threads must use it's own reference to the realm. // Those can not be transferred across threads. - val realm = Realm.getInstance(realmConfig) + val realm = Realm.getDefaultInstance() // Add ten persons in one transaction realm.executeTransaction { @@ -193,7 +190,7 @@ class KotlinExampleActivity : Activity() { // Realm implements the Closable interface, therefore we can make use of Kotlin's built-in // extension method 'use' (pun intended). - Realm.getInstance(realmConfig).use { + Realm.getDefaultInstance().use { // 'it' is the implicit lambda parameter of type Realm status += "\nNumber of persons: ${it.where(Person::class.java).count()}" diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Person.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Person.kt index 36deb5c3dc..c408a12472 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Person.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Person.kt @@ -30,7 +30,9 @@ open class Person( // All properties are by default persisted. // Properties can be annotated with PrimaryKey or Index. // If you use non-nullable types, properties must be initialized with non-null values. - @PrimaryKey open var name: String = "", + @PrimaryKey open var id: Long = 0, + + open var name: String = "", open var age: Int = 0, @@ -41,9 +43,8 @@ open class Person( open var cats: RealmList = RealmList(), // You can instruct Realm to ignore a field and not persist it. - @Ignore open var tempReference: Int = 0, + @Ignore open var tempReference: Int = 0 - open var id: Long = 0 ) : RealmObject() { // The Kotlin compiler generates standard getters and setters. // Realm will overload them and code inside them is ignored. From d0a1ba6d5d58a4f4f2b52bf48eb4a1ab2e521c4e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 4 Oct 2016 20:27:17 +0200 Subject: [PATCH 0139/2110] Fix error handling crashing in JNI and then crashing in Java --- realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp | 2 +- .../src/objectServer/java/io/realm/Session.java | 1 + .../src/objectServer/java/io/realm/SyncManager.java | 2 ++ .../java/io/realm/internal/objectserver/SyncSession.java | 7 ++++++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index f513c03a37..06f8ac1d2f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -107,7 +107,7 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient sync_client = std::make_unique(std::move(config)); // Throws // This function should only be called once, so below is safe. - sync_manager = sync_manager_class; + sync_manager = reinterpret_cast(env->NewGlobalRef(sync_manager_class)); sync_manager_notify_error_handler = env->GetStaticMethodID(sync_manager, "notifyErrorHandler", "(ILjava/lang/String;)V"); sync_client->set_error_handler(error_handler); diff --git a/realm/realm-library/src/objectServer/java/io/realm/Session.java b/realm/realm-library/src/objectServer/java/io/realm/Session.java index a5c44e257b..bb97fbaf64 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/Session.java +++ b/realm/realm-library/src/objectServer/java/io/realm/Session.java @@ -46,6 +46,7 @@ public final class Session { Session(SyncSession rosSession) { this.syncSession = rosSession; + rosSession.setUserSession(this); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 01891b667d..3ed441760c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -68,6 +68,8 @@ public void onError(Session session, ObjectServerError error) { case RECOVERABLE: RealmLog.info(errorMsg); break; + default: + throw new IllegalArgumentException("Unsupported error category: " + error.getErrorCode().getCategory()); } } }; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java index 212c60fe89..426b141c8d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java @@ -106,6 +106,7 @@ public final class SyncSession { private SessionState currentStateDescription; private FsmState currentState; private Session userSession; + private Session publicSession; /** * Creates a new Object Server Session. @@ -199,7 +200,7 @@ public synchronized void unbind() { public synchronized void onError(ObjectServerError error) { currentState.onError(error); // FSM needs to respond to the error first, before notifying the User if (errorHandler != null) { - errorHandler.onError(this.getUserSession(), error); + errorHandler.onError(getUserSession(), error); } } @@ -349,6 +350,10 @@ public Session getUserSession() { return userSession; } + public void setUserSession(Session userSession) { + this.userSession = userSession; + } + private native long nativeCreateSession(String localRealmPath); private native void nativeBind(long nativeSessionPointer, String remoteRealmUrl, String userToken); private native void nativeUnbind(long nativeSessionPointer); From c1d69881ea2d411a446496577c6d9a83326da289 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 4 Oct 2016 20:37:23 +0200 Subject: [PATCH 0140/2110] Downgrade to Sync-1.3-BETA --- CHANGELOG.md | 2 +- dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfc36dff9e..f11ff0b498 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ## Internal -* Upgraded to Realm Core 2.1.0 / Realm Sync 2.0-BETA +* Upgraded to Realm Core 2.0.1 / Realm Sync 1.3-BETA ## 2.0.0 diff --git a/dependencies.list b/dependencies.list index a7943dc708..4ebb448a91 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,2 +1,2 @@ -REALM_SYNC_VERSION=1.0.0-BETA-2.0 -REALM_SYNC_SHA256=c7eb59576b28373283e94dafe42737015657cb0deac435a66c28fc74629ce721 +REALM_SYNC_VERSION=1.0.0-BETA-1.3 +REALM_SYNC_SHA256=dfb527d58d3aa1b1044b8676a9e0fae60d38811e451cb04ae71376835f0acab4 diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index c5135a5935..a0ab785896 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit c5135a5935765fa8993fc1b796ef5ed7da609cdf +Subproject commit a0ab785896b3e362e9703c798b8db729a49c9fda From b7ea9d07a5202003bceaa2e21a43428dd0c0f05f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 5 Oct 2016 08:43:01 +0200 Subject: [PATCH 0141/2110] Fix threading example --- .../java/io/realm/examples/threads/PassingObjectsFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/PassingObjectsFragment.java b/examples/threadExample/src/main/java/io/realm/examples/threads/PassingObjectsFragment.java index c64cf21644..566bd07ef3 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/PassingObjectsFragment.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/PassingObjectsFragment.java @@ -100,10 +100,9 @@ public void onActivityCreated(Bundle savedInstanceState) { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { - person = realm.createObject(Person.class); + person = realm.createObject(Person.class, UUID.randomUUID().toString()); person.setName("Jane"); person.setAge(42); - person.setId(UUID.randomUUID().toString()); } }); textContent.setText(person.toString()); From f7b5ff28112b6ab71855680a808cfac7fcc5cf25 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 5 Oct 2016 08:43:41 +0200 Subject: [PATCH 0142/2110] Fix artefact names when uploading to Bintray --- realm/realm-library/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 259e2debc6..4e7d20c822 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -556,7 +556,7 @@ android.productFlavors.all { flavor -> "${buildDir}/outputs/aar/realm-android-library-${flavor.name}-release.aar", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-{project.version}.aar?publish=0" + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.aar?publish=0" } task("bintraySources${flavor.name.capitalize()}", type: Exec) { @@ -569,7 +569,7 @@ android.productFlavors.all { flavor -> "${buildDir}/libs/realm-android-library-${project.version}-sources.jar", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-sources-{project.version}.jar?publish=0" + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-sources.jar?publish=0" } task("bintrayJavadoc${flavor.name.capitalize()}", type: Exec) { @@ -582,7 +582,7 @@ android.productFlavors.all { flavor -> "${buildDir}/libs/realm-android-library-${project.version}-javadoc.jar", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-javadoc-{project.version}.jar?publish=0" + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-javadoc.jar?publish=0" } task("bintrayPom${flavor.name.capitalize()}", type: Exec) { @@ -595,7 +595,7 @@ android.productFlavors.all { flavor -> "${buildDir}/publications/${flavor.name}Publication/pom-default.xml", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-{project.version}.pom?publish=0" + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.pom?publish=0" } task("bintray${flavor.name.capitalize()}") { From c41ef023a72866d62c40dbfa58e9ba82bd5e667c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 5 Oct 2016 08:53:31 +0200 Subject: [PATCH 0143/2110] Release v2.0.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5e38773b3d..10bf840ed5 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.1-SNAPSHOT \ No newline at end of file +2.0.1 \ No newline at end of file From 52f42e5abd564bce98ff0ecb374f3e88db06b05d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 5 Oct 2016 08:53:31 +0200 Subject: [PATCH 0144/2110] Prepare next release v2.0.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 10bf840ed5..77762b9138 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.1 \ No newline at end of file +2.0.2-SNAPSHOT \ No newline at end of file From f8753026d707ec5e53cb90aec13f691009b67764 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 6 Oct 2016 18:25:45 +0900 Subject: [PATCH 0145/2110] fix build error on Java 7 environment (#3567) * fix build error on Java 7 environment * update CHANGELOG --- CHANGELOG.md | 6 ++++++ .../groovy/io/realm/gradle/RealmPluginExtension.groovy | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f11ff0b498..cb0d37f76a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.2 + +### Bug fixes + +* Build error when using Java 7 (#3563). + ## 2.0.1 ### Bug fixes diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy index ef1f21e97d..ac2fc4012a 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy @@ -31,11 +31,12 @@ class RealmPluginExtension { this.syncEnabled = value; // remove realm android library first - project.getConfigurations().getByName("compile").getDependencies().removeIf() { - if (it.group != 'io.realm') { - return false + def iterator = project.getConfigurations().getByName("compile").getDependencies().iterator(); + while (iterator.hasNext()) { + def item = iterator.next() + if (item.group == 'io.realm' && item.name.startsWith('realm-android-library')) { + iterator.remove() } - return it.name.startsWith('realm-android-library') } // then add again From 596ba48cec55e5635da498ca0103b3f63949d746 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 6 Oct 2016 12:25:59 +0200 Subject: [PATCH 0146/2110] Enable OJO upload for AAR flavours. (#3569) --- build.gradle | 42 +------------------ realm/build.gradle | 2 +- realm/realm-library/build.gradle | 71 ++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 42 deletions(-) diff --git a/build.gradle b/build.gradle index 0085acdb11..cd7c5facad 100644 --- a/build.gradle +++ b/build.gradle @@ -350,51 +350,11 @@ task bintrayUpload { dependsOn bintrayTransformer } -task s3Realm(type: GradleBuild) { - description = 'Publish the Realm AAR and AP to the internal S3 maven repository.' - group = 'Publishing' - buildFile = file('realm/build.gradle') - tasks = ['publish'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } -} - -task s3Annotations(type: GradleBuild) { - description = 'Publish the Realm Annotations to the internal S3 maven repository.' - group = 'Publishing' - buildFile = file('realm-annotations/build.gradle') - tasks = ['publish'] -} - -task s3GradlePlugin(type: GradleBuild) { - description = 'Publish the Realm Gradle Plugin to the internal S3 maven repository.' - group = 'Publishing' - buildFile = file('gradle-plugin/build.gradle') - tasks = ['publish'] -} - -task s3Transformer(type: GradleBuild) { - description = 'Publish the Realm Transformer to the internal S3 maven repository.' - group = 'Publishing' - buildFile = file('realm-transformer/build.gradle') - tasks = ['publish'] -} - -task s3Upload { - description = 'Publish all the Realm artifacts to the internal S3 maven repository.' - group = 'Publishing' - dependsOn s3Realm - dependsOn s3Annotations - dependsOn s3GradlePlugin - dependsOn s3Transformer -} - task ojoRealm(type: GradleBuild) { description = 'Publish the Realm AAR and AP SNAPSHOT to Bintray' group = 'Publishing' buildFile = file('realm/build.gradle') - tasks = ['artifactoryPublish'] + tasks = ['ojoUpload'] startParameter.projectProperties = gradle.startParameter.projectProperties if (project.hasProperty('buildTargetABIs')) { startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] diff --git a/realm/build.gradle b/realm/build.gradle index 70fd1cd30b..02d42b21b4 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -13,7 +13,7 @@ buildscript { classpath 'com.novoda:gradle-android-command-plugin:1.3.0' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.2' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0' - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.0.1' + classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.4.5' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 4e7d20c822..09e4c9f10c 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -546,6 +546,8 @@ android.productFlavors.all { flavor -> def userName = project.findProperty('bintrayUser') ?: 'noUser' def accessKey = project.findProperty('bintrayKey') ?: 'noKey' + // BINTRAY + task("bintrayAar${flavor.name.capitalize()}", type: Exec) { dependsOn "assemble${flavor.name.capitalize()}" group = 'Publishing' @@ -598,6 +600,60 @@ android.productFlavors.all { flavor -> "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.pom?publish=0" } + // OJO + + task("ojoAar${flavor.name.capitalize()}", type: Exec) { + dependsOn "assemble${flavor.name.capitalize()}" + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/outputs/aar/realm-android-library-${flavor.name}-release.aar", + '-u', + "${userName}:${accessKey}", + "https://oss.jfrog.org/artifactory/oss-snapshot-local/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.aar?publish=0" + } + + task("ojoSources${flavor.name.capitalize()}", type: Exec) { + dependsOn sourcesJar + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-android-library-${project.version}-sources.jar", + '-u', + "${userName}:${accessKey}", + "https://oss.jfrog.org/artifactory/oss-snapshot-local/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-sources.jar?publish=0" + } + + task("ojoJavadoc${flavor.name.capitalize()}", type: Exec) { + dependsOn javadocJar + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-android-library-${project.version}-javadoc.jar", + '-u', + "${userName}:${accessKey}", + "https://oss.jfrog.org/artifactory/oss-snapshot-local/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-javadoc.jar?publish=0" + } + + task("ojoPom${flavor.name.capitalize()}", type: Exec) { + dependsOn "publish${flavor.name.capitalize()}PublicationPublicationToMavenLocal" + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/publications/${flavor.name}Publication/pom-default.xml", + '-u', + "${userName}:${accessKey}", + "https://oss.jfrog.org/artifactory/oss-snapshot-local/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.pom?publish=0" + } + task("bintray${flavor.name.capitalize()}") { dependsOn "bintrayAar${flavor.name.capitalize()}" dependsOn "bintraySources${flavor.name.capitalize()}" @@ -605,6 +661,14 @@ android.productFlavors.all { flavor -> dependsOn "bintrayPom${flavor.name.capitalize()}" group = 'Publishing' } + + task("ojo${flavor.name.capitalize()}") { + dependsOn "ojoAar${flavor.name.capitalize()}" + dependsOn "ojoSources${flavor.name.capitalize()}" + dependsOn "ojoJavadoc${flavor.name.capitalize()}" + dependsOn "ojoPom${flavor.name.capitalize()}" + group = 'Publishing' + } } task bintrayUpload() { @@ -614,6 +678,13 @@ task bintrayUpload() { group = 'Publishing' } +task ojoUpload() { + android.productFlavors.all { flavor -> + dependsOn "ojo${flavor.name.capitalize()}" + } + group = 'Publishing' +} + def checkNdk(String ndkPath) { def detectedNdkVersion def releaseFile = new File(ndkPath, 'RELEASE.TXT') From 380dc540af32f5792af62caa1e1213e140b7aa24 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 6 Oct 2016 13:46:21 +0200 Subject: [PATCH 0147/2110] Upgraded to Sync Beta 2.0 (#3570) --- CHANGELOG.md | 6 ++++++ dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb0d37f76a..3fe89697b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ ## 2.0.2 +This release is not protocol-compatible with previous versions of the Realm Mobile Platform. The base library is still fully compatible. + ### Bug fixes * Build error when using Java 7 (#3563). +## Internal + +* Upgraded to Realm Core 2.1.0 / Realm Sync 2.0-BETA. + ## 2.0.1 ### Bug fixes diff --git a/dependencies.list b/dependencies.list index 4ebb448a91..a7943dc708 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,2 +1,2 @@ -REALM_SYNC_VERSION=1.0.0-BETA-1.3 -REALM_SYNC_SHA256=dfb527d58d3aa1b1044b8676a9e0fae60d38811e451cb04ae71376835f0acab4 +REALM_SYNC_VERSION=1.0.0-BETA-2.0 +REALM_SYNC_SHA256=c7eb59576b28373283e94dafe42737015657cb0deac435a66c28fc74629ce721 diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index a0ab785896..c5135a5935 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit a0ab785896b3e362e9703c798b8db729a49c9fda +Subproject commit c5135a5935765fa8993fc1b796ef5ed7da609cdf From a0a3e0e997c8099e0b18b3fab847483bc997e06e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 6 Oct 2016 13:47:15 +0200 Subject: [PATCH 0148/2110] Ignore flaky test (#3573) --- .../java/io/realm/android/UserStoreTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java index b55d740044..b856aa4c7b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java @@ -21,6 +21,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,6 +59,7 @@ public void tearDown() { } } + @Ignore("See https://github.com/realm/realm-java/issues/3555") @Test public void encrypt_decrypt_UsingAndroidKeyStoreUserStore() throws KeyStoreException { User user = createTestUser(); From 853e92dbd639a2222f49d8f9a380e0f04e15fb12 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 6 Oct 2016 13:53:55 +0200 Subject: [PATCH 0149/2110] Release v2.0.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 77762b9138..f93ea0ca33 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.2-SNAPSHOT \ No newline at end of file +2.0.2 \ No newline at end of file From 391d9b0a85d3245f131b2bdf907e0f0f9453f768 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 6 Oct 2016 13:53:55 +0200 Subject: [PATCH 0150/2110] Prepare next release v2.0.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f93ea0ca33..d77ea86f52 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.2 \ No newline at end of file +2.0.3-SNAPSHOT \ No newline at end of file From 68b81f3d9b76203c89044aac87dc10ea3dbdb5b4 Mon Sep 17 00:00:00 2001 From: Max Furman Date: Thu, 6 Oct 2016 10:39:25 -0400 Subject: [PATCH 0151/2110] Add ability for first and last methods to return a default --- .../io/realm/OrderedRealmCollectionTests.java | 16 ++++++++++ .../java/io/realm/OrderedRealmCollection.java | 14 +++++++++ .../src/main/java/io/realm/RealmList.java | 30 +++++++++++++++++++ .../src/main/java/io/realm/RealmResults.java | 25 ++++++++++++++++ 4 files changed, 85 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java index 4d4665e4d7..cd0570e9a0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java @@ -220,6 +220,14 @@ public void first_emptyCollection() { } } + @Test + public void first_withDefault() { + AllJavaTypes defaultObject = collection.get(0); + collection = createEmptyCollection(realm, collectionClass); + assertEquals(defaultObject, collection.first(defaultObject)); + assertEquals(null, collection.first(null)); // Null is an acceptable default + } + @Test public void last() { assertEquals(collection.get(TEST_SIZE - 1), collection.last()); @@ -235,6 +243,14 @@ public void last_emptyCollection() { } } + @Test + public void last_withDefault() { + AllJavaTypes defaultObject = collection.get(0); + collection = createEmptyCollection(realm, collectionClass); + assertEquals(defaultObject, collection.last(defaultObject)); + assertEquals(null, collection.last(null)); // Null is an acceptable default + } + @Test public void get_validIndex() { AllJavaTypes first = collection.get(0); diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java index 29376bff76..0af9acf3f8 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java @@ -34,6 +34,13 @@ public interface OrderedRealmCollection extends List, R */ E first(); + /** + * Gets the first object from the collection. If the collection is empty, the provided default will be used. + * + * @return the first object or the provided default. + */ + E first(E defaultObject); + /** * Gets the last object from the collection. * @@ -42,6 +49,13 @@ public interface OrderedRealmCollection extends List, R */ E last(); + /** + * Gets the last object from the collection. If the collection is empty, the provided default will be used. + * + * @return the last object or the provided default. + */ + E last(E defaultObject); + /** * Sorts a collection based on the provided field in ascending order. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 9ba427a4c4..8d9366e605 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -467,6 +467,21 @@ public E first() { throw new IndexOutOfBoundsException("The list is empty."); } + /** + * {@inheritDoc} + */ + public E first(E defaultValue) { + if (managedMode) { + checkValidView(); + if (!view.isEmpty()) { + return get(0); + } + } else if (unmanagedList != null && unmanagedList.size() > 0) { + return unmanagedList.get(0); + } + return defaultValue; + } + /** * {@inheritDoc} */ @@ -482,6 +497,21 @@ public E last() { throw new IndexOutOfBoundsException("The list is empty."); } + /** + * {@inheritDoc} + */ + public E last(E defaultValue) { + if (managedMode) { + checkValidView(); + if (!view.isEmpty()) { + return get((int) view.size() - 1); + } + } else if (unmanagedList != null && unmanagedList.size() > 0) { + return unmanagedList.get(unmanagedList.size() - 1); + } + return defaultValue; + } + /** * {@inheritDoc} */ diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index ea37900e37..c66d330860 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -232,6 +232,18 @@ public E first() { } } + /** + * {@inheritDoc} + */ + @Override + public E first(E defaultValue) { + if (size() > 0) { + return get(0); + } else { + return defaultValue; + } + } + /** * {@inheritDoc} */ @@ -245,6 +257,19 @@ public E last() { } } + /** + * {@inheritDoc} + */ + @Override + public E last(E defaultValue) { + int size = size(); + if (size > 0) { + return get(size - 1); + } else { + return defaultValue; + } + } + /** * {@inheritDoc} */ From 364e77fc39f387d534f330a7d9430aa986685765 Mon Sep 17 00:00:00 2001 From: Max Furman Date: Thu, 6 Oct 2016 12:37:43 -0400 Subject: [PATCH 0152/2110] restart CI From 92f16cfc4ceed8bb2983c09b56a3c352eda12789 Mon Sep 17 00:00:00 2001 From: Max Furman Date: Fri, 7 Oct 2016 14:47:30 -0400 Subject: [PATCH 0153/2110] Updated changelog; combined duplicate implementations for both .first and .last --- CHANGELOG.md | 1 + .../io/realm/OrderedRealmCollectionTests.java | 4 +- .../java/io/realm/OrderedRealmCollection.java | 8 ++-- .../src/main/java/io/realm/RealmList.java | 46 ++++++++++--------- .../src/main/java/io/realm/RealmResults.java | 41 ++++++++++------- 5 files changed, 55 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa076d3e1d..ccd6b95fc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Enhancement * `Realm.compactRealm()` works for encrypted Realms. +* Added `first(E defaultValue)` and `last(E defaultValue)` methods to `RealmList` and `RealmResult`. These methods will return the provided object instead of throwing an `IndexOutOfBoundsException` if the list is empty. ## 2.0.1 diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java index cd0570e9a0..8bee6afa70 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java @@ -222,7 +222,7 @@ public void first_emptyCollection() { @Test public void first_withDefault() { - AllJavaTypes defaultObject = collection.get(0); + AllJavaTypes defaultObject = new AllJavaTypes(); collection = createEmptyCollection(realm, collectionClass); assertEquals(defaultObject, collection.first(defaultObject)); assertEquals(null, collection.first(null)); // Null is an acceptable default @@ -245,7 +245,7 @@ public void last_emptyCollection() { @Test public void last_withDefault() { - AllJavaTypes defaultObject = collection.get(0); + AllJavaTypes defaultObject = new AllJavaTypes(); collection = createEmptyCollection(realm, collectionClass); assertEquals(defaultObject, collection.last(defaultObject)); assertEquals(null, collection.last(null)); // Null is an acceptable default diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java index 0af9acf3f8..724ec56cf5 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java @@ -35,11 +35,11 @@ public interface OrderedRealmCollection extends List, R E first(); /** - * Gets the first object from the collection. If the collection is empty, the provided default will be used. + * Gets the first object from the collection. If the collection is empty, the provided default will be used instead. * * @return the first object or the provided default. */ - E first(E defaultObject); + E first(E defaultValue); /** * Gets the last object from the collection. @@ -50,11 +50,11 @@ public interface OrderedRealmCollection extends List, R E last(); /** - * Gets the last object from the collection. If the collection is empty, the provided default will be used. + * Gets the last object from the collection. If the collection is empty, the provided default will be used instead. * * @return the last object or the provided default. */ - E last(E defaultObject); + E last(E defaultValue); /** * Sorts a collection based on the provided field in ascending order. diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 8d9366e605..3b0f54afcd 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -456,60 +456,62 @@ public E get(int location) { * {@inheritDoc} */ public E first() { - if (managedMode) { - checkValidView(); - if (!view.isEmpty()) { - return get(0); - } - } else if (unmanagedList != null && unmanagedList.size() > 0) { - return unmanagedList.get(0); - } - throw new IndexOutOfBoundsException("The list is empty."); + return firstImpl(true, null); } /** * {@inheritDoc} */ public E first(E defaultValue) { + return firstImpl(false, defaultValue); + } + + private E firstImpl(boolean shouldThrow, E defaultValue) { if (managedMode) { checkValidView(); if (!view.isEmpty()) { return get(0); } - } else if (unmanagedList != null && unmanagedList.size() > 0) { + } else if (unmanagedList != null && !unmanagedList.isEmpty()) { return unmanagedList.get(0); } - return defaultValue; + + if (shouldThrow) { + throw new IndexOutOfBoundsException("The list is empty."); + } else { + return defaultValue; + } } /** * {@inheritDoc} */ public E last() { - if (managedMode) { - checkValidView(); - if (!view.isEmpty()) { - return get((int) view.size() - 1); - } - } else if (unmanagedList != null && unmanagedList.size() > 0) { - return unmanagedList.get(unmanagedList.size() - 1); - } - throw new IndexOutOfBoundsException("The list is empty."); + return lastImpl(true, null); } /** * {@inheritDoc} */ public E last(E defaultValue) { + return lastImpl(false, defaultValue); + } + + private E lastImpl(boolean shouldThrow, E defaultValue) { if (managedMode) { checkValidView(); if (!view.isEmpty()) { return get((int) view.size() - 1); } - } else if (unmanagedList != null && unmanagedList.size() > 0) { + } else if (unmanagedList != null && !unmanagedList.isEmpty()) { return unmanagedList.get(unmanagedList.size() - 1); } - return defaultValue; + + if (shouldThrow) { + throw new IndexOutOfBoundsException("The list is empty."); + } else { + return defaultValue; + } } /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index c66d330860..7bc4df26c5 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -225,11 +225,7 @@ public E get(int location) { */ @Override public E first() { - if (size() > 0) { - return get(0); - } else { - throw new IndexOutOfBoundsException("No results were found."); - } + return firstImpl(true, null); } /** @@ -237,10 +233,18 @@ public E first() { */ @Override public E first(E defaultValue) { - if (size() > 0) { + return firstImpl(false, defaultValue); + } + + private E firstImpl(boolean shouldThrow, E defaultValue) { + if (!isEmpty()) { return get(0); } else { - return defaultValue; + if (shouldThrow) { + throw new IndexOutOfBoundsException("No results were found."); + } else { + return defaultValue; + } } } @@ -249,12 +253,7 @@ public E first(E defaultValue) { */ @Override public E last() { - int size = size(); - if (size > 0) { - return get(size - 1); - } else { - throw new IndexOutOfBoundsException("No results were found."); - } + return lastImpl(true, null); } /** @@ -262,11 +261,19 @@ public E last() { */ @Override public E last(E defaultValue) { - int size = size(); - if (size > 0) { - return get(size - 1); + return lastImpl(false, defaultValue); + + } + + private E lastImpl(boolean shouldThrow, E defaultValue) { + if (!isEmpty()) { + return get(size() - 1); } else { - return defaultValue; + if (shouldThrow) { + throw new IndexOutOfBoundsException("No results were found."); + } else { + return defaultValue; + } } } From 51bf47c4d536a8d659b675db0b1ad98c95336d13 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 7 Oct 2016 22:11:09 +0200 Subject: [PATCH 0154/2110] Update kotlin example (#3588) --- examples/kotlinExample/build.gradle | 3 +-- .../io/realm/examples/kotlin/KotlinExampleActivity.kt | 11 +++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index a7baf526da..8741e4a82f 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.0.3' + ext.kotlin_version = '1.0.4' repositories { jcenter() mavenCentral() @@ -51,6 +51,5 @@ android { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" - compile "org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}" compile 'org.jetbrains.anko:anko-sdk15:0.8.2' } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index 60ab821878..8b4112f4f9 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -30,11 +30,10 @@ import org.jetbrains.anko.async import org.jetbrains.anko.uiThread import kotlin.properties.Delegates - class KotlinExampleActivity : Activity() { companion object { - val TAG: String = KotlinExampleActivity::class.qualifiedName as String + val TAG: String = KotlinExampleActivity::class.java.simpleName } private var rootLayout: LinearLayout by Delegates.notNull() @@ -50,7 +49,7 @@ class KotlinExampleActivity : Activity() { // we can generally safely run them on the UI thread. // Open the realm for the UI thread. - realm = Realm.getDefaultInstance(); + realm = Realm.getDefaultInstance() basicCRUD(realm) basicQuery(realm) @@ -94,13 +93,13 @@ class KotlinExampleActivity : Activity() { // All writes must be wrapped in a transaction to facilitate safe multi threading realm.executeTransaction { // Add a person - var person = realm.createObject(Person::class.java, 1) + val person = realm.createObject(Person::class.java, 1) person.name = "Young Person" person.age = 14 } // Find the first person (no query conditions) and read a field - var person = realm.where(Person::class.java).findFirst() + val person = realm.where(Person::class.java).findFirst() showStatus(person.name + ": " + person.age) // Update person in a transaction @@ -177,7 +176,7 @@ class KotlinExampleActivity : Activity() { } // Sorting - val sortedPersons = realm.where(Person::class.java).findAllSorted("age", Sort.DESCENDING); + val sortedPersons = realm.where(Person::class.java).findAllSorted("age", Sort.DESCENDING) check(realm.where(Person::class.java).findAll().last().name == sortedPersons.first().name) status += "\nSorting ${sortedPersons.last().name} == ${realm.where(Person::class.java).findAll().first().name}" From 45fc7fd9b2ddea0ee9e7d5224d66ec7c21c50ec9 Mon Sep 17 00:00:00 2001 From: Max Furman Date: Mon, 10 Oct 2016 10:15:34 -0400 Subject: [PATCH 0155/2110] Don't change submodule version --- realm/realm-library/src/main/cpp/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index a0ab785896..c5135a5935 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit a0ab785896b3e362e9703c798b8db729a49c9fda +Subproject commit c5135a5935765fa8993fc1b796ef5ed7da609cdf From cb1a6ef1b97e8035fc7551ef0c5603771447dfc5 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 11 Oct 2016 02:51:49 -0500 Subject: [PATCH 0156/2110] Accelerate build with ccache and lcache (#3523) (#3609) See https://github.com/beeender/lcache for source code for lcache. It is very simple right now. It identify if the link target is cached by using checksums from command line + checksums of all input files. The realm-library gradle check those paths from project properties first , then the system env. So those can be set in the ~/.gradle/gradle.properties like: ccachePath=/usr/bin/ccache lcachePath=/usr/bin/lcache Or by system ENVs like: NDK_CCACHE=/usr/bin/ccache NDK_LCACHE=/usr/bin/lcache --- realm/realm-library/build.gradle | 4 ++++ realm/realm-library/src/main/cpp/CMakeLists.txt | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 09e4c9f10c..fa92cb8070 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -30,6 +30,8 @@ if (!ext.coreArchiveDir) { ext.coreArchiveFile = rootProject.file("${ext.coreArchiveDir}/realm-sync-android-${project.coreVersion}.tar.gz") ext.coreDistributionDir = file("${projectDir}/distribution/realm-core/") ext.coreDir = file("${project.coreDistributionDir.getAbsolutePath()}/core-${project.coreVersion}") +ext.ccachePath = project.findProperty('ccachePath') ?: System.getenv('NDK_CCACHE') +ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE') android { compileSdkVersion 24 @@ -50,6 +52,8 @@ android { // JNI build currently (lack of lto linking support). // This file should be removed and use the one from Android SDK cmake package when it supports lto. "-DCMAKE_TOOLCHAIN_FILE=${project.file('src/main/cpp/android.toolchain.cmake').path}" + if (project.ccachePath) arguments "-DNDK_CCACHE=$project.ccachePath" + if (project.lcachePath) arguments "-DNDK_LCACHE=$project.lcachePath" if (!project.hasProperty('android.injected.build.abi') && project.hasProperty('buildTargetABIs')) { abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*')) } else { diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 39c456f1da..957d90fda2 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -17,6 +17,11 @@ set(CMAKE_VERBOSE_MAKEFILE ON) # Generate compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# Setup lcache +if(NDK_LCACHE) + set(CMAKE_CXX_CREATE_SHARED_LIBRARY "${NDK_LCACHE} ${CMAKE_CXX_CREATE_SHARED_LIBRARY}") +endif() + # Set flag build_SYNC if (REALM_FLAVOR STREQUAL base) set(build_SYNC OFF) @@ -180,4 +185,3 @@ if (CMAKE_BUILD_TYPE STREQUAL "Release") COMMAND ${CMAKE_COMMAND} -E copy $ ${unstripped_SO_DIR} COMMAND ${CMAKE_STRIP} $) endif() - From a1e0f8a7db78adc50f5ee2202c7eea36821e8aa4 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Oct 2016 18:23:13 +0900 Subject: [PATCH 0157/2110] Update ProGuard configuration (#3596) * update ProGuard configuration * update comment * remove unnecessary comments * restore SyncSession#notifySessionError() * fix error * introduce KeepMember annotations and mark SyncSession#notifySessionError() with KeepMember * update year * update CHANGELOG --- CHANGELOG.md | 7 ++++ realm/realm-library/build.gradle | 3 +- realm/realm-library/proguard-rules-base.pro | 2 ++ realm/realm-library/proguard-rules-common.pro | 18 +++++++++++ .../proguard-rules-objectServer.pro | 7 ++++ realm/realm-library/proguard-rules.pro | 11 ------- .../src/main/cpp/objectserver_shared.hpp | 2 +- .../src/main/java/io/realm/internal/Keep.java | 2 +- .../java/io/realm/internal/KeepMember.java | 32 +++++++++++++++++++ .../java/io/realm/internal/RealmNotifier.java | 2 +- .../internal/objectserver/SyncSession.java | 10 +++--- 11 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 realm/realm-library/proguard-rules-base.pro create mode 100644 realm/realm-library/proguard-rules-common.pro create mode 100644 realm/realm-library/proguard-rules-objectServer.pro delete mode 100644 realm/realm-library/proguard-rules.pro create mode 100644 realm/realm-library/src/main/java/io/realm/internal/KeepMember.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fe89697b9..b5552cb894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.0.3 + +### Bug fixes + +* Those were not kept by ProGuard: names of native methods not in the `io.realm.internal` package, names of classes used in method signature (#3596). +* Missing ProGuard configuration for libraries used by Sync extension (#3596). + ## 2.0.2 This release is not protocol-compatible with previous versions of the Realm Mobile Platform. The base library is still fully compatible. diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index fa92cb8070..ef840e80f6 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -41,7 +41,6 @@ android { minSdkVersion 9 targetSdkVersion 24 project.archivesBaseName = "realm-android-library" - consumerProguardFiles 'proguard-rules.pro' testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { @@ -98,6 +97,7 @@ android { arguments "-DREALM_FLAVOR=base" } } + consumerProguardFiles 'proguard-rules-common.pro', 'proguard-rules-base.pro' } objectServer { externalNativeBuild { @@ -105,6 +105,7 @@ android { arguments "-DREALM_FLAVOR=objectServer" } } + consumerProguardFiles 'proguard-rules-common.pro', 'proguard-rules-objectServer.pro' } } } diff --git a/realm/realm-library/proguard-rules-base.pro b/realm/realm-library/proguard-rules-base.pro new file mode 100644 index 0000000000..26e41702f8 --- /dev/null +++ b/realm/realm-library/proguard-rules-base.pro @@ -0,0 +1,2 @@ +# It's OK not to exist SyncObjectServerFacade in base library. +-dontnote io.realm.internal.objectserver.SyncObjectServerFacade diff --git a/realm/realm-library/proguard-rules-common.pro b/realm/realm-library/proguard-rules-common.pro new file mode 100644 index 0000000000..2f8dfc843a --- /dev/null +++ b/realm/realm-library/proguard-rules-common.pro @@ -0,0 +1,18 @@ +-keep class io.realm.annotations.RealmModule +-keep @io.realm.annotations.RealmModule class * + +-keep class io.realm.internal.Keep +-keep,includedescriptorclasses @io.realm.internal.Keep class * { *; } + +-keep class io.realm.internal.KeepMember +-keep,includedescriptorclasses class * { @io.realm.internal.KeepMember *; } + +-dontwarn javax.** +-dontwarn io.realm.** +-keep class io.realm.RealmCollection +-keep class io.realm.OrderedRealmCollection +-keepclasseswithmembernames,includedescriptorclasses class io.realm.** { + native ; +} + +-dontnote rx.Observable \ No newline at end of file diff --git a/realm/realm-library/proguard-rules-objectServer.pro b/realm/realm-library/proguard-rules-objectServer.pro new file mode 100644 index 0000000000..d4b249abb8 --- /dev/null +++ b/realm/realm-library/proguard-rules-objectServer.pro @@ -0,0 +1,7 @@ +-dontnote android.security.KeyStore +-dontwarn okio.Okio +-dontwarn okio.DeflaterSink + +-dontnote com.android.org.conscrypt.SSLParametersImpl +-dontnote org.apache.harmony.xnet.provider.jsse.SSLParametersImpl +-dontnote sun.security.ssl.SSLContextImpl diff --git a/realm/realm-library/proguard-rules.pro b/realm/realm-library/proguard-rules.pro deleted file mode 100644 index 2ad1784f1a..0000000000 --- a/realm/realm-library/proguard-rules.pro +++ /dev/null @@ -1,11 +0,0 @@ --keep class io.realm.annotations.RealmModule --keep @io.realm.annotations.RealmModule class * --keep class io.realm.internal.Keep --keep @io.realm.internal.Keep class * { *; } --dontwarn javax.** --dontwarn io.realm.** --keep class io.realm.RealmCollection --keep class io.realm.OrderedRealmCollection --keepclasseswithmembernames class io.realm.internal.** { - native ; -} diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index acdd15103a..89f899da1a 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -69,7 +69,7 @@ class JniSession { return m_sync_session; } - // Call this just before destroying the object to release JNI ressources. + // Call this just before destroying the object to release JNI resources. inline void close(JNIEnv* env) { env->DeleteGlobalRef(m_global_obj_ref); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Keep.java b/realm/realm-library/src/main/java/io/realm/internal/Keep.java index 45f86dc70a..598ebccc48 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Keep.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Keep.java @@ -24,7 +24,7 @@ /** * This annotation is used to mark the classes to be kept by ProGuard/DexGuard. * The ProGuard configuration must have '-keep class io.realm.internal.Keep' - * and '-keep @io.realm.internal.Keep class *'. + * and '-keep,includedescriptorclasses @io.realm.internal.Keep class * { *; }'. */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) diff --git a/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java b/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java new file mode 100644 index 0000000000..43a810bb5c --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to mark the fields and methods to be kept by ProGuard/DexGuard. + * The ProGuard configuration must have '-keep class io.realm.internal.KeepMember' + * and '-keep,includedescriptorclasses class * { @io.realm.internal.KeepMember *; }'. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.METHOD,ElementType.FIELD}) +public @interface KeepMember { +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index d3464b2027..aaf97b6b5d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -34,7 +34,7 @@ public interface RealmNotifier { * This is getting called on the same thread which created this Realm when the same Realm file has been changed by * other thread. The changes on the same thread should not trigger this call. */ - @SuppressWarnings("unused") + @SuppressWarnings("unused") // called from java_binding_context.cpp void notifyCommitByOtherThread(); /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java index 426b141c8d..4b080cca33 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java @@ -20,22 +20,22 @@ import java.util.HashMap; import java.util.concurrent.Future; -import io.realm.RealmAsyncTask; -import io.realm.internal.Keep; -import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.log.RealmLog; import io.realm.ErrorCode; import io.realm.ObjectServerError; +import io.realm.RealmAsyncTask; import io.realm.Session; import io.realm.SessionState; import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.User; +import io.realm.internal.KeepMember; +import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.syncpolicy.SyncPolicy; +import io.realm.log.RealmLog; /** * Internal class describing a Realm Object Server Session. @@ -87,7 +87,6 @@ * * This object is thread safe. */ -@Keep public final class SyncSession { private final HashMap FSM = new HashMap(); @@ -206,6 +205,7 @@ public synchronized void onError(ObjectServerError error) { // Called from Session.cpp and SyncMaanger // This callback will happen on the thread running the Sync Client. + @KeepMember void notifySessionError(int errorCode, String errorMessage) { ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); onError(error); From 4907abe5a730204c132a7e2772adc2b7138e030c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 11 Oct 2016 06:19:45 -0500 Subject: [PATCH 0158/2110] Ignore android.injected.build.abi (#3612) It is set by AS randomly for some reasons, seems AS will inject ABIs according to execution target. But it is not having the right ABI values all the time. So just disable it, only check buildTargetABIs property. --- realm/realm-library/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ef840e80f6..a29db66b30 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -53,7 +53,7 @@ android { "-DCMAKE_TOOLCHAIN_FILE=${project.file('src/main/cpp/android.toolchain.cmake').path}" if (project.ccachePath) arguments "-DNDK_CCACHE=$project.ccachePath" if (project.lcachePath) arguments "-DNDK_LCACHE=$project.lcachePath" - if (!project.hasProperty('android.injected.build.abi') && project.hasProperty('buildTargetABIs')) { + if (project.hasProperty('buildTargetABIs')) { abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*')) } else { // armeabi is not supported anymore. From aa48c6cc121b2a70ba88ec472c896f1312ad5998 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 11 Oct 2016 07:14:28 -0500 Subject: [PATCH 0159/2110] Use debug log level for transformer (#3610) The AS 2.2.1 seems enable info level log by default which makes the build slow in the AS. Close #3608 --- CHANGELOG.md | 4 ++++ .../realm/transformer/BytecodeModifier.groovy | 12 ++++++------ .../realm/transformer/RealmTransformer.groovy | 18 +++++++++--------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5552cb894..8a7823c061 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ This release is not protocol-compatible with previous versions of the Realm Mobi * Build error when using Java 7 (#3563). +### Enhancements + +* Reduce transformer logger verbosity (#3608). + ## Internal * Upgraded to Realm Core 2.1.0 / Realm Sync 2.0-BETA. diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index ba770dfc6b..82f3ac292b 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -37,7 +37,7 @@ class BytecodeModifier { * @param clazz the CtClass to add accessors to. */ public static void addRealmAccessors(CtClass clazz) { - logger.info " Realm: Adding accessors to ${clazz.simpleName}" + logger.debug " Realm: Adding accessors to ${clazz.simpleName}" def methods = clazz.getDeclaredMethods()*.name clazz.declaredFields.each { CtField field -> if (!Modifier.isStatic(field.getModifiers()) && !field.hasAnnotation(Ignore.class)) { @@ -59,7 +59,7 @@ class BytecodeModifier { */ public static void useRealmAccessors(CtClass clazz, List managedFields) { clazz.getDeclaredBehaviors().each { behavior -> - logger.info " Behavior: ${behavior.name}" + logger.debug " Behavior: ${behavior.name}" if ( ( behavior instanceof CtMethod && @@ -104,13 +104,13 @@ class BytecodeModifier { @Override void edit(FieldAccess fieldAccess) throws CannotCompileException { - logger.info " Field being accessed: ${fieldAccess.className}.${fieldAccess.fieldName}" + logger.debug " Field being accessed: ${fieldAccess.className}.${fieldAccess.fieldName}" def isRealmFieldAccess = managedFields.find { fieldAccess.className.equals(it.declaringClass.name) && fieldAccess.fieldName.equals(it.name) } if (isRealmFieldAccess != null) { - logger.info " Realm: Manipulating ${ctClass.simpleName}.${behavior.name}(): ${fieldAccess.fieldName}" - logger.info " Methods: ${ctClass.declaredMethods}" + logger.debug " Realm: Manipulating ${ctClass.simpleName}.${behavior.name}(): ${fieldAccess.fieldName}" + logger.debug " Methods: ${ctClass.declaredMethods}" def fieldName = fieldAccess.fieldName if (fieldAccess.isReader()) { fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') @@ -127,7 +127,7 @@ class BytecodeModifier { * @param clazz The CtClass to modify. */ public static void overrideTransformedMarker(CtClass clazz) { - logger.info " Realm: Marking as transformed ${clazz.simpleName}" + logger.debug " Realm: Marking as transformed ${clazz.simpleName}" try { clazz.getDeclaredMethod("transformerApplied", new CtClass[0]) } catch (NotFoundException ignored) { diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index 0704cb2a26..857cd304b2 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -94,7 +94,7 @@ class RealmTransformer extends Transform { // javassist. See https://github.com/realm/realm-java/issues/2703. addBootClassesToClassPool(classPool) - logger.info "ClassPool contains Realm classes: ${classPool.getOrNull('io.realm.RealmList') != null}" + logger.debug "ClassPool contains Realm classes: ${classPool.getOrNull('io.realm.RealmList') != null}" // mark as transformed def baseProxyMediator = classPool.get('io.realm.internal.RealmProxyMediator') @@ -103,7 +103,7 @@ class RealmTransformer extends Transform { .findAll { it.matches(mediatorPattern) } .collect { classPool.getCtClass(it) } .findAll { it.superclass?.equals(baseProxyMediator) } - logger.info "Proxy Mediator Classes: ${proxyMediatorClasses*.name}" + logger.debug "Proxy Mediator Classes: ${proxyMediatorClasses*.name}" proxyMediatorClasses.each { BytecodeModifier.overrideTransformedMarker(it); } @@ -116,7 +116,7 @@ class RealmTransformer extends Transform { def inputModelClasses = allModelClasses.findAll { inputClassNames.contains(it.name) } - logger.info "Model Classes: ${allModelClasses*.name}" + logger.debug "Model Classes: ${allModelClasses*.name}" // Populate a list of the fields that need to be managed with bytecode manipulation def allManagedFields = [] @@ -125,7 +125,7 @@ class RealmTransformer extends Transform { !it.hasAnnotation(Ignore.class) && !Modifier.isStatic(it.getModifiers()) }) } - logger.info "Managed Fields: ${allManagedFields*.name}" + logger.debug "Managed Fields: ${allManagedFields*.name}" // Add accessors to the model classes in the target project inputModelClasses.each { @@ -135,7 +135,7 @@ class RealmTransformer extends Transform { // Use accessors instead of direct field access inputClassNames.each { - logger.info " Modifying class ${it}" + logger.debug " Modifying class ${it}" def ctClass = classPool.getCtClass(it) BytecodeModifier.useRealmAccessors(ctClass, allManagedFields) ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) @@ -144,7 +144,7 @@ class RealmTransformer extends Transform { copyResourceFiles(inputs, outputProvider) def toc = System.currentTimeMillis() - logger.info "Realm Transform time: ${toc-tic} milliseconds" + logger.debug "Realm Transform time: ${toc-tic} milliseconds" this.sendAnalytics(inputs, inputModelClasses) } @@ -263,7 +263,7 @@ class RealmTransformer extends Transform { def dirPath = it.file.absolutePath it.file.eachFileRecurse(FileType.FILES) { if (!it.absolutePath.endsWith(SdkConstants.DOT_CLASS)) { - logger.info " Copying resource ${it}" + logger.debug " Copying resource ${it}" def dest = new File(getOutputFile(outputProvider), it.absolutePath.substring(dirPath.length())) dest.parentFile.mkdirs() @@ -294,13 +294,13 @@ class RealmTransformer extends Transform { try { project.android.bootClasspath.each { String path = it.absolutePath - logger.info "Add boot class " + path + " to class pool." + logger.debug "Add boot class " + path + " to class pool." classPool.appendClassPath(path) } } catch (Exception e) { // Just log it. It might not impact the transforming if the method which needs to be transformer doesn't // contain classes from android.jar. - logger.info("Cannot get bootClasspath caused by:", e) + logger.debug("Cannot get bootClasspath caused by:", e) } } } From aeec61deb65ed3c2352dadf2586a1a41a2164ee1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 11 Oct 2016 07:20:03 -0500 Subject: [PATCH 0160/2110] Wrong entry in the change log (#3617) --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a7823c061..c3e44c1e86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ * Those were not kept by ProGuard: names of native methods not in the `io.realm.internal` package, names of classes used in method signature (#3596). * Missing ProGuard configuration for libraries used by Sync extension (#3596). +### Enhancements + +* Reduce transformer logger verbosity (#3608). + ## 2.0.2 This release is not protocol-compatible with previous versions of the Realm Mobile Platform. The base library is still fully compatible. @@ -13,10 +17,6 @@ This release is not protocol-compatible with previous versions of the Realm Mobi * Build error when using Java 7 (#3563). -### Enhancements - -* Reduce transformer logger verbosity (#3608). - ## Internal * Upgraded to Realm Core 2.1.0 / Realm Sync 2.0-BETA. From 4901d383154b2f0c6dd41286b26bf9abe26bf988 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 12 Oct 2016 11:28:45 +0200 Subject: [PATCH 0161/2110] Adds User.all() (#3600) --- CHANGELOG.md | 1 + .../java/io/realm/SyncManagerTests.java | 4 ++ .../java/io/realm/UserTests.java | 41 ++++++++++++++----- .../src/objectServer/java/io/realm/User.java | 20 +++++++++ .../objectServer/java/io/realm/UserStore.java | 6 +++ .../io/realm/android/SecureUserStore.java | 11 +++++ .../realm/android/SharedPrefsUserStore.java | 14 +++++++ 7 files changed, 86 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af4b613b40..69e7bb1c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Enhancement * `Realm.compactRealm()` works for encrypted Realms. +* Added `User.all()` that returns all known Realm Object Server users. ## 2.0.3 diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 1deaf9b2db..566228e7de 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -71,6 +71,10 @@ public User remove(String key) { public Collection allUsers() { return null; } + + @Override + public void clear() { + } }; } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java index 4cf3601779..0f90fe6905 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java @@ -19,29 +19,21 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import java.net.URL; -import java.util.concurrent.TimeUnit; +import java.util.Collection; import io.realm.android.SharedPrefsUserStore; -import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.network.AuthenticationServer; -import io.realm.internal.objectserver.Token; import io.realm.rule.RunInLooperThread; -import io.realm.rule.RunTestInLooperThread; import io.realm.util.SyncTestUtils; import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.when; +import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) public class UserTests { @@ -49,6 +41,12 @@ public class UserTests { @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); + @Before + public void setUp() { + Realm.init(InstrumentationRegistry.getTargetContext()); + SyncManager.getUserStore().clear(); + } + @Test public void toAndFromJson() { User user1 = createTestUser(); @@ -68,6 +66,27 @@ public void currentUser_returnsNullIfUserExpired() { assertNull(User.currentUser()); } + // `all()` returns an empty list if no users are logged in + @Test + public void all_empty() { + Collection users = User.all(); + assertTrue(users.isEmpty()); + } + + // `all()` returns only valid users. Invalid users are filtered. + @Test + public void all_validUsers() { + // Add 1 expired user and 1 valid user to the user store + UserStore userStore = new SharedPrefsUserStore(InstrumentationRegistry.getContext()); + SyncManager.setUserStore(userStore); + userStore.put(UserStore.CURRENT_USER_KEY, SyncTestUtils.createTestUser(Long.MIN_VALUE)); + userStore.put(UserStore.CURRENT_USER_KEY, SyncTestUtils.createTestUser(Long.MAX_VALUE)); + + Collection users = User.all(); + assertEquals(1, users.size()); + assertTrue(users.iterator().next().isValid()); + } + // Tests that the user store returns the last user to login /* FIXME: This test fails because of wrong JSON string. @Test diff --git a/realm/realm-library/src/objectServer/java/io/realm/User.java b/realm/realm-library/src/objectServer/java/io/realm/User.java index cab8285653..1798b42558 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/User.java @@ -28,7 +28,9 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; @@ -79,6 +81,24 @@ public static User currentUser() { return null; } + /** + * Returns all valid users known by this device. + * A user is invalidated when he/she logs out or the user's access token expires. + * + * @return a list of all known valid users. + */ + public static Collection all() { + UserStore userStore = SyncManager.getUserStore(); + Collection storedUsers = userStore.allUsers(); + List result = new ArrayList(storedUsers.size()); + for (User user : storedUsers) { + if (user.isValid()) { + result.add(user); + } + } + return result; + } + /** * Loads a user that has previously been serialized using {@link #toJson()}. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java index 140d013d0a..4ec9f355ff 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java @@ -67,4 +67,10 @@ public interface UserStore { * @return Collection of all users. If no users exist, an empty collection is returned. */ Collection allUsers(); + + + /** + * Removes all saved users. + */ + void clear(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java index 7aedf01db3..5a73075aeb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import java.util.Set; import io.realm.User; import io.realm.UserStore; @@ -159,4 +160,14 @@ public Collection allUsers() { } return users; } + + @Override + public void clear() { + Set all = sp.getAll().keySet(); + SharedPreferences.Editor editor = sp.edit(); + for (String key : all) { + editor.remove(key); + } + editor.apply(); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java index eab709cf07..4773cb682d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import java.util.Set; import io.realm.User; import io.realm.UserStore; @@ -114,4 +115,17 @@ public Collection allUsers() { } return users; } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + Set all = sp.getAll().keySet(); + SharedPreferences.Editor editor = sp.edit(); + for (String key : all) { + editor.remove(key); + } + editor.apply(); + } } From 466fe60facf35517e3b886875a17cafbf44083f8 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 13 Oct 2016 06:10:36 +0900 Subject: [PATCH 0162/2110] fix flaky test (#3626) --- .../src/androidTest/java/io/realm/RealmAsyncQueryTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index dc200d9493..9375d6efd8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -424,6 +424,7 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); + looperThread.keepStrongReference.add(results); assertFalse(results.isLoaded()); assertEquals(0, results.size()); From a4b1c485e1c3f4a6f4a9dd04aa22b3f3e2e873b8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 13 Oct 2016 03:23:25 -0500 Subject: [PATCH 0163/2110] Call notifySessionError from native code (#3620) Fix #3597 --- CHANGELOG.md | 1 + .../src/main/cpp/objectserver_shared.hpp | 16 +++++++++------- .../realm/internal/objectserver/SyncSession.java | 5 +++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e44c1e86..a7fd677a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Those were not kept by ProGuard: names of native methods not in the `io.realm.internal` package, names of classes used in method signature (#3596). * Missing ProGuard configuration for libraries used by Sync extension (#3596). +* Error handler was not called when sync session failed (#3597). ### Enhancements diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 89f899da1a..08ab2829b8 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -44,8 +44,8 @@ class JniSession { extern std::unique_ptr sync_client; // Get the coordinator for the given path, or null if there is none m_sync_session = new realm::sync::Session(*sync_client, local_realm_path); - m_global_obj_ref = env->NewGlobalRef(java_session_obj); - jobject global_obj_ref_tmp(m_global_obj_ref); + m_java_session_ref = env->NewGlobalRef(java_session_obj); + jobject global_obj_ref_tmp(m_java_session_ref); auto sync_transact_callback = [local_realm_path](realm::VersionID, realm::VersionID) { auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator( realm::StringData(local_realm_path)); @@ -54,11 +54,13 @@ class JniSession { } }; auto error_handler = [&, global_obj_ref_tmp](int error_code, std::string message) { - // FIXME: Simplify this by moving log_message to AndroidLogger JNIEnv *local_env; g_vm->AttachCurrentThread(&local_env, nullptr); - std::string log = num_to_string(error_code) + " " + message.c_str(); - log_message(local_env, log_debug, log.c_str()); + jclass java_session_class = local_env->GetObjectClass(global_obj_ref_tmp); + jmethodID notify_error_handler = local_env->GetMethodID(java_session_class, + "notifySessionError", "(ILjava/lang/String;)V"); + local_env->CallVoidMethod(global_obj_ref_tmp, + notify_error_handler, error_code, env->NewStringUTF(message.c_str())); }; m_sync_session->set_sync_transact_callback(sync_transact_callback); m_sync_session->set_error_handler(std::move(error_handler)); @@ -72,7 +74,7 @@ class JniSession { // Call this just before destroying the object to release JNI resources. inline void close(JNIEnv* env) { - env->DeleteGlobalRef(m_global_obj_ref); + env->DeleteGlobalRef(m_java_session_ref); } ~JniSession() @@ -82,7 +84,7 @@ class JniSession { private: realm::sync::Session* m_sync_session; - jobject m_global_obj_ref; + jobject m_java_session_ref; }; #endif // REALM_OBJECTSERVER_SHARED_HPP diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java index 4b080cca33..5c8be79d41 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java @@ -203,10 +203,11 @@ public synchronized void onError(ObjectServerError error) { } } - // Called from Session.cpp and SyncMaanger + // Called from JniSession in native code. // This callback will happen on the thread running the Sync Client. + @SuppressWarnings("unused") @KeepMember - void notifySessionError(int errorCode, String errorMessage) { + private void notifySessionError(int errorCode, String errorMessage) { ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); onError(error); } From 96008528367a45d0fc41cade777e4dd983c06cbd Mon Sep 17 00:00:00 2001 From: LYK Date: Thu, 13 Oct 2016 19:28:00 +0900 Subject: [PATCH 0164/2110] Remove comments that mention realm-java-private (#3628) --- realm/realm-library/src/main/cpp/objectserver_shared.hpp | 2 +- realm/realm-library/src/main/java/io/realm/AndroidNotifier.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 08ab2829b8..1253f539d4 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -30,7 +30,7 @@ // Wrapper class for realm::Session. This allows us to manage the C++ session and callback lifecycle correctly. -// TODO Use OS SyncSession instead - https://github.com/realm/realm-java-private/issues/123 +// TODO Use OS SyncSession instead class JniSession { public: diff --git a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java index 8e2ddc26d8..b0f1c194ae 100644 --- a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java @@ -65,7 +65,6 @@ public void notifyCommitByLocalThread() { // This is called by OS when other thread/process changes the Realm. // This is getting called on the same thread which created the Realm. - // https://github.com/realm/realm-java-private/issues/127 // |---------------------------------------------------------------+--------------+------------------------------------------------| // | Thread A | Thread B | Daemon Thread | // |---------------------------------------------------------------+--------------+------------------------------------------------| From 1c8d6960f573c0bea5883415bbfcced7764c99c4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 14 Oct 2016 04:13:46 -0500 Subject: [PATCH 0165/2110] Adding integration-tests (#3601) - An node-js service is created to control the ROS. By default it is runninng at port 8888. - To start the ROS set a GET request to http://127.0.0.1:8888/start . - To stop the ROS set a GET request to http://127.0.0.1:8888/stop. - See the README to see how to start the testing service locally. - Integrations tests locate in realm/realm-library/src/syncIntegrationTest - Before running those tests, a sync testing server needs to be started. - A DockerFile in tools/sync_test_server can be used to start the testing server - It can be started locally by calling tools/sync_test_server/start_server.sh - The same docker is used on CI for integration testing as well. So CI is running two docker container now, one for build, the other one for ROS testing server. They both run in the same docker network. - A basic AuthTests is added to verify this integration testing setup. --- Jenkinsfile | 128 ++++++---- README.md | 13 + dependencies.list | 1 + integration-tests/build.gradle | 34 --- .../gradle/wrapper/gradle-wrapper.properties | 6 - integration-tests/settings.gradle | 4 - integration-tests/sync/.gitignore | 3 - integration-tests/sync/README.md | 28 --- integration-tests/sync/build.gradle | 70 ------ .../sync/gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - integration-tests/sync/gradlew | 160 ------------ integration-tests/sync/gradlew.bat | 90 ------- integration-tests/sync/proguard-rules.pro | 17 -- .../realm/tests/sync/ProcessCommitTests.java | 98 -------- .../sync/src/main/AndroidManifest.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3418 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2206 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4842 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 7718 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 10486 -> 0 bytes .../sync/test_server/package.json | 13 - integration-tests/sync/test_server/server.js | 67 ------ integration-tests/sync/test_server/start.sh | 2 - realm/realm-library/build.gradle | 4 +- .../io/realm/AuthenticateRequestTests.java | 4 + .../java/io/realm/objectserver/AuthTests.java | 69 ++++++ .../objectserver/ProcessCommitTests.java | 175 ++++++++++++++ .../objectserver}/model/ProcessInfo.java | 5 +- .../realm/objectserver/model/TestObject.java | 28 ++- .../objectserver}/service/SendOneCommit.java | 20 +- .../realm/objectserver/service/SendsALot.java | 60 +++++ .../realm/objectserver/utils/Constants.java | 30 +++ .../realm/objectserver}/utils/HttpUtils.java | 37 ++- .../realm/objectserver/utils/UserFactory.java | 40 +++ tools/sync_test_server/Dockerfile | 17 ++ tools/sync_test_server/configuration.yml | 227 ++++++++++++++++++ .../keys/HowToGenerateKey.txt | 18 ++ tools/sync_test_server/keys/private.pem | 27 +++ tools/sync_test_server/keys/public.pem | 9 + tools/sync_test_server/keys/test_token.json | 11 + tools/sync_test_server/ros-testing-server.js | 86 +++++++ tools/sync_test_server/start_server.sh | 17 ++ tools/sync_test_server/stop_server.sh | 4 + 44 files changed, 955 insertions(+), 685 deletions(-) delete mode 100644 integration-tests/build.gradle delete mode 100644 integration-tests/gradle/wrapper/gradle-wrapper.properties delete mode 100644 integration-tests/settings.gradle delete mode 100644 integration-tests/sync/.gitignore delete mode 100644 integration-tests/sync/README.md delete mode 100644 integration-tests/sync/build.gradle delete mode 100644 integration-tests/sync/gradle/wrapper/gradle-wrapper.jar delete mode 100644 integration-tests/sync/gradle/wrapper/gradle-wrapper.properties delete mode 100755 integration-tests/sync/gradlew delete mode 100644 integration-tests/sync/gradlew.bat delete mode 100644 integration-tests/sync/proguard-rules.pro delete mode 100644 integration-tests/sync/src/androidTest/java/io/realm/tests/sync/ProcessCommitTests.java delete mode 100644 integration-tests/sync/src/main/AndroidManifest.xml delete mode 100644 integration-tests/sync/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 integration-tests/sync/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 integration-tests/sync/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 integration-tests/sync/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 integration-tests/sync/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 integration-tests/sync/test_server/package.json delete mode 100644 integration-tests/sync/test_server/server.js delete mode 100755 integration-tests/sync/test_server/start.sh create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java rename {integration-tests/sync/src/main/java/io/realm/tests/sync => realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver}/model/ProcessInfo.java (93%) rename integration-tests/sync/src/main/java/io/realm/tests/sync/utils/Constants.java => realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/TestObject.java (55%) rename {integration-tests/sync/src/main/java/io/realm/tests/sync => realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver}/service/SendOneCommit.java (77%) create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java rename {integration-tests/sync/src/main/java/io/realm/tests/sync => realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver}/utils/HttpUtils.java (66%) create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java create mode 100644 tools/sync_test_server/Dockerfile create mode 100644 tools/sync_test_server/configuration.yml create mode 100644 tools/sync_test_server/keys/HowToGenerateKey.txt create mode 100644 tools/sync_test_server/keys/private.pem create mode 100644 tools/sync_test_server/keys/public.pem create mode 100644 tools/sync_test_server/keys/test_token.json create mode 100755 tools/sync_test_server/ros-testing-server.js create mode 100755 tools/sync_test_server/start_server.sh create mode 100755 tools/sync_test_server/stop_server.sh diff --git a/Jenkinsfile b/Jenkinsfile index 5c3dfe4ccf..5ee2d7cef1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,6 +3,7 @@ import groovy.json.JsonOutput def buildSuccess = false +def rosContainer try { node('android') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) @@ -22,65 +23,86 @@ try { } def buildEnv + def rosEnv stage('Docker build') { + // Docker image for build buildEnv = docker.build 'realm-java:snapshot' + // Docker image for testing Realm Object Server + def dependProperties = readProperties file: 'dependencies.list' + def rosDeVersion = dependProperties["REALM_OBJECT_SERVER_DE_VERSION"] + rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_DE_VERSION=${rosDeVersion} tools/sync_test_server" } - buildEnv.inside("-e HOME=/tmp -e _JAVA_OPTIONS=-Duser.home=/tmp --privileged -v /dev/bus/usb:/dev/bus/usb -v ${env.HOME}/gradle-cache:/tmp/.gradle -v ${env.HOME}/.android:/tmp/.android -v ${env.HOME}/ccache:/tmp/.ccache") { - stage('JVM tests') { - try { - withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG}" + rosContainer = rosEnv.run("-v /tmp=/tmp/.ros " + + "--name ros") + + try { + buildEnv.inside("-e HOME=/tmp " + + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + + "--privileged " + + "-v /dev/bus/usb:/dev/bus/usb " + + "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + + "-v ${env.HOME}/.android:/tmp/.android " + + "-v ${env.HOME}/ccache:/tmp/.ccache " + + "--network container:ros") { + stage('JVM tests') { + try { + withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { + sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG}" + } + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) + } } - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) - } - } - - stage('Static code analysis') { - try { - gradle('realm', 'findbugs pmd checkstyle') - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) - step([$class: 'CheckStylePublisher', - canComputeNew: false, - defaultEncoding: '', - healthy: '', - pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - unHealthy: '' - ]) - } - } - - stage('Run instrumented tests') { - boolean archiveLog = true - String backgroundPid - try { - backgroundPid = startLogCatCollector() - gradle('realm', 'connectedUnitTests') - archiveLog = false; - } finally { - stopLogCatCollector(backgroundPid, archiveLog) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' - } - } - // TODO: add support for running monkey on the example apps + stage('Static code analysis') { + try { + gradle('realm', 'findbugs pmd checkstyle') + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) + step([$class: 'CheckStylePublisher', + canComputeNew: false, + defaultEncoding: '', + healthy: '', + pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', + unHealthy: '' + ]) + } + } - if (env.BRANCH_NAME == 'master') { - stage('Collect metrics') { - collectAarMetrics() - } + stage('Run instrumented tests') { + boolean archiveLog = true + String backgroundPid + try { + backgroundPid = startLogCatCollector() + forwardAdbPorts() + gradle('realm', 'connectedUnitTests') + archiveLog = false; + } finally { + stopLogCatCollector(backgroundPid, archiveLog) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } + } + + // TODO: add support for running monkey on the example apps + + if (env.BRANCH_NAME == 'master') { + stage('Collect metrics') { + collectAarMetrics() + } - stage('Publish to OJO') { - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + stage('Publish to OJO') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + } + } } } - } + } finally { + rosContainer.stop() } } } @@ -109,6 +131,12 @@ try { } } +def forwardAdbPorts() { + sh ''' adb reverse tcp:7800 tcp:7800 && + adb reverse tcp:8080 tcp:8080 && + adb reverse tcp:8888 tcp:8888 + ''' +} def String startLogCatCollector() { sh '''adb logcat -c @@ -127,7 +155,7 @@ def stopLogCatCollector(String backgroundPid, boolean archiveLog) { 'glob' : 'logcat.txt' ]) } - sh 'rm logcat.txt ' + sh 'rm logcat.txt' } def sendMetrics(String metricName, String metricValue, Map tags) { diff --git a/README.md b/README.md index 19058ea750..eb77162645 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,19 @@ The `./examples` folder contain a number of example projects showing how Realm c Standalone examples can be [downloaded from website](https://realm.io/docs/java/latest/#getting-started). +## Running testing Realm Object Server + +Tests in `syncIntegrationTest` require a running testing server to work. +A docker image can be built from `tools/sync_test_server/Dockerfile` to run a testing server. `tools/sync_test_server/start_server.sh` will build the docker image automatically. + +To run a testing server locally: +a) Install docker. +b) run the `tools/sync_test_server/start_server.sh`: +```sh +cd tools/sync_test_server +./start_server.sh +``` + ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for more details! diff --git a/dependencies.list b/dependencies.list index a7943dc708..b1240870a7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,2 +1,3 @@ REALM_SYNC_VERSION=1.0.0-BETA-2.0 REALM_SYNC_SHA256=c7eb59576b28373283e94dafe42737015657cb0deac435a66c28fc74629ce721 +REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-2.1-271 diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle deleted file mode 100644 index cd4ff9411b..0000000000 --- a/integration-tests/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -project.ext.sdkVersion = 24 -project.ext.buildTools = '24.0.0' - -// Don't cache SNAPSHOT (changing) dependencies. -configurations.all { - resolutionStrategy.cacheChangingModulesFor 0, 'seconds' -} - -allprojects { - def currentVersion = file("${rootDir}/../version.txt").text.trim() - - buildscript { - repositories { - mavenLocal() - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' - classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' - classpath 'com.novoda:gradle-android-command-plugin:1.5.0' - classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' - classpath "io.realm:realm-gradle-plugin:${currentVersion}" - } - } - - group = 'io.realm' - version = currentVersion - - repositories { - mavenLocal() - jcenter() - } -} diff --git a/integration-tests/gradle/wrapper/gradle-wrapper.properties b/integration-tests/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 473ebd531b..0000000000 --- a/integration-tests/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Mon Dec 28 10:00:20 PST 2015 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip diff --git a/integration-tests/settings.gradle b/integration-tests/settings.gradle deleted file mode 100644 index 1c525e6b59..0000000000 --- a/integration-tests/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ -include ':optionalAPIRemoved', ':optionalAPIExists', ':sync' - -rootProject.name = 'integration-tests' - diff --git a/integration-tests/sync/.gitignore b/integration-tests/sync/.gitignore deleted file mode 100644 index cfc15584e2..0000000000 --- a/integration-tests/sync/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/build -/test_server/realm-sync-server -/test_server/node_modules/ diff --git a/integration-tests/sync/README.md b/integration-tests/sync/README.md deleted file mode 100644 index e9b4862a09..0000000000 --- a/integration-tests/sync/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# RUNNING THE TESTSERVER - -This document describes how to configure and start the test server used by the integration tests. -This description is only temporary. We should find a better solution. - -## HOW TO - -1. Test server server needs to be started before running the integration test. - -a) Download the matching server version from S3: `s3://ealm-ci-artifacts/sync//cocoa/realm-sync-server-.zip` -b) Extract the files to `./realm-sync-server` - - -2. Start the test server - -a) Run `sh start.sh` - - -# Future plans - -The goal is to have standalone integration tests. - -This means that the test suite should be able to download and run the required server automatically. Also the above -link only points to server binaries for Mac OSX. The tests should run on any platform. - -An initial guess is that we should switch to using the node.js server instead but that still needs to be investigated. -If not we should create a gradle task that automatically downloads, unpacks and runs the Mac OS X server just like -we do for the core file. diff --git a/integration-tests/sync/build.gradle b/integration-tests/sync/build.gradle deleted file mode 100644 index bc863c60cc..0000000000 --- a/integration-tests/sync/build.gradle +++ /dev/null @@ -1,70 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'realm-android' - -android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" - - defaultConfig { - applicationId "io.realm.tests.sync" - minSdkVersion 9 - targetSdkVersion 23 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - - tasks.withType(JavaCompile) { - compileTask -> compileTask.dependsOn reverseNodeServerPort, reverseSyncServerPort - } -} - -task reverseNodeServerPort(type: Exec) { - def adb = android.getAdbExe()?.toString() ?: 'false' - commandLine adb, 'reverse', 'tcp:8888', 'tcp:8888' - ignoreExitValue true - doLast { - if (execResult.getExitValue() != 0) { - logger.error( - '===========================================================================\n' + - 'WARNING: Failed to automatically reverse port 8888.\n' + - 'Please reverse this port from localhost to the device or emulator being used to run the application.\n' + - 'You may need to add the appropriate flags to the command that failed:\n' + - ' adb -s DEVICE reverse tcp:8082 tcp:8082\n' + - '===========================================================================\n' - ) - } - } -} - -task reverseSyncServerPort(type: Exec) { - def adb = android.getAdbExe()?.toString() ?: 'false' - commandLine adb, 'reverse', 'tcp:7800', 'tcp:7800' - ignoreExitValue true - doLast { - if (execResult.getExitValue() != 0) { - logger.error( - '===========================================================================\n' + - 'WARNING: Failed to automatically reverse port 7800.\n' + - 'Please reverse this port from localhost to the device or emulator being used to run the application.\n' + - 'You may need to add the appropriate flags to the command that failed:\n' + - ' adb -s DEVICE reverse tcp:7800 tcp:7800\n' + - '===========================================================================\n' - ) - } - } -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.squareup.okhttp3:okhttp:3.3.1' - testCompile 'junit:junit:4.12' - androidTestCompile 'com.android.support.test:runner:0.4.1' - androidTestCompile 'com.android.support.test:rules:0.4.1' -} diff --git a/integration-tests/sync/gradle/wrapper/gradle-wrapper.jar b/integration-tests/sync/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 13372aef5e24af05341d49695ee84e5f9b594659..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53636 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ⅅ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 H-+%o-(+5aQ diff --git a/integration-tests/sync/gradle/wrapper/gradle-wrapper.properties b/integration-tests/sync/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 122a0dca2e..0000000000 --- a/integration-tests/sync/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Mon Dec 28 10:00:20 PST 2015 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/integration-tests/sync/gradlew b/integration-tests/sync/gradlew deleted file mode 100755 index 9d82f78915..0000000000 --- a/integration-tests/sync/gradlew +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/integration-tests/sync/gradlew.bat b/integration-tests/sync/gradlew.bat deleted file mode 100644 index aec99730b4..0000000000 --- a/integration-tests/sync/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/integration-tests/sync/proguard-rules.pro b/integration-tests/sync/proguard-rules.pro deleted file mode 100644 index 740907a636..0000000000 --- a/integration-tests/sync/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/Nabil/Library/Android/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/integration-tests/sync/src/androidTest/java/io/realm/tests/sync/ProcessCommitTests.java b/integration-tests/sync/src/androidTest/java/io/realm/tests/sync/ProcessCommitTests.java deleted file mode 100644 index 90e8b87c88..0000000000 --- a/integration-tests/sync/src/androidTest/java/io/realm/tests/sync/ProcessCommitTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.tests.sync; - -import android.content.Context; -import android.content.Intent; -import android.os.Looper; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import io.realm.Realm; -import io.realm.RealmChangeListener; -import io.realm.RealmConfiguration; -import io.realm.RealmResults; -import io.realm.tests.sync.model.ProcessInfo; -import io.realm.tests.sync.service.SendOneCommit; -import io.realm.tests.sync.utils.Constants; -import io.realm.tests.sync.utils.HttpUtils; - -import static org.junit.Assert.assertNotEquals; - -@RunWith(AndroidJUnit4.class) -public class ProcessCommitTests { - HttpUtils httpUtils = new HttpUtils(); - - @Before - public void setUp () throws Exception { - httpUtils.startSyncServer(); - } - - @After - public void tearDown () throws Exception { - httpUtils.stopSyncServer(); - } - - @Test - public void expectServerCommit() throws Exception { - final CountDownLatch testFinished = new CountDownLatch(1); - ExecutorService service = Executors.newSingleThreadExecutor(); - service.submit(new Runnable() { - @Override - public void run() { - try { - Looper.prepare(); - Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - final RealmConfiguration syncConfig = new RealmConfiguration - .Builder(targetContext) - .name("main_process") - .withSync(Constants.SYNC_SERVER_URL) - .syncUserToken(Constants.USER_TOKEN) - .build(); - final Realm realm = Realm.getInstance(syncConfig); - Intent intent = new Intent(targetContext, SendOneCommit.class); - targetContext.startService(intent); - - final RealmResults all = realm.where(ProcessInfo.class).findAll(); - all.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults element) { - assertNotEquals(0, all.size()); - testFinished.countDown(); - } - }); - - Looper.loop(); - - } catch (Throwable e) { - e.printStackTrace(); - } - } - }); - testFinished.await(10, TimeUnit.SECONDS); - } -} diff --git a/integration-tests/sync/src/main/AndroidManifest.xml b/integration-tests/sync/src/main/AndroidManifest.xml deleted file mode 100644 index 0c2acd3b67..0000000000 --- a/integration-tests/sync/src/main/AndroidManifest.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/integration-tests/sync/src/main/res/mipmap-hdpi/ic_launcher.png b/integration-tests/sync/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cde69bcccec65160d92116f20ffce4fce0b5245c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3418 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 diff --git a/integration-tests/sync/src/main/res/mipmap-xhdpi/ic_launcher.png b/integration-tests/sync/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bfa42f0e7b91d006d22352c9ff2f134e504e3c1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4842 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 diff --git a/integration-tests/sync/src/main/res/mipmap-xxhdpi/ic_launcher.png b/integration-tests/sync/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 324e72cdd7480cb983fa1bcc7ce686e51ef87fe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7718 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 diff --git a/integration-tests/sync/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/integration-tests/sync/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index aee44e138434630332d88b1680f33c4b24c70ab3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10486 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 { - console.log(`stdout: ${data}`); - }); - - syncServerChildProcess.stderr.on('data', (data) => { - console.log(`stderr: ${data}`); - }); - - syncServerChildProcess.on('close', (code) => { - console.log(`child process exited with code ${code}`); - }); - } - }); - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('Starting a server'); -}); - -// stop a previously started sync server -dispatcher.onGet("/stop", function(req, res) { - syncServerChildProcess.kill(); - temp.cleanupSync(); - // Do work - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('Stopping the server'); -}); - -//Create and start the Http server -var server = http.createServer(handleRequest); -server.listen(PORT, function() { - console.log("Integration test server listening on: 127.0.0.1:%s", PORT); -}); diff --git a/integration-tests/sync/test_server/start.sh b/integration-tests/sync/test_server/start.sh deleted file mode 100755 index 48d8ba32b2..0000000000 --- a/integration-tests/sync/test_server/start.sh +++ /dev/null @@ -1,2 +0,0 @@ -npm install -node server.js ./realm-sync-server diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index a29db66b30..9b58221a69 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -79,6 +79,9 @@ android { androidTest { java.srcDirs += 'src/benchmarks/java' } + androidTestObjectServer { + java.srcDirs += 'src/syncIntegrationTest/java' + } } packagingOptions { @@ -110,7 +113,6 @@ android { } } - coveralls.jacocoReportPath = "${buildDir}/reports/coverage/debug/report.xml" import io.realm.transformer.RealmTransformer diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index 458bb29cba..1ae3242220 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -64,6 +64,7 @@ public void userRefresh() throws URISyntaxException, JSONException { @Test public void errorsNotWrapped() { + AuthenticationServer originalAuthServer = SyncManager.getAuthServer(); AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); when(authServer.loginUser(any(Credentials.class), any(URL.class))).thenReturn(SyncTestUtils.createErrorResponse(ErrorCode.ACCESS_DENIED)); SyncManager.setAuthServerImpl(authServer); @@ -73,6 +74,9 @@ public void errorsNotWrapped() { fail(); } catch (ObjectServerError e) { assertEquals(ErrorCode.ACCESS_DENIED, e.getErrorCode()); + } finally { + // Reset the auth server implementation for other tests. + SyncManager.setAuthServerImpl(originalAuthServer); } } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java new file mode 100644 index 0000000000..6fa8244f90 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -0,0 +1,69 @@ +package io.realm.objectserver; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.realm.Credentials; +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.Realm; +import io.realm.User; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.HttpUtils; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class AuthTests { + @Rule + public RunInLooperThread looperThread = new RunInLooperThread(); + + @BeforeClass + public static void setUp () throws Exception { + Realm.init(InstrumentationRegistry.getContext()); + HttpUtils.startSyncServer(); + } + + @AfterClass + public static void tearDown () throws Exception { + HttpUtils.stopSyncServer(); + } + + @Test + public void login_userNotExist() { + Credentials credentials = Credentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); + try { + User.login(credentials, Constants.AUTH_URL); + fail(); + } catch (ObjectServerError expected) { + assertEquals(ErrorCode.UNKNOWN_ACCOUNT, expected.getErrorCode()); + } + } + + @Test + @RunTestInLooperThread + public void loginAsync_userNotExist() { + Credentials credentials = Credentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); + User.loginAsync(credentials, Constants.AUTH_URL, new User.Callback() { + @Override + public void onSuccess(User user) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.UNKNOWN_ACCOUNT, error.getErrorCode()); + looperThread.testComplete(); + } + }); + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java new file mode 100644 index 0000000000..6e465f53f9 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -0,0 +1,175 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import android.content.Context; +import android.content.Intent; +import android.os.Looper; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import io.realm.Realm; +import io.realm.RealmChangeListener; +import io.realm.RealmResults; +import io.realm.SyncConfiguration; +import io.realm.objectserver.model.ProcessInfo; +import io.realm.objectserver.model.TestObject; +import io.realm.objectserver.service.SendOneCommit; +import io.realm.objectserver.service.SendsALot; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.HttpUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class ProcessCommitTests { + @BeforeClass + public static void setUp () throws Exception { + HttpUtils.startSyncServer(); + } + + @AfterClass + public static void tearDown () throws Exception { + HttpUtils.stopSyncServer(); + } + + // FIXME: At least need one method in the test class + @Test + public void dummy() { + + } + + // FIXME: Disable for now. + /* + @Test + public void expectServerCommit() throws Throwable { + final Throwable[] exception = new Throwable[1]; + final CountDownLatch testFinished = new CountDownLatch(1); + ExecutorService service = Executors.newSingleThreadExecutor(); + service.submit(new Runnable() { + @Override + public void run() { + try { + Looper.prepare(); + Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + final SyncConfiguration syncConfig = new SyncConfiguration.Builder() + .name(SendOneCommit.class.getSimpleName()) + .serverUrl(Constants.SYNC_SERVER_URL ) + .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL, Constants.USER_TOKEN)) + .build(); + Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests + final Realm realm = Realm.getInstance(syncConfig); + Intent intent = new Intent(targetContext, SendOneCommit.class); + targetContext.startService(intent); + + final RealmResults all = realm.where(ProcessInfo.class).findAll(); + all.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults element) { + assertEquals(1, all.size()); + assertEquals("Background_Process1", all.get(0).getName()); + testFinished.countDown(); + } + }); + + Looper.loop(); + + } catch (Throwable e) { + exception[0] = e; + testFinished.countDown(); + } + } + }); + boolean testTimedOut = testFinished.await(300, TimeUnit.SECONDS); + if (exception[0] != null) { + throw exception[0]; + } else if (!testTimedOut) { + fail("Test timed out "); + } + } + */ + + //TODO send string from service and match + // replicate integration tests from Cocoa + // add gradle task to start the sh script automatically (create pid file, ==> run or kill existing process + // check the requirement for the issue again + /* + @Test + public void expectALot() throws Throwable { + final Throwable[] exception = new Throwable[1]; + final CountDownLatch testFinished = new CountDownLatch(1); + ExecutorService service = Executors.newSingleThreadExecutor(); + service.submit(new Runnable() { + @Override + public void run() { + try { + Looper.prepare(); + Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(targetContext) + .name(SendsALot.class.getSimpleName()) + .serverUrl(Constants.SYNC_SERVER_URL_2) + .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL_2, Constants.USER_TOKEN)) + .build(); + Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests + final Realm realm = Realm.getInstance(syncConfig); + Intent intent = new Intent(targetContext, SendsALot.class); + targetContext.startService(intent); + + final RealmResults all = realm.where(TestObject.class).findAllSorted("intProp"); + all.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults element) { + assertEquals(100, element.size()); + for (int i = 0; i < 100; i++) { + assertEquals(i, element.get(i).getIntProp()); + assertEquals("property " + i, element.get(i).getStringProp()); + } + + testFinished.countDown(); + } + }); + + Looper.loop(); + + } catch (Throwable e) { + exception[0] = e; + testFinished.countDown(); + } + } + }); + boolean testTimedOut = testFinished.await(30, TimeUnit.SECONDS); + if (exception[0] != null) { + throw exception[0]; + } else if (!testTimedOut) { + fail("Test timed out "); + } + } + */ +} diff --git a/integration-tests/sync/src/main/java/io/realm/tests/sync/model/ProcessInfo.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/ProcessInfo.java similarity index 93% rename from integration-tests/sync/src/main/java/io/realm/tests/sync/model/ProcessInfo.java rename to realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/ProcessInfo.java index d39312cca5..62e8ddcfcc 100644 --- a/integration-tests/sync/src/main/java/io/realm/tests/sync/model/ProcessInfo.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/ProcessInfo.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package io.realm.tests.sync.model; +package io.realm.objectserver.model; import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; public class ProcessInfo extends RealmObject { private String name; @@ -47,4 +46,4 @@ public long getThreadId() { public void setThreadId(long threadId) { this.threadId = threadId; } -} \ No newline at end of file +} diff --git a/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/TestObject.java similarity index 55% rename from integration-tests/sync/src/main/java/io/realm/tests/sync/utils/Constants.java rename to realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/TestObject.java index 540b919eea..2bd27a6ef8 100644 --- a/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/TestObject.java @@ -14,11 +14,27 @@ * limitations under the License. */ -package io.realm.tests.sync.utils; +package io.realm.objectserver.model; -public class Constants { - // to generate a valid token run this script - // https://realmio.slack.com/files/af/F1FSVND47/generate-realm-sync-credentials.sh - public static String USER_TOKEN = "ewoJImlkZW50aXR5IjogIk5hYmlsIiwKCSJhY2Nlc3MiOiBbInVwbG9hZCIsICJkb3dubG9hZCJdLAoJImFwcF9pZCI6ICJpby5yZWFsbS50ZXN0cyIKfQo=:"; - public static String SYNC_SERVER_URL = "realm://127.0.0.1:7800/public/tests"; +import io.realm.RealmObject; + +public class TestObject extends RealmObject { + private int intProp; + private String stringProp; + + public int getIntProp() { + return intProp; + } + + public void setIntProp(int intProp) { + this.intProp = intProp; + } + + public String getStringProp() { + return stringProp; + } + + public void setStringProp(String stringProp) { + this.stringProp = stringProp; + } } diff --git a/integration-tests/sync/src/main/java/io/realm/tests/sync/service/SendOneCommit.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java similarity index 77% rename from integration-tests/sync/src/main/java/io/realm/tests/sync/service/SendOneCommit.java rename to realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java index db408d2749..26c4f89de6 100644 --- a/integration-tests/sync/src/main/java/io/realm/tests/sync/service/SendOneCommit.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java @@ -14,17 +14,12 @@ * limitations under the License. */ -package io.realm.tests.sync.service; +package io.realm.objectserver.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.tests.sync.model.ProcessInfo; -import io.realm.tests.sync.utils.Constants; - /** * Open a sync Realm on a different process, then send one commit. */ @@ -33,22 +28,25 @@ public class SendOneCommit extends Service { @Override public void onCreate() { super.onCreate(); - final RealmConfiguration syncConfig = new RealmConfiguration - .Builder(this) + // FIXME: Disable for now + /* + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(this) .name(SendOneCommit.class.getSimpleName()) - .withSync(Constants.SYNC_SERVER_URL) - .syncUserToken(Constants.USER_TOKEN) + .serverUrl(Constants.SYNC_SERVER_URL) + .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL, Constants.USER_TOKEN)) .build(); + Realm.deleteRealm(syncConfig); Realm realm = Realm.getInstance(syncConfig); realm.beginTransaction(); ProcessInfo processInfo = realm.createObject(ProcessInfo.class); - processInfo.setName("Background"); + processInfo.setName("Background_Process1"); processInfo.setPid(android.os.Process.myPid()); processInfo.setThreadId(Thread.currentThread().getId()); realm.commitTransaction(); realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset + */ } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java new file mode 100644 index 0000000000..2bcdd9d717 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.service; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * Open a sync Realm on a different process, then send one commit. + */ +public class SendsALot extends Service { + + @Override + public void onCreate() { + super.onCreate(); + // FIXME: Disable for now. + /* + User user = UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL_2, Constants.USER_TOKEN); + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user) + .name(SendsALot.class.getSimpleName()) + .serverUrl(Constants.SYNC_SERVER_URL_2) + .user() + .build(); + Realm.deleteRealm(syncConfig); + Realm realm = Realm.getInstance(syncConfig); + + realm.beginTransaction(); + + for (int i = 0; i < 100; i++) { + TestObject testObject = realm.createObject(TestObject.class); + testObject.setIntProp(i); + testObject.setStringProp("property " + i); + } + realm.commitTransaction(); + + realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset + */ + } + + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java new file mode 100644 index 0000000000..e346c04c3c --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.utils; + +public class Constants { + // to generate a valid token follow the guide in + ///integration-tests/sync/test_server/keys/HowToGenerateKey.txt + public static String USER_TOKEN = "ewogICJpZGVudGl0eSI6ICJ0ZXN0MiIsCiAgImFjY2VzcyI6IFsKICAgICJkb3dubG9hZCIsCiAgICAidXBsb2FkIgogIF0sCiAgInRpbWVzdGFtcCI6IDE0NTU1MzA2MTQsCiAgImV4cGlyZXMiOiBudWxsLAogICJhcHBfaWQiOiAiaW8ucmVhbG0udGVzdHMuc3luYyIKfQ==" + + ":" + + "mR0/GMc0b5XHFNJEM4D9fb94oXMjho0jKxopaU1lQW4FqY1QPBa/bPiVCMhAosZVSNhEP6vEZxVjFHAxoPODKoml1Ry78geKt5Iql395HRvO6KCCN0VkMpx2eXy+SzF2pcEjU5jlldbTAcO6nMyVaQ9g2XF2SZPVjBqpkY1cy2IjMHN0HRWy9SfGelwZY/jW72jZM7+89kWpIB0SmNH8kEPKVZlnRMW4KwNAUPA8P0/+qyoRTr/4l7k7N6z5kBxIKB/+m55AeOUDiFsxA53QPlpHGvF7ThZpiv8i+UhyKZcQlXi1utoj8H1CzpeU/YzrrEf3xrr2qCO3/niU5WdnHA=="; + public static String SYNC_SERVER_URL = "realm://127.0.0.1:7800/tests"; + public static String SYNC_SERVER_URL_2 = "realm://127.0.0.1:7800/tests2"; + + public static String AUTH_SERVER_URL = "http://127.0.0.1:8080/"; + public static String AUTH_URL = AUTH_SERVER_URL + "auth"; +} diff --git a/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java similarity index 66% rename from integration-tests/sync/src/main/java/io/realm/tests/sync/utils/HttpUtils.java rename to realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index 1c77760837..d2cf5bf6ed 100644 --- a/integration-tests/sync/src/main/java/io/realm/tests/sync/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.tests.sync.utils; +package io.realm.objectserver.utils; import java.io.IOException; @@ -28,13 +28,13 @@ * temp directory & start a sync server on it for each unit test. */ public class HttpUtils { - private final OkHttpClient client = new OkHttpClient(); + private final static OkHttpClient client = new OkHttpClient(); // adb reverse tcp:8888 tcp:8888 // will forward this query to the host, running the integration test server on 8888 private final static String START_SERVER = "http://127.0.0.1:8888/start"; private final static String STOP_SERVER = "http://127.0.0.1:8888/stop"; - public void startSyncServer() throws Exception { + public static void startSyncServer() throws Exception { Request request = new Request.Builder() .url(START_SERVER) .build(); @@ -48,9 +48,38 @@ public void startSyncServer() throws Exception { } System.out.println(response.body().string()); + + // FIXME: Server ready checking should be done in the control server side! + if (!waitAuthServerReady()) { + stopSyncServer(); + throw new RuntimeException("Auth server cannot be started."); + } + } + + // Checking the server + private static boolean waitAuthServerReady() throws InterruptedException { + int retryTimes = 20; + Request request = new Request.Builder() + .url(Constants.AUTH_SERVER_URL) + .build(); + + while (retryTimes != 0) { + try { + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + return true; + } + } catch (IOException e) { + e.printStackTrace(); + Thread.sleep(50); + } + retryTimes--; + } + + return false; } - public void stopSyncServer() throws Exception { + public static void stopSyncServer() throws Exception { Request request = new Request.Builder() .url(STOP_SERVER) .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java new file mode 100644 index 0000000000..f341a32199 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.utils; + +import java.net.URI; +import java.net.URISyntaxException; + +import io.realm.User; +import io.realm.objectserver.utils.Constants; + +// Must be in `io.realm.objectserver` to work around package protected methods. +public class UserFactory { + // FIXME: Not working right now. + /* + public static User createDefaultUser(String SERVER_URL, String USER_TOKEN) { + try { + User user = User.createLocal(); + + user.addAccessToken(new URI(SERVER_URL), USER_TOKEN); + return user; + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + */ +} diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile new file mode 100644 index 0000000000..e659183e3b --- /dev/null +++ b/tools/sync_test_server/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:16.04 + +ARG ROS_DE_VERSION + +# Add realm repo +RUN apt-get update -qq \ + && apt-get install -y curl npm \ + && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash \ + && npm install winston temp httpdispatcher +COPY keys/private.pem keys/public.pem configuration.yml / +COPY ros-testing-server.js /usr/bin/ +# Install realm object server +RUN apt-get update -qq \ + && apt-get install -y realm-object-server-de=$ROS_DE_VERSION \ + && apt-get clean + +CMD /usr/bin/ros-testing-server.js /tmp/ros-testing-server.log diff --git a/tools/sync_test_server/configuration.yml b/tools/sync_test_server/configuration.yml new file mode 100644 index 0000000000..6080a3beda --- /dev/null +++ b/tools/sync_test_server/configuration.yml @@ -0,0 +1,227 @@ +# Realm Object Server Configuration +# +# For each possible setting, the commented out values are the default values +# unless another default is mentioned explicitly. +# +# Paths specified in this file can be either absolute or relative. +# Relative paths are relative to the current working directory. + + +## ---------------------------------------------------------------------------- +## The following options are MANDATORY, either by providing them in this file, +## or as command-line options: +## - storage: root_path +## - auth:public_key_path +## - auth:private_key_path +## ---------------------------------------------------------------------------- + + +storage: + ## The directory in which the realm server will store all its data files. + ## This configuration option is MANDATORY. + root_path: /var/realm/sync-services + +## ---------------------------------------------------------------------------- + +auth: + ## The path to the public and private keys (in PEM format) that will be used + ## to validate identity tokens sent by clients. + ## These configuration options are MANDATORY. + public_key_path: /public.pem + private_key_path: /private.pem + + database: + ## The path for the administration database synchronisation endpoint. Do NOT + ## change this unless asked by Realm Support. + # sync_uri_path: '/public/admin' + + ttls: + ## The validity duration for Refresh Tokens. This should be a fairly high + ## value, typically ranging 12 hours - 3 days. This value is represented in + ## seconds. Default: 24 hours. + # refresh_token: 86400 + + ## The validity duration for Access Tokens. This should be a fairly small + ## number, especially if you are concerned with revocations being applied + ## quickly. This value is represented in seconds. Default: 1 minute. + # access_token: 60 + +## ---------------------------------------------------------------------------- + +proxy: + ## Network settings for the externally accessible proxy module. + ## This can be enabled for both HTTP and HTTPS traffic simultaneously, and + ## forwards traffic to the sync and services internal modules. + ## It is possible to disable and replace the proxy module by another reverse proxy. + ## + ## Note: The proxy module forwards traffic to the internal modules on the + ## addresses and ports they listen on (as configured in the `network' section below). + ## + ## Shown below is a diagram of the default network configuration: + ## + ## +----------------------+ + ## | | + ## | Sync module | + ## | | + ## | (internal) | + ## | | + ## +-> | Defaults: | + ## +----------------+ +---------------------------+ | | Listen: 127.0.0.1 | + ## | | | | | | Ports: | + ## | Realm Client +------+ | Proxy module | | | WS: tcp/27800 | + ## | | | | | | | | + ## +----------------- | | (externally accessible) | | +----------------------+ + ## +----> | | | + ## | Defaults: | | + ## +----> | Listen: 0.0.0.0 +----+ + ## +------------ | | Ports: | | +----------------------+ + ## | | | | HTTP & WS: tcp/9080 | | | | + ## | Browser +------+ | HTTPS & WSS: tcp/9443 | | | Services module | + ## | | | | | | | + ## +-----------+ +---------------------------+ | | (internal) | + ## +-> | | + ## | Defaults: | + ## Note: The proxy module can be | Listen: 127.0.0.1 | + ## replaced by NGINX or other | Ports: | + ## reverse proxies | HTTP: tcp/27080 | + ## | | + ## +----------------------+ + + http: + ## Whether or not to enable the HTTP proxy module. It enables multiplexing requests + ## by forwarding incoming requests on a single port to all services. + # enable: true + + ## The address/interface on which the HTTP proxy module should listen. This defaults + ## to 127.0.0.1. If you wish to listen on all available interfaces, + ## uncomment the following line. + listen_address: '0.0.0.0' + + ## The port that the HTTP proxy module should bind to. + # listen_port: 9080 + + https: + ## Whether or not to enable the HTTPS proxy module. It enables multiplexing requests + ## by forwarding incoming requests on a single port to all services. + ## Note that even if it enabled, the HTTPS proxy will only start if supplied + ## with a valid pair of certificates through certificate_path and private_key_path below. + # enable: false + + ## The path to the certificate and private keys (in PEM format) that will be used + ## to set up the HTTPS server accepting connections. + ## These configuration options are MANDATORY to start the HTTPS proxy module. + # certificate_path: 'keys/https-proxy.crt' + # private_key_path: 'keys/https-proxy.key' + + ## The address/interface on which the HTTPS proxy module should listen. This defaults + ## to 127.0.0.1. If you wish to listen on all available interfaces, + ## uncomment the following line. + # listen_address: '0.0.0.0' + + ## The port that the HTTPS proxy module should bind to. + # listen_port: 9443 + +## ---------------------------------------------------------------------------- + +network: + ## Network settings for internal modules, to which traffic is forwarded from + ## the proxy module. The proxy module will automatically forward traffic to the + ## internal modules on the ports they are configured to listen on in this section. + + sync: + ## The address/interface on which the server should listen. This defaults + ## to 127.0.0.1. If you wish to listen on all available interfaces, + ## uncomment the following line. + listen_address: '0.0.0.0' + + ## The port on which to listen. The Realm sync server uses port 27800 by + ## default. For most deployments, there should not be a need to change this. + listen_port: 7800 + + http: + ## The address/interface on which the server should listen for HTTP + ## services. This includes Dashboard and Authentication APIs. + ## This defaults to 127.0.0.1. If you wish to listen on all available + ## interfaces, uncomment the following line. + listen_address: '0.0.0.0' + + ## The port on which to listen for incoming requests to the Dashboard + ## and authentication APIs. This defaults to 27080. + listen_port: 8080 + +## ---------------------------------------------------------------------------- + + providers: + ## Providers of authentication tokens. Each provider has a configuration + ## object associated with it. If a provider is included here and its + ## configuration is valid, it will be enabled. + + ## Possible providers: cloudkit, debug, facebook, realm, password + ## Providers 'realm' and 'password' are always enabled: + ## - The 'realm' provider is used to derive access tokens from a refresh token. + ## - The 'password' provider is required for the dashboard to work. It supports + ## authentication through username/password and uses a PBKDF2 implementation. + + ## This enables login via CloudKit's user record name. + # cloudkit: + ## The key ID retrieved when adding the public key derived from the + ## specified private_key_path in CloudKit's Server-to-Server Keys, + ## available through the API Access settings in the CloudKit dashboard. + # key_id: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' + + ## The path to the certificate. + # private_key_path: 'cloudkit_eckey.pem' + + ## The container identifier in reverse domain name notation. + # container: "iCloud.io.realm.exampleApp.ios" + + ## The environment in which CloudKit should be used. The default is + ## 'development'. For the production deployment for apps on the AppStore + ## you must specify 'production'. + # environment: 'development' + + ## This enables authentication via a Google Sign-In access token for a + ## specific app. + # google: + ## The client ID as retrieved when setting up the app in the Google + ## Developer Console. + # clientId: '012345678901-abcdefghijklmnopqrstvuvwxyz01234.apps.googleusercontent.com' + + ## This enables authentication via a Facebook access token for a specific app. + ## This provider needs no configuration (uncommenting the next line enables it). + # facebook: {} + +## ---------------------------------------------------------------------------- + +logging: + ## The logging level of the server. + ## + ## Note: This used to be an integer, but has been updated to be more + ## descriptive. The integer values are no longer supported. + ## + ## Possible values (from most to least verbose): + ## + ## all: no filtering + ## trace + ## debug + ## detail + ## info: good for production (default) + ## warn + ## error + ## fatal + ## off: all output suppressed + level: 'all' + + ## The file to which the synchronisation server should log. This should + ## be a writable path from the perspective of the user under which the + ## server runs. If no path is specified, the server will log to stdout. + path: '/tmp/realm-sync.log' + +## ---------------------------------------------------------------------------- + +performance: + ## The maximum number of Realm files that the server will have open + ## concurrently (LRU cache). The default is 256. + ## Only change this option if directed to by Realm support. + # max_open_files: 256 + diff --git a/tools/sync_test_server/keys/HowToGenerateKey.txt b/tools/sync_test_server/keys/HowToGenerateKey.txt new file mode 100644 index 0000000000..c3db0e9188 --- /dev/null +++ b/tools/sync_test_server/keys/HowToGenerateKey.txt @@ -0,0 +1,18 @@ +// The Base64-encoded user token is generated by the following command: +// cat test_token.json | base64 +// The Base64-encoded signature is generated by the following command: +// cat test_token.json | openssl dgst -sha256 -binary -sign private.pem | base64 +// The two are concatenated with a ':'. +// This token does not contain a "path" field, and therefore grants access to +// all Realms. + +// Example: +g_signed_test_user_token = + // cat test_token.json | base64 +"ewogICJpZGVudGl0eSI6ICJ0ZXN0IiwKICAiYWNjZXNzIjogWwogICAgImRvd25sb2FkIiwKICAgICJ1cGxvYWQiCiAgXSwKICAidGltZXN0YW1wIjogMTQ1NTUzMDYxNCwKICAiZXhwaXJlcyI6IG51bGwsCiAgImFwcF9pZCI6ICJpby5yZWFsbS50ZXN0cy5zeW5jIgp9" ++ ":" +// cat test_token.json | openssl dgst -sha256 -binary -sign private.pem | base64 +"Y5+K3Y+wd+McaZx6rte1MQvKpHgy7NoTqTzgF3CnGKcosMT7PkG1M71rLsq9/Fcldn6G26Bn3kb0vnw93TS2Ox4wa0FMiObK+N7VNdI6p/+dG5bDjBhtW2AFd2P0nOUCvx39EIdLVnGr3JUidJZEZGzFyFOdZVpnmIAnHNDaOIPOXt4vnASJ/dBjUTkOlexOwSRKIK1hvkA1GO9zpvnG5EbnVG6LuVSRM93Hp0tzuFdesns19P827/FsdZATDA9TFlwVTIa7vHz0KbzolSXKvIiOr5XWC2NXyDFEowxwFHyCuXN52jk9kylagFDTBvXu1ddmDZjWxg9SinJzS4lsYA==" + + +Reference https://github.com/realm/realm-sync/blob/master/test/test_sync.cpp#L65 \ No newline at end of file diff --git a/tools/sync_test_server/keys/private.pem b/tools/sync_test_server/keys/private.pem new file mode 100644 index 0000000000..e8f1a123c3 --- /dev/null +++ b/tools/sync_test_server/keys/private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAo65ZQ8mFIVk0ZB22bHdNuBr4G3K7SwfFlUhmBmjQFb2EdopA +nmu/XdXn+Zw3pmYlzIxe+3RX9M4eh8luIil0J2Nlb7tHOPZQkQAiuuud8JQ9RTND +ixUQTGS4YmhyQv+7LA9cjdaczD3Bf3Nw/yZQTQQqw7vsbTUJeAMz+6EFLeMj2Lxn +ZVLP7ePezxaSpKiQ8mp7eurQrZIqmvEC1xp8a7XvkgtqnMBepauIBiw6Wlpin0lP +D3P5uMtrP+z5MmQXpP/GOp6XjBlULMQAMH/V03WYMwnevzMWmKhF2apepnjd65nB +h29iaFuiE8tYvKpJxmrsmoU+aOvMt3ZORxj7LQIDAQABAoIBAGfSlWh8EOgAT00Z +07alTjTzVmECu25yNY/lZmG2ZhcEKVuPgkF6kt4Qap5XyqzPqjY+65iQSaJMg+0Z +hbRBmx3I3HSs1BZ7lssCzQTHo10QinS0ealk6Pur/5DcM23wDGd8LvcBJGAg4/XV +4dzWDqVreTzCnMsAk0r+rSB1GHXr0/jMiivPvUzvxpVRZ+dyGPdxUFBQivPGC7h8 +3VJRLj8zBFlf0az7xeVCGgZMAtiaJhhGtG2QCBKhk3mqlDmhIB6jTai+b+vnL1KK +tTOOhMsYXIhJXYeE6H2aXNn7z53sKoiRq2Zptzfl9csbQ5yelbtZ05CRC+nzAscr +XOl2BjkCgYEA0QYexhXsd9OA8vU+kfm1WyLZmGc1biK+RLqV+nhXzy1lRy1DdaOq +6raNgaZ9xgX5zHLqxk+2s5+dWMfyvbUxDPhl7C6R0yLNuXzW2tjsy3T5AI8ApWrA +STUOaamLyaqRt0VB8AVSco2bvHCVEjyY+Bc7RD0LHnDfBUSwobr+FrMCgYEAyHd+ +nGsWhqGEabtfZCS8f1f1PnS4jge5VY9PFjgSLT9K6KJv00tmG9PGRhKXgGiLs+DW +0EHiwWIYpAGHVOvnndWIUsxo14Mg8cRfJlA/a87RrXMNu6I/4rGPRwHqeRauNWmu +wuNSJZTul09UYo7iHqtiEFOxpSEufC4965QUlp8CgYA/9qZ+KYFWXdPNBX1jQE3e +GLkLqTGxhVJCR/LTVfZRAOxILrLBEheggcKl1SQR8Aw0I0py6zvWldaZr345zXO4 +K19NOicHvFPGGkzJZa54yE/WeuxQsm0rOeAyN17+lILI2ZnG8Gn9ghYRQUZs8TxC +VyGczS1U4Gdu/kkrBMTyfwKBgQCRYh//fqZ6gx7Ns2bt8LqHvBmO7wV9c9qUU3du +zMFZ8UH5Tvy8hz0JR1/PJ+KZ7LgMfy4rIO07hFIMd1NXYjK6w8a3DamnSmEVFW5Q +Efi8zeRA32UBRB0C4fTf8WLD6I/1Cq0Eh+nmeYlDUPQI+kjBJ1faMWhvMo5M3xhn +BiCcTwKBgQC/oQ5R6avo15UK7Tituj9TqduLf4leGJwn3ht6GsAPNDENDJZJc30A +wL+ghnvUieG1fz3OelZPx3Ber5QdNzhM8+24klevCLaCdF8alhg9nIEtWFrGpXEv +RLZ4jP2FUo1XJDNqXK4l17slzdWzEs1jiB7ePLvpoiA+GVcL3Anmkg== +-----END RSA PRIVATE KEY----- diff --git a/tools/sync_test_server/keys/public.pem b/tools/sync_test_server/keys/public.pem new file mode 100644 index 0000000000..8f81325947 --- /dev/null +++ b/tools/sync_test_server/keys/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo65ZQ8mFIVk0ZB22bHdN +uBr4G3K7SwfFlUhmBmjQFb2EdopAnmu/XdXn+Zw3pmYlzIxe+3RX9M4eh8luIil0 +J2Nlb7tHOPZQkQAiuuud8JQ9RTNDixUQTGS4YmhyQv+7LA9cjdaczD3Bf3Nw/yZQ +TQQqw7vsbTUJeAMz+6EFLeMj2LxnZVLP7ePezxaSpKiQ8mp7eurQrZIqmvEC1xp8 +a7XvkgtqnMBepauIBiw6Wlpin0lPD3P5uMtrP+z5MmQXpP/GOp6XjBlULMQAMH/V +03WYMwnevzMWmKhF2apepnjd65nBh29iaFuiE8tYvKpJxmrsmoU+aOvMt3ZORxj7 +LQIDAQAB +-----END PUBLIC KEY----- diff --git a/tools/sync_test_server/keys/test_token.json b/tools/sync_test_server/keys/test_token.json new file mode 100644 index 0000000000..8d043f2fb7 --- /dev/null +++ b/tools/sync_test_server/keys/test_token.json @@ -0,0 +1,11 @@ +{ + "identity": "test2", + "access": [ + "download", + "upload" + ], + "timestamp": 1455530614, + "expires": null, + "app_id": "io.realm.tests.sync" +} + diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js new file mode 100755 index 0000000000..c182652c39 --- /dev/null +++ b/tools/sync_test_server/ros-testing-server.js @@ -0,0 +1,86 @@ +#!/usr/bin/env nodejs + +var winston = require('winston');//logging +const temp = require('temp'); +const spawn = require('child_process').spawn; +var http = require('http'); +var dispatcher = require('httpdispatcher'); + +// Automatically track and cleanup files at exit +temp.track(); + +if (process. argv. length <= 2) { + console.log("Usage: " + __filename + " somefile.log"); + process.exit(-1); +} +const logFile = process.argv[2]; +winston.level = 'debug'; +winston.add(winston.transports.File, { filename: logFile }); + +const PORT = 8888; + +function handleRequest(request, response) { + try { + //log the request on console + winston.log(request.url); + //Disptach + dispatcher.dispatch(request, response); + } catch(err) { + console.log(err); + } +} + +var syncServerChildProcess = null; + +function startRealmObjectServer() { + stopRealmObjectServer(); + temp.mkdir('ros', function(err, path) { + if (!err) { + winston.info("Starting sync server in ", path); + syncServerChildProcess = spawn('realm-object-server', + ['--root', path, + '--configuration', '/configuration.yml']); + // local config: + syncServerChildProcess.stdout.on('data', (data) => { + winston.info(`stdout: ${data}`); + }); + + syncServerChildProcess.stderr.on('data', (data) => { + winston.info(`stderr: ${data}`); + }); + + syncServerChildProcess.on('close', (code) => { + winston.info(`child process exited with code ${code}`); + }); + } + }); +} + +function stopRealmObjectServer() { + if (syncServerChildProcess) { + syncServerChildProcess.kill(); + syncServerChildProcess = null; + } +} + + +// start sync server +dispatcher.onGet("/start", function(req, res) { + startRealmObjectServer(); + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Starting a server'); +}); + +// stop a previously started sync server +dispatcher.onGet("/stop", function(req, res) { + stopRealmObjectServer(); + winston.info("Sync server stopped"); + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Stopping the server'); +}); + +//Create and start the Http server +var server = http.createServer(handleRequest); +server.listen(PORT, function() { + winston.info("Integration test server listening on: 127.0.0.1:%s", PORT); +}); diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh new file mode 100755 index 0000000000..00930f5eb1 --- /dev/null +++ b/tools/sync_test_server/start_server.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# Get the script dir which contains the Dockerfile +DOCKERFILE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +ROS_DE_VERSION=$(grep REALM_OBJECT_SERVER_DE_VERSION $DOCKERFILE_DIR/../../dependencies.list | cut -d'=' -f2) + +TMP_DIR=$(mktemp -d /tmp/sync-test.XXXX) || { echo "Failed to mktemp $TEST_TEMP_DIR" ; exit 1 ; } + +adb reverse tcp:7800 tcp:7800 && \ +adb reverse tcp:8080 tcp:8080 && \ +adb reverse tcp:8888 tcp:8888 || { echo "Failed to reverse adb port." ; exit 1 ; } + +docker build $DOCKERFILE_DIR --build-arg ROS_DE_VERSION=$ROS_DE_VERSION -t sync-test-server || { echo "Failed to build Docker image." ; exit 1 ; } + +echo "See log files in $TMP_DIR" +docker run -p 8080:8080 -p 7800:7800 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server diff --git a/tools/sync_test_server/stop_server.sh b/tools/sync_test_server/stop_server.sh new file mode 100755 index 0000000000..6dd95f1fb4 --- /dev/null +++ b/tools/sync_test_server/stop_server.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +docker stop sync-test-server -t0 +docker rm sync-test-server From ddcc6395a76013609ab3cbf4d6de669b4372c231 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 14 Oct 2016 17:34:23 +0200 Subject: [PATCH 0166/2110] Align Sync classes with Cocoa (#3630) --- CHANGELOG.md | 4 ++ .../objectserver/CounterActivity.java | 8 +-- .../examples/objectserver/LoginActivity.java | 12 ++-- .../MainActivity.java | 12 ++-- .../io/realm/AuthenticateRequestTests.java | 6 +- .../java/io/realm/CredentialsTests.java | 28 ++++---- .../java/io/realm/SchemaTests.java | 3 +- .../java/io/realm/SessionTests.java | 8 +-- .../java/io/realm/SyncConfigurationTests.java | 42 +++++------ .../java/io/realm/SyncManagerTests.java | 24 +++---- .../java/io/realm/UserTests.java | 10 +-- .../java/io/realm/android/UserStoreTest.java | 9 ++- .../java/io/realm/util/SyncTestUtils.java | 12 ++-- .../realm-library/src/main/cpp/CMakeLists.txt | 4 +- ...rnal_objectserver_ObjectServerSession.cpp} | 12 ++-- .../java/io/realm/AuthenticationListener.java | 8 +-- .../java/io/realm/ObjectServerError.java | 2 +- .../java/io/realm/SyncConfiguration.java | 44 ++++++------ ...{Credentials.java => SyncCredentials.java} | 40 +++++------ .../java/io/realm/SyncManager.java | 30 ++++---- .../realm/{Session.java => SyncSession.java} | 40 +++++------ .../io/realm/{User.java => SyncUser.java} | 70 +++++++++---------- .../objectServer/java/io/realm/UserStore.java | 18 ++--- .../io/realm/android/SecureUserStore.java | 28 ++++---- .../realm/android/SharedPrefsUserStore.java | 22 +++--- .../internal/android/crypto/CipherClient.java | 6 +- .../internal/network/AuthenticateRequest.java | 4 +- .../network/AuthenticationServer.java | 10 +-- .../realm/internal/network/LogoutRequest.java | 4 +- .../network/OkHttpAuthenticationServer.java | 8 +-- .../objectserver/AuthenticatingState.java | 8 +-- .../internal/objectserver/FsmAction.java | 4 +- .../realm/internal/objectserver/FsmState.java | 14 ++-- ...cSession.java => ObjectServerSession.java} | 44 ++++++------ .../{SyncUser.java => ObjectServerUser.java} | 19 +++-- .../internal/objectserver/SessionStore.java | 22 +++--- .../internal/objectserver/StoppedState.java | 4 +- .../objectserver/SyncObjectServerFacade.java | 14 ++-- .../syncpolicy/AutomaticSyncPolicy.java | 14 ++-- .../realm/internal/syncpolicy/SyncPolicy.java | 46 ++++++------ 40 files changed, 359 insertions(+), 358 deletions(-) rename realm/realm-library/src/main/cpp/{io_realm_internal_objectserver_SyncSession.cpp => io_realm_internal_objectserver_ObjectServerSession.cpp} (84%) rename realm/realm-library/src/objectServer/java/io/realm/{Credentials.java => SyncCredentials.java} (82%) rename realm/realm-library/src/objectServer/java/io/realm/{Session.java => SyncSession.java} (71%) rename realm/realm-library/src/objectServer/java/io/realm/{User.java => SyncUser.java} (84%) rename realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/{SyncSession.java => ObjectServerSession.java} (90%) rename realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/{SyncUser.java => ObjectServerUser.java} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index de14e84a56..4b68f42ec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 2.1.0 +### Breaking changes + +* Renamed `User` to `SyncUser`, `Credentials` to `SyncCredentials` and `Session` to `SyncSession` to align names with Cocoa. + ### Enhancement * `Realm.compactRealm()` works for encrypted Realms. diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index accbf13284..97c67d2443 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -31,7 +31,7 @@ import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.SyncConfiguration; -import io.realm.User; +import io.realm.SyncUser; import io.realm.examples.objectserver.model.CRDTCounter; public class CounterActivity extends AppCompatActivity { @@ -40,7 +40,7 @@ public class CounterActivity extends AppCompatActivity { private Realm realm; private CRDTCounter counter; - private User user; + private SyncUser user; @BindView(R.id.text_counter) TextView counterView; @@ -51,7 +51,7 @@ protected void onCreate(Bundle savedInstanceState) { ButterKnife.bind(this); // Check if we have a valid user, otherwise redirect to login - if (User.currentUser() == null) { + if (SyncUser.currentUser() == null) { gotoLoginActivity(); } } @@ -59,7 +59,7 @@ protected void onCreate(Bundle savedInstanceState) { @Override protected void onStart() { super.onStart(); - user = User.currentUser(); + user = SyncUser.currentUser(); if (user != null) { // Create a RealmConfiguration for our user SyncConfiguration config = new SyncConfiguration.Builder(user, REALM_URL) diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java index 7018e8bd66..5ce56afb27 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -26,9 +26,9 @@ import butterknife.BindView; import butterknife.ButterKnife; -import io.realm.Credentials; +import io.realm.SyncCredentials; import io.realm.ObjectServerError; -import io.realm.User; +import io.realm.SyncUser; import io.realm.UserStore; import static io.realm.ErrorCode.UNKNOWN_ACCOUNT; @@ -76,11 +76,11 @@ public void login(boolean createUser) { String username = this.username.getText().toString(); String password = this.password.getText().toString(); - Credentials creds = Credentials.usernamePassword(username, password, createUser); + SyncCredentials creds = SyncCredentials.usernamePassword(username, password, createUser); String authUrl = "http://" + BuildConfig.OBJECT_SERVER_IP + ":9080/auth"; - User.Callback callback = new User.Callback() { + SyncUser.Callback callback = new SyncUser.Callback() { @Override - public void onSuccess(User user) { + public void onSuccess(SyncUser user) { progressDialog.dismiss(); onLoginSuccess(); } @@ -103,7 +103,7 @@ public void onError(ObjectServerError error) { } }; - User.loginAsync(creds, authUrl, callback); + SyncUser.loginAsync(creds, authUrl, callback); } @Override diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java b/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java index c570b81c2f..7042cf7094 100644 --- a/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java +++ b/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java @@ -33,10 +33,10 @@ import io.realm.Realm; import io.realm.SyncConfiguration; import io.realm.SyncManager; -import io.realm.User; +import io.realm.SyncUser; import io.realm.android.SecureUserStore; import io.realm.internal.android.crypto.CipherClient; -import io.realm.internal.objectserver.SyncUser; +import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; /** @@ -87,7 +87,7 @@ private void buildSyncConf () { try { SyncManager.setUserStore(new SecureUserStore(MainActivity.this)); // the rest of Sync logic ... - User user = createTestUser(0); + SyncUser user = createTestUser(0); String url = "realm://objectserver.realm.io/default"; SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); Realm realm = Realm.getInstance(secureConfig); @@ -101,10 +101,10 @@ private void buildSyncConf () { private final static String USER_TOKEN = UUID.randomUUID().toString(); private final static String REALM_TOKEN = UUID.randomUUID().toString(); - private static User createTestUser(long expires) { + private static SyncUser createTestUser(long expires) { Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); - SyncUser.AccessDescription desc = new SyncUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); + ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); JSONObject obj = new JSONObject(); try { @@ -117,7 +117,7 @@ private static User createTestUser(long expires) { obj.put("authUrl", "http://objectserver.realm.io/auth"); obj.put("userToken", userToken.toJson()); obj.put("realms", realmList); - return User.fromJson(obj.toString()); + return SyncUser.fromJson(obj.toString()); } catch (JSONException e) { throw new RuntimeException(e); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index 1ae3242220..5d90e9297f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -42,7 +42,7 @@ public void realmLogin() throws URISyntaxException, JSONException { @Test public void userLogin() throws URISyntaxException, JSONException { - AuthenticateRequest request = AuthenticateRequest.userLogin(Credentials.facebook("foo")); + AuthenticateRequest request = AuthenticateRequest.userLogin(SyncCredentials.facebook("foo")); JSONObject obj = new JSONObject(request.toJson()); assertFalse(obj.has("path")); @@ -66,11 +66,11 @@ public void userRefresh() throws URISyntaxException, JSONException { public void errorsNotWrapped() { AuthenticationServer originalAuthServer = SyncManager.getAuthServer(); AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); - when(authServer.loginUser(any(Credentials.class), any(URL.class))).thenReturn(SyncTestUtils.createErrorResponse(ErrorCode.ACCESS_DENIED)); + when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createErrorResponse(ErrorCode.ACCESS_DENIED)); SyncManager.setAuthServerImpl(authServer); try { - User.login(Credentials.facebook("foo"), "http://foo.bar/auth"); + SyncUser.login(SyncCredentials.facebook("foo"), "http://foo.bar/auth"); fail(); } catch (ObjectServerError e) { assertEquals(ErrorCode.ACCESS_DENIED, e.getErrorCode()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java index c464499403..26ded3ef2d 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java @@ -35,7 +35,7 @@ public class CredentialsTests { @Test public void getUserInfo_isUnmodifiable() { - Credentials creds = Credentials.custom("foo", "bar", null); + SyncCredentials creds = SyncCredentials.custom("foo", "bar", null); Map userInfo = creds.getUserInfo(); try { userInfo.put("boom", null); @@ -46,27 +46,27 @@ public void getUserInfo_isUnmodifiable() { @Test public void facebook() { - Credentials creds = Credentials.facebook("foo"); + SyncCredentials creds = SyncCredentials.facebook("foo"); - assertEquals(Credentials.IdentityProvider.FACEBOOK, creds.getIdentityProvider()); + assertEquals(SyncCredentials.IdentityProvider.FACEBOOK, creds.getIdentityProvider()); assertEquals("foo", creds.getUserIdentifier()); assertTrue(creds.getUserInfo().isEmpty()); } @Test public void google() { - Credentials creds = Credentials.google("foo"); + SyncCredentials creds = SyncCredentials.google("foo"); - assertEquals(Credentials.IdentityProvider.GOOGLE, creds.getIdentityProvider()); + assertEquals(SyncCredentials.IdentityProvider.GOOGLE, creds.getIdentityProvider()); assertEquals("foo", creds.getUserIdentifier()); assertTrue(creds.getUserInfo().isEmpty()); } @Test public void twitter() { - Credentials creds = Credentials.twitter("foo"); + SyncCredentials creds = SyncCredentials.twitter("foo"); - assertEquals(Credentials.IdentityProvider.TWITTER, creds.getIdentityProvider()); + assertEquals(SyncCredentials.IdentityProvider.TWITTER, creds.getIdentityProvider()); assertEquals("foo", creds.getUserIdentifier()); assertTrue(creds.getUserInfo().isEmpty()); } @@ -76,7 +76,7 @@ public void facebook_invalidInput() { String[] invalidInput = { null, ""}; for (String input : invalidInput) { try { - Credentials.facebook(input); + SyncCredentials.facebook(input); fail(input + " should have failed"); } catch (IllegalArgumentException ignored) { } @@ -85,11 +85,11 @@ public void facebook_invalidInput() { @Test public void usernamePassword() { - Credentials creds = Credentials.usernamePassword("foo", "bar", true); + SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar", true); assertEquals("foo", creds.getUserIdentifier()); Map userInfo = creds.getUserInfo(); - assertEquals(Credentials.IdentityProvider.USERNAME_PASSWORD, creds.getIdentityProvider()); + assertEquals(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, creds.getIdentityProvider()); assertEquals("bar", userInfo.get("password")); assertTrue((Boolean) userInfo.get("register")); } @@ -100,7 +100,7 @@ public void usernamePassword_invalidUserName() { String[] invalidInput = { null, ""}; for (String input : invalidInput) { try { - Credentials.usernamePassword(input, "bar", true); + SyncCredentials.usernamePassword(input, "bar", true); fail(input + " should have failed"); } catch (IllegalArgumentException ignored) { } @@ -113,7 +113,7 @@ public void custom_invalidUserName() { userInfo.put("custom", "property"); for (String username : new String[]{null, ""}) { try { - Credentials.custom("facebook", username, userInfo); + SyncCredentials.custom("facebook", username, userInfo); fail(); } catch (IllegalArgumentException ignored) { } @@ -124,7 +124,7 @@ public void custom_invalidUserName() { public void custom() { Map userInfo = new HashMap(); userInfo.put("custom", "property"); - Credentials creds = Credentials.custom("customProvider", "foo", userInfo); + SyncCredentials creds = SyncCredentials.custom("customProvider", "foo", userInfo); assertEquals("foo", creds.getUserIdentifier()); assertEquals("customProvider", creds.getIdentityProvider()); @@ -139,7 +139,7 @@ public void custom_invalidProvider() { for (String provider : new String[]{null, ""}) { try { - Credentials.custom(null, "foo", userInfo); + SyncCredentials.custom(null, "foo", userInfo); fail(); } catch (IllegalArgumentException ignored) { } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java index 8dc0c1eaa9..d9dd20f801 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java @@ -17,7 +17,6 @@ package io.realm; -import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.After; @@ -44,7 +43,7 @@ public class SchemaTests { @Before public void setUp() { - User user = SyncTestUtils.createTestUser(); + SyncUser user = SyncTestUtils.createTestUser(); config = new SyncConfiguration.Builder(user, "realm://objectserver.realm.io/~/default").build(); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 1fe1112e48..66a072e05f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -28,7 +28,7 @@ import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.OkHttpAuthenticationServer; -import io.realm.internal.objectserver.SyncSession; +import io.realm.internal.objectserver.ObjectServerSession; import io.realm.rule.TestRealmConfigurationFactory; import static io.realm.util.SyncTestUtils.createTestUser; @@ -43,7 +43,7 @@ public class SessionTests { private Context context; private AuthenticationServer authServer; private SyncConfiguration configuration; - private User user; + private SyncUser user; @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @@ -62,14 +62,14 @@ public void tearDown() throws Exception { @Test public void get_syncValues() { - SyncSession internalSession = new SyncSession( + ObjectServerSession internalSession = new ObjectServerSession( configuration, authServer, configuration.getUser().getSyncUser(), configuration.getSyncPolicy(), configuration.getErrorHandler() ); - Session session = new Session(internalSession); + SyncSession session = new SyncSession(internalSession); assertEquals("realm://objectserver.realm.io/JohnDoe/default", session.getServerUrl().toString()); assertEquals(user, session.getUser()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 8cd5e59097..d9f20f456c 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -84,7 +84,7 @@ public void user_invalidUserThrows() { } catch (IllegalArgumentException ignore) { } - User user = createTestUser(0); // Create user that has expired credentials + SyncUser user = createTestUser(0); // Create user that has expired credentials try { new SyncConfiguration.Builder(user, "realm://ros.realm.io/default"); } catch (IllegalArgumentException ignore) { @@ -93,7 +93,7 @@ public void user_invalidUserThrows() { @Test public void serverUrl_setsFolderAndFileName() { - User user = createTestUser(); + SyncUser user = createTestUser(); String[][] validUrls = { // , , { "realm://objectserver.realm.io/~/default", "realm-object-server/" + user.getIdentity(), "default" }, @@ -183,9 +183,9 @@ public void serverUrl_port() { @Test public void errorHandler() { SyncConfiguration.Builder builder = new SyncConfiguration.Builder(createTestUser(), "realm://objectserver.realm.io/default"); - Session.ErrorHandler errorHandler = new Session.ErrorHandler() { + SyncSession.ErrorHandler errorHandler = new SyncSession.ErrorHandler() { @Override - public void onError(Session session, ObjectServerError error) { + public void onError(SyncSession session, ObjectServerError error) { } }; @@ -196,16 +196,16 @@ public void onError(Session session, ObjectServerError error) { @Test public void errorHandler_fromSyncManager() { // Set default error handler - Session.ErrorHandler errorHandler = new Session.ErrorHandler() { + SyncSession.ErrorHandler errorHandler = new SyncSession.ErrorHandler() { @Override - public void onError(Session session, ObjectServerError error) { + public void onError(SyncSession session, ObjectServerError error) { } }; SyncManager.setDefaultSessionErrorHandler(errorHandler); // Create configuration using the default handler - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); assertEquals(errorHandler, config.getErrorHandler()); @@ -215,7 +215,7 @@ public void onError(Session session, ObjectServerError error) { @Test public void errorHandler_nullThrows() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration.Builder builder = new SyncConfiguration.Builder(user, url); @@ -227,7 +227,7 @@ public void errorHandler_nullThrows() { @Test public void equals() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url) .build(); @@ -236,7 +236,7 @@ public void equals() { @Test public void not_equals_same() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config1 = new SyncConfiguration.Builder(user, url).build(); SyncConfiguration config2 = new SyncConfiguration.Builder(user, url).build(); @@ -246,7 +246,7 @@ public void not_equals_same() { @Test public void equals_not() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url1 = "realm://objectserver.realm.io/default1"; String url2 = "realm://objectserver.realm.io/default2"; SyncConfiguration config1 = new SyncConfiguration.Builder(user, url1).build(); @@ -256,7 +256,7 @@ public void equals_not() { @Test public void hashCode_equal() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url) .build(); @@ -266,7 +266,7 @@ public void hashCode_equal() { @Test public void hashCode_notEquals() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url1 = "realm://objectserver.realm.io/default1"; String url2 = "realm://objectserver.realm.io/default2"; SyncConfiguration config1 = new SyncConfiguration.Builder(user, url1).build(); @@ -276,7 +276,7 @@ public void hashCode_notEquals() { @Test public void get_syncSpecificValues() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); assertTrue(user.equals(config.getUser())); @@ -287,7 +287,7 @@ public void get_syncSpecificValues() { @Test public void encryption() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url) .encryptionKey(TestHelper.getRandomKey()) @@ -297,7 +297,7 @@ public void encryption() { @Test(expected = IllegalArgumentException.class) public void encryption_invalid_null() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; new SyncConfiguration.Builder(user, url).encryptionKey(null); @@ -305,7 +305,7 @@ public void encryption_invalid_null() { @Test(expected = IllegalArgumentException.class) public void encryption_invalid_wrong_length() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; new SyncConfiguration.Builder(user, url).encryptionKey(new byte[]{1, 2, 3}); @@ -313,14 +313,14 @@ public void encryption_invalid_wrong_length() { @Test(expected = IllegalArgumentException.class) public void directory_null() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; new SyncConfiguration.Builder(user, url).directory(null); } @Test(expected = IllegalArgumentException.class) public void directory_writeProtectedDir() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; File dir = new File("/"); @@ -329,7 +329,7 @@ public void directory_writeProtectedDir() { @Test public void directory_dirIsAFile() throws IOException { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; File dir = configFactory.getRoot(); @@ -355,7 +355,7 @@ public void deleteOnLogout() { @Test public void initialData() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 566228e7de..15d366a4da 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -53,22 +53,22 @@ public void setUp() { context = InstrumentationRegistry.getContext(); userStore = new UserStore() { @Override - public User put(String key, User user) { + public SyncUser put(String key, SyncUser user) { return null; } @Override - public User get(String key) { + public SyncUser get(String key) { return null; } @Override - public User remove(String key) { + public SyncUser remove(String key) { return null; } @Override - public Collection allUsers() { + public Collection allUsers() { return null; } @@ -109,17 +109,17 @@ public void set_userStore_null() { @Test public void authListener() { - User user = createTestUser(); + SyncUser user = createTestUser(); final int[] counter = {0, 0}; AuthenticationListener authenticationListener = new AuthenticationListener() { @Override - public void loggedIn(User user) { + public void loggedIn(SyncUser user) { counter[0]++; } @Override - public void loggedOut(User user) { + public void loggedOut(SyncUser user) { counter[1]++; } }; @@ -138,17 +138,17 @@ public void authListener_null() { @Test public void authListener_remove() { - User user = createTestUser(); + SyncUser user = createTestUser(); final int[] counter = {0, 0}; AuthenticationListener authenticationListener = new AuthenticationListener() { @Override - public void loggedIn(User user) { + public void loggedIn(SyncUser user) { counter[0]++; } @Override - public void loggedOut(User user) { + public void loggedOut(SyncUser user) { counter[1]++; } }; @@ -167,12 +167,12 @@ public void loggedOut(User user) { @Test public void session() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url) .build(); - Session session = SyncManager.getSession(config); + SyncSession session = SyncManager.getSession(config); assertEquals(user, session.getUser()); // see also SessionTests } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java index 0f90fe6905..ded9ee13a2 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java @@ -49,8 +49,8 @@ public void setUp() { @Test public void toAndFromJson() { - User user1 = createTestUser(); - User user2 = User.fromJson(user1.toJson()); + SyncUser user1 = createTestUser(); + SyncUser user2 = SyncUser.fromJson(user1.toJson()); assertEquals(user1, user2); } @@ -63,13 +63,13 @@ public void currentUser_returnsNullIfUserExpired() { userStore.put(UserStore.CURRENT_USER_KEY, SyncTestUtils.createTestUser(Long.MIN_VALUE)); // Invalid users should not be returned when asking the for the current user - assertNull(User.currentUser()); + assertNull(SyncUser.currentUser()); } // `all()` returns an empty list if no users are logged in @Test public void all_empty() { - Collection users = User.all(); + Collection users = SyncUser.all(); assertTrue(users.isEmpty()); } @@ -82,7 +82,7 @@ public void all_validUsers() { userStore.put(UserStore.CURRENT_USER_KEY, SyncTestUtils.createTestUser(Long.MIN_VALUE)); userStore.put(UserStore.CURRENT_USER_KEY, SyncTestUtils.createTestUser(Long.MAX_VALUE)); - Collection users = User.all(); + Collection users = SyncUser.all(); assertEquals(1, users.size()); assertTrue(users.iterator().next().isValid()); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java index b856aa4c7b..271217da05 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java @@ -30,9 +30,8 @@ import io.realm.Realm; import io.realm.RealmConfiguration; -import io.realm.User; +import io.realm.SyncUser; import io.realm.UserStore; -import io.realm.android.SecureUserStore; import io.realm.rule.TestRealmConfigurationFactory; import static io.realm.util.SyncTestUtils.createTestUser; @@ -62,11 +61,11 @@ public void tearDown() { @Ignore("See https://github.com/realm/realm-java/issues/3555") @Test public void encrypt_decrypt_UsingAndroidKeyStoreUserStore() throws KeyStoreException { - User user = createTestUser(); + SyncUser user = createTestUser(); UserStore userStore = new SecureUserStore(InstrumentationRegistry.getTargetContext()); - User savedUser = userStore.put("crypted_entry", user); + SyncUser savedUser = userStore.put("crypted_entry", user); assertNull(savedUser); - User decrypted_entry = userStore.get("crypted_entry"); + SyncUser decrypted_entry = userStore.get("crypted_entry"); assertEquals(user, decrypted_entry); } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index dda3944952..a882b255a6 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -24,9 +24,9 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; -import io.realm.User; +import io.realm.SyncUser; import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.objectserver.SyncUser; +import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; public class SyncTestUtils { @@ -34,14 +34,14 @@ public class SyncTestUtils { public static String USER_TOKEN = UUID.randomUUID().toString(); public static String REALM_TOKEN = UUID.randomUUID().toString(); - public static User createTestUser() { + public static SyncUser createTestUser() { return createTestUser(Long.MAX_VALUE); } - public static User createTestUser(long expires) { + public static SyncUser createTestUser(long expires) { Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); - SyncUser.AccessDescription desc = new SyncUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); + ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); JSONObject obj = new JSONObject(); try { @@ -54,7 +54,7 @@ public static User createTestUser(long expires) { obj.put("authUrl", "http://objectserver.realm.io/auth"); obj.put("userToken", userToken.toJson()); obj.put("realms", realmList); - return User.fromJson(obj.toString()); + return SyncUser.fromJson(obj.toString()); } catch (JSONException e) { throw new RuntimeException(e); } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 957d90fda2..f9c3dad16b 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -41,7 +41,7 @@ set(classes_LIST set(jni_headers_PATH ${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST - io.realm.SyncManager io.realm.internal.objectserver.SyncSession) + io.realm.SyncManager io.realm.internal.objectserver.ObjectServerSession) endif() create_javah(TARGET jni_headers CLASSES ${classes_LIST} @@ -141,7 +141,7 @@ file(GLOB jni_SRC if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectserver_SyncSession.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectserver_ObjectServerSession.cpp) endif() # Object Store source files diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp similarity index 84% rename from realm/realm-library/src/main/cpp/io_realm_internal_objectserver_SyncSession.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp index f790f75da0..469c30b376 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp @@ -16,7 +16,7 @@ #include -#include "io_realm_internal_objectserver_SyncSession.h" +#include "io_realm_internal_objectserver_ObjectServerSession.h" #include "objectserver_shared.hpp" #include "util.hpp" #include @@ -37,7 +37,7 @@ using namespace realm; using namespace sync; -JNIEXPORT jlong JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeCreateSession +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeCreateSession (JNIEnv *env, jobject obj, jstring localRealmPath) { TR_ENTER(env) @@ -49,7 +49,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeCr return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeBind +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeBind (JNIEnv *env, jobject, jlong sessionPointer, jstring remoteUrl, jstring accessToken) { TR_ENTER(env) @@ -69,7 +69,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeBin } -JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeUnbind +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeUnbind (JNIEnv *env, jobject, jlong sessionPointer) { TR_ENTER(env) @@ -78,7 +78,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeUnb delete session; // TODO Can we avoid killing the session here? } -JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeRefresh +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeRefresh (JNIEnv *env, jobject, jlong sessionPointer, jstring accessToken) { TR_ENTER(env) @@ -93,7 +93,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_SyncSession_nativeRef } JNIEXPORT void JNICALL -Java_io_realm_internal_objectserver_SyncSession_nativeNotifyCommitHappened +Java_io_realm_internal_objectserver_ObjectServerSession_nativeNotifyCommitHappened (JNIEnv *env, jobject, jlong sessionPointer, jlong version) { TR_ENTER(env) diff --git a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java b/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java index 7fbed146cc..c769a7d693 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java @@ -27,14 +27,14 @@ public interface AuthenticationListener { /** * A user was logged into the Object Server * - * @param user {@link User} that is now logged in. + * @param user {@link SyncUser} that is now logged in. */ - void loggedIn(User user); + void loggedIn(SyncUser user); /** * A user was successfully logged out from the Object Server. * - * @param user {@link User} that was successfully logged out. + * @param user {@link SyncUser} that was successfully logged out. */ - void loggedOut(User user); + void loggedOut(SyncUser user); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java index bd2f0f467f..56109037f8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java @@ -112,7 +112,7 @@ public Throwable getException() { /** * Returns the {@link ErrorCode.Category} category for this error. * Errors that are {@link ErrorCode.Category#RECOVERABLE} mean that it is still possible for a - * given {@link Session} to resume synchronization. {@link ErrorCode.Category#FATAL} errors + * given {@link SyncSession} to resume synchronization. {@link ErrorCode.Category#FATAL} errors * means that session has stopped and cannot be recovered. * * @return the error category. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index e930e14c27..07583eb63d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -44,8 +44,8 @@ * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm * Object Server. *

      - * A valid {@link User} is required to create a {@link SyncConfiguration}. See {@link Credentials} and - * {@link User#loginAsync(Credentials, String, User.Callback)} for more information on + * A valid {@link SyncUser} is required to create a {@link SyncConfiguration}. See {@link SyncCredentials} and + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} for more information on * how to get a user object. *

      * A minimal {@link SyncConfiguration} can be found below. @@ -83,9 +83,9 @@ public final class SyncConfiguration extends RealmConfiguration { private static final char[] INVALID_CHARS = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'}; private final URI serverUrl; - private final User user; + private final SyncUser user; private final SyncPolicy syncPolicy; - private final Session.ErrorHandler errorHandler; + private final SyncSession.ErrorHandler errorHandler; private final boolean deleteRealmOnLogout; private SyncConfiguration(File directory, @@ -100,10 +100,10 @@ private SyncConfiguration(File directory, RealmProxyMediator schemaMediator, RxObservableFactory rxFactory, Realm.Transaction initialDataTransaction, - User user, + SyncUser user, URI serverUrl, SyncPolicy syncPolicy, - Session.ErrorHandler errorHandler, + SyncSession.ErrorHandler errorHandler, boolean deleteRealmOnLogout ) { super(directory, @@ -192,7 +192,7 @@ SyncPolicy getSyncPolicy() { * * @return the user. */ - public User getUser() { + public SyncUser getUser() { return user; } @@ -206,14 +206,14 @@ public URI getServerUrl() { return serverUrl; } - public Session.ErrorHandler getErrorHandler() { + public SyncSession.ErrorHandler getErrorHandler() { return errorHandler; } /** - * Returns {@code true} if the Realm file must be deleted once the {@link User} owning it logs out. + * Returns {@code true} if the Realm file must be deleted once the {@link SyncUser} owning it logs out. * - * @return {@code true} if the Realm file must be deleted if the {@link User} logs out. {@code false} if the file + * @return {@code true} if the Realm file must be deleted if the {@link SyncUser} logs out. {@code false} if the file * is allowed to remain behind. */ public boolean shouldDeleteRealmOnLogout() { @@ -240,9 +240,9 @@ public static final class Builder { private RxObservableFactory rxFactory; private Realm.Transaction initialDataTransaction; private URI serverUrl; - private User user = null; + private SyncUser user = null; private SyncPolicy syncPolicy = new AutomaticSyncPolicy(); - private Session.ErrorHandler errorHandler = SyncManager.defaultSessionErrorHandler; + private SyncSession.ErrorHandler errorHandler = SyncManager.defaultSessionErrorHandler; private File defaultFolder; private String defaultLocalFileName; private SharedRealm.Durability durability = SharedRealm.Durability.FULL; @@ -270,17 +270,17 @@ public static final class Builder { * If file name and underlying path are too long to handle for FAT32, a shorter unique name will be generated. * See also @{link https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx}. * - * @param user the user for this Realm. An authenticated {@link User} is required to open any Realm managed + * @param user the user for this Realm. An authenticated {@link SyncUser} is required to open any Realm managed * by a Realm Object Server. * @param uri URI identifying the Realm. * - * @see User#isValid() + * @see SyncUser#isValid() */ - public Builder(User user, String uri) { + public Builder(SyncUser user, String uri) { this(BaseRealm.applicationContext, user, uri); } - Builder(Context context, User user, String url) { + Builder(Context context, SyncUser user, String url) { if (context == null) { throw new IllegalStateException("Call `Realm.init(Context)` before creating a SyncConfiguration"); } @@ -293,7 +293,7 @@ public Builder(User user, String uri) { validateAndSet(url); } - private void validateAndSet(User user) { + private void validateAndSet(SyncUser user) { if (user == null) { throw new IllegalArgumentException("Non-null `user` required."); } @@ -488,7 +488,7 @@ public Builder inMemory() { * * @param syncPolicy policy to use. * - * @see Session + * @see SyncSession */ Builder syncPolicy(SyncPolicy syncPolicy) { // Package protected until SyncPolicy API is more stable. @@ -498,14 +498,14 @@ Builder syncPolicy(SyncPolicy syncPolicy) { /** * Sets the error handler used by this configuration. This will override any handler set by calling - * {@link SyncManager#setDefaultSessionErrorHandler(Session.ErrorHandler)}. + * {@link SyncManager#setDefaultSessionErrorHandler(SyncSession.ErrorHandler)}. *

      * Only errors not handled by the defined {@code SyncPolicy} will be reported to this error handler. * * @param errorHandler error handler used to report back errors when communicating with the Realm Object Server. * @throws IllegalArgumentException if {@code null} is given as an error handler. */ - public Builder errorHandler(Session.ErrorHandler errorHandler) { + public Builder errorHandler(SyncSession.ErrorHandler errorHandler) { if (errorHandler == null) { throw new IllegalArgumentException("Non-null 'errorHandler' required."); } @@ -530,8 +530,8 @@ private String MD5(String in) { } /** - * Setting this will cause the local Realm file used to synchronize changes to be deleted if the {@link User} - * owning this Realm logs out from the device using {@link User#logout()}. + * Setting this will cause the local Realm file used to synchronize changes to be deleted if the {@link SyncUser} + * owning this Realm logs out from the device using {@link SyncUser#logout()}. *

      * The default behavior is that the Realm file is allowed to stay behind, making it possible for users to log * in again and have access to their data faster. diff --git a/realm/realm-library/src/objectServer/java/io/realm/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java similarity index 82% rename from realm/realm-library/src/objectServer/java/io/realm/Credentials.java rename to realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 2f03ad6163..ebb8813760 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/Credentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -31,11 +31,11 @@ *

        *
      1. * Log in to 3rd party provider (Facebook, Google or Twitter). The result is usually an Authorization Grant that must be - * saved in a {@link Credentials} object of the proper type e.g., {@link Credentials#facebook(String)} for a + * saved in a {@link SyncCredentials} object of the proper type e.g., {@link SyncCredentials#facebook(String)} for a * Facebook login. *
      2. *
      3. - * Authenticate a {@link User} through the Object Server using these credentials. Once authenticated, + * Authenticate a {@link SyncUser} through the Object Server using these credentials. Once authenticated, * an Object Server user is returned. Then this user can be attached to a {@link SyncConfiguration}, which * will make it possible to synchronize data between the local and remote Realm. *

        @@ -64,7 +64,7 @@ * */ @Beta -public class Credentials { +public class SyncCredentials { private String identityProvider; private String userIdentifier; @@ -82,17 +82,17 @@ public class Credentials { * create a user twice when logging in, so this flag should only be set to {@code true} the first * time a users log in. * @return a set of credentials that can be used to log into the Object Server using - * {@link User#loginAsync(Credentials, String, User.Callback)}. + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ - public static Credentials usernamePassword(String username, String password, boolean createUser) { + public static SyncCredentials usernamePassword(String username, String password, boolean createUser) { if (username == null || username.equals("")) { throw new IllegalArgumentException("Non-null 'username' required."); } Map userInfo = new HashMap(); userInfo.put("register", createUser); userInfo.put("password", password); - return new Credentials(IdentityProvider.USERNAME_PASSWORD, username, userInfo); + return new SyncCredentials(IdentityProvider.USERNAME_PASSWORD, username, userInfo); } /** @@ -100,14 +100,14 @@ public static Credentials usernamePassword(String username, String password, boo * * @param facebookToken a facebook userIdentifier acquired by logging into Facebook. * @return a set of credentials that can be used to log into the Object Server using - * {@link User#loginAsync(Credentials, String, User.Callback)} + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} * @throws IllegalArgumentException if user name is either {@code null} or empty. */ - public static Credentials facebook(String facebookToken) { + public static SyncCredentials facebook(String facebookToken) { if (facebookToken == null || facebookToken.equals("")) { throw new IllegalArgumentException("Non-null 'facebookToken' required."); } - return new Credentials(IdentityProvider.FACEBOOK, facebookToken, null); + return new SyncCredentials(IdentityProvider.FACEBOOK, facebookToken, null); } /** @@ -115,14 +115,14 @@ public static Credentials facebook(String facebookToken) { * * @param googleToken a google userIdentifier acquired by logging into Google. * @return a set of credentials that can be used to log into the Object Server using - * {@link User#loginAsync(Credentials, String, User.Callback)} + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} * @throws IllegalArgumentException if user name is either {@code null} or empty. */ - public static Credentials google(String googleToken) { + public static SyncCredentials google(String googleToken) { if (googleToken == null || googleToken.equals("")) { throw new IllegalArgumentException("Non-null 'googleToken' required."); } - return new Credentials(IdentityProvider.GOOGLE, googleToken, null); + return new SyncCredentials(IdentityProvider.GOOGLE, googleToken, null); } /** @@ -130,14 +130,14 @@ public static Credentials google(String googleToken) { * * @param twitterToken a google userIdentifier acquired by logging into Twitter. * @return a set of credentials that can be used to log into the Object Server using - * {@link User#loginAsync(Credentials, String, User.Callback)} + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} * @throws IllegalArgumentException if user name is either {@code null} or empty. */ - public static Credentials twitter(String twitterToken) { + public static SyncCredentials twitter(String twitterToken) { if (twitterToken == null || twitterToken.equals("")) { throw new IllegalArgumentException("Non-null 'twitterToken' required."); } - return new Credentials(IdentityProvider.TWITTER, twitterToken, null); + return new SyncCredentials(IdentityProvider.TWITTER, twitterToken, null); } /** @@ -150,10 +150,10 @@ public static Credentials twitter(String twitterToken) { * data will be serialized to JSON, so all values must be mappable to a valid JSON data type. Custom * classes will be converted using {@code toString()}. * @return a set of credentials that can be used to log into the Object Server using - * {@link User#loginAsync(Credentials, String, User.Callback)}. + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if any parameter is either {@code null} or empty. */ - public static Credentials custom(String identityProvider, String userIdentifier, Map userInfo) { + public static SyncCredentials custom(String identityProvider, String userIdentifier, Map userInfo) { if (identityProvider == null || identityProvider.equals("")) { throw new IllegalArgumentException("Non-null 'identityProvider' required."); } @@ -163,10 +163,10 @@ public static Credentials custom(String identityProvider, String userIdentifier, if (userInfo == null) { userInfo = new HashMap(); } - return new Credentials(identityProvider, userIdentifier, userInfo); + return new SyncCredentials(identityProvider, userIdentifier, userInfo); } - private Credentials(String identityProvider, String token, Map userInfo) { + private SyncCredentials(String identityProvider, String token, Map userInfo) { this.identityProvider = identityProvider; this.userIdentifier = token; this.userInfo = (userInfo == null) ? new HashMap() : userInfo; @@ -192,7 +192,7 @@ public String getUserIdentifier() { /** * Returns any custom user information associated with this credential. - * The type of information will depend on the type of {@link Credentials.IdentityProvider} + * The type of information will depend on the type of {@link SyncCredentials.IdentityProvider} * used. * * @return a map of additional information about the user. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 3ed441760c..57557ad888 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -26,13 +26,13 @@ import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.OkHttpAuthenticationServer; import io.realm.internal.objectserver.SessionStore; -import io.realm.internal.objectserver.SyncSession; +import io.realm.internal.objectserver.ObjectServerSession; import io.realm.log.RealmLog; /** * @Beta * The SyncManager is the central controller for interacting with the Realm Object Server. - * It handles the creation of {@link Session}s and it is possible to configure session defaults and the underlying + * It handles the creation of {@link SyncSession}s and it is possible to configure session defaults and the underlying * network client using this class. *

        * Through the SyncManager, it is possible to add authentication listeners. An authentication listener will @@ -55,9 +55,9 @@ public final class SyncManager { public static final ThreadPoolExecutor NETWORK_POOL_EXECUTOR = new ThreadPoolExecutor( 10, 10, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(100)); - private static final Session.ErrorHandler SESSION_NO_OP_ERROR_HANDLER = new Session.ErrorHandler() { + private static final SyncSession.ErrorHandler SESSION_NO_OP_ERROR_HANDLER = new SyncSession.ErrorHandler() { @Override - public void onError(Session session, ObjectServerError error) { + public void onError(SyncSession session, ObjectServerError error) { String errorMsg = String.format("Session Error[%s]: %s", session.getConfiguration().getServerUrl(), error.toString()); @@ -81,7 +81,7 @@ public void onError(Session session, ObjectServerError error) { private static volatile AuthenticationServer authServer = new OkHttpAuthenticationServer(); private static volatile UserStore userStore; - static volatile Session.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; + static volatile SyncSession.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; @SuppressWarnings("FieldCanBeLocal") private static Thread clientThread; @@ -107,7 +107,7 @@ public void run() { /** * Set the {@link UserStore} used by the Realm Object Server to save user information. - * If no Userstore is specified {@link User#currentUser()} will always return {@code null}. + * If no Userstore is specified {@link SyncUser#currentUser()} will always return {@code null}. * * @param userStore {@link UserStore} to use. * @throws IllegalArgumentException if {@code userStore} is {@code null}. @@ -150,7 +150,7 @@ public static void removeAuthenticationListener(AuthenticationListener listener) * * @param errorHandler the default error handler used when interacting with a Realm managed by a Realm Object Server. */ - public static void setDefaultSessionErrorHandler(Session.ErrorHandler errorHandler) { + public static void setDefaultSessionErrorHandler(SyncSession.ErrorHandler errorHandler) { if (errorHandler == null) { defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; } else { @@ -159,14 +159,14 @@ public static void setDefaultSessionErrorHandler(Session.ErrorHandler errorHandl } /** - * Gets any cached {@link Session} for the given {@link SyncConfiguration} or create a new one if + * Gets any cached {@link SyncSession} for the given {@link SyncConfiguration} or create a new one if * no one exists. * * @param syncConfiguration configuration object for the synchronized Realm. - * @return the {@link Session} for the specified Realm. + * @return the {@link SyncSession} for the specified Realm. * @throws IllegalArgumentException if syncConfiguration is {@code null}. */ - public static synchronized Session getSession(SyncConfiguration syncConfiguration) { + public static synchronized SyncSession getSession(SyncConfiguration syncConfiguration) { if (syncConfiguration == null) { throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); } @@ -174,14 +174,14 @@ public static synchronized Session getSession(SyncConfiguration syncConfiguratio if (SessionStore.hasSession(syncConfiguration)) { return SessionStore.getPublicSession(syncConfiguration); } else { - SyncSession internalSession = new SyncSession( + ObjectServerSession internalSession = new ObjectServerSession( syncConfiguration, authServer, syncConfiguration.getUser().getSyncUser(), syncConfiguration.getSyncPolicy(), syncConfiguration.getErrorHandler() ); - Session publicSession = new Session(internalSession); + SyncSession publicSession = new SyncSession(internalSession); SessionStore.addSession(publicSession, internalSession); syncConfiguration.getUser().getSyncUser().addSession(publicSession); syncConfiguration.getSyncPolicy().onSessionCreated(internalSession); @@ -211,20 +211,20 @@ static UserStore getUserStore() { @SuppressWarnings("unused") private static void notifyErrorHandler(int errorCode, String errorMessage) { ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); - for (SyncSession session : SessionStore.getAllSessions()) { + for (ObjectServerSession session : SessionStore.getAllSessions()) { session.onError(error); } } // Notify listeners that a user logged in - static void notifyUserLoggedIn(User user) { + static void notifyUserLoggedIn(SyncUser user) { for (AuthenticationListener authListener : authListeners) { authListener.loggedIn(user); } } // Notify listeners that a user logged out successfully - static void notifyUserLoggedOut(User user) { + static void notifyUserLoggedOut(SyncUser user) { for (AuthenticationListener authListener : authListeners) { authListener.loggedOut(user); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/Session.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java similarity index 71% rename from realm/realm-library/src/objectServer/java/io/realm/Session.java rename to realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index bb97fbaf64..64db6280f7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/Session.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -21,7 +21,7 @@ import io.realm.annotations.Beta; import io.realm.internal.Keep; import io.realm.log.RealmLog; -import io.realm.internal.objectserver.SyncSession; +import io.realm.internal.objectserver.ObjectServerSession; /** * @Beta @@ -32,7 +32,7 @@ * is closed or the {@link SyncConfiguration} is no longer used. *

        * A session is fully controlled by Realm, but can provide additional information in case of errors. - * It is passed along in all {@link Session.ErrorHandler}s. + * It is passed along in all {@link SyncSession.ErrorHandler}s. *

        * This object is thread safe. * @@ -40,13 +40,13 @@ */ @Keep @Beta -public final class Session { +public final class SyncSession { - private final SyncSession syncSession; + private final ObjectServerSession osSession; - Session(SyncSession rosSession) { - this.syncSession = rosSession; - rosSession.setUserSession(this); + SyncSession(ObjectServerSession osSession) { + this.osSession = osSession; + osSession.setUserSession(this); } /** @@ -55,17 +55,17 @@ public final class Session { * @return SyncConfiguration that defines and controls this session. */ public SyncConfiguration getConfiguration() { - return syncSession.getConfiguration(); + return osSession.getConfiguration(); } /** - * Returns the {@link User} defined by the {@link SyncConfiguration} that is used to connect to the + * Returns the {@link SyncUser} defined by the {@link SyncConfiguration} that is used to connect to the * Realm Object Server. * - * @return {@link User} used to authenticate the session on the Realm Object Server. + * @return {@link SyncUser} used to authenticate the session on the Realm Object Server. */ - public User getUser() { - return syncSession.getConfiguration().getUser(); + public SyncUser getUser() { + return osSession.getConfiguration().getUser(); } /** @@ -74,7 +74,7 @@ public User getUser() { * @return {@link URI} describing the remote Realm. */ public URI getServerUrl() { - return syncSession.getConfiguration().getServerUrl(); + return osSession.getConfiguration().getServerUrl(); } /** @@ -83,19 +83,19 @@ public URI getServerUrl() { * @return the current {@link SessionState} for this session. */ public SessionState getState() { - return syncSession.getState(); + return osSession.getState(); } - SyncSession getSyncSession() { - return syncSession; + ObjectServerSession getOsSession() { + return osSession; } @Override protected void finalize() throws Throwable { super.finalize(); - if (syncSession.getState() != SessionState.STOPPED) { + if (osSession.getState() != SessionState.STOPPED) { RealmLog.warn("Session was not closed before being finalized. This is a potential resource leak."); - syncSession.stop(); + osSession.stop(); } } @@ -109,10 +109,10 @@ public interface ErrorHandler { /** * Callback for errors on a session object. * - * @param session {@link Session} this error happened on. + * @param session {@link SyncSession} this error happened on. * @param error type of error. */ - void onError(Session session, ObjectServerError error); + void onError(SyncSession session, ObjectServerError error); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/User.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java similarity index 84% rename from realm/realm-library/src/objectServer/java/io/realm/User.java rename to realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 1798b42558..de6641360d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -41,7 +41,7 @@ import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.LogoutResponse; -import io.realm.internal.objectserver.SyncUser; +import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; @@ -58,11 +58,11 @@ * as sensitive data. */ @Beta -public class User { +public class SyncUser { - private final SyncUser syncUser; + private final ObjectServerUser syncUser; - private User(SyncUser user) { + private SyncUser(ObjectServerUser user) { this.syncUser = user; } @@ -70,11 +70,11 @@ private User(SyncUser user) { * Returns the last user that has logged in and who is still valid. * A user is invalidated when he/she logs out or the user's access token expire. * - * @return last {@link User} that has logged in and who is still valid. {@code null} if no current user or user has + * @return last {@link SyncUser} that has logged in and who is still valid. {@code null} if no current user or user has * been invalidated. */ - public static User currentUser() { - User user = SyncManager.getUserStore().get(UserStore.CURRENT_USER_KEY); + public static SyncUser currentUser() { + SyncUser user = SyncManager.getUserStore().get(UserStore.CURRENT_USER_KEY); if (user != null && user.isValid()) { return user; } @@ -87,11 +87,11 @@ public static User currentUser() { * * @return a list of all known valid users. */ - public static Collection all() { + public static Collection all() { UserStore userStore = SyncManager.getUserStore(); - Collection storedUsers = userStore.allUsers(); - List result = new ArrayList(storedUsers.size()); - for (User user : storedUsers) { + Collection storedUsers = userStore.allUsers(); + List result = new ArrayList(storedUsers.size()); + for (SyncUser user : storedUsers) { if (user.isValid()) { result.add(user); } @@ -105,22 +105,22 @@ public static Collection all() { * @param user JSON string representing the user. * * @return the user object. - * @throws IllegalArgumentException if the JSON couldn't be converted to a valid {@link User} object. + * @throws IllegalArgumentException if the JSON couldn't be converted to a valid {@link SyncUser} object. */ - public static User fromJson(String user) { + public static SyncUser fromJson(String user) { try { JSONObject obj = new JSONObject(user); URL authUrl = new URL(obj.getString("authUrl")); Token userToken = Token.from(obj.getJSONObject("userToken")); - SyncUser syncUser = new SyncUser(userToken, authUrl); + ObjectServerUser syncUser = new ObjectServerUser(userToken, authUrl); JSONArray realmTokens = obj.getJSONArray("realms"); for (int i = 0; i < realmTokens.length(); i++) { JSONObject token = realmTokens.getJSONObject(i); URI uri = new URI(token.getString("uri")); - SyncUser.AccessDescription realmDesc = SyncUser.AccessDescription.fromJson(token.getJSONObject("description")); + ObjectServerUser.AccessDescription realmDesc = ObjectServerUser.AccessDescription.fromJson(token.getJSONObject("description")); syncUser.addRealm(uri, realmDesc); } - return new User(syncUser); + return new SyncUser(syncUser); } catch (JSONException e) { throw new IllegalArgumentException("Could not parse user json: " + user, e); } catch (MalformedURLException e) { @@ -139,7 +139,7 @@ public static User fromJson(String user) { * @throws ObjectServerError if the login failed. * @throws IllegalArgumentException if the URL is malformed. */ - public static User login(final Credentials credentials, final String authenticationUrl) throws ObjectServerError { + public static SyncUser login(final SyncCredentials credentials, final String authenticationUrl) throws ObjectServerError { final URL authUrl; try { authUrl = new URL(authenticationUrl); @@ -152,8 +152,8 @@ public static User login(final Credentials credentials, final String authenticat try { AuthenticateResponse result = server.loginUser(credentials, authUrl); if (result.isValid()) { - SyncUser syncUser = new SyncUser(result.getRefreshToken(), authUrl); - User user = new User(syncUser); + ObjectServerUser syncUser = new ObjectServerUser(result.getRefreshToken(), authUrl); + SyncUser user = new SyncUser(syncUser); RealmLog.info("Succeeded authenticating user.\n%s", user); SyncManager.getUserStore().put(UserStore.CURRENT_USER_KEY, user); SyncManager.notifyUserLoggedIn(user); @@ -178,7 +178,7 @@ public static User login(final Credentials credentials, final String authenticat * as this this method is called on. * @throws IllegalArgumentException if not on a Looper thread. */ - public static RealmAsyncTask loginAsync(final Credentials credentials, final String authenticationUrl, final Callback callback) { + public static RealmAsyncTask loginAsync(final SyncCredentials credentials, final String authenticationUrl, final Callback callback) { if (Looper.myLooper() == null) { throw new IllegalStateException("Asynchronous login is only possible from looper threads."); } @@ -188,7 +188,7 @@ public static RealmAsyncTask loginAsync(final Credentials credentials, final Str @Override public void run() { try { - User user = login(credentials, authenticationUrl); + SyncUser user = login(credentials, authenticationUrl); postSuccess(user); } catch (ObjectServerError e) { postError(e); @@ -206,7 +206,7 @@ public void run() { } } - private void postSuccess(final User user) { + private void postSuccess(final SyncUser user) { if (callback != null) { handler.post(new Runnable() { @Override @@ -242,8 +242,8 @@ public void logout() { // Ensure that we can log out. If any Realm file is still open we should abort before doing anything // else. - Collection sessions = syncUser.getSessions(); - for (Session session : sessions) { + Collection sessions = syncUser.getSessions(); + for (SyncSession session : sessions) { SyncConfiguration config = session.getConfiguration(); if (Realm.getGlobalInstanceCount(config) > 0) { throw new IllegalStateException("A Realm controlled by this user is still open. Close all Realms " + @@ -254,8 +254,8 @@ public void logout() { // Stop all active sessions immediately. If we waited until after talking to the server // there is a high chance errors would be reported from the Sync Client first which would // be confusing. - for (Session session : sessions) { - session.getSyncSession().stop(); + for (SyncSession session : sessions) { + session.getOsSession().stop(); } final AuthenticationServer server = SyncManager.getAuthServer(); @@ -264,7 +264,7 @@ public void logout() { @Override protected LogoutResponse execute() { - return server.logout(User.this, syncUser.getAuthenticationUrl()); + return server.logout(SyncUser.this, syncUser.getAuthenticationUrl()); } @Override @@ -272,12 +272,12 @@ protected void onSuccess(LogoutResponse response) { // Remove all local tokens, preventing further connections. syncUser.clearTokens(); - if (User.this.equals(User.currentUser())) { + if (SyncUser.this.equals(SyncUser.currentUser())) { SyncManager.getUserStore().remove(UserStore.CURRENT_USER_KEY); } // Delete all Realms if needed. - for (SyncUser.AccessDescription desc : syncUser.getRealms()) { + for (ObjectServerUser.AccessDescription desc : syncUser.getRealms()) { // FIXME: This will always be false since SyncConfiguration.Builder.deleteRealmOnLogout() is // disabled. Make sure this works for Realm opened in the client thread/other processes. if (desc.deleteOnLogout) { @@ -288,7 +288,7 @@ protected void onSuccess(LogoutResponse response) { } } - SyncManager.notifyUserLoggedOut(User.this); + SyncManager.notifyUserLoggedOut(SyncUser.this); } @Override @@ -320,8 +320,8 @@ public String toJson() { *

        * The user might still be have been logged out by the Realm Object Server which will not be detected before the * user tries to actively synchronize a Realm. If a logged out user tries to synchronize a Realm, an error will be - * reported to the {@link Session.ErrorHandler} defined by - * {@link SyncConfiguration.Builder#errorHandler(Session.ErrorHandler)}. + * reported to the {@link SyncSession.ErrorHandler} defined by + * {@link SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler)}. * * @return {@code true} if the User is logged into the Realm Object Server, {@code false} otherwise. */ @@ -357,7 +357,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - User user = (User) o; + SyncUser user = (SyncUser) o; return syncUser.equals(user.syncUser); @@ -369,12 +369,12 @@ public int hashCode() { } // Expose internal representation for other package protected classes - SyncUser getSyncUser() { + ObjectServerUser getSyncUser() { return syncUser; } public interface Callback { - void onSuccess(User user); + void onSuccess(SyncUser user); void onError(ObjectServerError error); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java index 4ec9f355ff..528cae598b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java @@ -37,36 +37,36 @@ public interface UserStore { String CURRENT_USER_KEY = "realm$currentUser"; /** - * Saves a {@link User} object under the given key. If another user already exists, it will be replaced. + * Saves a {@link SyncUser} object under the given key. If another user already exists, it will be replaced. * * @param key key used to store the User. - * @param user {@link User} object to store. + * @param user {@link SyncUser} object to store. * @return The previous user saved with this key or {@code null} if no user was replaced. * */ - User put(String key, User user); + SyncUser put(String key, SyncUser user); /** - * Retrieves the {@link User} with the given key. + * Retrieves the {@link SyncUser} with the given key. * - * @param key {@link User} saved under the given key or {@code null} if no user exists for that key. + * @param key {@link SyncUser} saved under the given key or {@code null} if no user exists for that key. */ - User get(String key); + SyncUser get(String key); /** * Removes the user with the given key from the store. * * @param key key for the user to remove. - * @return {@link User} that was removed or {@code null} if no user matched the key. + * @return {@link SyncUser} that was removed or {@code null} if no user matched the key. */ - User remove(String key); + SyncUser remove(String key); /** * Returns a collection of all users saved in the User store. * * @return Collection of all users. If no users exist, an empty collection is returned. */ - Collection allUsers(); + Collection allUsers(); /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java index 5a73075aeb..44ba36f04d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java @@ -25,12 +25,12 @@ import java.util.Map; import java.util.Set; -import io.realm.User; +import io.realm.SyncUser; import io.realm.UserStore; import io.realm.internal.android.crypto.CipherClient; /** - * Encrypt and decrypt the token ({@link User}) using Android built in KeyStore capabilities. + * Encrypt and decrypt the token ({@link SyncUser}) using Android built in KeyStore capabilities. * According to the Android API this picks the right algorithm to perfom the operations. * Prior to API 18 there were no AndroidKeyStore API, but the UNIX deamon existed to it's possible * with the help of this code: https://github.com/nelenkov/android-keystore. @@ -47,7 +47,7 @@ public class SecureUserStore implements UserStore { private static final String REALM_OBJECT_SERVER_USERS = "realm_object_server_users"; private final CipherClient cipherClient; private final SharedPreferences sp; - private User cachedCurrentUser; // Keep a quick reference to the current user + private SyncUser cachedCurrentUser; // Keep a quick reference to the current user public SecureUserStore(final Context context) throws KeyStoreException { cipherClient = new CipherClient(context); @@ -61,7 +61,7 @@ public SecureUserStore(final Context context) throws KeyStoreException { * @return The previous user saved with this key or {@code null} if no user was replaced. */ @Override - public User put(String key, User user) { + public SyncUser put(String key, SyncUser user) { String previousUser = sp.getString(key, null); SharedPreferences.Editor editor = sp.edit(); String userSerialisedAndEncrypted; @@ -81,7 +81,7 @@ public User put(String key, User user) { if (previousUser != null) { try { String userSerialisedAndDecrypted = cipherClient.decrypt(previousUser); - return User.fromJson(userSerialisedAndDecrypted); + return SyncUser.fromJson(userSerialisedAndDecrypted); } catch (KeyStoreException e) { e.printStackTrace(); return null; @@ -92,12 +92,12 @@ public User put(String key, User user) { } /** - * Retrieves the {@link User} by decrypting first the serialised Json. + * Retrieves the {@link SyncUser} by decrypting first the serialised Json. * @param key the {@link android.content.SharedPreferences} key. - * @return the {@link User} with the given key. + * @return the {@link SyncUser} with the given key. */ @Override - public User get(String key) { + public SyncUser get(String key) { if (key.equals(UserStore.CURRENT_USER_KEY) && cachedCurrentUser != null) { return cachedCurrentUser; } @@ -109,7 +109,7 @@ public User get(String key) { try { String userSerialisedAndDecrypted = cipherClient.decrypt(userData); - User user = User.fromJson(userSerialisedAndDecrypted); + SyncUser user = SyncUser.fromJson(userSerialisedAndDecrypted); if (UserStore.CURRENT_USER_KEY.equals(key)) { cachedCurrentUser = user; } @@ -121,7 +121,7 @@ public User get(String key) { } @Override - public User remove(String key) { + public SyncUser remove(String key) { String currentUser = sp.getString(key, null); SharedPreferences.Editor editor = sp.edit(); editor.putString(key, null); @@ -134,7 +134,7 @@ public User remove(String key) { if (currentUser != null) { try { String userSerialisedAndDecrypted = cipherClient.decrypt(currentUser); - return User.fromJson(userSerialisedAndDecrypted); + return SyncUser.fromJson(userSerialisedAndDecrypted); } catch (KeyStoreException e) { e.printStackTrace(); return null; @@ -145,9 +145,9 @@ public User remove(String key) { } @Override - public Collection allUsers() { + public Collection allUsers() { Map all = sp.getAll(); - ArrayList users = new ArrayList(all.size()); + ArrayList users = new ArrayList(all.size()); for (Object userJson : all.values()) { String userSerialisedAndDecrypted = null; try { @@ -156,7 +156,7 @@ public Collection allUsers() { e.printStackTrace(); // returning null will probably penalise the other Users } - users.add(User.fromJson(userSerialisedAndDecrypted)); + users.add(SyncUser.fromJson(userSerialisedAndDecrypted)); } return users; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java index 4773cb682d..31d4b2b1b5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java @@ -24,7 +24,7 @@ import java.util.Map; import java.util.Set; -import io.realm.User; +import io.realm.SyncUser; import io.realm.UserStore; /** @@ -33,7 +33,7 @@ public class SharedPrefsUserStore implements UserStore { private final SharedPreferences sp; - private User cachedCurrentUser; // Keep a quick reference to the current user + private SyncUser cachedCurrentUser; // Keep a quick reference to the current user public SharedPrefsUserStore(Context context) { sp = context.getSharedPreferences("realm_object_server_users", Context.MODE_PRIVATE); @@ -43,7 +43,7 @@ public SharedPrefsUserStore(Context context) { * {@inheritDoc} */ @Override - public User put(String key, User user) { + public SyncUser put(String key, SyncUser user) { String previousUser = sp.getString(key, null); SharedPreferences.Editor editor = sp.edit(); editor.putString(key, user.toJson()); @@ -55,7 +55,7 @@ public User put(String key, User user) { } if (previousUser != null) { - return User.fromJson(previousUser); + return SyncUser.fromJson(previousUser); } else { return null; } @@ -65,7 +65,7 @@ public User put(String key, User user) { * {@inheritDoc} */ @Override - public User get(String key) { + public SyncUser get(String key) { if (UserStore.CURRENT_USER_KEY.equals(key) && cachedCurrentUser != null) { return cachedCurrentUser; } @@ -75,7 +75,7 @@ public User get(String key) { return null; } - User user = User.fromJson(userData); + SyncUser user = SyncUser.fromJson(userData); if (UserStore.CURRENT_USER_KEY.equals(key)) { cachedCurrentUser = user; } @@ -86,7 +86,7 @@ public User get(String key) { * {@inheritDoc} */ @Override - public User remove(String key) { + public SyncUser remove(String key) { String currentUser = sp.getString(key, null); SharedPreferences.Editor editor = sp.edit(); editor.putString(key, null); @@ -97,7 +97,7 @@ public User remove(String key) { } if (currentUser != null) { - return User.fromJson(currentUser); + return SyncUser.fromJson(currentUser); } else { return null; } @@ -107,11 +107,11 @@ public User remove(String key) { * {@inheritDoc} */ @Override - public Collection allUsers() { + public Collection allUsers() { Map all = sp.getAll(); - ArrayList users = new ArrayList(all.size()); + ArrayList users = new ArrayList(all.size()); for (Object userJson : all.values()) { - users.add(User.fromJson((String) userJson)); + users.add(SyncUser.fromJson((String) userJson)); } return users; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java index 9253072b2d..c0f63adfb6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java @@ -20,6 +20,8 @@ import java.security.KeyStoreException; +import io.realm.SyncUser; + /** * A Helper to use the crypto API, it allows encryption/decryption and has methods to help test if the KeyStore is locked and help unlocked it. * This hides the complexity of different Android API to achieve those operations. @@ -41,7 +43,7 @@ public CipherClient(Context context) throws KeyStoreException { * Takes some plain text {@link String} and return the encrypted version * of this {@link String} using the Android Key Store. * - * @param user represents the Token of a {@link io.realm.User}. + * @param user represents the Token of a {@link SyncUser}. * @return the encrypted Token. * @throws KeyStoreException in case the Key Store is locked or other error. */ @@ -63,7 +65,7 @@ public String encrypt(String user) throws KeyStoreException { * Takes a previously {@link #encrypt(String)} to decrypted it * using the Android Key Store. * - * @param user_encrypted represents the encrypted Token of a {@link io.realm.User}. + * @param user_encrypted represents the encrypted Token of a {@link SyncUser}. * @return the decrypted Token. * @throws KeyStoreException in case the KeyStore is locked or other error. */ diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java index cd2a3f015d..6b27dfc7d7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java @@ -24,7 +24,7 @@ import java.util.Map; import io.realm.internal.objectserver.Token; -import io.realm.Credentials; +import io.realm.SyncCredentials; import io.realm.SyncManager; /** @@ -42,7 +42,7 @@ public class AuthenticateRequest { /** * Generates a proper login request for a new user. */ - public static AuthenticateRequest userLogin(Credentials credentials) { + public static AuthenticateRequest userLogin(SyncCredentials credentials) { if (credentials == null) { throw new IllegalArgumentException("Non-null credentials required."); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java index 07e21dbe59..5cde174353 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java @@ -19,8 +19,8 @@ import java.net.URI; import java.net.URL; -import io.realm.Credentials; -import io.realm.User; +import io.realm.SyncCredentials; +import io.realm.SyncUser; import io.realm.internal.objectserver.Token; /** @@ -34,12 +34,12 @@ public interface AuthenticationServer { * Login a User on the Object Server. This will create a "UserToken" (Currently called RefreshToken) that acts as * the users credentials. */ - AuthenticateResponse loginUser(Credentials credentials, URL authenticationUrl); + AuthenticateResponse loginUser(SyncCredentials credentials, URL authenticationUrl); /** * Requests access to a specific Realm. Only users with a valid user token can ask for permission to a remote Realm. * Permission to a Realm is granted through an "AccessToken". Each Realm have their own access token, and all - * tokens should be managed by {@link User}. + * tokens should be managed by {@link SyncUser}. */ AuthenticateResponse loginToRealm(Token userToken, URI serverUrl, URL authenticationUrl); @@ -55,5 +55,5 @@ public interface AuthenticationServer { * own refresh token, but if the refresh token for some reason was shared or stolen all these devices will be * logged out as well. */ - LogoutResponse logout(User user, URL authenticationUrl); + LogoutResponse logout(SyncUser user, URL authenticationUrl); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java index 62cb9b095a..c7706c27e5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java @@ -16,7 +16,7 @@ package io.realm.internal.network; -import io.realm.User; +import io.realm.SyncUser; /** * This class encapsulates a request to log out a user on the Realm Authentication Server. It is responsible for @@ -25,7 +25,7 @@ public class LogoutRequest { // TODO Endpoint not finished yet - LogoutRequest fromUser(User user) { + LogoutRequest fromUser(SyncUser user) { return new LogoutRequest(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 2cac097162..c84d408a12 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -20,10 +20,10 @@ import java.net.URL; import java.util.concurrent.TimeUnit; -import io.realm.Credentials; +import io.realm.SyncCredentials; import io.realm.ErrorCode; import io.realm.ObjectServerError; -import io.realm.User; +import io.realm.SyncUser; import io.realm.internal.objectserver.Token; import okhttp3.Call; import okhttp3.MediaType; @@ -46,7 +46,7 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { * Authenticate the given credentials on the specified Realm Authentication Server. */ @Override - public AuthenticateResponse loginUser(Credentials credentials, URL authenticationUrl) { + public AuthenticateResponse loginUser(SyncCredentials credentials, URL authenticationUrl) { try { String requestBody = AuthenticateRequest.userLogin(credentials).toJson(); return authenticate(authenticationUrl, requestBody); @@ -76,7 +76,7 @@ public AuthenticateResponse refreshUser(Token userToken, URL authenticationUrl) } @Override - public LogoutResponse logout(User user, URL authenticationUrl) { + public LogoutResponse logout(SyncUser user, URL authenticationUrl) { throw new UnsupportedOperationException("Not yet implemented"); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java index efbb49c224..9ca21ecf9e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java @@ -17,7 +17,7 @@ package io.realm.internal.objectserver; import io.realm.ObjectServerError; -import io.realm.Session; +import io.realm.SyncSession; import io.realm.SessionState; import io.realm.internal.network.NetworkStateReceiver; import io.realm.log.RealmLog; @@ -97,16 +97,16 @@ public void onStop() { gotoNextState(SessionState.STOPPED); } - private synchronized void authenticate(final SyncSession session) { + private synchronized void authenticate(final ObjectServerSession session) { session.authenticateRealm(new Runnable() { @Override public void run() { RealmLog.debug("Session[%s]: Access token acquired", session.getConfiguration().getPath()); gotoNextState(SessionState.BINDING); } - }, new Session.ErrorHandler() { + }, new SyncSession.ErrorHandler() { @Override - public void onError(Session s, ObjectServerError error) { + public void onError(SyncSession s, ObjectServerError error) { RealmLog.debug("Session[%s]: Failed to get access token (%d)", session.getConfiguration().getPath(), error.getErrorCode()); session.onError(error); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java index bf6c001d12..a4af906d3b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java @@ -17,10 +17,10 @@ package io.realm.internal.objectserver; import io.realm.ObjectServerError; -import io.realm.Session; +import io.realm.SyncSession; /** - * As {@link Session} is modeled as a state machine, this interface describe all + * As {@link SyncSession} is modeled as a state machine, this interface describe all * possible actions in that machine. *

        * All states should implement this interface so all possible permutations of state/actions are covered. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java index 726ede49d0..ee4089a848 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java @@ -16,26 +16,26 @@ package io.realm.internal.objectserver; -import io.realm.Session; +import io.realm.SyncSession; import io.realm.ObjectServerError; import io.realm.SessionState; /** - * Abstract class containing shared logic for all {@link Session} states. All states must extend + * Abstract class containing shared logic for all {@link SyncSession} states. All states must extend * this class as it contains the logic for entering and leaving states. */ abstract class FsmState implements FsmAction { - volatile SyncSession session; // This is non-null when this state is active. + volatile ObjectServerSession session; // This is non-null when this state is active. private boolean exiting; // TODO: Remind me again what race condition necessitated this. /** * Entry into the state. This method is also responsible for executing any asynchronous work * this state might run. * - * This should only be called from {@link Session}. + * This should only be called from {@link SyncSession}. */ - public void entry(SyncSession session) { + public void entry(ObjectServerSession session) { this.session = session; this.exiting = false; onEnterState(); @@ -43,9 +43,9 @@ public void entry(SyncSession session) { /** * Called just before leaving the state. Once this method is called no more state changes can be triggered from - * this state until {@link #entry(SyncSession)} has been called again. + * this state until {@link #entry(ObjectServerSession)} has been called again. *

        - * This should only be called from {@link Session}. + * This should only be called from {@link SyncSession}. */ public void exit() { exiting = true; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java similarity index 90% rename from realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java index 5c8be79d41..0453391016 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java @@ -23,11 +23,11 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.RealmAsyncTask; -import io.realm.Session; +import io.realm.SyncSession; import io.realm.SessionState; import io.realm.SyncConfiguration; import io.realm.SyncManager; -import io.realm.User; +import io.realm.SyncUser; import io.realm.internal.KeepMember; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.network.AuthenticateResponse; @@ -39,7 +39,7 @@ /** * Internal class describing a Realm Object Server Session. - * There is currently a split between the public {@link Session} and this class. + * There is currently a split between the public {@link SyncSession} and this class. * This class is intended as a wrapper for Object Store's Sync Session, but it is not that yet. *

        * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening @@ -87,16 +87,16 @@ * * This object is thread safe. */ -public final class SyncSession { +public final class ObjectServerSession { private final HashMap FSM = new HashMap(); // Variables used by the FSM final SyncConfiguration configuration; private final AuthenticationServer authServer; - private final Session.ErrorHandler errorHandler; + private final SyncSession.ErrorHandler errorHandler; private long nativeSessionPointer; - private final SyncUser user; + private final ObjectServerUser user; RealmAsyncTask networkRequest; NetworkStateReceiver.ConnectionListener networkListener; private SyncPolicy syncPolicy; @@ -104,8 +104,8 @@ public final class SyncSession { // Keeping track of current FSM state private SessionState currentStateDescription; private FsmState currentState; - private Session userSession; - private Session publicSession; + private SyncSession userSession; + private SyncSession publicSession; /** * Creates a new Object Server Session. @@ -114,11 +114,11 @@ public final class SyncSession { * @param authServer Authentication server used to refresh credentials if needed * @param policy Sync Policy to use by this Session. */ - public SyncSession(SyncConfiguration syncConfiguration, - AuthenticationServer authServer, - SyncUser user, - SyncPolicy policy, - Session.ErrorHandler errorHandler) { + public ObjectServerSession(SyncConfiguration syncConfiguration, + AuthenticationServer authServer, + ObjectServerUser user, + SyncPolicy policy, + SyncSession.ErrorHandler errorHandler) { this.configuration = syncConfiguration; this.user = user; this.authServer = authServer; @@ -175,8 +175,8 @@ public synchronized void stop() { * While this method will return immediately, binding a Realm is not guaranteed to succeed. Possible reasons for * failure could be if the device is offline or credentials have expired. Binding is an asynchronous * operation and all errors will be sent first to {@code SyncPolicy#onError(Session, ObjectServerError)} and if the - * SyncPolicy doesn't handle it, to the {@link Session.ErrorHandler} defined by - * {@link SyncConfiguration.Builder#errorHandler(Session.ErrorHandler)}. + * SyncPolicy doesn't handle it, to the {@link SyncSession.ErrorHandler} defined by + * {@link SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler)}. */ public synchronized void bind() { currentState.onBind(); @@ -251,7 +251,7 @@ void bindWithTokens() { } // Authenticate by getting access tokens for the specific Realm - void authenticateRealm(final Runnable onSuccess, final Session.ErrorHandler errorHandler) { + void authenticateRealm(final Runnable onSuccess, final SyncSession.ErrorHandler errorHandler) { if (networkRequest != null) { networkRequest.cancel(); } @@ -268,7 +268,7 @@ protected AuthenticateResponse execute() { @Override protected void onSuccess(AuthenticateResponse response) { - SyncUser.AccessDescription desc = new SyncUser.AccessDescription( + ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription( response.getAccessToken(), configuration.getPath(), configuration.shouldDeleteRealmOnLogout() @@ -305,12 +305,12 @@ public SyncConfiguration getConfiguration() { } /** - * Returns the {@link User} defined by the {@link SyncConfiguration} that is used to connect to the + * Returns the {@link SyncUser} defined by the {@link SyncConfiguration} that is used to connect to the * Realm Object Server. * - * @return {@link User} used to authenticate the session on the Realm Object Server. + * @return {@link SyncUser} used to authenticate the session on the Realm Object Server. */ - public User getUser() { + public SyncUser getUser() { return configuration.getUser(); } @@ -347,11 +347,11 @@ public SyncPolicy getSyncPolicy() { return syncPolicy; } - public Session getUserSession() { + public SyncSession getUserSession() { return userSession; } - public void setUserSession(Session userSession) { + public void setUserSession(SyncSession userSession) { this.userSession = userSession; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java similarity index 94% rename from realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java index 83e6185b55..f74c9dd0a9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java @@ -27,30 +27,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; -import io.realm.RealmAsyncTask; -import io.realm.Session; +import io.realm.SyncSession; import io.realm.SyncConfiguration; -import io.realm.User; /** * Internal representation of a user on the Realm Object Server. - * The public API is defined by {@link User}. + * The public API is defined by {@link io.realm.SyncUser}. */ -public class SyncUser { +public class ObjectServerUser { private final String identity; private Token refreshToken; private URL authenticationUrl; private Map realms = new HashMap(); - private List sessions = new ArrayList(); + private List sessions = new ArrayList(); private boolean loggedIn; /** * Create a new Realm Object Server User */ - public SyncUser(Token refreshToken, URL authenticationUrl) { + public ObjectServerUser(Token refreshToken, URL authenticationUrl) { this.identity = refreshToken.identity(); this.authenticationUrl = authenticationUrl; setRefreshToken(refreshToken); @@ -105,7 +102,7 @@ public void addRealm(URI uri, AccessDescription description) { } // When a session is started, add it to the user so it can be tracked - public void addSession(Session session) { + public void addSession(SyncSession session) { sessions.add(session); } @@ -140,7 +137,7 @@ public Token getUserToken() { return refreshToken; } - public List getSessions() { + public List getSessions() { return sessions; } @@ -164,7 +161,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - SyncUser syncUser = (SyncUser) o; + ObjectServerUser syncUser = (ObjectServerUser) o; if (!identity.equals(syncUser.identity)) return false; if (!refreshToken.equals(syncUser.refreshToken)) return false; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java index 6bbffe5ae8..a40fe01fd1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java @@ -21,29 +21,29 @@ import java.util.Iterator; import java.util.Map; -import io.realm.Session; +import io.realm.SyncSession; import io.realm.SyncManager; import io.realm.SyncConfiguration; /** * Private class for keeping track of sessions. - * If {@link Session} and {@link SyncSession} are combined at some point, this class can + * If {@link SyncSession} and {@link ObjectServerSession} are combined at some point, this class can * be folded into {@link SyncManager}; */ public class SessionStore { // Map of between a local Realm path and any associated sessionInfo - private static HashMap sessions = new HashMap(); - private static HashMap privateSessions = new HashMap(); + private static HashMap sessions = new HashMap(); + private static HashMap privateSessions = new HashMap(); - static synchronized void removeSession(Session session) { + static synchronized void removeSession(SyncSession session) { if (session == null) { return; } - Iterator> it = sessions.entrySet().iterator(); + Iterator> it = sessions.entrySet().iterator(); while (it.hasNext()) { - Map.Entry entry = it.next(); + Map.Entry entry = it.next(); if (entry.getValue().equals(session)) { it.remove(); break; @@ -51,7 +51,7 @@ static synchronized void removeSession(Session session) { } } - public static synchronized void addSession(Session publicSession, SyncSession internalSession) { + public static synchronized void addSession(SyncSession publicSession, ObjectServerSession internalSession) { String localPath = publicSession.getConfiguration().getPath(); sessions.put(localPath, publicSession); privateSessions.put(localPath, internalSession); @@ -62,17 +62,17 @@ public static synchronized boolean hasSession(SyncConfiguration config) { return sessions.containsKey(localPath); } - public static synchronized Session getPublicSession(SyncConfiguration config) { + public static synchronized SyncSession getPublicSession(SyncConfiguration config) { String localPath = config.getPath(); return sessions.get(localPath); } - public static synchronized SyncSession getPrivateSession(Session session) { + public static synchronized ObjectServerSession getPrivateSession(SyncSession session) { String localPath = session.getConfiguration().getPath(); return privateSessions.get(localPath); } - public static Collection getAllSessions() { + public static Collection getAllSessions() { return privateSessions.values(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java index 2928c0e9c3..f1b58008b0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java @@ -17,10 +17,10 @@ package io.realm.internal.objectserver; import io.realm.ObjectServerError; -import io.realm.Session; +import io.realm.SyncSession; /** - * STOPPED State. This is the final state for a {@link Session}. After this, all actions will throw an + * STOPPED State. This is the final state for a {@link SyncSession}. After this, all actions will throw an * {@link IllegalStateException}. */ class StoppedState extends FsmState { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java index 141fc35fe0..63e18261d4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java @@ -25,7 +25,7 @@ import java.lang.reflect.Method; import io.realm.RealmConfiguration; -import io.realm.Session; +import io.realm.SyncSession; import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.exceptions.RealmException; @@ -73,8 +73,8 @@ public void init(Context context) { @Override public void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { if (configuration instanceof SyncConfiguration) { - Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); - SyncSession session = SessionStore.getPrivateSession(publicSession); + SyncSession publicSession = SyncManager.getSession((SyncConfiguration) configuration); + ObjectServerSession session = SessionStore.getPrivateSession(publicSession); session.notifyCommit(lastSnapshotVersion); } else { throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); @@ -84,8 +84,8 @@ public void notifyCommit(RealmConfiguration configuration, long lastSnapshotVers @Override public void realmClosed(RealmConfiguration configuration) { if (configuration instanceof SyncConfiguration) { - Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); - SyncSession session = SessionStore.getPrivateSession(publicSession); + SyncSession publicSession = SyncManager.getSession((SyncConfiguration) configuration); + ObjectServerSession session = SessionStore.getPrivateSession(publicSession); session.getSyncPolicy().onRealmClosed(session); } else { throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); @@ -95,8 +95,8 @@ public void realmClosed(RealmConfiguration configuration) { @Override public void realmOpened(RealmConfiguration configuration) { if (configuration instanceof SyncConfiguration) { - Session publicSession = SyncManager.getSession((SyncConfiguration) configuration); - SyncSession session = SessionStore.getPrivateSession(publicSession); + SyncSession publicSession = SyncManager.getSession((SyncConfiguration) configuration); + ObjectServerSession session = SessionStore.getPrivateSession(publicSession); session.getSyncPolicy().onRealmOpened(session); } else { throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java index 4fd66809c5..fc01484223 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java @@ -17,7 +17,7 @@ package io.realm.internal.syncpolicy; import io.realm.ObjectServerError; -import io.realm.internal.objectserver.SyncSession; +import io.realm.internal.objectserver.ObjectServerSession; /** * This SyncPolicy will automatically start synchronizing changes to a Realm as soon as it is opened. @@ -28,28 +28,28 @@ public class AutomaticSyncPolicy implements SyncPolicy { private int recurringErrors = 0; @Override - public void onRealmOpened(SyncSession session) { + public void onRealmOpened(ObjectServerSession session) { session.bind(); // Bind Realm first time it is opened. } @Override - public void onRealmClosed(SyncSession session) { + public void onRealmClosed(ObjectServerSession session) { // TODO In order to preserve resources we should ideally close the session as well, but first // we want to make sure that all local changes have been synchronized to the remote Realm. } @Override - public void onSessionCreated(SyncSession session) { + public void onSessionCreated(ObjectServerSession session) { session.start(); } @Override - public void onSessionStopped(SyncSession session) { + public void onSessionStopped(ObjectServerSession session) { // Do nothing } @Override - public boolean onError(SyncSession session, ObjectServerError error) { + public boolean onError(ObjectServerSession session, ObjectServerError error) { switch(error.getCategory()) { case FATAL: return false; // Report all fatal errors to the user @@ -63,7 +63,7 @@ public boolean onError(SyncSession session, ObjectServerError error) { /** * Returns {@code true} if we decide to rebind, {@code false} if the error was determined to no longer be solvable. */ - private boolean rebind(SyncSession session) { + private boolean rebind(ObjectServerSession session) { // Track all calls to rebind(). If some error reported as RECOVERABLE keeps happening, we need to abort to // prevent run-away sessions. Right now we treat an error as recurring if it happens within 3 seconds of each // other. After 5 of such errors we terminate the session. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java index 2cae128bb9..ae14b6af3e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java @@ -17,24 +17,24 @@ package io.realm.internal.syncpolicy; import io.realm.ObjectServerError; -import io.realm.Session; +import io.realm.SyncSession; import io.realm.SyncConfiguration; -import io.realm.internal.objectserver.SyncSession; +import io.realm.internal.objectserver.ObjectServerSession; /** * Interface describing a given synchronization policy with the Realm Object Server. *

        - * The sole purpose of classes implementing this interface is to call {@link SyncSession#bind()} and - * {@link SyncSession#unbind()} as needed, which will control when changes are synchronized between a local and + * The sole purpose of classes implementing this interface is to call {@link ObjectServerSession#bind()} and + * {@link ObjectServerSession#unbind()} as needed, which will control when changes are synchronized between a local and * remote Realm. * - * The SyncPolicy is not responsible for managing the lifecycle of the {@link SyncSession} in general. So any - * implementation of this class should avoid calling {@link SyncSession#stop()} and - * {@link SyncSession#start()}. + * The SyncPolicy is not responsible for managing the lifecycle of the {@link ObjectServerSession} in general. So any + * implementation of this class should avoid calling {@link ObjectServerSession#stop()} and + * {@link ObjectServerSession#start()}. * - * If a session is stopped, {@link SyncSession#unbind()} is automatically called and any further calls to - * {@link SyncSession#bind()} and {@link SyncSession#unbind()} are ignored. - * {@link #onSessionStopped(SyncSession)} ()} will then be called so the sync policy have a chance to clean up + * If a session is stopped, {@link ObjectServerSession#unbind()} is automatically called and any further calls to + * {@link ObjectServerSession#bind()} and {@link ObjectServerSession#unbind()} are ignored. + * {@link #onSessionStopped(ObjectServerSession)} ()} will then be called so the sync policy have a chance to clean up * any resources it might be using. */ // Internal until we are sure this is the API we want @@ -44,34 +44,34 @@ public interface SyncPolicy { * Called when the session object is created. At this point it is possible to register any relevant error and event * listeners in either the Android framework or for the session itself. * - * {@link SyncSession#start()} will be automatically called after this method. + * {@link ObjectServerSession#start()} will be automatically called after this method. * - * @param session the {@link Session} just created. It has not yet been started. + * @param session the {@link SyncSession} just created. It has not yet been started. */ - void onSessionCreated(SyncSession session); + void onSessionCreated(ObjectServerSession session); /** - * The {@link SyncSession} has been stopped and will ignore any further calls to - * {@link SyncSession#bind()} and {@link SyncSession#unbind()}. All external resources should be + * The {@link ObjectServerSession} has been stopped and will ignore any further calls to + * {@link ObjectServerSession#bind()} and {@link ObjectServerSession#unbind()}. All external resources should be * cleaned up. * - * @param session {@link SyncSession} that has been stopped. + * @param session {@link ObjectServerSession} that has been stopped. */ - void onSessionStopped(SyncSession session); + void onSessionStopped(ObjectServerSession session); /** * Called the first time a Realm is opened on any thread. * - * @param session {@link SyncSession} associated with this Realm. + * @param session {@link ObjectServerSession} associated with this Realm. */ - void onRealmOpened(SyncSession session); + void onRealmOpened(ObjectServerSession session); /** * Called when the last Realm instance across all threads have been closed. * - * @param session {@link SyncSession} associated with this Realm. + * @param session {@link ObjectServerSession} associated with this Realm. */ - void onRealmClosed(SyncSession session); + void onRealmClosed(ObjectServerSession session); /** * Called if an error occurred in the underlying session. In many cases this has caused the session to become @@ -83,7 +83,7 @@ public interface SyncPolicy { * * This method is always called from a background thread, never the UI thread. * - * @see SyncConfiguration.Builder#errorHandler(Session.ErrorHandler) + * @see SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler) */ - boolean onError(SyncSession session, ObjectServerError error); + boolean onError(ObjectServerSession session, ObjectServerError error); } From 90907a342ac901d100fb70b9b45e7f7d38dd27d3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 17 Oct 2016 13:25:04 +0200 Subject: [PATCH 0167/2110] SyncUser.currentUser is now correctly cleared on logout + fixed compile errors. (#3640) --- CHANGELOG.md | 4 ++ .../java/io/realm/UserTests.java | 15 +++++++ .../objectServer/java/io/realm/SyncUser.java | 40 ++++++++++--------- .../java/io/realm/objectserver/AuthTests.java | 14 +++---- .../realm/objectserver/utils/UserFactory.java | 2 +- 5 files changed, 48 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b68f42ec7..d76dbec35b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ * Renamed `User` to `SyncUser`, `Credentials` to `SyncCredentials` and `Session` to `SyncSession` to align names with Cocoa. +### Bug fixes + +* `SyncUser.logout()` now correctly clears `SyncUser.currentUser()` (#3638). + ### Enhancement * `Realm.compactRealm()` works for encrypted Realms. diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java index ded9ee13a2..794b41fc30 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java @@ -66,6 +66,21 @@ public void currentUser_returnsNullIfUserExpired() { assertNull(SyncUser.currentUser()); } + // Test that current user is cleared if it is logged out + @Test + public void currentUser_clearedOnLogout() { + // Add an expired user to the user store + SyncUser user = SyncTestUtils.createTestUser(Long.MAX_VALUE); + UserStore userStore = new SharedPrefsUserStore(InstrumentationRegistry.getContext()); + SyncManager.setUserStore(userStore); + userStore.put(UserStore.CURRENT_USER_KEY, user); + + SyncUser savedUser = SyncUser.currentUser(); + assertEquals(user, savedUser); + savedUser.logout(); + assertNull(SyncUser.currentUser()); + } + // `all()` returns an empty list if no users are logged in @Test public void all_empty() { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index de6641360d..808ae04c0a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -258,6 +258,27 @@ public void logout() { session.getOsSession().stop(); } + // Remove all local tokens, preventing further connections. + // FIXME We still need to cache the user token so it can be revoked. + syncUser.clearTokens(); + + if (SyncUser.this.equals(SyncUser.currentUser())) { + SyncManager.getUserStore().remove(UserStore.CURRENT_USER_KEY); + } + + // Delete all Realms if needed. + for (ObjectServerUser.AccessDescription desc : syncUser.getRealms()) { + // FIXME: This will always be false since SyncConfiguration.Builder.deleteRealmOnLogout() is + // disabled. Make sure this works for Realm opened in the client thread/other processes. + if (desc.deleteOnLogout) { + File realmFile = new File(desc.localPath); + if (realmFile.exists() && !Util.deleteRealm(desc.localPath, realmFile.getParentFile(), realmFile.getName())) { + RealmLog.error("Could not delete Realm when user logged out: " + desc.localPath); + } + } + } + + // Finally revoke server token. The local user is logged out in any case. final AuthenticationServer server = SyncManager.getAuthServer(); ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; networkPoolExecutor.submit(new ExponentialBackoffTask() { @@ -269,25 +290,6 @@ protected LogoutResponse execute() { @Override protected void onSuccess(LogoutResponse response) { - // Remove all local tokens, preventing further connections. - syncUser.clearTokens(); - - if (SyncUser.this.equals(SyncUser.currentUser())) { - SyncManager.getUserStore().remove(UserStore.CURRENT_USER_KEY); - } - - // Delete all Realms if needed. - for (ObjectServerUser.AccessDescription desc : syncUser.getRealms()) { - // FIXME: This will always be false since SyncConfiguration.Builder.deleteRealmOnLogout() is - // disabled. Make sure this works for Realm opened in the client thread/other processes. - if (desc.deleteOnLogout) { - File realmFile = new File(desc.localPath); - if (realmFile.exists() && !Util.deleteRealm(desc.localPath, realmFile.getParentFile(), realmFile.getName())) { - RealmLog.error("Could not delete Realm when user logged out: " + desc.localPath); - } - } - } - SyncManager.notifyUserLoggedOut(SyncUser.this); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 6fa8244f90..d4601a47f2 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -9,11 +9,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import io.realm.Credentials; +import io.realm.SyncCredentials; import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.Realm; -import io.realm.User; +import io.realm.SyncUser; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.HttpUtils; import io.realm.rule.RunInLooperThread; @@ -40,9 +40,9 @@ public static void tearDown () throws Exception { @Test public void login_userNotExist() { - Credentials credentials = Credentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); + SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); try { - User.login(credentials, Constants.AUTH_URL); + SyncUser.login(credentials, Constants.AUTH_URL); fail(); } catch (ObjectServerError expected) { assertEquals(ErrorCode.UNKNOWN_ACCOUNT, expected.getErrorCode()); @@ -52,10 +52,10 @@ public void login_userNotExist() { @Test @RunTestInLooperThread public void loginAsync_userNotExist() { - Credentials credentials = Credentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); - User.loginAsync(credentials, Constants.AUTH_URL, new User.Callback() { + SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override - public void onSuccess(User user) { + public void onSuccess(SyncUser user) { fail(); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index f341a32199..1ebb2d6ab5 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -19,7 +19,7 @@ import java.net.URI; import java.net.URISyntaxException; -import io.realm.User; +import io.realm.SyncUser; import io.realm.objectserver.utils.Constants; // Must be in `io.realm.objectserver` to work around package protected methods. From 88dd96332d19960c27ed3ba83864581041cb4a62 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 18 Oct 2016 11:42:16 +0900 Subject: [PATCH 0168/2110] allow to put Realm database file on external storage. (#3591) * set the path of the directory of named pipes to fix #3140 * update CHANGELOG * add a test for issue3140 * update test * follow the chenges in object-store * skip an external storage test on the device where SELinux is not enforced * rename test * rename variable * update object-store * make SharedRealm#temporaryDirectory volatile * address findbugs error --- CHANGELOG.md | 1 + .../androidTest/java/io/realm/RealmTests.java | 35 ++++++++++++++ .../androidTest/java/io/realm/TestHelper.java | 48 +++++++++++++++++++ .../cpp/io_realm_internal_SharedRealm.cpp | 11 +++++ realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/Realm.java | 3 ++ .../java/io/realm/internal/SharedRealm.java | 28 +++++++++++ 7 files changed, 127 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7fd677a02..1d3b0ced28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Those were not kept by ProGuard: names of native methods not in the `io.realm.internal` package, names of classes used in method signature (#3596). * Missing ProGuard configuration for libraries used by Sync extension (#3596). * Error handler was not called when sync session failed (#3597). +* Permission error when a database file is located at external storage (#3140). ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index a9205744ba..14e27c6cd5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -31,6 +31,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -3811,4 +3812,38 @@ public void getLocalInstanceCount() { realm.close(); assertEquals(0, Realm.getGlobalInstanceCount(config)); } + + @Test + public void namedPipeDirForExternalStorage() { + + // test for https://github.com/realm/realm-java/issues/3140 + realm.close(); + realm = null; + + final File namedPipeDir = SharedRealm.getTemporaryDirectory(); + assertTrue(namedPipeDir.isDirectory()); + TestHelper.deleteRecursively(namedPipeDir); + //noinspection ResultOfMethodCallIgnored + namedPipeDir.mkdirs(); + + final File externalFilesDir = context.getExternalFilesDir(null); + final RealmConfiguration config = new RealmConfiguration.Builder() + .directory(externalFilesDir) + .name("external.realm") + .build(); + + // test if it works when the namedPipeDir is empty. + Realm realmOnExternalStorage = Realm.getInstance(config); + realmOnExternalStorage.close(); + + assertTrue(namedPipeDir.isDirectory()); + + Assume.assumeTrue("SELinux is not enforced on this device.", TestHelper.isSelinuxEnforcing()); + + assertEquals(2, namedPipeDir.list().length); + + // test if it works when the namedPipeDir and the named pipe files already exist. + realmOnExternalStorage = Realm.getInstance(config); + realmOnExternalStorage.close(); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index aca740d079..f4e08deae5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.res.AssetManager; +import android.os.Build; import android.os.Looper; import android.support.test.InstrumentationRegistry; import android.util.Log; @@ -38,6 +39,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Date; +import java.util.Locale; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -1086,4 +1088,50 @@ public void run() { throw throwable; } } + + @SuppressWarnings("WeakerAccess") + public static void deleteRecursively(File file) { + if (!file.exists()) { + return; + } + if (file.isDirectory()) { + for (File f : file.listFiles()) { + deleteRecursively(f); + } + } + + if (!file.delete()) { + throw new AssertionError("failed to delete " + file.getAbsolutePath()); + } + } + + @SuppressWarnings("WeakerAccess") + public static boolean isSelinuxEnforcing() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + // SELinux is not enabled for these versions. + return false; + } + try { + final Process process = new ProcessBuilder("/system/bin/getenforce").start(); + try { + final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + //noinspection TryFinallyCanBeTryWithResources + try { + return reader.readLine().toLowerCase(Locale.ENGLISH).equals("enforcing"); + } finally { + try { + reader.close(); + } catch (IOException ignored) { + } + } + } finally { + try { + process.waitFor(); + } catch (InterruptedException ignored) { + } + } + } catch (IOException e) { + return false; + } + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 30fca15276..5a01e10ac0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -24,6 +24,17 @@ static_assert(SchemaMode::Additive == static_assert(SchemaMode::Manual == static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_MANUAL), ""); +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv *env, jclass, jstring temporary_directory_path) +{ + TR_ENTER(env) + + try { + JStringAccessor path(env, temporary_directory_path); // throws + realm::set_temporary_directory(std::string(path)); // throws + } CATCH_STD() +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstring realm_path, jbyteArray key, jbyte schema_mode, jboolean in_memory, jboolean cache, jboolean disable_format_upgrade, diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index c5135a5935..bafafb1464 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit c5135a5935765fa8993fc1b796ef5ed7da609cdf +Subproject commit bafafb1464494d0a731a036399aa5c944d92a5bf diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 9a68057ec4..060c5b2614 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -27,6 +27,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -54,6 +55,7 @@ import io.realm.internal.RealmCore; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.AndroidLogger; @@ -190,6 +192,7 @@ public static synchronized void init(Context context) { defaultConfiguration = new RealmConfiguration.Builder(context).build(); ObjectServerFacade.getSyncFacadeIfPossible().init(context); BaseRealm.applicationContext = context.getApplicationContext(); + SharedRealm.initialize(new File(context.getFilesDir(), ".realmNamedPipes")); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 347807c0b5..c3fab6d786 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -33,6 +33,33 @@ public final class SharedRealm implements Closeable { public static final byte FILE_EXCEPTION_KIND_IMCOMPATIBLE_LOCK_FILE = 4; public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 5; + public static void initialize(File tempDirectory) { + if (SharedRealm.temporaryDirectory != null) { + // already initialized + return; + } + if (tempDirectory == null) { + throw new IllegalArgumentException("'tempDirectory' must not be null."); + } + + String temporaryDirectoryPath = tempDirectory.getAbsolutePath(); + if (!tempDirectory.isDirectory() && !tempDirectory.mkdirs() && !tempDirectory.isDirectory()) { + throw new IOException("failed to create temporary directory: " + temporaryDirectoryPath); + } + + if (!temporaryDirectoryPath.endsWith("/")) { + temporaryDirectoryPath += "/"; + } + nativeInit(temporaryDirectoryPath); + SharedRealm.temporaryDirectory = tempDirectory; + } + + public static File getTemporaryDirectory() { + return temporaryDirectory; + } + + private volatile static File temporaryDirectory; + public enum Durability { FULL(0), MEM_ONLY(1); @@ -340,6 +367,7 @@ public void invokeSchemaChangeListenerIfSchemaChanged() { } } + private static native void nativeInit(String temporaryDirectoryPath); private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory, boolean cache, boolean disableFormatUpgrade, boolean autoChangeNotification, From 49d566977604275797374ddfabb8cb295acb6fda Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 18 Oct 2016 16:04:10 +0900 Subject: [PATCH 0169/2110] Delete realm file before test (#3648) --- .../realm-library/src/androidTest/java/io/realm/RealmTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 14e27c6cd5..f0f3b670ba 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3831,6 +3831,7 @@ public void namedPipeDirForExternalStorage() { .directory(externalFilesDir) .name("external.realm") .build(); + Realm.deleteRealm(config); // test if it works when the namedPipeDir is empty. Realm realmOnExternalStorage = Realm.getInstance(config); From f835f0e6aba21fa84cea9cbddcae57dde4b160b8 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 18 Oct 2016 23:54:13 +0900 Subject: [PATCH 0170/2110] fix temp directory name (#3653) --- realm/realm-library/src/main/java/io/realm/Realm.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 060c5b2614..632ce353f0 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -192,7 +192,7 @@ public static synchronized void init(Context context) { defaultConfiguration = new RealmConfiguration.Builder(context).build(); ObjectServerFacade.getSyncFacadeIfPossible().init(context); BaseRealm.applicationContext = context.getApplicationContext(); - SharedRealm.initialize(new File(context.getFilesDir(), ".realmNamedPipes")); + SharedRealm.initialize(new File(context.getFilesDir(), ".realm.temp")); } } From 4e8c3d250dd0cd247d1830e0c9cf04976e6d23a7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 18 Oct 2016 21:37:32 -0500 Subject: [PATCH 0171/2110] Fix change log --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 605167e1ea..b87ba1b78c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,6 @@ ### Enhancements * Reduce transformer logger verbosity (#3608). ->>>>>>> base/master ## 2.0.2 From 38a082507b28b739a71b08197f9d3971c1bd7e40 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 19 Oct 2016 09:26:03 +0200 Subject: [PATCH 0172/2110] Added credits to changelog (#3655) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65e5c1658a..f064bbf2a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ * Added `first(E defaultValue)` and `last(E defaultValue)` methods to `RealmList` and `RealmResult`. These methods will return the provided object instead of throwing an `IndexOutOfBoundsException` if the list is empty. * Added `User.all()` that returns all known Realm Object Server users. +### Credits + +* Thanks to Max Furman (@maxfurman) for adding support for `first()` and `last()` default values. + ## 2.0.3 ### Bug fixes From acedd44bb0c194a509074a7d189cd53689c015fd Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Oct 2016 10:54:02 +0200 Subject: [PATCH 0173/2110] Upgrade Core to 2.1.3 and Sync to BETA-3.1 (#3675) --- CHANGELOG.md | 36 +++++++++++++++++------------------- dependencies.list | 9 +++++++-- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f064bbf2a3..e691e8d637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,29 +7,26 @@ ### Bug fixes * `SyncUser.logout()` now correctly clears `SyncUser.currentUser()` (#3638). - -### Enhancement - -* `Realm.compactRealm()` works for encrypted Realms. -* Added `first(E defaultValue)` and `last(E defaultValue)` methods to `RealmList` and `RealmResult`. These methods will return the provided object instead of throwing an `IndexOutOfBoundsException` if the list is empty. -* Added `User.all()` that returns all known Realm Object Server users. - -### Credits - -* Thanks to Max Furman (@maxfurman) for adding support for `first()` and `last()` default values. - -## 2.0.3 - -### Bug fixes - * Those were not kept by ProGuard: names of native methods not in the `io.realm.internal` package, names of classes used in method signature (#3596). * Missing ProGuard configuration for libraries used by Sync extension (#3596). * Error handler was not called when sync session failed (#3597). * Permission error when a database file is located at external storage (#3140). -### Enhancements +### Enhancement +* `Realm.compactRealm()` now works for encrypted Realms. +* Added `first(E defaultValue)` and `last(E defaultValue)` methods to `RealmList` and `RealmResult`. These methods will return the provided object instead of throwing an `IndexOutOfBoundsException` if the list is empty. * Reduce transformer logger verbosity (#3608). +* Added `User.all()` that returns all known Realm Object Server users. + +### Internal + +* Upgraded Realm Core to 2.1.3 +* Upgraded Realm Sync to 1.0.0-BETA-3.1 + +### Credits + +* Thanks to Max Furman (@maxfurman) for adding support for `first()` and `last()` default values. ## 2.0.2 @@ -39,9 +36,10 @@ This release is not protocol-compatible with previous versions of the Realm Mobi * Build error when using Java 7 (#3563). -## Internal +### Internal -* Upgraded to Realm Core 2.1.0 / Realm Sync 2.0-BETA. +* Upgraded Realm Core to 2.1.0 +* Upgraded Realm Sync to 1.0.0-BETA-2.0. ## 2.0.1 @@ -52,7 +50,7 @@ This release is not protocol-compatible with previous versions of the Realm Mobi * `distinctAsync` did not respect other query parameters (#3537). * `ConcurrentModificationException` from Gradle when building an application (#3501). -## Internal +### Internal * Upgraded to Realm Core 2.0.1 / Realm Sync 1.3-BETA diff --git a/dependencies.list b/dependencies.list index b1240870a7..441b3ce8eb 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,3 +1,8 @@ -REALM_SYNC_VERSION=1.0.0-BETA-2.0 -REALM_SYNC_SHA256=c7eb59576b28373283e94dafe42737015657cb0deac435a66c28fc74629ce721 +# Realm Sync Core release used by Realm Java +# https://github.com/realm/realm-sync/releases +REALM_SYNC_VERSION=1.0.0-BETA-3.1 +REALM_SYNC_SHA256=43f65bde124589eff9f06ec548816776644fb80498b9aded1fc3b2fd2b9aff5f + +# Object Server Release used by Integration tests +# https://packagecloud.io/realm/realm?filter=debs REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-2.1-271 From d6f03f70b07f1940e5edb0d7e53f15df377139a4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 24 Oct 2016 15:13:33 +0800 Subject: [PATCH 0174/2110] Pack unstripped so files for different flavors --- realm/realm-library/src/main/cpp/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 957d90fda2..484baf1100 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -178,7 +178,8 @@ endif() # Strip the release so files and backup the unstripped versions if (CMAKE_BUILD_TYPE STREQUAL "Release") - set(unstripped_SO_DIR "${CMAKE_SOURCE_DIR}/../../../build/outputs/jniLibs-unstripped/${ANDROID_ABI}") + set(unstripped_SO_DIR + "${CMAKE_SOURCE_DIR}/../../../build/outputs/jniLibs-unstripped/${REALM_FLAVOR}/${ANDROID_ABI}") add_custom_command(TARGET realm-jni POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${unstripped_SO_DIR} From 5b140856707c2399f7f7279fde985ddceb1e3e13 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 24 Oct 2016 14:23:47 +0800 Subject: [PATCH 0175/2110] Remove dup & uselss file --- .../cpp/io_realm_internal_Util.cpp | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 realm/realm-library/src/objectServer/cpp/io_realm_internal_Util.cpp diff --git a/realm/realm-library/src/objectServer/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/objectServer/cpp/io_realm_internal_Util.cpp deleted file mode 100644 index 84e7a3fe5a..0000000000 --- a/realm/realm-library/src/objectServer/cpp/io_realm_internal_Util.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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 - -#include -#include - -#include "io_realm_log_LogLevel.h" -#include "mem_usage.hpp" -#include "util.hpp" - -using std::string; - -//#define USE_VLD -#if defined(_MSC_VER) && defined(_DEBUG) && defined(USE_VLD) - #include "C:\\Program Files (x86)\\Visual Leak Detector\\include\\vld.h" -#endif - -// used by logging -int trace_level = 0; -jclass realmlog_class; -jmethodID log_trace; -jmethodID log_debug; -jmethodID log_info; -jmethodID log_warn; -jmethodID log_error; -jmethodID log_fatal; - -const string TABLE_PREFIX("class_"); - - -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) -{ - JNIEnv* env; - if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { - return JNI_ERR; - } - else { - g_vm = vm; - // Loading classes and constructors for later use - used by box typed fields and a few methods' return value - java_lang_long = GetClass(env, "java/lang/Long"); - java_lang_long_init = env->GetMethodID(java_lang_long, "", "(J)V"); - java_lang_float = GetClass(env, "java/lang/Float"); - java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); - java_lang_double = GetClass(env, "java/lang/Double"); - java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); - sync_manager = GetClass(env, "io/realm/SyncManager"); - realmlog_class = GetClass(env, "io/realm/log/RealmLog"); - log_trace = env->GetStaticMethodID(realmlog_class, "trace", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_debug = env->GetStaticMethodID(realmlog_class, "debug", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_info = env->GetStaticMethodID(realmlog_class, "info", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_warn = env->GetStaticMethodID(realmlog_class, "warn", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_error = env->GetStaticMethodID(realmlog_class, "error", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_fatal = env->GetStaticMethodID(realmlog_class, "fatal", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - } - - return JNI_VERSION_1_6; -} - -JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) -{ - JNIEnv* env; - if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { - return; - } - else { - env->DeleteGlobalRef(java_lang_long); - env->DeleteGlobalRef(java_lang_float); - env->DeleteGlobalRef(java_lang_double); - } -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Util_nativeSetDebugLevel(JNIEnv*, jclass, jint level) -{ - /** - * level should match one of the levels defined in LogLevel.java - * ALL = 1 - * TRACE = 2 - * DEBUG = 3 - * INFO = 4 - * WARN = 5 - * ERROR = 6 - * FATAL = 7 - * OFF = 8 - */ - trace_level = level; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Util_nativeGetMemUsage(JNIEnv*, jclass) -{ - return GetMemUsage(); -} - -JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeGetTablePrefix( - JNIEnv* env, jclass) -{ - realm::StringData sd(TABLE_PREFIX); - return to_jstring(env, sd); -} From d404c9de1a58d597d8ed80f1aec511230579cd22 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 24 Oct 2016 06:25:17 -0500 Subject: [PATCH 0176/2110] Add ANDROID_NDK back (#3676) --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index a632ba67e4..3fa5aac87f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64 ENV ANDROID_HOME /opt/android-sdk-linux # Need by cmake ENV ANDROID_NDK_HOME /opt/android-ndk +ENV ANDROID_NDK /opt/android-ndk ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} ENV NDK_CCACHE /usr/bin/ccache From 733eca83e4d4fb08dc7a21242f695052f545c865 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 24 Oct 2016 21:29:57 +0900 Subject: [PATCH 0177/2110] update github issue template (#3677) --- .github/ISSUE_TEMPLATE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 187ea46746..4d223c132d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -38,6 +38,8 @@ #### Version of Realm and tooling Realm version(s): ? +Realm sync feature enabled: yes/no + Android Studio version: ? Which Android version and device: ? From 1694c306cb0f7bb2f7dcc8cd84856ed580377922 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Oct 2016 15:20:36 +0200 Subject: [PATCH 0178/2110] Fix memory leak when unsubscribing from RxJava observables. (#3678) --- CHANGELOG.md | 1 + .../src/main/java/io/realm/rx/RealmObservableFactory.java | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e691e8d637..f85404202b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Missing ProGuard configuration for libraries used by Sync extension (#3596). * Error handler was not called when sync session failed (#3597). * Permission error when a database file is located at external storage (#3140). +* Memory leak when unsubscribing from a RealmResults/RealmObject RxJava Observable (#3552). ### Enhancement diff --git a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java index 285388bbc8..7c89ecd45a 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java @@ -322,10 +322,12 @@ public void releaseReference(K object) { Integer count = references.get(object); if (count == null) { throw new IllegalStateException("Object does not have any references: " + object); - } else if (count > 0) { + } else if (count > 1) { references.put(object, count - 1); - } else { + } else if (count == 1) { references.remove(object); + } else { + throw new IllegalStateException("Invalid reference count: " + count); } } } From 14a53d8fa75a4a70ce9f4b7e3398d1418c2dc136 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 24 Oct 2016 09:05:31 -0500 Subject: [PATCH 0179/2110] Refactor RealmLog (#3643) The motivation: * The previous implementaion for JNI log needs calls like: JNI -> Java -> JNI. It is not quite effecient for the most common case -- log in JNI and sync. * The previous log levels are bit confusing. * util.cpp gets messy. * The Logger class in Java gets confusing with the Logger from core/sync. So this commit makes below changes: * Adds RealmLogger to replace Logger. Creates a adaptor to ensure the subclass of Logger can still be used by the new system before we remove the Logger class. * Adds cpp namespace jni_impl and jni_util, and make log relevant code belong to these two namepsaces. * Moves the RealmLog to JNI. * Implenment a default Android logger in native code. * Add tag support for realm-java Logger. Java side is using tag "REALM_JAVA", JNI side is using tag "REALM_JNI" and sync is using tag "REALM_CORE". * All tags share the same log level. * Some cleanups. * Fix #3528 When there is existing java logger, calling log after ThrowNew will crash in native code because of a pending Java exception. It should be avoided. * Deprecate AndroidLogger. --- CHANGELOG.md | 6 + .../examples/objectserver/MyApplication.java | 4 +- .../java/io/realm/NotificationsTest.java | 11 +- .../java/io/realm/RealmLogTests.java | 156 +++++++++++++ .../androidTest/java/io/realm/TestHelper.java | 97 ++------ .../io/realm/AuthenticateRequestTests.java | 7 + .../realm-library/src/main/cpp/CMakeLists.txt | 5 +- .../src/main/cpp/io_realm_SyncManager.cpp | 73 +----- .../src/main/cpp/io_realm_internal_Util.cpp | 33 --- .../src/main/cpp/io_realm_log_RealmLog.cpp | 84 +++++++ .../src/main/cpp/jni_impl/android_logger.cpp | 93 ++++++++ .../src/main/cpp/jni_impl/android_logger.hpp | 43 ++++ .../src/main/cpp/jni_util/log.cpp | 191 ++++++++++++++++ .../src/main/cpp/jni_util/log.hpp | 194 ++++++++++++++++ realm/realm-library/src/main/cpp/util.cpp | 7 +- realm/realm-library/src/main/cpp/util.hpp | 79 ++----- .../src/main/java/io/realm/Realm.java | 2 - .../src/main/java/io/realm/internal/Util.java | 6 - .../main/java/io/realm/log/AndroidLogger.java | 3 + .../src/main/java/io/realm/log/LogLevel.java | 2 +- .../src/main/java/io/realm/log/Logger.java | 1 + .../src/main/java/io/realm/log/RealmLog.java | 216 ++++++++++++------ .../main/java/io/realm/log/RealmLogger.java | 38 +++ .../java/io/realm/SyncManager.java | 9 - 24 files changed, 1016 insertions(+), 344 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/log.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/log.hpp create mode 100644 realm/realm-library/src/main/java/io/realm/log/RealmLogger.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f85404202b..05a9d88fc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ### Breaking changes * Renamed `User` to `SyncUser`, `Credentials` to `SyncCredentials` and `Session` to `SyncSession` to align names with Cocoa. +* Removed `SyncManager.setLogLevel()`. Use `RealmLog.setLevel()` instead. + +### Deprecated + +* `Logger`. Use `RealmLogger` instead. +* `AndroidLogger`. The logger for Android is implemented in native code instead. ### Bug fixes diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java index 45050d8d89..8fb13a829b 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java @@ -20,7 +20,6 @@ import android.util.Log; import io.realm.Realm; -import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; public class MyApplication extends Application { @@ -32,8 +31,7 @@ public void onCreate() { // Enable full log output when debugging if (BuildConfig.DEBUG) { - RealmLog.clear(); - RealmLog.add(new AndroidLogger(Log.VERBOSE)); + RealmLog.setLevel(Log.VERBOSE); } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 90625fc4f8..0a8979656f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -47,7 +47,8 @@ import io.realm.entities.AllTypes; import io.realm.entities.Dog; -import io.realm.log.Logger; +import io.realm.log.LogLevel; +import io.realm.log.RealmLogger; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; @@ -647,7 +648,7 @@ public void run() { // Create a commit on another thread TestHelper.awaitOrFail(backgroundLooperStartedAndStopped); Realm realm = Realm.getInstance(realmConfig); - Logger logger = TestHelper.getFailureLogger(Log.WARN); + RealmLogger logger = TestHelper.getFailureLogger(Log.WARN); RealmLog.add(logger); realm.beginTransaction(); @@ -1180,9 +1181,11 @@ public void warnIfMixingSyncWritesAndAsyncQueries() { final AtomicBoolean warningLogged = new AtomicBoolean(false); final TestHelper.TestLogger testLogger = new TestHelper.TestLogger() { @Override - public void warn(Throwable t, String message, Object... args) { + public void log(int level, String tag, Throwable throwable, String message) { assertTrue(message.contains("Mixing asynchronous queries with local writes should be avoided.")); - warningLogged.set(true); + if (level == LogLevel.WARN) { + warningLogged.set(true); + } } }; RealmLog.add(testLogger); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java new file mode 100644 index 0000000000..e91ae3ba16 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java @@ -0,0 +1,156 @@ +package io.realm; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.realm.log.LogLevel; +import io.realm.log.Logger; +import io.realm.log.RealmLog; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class RealmLogTests { + + @Before + public void setUp() { + Realm.init(InstrumentationRegistry.getTargetContext()); + } + + @Test + public void add_remove() { + TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); + RealmLog.add(testLogger); + RealmLog.fatal("TEST"); + assertEquals("TEST", testLogger.message); + RealmLog.remove(testLogger); + RealmLog.fatal("TEST_AGAIN"); + assertEquals("TEST", testLogger.message); + } + + @Test + public void set_get_logLevel() { + TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); + RealmLog.add(testLogger); + + RealmLog.setLevel(LogLevel.FATAL); + assertEquals(LogLevel.FATAL, RealmLog.getLevel()); + RealmLog.debug("TEST_DEBUG"); + assertNull(testLogger.message); + + RealmLog.setLevel(LogLevel.DEBUG); + RealmLog.debug("TEST_DEBUG"); + assertEquals("TEST_DEBUG", testLogger.message); + RealmLog.fatal("TEST_FATAL"); + assertEquals("TEST_FATAL", testLogger.message); + + RealmLog.remove(testLogger); + } + + @Test + public void clear() { + TestHelper.TestLogger testLogger1 = new TestHelper.TestLogger(); + TestHelper.TestLogger testLogger2 = new TestHelper.TestLogger(); + RealmLog.add(testLogger1); + RealmLog.add(testLogger2); + RealmLog.fatal("TEST"); + + assertEquals("TEST", testLogger1.message); + assertEquals("TEST", testLogger2.message); + + RealmLog.clear(); + + RealmLog.fatal("TEST_AGAIN"); + assertEquals("TEST", testLogger1.message); + assertEquals("TEST", testLogger2.message); + + RealmLog.registerDefaultLogger(); + } + + @Test + public void throwable_passedToTheJavaLogger() { + TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); + RealmLog.add(testLogger); + Throwable throwable; + + try { + throw new RuntimeException("Test exception."); + } catch (RuntimeException e) { + throwable = e; + RealmLog.fatal(e); + } + + // Throwable has been passed. + assertEquals(throwable, testLogger.throwable); + // Message is the stacktrace. + assertTrue(testLogger.message.contains("RealmLogTests.java")); + RealmLog.remove(testLogger); + } + + static class TestOldLogger implements Logger { + String message; + Throwable throwable; + + @Override + public int getMinimumNativeDebugLevel() { + return 0; + } + + @Override + public void trace(Throwable throwable, String message, Object... args) { + } + + @Override + public void debug(Throwable throwable, String message, Object... args) { + } + + @Override + public void info(Throwable throwable, String message, Object... args) { + } + + @Override + public void warn(Throwable throwable, String message, Object... args) { + } + + @Override + public void error(Throwable throwable, String message, Object... args) { + } + + @Override + public void fatal(Throwable throwable, String message, Object... args) { + this.throwable = throwable; + this.message = message; + } + } + + @Test + public void loggerAdaptor() { + TestOldLogger testLogger = new TestOldLogger(); + RealmLog.add(testLogger); + Throwable throwable; + + try { + throw new RuntimeException("Test exception."); + } catch (RuntimeException e) { + throwable = e; + RealmLog.fatal(e); + } + + // Throwable has been passed. + assertEquals(throwable, testLogger.throwable); + assertTrue(testLogger.message.contains("RealmLogTests.java")); + + RealmLog.remove(testLogger); + RealmLog.fatal("new string"); + + // Logger has been removed, nothing should be changed. + assertEquals(throwable, testLogger.throwable); + assertTrue(testLogger.message.contains("RealmLogTests.java")); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index f4e08deae5..321c5d927c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -59,9 +59,8 @@ import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.async.RealmThreadPoolExecutor; -import io.realm.log.AndroidLogger; import io.realm.log.LogLevel; -import io.realm.log.Logger; +import io.realm.log.RealmLogger; import io.realm.rule.TestRealmConfigurationFactory; import static junit.framework.Assert.assertEquals; @@ -170,50 +169,23 @@ public static byte[] getRandomKey(long seed) { } /** - * Returns a Logger that will fail if it is asked to log a message above a certain level. + * Returns a RealmLogger that will fail if it is asked to log a message above a certain level. * * @param failureLevel {@link Log} level from which the unit test will fail. - * @return Logger implementation + * @return RealmLogger implementation */ - public static Logger getFailureLogger(final int failureLevel) { - return new AndroidLogger(Log.VERBOSE) { - - private void failIfEqualOrAbove(int logLevel, int failureLevel) { + public static RealmLogger getFailureLogger(final int failureLevel) { + return new RealmLogger() { + private void failIfEqualOrAbove(int logLevel) { if (logLevel >= failureLevel) { fail("Message logged that was above valid level: " + logLevel + " >= " + failureLevel); } } @Override - public void trace(Throwable t, String message, Object... args) { - failIfEqualOrAbove(Log.VERBOSE, failureLevel); - } - - @Override - public void debug(Throwable t, String message, Object... args) { - failIfEqualOrAbove(Log.DEBUG, failureLevel); - } - - @Override - public void info(Throwable t, String message, Object... args) { - failIfEqualOrAbove(Log.INFO, failureLevel); - } - - @Override - public void warn(Throwable t, String message, Object... args) { - failIfEqualOrAbove(Log.WARN, failureLevel); + public void log(int level, String tag, Throwable throwable, String message) { + failIfEqualOrAbove(level); } - - @Override - public void error(Throwable t, String message, Object... args) { - failIfEqualOrAbove(Log.ERROR, failureLevel); - } - - @Override - public void fatal(Throwable t, String message, Object... args) { - failIfEqualOrAbove(Log.ERROR, failureLevel); - } - }; } @@ -229,7 +201,7 @@ public static String getRandomString(int length) { /** * Returns a naive logger that can be used to test the values that are sent to the logger. */ - public static class TestLogger implements Logger { + public static class TestLogger implements RealmLogger { private final int minimumLevel; public String message; @@ -244,53 +216,10 @@ public TestLogger(int minimumLevel) { } @Override - public int getMinimumNativeDebugLevel() { - return minimumLevel; - } - - @Override - public void trace(Throwable t, String message, Object... args) { - if (minimumLevel <= LogLevel.TRACE) { - this.message = (message != null) ? String.format(message, args) : null; - this.throwable = t; - } - } - - @Override - public void debug(Throwable t, String message, Object... args) { - if (minimumLevel <= LogLevel.DEBUG) { - this.message = (message != null) ? String.format(message, args) : null; - this.throwable = t; - } - } - - @Override - public void info(Throwable t, String message, Object... args) { - if (minimumLevel <= LogLevel.INFO) { - this.message = (message != null) ? String.format(message, args) : null; - this.throwable = t; - } - } - - @Override - public void warn(Throwable t, String message, Object... args) { - this.message = (message != null) ? String.format(message, args) : null; - this.throwable = t; - } - - @Override - public void error(Throwable t, String message, Object... args) { - if (minimumLevel <= LogLevel.ERROR) { - this.message = (message != null) ? String.format(message, args) : null; - this.throwable = t; - } - } - - @Override - public void fatal(Throwable t, String message, Object... args) { - if (minimumLevel <= LogLevel.FATAL) { - this.message = (message != null) ? String.format(message, args) : null; - this.throwable = t; + public void log(int level, String tag, Throwable throwable, String message) { + if (minimumLevel <= level) { + this.message = message; + this.throwable = throwable; } } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index 5d90e9297f..a9646a0bea 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -1,10 +1,12 @@ package io.realm; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.json.JSONException; import org.json.JSONObject; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -27,6 +29,11 @@ @RunWith(AndroidJUnit4.class) public class AuthenticateRequestTests { + @Before + public void setUp() { + Realm.init(InstrumentationRegistry.getTargetContext()); + } + // Tests based on the schemas described here: https://github.com/realm/realm-sync-services/blob/master/doc/index.apib @Test diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 42addb5f0a..b45fb5a4dd 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -36,7 +36,8 @@ set(classes_LIST io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil - io.realm.log.LogLevel io.realm.Property io.realm.RealmSchema io.realm.RealmObjectSchema + io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema + io.realm.RealmObjectSchema ) set(jni_headers_PATH ${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) @@ -136,6 +137,8 @@ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${REALM_LINKER_FLAGS # JNI source files file(GLOB jni_SRC "*.cpp" + "jni_util/*.cpp" + "jni_impl/android_logger.cpp" ) # Those source file are only needed for sync. if (NOT build_SYNC) diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index 06f8ac1d2f..3486d9e4dc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -29,56 +29,14 @@ #include "io_realm_SyncManager.h" +#include "jni_util/log.hpp" + using namespace realm; using namespace realm::sync; +using namespace realm::jni_util; std::unique_ptr sync_client; -class AndroidLogger: public util::RootLogger -{ -public: - void do_log(Level level, std::string msg) - { - // FIXME Sync only calls the logger from the thread running the client, so it should - // be safe to store the env when starting the thread. - JNIEnv *env; - g_vm->AttachCurrentThread(&env, nullptr); - jmethodID log_method; - switch (level) { - case Level::trace: log_method = log_trace; break; - case Level::debug: log_method = log_debug; break; - case Level::detail: log_method = log_debug; break; - case Level::info: log_method = log_info; break; - case Level::warn: log_method = log_warn; break; - case Level::error: log_method = log_error; break; - case Level::fatal: log_method = log_fatal; break; - case Level::all: - case Level::off: - ThrowException(env, IllegalArgument, - util::format("Unknown logger argument: %s.", util::Logger::get_level_prefix(level))); - return; - } - log_message(env, log_method, msg.c_str()); - } - static AndroidLogger& shared() noexcept; -}; - -// Not used by now -struct AndroidLoggerFactory : public realm::SyncLoggerFactory { - std::unique_ptr make_logger(util::Logger::Level level) { - auto logger = std::make_unique(); - logger->set_level_threshold(level); - return std::unique_ptr(std::move(logger)); - } -} s_logger_factory; - -// TODO: Move to a better place & not needed after moving to OS -AndroidLogger& AndroidLogger::shared() noexcept -{ - static AndroidLogger logger; - return logger; -} - static jclass sync_manager = nullptr; static jmethodID sync_manager_notify_error_handler = nullptr; @@ -100,10 +58,8 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient if (sync_client) return; try { - AndroidLogger::shared().set_level_threshold(util::Logger::Level::warn); - sync::Client::Config config; - config.logger = &AndroidLogger::shared(); + config.logger = &CoreLoggerBridge::shared(); sync_client = std::make_unique(std::move(config)); // Throws // This function should only be called once, so below is safe. @@ -122,24 +78,3 @@ Java_io_realm_SyncManager_nativeRunClient(JNIEnv *env, jclass) sync_client->run(); } CATCH_STD() } - -JNIEXPORT void JNICALL -Java_io_realm_SyncManager_nativeSetSyncClientLogLevel(JNIEnv* env, jclass, jint logLevel) -{ - util::Logger::Level native_log_level; - switch(logLevel) { - case io_realm_log_LogLevel_ALL: native_log_level = util::Logger::Level::all; break; - case io_realm_log_LogLevel_TRACE: native_log_level = util::Logger::Level::trace; break; - case io_realm_log_LogLevel_DEBUG: native_log_level = util::Logger::Level::debug; break; - case io_realm_log_LogLevel_INFO: native_log_level = util::Logger::Level::info; break; - case io_realm_log_LogLevel_WARN: native_log_level = util::Logger::Level::warn; break; - case io_realm_log_LogLevel_ERROR: native_log_level = util::Logger::Level::error; break; - case io_realm_log_LogLevel_FATAL: native_log_level = util::Logger::Level::fatal; break; - case io_realm_log_LogLevel_OFF: native_log_level = util::Logger::Level::off; break; - default: - ThrowException(env, IllegalArgument, "Invalid log level: " + logLevel); - return; - } - // FIXME: This call is not thread safe. Switch to OS implementation to make it thread safe. - AndroidLogger::shared().set_level_threshold(native_log_level); -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 067ce9c8e6..241ce22908 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -19,7 +19,6 @@ #include #include -#include "io_realm_log_LogLevel.h" #include "mem_usage.hpp" #include "util.hpp" @@ -30,16 +29,6 @@ using std::string; #include "C:\\Program Files (x86)\\Visual Leak Detector\\include\\vld.h" #endif -// used by logging -int trace_level = 0; -jclass realmlog_class; -jmethodID log_trace; -jmethodID log_debug; -jmethodID log_info; -jmethodID log_warn; -jmethodID log_error; -jmethodID log_fatal; - const string TABLE_PREFIX("class_"); @@ -58,13 +47,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); java_lang_double = GetClass(env, "java/lang/Double"); java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); - realmlog_class = GetClass(env, "io/realm/log/RealmLog"); - log_trace = env->GetStaticMethodID(realmlog_class, "trace", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_debug = env->GetStaticMethodID(realmlog_class, "debug", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_info = env->GetStaticMethodID(realmlog_class, "info", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_warn = env->GetStaticMethodID(realmlog_class, "warn", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_error = env->GetStaticMethodID(realmlog_class, "error", "(Ljava/lang/String;[Ljava/lang/Object;)V"); - log_fatal = env->GetStaticMethodID(realmlog_class, "fatal", "(Ljava/lang/String;[Ljava/lang/Object;)V"); } return JNI_VERSION_1_6; @@ -83,21 +65,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) } } -JNIEXPORT void JNICALL Java_io_realm_internal_Util_nativeSetDebugLevel(JNIEnv*, jclass, jint level) -{ - /** - * level should match one of the levels defined in LogLevel.java - * ALL = 1 - * TRACE = 2 - * DEBUG = 3 - * INFO = 4 - * WARN = 5 - * ERROR = 6 - * FATAL = 7 - * OFF = 8 - */ - trace_level = level; -} JNIEXPORT jlong JNICALL Java_io_realm_internal_Util_nativeGetMemUsage(JNIEnv*, jclass) { diff --git a/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp b/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp new file mode 100644 index 0000000000..45aa8a72c2 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 "io_realm_log_RealmLog.h" +#include "jni_util/log.hpp" +#include "util.hpp" + +using namespace realm::util; +using namespace realm::jni_util; + +JNIEXPORT void JNICALL +Java_io_realm_log_RealmLog_nativeAddLogger(JNIEnv *env, jclass, jobject java_logger) +{ + try { + Log::shared().add_java_logger(env, java_logger); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_log_RealmLog_nativeRemoveLogger(JNIEnv *env, jclass, jobject java_logger) +{ + try { + Log::shared().remove_java_logger(env, java_logger); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_log_RealmLog_nativeClearLoggers(JNIEnv *env, jclass) +{ + try { + Log::shared().clear_loggers(); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_log_RealmLog_nativeRegisterDefaultLogger(JNIEnv *env, jclass) +{ + try { + Log::shared().register_default_logger(); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_log_RealmLog_nativeLog(JNIEnv *env, jclass, jint level, jstring tag, jthrowable throwable, + jstring message) +{ + try { + JStringAccessor tag_accessor(env, tag); + JStringAccessor message_accessor(env, message); + Log::shared().log(static_cast(level), std::string(tag_accessor).c_str(), throwable, + std::string(message_accessor).c_str()); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_log_RealmLog_nativeSetLogLevel(JNIEnv *env, jclass, jint level) +{ + try { + Log::shared().set_level(static_cast(level)); + } CATCH_STD() +} + +JNIEXPORT jint JNICALL +Java_io_realm_log_RealmLog_nativeGetLogLevel(JNIEnv *env, jclass) +{ + try { + return static_cast(Log::shared().get_level()); + } CATCH_STD() + + return static_cast(Log::Level::all); +} diff --git a/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp b/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp new file mode 100644 index 0000000000..b50ff1902a --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 + +#include "util/format.hpp" + +#include "android_logger.hpp" + +using namespace realm; +using namespace realm::jni_util; +using namespace realm::jni_impl; +using namespace realm::util; + +std::shared_ptr AndroidLogger::shared() +{ + // Private constructor, make_shared is not available. + static std::shared_ptr android_logger(new AndroidLogger()); + return android_logger; +} + +void AndroidLogger::log(Log::Level level, const char* tag, jthrowable, const char* message) { + android_LogPriority android_log_priority; + switch (level) { + case Log::Level::trace: + android_log_priority = ANDROID_LOG_VERBOSE; + break; + case Log::Level::debug: + android_log_priority = ANDROID_LOG_DEBUG; + break; + case Log::Level::info: + android_log_priority = ANDROID_LOG_INFO; + break; + case Log::Level::warn: + android_log_priority = ANDROID_LOG_WARN; + break; + case Log::Level::error: + android_log_priority = ANDROID_LOG_ERROR; + break; + case Log::Level::fatal: + android_log_priority = ANDROID_LOG_FATAL; + break; + default:// Cannot get here. + throw std::invalid_argument(format("Invalid log level: %1.", level)); + } + if (message) { + print(android_log_priority, tag, message); + } +} + +void AndroidLogger::print(android_LogPriority priority, const char* tag, const char* log_string) +{ + size_t log_size = strlen(log_string); + + if (log_size > LOG_ENTRY_MAX_LENGTH) { + size_t start = 0; + + while (start < log_size) { + size_t count = log_size - start > LOG_ENTRY_MAX_LENGTH ? LOG_ENTRY_MAX_LENGTH : log_size - start; + std::string tmp_str(log_string, start, count); + __android_log_write(priority, tag, tmp_str.c_str()); + start += count; + } + } else { + __android_log_write(priority, tag, log_string); + } +} + +namespace realm { +namespace jni_util { + +std::shared_ptr get_default_logger() +{ + return std::static_pointer_cast(AndroidLogger::shared()); +} + +} +} + + diff --git a/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp b/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp new file mode 100644 index 0000000000..6b90c0f6c6 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_IMPL_ANDROID_LOGGER_HPP +#define REALM_JNI_IMPL_ANDROID_LOGGER_HPP + +#include +#include "jni_util/log.hpp" + +namespace realm { +namespace jni_impl { + +//Default logger implementation for Android. +class AndroidLogger : public realm::jni_util::JniLogger { +public: + static std::shared_ptr shared(); + +protected: + void log(realm::jni_util::Log::Level level, const char* tag, jthrowable throwable, const char* message) override; + +private: + AndroidLogger() {}; + static void print(android_LogPriority priority, const char* tag, const char* log_string); + static const size_t LOG_ENTRY_MAX_LENGTH = 4000; +}; + +} +} + +#endif // REALM_JNI_IMPL_ANDROID_LOGGER_HPP diff --git a/realm/realm-library/src/main/cpp/jni_util/log.cpp b/realm/realm-library/src/main/cpp/jni_util/log.cpp new file mode 100644 index 0000000000..2e9ed01ae0 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/log.cpp @@ -0,0 +1,191 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 + +#include "jni_util/log.hpp" +#include "util/format.hpp" + +using namespace realm; +using namespace realm::jni_util; +using namespace realm::util; + +const char* CoreLoggerBridge::TAG = "REALM_CORE"; +const char* Log::REALM_JNI_TAG = "REALM_JNI"; +Log::Level Log::s_level = Log::Level::warn; + +// Native wrapper for Java RealmLogger class +class JavaLogger : public JniLogger { +public: + JavaLogger(JNIEnv* env, jobject java_logger); + ~JavaLogger(); + + bool is_same_object(JNIEnv* env, jobject java_logger); + +protected: + void log(Log::Level level, const char* tag, jthrowable throwable, const char* message) override; + +private: + JavaVM* m_jvm; + // Global ref of the logger object. + jobject m_java_logger; + jmethodID m_log_method; + + inline JNIEnv* get_current_env() noexcept + { + JNIEnv *env; + if (m_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) { + m_jvm->AttachCurrentThread(&env, nullptr); // Should never fail + } + return env; + } +}; + +JniLogger::JniLogger() + :m_is_java_logger(false) +{ +} + +JniLogger::JniLogger(bool is_java_logger) + :m_is_java_logger(is_java_logger) +{ +} + +JavaLogger::JavaLogger(JNIEnv* env, jobject java_logger) + :JniLogger(true) +{ + jint ret = env->GetJavaVM(&m_jvm); + if (ret != 0) { + throw std::runtime_error(util::format("Failed to get Java vm. Error: %d", ret)); + } + m_java_logger = env->NewGlobalRef(java_logger); + jclass cls = env->GetObjectClass(m_java_logger); + m_log_method = env->GetMethodID(cls, "log", "(ILjava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;)V"); +} + +JavaLogger::~JavaLogger() +{ + get_current_env()->DeleteGlobalRef(m_java_logger); +} + +void JavaLogger::log(Log::Level level, const char* tag, jthrowable throwable, const char* message) +{ + JNIEnv *env = get_current_env(); + + // NOTE: If a Java exception has been thrown in native code, the below call will trigger an JNI exception + // "JNI called with pending exception". This is something that should be avoided when printing log in JNI -- Always + // print log before calling env->ThrowNew. Doing env->ExceptionCheck() here creates overhead for normal cases. + env->CallVoidMethod(m_java_logger, m_log_method, level, env->NewStringUTF(tag), + throwable, env->NewStringUTF(message)); +} + +bool JavaLogger::is_same_object(JNIEnv* env, jobject java_logger) +{ + return env->IsSameObject(m_java_logger, java_logger); +} + +Log::Log() + : m_loggers() +{ + add_logger(get_default_logger()); +} + +Log& Log::shared() +{ + static Log log; + return log; +} + +void Log::add_java_logger(JNIEnv* env, const jobject java_logger) +{ + std::shared_ptr logger = std::make_shared(env, java_logger); + add_logger(logger); +} + +void Log::remove_java_logger(JNIEnv* env, const jobject java_logger) +{ + std::lock_guard lock(m_mutex); + m_loggers.erase(std::remove_if(m_loggers.begin(), m_loggers.end(), [&](const auto& obj) { + return obj->m_is_java_logger && std::static_pointer_cast(obj)->is_same_object(env, java_logger); + }), m_loggers.end()); +} + +void Log::add_logger(std::shared_ptr logger) +{ + std::lock_guard lock(m_mutex); + if (std::find(m_loggers.begin(), m_loggers.end(), logger) == m_loggers.end()) { + m_loggers.push_back(logger); + } +} + +void Log::remove_logger(std::shared_ptr logger) +{ + std::lock_guard lock(m_mutex); + + m_loggers.erase(std::remove_if(m_loggers.begin(), m_loggers.end(), [&](const auto& obj) { + return obj == logger; + }), m_loggers.end()); +} + +void Log::register_default_logger() { + add_logger(get_default_logger()); +} + +void Log::clear_loggers() +{ + std::lock_guard lock(m_mutex); + m_loggers.clear(); +} + +void Log::set_level(Level level) +{ + s_level = level; +} + +void Log::log(Level level, const char* tag, jthrowable throwable, const char* message) +{ + if (s_level <= level) { + std::lock_guard lock(m_mutex); + for (auto& logger : m_loggers) { + logger->log(level, tag, throwable, message); + } + } +} + +void CoreLoggerBridge::do_log(realm::util::Logger::Level level, std::string msg) +{ + // Ignore the level threshold from the root logger. + Log::Level jni_level; + switch (level) { + case Level::trace: jni_level = Log::trace; break; + case Level::debug: // Fall through. Map to same level debug. + case Level::detail: jni_level = Log::debug; break; + case Level::info: jni_level = Log::info; break; + case Level::warn: jni_level = Log::warn; break; + case Level::error: jni_level = Log::error; break; + case Level::fatal: jni_level = Log::fatal; break; + case Level::all: // Fall through. + case Level::off: // Fall through. + throw std::invalid_argument(format("Invalid log level.")); + } + Log::shared().log(jni_level, TAG, msg.c_str()); +} + +CoreLoggerBridge& CoreLoggerBridge::shared() +{ + static CoreLoggerBridge log_bridge; + return log_bridge; +} diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp new file mode 100644 index 0000000000..bed5925c46 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -0,0 +1,194 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_LOG_HPP +#define REALM_JNI_UTIL_LOG_HPP + +#include + +#include +#include +#include +#include + +#include "io_realm_log_LogLevel.h" + +#include "realm/util/logger.hpp" +#include "util/format.hpp" + +// FIXME: env is not needed any more. Will remove it in another PR. +#define TR_ENTER(env) \ + if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ + realm::jni_util::Log::t(" --> %1", __FUNCTION__); \ + } +#define TR_ENTER_PTR(env, ptr) \ + if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ + realm::jni_util::Log::t(" --> %1 %2" PRId64, __FUNCTION__, static_cast(ptr)); \ + } + +namespace realm { + +namespace jni_util { + +class JniLogger; + +// This is built for Realm logging, bother for Java and native side. +// Multiple loggers can be registered. All registered loggers will receive the same log events. +class Log { +public: + enum Level { + all = io_realm_log_LogLevel_ALL, + trace = io_realm_log_LogLevel_TRACE, + debug = io_realm_log_LogLevel_DEBUG, + info = io_realm_log_LogLevel_INFO, + warn = io_realm_log_LogLevel_WARN, + error = io_realm_log_LogLevel_ERROR, + fatal = io_realm_log_LogLevel_FATAL, + off = io_realm_log_LogLevel_OFF + }; + + // Add & Remove a Java RealmLogger. A Java logger needs to be implemented from io.realm.log.RealmLogger interface. + void add_java_logger(JNIEnv* env, const jobject java_logger); + void remove_java_logger(JNIEnv* env, const jobject java_logger); + + void add_logger(std::shared_ptr logger); + void remove_logger(std::shared_ptr logger); + + // Remove all custom loggers, but keep the default logger. + void clear_loggers(); + + // Add the default logger if it has been removed before. + void register_default_logger(); + + void set_level(Level level); + inline Level get_level() { + return s_level; + }; + + void log(Level level, const char* tag, jthrowable throwable, const char* message); + + inline void log(Level level, const char* tag, const char* message) + { + log(level, tag, nullptr, message); + } + + // Helper functions for logging with REALM_JNI tag. + inline static void t(const char* message) + { + shared().log(error, REALM_JNI_TAG, nullptr, message); + } + inline static void d(const char* message) + { + shared().log(error, REALM_JNI_TAG, nullptr, message); + } + inline static void i(const char* message) + { + shared().log(error, REALM_JNI_TAG, nullptr, message); + } + inline static void w(const char* message) + { + shared().log(error, REALM_JNI_TAG, nullptr, message); + } + inline static void e(const char* message) + { + shared().log(error, REALM_JNI_TAG, nullptr, message); + } + inline static void f(const char* message) + { + shared().log(error, REALM_JNI_TAG, nullptr, message); + } + + template + inline static void t(const char* fmt, Args&&... args) + { + shared().log(trace, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + } + template + inline static void d(const char* fmt, Args&&... args) + { + shared().log(debug, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + } + template + inline static void i(const char* fmt, Args&&... args) + { + shared().log(info, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + } + template + inline static void w(const char* fmt, Args&&... args) + { + shared().log(warn, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + } + template + inline static void e(const char* fmt, Args&&... args) + { + shared().log(error, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + } + template + inline static void f(const char* fmt, Args&&... args) { + shared().log(fatal, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + } + + // Get the shared Log instance. + static Log& shared(); + + // public & static for reading faster. For TR_ENTER check. + // Accessing to this var won't be thread safe and it is not necessary to be. Changing log level concurrently + // won't be a critical issue for commons cases. + static Level s_level; +private: + Log(); + + std::vector> m_loggers; + std::mutex m_mutex; + // Log tag for generic Realm JNI. + static const char* REALM_JNI_TAG; + +}; + +// Base Logger class. +class JniLogger { +protected: + JniLogger(); + // Used by JavaLogger. + JniLogger(bool is_java_logger); + // Indicate if this is a wrapper for Java RealmLogger class. See JavaLogger + bool m_is_java_logger; + +protected: + // Overwrite this method to handle the log event. + // throwable is the Throwable passed from Java which could be null. + virtual void log(Log::Level level, const char* tag, jthrowable throwable, const char* message) = 0; + friend class Log; +}; + +// Implement this function to return the default logger which will be registered during initialization. +extern std::shared_ptr get_default_logger(); + +class CoreLoggerBridge : public realm::util::RootLogger { +public: + void do_log(Logger::Level, std::string msg) override; + static CoreLoggerBridge& shared(); + +private: + CoreLoggerBridge() {}; + // Log tag for Realm core & sync. + static const char* TAG; +}; + +} // namespace jni_util +} // namespace realm + +#endif // REALM_JNI_UTIL_LOG_HPP diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index eb00c1afe2..26bb5e9094 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -29,6 +29,7 @@ using namespace std; using namespace realm; using namespace realm::util; +using namespace realm::jni_util; // Caching classes and constructors for boxed types. JavaVM* g_vm; @@ -94,7 +95,7 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla string message; jclass jExceptionClass = NULL; - TR_ERR(env, "jni: ThrowingException %d, %s, %s.", exception, classStr.c_str(), itemStr.c_str()) + Log::e("jni: ThrowingException %1, %2, %3.", exception, classStr.c_str(), itemStr.c_str()); switch (exception) { case ClassNotFound: @@ -148,11 +149,11 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla break; } if (jExceptionClass != NULL) { + Log::e("Exception has been throw: %1", message.c_str()); env->ThrowNew(jExceptionClass, message.c_str()); - TR_ERR(env, "Exception has been throw: %s", message.c_str()) } else { - TR_ERR_NO_VA_ARG(env, "ERROR: Couldn't throw exception.") + Log::e("ERROR: Couldn't throw exception."); } env->DeleteLocalRef(jExceptionClass); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 9db3763c60..754ebc12a2 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -37,7 +37,8 @@ #include #include "io_realm_internal_Util.h" -#include "io_realm_log_LogLevel.h" + +#include "jni_util/log.hpp" #define TRACE 1 // disable for performance #define CHECK_PARAMETERS 1 // Check all parameters in API and throw exceptions in java if invalid @@ -117,53 +118,6 @@ void ThrowNullValueException(JNIEnv* env, realm::Table *table, size_t col_ndx); jclass GetClass(JNIEnv* env, const char* classStr); -// Debug trace -extern int trace_level; -extern jclass realmlog_class; -extern jmethodID log_trace; -extern jmethodID log_debug; -extern jmethodID log_info; -extern jmethodID log_warn; -extern jmethodID log_error; -extern jmethodID log_fatal; - - -// Inspired by From http://www.netmite.com/android/mydroid/system/core/liblog/logd_write.c -inline void log_message(JNIEnv *env, jmethodID log_method, const char *msg, ...) -{ - // Check if a exception has already bee cast. In that case trying to log anything will crash. - if (env->ExceptionCheck()) { - return; - } - - va_list ap; - char buf[1024]; // Max logcat line length - va_start(ap, msg); - // Do formatting in C++. I gave up trying to send C++ variadic arguments back as Java var args. - vsnprintf(buf, 1024, msg, ap); - va_end(ap); - - jstring log_message = env->NewStringUTF(buf); - env->CallStaticVoidMethod(realmlog_class, log_method, log_message, NULL); - env->DeleteLocalRef(log_message); -} - -#if TRACE - #define TR_ENTER(env) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, " --> %s", __FUNCTION__); } else {} - #define TR_ENTER_PTR(env, ptr) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, " --> %s %" PRId64, __FUNCTION__, static_cast(ptr)); } else {} - #define TR(env, msg, ...) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, msg, __VA_ARGS__)); } else {} - #define TR_ERR(env, msg, ...) if (trace_level <= io_realm_log_LogLevel_ERROR) { log_message(env, log_error, msg, __VA_ARGS__); } else {} - #define TR_ERR_NO_VA_ARG(env, msg) if (trace_level <= io_realm_log_LogLevel_ERROR) { log_message(env, log_error, msg); } else {} - #define TR_LEAVE(env) if (trace_level <= io_realm_log_LogLevel_TRACE) { log_message(env, log_trace, " <-- %s", __FUNCTION__); } else {} -#else // TRACE - these macros must be empty - #define TR_ENTER(env) - #define TR_ENTER_PTR(env, ptr) - #define TR(env, msg, ...) - #define TR_ERR(env, msg, ...) - #define TR_ERR_NO_VA_ARG(env, msg) - #define TR_LEAVE(env) -#endif - // Check parameters #define TABLE_VALID(env,ptr) TableIsValid(env, ptr) @@ -235,7 +189,7 @@ inline bool TableIsValid(JNIEnv* env, T* objPtr) } if (!valid) { - TR_ERR(env, "Table %p is no longer attached!", VOID_PTR(objPtr)) + realm::jni_util::Log::e("Table %1 is no longer attached!", VOID_PTR(objPtr)); ThrowException(env, IllegalState, "Table is no longer valid to operate on."); } return valid; @@ -245,7 +199,7 @@ inline bool RowIsValid(JNIEnv* env, realm::Row* rowPtr) { bool valid = (rowPtr != NULL && rowPtr->is_attached()); if (!valid) { - TR_ERR(env, "Row %p is no longer attached!", VOID_PTR(rowPtr)) + realm::jni_util::Log::e("Row %1 is no longer attached!", VOID_PTR(rowPtr)); ThrowException(env, IllegalState, "Object is no longer valid to operate on. Was it deleted by another thread?"); } return valid; @@ -259,29 +213,30 @@ bool RowIndexesValid(JNIEnv* env, T* pTable, jlong startIndex, jlong endIndex, j if (endIndex == -1) endIndex = maxIndex; if (startIndex < 0) { - TR_ERR(env, "startIndex %" PRId64 " < 0 - invalid!", S64(startIndex)) + realm::jni_util::Log::e("startIndex %1 < 0 - invalid!", S64(startIndex)); ThrowException(env, IndexOutOfBounds, "startIndex < 0."); return false; } if (realm::util::int_greater_than(startIndex, maxIndex)) { - TR_ERR(env, "startIndex %" PRId64 " > %" PRId64 " - invalid!", S64(startIndex), S64(maxIndex)) + realm::jni_util::Log::e("startIndex %1 > %2 - invalid!", S64(startIndex), S64(maxIndex)); ThrowException(env, IndexOutOfBounds, "startIndex > available rows."); return false; } if (realm::util::int_greater_than(endIndex, maxIndex)) { - TR_ERR(env, "endIndex %" PRId64 " > %" PRId64 " - invalid!", S64(endIndex), S64(maxIndex)) + realm::jni_util::Log::e("endIndex %1 > %2 - invalid!", S64(endIndex), S64(maxIndex)); ThrowException(env, IndexOutOfBounds, "endIndex > available rows."); return false; } if (startIndex > endIndex) { - TR_ERR(env, "startIndex %" PRId64 " > endIndex %" PRId64 " - invalid!", S64(startIndex), S64(endIndex)) + realm::jni_util::Log::e( + "startIndex %1 > endIndex %2 - invalid!", S64(startIndex), S64(endIndex)); ThrowException(env, IndexOutOfBounds, "startIndex > endIndex."); return false; } if (range != -1 && range < 0) { - TR_ERR(env, "range %" PRId64 " < 0 - invalid!", S64(range)) + realm::jni_util::Log::e("range %1 < 0 - invalid!", S64(range)); ThrowException(env, IndexOutOfBounds, "range < 0."); return false; } @@ -301,7 +256,7 @@ inline bool RowIndexValid(JNIEnv* env, T pTable, jlong rowIndex, bool offset=fal size -= 1; bool rowErr = realm::util::int_greater_than_or_equal(rowIndex, size); if (rowErr) { - TR_ERR(env, "rowIndex %" PRId64 " > %" PRId64 " - invalid!", S64(rowIndex), S64(size)) + realm::jni_util::Log::e("rowIndex %1 > %2 - invalid!", S64(rowIndex), S64(size)); ThrowException(env, IndexOutOfBounds, "rowIndex > available rows: " + num_to_string(rowIndex) + " > " + num_to_string(size)); @@ -328,7 +283,8 @@ inline bool ColIndexValid(JNIEnv* env, T* pTable, jlong columnIndex) } bool colErr = realm::util::int_greater_than_or_equal(columnIndex, pTable->get_column_count()); if (colErr) { - TR_ERR(env, "columnIndex %" PRId64 " > %" PRId64 " - invalid!", S64(columnIndex), S64(pTable->get_column_count())) + realm::jni_util::Log::e( + "columnIndex %1 > %2 - invalid!", S64(columnIndex), S64(pTable->get_column_count())); ThrowException(env, IndexOutOfBounds, "columnIndex > available columns."); } return !colErr; @@ -370,7 +326,7 @@ inline bool TblIndexInsertValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong return false; bool rowErr = realm::util::int_greater_than(rowIndex, pTable->size()+1); if (rowErr) { - TR_ERR(env, "rowIndex %" PRId64 " > %" PRId64 " - invalid!", S64(rowIndex), S64(pTable->size())) + realm::jni_util::Log::e("rowIndex %1 > %2 - invalid!", S64(rowIndex), S64(pTable->size())); ThrowException(env, IndexOutOfBounds, "rowIndex " + num_to_string(rowIndex) + " > available rows " + num_to_string(pTable->size()) + "."); @@ -384,7 +340,7 @@ inline bool TypeValid(JNIEnv* env, T* pTable, jlong columnIndex, int expectColTy size_t col = static_cast(columnIndex); int colType = pTable->get_column_type(col); if (colType != expectColType) { - TR_ERR(env, "Expected columnType %d, but got %d.", expectColType, pTable->get_column_type(col)) + realm::jni_util::Log::e("Expected columnType %1, but got %2.", expectColType, pTable->get_column_type(col)); ThrowException(env, IllegalArgument, "ColumnType invalid."); return false; } @@ -400,7 +356,8 @@ inline bool TypeIsLinkLike(JNIEnv* env, T* pTable, jlong columnIndex) return true; } - TR_ERR(env, "Expected columnType %d or %d, but got %d", realm::type_Link, realm::type_LinkList, colType) + realm::jni_util::Log::e( + "Expected columnType %1 or %2, but got %3", realm::type_Link, realm::type_LinkList, colType); ThrowException(env, IllegalArgument, "ColumnType invalid: expected type_Link or type_LinkList"); return false; } @@ -423,7 +380,7 @@ inline bool ColIsNullable(JNIEnv* env, T* pTable, jlong columnIndex) return true; } - TR_ERR_NO_VA_ARG(env, "Expected nullable column type") + realm::jni_util::Log::e("Expected nullable column type"); ThrowException(env, IllegalArgument, "This field is not nullable."); return false; } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 632ce353f0..a6a6844345 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -58,7 +58,6 @@ import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.log.AndroidLogger; import io.realm.log.RealmLog; import rx.Observable; @@ -188,7 +187,6 @@ public static synchronized void init(Context context) { throw new IllegalArgumentException("Non-null context required."); } RealmCore.loadLibrary(context); - RealmLog.add(io.realm.BuildConfig.DEBUG ? new AndroidLogger(Log.DEBUG) : new AndroidLogger(Log.WARN)); defaultConfiguration = new RealmConfiguration.Builder(context).build(); ObjectServerFacade.getSyncFacadeIfPossible().init(context); BaseRealm.applicationContext = context.getApplicationContext(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 8674039cdb..d262452289 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -36,12 +36,6 @@ public static long getNativeMemUsage() { } static native long nativeGetMemUsage(); - // Set to level=1 to get some trace from JNI native part. - public static void setDebugLevel(int level) { - nativeSetDebugLevel(level); - } - static native void nativeSetDebugLevel(int level); - // Called by JNI. Do not remove static void javaPrint(String txt) { System.out.print(txt); diff --git a/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java b/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java index 9f460aeeed..d39f536486 100644 --- a/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java +++ b/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java @@ -36,6 +36,9 @@ * {@link LogLevel#OFF}Not supported. Remove the logger instead. * * + * + * @deprecated The new {@link RealmLogger} for Android is implemented in native code. This class will be removed in a + * future release. */ public class AndroidLogger implements Logger { diff --git a/realm/realm-library/src/main/java/io/realm/log/LogLevel.java b/realm/realm-library/src/main/java/io/realm/log/LogLevel.java index 8333656322..2811a67b93 100644 --- a/realm/realm-library/src/main/java/io/realm/log/LogLevel.java +++ b/realm/realm-library/src/main/java/io/realm/log/LogLevel.java @@ -22,7 +22,7 @@ * Realm uses the log levels defined by Log4J: * https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Level.html * - * @see RealmLog#add(Logger) + * @see RealmLog#add(RealmLogger) */ public class LogLevel { diff --git a/realm/realm-library/src/main/java/io/realm/log/Logger.java b/realm/realm-library/src/main/java/io/realm/log/Logger.java index 7da472b4a8..6e36fed33f 100644 --- a/realm/realm-library/src/main/java/io/realm/log/Logger.java +++ b/realm/realm-library/src/main/java/io/realm/log/Logger.java @@ -19,6 +19,7 @@ /** * Interface for custom loggers that can be registered at {@link RealmLog#add(Logger)}. * The different log levels are described in {@link LogLevel}. + * @deprecated Use {@link RealmLogger} instead. */ public interface Logger { diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index f83173214a..e16d1be4b6 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -16,48 +16,110 @@ package io.realm.log; -import java.util.ArrayList; -import java.util.List; +import android.util.Log; -import io.realm.internal.Keep; -import io.realm.internal.Util; +import java.util.IdentityHashMap; +import java.util.Map; /** * Global logger used by all Realm components. - * Custom loggers can be added by registering classes implementing {@link Logger}. + * Custom loggers can be added by registering classes implementing {@link RealmLogger}. */ -@Keep public final class RealmLog { - private static final Logger[] NO_LOGGERS = new Logger[0]; + @SuppressWarnings("FieldCanBeLocal") + private static String REALM_JAVA_TAG = "REALM_JAVA"; - // All of the below should be modified together under under a lock on LOGGERS. - private static final List LOGGERS = new ArrayList<>(); - private static volatile Logger[] loggersAsArray = NO_LOGGERS; - private static int minimumNativeLogLevel = Integer.MAX_VALUE; + /** + * To convert the old {@link Logger} to the new {@link RealmLogger}. + */ + private static class LoggerAdapter implements RealmLogger { + private Logger logger; + private static final Map loggerMap = new IdentityHashMap(); + + LoggerAdapter(Logger logger) { + this.logger = logger; + if (loggerMap.containsKey(logger)) { + throw new IllegalStateException(String.format("Logger %s exists in the map!", logger.toString())); + } + loggerMap.put(logger, this); + } + + static RealmLogger removeLogger(Logger logger) { + return loggerMap.remove(logger); + } + + static void clear() { + loggerMap.clear(); + } + + @Override + public void log(int level, String tag, Throwable throwable, String message) { + switch (level) { + case LogLevel.TRACE: + logger.trace(throwable, message); + break; + case LogLevel.INFO: + logger.info(throwable, message); + break; + case LogLevel.DEBUG: + logger.debug(throwable, message); + break; + case LogLevel.WARN: + logger.warn(throwable, message); + break; + case LogLevel.ERROR: + logger.error(throwable, message); + break; + case LogLevel.FATAL: + logger.fatal(throwable, message); + break; + default: + throw new IllegalArgumentException("Level: " + level + " cannot be logged."); + } + } + } /** * Adds a logger implementation that will be notified on log events. * - * @param logger the reference to a {@link Logger} implementation. + * @param logger the reference to a {@link RealmLogger} implementation. */ - public static void add(Logger logger) { + public static void add(RealmLogger logger) { if (logger == null) { throw new IllegalArgumentException("A non-null logger has to be provided"); } - synchronized (LOGGERS) { - LOGGERS.add(logger); - int minimumLogLevel = logger.getMinimumNativeDebugLevel(); - if (minimumLogLevel < minimumNativeLogLevel) { - setMinimumNativeDebugLevel(minimumLogLevel); - } - loggersAsArray = LOGGERS.toArray(new Logger[LOGGERS.size()]); + nativeAddLogger(logger); + } + + /** + * Adds a logger implementation that will be notified on log events. + * + * @param logger the reference to a {@link Logger} implementation. + * @deprecated use {@link #add(RealmLogger)} instead. + */ + public static void add(Logger logger) { + synchronized (LoggerAdapter.class) { + add(new LoggerAdapter(logger)); } } - private static void setMinimumNativeDebugLevel(int nativeDebugLevel) { - minimumNativeLogLevel = nativeDebugLevel; - Util.setDebugLevel(nativeDebugLevel); // Log level for Realm Core + /** + * Sets the current {@link LogLevel}. Setting this will affect all registered loggers. + * + * @param level see {@link LogLevel}. + */ + public static void setLevel(int level) { + nativeSetLogLevel(level); + } + + /** + * Get the current {@link LogLevel}. + * + * @return the current {@link LogLevel}. + */ + public static int getLevel() { + return nativeGetLogLevel(); } /** @@ -65,36 +127,52 @@ private static void setMinimumNativeDebugLevel(int nativeDebugLevel) { * * @return {@code true} if the logger was removed, {@code false} otherwise. */ - public static boolean remove(Logger logger) { + public static boolean remove(RealmLogger logger) { if (logger == null) { throw new IllegalArgumentException("A non-null logger has to be provided"); } - synchronized (LOGGERS) { - LOGGERS.remove(logger); - int newMinLevel = Integer.MAX_VALUE; - for (int i = 0; i < LOGGERS.size(); i++) { - int logMin = LOGGERS.get(i).getMinimumNativeDebugLevel(); - if (logMin < newMinLevel) { - newMinLevel = logMin; - } + nativeRemoveLogger(logger); + return true; + } + + /** + * Removes the given logger if it is currently added. + * + * @return {@code true} if the logger was removed, {@code false} otherwise. + * @deprecated use {@link #remove(RealmLogger)} instead. + */ + public static boolean remove(Logger logger) { + synchronized (LoggerAdapter.class) { + if (logger == null) { + throw new IllegalArgumentException("A non-null logger has to be provided"); + } + RealmLogger adaptor = LoggerAdapter.removeLogger(logger); + if (adaptor != null) { + nativeRemoveLogger(adaptor); } - setMinimumNativeDebugLevel(newMinLevel); - loggersAsArray = LOGGERS.toArray(new Logger[LOGGERS.size()]); } return true; } /** - * Remove all loggers. + * Removes all loggers. The default native logger will be removed as well. Use {@link #registerDefaultLogger()} to + * add it back. */ public static void clear() { - synchronized (LOGGERS) { - LOGGERS.clear(); - setMinimumNativeDebugLevel(Integer.MAX_VALUE); - loggersAsArray = NO_LOGGERS; + synchronized (LoggerAdapter.class) { + nativeClearLoggers(); + LoggerAdapter.clear(); } } + /** + * Adds default native logger if it has been removed before. If the default logger has been registered already, + * it won't be added again. The default logger on Android will log to logcat. + */ + public static void registerDefaultLogger() { + nativeRegisterDefaultLogger(); + } + /** * Logs a {@link LogLevel#TRACE} exception. * @@ -122,11 +200,7 @@ public static void trace(String message, Object... args) { * @param args optional args used to format the message using {@link String#format(String, Object...)}. */ public static void trace(Throwable throwable, String message, Object... args) { - Logger[] loggers = loggersAsArray; - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < loggers.length; i++) { - loggers[i].trace(throwable, message, args); - } + log(LogLevel.TRACE, throwable, message, args); } /** @@ -156,11 +230,7 @@ public static void debug(String message, Object... args) { * @param args optional args used to format the message using {@link String#format(String, Object...)}. */ public static void debug(Throwable throwable, String message, Object... args) { - Logger[] loggers = loggersAsArray; - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < loggers.length; i++) { - loggers[i].debug(throwable, message, args); - } + log(LogLevel.DEBUG, throwable, message, args); } /** @@ -190,11 +260,7 @@ public static void info(String message, Object... args) { * @param args optional args used to format the message using {@link String#format(String, Object...)}. */ public static void info(Throwable throwable, String message, Object... args) { - Logger[] loggers = loggersAsArray; - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < loggers.length; i++) { - loggers[i].info(throwable, message, args); - } + log(LogLevel.INFO, throwable, message, args); } /** @@ -224,11 +290,7 @@ public static void warn(String message, Object... args) { * @param args optional args used to format the message using {@link String#format(String, Object...)}. */ public static void warn(Throwable throwable, String message, Object... args) { - Logger[] loggers = loggersAsArray; - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < loggers.length; i++) { - loggers[i].warn(throwable, message, args); - } + log(LogLevel.WARN, throwable, message, args); } /** @@ -258,11 +320,7 @@ public static void error(String message, Object... args) { * @param args optional args used to format the message using {@link String#format(String, Object...)}. */ public static void error(Throwable throwable, String message, Object... args) { - Logger[] loggers = loggersAsArray; - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < loggers.length; i++) { - loggers[i].error(throwable, message, args); - } + log(LogLevel.ERROR, throwable, message, args); } /** @@ -292,10 +350,32 @@ public static void fatal(String message, Object... args) { * @param args optional args used to format the message using {@link String#format(String, Object...)}. */ public static void fatal(Throwable throwable, String message, Object... args) { - Logger[] loggers = loggersAsArray; - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < loggers.length; i++) { - loggers[i].fatal(throwable, message, args); + log(LogLevel.FATAL, throwable, message, args); + } + + // Format the message, parse the stacktrace of given throwable and pass them to nativeLog. + private static void log(int level, Throwable throwable, String message, Object... args) { + StringBuilder stringBuilder = new StringBuilder(); + if (args != null && args.length > 0) { + message = String.format(message, args); + } + if (throwable != null) { + stringBuilder.append(Log.getStackTraceString(throwable)); } + if (message != null) { + if (throwable != null) { + stringBuilder.append("\n"); + } + stringBuilder.append(message); + } + nativeLog(level,REALM_JAVA_TAG, throwable, stringBuilder.toString()); } + + private static native void nativeAddLogger(RealmLogger logger); + private static native void nativeRemoveLogger(RealmLogger logger); + private static native void nativeClearLoggers(); + private static native void nativeRegisterDefaultLogger(); + private static native void nativeLog(int level, String tag, Throwable throwable, String message); + private static native void nativeSetLogLevel(int level); + private static native int nativeGetLogLevel(); } diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java new file mode 100644 index 0000000000..844d2ddfd8 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.log; + +import io.realm.internal.KeepMember; + +/** + * Interface for custom loggers that can be registered at {@link RealmLog#add(RealmLogger)}. + * The different log levels are described in {@link LogLevel}. + */ +public interface RealmLogger { + + /** + * Handles a log event. + * + * @param level for this log event. It can only be a value between {@link LogLevel#TRACE} and + * {@link LogLevel#FATAL} + * @param tag for this log event. + * @param throwable optional exception to log. + * @param message optional additional message. + */ + @KeepMember + void log(int level, String tag, Throwable throwable, String message); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 57557ad888..59341b4fb7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -230,16 +230,7 @@ static void notifyUserLoggedOut(SyncUser user) { } } - /** - * Sets the log level for the underlying. - * @param logLevel - */ - public static void setLogLevel(int logLevel) { - nativeSetSyncClientLogLevel(logLevel); - } - private static native void nativeInitializeSyncClient(); - private static native void nativeSetSyncClientLogLevel(int logLevel); private static native void nativeRunClient(); } From 69fc405e0801f0dae7f6e7845e6c06dc47059499 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 25 Oct 2016 10:58:50 +0200 Subject: [PATCH 0180/2110] Upgrade to Sync BETA-3.2 (#3682) --- CHANGELOG.md | 2 +- README.md | 21 ++++++++++++++----- dependencies.list | 6 +++--- .../java/io/realm/objectserver/AuthTests.java | 4 ++-- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05a9d88fc9..bd2372d9ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ ### Internal * Upgraded Realm Core to 2.1.3 -* Upgraded Realm Sync to 1.0.0-BETA-3.1 +* Upgraded Realm Sync to 1.0.0-BETA-3.2 ### Credits diff --git a/README.md b/README.md index eb77162645..e552001903 100644 --- a/README.md +++ b/README.md @@ -164,19 +164,30 @@ The `./examples` folder contain a number of example projects showing how Realm c Standalone examples can be [downloaded from website](https://realm.io/docs/java/latest/#getting-started). -## Running testing Realm Object Server +## Running Tests Using The Realm Object Server -Tests in `syncIntegrationTest` require a running testing server to work. -A docker image can be built from `tools/sync_test_server/Dockerfile` to run a testing server. `tools/sync_test_server/start_server.sh` will build the docker image automatically. +Tests in `realm/realm-library/src/syncIntegrationTest` require a running testing server to work. +A docker image can be built from `tools/sync_test_server/Dockerfile` to run the test server. +`tools/sync_test_server/start_server.sh` will build the docker image automatically. To run a testing server locally: -a) Install docker. -b) run the `tools/sync_test_server/start_server.sh`: + +1. Install docker. + +2. Run `tools/sync_test_server/start_server.sh`: + ```sh cd tools/sync_test_server ./start_server.sh ``` +3. Run instrumentation tests: + +```sh +cd realm +./gradlew connectedObjectServerDebugAndroidTest +``` + ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for more details! diff --git a/dependencies.list b/dependencies.list index 441b3ce8eb..de35027e6e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.0-BETA-3.1 -REALM_SYNC_SHA256=43f65bde124589eff9f06ec548816776644fb80498b9aded1fc3b2fd2b9aff5f +REALM_SYNC_VERSION=1.0.0-BETA-3.2 +REALM_SYNC_SHA256=999f4fabe9f377ab03ced221e82317d6e02361da67e0a9928c66ddb56798e58e # Object Server Release used by Integration tests # https://packagecloud.io/realm/realm?filter=debs -REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-2.1-271 +REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-2.3-310 \ No newline at end of file diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index d4601a47f2..ccf997a3b8 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -45,7 +45,7 @@ public void login_userNotExist() { SyncUser.login(credentials, Constants.AUTH_URL); fail(); } catch (ObjectServerError expected) { - assertEquals(ErrorCode.UNKNOWN_ACCOUNT, expected.getErrorCode()); + assertEquals(ErrorCode.INVALID_CREDENTIALS, expected.getErrorCode()); } } @@ -61,7 +61,7 @@ public void onSuccess(SyncUser user) { @Override public void onError(ObjectServerError error) { - assertEquals(ErrorCode.UNKNOWN_ACCOUNT, error.getErrorCode()); + assertEquals(ErrorCode.INVALID_CREDENTIALS, error.getErrorCode()); looperThread.testComplete(); } }); From a232fe219f70cb1e12cd10b19ad9559942843231 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 25 Oct 2016 11:15:27 +0200 Subject: [PATCH 0181/2110] Grouping Object Server API changes under one header. --- CHANGELOG.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd2372d9ee..2bd97f49b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ ## 2.1.0 -### Breaking changes +### Object Server API Changes (In Beta) * Renamed `User` to `SyncUser`, `Credentials` to `SyncCredentials` and `Session` to `SyncSession` to align names with Cocoa. * Removed `SyncManager.setLogLevel()`. Use `RealmLog.setLevel()` instead. +* `SyncUser.logout()` now correctly clears `SyncUser.currentUser()` (#3638). +* Missing ProGuard configuration for libraries used by Sync extension (#3596). +* Error handler was not called when sync session failed (#3597). +* Added `User.all()` that returns all known Realm Object Server users. +* Upgraded Realm Sync to 1.0.0-BETA-3.2 ### Deprecated @@ -12,11 +17,8 @@ ### Bug fixes -* `SyncUser.logout()` now correctly clears `SyncUser.currentUser()` (#3638). -* Those were not kept by ProGuard: names of native methods not in the `io.realm.internal` package, names of classes used in method signature (#3596). -* Missing ProGuard configuration for libraries used by Sync extension (#3596). -* Error handler was not called when sync session failed (#3597). -* Permission error when a database file is located at external storage (#3140). +* The following were not kept by ProGuard: names of native methods not in the `io.realm.internal` package, names of classes used in method signature (#3596). +* Permission error when a database file was located on external storage (#3140). * Memory leak when unsubscribing from a RealmResults/RealmObject RxJava Observable (#3552). ### Enhancement @@ -24,12 +26,11 @@ * `Realm.compactRealm()` now works for encrypted Realms. * Added `first(E defaultValue)` and `last(E defaultValue)` methods to `RealmList` and `RealmResult`. These methods will return the provided object instead of throwing an `IndexOutOfBoundsException` if the list is empty. * Reduce transformer logger verbosity (#3608). -* Added `User.all()` that returns all known Realm Object Server users. +* `RealmLog.setLevel(int)` for setting the log level across all loggers. ### Internal * Upgraded Realm Core to 2.1.3 -* Upgraded Realm Sync to 1.0.0-BETA-3.2 ### Credits From 4fbc70e803c7c72ae6994b2dde766075bc6604bd Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 25 Oct 2016 11:17:16 +0200 Subject: [PATCH 0182/2110] Release v2.1.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 19d5f5f9c6..50aea0e7ab 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.1.0-SNAPSHOT \ No newline at end of file +2.1.0 \ No newline at end of file From dbc23ff9e1883ed50f87b78e956b3f2640176d02 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 25 Oct 2016 11:17:16 +0200 Subject: [PATCH 0183/2110] Prepare next release v2.1.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 50aea0e7ab..354105cd9b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.1.0 \ No newline at end of file +2.1.1-SNAPSHOT \ No newline at end of file From b89d1fe2e95a6e0ffb86add1bad64edb3f62d46d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 25 Oct 2016 14:21:19 +0200 Subject: [PATCH 0184/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 354105cd9b..31941db520 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.1.1-SNAPSHOT \ No newline at end of file +2.2.0-SNAPSHOT From 1f9319d3fdce1cf49c36804f5b1281a19012525c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 26 Oct 2016 08:45:26 -0500 Subject: [PATCH 0185/2110] Remove env for TR_ENTER (#3699) --- .../src/main/cpp/io_realm_Property.cpp | 6 +- .../main/cpp/io_realm_RealmObjectSchema.cpp | 10 ++-- .../src/main/cpp/io_realm_RealmSchema.cpp | 6 +- .../src/main/cpp/io_realm_SyncManager.cpp | 2 +- .../main/cpp/io_realm_internal_LinkView.cpp | 32 +++++----- .../cpp/io_realm_internal_SharedRealm.cpp | 58 +++++++++---------- .../src/main/cpp/io_realm_internal_Table.cpp | 4 +- .../main/cpp/io_realm_internal_TableQuery.cpp | 26 ++++----- .../main/cpp/io_realm_internal_TableView.cpp | 4 +- .../cpp/io_realm_internal_UncheckedRow.cpp | 56 +++++++++--------- ...ernal_objectserver_ObjectServerSession.cpp | 10 ++-- .../src/main/cpp/jni_util/log.hpp | 7 +-- realm/realm-library/src/main/cpp/util.hpp | 2 - 13 files changed, 110 insertions(+), 113 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_Property.cpp b/realm/realm-library/src/main/cpp/io_realm_Property.cpp index a59b0cd890..b107982b07 100644 --- a/realm/realm-library/src/main/cpp/io_realm_Property.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_Property.cpp @@ -29,7 +29,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2IZZZ(JNIEnv *env, jclass, jstring name_, jint type, jboolean is_primary, jboolean is_indexed, jboolean is_nullable) { - TR_ENTER(env) + TR_ENTER() try { JStringAccessor str(env, name_); PropertyType p_type = static_cast(static_cast(type)); @@ -52,7 +52,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2ILjava_lang_String_2(JNIEnv *env, jclass, jstring name_, jint type, jstring linkedToName_) { - TR_ENTER(env) + TR_ENTER() try { JStringAccessor name(env, name_); JStringAccessor link_name(env, linkedToName_); @@ -67,7 +67,7 @@ Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2ILjava_lang_Stri JNIEXPORT void JNICALL Java_io_realm_Property_nativeClose(JNIEnv *env, jclass, jlong property_ptr) { - TR_ENTER_PTR(env, property_ptr) + TR_ENTER_PTR(property_ptr) try { Property *property = reinterpret_cast(property_ptr); delete property; diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp index 5e3233e9a0..a2227dacad 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp @@ -25,7 +25,7 @@ using namespace realm; JNIEXPORT jlong JNICALL Java_io_realm_RealmObjectSchema_nativeCreateRealmObjectSchema(JNIEnv *env, jclass, jstring className_) { - TR_ENTER(env) + TR_ENTER() try { JStringAccessor name(env, className_); ObjectSchema *object_schema = new ObjectSchema(); @@ -38,7 +38,7 @@ Java_io_realm_RealmObjectSchema_nativeCreateRealmObjectSchema(JNIEnv *env, jclas JNIEXPORT void JNICALL Java_io_realm_RealmObjectSchema_nativeClose(JNIEnv *env, jclass, jlong native_ptr) { - TR_ENTER_PTR(env, native_ptr) + TR_ENTER_PTR(native_ptr) try { ObjectSchema* object_schema = reinterpret_cast(native_ptr); delete object_schema; @@ -49,7 +49,7 @@ Java_io_realm_RealmObjectSchema_nativeClose(JNIEnv *env, jclass, jlong native_pt JNIEXPORT void JNICALL Java_io_realm_RealmObjectSchema_nativeAddProperty(JNIEnv *env, jclass, jlong native_ptr, jlong property_ptr) { - TR_ENTER_PTR(env, native_ptr) + TR_ENTER_PTR(native_ptr) try { ObjectSchema* object_schema = reinterpret_cast(native_ptr); Property* property = reinterpret_cast(property_ptr); @@ -63,7 +63,7 @@ Java_io_realm_RealmObjectSchema_nativeAddProperty(JNIEnv *env, jclass, jlong nat JNIEXPORT jstring JNICALL Java_io_realm_RealmObjectSchema_nativeGetClassName(JNIEnv *env, jclass, jlong nativePtr) { - TR_ENTER_PTR(env, nativePtr) + TR_ENTER_PTR(nativePtr) try { ObjectSchema* object_schema = reinterpret_cast(nativePtr); auto name = object_schema->name; @@ -76,7 +76,7 @@ Java_io_realm_RealmObjectSchema_nativeGetClassName(JNIEnv *env, jclass, jlong na JNIEXPORT jlongArray JNICALL Java_io_realm_RealmObjectSchema_nativeGetProperties(JNIEnv *env, jclass, jlong nativePtr) { - TR_ENTER_PTR(env, nativePtr) + TR_ENTER_PTR(nativePtr) try { ObjectSchema* object_schema = reinterpret_cast(nativePtr); size_t size = object_schema->persisted_properties.size(); diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp index 8cb7f7daf1..98649caff4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp @@ -27,7 +27,7 @@ using namespace realm; JNIEXPORT jlong JNICALL Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv *env, jclass, jlongArray objectSchemaPtrs_) { - TR_ENTER(env) + TR_ENTER() try { std::vector object_schemas; JniLongArray array(env, objectSchemaPtrs_); @@ -44,14 +44,14 @@ Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv *env, jclass, jlongArray o JNIEXPORT void JNICALL Java_io_realm_RealmSchema_nativeClose(JNIEnv *env, jclass, jlong nativePtr) { - TR_ENTER_PTR(env, nativePtr) + TR_ENTER_PTR(nativePtr) Schema* schema = reinterpret_cast(nativePtr); delete schema; } JNIEXPORT jlongArray JNICALL Java_io_realm_RealmSchema_nativeGetAll(JNIEnv *env, jclass, jlong nativePtr) { - TR_ENTER_PTR(env, nativePtr) + TR_ENTER_PTR(nativePtr) try { Schema* schema = reinterpret_cast(nativePtr); size_t size = schema->size(); diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index 3486d9e4dc..e7da1e400c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -54,7 +54,7 @@ static void error_handler(int error_code, std::string message) JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient (JNIEnv *env, jclass sync_manager_class) { - TR_ENTER(env) + TR_ENTER() if (sync_client) return; try { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp index d1c4b5c580..52079074e5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp @@ -29,7 +29,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeClose JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetRow (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return -1; @@ -46,7 +46,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetRow JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetRowIndex (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong linkViewIndex) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, linkViewIndex)) { return -1; @@ -62,7 +62,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetRowIndex JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeAdd (JNIEnv* env, jclass, jlong nativeLinkViewPtr, jlong rowIndex) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); try { LinkViewRef lvr = *lv; @@ -74,7 +74,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeAdd JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeInsert (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos, jlong rowIndex) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); try { LinkViewRef lvr = *lv; @@ -86,7 +86,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeInsert JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeSet (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos, jlong rowIndex) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return; @@ -101,7 +101,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeSet JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeMove (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong old_pos, jlong new_pos) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -120,7 +120,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeMove JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemove (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) LinkViewRef *lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return; @@ -135,7 +135,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemove JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeClear (JNIEnv* env, jclass, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -148,7 +148,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeSize (JNIEnv* env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -161,7 +161,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeSize JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsEmpty (JNIEnv* env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -173,7 +173,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsEmpty JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeWhere (JNIEnv *env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -186,7 +186,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeWhere JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsAttached (JNIEnv *env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -198,7 +198,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsAttached JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeFind (JNIEnv *env, jobject, jlong nativeLinkViewPtr, jlong targetRowIndex) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) try { LinkViewRef *lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -214,7 +214,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeFind JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveAllTargetRows (JNIEnv *env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) try { LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -225,7 +225,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveAllTargetRows JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable (JNIEnv* env, jobject, jlong nativeLinkViewPtr) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; @@ -238,7 +238,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveTargetRow (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) { - TR_ENTER_PTR(env, nativeLinkViewPtr) + TR_ENTER_PTR(nativeLinkViewPtr) LinkViewRef* lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 5a01e10ac0..6d200de454 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -27,7 +27,7 @@ static_assert(SchemaMode::Manual == JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv *env, jclass, jstring temporary_directory_path) { - TR_ENTER(env) + TR_ENTER() try { JStringAccessor path(env, temporary_directory_path); // throws @@ -40,7 +40,7 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri jbyte schema_mode, jboolean in_memory, jboolean cache, jboolean disable_format_upgrade, jboolean auto_change_notification, jstring sync_server_url, jstring sync_user_token) { - TR_ENTER(env) + TR_ENTER() try { JStringAccessor path(env, realm_path); // throws @@ -71,7 +71,7 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv* env, jclass, jlong config_ptr) { - TR_ENTER_PTR(env, config_ptr) + TR_ENTER_PTR(config_ptr) auto config = reinterpret_cast(config_ptr); delete config; @@ -80,7 +80,7 @@ Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv* env, jclass, jlong JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr, jobject notifier) { - TR_ENTER_PTR(env, config_ptr) + TR_ENTER_PTR(config_ptr) auto config = reinterpret_cast(config_ptr); try { @@ -96,7 +96,7 @@ Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlo JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto ptr = reinterpret_cast(shared_realm_ptr); delete ptr; @@ -105,7 +105,7 @@ Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv* env, jclass, j JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -116,7 +116,7 @@ Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv *env, jclass, j JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -127,7 +127,7 @@ Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv *env, jclass, JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -139,7 +139,7 @@ Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv *env, jclass, JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); return static_cast(shared_realm->is_in_transaction()); @@ -148,7 +148,7 @@ Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv* env, jclass, jl JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv *env, jclass , jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -161,7 +161,7 @@ Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv *env, jclass , jlong s JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -172,7 +172,7 @@ Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong s JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv *env, jclass, jlong shared_realm_ptr, jlong version) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -190,7 +190,7 @@ Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv *env, jclass, jlong s JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -202,7 +202,7 @@ Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv *env, jclass, jlong shar JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRefresh__J(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -214,7 +214,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRefresh__JJJ(JNIEnv *env, jclass, jlong shared_realm_ptr, jlong version, jlong index) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); SharedGroup::VersionID version_id(static_cast(version), @@ -229,7 +229,7 @@ Java_io_realm_internal_SharedRealm_nativeRefresh__JJJ(JNIEnv *env, jclass, jlong JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -256,7 +256,7 @@ Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); return static_cast(shared_realm->is_closed()); @@ -266,7 +266,7 @@ Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv* env, jclass, jlong sha JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) try { JStringAccessor name(env, table_name); // throws @@ -288,7 +288,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv *env, jclass, jlong shared_realm_ptr, jint index) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -300,7 +300,7 @@ Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv *env, jclass, jlong JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -314,7 +314,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring old_table_name, jstring new_table_name) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -333,7 +333,7 @@ Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv *env, jclass, jlong JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -351,7 +351,7 @@ Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv *env, jclass, jlong JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr) + TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -365,7 +365,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring path, jbyteArray key) { - TR_ENTER_PTR(env, shared_realm_ptr); + TR_ENTER_PTR(shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -378,7 +378,7 @@ Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv *env, jclass, jlong sh JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr); + TR_ENTER_PTR(shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -392,7 +392,7 @@ Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv *env, jclass, jlon JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr); + TR_ENTER_PTR(shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -404,7 +404,7 @@ Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(env, shared_realm_ptr); + TR_ENTER_PTR(shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { @@ -417,7 +417,7 @@ Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv *env, jclass, jlong shar JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, jlong sharedRealmPtr) { - TR_ENTER_PTR(env, sharedRealmPtr) + TR_ENTER_PTR(sharedRealmPtr) auto shared_realm = *(reinterpret_cast(sharedRealmPtr)); try { @@ -431,7 +431,7 @@ Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv *env, jclass, jlong nativePtr, jlong nativeSchemaPtr, jlong version) { - TR_ENTER(env) + TR_ENTER() try { auto shared_realm = *(reinterpret_cast(nativePtr)); auto *schema = reinterpret_cast(nativeSchemaPtr); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 31bbca5e1b..6d1aac6c62 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1408,13 +1408,13 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid( JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClose( JNIEnv* env, jclass, jlong nativeTablePtr) { - TR_ENTER_PTR(env, nativeTablePtr) + TR_ENTER_PTR(nativeTablePtr) LangBindHelper::unbind_table_ptr(TBL(nativeTablePtr)); } JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_createNative(JNIEnv *env, jobject) { - TR_ENTER(env) + TR_ENTER() try { return reinterpret_cast(LangBindHelper::new_table()); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index d5ca832903..b5e89f153b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -47,7 +47,7 @@ const char* ERR_SORT_NOT_SUPPORTED = "Sort is not supported on binary data, obje //------------------------------------------------------- JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeClose(JNIEnv* env, jclass, jlong nativeQueryPtr) { - TR_ENTER_PTR(env, nativeQueryPtr) + TR_ENTER_PTR(nativeQueryPtr) delete Q(nativeQueryPtr); } @@ -87,7 +87,7 @@ static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& indicesArray static jlong findAllWithHandover(JNIEnv* env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong start, jlong end, jlong limit) { - TR_ENTER(env) + TR_ENTER() TableRef table = query.get()->get_table(); if (!QUERY_VALID(env, query.get()) || !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) { @@ -1100,7 +1100,7 @@ static std::unique_ptr handoverQueryToWorker(jlong bgSharedRealmPtr, jlon JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindWithHandover( JNIEnv* env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong fromTableRow) { - TR_ENTER(env) + TR_ENTER() try { std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, false); // throws TableRef table = query->get_table(); @@ -1136,7 +1136,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindWithHandover JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll( JNIEnv* env, jobject, jlong nativeQueryPtr, jlong start, jlong end, jlong limit) { - TR_ENTER(env) + TR_ENTER() Query* query = Q(nativeQueryPtr); TableRef table = query->get_table(); if (!QUERY_VALID(env, query) || @@ -1153,7 +1153,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll( JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllWithHandover (JNIEnv* env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit) { - TR_ENTER(env) + TR_ENTER() try { std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws return findAllWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit); @@ -1174,7 +1174,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate jobjectArray multi_sorted_indices_matrix, jobjectArray multi_sorted_order_matrix) { - TR_ENTER(env) + TR_ENTER() try { JniLongArray handover_queries_pointer_array(env, handover_queries_array); @@ -1290,7 +1290,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetDistinctViewWithHandover (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong columnIndex) { - TR_ENTER(env) + TR_ENTER() try { std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws return getDistinctViewWithHandover(env, bgSharedRealmPtr, std::move(query), columnIndex); @@ -1301,7 +1301,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetDistinctViewW JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllSortedWithHandover (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlong columnIndex, jboolean ascending) { - TR_ENTER(env) + TR_ENTER() try { std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws return findAllSortedWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit, columnIndex, ascending); @@ -1312,7 +1312,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllSortedWit JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllMultiSortedWithHandover (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlongArray columnIndices, jbooleanArray ascending) { - TR_ENTER(env) + TR_ENTER() try { // import the handover query pointer using the background SharedRealm std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws @@ -1710,7 +1710,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull( JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverTableViewIntoSharedGroup (JNIEnv *env, jobject, jlong handoverPtr, jlong callerSharedGrpPtr) { - TR_ENTER_PTR(env, handoverPtr) + TR_ENTER_PTR(handoverPtr) SharedGroup::Handover *handoverTableViewPtr = HO(TableView, handoverPtr); std::unique_ptr> handoverTableView(handoverTableViewPtr); try { @@ -1730,7 +1730,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverTa JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRowIntoSharedGroup (JNIEnv *env, jclass, jlong handoverPtr, jlong callerSharedGrpPtr) { - TR_ENTER_PTR(env, handoverPtr) + TR_ENTER_PTR(handoverPtr) SharedGroup::Handover *handoverRowPtr = HO(Row, handoverPtr); std::unique_ptr> handoverRow(handoverRowPtr); @@ -1751,7 +1751,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRo JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery (JNIEnv* env, jobject, jlong bgSharedRealmPtr, jlong nativeQueryPtr) { - TR_ENTER_PTR(env, nativeQueryPtr) + TR_ENTER_PTR(nativeQueryPtr) Query* pQuery = Q(nativeQueryPtr); if (!QUERY_VALID(env, pQuery)) return 0; @@ -1768,7 +1768,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeCloseQueryHandover (JNIEnv* env, jclass, jlong nativeHandoverQuery) { - TR_ENTER_PTR(env, nativeHandoverQuery) + TR_ENTER_PTR(nativeHandoverQuery) delete HO(Query, nativeHandoverQuery); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp index 7c4b5aee76..8d9aa78bd5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp @@ -945,7 +945,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_TableView_nativeToJson( JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeWhere( JNIEnv *env, jobject, jlong nativeViewPtr) { - TR_ENTER_PTR(env, nativeViewPtr) + TR_ENTER_PTR(nativeViewPtr) try { if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) return 0; @@ -975,7 +975,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSyncIfNeeded( JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindBySourceNdx (JNIEnv *env, jobject, jlong nativeViewPtr, jlong sourceIndex) { - TR_ENTER_PTR(env, nativeViewPtr); + TR_ENTER_PTR(nativeViewPtr); try { if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !ROW_INDEX_VALID(env, &(TV(nativeViewPtr)->get_parent()), sourceIndex)) return -1; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 8bde98aeed..6ee249c77e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -22,7 +22,7 @@ using namespace realm; JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount (JNIEnv *env, jobject, jlong nativeRowPtr) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW(nativeRowPtr)->is_attached()) return 0; @@ -32,7 +32,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnName (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -45,7 +45,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnNam JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex (JNIEnv* env, jobject, jlong nativeRowPtr, jstring columnName) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW(nativeRowPtr)->is_attached()) return 0; @@ -59,14 +59,14 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) return static_cast( ROW(nativeRowPtr)->get_column_type( S(columnIndex)) ); // noexcept } JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetIndex (JNIEnv* env, jobject, jlong nativeRowPtr) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -76,7 +76,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetIndex JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLong (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -86,7 +86,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLong JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeGetBoolean (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -96,7 +96,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeGetBoolean JNIEXPORT jfloat JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFloat (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -106,7 +106,7 @@ JNIEXPORT jfloat JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFloat JNIEXPORT jdouble JNICALL Java_io_realm_internal_UncheckedRow_nativeGetDouble (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -116,7 +116,7 @@ JNIEXPORT jdouble JNICALL Java_io_realm_internal_UncheckedRow_nativeGetDouble JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetTimestamp (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -126,7 +126,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetTimestamp JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetString (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -140,7 +140,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetString JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteArray (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -163,7 +163,7 @@ JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteAr JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLink (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -176,7 +176,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLink JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNullLink (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -186,7 +186,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNullLink JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLinkView (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return 0; @@ -197,7 +197,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLinkView JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLong (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jlong value) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -209,7 +209,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLong JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetBoolean (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jboolean value) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -221,7 +221,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetBoolean JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetFloat (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jfloat value) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -233,7 +233,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetFloat JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDouble (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jdouble value) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -245,7 +245,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDouble JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetTimestamp (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jlong value) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -257,7 +257,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetTimestamp JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetString (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jstring value) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -274,7 +274,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetString JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jbyteArray value) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -307,7 +307,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLink (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jlong value) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -319,7 +319,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLink JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; @@ -331,14 +331,14 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeClose (JNIEnv* env, jclass, jlong nativeRowPtr) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) delete ROW(nativeRowPtr); } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsAttached (JNIEnv* env, jobject, jlong nativeRowPtr) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) return ROW(nativeRowPtr)->is_attached(); } @@ -351,13 +351,13 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeHasColumn JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) return ROW(nativeRowPtr)->is_null(columnIndex); } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetNull (JNIEnv *env, jobject, jlong nativeRowPtr, jlong columnIndex) { - TR_ENTER_PTR(env, nativeRowPtr) + TR_ENTER_PTR(nativeRowPtr) if (!ROW_VALID(env, ROW(nativeRowPtr))) return; if (!TBL_AND_COL_NULLABLE(env, ROW(nativeRowPtr)->get_table(), columnIndex)) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp index 469c30b376..ea0ebecc4a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp @@ -40,7 +40,7 @@ using namespace sync; JNIEXPORT jlong JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeCreateSession (JNIEnv *env, jobject obj, jstring localRealmPath) { - TR_ENTER(env) + TR_ENTER() try { JStringAccessor local_path(env, localRealmPath); JniSession* jni_session = new JniSession(env, local_path, obj); @@ -52,7 +52,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeBind (JNIEnv *env, jobject, jlong sessionPointer, jstring remoteUrl, jstring accessToken) { - TR_ENTER(env) + TR_ENTER() try { auto *session_wrapper = reinterpret_cast(sessionPointer); @@ -72,7 +72,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_n JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeUnbind (JNIEnv *env, jobject, jlong sessionPointer) { - TR_ENTER(env) + TR_ENTER() JniSession* session = SS(sessionPointer); session->close(env); delete session; // TODO Can we avoid killing the session here? @@ -81,7 +81,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_n JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeRefresh (JNIEnv *env, jobject, jlong sessionPointer, jstring accessToken) { - TR_ENTER(env) + TR_ENTER() try { JniSession* session_wrapper = SS(sessionPointer); @@ -96,7 +96,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeNotifyCommitHappened (JNIEnv *env, jobject, jlong sessionPointer, jlong version) { - TR_ENTER(env) + TR_ENTER() try { JniSession* session_wrapper = SS(sessionPointer); session_wrapper->get_session()->nonsync_transact_notify(version); diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp index bed5925c46..ad29c6ede7 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -29,14 +29,13 @@ #include "realm/util/logger.hpp" #include "util/format.hpp" -// FIXME: env is not needed any more. Will remove it in another PR. -#define TR_ENTER(env) \ +#define TR_ENTER() \ if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ realm::jni_util::Log::t(" --> %1", __FUNCTION__); \ } -#define TR_ENTER_PTR(env, ptr) \ +#define TR_ENTER_PTR(ptr) \ if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ - realm::jni_util::Log::t(" --> %1 %2" PRId64, __FUNCTION__, static_cast(ptr)); \ + realm::jni_util::Log::t(" --> %1 %2", __FUNCTION__, static_cast(ptr)); \ } namespace realm { diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 754ebc12a2..68b9a89ee6 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -24,7 +24,6 @@ #include // Used by logging -#define __STDC_FORMAT_MACROS #include #include @@ -40,7 +39,6 @@ #include "jni_util/log.hpp" -#define TRACE 1 // disable for performance #define CHECK_PARAMETERS 1 // Check all parameters in API and throw exceptions in java if invalid #ifdef __cplusplus From c4a4edb1325b4ebe807267c7ffc871df0f3d7cdc Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 27 Oct 2016 14:56:50 +0900 Subject: [PATCH 0186/2110] Set default RxFactory in SyncConfiguration when RxJava is available. (#3695) * Set default RxFactory in SyncConfiguration when RxJava is available. * update CHANGELOG --- CHANGELOG.md | 6 ++++++ .../java/io/realm/SyncConfigurationTests.java | 9 +++++++++ .../src/main/java/io/realm/RealmConfiguration.java | 2 +- .../objectServer/java/io/realm/SyncConfiguration.java | 4 ++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bd97f49b5..7c5abf27c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.1.1 + +### Object Server API Changes (In Beta) + +* Set default RxFactory to `SyncConfiguration`. + ## 2.1.0 ### Object Server API Changes (In Beta) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index d9f20f456c..f4d102aa2e 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -382,4 +382,13 @@ public void execute(Realm realm) { assertEquals(1, realm2.where(StringOnly.class).count()); realm2.close(); } + + @Test + public void defaultRxFactory() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + + assertNotNull(config.getRxFactory()); + } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 06775f7abc..7cd6d6fcd4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -337,7 +337,7 @@ public String toString() { * * @return {@code true} if RxJava dependency exist, {@code false} otherwise. */ - private static synchronized boolean isRxJavaAvailable() { + static synchronized boolean isRxJavaAvailable() { if (rxJavaAvailable == null) { try { Class.forName("rx.Observable"); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 07583eb63d..d268398d76 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -561,6 +561,10 @@ public SyncConfiguration build() { " access token. Use a path without /~/."); } + if (rxFactory == null && isRxJavaAvailable()) { + rxFactory = new RealmObservableFactory(); + } + // Determine location on disk // Use the serverUrl + user to create a unique filepath unless it has been explicitly overridden. // // From 5c2cc8736bab193634bcb338fe5464703b673ac3 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 27 Oct 2016 16:14:00 +0900 Subject: [PATCH 0187/2110] fix a bug that proguard configuration keeps all class names. (#3693) * fix a bug that proguard configuration keeps all class names. * Update CHANGELOG.md * update changelog * update javadoc comment --- CHANGELOG.md | 4 ++++ realm/realm-library/proguard-rules-common.pro | 2 +- .../src/main/java/io/realm/internal/KeepMember.java | 5 +++-- .../src/main/java/io/realm/log/RealmLogger.java | 1 + .../io/realm/internal/objectserver/ObjectServerSession.java | 1 + 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c5abf27c9..92c4625bc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ * Set default RxFactory to `SyncConfiguration`. +### Bug fixes + +* ProGuard configuration introduced in 2.1.0 unexpectedly kept classes that did not have the @KeepMember annotation (#3689). + ## 2.1.0 ### Object Server API Changes (In Beta) diff --git a/realm/realm-library/proguard-rules-common.pro b/realm/realm-library/proguard-rules-common.pro index 2f8dfc843a..e4bb7abb4f 100644 --- a/realm/realm-library/proguard-rules-common.pro +++ b/realm/realm-library/proguard-rules-common.pro @@ -5,7 +5,7 @@ -keep,includedescriptorclasses @io.realm.internal.Keep class * { *; } -keep class io.realm.internal.KeepMember --keep,includedescriptorclasses class * { @io.realm.internal.KeepMember *; } +-keep,includedescriptorclasses @io.realm.internal.KeepMember class * { @io.realm.internal.KeepMember *; } -dontwarn javax.** -dontwarn io.realm.** diff --git a/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java b/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java index 43a810bb5c..e1308a9f21 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java +++ b/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java @@ -24,9 +24,10 @@ /** * This annotation is used to mark the fields and methods to be kept by ProGuard/DexGuard. * The ProGuard configuration must have '-keep class io.realm.internal.KeepMember' - * and '-keep,includedescriptorclasses class * { @io.realm.internal.KeepMember *; }'. + * and '-keep,includedescriptorclasses @io.realm.internal.KeepMember class * { @io.realm.internal.KeepMember *; }'. + * This annotation must be added to class as well to work. */ @Retention(RetentionPolicy.CLASS) -@Target({ElementType.METHOD,ElementType.FIELD}) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) public @interface KeepMember { } diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java index 844d2ddfd8..f94c7859d6 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java @@ -22,6 +22,7 @@ * Interface for custom loggers that can be registered at {@link RealmLog#add(RealmLogger)}. * The different log levels are described in {@link LogLevel}. */ +@KeepMember public interface RealmLogger { /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java index 0453391016..a0e4802df3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java @@ -87,6 +87,7 @@ * * This object is thread safe. */ +@KeepMember public final class ObjectServerSession { private final HashMap FSM = new HashMap(); From 12db1c7a0d2d93ae2ba0b591b22d73135252ad3a Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 27 Oct 2016 16:24:02 +0900 Subject: [PATCH 0188/2110] Release v2.1.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 354105cd9b..7c32728738 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.1.1-SNAPSHOT \ No newline at end of file +2.1.1 \ No newline at end of file From d1220b2d772bfef9afde85d2276964b5c439adfa Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 27 Oct 2016 16:24:02 +0900 Subject: [PATCH 0189/2110] Prepare next release v2.1.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7c32728738..bdedd4f4a2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.1.1 \ No newline at end of file +2.1.2-SNAPSHOT \ No newline at end of file From 19e874dbfa0f10755531291a12d6b2195cef58df Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 27 Oct 2016 06:02:07 -0500 Subject: [PATCH 0190/2110] Workaround for jni headers path in AS (#3706) See https://github.com/googlesamples/android-ndk/issues/319 --- realm/realm-library/src/main/cpp/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index b45fb5a4dd..8d9ce2b0ad 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -39,7 +39,9 @@ set(classes_LIST io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema io.realm.RealmObjectSchema ) -set(jni_headers_PATH ${PROJECT_BINARY_DIR}/jni_include) +# /./ is the workaround for the problem that AS cannot find the jni headers. +# See https://github.com/googlesamples/android-ndk/issues/319 +set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST io.realm.SyncManager io.realm.internal.objectserver.ObjectServerSession) From 2bae2c9d11620135410085452477195011f57212 Mon Sep 17 00:00:00 2001 From: mansonheart Date: Fri, 28 Oct 2016 12:34:20 +0400 Subject: [PATCH 0191/2110] Remove excess comment (#3712) --- realm/realm-library/src/main/java/io/realm/BaseRealm.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index e09226f3f6..c61b2cefae 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -60,7 +60,7 @@ abstract class BaseRealm implements Closeable { private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; - // Thread pool for all async operations (Query & transaction) + volatile static Context applicationContext; // Thread pool for all async operations (Query & transaction) From d5296f9ad400171c34e3818bd9258ac043c9cbc5 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 28 Oct 2016 21:27:55 +0900 Subject: [PATCH 0192/2110] fix typo (IMCOMPATIBLE to INCOMPATIBLE) (#3718) --- realm/realm-library/src/main/cpp/util.cpp | 2 +- .../src/main/java/io/realm/exceptions/RealmFileException.java | 2 +- .../src/main/java/io/realm/internal/SharedRealm.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 26bb5e9094..1ba8e3e2b1 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -180,7 +180,7 @@ void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::Rea kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_NOT_FOUND; break; case realm::RealmFileException::Kind::IncompatibleLockFile: - kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_IMCOMPATIBLE_LOCK_FILE; + kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE; break; case realm::RealmFileException::Kind::FormatUpgradeRequired: kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED; diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java index a12f94368a..5c7ba906d2 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java @@ -65,7 +65,7 @@ static Kind getKind(byte value) { return EXISTS; case SharedRealm.FILE_EXCEPTION_KIND_NOT_FOUND: return NOT_FOUND; - case SharedRealm.FILE_EXCEPTION_KIND_IMCOMPATIBLE_LOCK_FILE: + case SharedRealm.FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE: return INCOMPATIBLE_LOCK_FILE; case SharedRealm.FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED: return FORMAT_UPGRADE_REQUIRED; diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index c3fab6d786..e7ad6c0417 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -30,7 +30,7 @@ public final class SharedRealm implements Closeable { public static final byte FILE_EXCEPTION_KIND_PERMISSION_DENIED = 1; public static final byte FILE_EXCEPTION_KIND_EXISTS = 2; public static final byte FILE_EXCEPTION_KIND_NOT_FOUND = 3; - public static final byte FILE_EXCEPTION_KIND_IMCOMPATIBLE_LOCK_FILE = 4; + public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 4; public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 5; public static void initialize(File tempDirectory) { From 36088927897fc5a52005de9cca626420852c6242 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 31 Oct 2016 20:37:35 +0100 Subject: [PATCH 0193/2110] Enable lcache on CI (#3717) --- Dockerfile | 5 +++++ Jenkinsfile | 1 + 2 files changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index 3fa5aac87f..b3a8820a23 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ ENV ANDROID_NDK /opt/android-ndk ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} ENV NDK_CCACHE /usr/bin/ccache +ENV NDK_LCACHE /usr/bin/lcache # The 32 bit binaries because aapt requires it # `file` is need by the script that creates NDK toolchains @@ -72,3 +73,7 @@ RUN mkdir /opt/cmake-tmp && \ # Make the SDK universally readable RUN chmod -R a+rX ${ANDROID_HOME} + +# Install lcache +RUN wget -q https://github.com/beeender/lcache/releases/download/v0.0.2/lcache-linux -O /usr/bin/lcache && \ + chmod +x /usr/bin/lcache diff --git a/Jenkinsfile b/Jenkinsfile index 5ee2d7cef1..4e73462a90 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -44,6 +44,7 @@ try { "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + "-v ${env.HOME}/.android:/tmp/.android " + "-v ${env.HOME}/ccache:/tmp/.ccache " + + "-v ${env.HOME}/lcache:/tmp/.lcache " + "--network container:ros") { stage('JVM tests') { try { From ac4831f9dcc6fa8dc1c5ac3d179e56ef6881d442 Mon Sep 17 00:00:00 2001 From: octarino Date: Tue, 1 Nov 2016 15:58:10 +0000 Subject: [PATCH 0194/2110] Minor grammar fixes (#3722) --- .../main/java/io/realm/examples/intro/IntroExampleActivity.java | 2 +- .../kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt | 2 +- .../realm/examples/rxjava/throttle/ThrottleSearchActivity.java | 2 +- .../src/main/java/io/realm/internal/CheckedRow.java | 2 +- .../src/objectServer/java/io/realm/android/SecureUserStore.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java index ba58b50372..faa6b1eb78 100644 --- a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java +++ b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java @@ -144,7 +144,7 @@ private void basicLinkQuery(Realm realm) { private String complexReadWrite() { String status = "\nPerforming complex Read/Write operation..."; - // Open the default realm. All threads must use it's own reference to the realm. + // Open the default realm. All threads must use its own reference to the realm. // Those can not be transferred across threads. Realm realm = Realm.getDefaultInstance(); diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index 8b4112f4f9..d903e5a208 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -131,7 +131,7 @@ class KotlinExampleActivity : Activity() { private fun complexReadWrite(): String { var status = "\nPerforming complex Read/Write operation..." - // Open the default realm. All threads must use it's own reference to the realm. + // Open the default realm. All threads must use its own reference to the realm. // Those can not be transferred across threads. val realm = Realm.getDefaultInstance() diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java index 15a792100c..6a5c27991b 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java @@ -75,7 +75,7 @@ public Observable> call(TextViewTextChangeEvent event) { @Override public Boolean call(RealmResults persons) { // Only continue once data is actually loaded - // RealmObservables will emit the unloaded (empty) list as it's first item + // RealmObservables will emit the unloaded (empty) list as its first item return persons.isLoaded(); } }) diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index 7d10954838..e969452972 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -27,7 +27,7 @@ */ public class CheckedRow extends UncheckedRow { - // Used if created from other row. This keeps a strong reference to avoid GC'ing the original object, and it's + // Used if created from other row. This keeps a strong reference to avoid GC'ing the original object, and its // underlying native data. @SuppressWarnings("unused") private UncheckedRow originalRow; diff --git a/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java index 44ba36f04d..8f221bfdbd 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java @@ -32,7 +32,7 @@ /** * Encrypt and decrypt the token ({@link SyncUser}) using Android built in KeyStore capabilities. * According to the Android API this picks the right algorithm to perfom the operations. - * Prior to API 18 there were no AndroidKeyStore API, but the UNIX deamon existed to it's possible + * Prior to API 18 there were no AndroidKeyStore API, but the UNIX deamon existed so it's possible * with the help of this code: https://github.com/nelenkov/android-keystore. * * On API > = 18, we generate an AES key to encrypt we then generate and uses the RSA key inside the KeyStore From 8045dcefa638c39da3143429a6c691166d8db31b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 2 Nov 2016 10:49:48 +0100 Subject: [PATCH 0195/2110] Nh/remove usresync crypto (#3683) * remove usresync crypto as it lives in a separate repo. --- CHANGELOG.md | 12 +- .../secureTokenAndroidKeyStore/build.gradle | 3 +- .../src/main/AndroidManifest.xml | 5 +- .../MainActivity.java | 136 -------- .../java/io/realm/CredentialsTests.java | 9 - .../java/io/realm/android/UserStoreTest.java | 71 ---- .../java/io/realm/SyncCredentials.java | 22 +- .../io/realm/android/SecureUserStore.java | 173 ---------- .../internal/android/crypto/CipherClient.java | 105 ------ .../android/crypto/CipherFactory.java | 53 --- .../internal/android/crypto/SyncCrypto.java | 33 -- .../android/crypto/SyncCryptoFactory.java | 44 --- .../crypto/api_18/SyncCryptoApi18Impl.java | 316 ------------------ .../crypto/api_legacy/SyncCryptoLegacy.java | 262 --------------- .../android/crypto/ciper/CipherJB.java | 31 -- .../android/crypto/ciper/CipherLegacy.java | 31 -- .../android/crypto/ciper/CipherMM.java | 31 -- .../internal/android/crypto/misc/Base64.java | 31 -- .../android/crypto/misc/PRNGFixes.java | 94 ------ 19 files changed, 15 insertions(+), 1447 deletions(-) delete mode 100644 examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherFactory.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCrypto.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCryptoFactory.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_18/SyncCryptoApi18Impl.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_legacy/SyncCryptoLegacy.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherJB.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherLegacy.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherMM.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/Base64.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/PRNGFixes.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c4625bc0..370ea45df5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ ## 2.1.0 +### Breaking changes + +* * `SecureUserStore` has been moved to its own GitHub repository: https://github.com/realm/realm-android-user-store + See https://github.com/realm/realm-android-user-store/blob/master/README.md for further info on how to include it. + + ### Object Server API Changes (In Beta) * Renamed `User` to `SyncUser`, `Credentials` to `SyncCredentials` and `Session` to `SyncSession` to align names with Cocoa. @@ -57,7 +63,7 @@ This release is not protocol-compatible with previous versions of the Realm Mobi ### Internal * Upgraded Realm Core to 2.1.0 -* Upgraded Realm Sync to 1.0.0-BETA-2.0. +* Upgraded Realm Sync to 1.0.0-BETA-2.0. ## 2.0.1 @@ -74,7 +80,7 @@ This release is not protocol-compatible with previous versions of the Realm Mobi ## 2.0.0 -This release introduces support for the Realm Mobile Platform! +This release introduces support for the Realm Mobile Platform! See for an overview of these great new features. ### Breaking Changes @@ -280,7 +286,7 @@ No changes since 0.91.1. * Removed `HandlerController` from the public API. * Removed constructor of `RealmAsyncTask` from the public API (#1594). * `RealmBaseAdapter` has been moved to its own GitHub repository: https://github.com/realm/realm-android-adapters - See https://github.com/realm/realm-android-adapters/README.md for further info on how to include it. + See https://github.com/realm/realm-android-adapters/blob/master/README.md for further info on how to include it. * File format of Realm files is changed. Files will be automatically upgraded but opening a Realm file with older versions of Realm is not possible. diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index 7c3f9f7f96..7222838bea 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -6,7 +6,7 @@ android { buildToolsVersion "24.0.0" defaultConfig { - applicationId "examples.realm.io.securetokenandroidkeystore" + applicationId "io.realm.examples.securetokenandroidkeystore" minSdkVersion 9 targetSdkVersion 24 versionCode 1 @@ -30,6 +30,7 @@ dependencies { }) compile 'com.android.support:appcompat-v7:24.2.0' testCompile 'junit:junit:4.12' + compile 'io.realm:android-secure-userstore:1.0.0' } realm { diff --git a/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml b/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml index 89f0a5e281..15f02a70aa 100644 --- a/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml +++ b/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml @@ -3,8 +3,9 @@ package="com.example.securetokenandroidkeystore"> - + android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" + android:name="examples.io.realm.securetokenandroidkeystore.MyApplication"> + diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java b/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java deleted file mode 100644 index 7042cf7094..0000000000 --- a/examples/secureTokenAndroidKeyStore/src/main/java/examples/io/realm/securetokenandroidkeystore/securetokenandroidkeystore/MainActivity.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package examples.io.realm.securetokenandroidkeystore.securetokenandroidkeystore; - -import android.os.Bundle; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; -import android.widget.TextView; - -import com.example.securetokenandroidkeystore.R; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.security.KeyStoreException; -import java.util.UUID; - -import io.realm.Realm; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncUser; -import io.realm.android.SecureUserStore; -import io.realm.internal.android.crypto.CipherClient; -import io.realm.internal.objectserver.ObjectServerUser; -import io.realm.internal.objectserver.Token; - -/** - * Activity responsible of unlocking the KeyStore - * before using the {@link io.realm.android.SecureUserStore} to encrypt - * the Token we get from the session - */ -public class MainActivity extends AppCompatActivity { - private CipherClient cryptoClient; - private TextView txtKeystoreState; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - txtKeystoreState = (TextView) findViewById(R.id.txtLabelKeyStore); - - try { - cryptoClient = new CipherClient(this); - if (cryptoClient.isKeystoreUnlocked()) { - buildSyncConf(); - keystoreUnlockedMessage(); - } else { - cryptoClient.unlockKeystore(); - } - } catch (KeyStoreException e) { - e.printStackTrace(); - } - } - - @Override - protected void onResume() { - super.onResume(); - try { - // We return to the app after the KeyStore is unlocked or not. - if (cryptoClient.isKeystoreUnlocked()) { - buildSyncConf(); - keystoreUnlockedMessage (); - } else { - keystoreLockedMessage (); - } - } catch (KeyStoreException e) { - e.printStackTrace(); - } - } - - // build SyncConfiguration with a user store to store encrypted Token. - private void buildSyncConf () { - try { - SyncManager.setUserStore(new SecureUserStore(MainActivity.this)); - // the rest of Sync logic ... - SyncUser user = createTestUser(0); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); - Realm realm = Realm.getInstance(secureConfig); - // ... - - } catch (KeyStoreException e) { - e.printStackTrace(); - } - } - // Helpers - private final static String USER_TOKEN = UUID.randomUUID().toString(); - private final static String REALM_TOKEN = UUID.randomUUID().toString(); - - private static SyncUser createTestUser(long expires) { - Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); - Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); - ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); - - JSONObject obj = new JSONObject(); - try { - JSONArray realmList = new JSONArray(); - JSONObject realmDesc = new JSONObject(); - realmDesc.put("uri", "realm://objectserver.realm.io/default"); - realmDesc.put("description", desc.toJson()); - realmList.put(realmDesc); - - obj.put("authUrl", "http://objectserver.realm.io/auth"); - obj.put("userToken", userToken.toJson()); - obj.put("realms", realmList); - return SyncUser.fromJson(obj.toString()); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - private void keystoreLockedMessage () { - txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorLocked)); - txtKeystoreState.setText(R.string.locked_text); - } - - private void keystoreUnlockedMessage () { - txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorActivated)); - txtKeystoreState.setText(R.string.unlocked_text); - } -} - diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java index 26ded3ef2d..eaac178bb9 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java @@ -62,15 +62,6 @@ public void google() { assertTrue(creds.getUserInfo().isEmpty()); } - @Test - public void twitter() { - SyncCredentials creds = SyncCredentials.twitter("foo"); - - assertEquals(SyncCredentials.IdentityProvider.TWITTER, creds.getIdentityProvider()); - assertEquals("foo", creds.getUserIdentifier()); - assertTrue(creds.getUserInfo().isEmpty()); - } - @Test public void facebook_invalidInput() { String[] invalidInput = { null, ""}; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java deleted file mode 100644 index 271217da05..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/android/UserStoreTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.android; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.security.KeyStoreException; - -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.SyncUser; -import io.realm.UserStore; -import io.realm.rule.TestRealmConfigurationFactory; - -import static io.realm.util.SyncTestUtils.createTestUser; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -@RunWith(AndroidJUnit4.class) -public class UserStoreTest { - @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - - private Realm realm; - - @Before - public void setUp() { - RealmConfiguration realmConfig = configFactory.createConfiguration(); - realm = Realm.getInstance(realmConfig); - } - - @After - public void tearDown() { - if (realm != null) { - realm.close(); - } - } - - @Ignore("See https://github.com/realm/realm-java/issues/3555") - @Test - public void encrypt_decrypt_UsingAndroidKeyStoreUserStore() throws KeyStoreException { - SyncUser user = createTestUser(); - UserStore userStore = new SecureUserStore(InstrumentationRegistry.getTargetContext()); - SyncUser savedUser = userStore.put("crypted_entry", user); - assertNull(savedUser); - SyncUser decrypted_entry = userStore.get("crypted_entry"); - assertEquals(user, decrypted_entry); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index ebb8813760..827f1265cf 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -30,7 +30,7 @@ * Logging into the Realm Object Server consists of the following steps: *

          *
        1. - * Log in to 3rd party provider (Facebook, Google or Twitter). The result is usually an Authorization Grant that must be + * Log in to 3rd party provider (Facebook or Google). The result is usually an Authorization Grant that must be * saved in a {@link SyncCredentials} object of the proper type e.g., {@link SyncCredentials#facebook(String)} for a * Facebook login. *
        2. @@ -125,21 +125,6 @@ public static SyncCredentials google(String googleToken) { return new SyncCredentials(IdentityProvider.GOOGLE, googleToken, null); } - /** - * Creates credentials based on a Twitter login. - * - * @param twitterToken a google userIdentifier acquired by logging into Twitter. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} - * @throws IllegalArgumentException if user name is either {@code null} or empty. - */ - public static SyncCredentials twitter(String twitterToken) { - if (twitterToken == null || twitterToken.equals("")) { - throw new IllegalArgumentException("Non-null 'twitterToken' required."); - } - return new SyncCredentials(IdentityProvider.TWITTER, twitterToken, null); - } - /** * Creates a custom set of credentials. The behaviour will depend on the type of {@code identityProvider} and * {@code userInfo} used. @@ -222,11 +207,6 @@ public static final class IdentityProvider { */ public static final String GOOGLE = "google"; - /** - * Credentials will be verified by Twitter. - */ - public static final String TWITTER = "twitter"; - /** * Credentials will be verified by the Object Server. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java deleted file mode 100644 index 8f221bfdbd..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/android/SecureUserStore.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.android; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.security.KeyStoreException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -import io.realm.SyncUser; -import io.realm.UserStore; -import io.realm.internal.android.crypto.CipherClient; - -/** - * Encrypt and decrypt the token ({@link SyncUser}) using Android built in KeyStore capabilities. - * According to the Android API this picks the right algorithm to perfom the operations. - * Prior to API 18 there were no AndroidKeyStore API, but the UNIX deamon existed so it's possible - * with the help of this code: https://github.com/nelenkov/android-keystore. - * - * On API > = 18, we generate an AES key to encrypt we then generate and uses the RSA key inside the KeyStore - * to encrypt the AES key that we store along the encrypted data inside a private {@link android.content.SharedPreferences}. - * - * This throws a {@link java.security.KeyStoreException} in case of an error or KeyStore being unvailable (unlocked). - * - * See also: io.realm.internal.android.crypto.class.CipherClient - * @see
          Android KeyStore - */ -public class SecureUserStore implements UserStore { - private static final String REALM_OBJECT_SERVER_USERS = "realm_object_server_users"; - private final CipherClient cipherClient; - private final SharedPreferences sp; - private SyncUser cachedCurrentUser; // Keep a quick reference to the current user - - public SecureUserStore(final Context context) throws KeyStoreException { - cipherClient = new CipherClient(context); - sp = context.getSharedPreferences(REALM_OBJECT_SERVER_USERS, Context.MODE_PRIVATE); - } - - /** - * Store user as serialised and encrypted (Json), inside the private {@link android.content.SharedPreferences}. - * @param key the {@link android.content.SharedPreferences} key. - * @param user we want to save. - * @return The previous user saved with this key or {@code null} if no user was replaced. - */ - @Override - public SyncUser put(String key, SyncUser user) { - String previousUser = sp.getString(key, null); - SharedPreferences.Editor editor = sp.edit(); - String userSerialisedAndEncrypted; - try { - userSerialisedAndEncrypted = cipherClient.encrypt(user.toJson()); - } catch (KeyStoreException e) { - e.printStackTrace(); - return null; - } - editor.putString(key, userSerialisedAndEncrypted); - // Optimistically save. If the user isn't saved due to a process crash it isn't dangerous. - editor.apply(); - - if (UserStore.CURRENT_USER_KEY.equals(key)) { - cachedCurrentUser = user; - } - if (previousUser != null) { - try { - String userSerialisedAndDecrypted = cipherClient.decrypt(previousUser); - return SyncUser.fromJson(userSerialisedAndDecrypted); - } catch (KeyStoreException e) { - e.printStackTrace(); - return null; - } - } else { - return null; - } - } - - /** - * Retrieves the {@link SyncUser} by decrypting first the serialised Json. - * @param key the {@link android.content.SharedPreferences} key. - * @return the {@link SyncUser} with the given key. - */ - @Override - public SyncUser get(String key) { - if (key.equals(UserStore.CURRENT_USER_KEY) && cachedCurrentUser != null) { - return cachedCurrentUser; - } - - String userData = sp.getString(key, ""); - if (userData.equals("")) { - return null; - } - - try { - String userSerialisedAndDecrypted = cipherClient.decrypt(userData); - SyncUser user = SyncUser.fromJson(userSerialisedAndDecrypted); - if (UserStore.CURRENT_USER_KEY.equals(key)) { - cachedCurrentUser = user; - } - return user; - } catch (KeyStoreException e) { - e.printStackTrace(); - return null; - } - } - - @Override - public SyncUser remove(String key) { - String currentUser = sp.getString(key, null); - SharedPreferences.Editor editor = sp.edit(); - editor.putString(key, null); - editor.apply(); - - if (UserStore.CURRENT_USER_KEY.equals(key) && cachedCurrentUser != null) { - cachedCurrentUser = null; - } - - if (currentUser != null) { - try { - String userSerialisedAndDecrypted = cipherClient.decrypt(currentUser); - return SyncUser.fromJson(userSerialisedAndDecrypted); - } catch (KeyStoreException e) { - e.printStackTrace(); - return null; - } - } else { - return null; - } - } - - @Override - public Collection allUsers() { - Map all = sp.getAll(); - ArrayList users = new ArrayList(all.size()); - for (Object userJson : all.values()) { - String userSerialisedAndDecrypted = null; - try { - userSerialisedAndDecrypted = cipherClient.decrypt((String) userJson); - } catch (KeyStoreException e) { - e.printStackTrace(); - // returning null will probably penalise the other Users - } - users.add(SyncUser.fromJson(userSerialisedAndDecrypted)); - } - return users; - } - - @Override - public void clear() { - Set all = sp.getAll().keySet(); - SharedPreferences.Editor editor = sp.edit(); - for (String key : all) { - editor.remove(key); - } - editor.apply(); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java deleted file mode 100644 index c0f63adfb6..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherClient.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto; - -import android.content.Context; - -import java.security.KeyStoreException; - -import io.realm.SyncUser; - -/** - * A Helper to use the crypto API, it allows encryption/decryption and has methods to help test if the KeyStore is locked and help unlocked it. - * This hides the complexity of different Android API to achieve those operations. - * - * This support Android API 9 and forwards. - * This cipher uses the KeyStore provided by Android, hence we to need to be sure that the KeyStore is available - * before doing any {@link #encrypt(String)}/{@link #decrypt(String)} by calling {@link #isKeystoreUnlocked()} then - * {@link #unlockKeystore()}, note that the latter will open the system {@link android.app.Activity} to set a passowrd/PIN/Pattern required - * to unlock the sceen & the KeyStore. - */ -public class CipherClient { - private SyncCrypto syncCrypto; - - public CipherClient(Context context) throws KeyStoreException { - syncCrypto = SyncCryptoFactory.get(context); - } - - /** - * Takes some plain text {@link String} and return the encrypted version - * of this {@link String} using the Android Key Store. - * - * @param user represents the Token of a {@link SyncUser}. - * @return the encrypted Token. - * @throws KeyStoreException in case the Key Store is locked or other error. - */ - public String encrypt(String user) throws KeyStoreException { - if (syncCrypto.is_keystore_unlocked()) { - try { - syncCrypto.create_key(); - String encrypted = syncCrypto.encrypt(user); - return encrypted; - } catch (KeyStoreException ex) { - throw new KeyStoreException(ex); - } - } else { - throw new KeyStoreException("Trying to use SecureUserStore without an unlocked KeyStore"); - } - } - - /** - * Takes a previously {@link #encrypt(String)} to decrypted it - * using the Android Key Store. - * - * @param user_encrypted represents the encrypted Token of a {@link SyncUser}. - * @return the decrypted Token. - * @throws KeyStoreException in case the KeyStore is locked or other error. - */ - public String decrypt(String user_encrypted) throws KeyStoreException { - if (syncCrypto.is_keystore_unlocked()) { - try { - String decrypted = syncCrypto.decrypt(user_encrypted); - return decrypted; - } catch (KeyStoreException ex) { - throw new KeyStoreException(ex); - } - } else { - throw new KeyStoreException("Trying to use SecureUserStore without an unlocked KeyStore"); - } - } - - - /** - * Checks whether the Android KeyStore is available. - * This should be called before {@link #encrypt(String)} or {@link #decrypt(String)} as those need the KeyStore unlocked. - * @return {@code true} if the Android KeyStore in unlocked. - * @throws KeyStoreException in case of error. - */ - public boolean isKeystoreUnlocked () throws KeyStoreException { - return syncCrypto.is_keystore_unlocked(); - } - - /** - * Helps unlock the KeyStore this will launch the appropriate {@link android.content.Intent} - * to start the platform system {@link android.app.Activity} to create/unlock the KeyStore. - * - * @throws KeyStoreException in case of error. - */ - public void unlockKeystore () throws KeyStoreException { - syncCrypto.unlock_keystore(); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherFactory.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherFactory.java deleted file mode 100644 index 453f35c56a..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/CipherFactory.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto; - -import android.os.Build; - -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; - -import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; - -import io.realm.internal.android.crypto.ciper.CipherJB; -import io.realm.internal.android.crypto.ciper.CipherLegacy; -import io.realm.internal.android.crypto.ciper.CipherMM; - - -/** - * Return an appropriate {@link Cipher} given the version of Android. - * Ex: on API 23 OpenSSL is replaced by BoringSSL. - */ -public class CipherFactory { - - private static final boolean IS_JB43 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; - private static final boolean IS_MM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; - private static final boolean IS_GINGERBREAD = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; - - public static Cipher get() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { - if (IS_MM) { - return CipherMM.get(); - } else if (IS_JB43) { - return CipherJB.get(); - } else if (IS_GINGERBREAD) { - return CipherLegacy.get(); - } else { - throw new IllegalArgumentException("Not supported yet"); - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCrypto.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCrypto.java deleted file mode 100644 index cd3c63beeb..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCrypto.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto; - -import java.security.KeyStoreException; - -/** - * Define methods that Android API should expose regardless of the API version. - */ -public interface SyncCrypto { - String encrypt(String plainText) throws KeyStoreException; - String decrypt(String cipherText) throws KeyStoreException; - void create_key() throws KeyStoreException; - - // User is responsible of unlocking the keystore, we expose these methods as - // a helper. - boolean is_keystore_unlocked() throws KeyStoreException; - void unlock_keystore() throws KeyStoreException; -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCryptoFactory.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCryptoFactory.java deleted file mode 100644 index 9741aa73bf..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/SyncCryptoFactory.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto; - -import android.content.Context; -import android.os.Build; - -import java.security.KeyStoreException; - -import io.realm.internal.android.crypto.api_18.SyncCryptoApi18Impl; -import io.realm.internal.android.crypto.api_legacy.SyncCryptoLegacy; - -/** - * Return an appropriate {@link SyncCrypto} given the version of Android. - */ -public class SyncCryptoFactory { - - private static final boolean IS_JB43 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; - private static final boolean IS_GINGERBREAD = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; - - public static SyncCrypto get (Context context) throws KeyStoreException { - if (IS_JB43) { - return new SyncCryptoApi18Impl(context); - } else if (IS_GINGERBREAD) { - return new SyncCryptoLegacy(context); - } else { - throw new KeyStoreException("Unknown android version"); - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_18/SyncCryptoApi18Impl.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_18/SyncCryptoApi18Impl.java deleted file mode 100644 index 1551241c98..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_18/SyncCryptoApi18Impl.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto.api_18; - -import android.annotation.TargetApi; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.security.KeyPairGeneratorSpec; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.KeyPairGenerator; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SecureRandom; -import java.security.UnrecoverableEntryException; -import java.security.cert.CertificateException; -import java.security.interfaces.RSAPublicKey; -import java.util.ArrayList; -import java.util.Calendar; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import javax.security.auth.x500.X500Principal; - - -import io.realm.internal.android.crypto.CipherFactory; -import io.realm.internal.android.crypto.SyncCrypto; -import io.realm.internal.android.crypto.misc.Base64; -import io.realm.internal.android.crypto.misc.PRNGFixes; - -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; - -/** - * Implements {@link SyncCrypto} methods for API 18 (after the Android KeyStore public API). - */ -public class SyncCryptoApi18Impl implements SyncCrypto { - private java.security.KeyStore keyStore; - private Context context; - private String alias = "Realm"; - private static String X500Principal = "CN=Sync, O=Realm"; - private final static String DELIMITER = "]"; - - public static final String UNLOCK_ACTION = "com.android.credentials.UNLOCK"; - - private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; - - public SyncCryptoApi18Impl (Context context) throws KeyStoreException { - PRNGFixes.apply(); - this.context = context; - try { - keyStore = java.security.KeyStore.getInstance(ANDROID_KEYSTORE); - keyStore.load(null); - } catch (KeyStoreException e) { - e.printStackTrace(); - throw new KeyStoreException(e); - } catch (CertificateException e) { - e.printStackTrace(); - throw new KeyStoreException(e); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - throw new KeyStoreException(e); - } catch (IOException e) { - e.printStackTrace(); - throw new KeyStoreException(e); - } - } - - @Override - public String encrypt(String plainText) throws KeyStoreException { - try { - SecretKey key = generateAESKey(); - byte[] encrypted = encryptedUsingAESKey(key, plainText); - byte[] encryptedKey = encryptAESKeyUsingRSA(key); - // append with AES enc with RSA - return String.format("%s%s%s", Base64.to(encryptedKey), DELIMITER, - Base64.to(encrypted)); - } catch (Exception e) { - throw new KeyStoreException(e); - } - } - - @Override - public String decrypt(String cipherText) throws KeyStoreException { - try { - String[] fields = cipherText.split(DELIMITER); - if (fields.length != 2) { - throw new IllegalArgumentException("Invalid encrypted text format"); - } - - byte[] aesEncWithRSA = Base64.from(fields[0]); - byte[] encToken = Base64.from(fields[1]); - - // decrypt AES using RSA - SecretKey key = decrypytAESKeyUsingRSA(aesEncWithRSA); - - // decrypt Token using decrypted AES - return decryptedUsingAESKey(key, encToken); - } catch (Exception e) { - throw new KeyStoreException(e); - } - } - - @Override - public boolean is_keystore_unlocked() throws KeyStoreException { - try { - Class keyStoreClass = Class.forName("android.security.KeyStore"); - Method getInstanceMethod = keyStoreClass.getMethod("getInstance"); - Object invoke = getInstanceMethod.invoke(null); - - Method isUnlockedMethod = keyStoreClass.getMethod("isUnlocked"); - boolean isUnlocked = (boolean)isUnlockedMethod.invoke(invoke); - return isUnlocked; - } catch (ClassNotFoundException e) { - throw new KeyStoreException(e); - } catch (NoSuchMethodException e) { - throw new KeyStoreException(e); - } catch (IllegalAccessException e) { - throw new KeyStoreException(e); - } catch (InvocationTargetException e) { - throw new KeyStoreException(e); - } - } - - @Override - public void unlock_keystore() throws KeyStoreException { - try { - Intent intent = new Intent(UNLOCK_ACTION); - intent.addFlags(FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - throw new KeyStoreException(e); - } - } - - @TargetApi(18) - public void create_key() throws KeyStoreException { - try { - // Create new key. - // Avoid a known bug in Api 23 where we need names in KeyStore to be unique - // http://stackoverflow.com/questions/23977407/android-4-3-keystore-chain-null-while-trying-to-retrieve-keys - if (keyStore.containsAlias(alias)) { - keyStore.deleteEntry(alias); - } - Calendar start = Calendar.getInstance(); - Calendar end = Calendar.getInstance(); - end.add(Calendar.YEAR, 1); - KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) - .setAlias(alias) - .setSubject(new X500Principal(X500Principal)) - .setSerialNumber(BigInteger.ONE) - .setStartDate(start.getTime()) - .setEndDate(end.getTime()) - .build(); - KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", - "AndroidKeyStore"); - generator.initialize(spec); - generator.generateKeyPair(); - } catch (Exception e) { - throw new KeyStoreException(e); - } - } - - private SecretKey generateAESKey() throws NoSuchAlgorithmException { - // Generate a 256-bit key - final int outputKeyLength = 256; - - SecureRandom secureRandom = new SecureRandom(); - // Do *not* seed secureRandom! Automatically seeded from system entropy. - KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); - keyGenerator.init(outputKeyLength, secureRandom); - SecretKey key = keyGenerator.generateKey(); - return key; - } - - private byte[] encryptedUsingAESKey(SecretKey key, String plainText) throws KeyStoreException { - try { - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.ENCRYPT_MODE, key); - return cipher.doFinal(plainText.getBytes("UTF-8")); - } catch (NoSuchAlgorithmException e) { - throw new KeyStoreException(e); - } catch (NoSuchPaddingException e) { - throw new KeyStoreException(e); - } catch (BadPaddingException e) { - throw new KeyStoreException(e); - } catch (UnsupportedEncodingException e) { - throw new KeyStoreException(e); - } catch (IllegalBlockSizeException e) { - throw new KeyStoreException(e); - } catch (InvalidKeyException e) { - throw new KeyStoreException(e); - } - } - - private String decryptedUsingAESKey(SecretKey key, byte[] cipherText) throws KeyStoreException { - try { - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.DECRYPT_MODE, key); - byte[] encrypted = cipher.doFinal(cipherText); - return new String(encrypted, "UTF-8"); - } catch (NoSuchAlgorithmException e) { - throw new KeyStoreException(e); - } catch (NoSuchPaddingException e) { - throw new KeyStoreException(e); - } catch (BadPaddingException e) { - throw new KeyStoreException(e); - } catch (UnsupportedEncodingException e) { - throw new KeyStoreException(e); - } catch (IllegalBlockSizeException e) { - throw new KeyStoreException(e); - } catch (InvalidKeyException e) { - throw new KeyStoreException(e); - } - } - - private byte[] encryptAESKeyUsingRSA(SecretKey key) throws KeyStoreException { - try { - java.security.KeyStore.PrivateKeyEntry privateKeyEntry = (java.security.KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null); - RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey(); - - Cipher cipher = CipherFactory.get(); - - cipher.init(Cipher.ENCRYPT_MODE, publicKey); - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher); - cipherOutputStream.write(key.getEncoded()); - cipherOutputStream.close(); - - return outputStream.toByteArray(); - } catch (NoSuchPaddingException e) { - throw new KeyStoreException(e); - } catch (NoSuchAlgorithmException e) { - throw new KeyStoreException(e); - } catch (NoSuchProviderException e) { - throw new KeyStoreException(e); - } catch (InvalidKeyException e) { - throw new KeyStoreException(e); - } catch (KeyStoreException e) { - throw new KeyStoreException(e); - } catch (UnrecoverableEntryException e) { - throw new KeyStoreException(e); - } catch (IOException e) { - throw new KeyStoreException(e); - } - } - - private SecretKeySpec decrypytAESKeyUsingRSA(byte[] aesEncKey) throws KeyStoreException { - try { - java.security.KeyStore.PrivateKeyEntry privateKeyEntry = (java.security.KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null); - Cipher cipher = CipherFactory.get(); - cipher.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey()); - CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(aesEncKey), cipher); - - ArrayList values = new ArrayList<>(); - int nextByte; - while ((nextByte = cipherInputStream.read()) != -1) { - values.add((byte)nextByte); - } - - final byte[] bytes = new byte[values.size()]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = values.get(i).byteValue(); - } - - SecretKeySpec originalKey = new SecretKeySpec(bytes, "AES"); - return originalKey; - } catch (NoSuchPaddingException e) { - throw new KeyStoreException(e); - } catch (NoSuchAlgorithmException e) { - throw new KeyStoreException(e); - } catch (NoSuchProviderException e) { - throw new KeyStoreException(e); - } catch (UnsupportedEncodingException e) { - throw new KeyStoreException(e); - } catch (IOException e) { - throw new KeyStoreException(e); - } catch (InvalidKeyException e) { - throw new KeyStoreException(e); - } catch (UnrecoverableEntryException e) { - throw new KeyStoreException(e); - } catch (KeyStoreException e) { - throw new KeyStoreException(e); - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_legacy/SyncCryptoLegacy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_legacy/SyncCryptoLegacy.java deleted file mode 100644 index de8c5836fc..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/api_legacy/SyncCryptoLegacy.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto.api_legacy; - -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.net.LocalSocket; -import android.net.LocalSocketAddress; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; -import java.security.KeyStoreException; -import java.security.SecureRandom; -import java.util.ArrayList; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - - -import io.realm.internal.android.crypto.CipherFactory; -import io.realm.internal.android.crypto.SyncCrypto; -import io.realm.internal.android.crypto.misc.Base64; -import io.realm.internal.android.crypto.misc.PRNGFixes; - -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; - -/** - * Implements {@link SyncCrypto} methods for API 9 to 18 (pre Android KeyStore public API). - */ -public class SyncCryptoLegacy implements SyncCrypto { - private Context context; - private SecretKey key; - private String alias = "Realm"; - private int mError = NO_ERROR; - private SecureRandom random = new SecureRandom(); - - private static final String UNLOCK_ACTION = "android.credentials.UNLOCK"; - - // ResponseCodes - private static final int NO_ERROR = 1; - private static final int LOCKED = 2; - private static final int UNINITIALIZED = 3; - private static final int PROTOCOL_ERROR = 5; - - // States - private enum State { - UNLOCKED, LOCKED, UNINITIALIZED - }; - - private static final LocalSocketAddress sAddress = new LocalSocketAddress( - "keystore", LocalSocketAddress.Namespace.RESERVED); - private final static int KEY_LENGTH = 256; - private final static String DELIMITER = "]"; - - public SyncCryptoLegacy (Context context) throws KeyStoreException { - PRNGFixes.apply(); - this.context = context; - } - - @Override - public String encrypt(String plainText) throws KeyStoreException { - try { - Cipher cipher = CipherFactory.get(); - - byte[] iv = generateIv(cipher.getBlockSize()); - IvParameterSpec ivParams = new IvParameterSpec(iv); - cipher.init(Cipher.ENCRYPT_MODE, key, ivParams); - byte[] cipherText = cipher.doFinal(plainText.getBytes("UTF-8")); - - return String.format("%s%s%s", Base64.to(iv), DELIMITER, - Base64.to(cipherText)); - } catch (GeneralSecurityException e) { - throw new KeyStoreException(e); - } catch (UnsupportedEncodingException e) { - throw new KeyStoreException(e); - } - } - - @Override - public String decrypt(String cipherText) throws KeyStoreException { - byte[] keyBytes = get(alias); - if (keyBytes == null) { - return null; - } - SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); - - try { - String[] fields = cipherText.split(DELIMITER); - if (fields.length != 2) { - throw new IllegalArgumentException("Invalid encrypted text format"); - } - - byte[] iv = Base64.from(fields[0]); - byte[] cipherBytes = Base64.from(fields[1]); - Cipher cipher = CipherFactory.get(); - IvParameterSpec ivParams = new IvParameterSpec(iv); - cipher.init(Cipher.DECRYPT_MODE, key, ivParams); - byte[] plaintext = cipher.doFinal(cipherBytes); - return new String(plaintext, "UTF-8"); - } catch (GeneralSecurityException e) { - throw new KeyStoreException(e); - } catch (UnsupportedEncodingException e) { - throw new KeyStoreException(e); - } - } - - @Override - public boolean is_keystore_unlocked() throws KeyStoreException { - return state() == State.UNLOCKED; - } - - @Override - public void unlock_keystore() throws KeyStoreException { - try { - Intent intent = new Intent(UNLOCK_ACTION); - intent.addFlags(FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - throw new KeyStoreException(e); - } - } - - @Override - public void create_key() throws KeyStoreException { - try { - KeyGenerator kg = KeyGenerator.getInstance("AES"); - kg.init(KEY_LENGTH); - key = kg.generateKey(); - - boolean success = put(getBytes(alias), key.getEncoded()); - if (!success) { - throw new KeyStoreException("Keystore error"); - } - } catch (Exception e) { - throw new KeyStoreException(e); - } - } - - private State state() throws KeyStoreException { - execute('t'); - switch (mError) { - case NO_ERROR: - return State.UNLOCKED; - case LOCKED: - return State.LOCKED; - case UNINITIALIZED: - return State.UNINITIALIZED; - default: - throw new KeyStoreException("" + mError); - } - } - - private byte[] get(byte[] key) { - ArrayList values = execute('g', key); - return (values == null || values.isEmpty()) ? null : values.get(0); - } - - private byte[] get(String key) { - return get(getBytes(key)); - } - - private boolean put(byte[] key, byte[] value) { - execute('i', key, value); - return mError == NO_ERROR; - } - - private ArrayList execute(int code, byte[]... parameters) { - mError = PROTOCOL_ERROR; - - for (byte[] parameter : parameters) { - if (parameter == null || parameter.length > 65535) { - return null; - } - } - - LocalSocket socket = new LocalSocket(); - try { - socket.connect(sAddress); - - OutputStream out = socket.getOutputStream(); - out.write(code); - for (byte[] parameter : parameters) { - out.write(parameter.length >> 8); - out.write(parameter.length); - out.write(parameter); - } - out.flush(); - socket.shutdownOutput(); - - InputStream in = socket.getInputStream(); - if ((code = in.read()) != NO_ERROR) { - if (code != -1) { - mError = code; - } - return null; - } - - ArrayList values = new ArrayList(); - while (true) { - int i, j; - if ((i = in.read()) == -1) { - break; - } - if ((j = in.read()) == -1) { - return null; - } - byte[] value = new byte[i << 8 | j]; - for (i = 0; i < value.length; i += j) { - if ((j = in.read(value, i, value.length - i)) == -1) { - return null; - } - } - values.add(value); - } - mError = NO_ERROR; - return values; - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - socket.close(); - } catch (IOException e) { - } - } - return null; - } - - private static byte[] getBytes(String string) { - try { - return string.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - private byte[] generateIv(int length) { - byte[] b = new byte[length]; - random.nextBytes(b); - return b; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherJB.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherJB.java deleted file mode 100644 index cc4ee8bbd7..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherJB.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto.ciper; - -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; - -import javax.crypto.NoSuchPaddingException; - -/** - * Return a {@link javax.crypto.Cipher} that works for the API 18. - */ -public class CipherJB { - public static javax.crypto.Cipher get() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { - return javax.crypto.Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL"); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherLegacy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherLegacy.java deleted file mode 100644 index d34c728a7f..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherLegacy.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto.ciper; - -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; - -import javax.crypto.NoSuchPaddingException; - -/** - * Return a {@link javax.crypto.Cipher} that works for the legacy API 9 to 18. - */ -public class CipherLegacy { - public static javax.crypto.Cipher get() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { - return javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding"); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherMM.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherMM.java deleted file mode 100644 index 3f10d15205..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/ciper/CipherMM.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto.ciper; - -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; - -import javax.crypto.NoSuchPaddingException; - -/** - * Return a {@link javax.crypto.Cipher} that works for API > 23. - */ -public class CipherMM { - public static javax.crypto.Cipher get() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { - return javax.crypto.Cipher.getInstance("RSA/ECB/PKCS1Padding"); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/Base64.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/Base64.java deleted file mode 100644 index 19ad363ef1..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/Base64.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.android.crypto.misc; - -/** - * Base64 helper methods. - */ -public class Base64 { - public static String to(byte[] bytes) { - return android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP); - } - - public static byte[] from(String base64) { - return android.util.Base64.decode(base64, android.util.Base64.NO_WRAP); - } - -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/PRNGFixes.java b/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/PRNGFixes.java deleted file mode 100644 index 6ddddefc58..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/android/crypto/misc/PRNGFixes.java +++ /dev/null @@ -1,94 +0,0 @@ -package io.realm.internal.android.crypto.misc; - -import android.os.Build; -import android.os.Process; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; - -// Based on http://android-developers.blogspot.jp/2013/08/some-securerandom-thoughts.html -public class PRNGFixes { - - private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); - - private PRNGFixes() { - } - - public static void apply() { - applyOpenSSLFix(); - } - - public static void applyOpenSSLFix() throws SecurityException { - if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) - || (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2)) { - // No need to apply the fix - return; - } - - try { - // Mix in the device- and invocation-specific seed. - Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_seed", byte[].class) - .invoke(null, generateSeed()); - - // Mix output of Linux PRNG into OpenSSL's PRNG - int bytesRead = (Integer) Class - .forName( - "org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_load_file", String.class, long.class) - .invoke(null, "/dev/urandom", 1024); - if (bytesRead != 1024) { - throw new IOException( - "Unexpected number of bytes read from Linux PRNG: " - + bytesRead); - } - } catch (Exception e) { - throw new SecurityException("Failed to seed OpenSSL PRNG", e); - } - } - - private static byte[] generateSeed() { - try { - ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); - DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); - seedBufferOut.writeLong(System.currentTimeMillis()); - seedBufferOut.writeLong(System.nanoTime()); - seedBufferOut.writeInt(Process.myPid()); - seedBufferOut.writeInt(Process.myUid()); - seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); - seedBufferOut.close(); - return seedBuffer.toByteArray(); - } catch (IOException e) { - throw new SecurityException("Failed to generate seed", e); - } - } - - private static String getDeviceSerialNumber() { - // We're using the Reflection API because Build.SERIAL is only available - // since API Level 9 (Gingerbread, Android 2.3). - try { - return (String) Build.class.getField("SERIAL").get(null); - } catch (Exception ignored) { - return null; - } - } - - private static byte[] getBuildFingerprintAndDeviceSerial() { - StringBuilder result = new StringBuilder(); - String fingerprint = Build.FINGERPRINT; - if (fingerprint != null) { - result.append(fingerprint); - } - String serial = getDeviceSerialNumber(); - if (serial != null) { - result.append(serial); - } - try { - return result.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding not supported"); - } - } -} From 4e15a65920e8b8c12d41f6e0bb4594f8e0cd7cd1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 8 Nov 2016 11:20:39 +0100 Subject: [PATCH 0196/2110] DefaultRealmModule not created for empty Kotlin projects (#3749) --- CHANGELOG.md | 6 ++++++ .../main/java/io/realm/processor/ModuleMetaData.java | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c4625bc0..9f6e80f344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.1.2 + +### Bug fixes + +* Kotlin projects no longer create the `RealmDefaultModule` if no Realm model classes are present (#3746). + ## 2.1.1 ### Object Server API Changes (In Beta) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java index be87d9004c..1ace559402 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java @@ -101,11 +101,13 @@ public boolean generate(ProcessingEnvironment processingEnv) { return false; } - // Add default realm module if needed. - if (libraryModules.size() == 0) { + // Create default Realm module if needed. + // Note: Kotlin will trigger the annotation processor even if no Realm annotations are used. + // The DefaultRealmModule should not be created in this case either. + if (libraryModules.size() == 0 && availableClasses.size() > 0) { shouldCreateDefaultModule = true; - String defautModuleName = Constants.REALM_PACKAGE_NAME + "." + Constants.DEFAULT_MODULE_CLASS_NAME; - modules.put(defautModuleName, availableClasses); + String defaultModuleName = Constants.REALM_PACKAGE_NAME + "." + Constants.DEFAULT_MODULE_CLASS_NAME; + modules.put(defaultModuleName, availableClasses); } return true; From 179c7221659893bc3fb78811aedde83a48159eb6 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 8 Nov 2016 13:29:06 +0000 Subject: [PATCH 0197/2110] Nh/add secure token android key store files (#3736) * add secure token example `src` files, lost after a package rename. --- .../MainActivity.java | 137 ++++++++++++++++++ .../MyApplication.java | 31 ++++ 2 files changed, 168 insertions(+) create mode 100644 examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java create mode 100644 examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MyApplication.java diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java new file mode 100644 index 0000000000..c4b618b704 --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java @@ -0,0 +1,137 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.examples.securetokenandroidkeystore; + +import android.os.Bundle; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.widget.TextView; + +import com.example.securetokenandroidkeystore.R; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.security.KeyStoreException; +import java.util.UUID; + +import io.realm.android.CipherClient; +import io.realm.android.SecureUserStore; +import io.realm.SyncUser; +import io.realm.android.SecureUserStore; +import io.realm.SyncManager; +import io.realm.SyncConfiguration; +import io.realm.Realm; +import io.realm.internal.objectserver.Token; +import io.realm.internal.objectserver.ObjectServerUser; + +/** + * Activity responsible of unlocking the KeyStore + * before using the {@link realm.io.android.SecureUserStore} to encrypt + * the Token we get from the session + */ +public class MainActivity extends AppCompatActivity { + private CipherClient cryptoClient; + private TextView txtKeystoreState; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + txtKeystoreState = (TextView) findViewById(R.id.txtLabelKeyStore); + + try { + cryptoClient = new CipherClient(this); + if (cryptoClient.isKeystoreUnlocked()) { + buildSyncConf(); + keystoreUnlockedMessage(); + } else { + cryptoClient.unlockKeystore(); + } + } catch (KeyStoreException e) { + e.printStackTrace(); + } + } + + @Override + protected void onResume() { + super.onResume(); + try { + // We return to the app after the KeyStore is unlocked or not. + if (cryptoClient.isKeystoreUnlocked()) { + buildSyncConf(); + keystoreUnlockedMessage (); + } else { + keystoreLockedMessage (); + } + } catch (KeyStoreException e) { + e.printStackTrace(); + } + } + + // build SyncConfiguration with a user store to store encrypted Token. + private void buildSyncConf () { + try { + SyncManager.setUserStore(new SecureUserStore(MainActivity.this)); + // the rest of Sync logic ... + SyncUser user = createTestUser(0); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); + Realm realm = Realm.getInstance(secureConfig); + // ... + + } catch (KeyStoreException e) { + e.printStackTrace(); + } + } + // Helpers + private final static String USER_TOKEN = UUID.randomUUID().toString(); + private final static String REALM_TOKEN = UUID.randomUUID().toString(); + + private static SyncUser createTestUser(long expires) { + Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); + Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); + ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); + + JSONObject obj = new JSONObject(); + try { + JSONArray realmList = new JSONArray(); + JSONObject realmDesc = new JSONObject(); + realmDesc.put("uri", "realm://objectserver.realm.io/default"); + realmDesc.put("description", desc.toJson()); + realmList.put(realmDesc); + + obj.put("authUrl", "http://objectserver.realm.io/auth"); + obj.put("userToken", userToken.toJson()); + obj.put("realms", realmList); + return SyncUser.fromJson(obj.toString()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + private void keystoreLockedMessage () { + txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorLocked)); + txtKeystoreState.setText(R.string.locked_text); + } + + private void keystoreUnlockedMessage () { + txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorActivated)); + txtKeystoreState.setText(R.string.unlocked_text); + } +} + diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MyApplication.java b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MyApplication.java new file mode 100644 index 0000000000..8af665821a --- /dev/null +++ b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MyApplication.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.examples.securetokenandroidkeystore; + +import android.app.Application; + +import io.realm.Realm; + +public class MyApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + // Initialize Realm. Should only be done once when the application starts. + Realm.init(this); + } +} From c3d723bdb2da5526b157e9bb62cf260015fea6d3 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 9 Nov 2016 21:41:13 +0900 Subject: [PATCH 0198/2110] Support annotationProcessor configuration. (#3754) * Support annotationProcessor configuration. * Update Realm.groovy * ignore hasAnnotationProcessorConfiguration in Kotlin project * update logic for Kotlin project * address review comments * improve readability * fix typo in CHANGELOG --- CHANGELOG.md | 8 +++- examples/introExample/build.gradle | 1 - examples/objectServerExample/build.gradle | 2 +- examples/unitTestExample/build.gradle | 1 - .../main/groovy/io/realm/gradle/Realm.groovy | 42 ++++++++++++++----- 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec3e2a4c14..fa9cc665c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.2.0 + +### Enhancements + +* Added support for the `annotationProcessor` configuration provided by Android Gradle Plugin 2.2.0 or later. Realm plugin adds its annotation processor to the `annotationProcessor` configuration instead of `apt` configuration if it is available and the `com.neenbedankt.android-apt` plugin is not used. In Kotlin projects, `kapt` is used instead of the `annotationProcessor` configuration (#3026). + ## 2.1.2 ### Bug fixes @@ -43,7 +49,7 @@ * Permission error when a database file was located on external storage (#3140). * Memory leak when unsubscribing from a RealmResults/RealmObject RxJava Observable (#3552). -### Enhancement +### Enhancements * `Realm.compactRealm()` now works for encrypted Realms. * Added `first(E defaultValue)` and `last(E defaultValue)` methods to `RealmList` and `RealmResult`. These methods will return the provided object instead of throwing an `IndexOutOfBoundsException` if the list is empty. diff --git a/examples/introExample/build.gradle b/examples/introExample/build.gradle index d61e93b5a3..6559bb309a 100644 --- a/examples/introExample/build.gradle +++ b/examples/introExample/build.gradle @@ -1,6 +1,5 @@ apply plugin: 'com.android.application' apply plugin: 'android-command' -apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'realm-android' android { diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index dd0e37e1ae..52a4c63f4a 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -63,5 +63,5 @@ dependencies { compile 'com.android.support:support-v4:24.2.0' compile 'com.android.support:design:24.2.0' compile 'com.jakewharton:butterknife:8.3.0' - apt 'com.jakewharton:butterknife-compiler:8.3.0' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.3.0' } diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index f4a1b95430..a6752c2738 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -1,6 +1,5 @@ apply plugin: 'com.android.application' apply plugin: 'android-command' -apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'realm-android' android { diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index f3557026e5..0b28d9a15c 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -23,8 +23,6 @@ import io.realm.transformer.RealmTransformer import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.DependencyResolutionListener -import org.gradle.api.artifacts.ResolvableDependencies class Realm implements Plugin { @@ -44,27 +42,36 @@ class Realm implements Plugin { def syncEnabledDefault = false project.extensions.create('realm', RealmPluginExtension, project, syncEnabledDefault) - def usesKotlinPlugin = project.plugins.findPlugin('kotlin-android') != null def usesAptPlugin = project.plugins.findPlugin('com.neenbedankt.android-apt') != null + def isKotlinProject = project.plugins.findPlugin('kotlin-android') != null + def hasAnnotationProcessorConfiguration = project.getConfigurations().findByName('annotationProcessor') != null + // TODO add a parameter in 'realm' block if this should be specified by users + def preferAptOnKotlinProject = false - def isKaptProject = usesKotlinPlugin && !usesAptPlugin - - if (!isKaptProject) { + if (shouldApplyAndroidAptPlugin(usesAptPlugin, isKotlinProject, + hasAnnotationProcessorConfiguration, preferAptOnKotlinProject)) { project.plugins.apply(AndroidAptPlugin) + usesAptPlugin = true } project.android.registerTransform(new RealmTransformer(project)) project.repositories.add(project.getRepositories().jcenter()) project.dependencies.add("compile", "io.realm:realm-annotations:${Version.VERSION}") - if (isKaptProject) { - project.dependencies.add("kapt", "io.realm:realm-annotations:${Version.VERSION}") - project.dependencies.add("kapt", "io.realm:realm-annotations-processor:${Version.VERSION}") - } else { + if (usesAptPlugin) { project.dependencies.add("apt", "io.realm:realm-annotations:${Version.VERSION}") project.dependencies.add("apt", "io.realm:realm-annotations-processor:${Version.VERSION}") project.dependencies.add("androidTestApt", "io.realm:realm-annotations:${Version.VERSION}") project.dependencies.add("androidTestApt", "io.realm:realm-annotations-processor:${Version.VERSION}") + } else if (isKotlinProject && !preferAptOnKotlinProject) { + project.dependencies.add("kapt", "io.realm:realm-annotations:${Version.VERSION}") + project.dependencies.add("kapt", "io.realm:realm-annotations-processor:${Version.VERSION}") + } else { + assert hasAnnotationProcessorConfiguration + project.dependencies.add("annotationProcessor", "io.realm:realm-annotations:${Version.VERSION}") + project.dependencies.add("annotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}") + project.dependencies.add("androidTestAnnotationProcessor", "io.realm:realm-annotations:${Version.VERSION}") + project.dependencies.add("androidTestAnnotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}") } } @@ -76,4 +83,19 @@ class Realm implements Plugin { return false } } + + private static boolean shouldApplyAndroidAptPlugin(boolean usesAptPlugin, boolean isKotlinProject, + boolean hasAnnotationProcessorConfiguration, + boolean preferAptOnKotlinProject) { + if (usesAptPlugin) { + // for any projects that uses android-apt plugin already. No need to apply it twice. + return false + } + if (isKotlinProject) { + // for any Kotlin projects where user did not apply 'android-apt' plugin manually. + return preferAptOnKotlinProject && !hasAnnotationProcessorConfiguration + } + // for any Java Projects where user did not apply 'android-apt' plugin manually. + return !hasAnnotationProcessorConfiguration + } } From ed0f18001b6872a1f2ed29e356aead1e61109a4a Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 10 Nov 2016 12:20:22 +0000 Subject: [PATCH 0199/2110] Nh/fixes 3732 insertOrUpdate using other Realm (#3755) --- CHANGELOG.md | 8 +++- .../RealmProxyMediatorGenerator.java | 6 +-- .../io/realm/RealmDefaultModuleMediator.java | 6 +-- .../java/io/realm/BulkInsertTests.java | 41 +++++++++++++++++++ .../src/main/java/io/realm/BaseRealm.java | 2 +- .../src/main/java/io/realm/Realm.java | 6 +-- version.txt | 2 +- 7 files changed, 57 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f6e80f344..174bbd78ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ ## 2.1.1 +### Bug fixes + +* Fixed a bug in `Realm.insert` and `Realm.insertOrUpdate` methods causing a `StackOverFlow` when you try to insert a cyclic graph of objects between Realms (#3732). + ### Object Server API Changes (In Beta) * Set default RxFactory to `SyncConfiguration`. @@ -63,7 +67,7 @@ This release is not protocol-compatible with previous versions of the Realm Mobi ### Internal * Upgraded Realm Core to 2.1.0 -* Upgraded Realm Sync to 1.0.0-BETA-2.0. +* Upgraded Realm Sync to 1.0.0-BETA-2.0. ## 2.0.1 @@ -80,7 +84,7 @@ This release is not protocol-compatible with previous versions of the Realm Mobi ## 2.0.0 -This release introduces support for the Realm Mobile Platform! +This release introduces support for the Realm Mobile Platform! See for an overview of these great new features. ### Breaking Changes diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index ee9e2beab9..e438b10348 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -69,7 +69,7 @@ public void generate() throws IOException { "java.util.HashSet", "java.util.List", "java.util.Map", - "java.util.IdentityHashMap", + "java.util.HashMap", "java.util.Set", "java.util.Iterator", "java.util.Collection", @@ -330,7 +330,7 @@ private void emitInsertOrUpdateListToRealmMethod(JavaWriter writer) throws IOExc writer.emitStatement("Iterator iterator = objects.iterator()"); writer.emitStatement("RealmModel object = null"); - writer.emitStatement("Map cache = new IdentityHashMap(objects.size())"); + writer.emitStatement("Map cache = new HashMap(objects.size())"); writer.beginControlFlow("if (iterator.hasNext())") .emitSingleLineComment(" access the first element to figure out the clazz for the routing below") @@ -371,7 +371,7 @@ private void emitInsertListToRealmMethod(JavaWriter writer) throws IOException { writer.emitStatement("Iterator iterator = objects.iterator()"); writer.emitStatement("RealmModel object = null"); - writer.emitStatement("Map cache = new IdentityHashMap(objects.size())"); + writer.emitStatement("Map cache = new HashMap(objects.size())"); writer.beginControlFlow("if (iterator.hasNext())") .emitSingleLineComment(" access the first element to figure out the clazz for the routing below") diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 51049b4d90..e72e461844 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -12,8 +12,8 @@ import java.io.IOException; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; -import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -138,7 +138,7 @@ public void insert(Realm realm, RealmModel object, Map cache) public void insert(Realm realm, Collection objects) { Iterator iterator = objects.iterator(); RealmModel object = null; - Map cache = new IdentityHashMap(objects.size()); + Map cache = new HashMap(objects.size()); if (iterator.hasNext()) { // access the first element to figure out the clazz for the routing below object = iterator.next(); @@ -178,7 +178,7 @@ public void insertOrUpdate(Realm realm, RealmModel obj, Map ca public void insertOrUpdate(Realm realm, Collection objects) { Iterator iterator = objects.iterator(); RealmModel object = null; - Map cache = new IdentityHashMap(objects.size()); + Map cache = new HashMap(objects.size()); if (iterator.hasNext()) { // access the first element to figure out the clazz for the routing below object = iterator.next(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java index 4ae12cc5c7..ee6946c1f9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java @@ -43,6 +43,7 @@ import io.realm.entities.HumanModule; import io.realm.entities.NoPrimaryKeyWithPrimaryKeyObjectRelation; import io.realm.entities.NullTypes; +import io.realm.entities.Owner; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsLong; import io.realm.entities.PrimaryKeyAsString; @@ -292,6 +293,46 @@ public void insertOrUpdate_cyclicType() { assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); } + @Test + public void insertOrUpdate_cyclicDependenciesFromOtherRealm() { + RealmConfiguration config1 = configFactory.createConfiguration("realm1"); + RealmConfiguration config2 = configFactory.createConfiguration("realm2"); + + Realm realm1 = Realm.getInstance(config1); + Realm realm2 = Realm.getInstance(config2); + + realm1.beginTransaction(); + Owner owner = realm1.createObject(Owner.class); + owner.setName("Kiba"); + Dog dog = realm1.createObject(Dog.class); + dog.setName("Akamaru"); + owner.getDogs().add(dog); + dog.setOwner(owner); + realm1.commitTransaction(); + + //Copy object with relations from realm1 to realm2 + realm2.beginTransaction(); + realm2.insertOrUpdate(owner); + realm2.commitTransaction(); + + assertEquals(1, realm1.where(Owner.class).count()); + assertEquals(1, realm1.where(Owner.class).findFirst().getDogs().size()); + assertEquals(1, realm1.where(Dog.class).count()); + + assertEquals(realm1.where(Owner.class).count(), realm2.where(Owner.class).count()); + assertEquals(realm1.where(Dog.class).count(), realm2.where(Dog.class).count()); + + assertEquals(1, realm2.where(Owner.class).findFirst().getDogs().size()); + + assertEquals(realm1.where(Owner.class).findFirst().getName(), realm2.where(Owner.class).findFirst().getName()); + + assertEquals(realm1.where(Owner.class).findFirst().getDogs().first().getName() + , realm2.where(Owner.class).findFirst().getDogs().first().getName()); + + realm1.close(); + realm2.close(); + } + @Test public void insert_nullPrimaryKey() { PrimaryKeyAsString primaryKeyAsString = new PrimaryKeyAsString(); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index e09226f3f6..c61b2cefae 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -60,7 +60,7 @@ abstract class BaseRealm implements Closeable { private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; - // Thread pool for all async operations (Query & transaction) + volatile static Context applicationContext; // Thread pool for all async operations (Query & transaction) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index a6a6844345..506266aa72 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -21,7 +21,6 @@ import android.content.Context; import android.os.Build; import android.util.JsonReader; -import android.util.Log; import org.json.JSONArray; import org.json.JSONException; @@ -38,7 +37,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Scanner; @@ -996,7 +994,7 @@ public void insert(RealmModel object) { if (object == null) { throw new IllegalArgumentException("Null object cannot be inserted into Realm."); } - Map cache = new IdentityHashMap(); + Map cache = new HashMap(); configuration.getSchemaMediator().insert(this, object, cache); } @@ -1065,7 +1063,7 @@ public void insertOrUpdate(RealmModel object) { if (object == null) { throw new IllegalArgumentException("Null object cannot be inserted into Realm."); } - Map cache = new IdentityHashMap(); + Map cache = new HashMap(); configuration.getSchemaMediator().insertOrUpdate(this, object, cache); } diff --git a/version.txt b/version.txt index bdedd4f4a2..f1093dde49 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.1.2-SNAPSHOT \ No newline at end of file +2.1.2-SNAPSHOT From 07c391929a7d4d33312b92881582e4e6c29c5c3e Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 11 Nov 2016 19:07:10 +0900 Subject: [PATCH 0200/2110] Fixed a bug that caused unexpected MigrationNeededException in very rare case. (#3768) In sync mode, `validateTable()` must get all fields in the tabla. --- CHANGELOG.md | 1 + .../java/io/realm/processor/RealmProxyClassGenerator.java | 2 +- .../src/test/resources/io/realm/AllTypesRealmProxy.java | 4 ++-- .../src/test/resources/io/realm/BooleansRealmProxy.java | 2 +- .../src/test/resources/io/realm/NullTypesRealmProxy.java | 4 ++-- .../src/test/resources/io/realm/SimpleRealmProxy.java | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 174bbd78ec..90a1587b2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug fixes * Kotlin projects no longer create the `RealmDefaultModule` if no Realm model classes are present (#3746). +* Unexpected `RealmMigrationNeededException` was thrown when a field was added to synced Realm. ## 2.1.1 diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index b092637530..429999eca4 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -659,7 +659,7 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { // create type dictionary for lookup writer.emitStatement("Map columnTypes = new HashMap()"); - writer.beginControlFlow("for (long i = 0; i < " + metadata.getFields().size() + "; i++)"); + writer.beginControlFlow("for (long i = 0; i < columnCount; i++)"); writer.emitStatement("columnTypes.put(table.getColumnName(i), table.getColumnType(i))"); writer.endControlFlow(); writer.emitEmptyLine(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 3ceaa5677f..b4b27ae96e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -375,7 +375,7 @@ private void injectObjectContext() { } final Row row = proxyState.getRow$realm(); if (value == null) { - // Table#nullifyLink() does not support default value. Just use Row. + // Table#nullifyLink() does not support default value. Just using Row. row.nullifyLink(columnInfo.columnObjectIndex); return; } @@ -527,7 +527,7 @@ public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean } } Map columnTypes = new HashMap(); - for (long i = 0; i < 9; i++) { + for (long i = 0; i < columnCount; i++) { columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 54abdab25d..7200c4e70e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -260,7 +260,7 @@ public static BooleansColumnInfo validateTable(SharedRealm sharedRealm, boolean } } Map columnTypes = new HashMap(); - for (long i = 0; i < 4; i++) { + for (long i = 0; i < columnCount; i++) { columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 272de1cf78..608868bb72 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -980,7 +980,7 @@ private void injectObjectContext() { } final Row row = proxyState.getRow$realm(); if (value == null) { - // Table#nullifyLink() does not support default value. Just use Row. + // Table#nullifyLink() does not support default value. Just using Row. row.nullifyLink(columnInfo.fieldObjectNullIndex); return; } @@ -1088,7 +1088,7 @@ public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean } } Map columnTypes = new HashMap(); - for (long i = 0; i < 21; i++) { + for (long i = 0; i < columnCount; i++) { columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 7190607f14..3dc59b6feb 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -194,7 +194,7 @@ public static SimpleColumnInfo validateTable(SharedRealm sharedRealm, boolean al } } Map columnTypes = new HashMap(); - for (long i = 0; i < 2; i++) { + for (long i = 0; i < columnCount; i++) { columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } From d7519099558206993c14c8126771c1968d588196 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 11 Nov 2016 21:06:06 +0900 Subject: [PATCH 0201/2110] removed includedescriptorclasses option to supprt built-in shrinker of Android Gradle Plugin (#3776) * removed includedescriptorclasses option to supprt built-in shrinker of Android Gradle Plugin * fix typo --- CHANGELOG.md | 1 + realm/realm-library/proguard-rules-common.pro | 8 ++++---- .../main/java/io/realm/exceptions/RealmFileException.java | 1 + .../src/main/java/io/realm/log/RealmLogger.java | 5 ++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a1587b2a..dfcaa7540d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug fixes * Kotlin projects no longer create the `RealmDefaultModule` if no Realm model classes are present (#3746). +* Remove `includedescriptorclasses` option from ProGuard rule file in order to support built-in shrinker of Android Gradle Plugin (#3714). * Unexpected `RealmMigrationNeededException` was thrown when a field was added to synced Realm. ## 2.1.1 diff --git a/realm/realm-library/proguard-rules-common.pro b/realm/realm-library/proguard-rules-common.pro index e4bb7abb4f..fb972bb245 100644 --- a/realm/realm-library/proguard-rules-common.pro +++ b/realm/realm-library/proguard-rules-common.pro @@ -2,17 +2,17 @@ -keep @io.realm.annotations.RealmModule class * -keep class io.realm.internal.Keep --keep,includedescriptorclasses @io.realm.internal.Keep class * { *; } +-keep @io.realm.internal.Keep class * { *; } -keep class io.realm.internal.KeepMember --keep,includedescriptorclasses @io.realm.internal.KeepMember class * { @io.realm.internal.KeepMember *; } +-keep @io.realm.internal.KeepMember class * { @io.realm.internal.KeepMember *; } -dontwarn javax.** -dontwarn io.realm.** -keep class io.realm.RealmCollection -keep class io.realm.OrderedRealmCollection --keepclasseswithmembernames,includedescriptorclasses class io.realm.** { +-keepclasseswithmembernames class io.realm.** { native ; } --dontnote rx.Observable \ No newline at end of file +-dontnote rx.Observable diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java index 5c7ba906d2..07b63b39c8 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java @@ -26,6 +26,7 @@ public class RealmFileException extends RuntimeException { /** * The specific kind of this {@link RealmFileException}. */ + @Keep public enum Kind { /** * Thrown for any I/O related exception scenarios when a Realm is opened. diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java index f94c7859d6..5c7b0e4f08 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java @@ -16,13 +16,13 @@ package io.realm.log; -import io.realm.internal.KeepMember; +import io.realm.internal.Keep; /** * Interface for custom loggers that can be registered at {@link RealmLog#add(RealmLogger)}. * The different log levels are described in {@link LogLevel}. */ -@KeepMember +@Keep // This interface is used as a parameter type of a native method in SharedRealm.java public interface RealmLogger { /** @@ -34,6 +34,5 @@ public interface RealmLogger { * @param throwable optional exception to log. * @param message optional additional message. */ - @KeepMember void log(int level, String tag, Throwable throwable, String message); } From c3553283386687125d0dc07b8d59c9030ca765b1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 11 Nov 2016 14:17:35 +0100 Subject: [PATCH 0202/2110] Add support for the management-Realm (#3627) Add preliminary support for the management-Realm --- CHANGELOG.md | 10 +- Jenkinsfile | 3 + realm/build.gradle | 3 +- realm/config/findbugs/findbugs-filter.xml | 52 +++++++- .../io/realm/processor/ClassMetaData.java | 2 +- realm/realm-library/build.gradle | 4 +- .../{UserTests.java => SyncUserTests.java} | 35 ++++- .../java/io/realm/util/SyncTestUtils.java | 13 +- .../src/main/java/io/realm/Property.java | 8 +- .../objectServer/java/io/realm/SyncUser.java | 38 ++++++ .../realm/permissions/PermissionChange.java | 121 ++++++++++++++++++ .../realm/permissions/PermissionModule.java | 23 ++++ 12 files changed, 294 insertions(+), 18 deletions(-) rename realm/realm-library/src/androidTestObjectServer/java/io/realm/{UserTests.java => SyncUserTests.java} (75%) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionModule.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e16e620531..664c463421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,8 @@ ## 2.2.0 -### Enhancements - -* Added support for the `annotationProcessor` configuration provided by Android Gradle Plugin 2.2.0 or later. Realm plugin adds its annotation processor to the `annotationProcessor` configuration instead of `apt` configuration if it is available and the `com.neenbedankt.android-apt` plugin is not used. In Kotlin projects, `kapt` is used instead of the `annotationProcessor` configuration (#3026). +### Object Server API Changes (In Beta) -## 2.1.2 +* Added support for `SyncUser.getManagementRealm()` and permission changes. ### Bug fixes @@ -12,6 +10,10 @@ * Remove `includedescriptorclasses` option from ProGuard rule file in order to support built-in shrinker of Android Gradle Plugin (#3714). * Unexpected `RealmMigrationNeededException` was thrown when a field was added to synced Realm. +### Enhancements + +* Added support for the `annotationProcessor` configuration provided by Android Gradle Plugin 2.2.0 or later. Realm plugin adds its annotation processor to the `annotationProcessor` configuration instead of `apt` configuration if it is available and the `com.neenbedankt.android-apt` plugin is not used. In Kotlin projects, `kapt` is used instead of the `annotationProcessor` configuration (#3026). + ## 2.1.1 ### Bug fixes diff --git a/Jenkinsfile b/Jenkinsfile index 4e73462a90..d31960c03e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,6 +25,9 @@ try { def buildEnv def rosEnv stage('Docker build') { + // Clean any potential old containers + sh 'docker rm ros || true' + // Docker image for build buildEnv = docker.build 'realm-java:snapshot' // Docker image for testing Realm Object Server diff --git a/realm/build.gradle b/realm/build.gradle index 02d42b21b4..12082ca654 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -6,9 +6,8 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.2.2' classpath 'de.undercouch:gradle-download-task:3.1.1' - classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.novoda:gradle-android-command-plugin:1.3.0' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.2' diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index e2c1a8b2e1..10553d8870 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -37,6 +37,13 @@ + + + + + + + @@ -58,5 +65,48 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index 972a0f6314..b40263d4cc 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -322,7 +322,7 @@ public String getSimpleClassName() { */ public boolean isModelClass() { String type = classType.toString(); - if (type.equals("io.realm.dynamic.DynamicRealmObject")) { + if (type.equals("io.realm.DynamicRealmObject")) { return false; } return (!type.endsWith(".RealmObject") && !type.endsWith("RealmProxy")); diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ca07f306a0..35bd90b5c0 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -1,7 +1,6 @@ import java.security.MessageDigest apply plugin: 'com.android.library' -apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'maven-publish' apply plugin: 'com.jfrog.artifactory' @@ -124,6 +123,7 @@ repositories { } dependencies { + objectServerAnnotationProcessor project(':realm-annotations-processor') provided 'io.reactivex:rxjava:1.1.0' compile "io.realm:realm-annotations:${version}" compile 'com.getkeepsafe.relinker:relinker:1.2.2' @@ -136,7 +136,7 @@ dependencies { androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'com.opencsv:opencsv:3.4' androidTestCompile 'dk.ilios:spanner:0.6.0' - androidTestApt project(':realm-annotations-processor') + androidTestAnnotationProcessor project(':realm-annotations-processor') } task sourcesJar(type: Jar) { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java similarity index 75% rename from realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 794b41fc30..c7a91dff09 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -24,6 +24,10 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.util.Collection; import io.realm.android.SharedPrefsUserStore; @@ -31,12 +35,13 @@ import io.realm.util.SyncTestUtils; import static io.realm.util.SyncTestUtils.createTestUser; -import static org.junit.Assert.assertEquals; +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) -public class UserTests { +public class SyncUserTests { @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); @@ -113,4 +118,30 @@ public void currentUser_returnsUserAfterLogin() { assertEquals(user, User.currentUser()); } */ + + @Test + public void getManagementRealm() { + SyncUser user = SyncTestUtils.createTestUser(); + Realm managementRealm = user.getManagementRealm(); + assertNotNull(managementRealm); + managementRealm.close(); + } + + @Test + public void getManagementRealm_enforceTLS() throws URISyntaxException { + // Non TLS + SyncUser user = SyncTestUtils.createTestUser("http://objectserver.realm.io/auth"); + Realm managementRealm = user.getManagementRealm(); + SyncConfiguration config = (SyncConfiguration) managementRealm.getConfiguration(); + assertEquals(new URI("realm://objectserver.realm.io/" + user.getIdentity() + "/__management"), config.getServerUrl()); + managementRealm.close(); + + // TLS + user = SyncTestUtils.createTestUser("https://objectserver.realm.io/auth"); + managementRealm = user.getManagementRealm(); + config = (SyncConfiguration) managementRealm.getConfiguration(); + assertEquals(new URI("realms://objectserver.realm.io/" + user.getIdentity() + "/__management"), config.getServerUrl()); + managementRealm.close(); + } + } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index a882b255a6..bd067ab30b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -33,12 +33,21 @@ public class SyncTestUtils { public static String USER_TOKEN = UUID.randomUUID().toString(); public static String REALM_TOKEN = UUID.randomUUID().toString(); + public static String DEFAULT_AUTH_URL = "http://objectserver.realm.io/auth"; public static SyncUser createTestUser() { - return createTestUser(Long.MAX_VALUE); + return createTestUser(DEFAULT_AUTH_URL, Long.MAX_VALUE); } public static SyncUser createTestUser(long expires) { + return createTestUser(DEFAULT_AUTH_URL, expires); + } + + public static SyncUser createTestUser(String authUrl) { + return createTestUser(authUrl, Long.MAX_VALUE); + } + + public static SyncUser createTestUser(String authUrl, long expires) { Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); @@ -51,7 +60,7 @@ public static SyncUser createTestUser(long expires) { realmDesc.put("description", desc.toJson()); realmList.put(realmDesc); - obj.put("authUrl", "http://objectserver.realm.io/auth"); + obj.put("authUrl", authUrl); obj.put("userToken", userToken.toJson()); obj.put("realms", realmList); return SyncUser.fromJson(obj.toString()); diff --git a/realm/realm-library/src/main/java/io/realm/Property.java b/realm/realm-library/src/main/java/io/realm/Property.java index 82fb4a0655..ef9069c81a 100644 --- a/realm/realm-library/src/main/java/io/realm/Property.java +++ b/realm/realm-library/src/main/java/io/realm/Property.java @@ -21,10 +21,10 @@ * Class for handling properties/fields. */ -public class Property { - public static boolean PRIMARY_KEY = true; - public static boolean REQUIRED = true; - public static boolean INDEXED = true; +class Property { + public static final boolean PRIMARY_KEY = true; + public static final boolean REQUIRED = true; + public static final boolean INDEXED = true; private final long nativePtr; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 808ae04c0a..0a2f921531 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -44,6 +44,8 @@ import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; +import io.realm.permissions.PermissionChange; +import io.realm.permissions.PermissionModule; /** * @Beta @@ -60,6 +62,7 @@ @Beta public class SyncUser { + private SyncConfiguration managementRealmConfig; private final ObjectServerUser syncUser; private SyncUser(ObjectServerUser user) { @@ -354,6 +357,41 @@ public String getAccessToken() { return (userToken != null) ? userToken.value() : null; } + /** + * Returns an instance of the Management Realm owned by the user. + *

          + * This Realm can be used to control access and permissions for Realms owned by the user. This includes + * giving other users access to Realms. + * + * @see How to control permissions + */ + public Realm getManagementRealm() { + synchronized (this) { + if (managementRealmConfig == null) { + String managementUrl = getManagementRealmUrl(syncUser.getAuthenticationUrl()); + managementRealmConfig = new SyncConfiguration.Builder(this, managementUrl) + .modules(new PermissionModule()) + .build(); + } + } + + return Realm.getInstance(managementRealmConfig); + } + + // Creates the URL to the permission Realm based on the authentication URL. + private static String getManagementRealmUrl(URL authUrl) { + String scheme = "realm"; + if (authUrl.getProtocol().equalsIgnoreCase("https")) { + scheme = "realms"; + } + try { + return new URI(scheme, authUrl.getUserInfo(), authUrl.getHost(), authUrl.getPort(), + "/~/__management", null, null).toString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Could not create URL to the management Realm", e); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java new file mode 100644 index 0000000000..d7aa1919d4 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java @@ -0,0 +1,121 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm.permissions; + +import java.util.Date; +import java.util.UUID; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; + +/** + * This class is used for requesting changes to a Realm's permissions. + * + * @see Controlling Permissions + */ +public class PermissionChange extends RealmObject { + + // Base fields + @PrimaryKey + @Required + private String id = UUID.randomUUID().toString(); + @Required + private Date createdAt = new Date(); + @Required + private Date updatedAt = new Date(); + private Integer statusCode = null; // null=not processed, 0=success, >0=error + private String statusMessage; + + @Required + private String realmUrl; + @Required + private String userId; + private Boolean mayRead = false; + private Boolean mayWrite = false; + private Boolean mayManage = false; + + public PermissionChange() { + // Default constructor required by Realm + } + + /** + * Construct a Permission Change Object. + * + * @param realmUrl Realm to change permissions for. Use {@code *} to change the permissions of all Realms. + * @param userId User or users to effect. Use {@code *} to change the permissions for all users. + * @param mayRead Define read access. {@code true} or {@code false} to request this new value. {@code null} to + * keep current value. + * @param mayWrite Define write access. {@code true} or {@code false} to request this new value. {@code null} to + * keep current value. + * @param mayManage Define manage access. {@code true} or {@code false} to request this new value. {@code null} to + * keep current value. + * + * @see Controlling Permissions + */ + public PermissionChange(String realmUrl, String userId, Boolean mayRead, Boolean mayWrite, Boolean mayManage) { + this.realmUrl = realmUrl; + this.userId = userId; + this.mayRead = mayRead; + this.mayWrite = mayWrite; + this.mayManage = mayManage; + } + + public String getId() { + return id; + } + + public Date getCreatedAt() { + return createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + /** + * Returns the status code for this change. + * + * @return {@code null} if not yet processed. {@code 0} if successfull, {@code >0} if an error happened. See {@link #getStatusMessage()}. + */ + public Integer getStatusCode() { + return statusCode; + } + + public String getStatusMessage() { + return statusMessage; + } + + public String getRealmUrl() { + return realmUrl; + } + + public String getUserId() { + return userId; + } + + public Boolean mayRead() { + return mayRead; + } + + public Boolean mayWrite() { + return mayWrite; + } + + public Boolean mayManage() { + return mayManage; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionModule.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionModule.java new file mode 100644 index 0000000000..5c245e3215 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionModule.java @@ -0,0 +1,23 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.permissions; + +import io.realm.annotations.RealmModule; + +@RealmModule(library = true, classes = { PermissionChange.class }) +public class PermissionModule { +} From b34e43ebe659871294784916ec6d1c45d2d9251a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 11 Nov 2016 15:07:29 +0100 Subject: [PATCH 0203/2110] Release v2.2.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 31941db520..e3a4f19336 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.2.0-SNAPSHOT +2.2.0 \ No newline at end of file From af77082591f0b1c4a8a990dbb714306562e2db1a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 11 Nov 2016 15:07:29 +0100 Subject: [PATCH 0204/2110] Prepare next release v2.2.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e3a4f19336..bd134637f0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.2.0 \ No newline at end of file +2.2.1-SNAPSHOT \ No newline at end of file From 6a9c9abe4c3ef8a145503e8f37b182fd2711bdaa Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Sat, 12 Nov 2016 02:45:47 +0900 Subject: [PATCH 0205/2110] Update version.txt --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index bd134637f0..a9d981d17e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.2.1-SNAPSHOT \ No newline at end of file +2.3.0-SNAPSHOT From 7b8de792fb67180f4aee4ba778d7f406cf755d76 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Sun, 13 Nov 2016 01:36:42 +0900 Subject: [PATCH 0206/2110] fix a bug that realm-snnotations-processor is not deployed to ojo (#3778) --- realm/realm-annotations-processor/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 6c2c3cd3cd..1a95e37e2f 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -40,6 +40,11 @@ sourceSets { compileJava.dependsOn generateVersionClass compileTestJava.dependsOn ':realm-library:assemble' +task ojoUpload() { + dependsOn "artifactoryPublish" + group = 'Publishing' +} + def commonPom = { licenses { license { From 50885b08751983a842e12c59c170e13ba57095f1 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 14 Nov 2016 18:09:36 +0900 Subject: [PATCH 0207/2110] Set native library version to ReLinker (#3785) --- realm/realm-library/build.gradle | 1 + .../src/main/java/io/realm/internal/RealmCore.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 35bd90b5c0..a0200c19dd 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -39,6 +39,7 @@ android { defaultConfig { minSdkVersion 9 targetSdkVersion 24 + versionName version project.archivesBaseName = "realm-android-library" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java index 1890f18271..496aa18734 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java @@ -26,6 +26,8 @@ import java.lang.reflect.InvocationTargetException; import java.util.Locale; +import io.realm.BuildConfig; + /** * Utility methods for Realm Core. */ @@ -56,7 +58,7 @@ public static synchronized void loadLibrary(Context context) { if (libraryIsLoaded) { return; } - ReLinker.loadLibrary(context, "realm-jni"); + ReLinker.loadLibrary(context, "realm-jni", BuildConfig.VERSION_NAME); libraryIsLoaded = true; } From fef8e049195ef5411ee8d0004f4fa29551711b2f Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 14 Nov 2016 18:49:05 +0900 Subject: [PATCH 0208/2110] fix artifactoryPublish task in realm-annotations-processor (#3782) --- realm/build.gradle | 4 ++-- realm/realm-annotations-processor/build.gradle | 8 ++++---- realm/realm-library/build.gradle | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/realm/build.gradle b/realm/build.gradle index 12082ca654..d520cb8024 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -12,8 +12,8 @@ buildscript { classpath 'com.novoda:gradle-android-command-plugin:1.3.0' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.2' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0' - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.4.5' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' + classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:3.1.1' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" } } diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 6c2c3cd3cd..d8dc054925 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -61,13 +61,13 @@ def commonPom = { publishing { publications { - realmPublication(MavenPublication) { + basePublication(MavenPublication) { groupId 'io.realm' artifactId = 'realm-annotations-processor' from components.java pom.withXml { Node root = asNode() - root.appendNode('name', 'realm-gradle-plugin') + root.appendNode('name', 'realm-annotations-processor') root.appendNode('description', 'Annotation Processor for Realm. Realm is a mobile database: a replacement for SQLite & ORMs') root.appendNode('url', 'http://realm.io') root.children().last() + commonPom @@ -96,7 +96,7 @@ bintray { dryRun = false publish = false - publications = ['realmPublication'] + publications = ['basePublication'] pkg { repo = 'maven' name = 'realm-annotations-processor' @@ -119,7 +119,7 @@ artifactory { password = project.hasProperty('bintrayKey') ? bintrayKey : 'noKey' } defaults { - publications ('realmPublication') + publications ('basePublication') } } } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index a0200c19dd..71b1a6bda0 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -365,10 +365,9 @@ artifactory { repoKey = 'oss-snapshot-local' username = project.hasProperty('bintrayUser') ? bintrayUser : 'noUser' password = project.hasProperty('bintrayKey') ? bintrayKey : 'noKey' - maven = true } defaults { - publishConfigs('basePublication', 'objectServerPublication') + publications('basePublication', 'objectServerPublication') publishPom = true publishIvy = false } From cf222155031dedde1993413a89e0ab762d6106b2 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 16 Nov 2016 14:12:56 +0900 Subject: [PATCH 0209/2110] added changelog for #3785 (#3797) added changelog for #3785 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 664c463421..8f1de5477f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.2.1 + +### Bug fixes + +* Added version number to the native library, preventing ReLinker from accidentally loading old code (#3775). + ## 2.2.0 ### Object Server API Changes (In Beta) From 101b5b40e5a0675e37d12fd11fe229bfc7d27e5d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 16 Nov 2016 11:30:12 +0100 Subject: [PATCH 0210/2110] Fix SyncConfiguration.toString() (#3792) --- CHANGELOG.md | 4 ++++ .../java/io/realm/SyncConfigurationTests.java | 10 ++++++++++ .../java/io/realm/SyncUserTests.java | 7 +++++++ .../java/io/realm/SyncConfiguration.java | 13 +++++++++++-- .../src/objectServer/java/io/realm/SyncUser.java | 11 +++++++++++ 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1de5477f..b8c4bb2b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 2.2.1 +### Object Server API Changes (In Beta) + +* Fixed `SyncConfiguration.toString()` so it now outputs a correct description instead of an empty string (#3787). + ### Bug fixes * Added version number to the native library, preventing ReLinker from accidentally loading old code (#3775). diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index f4d102aa2e..17b734f903 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -391,4 +391,14 @@ public void defaultRxFactory() { assertNotNull(config.getRxFactory()); } + + @Test + public void toString_nonEmpty() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + + String configStr = config.toString(); + assertTrue(configStr != null && !configStr.isEmpty()); + } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index c7a91dff09..a587ca1709 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -144,4 +144,11 @@ public void getManagementRealm_enforceTLS() throws URISyntaxException { managementRealm.close(); } + @Test + public void toString_returnDescription() { + SyncUser user = SyncTestUtils.createTestUser("http://objectserver.realm.io/auth"); + String str = user.toString(); + assertTrue(str != null && !str.isEmpty()); + } + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index d268398d76..2c2178fec2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -177,8 +177,17 @@ public int hashCode() { @Override public String toString() { - StringBuilder stringBuilder = new StringBuilder(); - // TODO + StringBuilder stringBuilder = new StringBuilder(super.toString()); + stringBuilder.append("\n"); + stringBuilder.append("serverUrl: " + serverUrl); + stringBuilder.append("\n"); + stringBuilder.append("user: " + user); + stringBuilder.append("\n"); + stringBuilder.append("syncPolicy: " + syncPolicy); + stringBuilder.append("\n"); + stringBuilder.append("errorHandler: " + errorHandler); + stringBuilder.append("\n"); + stringBuilder.append("deleteRealmOnLogout: " + deleteRealmOnLogout); return stringBuilder.toString(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 0a2f921531..65130e75c3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -408,6 +408,17 @@ public int hashCode() { return syncUser.hashCode(); } + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + sb.append("UserId: ").append(syncUser.getIdentity()); + sb.append(", AuthUrl: ").append(syncUser.getAuthenticationUrl()); + sb.append(", IsValid: ").append(isValid()); + sb.append(", Sessions: ").append(syncUser.getSessions().size()); + sb.append("}"); + return sb.toString(); + } + // Expose internal representation for other package protected classes ObjectServerUser getSyncUser() { return syncUser; From 67e6e4b439c2c54ce3e3dddf4295f0786e4aa38a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 17 Nov 2016 08:40:03 +0100 Subject: [PATCH 0211/2110] Fix getLocalInstanceCount crashing when Realms are closed again. (#3798) --- CHANGELOG.md | 1 + .../src/androidTest/java/io/realm/RealmTests.java | 10 +++++----- .../src/main/java/io/realm/RealmCache.java | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c4bb2b54..1b50dc3904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Bug fixes * Added version number to the native library, preventing ReLinker from accidentally loading old code (#3775). +* `Realm.getLocalInstanceCount(config)` throwing NullPointerException if called after all Realms have been closed (#3791). ## 2.2.0 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 5c570e7df7..a571b7bd9f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3796,20 +3796,20 @@ public void run() { @Test public void getLocalInstanceCount() { final RealmConfiguration config = configFactory.createConfiguration("localInstanceCount"); - assertEquals(0, Realm.getGlobalInstanceCount(config)); + assertEquals(0, Realm.getLocalInstanceCount(config)); // Open thread local Realm Realm realm = Realm.getInstance(config); - assertEquals(1, Realm.getGlobalInstanceCount(config)); + assertEquals(1, Realm.getLocalInstanceCount(config)); // Open thread local DynamicRealm DynamicRealm dynRealm = DynamicRealm.getInstance(config); - assertEquals(2, Realm.getGlobalInstanceCount(config)); + assertEquals(2, Realm.getLocalInstanceCount(config)); dynRealm.close(); - assertEquals(1, Realm.getGlobalInstanceCount(config)); + assertEquals(1, Realm.getLocalInstanceCount(config)); realm.close(); - assertEquals(0, Realm.getGlobalInstanceCount(config)); + assertEquals(0, Realm.getLocalInstanceCount(config)); } @Test diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 09f9a60eca..980218b28f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -402,7 +402,8 @@ static int getLocalThreadCount(RealmConfiguration configuration) { } else { int totalRefCount = 0; for (RealmCacheType type : RealmCacheType.values()) { - totalRefCount += cache.refAndCountMap.get(type).localCount.get(); + Integer localCount = cache.refAndCountMap.get(type).localCount.get(); + totalRefCount += (localCount != null) ? localCount : 0; } return totalRefCount; } From 6be2071d2d9aab52ef9fee4cb6b23701b7b5b097 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 17 Nov 2016 15:13:38 +0100 Subject: [PATCH 0212/2110] WiP --- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../src/main/java/io/realm/RealmResults.java | 71 +++++++++++++++---- .../java/io/realm/internal/SharedRealm.java | 3 +- .../java/io/realm/internal/TableQuery.java | 5 ++ 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 8d9ce2b0ad..3d156b033d 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -37,7 +37,7 @@ set(classes_LIST io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema - io.realm.RealmObjectSchema + io.realm.RealmObjectSchema io.realm.RealmResults ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 7bc4df26c5..29121bf41a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -89,6 +89,12 @@ public final class RealmResults extends AbstractList im // clear it. private boolean viewUpdated = false; + private final long nativePtr; + + static RealmResults createFromQuery(BaseRealm realm, TableQuery query, Class clazz, + String fieldNames[], Sort[] sortOrder) { + return new RealmResults(realm, query, clazz, fieldNames, sortOrder); + } static RealmResults createFromTableQuery(BaseRealm realm, TableQuery query, Class clazz) { return new RealmResults(realm, query, clazz); @@ -110,16 +116,34 @@ static RealmResults createFromDynamicTableOrView(BaseRealm r return realmResults; } + private RealmResults(BaseRealm realm, TableQuery query, Class clazz, String fieldNames[], Sort[] sortOrder) { + this.realm = realm; + this.classSpec = clazz; + this.query = query; + + if (sortOrder.length != fieldNames.length) { + throw new IllegalArgumentException("Number of field names and sort orders does not match"); + } + + boolean[] order = new boolean[sortOrder.length]; + for (int i = 0; i < sortOrder.length; i++) { + order[i] = sortOrder[i] == Sort.ASCENDING; + } + this.nativePtr = nativeCreateResults(realm.sharedRealm.getNativePtr(), query.getNativePtr(), order); + } + private RealmResults(BaseRealm realm, TableQuery query, Class clazz) { this.realm = realm; this.classSpec = clazz; this.query = query; + this.nativePtr = 0; } private RealmResults(BaseRealm realm, TableQuery query, String className) { this.realm = realm; this.query = query; this.className = className; + this.nativePtr = 0; } private RealmResults(BaseRealm realm, TableOrView table, Class classSpec) { @@ -130,6 +154,7 @@ private RealmResults(BaseRealm realm, TableOrView table, Class classSpec) { this.pendingQuery = null; this.query = null; this.currentTableViewVersion = table.syncIfNeeded(); + this.nativePtr = 0; } private RealmResults(BaseRealm realm, String className) { @@ -138,6 +163,7 @@ private RealmResults(BaseRealm realm, String className) { pendingQuery = null; query = null; + this.nativePtr = 0; } private RealmResults(BaseRealm realm, TableOrView table, String className) { @@ -262,7 +288,7 @@ public E last() { @Override public E last(E defaultValue) { return lastImpl(false, defaultValue); - + } private E lastImpl(boolean shouldThrow, E defaultValue) { @@ -294,8 +320,12 @@ public void deleteFromRealm(int location) { public boolean deleteAllFromRealm() { realm.checkIfValid(); if (size() > 0) { - TableOrView table = getTableOrView(); - table.clear(); + if (nativePtr == 0) { + TableOrView table = getTableOrView(); + table.clear(); + } else { + nativeClear(nativePtr); + } return true; } else { return false; @@ -413,7 +443,12 @@ public int size() { if (!isLoaded()) { return 0; } else { - long size = getTableOrView().size(); + long size; + if (nativePtr == 0) { + size = getTableOrView().size(); + } else { + size = nativeSize(nativePtr); + } return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; } } @@ -424,15 +459,19 @@ public int size() { public Number min(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - switch (table.getColumnType(columnIndex)) { - case INTEGER: - return table.minimumLong(columnIndex); - case FLOAT: - return table.minimumFloat(columnIndex); - case DOUBLE: - return table.minimumDouble(columnIndex); - default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + if (nativePtr == 0) { + switch (table.getColumnType(columnIndex)) { + case INTEGER: + return table.minimumLong(columnIndex); + case FLOAT: + return table.minimumFloat(columnIndex); + case DOUBLE: + return table.minimumDouble(columnIndex); + default: + throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + } + } else { + return nativeAggregate(nativePtr, columnIndex, 1); } } @@ -1056,4 +1095,10 @@ void notifyChangeListeners(boolean forceNotify) { } } } + + native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, boolean[] order); + native long nativeGetRow(long nativePtr, int index); + native void nativeClear(long nativePtr); + native long nativeSize(long nativePtr); + native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index e7ad6c0417..9f9cf2d509 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -211,7 +211,8 @@ public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier r } } - long getNativePtr() { + // FIXME: can it be protected? + public long getNativePtr() { return nativePtr; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 5060ad4e23..80cffcbe11 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -60,6 +60,11 @@ public TableQuery(Context context, Table table, long nativeQueryPtr, TableOrView this.origin = origin; } + // FIXME: can it be protected? + public long getNativePtr() { + return this.nativePtr; + } + public void close() { synchronized (context) { if (nativePtr != 0) { From 32fd0ef760eab9c5fec335c615360f6493872db5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 18 Nov 2016 11:56:01 +0100 Subject: [PATCH 0213/2110] Release v2.2.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index bd134637f0..fae692e41d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.2.1-SNAPSHOT \ No newline at end of file +2.2.1 \ No newline at end of file From f4d67efdc1c02eb2768562ff001239943777e232 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 18 Nov 2016 11:56:01 +0100 Subject: [PATCH 0214/2110] Prepare next release v2.2.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index fae692e41d..a031df061e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.2.1 \ No newline at end of file +2.2.2-SNAPSHOT \ No newline at end of file From 525f86e4d10d2566041913e2e883c374ce80992e Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 18 Nov 2016 17:59:08 +0100 Subject: [PATCH 0215/2110] WiP - start using Object Store's Results class --- .../realm-library/src/main/cpp/CMakeLists.txt | 1 + .../src/main/cpp/io_realm_RealmResults.cpp | 164 ++++++++++++++++++ .../cpp/io_realm_internal_SharedRealm.cpp | 18 +- .../src/main/cpp/io_realm_internal_Util.cpp | 3 + realm/realm-library/src/main/cpp/util.cpp | 2 + realm/realm-library/src/main/cpp/util.hpp | 6 + .../src/main/java/io/realm/RealmResults.java | 157 ++++++++++++----- 7 files changed, 303 insertions(+), 48 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 3d156b033d..187ae3e1a1 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -157,6 +157,7 @@ file(GLOB objectstore_SRC "object-store/src/schema.cpp" "object-store/src/index_set.cpp" "object-store/src/shared_realm.cpp" + "object-store/src/results.cpp" "object-store/src/impl/realm_coordinator.cpp" "object-store/src/impl/collection_notifier.cpp" "object-store/src/impl/collection_change_builder.cpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp new file mode 100644 index 0000000000..9da609ef39 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp @@ -0,0 +1,164 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 +#include "io_realm_RealmResults.h" + +#include + +#include +#include + +#include "util.hpp" + +using namespace realm; + +JNIEXPORT jlong JNICALL +Java_io_realm_RealmResults_nativeCreateResults(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong query_ptr, jlongArray colunm_indices, jbooleanArray jsort_orders) { + TR_ENTER() + try { + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto query = reinterpret_cast(query_ptr); + + JniBooleanArray order(env, jsort_orders); + JniLongArray indices(env, colunm_indices); + + std::vector sort_order; + std::vector> sort_indices; + for(jsize i = 0; i < order.len(); ++i) { + sort_order.push_back(to_bool(order[i])); + sort_indices.push_back(std::vector { S(indices[i]) }); + } + + SortDescriptor sort_descriptor(*(query->get_table().get()), sort_indices, sort_order); + Results results(shared_realm, *query, sort_descriptor); + return reinterpret_cast(new Results(std::move(results))); + } CATCH_STD() + return reinterpret_cast(nullptr); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_RealmResults_nativeCreateSnapshort(JNIEnv* env, jclass, jlong native_ptr) { + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + auto snapshot = results->snapshot(); + return reinterpret_cast(new Results(snapshot)); + } CATCH_STD() + return reinterpret_cast(nullptr); +} + +// FIXME: we don't use it at the moment +JNIEXPORT jlong JNICALL +Java_io_realm_RealmResults_nativeGetRow(JNIEnv *env, jclass, jlong native_ptr, jint index) { + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + auto row = results->get(static_cast(index)); + return reinterpret_cast(new Row(std::move(row))); + } CATCH_STD() + return reinterpret_cast(nullptr); +} + +JNIEXPORT void JNICALL +Java_io_realm_RealmResults_nativeClear(JNIEnv *env, jclass, jlong native_ptr) { + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + results->clear(); + } CATCH_STD() +} + +JNIEXPORT jlong JNICALL +Java_io_realm_RealmResults_nativeSize(JNIEnv *env, jclass, jlong native_ptr) { + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + return static_cast(results->size()); + } CATCH_STD() + return 0; +} + +JNIEXPORT jobject JNICALL +Java_io_realm_RealmResults_nativeAggregate(JNIEnv *env, jclass, jlong native_ptr, jlong column_index, jbyte agg_func) { + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + + size_t index = S(column_index); + Optional value; + switch (agg_func) { + case io_realm_RealmResults_AGGREGATE_FUNCTION_MINIMUM: + value = results->min(index); + break; + case io_realm_RealmResults_AGGREGATE_FUNCTION_MAXIMUM: + value = results->max(index); + break; + case io_realm_RealmResults_AGGREGATE_FUNCTION_AVERAGE: + value = results->average(index); + break; + case io_realm_RealmResults_AGGREGATE_FUNCTION_SUM: + value = results->sum(index); + break; + } + + if (!value) { + return static_cast(nullptr); + } + + Mixed m = *value; + switch (m.get_type()) { + case type_Int: + return NewLong(env, m.get_int()); + case type_Float: + return NewFloat(env, m.get_float()); + case type_Double: + return NewDouble(env, m.get_double()); + case type_Timestamp: + return NewDate(env, m.get_timestamp()); + default: + throw std::invalid_argument("Excepted numeric type"); + } + } CATCH_STD() + return static_cast(nullptr); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_RealmResults_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jlongArray colunm_indices, jbooleanArray jsort_orders) { + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + + JniBooleanArray order(env, jsort_orders); + JniLongArray indices(env, colunm_indices); + + if (order.len() != indices.len()) { + throw std::invalid_argument("Number of columns and sorting orders do not match."); + } + + std::vector sort_orders; + std::vector> sort_indices; + for(jsize i = 0; i < order.len(); ++i) { + sort_orders.push_back(to_bool(order[i])); + sort_indices.push_back(std::vector { S(indices[i]) }); + } + + SortDescriptor sort_descriptor(*(results->get_query().get_table().get()), sort_indices, sort_orders); + auto sorted_result = results->sort(std::move(sort_descriptor)); + return reinterpret_cast(new Results(std::move(sorted_result))); + } CATCH_STD() + return reinterpret_cast(nullptr); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 6d200de454..1a847713e8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -1,4 +1,20 @@ -#include +/* + * Copyright 2016 Realm Inc. + * + * 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 #include "io_realm_internal_SharedRealm.h" #include "object_store.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 241ce22908..d796eaa38a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -47,6 +47,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); java_lang_double = GetClass(env, "java/lang/Double"); java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); + java_util_date = GetClass(env, "java/util/Date"); + java_util_date_init = env->GetMethodID(java_util_date, "", "(J)V"); } return JNI_VERSION_1_6; @@ -62,6 +64,7 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) env->DeleteGlobalRef(java_lang_long); env->DeleteGlobalRef(java_lang_float); env->DeleteGlobalRef(java_lang_double); + env->DeleteGlobalRef(java_util_date); } } diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 1ba8e3e2b1..b30989344f 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -39,6 +39,8 @@ jclass java_lang_float; jmethodID java_lang_float_init; jclass java_lang_double; jmethodID java_lang_double_init; +jclass java_util_date; +jmethodID java_util_date_init; jclass session_class_ref; jmethodID session_error_handler; diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 68b9a89ee6..a214e9f27b 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -666,6 +666,8 @@ extern jclass java_lang_float; extern jmethodID java_lang_float_init; extern jclass java_lang_double; extern jmethodID java_lang_double_init; +extern jclass java_util_date; +extern jmethodID java_util_date_init; // FIXME Move to own library extern jclass session_class_ref; @@ -704,6 +706,10 @@ inline realm::Timestamp from_milliseconds(jlong milliseconds) return realm::Timestamp(seconds, nanoseconds); } +inline jobject NewDate(JNIEnv* env, const realm::Timestamp& ts) { + return env->NewObject(java_util_date, java_util_date_init, to_milliseconds(ts)); +} + extern const std::string TABLE_PREFIX; static inline bool to_bool(jboolean b) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 29121bf41a..5a2aa84686 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -91,6 +91,12 @@ public final class RealmResults extends AbstractList im private final long nativePtr; + // Public for static checking in JNI + public static final byte AGGREGATE_FUNCTION_MINIMUM = 1; + public static final byte AGGREGATE_FUNCTION_MAXIMUM = 2; + public static final byte AGGREGATE_FUNCTION_AVERAGE = 3; + public static final byte AGGREGATE_FUNCTION_SUM = 4; + static RealmResults createFromQuery(BaseRealm realm, TableQuery query, Class clazz, String fieldNames[], Sort[] sortOrder) { return new RealmResults(realm, query, clazz, fieldNames, sortOrder); @@ -126,10 +132,13 @@ private RealmResults(BaseRealm realm, TableQuery query, Class clazz, String f } boolean[] order = new boolean[sortOrder.length]; + long[] indices = new long[sortOrder.length]; for (int i = 0; i < sortOrder.length; i++) { order[i] = sortOrder[i] == Sort.ASCENDING; + indices[i] = getColumnIndexForSort(fieldNames[i]); } - this.nativePtr = nativeCreateResults(realm.sharedRealm.getNativePtr(), query.getNativePtr(), order); + + this.nativePtr = nativeCreateResults(realm.sharedRealm.getNativePtr(), query.getNativePtr(), indices, order); } private RealmResults(BaseRealm realm, TableQuery query, Class clazz) { @@ -172,6 +181,13 @@ private RealmResults(BaseRealm realm, TableOrView table, String className) { this.currentTableViewVersion = table.syncIfNeeded(); } + private RealmResults(BaseRealm realm, String className, long nativePtr) { + this.realm = realm; + this.className = className; + this.nativePtr = nativePtr; + this.query = null; + } + TableOrView getTableOrView() { if (table == null) { return realm.schema.getTable(classSpec); @@ -404,7 +420,12 @@ private long getColumnIndexForSort(String fieldName) { */ @Override public RealmResults sort(String fieldName) { - return this.sort(fieldName, Sort.ASCENDING); + if (nativePtr == 0) { + return this.sort(fieldName, Sort.ASCENDING); + } else { + long ptr = nativeSort(nativePtr, new long[]{getColumnIndexForSort(fieldName)}, new boolean[]{Sort.ASCENDING.getValue()}); + return new RealmResults(realm, className, ptr); + } } /** @@ -412,7 +433,12 @@ public RealmResults sort(String fieldName) { */ @Override public RealmResults sort(String fieldName, Sort sortOrder) { - return where().findAllSorted(fieldName, sortOrder); + if (nativePtr == 0) { + return where().findAllSorted(fieldName, sortOrder); + } else { + long ptr = nativeSort(nativePtr, new long[]{getColumnIndexForSort(fieldName)}, new boolean[]{sortOrder == Sort.ASCENDING}); + return new RealmResults(realm, className, ptr); + } } /** @@ -420,7 +446,22 @@ public RealmResults sort(String fieldName, Sort sortOrder) { */ @Override public RealmResults sort(String fieldNames[], Sort sortOrders[]) { - return where().findAllSorted(fieldNames, sortOrders); + if (nativePtr == 0) { + return where().findAllSorted(fieldNames, sortOrders); + } else { + long columnIndices[] = new long[fieldNames.length]; + for(int i = 0; i < fieldNames.length; i++) { + columnIndices[i] = getColumnIndexForSort(fieldNames[i]); + } + + boolean orders[] = new boolean[sortOrders.length]; + for(int i = 0; i < sortOrders.length; i++) { + orders[i] = sortOrders[i].getValue() + } + + long ptr = nativeSort(nativePtr, columnIndices, orders); + return new RealmResults(realm, className, ptr); + } } /** @@ -471,7 +512,7 @@ public Number min(String fieldName) { throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); } } else { - return nativeAggregate(nativePtr, columnIndex, 1); + return (Number) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_MINIMUM); } } @@ -481,11 +522,14 @@ public Number min(String fieldName) { public Date minDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - if (table.getColumnType(columnIndex) == RealmFieldType.DATE) { - return table.minimumDate(columnIndex); - } - else { - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "Date")); + if (nativePtr == 0) { + if (table.getColumnType(columnIndex) == RealmFieldType.DATE) { + return table.minimumDate(columnIndex); + } else { + throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "Date")); + } + } else { + return (Date) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_MINIMUM); } } @@ -495,15 +539,19 @@ public Date minDate(String fieldName) { public Number max(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - switch (table.getColumnType(columnIndex)) { - case INTEGER: - return table.maximumLong(columnIndex); - case FLOAT: - return table.maximumFloat(columnIndex); - case DOUBLE: - return table.maximumDouble(columnIndex); - default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + if (nativePtr == 0) { + switch (table.getColumnType(columnIndex)) { + case INTEGER: + return table.maximumLong(columnIndex); + case FLOAT: + return table.maximumFloat(columnIndex); + case DOUBLE: + return table.maximumDouble(columnIndex); + default: + throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + } + } else { + return (Number) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_MAXIMUM); } } @@ -520,11 +568,14 @@ public Number max(String fieldName) { public Date maxDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - if (table.getColumnType(columnIndex) == RealmFieldType.DATE) { - return table.maximumDate(columnIndex); - } - else { - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "Date")); + if (nativePtr == 0) { + if (table.getColumnType(columnIndex) == RealmFieldType.DATE) { + return table.maximumDate(columnIndex); + } else { + throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "Date")); + } + } else { + return (Date) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_MAXIMUM); } } @@ -535,15 +586,19 @@ public Date maxDate(String fieldName) { public Number sum(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - switch (table.getColumnType(columnIndex)) { - case INTEGER: - return table.sumLong(columnIndex); - case FLOAT: - return table.sumFloat(columnIndex); - case DOUBLE: - return table.sumDouble(columnIndex); - default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + if (nativePtr == 0) { + switch (table.getColumnType(columnIndex)) { + case INTEGER: + return table.sumLong(columnIndex); + case FLOAT: + return table.sumFloat(columnIndex); + case DOUBLE: + return table.sumDouble(columnIndex); + default: + throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + } + } else { + return (Number) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_SUM); } } @@ -553,15 +608,21 @@ public Number sum(String fieldName) { public double average(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - switch (table.getColumnType(columnIndex)) { - case INTEGER: - return table.averageLong(columnIndex); - case DOUBLE: - return table.averageDouble(columnIndex); - case FLOAT: - return table.averageFloat(columnIndex); - default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + if (nativePtr == 0) { + switch (table.getColumnType(columnIndex)) { + case INTEGER: + return table.averageLong(columnIndex); + case DOUBLE: + return table.averageDouble(columnIndex); + case FLOAT: + return table.averageFloat(columnIndex); + default: + throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + } + } else { + // FIXME: Should we change return type to Double? + Number sum = (Number) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_AVERAGE); + return sum.doubleValue(); } } @@ -1096,9 +1157,11 @@ void notifyChangeListeners(boolean forceNotify) { } } - native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, boolean[] order); - native long nativeGetRow(long nativePtr, int index); - native void nativeClear(long nativePtr); - native long nativeSize(long nativePtr); - native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); + private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long[] columnIndices, boolean[] orders); + private static native long nativeCreateSnapshot(long nativePtr); + private static native long nativeGetRow(long nativePtr, int index); + private static native void nativeClear(long nativePtr); + private static native long nativeSize(long nativePtr); + private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); + private static native long nativeSort(long nativePtr, long[] columnIndices, boolean[] orders); } From ee31c66ef434d715ff5742ef8a3fd9470825fc3c Mon Sep 17 00:00:00 2001 From: Adam Lebsack Date: Mon, 21 Nov 2016 10:26:30 +0100 Subject: [PATCH 0216/2110] Get ROS logs --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index d31960c03e..985b6eae74 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -106,6 +106,7 @@ try { } } } finally { + sh "docker logs ros" rosContainer.stop() } } From c6c7ca4bacf4e2c2f8afcca0f7ef82ab410f169d Mon Sep 17 00:00:00 2001 From: Adam Lebsack Date: Mon, 21 Nov 2016 10:47:38 +0100 Subject: [PATCH 0217/2110] Specify httpdispatcher version --- tools/sync_test_server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index e659183e3b..32fc05da4b 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -6,7 +6,7 @@ ARG ROS_DE_VERSION RUN apt-get update -qq \ && apt-get install -y curl npm \ && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash \ - && npm install winston temp httpdispatcher + && npm install winston temp httpdispatcher@1.1.0 COPY keys/private.pem keys/public.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ # Install realm object server From 44701daf7bcf7140c4b50339bd26dca15006c2ac Mon Sep 17 00:00:00 2001 From: Adam Lebsack Date: Mon, 21 Nov 2016 10:55:50 +0100 Subject: [PATCH 0218/2110] Use httpdispatcher@1.0.0 --- tools/sync_test_server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 32fc05da4b..001f6ada49 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -6,7 +6,7 @@ ARG ROS_DE_VERSION RUN apt-get update -qq \ && apt-get install -y curl npm \ && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash \ - && npm install winston temp httpdispatcher@1.1.0 + && npm install winston temp httpdispatcher@1.0.0 COPY keys/private.pem keys/public.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ # Install realm object server From 21d33c1c76434c81c613ce1b33457ef78cc9045a Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 21 Nov 2016 12:38:43 +0100 Subject: [PATCH 0219/2110] Handling Results exceptions. Adding support for contains/index_of --- .../src/main/cpp/io_realm_RealmResults.cpp | 14 +++++++++++++- realm/realm-library/src/main/cpp/util.cpp | 16 ++++++++++++++++ realm/realm-library/src/main/cpp/util.hpp | 4 ++++ .../src/main/java/io/realm/RealmResults.java | 19 ++++++++++++++++--- .../java/io/realm/internal/NativeObject.java | 3 ++- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp index 9da609ef39..5ef1d4cb9f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp @@ -51,7 +51,7 @@ Java_io_realm_RealmResults_nativeCreateResults(JNIEnv* env, jclass, jlong shared } JNIEXPORT jlong JNICALL -Java_io_realm_RealmResults_nativeCreateSnapshort(JNIEnv* env, jclass, jlong native_ptr) { +Java_io_realm_RealmResults_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { auto results = reinterpret_cast(native_ptr); @@ -61,6 +61,18 @@ Java_io_realm_RealmResults_nativeCreateSnapshort(JNIEnv* env, jclass, jlong nati return reinterpret_cast(nullptr); } +JNIEXPORT jboolean JNICALL +Java_io_realm_RealmResults_nativeContains(JNIEnv *env, jclass, jlong native_ptr, jlong native_row_ptr) { + TR_ENTER_PTR(native_ptr); + try { + auto results = reinterpret_cast(native_ptr); + auto row = reinterpret_cast(native_row_ptr); + size_t index = results->index_of(*row); + return to_jbool(index != not_found); + } CATCH_STD(); + return JNI_FALSE; +} + // FIXME: we don't use it at the moment JNIEXPORT jlong JNICALL Java_io_realm_RealmResults_nativeGetRow(JNIEnv *env, jclass, jlong native_ptr, jint index) { diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index b30989344f..029f7c4cb3 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -25,6 +25,7 @@ #include "io_realm_internal_Util.h" #include "io_realm_internal_SharedRealm.h" #include "shared_realm.hpp" +#include "results.hpp" using namespace std; using namespace realm; @@ -80,6 +81,21 @@ void ConvertException(JNIEnv* env, const char *file, int line) ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalArgument, ss.str()); } + catch (Results::OutOfBoundsIndexException& e) { + ss << "Out of range in " << file << " line " << line + << "(requested: " << e.requested << " valid: " << e.valid_count << ")"; + ThrowException(env, IndexOutOfBounds, ss.str()); + } + catch (Results::IncorrectTableException& e) { + ss << "Incorrect class in " << file << " line " << line + << "(actual: " << e.actual << " expected: " << e.expected << ")"; + ThrowException(env, IllegalArgument, ss.str()); + } + catch (Results::UnsupportedColumnTypeException& e) { + ss << "Unsupported type in " << file << " line " << line + << "(field name: " << e.column_name << ")"; + ThrowException(env, IllegalArgument, ss.str()); + } catch (exception& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, FatalError, ss.str()); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index a214e9f27b..53825bf175 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -716,4 +716,8 @@ static inline bool to_bool(jboolean b) { return b == JNI_TRUE; } +static inline jboolean to_jbool(bool b) { + return b?JNI_TRUE:JNI_FALSE; +} + #endif // REALM_JAVA_UTIL_HPP diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 5a2aa84686..109a222cac 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -31,12 +31,14 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; +import io.realm.internal.CheckedRow; import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.TableQuery; import io.realm.internal.TableView; +import io.realm.internal.UncheckedRow; import io.realm.internal.async.BadVersionException; import io.realm.log.RealmLog; import rx.Observable; @@ -234,8 +236,18 @@ public boolean contains(Object object) { boolean contains = false; if (isLoaded() && object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; - if (realm.getPath().equals(proxy.realmGet$proxyState().getRealm$realm().getPath()) && proxy.realmGet$proxyState().getRow$realm() != InvalidRow.INSTANCE) { - contains = (table.sourceRowIndex(proxy.realmGet$proxyState().getRow$realm().getIndex()) != TableOrView.NO_MATCH); + if (nativePtr == 0) { + if (realm.getPath().equals(proxy.realmGet$proxyState().getRealm$realm().getPath()) && proxy.realmGet$proxyState().getRow$realm() != InvalidRow.INSTANCE) { + contains = (table.sourceRowIndex(proxy.realmGet$proxyState().getRow$realm().getIndex()) != TableOrView.NO_MATCH); + } + } else { + if (realm instanceof DynamicRealm) { + UncheckedRow row = (UncheckedRow) proxy.realmGet$proxyState().getRow$realm(); + contains = nativeContains(nativePtr, row.nativePointer); + } else { + CheckedRow row = (CheckedRow) proxy.realmGet$proxyState().getRow$realm(); + contains = nativeContains(nativePtr, row.nativePointer); + } } } return contains; @@ -456,7 +468,7 @@ public RealmResults sort(String fieldNames[], Sort sortOrders[]) { boolean orders[] = new boolean[sortOrders.length]; for(int i = 0; i < sortOrders.length; i++) { - orders[i] = sortOrders[i].getValue() + orders[i] = sortOrders[i].getValue(); } long ptr = nativeSort(nativePtr, columnIndices, orders); @@ -1160,6 +1172,7 @@ void notifyChangeListeners(boolean forceNotify) { private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long[] columnIndices, boolean[] orders); private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); + private static native boolean nativeContains(long nativePtr, long nativeRowPtr); private static native void nativeClear(long nativePtr); private static native long nativeSize(long nativePtr); private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); diff --git a/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java b/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java index cad7f390cc..7451722546 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java @@ -22,5 +22,6 @@ * All Java classes wrapping a core class should extend NativeObject. */ public abstract class NativeObject { - long nativePointer; + // FIXME: can it be protected? + public long nativePointer; } From 73778e8eada83d3ce8ec2802dc58c6f283456606 Mon Sep 17 00:00:00 2001 From: Adam Lebsack Date: Mon, 21 Nov 2016 10:26:30 +0100 Subject: [PATCH 0220/2110] Get ROS logs --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index d31960c03e..985b6eae74 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -106,6 +106,7 @@ try { } } } finally { + sh "docker logs ros" rosContainer.stop() } } From cc0fbb7b5e8da92b81a6c07dd250816dafa053d4 Mon Sep 17 00:00:00 2001 From: Adam Lebsack Date: Mon, 21 Nov 2016 10:47:38 +0100 Subject: [PATCH 0221/2110] Specify httpdispatcher version --- tools/sync_test_server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index e659183e3b..32fc05da4b 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -6,7 +6,7 @@ ARG ROS_DE_VERSION RUN apt-get update -qq \ && apt-get install -y curl npm \ && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash \ - && npm install winston temp httpdispatcher + && npm install winston temp httpdispatcher@1.1.0 COPY keys/private.pem keys/public.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ # Install realm object server From 6540d56df4e8b00710c1385dbbdbe9ae2a3d9e8b Mon Sep 17 00:00:00 2001 From: Adam Lebsack Date: Mon, 21 Nov 2016 10:55:50 +0100 Subject: [PATCH 0222/2110] Use httpdispatcher@1.0.0 --- tools/sync_test_server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 32fc05da4b..001f6ada49 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -6,7 +6,7 @@ ARG ROS_DE_VERSION RUN apt-get update -qq \ && apt-get install -y curl npm \ && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash \ - && npm install winston temp httpdispatcher@1.1.0 + && npm install winston temp httpdispatcher@1.0.0 COPY keys/private.pem keys/public.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ # Install realm object server From 87d1924ec7d17806ef055cb5e441ce0d4d2e7250 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 21 Nov 2016 16:19:27 +0100 Subject: [PATCH 0223/2110] Upgrade to latest version of ObjectStore including Sync (#3764) --- .../realm-library/src/main/cpp/CMakeLists.txt | 23 ++++++------------- .../src/main/cpp/io_realm_RealmSchema.cpp | 2 +- .../main/cpp/io_realm_internal_LinkView.cpp | 2 +- .../cpp/io_realm_internal_SharedRealm.cpp | 23 +++++++++++-------- .../src/main/cpp/io_realm_internal_Table.cpp | 2 +- .../main/cpp/io_realm_internal_TableQuery.cpp | 4 ++-- .../cpp/io_realm_internal_UncheckedRow.cpp | 10 ++++---- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/cpp/objectserver_shared.hpp | 2 +- 9 files changed, 32 insertions(+), 38 deletions(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 8d9ce2b0ad..544b5a5e31 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -118,9 +118,9 @@ endif() # 'best.m_nanoseconds' was declared here set(WARNING_CXX_FLAGS "-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros \ -Wno-missing-field-initializers -Wmissing-declarations -Wno-error=uninitialized -Wno-error=maybe-uninitialized") -set(REALM_COMMON_CXX_FLAGS "-DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14 -fsigned-char") +set(REALM_COMMON_CXX_FLAGS "-DREALM_ANDROID -DREALM_HAVE_CONFIG -DREALM_HAVE_EPOLL -DPIC -pthread -fvisibility=hidden -std=c++14 -fsigned-char") if (build_SYNC) - set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_SYNC") + set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ENABLE_SYNC") endif() # There might be an issue with -Os of ndk gcc 4.9. It will hang the encryption related tests. # And this issue doesn't seem to impact the core compiling. @@ -151,24 +151,15 @@ endif() # Object Store source files file(GLOB objectstore_SRC - "object-store/src/collection_notifications.cpp" - "object-store/src/object_schema.cpp" - "object-store/src/object_store.cpp" - "object-store/src/schema.cpp" - "object-store/src/index_set.cpp" - "object-store/src/shared_realm.cpp" - "object-store/src/impl/realm_coordinator.cpp" - "object-store/src/impl/collection_notifier.cpp" - "object-store/src/impl/collection_change_builder.cpp" - "object-store/src/impl/transact_log_handler.cpp" - "object-store/src/impl/weak_realm_notifier.cpp" - "object-store/src/impl/android/*.cpp" + "object-store/src/*.cpp" + "object-store/src/impl/*.cpp" + "object-store/src/impl/epoll/*.cpp" "object-store/src/util/*.cpp") + # Sync needed Object Store files if (build_SYNC) file(GLOB objectstore_sync_SRC - "object-store/src/sync_manager.cpp" - "object-store/src/sync_session.cpp") + "object-store/src/sync/*.cpp") endif() add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp index 98649caff4..9c6f1992e3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp @@ -43,7 +43,7 @@ Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv *env, jclass, jlongArray o } JNIEXPORT void JNICALL -Java_io_realm_RealmSchema_nativeClose(JNIEnv *env, jclass, jlong nativePtr) { +Java_io_realm_RealmSchema_nativeClose(JNIEnv*, jclass, jlong nativePtr) { TR_ENTER_PTR(nativePtr) Schema* schema = reinterpret_cast(nativePtr); delete schema; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp index 52079074e5..a7041451c2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp @@ -223,7 +223,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveAllTargetRows } JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable - (JNIEnv* env, jobject, jlong nativeLinkViewPtr) + (JNIEnv*, jobject, jlong nativeLinkViewPtr) { TR_ENTER_PTR(nativeLinkViewPtr) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 6d200de454..a1d191036d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -1,14 +1,14 @@ -#include #include "io_realm_internal_SharedRealm.h" +#ifdef REALM_ENABLE_SYNC +#include "object-store/src/sync/sync_manager.hpp" +#include "object-store/src/sync/sync_config.hpp" +#endif #include "object_store.hpp" #include "shared_realm.hpp" #include "java_binding_context.hpp" #include "util.hpp" -#ifdef REALM_SYNC -#include "sync_config.hpp" -#endif using namespace realm; using namespace realm::_impl; @@ -53,11 +53,14 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri config->cache = cache; config->disable_format_upgrade = disable_format_upgrade; config->automatic_change_notifications = auto_change_notification; -#ifdef REALM_SYNC +#ifdef REALM_ENABLE_SYNC if (sync_server_url) { JStringAccessor url(env, sync_server_url); JStringAccessor token(env, sync_user_token); - config->sync_config = std::make_shared(token, url, nullptr, SyncSessionStopPolicy::Immediately); + // FIXME: Ignore User token for now. Will be fixed when moving to OS + // For now the Java session takes care of users + config->sync_config = std::make_shared(nullptr, url, SyncSessionStopPolicy::Immediately, + nullptr, nullptr); // FIXME: Sync session is handled by java now. Remove this when adapt to OS sync implementation. config->sync_config->create_session = false; } @@ -69,7 +72,7 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri } JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv* env, jclass, jlong config_ptr) +Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv*, jclass, jlong config_ptr) { TR_ENTER_PTR(config_ptr) @@ -94,7 +97,7 @@ Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlo } JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv* env, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -137,7 +140,7 @@ Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv *env, jclass, JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -254,7 +257,7 @@ Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong } JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv* env, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 6d1aac6c62..0981f9fea2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1406,7 +1406,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid( } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClose( - JNIEnv* env, jclass, jlong nativeTablePtr) + JNIEnv*, jclass, jlong nativeTablePtr) { TR_ENTER_PTR(nativeTablePtr) LangBindHelper::unbind_table_ptr(TBL(nativeTablePtr)); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index b5e89f153b..35c16002b0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -46,7 +46,7 @@ const char* ERR_IMPORT_CLOSED_REALM = "Can not import results from a closed Real const char* ERR_SORT_NOT_SUPPORTED = "Sort is not supported on binary data, object references and RealmList"; //------------------------------------------------------- -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeClose(JNIEnv* env, jclass, jlong nativeQueryPtr) { +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeClose(JNIEnv*, jclass, jlong nativeQueryPtr) { TR_ENTER_PTR(nativeQueryPtr) delete Q(nativeQueryPtr); } @@ -1766,7 +1766,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeCloseQueryHandover - (JNIEnv* env, jclass, jlong nativeHandoverQuery) + (JNIEnv*, jclass, jlong nativeHandoverQuery) { TR_ENTER_PTR(nativeHandoverQuery) delete HO(Query, nativeHandoverQuery); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 6ee249c77e..9e8465cca8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -20,7 +20,7 @@ using namespace realm; JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount - (JNIEnv *env, jobject, jlong nativeRowPtr) + (JNIEnv*, jobject, jlong nativeRowPtr) { TR_ENTER_PTR(nativeRowPtr) if (!ROW(nativeRowPtr)->is_attached()) @@ -57,7 +57,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex } JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) + (JNIEnv*, jobject, jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) return static_cast( ROW(nativeRowPtr)->get_column_type( S(columnIndex)) ); // noexcept @@ -329,14 +329,14 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeClose - (JNIEnv* env, jclass, jlong nativeRowPtr) + (JNIEnv*, jclass, jlong nativeRowPtr) { TR_ENTER_PTR(nativeRowPtr) delete ROW(nativeRowPtr); } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsAttached - (JNIEnv* env, jobject, jlong nativeRowPtr) + (JNIEnv*, jobject, jlong nativeRowPtr) { TR_ENTER_PTR(nativeRowPtr) return ROW(nativeRowPtr)->is_attached(); @@ -350,7 +350,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeHasColumn } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { + (JNIEnv*, jobject, jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) return ROW(nativeRowPtr)->is_null(columnIndex); } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index bafafb1464..094cbdd336 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit bafafb1464494d0a731a036399aa5c944d92a5bf +Subproject commit 094cbdd336462fa40ade11536497022f7722ca24 diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 1253f539d4..75d7a38cba 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include "util.hpp" From 393f676b5e33daba4feff2377f5feaa1db683b75 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 22 Nov 2016 23:33:31 +0900 Subject: [PATCH 0224/2110] Added a support for androidTest on API 9 devices (#3824) * Added a support for androidTest on API 9 devices * simplify build.gradle * use hamcrest-library instead of hamcrest-all * no need to exclude hamcrest-core --- realm/realm-library/build.gradle | 1 + .../src/androidTest/AndroidManifest.xml | 2 +- .../realm/RealmJsonAbsentPrimaryKeyTests.java | 11 ++++ .../java/io/realm/RealmJsonTests.java | 57 +++++++++++++++++++ .../java/io/realm/RealmModelTests.java | 5 ++ .../androidTest/java/io/realm/RealmTests.java | 17 ++++-- .../io/realm/TypeBasedNotificationsTests.java | 7 +++ .../io/realm/internal/JNITableViewTest.java | 6 ++ .../src/main/AndroidManifest.xml | 5 +- 9 files changed, 102 insertions(+), 9 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 71b1a6bda0..a0810cb77c 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -135,6 +135,7 @@ dependencies { androidTestCompile 'com.android.support.test:rules:0.5' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' + androidTestCompile 'org.hamcrest:hamcrest-library:1.3' androidTestCompile 'com.opencsv:opencsv:3.4' androidTestCompile 'dk.ilios:spanner:0.6.0' androidTestAnnotationProcessor project(':realm-annotations-processor') diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index a44988ce92..c7741a695f 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -10,7 +10,7 @@ diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java index c985be7e62..58fa4950b3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java @@ -16,6 +16,8 @@ package io.realm; +import android.os.Build; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -37,6 +39,9 @@ import io.realm.entities.PrimaryKeyAsString; import io.realm.rule.TestRealmConfigurationFactory; +import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; +import static org.junit.Assume.assumeThat; + @RunWith(Parameterized.class) public class RealmJsonAbsentPrimaryKeyTests { @Rule @@ -122,6 +127,8 @@ public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonObject() throw // Testing absent primary key value for createObjectFromJson() stream version @Test public void createObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + realm.beginTransaction(); thrown.expect(IllegalArgumentException.class); realm.createObjectFromJson(clazz, TestHelper.stringToStream(jsonString)); @@ -140,6 +147,8 @@ public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonStream() th // Testing absent primary key value for createAllFromJson() stream version @Test public void createAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + JSONArray jsonArray = new JSONArray(); jsonArray.put(new JSONObject(jsonString)); realm.beginTransaction(); @@ -151,6 +160,8 @@ public void createAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONEx // Testing absent primary key value for createOrUpdateAllFromJson() stream version @Test public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + JSONArray jsonArray = new JSONArray(); jsonArray.put(new JSONObject(jsonString)); realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index 2f1219acd6..01733ddd9e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -17,6 +17,7 @@ package io.realm; import android.content.Context; +import android.os.Build; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.text.TextUtils; @@ -55,11 +56,13 @@ import io.realm.rule.TestRealmConfigurationFactory; import static io.realm.internal.test.ExtraTests.assertArrayEquals; +import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeThat; @RunWith(AndroidJUnit4.class) public class RealmJsonTests { @@ -693,12 +696,16 @@ public void createAllFromJson_stringNullClass() { @Test public void createAllFromJson_streamNull() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + realm.createAllFromJson(AllTypes.class, (InputStream) null); assertEquals(0, realm.where(AllTypes.class).count()); } @Test public void createObjectFromJson_streamAllSimpleTypes() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "all_simple_types.json"); realm.beginTransaction(); realm.createObjectFromJson(AllTypes.class, in); @@ -717,6 +724,8 @@ public void createObjectFromJson_streamAllSimpleTypes() throws IOException { @Test public void createObjectFromJson_streamDateAsLong() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "date_as_long.json"); realm.beginTransaction(); realm.createObjectFromJson(AllTypes.class, in); @@ -730,6 +739,8 @@ public void createObjectFromJson_streamDateAsLong() throws IOException { @Test public void createObjectFromJson_streamDateAsString() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "date_as_string.json"); realm.beginTransaction(); realm.createObjectFromJson(AllTypes.class, in); @@ -743,6 +754,8 @@ public void createObjectFromJson_streamDateAsString() throws IOException { @Test public void createObjectFromJson_streamDateAsISO8601String() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "date_as_iso8601_string.json"); realm.beginTransaction(); realm.createObjectFromJson(AllTypes.class, in); @@ -761,6 +774,8 @@ public void createObjectFromJson_streamDateAsISO8601String() throws IOException @Test public void createObjectFromJson_streamChildObject() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "single_child_object.json"); realm.beginTransaction(); realm.createObjectFromJson(AllTypes.class, in); @@ -773,6 +788,8 @@ public void createObjectFromJson_streamChildObject() throws IOException { @Test public void createObjectFromJson_streamEmptyChildObjectList() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "realmlist_empty.json"); realm.beginTransaction(); realm.createObjectFromJson(AllTypes.class, in); @@ -785,6 +802,8 @@ public void createObjectFromJson_streamEmptyChildObjectList() throws IOException @Test public void createObjectFromJson_streamChildObjectList() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "realmlist.json"); realm.beginTransaction(); realm.createObjectFromJson(AllTypes.class, in); @@ -797,6 +816,8 @@ public void createObjectFromJson_streamChildObjectList() throws IOException { @Test public void createAllFromJson_streamArray() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "array.json"); realm.beginTransaction(); realm.createAllFromJson(Dog.class, in); @@ -810,6 +831,8 @@ public void createAllFromJson_streamArray() throws IOException { // Test if Json object doesn't have the field, then the field should have default value. Stream version. @Test public void createObjectFromJson_streamNoValues() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "other_json_object.json"); realm.beginTransaction(); realm.createObjectFromJson(AllTypes.class, in); @@ -831,6 +854,8 @@ public void createObjectFromJson_streamNoValues() throws IOException { @Test public void createObjectFromJson_streamNullClass() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "array.json"); realm.beginTransaction(); assertNull(realm.createObjectFromJson(null, in)); @@ -840,6 +865,8 @@ public void createObjectFromJson_streamNullClass() throws IOException { @Test public void createObjectFromJson_streamNullJson() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "all_types_invalid.json"); realm.beginTransaction(); try { @@ -854,6 +881,8 @@ public void createObjectFromJson_streamNullJson() throws IOException { @Test public void createObjectFromJson_streamNullInputStream() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + realm.beginTransaction(); assertNull(realm.createObjectFromJson(AnnotationTypes.class, (InputStream) null)); realm.commitTransaction(); @@ -865,6 +894,8 @@ public void createObjectFromJson_streamNullInputStream() throws IOException { */ @Test public void createOrUpdateObjectFromJson_streamNullValues() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); Date date = new Date(0); obj.setColumnLong(1); // ID @@ -900,6 +931,8 @@ public void createOrUpdateObjectFromJson_streamNullValues() throws IOException { @Test public void createOrUpdateObjectFromJson_streamNullClass() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream in = TestHelper.loadJsonFromAssets(context, "all_types_primary_key_field_only.json"); realm.beginTransaction(); assertNull(realm.createOrUpdateObjectFromJson(null, in)); @@ -909,6 +942,8 @@ public void createOrUpdateObjectFromJson_streamNullClass() throws IOException { @Test public void createOrUpdateObjectFromJson_streamInvalidJson() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnLong(1); realm.beginTransaction(); @@ -929,6 +964,8 @@ public void createOrUpdateObjectFromJson_streamInvalidJson() throws IOException @Test public void createOrUpdateObjectFromJson_streamNoPrimaryKeyThrows() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + try { realm.createOrUpdateObjectFromJson(AllTypes.class, new TestHelper.StubInputStream()); fail(); @@ -938,6 +975,8 @@ public void createOrUpdateObjectFromJson_streamNoPrimaryKeyThrows() throws IOExc @Test public void createOrUpdateAllFromJson_streamInvalidJSonCurlyBracketThrows() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, TestHelper.stringToStream("{")); fail(); @@ -947,6 +986,8 @@ public void createOrUpdateAllFromJson_streamInvalidJSonCurlyBracketThrows() thro @Test public void createOrUpdateObjectFromJson_streamIgnoreUnsetProperties() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + realm.beginTransaction(); realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, TestHelper.loadJsonFromAssets(context, "list_alltypes_primarykey.json")); realm.commitTransaction(); @@ -961,6 +1002,8 @@ public void createOrUpdateObjectFromJson_streamIgnoreUnsetProperties() throws IO @Test public void createOrUpdateObjectFromJson_inputStream() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + realm.beginTransaction(); AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); @@ -982,6 +1025,8 @@ public void createOrUpdateObjectFromJson_inputStream() throws IOException { */ @Test public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream stream = TestHelper.stringToStream("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 @@ -1215,6 +1260,8 @@ public void createOrUpdateAllFromJson_jsonNullJson() { @Test public void createOrUpdateAllFromJson_streamNoPrimaryKeyThrows() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + try { realm.createOrUpdateAllFromJson(AllTypes.class, new TestHelper.StubInputStream()); fail(); @@ -1224,6 +1271,8 @@ public void createOrUpdateAllFromJson_streamNoPrimaryKeyThrows() throws IOExcept @Test public void createOrUpdateAllFromJson_streamInvalidJSonBracketThrows() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, TestHelper.stringToStream("[")); fail(); @@ -1289,6 +1338,8 @@ public void createOrUpdateAllFromJson_jsonArray() throws JSONException, IOExcept @Test public void createOrUpdateAllFromJson_inputStream() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + realm.beginTransaction(); realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, TestHelper.loadJsonFromAssets(context, "list_alltypes_primarykey.json")); realm.commitTransaction(); @@ -1328,6 +1379,8 @@ public void createAllFromJson_nullTypesJsonWithNulls() throws IOException, JSONE // Test creating objects form JSON stream, all nullable fields with null values or non-null values @Test public void createAllFromJson_nullTypesStreamJSONWithNulls() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + realm.beginTransaction(); realm.createAllFromJson(NullTypes.class, TestHelper.loadJsonFromAssets(context, "nulltypes.json")); realm.commitTransaction(); @@ -1485,6 +1538,8 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti */ @Test public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOException, JSONException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + String json = TestHelper.streamToString(TestHelper.loadJsonFromAssets(context, "nulltypes_invalid.json")); JSONArray array = new JSONArray(json); @@ -1612,6 +1667,8 @@ public void createObjectFromJson_objectNullClass() throws JSONException { */ @Test public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + InputStream stream = TestHelper.stringToStream("{\"id\": 1, \"name\": \"bar\"}"); realm.beginTransaction(); realm.createObject(OwnerPrimaryKey.class, 0); // id = 0 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index 4c1a04a037..95f063b2f2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -17,6 +17,7 @@ package io.realm; import android.content.Context; +import android.os.Build; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; @@ -44,9 +45,11 @@ import io.realm.rule.TestRealmConfigurationFactory; import static io.realm.internal.test.ExtraTests.assertArrayEquals; +import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeThat; // tests API methods when using a model class implementing RealmModel instead // of extending RealmObject. @@ -160,6 +163,8 @@ public void execute(Realm realm) { @Test public void createOrUpdateAllFromJson() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + realm.beginTransaction(); realm.createOrUpdateAllFromJson(AllTypesRealmModel.class, TestHelper.loadJsonFromAssets(context, "list_alltypes_primarykey.json")); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index a571b7bd9f..cb0ca8912a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -17,6 +17,7 @@ package io.realm; import android.content.Context; +import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -2017,17 +2018,25 @@ public void callMutableMethodOutsideTransaction() throws JSONException, IOExcept try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} - try { realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, jsonObjStream); fail(); } catch (IllegalStateException expected) {} + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + try { realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, jsonObjStream); fail(); } catch (IllegalStateException expected) {} + } try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} - try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStream2); fail(); } catch (IllegalStateException expected) {} + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStream2); fail(); } catch (IllegalStateException expected) {} + } try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} - try { realm.createAllFromJson(NoPrimaryKeyNullTypes.class, jsonArrStream); fail(); } catch (IllegalStateException expected) {} + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + try { realm.createAllFromJson(NoPrimaryKeyNullTypes.class, jsonArrStream); fail(); } catch (IllegalStateException expected) {} + } try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} - try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStream2); fail(); } catch (IllegalStateException expected) {} + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStream2);fail(); } catch (IllegalStateException expected) {} + } } // TODO: re-introduce this test mocking the ReferenceQueue instead of relying on the GC diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index b3784cbe05..5ce7dd5cb8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -16,6 +16,7 @@ package io.realm; import android.content.Context; +import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.support.test.InstrumentationRegistry; @@ -48,12 +49,14 @@ import io.realm.rule.TestRealmConfigurationFactory; import io.realm.util.RealmBackgroundTask; +import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeThat; @RunWith(AndroidJUnit4.class) public class TypeBasedNotificationsTests { @@ -250,6 +253,8 @@ public void onChange(PrimaryKeyAsLong object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_createObjectFromJson() { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + final Realm realm = looperThread.realm; realm.addChangeListener(new RealmChangeListener() { @Override @@ -356,6 +361,8 @@ public void onChange(AllTypes object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_createOrUpdateObjectFromJson() { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + final Realm realm = looperThread.realm; realm.addChangeListener(new RealmChangeListener() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java index e1088af833..47ed416aaa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java @@ -16,6 +16,7 @@ package io.realm.internal; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Before; @@ -28,6 +29,7 @@ import java.util.List; import java.util.Locale; +import io.realm.Realm; import io.realm.RealmFieldType; import io.realm.rule.TestRealmConfigurationFactory; @@ -35,6 +37,10 @@ @RunWith(AndroidJUnit4.class) public class JNITableViewTest { + static { + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); + } + @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); diff --git a/realm/realm-library/src/main/AndroidManifest.xml b/realm/realm-library/src/main/AndroidManifest.xml index 6c57d744f5..a5dc325ad0 100644 --- a/realm/realm-library/src/main/AndroidManifest.xml +++ b/realm/realm-library/src/main/AndroidManifest.xml @@ -1,5 +1,2 @@ - - - \ No newline at end of file + From 604df79e242600ddd02c256f720436943987e766 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 23 Nov 2016 19:55:26 +0800 Subject: [PATCH 0225/2110] Use Results for typed Query.findAll --- .../src/main/java/io/realm/RealmQuery.java | 3 ++- .../src/main/java/io/realm/RealmResults.java | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 566a050d44..593dc658c3 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1650,7 +1650,8 @@ public RealmResults findAll() { if (isDynamicQuery()) { realmResults = (RealmResults) RealmResults.createFromDynamicTableOrView(realm, query.findAll(), className); } else { - realmResults = RealmResults.createFromTableOrView(realm, query.findAll(), clazz); + //realmResults = RealmResults.createFromTableOrView(realm, query.findAll(), clazz); + realmResults = RealmResults.createFromQuery(realm, query, clazz, null, null); } return realmResults; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 109a222cac..b0d118e641 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -129,15 +129,19 @@ private RealmResults(BaseRealm realm, TableQuery query, Class clazz, String f this.classSpec = clazz; this.query = query; - if (sortOrder.length != fieldNames.length) { - throw new IllegalArgumentException("Number of field names and sort orders does not match"); - } - - boolean[] order = new boolean[sortOrder.length]; - long[] indices = new long[sortOrder.length]; - for (int i = 0; i < sortOrder.length; i++) { - order[i] = sortOrder[i] == Sort.ASCENDING; - indices[i] = getColumnIndexForSort(fieldNames[i]); + boolean[] order = null; + long[] indices = null; + + if (fieldNames != null && sortOrder != null) { + order = new boolean[sortOrder.length]; + indices = new long[sortOrder.length]; + if (sortOrder.length != fieldNames.length) { + throw new IllegalArgumentException("Number of field names and sort orders does not match"); + } + for (int i = 0; i < sortOrder.length; i++) { + order[i] = sortOrder[i] == Sort.ASCENDING; + indices[i] = getColumnIndexForSort(fieldNames[i]); + } } this.nativePtr = nativeCreateResults(realm.sharedRealm.getNativePtr(), query.getNativePtr(), indices, order); From 364aeca7a77a52e37b76492632a81d4ad8848919 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 23 Nov 2016 19:36:24 +0100 Subject: [PATCH 0226/2110] Update to latest OS master (#3835) --- realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/objectserver_shared.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 094cbdd336..a7df0504d6 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 094cbdd336462fa40ade11536497022f7722ca24 +Subproject commit a7df0504d6a5cd73d4ee6a9de6d38fe2f5c95bba diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 75d7a38cba..8d3dc3ff2c 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -50,7 +50,7 @@ class JniSession { auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator( realm::StringData(local_realm_path)); if (coordinator) { - coordinator->notify_others(); + coordinator->wake_up_notifier_worker(); } }; auto error_handler = [&, global_obj_ref_tmp](int error_code, std::string message) { From 0dbd6d6b734e608b0a5604ce0e9000ead9b5469e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 24 Nov 2016 17:20:30 +0800 Subject: [PATCH 0227/2110] Experimental with OS collection notification Seems working! --- .../io/realm/RealmChangeListenerTests.java | 31 +++++++++++++++++++ .../src/main/cpp/io_realm_RealmResults.cpp | 26 ++++++++++++++++ .../cpp/io_realm_internal_SharedRealm.cpp | 3 +- .../main/java/io/realm/AndroidNotifier.java | 2 ++ .../src/main/java/io/realm/RealmResults.java | 13 ++++++-- 5 files changed, 72 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java index b058f3357a..cb50a83935 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java @@ -27,6 +27,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.Cat; +import io.realm.entities.Dog; import io.realm.entities.pojo.AllTypesRealmModel; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; @@ -225,4 +226,34 @@ public void onChange(RealmResults result) { allTypes.setString(AllTypes.FIELD_STRING, "test data 1"); dynamicRealm.commitTransaction(); } + + @Test + @RunTestInLooperThread + // FIXME: Used for DEV. Remove before merge + public void myTest() { + Realm realm = looperThread.realm; + final RealmResults cats = realm.where(Cat.class).findAll(); + final RealmResults dogs = realm.where(Dog.class).findAll(); + looperThread.keepStrongReference.add(cats); + looperThread.keepStrongReference.add(dogs); + cats.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults result) { + assertEquals("cat1", result.first().getName()); + assertEquals("dog1", dogs.first().getName()); + looperThread.testComplete(); + } + }); + + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + Cat cat = realm.createObject(Cat.class); + cat.setName("cat1"); + + Dog dog = realm.createObject(Dog.class); + dog.setName("dog1"); + } + }); + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp index 5ef1d4cb9f..7075b29606 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp @@ -174,3 +174,29 @@ Java_io_realm_RealmResults_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jlo } CATCH_STD() return reinterpret_cast(nullptr); } + +JNIEXPORT jlong JNICALL +Java_io_realm_RealmResults_nativeAddListener(JNIEnv* env, jobject instance, jlong native_ptr) { + TR_ENTER_PTR(native_ptr) + + try { + auto results = reinterpret_cast(native_ptr); + + // FIXME: Those need to be freed for all the corner cases! + jobject weak_results = env->NewWeakGlobalRef(instance); + + auto cb = [=](realm::CollectionChangeSet const& changes, + std::exception_ptr err) { + jclass results_class = env->GetObjectClass(weak_results); + jmethodID notify_method = env->GetMethodID(results_class, "notifyChangeListeners", "()V"); + env->CallVoidMethod(weak_results, notify_method); + }; + + NotificationToken token = results->add_notification_callback(cb); + // FIXME: Let's leak them ALL for now!! + return reinterpret_cast( + new std::unique_ptr(new NotificationToken(std::move(token)))); + } CATCH_STD() + + return reinterpret_cast(nullptr); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 4de7a25cea..730a7f7f0a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -105,8 +105,9 @@ Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlo try { auto shared_realm = Realm::get_shared_realm(*config); shared_realm->m_binding_context = JavaBindingContext::create(env, notifier); + // FIXME: Disabled for the collection notifications. There might be some places still need it. // advance_read needs to be handled by Java because of async query. - shared_realm->set_auto_refresh(false); + //shared_realm->set_auto_refresh(false); return reinterpret_cast(new SharedRealm(std::move(shared_realm))); } CATCH_STD() return static_cast(NULL); diff --git a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java index b0f1c194ae..adc0c06f6c 100644 --- a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java @@ -76,6 +76,7 @@ public void notifyCommitByLocalThread() { // |---------------------------------------------------------------+--------------+------------------------------------------------| @Override public void notifyCommitByOtherThread() { + /* if (handler == null) { return; } @@ -92,6 +93,7 @@ public void notifyCommitByOtherThread() { RealmLog.warn("Cannot update Looper threads when the Looper has quit. Use realm.setAutoRefresh(false) " + "to prevent this."); } + */ } @Override diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index b0d118e641..28702766eb 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -1082,8 +1082,8 @@ public void addChangeListener(RealmChangeListener> listener) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); - if (!realm.handlerController.isAutoRefreshEnabled()) { - throw new IllegalStateException("You can't register a listener from a non-Looper thread or IntentService thread. "); + if (listeners.isEmpty()) { + nativeAddListener(nativePtr); } if (!listeners.contains(listener)) { listeners.add(listener); @@ -1173,6 +1173,14 @@ void notifyChangeListeners(boolean forceNotify) { } } + void notifyChangeListeners() { + if (!listeners.isEmpty()) { + for (RealmChangeListener listener : listeners) { + listener.onChange(this); + } + } + } + private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long[] columnIndices, boolean[] orders); private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); @@ -1181,4 +1189,5 @@ void notifyChangeListeners(boolean forceNotify) { private static native long nativeSize(long nativePtr); private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); private static native long nativeSort(long nativePtr, long[] columnIndices, boolean[] orders); + private native long nativeAddListener(long nativePtr); } From 5ce300386927f8e6bef5da4d352994f29bda1b5d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 24 Nov 2016 18:35:43 +0800 Subject: [PATCH 0228/2110] Experiment of Results.get() --- .../src/main/java/io/realm/BaseRealm.java | 12 ++++++++++++ .../src/main/java/io/realm/RealmResults.java | 6 ++++-- .../src/main/java/io/realm/internal/Table.java | 2 +- .../main/java/io/realm/internal/UncheckedRow.java | 7 +++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index c61b2cefae..6e5dc1ce9a 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -505,6 +505,18 @@ public RealmSchema getSchema() { return schema; } + // FIXME: Testing code + E get(Class clazz, long rowPtr) { + Table table = schema.getTable(clazz); + UncheckedRow row = UncheckedRow.getByRowPointer(table, rowPtr); + + E result = configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz), + false, Collections. emptyList()); + RealmObjectProxy proxy = (RealmObjectProxy) result; + proxy.realmGet$proxyState().setTableVersion$realm(); + return result; + } + E get(Class clazz, long rowIndex, boolean acceptDefaultValue, List excludeFields) { Table table = schema.getTable(clazz); UncheckedRow row = table.getUncheckedRow(rowIndex); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 28702766eb..c0e0f70db5 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -268,14 +268,16 @@ public boolean contains(Object object) { public E get(int location) { E obj; realm.checkIfValid(); + /* TableOrView table = getTableOrView(); if (table instanceof TableView) { obj = realm.get(classSpec, className, ((TableView) table).getSourceRowIndex(location)); } else { obj = realm.get(classSpec, className, location); } - - return obj; + */ + long rowPtr = nativeGetRow(nativePtr, location); + return realm.get(classSpec, rowPtr); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 88ba67d577..dd84818ce0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -44,7 +44,7 @@ public class Table implements TableOrView, TableSchema { private static final long NO_PRIMARY_KEY = -2; long nativePtr; - private final Context context; + final Context context; private final SharedRealm sharedRealm; private long cachedPrimaryKeyColumnIndex = NO_MATCH; diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 6e4bb8e9a4..523b3fbc81 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -71,6 +71,13 @@ public static UncheckedRow getByRowPointer(Context context, Table table, long na return row; } + // FIXME: Testing code + public static UncheckedRow getByRowPointer(Table table, long nativeRowPointer) { + UncheckedRow row = new UncheckedRow(table.context, table, nativeRowPointer); + table.context.addReference(NativeObjectReference.TYPE_ROW, row); + return row; + } + /** * Gets the row object associated to an index in a LinkView. * From 7502dc4e992225f7483a8651e6c15acf617fc1b4 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Thu, 24 Nov 2016 16:09:45 +0100 Subject: [PATCH 0229/2110] Lock the phone resource so we can have more executors per slave (#3839) --- Jenkinsfile | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 985b6eae74..7f30fea9f2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -78,16 +78,18 @@ try { } stage('Run instrumented tests') { - boolean archiveLog = true - String backgroundPid - try { - backgroundPid = startLogCatCollector() - forwardAdbPorts() - gradle('realm', 'connectedUnitTests') - archiveLog = false; - } finally { - stopLogCatCollector(backgroundPid, archiveLog) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + lock("${env.NODE_NAME}-android") { + boolean archiveLog = true + String backgroundPid + try { + backgroundPid = startLogCatCollector() + forwardAdbPorts() + gradle('realm', 'connectedUnitTests') + archiveLog = false; + } finally { + stopLogCatCollector(backgroundPid, archiveLog) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } } } From a8d6477e2244b31088ce3a5809edbc455071c5f3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 24 Nov 2016 09:12:06 -0600 Subject: [PATCH 0230/2110] BYE BYE finalizer (#3144) ## NativeObject Interface * NativeObject is an interface now. The implementation should supply a function to return a native deallocator pointer as well. * NativeObjectReference doesn't necessarily to know the pointer type anymore since it can always get a pointer to the deallocator function. ## Use phantom reference and daemon thread to replace finalizer for destruct native objects. * The phantom reference pool is implemented as a linked list which suppose to be fast insertion/removal. * A daemon thread is created to monitor and free all phantom reference. * Delayed disposal before native object creation is not needed any more. * SharedGroup still gets freed in the caller thread with a lock on context. * Native object needs to pass a destruction function pointer in addition to the native object pointer. --- .../processor/RealmProxyClassGenerator.java | 4 - .../io/realm/AllTypesRealmProxy.java | 4 - .../androidTest/java/io/realm/GCTests.java | 129 +++++++++++++++++ .../androidTest/java/io/realm/RealmTests.java | 61 -------- .../java/io/realm/internal/JNICloseTest.java | 66 --------- .../benchmarks/RealmAllocBenchmarks.java | 90 ++++++++++++ .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../main/cpp/io_realm_internal_LinkView.cpp | 20 ++- ...o_realm_internal_NativeObjectReference.cpp | 25 ++++ .../src/main/cpp/io_realm_internal_Table.cpp | 24 +++- .../main/cpp/io_realm_internal_TableQuery.cpp | 21 ++- .../main/cpp/io_realm_internal_TableView.cpp | 24 ++-- .../cpp/io_realm_internal_UncheckedRow.cpp | 23 ++- .../java/io/realm/internal/CheckedRow.java | 17 +-- .../main/java/io/realm/internal/Context.java | 131 ++---------------- .../io/realm/internal/FinalizerRunnable.java | 48 +++++++ .../main/java/io/realm/internal/LinkView.java | 72 +++++----- .../java/io/realm/internal/NativeObject.java | 19 ++- .../realm/internal/NativeObjectReference.java | 81 +++++++---- .../main/java/io/realm/internal/Table.java | 111 ++++----------- .../java/io/realm/internal/TableQuery.java | 59 ++------ .../java/io/realm/internal/TableView.java | 92 ++++-------- .../java/io/realm/internal/UncheckedRow.java | 111 ++++++++------- 23 files changed, 627 insertions(+), 607 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/GCTests.java delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java create mode 100644 realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmAllocBenchmarks.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_NativeObjectReference.cpp create mode 100644 realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 429999eca4..f87f679bb8 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -1076,7 +1076,6 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { .endControlFlow() .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) .endControlFlow() - .emitStatement("LinkView.nativeClose(%sNativeLinkViewPtr)", fieldName) .endControlFlow() .emitEmptyLine(); @@ -1154,7 +1153,6 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { .endControlFlow() .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) .endControlFlow() - .emitStatement("LinkView.nativeClose(%sNativeLinkViewPtr)", fieldName) .endControlFlow() .emitEmptyLine(); @@ -1233,7 +1231,6 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) .endControlFlow() .endControlFlow() - .emitStatement("LinkView.nativeClose(%sNativeLinkViewPtr)", fieldName) .emitEmptyLine(); } else { @@ -1314,7 +1311,6 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) .endControlFlow() .endControlFlow() - .emitStatement("LinkView.nativeClose(%sNativeLinkViewPtr)", fieldName) .emitEmptyLine(); } else { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index b4b27ae96e..d67ccc944c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -993,7 +993,6 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M } LinkView.nativeAdd(columnRealmListNativeLinkViewPtr, cacheItemIndexcolumnRealmList); } - LinkView.nativeClose(columnRealmListNativeLinkViewPtr); } } @@ -1123,7 +1121,6 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob LinkView.nativeAdd(columnRealmListNativeLinkViewPtr, cacheItemIndexcolumnRealmList); } } - LinkView.nativeClose(columnRealmListNativeLinkViewPtr); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/GCTests.java b/realm/realm-library/src/androidTest/java/io/realm/GCTests.java new file mode 100644 index 0000000000..106733e813 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/GCTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm; + + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.realm.entities.AllTypes; +import io.realm.entities.Dog; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.TestCase.assertNotNull; + +// This test is for the fact we don't have locks for native objects creation that when finalizer/phantom thread free the +// native object, the same Realm could have some native objects creation at the same time. +// If the native object's destructor is not thread safe, there is a big chance that those tests crash with a seg-fault. +// test_destructor_thread_safety.cpp in core tests the similar things. +@RunWith(AndroidJUnit4.class) +public class GCTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + private RealmConfiguration realmConfig; + + @Before + public void setUp() { + realmConfig = configFactory.createConfiguration(); + Realm realm = Realm.getInstance(realmConfig); + realm.beginTransaction(); + realm.createObject(AllTypes.class).getColumnRealmList().add(realm.createObject(Dog.class)); + realm.commitTransaction(); + realm.close(); + + } + + @After + public void tearDown() { + } + + @Test + public void createRealmResults() { + for (int i = 0; i < 100; i++) { + Realm realm = Realm.getInstance(realmConfig); + for (int j = 0; j < 1000; j++) { + realm.where(AllTypes.class).findAll(); + } + realm.close(); + } + } + + @Test + public void createRealmResultsFromRealmResults() { + for (int i = 0; i < 100; i++) { + Realm realm = Realm.getInstance(realmConfig); + for (int j = 0; j < 1000; j++) { + realm.where(AllTypes.class).findAll().where().findAll(); + } + realm.close(); + } + } + + @Test + public void createRealmResultsFromRealmList() { + for (int i = 0; i < 100; i++) { + Realm realm = Realm.getInstance(realmConfig); + for (int j = 0; j < 1000; j++) { + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + assertNotNull(allTypes); + allTypes.getColumnRealmList().where().findAll(); + } + realm.close(); + } + } + + @Test + public void createRealmObject() { + for (int i = 0; i < 100; i++) { + Realm realm = Realm.getInstance(realmConfig); + for (int j = 0; j < 1000; j++) { + realm.where(AllTypes.class).findFirst(); + } + realm.close(); + } + } + + @Test + public void createRealmObjectFromRealmResults() { + for (int i = 0; i < 100; i++) { + Realm realm = Realm.getInstance(realmConfig); + for (int j = 0; j < 1000; j++) { + assertNotNull(realm.where(AllTypes.class).findAll().first()); + } + realm.close(); + } + } + + @Test + public void createRealmObjectsFromRealmList() { + for (int i = 0; i < 100; i++) { + Realm realm = Realm.getInstance(realmConfig); + for (int j = 0; j < 1000; j++) { + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + assertNotNull(allTypes); + assertNotNull(allTypes.getColumnRealmList().first()); + } + realm.close(); + } + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index cb0ca8912a..528b9e1402 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -2039,67 +2039,6 @@ public void callMutableMethodOutsideTransaction() throws JSONException, IOExcept } } - // TODO: re-introduce this test mocking the ReferenceQueue instead of relying on the GC -/* // Check that FinalizerRunnable can free native resources (phantom refs) - public void testReferenceCleaning() throws NoSuchFieldException, IllegalAccessException { - testRealm.close(); - - RealmConfiguration config = new RealmConfiguration.Builder(getContext()).name("myown").build(); - Realm.deleteRealm(config); - testRealm = Realm.getInstance(config); - - // Manipulate field accessibility to facilitate testing - Field realmFileReference = BaseRealm.class.getDeclaredField("sharedGroupManager"); - realmFileReference.setAccessible(true); - Field contextField = SharedGroup.class.getDeclaredField("context"); - contextField.setAccessible(true); - Field rowReferencesField = io.realm.internal.Context.class.getDeclaredField("rowReferences"); - rowReferencesField.setAccessible(true); - - SharedGroupManager realmFile = (SharedGroupManager) realmFileReference.get(testRealm); - assertNotNull(realmFile); - - io.realm.internal.Context context = (io.realm.internal.Context) contextField.get(realmFile.getSharedGroup()); - assertNotNull(context); - - Map, Integer> rowReferences = (Map, Integer>) rowReferencesField.get(context); - assertNotNull(rowReferences); - - // insert some rows, then give the thread some time to cleanup - // we have 8 reference so far let's add more - final int numberOfPopulateTest = 1000; - final int numberOfObjects = 20; - final int totalNumberOfReferences = 8 + numberOfObjects * 2 * numberOfPopulateTest; - - long tic = System.currentTimeMillis(); - for (int i = 0; i < numberOfPopulateTest; i++) { - populateTestRealm(testRealm, numberOfObjects); - } - long toc = System.currentTimeMillis(); - Log.d(RealmTest.class.getName(), "Insertion time: " + (toc - tic)); - - final int MAX_GC_RETRIES = 5; - int numberOfRetries = 0; - Log.i("GCing", "Hoping for the best"); - while (rowReferences.size() > 0 && numberOfRetries < MAX_GC_RETRIES) { - SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); //1s - TestHelper.allocGarbage(0); - numberOfRetries++; - System.gc(); - } - context.cleanNativeReferences(); - - // we can't guarantee that all references have been GC'ed but we should detect a decrease - boolean isDecreasing = rowReferences.size() < totalNumberOfReferences; - if (!isDecreasing) { - fail("Native resources are not being closed"); - - } else { - android.util.Log.d(RealmTest.class.getName(), "References freed : " - + (totalNumberOfReferences - rowReferences.size()) + " out of " + totalNumberOfReferences); - } - }*/ - @Test public void createObject_cannotCreateDynamicRealmObject() { realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java deleted file mode 100644 index 5cbffdd653..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNICloseTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import android.test.AndroidTestCase; - -import io.realm.TestHelper; - -public class JNICloseTest extends AndroidTestCase { - - /** - * Make sure, that it's possible to use the query on a closed table - */ - public void testQueryAccessibleAfterTableClose() throws Throwable{ - Table table = TestHelper.getTableWithAllColumnTypes(); - table.addEmptyRows(10); - for (long i=0; i results = realm.where(AllTypes.class).findAll(); + for (long i = 0; i < reps; i++) { + results.first(); + } + } + + @Benchmark + public void createQueries(long reps) { + for (long i = 0; i < reps; i++) { + realm.where(AllTypes.class); + } + } + @Benchmark + public void createRealmResults(long reps) { + RealmQuery query = realm.where(AllTypes.class); + for (long i = 0; i < reps; i++) { + query.findAll(); + } + } + + @Benchmark + public void createRealmLists(long reps) { + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + for (long i = 0; i < reps; i++) { + //noinspection ConstantConditions + allTypes.getColumnRealmList(); + } + } +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 8d9ce2b0ad..e311ef6fd5 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -37,7 +37,7 @@ set(classes_LIST io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema - io.realm.RealmObjectSchema + io.realm.RealmObjectSchema io.realm.internal.NativeObjectReference ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp index 52079074e5..faf2325dd0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp @@ -19,12 +19,7 @@ using namespace realm; -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeClose - (JNIEnv*, jclass, jlong nativeLinkViewPtr) -{ - LangBindHelper::unbind_linklist_ptr(*LV(nativeLinkViewPtr)); -} - +static void finalize_link_view(jlong ptr); JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetRow (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) @@ -248,3 +243,16 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveTargetRow return lvr->remove_target_row( S(pos) ); } CATCH_STD() } + +static void finalize_link_view(jlong ptr) +{ + TR_ENTER_PTR(ptr) + LangBindHelper::unbind_linklist_ptr(*LV(ptr)); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetFinalizerPtr + (JNIEnv *, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_link_view); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_NativeObjectReference.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_NativeObjectReference.cpp new file mode 100644 index 0000000000..ab5de83fc5 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_NativeObjectReference.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 "io_realm_internal_NativeObjectReference.h" + +typedef void (*FinalizeFunc)(jlong); + +JNIEXPORT void JNICALL Java_io_realm_internal_NativeObjectReference_nativeCleanUp +(JNIEnv *, jclass, jlong finalizer_ptr, jlong native_ptr) { + FinalizeFunc finalize_func = reinterpret_cast(finalizer_ptr); + finalize_func(native_ptr); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 6d1aac6c62..9c697bdca5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -23,6 +23,8 @@ using namespace std; using namespace realm; +static void finalize_table(jlong ptr); + inline static bool is_allowed_to_index(JNIEnv* env, DataType column_type) { if (!(column_type == type_String || column_type == type_Int || @@ -1401,15 +1403,9 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeToJson( JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid( JNIEnv*, jobject, jlong nativeTablePtr) -{ - return TBL(nativeTablePtr)->is_attached(); // noexcept -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClose( - JNIEnv* env, jclass, jlong nativeTablePtr) { TR_ENTER_PTR(nativeTablePtr) - LangBindHelper::unbind_table_ptr(TBL(nativeTablePtr)); + return TBL(nativeTablePtr)->is_attached(); // noexcept } JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_createNative(JNIEnv *env, jobject) @@ -1647,3 +1643,17 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeVersion( } CATCH_STD() return 0; } + +static void finalize_table(jlong ptr) +{ + TR_ENTER_PTR(ptr) + LangBindHelper::unbind_table_ptr(TBL(ptr)); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetFinalizerPtr + (JNIEnv *, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_table); +} + diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index b5e89f153b..520b628c4a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -31,6 +31,8 @@ using namespace realm; #define QUERY_VALID(env, pQuery) (true) #endif +static void finalize_table_query(jlong ptr); + inline bool query_valid(JNIEnv* env, Query* pQuery) { return TABLE_VALID(env, pQuery->get_table().get()); @@ -46,11 +48,6 @@ const char* ERR_IMPORT_CLOSED_REALM = "Can not import results from a closed Real const char* ERR_SORT_NOT_SUPPORTED = "Sort is not supported on binary data, object references and RealmList"; //------------------------------------------------------- -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeClose(JNIEnv* env, jclass, jlong nativeQueryPtr) { - TR_ENTER_PTR(nativeQueryPtr) - delete Q(nativeQueryPtr); -} - JNIEXPORT jstring JNICALL Java_io_realm_internal_TableQuery_nativeValidateQuery (JNIEnv *env, jobject, jlong nativeQueryPtr) { @@ -1912,3 +1909,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty } } CATCH_STD() } + +static void finalize_table_query(jlong ptr) +{ + TR_ENTER_PTR(ptr) + delete Q(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetFinalizerPtr + (JNIEnv *, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_table_query); +} + diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp index 8d9aa78bd5..563054f643 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp @@ -25,6 +25,8 @@ using namespace realm; // if you disable the validation, please remember to call sync_in_needed() #define VIEW_VALID_AND_IN_SYNC(env, ptr) view_valid_and_in_sync(env, ptr) +static void finalize_table_view(jlong ptr); + inline bool view_valid_and_in_sync(JNIEnv* env, jlong nativeViewPtr) { bool valid = (TV(nativeViewPtr) != NULL); if (valid) { @@ -148,15 +150,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativePivot( } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeClose( - JNIEnv*, jclass, jlong nativeViewPtr) -{ - if (nativeViewPtr == 0) - return; - - delete TV(nativeViewPtr); -} - JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSize( JNIEnv* env, jobject, jlong nativeViewPtr) { @@ -985,3 +978,16 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindBySourceNdx } CATCH_STD() return -1; } + +static void finalize_table_view(jlong ptr) +{ + TR_ENTER_PTR(ptr) + delete TV(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetFinalizerPtr + (JNIEnv *, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_table_view); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 6ee249c77e..8039c5a541 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -19,6 +19,8 @@ using namespace realm; +static void finalize_unchecked_row(jlong ptr); + JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount (JNIEnv *env, jobject, jlong nativeRowPtr) { @@ -328,13 +330,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeClose - (JNIEnv* env, jclass, jlong nativeRowPtr) -{ - TR_ENTER_PTR(nativeRowPtr) - delete ROW(nativeRowPtr); -} - JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsAttached (JNIEnv* env, jobject, jlong nativeRowPtr) { @@ -366,3 +361,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetNull ROW(nativeRowPtr)->set_null(columnIndex); } CATCH_STD() } + +static void finalize_unchecked_row(jlong ptr) +{ + TR_ENTER_PTR(ptr) + delete ROW(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFinalizerPtr + (JNIEnv *, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_unchecked_row); +} + diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index e969452972..75acb7df6b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -29,7 +29,7 @@ public class CheckedRow extends UncheckedRow { // Used if created from other row. This keeps a strong reference to avoid GC'ing the original object, and its // underlying native data. - @SuppressWarnings("unused") + @SuppressWarnings({"unused", "FieldCanBeLocal"}) private UncheckedRow originalRow; private CheckedRow(Context context, Table parent, long nativePtr) { @@ -37,7 +37,7 @@ private CheckedRow(Context context, Table parent, long nativePtr) { } private CheckedRow(UncheckedRow row) { - super(row.context, row.parent, row.nativePointer); + super(row); this.originalRow = row; } @@ -51,9 +51,7 @@ private CheckedRow(UncheckedRow row) { */ public static CheckedRow get(Context context, Table table, long index) { long nativeRowPointer = table.nativeGetRowPtr(table.nativePtr, index); - CheckedRow row = new CheckedRow(context, table, nativeRowPointer); - context.addReference(NativeObjectReference.TYPE_ROW, row); - return row; + return new CheckedRow(context, table, nativeRowPointer); } /** @@ -65,11 +63,8 @@ public static CheckedRow get(Context context, Table table, long index) { * @return a checked instance of {@link Row} for the {@link LinkView} and index specified. */ public static CheckedRow get(Context context, LinkView linkView, long index) { - long nativeRowPointer = linkView.nativeGetRow(linkView.nativePointer, index); - CheckedRow row = new CheckedRow(context, linkView.getTargetTable(), - nativeRowPointer); - context.addReference(NativeObjectReference.TYPE_ROW, row); - return row; + long nativeRowPointer = linkView.nativeGetRow(linkView.getNativePtr(), index); + return new CheckedRow(context, linkView.getTargetTable(), nativeRowPointer); } /** @@ -77,7 +72,7 @@ public static CheckedRow get(Context context, LinkView linkView, long index) { * * @return an checked instance of {@link Row}. */ - public static CheckedRow getFromRow(UncheckedRow row) { + static CheckedRow getFromRow(UncheckedRow row) { return new CheckedRow(row); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Context.java b/realm/realm-library/src/main/java/io/realm/internal/Context.java index 66b236a786..9dc084e83f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Context.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Context.java @@ -17,127 +17,24 @@ package io.realm.internal; import java.lang.ref.ReferenceQueue; -import java.util.ArrayList; -import java.util.List; +// Currently we free native objects in two threads, the SharedGroup is freed in the caller thread, others are freed in +// RealmFinalizingDaemon thread. And the destruction in both threads are locked by the corresponding context. +// The purpose of locking on Context is: +// Destruction of SharedGroup (and hence Group and Table) is currently not thread-safe with respect to destruction of +// other accessors, you have to ensure mutual exclusion. This is also illustrated by the use of locks in the test +// test_destructor_thread_safety.cpp. Explicit call of SharedGroup::close() or Table::detach() is also not thread-safe +// with respect to destruction of other accessors. public class Context { + private final static ReferenceQueue referenceQueue = new ReferenceQueue(); + private final static Thread finalizingThread = new Thread(new FinalizerRunnable(referenceQueue)); - // Pool to hold the phantom references. - // The size of array for storing phantom references will never decrease. Instead, we use another array to hold the - // index of the free slot. When adding the reference, pick the last index from freeIndexList and put the reference - // to the corresponding slot. When removing the reference, simply add the index to the end of freeIndexList without - // setting the corresponding slot to null for efficiency reasons. The reference will be freed finally when the slot - // gets overwritten or the whole context gets freed. - private static class ReferencesPool { - ArrayList pool = new ArrayList(); - ArrayList freeIndexList = new ArrayList(); - - void add(NativeObjectReference ref) { - if (pool.size() <= ref.refIndex) { - pool.add(ref); - } else { - pool.set(ref.refIndex, ref); - } - } - - Integer getFreeIndex() { - Integer index; - int freeIndexListSize = freeIndexList.size(); - if (freeIndexListSize == 0) { - index = pool.size(); - } else { - index = freeIndexList.remove(freeIndexListSize - 1); - } - return index; - } - } - - // Each group of related Realm objects will have a Context object in the root. - // The root can be a table, a group, or a shared group. - // The Context object is used to store a list of native pointers - // whose disposal need to be handed over from the garbage - // collection thread to the users thread. - - private List abandonedTables = new ArrayList(); - private List abandonedTableViews = new ArrayList(); - private List abandonedQueries = new ArrayList(); - - private ReferencesPool referencesPool = new ReferencesPool(); - private ReferenceQueue referenceQueue = new ReferenceQueue(); - - private boolean isFinalized = false; - - public synchronized void addReference(int type, NativeObject referent) { - referencesPool.add(new NativeObjectReference(type, referent, referenceQueue, referencesPool.getFreeIndex())); - } - - public synchronized void executeDelayedDisposal() { - for (int i = 0; i < abandonedTables.size(); i++) { - long nativePointer = abandonedTables.get(i); - Table.nativeClose(nativePointer); - } - abandonedTables.clear(); - - for (int i = 0; i < abandonedTableViews.size(); i++) { - long nativePointer = abandonedTableViews.get(i); - TableView.nativeClose(nativePointer); - } - abandonedTableViews.clear(); - - for (int i = 0; i < abandonedQueries.size(); i++) { - long nativePointer = abandonedQueries.get(i); - TableQuery.nativeClose(nativePointer); - } - abandonedQueries.clear(); - - cleanNativeReferences(); - } - - private void cleanNativeReferences() { - NativeObjectReference reference = (NativeObjectReference) referenceQueue.poll(); - while (reference != null) { - // Dealloc the native resources - reference.cleanup(); - // Inline referencesPool.remove() to make it faster. - // referencesPool.pool.set(index, null); is not really needed. Make it faster by not - // setting the slot to null. - referencesPool.freeIndexList.add(reference.refIndex); - reference = (NativeObjectReference) referenceQueue.poll(); - } - } - - public void asyncDisposeTable(long nativePointer, boolean isRoot) { - if (isRoot || isFinalized) { - Table.nativeClose(nativePointer); - } - else { - abandonedTables.add(nativePointer); - } - } - - public void asyncDisposeTableView(long nativePointer) { - if (isFinalized) { - TableView.nativeClose(nativePointer); - } - else { - abandonedTableViews.add(nativePointer); - } - } - - public void asyncDisposeQuery(long nativePointer) { - if (isFinalized) { - TableQuery.nativeClose(nativePointer); - } - else { - abandonedQueries.add(nativePointer); - } + static { + finalizingThread.setName("RealmFinalizingDaemon"); + finalizingThread.start(); } - protected void finalize() throws Throwable { - synchronized (this) { - isFinalized = true; - } - executeDelayedDisposal(); - super.finalize(); + void addReference(NativeObject referent) { + new NativeObjectReference(this, referent, referenceQueue); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java b/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java new file mode 100644 index 0000000000..2e8db788c3 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + + +import java.lang.ref.ReferenceQueue; + +import io.realm.log.RealmLog; + +// Running in the FinalizingDaemon thread to free native objects. +class FinalizerRunnable implements Runnable { + private final ReferenceQueue referenceQueue; + + FinalizerRunnable(ReferenceQueue referenceQueue) { + this.referenceQueue = referenceQueue; + } + + @Override + public void run() { + while (true) { + try { + NativeObjectReference reference = (NativeObjectReference) referenceQueue.remove(); + reference.cleanup(); + } catch (InterruptedException e) { + // Restore the interrupted status + Thread.currentThread().interrupt(); + + RealmLog.fatal("The FinalizerRunnable thread has been interrupted." + + " Native resources cannot be freed anymore"); + break; + } + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index 6ba77cc800..f6718b4083 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -21,20 +21,32 @@ /** * The LinkView class represents a core {@link RealmFieldType#LIST}. */ -public class LinkView extends NativeObject { +public class LinkView implements NativeObject { private final Context context; final Table parent; final long columnIndexInParent; + private final long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); public LinkView(Context context, Table parent, long columnIndexInParent, long nativeLinkViewPtr) { this.context = context; this.parent = parent; this.columnIndexInParent = columnIndexInParent; - this.nativePointer = nativeLinkViewPtr; + this.nativePtr = nativeLinkViewPtr; - context.executeDelayedDisposal(); - context.addReference(NativeObjectReference.TYPE_LINK_VIEW, this); + context.addReference(this); + } + + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; } /** @@ -66,66 +78,59 @@ public CheckedRow getCheckedRow(long index) { * Returns the row index in the underlying table. */ public long getTargetRowIndex(long linkViewIndex) { - return nativeGetTargetRowIndex(nativePointer, linkViewIndex); + return nativeGetTargetRowIndex(nativePtr, linkViewIndex); } public void add(long rowIndex) { checkImmutable(); - nativeAdd(nativePointer, rowIndex); + nativeAdd(nativePtr, rowIndex); } public void insert(long pos, long rowIndex) { checkImmutable(); - nativeInsert(nativePointer, pos, rowIndex); + nativeInsert(nativePtr, pos, rowIndex); } public void set(long pos, long rowIndex) { checkImmutable(); - nativeSet(nativePointer, pos, rowIndex); + nativeSet(nativePtr, pos, rowIndex); } public void move(long oldPos, long newPos) { checkImmutable(); - nativeMove(nativePointer, oldPos, newPos); + nativeMove(nativePtr, oldPos, newPos); } public void remove(long pos) { checkImmutable(); - nativeRemove(nativePointer, pos); + nativeRemove(nativePtr, pos); } public void clear() { checkImmutable(); - nativeClear(nativePointer); + nativeClear(nativePtr); } public boolean contains(long tableRowIndex) { - long index = nativeFind(nativePointer, tableRowIndex); + long index = nativeFind(nativePtr, tableRowIndex); return (index != TableOrView.NO_MATCH); } public long size() { - return nativeSize(nativePointer); + return nativeSize(nativePtr); } public boolean isEmpty() { - return nativeIsEmpty(nativePointer); + return nativeIsEmpty(nativePtr); } public TableQuery where() { - // Execute the disposal of abandoned realm objects each time a new realm object is created - this.context.executeDelayedDisposal(); - long nativeQueryPtr = nativeWhere(nativePointer); - try { - return new TableQuery(this.context, this.parent, nativeQueryPtr); - } catch (RuntimeException e) { - TableQuery.nativeClose(nativeQueryPtr); - throw e; - } + long nativeQueryPtr = nativeWhere(nativePtr); + return new TableQuery(this.context, this.parent, nativeQueryPtr); } public boolean isAttached() { - return nativeIsAttached(nativePointer); + return nativeIsAttached(nativePtr); } /** @@ -140,7 +145,7 @@ public Table getTable() { */ public void removeAllTargetRows() { checkImmutable(); - nativeRemoveAllTargetRows(nativePointer); + nativeRemoveAllTargetRows(nativePtr); } /** @@ -148,20 +153,13 @@ public void removeAllTargetRows() { */ public void removeTargetRow(int index) { checkImmutable(); - nativeRemoveTargetRow(nativePointer, index); + nativeRemoveTargetRow(nativePtr, index); } public Table getTargetTable() { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); - long nativeTablePointer = nativeGetTargetTable(nativePointer); - try { - // Copy context reference from parent - return new Table(this.parent, nativeTablePointer); - } catch (RuntimeException e) { - Table.nativeClose(nativeTablePointer); - throw e; - } + long nativeTablePointer = nativeGetTargetTable(nativePtr); + Table table = new Table(this.parent, nativeTablePointer); + return table; } private void checkImmutable() { @@ -170,7 +168,6 @@ private void checkImmutable() { } } - public static native void nativeClose(long nativeLinkViewPtr); native long nativeGetRow(long nativeLinkViewPtr, long pos); private native long nativeGetTargetRowIndex(long nativeLinkViewPtr, long linkViewIndex); public static native void nativeAdd(long nativeLinkViewPtr, long rowIndex); @@ -187,4 +184,5 @@ private void checkImmutable() { private native void nativeRemoveTargetRow(long nativeLinkViewPtr, long rowIndex); private native void nativeRemoveAllTargetRows(long nativeLinkViewPtr); private native long nativeGetTargetTable(long nativeLinkViewPtr); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java b/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java index cad7f390cc..ce326dbf9b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java @@ -19,8 +19,21 @@ /** * This abstract class represents a native object from core. * It specifies the operations common to all such objects. - * All Java classes wrapping a core class should extend NativeObject. + * All Java classes wrapping a core class should implement NativeObject. */ -public abstract class NativeObject { - long nativePointer; +interface NativeObject { + /** + * Gets the pointer of a native object. + * + * @return the native pointer. + */ + long getNativePtr(); + + /** + * Gets the function pointer which points to the function to free the native object. + * The function should be defined like: {@code typedef void (*FinalizeFunc)(jlong ptr)}. + * + * @return the function pointer for freeing the native resource. + */ + long getNativeFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java b/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java index 2427e0a020..e07a4275e3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java +++ b/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java @@ -22,44 +22,75 @@ /** * This class is used for holding the reference to the native pointers present in NativeObjects. * This is required as phantom references cannot access the original objects for this value. + * The phantom references will be stored in a double linked list to avoid the reference itself gets GCed. When the + * referent get GCed, the reference will be added to the ReferenceQueue. Loop in the daemon thread will retrieve the + * phantom reference from the ReferenceQueue then dealloc the referent and remove the reference from the double linked + * list. See {@link FinalizerRunnable} for more implementation details. */ -public final class NativeObjectReference extends PhantomReference { +final class NativeObjectReference extends PhantomReference { - // Using int here instead of enum to make it faster since the cleanup needs to be called - // in a loop to dealloc every native reference. - public static final int TYPE_LINK_VIEW = 0; - public static final int TYPE_ROW = 1; + // Linked list to keep the reference of the PhantomReference + private static class ReferencePool { + NativeObjectReference head; + + synchronized void add(NativeObjectReference ref) { + ref.prev = null; + ref.next = head; + if (head != null) { + head.prev = ref; + } + head = ref; + } + + synchronized void remove(NativeObjectReference ref) { + NativeObjectReference next = ref.next; + NativeObjectReference prev = ref.prev; + ref.next = null; + ref.prev = null; + if (prev != null) { + prev.next = next; + } else { + head = next; + } + if (next != null) { + next.prev = prev; + } + } + } // The pointer to the native object to be handled - final long nativePointer; - final int type; - // Use boxed type to avoid box/un-box when access the freeIndexList - final Integer refIndex; + private final long nativePtr; + // The pointer to the native finalize function + private final long nativeFinalizerPtr; + private final Context context; + private NativeObjectReference prev; + private NativeObjectReference next; - NativeObjectReference(int type, + private static ReferencePool referencePool = new ReferencePool(); + + NativeObjectReference(Context context, NativeObject referent, - ReferenceQueue referenceQueue, - Integer index) { + ReferenceQueue referenceQueue) { super(referent, referenceQueue); - this.type = type; - this.nativePointer = referent.nativePointer; - refIndex = index; + this.nativePtr = referent.getNativePtr(); + this.nativeFinalizerPtr = referent.getNativeFinalizerPtr(); + this.context = context; + referencePool.add(this); } /** * To dealloc native resources. */ void cleanup() { - switch (type) { - case TYPE_LINK_VIEW: - LinkView.nativeClose(nativePointer); - break; - case TYPE_ROW: - UncheckedRow.nativeClose(nativePointer); - break; - default: - // Cannot get here. - throw new IllegalStateException("Unknown native reference type " + type + "."); + synchronized (context) { + nativeCleanUp(nativeFinalizerPtr, nativePtr); } + // Remove the PhantomReference from the pool to free it. + referencePool.remove(this); } + + /** + * Calls the native finalizer function to free the given native pointer. + */ + private static native void nativeCleanUp(long nativeFinalizer, long nativePointer); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 88ba67d577..b5c210ba29 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -28,7 +28,7 @@ * (define/insert/delete/update) a table has. All the native communications to the Realm C++ library are also handled by * this class. */ -public class Table implements TableOrView, TableSchema { +public class Table implements TableOrView, TableSchema, NativeObject { public static final int TABLE_MAX_LENGTH = 56; // Max length of class names without prefix public static final String TABLE_PREFIX = Util.getTablePrefix(); @@ -43,7 +43,8 @@ public class Table implements TableOrView, TableSchema { private static final long PRIMARY_KEY_FIELD_COLUMN_INDEX = 1; private static final long NO_PRIMARY_KEY = -2; - long nativePtr; + protected long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final Context context; private final SharedRealm sharedRealm; private long cachedPrimaryKeyColumnIndex = NO_MATCH; @@ -61,7 +62,8 @@ public Table() { if (nativePtr == 0) { throw new java.lang.OutOfMemoryError("Out of native memory."); } - sharedRealm = null; + this.sharedRealm = null; + context.addReference(this); } Table(Table parent, long nativePointer) { @@ -72,6 +74,17 @@ public Table() { this.context = sharedRealm.context; this.sharedRealm = sharedRealm; this.nativePtr = nativePointer; + context.addReference(this); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; } @Override @@ -83,18 +96,6 @@ public long getNativeTablePointer() { return nativePtr; } - @Override - protected void finalize() throws Throwable { - synchronized (context) { - if (nativePtr != 0) { - // Don't dispose the table immediately if it is created from a SharedRealm to avoid long run finalizer. - context.asyncDisposeTable(nativePtr, sharedRealm == null); - nativePtr = 0; // Set to 0 if finalize is called before close() for some reason - } - } - super.finalize(); - } - /* * Checks if the Table is valid. * Whenever a Table/subtable is changed/updated all it's subtables are invalidated. @@ -716,17 +717,10 @@ public long getLink(long columnIndex, long rowIndex) { } public Table getLinkTarget(long columnIndex) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeTablePointer = nativeGetLinkTarget(nativePtr, columnIndex); - try { - // Copy context reference from parent - return new Table(this.sharedRealm, nativeTablePointer); - } - catch (RuntimeException e) { - Table.nativeClose(nativeTablePointer); - throw e; - } + // Copy context reference from parent + Table table = new Table(this.sharedRealm, nativeTablePointer); + return table; } @Override @@ -1060,16 +1054,9 @@ public long count(long columnIndex, String value) { @Override public TableQuery where() { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeQueryPtr = nativeWhere(nativePtr); - try { - // Copy context reference from parent - return new TableQuery(this.context, this, nativeQueryPtr); - } catch (RuntimeException e) { - TableQuery.nativeClose(nativeQueryPtr); - throw e; - } + // Copy context reference from parent + return new TableQuery(this.context, this, nativeQueryPtr); } /** @@ -1133,66 +1120,32 @@ public long findFirstNull(long columnIndex) { @Override public TableView findAllLong(long columnIndex, long value) { - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllInt(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this, nativeViewPtr); } @Override public TableView findAllBoolean(long columnIndex, boolean value) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllBool(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this, nativeViewPtr); } @Override public TableView findAllFloat(long columnIndex, float value) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllFloat(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this, nativeViewPtr); } @Override public TableView findAllDouble(long columnIndex, double value) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllDouble(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this, nativeViewPtr); } @Override public TableView findAllString(long columnIndex, String value) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllString(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this, nativeViewPtr); } // Experimental feature @@ -1219,15 +1172,8 @@ public Table pivot(long stringCol, long intCol, PivotType pivotType) { // public TableView getDistinctView(long columnIndex) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - this.context.executeDelayedDisposal(); long nativeViewPtr = nativeGetDistinctView(nativePtr, columnIndex); - try { - return new TableView(this.context, this, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this, nativeViewPtr); } /** @@ -1324,8 +1270,6 @@ public static String tableNameToClassName(String tableName) { } protected native long createNative(); - // Free the underlying table ref. It is important that the nativeTablePtr become a invalid pointer after return. - static native void nativeClose(long nativeTablePtr); private native boolean nativeIsValid(long nativeTablePtr); private native long nativeAddColumn(long nativeTablePtr, int type, String name, boolean isNullable); private native long nativeAddColumnLink(long nativeTablePtr, int type, String name, long targetTablePtr); @@ -1419,4 +1363,5 @@ public static String tableNameToClassName(String tableName) { private native String nativeToJson(long nativeTablePtr); private native boolean nativeHasSameSchema(long thisTable, long otherTable); private native long nativeVersion(long nativeTablePtr); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 5060ad4e23..d8c6433681 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -16,17 +16,17 @@ package io.realm.internal; -import java.io.Closeable; import java.util.Date; import io.realm.Case; import io.realm.Sort; import io.realm.internal.async.BadVersionException; -public class TableQuery implements Closeable { +public class TableQuery implements NativeObject { protected boolean DEBUG = false; protected long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); protected final Table table; // Don't convert this into local variable and don't remove this. // Core requests Query to hold the TableView reference which it is built from. @@ -48,6 +48,7 @@ public TableQuery(Context context, Table table, long nativeQueryPtr) { this.table = table; this.nativePtr = nativeQueryPtr; this.origin = null; + context.addReference(this); } public TableQuery(Context context, Table table, long nativeQueryPtr, TableOrView origin) { @@ -58,28 +59,17 @@ public TableQuery(Context context, Table table, long nativeQueryPtr, TableOrView this.table = table; this.nativePtr = nativeQueryPtr; this.origin = origin; + context.addReference(this); } - public void close() { - synchronized (context) { - if (nativePtr != 0) { - nativeClose(nativePtr); - - if (DEBUG) - System.err.println("++++ Query CLOSE, ptr= " + nativePtr); - - nativePtr = 0; - } - } + @Override + public long getNativePtr() { + return nativePtr; } - protected void finalize() { - synchronized (context) { - if (nativePtr != 0) { - context.asyncDisposeQuery(nativePtr); - nativePtr = 0; // Set to 0 if finalize is called before close() for some reason - } - } + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; } /** @@ -449,29 +439,15 @@ public static long findWithHandover(SharedRealm sharedRealm, long ptrQuery) { public TableView findAll(long start, long end, long limit) { validateQuery(); - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAll(nativePtr, start, end, limit); - try { - return new TableView(this.context, this.table, nativeViewPtr, this); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this.table, nativeViewPtr, this); } public TableView findAll() { validateQuery(); - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAll(nativePtr, 0, Table.INFINITE, Table.INFINITE); - try { - return new TableView(this.context, this.table, nativeViewPtr, this); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this.table, nativeViewPtr, this); } // handover find* methods @@ -510,14 +486,7 @@ public static long[] batchUpdateQueries(SharedRealm sharedRealm, long[] handover */ public TableView importHandoverTableView(long handoverPtr, SharedRealm sharedRealm) throws BadVersionException { long nativeTvPtr = nativeImportHandoverTableViewIntoSharedGroup(handoverPtr, sharedRealm.getNativePtr()); - try { - return new TableView(this.context, this.table, nativeTvPtr); - } catch (RuntimeException e) { - if (nativeTvPtr != 0) { - TableView.nativeClose(nativeTvPtr); - } - throw e; - } + return new TableView(this.context, this.table, nativeTvPtr); } /** @@ -742,7 +711,6 @@ private void throwImmutable() { throw new IllegalStateException("Mutable method call during read transaction."); } - protected static native void nativeClose(long nativeQueryPtr); private native String nativeValidateQuery(long nativeQueryPtr); private native void nativeTableview(long nativeQueryPtr, long nativeTableViewPtr); private native void nativeGroup(long nativeQueryPtr); @@ -816,4 +784,5 @@ private void throwImmutable() { private static native long nativeImportHandoverRowIntoSharedGroup(long handoverRowPtr, long callerSharedRealmPtr); public static native void nativeCloseQueryHandover(long nativePtr); private static native long[] nativeBatchUpdateQueries(long bgSharedRealmPtr, long[] handoverQueries, long[][] parameters, long[][] queriesParameters, boolean[][] multiSortOrder) throws BadVersionException; + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableView.java b/realm/realm-library/src/main/java/io/realm/internal/TableView.java index 746e206b8d..9c352f08bc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableView.java @@ -29,13 +29,19 @@ * The view doesn't copy data from the table, but contains merely a list of row-references into the original table * with the real data. */ -public class TableView implements TableOrView { +public class TableView implements TableOrView, NativeObject { // Don't convert this into local variable and don't remove this. // Core requests TableView to hold the Query reference. @SuppressWarnings({"unused"}) private final TableQuery query; // the query which created this TableView private long version; // Last seen version number. Call refresh() to update this. + protected long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); + protected final Table parent; + private final Context context; + + /** * Creates a TableView. This constructor is used if the TableView is created from a table. * @@ -48,6 +54,7 @@ protected TableView(Context context, Table parent, long nativePtr) { this.parent = parent; this.nativePtr = nativePtr; this.query = null; + context.addReference(this); } /** @@ -64,21 +71,22 @@ protected TableView(Context context, Table parent, long nativePtr, TableQuery qu this.parent = parent; this.nativePtr = nativePtr; this.query = query; + context.addReference(this); } @Override - public Table getTable() { - return parent; + public long getNativePtr() { + return nativePtr; } @Override - protected void finalize() { - synchronized (context) { - if (nativePtr != 0) { - context.asyncDisposeTableView(nativePtr); - nativePtr = 0; // Set to 0 if finalize is called before close() for some reason - } - } + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + @Override + public Table getTable() { + return parent; } /** @@ -467,67 +475,32 @@ public long upperBoundLong(long columnIndex, long value) { @Override public TableView findAllLong(long columnIndex, long value){ - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllInt(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this.parent, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this.parent, nativeViewPtr); } @Override public TableView findAllBoolean(long columnIndex, boolean value) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllBool(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this.parent, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this.parent, nativeViewPtr); } @Override public TableView findAllFloat(long columnIndex, float value) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllFloat(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this.parent, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this.parent, nativeViewPtr); } @Override public TableView findAllDouble(long columnIndex, double value) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllDouble(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this.parent, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this.parent, nativeViewPtr); } @Override public TableView findAllString(long columnIndex, String value){ - // Execute the disposal of abandoned realm objects each time a new realm object is created - context.executeDelayedDisposal(); long nativeViewPtr = nativeFindAllString(nativePtr, columnIndex, value); - try { - return new TableView(this.context, this.parent, nativeViewPtr); - } catch (RuntimeException e) { - TableView.nativeClose(nativeViewPtr); - throw e; - } + return new TableView(this.context, this.parent, nativeViewPtr); } // @@ -694,15 +667,8 @@ public String toString() { @Override public TableQuery where() { - // Execute the disposal of abandoned realm objects each time a new realm object is created - this.context.executeDelayedDisposal(); long nativeQueryPtr = nativeWhere(nativePtr); - try { - return new TableQuery(this.context, this.parent, nativeQueryPtr, this); - } catch (RuntimeException e) { - TableQuery.nativeClose(nativeQueryPtr); - throw e; - } + return new TableQuery(this.context, this.parent, nativeQueryPtr, this); } /** @@ -721,10 +687,6 @@ private void throwImmutable() { throw new IllegalStateException("Realm data can only be changed inside a write transaction."); } - protected long nativePtr; - protected final Table parent; - private final Context context; - @Override public long count(long columnIndex, String value) { // TODO: implement @@ -757,8 +719,6 @@ public Table pivot(long stringCol, long intCol, PivotType pivotType){ * @throws UnsupportedOperationException if a column is not indexed. */ public void distinct(long columnIndex) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - this.context.executeDelayedDisposal(); nativeDistinct(nativePtr, columnIndex); } @@ -772,8 +732,6 @@ public void distinct(long columnIndex) { * @throws IllegalArgumentException if a column is unsupported type, or is not indexed. */ public void distinct(List columnIndexes) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - this.context.executeDelayedDisposal(); long[] indexes = new long[columnIndexes.size()]; for (int i = 0; i < columnIndexes.size(); i++) { indexes[i] = columnIndexes.get(i); @@ -787,7 +745,6 @@ public long syncIfNeeded() { return version; } - static native void nativeClose(long nativeViewPtr); private native long nativeSize(long nativeViewPtr); private native long nativeGetSourceRowIndex(long nativeViewPtr, long rowIndex); private native long nativeGetColumnCount(long nativeViewPtr); @@ -852,4 +809,5 @@ public long syncIfNeeded() { private native long nativeSyncIfNeeded(long nativeTablePtr); private native void nativeDistinctMulti(long nativeViewPtr, long[] columnIndexes); private native long nativeSync(long nativeTablePtr); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 6e4bb8e9a4..b5b963f072 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -29,17 +29,37 @@ * * For low-level access to Row data where error checking is required, use {@link CheckedRow}. */ -public class UncheckedRow extends NativeObject implements Row { +public class UncheckedRow implements NativeObject, Row { final Context context; // This is only kept because for now it's needed by the constructor of LinkView - final Table parent; + private final Table parent; + private final long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - protected UncheckedRow(Context context, Table parent, long nativePtr) { + UncheckedRow(Context context, Table parent, long nativePtr) { this.context = context; this.parent = parent; - this.nativePointer = nativePtr; + this.nativePtr = nativePtr; + context.addReference(this); + } + + // This is called by the CheckedRow constructor. The caller should hold a reference to the + // source UncheckedRow since the native destruction is handled by the source UncheckedRow. + UncheckedRow(UncheckedRow row) { + this.context = row.context; + this.parent = row.parent; + this.nativePtr = row.nativePtr; + // The destruction is handled by the source UncheckedRow. No need to add to the ref pool. + } - context.executeDelayedDisposal(); + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; } /** @@ -50,11 +70,9 @@ protected UncheckedRow(Context context, Table parent, long nativePtr) { * @param index the index of the row. * @return an instance of Row for the table and index specified. */ - public static UncheckedRow getByRowIndex(Context context, Table table, long index) { + static UncheckedRow getByRowIndex(Context context, Table table, long index) { long nativeRowPointer = table.nativeGetRowPtr(table.nativePtr, index); - UncheckedRow row = new UncheckedRow(context, table, nativeRowPointer); - context.addReference(NativeObjectReference.TYPE_ROW, row); - return row; + return new UncheckedRow(context, table, nativeRowPointer); } /** @@ -65,10 +83,8 @@ public static UncheckedRow getByRowIndex(Context context, Table table, long inde * @param nativeRowPointer pointer of a row. * @return an instance of Row for the table and row specified. */ - public static UncheckedRow getByRowPointer(Context context, Table table, long nativeRowPointer) { - UncheckedRow row = new UncheckedRow(context, table, nativeRowPointer); - context.addReference(NativeObjectReference.TYPE_ROW, row); - return row; + static UncheckedRow getByRowPointer(Context context, Table table, long nativeRowPointer) { + return new UncheckedRow(context, table, nativeRowPointer); } /** @@ -79,22 +95,19 @@ public static UncheckedRow getByRowPointer(Context context, Table table, long na * @param index the index of the row. * @return an instance of Row for the LinkView and index specified. */ - public static UncheckedRow getByRowIndex(Context context, LinkView linkView, long index) { - long nativeRowPointer = linkView.nativeGetRow(linkView.nativePointer, index); - UncheckedRow row = new UncheckedRow(context, linkView.getTargetTable(), - nativeRowPointer); - context.addReference(NativeObjectReference.TYPE_ROW, row); - return row; + static UncheckedRow getByRowIndex(Context context, LinkView linkView, long index) { + long nativeRowPointer = linkView.nativeGetRow(linkView.getNativePtr(), index); + return new UncheckedRow(context, linkView.getTargetTable(), nativeRowPointer); } @Override public long getColumnCount() { - return nativeGetColumnCount(nativePointer); + return nativeGetColumnCount(nativePtr); } @Override public String getColumnName(long columnIndex) { - return nativeGetColumnName(nativePointer, columnIndex); + return nativeGetColumnName(nativePtr, columnIndex); } @@ -103,12 +116,12 @@ public long getColumnIndex(String columnName) { if (columnName == null) { throw new IllegalArgumentException("Column name can not be null."); } - return nativeGetColumnIndex(nativePointer, columnName); + return nativeGetColumnIndex(nativePtr, columnName); } @Override public RealmFieldType getColumnType(long columnIndex) { - return RealmFieldType.fromNativeValue(nativeGetColumnType(nativePointer, columnIndex)); + return RealmFieldType.fromNativeValue(nativeGetColumnType(nativePtr, columnIndex)); } // Getters @@ -120,57 +133,57 @@ public Table getTable() { @Override public long getIndex() { - return nativeGetIndex(nativePointer); + return nativeGetIndex(nativePtr); } @Override public long getLong(long columnIndex) { - return nativeGetLong(nativePointer, columnIndex); + return nativeGetLong(nativePtr, columnIndex); } @Override public boolean getBoolean(long columnIndex) { - return nativeGetBoolean(nativePointer, columnIndex); + return nativeGetBoolean(nativePtr, columnIndex); } @Override public float getFloat(long columnIndex) { - return nativeGetFloat(nativePointer, columnIndex); + return nativeGetFloat(nativePtr, columnIndex); } @Override public double getDouble(long columnIndex) { - return nativeGetDouble(nativePointer, columnIndex); + return nativeGetDouble(nativePtr, columnIndex); } @Override public Date getDate(long columnIndex) { - return new Date(nativeGetTimestamp(nativePointer, columnIndex)); + return new Date(nativeGetTimestamp(nativePtr, columnIndex)); } @Override public String getString(long columnIndex) { - return nativeGetString(nativePointer, columnIndex); + return nativeGetString(nativePtr, columnIndex); } @Override public byte[] getBinaryByteArray(long columnIndex) { - return nativeGetByteArray(nativePointer, columnIndex); + return nativeGetByteArray(nativePtr, columnIndex); } @Override public long getLink(long columnIndex) { - return nativeGetLink(nativePointer, columnIndex); + return nativeGetLink(nativePtr, columnIndex); } @Override public boolean isNullLink(long columnIndex) { - return nativeIsNullLink(nativePointer, columnIndex); + return nativeIsNullLink(nativePtr, columnIndex); } @Override public LinkView getLinkList(long columnIndex) { - long nativeLinkViewPtr = nativeGetLinkView(nativePointer, columnIndex); + long nativeLinkViewPtr = nativeGetLinkView(nativePtr, columnIndex); return new LinkView(context, parent, columnIndex, nativeLinkViewPtr); } @@ -180,25 +193,25 @@ public LinkView getLinkList(long columnIndex) { public void setLong(long columnIndex, long value) { parent.checkImmutable(); getTable().checkIntValueIsLegal(columnIndex, getIndex(), value); - nativeSetLong(nativePointer, columnIndex, value); + nativeSetLong(nativePtr, columnIndex, value); } @Override public void setBoolean(long columnIndex, boolean value) { parent.checkImmutable(); - nativeSetBoolean(nativePointer, columnIndex, value); + nativeSetBoolean(nativePtr, columnIndex, value); } @Override public void setFloat(long columnIndex, float value) { parent.checkImmutable(); - nativeSetFloat(nativePointer, columnIndex, value); + nativeSetFloat(nativePtr, columnIndex, value); } @Override public void setDouble(long columnIndex, double value) { parent.checkImmutable(); - nativeSetDouble(nativePointer, columnIndex, value); + nativeSetDouble(nativePtr, columnIndex, value); } @Override @@ -208,7 +221,7 @@ public void setDate(long columnIndex, Date date) { throw new IllegalArgumentException("Null Date is not allowed."); } long timestamp = date.getTime(); - nativeSetTimestamp(nativePointer, columnIndex, timestamp); + nativeSetTimestamp(nativePtr, columnIndex, timestamp); } /** @@ -222,34 +235,34 @@ public void setString(long columnIndex, String value) { parent.checkImmutable(); if (value == null) { getTable().checkDuplicatedNullForPrimaryKeyValue(columnIndex, getIndex()); - nativeSetNull(nativePointer, columnIndex); + nativeSetNull(nativePtr, columnIndex); } else { getTable().checkStringValueIsLegal(columnIndex, getIndex(), value); - nativeSetString(nativePointer, columnIndex, value); + nativeSetString(nativePtr, columnIndex, value); } } @Override public void setBinaryByteArray(long columnIndex, byte[] data) { parent.checkImmutable(); - nativeSetByteArray(nativePointer, columnIndex, data); + nativeSetByteArray(nativePtr, columnIndex, data); } @Override public void setLink(long columnIndex, long value) { parent.checkImmutable(); - nativeSetLink(nativePointer, columnIndex, value); + nativeSetLink(nativePtr, columnIndex, value); } @Override public void nullifyLink(long columnIndex) { parent.checkImmutable(); - nativeNullifyLink(nativePointer, columnIndex); + nativeNullifyLink(nativePtr, columnIndex); } @Override public boolean isNull(long columnIndex) { - return nativeIsNull(nativePointer, columnIndex); + return nativeIsNull(nativePtr, columnIndex); } /** @@ -261,7 +274,7 @@ public boolean isNull(long columnIndex) { public void setNull(long columnIndex) { parent.checkImmutable(); getTable().checkDuplicatedNullForPrimaryKeyValue(columnIndex, getIndex()); - nativeSetNull(nativePointer, columnIndex); + nativeSetNull(nativePtr, columnIndex); } /** @@ -275,12 +288,12 @@ public CheckedRow convertToChecked() { @Override public boolean isAttached() { - return nativePointer != 0 && nativeIsAttached(nativePointer); + return nativePtr != 0 && nativeIsAttached(nativePtr); } @Override public boolean hasColumn(String fieldName) { - return nativeHasColumn(nativePointer, fieldName); + return nativeHasColumn(nativePtr, fieldName); } protected native long nativeGetColumnCount(long nativeTablePtr); @@ -307,9 +320,9 @@ public boolean hasColumn(String fieldName) { protected native void nativeSetByteArray(long nativePtr, long columnIndex, byte[] data); protected native void nativeSetLink(long nativeRowPtr, long columnIndex, long value); protected native void nativeNullifyLink(long nativeRowPtr, long columnIndex); - static native void nativeClose(long nativeRowPtr); protected native boolean nativeIsAttached(long nativeRowPtr); protected native boolean nativeHasColumn(long nativeRowPtr, String columnName); protected native boolean nativeIsNull(long nativeRowPtr, long columnIndex); protected native void nativeSetNull(long nativeRowPtr, long columnIndex); + private static native long nativeGetFinalizerPtr(); } From 72be34c0e2749b7888dbfac2a5625336bedebd5a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 25 Nov 2016 14:50:22 +0800 Subject: [PATCH 0231/2110] Move the OS Results wrapper to internal.collection This does make lots of sense since the RealmList could also be backed by this class. --- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- ...s.cpp => io_realm_internal_Collection.cpp} | 40 ++++++++----- .../java/io/realm/internal/Collection.java | 60 +++++++++++++++++++ 3 files changed, 87 insertions(+), 15 deletions(-) rename realm/realm-library/src/main/cpp/{io_realm_RealmResults.cpp => io_realm_internal_Collection.cpp} (81%) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/Collection.java diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 40dcbb8270..d3dadc3260 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -37,7 +37,7 @@ set(classes_LIST io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema - io.realm.RealmObjectSchema io.realm.RealmResults io.realm.internal.NativeObjectReference + io.realm.RealmObjectSchema io.realm.internal.Collection io.realm.internal.NativeObjectReference ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp similarity index 81% rename from realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 7075b29606..c4e8d6c616 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -15,7 +15,7 @@ */ #include -#include "io_realm_RealmResults.h" +#include "io_realm_internal_Collection.h" #include @@ -27,7 +27,9 @@ using namespace realm; JNIEXPORT jlong JNICALL -Java_io_realm_RealmResults_nativeCreateResults(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong query_ptr, jlongArray colunm_indices, jbooleanArray jsort_orders) { +Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong query_ptr, + jlongArray colunm_indices, jbooleanArray jsort_orders) +{ TR_ENTER() try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); @@ -51,7 +53,8 @@ Java_io_realm_RealmResults_nativeCreateResults(JNIEnv* env, jclass, jlong shared } JNIEXPORT jlong JNICALL -Java_io_realm_RealmResults_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) { +Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) +{ TR_ENTER_PTR(native_ptr) try { auto results = reinterpret_cast(native_ptr); @@ -62,7 +65,8 @@ Java_io_realm_RealmResults_nativeCreateSnapshot(JNIEnv* env, jclass, jlong nativ } JNIEXPORT jboolean JNICALL -Java_io_realm_RealmResults_nativeContains(JNIEnv *env, jclass, jlong native_ptr, jlong native_row_ptr) { +Java_io_realm_internal_Collection_nativeContains(JNIEnv *env, jclass, jlong native_ptr, jlong native_row_ptr) +{ TR_ENTER_PTR(native_ptr); try { auto results = reinterpret_cast(native_ptr); @@ -75,7 +79,8 @@ Java_io_realm_RealmResults_nativeContains(JNIEnv *env, jclass, jlong native_ptr, // FIXME: we don't use it at the moment JNIEXPORT jlong JNICALL -Java_io_realm_RealmResults_nativeGetRow(JNIEnv *env, jclass, jlong native_ptr, jint index) { +Java_io_realm_internal_Collection_nativeGetRow(JNIEnv *env, jclass, jlong native_ptr, jint index) +{ TR_ENTER_PTR(native_ptr) try { auto results = reinterpret_cast(native_ptr); @@ -86,7 +91,8 @@ Java_io_realm_RealmResults_nativeGetRow(JNIEnv *env, jclass, jlong native_ptr, j } JNIEXPORT void JNICALL -Java_io_realm_RealmResults_nativeClear(JNIEnv *env, jclass, jlong native_ptr) { +Java_io_realm_internal_Collection_nativeClear(JNIEnv *env, jclass, jlong native_ptr) +{ TR_ENTER_PTR(native_ptr) try { auto results = reinterpret_cast(native_ptr); @@ -95,7 +101,8 @@ Java_io_realm_RealmResults_nativeClear(JNIEnv *env, jclass, jlong native_ptr) { } JNIEXPORT jlong JNICALL -Java_io_realm_RealmResults_nativeSize(JNIEnv *env, jclass, jlong native_ptr) { +Java_io_realm_internal_Collection_nativeSize(JNIEnv *env, jclass, jlong native_ptr) +{ TR_ENTER_PTR(native_ptr) try { auto results = reinterpret_cast(native_ptr); @@ -105,7 +112,9 @@ Java_io_realm_RealmResults_nativeSize(JNIEnv *env, jclass, jlong native_ptr) { } JNIEXPORT jobject JNICALL -Java_io_realm_RealmResults_nativeAggregate(JNIEnv *env, jclass, jlong native_ptr, jlong column_index, jbyte agg_func) { +Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong native_ptr, jlong column_index, + jbyte agg_func) +{ TR_ENTER_PTR(native_ptr) try { auto results = reinterpret_cast(native_ptr); @@ -113,16 +122,16 @@ Java_io_realm_RealmResults_nativeAggregate(JNIEnv *env, jclass, jlong native_ptr size_t index = S(column_index); Optional value; switch (agg_func) { - case io_realm_RealmResults_AGGREGATE_FUNCTION_MINIMUM: + case io_realm_internal_Collection_AGGREGATE_FUNCTION_MINIMUM: value = results->min(index); break; - case io_realm_RealmResults_AGGREGATE_FUNCTION_MAXIMUM: + case io_realm_internal_Collection_AGGREGATE_FUNCTION_MAXIMUM: value = results->max(index); break; - case io_realm_RealmResults_AGGREGATE_FUNCTION_AVERAGE: + case io_realm_internal_Collection_AGGREGATE_FUNCTION_AVERAGE: value = results->average(index); break; - case io_realm_RealmResults_AGGREGATE_FUNCTION_SUM: + case io_realm_internal_Collection_AGGREGATE_FUNCTION_SUM: value = results->sum(index); break; } @@ -149,7 +158,9 @@ Java_io_realm_RealmResults_nativeAggregate(JNIEnv *env, jclass, jlong native_ptr } JNIEXPORT jlong JNICALL -Java_io_realm_RealmResults_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jlongArray colunm_indices, jbooleanArray jsort_orders) { +Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jlongArray colunm_indices, + jbooleanArray jsort_orders) +{ TR_ENTER_PTR(native_ptr) try { auto results = reinterpret_cast(native_ptr); @@ -176,7 +187,8 @@ Java_io_realm_RealmResults_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jlo } JNIEXPORT jlong JNICALL -Java_io_realm_RealmResults_nativeAddListener(JNIEnv* env, jobject instance, jlong native_ptr) { +Java_io_realm_internal_Collection_nativeAddListener(JNIEnv* env, jobject instance, jlong native_ptr) +{ TR_ENTER_PTR(native_ptr) try { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java new file mode 100644 index 0000000000..b227851ca0 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +public class Collection implements NativeObject { + + private final long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); + private final Context context; + private final TableQuery query; + + // Public for static checking in JNI + public static final byte AGGREGATE_FUNCTION_MINIMUM = 1; + public static final byte AGGREGATE_FUNCTION_MAXIMUM = 2; + public static final byte AGGREGATE_FUNCTION_AVERAGE = 3; + public static final byte AGGREGATE_FUNCTION_SUM = 4; + + protected Collection(SharedRealm sharedRealm, TableQuery query, long indices[], boolean[] orders) { + this.context = sharedRealm.context; + this.query = query; + + this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), indices, orders); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + private static native long nativeGetFinalizerPtr(); + private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long[] columnIndices, + boolean[] orders); + private static native long nativeCreateSnapshot(long nativePtr); + private static native long nativeGetRow(long nativePtr, int index); + private static native boolean nativeContains(long nativePtr, long nativeRowPtr); + private static native void nativeClear(long nativePtr); + private static native long nativeSize(long nativePtr); + private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); + private static native long nativeSort(long nativePtr, long[] columnIndices, boolean[] orders); + private native long nativeAddListener(long nativePtr); +} From d7a093ba3393f622506ebb559762e0f5cd9c4fa2 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 25 Nov 2016 16:34:29 +0800 Subject: [PATCH 0232/2110] Back the RealmResults by collection --- .../src/main/java/io/realm/BaseRealm.java | 4 +- .../src/main/java/io/realm/RealmResults.java | 203 +++++------------- .../java/io/realm/internal/Collection.java | 40 ++++ 3 files changed, 96 insertions(+), 151 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 6e5dc1ce9a..a32f249412 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -506,9 +506,7 @@ public RealmSchema getSchema() { } // FIXME: Testing code - E get(Class clazz, long rowPtr) { - Table table = schema.getTable(clazz); - UncheckedRow row = UncheckedRow.getByRowPointer(table, rowPtr); + E get(Class clazz, Row row) { E result = configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz), false, Collections. emptyList()); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 3d2bf456f0..2c4e5789aa 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -18,9 +18,9 @@ import android.app.IntentService; +import android.os.Looper; import java.util.AbstractList; -import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Date; @@ -31,24 +31,21 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; -import io.realm.internal.CheckedRow; -import io.realm.internal.InvalidRow; -import io.realm.internal.RealmObjectProxy; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.TableQuery; import io.realm.internal.TableView; -import io.realm.internal.UncheckedRow; +import io.realm.internal.Collection; import io.realm.internal.async.BadVersionException; import io.realm.log.RealmLog; import rx.Observable; /** - * This class holds all the matches of a {@link io.realm.RealmQuery} for a given Realm. The objects are not copied from + * This class holds all the matches of a {@link RealmQuery} for a given Realm. The objects are not copied from * the Realm to the RealmResults list, but are just referenced from the RealmResult instead. This saves memory and * increases speed. *

          - * RealmResults are live views, which means that if it is on an {@link android.os.Looper} thread, it will automatically + * RealmResults are live views, which means that if it is on an {@link Looper} thread, it will automatically * update its query results after a transaction has been committed. If on a non-looper thread, {@link Realm#waitForChange()} * must be called to update the results. *

          @@ -68,7 +65,7 @@ * * @param The class of objects in this list. * @see RealmQuery#findAll() - * @see io.realm.Realm#executeTransaction(Realm.Transaction) + * @see Realm#executeTransaction(Realm.Transaction) */ public final class RealmResults extends AbstractList implements OrderedRealmCollection { @@ -84,6 +81,7 @@ public final class RealmResults extends AbstractList im private long currentTableViewVersion = TABLE_VIEW_VERSION_NONE; private final TableQuery query; + private final io.realm.internal.Collection collection; private final List>> listeners = new CopyOnWriteArrayList>>(); private Future pendingQuery; private boolean asyncQueryCompleted = false; @@ -91,8 +89,6 @@ public final class RealmResults extends AbstractList im // clear it. private boolean viewUpdated = false; - private final long nativePtr; - // Public for static checking in JNI public static final byte AGGREGATE_FUNCTION_MINIMUM = 1; public static final byte AGGREGATE_FUNCTION_MAXIMUM = 2; @@ -124,6 +120,13 @@ static RealmResults createFromDynamicTableOrView(BaseRealm r return realmResults; } + RealmResults(BaseRealm realm, io.realm.internal.Collection collection, Class clazz) { + this.realm = realm; + this.query = null; + this.classSpec = clazz; + this.collection = collection; + } + private RealmResults(BaseRealm realm, TableQuery query, Class clazz, String fieldNames[], Sort[] sortOrder) { this.realm = realm; this.classSpec = clazz; @@ -144,21 +147,21 @@ private RealmResults(BaseRealm realm, TableQuery query, Class clazz, String f } } - this.nativePtr = nativeCreateResults(realm.sharedRealm.getNativePtr(), query.getNativePtr(), indices, order); + collection = null; } private RealmResults(BaseRealm realm, TableQuery query, Class clazz) { this.realm = realm; this.classSpec = clazz; this.query = query; - this.nativePtr = 0; + collection = null; } private RealmResults(BaseRealm realm, TableQuery query, String className) { this.realm = realm; this.query = query; this.className = className; - this.nativePtr = 0; + collection = null; } private RealmResults(BaseRealm realm, TableOrView table, Class classSpec) { @@ -169,7 +172,7 @@ private RealmResults(BaseRealm realm, TableOrView table, Class classSpec) { this.pendingQuery = null; this.query = null; this.currentTableViewVersion = table.syncIfNeeded(); - this.nativePtr = 0; + collection = null; } private RealmResults(BaseRealm realm, String className) { @@ -178,7 +181,7 @@ private RealmResults(BaseRealm realm, String className) { pendingQuery = null; query = null; - this.nativePtr = 0; + collection = null; } private RealmResults(BaseRealm realm, TableOrView table, String className) { @@ -190,8 +193,8 @@ private RealmResults(BaseRealm realm, TableOrView table, String className) { private RealmResults(BaseRealm realm, String className, long nativePtr) { this.realm = realm; this.className = className; - this.nativePtr = nativePtr; this.query = null; + collection = null; } TableOrView getTableOrView() { @@ -237,6 +240,7 @@ public RealmQuery where() { */ @Override public boolean contains(Object object) { + /* boolean contains = false; if (isLoaded() && object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; @@ -255,6 +259,8 @@ public boolean contains(Object object) { } } return contains; + */ + return false; } /** @@ -266,18 +272,8 @@ public boolean contains(Object object) { */ @Override public E get(int location) { - E obj; realm.checkIfValid(); - /* - TableOrView table = getTableOrView(); - if (table instanceof TableView) { - obj = realm.get(classSpec, className, ((TableView) table).getSourceRowIndex(location)); - } else { - obj = realm.get(classSpec, className, location); - } - */ - long rowPtr = nativeGetRow(nativePtr, location); - return realm.get(classSpec, rowPtr); + return realm.get(classSpec, collection.getUncheckedRow(location)); } /** @@ -354,21 +350,14 @@ public void deleteFromRealm(int location) { public boolean deleteAllFromRealm() { realm.checkIfValid(); if (size() > 0) { - if (nativePtr == 0) { - TableOrView table = getTableOrView(); - table.clear(); - } else { - nativeClear(nativePtr); - } - return true; - } else { - return false; + collection.clear(); } + return false; } /** * Returns an iterator for the results of a query. Any change to Realm while iterating will cause this iterator to - * throw a {@link java.util.ConcurrentModificationException} if accessed. + * throw a {@link ConcurrentModificationException} if accessed. * * @return an iterator on the elements of this list. * @see Iterator @@ -384,7 +373,7 @@ public Iterator iterator() { /** * Returns a list iterator for the results of a query. Any change to Realm while iterating will cause the iterator - * to throw a {@link java.util.ConcurrentModificationException} if accessed. + * to throw a {@link ConcurrentModificationException} if accessed. * * @return a ListIterator on the elements of this list. * @see ListIterator @@ -400,7 +389,7 @@ public ListIterator listIterator() { /** * Returns a list iterator on the results of a query. Any change to Realm while iterating will cause the iterator to - * throw a {@link java.util.ConcurrentModificationException} if accessed. + * throw a {@link ConcurrentModificationException} if accessed. * * @param location the index at which to start the iteration. * @return a ListIterator on the elements of this list. @@ -438,12 +427,15 @@ private long getColumnIndexForSort(String fieldName) { */ @Override public RealmResults sort(String fieldName) { + /* if (nativePtr == 0) { return this.sort(fieldName, Sort.ASCENDING); } else { long ptr = nativeSort(nativePtr, new long[]{getColumnIndexForSort(fieldName)}, new boolean[]{Sort.ASCENDING.getValue()}); return new RealmResults(realm, className, ptr); } + */ + return null; } /** @@ -451,12 +443,15 @@ public RealmResults sort(String fieldName) { */ @Override public RealmResults sort(String fieldName, Sort sortOrder) { + /* if (nativePtr == 0) { return where().findAllSorted(fieldName, sortOrder); } else { long ptr = nativeSort(nativePtr, new long[]{getColumnIndexForSort(fieldName)}, new boolean[]{sortOrder == Sort.ASCENDING}); return new RealmResults(realm, className, ptr); } + */ + return null; } /** @@ -464,6 +459,7 @@ public RealmResults sort(String fieldName, Sort sortOrder) { */ @Override public RealmResults sort(String fieldNames[], Sort sortOrders[]) { + /* if (nativePtr == 0) { return where().findAllSorted(fieldNames, sortOrders); } else { @@ -480,6 +476,8 @@ public RealmResults sort(String fieldNames[], Sort sortOrders[]) { long ptr = nativeSort(nativePtr, columnIndices, orders); return new RealmResults(realm, className, ptr); } + */ + return null; } /** @@ -501,15 +499,9 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName public int size() { if (!isLoaded()) { return 0; - } else { - long size; - if (nativePtr == 0) { - size = getTableOrView().size(); - } else { - size = nativeSize(nativePtr); - } - return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; } + + return collection.size(); } /** @@ -518,20 +510,7 @@ public int size() { public Number min(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - if (nativePtr == 0) { - switch (table.getColumnType(columnIndex)) { - case INTEGER: - return table.minimumLong(columnIndex); - case FLOAT: - return table.minimumFloat(columnIndex); - case DOUBLE: - return table.minimumDouble(columnIndex); - default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); - } - } else { - return (Number) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_MINIMUM); - } + return (Number)collection.aggregate(io.realm.internal.Collection.Aggregate.MINIMUM, columnIndex); } /** @@ -540,15 +519,7 @@ public Number min(String fieldName) { public Date minDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - if (nativePtr == 0) { - if (table.getColumnType(columnIndex) == RealmFieldType.DATE) { - return table.minimumDate(columnIndex); - } else { - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "Date")); - } - } else { - return (Date) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_MINIMUM); - } + return (Date) collection.aggregate(Collection.Aggregate.MINIMUM, columnIndex); } /** @@ -557,20 +528,7 @@ public Date minDate(String fieldName) { public Number max(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - if (nativePtr == 0) { - switch (table.getColumnType(columnIndex)) { - case INTEGER: - return table.maximumLong(columnIndex); - case FLOAT: - return table.maximumFloat(columnIndex); - case DOUBLE: - return table.maximumDouble(columnIndex); - default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); - } - } else { - return (Number) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_MAXIMUM); - } + return (Number) collection.aggregate(Collection.Aggregate.MAXIMUM, columnIndex); } /** @@ -581,20 +539,12 @@ public Number max(String fieldName) { * @return if no objects exist or they all have {@code null} as the value for the given date field, {@code null} * will be returned. Otherwise the maximum date is returned. When determining the maximum date, objects with * {@code null} values are ignored. - * @throws java.lang.IllegalArgumentException if fieldName is not a Date field. + * @throws IllegalArgumentException if fieldName is not a Date field. */ public Date maxDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - if (nativePtr == 0) { - if (table.getColumnType(columnIndex) == RealmFieldType.DATE) { - return table.maximumDate(columnIndex); - } else { - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "Date")); - } - } else { - return (Date) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_MAXIMUM); - } + return (Date) collection.aggregate(Collection.Aggregate.MAXIMUM, columnIndex); } @@ -604,20 +554,7 @@ public Date maxDate(String fieldName) { public Number sum(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - if (nativePtr == 0) { - switch (table.getColumnType(columnIndex)) { - case INTEGER: - return table.sumLong(columnIndex); - case FLOAT: - return table.sumFloat(columnIndex); - case DOUBLE: - return table.sumDouble(columnIndex); - default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); - } - } else { - return (Number) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_SUM); - } + return (Number) collection.aggregate(Collection.Aggregate.SUM, columnIndex); } /** @@ -626,22 +563,10 @@ public Number sum(String fieldName) { public double average(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - if (nativePtr == 0) { - switch (table.getColumnType(columnIndex)) { - case INTEGER: - return table.averageLong(columnIndex); - case DOUBLE: - return table.averageDouble(columnIndex); - case FLOAT: - return table.averageFloat(columnIndex); - default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); - } - } else { - // FIXME: Should we change return type to Double? - Number sum = (Number) nativeAggregate(nativePtr, columnIndex, AGGREGATE_FUNCTION_AVERAGE); - return sum.doubleValue(); - } + + // FIXME: Should we change return type to Double? + Number sum = (Number) collection.aggregate(Collection.Aggregate.AVERAGE, columnIndex); + return sum.doubleValue(); } /** @@ -674,7 +599,7 @@ public RealmResults distinct(String fieldName) { * * @param fieldName the field name. * @return immediately a {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the + * {@link RealmResults#addChangeListener(RealmChangeListener)} to be notified when the * query completes. * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, * is not indexed, or points to linked fields. @@ -730,7 +655,7 @@ public boolean remove(Object object) { */ @Deprecated @Override - public boolean removeAll(Collection collection) { + public boolean removeAll(java.util.Collection collection) { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); } @@ -754,7 +679,7 @@ public E set(int location, E object) { */ @Deprecated @Override - public boolean retainAll(Collection collection) { + public boolean retainAll(java.util.Collection collection) { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); } @@ -845,7 +770,7 @@ public void add(int index, E element) { */ @Override @Deprecated - public boolean addAll(int location, Collection collection) { + public boolean addAll(int location, java.util.Collection collection) { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); } @@ -856,7 +781,7 @@ public boolean addAll(int location, Collection collection) { */ @Deprecated @Override - public boolean addAll(Collection collection) { + public boolean addAll(java.util.Collection collection) { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); } @@ -1085,7 +1010,7 @@ public void addChangeListener(RealmChangeListener> listener) { } realm.checkIfValid(); if (listeners.isEmpty()) { - nativeAddListener(nativePtr); + //nativeAddListener(nativePtr); } if (!listeners.contains(listener)) { listeners.add(listener); @@ -1174,22 +1099,4 @@ void notifyChangeListeners(boolean forceNotify) { } } } - - void notifyChangeListeners() { - if (!listeners.isEmpty()) { - for (RealmChangeListener listener : listeners) { - listener.onChange(this); - } - } - } - - private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long[] columnIndices, boolean[] orders); - private static native long nativeCreateSnapshot(long nativePtr); - private static native long nativeGetRow(long nativePtr, int index); - private static native boolean nativeContains(long nativePtr, long nativeRowPtr); - private static native void nativeClear(long nativePtr); - private static native long nativeSize(long nativePtr); - private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); - private static native long nativeSort(long nativePtr, long[] columnIndices, boolean[] orders); - private native long nativeAddListener(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index b227851ca0..be3c35dfe1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -16,7 +16,13 @@ package io.realm.internal; +import io.realm.RealmChangeListener; + public class Collection implements NativeObject { + + public interface Listener { + void onChange(); + } private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); @@ -29,6 +35,23 @@ public class Collection implements NativeObject { public static final byte AGGREGATE_FUNCTION_AVERAGE = 3; public static final byte AGGREGATE_FUNCTION_SUM = 4; + public enum Aggregate { + MINIMUM(AGGREGATE_FUNCTION_MINIMUM), + MAXIMUM(AGGREGATE_FUNCTION_MAXIMUM), + AVERAGE(AGGREGATE_FUNCTION_AVERAGE), + SUM(AGGREGATE_FUNCTION_SUM); + + private final byte value; + + Aggregate(byte value) { + this.value = value; + } + + public byte getValue() { + return value; + } + } + protected Collection(SharedRealm sharedRealm, TableQuery query, long indices[], boolean[] orders) { this.context = sharedRealm.context; this.query = query; @@ -46,6 +69,23 @@ public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } + public UncheckedRow getUncheckedRow(int index) { + return UncheckedRow.getByRowPointer(query.table, nativeGetRow(nativePtr, index)); + } + + public Object aggregate(Aggregate aggregateMethod, long columnIndex) { + return nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); + } + + public int size() { + long size = nativeSize(nativePtr); + return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; + } + + public void clear() { + nativeClear(nativePtr); + } + private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long[] columnIndices, boolean[] orders); From 916a159d1e66ca6345dead8fc6ff2c5bf002da39 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 28 Nov 2016 14:16:35 +0800 Subject: [PATCH 0233/2110] Wrap NotificationToken --- .../main/cpp/io_realm_internal_Collection.cpp | 43 +++++++- .../src/main/java/io/realm/RealmResults.java | 14 +-- .../java/io/realm/internal/Collection.java | 103 +++++++++++++++++- .../main/java/io/realm/internal/Context.java | 3 + 4 files changed, 147 insertions(+), 16 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index c4e8d6c616..dcd63e8ca2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -26,6 +26,24 @@ using namespace realm; +static void finalize_results(jlong ptr); +static void finalize_notification_token(jlong ptr); + +static void finalize_results(jlong ptr) +{ + TR_ENTER_PTR(ptr); + delete reinterpret_cast(ptr); +} + +static void finalize_notification_token(jlong ptr) +{ + TR_ENTER_PTR(ptr); + // NotificationToken can be closed by NotificationToken.close(). Then ptr will be reset in that case. + if (ptr) { + delete reinterpret_cast(ptr); + } +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong query_ptr, jlongArray colunm_indices, jbooleanArray jsort_orders) @@ -205,10 +223,29 @@ Java_io_realm_internal_Collection_nativeAddListener(JNIEnv* env, jobject instanc }; NotificationToken token = results->add_notification_callback(cb); - // FIXME: Let's leak them ALL for now!! - return reinterpret_cast( - new std::unique_ptr(new NotificationToken(std::move(token)))); + return reinterpret_cast(new NotificationToken(std::move(token))); } CATCH_STD() return reinterpret_cast(nullptr); } + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeGetFinalizerPtr(JNIEnv *, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_results); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeNotificationTokenGetFinalizerPtr(JNIEnv *, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_notification_token); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeNotificationTokenClose(JNIEnv *, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + delete reinterpret_cast(native_ptr); +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 2c4e5789aa..4a68b92061 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -97,7 +97,8 @@ public final class RealmResults extends AbstractList im static RealmResults createFromQuery(BaseRealm realm, TableQuery query, Class clazz, String fieldNames[], Sort[] sortOrder) { - return new RealmResults(realm, query, clazz, fieldNames, sortOrder); + Collection collection = new Collection(realm.sharedRealm, query, null, null); + return new RealmResults(realm, collection, clazz); } static RealmResults createFromTableQuery(BaseRealm realm, TableQuery query, Class clazz) { @@ -1009,12 +1010,7 @@ public void addChangeListener(RealmChangeListener> listener) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); - if (listeners.isEmpty()) { - //nativeAddListener(nativePtr); - } - if (!listeners.contains(listener)) { - listeners.add(listener); - } + collection.addListener(new Collection.Listener(listener, this)); } /** @@ -1029,7 +1025,7 @@ public void removeChangeListener(RealmChangeListener listener) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); - listeners.remove(listener); + collection.removeListener(new Collection.Listener(listener, this)); } /** @@ -1037,7 +1033,7 @@ public void removeChangeListener(RealmChangeListener listener) { */ public void removeChangeListeners() { realm.checkIfValid(); - listeners.clear(); + collection.removeAllListeners(); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index be3c35dfe1..05872dd9ae 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -16,18 +16,69 @@ package io.realm.internal; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + import io.realm.RealmChangeListener; public class Collection implements NativeObject { - public interface Listener { - void onChange(); + public static class Listener { + private final RealmChangeListener realmChangeListener; + private final WeakReference objectRef; + + public Listener(RealmChangeListener realmChangeListener, Object objectRef) { + this.realmChangeListener = realmChangeListener; + this.objectRef = new WeakReference(objectRef); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof Listener) { + Listener anotherListener = (Listener) obj; + return realmChangeListener.equals(anotherListener.realmChangeListener) && + objectRef.equals(anotherListener.objectRef); + } + return false; + } + } + + private static class NotificationToken implements NativeObject { + private long nativePtr; + private static final long nativeFinalizerPtr = nativeNotificationTokenGetFinalizerPtr(); + + NotificationToken(long nativePtr) { + this.nativePtr = nativePtr; + Context.sharedContext.addReference(this); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + public void close() { + nativeNotificationTokenClose(nativePtr); + nativePtr = 0; + } } - + private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final Context context; private final TableQuery query; + private final List listeners = new CopyOnWriteArrayList(); + private NotificationToken notificationToken = null; // Public for static checking in JNI public static final byte AGGREGATE_FUNCTION_MINIMUM = 1; @@ -52,11 +103,12 @@ public byte getValue() { } } - protected Collection(SharedRealm sharedRealm, TableQuery query, long indices[], boolean[] orders) { + public Collection(SharedRealm sharedRealm, TableQuery query, long indices[], boolean[] orders) { this.context = sharedRealm.context; this.query = query; this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), indices, orders); + this.context.addReference(this); } @Override @@ -86,6 +138,47 @@ public void clear() { nativeClear(nativePtr); } + public void addListener(Listener listener) { + if (!listeners.contains(listener)) { + listeners.add(listener); + } + if (notificationToken == null) { + notificationToken = new NotificationToken(nativeAddListener(nativePtr)); + } + } + + public void removeListener(Listener listener) { + listeners.remove(listener); + if (listeners.isEmpty() && notificationToken != null) { + notificationToken.close(); + notificationToken = null; + } + } + + public void removeAllListeners() { + listeners.clear(); + if (notificationToken != null) { + notificationToken.close(); + notificationToken = null; + } + } + + // Called by JNI + @SuppressWarnings("unused") + private void notifyChangeListeners() { + if (!listeners.isEmpty()) { + for (Listener listener : listeners) { + Object obj = listener.objectRef.get(); + if (obj == null) { + listeners.remove(listener); + continue; + } + //noinspection unchecked + listener.realmChangeListener.onChange(obj); + } + } + } + private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long[] columnIndices, boolean[] orders); @@ -97,4 +190,6 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); private static native long nativeSort(long nativePtr, long[] columnIndices, boolean[] orders); private native long nativeAddListener(long nativePtr); + private static native long nativeNotificationTokenGetFinalizerPtr(); + private static native long nativeNotificationTokenClose(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Context.java b/realm/realm-library/src/main/java/io/realm/internal/Context.java index 9dc084e83f..d68bceba93 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Context.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Context.java @@ -29,6 +29,9 @@ public class Context { private final static ReferenceQueue referenceQueue = new ReferenceQueue(); private final static Thread finalizingThread = new Thread(new FinalizerRunnable(referenceQueue)); + // Context instance for native objects which are always thread-safe to be created and freed. + public final static Context sharedContext = new Context(); + static { finalizingThread.setName("RealmFinalizingDaemon"); finalizingThread.start(); From fadefb5787678b67d9aba7517784d976d21fcc35 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 28 Nov 2016 16:08:05 +0800 Subject: [PATCH 0234/2110] Wrap SortDescriptor --- .../realm-library/src/main/cpp/CMakeLists.txt | 3 +- .../src/main/cpp/io_realm_SortDescriptor.cpp | 48 ++++++ .../main/cpp/io_realm_internal_Collection.cpp | 19 +-- realm/realm-library/src/main/cpp/util.hpp | 61 +++++++- .../src/main/java/io/realm/RealmQuery.java | 17 ++- .../src/main/java/io/realm/RealmResults.java | 16 +- .../java/io/realm/internal/Collection.java | 13 +- .../io/realm/internal/FieldDescriptor.java | 91 ++++++++++++ .../io/realm/internal/SortDescriptor.java | 139 ++++++++++++++++++ .../java/io/realm/internal/TableQuery.java | 5 + 10 files changed, 373 insertions(+), 39 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/io_realm_SortDescriptor.cpp create mode 100644 realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index d3dadc3260..d66571c67f 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -37,7 +37,8 @@ set(classes_LIST io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema - io.realm.RealmObjectSchema io.realm.internal.Collection io.realm.internal.NativeObjectReference + io.realm.RealmObjectSchema io.realm.internal.Collection io.realm.internal.SortDescriptor + io.realm.internal.NativeObjectReference ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 diff --git a/realm/realm-library/src/main/cpp/io_realm_SortDescriptor.cpp b/realm/realm-library/src/main/cpp/io_realm_SortDescriptor.cpp new file mode 100644 index 0000000000..7dcc8ef239 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_SortDescriptor.cpp @@ -0,0 +1,48 @@ +#include "io_realm_internal_SortDescriptor.h" + +#include + +#include "util.hpp" + +using namespace realm; + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_SortDescriptor_nativeCreate(JNIEnv* env, jclass, jlong table_ptr, jobjectArray column_indices, + jbooleanArray ascending) +{ + try { + JniArrayOfArrays arrays(env, column_indices); + JniBooleanArray ascending_array(env, ascending); + jsize arr_len = arrays.len(); + + std::vector> indices; + std::vector ascending_list; + + for (int i = 0; i < arr_len; ++i) { + JniLongArray& jni_long_array = arrays[i]; + std::vector col_indices; + for (int j = 0; j < jni_long_array.len(); ++j) { + col_indices.push_back(static_cast(jni_long_array[j])); + } + indices.push_back(std::move(col_indices)); + if (ascending) { + ascending_list.push_back(static_cast(ascending_array[i])); + } + } + + SortDescriptor* descriptor = ascending ? + new SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices), std::move(ascending_list)) + : new SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices)); + return reinterpret_cast(descriptor); + } CATCH_STD() + + return reinterpret_cast(nullptr); +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_SortDescriptor_nativeClose(JNIEnv* env, jclass, jlong ptr) { + try { + SortDescriptor* descriptor = reinterpret_cast(ptr); + delete descriptor; + } CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index dcd63e8ca2..99bfcfee2b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -46,26 +46,17 @@ static void finalize_notification_token(jlong ptr) JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong query_ptr, - jlongArray colunm_indices, jbooleanArray jsort_orders) + jlong sort_desc_native_ptr) { TR_ENTER() try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); auto query = reinterpret_cast(query_ptr); + Results* results = sort_desc_native_ptr ? + new Results(shared_realm, *query, *reinterpret_cast(sort_desc_native_ptr)) : + new Results(shared_realm, *query, {}) ; - JniBooleanArray order(env, jsort_orders); - JniLongArray indices(env, colunm_indices); - - std::vector sort_order; - std::vector> sort_indices; - for(jsize i = 0; i < order.len(); ++i) { - sort_order.push_back(to_bool(order[i])); - sort_indices.push_back(std::vector { S(indices[i]) }); - } - - SortDescriptor sort_descriptor(*(query->get_table().get()), sort_indices, sort_order); - Results results(shared_realm, *query, sort_descriptor); - return reinterpret_cast(new Results(std::move(results))); + return reinterpret_cast(results); } CATCH_STD() return reinterpret_cast(nullptr); } diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 53825bf175..2c0c5ca92a 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -493,6 +493,21 @@ class JniLongArray { , m_releaseMode(JNI_ABORT) { } + JniLongArray(JniLongArray& other) = delete; + + JniLongArray(JniLongArray&& other) + : m_env(other.m_env) + , m_javaArray(other.m_javaArray) + , m_arrayLength(other.m_arrayLength) + , m_array(other.m_array) + , m_releaseMode(other.m_releaseMode) + { + other.m_env = nullptr; + other.m_javaArray = nullptr; + other.m_arrayLength = 0; + other.m_array = nullptr; + } + ~JniLongArray() { if (m_array) { @@ -521,11 +536,47 @@ class JniLongArray { } private: - JNIEnv* const m_env; - jlongArray const m_javaArray; - jsize const m_arrayLength; - jlong* const m_array; - jint m_releaseMode; + JNIEnv* m_env; + jlongArray m_javaArray; + jsize m_arrayLength; + jlong* m_array; + jint m_releaseMode; +}; + +template +class JniArrayOfArrays { +public: + JniArrayOfArrays(JNIEnv* env, jobjectArray javaArray) + : m_env(env) + , m_javaArray(javaArray) + , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) + { + for (int i = 0; i < m_arrayLength; i++) { + // No type checking. Internal use only. + J j_array = static_cast(env->GetObjectArrayElement(m_javaArray, i)); + m_array.push_back(T(env, j_array)); + } + } + + ~JniArrayOfArrays() + { + } + + inline jsize len() const noexcept + { + return m_arrayLength; + } + + inline T& operator[](const int index) noexcept + { + return m_array[index]; + } + +private: + JNIEnv* const m_env; + jobjectArray const m_javaArray; + jsize const m_arrayLength; + std::vector m_array; }; class JniByteArray { diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 593dc658c3..8b87a8904e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -27,11 +27,13 @@ import java.util.concurrent.Future; import io.realm.annotations.Required; +import io.realm.internal.Collection; import io.realm.internal.LinkView; import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; +import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.TableQuery; @@ -1647,11 +1649,11 @@ public long count() { public RealmResults findAll() { checkQueryIsNotReused(); RealmResults realmResults; + Collection collection = new Collection(realm.sharedRealm, query, null); if (isDynamicQuery()) { - realmResults = (RealmResults) RealmResults.createFromDynamicTableOrView(realm, query.findAll(), className); + realmResults = new RealmResults(realm, collection, className); } else { - //realmResults = RealmResults.createFromTableOrView(realm, query.findAll(), clazz); - realmResults = RealmResults.createFromQuery(realm, query, clazz, null, null); + realmResults = new RealmResults(realm, collection, clazz); } return realmResults; } @@ -1755,15 +1757,14 @@ public Long call() throws Exception { @SuppressWarnings("unchecked") public RealmResults findAllSorted(String fieldName, Sort sortOrder) { checkQueryIsNotReused(); - TableView tableView = query.findAll(); - long columnIndex = getColumnIndexForSort(fieldName); - tableView.sort(columnIndex, sortOrder); + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldName, sortOrder); + Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor); RealmResults realmResults; if (isDynamicQuery()) { - realmResults = (RealmResults) RealmResults.createFromDynamicTableOrView(realm, tableView, className); + realmResults = new RealmResults(realm, collection, className); } else { - realmResults = RealmResults.createFromTableOrView(realm, tableView, clazz); + realmResults = new RealmResults(realm, collection, clazz); } return realmResults; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 4a68b92061..5acdb45637 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -95,12 +95,6 @@ public final class RealmResults extends AbstractList im public static final byte AGGREGATE_FUNCTION_AVERAGE = 3; public static final byte AGGREGATE_FUNCTION_SUM = 4; - static RealmResults createFromQuery(BaseRealm realm, TableQuery query, Class clazz, - String fieldNames[], Sort[] sortOrder) { - Collection collection = new Collection(realm.sharedRealm, query, null, null); - return new RealmResults(realm, collection, clazz); - } - static RealmResults createFromTableQuery(BaseRealm realm, TableQuery query, Class clazz) { return new RealmResults(realm, query, clazz); } @@ -128,6 +122,13 @@ static RealmResults createFromDynamicTableOrView(BaseRealm r this.collection = collection; } + RealmResults(BaseRealm realm, io.realm.internal.Collection collection, String className) { + this.realm = realm; + this.query = null; + this.className = className; + this.collection = collection; + } + private RealmResults(BaseRealm realm, TableQuery query, Class clazz, String fieldNames[], Sort[] sortOrder) { this.realm = realm; this.classSpec = clazz; @@ -807,7 +808,8 @@ public boolean hasNext() { */ public E next() { realm.checkIfValid(); - checkRealmIsStable(); + // FIXME: Enable this + //checkRealmIsStable(); pos++; if (pos >= size()) { throw new NoSuchElementException("Cannot access index " + pos + " when size is " + size() + ". Remember to check hasNext() before using next()."); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 05872dd9ae..7b4e83813a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -103,11 +103,16 @@ public byte getValue() { } } - public Collection(SharedRealm sharedRealm, TableQuery query, long indices[], boolean[] orders) { + public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor) { this.context = sharedRealm.context; this.query = query; - this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), indices, orders); + if (sortDescriptor == null) { + this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), 0); + } else { + this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), + sortDescriptor.getNativePtr()); + } this.context.addReference(this); } @@ -180,8 +185,8 @@ private void notifyChangeListeners() { } private static native long nativeGetFinalizerPtr(); - private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long[] columnIndices, - boolean[] orders); + private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, + long sortDescNativePtr); private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); private static native boolean nativeContains(long nativePtr, long nativeRowPtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java new file mode 100644 index 0000000000..6cfdd6b884 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm.internal; + +import io.realm.RealmFieldType; + +public class FieldDescriptor { + + private long[] columnIndices; + private RealmFieldType lastFieldType; + private String lastFieldName; + + public FieldDescriptor(Table table, String fieldDescription, boolean allowList) { + if (fieldDescription == null || fieldDescription.isEmpty()) { + throw new IllegalArgumentException("Non-empty field name must be provided"); + } + if (fieldDescription.startsWith(".") || fieldDescription.endsWith(".")) { + throw new IllegalArgumentException("Illegal field name. It cannot start or end with a '.': " + fieldDescription); + } + if (fieldDescription.contains(".")) { + // Resolve field description down to last field name + String[] names = fieldDescription.split("\\."); + long[] columnIndices = new long[names.length]; + for (int i = 0; i < names.length - 1; i++) { + long index = table.getColumnIndex(names[i]); + if (index < 0) { + throw new IllegalArgumentException( + String.format("Invalid field name: '%s' does not refer to a class.", names[i])); + } + RealmFieldType type = table.getColumnType(index); + if (type == RealmFieldType.OBJECT || (allowList && type == RealmFieldType.LIST)) { + table = table.getLinkTarget(index); + columnIndices[i] = index; + } else if (!allowList && type == RealmFieldType.LIST) { + throw new IllegalArgumentException( + String.format("'RealmList' field '%s' is not a supported link field here.", names[i])); + } else { + throw new IllegalArgumentException( + String.format("Invalid field name: '%s' does not refer to a class.", names[i])); + } + // TODO: Check search index for distinct? + } + + // Check if last field name is a valid field + String columnName = names[names.length - 1]; + long columnIndex = table.getColumnIndex(columnName); + columnIndices[names.length - 1] = columnIndex; + if (columnIndex < 0) { + throw new IllegalArgumentException( + String.format("'%s' is not a field name in class '%s'.", columnName, table.getName())); + } + + this.lastFieldType = table.getColumnType(columnIndex); + this.lastFieldName = columnName; + this.columnIndices = columnIndices; + } else { + long fieldIndex = table.getColumnIndex(fieldDescription); + if (fieldIndex == Table.NO_MATCH) { + throw new IllegalArgumentException(String.format("Field '%s' does not exist.", fieldDescription)); + } + this.lastFieldType = table.getColumnType(fieldIndex); + this.lastFieldName = fieldDescription; + this.columnIndices = new long[] {fieldIndex}; + } + } + + public long[] getColumnIndices() { + return columnIndices; + } + + public RealmFieldType getLastFieldType() { + return lastFieldType; + } + + public String getLastFieldName() { + return lastFieldName; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java new file mode 100644 index 0000000000..1b14544273 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java @@ -0,0 +1,139 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import java.io.*; + +import io.realm.RealmFieldType; +import io.realm.Sort; + +public class SortDescriptor implements Closeable { + + private final long[][] columnIndices; + private final boolean[] ascendings; + private long nativePtr = 0; + private final static RealmFieldType[] validFieldTypesForSort = new RealmFieldType[] { + RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, + RealmFieldType.STRING, RealmFieldType.DATE + }; + private final static RealmFieldType[] validFieldTypesForDistinct = new RealmFieldType[] { + RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE + }; + + // Internal use only. For JNI testing. + SortDescriptor(Table table, long[] columnIndices) { + this(table, new long[][] {columnIndices}, null); + } + + // Internal use only. For JNI testing. + SortDescriptor(Table table, long[] columnIndices, Sort sortOrder) { + this(table, new long[][] {columnIndices}, new Sort[] {sortOrder}); + } + + private SortDescriptor(Table table, long[][] columnIndices, Sort[] sortOrders) { + if (sortOrders != null) { + ascendings = new boolean[sortOrders.length]; + for (int i = 0; i < sortOrders.length; i++) { + ascendings[i] = sortOrders[i].getValue(); + } + } else { + ascendings = null; + } + + this.columnIndices = columnIndices; + nativePtr = nativeCreate(table.getNativePtr(), columnIndices, ascendings); + } + + public static SortDescriptor getInstanceForSort(Table table, String fieldDescription, Sort sortOrder) { + return getInstanceForSort(table, new String[] {fieldDescription}, new Sort[] {sortOrder}); + } + + public static SortDescriptor getInstanceForSort(Table table, String[] fieldDescriptions, Sort[] sortOrders) { + if (fieldDescriptions == null || fieldDescriptions.length == 0) { + throw new IllegalArgumentException("You must provide at least one field name."); + } + if (sortOrders == null || sortOrders.length == 0) { + throw new IllegalArgumentException("You must provide at least one sort order."); + } + if (fieldDescriptions.length != sortOrders.length) { + throw new IllegalArgumentException("Number of fields and sort orders do not match."); + } + + long[][] columnIndices = new long[fieldDescriptions.length][]; + for (int i = 0; i < fieldDescriptions.length; i++) { + FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], false); + checkFieldTypeForSort(descriptor.getLastFieldType(), descriptor.getLastFieldName(), fieldDescriptions[i]); + columnIndices[i] = descriptor.getColumnIndices(); + } + + return new SortDescriptor(table, columnIndices, sortOrders); + } + + public static SortDescriptor getInstanceForDistinct(Table table, String fieldDescription) { + return getInstanceForDistinct(table, new String[] {fieldDescription}); + } + + public static SortDescriptor getInstanceForDistinct(Table table, String[] fieldDescriptions) { + if (fieldDescriptions == null || fieldDescriptions.length == 0) { + throw new IllegalArgumentException("You must provide at least one field name."); + } + + long[][] columnIndices = new long[fieldDescriptions.length][]; + for (int i = 0; i < fieldDescriptions.length; i++) { + FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], false); + checkFieldTypeForDistinct( + descriptor.getLastFieldType(), descriptor.getLastFieldName(), fieldDescriptions[i]); + columnIndices[i] = descriptor.getColumnIndices(); + } + + return new SortDescriptor(table, columnIndices, null); + } + + public long getNativePtr() { + return nativePtr; + } + + private static void checkFieldTypeForSort(RealmFieldType type, String fieldName, String fieldDescriptions) { + for (RealmFieldType aValidFieldTypesForSort : validFieldTypesForSort) { + if (aValidFieldTypesForSort == type) { + return; + } + } + throw new IllegalArgumentException(String.format( + "Sort is not supported on '%s' field '%s' in '%s'.", type.toString(), fieldName, fieldDescriptions)); + } + + private static void checkFieldTypeForDistinct(RealmFieldType type, String fieldName, String fieldDescriptions) { + for (RealmFieldType aValidFieldTypesForSort : validFieldTypesForDistinct) { + if (aValidFieldTypesForSort == type) { + return; + } + } + throw new IllegalArgumentException(String.format( + "Distinct is not supported on '%s' field '%s' in '%s'.", + type.toString(), fieldName, fieldDescriptions)); + } + + @Override + public void close() { + nativeClose(nativePtr); + nativePtr = 0; + } + + private static native long nativeCreate(long tablePtr, long[][] columnIndices, boolean[] ascending); + private static native void nativeClose(long ptr); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index d8c6433681..342c1762ec 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -72,6 +72,11 @@ public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } + // FIXME: Hide this? + public Table getTable() { + return table; + } + /** * Checks in core if query syntax is valid. Throws exception, if not. */ From c79826e618efca679802e60f88871fab0aa69b95 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 28 Nov 2016 16:35:10 +0800 Subject: [PATCH 0235/2110] Results.sort() --- .../main/cpp/io_realm_internal_Collection.cpp | 26 ++++--------------- .../src/main/java/io/realm/RealmResults.java | 20 ++++++++------ .../java/io/realm/internal/Collection.java | 21 ++++++++++++++- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 99bfcfee2b..fb084226fd 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -52,9 +52,9 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); auto query = reinterpret_cast(query_ptr); - Results* results = sort_desc_native_ptr ? - new Results(shared_realm, *query, *reinterpret_cast(sort_desc_native_ptr)) : - new Results(shared_realm, *query, {}) ; + auto results = sort_desc_native_ptr ? + new Results(shared_realm, *query, *reinterpret_cast(sort_desc_native_ptr)) : + new Results(shared_realm, *query, {}) ; return reinterpret_cast(results); } CATCH_STD() @@ -167,28 +167,12 @@ Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong nat } JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jlongArray colunm_indices, - jbooleanArray jsort_orders) +Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jlong sort_desc_native_ptr) { TR_ENTER_PTR(native_ptr) try { auto results = reinterpret_cast(native_ptr); - - JniBooleanArray order(env, jsort_orders); - JniLongArray indices(env, colunm_indices); - - if (order.len() != indices.len()) { - throw std::invalid_argument("Number of columns and sorting orders do not match."); - } - - std::vector sort_orders; - std::vector> sort_indices; - for(jsize i = 0; i < order.len(); ++i) { - sort_orders.push_back(to_bool(order[i])); - sort_indices.push_back(std::vector { S(indices[i]) }); - } - - SortDescriptor sort_descriptor(*(results->get_query().get_table().get()), sort_indices, sort_orders); + auto sort_descriptor = *reinterpret_cast(sort_desc_native_ptr); auto sorted_result = results->sort(std::move(sort_descriptor)); return reinterpret_cast(new Results(std::move(sorted_result))); } CATCH_STD() diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 5acdb45637..766e7a7866 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -31,6 +31,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; +import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.TableQuery; @@ -429,15 +430,18 @@ private long getColumnIndexForSort(String fieldName) { */ @Override public RealmResults sort(String fieldName) { - /* - if (nativePtr == 0) { - return this.sort(fieldName, Sort.ASCENDING); - } else { - long ptr = nativeSort(nativePtr, new long[]{getColumnIndexForSort(fieldName)}, new boolean[]{Sort.ASCENDING.getValue()}); - return new RealmResults(realm, className, ptr); + SortDescriptor sortDescriptor = + SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, Sort.ASCENDING); + try { + Collection sortedCollection = collection.sort(sortDescriptor); + if (className != null) { + return new RealmResults(realm, sortedCollection, className); + } else { + return new RealmResults(realm, sortedCollection, classSpec); + } + } finally { + sortDescriptor.close(); } - */ - return null; } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 7b4e83813a..d0cdd46bcd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -75,6 +75,7 @@ public void close() { private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); + private final SharedRealm sharedRealm; private final Context context; private final TableQuery query; private final List listeners = new CopyOnWriteArrayList(); @@ -104,6 +105,7 @@ public byte getValue() { } public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor) { + this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.query = query; @@ -116,6 +118,15 @@ public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sort this.context.addReference(this); } + public Collection(SharedRealm sharedRealm, TableQuery query, long nativePtr) { + this.sharedRealm = sharedRealm; + this.context = sharedRealm.context; + this.query = query; + this.nativePtr = nativePtr; + + this.context.addReference(this); + } + @Override public long getNativePtr() { return nativePtr; @@ -130,6 +141,10 @@ public UncheckedRow getUncheckedRow(int index) { return UncheckedRow.getByRowPointer(query.table, nativeGetRow(nativePtr, index)); } + public Table getTable() { + return query.getTable(); + } + public Object aggregate(Aggregate aggregateMethod, long columnIndex) { return nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); } @@ -143,6 +158,10 @@ public void clear() { nativeClear(nativePtr); } + public Collection sort(SortDescriptor sortDescriptor) { + return new Collection(sharedRealm, query, nativeSort(nativePtr, sortDescriptor.getNativePtr())); + } + public void addListener(Listener listener) { if (!listeners.contains(listener)) { listeners.add(listener); @@ -193,7 +212,7 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native void nativeClear(long nativePtr); private static native long nativeSize(long nativePtr); private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); - private static native long nativeSort(long nativePtr, long[] columnIndices, boolean[] orders); + private static native long nativeSort(long nativePtr, long sortDescNativePtr); private native long nativeAddListener(long nativePtr); private static native long nativeNotificationTokenGetFinalizerPtr(); private static native long nativeNotificationTokenClose(long nativePtr); From 54fd64be23153c0c3a49ca1e16d53e685529a0a7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 28 Nov 2016 16:45:10 +0800 Subject: [PATCH 0236/2110] Results.contains() --- .../src/main/java/io/realm/RealmResults.java | 23 ++++++++----------- .../java/io/realm/internal/Collection.java | 4 ++++ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 766e7a7866..a07d54dc10 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -31,12 +31,17 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; +import io.realm.internal.CheckedRow; +import io.realm.internal.InvalidRow; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.TableQuery; import io.realm.internal.TableView; import io.realm.internal.Collection; +import io.realm.internal.UncheckedRow; import io.realm.internal.async.BadVersionException; import io.realm.log.RealmLog; import rx.Observable; @@ -243,27 +248,17 @@ public RealmQuery where() { */ @Override public boolean contains(Object object) { - /* boolean contains = false; if (isLoaded() && object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; - if (nativePtr == 0) { - if (realm.getPath().equals(proxy.realmGet$proxyState().getRealm$realm().getPath()) && proxy.realmGet$proxyState().getRow$realm() != InvalidRow.INSTANCE) { - contains = (table.sourceRowIndex(proxy.realmGet$proxyState().getRow$realm().getIndex()) != TableOrView.NO_MATCH); - } + Row row = proxy.realmGet$proxyState().getRow$realm(); + if (row instanceof InvalidRow) { + contains = false; } else { - if (realm instanceof DynamicRealm) { - UncheckedRow row = (UncheckedRow) proxy.realmGet$proxyState().getRow$realm(); - contains = nativeContains(nativePtr, row.getNativePtr()); - } else { - CheckedRow row = (CheckedRow) proxy.realmGet$proxyState().getRow$realm(); - contains = nativeContains(nativePtr, row.getNativePtr()); - } + contains = collection.contains((UncheckedRow) row); } } return contains; - */ - return false; } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index d0cdd46bcd..24bd0b3322 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -162,6 +162,10 @@ public Collection sort(SortDescriptor sortDescriptor) { return new Collection(sharedRealm, query, nativeSort(nativePtr, sortDescriptor.getNativePtr())); } + public boolean contains(UncheckedRow row) { + return nativeContains(nativePtr, row.getNativePtr()); + } + public void addListener(Listener listener) { if (!listeners.contains(listener)) { listeners.add(listener); From 49da95311c3f4c787836eb3dcdec312f81582457 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 28 Nov 2016 17:36:24 +0800 Subject: [PATCH 0237/2110] More RealmResults.sort() --- .../src/main/java/io/realm/RealmResults.java | 53 ++++++++----------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index a07d54dc10..2568177cfd 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -198,13 +198,6 @@ private RealmResults(BaseRealm realm, TableOrView table, String className) { this.currentTableViewVersion = table.syncIfNeeded(); } - private RealmResults(BaseRealm realm, String className, long nativePtr) { - this.realm = realm; - this.className = className; - this.query = null; - collection = null; - } - TableOrView getTableOrView() { if (table == null) { return realm.schema.getTable(classSpec); @@ -444,15 +437,18 @@ public RealmResults sort(String fieldName) { */ @Override public RealmResults sort(String fieldName, Sort sortOrder) { - /* - if (nativePtr == 0) { - return where().findAllSorted(fieldName, sortOrder); - } else { - long ptr = nativeSort(nativePtr, new long[]{getColumnIndexForSort(fieldName)}, new boolean[]{sortOrder == Sort.ASCENDING}); - return new RealmResults(realm, className, ptr); + SortDescriptor sortDescriptor = + SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, sortOrder); + try { + Collection sortedCollection = collection.sort(sortDescriptor); + if (className != null) { + return new RealmResults(realm, sortedCollection, className); + } else { + return new RealmResults(realm, sortedCollection, classSpec); + } + } finally { + sortDescriptor.close(); } - */ - return null; } /** @@ -460,25 +456,18 @@ public RealmResults sort(String fieldName, Sort sortOrder) { */ @Override public RealmResults sort(String fieldNames[], Sort sortOrders[]) { - /* - if (nativePtr == 0) { - return where().findAllSorted(fieldNames, sortOrders); - } else { - long columnIndices[] = new long[fieldNames.length]; - for(int i = 0; i < fieldNames.length; i++) { - columnIndices[i] = getColumnIndexForSort(fieldNames[i]); - } - - boolean orders[] = new boolean[sortOrders.length]; - for(int i = 0; i < sortOrders.length; i++) { - orders[i] = sortOrders[i].getValue(); + SortDescriptor sortDescriptor = + SortDescriptor.getInstanceForSort(collection.getTable(), fieldNames, sortOrders); + try { + Collection sortedCollection = collection.sort(sortDescriptor); + if (className != null) { + return new RealmResults(realm, sortedCollection, className); + } else { + return new RealmResults(realm, sortedCollection, classSpec); } - - long ptr = nativeSort(nativePtr, columnIndices, orders); - return new RealmResults(realm, className, ptr); + } finally { + sortDescriptor.close(); } - */ - return null; } /** From ec21df4fc9fc1453488ebfffa0c6ae98b517fe92 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 28 Nov 2016 18:50:57 +0800 Subject: [PATCH 0238/2110] Fix contains & deleteAll --- realm/realm-library/src/main/java/io/realm/RealmResults.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 2568177cfd..052d8bba8c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -342,6 +342,7 @@ public boolean deleteAllFromRealm() { realm.checkIfValid(); if (size() > 0) { collection.clear(); + return true; } return false; } @@ -406,7 +407,7 @@ private long getColumnIndexForSort(String fieldName) { if (fieldName.contains(".")) { throw new IllegalArgumentException("Sorting using child object fields is not supported: " + fieldName); } - long columnIndex = table.getColumnIndex(fieldName); + long columnIndex = collection.getTable().getColumnIndex(fieldName); if (columnIndex < 0) { throw new IllegalArgumentException(String.format("Field '%s' does not exist.", fieldName)); } From 668fae440e79859791183869a8970ea0f71f01de Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 28 Nov 2016 18:58:31 +0800 Subject: [PATCH 0239/2110] Deprecate findAllxxxAsync queries --- .../src/main/java/io/realm/RealmQuery.java | 274 +----------------- 1 file changed, 9 insertions(+), 265 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 8b87a8904e..41effef6a4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1659,86 +1659,10 @@ public RealmResults findAll() { } /** - * Finds all objects that fulfill the query conditions and sorted by specific field name. - * This method is only available from a Looper thread. - * - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @see io.realm.RealmResults + * @deprecated use {@link #findAll()} instead. */ public RealmResults findAllAsync() { - checkQueryIsNotReused(); - final WeakReference weakNotifier = getWeakReferenceNotifier(); - - // handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - - // save query arguments (for future update) - argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_ALL); - - // we need to use the same configuration to open a background SharedRealm (i.e Realm) - // to perform the query - final RealmConfiguration realmConfiguration = realm.getConfiguration(); - - // prepare an empty reference of the RealmResults, so we can return it immediately (promise) - // then update it once the query completes in the background. - RealmResults realmResults; - if (isDynamicQuery()) { - //noinspection unchecked - realmResults = (RealmResults) RealmResults.createFromDynamicClass(realm, query, className); - } else { - realmResults = RealmResults.createFromTableQuery(realm, query, clazz); - } - - final WeakReference> weakRealmResults = realm.handlerController.addToAsyncRealmResults(realmResults, this); - - final Future pendingQuery = Realm.asyncTaskExecutor.submitQuery(new Callable() { - @Override - public Long call() throws Exception { - if (!Thread.currentThread().isInterrupted()) { - SharedRealm sharedRealm = null; - - try { - sharedRealm = SharedRealm.getInstance(realmConfiguration); - - // Run the query & handover the table view for the caller thread - // Note: the handoverQueryPointer contains the versionID needed by the SG in order - // to import it. - long handoverTableViewPointer = TableQuery.findAllWithHandover(sharedRealm, - handoverQueryPointer); - - QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); - result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); - result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS, result); - - return handoverTableViewPointer; - - } catch (BadVersionException e) { - // In some rare race conditions, this can happen. In that case, just ignore the error. - RealmLog.debug("findAllAsync handover could not complete due to a BadVersionException. " + - "Retry is scheduled by a REALM_CHANGED event."); - - } catch (Throwable e) { - RealmLog.error(e); - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); - } finally { - if (sharedRealm != null && !sharedRealm.isClosed()) { - sharedRealm.close(); - } - } - } else { - TableQuery.nativeCloseQueryHandover(handoverQueryPointer); - } - - return INVALID_NATIVE_POINTER; - } - }); - - realmResults.setPendingQuery(pendingQuery); - return realmResults; + return findAll(); } /** @@ -1770,88 +1694,10 @@ public RealmResults findAllSorted(String fieldName, Sort sortOrder) { } /** - * Similar to {@link #findAllSorted(String, Sort)} but runs asynchronously on a worker thread - * (Need a Realm opened from a looper thread to work). - * - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @throws java.lang.IllegalArgumentException if field name does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. + * @deprecated use {@link #findAllSorted(String, Sort) instead.} */ public RealmResults findAllSortedAsync(final String fieldName, final Sort sortOrder) { - checkQueryIsNotReused(); - long columnIndex = getColumnIndexForSort(fieldName); - - // capture the query arguments for future retries & update - argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_ALL_SORTED); - argumentsHolder.sortOrder = sortOrder; - argumentsHolder.columnIndex = columnIndex; - - final WeakReference weakNotifier = getWeakReferenceNotifier(); - - // handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - - // we need to use the same configuration to open a background SharedRealm to perform the query - final RealmConfiguration realmConfiguration = realm.getConfiguration(); - - RealmResults realmResults; - if (isDynamicQuery()) { - //noinspection unchecked - realmResults = (RealmResults) RealmResults.createFromDynamicClass(realm, query, className); - } else { - realmResults = RealmResults.createFromTableQuery(realm, query, clazz); - } - - final WeakReference> weakRealmResults = - realm.handlerController.addToAsyncRealmResults(realmResults, this); - - final Future pendingQuery = Realm.asyncTaskExecutor.submitQuery(new Callable() { - @Override - public Long call() throws Exception { - if (!Thread.currentThread().isInterrupted()) { - SharedRealm sharedRealm = null; - - try { - sharedRealm = SharedRealm.getInstance(realmConfiguration); - - long columnIndex = getColumnIndexForSort(fieldName); - - // run the query & handover the table view for the caller thread - long handoverTableViewPointer = TableQuery.findAllSortedWithHandover(sharedRealm, - handoverQueryPointer, columnIndex, sortOrder); - - QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); - result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); - result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS, result); - - return handoverTableViewPointer; - } catch (BadVersionException e) { - // In some rare race conditions, this can happen. In that case, just ignore the error. - RealmLog.debug("findAllSortedAsync handover could not complete due to a BadVersionException. " + - "Retry is scheduled by a REALM_CHANGED event."); - - } catch (Throwable e) { - RealmLog.error(e); - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); - - } finally { - if (sharedRealm!= null && !sharedRealm.isClosed()) { - sharedRealm.close(); - } - } - } else { - TableQuery.nativeCloseQueryHandover(handoverQueryPointer); - } - - return INVALID_NATIVE_POINTER; - } - }); - realmResults.setPendingQuery(pendingQuery); - return realmResults; + return findAllSorted(fieldName, sortOrder); } @@ -1872,13 +1718,7 @@ public RealmResults findAllSorted(String fieldName) { } /** - * Similar to {@link #findAllSorted(String)} but runs asynchronously on a worker thread - * This method is only available from a Looper thread. - * - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @throws java.lang.IllegalArgumentException if the field name does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. + * @deprecated use {@link #findAllSorted(String)} instead. */ public RealmResults findAllSortedAsync(String fieldName) { return findAllSortedAsync(fieldName, Sort.ASCENDING); @@ -1892,7 +1732,7 @@ public RealmResults findAllSortedAsync(String fieldName) { * * @param fieldNames an array of field names to sort by. * @param sortOrders how to sort the field names. - * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero + * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero * objects is returned. * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. @@ -1929,100 +1769,10 @@ private boolean isDynamicQuery() { } /** - * Similar to {@link #findAllSorted(String[], Sort[])} but runs asynchronously - * from a worker thread. - * This method is only available from a Looper thread. - * - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @see io.realm.RealmResults - * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. + * @deprecated use {@link #findAllSorted(String[], Sort[])} instead. */ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sortOrders) { - checkQueryIsNotReused(); - checkSortParameters(fieldNames, sortOrders); - - if (fieldNames.length == 1 && sortOrders.length == 1) { - return findAllSortedAsync(fieldNames[0], sortOrders[0]); - - } else { - final WeakReference weakNotifier = getWeakReferenceNotifier(); - - // Handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - - // We need to use the same configuration to open a background SharedRealm to perform the query - final RealmConfiguration realmConfiguration = realm.getConfiguration(); - - final long indices[] = new long[fieldNames.length]; - for (int i = 0; i < fieldNames.length; i++) { - String fieldName = fieldNames[i]; - long columnIndex = getColumnIndexForSort(fieldName); - indices[i] = columnIndex; - } - - // capture the query arguments for future retries & update - argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_ALL_MULTI_SORTED); - argumentsHolder.sortOrders = sortOrders; - argumentsHolder.columnIndices = indices; - - // prepare the promise result - RealmResults realmResults; - if (isDynamicQuery()) { - //noinspection unchecked - realmResults = (RealmResults) RealmResults.createFromDynamicClass(realm, query, className); - } else { - realmResults = RealmResults.createFromTableQuery(realm, query, clazz); - } - - final WeakReference> weakRealmResults = realm.handlerController.addToAsyncRealmResults(realmResults, this); - - final Future pendingQuery = Realm.asyncTaskExecutor.submitQuery(new Callable() { - @Override - public Long call() throws Exception { - if (!Thread.currentThread().isInterrupted()) { - SharedRealm sharedRealm = null; - - try { - sharedRealm = SharedRealm.getInstance(realmConfiguration); - - // run the query & handover the table view for the caller thread - long handoverTableViewPointer = TableQuery.findAllMultiSortedWithHandover(sharedRealm, - handoverQueryPointer, indices, sortOrders); - - QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); - result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); - result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS, result); - - return handoverTableViewPointer; - } catch (BadVersionException e) { - // In some rare race conditions, this can happen. In that case, just ignore the error. - RealmLog.debug("findAllSortedAsync handover could not complete due to a BadVersionException. " + - "Retry is scheduled by a REALM_CHANGED event."); - - } catch (Throwable e) { - RealmLog.error(e); - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); - } finally { - if (sharedRealm != null && !sharedRealm.isClosed()) { - sharedRealm.close(); - } - } - } else { - TableQuery.nativeCloseQueryHandover(handoverQueryPointer); - } - - return INVALID_NATIVE_POINTER; - } - }); - - realmResults.setPendingQuery(pendingQuery); - return realmResults; - } + return findAllSorted(fieldNames, sortOrders); } /** @@ -2046,13 +1796,7 @@ public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, } /** - * Similar to {@link #findAllSorted(String, Sort, String, Sort)} but runs asynchronously on a worker thread - * This method is only available from a Looper thread. - * - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @throws java.lang.IllegalArgumentException if a field name does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. + * @deprecated use {@link #findAllSorted(String, Sort, String, Sort)} instead. */ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { From c69deb3cf495bf7ed14895543c68ea0dbb407add Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 28 Nov 2016 19:04:59 +0800 Subject: [PATCH 0240/2110] Remove useless code --- .../src/main/java/io/realm/RealmResults.java | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 052d8bba8c..d971354843 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -31,7 +31,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; -import io.realm.internal.CheckedRow; import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; @@ -95,12 +94,6 @@ public final class RealmResults extends AbstractList im // clear it. private boolean viewUpdated = false; - // Public for static checking in JNI - public static final byte AGGREGATE_FUNCTION_MINIMUM = 1; - public static final byte AGGREGATE_FUNCTION_MAXIMUM = 2; - public static final byte AGGREGATE_FUNCTION_AVERAGE = 3; - public static final byte AGGREGATE_FUNCTION_SUM = 4; - static RealmResults createFromTableQuery(BaseRealm realm, TableQuery query, Class clazz) { return new RealmResults(realm, query, clazz); } @@ -135,29 +128,6 @@ static RealmResults createFromDynamicTableOrView(BaseRealm r this.collection = collection; } - private RealmResults(BaseRealm realm, TableQuery query, Class clazz, String fieldNames[], Sort[] sortOrder) { - this.realm = realm; - this.classSpec = clazz; - this.query = query; - - boolean[] order = null; - long[] indices = null; - - if (fieldNames != null && sortOrder != null) { - order = new boolean[sortOrder.length]; - indices = new long[sortOrder.length]; - if (sortOrder.length != fieldNames.length) { - throw new IllegalArgumentException("Number of field names and sort orders does not match"); - } - for (int i = 0; i < sortOrder.length; i++) { - order[i] = sortOrder[i] == Sort.ASCENDING; - indices[i] = getColumnIndexForSort(fieldNames[i]); - } - } - - collection = null; - } - private RealmResults(BaseRealm realm, TableQuery query, Class clazz) { this.realm = realm; this.classSpec = clazz; From 6c9d77d7ad4c9190db81e36808f8b7f7fa5559c4 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Mon, 28 Nov 2016 14:51:21 +0100 Subject: [PATCH 0241/2110] Modernize the git checkout code in the Jenkinsfile (#3851) --- Jenkinsfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7f30fea9f2..364ad7d9fb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,13 +13,12 @@ try { $class: 'GitSCM', branches: scm.branches, gitTool: 'native git', - extensions: scm.extensions + [[$class: 'CleanCheckout']], + extensions: scm.extensions + [ + [$class: 'CleanCheckout'], + [$class: 'SubmoduleOption', recursiveSubmodules: true] + ], userRemoteConfigs: scm.userRemoteConfigs ]) - sh 'git submodule sync' - sh 'git submodule update --init --recursive' - // Make sure not to delete the folder that Jenkins allocates to store scripts - sh 'git clean -ffdx -e .????????' } def buildEnv From c23e5c2edeba9607cb996fc505cf79dc1c0c85d8 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Mon, 28 Nov 2016 14:52:46 +0100 Subject: [PATCH 0242/2110] Revert "Modernize the git checkout code in the Jenkinsfile" (#3852) --- Jenkinsfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 364ad7d9fb..7f30fea9f2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,12 +13,13 @@ try { $class: 'GitSCM', branches: scm.branches, gitTool: 'native git', - extensions: scm.extensions + [ - [$class: 'CleanCheckout'], - [$class: 'SubmoduleOption', recursiveSubmodules: true] - ], + extensions: scm.extensions + [[$class: 'CleanCheckout']], userRemoteConfigs: scm.userRemoteConfigs ]) + sh 'git submodule sync' + sh 'git submodule update --init --recursive' + // Make sure not to delete the folder that Jenkins allocates to store scripts + sh 'git clean -ffdx -e .????????' } def buildEnv From 5c56872ef5c2a4f748904c91b5a0f179ad5741ce Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Mon, 28 Nov 2016 14:54:05 +0100 Subject: [PATCH 0243/2110] Modernize the git checkout code in the Jenkinsfile (#3853) --- Jenkinsfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7f30fea9f2..364ad7d9fb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,13 +13,12 @@ try { $class: 'GitSCM', branches: scm.branches, gitTool: 'native git', - extensions: scm.extensions + [[$class: 'CleanCheckout']], + extensions: scm.extensions + [ + [$class: 'CleanCheckout'], + [$class: 'SubmoduleOption', recursiveSubmodules: true] + ], userRemoteConfigs: scm.userRemoteConfigs ]) - sh 'git submodule sync' - sh 'git submodule update --init --recursive' - // Make sure not to delete the folder that Jenkins allocates to store scripts - sh 'git clean -ffdx -e .????????' } def buildEnv From d9ecff7b858d65b8235038098a0932eeacc9b18a Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Mon, 28 Nov 2016 14:55:25 +0100 Subject: [PATCH 0244/2110] Revert "Revert "Modernize the git checkout code in the Jenkinsfile"" (#3854) --- Jenkinsfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7f30fea9f2..364ad7d9fb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,13 +13,12 @@ try { $class: 'GitSCM', branches: scm.branches, gitTool: 'native git', - extensions: scm.extensions + [[$class: 'CleanCheckout']], + extensions: scm.extensions + [ + [$class: 'CleanCheckout'], + [$class: 'SubmoduleOption', recursiveSubmodules: true] + ], userRemoteConfigs: scm.userRemoteConfigs ]) - sh 'git submodule sync' - sh 'git submodule update --init --recursive' - // Make sure not to delete the folder that Jenkins allocates to store scripts - sh 'git clean -ffdx -e .????????' } def buildEnv From c338d9382336ac9b8fbf00cebdfa11c00d608007 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 13:58:53 +0800 Subject: [PATCH 0245/2110] Distinct support --- .../main/cpp/io_realm_internal_Collection.cpp | 10 +- .../src/main/java/io/realm/RealmQuery.java | 176 +++--------------- .../src/main/java/io/realm/RealmResults.java | 22 +-- .../java/io/realm/internal/Collection.java | 29 ++- 4 files changed, 50 insertions(+), 187 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index fb084226fd..abb5ae65d5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -46,15 +46,17 @@ static void finalize_notification_token(jlong ptr) JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong query_ptr, - jlong sort_desc_native_ptr) + jlong sort_desc_native_ptr, jlong distinct_desc_native_ptr) { TR_ENTER() try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); auto query = reinterpret_cast(query_ptr); - auto results = sort_desc_native_ptr ? - new Results(shared_realm, *query, *reinterpret_cast(sort_desc_native_ptr)) : - new Results(shared_realm, *query, {}) ; + auto sort_desc_ptr = reinterpret_cast(sort_desc_native_ptr); + auto distinct_desc_ptr = reinterpret_cast(distinct_desc_native_ptr); + auto results = new Results(shared_realm, *query, + sort_desc_ptr ? *sort_desc_ptr : SortDescriptor(), + distinct_desc_ptr ? *distinct_desc_ptr : SortDescriptor()); return reinterpret_cast(results); } CATCH_STD() diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 41effef6a4..ea6ace2f7c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -39,7 +39,6 @@ import io.realm.internal.TableQuery; import io.realm.internal.TableView; import io.realm.internal.async.ArgumentsHolder; -import io.realm.internal.async.BadVersionException; import io.realm.internal.async.QueryUpdateTask; import io.realm.log.RealmLog; @@ -1334,122 +1333,16 @@ public RealmQuery isNotEmpty(String fieldName) { */ public RealmResults distinct(String fieldName) { checkQueryIsNotReused(); - long columnIndex = getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); - TableView tableView = this.query.findAll(); - tableView.distinct(columnIndex); - - RealmResults realmResults; - if (isDynamicQuery()) { - //noinspection unchecked - realmResults = (RealmResults) RealmResults.createFromDynamicTableOrView(realm, tableView, className); - } else { - realmResults = RealmResults.createFromTableOrView(realm, tableView, clazz); - } - return realmResults; + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(query.getTable(), fieldName); + Collection collection = new Collection(realm.sharedRealm, query, null, distinctDescriptor); + return createRealmResults(collection); } /** - * Asynchronously returns a distinct set of objects of a specific class. If the result is - * sorted, the first object will be returned in case of multiple occurrences, otherwise it is - * undefined which object is returned. - * - * @param fieldName the field name. - * @return immediately a {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the - * query completes. - * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, - * is not indexed, or points to linked fields. + * @deprecated use {@link #distinct(String)} instead. */ public RealmResults distinctAsync(String fieldName) { - checkQueryIsNotReused(); - final long columnIndex = getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); - final WeakReference weakNotifier = getWeakReferenceNotifier(); - - // handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - - // save query arguments (for future update) - argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_DISTINCT); - argumentsHolder.columnIndex = columnIndex; - - // we need to use the same configuration to open a background SharedRealm (i.e Realm) - // to perform the query - final RealmConfiguration realmConfiguration = realm.getConfiguration(); - - // prepare an empty reference of the RealmResults, so we can return it immediately (promise) - // then update it once the query completes in the background. - RealmResults realmResults; - if (isDynamicQuery()) { - //noinspection unchecked - realmResults = (RealmResults) RealmResults.createFromDynamicClass(realm, query, className); - } else { - realmResults = RealmResults.createFromTableQuery(realm, query, clazz); - } - - final WeakReference> weakRealmResults = realm.handlerController.addToAsyncRealmResults(realmResults, this); - - final Future pendingQuery = Realm.asyncTaskExecutor.submitQuery(new Callable() { - @Override - public Long call() throws Exception { - if (!Thread.currentThread().isInterrupted()) { - SharedRealm sharedRealm = null; - - try { - sharedRealm = SharedRealm.getInstance(realmConfiguration); - - long handoverTableViewPointer = TableQuery. - findDistinctWithHandover(sharedRealm, - handoverQueryPointer, - columnIndex); - - QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmResultsResponse(); - result.updatedTableViews.put(weakRealmResults, handoverTableViewPointer); - result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS, result); - - return handoverTableViewPointer; - } catch (Throwable e) { - RealmLog.error(e); - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); - } finally { - if (sharedRealm != null && !sharedRealm.isClosed()) { - sharedRealm.close(); - } - } - } else { - TableQuery.nativeCloseQueryHandover(handoverQueryPointer); - } - - return INVALID_NATIVE_POINTER; - } - }); - - realmResults.setPendingQuery(pendingQuery); - return realmResults; - } - - // Find and validate the column index for the field name used to create a distinctive TableView. - static long getAndValidateDistinctColumnIndex(String fieldName, Table table) { - // Check empty field name - if (fieldName == null || fieldName.isEmpty()) { - throw new IllegalArgumentException("Non-empty field name must be provided."); - } - long columnIndex = table.getColumnIndex(fieldName); - // Check if field exists - if (columnIndex == -1) { - throw new IllegalArgumentException(String.format("Field name '%s' does not exist.", fieldName)); - } - // Check linked fields - if (fieldName.contains(".")) { - throw new IllegalArgumentException("Distinct operation on linked properties is not supported: " + fieldName); - } - // check if the field is indexed - if (!table.hasSearchIndex(columnIndex)) { - throw new IllegalArgumentException(String.format("Field name '%s' must be indexed in order to use it for distinct queries.", fieldName)); - } - return columnIndex; + return distinct(fieldName); } /** @@ -1466,34 +1359,13 @@ static long getAndValidateDistinctColumnIndex(String fieldName, Table table) { */ public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { checkQueryIsNotReused(); - List columnIndexes = getValidatedColumIndexes(this.table.getTable(), firstFieldName, remainingFieldNames); - TableView tableView = this.query.findAll(); - tableView.distinct(columnIndexes); + String[] fieldNames = new String[1 + remainingFieldNames.length]; - RealmResults realmResults; - if (isDynamicQuery()) { - //noinspection unchecked - realmResults = (RealmResults) RealmResults.createFromDynamicTableOrView(realm, tableView, className); - } else { - realmResults = RealmResults.createFromTableOrView(realm, tableView, clazz); - } - return realmResults; - } - - // find and validate the column indices of fields for building a distinctive TableView with multi-args - static List getValidatedColumIndexes(Table table, String firstFieldName, String... remainingFieldNames) { - List columnIndexes = new ArrayList(); - // find the first index - long firstIndex = getAndValidateDistinctColumnIndex(firstFieldName, table); - columnIndexes.add(firstIndex); - // add remaining of indexes - if (remainingFieldNames != null && 0 < remainingFieldNames.length) { - for (String field : remainingFieldNames) { - long index = getAndValidateDistinctColumnIndex(field, table); - columnIndexes.add(index); - } - } - return columnIndexes; + fieldNames[0] = firstFieldName; + System.arraycopy(remainingFieldNames, 0, fieldNames, 1, remainingFieldNames.length); + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table.getTable(), fieldNames); + Collection collection = new Collection(realm.sharedRealm, query, null, distinctDescriptor); + return createRealmResults(collection); } // Aggregates @@ -1648,14 +1520,8 @@ public long count() { @SuppressWarnings("unchecked") public RealmResults findAll() { checkQueryIsNotReused(); - RealmResults realmResults; - Collection collection = new Collection(realm.sharedRealm, query, null); - if (isDynamicQuery()) { - realmResults = new RealmResults(realm, collection, className); - } else { - realmResults = new RealmResults(realm, collection, clazz); - } - return realmResults; + Collection collection = new Collection(realm.sharedRealm, query); + return createRealmResults(collection); } /** @@ -1684,13 +1550,7 @@ public RealmResults findAllSorted(String fieldName, Sort sortOrder) { SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldName, sortOrder); Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor); - RealmResults realmResults; - if (isDynamicQuery()) { - realmResults = new RealmResults(realm, collection, className); - } else { - realmResults = new RealmResults(realm, collection, clazz); - } - return realmResults; + return createRealmResults(collection); } /** @@ -1985,6 +1845,14 @@ private long getColumnIndexForSort(String fieldName) { return columnIndex; } + private RealmResults createRealmResults(Collection collection) { + if (isDynamicQuery()) { + return new RealmResults(realm, collection, className); + } else { + return new RealmResults(realm, collection, clazz); + } + } + public ArgumentsHolder getArgument() { return argumentsHolder; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index d971354843..e0ae475a74 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -541,29 +541,11 @@ public double average(String fieldName) { * is not indexed, or points to linked fields. */ public RealmResults distinct(String fieldName) { - realm.checkIfValid(); - long columnIndex = RealmQuery.getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); - - TableOrView tableOrView = getTableOrView(); - if (tableOrView instanceof Table) { - this.table = ((Table) tableOrView).getDistinctView(columnIndex); - } else { - ((TableView) tableOrView).distinct(columnIndex); - } - return this; + return where().distinct(fieldName); } /** - * Asynchronously returns a distinct set of objects of a specific class. If the result is - * sorted, the first object will be returned in case of multiple occurrences, otherwise it is - * undefined which object is returned. - * - * @param fieldName the field name. - * @return immediately a {@link RealmResults}. Users need to register a listener - * {@link RealmResults#addChangeListener(RealmChangeListener)} to be notified when the - * query completes. - * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, - * is not indexed, or points to linked fields. + * @deprecated use {@link #distinct(String)} instead. */ public RealmResults distinctAsync(String fieldName) { return where().distinctAsync(fieldName); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 24bd0b3322..95a658f7aa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -82,9 +82,13 @@ public void close() { private NotificationToken notificationToken = null; // Public for static checking in JNI + @SuppressWarnings("WeakerAccess") public static final byte AGGREGATE_FUNCTION_MINIMUM = 1; + @SuppressWarnings("WeakerAccess") public static final byte AGGREGATE_FUNCTION_MAXIMUM = 2; + @SuppressWarnings("WeakerAccess") public static final byte AGGREGATE_FUNCTION_AVERAGE = 3; + @SuppressWarnings("WeakerAccess") public static final byte AGGREGATE_FUNCTION_SUM = 4; public enum Aggregate { @@ -104,21 +108,28 @@ public byte getValue() { } } - public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor) { + public Collection(SharedRealm sharedRealm, TableQuery query, + SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.query = query; - if (sortDescriptor == null) { - this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), 0); - } else { - this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), - sortDescriptor.getNativePtr()); - } + this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), + sortDescriptor == null ? 0 : sortDescriptor.getNativePtr(), + distinctDescriptor == null ? 0 : distinctDescriptor.getNativePtr()); this.context.addReference(this); } - public Collection(SharedRealm sharedRealm, TableQuery query, long nativePtr) { + public Collection(SharedRealm sharedRealm, TableQuery query, + SortDescriptor sortDescriptor) { + this(sharedRealm, query, sortDescriptor, null); + } + + public Collection(SharedRealm sharedRealm, TableQuery query) { + this(sharedRealm, query, null, null); + } + + private Collection(SharedRealm sharedRealm, TableQuery query, long nativePtr) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.query = query; @@ -209,7 +220,7 @@ private void notifyChangeListeners() { private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, - long sortDescNativePtr); + long sortDescNativePtr, long distinctDescNativePtr); private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); private static native boolean nativeContains(long nativePtr, long nativeRowPtr); From 1f171daa0e689367e89c9440befca14e2f0df9ae Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 14:04:29 +0800 Subject: [PATCH 0246/2110] Remove useless code and fix one missed findAllxxx function. --- .../src/main/java/io/realm/RealmQuery.java | 26 ++------- .../src/main/java/io/realm/RealmResults.java | 54 ------------------- 2 files changed, 4 insertions(+), 76 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index ea6ace2f7c..602c2c3273 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1597,31 +1597,13 @@ public RealmResults findAllSortedAsync(String fieldName) { * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ - @SuppressWarnings("unchecked") public RealmResults findAllSorted(String fieldNames[], Sort sortOrders[]) { - checkSortParameters(fieldNames, sortOrders); + checkQueryIsNotReused(); - if (fieldNames.length == 1 && sortOrders.length == 1) { - return findAllSorted(fieldNames[0], sortOrders[0]); - } else { - TableView tableView = query.findAll(); - List columnIndices = new ArrayList(); - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < fieldNames.length; i++) { - String fieldName = fieldNames[i]; - long columnIndex = getColumnIndexForSort(fieldName); - columnIndices.add(columnIndex); - } - tableView.sort(columnIndices, sortOrders); + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldNames, sortOrders); - RealmResults realmResults; - if (isDynamicQuery()) { - realmResults = (RealmResults) RealmResults.createFromDynamicTableOrView(realm, tableView, className); - } else { - realmResults = RealmResults.createFromTableOrView(realm, tableView, clazz); - } - return realmResults; - } + Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor); + return createRealmResults(collection); } private boolean isDynamicQuery() { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index e0ae475a74..2627d49dbe 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -35,10 +35,8 @@ import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SortDescriptor; -import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.TableQuery; -import io.realm.internal.TableView; import io.realm.internal.Collection; import io.realm.internal.UncheckedRow; import io.realm.internal.async.BadVersionException; @@ -81,7 +79,6 @@ public final class RealmResults extends AbstractList im String className; // Class name used by DynamicRealmObjects private TableOrView table = null; - private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final long TABLE_VIEW_VERSION_NONE = -1; private long currentTableViewVersion = TABLE_VIEW_VERSION_NONE; @@ -94,26 +91,6 @@ public final class RealmResults extends AbstractList im // clear it. private boolean viewUpdated = false; - static RealmResults createFromTableQuery(BaseRealm realm, TableQuery query, Class clazz) { - return new RealmResults(realm, query, clazz); - } - - static RealmResults createFromTableOrView(BaseRealm realm, TableOrView table, Class clazz) { - RealmResults realmResults = new RealmResults(realm, table, clazz); - realm.handlerController.addToRealmResults(realmResults); - return realmResults; - } - - static RealmResults createFromDynamicClass(BaseRealm realm, TableQuery query, String className) { - return new RealmResults(realm, query, className); - } - - static RealmResults createFromDynamicTableOrView(BaseRealm realm, TableOrView table, String className) { - RealmResults realmResults = new RealmResults(realm, table, className); - realm.handlerController.addToRealmResults(realmResults); - return realmResults; - } - RealmResults(BaseRealm realm, io.realm.internal.Collection collection, Class clazz) { this.realm = realm; this.query = null; @@ -128,31 +105,6 @@ static RealmResults createFromDynamicTableOrView(BaseRealm r this.collection = collection; } - private RealmResults(BaseRealm realm, TableQuery query, Class clazz) { - this.realm = realm; - this.classSpec = clazz; - this.query = query; - collection = null; - } - - private RealmResults(BaseRealm realm, TableQuery query, String className) { - this.realm = realm; - this.query = query; - this.className = className; - collection = null; - } - - private RealmResults(BaseRealm realm, TableOrView table, Class classSpec) { - this.realm = realm; - this.classSpec = classSpec; - this.table = table; - - this.pendingQuery = null; - this.query = null; - this.currentTableViewVersion = table.syncIfNeeded(); - collection = null; - } - private RealmResults(BaseRealm realm, String className) { this.realm = realm; this.className = className; @@ -162,12 +114,6 @@ private RealmResults(BaseRealm realm, String className) { collection = null; } - private RealmResults(BaseRealm realm, TableOrView table, String className) { - this(realm, className); - this.table = table; - this.currentTableViewVersion = table.syncIfNeeded(); - } - TableOrView getTableOrView() { if (table == null) { return realm.schema.getTable(classSpec); From 686829ffd0001ca5b245cb793387727ef05b00a1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 14:21:59 +0800 Subject: [PATCH 0247/2110] delete code --- .../main/java/io/realm/HandlerController.java | 16 +- .../src/main/java/io/realm/RealmResults.java | 169 ++---------------- 2 files changed, 22 insertions(+), 163 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java index 071dcc6b77..134ead5ab8 100644 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java @@ -302,7 +302,7 @@ void notifyAllListeners(List> realmResultsToB // Notify all RealmResults (async and synchronous). for (Iterator> it = realmResultsToBeNotified.iterator(); !realm.isClosed() && it.hasNext(); ) { RealmResults realmResults = it.next(); - realmResults.notifyChangeListeners(false); + //realmResults.notifyChangeListeners(false); } // Notify all loaded RealmObjects @@ -345,7 +345,7 @@ private void collectRealmResultsCallbacks(Iterator extends AbstractList im private static final long TABLE_VIEW_VERSION_NONE = -1; private long currentTableViewVersion = TABLE_VIEW_VERSION_NONE; - private final TableQuery query; private final io.realm.internal.Collection collection; - private final List>> listeners = new CopyOnWriteArrayList>>(); - private Future pendingQuery; - private boolean asyncQueryCompleted = false; - // Keep track of changes to the RealmResult. Is updated after a call to `syncIfNeeded()`. Calling notifyListeners will - // clear it. - private boolean viewUpdated = false; RealmResults(BaseRealm realm, io.realm.internal.Collection collection, Class clazz) { this.realm = realm; - this.query = null; this.classSpec = clazz; this.collection = collection; } RealmResults(BaseRealm realm, io.realm.internal.Collection collection, String className) { this.realm = realm; - this.query = null; this.className = className; this.collection = collection; } - private RealmResults(BaseRealm realm, String className) { - this.realm = realm; - this.className = className; - - pendingQuery = null; - query = null; - collection = null; - } - TableOrView getTableOrView() { if (table == null) { return realm.schema.getTable(classSpec); @@ -246,8 +221,8 @@ private E lastImpl(boolean shouldThrow, E defaultValue) { @Override public void deleteFromRealm(int location) { realm.checkIfValid(); - TableOrView table = getTableOrView(); - table.remove(location); + // FIXME: Implement this! + throw new RuntimeException("FIXME: Implement this!"); } /** @@ -272,10 +247,6 @@ public boolean deleteAllFromRealm() { */ @Override public Iterator iterator() { - if (!isLoaded()) { - // Collections.emptyIterator(); is only available since API 19 - return Collections.emptyList().iterator(); - } return new RealmResultsIterator(); } @@ -288,10 +259,6 @@ public Iterator iterator() { */ @Override public ListIterator listIterator() { - if (!isLoaded()) { - // Collections.emptyListIterator() is only available since API 19 - return Collections.emptyList().listIterator(); - } return new RealmResultsListIterator(0); } @@ -306,10 +273,6 @@ public ListIterator listIterator() { */ @Override public ListIterator listIterator(int location) { - if (!isLoaded()) { - // Collections.emptyListIterator() is only available since API 19 - return Collections.emptyList().listIterator(location); - } return new RealmResultsListIterator(location); } @@ -404,10 +367,6 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName */ @Override public int size() { - if (!isLoaded()) { - return 0; - } - return collection.size(); } @@ -581,28 +540,14 @@ public boolean retainAll(java.util.Collection collection) { public boolean deleteLastFromRealm() { realm.checkIfValid(); if (size() > 0) { - TableOrView table = getTableOrView(); - table.removeLast(); - return true; + // FIXME: Implement this! + throw new RuntimeException("FIXME: Implement this!"); + //return true; } else { return false; } } - /** - * Syncs this RealmResults, so it is up to date after `advance_read` has been called. - * Not doing so can leave detached accessors in the table view. - * - * By design, we should only call this on looper events. - * - * NOTE: Calling this is a prerequisite to calling {@link #notifyChangeListeners(boolean)}. - */ - void syncIfNeeded() { - long newVersion = table.syncIfNeeded(); - viewUpdated = newVersion != currentTableViewVersion; - currentTableViewVersion = newVersion; - } - /** * Removes the first object in the list. This also deletes the object from the underlying Realm. * @@ -611,9 +556,9 @@ void syncIfNeeded() { @Override public boolean deleteFirstFromRealm() { if (size() > 0) { - TableOrView table = getTableOrView(); - table.removeFirst(); - return true; + // FIXME: Implement this! + throw new RuntimeException("FIXME: Implement this!"); + //return true; } else { return false; } @@ -715,6 +660,8 @@ public void remove() { } protected void checkRealmIsStable() { + // FIXME: Check this! + /* long version = table.getVersion(); // Any change within a write transaction will immediately update the table version. This means that we // cannot depend on the tableVersion heuristic in that case. @@ -725,6 +672,7 @@ protected void checkRealmIsStable() { throw new ConcurrentModificationException("No outside changes to a Realm is allowed while iterating a RealmResults. Don't call Realm.refresh() while iterating."); } tableViewVersion = version; + */ } } @@ -803,87 +751,16 @@ public void set(E object) { } /** - * Swaps the table_view pointer used by this RealmResults mostly called when updating the RealmResults from a worker - * thread. - * - * @param handoverTableViewPointer handover pointer to the new table_view. - * @throws IllegalStateException if caller and worker are not at the same version. - */ - void swapTableViewPointer(long handoverTableViewPointer) { - try { - table = query.importHandoverTableView(handoverTableViewPointer, realm.sharedRealm); - asyncQueryCompleted = true; - } catch (BadVersionException e) { - throw new IllegalStateException("Caller and Worker Realm should have been at the same version"); - } - } - - /** - * Sets the Future instance returned by the worker thread, we need this instance to force {@link #load()} an async - * query, we use it to determine if the current RealmResults is a sync or async one. - * - * @param pendingQuery pending query. - */ - void setPendingQuery(Future pendingQuery) { - this.pendingQuery = pendingQuery; - if (isLoaded()) { - // the query completed before RealmQuery - // had a chance to call setPendingQuery to register the pendingQuery (used - // to determine isLoaded behaviour) - onAsyncQueryCompleted(); - } // else, it will be handled by the {@link BaseRealm#handlerController#handleMessage} - } - - /** - * Returns {@code false} if the results are not yet loaded, {@code true} if they are loaded. Synchronous - * query methods like findAll() will always return {@code true}, while asynchronous query methods like - * findAllAsync() will return {@code false} until the results are available. - * - * @return {@code true} if the query has completed and the data is available, {@code false} if the query is still - * running. + * @deprecated */ public boolean isLoaded() { - realm.checkIfValid(); - return pendingQuery == null || asyncQueryCompleted; + return true; } /** - * Makes an asynchronous query blocking. This will also trigger any registered {@link RealmChangeListener} when - * the query completes. - * - * @return {@code true} if it successfully completed the query, {@code false} otherwise. {@code true} will always - * be returned for unmanaged objects. + * @deprecated */ public boolean load() { - //noinspection SimplifiableIfStatement - if (isLoaded()) { - return true; - } else { - // doesn't guarantee to correctly import the result (because the user may have advanced) - // in this case the Realm#handler will be responsible of retrying - return onAsyncQueryCompleted(); - } - } - - /** - * Called to import the handover table_view pointer & notify listeners. - * This should be invoked once the {@link #pendingQuery} finish, unless the user force {@link #load()}. - * - * @return {@code true} if it successfully completed the query, {@code false} otherwise. - */ - private boolean onAsyncQueryCompleted() { - try { - long tvHandover = pendingQuery.get();// make the query blocking - // this may fail with BadVersionException if the caller and/or the worker thread - // are not in sync. COMPLETED_ASYNC_REALM_RESULTS will be fired by the worker thread - // this should handle more complex use cases like retry, ignore etc - table = query.importHandoverTableView(tvHandover, realm.sharedRealm); - asyncQueryCompleted = true; - notifyChangeListeners(true); - } catch (Exception e) { - RealmLog.debug(e.getMessage()); - return false; - } return true; } @@ -966,22 +843,4 @@ public Observable> asObservable() { throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava."); } } - - /** - * Notifies all registered listeners. - * - * NOTE: Remember to call `syncIfNeeded` before calling this method. - */ - void notifyChangeListeners(boolean forceNotify) { - if (!listeners.isEmpty()) { - // table might be null (if the async query didn't complete - // but we have already registered listeners for it) - if (pendingQuery != null && !asyncQueryCompleted) return; - if (!viewUpdated && !forceNotify) return; - viewUpdated = false; - for (RealmChangeListener listener : listeners) { - listener.onChange(this); - } - } - } } From f8c72d5b90e647c0b646018e3faa5c6f7219cff2 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 29 Nov 2016 16:42:36 +0900 Subject: [PATCH 0248/2110] updated README.md (#3855) --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index e552001903..10e737e150 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,7 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites - * Make sure `make` is available in your `$PATH`. * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install s3cmd (`brew install s3cmd` on Mac, `sudo apt-get install s3cmd` on Ubuntu). - * Get `.s3cfg` file and put it in your home directory. If you'd like to put it other location, add `s3cfg=` in `~/.gradle/gradle.properties`. * Download & install the Android SDK **Build-Tools 24.0.0**, **Android N (API 24)** (for example through Android Studio’s **Android SDK Manager**). * Download the **Android NDK (= r10e)** for [OS X](http://dl.google.com/android/ndk/android-ndk-r10e-darwin-x86_64.bin) or [Linux](http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). From 3292e90c89dcac3981944959231e1d9d33b1106d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 16:14:01 +0800 Subject: [PATCH 0249/2110] RealmResults.where() --- .../src/main/cpp/io_realm_internal_Collection.cpp | 14 ++++++++++++++ .../src/main/java/io/realm/RealmQuery.java | 4 ++-- .../src/main/java/io/realm/RealmResults.java | 10 +++++++--- .../main/java/io/realm/internal/Collection.java | 6 ++++++ 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index abb5ae65d5..f71a316713 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -21,6 +21,7 @@ #include #include +#include #include "util.hpp" @@ -226,3 +227,16 @@ Java_io_realm_internal_Collection_nativeNotificationTokenClose(JNIEnv *, jclass, TR_ENTER_PTR(native_ptr) delete reinterpret_cast(native_ptr); } + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeWhere(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + + Query *query = new Query(results->get_query()); + return reinterpret_cast(query); + } CATCH_STD() + return 0; +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 602c2c3273..3bd195c761 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -148,7 +148,7 @@ private RealmQuery(RealmResults queryResults, Class clazz) { this.schema = realm.schema.getSchemaForClass(clazz); this.table = queryResults.getTableOrView(); this.linkView = null; - this.query = this.table.where(); + this.query = queryResults.getCollection().where(); } private RealmQuery(BaseRealm realm, LinkView linkView, Class clazz) { @@ -173,7 +173,7 @@ private RealmQuery(RealmResults queryResults, String classNa this.className = className; this.schema = realm.schema.getSchemaForClass(className); this.table = schema.table; - this.query = queryResults.getTableOrView().where(); + this.query = queryResults.getCollection().where(); } private RealmQuery(BaseRealm realm, LinkView linkView, String className) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 471a0787f9..d76e1d6022 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -75,15 +75,15 @@ public final class RealmResults extends AbstractList im private static final long TABLE_VIEW_VERSION_NONE = -1; private long currentTableViewVersion = TABLE_VIEW_VERSION_NONE; - private final io.realm.internal.Collection collection; + private final Collection collection; - RealmResults(BaseRealm realm, io.realm.internal.Collection collection, Class clazz) { + RealmResults(BaseRealm realm, Collection collection, Class clazz) { this.realm = realm; this.classSpec = clazz; this.collection = collection; } - RealmResults(BaseRealm realm, io.realm.internal.Collection collection, String className) { + RealmResults(BaseRealm realm, Collection collection, String className) { this.realm = realm; this.className = className; this.collection = collection; @@ -97,6 +97,10 @@ TableOrView getTableOrView() { } } + public Collection getCollection() { + return collection; + } + /** * {@inheritDoc} */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 95a658f7aa..0787c1ede5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -156,6 +156,11 @@ public Table getTable() { return query.getTable(); } + public TableQuery where() { + long nativeQueryPtr = nativeWhere(nativePtr); + return new TableQuery(this.context, this.getTable(), nativeQueryPtr); + } + public Object aggregate(Aggregate aggregateMethod, long columnIndex) { return nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); } @@ -231,4 +236,5 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private native long nativeAddListener(long nativePtr); private static native long nativeNotificationTokenGetFinalizerPtr(); private static native long nativeNotificationTokenClose(long nativePtr); + private static native long nativeWhere(long nativePtr); } From b0a7c335403f147c3dd144d881bf3876d8915fc3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 16:45:03 +0800 Subject: [PATCH 0250/2110] Collection's indexOf --- .../androidTest/java/io/realm/SortTest.java | 32 +++++++++---------- .../main/cpp/io_realm_internal_Collection.cpp | 30 ++++++++++++++++- .../java/io/realm/internal/Collection.java | 14 +++++++- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index bb2f61f4cb..923f7d6f80 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -153,19 +153,19 @@ private void checkSortTwoFieldsStringAscendingIntAscending(RealmResults results) { @@ -179,19 +179,19 @@ private void checkSortTwoFieldsIntString(RealmResults results) { assertEquals("Adam", results.get(0).getColumnString()); assertEquals(4, results.get(0).getColumnLong()); - assertEquals(2, ((TableView) results.getTableOrView()).getSourceRowIndex(0)); + assertEquals(0, results.getCollection().indexOf(2)); assertEquals("Brian", results.get(1).getColumnString()); assertEquals(4, results.get(1).getColumnLong()); - assertEquals(1, ((TableView) results.getTableOrView()).getSourceRowIndex(1)); + assertEquals(1, results.getCollection().indexOf(1)); assertEquals("Adam", results.get(2).getColumnString()); assertEquals(5, results.get(2).getColumnLong()); - assertEquals(0, ((TableView) results.getTableOrView()).getSourceRowIndex(2)); + assertEquals(2, results.getCollection().indexOf(0)); assertEquals("Adam", results.get(3).getColumnString()); assertEquals(5, results.get(3).getColumnLong()); - assertEquals(3, ((TableView) results.getTableOrView()).getSourceRowIndex(3)); + assertEquals(3, results.getCollection().indexOf(3)); } private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -205,19 +205,19 @@ private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -231,19 +231,19 @@ private void checkSortTwoFieldsStringAscendingIntDescending(RealmResults(&finalize_notification_token); } -JNIEXPORT jlong JNICALL +JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeNotificationTokenClose(JNIEnv *, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) @@ -240,3 +240,31 @@ Java_io_realm_internal_Collection_nativeWhere(JNIEnv *env, jclass, jlong native_ } CATCH_STD() return 0; } + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv *env, jclass, jlong native_ptr, jlong row_native_ptr) +{ + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + auto row = reinterpret_cast(row_native_ptr); + + return static_cast(results->index_of(*row)); + } CATCH_STD() + return npos; +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeIndexOfBySourceRowIndex(JNIEnv *env, jclass, jlong native_ptr, + jlong source_row_index) +{ + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + auto index = static_cast(source_row_index); + + return static_cast(results->index_of(index)); + } CATCH_STD() + return npos; + +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 0787c1ede5..4ba3bdf549 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -182,6 +182,16 @@ public boolean contains(UncheckedRow row) { return nativeContains(nativePtr, row.getNativePtr()); } + public int indexOf(UncheckedRow row) { + long index = nativeIndexOf(nativePtr, row.getNativePtr()); + return (index > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) index; + } + + public int indexOf(long sourceRowIndex) { + long index = nativeIndexOfBySourceRowIndex(nativePtr, sourceRowIndex); + return (index > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) index; + } + public void addListener(Listener listener) { if (!listeners.contains(listener)) { listeners.add(listener); @@ -235,6 +245,8 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native long nativeSort(long nativePtr, long sortDescNativePtr); private native long nativeAddListener(long nativePtr); private static native long nativeNotificationTokenGetFinalizerPtr(); - private static native long nativeNotificationTokenClose(long nativePtr); + private static native void nativeNotificationTokenClose(long nativePtr); private static native long nativeWhere(long nativePtr); + private static native long nativeIndexOf(long nativePtr, long rowNativePtr); + private static native long nativeIndexOfBySourceRowIndex(long nativePtr, long sourceRowIndex); } From a52d1e877c44edc5b99cf8b411404481d70cbb40 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 16:46:29 +0800 Subject: [PATCH 0251/2110] Remove RealmResults.tableOrView --- .../realm-library/src/main/java/io/realm/RealmQuery.java | 6 +----- .../src/main/java/io/realm/RealmResults.java | 9 ++------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 3bd195c761..d9ee232d04 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -18,10 +18,8 @@ import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.List; import java.util.Locale; import java.util.concurrent.Callable; import java.util.concurrent.Future; @@ -34,10 +32,8 @@ import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.SortDescriptor; -import io.realm.internal.Table; import io.realm.internal.TableOrView; import io.realm.internal.TableQuery; -import io.realm.internal.TableView; import io.realm.internal.async.ArgumentsHolder; import io.realm.internal.async.QueryUpdateTask; import io.realm.log.RealmLog; @@ -146,7 +142,7 @@ private RealmQuery(RealmResults queryResults, Class clazz) { this.realm = queryResults.realm; this.clazz = clazz; this.schema = realm.schema.getSchemaForClass(clazz); - this.table = queryResults.getTableOrView(); + this.table = queryResults.getTable(); this.linkView = null; this.query = queryResults.getCollection().where(); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index d76e1d6022..a349021217 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -70,7 +70,6 @@ public final class RealmResults extends AbstractList im final BaseRealm realm; Class classSpec; // Return type String className; // Class name used by DynamicRealmObjects - private TableOrView table = null; private static final long TABLE_VIEW_VERSION_NONE = -1; @@ -89,12 +88,8 @@ public final class RealmResults extends AbstractList im this.collection = collection; } - TableOrView getTableOrView() { - if (table == null) { - return realm.schema.getTable(classSpec); - } else { - return table; - } + TableOrView getTable() { + return collection.getTable(); } public Collection getCollection() { From 4f432f0b63cb8dd56d4583fe07875482c2b810fa Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 16:59:55 +0800 Subject: [PATCH 0252/2110] Lint warnings --- .../src/main/java/io/realm/RealmResults.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index a349021217..9bfe605a2f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -71,9 +71,9 @@ public final class RealmResults extends AbstractList im Class classSpec; // Return type String className; // Class name used by DynamicRealmObjects - private static final long TABLE_VIEW_VERSION_NONE = -1; + //private static final long TABLE_VIEW_VERSION_NONE = -1; - private long currentTableViewVersion = TABLE_VIEW_VERSION_NONE; + //private long currentTableViewVersion = TABLE_VIEW_VERSION_NONE; private final Collection collection; RealmResults(BaseRealm realm, Collection collection, Class clazz) { @@ -92,7 +92,7 @@ TableOrView getTable() { return collection.getTable(); } - public Collection getCollection() { + Collection getCollection() { return collection; } @@ -132,14 +132,10 @@ public RealmQuery where() { @Override public boolean contains(Object object) { boolean contains = false; - if (isLoaded() && object instanceof RealmObjectProxy) { + if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; Row row = proxy.realmGet$proxyState().getRow$realm(); - if (row instanceof InvalidRow) { - contains = false; - } else { - contains = collection.contains((UncheckedRow) row); - } + contains = !(row instanceof InvalidRow) && collection.contains((UncheckedRow) row); } return contains; } @@ -244,6 +240,7 @@ public boolean deleteAllFromRealm() { * @return an iterator on the elements of this list. * @see Iterator */ + @SuppressWarnings("NullableProblems") @Override public Iterator iterator() { return new RealmResultsIterator(); @@ -270,6 +267,7 @@ public ListIterator listIterator() { * @throws IndexOutOfBoundsException if {@code location < 0 || location > size()}. * @see ListIterator */ + @SuppressWarnings("NullableProblems") @Override public ListIterator listIterator(int location) { return new RealmResultsListIterator(location); @@ -452,7 +450,7 @@ public RealmResults distinct(String fieldName) { * @deprecated use {@link #distinct(String)} instead. */ public RealmResults distinctAsync(String fieldName) { - return where().distinctAsync(fieldName); + return distinct(fieldName); } /** @@ -502,7 +500,7 @@ public boolean remove(Object object) { */ @Deprecated @Override - public boolean removeAll(java.util.Collection collection) { + public boolean removeAll(@SuppressWarnings("NullableProblems") java.util.Collection collection) { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); } @@ -526,7 +524,7 @@ public E set(int location, E object) { */ @Deprecated @Override - public boolean retainAll(java.util.Collection collection) { + public boolean retainAll(@SuppressWarnings("NullableProblems") java.util.Collection collection) { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); } @@ -603,7 +601,8 @@ public void add(int index, E element) { */ @Override @Deprecated - public boolean addAll(int location, java.util.Collection collection) { + public boolean addAll(int location, + @SuppressWarnings("NullableProblems") java.util.Collection collection) { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); } @@ -614,17 +613,17 @@ public boolean addAll(int location, java.util.Collection collection */ @Deprecated @Override - public boolean addAll(java.util.Collection collection) { + public boolean addAll(@SuppressWarnings("NullableProblems") java.util.Collection collection) { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); } // Custom RealmResults iterator. It ensures that we only iterate on a Realm that hasn't changed. private class RealmResultsIterator implements Iterator { - long tableViewVersion = 0; + //long tableViewVersion = 0; int pos = -1; RealmResultsIterator() { - tableViewVersion = currentTableViewVersion; + //tableViewVersion = currentTableViewVersion; } /** From daa8ee6bd5a278cc9fb63796bc8845f3eb96f984 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 17:37:33 +0800 Subject: [PATCH 0253/2110] Move TableOrView.NO_MATCH to Table --- .../processor/RealmProxyClassGenerator.java | 21 ++++++++-------- .../io/realm/AllTypesRealmProxy.java | 25 +++++++++---------- .../io/realm/BooleansRealmProxy.java | 1 - .../io/realm/NullTypesRealmProxy.java | 1 - .../resources/io/realm/SimpleRealmProxy.java | 1 - .../main/java/io/realm/RealmObjectSchema.java | 7 +++--- .../src/main/java/io/realm/RealmQuery.java | 4 +-- .../src/main/java/io/realm/RealmResults.java | 4 +-- .../main/java/io/realm/internal/LinkView.java | 2 +- .../main/java/io/realm/internal/Table.java | 7 +++--- .../java/io/realm/internal/TableOrView.java | 14 +++++------ .../java/io/realm/internal/TableView.java | 2 +- 12 files changed, 41 insertions(+), 48 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index f87f679bb8..2939fcfc1a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -73,7 +73,6 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("io.realm.internal.RealmObjectProxy"); imports.add("io.realm.internal.Row"); imports.add("io.realm.internal.Table"); - imports.add("io.realm.internal.TableOrView"); imports.add("io.realm.internal.SharedRealm"); imports.add("io.realm.internal.LinkView"); imports.add("io.realm.internal.android.JsonUtils"); @@ -715,7 +714,7 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { // check before migrating a nullable field containing null value to not-nullable PrimaryKey field for Realm version 0.89+ if (metadata.isPrimaryKey(field)) { writer - .beginControlFlow("if (table.isColumnNullable(%s) && table.findFirstNull(%s) != TableOrView.NO_MATCH)", + .beginControlFlow("if (table.isColumnNullable(%s) && table.findFirstNull(%s) != Table.NO_MATCH)", fieldIndexVariableReference(field), fieldIndexVariableReference(field)) .emitStatement("throw new IllegalStateException(\"Cannot migrate an object with null value in field '%s'." + " Either maintain the same type for primary key field '%s', or remove the object with null value before migration.\")", @@ -863,7 +862,7 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { if (Utils.isString(primaryKeyElement)) { writer .emitStatement("String value = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = TableOrView.NO_MATCH") + .emitStatement("long rowIndex = Table.NO_MATCH") .beginControlFlow("if (value == null)") .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") .nextControlFlow("else") @@ -872,7 +871,7 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { } else { writer .emitStatement("Number value = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = TableOrView.NO_MATCH") + .emitStatement("long rowIndex = Table.NO_MATCH") .beginControlFlow("if (value == null)") .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") .nextControlFlow("else") @@ -886,7 +885,7 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { } writer - .beginControlFlow("if (rowIndex != TableOrView.NO_MATCH)") + .beginControlFlow("if (rowIndex != Table.NO_MATCH)") .beginControlFlow("try") .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex)," + " realm.schema.getColumnInfo(%s.class)," + @@ -1334,7 +1333,7 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP if (Utils.isString(primaryKeyElement)) { writer .emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = TableOrView.NO_MATCH") + .emitStatement("long rowIndex = Table.NO_MATCH") .beginControlFlow("if (primaryKeyValue == null)") .emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") .nextControlFlow("else") @@ -1343,7 +1342,7 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP } else { writer .emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = TableOrView.NO_MATCH") + .emitStatement("long rowIndex = Table.NO_MATCH") .beginControlFlow("if (primaryKeyValue == null)") .emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") .nextControlFlow("else") @@ -1351,7 +1350,7 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP .endControlFlow(); } } else { - writer.emitStatement("long rowIndex = TableOrView.NO_MATCH"); + writer.emitStatement("long rowIndex = Table.NO_MATCH"); writer.emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter); writer.beginControlFlow("if (primaryKeyValue != null)"); @@ -1363,7 +1362,7 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP writer.endControlFlow(); } - writer.beginControlFlow("if (rowIndex == TableOrView.NO_MATCH)"); + writer.beginControlFlow("if (rowIndex == Table.NO_MATCH)"); if (Utils.isString(metadata.getPrimaryKey())) { writer.emitStatement("rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false)"); } else { @@ -1732,7 +1731,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep .beginControlFlow("if (update)") .emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName) .emitStatement("long pkColumnIndex = table.getPrimaryKey()") - .emitStatement("long rowIndex = TableOrView.NO_MATCH"); + .emitStatement("long rowIndex = Table.NO_MATCH"); if (metadata.isNullable(metadata.getPrimaryKey())) { writer .beginControlFlow("if (json.isNull(\"%s\"))", metadata.getPrimaryKey().getSimpleName()) @@ -1749,7 +1748,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep .endControlFlow(); } writer - .beginControlFlow("if (rowIndex != TableOrView.NO_MATCH)") + .beginControlFlow("if (rowIndex != Table.NO_MATCH)") .emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") .beginControlFlow("try") .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex)," + diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index d67ccc944c..09d5c21d07 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -14,7 +14,6 @@ import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; -import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; import io.realm.log.RealmLog; import java.io.IOException; @@ -650,13 +649,13 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON if (update) { Table table = realm.getTable(some.test.AllTypes.class); long pkColumnIndex = table.getPrimaryKey(); - long rowIndex = TableOrView.NO_MATCH; + long rowIndex = Table.NO_MATCH; if (json.isNull("columnString")) { rowIndex = table.findFirstNull(pkColumnIndex); } else { rowIndex = table.findFirstString(pkColumnIndex, json.getString("columnString")); } - if (rowIndex != TableOrView.NO_MATCH) { + if (rowIndex != Table.NO_MATCH) { final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); try { objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.schema.getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); @@ -868,13 +867,13 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob Table table = realm.getTable(some.test.AllTypes.class); long pkColumnIndex = table.getPrimaryKey(); String value = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = TableOrView.NO_MATCH; + long rowIndex = Table.NO_MATCH; if (value == null) { rowIndex = table.findFirstNull(pkColumnIndex); } else { rowIndex = table.findFirstString(pkColumnIndex, value); } - if (rowIndex != TableOrView.NO_MATCH) { + if (rowIndex != Table.NO_MATCH) { try { objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.schema.getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); realmObject = new io.realm.AllTypesRealmProxy(); @@ -949,13 +948,13 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M continue; } String primaryKeyValue = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = TableOrView.NO_MATCH; + long rowIndex = Table.NO_MATCH; if (primaryKeyValue == null) { rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); } else { rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } - if (rowIndex == TableOrView.NO_MATCH) { + if (rowIndex == Table.NO_MATCH) { rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); @@ -1071,13 +1070,13 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob continue; } String primaryKeyValue = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = TableOrView.NO_MATCH; + long rowIndex = Table.NO_MATCH; if (primaryKeyValue == null) { rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); } else { rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } - if (rowIndex == TableOrView.NO_MATCH) { + if (rowIndex == Table.NO_MATCH) { rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); } cache.put(object, rowIndex); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 7200c4e70e..7323ad27f6 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -14,7 +14,6 @@ import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; -import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; import io.realm.log.RealmLog; import java.io.IOException; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 608868bb72..ee1b354fc1 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -14,7 +14,6 @@ import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; -import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; import io.realm.log.RealmLog; import java.io.IOException; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 3dc59b6feb..a1e79528a5 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -14,7 +14,6 @@ import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; -import io.realm.internal.TableOrView; import io.realm.internal.android.JsonUtils; import io.realm.log.RealmLog; import java.io.IOException; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index febc7e89db..d74833fbbf 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -26,7 +26,6 @@ import io.realm.annotations.Required; import io.realm.internal.Table; -import io.realm.internal.TableOrView; /** * Class for interacting with the schema for a given RealmObject class. This makes it possible to @@ -332,7 +331,7 @@ public RealmObjectSchema renameField(String currentFieldName, String newFieldNam * @return {@code true} if the field exists, {@code false} otherwise. */ public boolean hasField(String fieldName) { - return table.getColumnIndex(fieldName) != TableOrView.NO_MATCH; + return table.getColumnIndex(fieldName) != Table.NO_MATCH; } /** @@ -632,13 +631,13 @@ private void checkLegalName(String fieldName) { } private void checkFieldNameIsAvailable(String fieldName) { - if (table.getColumnIndex(fieldName) != TableOrView.NO_MATCH) { + if (table.getColumnIndex(fieldName) != Table.NO_MATCH) { throw new IllegalArgumentException("Field already exists in '" + getClassName() + "': " + fieldName); } } private void checkFieldExists(String fieldName) { - if (table.getColumnIndex(fieldName) == TableOrView.NO_MATCH) { + if (table.getColumnIndex(fieldName) == Table.NO_MATCH) { throw new IllegalArgumentException("Field name doesn't exist on object '" + getClassName() + "': " + fieldName); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index d9ee232d04..281fa7dcb2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -32,7 +32,7 @@ import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.SortDescriptor; -import io.realm.internal.TableOrView; +import io.realm.internal.Table; import io.realm.internal.TableQuery; import io.realm.internal.async.ArgumentsHolder; import io.realm.internal.async.QueryUpdateTask; @@ -61,7 +61,7 @@ public final class RealmQuery { private BaseRealm realm; private Class clazz; private String className; - private TableOrView table; + private Table table; private RealmObjectSchema schema; private LinkView linkView; private TableQuery query; diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 9bfe605a2f..e65f5eb253 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -31,7 +31,7 @@ import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SortDescriptor; -import io.realm.internal.TableOrView; +import io.realm.internal.Table; import io.realm.internal.Collection; import io.realm.internal.UncheckedRow; import rx.Observable; @@ -88,7 +88,7 @@ public final class RealmResults extends AbstractList im this.collection = collection; } - TableOrView getTable() { + Table getTable() { return collection.getTable(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index f6718b4083..2218a9e015 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -113,7 +113,7 @@ public void clear() { public boolean contains(long tableRowIndex) { long index = nativeFind(nativePtr, tableRowIndex); - return (index != TableOrView.NO_MATCH); + return (index != Table.NO_MATCH); } public long size() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index ca0a0b186e..1a0f628120 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -35,6 +35,7 @@ public class Table implements TableOrView, TableSchema, NativeObject { public static final long INFINITE = -1; public static final boolean NULLABLE = true; public static final boolean NOT_NULLABLE = false; + public static final int NO_MATCH = -1; private static final String PRIMARY_KEY_TABLE_NAME = "pk"; private static final String PRIMARY_KEY_CLASS_COLUMN_NAME = "pk_table"; @@ -621,7 +622,7 @@ public boolean hasPrimaryKey() { void checkStringValueIsLegal(long columnIndex, long rowToUpdate, String value) { if (isPrimaryKey(columnIndex)) { long rowIndex = findFirstString(columnIndex, value); - if (rowIndex != rowToUpdate && rowIndex != TableOrView.NO_MATCH) { + if (rowIndex != rowToUpdate && rowIndex != NO_MATCH) { throwDuplicatePrimaryKeyException(value); } } @@ -630,7 +631,7 @@ void checkStringValueIsLegal(long columnIndex, long rowToUpdate, String value) { void checkIntValueIsLegal(long columnIndex, long rowToUpdate, long value) { if (isPrimaryKeyColumn(columnIndex)) { long rowIndex = findFirstLong(columnIndex, value); - if (rowIndex != rowToUpdate && rowIndex != TableOrView.NO_MATCH) { + if (rowIndex != rowToUpdate && rowIndex != NO_MATCH) { throwDuplicatePrimaryKeyException(value); } } @@ -644,7 +645,7 @@ void checkDuplicatedNullForPrimaryKeyValue(long columnIndex, long rowToUpdate) { case STRING: case INTEGER: long rowIndex = findFirstNull(columnIndex); - if (rowIndex != rowToUpdate && rowIndex != TableOrView.NO_MATCH) { + if (rowIndex != rowToUpdate && rowIndex != NO_MATCH) { throwDuplicatePrimaryKeyException("null"); } break; diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java b/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java index 58404ee5bf..a45ad1331a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java @@ -26,8 +26,6 @@ */ public interface TableOrView { - int NO_MATCH = -1; - void clear(); /** @@ -257,7 +255,7 @@ public interface TableOrView { * * @param columnIndex the column to search in. * @param value the value to search for. - * @return the row index for the first match found or {@link #NO_MATCH}. + * @return the row index for the first match found or {@link Table#NO_MATCH}. */ long findFirstLong(long columnIndex, long value); @@ -266,7 +264,7 @@ public interface TableOrView { * * @param columnIndex the column to search in. * @param value the alue to search for. - * @return the row index for the first match found or {@link #NO_MATCH}. + * @return the row index for the first match found or {@link Table#NO_MATCH}. */ long findFirstBoolean(long columnIndex, boolean value); @@ -275,7 +273,7 @@ public interface TableOrView { * * @param columnIndex the column to search in. * @param value the value to search for. - * @return the row index for the first match found or {@link #NO_MATCH}. + * @return the row index for the first match found or {@link Table#NO_MATCH}. */ long findFirstFloat(long columnIndex, float value); @@ -284,7 +282,7 @@ public interface TableOrView { * * @param columnIndex the column to search in. * @param value the value to search for. - * @return the row index for the first match found or {@link #NO_MATCH}. + * @return the row index for the first match found or {@link Table#NO_MATCH}. */ long findFirstDouble(long columnIndex, double value); @@ -293,7 +291,7 @@ public interface TableOrView { * * @param columnIndex the column to search in. * @param value the value to search for. - * @return the row index for the first match found or {@link #NO_MATCH}. + * @return the row index for the first match found or {@link Table#NO_MATCH}. */ long findFirstDate(long columnIndex, Date value); @@ -302,7 +300,7 @@ public interface TableOrView { * * @param columnIndex the column to search in. * @param value the value to search for. - * @return the row index for the first match found or {@link #NO_MATCH}. + * @return the row index for the first match found or {@link Table#NO_MATCH}. */ long findFirstString(long columnIndex, String value); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableView.java b/realm/realm-library/src/main/java/io/realm/internal/TableView.java index 3f5467522d..1a5cd59d48 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableView.java @@ -450,7 +450,7 @@ public long findFirstDouble(long columnIndex, double value) { @Override public long findFirstDate(long columnIndex, Date date) { // FIXME: waiting for implementation - return NO_MATCH; + return Table.NO_MATCH; // return nativeFindFirstDate(nativePtr, columnIndex, date.getTime()); } From 7ee536f544399341891efa140f9eaa5584920c23 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 17:57:05 +0800 Subject: [PATCH 0254/2110] Collection.size() return long --- .../androidTest/java/io/realm/RealmResultsTests.java | 11 ++++++----- .../src/androidTest/java/io/realm/TestHelper.java | 10 +++++----- .../src/main/java/io/realm/RealmResults.java | 3 ++- .../src/main/java/io/realm/internal/Collection.java | 5 ++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index cbce2dfd79..53413f3ef2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -44,6 +44,7 @@ import io.realm.entities.Owner; import io.realm.entities.RandomPrimaryKey; import io.realm.entities.StringOnly; +import io.realm.internal.Collection; import io.realm.internal.Table; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; @@ -103,14 +104,14 @@ public void findFirst() { @Test public void size_returns_Integer_MAX_VALUE_for_huge_results() { - final Table table = Mockito.mock(Table.class); - final RealmResults targetResult = TestHelper.newRealmResults(realm, table, AllTypes.class); + final Collection collection = Mockito.mock(Collection.class); + final RealmResults targetResult = TestHelper.newRealmResults(realm, collection, AllTypes.class); - Mockito.when(table.size()).thenReturn(((long) Integer.MAX_VALUE) - 1); + Mockito.when(collection.size()).thenReturn(((long) Integer.MAX_VALUE) - 1); assertEquals(Integer.MAX_VALUE - 1, targetResult.size()); - Mockito.when(table.size()).thenReturn(((long) Integer.MAX_VALUE)); + Mockito.when(collection.size()).thenReturn(((long) Integer.MAX_VALUE)); assertEquals(Integer.MAX_VALUE, targetResult.size()); - Mockito.when(table.size()).thenReturn(((long) Integer.MAX_VALUE) + 1); + Mockito.when(collection.size()).thenReturn(((long) Integer.MAX_VALUE) + 1); assertEquals(Integer.MAX_VALUE, targetResult.size()); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 321c5d927c..f6a2299618 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -56,8 +56,8 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; +import io.realm.internal.Collection; import io.realm.internal.Table; -import io.realm.internal.TableOrView; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.LogLevel; import io.realm.log.RealmLogger; @@ -778,7 +778,7 @@ public static void populateForDistinctFieldsOrder(Realm realm, long numberOfBloc } public static void awaitOrFail(CountDownLatch latch) { - awaitOrFail(latch, 7); + awaitOrFail(latch, 700000); } public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { @@ -848,14 +848,14 @@ public static void quitLooperOrFail() { * @return a created {@link RealmResults} instance. */ public static RealmResults newRealmResults( - BaseRealm realm, TableOrView table, Class tableClass) { + BaseRealm realm, Collection collection, Class tableClass) { //noinspection TryWithIdenticalCatches try { final Constructor c = RealmResults.class.getDeclaredConstructor( - BaseRealm.class, TableOrView.class, Class.class); + BaseRealm.class, Collection.class, Class.class); c.setAccessible(true); //noinspection unchecked - return c.newInstance(realm, table, tableClass); + return c.newInstance(realm, collection, tableClass); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InstantiationException e) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index e65f5eb253..980b7cf027 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -364,7 +364,8 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName */ @Override public int size() { - return collection.size(); + long size = collection.size(); + return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 4ba3bdf549..2f10c4568b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -165,9 +165,8 @@ public Object aggregate(Aggregate aggregateMethod, long columnIndex) { return nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); } - public int size() { - long size = nativeSize(nativePtr); - return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; + public long size() { + return nativeSize(nativePtr); } public void clear() { From 11602d8e8d4e9bf23399f5c91261a1ec3945037a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 29 Nov 2016 20:07:08 +0800 Subject: [PATCH 0255/2110] Add CollectionTests --- .../io/realm/internal/CollectionTests.java | 164 ++++++++++++++++++ .../java/io/realm/internal/JNIQueryTest.java | 50 ------ 2 files changed, 164 insertions(+), 50 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java new file mode 100644 index 0000000000..b78434de00 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -0,0 +1,164 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.realm.RealmConfiguration; +import io.realm.RealmFieldType; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + + +@RunWith(AndroidJUnit4.class) +public class CollectionTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + private SharedRealm sharedRealm; + private Table table; + + @Before + public void setUp() { + RealmConfiguration config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); + sharedRealm.beginTransaction(); + table = sharedRealm.getTable("test_table"); + populateData(table); + } + + @After + public void tearDown() { + sharedRealm.cancelTransaction(); + sharedRealm.close(); + } + + private void populateData(Table table) { + // Specify the column types and names + table.addColumn(RealmFieldType.STRING, "firstName"); + table.addColumn(RealmFieldType.STRING, "lastName"); + table.addColumn(RealmFieldType.INTEGER, "age"); + + // Add data to the table + long row = table.addEmptyRow(); + table.setString(0, row, "John", false); + table.setString(1, row, "Lee", false); + table.setLong(2, row, 4, false); + + row = table.addEmptyRow(); + table.setString(0, row, "John", false); + table.setString(1, row, "Anderson", false); + table.setLong(2, row, 3, false); + + row = table.addEmptyRow(); + table.setString(0, row, "Erik", false); + table.setString(1, row, "Lee", false); + table.setLong(2, row, 1, false); + + row = table.addEmptyRow(); + table.setString(0, row, "Henry", false); + table.setString(1, row, "Anderson", false); + table.setLong(2, row, 1, false); + } + + @Test + public void size() { + Collection collection = new Collection(sharedRealm, table.where()); + assertEquals(3, collection.size()); + } + + @Test + public void where() { + Collection collection = new Collection(sharedRealm, table.where()); + Collection collection2 =new Collection(sharedRealm, collection.where().equalTo(new long[]{0}, "John")); + Collection collection3 =new Collection(sharedRealm, collection2.where().equalTo(new long[]{1}, "Anderson")); + + // A new native Results should be created. + assertTrue(collection.getNativePtr() != collection2.getNativePtr()); + assertTrue(collection2.getNativePtr() != collection3.getNativePtr()); + + assertEquals(4, collection.size()); + assertEquals(2, collection2.size()); + assertEquals(1, collection3.size()); + } + + @Test + public void sort() { + Collection collection = new Collection(sharedRealm, table.where()); + SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); + try { + Collection collection2 =collection.sort(sortDescriptor); + + // A new native Results should be created. + assertTrue(collection.getNativePtr() != collection2.getNativePtr()); + assertEquals(4, collection.size()); + assertEquals(4, collection2.size()); + + assertEquals(collection2.getUncheckedRow(0).getLong(2), 1); + assertEquals(collection2.getUncheckedRow(3).getLong(2), 4); + } finally { + sortDescriptor.close(); + } + } + + @Test + public void clear() { + assertEquals(table.size(), 4); + Collection collection = new Collection(sharedRealm, table.where()); + collection.clear(); + assertEquals(table.size(), 0); + } + + @Test + public void contains() { + Collection collection = new Collection(sharedRealm, table.where()); + UncheckedRow row = table.getUncheckedRow(0); + assertTrue(collection.contains(row)); + } + + @Test + public void indexOf() { + SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); + try { + Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); + UncheckedRow row = table.getUncheckedRow(0); + assertEquals(collection.indexOf(row), 3); + } finally { + sortDescriptor.close(); + } + } + + @Test + public void indexOf_long() { + SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); + try { + Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); + assertEquals(collection.indexOf(0), 3); + } finally { + sortDescriptor.close(); + } + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index 71f504509d..b94f545227 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -614,56 +614,6 @@ public void testColumnIndexOutOfBounds() { try { query.equalTo(new long[]{7}, true); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} } - - public void testQueryOnView() { - Table table = new Table(); - - // Specify the column types and names - table.addColumn(RealmFieldType.STRING, "firstName"); - table.addColumn(RealmFieldType.STRING, "lastName"); - table.addColumn(RealmFieldType.INTEGER, "salary"); - - // Add data to the table - table.add("John", "Lee", 10000); - table.add("Jane", "Lee", 15000); - table.add("John", "Anderson", 20000); - table.add("Erik", "Lee", 30000); - table.add("Henry", "Anderson", 10000); - - TableView view = table.where().findAll(); - - TableView view2 = view.where().equalTo(new long[]{0}, "John").findAll(); - - assertEquals(2, view2.size()); - - TableView view3 = view2.where().equalTo(new long[]{1}, "Anderson").findAll(); - - assertEquals(1, view3.size()); - } - - - public void testQueryOnViewWithAlreadyQueriedTable() { - Table table = new Table(); - - // Specify the column types and names - table.addColumn(RealmFieldType.STRING, "firstName"); - table.addColumn(RealmFieldType.STRING, "lastName"); - table.addColumn(RealmFieldType.INTEGER, "salary"); - - // Add data to the table - table.add("John", "Lee", 10000); - table.add("Jane", "Lee", 15000); - table.add("John", "Anderson", 20000); - table.add("Erik", "Lee", 30000); - table.add("Henry", "Anderson", 10000); - - TableView view = table.where().equalTo(new long[]{0}, "John").findAll(); - - TableView view2 = view.where().equalTo(new long[]{1}, "Anderson").findAll(); - - assertEquals(1, view2.size()); - } - public void testMaximumDate() { Table table = new Table(); From da9f7072751ffea83a0beb09aad3d8bea92d46d5 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 30 Nov 2016 13:18:26 +0800 Subject: [PATCH 0256/2110] Remove TableView test cases --- .../io/realm/internal/JNITableViewTest.java | 147 ------ .../java/io/realm/internal/JNIViewTest.java | 420 ------------------ 2 files changed, 567 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java deleted file mode 100644 index 47ed416aaa..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -import io.realm.Realm; -import io.realm.RealmFieldType; -import io.realm.rule.TestRealmConfigurationFactory; - -import static junit.framework.Assert.assertEquals; - -@RunWith(AndroidJUnit4.class) -public class JNITableViewTest { - static { - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - } - - @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - - private static final String TABLE_NAME = Table.TABLE_PREFIX + "JNITableViewTest"; - private static final int ROW_COUNT = 10; - - private static final List FIELDS = Arrays.asList( - RealmFieldType.INTEGER, - RealmFieldType.BOOLEAN, - RealmFieldType.STRING, - RealmFieldType.BINARY, - RealmFieldType.DATE, - RealmFieldType.FLOAT, - RealmFieldType.DOUBLE); - private static final long INTEGER_COLUMN_INDEX = 0; - private static final long STRING_COLUMN_INDEX = 2; - - private SharedRealm sharedRealm; - - private Table table; - - @Before - public void setUp() { - sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); - sharedRealm.beginTransaction(); - try { - table = sharedRealm.getTable(TABLE_NAME); - - for (RealmFieldType field : FIELDS) { - final long index = table.addColumn(field, field.name().toLowerCase(Locale.ENGLISH) + "Column"); - table.convertColumnToNullable(index); - } - - for (int i = 0; i < ROW_COUNT; i++) { - table.add(i, true, "abcd", new byte[]{123, -123}, new Date(12345), 1.234f, 3.446d); - } - } finally { - sharedRealm.commitTransaction(); - } - } - - @Test - public void setNull() { - TableQuery query = table.where(); - for (int i = 0; i < ROW_COUNT; i++) { - if (isOdd(i)) { - query = query.or().equalTo(new long[]{INTEGER_COLUMN_INDEX}, (long) i); - } - } - final TableView oddRows = query.findAll(); - - sharedRealm.beginTransaction(); - for (int i = 0; i < oddRows.size(); i++) { - oddRows.setNull(STRING_COLUMN_INDEX, i, false); - } - sharedRealm.commitTransaction(); - - // check if TableView#setNull() worked as expected - for (int i = 0; i < table.size(); i++) { - assertEquals("index: " + i, isOdd(i), table.isNull(STRING_COLUMN_INDEX, i)); - } - } - - @Test - public void isNull() { - - sharedRealm.beginTransaction(); - for (int i = 0; i < table.size(); i++) { - if (isOdd(i)) { - table.setNull(STRING_COLUMN_INDEX, i, false); - } - } - sharedRealm.commitTransaction(); - - TableQuery query = table.where(); - for (int i = 0; i < ROW_COUNT; i++) { - if (isOdd(i)) { - query = query.or().equalTo(new long[]{INTEGER_COLUMN_INDEX}, (long) i); - } - } - final TableView oddRows = query.findAll(); - for (int i = 0; i < oddRows.size(); i++) { - assertEquals("index: " + i, true, oddRows.isNull(STRING_COLUMN_INDEX, i)); - } - - query = table.where(); - for (int i = 0; i < ROW_COUNT; i++) { - if (isEven(i)) { - query = query.or().equalTo(new long[]{INTEGER_COLUMN_INDEX}, (long) i); - } - } - final TableView evenRows = query.findAll(); - for (int i = 0; i < evenRows.size(); i++) { - assertEquals("index: " + i, false, evenRows.isNull(STRING_COLUMN_INDEX, i)); - } - } - - private static boolean isEven(int i) { - return i % 2 == 0; - } - private static boolean isOdd(int i) { - return i % 2 == 1; - } -} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java deleted file mode 100644 index 661e6c1a6d..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import android.test.MoreAsserts; - -import junit.framework.TestCase; - -import java.util.Date; - -import io.realm.RealmFieldType; -import io.realm.Sort; -import io.realm.TestHelper; - -@SuppressWarnings("deprecation") -public class JNIViewTest extends TestCase { - Table t; - Date date1 = new Date(2010-1900, 1, 5); - Date date2 = new Date(1999-1900, 12, 1); - Date date3 = new Date(1990-1900, 12, 24); - Date date4 = new Date(2010-1900, 1, 4); - - @Override - public void setUp() { - //Specify table - t = new Table(); - t.addColumn(RealmFieldType.STRING, "Name"); - t.addColumn(RealmFieldType.BOOLEAN, "Study"); - t.addColumn(RealmFieldType.INTEGER, "Age"); - t.addColumn(RealmFieldType.DATE, "Birthday"); - - //Add data - t.add("cc", true, 24, date1); - t.add("dd", false, 35, date2); - t.add("bb", true, 22, date3); - t.add("aa", false, 22, date4); - - assertEquals(date1, t.getDate(3, 0)); - assertEquals(date2, t.getDate(3, 1)); - assertEquals(date3, t.getDate(3, 2)); - assertEquals(date4, t.getDate(3, 3)); - } - - public void testUnimplementedMethodsShouldFail() { - //Get a view containing all rows in table since you can only sort views currently. - TableView view = t.where().findAll(); - - try { view.upperBoundLong(0, 0); fail("Not implemented yet"); } catch (RuntimeException e ) { } - try { view.lowerBoundLong(0, 0); fail("Not implemented yet"); } catch (RuntimeException e ) { } - // try { view.lookup("Some String"); fail("Not implemented yet"); } catch (RuntimeException e ) { } - try { view.count(0, "Some String"); fail("Not implemented yet"); } catch (RuntimeException e ) { } - } - - - public void testShouldSortViewDate() { - //Get a view containing all rows in table since you can only sort views currently. - TableView view = t.where().findAll(); - - //Sort without specifying the order, should default to ascending. - view.sort(3); - assertEquals(date3, view.getDate(3, 0)); - assertEquals(date2, view.getDate(3, 1)); - assertEquals(date4, view.getDate(3, 2)); - assertEquals(date1, view.getDate(3, 3)); - assertEquals("cc", view.getString(0, 3)); - } - - - public void testShouldSortViewIntegers() { - //Get a view containing all rows in table since you can only sort views currently. - TableView view = t.where().findAll(); - - //Sort without specifying the order, should default to ascending. - view.sort(2); - assertEquals(22, view.getLong(2, 0)); - assertEquals(22, view.getLong(2, 1)); - assertEquals(24, view.getLong(2, 2)); - assertEquals(35, view.getLong(2, 3)); - assertEquals("dd", view.getString(0, 3)); - - //Sort descending - creating a new view - view.sort(2, Sort.DESCENDING); - assertEquals(35, view.getLong(2, 0)); - assertEquals(24, view.getLong(2, 1)); - assertEquals(22, view.getLong(2, 2)); - assertEquals(22, view.getLong(2, 3)); - assertEquals("dd", view.getString(0, 0)); - - //Sort ascending. - TableView view2 = t.where().findAll(); - view2.sort(2, Sort.ASCENDING); - assertEquals(22, view2.getLong(2, 0)); - assertEquals(22, view2.getLong(2, 1)); - assertEquals(24, view2.getLong(2, 2)); - assertEquals(35, view2.getLong(2, 3)); - assertEquals("dd", view2.getString(0, 3)); - - // Check that old view is still the same - assertEquals(35, view.getLong(2, 0)); - assertEquals(24, view.getLong(2, 1)); - assertEquals(22, view.getLong(2, 2)); - assertEquals(22, view.getLong(2, 3)); - assertEquals("dd", view.getString(0, 0)); - } - - - public void testSetBinary() { - - Table table = new Table(); - table.addColumn(RealmFieldType.BINARY, "binary"); - - byte[] arr1 = new byte[] {1,2,3}; - table.add(new Object[]{arr1}); - MoreAsserts.assertEquals(arr1, table.getBinaryByteArray(0, 0)); - - TableView view = table.where().findAll(); - - byte[] arr2 = new byte[] {1,2,3, 4, 5}; - - view.setBinaryByteArray(0, 0, arr2, false); - - MoreAsserts.assertEquals(arr2, view.getBinaryByteArray(0, 0)); - } - - public void testSortOnNonexistingColumn() { - TableView view = t.where().findAll(); - - try { view.sort(-1); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.sort(-100); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.sort(100); fail("Column is 100, column does not exist"); } catch (ArrayIndexOutOfBoundsException e) { } - } - - - public void testFindFirstNonExisting() { - Table tt = TestHelper.getTableWithAllColumnTypes(); - tt.add(new byte[]{1,2,3}, true, new Date(1384423149761l), 4.5d, 5.7f, 100, "string"); - TableView v = tt.where().findAll(); - - assertEquals(-1, v.findFirstBoolean(1, false)); - //FIXME: enable when find_first_timestamp() is implemented: assertEquals(-1, v.findFirstDate(2, new Date(138442314986l))); - assertEquals(-1, v.findFirstDouble(3, 1.0d)); - assertEquals(-1, v.findFirstFloat(4, 1.0f)); - assertEquals(-1, v.findFirstLong(5, 50)); - } - - - public void testGetValuesFromNonExistingColumn() { - Table table = TestHelper.getTableWithAllColumnTypes(); - TableView view = table.where().findAll(); - - try { view.getBinaryByteArray(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getBinaryByteArray(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getBinaryByteArray(100, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException e) { } - - try { view.getBoolean(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getBoolean(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getBoolean(100, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException e) { } - - try { view.getDate(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getDate(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getDate(100, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException e) { } - - try { view.getDouble(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getDouble(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getDouble(100, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException e) { } - - try { view.getFloat(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getFloat(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getFloat(100, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException e) { } - - try { view.getLong(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getLong(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getLong(100, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException e) { } - - try { view.getString(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getString(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException e) { } - try { view.getString(100, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException e) { } - } - - - public void testGetSourceRow() { - Table t = new Table(); - t.addColumn(RealmFieldType.STRING, ""); - t.addColumn(RealmFieldType.INTEGER, ""); - t.addColumn(RealmFieldType.BOOLEAN, ""); - - t.add("1", 1, true); - t.add("2", 2, true); - t.add("3", 3, false); - t.add("4", 5, false); - - TableView v = t.where().equalTo(new long[]{2}, false).findAll(); - - assertEquals(2, v.getSourceRowIndex(0)); - assertEquals(3, v.getSourceRowIndex(1)); - - // Out of bound - try { assertEquals(0, v.getSourceRowIndex(2)); fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { } - try { assertEquals(0, v.getSourceRowIndex(100)); fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { } - try { assertEquals(0, v.getSourceRowIndex(-1)); fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { } - try { assertEquals(0, v.getSourceRowIndex(-100)); fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { } - } - - - public void testGetSourceRowNoRows() { - Table t = new Table(); - t.addColumn(RealmFieldType.STRING, ""); - t.addColumn(RealmFieldType.INTEGER, ""); - t.addColumn(RealmFieldType.BOOLEAN, ""); - // No data is added - TableView v = t.where().findAll(); - - // Out of bound - try { assertEquals(0, v.getSourceRowIndex(0)); fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { } - try { assertEquals(0, v.getSourceRowIndex(1)); fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { } - } - - - public void testGetSourceRowEmptyTable() { - Table t = new Table(); - // No columns - TableView v = t.where().findAll(); - - // Out of bound - try { assertEquals(0, v.getSourceRowIndex(0)); fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { } - try { assertEquals(0, v.getSourceRowIndex(1)); fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { } - } - - - public void testShouldSortViewBool() { - //Get a view containing all rows in table since you can only sort views currently. - TableView view = t.where().findAll(); - - //Sort without specifying the order, should default to ascending. - view.sort(1); - assertEquals(false, view.getBoolean(1, 0)); - assertEquals(false, view.getBoolean(1, 1)); - assertEquals(true, view.getBoolean(1, 2)); - assertEquals(true, view.getBoolean(1, 3)); - assertEquals("bb", view.getString(0, 3)); - } - - public void testShouldSearchByColumnValue() { - Table table = new Table(); - table.addColumn(RealmFieldType.STRING, "name"); - - table.add("Foo"); - table.add("Bar"); - - TableQuery query = table.where(); - TableView view = query.findAll(0, table.size(), Integer.MAX_VALUE); - assertEquals(2, view.size()); - - view.findAllString(0, "Foo"); - } - - public void testShouldQueryInView() { - Table table = new Table(); - table.addColumn(RealmFieldType.STRING, "name"); - - table.add("A1"); - table.add("B"); - table.add("A2"); - table.add("B"); - table.add("A3"); - table.add("B"); - table.add("A3"); - - TableQuery query = table.where(); - TableView view = query.beginsWith(new long[]{0}, "A").findAll(0, table.size(), Table.INFINITE); - assertEquals(4, view.size()); - - TableQuery query2 = table.where(); - TableView view2 = query2.tableview(view).contains(new long[]{0}, "3").findAll(); - assertEquals(2, view2.size()); - } - - public void testGetNonExistingColumn() { - Table t = new Table(); - t.addColumn(RealmFieldType.INTEGER, "int"); - TableView view = t.where().findAll(); - assertEquals(-1, view.getColumnIndex("non-existing column")); - } - - public void testGetNullColumn() { - Table t = new Table(); - t.addColumn(RealmFieldType.INTEGER, ""); - TableView view = t.where().findAll(); - try { view.getColumnIndex(null); fail("Getting null column"); } catch(IllegalArgumentException e) { } - } - - - public void testViewToString() { - Table t = new Table(); - t.addColumn(RealmFieldType.STRING, "stringCol"); - t.addColumn(RealmFieldType.INTEGER, "intCol"); - t.addColumn(RealmFieldType.BOOLEAN, "boolCol"); - - t.add("s1", 1, true); - t.add("s2", 2, false); - - TableView view = t.where().findAll(); - - String expected = "The TableView contains 3 columns: stringCol, intCol, boolCol. And 2 rows."; - - assertEquals(expected, view.toString()); - } - - void accessingViewOk(TableView view) - { - view.size(); - view.isEmpty(); - view.getLong(0, 0); - view.getColumnCount(); - view.getColumnName(0); - view.getColumnIndex(""); - view.getColumnType(0); - view.averageLong(0); - view.maximumLong(0); - view.minimumLong(0); - view.sumLong(0); - view.findAllLong(0, 2); - view.findFirstLong(0, 2); - view.where(); - view.toJson(); - view.toString(); - } - - void accessingViewMustThrow(TableView view) - { - try { view.size(); assert(false); } catch (IllegalStateException e) {} - try { view.isEmpty(); assert(false); } catch (IllegalStateException e) {} - try { view.getLong(0,0); assert(false); } catch (IllegalStateException e) {} - try { view.getColumnCount(); assert(false); } catch (IllegalStateException e) {} - try { view.getColumnName(0); assert(false); } catch (IllegalStateException e) {} - try { view.getColumnIndex(""); assert(false); } catch (IllegalStateException e) {} - try { view.getColumnType(0); assert(false); } catch (IllegalStateException e) {} - try { view.averageLong(0); assert(false); } catch (IllegalStateException e) {} - try { view.maximumLong(0); assert(false); } catch (IllegalStateException e) {} - try { view.minimumLong(0); assert(false); } catch (IllegalStateException e) {} - try { view.sumLong(0); assert(false); } catch (IllegalStateException e) {} - try { view.findAllLong(0, 2); assert(false); } catch (IllegalStateException e) {} - try { view.findFirstLong(0, 2); assert(false); } catch (IllegalStateException e) {} - try { view.where(); assert(false); } catch (IllegalStateException e) {} - try { view.toJson(); assert(false); } catch (IllegalStateException e) {} - try { view.toString(); assert(false); } catch (IllegalStateException e) {} - } - - public void testViewShouldInvalidate() { - Table t = new Table(); - t.addColumn(RealmFieldType.INTEGER, "intCol"); - t.add(1); - t.add(2); - t.add(3); - - TableView view = t.where().equalTo(new long[]{0}, 2).findAll(); - // access view is ok. - assertEquals(1, view.size()); - - // access view after change in value is ok - t.setLong(0, 0, 3, false); - accessingViewOk(view); - - // access view after additions to table must fail - t.add(4); - accessingViewMustThrow(view); - - // recreate view to access again - view = t.where().equalTo(new long[]{0}, 2).findAll(); - accessingViewOk(view); - - // Removing any row in Table should invalidate view - t.remove(3); - accessingViewMustThrow(view); - } - - public void testMaximumDate() { - - Table table = new Table(); - table.addColumn(RealmFieldType.DATE, "date"); - - table.add(new Date(0)); - table.add(new Date(10000)); - table.add(new Date(1000)); - - TableView view = table.where().findAll(); - - assertEquals(new Date(10000), view.maximumDate(0)); - - } - - public void testMinimumDate() { - - Table table = new Table(); - table.addColumn(RealmFieldType.DATE, "date"); - - table.add(new Date(10000)); - table.add(new Date(0)); - table.add(new Date(1000)); - - TableView view = table.where().findAll(); - - assertEquals(new Date(0), view.minimumDate(0)); - - } -} From 43e727e0541882264124fe4e750a038442b72411 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 30 Nov 2016 15:28:23 +0800 Subject: [PATCH 0257/2110] OS Results's first and last --- .../main/cpp/io_realm_internal_Collection.cpp | 29 +++++++++++++++++++ .../src/main/java/io/realm/RealmResults.java | 12 +++++--- .../java/io/realm/internal/Collection.java | 10 +++++++ .../java/io/realm/internal/PendingRow.java | 8 +++++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/PendingRow.java diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index c084f7713d..cf6fc49620 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -102,6 +102,35 @@ Java_io_realm_internal_Collection_nativeGetRow(JNIEnv *env, jclass, jlong native return reinterpret_cast(nullptr); } +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + auto optional_row = results->first(); + if (optional_row) { + return reinterpret_cast(new Row(std::move(optional_row.value()))); + } + } CATCH_STD() + return reinterpret_cast(nullptr); + +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeLastRow(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + try { + auto results = reinterpret_cast(native_ptr); + auto optional_row = results->last(); + if (optional_row) { + return reinterpret_cast(new Row(std::move(optional_row.value()))); + } + } CATCH_STD() + return reinterpret_cast(nullptr); +} + JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeClear(JNIEnv *env, jclass, jlong native_ptr) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 980b7cf027..9feb82b94d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -170,8 +170,10 @@ public E first(E defaultValue) { } private E firstImpl(boolean shouldThrow, E defaultValue) { - if (!isEmpty()) { - return get(0); + Row row = collection.firstUncheckedRow(); + + if (row != null) { + return realm.get(classSpec, row); } else { if (shouldThrow) { throw new IndexOutOfBoundsException("No results were found."); @@ -199,8 +201,10 @@ public E last(E defaultValue) { } private E lastImpl(boolean shouldThrow, E defaultValue) { - if (!isEmpty()) { - return get(size() - 1); + Row row = collection.lastUncheckedRow(); + + if (row != null) { + return realm.get(classSpec, row); } else { if (shouldThrow) { throw new IndexOutOfBoundsException("No results were found."); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 2f10c4568b..7cd19d01aa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -152,6 +152,14 @@ public UncheckedRow getUncheckedRow(int index) { return UncheckedRow.getByRowPointer(query.table, nativeGetRow(nativePtr, index)); } + public UncheckedRow firstUncheckedRow() { + return UncheckedRow.getByRowPointer(query.table, nativeFirstRow(nativePtr)); + } + + public UncheckedRow lastUncheckedRow() { + return UncheckedRow.getByRowPointer(query.table, nativeLastRow(nativePtr)); + } + public Table getTable() { return query.getTable(); } @@ -237,6 +245,8 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q long sortDescNativePtr, long distinctDescNativePtr); private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); + private static native long nativeFirstRow(long nativePtr); + private static native long nativeLastRow(long nativePtr); private static native boolean nativeContains(long nativePtr, long nativeRowPtr); private static native void nativeClear(long nativePtr); private static native long nativeSize(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java new file mode 100644 index 0000000000..25fcf2cce1 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -0,0 +1,8 @@ +package io.realm.internal; + +/** + * Created by cc on 16-11-30. + */ + +public class PendingRow implements Row { +} From ec6d0ef8ad054d0a8f192ffef9b378c31f0aa250 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 30 Nov 2016 17:19:16 +0800 Subject: [PATCH 0258/2110] Add PendingRow to suport findFirst --- .../main/java/io/realm/HandlerController.java | 3 +- .../src/main/java/io/realm/ProxyState.java | 34 +-- .../src/main/java/io/realm/RealmQuery.java | 95 ++----- .../java/io/realm/internal/PendingRow.java | 231 +++++++++++++++++- .../java/io/realm/internal/UncheckedRow.java | 6 +- 5 files changed, 262 insertions(+), 107 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java index 134ead5ab8..752fdc1be6 100644 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java @@ -298,7 +298,7 @@ private void updateAsyncEmptyRealmObject() { * @param realmResultsToBeNotified list of all RealmResults listeners that can be notified. */ void notifyAllListeners(List> realmResultsToBeNotified) { - +/* // Notify all RealmResults (async and synchronous). for (Iterator> it = realmResultsToBeNotified.iterator(); !realm.isClosed() && it.hasNext(); ) { RealmResults realmResults = it.next(); @@ -320,6 +320,7 @@ void notifyAllListeners(List> realmResultsToB // Trigger global listeners last. // Note that NotificationTest.callingOrdersOfListeners will fail if orders change. notifyGlobalListeners(); + */ } private void collectAsyncRealmResultsCallbacks(List> resultsToBeNotified) { diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index af3d807c7a..3e67066919 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -20,6 +20,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; +import io.realm.internal.PendingRow; import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.TableQuery; @@ -29,7 +30,7 @@ * This implements {@code RealmObjectProxy} interface, to eliminate copying logic between * {@link RealmObject} and {@link DynamicRealmObject}. */ -public final class ProxyState { +public final class ProxyState implements PendingRow.FrontEnd { private E model; private String className; private Class clazzName; @@ -163,27 +164,8 @@ public ProxyState(Class clazzName, E model) { */ void notifyChangeListeners$realm() { if (!listeners.isEmpty()) { - boolean notify = false; - - Table table = row.getTable(); - if (table == null) { - // Completed async queries might result in `table == null`, `isCompleted == true` and `row == Row.EMPTY_ROW` - // We still want to trigger change notifications for these cases. - // isLoaded / isValid should be considered properties on RealmObjects as well so any change to these - // should trigger a RealmChangeListener. - notify = true; - } else { - long version = table.getVersion(); - if (currentTableVersion != version) { - currentTableVersion = version; - notify = true; - } - } - - if (notify) { - for (RealmChangeListener listener : listeners) { - listener.onChange(model); - } + for (RealmChangeListener listener : listeners) { + listener.onChange(model); } } } @@ -219,4 +201,12 @@ private boolean isLoaded() { realm.checkIfValid(); return getPendingQuery$realm() == null || isCompleted$realm(); } + + @Override + public void onQueryFinished(Row row, boolean asyncQuery) { + this.row = row; + if (asyncQuery) { + notifyChangeListeners$realm(); + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 281fa7dcb2..0e20605374 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -21,22 +21,19 @@ import java.util.Collections; import java.util.Date; import java.util.Locale; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; import io.realm.annotations.Required; import io.realm.internal.Collection; import io.realm.internal.LinkView; +import io.realm.internal.PendingRow; import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; -import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.TableQuery; import io.realm.internal.async.ArgumentsHolder; import io.realm.internal.async.QueryUpdateTask; -import io.realm.log.RealmLog; /** * A RealmQuery encapsulates a query on a {@link io.realm.Realm} or a {@link io.realm.RealmResults} using the Builder @@ -1649,98 +1646,36 @@ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, */ public E findFirst() { checkQueryIsNotReused(); - long tableRowIndex = getSourceRowIndexForFirstObject(); - if (tableRowIndex >= 0) { - E realmObject = realm.get(clazz, className, tableRowIndex); - return realmObject; - } else { - return null; - } - } - - /** - * Similar to {@link #findFirst()} but runs asynchronously on a worker thread - * This method is only available from a Looper thread. - * - * @return immediately an empty {@link RealmObject}. Trying to access any field on the returned object - * before it is loaded will throw an {@code IllegalStateException}. Use {@link RealmObject#isLoaded()} to check if - * the object is fully loaded or register a listener {@link io.realm.RealmObject#addChangeListener} - * to be notified when the query completes. If no RealmObject was found after the query completed, the returned - * RealmObject will have {@link RealmObject#isLoaded()} set to {@code true} and {@link RealmObject#isValid()} set to - * {@code false}. - */ - public E findFirstAsync() { - checkQueryIsNotReused(); - final WeakReference weakNotifier = getWeakReferenceNotifier(); - - // handover the query (to be used by a worker thread) - final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - - // save query arguments (for future update) - argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_FIRST); - - final RealmConfiguration realmConfiguration = realm.getConfiguration(); + // TODO: The performance by the pending query will be a little bit worse than directly calling core's + // Query.find(). The overhead comes with core needs to add all the row indices to the vector. However this can + // be optimized by adding support of limit in OS's Results which is supported by core already. + PendingRow pendingRow = new PendingRow(realm.sharedRealm, query, null); // prepare an empty reference of the RealmObject, so we can return it immediately (promise) // then update it once the query complete in the background. final E result; if (isDynamicQuery()) { //noinspection unchecked - result = (E) new DynamicRealmObject(className, realm, Row.EMPTY_ROW); + result = (E) new DynamicRealmObject(className, realm, pendingRow); } else { result = realm.getConfiguration().getSchemaMediator().newInstance( - clazz, realm, Row.EMPTY_ROW, realm.getSchema().getColumnInfo(clazz), + clazz, realm, pendingRow, realm.getSchema().getColumnInfo(clazz), false, Collections.emptyList()); } final RealmObjectProxy proxy = (RealmObjectProxy) result; - final WeakReference realmObjectWeakReference = realm.handlerController.addToAsyncRealmObject(proxy, this); - - final Future pendingQuery = Realm.asyncTaskExecutor.submitQuery(new Callable() { - @Override - public Long call() throws Exception { - if (!Thread.currentThread().isInterrupted()) { - SharedRealm sharedRealm = null; - - try { - sharedRealm = SharedRealm.getInstance(realmConfiguration); - - long handoverRowPointer = TableQuery.findWithHandover(sharedRealm, handoverQueryPointer); - if (handoverRowPointer == 0) { // empty row - realm.handlerController.addToEmptyAsyncRealmObject(realmObjectWeakReference, RealmQuery.this); - realm.handlerController.removeFromAsyncRealmObject(realmObjectWeakReference); - } - - QueryUpdateTask.Result result = QueryUpdateTask.Result.newRealmObjectResponse(); - result.updatedRow.put(realmObjectWeakReference, handoverRowPointer); - result.versionID = sharedRealm.getVersionID(); - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_OBJECT, result); - - return handoverRowPointer; - - } catch (Throwable e) { - RealmLog.error(e); - // handler can't throw a checked exception need to wrap it into unchecked Exception - closeSharedRealmAndSendEventToNotifier(sharedRealm, - weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); - } finally { - if (sharedRealm != null && !sharedRealm.isClosed()) { - sharedRealm.close(); - } - } - } else { - TableQuery.nativeCloseQueryHandover(handoverQueryPointer); - } - - return INVALID_NATIVE_POINTER; - } - }); - proxy.realmGet$proxyState().setPendingQuery$realm(pendingQuery); + pendingRow.setFrontEnd(proxy.realmGet$proxyState()); return result; } + /** + * @deprecated use {@link #findFirst()} instead. + */ + public E findFirstAsync() { + return findFirst(); + } + private void checkSortParameters(String fieldNames[], final Sort[] sortOrders) { if (fieldNames == null) { throw new IllegalArgumentException("fieldNames cannot be 'null'."); diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 25fcf2cce1..3f24caf8c9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -1,8 +1,235 @@ package io.realm.internal; +import java.lang.ref.WeakReference; +import java.util.Date; + +import io.realm.RealmChangeListener; +import io.realm.RealmFieldType; + /** - * Created by cc on 16-11-30. + * A PendingRow is a row relies on a pending async query. + * Before the query returns, calling any accessors will immediately execute the query and call the corresponding + * accessor on the query result. If the query results is empty, an {@link IllegalStateException} will be thrown. + * After the query returns, {@link FrontEnd#onQueryFinished(Row, boolean)} will be called to give the front end a + * chance to reset the row. If the async query returns an empty result, the query will be executed again later until a + * valid row is contained by the query results. */ - public class PendingRow implements Row { + + // Implement this interface to reset the PendingRow to a Row backed by real data when query returned. + public interface FrontEnd { + // When asyncQuery is true, the pending query is executed asynchronously. Otherwise the query is triggered by + // calling any accessors before the async query returns. + void onQueryFinished(Row row, boolean asyncQuery); + } + + private static final String EMPTY_ROW_MESSAGE = + "This RealmObject is empty. There isn't any objects match the query."; + private static final String PROXY_NOT_SET_MESSAGE = "The 'frontEnd' has not been set."; + private static final String QUERY_EXECUTED_MESSAGE = + "The query has been executed. This 'PendingRow' is not valid anymore."; + + private Collection pendingCollection; + private Collection.Listener listener; + private WeakReference frontEnd; + + public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor) { + pendingCollection = new Collection(sharedRealm, query, sortDescriptor); + listener = new Collection.Listener(new RealmChangeListener() { + @Override + public void onChange(PendingRow pendingRow) { + if (frontEnd == null) { + throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); + } + // TODO: PendingRow will always get the first Row of the query since we only support findFirst. + Row row = pendingCollection.firstUncheckedRow(); + if (frontEnd.get() == null) { + // The front end is GCed. + clearPendingCollection(); + return; + } + // If no rows returned by the query, just wait for the query updates until it returns a valid row. + if (row != null) { + // Ask the front end to reset the row and stop async query. + frontEnd.get().onQueryFinished(row, true); + clearPendingCollection(); + } + } + }, this); + pendingCollection.addListener(listener); + } + + // To set the front end of this PendingRow. + public void setFrontEnd(FrontEnd frontEnd) { + this.frontEnd = new WeakReference(frontEnd); + } + + @Override + public long getColumnCount() { + return executeQuery().getColumnCount(); + } + + @Override + public String getColumnName(long columnIndex) { + return executeQuery().getColumnName(columnIndex); + } + + @Override + public long getColumnIndex(String columnName) { + return executeQuery().getColumnIndex(columnName); + } + + @Override + public RealmFieldType getColumnType(long columnIndex) { + return executeQuery().getColumnType(columnIndex); + } + + @Override + public Table getTable() { + return executeQuery().getTable(); + } + + @Override + public long getIndex() { + return executeQuery().getIndex(); + } + + @Override + public long getLong(long columnIndex) { + return executeQuery().getLong(columnIndex); + } + + @Override + public boolean getBoolean(long columnIndex) { + return executeQuery().getBoolean(columnIndex); + } + + @Override + public float getFloat(long columnIndex) { + return executeQuery().getFloat(columnIndex); + } + + @Override + public double getDouble(long columnIndex) { + return executeQuery().getDouble(columnIndex); + } + + @Override + public Date getDate(long columnIndex) { + return executeQuery().getDate(columnIndex); + } + + @Override + public String getString(long columnIndex) { + return executeQuery().getString(columnIndex); + } + + @Override + public byte[] getBinaryByteArray(long columnIndex) { + return executeQuery().getBinaryByteArray(columnIndex); + } + + @Override + public long getLink(long columnIndex) { + return executeQuery().getLink(columnIndex); + } + + @Override + public boolean isNullLink(long columnIndex) { + return executeQuery().isNullLink(columnIndex); + } + + @Override + public LinkView getLinkList(long columnIndex) { + return executeQuery().getLinkList(columnIndex); + } + + @Override + public void setLong(long columnIndex, long value) { + executeQuery().setLong(columnIndex, value); + } + + @Override + public void setBoolean(long columnIndex, boolean value) { + executeQuery().setBoolean(columnIndex, value); + } + + @Override + public void setFloat(long columnIndex, float value) { + executeQuery().setFloat(columnIndex, value); + } + + @Override + public void setDouble(long columnIndex, double value) { + executeQuery().setDouble(columnIndex, value); + } + + @Override + public void setDate(long columnIndex, Date date) { + executeQuery().setDate(columnIndex, date); + } + + @Override + public void setString(long columnIndex, String value) { + executeQuery().setString(columnIndex, value); + } + + @Override + public void setBinaryByteArray(long columnIndex, byte[] data) { + executeQuery().setBinaryByteArray(columnIndex, data); + } + + @Override + public void setLink(long columnIndex, long value) { + executeQuery().setLink(columnIndex, value); + } + + @Override + public void nullifyLink(long columnIndex) { + executeQuery().nullifyLink(columnIndex); + } + + @Override + public boolean isNull(long columnIndex) { + return executeQuery().isNull(columnIndex); + } + + @Override + public void setNull(long columnIndex) { + executeQuery().setNull(columnIndex); + } + + @Override + public boolean isAttached() { + return executeQuery().isAttached(); + } + + @Override + public boolean hasColumn(String fieldName) { + return executeQuery().hasColumn(fieldName); + } + + private void clearPendingCollection() { + pendingCollection.removeListener(listener); + pendingCollection = null; + listener = null; + } + + private Row executeQuery() { + if (pendingCollection == null) { + throw new IllegalStateException(QUERY_EXECUTED_MESSAGE); + } + if (frontEnd == null) { + throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); + } + Row row = pendingCollection.getUncheckedRow(0); + if (row == null) { + throw new IllegalStateException(EMPTY_ROW_MESSAGE); + } + if (frontEnd.get() != null) { + frontEnd.get().onQueryFinished(pendingCollection.firstUncheckedRow(), false); + } + clearPendingCollection(); + return row; + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 7802428011..5b5b441218 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -89,8 +89,10 @@ static UncheckedRow getByRowPointer(Context context, Table table, long nativeRow // FIXME: Testing code public static UncheckedRow getByRowPointer(Table table, long nativeRowPointer) { - UncheckedRow row = new UncheckedRow(table.context, table, nativeRowPointer); - return row; + if (nativeRowPointer != 0) { + return new UncheckedRow(table.context, table, nativeRowPointer); + } + return null; } /** From 53aa306b1fc0200b4314c33da42a5449609d5072 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 1 Dec 2016 15:45:53 +0000 Subject: [PATCH 0259/2110] Nh/objectstore userstore (#3838) * Add a UserStore based on ObjectStore implementation --- CHANGELOG.md | 1 + .../java/io/realm/SyncManagerTests.java | 24 +--- .../java/io/realm/SyncUserTests.java | 38 ++--- .../realm-library/src/main/cpp/CMakeLists.txt | 8 +- .../main/cpp/io_realm_RealmFileUserStore.cpp | 129 +++++++++++++++++ .../src/main/cpp/io_realm_internal_Util.cpp | 2 + realm/realm-library/src/main/cpp/util.cpp | 1 + realm/realm-library/src/main/cpp/util.hpp | 1 + .../java/io/realm/ObjectServer.java | 3 +- .../java/io/realm/RealmFileUserStore.java | 92 ++++++++++++ .../objectServer/java/io/realm/SyncUser.java | 10 +- .../objectServer/java/io/realm/UserStore.java | 39 +++--- .../realm/android/SharedPrefsUserStore.java | 131 ------------------ 13 files changed, 275 insertions(+), 204 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b50dc3904..76572fcbdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Object Server API Changes (In Beta) * Fixed `SyncConfiguration.toString()` so it now outputs a correct description instead of an empty string (#3787). +* Add a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`). ### Bug fixes diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 15d366a4da..510bfca5aa 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -16,11 +16,8 @@ package io.realm; -import android.content.Context; -import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -39,7 +36,6 @@ @RunWith(AndroidJUnit4.class) public class SyncManagerTests { - private Context context; private UserStore userStore; @Rule @@ -50,41 +46,29 @@ public class SyncManagerTests { @Before public void setUp() { - context = InstrumentationRegistry.getContext(); userStore = new UserStore() { @Override - public SyncUser put(String key, SyncUser user) { - return null; - } + public void put(SyncUser user) {} @Override - public SyncUser get(String key) { + public SyncUser get() { return null; } @Override - public SyncUser remove(String key) { - return null; - } + public void remove() {} @Override public Collection allUsers() { return null; } - @Override - public void clear() { - } }; } - @After - public void tearDown() { - } - @Test public void init() { - // Realm.init() calls SyncManager.init() wihich will start a thread for the sync client + // Realm.init() calls SyncManager.init() which will start a thread for the sync client boolean found = false; Set threads = Thread.getAllStackTraces().keySet(); for (Thread thread : threads) { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index a587ca1709..1c017f7929 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -19,18 +19,16 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import org.junit.Before; +import org.junit.After; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; import java.util.Collection; -import io.realm.android.SharedPrefsUserStore; import io.realm.rule.RunInLooperThread; import io.realm.util.SyncTestUtils; @@ -46,10 +44,15 @@ public class SyncUserTests { @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); - @Before - public void setUp() { - Realm.init(InstrumentationRegistry.getTargetContext()); - SyncManager.getUserStore().clear(); + @BeforeClass + public static void initUserStore() { + UserStore userStore = new RealmFileUserStore(InstrumentationRegistry.getTargetContext().getFilesDir().getPath()); + SyncManager.setUserStore(userStore); + } + + @After + public void tearDown() { + RealmFileUserStore.nativeResetForTesting(); } @Test @@ -63,9 +66,8 @@ public void toAndFromJson() { @Test public void currentUser_returnsNullIfUserExpired() { // Add an expired user to the user store - UserStore userStore = new SharedPrefsUserStore(InstrumentationRegistry.getContext()); - SyncManager.setUserStore(userStore); - userStore.put(UserStore.CURRENT_USER_KEY, SyncTestUtils.createTestUser(Long.MIN_VALUE)); + UserStore userStore = SyncManager.getUserStore(); + userStore.put(SyncTestUtils.createTestUser(Long.MIN_VALUE)); // Invalid users should not be returned when asking the for the current user assertNull(SyncUser.currentUser()); @@ -74,11 +76,10 @@ public void currentUser_returnsNullIfUserExpired() { // Test that current user is cleared if it is logged out @Test public void currentUser_clearedOnLogout() { - // Add an expired user to the user store + // Add 1 valid user to the user store SyncUser user = SyncTestUtils.createTestUser(Long.MAX_VALUE); - UserStore userStore = new SharedPrefsUserStore(InstrumentationRegistry.getContext()); - SyncManager.setUserStore(userStore); - userStore.put(UserStore.CURRENT_USER_KEY, user); + UserStore userStore = SyncManager.getUserStore(); + userStore.put(user); SyncUser savedUser = SyncUser.currentUser(); assertEquals(user, savedUser); @@ -97,10 +98,9 @@ public void all_empty() { @Test public void all_validUsers() { // Add 1 expired user and 1 valid user to the user store - UserStore userStore = new SharedPrefsUserStore(InstrumentationRegistry.getContext()); - SyncManager.setUserStore(userStore); - userStore.put(UserStore.CURRENT_USER_KEY, SyncTestUtils.createTestUser(Long.MIN_VALUE)); - userStore.put(UserStore.CURRENT_USER_KEY, SyncTestUtils.createTestUser(Long.MAX_VALUE)); + UserStore userStore = SyncManager.getUserStore(); + userStore.put(SyncTestUtils.createTestUser(Long.MIN_VALUE)); + userStore.put(SyncTestUtils.createTestUser(Long.MAX_VALUE)); Collection users = SyncUser.all(); assertEquals(1, users.size()); diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 83078a21ec..cce5819504 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -44,7 +44,7 @@ set(classes_LIST set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST - io.realm.SyncManager io.realm.internal.objectserver.ObjectServerSession) + io.realm.SyncManager io.realm.internal.objectserver.ObjectServerSession io.realm.RealmFileUserStore) endif() create_javah(TARGET jni_headers CLASSES ${classes_LIST} @@ -146,7 +146,8 @@ file(GLOB jni_SRC if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectserver_ObjectServerSession.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectserver_ObjectServerSession.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFileUserStore.cpp) endif() # Object Store source files @@ -159,7 +160,8 @@ file(GLOB objectstore_SRC # Sync needed Object Store files if (build_SYNC) file(GLOB objectstore_sync_SRC - "object-store/src/sync/*.cpp") + "object-store/src/sync/*.cpp" + "object-store/src/sync/impl/*.cpp") endif() add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp new file mode 100644 index 0000000000..0c7e45c0d5 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -0,0 +1,129 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 +#include +#include "io_realm_RealmFileUserStore.h" +#include "sync/sync_manager.hpp" +#include "sync/sync_user.hpp" +#include "util.hpp" + +using namespace realm; + +static const char* ERR_MULTIPLE_LOGGED_IN_USERS = "Cannot be called if more that one valid, logged in user exists."; +static const char* ERR_NO_LOGGED_IN_USER = "No user logged in yet."; +static const char* ERR_COULD_NOT_ALLOCATE_MEMORY = "Could not allocate memory to return all users."; + +static const std::shared_ptr& currentUserOrThrow(); + +JNIEXPORT jstring JNICALL +Java_io_realm_RealmFileUserStore_nativeGetCurrentUser (JNIEnv *env, jclass) +{ + TR_ENTER() + try { + const std::shared_ptr &user = currentUserOrThrow(); + if (user->state() == SyncUser::State::Active) { + return to_jstring(env, user->refresh_token().data()); + } else { + return nullptr; + } + } CATCH_STD() + return nullptr; +} + +JNIEXPORT void JNICALL +Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser (JNIEnv *env, jclass, jstring identity, jstring jsonToken, jstring url) +{ + TR_ENTER() + try { + JStringAccessor user_identity(env, identity); // throws + JStringAccessor user_json_token(env, jsonToken); // throws + JStringAccessor auth_url(env, url); // throws + + SyncManager::shared().get_user(user_identity, user_json_token, std::string(auth_url)); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_RealmFileUserStore_nativeLogoutCurrentUser (JNIEnv *env, jclass) +{ + TR_ENTER() + try { + const std::shared_ptr& user = currentUserOrThrow(); + user->log_out(); + } CATCH_STD() +} + + +JNIEXPORT void JNICALL +Java_io_realm_RealmFileUserStore_nativeConfigureMetaDataSystem (JNIEnv *env, jclass, jstring baseFile) +{ + TR_ENTER() + try { + JStringAccessor base_file_path(env, baseFile); // throws + SyncManager::shared().configure_file_system(base_file_path, SyncManager::MetadataMode::NoEncryption); + } CATCH_STD() +} + +JNIEXPORT jobjectArray JNICALL +Java_io_realm_RealmFileUserStore_nativeGetAllUsers (JNIEnv *env, jclass) +{ + TR_ENTER() + std::vector> all_users = SyncManager::shared().all_users(); + if (!all_users.empty()) { + std::vector> valid_users; + jsize array_length = std::count_if(all_users.begin(),all_users.end(), + [&](const std::shared_ptr& user) { + if (user->state() == SyncUser::State::Active) { + valid_users.emplace_back(std::move(user)); + return true; + } + return false; + }); + + jobjectArray users_token = env->NewObjectArray(array_length, java_lang_string, 0); + if (users_token == NULL) { + ThrowException(env, OutOfMemory, ERR_COULD_NOT_ALLOCATE_MEMORY); + return nullptr; + } + + for (auto user : valid_users) { + env->SetObjectArrayElement(users_token, --array_length, to_jstring(env, user->refresh_token().data())); + } + + return users_token; + } + return nullptr; +} + +JNIEXPORT void JNICALL +Java_io_realm_RealmFileUserStore_nativeResetForTesting (JNIEnv *, jclass) +{ + TR_ENTER(); + SyncManager::shared().reset_for_testing(); +} + +static const std::shared_ptr& currentUserOrThrow() //throws +{ + std::vector> all_users = SyncManager::shared().all_users(); + if (all_users.size() > 1) { + throw std::runtime_error(ERR_MULTIPLE_LOGGED_IN_USERS); + } else if (all_users.size() < 1) { + throw std::runtime_error(ERR_NO_LOGGED_IN_USER); + } else { + return all_users.front(); + } +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 241ce22908..03bb3284a0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -46,6 +46,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_float = GetClass(env, "java/lang/Float"); java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); java_lang_double = GetClass(env, "java/lang/Double"); + java_lang_string = GetClass(env, "java/lang/String"); java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); } @@ -62,6 +63,7 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) env->DeleteGlobalRef(java_lang_long); env->DeleteGlobalRef(java_lang_float); env->DeleteGlobalRef(java_lang_double); + env->DeleteGlobalRef(java_lang_string); } } diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 1ba8e3e2b1..99d917c817 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -38,6 +38,7 @@ jmethodID java_lang_long_init; jclass java_lang_float; jmethodID java_lang_float_init; jclass java_lang_double; +jclass java_lang_string; jmethodID java_lang_double_init; jclass session_class_ref; jmethodID session_error_handler; diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 68b9a89ee6..98cce1fd3a 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -665,6 +665,7 @@ extern jmethodID java_lang_long_init; extern jclass java_lang_float; extern jmethodID java_lang_float_init; extern jclass java_lang_double; +extern jclass java_lang_string; extern jmethodID java_lang_double_init; // FIXME Move to own library diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index 38b0c0c1cb..9e8abe33a4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -19,7 +19,6 @@ import android.content.Context; import android.content.pm.PackageInfo; -import io.realm.android.SharedPrefsUserStore; import io.realm.annotations.Beta; import io.realm.internal.Keep; @@ -43,7 +42,7 @@ public static void init(Context context) { } // Configure default UserStore - UserStore userStore = new SharedPrefsUserStore(context); + UserStore userStore = new RealmFileUserStore(context.getFilesDir().getPath()); SyncManager.init(appId, userStore); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java new file mode 100644 index 0000000000..0d89455e30 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +/** + * A User Store backed by a Realm file to store user. + */ +public class RealmFileUserStore implements UserStore { + protected RealmFileUserStore(String path) { + nativeConfigureMetaDataSystem(path); + } + + /** + * {@inheritDoc} + */ + @Override + public void put(SyncUser user) { + String userJson = user.toJson(); + // create or update token (userJson) using identity + nativeUpdateOrCreateUser(user.getIdentity(), userJson, user.getSyncUser().getAuthenticationUrl().toString()); + } + + /** + * {@inheritDoc} + */ + @Override + public SyncUser get() { + String userJson = nativeGetCurrentUser(); + if (userJson != null) { + return SyncUser.fromJson(userJson); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void remove() { + nativeLogoutCurrentUser(); + } + + /** + * {@inheritDoc} + */ + @Override + public Collection allUsers() { + String[] allUsers = nativeGetAllUsers(); + if (allUsers != null && allUsers.length > 0) { + ArrayList users = new ArrayList(allUsers.length); + for (String userJson : allUsers) { + users.add(SyncUser.fromJson(userJson)); + } + return users; + } + return Collections.emptyList(); + } + + // init and load the Metadata Realm containing SyncUsers + protected static native void nativeConfigureMetaDataSystem(String baseFile); + + // return json data (token) of the current logged in user + protected static native String nativeGetCurrentUser(); + + protected static native String[] nativeGetAllUsers(); + + protected static native void nativeUpdateOrCreateUser(String identity, String jsonToken, String url); + + protected static native void nativeLogoutCurrentUser(); + + // Should only be called for tests + static native void nativeResetForTesting(); + +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 65130e75c3..a892c980bb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -77,7 +77,7 @@ private SyncUser(ObjectServerUser user) { * been invalidated. */ public static SyncUser currentUser() { - SyncUser user = SyncManager.getUserStore().get(UserStore.CURRENT_USER_KEY); + SyncUser user = SyncManager.getUserStore().get(); if (user != null && user.isValid()) { return user; } @@ -114,7 +114,7 @@ public static SyncUser fromJson(String user) { try { JSONObject obj = new JSONObject(user); URL authUrl = new URL(obj.getString("authUrl")); - Token userToken = Token.from(obj.getJSONObject("userToken")); + Token userToken = Token.from(obj.getJSONObject("userToken"));//TODO rename to refresh_token ObjectServerUser syncUser = new ObjectServerUser(userToken, authUrl); JSONArray realmTokens = obj.getJSONArray("realms"); for (int i = 0; i < realmTokens.length(); i++) { @@ -158,7 +158,7 @@ public static SyncUser login(final SyncCredentials credentials, final String aut ObjectServerUser syncUser = new ObjectServerUser(result.getRefreshToken(), authUrl); SyncUser user = new SyncUser(syncUser); RealmLog.info("Succeeded authenticating user.\n%s", user); - SyncManager.getUserStore().put(UserStore.CURRENT_USER_KEY, user); + SyncManager.getUserStore().put(user); SyncManager.notifyUserLoggedIn(user); return user; } else { @@ -265,9 +265,7 @@ public void logout() { // FIXME We still need to cache the user token so it can be revoked. syncUser.clearTokens(); - if (SyncUser.this.equals(SyncUser.currentUser())) { - SyncManager.getUserStore().remove(UserStore.CURRENT_USER_KEY); - } + SyncManager.getUserStore().remove(); // Delete all Realms if needed. for (ObjectServerUser.AccessDescription desc : syncUser.getRealms()) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java index 528cae598b..052f3759b9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java @@ -18,7 +18,6 @@ import java.util.Collection; -import io.realm.android.SharedPrefsUserStore; import io.realm.annotations.Beta; /** @@ -29,37 +28,37 @@ * be called on the Main Thread. All implementations of this interface should be thread safe. * * @see SyncManager#setUserStore(UserStore) - * @see SharedPrefsUserStore + * @see RealmFileUserStore */ @Beta public interface UserStore { - String CURRENT_USER_KEY = "realm$currentUser"; - /** - * Saves a {@link SyncUser} object under the given key. If another user already exists, it will be replaced. + * Saves a {@link SyncUser} object. If another user already exists, it will be replaced. + * {@link SyncUser#getIdentity()} is used as a unique identifier of a given {@link SyncUser}. * - * @param key key used to store the User. * @param user {@link SyncUser} object to store. - * @return The previous user saved with this key or {@code null} if no user was replaced. - * */ - SyncUser put(String key, SyncUser user); + void put(SyncUser user); /** - * Retrieves the {@link SyncUser} with the given key. + * Retrieves the current {@link SyncUser}. * - * @param key {@link SyncUser} saved under the given key or {@code null} if no user exists for that key. + * For now, current User cannot be called if more that one valid, logged in user + * exists, it will throw an exception. */ - SyncUser get(String key); + //TODO when ObjectStore integration of SyncManager is completed & multiple + // users are allowed, consider passing the User identity to lookup apply + // the operation to a particular user. + SyncUser get(); /** - * Removes the user with the given key from the store. - * - * @param key key for the user to remove. - * @return {@link SyncUser} that was removed or {@code null} if no user matched the key. + * Removes the current user from the store. */ - SyncUser remove(String key); + //TODO when ObjectStore integration of SyncManager is completed & multiple + // users are allowed, consider passing the User identity to lookup apply + // the operation to a particular user. + void remove(); /** * Returns a collection of all users saved in the User store. @@ -67,10 +66,4 @@ public interface UserStore { * @return Collection of all users. If no users exist, an empty collection is returned. */ Collection allUsers(); - - - /** - * Removes all saved users. - */ - void clear(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java deleted file mode 100644 index 31d4b2b1b5..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/android/SharedPrefsUserStore.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.android; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -import io.realm.SyncUser; -import io.realm.UserStore; - -/** - * A User Store backed by a SharedPreferences file. - */ -public class SharedPrefsUserStore implements UserStore { - - private final SharedPreferences sp; - private SyncUser cachedCurrentUser; // Keep a quick reference to the current user - - public SharedPrefsUserStore(Context context) { - sp = context.getSharedPreferences("realm_object_server_users", Context.MODE_PRIVATE); - } - - /** - * {@inheritDoc} - */ - @Override - public SyncUser put(String key, SyncUser user) { - String previousUser = sp.getString(key, null); - SharedPreferences.Editor editor = sp.edit(); - editor.putString(key, user.toJson()); - // Optimistically save. If the user isn't saved due to a process crash it isn't dangerous. - editor.apply(); - - if (UserStore.CURRENT_USER_KEY.equals(key)) { - cachedCurrentUser = user; - } - - if (previousUser != null) { - return SyncUser.fromJson(previousUser); - } else { - return null; - } - } - - /** - * {@inheritDoc} - */ - @Override - public SyncUser get(String key) { - if (UserStore.CURRENT_USER_KEY.equals(key) && cachedCurrentUser != null) { - return cachedCurrentUser; - } - - String userData = sp.getString(key, ""); - if (userData.equals("")) { - return null; - } - - SyncUser user = SyncUser.fromJson(userData); - if (UserStore.CURRENT_USER_KEY.equals(key)) { - cachedCurrentUser = user; - } - return user; - } - - /** - * {@inheritDoc} - */ - @Override - public SyncUser remove(String key) { - String currentUser = sp.getString(key, null); - SharedPreferences.Editor editor = sp.edit(); - editor.putString(key, null); - editor.apply(); - - if (UserStore.CURRENT_USER_KEY.equals(key) && cachedCurrentUser != null) { - cachedCurrentUser = null; - } - - if (currentUser != null) { - return SyncUser.fromJson(currentUser); - } else { - return null; - } - } - - /** - * {@inheritDoc} - */ - @Override - public Collection allUsers() { - Map all = sp.getAll(); - ArrayList users = new ArrayList(all.size()); - for (Object userJson : all.values()) { - users.add(SyncUser.fromJson((String) userJson)); - } - return users; - } - - /** - * {@inheritDoc} - */ - @Override - public void clear() { - Set all = sp.getAll().keySet(); - SharedPreferences.Editor editor = sp.edit(); - for (String key : all) { - editor.remove(key); - } - editor.apply(); - } -} From 538b532bd34ed60da3680d4ce48d5943e4acae97 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 2 Dec 2016 15:05:21 +0800 Subject: [PATCH 0260/2110] Use OS notification for RealmObject this makes the fine grained notifications for RealmObject possible. --- .../cpp/io_realm_internal_SharedRealm.cpp | 5 +- .../src/main/cpp/java_binding_context.cpp | 73 +++++++++++++++-- .../src/main/cpp/java_binding_context.hpp | 25 ++++-- .../main/java/io/realm/AndroidNotifier.java | 2 + .../main/java/io/realm/HandlerController.java | 2 +- .../src/main/java/io/realm/ProxyState.java | 28 +++++++ .../src/main/java/io/realm/RealmObject.java | 12 +-- .../src/main/java/io/realm/RealmQuery.java | 31 +++++--- .../java/io/realm/internal/RowNotifier.java | 78 +++++++++++++++++++ .../java/io/realm/internal/SharedRealm.java | 11 ++- 10 files changed, 227 insertions(+), 40 deletions(-) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 730a7f7f0a..497a52c111 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -97,14 +97,15 @@ Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv*, jclass, jlong conf } JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr, jobject notifier) +Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr, jobject realm_notifier, + jobject row_notifier) { TR_ENTER_PTR(config_ptr) auto config = reinterpret_cast(config_ptr); try { auto shared_realm = Realm::get_shared_realm(*config); - shared_realm->m_binding_context = JavaBindingContext::create(env, notifier); + shared_realm->m_binding_context = JavaBindingContext::create(env, realm_notifier, row_notifier); // FIXME: Disabled for the collection notifications. There might be some places still need it. // advance_read needs to be handled by Java because of async query. //shared_realm->set_auto_refresh(false); diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index b8954caeb6..4a9270cb14 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -17,6 +17,7 @@ #include "java_binding_context.hpp" #include "util/format.hpp" +#include "util.hpp" using namespace realm; using namespace realm::_impl; @@ -28,33 +29,89 @@ JavaBindingContext::JavaBindingContext(const ConcreteJavaBindContext& concrete_c if (ret != 0) { throw std::runtime_error(util::format("Failed to get Java vm. Error: %d", ret)); } - if (concrete_context.java_notifier) { - m_java_notifier = m_local_jni_env->NewWeakGlobalRef(concrete_context.java_notifier); - jclass cls = m_local_jni_env->GetObjectClass(m_java_notifier); + if (concrete_context.realm_notifier) { + m_realm_notifier = m_local_jni_env->NewWeakGlobalRef(concrete_context.realm_notifier); + jclass cls = m_local_jni_env->GetObjectClass(m_realm_notifier); m_notify_by_other_method = m_local_jni_env->GetMethodID(cls, "notifyCommitByOtherThread", "()V"); } else { - m_java_notifier = nullptr; + m_realm_notifier = nullptr; + } + if (concrete_context.row_notifier) { + m_row_notifier = m_local_jni_env->NewWeakGlobalRef(concrete_context.row_notifier); + jclass cls = m_local_jni_env->GetObjectClass(m_row_notifier); + m_get_observers_method = m_local_jni_env->GetMethodID(cls, "getObservers", + "()[Lio/realm/internal/RowNotifier$Observer;"); + m_get_observed_row_ptrs_method = m_local_jni_env->GetMethodID(cls, "getObservedRowPtrs", + "([Lio/realm/internal/RowNotifier$Observer;)[J"); + m_clear_row_refs = m_local_jni_env->GetMethodID(cls, "clearRowRefs", "()V"); + jclass observer_cls = GetClass(m_local_jni_env, "io/realm/internal/RowNotifier$Observer"); + m_observer_notify_listener = m_local_jni_env->GetMethodID(observer_cls, "notifyListener", "()V"); + } else { + m_row_notifier = nullptr; } } JavaBindingContext::~JavaBindingContext() { - if (m_java_notifier) { + if (m_realm_notifier) { // Always try to attach here since this may be called in the finalizer/phantom thread where m_local_jni_env // should not be used on. No need to call DetachCurrentThread since this thread should always be created by // JVM. JNIEnv *env; m_jvm->AttachCurrentThread(&env, nullptr); - env->DeleteWeakGlobalRef(m_java_notifier); + env->DeleteWeakGlobalRef(m_realm_notifier); } } void JavaBindingContext::changes_available() { - jobject notifier = m_local_jni_env->NewLocalRef(m_java_notifier); + jobject notifier = m_local_jni_env->NewLocalRef(m_realm_notifier); if (notifier) { - m_local_jni_env->CallVoidMethod(m_java_notifier, m_notify_by_other_method); + m_local_jni_env->CallVoidMethod(m_realm_notifier, m_notify_by_other_method); m_local_jni_env->DeleteLocalRef(notifier); } } +std::vector JavaBindingContext::get_observed_rows() +{ + jobject row_notifier = m_local_jni_env->NewLocalRef(m_row_notifier); + if (!row_notifier) { + // The row notifier got GCed + return {}; + } + + jobjectArray observers = static_cast( + m_local_jni_env->CallObjectMethod(row_notifier, m_get_observers_method)); + jlongArray row_ptr_jarray = static_cast( + m_local_jni_env->CallObjectMethod(row_notifier, m_get_observed_row_ptrs_method, observers)); + JniLongArray row_ptrs(m_local_jni_env, row_ptr_jarray); + + std::vector state_list; + for (jsize i = 0; i < row_ptrs.len(); ++i) { + BindingContext::ObserverState observer_state; + Row* row = reinterpret_cast(row_ptrs[i]); + observer_state.table_ndx = row->get_table()->get_index_in_group(); + observer_state.row_ndx = row->get_index(); + observer_state.info = m_local_jni_env->GetObjectArrayElement(observers, i); + state_list.push_back(std::move(observer_state)); + } + return state_list; +} + +void JavaBindingContext::did_change(std::vector const& observer_state_list, + std::vector const& invalidated, + bool /*version_changed*/) +{ + for (auto state : observer_state_list) { + jobject observer = reinterpret_cast(state.info); + //if (!state.changes.empty()) { + m_local_jni_env->CallVoidMethod(observer, m_observer_notify_listener); + //} + } + for (auto deleted_row_observer : invalidated) { + jobject observer = reinterpret_cast(deleted_row_observer); + m_local_jni_env->CallVoidMethod(observer, m_observer_notify_listener); + } + m_local_jni_env->CallVoidMethod(m_row_notifier, m_clear_row_refs); +} + diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index a058691b3e..2632cd3806 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -31,9 +31,8 @@ class JavaBindingContext final : public BindingContext { private: struct ConcreteJavaBindContext { JNIEnv* jni_env; - jobject java_notifier; - explicit ConcreteJavaBindContext(JNIEnv* env, jobject notifier) - :jni_env(env), java_notifier(notifier) { } + jobject realm_notifier; + jobject row_notifier; }; // The JNIEnv for the thread which creates the Realm. This should only be used on the current thread. @@ -43,13 +42,27 @@ class JavaBindingContext final : public BindingContext { JavaVM* m_jvm; // A weak global ref to the implementation of RealmNotifier // Java should hold a strong ref to it as long as the SharedRealm lives - jobject m_java_notifier; + jobject m_realm_notifier; // Method IDs from RealmNotifier implementation. Cache them as member vars. jmethodID m_notify_by_other_method; + // A weak global ref to the RowNotifier object. Java should hold a strong ref to it. + jobject m_row_notifier; + // RowNotifier.getObservers() + jmethodID m_get_observers_method; + // RowNotifier.getObservedRowPtrs(Observer[]) + jmethodID m_get_observed_row_ptrs_method; + // RowNotifier.clearRowRefs() + jmethodID m_clear_row_refs; + jmethodID m_observer_notify_listener; public: virtual ~JavaBindingContext(); virtual void changes_available(); + virtual std::vector get_observed_rows(); + virtual void did_change(std::vector const& observers, + std::vector const& invalidated, + bool version_changed=true); + explicit JavaBindingContext(const ConcreteJavaBindContext&); JavaBindingContext(const JavaBindingContext&) = delete; @@ -57,9 +70,9 @@ class JavaBindingContext final : public BindingContext { JavaBindingContext(JavaBindingContext&&) = delete; JavaBindingContext& operator=(JavaBindingContext&&) = delete; - static inline std::unique_ptr create(JNIEnv* env, jobject notifier) + static inline std::unique_ptr create(JNIEnv* env, jobject notifier, jobject row_notifier) { - return std::make_unique(ConcreteJavaBindContext{env, notifier}); + return std::make_unique(ConcreteJavaBindContext{env, notifier, row_notifier}); }; }; diff --git a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java index adc0c06f6c..c2949528b0 100644 --- a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java @@ -55,12 +55,14 @@ public void notifyCommitByLocalThread() { // event starved as it only starts handling Realm events instead. This is an acceptable risk as // that behaviour indicate a user bug. Previously this would be hidden as the UI would still // be responsive. + /* Message msg = Message.obtain(); msg.what = HandlerControllerConstants.LOCAL_COMMIT; if (!handler.hasMessages(HandlerControllerConstants.LOCAL_COMMIT)) { handler.removeMessages(HandlerControllerConstants.REALM_CHANGED); handler.sendMessageAtFrontOfQueue(msg); } + */ } // This is called by OS when other thread/process changes the Realm. diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java index 752fdc1be6..a0b06bc580 100644 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java @@ -164,7 +164,7 @@ public void handleAsyncTransactionCompleted(Runnable onSuccess) { if (onSuccess != null) { pendingOnSuccessAsyncTransactionCallbacks.add(onSuccess); } - realmChanged(false); + //realmChanged(false); } } diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 3e67066919..c36f12d6a8 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -22,8 +22,10 @@ import io.realm.internal.PendingRow; import io.realm.internal.Row; +import io.realm.internal.RowNotifier; import io.realm.internal.Table; import io.realm.internal.TableQuery; +import io.realm.internal.UncheckedRow; import io.realm.log.RealmLog; /** @@ -170,6 +172,21 @@ public ProxyState(Class clazzName, E model) { } } + public void addChangeListener(RealmChangeListener listener) { + if (!listeners.contains(listener)) { + listeners.add(listener); + } + if (row instanceof UncheckedRow) { + RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; + rowNotifier.registerListener((UncheckedRow) row, new RealmChangeListener>() { + @Override + public void onChange(ProxyState proxyState) { + proxyState.notifyChangeListeners$realm(); + } + }, this); + } + } + public void setTableVersion$realm() { if (row.getTable() != null) { currentTableVersion = row.getTable().getVersion(); @@ -208,5 +225,16 @@ public void onQueryFinished(Row row, boolean asyncQuery) { if (asyncQuery) { notifyChangeListeners$realm(); } + // FIXME: Figure out why this can be null. + if (realm.sharedRealm == null) { + return; + } + RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; + rowNotifier.registerListener((UncheckedRow) row, new RealmChangeListener>() { + @Override + public void onChange(ProxyState proxyState) { + proxyState.notifyChangeListeners$realm(); + } + }, this); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index ed03ec6e4b..4d3a6b0804 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -355,16 +355,8 @@ public static void addChangeListener(E object, RealmChang if (!realm.handlerController.isAutoRefreshEnabled()) { throw new IllegalStateException("You can't register a listener from a non-Looper thread or IntentService thread."); } - List listeners = proxy.realmGet$proxyState().getListeners$realm(); - if (!listeners.contains(listener)) { - listeners.add(listener); - } - if (isLoaded(proxy)) { - // Try to add this object to the realmObjects if it has already been loaded. - // For newly created async objects, it will be handled in RealmQuery.findFirstAsync & - // HandlerController.completedAsyncRealmObject. - realm.handlerController.addToRealmObjects(proxy); - } + //noinspection unchecked + proxy.realmGet$proxyState().addChangeListener(listener); } else { throw new IllegalArgumentException("Cannot add listener from this unmanaged RealmObject (created outside of Realm)"); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 0e20605374..337e72d985 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -28,6 +28,7 @@ import io.realm.internal.PendingRow; import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.SortDescriptor; import io.realm.internal.Table; @@ -1647,24 +1648,34 @@ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, public E findFirst() { checkQueryIsNotReused(); - // TODO: The performance by the pending query will be a little bit worse than directly calling core's - // Query.find(). The overhead comes with core needs to add all the row indices to the vector. However this can - // be optimized by adding support of limit in OS's Results which is supported by core already. - PendingRow pendingRow = new PendingRow(realm.sharedRealm, query, null); - // prepare an empty reference of the RealmObject, so we can return it immediately (promise) - // then update it once the query complete in the background. + Row row; + if (realm.isInTransaction()) { + // It is not possible to create async query inside a transaction. So immediately query the first object. + // See OS Results::prepare_async() + row = new Collection(realm.sharedRealm, query).firstUncheckedRow(); + } else { + // prepare an empty reference of the RealmObject which is backed by a pending query, + // then update it once the query complete in the background. + + // TODO: The performance by the pending query will be a little bit worse than directly calling core's + // Query.find(). The overhead comes with core needs to add all the row indices to the vector. However this + // can be optimized by adding support of limit in OS's Results which is supported by core already. + row = new PendingRow(realm.sharedRealm, query, null); + } final E result; if (isDynamicQuery()) { //noinspection unchecked - result = (E) new DynamicRealmObject(className, realm, pendingRow); + result = (E) new DynamicRealmObject(className, realm, row); } else { result = realm.getConfiguration().getSchemaMediator().newInstance( - clazz, realm, pendingRow, realm.getSchema().getColumnInfo(clazz), + clazz, realm, row, realm.getSchema().getColumnInfo(clazz), false, Collections.emptyList()); } - final RealmObjectProxy proxy = (RealmObjectProxy) result; - pendingRow.setFrontEnd(proxy.realmGet$proxyState()); + if (row instanceof PendingRow) { + final RealmObjectProxy proxy = (RealmObjectProxy) result; + ((PendingRow) row).setFrontEnd(proxy.realmGet$proxyState()); + } return result; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java new file mode 100644 index 0000000000..4e6f2b7b08 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; + +import io.realm.RealmChangeListener; + +public class RowNotifier { + + private static class Observer { + final RealmChangeListener listener; + final Object object; + UncheckedRow row; + Observer(RealmChangeListener listener, Object object) { + this.listener = listener; + this.object = object; + this.row = null; + } + public void notifyListener() { + listener.onChange(object); + } + } + + // FIXME: Use weak ref for the key. And make the memory ownership clear in the doc. + Map rowObserverMap = new HashMap<>(); + + public void registerListener(UncheckedRow row, RealmChangeListener listener, Object object) { + Observer observer = new Observer(listener, object); + rowObserverMap.put(row, observer); + } + + // Called by JNI + @SuppressWarnings("unused") + private Observer[] getObservers() { + Observer[] observers = new Observer[rowObserverMap.size()]; + int i = 0; + for (Map.Entry entry : rowObserverMap.entrySet()) { + observers[i] = entry.getValue(); + observers[i].row = entry.getKey(); + } + return observers; + } + + // Called by JNI + @SuppressWarnings("unused") + private long[] getObservedRowPtrs(Observer[] observers) { + long[] ptrs = new long[observers.length]; + for (int i = 0; i < observers.length; i++) { + ptrs[i] = observers[i].row.getNativePtr(); + } + return ptrs; + } + + // Called by JNI + @SuppressWarnings("unused") + private void clearRowRefs() { + for (Observer observer : rowObserverMap.values()) { + observer.row = null; + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 9f9cf2d509..74110fb746 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -102,6 +102,7 @@ public byte getNativeValue() { // JNI will only hold a weak global ref to this. public final RealmNotifier realmNotifier; + public final RowNotifier rowNotifier; public final ObjectServerFacade objectServerFacade; public static class VersionID implements Comparable { @@ -168,10 +169,11 @@ public interface SchemaVersionListener { private final SchemaVersionListener schemaChangeListener; private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotifier notifier, - SchemaVersionListener schemaVersionListener) { + RowNotifier rowNotifier, SchemaVersionListener schemaVersionListener) { this.nativePtr = nativePtr; this.configuration = configuration; this.realmNotifier = notifier; + this.rowNotifier = rowNotifier; this.schemaChangeListener = schemaVersionListener; context = new Context(); this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion(); @@ -200,11 +202,13 @@ public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier r autoChangeNotifications, rosServerUrl, rosUserToken); + RowNotifier rowNotifier = new RowNotifier(); try { return new SharedRealm( - nativeGetSharedRealm(nativeConfigPtr, realmNotifier), + nativeGetSharedRealm(nativeConfigPtr, realmNotifier, rowNotifier), config, realmNotifier, + rowNotifier, schemaVersionListener); } finally { nativeCloseConfig(nativeConfigPtr); @@ -374,7 +378,8 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte boolean autoChangeNotification, String syncServerURL, String syncUserToken); private static native void nativeCloseConfig(long nativeConfigPtr); - private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier); + private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier, + RowNotifier rowNotifier); private static native void nativeCloseSharedRealm(long nativeSharedRealmPtr); private static native boolean nativeIsClosed(long nativeSharedRealmPtr); private static native void nativeBeginTransaction(long nativeSharedRealmPtr); From b656da2fc6cd639ada6236379087a2581b7a5939 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 5 Dec 2016 12:37:28 +0800 Subject: [PATCH 0261/2110] Remove TableView and TableOrView YEAH! --- .../java/io/realm/RealmQueryTests.java | 25 + .../androidTest/java/io/realm/SortTest.java | 1 - .../androidTest/java/io/realm/TestHelper.java | 2 +- .../io/realm/internal/CollectionTests.java | 17 + .../io/realm/internal/JNIColumnInfoTest.java | 14 +- .../io/realm/internal/JNIDistinctTest.java | 91 -- .../java/io/realm/internal/JNIQueryTest.java | 263 +++-- .../io/realm/internal/JNISortedLongTest.java | 10 +- .../java/io/realm/internal/JNITableTest.java | 2 - .../java/io/realm/internal/PivotTest.java | 6 +- .../internal/TableIndexAndDistinctTest.java | 38 +- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../main/cpp/io_realm_internal_Collection.cpp | 7 +- .../src/main/cpp/io_realm_internal_Table.cpp | 87 -- .../main/cpp/io_realm_internal_TableQuery.cpp | 426 +------- .../main/cpp/io_realm_internal_TableView.cpp | 993 ------------------ .../java/io/realm/internal/Collection.java | 1 + .../main/java/io/realm/internal/Table.java | 132 +-- .../java/io/realm/internal/TableOrView.java | 376 ------- .../java/io/realm/internal/TableQuery.java | 98 +- .../java/io/realm/internal/TableView.java | 813 -------------- .../realm/internal/async/QueryUpdateTask.java | 77 -- 22 files changed, 219 insertions(+), 3262 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/JNIDistinctTest.java delete mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/TableOrView.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/TableView.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index d23e6796c1..85242d62b8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -263,6 +263,21 @@ public void or() { assertEquals(22, resultList.size()); } + @Test(expected = UnsupportedOperationException.class) + public void or_missingFilters() { + realm.where(AllTypes.class).or().findAll(); + } + + @Test(expected = UnsupportedOperationException.class) + public void or_missingFilterBefore() { + realm.where(AllTypes.class).or().equalTo(AllTypes.FIELD_FLOAT, 31.234567f).findAll(); + } + + @Test(expected = UnsupportedOperationException.class) + public void or_missingFilterAfter() { + realm.where(AllTypes.class).or().equalTo(AllTypes.FIELD_FLOAT, 31.234567f).findAll(); + } + @Test public void not() { populateTestRealm(); // create TEST_DATA_SIZE objects @@ -3123,4 +3138,14 @@ public void distinctMultiArgs_invalidTypesLinkedFields() { } catch (IllegalArgumentException ignored) { } } + + @Test(expected = UnsupportedOperationException.class) + public void beginGroup_missingEndGroup() { + realm.where(AllTypes.class).beginGroup().findAll(); + } + + @Test(expected = UnsupportedOperationException.class) + public void endGroup_missingBeginGroup() { + realm.where(AllTypes.class).endGroup().findAll(); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 923f7d6f80..c6646a2bc6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -30,7 +30,6 @@ import java.util.concurrent.atomic.AtomicInteger; import io.realm.entities.AllTypes; -import io.realm.internal.TableView; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index f6a2299618..bd2af67222 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -843,7 +843,7 @@ public static void quitLooperOrFail() { * This helper method is useful to create a mocked {@link RealmResults}. * * @param realm a {@link Realm} or a {@link DynamicRealm} instance. - * @param table a {@link Table} or a {@link io.realm.internal.TableView} instance. + * @param collection a {@link Collection} instance. * @param tableClass a Class of Table. * @return a created {@link RealmResults} instance. */ diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index b78434de00..ec44babfe8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -84,6 +84,12 @@ private void populateData(Table table) { table.setLong(2, row, 1, false); } + @Test(expected = UnsupportedOperationException.class) + public void constructor_queryIsValidated() { + // Collection's constructor should call TableQuery.validateQuery() + new Collection(sharedRealm, table.where().or()); + } + @Test public void size() { Collection collection = new Collection(sharedRealm, table.where()); @@ -161,4 +167,15 @@ public void indexOf_long() { sortDescriptor.close(); } } + + @Test + public void distinct() { + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table, "firstName"); + Collection collection = new Collection(sharedRealm, table.where(), null, distinctDescriptor); + + assertEquals(collection.size(), 3); + assertEquals(collection.getUncheckedRow(0).getString(0), "John"); + assertEquals(collection.getUncheckedRow(1).getString(0), "Erik"); + assertEquals(collection.getUncheckedRow(2).getString(0), "Henry"); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java index 30c64ac29c..a44043f00b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java @@ -16,8 +16,11 @@ package io.realm.internal; +import android.support.test.InstrumentationRegistry; + import junit.framework.TestCase; +import io.realm.Realm; import io.realm.RealmFieldType; public class JNIColumnInfoTest extends TestCase { @@ -26,6 +29,7 @@ public class JNIColumnInfoTest extends TestCase { @Override public void setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); table = new Table(); table.addColumn(RealmFieldType.STRING, "firstName"); table.addColumn(RealmFieldType.STRING, "lastName"); @@ -45,15 +49,13 @@ public void testShouldGetColumnInformation() { public void testValidateColumnInfo() { - TableView view = table.where().findAll(); - - assertEquals(2, view.getColumnCount()); + assertEquals(2, table.getColumnCount()); - assertEquals("lastName", view.getColumnName(1)); + assertEquals("lastName", table.getColumnName(1)); - assertEquals(1, view.getColumnIndex("lastName")); + assertEquals(1, table.getColumnIndex("lastName")); - assertEquals(RealmFieldType.STRING, view.getColumnType(1)); + assertEquals(RealmFieldType.STRING, table.getColumnType(1)); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIDistinctTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIDistinctTest.java deleted file mode 100644 index 1759b9d8c4..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIDistinctTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -// TODO: Check that Index can be set on multiple columns. - -import junit.framework.TestCase; - -import io.realm.RealmFieldType; - -@SuppressWarnings("unused") -public class JNIDistinctTest extends TestCase { - Table table; - - void init() { - table = new Table(); - table.addColumn(RealmFieldType.INTEGER, "number"); - table.addColumn(RealmFieldType.STRING, "name"); - - long i = 0; - table.add(0, "A"); - table.add(1, "B"); - table.add(2, "C"); - table.add(3, "B"); - table.add(4, "D"); - table.add(5, "D"); - table.add(6, "D"); - assertEquals(7, table.size()); - } - - public void testShouldTestDistinct() { - init(); - - // Must set index before using distinct() - table.addSearchIndex(1); - assertEquals(true, table.hasSearchIndex(1)); - - TableView view = table.getDistinctView(1); - assertEquals(4, view.size()); - assertEquals(0, view.getLong(0, 0)); - assertEquals(1, view.getLong(0, 1)); - assertEquals(2, view.getLong(0, 2)); - assertEquals(4, view.getLong(0, 3)); - } - - public void testShouldTestDistinctErrorWhenNoIndex() { - init(); - try { - TableView view = table.getDistinctView(1); - fail(); - } catch (UnsupportedOperationException e) { - assertNotNull(e); - } - } - - public void testShouldTestDistinctErrorWhenIndexOutOfBounds() { - init(); - try { - TableView view = table.getDistinctView(3); - fail(); - } catch (Exception e) { - assertNotNull(e); - } - } - - public void testShouldTestDistinctErrorWhenWrongColumnType() { - init(); - table.addSearchIndex(1); - try { - TableView view = table.getDistinctView(0); - fail(); - } catch (Exception e) { - assertNotNull(e); - } - } - -} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index b94f545227..807600e9b3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -16,12 +16,15 @@ package io.realm.internal; +import android.support.test.InstrumentationRegistry; + import junit.framework.TestCase; import java.util.Date; import java.util.concurrent.TimeUnit; import io.realm.Case; +import io.realm.Realm; import io.realm.RealmFieldType; import io.realm.Sort; import io.realm.TestHelper; @@ -30,6 +33,12 @@ public class JNIQueryTest extends TestCase { Table table; + @Override + protected void setUp() throws Exception { + super.setUp(); + Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); + } + void init() { table = new Table(); table.addColumn(RealmFieldType.INTEGER, "number"); @@ -71,38 +80,24 @@ public void testNonCompleteQuery() { init(); // All the following queries are not valid, e.g contain a group but not a closing group, an or() but not a second filter etc - try { table.where().equalTo(new long[]{0}, 1).or().findAll(); fail("missing a second filter"); } catch (UnsupportedOperationException ignore) {} - try { table.where().or().findAll(); fail("just an or()"); } catch (UnsupportedOperationException ignore) {} - try { table.where().group().equalTo(new long[]{0}, 1).findAll(); fail("missing a closing group"); } catch (UnsupportedOperationException ignore) {} + try { table.where().equalTo(new long[]{0}, 1).or().validateQuery(); fail("missing a second filter"); } catch (UnsupportedOperationException ignore) {} + try { table.where().or().validateQuery(); fail("just an or()"); } catch (UnsupportedOperationException ignore) {} + try { table.where().group().equalTo(new long[]{0}, 1).validateQuery(); fail("missing a closing group"); } catch (UnsupportedOperationException ignore) {} try { table.where().group().count(); fail(); } catch (UnsupportedOperationException ignore) {} - try { table.where().group().findAll(); fail(); } catch (UnsupportedOperationException ignore) {} + try { table.where().group().validateQuery(); fail(); } catch (UnsupportedOperationException ignore) {} try { table.where().group().find(); fail(); } catch (UnsupportedOperationException ignore) {} try { table.where().group().minimumInt(0); fail(); } catch (UnsupportedOperationException ignore) {} try { table.where().group().maximumInt(0); fail(); } catch (UnsupportedOperationException ignore) {} try { table.where().group().sumInt(0); fail(); } catch (UnsupportedOperationException ignore) {} try { table.where().group().averageInt(0); fail(); } catch (UnsupportedOperationException ignore) {} - try { table.where().endGroup().equalTo(new long[]{0}, 1).findAll(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - try { table.where().equalTo(new long[]{0}, 1).endGroup().findAll(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} + try { table.where().endGroup().equalTo(new long[]{0}, 1).validateQuery(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} + try { table.where().equalTo(new long[]{0}, 1).endGroup().validateQuery(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} try { table.where().equalTo(new long[]{0}, 1).endGroup().find(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} try { table.where().equalTo(new long[]{0}, 1).endGroup().find(0); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} try { table.where().equalTo(new long[]{0}, 1).endGroup().find(1); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - - try { table.where().equalTo(new long[]{0}, 1).endGroup().findAll(0, -1, -1); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - - - - // step by step buildup - TableQuery q = table.where().equalTo(new long[]{0}, 1); // valid - q.findAll(); - q.or(); // not valid - try { q.findAll(); fail("no start group"); } catch (UnsupportedOperationException ignore) { } - q.equalTo(new long[]{0}, 100); // valid again - q.findAll(); - q.equalTo(new long[]{0}, 200); // still valid - q.findAll(); } public void testInvalidColumnIndexEqualTo() { @@ -110,45 +105,45 @@ public void testInvalidColumnIndexEqualTo() { TableQuery query = table.where(); // Boolean - try { query.equalTo(new long[]{-1}, true).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, true).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, true).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, true); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, true); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, true); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Date - try { query.equalTo(new long[]{-1}, new Date()).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, new Date()).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, new Date()).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.equalTo(new long[]{-1}, 4.5d).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, 4.5d).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, 4.5d).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.equalTo(new long[]{-1}, 1.4f).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, 1.4f).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, 1.4f).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.equalTo(new long[]{-1}, 1).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, 1).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, 1).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String - try { query.equalTo(new long[]{-1}, "a").findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, "a").findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, "a").findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case true - try { query.equalTo(new long[]{-1}, "a", Case.SENSITIVE).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, "a", Case.SENSITIVE).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, "a", Case.SENSITIVE).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, "a", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case false - try { query.equalTo(new long[]{-1}, "a", Case.INSENSITIVE).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, "a", Case.INSENSITIVE).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, "a", Case.INSENSITIVE).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } public void testInvalidColumnIndexNotEqualTo() { @@ -157,40 +152,40 @@ public void testInvalidColumnIndexNotEqualTo() { // Date - try { query.notEqualTo(new long[]{-1}, new Date()).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, new Date()).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, new Date()).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.notEqualTo(new long[]{-1}, 4.5d).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, 4.5d).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, 4.5d).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.notEqualTo(new long[]{-1}, 1.4f).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, 1.4f).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, 1.4f).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.notEqualTo(new long[]{-1}, 1).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, 1).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, 1).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String - try { query.notEqualTo(new long[]{-1}, "a").findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, "a").findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, "a").findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case true - try { query.notEqualTo(new long[]{-1}, "a", Case.SENSITIVE).findAll(); fail("-1column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, "a", Case.SENSITIVE).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, "a", Case.SENSITIVE).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, "a", Case.SENSITIVE); fail("-1column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case false - try { query.notEqualTo(new long[]{-1}, "a", Case.INSENSITIVE).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, "a", Case.INSENSITIVE).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, "a", Case.INSENSITIVE).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } @@ -199,25 +194,25 @@ public void testInvalidColumnIndexGreaterThan() { TableQuery query = table.where(); // Date - try { query.greaterThan(new long[]{-1}, new Date()).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, new Date()).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, new Date()).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.greaterThan(new long[]{-1}, 4.5d).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, 4.5d).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, 4.5d).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.greaterThan(new long[]{-1}, 1.4f).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, 1.4f).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, 1.4f).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.greaterThan(new long[]{-1}, 1).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, 1).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, 1).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } @@ -226,25 +221,25 @@ public void testInvalidColumnIndexGreaterThanOrEqual() { TableQuery query = table.where(); // Date - try { query.greaterThanOrEqual(new long[]{-1}, new Date()).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, new Date()).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, new Date()).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.greaterThanOrEqual(new long[]{-1}, 4.5d).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, 4.5d).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, 4.5d).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.greaterThanOrEqual(new long[]{-1}, 1.4f).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, 1.4f).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, 1.4f).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.greaterThanOrEqual(new long[]{-1}, 1).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, 1).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, 1).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } @@ -253,25 +248,25 @@ public void testInvalidColumnIndexLessThan() { TableQuery query = table.where(); // Date - try { query.lessThan(new long[]{-1}, new Date()).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, new Date()).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, new Date()).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.lessThan(new long[]{-1}, 4.5d).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, 4.5d).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, 4.5d).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.lessThan(new long[]{-1}, 1.4f).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, 1.4f).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, 1.4f).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.lessThan(new long[]{-1}, 1).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, 1).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, 1).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } public void testInvalidColumnIndexLessThanOrEqual() { @@ -279,25 +274,25 @@ public void testInvalidColumnIndexLessThanOrEqual() { TableQuery query = table.where(); // Date - try { query.lessThanOrEqual(new long[]{-1}, new Date()).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, new Date()).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, new Date()).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.lessThanOrEqual(new long[]{-1}, 4.5d).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, 4.5d).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, 4.5d).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.lessThanOrEqual(new long[]{-1}, 1.4f).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, 1.4f).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, 1.4f).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.lessThanOrEqual(new long[]{-1}, 1).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, 1).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, 1).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } @@ -306,25 +301,25 @@ public void testInvalidColumnIndexBetween() { TableQuery query = table.where(); // Date - try { query.between(new long[]{-1}, new Date(), new Date()).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{9}, new Date(), new Date()).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{10}, new Date(), new Date()).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{-1}, new Date(), new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{9}, new Date(), new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{10}, new Date(), new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.between(new long[]{-1}, 4.5d, 6.0d).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{9}, 4.5d, 6.0d).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{10}, 4.5d, 6.0d).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{-1}, 4.5d, 6.0d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{9}, 4.5d, 6.0d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{10}, 4.5d, 6.0d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.between(new long[]{-1}, 1.4f, 5.8f).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{9}, 1.4f, 5.8f).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{10}, 1.4f, 5.8f).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{-1}, 1.4f, 5.8f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{9}, 1.4f, 5.8f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{10}, 1.4f, 5.8f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.between(new long[]{-1}, 1, 10).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{9}, 1, 10).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{10}, 1, 10).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{-1}, 1, 10); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{9}, 1, 10); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{10}, 1, 10); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } @@ -333,19 +328,19 @@ public void testInvalidColumnIndexContains() { TableQuery query = table.where(); // String - try { query.contains(new long[]{-1}, "hey").findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{9}, "hey").findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{10}, "hey").findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{-1}, "hey"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{9}, "hey"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{10}, "hey"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case true - try { query.contains(new long[]{-1}, "hey", Case.SENSITIVE).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{9}, "hey", Case.SENSITIVE).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{10}, "hey", Case.SENSITIVE).findAll(); fail("-0 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{-1}, "hey", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{9}, "hey", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{10}, "hey", Case.SENSITIVE); fail("-0 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case false - try { query.contains(new long[]{-1}, "hey", Case.INSENSITIVE).findAll(); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{9}, "hey", Case.INSENSITIVE).findAll(); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{10}, "hey", Case.INSENSITIVE).findAll(); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{-1}, "hey", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{9}, "hey", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{10}, "hey", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } public void testNullInputQuery() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java index 4272b7599e..cda5ab4458 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java @@ -16,15 +16,18 @@ package io.realm.internal; +import android.support.test.InstrumentationRegistry; + import junit.framework.TestCase; +import io.realm.Realm; import io.realm.RealmFieldType; public class JNISortedLongTest extends TestCase { Table table; - TableView view; void init() { + Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); table = new Table(); table.addColumn(RealmFieldType.INTEGER, "number"); table.addColumn(RealmFieldType.STRING, "name"); @@ -39,11 +42,6 @@ void init() { table.add(60, "D"); assertEquals(8, table.size()); - - view = table.where().findAll(); - - assertEquals(view.size(), table.size()); - } public void testShouldTestSortedIntTable() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index 057b2f3009..6c2a9cf306 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -294,8 +294,6 @@ public void tableNumbers() { assertEquals(3, t.count(2, 3.0f)); assertEquals(3, t.count(3, "s1")); - assertEquals(3, t.findAllDouble(1, 2.0d).size()); - assertEquals(3, t.findAllFloat(2, 3.0f).size()); assertEquals(3, t.findFirstDouble(1, 20.0d)); // Find rows index for first double value of 20.0 in column 1 assertEquals(4, t.findFirstFloat(2, 300.0f)); // Find rows index for first float value of 300.0 in column 2 diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java index 1ee36c4c23..67006b5592 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java @@ -16,10 +16,13 @@ package io.realm.internal; +import android.support.test.InstrumentationRegistry; + import junit.framework.TestCase; +import io.realm.Realm; import io.realm.RealmFieldType; -import io.realm.internal.TableOrView.PivotType; +import io.realm.internal.Table.PivotType; public class PivotTest extends TestCase { @@ -30,6 +33,7 @@ public class PivotTest extends TestCase { @Override public void setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); t = new Table(); colIndexSex = t.addColumn(RealmFieldType.STRING, "sex"); colIndexAge = t.addColumn(RealmFieldType.INTEGER, "age"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java index 751158bc98..d2a64b2e2a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java @@ -39,21 +39,7 @@ void init() { assertEquals(7, table.size()); } - public void testShouldTestDistinct() { - init(); - - // Must set index before using distinct() - table.addSearchIndex(1); - assertEquals(true, table.hasSearchIndex(1)); - - TableView view = table.getDistinctView(1); - assertEquals(4, view.size()); - assertEquals(0, view.getLong(0, 0)); - assertEquals(1, view.getLong(0, 1)); - assertEquals(2, view.getLong(0, 2)); - assertEquals(4, view.getLong(0, 3)); - } - + // FIXME: Check or delete this. // TODO: parametric test /* *//** * Should throw exception if trying to get distinct on columns where index has not been set @@ -69,17 +55,6 @@ public void shouldTestDistinctErrorWhenNoIndex(Long index) { TableView view = table.getDistinctView(1); }*/ - public void testShouldTestDistinctErrorWhenIndexOutOfBounds() { - init(); - - try { - TableView view = table.getDistinctView(3); - fail(); - } catch (ArrayIndexOutOfBoundsException e) { - assertNotNull(e); - } - } - /** * Check that Index can be set on multiple columns, with the String * @param @@ -131,17 +106,6 @@ public void testShouldCheckIndexIsOkOnColumn() { table.addSearchIndex(1); } - public void testShouldThrowDistinctErrorWhenWrongColumnType() { - init(); - table.addSearchIndex(1); - try { - TableView view = table.getDistinctView(0); - fail(); - } catch (UnsupportedOperationException e) { - assertNotNull(e); - } - } - public void testRemoveSearchIndex() { init(); table.addSearchIndex(1); diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index d66571c67f..27f52bdaa6 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -33,7 +33,7 @@ endif() string(TOLOWER ${CMAKE_BUILD_TYPE} build_type_FOLDER) set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/classes/${REALM_FLAVOR}/${build_type_FOLDER}/) set(classes_LIST - io.realm.internal.Table io.realm.internal.TableView io.realm.internal.CheckedRow + io.realm.internal.Table io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index cf6fc49620..c6c089c260 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -51,8 +51,13 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong { TR_ENTER() try { - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); auto query = reinterpret_cast(query_ptr); + /* FIXME: Add check here + if (!query_va(env, query) || !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) + return nullptr; + */ + + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); auto sort_desc_ptr = reinterpret_cast(sort_desc_native_ptr); auto distinct_desc_ptr = reinterpret_cast(distinct_desc_native_ptr); auto results = new Results(shared_realm, *query, diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 9c697bdca5..fa1f66e3ff 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1178,52 +1178,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull( // FindAll -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllInt( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong value) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) - return 0; - try { - TableView* pTableView = new TableView( TBL(nativeTablePtr)->find_all_int( S(columnIndex), value) ); - return reinterpret_cast(pTableView); - } CATCH_STD() - return 0; -} -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllFloat( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jfloat value) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) - return 0; - try { - TableView* pTableView = new TableView( TBL(nativeTablePtr)->find_all_float( S(columnIndex), value) ); - return reinterpret_cast(pTableView); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllDouble( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jdouble value) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) - return 0; - try { - TableView* pTableView = new TableView( TBL(nativeTablePtr)->find_all_double( S(columnIndex), value) ); - return reinterpret_cast(pTableView); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllBool( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jboolean value) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Bool)) - return 0; - TableView* pTableView = new TableView( TBL(nativeTablePtr)->find_all_bool( S(columnIndex), - value != 0 ? true : false) ); - return reinterpret_cast(pTableView); -} // FIXME: reenable when find_first_timestamp() is implemented /* @@ -1240,20 +1196,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllTimestamp( } */ -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllString( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jstring value) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_String)) - return 0; - - Table* pTable = TBL(nativeTablePtr); - try { - JStringAccessor value2(env, value); // throws - TableView* pTableView = new TableView( pTable->find_all_string( S(columnIndex), value2) ); - return reinterpret_cast(pTableView); - } CATCH_STD() - return 0; -} // experimental @@ -1287,35 +1229,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeUpperBoundInt( // -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetDistinctView( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) -{ - Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) - return 0; - if (!pTable->has_search_index(S(columnIndex))) { - ThrowException(env, UnsupportedOperation, "The field must be indexed before distinct() can be used."); - return 0; - } - switch (pTable->get_column_type(S(columnIndex))) { - case type_Bool: - case type_Int: - case type_String: - case type_Timestamp: - try { - TableView* pTableView = new TableView( pTable->get_distinct_view(S(columnIndex)) ); - return reinterpret_cast(pTableView); - } CATCH_STD() - break; - default: - ThrowException(env, IllegalArgument, "Invalid type - Only String, Date, boolean, byte, short, int, long and their boxed variants are supported."); - return 0; - break; - } - return 0; -} - - JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti( JNIEnv *env, jobject, jlong nativeTablePtr, jlongArray columnIndices, jbooleanArray ascending) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 690bb7b3d2..6ad3403f6c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -14,12 +14,16 @@ * limitations under the License. */ +#include "io_realm_internal_TableQuery.h" + #include #include + #include #include +#include + #include "util.hpp" -#include "io_realm_internal_TableQuery.h" using namespace realm; @@ -82,156 +86,6 @@ static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& indicesArray return table_ref; } -static jlong findAllWithHandover(JNIEnv* env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong start, jlong end, jlong limit) -{ - TR_ENTER() - TableRef table = query.get()->get_table(); - if (!QUERY_VALID(env, query.get()) || - !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) { - return 0; - } - // run the query - TableView tableView(query->find_all(S(start), S(end), S(limit))); - - // handover the result - auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); - using rf = realm::_impl::RealmFriend; - auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(tableView, MutableSourcePayload::Move); - return reinterpret_cast(handover.release()); -} - -static jlong getDistinctViewWithHandover - (JNIEnv *env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong columnIndex) -{ - TableRef table = query->get_table(); - if (!QUERY_VALID(env, query.get()) || - !TBL_AND_COL_INDEX_VALID(env, table.get(), columnIndex)) { - return 0; - } - switch (table->get_column_type(S(columnIndex))) { - case type_Bool: - case type_Int: - case type_Timestamp: - case type_String: { - TableView tableView(query->find_all()); - tableView.distinct(S(columnIndex)); - - // handover the result - auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); - using rf = realm::_impl::RealmFriend; - auto handover = rf::get_shared_group(*sharedRealm).export_for_handover( - tableView, MutableSourcePayload::Move); - return reinterpret_cast(handover.release()); - } - default: - ThrowException(env, IllegalArgument, "Invalid type - Only String, Date, boolean, short, int, long and their boxed variants are supported."); - return 0; - } - return 0; -} - -static jlong findAllSortedWithHandover - (JNIEnv *env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong start, jlong end, jlong limit, jlong columnIndex, jboolean ascending) -{ - TableRef table = query->get_table(); - - if (!(QUERY_VALID(env, query.get()) && ROW_INDEXES_VALID(env, table.get(), start, end, limit))) { - return 0; - } - - // run the query - TableView tableView( query->find_all(S(start), S(end), S(limit)) ); - - // sorting the results - if (!COL_INDEX_VALID(env, &tableView, columnIndex)) { - return 0; - } - - int colType = tableView.get_column_type( S(columnIndex) ); - switch (colType) { - case type_Bool: - case type_Int: - case type_Float: - case type_Double: - case type_String: - case type_Timestamp: - tableView.sort( S(columnIndex), ascending != 0); - break; - default: - ThrowException(env, IllegalArgument, ERR_SORT_NOT_SUPPORTED); - return 0; - } - - // handover the result - auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); - using rf = realm::_impl::RealmFriend; - auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(tableView, MutableSourcePayload::Move); - return reinterpret_cast(handover.release()); -} - -static jlong findAllMultiSortedWithHandover - (JNIEnv *env, jlong bgSharedRealmPtr, std::unique_ptr query, jlong start, jlong end, jlong limit, jlongArray columnIndices, jbooleanArray ascending) -{ - JniLongArray long_arr(env, columnIndices); - JniBooleanArray bool_arr(env, ascending); - jsize arr_len = long_arr.len(); - jsize asc_len = bool_arr.len(); - - if (arr_len == 0) { - ThrowException(env, IllegalArgument, "You must provide at least one field name."); - return 0; - } - if (asc_len == 0) { - ThrowException(env, IllegalArgument, "You must provide at least one sort order."); - return 0; - } - if (arr_len != asc_len) { - ThrowException(env, IllegalArgument, "Number of fields and sort orders do not match."); - return 0; - } - - TableRef table = query->get_table(); - - if (!QUERY_VALID(env, query.get()) || !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) { - return 0; - } - - // run the query - TableView tableView( query->find_all(S(start), S(end), S(limit)) ); - - // sorting the results - std::vector> indices; - std::vector ascendings; - for (int i = 0; i < arr_len; ++i) { - if (!COL_INDEX_VALID(env, &tableView, long_arr[i])) { - return -1; - } - int colType = tableView.get_column_type( S(long_arr[i]) ); - switch (colType) { - case type_Bool: - case type_Int: - case type_Float: - case type_Double: - case type_String: - case type_Timestamp: - indices.push_back(std::vector { S(long_arr[i]) }); - ascendings.push_back( B(bool_arr[i]) ); - break; - default: - ThrowException(env, IllegalArgument, ERR_SORT_NOT_SUPPORTED); - return 0; - } - } - - tableView.sort(SortDescriptor(*table, indices, ascendings)); - - // handover the result - auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); - using rf = realm::_impl::RealmFriend; - auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(tableView, MutableSourcePayload::Move); - return reinterpret_cast(handover.release()); -} - template Query numeric_link_equal(TableRef tbl, jlong columnIndex, javatype value) { return tbl->column(size_t(columnIndex)) == cpptype(value); @@ -991,17 +845,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3B // as they are called for each method when building up the query. // Consider to reduce to just the "action" methods on Query -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeTableview( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlong nativeTableViewPtr) -{ - Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) - return; - try { - pQuery->get_table()->where(TV(nativeTableViewPtr)); - } CATCH_STD() -} - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGroup( JNIEnv* env, jobject, jlong nativeQueryPtr) { @@ -1071,65 +914,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFind( return -1; } -// Returns a pointer to query on the worker SharedRealm or throw a BadVersion if the SharedRealm version required -// for the handover is no longer available. -static std::unique_ptr handoverQueryToWorker(jlong bgSharedRealmPtr, jlong queryPtr, bool advanceToLatestVersion) -{ - SharedGroup::Handover *handoverQueryPtr = HO(Query, queryPtr); - std::unique_ptr> handoverQuery(handoverQueryPtr); - - // The Handover object doesn't prevent a SharedGroup version from no longer being accessible. In rare - // cases this means that the version in the Handover object is invalid and Realm Core will throw a - // BadVersion as result. - auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); - using rf = realm::_impl::RealmFriend; - rf::read_group_to(*sharedRealm, handoverQuery->version); - auto query = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverQuery)); - - if (advanceToLatestVersion) { - sharedRealm->refresh(); - } - - return query; -} - -// queryPtr would be owned and released by this function -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindWithHandover( - JNIEnv* env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong fromTableRow) -{ - TR_ENTER() - try { - std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, false); // throws - TableRef table = query->get_table(); - - if (!QUERY_VALID(env, query.get())) { - return 0; - } - - // It's valid to go 1 past the end index - if ((fromTableRow < 0) || (S(fromTableRow) > table->size())) { - // below check will fail with appropriate exception - (void) ROW_INDEX_VALID(env, table.get(), fromTableRow); - return 0; - } - - size_t r = query->find(S(fromTableRow)); - if (r == not_found) { - return 0; - } else { - // handover the result - Row row = (*table)[r]; - auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); - using rf = realm::_impl::RealmFriend; - auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(row); - return reinterpret_cast(handover.release()); - } - - } CATCH_STD() - return 0; -} - - JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll( JNIEnv* env, jobject, jlong nativeQueryPtr, jlong start, jlong end, jlong limit) { @@ -1146,178 +930,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll( return -1; } -// queryPtr would be owned and released by this function -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllWithHandover - (JNIEnv* env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit) - { - TR_ENTER() - try { - std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws - return findAllWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit); - } CATCH_STD() - return 0; - } - - - -// Should match the values in Java ArgumentsHolder class -enum query_type {QUERY_TYPE_FIND_ALL = 0, QUERY_TYPE_DISTINCT = 4, QUERY_TYPE_FIND_ALL_SORTED = 1, QUERY_TYPE_FIND_ALL_MULTI_SORTED = 2}; - -// batch update of async queries -JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdateQueries - (JNIEnv *env, jclass, jlong bgSharedRealmPtr, - jlongArray handover_queries_array /*list of handover queries*/, - jobjectArray query_param_matrix /*type & params of the query to be updated*/, - jobjectArray multi_sorted_indices_matrix, - jobjectArray multi_sorted_order_matrix) -{ - TR_ENTER() - try { - JniLongArray handover_queries_pointer_array(env, handover_queries_array); - - const size_t number_of_queries = env->GetArrayLength(query_param_matrix); - - std::vector exported_handover_tableview_array(number_of_queries); - - // Step1: Position the shared group at the handover query version so we can import all queries - // read the first query to determine the version we should use - SharedGroup::Handover *handoverQueryPtr = HO(Query, handover_queries_pointer_array[0]); - std::unique_ptr> handoverQuery(handoverQueryPtr); - - // if the SharedGroup is not in Read Transaction, we position it at the same version as the handover - // The Handover object doesn't prevent a SharedGroup version from no longer being accessible. In rare - // cases this means that the version in the Handover object is invalid and Realm Core will throw a - // BadVersion as result. - auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); - using rf = realm::_impl::RealmFriend; - rf::read_group_to(*sharedRealm, handoverQuery->version); - - std::vector> queries(number_of_queries); - - // import the first query - queries[0] = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverQuery)); - - // import the rest of the queries - for (size_t i = 1; i < number_of_queries; ++i) { - std::unique_ptr> handoverQuery(HO(Query, handover_queries_pointer_array[i])); - using rf = realm::_impl::RealmFriend; - queries[i] = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverQuery)); - } - - // Step2: Bring the queries into the latest shared group version - sharedRealm->refresh(); - - // Step3: Run & export the queries against the latest shared group - for (size_t i = 0; i < number_of_queries; ++i) { - // Delete the local ref since we might have a long loop - JniLocalRef local_ref(env, (jlongArray) env->GetObjectArrayElement(query_param_matrix, i)); - JniLongArray query_param_array(env, local_ref); - - switch (query_param_array[0]) { // 0, index of the type of query, the next indicies are parameters - case QUERY_TYPE_FIND_ALL: {// nativeFindAllWithHandover - exported_handover_tableview_array[i] = - findAllWithHandover - (env, - bgSharedRealmPtr, - std::move(queries[i]), - query_param_array[1]/*start*/, - query_param_array[2]/*end*/, - query_param_array[3]/*limit*/); - break; - } - case QUERY_TYPE_DISTINCT: {// nativeGetDistinctViewWithHandover - exported_handover_tableview_array[i] = - getDistinctViewWithHandover - (env, - bgSharedRealmPtr, - std::move(queries[i]), - query_param_array[1]/*columnIndex*/); - break; - } - case QUERY_TYPE_FIND_ALL_SORTED: {// nativeFindAllSortedWithHandover - exported_handover_tableview_array[i] = - findAllSortedWithHandover - (env, - bgSharedRealmPtr, - std::move(queries[i]), - query_param_array[1]/*start*/, - query_param_array[2]/*end*/, - query_param_array[3]/*limit*/, - query_param_array[4]/*columnIndex*/, - query_param_array[5] == 1/*ascending order*/); - break; - } - case QUERY_TYPE_FIND_ALL_MULTI_SORTED: {// nativeFindAllMultiSortedWithHandover - jlongArray column_indices_array = (jlongArray) env->GetObjectArrayElement( - multi_sorted_indices_matrix, i); - jbooleanArray column_order_array = (jbooleanArray) env->GetObjectArrayElement( - multi_sorted_order_matrix, i); - exported_handover_tableview_array[i] = - findAllMultiSortedWithHandover - (env, - bgSharedRealmPtr, - std::move(queries[i]), - query_param_array[1]/*start*/, - query_param_array[2]/*end*/, - query_param_array[3]/*limit*/, - column_indices_array/*columnIndices*/, - column_order_array/*ascending orders*/); - break; - } - default: - ThrowException(env, FatalError, "Unknown type of query."); - return NULL; - } - } - - jlongArray exported_handover_tableview = env->NewLongArray(number_of_queries); - if (exported_handover_tableview == NULL) { - ThrowException(env, OutOfMemory, "Could not allocate memory to return updated queries."); - return NULL; - } - env->SetLongArrayRegion(exported_handover_tableview, 0, number_of_queries, - exported_handover_tableview_array.data()); - return exported_handover_tableview; - - } CATCH_STD() - return NULL; -} - - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetDistinctViewWithHandover - (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong columnIndex) -{ - TR_ENTER() - try { - std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws - return getDistinctViewWithHandover(env, bgSharedRealmPtr, std::move(query), columnIndex); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllSortedWithHandover - (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlong columnIndex, jboolean ascending) - { - TR_ENTER() - try { - std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws - return findAllSortedWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit, columnIndex, ascending); - } CATCH_STD() - return 0; - } - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAllMultiSortedWithHandover - (JNIEnv *env, jclass, jlong bgSharedRealmPtr, jlong queryPtr, jlong start, jlong end, jlong limit, jlongArray columnIndices, jbooleanArray ascending) - { - TR_ENTER() - try { - // import the handover query pointer using the background SharedRealm - std::unique_ptr query = handoverQueryToWorker(bgSharedRealmPtr, queryPtr, true); // throws - return findAllMultiSortedWithHandover(env, bgSharedRealmPtr, std::move(query), start, end, limit,columnIndices, ascending); - } CATCH_STD() - return 0; - } - // Integer Aggregates JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeSumInt( @@ -1703,27 +1315,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull( } CATCH_STD() } -// handoverPtr will be released in this function -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverTableViewIntoSharedGroup - (JNIEnv *env, jobject, jlong handoverPtr, jlong callerSharedGrpPtr) - { - TR_ENTER_PTR(handoverPtr) - SharedGroup::Handover *handoverTableViewPtr = HO(TableView, handoverPtr); - std::unique_ptr> handoverTableView(handoverTableViewPtr); - try { - // import_from_handover will free (delete) the handover - auto sharedRealm = *(reinterpret_cast(callerSharedGrpPtr)); - if (!sharedRealm->is_closed()) { - using rf = realm::_impl::RealmFriend; - auto tableView = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverTableView)); - return reinterpret_cast(tableView.release()); - } else { - ThrowException(env, RuntimeError, ERR_IMPORT_CLOSED_REALM); - } - } CATCH_STD() - return 0; - } - JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRowIntoSharedGroup (JNIEnv *env, jclass, jlong handoverPtr, jlong callerSharedGrpPtr) { @@ -1762,13 +1353,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeCloseQueryHandover - (JNIEnv*, jclass, jlong nativeHandoverQuery) - { - TR_ENTER_PTR(nativeHandoverQuery) - delete HO(Query, nativeHandoverQuery); - } - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull (JNIEnv *env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes) { JniLongArray arr(env, columnIndexes); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp deleted file mode 100644 index 563054f643..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableView.cpp +++ /dev/null @@ -1,993 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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 "util.hpp" -#include "tablebase_tpl.hpp" -#include "io_realm_internal_TableView.h" -#include "realm/array.hpp" -#include - -using namespace realm; - -// if you disable the validation, please remember to call sync_in_needed() -#define VIEW_VALID_AND_IN_SYNC(env, ptr) view_valid_and_in_sync(env, ptr) - -static void finalize_table_view(jlong ptr); - -inline bool view_valid_and_in_sync(JNIEnv* env, jlong nativeViewPtr) { - bool valid = (TV(nativeViewPtr) != NULL); - if (valid) { - if (!TV(nativeViewPtr)->is_attached()) { - ThrowException(env, IllegalState, "The Realm has been closed and is no longer accessible."); - return false; - } - // depends_on_deleted_linklist() will return true if and only if the current TableView was created from a - // query on a RealmList and that RealmList was then deleted (as a result of the object being deleted). - if (!TV(nativeViewPtr)->is_in_sync() && TV(nativeViewPtr)->depends_on_deleted_object()) { - // This table view is no longer valid. By calling sync_if_needed we ensure it behaves - // properly as a 0-size TableView. - TV(nativeViewPtr)->sync_if_needed(); - } - } - return valid; -} - - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_createNativeTableView( - JNIEnv* env, jobject, jobject, jlong) -{ - try { - return reinterpret_cast( new TableView() ); - } CATCH_STD() - return 0; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeDistinct( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return; - if (!COL_INDEX_VALID(env, TV(nativeViewPtr), columnIndex)) - return; - if (!TV(nativeViewPtr)->get_parent().has_search_index(S(columnIndex))) { - ThrowException(env, UnsupportedOperation, "The field must be indexed before distinct() can be used."); - return; - } - try { - switch (TV(nativeViewPtr)->get_column_type(S(columnIndex))) { - case type_Bool: - case type_Int: - case type_String: - case type_Timestamp: - TV(nativeViewPtr)->distinct(S(columnIndex)); - break; - default: - ThrowException(env, IllegalArgument, "Invalid type - Only String, Date, boolean, byte, short, int, long and their boxed variants are supported."); - break; - } - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeDistinctMulti( - JNIEnv* env, jobject, jlong nativeViewPtr, jlongArray columnIndexes) -{ - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return; - try { - TableView* tv = TV(nativeViewPtr); - JniLongArray indexes(env, columnIndexes); - jsize indexes_len = indexes.len(); - std::vector> columns; - std::vector ascending; - for (int i = 0; i < indexes_len; ++i) { - if (!COL_INDEX_VALID(env, tv, indexes[i])) { - return; - } - if (!tv->get_parent().has_search_index(S(indexes[i]))) { - ThrowException(env, IllegalArgument, "The field must be indexed before distinct(...) can be used."); - return; - } - switch (tv->get_column_type(S(indexes[i]))) { - case type_Bool: - case type_Int: - case type_String: - case type_Timestamp: - columns.push_back(std::vector { S(indexes[i]) }); - ascending.push_back(true); - break; - default: - ThrowException(env, IllegalArgument, "Invalid type - Only String, Date, boolean, byte, short, int, long and their boxed variants are supported."); - return; - } - } - tv->distinct(SortDescriptor(tv->get_parent(), columns, ascending)); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativePivot( - JNIEnv *env, jobject, jlong dataTablePtr, jlong stringCol, jlong intCol, jint operation, jlong resultTablePtr) -{ - - try { - TV(dataTablePtr)->sync_if_needed(); - TableView* dataTable = TV(dataTablePtr); - Table* resultTable = TBL(resultTablePtr); - Table::AggrType pivotOp; - switch (operation) { - case 0: - pivotOp = Table::aggr_count; - break; - case 1: - pivotOp = Table::aggr_sum; - break; - case 2: - pivotOp = Table::aggr_avg; - break; - case 3: - pivotOp = Table::aggr_min; - break; - case 4: - pivotOp = Table::aggr_max; - break; - default: - ThrowException(env, UnsupportedOperation, "No pivot operation specified."); - return; - } - dataTable->aggregate(S(stringCol), S(intCol), pivotOp, *resultTable); - } CATCH_STD() -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSize( - JNIEnv* env, jobject, jlong nativeViewPtr) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return 0; - } CATCH_STD() - return TV(nativeViewPtr)->size(); // noexcept -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetSourceRowIndex -(JNIEnv *env, jobject, jlong nativeViewPtr, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return to_jlong_or_not_found(-1); - if (!ROW_INDEX_VALID(env, TV(nativeViewPtr), rowIndex)) - return to_jlong_or_not_found(-1); - if (!TV(nativeViewPtr)->is_row_attached(rowIndex)) - return to_jlong_or_not_found(-1); - } CATCH_STD() - return TV(nativeViewPtr)->get_source_ndx(S(rowIndex)); // noexcept -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetColumnCount - (JNIEnv *env, jobject, jlong nativeViewPtr) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return 0; - } CATCH_STD() - return TV(nativeViewPtr)->get_column_count(); -} - -JNIEXPORT jstring JNICALL Java_io_realm_internal_TableView_nativeGetColumnName - (JNIEnv *env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !COL_INDEX_VALID(env, TV(nativeViewPtr), columnIndex)) - return NULL; - return to_jstring(env, TV(nativeViewPtr)->get_column_name( S(columnIndex))); - } CATCH_STD() - return NULL; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetColumnIndex - (JNIEnv *env, jobject, jlong nativeViewPtr, jstring columnName) - -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return 0; - JStringAccessor columnName2(env, columnName); // throws - return to_jlong_or_not_found( TV(nativeViewPtr)->get_column_index(columnName2) ); // noexcept - } CATCH_STD() - return 0; -} - -JNIEXPORT jint JNICALL Java_io_realm_internal_TableView_nativeGetColumnType - (JNIEnv *env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !COL_INDEX_VALID(env, TV(nativeViewPtr), columnIndex)) - return 0; - } CATCH_STD() - return static_cast( TV(nativeViewPtr)->get_column_type( S(columnIndex)) ); -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetLong( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Int)) - return 0; - } CATCH_STD() - return TV(nativeViewPtr)->get_int( S(columnIndex), S(rowIndex)); // noexcept -} - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_TableView_nativeGetBoolean( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Bool)) - return 0; - } CATCH_STD() - return TV(nativeViewPtr)->get_bool( S(columnIndex), S(rowIndex)); // noexcept -} - -JNIEXPORT jfloat JNICALL Java_io_realm_internal_TableView_nativeGetFloat( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Float)) - return 0; - } CATCH_STD() - return TV(nativeViewPtr)->get_float( S(columnIndex), S(rowIndex)); // noexcept -} - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeGetDouble( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Double)) - return 0; - } CATCH_STD() - return TV(nativeViewPtr)->get_double( S(columnIndex), S(rowIndex)); // noexcept -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetTimestamp( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Timestamp)) - return 0; - } CATCH_STD() - return to_milliseconds(TV(nativeViewPtr)->get_timestamp( S(columnIndex), S(rowIndex))); -} - -JNIEXPORT jstring JNICALL Java_io_realm_internal_TableView_nativeGetString( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_String)) - return NULL; - - return to_jstring(env, TV(nativeViewPtr)->get_string( S(columnIndex), S(rowIndex)) // noexcept - ); - } CATCH_STD() - return NULL; -} - -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_TableView_nativeGetByteArray( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Binary)) - return NULL; - return tbl_GetByteArray(env, nativeViewPtr, columnIndex, rowIndex); - } CATCH_STD() - return NULL; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetLink - (JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Link)) - return 0; - } CATCH_STD() - return TV(nativeViewPtr)->get_link( S(columnIndex), S(rowIndex)); // noexcept -} - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_TableView_nativeIsNull - (JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return 0; - return TV(nativeViewPtr)->get_parent().is_null( S(columnIndex), TV(nativeViewPtr)->get_source_ndx(S(rowIndex))) ? JNI_TRUE : JNI_FALSE; // noexcept - } CATCH_STD() - return 0; -} - -// Setters - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetLong( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex, jlong value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Int)) - return; - TV(nativeViewPtr)->set_int( S(columnIndex), S(rowIndex), value); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetBoolean( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex, jboolean value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Bool)) - return; - TV(nativeViewPtr)->set_bool( S(columnIndex), S(rowIndex), value != 0 ? true : false); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetFloat( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex, jfloat value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Float)) - return; - TV(nativeViewPtr)->set_float( S(columnIndex), S(rowIndex), value); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetDouble( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex, jdouble value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Double)) - return; - TV(nativeViewPtr)->set_double( S(columnIndex), S(rowIndex), value); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetTimestampValue( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex, jlong timestampValue) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Timestamp)) - return; - TV(nativeViewPtr)->set_timestamp( S(columnIndex), S(rowIndex), from_milliseconds(timestampValue)); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetString( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex, jstring value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_String)) - return; - if (!TV(nativeViewPtr)->get_parent().is_nullable(S(columnIndex))) { - ThrowNullValueException(env, &(TV(nativeViewPtr)->get_parent()), S(columnIndex)); - return; - } - JStringAccessor value2(env, value); // throws - TV(nativeViewPtr)->set_string( S(columnIndex), S(rowIndex), value2); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetByteArray( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex, jbyteArray byteArray) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Binary)) - return; - - JniByteArray bytesAccessor(env, byteArray); - TV(nativeViewPtr)->set_binary(S(columnIndex), S(rowIndex), bytesAccessor); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSetLink - (JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex, jlong targetIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Link)) - return; - TV(nativeViewPtr)->set_link( S(columnIndex), S(rowIndex), S(targetIndex)); - } CATCH_STD() -} - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_TableView_nativeIsNullLink - (JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Link)) - return 0; - return TV(nativeViewPtr)->is_null_link( S(columnIndex), S(rowIndex)); - } CATCH_STD() - return 0; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeNullifyLink - (JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, rowIndex, type_Link)) - return; - TV(nativeViewPtr)->nullify_link( S(columnIndex), S(rowIndex)); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeClear( - JNIEnv* env, jobject, jlong nativeViewPtr) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return; - TV(nativeViewPtr)->clear(RemoveMode::unordered); - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeRemoveRow( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong rowIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !ROW_INDEX_VALID(env, TV(nativeViewPtr), rowIndex)) - return; - TV(nativeViewPtr)->remove( S(rowIndex), RemoveMode::unordered); - } CATCH_STD() -} - -// FindFirst* - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstInt( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Int)) - return 0; - return to_jlong_or_not_found( TV(nativeViewPtr)->find_first_int( S(columnIndex), value) ); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstBool( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jboolean value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Bool)) - return 0; - size_t res = TV(nativeViewPtr)->find_first_bool( S(columnIndex), value != 0 ? true : false); - return to_jlong_or_not_found( res ); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstFloat( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jfloat value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Float)) - return 0; - return to_jlong_or_not_found( TV(nativeViewPtr)->find_first_float( S(columnIndex), value) ); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstDouble( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jdouble value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Double)) - return 0; - return to_jlong_or_not_found( (TV(nativeViewPtr)->find_first_double( S(columnIndex), value)) ); - } CATCH_STD() - return 0; -} - -// FIXME: find_first_timestamp() isn't implemented -/* -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstDate( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong dateTimeValue) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_DateTime)) - return 0; - return to_jlong_or_not_found( TV(nativeViewPtr)->find_first_datetime( S(columnIndex), DateTime(dateTimeValue)) ); - } CATCH_STD() - return 0; -} -*/ - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindFirstString( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jstring value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_String)) - return 0; - JStringAccessor value2(env, value); // throws - size_t searchIndex = TV(nativeViewPtr)->find_first_string( S(columnIndex), value2); - return to_jlong_or_not_found( searchIndex ); - } CATCH_STD() - return 0; -} - -// FindAll* - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllInt( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Int)) - return 0; - TableView* pResultView = new TableView( TV(nativeViewPtr)->find_all_int( S(columnIndex), value) ); - return reinterpret_cast(pResultView); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllBool( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jboolean value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Bool)) - return 0; - TableView* pResultView = new TableView( TV(nativeViewPtr)->find_all_bool( S(columnIndex), - value != 0 ? true : false) ); - return reinterpret_cast(pResultView); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllFloat( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jfloat value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Float)) - return 0; - TableView* pResultView = new TableView( TV(nativeViewPtr)->find_all_float( S(columnIndex), value) ); - return reinterpret_cast(pResultView); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllDouble( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jdouble value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Double)) - return 0; - TableView* pResultView = new TableView( TV(nativeViewPtr)->find_all_double( S(columnIndex), value) ); - return reinterpret_cast(pResultView); - } CATCH_STD() - return 0; -} - -// FIXME: find_all_timestamp() isn't implemented -/* -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllDate( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jlong dateTimeValue) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_DateTime)) - return 0; - TableView* pResultView = new TableView( TV(nativeViewPtr)->find_all_datetime( S(columnIndex), - DateTime(dateTimeValue)) ); - return reinterpret_cast(pResultView); - } CATCH_STD() - return 0; -} -*/ - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindAllString( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jstring value) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_String)) - return 0; - JStringAccessor value2(env, value); // throws - TableView* pResultView = new TableView( TV(nativeViewPtr)->find_all_string( S(columnIndex), value2) ); - return reinterpret_cast(pResultView); - } CATCH_STD() - return 0; -} - -// Integer aggregates - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSumInt( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Int)) - return 0; - return TV(nativeViewPtr)->sum_int( S(columnIndex)); - } CATCH_STD() - return 0; -} - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeAverageInt( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Int)) - return 0; - return static_cast( TV(nativeViewPtr)->average_int( S(columnIndex))); - } CATCH_STD() - return 0; -} - -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMaximumInt( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Int)) - return NULL; - size_t return_ndx; - int64_t result = TV(nativeViewPtr)->maximum_int( S(columnIndex), &return_ndx); - if (return_ndx != npos) { - return NewLong(env, result); - } - } CATCH_STD() - return NULL; -} - -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMinimumInt( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Int)) - return NULL; - size_t return_ndx; - int64_t result = TV(nativeViewPtr)->minimum_int( S(columnIndex), &return_ndx); - if (return_ndx != npos) { - return NewLong(env, result); - } - } CATCH_STD() - return NULL; -} - -// float aggregates - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeSumFloat( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Float)) - return 0; - return TV(nativeViewPtr)->sum_float( S(columnIndex)); - } CATCH_STD() - return 0; -} - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeAverageFloat( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Float)) - return 0; - return TV(nativeViewPtr)->average_float( S(columnIndex)); - } CATCH_STD() - return 0; -} - -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMaximumFloat( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Float)) - return NULL; - size_t return_ndx; - float result = TV(nativeViewPtr)->maximum_float( S(columnIndex), &return_ndx); - if (return_ndx != npos) { - return NewFloat(env, result); - } - } CATCH_STD() - return NULL; -} - -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMinimumFloat( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Float)) - return NULL; - size_t return_ndx; - float result = TV(nativeViewPtr)->minimum_float( S(columnIndex), &return_ndx); - if (return_ndx != npos) { - return NewFloat(env, result); - } - } CATCH_STD() - return NULL; -} - -// double aggregates - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeSumDouble( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Double)) - return 0; - return TV(nativeViewPtr)->sum_double( S(columnIndex)); - } CATCH_STD() - return 0; -} - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableView_nativeAverageDouble( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Double)) - return 0; - return static_cast( TV(nativeViewPtr)->average_double( S(columnIndex)) ); - } CATCH_STD() - return 0; -} - -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMaximumDouble( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Double)) - return NULL; - size_t return_ndx; - double result = TV(nativeViewPtr)->maximum_double( S(columnIndex), &return_ndx); - if (return_ndx != npos) { - return NewDouble(env, result); - } - } CATCH_STD() - return NULL; -} - -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMinimumDouble( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Double)) - return NULL; - size_t return_ndx; - double result = TV(nativeViewPtr)->minimum_double( S(columnIndex), &return_ndx); - if (return_ndx != npos) { - return NewDouble(env, result); - } - } CATCH_STD() - return NULL; -} - - -// date aggregates - -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMaximumTimestamp( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Timestamp)) - return NULL; - - size_t return_ndx; - Timestamp result = TV(nativeViewPtr)->maximum_timestamp( S(columnIndex), &return_ndx); - if (return_ndx != npos) { - return NewLong(env, to_milliseconds(result)); - } - } CATCH_STD() - return NULL; -} - -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableView_nativeMinimumTimestamp( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_AND_TYPE_VALID(env, TV(nativeViewPtr), columnIndex, type_Timestamp)) - return NULL; - - size_t return_ndx; - Timestamp result = TV(nativeViewPtr)->minimum_timestamp( S(columnIndex), &return_ndx); - if (return_ndx != npos) { - return NewLong(env, to_milliseconds(result)); - } - } CATCH_STD() - return NULL; -} - -// sort -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSort( - JNIEnv* env, jobject, jlong nativeViewPtr, jlong columnIndex, jboolean ascending) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || - !COL_INDEX_VALID(env, TV(nativeViewPtr), columnIndex)) - return; - int colType = TV(nativeViewPtr)->get_column_type( S(columnIndex) ); - - switch (colType) { - case type_Bool: - case type_Int: - case type_Float: - case type_Double: - case type_String: - case type_Timestamp: - TV(nativeViewPtr)->sort( S(columnIndex), ascending != 0 ? true : false); - break; - default: - ThrowException(env, IllegalArgument, "Sort is not supported on binary data, object references and RealmList."); - return; - } - } CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_TableView_nativeSortMulti( - JNIEnv* env, jobject, jlong nativeViewPtr, jlongArray columnIndices, jbooleanArray ascending) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return; - - JniLongArray long_arr(env, columnIndices); - JniBooleanArray bool_arr(env, ascending); - jsize arr_len = long_arr.len(); - jsize asc_len = bool_arr.len(); - - if (arr_len == 0) { - ThrowException(env, IllegalArgument, "You must provide at least one field name."); - return; - } - if (asc_len == 0) { - ThrowException(env, IllegalArgument, "You must provide at least one sort order."); - return; - } - if (arr_len != asc_len) { - ThrowException(env, IllegalArgument, "Number of fields and sort orders do not match."); - return; - } - - TableView* tv = TV(nativeViewPtr); - std::vector> indices; - std::vector ascendings; - - for (int i = 0; i < arr_len; ++i) { - if (!COL_INDEX_VALID(env, tv, long_arr[i])) { - return; - } - int colType = tv->get_column_type( S(long_arr[i]) ); - switch (colType) { - case type_Bool: - case type_Int: - case type_Float: - case type_Double: - case type_String: - case type_Timestamp: - indices.push_back(std::vector { S(long_arr[i]) }); - ascendings.push_back( B(bool_arr[i]) ); - break; - default: - ThrowException(env, IllegalArgument, "Sort is not supported on binary data, object references and RealmList."); - return; - } - } - tv->sort(SortDescriptor(tv->get_parent(), indices, ascendings)); - } CATCH_STD() -} - -JNIEXPORT jstring JNICALL Java_io_realm_internal_TableView_nativeToJson( - JNIEnv *env, jobject, jlong nativeViewPtr) -{ - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return NULL; - - // Write table to string in JSON format - std::stringstream ss; - ss.sync_with_stdio(false); // for performance - TV(nativeViewPtr)->to_json(ss); - const std::string str = ss.str(); - return to_jstring(env, str); - } CATCH_STD() - return NULL; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeWhere( - JNIEnv *env, jobject, jlong nativeViewPtr) -{ - TR_ENTER_PTR(nativeViewPtr) - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr)) - return 0; - - Query *queryPtr = new Query(TV(nativeViewPtr)->get_parent().where(TV(nativeViewPtr))); - return reinterpret_cast(queryPtr); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeSyncIfNeeded( - JNIEnv* env, jobject, jlong nativeViewPtr) -{ - bool valid = (TV(nativeViewPtr) != NULL); - if (valid) { - if (!TV(nativeViewPtr)->is_attached()) { - ThrowException(env, IllegalState, "The Realm has been closed and is no longer accessible."); - return 0; - } - } - try { - return (jlong) TV(nativeViewPtr)->sync_if_needed(); - } CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeFindBySourceNdx - (JNIEnv *env, jobject, jlong nativeViewPtr, jlong sourceIndex) -{ - TR_ENTER_PTR(nativeViewPtr); - try { - if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !ROW_INDEX_VALID(env, &(TV(nativeViewPtr)->get_parent()), sourceIndex)) - return -1; - - size_t ndx = TV(nativeViewPtr)->find_by_source_ndx(sourceIndex); - return to_jlong_or_not_found(ndx); - } CATCH_STD() - return -1; -} - -static void finalize_table_view(jlong ptr) -{ - TR_ENTER_PTR(ptr) - delete TV(ptr); -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetFinalizerPtr - (JNIEnv *, jclass) -{ - TR_ENTER() - return reinterpret_cast(&finalize_table_view); -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 7cd19d01aa..af4df781ea 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -110,6 +110,7 @@ public byte getValue() { public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { + query.validateQuery(); this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.query = query; diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 1a0f628120..9b92b00146 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -28,7 +28,21 @@ * (define/insert/delete/update) a table has. All the native communications to the Realm C++ library are also handled by * this class. */ -public class Table implements TableOrView, TableSchema, NativeObject { +public class Table implements TableSchema, NativeObject { + + enum PivotType { + COUNT(0), + SUM(1), + AVG(2), + MIN(3), + MAX(4); + + final int value; // Package protected, accessible from Table + + PivotType(int value) { + this.value = value; + } + } public static final int TABLE_MAX_LENGTH = 56; // Max length of class names without prefix public static final String TABLE_PREFIX = Util.getTablePrefix(); @@ -88,7 +102,6 @@ public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } - @Override public Table getTable() { return this; } @@ -264,7 +277,6 @@ public void convertColumnToNotNullable(long columnIndex) { * * @return the number of rows. */ - @Override public long size() { return nativeSize(nativePtr); } @@ -274,7 +286,6 @@ public long size() { * * @return {@code true} if empty, otherwise {@code false}. */ - @Override public boolean isEmpty() { return size() == 0; } @@ -282,7 +293,6 @@ public boolean isEmpty() { /** * Clears the table i.e., deleting all rows in the table. */ - @Override public void clear() { checkImmutable(); nativeClear(nativePtr); @@ -294,7 +304,6 @@ public void clear() { * * @return the number of columns. */ - @Override public long getColumnCount() { return nativeGetColumnCount(nativePtr); } @@ -305,7 +314,6 @@ public long getColumnCount() { * @param columnIndex the column index. * @return the name of the column. */ - @Override public String getColumnName(long columnIndex) { return nativeGetColumnName(nativePtr, columnIndex); } @@ -316,7 +324,6 @@ public String getColumnName(long columnIndex) { * @param columnName column name. * @return the index, {@link #NO_MATCH} if not found. */ - @Override public long getColumnIndex(String columnName) { if (columnName == null) { throw new IllegalArgumentException("Column name can not be null."); @@ -330,7 +337,6 @@ public long getColumnIndex(String columnName) { * @param columnIndex index of the column. * @return the type of the particular column. */ - @Override public RealmFieldType getColumnType(long columnIndex) { return RealmFieldType.fromNativeValue(nativeGetColumnType(nativePtr, columnIndex)); } @@ -342,19 +348,16 @@ public RealmFieldType getColumnType(long columnIndex) { * @param rowIndex the row index (starting with 0) * */ - @Override public void remove(long rowIndex) { checkImmutable(); nativeRemove(nativePtr, rowIndex); } - @Override public void removeFirst() { checkImmutable(); remove(0); } - @Override public void removeLast() { checkImmutable(); nativeRemoveLast(nativePtr); @@ -670,27 +673,22 @@ public static void throwDuplicatePrimaryKeyException(Object value) { // Getters // - @Override public long getLong(long columnIndex, long rowIndex) { return nativeGetLong(nativePtr, columnIndex, rowIndex); } - @Override public boolean getBoolean(long columnIndex, long rowIndex) { return nativeGetBoolean(nativePtr, columnIndex, rowIndex); } - @Override public float getFloat(long columnIndex, long rowIndex) { return nativeGetFloat(nativePtr, columnIndex, rowIndex); } - @Override public double getDouble(long columnIndex, long rowIndex) { return nativeGetDouble(nativePtr, columnIndex, rowIndex); } - @Override public Date getDate(long columnIndex, long rowIndex) { return new Date(nativeGetTimestamp(nativePtr, columnIndex, rowIndex)); } @@ -702,17 +700,14 @@ public Date getDate(long columnIndex, long rowIndex) { * @param rowIndex 0 based index of the row. * @return value of the particular cell */ - @Override public String getString(long columnIndex, long rowIndex) { return nativeGetString(nativePtr, columnIndex, rowIndex); } - @Override public byte[] getBinaryByteArray(long columnIndex, long rowIndex) { return nativeGetByteArray(nativePtr, columnIndex, rowIndex); } - @Override public long getLink(long columnIndex, long rowIndex) { return nativeGetLink(nativePtr, columnIndex, rowIndex); } @@ -724,7 +719,6 @@ public Table getLinkTarget(long columnIndex) { return table; } - @Override public boolean isNull(long columnIndex, long rowIndex) { return nativeIsNull(nativePtr, columnIndex, rowIndex); } @@ -768,32 +762,27 @@ public CheckedRow getCheckedRow(long index) { // Setters // - @Override public void setLong(long columnIndex, long rowIndex, long value, boolean isDefault) { checkImmutable(); checkIntValueIsLegal(columnIndex, rowIndex, value); nativeSetLong(nativePtr, columnIndex, rowIndex, value, isDefault); } - @Override public void setBoolean(long columnIndex, long rowIndex, boolean value, boolean isDefault) { checkImmutable(); nativeSetBoolean(nativePtr, columnIndex, rowIndex, value, isDefault); } - @Override public void setFloat(long columnIndex, long rowIndex, float value, boolean isDefault) { checkImmutable(); nativeSetFloat(nativePtr, columnIndex, rowIndex, value, isDefault); } - @Override public void setDouble(long columnIndex, long rowIndex, double value, boolean isDefault) { checkImmutable(); nativeSetDouble(nativePtr, columnIndex, rowIndex, value, isDefault); } - @Override public void setDate(long columnIndex, long rowIndex, Date date, boolean isDefault) { if (date == null) throw new IllegalArgumentException("Null Date is not allowed."); @@ -808,7 +797,6 @@ public void setDate(long columnIndex, long rowIndex, Date date, boolean isDefaul * @param rowIndex 0 based index value of the cell row. * @param value a String value to set in the cell. */ - @Override public void setString(long columnIndex, long rowIndex, String value, boolean isDefault) { checkImmutable(); if (value == null) { @@ -820,13 +808,11 @@ public void setString(long columnIndex, long rowIndex, String value, boolean isD } } - @Override public void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data, boolean isDefault) { checkImmutable(); nativeSetByteArray(nativePtr, columnIndex, rowIndex, data, isDefault); } - @Override public void setLink(long columnIndex, long rowIndex, long value, boolean isDefault) { checkImmutable(); nativeSetLink(nativePtr, columnIndex, rowIndex, value, isDefault); @@ -922,12 +908,10 @@ public boolean hasSearchIndex(long columnIndex) { return nativeHasSearchIndex(nativePtr, columnIndex); } - @Override public boolean isNullLink(long columnIndex, long rowIndex) { return nativeIsNullLink(nativePtr, columnIndex, rowIndex); } - @Override public void nullifyLink(long columnIndex, long rowIndex) { nativeNullifyLink(nativePtr, columnIndex, rowIndex); } @@ -954,76 +938,62 @@ private void checkHasPrimaryKey() { // // Integers - @Override public long sumLong(long columnIndex) { return nativeSumInt(nativePtr, columnIndex); } - @Override public Long maximumLong(long columnIndex) { return nativeMaximumInt(nativePtr, columnIndex); } - @Override public Long minimumLong(long columnIndex) { return nativeMinimumInt(nativePtr, columnIndex); } - @Override public double averageLong(long columnIndex) { return nativeAverageInt(nativePtr, columnIndex); } // Floats - @Override public double sumFloat(long columnIndex) { return nativeSumFloat(nativePtr, columnIndex); } - @Override public Float maximumFloat(long columnIndex) { return nativeMaximumFloat(nativePtr, columnIndex); } - @Override public Float minimumFloat(long columnIndex) { return nativeMinimumFloat(nativePtr, columnIndex); } - @Override public double averageFloat(long columnIndex) { return nativeAverageFloat(nativePtr, columnIndex); } // Doubles - @Override public double sumDouble(long columnIndex) { return nativeSumDouble(nativePtr, columnIndex); } - @Override public Double maximumDouble(long columnIndex) { return nativeMaximumDouble(nativePtr, columnIndex); } - @Override public Double minimumDouble(long columnIndex) { return nativeMinimumDouble(nativePtr, columnIndex); } - @Override public double averageDouble(long columnIndex) { return nativeAverageDouble(nativePtr, columnIndex); } // Date aggregates - @Override public Date maximumDate(long columnIndex) { return new Date(nativeMaximumTimestamp(nativePtr, columnIndex)); } - @Override public Date minimumDate(long columnIndex) { return new Date(nativeMinimumTimestamp(nativePtr, columnIndex)); } @@ -1044,7 +1014,6 @@ public long count(long columnIndex, double value) { return nativeCountDouble(nativePtr, columnIndex, value); } - @Override public long count(long columnIndex, String value) { return nativeCountString(nativePtr, columnIndex, value); } @@ -1053,47 +1022,28 @@ public long count(long columnIndex, String value) { // Searching methods. // - @Override public TableQuery where() { long nativeQueryPtr = nativeWhere(nativePtr); // Copy context reference from parent return new TableQuery(this.context, this, nativeQueryPtr); } - /** - * Returns the same rowIndex that is passed in via the {@code rowIndex}. - * This interface method allows for contains() usage in the {@link TableView} class. - * See {@link TableView#sourceRowIndex(long)} for more information. - * - * @param rowIndex the index of the row. - * @return the row index. - */ - @Override - public long sourceRowIndex(long rowIndex) { - return rowIndex; - } - - @Override public long findFirstLong(long columnIndex, long value) { return nativeFindFirstInt(nativePtr, columnIndex, value); } - @Override public long findFirstBoolean(long columnIndex, boolean value) { return nativeFindFirstBool(nativePtr, columnIndex, value); } - @Override public long findFirstFloat(long columnIndex, float value) { return nativeFindFirstFloat(nativePtr, columnIndex, value); } - @Override public long findFirstDouble(long columnIndex, double value) { return nativeFindFirstDouble(nativePtr, columnIndex, value); } - @Override public long findFirstDate(long columnIndex, Date date) { if (date == null) { throw new IllegalArgumentException("null is not supported"); @@ -1101,7 +1051,6 @@ public long findFirstDate(long columnIndex, Date date) { return nativeFindFirstTimestamp(nativePtr, columnIndex, date.getTime()); } - @Override public long findFirstString(long columnIndex, String value) { if (value == null) { throw new IllegalArgumentException("null is not supported"); @@ -1119,47 +1068,14 @@ public long findFirstNull(long columnIndex) { return nativeFindFirstNull(nativePtr, columnIndex); } - @Override - public TableView findAllLong(long columnIndex, long value) { - long nativeViewPtr = nativeFindAllInt(nativePtr, columnIndex, value); - return new TableView(this.context, this, nativeViewPtr); - } - - @Override - public TableView findAllBoolean(long columnIndex, boolean value) { - long nativeViewPtr = nativeFindAllBool(nativePtr, columnIndex, value); - return new TableView(this.context, this, nativeViewPtr); - } - - @Override - public TableView findAllFloat(long columnIndex, float value) { - long nativeViewPtr = nativeFindAllFloat(nativePtr, columnIndex, value); - return new TableView(this.context, this, nativeViewPtr); - } - - @Override - public TableView findAllDouble(long columnIndex, double value) { - long nativeViewPtr = nativeFindAllDouble(nativePtr, columnIndex, value); - return new TableView(this.context, this, nativeViewPtr); - } - - @Override - public TableView findAllString(long columnIndex, String value) { - long nativeViewPtr = nativeFindAllString(nativePtr, columnIndex, value); - return new TableView(this.context, this, nativeViewPtr); - } - // Experimental feature - @Override public long lowerBoundLong(long columnIndex, long value) { return nativeLowerBoundInt(nativePtr, columnIndex, value); } - @Override public long upperBoundLong(long columnIndex, long value) { return nativeUpperBoundInt(nativePtr, columnIndex, value); } - @Override public Table pivot(long stringCol, long intCol, PivotType pivotType) { if (! this.getColumnType(stringCol).equals(RealmFieldType.STRING )) throw new UnsupportedOperationException("Group by column must be of type String"); @@ -1172,11 +1088,6 @@ public Table pivot(long stringCol, long intCol, PivotType pivotType) { // - public TableView getDistinctView(long columnIndex) { - long nativeViewPtr = nativeGetDistinctView(nativePtr, columnIndex); - return new TableView(this.context, this, nativeViewPtr); - } - /** * Returns the table name as it is in the associated group. * @@ -1186,12 +1097,10 @@ public String getName() { return nativeGetName(nativePtr); } - @Override public String toJson() { return nativeToJson(nativePtr); } - @Override public String toString() { long columnCount = getColumnCount(); String name = getName(); @@ -1223,11 +1132,6 @@ public String toString() { return stringBuilder.toString(); } - @Override - public long syncIfNeeded() { - throw new RuntimeException("Not supported for tables"); - } - private static void throwImmutable() { throw new IllegalStateException("Changing Realm data can only be done from inside a transaction."); } @@ -1349,17 +1253,11 @@ public static String tableNameToClassName(String tableName) { private native long nativeFindFirstTimestamp(long nativeTablePtr, long columnIndex, long dateTimeValue); public static native long nativeFindFirstString(long nativeTablePtr, long columnIndex, String value); public static native long nativeFindFirstNull(long nativeTablePtr, long columnIndex); - private native long nativeFindAllInt(long nativePtr, long columnIndex, long value); - private native long nativeFindAllBool(long nativePtr, long columnIndex, boolean value); - private native long nativeFindAllFloat(long nativePtr, long columnIndex, float value); - private native long nativeFindAllDouble(long nativePtr, long columnIndex, double value); // FIXME: Disabled in cpp code, see comments there // private native long nativeFindAllTimestamp(long nativePtr, long columnIndex, long dateTimeValue); - private native long nativeFindAllString(long nativePtr, long columnIndex, String value); private native long nativeLowerBoundInt(long nativePtr, long columnIndex, long value); private native long nativeUpperBoundInt(long nativePtr, long columnIndex, long value); private native void nativePivot(long nativeTablePtr, long stringCol, long intCol, int pivotType, long resultPtr); - private native long nativeGetDistinctView(long nativePtr, long columnIndex); private native String nativeGetName(long nativeTablePtr); private native String nativeToJson(long nativeTablePtr); private native boolean nativeHasSameSchema(long thisTable, long otherTable); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java b/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java deleted file mode 100644 index a45ad1331a..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import java.util.Date; - -import io.realm.Realm; -import io.realm.RealmFieldType; - -/** - * Specification of the common operations for the low-level table and view API. - */ -public interface TableOrView { - - void clear(); - - /** - * Returns the table. - * - * @return - */ - Table getTable(); - - /** - * Returns the number of entries of the table/view. - * - * @return - */ - long size(); - - /** - * Checks whether the table/view is empty or not. - * - * @return {@code true} if empty, otherwise {@code false}. - */ - boolean isEmpty(); - - /** - * Removes a particular row identified by the index from the table/view. - * [citation needed] The corresponding row of the table also gets deleted for which the table/view is part of. - * - * @param index - */ - void remove(long index); - - void removeLast(); - - long getColumnCount(); - - String getColumnName(long columnIndex); - - long getColumnIndex(String name); - - RealmFieldType getColumnType(long columnIndex); - - /** - * Gets the long value of a cell of the table/view identified by the columnIndex and rowIndex. - * - * @param columnIndex - * @param rowIndex - * @return - */ - long getLong(long columnIndex, long rowIndex); - - /** - * Gets the boolean value of a cell of the table identified by the columnIndex and rowIndex. - * - * @param columnIndex - * @param rowIndex - * @return - */ - boolean getBoolean(long columnIndex, long rowIndex); - - /** - * Gets the float value of a cell of the table identified by the columnIndex and rowIndex. - * - * @param columnIndex - * @param rowIndex - * @return - */ - float getFloat(long columnIndex, long rowIndex); - - /** - * Gets the double value of a cell of the table identified by the columnIndex and rowIndex. - * - * @param columnIndex - * @param rowIndex - * @return - */ - double getDouble(long columnIndex, long rowIndex); - - /** - * Gets the string value of a cell identified by the columnIndex and rowIndex of the cell. - * - * @param columnIndex - * @param rowIndex - * @return - */ - String getString(long columnIndex, long rowIndex); - - /** - * Returns the Date value (java.util.Date) for a particular cell specified by the columnIndex and rowIndex of the - * cell. - * - * @param columnIndex - * @param rowIndex - * @return - */ - Date getDate(long columnIndex, long rowIndex); - - /** - * Returns the binary data for a cell identified by the columnIndex and rowIndex of that cell. - * - * @param columnIndex - * @param rowIndex - * @return - */ - //ByteBuffer getBinaryByteBuffer(long columnIndex, long rowIndex); - - byte[] getBinaryByteArray(long columnIndex, long rowIndex); - - /** - * Gets the link index of a cell of the table/view identified by the columnIndex and rowIndex. - * - * @param columnIndex - * @param rowIndex - * @return - */ - long getLink(long columnIndex, long rowIndex); - - /** - * Sets the long value for a particular cell identified by columnIndex and rowIndex of that cell. - * - * @param columnIndex - * @param rowIndex - * @param value - */ - void setLong(long columnIndex, long rowIndex, long value, boolean isDefault); - - /** - * Sets the boolean value of a cell identified by the columnIndex and the rowIndex of that cell. - * - * @param columnIndex - * @param rowIndex - * @param value - */ - void setBoolean(long columnIndex, long rowIndex, boolean value, boolean isDefault); - - /** - * Sets the float value of a cell identified by the columnIndex and the rowIndex of that cell. - * - * @param columnIndex - * @param rowIndex - * @param value - */ - void setFloat(long columnIndex, long rowIndex, float value, boolean isDefault); - - /** - * Sets the double value of a cell identified by the columnIndex and the rowIndex of that cell. - * - * @param columnIndex - * @param rowIndex - * @param value - */ - void setDouble(long columnIndex, long rowIndex, double value, boolean isDefault); - - /** - * Sets the string value of a particular cell of the table/view identified by the columnIndex and the rowIndex of - * this table/view - * - * @param columnIndex - * @param rowIndex - * @param value - */ - void setString(long columnIndex, long rowIndex, String value, boolean isDefault); - - /** - * Sets the binary value for a particular cell identified by the rowIndex and columnIndex of the cell. - * - * @param columnIndex - * @param rowIndex - * @param data - */ - //void setBinaryByteBuffer(long columnIndex, long rowIndex, ByteBuffer data); - - void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data, boolean isDefault); - - void setDate(long columnIndex, long rowIndex, Date date, boolean isDefault); - - boolean isNullLink(long columnIndex, long rowIndex); - - void nullifyLink(long columnIndex, long rowIndex); - - /** - * Sets the link index for a particular cell identified by columnIndex and rowIndex of that cell. - * - * @param columnIndex - * @param rowIndex - * @param value - */ - void setLink(long columnIndex, long rowIndex, long value, boolean isDefault); - - void setNull(long columnIndex, long rowIndex, boolean isDefault); - - boolean isNull(long columnIndex, long rowIndex); - - long sumLong(long columnIndex); - - Long maximumLong(long columnIndex); - - Long minimumLong(long columnIndex); - - double averageLong(long columnIndex); - - - double sumFloat(long columnIndex); - - Float maximumFloat(long columnIndex); - - Float minimumFloat(long columnIndex); - - double averageFloat(long columnIndex); - - - double sumDouble(long columnIndex); - - Double maximumDouble(long columnIndex); - - Double minimumDouble(long columnIndex); - - double averageDouble(long columnIndex); - - - Date maximumDate(long columnIndex); - - Date minimumDate(long columnIndex); - - - /** - * Searches for first occurrence of a value. Beware that the order in the column is undefined. - * - * @param columnIndex the column to search in. - * @param value the value to search for. - * @return the row index for the first match found or {@link Table#NO_MATCH}. - */ - long findFirstLong(long columnIndex, long value); - - /** - * Searches for first occurrence of a value. Beware that the order in the column is undefined. - * - * @param columnIndex the column to search in. - * @param value the alue to search for. - * @return the row index for the first match found or {@link Table#NO_MATCH}. - */ - long findFirstBoolean(long columnIndex, boolean value); - - /** - * Searches for first occurrence of a value. Beware that the order in the column is undefined. - * - * @param columnIndex the column to search in. - * @param value the value to search for. - * @return the row index for the first match found or {@link Table#NO_MATCH}. - */ - long findFirstFloat(long columnIndex, float value); - - /** - * Searches for first occurrence of a value. Beware that the order in the column is undefined. - * - * @param columnIndex the column to search in. - * @param value the value to search for. - * @return the row index for the first match found or {@link Table#NO_MATCH}. - */ - long findFirstDouble(long columnIndex, double value); - - /** - * Searches for first occurrence of a value. Beware that the order in the column is undefined. - * - * @param columnIndex the column to search in. - * @param value the value to search for. - * @return the row index for the first match found or {@link Table#NO_MATCH}. - */ - long findFirstDate(long columnIndex, Date value); - - /** - * Searches for first occurrence of a value. Beware that the order in the column is undefined. - * - * @param columnIndex the column to search in. - * @param value the value to search for. - * @return the row index for the first match found or {@link Table#NO_MATCH}. - */ - long findFirstString(long columnIndex, String value); - - long lowerBoundLong(long columnIndex, long value); - long upperBoundLong(long columnIndex, long value); - - - TableView findAllLong(long columnIndex, long value); - - TableView findAllBoolean(long columnIndex, boolean value); - - TableView findAllFloat(long columnIndex, float value); - - TableView findAllDouble(long columnIndex, double value); - - TableView findAllString(long columnIndex, String value); - - String toJson(); - - String toString(); - - TableQuery where(); - - /** - * Finds a row with in the table or view with the given index. - * - * @param rowIndex the index of the row. - * @return the index if found, or -1 for not found. - */ - long sourceRowIndex(long rowIndex); - - // Experimental: - - long count(long columnIndex, String value); - - /** - * Report the current versioning counter for the table. The versioning counter is guaranteed to - * change when the contents of the table changes after advance_read() or promote_to_write(), or - * immediately after calls to methods which change the table. - * - * @return version_counter for the table. - */ - long getVersion(); - - void removeFirst(); - - enum PivotType { - COUNT(0), - SUM(1), - AVG(2), - MIN(3), - MAX(4); - - final int value; // Package protected, accessible from Table and TableView - - PivotType(int value) { - this.value = value; - } - } - - Table pivot(long stringCol, long intCol, PivotType pivotType); - - /** - * Syncs the TableView with the underlying table data. This is effectively the same as rerunning the query, so it - * should not be called on TableViews created by an async query. - * - * This method gets automatically called when calling {@link Realm#refresh()} or when another thread updates - * the Realm, but it will _not_ be called if the same thread commits a transaction. - * - * @return the version number for the updated TableView. - */ - long syncIfNeeded(); -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 342c1762ec..fc722f9dec 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -28,10 +28,6 @@ public class TableQuery implements NativeObject { protected long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); protected final Table table; - // Don't convert this into local variable and don't remove this. - // Core requests Query to hold the TableView reference which it is built from. - @SuppressWarnings({"unused"}) - private final TableOrView origin; // Table or TableView which created this TableQuery private final Context context; // All actions (find(), findAll(), sum(), etc.) must call validateQuery() before performing @@ -47,18 +43,6 @@ public TableQuery(Context context, Table table, long nativeQueryPtr) { this.context = context; this.table = table; this.nativePtr = nativeQueryPtr; - this.origin = null; - context.addReference(this); - } - - public TableQuery(Context context, Table table, long nativeQueryPtr, TableOrView origin) { - if (DEBUG) { - System.err.println("++++++ new TableQuery, ptr= " + nativeQueryPtr); - } - this.context = context; - this.table = table; - this.nativePtr = nativeQueryPtr; - this.origin = origin; context.addReference(this); } @@ -80,7 +64,7 @@ public Table getTable() { /** * Checks in core if query syntax is valid. Throws exception, if not. */ - private void validateQuery() { + void validateQuery() { if (! queryValidated) { // If not yet validated, check if syntax is valid String invalidMessage = nativeValidateQuery(nativePtr); if (invalidMessage.equals("")) @@ -90,12 +74,6 @@ private void validateQuery() { } } - // Query TableView - public TableQuery tableview(TableView tv) { - nativeTableview(nativePtr, tv.nativePtr); - return this; - } - // Grouping public TableQuery group() { @@ -429,71 +407,6 @@ public long find() { return nativeFind(nativePtr, 0); } - /** - * Performs a find query then handover the resulted Row (ready to be imported by another thread/shared_group). - * - * @param sharedRealm current {@link SharedRealm }from which to operate the query. - * @param ptrQuery query to run the the find against. - * @return pointer to the handover result (table_view). - */ - public static long findWithHandover(SharedRealm sharedRealm, long ptrQuery) { - // Execute the disposal of abandoned realm objects each time a new realm object is created - return nativeFindWithHandover(sharedRealm.getNativePtr(), ptrQuery, 0); - } - - public TableView findAll(long start, long end, long limit) { - validateQuery(); - - long nativeViewPtr = nativeFindAll(nativePtr, start, end, limit); - return new TableView(this.context, this.table, nativeViewPtr, this); - } - - public TableView findAll() { - validateQuery(); - - long nativeViewPtr = nativeFindAll(nativePtr, 0, Table.INFINITE, Table.INFINITE); - return new TableView(this.context, this.table, nativeViewPtr, this); - } - - // handover find* methods - // this will use a background SharedGroup to import the query (using the handover object) - // run the query, and return the table view to the caller SharedGroup using the handover object. - public static long findAllWithHandover(SharedRealm sharedRealm, long ptrQuery) throws BadVersionException { - return nativeFindAllWithHandover(sharedRealm.getNativePtr(), ptrQuery, 0, Table.INFINITE, Table.INFINITE); - } - - public static long findDistinctWithHandover(SharedRealm sharedRealm, long ptrQuery, long columnIndex) throws BadVersionException { - return nativeGetDistinctViewWithHandover(sharedRealm.getNativePtr(), ptrQuery, columnIndex); - } - - public static long findAllSortedWithHandover(SharedRealm sharedRealm, long ptrQuery, long columnIndex, Sort sortOrder) throws BadVersionException { - return nativeFindAllSortedWithHandover(sharedRealm.getNativePtr(), ptrQuery, 0, Table.INFINITE, Table.INFINITE, columnIndex, sortOrder.getValue()); - } - - public static long findAllMultiSortedWithHandover(SharedRealm sharedRealm, long ptrQuery, long[] columnIndices, Sort[] sortOrders) throws BadVersionException { - boolean[] ascendings = getNativeSortOrderValues(sortOrders); - return nativeFindAllMultiSortedWithHandover(sharedRealm.getNativePtr(), ptrQuery, 0, Table.INFINITE, Table.INFINITE, columnIndices, ascendings); - } - - public static long[] batchUpdateQueries(SharedRealm sharedRealm, long[] handoverQueries, long[][] parameters, - long[][] queriesParameters, boolean[][] multiSortOrder) - throws BadVersionException { - return nativeBatchUpdateQueries(sharedRealm.getNativePtr(), handoverQueries, parameters, queriesParameters, - multiSortOrder); - } - /** - * Imports a TableView from a worker thread to the caller thread. - * - * @param handoverPtr pointer to the handover object - * @param sharedRealm the SharedRealm on the caller thread. - * @return the TableView on the caller thread. - * @throws BadVersionException if the worker thread and caller thread are not at the same version. - */ - public TableView importHandoverTableView(long handoverPtr, SharedRealm sharedRealm) throws BadVersionException { - long nativeTvPtr = nativeImportHandoverTableViewIntoSharedGroup(handoverPtr, sharedRealm.getNativePtr()); - return new TableView(this.context, this.table, nativeTvPtr); - } - /** * Imports a row from a worker thread to the caller thread. * @@ -717,7 +630,6 @@ private void throwImmutable() { } private native String nativeValidateQuery(long nativeQueryPtr); - private native void nativeTableview(long nativeQueryPtr, long nativeTableViewPtr); private native void nativeGroup(long nativeQueryPtr); private native void nativeEndGroup(long nativeQueryPtr); private native void nativeOr(long nativeQueryPtr); @@ -779,15 +691,7 @@ private void throwImmutable() { private native void nativeIsNotNull(long nativePtr, long columnIndices[]); private native long nativeCount(long nativeQueryPtr, long start, long end, long limit); private native long nativeRemove(long nativeQueryPtr); - private native long nativeImportHandoverTableViewIntoSharedGroup(long handoverTableViewPtr, long callerSharedRealmPtr) throws BadVersionException; private native long nativeHandoverQuery(long callerSharedRealmPtr, long nativeQueryPtr); - private static native long nativeFindAllSortedWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long start, long end, long limit, long columnIndex, boolean ascending) throws BadVersionException; - private static native long nativeFindAllWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long start, long end, long limit) throws BadVersionException; - private static native long nativeGetDistinctViewWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long columnIndex) throws BadVersionException; - private static native long nativeFindWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long fromTableRow); - private static native long nativeFindAllMultiSortedWithHandover(long bgSharedRealmPtr, long nativeQueryPtr, long start, long end, long limit, long[] columnIndices, boolean[] ascending) throws BadVersionException; private static native long nativeImportHandoverRowIntoSharedGroup(long handoverRowPtr, long callerSharedRealmPtr); - public static native void nativeCloseQueryHandover(long nativePtr); - private static native long[] nativeBatchUpdateQueries(long bgSharedRealmPtr, long[] handoverQueries, long[][] parameters, long[][] queriesParameters, boolean[][] multiSortOrder) throws BadVersionException; private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableView.java b/realm/realm-library/src/main/java/io/realm/internal/TableView.java deleted file mode 100644 index 1a5cd59d48..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/TableView.java +++ /dev/null @@ -1,813 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import java.util.Date; -import java.util.List; - -import io.realm.RealmFieldType; -import io.realm.Sort; - -/** - * This class represents a view of a particular table. We can think of a tableview as a subset of a table. It contains - * less than or equal to the number of entries of a table. A table view is often a result of a query. - * - * The view doesn't copy data from the table, but contains merely a list of row-references into the original table - * with the real data. - */ -public class TableView implements TableOrView, NativeObject { - // Don't convert this into local variable and don't remove this. - // Core requests TableView to hold the Query reference. - @SuppressWarnings({"unused"}) - private final TableQuery query; // the query which created this TableView - private long version; // Last seen version number. Call refresh() to update this. - - protected long nativePtr; - private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - protected final Table parent; - private final Context context; - - - /** - * Creates a TableView. This constructor is used if the TableView is created from a table. - * - * @param context - * @param parent - * @param nativePtr - */ - protected TableView(Context context, Table parent, long nativePtr) { - this.context = context; - this.parent = parent; - this.nativePtr = nativePtr; - this.query = null; - context.addReference(this); - } - - /** - * Creates a TableView with already created Java TableView Object and a native native TableView object reference. - * The method is not supposed to be called by the user of the db. The method is for internal use only. - * - * @param context - * @param parent A table. - * @param nativePtr pointer to table view. - * @param query a reference to the query which the table view is based. - */ - protected TableView(Context context, Table parent, long nativePtr, TableQuery query) { - this.context = context; - this.parent = parent; - this.nativePtr = nativePtr; - this.query = query; - context.addReference(this); - } - - @Override - public long getNativePtr() { - return nativePtr; - } - - @Override - public long getNativeFinalizerPtr() { - return nativeFinalizerPtr; - } - - @Override - public Table getTable() { - return parent; - } - - /** - * Checks whether this table is empty or not. - * - * @return {@code true} if empty, otherwise {@code false}. - */ - @Override - public boolean isEmpty(){ - return size() == 0; - } - - /** - * Gets the number of entries/rows of this table. - * - * @return the number of rows. - */ - @Override - public long size() { - return nativeSize(nativePtr); - } - - /** - * Returns the index of the row in the source table. - * - * @param rowIndex row index in the TableView. - * @return the translated row number in the source table. - */ - public long getSourceRowIndex(long rowIndex) { - return nativeGetSourceRowIndex(nativePtr, rowIndex); - } - - /** - * Returns the number of columns in the table. - * - * @return the number of columns. - */ - @Override - public long getColumnCount() { - return nativeGetColumnCount(nativePtr); - } - - /** - * Returns the name of a column identified by columnIndex. Notice that the index is zero based. - * - * @param columnIndex the column index. - * @return the name of the column. - */ - @Override - public String getColumnName(long columnIndex) { - return nativeGetColumnName(nativePtr, columnIndex); - } - - /** - * Returns the 0-based index of a column based on the name. - * - * @param columnName column name. - * @return the index, {@code -1} if not found. - */ - @Override - public long getColumnIndex(String columnName) { - if (columnName == null) - throw new IllegalArgumentException("Column name can not be null."); - return nativeGetColumnIndex(nativePtr, columnName); - } - - /** - * Gets the type of a column identified by the columnIndex. - * - * @param columnIndex index of the column. - * @return type of the particular column. - */ - @Override - public RealmFieldType getColumnType(long columnIndex) { - return RealmFieldType.fromNativeValue(nativeGetColumnType(nativePtr, columnIndex)); - } - - /** - * Gets the value of the particular (integer) cell. - * - * @param columnIndex 0 based index value of the column. - * @param rowIndex 0 based row value of the column. - * @return value of the particular cell. - */ - @Override - public long getLong(long columnIndex, long rowIndex){ - return nativeGetLong(nativePtr, columnIndex, rowIndex); - } - - /** - * Gets the value of the particular (boolean) cell. - * - * @param columnIndex 0 based index value of the cell column. - * @param rowIndex 0 based index of the row. - * @return value of the particular cell. - */ - @Override - public boolean getBoolean(long columnIndex, long rowIndex){ - return nativeGetBoolean(nativePtr, columnIndex, rowIndex); - } - - /** - * Gets the value of the particular (float) cell. - * - * @param columnIndex 0 based index value of the cell column. - * @param rowIndex 0 based index of the row. - * @return value of the particular cell. - */ - @Override - public float getFloat(long columnIndex, long rowIndex){ - return nativeGetFloat(nativePtr, columnIndex, rowIndex); - } - - /** - * Gets the value of the particular (double) cell. - * - * @param columnIndex 0 based index value of the cell column. - * @param rowIndex 0 based index of the row. - * @return value of the particular cell. - */ - @Override - public double getDouble(long columnIndex, long rowIndex){ - return nativeGetDouble(nativePtr, columnIndex, rowIndex); - } - - /** - * Gets the value of the particular (date) cell. - * - * @param columnIndex 0 based index value of the cell column. - * @param rowIndex 0 based index of the row. - * @return value of the particular cell. - */ - @Override - public Date getDate(long columnIndex, long rowIndex){ - return new Date(nativeGetTimestamp(nativePtr, columnIndex, rowIndex)); - } - - /** - * Gets the value of a (string )cell. - * - * @param columnIndex 0 based index value of the column. - * @param rowIndex 0 based index of the row. - * @return value of the particular cell. - */ - @Override - public String getString(long columnIndex, long rowIndex){ - return nativeGetString(nativePtr, columnIndex, rowIndex); - } - - /** - * Gets the value of a (binary) cell. - * - * @param columnIndex 0 based index value of the cell column. - * @param rowIndex 0 based index value of the cell row. - * @return value of the particular cell. - */ - /* - @Override - public ByteBuffer getBinaryByteBuffer(long columnIndex, long rowIndex){ - return nativeGetBinary(nativePtr, columnIndex, rowIndex); - } - - protected native ByteBuffer nativeGetBinary(long nativeViewPtr, long columnIndex, long rowIndex); -*/ - - @Override - public byte[] getBinaryByteArray(long columnIndex, long rowIndex){ - return nativeGetByteArray(nativePtr, columnIndex, rowIndex); - } - - public long getLink(long columnIndex, long rowIndex){ - return nativeGetLink(nativePtr, columnIndex, rowIndex); - } - - @Override - public boolean isNull(long columnIndex, long rowIndex) { - return nativeIsNull(nativePtr, columnIndex, rowIndex); - } - - // Methods for setting values. - - /** - * Sets the value for a particular (integer) cell. - * - * @param columnIndex column index of the cell. - * @param rowIndex row index of the cell. - * @param value the value. - */ - @Override - public void setLong(long columnIndex, long rowIndex, long value, boolean isDefault){ - if (parent.isImmutable()) throwImmutable(); - nativeSetLong(nativePtr, columnIndex, rowIndex, value); - } - - /** - * Sets the value for a particular (boolean) cell. - * - * @param columnIndex column index of the cell. - * @param rowIndex row index of the cell. - * @param value the value. - */ - @Override - public void setBoolean(long columnIndex, long rowIndex, boolean value, boolean isDefault){ - if (parent.isImmutable()) throwImmutable(); - nativeSetBoolean(nativePtr, columnIndex, rowIndex, value); - } - - /** - * Sets the value for a particular (float) cell. - * - * @param columnIndex column index of the cell. - * @param rowIndex row index of the cell. - * @param value the value. - */ - @Override - public void setFloat(long columnIndex, long rowIndex, float value, boolean isDefault){ - if (parent.isImmutable()) throwImmutable(); - nativeSetFloat(nativePtr, columnIndex, rowIndex, value); - } - - /** - * Sets the value for a particular (double) cell. - * - * @param columnIndex column index of the cell. - * @param rowIndex row index of the cell. - * @param value the value. - */ - @Override - public void setDouble(long columnIndex, long rowIndex, double value, boolean isDefault){ - if (parent.isImmutable()) throwImmutable(); - nativeSetDouble(nativePtr, columnIndex, rowIndex, value); - } - - /** - * Sets the value for a particular (date) cell. - * - * @param columnIndex column index of the cell. - * @param rowIndex row index of the cell. - * @param value the value. - */ - @Override - public void setDate(long columnIndex, long rowIndex, Date value, boolean isDefault){ - if (parent.isImmutable()) throwImmutable(); - nativeSetTimestampValue(nativePtr, columnIndex, rowIndex, value.getTime()); - } - - /** - * Sets the value for a particular (sting) cell. - * - * @param columnIndex column index of the. - * @param rowIndex row index of the cell. - * @param value the value. - */ - @Override - public void setString(long columnIndex, long rowIndex, String value, boolean isDefault){ - if (parent.isImmutable()) throwImmutable(); - nativeSetString(nativePtr, columnIndex, rowIndex, value); - } - - /** - * Sets the value for a particular (binary) cell. - * - * @param columnIndex column index of the cell. - * @param rowIndex row index of the cell. - * @param data the value. - */ - /* - @Override - public void setBinaryByteBuffer(long columnIndex, long rowIndex, ByteBuffer data){ - if (immutable) throwImmutable(); - nativeSetBinary(nativePtr, columnIndex, rowIndex, data); - } - - protected native void nativeSetBinary(long nativeViewPtr, long columnIndex, long rowIndex, ByteBuffer data); - */ - - @Override - public void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data, boolean isDefault){ - if (parent.isImmutable()) throwImmutable(); - nativeSetByteArray(nativePtr, columnIndex, rowIndex, data); - } - - @Override - public void setLink(long columnIndex, long rowIndex, long value, boolean isDefault){ - if (parent.isImmutable()) throwImmutable(); - nativeSetLink(nativePtr, columnIndex, rowIndex, value); - } - - @Override - public void setNull(long columnIndex, long rowIndex, boolean isDefault) { - if (parent.isImmutable()) throwImmutable(); - getTable().setNull(columnIndex, getSourceRowIndex(rowIndex), isDefault); - } - - @Override - public boolean isNullLink(long columnIndex, long rowIndex) { - return nativeIsNullLink(nativePtr, columnIndex, rowIndex); - } - - @Override - public void nullifyLink(long columnIndex, long rowIndex) { - nativeNullifyLink(nativePtr, columnIndex, rowIndex); - } - - // Methods for deleting. - @Override - public void clear(){ - if (parent.isImmutable()) throwImmutable(); - nativeClear(nativePtr); - } - - /** - * Removes a particular row identified by the index from the tableview. - * The corresponding row of the underlying table also get deleted. - * - * @param rowIndex the row index. - */ - @Override - public void remove(long rowIndex){ - if (parent.isImmutable()) throwImmutable(); - nativeRemoveRow(nativePtr, rowIndex); - } - - @Override - public void removeFirst() { - if (parent.isImmutable()) throwImmutable(); - if (!isEmpty()) { - nativeRemoveRow(nativePtr, 0); - } - } - - @Override - public void removeLast() { - if (parent.isImmutable()) throwImmutable(); - if (!isEmpty()) { - nativeRemoveRow(nativePtr, size() - 1); - } - } - - // Search for first match - @Override - public long findFirstLong(long columnIndex, long value){ - return nativeFindFirstInt(nativePtr, columnIndex, value); - } - - @Override - public long findFirstBoolean(long columnIndex, boolean value) { - return nativeFindFirstBool(nativePtr, columnIndex, value); - } - - @Override - public long findFirstFloat(long columnIndex, float value) { - return nativeFindFirstFloat(nativePtr, columnIndex, value); - } - - @Override - public long findFirstDouble(long columnIndex, double value) { - return nativeFindFirstDouble(nativePtr, columnIndex, value); - } - - @Override - public long findFirstDate(long columnIndex, Date date) { - // FIXME: waiting for implementation - return Table.NO_MATCH; - // return nativeFindFirstDate(nativePtr, columnIndex, date.getTime()); - } - - @Override - public long findFirstString(long columnIndex, String value){ - return nativeFindFirstString(nativePtr, columnIndex, value); - } - - // Search for all matches - - // TODO.. - @Override - public long lowerBoundLong(long columnIndex, long value) { - throw new RuntimeException("Not implemented yet"); - } - - // TODO.. - @Override - public long upperBoundLong(long columnIndex, long value) { - throw new RuntimeException("Not implemented yet"); - } - - @Override - public TableView findAllLong(long columnIndex, long value){ - long nativeViewPtr = nativeFindAllInt(nativePtr, columnIndex, value); - return new TableView(this.context, this.parent, nativeViewPtr); - } - - @Override - public TableView findAllBoolean(long columnIndex, boolean value) { - long nativeViewPtr = nativeFindAllBool(nativePtr, columnIndex, value); - return new TableView(this.context, this.parent, nativeViewPtr); - } - - @Override - public TableView findAllFloat(long columnIndex, float value) { - long nativeViewPtr = nativeFindAllFloat(nativePtr, columnIndex, value); - return new TableView(this.context, this.parent, nativeViewPtr); - } - - @Override - public TableView findAllDouble(long columnIndex, double value) { - long nativeViewPtr = nativeFindAllDouble(nativePtr, columnIndex, value); - return new TableView(this.context, this.parent, nativeViewPtr); - } - - @Override - public TableView findAllString(long columnIndex, String value){ - long nativeViewPtr = nativeFindAllString(nativePtr, columnIndex, value); - return new TableView(this.context, this.parent, nativeViewPtr); - } - - // - // Integer Aggregates - // - - /** - * Calculates the sum of the values in a particular column of this tableview. - * - * Note: the type of the column marked by the columnIndex has to be of type RealmFieldType.INTEGER. - * - * @param columnIndex column index. - * @return the sum of the values in the column. - */ - @Override - public long sumLong(long columnIndex){ - return nativeSumInt(nativePtr, columnIndex); - } - - /** - * Returns the maximum value of the cells in a column. - * - * Note: for this method to work the Type of the column identified by the columnIndex has to be - * RealmFieldType.INTEGER. - * - * @param columnIndex column index. - * @return the maximum value. - */ - @Override - public Long maximumLong(long columnIndex){ - return nativeMaximumInt(nativePtr, columnIndex); - } - - /** - * Returns the minimum value of the cells in a column. - * - * Note: for this method to work the Type of the column identified by the columnIndex has to be - * RealmFieldType.INTEGER. - * - * @param columnIndex column index. - * @return the minimum value. - */ - @Override - public Long minimumLong(long columnIndex){ - return nativeMinimumInt(nativePtr, columnIndex); - } - - @Override - public double averageLong(long columnIndex) { - return nativeAverageInt(nativePtr, columnIndex); - } - - // Float aggregates - - @Override - public double sumFloat(long columnIndex){ - return nativeSumFloat(nativePtr, columnIndex); - } - - @Override - public Float maximumFloat(long columnIndex){ - return nativeMaximumFloat(nativePtr, columnIndex); - } - - @Override - public Float minimumFloat(long columnIndex){ - return nativeMinimumFloat(nativePtr, columnIndex); - } - - @Override - public double averageFloat(long columnIndex) { - return nativeAverageFloat(nativePtr, columnIndex); - } - - // Double aggregates - - @Override - public double sumDouble(long columnIndex){ - return nativeSumDouble(nativePtr, columnIndex); - } - - @Override - public Double maximumDouble(long columnIndex){ - return nativeMaximumDouble(nativePtr, columnIndex); - } - - - @Override - public Double minimumDouble(long columnIndex){ - return nativeMinimumDouble(nativePtr, columnIndex); - } - - @Override - public double averageDouble(long columnIndex) { - return nativeAverageDouble(nativePtr, columnIndex); - } - - // Date aggregates - - @Override - public Date maximumDate(long columnIndex) { - Long result = nativeMaximumTimestamp(nativePtr, columnIndex); - if (result == null) { - return null; - } - return new Date(result); - } - - @Override - public Date minimumDate(long columnIndex) { - Long result = nativeMinimumTimestamp(nativePtr, columnIndex); - if (result == null) { - return null; - } - return new Date(result); - } - - // Sorting - public void sort(long columnIndex, Sort sortOrder) { - // Don't check for immutable. Sorting does not modify original table - nativeSort(nativePtr, columnIndex, sortOrder.getValue()); - } - - public void sort(long columnIndex) { - // Don't check for immutable. Sorting does not modify original table - nativeSort(nativePtr, columnIndex, true); - } - - public void sort(List columnIndices, Sort[] sortOrders) { - long indices[] = new long[columnIndices.size()]; - for (int i = 0; i < columnIndices.size(); i++) { - indices[i] = columnIndices.get(i); - } - boolean nativeSortOrder[] = TableQuery.getNativeSortOrderValues(sortOrders); - nativeSortMulti(nativePtr, indices, nativeSortOrder); - } - - @Override - public String toJson() { - return nativeToJson(nativePtr); - } - - @Override - public String toString() { - long columnCount = getColumnCount(); - StringBuilder stringBuilder = new StringBuilder("The TableView contains "); - stringBuilder.append(columnCount); - stringBuilder.append(" columns: "); - - for (int i = 0; i < columnCount; i++) { - if (i != 0) { - stringBuilder.append(", "); - } - stringBuilder.append(getColumnName(i)); - } - stringBuilder.append("."); - - stringBuilder.append(" And "); - stringBuilder.append(size()); - stringBuilder.append(" rows."); - - return stringBuilder.toString(); - } - - @Override - public TableQuery where() { - long nativeQueryPtr = nativeWhere(nativePtr); - return new TableQuery(this.context, this.parent, nativeQueryPtr, this); - } - - /** - * Finds a row in the parent table with the given {@code rowIndex} - * - * @param rowIndex the index of the row. - * @return the row index or -1 for not found. - */ - @Override - public long sourceRowIndex(long rowIndex) { - return nativeFindBySourceNdx(nativePtr, rowIndex); - } - - - private void throwImmutable() { - throw new IllegalStateException("Realm data can only be changed inside a write transaction."); - } - - @Override - public long count(long columnIndex, String value) { - // TODO: implement - throw new RuntimeException("Not implemented yet."); - } - - @Override - public long getVersion() { - return version; - } - - @Override - public Table pivot(long stringCol, long intCol, PivotType pivotType){ - if (! this.getColumnType(stringCol).equals(RealmFieldType.STRING )) - throw new UnsupportedOperationException("Group by column must be of type String"); - if (! this.getColumnType(intCol).equals(RealmFieldType.INTEGER )) - throw new UnsupportedOperationException("Aggregation column must be of type Int"); - Table result = new Table(); - nativePivot(nativePtr, stringCol, intCol, pivotType.value, result.getNativePtr()); - return result; - } - - /** - * Removes rows that are duplicated with respect to the column set passed as argument. - * If two rows are indentical (for the given set of distinct-columns), then the last row is - * removed unless sorted, in which case the first object is returned. - * - * @param columnIndex the column index. - * @throws IllegalArgumentException if the type of the column is unsupported. - * @throws UnsupportedOperationException if a column is not indexed. - */ - public void distinct(long columnIndex) { - nativeDistinct(nativePtr, columnIndex); - } - - /** - * If two rows are indentical (for the given set of distinct-columns), then the last row is - * removed unless sorted, in which case the first object is returned. - * Each time distinct() gets called, it will first fetch the full original TableView contents - * and then apply distinct() on that, invalidating previous distinct(). - * - * @param columnIndexes the column indexes. - * @throws IllegalArgumentException if a column is unsupported type, or is not indexed. - */ - public void distinct(List columnIndexes) { - long[] indexes = new long[columnIndexes.size()]; - for (int i = 0; i < columnIndexes.size(); i++) { - indexes[i] = columnIndexes.get(i); - } - nativeDistinctMulti(nativePtr, indexes); - } - - @Override - public long syncIfNeeded() { - version = nativeSyncIfNeeded(nativePtr); - return version; - } - - private native long nativeSize(long nativeViewPtr); - private native long nativeGetSourceRowIndex(long nativeViewPtr, long rowIndex); - private native long nativeGetColumnCount(long nativeViewPtr); - private native String nativeGetColumnName(long nativeViewPtr, long columnIndex); - private native long nativeGetColumnIndex(long nativeViewPtr, String columnName); - private native int nativeGetColumnType(long nativeViewPtr, long columnIndex); - private native long nativeGetLong(long nativeViewPtr, long columnIndex, long rowIndex); - private native boolean nativeGetBoolean(long nativeViewPtr, long columnIndex, long rowIndex); - private native float nativeGetFloat(long nativeViewPtr, long columnIndex, long rowIndex); - private native double nativeGetDouble(long nativeViewPtr, long columnIndex, long rowIndex); - private native long nativeGetTimestamp(long nativeViewPtr, long columnIndex, long rowIndex); - private native String nativeGetString(long nativeViewPtr, long columnIndex, long rowIndex); - private native byte[] nativeGetByteArray(long nativePtr, long columnIndex, long rowIndex); - private native long nativeGetLink(long nativeViewPtr, long columnIndex, long rowIndex); - private native boolean nativeIsNull(long nativePtr, long columnIndex, long rowIndex); - private native void nativeSetLong(long nativeViewPtr, long columnIndex, long rowIndex, long value); - private native void nativeSetBoolean(long nativeViewPtr, long columnIndex, long rowIndex, boolean value); - private native void nativeSetFloat(long nativeViewPtr, long columnIndex, long rowIndex, float value); - private native void nativeSetDouble(long nativeViewPtr, long columnIndex, long rowIndex, double value); - private native void nativeSetTimestampValue(long nativePtr, long columnIndex, long rowIndex, long dateTimeValue); - private native void nativeSetString(long nativeViewPtr, long columnIndex, long rowIndex, String value); - private native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data); - private native void nativeSetLink(long nativeViewPtr, long columnIndex, long rowIndex, long value); - private native boolean nativeIsNullLink(long nativePtr, long columnIndex, long rowIndex); - private native void nativeNullifyLink(long nativePtr, long columnIndex, long rowIndex); - private native void nativeClear(long nativeViewPtr); - private native void nativeRemoveRow(long nativeViewPtr, long rowIndex); - private native long nativeFindFirstInt(long nativeTableViewPtr, long columnIndex, long value); - private native long nativeFindFirstBool(long nativePtr, long columnIndex, boolean value); - private native long nativeFindFirstFloat(long nativePtr, long columnIndex, float value); - private native long nativeFindFirstDouble(long nativePtr, long columnIndex, double value); - private native long nativeFindFirstDate(long nativeTablePtr, long columnIndex, long dateTimeValue); - private native long nativeFindFirstString(long nativePtr, long columnIndex, String value); - private native long nativeFindAllInt(long nativePtr, long columnIndex, long value); - private native long nativeFindAllBool(long nativePtr, long columnIndex, boolean value); - private native long nativeFindAllFloat(long nativePtr, long columnIndex, float value); - private native long nativeFindAllDouble(long nativePtr, long columnIndex, double value); - private native long nativeFindAllDate(long nativePtr, long columnIndex, long dateTimeValue); - private native long nativeFindBySourceNdx(long nativePtr, long rowIndex); - private native long nativeSumInt(long nativeViewPtr, long columnIndex); - private native long nativeFindAllString(long nativePtr, long columnIndex, String value); - private native Long nativeMaximumInt(long nativeViewPtr, long columnIndex); - private native Long nativeMinimumInt(long nativeViewPtr, long columnIndex); - private native double nativeAverageInt(long nativePtr, long columnIndex); - private native double nativeSumFloat(long nativeViewPtr, long columnIndex); - private native Float nativeMaximumFloat(long nativeViewPtr, long columnIndex); - private native Float nativeMinimumFloat(long nativeViewPtr, long columnIndex); - private native double nativeAverageFloat(long nativePtr, long columnIndex); - private native double nativeSumDouble(long nativeViewPtr, long columnIndex); - private native Double nativeMaximumDouble(long nativeViewPtr, long columnIndex); - private native Double nativeMinimumDouble(long nativeViewPtr, long columnIndex); - private native double nativeAverageDouble(long nativePtr, long columnIndex); - private native Long nativeMaximumTimestamp(long nativePtr, long columnIndex); - private native Long nativeMinimumTimestamp(long nativePtr, long columnIndex); - private native void nativeSort(long nativeTableViewPtr, long columnIndex, boolean sortOrder); - private native void nativeSortMulti(long nativeTableViewPtr, long columnIndices[], boolean ascending[]); - private native long createNativeTableView(Table table, long nativeTablePtr); - private native String nativeToJson(long nativeViewPtr); - private native long nativeWhere(long nativeViewPtr); - private native void nativePivot(long nativeTablePtr, long stringCol, long intCol, int pivotType, long result); - private native void nativeDistinct(long nativeViewPtr, long columnIndex); - private native long nativeSyncIfNeeded(long nativeTablePtr); - private native void nativeDistinctMulti(long nativeViewPtr, long[] columnIndexes); - private native long nativeSync(long nativeTablePtr); - private static native long nativeGetFinalizerPtr(); -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java index b711d45de9..0a2e09d421 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java @@ -75,64 +75,6 @@ public static Builder.RealmConfigurationStep newBuilder() { @Override public void run() { - SharedRealm sharedRealm = null; - try { - sharedRealm = SharedRealm.getInstance(realmConfiguration); - - Result result; - boolean updateSuccessful; - if (updateMode == MODE_UPDATE_REALM_RESULTS) { - result = Result.newRealmResultsResponse(); - AlignedQueriesParameters alignedParameters = prepareQueriesParameters(); - long[] handoverTableViewPointer = TableQuery.batchUpdateQueries(sharedRealm, - alignedParameters.handoverQueries, - alignedParameters.queriesParameters, - alignedParameters.multiSortColumnIndices, - alignedParameters.multiSortOrder); - swapPointers(result, handoverTableViewPointer); - updateSuccessful = true; - result.versionID = sharedRealm.getVersionID(); - - } else { - result = Result.newRealmObjectResponse(); - updateSuccessful = updateRealmObjectQuery(sharedRealm, result); - result.versionID = sharedRealm.getVersionID(); - } - - RealmNotifier notifier = callerNotifier.get(); - if (updateSuccessful && !isTaskCancelled() && notifier != null) { - switch (event) { - case COMPLETE_ASYNC_RESULTS: - notifier.completeAsyncResults(result); - break; - case COMPLETE_ASYNC_OBJECT: - notifier.completeAsyncObject(result); - break; - case COMPLETE_UPDATE_ASYNC_QUERIES: - notifier.completeUpdateAsyncQueries(result); - break; - default: - throw new IllegalStateException(String.format("%s is not handled here.", event)); - } - } - - } catch (BadVersionException e) { - // In some rare race conditions, this can happen. In that case, just ignore the error. - RealmLog.debug("Query update task could not complete due to a BadVersionException. " + - "Retry is scheduled by a REALM_CHANGED event."); - - } catch (Throwable e) { - RealmLog.error(e); - RealmNotifier notifier = callerNotifier.get(); - if (notifier!= null) { - notifier.throwBackgroundException(e); - } - - } finally { - if (sharedRealm != null) { - sharedRealm.close(); - } - } } private AlignedQueriesParameters prepareQueriesParameters() { @@ -199,25 +141,6 @@ private void swapPointers(Result result, long[] handoverTableViewPointer) { } } - private boolean updateRealmObjectQuery(SharedRealm sharedRealm, Result result) { - if (!isTaskCancelled()) { - switch (realmObjectEntry.queryArguments.type) { - case ArgumentsHolder.TYPE_FIND_FIRST: { - long handoverRowPointer = TableQuery.findWithHandover(sharedRealm, - realmObjectEntry.handoverQueryPointer); - result.updatedRow.put(realmObjectEntry.element, handoverRowPointer); - break; - } - default: - throw new IllegalArgumentException("Query mode " + realmObjectEntry.queryArguments.type + " not supported"); - } - } else { - TableQuery.nativeCloseQueryHandover(realmObjectEntry.handoverQueryPointer); - return false; - } - return true; - } - private boolean isTaskCancelled() { // no point continuing if the caller thread was stopped or this thread was interrupted return Thread.currentThread().isInterrupted(); From 42ed8cc466e52842eb6466f4803141c3dc3ac975 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 5 Dec 2016 20:46:16 +0800 Subject: [PATCH 0262/2110] Deliver global notification through OS did_change Remove AndroidNotifier which will have a common implementation among all platforms. --- .../java/io/realm/DynamicRealmTests.java | 2 +- .../java/io/realm/RealmAsyncQueryTests.java | 16 +- .../io/realm/RealmChangeListenerTests.java | 26 ++- .../src/main/cpp/java_binding_context.cpp | 7 + .../src/main/cpp/java_binding_context.hpp | 2 + .../src/main/cpp/jni_util/method.hpp | 56 ++++++ .../main/java/io/realm/AndroidNotifier.java | 171 ------------------ .../src/main/java/io/realm/BaseRealm.java | 24 +-- .../main/java/io/realm/HandlerController.java | 8 + .../src/main/java/io/realm/Realm.java | 8 +- .../src/main/java/io/realm/RealmQuery.java | 93 ---------- .../java/io/realm/internal/Capabilities.java | 21 +++ .../java/io/realm/internal/ObserverPair.java | 27 +++ .../java/io/realm/internal/RealmNotifier.java | 86 ++++++--- .../java/io/realm/internal/RowNotifier.java | 6 +- .../internal/android/AndroidCapabilities.java | 36 ++++ 16 files changed, 269 insertions(+), 320 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/jni_util/method.hpp delete mode 100644 realm/realm-library/src/main/java/io/realm/AndroidNotifier.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/Capabilities.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index cca89c96ca..98756f6827 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -563,7 +563,7 @@ public void run() { } }; - dynamicRealm.setHandler(handler); + //dynamicRealm.setHandler(handler); dynamicRealmObject[0] = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 4, 9) .findFirstAsync(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 9375d6efd8..0244095425 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -505,7 +505,7 @@ public boolean onInterceptInMessage(int what) { return false; } }; - realm.setHandler(handler); + //realm.setHandler(handler); // 3. Create a async query final RealmResults realmResults = realm.where(AllTypes.class) @@ -573,7 +573,7 @@ public void doInBackground(Realm realm) { return false; } }; - realm.setHandler(handler); + //realm.setHandler(handler); // 2. Create 2 async queries and check they are not loaded final RealmResults realmResults1 = realm.where(AllTypes.class).findAllAsync(); @@ -674,7 +674,7 @@ public boolean onInterceptInMessage(int what) { return false; } }; - realm.setHandler(handler); + //realm.setHandler(handler); // Create async query and verify it has not been loaded. final RealmResults realmResults = realm.where(AllTypes.class) @@ -743,7 +743,7 @@ public boolean onInterceptInMessage(int what) { return false; } }; - realm.setHandler(handler); + //realm.setHandler(handler); Realm.asyncTaskExecutor.pause(); // Create async queries and check they haven't completed @@ -977,7 +977,7 @@ public boolean onInterceptInMessage(int what) { return false; } }; - realm.setHandler(handler); + //realm.setHandler(handler); // Create a async query and verify it is not still loaded. final AllTypes realmResults = realm.where(AllTypes.class) @@ -1074,7 +1074,7 @@ public boolean onInterceptInMessage(int what) { return false; } }; - realm.setHandler(handler); + //realm.setHandler(handler); // 3. This will add a task to the paused asyncTaskExecutor final RealmResults realmResults = realm.where(AllTypes.class) @@ -1151,7 +1151,7 @@ public void run() { return false; } }; - realm.setHandler(handler); + //realm.setHandler(handler); // 3. Create 2 async queries final RealmResults realmResults1 = realm.where(AllTypes.class) @@ -1290,7 +1290,7 @@ public void doInBackground(Realm realm) { return false; } }; - realm.setHandler(handler); + //realm.setHandler(handler); // 3. Create 2 async queries final RealmResults realmResults1 = realm.where(AllTypes.class) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java index cb50a83935..ebb0a0bf1d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java @@ -29,6 +29,8 @@ import io.realm.entities.Cat; import io.realm.entities.Dog; import io.realm.entities.pojo.AllTypesRealmModel; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -146,11 +148,21 @@ public void returnedRealmObjectIsNotNull() { cat.addChangeListener(new RealmChangeListener() { @Override public void onChange(Cat object) { - assertEquals("cat1", object.getName()); - looperThread.testComplete(); + //assertEquals("cat1", object.getName()); + //looperThread.testComplete(); + Cat cat = object; } }); + cat.getAge(); + /* + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.where(Cat.class).findFirst().setName("cat1"); + } + }); + */ realm.beginTransaction(); cat.setName("cat1"); realm.commitTransaction(); @@ -232,13 +244,23 @@ public void onChange(RealmResults result) { // FIXME: Used for DEV. Remove before merge public void myTest() { Realm realm = looperThread.realm; + RealmLog.setLevel(LogLevel.ALL); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class); + } + }); final RealmResults cats = realm.where(Cat.class).findAll(); final RealmResults dogs = realm.where(Dog.class).findAll(); + final RealmResults allTypes = realm.where(AllTypes.class).findAll(); + double avg = allTypes.average(AllTypes.FIELD_DOUBLE); looperThread.keepStrongReference.add(cats); looperThread.keepStrongReference.add(dogs); cats.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults result) { + Cat cat = result.first(); assertEquals("cat1", result.first().getName()); assertEquals("dog1", dogs.first().getName()); looperThread.testComplete(); diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index 4a9270cb14..76eaf93d8a 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -65,11 +65,13 @@ JavaBindingContext::~JavaBindingContext() void JavaBindingContext::changes_available() { + /* jobject notifier = m_local_jni_env->NewLocalRef(m_realm_notifier); if (notifier) { m_local_jni_env->CallVoidMethod(m_realm_notifier, m_notify_by_other_method); m_local_jni_env->DeleteLocalRef(notifier); } + */ } std::vector JavaBindingContext::get_observed_rows() @@ -113,5 +115,10 @@ void JavaBindingContext::did_change(std::vector c m_local_jni_env->CallVoidMethod(observer, m_observer_notify_listener); } m_local_jni_env->CallVoidMethod(m_row_notifier, m_clear_row_refs); + jobject notifier = m_local_jni_env->NewLocalRef(m_realm_notifier); + if (notifier) { + m_local_jni_env->CallVoidMethod(m_realm_notifier, m_notify_by_other_method); + m_local_jni_env->DeleteLocalRef(notifier); + } } diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index 2632cd3806..0a338dd6ac 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -45,6 +45,8 @@ class JavaBindingContext final : public BindingContext { jobject m_realm_notifier; // Method IDs from RealmNotifier implementation. Cache them as member vars. jmethodID m_notify_by_other_method; + jmethodID m_realm_notifier_on_change; + // A weak global ref to the RowNotifier object. Java should hold a strong ref to it. jobject m_row_notifier; // RowNotifier.getObservers() diff --git a/realm/realm-library/src/main/cpp/jni_util/method.hpp b/realm/realm-library/src/main/cpp/jni_util/method.hpp new file mode 100644 index 0000000000..e56e9ed948 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/method.hpp @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_METHOD_HPP +#define REALM_JNI_UTIL_METHOD_HPP + +#include +#include + +namespace realm { +namespace jni_util { + +class JniMethod { +public: + JniMethod(JNIEnv *env, jobject obj, const char* method_name, const char* signature) { + jclass cls = env->GetObjectClass(obj); + m_method_id = env->GetMethodID(cls, method_name, signature); + env->DeleteLocalRef(cls); + } + + JniMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature) { + jclass cls = env->FindClass(class_name); + if (cls == NULL) { + // TODO: Throw a cpp exception instead. + ThrowException(env, ClassNotFound, class_name); + m_method_id = nullptr; + } else { + m_method_id = env->GetMethodID(cls, method_name, signature); + } + } + + ~JniMethod() { } + + inline operator jmethodID&() const { return m_method_id; } + +private: + jmethodID m_method_id; +}; + +} // namespace realm +} // namespace jni_util + +#endif //REALM_JNI_UTIL_METHOD_HPP diff --git a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java deleted file mode 100644 index c2949528b0..0000000000 --- a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.os.Handler; -import android.os.Looper; -import android.os.Message; - -import io.realm.internal.HandlerControllerConstants; -import io.realm.internal.RealmNotifier; -import io.realm.internal.async.QueryUpdateTask; -import io.realm.log.RealmLog; - -/** - * Implementation of {@link RealmNotifier} for Android based on {@link Handler}. - */ -class AndroidNotifier implements RealmNotifier { - private Handler handler; - - public AndroidNotifier(HandlerController handlerController) { - if (isAutoRefreshAvailable()) { - handler = new Handler(handlerController); - } - } - - // Called by Java when transaction committed to send LOCAL_COMMIT to current thread's handler. - @Override - public void notifyCommitByLocalThread() { - if (handler == null) { - return; - } - - // Force any updates on the current thread to the front the queue. Doing this is mostly - // relevant on the UI thread where it could otherwise process a motion event before the - // REALM_CHANGED event. This could in turn cause a UI component like ListView to crash. See - // https://github.com/realm/realm-android-adapters/issues/11 for such a case. - // Other Looper threads could process similar events. For that reason all looper threads will - // prioritize local commits. - // - // If a user is doing commits inside a RealmChangeListener this can cause the Looper thread to get - // event starved as it only starts handling Realm events instead. This is an acceptable risk as - // that behaviour indicate a user bug. Previously this would be hidden as the UI would still - // be responsive. - /* - Message msg = Message.obtain(); - msg.what = HandlerControllerConstants.LOCAL_COMMIT; - if (!handler.hasMessages(HandlerControllerConstants.LOCAL_COMMIT)) { - handler.removeMessages(HandlerControllerConstants.REALM_CHANGED); - handler.sendMessageAtFrontOfQueue(msg); - } - */ - } - - // This is called by OS when other thread/process changes the Realm. - // This is getting called on the same thread which created the Realm. - // |---------------------------------------------------------------+--------------+------------------------------------------------| - // | Thread A | Thread B | Daemon Thread | - // |---------------------------------------------------------------+--------------+------------------------------------------------| - // | | Make changes | | - // | | | Detect and notify thread A through JNI ALooper | - // | Call OS's Realm::notify() from OS's ALooper callback | | | - // | Realm::notify() calls JavaBindingContext:change_available() | | | - // | change_available calls into this method to send REALM_CHANGED | | | - // |---------------------------------------------------------------+--------------+------------------------------------------------| - @Override - public void notifyCommitByOtherThread() { - /* - if (handler == null) { - return; - } - - // Note there is a race condition with handler.hasMessages() and handler.sendEmptyMessage() - // as the target thread consumes messages at the same time. In this case it is not a problem as worst - // case we end up with two REALM_CHANGED messages in the queue. - boolean messageHandled = true; - if (!handler.hasMessages(HandlerControllerConstants.REALM_CHANGED) && - !handler.hasMessages(HandlerControllerConstants.LOCAL_COMMIT)) { - messageHandled = handler.sendEmptyMessage(HandlerControllerConstants.REALM_CHANGED); - } - if (!messageHandled) { - RealmLog.warn("Cannot update Looper threads when the Looper has quit. Use realm.setAutoRefresh(false) " + - "to prevent this."); - } - */ - } - - @Override - public void post(Runnable runnable) { - Looper looper = handler.getLooper(); - if (looper.getThread().isAlive()) { // The receiving thread is alive - handler.post(runnable); - } - } - - @Override - public boolean isValid() { - return handler != null; - } - - @Override - public void close() { - if (handler != null) { - handler.removeCallbacksAndMessages(null); - handler = null; - } - } - - @Override - public void completeAsyncResults(QueryUpdateTask.Result result) { - Looper looper = handler.getLooper(); - if (looper.getThread().isAlive()) { // The receiving thread is alive - handler.obtainMessage(HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS, result).sendToTarget(); - } - } - - @Override - public void completeAsyncObject(QueryUpdateTask.Result result) { - Looper looper = handler.getLooper(); - if (looper.getThread().isAlive()) { // The receiving thread is alive - handler.obtainMessage(HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT, result).sendToTarget(); - } - } - - @Override - public void throwBackgroundException(Throwable throwable) { - Looper looper = handler.getLooper(); - if (looper.getThread().isAlive()) { // The receiving thread is alive - handler.obtainMessage( - HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION, new Error(throwable)).sendToTarget(); - } - } - - @Override - public void completeUpdateAsyncQueries(QueryUpdateTask.Result result) { - Looper looper = handler.getLooper(); - if (looper.getThread().isAlive()) { // The receiving thread is alive - handler.obtainMessage(HandlerControllerConstants.COMPLETED_UPDATE_ASYNC_QUERIES, result).sendToTarget(); - } - } - - private static boolean isAutoRefreshAvailable() { - return (Looper.myLooper() != null && !isIntentServiceThread()); - } - - private static boolean isIntentServiceThread() { - // Tries to determine if a thread is an IntentService thread. No public API can detect this, - // so use the thread name as a heuristic: - // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/IntentService.java#108 - String threadName = Thread.currentThread().getName(); - return threadName != null && threadName.startsWith("IntentService["); - } - - // For testing purpose only. Should be removed ideally. - public void setHandler(Handler handler) { - this.handler = handler; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index a32f249412..89604e52a4 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -30,6 +30,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.InvalidRow; +import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.SharedRealm; import io.realm.internal.ColumnInfo; @@ -79,7 +80,7 @@ protected BaseRealm(RealmConfiguration configuration) { this.configuration = configuration; this.handlerController = new HandlerController(this); - this.sharedRealm = SharedRealm.getInstance(configuration, new AndroidNotifier(this.handlerController), + this.sharedRealm = SharedRealm.getInstance(configuration, new RealmNotifier(), !(this instanceof Realm) ? null : new SharedRealm.SchemaVersionListener() { @Override @@ -138,7 +139,7 @@ protected void addListener(RealmChangeListener listener) { if (!handlerController.isAutoRefreshEnabled()) { throw new IllegalStateException("You can't register a listener from a non-Looper or IntentService thread."); } - handlerController.addChangeListener(listener); + sharedRealm.realmNotifier.addChangeListener(this, listener); } /** @@ -157,7 +158,7 @@ public void removeChangeListener(RealmChangeListener listen if (!handlerController.isAutoRefreshEnabled()) { throw new IllegalStateException("You can't remove a listener from a non-Looper thread "); } - handlerController.removeChangeListener(listener); + sharedRealm.realmNotifier.removeChangeListener(this, listener); } /** @@ -191,16 +192,9 @@ public void removeAllChangeListeners() { if (!handlerController.isAutoRefreshEnabled()) { throw new IllegalStateException("You can't remove listeners from a non-Looper thread "); } - handlerController.removeAllChangeListeners(); + sharedRealm.realmNotifier.removeAllChangeListeners(); } - // WARNING: If this method is used after calling any async method, the old handler will still be used. - // package private, for test purpose only - void setHandler(Handler handler) { - ((AndroidNotifier)sharedRealm.realmNotifier).setHandler(handler); - } - - /** * Writes a compacted copy of the Realm to the given destination File. *

          @@ -350,11 +344,14 @@ void commitTransaction(boolean notifyLocalThread) { ObjectServerFacade.getFacade(configuration.isSyncConfiguration()) .notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); + // FIXME: Check if this is still needed. // Sometimes we don't want to notify the local thread about commits, e.g. creating a completely new Realm // file will make a commit in order to create the schema. Users should not be notified about that. + /* if (notifyLocalThread) { sharedRealm.realmNotifier.notifyCommitByLocalThread(); } + */ } /** @@ -663,11 +660,6 @@ public void onResult(int count) { } } - // Return true if this Realm can receive notifications. - boolean hasValidNotifier() { - return sharedRealm.realmNotifier != null && sharedRealm.realmNotifier.isValid(); - } - @Override protected void finalize() throws Throwable { if (sharedRealm != null && !sharedRealm.isClosed()) { diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java index a0b06bc580..b3c28c8420 100644 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java @@ -267,6 +267,7 @@ private void notifyGlobalListeners() { } private void updateAsyncEmptyRealmObject() { + /* Iterator, RealmQuery>> iterator = emptyAsyncRealmObject.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry, RealmQuery> next = iterator.next(); @@ -285,6 +286,7 @@ private void updateAsyncEmptyRealmObject() { iterator.remove(); } } + */ } /** @@ -382,6 +384,7 @@ private void notifyRealmObjectCallbacks() { } private void updateAsyncQueries() { + /* if (updateAsyncQueriesTask != null && !updateAsyncQueriesTask.isDone()) { // try to cancel any pending update since we're submitting a new one anyway updateAsyncQueriesTask.cancel(true); @@ -425,6 +428,7 @@ private void updateAsyncQueries() { .build(); updateAsyncQueriesTask = Realm.asyncTaskExecutor.submitQueryUpdate(queryUpdateTask); } + */ } private void realmChanged(boolean localCommit) { @@ -458,6 +462,7 @@ private void realmChanged(boolean localCommit) { } private void completedAsyncRealmResults(QueryUpdateTask.Result result) { + /* Set>> updatedTableViewsKeys = result.updatedTableViews.keySet(); if (updatedTableViewsKeys.size() > 0) { WeakReference> weakRealmResults = updatedTableViewsKeys.iterator().next(); @@ -526,6 +531,7 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) { } } } + */ } private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) { @@ -604,6 +610,7 @@ private void notifyAsyncTransactionCallbacks() { } private void completedAsyncRealmObject(QueryUpdateTask.Result result) { + /* Set> updatedRowKey = result.updatedRow.keySet(); if (updatedRowKey.size() > 0) { WeakReference realmObjectWeakReference = updatedRowKey.iterator().next(); @@ -664,6 +671,7 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) { } } // else: element GC'd in the meanwhile } + */ } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 506266aa72..dab358da60 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1327,10 +1327,13 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, fin // If the user provided a Callback then we make sure, the current Realm has a Handler // we can use to deliver the result + // FIXME: Implement checking here. + /* if ((onSuccess != null || onError != null) && !hasValidNotifier()) { throw new IllegalStateException("Your Realm is opened from a thread without a Looper" + " and you provided a callback, we need a Handler to invoke your callback"); } + */ // We need to use the same configuration to open a background SharedRealm (i.e Realm) // to perform the transaction @@ -1370,12 +1373,14 @@ public void run() { bgRealm.close(); } + // This will be treated like a special REALM_CHANGED event + // FIXME: Find a way to deliver the callback with current architecture + /* final Throwable backgroundException = exception[0]; // Send response as the final step to ensure the bg thread quit before others get the response! if (hasValidNotifier() && !Thread.currentThread().isInterrupted()) { if (transactionCommitted) { - // This will be treated like a special REALM_CHANGED event sharedRealm.realmNotifier.post(new Runnable() { @Override public void run() { @@ -1429,6 +1434,7 @@ public void run() { } } } + */ } } }); diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 337e72d985..6b93e711b4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -66,10 +66,6 @@ public final class RealmQuery { private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; - - private final static Long INVALID_NATIVE_POINTER = 0L; - private ArgumentsHolder argumentsHolder; - /** * Creates a query for objects of a given class from a {@link Realm}. * @@ -1326,7 +1322,6 @@ public RealmQuery isNotEmpty(String fieldName) { * is not indexed, or points to linked fields. */ public RealmResults distinct(String fieldName) { - checkQueryIsNotReused(); SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(query.getTable(), fieldName); Collection collection = new Collection(realm.sharedRealm, query, null, distinctDescriptor); return createRealmResults(collection); @@ -1352,7 +1347,6 @@ public RealmResults distinctAsync(String fieldName) { * is an unsupported type, or points to a linked field. */ public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { - checkQueryIsNotReused(); String[] fieldNames = new String[1 + remainingFieldNames.length]; fieldNames[0] = firstFieldName; @@ -1513,7 +1507,6 @@ public long count() { */ @SuppressWarnings("unchecked") public RealmResults findAll() { - checkQueryIsNotReused(); Collection collection = new Collection(realm.sharedRealm, query); return createRealmResults(collection); } @@ -1540,7 +1533,6 @@ public RealmResults findAllAsync() { */ @SuppressWarnings("unchecked") public RealmResults findAllSorted(String fieldName, Sort sortOrder) { - checkQueryIsNotReused(); SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldName, sortOrder); Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor); @@ -1592,8 +1584,6 @@ public RealmResults findAllSortedAsync(String fieldName) { * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSorted(String fieldNames[], Sort sortOrders[]) { - checkQueryIsNotReused(); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldNames, sortOrders); Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor); @@ -1646,8 +1636,6 @@ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, * @see io.realm.RealmObject */ public E findFirst() { - checkQueryIsNotReused(); - Row row; if (realm.isInTransaction()) { // It is not possible to create async query inside a transaction. So immediately query the first object. @@ -1701,74 +1689,6 @@ private void checkSortParameters(String fieldNames[], final Sort[] sortOrders) { } } - private WeakReference getWeakReferenceNotifier() { - if (realm.sharedRealm.realmNotifier == null || !realm.sharedRealm.realmNotifier.isValid()) { - throw new IllegalStateException("Your Realm is opened from a thread without a Looper." + - " Async queries need a Handler to send results of your query"); - } - return new WeakReference(realm.sharedRealm.realmNotifier); // use caller Realm's Looper - } - - // The shared group needs to be closed before sending the message to other threads to avoid timing problems. - // eg.: The other thread wants to delete Realm when getting notified. - private void closeSharedRealmAndSendEventToNotifier(SharedRealm sharedRealm, - WeakReference weakNotifier, - QueryUpdateTask.NotifyEvent event, Object obj) { - if (sharedRealm != null) { - sharedRealm.close(); - } - - RealmNotifier notifier = weakNotifier.get(); - if (notifier!= null) { - switch (event) { - case COMPLETE_ASYNC_RESULTS: - notifier.completeAsyncResults((QueryUpdateTask.Result)obj); - break; - case COMPLETE_ASYNC_OBJECT: - notifier.completeAsyncObject((QueryUpdateTask.Result)obj); - break; - case THROW_BACKGROUND_EXCEPTION: - notifier.throwBackgroundException((Throwable)obj); - break; - default: - // Should not get here. - throw new IllegalStateException(String.format("%s is not handled here.", event)); - } - } - } - - // We need to prevent the user from using the query again (mostly for async) - // Ex: if the first query fail with findFirstAsync, if the user reuse the same RealmQuery - // with findAllSorted, argumentsHolder of the first query will be overridden, - // which cause any retry to use the findAllSorted argumentsHolder. - private void checkQueryIsNotReused() { - if (argumentsHolder != null) { - throw new IllegalStateException("This RealmQuery is already used by a find* query, please create a new query"); - } - } - - private long getSourceRowIndexForFirstObject() { - long tableRowIndex = this.query.find(); - return tableRowIndex; - } - // Get the column index for sorting related functions. A proper exception will be thrown if the field doesn't exist - // or it belongs to the child object. - private long getColumnIndexForSort(String fieldName) { - if (fieldName == null || fieldName.isEmpty()) { - throw new IllegalArgumentException("Non-empty fieldname required."); - } - if (fieldName.contains(".")) { - throw new IllegalArgumentException("Sorting using child object fields is not supported: " + fieldName); - } - - Long columnIndex = schema.getFieldIndex(fieldName); - if (columnIndex == null) { - throw new IllegalArgumentException(String.format("Field name '%s' does not exist.", fieldName)); - } - - return columnIndex; - } - private RealmResults createRealmResults(Collection collection) { if (isDynamicQuery()) { return new RealmResults(realm, collection, className); @@ -1776,17 +1696,4 @@ private RealmResults createRealmResults(Collection collection) { return new RealmResults(realm, collection, clazz); } } - - public ArgumentsHolder getArgument() { - return argumentsHolder; - } - - /** - * Exports & handovers the query to be used by a worker thread. - * - * @return the exported handover pointer for this RealmQuery. - */ - long handoverQueryPointer() { - return query.handoverQuery(realm.sharedRealm); - } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java new file mode 100644 index 0000000000..230c17cd27 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java @@ -0,0 +1,21 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +public interface Capabilities { + boolean canDeliverNotification(); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java new file mode 100644 index 0000000000..190f8fd639 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java @@ -0,0 +1,27 @@ +package io.realm.internal; + +import java.lang.ref.WeakReference; + +public abstract class ObserverPair { + public final T listener; + public final WeakReference observerRef; + + public ObserverPair(T listener, Object objectRef) { + this.listener = listener; + this.observerRef = new WeakReference(objectRef); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof ObserverPair) { + ObserverPair anotherPair = (ObserverPair) obj; + return listener.equals(anotherPair.listener) && + observerRef.get() == anotherPair.observerRef.get(); + } + return false; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index aaf97b6b5d..689d8b9046 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -16,50 +16,82 @@ package io.realm.internal; -import io.realm.internal.async.QueryUpdateTask; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import io.realm.RealmChangeListener; /** * This interface needs to be implemented by Java and pass to Realm Object Store in order to get notifications when * other thread/process changes the Realm file. */ @Keep -public interface RealmNotifier { - /** - * This is called from Java when the changes have been made on the same thread. - */ - void notifyCommitByLocalThread(); +public class RealmNotifier { + + private static class RealmObserverPair extends ObserverPair { + + public RealmObserverPair(Object observer, RealmChangeListener listener) { + super(listener, observer); + } + + private void onChange() { + Object observer = observerRef.get(); + if (observer != null) { + listener.onChange(observer); + } + } + } + + private List realmObserverPairs = new CopyOnWriteArrayList(); + // This is called by OS when other thread/process changes the Realm. + // This is getting called on the same thread which created the Realm. + // |---------------------------------------------------------------+--------------+------------------------------------------------| + // | Thread A | Thread B | Daemon Thread | + // |---------------------------------------------------------------+--------------+------------------------------------------------| + // | | Make changes | | + // | | | Detect and notify thread A through JNI ALooper | + // | Call OS's Realm::notify() from OS's ALooper callback | | | + // | Realm::notify() calls JavaBindingContext:change_available() | | | + // | change_available calls into this method to send REALM_CHANGED | | | + // |---------------------------------------------------------------+--------------+------------------------------------------------| /** * This is called in Realm Object Store's JavaBindingContext::changes_available. * This is getting called on the same thread which created this Realm when the same Realm file has been changed by * other thread. The changes on the same thread should not trigger this call. */ @SuppressWarnings("unused") // called from java_binding_context.cpp - void notifyCommitByOtherThread(); + void notifyCommitByOtherThread() { + for (RealmObserverPair observerPair : realmObserverPairs) { + Object observer = observerPair.observerRef.get(); + if (observer == null) { + realmObserverPairs.remove(observerPair); + } else { + observerPair.onChange(); + } + } + } /** - * Post a runnable to be run in the next event loop on the thread which creates the corresponding Realm. - * - * @param runnable to be posted. + * Called when close SharedRealm to clean up any event left in to queue. */ - void post(Runnable runnable); + public void close() { + removeAllChangeListeners(); + } - /** - * Is the current notifier valid? eg. Notifier created on non-looper thread cannot be notified. - * - * @return {@code true} if the thread which owns this notifier can be notified. Otherwise {@code false} - */ - boolean isValid(); + public void addChangeListener(Object observer, RealmChangeListener realmChangeListener) { + RealmObserverPair observerPair = new RealmObserverPair(observer, realmChangeListener); + if (!realmObserverPairs.contains(observerPair)) { + realmObserverPairs.add(observerPair); + } + } - /** - * Called when close SharedRealm to clean up any event left in to queue. - */ - void close(); + public void removeChangeListener(Object observer, RealmChangeListener realmChangeListener) { + RealmObserverPair observerPair = new RealmObserverPair(observer, realmChangeListener); + realmObserverPairs.remove(observerPair); + } - // FIXME: These are for decoupling handler from async query. Async query needs refactor to either adapt the OS or - // abstract the logic from Android handlers. - void completeAsyncResults(QueryUpdateTask.Result result); - void completeAsyncObject(QueryUpdateTask.Result result); - void throwBackgroundException(Throwable throwable); - void completeUpdateAsyncQueries(QueryUpdateTask.Result result); + public void removeAllChangeListeners() { + realmObserverPairs.clear(); + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java index 4e6f2b7b08..b0de8444e6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java @@ -16,14 +16,15 @@ package io.realm.internal; -import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; import io.realm.RealmChangeListener; +@Keep public class RowNotifier { + @Keep private static class Observer { final RealmChangeListener listener; final Object object; @@ -33,6 +34,9 @@ private static class Observer { this.object = object; this.row = null; } + + // Called by JNI + @SuppressWarnings("unused") public void notifyListener() { listener.onChange(object); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java new file mode 100644 index 0000000000..d043867e14 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm.internal.android; + +import android.os.Looper; + +import io.realm.internal.Capabilities; + +public class AndroidCapabilities implements Capabilities { + + @Override + public boolean canDeliverNotification() { + return (Looper.myLooper() != null && !isIntentServiceThread()); + } + + private static boolean isIntentServiceThread() { + // Tries to determine if a thread is an IntentService thread. No public API can detect this, + // so use the thread name as a heuristic: + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/IntentService.java#108 + String threadName = Thread.currentThread().getName(); + return threadName != null && threadName.startsWith("IntentService["); + } +} From 71516eb03a8850d88493ad68a69175bf287fcf30 Mon Sep 17 00:00:00 2001 From: Ricardo Fuhrmann Date: Mon, 5 Dec 2016 09:56:48 -0300 Subject: [PATCH 0263/2110] Minor typo in ExampleRealmTest doc (#3870) --- .../java/io/realm/examples/unittesting/ExampleRealmTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java index 1c12d4114d..147c9aa553 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java @@ -107,7 +107,7 @@ public void shouldVerifyThatDogWasCreated() { dogRepo.createDog("Spot"); // Attempting to verify that a method was called (executeTransaction) on a partial - // mock will return unexpected resultes due to the partial mock. For example, + // mock will return unexpected results due to the partial mock. For example, // verifying that `executeTransaction` was called only once will fail as Powermock // actually calls the method 3 times for some reason. I cannot determine why at this // point. From ba373f301634dc995473acddb46fd1c8b9257f92 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 5 Dec 2016 21:19:21 +0800 Subject: [PATCH 0264/2110] Call OS set_auto_refresh() & auto_refresh() --- .../java/io/realm/NotificationsTest.java | 2 +- .../cpp/io_realm_internal_SharedRealm.cpp | 39 ++++++++++++++----- .../src/main/java/io/realm/BaseRealm.java | 10 +---- .../java/io/realm/internal/Capabilities.java | 1 + .../java/io/realm/internal/SharedRealm.java | 15 +++++++ .../internal/android/AndroidCapabilities.java | 10 +++++ 6 files changed, 58 insertions(+), 19 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 0a8979656f..653a0117ae 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -88,7 +88,7 @@ public void tearDown() { } @Test - public void failingSetAutoRefreshOnNonLooperThread() throws ExecutionException, InterruptedException { + public void setAutoRefresh_failsOnNonLooperThread() throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new Callable() { @Override diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 497a52c111..c6cb20b27f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -436,11 +436,11 @@ Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv *env, jclass, jlong shar } JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, jlong sharedRealmPtr) +Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(sharedRealmPtr) + TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(sharedRealmPtr)); + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { using rf = realm::_impl::RealmFriend; auto& shared_group = rf::get_shared_group(*shared_realm); @@ -450,15 +450,34 @@ Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, } JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv *env, jclass, jlong nativePtr, - jlong nativeSchemaPtr, jlong version) { - TR_ENTER() +Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv *env, jclass, jlong shared_realm_ptr, + jlong schema_ptr, jlong version) { + TR_ENTER_PTR(shared_realm_ptr) try { - auto shared_realm = *(reinterpret_cast(nativePtr)); - auto *schema = reinterpret_cast(nativeSchemaPtr); + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto *schema = reinterpret_cast(schema_ptr); shared_realm->update_schema(*schema, static_cast(version)); - } - CATCH_STD() + } CATCH_STD() } +JNIEXPORT void JNICALL +Java_io_realm_internal_SharedRealm_nativeSetAutoRefresh(JNIEnv *env, jclass, jlong shared_realm_ptr, jboolean enabled) +{ + TR_ENTER_PTR(shared_realm_ptr) + try { + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + shared_realm->set_auto_refresh(enabled); + } CATCH_STD() +} + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_SharedRealm_nativeIsAutoRefresh(JNIEnv *env, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + try { + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return static_cast(shared_realm->auto_refresh()); + } CATCH_STD() + return JNI_FALSE; +} diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 89604e52a4..420dc9178b 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -17,7 +17,6 @@ package io.realm; import android.content.Context; -import android.os.Handler; import android.os.Looper; import java.io.Closeable; @@ -89,10 +88,6 @@ public void onSchemaVersionChanged(long currentVersion) { } }); this.schema = new RealmSchema(this); - - if (handlerController.isAutoRefreshAvailable()) { - setAutoRefresh(true); - } } /** @@ -108,8 +103,7 @@ public void onSchemaVersionChanged(long currentVersion) { */ public void setAutoRefresh(boolean autoRefresh) { checkIfValid(); - handlerController.checkCanBeAutoRefreshed(); - handlerController.setAutoRefresh(autoRefresh); + sharedRealm.setAutoRefresh(autoRefresh); } /** @@ -118,7 +112,7 @@ public void setAutoRefresh(boolean autoRefresh) { * @return the auto-refresh status. */ public boolean isAutoRefresh() { - return handlerController.isAutoRefreshEnabled(); + return sharedRealm.isAutoRefresh(); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java index 230c17cd27..608538c5bf 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java @@ -18,4 +18,5 @@ public interface Capabilities { boolean canDeliverNotification(); + void checkCanDeliverNotification(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 74110fb746..5ab76d171d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -21,6 +21,7 @@ import io.realm.RealmConfiguration; import io.realm.RealmSchema; +import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.async.BadVersionException; public final class SharedRealm implements Closeable { @@ -33,6 +34,8 @@ public final class SharedRealm implements Closeable { public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 4; public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 5; + public static final Capabilities capabilities = new AndroidCapabilities(); + public static void initialize(File tempDirectory) { if (SharedRealm.temporaryDirectory != null) { // already initialized @@ -178,6 +181,7 @@ private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotif context = new Context(); this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion(); objectServerFacade = null; + nativeSetAutoRefresh(nativePtr, capabilities.canDeliverNotification()); } public static SharedRealm getInstance(RealmConfiguration config) { @@ -332,6 +336,15 @@ public void updateSchema(RealmSchema schema, long version) { nativeUpdateSchema(nativePtr, schema.getNativePtr(), version); } + public void setAutoRefresh(boolean enabled) { + capabilities.checkCanDeliverNotification(); + nativeSetAutoRefresh(nativePtr, enabled); + } + + public boolean isAutoRefresh() { + return nativeIsAutoRefresh(nativePtr); + } + @Override public void close() { if (realmNotifier != null) { @@ -405,4 +418,6 @@ private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotif private static native void nativeStopWaitForChange(long nativeSharedRealmPtr); private static native boolean nativeCompact(long nativeSharedRealmPtr); private static native void nativeUpdateSchema(long nativePtr, long nativeSchemaPtr, long version); + private static native void nativeSetAutoRefresh(long nativePtr, boolean enabled); + private static native boolean nativeIsAutoRefresh(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java index d043867e14..4aeb51af27 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java @@ -26,6 +26,16 @@ public boolean canDeliverNotification() { return (Looper.myLooper() != null && !isIntentServiceThread()); } + @Override + public void checkCanDeliverNotification() { + if (Looper.myLooper() == null) { + throw new IllegalStateException("Cannot set auto-refresh in a Thread without a Looper"); + } + if (isIntentServiceThread()) { + throw new IllegalStateException("Cannot set auto-refresh in an IntentService thread."); + } + } + private static boolean isIntentServiceThread() { // Tries to determine if a thread is an IntentService thread. No public API can detect this, // so use the thread name as a heuristic: From 2a014f7b2629f642fac5ef53fd306992452286fa Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 5 Dec 2016 21:48:31 +0800 Subject: [PATCH 0265/2110] Remove handler related code The event handler logic stays in OS only, see util/android/event_loop_signal.hpp All the async related logic should be platform independent in java. --- realm/config/findbugs/findbugs-filter.xml | 5 - .../java/io/realm/DynamicRealmTests.java | 3 +- .../java/io/realm/HandlerProxy.java | 82 -- .../java/io/realm/NotificationsTest.java | 8 +- .../java/io/realm/RealmAsyncQueryTests.java | 7 +- .../java/io/realm/RealmObjectTests.java | 12 +- .../java/io/realm/RxJavaTests.java | 8 +- .../io/realm/TypeBasedNotificationsTests.java | 4 +- .../src/main/java/io/realm/BaseRealm.java | 18 +- .../main/java/io/realm/HandlerController.java | 839 ------------------ .../src/main/java/io/realm/RealmObject.java | 4 +- .../src/main/java/io/realm/RealmQuery.java | 4 - .../java/io/realm/internal/Capabilities.java | 2 +- .../internal/HandlerControllerConstants.java | 30 - .../java/io/realm/internal/SharedRealm.java | 8 +- .../internal/android/AndroidCapabilities.java | 8 +- .../realm/internal/async/ArgumentsHolder.java | 40 - .../realm/internal/async/QueryUpdateTask.java | 283 ------ 18 files changed, 39 insertions(+), 1326 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/HandlerProxy.java delete mode 100644 realm/realm-library/src/main/java/io/realm/HandlerController.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/HandlerControllerConstants.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/async/ArgumentsHolder.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index 10553d8870..d7edcde952 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -60,11 +60,6 @@ - - - - - diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 98756f6827..c564f4c162 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -41,7 +41,6 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.exceptions.RealmException; -import io.realm.internal.HandlerControllerConstants; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; @@ -531,6 +530,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void accessingDynamicRealmObjectBeforeAsyncQueryCompleted() { + /* final DynamicRealm dynamicRealm = initializeDynamicRealm(); final DynamicRealmObject[] dynamicRealmObject = new DynamicRealmObject[1]; @@ -567,6 +567,7 @@ public void run() { dynamicRealmObject[0] = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 4, 9) .findFirstAsync(); + */ } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/HandlerProxy.java b/realm/realm-library/src/androidTest/java/io/realm/HandlerProxy.java deleted file mode 100644 index 507e7f8edf..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/HandlerProxy.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.os.Handler; -import android.os.Message; - -/** - * Handler decorator, to help intercept some messages before they are sent and received. - */ -abstract class HandlerProxy extends Handler { - - private final HandlerController controller; - - public HandlerProxy(HandlerController controller) { - if (null == controller) { - throw new IllegalArgumentException("non-null HandlerController required."); - } - this.controller = controller; - } - - /** - * @see {@link Handler#postAtFrontOfQueue(Runnable)} - */ - public void postAtFront(Runnable runnable) { - if (onInterceptOutMessage(0)) { - postAtFrontOfQueue(runnable); - } - } - - @Override - public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - boolean eventConsumed = onInterceptOutMessage(msg.what); - return !eventConsumed && super.sendMessageAtTime(msg, uptimeMillis); - } - - @Override - public void handleMessage(Message msg) { - boolean eventConsumed = onInterceptInMessage(msg.what); - if (!eventConsumed) { - controller.handleMessage(msg); - } - } - - /** - * Intercepts a message as it is being posted. Return {@code false} to continue sending it. {@code true} to - * swallow it. - * - * This method will be executed on the thread sending the message. - * - * @return {@code true} if message should be swallowed. {@code false} to continue processing it. - */ - protected boolean onInterceptOutMessage(int what) { - return false; - } - - /** - * Intercepts a message as it is being received. Return {@code false} to let subclasses continue the handling. - * {@code true} to swallow it. - * - * This method will be executed on the thread of the Looper backing the Handler - * - * @return {@code true} if message should be swallowed. {@code false} to continue processing it. - */ - protected boolean onInterceptInMessage(int what) { - return false; - } -} diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 653a0117ae..573133c04b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -426,7 +426,7 @@ public void onChange(Realm object) { @Test @RunTestInLooperThread public void weakReferenceListener() throws InterruptedException { - final AtomicInteger weakCounter = new AtomicInteger(0); +/* final AtomicInteger weakCounter = new AtomicInteger(0); final AtomicInteger strongCounter = new AtomicInteger(0); final Realm realm = looperThread.realm; @@ -467,7 +467,7 @@ public void onChange(Realm object) { // Trigger change listeners realm.beginTransaction(); realm.createObject(AllTypes.class); - realm.commitTransaction(); + realm.commitTransaction();*/ } @@ -477,7 +477,7 @@ public void onChange(Realm object) { @Test @RunTestInLooperThread public void removingWeakReferenceListener() throws InterruptedException { - final AtomicInteger counter = new AtomicInteger(0); +/* final AtomicInteger counter = new AtomicInteger(0); final Realm realm = looperThread.realm; RealmChangeListener listenerA = new RealmChangeListener() { @Override @@ -505,7 +505,7 @@ public void onChange(Realm object) { realm.beginTransaction(); realm.createObject(AllTypes.class); - realm.commitTransaction(); + realm.commitTransaction();*/ } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 0244095425..10d7bdd193 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -42,7 +42,6 @@ import io.realm.entities.NonLatinFieldNames; import io.realm.entities.Owner; import io.realm.instrumentation.MockActivityManager; -import io.realm.internal.HandlerControllerConstants; import io.realm.internal.RealmObjectProxy; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.LogLevel; @@ -61,7 +60,7 @@ @RunWith(AndroidJUnit4.class) public class RealmAsyncQueryTests { - +/* @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); @Rule @@ -1125,7 +1124,7 @@ public void findAllSortedAsync_batchUpdate() { public boolean onInterceptInMessage(int what) { switch (what) { case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: { - if (numberOfIntercept.incrementAndGet() == 2 /* 2 queries are both completed */) { + if (numberOfIntercept.incrementAndGet() == 2 *//* 2 queries are both completed *//*) { // 6. The first time the async queries complete we start an update from // another background thread. This will cause queries to rerun when the // background thread notifies this thread. @@ -2231,5 +2230,5 @@ private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOf } } realm.commitTransaction(); - } + }*/ } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 0224d6496c..b6243cfb33 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -1756,7 +1756,7 @@ public void onChange(AllTypesPrimaryKey element) { @Test @UiThreadTest public void addChangeListener_shouldAddTheObjectToHandlerRealmObjects() { - realm.beginTransaction(); +/* realm.beginTransaction(); AllTypesPrimaryKey allTypesPrimaryKey = realm.createObject(AllTypesPrimaryKey.class, 1); realm.commitTransaction(); final ConcurrentHashMap, Object> realmObjects = @@ -1773,14 +1773,14 @@ public void onChange(AllTypesPrimaryKey element) { assertEquals(1, realmObjects.size()); for (WeakReference ref : realmObjects.keySet()) { assertTrue(ref.get() == allTypesPrimaryKey); - } + }*/ } // The object should be added to HandlerController.realmObjects only once. @Test @UiThreadTest public void addChangeListener_shouldNotAddDupEntriesToHandlerRealmObjects() { - realm.beginTransaction(); +/* realm.beginTransaction(); AllTypesPrimaryKey allTypesPrimaryKey = realm.createObject(AllTypesPrimaryKey.class, 1); realm.commitTransaction(); final ConcurrentHashMap, Object> realmObjects = @@ -1805,14 +1805,14 @@ public void onChange(AllTypesPrimaryKey element) { assertEquals(1, realmObjects.size()); for (WeakReference ref : realmObjects.keySet()) { assertTrue(ref.get() == allTypesPrimaryKey); - } + }*/ } // The object should not be added to HandlerController again after the async query loaded. @Test @RunTestInLooperThread public void addChangeListener_checkHandlerRealmObjectsWhenCallingOnAsyncObject() { - Realm realm = looperThread.realm; +/* Realm realm = looperThread.realm; realm.beginTransaction(); realm.createObject(AllTypesPrimaryKey.class, 1); realm.commitTransaction(); @@ -1837,6 +1837,6 @@ public void onChange(AllTypesPrimaryKey element) { assertEquals(1, realmObjects.size()); for (Object query : realmObjects.values()) { assertNotNull(query); - } + }*/ } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index ba4898e4a3..461533f9ff 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -379,7 +379,7 @@ public void call(DynamicRealm rxRealm) { @Test @UiThreadTest public void unsubscribe_sameThread() { - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); +/* final AtomicBoolean subscribedNotified = new AtomicBoolean(false); subscription = realm.asObservable().subscribe(new Action1() { @Override public void call(Realm rxRealm) { @@ -389,13 +389,13 @@ public void call(Realm rxRealm) { }); assertEquals(1, realm.handlerController.changeListeners.size()); subscription.unsubscribe(); - assertEquals(0, realm.handlerController.changeListeners.size()); + assertEquals(0, realm.handlerController.changeListeners.size());*/ } @Test @UiThreadTest public void unsubscribe_fromOtherThread() { - final CountDownLatch unsubscribeCompleted = new CountDownLatch(1); +/* final CountDownLatch unsubscribeCompleted = new CountDownLatch(1); final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final Subscription subscription = realm.asObservable().subscribe(new Action1() { @Override @@ -422,7 +422,7 @@ public void run() { assertEquals(1, realm.handlerController.changeListeners.size()); // We cannot call subscription.unsubscribe() again, so manually close the extra Realm instance opened by // the Observable. - realm.close(); + realm.close();*/ } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index 5ce7dd5cb8..cb9410d285 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -1431,7 +1431,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void change_realm_results_map_in_listener() throws InterruptedException { - final CountDownLatch finishedLatch = new CountDownLatch(2); +/* final CountDownLatch finishedLatch = new CountDownLatch(2); final Realm realm = looperThread.realm; // Two results needed to make sure list modification happen while iterating @@ -1463,7 +1463,7 @@ public void onChange(Object object) { realm.beginTransaction(); realm.createObject(Owner.class); - realm.commitTransaction(); + realm.commitTransaction();*/ } // Build a RealmResults from a RealmList, and delete the RealmList. Test the behavior of ChangeListener on the diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 420dc9178b..7aa9f27a23 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -71,14 +71,11 @@ abstract class BaseRealm implements Closeable { protected SharedRealm sharedRealm; RealmSchema schema; - HandlerController handlerController; - protected BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; - this.handlerController = new HandlerController(this); this.sharedRealm = SharedRealm.getInstance(configuration, new RealmNotifier(), !(this instanceof Realm) ? null : new SharedRealm.SchemaVersionListener() { @@ -130,9 +127,7 @@ protected void addListener(RealmChangeListener listener) { throw new IllegalArgumentException("Listener should not be null"); } checkIfValid(); - if (!handlerController.isAutoRefreshEnabled()) { - throw new IllegalStateException("You can't register a listener from a non-Looper or IntentService thread."); - } + sharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be registered."); sharedRealm.realmNotifier.addChangeListener(this, listener); } @@ -149,9 +144,7 @@ public void removeChangeListener(RealmChangeListener listen throw new IllegalArgumentException("Listener should not be null"); } checkIfValid(); - if (!handlerController.isAutoRefreshEnabled()) { - throw new IllegalStateException("You can't remove a listener from a non-Looper thread "); - } + sharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be removed."); sharedRealm.realmNotifier.removeChangeListener(this, listener); } @@ -183,9 +176,7 @@ public void removeChangeListener(RealmChangeListener listen */ public void removeAllChangeListeners() { checkIfValid(); - if (!handlerController.isAutoRefreshEnabled()) { - throw new IllegalStateException("You can't remove listeners from a non-Looper thread "); - } + sharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be removed."); sharedRealm.realmNotifier.removeAllChangeListeners(); } @@ -251,7 +242,8 @@ public boolean waitForChange() { if (hasChanged) { // Since this Realm instance has been waiting for change, advance realm & refresh realm. sharedRealm.refresh(); - handlerController.refreshSynchronousTableViews(); + // FIXME: CHECK THIS!!! Maybe call OS SharedRealm.refresh()? + //handlerController.refreshSynchronousTableViews(); } return hasChanged; } diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java deleted file mode 100644 index b3c28c8420..0000000000 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ /dev/null @@ -1,839 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.os.Handler; -import android.os.Looper; -import android.os.Message; - -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Future; - -import io.realm.internal.HandlerControllerConstants; -import io.realm.internal.IdentitySet; -import io.realm.internal.RealmObjectProxy; -import io.realm.internal.Row; -import io.realm.internal.SharedRealm; -import io.realm.internal.async.BadVersionException; -import io.realm.internal.async.QueryUpdateTask; -import io.realm.log.RealmLog; - -import static io.realm.internal.HandlerControllerConstants.LOCAL_COMMIT; -import static io.realm.internal.HandlerControllerConstants.REALM_CHANGED; - -/** - * Centralises all Handler callbacks, including updating async queries and refreshing the Realm. - */ -final class HandlerController implements Handler.Callback { - - private final static Boolean NO_REALM_QUERY = Boolean.TRUE; - - // Keep a strong reference to the registered RealmChangeListener - // user should unregister those listeners - final CopyOnWriteArrayList> changeListeners = new CopyOnWriteArrayList>(); - - // Keep a weak reference to the registered RealmChangeListener those are Weak since - // for some UC (ex: RealmBaseAdapter) we don't know when it's the best time to unregister the listener - final List>> weakChangeListeners = - new CopyOnWriteArrayList>>(); - - final BaseRealm realm; - private boolean autoRefresh; // Requires a Looper thread to be true. - - // pending update of async queries - private Future updateAsyncQueriesTask; - - private final ReferenceQueue> referenceQueueAsyncRealmResults = - new ReferenceQueue>(); - private final ReferenceQueue> referenceQueueSyncRealmResults = - new ReferenceQueue>(); - final ReferenceQueue referenceQueueRealmObject = new ReferenceQueue(); - // keep a WeakReference list to RealmResults obtained asynchronously in order to update them - // RealmQuery is not WeakReferenced to prevent it from being GC'd. RealmQuery should be - // cleaned if RealmResults is cleaned. we need to keep RealmQuery because it contains the query - // pointer (to handover for each update) + all the arguments necessary to rerun the query: - // sorting orders, soring columns, type (findAll, findFirst, findAllSorted etc.) - final Map>, RealmQuery> asyncRealmResults = - new IdentityHashMap>, RealmQuery>(); - // Keep a WeakReference to the currently empty RealmObjects obtained asynchronously. We need to keep re-running - // the query in the background for each commit, until we got a valid Row (pointer) - final Map, RealmQuery> emptyAsyncRealmObject = - new ConcurrentHashMap, RealmQuery>(); - - // Keep a reference to the list of sync RealmResults, we'll use it - // to deliver type based notification once the shared_group advance - final IdentitySet>> syncRealmResults = - new IdentitySet>>(); - - // Since ConcurrentHashMap doesn't support null value, and since java.util.Optional are not - // yet an option (using Java 6) we use an Object with the dummy value Boolean.TRUE to indicate - // a null value (no RealmQuery) this is the same approach used in the JDK - // ex here https://android.googlesource.com/platform/libcore/+/refs/heads/master/luni/src/main/java/java/util/concurrent/ConcurrentSkipListSet.java#214 - final ConcurrentHashMap, Object> realmObjects = - new ConcurrentHashMap, Object>(); - - // List of onSuccess callbacks from async transactions. We need to track all callbacks as notifying listeners might - // be delayed due to the presence of async queries. This can mean that multiple async transactions can complete - // before we are ready to notify all of them. - private final List pendingOnSuccessAsyncTransactionCallbacks = new ArrayList(); - - public HandlerController(BaseRealm realm) { - this.realm = realm; - } - - @Override - public boolean handleMessage(Message message) { - // Due to how a ConcurrentHashMap iterator is created we cannot be sure that other threads are - // aware when this threads handler is removed before they send messages to it. We don't wish to synchronize - // access to the handlers as they are the prime mean of notifying about updates. Instead we make sure - // that if a message does slip though (however unlikely), it will not try to update a SharedGroup that no - // longer exists. `sharedRealm` will only be null if a Realm is really closed. - if (realm.sharedRealm != null) { - QueryUpdateTask.Result result; - switch (message.what) { - - case LOCAL_COMMIT: - case REALM_CHANGED: - realmChanged(message.what == LOCAL_COMMIT); - break; - - case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: - result = (QueryUpdateTask.Result) message.obj; - completedAsyncRealmResults(result); - break; - - case HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT: - result = (QueryUpdateTask.Result) message.obj; - completedAsyncRealmObject(result); - break; - - case HandlerControllerConstants.COMPLETED_UPDATE_ASYNC_QUERIES: - // this is called once the background thread completed the update of the async queries - result = (QueryUpdateTask.Result) message.obj; - completedAsyncQueriesUpdate(result); - break; - - case HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION: - // Don't fail silently in the background in case of Core exception - throw (Error) message.obj; - - default: - throw new IllegalArgumentException("Unknown message: " + message.what); - } - } - return true; - } - - /** - * Properly handles when an async transaction completes. This will be treated as a REALM_CHANGED event when - * determining which queries to re-run and when to notify listeners. - *

          - * NOTE: This is needed as it is not possible to combine a `Message.what` value and a callback runnable. So instead - * of posting two messages, we post a runnable that runs this method. This means it is possible to interpret - * `REALM_CHANGED + Runnable` as one atomic message. - * - * @param onSuccess onSuccess callback to run for the async transaction that completed. - */ - public void handleAsyncTransactionCompleted(Runnable onSuccess) { - // Same reason as handleMessage() - if (realm.sharedRealm != null) { - if (onSuccess != null) { - pendingOnSuccessAsyncTransactionCallbacks.add(onSuccess); - } - //realmChanged(false); - } - } - - void addChangeListener(RealmChangeListener listener) { - changeListeners.addIfAbsent(listener); - } - - /** - * For internal use only. - *

          - * Sometimes we don't know when to unregister listeners (e.g., {@code RealmBaseAdapter}). Using - * a WeakReference the listener doesn't need to be explicitly unregistered. - * - * @param listener the change listener. - */ - void addChangeListenerAsWeakReference(RealmChangeListener listener) { - Iterator>> iterator = weakChangeListeners.iterator(); - List>> toRemoveList = null; - boolean addListener = true; - while (iterator.hasNext()) { - WeakReference> weakRef = iterator.next(); - RealmChangeListener weakListener = weakRef.get(); - - // Collect all listeners that are GC'ed - if (weakListener == null) { - if (toRemoveList == null) { - toRemoveList = new ArrayList>>(weakChangeListeners.size()); - } - toRemoveList.add(weakRef); - } - - // Check if Listener already exists - if (weakListener == listener) { - addListener = false; - } - } - if (toRemoveList != null) { - weakChangeListeners.removeAll(toRemoveList); - } - if (addListener) { - weakChangeListeners.add(new WeakReference>(listener)); - } - } - - @SuppressWarnings("unused") - void removeWeakChangeListener(RealmChangeListener listener) { - List>> toRemoveList = null; - for (int i = 0; i < weakChangeListeners.size(); i++) { - WeakReference> weakRef = weakChangeListeners.get(i); - RealmChangeListener weakListener = weakRef.get(); - - // Collect all listeners that are GC'ed or we need to remove - if (weakListener == null || weakListener == listener) { - if (toRemoveList == null) { - toRemoveList = new ArrayList>>(weakChangeListeners.size()); - } - toRemoveList.add(weakRef); - } - } - - weakChangeListeners.removeAll(toRemoveList); - } - - void removeChangeListener(RealmChangeListener listener) { - changeListeners.remove(listener); - } - - void removeAllChangeListeners() { - changeListeners.clear(); - } - - /** - * NOTE: Should only be called from {@link #notifyAllListeners(List)}. - */ - private void notifyGlobalListeners() { - // notify strong reference listener - Iterator> iteratorStrongListeners = changeListeners.iterator(); - while (!realm.isClosed() && iteratorStrongListeners.hasNext()) { // every callback could close the realm - RealmChangeListener listener = iteratorStrongListeners.next(); - listener.onChange(realm); - } - // notify weak reference listener (internals) - Iterator>> iteratorWeakListeners = weakChangeListeners.iterator(); - List>> toRemoveList = null; - while (!realm.isClosed() && iteratorWeakListeners.hasNext()) { - WeakReference> weakRef = iteratorWeakListeners.next(); - RealmChangeListener listener = weakRef.get(); - if (listener == null) { - if (toRemoveList == null) { - toRemoveList = new ArrayList>>(weakChangeListeners.size()); - } - toRemoveList.add(weakRef); - } else { - listener.onChange(realm); - } - } - if (toRemoveList != null) { - weakChangeListeners.removeAll(toRemoveList); - } - } - - private void updateAsyncEmptyRealmObject() { - /* - Iterator, RealmQuery>> iterator = emptyAsyncRealmObject.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry, RealmQuery> next = iterator.next(); - if (next.getKey().get() != null) { - Realm.asyncTaskExecutor - .submitQueryUpdate(QueryUpdateTask.newBuilder() - .realmConfiguration(realm.getConfiguration()) - .addObject(next.getKey(), - next.getValue().handoverQueryPointer(), - next.getValue().getArgument()) - .sendToNotifier(realm.sharedRealm.realmNotifier, - QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_OBJECT) - .build()); - - } else { - iterator.remove(); - } - } - */ - } - - /** - * This method calls all registered listeners for Realm, RealmResults and RealmObjects, and callbacks for async - * transactions. - * - * PREREQUISITE: Only call this method after all objects are up to date. This means: - * - `advance_read` was called on the Realm. - * - `RealmResults.syncIfNeeded()` was called when collecting RealmResults listeners. - * - * @param realmResultsToBeNotified list of all RealmResults listeners that can be notified. - */ - void notifyAllListeners(List> realmResultsToBeNotified) { -/* - // Notify all RealmResults (async and synchronous). - for (Iterator> it = realmResultsToBeNotified.iterator(); !realm.isClosed() && it.hasNext(); ) { - RealmResults realmResults = it.next(); - //realmResults.notifyChangeListeners(false); - } - - // Notify all loaded RealmObjects - notifyRealmObjectCallbacks(); - - // Re-run any async single objects that are still not loaded. - // TODO: Why is this here? This was not called in `completedAsyncQueriesUpdate()`. Problem? - if (!realm.isClosed() && threadContainsAsyncEmptyRealmObject()) { - updateAsyncEmptyRealmObject(); - } - - // Notify any completed async transactions - notifyAsyncTransactionCallbacks(); - - // Trigger global listeners last. - // Note that NotificationTest.callingOrdersOfListeners will fail if orders change. - notifyGlobalListeners(); - */ - } - - private void collectAsyncRealmResultsCallbacks(List> resultsToBeNotified) { - collectRealmResultsCallbacks(asyncRealmResults.keySet().iterator(), resultsToBeNotified); - } - - private void collectSyncRealmResultsCallbacks(List> resultsToBeNotified) { - collectRealmResultsCallbacks(syncRealmResults.keySet().iterator(), resultsToBeNotified); - } - - - private void collectRealmResultsCallbacks(Iterator>> iterator, - List> resultsToBeNotified) { - while (iterator.hasNext()) { - WeakReference> weakRealmResults = iterator.next(); - RealmResults realmResults = weakRealmResults.get(); - if (realmResults == null) { - iterator.remove(); - } else { - // Sync the RealmResult so it is completely up to date. - // This is a prerequisite to calling the listener, so when the listener is finally triggered, all - // RealmResults will be up to date. - // Local commits can accidentially cause async RealmResults to be notified, so we only want to - // include those that are actually done loading. - if (realmResults.isLoaded()) { - //realmResults.syncIfNeeded(); - resultsToBeNotified.add(realmResults); - } - } - } - } - - /** - * NOTE: Should only be called from {@link #notifyAllListeners(List)}. - */ - private void notifyRealmObjectCallbacks() { - List objectsToBeNotified = new ArrayList(); - Iterator> iterator = realmObjects.keySet().iterator(); - while (iterator.hasNext()) { - WeakReference weakRealmObject = iterator.next(); - RealmObjectProxy realmObject = weakRealmObject.get(); - if (realmObject == null) { - iterator.remove(); - - } else { - if (realmObject.realmGet$proxyState().getRow$realm().isAttached()) { - // It should be legal to modify realmObjects in the listener - objectsToBeNotified.add(realmObject); - } else if (realmObject.realmGet$proxyState().getRow$realm() != Row.EMPTY_ROW) { - iterator.remove(); - } - } - } - - for (Iterator it = objectsToBeNotified.iterator(); !realm.isClosed() && it.hasNext(); ) { - RealmObjectProxy realmObject = it.next(); - realmObject.realmGet$proxyState().notifyChangeListeners$realm(); - } - } - - private void updateAsyncQueries() { - /* - if (updateAsyncQueriesTask != null && !updateAsyncQueriesTask.isDone()) { - // try to cancel any pending update since we're submitting a new one anyway - updateAsyncQueriesTask.cancel(true); - Realm.asyncTaskExecutor.getQueue().remove(updateAsyncQueriesTask); - RealmLog.trace("REALM_CHANGED realm: %s cancelling pending COMPLETED_UPDATE_ASYNC_QUERIES updates", HandlerController.this); - } - RealmLog.trace("REALM_CHANGED realm: %s updating async queries, total: %d", HandlerController.this, asyncRealmResults.size()); - // prepare a QueryUpdateTask to current async queries in this thread - QueryUpdateTask.Builder.UpdateQueryStep updateQueryStep = QueryUpdateTask.newBuilder() - .realmConfiguration(realm.getConfiguration()); - QueryUpdateTask.Builder.RealmResultsQueryStep realmResultsQueryStep = null; - - // we iterate over non GC'd async RealmResults then add them to the list to be updated (in a batch) - Iterator>, RealmQuery>> iterator = asyncRealmResults.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry>, RealmQuery> entry = iterator.next(); - WeakReference> weakReference = entry.getKey(); - RealmResults realmResults = weakReference.get(); - if (realmResults == null) { - // GC'd instance remove from the list - iterator.remove(); - - } else { - realmResultsQueryStep = updateQueryStep.add(weakReference, - entry.getValue().handoverQueryPointer(), - entry.getValue().getArgument()); - } - - // Note: we're passing an WeakRef of a RealmResults to another thread - // this is safe as long as we don't invoke any of the RealmResults methods. - // we're just using it as a Key in an IdentityHashMap (i.e doesn't call - // AbstractList's hashCode, that require accessing objects from another thread) - // - // watch out when you debug, as you're IDE try to evaluate RealmResults - // which break the Thread confinement constraints. - } - if (realmResultsQueryStep != null) { - QueryUpdateTask queryUpdateTask = realmResultsQueryStep - .sendToNotifier(realm.sharedRealm.realmNotifier, - QueryUpdateTask.NotifyEvent.COMPLETE_UPDATE_ASYNC_QUERIES) - .build(); - updateAsyncQueriesTask = Realm.asyncTaskExecutor.submitQueryUpdate(queryUpdateTask); - } - */ - } - - private void realmChanged(boolean localCommit) { - RealmLog.debug("%s : %s", (localCommit ? "LOCAL_COMMIT" : "REALM_CHANGED"), HandlerController.this); - deleteWeakReferences(); - boolean threadContainsAsyncQueries = threadContainsAsyncQueries(); - - // Mixing local transactions and async queries has unavoidable race conditions - if (localCommit && threadContainsAsyncQueries) { - RealmLog.warn("Mixing asynchronous queries with local writes should be avoided. " + - "Realm will convert any async queries to synchronous in order to remain consistent. Use " + - "asynchronous writes instead. You can read more here: " + - "https://realm.io/docs/java/latest/#asynchronous-transactions"); - } - - if (!localCommit && threadContainsAsyncQueries) { - // For changes from other threads, swallow the change and re-run async queries first. - updateAsyncQueries(); - } else { - // Following cases handled by this: - // localCommit && threadContainsAsyncQueries (this is the case the warning above is about) - // localCommit && !threadContainsAsyncQueries - // !localCommit && !threadContainsAsyncQueries - realm.sharedRealm.refresh(); - - List> resultsToBeNotified = new ArrayList>(); - collectAsyncRealmResultsCallbacks(resultsToBeNotified); - collectSyncRealmResultsCallbacks(resultsToBeNotified); - notifyAllListeners(resultsToBeNotified); - } - } - - private void completedAsyncRealmResults(QueryUpdateTask.Result result) { - /* - Set>> updatedTableViewsKeys = result.updatedTableViews.keySet(); - if (updatedTableViewsKeys.size() > 0) { - WeakReference> weakRealmResults = updatedTableViewsKeys.iterator().next(); - - RealmResults realmResults = weakRealmResults.get(); - if (realmResults == null) { - asyncRealmResults.remove(weakRealmResults); - RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] realm: %s RealmResults GC'd ignore results", - weakRealmResults, HandlerController.this); - } else { - SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID(); - int compare = callerVersionID.compareTo(result.versionID); - if (compare == 0) { - // if the RealmResults is empty (has not completed yet) then use the value - // otherwise a task (grouped update) has already updated this RealmResults - if (!realmResults.isLoaded()) { - RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] , realm: %s same versions, using results (RealmResults is not loaded)", - weakRealmResults, HandlerController.this); - // swap pointer - //realmResults.swapTableViewPointer(result.updatedTableViews.get(weakRealmResults)); - // notify callbacks - //realmResults.syncIfNeeded(); - //realmResults.notifyChangeListeners(false); - } else { - RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] , realm: %s ignoring result the RealmResults (is already loaded)", - weakRealmResults, HandlerController.this); - } - - } else if (compare > 0) { - // we have two use cases: - // 1- this RealmResults is not empty, this means that after we started the async - // query, we received a REALM_CHANGE that triggered an update of all async queries - // including the last async submitted, so no need to use the provided TableView pointer - // (or the user forced the sync behaviour .load()) - // 2- This RealmResults is still empty but this caller thread is advanced than the worker thread - // this could happen if the current thread advanced the shared_group (via a write or refresh) - // this means that we need to rerun the query against a newer worker thread. - - if (!realmResults.isLoaded()) { // UC2 - // UC covered by this test: RealmAsyncQueryTests#testFindAllAsyncRetry - RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s ] , %s caller is more advanced & RealmResults is not loaded, rerunning the query against the latest version", weakRealmResults, HandlerController.this); - - RealmQuery query = asyncRealmResults.get(weakRealmResults); - QueryUpdateTask queryUpdateTask = QueryUpdateTask.newBuilder() - .realmConfiguration(realm.getConfiguration()) - .add(weakRealmResults, - query.handoverQueryPointer(), - query.getArgument()) - .sendToNotifier(realm.sharedRealm.realmNotifier, - QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_RESULTS) - .build(); - - Realm.asyncTaskExecutor.submitQueryUpdate(queryUpdateTask); - - } else { - // UC covered by this test: RealmAsyncQueryTests#testFindAllCallerIsAdvanced - RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] , %s caller is more advanced & RealmResults is loaded ignore the outdated result", weakRealmResults, HandlerController.this); - } - - } else { - // the caller thread is behind the worker thread, - // no need to rerun the query, since we're going to receive the update signal - // & batch update all async queries including this one - // UC covered by this test: RealmAsyncQueryTests#testFindAllCallerThreadBehind - RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] , %s caller thread behind worker thread, ignore results (a batch update will update everything including this query)", weakRealmResults, HandlerController.this); - } - } - } - */ - } - - private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) { - SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID(); - int compare = callerVersionID.compareTo(result.versionID); - if (compare > 0) { - // if the caller thread is more advanced than the worker thread, it means it did a local commit. - // This should also have put a REALM_CHANGED event on the Looper queue, so ignoring this result should - // be safe as all async queries will be rerun when processing the REALM_CHANGED event. - RealmLog.trace("COMPLETED_UPDATE_ASYNC_QUERIES %s caller is more advanced, Looper will updates queries", HandlerController.this); - - } else { - // We're behind or on the same version as the worker thread - - // only advance if we're behind - if (compare != 0) { - // no need to remove old pointers from TableView, since they're - // imperative TV, they will not rerun if the SharedGroup advance - - // UC covered by this test: RealmAsyncQueryTests#testFindAllCallerThreadBehind - RealmLog.trace("COMPLETED_UPDATE_ASYNC_QUERIES %s caller is behind advance_read", HandlerController.this); - // refresh the Realm to the version provided by the worker thread - // (advanceRead to the latest version may cause a version mismatch error) preventing us - // from importing correctly the handover table view - try { - realm.sharedRealm.refresh(result.versionID); - } catch (BadVersionException e) { - // The version comparison above should have ensured that that the Caller version is less than the - // Worker version. In that case it should always be safe to advance_read. - throw new IllegalStateException("Failed to advance Caller Realm to Worker Realm version", e); - } - } - - // It's dangerous to notify the callback about new results before updating - // the pointers, because the callback may use another RealmResults not updated yet - // this is why we defer the notification until we're done updating all pointers. - ArrayList> resultsToBeNotified = new ArrayList>(result.updatedTableViews.size()); - for (Map.Entry>, Long> query : result.updatedTableViews.entrySet()) { - WeakReference> weakRealmResults = query.getKey(); - RealmResults realmResults = weakRealmResults.get(); - if (realmResults == null) { - // don't update GC'd instance - asyncRealmResults.remove(weakRealmResults); - - } else { - // update the instance with the new pointer - //realmResults.swapTableViewPointer(query.getValue()); - //realmResults.syncIfNeeded(); - resultsToBeNotified.add(realmResults); - - RealmLog.trace("COMPLETED_UPDATE_ASYNC_QUERIES updating RealmResults %s", HandlerController.this, weakRealmResults); - } - } - collectSyncRealmResultsCallbacks(resultsToBeNotified); - - // We need to notify all listeners, since the original REALM_CHANGE - // was delayed/swallowed in order to be able to update the async queries. - notifyAllListeners(resultsToBeNotified); - - updateAsyncQueriesTask = null; - } - } - - /** - * Trigger onSuccess for all completed async transaction. - *

          - * NOTE: Should only be called from {@link #notifyAllListeners(List)}. - */ - private void notifyAsyncTransactionCallbacks() { - if (!pendingOnSuccessAsyncTransactionCallbacks.isEmpty()) { - for (Runnable callback : pendingOnSuccessAsyncTransactionCallbacks) { - callback.run(); - } - pendingOnSuccessAsyncTransactionCallbacks.clear(); - } - } - - private void completedAsyncRealmObject(QueryUpdateTask.Result result) { - /* - Set> updatedRowKey = result.updatedRow.keySet(); - if (updatedRowKey.size() > 0) { - WeakReference realmObjectWeakReference = updatedRowKey.iterator().next(); - RealmObjectProxy proxy = realmObjectWeakReference.get(); - - if (proxy != null) { - SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID(); - int compare = callerVersionID.compareTo(result.versionID); - // we always query on the same version - // only two use cases could happen 1. we're on the same version or 2. the caller has advanced in the meanwhile - if (compare == 0) { //same version import the handover - long rowPointer = result.updatedRow.get(realmObjectWeakReference); - if (rowPointer != 0 && emptyAsyncRealmObject.containsKey(realmObjectWeakReference)) { - // cleanup a previously empty async RealmObject - emptyAsyncRealmObject.remove(realmObjectWeakReference); - realmObjects.put(realmObjectWeakReference, NO_REALM_QUERY); - } - proxy.realmGet$proxyState().onCompleted$realm(rowPointer); - proxy.realmGet$proxyState().notifyChangeListeners$realm(); - - } else if (compare > 0) { - // the caller has advanced we need to - // retry against the current version of the caller if it's still empty - if (RealmObject.isValid(proxy)) { // already completed & has a valid pointer no need to re-run - RealmLog.trace("[COMPLETED_ASYNC_REALM_OBJECT %s], realm: %s. " + - "RealmObject is already loaded, just notify it", - realm, HandlerController.this); - proxy.realmGet$proxyState().notifyChangeListeners$realm(); - - } else { - RealmLog.trace("[COMPLETED_ASYNC_REALM_OBJECT %s, realm: %s. " + - "RealmObject is not loaded yet. Rerun the query.", - proxy, HandlerController.this); - Object value = realmObjects.get(realmObjectWeakReference); - RealmQuery realmQuery; - if (value == null || value == NO_REALM_QUERY) { // this is a retry of an empty RealmObject - realmQuery = emptyAsyncRealmObject.get(realmObjectWeakReference); - - } else { - realmQuery = (RealmQuery) value; - } - - QueryUpdateTask queryUpdateTask = QueryUpdateTask.newBuilder() - .realmConfiguration(realm.getConfiguration()) - .addObject(realmObjectWeakReference, - realmQuery.handoverQueryPointer(), - realmQuery.getArgument()) - .sendToNotifier(realm.sharedRealm.realmNotifier, - QueryUpdateTask.NotifyEvent.COMPLETE_ASYNC_OBJECT) - .build(); - - Realm.asyncTaskExecutor.submitQueryUpdate(queryUpdateTask); - } - } else { - // should not happen, since the the background thread position itself against the provided version - // and the caller thread can only go forward (advance_read) - throw new IllegalStateException("Caller thread behind the Worker thread"); - } - } // else: element GC'd in the meanwhile - } - */ - } - - /** - * Indicate the presence of {@code RealmResults} obtained asynchronously, this will prevent advancing the Realm - * before updating the {@code RealmResults}, otherwise we will potentially re-run the queries in this thread. - * - * @return {@code true} if there is at least one (non GC'ed) instance of {@link RealmResults} {@code false} - * otherwise. - */ - private boolean threadContainsAsyncQueries() { - boolean isEmpty = true; - Iterator>, RealmQuery>> iterator = asyncRealmResults.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry>, RealmQuery> next = iterator.next(); - if (next.getKey().get() == null) { - iterator.remove(); - } else { - isEmpty = false; - } - } - - return !isEmpty; - } - - /** - * Indicates the presence of empty {@code RealmObject} obtained asynchronously using {@link RealmQuery#findFirstAsync()}. - * Empty means no pointer to a valid Row. This will help caller to decide when to rerun the query. - * - * @return {@code true} if there is at least one (non GC'ed) instance of {@link RealmObject}, {@code false} otherwise. - */ - boolean threadContainsAsyncEmptyRealmObject() { - boolean isEmpty = true; - Iterator, RealmQuery>> iterator = emptyAsyncRealmObject.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry, RealmQuery> next = iterator.next(); - if (next.getKey().get() == null) { - iterator.remove(); - } else { - isEmpty = false; - } - } - - return !isEmpty; - } - - private void deleteWeakReferences() { - Reference> weakReferenceResults; - Reference weakReferenceObject; - while ((weakReferenceResults = referenceQueueAsyncRealmResults.poll()) != null ) { // Does not wait for a reference to become available. - asyncRealmResults.remove(weakReferenceResults); - } - while ((weakReferenceResults = referenceQueueSyncRealmResults.poll()) != null ) { - syncRealmResults.remove(weakReferenceResults); - } - while ((weakReferenceObject = referenceQueueRealmObject.poll()) != null ) { - realmObjects.remove(weakReferenceObject); - } - } - - WeakReference> addToAsyncRealmResults(RealmResults realmResults, RealmQuery realmQuery) { - WeakReference> weakRealmResults = new WeakReference>(realmResults, - referenceQueueAsyncRealmResults); - asyncRealmResults.put(weakRealmResults, realmQuery); - return weakRealmResults; - } - - void addToRealmResults(RealmResults realmResults) { - WeakReference> realmResultsWeakReference - = new WeakReference>(realmResults, referenceQueueSyncRealmResults); - syncRealmResults.add(realmResultsWeakReference); - } - - // Add to the list of RealmObject to be notified after a commit. - // This method will check if the object exists in the list. It won't add the same object multiple times - void addToRealmObjects(E realmObject) { - for (WeakReference ref : realmObjects.keySet()) { - if (ref.get() == realmObject) { - return; - } - } - final WeakReference realmObjectWeakReference = - new WeakReference(realmObject, referenceQueueRealmObject); - realmObjects.put(realmObjectWeakReference, NO_REALM_QUERY); - } - - WeakReference addToAsyncRealmObject(E realmObject, RealmQuery realmQuery) { - final WeakReference realmObjectWeakReference = new WeakReference(realmObject, referenceQueueRealmObject); - realmObjects.put(realmObjectWeakReference, realmQuery); - return realmObjectWeakReference; - } - - void removeFromAsyncRealmObject(WeakReference realmObjectWeakReference) { - realmObjects.remove(realmObjectWeakReference); - } - - void addToEmptyAsyncRealmObject(WeakReference realmObjectWeakReference, RealmQuery realmQuery) { - emptyAsyncRealmObject.put(realmObjectWeakReference, realmQuery); - } - - /** - * Refreshes all synchronous RealmResults by calling {@code sync_if_needed} on them. This will cause any backing queries - * to rerun and any deleted objects will be removed from the TableView. - *

          - * WARNING: This will _NOT_ refresh TableViews created from async queries. - *

          - * Note this will _not_ notify any registered listeners. - */ - public void refreshSynchronousTableViews() { - Iterator>> iterator = syncRealmResults.keySet().iterator(); - while (iterator.hasNext()) { - WeakReference> weakRealmResults = iterator.next(); - RealmResults realmResults = weakRealmResults.get(); - if (realmResults == null) { - iterator.remove(); - } else { - //realmResults.syncIfNeeded(); - } - } - } - - /** - * Toggles the auto refresh flag. Will throw an {@link IllegalStateException} if auto-refresh is not available. - */ - public void setAutoRefresh(boolean autoRefresh) { - checkCanBeAutoRefreshed(); - this.autoRefresh = autoRefresh; - } - - public boolean isAutoRefreshEnabled() { - return autoRefresh; - } - - /** - * Validates that the current thread can enable auto refresh. An {@link IllegalStateException} will be thrown if that - * is not the case. - */ - public void checkCanBeAutoRefreshed() { - if (Looper.myLooper() == null) { - throw new IllegalStateException("Cannot set auto-refresh in a Thread without a Looper"); - } - if (isIntentServiceThread()) { - throw new IllegalStateException("Cannot set auto-refresh in an IntentService thread."); - } - } - - /** - * Checks if the auto-refresh feature is available on this thread. Calling {@link #setAutoRefresh(boolean)} - * will throw if this method return {@code false}. - */ - public boolean isAutoRefreshAvailable() { - if (Looper.myLooper() == null || isIntentServiceThread()) { - return false; - } - - return true; - } - - private static boolean isIntentServiceThread() { - // Tries to determine if a thread is an IntentService thread. No public API can detect this, - // so use the thread name as a heuristic: - // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/IntentService.java#108 - String threadName = Thread.currentThread().getName(); - return threadName != null && threadName.startsWith("IntentService["); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 4d3a6b0804..ee1e7967ac 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -352,9 +352,7 @@ public static void addChangeListener(E object, RealmChang RealmObjectProxy proxy = (RealmObjectProxy) object; BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); realm.checkIfValid(); - if (!realm.handlerController.isAutoRefreshEnabled()) { - throw new IllegalStateException("You can't register a listener from a non-Looper thread or IntentService thread."); - } + realm.sharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be added."); //noinspection unchecked proxy.realmGet$proxyState().addChangeListener(listener); } else { diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 6b93e711b4..6dfe00db63 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -26,15 +26,11 @@ import io.realm.internal.Collection; import io.realm.internal.LinkView; import io.realm.internal.PendingRow; -import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SharedRealm; import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.TableQuery; -import io.realm.internal.async.ArgumentsHolder; -import io.realm.internal.async.QueryUpdateTask; /** * A RealmQuery encapsulates a query on a {@link io.realm.Realm} or a {@link io.realm.RealmResults} using the Builder diff --git a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java index 608538c5bf..021fffbd9f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java @@ -18,5 +18,5 @@ public interface Capabilities { boolean canDeliverNotification(); - void checkCanDeliverNotification(); + void checkCanDeliverNotification(String exceptionMessage); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/HandlerControllerConstants.java b/realm/realm-library/src/main/java/io/realm/internal/HandlerControllerConstants.java deleted file mode 100644 index 029c91bc02..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/HandlerControllerConstants.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -/** - * This class is to share some Android handler related constants between package {@link io.realm} and - * {@link io.realm.internal.async}. - */ -public final class HandlerControllerConstants { - public static final int REALM_CHANGED = 14930352; // Hopefully it won't clash with other message IDs. - public static final int COMPLETED_UPDATE_ASYNC_QUERIES = 24157817; - public static final int COMPLETED_ASYNC_REALM_RESULTS = 39088169; - public static final int COMPLETED_ASYNC_REALM_OBJECT = 63245986; - public static final int REALM_ASYNC_BACKGROUND_EXCEPTION = 102334155; - public static final int LOCAL_COMMIT = 165580141; -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 5ab76d171d..2b075b7726 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -34,7 +34,7 @@ public final class SharedRealm implements Closeable { public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 4; public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 5; - public static final Capabilities capabilities = new AndroidCapabilities(); + private static final Capabilities capabilities = new AndroidCapabilities(); public static void initialize(File tempDirectory) { if (SharedRealm.temporaryDirectory != null) { @@ -337,7 +337,7 @@ public void updateSchema(RealmSchema schema, long version) { } public void setAutoRefresh(boolean enabled) { - capabilities.checkCanDeliverNotification(); + capabilities.checkCanDeliverNotification(null); nativeSetAutoRefresh(nativePtr, enabled); } @@ -345,6 +345,10 @@ public boolean isAutoRefresh() { return nativeIsAutoRefresh(nativePtr); } + public Capabilities getCapabilities() { + return capabilities; + } + @Override public void close() { if (realmNotifier != null) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java index 4aeb51af27..93f00cf2aa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java @@ -27,12 +27,14 @@ public boolean canDeliverNotification() { } @Override - public void checkCanDeliverNotification() { + public void checkCanDeliverNotification(String exceptionMessage) { if (Looper.myLooper() == null) { - throw new IllegalStateException("Cannot set auto-refresh in a Thread without a Looper"); + throw new IllegalStateException( exceptionMessage == null ? "" : (exceptionMessage + " ") + + "Realm cannot be automatically updated on a thread without a looper."); } if (isIntentServiceThread()) { - throw new IllegalStateException("Cannot set auto-refresh in an IntentService thread."); + throw new IllegalStateException( exceptionMessage == null ? "" : (exceptionMessage + " ") + + "Realm cannot be automatically updated on a IntentService thread."); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/ArgumentsHolder.java b/realm/realm-library/src/main/java/io/realm/internal/async/ArgumentsHolder.java deleted file mode 100644 index 8fed5b45c6..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/async/ArgumentsHolder.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal.async; - -import io.realm.Sort; - -/** - * Value holder class to encapsulate the arguments of a RealmQuery (in case we want to re-query). - */ -public class ArgumentsHolder { - public final static int TYPE_FIND_ALL = 0; - public final static int TYPE_FIND_ALL_SORTED = 1; - public final static int TYPE_FIND_ALL_MULTI_SORTED = 2; - public final static int TYPE_FIND_FIRST = 3; - public final static int TYPE_DISTINCT = 4; - - public final int type; - public long columnIndex; - public Sort sortOrder; - public long[] columnIndices; - public Sort[] sortOrders; - - public ArgumentsHolder(int type) { - this.type = type; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java b/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java deleted file mode 100644 index 0a2e09d421..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/async/QueryUpdateTask.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal.async; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.List; - -import io.realm.RealmConfiguration; -import io.realm.RealmModel; -import io.realm.RealmResults; -import io.realm.internal.RealmNotifier; -import io.realm.internal.RealmObjectProxy; -import io.realm.internal.SharedRealm; -import io.realm.internal.Table; -import io.realm.internal.TableQuery; -import io.realm.log.RealmLog; - -/** - * Manages the update of async queries. - */ -public class QueryUpdateTask implements Runnable { - - public enum NotifyEvent { - COMPLETE_ASYNC_RESULTS, - COMPLETE_ASYNC_OBJECT, - COMPLETE_UPDATE_ASYNC_QUERIES, - THROW_BACKGROUND_EXCEPTION, - } - - // true if updating RealmResults, false if updating RealmObject, can't mix both - // the builder pattern will prevent this. - private final static int MODE_UPDATE_REALM_RESULTS = 0; - private final static int MODE_UPDATE_REALM_OBJECT = 1; - private final int updateMode; - - private RealmConfiguration realmConfiguration; - private List realmResultsEntries; - private Builder.QueryEntry realmObjectEntry; - private WeakReference callerNotifier; - private NotifyEvent event; - - private QueryUpdateTask (int mode, - RealmConfiguration realmConfiguration, - List listOfRealmResults, - Builder.QueryEntry realmObject, - WeakReference notifier, - NotifyEvent event) { - this.updateMode = mode; - this.realmConfiguration = realmConfiguration; - this.realmResultsEntries = listOfRealmResults; - this.realmObjectEntry = realmObject; - this.callerNotifier = notifier; - this.event = event; - } - - public static Builder.RealmConfigurationStep newBuilder() { - return new Builder.Steps(); - } - - @Override - public void run() { - } - - private AlignedQueriesParameters prepareQueriesParameters() { - long[] handoverQueries = new long[realmResultsEntries.size()]; - long[][] queriesParameters = new long[realmResultsEntries.size()][6]; - long[][] multiSortColumnIndices = new long[realmResultsEntries.size()][]; - boolean[][] multiSortOrder = new boolean[realmResultsEntries.size()][]; - - int i = 0; - for (Builder.QueryEntry queryEntry : realmResultsEntries) { - switch (queryEntry.queryArguments.type) { - case ArgumentsHolder.TYPE_FIND_ALL: { - handoverQueries[i] = queryEntry.handoverQueryPointer; - queriesParameters[i][0] = ArgumentsHolder.TYPE_FIND_ALL; - queriesParameters[i][1] = 0; - queriesParameters[i][2] = Table.INFINITE; - queriesParameters[i][3] = Table.INFINITE; - break; - } - case ArgumentsHolder.TYPE_DISTINCT: { - handoverQueries[i] = queryEntry.handoverQueryPointer; - queriesParameters[i][0] = ArgumentsHolder.TYPE_DISTINCT; - queriesParameters[i][1] = queryEntry.queryArguments.columnIndex; - break; - } - case ArgumentsHolder.TYPE_FIND_ALL_SORTED: { - handoverQueries[i] = queryEntry.handoverQueryPointer; - queriesParameters[i][0] = ArgumentsHolder.TYPE_FIND_ALL_SORTED; - queriesParameters[i][1] = 0; - queriesParameters[i][2] = Table.INFINITE; - queriesParameters[i][3] = Table.INFINITE; - queriesParameters[i][4] = queryEntry.queryArguments.columnIndex; - queriesParameters[i][5] = (queryEntry.queryArguments.sortOrder.getValue()) ? 1 : 0; - break; - } - case ArgumentsHolder.TYPE_FIND_ALL_MULTI_SORTED: - handoverQueries[i] = queryEntry.handoverQueryPointer; - queriesParameters[i][0] = ArgumentsHolder.TYPE_FIND_ALL_MULTI_SORTED; - queriesParameters[i][1] = 0; - queriesParameters[i][2] = Table.INFINITE; - queriesParameters[i][3] = Table.INFINITE; - multiSortColumnIndices[i] = queryEntry.queryArguments.columnIndices; - multiSortOrder[i] = TableQuery.getNativeSortOrderValues(queryEntry.queryArguments.sortOrders); - break; - default: - throw new IllegalArgumentException("Query mode " + queryEntry.queryArguments.type + " not supported"); - } - i++; - } - AlignedQueriesParameters alignedParameters = new AlignedQueriesParameters(); - - alignedParameters.handoverQueries = handoverQueries; - alignedParameters.multiSortColumnIndices = multiSortColumnIndices; - alignedParameters.multiSortOrder = multiSortOrder; - alignedParameters.queriesParameters = queriesParameters; - - return alignedParameters; - } - - private void swapPointers(Result result, long[] handoverTableViewPointer) { - int i = 0; - for (Builder.QueryEntry queryEntry : realmResultsEntries) { - result.updatedTableViews.put(queryEntry.element, handoverTableViewPointer[i++]); - } - } - - private boolean isTaskCancelled() { - // no point continuing if the caller thread was stopped or this thread was interrupted - return Thread.currentThread().isInterrupted(); - } - - // result of the async query - public static class Result { - public IdentityHashMap>, Long> updatedTableViews; - public IdentityHashMap, Long> updatedRow; - public SharedRealm.VersionID versionID; - - public static Result newRealmResultsResponse() { - Result result = new Result(); - result.updatedTableViews = new IdentityHashMap>, Long>(1); - return result; - } - - public static Result newRealmObjectResponse() { - Result result = new Result(); - result.updatedRow = new IdentityHashMap, Long>(1); - return result; - } - } - private static class AlignedQueriesParameters { - long[] handoverQueries; - long[][] queriesParameters; - long[][] multiSortColumnIndices; - boolean[][] multiSortOrder; - } - /* - This uses the step builder pattern to guide the caller throughout the creation of the instance - http://rdafbn.blogspot.ie/2012/07/step-builder-pattern_28.html - Example of call: - QueryUpdateTask task = QueryUpdateTask.newBuilder() - .realmConfiguration(null, null) - .add(null, 0, null) - .add(null, 0, null) - .sendToNotifier(null, 0) - .build(); - - QueryUpdateTask task2 = QueryUpdateTask.newBuilder() - .realmConfiguration(null, null) - .addObject(null, 0, null) - .sendToNotifier(null, 0) - .build(); - */ - public static class Builder { - public interface RealmConfigurationStep { - UpdateQueryStep realmConfiguration (RealmConfiguration realmConfiguration); - } - - public interface UpdateQueryStep { - RealmResultsQueryStep add(WeakReference> weakReference, - long handoverQueryPointer, - ArgumentsHolder queryArguments); - HandlerStep addObject(WeakReference weakReference, - long handoverQueryPointer, - ArgumentsHolder queryArguments);// can only update 1 element - } - - public interface RealmResultsQueryStep { - RealmResultsQueryStep add(WeakReference> weakReference, - long handoverQueryPointer, - ArgumentsHolder queryArguments); - BuilderStep sendToNotifier(RealmNotifier notifier, NotifyEvent event); - } - - public interface HandlerStep { - BuilderStep sendToNotifier(RealmNotifier notifier, NotifyEvent event); - } - - public interface BuilderStep { - QueryUpdateTask build(); - } - - private static class Steps implements RealmConfigurationStep, UpdateQueryStep, RealmResultsQueryStep, HandlerStep, BuilderStep { - private RealmConfiguration realmConfiguration; - private List realmResultsEntries; - private QueryEntry realmObjectEntry; - private WeakReference callerNotifier; - private NotifyEvent event; - - @Override - public UpdateQueryStep realmConfiguration(RealmConfiguration realmConfiguration) { - this.realmConfiguration = realmConfiguration; - return this; - } - - @Override - public RealmResultsQueryStep add(WeakReference> weakReference, - long handoverQueryPointer, - ArgumentsHolder queryArguments) { - if (this.realmResultsEntries == null) { - this.realmResultsEntries = new ArrayList(1); - } - this.realmResultsEntries.add(new QueryEntry(weakReference, handoverQueryPointer, queryArguments)); - return this; - } - - @Override - public HandlerStep addObject(WeakReference weakReference, - long handoverQueryPointer, - ArgumentsHolder queryArguments) { - realmObjectEntry = - new QueryEntry(weakReference, handoverQueryPointer, queryArguments); - return this; - } - - @Override - public BuilderStep sendToNotifier(RealmNotifier notifier, NotifyEvent event) { - this.callerNotifier = new WeakReference(notifier); - this.event = event; - return this; - } - - @Override - public QueryUpdateTask build() { - return new QueryUpdateTask( - (realmResultsEntries != null) ? MODE_UPDATE_REALM_RESULTS : MODE_UPDATE_REALM_OBJECT, - realmConfiguration, - realmResultsEntries, - realmObjectEntry, - callerNotifier, - event); - } - } - - private static class QueryEntry { - final WeakReference element; - long handoverQueryPointer; - final ArgumentsHolder queryArguments; - - private QueryEntry(WeakReference element, long handoverQueryPointer, ArgumentsHolder queryArguments) { - this.element = element; - this.handoverQueryPointer = handoverQueryPointer; - this.queryArguments = queryArguments; - } - } - } -} From 38a66e96b509b9c7f452df81349f699886ccf192 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 11:34:27 +0800 Subject: [PATCH 0266/2110] Add global listener to a non-looper thread Realm Listener will be triggered immediately when a local transaction commited. Or it will be triggered when waitForChange detect changes. --- .../java/io/realm/NotificationsTest.java | 64 ++++++++++++++++++- .../src/main/java/io/realm/BaseRealm.java | 3 - .../src/main/java/io/realm/DynamicRealm.java | 5 +- .../src/main/java/io/realm/Realm.java | 5 +- .../java/io/realm/internal/SharedRealm.java | 9 --- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 573133c04b..9f8bf31ad2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -319,19 +319,40 @@ public void run() { @Test @RunTestInLooperThread - public void commitTransaction_delayChangeListenerOnSameThread() { + public void globalListener_looperThread_triggeredByLocalCommit() { final AtomicInteger success = new AtomicInteger(0); Realm realm = looperThread.realm; realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { - assertEquals(1, success.get()); + assertEquals(0, success.getAndIncrement()); looperThread.testComplete(); } }); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); + assertEquals(1, success.get()); + } + + @Test + @RunTestInLooperThread + public void globalListener_looperThread_triggeredByRemoteCommit() { + final AtomicInteger success = new AtomicInteger(0); + Realm realm = looperThread.realm; + realm.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(Realm object) { + assertEquals(1, success.get()); + looperThread.testComplete(); + } + }); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class); + } + }); assertEquals(0, success.getAndIncrement()); } @@ -1360,4 +1381,43 @@ public void onChange(RealmModel element) { } catch (IllegalStateException ignored) { } } + + + @Test + public void globalListener_nonLooperThread_triggeredByWaitForChange() { + final CountDownLatch latch = new CountDownLatch(1); + realm.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(Realm element) { + latch.countDown(); + } + }); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class); + } + }); + realm.waitForChange(); + TestHelper.awaitOrFail(latch); + } + + @Test + public void globalListener_nonLooperThread_triggeredByLocalCommit() { + final CountDownLatch latch = new CountDownLatch(1); + realm.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(Realm element) { + latch.countDown(); + } + }); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class); + } + }); + TestHelper.awaitOrFail(latch); + } + } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 7aa9f27a23..598c8f3195 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -127,7 +127,6 @@ protected void addListener(RealmChangeListener listener) { throw new IllegalArgumentException("Listener should not be null"); } checkIfValid(); - sharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be registered."); sharedRealm.realmNotifier.addChangeListener(this, listener); } @@ -144,7 +143,6 @@ public void removeChangeListener(RealmChangeListener listen throw new IllegalArgumentException("Listener should not be null"); } checkIfValid(); - sharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be removed."); sharedRealm.realmNotifier.removeChangeListener(this, listener); } @@ -176,7 +174,6 @@ public void removeChangeListener(RealmChangeListener listen */ public void removeAllChangeListeners() { checkIfValid(); - sharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be removed."); sharedRealm.realmNotifier.removeAllChangeListeners(); } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 9b529e0f24..17f7a2f2d3 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -123,8 +123,7 @@ public RealmQuery where(String className) { /** * Adds a change listener to the Realm. *

          - * The listeners will be executed on every loop of a Handler thread if changes are committed by - * this or another thread. + * The listeners will be executed when changes are committed by this or another thread. *

          * Realm instances are cached per thread. For that reason it is important to * remember to remove listeners again either using {@link #removeChangeListener(RealmChangeListener)} @@ -132,10 +131,10 @@ public RealmQuery where(String className) { * * @param listener the change listener. * @throws IllegalArgumentException if the change listener is {@code null}. - * @throws IllegalStateException if you try to register a listener from a non-Looper or {@link IntentService} thread. * @see io.realm.RealmChangeListener * @see #removeChangeListener(RealmChangeListener) * @see #removeAllChangeListeners() + * @see #waitForChange() */ public void addChangeListener(RealmChangeListener listener) { super.addListener(listener); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index dab358da60..9ce7d3be88 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1216,8 +1216,7 @@ public RealmQuery where(Class clazz) { /** * Adds a change listener to the Realm. *

          - * The listeners will be executed on every loop of a Handler thread if - * the current thread or other threads committed changes to the Realm. + * The listeners will be executed when changes are committed by this or another thread. *

          * Realm instances are per thread singletons and cached, so listeners should be * removed manually even if calling {@link #close()}. Otherwise there is a @@ -1225,10 +1224,10 @@ public RealmQuery where(Class clazz) { * * @param listener the change listener. * @throws IllegalArgumentException if the change listener is {@code null}. - * @throws IllegalStateException if you try to register a listener from a non-Looper or {@link IntentService} thread. * @see io.realm.RealmChangeListener * @see #removeChangeListener(RealmChangeListener) * @see #removeAllChangeListeners() + * @see #waitForChange() */ public void addChangeListener(RealmChangeListener listener) { super.addListener(listener); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 2b075b7726..22c91bddae 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -291,15 +291,6 @@ public void refresh() { invokeSchemaChangeListenerIfSchemaChanged(); } - public void refresh(SharedRealm.VersionID version) throws BadVersionException { - // FIXME: This will have a different behaviour compared to refresh to the latest version. - // In the JNI this will just advance read the corresponding SharedGroup to the specific version without notifier - // or transact log observer involved. Before we use notification & fine grained notification from OS, it is not - // a problem. - nativeRefresh(nativePtr, version.version, version.index); - invokeSchemaChangeListenerIfSchemaChanged(); - } - public SharedRealm.VersionID getVersionID() { long[] versionId = nativeGetVersionID (nativePtr); return new SharedRealm.VersionID(versionId[0], versionId[1]); From eea3d331002f01e4ea6360a4b579340b40af85b2 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 11:44:36 +0800 Subject: [PATCH 0267/2110] Remove commitTransaction(boolean) --- .../src/main/java/io/realm/BaseRealm.java | 19 ------------------- .../src/main/java/io/realm/Realm.java | 5 +++-- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 598c8f3195..72952a2f98 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -312,29 +312,10 @@ public void beginTransaction() { * changes from this commit. */ public void commitTransaction() { - commitTransaction(true); - } - - /** - * Commits transaction and sends notifications to local thread. - * - * @param notifyLocalThread set to {@code false} to prevent this commit from triggering thread local change - * listeners. - */ - void commitTransaction(boolean notifyLocalThread) { checkIfValid(); sharedRealm.commitTransaction(); ObjectServerFacade.getFacade(configuration.isSyncConfiguration()) .notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); - - // FIXME: Check if this is still needed. - // Sometimes we don't want to notify the local thread about commits, e.g. creating a completely new Realm - // file will make a commit in order to create the schema. Users should not be notified about that. - /* - if (notifyLocalThread) { - sharedRealm.realmNotifier.notifyCommitByLocalThread(); - } - */ } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 9ce7d3be88..7e8dd85c65 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -374,7 +374,7 @@ public void execute(Realm realm) { } finally { if (!syncAvailable) { if (commitNeeded) { - realm.commitTransaction(false); + realm.commitTransaction(); } else { realm.cancelTransaction(); } @@ -1347,6 +1347,7 @@ public void run() { boolean transactionCommitted = false; final Throwable[] exception = new Throwable[1]; + // FIXME: Disable notifier in SharedRealm final Realm bgRealm = Realm.getInstance(realmConfiguration); bgRealm.beginTransaction(); try { @@ -1354,7 +1355,7 @@ public void run() { if (!Thread.currentThread().isInterrupted()) { // No need to send change notification to the work thread. - bgRealm.commitTransaction(false); + bgRealm.commitTransaction(); // The bgRealm needs to be closed before post event to caller's handler to avoid concurrency // problem. This is currently guaranteed by posting handleAsyncTransactionCompleted below. bgRealm.close(); From d73fbafaf29cfe1fb0987a67cc0b2aee89f8d156 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 11:48:17 +0800 Subject: [PATCH 0268/2110] warnings --- realm/realm-library/src/main/java/io/realm/DynamicRealm.java | 2 -- realm/realm-library/src/main/java/io/realm/Realm.java | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 17f7a2f2d3..91f3ca9578 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -16,8 +16,6 @@ package io.realm; -import android.app.IntentService; - import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.internal.Table; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 7e8dd85c65..28a6dfb259 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -17,7 +17,6 @@ package io.realm; import android.annotation.TargetApi; -import android.app.IntentService; import android.content.Context; import android.os.Build; import android.util.JsonReader; @@ -621,8 +620,7 @@ public E createOrUpdateObjectFromJson(Class clazz, JSO checkIfValid(); checkHasPrimaryKey(clazz); try { - E realmObject = configuration.getSchemaMediator().createOrUpdateUsingJsonObject(clazz, this, json, true); - return realmObject; + return configuration.getSchemaMediator().createOrUpdateUsingJsonObject(clazz, this, json, true); } catch (JSONException e) { throw new RealmException("Could not map JSON", e); } From 2f7dcfc921080ec5e27c76de801732687f7176c5 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 13:39:55 +0800 Subject: [PATCH 0269/2110] Use getTargetTable to build query on linkview otherwise RealmQueryTests.findFirst() will fail because it compares the row with the tables' name first. --- .../realm-library/src/main/java/io/realm/internal/LinkView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index 2218a9e015..996ebbe078 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -126,7 +126,7 @@ public boolean isEmpty() { public TableQuery where() { long nativeQueryPtr = nativeWhere(nativePtr); - return new TableQuery(this.context, this.parent, nativeQueryPtr); + return new TableQuery(this.context, this.getTargetTable(), nativeQueryPtr); } public boolean isAttached() { From 9d6cb1aadb70078809104645354bc7a2226d9a30 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 14:32:48 +0800 Subject: [PATCH 0270/2110] Fix link's field query tests --- .../java/io/realm/RealmQueryTests.java | 84 ++++++++++++++----- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 85242d62b8..4b3cae293c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -108,6 +108,11 @@ private void populateTestRealm(Realm testRealm, int dataSize) { nonLatinFieldNames.setΔέλτα(i); nonLatinFieldNames.set베타(1.234567f + i); nonLatinFieldNames.setΒήτα(1.234567f + i); + + Dog dog = testRealm.createObject(Dog.class); + dog.setAge(i); + dog.setName("test data " + i); + allTypes.setColumnRealmObject(dog); } testRealm.commitTransaction(); } @@ -2586,40 +2591,73 @@ public void execute(Realm realm) { } @Test - public void findAllSorted_onSubObjectFieldThrows() { - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Sorting using child object fields is not supported: "); - realm.where(AllTypes.class).findAllSorted(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BOOLEAN); + public void findAllSorted_onSubObjectField() { + populateTestRealm(realm, TEST_DATA_SIZE); + RealmResults results = realm.where(AllTypes.class) + .findAllSorted(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE); + assertEquals(0, results.get(0).getColumnRealmObject().getAge()); + assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); } @Test - public void findAllSortedAsync_onSubObjectFieldThrows() { - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Sorting using child object fields is not supported: "); - realm.where(AllTypes.class).findAllSortedAsync( - AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BOOLEAN); + @RunTestInLooperThread + public void findAllSorted_async_onSubObjectField() { + Realm realm = looperThread.realm; + populateTestRealm(realm, TEST_DATA_SIZE); + RealmResults results = realm.where(AllTypes.class) + .findAllSorted(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE); + looperThread.keepStrongReference.add(results); + results.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults results) { + assertEquals(0, results.get(0).getColumnRealmObject().getAge()); + assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); + looperThread.testComplete(); + } + }); } @Test - public void findAllSorted_listOnSubObjectFieldThrows() { - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Sorting using child object fields is not supported: "); - String[] fieldNames = new String[1]; - fieldNames[0] = AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BOOLEAN; - Sort[] sorts = new Sort[1]; + public void findAllSorted_listOnSubObjectField() { + String[] fieldNames = new String[2]; + fieldNames[0] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; + fieldNames[1] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; + + Sort[] sorts = new Sort[2]; sorts[0] = Sort.ASCENDING; - realm.where(AllTypes.class).findAllSorted(fieldNames, sorts); + sorts[1] = Sort.ASCENDING; + + populateTestRealm(realm, TEST_DATA_SIZE); + RealmResults results = realm.where(AllTypes.class) + .findAllSorted(fieldNames, sorts); + assertEquals(0, results.get(0).getColumnRealmObject().getAge()); + assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); } @Test - public void findAllSortedAsync_listOnSubObjectFieldThrows() { - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Sorting using child object fields is not supported: "); - String[] fieldNames = new String[1]; - fieldNames[0] = AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BOOLEAN; - Sort[] sorts = new Sort[1]; + @RunTestInLooperThread + public void findAllSorted_async_listOnSubObjectField() { + Realm realm = looperThread.realm; + String[] fieldNames = new String[2]; + fieldNames[0] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; + fieldNames[1] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; + + Sort[] sorts = new Sort[2]; sorts[0] = Sort.ASCENDING; - realm.where(AllTypes.class).findAllSortedAsync(fieldNames, sorts); + sorts[1] = Sort.ASCENDING; + + populateTestRealm(realm, TEST_DATA_SIZE); + RealmResults results = realm.where(AllTypes.class) + .findAllSorted(fieldNames, sorts); + looperThread.keepStrongReference.add(results); + results.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults results) { + assertEquals(0, results.get(0).getColumnRealmObject().getAge()); + assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); + looperThread.testComplete(); + } + }); } // RealmQuery.distinct(): requires indexing, and type = boolean, integer, date, string From d51fd4815d28c34cd120e781013b016ecba50d1b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 15:24:22 +0800 Subject: [PATCH 0271/2110] PendingRow can return a CheckedRow for DynamicRealmObject. Also some code cleanup. --- .../processor/RealmProxyClassGenerator.java | 2 +- .../io/realm/AllTypesRealmProxy.java | 2 +- .../io/realm/BooleansRealmProxy.java | 2 +- .../io/realm/NullTypesRealmProxy.java | 2 +- .../resources/io/realm/SimpleRealmProxy.java | 2 +- .../java/io/realm/DynamicRealmTests.java | 17 ++- .../java/io/realm/DynamicRealmObject.java | 8 -- .../src/main/java/io/realm/ProxyState.java | 93 ---------------- .../src/main/java/io/realm/RealmObject.java | 105 ++---------------- .../src/main/java/io/realm/RealmQuery.java | 4 +- .../java/io/realm/internal/PendingRow.java | 17 ++- 11 files changed, 43 insertions(+), 211 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 2939fcfc1a..6f7c49e075 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -501,7 +501,7 @@ private void emitInjectContextMethod(JavaWriter writer) throws IOException { writer.emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()"); writer.emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()); - writer.emitStatement("this.proxyState = new ProxyState(%1$s.class, this)", qualifiedClassName); + writer.emitStatement("this.proxyState = new ProxyState(this)"); writer.emitStatement("proxyState.setRealm$realm(context.getRealm())"); writer.emitStatement("proxyState.setRow$realm(context.getRow())"); writer.emitStatement("proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue())"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 09d5c21d07..d10c4af98d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -118,7 +118,7 @@ public final AllTypesColumnInfo clone() { private void injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (AllTypesColumnInfo) context.getColumnInfo(); - this.proxyState = new ProxyState(some.test.AllTypes.class, this); + this.proxyState = new ProxyState(this); proxyState.setRealm$realm(context.getRealm()); proxyState.setRow$realm(context.getRow()); proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 7323ad27f6..8fa4bcdcc7 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -92,7 +92,7 @@ public final BooleansColumnInfo clone() { private void injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (BooleansColumnInfo) context.getColumnInfo(); - this.proxyState = new ProxyState(some.test.Booleans.class, this); + this.proxyState = new ProxyState(this); proxyState.setRealm$realm(context.getRealm()); proxyState.setRow$realm(context.getRow()); proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index ee1b354fc1..538ab4df5b 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -177,7 +177,7 @@ public final NullTypesColumnInfo clone() { private void injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (NullTypesColumnInfo) context.getColumnInfo(); - this.proxyState = new ProxyState(some.test.NullTypes.class, this); + this.proxyState = new ProxyState(this); proxyState.setRealm$realm(context.getRealm()); proxyState.setRow$realm(context.getRow()); proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index a1e79528a5..5092e82e7f 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -82,7 +82,7 @@ public final SimpleColumnInfo clone() { private void injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (SimpleColumnInfo) context.getColumnInfo(); - this.proxyState = new ProxyState(some.test.Simple.class, this); + this.proxyState = new ProxyState(this); proxyState.setRealm$realm(context.getRealm()); proxyState.setRow$realm(context.getRow()); proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index c564f4c162..bad48a2574 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -16,7 +16,6 @@ package io.realm; -import android.os.Handler; import android.support.test.runner.AndroidJUnit4; import org.junit.After; @@ -41,6 +40,7 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.exceptions.RealmException; +import io.realm.internal.PendingRow; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; @@ -340,14 +340,23 @@ public void execute(DynamicRealm realm) { assertEquals(0, realm.where("Owner").count()); } + @Test + public void findFirst() { + final DynamicRealmObject allTypes = realm.where(AllTypes.CLASS_NAME) + .between(AllTypes.FIELD_LONG, 4, 9) + .findFirst(); + populateTestRealm(realm, 10); + assertEquals("test data 4", allTypes.getString(AllTypes.FIELD_STRING)); + } + @Test @RunTestInLooperThread - public void findFirstAsync() { + public void findFirst_async() { final DynamicRealm dynamicRealm = initializeDynamicRealm(); final DynamicRealmObject allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 4, 9) - .findFirstAsync(); - assertFalse(allTypes.isLoaded()); + .findFirst(); + assertTrue(allTypes.realmGet$proxyState().getRow$realm() instanceof PendingRow); looperThread.keepStrongReference.add(allTypes); allTypes.addChangeListener(new RealmChangeListener() { @Override diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 95c4ae2014..dd7a78c11c 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -75,14 +75,6 @@ public DynamicRealmObject(RealmModel obj) { proxyState.setConstructionFinished(); } - // row must not be an instance of UncheckedRow - DynamicRealmObject(String className, BaseRealm realm, Row row) { - proxyState.setClassName(className); - proxyState.setRealm$realm(realm); - proxyState.setRow$realm(row); - proxyState.setConstructionFinished(); - } - /** * Returns the value for the given field. * diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index c36f12d6a8..41bd384c0c 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -18,15 +18,11 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Future; import io.realm.internal.PendingRow; import io.realm.internal.Row; import io.realm.internal.RowNotifier; -import io.realm.internal.Table; -import io.realm.internal.TableQuery; import io.realm.internal.UncheckedRow; -import io.realm.log.RealmLog; /** * This implements {@code RealmObjectProxy} interface, to eliminate copying logic between @@ -34,8 +30,6 @@ */ public final class ProxyState implements PendingRow.FrontEnd { private E model; - private String className; - private Class clazzName; // true only while executing the constructor of the enclosing proxy object private boolean underConstruction = true; @@ -46,8 +40,6 @@ public final class ProxyState implements PendingRow.FrontE private List excludeFields; private final List> listeners = new CopyOnWriteArrayList>(); - private Future pendingQuery; - private boolean isCompleted = false; protected long currentTableVersion = -1; public ProxyState() {} @@ -56,28 +48,6 @@ public ProxyState(E model) { this.model = model; } - public ProxyState(Class clazzName, E model) { - this.clazzName = clazzName; - this.model = model; - } - - /** - * Sets the Future instance returned by the worker thread, we need this instance to force {@link RealmObject#load()} an async - * query, we use it to determine if the current RealmResults is a sync or async one. - * - * @param pendingQuery pending query. - */ - public void setPendingQuery$realm(Future pendingQuery) { - this.pendingQuery = pendingQuery; - if (isLoaded()) { - // the query completed before RealmQuery - // had a chance to call setPendingQuery to register the pendingQuery (used btw - // to determine isLoaded behaviour) - onCompleted$realm(); - - } // else, it will be handled by the Realm#handler - } - public BaseRealm getRealm$realm() { return realm; } @@ -110,57 +80,10 @@ public ProxyState(Class clazzName, E model) { this.excludeFields = excludeFields; } - public Object getPendingQuery$realm() { - return pendingQuery; - } - - public boolean isCompleted$realm() { - return isCompleted; - } - - /** - * Called to import the handover row pointer and notify listeners. - * - * @return {@code true} if it successfully completed the query, {@code false} otherwise. - */ - public boolean onCompleted$realm() { - try { - Long handoverResult = pendingQuery.get();// make the query blocking - if (handoverResult != 0) { - // this may fail with BadVersionException if the caller and/or the worker thread - // are not in sync (same shared_group version). - // COMPLETED_ASYNC_REALM_OBJECT will be fired by the worker thread - // this should handle more complex use cases like retry, ignore etc - onCompleted$realm(handoverResult); - notifyChangeListeners$realm(); - } else { - isCompleted = true; - } - } catch (Exception e) { - RealmLog.debug(e); - return false; - } - return true; - } - public List> getListeners$realm() { return listeners; } - public void onCompleted$realm(long handoverRowPointer) { - if (handoverRowPointer == 0) { - // we'll retry later to update the row pointer, but we consider - // the query done - isCompleted = true; - - } else if (!isCompleted || row == Row.EMPTY_ROW) { - isCompleted = true; - long nativeRowPointer = TableQuery.importHandoverRow(handoverRowPointer, realm.sharedRealm); - Table table = getTable(); - this.row = table.getUncheckedRowByPointer(nativeRowPointer); - }// else: already loaded query no need to import again the pointer - } - /** * Notifies all registered listeners. */ @@ -193,10 +116,6 @@ public void onChange(ProxyState proxyState) { } } - public void setClassName(String className) { - this.className = className; - } - public boolean isUnderConstruction() { return underConstruction; } @@ -207,18 +126,6 @@ public void setConstructionFinished() { excludeFields = null; } - private Table getTable () { - if (className != null) { - return getRealm$realm().schema.getTable(className); - } - return getRealm$realm().schema.getTable(clazzName); - } - - private boolean isLoaded() { - realm.checkIfValid(); - return getPendingQuery$realm() == null || isCompleted$realm(); - } - @Override public void onQueryFinished(Row row, boolean asyncQuery) { this.row = row; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index ee1e7967ac..a4b714c4e5 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -18,8 +18,6 @@ import android.app.IntentService; -import java.util.List; - import io.realm.annotations.RealmClass; import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; @@ -151,94 +149,27 @@ public static boolean isValid(E object) { } /** - * Checks if the query used to find this RealmObject has completed. - * - * Async methods like {@link RealmQuery#findFirstAsync()} return an {@link RealmObject} that represents the future result - * of the {@link RealmQuery}. It can be considered similar to a {@link java.util.concurrent.Future} in this regard. - * - * Once {@code isLoaded()} returns {@code true}, the object represents the query result even if the query - * didn't find any object matching the query parameters. In this case the {@link RealmObject} will - * become a "null" object. - * - * "Null" objects represents {@code null}. An exception is throw if any accessor is called, so it is important to also - * check {@link #isValid()} before calling any methods. A common pattern is: - * - *

          -     * {@code
          -     * Person person = realm.where(Person.class).findFirstAsync();
          -     * person.isLoaded(); // == false
          -     * person.addChangeListener(new RealmChangeListener() {
          -     *      \@Override
          -     *      public void onChange(Person person) {
          -     *          person.isLoaded(); // Always true here
          -     *          if (person.isValid()) {
          -     *              // It is safe to access the person.
          -     *          }
          -     *      }
          -     * });
          -     * }
          -     * 
          - * - * Synchronous RealmObjects are by definition blocking hence this method will always return {@code true} for them. - * This method will return {@code true} if called on an unmanaged object (created outside of Realm). - * - * @return {@code true} if the query has completed, {@code false} if the query is in - * progress. + * @deprecated + * @return {@code true} always. * * @see #isValid() */ public final boolean isLoaded() { + //noinspection deprecation return RealmObject.isLoaded(this); } /** - * Checks if the query used to find this RealmObject has completed. - * - * Async methods like {@link RealmQuery#findFirstAsync()} return an {@link RealmObject} that represents the future result - * of the {@link RealmQuery}. It can be considered similar to a {@link java.util.concurrent.Future} in this regard. - * - * Once {@code isLoaded()} returns {@code true}, the object represents the query result even if the query - * didn't find any object matching the query parameters. In this case the {@link RealmObject} will - * become a "null" object. - * - * "Null" objects represents {@code null}. An exception is throw if any accessor is called, so it is important to also - * check {@link #isValid()} before calling any methods. A common pattern is: - * - *
          -     * {@code
          -     * Person person = realm.where(Person.class).findFirstAsync();
          -     * RealmObject.isLoaded(person); // == false
          -     * RealmObject.addChangeListener(person, new RealmChangeListener() {
          -     *      \@Override
          -     *      public void onChange(Person person) {
          -     *          RealmObject.isLoaded(person); // always true here
          -     *          if (RealmObject.isValid(person)) {
          -     *              // It is safe to access the person.
          -     *          }
          -     *      }
          -     * });
          -     * }
          -     * 
          - * - * Synchronous RealmObjects are by definition blocking hence this method will always return {@code true} for them. - * This method will return {@code true} if called on an unmanaged object (created outside of Realm). - * - * + * @deprecated * @param object RealmObject to check. - * @return {@code true} if the query has completed, {@code false} if the query is in - * progress. + * @return {@code true} always. * * @see #isValid(RealmModel) */ + @SuppressWarnings("UnusedParameters") public static boolean isLoaded(E object) { - if (object instanceof RealmObjectProxy) { - RealmObjectProxy proxy = (RealmObjectProxy) object; - proxy.realmGet$proxyState().getRealm$realm().checkIfValid(); - return proxy.realmGet$proxyState().getPendingQuery$realm() == null || proxy.realmGet$proxyState().isCompleted$realm(); - } else { - return true; - } + return true; } /** @@ -295,29 +226,15 @@ public static boolean isManaged(E object) { * @return {@code true} if it successfully completed the query, {@code false} otherwise. */ public final boolean load() { + //noinspection deprecation return RealmObject.load(this); } /** - * Makes an asynchronous query blocking. This will also trigger any registered listeners. - *

          - * Note: This will return {@code true} if called for an unmanaged object (created outside of Realm). - * - * @param object RealmObject to force load. - * @return {@code true} if it successfully completed the query, {@code false} otherwise. + * @deprecated */ public static boolean load(E object) { - if (RealmObject.isLoaded(object)) { - return true; - } else { - if (object instanceof RealmObjectProxy) { - // doesn't guarantee to import correctly the result (because the user may have advanced) - // in this case the Realm#handler will be responsible of retrying - return ((RealmObjectProxy) object).realmGet$proxyState().onCompleted$realm(); - } else { - return false; - } - } + return object instanceof RealmObjectProxy; } /** @@ -329,6 +246,7 @@ public static boolean load(E object) { * @throws IllegalStateException if you try to add a listener from a non-Looper or {@link IntentService} thread. */ public final void addChangeListener(RealmChangeListener listener) { + //noinspection unchecked RealmObject.addChangeListener((E) this, listener); } @@ -453,6 +371,7 @@ public static void removeChangeListeners(E object) { * @see RxJava and Realm */ public final Observable asObservable() { + //noinspection unchecked return (Observable) RealmObject.asObservable(this); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 6dfe00db63..49108ac3df 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1644,12 +1644,12 @@ public E findFirst() { // TODO: The performance by the pending query will be a little bit worse than directly calling core's // Query.find(). The overhead comes with core needs to add all the row indices to the vector. However this // can be optimized by adding support of limit in OS's Results which is supported by core already. - row = new PendingRow(realm.sharedRealm, query, null); + row = new PendingRow(realm.sharedRealm, query, null, isDynamicQuery()); } final E result; if (isDynamicQuery()) { //noinspection unchecked - result = (E) new DynamicRealmObject(className, realm, row); + result = (E) new DynamicRealmObject(realm, row); } else { result = realm.getConfiguration().getSchemaMediator().newInstance( clazz, realm, row, realm.getSchema().getColumnInfo(clazz), diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 3f24caf8c9..c54a0661c0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -32,8 +32,10 @@ public interface FrontEnd { private Collection pendingCollection; private Collection.Listener listener; private WeakReference frontEnd; + private boolean returnCheckedRow; - public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor) { + public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, + final boolean returnCheckedRow) { pendingCollection = new Collection(sharedRealm, query, sortDescriptor); listener = new Collection.Listener(new RealmChangeListener() { @Override @@ -42,14 +44,15 @@ public void onChange(PendingRow pendingRow) { throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); } // TODO: PendingRow will always get the first Row of the query since we only support findFirst. - Row row = pendingCollection.firstUncheckedRow(); if (frontEnd.get() == null) { // The front end is GCed. clearPendingCollection(); return; } + UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); // If no rows returned by the query, just wait for the query updates until it returns a valid row. - if (row != null) { + if (uncheckedRow != null) { + Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; // Ask the front end to reset the row and stop async query. frontEnd.get().onQueryFinished(row, true); clearPendingCollection(); @@ -57,6 +60,7 @@ public void onChange(PendingRow pendingRow) { } }, this); pendingCollection.addListener(listener); + this.returnCheckedRow = returnCheckedRow; } // To set the front end of this PendingRow. @@ -222,12 +226,13 @@ private Row executeQuery() { if (frontEnd == null) { throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); } - Row row = pendingCollection.getUncheckedRow(0); - if (row == null) { + UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); + if (uncheckedRow == null) { throw new IllegalStateException(EMPTY_ROW_MESSAGE); } + Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; if (frontEnd.get() != null) { - frontEnd.get().onQueryFinished(pendingCollection.firstUncheckedRow(), false); + frontEnd.get().onQueryFinished(row, false); } clearPendingCollection(); return row; From b85ba8d879a062e9064e3a7d5d9dc5875386dd4d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 15:25:14 +0800 Subject: [PATCH 0272/2110] Remove EMPTY_ROW --- .../src/main/java/io/realm/internal/Row.java | 150 ------------------ 1 file changed, 150 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index b63f85fd34..78ddf7ce24 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -111,154 +111,4 @@ public interface Row { * @return {@code true} if field name exists, {@code false} otherwise. */ boolean hasColumn(String fieldName); - - Row EMPTY_ROW = new Row() { - private final static String UNLOADED_ROW_MESSAGE = "Can't access a row that hasn't been loaded or represents 'null', " + - "make sure the instance is loaded and is valid by calling 'RealmObject.isLoaded() && RealmObject.isValid()'."; - - @Override - public long getColumnCount() { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public String getColumnName(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public long getColumnIndex(String columnName) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public RealmFieldType getColumnType(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public Table getTable() { - return null; - } - - @Override - public long getIndex() { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public long getLong(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public boolean getBoolean(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public float getFloat(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public double getDouble(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public Date getDate(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public String getString(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public byte[] getBinaryByteArray(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public long getLink(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public boolean isNullLink(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public boolean isNull(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void setNull(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public LinkView getLinkList(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void setLong(long columnIndex, long value) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void setBoolean(long columnIndex, boolean value) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void setFloat(long columnIndex, float value) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void setDouble(long columnIndex, double value) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void setDate(long columnIndex, Date date) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void setString(long columnIndex, String value) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void setBinaryByteArray(long columnIndex, byte[] data) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void setLink(long columnIndex, long value) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public void nullifyLink(long columnIndex) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - - @Override - public boolean isAttached() { - return false; - } - - @Override - public boolean hasColumn(String fieldName) { - throw new IllegalStateException(UNLOADED_ROW_MESSAGE); - } - }; } From 6100fd2e4d331b2b312a5bd6ebfa19ec52231ff4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 21:07:10 +0800 Subject: [PATCH 0273/2110] Clear the memory ownership for RowNotifier --- .../src/main/cpp/java_binding_context.cpp | 68 ++++++----- .../src/main/cpp/java_binding_context.hpp | 20 +-- .../src/main/cpp/jni_util/method.hpp | 22 +++- .../src/main/java/io/realm/BaseRealm.java | 10 +- .../src/main/java/io/realm/ProxyState.java | 8 +- .../java/io/realm/internal/ObserverPair.java | 10 +- .../java/io/realm/internal/RealmNotifier.java | 23 ++-- .../java/io/realm/internal/RowNotifier.java | 114 +++++++++++++----- 8 files changed, 177 insertions(+), 98 deletions(-) diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index 76eaf93d8a..b113701647 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -16,11 +16,15 @@ #include "java_binding_context.hpp" -#include "util/format.hpp" -#include "util.hpp" - using namespace realm; using namespace realm::_impl; +using namespace realm::jni_util; + +JniMethod JavaBindingContext::m_realm_notifier_on_change_method; +JniMethod JavaBindingContext::m_get_observers_method; +JniMethod JavaBindingContext::m_get_observed_row_ptrs_method; +JniMethod JavaBindingContext::m_clear_row_refs_method; +JniMethod JavaBindingContext::m_row_observer_pair_on_change_method; JavaBindingContext::JavaBindingContext(const ConcreteJavaBindContext& concrete_context) : m_local_jni_env(concrete_context.jni_env) @@ -29,23 +33,33 @@ JavaBindingContext::JavaBindingContext(const ConcreteJavaBindContext& concrete_c if (ret != 0) { throw std::runtime_error(util::format("Failed to get Java vm. Error: %d", ret)); } + if (concrete_context.realm_notifier) { m_realm_notifier = m_local_jni_env->NewWeakGlobalRef(concrete_context.realm_notifier); - jclass cls = m_local_jni_env->GetObjectClass(m_realm_notifier); - m_notify_by_other_method = m_local_jni_env->GetMethodID(cls, "notifyCommitByOtherThread", "()V"); + if (!m_realm_notifier_on_change_method) { + m_realm_notifier_on_change_method = JniMethod(m_local_jni_env, m_realm_notifier, + "onChange", "()V"); + } } else { m_realm_notifier = nullptr; } + if (concrete_context.row_notifier) { m_row_notifier = m_local_jni_env->NewWeakGlobalRef(concrete_context.row_notifier); - jclass cls = m_local_jni_env->GetObjectClass(m_row_notifier); - m_get_observers_method = m_local_jni_env->GetMethodID(cls, "getObservers", - "()[Lio/realm/internal/RowNotifier$Observer;"); - m_get_observed_row_ptrs_method = m_local_jni_env->GetMethodID(cls, "getObservedRowPtrs", - "([Lio/realm/internal/RowNotifier$Observer;)[J"); - m_clear_row_refs = m_local_jni_env->GetMethodID(cls, "clearRowRefs", "()V"); - jclass observer_cls = GetClass(m_local_jni_env, "io/realm/internal/RowNotifier$Observer"); - m_observer_notify_listener = m_local_jni_env->GetMethodID(observer_cls, "notifyListener", "()V"); + if (!m_get_observers_method || !m_get_observed_row_ptrs_method || !m_clear_row_refs_method) { + // They should be false or true all together + jclass cls = m_local_jni_env->GetObjectClass(m_row_notifier); + m_get_observers_method = JniMethod(m_local_jni_env, cls, + "getObservers", "()[Lio/realm/internal/RowNotifier$RowObserverPair;"); + m_get_observed_row_ptrs_method = JniMethod(m_local_jni_env, cls, "getObservedRowPtrs", + "([Lio/realm/internal/RowNotifier$RowObserverPair;)[J"); + m_clear_row_refs_method = JniMethod(m_local_jni_env, cls, "clearRowRefs", "()V"); + m_local_jni_env->DeleteLocalRef(cls); + } + if (!m_row_observer_pair_on_change_method) { + m_row_observer_pair_on_change_method = + JniMethod(m_local_jni_env, "io/realm/internal/RowNotifier$RowObserverPair", "onChange", "()V"); + } } else { m_row_notifier = nullptr; } @@ -53,25 +67,19 @@ JavaBindingContext::JavaBindingContext(const ConcreteJavaBindContext& concrete_c JavaBindingContext::~JavaBindingContext() { - if (m_realm_notifier) { + if (m_realm_notifier || m_row_notifier) { // Always try to attach here since this may be called in the finalizer/phantom thread where m_local_jni_env // should not be used on. No need to call DetachCurrentThread since this thread should always be created by // JVM. JNIEnv *env; m_jvm->AttachCurrentThread(&env, nullptr); - env->DeleteWeakGlobalRef(m_realm_notifier); - } -} - -void JavaBindingContext::changes_available() -{ - /* - jobject notifier = m_local_jni_env->NewLocalRef(m_realm_notifier); - if (notifier) { - m_local_jni_env->CallVoidMethod(m_realm_notifier, m_notify_by_other_method); - m_local_jni_env->DeleteLocalRef(notifier); + if (m_realm_notifier) { + env->DeleteWeakGlobalRef(m_realm_notifier); + } + if (m_row_notifier) { + env->DeleteWeakGlobalRef(m_row_notifier); + } } - */ } std::vector JavaBindingContext::get_observed_rows() @@ -107,17 +115,17 @@ void JavaBindingContext::did_change(std::vector c for (auto state : observer_state_list) { jobject observer = reinterpret_cast(state.info); //if (!state.changes.empty()) { - m_local_jni_env->CallVoidMethod(observer, m_observer_notify_listener); + m_local_jni_env->CallVoidMethod(observer, m_row_observer_pair_on_change_method); //} } for (auto deleted_row_observer : invalidated) { jobject observer = reinterpret_cast(deleted_row_observer); - m_local_jni_env->CallVoidMethod(observer, m_observer_notify_listener); + m_local_jni_env->CallVoidMethod(observer, m_row_observer_pair_on_change_method); } - m_local_jni_env->CallVoidMethod(m_row_notifier, m_clear_row_refs); + m_local_jni_env->CallVoidMethod(m_row_notifier, m_clear_row_refs_method); jobject notifier = m_local_jni_env->NewLocalRef(m_realm_notifier); if (notifier) { - m_local_jni_env->CallVoidMethod(m_realm_notifier, m_notify_by_other_method); + m_local_jni_env->CallVoidMethod(m_realm_notifier, m_realm_notifier_on_change_method); m_local_jni_env->DeleteLocalRef(notifier); } } diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index 0a338dd6ac..38e195c337 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -22,6 +22,8 @@ #include "binding_context.hpp" +#include "jni_util/method.hpp" + namespace realm { namespace _impl { @@ -43,29 +45,29 @@ class JavaBindingContext final : public BindingContext { // A weak global ref to the implementation of RealmNotifier // Java should hold a strong ref to it as long as the SharedRealm lives jobject m_realm_notifier; - // Method IDs from RealmNotifier implementation. Cache them as member vars. - jmethodID m_notify_by_other_method; - jmethodID m_realm_notifier_on_change; // A weak global ref to the RowNotifier object. Java should hold a strong ref to it. jobject m_row_notifier; + + // Cache the method IDs + // RealmNotifier.onChange() + static realm::jni_util::JniMethod m_realm_notifier_on_change_method; // RowNotifier.getObservers() - jmethodID m_get_observers_method; + static realm::jni_util::JniMethod m_get_observers_method; // RowNotifier.getObservedRowPtrs(Observer[]) - jmethodID m_get_observed_row_ptrs_method; + static realm::jni_util::JniMethod m_get_observed_row_ptrs_method; // RowNotifier.clearRowRefs() - jmethodID m_clear_row_refs; - jmethodID m_observer_notify_listener; + static realm::jni_util::JniMethod m_clear_row_refs_method; + // RowNotifier.RowObserverPair.onChange() + static realm::jni_util::JniMethod m_row_observer_pair_on_change_method; public: virtual ~JavaBindingContext(); - virtual void changes_available(); virtual std::vector get_observed_rows(); virtual void did_change(std::vector const& observers, std::vector const& invalidated, bool version_changed=true); - explicit JavaBindingContext(const ConcreteJavaBindContext&); JavaBindingContext(const JavaBindingContext&) = delete; JavaBindingContext& operator=(const JavaBindingContext&) = delete; diff --git a/realm/realm-library/src/main/cpp/jni_util/method.hpp b/realm/realm-library/src/main/cpp/jni_util/method.hpp index e56e9ed948..c464ca4390 100644 --- a/realm/realm-library/src/main/cpp/jni_util/method.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/method.hpp @@ -25,15 +25,24 @@ namespace jni_util { class JniMethod { public: - JniMethod(JNIEnv *env, jobject obj, const char* method_name, const char* signature) { + JniMethod() : m_method_id(nullptr) {} + + JniMethod(JNIEnv *env, jclass cls, const char* method_name, const char* signature) + { + m_method_id = env->GetMethodID(cls, method_name, signature); + } + + JniMethod(JNIEnv *env, jobject obj, const char* method_name, const char* signature) + { jclass cls = env->GetObjectClass(obj); m_method_id = env->GetMethodID(cls, method_name, signature); env->DeleteLocalRef(cls); } - JniMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature) { + JniMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature) + { jclass cls = env->FindClass(class_name); - if (cls == NULL) { + if (cls == nullptr) { // TODO: Throw a cpp exception instead. ThrowException(env, ClassNotFound, class_name); m_method_id = nullptr; @@ -44,7 +53,12 @@ class JniMethod { ~JniMethod() { } - inline operator jmethodID&() const { return m_method_id; } + operator bool() const noexcept + { + return m_method_id != nullptr; + } + + inline operator const jmethodID&() const { return m_method_id; } private: jmethodID m_method_id; diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 72952a2f98..44d3237bfe 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -122,12 +122,13 @@ public boolean isInTransaction() { return sharedRealm.isInTransaction(); } - protected void addListener(RealmChangeListener listener) { + protected void addListener(RealmChangeListener listener) { if (listener == null) { throw new IllegalArgumentException("Listener should not be null"); } checkIfValid(); - sharedRealm.realmNotifier.addChangeListener(this, listener); + //noinspection unchecked + sharedRealm.realmNotifier.addChangeListener((T) this, listener); } /** @@ -138,12 +139,13 @@ protected void addListener(RealmChangeListener listener) { * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. * @see io.realm.RealmChangeListener */ - public void removeChangeListener(RealmChangeListener listener) { + public void removeChangeListener(RealmChangeListener listener) { if (listener == null) { throw new IllegalArgumentException("Listener should not be null"); } checkIfValid(); - sharedRealm.realmNotifier.removeChangeListener(this, listener); + //noinspection unchecked + sharedRealm.realmNotifier.removeChangeListener((T) this, listener); } /** diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 41bd384c0c..799bbeaf06 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -101,12 +101,12 @@ public void addChangeListener(RealmChangeListener listener) { } if (row instanceof UncheckedRow) { RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; - rowNotifier.registerListener((UncheckedRow) row, new RealmChangeListener>() { + rowNotifier.registerListener((UncheckedRow) row, this, new RealmChangeListener>() { @Override public void onChange(ProxyState proxyState) { proxyState.notifyChangeListeners$realm(); } - }, this); + }); } } @@ -137,11 +137,11 @@ public void onQueryFinished(Row row, boolean asyncQuery) { return; } RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; - rowNotifier.registerListener((UncheckedRow) row, new RealmChangeListener>() { + rowNotifier.registerListener((UncheckedRow) row, this, new RealmChangeListener>() { @Override public void onChange(ProxyState proxyState) { proxyState.notifyChangeListeners$realm(); } - }, this); + }); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java index 190f8fd639..708213900b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java @@ -2,13 +2,13 @@ import java.lang.ref.WeakReference; -public abstract class ObserverPair { - public final T listener; - public final WeakReference observerRef; +public abstract class ObserverPair { + public final WeakReference observerRef; + public final S listener; - public ObserverPair(T listener, Object objectRef) { + public ObserverPair(T observer, S listener) { this.listener = listener; - this.observerRef = new WeakReference(objectRef); + this.observerRef = new WeakReference(observer); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 689d8b9046..f6b9cfd770 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -16,6 +16,7 @@ package io.realm.internal; +import java.io.Closeable; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -26,16 +27,15 @@ * other thread/process changes the Realm file. */ @Keep -public class RealmNotifier { +public class RealmNotifier implements Closeable { - private static class RealmObserverPair extends ObserverPair { - - public RealmObserverPair(Object observer, RealmChangeListener listener) { - super(listener, observer); + private static class RealmObserverPair extends ObserverPair> { + public RealmObserverPair(T observer, RealmChangeListener listener) { + super(observer, listener); } private void onChange() { - Object observer = observerRef.get(); + T observer = observerRef.get(); if (observer != null) { listener.onChange(observer); } @@ -61,7 +61,7 @@ private void onChange() { * other thread. The changes on the same thread should not trigger this call. */ @SuppressWarnings("unused") // called from java_binding_context.cpp - void notifyCommitByOtherThread() { + void onChange() { for (RealmObserverPair observerPair : realmObserverPairs) { Object observer = observerPair.observerRef.get(); if (observer == null) { @@ -75,19 +75,20 @@ void notifyCommitByOtherThread() { /** * Called when close SharedRealm to clean up any event left in to queue. */ + @Override public void close() { removeAllChangeListeners(); } - public void addChangeListener(Object observer, RealmChangeListener realmChangeListener) { - RealmObserverPair observerPair = new RealmObserverPair(observer, realmChangeListener); + public void addChangeListener(T observer, RealmChangeListener realmChangeListener) { + RealmObserverPair observerPair = new RealmObserverPair(observer, realmChangeListener); if (!realmObserverPairs.contains(observerPair)) { realmObserverPairs.add(observerPair); } } - public void removeChangeListener(Object observer, RealmChangeListener realmChangeListener) { - RealmObserverPair observerPair = new RealmObserverPair(observer, realmChangeListener); + public void removeChangeListener(E observer, RealmChangeListener realmChangeListener) { + RealmObserverPair observerPair = new RealmObserverPair(observer, realmChangeListener); realmObserverPairs.remove(observerPair); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java index b0de8444e6..5e1db36f5e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java @@ -16,58 +16,110 @@ package io.realm.internal; -import java.util.HashMap; -import java.util.Map; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import io.realm.RealmChangeListener; +/** + * To bridge object store's row notification to java. {@link SharedRealm} is supposed to hold a instance of this class + * and pass it to JavaBindingContext. Row notifications callback will be executed when there are changes on a specific + * row. + */ @Keep public class RowNotifier { - @Keep - private static class Observer { - final RealmChangeListener listener; - final Object object; + private static class RowObserverPair extends ObserverPair> { + final WeakReference rowRef; + // Keep a strong ref to row when getRowRefs called and set it to null in clearRowRefs. + // This is to avoid the row gets GCed in between. UncheckedRow row; - Observer(RealmChangeListener listener, Object object) { - this.listener = listener; - this.object = object; - this.row = null; + public RowObserverPair(UncheckedRow row, T observer, RealmChangeListener listener) { + super(observer, listener); + this.rowRef = new WeakReference(row); } - // Called by JNI + // Called by JNI in JavaBindingContext::did_change(). @SuppressWarnings("unused") - public void notifyListener() { - listener.onChange(object); + private void onChange() { + T observer = observerRef.get(); + if (observer != null) { + listener.onChange(observer); + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof ObserverPair) { + RowObserverPair anotherPair = (RowObserverPair) obj; + return listener.equals(anotherPair.listener) && + observerRef.get() == anotherPair.observerRef.get() && + rowRef.get() == anotherPair.rowRef.get(); + } + return false; } } - // FIXME: Use weak ref for the key. And make the memory ownership clear in the doc. - Map rowObserverMap = new HashMap<>(); + // We don't take care of the duplicated rows here. The duplicated rows means the same Row object or different + // Row objects point to the same row in the same table. The duplicated rows will all get notifications but there are + // overheads when duplicated rows added since they all need to be processed to compute the differences for the row + // level fine grained notifications in the object store. + private CopyOnWriteArrayList rowObserverPairs = new CopyOnWriteArrayList(); - public void registerListener(UncheckedRow row, RealmChangeListener listener, Object object) { - Observer observer = new Observer(listener, object); - rowObserverMap.put(row, observer); + /** + * Register a listener on a row. + * + * @param row row to be observed. + * @param observer the observer which will be passed back in the {@link RealmChangeListener#onChange(Object)}. + * @param listener the listener. + * @param observer class. + */ + public void registerListener(UncheckedRow row, T observer, RealmChangeListener listener) { + RowObserverPair rowObserverPair = new RowObserverPair(row, observer, listener); + if (!rowObserverPairs.contains(rowObserverPair)) { + rowObserverPairs.add(rowObserverPair); + } } + + // The calling orders in JNI: + // 1. getObservers() to get the array of current ObserverPair. (called in BindingContext::get_observed_rows) + // 2. getObservedRowPtrs() with return value from step 1. To get an array of Row pointers. (called in + // BindingContext::get_observed_rows) + // 3. Every RowObserverPair.onChange() deliver the changes to java. (called in BindingContext::did_change()) + // 4. clearRowRefs() to reset the strong reference we hold in the ObserverPair. (called in + // BindingContext::did_change()) // Called by JNI @SuppressWarnings("unused") - private Observer[] getObservers() { - Observer[] observers = new Observer[rowObserverMap.size()]; - int i = 0; - for (Map.Entry entry : rowObserverMap.entrySet()) { - observers[i] = entry.getValue(); - observers[i].row = entry.getKey(); + private RowObserverPair[] getObservers() { + List pairList = new ArrayList(rowObserverPairs.size()); + for (RowObserverPair pair : rowObserverPairs) { + // FIXME: Anyone could tell me why wo we have to cast it here? + UncheckedRow uncheckedRow = (UncheckedRow) pair.rowRef.get(); + if (pair.observerRef.get() == null || uncheckedRow == null || !uncheckedRow.isAttached()) { + // The observer object or the row get GCed. Remove it. + rowObserverPairs.remove(pair); + } else { + // Keep a strong ref of the row! in case it gets GCed before clearRowRefs! + pair.row = uncheckedRow; + pairList.add(pair); + } } - return observers; + return pairList.toArray(new RowObserverPair[pairList.size()]); } // Called by JNI @SuppressWarnings("unused") - private long[] getObservedRowPtrs(Observer[] observers) { - long[] ptrs = new long[observers.length]; - for (int i = 0; i < observers.length; i++) { - ptrs[i] = observers[i].row.getNativePtr(); + private long[] getObservedRowPtrs(RowObserverPair[] observerPairs) { + long[] ptrs = new long[observerPairs.length]; + for (int i = 0; i < observerPairs.length; i++) { + ptrs[i] = observerPairs[i].row.getNativePtr(); } return ptrs; } @@ -75,8 +127,8 @@ private long[] getObservedRowPtrs(Observer[] observers) { // Called by JNI @SuppressWarnings("unused") private void clearRowRefs() { - for (Observer observer : rowObserverMap.values()) { - observer.row = null; + for (RowObserverPair observerPair: rowObserverPairs) { + observerPair.row = null; } } } From ab13921e7bd52311abd49fd11fff5bcc9bb7d487 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 21:45:56 +0800 Subject: [PATCH 0274/2110] Check java excpetion before notifier callback --- .../src/main/cpp/io_realm_internal_Collection.cpp | 3 +++ realm/realm-library/src/main/cpp/java_binding_context.cpp | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index c6c089c260..36bebfc0e4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -229,6 +229,9 @@ Java_io_realm_internal_Collection_nativeAddListener(JNIEnv* env, jobject instanc auto cb = [=](realm::CollectionChangeSet const& changes, std::exception_ptr err) { + // OS will call all notifiers' callback in one run, so check the Java excpetion first!! + if (env->ExceptionCheck()) return; + jclass results_class = env->GetObjectClass(weak_results); jmethodID notify_method = env->GetMethodID(results_class, "notifyChangeListeners", "()V"); env->CallVoidMethod(weak_results, notify_method); diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index b113701647..bbda5f39b0 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -113,16 +113,23 @@ void JavaBindingContext::did_change(std::vector c bool /*version_changed*/) { for (auto state : observer_state_list) { + if (m_local_jni_env->ExceptionCheck()) return; + jobject observer = reinterpret_cast(state.info); //if (!state.changes.empty()) { m_local_jni_env->CallVoidMethod(observer, m_row_observer_pair_on_change_method); //} } for (auto deleted_row_observer : invalidated) { + if (m_local_jni_env->ExceptionCheck()) return; + jobject observer = reinterpret_cast(deleted_row_observer); m_local_jni_env->CallVoidMethod(observer, m_row_observer_pair_on_change_method); } + m_local_jni_env->CallVoidMethod(m_row_notifier, m_clear_row_refs_method); + + if (m_local_jni_env->ExceptionCheck()) return; jobject notifier = m_local_jni_env->NewLocalRef(m_realm_notifier); if (notifier) { m_local_jni_env->CallVoidMethod(m_realm_notifier, m_realm_notifier_on_change_method); From 73301adb6c53ff125b56e3a5a99c30fb6cf42e67 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 21:47:37 +0800 Subject: [PATCH 0275/2110] Fix get DynamicRealmObject from RealmResults. --- .../src/main/java/io/realm/BaseRealm.java | 18 ++++++++++++++---- .../src/main/java/io/realm/RealmResults.java | 10 +++++----- .../java/io/realm/internal/CheckedRow.java | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 44d3237bfe..9f3734114c 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -28,6 +28,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.CheckedRow; import io.realm.internal.InvalidRow; import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; @@ -468,11 +469,19 @@ public RealmSchema getSchema() { return schema; } - // FIXME: Testing code - E get(Class clazz, Row row) { + // Used by RealmList/RealmResults, to create RealmObject from a Collection. + // Invariant: if dynamicClassName != null -> clazz == DynamicRealmObject + E get(Class clazz, String dynamicClassName, UncheckedRow row) { + final boolean isDynamicRealmObject = dynamicClassName != null; - E result = configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz), - false, Collections. emptyList()); + E result; + if (isDynamicRealmObject) { + //noinspection unchecked + result = (E) new DynamicRealmObject(this, CheckedRow.getFromRow(row)); + } else { + result = configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz), + false, Collections. emptyList()); + } RealmObjectProxy proxy = (RealmObjectProxy) result; proxy.realmGet$proxyState().setTableVersion$realm(); return result; @@ -490,6 +499,7 @@ E get(Class clazz, long rowIndex, boolean acceptDefaul // Used by RealmList/RealmResults // Invariant: if dynamicClassName != null -> clazz == DynamicRealmObject + // TODO: Remove this after RealmList is backed by OS Results. E get(Class clazz, String dynamicClassName, long rowIndex) { final boolean isDynamicRealmObject = dynamicClassName != null; final Table table = isDynamicRealmObject ? schema.getTable(dynamicClassName) : schema.getTable(clazz); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 9feb82b94d..ab66b03365 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -150,7 +150,7 @@ public boolean contains(Object object) { @Override public E get(int location) { realm.checkIfValid(); - return realm.get(classSpec, collection.getUncheckedRow(location)); + return realm.get(classSpec, className, collection.getUncheckedRow(location)); } /** @@ -170,10 +170,10 @@ public E first(E defaultValue) { } private E firstImpl(boolean shouldThrow, E defaultValue) { - Row row = collection.firstUncheckedRow(); + UncheckedRow row = collection.firstUncheckedRow(); if (row != null) { - return realm.get(classSpec, row); + return realm.get(classSpec, className, row); } else { if (shouldThrow) { throw new IndexOutOfBoundsException("No results were found."); @@ -201,10 +201,10 @@ public E last(E defaultValue) { } private E lastImpl(boolean shouldThrow, E defaultValue) { - Row row = collection.lastUncheckedRow(); + UncheckedRow row = collection.lastUncheckedRow(); if (row != null) { - return realm.get(classSpec, row); + return realm.get(classSpec, className, row); } else { if (shouldThrow) { throw new IndexOutOfBoundsException("No results were found."); diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index 435075df5f..d67ba7275e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -72,7 +72,7 @@ public static CheckedRow get(Context context, LinkView linkView, long index) { * * @return an checked instance of {@link Row}. */ - static CheckedRow getFromRow(UncheckedRow row) { + public static CheckedRow getFromRow(UncheckedRow row) { return new CheckedRow(row); } From 234510f94ca7a437e2dce4943fcd98b87fc9b67d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 6 Dec 2016 21:52:30 +0800 Subject: [PATCH 0276/2110] Remove accessingDynamicRealmObjectBeforeAsyncQueryCompleted Replaced by DynamicRealmRealmTests.findFirst() --- .../java/io/realm/DynamicRealmTests.java | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index bad48a2574..cdd8648266 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -536,49 +536,6 @@ public void onChange(RealmResults object) { looperThread.keepStrongReference.add(realmResults2); } - @Test - @RunTestInLooperThread - public void accessingDynamicRealmObjectBeforeAsyncQueryCompleted() { - /* - final DynamicRealm dynamicRealm = initializeDynamicRealm(); - final DynamicRealmObject[] dynamicRealmObject = new DynamicRealmObject[1]; - - // Intercept completion of the async DynamicRealmObject query - Handler handler = new HandlerProxy(dynamicRealm.handlerController) { - @Override - public boolean onInterceptInMessage(int what) { - switch (what) { - case HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT: { - post(new Runnable() { - @Override - public void run() { - assertFalse(dynamicRealmObject[0].isLoaded()); - assertFalse(dynamicRealmObject[0].isValid()); - try { - dynamicRealmObject[0].getObject(AllTypes.FIELD_BINARY); - fail("trying to access a DynamicRealmObject property should throw"); - } catch (IllegalStateException ignored) { - - } finally { - dynamicRealm.close(); - looperThread.testComplete(); - } - } - }); - return true; - } - } - return false; - } - }; - - //dynamicRealm.setHandler(handler); - dynamicRealmObject[0] = dynamicRealm.where(AllTypes.CLASS_NAME) - .between(AllTypes.FIELD_LONG, 4, 9) - .findFirstAsync(); - */ - } - @Test public void deleteAll() { realm.beginTransaction(); From 9b8828ceae332c920a9f84674e8fe69a909bdfce Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 7 Dec 2016 15:14:39 +0800 Subject: [PATCH 0277/2110] Memory ownership for Collection and OS Results --- .../main/cpp/io_realm_internal_Collection.cpp | 157 ++++++++++-------- .../java/io/realm/internal/Collection.java | 57 ++----- 2 files changed, 102 insertions(+), 112 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 36bebfc0e4..b50c861e92 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -21,28 +21,45 @@ #include #include -#include #include "util.hpp" +#include "jni_util/method.hpp" using namespace realm; +using namespace realm::jni_util; + +// We need to control the life cycle of Results, weak ref of Java Collection object and the NotificationToken. +// Wrap all three together, so when the Java Collection object gets GCed, all three of them will be invalidated. +struct ResultsWrapper { + jobject m_collection_weak_ref; + Results m_results; + NotificationToken m_notification_token; + + ResultsWrapper(Results&& results) + : m_collection_weak_ref(nullptr), m_results(results), m_notification_token() {} + + ResultsWrapper(ResultsWrapper&&) = delete; + ResultsWrapper& operator=(ResultsWrapper&&) = delete; + + ResultsWrapper(ResultsWrapper const&) = delete; + ResultsWrapper& operator=(ResultsWrapper const&) = delete; + + ~ResultsWrapper() + { + if (m_collection_weak_ref) { + JNIEnv *env; + g_vm->AttachCurrentThread(&env, nullptr); + env->DeleteWeakGlobalRef(m_collection_weak_ref); + } + } +}; static void finalize_results(jlong ptr); -static void finalize_notification_token(jlong ptr); static void finalize_results(jlong ptr) { TR_ENTER_PTR(ptr); - delete reinterpret_cast(ptr); -} - -static void finalize_notification_token(jlong ptr) -{ - TR_ENTER_PTR(ptr); - // NotificationToken can be closed by NotificationToken.close(). Then ptr will be reset in that case. - if (ptr) { - delete reinterpret_cast(ptr); - } + delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL @@ -60,11 +77,12 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); auto sort_desc_ptr = reinterpret_cast(sort_desc_native_ptr); auto distinct_desc_ptr = reinterpret_cast(distinct_desc_native_ptr); - auto results = new Results(shared_realm, *query, - sort_desc_ptr ? *sort_desc_ptr : SortDescriptor(), - distinct_desc_ptr ? *distinct_desc_ptr : SortDescriptor()); + Results results(shared_realm, *query, + sort_desc_ptr ? *sort_desc_ptr : SortDescriptor(), + distinct_desc_ptr ? *distinct_desc_ptr : SortDescriptor()); + auto wrapper = new ResultsWrapper(std::move(results)); - return reinterpret_cast(results); + return reinterpret_cast(wrapper); } CATCH_STD() return reinterpret_cast(nullptr); } @@ -74,8 +92,8 @@ Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlon { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); - auto snapshot = results->snapshot(); + auto wrapper = reinterpret_cast(native_ptr); + auto snapshot = wrapper->m_results.snapshot(); return reinterpret_cast(new Results(snapshot)); } CATCH_STD() return reinterpret_cast(nullptr); @@ -86,22 +104,21 @@ Java_io_realm_internal_Collection_nativeContains(JNIEnv *env, jclass, jlong nati { TR_ENTER_PTR(native_ptr); try { - auto results = reinterpret_cast(native_ptr); + auto wrapper = reinterpret_cast(native_ptr); auto row = reinterpret_cast(native_row_ptr); - size_t index = results->index_of(*row); + size_t index = wrapper->m_results.index_of(*row); return to_jbool(index != not_found); } CATCH_STD(); return JNI_FALSE; } -// FIXME: we don't use it at the moment JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeGetRow(JNIEnv *env, jclass, jlong native_ptr, jint index) { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); - auto row = results->get(static_cast(index)); + auto wrapper = reinterpret_cast(native_ptr); + auto row = wrapper->m_results.get(static_cast(index)); return reinterpret_cast(new Row(std::move(row))); } CATCH_STD() return reinterpret_cast(nullptr); @@ -112,8 +129,8 @@ Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv *env, jclass, jlong nati { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); - auto optional_row = results->first(); + auto wrapper = reinterpret_cast(native_ptr); + auto optional_row = wrapper->m_results.first(); if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } @@ -127,8 +144,8 @@ Java_io_realm_internal_Collection_nativeLastRow(JNIEnv *env, jclass, jlong nativ { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); - auto optional_row = results->last(); + auto wrapper = reinterpret_cast(native_ptr); + auto optional_row = wrapper->m_results.last(); if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } @@ -141,8 +158,8 @@ Java_io_realm_internal_Collection_nativeClear(JNIEnv *env, jclass, jlong native_ { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); - results->clear(); + auto wrapper = reinterpret_cast(native_ptr); + wrapper->m_results.clear(); } CATCH_STD() } @@ -151,8 +168,8 @@ Java_io_realm_internal_Collection_nativeSize(JNIEnv *env, jclass, jlong native_p { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); - return static_cast(results->size()); + auto wrapper = reinterpret_cast(native_ptr); + return static_cast(wrapper->m_results.size()); } CATCH_STD() return 0; } @@ -163,23 +180,25 @@ Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong nat { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); + auto wrapper = reinterpret_cast(native_ptr); size_t index = S(column_index); Optional value; switch (agg_func) { case io_realm_internal_Collection_AGGREGATE_FUNCTION_MINIMUM: - value = results->min(index); + value = wrapper->m_results.min(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_MAXIMUM: - value = results->max(index); + value = wrapper->m_results.max(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_AVERAGE: - value = results->average(index); + value = wrapper->m_results.average(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_SUM: - value = results->sum(index); + value = wrapper->m_results.sum(index); break; + default: + REALM_UNREACHABLE(); } if (!value) { @@ -208,61 +227,55 @@ Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_p { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); + auto wrapper = reinterpret_cast(native_ptr); auto sort_descriptor = *reinterpret_cast(sort_desc_native_ptr); - auto sorted_result = results->sort(std::move(sort_descriptor)); - return reinterpret_cast(new Results(std::move(sorted_result))); + auto sorted_result = wrapper->m_results.sort(std::move(sort_descriptor)); + return reinterpret_cast(new ResultsWrapper(std::move(sorted_result))); } CATCH_STD() return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeAddListener(JNIEnv* env, jobject instance, jlong native_ptr) +JNIEXPORT void JNICALL +Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject instance, jlong native_ptr) { TR_ENTER_PTR(native_ptr) - try { - auto results = reinterpret_cast(native_ptr); + static JniMethod notify_change_listeners(env, instance, "notifyChangeListeners", "()V"); - // FIXME: Those need to be freed for all the corner cases! - jobject weak_results = env->NewWeakGlobalRef(instance); + try { + auto wrapper = reinterpret_cast(native_ptr); + if (wrapper->m_collection_weak_ref == nullptr) { + wrapper->m_collection_weak_ref = env->NewWeakGlobalRef(instance); + } auto cb = [=](realm::CollectionChangeSet const& changes, std::exception_ptr err) { - // OS will call all notifiers' callback in one run, so check the Java excpetion first!! + // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; - jclass results_class = env->GetObjectClass(weak_results); - jmethodID notify_method = env->GetMethodID(results_class, "notifyChangeListeners", "()V"); - env->CallVoidMethod(weak_results, notify_method); + env->CallVoidMethod(wrapper->m_collection_weak_ref, notify_change_listeners); }; - NotificationToken token = results->add_notification_callback(cb); - return reinterpret_cast(new NotificationToken(std::move(token))); + wrapper->m_notification_token = wrapper->m_results.add_notification_callback(cb); } CATCH_STD() - - return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeGetFinalizerPtr(JNIEnv *, jclass) +JNIEXPORT void JNICALL +Java_io_realm_internal_Collection_nativeStopListening(JNIEnv *env, jobject, jlong native_ptr) { - TR_ENTER() - return reinterpret_cast(&finalize_results); + TR_ENTER_PTR(native_ptr) + + try { + auto wrapper = reinterpret_cast(native_ptr); + wrapper->m_notification_token = {}; + } CATCH_STD() } JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeNotificationTokenGetFinalizerPtr(JNIEnv *, jclass) +Java_io_realm_internal_Collection_nativeGetFinalizerPtr(JNIEnv *, jclass) { TR_ENTER() - return reinterpret_cast(&finalize_notification_token); -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_Collection_nativeNotificationTokenClose(JNIEnv *, jclass, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - delete reinterpret_cast(native_ptr); + return reinterpret_cast(&finalize_results); } JNIEXPORT jlong JNICALL @@ -270,9 +283,9 @@ Java_io_realm_internal_Collection_nativeWhere(JNIEnv *env, jclass, jlong native_ { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); + auto wrapper = reinterpret_cast(native_ptr); - Query *query = new Query(results->get_query()); + Query *query = new Query(wrapper->m_results.get_query()); return reinterpret_cast(query); } CATCH_STD() return 0; @@ -283,10 +296,10 @@ Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv *env, jclass, jlong nativ { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); + auto wrapper = reinterpret_cast(native_ptr); auto row = reinterpret_cast(row_native_ptr); - return static_cast(results->index_of(*row)); + return static_cast(wrapper->m_results.index_of(*row)); } CATCH_STD() return npos; } @@ -297,10 +310,10 @@ Java_io_realm_internal_Collection_nativeIndexOfBySourceRowIndex(JNIEnv *env, jcl { TR_ENTER_PTR(native_ptr) try { - auto results = reinterpret_cast(native_ptr); + auto wrapper = reinterpret_cast(native_ptr); auto index = static_cast(source_row_index); - return static_cast(results->index_of(index)); + return static_cast(wrapper->m_results.index_of(index)); } CATCH_STD() return npos; diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index af4df781ea..48b76f28f4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -22,7 +22,12 @@ import io.realm.RealmChangeListener; -public class Collection implements NativeObject { +/** + * Java wrapper of OS Results class. + * It is supposed to be the backend of binding's query results, link list and back links. + */ +@KeepMember +public final class Collection implements NativeObject { public static class Listener { private final RealmChangeListener realmChangeListener; @@ -48,38 +53,12 @@ public boolean equals(Object obj) { } } - private static class NotificationToken implements NativeObject { - private long nativePtr; - private static final long nativeFinalizerPtr = nativeNotificationTokenGetFinalizerPtr(); - - NotificationToken(long nativePtr) { - this.nativePtr = nativePtr; - Context.sharedContext.addReference(this); - } - - @Override - public long getNativePtr() { - return nativePtr; - } - - @Override - public long getNativeFinalizerPtr() { - return nativeFinalizerPtr; - } - - public void close() { - nativeNotificationTokenClose(nativePtr); - nativePtr = 0; - } - } - private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final SharedRealm sharedRealm; private final Context context; private final TableQuery query; private final List listeners = new CopyOnWriteArrayList(); - private NotificationToken notificationToken = null; // Public for static checking in JNI @SuppressWarnings("WeakerAccess") @@ -201,31 +180,28 @@ public int indexOf(long sourceRowIndex) { } public void addListener(Listener listener) { + if (listeners.isEmpty()) { + nativeStartListening(nativePtr); + } if (!listeners.contains(listener)) { listeners.add(listener); } - if (notificationToken == null) { - notificationToken = new NotificationToken(nativeAddListener(nativePtr)); - } } public void removeListener(Listener listener) { listeners.remove(listener); - if (listeners.isEmpty() && notificationToken != null) { - notificationToken.close(); - notificationToken = null; + if (listeners.isEmpty()) { + nativeStopListening(nativePtr); } } public void removeAllListeners() { listeners.clear(); - if (notificationToken != null) { - notificationToken.close(); - notificationToken = null; - } + nativeStopListening(nativePtr); } // Called by JNI + @KeepMember @SuppressWarnings("unused") private void notifyChangeListeners() { if (!listeners.isEmpty()) { @@ -244,6 +220,7 @@ private void notifyChangeListeners() { private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long sortDescNativePtr, long distinctDescNativePtr); + @SuppressWarnings("unused") // Not used for now private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); private static native long nativeFirstRow(long nativePtr); @@ -253,9 +230,9 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native long nativeSize(long nativePtr); private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); private static native long nativeSort(long nativePtr, long sortDescNativePtr); - private native long nativeAddListener(long nativePtr); - private static native long nativeNotificationTokenGetFinalizerPtr(); - private static native void nativeNotificationTokenClose(long nativePtr); + // Non-static, we need this Collection object in JNI. + private native void nativeStartListening(long nativePtr); + private native void nativeStopListening(long nativePtr); private static native long nativeWhere(long nativePtr); private static native long nativeIndexOf(long nativePtr, long rowNativePtr); private static native long nativeIndexOfBySourceRowIndex(long nativePtr, long sourceRowIndex); From 99f7c11853ece5d3cf0334b156c6b6ef965e6676 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 7 Dec 2016 17:10:28 +0800 Subject: [PATCH 0278/2110] Check query before create Results --- .../java/io/realm/internal/CollectionTests.java | 12 ++++++++++++ .../src/main/cpp/io_realm_internal_Collection.cpp | 7 +++---- .../src/main/cpp/io_realm_internal_TableQuery.cpp | 7 ------- realm/realm-library/src/main/cpp/util.hpp | 7 +++++++ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index ec44babfe8..c0e26f0dae 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -23,6 +23,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import io.realm.RealmConfiguration; @@ -37,6 +38,8 @@ public class CollectionTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); private SharedRealm sharedRealm; private Table table; @@ -90,6 +93,15 @@ public void constructor_queryIsValidated() { new Collection(sharedRealm, table.where().or()); } + @Test + public void constructor_queryOnDeletedTable() { + TableQuery query = table.where(); + sharedRealm.removeTable(table.getName()); + // Query should be checked before creating OS Results. + thrown.expect(IllegalStateException.class); + new Collection(sharedRealm, query); + } + @Test public void size() { Collection collection = new Collection(sharedRealm, table.where()); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index b50c861e92..ff1176a487 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -69,10 +69,9 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong TR_ENTER() try { auto query = reinterpret_cast(query_ptr); - /* FIXME: Add check here - if (!query_va(env, query) || !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) - return nullptr; - */ + if (!QUERY_VALID(env, query)) { + return reinterpret_cast(nullptr); + } auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); auto sort_desc_ptr = reinterpret_cast(sort_desc_native_ptr); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 6ad3403f6c..063e571a0d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -29,19 +29,12 @@ using namespace realm; #if 1 #define QUERY_COL_TYPE_VALID(env, jPtr, col, type) query_col_type_valid(env, jPtr, col, type) -#define QUERY_VALID(env, pQuery) query_valid(env, pQuery) #else #define QUERY_COL_TYPE_VALID(env, jPtr, col, type) (true) -#define QUERY_VALID(env, pQuery) (true) #endif static void finalize_table_query(jlong ptr); -inline bool query_valid(JNIEnv* env, Query* pQuery) -{ - return TABLE_VALID(env, pQuery->get_table().get()); -} - inline bool query_col_type_valid(JNIEnv* env, jlong nativeQueryPtr, jlong colIndex, DataType type) { return TBL_AND_COL_INDEX_AND_TYPE_VALID(env, Q(nativeQueryPtr)->get_table().get(), colIndex, type); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 597124043b..cafffa1b1a 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -120,6 +120,7 @@ jclass GetClass(JNIEnv* env, const char* classStr); #define TABLE_VALID(env,ptr) TableIsValid(env, ptr) #define ROW_VALID(env,ptr) RowIsValid(env, ptr) +#define QUERY_VALID(env, ptr) QueryIsValid(env, ptr) #if CHECK_PARAMETERS @@ -203,6 +204,12 @@ inline bool RowIsValid(JNIEnv* env, realm::Row* rowPtr) return valid; } +inline bool QueryIsValid(JNIEnv* env, realm::Query* query) +{ + return TableIsValid(env, query->get_table().get()); +} + + // Requires an attached Table template bool RowIndexesValid(JNIEnv* env, T* pTable, jlong startIndex, jlong endIndex, jlong range) From 731d9d8116c919927775173810cb506a46c3808d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 7 Dec 2016 17:53:51 +0800 Subject: [PATCH 0279/2110] Use ObserverPair to manage collection listeners --- .../src/main/java/io/realm/RealmResults.java | 4 +- .../java/io/realm/internal/Collection.java | 68 ++++++++----------- .../java/io/realm/internal/PendingRow.java | 11 +-- 3 files changed, 37 insertions(+), 46 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index ab66b03365..77eae575a6 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -779,7 +779,7 @@ public void addChangeListener(RealmChangeListener> listener) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); - collection.addListener(new Collection.Listener(listener, this)); + collection.addListener(this, listener); } /** @@ -794,7 +794,7 @@ public void removeChangeListener(RealmChangeListener listener) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); - collection.removeListener(new Collection.Listener(listener, this)); + collection.removeListener(this, listener); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 48b76f28f4..70e4b5de44 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -16,7 +16,6 @@ package io.realm.internal; -import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -29,27 +28,17 @@ @KeepMember public final class Collection implements NativeObject { - public static class Listener { - private final RealmChangeListener realmChangeListener; - private final WeakReference objectRef; - - public Listener(RealmChangeListener realmChangeListener, Object objectRef) { - this.realmChangeListener = realmChangeListener; - this.objectRef = new WeakReference(objectRef); + private static class CollectionObserverPair extends ObserverPair>{ + public CollectionObserverPair(T observer, RealmChangeListener listener) { + super(observer, listener); } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } + public void onChange() { + T observer = observerRef.get(); + if (observer != null) { - if (obj instanceof Listener) { - Listener anotherListener = (Listener) obj; - return realmChangeListener.equals(anotherListener.realmChangeListener) && - objectRef.equals(anotherListener.objectRef); + listener.onChange(observerRef.get()); } - return false; } } @@ -58,7 +47,7 @@ public boolean equals(Object obj) { private final SharedRealm sharedRealm; private final Context context; private final TableQuery query; - private final List listeners = new CopyOnWriteArrayList(); + private final List observerPairs = new CopyOnWriteArrayList(); // Public for static checking in JNI @SuppressWarnings("WeakerAccess") @@ -90,13 +79,14 @@ public byte getValue() { public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { query.validateQuery(); - this.sharedRealm = sharedRealm; - this.context = sharedRealm.context; - this.query = query; this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), sortDescriptor == null ? 0 : sortDescriptor.getNativePtr(), distinctDescriptor == null ? 0 : distinctDescriptor.getNativePtr()); + + this.sharedRealm = sharedRealm; + this.context = sharedRealm.context; + this.query = query; this.context.addReference(this); } @@ -110,6 +100,7 @@ public Collection(SharedRealm sharedRealm, TableQuery query) { } private Collection(SharedRealm sharedRealm, TableQuery query, long nativePtr) { + query.validateQuery(); this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.query = query; @@ -179,24 +170,26 @@ public int indexOf(long sourceRowIndex) { return (index > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) index; } - public void addListener(Listener listener) { - if (listeners.isEmpty()) { + public void addListener(T observer, RealmChangeListener listener) { + if (observerPairs.isEmpty()) { nativeStartListening(nativePtr); } - if (!listeners.contains(listener)) { - listeners.add(listener); + CollectionObserverPair collectionObserverPair = new CollectionObserverPair(observer, listener); + if (!observerPairs.contains(collectionObserverPair)) { + observerPairs.add(collectionObserverPair); } } - public void removeListener(Listener listener) { - listeners.remove(listener); - if (listeners.isEmpty()) { + public void removeListener(T observer, RealmChangeListener listener) { + CollectionObserverPair collectionObserverPair = new CollectionObserverPair(observer, listener); + observerPairs.remove(collectionObserverPair); + if (observerPairs.isEmpty()) { nativeStopListening(nativePtr); } } public void removeAllListeners() { - listeners.clear(); + observerPairs.clear(); nativeStopListening(nativePtr); } @@ -204,15 +197,12 @@ public void removeAllListeners() { @KeepMember @SuppressWarnings("unused") private void notifyChangeListeners() { - if (!listeners.isEmpty()) { - for (Listener listener : listeners) { - Object obj = listener.objectRef.get(); - if (obj == null) { - listeners.remove(listener); - continue; - } - //noinspection unchecked - listener.realmChangeListener.onChange(obj); + for (CollectionObserverPair pair: observerPairs) { + Object object = pair.observerRef.get(); + if (object != null) { + pair.onChange(); + } else { + observerPairs.remove(pair); } } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index c54a0661c0..172215f52d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -30,14 +30,15 @@ public interface FrontEnd { "The query has been executed. This 'PendingRow' is not valid anymore."; private Collection pendingCollection; - private Collection.Listener listener; + private RealmChangeListener listener; private WeakReference frontEnd; private boolean returnCheckedRow; public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, final boolean returnCheckedRow) { pendingCollection = new Collection(sharedRealm, query, sortDescriptor); - listener = new Collection.Listener(new RealmChangeListener() { + + listener = new RealmChangeListener() { @Override public void onChange(PendingRow pendingRow) { if (frontEnd == null) { @@ -58,8 +59,8 @@ public void onChange(PendingRow pendingRow) { clearPendingCollection(); } } - }, this); - pendingCollection.addListener(listener); + }; + pendingCollection.addListener(this, listener); this.returnCheckedRow = returnCheckedRow; } @@ -214,7 +215,7 @@ public boolean hasColumn(String fieldName) { } private void clearPendingCollection() { - pendingCollection.removeListener(listener); + pendingCollection.removeListener(this, listener); pendingCollection = null; listener = null; } From d3e849529206ad9abe01737b0d4b7bbb58071360 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 7 Dec 2016 20:35:34 +0900 Subject: [PATCH 0280/2110] fix typo (#3877) --- .../java/io/realm/permissions/PermissionChange.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java index d7aa1919d4..e8d402d259 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java @@ -89,7 +89,7 @@ public Date getUpdatedAt() { /** * Returns the status code for this change. * - * @return {@code null} if not yet processed. {@code 0} if successfull, {@code >0} if an error happened. See {@link #getStatusMessage()}. + * @return {@code null} if not yet processed. {@code 0} if successful, {@code >0} if an error happened. See {@link #getStatusMessage()}. */ public Integer getStatusCode() { return statusCode; From 687a90f25b4f94d2207e5a2ee7627e312f38ff71 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Dec 2016 01:01:20 +0800 Subject: [PATCH 0281/2110] Add test case for Collection notification behavior --- .../io/realm/internal/CollectionTests.java | 214 +++++++++++++++++- .../main/cpp/io_realm_internal_Collection.cpp | 2 + 2 files changed, 209 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index c0e26f0dae..815aabcc57 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -26,8 +26,14 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; + +import io.realm.RealmChangeListener; import io.realm.RealmConfiguration; import io.realm.RealmFieldType; +import io.realm.TestHelper; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; import static junit.framework.Assert.assertEquals; @@ -40,26 +46,28 @@ public class CollectionTests { public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule public final ExpectedException thrown = ExpectedException.none(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + RealmConfiguration config; private SharedRealm sharedRealm; private Table table; @Before public void setUp() { - RealmConfiguration config = configFactory.createConfiguration(); + config = configFactory.createConfiguration(); sharedRealm = SharedRealm.getInstance(config); - sharedRealm.beginTransaction(); - table = sharedRealm.getTable("test_table"); - populateData(table); + populateData(); } @After public void tearDown() { - sharedRealm.cancelTransaction(); sharedRealm.close(); } - private void populateData(Table table) { + private void populateData() { + sharedRealm.beginTransaction(); + table = sharedRealm.getTable("test_table"); // Specify the column types and names table.addColumn(RealmFieldType.STRING, "firstName"); table.addColumn(RealmFieldType.STRING, "lastName"); @@ -85,6 +93,35 @@ private void populateData(Table table) { table.setString(0, row, "Henry", false); table.setString(1, row, "Anderson", false); table.setLong(2, row, 1, false); + sharedRealm.commitTransaction(); + } + + private void addRowAsync() { + final CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + SharedRealm sharedRealm = SharedRealm.getInstance(config); + addRow(sharedRealm); + sharedRealm.close(); + latch.countDown(); + } + }).start(); + TestHelper.awaitOrFail(latch); + } + + private void addRow(SharedRealm sharedRealm) { + sharedRealm.beginTransaction(); + table = sharedRealm.getTable("test_table"); + table.addEmptyRow(); + sharedRealm.commitTransaction(); + } + + private void removeRow(SharedRealm sharedRealm) { + sharedRealm.beginTransaction(); + table = sharedRealm.getTable("test_table"); + table.remove(0); + sharedRealm.commitTransaction(); } @Test(expected = UnsupportedOperationException.class) @@ -96,7 +133,9 @@ public void constructor_queryIsValidated() { @Test public void constructor_queryOnDeletedTable() { TableQuery query = table.where(); + sharedRealm.beginTransaction(); sharedRealm.removeTable(table.getName()); + sharedRealm.commitTransaction(); // Query should be checked before creating OS Results. thrown.expect(IllegalStateException.class); new Collection(sharedRealm, query); @@ -105,7 +144,7 @@ public void constructor_queryOnDeletedTable() { @Test public void size() { Collection collection = new Collection(sharedRealm, table.where()); - assertEquals(3, collection.size()); + assertEquals(4, collection.size()); } @Test @@ -146,7 +185,9 @@ public void sort() { public void clear() { assertEquals(table.size(), 4); Collection collection = new Collection(sharedRealm, table.where()); + sharedRealm.beginTransaction(); collection.clear(); + sharedRealm.commitTransaction(); assertEquals(table.size(), 0); } @@ -190,4 +231,163 @@ public void distinct() { assertEquals(collection.getUncheckedRow(1).getString(0), "Erik"); assertEquals(collection.getUncheckedRow(2).getString(0), "Henry"); } + + @Test + public void addListener_shouldBeCalledWhenRefreshAfterLocalCommit() { + final CountDownLatch latch = new CountDownLatch(1); + Collection collection = new Collection(sharedRealm, table.where()); + collection.size(); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection element) { + assertEquals(latch.getCount(), 1); + latch.countDown(); + } + }); + sharedRealm.beginTransaction(); + table.addEmptyRow(); + sharedRealm.commitTransaction(); + sharedRealm.refresh(); + TestHelper.awaitOrFail(latch); + } + + @Test + public void addListener_shouldBeCalledByWaitForChangeThenRefresh() { + final CountDownLatch latch = new CountDownLatch(1); + Collection collection = new Collection(sharedRealm, table.where()); + collection.size(); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection element) { + assertEquals(latch.getCount(), 1); + latch.countDown(); + } + }); + + addRowAsync(); + + sharedRealm.waitForChange(); + sharedRealm.refresh(); + TestHelper.awaitOrFail(latch); + } + + @Test + @RunTestInLooperThread + public void addListener_queryNotReturned() { + final SharedRealm sharedRealm = SharedRealm.getInstance(config); + Table table = sharedRealm.getTable("test_table"); + + final Collection collection = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collection); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection collection1) { + assertEquals(collection1, collection); + assertEquals(collection1.size(), 5); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + + addRowAsync(); + } + + @Test + @RunTestInLooperThread + public void addListener_queryReturned() { + final SharedRealm sharedRealm = SharedRealm.getInstance(config); + Table table = sharedRealm.getTable("test_table"); + + final Collection collection = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collection); + assertEquals(collection.size(), 4); // Trigger the query to run. + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection collection1) { + assertEquals(collection1, collection); + assertEquals(collection1.size(), 5); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + + addRowAsync(); + } + + // The query has not been executed. + // Local commit won't trigger the listener immediately. Instead, the notification comes after the background commit. + @Test + @RunTestInLooperThread + public void addListener_queryNotReturnedLocalAndRemoteCommit() { + final SharedRealm sharedRealm = SharedRealm.getInstance(config); + Table table = sharedRealm.getTable("test_table"); + + final Collection collection = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collection); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection collection1) { + assertEquals(collection1, collection); + assertEquals(collection1.size(), 6); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + addRow(sharedRealm); + addRowAsync(); + } + + // The query has not been executed. + // Local commit will trigger the listener in following event loops. + @Test + @RunTestInLooperThread + public void addListener_queryNotReturnedLocalCommitOnly() { + final SharedRealm sharedRealm = SharedRealm.getInstance(config); + Table table = sharedRealm.getTable("test_table"); + + final Collection collection = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collection); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection collection1) { + assertEquals(collection1, collection); + assertEquals(collection1.size(), 5); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + addRow(sharedRealm); + } + + // The query has been executed. + // Local commit will trigger the listener in following event loops. + @Test + @RunTestInLooperThread + public void addListener_queryReturnedLocalCommitOnly() { + final SharedRealm sharedRealm = SharedRealm.getInstance(config); + Table table = sharedRealm.getTable("test_table"); + + final Collection collection = new Collection(sharedRealm, table.where()); + assertEquals(collection.size(), 4); // Trigger the query to run. + looperThread.keepStrongReference.add(collection); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection collection1) { + assertEquals(collection1, collection); + assertEquals(collection1.size(), 5); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + addRow(sharedRealm); + } + + @Test + public void size_doesNotChangeAfterLocalCommit() { + final Collection collection = new Collection(sharedRealm, table.where()); + assertEquals(collection.size(), 4); + addRow(sharedRealm); + assertEquals(collection.size(), 4); + sharedRealm.refresh(); + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index ff1176a487..0c82e4bfc8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -251,6 +251,8 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst std::exception_ptr err) { // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; + // No changes. + if (changes.empty()) return; env->CallVoidMethod(wrapper->m_collection_weak_ref, notify_change_listeners); }; From fde9917fcfe7db02573d9ec1678a07164e0324ff Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Dec 2016 03:07:35 +0800 Subject: [PATCH 0282/2110] Hack with snapshot to support stable iterator But something is going wrong, more like a bug in core than Object Store. --- .../io/realm/internal/CollectionTests.java | 45 ++++++++-- .../main/cpp/io_realm_internal_Collection.cpp | 87 +++++++++++++++---- .../src/main/java/io/realm/RealmObject.java | 3 +- .../java/io/realm/internal/Collection.java | 12 +++ .../java/io/realm/internal/RealmNotifier.java | 4 +- .../java/io/realm/internal/SharedRealm.java | 45 +++++++++- .../android/AndroidRealmNotifier.java | 26 ++++++ 7 files changed, 193 insertions(+), 29 deletions(-) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 815aabcc57..648e304ee7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -32,6 +32,7 @@ import io.realm.RealmConfiguration; import io.realm.RealmFieldType; import io.realm.TestHelper; +import io.realm.internal.android.AndroidRealmNotifier; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -56,7 +57,7 @@ public class CollectionTests { @Before public void setUp() { config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = getSharedRealm(); populateData(); } @@ -65,6 +66,10 @@ public void tearDown() { sharedRealm.close(); } + private SharedRealm getSharedRealm() { + return SharedRealm.getInstance(config, new AndroidRealmNotifier(), null); + } + private void populateData() { sharedRealm.beginTransaction(); table = sharedRealm.getTable("test_table"); @@ -101,7 +106,7 @@ private void addRowAsync() { new Thread(new Runnable() { @Override public void run() { - SharedRealm sharedRealm = SharedRealm.getInstance(config); + SharedRealm sharedRealm = getSharedRealm(); addRow(sharedRealm); sharedRealm.close(); latch.countDown(); @@ -274,7 +279,7 @@ public void onChange(Collection element) { @Test @RunTestInLooperThread public void addListener_queryNotReturned() { - final SharedRealm sharedRealm = SharedRealm.getInstance(config); + final SharedRealm sharedRealm = getSharedRealm(); Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); @@ -295,7 +300,7 @@ public void onChange(Collection collection1) { @Test @RunTestInLooperThread public void addListener_queryReturned() { - final SharedRealm sharedRealm = SharedRealm.getInstance(config); + final SharedRealm sharedRealm = getSharedRealm(); Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); @@ -319,7 +324,7 @@ public void onChange(Collection collection1) { @Test @RunTestInLooperThread public void addListener_queryNotReturnedLocalAndRemoteCommit() { - final SharedRealm sharedRealm = SharedRealm.getInstance(config); + final SharedRealm sharedRealm = getSharedRealm(); Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); @@ -342,7 +347,7 @@ public void onChange(Collection collection1) { @Test @RunTestInLooperThread public void addListener_queryNotReturnedLocalCommitOnly() { - final SharedRealm sharedRealm = SharedRealm.getInstance(config); + final SharedRealm sharedRealm = getSharedRealm(); Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); @@ -364,7 +369,7 @@ public void onChange(Collection collection1) { @Test @RunTestInLooperThread public void addListener_queryReturnedLocalCommitOnly() { - final SharedRealm sharedRealm = SharedRealm.getInstance(config); + final SharedRealm sharedRealm = getSharedRealm(); Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); @@ -383,11 +388,35 @@ public void onChange(Collection collection1) { } @Test - public void size_doesNotChangeAfterLocalCommit() { + public void switchSnapshot_nonLooperThread() { final Collection collection = new Collection(sharedRealm, table.where()); assertEquals(collection.size(), 4); addRow(sharedRealm); + // The results is backed by snapshot now. assertEquals(collection.size(), 4); sharedRealm.refresh(); + // The results is switched back to the original Results. + assertEquals(collection.size(), 5); + } + + @Test + @RunTestInLooperThread + public void switchSnapshot_looperThread() { + final SharedRealm sharedRealm = getSharedRealm(); + final Collection collection = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collection); + assertEquals(collection.size(), 4); + looperThread.postRunnable(new Runnable() { + @Override + public void run() { + // The results is switched back to the original Results. + assertEquals(collection.size(), 5); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + addRow(sharedRealm); + // The results is backed by snapshot now. + assertEquals(collection.size(), 4); } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 0c82e4bfc8..6203d61950 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -24,6 +24,7 @@ #include "util.hpp" #include "jni_util/method.hpp" +#include "jni_util/log.hpp" using namespace realm; using namespace realm::jni_util; @@ -32,7 +33,6 @@ using namespace realm::jni_util; // Wrap all three together, so when the Java Collection object gets GCed, all three of them will be invalidated. struct ResultsWrapper { jobject m_collection_weak_ref; - Results m_results; NotificationToken m_notification_token; ResultsWrapper(Results&& results) @@ -52,6 +52,36 @@ struct ResultsWrapper { env->DeleteWeakGlobalRef(m_collection_weak_ref); } } + + inline Results& get_original_results() + { + return m_results; + } + + inline Results& get_results() + { + if (m_snapshot.get_mode() == Results::Mode::Empty) { + Log::e("Using origin."); + return m_results; + } else { + Log::e("Using snapshot."); + return m_snapshot; + } + } + + inline void switch_to_snapshot() + { + m_snapshot = m_results.snapshot(); + } + + inline void switch_to_origin() + { + m_snapshot = Results(); + } + +private: + Results m_results; + Results m_snapshot; }; static void finalize_results(jlong ptr); @@ -80,6 +110,9 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong sort_desc_ptr ? *sort_desc_ptr : SortDescriptor(), distinct_desc_ptr ? *distinct_desc_ptr : SortDescriptor()); auto wrapper = new ResultsWrapper(std::move(results)); + if (shared_realm->is_in_transaction()) { + wrapper->switch_to_snapshot(); + } return reinterpret_cast(wrapper); } CATCH_STD() @@ -92,7 +125,7 @@ Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlon TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto snapshot = wrapper->m_results.snapshot(); + auto snapshot = wrapper->get_original_results(); return reinterpret_cast(new Results(snapshot)); } CATCH_STD() return reinterpret_cast(nullptr); @@ -105,7 +138,7 @@ Java_io_realm_internal_Collection_nativeContains(JNIEnv *env, jclass, jlong nati try { auto wrapper = reinterpret_cast(native_ptr); auto row = reinterpret_cast(native_row_ptr); - size_t index = wrapper->m_results.index_of(*row); + size_t index = wrapper->get_results().index_of(*row); return to_jbool(index != not_found); } CATCH_STD(); return JNI_FALSE; @@ -117,7 +150,7 @@ Java_io_realm_internal_Collection_nativeGetRow(JNIEnv *env, jclass, jlong native TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->m_results.get(static_cast(index)); + auto row = wrapper->get_results().get(static_cast(index)); return reinterpret_cast(new Row(std::move(row))); } CATCH_STD() return reinterpret_cast(nullptr); @@ -129,7 +162,7 @@ Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv *env, jclass, jlong nati TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto optional_row = wrapper->m_results.first(); + auto optional_row = wrapper->get_results().first(); if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } @@ -144,7 +177,7 @@ Java_io_realm_internal_Collection_nativeLastRow(JNIEnv *env, jclass, jlong nativ TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto optional_row = wrapper->m_results.last(); + auto optional_row = wrapper->get_results().last(); if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } @@ -158,7 +191,7 @@ Java_io_realm_internal_Collection_nativeClear(JNIEnv *env, jclass, jlong native_ TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - wrapper->m_results.clear(); + wrapper->get_results().clear(); } CATCH_STD() } @@ -168,7 +201,7 @@ Java_io_realm_internal_Collection_nativeSize(JNIEnv *env, jclass, jlong native_p TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - return static_cast(wrapper->m_results.size()); + return static_cast(wrapper->get_results().size()); } CATCH_STD() return 0; } @@ -185,16 +218,16 @@ Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong nat Optional value; switch (agg_func) { case io_realm_internal_Collection_AGGREGATE_FUNCTION_MINIMUM: - value = wrapper->m_results.min(index); + value = wrapper->get_results().min(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_MAXIMUM: - value = wrapper->m_results.max(index); + value = wrapper->get_results().max(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_AVERAGE: - value = wrapper->m_results.average(index); + value = wrapper->get_results().average(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_SUM: - value = wrapper->m_results.sum(index); + value = wrapper->get_results().sum(index); break; default: REALM_UNREACHABLE(); @@ -228,7 +261,7 @@ Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_p try { auto wrapper = reinterpret_cast(native_ptr); auto sort_descriptor = *reinterpret_cast(sort_desc_native_ptr); - auto sorted_result = wrapper->m_results.sort(std::move(sort_descriptor)); + auto sorted_result = wrapper->get_results().sort(std::move(sort_descriptor)); return reinterpret_cast(new ResultsWrapper(std::move(sorted_result))); } CATCH_STD() return reinterpret_cast(nullptr); @@ -257,7 +290,7 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst env->CallVoidMethod(wrapper->m_collection_weak_ref, notify_change_listeners); }; - wrapper->m_notification_token = wrapper->m_results.add_notification_callback(cb); + wrapper->m_notification_token = wrapper->get_original_results().add_notification_callback(cb); } CATCH_STD() } @@ -286,7 +319,7 @@ Java_io_realm_internal_Collection_nativeWhere(JNIEnv *env, jclass, jlong native_ try { auto wrapper = reinterpret_cast(native_ptr); - Query *query = new Query(wrapper->m_results.get_query()); + Query *query = new Query(wrapper->get_original_results().get_query()); return reinterpret_cast(query); } CATCH_STD() return 0; @@ -300,7 +333,7 @@ Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv *env, jclass, jlong nativ auto wrapper = reinterpret_cast(native_ptr); auto row = reinterpret_cast(row_native_ptr); - return static_cast(wrapper->m_results.index_of(*row)); + return static_cast(wrapper->get_results().index_of(*row)); } CATCH_STD() return npos; } @@ -314,8 +347,28 @@ Java_io_realm_internal_Collection_nativeIndexOfBySourceRowIndex(JNIEnv *env, jcl auto wrapper = reinterpret_cast(native_ptr); auto index = static_cast(source_row_index); - return static_cast(wrapper->m_results.index_of(index)); + return static_cast(wrapper->get_results().index_of(index)); } CATCH_STD() return npos; } + +JNIEXPORT void JNICALL +Java_io_realm_internal_Collection_nativeEnableSnapshot(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + try { + auto wrapper = reinterpret_cast(native_ptr); + wrapper->switch_to_snapshot(); + } CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_Collection_nativeDisableSnapshot(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + try { + auto wrapper = reinterpret_cast(native_ptr); + wrapper->switch_to_origin(); + } CATCH_STD() +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index a4b714c4e5..bd95936e86 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -22,6 +22,7 @@ import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; +import io.realm.internal.SharedRealm; import rx.Observable; /** @@ -270,7 +271,7 @@ public static void addChangeListener(E object, RealmChang RealmObjectProxy proxy = (RealmObjectProxy) object; BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); realm.checkIfValid(); - realm.sharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be added."); + SharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be added."); //noinspection unchecked proxy.realmGet$proxyState().addChangeListener(listener); } else { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 70e4b5de44..309816722c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -88,6 +88,7 @@ public Collection(SharedRealm sharedRealm, TableQuery query, this.context = sharedRealm.context; this.query = query; this.context.addReference(this); + sharedRealm.addCollection(this); } public Collection(SharedRealm sharedRealm, TableQuery query, @@ -107,6 +108,7 @@ private Collection(SharedRealm sharedRealm, TableQuery query, long nativePtr) { this.nativePtr = nativePtr; this.context.addReference(this); + sharedRealm.addCollection(this); } @Override @@ -207,6 +209,14 @@ private void notifyChangeListeners() { } } + void enableSnapshot() { + nativeEnableSnapshot(nativePtr); + } + + void disableSnapshot() { + nativeDisableSnapshot(nativePtr); + } + private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long sortDescNativePtr, long distinctDescNativePtr); @@ -226,4 +236,6 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native long nativeWhere(long nativePtr); private static native long nativeIndexOf(long nativePtr, long rowNativePtr); private static native long nativeIndexOfBySourceRowIndex(long nativePtr, long sourceRowIndex); + private static native void nativeEnableSnapshot(long nativePtr); + private static native void nativeDisableSnapshot(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index f6b9cfd770..4db2c2e4c2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -27,7 +27,7 @@ * other thread/process changes the Realm file. */ @Keep -public class RealmNotifier implements Closeable { +public abstract class RealmNotifier implements Closeable { private static class RealmObserverPair extends ObserverPair> { public RealmObserverPair(T observer, RealmChangeListener listener) { @@ -95,4 +95,6 @@ public void removeChangeListener(E observer, RealmChangeListener realmCha public void removeAllChangeListeners() { realmObserverPairs.clear(); } + + public abstract void postAtFrontOfQueue(Runnable runnable); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 22c91bddae..389076cee0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -18,11 +18,13 @@ import java.io.Closeable; import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; import io.realm.RealmConfiguration; import io.realm.RealmSchema; import io.realm.internal.android.AndroidCapabilities; -import io.realm.internal.async.BadVersionException; public final class SharedRealm implements Closeable { @@ -107,6 +109,7 @@ public byte getNativeValue() { public final RealmNotifier realmNotifier; public final RowNotifier rowNotifier; public final ObjectServerFacade objectServerFacade; + public final List> collections = new ArrayList>(); public static class VersionID implements Comparable { public final long version; @@ -227,14 +230,24 @@ public long getNativePtr() { public void beginTransaction() { nativeBeginTransaction(nativePtr); invokeSchemaChangeListenerIfSchemaChanged(); + enableCollectionSnapshot(); } public void commitTransaction() { nativeCommitTransaction(nativePtr); + if (realmNotifier != null && !collections.isEmpty()) { + realmNotifier.postAtFrontOfQueue(new Runnable() { + @Override + public void run() { + disableCollectionSnapshot(); + } + }); + } } public void cancelTransaction() { nativeCancelTransaction(nativePtr); + disableCollectionSnapshot(); } public boolean isInTransaction() { @@ -289,6 +302,7 @@ public boolean isEmpty() { public void refresh() { nativeRefresh(nativePtr); invokeSchemaChangeListenerIfSchemaChanged(); + disableCollectionSnapshot(); } public SharedRealm.VersionID getVersionID() { @@ -336,7 +350,7 @@ public boolean isAutoRefresh() { return nativeIsAutoRefresh(nativePtr); } - public Capabilities getCapabilities() { + public static Capabilities getCapabilities() { return capabilities; } @@ -380,6 +394,33 @@ public void invokeSchemaChangeListenerIfSchemaChanged() { } } + // Should only be called by Collection's constructor + void addCollection(Collection collection) { + collections.add(new WeakReference(collection)); + } + + private void enableCollectionSnapshot() { + for (WeakReference collectionRef : collections) { + Collection collection = collectionRef.get(); + if (collection == null) { + collections.remove(collectionRef); + } else { + collection.enableSnapshot(); + } + } + } + + void disableCollectionSnapshot() { + for (WeakReference collectionRef : collections) { + Collection collection = collectionRef.get(); + if (collection == null) { + collections.remove(collectionRef); + } else { + collection.disableSnapshot(); + } + } + } + private static native void nativeInit(String temporaryDirectoryPath); private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory, boolean cache, boolean disableFormatUpgrade, diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java new file mode 100644 index 0000000000..75e4c04873 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java @@ -0,0 +1,26 @@ +package io.realm.internal.android; + +import android.os.Handler; +import android.os.Looper; + +import io.realm.internal.RealmNotifier; +import io.realm.internal.SharedRealm; + +public class AndroidRealmNotifier extends RealmNotifier { + private final Handler handler; + + public AndroidRealmNotifier() { + if (SharedRealm.getCapabilities().canDeliverNotification()) { + handler = new Handler(Looper.myLooper()); + } else { + handler = null; + } + } + + @Override + public void postAtFrontOfQueue(Runnable runnable) { + if (handler != null) { + handler.postAtFrontOfQueue(runnable); + } + } +} From 953d4b417b66dc118a4086d059f76187603ae72e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Dec 2016 03:12:39 +0800 Subject: [PATCH 0283/2110] Bug in the tests! --- .../src/androidTest/java/io/realm/internal/CollectionTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 648e304ee7..697594fbf6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -403,6 +403,7 @@ public void switchSnapshot_nonLooperThread() { @RunTestInLooperThread public void switchSnapshot_looperThread() { final SharedRealm sharedRealm = getSharedRealm(); + Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); looperThread.keepStrongReference.add(collection); assertEquals(collection.size(), 4); From 9db954757bf355774b5a1cb6e608e2815ad61491 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Dec 2016 14:58:37 +0800 Subject: [PATCH 0284/2110] Workaround for the Alooper and postAtFrontQueue See https://github.com/realm/realm-java/issues/3883#issuecomment-265659475 --- .../io/realm/internal/CollectionTests.java | 1 + .../main/cpp/io_realm_internal_Collection.cpp | 4 ++- .../src/main/cpp/java_binding_context.cpp | 25 +++++++++++++++---- .../src/main/cpp/java_binding_context.hpp | 7 ++++-- .../src/main/java/io/realm/BaseRealm.java | 3 ++- .../java/io/realm/internal/Collection.java | 8 ++++-- .../java/io/realm/internal/RealmNotifier.java | 13 +++++++++- .../java/io/realm/internal/SharedRealm.java | 9 ++++++- 8 files changed, 57 insertions(+), 13 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 697594fbf6..1f7a49376d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -385,6 +385,7 @@ public void onChange(Collection collection1) { } }); addRow(sharedRealm); + assertEquals(collection.size(), 4); } @Test diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 6203d61950..daf17d3828 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -76,7 +76,9 @@ struct ResultsWrapper { inline void switch_to_origin() { - m_snapshot = Results(); + if (m_snapshot.get_mode() != Results::Mode::Empty) { + m_snapshot = Results(); + } } private: diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index bbda5f39b0..a49bc9e196 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -20,7 +20,8 @@ using namespace realm; using namespace realm::_impl; using namespace realm::jni_util; -JniMethod JavaBindingContext::m_realm_notifier_on_change_method; +JniMethod JavaBindingContext::m_realm_notifier_did_change_method; +JniMethod JavaBindingContext::m_realm_notifier_changes_available_method; JniMethod JavaBindingContext::m_get_observers_method; JniMethod JavaBindingContext::m_get_observed_row_ptrs_method; JniMethod JavaBindingContext::m_clear_row_refs_method; @@ -36,9 +37,12 @@ JavaBindingContext::JavaBindingContext(const ConcreteJavaBindContext& concrete_c if (concrete_context.realm_notifier) { m_realm_notifier = m_local_jni_env->NewWeakGlobalRef(concrete_context.realm_notifier); - if (!m_realm_notifier_on_change_method) { - m_realm_notifier_on_change_method = JniMethod(m_local_jni_env, m_realm_notifier, - "onChange", "()V"); + jclass cls = m_local_jni_env->GetObjectClass(m_realm_notifier); + if (!m_realm_notifier_did_change_method || ! m_realm_notifier_changes_available_method) { + m_realm_notifier_did_change_method = JniMethod(m_local_jni_env, cls, + "didChange", "()V"); + m_realm_notifier_changes_available_method = JniMethod(m_local_jni_env, cls, + "changesAvailable", "()V"); } } else { m_realm_notifier = nullptr; @@ -108,6 +112,17 @@ std::vector JavaBindingContext::get_observed_rows return state_list; } +void JavaBindingContext::changes_available() +{ + if (m_local_jni_env->ExceptionCheck()) return; + + jobject notifier = m_local_jni_env->NewLocalRef(m_realm_notifier); + if (notifier) { + m_local_jni_env->CallVoidMethod(notifier, m_realm_notifier_changes_available_method); + m_local_jni_env->DeleteLocalRef(notifier); + } +} + void JavaBindingContext::did_change(std::vector const& observer_state_list, std::vector const& invalidated, bool /*version_changed*/) @@ -132,7 +147,7 @@ void JavaBindingContext::did_change(std::vector c if (m_local_jni_env->ExceptionCheck()) return; jobject notifier = m_local_jni_env->NewLocalRef(m_realm_notifier); if (notifier) { - m_local_jni_env->CallVoidMethod(m_realm_notifier, m_realm_notifier_on_change_method); + m_local_jni_env->CallVoidMethod(notifier, m_realm_notifier_did_change_method); m_local_jni_env->DeleteLocalRef(notifier); } } diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index 38e195c337..59e03d0a85 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -50,8 +50,10 @@ class JavaBindingContext final : public BindingContext { jobject m_row_notifier; // Cache the method IDs - // RealmNotifier.onChange() - static realm::jni_util::JniMethod m_realm_notifier_on_change_method; + // RealmNotifier.didChange() + static realm::jni_util::JniMethod m_realm_notifier_did_change_method; + // RealmNotifier.changesAvailable() + static realm::jni_util::JniMethod m_realm_notifier_changes_available_method; // RowNotifier.getObservers() static realm::jni_util::JniMethod m_get_observers_method; // RowNotifier.getObservedRowPtrs(Observer[]) @@ -64,6 +66,7 @@ class JavaBindingContext final : public BindingContext { public: virtual ~JavaBindingContext(); virtual std::vector get_observed_rows(); + virtual void changes_available(); virtual void did_change(std::vector const& observers, std::vector const& invalidated, bool version_changed=true); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 9f3734114c..0b2a00af99 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -38,6 +38,7 @@ import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.Util; +import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.RealmLog; import io.realm.internal.ObjectServerFacade; @@ -77,7 +78,7 @@ protected BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; - this.sharedRealm = SharedRealm.getInstance(configuration, new RealmNotifier(), + this.sharedRealm = SharedRealm.getInstance(configuration, new AndroidRealmNotifier(), !(this instanceof Realm) ? null : new SharedRealm.SchemaVersionListener() { @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 309816722c..b1eb397407 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -28,7 +28,7 @@ @KeepMember public final class Collection implements NativeObject { - private static class CollectionObserverPair extends ObserverPair>{ + private class CollectionObserverPair extends ObserverPair>{ public CollectionObserverPair(T observer, RealmChangeListener listener) { super(observer, listener); } @@ -36,7 +36,6 @@ public CollectionObserverPair(T observer, RealmChangeListener listener) { public void onChange() { T observer = observerRef.get(); if (observer != null) { - listener.onChange(observerRef.get()); } } @@ -199,6 +198,11 @@ public void removeAllListeners() { @KeepMember @SuppressWarnings("unused") private void notifyChangeListeners() { + // For the stable iteration. + // It is needed when the local commit triggered async query updates. And this is called in the next event loop + // by OS Realm::notify(). + this.disableSnapshot(); + for (CollectionObserverPair pair: observerPairs) { Object object = pair.observerRef.get(); if (object != null) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 4db2c2e4c2..4f7a0a4a84 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -29,6 +29,8 @@ @Keep public abstract class RealmNotifier implements Closeable { + private SharedRealm sharedRealm; + private static class RealmObserverPair extends ObserverPair> { public RealmObserverPair(T observer, RealmChangeListener listener) { super(observer, listener); @@ -61,7 +63,7 @@ private void onChange() { * other thread. The changes on the same thread should not trigger this call. */ @SuppressWarnings("unused") // called from java_binding_context.cpp - void onChange() { + protected void didChange() { for (RealmObserverPair observerPair : realmObserverPairs) { Object observer = observerPair.observerRef.get(); if (observer == null) { @@ -72,6 +74,15 @@ void onChange() { } } + @SuppressWarnings("unused") // called from java_binding_context.cpp + protected void changesAvailable() { + sharedRealm.disableCollectionSnapshot(); + } + + void setSharedRealm(SharedRealm sharedRealm) { + this.sharedRealm = sharedRealm; + } + /** * Called when close SharedRealm to clean up any event left in to queue. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 389076cee0..aa1cfc8807 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -178,7 +178,12 @@ private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotif RowNotifier rowNotifier, SchemaVersionListener schemaVersionListener) { this.nativePtr = nativePtr; this.configuration = configuration; + + if (notifier != null) { + notifier.setSharedRealm(this); + } this.realmNotifier = notifier; + this.rowNotifier = rowNotifier; this.schemaChangeListener = schemaVersionListener; context = new Context(); @@ -396,7 +401,9 @@ public void invokeSchemaChangeListenerIfSchemaChanged() { // Should only be called by Collection's constructor void addCollection(Collection collection) { - collections.add(new WeakReference(collection)); + if (realmNotifier != null) { + collections.add(new WeakReference(collection)); + } } private void enableCollectionSnapshot() { From 78715308feb9f2528bd3d05b16e55a688c7d0c14 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Dec 2016 15:57:13 +0800 Subject: [PATCH 0285/2110] Add Collection.deleteXxx --- .../main/cpp/io_realm_internal_Collection.cpp | 56 ++++++++++++++++++- .../src/main/java/io/realm/RealmResults.java | 27 +++------ .../java/io/realm/internal/Collection.java | 15 +++++ 3 files changed, 77 insertions(+), 21 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index daf17d3828..71312a4f77 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -24,7 +24,6 @@ #include "util.hpp" #include "jni_util/method.hpp" -#include "jni_util/log.hpp" using namespace realm; using namespace realm::jni_util; @@ -61,10 +60,8 @@ struct ResultsWrapper { inline Results& get_results() { if (m_snapshot.get_mode() == Results::Mode::Empty) { - Log::e("Using origin."); return m_results; } else { - Log::e("Using snapshot."); return m_snapshot; } } @@ -374,3 +371,56 @@ Java_io_realm_internal_Collection_nativeDisableSnapshot(JNIEnv *env, jclass, jlo wrapper->switch_to_origin(); } CATCH_STD() } + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + try { + auto wrapper = reinterpret_cast(native_ptr); + if (wrapper->get_results().size() > 0) { + wrapper->get_results().get_tableview().remove_last(); + // Refresh snapshot + wrapper->switch_to_snapshot(); + return JNI_TRUE; + } + } CATCH_STD() + + return JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_Collection_nativeDeleteFirst(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + + try { + auto wrapper = reinterpret_cast(native_ptr); + if (wrapper->get_results().size() > 0) { + wrapper->get_results().get_tableview().remove(0); + // Refresh snapshot + wrapper->switch_to_snapshot(); + return JNI_TRUE; + } + } CATCH_STD() + + return JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_Collection_nativeDelete(JNIEnv *env, jclass, jlong native_ptr, jlong index) +{ + TR_ENTER_PTR(native_ptr) + + try { + auto wrapper = reinterpret_cast(native_ptr); + auto view = wrapper->get_results().get_tableview(); + size_t size = view.size(); + if (index < 0 || index >= size) { + throw Results::OutOfBoundsIndexException{static_cast(index), size}; + } + view.remove(static_cast(index)); + // Refresh snapshot + wrapper->switch_to_snapshot(); + } CATCH_STD() +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 77eae575a6..83a92bc5e2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -219,9 +219,9 @@ private E lastImpl(boolean shouldThrow, E defaultValue) { */ @Override public void deleteFromRealm(int location) { - realm.checkIfValid(); - // FIXME: Implement this! - throw new RuntimeException("FIXME: Implement this!"); + // TODO: Implement the deleteLast in OS level and do check there! + realm.checkIfValidAndInTransaction(); + collection.delete(location); } /** @@ -540,14 +540,9 @@ public boolean retainAll(@SuppressWarnings("NullableProblems") java.util.Collect */ @Override public boolean deleteLastFromRealm() { - realm.checkIfValid(); - if (size() > 0) { - // FIXME: Implement this! - throw new RuntimeException("FIXME: Implement this!"); - //return true; - } else { - return false; - } + // TODO: Implement the deleteLast in OS level and do check there! + realm.checkIfValidAndInTransaction(); + return collection.deleteLast(); } /** @@ -557,13 +552,9 @@ public boolean deleteLastFromRealm() { */ @Override public boolean deleteFirstFromRealm() { - if (size() > 0) { - // FIXME: Implement this! - throw new RuntimeException("FIXME: Implement this!"); - //return true; - } else { - return false; - } + // TODO: Implement the deleteLast in OS level and do check there! + realm.checkIfValidAndInTransaction(); + return collection.deleteFirst(); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index b1eb397407..1443bf72ec 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -171,6 +171,18 @@ public int indexOf(long sourceRowIndex) { return (index > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) index; } + public void delete(long index) { + nativeDelete(nativePtr, index); + } + + public boolean deleteFirst() { + return nativeDeleteFirst(nativePtr); + } + + public boolean deleteLast() { + return nativeDeleteLast(nativePtr); + } + public void addListener(T observer, RealmChangeListener listener) { if (observerPairs.isEmpty()) { nativeStartListening(nativePtr); @@ -234,6 +246,9 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native long nativeSize(long nativePtr); private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); private static native long nativeSort(long nativePtr, long sortDescNativePtr); + private static native boolean nativeDeleteFirst(long nativePtr); + private static native boolean nativeDeleteLast(long nativePtr); + private static native void nativeDelete(long nativePtr, long index); // Non-static, we need this Collection object in JNI. private native void nativeStartListening(long nativePtr); private native void nativeStopListening(long nativePtr); From 05fd42babe8a52b9a17c6237cb2c9391e1b3c290 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Dec 2016 16:04:46 +0800 Subject: [PATCH 0286/2110] Convert IncorrectThreadException --- realm/realm-library/src/main/cpp/util.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 5baa5ef2cb..db0f748f95 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -97,6 +97,10 @@ void ConvertException(JNIEnv* env, const char *file, int line) << "(field name: " << e.column_name << ")"; ThrowException(env, IllegalArgument, ss.str()); } + catch (IncorrectThreadException& e) { + ss << e.what() << " in " << file << " line " << line; + ThrowException(env, IllegalState, ss.str()); + } catch (exception& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, FatalError, ss.str()); From f94a76792fe83fb81272d0a0b0d6ecff73ab7a6b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Dec 2016 16:15:06 +0800 Subject: [PATCH 0287/2110] Fix concurrency problem with collection list --- .../src/main/java/io/realm/internal/SharedRealm.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index aa1cfc8807..c3edef25e1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -19,8 +19,8 @@ import java.io.Closeable; import java.io.File; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import io.realm.RealmConfiguration; import io.realm.RealmSchema; @@ -109,7 +109,7 @@ public byte getNativeValue() { public final RealmNotifier realmNotifier; public final RowNotifier rowNotifier; public final ObjectServerFacade objectServerFacade; - public final List> collections = new ArrayList>(); + public final List> collections = new CopyOnWriteArrayList>(); public static class VersionID implements Comparable { public final long version; From 13ce12c84ba3c2ee96f7c275099aa73beaa0d593 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Dec 2016 16:19:35 +0800 Subject: [PATCH 0288/2110] We support link field sorting --- .../ManagedOrderedRealmCollectionTests.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index c903d110c7..0dbcfb8a43 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -448,22 +448,20 @@ public void sort_twoLanguages() { @Test public void sort_usingChildObject() { - realm.beginTransaction(); - Owner owner = realm.createObject(Owner.class); - owner.setName("owner"); - Cat cat = realm.createObject(Cat.class); - cat.setName("cat"); - owner.setCat(cat); - realm.commitTransaction(); + OrderedRealmCollection resultList = collection; + OrderedRealmCollection sortedList = createCollection(collectionClass); + sortedList = sortedList.sort(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LONG, Sort.DESCENDING); + assertEquals("Should have same size", resultList.size(), sortedList.size()); + assertEquals(TEST_SIZE, sortedList.size()); + assertEquals("First excepted to be last", resultList.first().getFieldLong(), sortedList.last().getFieldLong()); - RealmQuery query = realm.where(Owner.class); - RealmResults owners = query.findAll(); + sortedList = sortedList.sort(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + assertEquals(TEST_SIZE, sortedList.size()); + assertEquals("First excepted to be first", resultList.first().getFieldLong(), sortedList.first().getFieldLong()); + assertEquals("Last excepted to be last", resultList.last().getFieldLong(), sortedList.last().getFieldLong()); - try { - owners.sort("cat.name"); - fail("Sorting by child object properties should result in a IllegalArgumentException"); - } catch (IllegalArgumentException ignore) { - } + sortedList = sortedList.sort(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LONG, Sort.DESCENDING); + assertEquals(TEST_SIZE, sortedList.size()); } @Test From d3af655f41745b4da67f6f8fa1de1f9f0b523d46 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Dec 2016 17:47:50 +0800 Subject: [PATCH 0289/2110] Accessor of PendingRow should throw Explicitly to let the front end run the pending query. --- .../src/main/java/io/realm/ProxyState.java | 44 +++++---- .../java/io/realm/internal/PendingRow.java | 96 +++++++++---------- 2 files changed, 74 insertions(+), 66 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 799bbeaf06..13cde56a29 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -57,6 +57,10 @@ public ProxyState(E model) { } public Row getRow$realm() { + if (row instanceof PendingRow) { + row = ((PendingRow) row).executeQuery(); + registerToRowNotifier(); + } return row; } @@ -87,9 +91,9 @@ public ProxyState(E model) { /** * Notifies all registered listeners. */ - void notifyChangeListeners$realm() { + private void notifyChangeListeners() { if (!listeners.isEmpty()) { - for (RealmChangeListener listener : listeners) { + for (RealmChangeListener listener : listeners) { listener.onChange(model); } } @@ -99,12 +103,14 @@ public void addChangeListener(RealmChangeListener listener) { if (!listeners.contains(listener)) { listeners.add(listener); } + // this might be called after query returns. So it is still necessary to register. if (row instanceof UncheckedRow) { RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; + // RowNotifier will take care of the duplicated ObserverPairs rowNotifier.registerListener((UncheckedRow) row, this, new RealmChangeListener>() { @Override public void onChange(ProxyState proxyState) { - proxyState.notifyChangeListeners$realm(); + proxyState.notifyChangeListeners(); } }); } @@ -126,22 +132,26 @@ public void setConstructionFinished() { excludeFields = null; } - @Override - public void onQueryFinished(Row row, boolean asyncQuery) { - this.row = row; - if (asyncQuery) { - notifyChangeListeners$realm(); + private void registerToRowNotifier() { + RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; + if (row.isAttached()) { + rowNotifier.registerListener((UncheckedRow) row, this, new RealmChangeListener>() { + @Override + public void onChange(ProxyState proxyState) { + proxyState.notifyChangeListeners(); + } + }); } - // FIXME: Figure out why this can be null. - if (realm.sharedRealm == null) { + } + + @Override + public void onQueryFinished(Row row) { + if (realm.sharedRealm == null || realm.sharedRealm.isClosed()) { return; } - RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; - rowNotifier.registerListener((UncheckedRow) row, this, new RealmChangeListener>() { - @Override - public void onChange(ProxyState proxyState) { - proxyState.notifyChangeListeners$realm(); - } - }); + + this.row = row; + notifyChangeListeners(); + registerToRowNotifier(); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 172215f52d..3dbd0d1317 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -8,23 +8,22 @@ /** * A PendingRow is a row relies on a pending async query. - * Before the query returns, calling any accessors will immediately execute the query and call the corresponding - * accessor on the query result. If the query results is empty, an {@link IllegalStateException} will be thrown. - * After the query returns, {@link FrontEnd#onQueryFinished(Row, boolean)} will be called to give the front end a - * chance to reset the row. If the async query returns an empty result, the query will be executed again later until a - * valid row is contained by the query results. + * Before the query returns, calling any accessors will immediately throw. In this case run {@link #executeQuery()} to + * get the queried row immediately. If the query results is empty, an {@link InvalidRow} will be returned. + * After the query returns, {@link FrontEnd#onQueryFinished(Row)} will be called to give the front end a chance to reset + * the row. If the async query returns an empty result, the query will be executed again later until a valid row is + * contained by the query results. */ public class PendingRow implements Row { // Implement this interface to reset the PendingRow to a Row backed by real data when query returned. public interface FrontEnd { - // When asyncQuery is true, the pending query is executed asynchronously. Otherwise the query is triggered by - // calling any accessors before the async query returns. - void onQueryFinished(Row row, boolean asyncQuery); + // When asyncQuery is true, the pending query is executed asynchronously. + void onQueryFinished(Row row); } - private static final String EMPTY_ROW_MESSAGE = - "This RealmObject is empty. There isn't any objects match the query."; + private static final String QUERY_NOT_RETURNED_MESSAGE = + "The pending query has not been executed."; private static final String PROXY_NOT_SET_MESSAGE = "The 'frontEnd' has not been set."; private static final String QUERY_EXECUTED_MESSAGE = "The query has been executed. This 'PendingRow' is not valid anymore."; @@ -44,18 +43,19 @@ public void onChange(PendingRow pendingRow) { if (frontEnd == null) { throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); } - // TODO: PendingRow will always get the first Row of the query since we only support findFirst. if (frontEnd.get() == null) { // The front end is GCed. clearPendingCollection(); return; } + + // PendingRow will always get the first Row of the query since we only support findFirst. UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); // If no rows returned by the query, just wait for the query updates until it returns a valid row. if (uncheckedRow != null) { Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; // Ask the front end to reset the row and stop async query. - frontEnd.get().onQueryFinished(row, true); + frontEnd.get().onQueryFinished(row); clearPendingCollection(); } } @@ -71,147 +71,147 @@ public void setFrontEnd(FrontEnd frontEnd) { @Override public long getColumnCount() { - return executeQuery().getColumnCount(); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public String getColumnName(long columnIndex) { - return executeQuery().getColumnName(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public long getColumnIndex(String columnName) { - return executeQuery().getColumnIndex(columnName); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public RealmFieldType getColumnType(long columnIndex) { - return executeQuery().getColumnType(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public Table getTable() { - return executeQuery().getTable(); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public long getIndex() { - return executeQuery().getIndex(); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public long getLong(long columnIndex) { - return executeQuery().getLong(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean getBoolean(long columnIndex) { - return executeQuery().getBoolean(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public float getFloat(long columnIndex) { - return executeQuery().getFloat(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public double getDouble(long columnIndex) { - return executeQuery().getDouble(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public Date getDate(long columnIndex) { - return executeQuery().getDate(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public String getString(long columnIndex) { - return executeQuery().getString(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public byte[] getBinaryByteArray(long columnIndex) { - return executeQuery().getBinaryByteArray(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public long getLink(long columnIndex) { - return executeQuery().getLink(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean isNullLink(long columnIndex) { - return executeQuery().isNullLink(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public LinkView getLinkList(long columnIndex) { - return executeQuery().getLinkList(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setLong(long columnIndex, long value) { - executeQuery().setLong(columnIndex, value); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setBoolean(long columnIndex, boolean value) { - executeQuery().setBoolean(columnIndex, value); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setFloat(long columnIndex, float value) { - executeQuery().setFloat(columnIndex, value); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setDouble(long columnIndex, double value) { - executeQuery().setDouble(columnIndex, value); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setDate(long columnIndex, Date date) { - executeQuery().setDate(columnIndex, date); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setString(long columnIndex, String value) { - executeQuery().setString(columnIndex, value); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setBinaryByteArray(long columnIndex, byte[] data) { - executeQuery().setBinaryByteArray(columnIndex, data); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setLink(long columnIndex, long value) { - executeQuery().setLink(columnIndex, value); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void nullifyLink(long columnIndex) { - executeQuery().nullifyLink(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean isNull(long columnIndex) { - return executeQuery().isNull(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setNull(long columnIndex) { - executeQuery().setNull(columnIndex); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean isAttached() { - return executeQuery().isAttached(); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean hasColumn(String fieldName) { - return executeQuery().hasColumn(fieldName); + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } private void clearPendingCollection() { @@ -220,22 +220,20 @@ private void clearPendingCollection() { listener = null; } - private Row executeQuery() { + public Row executeQuery() { if (pendingCollection == null) { throw new IllegalStateException(QUERY_EXECUTED_MESSAGE); } if (frontEnd == null) { throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); } + UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); + clearPendingCollection(); + if (uncheckedRow == null) { - throw new IllegalStateException(EMPTY_ROW_MESSAGE); - } - Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; - if (frontEnd.get() != null) { - frontEnd.get().onQueryFinished(row, false); + return InvalidRow.INSTANCE; } - clearPendingCollection(); - return row; + return returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; } } From 355267c6c73c0e01a5082ef369d85b33b58d932b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 8 Dec 2016 12:04:11 +0000 Subject: [PATCH 0290/2110] Nh/fix test (#3868) * Fix SyncConfiguration test --- .../java/io/realm/SyncConfigurationTests.java | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- .../syncpolicy/AutomaticSyncPolicy.java | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 17b734f903..5eae0aeb50 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -235,13 +235,13 @@ public void equals() { } @Test - public void not_equals_same() { + public void equals_same() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config1 = new SyncConfiguration.Builder(user, url).build(); SyncConfiguration config2 = new SyncConfiguration.Builder(user, url).build(); - assertFalse(config1.equals(config2)); + assertTrue(config1.equals(config2)); } @Test diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index a7df0504d6..9ec19d106c 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit a7df0504d6a5cd73d4ee6a9de6d38fe2f5c95bba +Subproject commit 9ec19d106c3f12916ba64b89557daae82d47c756 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java index fc01484223..6f4b784181 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java @@ -86,4 +86,22 @@ private boolean rebind(ObjectServerSession session) { return true; } } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AutomaticSyncPolicy that = (AutomaticSyncPolicy) o; + + if (recurringErrors != that.recurringErrors) return false; + return lastError != null ? lastError.equals(that.lastError) : that.lastError == null; + } + + @Override + public int hashCode() { + int result = lastError != null ? lastError.hashCode() : 0; + result = 31 * result + recurringErrors; + return result; + } } From 694b342d8bf1e36036b9ecd54a3b84608efe4243 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 17:37:25 +0800 Subject: [PATCH 0291/2110] Workaround the collection average behavior --- .../src/main/cpp/io_realm_internal_Collection.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 71312a4f77..e1a1ba7728 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -224,6 +224,10 @@ Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong nat break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_AVERAGE: value = wrapper->get_results().average(index); + // TODO: Align the behavior with ObjectStore. + if (!value) { + value = Optional(0.0); + } break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_SUM: value = wrapper->get_results().sum(index); From 434845ee6d7acf37afa16f25e4f8f888b392e6d3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 17:51:22 +0800 Subject: [PATCH 0292/2110] Refresh snapshot when Collection.clear --- .../realm-library/src/main/cpp/io_realm_internal_Collection.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index e1a1ba7728..df64a65126 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -191,6 +191,8 @@ Java_io_realm_internal_Collection_nativeClear(JNIEnv *env, jclass, jlong native_ try { auto wrapper = reinterpret_cast(native_ptr); wrapper->get_results().clear(); + // Refresh snapshot + wrapper->switch_to_snapshot(); } CATCH_STD() } From f818c1e6335b9d6bf4e89d8bede43398280d86a6 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 18:04:13 +0800 Subject: [PATCH 0293/2110] Fix wrong thread test When call the RealmResults.contains(), the given object must be a managed RealmObject, otherwise it will just return false. --- .../io/realm/ManagedRealmCollectionTests.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 0c40da4b90..e5b49b71b1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -699,15 +699,19 @@ public void mutableMethodsOutsideTransactions() { @Test public void methodsThrowOnWrongThread() throws ExecutionException, InterruptedException { + realm.beginTransaction(); + AllJavaTypes allJavaTypes = realm.createObject(AllJavaTypes.class, 42); + realm.commitTransaction(); for (RealmCollectionMethod method : RealmCollectionMethod.values()) { assertTrue(method + " failed", runMethodOnWrongThread(method)); } for (CollectionMethod method : CollectionMethod.values()) { - assertTrue(method + " failed", runMethodOnWrongThread(method)); + assertTrue(method + " failed", runMethodOnWrongThread(method, allJavaTypes)); } } - private boolean runMethodOnWrongThread(final RealmCollectionMethod method) throws ExecutionException, InterruptedException { + private boolean runMethodOnWrongThread(final RealmCollectionMethod method) + throws ExecutionException, InterruptedException { realm.beginTransaction(); ExecutorService executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new Callable() { @@ -737,7 +741,8 @@ public Boolean call() throws Exception { return result; } - private boolean runMethodOnWrongThread(final CollectionMethod method) throws ExecutionException, InterruptedException { + private boolean runMethodOnWrongThread(final CollectionMethod method, final AllJavaTypes tempObject) + throws ExecutionException, InterruptedException { realm.beginTransaction(); ExecutorService executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new Callable() { @@ -762,8 +767,9 @@ public Boolean call() throws Exception { switch (method) { case ADD_OBJECT: collection.add(new AllJavaTypes()); break; case ADD_ALL_OBJECTS: collection.addAll(Collections.singletonList(new AllJavaTypes())); break; - case CLEAR: collection.clear(); case CONTAINS: - case CONTAINS_ALL: collection.containsAll(Collections.singletonList(new AllJavaTypes())); break; + case CLEAR: collection.clear(); + case CONTAINS: + case CONTAINS_ALL: collection.containsAll(Collections.singletonList(tempObject)); break; case EQUALS: collection.equals(createCollection(collectionClass)); break; case HASHCODE: //noinspection ResultOfMethodCallIgnored From c1e6ab693c8ed3192f1d7a4587e94a8d85ff1597 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 19:11:11 +0800 Subject: [PATCH 0294/2110] Test case needs the row to be querid first --- .../src/androidTest/java/io/realm/DynamicRealmObjectTests.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 9386beb7cb..d490d8ee30 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -614,6 +614,9 @@ public void setObject_objectBelongToTypedRealmThrows() { @Test public void setObject_objectBelongToDiffThreadRealmThrows() { final CountDownLatch finishedLatch = new CountDownLatch(1); + // To run the query of the PendingRow first. + assertTrue(dObjDynamic.isValid()); + new Thread(new Runnable() { @Override public void run() { From 28a55ed9f13e05425f690466fed940daf69bff40 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 19:20:28 +0800 Subject: [PATCH 0295/2110] We cannot deprecate the findFirstAsync findFirst() may return null before which has a differnt API behavior. --- .../io/realm/DynamicRealmObjectTests.java | 2 -- .../src/main/java/io/realm/RealmQuery.java | 33 ++++++++++++++----- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index d490d8ee30..dae1912958 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -614,8 +614,6 @@ public void setObject_objectBelongToTypedRealmThrows() { @Test public void setObject_objectBelongToDiffThreadRealmThrows() { final CountDownLatch finishedLatch = new CountDownLatch(1); - // To run the query of the PendingRow first. - assertTrue(dObjDynamic.isValid()); new Thread(new Runnable() { @Override diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 49108ac3df..bb311d623f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -17,7 +17,6 @@ package io.realm; -import java.lang.ref.WeakReference; import java.util.Collections; import java.util.Date; import java.util.Locale; @@ -1632,6 +1631,27 @@ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, * @see io.realm.RealmObject */ public E findFirst() { + long tableRowIndex = getSourceRowIndexForFirstObject(); + if (tableRowIndex >= 0) { + E realmObject = realm.get(clazz, className, tableRowIndex); + return realmObject; + } else { + return null; + } + } + + /** + * Similar to {@link #findFirst()} but runs asynchronously on a worker thread + * This method is only available from a Looper thread. + * + * @return immediately an empty {@link RealmObject}. Trying to access any field on the returned object + * before it is loaded will throw an {@code IllegalStateException}. Use {@link RealmObject#isLoaded()} to check if + * the object is fully loaded or register a listener {@link io.realm.RealmObject#addChangeListener} + * to be notified when the query completes. If no RealmObject was found after the query completed, the returned + * RealmObject will have {@link RealmObject#isLoaded()} set to {@code true} and {@link RealmObject#isValid()} set to + * {@code false}. + */ + public E findFirstAsync() { Row row; if (realm.isInTransaction()) { // It is not possible to create async query inside a transaction. So immediately query the first object. @@ -1664,13 +1684,6 @@ public E findFirst() { return result; } - /** - * @deprecated use {@link #findFirst()} instead. - */ - public E findFirstAsync() { - return findFirst(); - } - private void checkSortParameters(String fieldNames[], final Sort[] sortOrders) { if (fieldNames == null) { throw new IllegalArgumentException("fieldNames cannot be 'null'."); @@ -1692,4 +1705,8 @@ private RealmResults createRealmResults(Collection collection) { return new RealmResults(realm, collection, clazz); } } + + private long getSourceRowIndexForFirstObject() { + return this.query.find(); + } } From a9e0606925c0ec3826f384b105722932556b434a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 19:47:32 +0800 Subject: [PATCH 0296/2110] Collection change callback should always be called even when the change set is empty. When it is empty, it could be: 1. The async query finished. 2. A local transaction is started. In this case, the collections will be switch to original Results first then switch to snapshot in after nativeBeginTransaction returns. --- .../realm-library/src/main/cpp/io_realm_internal_Collection.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index df64a65126..95620965b3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -289,8 +289,6 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst std::exception_ptr err) { // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; - // No changes. - if (changes.empty()) return; env->CallVoidMethod(wrapper->m_collection_weak_ref, notify_change_listeners); }; From 04c4b27168dafe14e63924527abe31e1ce2dda2a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 19:53:27 +0800 Subject: [PATCH 0297/2110] Only cast type once. --- .../src/main/java/io/realm/RealmResults.java | 15 +++++++-------- .../main/java/io/realm/internal/Collection.java | 11 +++++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 83a92bc5e2..36000c17bf 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -378,7 +378,7 @@ public int size() { public Number min(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return (Number)collection.aggregate(io.realm.internal.Collection.Aggregate.MINIMUM, columnIndex); + return collection.aggregateNumber(io.realm.internal.Collection.Aggregate.MINIMUM, columnIndex); } /** @@ -387,7 +387,7 @@ public Number min(String fieldName) { public Date minDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return (Date) collection.aggregate(Collection.Aggregate.MINIMUM, columnIndex); + return collection.aggregateDate(Collection.Aggregate.MINIMUM, columnIndex); } /** @@ -396,7 +396,7 @@ public Date minDate(String fieldName) { public Number max(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return (Number) collection.aggregate(Collection.Aggregate.MAXIMUM, columnIndex); + return collection.aggregateNumber(Collection.Aggregate.MAXIMUM, columnIndex); } /** @@ -412,7 +412,7 @@ public Number max(String fieldName) { public Date maxDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return (Date) collection.aggregate(Collection.Aggregate.MAXIMUM, columnIndex); + return collection.aggregateDate(Collection.Aggregate.MAXIMUM, columnIndex); } @@ -422,7 +422,7 @@ public Date maxDate(String fieldName) { public Number sum(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return (Number) collection.aggregate(Collection.Aggregate.SUM, columnIndex); + return collection.aggregateNumber(Collection.Aggregate.SUM, columnIndex); } /** @@ -432,9 +432,8 @@ public double average(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - // FIXME: Should we change return type to Double? - Number sum = (Number) collection.aggregate(Collection.Aggregate.AVERAGE, columnIndex); - return sum.doubleValue(); + Number avg = collection.aggregateNumber(Collection.Aggregate.AVERAGE, columnIndex); + return avg.doubleValue(); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 1443bf72ec..7415f3c0fd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -16,6 +16,7 @@ package io.realm.internal; +import java.util.Date; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -141,8 +142,14 @@ public TableQuery where() { return new TableQuery(this.context, this.getTable(), nativeQueryPtr); } - public Object aggregate(Aggregate aggregateMethod, long columnIndex) { - return nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); + public Number aggregateNumber(Aggregate aggregateMethod, long columnIndex) { + Number results = (Number) nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); + return results; + } + + public Date aggregateDate(Aggregate aggregateMethod, long columnIndex) { + Date date = (Date) nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); + return date; } public long size() { From 4f45b50b198510bd9a4e2576e8db8cf0d9e11739 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 19:53:48 +0800 Subject: [PATCH 0298/2110] Realm should be validated always before callback --- .../src/main/java/io/realm/ProxyState.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 13cde56a29..879d027096 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -94,6 +94,9 @@ public ProxyState(E model) { private void notifyChangeListeners() { if (!listeners.isEmpty()) { for (RealmChangeListener listener : listeners) { + if (realm.sharedRealm == null || realm.sharedRealm.isClosed()) { + return; + } listener.onChange(model); } } @@ -133,6 +136,10 @@ public void setConstructionFinished() { } private void registerToRowNotifier() { + if (realm.sharedRealm == null || realm.sharedRealm.isClosed()) { + return; + } + RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; if (row.isAttached()) { rowNotifier.registerListener((UncheckedRow) row, this, new RealmChangeListener>() { @@ -146,10 +153,6 @@ public void onChange(ProxyState proxyState) { @Override public void onQueryFinished(Row row) { - if (realm.sharedRealm == null || realm.sharedRealm.isClosed()) { - return; - } - this.row = row; notifyChangeListeners(); registerToRowNotifier(); From 2a0f7c25298b05de9192c18f41bd0713f26322fb Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 19:55:12 +0800 Subject: [PATCH 0299/2110] Fix DynamicRealmTests --- .../java/io/realm/DynamicRealmTests.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index cdd8648266..4699a1a868 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -342,21 +342,20 @@ public void execute(DynamicRealm realm) { @Test public void findFirst() { + populateTestRealm(realm, 10); final DynamicRealmObject allTypes = realm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 4, 9) .findFirst(); - populateTestRealm(realm, 10); assertEquals("test data 4", allTypes.getString(AllTypes.FIELD_STRING)); } @Test @RunTestInLooperThread - public void findFirst_async() { + public void findFirstAsync() { final DynamicRealm dynamicRealm = initializeDynamicRealm(); final DynamicRealmObject allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 4, 9) - .findFirst(); - assertTrue(allTypes.realmGet$proxyState().getRow$realm() instanceof PendingRow); + .findFirstAsync(); looperThread.keepStrongReference.add(allTypes); allTypes.addChangeListener(new RealmChangeListener() { @Override @@ -370,14 +369,11 @@ public void onChange(DynamicRealmObject object) { @Test @RunTestInLooperThread - public void findAllAsync() { + public void findAll_async() { final DynamicRealm dynamicRealm = initializeDynamicRealm(); final RealmResults allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 4, 9) - .findAllAsync(); - - assertFalse(allTypes.isLoaded()); - assertEquals(0, allTypes.size()); + .findAll(); allTypes.addChangeListener(new RealmChangeListener>() { @Override @@ -395,13 +391,11 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread - public void findAllSortedAsync() { + public void findAllSorted_async() { final DynamicRealm dynamicRealm = initializeDynamicRealm(); final RealmResults allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 0, 4) - .findAllSortedAsync(AllTypes.FIELD_STRING, Sort.DESCENDING); - assertFalse(allTypes.isLoaded()); - assertEquals(0, allTypes.size()); + .findAllSorted(AllTypes.FIELD_STRING, Sort.DESCENDING); allTypes.addChangeListener(new RealmChangeListener>() { @Override From ac4e93367ad5956d3fdae37336eee61a95fee787 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 20:13:03 +0800 Subject: [PATCH 0300/2110] =?UTF-8?q?=E2=81=A0=E2=81=A0=E2=81=A0=E2=81=A0O?= =?UTF-8?q?bjectServerFacade.noCommit=20is=20not=20needed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- realm/realm-library/src/main/cpp/java_binding_context.cpp | 1 + realm/realm-library/src/main/java/io/realm/BaseRealm.java | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index a49bc9e196..640d83d3c0 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -142,6 +142,7 @@ void JavaBindingContext::did_change(std::vector c m_local_jni_env->CallVoidMethod(observer, m_row_observer_pair_on_change_method); } + if (m_local_jni_env->ExceptionCheck()) return; m_local_jni_env->CallVoidMethod(m_row_notifier, m_clear_row_refs_method); if (m_local_jni_env->ExceptionCheck()) return; diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 0b2a00af99..b866d2f3cb 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -30,7 +30,6 @@ import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.CheckedRow; import io.realm.internal.InvalidRow; -import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.SharedRealm; import io.realm.internal.ColumnInfo; @@ -41,7 +40,6 @@ import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.RealmLog; -import io.realm.internal.ObjectServerFacade; import rx.Observable; /** @@ -318,8 +316,6 @@ public void beginTransaction() { public void commitTransaction() { checkIfValid(); sharedRealm.commitTransaction(); - ObjectServerFacade.getFacade(configuration.isSyncConfiguration()) - .notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); } /** From cffbbf6dafef530486a8fc416886a444780006f1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 9 Dec 2016 20:47:03 +0800 Subject: [PATCH 0301/2110] Post to disable snapshot only once --- .../src/main/java/io/realm/internal/SharedRealm.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index c3edef25e1..3fb19aec2a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -110,6 +110,8 @@ public byte getNativeValue() { public final RowNotifier rowNotifier; public final ObjectServerFacade objectServerFacade; public final List> collections = new CopyOnWriteArrayList>(); + // To prevent overflow the message queue. + public boolean disableSnapshotPosted = false; public static class VersionID implements Comparable { public final long version; @@ -240,10 +242,12 @@ public void beginTransaction() { public void commitTransaction() { nativeCommitTransaction(nativePtr); - if (realmNotifier != null && !collections.isEmpty()) { + if (realmNotifier != null && !collections.isEmpty() && !disableSnapshotPosted) { + disableSnapshotPosted = true; realmNotifier.postAtFrontOfQueue(new Runnable() { @Override public void run() { + disableSnapshotPosted = false; disableCollectionSnapshot(); } }); From 490828443fdce214b0408ac1833085873e37805d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 12 Dec 2016 15:22:47 +0800 Subject: [PATCH 0302/2110] Generalize the ObserverPairList Since RowNotifier, CollectioNotifier and RealmNotifier have the similar requirements -- control the life cycle of the anonymous listener, create a ObserverPairList class for it. Also tests for the ObserverPairList. Then we can remove weak ref related tests from others. --- .../java/io/realm/NotificationsTest.java | 87 +----- .../java/io/realm/RealmAsyncQueryTests.java | 34 --- .../java/io/realm/RealmObjectTests.java | 57 ---- .../io/realm/TypeBasedNotificationsTests.java | 40 --- .../realm/internal/ObserverPairListTests.java | 270 ++++++++++++++++++ .../src/main/java/io/realm/ProxyState.java | 9 +- .../java/io/realm/internal/Collection.java | 41 ++- .../java/io/realm/internal/ObserverPair.java | 27 -- .../io/realm/internal/ObserverPairList.java | 128 +++++++++ .../java/io/realm/internal/RealmNotifier.java | 30 +- .../java/io/realm/internal/RowNotifier.java | 40 +-- 11 files changed, 450 insertions(+), 313 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 9f8bf31ad2..315fdfbd64 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -33,7 +33,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.lang.ref.WeakReference; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -444,91 +443,6 @@ public void onChange(Realm object) { realm.commitTransaction(); } - @Test - @RunTestInLooperThread - public void weakReferenceListener() throws InterruptedException { -/* final AtomicInteger weakCounter = new AtomicInteger(0); - final AtomicInteger strongCounter = new AtomicInteger(0); - - final Realm realm = looperThread.realm; - - // Setup weak listener - RealmChangeListener weakListener = new RealmChangeListener() { - @Override - public void onChange(Realm object) { - weakCounter.incrementAndGet(); - } - }; - realm.handlerController.addChangeListenerAsWeakReference(weakListener); - assertEquals(1, realm.handlerController.weakChangeListeners.size()); - - // This is not a weak listener so will be called. When this is triggered the weak references have not been - // removed yet. So make another change to ensure that they really are removed before validating. - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - int count = strongCounter.incrementAndGet(); - if (count == 1) { - realm.beginTransaction(); - realm.createObject(AllTypes.class); - realm.commitTransaction(); - } else if (count == 2) { - assertEquals(0, weakCounter.get()); - assertEquals(0, realm.handlerController.weakChangeListeners.size()); - looperThread.testComplete(); - } - } - }); - - // Hack: There is no guaranteed way to release the WeakReference, just clear it. - for (WeakReference> weakRef : realm.handlerController.weakChangeListeners) { - weakRef.clear(); - } - - // Trigger change listeners - realm.beginTransaction(); - realm.createObject(AllTypes.class); - realm.commitTransaction();*/ - } - - - // Test that that a WeakReferenceListener can be removed. - // This test is not a proper GC test, but just ensures that listeners can be removed from the list of weak listeners - // without throwing an exception. - @Test - @RunTestInLooperThread - public void removingWeakReferenceListener() throws InterruptedException { -/* final AtomicInteger counter = new AtomicInteger(0); - final Realm realm = looperThread.realm; - RealmChangeListener listenerA = new RealmChangeListener() { - @Override - public void onChange(Realm object) { - counter.incrementAndGet(); - } - }; - RealmChangeListener listenerB = new RealmChangeListener() { - @Override - public void onChange(Realm object) { - assertEquals(0, counter.get()); - assertEquals(1, realm.handlerController.weakChangeListeners.size()); - looperThread.testComplete(); - } - }; - realm.handlerController.addChangeListenerAsWeakReference(listenerA); - - // There is no guaranteed way to release the WeakReference, - // just clear it. - for (WeakReference> weakRef : realm.handlerController.weakChangeListeners) { - weakRef.clear(); - } - - realm.handlerController.addChangeListenerAsWeakReference(listenerB); - - realm.beginTransaction(); - realm.createObject(AllTypes.class); - realm.commitTransaction();*/ - } - @Test @RunTestInLooperThread public void realmNotificationOrder() { @@ -916,6 +830,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread + @Ignore public void realmObjectListenerAddedAfterCommit() { Realm realm = looperThread.realm; realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 10d7bdd193..d0c8556451 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -1428,40 +1428,6 @@ public void onChange(RealmResults object) { }); } - // make sure the notification listener does not leak the enclosing class - // if unregistered properly. - @Test - @RunTestInLooperThread - public void listenerShouldNotLeak() { - populateTestRealm(looperThread.realm, 10); - - // simulate the ActivityManager by creating 1 instance responsible - // of attaching an onChange listener, then simulate a configuration - // change (ex: screen rotation), this change will create a new instance. - // we make sure that the GC enqueue the reference of the destroyed instance - // which indicate no memory leak - MockActivityManager mockActivityManager = - MockActivityManager.newInstance(looperThread.realm.getConfiguration()); - - mockActivityManager.sendConfigurationChange(); - - assertEquals(1, mockActivityManager.numberOfInstances()); - // remove GC'd reference & assert that one instance should remain - Iterator>, RealmQuery>> iterator = - looperThread.realm.handlerController.asyncRealmResults.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry>, RealmQuery> entry = iterator.next(); - RealmResults weakReference = entry.getKey().get(); - if (weakReference == null) { - iterator.remove(); - } - } - - assertEquals(1, looperThread.realm.handlerController.asyncRealmResults.size()); - mockActivityManager.onStop();// to close the Realm - looperThread.testComplete(); - } - @Test @RunTestInLooperThread public void combiningAsyncAndSync() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index b6243cfb33..25c728b67a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -28,7 +28,6 @@ import org.junit.runner.RunWith; import java.io.FileNotFoundException; -import java.lang.ref.WeakReference; import java.util.Calendar; import java.util.Date; import java.util.concurrent.Callable; @@ -1752,62 +1751,6 @@ public void onChange(AllTypesPrimaryKey element) { }); } - // The object should be added to HandlerController.realmObjects only when the first time addListener called. - @Test - @UiThreadTest - public void addChangeListener_shouldAddTheObjectToHandlerRealmObjects() { -/* realm.beginTransaction(); - AllTypesPrimaryKey allTypesPrimaryKey = realm.createObject(AllTypesPrimaryKey.class, 1); - realm.commitTransaction(); - final ConcurrentHashMap, Object> realmObjects = - realm.handlerController.realmObjects; - - assertTrue(realmObjects.isEmpty()); - - allTypesPrimaryKey.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(AllTypesPrimaryKey element) { - } - }); - - assertEquals(1, realmObjects.size()); - for (WeakReference ref : realmObjects.keySet()) { - assertTrue(ref.get() == allTypesPrimaryKey); - }*/ - } - - // The object should be added to HandlerController.realmObjects only once. - @Test - @UiThreadTest - public void addChangeListener_shouldNotAddDupEntriesToHandlerRealmObjects() { -/* realm.beginTransaction(); - AllTypesPrimaryKey allTypesPrimaryKey = realm.createObject(AllTypesPrimaryKey.class, 1); - realm.commitTransaction(); - final ConcurrentHashMap, Object> realmObjects = - realm.handlerController.realmObjects; - - for (WeakReference ref : realmObjects.keySet()) { - assertFalse(ref.get() == allTypesPrimaryKey); - } - - // Add different listeners twice - allTypesPrimaryKey.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(AllTypesPrimaryKey element) { - } - }); - allTypesPrimaryKey.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(AllTypesPrimaryKey element) { - } - }); - - assertEquals(1, realmObjects.size()); - for (WeakReference ref : realmObjects.keySet()) { - assertTrue(ref.get() == allTypesPrimaryKey); - }*/ - } - // The object should not be added to HandlerController again after the async query loaded. @Test @RunTestInLooperThread diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index cb9410d285..db0404ec7e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -32,7 +32,6 @@ import java.io.IOException; import java.io.InputStream; -import java.lang.ref.WeakReference; import java.util.Date; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -1427,45 +1426,6 @@ public void onChange(RealmResults object) { TestHelper.awaitOrFail(signalTestFinished); } - // Test modifying syncRealmResults in RealmResults's change listener - @Test - @RunTestInLooperThread - public void change_realm_results_map_in_listener() throws InterruptedException { -/* final CountDownLatch finishedLatch = new CountDownLatch(2); - - final Realm realm = looperThread.realm; - // Two results needed to make sure list modification happen while iterating - RealmResults results1 = realm.where(Owner.class).findAll(); - RealmResults results2 = realm.where(Cat.class).findAll(); - RealmChangeListener listener = new RealmChangeListener() { - @Override - public void onChange(Object object) { - RealmResults results = realm.where(Owner.class).findAll(); - boolean foundKey = false; - // Check if the results has been added to the syncRealmResults in case of the behaviour of - // allObjects changes - for (WeakReference> weakReference : - realm.handlerController.syncRealmResults.keySet()) { - if (weakReference.get() == results) { - foundKey = true; - break; - } - } - assertTrue(foundKey); - looperThread.testComplete(); - finishedLatch.countDown(); - } - }; - looperThread.keepStrongReference.add(results1); - looperThread.keepStrongReference.add(results2); - results1.addChangeListener(listener); - results2.addChangeListener(listener); - - realm.beginTransaction(); - realm.createObject(Owner.class); - realm.commitTransaction();*/ - } - // Build a RealmResults from a RealmList, and delete the RealmList. Test the behavior of ChangeListener on the // "invalid" RealmResults. @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java new file mode 100644 index 0000000000..5345ec8973 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java @@ -0,0 +1,270 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.atomic.AtomicInteger; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class ObserverPairListTests { + + private static class TestListener { + void onChange(Integer integer) { + } + } + + private static class TestObserverPair extends ObserverPairList.ObserverPair { + public TestObserverPair(Integer observer, TestListener listener) { + super(observer, listener); + } + } + + private ObserverPairList observerPairs; + TestListener testListener = new TestListener(); + + private static final Integer ONE = 1; + private static final Integer TWO = 2; + private static final Integer THREE = 3; + + @Before + public void setUp() { + observerPairs = new ObserverPairList(); + } + + @After + public void tearDown() { + observerPairs = null; + } + + @Test + public void add() { + TestObserverPair pair = new TestObserverPair(ONE, testListener); + observerPairs.add(pair); + assertEquals(1, observerPairs.size()); + + // Same observer object, different listener. + pair = new TestObserverPair(ONE, new TestListener()); + observerPairs.add(pair); + assertEquals(2, observerPairs.size()); + + // Different observer object, different listener. + pair = new TestObserverPair(TWO, new TestListener()); + observerPairs.add(pair); + assertEquals(3, observerPairs.size()); + + // Different observer object, same listener. + pair = new TestObserverPair(TWO, testListener); + observerPairs.add(pair); + assertEquals(4, observerPairs.size()); + } + + @Test + // The Observer pair is treated as the same when the observer is the same object and the listener is the same too. + public void add_noDuplicate() { + TestObserverPair pair = new TestObserverPair(ONE, testListener); + observerPairs.add(pair); + assertEquals(1, observerPairs.size()); + + pair = new TestObserverPair(ONE, testListener); + observerPairs.add(pair); + assertEquals(1, observerPairs.size()); + } + + @Test + public void remove() { + TestObserverPair pair = new TestObserverPair(ONE, testListener); + observerPairs.add(pair); + assertEquals(1, observerPairs.size()); + + // Create a new Integer 1 to see if the equality is checked by the same object. + //noinspection UnnecessaryBoxing + pair = new TestObserverPair(new Integer(1), testListener); + observerPairs.remove(pair); + assertEquals(1, observerPairs.size()); + + // Different listener + pair = new TestObserverPair(ONE, new TestListener()); + observerPairs.remove(pair); + assertEquals(1, observerPairs.size()); + + // Should remove now + pair = new TestObserverPair(ONE, testListener); + observerPairs.remove(pair); + assertEquals(0, observerPairs.size()); + } + + @Test + public void clear() { + TestObserverPair pair = new TestObserverPair(ONE, new TestListener()); + observerPairs.add(pair); + assertEquals(1, observerPairs.size()); + observerPairs.clear(); + assertEquals(0, observerPairs.size()); + } + + @Test + public void isEmpty() { + assertTrue(observerPairs.isEmpty()); + TestObserverPair pair = new TestObserverPair(ONE, new TestListener()); + observerPairs.add(pair); + assertFalse(observerPairs.isEmpty()); + observerPairs.clear(); + assertTrue(observerPairs.isEmpty()); + } + + @Test + public void foreach() { + final boolean[] onChangesCalled = {false, false}; + TestListener listener = new TestListener() { + @Override + void onChange(Integer i) { + onChangesCalled[i-1] = true; + } + }; + + TestObserverPair pair = new TestObserverPair(ONE, listener); + observerPairs.add(pair); + pair = new TestObserverPair(TWO, listener); + observerPairs.add(pair); + observerPairs.foreach(new ObserverPairList.Callback() { + @Override + public void onCalled(TestObserverPair pair, Object observer) { + //noinspection unchecked + pair.listener.onChange(observer); + } + }); + assertTrue(onChangesCalled[0] && onChangesCalled[1]); + } + + // Test if the observer is GCed, the relevant listener should be removed when foreach called. + @Test + public void foreach_shouldRemoveWeakRefs() { + TestObserverPair pair = new TestObserverPair(ONE, new TestListener()); + observerPairs.add(pair); + assertEquals(1, observerPairs.size()); + observerPairs.foreach(new ObserverPairList.Callback() { + @Override + public void onCalled(TestObserverPair pair, Object observer) { + // There is no guaranteed way to release the WeakReference, + // just clear it. + pair.observerRef.clear(); + } + }); + assertEquals(1, observerPairs.size()); + + observerPairs.foreach(new ObserverPairList.Callback() { + @Override + public void onCalled(TestObserverPair pair, Object observer) { + fail(); + } + }); + assertEquals(0, observerPairs.size()); + } + + @Test + public void foreach_canRemove() { + final AtomicInteger count = new AtomicInteger(0); + final TestObserverPair pair1 = new TestObserverPair(ONE, new TestListener()); + final TestObserverPair pair2 = new TestObserverPair(TWO, new TestListener()); + final TestObserverPair pair3 = new TestObserverPair(THREE, new TestListener()); + observerPairs.add(pair1); + observerPairs.add(pair2); + observerPairs.add(pair3); + assertEquals(3, observerPairs.size()); + observerPairs.foreach(new ObserverPairList.Callback() { + @Override + public void onCalled(TestObserverPair pair, Object observer) { + assertFalse(((Integer) observer) == 2); + observerPairs.remove(pair2); + count.getAndIncrement(); + } + }); + assertEquals(2, observerPairs.size()); + assertEquals(2, count.get()); + } + + @Test + public void foreach_canClear() { + final AtomicInteger count = new AtomicInteger(0); + final TestObserverPair pair1 = new TestObserverPair(ONE, new TestListener()); + final TestObserverPair pair2 = new TestObserverPair(TWO, new TestListener()); + final TestObserverPair pair3 = new TestObserverPair(THREE, new TestListener()); + observerPairs.add(pair1); + observerPairs.add(pair2); + observerPairs.add(pair3); + assertEquals(3, observerPairs.size()); + observerPairs.foreach(new ObserverPairList.Callback() { + @Override + public void onCalled(TestObserverPair pair, Object observer) { + assertFalse(((Integer) observer) == 2); + assertFalse(((Integer) observer) == 3); + observerPairs.clear(); + count.getAndIncrement(); + } + }); + assertEquals(0, observerPairs.size()); + assertEquals(1, count.get()); + + observerPairs.add(pair1); + assertEquals(1, observerPairs.size()); + observerPairs.foreach(new ObserverPairList.Callback() { + @Override + public void onCalled(TestObserverPair pair, Object observer) { + assertTrue(((Integer) observer) == 1); + } + }); + } + + @Test + public void foreach_canAdd() { + final AtomicInteger count = new AtomicInteger(0); + final TestObserverPair pair1 = new TestObserverPair(ONE, new TestListener()); + final TestObserverPair pair2 = new TestObserverPair(TWO, new TestListener()); + observerPairs.add(pair1); + assertEquals(1, observerPairs.size()); + observerPairs.foreach(new ObserverPairList.Callback() { + @Override + public void onCalled(TestObserverPair pair, Object observer) { + observerPairs.add(pair2); + count.getAndIncrement(); + } + }); + assertEquals(2, observerPairs.size()); + assertEquals(1, count.get()); + + count.set(0); + assertEquals(2, observerPairs.size()); + observerPairs.foreach(new ObserverPairList.Callback() { + @Override + public void onCalled(TestObserverPair pair, Object observer) { + count.getAndIncrement(); + } + }); + assertEquals(2, count.get()); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 879d027096..d024fd00f1 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -108,14 +108,7 @@ public void addChangeListener(RealmChangeListener listener) { } // this might be called after query returns. So it is still necessary to register. if (row instanceof UncheckedRow) { - RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; - // RowNotifier will take care of the duplicated ObserverPairs - rowNotifier.registerListener((UncheckedRow) row, this, new RealmChangeListener>() { - @Override - public void onChange(ProxyState proxyState) { - proxyState.notifyChangeListeners(); - } - }); + registerToRowNotifier(); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 7415f3c0fd..9529d2ca32 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -17,8 +17,6 @@ package io.realm.internal; import java.util.Date; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import io.realm.RealmChangeListener; @@ -29,16 +27,13 @@ @KeepMember public final class Collection implements NativeObject { - private class CollectionObserverPair extends ObserverPair>{ + private class CollectionObserverPair extends ObserverPairList.ObserverPair> { public CollectionObserverPair(T observer, RealmChangeListener listener) { super(observer, listener); } - public void onChange() { - T observer = observerRef.get(); - if (observer != null) { - listener.onChange(observerRef.get()); - } + public void onChange(T observer) { + listener.onChange(observer); } } @@ -47,7 +42,16 @@ public void onChange() { private final SharedRealm sharedRealm; private final Context context; private final TableQuery query; - private final List observerPairs = new CopyOnWriteArrayList(); + private final ObserverPairList observerPairs = + new ObserverPairList(); + private static final ObserverPairList.Callback onChangeCallback = + new ObserverPairList.Callback() { + @Override + public void onCalled(CollectionObserverPair pair, Object observer) { + //noinspection unchecked + pair.onChange(observer); + } + }; // Public for static checking in JNI @SuppressWarnings("WeakerAccess") @@ -143,13 +147,11 @@ public TableQuery where() { } public Number aggregateNumber(Aggregate aggregateMethod, long columnIndex) { - Number results = (Number) nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); - return results; + return (Number) nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); } public Date aggregateDate(Aggregate aggregateMethod, long columnIndex) { - Date date = (Date) nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); - return date; + return (Date) nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); } public long size() { @@ -195,9 +197,7 @@ public void addListener(T observer, RealmChangeListener listener) { nativeStartListening(nativePtr); } CollectionObserverPair collectionObserverPair = new CollectionObserverPair(observer, listener); - if (!observerPairs.contains(collectionObserverPair)) { - observerPairs.add(collectionObserverPair); - } + observerPairs.add(collectionObserverPair); } public void removeListener(T observer, RealmChangeListener listener) { @@ -222,14 +222,7 @@ private void notifyChangeListeners() { // by OS Realm::notify(). this.disableSnapshot(); - for (CollectionObserverPair pair: observerPairs) { - Object object = pair.observerRef.get(); - if (object != null) { - pair.onChange(); - } else { - observerPairs.remove(pair); - } - } + observerPairs.foreach(onChangeCallback); } void enableSnapshot() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java deleted file mode 100644 index 708213900b..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPair.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.realm.internal; - -import java.lang.ref.WeakReference; - -public abstract class ObserverPair { - public final WeakReference observerRef; - public final S listener; - - public ObserverPair(T observer, S listener) { - this.listener = listener; - this.observerRef = new WeakReference(observer); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj instanceof ObserverPair) { - ObserverPair anotherPair = (ObserverPair) obj; - return listener.equals(anotherPair.listener) && - observerRef.get() == anotherPair.observerRef.get(); - } - return false; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java new file mode 100644 index 0000000000..b457024cd5 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java @@ -0,0 +1,128 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + + +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * An ObserverPairList holds a list of ObserverPairs. An {@link ObserverPair} is pair contains an observer and a + * listener. The observer is the object to react to the changes through the listener. The observer is saved as an weak + * reference in the pair to control the life cycle of the listener. When the observer gets GCed, the corresponding pair + * will be removed from the list. So DO NOT keep a strong reference to the observer in the subclass of listener since it + * will cause leaks! + *

          + * This class is not thread safe and it is not supposed to be. + * + * @param the type of {@link ObserverPair}. + */ +public class ObserverPairList { + + /** + * @param the type of observer. + * @param the type of listener. + */ + public abstract static class ObserverPair { + protected final WeakReference observerRef; + protected final S listener; + // Should only be set by the outer class. To marked it as removed in case it is removed in foreach callback. + boolean removed = false; + + public ObserverPair(T observer, S listener) { + this.listener = listener; + this.observerRef = new WeakReference(observer); + } + + // The two pairs will be treated as the same only when the observers are the same and the listeners are the same + // as well. + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof ObserverPair) { + ObserverPair anotherPair = (ObserverPair) obj; + return listener.equals(anotherPair.listener) && + observerRef.get() == anotherPair.observerRef.get(); + } + return false; + } + } + + /** + * Callback passed to the {@link #foreach(Callback)} call. + * + * @param type of ObserverPair. + */ + interface Callback { + void onCalled(T pair, Object observer); + } + + private List pairs = new CopyOnWriteArrayList(); + // In case the clear() called during the foreach loop. + private boolean cleared = false; + + /** + * Iterate every valid pair in the list and call the callback on it. The pair with GCed observer will be removed and + * callback won't be executed. Before executing the callback, a strong reference to the observer will be kept and + * passed to the callback in case the observer gets GCed before callback returns. + * + * @param callback to be executed on the pair. + */ + public void foreach(Callback callback) { + for (T pair : pairs) { + Object observer = pair.observerRef.get(); + if (observer == null) { + pairs.remove(pair); + } else if (cleared) { + break; + } else if (!pair.removed) { + callback.onCalled(pair, observer); + } + } + } + + public boolean isEmpty() { + return pairs.isEmpty(); + } + + public void clear() { + cleared = true; + pairs.clear(); + } + + public void add(T pair) { + if (!pairs.contains(pair)) { + pairs.add(pair); + } + if (!cleared) { + cleared = false; + } + } + + public void remove(T pair) { + pair.removed = true; + pairs.remove(pair); + } + + public int size() { + return pairs.size(); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 4f7a0a4a84..1846779a05 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -17,8 +17,6 @@ package io.realm.internal; import java.io.Closeable; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import io.realm.RealmChangeListener; @@ -31,20 +29,27 @@ public abstract class RealmNotifier implements Closeable { private SharedRealm sharedRealm; - private static class RealmObserverPair extends ObserverPair> { + private static class RealmObserverPair extends ObserverPairList.ObserverPair> { public RealmObserverPair(T observer, RealmChangeListener listener) { super(observer, listener); } - private void onChange() { - T observer = observerRef.get(); + private void onChange(T observer) { if (observer != null) { listener.onChange(observer); } } } - private List realmObserverPairs = new CopyOnWriteArrayList(); + private ObserverPairList realmObserverPairs = new ObserverPairList(); + private final static ObserverPairList.Callback onChangeCallBack = + new ObserverPairList.Callback() { + @Override + public void onCalled(RealmObserverPair pair, Object observer) { + //noinspection unchecked + pair.onChange(observer); + } + }; // This is called by OS when other thread/process changes the Realm. // This is getting called on the same thread which created the Realm. @@ -64,14 +69,7 @@ private void onChange() { */ @SuppressWarnings("unused") // called from java_binding_context.cpp protected void didChange() { - for (RealmObserverPair observerPair : realmObserverPairs) { - Object observer = observerPair.observerRef.get(); - if (observer == null) { - realmObserverPairs.remove(observerPair); - } else { - observerPair.onChange(); - } - } + realmObserverPairs.foreach(onChangeCallBack); } @SuppressWarnings("unused") // called from java_binding_context.cpp @@ -93,9 +91,7 @@ public void close() { public void addChangeListener(T observer, RealmChangeListener realmChangeListener) { RealmObserverPair observerPair = new RealmObserverPair(observer, realmChangeListener); - if (!realmObserverPairs.contains(observerPair)) { - realmObserverPairs.add(observerPair); - } + realmObserverPairs.add(observerPair); } public void removeChangeListener(E observer, RealmChangeListener realmChangeListener) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java index 5e1db36f5e..f250726710 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java @@ -19,7 +19,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import io.realm.RealmChangeListener; @@ -31,7 +30,7 @@ @Keep public class RowNotifier { @Keep - private static class RowObserverPair extends ObserverPair> { + private static class RowObserverPair extends ObserverPairList.ObserverPair> { final WeakReference rowRef; // Keep a strong ref to row when getRowRefs called and set it to null in clearRowRefs. // This is to avoid the row gets GCed in between. @@ -56,7 +55,7 @@ public boolean equals(Object obj) { return true; } - if (obj instanceof ObserverPair) { + if (obj instanceof ObserverPairList.ObserverPair) { RowObserverPair anotherPair = (RowObserverPair) obj; return listener.equals(anotherPair.listener) && observerRef.get() == anotherPair.observerRef.get() && @@ -70,7 +69,15 @@ public boolean equals(Object obj) { // Row objects point to the same row in the same table. The duplicated rows will all get notifications but there are // overheads when duplicated rows added since they all need to be processed to compute the differences for the row // level fine grained notifications in the object store. - private CopyOnWriteArrayList rowObserverPairs = new CopyOnWriteArrayList(); + //private CopyOnWriteArrayList rowObserverPairs = new CopyOnWriteArrayList(); + private ObserverPairList rowObserverPairs = new ObserverPairList(); + private static final ObserverPairList.Callback toClearRowCallback = + new ObserverPairList.Callback() { + @Override + public void onCalled(RowObserverPair pair, Object observer) { + pair.row = null; + } + }; /** * Register a listener on a row. @@ -82,9 +89,7 @@ public boolean equals(Object obj) { */ public void registerListener(UncheckedRow row, T observer, RealmChangeListener listener) { RowObserverPair rowObserverPair = new RowObserverPair(row, observer, listener); - if (!rowObserverPairs.contains(rowObserverPair)) { - rowObserverPairs.add(rowObserverPair); - } + rowObserverPairs.add(rowObserverPair); } @@ -98,19 +103,16 @@ public void registerListener(UncheckedRow row, T observer, RealmChangeListen // Called by JNI @SuppressWarnings("unused") private RowObserverPair[] getObservers() { - List pairList = new ArrayList(rowObserverPairs.size()); - for (RowObserverPair pair : rowObserverPairs) { - // FIXME: Anyone could tell me why wo we have to cast it here? - UncheckedRow uncheckedRow = (UncheckedRow) pair.rowRef.get(); - if (pair.observerRef.get() == null || uncheckedRow == null || !uncheckedRow.isAttached()) { - // The observer object or the row get GCed. Remove it. - rowObserverPairs.remove(pair); - } else { + final List pairList = new ArrayList(rowObserverPairs.size()); + rowObserverPairs.foreach(new ObserverPairList.Callback() { + @Override + public void onCalled(RowObserverPair pair, Object observer) { + // TODO: Anyone knows why do we need to cast it here? // Keep a strong ref of the row! in case it gets GCed before clearRowRefs! - pair.row = uncheckedRow; + pair.row = (UncheckedRow) pair.rowRef.get(); pairList.add(pair); } - } + }); return pairList.toArray(new RowObserverPair[pairList.size()]); } @@ -127,8 +129,6 @@ private long[] getObservedRowPtrs(RowObserverPair[] observerPairs) { // Called by JNI @SuppressWarnings("unused") private void clearRowRefs() { - for (RowObserverPair observerPair: rowObserverPairs) { - observerPair.row = null; - } + rowObserverPairs.foreach(toClearRowCallback); } } From 7b911c0c539fe3c264ba4cf2d7b9d5d95860c6ba Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 13 Dec 2016 19:48:13 +0800 Subject: [PATCH 0303/2110] Add tests for row notifications The tests cannot be passed now. We are waiting for the new implementation of object level notifications from OS. --- .../io/realm/internal/CollectionTests.java | 2 +- .../io/realm/internal/RowNotifierTests.java | 160 ++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 1f7a49376d..e162fbdcb5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -50,7 +50,7 @@ public class CollectionTests { @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); - RealmConfiguration config; + private RealmConfiguration config; private SharedRealm sharedRealm; private Table table; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java new file mode 100644 index 0000000000..fc93fc092a --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java @@ -0,0 +1,160 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; + +import io.realm.RealmChangeListener; +import io.realm.RealmConfiguration; +import io.realm.RealmFieldType; +import io.realm.TestHelper; +import io.realm.internal.android.AndroidRealmNotifier; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class RowNotifierTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + private RealmConfiguration config; + private SharedRealm sharedRealm; + private Table table; + private final static String TABLE_NAME = "test_table"; + private final static long STRING_COLUMN_INDEX = 0; + + @Before + public void setUp() { + config = configFactory.createConfiguration(); + sharedRealm = getSharedRealm(); + populateData(); + } + + @After + public void tearDown() { + sharedRealm.close(); + } + + private SharedRealm getSharedRealm() { + return SharedRealm.getInstance(config, new AndroidRealmNotifier(), null); + } + + private void populateData() { + sharedRealm.beginTransaction(); + table = sharedRealm.getTable(TABLE_NAME); + // Specify the column types and names + assertEquals(STRING_COLUMN_INDEX, table.addColumn(RealmFieldType.STRING, "string")); + table.addEmptyRow(); + sharedRealm.commitTransaction(); + } + + private void changeRowAsync() { + final CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + SharedRealm sharedRealm = getSharedRealm(); + changeRow(sharedRealm); + sharedRealm.close(); + latch.countDown(); + } + }).start(); + TestHelper.awaitOrFail(latch); + } + + private void changeRow(SharedRealm sharedRealm) { + sharedRealm.beginTransaction(); + table = sharedRealm.getTable(TABLE_NAME); + UncheckedRow row = table.getUncheckedRow(0); + row.setString(STRING_COLUMN_INDEX, "changed"); + sharedRealm.commitTransaction(); + } + + @Test + @RunTestInLooperThread + public void listener_triggeredByRemoteCommit() { + SharedRealm sharedRealm = getSharedRealm(); + Table table = sharedRealm.getTable(TABLE_NAME); + UncheckedRow row = table.getUncheckedRow(0); + looperThread.keepStrongReference.add(row); + sharedRealm.rowNotifier.registerListener(row, row, new RealmChangeListener() { + @Override + public void onChange(UncheckedRow row) { + assertEquals("changed", row.getString(STRING_COLUMN_INDEX)); + looperThread.testComplete(); + } + }); + + changeRowAsync(); + } + + @Test + @RunTestInLooperThread + public void listener_triggeredByLocalCommit() { + SharedRealm sharedRealm = getSharedRealm(); + Table table = sharedRealm.getTable(TABLE_NAME); + UncheckedRow row = table.getUncheckedRow(0); + looperThread.keepStrongReference.add(row); + sharedRealm.rowNotifier.registerListener(row, row, new RealmChangeListener() { + @Override + public void onChange(UncheckedRow row) { + String testString = row.getString(STRING_COLUMN_INDEX); + //assertEquals("changed", row.getString(STRING_COLUMN_INDEX)); + //looperThread.testComplete(); + } + }); + + changeRow(sharedRealm); + } + + @Test + @RunTestInLooperThread + public void listener_triggeredByLocalTransactionBegin() { + SharedRealm sharedRealm = getSharedRealm(); + Table table = sharedRealm.getTable(TABLE_NAME); + + changeRow(sharedRealm); + + UncheckedRow row = table.getUncheckedRow(0); + looperThread.keepStrongReference.add(row); + sharedRealm.rowNotifier.registerListener(row, row, new RealmChangeListener() { + @Override + public void onChange(UncheckedRow row) { + assertEquals("changed", row.getString(STRING_COLUMN_INDEX)); + looperThread.testComplete(); + } + }); + + sharedRealm.beginTransaction(); + } +} From b810f01bc3b518fef7e176dae80ea78ec8f3d530 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Wed, 14 Dec 2016 16:12:43 +0100 Subject: [PATCH 0304/2110] Zeroing memory before using it (#3898) * Zeroing memory * Additional micro benchmarks --- .../benchmarks/RealmObjectWriteBenchmarks.java | 16 +++++++++++++++- realm/realm-library/src/main/cpp/util.cpp | 5 ++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java index ff8be605c7..68719a222f 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java @@ -54,12 +54,26 @@ public void after() { } @Benchmark - public void writeString(long reps) { + public void writeShortString(long reps) { for (long i = 0; i < reps; i++) { writeObject.setColumnString("Foo"); } } + @Benchmark + public void writeMediumString(long reps) { + for (long i = 0; i < reps; i++) { + writeObject.setColumnString("ABCDEFHIJKLMNOPQ"); + } + } + + @Benchmark + public void writeLongString(long reps) { + for (long i = 0; i < reps; i++) { + writeObject.setColumnString("ABCDEFHIJKLMNOPQABCDEFHIJKLMNOPQABCDEFHIJKLMNOPQABCDEFHIJKLMNOPQ"); + } + } + @Benchmark public void writeLong(long reps) { for (long i = 0; i < reps; i++) { diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 99d917c817..570bfa9185 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -405,7 +405,8 @@ JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) size_t error_code; buf_size = Xcode::find_utf8_buf_size(begin, end, error_code); } - m_data.reset(new char[buf_size]); // throws + char* tmp_char_array = new char[buf_size]; // throws + m_data.reset(tmp_char_array); { const jchar* in_begin = chars.data(); const jchar* in_end = in_begin + chars.size(); @@ -419,6 +420,8 @@ JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) throw invalid_argument(string_to_hex("in_begin != in_end when converting to UTF-8", chars.data(), chars.size(), error_code)); } m_size = out_begin - m_data.get(); + // FIXME: Does this help on string issues? Or does it only help lldb? + std::memset(tmp_char_array + m_size, 0, buf_size - m_size); } } From 8f3842edb553e9b49a586fbe3f7104b72b92d8b3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 15 Dec 2016 11:18:46 +0100 Subject: [PATCH 0305/2110] Remove final modifier from all major classes. (#3911) --- CHANGELOG.md | 8 ++++++++ .../src/main/java/io/realm/DynamicRealm.java | 2 +- .../src/main/java/io/realm/DynamicRealmObject.java | 2 +- realm/realm-library/src/main/java/io/realm/Realm.java | 2 +- realm/realm-library/src/main/java/io/realm/RealmList.java | 2 +- .../src/main/java/io/realm/RealmObjectSchema.java | 2 +- .../realm-library/src/main/java/io/realm/RealmQuery.java | 2 +- .../src/main/java/io/realm/RealmResults.java | 2 +- .../realm-library/src/main/java/io/realm/RealmSchema.java | 2 +- .../src/objectServer/java/io/realm/SyncConfiguration.java | 2 +- .../src/objectServer/java/io/realm/SyncManager.java | 2 +- .../src/objectServer/java/io/realm/SyncSession.java | 2 +- 12 files changed, 19 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b50dc3904..58be2afe4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.2.2 + +### Enhancements + +* All major public classes are now non-final. This is mostly a compromise to + support Mockito. All protected fields/methods are still not considered part of + the public API and can change without notice (#3869). + ## 2.2.1 ### Object Server API Changes (In Beta) diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 9b529e0f24..6df8fea533 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -44,7 +44,7 @@ * @see Realm * @see RealmSchema */ -public final class DynamicRealm extends BaseRealm { +public class DynamicRealm extends BaseRealm { private DynamicRealm(RealmConfiguration configuration) { super(configuration); diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 95c4ae2014..619b92ca60 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -33,7 +33,7 @@ * Using a DynamicRealmObject is slower than using the regular RealmObject class. */ @SuppressWarnings("WeakerAccess") -public final class DynamicRealmObject extends RealmObject implements RealmObjectProxy { +public class DynamicRealmObject extends RealmObject implements RealmObjectProxy { private final ProxyState proxyState = new ProxyState(this); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 506266aa72..f74055d402 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -122,7 +122,7 @@ * @see ACID * @see Examples using Realm */ -public final class Realm extends BaseRealm { +public class Realm extends BaseRealm { public static final String DEFAULT_REALM_NAME = RealmConfiguration.DEFAULT_REALM_NAME; diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 3b0f54afcd..1fcb3fedef 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -49,7 +49,7 @@ * @param the class of objects in list. */ -public final class RealmList extends AbstractList implements OrderedRealmCollection { +public class RealmList extends AbstractList implements OrderedRealmCollection { private static final String ONLY_IN_MANAGED_MODE_MESSAGE = "This method is only available in managed mode"; private static final String NULL_OBJECTS_NOT_ALLOWED_MESSAGE = "RealmList does not accept null values"; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index febc7e89db..e4d8115237 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -34,7 +34,7 @@ * * @see io.realm.RealmMigration */ -public final class RealmObjectSchema { +public class RealmObjectSchema { private static final Map, FieldMetaData> SUPPORTED_SIMPLE_FIELDS; static { diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 566a050d44..b9f6cc31c5 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -59,7 +59,7 @@ * @see Realm#where(Class) * @see RealmResults#where() */ -public final class RealmQuery { +public class RealmQuery { private BaseRealm realm; private Class clazz; diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 7bc4df26c5..6311d45e47 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -68,7 +68,7 @@ * @see RealmQuery#findAll() * @see io.realm.Realm#executeTransaction(Realm.Transaction) */ -public final class RealmResults extends AbstractList implements OrderedRealmCollection { +public class RealmResults extends AbstractList implements OrderedRealmCollection { private final static String NOT_SUPPORTED_MESSAGE = "This method is not supported by RealmResults."; diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 99264bf6b1..7713b75854 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -35,7 +35,7 @@ * * @see io.realm.RealmMigration */ -public final class RealmSchema { +public class RealmSchema { private static final String TABLE_PREFIX = Table.TABLE_PREFIX; private static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 2c2178fec2..bc630cf99b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -71,7 +71,7 @@ * {@link Realm#getDefaultInstance()} like ordinary unsynchronized Realms. */ @Beta -public final class SyncConfiguration extends RealmConfiguration { +public class SyncConfiguration extends RealmConfiguration { public static final int PORT_REALM = 80; public static final int PORT_REALMS = 443; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 59341b4fb7..0fd80867e4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -43,7 +43,7 @@ */ @Keep @Beta -public final class SyncManager { +public class SyncManager { /** * APP ID sent to the Realm Object Server. Is automatically initialized to the package name for the app. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 64db6280f7..0aabe1e5d9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -40,7 +40,7 @@ */ @Keep @Beta -public final class SyncSession { +public class SyncSession { private final ObjectServerSession osSession; From c20b487f7903441643a5cb181537914738d76b03 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 15 Dec 2016 15:19:22 +0100 Subject: [PATCH 0306/2110] Disabling sync/compact combo (#3899) * Disabling sync/compact combo --- CHANGELOG.md | 4 ++++ .../java/io/realm/SyncConfigurationTests.java | 10 ++++++++++ realm/realm-library/src/main/java/io/realm/Realm.java | 7 +++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58be2afe4d..afd128cef9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 2.2.2 +### Object Server API Changes (In Beta) + +* Disabled `Realm.compactRealm()` when sync is enabled as it might corrupt the Realm (https://github.com/realm/realm-core/issues/2345). + ### Enhancements * All major public classes are now non-final. This is mostly a compromise to diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 17b734f903..938fe33480 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -401,4 +401,14 @@ public void toString_nonEmpty() { String configStr = config.toString(); assertTrue(configStr != null && !configStr.isEmpty()); } + + // FIXME: This test can be removed when https://github.com/realm/realm-core/issues/2345 is resolved + @Test(expected = UnsupportedOperationException.class) + public void compact_NotAllowed() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + + Realm.compactRealm(config); + } } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index f74055d402..81c4bdebf1 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1555,10 +1555,13 @@ public static boolean deleteRealm(RealmConfiguration configuration) { * * @param configuration a {@link RealmConfiguration} pointing to a Realm file. * @return {@code true} if successful, {@code false} if any file operation failed. - * @throws IllegalArgumentException if the realm file is encrypted. Compacting an encrypted Realm file is not - * supported yet. + * @throws UnsupportedOperationException if Realm is synchronized. */ public static boolean compactRealm(RealmConfiguration configuration) { + // FIXME: remove this restriction when https://github.com/realm/realm-core/issues/2345 is resolved + if (configuration.isSyncConfiguration()) { + throw new UnsupportedOperationException("Compacting is not supported yet on synced Realms. See https://github.com/realm/realm-core/issues/2345"); + } return BaseRealm.compactRealm(configuration); } From d06f9206b34cc11617dd81f29149940aa584f8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 15 Dec 2016 15:32:58 +0100 Subject: [PATCH 0307/2110] Improve Error Message Output when an Entity Class hasn't a default constructor with no argument (#3906) --- .../src/main/java/io/realm/processor/ClassMetaData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index b40263d4cc..de43217461 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -204,7 +204,7 @@ private boolean checkReferenceTypes() { // Report if the default constructor is missing private boolean checkDefaultConstructor() { if (!hasDefaultConstructor) { - Utils.error("A default public constructor with no argument must be declared if a custom constructor is declared."); + Utils.error("A default public constructor with no argument must be declared in " + className + " if a custom constructor is declared."); return false; } else { return true; From 89d878bffab749a8057f35dcf836bd8b4f7107a8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Dec 2016 13:13:16 +0800 Subject: [PATCH 0308/2110] Waiting longer time for checking auth server (#3913) From log i saw sometimes the ros testing server fails to send the response, maybe 20+50 = 1s sometimes is too short when the docker host is under heave loading. --- .../io/realm/objectserver/utils/HttpUtils.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index d2cf5bf6ed..9b15ae11e3 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -18,6 +18,7 @@ import java.io.IOException; +import io.realm.log.RealmLog; import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -44,10 +45,10 @@ public static void startSyncServer() throws Exception { Headers responseHeaders = response.headers(); for (int i = 0; i < responseHeaders.size(); i++) { - System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); + RealmLog.debug(responseHeaders.name(i) + ": " + responseHeaders.value(i)); } - System.out.println(response.body().string()); + RealmLog.debug(response.body().string()); // FIXME: Server ready checking should be done in the control server side! if (!waitAuthServerReady()) { @@ -58,7 +59,7 @@ public static void startSyncServer() throws Exception { // Checking the server private static boolean waitAuthServerReady() throws InterruptedException { - int retryTimes = 20; + int retryTimes = 50; Request request = new Request.Builder() .url(Constants.AUTH_SERVER_URL) .build(); @@ -69,9 +70,10 @@ private static boolean waitAuthServerReady() throws InterruptedException { if (response.isSuccessful()) { return true; } + RealmLog.error("Error response from auth server: %s", response.toString()); } catch (IOException e) { - e.printStackTrace(); - Thread.sleep(50); + RealmLog.error(e); + Thread.sleep(100); } retryTimes--; } @@ -89,9 +91,9 @@ public static void stopSyncServer() throws Exception { Headers responseHeaders = response.headers(); for (int i = 0; i < responseHeaders.size(); i++) { - System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); + RealmLog.debug(responseHeaders.name(i) + ": " + responseHeaders.value(i)); } - System.out.println(response.body().string()); + RealmLog.debug(response.body().string()); } } From 73f1314353a17e4eaaf49f9551a27c03e5e997a8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Dec 2016 14:56:43 +0800 Subject: [PATCH 0309/2110] Create capabilities for every Realm instance --- .../io/realm/internal/CollectionTests.java | 3 +- .../io/realm/internal/RowNotifierTests.java | 3 +- .../io/realm/internal/SharedRealmTests.java | 4 +-- .../src/main/java/io/realm/BaseRealm.java | 3 +- .../src/main/java/io/realm/RealmObject.java | 2 +- .../java/io/realm/internal/Capabilities.java | 16 +++++++++ .../java/io/realm/internal/RealmNotifier.java | 14 ++++++++ .../java/io/realm/internal/SharedRealm.java | 36 ++++++++++--------- .../internal/android/AndroidCapabilities.java | 17 +++++++-- .../android/AndroidRealmNotifier.java | 15 +++++--- 10 files changed, 80 insertions(+), 33 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index e162fbdcb5..d658ff53f9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -32,7 +32,6 @@ import io.realm.RealmConfiguration; import io.realm.RealmFieldType; import io.realm.TestHelper; -import io.realm.internal.android.AndroidRealmNotifier; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -67,7 +66,7 @@ public void tearDown() { } private SharedRealm getSharedRealm() { - return SharedRealm.getInstance(config, new AndroidRealmNotifier(), null); + return SharedRealm.getInstance(config, null); } private void populateData() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java index fc93fc092a..dacbaa61b6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java @@ -31,7 +31,6 @@ import io.realm.RealmConfiguration; import io.realm.RealmFieldType; import io.realm.TestHelper; -import io.realm.internal.android.AndroidRealmNotifier; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -66,7 +65,7 @@ public void tearDown() { } private SharedRealm getSharedRealm() { - return SharedRealm.getInstance(config, new AndroidRealmNotifier(), null); + return SharedRealm.getInstance(config, null); } private void populateData() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java index 9dab44e638..f7b0ec9963 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java @@ -163,7 +163,7 @@ public void beginTransaction_SchemaVersionListener() { final AtomicLong schemaVersionFromListener = new AtomicLong(-1L); sharedRealm.close(); - sharedRealm = SharedRealm.getInstance(config, null, new SharedRealm.SchemaVersionListener() { + sharedRealm = SharedRealm.getInstance(config, new SharedRealm.SchemaVersionListener() { @Override public void onSchemaVersionChanged(long currentVersion) { listenerCalled.set(true); @@ -202,7 +202,7 @@ public void refresh_SchemaVersionListener() { final AtomicLong schemaVersionFromListener = new AtomicLong(-1L); sharedRealm.close(); - sharedRealm = SharedRealm.getInstance(config, null, new SharedRealm.SchemaVersionListener() { + sharedRealm = SharedRealm.getInstance(config, new SharedRealm.SchemaVersionListener() { @Override public void onSchemaVersionChanged(long currentVersion) { listenerCalled.set(true); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index b866d2f3cb..15b00a18b6 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -37,7 +37,6 @@ import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.Util; -import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.RealmLog; import rx.Observable; @@ -76,7 +75,7 @@ protected BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; - this.sharedRealm = SharedRealm.getInstance(configuration, new AndroidRealmNotifier(), + this.sharedRealm = SharedRealm.getInstance(configuration, !(this instanceof Realm) ? null : new SharedRealm.SchemaVersionListener() { @Override diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index bd95936e86..7462444cf5 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -271,7 +271,7 @@ public static void addChangeListener(E object, RealmChang RealmObjectProxy proxy = (RealmObjectProxy) object; BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); realm.checkIfValid(); - SharedRealm.getCapabilities().checkCanDeliverNotification("Listener cannot be added."); + realm.sharedRealm.capabilities.checkCanDeliverNotification("Listener cannot be added."); //noinspection unchecked proxy.realmGet$proxyState().addChangeListener(listener); } else { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java index 021fffbd9f..abfff3dd8b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java @@ -16,7 +16,23 @@ package io.realm.internal; +/** + * To describe what does the Realm instance can do associated with the thread it is created on. + * The capabilities are determined when the Realm gets created. This interface could be called from another thread which + * is different from where the Realm is created on. + */ public interface Capabilities { + /** + * Return true if this Realm can be notified by another thread. + * + * @return true if this Realm can be notified from an other thread. + */ boolean canDeliverNotification(); + + /** + * Throw if this Realm cannot receive notifications. + * + * @param exceptionMessage message which is contained in the exception. + */ void checkCanDeliverNotification(String exceptionMessage); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 1846779a05..3243243acb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -17,6 +17,8 @@ package io.realm.internal; import java.io.Closeable; +import java.util.ArrayList; +import java.util.List; import io.realm.RealmChangeListener; @@ -51,6 +53,8 @@ public void onCalled(RealmObserverPair pair, Object observer) { } }; + private List transactionCallbacks = new ArrayList(); + // This is called by OS when other thread/process changes the Realm. // This is getting called on the same thread which created the Realm. // |---------------------------------------------------------------+--------------+------------------------------------------------| @@ -70,6 +74,10 @@ public void onCalled(RealmObserverPair pair, Object observer) { @SuppressWarnings("unused") // called from java_binding_context.cpp protected void didChange() { realmObserverPairs.foreach(onChangeCallBack); + for (Runnable runnable : transactionCallbacks) { + runnable.run(); + } + transactionCallbacks.clear(); } @SuppressWarnings("unused") // called from java_binding_context.cpp @@ -103,5 +111,11 @@ public void removeAllChangeListeners() { realmObserverPairs.clear(); } + public void addTransactionCallback(Runnable runnable) { + transactionCallbacks.add(runnable); + } + public abstract void postAtFrontOfQueue(Runnable runnable); + + public abstract void post(Runnable runnable); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 3fb19aec2a..de6fe56836 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -25,6 +25,7 @@ import io.realm.RealmConfiguration; import io.realm.RealmSchema; import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.android.AndroidRealmNotifier; public final class SharedRealm implements Closeable { @@ -36,8 +37,6 @@ public final class SharedRealm implements Closeable { public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 4; public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 5; - private static final Capabilities capabilities = new AndroidCapabilities(); - public static void initialize(File tempDirectory) { if (SharedRealm.temporaryDirectory != null) { // already initialized @@ -110,6 +109,8 @@ public byte getNativeValue() { public final RowNotifier rowNotifier; public final ObjectServerFacade objectServerFacade; public final List> collections = new CopyOnWriteArrayList>(); + public final Capabilities capabilities; + // To prevent overflow the message queue. public boolean disableSnapshotPosted = false; @@ -176,29 +177,30 @@ public interface SchemaVersionListener { private long lastSchemaVersion; private final SchemaVersionListener schemaChangeListener; - private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotifier notifier, - RowNotifier rowNotifier, SchemaVersionListener schemaVersionListener) { + private SharedRealm(long nativePtr, RealmConfiguration configuration, Capabilities capabilities, + RealmNotifier notifier, RowNotifier rowNotifier, SchemaVersionListener schemaVersionListener) { + context = new Context(); + this.nativePtr = nativePtr; this.configuration = configuration; - if (notifier != null) { - notifier.setSharedRealm(this); - } + this.capabilities = capabilities; this.realmNotifier = notifier; - + if (this.realmNotifier != null) { + this.realmNotifier.setSharedRealm(this); + } this.rowNotifier = rowNotifier; this.schemaChangeListener = schemaVersionListener; - context = new Context(); this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion(); objectServerFacade = null; nativeSetAutoRefresh(nativePtr, capabilities.canDeliverNotification()); } public static SharedRealm getInstance(RealmConfiguration config) { - return getInstance(config, null, null); + return getInstance(config, null); } - public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier realmNotifier, + public static SharedRealm getInstance(RealmConfiguration config, SchemaVersionListener schemaVersionListener) { String[] userAndServer = ObjectServerFacade.getSyncFacadeIfPossible().getUserAndServerUrl(config); String rosServerUrl = userAndServer[0]; @@ -206,6 +208,7 @@ public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier r boolean enable_caching = false; // Handled in Java currently boolean disableFormatUpgrade = false; // TODO Double negatives :/ boolean autoChangeNotifications = true; + long nativeConfigPtr = nativeCreateConfig( config.getPath(), config.getEncryptionKey(), @@ -216,11 +219,15 @@ public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier r autoChangeNotifications, rosServerUrl, rosUserToken); + + Capabilities capabilities = new AndroidCapabilities(); + RealmNotifier realmNotifier = new AndroidRealmNotifier(capabilities); RowNotifier rowNotifier = new RowNotifier(); try { return new SharedRealm( nativeGetSharedRealm(nativeConfigPtr, realmNotifier, rowNotifier), config, + capabilities, realmNotifier, rowNotifier, schemaVersionListener); @@ -229,8 +236,7 @@ public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier r } } - // FIXME: can it be protected? - public long getNativePtr() { + long getNativePtr() { return nativePtr; } @@ -359,10 +365,6 @@ public boolean isAutoRefresh() { return nativeIsAutoRefresh(nativePtr); } - public static Capabilities getCapabilities() { - return capabilities; - } - @Override public void close() { if (realmNotifier != null) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java index 93f00cf2aa..1add1cc711 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java @@ -19,20 +19,31 @@ import io.realm.internal.Capabilities; +/** + * Realm capabilities for Android. + */ public class AndroidCapabilities implements Capabilities { + private final boolean hasLooper; + private final boolean isIntentServiceThread; + + public AndroidCapabilities() { + hasLooper = Looper.myLooper() != null; + isIntentServiceThread = isIntentServiceThread(); + } + @Override public boolean canDeliverNotification() { - return (Looper.myLooper() != null && !isIntentServiceThread()); + return hasLooper && !isIntentServiceThread; } @Override public void checkCanDeliverNotification(String exceptionMessage) { - if (Looper.myLooper() == null) { + if (!hasLooper) { throw new IllegalStateException( exceptionMessage == null ? "" : (exceptionMessage + " ") + "Realm cannot be automatically updated on a thread without a looper."); } - if (isIntentServiceThread()) { + if (isIntentServiceThread) { throw new IllegalStateException( exceptionMessage == null ? "" : (exceptionMessage + " ") + "Realm cannot be automatically updated on a IntentService thread."); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java index 75e4c04873..9ad9fc5efa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java @@ -3,14 +3,14 @@ import android.os.Handler; import android.os.Looper; +import io.realm.internal.Capabilities; import io.realm.internal.RealmNotifier; -import io.realm.internal.SharedRealm; public class AndroidRealmNotifier extends RealmNotifier { - private final Handler handler; + private Handler handler; - public AndroidRealmNotifier() { - if (SharedRealm.getCapabilities().canDeliverNotification()) { + public AndroidRealmNotifier(Capabilities capabilities) { + if (capabilities.canDeliverNotification()) { handler = new Handler(Looper.myLooper()); } else { handler = null; @@ -23,4 +23,11 @@ public void postAtFrontOfQueue(Runnable runnable) { handler.postAtFrontOfQueue(runnable); } } + + @Override + public void post(Runnable runnable) { + if (handler != null) { + handler.post(runnable); + } + } } From 5616a27a7c69183350e850b58cd551701db51944 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Dec 2016 15:18:51 +0800 Subject: [PATCH 0310/2110] Enable async transaction test --- .../java/io/realm/RealmAsyncQueryTests.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index d0c8556451..92ac808889 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -60,7 +60,6 @@ @RunWith(AndroidJUnit4.class) public class RealmAsyncQueryTests { -/* @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); @Rule @@ -128,18 +127,20 @@ public void onSuccess() { @RunTestInLooperThread public void executeTransactionAsync_onError() throws Throwable { final Realm realm = looperThread.realm; + final RuntimeException runtimeException = new RuntimeException("Oh! What a Terrible Failure"); assertEquals(0, realm.where(Owner.class).count()); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { - throw new RuntimeException("Oh! What a Terrible Failure"); + throw runtimeException; } }, new Realm.Transaction.OnError() { @Override public void onError(Throwable error) { assertEquals(0, realm.where(Owner.class).count()); assertNull(realm.where(Owner.class).findFirst()); + assertEquals(runtimeException, error); looperThread.testComplete(); } }); @@ -167,10 +168,10 @@ public void onChange(Realm object) { }); } - // Test that an async transaction that throws an exception propagate it properly to the user. + // Test that an async transaction that throws when call cancelTransaction manually. @Test @RunTestInLooperThread - public void executeTransactionAsync_exceptionHandling() throws Throwable { + public void executeTransactionAsync_cancelTransactionInside() throws Throwable { final TestHelper.TestLogger testLogger = new TestHelper.TestLogger(LogLevel.DEBUG); RealmLog.add(testLogger); @@ -183,8 +184,7 @@ public void executeTransactionAsync_exceptionHandling() throws Throwable { public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName("Owner"); - realm.cancelTransaction(); // Cancel the transaction then throw - throw new RuntimeException("Boom"); + realm.cancelTransaction(); } }, new Realm.Transaction.OnSuccess() { @Override @@ -195,7 +195,9 @@ public void onSuccess() { @Override public void onError(Throwable error) { // Ensure we are giving developers quality messages in the logs. - assertEquals("Could not cancel transaction, not currently in a transaction.", testLogger.message); + assertTrue(testLogger.message.contains( + "Exception has been throw: Can't commit a non-existing write transaction")); + assertTrue(error instanceof IllegalStateException); RealmLog.remove(testLogger); looperThread.testComplete(); } @@ -311,6 +313,7 @@ public void onError(Throwable error) { }); } +/* // ************************************ // *** promises based async queries *** // ************************************ From 07569c41700d15a16f982b0503b417b2454ef547 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Dec 2016 16:13:49 +0800 Subject: [PATCH 0311/2110] Deliver the callbacks for async transaction --- .../java/io/realm/RealmAsyncQueryTests.java | 86 +++++++++++ .../src/main/java/io/realm/Realm.java | 145 ++++++++---------- .../java/io/realm/internal/RealmNotifier.java | 12 ++ 3 files changed, 163 insertions(+), 80 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 92ac808889..b8e1d1819c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -24,6 +24,7 @@ import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import java.lang.ref.WeakReference; @@ -66,6 +67,8 @@ public class RealmAsyncQueryTests { public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); // **************************** @@ -123,6 +126,31 @@ public void onSuccess() { }); } + @Test + @RunTestInLooperThread + public void executeTransactionAsync_onSuccessCallerRealmClosed() throws Throwable { + final Realm realm = looperThread.realm; + assertEquals(0, realm.where(Owner.class).count()); + + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + Owner owner = realm.createObject(Owner.class); + owner.setName("Owner"); + } + }, new Realm.Transaction.OnSuccess() { + @Override + public void onSuccess() { + assertTrue(realm.isClosed()); + Realm newRealm = Realm.getInstance(looperThread.realmConfiguration); + assertEquals(1, newRealm.where(Owner.class).count()); + assertEquals("Owner", newRealm.where(Owner.class).findFirst().getName()); + looperThread.testComplete(); + } + }); + realm.close(); + } + @Test @RunTestInLooperThread public void executeTransactionAsync_onError() throws Throwable { @@ -146,6 +174,32 @@ public void onError(Throwable error) { }); } + @Test + @RunTestInLooperThread + public void executeTransactionAsync_onErrorCallerRealmClosed() throws Throwable { + final Realm realm = looperThread.realm; + final RuntimeException runtimeException = new RuntimeException("Oh! What a Terrible Failure"); + assertEquals(0, realm.where(Owner.class).count()); + + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + throw runtimeException; + } + }, new Realm.Transaction.OnError() { + @Override + public void onError(Throwable error) { + assertTrue(realm.isClosed()); + Realm newRealm = Realm.getInstance(looperThread.realmConfiguration); + assertEquals(0, newRealm.where(Owner.class).count()); + assertNull(newRealm.where(Owner.class).findFirst()); + assertEquals(runtimeException, error); + looperThread.testComplete(); + } + }); + realm.close(); + } + @Test @RunTestInLooperThread public void executeTransactionAsync_NoCallbacks() throws Throwable { @@ -313,6 +367,38 @@ public void onError(Throwable error) { }); } + @Test + public void executeTransactionAsync_onSuccessOnNonLooperThreadThrows() { + Realm realm = Realm.getInstance(configFactory.createConfiguration()); + thrown.expect(IllegalStateException.class); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + + } + }, new Realm.Transaction.OnSuccess() { + @Override + public void onSuccess() { + } + }); + } + + @Test + public void executeTransactionAsync_onErrorOnNonLooperThreadThrows() { + Realm realm = Realm.getInstance(configFactory.createConfiguration()); + thrown.expect(IllegalStateException.class); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + + } + }, new Realm.Transaction.OnError() { + @Override + public void onError(Throwable error) { + } + }); + } + /* // ************************************ // *** promises based async queries *** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 28a6dfb259..0126c11702 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -46,10 +46,12 @@ import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.Capabilities; import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; import io.realm.internal.ObjectServerFacade; import io.realm.internal.RealmCore; +import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.SharedRealm; @@ -1315,26 +1317,30 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, fin * @throws IllegalArgumentException if the {@code transaction} is {@code null}, or if the realm is opened from * another thread. */ - public RealmAsyncTask executeTransactionAsync(final Transaction transaction, final Realm.Transaction.OnSuccess onSuccess, final Realm.Transaction.OnError onError) { + public RealmAsyncTask executeTransactionAsync(final Transaction transaction, + final Realm.Transaction.OnSuccess onSuccess, + final Realm.Transaction.OnError onError) { checkIfValid(); if (transaction == null) { throw new IllegalArgumentException("Transaction should not be null"); } - // If the user provided a Callback then we make sure, the current Realm has a Handler - // we can use to deliver the result - // FIXME: Implement checking here. - /* - if ((onSuccess != null || onError != null) && !hasValidNotifier()) { - throw new IllegalStateException("Your Realm is opened from a thread without a Looper" + - " and you provided a callback, we need a Handler to invoke your callback"); + // Avoid to call canDeliverNotification() in bg thread. + final boolean canDeliverNotification = sharedRealm.capabilities.canDeliverNotification(); + + // If the user provided a Callback then we have to make sure the current Realm has an events looper to deliver + // the results. + if ((onSuccess != null || onError != null) && !canDeliverNotification) { + throw new IllegalStateException("Your Realm is opened from a thread without a event looper." + + " The callback cannot be invoked."); } - */ // We need to use the same configuration to open a background SharedRealm (i.e Realm) // to perform the transaction final RealmConfiguration realmConfiguration = getConfiguration(); + // We need to deliver the callback even if the Realm is closed. So acquire a reference to the notifier here. + final RealmNotifier realmNotifier = sharedRealm.realmNotifier; final Future pendingTransaction = asyncTaskExecutor.submitTransaction(new Runnable() { @Override @@ -1343,97 +1349,76 @@ public void run() { return; } - boolean transactionCommitted = false; + final SharedRealm.VersionID[] versionID = new SharedRealm.VersionID[1]; final Throwable[] exception = new Throwable[1]; - // FIXME: Disable notifier in SharedRealm + final Realm bgRealm = Realm.getInstance(realmConfiguration); bgRealm.beginTransaction(); try { transaction.execute(bgRealm); - if (!Thread.currentThread().isInterrupted()) { - // No need to send change notification to the work thread. - bgRealm.commitTransaction(); - // The bgRealm needs to be closed before post event to caller's handler to avoid concurrency - // problem. This is currently guaranteed by posting handleAsyncTransactionCompleted below. - bgRealm.close(); - transactionCommitted = true; + if (Thread.currentThread().isInterrupted()) { + return; } + + bgRealm.commitTransaction(); + // The bgRealm needs to be closed before post event to caller's handler to avoid concurrency + // problem. This is currently guaranteed by posting callbacks later below. + versionID[0] = bgRealm.sharedRealm.getVersionID(); } catch (final Throwable e) { exception[0] = e; } finally { - if (!bgRealm.isClosed()) { - if (bgRealm.isInTransaction()) { - bgRealm.cancelTransaction(); - } else if (exception[0] != null) { - RealmLog.warn("Could not cancel transaction, not currently in a transaction."); - } - bgRealm.close(); - } + // SharedGroup::close() will cancel the transaction if needed. + bgRealm.close(); + } - // This will be treated like a special REALM_CHANGED event - // FIXME: Find a way to deliver the callback with current architecture - /* - final Throwable backgroundException = exception[0]; - // Send response as the final step to ensure the bg thread quit before others get the response! - if (hasValidNotifier() && !Thread.currentThread().isInterrupted()) { - - if (transactionCommitted) { - sharedRealm.realmNotifier.post(new Runnable() { - @Override - public void run() { - handlerController.handleAsyncTransactionCompleted(onSuccess != null ? new Runnable() { + final Throwable backgroundException = exception[0]; + // Cannot be interrupted anymore. + if (canDeliverNotification ) { + if (versionID[0] != null && onSuccess != null) { + realmNotifier.post(new Runnable() { + @Override + public void run() { + if (isClosed()) { + // The caller Realm is closed. Just call the onSuccess. Since the new created Realm + // cannot be behind the background one. + onSuccess.onSuccess(); + return; + } + + if (sharedRealm.getVersionID().compareTo(versionID[0]) < 0) { + sharedRealm.realmNotifier.addTransactionCallback(new Runnable() { @Override public void run() { onSuccess.onSuccess(); } - } : null); + }); + } else { + onSuccess.onSuccess(); } - }); - } - - // Send errors directly to the looper, so they don't get intercepted by the HandlerController. - if (backgroundException != null) { - if (onError != null) { - sharedRealm.realmNotifier.post(new Runnable() { - @Override - public void run() { - onError.onError(backgroundException); - } - }); - } else { - sharedRealm.realmNotifier.post(new Runnable() { - @Override - public void run() { - if (backgroundException instanceof RuntimeException) { - throw (RuntimeException) backgroundException; - } else if (backgroundException instanceof Exception) { - throw new RealmException("Async transaction failed", backgroundException); - } else if (backgroundException instanceof Error) { - throw (Error) backgroundException; - } - } - }); } - } - - } else { - // Throw exception in the worker thread if the caller thread terminated - if (backgroundException != null) { - if (backgroundException instanceof RuntimeException) { - //noinspection ThrowFromFinallyBlock - throw (RuntimeException) backgroundException; - } else if (backgroundException instanceof Exception) { - //noinspection ThrowFromFinallyBlock - throw new RealmException("Async transaction failed", backgroundException); - } else if (backgroundException instanceof Error) { - //noinspection ThrowFromFinallyBlock - throw (Error) backgroundException; + }); + } else if (backgroundException != null) { + realmNotifier.post(new Runnable() { + @Override + public void run() { + if (onError != null) { + onError.onError(backgroundException); + } else { + throw new RealmException("Async transaction failed", backgroundException); + } } - } + }); + } + } else { + if (backgroundException != null) { + // FIXME: ThreadPoolExecutor will never throw the exception in the background. We need a + // redesign of the async transaction API. + // Throw in the worker thread since the caller thread cannot get notifications. + throw new RealmException("Async transaction failed", backgroundException); } - */ } + } }); diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 3243243acb..826bfe835c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -53,6 +53,13 @@ public void onCalled(RealmObserverPair pair, Object observer) { } }; + // TODO: The only reason we have this is that async transactions is not supported by OS yet. And OS is using ALopper + // which will be using a different message queue from which java is using to deliver remote Realm changes message. + // We need a way to deliver the async transaction onSuccess callback to the caller thread after the caller Realm + // advanced. This is implemented by posting the callback by RealmNotifier.post() first, and check the realm version + // in the posted Runnable. If the Realm version there is still behind the async transaction we committed, the + // onSuccess callback will be added to this list and be executed later when we get the change event from OS. + // This list is NOT supposed to be thread safe! private List transactionCallbacks = new ArrayList(); // This is called by OS when other thread/process changes the Realm. @@ -117,5 +124,10 @@ public void addTransactionCallback(Runnable runnable) { public abstract void postAtFrontOfQueue(Runnable runnable); + /** + * For current implementation of async transaction only. See comments for {@link #transactionCallbacks}. + * + * @param runnable to be executed in the following event loop. + */ public abstract void post(Runnable runnable); } From 7c1524285a758c5c318603abc1b93f22ca24f4b5 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Dec 2016 16:48:36 +0800 Subject: [PATCH 0312/2110] Remove realmListenerAddedAfterCommit This test is not needed since the listener won't be triggered anymore when Realm advanced before add listener. --- .../java/io/realm/NotificationsTest.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 315fdfbd64..49a45392ca 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -795,21 +795,6 @@ public void run() { }); } - @Test - @RunTestInLooperThread - public void realmListenerAddedAfterCommit() { - Realm realm = looperThread.realm; - realm.beginTransaction(); - realm.commitTransaction(); - - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - looperThread.testComplete(); - } - }); - } - @Test @RunTestInLooperThread public void realmResultsListenerAddedAfterCommit() { From 8c5f4cf4a8fe884cf9cc7dc6f704e070e5e57492 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Dec 2016 17:06:46 +0800 Subject: [PATCH 0313/2110] Update core, sync and object store (#3904) - Core to v2.2.0. - Sync to v1.0.0-BETA-5.0. - Adapt changes from latest object store. - REALM_ENABLE_SYNC should be boolean macro - Create Realm sync history without having a ObjectStore SyncConfig object. - deleteRealm test fix. The single notifier thread is enabled, the .note file is not created in the Realm file directory anymore. - Update ObjectStore submodule to 300a2d6f28 which is committed in https://github.com/realm/realm-object-store/pull/287 --- CHANGELOG.md | 13 ++++++++++--- dependencies.list | 6 +++--- .../src/androidTest/java/io/realm/RealmTests.java | 4 +--- realm/realm-library/src/main/cpp/CMakeLists.txt | 11 +++++------ .../src/main/cpp/io_realm_internal_SharedRealm.cpp | 13 ++++--------- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/cpp/objectserver_shared.hpp | 7 ++++--- tools/sync_test_server/Dockerfile | 10 +++++++--- 8 files changed, 35 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afd128cef9..bdd9e91745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,18 @@ * Disabled `Realm.compactRealm()` when sync is enabled as it might corrupt the Realm (https://github.com/realm/realm-core/issues/2345). +### Bug fixes + +* "operation not permitted" issue when creating Realm file on some devices' external storage (#3629). + ### Enhancements -* All major public classes are now non-final. This is mostly a compromise to - support Mockito. All protected fields/methods are still not considered part of - the public API and can change without notice (#3869). +* All major public classes are now non-final. This is mostly a compromise to support Mockito. All protected fields/methods are still not considered part of the public API and can change without notice (#3869). + +### Internal + +* Upgraded Realm Core to 2.1.0. +* Upgraded Realm Sync to 1.0.0-BETA-5.0. ## 2.2.1 diff --git a/dependencies.list b/dependencies.list index de35027e6e..e1ea9c6811 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.0-BETA-3.2 -REALM_SYNC_SHA256=999f4fabe9f377ab03ced221e82317d6e02361da67e0a9928c66ddb56798e58e +REALM_SYNC_VERSION=1.0.0-BETA-5.0 +REALM_SYNC_SHA256=7bbaa9cdef722d85489feb1b70da11d5640869540d9a0fc40621de7352dd9ffd # Object Server Release used by Integration tests # https://packagecloud.io/realm/realm?filter=debs -REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-2.3-310 \ No newline at end of file +REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-4.11-449 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 528b9e1402..5e65a027c3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -1983,9 +1983,7 @@ public void run() { assertTrue(Realm.deleteRealm(configuration)); // Directory should be empty now - // FIXME: .note file is the named pipe for OS android notification. Just don't delete it until we figure out - // one single daemon thread for notification. - assertEquals(/*0*/1, tempDir.listFiles().length); + assertEquals(0, tempDir.listFiles().length); } // Test that all methods that require a transaction (ie. any function that mutates Realm data) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index e311ef6fd5..6adbe71a32 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -120,7 +120,7 @@ set(WARNING_CXX_FLAGS "-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macr -Wno-missing-field-initializers -Wmissing-declarations -Wno-error=uninitialized -Wno-error=maybe-uninitialized") set(REALM_COMMON_CXX_FLAGS "-DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14 -fsigned-char") if (build_SYNC) - set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_SYNC") + set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ENABLE_SYNC=1") endif() # There might be an issue with -Os of ndk gcc 4.9. It will hang the encryption related tests. # And this issue doesn't seem to impact the core compiling. @@ -162,13 +162,12 @@ file(GLOB objectstore_SRC "object-store/src/impl/collection_change_builder.cpp" "object-store/src/impl/transact_log_handler.cpp" "object-store/src/impl/weak_realm_notifier.cpp" - "object-store/src/impl/android/*.cpp" - "object-store/src/util/*.cpp") + "object-store/src/util/*.cpp" + "object-store/src/impl/epoll/*.cpp" + "object-store/src/util/android/*.cpp") # Sync needed Object Store files if (build_SYNC) - file(GLOB objectstore_sync_SRC - "object-store/src/sync_manager.cpp" - "object-store/src/sync_session.cpp") + file(GLOB objectstore_sync_SRC "object-store/src/sync/*") endif() add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 6d200de454..644a952dce 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -1,4 +1,3 @@ -#include #include "io_realm_internal_SharedRealm.h" #include "object_store.hpp" @@ -6,8 +5,8 @@ #include "java_binding_context.hpp" #include "util.hpp" -#ifdef REALM_SYNC -#include "sync_config.hpp" +#if REALM_ENABLE_SYNC +#include "sync/sync_manager.hpp" #endif using namespace realm; @@ -53,13 +52,9 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri config->cache = cache; config->disable_format_upgrade = disable_format_upgrade; config->automatic_change_notifications = auto_change_notification; -#ifdef REALM_SYNC +#if REALM_ENABLE_SYNC if (sync_server_url) { - JStringAccessor url(env, sync_server_url); - JStringAccessor token(env, sync_user_token); - config->sync_config = std::make_shared(token, url, nullptr, SyncSessionStopPolicy::Immediately); - // FIXME: Sync session is handled by java now. Remove this when adapt to OS sync implementation. - config->sync_config->create_session = false; + config->force_sync_history = true; } #endif return reinterpret_cast(config); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index bafafb1464..300a2d6f28 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit bafafb1464494d0a731a036399aa5c944d92a5bf +Subproject commit 300a2d6f284391540dcfd346893de49fa15e1771 diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 1253f539d4..bd78254ba4 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -23,8 +23,9 @@ #include #include #include -#include -#include + +#include +#include #include "util.hpp" @@ -50,7 +51,7 @@ class JniSession { auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator( realm::StringData(local_realm_path)); if (coordinator) { - coordinator->notify_others(); + coordinator->wake_up_notifier_worker(); } }; auto error_handler = [&, global_obj_ref_tmp](int error_code, std::string message) { diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 001f6ada49..f733015ca4 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -5,13 +5,17 @@ ARG ROS_DE_VERSION # Add realm repo RUN apt-get update -qq \ && apt-get install -y curl npm \ - && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash \ - && npm install winston temp httpdispatcher@1.0.0 + && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash + +# ROS npm dependencies +RUN npm init -y +RUN npm install winston temp httpdispatcher@1.0.0 + COPY keys/private.pem keys/public.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ # Install realm object server RUN apt-get update -qq \ - && apt-get install -y realm-object-server-de=$ROS_DE_VERSION \ + && apt-get install -y realm-object-server-developer=$ROS_DE_VERSION \ && apt-get clean CMD /usr/bin/ros-testing-server.js /tmp/ros-testing-server.log From 09fa65408c66bb3b56f7052cc4dd2219c2bcf1a7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Dec 2016 17:31:57 +0800 Subject: [PATCH 0314/2110] Adapt OS method name change --- .../realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 0c7e45c0d5..ba01381d7f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -118,7 +118,7 @@ Java_io_realm_RealmFileUserStore_nativeResetForTesting (JNIEnv *, jclass) static const std::shared_ptr& currentUserOrThrow() //throws { - std::vector> all_users = SyncManager::shared().all_users(); + std::vector> all_users = SyncManager::shared().all_logged_in_users(); if (all_users.size() > 1) { throw std::runtime_error(ERR_MULTIPLE_LOGGED_IN_USERS); } else if (all_users.size() < 1) { From d2a452818978138e1e72f509228f977284aa5c7e Mon Sep 17 00:00:00 2001 From: LYK Date: Fri, 16 Dec 2016 18:33:26 +0900 Subject: [PATCH 0315/2110] Fix CHANGELOG.md It's not for 2.2.1 and it was merged into master. --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 424ccbd20e..50f7689631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.3.0 + +### Object Server API Changes (In Beta) + +* Add a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`). + ## 2.2.2 ### Object Server API Changes (In Beta) @@ -15,7 +21,6 @@ ### Object Server API Changes (In Beta) * Fixed `SyncConfiguration.toString()` so it now outputs a correct description instead of an empty string (#3787). -* Add a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`). ### Bug fixes From 94c6d108560813c68790a5a44619c06cb0d569f1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Dec 2016 17:40:16 +0800 Subject: [PATCH 0316/2110] One more method name change --- .../realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index ba01381d7f..790f08e58c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -82,7 +82,7 @@ JNIEXPORT jobjectArray JNICALL Java_io_realm_RealmFileUserStore_nativeGetAllUsers (JNIEnv *env, jclass) { TR_ENTER() - std::vector> all_users = SyncManager::shared().all_users(); + std::vector> all_users = SyncManager::shared().all_logged_in_users(); if (!all_users.empty()) { std::vector> valid_users; jsize array_length = std::count_if(all_users.begin(),all_users.end(), From 39278133b23044651c50cc64cd080bfd47128ff8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Sat, 17 Dec 2016 21:01:49 +0800 Subject: [PATCH 0317/2110] Use SyncManager::get_current_user Instead of iterating and return the first in JNI side. OS SyncManager will take care of throwing when more than one logged in users exist. --- .../java/io/realm/SyncUserTests.java | 2 ++ .../main/cpp/io_realm_RealmFileUserStore.cpp | 27 +++++++------------ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 1c017f7929..48b1a8ef6b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -46,6 +46,7 @@ public class SyncUserTests { @BeforeClass public static void initUserStore() { + Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); UserStore userStore = new RealmFileUserStore(InstrumentationRegistry.getTargetContext().getFilesDir().getPath()); SyncManager.setUserStore(userStore); } @@ -83,6 +84,7 @@ public void currentUser_clearedOnLogout() { SyncUser savedUser = SyncUser.currentUser(); assertEquals(user, savedUser); + assertNotNull(savedUser); savedUser.logout(); assertNull(SyncUser.currentUser()); } diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 790f08e58c..99a06a5a39 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -23,19 +23,17 @@ using namespace realm; -static const char* ERR_MULTIPLE_LOGGED_IN_USERS = "Cannot be called if more that one valid, logged in user exists."; static const char* ERR_NO_LOGGED_IN_USER = "No user logged in yet."; static const char* ERR_COULD_NOT_ALLOCATE_MEMORY = "Could not allocate memory to return all users."; -static const std::shared_ptr& currentUserOrThrow(); - JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetCurrentUser (JNIEnv *env, jclass) { TR_ENTER() try { - const std::shared_ptr &user = currentUserOrThrow(); - if (user->state() == SyncUser::State::Active) { + + const std::shared_ptr &user = SyncManager::shared().get_current_user(); + if (user) { return to_jstring(env, user->refresh_token().data()); } else { return nullptr; @@ -62,8 +60,12 @@ Java_io_realm_RealmFileUserStore_nativeLogoutCurrentUser (JNIEnv *env, jclass) { TR_ENTER() try { - const std::shared_ptr& user = currentUserOrThrow(); - user->log_out(); + const std::shared_ptr& user = SyncManager::shared().get_current_user(); + if (user) { + user->log_out(); + } else { + throw std::runtime_error(ERR_NO_LOGGED_IN_USER); + } } CATCH_STD() } @@ -116,14 +118,3 @@ Java_io_realm_RealmFileUserStore_nativeResetForTesting (JNIEnv *, jclass) SyncManager::shared().reset_for_testing(); } -static const std::shared_ptr& currentUserOrThrow() //throws -{ - std::vector> all_users = SyncManager::shared().all_logged_in_users(); - if (all_users.size() > 1) { - throw std::runtime_error(ERR_MULTIPLE_LOGGED_IN_USERS); - } else if (all_users.size() < 1) { - throw std::runtime_error(ERR_NO_LOGGED_IN_USER); - } else { - return all_users.front(); - } -} From 31869e0d81ac3040dde047f84e678518f08ea23e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Sat, 17 Dec 2016 22:15:54 +0800 Subject: [PATCH 0318/2110] all_logged_in_users does checking active --- .../main/cpp/io_realm_RealmFileUserStore.cpp | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 99a06a5a39..87600fa09d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -86,24 +86,14 @@ Java_io_realm_RealmFileUserStore_nativeGetAllUsers (JNIEnv *env, jclass) TR_ENTER() std::vector> all_users = SyncManager::shared().all_logged_in_users(); if (!all_users.empty()) { - std::vector> valid_users; - jsize array_length = std::count_if(all_users.begin(),all_users.end(), - [&](const std::shared_ptr& user) { - if (user->state() == SyncUser::State::Active) { - valid_users.emplace_back(std::move(user)); - return true; - } - return false; - }); - - jobjectArray users_token = env->NewObjectArray(array_length, java_lang_string, 0); - if (users_token == NULL) { + size_t len = all_users.size(); + jobjectArray users_token = env->NewObjectArray(len, java_lang_string, 0); + if (users_token == nullptr) { ThrowException(env, OutOfMemory, ERR_COULD_NOT_ALLOCATE_MEMORY); return nullptr; } - - for (auto user : valid_users) { - env->SetObjectArrayElement(users_token, --array_length, to_jstring(env, user->refresh_token().data())); + for (int i = 0; i < len; ++i) { + env->SetObjectArrayElement(users_token, i, to_jstring(env, all_users[i]->refresh_token().data())); } return users_token; From b2dab37038396b879d76a3ebb1d1830987634253 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 14 Dec 2016 23:37:24 +0800 Subject: [PATCH 0319/2110] Fix the warnings of StrictMode The StrictMode warnings is caused by testInMemoryRealm which try to detect if there are IOs in the thread when using in-memory Realm. Well, that test is created by me and it doesn't make any sense to check with StrictMode. In memory Realm doesn't mean there will be no disk IOs, it only means if all the instances are closed, the data will not be persisted on the disk. And the StrictMode warning is cause by the last statement `StrictMode.enableDefault()`. StrictMode is not turned on for testing by default! Fix #3141 --- .../java/io/realm/RealmInMemoryTest.java | 71 +++++++++++-------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index c1a572e9c6..b273290f41 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -16,33 +16,46 @@ package io.realm; -import android.os.StrictMode; -import android.test.AndroidTestCase; +import android.support.test.runner.AndroidJUnit4; import junit.framework.AssertionFailedError; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.File; -import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import io.realm.entities.Dog; import io.realm.exceptions.RealmFileException; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class RealmInMemoryTest { -public class RealmInMemoryTest extends AndroidTestCase { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); private final static String IDENTIFIER = "InMemRealmTest"; private Realm testRealm; private RealmConfiguration inMemConf; - @Override - protected void setUp() throws Exception { - RealmConfiguration onDiskConf = new RealmConfiguration.Builder(getContext()) + @Before + public void setUp() { + RealmConfiguration onDiskConf = configFactory.createConfigurationBuilder() .name(IDENTIFIER) .build(); - inMemConf = new RealmConfiguration.Builder(getContext()) + inMemConf = configFactory.createConfigurationBuilder() .name(IDENTIFIER) .inMemory() .build(); @@ -52,8 +65,8 @@ protected void setUp() throws Exception { testRealm = Realm.getInstance(inMemConf); } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() { if (testRealm != null) { testRealm.close(); } @@ -61,14 +74,8 @@ protected void tearDown() throws Exception { // Testing the in-memory Realm by Creating one instance, adding a record, then close the instance. // By the next time in-memory Realm instance with the same name created, it should be empty. - // Use StrictMode to check no disk IO would happen in VM to this thread. - public void testInMemoryRealm() { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectDiskReads() - .detectDiskWrites() - .penaltyDeath() - .build()); - + @Test + public void inMemoryRealm() { testRealm.beginTransaction(); Dog dog = testRealm.createObject(Dog.class); dog.setName("DinoDog"); @@ -82,19 +89,18 @@ public void testInMemoryRealm() { // in-mem-realm with same identifier should create a fresh new instance. testRealm = Realm.getInstance(inMemConf); assertEquals(testRealm.where(Dog.class).count(), 0); - - StrictMode.enableDefaults(); } // Two in-memory Realms with different names should not affect each other. - public void testInMemoryRealmWithDifferentNames() { + @Test + public void inMemoryRealmWithDifferentNames() { testRealm.beginTransaction(); Dog dog = testRealm.createObject(Dog.class); dog.setName("DinoDog"); testRealm.commitTransaction(); // Create the 2nd in-memory Realm with a different name. To make sure they are not affecting each other. - RealmConfiguration inMemConf2 = new RealmConfiguration.Builder(getContext()) + RealmConfiguration inMemConf2 = configFactory.createConfigurationBuilder() .name(IDENTIFIER + "2") .inMemory() .build(); @@ -105,15 +111,18 @@ public void testInMemoryRealmWithDifferentNames() { testRealm2.commitTransaction(); assertEquals(testRealm.where(Dog.class).count(), 1); + //noinspection ConstantConditions assertEquals(testRealm.where(Dog.class).findFirst().getName(), "DinoDog"); assertEquals(testRealm2.where(Dog.class).count(), 1); + //noinspection ConstantConditions assertEquals(testRealm2.where(Dog.class).findFirst().getName(), "UFODog"); testRealm2.close(); } // Test deleteRealm called on a in-memory Realm instance - public void testDelete() { + @Test + public void delete() { RealmConfiguration configuration = testRealm.getConfiguration(); try { Realm.deleteRealm(configuration); @@ -128,14 +137,15 @@ public void testDelete() { } // Test if an in-memory Realm can be written to disk with/without encryption - public void testWriteCopyTo() { + @Test + public void writeCopyTo() { byte[] key = TestHelper.getRandomKey(); String fileName = IDENTIFIER + ".realm"; String encFileName = IDENTIFIER + ".enc.realm"; - RealmConfiguration conf = new RealmConfiguration.Builder(getContext()) + RealmConfiguration conf = configFactory.createConfigurationBuilder() .name(fileName) .build(); - RealmConfiguration encConf = new RealmConfiguration.Builder(getContext()) + RealmConfiguration encConf = configFactory.createConfigurationBuilder() .name(encFileName) .encryptionKey(key) .build(); @@ -149,19 +159,19 @@ public void testWriteCopyTo() { testRealm.commitTransaction(); // Test a normal Realm file - testRealm.writeCopyTo(new File(getContext().getFilesDir(), fileName)); + testRealm.writeCopyTo(new File(configFactory.getRoot(), fileName)); Realm onDiskRealm = Realm.getInstance(conf); assertEquals(onDiskRealm.where(Dog.class).count(), 1); onDiskRealm.close(); // Test a encrypted Realm file - testRealm.writeEncryptedCopyTo(new File(getContext().getFilesDir(), encFileName), key); + testRealm.writeEncryptedCopyTo(new File(configFactory.getRoot(), encFileName), key); onDiskRealm = Realm.getInstance(encConf); assertEquals(onDiskRealm.where(Dog.class).count(), 1); onDiskRealm.close(); // Test with a wrong key to see if it fails as expected. try { - RealmConfiguration wrongKeyConf = new RealmConfiguration.Builder(getContext()) + RealmConfiguration wrongKeyConf = configFactory.createConfigurationBuilder() .name(encFileName) .encryptionKey(TestHelper.getRandomKey(42)) .build(); @@ -179,7 +189,8 @@ public void testWriteCopyTo() { // another instance is still held by the other thread. // 4. Close the in-memory Realm instance and the Realm data should be released since no more instance with the // specific name exists. - public void testMultiThread() throws InterruptedException, ExecutionException { + @Test + public void multiThread() throws InterruptedException, ExecutionException { final CountDownLatch workerCommittedLatch = new CountDownLatch(1); final CountDownLatch workerClosedLatch = new CountDownLatch(1); final CountDownLatch realmInMainClosedLatch = new CountDownLatch(1); From b90a7120596c270f1eb711aafe344864668ba29e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 21 Dec 2016 16:08:23 +0800 Subject: [PATCH 0320/2110] snapshot local url should be inside allprojects Fix #3935 --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 10e737e150..1496891c4c 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,11 @@ buildscript { } } -repositories { - maven { - url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' +allprojects { + repositories { + maven { + url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' + } } } ``` From 3dc21892288904f3345b9c8201984c5df280f567 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 21 Dec 2016 11:43:54 +0100 Subject: [PATCH 0321/2110] Throw correct exception for multiple logged in users (#3921) --- .../java/io/realm/SyncUserTests.java | 44 +++++++++++++++++++ .../java/io/realm/util/SyncTestUtils.java | 22 +++++++--- .../main/cpp/io_realm_RealmFileUserStore.cpp | 1 - realm/realm-library/src/main/cpp/util.cpp | 3 ++ .../objectServer/java/io/realm/SyncUser.java | 9 ++-- 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 48b1a8ef6b..18a8eb9aea 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -24,11 +24,18 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.util.Collection; +import java.util.UUID; +import io.realm.internal.network.AuthenticateResponse; +import io.realm.internal.network.AuthenticationServer; import io.realm.rule.RunInLooperThread; import io.realm.util.SyncTestUtils; @@ -37,6 +44,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; @RunWith(AndroidJUnit4.class) public class SyncUserTests { @@ -74,6 +84,40 @@ public void currentUser_returnsNullIfUserExpired() { assertNull(SyncUser.currentUser()); } + @Test + public void currentUser_throwsIfMultipleUsersLoggedIn() { + AuthenticationServer originalAuthServer = SyncManager.getAuthServer(); + AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + SyncManager.setAuthServerImpl(authServer); + try { + // 1. Login two random users + when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenAnswer(new Answer() { + @Override + public AuthenticateResponse answer(InvocationOnMock invocationOnMock) throws Throwable { + return getNewRandomUser(); + } + }); + SyncUser.login(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); + SyncUser.login(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); + + // 2. Verify currentUser() now throws + try { + SyncUser.currentUser(); + fail(); + } catch (IllegalStateException ignore) { + } + } finally { + SyncManager.setAuthServerImpl(originalAuthServer); + } + + } + + private AuthenticateResponse getNewRandomUser() { + String identity = UUID.randomUUID().toString(); + String userTokenValue = UUID.randomUUID().toString(); + return SyncTestUtils.createLoginResponse(userTokenValue, identity, Long.MAX_VALUE); + } + // Test that current user is cleared if it is logged out @Test public void currentUser_clearedOnLogout() { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index bd067ab30b..ef3ead5f2f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -35,21 +35,25 @@ public class SyncTestUtils { public static String REALM_TOKEN = UUID.randomUUID().toString(); public static String DEFAULT_AUTH_URL = "http://objectserver.realm.io/auth"; + public static SyncUser createRandomTestUser() { + return createTestUser(UUID.randomUUID().toString(), UUID.randomUUID().toString(), DEFAULT_AUTH_URL, Long.MAX_VALUE); + } + public static SyncUser createTestUser() { - return createTestUser(DEFAULT_AUTH_URL, Long.MAX_VALUE); + return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_AUTH_URL, Long.MAX_VALUE); } public static SyncUser createTestUser(long expires) { - return createTestUser(DEFAULT_AUTH_URL, expires); + return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_AUTH_URL, expires); } public static SyncUser createTestUser(String authUrl) { - return createTestUser(authUrl, Long.MAX_VALUE); + return createTestUser(USER_TOKEN, REALM_TOKEN, authUrl, Long.MAX_VALUE); } - public static SyncUser createTestUser(String authUrl, long expires) { - Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); - Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); + public static SyncUser createTestUser(String userTokenValue, String realmTokenValue, String authUrl, long expires) { + Token userToken = new Token(userTokenValue, "JohnDoe", null, expires, null); + Token accessToken = new Token(realmTokenValue, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); JSONObject obj = new JSONObject(); @@ -70,8 +74,12 @@ public static SyncUser createTestUser(String authUrl, long expires) { } public static AuthenticateResponse createLoginResponse(long expires) { + return createLoginResponse(USER_TOKEN, "JohnDoe", expires); + } + + public static AuthenticateResponse createLoginResponse(String userTokenValue, String userIdentity, long expires) { try { - Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); + Token userToken = new Token(userTokenValue, userIdentity, null, expires, null); JSONObject response = new JSONObject(); response.put("refresh_token", userToken.toJson()); return AuthenticateResponse.from(response.toString()); diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 87600fa09d..8c86146d45 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -31,7 +31,6 @@ Java_io_realm_RealmFileUserStore_nativeGetCurrentUser (JNIEnv *env, jclass) { TR_ENTER() try { - const std::shared_ptr &user = SyncManager::shared().get_current_user(); if (user) { return to_jstring(env, user->refresh_token().data()); diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 570bfa9185..5379e48c2c 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -79,6 +79,9 @@ void ConvertException(JNIEnv* env, const char *file, int line) ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalArgument, ss.str()); } + catch (std::logic_error e) { + ThrowException(env, IllegalState, e.what()); + } catch (exception& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, FatalError, ss.str()); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index a892c980bb..6486bf3c5e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -70,11 +70,12 @@ private SyncUser(ObjectServerUser user) { } /** - * Returns the last user that has logged in and who is still valid. - * A user is invalidated when he/she logs out or the user's access token expire. + * Returns the current user that is logged in and still valid. + * A user is invalidated when he/she logs out or the user's access token expires. * - * @return last {@link SyncUser} that has logged in and who is still valid. {@code null} if no current user or user has - * been invalidated. + * @return current {@link SyncUser} that has logged in and is still valid. {@code null} if no user is logged in or the user has + * expired. + * @throws IllegalStateException if multiple users are logged in. */ public static SyncUser currentUser() { SyncUser user = SyncManager.getUserStore().get(); From 293e393bb2cba9ad10690b7787d53dacc04b13bb Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 21 Dec 2016 22:49:15 +0800 Subject: [PATCH 0322/2110] Population data of collection tests Set the linked object to itself for linked field sorting tests. --- .../src/androidTest/java/io/realm/CollectionTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index 1ef8aad27b..8f63b29412 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -91,6 +91,8 @@ protected void populateRealm(Realm realm, int objects) { NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); + // Set the linked object to itself. + obj.setFieldObject(obj); } // Add all items to the RealmList on the first object From 1d4ecaedf3ff5922cbc306e71d3d0f577a81e112 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 21 Dec 2016 23:25:42 +0800 Subject: [PATCH 0323/2110] Call Realm::close OS Results is actually holding a SharedRealm instance as a member var which means when call java Realm.close(), the relevant Realm instance is not surely to be closed since it only release the shared ptr. Without closing the Realm, the validations in Results will be useless. --- .../src/main/cpp/io_realm_internal_SharedRealm.cpp | 1 + realm/realm-library/src/main/cpp/util.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 9e74641696..6d30dc9694 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -116,6 +116,7 @@ Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong TR_ENTER_PTR(shared_realm_ptr) auto ptr = reinterpret_cast(shared_realm_ptr); + (*ptr)->close(); delete ptr; } diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 4ae966c85c..b1b7283e11 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -97,6 +97,10 @@ void ConvertException(JNIEnv* env, const char *file, int line) << "(field name: " << e.column_name << ")"; ThrowException(env, IllegalArgument, ss.str()); } + catch (Results::InvalidatedException& e) { + ss << e.what() << " in " << file << " line " << line; + ThrowException(env, IllegalState, ss.str()); + } catch (IncorrectThreadException& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalState, ss.str()); From f59c9cf2af8f69da6af80a80581136b550798691 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 11:16:30 +0800 Subject: [PATCH 0324/2110] More tests for collection detach --- .../io/realm/internal/CollectionTests.java | 172 +++++++++++++++++- .../main/cpp/io_realm_internal_Collection.cpp | 28 ++- .../java/io/realm/internal/Collection.java | 15 +- .../java/io/realm/internal/SharedRealm.java | 2 +- 4 files changed, 203 insertions(+), 14 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index d658ff53f9..5df7fa8635 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -27,6 +27,7 @@ import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; import io.realm.RealmChangeListener; import io.realm.RealmConfiguration; @@ -37,6 +38,7 @@ import io.realm.rule.TestRealmConfigurationFactory; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -236,6 +238,49 @@ public void distinct() { assertEquals(collection.getUncheckedRow(2).getString(0), "Henry"); } + // 1. Create a results and add listener. + // 2. Query results should be returned in the next loop. + @Test + @RunTestInLooperThread + public void addListener_shouldBeCalledToReturnTheQueryResults() { + final SharedRealm sharedRealm = getSharedRealm(); + Table table = sharedRealm.getTable("test_table"); + + final Collection collection = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collection); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection collection1) { + assertEquals(collection1, collection); + assertEquals(collection1.size(), 4); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + } + + // 1. Create a results and add listener on a non-looper thread. + // 2. Query results should be returned when refresh() called. + @Test + public void addListener_shouldBeCalledWhenRefreshToReturnTheQueryResults() { + final AtomicBoolean onChangeCalled = new AtomicBoolean(false); + final SharedRealm sharedRealm = getSharedRealm(); + Table table = sharedRealm.getTable("test_table"); + + final Collection collection = new Collection(sharedRealm, table.where()); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection collection1) { + assertEquals(collection1, collection); + assertEquals(collection1.size(), 4); + sharedRealm.close(); + onChangeCalled.set(true); + } + }); + sharedRealm.refresh(); + assertTrue(onChangeCalled.get()); + } + @Test public void addListener_shouldBeCalledWhenRefreshAfterLocalCommit() { final CountDownLatch latch = new CountDownLatch(1); @@ -388,20 +433,111 @@ public void onChange(Collection collection1) { } @Test - public void switchSnapshot_nonLooperThread() { + public void detach_byBeginTransaction() { + final Collection collection = new Collection(sharedRealm, table.where()); + assertFalse(collection.isDetached()); + assertEquals(collection.size(), 4); + addRowAsync(); + // beginTransaction will do advance read, but the table view should stay without changes. + sharedRealm.beginTransaction(); + assertTrue(collection.isDetached()); + assertEquals(collection.size(), 4); + } + + @Test + public void detach_newCollectionCreatedInTransaction() { + sharedRealm.beginTransaction(); + final Collection collection = new Collection(sharedRealm, table.where()); + assertTrue(collection.isDetached()); + } + + @Test + public void detach_commitTransactionWontReattach() { + final Collection collection = new Collection(sharedRealm, table.where()); + sharedRealm.beginTransaction(); + sharedRealm.commitTransaction(); + assertTrue(collection.isDetached()); + assertEquals(collection.size(), 4); + } + + @Test + public void reattach_byCancelTransaction() { + final Collection collection = new Collection(sharedRealm, table.where()); + sharedRealm.beginTransaction(); + assertTrue(collection.isDetached()); + sharedRealm.cancelTransaction(); + assertFalse(collection.isDetached()); + assertEquals(collection.size(), 4); + } + + @Test + public void reattach_nonLooperThread_byRefresh() { final Collection collection = new Collection(sharedRealm, table.where()); assertEquals(collection.size(), 4); addRow(sharedRealm); // The results is backed by snapshot now. + assertTrue(collection.isDetached()); assertEquals(collection.size(), 4); sharedRealm.refresh(); // The results is switched back to the original Results. + assertFalse(collection.isDetached()); assertEquals(collection.size(), 5); } @Test @RunTestInLooperThread - public void switchSnapshot_looperThread() { + public void reattach_looperThread_byLocalTransaction() { + final SharedRealm sharedRealm = getSharedRealm(); + Table table = sharedRealm.getTable("test_table"); + final Collection collection = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collection); + assertFalse(collection.isDetached()); + assertEquals(collection.size(), 4); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection element) { + assertFalse(collection.isDetached()); + assertEquals(collection.size(), 5); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + addRow(sharedRealm); + // The results is backed by snapshot now. + assertTrue(collection.isDetached()); + assertEquals(collection.size(), 4); + } + + @Test + @RunTestInLooperThread + public void reattach_looperThread_byRemoteTransaction() { + final SharedRealm sharedRealm = getSharedRealm(); + Table table = sharedRealm.getTable("test_table"); + final Collection collection = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collection); + assertFalse(collection.isDetached()); + assertEquals(collection.size(), 4); + collection.addListener(collection, new RealmChangeListener() { + @Override + public void onChange(Collection element) { + assertFalse(collection.isDetached()); + assertEquals(collection.size(), 5); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + sharedRealm.beginTransaction(); + sharedRealm.commitTransaction(); + // The results is backed by snapshot now. + assertTrue(collection.isDetached()); + assertEquals(collection.size(), 4); + + addRowAsync(); + } + + @Test + @RunTestInLooperThread + public void reattach_looperThread_shouldHappenBeforeAnyOtherLoopEventWithEmptyLocalTransaction() { final SharedRealm sharedRealm = getSharedRealm(); Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); @@ -411,13 +547,41 @@ public void switchSnapshot_looperThread() { @Override public void run() { // The results is switched back to the original Results. - assertEquals(collection.size(), 5); + assertFalse(collection.isDetached()); + assertEquals(collection.size(), 4); sharedRealm.close(); looperThread.testComplete(); } }); - addRow(sharedRealm); + sharedRealm.beginTransaction(); + sharedRealm.commitTransaction(); + // The results is backed by snapshot now. + assertTrue(collection.isDetached()); + assertEquals(collection.size(), 4); + } + + @Test + @RunTestInLooperThread + public void reattach_looperThread_shouldHappenBeforeAnyOtherLoopEventWithLocalTransactionCanceled() { + final SharedRealm sharedRealm = getSharedRealm(); + Table table = sharedRealm.getTable("test_table"); + final Collection collection = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collection); + assertEquals(collection.size(), 4); + looperThread.postRunnable(new Runnable() { + @Override + public void run() { + // The results is switched back to the original Results. + assertFalse(collection.isDetached()); + assertEquals(collection.size(), 4); + sharedRealm.close(); + looperThread.testComplete(); + } + }); + sharedRealm.beginTransaction(); // The results is backed by snapshot now. + assertTrue(collection.isDetached()); assertEquals(collection.size(), 4); + sharedRealm.cancelTransaction(); } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 95620965b3..1792444564 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -19,8 +19,8 @@ #include -#include -#include +#include +#include #include "util.hpp" #include "jni_util/method.hpp" @@ -68,7 +68,9 @@ struct ResultsWrapper { inline void switch_to_snapshot() { - m_snapshot = m_results.snapshot(); + if (m_snapshot.get_mode() == Results::Mode::Empty) { + m_snapshot = m_results.snapshot(); + } } inline void switch_to_origin() @@ -78,6 +80,11 @@ struct ResultsWrapper { } } + inline bool is_detached() + { + return m_snapshot.get_mode() != Results::Mode::Empty; + } + private: Results m_results; Results m_snapshot; @@ -277,7 +284,7 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst { TR_ENTER_PTR(native_ptr) - static JniMethod notify_change_listeners(env, instance, "notifyChangeListeners", "()V"); + static JniMethod notify_change_listeners(env, instance, "notifyChangeListeners", "(Z)V"); try { auto wrapper = reinterpret_cast(native_ptr); @@ -290,7 +297,9 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; - env->CallVoidMethod(wrapper->m_collection_weak_ref, notify_change_listeners); + //if (!wrapper->is_detached()) { + env->CallVoidMethod(wrapper->m_collection_weak_ref, notify_change_listeners, changes.empty()); + //} }; wrapper->m_notification_token = wrapper->get_original_results().add_notification_callback(cb); @@ -376,6 +385,14 @@ Java_io_realm_internal_Collection_nativeDisableSnapshot(JNIEnv *env, jclass, jlo } CATCH_STD() } +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_Collection_nativeIsDetached(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + auto wrapper = reinterpret_cast(native_ptr); + return wrapper->is_detached(); +} + JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv *env, jclass, jlong native_ptr) { @@ -428,3 +445,4 @@ Java_io_realm_internal_Collection_nativeDelete(JNIEnv *env, jclass, jlong native wrapper->switch_to_snapshot(); } CATCH_STD() } + diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 9529d2ca32..2956b93fe7 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -95,8 +95,7 @@ public Collection(SharedRealm sharedRealm, TableQuery query, sharedRealm.addCollection(this); } - public Collection(SharedRealm sharedRealm, TableQuery query, - SortDescriptor sortDescriptor) { + public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor) { this(sharedRealm, query, sortDescriptor, null); } @@ -216,12 +215,15 @@ public void removeAllListeners() { // Called by JNI @KeepMember @SuppressWarnings("unused") - private void notifyChangeListeners() { + private void notifyChangeListeners(boolean emptyChanges) { // For the stable iteration. // It is needed when the local commit triggered async query updates. And this is called in the next event loop // by OS Realm::notify(). - this.disableSnapshot(); + if (!emptyChanges) { + this.disableSnapshot(); + } + if (emptyChanges && isDetached()) return; observerPairs.foreach(onChangeCallback); } @@ -233,6 +235,10 @@ void disableSnapshot() { nativeDisableSnapshot(nativePtr); } + boolean isDetached() { + return nativeIsDetached(nativePtr); + } + private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long sortDescNativePtr, long distinctDescNativePtr); @@ -257,4 +263,5 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native long nativeIndexOfBySourceRowIndex(long nativePtr, long sourceRowIndex); private static native void nativeEnableSnapshot(long nativePtr); private static native void nativeDisableSnapshot(long nativePtr); + private static native boolean nativeIsDetached(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index de6fe56836..c5d5bce071 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -241,9 +241,9 @@ long getNativePtr() { } public void beginTransaction() { + enableCollectionSnapshot(); nativeBeginTransaction(nativePtr); invokeSchemaChangeListenerIfSchemaChanged(); - enableCollectionSnapshot(); } public void commitTransaction() { From c3f6ac0bcc10def414768a1aae1987e47cbc5cdd Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 11:49:40 +0800 Subject: [PATCH 0325/2110] Fix Collection's deletion APIs --- .../src/main/cpp/io_realm_internal_Collection.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 1792444564..c6b60da8e0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -80,6 +80,13 @@ struct ResultsWrapper { } } + // TODO: This is for deletion related APIs on the Collection. This is not efficient at all since it moves and + // and creates TableView. Expose the reference of snapshot's TableView from Results and use that instead. + inline void refresh_snapshot() + { + m_snapshot = m_results.snapshot(); + } + inline bool is_detached() { return m_snapshot.get_mode() != Results::Mode::Empty; @@ -199,7 +206,7 @@ Java_io_realm_internal_Collection_nativeClear(JNIEnv *env, jclass, jlong native_ auto wrapper = reinterpret_cast(native_ptr); wrapper->get_results().clear(); // Refresh snapshot - wrapper->switch_to_snapshot(); + wrapper->refresh_snapshot(); } CATCH_STD() } @@ -402,7 +409,7 @@ Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv *env, jclass, jlong na if (wrapper->get_results().size() > 0) { wrapper->get_results().get_tableview().remove_last(); // Refresh snapshot - wrapper->switch_to_snapshot(); + wrapper->refresh_snapshot(); return JNI_TRUE; } } CATCH_STD() @@ -420,7 +427,7 @@ Java_io_realm_internal_Collection_nativeDeleteFirst(JNIEnv *env, jclass, jlong n if (wrapper->get_results().size() > 0) { wrapper->get_results().get_tableview().remove(0); // Refresh snapshot - wrapper->switch_to_snapshot(); + wrapper->refresh_snapshot(); return JNI_TRUE; } } CATCH_STD() @@ -442,7 +449,7 @@ Java_io_realm_internal_Collection_nativeDelete(JNIEnv *env, jclass, jlong native } view.remove(static_cast(index)); // Refresh snapshot - wrapper->switch_to_snapshot(); + wrapper->refresh_snapshot(); } CATCH_STD() } From e28abca2c7d87a4c44a5a2e42f2ed21fda9c709a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 15:36:05 +0800 Subject: [PATCH 0326/2110] Tests for SortDescriptor Also adding hasSearchIndex to fieldDescriptor for throwing a better exception message. --- .../realm/internal/SortDescriptorTests.java | 284 ++++++++++++++++++ .../io/realm/internal/FieldDescriptor.java | 28 +- .../io/realm/internal/SortDescriptor.java | 50 +-- 3 files changed, 331 insertions(+), 31 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java new file mode 100644 index 0000000000..017cd19274 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java @@ -0,0 +1,284 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +import io.realm.RealmConfiguration; +import io.realm.RealmFieldType; +import io.realm.Sort; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class SortDescriptorTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private SharedRealm sharedRealm; + private Table table; + + @Before + public void setUp() { + RealmConfiguration config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); + sharedRealm.beginTransaction(); + table = sharedRealm.getTable("test_table"); + } + + @After + public void tearDown() { + sharedRealm.close(); + } + + @Test + public void getInstanceForDistinct() { + for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { + long column = table.addColumn(type, type.name()); + table.addSearchIndex(column); + } + + long i = 0; + for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(table, type.name()); + assertEquals(1, sortDescriptor.getColumnIndices()[0].length); + assertEquals(i, sortDescriptor.getColumnIndices()[0][0]); + assertNull(sortDescriptor.getAscendings()); + i++; + } + } + + @Test + public void getInstanceForDistinct_linkField() { + for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { + long column = table.addColumn(type, type.name()); + table.addSearchIndex(column); + } + RealmFieldType objectType = RealmFieldType.OBJECT; + long columnLink = table.addColumnLink(objectType, objectType.name(), table); + + long i = 0; + for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(table, + String.format("%s.%s", objectType.name(), type.name())); + assertEquals(2, sortDescriptor.getColumnIndices()[0].length); + assertEquals(columnLink, sortDescriptor.getColumnIndices()[0][0]); + assertEquals(i, sortDescriptor.getColumnIndices()[0][1]); + assertNull(sortDescriptor.getAscendings()); + i++; + } + } + + @Test + public void getInstanceForDistinct_multipleFields() { + RealmFieldType stringType = RealmFieldType.STRING; + long stringColumn = table.addColumn(stringType, stringType.name()); + table.addSearchIndex(stringColumn); + RealmFieldType intType = RealmFieldType.INTEGER; + long intColumn = table.addColumn(intType, intType.name()); + table.addSearchIndex(intColumn); + + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(table, new String[] { + stringType.name(), intType.name()}); + assertEquals(2, sortDescriptor.getColumnIndices().length); + assertNull(sortDescriptor.getAscendings()); + assertEquals(1, sortDescriptor.getColumnIndices()[0].length); + assertEquals(stringColumn, sortDescriptor.getColumnIndices()[0][0]); + assertEquals(1, sortDescriptor.getColumnIndices()[1].length); + assertEquals(intColumn, sortDescriptor.getColumnIndices()[1][0]); + + } + + @Test + public void getInstanceForDistinct_shouldThrowIfNoSearchIndex() { + RealmFieldType type = RealmFieldType.STRING; + table.addColumn(type, type.name()); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("must be indexed"); + SortDescriptor.getInstanceForDistinct(table, type.name()); + } + + @Test + public void getInstanceForDistinct_shouldThrowOnInvalidField() { + List types = new ArrayList(); + for (RealmFieldType type : RealmFieldType.values()) { + if (!SortDescriptor.validFieldTypesForDistinct.contains(type) && + type != RealmFieldType.UNSUPPORTED_DATE && + type != RealmFieldType.UNSUPPORTED_TABLE&& + type != RealmFieldType.UNSUPPORTED_MIXED) { + if (type == RealmFieldType.LIST || type == RealmFieldType.OBJECT) { + table.addColumnLink(type, type.name(), table); + } else { + table.addColumn(type, type.name()); + } + types.add(type); + } + } + + for (RealmFieldType type : types) { + try { + SortDescriptor.getInstanceForDistinct(table, type.name()); + fail(); + } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains("Distinct is not supported")); + } + } + } + + @Test + public void getInstanceForDistinct_shouldThrowOnLinkListField() { + RealmFieldType type = RealmFieldType.STRING; + RealmFieldType listType = RealmFieldType.LIST; + table.addColumn(type, type.name()); + table.addColumnLink(listType, listType.name(), table); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("is not a supported link field"); + SortDescriptor.getInstanceForDistinct(table, String.format("%s.%s", listType.name(), type.name())); + } + + @Test + public void getInstanceForSort() { + for (RealmFieldType type : SortDescriptor.validFieldTypesForSort) { + table.addColumn(type, type.name()); + } + + long i = 0; + for (RealmFieldType type : SortDescriptor.validFieldTypesForSort) { + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(table, type.name(), Sort.DESCENDING); + assertEquals(1, sortDescriptor.getColumnIndices()[0].length); + assertEquals(i, sortDescriptor.getColumnIndices()[0][0]); + assertFalse(sortDescriptor.getAscendings()[0]); + i++; + } + } + + @Test + public void getInstanceForSort_linkField() { + for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { + long column = table.addColumn(type, type.name()); + table.addSearchIndex(column); + } + RealmFieldType objectType = RealmFieldType.OBJECT; + long columnLink = table.addColumnLink(objectType, objectType.name(), table); + + long i = 0; + for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(table, + String.format("%s.%s", objectType.name(), type.name()), Sort.ASCENDING); + assertEquals(2, sortDescriptor.getColumnIndices()[0].length); + assertEquals(columnLink, sortDescriptor.getColumnIndices()[0][0]); + assertEquals(i, sortDescriptor.getColumnIndices()[0][1]); + assertTrue(sortDescriptor.getAscendings()[0]); + i++; + } + } + + @Test + public void getInstanceForSort_multipleFields() { + RealmFieldType stringType = RealmFieldType.STRING; + long stringColumn = table.addColumn(stringType, stringType.name()); + RealmFieldType intType = RealmFieldType.INTEGER; + long intColumn = table.addColumn(intType, intType.name()); + + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(table, new String[] { + stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING, Sort.DESCENDING}); + + assertEquals(2, sortDescriptor.getAscendings().length); + assertEquals(2, sortDescriptor.getColumnIndices().length); + + assertEquals(1, sortDescriptor.getColumnIndices()[0].length); + assertEquals(stringColumn, sortDescriptor.getColumnIndices()[0][0]); + assertTrue(sortDescriptor.getAscendings()[0]); + + assertEquals(1, sortDescriptor.getColumnIndices()[1].length); + assertEquals(intColumn, sortDescriptor.getColumnIndices()[1][0]); + assertFalse(sortDescriptor.getAscendings()[1]); + + } + + @Test + public void getInstanceForSort_numOfFeildsAndSortOrdersNotMatch() { + RealmFieldType stringType = RealmFieldType.STRING; + table.addColumn(stringType, stringType.name()); + RealmFieldType intType = RealmFieldType.INTEGER; + table.addColumn(intType, intType.name()); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Number of fields and sort orders do not match."); + SortDescriptor.getInstanceForSort(table, new String[] { + stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING}); + + } + + @Test + public void getInstanceForSort_shouldThrowOnInvalidField() { + List types = new ArrayList(); + for (RealmFieldType type : RealmFieldType.values()) { + if (!SortDescriptor.validFieldTypesForSort.contains(type) && + type != RealmFieldType.UNSUPPORTED_DATE && + type != RealmFieldType.UNSUPPORTED_TABLE&& + type != RealmFieldType.UNSUPPORTED_MIXED) { + if (type == RealmFieldType.LIST || type == RealmFieldType.OBJECT) { + table.addColumnLink(type, type.name(), table); + } else { + table.addColumn(type, type.name()); + } + types.add(type); + } + } + + for (RealmFieldType type : types) { + try { + SortDescriptor.getInstanceForSort(table, type.name(), Sort.ASCENDING); + fail(); + } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains("Sort is not supported")); + } + } + } + + @Test + public void getInstanceForSort_shouldThrowOnLinkListField() { + RealmFieldType type = RealmFieldType.STRING; + RealmFieldType listType = RealmFieldType.LIST; + table.addColumn(type, type.name()); + table.addColumnLink(listType, listType.name(), table); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("is not a supported link field"); + SortDescriptor.getInstanceForSort(table, String.format("%s.%s", listType.name(), type.name()), Sort.ASCENDING); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java index 6cfdd6b884..9d196804c6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java @@ -20,8 +20,9 @@ public class FieldDescriptor { private long[] columnIndices; - private RealmFieldType lastFieldType; - private String lastFieldName; + private RealmFieldType fieldType; + private String fieldName; + private boolean searchIndex; public FieldDescriptor(Table table, String fieldDescription, boolean allowList) { if (fieldDescription == null || fieldDescription.isEmpty()) { @@ -51,7 +52,6 @@ public FieldDescriptor(Table table, String fieldDescription, boolean allowList) throw new IllegalArgumentException( String.format("Invalid field name: '%s' does not refer to a class.", names[i])); } - // TODO: Check search index for distinct? } // Check if last field name is a valid field @@ -63,17 +63,19 @@ public FieldDescriptor(Table table, String fieldDescription, boolean allowList) String.format("'%s' is not a field name in class '%s'.", columnName, table.getName())); } - this.lastFieldType = table.getColumnType(columnIndex); - this.lastFieldName = columnName; + this.fieldType = table.getColumnType(columnIndex); + this.fieldName = columnName; this.columnIndices = columnIndices; + this.searchIndex = table.hasSearchIndex(columnIndex); } else { long fieldIndex = table.getColumnIndex(fieldDescription); if (fieldIndex == Table.NO_MATCH) { throw new IllegalArgumentException(String.format("Field '%s' does not exist.", fieldDescription)); } - this.lastFieldType = table.getColumnType(fieldIndex); - this.lastFieldName = fieldDescription; + this.fieldType = table.getColumnType(fieldIndex); + this.fieldName = fieldDescription; this.columnIndices = new long[] {fieldIndex}; + this.searchIndex = table.hasSearchIndex(fieldIndex); } } @@ -81,11 +83,15 @@ public long[] getColumnIndices() { return columnIndices; } - public RealmFieldType getLastFieldType() { - return lastFieldType; + public RealmFieldType getFieldType() { + return fieldType; } - public String getLastFieldName() { - return lastFieldName; + public String getFieldName() { + return fieldName; + } + + public boolean hasSearchIndex() { + return searchIndex; } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java index 1b14544273..5ca4b97197 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java @@ -17,6 +17,8 @@ package io.realm.internal; import java.io.*; +import java.util.Arrays; +import java.util.List; import io.realm.RealmFieldType; import io.realm.Sort; @@ -26,13 +28,11 @@ public class SortDescriptor implements Closeable { private final long[][] columnIndices; private final boolean[] ascendings; private long nativePtr = 0; - private final static RealmFieldType[] validFieldTypesForSort = new RealmFieldType[] { + final static List validFieldTypesForSort = Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, - RealmFieldType.STRING, RealmFieldType.DATE - }; - private final static RealmFieldType[] validFieldTypesForDistinct = new RealmFieldType[] { - RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE - }; + RealmFieldType.STRING, RealmFieldType.DATE); + final static List validFieldTypesForDistinct = Arrays.asList( + RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE); // Internal use only. For JNI testing. SortDescriptor(Table table, long[] columnIndices) { @@ -76,7 +76,7 @@ public static SortDescriptor getInstanceForSort(Table table, String[] fieldDescr long[][] columnIndices = new long[fieldDescriptions.length][]; for (int i = 0; i < fieldDescriptions.length; i++) { FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], false); - checkFieldTypeForSort(descriptor.getLastFieldType(), descriptor.getLastFieldName(), fieldDescriptions[i]); + checkFieldTypeForSort(descriptor, fieldDescriptions[i]); columnIndices[i] = descriptor.getColumnIndices(); } @@ -95,8 +95,7 @@ public static SortDescriptor getInstanceForDistinct(Table table, String[] fieldD long[][] columnIndices = new long[fieldDescriptions.length][]; for (int i = 0; i < fieldDescriptions.length; i++) { FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], false); - checkFieldTypeForDistinct( - descriptor.getLastFieldType(), descriptor.getLastFieldName(), fieldDescriptions[i]); + checkFieldTypeForDistinct(descriptor, fieldDescriptions[i]); columnIndices[i] = descriptor.getColumnIndices(); } @@ -107,25 +106,36 @@ public long getNativePtr() { return nativePtr; } - private static void checkFieldTypeForSort(RealmFieldType type, String fieldName, String fieldDescriptions) { + private static void checkFieldTypeForSort(FieldDescriptor descriptor, String fieldDescriptions) { for (RealmFieldType aValidFieldTypesForSort : validFieldTypesForSort) { - if (aValidFieldTypesForSort == type) { + if (aValidFieldTypesForSort == descriptor.getFieldType()) { return; } } throw new IllegalArgumentException(String.format( - "Sort is not supported on '%s' field '%s' in '%s'.", type.toString(), fieldName, fieldDescriptions)); + "Sort is not supported on '%s' field '%s' in '%s'.", descriptor.toString(), descriptor.getFieldName(), + fieldDescriptions)); } - private static void checkFieldTypeForDistinct(RealmFieldType type, String fieldName, String fieldDescriptions) { - for (RealmFieldType aValidFieldTypesForSort : validFieldTypesForDistinct) { - if (aValidFieldTypesForSort == type) { - return; - } + private static void checkFieldTypeForDistinct(FieldDescriptor descriptor, String fieldDescriptions) { + if (!validFieldTypesForDistinct.contains(descriptor.getFieldType())) { + throw new IllegalArgumentException(String.format( + "Distinct is not supported on '%s' field '%s' in '%s'.", + descriptor.getFieldType().toString(), descriptor.getFieldName(), fieldDescriptions)); } - throw new IllegalArgumentException(String.format( - "Distinct is not supported on '%s' field '%s' in '%s'.", - type.toString(), fieldName, fieldDescriptions)); + if (!descriptor.hasSearchIndex()) { + throw new IllegalArgumentException(String.format( + "Field '%s' in '%s' must be indexed in order to use it for distinct queries.", + descriptor.getFieldName(), fieldDescriptions)); + } + } + + long[][] getColumnIndices() { + return columnIndices; + } + + boolean[] getAscendings() { + return ascendings; } @Override From 3f2a47f479e0aa6348d8984003a31c6c206a4dd9 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 17:21:41 +0800 Subject: [PATCH 0327/2110] Don't hold pointer of SortDescriptor in java instead, create a cpp SortDescriptor from java SortDescriptor object only when needed. ObjectStore always consume the SortDescriptor with move construction, holding a pointer makes less sense. And column indices could be changed when schema changes, SortDescriptor should not be a long live object in java. --- .../io/realm/internal/CollectionTests.java | 39 +++++------ .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../src/main/cpp/io_realm_SortDescriptor.cpp | 48 -------------- .../main/cpp/io_realm_internal_Collection.cpp | 15 ++--- .../src/main/cpp/java_sort_descriptor.cpp | 66 +++++++++++++++++++ .../src/main/cpp/java_sort_descriptor.hpp | 49 ++++++++++++++ .../src/main/java/io/realm/RealmResults.java | 45 +++++-------- .../java/io/realm/internal/Collection.java | 10 +-- .../io/realm/internal/SortDescriptor.java | 42 ++++++------ 9 files changed, 183 insertions(+), 133 deletions(-) delete mode 100644 realm/realm-library/src/main/cpp/io_realm_SortDescriptor.cpp create mode 100644 realm/realm-library/src/main/cpp/java_sort_descriptor.cpp create mode 100644 realm/realm-library/src/main/cpp/java_sort_descriptor.hpp diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 5df7fa8635..6be30801e5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -172,19 +172,16 @@ public void where() { public void sort() { Collection collection = new Collection(sharedRealm, table.where()); SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); - try { - Collection collection2 =collection.sort(sortDescriptor); - // A new native Results should be created. - assertTrue(collection.getNativePtr() != collection2.getNativePtr()); - assertEquals(4, collection.size()); - assertEquals(4, collection2.size()); + Collection collection2 =collection.sort(sortDescriptor); - assertEquals(collection2.getUncheckedRow(0).getLong(2), 1); - assertEquals(collection2.getUncheckedRow(3).getLong(2), 4); - } finally { - sortDescriptor.close(); - } + // A new native Results should be created. + assertTrue(collection.getNativePtr() != collection2.getNativePtr()); + assertEquals(4, collection.size()); + assertEquals(4, collection2.size()); + + assertEquals(collection2.getUncheckedRow(0).getLong(2), 1); + assertEquals(collection2.getUncheckedRow(3).getLong(2), 4); } @Test @@ -207,24 +204,18 @@ public void contains() { @Test public void indexOf() { SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); - try { - Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); - UncheckedRow row = table.getUncheckedRow(0); - assertEquals(collection.indexOf(row), 3); - } finally { - sortDescriptor.close(); - } + + Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); + UncheckedRow row = table.getUncheckedRow(0); + assertEquals(collection.indexOf(row), 3); } @Test public void indexOf_long() { SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); - try { - Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); - assertEquals(collection.indexOf(0), 3); - } finally { - sortDescriptor.close(); - } + + Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); + assertEquals(collection.indexOf(0), 3); } @Test diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 914477ab9d..f4942fa27c 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -37,7 +37,7 @@ set(classes_LIST io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema - io.realm.RealmObjectSchema io.realm.internal.Collection io.realm.internal.SortDescriptor + io.realm.RealmObjectSchema io.realm.internal.Collection io.realm.internal.NativeObjectReference ) # /./ is the workaround for the problem that AS cannot find the jni headers. diff --git a/realm/realm-library/src/main/cpp/io_realm_SortDescriptor.cpp b/realm/realm-library/src/main/cpp/io_realm_SortDescriptor.cpp deleted file mode 100644 index 7dcc8ef239..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_SortDescriptor.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "io_realm_internal_SortDescriptor.h" - -#include - -#include "util.hpp" - -using namespace realm; - -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SortDescriptor_nativeCreate(JNIEnv* env, jclass, jlong table_ptr, jobjectArray column_indices, - jbooleanArray ascending) -{ - try { - JniArrayOfArrays arrays(env, column_indices); - JniBooleanArray ascending_array(env, ascending); - jsize arr_len = arrays.len(); - - std::vector> indices; - std::vector ascending_list; - - for (int i = 0; i < arr_len; ++i) { - JniLongArray& jni_long_array = arrays[i]; - std::vector col_indices; - for (int j = 0; j < jni_long_array.len(); ++j) { - col_indices.push_back(static_cast(jni_long_array[j])); - } - indices.push_back(std::move(col_indices)); - if (ascending) { - ascending_list.push_back(static_cast(ascending_array[i])); - } - } - - SortDescriptor* descriptor = ascending ? - new SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices), std::move(ascending_list)) - : new SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices)); - return reinterpret_cast(descriptor); - } CATCH_STD() - - return reinterpret_cast(nullptr); -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_SortDescriptor_nativeClose(JNIEnv* env, jclass, jlong ptr) { - try { - SortDescriptor* descriptor = reinterpret_cast(ptr); - delete descriptor; - } CATCH_STD() -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index c6b60da8e0..7a2fe1c74f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -23,10 +23,12 @@ #include #include "util.hpp" +#include "java_sort_descriptor.hpp" #include "jni_util/method.hpp" using namespace realm; using namespace realm::jni_util; +using namespace realm::_impl; // We need to control the life cycle of Results, weak ref of Java Collection object and the NotificationToken. // Wrap all three together, so when the Java Collection object gets GCed, all three of them will be invalidated. @@ -107,7 +109,7 @@ static void finalize_results(jlong ptr) JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong query_ptr, - jlong sort_desc_native_ptr, jlong distinct_desc_native_ptr) + jobject sort_desc, jobject distinct_desc) { TR_ENTER() try { @@ -117,11 +119,9 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong } auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto sort_desc_ptr = reinterpret_cast(sort_desc_native_ptr); - auto distinct_desc_ptr = reinterpret_cast(distinct_desc_native_ptr); Results results(shared_realm, *query, - sort_desc_ptr ? *sort_desc_ptr : SortDescriptor(), - distinct_desc_ptr ? *distinct_desc_ptr : SortDescriptor()); + SortDescriptor(JavaSortDescriptor(env, sort_desc)), + SortDescriptor(JavaSortDescriptor(env, distinct_desc))); auto wrapper = new ResultsWrapper(std::move(results)); if (shared_realm->is_in_transaction()) { wrapper->switch_to_snapshot(); @@ -274,13 +274,12 @@ Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong nat } JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jlong sort_desc_native_ptr) +Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jobject sort_desc) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto sort_descriptor = *reinterpret_cast(sort_desc_native_ptr); - auto sorted_result = wrapper->get_results().sort(std::move(sort_descriptor)); + auto sorted_result = wrapper->get_results().sort(JavaSortDescriptor(env, sort_desc)); return reinterpret_cast(new ResultsWrapper(std::move(sorted_result))); } CATCH_STD() return reinterpret_cast(nullptr); diff --git a/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp b/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp new file mode 100644 index 0000000000..1e3f05050d --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 "java_sort_descriptor.hpp" +#include "jni_util/method.hpp" + +using namespace realm; +using namespace realm::_impl; +using namespace realm::jni_util; + +JavaSortDescriptor::operator realm::SortDescriptor() const noexcept +{ + if (m_sort_desc_obj == nullptr) { + return SortDescriptor(); + } + + // Cache the method IDs. + static JniMethod getColumnIndices = JniMethod(m_env, m_sort_desc_obj, "getColumnIndices", "()[[J"); + static JniMethod getAscendings = JniMethod(m_env, m_sort_desc_obj, "getAscendings", "()[Z"); + static JniMethod getTablePtr = JniMethod(m_env, m_sort_desc_obj, "getTablePtr", "()J"); + + jobjectArray column_indices = + static_cast(m_env->CallObjectMethod(m_sort_desc_obj, getColumnIndices)); + jbooleanArray ascendings = + static_cast(m_env->CallObjectMethod(m_sort_desc_obj, getAscendings)); + jlong table_ptr = m_env->CallLongMethod(m_sort_desc_obj, getTablePtr); + + JniArrayOfArrays arrays(m_env, column_indices); + JniBooleanArray ascending_array(m_env, ascendings); + jsize arr_len = arrays.len(); + + std::vector> indices; + std::vector ascending_list; + + for (int i = 0; i < arr_len; ++i) { + JniLongArray& jni_long_array = arrays[i]; + std::vector col_indices; + for (int j = 0; j < jni_long_array.len(); ++j) { + col_indices.push_back(static_cast(jni_long_array[j])); + } + indices.push_back(std::move(col_indices)); + if (ascendings) { + ascending_list.push_back(static_cast(ascending_array[i])); + } + } + + return ascendings ? + SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices), std::move(ascending_list)) + : SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices)); +} + + diff --git a/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp b/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp new file mode 100644 index 0000000000..8c26543e65 --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +#ifndef JAVA_SORT_DESCRIPTOR_HPP +#define JAVA_SORT_DESCRIPTOR_HPP + +#include +#include + +namespace realm { +namespace _impl { + +// For converting a Java SortDescriptor object to realm::SortDescriptor. +// This class is not designed to be used across JNI calls. So it doesn't acquire a reference to the given Java object. +// We don't holding a pointer to the SortDescriptor in the Java object like normally we do is because of the ObjectStore +// always consume the SortDescriptor by calling the move constructor. Holding a empty SortDescriptor in Java level +// doesn't make too much sense and causes troubles with memory management. +class JavaSortDescriptor { +public: + JavaSortDescriptor(JNIEnv* env, jobject sort_desc_obj) : m_env(env), m_sort_desc_obj(sort_desc_obj) {} + + JavaSortDescriptor(const JavaSortDescriptor&) = delete; + JavaSortDescriptor& operator=(const JavaSortDescriptor&) = delete; + JavaSortDescriptor(JavaSortDescriptor&&) = delete; + JavaSortDescriptor& operator=(JavaSortDescriptor&&) = delete; + + operator realm::SortDescriptor() const noexcept; + +private: + JNIEnv* m_env; + jobject m_sort_desc_obj; +}; + +} // namespace _impl +} // namespace realm +#endif //JAVA_SORT_DESCRIPTOR_HPP diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 909578d8e2..199467274e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -301,15 +301,12 @@ private long getColumnIndexForSort(String fieldName) { public RealmResults sort(String fieldName) { SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, Sort.ASCENDING); - try { - Collection sortedCollection = collection.sort(sortDescriptor); - if (className != null) { - return new RealmResults(realm, sortedCollection, className); - } else { - return new RealmResults(realm, sortedCollection, classSpec); - } - } finally { - sortDescriptor.close(); + + Collection sortedCollection = collection.sort(sortDescriptor); + if (className != null) { + return new RealmResults(realm, sortedCollection, className); + } else { + return new RealmResults(realm, sortedCollection, classSpec); } } @@ -320,15 +317,12 @@ public RealmResults sort(String fieldName) { public RealmResults sort(String fieldName, Sort sortOrder) { SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, sortOrder); - try { - Collection sortedCollection = collection.sort(sortDescriptor); - if (className != null) { - return new RealmResults(realm, sortedCollection, className); - } else { - return new RealmResults(realm, sortedCollection, classSpec); - } - } finally { - sortDescriptor.close(); + + Collection sortedCollection = collection.sort(sortDescriptor); + if (className != null) { + return new RealmResults(realm, sortedCollection, className); + } else { + return new RealmResults(realm, sortedCollection, classSpec); } } @@ -339,15 +333,12 @@ public RealmResults sort(String fieldName, Sort sortOrder) { public RealmResults sort(String fieldNames[], Sort sortOrders[]) { SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(collection.getTable(), fieldNames, sortOrders); - try { - Collection sortedCollection = collection.sort(sortDescriptor); - if (className != null) { - return new RealmResults(realm, sortedCollection, className); - } else { - return new RealmResults(realm, sortedCollection, classSpec); - } - } finally { - sortDescriptor.close(); + + Collection sortedCollection = collection.sort(sortDescriptor); + if (className != null) { + return new RealmResults(realm, sortedCollection, className); + } else { + return new RealmResults(realm, sortedCollection, classSpec); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 2956b93fe7..c91eff3f06 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -85,8 +85,8 @@ public Collection(SharedRealm sharedRealm, TableQuery query, query.validateQuery(); this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), - sortDescriptor == null ? 0 : sortDescriptor.getNativePtr(), - distinctDescriptor == null ? 0 : distinctDescriptor.getNativePtr()); + sortDescriptor, + distinctDescriptor); this.sharedRealm = sharedRealm; this.context = sharedRealm.context; @@ -162,7 +162,7 @@ public void clear() { } public Collection sort(SortDescriptor sortDescriptor) { - return new Collection(sharedRealm, query, nativeSort(nativePtr, sortDescriptor.getNativePtr())); + return new Collection(sharedRealm, query, nativeSort(nativePtr, sortDescriptor)); } public boolean contains(UncheckedRow row) { @@ -241,7 +241,7 @@ boolean isDetached() { private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, - long sortDescNativePtr, long distinctDescNativePtr); + SortDescriptor sortDesc, SortDescriptor distinctDesc); @SuppressWarnings("unused") // Not used for now private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); @@ -251,7 +251,7 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native void nativeClear(long nativePtr); private static native long nativeSize(long nativePtr); private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); - private static native long nativeSort(long nativePtr, long sortDescNativePtr); + private static native long nativeSort(long nativePtr, SortDescriptor sortDesc); private static native boolean nativeDeleteFirst(long nativePtr); private static native boolean nativeDeleteLast(long nativePtr); private static native void nativeDelete(long nativePtr, long index); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java index 5ca4b97197..051149f8f6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java @@ -16,18 +16,27 @@ package io.realm.internal; -import java.io.*; import java.util.Arrays; import java.util.List; import io.realm.RealmFieldType; import io.realm.Sort; -public class SortDescriptor implements Closeable { +/** + * Java class to present the same name core class in Java. This can be converted to a cpp realm::SortDescriptor object + * through realm::_impl::JavaSortDescriptor. + *

          + * NOTE: Since the column indices are determined when constructing the object with the given table's status, the indices + * could be wrong when schema changes. Always create and consume the instance when needed, DON'T store a SortDescriptor + * and use it whenever the ShareGroup can be in different versions. + */ +@KeepMember +public class SortDescriptor { private final long[][] columnIndices; private final boolean[] ascendings; - private long nativePtr = 0; + private final Table table; + final static List validFieldTypesForSort = Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, RealmFieldType.STRING, RealmFieldType.DATE); @@ -39,11 +48,6 @@ public class SortDescriptor implements Closeable { this(table, new long[][] {columnIndices}, null); } - // Internal use only. For JNI testing. - SortDescriptor(Table table, long[] columnIndices, Sort sortOrder) { - this(table, new long[][] {columnIndices}, new Sort[] {sortOrder}); - } - private SortDescriptor(Table table, long[][] columnIndices, Sort[] sortOrders) { if (sortOrders != null) { ascendings = new boolean[sortOrders.length]; @@ -55,7 +59,7 @@ private SortDescriptor(Table table, long[][] columnIndices, Sort[] sortOrders) { } this.columnIndices = columnIndices; - nativePtr = nativeCreate(table.getNativePtr(), columnIndices, ascendings); + this.table = table; } public static SortDescriptor getInstanceForSort(Table table, String fieldDescription, Sort sortOrder) { @@ -102,10 +106,6 @@ public static SortDescriptor getInstanceForDistinct(Table table, String[] fieldD return new SortDescriptor(table, columnIndices, null); } - public long getNativePtr() { - return nativePtr; - } - private static void checkFieldTypeForSort(FieldDescriptor descriptor, String fieldDescriptions) { for (RealmFieldType aValidFieldTypesForSort : validFieldTypesForSort) { if (aValidFieldTypesForSort == descriptor.getFieldType()) { @@ -130,20 +130,22 @@ private static void checkFieldTypeForDistinct(FieldDescriptor descriptor, String } } + // Called by JNI. + @KeepMember long[][] getColumnIndices() { return columnIndices; } + // Called by JNI. + @KeepMember boolean[] getAscendings() { return ascendings; } - @Override - public void close() { - nativeClose(nativePtr); - nativePtr = 0; + // Called by JNI. + @KeepMember + @SuppressWarnings("unused") + private long getTablePtr() { + return table.getNativePtr(); } - - private static native long nativeCreate(long tablePtr, long[][] columnIndices, boolean[] ascending); - private static native void nativeClose(long ptr); } From a754facddf83aa7eb794e338a81664e3a7fb76c4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 17:56:57 +0800 Subject: [PATCH 0328/2110] Fix distinctAsync tests distinct is async by default, and distinctAsync is deprecated. --- .../java/io/realm/RealmQueryTests.java | 190 ++++-------------- 1 file changed, 40 insertions(+), 150 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 4b3cae293c..1946af60ef 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2796,42 +2796,23 @@ public void distinct_invalidTypesLinkedFields() { } } - // distinctAsync - private Realm openRealmInstance(String name) { - RealmConfiguration config = configFactory.createConfiguration(name); - Realm.deleteRealm(config); - return Realm.getInstance(config); - } - @Test @RunTestInLooperThread - public void distinctAsync() throws Throwable { + public void distinct_async() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); final Realm realm = looperThread.realm; final long numberOfBlocks = 25; final long numberOfObjects = 10; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_LONG); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); - - assertFalse(distinctBool.isLoaded()); - assertTrue(distinctBool.isValid()); - assertTrue(distinctBool.isEmpty()); - - assertFalse(distinctLong.isLoaded()); - assertTrue(distinctLong.isValid()); - assertTrue(distinctLong.isEmpty()); - - assertFalse(distinctDate.isLoaded()); - assertTrue(distinctDate.isValid()); - assertTrue(distinctDate.isEmpty()); - - assertFalse(distinctString.isLoaded()); - assertTrue(distinctString.isValid()); - assertTrue(distinctString.isEmpty()); + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) + .distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class) + .distinct(AnnotationIndexTypes.FIELD_INDEX_LONG); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) + .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) + .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING); final Runnable endTest = new Runnable() { @Override @@ -2880,137 +2861,46 @@ public void onChange(RealmResults object) { } @Test - public void distinctAsync_withNullValues() throws Throwable { - final CountDownLatch signalCallbackFinished = new CountDownLatch(2); - final CountDownLatch signalClosedRealm = new CountDownLatch(1); - final Throwable[] threadAssertionError = new Throwable[1]; - final Looper[] backgroundLooper = new Looper[1]; - final ExecutorService executorService = Executors.newSingleThreadExecutor(); - executorService.submit(new Runnable() { - @Override - public void run() { - Looper.prepare(); - backgroundLooper[0] = Looper.myLooper(); - - Realm asyncRealm = null; - try { - Realm.asyncTaskExecutor.pause(); - asyncRealm = openRealmInstance("testDistinctAsyncQueryWithNull"); - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // must be greater than 1 - populateForDistinct(asyncRealm, numberOfBlocks, numberOfObjects, true); - - final RealmResults distinctDate = asyncRealm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = asyncRealm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); - - assertFalse(distinctDate.isLoaded()); - assertTrue(distinctDate.isValid()); - assertTrue(distinctDate.isEmpty()); - - assertFalse(distinctString.isLoaded()); - assertTrue(distinctString.isValid()); - assertTrue(distinctString.isEmpty()); - - Realm.asyncTaskExecutor.resume(); - - distinctDate.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(1, distinctDate.size()); - signalCallbackFinished.countDown(); - } - }); - - distinctString.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(1, distinctString.size()); - signalCallbackFinished.countDown(); - } - }); + @RunTestInLooperThread + public void distinct_async_withNullValues() throws Throwable { + final AtomicInteger changeListenerCalled = new AtomicInteger(2); + final Realm realm = looperThread.realm; + final long numberOfBlocks = 25; + final long numberOfObjects = 10; // must be greater than 1 + populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - Looper.loop(); - } catch (Throwable e) { - e.printStackTrace(); - threadAssertionError[0] = e; + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) + .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) + .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING); - } finally { - if (signalCallbackFinished.getCount() > 0) { - signalCallbackFinished.countDown(); - } - if (asyncRealm != null) { - asyncRealm.close(); - } - signalClosedRealm.countDown(); + final Runnable endTest = new Runnable() { + @Override + public void run() { + if (changeListenerCalled.decrementAndGet() == 0) { + looperThread.testComplete(); } } - }); - - TestHelper.exitOrThrow(executorService, signalCallbackFinished, signalClosedRealm, backgroundLooper, threadAssertionError); - } - - @Test - public void distinctAsync_notIndexedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).distinctAsync(field); - fail(field); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void distinctAsync_doesNotExist() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - try { - realm.where(AnnotationIndexTypes.class).distinctAsync("doesNotExist"); - } catch (IllegalArgumentException ignored) { - } - } + }; - @Test - public void distinctAsync_invalidTypes() { - populateTestRealm(realm, TEST_DATA_SIZE); + looperThread.keepStrongReference.add(distinctDate); + looperThread.keepStrongReference.add(distinctString); - for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { - try { - realm.where(AllTypes.class).distinctAsync(field); - } catch (IllegalArgumentException ignored) { + distinctDate.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(1, distinctDate.size()); + endTest.run(); } - } - } - - @Test - public void distinctAsync_indexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); + }); - for (String field : AnnotationIndexTypes.INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_OBJECT + "." + field); - fail("Unsupported " + field + " linked field"); - } catch (IllegalArgumentException ignored) { + distinctString.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(1, distinctString.size()); + endTest.run(); } - } - } - - @Test - public void distinctAsync_notIndexedLinkedFields() { - populateForDistinctInvalidTypesLinked(realm); - - try { - realm.where(AllJavaTypes.class).distinctAsync(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); - } catch (IllegalArgumentException ignored) { - } + }); } @Test From 4285ba71070272a6f046c0d18e39a37c7ecbdbff Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 18:07:27 +0800 Subject: [PATCH 0329/2110] Disable distinct on link's field The distinct results on link's fields don't seem to be expected. Just throw for now, we can enable it later if needed. --- .../realm/internal/SortDescriptorTests.java | 24 ++++++------------- .../io/realm/internal/FieldDescriptor.java | 11 +++++---- .../io/realm/internal/SortDescriptor.java | 4 ++-- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java index 017cd19274..2fbff84510 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java @@ -80,24 +80,15 @@ public void getInstanceForDistinct() { } @Test - public void getInstanceForDistinct_linkField() { - for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { - long column = table.addColumn(type, type.name()); - table.addSearchIndex(column); - } + public void getInstanceForDistinct_shouldThrowOnLinkField() { + RealmFieldType type = RealmFieldType.STRING; RealmFieldType objectType = RealmFieldType.OBJECT; - long columnLink = table.addColumnLink(objectType, objectType.name(), table); + table.addColumn(type, type.name()); + table.addColumnLink(objectType, objectType.name(), table); - long i = 0; - for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(table, - String.format("%s.%s", objectType.name(), type.name())); - assertEquals(2, sortDescriptor.getColumnIndices()[0].length); - assertEquals(columnLink, sortDescriptor.getColumnIndices()[0][0]); - assertEquals(i, sortDescriptor.getColumnIndices()[0][1]); - assertNull(sortDescriptor.getAscendings()); - i++; - } + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("is not a supported link field"); + SortDescriptor.getInstanceForDistinct(table, String.format("%s.%s", objectType.name(), type.name())); } @Test @@ -117,7 +108,6 @@ public void getInstanceForDistinct_multipleFields() { assertEquals(stringColumn, sortDescriptor.getColumnIndices()[0][0]); assertEquals(1, sortDescriptor.getColumnIndices()[1].length); assertEquals(intColumn, sortDescriptor.getColumnIndices()[1][0]); - } @Test diff --git a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java index 9d196804c6..56a9f1cfe6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java @@ -24,7 +24,7 @@ public class FieldDescriptor { private String fieldName; private boolean searchIndex; - public FieldDescriptor(Table table, String fieldDescription, boolean allowList) { + public FieldDescriptor(Table table, String fieldDescription, boolean allowLink, boolean allowList) { if (fieldDescription == null || fieldDescription.isEmpty()) { throw new IllegalArgumentException("Non-empty field name must be provided"); } @@ -42,12 +42,15 @@ public FieldDescriptor(Table table, String fieldDescription, boolean allowList) String.format("Invalid field name: '%s' does not refer to a class.", names[i])); } RealmFieldType type = table.getColumnType(index); - if (type == RealmFieldType.OBJECT || (allowList && type == RealmFieldType.LIST)) { - table = table.getLinkTarget(index); - columnIndices[i] = index; + if (!allowLink && type == RealmFieldType.OBJECT) { + throw new IllegalArgumentException( + String.format("'RealmObject' field '%s' is not a supported link field here.", names[i])); } else if (!allowList && type == RealmFieldType.LIST) { throw new IllegalArgumentException( String.format("'RealmList' field '%s' is not a supported link field here.", names[i])); + } else if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { + table = table.getLinkTarget(index); + columnIndices[i] = index; } else { throw new IllegalArgumentException( String.format("Invalid field name: '%s' does not refer to a class.", names[i])); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java index 051149f8f6..94a0d2aeb2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java @@ -79,7 +79,7 @@ public static SortDescriptor getInstanceForSort(Table table, String[] fieldDescr long[][] columnIndices = new long[fieldDescriptions.length][]; for (int i = 0; i < fieldDescriptions.length; i++) { - FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], false); + FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], true, false); checkFieldTypeForSort(descriptor, fieldDescriptions[i]); columnIndices[i] = descriptor.getColumnIndices(); } @@ -98,7 +98,7 @@ public static SortDescriptor getInstanceForDistinct(Table table, String[] fieldD long[][] columnIndices = new long[fieldDescriptions.length][]; for (int i = 0; i < fieldDescriptions.length; i++) { - FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], false); + FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], false, false); checkFieldTypeForDistinct(descriptor, fieldDescriptions[i]); columnIndices[i] = descriptor.getColumnIndices(); } From 8963630129972ff3e84be45c99868a061060bf28 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 18:14:13 +0800 Subject: [PATCH 0330/2110] Fix tests for RealmResults.distinctAsync() --- .../java/io/realm/RealmResultsTests.java | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 53413f3ef2..e89217d69f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -417,33 +417,21 @@ private void populateTestRealm(Realm testRealm, int objects) { @Test @RunTestInLooperThread - public void distinctAsync() throws Throwable { + public void distinct_async() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); final Realm realm = looperThread.realm; final long numberOfBlocks = 25; final long numberOfObjects = 10; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_LONG); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); - - assertFalse(distinctBool.isLoaded()); - assertTrue(distinctBool.isValid()); - assertTrue(distinctBool.isEmpty()); - - assertFalse(distinctLong.isLoaded()); - assertTrue(distinctLong.isValid()); - assertTrue(distinctLong.isEmpty()); - - assertFalse(distinctDate.isLoaded()); - assertTrue(distinctDate.isValid()); - assertTrue(distinctDate.isEmpty()); - - assertFalse(distinctString.isLoaded()); - assertTrue(distinctString.isValid()); - assertTrue(distinctString.isEmpty()); + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll() + .distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).findAll() + .distinct(AnnotationIndexTypes.FIELD_INDEX_LONG); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll() + .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll() + .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING); final Runnable endTest = new Runnable() { @Override @@ -493,23 +481,17 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread - public void distinctAsync_withNullValues() throws Throwable { + public void distinct_async_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); final Realm realm = looperThread.realm; final long numberOfBlocks = 25; final long numberOfObjects = 10; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); - - assertFalse(distinctDate.isLoaded()); - assertTrue(distinctDate.isValid()); - assertTrue(distinctDate.isEmpty()); - - assertFalse(distinctString.isLoaded()); - assertTrue(distinctString.isValid()); - assertTrue(distinctString.isEmpty()); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll() + .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll() + .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING); final Runnable endTest = new Runnable() { @Override From d2a6667170baf1c51b9d1ba530848df2f373299d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 19:25:35 +0800 Subject: [PATCH 0331/2110] Don't hold ref to query in Collection When build the OS Results, TableQuery will be consumed through the move constructor. --- .../io/realm/internal/CollectionTests.java | 10 ++++----- .../java/io/realm/internal/Collection.java | 21 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 6be30801e5..316019c5c7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -170,18 +170,18 @@ public void where() { @Test public void sort() { - Collection collection = new Collection(sharedRealm, table.where()); + Collection collection = new Collection(sharedRealm, table.where().greaterThan(new long[]{2}, 1)); SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); Collection collection2 =collection.sort(sortDescriptor); // A new native Results should be created. assertTrue(collection.getNativePtr() != collection2.getNativePtr()); - assertEquals(4, collection.size()); - assertEquals(4, collection2.size()); + assertEquals(2, collection.size()); + assertEquals(2, collection2.size()); - assertEquals(collection2.getUncheckedRow(0).getLong(2), 1); - assertEquals(collection2.getUncheckedRow(3).getLong(2), 4); + assertEquals(collection2.getUncheckedRow(0).getLong(2), 3); + assertEquals(collection2.getUncheckedRow(1).getLong(2), 4); } @Test diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index c91eff3f06..dc253dabfa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -41,7 +41,7 @@ public void onChange(T observer) { private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final SharedRealm sharedRealm; private final Context context; - private final TableQuery query; + private final Table table; private final ObserverPairList observerPairs = new ObserverPairList(); private static final ObserverPairList.Callback onChangeCallback = @@ -90,7 +90,7 @@ public Collection(SharedRealm sharedRealm, TableQuery query, this.sharedRealm = sharedRealm; this.context = sharedRealm.context; - this.query = query; + this.table = query.getTable(); this.context.addReference(this); sharedRealm.addCollection(this); } @@ -103,11 +103,10 @@ public Collection(SharedRealm sharedRealm, TableQuery query) { this(sharedRealm, query, null, null); } - private Collection(SharedRealm sharedRealm, TableQuery query, long nativePtr) { - query.validateQuery(); + private Collection(SharedRealm sharedRealm, Table table, long nativePtr) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; - this.query = query; + this.table = table; this.nativePtr = nativePtr; this.context.addReference(this); @@ -125,24 +124,24 @@ public long getNativeFinalizerPtr() { } public UncheckedRow getUncheckedRow(int index) { - return UncheckedRow.getByRowPointer(query.table, nativeGetRow(nativePtr, index)); + return UncheckedRow.getByRowPointer(table, nativeGetRow(nativePtr, index)); } public UncheckedRow firstUncheckedRow() { - return UncheckedRow.getByRowPointer(query.table, nativeFirstRow(nativePtr)); + return UncheckedRow.getByRowPointer(table, nativeFirstRow(nativePtr)); } public UncheckedRow lastUncheckedRow() { - return UncheckedRow.getByRowPointer(query.table, nativeLastRow(nativePtr)); + return UncheckedRow.getByRowPointer(table, nativeLastRow(nativePtr)); } public Table getTable() { - return query.getTable(); + return table; } public TableQuery where() { long nativeQueryPtr = nativeWhere(nativePtr); - return new TableQuery(this.context, this.getTable(), nativeQueryPtr); + return new TableQuery(this.context, this.table, nativeQueryPtr); } public Number aggregateNumber(Aggregate aggregateMethod, long columnIndex) { @@ -162,7 +161,7 @@ public void clear() { } public Collection sort(SortDescriptor sortDescriptor) { - return new Collection(sharedRealm, query, nativeSort(nativePtr, sortDescriptor)); + return new Collection(sharedRealm, table, nativeSort(nativePtr, sortDescriptor)); } public boolean contains(UncheckedRow row) { From 99012e32b243965e5027ddf5a4609580c4358b90 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 19:51:30 +0800 Subject: [PATCH 0332/2110] RealmResults.distinct() returns a new RealmResults Doing distinct on the original results creates lots of problems: - It is not supported by OS. - It won't play well with fine grained notifications. - It has a different behaviour compared with RealmResults.sort(). --- CHANGELOG.md | 4 +++ .../java/io/realm/RealmResultsTests.java | 6 ++-- .../io/realm/internal/CollectionTests.java | 29 +++++++++++++++---- .../main/cpp/io_realm_internal_Collection.cpp | 11 +++++++ .../src/main/java/io/realm/RealmResults.java | 10 +++++-- .../java/io/realm/internal/Collection.java | 5 ++++ 6 files changed, 54 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index deecc12ea7..7eb9de275a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 2.3.0 +### Breaking changes + +* `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object. + ### Object Server API Changes (In Beta) * Add a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`). diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index e89217d69f..734c129c43 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -244,9 +244,9 @@ public void distinct_restrictedByPreviousDistinct() { // distinctive Booleans RealmResults distinctBooleans = distinctDates.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL); assertEquals("Distinctive Booleans", 2, distinctBooleans.size()); - // all three results are the same object - assertTrue(allResults == distinctDates); - assertTrue(allResults == distinctBooleans); + // distinct results are not the same object + assertTrue(allResults != distinctDates); + assertTrue(allResults != distinctBooleans); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 316019c5c7..f8ff718a30 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -130,6 +130,18 @@ private void removeRow(SharedRealm sharedRealm) { sharedRealm.commitTransaction(); } + @Test + public void constructor_withDistinct() { + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table, "firstName"); + Collection collection = new Collection(sharedRealm, table.where(), null, distinctDescriptor); + + assertEquals(collection.size(), 3); + assertEquals(collection.getUncheckedRow(0).getString(0), "John"); + assertEquals(collection.getUncheckedRow(1).getString(0), "Erik"); + assertEquals(collection.getUncheckedRow(2).getString(0), "Henry"); + } + + @Test(expected = UnsupportedOperationException.class) public void constructor_queryIsValidated() { // Collection's constructor should call TableQuery.validateQuery() @@ -220,13 +232,18 @@ public void indexOf_long() { @Test public void distinct() { - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table, "firstName"); - Collection collection = new Collection(sharedRealm, table.where(), null, distinctDescriptor); + Collection collection = new Collection(sharedRealm, table.where().lessThan(new long[]{2}, 4)); - assertEquals(collection.size(), 3); - assertEquals(collection.getUncheckedRow(0).getString(0), "John"); - assertEquals(collection.getUncheckedRow(1).getString(0), "Erik"); - assertEquals(collection.getUncheckedRow(2).getString(0), "Henry"); + SortDescriptor distinctDescriptor = new SortDescriptor(table, new long[] {2}); + Collection collection2 =collection.distinct(distinctDescriptor); + + // A new native Results should be created. + assertTrue(collection.getNativePtr() != collection2.getNativePtr()); + assertEquals(3, collection.size()); + assertEquals(2, collection2.size()); + + assertEquals(collection2.getUncheckedRow(0).getLong(2), 3); + assertEquals(collection2.getUncheckedRow(1).getLong(2), 1); } // 1. Create a results and add listener. diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 7a2fe1c74f..6834ac29f7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -285,6 +285,17 @@ Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_p return reinterpret_cast(nullptr); } +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeDistinct(JNIEnv *env, jclass, jlong native_ptr, jobject distinct_desc) { + TR_ENTER_PTR(native_ptr) + try { + auto wrapper = reinterpret_cast(native_ptr); + auto distinct_result = wrapper->get_results().distinct(JavaSortDescriptor(env, distinct_desc)); + return reinterpret_cast(new ResultsWrapper(std::move(distinct_result))); + } CATCH_STD() + return reinterpret_cast(nullptr); +} + JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject instance, jlong native_ptr) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 199467274e..38a4453302 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -433,12 +433,18 @@ public double average(String fieldName) { * object is returned. * * @param fieldName the field name. - * @return a non-null {@link RealmResults} containing the distinct objects. + * @return a new non-null {@link RealmResults} containing the distinct objects. * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, * is not indexed, or points to linked fields. */ public RealmResults distinct(String fieldName) { - return where().distinct(fieldName); + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(collection.getTable(), fieldName); + Collection distinctCollection = collection.distinct(distinctDescriptor); + if (className != null) { + return new RealmResults(realm, distinctCollection, className); + } else { + return new RealmResults(realm, distinctCollection, classSpec); + } } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index dc253dabfa..edae65e704 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -164,6 +164,10 @@ public Collection sort(SortDescriptor sortDescriptor) { return new Collection(sharedRealm, table, nativeSort(nativePtr, sortDescriptor)); } + public Collection distinct(SortDescriptor distinctDescriptor) { + return new Collection(sharedRealm, table, nativeDistinct(nativePtr, distinctDescriptor)); + } + public boolean contains(UncheckedRow row) { return nativeContains(nativePtr, row.getNativePtr()); } @@ -251,6 +255,7 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native long nativeSize(long nativePtr); private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); private static native long nativeSort(long nativePtr, SortDescriptor sortDesc); + private static native long nativeDistinct(long nativePtr, SortDescriptor distinctDesc); private static native boolean nativeDeleteFirst(long nativePtr); private static native boolean nativeDeleteLast(long nativePtr); private static native void nativeDelete(long nativePtr, long index); From deda512421a94833a5c63a06aad5543abda13188 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 19:55:57 +0800 Subject: [PATCH 0333/2110] Remove tests for deprecated distinctAsync --- .../java/io/realm/RealmResultsTests.java | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 734c129c43..357aa84b8b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -521,70 +521,6 @@ public void onChange(RealmResults object) { }); } - @Test - public void distinctAsync_notIndexedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(field); - fail(field); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void distinctAsync_doesNotExist() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - try { - realm.where(AnnotationIndexTypes.class).findAll().distinctAsync("doesNotExist"); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void distinctAsync_invalidTypes() { - populateTestRealm(realm, TEST_DATA_SIZE); - - for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { - try { - realm.where(AllTypes.class).findAll().distinctAsync(field); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void distinctAsync_indexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - for (String field : AnnotationIndexTypes.INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_OBJECT + "." + field); - fail("Unsupported " + field + " linked field"); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void distinctAsync_notIndexedLinkedFields() { - populateForDistinctInvalidTypesLinked(realm); - - try { - realm.where(AllJavaTypes.class).findAll().distinctAsync(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); - } catch (IllegalArgumentException ignored) { - } - } - @Test public void distinctMultiArgs() { final long numberOfBlocks = 25; From 26b427ab3282a94eeefeef8b621e23f0a815f551 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Dec 2016 20:54:06 +0800 Subject: [PATCH 0334/2110] Remove final from Collection definition for mock --- .../src/main/java/io/realm/internal/Collection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index edae65e704..ea2dea8827 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -25,7 +25,7 @@ * It is supposed to be the backend of binding's query results, link list and back links. */ @KeepMember -public final class Collection implements NativeObject { +public class Collection implements NativeObject { private class CollectionObserverPair extends ObserverPairList.ObserverPair> { public CollectionObserverPair(T observer, RealmChangeListener listener) { From 0b980a2c01e43f7a8ab60bf5484d664dc90b10f2 Mon Sep 17 00:00:00 2001 From: LYK Date: Fri, 23 Dec 2016 23:59:00 +0900 Subject: [PATCH 0335/2110] Add the underlying information on RealmFileException. (#3940) Add the underlying information on RealmFileException to help investigating Incompatible lock file issue. --- realm/realm-library/src/main/cpp/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 1ba8e3e2b1..5d77478c41 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -67,7 +67,7 @@ void ConvertException(JNIEnv* env, const char *file, int line) ThrowException(env, IllegalArgument, ss.str()); } catch (RealmFileException& e) { - ss << e.what() << " in " << file << " line " << line; + ss << e.what() << " (" << e.underlying() << ") in " << file << " line " << line; ThrowRealmFileException(env, ss.str(), e.kind()); } catch (InvalidTransactionException& e) { From b60d4779c4d55af184981ae9df6f57274b23d636 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 26 Dec 2016 16:13:41 +0800 Subject: [PATCH 0336/2110] Distinct needs search index --- .../androidTest/java/io/realm/internal/CollectionTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index f8ff718a30..697acccd03 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -75,7 +75,8 @@ private void populateData() { sharedRealm.beginTransaction(); table = sharedRealm.getTable("test_table"); // Specify the column types and names - table.addColumn(RealmFieldType.STRING, "firstName"); + long columnIdx = table.addColumn(RealmFieldType.STRING, "firstName"); + table.addSearchIndex(columnIdx); table.addColumn(RealmFieldType.STRING, "lastName"); table.addColumn(RealmFieldType.INTEGER, "age"); From 94f63cc93d6df652e56329039d5ed82423bbd31c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 26 Dec 2016 20:40:20 +0800 Subject: [PATCH 0337/2110] Fix LinkView based RealmResults's tests behaviour The original tests for deleted LinkView based RealmResults are incorrect. Before the next event loop, even if the LinkView is not valid anymore, for the purpose of maintaining the stable iterator, the RealmResults has to stay the same without calling sync_if_needed. The results becomes empty only if the we enter into the next event loop and reattach to the original TableView. Also, a core bug was found during changing the tests, see https://github.com/realm/realm-core/issues/2378 --- .../java/io/realm/RealmResultsTests.java | 186 +++++++++++------- 1 file changed, 118 insertions(+), 68 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 357aa84b8b..5664434fc3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -37,7 +37,6 @@ import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AnnotationIndexTypes; -import io.realm.entities.CyclicType; import io.realm.entities.DefaultValueOfField; import io.realm.entities.Dog; import io.realm.entities.NonLatinFieldNames; @@ -45,13 +44,11 @@ import io.realm.entities.RandomPrimaryKey; import io.realm.entities.StringOnly; import io.realm.internal.Collection; -import io.realm.internal.Table; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -685,97 +682,150 @@ public void distinctMultiArgs_invalidTypesLinkedFields() { } } - private RealmResults populateRealmResultsOnDeletedLinkView() { + private RealmResults populateRealmResultsOnLinkView(Realm realm) { realm.beginTransaction(); Owner owner = realm.createObject(Owner.class); for (int i = 0; i < 10; i++) { Dog dog = new Dog(); dog.setName("name_" + i); dog.setOwner(owner); + dog.setAge(i); + dog.setBirthday(new Date(i)); owner.getDogs().add(dog); } realm.commitTransaction(); - RealmResults dogs = owner.getDogs().where().equalTo(Dog.FIELD_NAME, "name_0").findAll(); - - realm.beginTransaction(); - owner.deleteFromRealm(); - realm.commitTransaction(); - return dogs; + return owner.getDogs().where().lessThan(Dog.FIELD_AGE, 5).findAll(); } - // It will still be treated as valid table view in core, just always be empty. + // If a RealmResults is built on a link view, when the link view is deleted on the same thread, within the same + // event loop, the RealmResults stays without changes since it is detached until the next event loop. In the next + // event loop, the results will be empty because of the parent link view is deleted. + // 1. Create results from link view. + // 2. Delete the parent link view by a local transaction. + // 3. Within the same event loop, the results stays the same. + // 4. The results change listener called, the results becomes empty. @Test - public void isValid_resultsBuiltOnDeletedLinkView() { - assertEquals(true, populateRealmResultsOnDeletedLinkView().isValid()); - } + @RunTestInLooperThread + public void accessors_resultsBuiltOnDeletedLinkView_deletionAsALocalCommit() { + Realm realm = looperThread.realm; + // Step 1 + RealmResults dogs = populateRealmResultsOnLinkView(realm); + looperThread.keepStrongReference.add(dogs); + dogs.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults dogs) { + // Step 4. + // The results is still valid, but empty. + assertEquals(true, dogs.isValid()); + assertEquals(true, dogs.isEmpty()); + assertEquals(0, dogs.size()); + try { + dogs.first(); + fail(); + } catch (IndexOutOfBoundsException ignored) { + } - @Test - public void size_resultsBuiltOnDeletedLinkView() { - assertEquals(0, populateRealmResultsOnDeletedLinkView().size()); - } + assertEquals(0, dogs.sum(Dog.FIELD_AGE).intValue()); + assertEquals(0f, dogs.sum(Dog.FIELD_HEIGHT).floatValue(), 0f); + assertEquals(0d, dogs.sum(Dog.FIELD_WEIGHT).doubleValue(), 0d); + assertEquals(0d, dogs.average(Dog.FIELD_AGE), 0d); + assertEquals(0d, dogs.average(Dog.FIELD_HEIGHT), 0d); + assertEquals(0d, dogs.average(Dog.FIELD_WEIGHT), 0d); + assertEquals(null, dogs.min(Dog.FIELD_AGE)); + assertEquals(null, dogs.max(Dog.FIELD_AGE)); + assertEquals(null, dogs.minDate(Dog.FIELD_BIRTHDAY)); + assertEquals(null, dogs.maxDate(Dog.FIELD_BIRTHDAY)); - @Test - public void first_resultsBuiltOnDeletedLinkView() { - try { - populateRealmResultsOnDeletedLinkView().first(); - } catch (IndexOutOfBoundsException ignored) { - } - } + // FIXME: Enable this when https://github.com/realm/realm-core/issues/2378 fixed. + //assertEquals(0, dogs.where().findAll().size()); - @Test - public void last_resultsBuiltOnDeletedLinkView() { - try { - populateRealmResultsOnDeletedLinkView().last(); - } catch (IndexOutOfBoundsException ignored) { - } - } + looperThread.testComplete(); + } + }); - @Test - public void sum_resultsBuiltOnDeletedLinkView() { - RealmResults dogs = populateRealmResultsOnDeletedLinkView(); - assertEquals(0, dogs.sum(Dog.FIELD_AGE).intValue()); - assertEquals(0f, dogs.sum(Dog.FIELD_HEIGHT).floatValue(), 0f); - assertEquals(0d, dogs.sum(Dog.FIELD_WEIGHT).doubleValue(), 0d); - } + // Step 2 + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.where(Owner.class).findAll().deleteAllFromRealm(); + } + }); - @Test - public void average_resultsBuiltOnDeletedLinkView() { - RealmResults dogs = populateRealmResultsOnDeletedLinkView(); - assertEquals(0d, dogs.average(Dog.FIELD_AGE), 0d); - assertEquals(0d, dogs.average(Dog.FIELD_HEIGHT), 0d); - assertEquals(0d, dogs.average(Dog.FIELD_WEIGHT), 0d); + // Step 3 + assertEquals(true, dogs.isValid()); + assertEquals(5, dogs.size()); + assertEquals("name_0", dogs.first().getName()); + assertEquals("name_4", dogs.last().getName()); + assertEquals(0, dogs.min(Dog.FIELD_AGE).intValue()); + assertEquals(4, dogs.max(Dog.FIELD_AGE).intValue()); + assertEquals(new Date(0), dogs.minDate(Dog.FIELD_BIRTHDAY)); + assertEquals(new Date(4), dogs.maxDate(Dog.FIELD_BIRTHDAY)); + // The link view has been deleted. + assertEquals(0, dogs.where().findAll().size()); } + // If a RealmResults is built on a link view, when the link view is deleted on a remote thread, within the same + // event loop, the RealmResults stays without changes since the Realm version doesn't change. In the next + // event loop, the results will be empty because of the parent link view is deleted. + // 1. Create results from link view. + // 2. Delete the parent link view by a remote transaction. + // 3. Within the same event loop, the results stays the same. + // 4. The results change listener called, the results becomes empty. @Test - public void where_resultsBuiltOnDeletedLinkView() { - OrderedRealmCollection results = populateCollectionOnDeletedLinkView(realm, ManagedCollection.REALMRESULTS); - assertEquals(0, results.where().findAll().size()); - } + @RunTestInLooperThread + public void accessors_resultsBuiltOnDeletedLinkView_deletionAsARemoteCommit() { + // Step 1 + Realm realm = looperThread.realm; + RealmResults dogs = populateRealmResultsOnLinkView(realm); + looperThread.keepStrongReference.add(dogs); + dogs.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults dogs) { + // Step 4 + // The results is still valid, but empty. + assertEquals(true, dogs.isValid()); + assertEquals(true, dogs.isEmpty()); + assertEquals(0, dogs.size()); + try { + dogs.first(); + fail(); + } catch (IndexOutOfBoundsException ignored) { + } - @Test - public void min_resultsBuiltOnDeletedLinkView() { - OrderedRealmCollection results = populateCollectionOnDeletedLinkView(realm, ManagedCollection.REALMRESULTS); - assertNull(results.min(CyclicType.FIELD_ID)); - } + assertEquals(0, dogs.sum(Dog.FIELD_AGE).intValue()); + assertEquals(0f, dogs.sum(Dog.FIELD_HEIGHT).floatValue(), 0f); + assertEquals(0d, dogs.sum(Dog.FIELD_WEIGHT).doubleValue(), 0d); + assertEquals(0d, dogs.average(Dog.FIELD_AGE), 0d); + assertEquals(0d, dogs.average(Dog.FIELD_HEIGHT), 0d); + assertEquals(0d, dogs.average(Dog.FIELD_WEIGHT), 0d); + assertEquals(null, dogs.min(Dog.FIELD_AGE)); + assertEquals(null, dogs.max(Dog.FIELD_AGE)); + assertEquals(null, dogs.minDate(Dog.FIELD_BIRTHDAY)); + assertEquals(null, dogs.maxDate(Dog.FIELD_BIRTHDAY)); - @Test - public void min_dateResultsBuiltOnDeletedLinkView() { - OrderedRealmCollection results = populateCollectionOnDeletedLinkView(realm, ManagedCollection.REALMRESULTS); - assertEquals(null, results.minDate(CyclicType.FIELD_DATE)); - } + // FIXME: Enable this when https://github.com/realm/realm-core/issues/2378 fixed. + // assertEquals(0, dogs.where().findAll().size()); - @Test - public void max_dateResultsBuiltOnDeletedLinkView() { - OrderedRealmCollection results = populateCollectionOnDeletedLinkView(realm, ManagedCollection.REALMRESULTS); - assertEquals(null, results.maxDate(CyclicType.FIELD_DATE)); - } + looperThread.testComplete(); + } + }); - @Test - public void max_resultsBuiltOnDeletedLinkView() { - OrderedRealmCollection results = populateCollectionOnDeletedLinkView(realm, ManagedCollection.REALMRESULTS); - assertNull(results.max(CyclicType.FIELD_ID)); + + // Step 2 + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.where(Owner.class).findAll().deleteAllFromRealm(); + } + }); + + // Step 3 + assertEquals(true, dogs.isValid()); + assertEquals(5, dogs.size()); + // The link view still exists + assertEquals(5, dogs.where().findAll().size()); } @Test From ba31d3848e41fbbafef62e6e0b2ae4ce6cff4712 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 29 Dec 2016 10:21:22 +0800 Subject: [PATCH 0338/2110] Wait forever in debugging mode (#3943) * Wait forever in debugging mode It is quite annoy when we stop at a breakpoint the awaitOrFail timeout happens. By checking if the debugger connected, we can know we are actually debugging the test and don't want to be interrupted by the timeout. --- .../src/androidTest/java/io/realm/TestHelper.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 321c5d927c..87102f90da 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -783,7 +783,11 @@ public static void awaitOrFail(CountDownLatch latch) { public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { try { - if (!latch.await(numberOfSeconds, TimeUnit.SECONDS)) { + if (android.os.Debug.isDebuggerConnected()) { + // If we are debugging the tests, just wait without a timeout. In case we are stopping at a break point + // and timeout happens. + latch.await(); + } else if (!latch.await(numberOfSeconds, TimeUnit.SECONDS)) { fail("Test took longer than " + numberOfSeconds + " seconds"); } } catch (InterruptedException e) { From ce130ae5bd1c448055896be1c46587b15ba74e1c Mon Sep 17 00:00:00 2001 From: Kayvan Date: Fri, 30 Dec 2016 01:27:15 -0800 Subject: [PATCH 0339/2110] Update Realm.java (#3962) Added Missing `throws` in the javaDoc --- realm/realm-library/src/main/java/io/realm/Realm.java | 1 + 1 file changed, 1 insertion(+) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 81c4bdebf1..fffb1f57eb 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1539,6 +1539,7 @@ public void migrationComplete() { * * @param configuration a {@link RealmConfiguration}. * @return {@code false} if a file could not be deleted. The failing file will be logged. + * @throws IllegalStateException if not all realm instances are closed. */ public static boolean deleteRealm(RealmConfiguration configuration) { return BaseRealm.deleteRealm(configuration); From 617ecfd93bcc6582cbb878bf21868887a0691133 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 3 Jan 2017 12:53:18 +0800 Subject: [PATCH 0340/2110] Java lint warnings with proxy class (#3948) Fix #2929 --- CHANGELOG.md | 1 + .../java/io/realm/processor/RealmProxyClassGenerator.java | 6 +++--- .../src/test/resources/io/realm/AllTypesRealmProxy.java | 6 +++--- .../src/test/resources/io/realm/BooleansRealmProxy.java | 6 +++--- .../src/test/resources/io/realm/NullTypesRealmProxy.java | 6 +++--- .../src/test/resources/io/realm/SimpleRealmProxy.java | 6 +++--- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdd9e91745..58a4498e9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Enhancements * All major public classes are now non-final. This is mostly a compromise to support Mockito. All protected fields/methods are still not considered part of the public API and can change without notice (#3869). +* Fixed Java lint warnings with generated proxy classes (#2929). ### Internal diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index f87f679bb8..a29b69dff8 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -205,7 +205,7 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { private void emitClassFields(JavaWriter writer) throws IOException { writer.emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE)); - writer.emitField("ProxyState", "proxyState", EnumSet.of(Modifier.PRIVATE)); + writer.emitField("ProxyState<" + qualifiedClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)); for (VariableElement variableElement : metadata.getFields()) { if (Utils.isRealmList(variableElement)) { @@ -502,7 +502,7 @@ private void emitInjectContextMethod(JavaWriter writer) throws IOException { writer.emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()"); writer.emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()); - writer.emitStatement("this.proxyState = new ProxyState(%1$s.class, this)", qualifiedClassName); + writer.emitStatement("this.proxyState = new ProxyState<%1$s>(%1$s.class, this)", qualifiedClassName); writer.emitStatement("proxyState.setRealm$realm(context.getRealm())"); writer.emitStatement("proxyState.setRow$realm(context.getRow())"); writer.emitStatement("proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue())"); @@ -1490,7 +1490,7 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException .endControlFlow() .nextControlFlow("else") .emitStatement("unmanagedObject = new %s()", qualifiedClassName) - .emitStatement("cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject))") + .emitStatement("cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject))") .endControlFlow(); for (VariableElement field : metadata.getFields()) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index d67ccc944c..e069a99d88 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -92,7 +92,7 @@ public final AllTypesColumnInfo clone() { } private AllTypesColumnInfo columnInfo; - private ProxyState proxyState; + private ProxyState proxyState; private RealmList columnRealmListRealmList; private static final List FIELD_NAMES; static { @@ -119,7 +119,7 @@ public final AllTypesColumnInfo clone() { private void injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (AllTypesColumnInfo) context.getColumnInfo(); - this.proxyState = new ProxyState(some.test.AllTypes.class, this); + this.proxyState = new ProxyState(some.test.AllTypes.class, this); proxyState.setRealm$realm(context.getRealm()); proxyState.setRow$realm(context.getRow()); proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); @@ -1210,7 +1210,7 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje } } else { unmanagedObject = new some.test.AllTypes(); - cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); } ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnString(((AllTypesRealmProxyInterface) realmObject).realmGet$columnString()); ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnLong(((AllTypesRealmProxyInterface) realmObject).realmGet$columnLong()); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 7200c4e70e..081e18d42b 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -72,7 +72,7 @@ public final BooleansColumnInfo clone() { } private BooleansColumnInfo columnInfo; - private ProxyState proxyState; + private ProxyState proxyState; private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -93,7 +93,7 @@ public final BooleansColumnInfo clone() { private void injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (BooleansColumnInfo) context.getColumnInfo(); - this.proxyState = new ProxyState(some.test.Booleans.class, this); + this.proxyState = new ProxyState(some.test.Booleans.class, this); proxyState.setRealm$realm(context.getRealm()); proxyState.setRow$realm(context.getRow()); proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); @@ -521,7 +521,7 @@ public static some.test.Booleans createDetachedCopy(some.test.Booleans realmObje } } else { unmanagedObject = new some.test.Booleans(); - cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); } ((BooleansRealmProxyInterface) unmanagedObject).realmSet$done(((BooleansRealmProxyInterface) realmObject).realmGet$done()); ((BooleansRealmProxyInterface) unmanagedObject).realmSet$isReady(((BooleansRealmProxyInterface) realmObject).realmGet$isReady()); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 608868bb72..9fe3b2f11d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -140,7 +140,7 @@ public final NullTypesColumnInfo clone() { } private NullTypesColumnInfo columnInfo; - private ProxyState proxyState; + private ProxyState proxyState; private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -178,7 +178,7 @@ public final NullTypesColumnInfo clone() { private void injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (NullTypesColumnInfo) context.getColumnInfo(); - this.proxyState = new ProxyState(some.test.NullTypes.class, this); + this.proxyState = new ProxyState(some.test.NullTypes.class, this); proxyState.setRealm$realm(context.getRealm()); proxyState.setRow$realm(context.getRow()); proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); @@ -2221,7 +2221,7 @@ public static some.test.NullTypes createDetachedCopy(some.test.NullTypes realmOb } } else { unmanagedObject = new some.test.NullTypes(); - cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); } ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldStringNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldStringNotNull()); ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldStringNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldStringNull()); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 3dc59b6feb..bdb167bbf2 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -64,7 +64,7 @@ public final SimpleColumnInfo clone() { } private SimpleColumnInfo columnInfo; - private ProxyState proxyState; + private ProxyState proxyState; private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -83,7 +83,7 @@ public final SimpleColumnInfo clone() { private void injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (SimpleColumnInfo) context.getColumnInfo(); - this.proxyState = new ProxyState(some.test.Simple.class, this); + this.proxyState = new ProxyState(some.test.Simple.class, this); proxyState.setRealm$realm(context.getRealm()); proxyState.setRow$realm(context.getRow()); proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); @@ -415,7 +415,7 @@ public static some.test.Simple createDetachedCopy(some.test.Simple realmObject, } } else { unmanagedObject = new some.test.Simple(); - cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); } ((SimpleRealmProxyInterface) unmanagedObject).realmSet$name(((SimpleRealmProxyInterface) realmObject).realmGet$name()); ((SimpleRealmProxyInterface) unmanagedObject).realmSet$age(((SimpleRealmProxyInterface) realmObject).realmGet$age()); From 0dfe1bab112a7851ef05a0aa91c8b95eae9a0042 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 3 Jan 2017 19:46:57 +0800 Subject: [PATCH 0341/2110] RAII wrapper for some JNI resources (#3959) * Move the global jvm pointer to JniUtils and add some helper functions. * Wrapper for jmethodID, global weak ref. * Refactor the wrapper for the local ref. * Before using the global weak ref, always try to acquire a local ref first since until Android 4.0 weak global references could only be passed to NewLocalRef, NewGlobalRef, and DeleteWeakGlobalRef. See https://developer.android.com/training/articles/perf-jni.html#unsupported for more details. This fix #3726 . --- CHANGELOG.md | 1 + .../src/main/cpp/io_realm_SyncManager.cpp | 6 +- .../main/cpp/io_realm_internal_TableQuery.cpp | 4 +- .../src/main/cpp/io_realm_internal_Util.cpp | 5 +- .../src/main/cpp/java_binding_context.cpp | 42 +++--------- .../src/main/cpp/java_binding_context.hpp | 16 ++--- .../cpp/jni_util/java_global_weak_ref.cpp | 41 ++++++++++++ .../cpp/jni_util/java_global_weak_ref.hpp | 67 +++++++++++++++++++ .../src/main/cpp/jni_util/java_local_ref.hpp | 57 ++++++++++++++++ .../src/main/cpp/jni_util/java_method.cpp | 43 ++++++++++++ .../src/main/cpp/jni_util/java_method.hpp | 51 ++++++++++++++ .../src/main/cpp/jni_util/jni_utils.cpp | 48 +++++++++++++ .../src/main/cpp/jni_util/jni_utils.hpp | 46 +++++++++++++ .../src/main/cpp/objectserver_shared.hpp | 6 +- realm/realm-library/src/main/cpp/util.cpp | 1 - realm/realm-library/src/main/cpp/util.hpp | 24 ------- 16 files changed, 381 insertions(+), 77 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_method.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_method.hpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a4498e9d..c979bb44ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Bug fixes * "operation not permitted" issue when creating Realm file on some devices' external storage (#3629). +* Crash on API 10 devices (#3726). ### Enhancements diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index e7da1e400c..156786251a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -30,6 +30,7 @@ #include "io_realm_SyncManager.h" #include "jni_util/log.hpp" +#include "jni_util/jni_utils.hpp" using namespace realm; using namespace realm::sync; @@ -42,10 +43,7 @@ static jmethodID sync_manager_notify_error_handler = nullptr; static void error_handler(int error_code, std::string message) { - JNIEnv* env; - if (g_vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { - throw std::runtime_error("JVM is not attached to this thread. Called in error_handler."); - } + JNIEnv* env = JniUtils::get_env(); env->CallStaticVoidMethod(sync_manager, sync_manager_notify_error_handler, error_code, env->NewStringUTF(message.c_str())); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 520b628c4a..ad1f39588a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -19,9 +19,11 @@ #include #include #include "util.hpp" +#include "jni_util/java_local_ref.hpp" #include "io_realm_internal_TableQuery.h" using namespace realm; +using namespace realm::jni_util; #if 1 #define QUERY_COL_TYPE_VALID(env, jPtr, col, type) query_col_type_valid(env, jPtr, col, type) @@ -1210,7 +1212,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeBatchUpdate // Step3: Run & export the queries against the latest shared group for (size_t i = 0; i < number_of_queries; ++i) { // Delete the local ref since we might have a long loop - JniLocalRef local_ref(env, (jlongArray) env->GetObjectArrayElement(query_param_matrix, i)); + JavaLocalRef local_ref(env, (jlongArray) env->GetObjectArrayElement(query_param_matrix, i)); JniLongArray query_param_array(env, local_ref); switch (query_param_array[0]) { // 0, index of the type of query, the next indicies are parameters diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 241ce22908..cac87ba165 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -22,7 +22,10 @@ #include "mem_usage.hpp" #include "util.hpp" +#include "jni_util/jni_utils.hpp" + using std::string; +using namespace realm::jni_util; //#define USE_VLD #if defined(_MSC_VER) && defined(_DEBUG) && defined(USE_VLD) @@ -39,7 +42,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) return JNI_ERR; } else { - g_vm = vm; + JniUtils::initialize(vm, JNI_VERSION_1_6); // Loading classes and constructors for later use - used by box typed fields and a few methods' return value java_lang_long = GetClass(env, "java/lang/Long"); java_lang_long_init = env->GetMethodID(java_lang_long, "", "(J)V"); diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index b8954caeb6..f876847b86 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -15,46 +15,20 @@ */ #include "java_binding_context.hpp" - -#include "util/format.hpp" +#include "jni_util/java_method.hpp" using namespace realm; using namespace realm::_impl; - -JavaBindingContext::JavaBindingContext(const ConcreteJavaBindContext& concrete_context) - : m_local_jni_env(concrete_context.jni_env) -{ - jint ret = m_local_jni_env->GetJavaVM(&m_jvm); - if (ret != 0) { - throw std::runtime_error(util::format("Failed to get Java vm. Error: %d", ret)); - } - if (concrete_context.java_notifier) { - m_java_notifier = m_local_jni_env->NewWeakGlobalRef(concrete_context.java_notifier); - jclass cls = m_local_jni_env->GetObjectClass(m_java_notifier); - m_notify_by_other_method = m_local_jni_env->GetMethodID(cls, "notifyCommitByOtherThread", "()V"); - } else { - m_java_notifier = nullptr; - } -} - -JavaBindingContext::~JavaBindingContext() -{ - if (m_java_notifier) { - // Always try to attach here since this may be called in the finalizer/phantom thread where m_local_jni_env - // should not be used on. No need to call DetachCurrentThread since this thread should always be created by - // JVM. - JNIEnv *env; - m_jvm->AttachCurrentThread(&env, nullptr); - env->DeleteWeakGlobalRef(m_java_notifier); - } -} +using namespace realm::jni_util; void JavaBindingContext::changes_available() { - jobject notifier = m_local_jni_env->NewLocalRef(m_java_notifier); - if (notifier) { - m_local_jni_env->CallVoidMethod(m_java_notifier, m_notify_by_other_method); - m_local_jni_env->DeleteLocalRef(notifier); + if (m_java_notifier) { + m_java_notifier.call_with_local_ref([&] (JNIEnv* env, jobject notifier_obj) { + // Method IDs from RealmNotifier implementation. Cache them as member vars. + static JavaMethod notify_by_other_method(env, notifier_obj, "notifyCommitByOtherThread", "()V"); + env->CallVoidMethod(notifier_obj, notify_by_other_method); + }); } } diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index a058691b3e..cba34dedba 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -22,6 +22,8 @@ #include "binding_context.hpp" +#include "jni_util/java_global_weak_ref.hpp" + namespace realm { namespace _impl { @@ -36,22 +38,16 @@ class JavaBindingContext final : public BindingContext { :jni_env(env), java_notifier(notifier) { } }; - // The JNIEnv for the thread which creates the Realm. This should only be used on the current thread. - JNIEnv* m_local_jni_env; - // All methods should be called from the thread which creates the realm except the destructor which might be - // called from finalizer/phantom daemon. So we need a jvm pointer to create JNIEnv there if needed. - JavaVM* m_jvm; // A weak global ref to the implementation of RealmNotifier // Java should hold a strong ref to it as long as the SharedRealm lives - jobject m_java_notifier; - // Method IDs from RealmNotifier implementation. Cache them as member vars. - jmethodID m_notify_by_other_method; + jni_util::JavaGlobalWeakRef m_java_notifier; public: - virtual ~JavaBindingContext(); + virtual ~JavaBindingContext() {}; virtual void changes_available(); - explicit JavaBindingContext(const ConcreteJavaBindContext&); + explicit JavaBindingContext(const ConcreteJavaBindContext& concrete_context) + : m_java_notifier(concrete_context.jni_env, concrete_context.java_notifier) {} JavaBindingContext(const JavaBindingContext&) = delete; JavaBindingContext& operator=(const JavaBindingContext&) = delete; JavaBindingContext(JavaBindingContext&&) = delete; diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp new file mode 100644 index 0000000000..ceac7d8466 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "java_global_weak_ref.hpp" +#include "java_local_ref.hpp" + +using namespace realm::jni_util; + +bool JavaGlobalWeakRef::call_with_local_ref(JNIEnv* env, std::function callback) +{ + if (!m_weak) { + return false; + } + + JavaLocalRef obj(env, m_weak, need_to_create_local_ref); + + if (!obj) { + return false; + } + callback(env, obj); + return true; +} + +bool JavaGlobalWeakRef::call_with_local_ref(std::function callback) +{ + return call_with_local_ref(JniUtils::get_env(), callback); +} + diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp new file mode 100644 index 0000000000..676f92b37f --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp @@ -0,0 +1,67 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_JAVA_GLOBAL_WEAK_REF_HPP +#define REALM_JNI_UTIL_JAVA_GLOBAL_WEAK_REF_HPP + +#include +#include + +#include "jni_utils.hpp" + +namespace realm { +namespace jni_util { + +// RAII wrapper for weak global ref. +class JavaGlobalWeakRef { +public: + JavaGlobalWeakRef() : m_weak(nullptr) {} + JavaGlobalWeakRef(JNIEnv* env, jobject obj) : m_weak(obj ? env->NewWeakGlobalRef(obj) : nullptr) { } + ~JavaGlobalWeakRef() + { + if (m_weak) { + JniUtils::get_env()->DeleteWeakGlobalRef(m_weak); + } + } + + // Implement those when needed. + JavaGlobalWeakRef(const JavaGlobalWeakRef&) = delete; + JavaGlobalWeakRef& operator=(const JavaGlobalWeakRef&) = delete; + JavaGlobalWeakRef(JavaGlobalWeakRef&& rhs) = delete; + JavaGlobalWeakRef& operator=(JavaGlobalWeakRef&& rhs) = delete; + + inline operator bool() const noexcept + { + return m_weak != nullptr; + } + + using Callback = void(JNIEnv* env, jobject obj); + + // Acquire a local ref and run the callback with it if the weak ref is valid. The local ref will be deleted after + // callback finished. Return false if the weak ref is not valid anymore. + bool call_with_local_ref(JNIEnv* env, std::function callback); + // Try to get an JNIEnv for current thread then run the callback. + bool call_with_local_ref(std::function callback); + +private: + jweak m_weak; +}; + +} // namespace jni_util +} // namespace realm + +#endif // REALM_JNI_UTIL_JAVA_GLOBAL_WEAK_REF_HPP + diff --git a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp new file mode 100644 index 0000000000..7b38bb69a8 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_JAVA_LOCAL_REF_HPP +#define REALM_JNI_UTIL_JAVA_LOCAL_REF_HPP + +#include + +namespace realm { +namespace jni_util { + +struct NeedToCreateLocalRef {}; +static constexpr NeedToCreateLocalRef need_to_create_local_ref{}; + +// Wraps jobject and automatically calls DeleteLocalRef when this object is destroyed. +// DeleteLocalRef is not necessary to be called in most cases since all local references will be cleaned up when the +// program returns to Java from native. But if the local ref is created in a loop, consider to use this class to wrap it +// because the size of local reference table is relative small (512 bytes on Android). +template +class JavaLocalRef { +public: + // need_to_create is useful when acquire a local ref from a global weak ref. + inline JavaLocalRef(JNIEnv* env, T obj) noexcept : m_jobject(obj), m_env(env) {}; + inline JavaLocalRef(JNIEnv* env, T obj, NeedToCreateLocalRef) noexcept + : m_jobject(env->NewLocalRef(obj)), m_env(env) {}; + inline ~JavaLocalRef() { m_env->DeleteLocalRef(m_jobject); } + + JavaLocalRef(const JavaLocalRef&) = delete; + JavaLocalRef& operator=(const JavaLocalRef&) = delete; + JavaLocalRef(JavaLocalRef&& rhs) = delete; + JavaLocalRef& operator=(JavaLocalRef&& rhs) = delete; + + inline operator bool() const noexcept { return m_jobject != nullptr; }; + inline operator T() const noexcept { return m_jobject; } + +private: + T m_jobject; + JNIEnv* m_env; +}; + +} // namespace realm +} // namespace jni_util +#endif // REALM_JNI_UTIL_JAVA_LOCAL_REF_HPP + diff --git a/realm/realm-library/src/main/cpp/jni_util/java_method.cpp b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp new file mode 100644 index 0000000000..61f37748cd --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "java_method.hpp" + +#include + +using namespace realm::jni_util; + +JavaMethod::JavaMethod(JNIEnv *env, jclass cls, const char* method_name, const char* signature) +{ + m_method_id = env->GetMethodID(cls, method_name, signature); + REALM_ASSERT_DEBUG(m_method_id != nullptr); +} + +JavaMethod::JavaMethod(JNIEnv *env, jobject obj, const char* method_name, const char* signature) +{ + jclass cls = env->GetObjectClass(obj); + m_method_id = env->GetMethodID(cls, method_name, signature); + REALM_ASSERT_DEBUG(m_method_id != nullptr); + env->DeleteLocalRef(cls); +} + +JavaMethod::JavaMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature) +{ + jclass cls = env->FindClass(class_name); + REALM_ASSERT_DEBUG(cls != nullptr); + m_method_id = env->GetMethodID(cls, method_name, signature); +} + diff --git a/realm/realm-library/src/main/cpp/jni_util/java_method.hpp b/realm/realm-library/src/main/cpp/jni_util/java_method.hpp new file mode 100644 index 0000000000..81c3fd64a4 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_method.hpp @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_JAVA_METHOD_HPP +#define REALM_JNI_UTIL_JAVA_METHOD_HPP + +#include + +namespace realm { +namespace jni_util { + +// RAII wrapper for java method ID. Since normally method ID stays unchanged for the whole JVM life cycle, it would be +// safe to have a static JavaMethod object to avoid calling GetMethodID multiple times. +class JavaMethod { +public: + JavaMethod() : m_method_id(nullptr) {} + JavaMethod(JNIEnv *env, jclass cls, const char* method_name, const char* signature); + JavaMethod(JNIEnv *env, jobject obj, const char* method_name, const char* signature); + JavaMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature); + + JavaMethod(const JavaMethod&) = default; + JavaMethod& operator=(const JavaMethod&) = default; + JavaMethod(JavaMethod&& rhs) = delete; + JavaMethod& operator=(JavaMethod&& rhs) = delete; + + ~JavaMethod() { } + + inline operator bool() const noexcept { return m_method_id != nullptr; } + inline operator const jmethodID&() const noexcept { return m_method_id; } + +private: + jmethodID m_method_id; +}; + +} // namespace realm +} // namespace jni_util + +#endif //REALM_JNI_UTIL_JAVA_METHOD_HPP diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp new file mode 100644 index 0000000000..7db8ce14d9 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "jni_utils.hpp" + +#include + +#include + +using namespace realm::jni_util; + +static std::unique_ptr s_instance; + +void JniUtils::initialize(JavaVM *vm, jint vm_version) noexcept { + REALM_ASSERT_DEBUG(!s_instance); + + s_instance = std::unique_ptr(new JniUtils(vm, vm_version)); +} + +JNIEnv* JniUtils::get_env(bool attach_if_needed) { + REALM_ASSERT_DEBUG(s_instance); + + JNIEnv* env; + if (s_instance->m_vm->GetEnv(reinterpret_cast(&env), s_instance->m_vm_version) != JNI_OK) { + if (attach_if_needed) { + jint ret = s_instance->m_vm->AttachCurrentThread(&env, nullptr); + REALM_ASSERT_RELEASE(ret == JNI_OK); + } else { + REALM_ASSERT_RELEASE(false); + } + } + + return env; +} + diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp new file mode 100644 index 0000000000..61d8fbddfb --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_JNI_UTILS_HPP +#define REALM_JNI_UTIL_JNI_UTILS_HPP + +#include + +namespace realm { +namespace jni_util { + +// Util functions for JNI. +class JniUtils { +public: + ~JniUtils() {} + + // Call this only once in JNI_OnLoad. + static void initialize(JavaVM* vm, jint vm_version) noexcept; + // When attach_if_needed is false, returns the JNIEnv if there is one attached to this thread. Assert if there is + // none. When attach_if_needed is true, try to attach and return a JNIEnv if necessary. + static JNIEnv* get_env(bool attach_if_needed = false); + +private: + JniUtils(JavaVM* vm, jint vm_version) noexcept : m_vm(vm), m_vm_version(vm_version) {} + + JavaVM* m_vm; + jint m_vm_version; +}; + +} // namespace realm +} // namespace jni_util + +#endif //REALM_JNI_UTIL_JNI_UTILS_HPP diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index bd78254ba4..4ac8b022b6 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -28,6 +28,9 @@ #include #include "util.hpp" +#include "jni_util/jni_utils.hpp" + +using namespace realm::jni_util; // Wrapper class for realm::Session. This allows us to manage the C++ session and callback lifecycle correctly. @@ -55,8 +58,7 @@ class JniSession { } }; auto error_handler = [&, global_obj_ref_tmp](int error_code, std::string message) { - JNIEnv *local_env; - g_vm->AttachCurrentThread(&local_env, nullptr); + JNIEnv *local_env = JniUtils::get_env(true); jclass java_session_class = local_env->GetObjectClass(global_obj_ref_tmp); jmethodID notify_error_handler = local_env->GetMethodID(java_session_class, "notifySessionError", "(ILjava/lang/String;)V"); diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 5d77478c41..97c62dd707 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -32,7 +32,6 @@ using namespace realm::util; using namespace realm::jni_util; // Caching classes and constructors for boxed types. -JavaVM* g_vm; jclass java_lang_long; jmethodID java_lang_long_init; jclass java_lang_float; diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 68b9a89ee6..db9e4cd88c 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -636,30 +636,6 @@ class JniBooleanArray { jint m_releaseMode; }; -// Wraps jobject and automatically calls DeleteLocalRef when this object is destroyed. -// DeleteLocalRef is not necessary to be called in most cases since all local references will be cleaned up when the -// program returns to Java from native. But if the LocaRef is created in a loop, consider to use this class to wrap it -// because the size of local reference table is relative small (512 on Android). -template -class JniLocalRef { -public: - JniLocalRef(JNIEnv* env, T obj) : m_jobject(obj), m_env(env) {}; - ~JniLocalRef() - { - m_env->DeleteLocalRef(m_jobject); - } - - inline operator T() const noexcept - { - return m_jobject; - } - -private: - T m_jobject; - JNIEnv* m_env; -}; - -extern JavaVM* g_vm; extern jclass java_lang_long; extern jmethodID java_lang_long_init; extern jclass java_lang_float; From 0855425d6f9f35de3f7d5be1311ce589f1ae1aca Mon Sep 17 00:00:00 2001 From: LYK Date: Tue, 3 Jan 2017 22:48:25 +0900 Subject: [PATCH 0342/2110] Update Javadoc of Realm.compactRealm. (#3973) It is related #3520. --- realm/realm-library/src/main/java/io/realm/BaseRealm.java | 1 - 1 file changed, 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index c61b2cefae..2bf2e75a26 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -579,7 +579,6 @@ public void onResult(int count) { * Compacts the Realm file defined by the given configuration. * * @param configuration configuration for the Realm to compact. - * @throws IllegalArgumentException if Realm is encrypted. * @return {@code true} if compaction succeeded, {@code false} otherwise. */ static boolean compactRealm(final RealmConfiguration configuration) { From c385ecda12935f44378f2b33adb52702066c30cd Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 3 Jan 2017 23:50:30 +0800 Subject: [PATCH 0343/2110] Update core to 2.3.0 (#3970) Also update object-store to 99570ba6e0 . * Adapt changes from sync - Client::set_error_handler is removed. - Session error handler signature changed. * Session error handler called after destruction - According to the doc of Session::set_error_handler, the error handler could be called after the session object is destroyed. That is a problem since the java session object can be destroyed at that time. Use a weak_ptr of JavaGlobalRef in the lambda to solve the problem. * Ignore unknown category error from sync --- CHANGELOG.md | 4 +- dependencies.list | 4 +- .../src/main/cpp/io_realm_SyncManager.cpp | 17 ------- ...ernal_objectserver_ObjectServerSession.cpp | 1 - realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/cpp/objectserver_shared.hpp | 50 ++++++++++++------- .../java/io/realm/SyncManager.java | 11 ---- 7 files changed, 37 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c979bb44ca..90ee4852b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,8 @@ ### Internal -* Upgraded Realm Core to 2.1.0. -* Upgraded Realm Sync to 1.0.0-BETA-5.0. +* Upgraded Realm Core to 2.3.0. +* Upgraded Realm Sync to 1.0.0-BETA-6.5. ## 2.2.1 diff --git a/dependencies.list b/dependencies.list index e1ea9c6811..53ffc5b49c 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.0-BETA-5.0 -REALM_SYNC_SHA256=7bbaa9cdef722d85489feb1b70da11d5640869540d9a0fc40621de7352dd9ffd +REALM_SYNC_VERSION=1.0.0-BETA-6.5 +REALM_SYNC_SHA256=dad59e910e4a8cab75791bab152e7c9e43712b174e0dce5a1596273976eb4de3 # Object Server Release used by Integration tests # https://packagecloud.io/realm/realm?filter=debs diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index 156786251a..433e44307e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -38,17 +38,6 @@ using namespace realm::jni_util; std::unique_ptr sync_client; -static jclass sync_manager = nullptr; -static jmethodID sync_manager_notify_error_handler = nullptr; - -static void error_handler(int error_code, std::string message) -{ - JNIEnv* env = JniUtils::get_env(); - - env->CallStaticVoidMethod(sync_manager, - sync_manager_notify_error_handler, error_code, env->NewStringUTF(message.c_str())); -} - JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient (JNIEnv *env, jclass sync_manager_class) { @@ -59,12 +48,6 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient sync::Client::Config config; config.logger = &CoreLoggerBridge::shared(); sync_client = std::make_unique(std::move(config)); // Throws - - // This function should only be called once, so below is safe. - sync_manager = reinterpret_cast(env->NewGlobalRef(sync_manager_class)); - sync_manager_notify_error_handler = env->GetStaticMethodID(sync_manager, - "notifyErrorHandler", "(ILjava/lang/String;)V"); - sync_client->set_error_handler(error_handler); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp index ea0ebecc4a..cbd02f0d35 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp @@ -74,7 +74,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_n { TR_ENTER() JniSession* session = SS(sessionPointer); - session->close(env); delete session; // TODO Can we avoid killing the session here? } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 300a2d6f28..99570ba6e0 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 300a2d6f284391540dcfd346893de49fa15e1771 +Subproject commit 99570ba6e0711820ef71b20d7ce0975b52cfd197 diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 4ac8b022b6..ab7a9f7369 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -19,9 +19,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -29,8 +31,8 @@ #include "util.hpp" #include "jni_util/jni_utils.hpp" - -using namespace realm::jni_util; +#include "jni_util/java_global_weak_ref.hpp" +#include "jni_util/java_method.hpp" // Wrapper class for realm::Session. This allows us to manage the C++ session and callback lifecycle correctly. @@ -44,12 +46,14 @@ class JniSession { JniSession& operator=(JniSession&&) = delete; JniSession(JNIEnv* env, std::string local_realm_path, jobject java_session_obj) + : m_java_session_ref(std::make_shared(env, java_session_obj)) { extern std::unique_ptr sync_client; // Get the coordinator for the given path, or null if there is none m_sync_session = new realm::sync::Session(*sync_client, local_realm_path); - m_java_session_ref = env->NewGlobalRef(java_session_obj); - jobject global_obj_ref_tmp(m_java_session_ref); + // error_handler could be called after JniSession destructed. So we need to pass a weak ref to lambda to avoid + // the corrupted pointer. + std::weak_ptr weak_session_ref(m_java_session_ref); auto sync_transact_callback = [local_realm_path](realm::VersionID, realm::VersionID) { auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator( realm::StringData(local_realm_path)); @@ -57,13 +61,29 @@ class JniSession { coordinator->wake_up_notifier_worker(); } }; - auto error_handler = [&, global_obj_ref_tmp](int error_code, std::string message) { - JNIEnv *local_env = JniUtils::get_env(true); - jclass java_session_class = local_env->GetObjectClass(global_obj_ref_tmp); - jmethodID notify_error_handler = local_env->GetMethodID(java_session_class, - "notifySessionError", "(ILjava/lang/String;)V"); - local_env->CallVoidMethod(global_obj_ref_tmp, - notify_error_handler, error_code, env->NewStringUTF(message.c_str())); + auto error_handler = [weak_session_ref](std::error_code error_code, bool is_fatal, const std::string message) { + if (error_code.category() != realm::sync::protocol_error_category() || + error_code.category() != realm::sync::client_error_category()) { + // FIXME: Consider below when moving to the OS sync manager. + // Ignore this error since it may cause exceptions in java ErrorCode.fromInt(). Throwing exception there + // will trigger "called with pending exception" later since the thread is created by java, and the + // endless loop is in native code. The java exception will never be thrown because of the endless loop + // will never quit to java land. + realm::jni_util::Log::e("Unhandled sync client error code %1, %2. is_fatal: %3.", + error_code.value(), error_code.message(), is_fatal); + return; + } + + auto session_ref = weak_session_ref.lock(); + if (session_ref) { + session_ref.get()->call_with_local_ref([&](JNIEnv* local_env, jobject obj) { + jclass java_session_class = local_env->GetObjectClass(obj); + static realm::jni_util::JavaMethod notify_error_handler( + local_env, obj, "notifySessionError", "(ILjava/lang/String;)V"); + local_env->CallVoidMethod( + obj, notify_error_handler, error_code.value(), local_env->NewStringUTF(message.c_str())); + }); + } }; m_sync_session->set_sync_transact_callback(sync_transact_callback); m_sync_session->set_error_handler(std::move(error_handler)); @@ -74,12 +94,6 @@ class JniSession { return m_sync_session; } - // Call this just before destroying the object to release JNI resources. - inline void close(JNIEnv* env) - { - env->DeleteGlobalRef(m_java_session_ref); - } - ~JniSession() { delete m_sync_session; @@ -87,7 +101,7 @@ class JniSession { private: realm::sync::Session* m_sync_session; - jobject m_java_session_ref; + std::shared_ptr m_java_session_ref; }; #endif // REALM_OBJECTSERVER_SHARED_HPP diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 0fd80867e4..f3fe17bc42 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -205,17 +205,6 @@ static UserStore getUserStore() { return userStore; } - // This is called from SyncManager.cpp from the worker thread the Sync Client is running on - // Right now Core doesn't send these errors to the proper session, so instead we need to notify all sessions - // from here. This can be removed once better error propagation is implemented in Sync Core. - @SuppressWarnings("unused") - private static void notifyErrorHandler(int errorCode, String errorMessage) { - ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); - for (ObjectServerSession session : SessionStore.getAllSessions()) { - session.onError(error); - } - } - // Notify listeners that a user logged in static void notifyUserLoggedIn(SyncUser user) { for (AuthenticationListener authListener : authListeners) { From b97b0888a4d9303dc8e81ce857909ad0ed0490a4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 3 Jan 2017 23:54:55 +0800 Subject: [PATCH 0344/2110] Update object store to 814beb5a1e9 Fix #3945 Fix #3964 --- CHANGELOG.md | 2 ++ realm/realm-library/src/main/cpp/object-store | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90ee4852b8..2f2aec0fbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ * "operation not permitted" issue when creating Realm file on some devices' external storage (#3629). * Crash on API 10 devices (#3726). +* `UnsatisfiedLinkError` caused by `pipe2` (#3945). +* Unrecoverable error with message "Try again" when the notification fifo is full (#3964). ### Enhancements diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 99570ba6e0..814beb5a1e 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 99570ba6e0711820ef71b20d7ce0975b52cfd197 +Subproject commit 814beb5a1e96f0bb72cf78e206b2e710ac79e217 From a94bb3fa04e5ae5689daf1b8afb16064fdc102f8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 4 Jan 2017 19:48:28 +0800 Subject: [PATCH 0345/2110] Enable -Werror and fix warnings (#3961) Adapt the same warning options from object store --- realm/realm-library/src/main/cpp/CMakeLists.txt | 6 ++++-- .../src/main/cpp/io_realm_RealmSchema.cpp | 2 +- .../src/main/cpp/io_realm_SyncManager.cpp | 2 +- .../src/main/cpp/io_realm_internal_LinkView.cpp | 2 +- .../main/cpp/io_realm_internal_SharedRealm.cpp | 16 +++++++++------- .../main/cpp/io_realm_internal_TableQuery.cpp | 2 +- .../main/cpp/io_realm_internal_UncheckedRow.cpp | 8 ++++---- ...internal_objectserver_ObjectServerSession.cpp | 2 +- .../realm-library/src/main/cpp/jni_util/log.cpp | 2 +- .../src/main/cpp/objectserver_shared.hpp | 1 - realm/realm-library/src/main/cpp/util.cpp | 5 ++++- .../io/realm/exceptions/RealmFileException.java | 6 ++++++ .../main/java/io/realm/internal/SharedRealm.java | 11 ++++++----- 13 files changed, 39 insertions(+), 26 deletions(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 6adbe71a32..95bfdd835f 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -116,8 +116,10 @@ endif() # d.init(ValueBase::m_from_link_list, ValueBase::m_values, D{}); #FIXME maybe-uninitialized is reported by table_view.cpp:272:15: # 'best.m_nanoseconds' was declared here -set(WARNING_CXX_FLAGS "-Wall -Wextra -pedantic -Wno-long-long -Wno-variadic-macros \ --Wno-missing-field-initializers -Wmissing-declarations -Wno-error=uninitialized -Wno-error=maybe-uninitialized") +# -Wno-missing-field-initializers disable in object store as well. +set(WARNING_CXX_FLAGS "-Werror -Wall -Wextra -pedantic -Wmissing-declarations \ + -Wempty-body -Wparentheses -Wunknown-pragmas -Wunreachable-code \ + -Wno-missing-field-initializers -Wno-maybe-uninitialized -Wno-uninitialized") set(REALM_COMMON_CXX_FLAGS "-DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14 -fsigned-char") if (build_SYNC) set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ENABLE_SYNC=1") diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp index 98649caff4..9c6f1992e3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp @@ -43,7 +43,7 @@ Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv *env, jclass, jlongArray o } JNIEXPORT void JNICALL -Java_io_realm_RealmSchema_nativeClose(JNIEnv *env, jclass, jlong nativePtr) { +Java_io_realm_RealmSchema_nativeClose(JNIEnv*, jclass, jlong nativePtr) { TR_ENTER_PTR(nativePtr) Schema* schema = reinterpret_cast(nativePtr); delete schema; diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index 433e44307e..17406c075b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -39,7 +39,7 @@ using namespace realm::jni_util; std::unique_ptr sync_client; JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient - (JNIEnv *env, jclass sync_manager_class) + (JNIEnv *env, jclass) { TR_ENTER() if (sync_client) return; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp index faf2325dd0..119cdf30fb 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp @@ -218,7 +218,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveAllTargetRows } JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable - (JNIEnv* env, jobject, jlong nativeLinkViewPtr) + (JNIEnv*, jobject, jlong nativeLinkViewPtr) { TR_ENTER_PTR(nativeLinkViewPtr) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 644a952dce..641ab053c9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -1,5 +1,7 @@ #include "io_realm_internal_SharedRealm.h" +#include + #include "object_store.hpp" #include "shared_realm.hpp" @@ -37,7 +39,7 @@ Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv *env, jclass, jstring tempo JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstring realm_path, jbyteArray key, jbyte schema_mode, jboolean in_memory, jboolean cache, jboolean disable_format_upgrade, - jboolean auto_change_notification, jstring sync_server_url, jstring sync_user_token) + jboolean auto_change_notification, REALM_UNUSED jstring sync_server_url, jstring /*sync_user_token*/) { TR_ENTER() @@ -52,11 +54,9 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri config->cache = cache; config->disable_format_upgrade = disable_format_upgrade; config->automatic_change_notifications = auto_change_notification; -#if REALM_ENABLE_SYNC if (sync_server_url) { config->force_sync_history = true; } -#endif return reinterpret_cast(config); } CATCH_STD() @@ -64,7 +64,7 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri } JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv* env, jclass, jlong config_ptr) +Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv*, jclass, jlong config_ptr) { TR_ENTER_PTR(config_ptr) @@ -89,7 +89,7 @@ Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlo } JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv* env, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -132,7 +132,7 @@ Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv *env, jclass, JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -162,6 +162,8 @@ Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong s try { return static_cast(ObjectStore::get_schema_version(shared_realm->read_group())); } CATCH_STD() + + return static_cast(ObjectStore::NotVersioned); } JNIEXPORT void JNICALL @@ -249,7 +251,7 @@ Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong } JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv* env, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index ad1f39588a..82516b730d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -1765,7 +1765,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeCloseQueryHandover - (JNIEnv* env, jclass, jlong nativeHandoverQuery) + (JNIEnv*, jclass, jlong nativeHandoverQuery) { TR_ENTER_PTR(nativeHandoverQuery) delete HO(Query, nativeHandoverQuery); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 8039c5a541..b55fdc68e2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -22,7 +22,7 @@ using namespace realm; static void finalize_unchecked_row(jlong ptr); JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount - (JNIEnv *env, jobject, jlong nativeRowPtr) + (JNIEnv*, jobject, jlong nativeRowPtr) { TR_ENTER_PTR(nativeRowPtr) if (!ROW(nativeRowPtr)->is_attached()) @@ -59,7 +59,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex } JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) + (JNIEnv*, jobject, jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) return static_cast( ROW(nativeRowPtr)->get_column_type( S(columnIndex)) ); // noexcept @@ -331,7 +331,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsAttached - (JNIEnv* env, jobject, jlong nativeRowPtr) + (JNIEnv*, jobject, jlong nativeRowPtr) { TR_ENTER_PTR(nativeRowPtr) return ROW(nativeRowPtr)->is_attached(); @@ -345,7 +345,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeHasColumn } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { + (JNIEnv*, jobject, jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) return ROW(nativeRowPtr)->is_null(columnIndex); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp index cbd02f0d35..0caa7acccc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp @@ -70,7 +70,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_n JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeUnbind - (JNIEnv *env, jobject, jlong sessionPointer) + (JNIEnv *, jobject, jlong sessionPointer) { TR_ENTER() JniSession* session = SS(sessionPointer); diff --git a/realm/realm-library/src/main/cpp/jni_util/log.cpp b/realm/realm-library/src/main/cpp/jni_util/log.cpp index 2e9ed01ae0..d4dfbefbc6 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.cpp @@ -168,7 +168,7 @@ void Log::log(Level level, const char* tag, jthrowable throwable, const char* me void CoreLoggerBridge::do_log(realm::util::Logger::Level level, std::string msg) { // Ignore the level threshold from the root logger. - Log::Level jni_level; + Log::Level jni_level = Log::all; // Initial value to suppress the false positive compile warning. switch (level) { case Level::trace: jni_level = Log::trace; break; case Level::debug: // Fall through. Map to same level debug. diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index ab7a9f7369..bf079cb824 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -77,7 +77,6 @@ class JniSession { auto session_ref = weak_session_ref.lock(); if (session_ref) { session_ref.get()->call_with_local_ref([&](JNIEnv* local_env, jobject obj) { - jclass java_session_class = local_env->GetObjectClass(obj); static realm::jni_util::JavaMethod notify_error_handler( local_env, obj, "notifySessionError", "(ILjava/lang/String;)V"); local_env->CallVoidMethod( diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 97c62dd707..135d3e56b8 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -164,11 +164,14 @@ void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::Rea jmethodID constructor = env->GetMethodID(cls, "", "(BLjava/lang/String;)V"); // Initial value to suppress gcc warning. - jbyte kind_code; + jbyte kind_code = -1; // To suppress compile warning. switch (kind) { case realm::RealmFileException::Kind::AccessError: kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_ACCESS_ERROR; break; + case realm::RealmFileException::Kind::BadHistoryError: + kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_BAD_HISTORY; + break; case realm::RealmFileException::Kind::PermissionDenied: kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_PERMISSION_DENIED; break; diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java index 07b63b39c8..e4bca48d1b 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java @@ -32,6 +32,10 @@ public enum Kind { * Thrown for any I/O related exception scenarios when a Realm is opened. */ ACCESS_ERROR, + /** + * Thrown if the history type of the on-disk Realm is unexpected or incompatible. + */ + BAD_HISTORY, /** * Thrown if the user does not have permission to open or create the specified file in the specified access * mode when the Realm is opened. @@ -70,6 +74,8 @@ static Kind getKind(byte value) { return INCOMPATIBLE_LOCK_FILE; case SharedRealm.FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED: return FORMAT_UPGRADE_REQUIRED; + case SharedRealm.FILE_EXCEPTION_KIND_BAD_HISTORY: + return BAD_HISTORY; default: throw new RuntimeException("Unknown value for RealmFileException kind."); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index e7ad6c0417..356460a9c3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -27,11 +27,12 @@ public final class SharedRealm implements Closeable { // Const value for RealmFileException conversion public static final byte FILE_EXCEPTION_KIND_ACCESS_ERROR = 0; - public static final byte FILE_EXCEPTION_KIND_PERMISSION_DENIED = 1; - public static final byte FILE_EXCEPTION_KIND_EXISTS = 2; - public static final byte FILE_EXCEPTION_KIND_NOT_FOUND = 3; - public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 4; - public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 5; + public static final byte FILE_EXCEPTION_KIND_BAD_HISTORY = 1; + public static final byte FILE_EXCEPTION_KIND_PERMISSION_DENIED = 2; + public static final byte FILE_EXCEPTION_KIND_EXISTS = 3; + public static final byte FILE_EXCEPTION_KIND_NOT_FOUND = 4; + public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 5; + public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 6; public static void initialize(File tempDirectory) { if (SharedRealm.temporaryDirectory != null) { From 6f03d34405bb277d47fca88d3ae878b0e356f568 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 4 Jan 2017 20:07:01 +0800 Subject: [PATCH 0346/2110] Warning - comparison between signed and unsigned --- .../realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 8c86146d45..a3f98fc0ca 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -91,7 +91,7 @@ Java_io_realm_RealmFileUserStore_nativeGetAllUsers (JNIEnv *env, jclass) ThrowException(env, OutOfMemory, ERR_COULD_NOT_ALLOCATE_MEMORY); return nullptr; } - for (int i = 0; i < len; ++i) { + for (size_t i = 0; i < len; ++i) { env->SetObjectArrayElement(users_token, i, to_jstring(env, all_users[i]->refresh_token().data())); } From 5d1d1f1fe39ebcb5c03291da803a55ddfec09bd4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 4 Jan 2017 20:25:17 +0800 Subject: [PATCH 0347/2110] Single daemon thread for notification (#3666) With changes in https://github.com/realm/realm-object-store/pull/197 , there is only one thread will be create for listening changes for all different Realms. But an additional SharedGroup will be created in the daemon thread for determine which SharedGroup has changed since last time. That requires some changes in the java side especially the daemon thread should not be created when Realm.compactRealm called. --- CHANGELOG.md | 1 + .../src/androidTest/java/io/realm/RealmTests.java | 12 +++++++++++- .../java/io/realm/internal/SharedRealmTests.java | 4 ++-- .../src/main/java/io/realm/BaseRealm.java | 2 +- .../src/main/java/io/realm/internal/SharedRealm.java | 8 +++++--- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f2aec0fbd..27ae682989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Enhancements * All major public classes are now non-final. This is mostly a compromise to support Mockito. All protected fields/methods are still not considered part of the public API and can change without notice (#3869). +* All Realm instances share a single notification daemon thread. * Fixed Java lint warnings with generated proxy classes (#2929). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 5e65a027c3..5c8491f488 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -41,6 +41,7 @@ import org.junit.runner.RunWith; import java.io.File; +import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -3786,7 +3787,16 @@ public void namedPipeDirForExternalStorage() { Assume.assumeTrue("SELinux is not enforced on this device.", TestHelper.isSelinuxEnforcing()); - assertEquals(2, namedPipeDir.list().length); + // Only check the fifo file created by call, since all Realm instances share the same fifo created by + // external_commit_helper which might not be created in the newly created dir if there are Realm instances + // are not deleted when TestHelper.deleteRecursively(namedPipeDir) called. + File[] files = namedPipeDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.matches("realm_.*cv"); + } + }); + assertEquals(1, files.length); // test if it works when the namedPipeDir and the named pipe files already exist. realmOnExternalStorage = Realm.getInstance(config); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java index 9dab44e638..b0b339e230 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java @@ -169,7 +169,7 @@ public void onSchemaVersionChanged(long currentVersion) { listenerCalled.set(true); schemaVersionFromListener.set(currentVersion); } - }); + }, true); final long before = sharedRealm.getSchemaVersion(); @@ -208,7 +208,7 @@ public void onSchemaVersionChanged(long currentVersion) { listenerCalled.set(true); schemaVersionFromListener.set(currentVersion); } - }); + }, true); final long before = sharedRealm.getSchemaVersion(); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index c61b2cefae..6a4168244b 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -86,7 +86,7 @@ protected BaseRealm(RealmConfiguration configuration) { public void onSchemaVersionChanged(long currentVersion) { RealmCache.updateSchemaCache((Realm) BaseRealm.this); } - }); + }, true); this.schema = new RealmSchema(this); if (handlerController.isAutoRefreshAvailable()) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 356460a9c3..cc3f52827e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -179,18 +179,20 @@ private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotif objectServerFacade = null; } + // This will create a SharedRealm where autoChangeNotifications is false, + // If autoChangeNotifications is true, an additional SharedGroup might be created in the OS's external commit helper. + // That is not needed for some cases: eg.: An extra opened SharedGroup will cause a compact failure. public static SharedRealm getInstance(RealmConfiguration config) { - return getInstance(config, null, null); + return getInstance(config, null, null, false); } public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier realmNotifier, - SchemaVersionListener schemaVersionListener) { + SchemaVersionListener schemaVersionListener, boolean autoChangeNotifications) { String[] userAndServer = ObjectServerFacade.getSyncFacadeIfPossible().getUserAndServerUrl(config); String rosServerUrl = userAndServer[0]; String rosUserToken = userAndServer[1]; boolean enable_caching = false; // Handled in Java currently boolean disableFormatUpgrade = false; // TODO Double negatives :/ - boolean autoChangeNotifications = true; long nativeConfigPtr = nativeCreateConfig( config.getPath(), config.getEncryptionKey(), From f05ab89dc0a4a8f59615e6e7efc2e40d77176b79 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 5 Jan 2017 02:07:05 +0000 Subject: [PATCH 0348/2110] Nh/fix 3966 (#3979) Realm migration is triggered, when the primary key definition is altered (#3966) --- CHANGELOG.md | 1 + .../processor/RealmProxyClassGenerator.java | 26 ++++-- .../io/realm/AllTypesRealmProxy.java | 11 ++- .../io/realm/BooleansRealmProxy.java | 4 + .../io/realm/NullTypesRealmProxy.java | 4 + .../resources/io/realm/SimpleRealmProxy.java | 4 + .../java/io/realm/RealmMigrationTests.java | 86 ++++++++++++++++++- .../realm/migration/MigrationPrimaryKey.java | 12 +-- 8 files changed, 128 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27ae682989..4bf8a52953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Crash on API 10 devices (#3726). * `UnsatisfiedLinkError` caused by `pipe2` (#3945). * Unrecoverable error with message "Try again" when the notification fifo is full (#3964). +* Realm migration wasn't triggered when the primary key definition was altered (#3966). ### Enhancements diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index a29b69dff8..eb99fbeb6e 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -668,6 +668,25 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.emitStatement("final %1$s columnInfo = new %1$s(sharedRealm.getPath(), table)", columnInfoClassName()); writer.emitEmptyLine(); + // verify primary key definition was not altered + if (metadata.hasPrimaryKey()) { + // the current model defines a PK, make sure it's defined in the Realm schema + String fieldName = metadata.getPrimaryKey().getSimpleName().toString(); + writer.beginControlFlow("if (!table.hasPrimaryKey())") + .emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Primary key not defined for field '%s' in existing Realm file. @PrimaryKey was added.\")", metadata.getPrimaryKey().getSimpleName().toString()) + .nextControlFlow("else") + .beginControlFlow("if (table.getPrimaryKey() != columnInfo.%sIndex)", fieldName) + .emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Primary Key annotation definition was changed, from field \" + table.getColumnName(table.getPrimaryKey()) + \" to field %s\")" ,metadata.getPrimaryKey().getSimpleName().toString()) + .endControlFlow() + .endControlFlow(); + } else { + // the current model doesn't define a PK, make sure it's not defined in the Realm schema + writer.beginControlFlow("if (table.hasPrimaryKey())") + .emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Primary Key defined for field \" + table.getColumnName(table.getPrimaryKey()) + \" was removed.\")") + .endControlFlow(); + } + writer.emitEmptyLine(); + // For each field verify there is a corresponding long fieldIndex = 0; for (VariableElement field : metadata.getFields()) { @@ -738,13 +757,6 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { } } - // Validate @PrimaryKey - if (metadata.isPrimaryKey(field)) { - writer.beginControlFlow("if (table.getPrimaryKey() != table.getColumnIndex(\"%s\"))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Primary key not defined for field '%s' in existing Realm file. Add @PrimaryKey.\")", fieldName); - writer.endControlFlow(); - } - // Validate @Index if (metadata.getIndexedFields().contains(field)) { writer.beginControlFlow("if (!table.hasSearchIndex(table.getColumnIndex(\"%s\")))", fieldName); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index e069a99d88..7bf9205056 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -533,6 +533,14 @@ public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean final AllTypesColumnInfo columnInfo = new AllTypesColumnInfo(sharedRealm.getPath(), table); + if (!table.hasPrimaryKey()) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary key not defined for field 'columnString' in existing Realm file. @PrimaryKey was added."); + } else { + if (table.getPrimaryKey() != columnInfo.columnStringIndex) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key annotation definition was changed, from field " + table.getColumnName(table.getPrimaryKey()) + " to field columnString"); + } + } + if (!columnTypes.containsKey("columnString")) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnString' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } @@ -542,9 +550,6 @@ public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean if (!table.isColumnNullable(columnInfo.columnStringIndex)) { throw new RealmMigrationNeededException(sharedRealm.getPath(),"@PrimaryKey field 'columnString' does not support null values in the existing Realm file. Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required."); } - if (table.getPrimaryKey() != table.getColumnIndex("columnString")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary key not defined for field 'columnString' in existing Realm file. Add @PrimaryKey."); - } if (!table.hasSearchIndex(table.getColumnIndex("columnString"))) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "Index not defined for field 'columnString' in existing Realm file. Either set @Index or migrate using io.realm.internal.Table.removeSearchIndex()."); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 081e18d42b..da1971a009 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -266,6 +266,10 @@ public static BooleansColumnInfo validateTable(SharedRealm sharedRealm, boolean final BooleansColumnInfo columnInfo = new BooleansColumnInfo(sharedRealm.getPath(), table); + if (table.hasPrimaryKey()) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); + } + if (!columnTypes.containsKey("done")) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'done' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 9fe3b2f11d..88e3de0cca 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -1094,6 +1094,10 @@ public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean final NullTypesColumnInfo columnInfo = new NullTypesColumnInfo(sharedRealm.getPath(), table); + if (table.hasPrimaryKey()) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); + } + if (!columnTypes.containsKey("fieldStringNotNull")) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldStringNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index bdb167bbf2..bd50bf8120 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -200,6 +200,10 @@ public static SimpleColumnInfo validateTable(SharedRealm sharedRealm, boolean al final SimpleColumnInfo columnInfo = new SimpleColumnInfo(sharedRealm.getPath(), table); + if (table.hasPrimaryKey()) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); + } + if (!columnTypes.containsKey("name")) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'name' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index cbba8cc04c..cb26f70fb5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -47,6 +47,7 @@ import io.realm.entities.PrimaryKeyAsShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; +import io.realm.entities.Thread; import io.realm.entities.migration.MigrationClassRenamed; import io.realm.entities.migration.MigrationFieldRenamed; import io.realm.entities.migration.MigrationFieldTypeToInt; @@ -196,11 +197,11 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { } @Test - public void notSettingPrimaryKeyThrows() { + public void addingPrimaryKeyThrows() { // Create v0 of the Realm RealmConfiguration originalConfig = configFactory.createConfigurationBuilder() - .schema(AllTypes.class) + .schema(Thread.class) .build(); Realm.getInstance(originalConfig).close(); @@ -218,14 +219,91 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { // Create v1 of the Realm RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .schemaVersion(1) - .schema(AllTypes.class, AnnotationTypes.class) + .schema(Thread.class, AnnotationTypes.class) + .migration(migration) + .build(); + try { + realm = Realm.getInstance(realmConfig); + fail(); + } catch (RealmMigrationNeededException e) { + if (!e.getMessage().equals("Primary key not defined for field 'id' in existing Realm file. @PrimaryKey was added.")) { + fail(e.toString()); + } + } finally { + if (realm != null) { + realm.close(); + } + } + } + + @Test + public void removingPrimaryKeyThrows() { + + // Create v0 of the Realm + RealmConfiguration originalConfig = configFactory.createConfigurationBuilder() + .schema(Thread.class) + .build(); + Realm.getInstance(originalConfig).close(); + + RealmMigration migration = new RealmMigration() { + @Override + public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { + RealmSchema schema = realm.getSchema(); + schema.create("StringOnly") + .addField("chars", String.class, FieldAttribute.PRIMARY_KEY); + } + }; + + // Create v1 of the Realm + RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() + .schemaVersion(1) + .schema(Thread.class, StringOnly.class) + .migration(migration) + .build(); + try { + realm = Realm.getInstance(realmConfig); + fail(); + } catch (RealmMigrationNeededException e) { + if (!e.getMessage().equals("Primary Key defined for field chars was removed.")) { + fail(e.toString()); + } + } finally { + if (realm != null) { + realm.close(); + } + } + } + + @Test + public void changingPrimaryKeyThrows() { + + // Create v0 of the Realm + RealmConfiguration originalConfig = configFactory.createConfigurationBuilder() + .schema(Thread.class) + .build(); + Realm.getInstance(originalConfig).close(); + + RealmMigration migration = new RealmMigration() { + @Override + public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { + RealmSchema schema = realm.getSchema(); + schema.create("PrimaryKeyAsString") + .addField("id", long.class, FieldAttribute.PRIMARY_KEY) // initial @PrimaryKey is on the int + .addField("name", String.class); + } + }; + + // Create v1 of the Realm + RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() + .schemaVersion(1) + .schema(Thread.class, PrimaryKeyAsString.class) .migration(migration) .build(); try { realm = Realm.getInstance(realmConfig); fail(); } catch (RealmMigrationNeededException e) { - if (!e.getMessage().equals("Primary key not defined for field 'id' in existing Realm file. Add @PrimaryKey.")) { + if (!e.getMessage().equals("Primary Key annotation definition was changed, from field id to field name")) { fail(e.toString()); } } finally { diff --git a/realm/realm-library/src/androidTest/java/io/realm/migration/MigrationPrimaryKey.java b/realm/realm-library/src/androidTest/java/io/realm/migration/MigrationPrimaryKey.java index f53221b9ef..79d55aedb8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/migration/MigrationPrimaryKey.java +++ b/realm/realm-library/src/androidTest/java/io/realm/migration/MigrationPrimaryKey.java @@ -22,13 +22,13 @@ * there does not exist. */ public interface MigrationPrimaryKey { - public static String CLASS_NAME = "MigrationPrimaryKey"; + String CLASS_NAME = "MigrationPrimaryKey"; - public static String FIELD_FIRST = "fieldFirst"; - public static String FIELD_SECOND = "fieldSecond"; + String FIELD_FIRST = "fieldFirst"; + String FIELD_SECOND = "fieldSecond"; // this is original primary key field name. - public static String FIELD_PRIMARY = "fieldPrimary"; - public static String FIELD_FOURTH = "fieldFourth"; - public static String FIELD_FIFTH = "fieldFifth"; + String FIELD_PRIMARY = "fieldPrimary"; + String FIELD_FOURTH = "fieldFourth"; + String FIELD_FIFTH = "fieldFifth"; } From 9fe3b50e5a4048dc9ea89537c33b272d77662437 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 10:30:39 +0800 Subject: [PATCH 0349/2110] Add missing changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf8a52953..b4b81b0039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * `UnsatisfiedLinkError` caused by `pipe2` (#3945). * Unrecoverable error with message "Try again" when the notification fifo is full (#3964). * Realm migration wasn't triggered when the primary key definition was altered (#3966). +* Use phantom reference to solve the finalize time out issue (#2496). ### Enhancements From 440368366b600572947e8fcdc81cade1c37d024c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 10:33:36 +0800 Subject: [PATCH 0350/2110] Release v2.2.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a031df061e..7e541aec69 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.2.2-SNAPSHOT \ No newline at end of file +2.2.2 \ No newline at end of file From faca541f5ef26b89071a5eb4d802830e8dfdf2ed Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 10:33:36 +0800 Subject: [PATCH 0351/2110] Prepare next release v2.2.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7e541aec69..7fea99011a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.2.2 \ No newline at end of file +2.2.3-SNAPSHOT \ No newline at end of file From 7d9052b14974611804456495b1788c7057bb73cb Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 15:36:53 +0800 Subject: [PATCH 0352/2110] Fix compile error --- .../realm-library/src/main/cpp/io_realm_internal_Collection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 391c90fb18..54b9b89700 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -450,7 +450,7 @@ Java_io_realm_internal_Collection_nativeDelete(JNIEnv *env, jclass, jlong native auto wrapper = reinterpret_cast(native_ptr); auto view = wrapper->get_results().get_tableview(); size_t size = view.size(); - if (index < 0 || index >= size) { + if (index < 0 || index >= static_cast(size)) { throw Results::OutOfBoundsIndexException{static_cast(index), size}; } view.remove(static_cast(index)); From 8857c82e1484bf3adc2c078f4328f18deab656ba Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 16:55:20 +0800 Subject: [PATCH 0353/2110] Tests for RealmNotififier --- .../io/realm/internal/RealmNotifierTests.java | 136 ++++++++++++++++++ .../src/main/cpp/java_binding_context.cpp | 13 +- .../java/io/realm/internal/RealmNotifier.java | 6 +- 3 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java new file mode 100644 index 0000000000..14d10c0078 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.internal; + + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.realm.RealmChangeListener; +import io.realm.RealmConfiguration; +import io.realm.internal.android.AndroidRealmNotifier; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class RealmNotifierTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + private RealmConfiguration config; + Capabilities capabilitiesCanDeliver = new Capabilities() { + @Override + public boolean canDeliverNotification() { + return true; + } + + @Override + public void checkCanDeliverNotification(String exceptionMessage) { + } + }; + + @Before + public void setUp() throws Exception { + config = configFactory.createConfiguration(); + } + + @After + public void tearDown() { + } + + private SharedRealm getSharedRealm() { + return SharedRealm.getInstance(config, null, true); + } + + @Test + @RunTestInLooperThread + public void post() { + RealmNotifier notifier = new AndroidRealmNotifier(capabilitiesCanDeliver); + notifier.post(new Runnable() { + @Override + public void run() { + looperThread.testComplete(); + } + }); + } + + @Test + @RunTestInLooperThread + public void postAtFrontOfQueue() { + RealmNotifier notifier = new AndroidRealmNotifier(capabilitiesCanDeliver); + notifier.post(new Runnable() { + @Override + public void run() { + fail(); + } + }); + notifier.postAtFrontOfQueue(new Runnable() { + @Override + public void run() { + looperThread.testComplete(); + } + }); + } + + @Test + @RunTestInLooperThread + public void addChangeListener_byLocalChanges() { + SharedRealm sharedRealm = getSharedRealm(); + sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { + @Override + public void onChange(SharedRealm sharedRealm) { + // Transaction has been committed in core, but commitTransaction hasn't returned in java. + // Need a flag in java. + //assertTrue(sharedRealm.isInTransaction()); + looperThread.testComplete(); + } + }); + sharedRealm.beginTransaction(); + sharedRealm.commitTransaction(); + } + + @Test + @RunTestInLooperThread + public void addChangeListener_byRemoteChanges() { + SharedRealm sharedRealm = getSharedRealm(); + sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { + @Override + public void onChange(SharedRealm sharedRealm) { + looperThread.testComplete(); + } + }); + new Thread(new Runnable() { + @Override + public void run() { + SharedRealm sharedRealm = getSharedRealm(); + sharedRealm.beginTransaction(); + sharedRealm.commitTransaction(); + sharedRealm.close(); + } + }).start(); + } +} diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index ba5e728cac..4d7d253354 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -55,12 +55,15 @@ std::vector JavaBindingContext::get_observed_rows return state_list; } + void JavaBindingContext::changes_available() { if (m_java_notifier) { m_java_notifier.call_with_local_ref([&] (JNIEnv* env, jobject notifier_obj) { // Method IDs from RealmNotifier implementation. Cache them as member vars. - static JavaMethod notify_by_other_method(env, notifier_obj, "changesAvailable", "()V"); + static JavaMethod notify_by_other_method(env, + notifier_obj, + "changesAvailable", "()V"); env->CallVoidMethod(notifier_obj, notify_by_other_method); }); } @@ -73,8 +76,7 @@ void JavaBindingContext::did_change(std::vector c auto env = JniUtils::get_env(); static JavaMethod row_observer_pair_on_change_method(env, "io/realm/internal/RowNotifier$RowObserverPair", - "onChange", - "()V"); + "onChange", "()V"); for (auto state : observer_state_list) { if (env->ExceptionCheck()) return; @@ -99,8 +101,9 @@ void JavaBindingContext::did_change(std::vector c if (env->ExceptionCheck()) return; m_java_notifier.call_with_local_ref(env, [&] (JNIEnv*, jobject notifier_obj) { - static JavaMethod realm_notifier_did_change_method(env, notifier_obj, - "didChange", "()V"); + static JavaMethod realm_notifier_did_change_method(env, + notifier_obj, + "didChange", "()V"); env->CallVoidMethod(notifier_obj, realm_notifier_did_change_method); }); diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 826bfe835c..5a7703e1a0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -78,8 +78,9 @@ public void onCalled(RealmObserverPair pair, Object observer) { * This is getting called on the same thread which created this Realm when the same Realm file has been changed by * other thread. The changes on the same thread should not trigger this call. */ + // Package protected to avoid finding class by name in JNI. @SuppressWarnings("unused") // called from java_binding_context.cpp - protected void didChange() { + void didChange() { realmObserverPairs.foreach(onChangeCallBack); for (Runnable runnable : transactionCallbacks) { runnable.run(); @@ -88,7 +89,8 @@ protected void didChange() { } @SuppressWarnings("unused") // called from java_binding_context.cpp - protected void changesAvailable() { + // Package protected to avoid finding class by name in JNI. + void changesAvailable() { sharedRealm.disableCollectionSnapshot(); } From 221e38b270ed8cbfe2d15119a266d2fc2eed712a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 17:05:04 +0800 Subject: [PATCH 0354/2110] Disallow Realm listeners on non-looper thread It is possible to have listeners for non-looper thread, and trigger them through SharedRealm.refresh(). But there are many corner cases need to be covered if we enable it. Disallow it for now to stick with current behaviour. --- realm/realm-library/src/main/java/io/realm/BaseRealm.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 4408de71a7..d2033322f3 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -58,6 +58,8 @@ abstract class BaseRealm implements Closeable { "This Realm instance has already been closed, making it unusable."; private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; + private static final String LISTENER_NOT_ALLOWED_MESSAGE = + "Listeners cannot be used on current thread."; volatile static Context applicationContext; @@ -126,6 +128,7 @@ protected void addListener(RealmChangeListener listener throw new IllegalArgumentException("Listener should not be null"); } checkIfValid(); + sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); //noinspection unchecked sharedRealm.realmNotifier.addChangeListener((T) this, listener); } @@ -143,6 +146,7 @@ public void removeChangeListener(RealmChangeListener li throw new IllegalArgumentException("Listener should not be null"); } checkIfValid(); + sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); //noinspection unchecked sharedRealm.realmNotifier.removeChangeListener((T) this, listener); } @@ -175,6 +179,7 @@ public void removeChangeListener(RealmChangeListener li */ public void removeAllChangeListeners() { checkIfValid(); + sharedRealm.capabilities.checkCanDeliverNotification("removeListener cannot be called on current thread."); sharedRealm.realmNotifier.removeAllChangeListeners(); } From 0710b12d92e77fca3642a10f800e9d41d5614867 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 17:27:59 +0800 Subject: [PATCH 0355/2110] Add Collection.isValid Also checking in the pending row, if the pending collection becomes invalid, just do nothing. --- .../main/cpp/io_realm_internal_Collection.cpp | 10 ++++++++++ .../src/main/java/io/realm/RealmResults.java | 2 +- .../java/io/realm/internal/Collection.java | 5 +++++ .../java/io/realm/internal/PendingRow.java | 19 ++++++++++++------- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 54b9b89700..05cb295e50 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -459,3 +459,13 @@ Java_io_realm_internal_Collection_nativeDelete(JNIEnv *env, jclass, jlong native } CATCH_STD() } +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_Collection_nativeIsValid(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + try { + auto wrapper = reinterpret_cast(native_ptr); + return wrapper->get_results().is_valid(); + } CATCH_STD() + return JNI_FALSE; +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 38a4453302..822172e92c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -100,7 +100,7 @@ Collection getCollection() { * {@inheritDoc} */ public boolean isValid() { - return !realm.isClosed(); + return collection.isValid(); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index ea2dea8827..8cbc460da8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -215,6 +215,10 @@ public void removeAllListeners() { nativeStopListening(nativePtr); } + public boolean isValid() { + return nativeIsValid(nativePtr); + } + // Called by JNI @KeepMember @SuppressWarnings("unused") @@ -268,4 +272,5 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native void nativeEnableSnapshot(long nativePtr); private static native void nativeDisableSnapshot(long nativePtr); private static native boolean nativeIsDetached(long nativePtr); + private static native boolean nativeIsValid(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 3dbd0d1317..e936e6b144 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -49,13 +49,18 @@ public void onChange(PendingRow pendingRow) { return; } - // PendingRow will always get the first Row of the query since we only support findFirst. - UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); - // If no rows returned by the query, just wait for the query updates until it returns a valid row. - if (uncheckedRow != null) { - Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; - // Ask the front end to reset the row and stop async query. - frontEnd.get().onQueryFinished(row); + if (pendingCollection.isValid()) { + // PendingRow will always get the first Row of the query since we only support findFirst. + UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); + // If no rows returned by the query, just wait for the query updates until it returns a valid row. + if (uncheckedRow != null) { + Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; + // Ask the front end to reset the row and stop async query. + frontEnd.get().onQueryFinished(row); + clearPendingCollection(); + } + } else { + // The Realm is closed. Do nothing then. clearPendingCollection(); } } From 68b42835cf6c8818359f56aae06fa0c7d6a850ba Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 18:18:49 +0800 Subject: [PATCH 0356/2110] Disallow listeners for Results & Object on non-looper --- .../src/main/java/io/realm/BaseRealm.java | 3 +-- .../src/main/java/io/realm/RealmObject.java | 12 +++++++++--- .../src/main/java/io/realm/RealmResults.java | 4 ++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index d2033322f3..7340cf5343 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -58,8 +58,7 @@ abstract class BaseRealm implements Closeable { "This Realm instance has already been closed, making it unusable."; private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; - private static final String LISTENER_NOT_ALLOWED_MESSAGE = - "Listeners cannot be used on current thread."; + private static final String LISTENER_NOT_ALLOWED_MESSAGE = "Listeners cannot be used on current thread."; volatile static Context applicationContext; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 7462444cf5..2923c8a09c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -67,6 +67,7 @@ @RealmClass public abstract class RealmObject implements RealmModel { + private static final String LISTENER_NOT_ALLOWED_MESSAGE = "Listeners cannot be used on current thread."; /** * Deletes the object from the Realm it is currently associated to. @@ -271,7 +272,7 @@ public static void addChangeListener(E object, RealmChang RealmObjectProxy proxy = (RealmObjectProxy) object; BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); realm.checkIfValid(); - realm.sharedRealm.capabilities.checkCanDeliverNotification("Listener cannot be added."); + realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); //noinspection unchecked proxy.realmGet$proxyState().addChangeListener(listener); } else { @@ -309,7 +310,10 @@ public static void removeChangeListener(E object, RealmCh } if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; - proxy.realmGet$proxyState().getRealm$realm().checkIfValid(); + BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); + realm.checkIfValid(); + realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); + // FIXME: Below doesn't seem to be correct? proxy.realmGet$proxyState().getListeners$realm().remove(listener); } else { throw new IllegalArgumentException("Cannot remove listener from this unmanaged RealmObject (created outside of Realm)"); @@ -332,7 +336,9 @@ public final void removeChangeListeners() { public static void removeChangeListeners(E object) { if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; - proxy.realmGet$proxyState().getRealm$realm().checkIfValid(); + BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); + realm.checkIfValid(); + realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); proxy.realmGet$proxyState().getListeners$realm().clear(); } else { throw new IllegalArgumentException("Cannot remove listeners from this unmanaged RealmObject (created outside of Realm)"); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 822172e92c..b5304de075 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -66,6 +66,7 @@ public class RealmResults extends AbstractList implements OrderedRealmCollection { private final static String NOT_SUPPORTED_MESSAGE = "This method is not supported by RealmResults."; + private static final String LISTENER_NOT_ALLOWED_MESSAGE = "Listeners cannot be used on current thread."; final BaseRealm realm; Class classSpec; // Return type @@ -766,6 +767,7 @@ public void addChangeListener(RealmChangeListener> listener) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); + realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); collection.addListener(this, listener); } @@ -781,6 +783,7 @@ public void removeChangeListener(RealmChangeListener listener) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); + realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); collection.removeListener(this, listener); } @@ -789,6 +792,7 @@ public void removeChangeListener(RealmChangeListener listener) { */ public void removeChangeListeners() { realm.checkIfValid(); + realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); collection.removeAllListeners(); } From 1194a10e048150f91a64e3fae7db6fc1f210f947 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 18:44:54 +0800 Subject: [PATCH 0357/2110] Only trigger realm notifier when version changed --- .../src/main/cpp/java_binding_context.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index 4d7d253354..7e7d3878d5 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -71,7 +71,7 @@ void JavaBindingContext::changes_available() void JavaBindingContext::did_change(std::vector const& observer_state_list, std::vector const& invalidated, - bool /*version_changed*/) + bool version_changed) { auto env = JniUtils::get_env(); static JavaMethod row_observer_pair_on_change_method(env, @@ -100,12 +100,11 @@ void JavaBindingContext::did_change(std::vector c }); if (env->ExceptionCheck()) return; - m_java_notifier.call_with_local_ref(env, [&] (JNIEnv*, jobject notifier_obj) { - static JavaMethod realm_notifier_did_change_method(env, - notifier_obj, - "didChange", "()V"); - - env->CallVoidMethod(notifier_obj, realm_notifier_did_change_method); - }); + if (version_changed) { + m_java_notifier.call_with_local_ref(env, [&] (JNIEnv*, jobject notifier_obj) { + static JavaMethod realm_notifier_did_change_method(env, notifier_obj, "didChange", "()V"); + env->CallVoidMethod(notifier_obj, realm_notifier_did_change_method); + }); + } } From 4c97c4bed50fafb3595f83042a64802c3bf4a928 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 11:53:26 +0800 Subject: [PATCH 0358/2110] realm-java-.zip cannot be uploaded The gradle exec task only takes the last commandLine arg. To fix the manual uploading in the release process. --- build.gradle | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index cd7c5facad..870ada9c85 100644 --- a/build.gradle +++ b/build.gradle @@ -266,13 +266,21 @@ task clean { dependsOn cleanLocalMavenRepos } -task uploadDistributionPackage(type: Exec) { +task uploadDistributionPackage { group = 'Release' description = 'Upload the distribution package to S3' dependsOn distributionPackage dependsOn distributionJniUnstrippedPackage - commandLine 's3cmd', 'put', "${buildDir}/outputs/distribution/realm-java-${currentVersion}.zip", 's3://static.realm.io/downloads/java/' - commandLine 's3cmd', 'put', "${buildDir}/outputs/distribution/realm-java-jni-libs-unstripped-${currentVersion}.zip", 's3://static.realm.io/downloads/java/' + doLast { + exec { + workingDir "${buildDir}/outputs/distribution/" + commandLine 's3cmd', 'put', "realm-java-${currentVersion}.zip", 's3://static.realm.io/downloads/java/' + } + exec { + workingDir "${buildDir}/outputs/distribution/" + commandLine 's3cmd', 'put', "realm-java-jni-libs-unstripped-${currentVersion}.zip", 's3://static.realm.io/downloads/java/' + } + } } task createEmptyFile(type: Exec) { From bdc40a11baeb64e65bbc46dc2489b5f93c40d91a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 5 Jan 2017 19:58:46 +0800 Subject: [PATCH 0359/2110] Life is limited --- .../realm-library/src/androidTest/java/io/realm/TestHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index f849231d86..ae41d531dd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -778,7 +778,7 @@ public static void populateForDistinctFieldsOrder(Realm realm, long numberOfBloc } public static void awaitOrFail(CountDownLatch latch) { - awaitOrFail(latch, 700000); + awaitOrFail(latch, 7); } public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { From 61aabad578b0ffdf84e7be2ac66168b5ffd88916 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Thu, 5 Jan 2017 09:42:58 -0800 Subject: [PATCH 0360/2110] Raise the timeout for the connectedUnitTests from 7 to 10 seconds (required by Nexus 4, 5.1.1) (#3985) Edited the README.md to clarify some of the "Gottchas" and to separate connected tests that require a server and those that do not --- README.md | 75 ++++++++++++++----- .../androidTest/java/io/realm/TestHelper.java | 2 +- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 1496891c4c..ee521543c4 100644 --- a/README.md +++ b/README.md @@ -70,38 +70,41 @@ In case you don't want to use the precompiled version, you can build Realm yours brew install android-ndk-r10e ``` - * Add two environment variables to your profile: + * Add two environment variables to your profile (presuming you used brew to install the NDK): ``` export ANDROID_HOME=~/Library/Android/sdk export ANDROID_NDK_HOME=/usr/local/Cellar/android-ndk-r10e/r10e ``` - * If you want to build with Android Studio, `ndk.dir` has to be defined in the `realm/local.properties` as well. + * If you want to build with Android Studio, `ndk.dir` has to be defined in the `realm/local.properties` as well. Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited. Again, presuming you used brew to install the NDK: ``` ndk.dir=/usr/local/Cellar/android-ndk-r10e/r10e ``` - * If you are using OS X, you'd be better to add following lines to `~/.profile` (or `~/.zprofile` if the login shell is `zsh`) in order for Android Studio to see those environment variables. + * If you will be launching Android Studio from the OS X Finder, you should also run the following two commands: ``` launchctl setenv ANDROID_HOME "$ANDROID_HOME" launchctl setenv ANDROID_NDK_HOME "$ANDROID_NDK_HOME" ``` - * And if you'd like to specify the location to store the archives of Realm's core, set `REALM_CORE_DOWNLOAD_DIR` environment variable. It enables you to keep core's archive when executing `git clean -xfd`. + * If you'd like to specify the location in which to store the archives of Realm Core, define the `REALM_CORE_DOWNLOAD_DIR` environment variable. It enables you to keep Core's archive when executing `git clean -xfd`. ``` export REALM_CORE_DOWNLOAD_DIR=~/.realmCore ``` - OS X users should also add following line to `~/.profile` (or `~/.zprofile` if the login shell is `zsh`) in order for Android Studio to see this environment variable.. + OS X users must also run the following command in order for Android Studio to see this environment variable.. ``` launchctl setenv REALM_CORE_DOWNLOAD_DIR "$REALM_CORE_DOWNLOAD_DIR" ``` +It would be a good idea to add all of the symbol definitions (and their accompanying `launchctl` commands, if you are using OS X) to your `~/.profile` (or `~/.zprofile` if the login shell is `zsh`) + + ### Download sources You can download the source code of Realm Java by using git. Since realm-java has git submodules, use `--recursive` when cloning the repository. @@ -131,16 +134,18 @@ That command will generate: * a jar file for the annotations * a jar file for the annotations processor +The full build may take an hour or more, to complete. + ### Other Commands * `./gradlew tasks` will show all the available tasks * `./gradlew javadoc` will generate the Javadocs * `./gradlew monkeyExamples` will run the monkey tests on all the examples * `./gradlew installRealmJava` will install the Realm library and plugin to mavenLocal() - * `./gradlew clean -PdontCleanJniFiles` will remove all generated files except for JNI related files. This saves recompilation time a lot. - * `./gradlew connectedUnitTests -PbuildTargetABIs=$(adb shell getprop ro.product.cpu.abi)` will build JNI files only for the ABI which corresponds to the connected device. + * `./gradlew clean -PdontCleanJniFiles` will remove all generated files except for JNI related files. This reduces recompilation time a lot. + * `./gradlew connectedUnitTests -PbuildTargetABIs=$(adb shell getprop ro.product.cpu.abi)` will build JNI files only for the ABI which corresponds to the connected device. These tests require a running Object Server (see below) -Generating the Javadoc using the command above will report a large number of warnings. The Javadoc is generated, and we will fix the issue in the near future. +Generating the Javadoc using the command above may generate warnings. The Javadoc is generated despite the warnings. ### Gotchas @@ -151,11 +156,15 @@ The repository is organized in six Gradle projects: * `realm-transformer`: it contains the bytecode transformer. * `gradle-plugin`: it contains the Gradle plugin. * `examples`: it contains the example projects. This project directly depends on `gradle-plugin` which adds a dependency to the artifacts produced by `realm`. - * The root folder is another Gradle project and all it does is orchestrating the other jobs + * The root folder is another Gradle project. All it does is orchestrate the other jobs This means that `./gradlew clean` and `./gradlew cleanExamples` will fail if `assembleExamples` has not been executed first. Note that IntelliJ [does not support multiple projects in the same window](https://youtrack.jetbrains.com/issue/IDEABKL-6118#) -so each sub-project must be opened in its own window. +so each of the six Gradle projects must be imported as a separate IntelliJ project. + +Since the repository contains several completely independent Gradle projects, several independent builds are run to assemble it. +Seeing a line like: `:realm:realm-library:compileBaseDebugAndroidTestSources UP-TO-DATE` in the build log does *not* imply +that you can run `./gradlew :realm:realm-library:compileBaseDebugAndroidTestSources`. ## Examples @@ -163,6 +172,26 @@ The `./examples` folder contain a number of example projects showing how Realm c Standalone examples can be [downloaded from website](https://realm.io/docs/java/latest/#getting-started). +## Running Tests on a Device + +To run these tests you must have a device connected to the build computer and the `adb` command must be in your `PATH` + +1. Connect an Android device and verify that that the command `adb devices` shows a connected device: + + ```sh + adb devices + List of devices attached + 004c03eb5615429f device + ``` +2. Run instrumentation tests: + + ```sh + cd realm + ./gradlew connectedBaseDebugAndroidTest + ``` + +These tests may take as much as half an hour to complete. + ## Running Tests Using The Realm Object Server Tests in `realm/realm-library/src/syncIntegrationTest` require a running testing server to work. @@ -171,21 +200,27 @@ A docker image can be built from `tools/sync_test_server/Dockerfile` to run the To run a testing server locally: -1. Install docker. +1. Install [docker](https://www.docker.com/products/overview). 2. Run `tools/sync_test_server/start_server.sh`: -```sh -cd tools/sync_test_server -./start_server.sh -``` + ```sh + cd tools/sync_test_server + ./start_server.sh + ``` -3. Run instrumentation tests: + This command will not complete until the server has stopped. -```sh -cd realm -./gradlew connectedObjectServerDebugAndroidTest -``` +3. Run instrumentation tests + + In a new terminal window, run: + + ```sh + cd realm + ./gradlew connectedObjectServerDebugAndroidTest + ``` + +These tests may take as much as half an hour to complete. ## Contributing diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 87102f90da..9c13abf838 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -778,7 +778,7 @@ public static void populateForDistinctFieldsOrder(Realm realm, long numberOfBloc } public static void awaitOrFail(CountDownLatch latch) { - awaitOrFail(latch, 7); + awaitOrFail(latch, 10); } public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { From a901e6d34f7d36a11c0a370c67cb8b8d43f27655 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 6 Jan 2017 12:30:52 +0800 Subject: [PATCH 0361/2110] Ignore non-looper listener related test cases --- .../src/androidTest/java/io/realm/NotificationsTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 49a45392ca..383999ac12 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -1284,7 +1284,9 @@ public void onChange(RealmModel element) { @Test + @Ignore("Listeners for non-looper thread are not allowed for now") public void globalListener_nonLooperThread_triggeredByWaitForChange() { + realm = Realm.getInstance(realmConfig); final CountDownLatch latch = new CountDownLatch(1); realm.addChangeListener(new RealmChangeListener() { @Override @@ -1303,7 +1305,9 @@ public void execute(Realm realm) { } @Test + @Ignore("Listeners for non-looper thread are not allowed for now") public void globalListener_nonLooperThread_triggeredByLocalCommit() { + realm = Realm.getInstance(realmConfig); final CountDownLatch latch = new CountDownLatch(1); realm.addChangeListener(new RealmChangeListener() { @Override From e9bc165e908969968fee3968a6d15590b91810b2 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 6 Jan 2017 12:36:36 +0800 Subject: [PATCH 0362/2110] reattach when BindingContext::before_notify --- .../java/io/realm/internal/RealmNotifierTests.java | 3 ++- realm/realm-library/src/main/cpp/java_binding_context.cpp | 2 +- realm/realm-library/src/main/cpp/java_binding_context.hpp | 2 +- .../src/main/java/io/realm/internal/Collection.java | 7 ------- .../src/main/java/io/realm/internal/RealmNotifier.java | 1 + 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 14d10c0078..48b9baee19 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -31,7 +31,6 @@ import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; -import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -107,6 +106,7 @@ public void onChange(SharedRealm sharedRealm) { // Need a flag in java. //assertTrue(sharedRealm.isInTransaction()); looperThread.testComplete(); + sharedRealm.close(); } }); sharedRealm.beginTransaction(); @@ -121,6 +121,7 @@ public void addChangeListener_byRemoteChanges() { @Override public void onChange(SharedRealm sharedRealm) { looperThread.testComplete(); + sharedRealm.close(); } }); new Thread(new Runnable() { diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index 7e7d3878d5..b68db66bfe 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -56,7 +56,7 @@ std::vector JavaBindingContext::get_observed_rows return state_list; } -void JavaBindingContext::changes_available() +void JavaBindingContext::before_notify() { if (m_java_notifier) { m_java_notifier.call_with_local_ref([&] (JNIEnv* env, jobject notifier_obj) { diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index aaed72b2fc..18535fc12f 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -46,7 +46,7 @@ class JavaBindingContext final : public BindingContext { public: virtual ~JavaBindingContext() {}; virtual std::vector get_observed_rows(); - virtual void changes_available(); + virtual void before_notify(); virtual void did_change(std::vector const& observers, std::vector const& invalidated, bool version_changed=true); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 8cbc460da8..fee23909e7 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -223,13 +223,6 @@ public boolean isValid() { @KeepMember @SuppressWarnings("unused") private void notifyChangeListeners(boolean emptyChanges) { - // For the stable iteration. - // It is needed when the local commit triggered async query updates. And this is called in the next event loop - // by OS Realm::notify(). - if (!emptyChanges) { - this.disableSnapshot(); - } - if (emptyChanges && isDetached()) return; observerPairs.foreach(onChangeCallback); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 5a7703e1a0..93c9d5734c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -91,6 +91,7 @@ void didChange() { @SuppressWarnings("unused") // called from java_binding_context.cpp // Package protected to avoid finding class by name in JNI. void changesAvailable() { + // For the stable iteration. sharedRealm.disableCollectionSnapshot(); } From 95e56927f735c0663ad836ae0dc821e2cdb54ff9 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 6 Jan 2017 13:14:12 +0800 Subject: [PATCH 0363/2110] Remove warnIfMixingSyncWritesAndAsyncQueries test Since all the queries are async by default, print this warning doesn't help too much anymore. --- .../java/io/realm/NotificationsTest.java | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 383999ac12..8fb3a5c9f9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -1095,38 +1095,6 @@ public void execute(Realm realm) { }); } - @Test - @RunTestInLooperThread - public void warnIfMixingSyncWritesAndAsyncQueries() { - final Realm realm = looperThread.realm; - final AtomicBoolean warningLogged = new AtomicBoolean(false); - final TestHelper.TestLogger testLogger = new TestHelper.TestLogger() { - @Override - public void log(int level, String tag, Throwable throwable, String message) { - assertTrue(message.contains("Mixing asynchronous queries with local writes should be avoided.")); - if (level == LogLevel.WARN) { - warningLogged.set(true); - } - } - }; - RealmLog.add(testLogger); - - realm.beginTransaction(); - realm.createObject(AllTypes.class); - realm.commitTransaction(); - - RealmResults results = realm.where(AllTypes.class).findAllAsync(); - looperThread.keepStrongReference.add(results); - results.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults element) { - RealmLog.remove(testLogger); - assertTrue(warningLogged.get()); - looperThread.testComplete(); - } - }); - } - @Test @RunTestInLooperThread public void accessingSyncRealmResultInsideAsyncResultListener() { From b73a0a5ea5e5c67615995c423846e1acb1ebd361 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 6 Jan 2017 13:43:09 +0800 Subject: [PATCH 0364/2110] Adapt local_commit first related test cases --- .../java/io/realm/NotificationsTest.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 8fb3a5c9f9..132b416c1b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -961,12 +961,12 @@ public void execute(Realm realm) { // Step 2: Post a runnable to caller thread. // Event Queue: |Posted Runnable| <- TOP // Step 3: Delete object which will make the results contain an invalid object at this moment - // Right Event Queue: |LOCAL_COMMIT | Wrong Event Queue: |Posted Runnable | <- TOP - // |Posted Runnable| |REALM_CHANGED/LOCAL_COMMIT| + // Right Event Queue: |Reattach | Wrong Event Queue: |Posted Runnable | <- TOP + // |Posted Runnable| |REALM_CHANGED/Reattach| // Step 4: Posted runnable called. @Test @RunTestInLooperThread(/*step1*/ before = PopulateOneAllTypes.class) - public void realmListener_localChangeShouldBeSendAtFrontOfTheQueue() { + public void realmListener_reattachResultsShouldHappenFirst() { final Realm realm = looperThread.realm; final RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(1, results.size()); @@ -1002,15 +1002,15 @@ public void execute(Realm realm) { // Step 3: Post a runnable to caller thread. // Event Queue: |Posted Runnable| <- TOP // Step 4: Delete object which will make the results contain a invalid object at this moment - // Right Event Queue: |LOCAL_COMMIT | Wrong Event Queue: |Posted Runnable | <- TOP - // |Posted Runnable| |REALM_CHANGED/LOCAL_COMMIT| + // Right Event Queue: |Reattach | Wrong Event Queue: |Posted Runnable | <- TOP + // |Posted Runnable| |REALM_CHANGED/Reattach| // Step 5: Posted runnable called. @Test @RunTestInLooperThread(/*step1*/before = PopulateOneAllTypes.class) - public void realmListener_localChangeShouldBeSendAtFrontOfTheQueueWithLoadedAsync() { + public void realmListener_reattachResultsShouldHappenFirstWithReturnedAsync() { final AtomicBoolean changedFirstTime = new AtomicBoolean(false); final Realm realm = looperThread.realm; - final RealmResults asyncResults = realm.where(AllTypes.class).findAllAsync(); + final RealmResults asyncResults = realm.where(AllTypes.class).findAll(); final RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(1, results.size()); @@ -1051,38 +1051,38 @@ public void execute(Realm realm) { // See https://github.com/realm/realm-android-adapters/issues/48 // Step 1: Populate the db - // Step 2: Create a async query, and pause it - // Step 3: Post a runnable to caller thread. - // Event Queue: |Posted Runnable| <- TOP + // Step 2: Create a async query + // Step 3: Add listener to the async results + // Event Queue: |async callback| <- TOP // Step 4: Delete object which will make the results contain a invalid object at this moment - // Right Event Queue: |LOCAL_COMMIT | Wrong Event Queue: |Posted Runnable | <- TOP - // |Posted Runnable| |REALM_CHANGED/LOCAL_COMMIT| + // Right calling order: |Reattach | Wrong order: |async callback| <- TOP + // |async callback| |Reattach | // Step 5: Posted runnable called. // @Test @RunTestInLooperThread(/*step1*/before = PopulateOneAllTypes.class) - public void realmListener_localChangeShouldBeSendAtFrontOfTheQueueWithPausedAsync() { + public void realmListener_reattachResultsShouldHappenFirstNonReturnedAsync() { final Realm realm = looperThread.realm; - Realm.asyncTaskExecutor.pause(); - final RealmResults asyncResults = realm.where(AllTypes.class).findAllAsync(); + // Step 2 + final RealmResults asyncResults = realm.where(AllTypes.class).findAll(); final RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(1, results.size()); - // Step 2 - // The transaction later will trigger the results sync, and it should be run before this runnable. - looperThread.postRunnable(new Runnable() { + // Step 3 + looperThread.keepStrongReference.add(asyncResults); + asyncResults.addChangeListener(new RealmChangeListener>() { @Override - public void run() { + public void onChange(RealmResults element) { // Step 5 - assertFalse(asyncResults.isLoaded()); + assertEquals(0, asyncResults.size()); assertEquals(0, results.size()); looperThread.testComplete(); } }); - // Step 3 + // Step 4 realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { @@ -1103,7 +1103,7 @@ public void accessingSyncRealmResultInsideAsyncResultListener() { final RealmResults syncResults = realm.where(AllTypes.class).findAll(); - RealmResults results = realm.where(AllTypes.class).findAllAsync(); + RealmResults results = realm.where(AllTypes.class).findAll(); looperThread.keepStrongReference.add(results); results.addChangeListener(new RealmChangeListener>() { @Override From b9857f174cb3e287d198c25b7def8ae69afff5e7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 6 Jan 2017 14:05:55 +0800 Subject: [PATCH 0365/2110] Discard temp tests --- .../io/realm/RealmChangeListenerTests.java | 57 +------------------ 1 file changed, 2 insertions(+), 55 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java index ebb0a0bf1d..b058f3357a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java @@ -27,10 +27,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.Cat; -import io.realm.entities.Dog; import io.realm.entities.pojo.AllTypesRealmModel; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -148,21 +145,11 @@ public void returnedRealmObjectIsNotNull() { cat.addChangeListener(new RealmChangeListener() { @Override public void onChange(Cat object) { - //assertEquals("cat1", object.getName()); - //looperThread.testComplete(); - Cat cat = object; + assertEquals("cat1", object.getName()); + looperThread.testComplete(); } }); - cat.getAge(); - /* - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.where(Cat.class).findFirst().setName("cat1"); - } - }); - */ realm.beginTransaction(); cat.setName("cat1"); realm.commitTransaction(); @@ -238,44 +225,4 @@ public void onChange(RealmResults result) { allTypes.setString(AllTypes.FIELD_STRING, "test data 1"); dynamicRealm.commitTransaction(); } - - @Test - @RunTestInLooperThread - // FIXME: Used for DEV. Remove before merge - public void myTest() { - Realm realm = looperThread.realm; - RealmLog.setLevel(LogLevel.ALL); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObject(AllTypes.class); - } - }); - final RealmResults cats = realm.where(Cat.class).findAll(); - final RealmResults dogs = realm.where(Dog.class).findAll(); - final RealmResults allTypes = realm.where(AllTypes.class).findAll(); - double avg = allTypes.average(AllTypes.FIELD_DOUBLE); - looperThread.keepStrongReference.add(cats); - looperThread.keepStrongReference.add(dogs); - cats.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults result) { - Cat cat = result.first(); - assertEquals("cat1", result.first().getName()); - assertEquals("dog1", dogs.first().getName()); - looperThread.testComplete(); - } - }); - - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - Cat cat = realm.createObject(Cat.class); - cat.setName("cat1"); - - Dog dog = realm.createObject(Dog.class); - dog.setName("dog1"); - } - }); - } } From 2850670b12cb719ba41d55fe555a60941157d7f1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 6 Jan 2017 14:15:07 +0800 Subject: [PATCH 0366/2110] Fix test contains_realmObjectFromOtherRealm --- .../realm-library/src/main/java/io/realm/RealmResults.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index b5304de075..6921b24bc3 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -135,8 +135,11 @@ public boolean contains(Object object) { boolean contains = false; if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; - Row row = proxy.realmGet$proxyState().getRow$realm(); - contains = !(row instanceof InvalidRow) && collection.contains((UncheckedRow) row); + // TODO: Maybe we should just let OS throw? + if (proxy.realmGet$proxyState().getRealm$realm() == realm) { + Row row = proxy.realmGet$proxyState().getRow$realm(); + contains = !(row instanceof InvalidRow) && collection.contains((UncheckedRow) row); + } } return contains; } From 533df9ec210bfdecd30a3f588b666f6d2b72121a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 6 Jan 2017 17:18:19 +0800 Subject: [PATCH 0367/2110] Clear handler when close RealmNotifier --- .../java/io/realm/internal/RealmNotifierTests.java | 3 ++- .../io/realm/internal/android/AndroidRealmNotifier.java | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 48b9baee19..078c555aac 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -80,7 +80,7 @@ public void run() { @Test @RunTestInLooperThread public void postAtFrontOfQueue() { - RealmNotifier notifier = new AndroidRealmNotifier(capabilitiesCanDeliver); + final RealmNotifier notifier = new AndroidRealmNotifier(capabilitiesCanDeliver); notifier.post(new Runnable() { @Override public void run() { @@ -91,6 +91,7 @@ public void run() { @Override public void run() { looperThread.testComplete(); + notifier.close(); } }); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java index 9ad9fc5efa..c6ed80f37b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java @@ -30,4 +30,12 @@ public void post(Runnable runnable) { handler.post(runnable); } } + + @Override + public void close() { + super.close(); + if (handler != null) { + handler.removeCallbacksAndMessages(null); + } + } } From f1bcb141244f32008b9c226c255a5a531fb66217 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 6 Jan 2017 14:11:34 +0100 Subject: [PATCH 0368/2110] Fix memory leak (#3993). --- CHANGELOG.md | 6 ++++++ .../realm-library/src/main/cpp/io_realm_internal_Table.cpp | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4b81b0039..85498f2dfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.2.3 + +### Bug fixes + +* Fixed memory leak in `Java_io_realm_internal_Table_nativeSetPrimaryKey` (#3993). + ## 2.2.2 ### Object Server API Changes (In Beta) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 9c697bdca5..c42b2b23ad 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1505,8 +1505,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey( else { // Primary key already exists // We only wish to check for duplicate values if a column isn't already a primary key - Row* row = new Row((*pk_table)[row_index]); - StringData current_primary_key = row->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX); + StringData current_primary_key = pk_table->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index); if (new_primary_key_column_name != current_primary_key) { if (check_valid_primary_key_column(env, table, new_primary_key_column_name)) { pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, new_primary_key_column_name); From db07dd398cebed8b35ab0e01f22f9aca51f5d016 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 6 Jan 2017 14:14:02 +0100 Subject: [PATCH 0369/2110] Revert "Fix memory leak (#3993)." This reverts commit f1bcb141244f32008b9c226c255a5a531fb66217. --- CHANGELOG.md | 6 ------ .../realm-library/src/main/cpp/io_realm_internal_Table.cpp | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85498f2dfd..b4b81b0039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,3 @@ -## 2.2.3 - -### Bug fixes - -* Fixed memory leak in `Java_io_realm_internal_Table_nativeSetPrimaryKey` (#3993). - ## 2.2.2 ### Object Server API Changes (In Beta) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index c42b2b23ad..9c697bdca5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1505,7 +1505,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey( else { // Primary key already exists // We only wish to check for duplicate values if a column isn't already a primary key - StringData current_primary_key = pk_table->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index); + Row* row = new Row((*pk_table)[row_index]); + StringData current_primary_key = row->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX); if (new_primary_key_column_name != current_primary_key) { if (check_valid_primary_key_column(env, table, new_primary_key_column_name)) { pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, new_primary_key_column_name); From 4f1ac5d4797faa95351e417d62739b1b7ab4ff6e Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 6 Jan 2017 17:45:09 +0100 Subject: [PATCH 0370/2110] Fix memory leak (#3993) (#3995) Fix memory leak (#3993) --- CHANGELOG.md | 6 ++++++ .../realm-library/src/main/cpp/io_realm_internal_Table.cpp | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4b81b0039..984d063637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.2.3 + +### Bug fixes + +* Fixed native memory leak setting the value of a primary key (#3993). + ## 2.2.2 ### Object Server API Changes (In Beta) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 9c697bdca5..c42b2b23ad 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1505,8 +1505,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey( else { // Primary key already exists // We only wish to check for duplicate values if a column isn't already a primary key - Row* row = new Row((*pk_table)[row_index]); - StringData current_primary_key = row->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX); + StringData current_primary_key = pk_table->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index); if (new_primary_key_column_name != current_primary_key) { if (check_valid_primary_key_column(env, table, new_primary_key_column_name)) { pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, new_primary_key_column_name); From fd9ced1252b435cd50e13b212fdf6b6c0073de7d Mon Sep 17 00:00:00 2001 From: Realm CI Date: Fri, 6 Jan 2017 18:43:16 +0100 Subject: [PATCH 0371/2110] Fix memory leak (#3993) (#3995) (#3996) Fix memory leak (#3993) --- CHANGELOG.md | 6 ++++++ .../realm-library/src/main/cpp/io_realm_internal_Table.cpp | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e397d0c99e..ffc6532b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ * Add a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`). +## 2.2.3 + +### Bug fixes + +* Fixed native memory leak setting the value of a primary key (#3993). + ## 2.2.2 ### Object Server API Changes (In Beta) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 9c697bdca5..c42b2b23ad 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1505,8 +1505,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey( else { // Primary key already exists // We only wish to check for duplicate values if a column isn't already a primary key - Row* row = new Row((*pk_table)[row_index]); - StringData current_primary_key = row->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX); + StringData current_primary_key = pk_table->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index); if (new_primary_key_column_name != current_primary_key) { if (check_valid_primary_key_column(env, table, new_primary_key_column_name)) { pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, new_primary_key_column_name); From d258dc011ffbd8149381d7296034a0bd022a3308 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 9 Jan 2017 21:11:51 +0800 Subject: [PATCH 0372/2110] canceltransaction posts to reattach collections To maintain the consistency behaviour: once a collection becomes detached because of beginTransaction, it can only be reattached in the next event loop. --- .../io/realm/internal/CollectionTests.java | 36 +++++++++++++------ .../java/io/realm/internal/SharedRealm.java | 32 ++++++++++------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index ce6d6b7a2a..227401d811 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -470,12 +470,11 @@ public void detach_commitTransactionWontReattach() { } @Test - public void reattach_byCancelTransaction() { + public void detach_cancelTransactionWontReattach() { final Collection collection = new Collection(sharedRealm, table.where()); sharedRealm.beginTransaction(); - assertTrue(collection.isDetached()); sharedRealm.cancelTransaction(); - assertFalse(collection.isDetached()); + assertTrue(collection.isDetached()); assertEquals(collection.size(), 4); } @@ -574,23 +573,40 @@ public void run() { public void reattach_looperThread_shouldHappenBeforeAnyOtherLoopEventWithLocalTransactionCanceled() { final SharedRealm sharedRealm = getSharedRealm(); Table table = sharedRealm.getTable("test_table"); - final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); - assertEquals(collection.size(), 4); + final Collection[] collections = new Collection[2]; + collections[0] = new Collection(sharedRealm, table.where()); + looperThread.keepStrongReference.add(collections[0]); + assertEquals(collections[0].size(), 4); looperThread.postRunnable(new Runnable() { @Override public void run() { // The results is switched back to the original Results. - assertFalse(collection.isDetached()); - assertEquals(collection.size(), 4); + assertFalse(collections[0].isDetached()); + assertEquals(collections[0].size(), 4); + assertFalse(collections[1].isDetached()); + assertEquals(collections[1].size(), 4); + sharedRealm.close(); looperThread.testComplete(); } }); sharedRealm.beginTransaction(); // The results is backed by snapshot now. - assertTrue(collection.isDetached()); - assertEquals(collection.size(), 4); + assertTrue(collections[0].isDetached()); + assertEquals(collections[0].size(), 4); + + table.addEmptyRow(); + collections[1] = new Collection(sharedRealm, table.where()); + UncheckedRow row = collections[1].getUncheckedRow(4); + assertTrue(row.isAttached()); + assertEquals(collections[1].size(), 5); sharedRealm.cancelTransaction(); + + // The results is still backed by snapshot. + assertTrue(collections[0].isDetached()); + assertEquals(collections[0].size(), 4); + assertEquals(collections[1].size(), 5); + row = collections[1].getUncheckedRow(4); + assertFalse(row.isAttached()); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 42a01649ba..bcbc82ee87 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -252,21 +252,12 @@ public void beginTransaction() { public void commitTransaction() { nativeCommitTransaction(nativePtr); - if (realmNotifier != null && !collections.isEmpty() && !disableSnapshotPosted) { - disableSnapshotPosted = true; - realmNotifier.postAtFrontOfQueue(new Runnable() { - @Override - public void run() { - disableSnapshotPosted = false; - disableCollectionSnapshot(); - } - }); - } + postToReattachCollections(); } public void cancelTransaction() { nativeCancelTransaction(nativePtr); - disableCollectionSnapshot(); + postToReattachCollections(); } public boolean isInTransaction() { @@ -321,7 +312,6 @@ public boolean isEmpty() { public void refresh() { nativeRefresh(nativePtr); invokeSchemaChangeListenerIfSchemaChanged(); - disableCollectionSnapshot(); } public SharedRealm.VersionID getVersionID() { @@ -428,6 +418,11 @@ private void enableCollectionSnapshot() { } void disableCollectionSnapshot() { + if (isInTransaction()) { + // This should never happen. + throw new IllegalStateException( "Collection cannot be reattached if the Realm is in transaction." + + " Please remember to commit or cancel transaction before finishing the current event loop."); + } for (WeakReference collectionRef : collections) { Collection collection = collectionRef.get(); if (collection == null) { @@ -438,6 +433,19 @@ void disableCollectionSnapshot() { } } + private void postToReattachCollections() { + if (realmNotifier != null && !collections.isEmpty() && !disableSnapshotPosted) { + disableSnapshotPosted = true; + realmNotifier.postAtFrontOfQueue(new Runnable() { + @Override + public void run() { + disableSnapshotPosted = false; + disableCollectionSnapshot(); + } + }); + } + } + private static native void nativeInit(String temporaryDirectoryPath); private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory, boolean cache, boolean disableFormatUpgrade, From 5c0a951efa9e3cdb3dde7448b38f155b97ac3974 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 9 Jan 2017 22:17:06 +0800 Subject: [PATCH 0373/2110] Detect if collection itarator becomes unstable Instead of checking the version of TableView which requires exposing the TableView version from OS, we simply check if the iterator is used across detach/reattach. If reattach happens, the TableView will be synced, the iterators are not stable anymore. --- .../src/main/java/io/realm/RealmResults.java | 27 ++------------- .../java/io/realm/internal/Collection.java | 34 ++++++++++++++++++- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 6921b24bc3..97e95ee965 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -72,9 +72,6 @@ public class RealmResults extends AbstractList implemen Class classSpec; // Return type String className; // Class name used by DynamicRealmObjects - //private static final long TABLE_VIEW_VERSION_NONE = -1; - - //private long currentTableViewVersion = TABLE_VIEW_VERSION_NONE; private final Collection collection; RealmResults(BaseRealm realm, Collection collection, Class clazz) { @@ -614,12 +611,11 @@ public boolean addAll(@SuppressWarnings("NullableProblems") java.util.Collection } // Custom RealmResults iterator. It ensures that we only iterate on a Realm that hasn't changed. - private class RealmResultsIterator implements Iterator { - //long tableViewVersion = 0; + private class RealmResultsIterator extends Collection.Iterator { int pos = -1; RealmResultsIterator() { - //tableViewVersion = currentTableViewVersion; + super(collection); } /** @@ -634,8 +630,7 @@ public boolean hasNext() { */ public E next() { realm.checkIfValid(); - // FIXME: Enable this - //checkRealmIsStable(); + checkRealmIsStable(); pos++; if (pos >= size()) { throw new NoSuchElementException("Cannot access index " + pos + " when size is " + size() + ". Remember to check hasNext() before using next()."); @@ -652,22 +647,6 @@ public E next() { public void remove() { throw new UnsupportedOperationException("remove() is not supported by RealmResults iterators."); } - - protected void checkRealmIsStable() { - // FIXME: Check this! - /* - long version = table.getVersion(); - // Any change within a write transaction will immediately update the table version. This means that we - // cannot depend on the tableVersion heuristic in that case. - // You could argue that in that case it is not really a "ConcurrentModification", but this interpretation - // is still more lax than what the standard Java Collection API gives. - // TODO: Try to come up with a better scheme - if (!realm.isInTransaction() && tableViewVersion > -1 && version != tableViewVersion) { - throw new ConcurrentModificationException("No outside changes to a Realm is allowed while iterating a RealmResults. Don't call Realm.refresh() while iterating."); - } - tableViewVersion = version; - */ - } } // Custom RealmResults list iterator. diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index fee23909e7..5b5c0cc3b8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -16,7 +16,11 @@ package io.realm.internal; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.ConcurrentModificationException; import java.util.Date; +import java.util.List; import io.realm.RealmChangeListener; @@ -37,6 +41,30 @@ public void onChange(T observer) { } } + // Custom Collection iterator. It ensures that we only iterate on a Realm collection that hasn't changed. + // TODO: Consider to replace RealmResultsIterator implementation by this since it could be shared by the RealmList. + public static abstract class Iterator implements java.util.Iterator { + private final WeakReference collectionWeakReference; + public Iterator(Collection collection) { + collectionWeakReference = new WeakReference(collection); + collection.stableIterators.add(new WeakReference(this)); + } + + protected void checkRealmIsStable() { + Collection collection = collectionWeakReference.get(); + if (collection != null) { + for (WeakReference it : collection.stableIterators) { + if (it.get() == this) { + return; + } + } + } + throw new ConcurrentModificationException( + "No outside changes to a Realm is allowed while iterating a RealmResults." + + " Don't call Realm.refresh() while iterating."); + } + } + private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final SharedRealm sharedRealm; @@ -52,6 +80,8 @@ public void onCalled(CollectionObserverPair pair, Object observer) { pair.onChange(observer); } }; + // Maintain a list of stable iterators. Iterator becomes invalid when the reattaching happens. + private final List> stableIterators = new ArrayList>(); // Public for static checking in JNI @SuppressWarnings("WeakerAccess") @@ -223,7 +253,7 @@ public boolean isValid() { @KeepMember @SuppressWarnings("unused") private void notifyChangeListeners(boolean emptyChanges) { - if (emptyChanges && isDetached()) return; + if (isDetached()) return; observerPairs.foreach(onChangeCallback); } @@ -232,6 +262,8 @@ void enableSnapshot() { } void disableSnapshot() { + // Invalidate all current iterators. + stableIterators.clear(); nativeDisableSnapshot(nativePtr); } From 0cf0ea30c8dad2863dc36f49b76e2a32c32bd312 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Mon, 9 Jan 2017 10:50:40 -0800 Subject: [PATCH 0374/2110] Count 'cpuX' files in /sys/devices/system/cpu as a more accurate way (#3997) * Count 'cpuX' files in /sys/devices/system/cpu as a more accurate way of counting processors and setting the max threads for the Executor. (#3810) * Suppress Findbugs warning * Better comments; Default to Runtime.getRuntime().availableProcessors() --- realm/realm-library/build.gradle | 1 + .../async/RealmThreadPoolExecutor.java | 48 +++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index a0810cb77c..9fbea1a4bc 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -126,6 +126,7 @@ repositories { dependencies { objectServerAnnotationProcessor project(':realm-annotations-processor') provided 'io.reactivex:rxjava:1.1.0' + provided 'net.sourceforge.findbugs:annotations:1.3.2' compile "io.realm:realm-annotations:${version}" compile 'com.getkeepsafe.relinker:relinker:1.2.2' objectServerCompile 'com.squareup.okhttp3:okhttp:3.4.1' diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java index 7f251618a2..7d943b06a1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java @@ -16,6 +16,8 @@ package io.realm.internal.async; +import java.io.File; +import java.io.FileFilter; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.Future; @@ -23,8 +25,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; -import io.realm.Realm; +import edu.umd.cs.findbugs.annotations.SuppressWarnings; /** * Custom thread pool settings, instances of this executor can be paused, and resumed, this will also set @@ -32,9 +35,10 @@ * Androids recommendation. */ public class RealmThreadPoolExecutor extends ThreadPoolExecutor { - // reduce context switch by using a number of thread proportionate to the number of cores - // from AOSP https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/AsyncTask.java#182 - private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2 + 1; + private static final String SYS_CPU_DIR = "/sys/devices/system/cpu/"; + + // reduce context switching by using a number of thread proportionate to the number of cores + private static final int CORE_POOL_SIZE = calculateCorePoolSize(); private static final int QUEUE_SIZE = 100; private boolean isPaused; @@ -55,6 +59,42 @@ public static RealmThreadPoolExecutor newSingleThreadExecutor() { return new RealmThreadPoolExecutor(1, 1); } + /** + * Try using the number of files named 'cpuNN' in sysfs to figure out the number of + * processors on this device. `Runtime.getRuntime().availableProcessors()` may return + * a smaller number when the device is sleeping. + * + * @return the number of threads to be allocated for the executor pool + */ + @SuppressWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME") + private static int calculateCorePoolSize() { + int cpus = countFilesInDir(SYS_CPU_DIR, "cpu[0-9]+"); + if (cpus <= 0) { + cpus = Runtime.getRuntime().availableProcessors(); + } + return (cpus <= 0) ? 1 : (cpus * 2) + 1; + } + + /** + * @param dirPath A directory path + * @param pattern A regex + * @return the number of files, in the `dirPath` directory, whose names match `pattern` + */ + private static int countFilesInDir(String dirPath, String pattern) { + final Pattern filePattern = Pattern.compile(pattern); + try { + File[] files = new File(dirPath).listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return filePattern.matcher(file.getName()).matches(); + } + }); + return (files == null) ? 0 : files.length; + } catch (SecurityException ignore) { + } + return 0; + } + private RealmThreadPoolExecutor(int corePoolSize, int maxPoolSize) { super(corePoolSize, maxPoolSize, 0L, TimeUnit.MILLISECONDS, //terminated idle thread From 20b59a718e205ddd58b9385de106ac8758ba43b5 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 14:52:45 +0800 Subject: [PATCH 0375/2110] Fix addChangeListener_returnedObjectOfCopyToRealmOrUpdate The original test won't pass since the object doesn't change after adding the listener. So change it to trigger the listener. This doesn't change the purpose of the test, the target of the test is to check if the listener on the return value of copyToRealmOrUpdate will be called. --- .../src/androidTest/java/io/realm/RealmObjectTests.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 25c728b67a..c1516d30aa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -1737,7 +1737,7 @@ public void addChangeListener_returnedObjectOfCopyToRealmOrUpdate() { AllTypesPrimaryKey allTypesPrimaryKey = new AllTypesPrimaryKey(); allTypesPrimaryKey.setColumnLong(1); - allTypesPrimaryKey.setColumnFloat(42f); + allTypesPrimaryKey.setColumnFloat(0f); allTypesPrimaryKey = realm.copyToRealmOrUpdate(allTypesPrimaryKey); realm.commitTransaction(); @@ -1749,6 +1749,11 @@ public void onChange(AllTypesPrimaryKey element) { looperThread.testComplete(); } }); + + // Change the object to trigger the listener. + realm.beginTransaction(); + allTypesPrimaryKey.setColumnFloat(42f); + realm.commitTransaction(); } // The object should not be added to HandlerController again after the async query loaded. From 8c47078a514f8ba32ab0575dc85c1afaf0a5e442 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 15:07:29 +0800 Subject: [PATCH 0376/2110] Fix addChangeListener_listenerShouldBeCalledIfObjectChangesAfterAsyncReturn Since the HandlerController is removed, the orginal test doesn't make sense anymore. Rewrite it to a more generic test. --- .../java/io/realm/RealmObjectTests.java | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index c1516d30aa..1c40e07c97 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -37,6 +37,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllJavaTypes; @@ -1756,35 +1757,41 @@ public void onChange(AllTypesPrimaryKey element) { realm.commitTransaction(); } - // The object should not be added to HandlerController again after the async query loaded. + // step 1: findFirstAsync + // step 2: async query returns, change the object in the listener + // step 3: listener gets called again @Test @RunTestInLooperThread - public void addChangeListener_checkHandlerRealmObjectsWhenCallingOnAsyncObject() { -/* Realm realm = looperThread.realm; + public void addChangeListener_listenerShouldBeCalledIfObjectChangesAfterAsyncReturn() { + final AtomicInteger listenerCounter = new AtomicInteger(0); + final Realm realm = looperThread.realm; realm.beginTransaction(); realm.createObject(AllTypesPrimaryKey.class, 1); realm.commitTransaction(); - final ConcurrentHashMap, Object> realmObjects = - realm.handlerController.realmObjects; + // Step 1 final AllTypesPrimaryKey allTypesPrimaryKey = realm.where(AllTypesPrimaryKey.class).findFirstAsync(); looperThread.keepStrongReference.add(allTypesPrimaryKey); allTypesPrimaryKey.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypesPrimaryKey element) { - allTypesPrimaryKey.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(AllTypesPrimaryKey element) { - - } - }); - assertEquals(1, realmObjects.size()); - looperThread.testComplete(); + int count = listenerCounter.getAndAdd(1); + if (count == 0) { + // Step 2 + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.where(AllTypesPrimaryKey.class).findFirst().setColumnFloat(42f); + } + }); + } else if (count == 1) { + // Step 3 + assertEquals(allTypesPrimaryKey.getColumnFloat(), 42f, 0); + looperThread.testComplete(); + } else { + fail(); + } } }); - assertEquals(1, realmObjects.size()); - for (Object query : realmObjects.values()) { - assertNotNull(query); - }*/ } } From 06ecfc3c97972a2acc9c4a4f43af4e91c6e9d455 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 16:17:44 +0800 Subject: [PATCH 0377/2110] Fix type based tests The original tests rely on the timing of the global listener and type based listeners which are changed a lot by integration of OS async. So rewrite those tests by removing the dependencies of global listener without changing the testing purpose. PS. : The global listener won't be called if it is added after the local transaction committed. --- .../io/realm/TypeBasedNotificationsTests.java | 182 ++++++------------ 1 file changed, 63 insertions(+), 119 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index db0404ec7e..dc9656c427 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -486,7 +486,7 @@ public void onChange(AllTypesPrimaryKey object) { public void callback_with_relevant_commit_realmobject_sync() { final Realm realm = looperThread.realm; - // Step 1: Trigger global Realm change listener + // Step 1: Create object realm.beginTransaction(); final Dog akamaru = realm.createObject(Dog.class); akamaru.setName("Akamaru"); @@ -501,38 +501,21 @@ public void onChange(Dog object) { typebasedCommitInvocations.incrementAndGet(); assertEquals("Akamaru", dog.getName()); assertEquals(17, dog.getAge()); + looperThread.testComplete(); } }); - realm.addChangeListener(new RealmChangeListener() { + // Step 2: Trigger non-related commit + realm.executeTransactionAsync(new Realm.Transaction() { @Override - public void onChange(Realm object) { - int commits = globalCommitInvocations.incrementAndGet(); - switch (commits) { - case 1: - // Step 2: Trigger non-related commit - realm.beginTransaction(); - realm.commitTransaction(); - break; - - case 2: - // Step 3: Trigger related commit - realm.beginTransaction(); - akamaru.setAge(17); - realm.commitTransaction(); - break; - - case 3: - // Step 5: Complete test - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(1, typebasedCommitInvocations.get()); - looperThread.testComplete(); - } - }); + public void execute(Realm realm) { + } + }); - } + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.where(Dog.class).findFirst().setAge(17); } }); } @@ -543,7 +526,7 @@ public void run() { public void callback_with_relevant_commit_realmobject_async() { final Realm realm = looperThread.realm; - // Step 1: Trigger global Realm change listener + // Step 1: Create object realm.beginTransaction(); final Dog akamaru = realm.createObject(Dog.class); akamaru.setName("Akamaru"); @@ -558,47 +541,33 @@ public void callback_with_relevant_commit_realmobject_async() { public void onChange(Dog object) { switch (typebasedCommitInvocations.incrementAndGet()) { case 1: + // Async query returns. assertEquals("Akamaru", dog.getName()); assertEquals(0, dog.getAge()); - break; - case 2: - // Step 4: Respond to relevant change - assertEquals(17, dog.getAge()); - break; - } - } - }); - - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - int commits = globalCommitInvocations.incrementAndGet(); - switch (commits) { - case 1: // Step 2: Trigger non-related commit - realm.beginTransaction(); - realm.commitTransaction(); - break; + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + } + }); - case 2: // Step 3: Trigger related commit - realm.beginTransaction(); - akamaru.setAge(17); - realm.commitTransaction(); - break; - - case 3: - // Step 5: Complete test - looperThread.postRunnable(new Runnable() { + realm.executeTransactionAsync(new Realm.Transaction() { @Override - public void run() { - assertEquals(3, globalCommitInvocations.get()); - assertEquals(2, typebasedCommitInvocations.get()); - looperThread.testComplete(); + public void execute(Realm realm) { + realm.where(Dog.class).findFirst().setAge(17); } }); + break; + case 2: + // Step 4: Respond to relevant change + assertEquals(17, dog.getAge()); + looperThread.testComplete(); + break; + default: + fail(); } } }); @@ -809,13 +778,15 @@ public void run() { public void callback_with_relevant_commit_realmresults_sync() { final Realm realm = looperThread.realm; - // Step 1: Trigger global Realm change listener + // Step 1: Create object realm.beginTransaction(); final Dog akamaru = realm.createObject(Dog.class); akamaru.setName("Akamaru"); realm.commitTransaction(); final RealmResults dogs = realm.where(Dog.class).findAll(); + // Execute the query. + assertEquals(1, dogs.size()); looperThread.keepStrongReference.add(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override @@ -825,37 +796,22 @@ public void onChange(RealmResults object) { assertEquals(1, dogs.size()); assertEquals("Akamaru", dogs.get(0).getName()); assertEquals(17, dogs.get(0).getAge()); + looperThread.testComplete(); } }); - realm.addChangeListener(new RealmChangeListener() { + // Step 2: Trigger non-related commit. If this triggered the results listener, assertion will happen there. + realm.executeTransactionAsync(new Realm.Transaction() { @Override - public void onChange(Realm object) { - int commits = globalCommitInvocations.incrementAndGet(); - switch (commits) { - case 1: - // Step 2: Trigger non-related commit - realm.beginTransaction(); - realm.commitTransaction(); - break; - - case 2: - // Step 3: Trigger related commit - realm.beginTransaction(); - akamaru.setAge(17); - realm.commitTransaction(); - break; + public void execute(Realm realm) { + } + }); - case 3: - // Step 5: Complete test - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(1, typebasedCommitInvocations.get()); - looperThread.testComplete(); - } - }); - } + // Step 3: Trigger related commit + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.where(Dog.class).findFirst().setAge(17); } }); } @@ -866,14 +822,13 @@ public void run() { public void callback_with_relevant_commit_realmresults_async() { final Realm realm = looperThread.realm; - // Step 1: Trigger global Realm change listener + // Step 1: Create object realm.beginTransaction(); final Dog akamaru = realm.createObject(Dog.class); akamaru.setName("Akamaru"); realm.commitTransaction(); - final RealmResults dogs = realm.where(Dog.class).findAllAsync(); - assertTrue(dogs.load()); + final RealmResults dogs = realm.where(Dog.class).findAll(); looperThread.keepStrongReference.add(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override @@ -881,43 +836,32 @@ public void onChange(RealmResults object) { // Step 4: Respond to relevant change int commits = typebasedCommitInvocations.incrementAndGet(); switch (commits) { - case 2: - assertEquals(17, dogs.get(0).getAge()); case 1: + // Async query returns. assertEquals(1, dogs.size()); assertEquals("Akamaru", dogs.get(0).getName()); + // Step 2: Trigger non-related commit. If this triggered the results listener, + // assertion will happen there. + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + } + }); - } - } - }); - - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - int commits = globalCommitInvocations.incrementAndGet(); - switch (commits) { - case 1: - // Step 2: Trigger non-related commit - realm.beginTransaction(); - realm.commitTransaction(); - break; - - case 2: // Step 3: Trigger related commit - realm.beginTransaction(); - akamaru.setAge(17); - realm.commitTransaction(); - break; - - case 3: - // Step 5: Complete test - looperThread.postRunnable(new Runnable() { + realm.executeTransactionAsync(new Realm.Transaction() { @Override - public void run() { - assertEquals(2, typebasedCommitInvocations.get()); - looperThread.testComplete(); + public void execute(Realm realm) { + realm.where(Dog.class).findFirst().setAge(17); } }); + break; + case 2: + assertEquals(17, dogs.get(0).getAge()); + looperThread.testComplete(); + break; + default: + fail(); } } }); From f37bd67d47d8bc41fc1260f5586b9a980d3406a7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 16:29:19 +0800 Subject: [PATCH 0378/2110] Remove two test cases Those two tests case has little value after OS async integration. The main testing purpose has been covered by callback_with_relevant_commit_realmresults_sync and callback_with_relevant_commit_realmresults_async. --- .../io/realm/TypeBasedNotificationsTests.java | 199 ------------------ 1 file changed, 199 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index dc9656c427..e2e212716e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -573,205 +573,6 @@ public void execute(Realm realm) { }); } - // UC 1 Async RealmObject - @Test - @RunTestInLooperThread - public void callback_with_relevant_commit_from_different_looper_realmobject_async() { - final CountDownLatch looperThread1Done = new CountDownLatch(1); - final CountDownLatch looperThread2Done = new CountDownLatch(1); - final CountDownLatch looperThread3Done = new CountDownLatch(1); - final HandlerThread looperThread1 = new HandlerThread("looperThread1"); - final HandlerThread looperThread2 = new HandlerThread("looperThread2"); - final HandlerThread looperThread3 = new HandlerThread("looperThread3"); - looperThread1.start(); - looperThread2.start(); - looperThread3.start(); - final Handler looperHandler1 = new Handler(looperThread1.getLooper()); - final Handler looperHandler2 = new Handler(looperThread2.getLooper()); - final Handler looperHandler3 = new Handler(looperThread3.getLooper()); - final Realm realm = looperThread.realm; - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - globalCommitInvocations.incrementAndGet(); - } - }); - - final Dog dog = realm.where(Dog.class).findFirstAsync(); - assertTrue(dog.load()); - looperThread.keepStrongReference.add(dog); - dog.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Dog object) { - switch (typebasedCommitInvocations.incrementAndGet()) { - case 1: // triggered by COMPLETED_ASYNC_REALM_OBJECT from calling dog.load() - assertTrue(dog.isLoaded()); - assertFalse(dog.isValid()); - - looperHandler1.post(new Runnable() { - @Override - public void run() { - Realm realmLooperThread1 = Realm.getInstance(realm.getConfiguration()); - realmLooperThread1.beginTransaction(); - realmLooperThread1.commitTransaction(); - realmLooperThread1.close(); - looperThread1Done.countDown(); - } - }); - break; - case 2: // triggered by the irrelevant commit (not affecting Dog table) from LooperThread1 - assertTrue(dog.isLoaded()); - assertFalse(dog.isValid()); - - looperHandler2.post(new Runnable() { - @Override - public void run() { - Realm realmLooperThread2 = Realm.getInstance(realm.getConfiguration()); - // trigger first callback invocation - realmLooperThread2.beginTransaction(); - Dog dog = realmLooperThread2.createObject(Dog.class); - dog.setName("Akamaru"); - realmLooperThread2.commitTransaction(); - realmLooperThread2.close(); - looperThread2Done.countDown(); - } - }); - break; - - case 3: // triggered by relevant commit from LooperThread2 - assertEquals("Akamaru", dog.getName()); - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - // trigger second callback invocation - looperHandler3.post(new Runnable() { - @Override - public void run() { - Realm realmLooperThread3 = Realm.getInstance(realm.getConfiguration()); - realmLooperThread3.beginTransaction(); - realmLooperThread3.where(Dog.class).findFirst().setAge(17); - realmLooperThread3.commitTransaction(); - realmLooperThread3.close(); - looperThread3Done.countDown(); - } - }); - } - }); - break; - case 4: - assertEquals("Akamaru", dog.getName()); - assertEquals(17, dog.getAge()); - // posting as an event will give the handler a chance - // to deliver the notification for globalCommitInvocations - // otherwise, test will exit before the callback get a chance to be invoked - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(3, globalCommitInvocations.get()); - assertEquals(4, typebasedCommitInvocations.get()); - looperThread1.quit(); - looperThread2.quit(); - looperThread3.quit(); - TestHelper.awaitOrFail(looperThread1Done); - TestHelper.awaitOrFail(looperThread2Done); - TestHelper.awaitOrFail(looperThread3Done); - looperThread.testComplete(); - } - }); - break; - } - } - }); - - } - - // UC 1 Async RealmObject - @Test - @RunTestInLooperThread - public void callback_with_relevant_commit_from_different_non_looper_realmobject_async() throws Throwable { - final CountDownLatch nonLooperThread3CloseLatch = new CountDownLatch(1); - final Realm realm = looperThread.realm; - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - globalCommitInvocations.incrementAndGet(); - } - }); - - final Dog dog = realm.where(Dog.class).findFirstAsync(); - assertTrue(dog.load()); - looperThread.keepStrongReference.add(dog); - dog.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Dog object) { - switch (typebasedCommitInvocations.incrementAndGet()) { - case 1: // triggered by COMPLETED_ASYNC_REALM_OBJECT - new RealmBackgroundTask(realm.configuration) { - @Override - protected void doInBackground(Realm realm) { - realm.beginTransaction(); - realm.commitTransaction(); - } - }.awaitOrFail(); - break; - - case 2: {// triggered by the irrelevant commit (not affecting Dog table) - assertTrue(dog.isLoaded()); - assertFalse(dog.isValid()); - new RealmBackgroundTask(realm.configuration) { - @Override - protected void doInBackground(Realm realm) { - realm.beginTransaction(); - realm.createObject(Dog.class).setName("Akamaru"); - realm.commitTransaction(); - - } - }.awaitOrFail(); - break; - } - case 3: { - assertEquals("Akamaru", dog.getName()); - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - // trigger second callback invocation - new Thread() { - @Override - public void run() { - Realm realmNonLooperThread3 = Realm.getInstance(realm.getConfiguration()); - realmNonLooperThread3.beginTransaction(); - realmNonLooperThread3.where(Dog.class).findFirst().setAge(17); - realmNonLooperThread3.commitTransaction(); - realmNonLooperThread3.close(); - nonLooperThread3CloseLatch.countDown(); - } - }.start(); - } - }); - break; - } - case 4: { - assertEquals("Akamaru", dog.getName()); - assertEquals(17, dog.getAge()); - // posting as an event will give the handler a chance - // to deliver the notification for globalCommitInvocations - // otherwise, test will exit before the callback get a chance to be invoked - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(3, globalCommitInvocations.get()); - assertEquals(4, typebasedCommitInvocations.get()); - TestHelper.awaitOrFail(nonLooperThread3CloseLatch); - looperThread.testComplete(); - } - }); - break; - } - } - } - }); - } - // UC 1 Sync RealmResults @Test @RunTestInLooperThread From 1e2fba0e01ffc6bf2c89a33de7e81dd8313d1014 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 18:13:28 +0800 Subject: [PATCH 0379/2110] Use RealmNotifier for RealmObject listener Ideally we should use the object notifications from object store, but since it is still in progress and the KVO notifications is not generic enough for us to use (there are some logic issues), we simply use the RealmNotifier to trigger the RealmObject listeners. There will be false positive notifications with this implementation since we are only checking the Row's table version. But this problem exists even before this commit. --- .../realm/internal/ObserverPairListTests.java | 21 ++++++++ .../io/realm/internal/RealmNotifierTests.java | 34 +++++++++++++ .../src/main/java/io/realm/BaseRealm.java | 2 +- .../src/main/java/io/realm/ProxyState.java | 50 ++++++++++++------- .../src/main/java/io/realm/RealmObject.java | 6 +-- .../io/realm/internal/ObserverPairList.java | 11 ++++ .../java/io/realm/internal/RealmNotifier.java | 8 ++- 7 files changed, 110 insertions(+), 22 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java index 5345ec8973..8862894219 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java @@ -118,6 +118,27 @@ public void remove() { assertEquals(0, observerPairs.size()); } + @Test + public void removeByObserver() { + TestObserverPair pair = new TestObserverPair(ONE, testListener); + observerPairs.add(pair); + pair = new TestObserverPair(ONE, new TestListener()); + observerPairs.add(pair); + assertEquals(2, observerPairs.size()); + + // An different observer + //noinspection UnnecessaryBoxing + pair = new TestObserverPair(TWO, testListener); + observerPairs.add(pair); + assertEquals(3, observerPairs.size()); + + observerPairs.removeByObserver(ONE); + assertEquals(1, observerPairs.size()); + + observerPairs.removeByObserver(TWO); + assertEquals(0, observerPairs.size()); + } + @Test public void clear() { TestObserverPair pair = new TestObserverPair(ONE, new TestListener()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 078c555aac..910a4f520b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -135,4 +135,38 @@ public void run() { } }).start(); } + + @Test + @RunTestInLooperThread + public void removeChangeListeners() { + SharedRealm sharedRealm = getSharedRealm(); + Integer dummyObserver = 1; + looperThread.keepStrongReference.add(dummyObserver); + sharedRealm.realmNotifier.addChangeListener(dummyObserver, new RealmChangeListener() { + @Override + public void onChange(Integer dummy) { + fail(); + } + }); + sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { + @Override + public void onChange(SharedRealm sharedRealm) { + sharedRealm.close(); + looperThread.testComplete(); + } + }); + + // This should only remove the listeners related with dummyObserver + sharedRealm.realmNotifier.removeChangeListeners(dummyObserver); + + new Thread(new Runnable() { + @Override + public void run() { + SharedRealm sharedRealm = getSharedRealm(); + sharedRealm.beginTransaction(); + sharedRealm.commitTransaction(); + sharedRealm.close(); + } + }).start(); + } } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 7340cf5343..c7897bf3e3 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -179,7 +179,7 @@ public void removeChangeListener(RealmChangeListener li public void removeAllChangeListeners() { checkIfValid(); sharedRealm.capabilities.checkCanDeliverNotification("removeListener cannot be called on current thread."); - sharedRealm.realmNotifier.removeAllChangeListeners(); + sharedRealm.realmNotifier.removeChangeListeners(this); } /** diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index d024fd00f1..d0a80069d0 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -21,7 +21,6 @@ import io.realm.internal.PendingRow; import io.realm.internal.Row; -import io.realm.internal.RowNotifier; import io.realm.internal.UncheckedRow; /** @@ -59,7 +58,7 @@ public ProxyState(E model) { public Row getRow$realm() { if (row instanceof PendingRow) { row = ((PendingRow) row).executeQuery(); - registerToRowNotifier(); + registerToRealmNotifier(); } return row; } @@ -84,10 +83,6 @@ public ProxyState(E model) { this.excludeFields = excludeFields; } - public List> getListeners$realm() { - return listeners; - } - /** * Notifies all registered listeners. */ @@ -108,7 +103,21 @@ public void addChangeListener(RealmChangeListener listener) { } // this might be called after query returns. So it is still necessary to register. if (row instanceof UncheckedRow) { - registerToRowNotifier(); + registerToRealmNotifier(); + } + } + + public void removeChangeListener(RealmChangeListener listener) { + listeners.remove(listener); + if (listeners.isEmpty() && row instanceof UncheckedRow) { + realm.sharedRealm.realmNotifier.removeChangeListeners(this); + } + } + + public void removeAllChangeListeners() { + listeners.clear(); + if (row instanceof UncheckedRow) { + realm.sharedRealm.realmNotifier.removeChangeListeners(this); } } @@ -128,26 +137,33 @@ public void setConstructionFinished() { excludeFields = null; } - private void registerToRowNotifier() { + private void registerToRealmNotifier() { if (realm.sharedRealm == null || realm.sharedRealm.isClosed()) { return; } - RowNotifier rowNotifier = realm.sharedRealm.rowNotifier; - if (row.isAttached()) { - rowNotifier.registerListener((UncheckedRow) row, this, new RealmChangeListener>() { - @Override - public void onChange(ProxyState proxyState) { - proxyState.notifyChangeListeners(); + realm.sharedRealm.realmNotifier.addChangeListener(this, new RealmChangeListener>() { + @Override + public void onChange(ProxyState element) { + long tableVersion = -1; + if (row.isAttached()) { + // If the Row gets detached, table version will be -1 and it is different from current value. + tableVersion = row.getTable().getVersion(); } - }); - } + if (currentTableVersion != tableVersion) { + currentTableVersion = tableVersion; + notifyChangeListeners(); + } + } + }); } @Override public void onQueryFinished(Row row) { this.row = row; + // getTable should return a non-null table since the row should always be valid here. + currentTableVersion = row.getTable().getVersion(); notifyChangeListeners(); - registerToRowNotifier(); + registerToRealmNotifier(); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 2923c8a09c..3cfe6d328c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -313,8 +313,8 @@ public static void removeChangeListener(E object, RealmCh BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); - // FIXME: Below doesn't seem to be correct? - proxy.realmGet$proxyState().getListeners$realm().remove(listener); + //noinspection unchecked + proxy.realmGet$proxyState().removeChangeListener(listener); } else { throw new IllegalArgumentException("Cannot remove listener from this unmanaged RealmObject (created outside of Realm)"); } @@ -339,7 +339,7 @@ public static void removeChangeListeners(E object) { BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); - proxy.realmGet$proxyState().getListeners$realm().clear(); + proxy.realmGet$proxyState().removeAllChangeListeners(); } else { throw new IllegalArgumentException("Cannot remove listeners from this unmanaged RealmObject (created outside of Realm)"); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java index b457024cd5..3e5548ed0a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java @@ -122,6 +122,17 @@ public void remove(T pair) { pairs.remove(pair); } + public void removeByObserver(Object observer) { + for (T pair : pairs) { + Object object = pair.observerRef.get(); + if (object == null) { + pairs.remove(pair); + } else if (object == observer) { + pairs.remove(pair); + } + } + } + public int size() { return pairs.size(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 93c9d5734c..59a26c5bd9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -117,7 +117,13 @@ public void removeChangeListener(E observer, RealmChangeListener realmCha realmObserverPairs.remove(observerPair); } - public void removeAllChangeListeners() { + public void removeChangeListeners(E observer) { + realmObserverPairs.removeByObserver(observer); + } + + // Since RealmObject is using this notifier as well, use removeChangeListeners to remove all listeners by the given + // observer. + private void removeAllChangeListeners() { realmObserverPairs.clear(); } From 99fd787dbcf8cb1103e3e7b20cad742b0d9f4a67 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 18:32:53 +0800 Subject: [PATCH 0380/2110] Remove RowNotifier It is not needed for this PR. It can be added back when we integrate the OS object notification. --- .../io/realm/internal/RowNotifierTests.java | 159 ------------------ .../cpp/io_realm_internal_SharedRealm.cpp | 5 +- .../src/main/cpp/java_binding_context.cpp | 62 +------ .../src/main/cpp/java_binding_context.hpp | 13 +- .../java/io/realm/internal/RowNotifier.java | 134 --------------- .../java/io/realm/internal/SharedRealm.java | 11 +- 6 files changed, 12 insertions(+), 372 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java deleted file mode 100644 index 61be79de20..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RowNotifierTests.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -import java.util.concurrent.CountDownLatch; - -import io.realm.RealmChangeListener; -import io.realm.RealmConfiguration; -import io.realm.RealmFieldType; -import io.realm.TestHelper; -import io.realm.rule.RunInLooperThread; -import io.realm.rule.RunTestInLooperThread; -import io.realm.rule.TestRealmConfigurationFactory; - -import static junit.framework.Assert.assertEquals; - -@RunWith(AndroidJUnit4.class) -public class RowNotifierTests { - @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - @Rule - public final ExpectedException thrown = ExpectedException.none(); - @Rule - public final RunInLooperThread looperThread = new RunInLooperThread(); - - private RealmConfiguration config; - private SharedRealm sharedRealm; - private Table table; - private final static String TABLE_NAME = "test_table"; - private final static long STRING_COLUMN_INDEX = 0; - - @Before - public void setUp() { - config = configFactory.createConfiguration(); - sharedRealm = getSharedRealm(); - populateData(); - } - - @After - public void tearDown() { - sharedRealm.close(); - } - - private SharedRealm getSharedRealm() { - return SharedRealm.getInstance(config, null, true); - } - - private void populateData() { - sharedRealm.beginTransaction(); - table = sharedRealm.getTable(TABLE_NAME); - // Specify the column types and names - assertEquals(STRING_COLUMN_INDEX, table.addColumn(RealmFieldType.STRING, "string")); - table.addEmptyRow(); - sharedRealm.commitTransaction(); - } - - private void changeRowAsync() { - final CountDownLatch latch = new CountDownLatch(1); - new Thread(new Runnable() { - @Override - public void run() { - SharedRealm sharedRealm = getSharedRealm(); - changeRow(sharedRealm); - sharedRealm.close(); - latch.countDown(); - } - }).start(); - TestHelper.awaitOrFail(latch); - } - - private void changeRow(SharedRealm sharedRealm) { - sharedRealm.beginTransaction(); - table = sharedRealm.getTable(TABLE_NAME); - UncheckedRow row = table.getUncheckedRow(0); - row.setString(STRING_COLUMN_INDEX, "changed"); - sharedRealm.commitTransaction(); - } - - @Test - @RunTestInLooperThread - public void listener_triggeredByRemoteCommit() { - SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable(TABLE_NAME); - UncheckedRow row = table.getUncheckedRow(0); - looperThread.keepStrongReference.add(row); - sharedRealm.rowNotifier.registerListener(row, row, new RealmChangeListener() { - @Override - public void onChange(UncheckedRow row) { - assertEquals("changed", row.getString(STRING_COLUMN_INDEX)); - looperThread.testComplete(); - } - }); - - changeRowAsync(); - } - - @Test - @RunTestInLooperThread - public void listener_triggeredByLocalCommit() { - SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable(TABLE_NAME); - UncheckedRow row = table.getUncheckedRow(0); - looperThread.keepStrongReference.add(row); - sharedRealm.rowNotifier.registerListener(row, row, new RealmChangeListener() { - @Override - public void onChange(UncheckedRow row) { - String testString = row.getString(STRING_COLUMN_INDEX); - //assertEquals("changed", row.getString(STRING_COLUMN_INDEX)); - //looperThread.testComplete(); - } - }); - - changeRow(sharedRealm); - } - - @Test - @RunTestInLooperThread - public void listener_triggeredByLocalTransactionBegin() { - SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable(TABLE_NAME); - - changeRow(sharedRealm); - - UncheckedRow row = table.getUncheckedRow(0); - looperThread.keepStrongReference.add(row); - sharedRealm.rowNotifier.registerListener(row, row, new RealmChangeListener() { - @Override - public void onChange(UncheckedRow row) { - assertEquals("changed", row.getString(STRING_COLUMN_INDEX)); - looperThread.testComplete(); - } - }); - - sharedRealm.beginTransaction(); - } -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 991a1b14e4..f7d34071de 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -93,15 +93,14 @@ Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv*, jclass, jlong conf } JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr, jobject realm_notifier, - jobject row_notifier) +Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr, jobject realm_notifier) { TR_ENTER_PTR(config_ptr) auto config = reinterpret_cast(config_ptr); try { auto shared_realm = Realm::get_shared_realm(*config); - shared_realm->m_binding_context = JavaBindingContext::create(env, realm_notifier, row_notifier); + shared_realm->m_binding_context = JavaBindingContext::create(env, realm_notifier); // FIXME: Disabled for the collection notifications. There might be some places still need it. // advance_read needs to be handled by Java because of async query. //shared_realm->set_auto_refresh(false); diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index b68db66bfe..883745479f 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -23,41 +23,9 @@ using namespace realm; using namespace realm::_impl; using namespace realm::jni_util; -std::vector JavaBindingContext::get_observed_rows() -{ - std::vector state_list; - - if (m_row_notifier) { - m_row_notifier.call_with_local_ref([&] (auto env, auto row_notifier) { - static JavaMethod get_observers_method(env, row_notifier, - "getObservers", - "()[Lio/realm/internal/RowNotifier$RowObserverPair;"); - static JavaMethod get_observed_row_ptrs_method(env, row_notifier, - "getObservedRowPtrs", - "([Lio/realm/internal/RowNotifier$RowObserverPair;)[J"); - - jobjectArray observers = static_cast( - env->CallObjectMethod(row_notifier, get_observers_method)); - jlongArray row_ptr_jarray = static_cast( - env->CallObjectMethod(row_notifier, get_observed_row_ptrs_method, observers)); - JniLongArray row_ptrs(env, row_ptr_jarray); - - for (jsize i = 0; i < row_ptrs.len(); ++i) { - BindingContext::ObserverState observer_state; - Row* row = reinterpret_cast(row_ptrs[i]); - observer_state.table_ndx = row->get_table()->get_index_in_group(); - observer_state.row_ndx = row->get_index(); - observer_state.info = env->GetObjectArrayElement(observers, i); - state_list.push_back(std::move(observer_state)); - } - }); - } - - return state_list; -} - void JavaBindingContext::before_notify() { + if (JniUtils::get_env()->ExceptionCheck()) return; if (m_java_notifier) { m_java_notifier.call_with_local_ref([&] (JNIEnv* env, jobject notifier_obj) { // Method IDs from RealmNotifier implementation. Cache them as member vars. @@ -69,35 +37,11 @@ void JavaBindingContext::before_notify() } } -void JavaBindingContext::did_change(std::vector const& observer_state_list, - std::vector const& invalidated, +void JavaBindingContext::did_change(std::vector const&, + std::vector const&, bool version_changed) { auto env = JniUtils::get_env(); - static JavaMethod row_observer_pair_on_change_method(env, - "io/realm/internal/RowNotifier$RowObserverPair", - "onChange", "()V"); - - for (auto state : observer_state_list) { - if (env->ExceptionCheck()) return; - - jobject observer = reinterpret_cast(state.info); - //if (!state.changes.empty()) { - env->CallVoidMethod(observer, row_observer_pair_on_change_method); - //} - } - for (auto deleted_row_observer : invalidated) { - if (env->ExceptionCheck()) return; - - jobject observer = reinterpret_cast(deleted_row_observer); - env->CallVoidMethod(observer, row_observer_pair_on_change_method); - } - - if (env->ExceptionCheck()) return; - m_row_notifier.call_with_local_ref(env, [&] (JNIEnv*, jobject row_notifier_obj) { - static JavaMethod clear_row_refs_method(env, row_notifier_obj, "clearRowRefs", "()V"); - env->CallVoidMethod(row_notifier_obj, clear_row_refs_method); - }); if (env->ExceptionCheck()) return; if (version_changed) { diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index 18535fc12f..a459058c36 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -34,34 +34,29 @@ class JavaBindingContext final : public BindingContext { struct ConcreteJavaBindContext { JNIEnv* jni_env; jobject java_notifier; - jobject row_notifier; }; // A weak global ref to the implementation of RealmNotifier // Java should hold a strong ref to it as long as the SharedRealm lives jni_util::JavaGlobalWeakRef m_java_notifier; - // A weak global ref to the RowNotifier object. Java should hold a strong ref to it. - jni_util::JavaGlobalWeakRef m_row_notifier; public: - virtual ~JavaBindingContext() {}; - virtual std::vector get_observed_rows(); + virtual ~JavaBindingContext() { }; virtual void before_notify(); virtual void did_change(std::vector const& observers, std::vector const& invalidated, bool version_changed=true); explicit JavaBindingContext(const ConcreteJavaBindContext& concrete_context) - : m_java_notifier(concrete_context.jni_env, concrete_context.java_notifier), - m_row_notifier(concrete_context.jni_env, concrete_context.row_notifier) {} + : m_java_notifier(concrete_context.jni_env, concrete_context.java_notifier) { } JavaBindingContext(const JavaBindingContext&) = delete; JavaBindingContext& operator=(const JavaBindingContext&) = delete; JavaBindingContext(JavaBindingContext&&) = delete; JavaBindingContext& operator=(JavaBindingContext&&) = delete; - static inline std::unique_ptr create(JNIEnv* env, jobject notifier, jobject row_notifier) + static inline std::unique_ptr create(JNIEnv* env, jobject notifier) { - return std::make_unique(ConcreteJavaBindContext{env, notifier, row_notifier}); + return std::make_unique(ConcreteJavaBindContext{env, notifier}); }; }; diff --git a/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java deleted file mode 100644 index f250726710..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/RowNotifier.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -import io.realm.RealmChangeListener; - -/** - * To bridge object store's row notification to java. {@link SharedRealm} is supposed to hold a instance of this class - * and pass it to JavaBindingContext. Row notifications callback will be executed when there are changes on a specific - * row. - */ -@Keep -public class RowNotifier { - @Keep - private static class RowObserverPair extends ObserverPairList.ObserverPair> { - final WeakReference rowRef; - // Keep a strong ref to row when getRowRefs called and set it to null in clearRowRefs. - // This is to avoid the row gets GCed in between. - UncheckedRow row; - public RowObserverPair(UncheckedRow row, T observer, RealmChangeListener listener) { - super(observer, listener); - this.rowRef = new WeakReference(row); - } - - // Called by JNI in JavaBindingContext::did_change(). - @SuppressWarnings("unused") - private void onChange() { - T observer = observerRef.get(); - if (observer != null) { - listener.onChange(observer); - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj instanceof ObserverPairList.ObserverPair) { - RowObserverPair anotherPair = (RowObserverPair) obj; - return listener.equals(anotherPair.listener) && - observerRef.get() == anotherPair.observerRef.get() && - rowRef.get() == anotherPair.rowRef.get(); - } - return false; - } - } - - // We don't take care of the duplicated rows here. The duplicated rows means the same Row object or different - // Row objects point to the same row in the same table. The duplicated rows will all get notifications but there are - // overheads when duplicated rows added since they all need to be processed to compute the differences for the row - // level fine grained notifications in the object store. - //private CopyOnWriteArrayList rowObserverPairs = new CopyOnWriteArrayList(); - private ObserverPairList rowObserverPairs = new ObserverPairList(); - private static final ObserverPairList.Callback toClearRowCallback = - new ObserverPairList.Callback() { - @Override - public void onCalled(RowObserverPair pair, Object observer) { - pair.row = null; - } - }; - - /** - * Register a listener on a row. - * - * @param row row to be observed. - * @param observer the observer which will be passed back in the {@link RealmChangeListener#onChange(Object)}. - * @param listener the listener. - * @param observer class. - */ - public void registerListener(UncheckedRow row, T observer, RealmChangeListener listener) { - RowObserverPair rowObserverPair = new RowObserverPair(row, observer, listener); - rowObserverPairs.add(rowObserverPair); - } - - - // The calling orders in JNI: - // 1. getObservers() to get the array of current ObserverPair. (called in BindingContext::get_observed_rows) - // 2. getObservedRowPtrs() with return value from step 1. To get an array of Row pointers. (called in - // BindingContext::get_observed_rows) - // 3. Every RowObserverPair.onChange() deliver the changes to java. (called in BindingContext::did_change()) - // 4. clearRowRefs() to reset the strong reference we hold in the ObserverPair. (called in - // BindingContext::did_change()) - // Called by JNI - @SuppressWarnings("unused") - private RowObserverPair[] getObservers() { - final List pairList = new ArrayList(rowObserverPairs.size()); - rowObserverPairs.foreach(new ObserverPairList.Callback() { - @Override - public void onCalled(RowObserverPair pair, Object observer) { - // TODO: Anyone knows why do we need to cast it here? - // Keep a strong ref of the row! in case it gets GCed before clearRowRefs! - pair.row = (UncheckedRow) pair.rowRef.get(); - pairList.add(pair); - } - }); - return pairList.toArray(new RowObserverPair[pairList.size()]); - } - - // Called by JNI - @SuppressWarnings("unused") - private long[] getObservedRowPtrs(RowObserverPair[] observerPairs) { - long[] ptrs = new long[observerPairs.length]; - for (int i = 0; i < observerPairs.length; i++) { - ptrs[i] = observerPairs[i].row.getNativePtr(); - } - return ptrs; - } - - // Called by JNI - @SuppressWarnings("unused") - private void clearRowRefs() { - rowObserverPairs.foreach(toClearRowCallback); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index bcbc82ee87..2751cee2ee 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -107,7 +107,6 @@ public byte getNativeValue() { // JNI will only hold a weak global ref to this. public final RealmNotifier realmNotifier; - public final RowNotifier rowNotifier; public final ObjectServerFacade objectServerFacade; public final List> collections = new CopyOnWriteArrayList>(); public final Capabilities capabilities; @@ -179,7 +178,7 @@ public interface SchemaVersionListener { private final SchemaVersionListener schemaChangeListener; private SharedRealm(long nativePtr, RealmConfiguration configuration, Capabilities capabilities, - RealmNotifier notifier, RowNotifier rowNotifier, SchemaVersionListener schemaVersionListener) { + RealmNotifier notifier, SchemaVersionListener schemaVersionListener) { context = new Context(); this.nativePtr = nativePtr; @@ -190,7 +189,6 @@ private SharedRealm(long nativePtr, RealmConfiguration configuration, Capabiliti if (this.realmNotifier != null) { this.realmNotifier.setSharedRealm(this); } - this.rowNotifier = rowNotifier; this.schemaChangeListener = schemaVersionListener; this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion(); objectServerFacade = null; @@ -226,14 +224,12 @@ public static SharedRealm getInstance(RealmConfiguration config, SchemaVersionLi Capabilities capabilities = new AndroidCapabilities(); RealmNotifier realmNotifier = new AndroidRealmNotifier(capabilities); - RowNotifier rowNotifier = new RowNotifier(); try { return new SharedRealm( - nativeGetSharedRealm(nativeConfigPtr, realmNotifier, rowNotifier), + nativeGetSharedRealm(nativeConfigPtr, realmNotifier), config, capabilities, realmNotifier, - rowNotifier, schemaVersionListener); } finally { nativeCloseConfig(nativeConfigPtr); @@ -452,8 +448,7 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte boolean autoChangeNotification, String syncServerURL, String syncUserToken); private static native void nativeCloseConfig(long nativeConfigPtr); - private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier, - RowNotifier rowNotifier); + private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier); private static native void nativeCloseSharedRealm(long nativeSharedRealmPtr); private static native boolean nativeIsClosed(long nativeSharedRealmPtr); private static native void nativeBeginTransaction(long nativeSharedRealmPtr); From 4079726324ead77d730109b2e458c234c66e1feb Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 20:28:03 +0800 Subject: [PATCH 0381/2110] Async transaction to avoid endless recursion --- .../src/androidTest/java/io/realm/NotificationsTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 132b416c1b..ebc4ca96b9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -472,9 +472,12 @@ public void onChange(Realm object) { realm.removeAllChangeListeners(); realm.addChangeListener(this); realm.addChangeListener(listenerA); - realm.beginTransaction(); - realm.commitTransaction(); - + // Async transaction to avoid endless recursion. + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + } + }); } } }; From 39bca0abcebd97599baf3b99797150997144cea0 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 20:36:30 +0800 Subject: [PATCH 0382/2110] Remove callingOrdersOfListeners The listeners calling orders are controlled by the Object Store now. No point to test this anymore. --- .../java/io/realm/NotificationsTest.java | 78 ------------------- 1 file changed, 78 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index ebc4ca96b9..9b642831bc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -880,84 +880,6 @@ public void onChange(Realm element) { }); } - // FIXME check if the SharedRealm Changed in handleAsyncTransactionCompleted and reenable this test. - // We precisely depend on the order of triggering change listeners right now. - // So it should be: - // 1. Synced object listener - // 2. Synced results listener - // 3. Global listener - // Async listeners are not concerned by this test. Since they are triggered by different event and no advance read - // involved. - // If this case fails on your code, think twice before changing the test! - // https://github.com/realm/realm-java/issues/2408 is related to this test! - @Test - @Ignore("Listener on Realm might be trigger more times, ignore for now") - @RunTestInLooperThread - public void callingOrdersOfListeners() { - final Realm realm = looperThread.realm; - final AtomicInteger count = new AtomicInteger(0); - - final RealmChangeListener> syncedResultsListener = - new RealmChangeListener>() { - @Override - public void onChange(RealmResults element) { - // First called - assertEquals(0, count.getAndIncrement()); - } - }; - - final RealmChangeListener syncedObjectListener = new RealmChangeListener() { - @Override - public void onChange(AllTypes element) { - // Second called - assertEquals(1, count.getAndIncrement()); - } - }; - final RealmChangeListener globalListener = new RealmChangeListener() { - @Override - public void onChange(Realm element) { - // third called - assertEquals(2, count.getAndIncrement()); - looperThread.testComplete(); - } - }; - - - realm.beginTransaction(); - final AllTypes allTypes = realm.createObject(AllTypes.class); - realm.commitTransaction(); - - // We need to create one objects first and let the pass the first change event - final RealmChangeListener initListener = new RealmChangeListener() { - @Override - public void onChange(Realm element) { - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - // Clear the change listeners - realm.removeAllChangeListeners(); - - // Now we can start testing - allTypes.addChangeListener(syncedObjectListener); - RealmResults results = realm.where(AllTypes.class).findAll(); - results.addChangeListener(syncedResultsListener); - realm.addChangeListener(globalListener); - - // Now we trigger those listeners - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - AllTypes allTypes = realm.where(AllTypes.class).findFirst(); - assertNotNull(allTypes); - allTypes.setColumnLong(42); - } - }); - } - }); - } - }; - realm.addChangeListener(initListener); - } // See https://github.com/realm/realm-android-adapters/issues/48 // Step 1: Populate the db From 305d395bec5b51828d433bfddb3498efb27378fd Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 20:37:54 +0800 Subject: [PATCH 0383/2110] Remove ignored test cases. Those are ignored since currently adding listeners in non-looper thread is not allowed. --- .../java/io/realm/NotificationsTest.java | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 9b642831bc..4b1092de15 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -46,7 +46,6 @@ import io.realm.entities.AllTypes; import io.realm.entities.Dog; -import io.realm.log.LogLevel; import io.realm.log.RealmLogger; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; @@ -56,7 +55,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1174,47 +1172,4 @@ public void onChange(RealmModel element) { } catch (IllegalStateException ignored) { } } - - - @Test - @Ignore("Listeners for non-looper thread are not allowed for now") - public void globalListener_nonLooperThread_triggeredByWaitForChange() { - realm = Realm.getInstance(realmConfig); - final CountDownLatch latch = new CountDownLatch(1); - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm element) { - latch.countDown(); - } - }); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObject(AllTypes.class); - } - }); - realm.waitForChange(); - TestHelper.awaitOrFail(latch); - } - - @Test - @Ignore("Listeners for non-looper thread are not allowed for now") - public void globalListener_nonLooperThread_triggeredByLocalCommit() { - realm = Realm.getInstance(realmConfig); - final CountDownLatch latch = new CountDownLatch(1); - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm element) { - latch.countDown(); - } - }); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObject(AllTypes.class); - } - }); - TestHelper.awaitOrFail(latch); - } - } From 5a2bf14834f489d5ff233acdc325bbe025f4e7ba Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 21:33:34 +0800 Subject: [PATCH 0384/2110] Fix test cases --- .../io/realm/internal/RealmNotifierTests.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 910a4f520b..f7123d37b2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -61,7 +61,7 @@ public void setUp() throws Exception { public void tearDown() { } - private SharedRealm getSharedRealm() { + private SharedRealm getSharedRealm(RealmConfiguration config) { return SharedRealm.getInstance(config, null, true); } @@ -99,7 +99,7 @@ public void run() { @Test @RunTestInLooperThread public void addChangeListener_byLocalChanges() { - SharedRealm sharedRealm = getSharedRealm(); + SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { @@ -117,18 +117,19 @@ public void onChange(SharedRealm sharedRealm) { @Test @RunTestInLooperThread public void addChangeListener_byRemoteChanges() { - SharedRealm sharedRealm = getSharedRealm(); + SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { + // FIXME: Enable this after https://github.com/realm/realm-object-store/pull/318 fixed + //sharedRealm.close(); looperThread.testComplete(); - sharedRealm.close(); } }); new Thread(new Runnable() { @Override public void run() { - SharedRealm sharedRealm = getSharedRealm(); + SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); sharedRealm.beginTransaction(); sharedRealm.commitTransaction(); sharedRealm.close(); @@ -139,7 +140,7 @@ public void run() { @Test @RunTestInLooperThread public void removeChangeListeners() { - SharedRealm sharedRealm = getSharedRealm(); + SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); Integer dummyObserver = 1; looperThread.keepStrongReference.add(dummyObserver); sharedRealm.realmNotifier.addChangeListener(dummyObserver, new RealmChangeListener() { @@ -151,7 +152,8 @@ public void onChange(Integer dummy) { sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { - sharedRealm.close(); + // FIXME: Enable this after https://github.com/realm/realm-object-store/pull/318 fixed + //sharedRealm.close(); looperThread.testComplete(); } }); @@ -162,7 +164,7 @@ public void onChange(SharedRealm sharedRealm) { new Thread(new Runnable() { @Override public void run() { - SharedRealm sharedRealm = getSharedRealm(); + SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); sharedRealm.beginTransaction(); sharedRealm.commitTransaction(); sharedRealm.close(); From 493a8bd8610802b828dd8f48dd67b24b32f6a3d7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 21:39:30 +0800 Subject: [PATCH 0385/2110] Temporarily disable a test https://github.com/realm/realm-core/pull/2385 --- .../androidTest/java/io/realm/TypeBasedNotificationsTests.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index e2e212716e..d39fbc263d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -26,6 +26,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -1175,6 +1176,8 @@ public void onChange(RealmResults object) { // "invalid" RealmResults. @Test @RunTestInLooperThread + // FIXME: https://github.com/realm/realm-core/pull/2385 + @Ignore("Enable this after core 2.3.1 released!!") public void changeListener_onResultsBuiltOnDeletedLinkView() { final Realm realm = looperThread.realm; realm.beginTransaction(); From f10786bc0904721c934166bc80dbbdf3a591ccf8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 21:41:45 +0800 Subject: [PATCH 0386/2110] Temporarily disable a flaky test --- .../androidTest/java/io/realm/TypeBasedNotificationsTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index d39fbc263d..60a5a04324 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -577,6 +577,8 @@ public void execute(Realm realm) { // UC 1 Sync RealmResults @Test @RunTestInLooperThread + @Ignore("Flaky test because of Object Store always run Results query callbacks even " + + "if the query returned and nothing changes.") public void callback_with_relevant_commit_realmresults_sync() { final Realm realm = looperThread.realm; From 3e41acd4b97efc8b9495b8a8e58c3a86d5afd340 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 21:42:14 +0800 Subject: [PATCH 0387/2110] Update Object Store to 5fcdf186cc --- realm/realm-library/src/main/cpp/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 814beb5a1e..5fcdf186cc 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 814beb5a1e96f0bb72cf78e206b2e710ac79e217 +Subproject commit 5fcdf186cc5ecedacfb2cc63785534a070533dce From 9b431c88cfb1a058ba92c88bb6e5bf46831a9cd6 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 22:30:23 +0800 Subject: [PATCH 0388/2110] Remove unused SharedRealm.nativeRefresh --- .../main/cpp/io_realm_internal_SharedRealm.cpp | 18 +----------------- .../java/io/realm/internal/SharedRealm.java | 1 - 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index f7d34071de..17843218ed 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -219,7 +219,7 @@ Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv *env, jclass, jlong shar } JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeRefresh__J(JNIEnv *env, jclass, jlong shared_realm_ptr) +Java_io_realm_internal_SharedRealm_nativeRefresh(JNIEnv *env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -229,22 +229,6 @@ Java_io_realm_internal_SharedRealm_nativeRefresh__J(JNIEnv *env, jclass, jlong s } CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeRefresh__JJJ(JNIEnv *env, jclass, jlong shared_realm_ptr, jlong version, - jlong index) -{ - TR_ENTER_PTR(shared_realm_ptr) - - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - SharedGroup::VersionID version_id(static_cast(version), - static_cast(index)); - try { - using rf = realm::_impl::RealmFriend; - auto& shared_group = rf::get_shared_group(*shared_realm); - LangBindHelper::advance_read(shared_group, version_id); - } CATCH_STD() -} - JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong shared_realm_ptr) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 2751cee2ee..c96d2bff10 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -461,7 +461,6 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte private static native long nativeReadGroup(long nativeSharedRealmPtr); private static native boolean nativeIsEmpty(long nativeSharedRealmPtr); private static native void nativeRefresh(long nativeSharedRealmPtr); - private static native void nativeRefresh(long nativeSharedRealmPtr, long version, long index); private static native long[] nativeGetVersionID(long nativeSharedRealmPtr); private static native long nativeGetTable(long nativeSharedRealmPtr, String tableName); private static native String nativeGetTableName(long nativeSharedRealmPtr, int index); From ecb06237470af9fe122f6006172fa93a2d4f0dcd Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 10 Jan 2017 22:59:33 +0800 Subject: [PATCH 0389/2110] Fix findbugs issue --- .../main/java/io/realm/internal/ObserverPairList.java | 10 ++++++++++ .../src/main/java/io/realm/internal/PendingRow.java | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java index 3e5548ed0a..b3d3d2d088 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java @@ -64,6 +64,16 @@ public boolean equals(Object obj) { } return false; } + + @Override + public int hashCode() { + T observer = observerRef.get(); + + int result = 17; + result = 31 * result + ((observer != null) ? observer.hashCode() : 0); + result = 31 * result + ((listener != null) ? listener.hashCode() : 0); + return result; + } } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index e936e6b144..9b641e2af1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -49,6 +49,10 @@ public void onChange(PendingRow pendingRow) { return; } + if (pendingCollection == null) { + // Should not happen, but make findbugs happy. + return; + } if (pendingCollection.isValid()) { // PendingRow will always get the first Row of the query since we only support findFirst. UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); From 6d30d58cae82ccafb76bec8048b31fe8ccd4f4cd Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 11 Jan 2017 11:01:44 +0800 Subject: [PATCH 0390/2110] More findbug issues --- .../java/io/realm/internal/FieldDescriptor.java | 4 +++- .../main/java/io/realm/internal/PendingRow.java | 17 +++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java index 56a9f1cfe6..a1d69da7f9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java @@ -15,6 +15,8 @@ */ package io.realm.internal; +import java.util.Arrays; + import io.realm.RealmFieldType; public class FieldDescriptor { @@ -83,7 +85,7 @@ public FieldDescriptor(Table table, String fieldDescription, boolean allowLink, } public long[] getColumnIndices() { - return columnIndices; + return Arrays.copyOf(columnIndices, columnIndices.length); } public RealmFieldType getFieldType() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 9b641e2af1..af4a24ad09 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -30,7 +30,7 @@ public interface FrontEnd { private Collection pendingCollection; private RealmChangeListener listener; - private WeakReference frontEnd; + private WeakReference frontEndRef; private boolean returnCheckedRow; public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, @@ -40,19 +40,16 @@ public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sort listener = new RealmChangeListener() { @Override public void onChange(PendingRow pendingRow) { - if (frontEnd == null) { + if (frontEndRef == null) { throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); } - if (frontEnd.get() == null) { + FrontEnd frontEnd = frontEndRef.get(); + if (frontEnd == null) { // The front end is GCed. clearPendingCollection(); return; } - if (pendingCollection == null) { - // Should not happen, but make findbugs happy. - return; - } if (pendingCollection.isValid()) { // PendingRow will always get the first Row of the query since we only support findFirst. UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); @@ -60,7 +57,7 @@ public void onChange(PendingRow pendingRow) { if (uncheckedRow != null) { Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; // Ask the front end to reset the row and stop async query. - frontEnd.get().onQueryFinished(row); + frontEnd.onQueryFinished(row); clearPendingCollection(); } } else { @@ -75,7 +72,7 @@ public void onChange(PendingRow pendingRow) { // To set the front end of this PendingRow. public void setFrontEnd(FrontEnd frontEnd) { - this.frontEnd = new WeakReference(frontEnd); + this.frontEndRef = new WeakReference(frontEnd); } @Override @@ -233,7 +230,7 @@ public Row executeQuery() { if (pendingCollection == null) { throw new IllegalStateException(QUERY_EXECUTED_MESSAGE); } - if (frontEnd == null) { + if (frontEndRef == null) { throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); } From c4151236bc6bd1df6ced5b16fc56c2cab107114f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 11 Jan 2017 15:53:41 +0800 Subject: [PATCH 0391/2110] Use term detach/reattach for Collection snapshot Also add some comments --- .../main/cpp/io_realm_internal_Collection.cpp | 4 +- .../java/io/realm/internal/Collection.java | 16 +++++--- .../java/io/realm/internal/RealmNotifier.java | 2 +- .../java/io/realm/internal/SharedRealm.java | 40 +++++++++++++------ 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 05cb295e50..92f6ab8fb3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -379,7 +379,7 @@ Java_io_realm_internal_Collection_nativeIndexOfBySourceRowIndex(JNIEnv *env, jcl } JNIEXPORT void JNICALL -Java_io_realm_internal_Collection_nativeEnableSnapshot(JNIEnv *env, jclass, jlong native_ptr) +Java_io_realm_internal_Collection_nativeDetach(JNIEnv *env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -389,7 +389,7 @@ Java_io_realm_internal_Collection_nativeEnableSnapshot(JNIEnv *env, jclass, jlon } JNIEXPORT void JNICALL -Java_io_realm_internal_Collection_nativeDisableSnapshot(JNIEnv *env, jclass, jlong native_ptr) +Java_io_realm_internal_Collection_nativeReattach(JNIEnv *env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 5b5c0cc3b8..f40d61ea28 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -257,16 +257,20 @@ private void notifyChangeListeners(boolean emptyChanges) { observerPairs.foreach(onChangeCallback); } - void enableSnapshot() { - nativeEnableSnapshot(nativePtr); + // Turns this collection to be backed by a snapshot results. + // A snapshot results will never be auto-updated. + void detach() { + nativeDetach(nativePtr); } - void disableSnapshot() { + // Turns this collection to be backed by the original results to enable the auto-updating again. + void reattach() { // Invalidate all current iterators. stableIterators.clear(); - nativeDisableSnapshot(nativePtr); + nativeReattach(nativePtr); } + // Return true if this is backed by a snapshot results. boolean isDetached() { return nativeIsDetached(nativePtr); } @@ -294,8 +298,8 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native long nativeWhere(long nativePtr); private static native long nativeIndexOf(long nativePtr, long rowNativePtr); private static native long nativeIndexOfBySourceRowIndex(long nativePtr, long sourceRowIndex); - private static native void nativeEnableSnapshot(long nativePtr); - private static native void nativeDisableSnapshot(long nativePtr); + private static native void nativeDetach(long nativePtr); + private static native void nativeReattach(long nativePtr); private static native boolean nativeIsDetached(long nativePtr); private static native boolean nativeIsValid(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 59a26c5bd9..f0765657aa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -92,7 +92,7 @@ void didChange() { // Package protected to avoid finding class by name in JNI. void changesAvailable() { // For the stable iteration. - sharedRealm.disableCollectionSnapshot(); + sharedRealm.reattachCollections(); } void setSharedRealm(SharedRealm sharedRealm) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index c96d2bff10..7442548fc5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -112,7 +112,7 @@ public byte getNativeValue() { public final Capabilities capabilities; // To prevent overflow the message queue. - public boolean disableSnapshotPosted = false; + public boolean reattachCollectionsPosted = false; public static class VersionID implements Comparable { public final long version; @@ -177,8 +177,11 @@ public interface SchemaVersionListener { private long lastSchemaVersion; private final SchemaVersionListener schemaChangeListener; - private SharedRealm(long nativePtr, RealmConfiguration configuration, Capabilities capabilities, - RealmNotifier notifier, SchemaVersionListener schemaVersionListener) { + private SharedRealm(long nativePtr, + RealmConfiguration configuration, + Capabilities capabilities, + RealmNotifier notifier, + SchemaVersionListener schemaVersionListener) { context = new Context(); this.nativePtr = nativePtr; @@ -241,7 +244,7 @@ long getNativePtr() { } public void beginTransaction() { - enableCollectionSnapshot(); + detachCollections(); nativeBeginTransaction(nativePtr); invokeSchemaChangeListenerIfSchemaChanged(); } @@ -395,25 +398,35 @@ public void invokeSchemaChangeListenerIfSchemaChanged() { } } - // Should only be called by Collection's constructor + // addCollection(), detachCollections(), reattachCollections() and postToReattachCollections() are used to make + // RealmResults stable iterators work. When a Collection is detached from a living OS Results, it won't receive + // notifications and its elements won't be changed. + // See https://github.com/realm/realm-java/issues/3883 for more information. + // Should only be called by Collection's constructor. void addCollection(Collection collection) { if (realmNotifier != null) { collections.add(new WeakReference(collection)); } } - private void enableCollectionSnapshot() { + // The detaching should happen before transaction begins. + private void detachCollections() { for (WeakReference collectionRef : collections) { Collection collection = collectionRef.get(); if (collection == null) { collections.remove(collectionRef); } else { - collection.enableSnapshot(); + collection.detach(); } } } - void disableCollectionSnapshot() { + // Ideally the reattaching should happen at the very end of the event loop, but it is impossible for most event + // framework. We need to ensure: + // 1) It happens before any other coming events get handled (eg: UI redraw event). + // 2) It happens before Object Store async callbacks since the Object Store event_loop_signal might use a different + // event queue. This is guaranteed by call this function in the binding_context::before_notify callback. + void reattachCollections() { if (isInTransaction()) { // This should never happen. throw new IllegalStateException( "Collection cannot be reattached if the Realm is in transaction." + @@ -424,19 +437,20 @@ void disableCollectionSnapshot() { if (collection == null) { collections.remove(collectionRef); } else { - collection.disableSnapshot(); + collection.reattach(); } } } + // To handle the point 1) in the reattachCollections comments. private void postToReattachCollections() { - if (realmNotifier != null && !collections.isEmpty() && !disableSnapshotPosted) { - disableSnapshotPosted = true; + if (realmNotifier != null && !collections.isEmpty() && !reattachCollectionsPosted) { + reattachCollectionsPosted = true; realmNotifier.postAtFrontOfQueue(new Runnable() { @Override public void run() { - disableSnapshotPosted = false; - disableCollectionSnapshot(); + reattachCollectionsPosted = false; + reattachCollections(); } }); } From 2ecbbe57b59af02567f29baf70443fd542ae2a9b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 11 Jan 2017 16:06:03 +0800 Subject: [PATCH 0392/2110] Bring ObjectServer notifyCommit back It can be removed in another PR. It is not related with OS Results integration. --- realm/realm-library/src/main/java/io/realm/BaseRealm.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index c7897bf3e3..18eb3bc8fd 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -30,6 +30,7 @@ import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.CheckedRow; import io.realm.internal.InvalidRow; +import io.realm.internal.ObjectServerFacade; import io.realm.internal.RealmObjectProxy; import io.realm.internal.SharedRealm; import io.realm.internal.ColumnInfo; @@ -319,6 +320,8 @@ public void beginTransaction() { public void commitTransaction() { checkIfValid(); sharedRealm.commitTransaction(); + ObjectServerFacade.getFacade(configuration.isSyncConfiguration()) + .notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); } /** From 1b3b8ef2071d542d688815a5a062ea0cc3b17e0e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 11 Jan 2017 16:24:20 +0800 Subject: [PATCH 0393/2110] Init some member vars in SharedRealm constructor move initialization of RealmNotifier and Capabilities to SharedRealm constructor to make it less confusing. --- .../io/realm/internal/RealmNotifierTests.java | 4 ++-- .../java/io/realm/internal/RealmNotifier.java | 8 +++---- .../java/io/realm/internal/SharedRealm.java | 23 ++++++------------- .../android/AndroidRealmNotifier.java | 4 +++- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index f7123d37b2..88c29e424f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -68,7 +68,7 @@ private SharedRealm getSharedRealm(RealmConfiguration config) { @Test @RunTestInLooperThread public void post() { - RealmNotifier notifier = new AndroidRealmNotifier(capabilitiesCanDeliver); + RealmNotifier notifier = new AndroidRealmNotifier(null, capabilitiesCanDeliver); notifier.post(new Runnable() { @Override public void run() { @@ -80,7 +80,7 @@ public void run() { @Test @RunTestInLooperThread public void postAtFrontOfQueue() { - final RealmNotifier notifier = new AndroidRealmNotifier(capabilitiesCanDeliver); + final RealmNotifier notifier = new AndroidRealmNotifier(null, capabilitiesCanDeliver); notifier.post(new Runnable() { @Override public void run() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index f0765657aa..b725025993 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -53,6 +53,10 @@ public void onCalled(RealmObserverPair pair, Object observer) { } }; + protected RealmNotifier(SharedRealm sharedRealm) { + this.sharedRealm = sharedRealm; + } + // TODO: The only reason we have this is that async transactions is not supported by OS yet. And OS is using ALopper // which will be using a different message queue from which java is using to deliver remote Realm changes message. // We need a way to deliver the async transaction onSuccess callback to the caller thread after the caller Realm @@ -95,10 +99,6 @@ void changesAvailable() { sharedRealm.reattachCollections(); } - void setSharedRealm(SharedRealm sharedRealm) { - this.sharedRealm = sharedRealm; - } - /** * Called when close SharedRealm to clean up any event left in to queue. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 7442548fc5..31b2e414b2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -177,21 +177,19 @@ public interface SchemaVersionListener { private long lastSchemaVersion; private final SchemaVersionListener schemaChangeListener; - private SharedRealm(long nativePtr, + private SharedRealm(long nativeConfigPtr, RealmConfiguration configuration, - Capabilities capabilities, - RealmNotifier notifier, SchemaVersionListener schemaVersionListener) { context = new Context(); - this.nativePtr = nativePtr; + Capabilities capabilities = new AndroidCapabilities(); + RealmNotifier realmNotifier = new AndroidRealmNotifier(this, capabilities); + + this.nativePtr = nativeGetSharedRealm(nativeConfigPtr, realmNotifier); this.configuration = configuration; this.capabilities = capabilities; - this.realmNotifier = notifier; - if (this.realmNotifier != null) { - this.realmNotifier.setSharedRealm(this); - } + this.realmNotifier = realmNotifier; this.schemaChangeListener = schemaVersionListener; this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion(); objectServerFacade = null; @@ -225,15 +223,8 @@ public static SharedRealm getInstance(RealmConfiguration config, SchemaVersionLi rosServerUrl, rosUserToken); - Capabilities capabilities = new AndroidCapabilities(); - RealmNotifier realmNotifier = new AndroidRealmNotifier(capabilities); try { - return new SharedRealm( - nativeGetSharedRealm(nativeConfigPtr, realmNotifier), - config, - capabilities, - realmNotifier, - schemaVersionListener); + return new SharedRealm(nativeConfigPtr, config, schemaVersionListener); } finally { nativeCloseConfig(nativeConfigPtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java index c6ed80f37b..0f6ea5f8ad 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java @@ -5,11 +5,13 @@ import io.realm.internal.Capabilities; import io.realm.internal.RealmNotifier; +import io.realm.internal.SharedRealm; public class AndroidRealmNotifier extends RealmNotifier { private Handler handler; - public AndroidRealmNotifier(Capabilities capabilities) { + public AndroidRealmNotifier(SharedRealm sharedRealm, Capabilities capabilities) { + super(sharedRealm); if (capabilities.canDeliverNotification()) { handler = new Handler(Looper.myLooper()); } else { From b582e6ea546b9f7747bbe8400e0392a935758d71 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 11 Jan 2017 12:07:17 +0100 Subject: [PATCH 0394/2110] Add support for SyncCredentials.accessToken. Updated integration tests. --- README.md | 3 ++ .../src/androidTest/AndroidManifest.xml | 17 +++++++ .../java/io/realm/SyncUserTests.java | 19 +++++++ .../java/io/realm/SyncCredentials.java | 25 ++++++++++ .../objectServer/java/io/realm/SyncUser.java | 15 +++++- .../network/AuthenticateResponse.java | 18 +++++++ .../io/realm/internal/objectserver/Token.java | 2 + .../java/io/realm/objectserver/AuthTests.java | 19 +++++++ .../objectserver/ProcessCommitTests.java | 50 ++++++++++++------- .../objectserver/service/SendOneCommit.java | 18 ++++--- .../realm/objectserver/service/SendsALot.java | 18 ++++--- .../realm/objectserver/utils/HttpUtils.java | 25 ++++++++-- .../realm/objectserver/utils/UserFactory.java | 20 ++------ tools/sync_test_server/ros-testing-server.js | 4 +- tools/sync_test_server/start_server.sh | 3 +- 15 files changed, 198 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index ee521543c4..268af4271c 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,9 @@ To run a testing server locally: ./gradlew connectedObjectServerDebugAndroidTest ``` +Note that if using VirtualBox (Genymotion), the network needs to be bridged for the tests to work. +This is done in `VirtualBox > Network`. Set "Adapter 2" to "Bridged Adapter". + These tests may take as much as half an hour to complete. ## Contributing diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index c7741a695f..d9e252dce4 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -21,6 +21,23 @@ android:exported="true" android:process=":remote"> + + + + + + diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index a587ca1709..c31ea68386 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -23,6 +23,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import java.net.MalformedURLException; import java.net.URI; @@ -31,6 +32,7 @@ import java.util.Collection; import io.realm.android.SharedPrefsUserStore; +import io.realm.internal.network.AuthenticationServer; import io.realm.rule.RunInLooperThread; import io.realm.util.SyncTestUtils; @@ -39,6 +41,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; @RunWith(AndroidJUnit4.class) public class SyncUserTests { @@ -151,4 +155,19 @@ public void toString_returnDescription() { assertTrue(str != null && !str.isEmpty()); } + // Test that a login an access token logs the user in directly without touching the network + @Test + public void login_withAccessToken() { + AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenThrow(new AssertionError("Server contacted.")); + AuthenticationServer originalServer = SyncManager.getAuthServer(); + SyncManager.setAuthServerImpl(authServer); + try { + SyncCredentials credentials = SyncCredentials.accessToken("foo", "bar"); + SyncUser user = SyncUser.login(credentials, "http://ros.realm.io/auth"); + assertTrue(user.isValid()); + } finally { + SyncManager.setAuthServerImpl(originalServer); + } + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 827f1265cf..b9dcbc4ab1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -151,6 +151,23 @@ public static SyncCredentials custom(String identityProvider, String userIdentif return new SyncCredentials(identityProvider, userIdentifier, userInfo); } + /** + * Creates credentials from an existing access token. Since an access token is the proof that a user already + * has logged in. Credentials created this way are automatically assumed to have successfully logged in. + * This means that providing this credential to {@link SyncUser#login(SyncCredentials, String)} will always + * succeed, but accessing any Realm after might fail if the token is no longer valid. + * + * @param accessToken Users access token. + * @param identifier User identifier. + * @return a set of credentials that can be used to log into the Object Server using + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} + */ + public static SyncCredentials accessToken(String accessToken, String identifier) { + HashMap userInfo = new HashMap(); + userInfo.put("_token", accessToken); + return new SyncCredentials(IdentityProvider.ACCESS_TOKEN, identifier, userInfo); + } + private SyncCredentials(String identityProvider, String token, Map userInfo) { this.identityProvider = identityProvider; this.userIdentifier = token; @@ -191,6 +208,14 @@ public Map getUserInfo() { * verifying that a given credential is valid. */ public static final class IdentityProvider { + + /** + * The provided identify is an already registered user (represented by the access token). Logging in with this + * type of identity will happen purely on the device without contacting the Realm Object Server. Acquiring + * access to individual Realms will still require talking to the Object Server. + */ + public static final String ACCESS_TOKEN = "_access_token"; + /** * Any credentials verified by the debug identity provider will always be considered valid. * It is only available if configured on the Object Server, and it is disabled by default. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 65130e75c3..379c3ddbf4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; @@ -150,10 +151,20 @@ public static SyncUser login(final SyncCredentials credentials, final String aut throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e); } - final AuthenticationServer server = SyncManager.getAuthServer(); ObjectServerError error; try { - AuthenticateResponse result = server.loginUser(credentials, authUrl); + AuthenticateResponse result; + if (credentials.getIdentityProvider().equals(SyncCredentials.IdentityProvider.ACCESS_TOKEN)) { + // Credentials using ACCESS_TOKEN as IdentityProvider are optimistically assumed to be valid already + // So log them in directly without contacting the authentication server. This is done by mirroring + // the JSON response expected from the server. + String userIdentifier = credentials.getUserIdentifier(); + String token = (String) credentials.getUserInfo().get("_token"); + result = AuthenticateResponse.createValidResponseWithUser(userIdentifier, token); + } else { + final AuthenticationServer server = SyncManager.getAuthServer(); + result = server.loginUser(credentials, authUrl); + } if (result.isValid()) { ObjectServerUser syncUser = new ObjectServerUser(result.getRefreshToken(), authUrl); SyncUser user = new SyncUser(syncUser); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index 75e31a0818..9408a55bcf 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -74,6 +74,24 @@ public static AuthenticateResponse from(ObjectServerError error) { return new AuthenticateResponse(error); } + /** + * Helper method for creating a valid user login response. The user returned will be assumed to have all permissions + * as doesn't expire. + * + * @param identifier User identifier. + * @param token Users refresh token. + * @return Response + */ + public static AuthenticateResponse createValidResponseWithUser(String identifier, String token) { + try { + JSONObject response = new JSONObject(); + response.put(JSON_FIELD_REFRESH_TOKEN, new Token(token, identifier, null, Long.MAX_VALUE, Token.Permission.ALL).toJson()); + return new AuthenticateResponse(response.toString()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + /** * Creates an unsuccessful authentication response. This should only happen in case of network or I/O related * issues. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java index 78f0acfb04..3ae10a206d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java @@ -151,5 +151,7 @@ public enum Permission { DOWNLOAD, REFRESH, MANAGE; + + public static final Permission[] ALL = { UPLOAD, DOWNLOAD, REFRESH, MANAGE }; } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index ccf997a3b8..a38b774f45 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -9,6 +9,8 @@ import org.junit.Test; import org.junit.runner.RunWith; +import io.realm.RealmConfiguration; +import io.realm.SyncConfiguration; import io.realm.SyncCredentials; import io.realm.ErrorCode; import io.realm.ObjectServerError; @@ -66,4 +68,21 @@ public void onError(ObjectServerError error) { } }); } + + @Test + @RunTestInLooperThread + public void login_withAccessToken() { + SyncCredentials credentials = SyncCredentials.accessToken(Constants.USER_TOKEN, "access-token-user"); + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser user) { + SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL).build(); + } + + @Override + public void onError(ObjectServerError error) { + fail("Error thrown:" + error); + } + }); + } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 6e465f53f9..73df932b35 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -24,6 +24,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,16 +33,20 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmResults; import io.realm.SyncConfiguration; +import io.realm.SyncSession; +import io.realm.SyncUser; import io.realm.objectserver.model.ProcessInfo; import io.realm.objectserver.model.TestObject; import io.realm.objectserver.service.SendOneCommit; import io.realm.objectserver.service.SendsALot; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.HttpUtils; +import io.realm.objectserver.utils.UserFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -50,6 +55,7 @@ public class ProcessCommitTests { @BeforeClass public static void setUp () throws Exception { + Realm.init(InstrumentationRegistry.getContext()); HttpUtils.startSyncServer(); } @@ -58,15 +64,10 @@ public static void tearDown () throws Exception { HttpUtils.stopSyncServer(); } - // FIXME: At least need one method in the test class - @Test - public void dummy() { - - } - - // FIXME: Disable for now. - /* + // FIXME: Ignore for now. They do still not work. It might be caused by two processes each creating + // a Sync Client, but it needs to be investigated. @Test + @Ignore public void expectServerCommit() throws Throwable { final Throwable[] exception = new Throwable[1]; final CountDownLatch testFinished = new CountDownLatch(1); @@ -76,18 +77,23 @@ public void expectServerCommit() throws Throwable { public void run() { try { Looper.prepare(); - Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Context targetContext = InstrumentationRegistry.getTargetContext(); - final SyncConfiguration syncConfig = new SyncConfiguration.Builder() + SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL, Constants.USER_TOKEN); + String realmUrl = Constants.SYNC_SERVER_URL; + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) .name(SendOneCommit.class.getSimpleName()) - .serverUrl(Constants.SYNC_SERVER_URL ) - .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL, Constants.USER_TOKEN)) + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail("Sync failure: " + error); + } + }) .build(); Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests final Realm realm = Realm.getInstance(syncConfig); Intent intent = new Intent(targetContext, SendOneCommit.class); targetContext.startService(intent); - final RealmResults all = realm.where(ProcessInfo.class).findAll(); all.addChangeListener(new RealmChangeListener>() { @Override @@ -113,14 +119,15 @@ public void onChange(RealmResults element) { fail("Test timed out "); } } - */ + // FIXME: Ignore for now. They do still not work. It might be caused by two processes each creating + // a Sync Client, but it needs to be investigated. //TODO send string from service and match // replicate integration tests from Cocoa // add gradle task to start the sh script automatically (create pid file, ==> run or kill existing process // check the requirement for the issue again - /* @Test + @Ignore public void expectALot() throws Throwable { final Throwable[] exception = new Throwable[1]; final CountDownLatch testFinished = new CountDownLatch(1); @@ -132,10 +139,16 @@ public void run() { Looper.prepare(); Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(targetContext) + SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL, Constants.USER_TOKEN); + String realmUrl = Constants.SYNC_SERVER_URL_2; + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) .name(SendsALot.class.getSimpleName()) - .serverUrl(Constants.SYNC_SERVER_URL_2) - .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL_2, Constants.USER_TOKEN)) + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail("Sync failure: " + error); + } + }) .build(); Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests final Realm realm = Realm.getInstance(syncConfig); @@ -171,5 +184,4 @@ public void onChange(RealmResults element) { fail("Test timed out "); } } - */ } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java index 26c4f89de6..2620be80dc 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java @@ -20,6 +20,13 @@ import android.content.Intent; import android.os.IBinder; +import io.realm.Realm; +import io.realm.SyncConfiguration; +import io.realm.SyncUser; +import io.realm.objectserver.model.ProcessInfo; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.UserFactory; + /** * Open a sync Realm on a different process, then send one commit. */ @@ -28,12 +35,11 @@ public class SendOneCommit extends Service { @Override public void onCreate() { super.onCreate(); - // FIXME: Disable for now - /* - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(this) + Realm.init(getApplicationContext()); + SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL, Constants.USER_TOKEN); + String realmUrl = Constants.SYNC_SERVER_URL; + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) .name(SendOneCommit.class.getSimpleName()) - .serverUrl(Constants.SYNC_SERVER_URL) - .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL, Constants.USER_TOKEN)) .build(); Realm.deleteRealm(syncConfig); Realm realm = Realm.getInstance(syncConfig); @@ -46,10 +52,8 @@ public void onCreate() { realm.commitTransaction(); realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset - */ } - @Override public IBinder onBind(Intent intent) { return null; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java index 2bcdd9d717..27df640c06 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java @@ -20,6 +20,13 @@ import android.content.Intent; import android.os.IBinder; +import io.realm.Realm; +import io.realm.SyncConfiguration; +import io.realm.SyncUser; +import io.realm.objectserver.model.TestObject; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.UserFactory; + /** * Open a sync Realm on a different process, then send one commit. */ @@ -28,13 +35,11 @@ public class SendsALot extends Service { @Override public void onCreate() { super.onCreate(); - // FIXME: Disable for now. - /* - User user = UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL_2, Constants.USER_TOKEN); - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user) + Realm.init(getApplicationContext()); + SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL, Constants.USER_TOKEN); + String realmUrl = Constants.SYNC_SERVER_URL_2; + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) .name(SendsALot.class.getSimpleName()) - .serverUrl(Constants.SYNC_SERVER_URL_2) - .user() .build(); Realm.deleteRealm(syncConfig); Realm realm = Realm.getInstance(syncConfig); @@ -49,7 +54,6 @@ public void onCreate() { realm.commitTransaction(); realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset - */ } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index 9b15ae11e3..1fa4fb90dc 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -16,12 +16,17 @@ package io.realm.objectserver.utils; +import android.support.test.InstrumentationRegistry; + import java.io.IOException; +import io.realm.Realm; import io.realm.log.RealmLog; import okhttp3.Headers; +import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -29,7 +34,10 @@ * temp directory & start a sync server on it for each unit test. */ public class HttpUtils { - private final static OkHttpClient client = new OkHttpClient(); + private final static OkHttpClient client = new OkHttpClient.Builder() + .retryOnConnectionFailure(true) + .build(); + // adb reverse tcp:8888 tcp:8888 // will forward this query to the host, running the integration test server on 8888 private final static String START_SERVER = "http://127.0.0.1:8888/start"; @@ -60,20 +68,29 @@ public static void startSyncServer() throws Exception { // Checking the server private static boolean waitAuthServerReady() throws InterruptedException { int retryTimes = 50; + + // Dummy invalid request, which will trigger a 400 (BAD REQUEST), but indicate the auth + // server is responsive Request request = new Request.Builder() - .url(Constants.AUTH_SERVER_URL) + .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), "")) + .url(Constants.AUTH_URL) .build(); while (retryTimes != 0) { + Response response = null; try { - Response response = client.newCall(request).execute(); - if (response.isSuccessful()) { + response = client.newCall(request).execute(); + if (response.code() == 400) { return true; } RealmLog.error("Error response from auth server: %s", response.toString()); } catch (IOException e) { RealmLog.error(e); Thread.sleep(100); + } finally { + if (response != null) { + response.close(); + } } retryTimes--; } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index 1ebb2d6ab5..9e6c43da84 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -16,25 +16,13 @@ package io.realm.objectserver.utils; -import java.net.URI; -import java.net.URISyntaxException; - +import io.realm.SyncCredentials; import io.realm.SyncUser; -import io.realm.objectserver.utils.Constants; // Must be in `io.realm.objectserver` to work around package protected methods. public class UserFactory { - // FIXME: Not working right now. - /* - public static User createDefaultUser(String SERVER_URL, String USER_TOKEN) { - try { - User user = User.createLocal(); - - user.addAccessToken(new URI(SERVER_URL), USER_TOKEN); - return user; - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } + public static SyncUser createDefaultUser(String authUrl, String accessToken) { + SyncCredentials credentials = SyncCredentials.accessToken(accessToken, "sync-integration-user"); + return SyncUser.login(credentials, authUrl); } - */ } diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index c182652c39..6ddef03eb9 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -1,6 +1,6 @@ #!/usr/bin/env nodejs -var winston = require('winston');//logging +var winston = require('winston'); //logging const temp = require('temp'); const spawn = require('child_process').spawn; var http = require('http'); @@ -23,7 +23,7 @@ function handleRequest(request, response) { try { //log the request on console winston.log(request.url); - //Disptach + //Dispatch dispatcher.dispatch(request, response); } catch(err) { console.log(err); diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 00930f5eb1..277bb4b2ba 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -9,9 +9,10 @@ TMP_DIR=$(mktemp -d /tmp/sync-test.XXXX) || { echo "Failed to mktemp $TEST_TEMP_ adb reverse tcp:7800 tcp:7800 && \ adb reverse tcp:8080 tcp:8080 && \ +adb reverse tcp:9080 tcp:9080 && \ adb reverse tcp:8888 tcp:8888 || { echo "Failed to reverse adb port." ; exit 1 ; } docker build $DOCKERFILE_DIR --build-arg ROS_DE_VERSION=$ROS_DE_VERSION -t sync-test-server || { echo "Failed to build Docker image." ; exit 1 ; } echo "See log files in $TMP_DIR" -docker run -p 8080:8080 -p 7800:7800 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server +docker run -p 9080:9080 -p 8080:8080 -p 7800:7800 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server From 6adda004e835a5d9ebf4915e1a4f9f095c1c2563 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 11 Jan 2017 12:08:08 +0100 Subject: [PATCH 0395/2110] Revert "Add support for SyncCredentials.accessToken. Updated integration tests." This reverts commit b582e6ea546b9f7747bbe8400e0392a935758d71. --- README.md | 3 -- .../src/androidTest/AndroidManifest.xml | 17 ------- .../java/io/realm/SyncUserTests.java | 19 ------- .../java/io/realm/SyncCredentials.java | 25 ---------- .../objectServer/java/io/realm/SyncUser.java | 15 +----- .../network/AuthenticateResponse.java | 18 ------- .../io/realm/internal/objectserver/Token.java | 2 - .../java/io/realm/objectserver/AuthTests.java | 19 ------- .../objectserver/ProcessCommitTests.java | 50 +++++++------------ .../objectserver/service/SendOneCommit.java | 18 +++---- .../realm/objectserver/service/SendsALot.java | 18 +++---- .../realm/objectserver/utils/HttpUtils.java | 25 ++-------- .../realm/objectserver/utils/UserFactory.java | 20 ++++++-- tools/sync_test_server/ros-testing-server.js | 4 +- tools/sync_test_server/start_server.sh | 3 +- 15 files changed, 58 insertions(+), 198 deletions(-) diff --git a/README.md b/README.md index 268af4271c..ee521543c4 100644 --- a/README.md +++ b/README.md @@ -220,9 +220,6 @@ To run a testing server locally: ./gradlew connectedObjectServerDebugAndroidTest ``` -Note that if using VirtualBox (Genymotion), the network needs to be bridged for the tests to work. -This is done in `VirtualBox > Network`. Set "Adapter 2" to "Bridged Adapter". - These tests may take as much as half an hour to complete. ## Contributing diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index d9e252dce4..c7741a695f 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -21,23 +21,6 @@ android:exported="true" android:process=":remote"> - - - - - - diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index c31ea68386..a587ca1709 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -23,7 +23,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; import java.net.MalformedURLException; import java.net.URI; @@ -32,7 +31,6 @@ import java.util.Collection; import io.realm.android.SharedPrefsUserStore; -import io.realm.internal.network.AuthenticationServer; import io.realm.rule.RunInLooperThread; import io.realm.util.SyncTestUtils; @@ -41,8 +39,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.when; @RunWith(AndroidJUnit4.class) public class SyncUserTests { @@ -155,19 +151,4 @@ public void toString_returnDescription() { assertTrue(str != null && !str.isEmpty()); } - // Test that a login an access token logs the user in directly without touching the network - @Test - public void login_withAccessToken() { - AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); - when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenThrow(new AssertionError("Server contacted.")); - AuthenticationServer originalServer = SyncManager.getAuthServer(); - SyncManager.setAuthServerImpl(authServer); - try { - SyncCredentials credentials = SyncCredentials.accessToken("foo", "bar"); - SyncUser user = SyncUser.login(credentials, "http://ros.realm.io/auth"); - assertTrue(user.isValid()); - } finally { - SyncManager.setAuthServerImpl(originalServer); - } - } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index b9dcbc4ab1..827f1265cf 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -151,23 +151,6 @@ public static SyncCredentials custom(String identityProvider, String userIdentif return new SyncCredentials(identityProvider, userIdentifier, userInfo); } - /** - * Creates credentials from an existing access token. Since an access token is the proof that a user already - * has logged in. Credentials created this way are automatically assumed to have successfully logged in. - * This means that providing this credential to {@link SyncUser#login(SyncCredentials, String)} will always - * succeed, but accessing any Realm after might fail if the token is no longer valid. - * - * @param accessToken Users access token. - * @param identifier User identifier. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} - */ - public static SyncCredentials accessToken(String accessToken, String identifier) { - HashMap userInfo = new HashMap(); - userInfo.put("_token", accessToken); - return new SyncCredentials(IdentityProvider.ACCESS_TOKEN, identifier, userInfo); - } - private SyncCredentials(String identityProvider, String token, Map userInfo) { this.identityProvider = identityProvider; this.userIdentifier = token; @@ -208,14 +191,6 @@ public Map getUserInfo() { * verifying that a given credential is valid. */ public static final class IdentityProvider { - - /** - * The provided identify is an already registered user (represented by the access token). Logging in with this - * type of identity will happen purely on the device without contacting the Realm Object Server. Acquiring - * access to individual Realms will still require talking to the Object Server. - */ - public static final String ACCESS_TOKEN = "_access_token"; - /** * Any credentials verified by the debug identity provider will always be considered valid. * It is only available if configured on the Object Server, and it is disabled by default. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 379c3ddbf4..65130e75c3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -31,7 +31,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; @@ -151,20 +150,10 @@ public static SyncUser login(final SyncCredentials credentials, final String aut throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e); } + final AuthenticationServer server = SyncManager.getAuthServer(); ObjectServerError error; try { - AuthenticateResponse result; - if (credentials.getIdentityProvider().equals(SyncCredentials.IdentityProvider.ACCESS_TOKEN)) { - // Credentials using ACCESS_TOKEN as IdentityProvider are optimistically assumed to be valid already - // So log them in directly without contacting the authentication server. This is done by mirroring - // the JSON response expected from the server. - String userIdentifier = credentials.getUserIdentifier(); - String token = (String) credentials.getUserInfo().get("_token"); - result = AuthenticateResponse.createValidResponseWithUser(userIdentifier, token); - } else { - final AuthenticationServer server = SyncManager.getAuthServer(); - result = server.loginUser(credentials, authUrl); - } + AuthenticateResponse result = server.loginUser(credentials, authUrl); if (result.isValid()) { ObjectServerUser syncUser = new ObjectServerUser(result.getRefreshToken(), authUrl); SyncUser user = new SyncUser(syncUser); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index 9408a55bcf..75e31a0818 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -74,24 +74,6 @@ public static AuthenticateResponse from(ObjectServerError error) { return new AuthenticateResponse(error); } - /** - * Helper method for creating a valid user login response. The user returned will be assumed to have all permissions - * as doesn't expire. - * - * @param identifier User identifier. - * @param token Users refresh token. - * @return Response - */ - public static AuthenticateResponse createValidResponseWithUser(String identifier, String token) { - try { - JSONObject response = new JSONObject(); - response.put(JSON_FIELD_REFRESH_TOKEN, new Token(token, identifier, null, Long.MAX_VALUE, Token.Permission.ALL).toJson()); - return new AuthenticateResponse(response.toString()); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - /** * Creates an unsuccessful authentication response. This should only happen in case of network or I/O related * issues. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java index 3ae10a206d..78f0acfb04 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java @@ -151,7 +151,5 @@ public enum Permission { DOWNLOAD, REFRESH, MANAGE; - - public static final Permission[] ALL = { UPLOAD, DOWNLOAD, REFRESH, MANAGE }; } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index a38b774f45..ccf997a3b8 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -9,8 +9,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import io.realm.RealmConfiguration; -import io.realm.SyncConfiguration; import io.realm.SyncCredentials; import io.realm.ErrorCode; import io.realm.ObjectServerError; @@ -68,21 +66,4 @@ public void onError(ObjectServerError error) { } }); } - - @Test - @RunTestInLooperThread - public void login_withAccessToken() { - SyncCredentials credentials = SyncCredentials.accessToken(Constants.USER_TOKEN, "access-token-user"); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL).build(); - } - - @Override - public void onError(ObjectServerError error) { - fail("Error thrown:" + error); - } - }); - } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 73df932b35..6e465f53f9 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -24,7 +24,6 @@ import org.junit.AfterClass; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,20 +32,16 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmResults; import io.realm.SyncConfiguration; -import io.realm.SyncSession; -import io.realm.SyncUser; import io.realm.objectserver.model.ProcessInfo; import io.realm.objectserver.model.TestObject; import io.realm.objectserver.service.SendOneCommit; import io.realm.objectserver.service.SendsALot; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.HttpUtils; -import io.realm.objectserver.utils.UserFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -55,7 +50,6 @@ public class ProcessCommitTests { @BeforeClass public static void setUp () throws Exception { - Realm.init(InstrumentationRegistry.getContext()); HttpUtils.startSyncServer(); } @@ -64,10 +58,15 @@ public static void tearDown () throws Exception { HttpUtils.stopSyncServer(); } - // FIXME: Ignore for now. They do still not work. It might be caused by two processes each creating - // a Sync Client, but it needs to be investigated. + // FIXME: At least need one method in the test class + @Test + public void dummy() { + + } + + // FIXME: Disable for now. + /* @Test - @Ignore public void expectServerCommit() throws Throwable { final Throwable[] exception = new Throwable[1]; final CountDownLatch testFinished = new CountDownLatch(1); @@ -77,23 +76,18 @@ public void expectServerCommit() throws Throwable { public void run() { try { Looper.prepare(); - Context targetContext = InstrumentationRegistry.getTargetContext(); + Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL, Constants.USER_TOKEN); - String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + final SyncConfiguration syncConfig = new SyncConfiguration.Builder() .name(SendOneCommit.class.getSimpleName()) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail("Sync failure: " + error); - } - }) + .serverUrl(Constants.SYNC_SERVER_URL ) + .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL, Constants.USER_TOKEN)) .build(); Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests final Realm realm = Realm.getInstance(syncConfig); Intent intent = new Intent(targetContext, SendOneCommit.class); targetContext.startService(intent); + final RealmResults all = realm.where(ProcessInfo.class).findAll(); all.addChangeListener(new RealmChangeListener>() { @Override @@ -119,15 +113,14 @@ public void onChange(RealmResults element) { fail("Test timed out "); } } + */ - // FIXME: Ignore for now. They do still not work. It might be caused by two processes each creating - // a Sync Client, but it needs to be investigated. //TODO send string from service and match // replicate integration tests from Cocoa // add gradle task to start the sh script automatically (create pid file, ==> run or kill existing process // check the requirement for the issue again + /* @Test - @Ignore public void expectALot() throws Throwable { final Throwable[] exception = new Throwable[1]; final CountDownLatch testFinished = new CountDownLatch(1); @@ -139,16 +132,10 @@ public void run() { Looper.prepare(); Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL, Constants.USER_TOKEN); - String realmUrl = Constants.SYNC_SERVER_URL_2; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(targetContext) .name(SendsALot.class.getSimpleName()) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail("Sync failure: " + error); - } - }) + .serverUrl(Constants.SYNC_SERVER_URL_2) + .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL_2, Constants.USER_TOKEN)) .build(); Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests final Realm realm = Realm.getInstance(syncConfig); @@ -184,4 +171,5 @@ public void onChange(RealmResults element) { fail("Test timed out "); } } + */ } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java index 2620be80dc..26c4f89de6 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java @@ -20,13 +20,6 @@ import android.content.Intent; import android.os.IBinder; -import io.realm.Realm; -import io.realm.SyncConfiguration; -import io.realm.SyncUser; -import io.realm.objectserver.model.ProcessInfo; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; - /** * Open a sync Realm on a different process, then send one commit. */ @@ -35,11 +28,12 @@ public class SendOneCommit extends Service { @Override public void onCreate() { super.onCreate(); - Realm.init(getApplicationContext()); - SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL, Constants.USER_TOKEN); - String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + // FIXME: Disable for now + /* + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(this) .name(SendOneCommit.class.getSimpleName()) + .serverUrl(Constants.SYNC_SERVER_URL) + .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL, Constants.USER_TOKEN)) .build(); Realm.deleteRealm(syncConfig); Realm realm = Realm.getInstance(syncConfig); @@ -52,8 +46,10 @@ public void onCreate() { realm.commitTransaction(); realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset + */ } + @Override public IBinder onBind(Intent intent) { return null; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java index 27df640c06..2bcdd9d717 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java @@ -20,13 +20,6 @@ import android.content.Intent; import android.os.IBinder; -import io.realm.Realm; -import io.realm.SyncConfiguration; -import io.realm.SyncUser; -import io.realm.objectserver.model.TestObject; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; - /** * Open a sync Realm on a different process, then send one commit. */ @@ -35,11 +28,13 @@ public class SendsALot extends Service { @Override public void onCreate() { super.onCreate(); - Realm.init(getApplicationContext()); - SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL, Constants.USER_TOKEN); - String realmUrl = Constants.SYNC_SERVER_URL_2; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + // FIXME: Disable for now. + /* + User user = UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL_2, Constants.USER_TOKEN); + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user) .name(SendsALot.class.getSimpleName()) + .serverUrl(Constants.SYNC_SERVER_URL_2) + .user() .build(); Realm.deleteRealm(syncConfig); Realm realm = Realm.getInstance(syncConfig); @@ -54,6 +49,7 @@ public void onCreate() { realm.commitTransaction(); realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset + */ } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index 1fa4fb90dc..9b15ae11e3 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -16,17 +16,12 @@ package io.realm.objectserver.utils; -import android.support.test.InstrumentationRegistry; - import java.io.IOException; -import io.realm.Realm; import io.realm.log.RealmLog; import okhttp3.Headers; -import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.RequestBody; import okhttp3.Response; /** @@ -34,10 +29,7 @@ * temp directory & start a sync server on it for each unit test. */ public class HttpUtils { - private final static OkHttpClient client = new OkHttpClient.Builder() - .retryOnConnectionFailure(true) - .build(); - + private final static OkHttpClient client = new OkHttpClient(); // adb reverse tcp:8888 tcp:8888 // will forward this query to the host, running the integration test server on 8888 private final static String START_SERVER = "http://127.0.0.1:8888/start"; @@ -68,29 +60,20 @@ public static void startSyncServer() throws Exception { // Checking the server private static boolean waitAuthServerReady() throws InterruptedException { int retryTimes = 50; - - // Dummy invalid request, which will trigger a 400 (BAD REQUEST), but indicate the auth - // server is responsive Request request = new Request.Builder() - .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), "")) - .url(Constants.AUTH_URL) + .url(Constants.AUTH_SERVER_URL) .build(); while (retryTimes != 0) { - Response response = null; try { - response = client.newCall(request).execute(); - if (response.code() == 400) { + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { return true; } RealmLog.error("Error response from auth server: %s", response.toString()); } catch (IOException e) { RealmLog.error(e); Thread.sleep(100); - } finally { - if (response != null) { - response.close(); - } } retryTimes--; } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index 9e6c43da84..1ebb2d6ab5 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -16,13 +16,25 @@ package io.realm.objectserver.utils; -import io.realm.SyncCredentials; +import java.net.URI; +import java.net.URISyntaxException; + import io.realm.SyncUser; +import io.realm.objectserver.utils.Constants; // Must be in `io.realm.objectserver` to work around package protected methods. public class UserFactory { - public static SyncUser createDefaultUser(String authUrl, String accessToken) { - SyncCredentials credentials = SyncCredentials.accessToken(accessToken, "sync-integration-user"); - return SyncUser.login(credentials, authUrl); + // FIXME: Not working right now. + /* + public static User createDefaultUser(String SERVER_URL, String USER_TOKEN) { + try { + User user = User.createLocal(); + + user.addAccessToken(new URI(SERVER_URL), USER_TOKEN); + return user; + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } } + */ } diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index 6ddef03eb9..c182652c39 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -1,6 +1,6 @@ #!/usr/bin/env nodejs -var winston = require('winston'); //logging +var winston = require('winston');//logging const temp = require('temp'); const spawn = require('child_process').spawn; var http = require('http'); @@ -23,7 +23,7 @@ function handleRequest(request, response) { try { //log the request on console winston.log(request.url); - //Dispatch + //Disptach dispatcher.dispatch(request, response); } catch(err) { console.log(err); diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 277bb4b2ba..00930f5eb1 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -9,10 +9,9 @@ TMP_DIR=$(mktemp -d /tmp/sync-test.XXXX) || { echo "Failed to mktemp $TEST_TEMP_ adb reverse tcp:7800 tcp:7800 && \ adb reverse tcp:8080 tcp:8080 && \ -adb reverse tcp:9080 tcp:9080 && \ adb reverse tcp:8888 tcp:8888 || { echo "Failed to reverse adb port." ; exit 1 ; } docker build $DOCKERFILE_DIR --build-arg ROS_DE_VERSION=$ROS_DE_VERSION -t sync-test-server || { echo "Failed to build Docker image." ; exit 1 ; } echo "See log files in $TMP_DIR" -docker run -p 9080:9080 -p 8080:8080 -p 7800:7800 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server +docker run -p 8080:8080 -p 7800:7800 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server From f40fe42b90eda486cda7b8a5d9a39ad1e01a641e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 11 Jan 2017 22:00:06 +0800 Subject: [PATCH 0396/2110] Add Collection.getMode() --- .../io/realm/internal/CollectionTests.java | 8 ++++ .../main/cpp/io_realm_internal_Collection.cpp | 23 ++++++++++ .../java/io/realm/internal/Collection.java | 45 ++++++++++++++++++- 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 227401d811..1a897347e3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -609,4 +609,12 @@ public void run() { row = collections[1].getUncheckedRow(4); assertFalse(row.isAttached()); } + + @Test + public void getMode() { + Collection collection = new Collection(sharedRealm, table.where()); + assertTrue(Collection.Mode.QUERY == collection.getMode()); + collection.firstUncheckedRow(); // Run the query + assertTrue(Collection.Mode.TABLEVIEW == collection.getMode()); + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 92f6ab8fb3..9c6ca14597 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -469,3 +469,26 @@ Java_io_realm_internal_Collection_nativeIsValid(JNIEnv *env, jclass, jlong nativ } CATCH_STD() return JNI_FALSE; } + +JNIEXPORT jbyte JNICALL +Java_io_realm_internal_Collection_nativeGetMode(JNIEnv *env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + try { + auto wrapper = reinterpret_cast(native_ptr); + switch (wrapper->get_original_results().get_mode()) { + case Results::Mode::Empty: + return io_realm_internal_Collection_MODE_EMPTY; + case Results::Mode::Table: + return io_realm_internal_Collection_MODE_TABLE; + case Results::Mode::Query: + return io_realm_internal_Collection_MODE_QUERY; + case Results::Mode::LinkView: + return io_realm_internal_Collection_MODE_LINKVIEW; + case Results::Mode::TableView: + return io_realm_internal_Collection_MODE_TABLEVIEW; + } + } CATCH_STD() + return -1; // Invalid mode value +} + diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index f40d61ea28..623f50d89b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -45,6 +45,7 @@ public void onChange(T observer) { // TODO: Consider to replace RealmResultsIterator implementation by this since it could be shared by the RealmList. public static abstract class Iterator implements java.util.Iterator { private final WeakReference collectionWeakReference; + public Iterator(Collection collection) { collectionWeakReference = new WeakReference(collection); collection.stableIterators.add(new WeakReference(this)); @@ -91,7 +92,7 @@ public void onCalled(CollectionObserverPair pair, Object observer) { @SuppressWarnings("WeakerAccess") public static final byte AGGREGATE_FUNCTION_AVERAGE = 3; @SuppressWarnings("WeakerAccess") - public static final byte AGGREGATE_FUNCTION_SUM = 4; + public static final byte AGGREGATE_FUNCTION_SUM = 4; public enum Aggregate { MINIMUM(AGGREGATE_FUNCTION_MINIMUM), @@ -110,6 +111,43 @@ public byte getValue() { } } + @SuppressWarnings("WeakerAccess") + public static final byte MODE_EMPTY = 0; + @SuppressWarnings("WeakerAccess") + public static final byte MODE_TABLE = 1; + @SuppressWarnings("WeakerAccess") + public static final byte MODE_QUERY = 2; + @SuppressWarnings("WeakerAccess") + public static final byte MODE_LINKVIEW = 3; + @SuppressWarnings("WeakerAccess") + public static final byte MODE_TABLEVIEW = 4; + + public enum Mode { + EMPTY, // Backed by nothing (for missing tables) + TABLE, // Backed directly by a Table + QUERY, // Backed by a query that has not yet been turned into a TableView + LINKVIEW, // Backed directly by a LinkView + TABLEVIEW; // Backed by a TableView created from a Query + + static Mode getByValue(byte value) { + switch (value) { + case MODE_EMPTY: + return EMPTY; + case MODE_TABLE: + return TABLE; + case MODE_QUERY: + return QUERY; + case MODE_LINKVIEW: + return LINKVIEW; + case MODE_TABLEVIEW: + return TABLEVIEW; + default: + throw new IllegalArgumentException("Invalid value: " + value); + } + } + } + + public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { query.validateQuery(); @@ -257,6 +295,10 @@ private void notifyChangeListeners(boolean emptyChanges) { observerPairs.foreach(onChangeCallback); } + public Mode getMode() { + return Mode.getByValue(nativeGetMode(nativePtr)); + } + // Turns this collection to be backed by a snapshot results. // A snapshot results will never be auto-updated. void detach() { @@ -302,4 +344,5 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native void nativeReattach(long nativePtr); private static native boolean nativeIsDetached(long nativePtr); private static native boolean nativeIsValid(long nativePtr); + private static native byte nativeGetMode(long nativePtr); } From 020aea727b0cf3f3abf81e2dd07187fe7427e576 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 11 Jan 2017 22:19:44 +0800 Subject: [PATCH 0397/2110] Implement RealmObject.isLoaded() This API is still useful for RxJava support. --- .../java/io/realm/DynamicRealmTests.java | 2 + .../src/main/java/io/realm/ProxyState.java | 4 + .../src/main/java/io/realm/RealmObject.java | 89 ++++++++++++++++--- 3 files changed, 82 insertions(+), 13 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 4699a1a868..cf8329d192 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -356,6 +356,7 @@ public void findFirstAsync() { final DynamicRealmObject allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 4, 9) .findFirstAsync(); + assertFalse(allTypes.isLoaded()); looperThread.keepStrongReference.add(allTypes); allTypes.addChangeListener(new RealmChangeListener() { @Override @@ -396,6 +397,7 @@ public void findAllSorted_async() { final RealmResults allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 0, 4) .findAllSorted(AllTypes.FIELD_STRING, Sort.DESCENDING); + assertFalse(allTypes.isLoaded()); allTypes.addChangeListener(new RealmChangeListener>() { @Override diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index d0a80069d0..49e2237b8e 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -158,6 +158,10 @@ public void onChange(ProxyState element) { }); } + public boolean isLoaded() { + return !(row instanceof PendingRow); + } + @Override public void onQueryFinished(Row row) { this.row = row; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 3cfe6d328c..df0a903b2a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -22,7 +22,6 @@ import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SharedRealm; import rx.Observable; /** @@ -151,26 +150,92 @@ public static boolean isValid(E object) { } /** - * @deprecated - * @return {@code true} always. + * Checks if the query used to find this RealmObject has completed. + * + * Async methods like {@link RealmQuery#findFirstAsync()} return an {@link RealmObject} that represents the future result + * of the {@link RealmQuery}. It can be considered similar to a {@link java.util.concurrent.Future} in this regard. + * + * Once {@code isLoaded()} returns {@code true}, the object represents the query result even if the query + * didn't find any object matching the query parameters. In this case the {@link RealmObject} will + * become a "null" object. + * + * "Null" objects represents {@code null}. An exception is throw if any accessor is called, so it is important to also + * check {@link #isValid()} before calling any methods. A common pattern is: + * + *

          +     * {@code
          +     * Person person = realm.where(Person.class).findFirstAsync();
          +     * person.isLoaded(); // == false
          +     * person.addChangeListener(new RealmChangeListener() {
          +     *      \@Override
          +     *      public void onChange(Person person) {
          +     *          person.isLoaded(); // Always true here
          +     *          if (person.isValid()) {
          +     *              // It is safe to access the person.
          +     *          }
          +     *      }
          +     * });
          +     * }
          +     * 
          + * + * Synchronous RealmObjects are by definition blocking hence this method will always return {@code true} for them. + * This method will return {@code true} if called on an unmanaged object (created outside of Realm). + * + * @return {@code true} if the query has completed, {@code false} if the query is in + * progress. * * @see #isValid() */ public final boolean isLoaded() { - //noinspection deprecation return RealmObject.isLoaded(this); } /** - * @deprecated + * Checks if the query used to find this RealmObject has completed. + * + * Async methods like {@link RealmQuery#findFirstAsync()} return an {@link RealmObject} that represents the future result + * of the {@link RealmQuery}. It can be considered similar to a {@link java.util.concurrent.Future} in this regard. + * + * Once {@code isLoaded()} returns {@code true}, the object represents the query result even if the query + * didn't find any object matching the query parameters. In this case the {@link RealmObject} will + * become a "null" object. + * + * "Null" objects represents {@code null}. An exception is throw if any accessor is called, so it is important to also + * check {@link #isValid()} before calling any methods. A common pattern is: + * + *
          +     * {@code
          +     * Person person = realm.where(Person.class).findFirstAsync();
          +     * RealmObject.isLoaded(person); // == false
          +     * RealmObject.addChangeListener(person, new RealmChangeListener() {
          +     *      \@Override
          +     *      public void onChange(Person person) {
          +     *          RealmObject.isLoaded(person); // always true here
          +     *          if (RealmObject.isValid(person)) {
          +     *              // It is safe to access the person.
          +     *          }
          +     *      }
          +     * });
          +     * }
          +     * 
          + * + * Synchronous RealmObjects are by definition blocking hence this method will always return {@code true} for them. + * This method will return {@code true} if called on an unmanaged object (created outside of Realm). + * + * * @param object RealmObject to check. - * @return {@code true} always. + * @return {@code true} if the query has completed, {@code false} if the query is in + * progress. * * @see #isValid(RealmModel) */ - @SuppressWarnings("UnusedParameters") public static boolean isLoaded(E object) { + if (object instanceof RealmObjectProxy) { + RealmObjectProxy proxy = (RealmObjectProxy) object; + proxy.realmGet$proxyState().getRealm$realm().checkIfValid(); + return proxy.realmGet$proxyState().isLoaded(); + } return true; } @@ -221,11 +286,8 @@ public static boolean isManaged(E object) { } /** - * Makes an asynchronous query blocking. This will also trigger any registered listeners. - *

          - * Note: This will return {@code true} if called for an unmanaged object (created outside of Realm). - * - * @return {@code true} if it successfully completed the query, {@code false} otherwise. + * @return {@code true} if this is a managed object. + * @deprecated see Async Queries for more information. */ public final boolean load() { //noinspection deprecation @@ -233,7 +295,8 @@ public final boolean load() { } /** - * @deprecated + * @return {@code true} if this is a managed object. + * @deprecated see Async Queries for more information. */ public static boolean load(E object) { return object instanceof RealmObjectProxy; From 0218cd24855606bf02d8e3e4e18d42cb0687e718 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 11 Jan 2017 22:27:45 +0800 Subject: [PATCH 0398/2110] Add RealmResults.isLoaded() Needed by RxJava support. --- .../src/androidTest/java/io/realm/DynamicRealmTests.java | 1 + .../src/main/java/io/realm/RealmResults.java | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index cf8329d192..163944c56c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -402,6 +402,7 @@ public void findAllSorted_async() { allTypes.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { + assertTrue(allTypes.isLoaded()); assertEquals(5, allTypes.size()); for (int i = 0; i < 5; i++) { int iteration = (4 - i); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 97e95ee965..386d77c995 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -724,10 +724,14 @@ public void set(E object) { } /** - * @deprecated + * Returns {@code false} if the results are not yet loaded, {@code true} if they are loaded. + * + * @return {@code true} if the query has completed and the data is available, {@code false} if the query is still + * running in the background. */ public boolean isLoaded() { - return true; + realm.checkIfValid(); + return collection.getMode() == Collection.Mode.TABLEVIEW; } /** From 1b70f8caf62df1912f25984b19d879fd8482b1d1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 13:16:06 +0800 Subject: [PATCH 0399/2110] Get findAllAsync and findFirstAsync back the findAllAsync got deprecated since from OS Results point of view, the query updating will run in the background by default, then there would be no difference between findAll and findAsync. Apparently the isLoad() and load() should be deprecated as well. So like cocoa, the Query will be executed immediately if user tries to access the element in the Results. But ... That was wrong because of those use cases: 1) RxJava support, in the subscription, it needs to check isLoaded() to determine the next step. And it will be fired once after the Observable created. By always returning true from isLoaded(), that means for RxJava will always run sync query at the first time. 2) Create a RealmResults and pass it to a list adapter. Since UI will always call size() which will run the query immediately and the first time query becomes synced. So, we get all async related APIs back and keep them having the same functionality like before. The behavior won't be exact the same, but from user's perspective, they are the same as before. --- .../java/io/realm/RealmQueryTests.java | 85 +++++++++++-- .../java/io/realm/RealmResultsTests.java | 112 +++++++++++++++--- .../src/main/java/io/realm/ProxyState.java | 15 ++- .../src/main/java/io/realm/RealmObject.java | 23 +++- .../src/main/java/io/realm/RealmQuery.java | 111 ++++++++++------- .../src/main/java/io/realm/RealmResults.java | 35 ++++-- .../java/io/realm/internal/Collection.java | 14 ++- .../java/io/realm/internal/PendingRow.java | 4 +- 8 files changed, 313 insertions(+), 86 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 1946af60ef..32764c7a75 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2601,7 +2601,7 @@ public void findAllSorted_onSubObjectField() { @Test @RunTestInLooperThread - public void findAllSorted_async_onSubObjectField() { + public void findAllSortedAsync_onSubObjectField() { Realm realm = looperThread.realm; populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) @@ -2636,7 +2636,7 @@ public void findAllSorted_listOnSubObjectField() { @Test @RunTestInLooperThread - public void findAllSorted_async_listOnSubObjectField() { + public void findAllSortedAsync_listOnSubObjectField() { Realm realm = looperThread.realm; String[] fieldNames = new String[2]; fieldNames[0] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; @@ -2798,21 +2798,33 @@ public void distinct_invalidTypesLinkedFields() { @Test @RunTestInLooperThread - public void distinct_async() throws Throwable { + public void distinctAsync() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); final Realm realm = looperThread.realm; final long numberOfBlocks = 25; final long numberOfObjects = 10; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_INDEX_LONG); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING); + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_LONG); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); + + assertFalse(distinctBool.isLoaded()); + assertTrue(distinctBool.isValid()); + assertTrue(distinctBool.isEmpty()); + + assertFalse(distinctLong.isLoaded()); + assertTrue(distinctLong.isValid()); + assertTrue(distinctLong.isEmpty()); + + assertFalse(distinctDate.isLoaded()); + assertTrue(distinctDate.isValid()); + assertTrue(distinctDate.isEmpty()); + + assertFalse(distinctString.isLoaded()); + assertTrue(distinctString.isValid()); + assertTrue(distinctString.isEmpty()); final Runnable endTest = new Runnable() { @Override @@ -2862,7 +2874,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread - public void distinct_async_withNullValues() throws Throwable { + public void distinctAsync_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); final Realm realm = looperThread.realm; final long numberOfBlocks = 25; @@ -2903,6 +2915,55 @@ public void onChange(RealmResults object) { }); } + @Test + public void distinctAsync_doesNotExist() { + final long numberOfBlocks = 25; + final long numberOfObjects = 10; + populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); + + try { + realm.where(AnnotationIndexTypes.class).distinctAsync("doesNotExist"); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void distinctAsync_invalidTypes() { + populateTestRealm(realm, TEST_DATA_SIZE); + + for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { + try { + realm.where(AllTypes.class).distinctAsync(field); + } catch (IllegalArgumentException ignored) { + } + } + } + + @Test + public void distinctAsync_indexedLinkedFields() { + final long numberOfBlocks = 25; + final long numberOfObjects = 10; + populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); + + for (String field : AnnotationIndexTypes.INDEX_FIELDS) { + try { + realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_OBJECT + "." + field); + fail("Unsupported " + field + " linked field"); + } catch (IllegalArgumentException ignored) { + } + } + } + + @Test + public void distinctAsync_notIndexedLinkedFields() { + populateForDistinctInvalidTypesLinked(realm); + + try { + realm.where(AllJavaTypes.class).distinctAsync(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); + } catch (IllegalArgumentException ignored) { + } + } + @Test public void distinctMultiArgs() { final long numberOfBlocks = 25; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 5664434fc3..5c92ab9207 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -49,6 +49,7 @@ import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -103,6 +104,7 @@ public void findFirst() { public void size_returns_Integer_MAX_VALUE_for_huge_results() { final Collection collection = Mockito.mock(Collection.class); final RealmResults targetResult = TestHelper.newRealmResults(realm, collection, AllTypes.class); + targetResult.load(); Mockito.when(collection.size()).thenReturn(((long) Integer.MAX_VALUE) - 1); assertEquals(Integer.MAX_VALUE - 1, targetResult.size()); @@ -414,21 +416,33 @@ private void populateTestRealm(Realm testRealm, int objects) { @Test @RunTestInLooperThread - public void distinct_async() throws Throwable { + public void distinctAsync() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); final Realm realm = looperThread.realm; final long numberOfBlocks = 25; final long numberOfObjects = 10; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll() - .distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).findAll() - .distinct(AnnotationIndexTypes.FIELD_INDEX_LONG); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll() - .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll() - .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING); + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_LONG); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); + + assertFalse(distinctBool.isLoaded()); + assertTrue(distinctBool.isValid()); + assertTrue(distinctBool.isEmpty()); + + assertFalse(distinctLong.isLoaded()); + assertTrue(distinctLong.isValid()); + assertTrue(distinctLong.isEmpty()); + + assertFalse(distinctDate.isLoaded()); + assertTrue(distinctDate.isValid()); + assertTrue(distinctDate.isEmpty()); + + assertFalse(distinctString.isLoaded()); + assertTrue(distinctString.isValid()); + assertTrue(distinctString.isEmpty()); final Runnable endTest = new Runnable() { @Override @@ -478,17 +492,23 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread - public void distinct_async_withNullValues() throws Throwable { + public void distinctAsync_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); final Realm realm = looperThread.realm; final long numberOfBlocks = 25; final long numberOfObjects = 10; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll() - .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll() - .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); + + assertFalse(distinctDate.isLoaded()); + assertTrue(distinctDate.isValid()); + assertTrue(distinctDate.isEmpty()); + + assertFalse(distinctString.isLoaded()); + assertTrue(distinctString.isValid()); + assertTrue(distinctString.isEmpty()); final Runnable endTest = new Runnable() { @Override @@ -518,6 +538,70 @@ public void onChange(RealmResults object) { }); } + @Test + public void distinctAsync_notIndexedFields() { + final long numberOfBlocks = 25; + final long numberOfObjects = 10; + populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); + + for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { + try { + realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(field); + fail(field); + } catch (IllegalArgumentException ignored) { + } + } + } + + @Test + public void distinctAsync_doesNotExist() { + final long numberOfBlocks = 25; + final long numberOfObjects = 10; + populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); + + try { + realm.where(AnnotationIndexTypes.class).findAll().distinctAsync("doesNotExist"); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void distinctAsync_invalidTypes() { + populateTestRealm(realm, TEST_DATA_SIZE); + + for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { + try { + realm.where(AllTypes.class).findAll().distinctAsync(field); + } catch (IllegalArgumentException ignored) { + } + } + } + + @Test + public void distinctAsync_indexedLinkedFields() { + final long numberOfBlocks = 25; + final long numberOfObjects = 10; + populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); + + for (String field : AnnotationIndexTypes.INDEX_FIELDS) { + try { + realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_OBJECT + "." + field); + fail("Unsupported " + field + " linked field"); + } catch (IllegalArgumentException ignored) { + } + } + } + + @Test + public void distinctAsync_notIndexedLinkedFields() { + populateForDistinctInvalidTypesLinked(realm); + + try { + realm.where(AllJavaTypes.class).findAll().distinctAsync(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); + } catch (IllegalArgumentException ignored) { + } + } + @Test public void distinctMultiArgs() { final long numberOfBlocks = 25; diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 49e2237b8e..d57c2cf596 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import io.realm.internal.InvalidRow; import io.realm.internal.PendingRow; import io.realm.internal.Row; import io.realm.internal.UncheckedRow; @@ -56,10 +57,6 @@ public ProxyState(E model) { } public Row getRow$realm() { - if (row instanceof PendingRow) { - row = ((PendingRow) row).executeQuery(); - registerToRealmNotifier(); - } return row; } @@ -162,6 +159,16 @@ public boolean isLoaded() { return !(row instanceof PendingRow); } + public void load() { + if (row instanceof PendingRow) { + row = ((PendingRow) row).executeQuery(); + if (!(row instanceof InvalidRow)) { + registerToRealmNotifier(); + } + notifyChangeListeners(); + } + } + @Override public void onQueryFinished(Row row) { this.row = row; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index df0a903b2a..487a6eabc1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -286,8 +286,11 @@ public static boolean isManaged(E object) { } /** - * @return {@code true} if this is a managed object. - * @deprecated see Async Queries for more information. + * Makes an asynchronous query blocking. This will also trigger any registered listeners. + *

          + * Note: This will return {@code true} if called for an unmanaged object (created outside of Realm). + * + * @return {@code true} if it successfully completed the query, {@code false} otherwise. */ public final boolean load() { //noinspection deprecation @@ -295,11 +298,21 @@ public final boolean load() { } /** - * @return {@code true} if this is a managed object. - * @deprecated see Async Queries for more information. + * Makes an asynchronous query blocking. This will also trigger any registered listeners. + *

          + * Note: This will return {@code true} if called for an unmanaged object (created outside of Realm). + * + * @param object RealmObject to force load. + * @return {@code true} if it successfully completed the query, {@code false} otherwise. */ public static boolean load(E object) { - return object instanceof RealmObjectProxy; + if (RealmObject.isLoaded(object)) { + return true; + } else if (object instanceof RealmObjectProxy) { + ((RealmObjectProxy) object).realmGet$proxyState().load(); + return true; + } + return false; } /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index f0da40ec60..e335f4e696 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1318,15 +1318,24 @@ public RealmQuery isNotEmpty(String fieldName) { */ public RealmResults distinct(String fieldName) { SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(query.getTable(), fieldName); - Collection collection = new Collection(realm.sharedRealm, query, null, distinctDescriptor); - return createRealmResults(collection); + return createRealmResults(query, null, distinctDescriptor, true); } /** - * @deprecated use {@link #distinct(String)} instead. + * Asynchronously returns a distinct set of objects of a specific class. If the result is + * sorted, the first object will be returned in case of multiple occurrences, otherwise it is + * undefined which object is returned. + * + * @param fieldName the field name. + * @return immediately a {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the + * query completes. + * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, + * is not indexed, or points to linked fields. */ public RealmResults distinctAsync(String fieldName) { - return distinct(fieldName); + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(query.getTable(), fieldName); + return createRealmResults(query, null, distinctDescriptor, false); } /** @@ -1347,8 +1356,7 @@ public RealmResults distinct(String firstFieldName, String... remainingFieldN fieldNames[0] = firstFieldName; System.arraycopy(remainingFieldNames, 0, fieldNames, 1, remainingFieldNames.length); SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table.getTable(), fieldNames); - Collection collection = new Collection(realm.sharedRealm, query, null, distinctDescriptor); - return createRealmResults(collection); + return createRealmResults(query, null, distinctDescriptor, true); } // Aggregates @@ -1502,15 +1510,19 @@ public long count() { */ @SuppressWarnings("unchecked") public RealmResults findAll() { - Collection collection = new Collection(realm.sharedRealm, query); - return createRealmResults(collection); + return createRealmResults(query, null, null, true); } /** - * @deprecated use {@link #findAll()} instead. + * Finds all objects that fulfill the query conditions and sorted by specific field name. + * This method is only available from a Looper thread. + * + * @return immediately an empty {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * @see io.realm.RealmResults */ public RealmResults findAllAsync() { - return findAll(); + return createRealmResults(query, null, null, false); } /** @@ -1529,16 +1541,21 @@ public RealmResults findAllAsync() { @SuppressWarnings("unchecked") public RealmResults findAllSorted(String fieldName, Sort sortOrder) { SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldName, sortOrder); - - Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor); - return createRealmResults(collection); + return createRealmResults(query, sortDescriptor, null, true); } /** - * @deprecated use {@link #findAllSorted(String, Sort) instead.} + * Similar to {@link #findAllSorted(String, Sort)} but runs asynchronously on a worker thread + * (Need a Realm opened from a looper thread to work). + * + * @return immediately an empty {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * @throws java.lang.IllegalArgumentException if field name does not exist or it belongs to a child + * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSortedAsync(final String fieldName, final Sort sortOrder) { - return findAllSorted(fieldName, sortOrder); + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldName, sortOrder); + return createRealmResults(query, sortDescriptor, null, false); } @@ -1559,7 +1576,13 @@ public RealmResults findAllSorted(String fieldName) { } /** - * @deprecated use {@link #findAllSorted(String)} instead. + * Similar to {@link #findAllSorted(String)} but runs asynchronously on a worker thread + * This method is only available from a Looper thread. + * + * @return immediately an empty {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * @throws java.lang.IllegalArgumentException if the field name does not exist or it belongs to a child + * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSortedAsync(String fieldName) { return findAllSortedAsync(fieldName, Sort.ASCENDING); @@ -1573,16 +1596,14 @@ public RealmResults findAllSortedAsync(String fieldName) { * * @param fieldNames an array of field names to sort by. * @param sortOrders how to sort the field names. - * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero + * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero * objects is returned. * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSorted(String fieldNames[], Sort sortOrders[]) { SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldNames, sortOrders); - - Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor); - return createRealmResults(collection); + return createRealmResults(query, sortDescriptor, null, true); } private boolean isDynamicQuery() { @@ -1590,10 +1611,19 @@ private boolean isDynamicQuery() { } /** - * @deprecated use {@link #findAllSorted(String[], Sort[])} instead. + * Similar to {@link #findAllSorted(String[], Sort[])} but runs asynchronously + * from a worker thread. + * This method is only available from a Looper thread. + * + * @return immediately an empty {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * @see io.realm.RealmResults + * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child + * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sortOrders) { - return findAllSorted(fieldNames, sortOrders); + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldNames, sortOrders); + return createRealmResults(query, sortDescriptor, null, false); } /** @@ -1617,7 +1647,13 @@ public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, } /** - * @deprecated use {@link #findAllSorted(String, Sort, String, Sort)} instead. + * Similar to {@link #findAllSorted(String, Sort, String, Sort)} but runs asynchronously on a worker thread + * This method is only available from a Looper thread. + * + * @return immediately an empty {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * @throws java.lang.IllegalArgumentException if a field name does not exist or it belongs to a child + * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { @@ -1684,26 +1720,21 @@ public E findFirstAsync() { return result; } - private void checkSortParameters(String fieldNames[], final Sort[] sortOrders) { - if (fieldNames == null) { - throw new IllegalArgumentException("fieldNames cannot be 'null'."); - } else if (sortOrders == null) { - throw new IllegalArgumentException("sortOrders cannot be 'null'."); - } else if (fieldNames.length == 0) { - throw new IllegalArgumentException("At least one field name must be specified."); - } else if (fieldNames.length != sortOrders.length) { - throw new IllegalArgumentException(String.format(Locale.ENGLISH, - "Number of field names (%d) and sort orders (%d) does not match.", - fieldNames.length, sortOrders.length)); - } - } - - private RealmResults createRealmResults(Collection collection) { + private RealmResults createRealmResults(TableQuery query, + SortDescriptor sortDescriptor, + SortDescriptor distinctDescriptor, + boolean loadResults) { + RealmResults results; + Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); if (isDynamicQuery()) { - return new RealmResults(realm, collection, className); + results = new RealmResults(realm, collection, className); } else { - return new RealmResults(realm, collection, clazz); + results = new RealmResults(realm, collection, clazz); + } + if (loadResults) { + results.load(); } + return results; } private long getSourceRowIndexForFirstObject() { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 386d77c995..0bdd8e5940 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -73,6 +73,7 @@ public class RealmResults extends AbstractList implemen String className; // Class name used by DynamicRealmObjects private final Collection collection; + private boolean loadedManually = false; RealmResults(BaseRealm realm, Collection collection, Class clazz) { this.realm = realm; @@ -130,7 +131,7 @@ public RealmQuery where() { @Override public boolean contains(Object object) { boolean contains = false; - if (object instanceof RealmObjectProxy) { + if (isLoaded() && object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; // TODO: Maybe we should just let OS throw? if (proxy.realmGet$proxyState().getRealm$realm() == realm) { @@ -360,8 +361,11 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName */ @Override public int size() { - long size = collection.size(); - return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; + if (isLoaded()) { + long size = collection.size(); + return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; + } + return 0; } /** @@ -449,10 +453,19 @@ public RealmResults distinct(String fieldName) { } /** - * @deprecated use {@link #distinct(String)} instead. + * Asynchronously returns a distinct set of objects of a specific class. If the result is + * sorted, the first object will be returned in case of multiple occurrences, otherwise it is + * undefined which object is returned. + * + * @param fieldName the field name. + * @return immediately a {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the + * query completes. + * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, + * is not indexed, or points to linked fields. */ public RealmResults distinctAsync(String fieldName) { - return distinct(fieldName); + return where().distinctAsync(fieldName); } /** @@ -731,13 +744,21 @@ public void set(E object) { */ public boolean isLoaded() { realm.checkIfValid(); - return collection.getMode() == Collection.Mode.TABLEVIEW; + return loadedManually || collection.getMode() == Collection.Mode.TABLEVIEW; } /** - * @deprecated + * Makes an asynchronous query blocking. This will also trigger any registered {@link RealmChangeListener} when + * the query completes. + * + * @return {@code true} if it successfully completed the query, {@code false} otherwise. */ public boolean load() { + // The Collection doesn't have to be loaded before accessing it if the query has not returned. + // Instead, accessing the Collection will just trigger the execution of query if needed. We add this flag is + // only to keep the original behavior of those APIs. eg.: For a async RealmResults, before query returns, the + // size() call should return 0 instead of running the query get the real size. + loadedManually = true; return true; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 623f50d89b..bf0e12081f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -148,8 +148,11 @@ static Mode getByValue(byte value) { } + // neverDetach means the collection won't be detached when local transaction starts. This is useful for the + // PendingRow implementation. public Collection(SharedRealm sharedRealm, TableQuery query, - SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { + SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor, + boolean neverDetach) { query.validateQuery(); this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), @@ -160,7 +163,14 @@ public Collection(SharedRealm sharedRealm, TableQuery query, this.context = sharedRealm.context; this.table = query.getTable(); this.context.addReference(this); - sharedRealm.addCollection(this); + if (!neverDetach) { + sharedRealm.addCollection(this); + } + } + + public Collection(SharedRealm sharedRealm, TableQuery query, + SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { + this(sharedRealm, query, sortDescriptor, distinctDescriptor, false); } public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index af4a24ad09..79a3f5a950 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -35,7 +35,7 @@ public interface FrontEnd { public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, final boolean returnCheckedRow) { - pendingCollection = new Collection(sharedRealm, query, sortDescriptor); + pendingCollection = new Collection(sharedRealm, query, sortDescriptor, null, true); listener = new RealmChangeListener() { @Override @@ -212,7 +212,7 @@ public void setNull(long columnIndex) { @Override public boolean isAttached() { - throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + return false; } @Override From 1efc2bc4641de1b21267ff917dcc3c2f02c3d2bc Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 13:21:52 +0800 Subject: [PATCH 0400/2110] Remove tests related with async handler impl 1. Remove some tests highly tighted with the old async implementation. 2. Fix some tests according to current OS async implementation. --- .../java/io/realm/RealmAsyncQueryTests.java | 1102 +---------------- 1 file changed, 17 insertions(+), 1085 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index b8e1d1819c..611083db53 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -16,9 +16,7 @@ package io.realm; -import android.os.Handler; import android.os.SystemClock; -import android.support.test.annotation.UiThreadTest; import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; @@ -27,10 +25,7 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; -import java.lang.ref.WeakReference; import java.util.Date; -import java.util.Iterator; -import java.util.Map; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -42,8 +37,6 @@ import io.realm.entities.Dog; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.Owner; -import io.realm.instrumentation.MockActivityManager; -import io.realm.internal.RealmObjectProxy; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.LogLevel; import io.realm.log.RealmLog; @@ -356,7 +349,12 @@ public void execute(Realm realm) { @Override public void onSuccess() { assertEquals(1, realm.where(AllTypes.class).count()); - assertEquals(1, results.size()); + // We cannot guarantee the async results get delivered from OS. + if (results.isLoaded()) { + assertEquals(1, results.size()); + } else { + assertEquals(0, results.size()); + } looperThread.testComplete(); } }, new Realm.Transaction.OnError() { @@ -399,7 +397,6 @@ public void onError(Throwable error) { }); } -/* // ************************************ // *** promises based async queries *** // ************************************ @@ -469,29 +466,6 @@ public void findAllAsync_throwsOnNonLooperThread() throws Throwable { } } - @Test - @RunTestInLooperThread - public void findAllAsync_reusingQuery() throws Throwable { - Realm realm = looperThread.realm; - populateTestRealm(realm, 10); - - RealmQuery query = realm.where(AllTypes.class) - .between("columnLong", 0, 4); - RealmResults queryAllSync = query.findAll(); - RealmResults allAsync = query.findAllAsync(); - - assertTrue(allAsync.load()); - assertEquals(allAsync, queryAllSync); - - // the RealmQuery already has an argumentHolder, can't reuse it - try { - query.findAllSorted("columnLong"); - fail("Should throw an exception, can not reuse RealmQuery"); - } catch (IllegalStateException ignored) { - looperThread.testComplete(); - } - } - // finding elements [0-4] asynchronously then wait for the promise to be loaded // using a callback to be notified when the data is loaded @Test @@ -551,352 +525,6 @@ public void onChange(RealmResults object) { assertEquals(5, realmResults.size()); } - // UC: - // 1- insert 10 objects - // 2- start an async query to find object [0-4] - // 3- assert current RealmResults is empty (Worker Thread didn't complete) - // 4- when the worker thread complete, advance the Realm - // 5- the caller thread is ahead of the result provided by the worker thread - // 6- retry automatically the async query - // 7- the returned RealmResults is now in the same version as the caller thread - // 8- the notification should be called once (when we retry automatically we shouldn't - // notify the user). - @Test - @RunTestInLooperThread - public void findAllAsync_retry() throws Throwable { - final AtomicInteger numberOfIntercept = new AtomicInteger(0); - final AtomicInteger numberOfInvocation = new AtomicInteger(0); - final Realm realm = looperThread.realm; - - // 1. Populate initial data - realm.setAutoRefresh(false); - populateTestRealm(realm, 10); - realm.setAutoRefresh(true); - - // 2. Configure handler interceptor - final Handler handler = new HandlerProxy(realm.handlerController) { - @Override - public boolean onInterceptInMessage(int what) { - // Intercepts in order: [QueryComplete, RealmChanged, QueryUpdated] - int intercepts = numberOfIntercept.incrementAndGet(); - switch (what) { - // 5. Intercept all messages from other threads. On the first complete, we advance the tread - // which will cause the async query to rerun instead of triggering the change listener. - case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: - if (intercepts == 1) { - // We advance the Realm so we can simulate a retry - realm.beginTransaction(); - realm.delete(AllTypes.class); - realm.commitTransaction(); - } - } - return false; - } - }; - //realm.setHandler(handler); - - // 3. Create a async query - final RealmResults realmResults = realm.where(AllTypes.class) - .between("columnLong", 0, 4) - .findAllAsync(); - - // 4. Ensure that query isn't loaded yet - assertFalse(realmResults.isLoaded()); - assertEquals(0, realmResults.size()); - - // 6. Callback triggered after retry has completed - looperThread.keepStrongReference.add(realmResults); - realmResults.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(3, numberOfIntercept.get()); - assertEquals(1, numberOfInvocation.incrementAndGet()); - assertTrue(realmResults.isLoaded()); - assertEquals(0, realmResults.size()); - looperThread.testComplete(); - } - }); - } - - // UC: - // 1- insert 10 objects - // 2- start 2 async queries to find all objects [0-9] & objects[0-4] - // 3- assert both RealmResults are empty (Worker Thread didn't complete) - // 4- the queries will complete with the same version as the caller thread - // 5- using a background thread update the Realm - // 6- now REALM_CHANGED will trigger a COMPLETED_UPDATE_ASYNC_QUERIES that should update all queries - // 7- callbacks are notified with the latest results (called twice overall) - @Test - @RunTestInLooperThread - public void findAllAsync_batchUpdate() throws Throwable { - final AtomicInteger numberOfNotificationsQuery1 = new AtomicInteger(0); - final AtomicInteger numberOfNotificationsQuery2 = new AtomicInteger(0); - final AtomicInteger numberOfIntercept = new AtomicInteger(0); - final Realm realm = looperThread.realm; - populateTestRealm(realm, 10); - - // 1. Configure Handler interceptor - Handler handler = new HandlerProxy(realm.handlerController) { - @Override - public boolean onInterceptInMessage(int what) { - int intercepts = numberOfIntercept.getAndIncrement(); - if (what == HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS && intercepts == 1) { - // 4. The first time the async queries complete we start an update from - // another background thread. This will cause queries to rerun when the - // background thread notifies this thread. - new RealmBackgroundTask(looperThread.realmConfiguration) { - @Override - public void doInBackground(Realm realm) { - realm.beginTransaction(); - realm.where(AllTypes.class) - .equalTo(AllTypes.FIELD_LONG, 4) - .findFirst() - .setColumnString("modified"); - realm.createObject(AllTypes.class); - realm.createObject(AllTypes.class); - realm.commitTransaction(); - } - }.awaitOrFail(); - } - return false; - } - }; - //realm.setHandler(handler); - - // 2. Create 2 async queries and check they are not loaded - final RealmResults realmResults1 = realm.where(AllTypes.class).findAllAsync(); - final RealmResults realmResults2 = realm.where(AllTypes.class).between("columnLong", 0, 4).findAllAsync(); - - assertFalse(realmResults1.isLoaded()); - assertFalse(realmResults2.isLoaded()); - assertEquals(0, realmResults1.size()); - assertEquals(0, realmResults2.size()); - - // 3. Change listeners will be called twice. Once when the first query completely and then - // when the background thread has completed, notifying this thread to rerun and then receive - // the updated results. - final Runnable signalCallbackDone = new Runnable() { - private AtomicInteger signalCallbackFinished = new AtomicInteger(2); - @Override - public void run() { - if (signalCallbackFinished.decrementAndGet() == 0) { - assertEquals(4, numberOfIntercept.get()); - assertEquals(2, numberOfNotificationsQuery1.get()); - assertEquals(2, numberOfNotificationsQuery2.get()); - looperThread.testComplete(); - } - } - }; - - looperThread.keepStrongReference.add(realmResults1); - looperThread.keepStrongReference.add(realmResults2); - - realmResults1.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - switch (numberOfNotificationsQuery1.incrementAndGet()) { - case 1: // first callback invocation - assertTrue(realmResults1.isLoaded()); - assertEquals(10, realmResults1.size()); - assertEquals("test data 4", realmResults1.get(4).getColumnString()); - break; - - case 2: // second callback - assertTrue(realmResults1.isLoaded()); - assertEquals(12, realmResults1.size()); - assertEquals("modified", realmResults1.get(4).getColumnString()); - signalCallbackDone.run(); - break; - } - } - }); - - - realmResults2.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - switch (numberOfNotificationsQuery2.incrementAndGet()) { - case 1: // first callback invocation - assertTrue(realmResults2.isLoaded()); - assertEquals(5, realmResults2.size()); - assertEquals("test data 4", realmResults2.get(4).getColumnString()); - break; - - case 2: // second callback - assertTrue(realmResults2.isLoaded()); - assertEquals(7, realmResults2.size()); - assertEquals("modified", realmResults2.get(4).getColumnString()); - signalCallbackDone.run(); - break; - } - } - }); - } - - // simulate a use case, when the caller thread advance read, while the background thread - // is operating on a previous version, this should retry the query on the worker thread - // to deliver the results once (using the latest version of the Realm) - @Test - @RunTestInLooperThread - public void findAllAsync_callerIsAdvanced() throws Throwable { - final AtomicInteger numberOfIntercept = new AtomicInteger(0); - final Realm realm = looperThread.realm; - populateTestRealm(realm, 10); - - // Configure handler interceptor - final Handler handler = new HandlerProxy(realm.handlerController) { - @Override - public boolean onInterceptInMessage(int what) { - // Intercepts in order [QueryCompleted, RealmChanged, QueryUpdated] - int intercepts = numberOfIntercept.incrementAndGet(); - switch (what) { - case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: { - // we advance the Realm so we can simulate a retry - if (intercepts == 1) { - realm.beginTransaction(); - realm.createObject(AllTypes.class).setColumnLong(0); - realm.commitTransaction(); - } - } - } - return false; - } - }; - //realm.setHandler(handler); - - // Create async query and verify it has not been loaded. - final RealmResults realmResults = realm.where(AllTypes.class) - .between("columnLong", 0, 4) - .findAllAsync(); - - assertFalse(realmResults.isLoaded()); - assertEquals(0, realmResults.size()); - - looperThread.keepStrongReference.add(realmResults); - - // Add change listener that should only be called once - realmResults.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(3, numberOfIntercept.get()); - assertTrue(realmResults.isLoaded()); - assertEquals(6, realmResults.size()); - looperThread.testComplete(); - } - }); - } - - // UC: - // 1- insert 10 objects - // 2- start 2 async queries to find all objects [0-9] & objects[0-4] - // 3- assert both RealmResults are empty (Worker Thread didn't complete) - // 4- start a third thread to insert 2 more elements - // 5- the third thread signal a REALM_CHANGE that should update all async queries - // 6- when the results from step [2] completes they should be ignored, since a pending - // update (using the latest realm) for all async queries is in progress - // 7- onChange notification will be triggered once - @Test - @RunTestInLooperThread - public void findAllAsync_callerThreadBehind() throws Throwable { - final AtomicInteger numberOfCompletedAsyncQuery = new AtomicInteger(0); - final AtomicInteger numberOfInterceptedChangeMessage = new AtomicInteger(0); - final AtomicInteger maxNumberOfNotificationsQuery1 = new AtomicInteger(1); - final AtomicInteger maxNumberOfNotificationsQuery2 = new AtomicInteger(1); - final Realm realm = looperThread.realm; - populateTestRealm(realm, 10); - - // Configure Handler Interceptor - final Handler handler = new HandlerProxy(realm.handlerController) { - @Override - public boolean onInterceptInMessage(int what) { - switch (what) { - case HandlerControllerConstants.REALM_CHANGED: { - // should only intercept the first REALM_CHANGED coming from the - // background update thread - - // swallow this message, so the caller thread - // remain behind the worker thread. This has as - // a consequence to ignore the delivered result & wait for the - // upcoming REALM_CHANGED to batch update all async queries - return numberOfInterceptedChangeMessage.getAndIncrement() == 0; - } - case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: { - if (numberOfCompletedAsyncQuery.incrementAndGet() == 2) { - // both queries have completed now (& their results should be ignored) - // now send the REALM_CHANGED event that should batch update all queries - sendEmptyMessage(HandlerControllerConstants.REALM_CHANGED); - } - } - } - return false; - } - }; - //realm.setHandler(handler); - Realm.asyncTaskExecutor.pause(); - - // Create async queries and check they haven't completed - final RealmResults realmResults1 = realm.where(AllTypes.class) - .findAllAsync(); - final RealmResults realmResults2 = realm.where(AllTypes.class) - .between("columnLong", 0, 4).findAllAsync(); - - assertFalse(realmResults1.isLoaded()); - assertFalse(realmResults2.isLoaded()); - assertEquals(0, realmResults1.size()); - assertEquals(0, realmResults2.size()); - - // advance the Realm from a background thread - new RealmBackgroundTask(looperThread.realmConfiguration) { - @Override - public void doInBackground(Realm realm) { - realm.beginTransaction(); - realm.where(AllTypes.class).equalTo("columnLong", 4).findFirst().setColumnString("modified"); - realm.createObject(AllTypes.class); - realm.createObject(AllTypes.class); - realm.commitTransaction(); - } - }.awaitOrFail(); - Realm.asyncTaskExecutor.resume(); - - // Setup change listeners - final Runnable signalCallbackDone = new Runnable() { - private AtomicInteger signalCallbackFinished = new AtomicInteger(2); - @Override - public void run() { - if (signalCallbackFinished.decrementAndGet() == 0) { - assertEquals(0, maxNumberOfNotificationsQuery1.get()); - assertEquals(0, maxNumberOfNotificationsQuery2.get()); - looperThread.testComplete(); - } - } - }; - - looperThread.keepStrongReference.add(realmResults1); - looperThread.keepStrongReference.add(realmResults2); - - realmResults1.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertTrue(maxNumberOfNotificationsQuery1.getAndDecrement() > 0); - assertTrue(realmResults1.isLoaded()); - assertEquals(12, realmResults1.size()); - assertEquals("modified", realmResults1.get(4).getColumnString()); - signalCallbackDone.run(); - } - }); - - realmResults2.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertTrue(maxNumberOfNotificationsQuery2.getAndDecrement() > 0); - assertTrue(realmResults2.isLoaded()); - assertEquals(7, realmResults2.size());// the 2 add rows has columnLong == 0 - assertEquals("modified", realmResults2.get(4).getColumnString()); - signalCallbackDone.run(); - } - }); - } - // ********************************** // *** 'findFirst' async queries *** // ********************************** @@ -938,13 +566,13 @@ public void onChange(AllTypes object) { looperThread.testComplete(); } }); - assertTrue(firstAsync.load()); - assertTrue(firstAsync.isLoaded()); - assertFalse(firstAsync.isValid()); realm.beginTransaction(); realm.createObject(AllTypes.class).setColumnLong(0); realm.commitTransaction(); + + assertTrue(firstAsync.load()); + assertTrue(firstAsync.isLoaded()); } @Test @@ -1026,74 +654,6 @@ public void findFirstAsync_forceLoad() throws Throwable { looperThread.testComplete(); } - // similar UC as #testFindAllAsyncRetry using 'findFirst' - // UC: - // 1- insert 10 objects - // 2- start an async query to find object [0-4] - // 3- assert current RealmResults is empty (Worker Thread didn't complete) - // 4- when the worker thread complete, advance the Realm - // 5- the caller thread is ahead of the result provided by the worker thread - // 6- retry automatically the async query - // 7- the returned RealmResults is now in the same version as the caller thread - // 8- the notification should be called once (when we retry automatically we shouldn't - // notify the user). - @Test - @RunTestInLooperThread - public void findFirstAsync_retry() throws Throwable { - final AtomicInteger numberOfIntercept = new AtomicInteger(0); - final Realm realm = looperThread.realm; - populateTestRealm(realm, 10); - - // Configure interceptor handler - final Handler handler = new HandlerProxy(realm.handlerController) { - @Override - public boolean onInterceptInMessage(int what) { - int intercepts = numberOfIntercept.incrementAndGet(); - switch (what) { - case HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT: { - if (intercepts == 1) { - // we advance the Realm so we can simulate a retry - realm.beginTransaction(); - realm.delete(AllTypes.class); - AllTypes object = realm.createObject(AllTypes.class); - object.setColumnString("The Endless River"); - object.setColumnLong(5); - realm.commitTransaction(); - } - } - } - return false; - } - }; - //realm.setHandler(handler); - - // Create a async query and verify it is not still loaded. - final AllTypes realmResults = realm.where(AllTypes.class) - .between("columnLong", 4, 6) - .findFirstAsync(); - - assertFalse(realmResults.isLoaded()); - - try { - realmResults.getColumnString(); - fail("Accessing property on an empty row"); - } catch (IllegalStateException ignored) { - } - - // Add change listener that should only be called once after the retry completed. - looperThread.keepStrongReference.add(realmResults); - realmResults.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(AllTypes object) { - assertEquals(3, numberOfIntercept.get()); - assertTrue(realmResults.isLoaded()); - assertEquals(5, realmResults.getColumnLong()); - assertEquals("The Endless River", realmResults.getColumnString()); - looperThread.testComplete(); - } - }); - } - // ************************************** // *** 'findAllSorted' async queries *** // ************************************** @@ -1127,416 +687,24 @@ public void onChange(RealmResults object) { }); } - - // finding elements [4-8] asynchronously then wait for the promise to be loaded - // using a callback to be notified when the data is loaded - @Test - @RunTestInLooperThread - public void findAllSortedAsync_retry() throws Throwable { - final AtomicInteger numberOfIntercept = new AtomicInteger(0); - final Realm realm = looperThread.realm; - - // 1. Populate the Realm without triggering a RealmChangeEvent. - realm.setAutoRefresh(false); - populateTestRealm(realm, 10); - realm.setAutoRefresh(true); - - // 2. Configure proxy handler to intercept messages - final Handler handler = new HandlerProxy(realm.handlerController) { - @Override - public boolean onInterceptInMessage(int what) { - // In order [QueryCompleted, RealmChanged, QueryUpdated] - int intercepts = numberOfIntercept.incrementAndGet(); - switch (what) { - case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: { - if (intercepts == 1) { - // We advance the Realm so we can simulate a retry before listeners are - // called. - realm.beginTransaction(); - realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 8).findFirst().deleteFromRealm(); - realm.commitTransaction(); - } - break; - } - } - return false; - } - }; - //realm.setHandler(handler); - - // 3. This will add a task to the paused asyncTaskExecutor - final RealmResults realmResults = realm.where(AllTypes.class) - .between("columnLong", 4, 8) - .findAllSortedAsync("columnString", Sort.ASCENDING); - - assertFalse(realmResults.isLoaded()); - assertEquals(0, realmResults.size()); - - // 4. Intercepting the query completed event the first time will - // cause a commit that should cause the findAllSortedAsync to be re-run. - // This change listener should only be called with the final result. - looperThread.keepStrongReference.add(realmResults); - realmResults.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(3, numberOfIntercept.get()); - looperThread.testComplete(); - } - }); - } - - // similar UC as #testFindAllAsyncBatchUpdate using 'findAllSorted' - // UC: - // 1- insert 10 objects - // 2- start 2 async queries to find all objects [0-9] & objects[0-4] - // 3- assert both RealmResults are empty (Worker Thread didn't complete) - // 4- the queries will complete with the same version as the caller thread - // 5- using a background thread update the Realm - // 6- now REALM_CHANGED will trigger a COMPLETED_UPDATE_ASYNC_QUERIES that should update all queries - // 7- callbacks are notified with the latest results (called twice overall) - @Test - @RunTestInLooperThread - public void findAllSortedAsync_batchUpdate() { - final AtomicInteger numberOfNotificationsQuery1 = new AtomicInteger(0); - final AtomicInteger numberOfNotificationsQuery2 = new AtomicInteger(0); - final AtomicInteger numberOfIntercept = new AtomicInteger(0); - Realm realm = looperThread.realm; - - // 1. Add initial 10 objects - realm.setAutoRefresh(false); - populateTestRealm(realm, 10); - realm.setAutoRefresh(true); - - // 2. Configure interceptor - final Handler handler = new HandlerProxy(realm.handlerController) { - @Override - public boolean onInterceptInMessage(int what) { - switch (what) { - case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: { - if (numberOfIntercept.incrementAndGet() == 2 *//* 2 queries are both completed *//*) { - // 6. The first time the async queries complete we start an update from - // another background thread. This will cause queries to rerun when the - // background thread notifies this thread. - final CountDownLatch bgThreadLatch = new CountDownLatch(1); - new Thread() { - @Override - public void run() { - Realm bgRealm = Realm.getInstance(looperThread.realmConfiguration); - bgRealm.beginTransaction(); - bgRealm.where(AllTypes.class).equalTo("columnLong", 4).findFirst().setColumnString("modified"); - bgRealm.createObject(AllTypes.class); - bgRealm.createObject(AllTypes.class); - bgRealm.commitTransaction(); - bgRealm.close(); - bgThreadLatch.countDown(); - } - }.start(); - TestHelper.awaitOrFail(bgThreadLatch); - } - } - break; - } - return false; - } - }; - //realm.setHandler(handler); - - // 3. Create 2 async queries - final RealmResults realmResults1 = realm.where(AllTypes.class) - .findAllSortedAsync("columnString", Sort.ASCENDING); - final RealmResults realmResults2 = realm.where(AllTypes.class) - .between("columnLong", 0, 4) - .findAllSortedAsync("columnString", Sort.DESCENDING); - - // 4. Assert that queries have not finished - assertFalse(realmResults1.isLoaded()); - assertFalse(realmResults2.isLoaded()); - assertEquals(0, realmResults1.size()); - assertEquals(0, realmResults2.size()); - - // 5. Change listeners will be called twice. Once when the first query completely and then - // when the background thread has completed, notifying this thread to rerun and then receive - // the updated results. - final Runnable signalCallbackDone = new Runnable() { - private AtomicInteger signalCallbackFinished = new AtomicInteger(2); - @Override - public void run() { - if (signalCallbackFinished.decrementAndGet() == 0) { - assertEquals(2, numberOfNotificationsQuery1.get()); - assertEquals(2, numberOfNotificationsQuery2.get()); - looperThread.testComplete(); - } - } - }; - - looperThread.keepStrongReference.add(realmResults1); - looperThread.keepStrongReference.add(realmResults2); - - realmResults1.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - switch (numberOfNotificationsQuery1.incrementAndGet()) { - case 1: { // first callback invocation - assertTrue(realmResults1.isLoaded()); - assertEquals(10, realmResults1.size()); - assertEquals("test data 4", realmResults1.get(4).getColumnString()); - break; - } - case 2: { // second callback - assertTrue(realmResults1.isLoaded()); - assertEquals(12, realmResults1.size()); - assertEquals("modified", realmResults1.get(2).getColumnString()); - signalCallbackDone.run(); - break; - } - } - } - }); - - realmResults2.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - switch (numberOfNotificationsQuery2.incrementAndGet()) { - case 1: { // first callback invocation - assertTrue(realmResults2.isLoaded()); - assertEquals(5, realmResults2.size()); - assertEquals("test data 4", realmResults2.get(0).getColumnString()); - break; - } - case 2: { // second callback - assertTrue(realmResults2.isLoaded()); - assertEquals(7, realmResults2.size()); - assertEquals("modified", realmResults2.get(4).getColumnString()); - signalCallbackDone.run(); - break; - } - } - } - }); - } - - // similar UC as #testFindAllAsyncBatchUpdate using 'findAllSortedMulti' - // UC: - // 1- insert 10 objects - // 2- start 2 async queries to find all objects [0-9] & objects[0-4] - // 3- assert both RealmResults are empty (Worker Thread didn't complete) - // 4- the queries will complete with the same version as the caller thread - // 5- using a background thread update the Realm - // 6- now REALM_CHANGED will trigger a COMPLETED_UPDATE_ASYNC_QUERIES that should update all queries - // 7- callbacks are notified with the latest results (called twice overall) - @Test - @RunTestInLooperThread - public void findAllSortedAsync_multipleFields_batchUpdate() throws Throwable { - final AtomicInteger numberOfNotificationsQuery1 = new AtomicInteger(0); - final AtomicInteger numberOfNotificationsQuery2 = new AtomicInteger(0); - final AtomicInteger numberOfIntercept = new AtomicInteger(0); - Realm realm = looperThread.realm; - - // 1. Add initial objects - realm.setAutoRefresh(false); - realm.beginTransaction(); - for (int i = 0; i < 5; ) { - AllTypes allTypes = realm.createObject(AllTypes.class); - allTypes.setColumnLong(i); - allTypes.setColumnString("data " + i % 3); - - allTypes = realm.createObject(AllTypes.class); - allTypes.setColumnLong(i); - allTypes.setColumnString("data " + (++i % 3)); - } - realm.commitTransaction(); - realm.setAutoRefresh(true); - - // 2. Configure interceptor - final Handler handler = new HandlerProxy(realm.handlerController) { - @Override - public boolean onInterceptInMessage(int what) { - int intercepts = numberOfIntercept.incrementAndGet(); - if (what == HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS && intercepts == 1) { - // 6. The first time the async queries complete we start an update from - // another background thread. This will cause queries to rerun when the - // background thread notifies this thread. - new RealmBackgroundTask(looperThread.realmConfiguration) { - @Override - public void doInBackground(Realm realm) { - realm.beginTransaction(); - realm.where(AllTypes.class) - .equalTo("columnString", "data 1") - .equalTo("columnLong", 0) - .findFirst().setColumnDouble(Math.PI); - AllTypes allTypes = realm.createObject(AllTypes.class); - allTypes.setColumnLong(2); - allTypes.setColumnString("data " + 5); - - allTypes = realm.createObject(AllTypes.class); - allTypes.setColumnLong(0); - allTypes.setColumnString("data " + 5); - realm.commitTransaction(); - } - }.awaitOrFail(); - } - return false; - } - }; - //realm.setHandler(handler); - - // 3. Create 2 async queries - final RealmResults realmResults1 = realm.where(AllTypes.class) - .findAllSortedAsync("columnString", Sort.ASCENDING, "columnLong", Sort.DESCENDING); - final RealmResults realmResults2 = realm.where(AllTypes.class) - .between("columnLong", 0, 5) - .findAllSortedAsync("columnString", Sort.DESCENDING, "columnLong", Sort.ASCENDING); - - // 4. Assert that queries have not finished - assertFalse(realmResults1.isLoaded()); - assertFalse(realmResults2.isLoaded()); - assertEquals(0, realmResults1.size()); - assertEquals(0, realmResults2.size()); - assertFalse(realmResults1.isLoaded()); - assertFalse(realmResults2.isLoaded()); - assertEquals(0, realmResults1.size()); - assertEquals(0, realmResults2.size()); - - // 5. Change listeners will be called twice. Once when the first query completely and then - // when the background thread has completed, notifying this thread to rerun and then receive - // the updated results. - final Runnable signalCallbackDone = new Runnable() { - private AtomicInteger signalCallbackFinished = new AtomicInteger(2); - @Override - public void run() { - if (signalCallbackFinished.decrementAndGet() == 0) { - assertEquals(4, numberOfIntercept.get()); - assertEquals(2, numberOfNotificationsQuery1.get()); - assertEquals(2, numberOfNotificationsQuery2.get()); - looperThread.testComplete(); - } - } - }; - - looperThread.keepStrongReference.add(realmResults1); - looperThread.keepStrongReference.add(realmResults2); - - realmResults1.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - switch (numberOfNotificationsQuery1.incrementAndGet()) { - case 1: // first callback invocation - assertTrue(realmResults1.isLoaded()); - assertEquals(10, realmResults1.size()); - - assertEquals("data 0", realmResults1.get(0).getColumnString()); - assertEquals(3, realmResults1.get(0).getColumnLong()); - assertEquals("data 0", realmResults1.get(1).getColumnString()); - assertEquals(2, realmResults1.get(1).getColumnLong()); - assertEquals("data 0", realmResults1.get(2).getColumnString()); - assertEquals(0, realmResults1.get(2).getColumnLong()); - - assertEquals("data 1", realmResults1.get(3).getColumnString()); - assertEquals(4, realmResults1.get(3).getColumnLong()); - assertEquals("data 1", realmResults1.get(4).getColumnString()); - assertEquals(3, realmResults1.get(4).getColumnLong()); - assertEquals("data 1", realmResults1.get(5).getColumnString()); - assertEquals(1, realmResults1.get(5).getColumnLong()); - assertEquals("data 1", realmResults1.get(6).getColumnString()); - assertEquals(0, realmResults1.get(6).getColumnLong()); - - assertEquals("data 2", realmResults1.get(7).getColumnString()); - assertEquals(4, realmResults1.get(7).getColumnLong()); - assertEquals("data 2", realmResults1.get(8).getColumnString()); - assertEquals(2, realmResults1.get(8).getColumnLong()); - assertEquals("data 2", realmResults1.get(9).getColumnString()); - assertEquals(1, realmResults1.get(9).getColumnLong()); - break; - - case 2: // second callback - assertTrue(realmResults1.isLoaded()); - assertEquals(12, realmResults1.size()); - //first - assertEquals("data 0", realmResults1.get(0).getColumnString()); - assertEquals(3, realmResults1.get(0).getColumnLong()); - - //last - assertEquals("data 5", realmResults1.get(11).getColumnString()); - assertEquals(0, realmResults1.get(11).getColumnLong()); - - signalCallbackDone.run(); - break; - } - } - }); - - realmResults2.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - switch (numberOfNotificationsQuery2.incrementAndGet()) { - case 1: // first callback invocation - assertTrue(realmResults2.isLoaded()); - assertEquals(10, realmResults2.size()); - - assertEquals("data 2", realmResults2.get(0).getColumnString()); - assertEquals(1, realmResults2.get(0).getColumnLong()); - assertEquals("data 2", realmResults2.get(1).getColumnString()); - assertEquals(2, realmResults2.get(1).getColumnLong()); - assertEquals("data 2", realmResults2.get(2).getColumnString()); - assertEquals(4, realmResults2.get(2).getColumnLong()); - - assertEquals("data 1", realmResults2.get(3).getColumnString()); - assertEquals(0, realmResults2.get(3).getColumnLong()); - assertEquals("data 1", realmResults2.get(4).getColumnString()); - assertEquals(1, realmResults2.get(4).getColumnLong()); - assertEquals("data 1", realmResults2.get(5).getColumnString()); - assertEquals(3, realmResults2.get(5).getColumnLong()); - assertEquals("data 1", realmResults2.get(6).getColumnString()); - assertEquals(4, realmResults2.get(6).getColumnLong()); - - assertEquals("data 0", realmResults2.get(7).getColumnString()); - assertEquals(0, realmResults2.get(7).getColumnLong()); - assertEquals("data 0", realmResults2.get(8).getColumnString()); - assertEquals(2, realmResults2.get(8).getColumnLong()); - assertEquals("data 0", realmResults2.get(9).getColumnString()); - assertEquals(3, realmResults2.get(9).getColumnLong()); - break; - - case 2: // second callback - assertTrue(realmResults2.isLoaded()); - assertEquals(12, realmResults2.size()); - - assertEquals("data 5", realmResults2.get(0).getColumnString()); - assertEquals(0, realmResults2.get(0).getColumnLong()); - - assertEquals("data 0", realmResults2.get(11).getColumnString()); - assertEquals(3, realmResults2.get(11).getColumnLong()); - - assertEquals("data 1", realmResults2.get(5).getColumnString()); - assertEquals(Math.PI, realmResults2.get(5).getColumnDouble(), 0.000000000001D); - - signalCallbackDone.run(); - break; - } - } - }); - } - @Test @RunTestInLooperThread public void combiningAsyncAndSync() { populateTestRealm(looperThread.realm, 10); - Realm.asyncTaskExecutor.pause(); final RealmResults allTypesAsync = looperThread.realm.where(AllTypes.class).greaterThan("columnLong", 5).findAllAsync(); final RealmResults allTypesSync = allTypesAsync.where().greaterThan("columnLong", 3).findAll(); assertEquals(0, allTypesAsync.size()); - assertEquals(6, allTypesSync.size()); + assertEquals(4, allTypesSync.size()); // columnLong > 5 && columnLong > 3 allTypesAsync.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { assertEquals(4, allTypesAsync.size()); - assertEquals(6, allTypesSync.size()); + assertEquals(4, allTypesSync.size()); looperThread.testComplete(); } }); - Realm.asyncTaskExecutor.resume(); looperThread.keepStrongReference.add(allTypesAsync); } @@ -1918,153 +1086,11 @@ public void doInBackground(Realm realm) { }); } - // Make sure we don't get the run into the IllegalStateException - // (Caller thread behind the worker thread) - // Scenario: - // - Caller thread is in version 1, start an asyncFindFirst - // - Another thread advance the Realm, now the latest version = 2 - // - The worker thread should query against version 1 not version 2 - // otherwise the caller thread wouldn't be able to import the result - // - The notification mechanism will guarantee that the REALM_CHANGE triggered by - // the background thread, will update the caller thread (advancing it to version 2) - @Test - @RunTestInLooperThread - public void testFindFirstUsesCallerThreadVersion() throws Throwable { - final CountDownLatch signalClosedRealm = new CountDownLatch(1); - - populateTestRealm(looperThread.realm, 10); - Realm.asyncTaskExecutor.pause(); - - final AllTypes firstAsync = looperThread.realm.where(AllTypes.class).findFirstAsync(); - looperThread.keepStrongReference.add(firstAsync); - firstAsync.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(AllTypes object) { - assertNotNull(firstAsync); - assertEquals("test data 0", firstAsync.getColumnString()); - looperThread.testComplete(signalClosedRealm); - } - }); - - // advance the background Realm - new Thread() { - @Override - public void run() { - Realm bgRealm = Realm.getInstance(looperThread.realmConfiguration); - // Advancing the Realm without generating notifications - bgRealm.sharedRealm.beginTransaction(); - bgRealm.sharedRealm.commitTransaction(); - Realm.asyncTaskExecutor.resume(); - bgRealm.close(); - signalClosedRealm.countDown(); - } - }.start(); - } - - // Test case for https://github.com/realm/realm-java/issues/2417 - // Ensure that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault. - @Test - @UiThreadTest - public void badVersion_findAll() throws NoSuchFieldException, IllegalAccessException { - TestHelper.replaceRealmThreadExecutor(RealmThreadPoolExecutor.newSingleThreadExecutor()); - RealmConfiguration config = configFactory.createConfiguration(); - Realm realm = Realm.getInstance(config); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.deleteAll(); - } - }); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.deleteAll(); - } - }); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.deleteAll(); - } - }); - boolean result = realm.where(AllTypes.class).findAllAsync().load(); - try { - assertFalse(result); - } finally { - realm.close(); - } - TestHelper.resetRealmThreadExecutor(); - } - - // Test case for https://github.com/realm/realm-java/issues/2417 - // Ensure that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault. - @Test - @UiThreadTest - public void badVersion_findAllSortedAsync() throws NoSuchFieldException, IllegalAccessException { - TestHelper.replaceRealmThreadExecutor(RealmThreadPoolExecutor.newSingleThreadExecutor()); - RealmConfiguration config = configFactory.createConfiguration(); - Realm realm = Realm.getInstance(config); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.deleteAll(); - } - }); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.deleteAll(); - } - }); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.deleteAll(); - } - }); - realm.where(AllTypes.class) - .findAllSortedAsync(AllTypes.FIELD_STRING, Sort.ASCENDING, AllTypes.FIELD_LONG, Sort.DESCENDING) - .load(); - realm.close(); - TestHelper.resetRealmThreadExecutor(); - } - - // Test case for https://github.com/realm/realm-java/issues/2417 - // Ensure that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault. - @Test - @UiThreadTest - public void badVersion_distinct() throws NoSuchFieldException, IllegalAccessException { - TestHelper.replaceRealmThreadExecutor(RealmThreadPoolExecutor.newSingleThreadExecutor()); - RealmConfiguration config = configFactory.createConfiguration(); - Realm realm = Realm.getInstance(config); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.deleteAll(); - } - }); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.deleteAll(); - } - }); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.deleteAll(); - } - }); - realm.where(AllJavaTypes.class) - .distinctAsync(AllJavaTypes.FIELD_STRING) - .load(); - - realm.close(); - TestHelper.resetRealmThreadExecutor(); - } // Test case for https://github.com/realm/realm-java/issues/2417 // Ensure that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault. + // NOTE: This test is not checking the same thing after the OS results integration. Just keep it for an additional + // test for async. @Test @RunTestInLooperThread public void badVersion_syncTransaction() throws NoSuchFieldException, IllegalAccessException { @@ -2087,7 +1113,7 @@ public void onChange(RealmResults object) { } }); - // 2. Advance the calle Realm, invalidating the version in the handover object + // 2. Advance the caller Realm, invalidating the version in the handover object realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -2097,105 +1123,11 @@ public void onChange(RealmResults object) { TestHelper.resetRealmThreadExecutor(); } - // handlerController#emptyAsyncRealmObject is accessed from different threads - // make sure that we iterate over it safely without any race condition (ConcurrentModification) - @Test - @UiThreadTest - public void concurrentModificationEmptyAsyncRealmObject() { - RealmConfiguration config = configFactory.createConfiguration(); - final Realm realm = Realm.getInstance(config); - Dog dog1 = new Dog(); - dog1.setName("Dog 1"); - - Dog dog2 = new Dog(); - dog2.setName("Dog 2"); - - realm.beginTransaction(); - dog1 = realm.copyToRealm(dog1); - dog2 = realm.copyToRealm(dog2); - realm.commitTransaction(); - - final WeakReference weakReference1 = new WeakReference((RealmObjectProxy)dog1); - final WeakReference weakReference2 = new WeakReference((RealmObjectProxy)dog2); - - final RealmQuery dummyQuery = RealmQuery.createQuery(realm, Dog.class); - // Initialize the emptyAsyncRealmObject map, to make sure that iterating is safe - // even if we modify the map from a background thread (in case of an empty findFirstAsync) - realm.handlerController.emptyAsyncRealmObject.put(weakReference1, dummyQuery); - - final CountDownLatch dogAddFromBg = new CountDownLatch(1); - Iterator, RealmQuery>> iterator = realm.handlerController.emptyAsyncRealmObject.entrySet().iterator(); - AtomicBoolean fireOnce = new AtomicBoolean(true); - while (iterator.hasNext()) { - Dog next = (Dog) iterator.next().getKey().get(); - // add a new Dog from a background thread - if (fireOnce.compareAndSet(true, false)) { - new Thread() { - @Override - public void run() { - // add a WeakReference to simulate an empty row using a findFirstAsync - // this is added on an Executor thread, hence the dedicated thread - realm.handlerController.emptyAsyncRealmObject.put(weakReference2, dummyQuery); - dogAddFromBg.countDown(); - } - }.start(); - TestHelper.awaitOrFail(dogAddFromBg); - } - assertEquals("Dog 1", next.getName()); - assertFalse(iterator.hasNext()); - } - realm.close(); - } - - // handlerController#realmObjects is accessed from different threads - // make sure that we iterate over it safely without any race condition (ConcurrentModification) - @Test - @UiThreadTest - public void concurrentModificationRealmObjects() { - RealmConfiguration config = configFactory.createConfiguration(); - final Realm realm = Realm.getInstance(config); - Dog dog1 = new Dog(); - dog1.setName("Dog 1"); - - Dog dog2 = new Dog(); - dog2.setName("Dog 2"); - - realm.beginTransaction(); - dog1 = realm.copyToRealm(dog1); - dog2 = realm.copyToRealm(dog2); - realm.commitTransaction(); - - final WeakReference weakReference1 = new WeakReference((RealmObjectProxy)dog1); - final WeakReference weakReference2 = new WeakReference((RealmObjectProxy)dog2); - - realm.handlerController.realmObjects.put(weakReference1, Boolean.TRUE); - - final CountDownLatch dogAddFromBg = new CountDownLatch(1); - Iterator, Object>> iterator = realm.handlerController.realmObjects.entrySet().iterator(); - AtomicBoolean fireOnce = new AtomicBoolean(true); - while (iterator.hasNext()) { - Dog next = (Dog) iterator.next().getKey().get(); - // add a new Dog from a background thread - if (fireOnce.compareAndSet(true, false)) { - new Thread() { - @Override - public void run() { - realm.handlerController.realmObjects.put(weakReference2, Boolean.TRUE); - dogAddFromBg.countDown(); - } - }.start(); - TestHelper.awaitOrFail(dogAddFromBg); - } - assertEquals("Dog 1", next.getName()); - assertFalse(iterator.hasNext()); - } - - realm.close(); - } - // This test reproduce the issue in https://secure.helpscout.net/conversation/244053233/6163/?folderId=366141 // First it creates 512 async queries, then trigger a transaction to make the queries gets update with // nativeBatchUpdateQueries. It should not exceed the limits of local ref map size in JNI. + // NOTE: This test is not checking the same thing after the OS results integration. Just keep it for an additional + // test for async. @Test @RunTestInLooperThread public void batchUpdate_localRefIsDeletedInLoopOfNativeBatchUpdateQueries() { @@ -2285,5 +1217,5 @@ private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOf } } realm.commitTransaction(); - }*/ + } } From 022155399909b8b4c51ec62435972d178a97d0bd Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 16:09:22 +0800 Subject: [PATCH 0401/2110] Revert findAllAsync tests for DynamicRealmTests --- .../java/io/realm/DynamicRealmTests.java | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 163944c56c..7a51be4d72 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -40,7 +40,6 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.exceptions.RealmException; -import io.realm.internal.PendingRow; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; @@ -370,11 +369,14 @@ public void onChange(DynamicRealmObject object) { @Test @RunTestInLooperThread - public void findAll_async() { + public void findAllAsync() { final DynamicRealm dynamicRealm = initializeDynamicRealm(); final RealmResults allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 4, 9) - .findAll(); + .findAllAsync(); + + assertFalse(allTypes.isLoaded()); + assertEquals(0, allTypes.size()); allTypes.addChangeListener(new RealmChangeListener>() { @Override @@ -392,17 +394,17 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread - public void findAllSorted_async() { + public void findAllSortedAsync() { final DynamicRealm dynamicRealm = initializeDynamicRealm(); final RealmResults allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 0, 4) - .findAllSorted(AllTypes.FIELD_STRING, Sort.DESCENDING); + .findAllSortedAsync(AllTypes.FIELD_STRING, Sort.DESCENDING); assertFalse(allTypes.isLoaded()); + assertEquals(0, allTypes.size()); allTypes.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { - assertTrue(allTypes.isLoaded()); assertEquals(5, allTypes.size()); for (int i = 0; i < 5; i++) { int iteration = (4 - i); @@ -533,6 +535,26 @@ public void onChange(RealmResults object) { looperThread.keepStrongReference.add(realmResults2); } + @Test + @RunTestInLooperThread + public void accessingDynamicRealmObjectBeforeAsyncQueryCompleted() { + final DynamicRealm dynamicRealm = initializeDynamicRealm(); + final DynamicRealmObject dynamicRealmObject = dynamicRealm.where(AllTypes.CLASS_NAME) + .between(AllTypes.FIELD_LONG, 4, 9) + .findFirstAsync(); + assertFalse(dynamicRealmObject.isLoaded()); + assertFalse(dynamicRealmObject.isValid()); + try { + dynamicRealmObject.getObject(AllTypes.FIELD_BINARY); + fail("trying to access a DynamicRealmObject property should throw"); + } catch (IllegalStateException ignored) { + + } finally { + dynamicRealm.close(); + looperThread.testComplete(); + } + } + @Test public void deleteAll() { realm.beginTransaction(); From 2924372ff33b463a1bbe1587c0fd4268179ff35b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 16:15:42 +0800 Subject: [PATCH 0402/2110] Expose listeners list size from RealmNotifier And enable related RxJava tests. --- .../src/androidTest/java/io/realm/RxJavaTests.java | 14 +++++++------- .../main/java/io/realm/internal/RealmNotifier.java | 4 ++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index 461533f9ff..42d989d73e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -379,7 +379,7 @@ public void call(DynamicRealm rxRealm) { @Test @UiThreadTest public void unsubscribe_sameThread() { -/* final AtomicBoolean subscribedNotified = new AtomicBoolean(false); + final AtomicBoolean subscribedNotified = new AtomicBoolean(false); subscription = realm.asObservable().subscribe(new Action1() { @Override public void call(Realm rxRealm) { @@ -387,15 +387,15 @@ public void call(Realm rxRealm) { subscribedNotified.set(true); } }); - assertEquals(1, realm.handlerController.changeListeners.size()); + assertEquals(1, realm.sharedRealm.realmNotifier.getListnersListSize()); subscription.unsubscribe(); - assertEquals(0, realm.handlerController.changeListeners.size());*/ + assertEquals(0, realm.sharedRealm.realmNotifier.getListnersListSize()); } @Test @UiThreadTest public void unsubscribe_fromOtherThread() { -/* final CountDownLatch unsubscribeCompleted = new CountDownLatch(1); + final CountDownLatch unsubscribeCompleted = new CountDownLatch(1); final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final Subscription subscription = realm.asObservable().subscribe(new Action1() { @Override @@ -405,7 +405,7 @@ public void call(Realm rxRealm) { } }); assertTrue(subscribedNotified.get()); - assertEquals(1, realm.handlerController.changeListeners.size()); + assertEquals(1, realm.sharedRealm.realmNotifier.getListnersListSize()); new Thread(new Runnable() { @Override public void run() { @@ -419,10 +419,10 @@ public void run() { } }).start(); TestHelper.awaitOrFail(unsubscribeCompleted); - assertEquals(1, realm.handlerController.changeListeners.size()); + assertEquals(1, realm.sharedRealm.realmNotifier.getListnersListSize()); // We cannot call subscription.unsubscribe() again, so manually close the extra Realm instance opened by // the Observable. - realm.close();*/ + realm.close(); } @Test diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index b725025993..3e5d8237c1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -139,4 +139,8 @@ public void addTransactionCallback(Runnable runnable) { * @param runnable to be executed in the following event loop. */ public abstract void post(Runnable runnable); + + public int getListnersListSize() { + return realmObserverPairs.size(); + } } From c6c081b0790431255eb6d68200e01f5d87c950f3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 16:38:46 +0800 Subject: [PATCH 0403/2110] Load the results of sort/distinct on RealmResults Otherwise the size will return 0 which is unexpected. --- .../src/main/java/io/realm/RealmResults.java | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 0bdd8e5940..4677e1337a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -305,11 +305,7 @@ public RealmResults sort(String fieldName) { SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, Sort.ASCENDING); Collection sortedCollection = collection.sort(sortDescriptor); - if (className != null) { - return new RealmResults(realm, sortedCollection, className); - } else { - return new RealmResults(realm, sortedCollection, classSpec); - } + return createLoadedResults(sortedCollection); } /** @@ -321,11 +317,7 @@ public RealmResults sort(String fieldName, Sort sortOrder) { SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, sortOrder); Collection sortedCollection = collection.sort(sortDescriptor); - if (className != null) { - return new RealmResults(realm, sortedCollection, className); - } else { - return new RealmResults(realm, sortedCollection, classSpec); - } + return createLoadedResults(sortedCollection); } /** @@ -337,11 +329,7 @@ public RealmResults sort(String fieldNames[], Sort sortOrders[]) { SortDescriptor.getInstanceForSort(collection.getTable(), fieldNames, sortOrders); Collection sortedCollection = collection.sort(sortDescriptor); - if (className != null) { - return new RealmResults(realm, sortedCollection, className); - } else { - return new RealmResults(realm, sortedCollection, classSpec); - } + return createLoadedResults(sortedCollection); } /** @@ -445,11 +433,7 @@ public double average(String fieldName) { public RealmResults distinct(String fieldName) { SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(collection.getTable(), fieldName); Collection distinctCollection = collection.distinct(distinctDescriptor); - if (className != null) { - return new RealmResults(realm, distinctCollection, className); - } else { - return new RealmResults(realm, distinctCollection, classSpec); - } + return createLoadedResults(distinctCollection); } /** @@ -844,4 +828,15 @@ public Observable> asObservable() { throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava."); } } + + private RealmResults createLoadedResults(Collection newCollection) { + RealmResults results; + if (className != null) { + results = new RealmResults(realm, newCollection, className); + } else { + results = new RealmResults(realm, newCollection, classSpec); + } + results.load(); + return results; + } } From eb3ff7ba85fd2a932a3fd4f8f13a530d59a29223 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 16:51:53 +0800 Subject: [PATCH 0404/2110] Disallow calling findAllAsync on non-looper thread --- .../src/androidTest/java/io/realm/RealmAsyncQueryTests.java | 1 + realm/realm-library/src/main/java/io/realm/RealmQuery.java | 5 +++++ realm/realm-library/src/main/java/io/realm/RealmResults.java | 1 + 3 files changed, 7 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 611083db53..69bba49553 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -460,6 +460,7 @@ public void findAllAsync_throwsOnNonLooperThread() throws Throwable { Realm realm = Realm.getInstance(configFactory.createConfiguration()); try { realm.where(AllTypes.class).findAllAsync(); + fail(); } catch (IllegalStateException ignored) { } finally { realm.close(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index e335f4e696..6ff652cd88 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -60,6 +60,7 @@ public class RealmQuery { private TableQuery query; private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; + static final String ASYNC_QUERY_WRONG_THREAD_MESSAGE = "Async query cannot be created on current thread."; /** * Creates a query for objects of a given class from a {@link Realm}. @@ -1522,6 +1523,7 @@ public RealmResults findAll() { * @see io.realm.RealmResults */ public RealmResults findAllAsync() { + realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); return createRealmResults(query, null, null, false); } @@ -1554,6 +1556,7 @@ public RealmResults findAllSorted(String fieldName, Sort sortOrder) { * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSortedAsync(final String fieldName, final Sort sortOrder) { + realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldName, sortOrder); return createRealmResults(query, sortDescriptor, null, false); } @@ -1622,6 +1625,7 @@ private boolean isDynamicQuery() { * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sortOrders) { + realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldNames, sortOrders); return createRealmResults(query, sortDescriptor, null, false); } @@ -1688,6 +1692,7 @@ public E findFirst() { * {@code false}. */ public E findFirstAsync() { + realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); Row row; if (realm.isInTransaction()) { // It is not possible to create async query inside a transaction. So immediately query the first object. diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 4677e1337a..b2aef35caa 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -449,6 +449,7 @@ public RealmResults distinct(String fieldName) { * is not indexed, or points to linked fields. */ public RealmResults distinctAsync(String fieldName) { + realm.sharedRealm.capabilities.checkCanDeliverNotification(RealmQuery.ASYNC_QUERY_WRONG_THREAD_MESSAGE); return where().distinctAsync(fieldName); } From 9dda80b689cd26a8cbfd284fe8df7472c965a48d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 16:55:19 +0800 Subject: [PATCH 0405/2110] Remove useless change --- .../realm-library/src/main/java/io/realm/internal/Context.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/Context.java b/realm/realm-library/src/main/java/io/realm/internal/Context.java index d68bceba93..9dc084e83f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Context.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Context.java @@ -29,9 +29,6 @@ public class Context { private final static ReferenceQueue referenceQueue = new ReferenceQueue(); private final static Thread finalizingThread = new Thread(new FinalizerRunnable(referenceQueue)); - // Context instance for native objects which are always thread-safe to be created and freed. - public final static Context sharedContext = new Context(); - static { finalizingThread.setName("RealmFinalizingDaemon"); finalizingThread.start(); From d17e145c07000f0fb1985ca4d34bcac134b708e1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 17:03:23 +0800 Subject: [PATCH 0406/2110] Typo fix --- .../src/androidTest/java/io/realm/RxJavaTests.java | 8 ++++---- .../src/main/java/io/realm/internal/RealmNotifier.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index 42d989d73e..afd0b472b4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -387,9 +387,9 @@ public void call(Realm rxRealm) { subscribedNotified.set(true); } }); - assertEquals(1, realm.sharedRealm.realmNotifier.getListnersListSize()); + assertEquals(1, realm.sharedRealm.realmNotifier.getListenersListSize()); subscription.unsubscribe(); - assertEquals(0, realm.sharedRealm.realmNotifier.getListnersListSize()); + assertEquals(0, realm.sharedRealm.realmNotifier.getListenersListSize()); } @Test @@ -405,7 +405,7 @@ public void call(Realm rxRealm) { } }); assertTrue(subscribedNotified.get()); - assertEquals(1, realm.sharedRealm.realmNotifier.getListnersListSize()); + assertEquals(1, realm.sharedRealm.realmNotifier.getListenersListSize()); new Thread(new Runnable() { @Override public void run() { @@ -419,7 +419,7 @@ public void run() { } }).start(); TestHelper.awaitOrFail(unsubscribeCompleted); - assertEquals(1, realm.sharedRealm.realmNotifier.getListnersListSize()); + assertEquals(1, realm.sharedRealm.realmNotifier.getListenersListSize()); // We cannot call subscription.unsubscribe() again, so manually close the extra Realm instance opened by // the Observable. realm.close(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 3e5d8237c1..eda0e87430 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -140,7 +140,7 @@ public void addTransactionCallback(Runnable runnable) { */ public abstract void post(Runnable runnable); - public int getListnersListSize() { + public int getListenersListSize() { return realmObserverPairs.size(); } } From 1244a36da5d322e4c57774a3b07bdfa8c8987007 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 17:49:16 +0800 Subject: [PATCH 0407/2110] Realm could be closed in the listener --- .../realm-library/src/main/java/io/realm/BaseRealm.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 18eb3bc8fd..66298e072c 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -320,8 +320,13 @@ public void beginTransaction() { public void commitTransaction() { checkIfValid(); sharedRealm.commitTransaction(); - ObjectServerFacade.getFacade(configuration.isSyncConfiguration()) - .notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); + if (!isClosed()) { + // The checking is because of the global listener is being called in commitTransaction from object store. + // The Realm could be closed inside the listener. In this case, we have no way to handle it. Moving + // SyncManger to Object Store will solve this. + ObjectServerFacade.getFacade(configuration.isSyncConfiguration()) + .notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); + } } /** From 8477d37c8fc2943ae1e7d9e4b68c9533009fa030 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 12 Jan 2017 17:54:36 +0800 Subject: [PATCH 0408/2110] Fix tests --- .../androidTest/java/io/realm/RealmResultsTests.java | 10 ++++++++++ .../java/io/realm/TypeBasedNotificationsTests.java | 10 +--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 5c92ab9207..0f92108f5b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -539,6 +539,7 @@ public void onChange(RealmResults object) { } @Test + @RunTestInLooperThread public void distinctAsync_notIndexedFields() { final long numberOfBlocks = 25; final long numberOfObjects = 10; @@ -551,9 +552,11 @@ public void distinctAsync_notIndexedFields() { } catch (IllegalArgumentException ignored) { } } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void distinctAsync_doesNotExist() { final long numberOfBlocks = 25; final long numberOfObjects = 10; @@ -563,9 +566,11 @@ public void distinctAsync_doesNotExist() { realm.where(AnnotationIndexTypes.class).findAll().distinctAsync("doesNotExist"); } catch (IllegalArgumentException ignored) { } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void distinctAsync_invalidTypes() { populateTestRealm(realm, TEST_DATA_SIZE); @@ -575,9 +580,11 @@ public void distinctAsync_invalidTypes() { } catch (IllegalArgumentException ignored) { } } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void distinctAsync_indexedLinkedFields() { final long numberOfBlocks = 25; final long numberOfObjects = 10; @@ -590,9 +597,11 @@ public void distinctAsync_indexedLinkedFields() { } catch (IllegalArgumentException ignored) { } } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void distinctAsync_notIndexedLinkedFields() { populateForDistinctInvalidTypesLinked(realm); @@ -600,6 +609,7 @@ public void distinctAsync_notIndexedLinkedFields() { realm.where(AllJavaTypes.class).findAll().distinctAsync(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); } catch (IllegalArgumentException ignored) { } + looperThread.testComplete(); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index 60a5a04324..3fba8aa185 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -17,8 +17,6 @@ import android.content.Context; import android.os.Build; -import android.os.Handler; -import android.os.HandlerThread; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.util.Base64; @@ -35,24 +33,19 @@ import java.io.InputStream; import java.util.Date; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; -import io.realm.entities.Cat; import io.realm.entities.Dog; -import io.realm.entities.Owner; import io.realm.entities.PrimaryKeyAsLong; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; -import io.realm.util.RealmBackgroundTask; import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -534,7 +527,6 @@ public void callback_with_relevant_commit_realmobject_async() { realm.commitTransaction(); final Dog dog = realm.where(Dog.class).findFirstAsync(); - assertTrue(dog.load()); looperThread.keepStrongReference.add(dog); dog.addChangeListener(new RealmChangeListener() { @@ -590,7 +582,7 @@ public void callback_with_relevant_commit_realmresults_sync() { final RealmResults dogs = realm.where(Dog.class).findAll(); // Execute the query. - assertEquals(1, dogs.size()); + dogs.first(); looperThread.keepStrongReference.add(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override From c9914c770a57caf6be7be17e9f83c8dbc1248f31 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 12 Jan 2017 15:04:45 +0100 Subject: [PATCH 0409/2110] Upgrading to Realm Sync 1.0.0 BETA 7.0 (#4026) --- CHANGELOG.md | 4 ++++ dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/CMakeLists.txt | 4 +++- realm/realm-library/src/main/cpp/object-store | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 984d063637..f5703615f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ * Fixed native memory leak setting the value of a primary key (#3993). +### Internal + +* Updated Realm Sync to 1.0.0-BETA-7.0. + ## 2.2.2 ### Object Server API Changes (In Beta) diff --git a/dependencies.list b/dependencies.list index 53ffc5b49c..ebe1c39f5f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.0-BETA-6.5 -REALM_SYNC_SHA256=dad59e910e4a8cab75791bab152e7c9e43712b174e0dce5a1596273976eb4de3 +REALM_SYNC_VERSION=1.0.0-BETA-7.0 +REALM_SYNC_SHA256=76dcf2052681aa21992489ca9b62172d9cb07f4def5e94011975df67bd552276 # Object Server Release used by Integration tests # https://packagecloud.io/realm/realm?filter=debs diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 95bfdd835f..8ba8566b5b 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -169,7 +169,9 @@ file(GLOB objectstore_SRC "object-store/src/util/android/*.cpp") # Sync needed Object Store files if (build_SYNC) - file(GLOB objectstore_sync_SRC "object-store/src/sync/*") + file(GLOB objectstore_sync_SRC + "object-store/src/sync/*" + "object-store/src/sync/impl/*") endif() add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 814beb5a1e..ac2f607264 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 814beb5a1e96f0bb72cf78e206b2e710ac79e217 +Subproject commit ac2f60726434654cace0bb5e57a92df7555c8f1f From cd3988c003d99632c56d9f7062faa066bb491a9c Mon Sep 17 00:00:00 2001 From: Realm CI Date: Thu, 12 Jan 2017 16:07:48 +0100 Subject: [PATCH 0410/2110] Fix merge from c9914c to master (#4027) * Upgrading to Realm Sync 1.0.0 BETA 7.0 (#4026) --- CHANGELOG.md | 4 ++++ dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffc6532b28..c9fca07427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ * Fixed native memory leak setting the value of a primary key (#3993). +### Internal + +* Updated Realm Sync to 1.0.0-BETA-7.0. + ## 2.2.2 ### Object Server API Changes (In Beta) diff --git a/dependencies.list b/dependencies.list index 53ffc5b49c..ebe1c39f5f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.0-BETA-6.5 -REALM_SYNC_SHA256=dad59e910e4a8cab75791bab152e7c9e43712b174e0dce5a1596273976eb4de3 +REALM_SYNC_VERSION=1.0.0-BETA-7.0 +REALM_SYNC_SHA256=76dcf2052681aa21992489ca9b62172d9cb07f4def5e94011975df67bd552276 # Object Server Release used by Integration tests # https://packagecloud.io/realm/realm?filter=debs diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 814beb5a1e..ac2f607264 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 814beb5a1e96f0bb72cf78e206b2e710ac79e217 +Subproject commit ac2f60726434654cace0bb5e57a92df7555c8f1f From 7482e99d6671bc86b23a97e86762b8ea31406aa2 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Thu, 12 Jan 2017 10:59:03 -0800 Subject: [PATCH 0411/2110] Add an overloaded 2 arg usernamePassword method that defaults createUser false Change the order of args for the custom method to match the iOS call Add unit tests as appropriate Fixes #3698 --- CHANGELOG.md | 1 + .../java/io/realm/CredentialsTests.java | 91 +++++++++++++------ .../java/io/realm/SyncCredentials.java | 90 ++++++++++-------- 3 files changed, 116 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9fca07427..bfccefea53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Object Server API Changes (In Beta) * Add a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`). +* Change the order of arguments to SyncCredentials.custom to match iOS: token, provider, userInfo ## 2.2.3 diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java index eaac178bb9..8143418f05 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java @@ -35,7 +35,7 @@ public class CredentialsTests { @Test public void getUserInfo_isUnmodifiable() { - SyncCredentials creds = SyncCredentials.custom("foo", "bar", null); + SyncCredentials creds = SyncCredentials.custom("foo", "customProvider", null); Map userInfo = creds.getUserInfo(); try { userInfo.put("boom", null); @@ -53,6 +53,18 @@ public void facebook() { assertTrue(creds.getUserInfo().isEmpty()); } + @Test + public void facebook_invalidInput() { + String[] invalidInput = {null, ""}; + for (String input : invalidInput) { + try { + SyncCredentials.facebook(input); + fail(input + " should have failed"); + } catch (IllegalArgumentException ignored) { + } + } + } + @Test public void google() { SyncCredentials creds = SyncCredentials.google("foo"); @@ -63,11 +75,11 @@ public void google() { } @Test - public void facebook_invalidInput() { - String[] invalidInput = { null, ""}; + public void google_invalidInput() { + String[] invalidInput = {null, ""}; for (String input : invalidInput) { try { - SyncCredentials.facebook(input); + SyncCredentials.google(input); fail(input + " should have failed"); } catch (IllegalArgumentException ignored) { } @@ -75,20 +87,27 @@ public void facebook_invalidInput() { } @Test - public void usernamePassword() { + public void usernamePassword_register() { SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar", true); - assertEquals("foo", creds.getUserIdentifier()); - Map userInfo = creds.getUserInfo(); + assertUsernamePassword(creds, "foo", "bar", true); + } - assertEquals(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, creds.getIdentityProvider()); - assertEquals("bar", userInfo.get("password")); - assertTrue((Boolean) userInfo.get("register")); + @Test + public void usernamePassword_noRegister() { + SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar", false); + assertUsernamePassword(creds, "foo", "bar", false); + } + + @Test + public void usernamePassword_defaultRegister() { + SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar"); + assertUsernamePassword(creds, "foo", "bar", false); } // Only validate username. All passwords are allowed @Test public void usernamePassword_invalidUserName() { - String[] invalidInput = { null, ""}; + String[] invalidInput = {null, ""}; for (String input : invalidInput) { try { SyncCredentials.usernamePassword(input, "bar", true); @@ -98,24 +117,18 @@ public void usernamePassword_invalidUserName() { } } + // Null passwords are allowed @Test - public void custom_invalidUserName() { - Map userInfo = new HashMap<>(); - userInfo.put("custom", "property"); - for (String username : new String[]{null, ""}) { - try { - SyncCredentials.custom("facebook", username, userInfo); - fail(); - } catch (IllegalArgumentException ignored) { - } - } + public void usernamePassword_nullPassword() { + SyncCredentials creds = SyncCredentials.usernamePassword("foo", null, true); + assertUsernamePassword(creds, "foo", null, true); } @Test public void custom() { Map userInfo = new HashMap(); userInfo.put("custom", "property"); - SyncCredentials creds = SyncCredentials.custom("customProvider", "foo", userInfo); + SyncCredentials creds = SyncCredentials.custom("foo", "customProvider", userInfo); assertEquals("foo", creds.getUserIdentifier()); assertEquals("customProvider", creds.getIdentityProvider()); @@ -124,16 +137,42 @@ public void custom() { } @Test - public void custom_invalidProvider() { + public void custom_invalidUserName() { Map userInfo = new HashMap<>(); - userInfo.put("custom", "property"); - for (String provider : new String[]{null, ""}) { + String[] invalidInput = {null, ""}; + for (String username : invalidInput) { try { - SyncCredentials.custom(null, "foo", userInfo); + SyncCredentials.custom(username, SyncCredentials.IdentityProvider.FACEBOOK, userInfo); fail(); } catch (IllegalArgumentException ignored) { } } } + + @Test + public void custom_invalidProvider() { + Map userInfo = new HashMap<>(); + + try { + SyncCredentials.custom("foo", null, userInfo); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + private void assertUsernamePassword(SyncCredentials creds, String username, String password, boolean register) { + assertEquals(username, creds.getUserIdentifier()); + + Map userInfo = creds.getUserInfo(); + assertEquals(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, creds.getIdentityProvider()); + + assertEquals(password, userInfo.get("password")); + + Boolean registerActual = (Boolean) userInfo.get("register"); + if (registerActual == null) { + registerActual = Boolean.FALSE; + } + assertEquals(register, registerActual); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 827f1265cf..02e925643c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -66,12 +66,38 @@ @Beta public class SyncCredentials { - private String identityProvider; - private String userIdentifier; - private Map userInfo; + private final String userIdentifier; + private final String identityProvider; + private final Map userInfo; // Factory constructors + /** + * Creates credentials based on a Facebook login. + * + * @param facebookToken a facebook userIdentifier acquired by logging into Facebook. + * @return a set of credentials that can be used to log into the Object Server using + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * @throws IllegalArgumentException if user name is either {@code null} or empty. + */ + public static SyncCredentials facebook(String facebookToken) { + assertStringNotEmpty(facebookToken, "facebookToken"); + return new SyncCredentials(facebookToken, IdentityProvider.FACEBOOK, null); + } + + /** + * Creates credentials based on a Google login. + * + * @param googleToken a google userIdentifier acquired by logging into Google. + * @return a set of credentials that can be used to log into the Object Server using + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * @throws IllegalArgumentException if user name is either {@code null} or empty. + */ + public static SyncCredentials google(String googleToken) { + assertStringNotEmpty(googleToken, "googleToken"); + return new SyncCredentials(googleToken, IdentityProvider.GOOGLE, null); + } + /** * Creates credentials based on a login with username and password. These credentials will only be verified * by the Object Server. @@ -86,51 +112,33 @@ public class SyncCredentials { * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static SyncCredentials usernamePassword(String username, String password, boolean createUser) { - if (username == null || username.equals("")) { - throw new IllegalArgumentException("Non-null 'username' required."); - } + assertStringNotEmpty(username, "username"); Map userInfo = new HashMap(); userInfo.put("register", createUser); userInfo.put("password", password); - return new SyncCredentials(IdentityProvider.USERNAME_PASSWORD, username, userInfo); + return new SyncCredentials(username, IdentityProvider.USERNAME_PASSWORD, userInfo); } /** - * Creates credentials based on a Facebook login. - * - * @param facebookToken a facebook userIdentifier acquired by logging into Facebook. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} - * @throws IllegalArgumentException if user name is either {@code null} or empty. - */ - public static SyncCredentials facebook(String facebookToken) { - if (facebookToken == null || facebookToken.equals("")) { - throw new IllegalArgumentException("Non-null 'facebookToken' required."); - } - return new SyncCredentials(IdentityProvider.FACEBOOK, facebookToken, null); - } - - /** - * Creates credentials based on a Google login. + * Creates credentials based on a login with username and password. These credentials will only be verified + * by the Object Server. The user is not created if she does not exist. * - * @param googleToken a google userIdentifier acquired by logging into Google. + * @param username username of the user. + * @param password the users password. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ - public static SyncCredentials google(String googleToken) { - if (googleToken == null || googleToken.equals("")) { - throw new IllegalArgumentException("Non-null 'googleToken' required."); - } - return new SyncCredentials(IdentityProvider.GOOGLE, googleToken, null); + public static SyncCredentials usernamePassword(String username, String password) { + return usernamePassword(username, password, false); } /** * Creates a custom set of credentials. The behaviour will depend on the type of {@code identityProvider} and * {@code userInfo} used. * - * @param identityProvider provider used to verify the credentials. * @param userIdentifier String identifying the user. Usually a username of userIdentifier. + * @param identityProvider provider used to verify the credentials. * @param userInfo data describing the user further or {@code null} if the user does not have any extra data. The * data will be serialized to JSON, so all values must be mappable to a valid JSON data type. Custom * classes will be converted using {@code toString()}. @@ -138,20 +146,22 @@ public static SyncCredentials google(String googleToken) { * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if any parameter is either {@code null} or empty. */ - public static SyncCredentials custom(String identityProvider, String userIdentifier, Map userInfo) { - if (identityProvider == null || identityProvider.equals("")) { - throw new IllegalArgumentException("Non-null 'identityProvider' required."); - } - if (userIdentifier == null || userIdentifier.equals("")) { - throw new IllegalArgumentException("Non-null 'userIdentifier' required."); - } + public static SyncCredentials custom(String userIdentifier, String identityProvider, Map userInfo) { + assertStringNotEmpty(userIdentifier, "userIdentifier"); + assertStringNotEmpty(identityProvider, "identityProvider"); if (userInfo == null) { userInfo = new HashMap(); } - return new SyncCredentials(identityProvider, userIdentifier, userInfo); + return new SyncCredentials(userIdentifier, identityProvider, userInfo); + } + + private static void assertStringNotEmpty(String string, String message) { + if (string == null || "".equals(string)) { + throw new IllegalArgumentException("Non-null '" + message + "' required."); + } } - private SyncCredentials(String identityProvider, String token, Map userInfo) { + private SyncCredentials(String token, String identityProvider, Map userInfo) { this.identityProvider = identityProvider; this.userIdentifier = token; this.userInfo = (userInfo == null) ? new HashMap() : userInfo; From 7be2450a4045951dcf7a81e1ede60b365b43a1db Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Thu, 12 Jan 2017 11:17:18 -0800 Subject: [PATCH 0412/2110] Wire the "like" predicate into RealmQuery (#3992) * Wire the "like" predicate into RealmQuery Fixes #3752 --- CHANGELOG.md | 1 + .../java/io/realm/RealmQueryTests.java | 109 +++++++++++++++++- .../java/io/realm/internal/JNIQueryTest.java | 16 ++- .../main/cpp/io_realm_internal_TableQuery.cpp | 15 ++- .../src/main/java/io/realm/RealmQuery.java | 46 +++++++- .../java/io/realm/internal/TableQuery.java | 13 +++ 6 files changed, 188 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfccefea53..1e9655227d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ * All major public classes are now non-final. This is mostly a compromise to support Mockito. All protected fields/methods are still not considered part of the public API and can change without notice (#3869). * All Realm instances share a single notification daemon thread. * Fixed Java lint warnings with generated proxy classes (#2929). +* Add 'like' predicate for String fields (#3752) ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index d23e6796c1..6ef934579b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -880,6 +880,90 @@ public void contains_caseSensitiveWithNonLatinCharacters() { assertEquals(0, resultList.size()); } + @Test + public void like_caseSensitive() { + final int TEST_OBJECTS_COUNT = 200; + populateTestRealm(realm, TEST_OBJECTS_COUNT); + + RealmResults resultList = realm.where(AllTypes.class).like("columnString", "*DaTa*").findAll(); + assertEquals(0, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "*DaTa*", Case.INSENSITIVE).findAll(); + assertEquals(TEST_OBJECTS_COUNT, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "*DaTa 2?").findAll(); + assertEquals(0, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "*DaTa 2?", Case.INSENSITIVE).findAll(); + assertEquals(10, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "TEST*0").findAll(); + assertEquals(0, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "TEST*0", Case.INSENSITIVE).findAll(); + assertEquals(20, resultList.size()); + } + + @Test + public void like_caseSensitiveWithNonLatinCharacters() { + populateTestRealm(); + + String flagEmoji = new StringBuilder().append(Character.toChars(0x1F1E9)).toString(); + String emojis = "ABC" + flagEmoji + "DEF"; + + realm.beginTransaction(); + realm.delete(AllTypes.class); + AllTypes at1 = realm.createObject(AllTypes.class); + at1.setColumnString("Αλφα"); + AllTypes at2 = realm.createObject(AllTypes.class); + at2.setColumnString("βήτα"); + AllTypes at3 = realm.createObject(AllTypes.class); + at3.setColumnString("δέλτα"); + AllTypes at4 = realm.createObject(AllTypes.class); + at4.setColumnString(emojis); + realm.commitTransaction(); + + RealmResults resultList = realm.where(AllTypes.class).like("columnString", "*Α*").findAll(); + assertEquals(1, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "*λ*").findAll(); + assertEquals(2, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "*Δ*").findAll(); + assertEquals(0, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "*Α*", Case.INSENSITIVE).findAll(); + //without ASCII-only limitation A matches α + //assertEquals(3, resultList.size()); + assertEquals(1, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "*λ*", Case.INSENSITIVE).findAll(); + assertEquals(2, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "*Δ*", Case.INSENSITIVE).findAll(); + //without ASCII-only limitation Δ matches δ + //assertEquals(1, resultList.size()); + assertEquals(0, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "?λ*").findAll(); + assertEquals(1, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "??λ*").findAll(); + assertEquals(1, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "?λ*").findAll(); + assertEquals(1, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "??λ*").findAll(); + assertEquals(1, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "ABC?DEF*").findAll(); + assertEquals(1, resultList.size()); + + resultList = realm.where(AllTypes.class).like("columnString", "*" + flagEmoji + "*").findAll(); + assertEquals(1, resultList.size()); + } + @Test public void equalTo_withNonExistingField() { try { @@ -1258,6 +1342,17 @@ public void endsWith_nullStringPrimaryKey() { assertEquals(SECONDARY_FIELD_NUMBER, realm.where(PrimaryKeyAsString.class).endsWith(PrimaryKeyAsString.FIELD_PRIMARY_KEY, (String) null).findAll().first().getId()); } + @Test + public void like_nullStringPrimaryKey() { + final long SECONDARY_FIELD_NUMBER = 49992417L; + TestHelper.populateTestRealmWithStringPrimaryKey(realm, (String) null, SECONDARY_FIELD_NUMBER, 10, -5); + + assertEquals( + SECONDARY_FIELD_NUMBER, + realm.where(PrimaryKeyAsString.class).like(PrimaryKeyAsString.FIELD_PRIMARY_KEY, (String) null) + .findAll().first().getId()); + } + @Test public void between_nullPrimaryKeysIsNotZero() { // fill up a realm with one user PrimaryKey value and 9 numeric values, starting from -5 @@ -1504,7 +1599,7 @@ public void beginWith_nullForNullableStrings() { (String) null).findFirst().getFieldStringNotNull()); } - // Querying nullable field with endsWith - all strings contain with null + // Querying nullable field with contains - all strings contain null @Test public void contains_nullForNullableStrings() { TestHelper.populateTestRealmForNullTests(realm); @@ -1520,6 +1615,18 @@ public void endsWith_nullForNullableStrings() { (String) null).findFirst().getFieldStringNotNull()); } + // Querying nullable field with like - nulls do not match either '?' or '*' + @Test + public void like_nullForNullableStrings() { + TestHelper.populateTestRealmForNullTests(realm); + RealmResults resultList = realm.where(NullTypes.class).like(NullTypes.FIELD_STRING_NULL, "*") + .findAll(); + assertEquals(2, resultList.size()); + + resultList = realm.where(NullTypes.class).like(NullTypes.FIELD_STRING_NULL, "?").findAll(); + assertEquals(0, resultList.size()); + } + // Querying with between and table has null values in row. @Test public void between_nullValuesInRow() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index 71f504509d..861d3e660c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -375,6 +375,8 @@ public void testNullInputQuery() { try { t.where().beginsWith(new long[]{1}, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } try { t.where().endsWith(new long[]{1}, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } try { t.where().endsWith(new long[]{1}, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().like(new long[]{1}, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().like(new long[]{1}, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } } @@ -448,9 +450,10 @@ public void testQueryWithWrongDataType() { for (int i = 0; i <= 6; i++) { try { query.equalTo(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} try { query.notEqualTo(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.beginsWith(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.endsWith(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.contains(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.beginsWith(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.endsWith(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.like(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.contains(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} } // Compare integer in non integer columns @@ -575,9 +578,10 @@ public void testColumnIndexOutOfBounds() { // Out of bounds for string try { query.equalTo(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} try { query.notEqualTo(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.beginsWith(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.endsWith(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.beginsWith(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.endsWith(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.like(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} // Out of bounds for integer diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 82516b730d..7a9bd522c8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -831,7 +831,8 @@ enum StringPredicate { StringNotEqual, StringContains, StringBeginsWith, - StringEndsWith + StringEndsWith, + StringLike }; @@ -866,6 +867,9 @@ static void TableQuery_StringPredicate(JNIEnv *env, jlong nativeQueryPtr, jlongA case StringEndsWith: Q(nativeQueryPtr)->ends_with(S(arr[0]), value2, is_case_sensitive); break; + case StringLike: + Q(nativeQueryPtr)->like(S(arr[0]), value2, is_case_sensitive); + break; } } else { @@ -886,6 +890,9 @@ static void TableQuery_StringPredicate(JNIEnv *env, jlong nativeQueryPtr, jlongA case StringEndsWith: Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])).ends_with(StringData(value2), is_case_sensitive)); break; + case StringLike: + Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])).like(StringData(value2), is_case_sensitive)); + break; } } } CATCH_STD() @@ -915,6 +922,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndsWith( TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringEndsWith); } +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLike( + JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) +{ + TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringLike); +} + JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeContains( JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index b9f6cc31c5..f42c9734df 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -45,7 +45,7 @@ * A RealmQuery encapsulates a query on a {@link io.realm.Realm} or a {@link io.realm.RealmResults} using the Builder * pattern. The query is executed using either {@link #findAll()} or {@link #findFirst()}. *

          - * The input to many of the query functions take a field name as String. Note that this is not type safe. If a + * The input to many of the query functions take a field name as String. Note that this is not type safe. If a * RealmObject class is refactored care has to be taken to not break any queries. *

          * A {@link io.realm.Realm} is unordered, which means that there is no guarantee that querying a Realm will return the @@ -1238,9 +1238,9 @@ public RealmQuery endsWith(String fieldName, String value) { * * @param fieldName the field to compare. * @param value the substring. - * @param casing how to handle casing. Setting this to {@link Case#INSENSITIVE} only works for Latin-1 characters. + * @param casing how to handle casing. Setting this to {@link Case#INSENSITIVE} only works for Latin-1 characters. * @return the query object. - * @throws java.lang.IllegalArgumentException One or more arguments do not match class or field type. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery endsWith(String fieldName, String value, Case casing) { long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); @@ -1248,6 +1248,44 @@ public RealmQuery endsWith(String fieldName, String value, Case casing) { return this; } + // Like + + /** + * Condition that the value of field matches with the specified substring, with wildcards: + *

            + *
          • '*' matches [0, n] unicode chars
          • + *
          • '?' matches a single unicode char.
          • + *
          + * + * @param fieldName the field to compare. + * @param value the wildcard string. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery like(String fieldName, String value) { + return like(fieldName, value, Case.SENSITIVE); + } + + /** + * Condition that the value of field matches with the specified substring, with wildcards: + *
            + *
          • '*' matches [0, n] unicode chars
          • + *
          • '?' matches a single unicode char.
          • + *
          + * + * @param fieldName the field to compare. + * @param value the wildcard string. + * @param casing how to handle casing. Setting this to {@link Case#INSENSITIVE} only works for Latin-1 characters. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery like(String fieldName, String value, Case casing) { + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + this.query.like(columnIndices, value, casing); + return this; + } + + // Grouping /** @@ -1890,7 +1928,7 @@ public RealmResults findAllSortedAsync(String fieldName) { * * @param fieldNames an array of field names to sort by. * @param sortOrders how to sort the field names. - * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero + * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero * objects is returned. * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index d8c6433681..05cdae38d5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -386,6 +386,18 @@ public TableQuery endsWith(long columnIndices[], String value) { return this; } + public TableQuery like(long columnIndices[], String value, Case caseSensitive) { + nativeLike(nativePtr, columnIndices, value, caseSensitive.getValue()); + queryValidated = false; + return this; + } + + public TableQuery like(long columnIndices[], String value) { + nativeLike(nativePtr, columnIndices, value, true); + queryValidated = false; + return this; + } + public TableQuery contains(long columnIndices[], String value, Case caseSensitive) { nativeContains(nativePtr, columnIndices, value, caseSensitive.getValue()); queryValidated = false; @@ -752,6 +764,7 @@ private void throwImmutable() { private native void nativeNotEqual(long nativeQueryPtr, long columnIndex[], String value, boolean caseSensitive); private native void nativeBeginsWith(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); private native void nativeEndsWith(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); + private native void nativeLike(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); private native void nativeContains(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); private native void nativeIsEmpty(long nativePtr, long[] columnIndices); private native long nativeFind(long nativeQueryPtr, long fromTableRow); From 4b778c7a3d764de2f983f61d333aea4659ae5910 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Thu, 12 Jan 2017 11:48:35 -0800 Subject: [PATCH 0413/2110] Convert to annotations for findbugs exceptions * Clean up code in SyncUser to eliminate FB exception * Add usage for the config files, to the top level README.md * Note UT findbugs failures --- README.md | 5 + realm/config/findbugs/findbugs-filter.xml | 116 +-------- realm/config/studio/Realm-style.xml | 238 ++++++++++++++++++ realm/config/studio/Realm_lint.xml | 136 ++++++++++ realm/realm-library/build.gradle | 2 +- .../main/java/io/realm/HandlerController.java | 2 + .../java/io/realm/SyncManager.java | 2 + .../objectServer/java/io/realm/SyncUser.java | 32 ++- .../realm/permissions/PermissionChange.java | 3 + 9 files changed, 418 insertions(+), 118 deletions(-) create mode 100644 realm/config/studio/Realm-style.xml create mode 100644 realm/config/studio/Realm_lint.xml diff --git a/README.md b/README.md index ee521543c4..7e985ef5c4 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,11 @@ This project adheres to the [Contributor Covenant Code of Conduct](https://realm By participating, you are expected to uphold this code. Please report unacceptable behavior to [info@realm.io](mailto:info@realm.io). +The directory `realm/config/studio` contains lint and style files recommended for project code. +Import them from Android Studio with Android Studio > Preferences... > Code Style > Manage... > Import, +or Android Studio > Preferences... > Inspections > Manage... > Import. Once imported select the +style/lint in the drop-down to the left of the Manage... button. + ## License Realm Java is published under the Apache 2.0 license. diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index 10553d8870..e28c4a627c 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -1,112 +1,18 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + diff --git a/realm/config/studio/Realm-style.xml b/realm/config/studio/Realm-style.xml new file mode 100644 index 0000000000..6f11f49147 --- /dev/null +++ b/realm/config/studio/Realm-style.xml @@ -0,0 +1,238 @@ + + + + diff --git a/realm/config/studio/Realm_lint.xml b/realm/config/studio/Realm_lint.xml new file mode 100644 index 0000000000..2498bef0ce --- /dev/null +++ b/realm/config/studio/Realm_lint.xml @@ -0,0 +1,136 @@ + + + + diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 9fbea1a4bc..5e1e6be367 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -126,7 +126,7 @@ repositories { dependencies { objectServerAnnotationProcessor project(':realm-annotations-processor') provided 'io.reactivex:rxjava:1.1.0' - provided 'net.sourceforge.findbugs:annotations:1.3.2' + provided 'com.google.code.findbugs:findbugs-annotations:3.0.1' compile "io.realm:realm-annotations:${version}" compile 'com.getkeepsafe.relinker:relinker:1.2.2' objectServerCompile 'com.squareup.okhttp3:okhttp:3.4.1' diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java index 071dcc6b77..5f76391b8a 100644 --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java @@ -33,6 +33,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.HandlerControllerConstants; import io.realm.internal.IdentitySet; import io.realm.internal.RealmObjectProxy; @@ -602,6 +603,7 @@ private void notifyAsyncTransactionCallbacks() { } } + @SuppressFBWarnings("RC_REF_COMPARISON_BAD_PRACTICE_BOOLEAN") private void completedAsyncRealmObject(QueryUpdateTask.Result result) { Set> updatedRowKey = result.updatedRow.keySet(); if (updatedRowKey.size() > 0) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index f3fe17bc42..3406054f72 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -21,6 +21,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.annotations.Beta; import io.realm.internal.Keep; import io.realm.internal.network.AuthenticationServer; @@ -43,6 +44,7 @@ */ @Keep @Beta +@SuppressFBWarnings("MS_CANNOT_BE_FINAL") public class SyncManager { /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 6486bf3c5e..3f89f5f6b0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -44,7 +44,6 @@ import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; -import io.realm.permissions.PermissionChange; import io.realm.permissions.PermissionModule; /** @@ -62,7 +61,25 @@ @Beta public class SyncUser { - private SyncConfiguration managementRealmConfig; + private static class ManagementConfig { + private SyncConfiguration managementRealmConfig; + + synchronized SyncConfiguration initAndGetManagementRealmConfig( + ObjectServerUser syncUser, SyncUser user) { + if (managementRealmConfig == null) { + managementRealmConfig = new SyncConfiguration.Builder( + user, getManagementRealmUrl(syncUser.getAuthenticationUrl())) + .modules(new PermissionModule()) + .build(); + } + + return managementRealmConfig; + } + } + + + private final ManagementConfig managementConfig = new ManagementConfig(); + private final ObjectServerUser syncUser; private SyncUser(ObjectServerUser user) { @@ -365,16 +382,7 @@ public String getAccessToken() { * @see How to control permissions */ public Realm getManagementRealm() { - synchronized (this) { - if (managementRealmConfig == null) { - String managementUrl = getManagementRealmUrl(syncUser.getAuthenticationUrl()); - managementRealmConfig = new SyncConfiguration.Builder(this, managementUrl) - .modules(new PermissionModule()) - .build(); - } - } - - return Realm.getInstance(managementRealmConfig); + return Realm.getInstance(managementConfig.initAndGetManagementRealmConfig(syncUser, this)); } // Creates the URL to the permission Realm based on the authentication URL. diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java index e8d402d259..19f88c840f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionChange.java @@ -18,6 +18,7 @@ import java.util.Date; import java.util.UUID; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; @@ -78,10 +79,12 @@ public String getId() { return id; } + @SuppressFBWarnings("EI_EXPOSE_REP") public Date getCreatedAt() { return createdAt; } + @SuppressFBWarnings("EI_EXPOSE_REP") public Date getUpdatedAt() { return updatedAt; } From e5d6a7f4bd0d6d49daeb5f0b0b0f5728b0d4d497 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 13 Jan 2017 06:13:06 +0900 Subject: [PATCH 0414/2110] Activated Realm's annotation processor on connectedTest when the project is using kapt (#4008). (#4022) --- CHANGELOG.md | 1 + gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5703615f1..62a534d227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug fixes * Fixed native memory leak setting the value of a primary key (#3993). +* Activated Realm's annotation processor on connectedTest when the project is using kapt (#4008). ### Internal diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 0b28d9a15c..38829e6538 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -66,6 +66,8 @@ class Realm implements Plugin { } else if (isKotlinProject && !preferAptOnKotlinProject) { project.dependencies.add("kapt", "io.realm:realm-annotations:${Version.VERSION}") project.dependencies.add("kapt", "io.realm:realm-annotations-processor:${Version.VERSION}") + project.dependencies.add("kaptAndroidTest", "io.realm:realm-annotations:${Version.VERSION}") + project.dependencies.add("kaptAndroidTest", "io.realm:realm-annotations-processor:${Version.VERSION}") } else { assert hasAnnotationProcessorConfiguration project.dependencies.add("annotationProcessor", "io.realm:realm-annotations:${Version.VERSION}") From 9d96c4e0c9c0f2589d8f331fca4d9393f238e29e Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 13 Jan 2017 09:16:04 +0100 Subject: [PATCH 0415/2110] Upgrading Realm Sync to v1.0.0-BETA-7.1 (#4033) --- CHANGELOG.md | 2 +- dependencies.list | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62a534d227..41cb4d2919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ ### Internal -* Updated Realm Sync to 1.0.0-BETA-7.0. +* Updated Realm Sync to 1.0.0-BETA-7.1. ## 2.2.2 diff --git a/dependencies.list b/dependencies.list index ebe1c39f5f..769de14b97 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.0-BETA-7.0 -REALM_SYNC_SHA256=76dcf2052681aa21992489ca9b62172d9cb07f4def5e94011975df67bd552276 +REALM_SYNC_VERSION=1.0.0-BETA-7.1 +REALM_SYNC_SHA256=5412b42d96dd525af3ec6a0b7943330c43beae2c663b32376b77278ea54e34a5 # Object Server Release used by Integration tests # https://packagecloud.io/realm/realm?filter=debs From 7d0fe58ba740faa76a7a002c68476e03908870c5 Mon Sep 17 00:00:00 2001 From: Realm CI Date: Fri, 13 Jan 2017 03:55:35 -0800 Subject: [PATCH 0416/2110] Fix merge from 9d96c4 to master (#4041) --- CHANGELOG.md | 3 ++- dependencies.list | 4 ++-- gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9655227d..887cdc03f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,11 @@ ### Bug fixes * Fixed native memory leak setting the value of a primary key (#3993). +* Activated Realm's annotation processor on connectedTest when the project is using kapt (#4008). ### Internal -* Updated Realm Sync to 1.0.0-BETA-7.0. +* Updated Realm Sync to 1.0.0-BETA-7.1. ## 2.2.2 diff --git a/dependencies.list b/dependencies.list index ebe1c39f5f..769de14b97 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.0-BETA-7.0 -REALM_SYNC_SHA256=76dcf2052681aa21992489ca9b62172d9cb07f4def5e94011975df67bd552276 +REALM_SYNC_VERSION=1.0.0-BETA-7.1 +REALM_SYNC_SHA256=5412b42d96dd525af3ec6a0b7943330c43beae2c663b32376b77278ea54e34a5 # Object Server Release used by Integration tests # https://packagecloud.io/realm/realm?filter=debs diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 0b28d9a15c..38829e6538 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -66,6 +66,8 @@ class Realm implements Plugin { } else if (isKotlinProject && !preferAptOnKotlinProject) { project.dependencies.add("kapt", "io.realm:realm-annotations:${Version.VERSION}") project.dependencies.add("kapt", "io.realm:realm-annotations-processor:${Version.VERSION}") + project.dependencies.add("kaptAndroidTest", "io.realm:realm-annotations:${Version.VERSION}") + project.dependencies.add("kaptAndroidTest", "io.realm:realm-annotations-processor:${Version.VERSION}") } else { assert hasAnnotationProcessorConfiguration project.dependencies.add("annotationProcessor", "io.realm:realm-annotations:${Version.VERSION}") From 71855ff62aa2ccfe9117b78f6bdc80400a30fff1 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 13 Jan 2017 13:00:48 +0100 Subject: [PATCH 0417/2110] Ignore exceptions (#4028) * Ignore exceptions --- CHANGELOG.md | 4 +++ .../java/io/realm/SyncSession.java | 5 +++- .../objectServer/java/io/realm/SyncUser.java | 8 +++-- .../java/io/realm/objectserver/AuthTests.java | 30 ++++++++++++++++++- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41cb4d2919..75ad9aed55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ * Fixed native memory leak setting the value of a primary key (#3993). * Activated Realm's annotation processor on connectedTest when the project is using kapt (#4008). +### Object Server API Changes (In Beta) + +* Exceptions thrown in error handlers are ignored but logged (#3559). + ### Internal * Updated Realm Sync to 1.0.0-BETA-7.1. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 0aabe1e5d9..046295c876 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -107,7 +107,10 @@ protected void finalize() throws Throwable { */ public interface ErrorHandler { /** - * Callback for errors on a session object. + * Callback for errors on a session object. It is not allowed to throw an exception inside an error handler. + * If the operations in an error handler can throw, it is safer to catch any exception in the error handler. + * When an exception is thrown in the error handler, the occurrence will be logged and the exception + * will be ignored. * * @param session {@link SyncSession} this error happened on. * @param error type of error. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 65130e75c3..da66114aa2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -44,7 +44,6 @@ import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; -import io.realm.permissions.PermissionChange; import io.realm.permissions.PermissionModule; /** @@ -203,7 +202,12 @@ private void postError(final ObjectServerError error) { handler.post(new Runnable() { @Override public void run() { - callback.onError(error); + try { + callback.onError(error); + } catch (Exception e) { + RealmLog.info("onError has thrown an exception but is ignoring it: %s", + Util.getStackTrace(e)); + } } }); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index ccf997a3b8..bf6a0fc4df 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -9,10 +9,10 @@ import org.junit.Test; import org.junit.runner.RunWith; -import io.realm.SyncCredentials; import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.Realm; +import io.realm.SyncCredentials; import io.realm.SyncUser; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.HttpUtils; @@ -66,4 +66,32 @@ public void onError(ObjectServerError error) { } }); } + + // The error handler throws an exception but it is ignored (but logged). That means, this test should not + // pass and not be stopped by an IllegalArgumentException. + @Test + @RunTestInLooperThread + public void loginAsync_errorHandlerThrows() { + SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser user) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.INVALID_CREDENTIALS, error.getErrorCode()); + throw new IllegalArgumentException("BOOM"); + } + }); + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + fail(); + } + looperThread.testComplete(); + } } From 76e0a396a3e91e43cc48564d7c3d30f9f3130429 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 13 Jan 2017 20:44:29 +0800 Subject: [PATCH 0418/2110] More docs for RealmNotifier Also added one more test case for it. --- .../io/realm/internal/RealmNotifierTests.java | 48 ++++++++++++---- .../src/main/cpp/java_binding_context.cpp | 12 ++-- .../java/io/realm/internal/RealmNotifier.java | 55 ++++++++++++------- .../android/AndroidRealmNotifier.java | 5 ++ 4 files changed, 84 insertions(+), 36 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 88c29e424f..edae90e47a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -24,6 +24,8 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.atomic.AtomicInteger; + import io.realm.RealmChangeListener; import io.realm.RealmConfiguration; import io.realm.internal.android.AndroidRealmNotifier; @@ -31,6 +33,7 @@ import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -114,27 +117,48 @@ public void onChange(SharedRealm sharedRealm) { sharedRealm.commitTransaction(); } + private void makeRemoteChanges(final RealmConfiguration config) { + new Thread(new Runnable() { + @Override + public void run() { + SharedRealm sharedRealm = getSharedRealm(config); + sharedRealm.beginTransaction(); + sharedRealm.commitTransaction(); + sharedRealm.close(); + } + }).start(); + } + @Test @RunTestInLooperThread public void addChangeListener_byRemoteChanges() { + // To catch https://github.com/realm/realm-java/pull/4037 CI failure. + // In this case, object store should not send more than 100 notifications. + final int TIMES = 100; + final AtomicInteger commitCounter = new AtomicInteger(0); + final AtomicInteger listenerCounter = new AtomicInteger(0); + + looperThread.realm.close(); + SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { - // FIXME: Enable this after https://github.com/realm/realm-object-store/pull/318 fixed - //sharedRealm.close(); - looperThread.testComplete(); + int commits = commitCounter.get(); + int listenerCount = listenerCounter.addAndGet(1); + assertEquals(commits, listenerCount); + if (commits == TIMES) { + // FIXME: Enable this after https://github.com/realm/realm-object-store/pull/318 fixed + //sharedRealm.close(); + looperThread.testComplete(); + } else { + makeRemoteChanges(looperThread.realmConfiguration); + commitCounter.getAndIncrement(); + } } }); - new Thread(new Runnable() { - @Override - public void run() { - SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); - sharedRealm.beginTransaction(); - sharedRealm.commitTransaction(); - sharedRealm.close(); - } - }).start(); + makeRemoteChanges(looperThread.realmConfiguration); + commitCounter.getAndIncrement(); } @Test diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index 883745479f..525c70fc36 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -25,13 +25,13 @@ using namespace realm::jni_util; void JavaBindingContext::before_notify() { - if (JniUtils::get_env()->ExceptionCheck()) return; + if (JniUtils::get_env()->ExceptionCheck()) { + return; + } if (m_java_notifier) { m_java_notifier.call_with_local_ref([&] (JNIEnv* env, jobject notifier_obj) { // Method IDs from RealmNotifier implementation. Cache them as member vars. - static JavaMethod notify_by_other_method(env, - notifier_obj, - "changesAvailable", "()V"); + static JavaMethod notify_by_other_method(env, notifier_obj, "beforeNotify", "()V"); env->CallVoidMethod(notifier_obj, notify_by_other_method); }); } @@ -43,7 +43,9 @@ void JavaBindingContext::did_change(std::vector c { auto env = JniUtils::get_env(); - if (env->ExceptionCheck()) return; + if (JniUtils::get_env()->ExceptionCheck()) { + return; + } if (version_changed) { m_java_notifier.call_with_local_ref(env, [&] (JNIEnv*, jobject notifier_obj) { static JavaMethod realm_notifier_did_change_method(env, notifier_obj, "didChange", "()V"); diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index eda0e87430..96c0a7e678 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -29,7 +29,22 @@ @Keep public abstract class RealmNotifier implements Closeable { - private SharedRealm sharedRealm; +// Calling sequences for a remote commit +// |-------------------------------+--------------+-----------------------------------| +// | Thread A | Thread B | Daemon Thread | +// |-------------------------------+--------------+-----------------------------------| +// | | Make changes | | +// |-------------------------------+--------------+-----------------------------------| +// | | | epoll callback and notify ALooper | +// |-------------------------------+--------------+-----------------------------------| +// | ALooper callback | | | +// | BindingContext::before_notify | | | +// | RealmNotifier.beforeNotify | | | +// | BindingContext::did_change | | | +// | RealmNotifier.didChange | | | +// | process_available_async | | | +// | Collection listeners | | | +// |-------------------------------+--------------+-----------------------------------| private static class RealmObserverPair extends ObserverPairList.ObserverPair> { public RealmObserverPair(T observer, RealmChangeListener listener) { @@ -57,6 +72,7 @@ protected RealmNotifier(SharedRealm sharedRealm) { this.sharedRealm = sharedRealm; } + private SharedRealm sharedRealm; // TODO: The only reason we have this is that async transactions is not supported by OS yet. And OS is using ALopper // which will be using a different message queue from which java is using to deliver remote Realm changes message. // We need a way to deliver the async transaction onSuccess callback to the caller thread after the caller Realm @@ -66,22 +82,13 @@ protected RealmNotifier(SharedRealm sharedRealm) { // This list is NOT supposed to be thread safe! private List transactionCallbacks = new ArrayList(); - // This is called by OS when other thread/process changes the Realm. - // This is getting called on the same thread which created the Realm. - // |---------------------------------------------------------------+--------------+------------------------------------------------| - // | Thread A | Thread B | Daemon Thread | - // |---------------------------------------------------------------+--------------+------------------------------------------------| - // | | Make changes | | - // | | | Detect and notify thread A through JNI ALooper | - // | Call OS's Realm::notify() from OS's ALooper callback | | | - // | Realm::notify() calls JavaBindingContext:change_available() | | | - // | change_available calls into this method to send REALM_CHANGED | | | - // |---------------------------------------------------------------+--------------+------------------------------------------------| - /** - * This is called in Realm Object Store's JavaBindingContext::changes_available. - * This is getting called on the same thread which created this Realm when the same Realm file has been changed by - * other thread. The changes on the same thread should not trigger this call. - */ + + // Called from JavaBindingContext::did_change. + // This will be called in the caller thread when: + // - A committed remote transaction, called from changed event handler. + // - A committed remote transaction, called directly from refresh call. + // - A committed local transaction, called directly from commitTransaction instead of next event. + // loop. // Package protected to avoid finding class by name in JNI. @SuppressWarnings("unused") // called from java_binding_context.cpp void didChange() { @@ -92,9 +99,14 @@ void didChange() { transactionCallbacks.clear(); } - @SuppressWarnings("unused") // called from java_binding_context.cpp + // Called from JavaBindingContext::before_notify. + // This will be called in the caller thread when: + // 1. Get changed notification by this/other Realm instances. + // 2. SharedRealm::refresh called. + // In both cases, this will be called before the any other callbacks (changed callbacks, async query callbacks.). // Package protected to avoid finding class by name in JNI. - void changesAvailable() { + @SuppressWarnings("unused") + void beforeNotify() { // For the stable iteration. sharedRealm.reattachCollections(); } @@ -131,6 +143,11 @@ public void addTransactionCallback(Runnable runnable) { transactionCallbacks.add(runnable); } + /** + * Post a runnable to be executed at the very next event loop. Used by current stable Collection iterator. + * + * @param runnable to be executed at the next event loop. + */ public abstract void postAtFrontOfQueue(Runnable runnable); /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java index 0f6ea5f8ad..2d74b1f0d2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java @@ -4,9 +4,14 @@ import android.os.Looper; import io.realm.internal.Capabilities; +import io.realm.internal.Keep; import io.realm.internal.RealmNotifier; import io.realm.internal.SharedRealm; +/** + * {@link RealmNotifier} implementation for Android. + */ +@Keep public class AndroidRealmNotifier extends RealmNotifier { private Handler handler; From a0dff1d395b4fc6d913f17330868c86fd841741f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 13 Jan 2017 21:04:32 +0800 Subject: [PATCH 0419/2110] Clean up Collection related code --- .../io/realm/internal/CollectionTests.java | 7 ----- .../main/cpp/io_realm_internal_Collection.cpp | 26 +++++-------------- .../java/io/realm/internal/Collection.java | 12 +++------ 3 files changed, 10 insertions(+), 35 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 1a897347e3..05ad2e55ab 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -124,13 +124,6 @@ private void addRow(SharedRealm sharedRealm) { sharedRealm.commitTransaction(); } - private void removeRow(SharedRealm sharedRealm) { - sharedRealm.beginTransaction(); - table = sharedRealm.getTable("test_table"); - table.remove(0); - sharedRealm.commitTransaction(); - } - @Test public void constructor_withDistinct() { SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table, "firstName"); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 9c6ca14597..27f727b293 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -17,15 +17,16 @@ #include #include "io_realm_internal_Collection.h" -#include +#include #include #include -#include "util.hpp" #include "java_sort_descriptor.hpp" -#include "jni_util/java_method.hpp" +#include "util.hpp" + #include "jni_util/java_global_weak_ref.hpp" +#include "jni_util/java_method.hpp" using namespace realm; using namespace realm::jni_util; @@ -126,18 +127,6 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - auto wrapper = reinterpret_cast(native_ptr); - auto snapshot = wrapper->get_original_results(); - return reinterpret_cast(new Results(snapshot)); - } CATCH_STD() - return reinterpret_cast(nullptr); -} - JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeContains(JNIEnv *env, jclass, jlong native_ptr, jlong native_row_ptr) { @@ -308,11 +297,12 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; - //if (!wrapper->is_detached()) { + // It should have been reattached before the callback. + REALM_ASSERT_DEBUG(!wrapper->is_detached()); + wrapper->m_collection_weak_ref.call_with_local_ref(env, [&] (JNIEnv* local_env, jobject collection_obj) { local_env->CallVoidMethod(collection_obj, notify_change_listeners, changes.empty()); }); - //} }; wrapper->m_notification_token = wrapper->get_original_results().add_notification_callback(cb); @@ -419,7 +409,6 @@ Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv *env, jclass, jlong na return JNI_TRUE; } } CATCH_STD() - return JNI_FALSE; } @@ -437,7 +426,6 @@ Java_io_realm_internal_Collection_nativeDeleteFirst(JNIEnv *env, jclass, jlong n return JNI_TRUE; } } CATCH_STD() - return JNI_FALSE; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index bf0e12081f..6e54703fca 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -26,9 +26,9 @@ /** * Java wrapper of OS Results class. - * It is supposed to be the backend of binding's query results, link list and back links. + * It is the backend of binding's query results, link list and back links. */ -@KeepMember +@Keep public class Collection implements NativeObject { private class CollectionObserverPair extends ObserverPairList.ObserverPair> { @@ -93,7 +93,6 @@ public void onCalled(CollectionObserverPair pair, Object observer) { public static final byte AGGREGATE_FUNCTION_AVERAGE = 3; @SuppressWarnings("WeakerAccess") public static final byte AGGREGATE_FUNCTION_SUM = 4; - public enum Aggregate { MINIMUM(AGGREGATE_FUNCTION_MINIMUM), MAXIMUM(AGGREGATE_FUNCTION_MAXIMUM), @@ -121,7 +120,6 @@ public byte getValue() { public static final byte MODE_LINKVIEW = 3; @SuppressWarnings("WeakerAccess") public static final byte MODE_TABLEVIEW = 4; - public enum Mode { EMPTY, // Backed by nothing (for missing tables) TABLE, // Backed directly by a Table @@ -298,7 +296,6 @@ public boolean isValid() { } // Called by JNI - @KeepMember @SuppressWarnings("unused") private void notifyChangeListeners(boolean emptyChanges) { if (isDetached()) return; @@ -309,8 +306,7 @@ public Mode getMode() { return Mode.getByValue(nativeGetMode(nativePtr)); } - // Turns this collection to be backed by a snapshot results. - // A snapshot results will never be auto-updated. + // Turns this collection to be backed by a snapshot results. A snapshot results will never be auto-updated. void detach() { nativeDetach(nativePtr); } @@ -330,8 +326,6 @@ boolean isDetached() { private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, SortDescriptor sortDesc, SortDescriptor distinctDesc); - @SuppressWarnings("unused") // Not used for now - private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); private static native long nativeFirstRow(long nativePtr); private static native long nativeLastRow(long nativePtr); From cd982aaba66685a0c878d7c4005d762f5a5a637d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 13 Jan 2017 21:24:58 +0800 Subject: [PATCH 0420/2110] Fix lots of minor issues --- .../java/io/realm/internal/SortDescriptorTests.java | 6 +++--- .../src/main/cpp/io_realm_internal_SharedRealm.cpp | 4 ++-- .../realm-library/src/main/java/io/realm/BaseRealm.java | 2 +- .../src/main/java/io/realm/RealmObject.java | 9 ++++----- .../realm-library/src/main/java/io/realm/RealmQuery.java | 6 +++--- .../src/main/java/io/realm/RealmResults.java | 7 +++---- .../src/main/java/io/realm/internal/Capabilities.java | 2 +- 7 files changed, 17 insertions(+), 19 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java index 2fbff84510..3b82038bdb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java @@ -126,7 +126,7 @@ public void getInstanceForDistinct_shouldThrowOnInvalidField() { for (RealmFieldType type : RealmFieldType.values()) { if (!SortDescriptor.validFieldTypesForDistinct.contains(type) && type != RealmFieldType.UNSUPPORTED_DATE && - type != RealmFieldType.UNSUPPORTED_TABLE&& + type != RealmFieldType.UNSUPPORTED_TABLE && type != RealmFieldType.UNSUPPORTED_MIXED) { if (type == RealmFieldType.LIST || type == RealmFieldType.OBJECT) { table.addColumnLink(type, type.name(), table); @@ -228,8 +228,8 @@ public void getInstanceForSort_numOfFeildsAndSortOrdersNotMatch() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Number of fields and sort orders do not match."); - SortDescriptor.getInstanceForSort(table, new String[] { - stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING}); + SortDescriptor.getInstanceForSort(table, + new String[] { stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING}); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 17843218ed..188adf856f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -449,7 +449,7 @@ Java_io_realm_internal_SharedRealm_nativeSetAutoRefresh(JNIEnv *env, jclass, jlo TR_ENTER_PTR(shared_realm_ptr) try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - shared_realm->set_auto_refresh(enabled); + shared_realm->set_auto_refresh(to_bool(enabled)); } CATCH_STD() } @@ -459,7 +459,7 @@ Java_io_realm_internal_SharedRealm_nativeIsAutoRefresh(JNIEnv *env, jclass, jlon TR_ENTER_PTR(shared_realm_ptr) try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - return static_cast(shared_realm->auto_refresh()); + return to_jbool(shared_realm->auto_refresh()); } CATCH_STD() return JNI_FALSE; } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 66298e072c..49ebc4b91c 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -59,7 +59,7 @@ abstract class BaseRealm implements Closeable { "This Realm instance has already been closed, making it unusable."; private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; - private static final String LISTENER_NOT_ALLOWED_MESSAGE = "Listeners cannot be used on current thread."; + static final String LISTENER_NOT_ALLOWED_MESSAGE = "Listeners cannot be used on current thread."; volatile static Context applicationContext; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 487a6eabc1..d2951115a7 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -66,7 +66,6 @@ @RealmClass public abstract class RealmObject implements RealmModel { - private static final String LISTENER_NOT_ALLOWED_MESSAGE = "Listeners cannot be used on current thread."; /** * Deletes the object from the Realm it is currently associated to. @@ -159,7 +158,7 @@ public static boolean isValid(E object) { * didn't find any object matching the query parameters. In this case the {@link RealmObject} will * become a "null" object. * - * "Null" objects represents {@code null}. An exception is throw if any accessor is called, so it is important to also + * "Null" objects represent {@code null}. An exception is thrown if any accessor is called, so it is important to also * check {@link #isValid()} before calling any methods. A common pattern is: * *
          @@ -348,7 +347,7 @@ public static  void addChangeListener(E object, RealmChang
                       RealmObjectProxy proxy = (RealmObjectProxy) object;
                       BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm();
                       realm.checkIfValid();
          -            realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE);
          +            realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE);
                       //noinspection unchecked
                       proxy.realmGet$proxyState().addChangeListener(listener);
                   } else {
          @@ -388,7 +387,7 @@ public static  void removeChangeListener(E object, RealmCh
                       RealmObjectProxy proxy = (RealmObjectProxy) object;
                       BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm();
                       realm.checkIfValid();
          -            realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE);
          +            realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE);
                       //noinspection unchecked
                       proxy.realmGet$proxyState().removeChangeListener(listener);
                   } else {
          @@ -414,7 +413,7 @@ public static  void removeChangeListeners(E object) {
                       RealmObjectProxy proxy = (RealmObjectProxy) object;
                       BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm();
                       realm.checkIfValid();
          -            realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE);
          +            realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE);
                       proxy.realmGet$proxyState().removeAllChangeListeners();
                   } else {
                       throw new IllegalArgumentException("Cannot remove listeners from this unmanaged RealmObject (created outside of Realm)");
          diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java
          index 6ff652cd88..e24cdc72f9 100644
          --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java
          +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java
          @@ -1548,7 +1548,7 @@ public RealmResults findAllSorted(String fieldName, Sort sortOrder) {
           
               /**
                * Similar to {@link #findAllSorted(String, Sort)} but runs asynchronously on a worker thread
          -     * (Need a Realm opened from a looper thread to work).
          +     * (need a Realm opened from a looper thread to work).
                *
                * @return immediately an empty {@link RealmResults}. Users need to register a listener
                *         {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes.
          @@ -1579,7 +1579,7 @@ public RealmResults findAllSorted(String fieldName) {
               }
           
               /**
          -     * Similar to {@link #findAllSorted(String)} but runs asynchronously on a worker thread
          +     * Similar to {@link #findAllSorted(String)} but runs asynchronously on a worker thread.
                * This method is only available from a Looper thread.
                *
                * @return immediately an empty {@link RealmResults}. Users need to register a listener
          @@ -1614,7 +1614,7 @@ private boolean isDynamicQuery() {
               }
           
               /**
          -     * Similar to {@link #findAllSorted(String[], Sort[])} but runs asynchronously
          +     * Similar to {@link #findAllSorted(String[], Sort[])} but runs asynchronously.
                * from a worker thread.
                * This method is only available from a Looper thread.
                *
          diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java
          index b2aef35caa..ea19abcf7a 100644
          --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java
          +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java
          @@ -66,7 +66,6 @@
           public class RealmResults extends AbstractList implements OrderedRealmCollection {
           
               private final static String NOT_SUPPORTED_MESSAGE = "This method is not supported by RealmResults.";
          -    private static final String LISTENER_NOT_ALLOWED_MESSAGE = "Listeners cannot be used on current thread.";
           
               final BaseRealm realm;
               Class classSpec;   // Return type
          @@ -759,7 +758,7 @@ public void addChangeListener(RealmChangeListener> listener) {
                       throw new IllegalArgumentException("Listener should not be null");
                   }
                   realm.checkIfValid();
          -        realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE);
          +        realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE);
                   collection.addListener(this, listener);
               }
           
          @@ -775,7 +774,7 @@ public void removeChangeListener(RealmChangeListener listener) {
                       throw new IllegalArgumentException("Listener should not be null");
                   }
                   realm.checkIfValid();
          -        realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE);
          +        realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE);
                   collection.removeListener(this, listener);
               }
           
          @@ -784,7 +783,7 @@ public void removeChangeListener(RealmChangeListener listener) {
                */
               public void removeChangeListeners() {
                   realm.checkIfValid();
          -        realm.sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE);
          +        realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE);
                   collection.removeAllListeners();
               }
           
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java
          index abfff3dd8b..6f89710304 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java
          @@ -17,7 +17,7 @@
           package io.realm.internal;
           
           /**
          - * To describe what does the Realm instance can do associated with the thread it is created on.
          + * To describe what the Realm instance can do associated with the thread it is created on.
            * The capabilities are determined when the Realm gets created. This interface could be called from another thread which
            * is different from where the Realm is created on.
            */
          
          From 1fcb027babdfa883b0f7cb7907b867a9579a09e6 Mon Sep 17 00:00:00 2001
          From: Nabil Hachicha 
          Date: Sat, 14 Jan 2017 16:04:17 +0000
          Subject: [PATCH 0421/2110] Nh/fixes token renew (#4040)
          
          * fixes #4039 and fixes #4038
          ---
           CHANGELOG.md                                      |  1 +
           .../src/main/cpp/objectserver_shared.hpp          |  2 +-
           .../realm/internal/objectserver/BoundState.java   | 15 +++++++++++++++
           .../objectserver/ObjectServerSession.java         |  6 +++++-
           .../internal/objectserver/ObjectServerUser.java   | 12 ++++++++----
           5 files changed, 30 insertions(+), 6 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 75ad9aed55..1dee611f58 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -4,6 +4,7 @@
           
           * Fixed native memory leak setting the value of a primary key (#3993).
           * Activated Realm's annotation processor on connectedTest when the project is using kapt (#4008).
          +* Fixed bug, preventing Sync client to renew the access token (#4038) (#4039).
           
           ### Object Server API Changes (In Beta)
           
          diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp
          index bf079cb824..8b41842446 100644
          --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp
          +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp
          @@ -62,7 +62,7 @@ class JniSession {
                       }
                   };
                   auto error_handler = [weak_session_ref](std::error_code error_code, bool is_fatal, const std::string message) {
          -            if (error_code.category() != realm::sync::protocol_error_category() ||
          +            if (error_code.category() != realm::sync::protocol_error_category() &&
                               error_code.category() != realm::sync::client_error_category()) {
                           // FIXME: Consider below when moving to the OS sync manager.
                           // Ignore this error since it may cause exceptions in java ErrorCode.fromInt(). Throwing exception there
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java
          index a941440867..976bada982 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java
          @@ -52,7 +52,22 @@ public void onError(ObjectServerError error) {
                   // If a Realms access token has expired, trigger a rebind. If the user is still valid it will automatically
                   // refresh it.
                   if (error.getErrorCode() == ErrorCode.TOKEN_EXPIRED) {
          +            //  the server can send a 202 (expired access token) even if the client
          +            //  still consider this token to be valid (based on timestamps for example)
          +            //
          +            //  this may cause the server to send a fatal error (203 bad refresh) if we try to bind
          +            //  the session with this token. To be safe we remove the token that has been considered by the
          +            //  the server to be invalid.
          +
          +            // stop the session to avoid sending a bind to the server which will cause it to return
          +            // a fatal 203 (bad refresh)
          +            session.stopNativeSession();
          +            session.removeAccessToken();
          +
          +            // Create a new session & bind it
          +            session.createNativeSession();
                       gotoNextState(SessionState.BINDING);
          +            
                   } else {
                       switch (error.getCategory()) {
                           case FATAL: gotoNextState(SessionState.STOPPED); break;
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java
          index a0e4802df3..3d78d24aa5 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java
          @@ -23,10 +23,10 @@
           import io.realm.ErrorCode;
           import io.realm.ObjectServerError;
           import io.realm.RealmAsyncTask;
          -import io.realm.SyncSession;
           import io.realm.SessionState;
           import io.realm.SyncConfiguration;
           import io.realm.SyncManager;
          +import io.realm.SyncSession;
           import io.realm.SyncUser;
           import io.realm.internal.KeepMember;
           import io.realm.internal.async.RealmAsyncTaskImpl;
          @@ -240,6 +240,10 @@ void stopNativeSession() {
                   }
               }
           
          +    void removeAccessToken() {
          +        user.removeAccessToken(configuration.getServerUrl());
          +    }
          +
               // Bind with proper access tokens
               // Access tokens are presumed to be present and valid at this point
               void bindWithTokens() {
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java
          index f74c9dd0a9..f6caa7fe7d 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java
          @@ -28,8 +28,8 @@
           import java.util.List;
           import java.util.Map;
           
          -import io.realm.SyncSession;
           import io.realm.SyncConfiguration;
          +import io.realm.SyncSession;
           
           /**
            * Internal representation of a user on the Realm Object Server.
          @@ -54,7 +54,7 @@ public ObjectServerUser(Token refreshToken, URL authenticationUrl) {
                   this.loggedIn = true;
               }
           
          -    public void setRefreshToken(final Token refreshToken) {
          +    private void setRefreshToken(final Token refreshToken) {
                   this.refreshToken = refreshToken; // Replace any existing token. TODO re-save the user with latest token.
               }
           
          @@ -64,7 +64,7 @@ public void setRefreshToken(final Token refreshToken) {
                *
                * Authenticating will happen automatically as part of opening a Realm.
                */
          -    public boolean isAuthenticated(SyncConfiguration configuration) {
          +    boolean isAuthenticated(SyncConfiguration configuration) {
                   Token token = getAccessToken(configuration.getServerUrl());
                   return token != null && token.expiresMs() > System.currentTimeMillis();
               }
          @@ -92,11 +92,15 @@ public String getIdentity() {
                   return identity;
               }
           
          -    public Token getAccessToken(URI serverUrl) {
          +    Token getAccessToken(URI serverUrl) {
                   AccessDescription accessDescription = realms.get(serverUrl);
                   return (accessDescription != null) ? accessDescription.accessToken : null;
               }
           
          +    void removeAccessToken(URI serverUrl) {
          +        realms.remove(serverUrl);
          +    }
          +
               public void addRealm(URI uri, AccessDescription description) {
                   realms.put(uri, description);
               }
          
          From 63a0ad273765d5610de10757c97ef0d4794d078e Mon Sep 17 00:00:00 2001
          From: Nabil Hachicha 
          Date: Sat, 14 Jan 2017 18:06:11 +0000
          Subject: [PATCH 0422/2110] add backup option when receiving a client reset
           from Sync protocol (#4029)
          
          * add backup option when receiving a client reset from Sync protocol
          ---
           CHANGELOG.md                                  |  1 +
           .../src/main/cpp/objectserver_shared.hpp      | 51 ++++++++++++++++---
           .../internal/objectserver/BoundState.java     |  2 +-
           3 files changed, 45 insertions(+), 9 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 1dee611f58..fca1711e29 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -13,6 +13,7 @@
           ### Internal
           
           * Updated Realm Sync to 1.0.0-BETA-7.1.
          +* Add a Realm backup when receiving a Sync client reset message from the server.
           
           ## 2.2.2
           
          diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp
          index 8b41842446..bbafb6061f 100644
          --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp
          +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp
          @@ -28,6 +28,8 @@
           
           #include 
           #include 
          +#include 
          +#include 
           
           #include "util.hpp"
           #include "jni_util/jni_utils.hpp"
          @@ -61,7 +63,7 @@ class JniSession {
                           coordinator->wake_up_notifier_worker();
                       }
                   };
          -        auto error_handler = [weak_session_ref](std::error_code error_code, bool is_fatal, const std::string message) {
          +        auto error_handler = [weak_session_ref, local_realm_path](std::error_code error_code, bool is_fatal, const std::string message) {
                       if (error_code.category() != realm::sync::protocol_error_category() &&
                               error_code.category() != realm::sync::client_error_category()) {
                           // FIXME: Consider below when moving to the OS sync manager.
          @@ -74,14 +76,47 @@ class JniSession {
                           return;
                       }
           
          -            auto session_ref = weak_session_ref.lock();
          -            if (session_ref) {
          -                session_ref.get()->call_with_local_ref([&](JNIEnv* local_env, jobject obj) {
          -                    static realm::jni_util::JavaMethod notify_error_handler(
          -                            local_env, obj, "notifySessionError", "(ILjava/lang/String;)V");
          -                    local_env->CallVoidMethod(
          -                            obj, notify_error_handler, error_code.value(), local_env->NewStringUTF(message.c_str()));
          +            // Handle client reset, without returning to Java
          +
          +            // we don't have the original SyncError so we can't call SyncError#is_client_reset_requested
          +            // we need to transform the error code to an enum, then do the check manually
          +            using ProtocolError = realm::sync::ProtocolError;
          +            auto protocol_error = static_cast(error_code.value());
          +
          +            // Documented here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup
          +            if (protocol_error == ProtocolError::bad_server_file_ident
          +                || protocol_error == ProtocolError::bad_client_file_ident
          +                || protocol_error == ProtocolError::bad_server_version
          +                || protocol_error == ProtocolError::diverging_histories) {
          +
          +                // Add a SyncFileActionMetadata marking the Realm as needing to be deleted.
          +                auto recovery_path = realm::util::reserve_unique_file_name(
          +                        realm::SyncManager::shared().recovery_directory_path(),
          +                        realm::util::create_timestamped_template("recovered_realm"));
          +                auto original_path = local_realm_path;
          +
          +                realm::jni_util::Log::d("A client reset is scheduled for the next app start");
          +                realm::SyncManager::shared().perform_metadata_update([original_path = std::move(original_path),
          +                        recovery_path = std::move(recovery_path)](const auto &manager) {
          +                    realm::SyncFileActionMetadata(manager,
          +                                                  realm::SyncFileActionMetadata::Action::HandleRealmForClientReset,
          +                                                  original_path,
          +                                                  nullptr,
          +                                                  nullptr,
          +                                                  realm::util::Optional(
          +                                                          std::move(recovery_path)));
                           });
          +
          +            } else {
          +                auto session_ref = weak_session_ref.lock();
          +                if (session_ref) {
          +                        session_ref.get()->call_with_local_ref([&](JNIEnv* local_env, jobject obj) {
          +                            static realm::jni_util::JavaMethod notify_error_handler(
          +                                    local_env, obj, "notifySessionError", "(ILjava/lang/String;)V");
          +                            local_env->CallVoidMethod(
          +                                    obj, notify_error_handler, error_code.value(), local_env->NewStringUTF(message.c_str()));
          +                        });
          +                }
                       }
                   };
                   m_sync_session->set_sync_transact_callback(sync_transact_callback);
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java
          index 976bada982..e3e4aa53b7 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java
          @@ -67,7 +67,7 @@ public void onError(ObjectServerError error) {
                       // Create a new session & bind it
                       session.createNativeSession();
                       gotoNextState(SessionState.BINDING);
          -            
          +
                   } else {
                       switch (error.getCategory()) {
                           case FATAL: gotoNextState(SessionState.STOPPED); break;
          
          From 9da0754b7a99221202854570438ea16c6f237b67 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Fri, 13 Jan 2017 14:47:49 +0800
          Subject: [PATCH 0423/2110] Fix too many open files crash
          
          Update object store to 163c1e8fb0 to fix #4002
          ---
           CHANGELOG.md                                  | 1 +
           realm/realm-library/src/main/cpp/object-store | 2 +-
           2 files changed, 2 insertions(+), 1 deletion(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index fca1711e29..f8a905756b 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -5,6 +5,7 @@
           * Fixed native memory leak setting the value of a primary key (#3993).
           * Activated Realm's annotation processor on connectedTest when the project is using kapt (#4008).
           * Fixed bug, preventing Sync client to renew the access token (#4038) (#4039).
          +* Fixed "too many open files" issue (#4002).
           
           ### Object Server API Changes (In Beta)
           
          diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store
          index ac2f607264..163c1e8fb0 160000
          --- a/realm/realm-library/src/main/cpp/object-store
          +++ b/realm/realm-library/src/main/cpp/object-store
          @@ -1 +1 @@
          -Subproject commit ac2f60726434654cace0bb5e57a92df7555c8f1f
          +Subproject commit 163c1e8fb026a05d281e82096e64622d2e735f17
          
          From f89ef5aa52d1d5e13b06884a94c39aab3016da4c Mon Sep 17 00:00:00 2001
          From: Kenneth Geisshirt 
          Date: Mon, 16 Jan 2017 09:52:42 +0100
          Subject: [PATCH 0424/2110] Updating to Realm Sync v1.0.0-BETA-7.2
          
          ---
           CHANGELOG.md      | 2 +-
           dependencies.list | 4 ++--
           2 files changed, 3 insertions(+), 3 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index f8a905756b..39f727cf2a 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -13,7 +13,7 @@
           
           ### Internal
           
          -* Updated Realm Sync to 1.0.0-BETA-7.1.
          +* Updated Realm Sync to 1.0.0-BETA-7.2.
           * Add a Realm backup when receiving a Sync client reset message from the server.
           
           ## 2.2.2
          diff --git a/dependencies.list b/dependencies.list
          index 769de14b97..98e6ed2b53 100644
          --- a/dependencies.list
          +++ b/dependencies.list
          @@ -1,7 +1,7 @@
           # Realm Sync Core release used by Realm Java
           # https://github.com/realm/realm-sync/releases
          -REALM_SYNC_VERSION=1.0.0-BETA-7.1
          -REALM_SYNC_SHA256=5412b42d96dd525af3ec6a0b7943330c43beae2c663b32376b77278ea54e34a5
          +REALM_SYNC_VERSION=1.0.0-BETA-7.2
          +REALM_SYNC_SHA256=d2474e81e1e820d19e16c208c410b10cc7e22f3791f9e9d3d0745c259754b238
           
           # Object Server Release used by Integration tests
           # https://packagecloud.io/realm/realm?filter=debs
          
          From ec7386beeb587b6fde476bad9467b854d0d7692c Mon Sep 17 00:00:00 2001
          From: LYK 
          Date: Mon, 16 Jan 2017 18:46:44 +0900
          Subject: [PATCH 0425/2110] SyncUser.all() returns Map (#4036)
          
          * SyncUser.all() returns Map
          
          * Update the document
          ---
           CHANGELOG.md                                      |  1 +
           .../java/io/realm/SyncUserTests.java              |  8 ++++----
           .../src/objectServer/java/io/realm/SyncUser.java  | 15 ++++++++-------
           3 files changed, 13 insertions(+), 11 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 325d7ebfeb..5a603c214d 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -4,6 +4,7 @@
           
           * Add a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`).
           * Change the order of arguments to SyncCredentials.custom to match iOS: token, provider, userInfo
          +* `SyncUser.all()` now returns Map instead of List.
           
           ## 2.2.3
           
          diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          index 18a8eb9aea..6a746f08b5 100644
          --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          @@ -31,7 +31,7 @@
           import java.net.URI;
           import java.net.URISyntaxException;
           import java.net.URL;
          -import java.util.Collection;
          +import java.util.Map;
           import java.util.UUID;
           
           import io.realm.internal.network.AuthenticateResponse;
          @@ -136,7 +136,7 @@ public void currentUser_clearedOnLogout() {
               // `all()` returns an empty list if no users are logged in
               @Test
               public void all_empty() {
          -        Collection users = SyncUser.all();
          +        Map users = SyncUser.all();
                   assertTrue(users.isEmpty());
               }
           
          @@ -148,9 +148,9 @@ public void all_validUsers() {
                   userStore.put(SyncTestUtils.createTestUser(Long.MIN_VALUE));
                   userStore.put(SyncTestUtils.createTestUser(Long.MAX_VALUE));
           
          -        Collection users = SyncUser.all();
          +        Map users = SyncUser.all();
                   assertEquals(1, users.size());
          -        assertTrue(users.iterator().next().isValid());
          +        assertTrue(users.get(users.keySet().iterator().next()).isValid());
               }
           
               // Tests that the user store returns the last user to login
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          index c1097e8118..3e988da558 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          @@ -28,9 +28,10 @@
           import java.net.URI;
           import java.net.URISyntaxException;
           import java.net.URL;
          -import java.util.ArrayList;
           import java.util.Collection;
          -import java.util.List;
          +import java.util.Collections;
          +import java.util.HashMap;
          +import java.util.Map;
           import java.util.concurrent.Future;
           import java.util.concurrent.ThreadPoolExecutor;
           
          @@ -106,18 +107,18 @@ public static SyncUser currentUser() {
                * Returns all valid users known by this device.
                * A user is invalidated when he/she logs out or the user's access token expires.
                *
          -     * @return a list of all known valid users.
          +     * @return a map from user identifier to user. It includes all known valid users.
                */
          -    public static Collection all() {
          +    public static Map all() {
                   UserStore userStore = SyncManager.getUserStore();
                   Collection storedUsers = userStore.allUsers();
          -        List result = new ArrayList(storedUsers.size());
          +        Map map = new HashMap();
                   for (SyncUser user : storedUsers) {
                       if (user.isValid()) {
          -                result.add(user);
          +                map.put(user.getIdentity(), user);
                       }
                   }
          -        return result;
          +        return Collections.unmodifiableMap(map);
               }
           
               /**
          
          From 6af15012282aac90bdba39c7c610ac85c220b46f Mon Sep 17 00:00:00 2001
          From: Kenneth Geisshirt 
          Date: Mon, 16 Jan 2017 13:27:05 +0100
          Subject: [PATCH 0426/2110] Removing unused public constants
          
          ---
           CHANGELOG.md                                                   | 1 +
           .../src/objectServer/java/io/realm/SyncConfiguration.java      | 3 ---
           2 files changed, 1 insertion(+), 3 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 39f727cf2a..6ac97e470b 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -10,6 +10,7 @@
           ### Object Server API Changes (In Beta)
           
           * Exceptions thrown in error handlers are ignored but logged (#3559).
          +* Removed unused public constants in `SyncConfiguration` (#4047).
           
           ### Internal
           
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java
          index bc630cf99b..3b3b5e5e57 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java
          @@ -73,9 +73,6 @@
           @Beta
           public class SyncConfiguration extends RealmConfiguration {
           
          -    public static final int PORT_REALM = 80;
          -    public static final int PORT_REALMS = 443;
          -
               // The FAT file system has limitations of length. Also, not all characters are permitted.
               // https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
               public static final int MAX_FULL_PATH_LENGTH = 256;
          
          From c95c751d398a384c5c67e8974c5f5b6e39641653 Mon Sep 17 00:00:00 2001
          From: Kenneth Geisshirt 
          Date: Mon, 16 Jan 2017 14:24:39 +0100
          Subject: [PATCH 0427/2110] Adding error code disabled_session
          
          ---
           .../src/objectServer/java/io/realm/ErrorCode.java              | 3 ++-
           1 file changed, 2 insertions(+), 1 deletion(-)
          
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java
          index 17e0365396..ad4f603734 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java
          @@ -25,7 +25,7 @@
           @Beta
           public enum ErrorCode {
           
          -    // See https://github.com/realm/realm-sync/blob/master/doc/protocol.md
          +    // See https://github.com/realm/realm-sync/blob/master/doc/protocol_16.md
           
               // Realm Java errors (0-49)
               UNKNOWN(-1),                                // Catch-all
          @@ -63,6 +63,7 @@ public enum ErrorCode {
               BAD_CLIENT_VERSION(210),                        // Bad client version (IDENT, UPLOAD)
               DIVERGING_HISTORIES(211),                       // Diverging histories (IDENT)
               BAD_CHANGESET(212),                             // Bad changeset (UPLOAD)
          +    DISABLED_SESSION(213),                          // Disabled session
           
               // 300 - 599 Reserved for Standard HTTP error codes
           
          
          From 0ca9536713778af83bbdbe4d61e9ea26377688c2 Mon Sep 17 00:00:00 2001
          From: Christian Melchior 
          Date: Tue, 17 Jan 2017 08:27:00 +0100
          Subject: [PATCH 0428/2110] SyncCredentials.accessToken + Integration tests
           (#4018)
          
          This PR adds support for SyncCredentials.accessToken() which
           is required for #4005. I also found a number of issues with
           the integration tests. They have been fixed as well.
          ---
           Jenkinsfile                                   |   3 +-
           README.md                                     |   3 +
           dependencies.list                             |   4 +-
           .../src/androidTest/AndroidManifest.xml       |  17 ++
           .../java/io/realm/rule/RunInLooperThread.java |   8 +
           .../java/io/realm/SyncUserTests.java          |  15 ++
           .../java/io/realm/SyncCredentials.java        |  27 ++-
           .../java/io/realm/SyncManager.java            |  12 ++
           .../objectServer/java/io/realm/SyncUser.java  |  14 +-
           .../network/AuthenticateResponse.java         |  19 +-
           .../network/NetworkStateReceiver.java         |   4 +
           .../network/OkHttpAuthenticationServer.java   |   2 +-
           .../io/realm/internal/objectserver/Token.java |   5 +
           .../java/io/realm/objectserver/AuthTests.java |  60 ++++--
           .../objectserver/BaseIntegrationTest.java     |  52 +++++
           .../objectserver/ProcessCommitTests.java      |  60 +++---
           .../objectserver/service/SendOneCommit.java   |  18 +-
           .../realm/objectserver/service/SendsALot.java |  18 +-
           .../realm/objectserver/utils/Constants.java   |  12 +-
           .../realm/objectserver/utils/HttpUtils.java   |  23 +-
           .../realm/objectserver/utils/UserFactory.java |  25 +--
           tools/sync_test_server/Dockerfile             |   4 +-
           tools/sync_test_server/configuration.yml      | 203 ++++++++++++------
           tools/sync_test_server/ros-testing-server.js  |  10 +-
           tools/sync_test_server/start_server.sh        |   5 +-
           25 files changed, 455 insertions(+), 168 deletions(-)
           create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java
          
          diff --git a/Jenkinsfile b/Jenkinsfile
          index 364ad7d9fb..e196b273b1 100644
          --- a/Jenkinsfile
          +++ b/Jenkinsfile
          @@ -138,8 +138,7 @@ try {
           }
           
           def forwardAdbPorts() {
          -  sh ''' adb reverse tcp:7800 tcp:7800 &&
          -      adb reverse tcp:8080 tcp:8080 &&
          +  sh ''' adb reverse tcp:9080 tcp:9080 &&
                 adb reverse tcp:8888 tcp:8888
             '''
           }
          diff --git a/README.md b/README.md
          index 7e985ef5c4..3e1b64441d 100644
          --- a/README.md
          +++ b/README.md
          @@ -220,6 +220,9 @@ To run a testing server locally:
           	./gradlew connectedObjectServerDebugAndroidTest
           	```
           
          +Note that if using VirtualBox (Genymotion), the network needs to be bridged for the tests to work.
          +This is done in `VirtualBox > Network`. Set "Adapter 2" to "Bridged Adapter".
          +
           These tests may take as much as half an hour to complete.
           
           ## Contributing
          diff --git a/dependencies.list b/dependencies.list
          index 98e6ed2b53..20a4a9ecb9 100644
          --- a/dependencies.list
          +++ b/dependencies.list
          @@ -4,5 +4,7 @@ REALM_SYNC_VERSION=1.0.0-BETA-7.2
           REALM_SYNC_SHA256=d2474e81e1e820d19e16c208c410b10cc7e22f3791f9e9d3d0745c259754b238
           
           # Object Server Release used by Integration tests
          +# `realm` is stable releases, `realm-testing` is developer builds.
           # https://packagecloud.io/realm/realm?filter=debs
          -REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-4.11-449
          +# https://packagecloud.io/realm/realm-testing?filter=debs
          +REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-5.1-74
          diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml
          index c7741a695f..d9e252dce4 100644
          --- a/realm/realm-library/src/androidTest/AndroidManifest.xml
          +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml
          @@ -21,6 +21,23 @@
                       android:exported="true"
                       android:process=":remote">
                   
          +
          +         
          +        
          +        
          +        
          +        
               
           
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java
          index 8a77d1e983..bdf074b546 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java
          @@ -48,7 +48,11 @@
            * and this class does not agree in which order to delete all open Realms.
            */
           public class RunInLooperThread extends TestRealmConfigurationFactory {
          +
          +    // Default Realm created by this Rule. It is guaranteed to be closed when the test finishes.
               public Realm realm;
          +    // Custom Realm used by the test. Saving the reference here will guarantee the instance is closed when exiting the test.
          +    public Realm testRealm;
               public RealmConfiguration realmConfiguration;
               private CountDownLatch signalTestCompleted;
               private Handler backgroundHandler;
          @@ -72,6 +76,7 @@ protected void after() {
                   super.after();
                   realmConfiguration = null;
                   realm = null;
          +        testRealm = null;
                   keepStrongReference = null;
               }
           
          @@ -128,6 +133,9 @@ public void run() {
                                           if (realm != null) {
                                               realm.close();
                                           }
          +                                if (testRealm != null) {
          +                                    testRealm.close();
          +                                }
                                           signalClosedRealm.countDown();
                                       }
                                   }
          diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          index 6a746f08b5..644586ed63 100644
          --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          @@ -197,4 +197,19 @@ public void toString_returnDescription() {
                   assertTrue(str != null && !str.isEmpty());
               }
           
          +    // Test that a login with an access token logs the user in directly without touching the network
          +    @Test
          +    public void login_withAccessToken() {
          +        AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class);
          +        when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenThrow(new AssertionError("Server contacted."));
          +        AuthenticationServer originalServer = SyncManager.getAuthServer();
          +        SyncManager.setAuthServerImpl(authServer);
          +        try {
          +            SyncCredentials credentials = SyncCredentials.accessToken("foo", "bar");
          +            SyncUser user = SyncUser.login(credentials, "http://ros.realm.io/auth");
          +            assertTrue(user.isValid());
          +        } finally {
          +            SyncManager.setAuthServerImpl(originalServer);
          +        }
          +    }
           }
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java
          index 02e925643c..ff97918de8 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java
          @@ -137,7 +137,7 @@ public static SyncCredentials usernamePassword(String username, String password)
                * Creates a custom set of credentials. The behaviour will depend on the type of {@code identityProvider} and
                * {@code userInfo} used.
                *
          -     * @param userIdentifier String identifying the user. Usually a username of userIdentifier.
          +     * @param userIdentifier string identifying the user. Usually a username of userIdentifier.
                * @param identityProvider provider used to verify the credentials.
                * @param userInfo data describing the user further or {@code null} if the user does not have any extra data. The
                *              data will be serialized to JSON, so all values must be mappable to a valid JSON data type. Custom
          @@ -155,6 +155,23 @@ public static SyncCredentials custom(String userIdentifier, String identityProvi
                   return new SyncCredentials(userIdentifier, identityProvider, userInfo);
               }
           
          +    /**
          +     * Creates credentials from an existing access token. Since an access token is the proof that a user already
          +     * has logged in. Credentials created this way are automatically assumed to have successfully logged in.
          +     * This means that providing this credential to {@link SyncUser#login(SyncCredentials, String)} will always
          +     * succeed, but accessing any Realm after might fail if the token is no longer valid.
          +     *
          +     * @param accessToken user's access token.
          +     * @param identifier user identifier.
          +     * @return a set of credentials that can be used to log into the Object Server using
          +     *         {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}
          +     */
          +    public static SyncCredentials accessToken(String accessToken, String identifier) {
          +        HashMap userInfo = new HashMap();
          +        userInfo.put("_token", accessToken);
          +        return new SyncCredentials(identifier, IdentityProvider.ACCESS_TOKEN, userInfo);
          +    }
          +
               private static void assertStringNotEmpty(String string, String message) {
                   if (string == null || "".equals(string)) {
                       throw new IllegalArgumentException("Non-null '" + message + "' required.");
          @@ -201,6 +218,14 @@ public Map getUserInfo() {
                * verifying that a given credential is valid.
                */
               public static final class IdentityProvider {
          +
          +        /**
          +         * The provided identity is an already registered user (represented by the access token). Logging in with this
          +         * type of identity will happen purely on the device without contacting the Realm Object Server. Acquiring
          +         * access to individual Realms will still require talking to the Object Server.
          +         */
          +        public static final String ACCESS_TOKEN = "_access_token";
          +
                   /**
                    * Any credentials verified by the debug identity provider will always be considered valid.
                    * It is only available if configured on the Object Server, and it is disabled by default.
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java
          index 3406054f72..a1f22e84cd 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java
          @@ -47,6 +47,18 @@
           @SuppressFBWarnings("MS_CANNOT_BE_FINAL")
           public class SyncManager {
           
          +    /**
          +     * Debugging related options.
          +     */
          +    @SuppressFBWarnings("MS_SHOULD_BE_FINAL")
          +    public static class Debug {
          +        /**
          +         * Set this to true to bypass checking if the device is offline before making HTTP requests.
          +         */
          +        public static boolean skipOnlineChecking = false;
          +
          +    }
          +
               /**
                * APP ID sent to the Realm Object Server. Is automatically initialized to the package name for the app.
                */
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          index 3e988da558..8d64d7da16 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          @@ -169,10 +169,20 @@ public static SyncUser login(final SyncCredentials credentials, final String aut
                       throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e);
                   }
           
          -        final AuthenticationServer server = SyncManager.getAuthServer();
                   ObjectServerError error;
                   try {
          -            AuthenticateResponse result = server.loginUser(credentials, authUrl);
          +            AuthenticateResponse result;
          +            if (credentials.getIdentityProvider().equals(SyncCredentials.IdentityProvider.ACCESS_TOKEN)) {
          +                // Credentials using ACCESS_TOKEN as IdentityProvider are optimistically assumed to be valid already.
          +                // So log them in directly without contacting the authentication server. This is done by mirroring
          +                // the JSON response expected from the server.
          +                String userIdentifier = credentials.getUserIdentifier();
          +                String token = (String) credentials.getUserInfo().get("_token");
          +                result = AuthenticateResponse.createValidResponseWithUser(userIdentifier, token);
          +            } else {
          +                final AuthenticationServer server = SyncManager.getAuthServer();
          +                result = server.loginUser(credentials, authUrl);
          +            }
                       if (result.isValid()) {
                           ObjectServerUser syncUser = new ObjectServerUser(result.getRefreshToken(), authUrl);
                           SyncUser user = new SyncUser(syncUser);
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java
          index 75e31a0818..4bb65ea1e9 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java
          @@ -74,6 +74,23 @@ public static AuthenticateResponse from(ObjectServerError error) {
                   return new AuthenticateResponse(error);
               }
           
          +    /**
          +     * Helper method for creating a valid user login response. The user returned will be assumed to have all permissions
          +     * and doesn't expire.
          +     *
          +     * @param identifier user identifier.
          +     * @param refreshToken user's refresh token.
          +     */
          +    public static AuthenticateResponse createValidResponseWithUser(String identifier, String refreshToken) {
          +        try {
          +            JSONObject response = new JSONObject();
          +            response.put(JSON_FIELD_REFRESH_TOKEN, new Token(refreshToken, identifier, null, Long.MAX_VALUE, Token.Permission.ALL).toJson());
          +            return new AuthenticateResponse(response.toString());
          +        } catch (JSONException e) {
          +            throw new RuntimeException(e);
          +        }
          +    }
          +
               /**
                * Creates an unsuccessful authentication response. This should only happen in case of network or I/O related
                * issues.
          @@ -81,7 +98,7 @@ public static AuthenticateResponse from(ObjectServerError error) {
                * @param error the network or I/O error.
                */
               private AuthenticateResponse(ObjectServerError error) {
          -        RealmLog.debug("AuthenticateResponse. Error " + error.getErrorMessage());
          +        RealmLog.debug("AuthenticateResponse - Error: " + error);
                   setError(error);
                   this.accessToken = null;
                   this.refreshToken = null;
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java
          index 5fa68176a9..d7f426d350 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java
          @@ -25,6 +25,7 @@
           import java.util.List;
           import java.util.concurrent.CopyOnWriteArrayList;
           
          +import io.realm.SyncManager;
           import io.realm.internal.Util;
           
           /**
          @@ -67,6 +68,9 @@ public static synchronized void removeListener(ConnectionListener listener) {
                * @return {@code true} if device is online, otherwise {@code false}.
                */
               public static boolean isOnline(Context context) {
          +        if (SyncManager.Debug.skipOnlineChecking) {
          +            return true;
          +        }
                   ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
                   NetworkInfo networkInfo = cm.getActiveNetworkInfo();
                   return ((networkInfo != null && networkInfo.isConnectedOrConnecting()) || Util.isEmulator());
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java
          index c84d408a12..8661ba3ff4 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java
          @@ -25,6 +25,7 @@
           import io.realm.ObjectServerError;
           import io.realm.SyncUser;
           import io.realm.internal.objectserver.Token;
          +import io.realm.log.RealmLog;
           import okhttp3.Call;
           import okhttp3.MediaType;
           import okhttp3.OkHttpClient;
          @@ -85,7 +86,6 @@ private AuthenticateResponse authenticate(URL authenticationUrl, String requestB
                           .url(authenticationUrl)
                           .addHeader("Content-Type", "application/json")
                           .addHeader("Accept", "application/json")
          -                .addHeader("Connection", "close") //  See https://github.com/square/okhttp/issues/2363
                           .post(RequestBody.create(JSON, requestBody))
                           .build();
                   Call call = client.newCall(request);
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java
          index 78f0acfb04..1d45632b40 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java
          @@ -23,6 +23,8 @@
           import java.util.Arrays;
           import java.util.Locale;
           
          +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
          +
           /**
            * This class represents a value from the Realm Authentication Server.
            */
          @@ -97,6 +99,7 @@ public long expiresMs() {
                   }
               }
           
          +    @SuppressFBWarnings("MS_MUTABLE_ARRAY")
               public Permission[] permissions() {
                   return Arrays.copyOf(permissions, permissions.length);
               }
          @@ -151,5 +154,7 @@ public enum Permission {
                   DOWNLOAD,
                   REFRESH,
                   MANAGE;
          +
          +        public static final Permission[] ALL = { UPLOAD, DOWNLOAD, REFRESH, MANAGE };
               }
           }
          diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java
          index bf6a0fc4df..37fb540706 100644
          --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java
          +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java
          @@ -1,10 +1,7 @@
           package io.realm.objectserver;
           
          -import android.support.test.InstrumentationRegistry;
           import android.support.test.runner.AndroidJUnit4;
           
          -import org.junit.AfterClass;
          -import org.junit.BeforeClass;
           import org.junit.Rule;
           import org.junit.Test;
           import org.junit.runner.RunWith;
          @@ -12,10 +9,14 @@
           import io.realm.ErrorCode;
           import io.realm.ObjectServerError;
           import io.realm.Realm;
          +import io.realm.SessionState;
          +import io.realm.SyncConfiguration;
           import io.realm.SyncCredentials;
          +import io.realm.SyncManager;
          +import io.realm.SyncSession;
           import io.realm.SyncUser;
           import io.realm.objectserver.utils.Constants;
          -import io.realm.objectserver.utils.HttpUtils;
          +import io.realm.objectserver.utils.UserFactory;
           import io.realm.rule.RunInLooperThread;
           import io.realm.rule.RunTestInLooperThread;
           
          @@ -23,21 +24,10 @@
           import static junit.framework.Assert.fail;
           
           @RunWith(AndroidJUnit4.class)
          -public class AuthTests {
          +public class AuthTests extends BaseIntegrationTest {
               @Rule
               public RunInLooperThread looperThread = new RunInLooperThread();
           
          -    @BeforeClass
          -    public static void setUp () throws Exception {
          -        Realm.init(InstrumentationRegistry.getContext());
          -        HttpUtils.startSyncServer();
          -    }
          -
          -    @AfterClass
          -    public static void tearDown () throws Exception {
          -        HttpUtils.stopSyncServer();
          -    }
          -
               @Test
               public void login_userNotExist() {
                   SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false);
          @@ -67,6 +57,44 @@ public void onError(ObjectServerError error) {
                   });
               }
           
          +    @Test
          +    @RunTestInLooperThread
          +    public void login_withAccessToken() {
          +        SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL);
          +        SyncCredentials credentials = SyncCredentials.accessToken(admin.getAccessToken(), "custom-admin-user");
          +        SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() {
          +            @Override
          +            public void onSuccess(SyncUser user) {
          +                final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL)
          +                        .errorHandler(new SyncSession.ErrorHandler() {
          +                            @Override
          +                            public void onError(SyncSession session, ObjectServerError error) {
          +                                fail("Session failed: " + error);
          +                            }
          +                        })
          +                        .build();
          +
          +                final Realm realm = Realm.getInstance(config);
          +                looperThread.testRealm = realm;
          +
          +                // FIXME: Right now we have no Java API for detecting when a session is established
          +                // So we optimistically assume it has been connected after 1 second.
          +                looperThread.postRunnableDelayed(new Runnable() {
          +                    @Override
          +                    public void run() {
          +                        assertEquals(SessionState.BOUND, SyncManager.getSession(config).getState());
          +                        looperThread.testComplete();
          +                    }
          +                }, 1000);
          +            }
          +
          +            @Override
          +            public void onError(ObjectServerError error) {
          +                fail("Login failed: " + error);
          +            }
          +        });
          +    }
          +
               // The error handler throws an exception but it is ignored (but logged). That means, this test should not
               // pass and not be stopped by an IllegalArgumentException.
               @Test
          diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java
          new file mode 100644
          index 0000000000..764b511ee7
          --- /dev/null
          +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java
          @@ -0,0 +1,52 @@
          +/*
          + * Copyright 2017 Realm Inc.
          + *
          + * 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.
          + */
          +
          +package io.realm.objectserver;
          +
          +import android.support.test.InstrumentationRegistry;
          +
          +import org.junit.AfterClass;
          +import org.junit.BeforeClass;
          +
          +import io.realm.Realm;
          +import io.realm.SyncManager;
          +import io.realm.log.RealmLog;
          +import io.realm.objectserver.utils.HttpUtils;
          +
          +class BaseIntegrationTest {
          +
          +    @BeforeClass
          +    public static void setUp () throws Exception {
          +        SyncManager.Debug.skipOnlineChecking = true;
          +        try {
          +            Realm.init(InstrumentationRegistry.getContext());
          +            HttpUtils.startSyncServer();
          +        } catch (Exception e) {
          +            // Throwing an exception from this method will crash JUnit. Instead just log it.
          +            // If this setup method fails, all unit tests in the class extending it will most likely fail as well.
          +            RealmLog.error("Could not start Sync Server", e);
          +        }
          +    }
          +
          +    @AfterClass
          +    public static void tearDown () throws Exception {
          +        try {
          +            HttpUtils.stopSyncServer();
          +        } catch (Exception e) {
          +            RealmLog.error("Failed to stop Sync Server", e);
          +        }
          +    }
          +}
          diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java
          index 6e465f53f9..68ce590f3e 100644
          --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java
          +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java
          @@ -24,6 +24,7 @@
           
           import org.junit.AfterClass;
           import org.junit.BeforeClass;
          +import org.junit.Ignore;
           import org.junit.Test;
           import org.junit.runner.RunWith;
           
          @@ -32,41 +33,31 @@
           import java.util.concurrent.Executors;
           import java.util.concurrent.TimeUnit;
           
          +import io.realm.ObjectServerError;
           import io.realm.Realm;
           import io.realm.RealmChangeListener;
           import io.realm.RealmResults;
           import io.realm.SyncConfiguration;
          +import io.realm.SyncSession;
          +import io.realm.SyncUser;
           import io.realm.objectserver.model.ProcessInfo;
           import io.realm.objectserver.model.TestObject;
           import io.realm.objectserver.service.SendOneCommit;
           import io.realm.objectserver.service.SendsALot;
           import io.realm.objectserver.utils.Constants;
           import io.realm.objectserver.utils.HttpUtils;
          +import io.realm.objectserver.utils.UserFactory;
           
           import static org.junit.Assert.assertEquals;
           import static org.junit.Assert.fail;
           
           @RunWith(AndroidJUnit4.class)
          -public class ProcessCommitTests {
          -    @BeforeClass
          -    public static void setUp () throws Exception {
          -        HttpUtils.startSyncServer();
          -    }
          -
          -    @AfterClass
          -    public static void tearDown () throws Exception {
          -        HttpUtils.stopSyncServer();
          -    }
          +public class ProcessCommitTests extends BaseIntegrationTest {
           
          -    // FIXME: At least need one method in the test class
          -    @Test
          -    public void dummy() {
          -
          -    }
          -
          -    // FIXME: Disable for now.
          -    /*
          +    // FIXME: Ignore for now. They do still not work. It might be caused by two processes each creating
          +    // a Sync Client, but it needs to be investigated.
               @Test
          +    @Ignore
               public void expectServerCommit() throws Throwable {
                   final Throwable[] exception = new Throwable[1];
                   final CountDownLatch testFinished = new CountDownLatch(1);
          @@ -76,18 +67,23 @@ public void expectServerCommit() throws Throwable {
                       public void run() {
                           try {
                               Looper.prepare();
          -                    Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
          +                    Context targetContext = InstrumentationRegistry.getTargetContext();
           
          -                    final SyncConfiguration syncConfig = new SyncConfiguration.Builder()
          +                    SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL);
          +                    String realmUrl = Constants.SYNC_SERVER_URL;
          +                    final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl)
                                       .name(SendOneCommit.class.getSimpleName())
          -                            .serverUrl(Constants.SYNC_SERVER_URL )
          -                            .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL, Constants.USER_TOKEN))
          +                            .errorHandler(new SyncSession.ErrorHandler() {
          +                                @Override
          +                                public void onError(SyncSession session, ObjectServerError error) {
          +                                    fail("Sync failure: " + error);
          +                                }
          +                            })
                                       .build();
                               Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests
                               final Realm realm = Realm.getInstance(syncConfig);
                               Intent intent = new Intent(targetContext, SendOneCommit.class);
                               targetContext.startService(intent);
          -
                               final RealmResults all = realm.where(ProcessInfo.class).findAll();
                               all.addChangeListener(new RealmChangeListener>() {
                                   @Override
          @@ -113,14 +109,15 @@ public void onChange(RealmResults element) {
                       fail("Test timed out ");
                   }
               }
          -    */
           
          +    // FIXME: Ignore for now. They do still not work. It might be caused by two processes each creating
          +    // a Sync Client, but it needs to be investigated.
               //TODO send string from service and match
               //     replicate integration tests from Cocoa
               //     add gradle task to start the sh script automatically (create pid file, ==> run or kill existing process
               //     check the requirement for the issue again
          -    /*
               @Test
          +    @Ignore
               public void expectALot() throws Throwable {
                   final Throwable[] exception = new Throwable[1];
                   final CountDownLatch testFinished = new CountDownLatch(1);
          @@ -132,10 +129,16 @@ public void run() {
                               Looper.prepare();
                               Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
           
          -                    final SyncConfiguration syncConfig = new SyncConfiguration.Builder(targetContext)
          +                    SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL);
          +                    String realmUrl = Constants.SYNC_SERVER_URL_2;
          +                    final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl)
                                       .name(SendsALot.class.getSimpleName())
          -                            .serverUrl(Constants.SYNC_SERVER_URL_2)
          -                            .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL_2, Constants.USER_TOKEN))
          +                            .errorHandler(new SyncSession.ErrorHandler() {
          +                                @Override
          +                                public void onError(SyncSession session, ObjectServerError error) {
          +                                    fail("Sync failure: " + error);
          +                                }
          +                            })
                                       .build();
                               Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests
                               final Realm realm = Realm.getInstance(syncConfig);
          @@ -171,5 +174,4 @@ public void onChange(RealmResults element) {
                       fail("Test timed out ");
                   }
               }
          -    */
           }
          diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java
          index 26c4f89de6..4653b26211 100644
          --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java
          +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java
          @@ -20,6 +20,13 @@
           import android.content.Intent;
           import android.os.IBinder;
           
          +import io.realm.Realm;
          +import io.realm.SyncConfiguration;
          +import io.realm.SyncUser;
          +import io.realm.objectserver.model.ProcessInfo;
          +import io.realm.objectserver.utils.Constants;
          +import io.realm.objectserver.utils.UserFactory;
          +
           /**
            * Open a sync Realm on a different process, then send one commit.
            */
          @@ -28,12 +35,11 @@ public class SendOneCommit extends Service {
               @Override
               public void onCreate() {
                   super.onCreate();
          -        // FIXME: Disable for now
          -        /*
          -        final SyncConfiguration syncConfig = new SyncConfiguration.Builder(this)
          +        Realm.init(getApplicationContext());
          +        SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL);
          +        String realmUrl = Constants.SYNC_SERVER_URL;
          +        final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl)
                           .name(SendOneCommit.class.getSimpleName())
          -                .serverUrl(Constants.SYNC_SERVER_URL)
          -                .user(UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL, Constants.USER_TOKEN))
                           .build();
                   Realm.deleteRealm(syncConfig);
                   Realm realm = Realm.getInstance(syncConfig);
          @@ -46,10 +52,8 @@ public void onCreate() {
                   realm.commitTransaction();
           
                   realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset
          -        */
               }
           
          -
               @Override
               public IBinder onBind(Intent intent) {
                   return null;
          diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java
          index 2bcdd9d717..dca642beb2 100644
          --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java
          +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java
          @@ -20,6 +20,13 @@
           import android.content.Intent;
           import android.os.IBinder;
           
          +import io.realm.Realm;
          +import io.realm.SyncConfiguration;
          +import io.realm.SyncUser;
          +import io.realm.objectserver.model.TestObject;
          +import io.realm.objectserver.utils.Constants;
          +import io.realm.objectserver.utils.UserFactory;
          +
           /**
            * Open a sync Realm on a different process, then send one commit.
            */
          @@ -28,13 +35,11 @@ public class SendsALot extends Service {
               @Override
               public void onCreate() {
                   super.onCreate();
          -        // FIXME: Disable for now.
          -        /*
          -        User user = UserFactory.createDefaultUser(Constants.SYNC_SERVER_URL_2, Constants.USER_TOKEN);
          -        final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user)
          +        Realm.init(getApplicationContext());
          +        SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL);
          +        String realmUrl = Constants.SYNC_SERVER_URL_2;
          +        final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl)
                           .name(SendsALot.class.getSimpleName())
          -                .serverUrl(Constants.SYNC_SERVER_URL_2)
          -                .user()
                           .build();
                   Realm.deleteRealm(syncConfig);
                   Realm realm = Realm.getInstance(syncConfig);
          @@ -49,7 +54,6 @@ public void onCreate() {
                   realm.commitTransaction();
           
                   realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset
          -        */
               }
           
           
          diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java
          index e346c04c3c..e5347effc8 100644
          --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java
          +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java
          @@ -17,14 +17,10 @@
           package io.realm.objectserver.utils;
           
           public class Constants {
          -    // to generate a valid token follow the guide in
          -    ///integration-tests/sync/test_server/keys/HowToGenerateKey.txt
          -    public static String USER_TOKEN = "ewogICJpZGVudGl0eSI6ICJ0ZXN0MiIsCiAgImFjY2VzcyI6IFsKICAgICJkb3dubG9hZCIsCiAgICAidXBsb2FkIgogIF0sCiAgInRpbWVzdGFtcCI6IDE0NTU1MzA2MTQsCiAgImV4cGlyZXMiOiBudWxsLAogICJhcHBfaWQiOiAiaW8ucmVhbG0udGVzdHMuc3luYyIKfQ=="
          -            + ":" +
          -            "mR0/GMc0b5XHFNJEM4D9fb94oXMjho0jKxopaU1lQW4FqY1QPBa/bPiVCMhAosZVSNhEP6vEZxVjFHAxoPODKoml1Ry78geKt5Iql395HRvO6KCCN0VkMpx2eXy+SzF2pcEjU5jlldbTAcO6nMyVaQ9g2XF2SZPVjBqpkY1cy2IjMHN0HRWy9SfGelwZY/jW72jZM7+89kWpIB0SmNH8kEPKVZlnRMW4KwNAUPA8P0/+qyoRTr/4l7k7N6z5kBxIKB/+m55AeOUDiFsxA53QPlpHGvF7ThZpiv8i+UhyKZcQlXi1utoj8H1CzpeU/YzrrEf3xrr2qCO3/niU5WdnHA==";
          -    public static String SYNC_SERVER_URL = "realm://127.0.0.1:7800/tests";
          -    public static String SYNC_SERVER_URL_2 = "realm://127.0.0.1:7800/tests2";
           
          -    public static String AUTH_SERVER_URL = "http://127.0.0.1:8080/";
          +    public static String SYNC_SERVER_URL = "realm://127.0.0.1/tests";
          +    public static String SYNC_SERVER_URL_2 = "realm://127.0.0.1/tests2";
          +
          +    public static String AUTH_SERVER_URL = "http://127.0.0.1:9080/";
               public static String AUTH_URL = AUTH_SERVER_URL + "auth";
           }
          diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java
          index 9b15ae11e3..46bc59994c 100644
          --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java
          +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java
          @@ -16,6 +16,7 @@
           
           package io.realm.objectserver.utils;
           
          +
           import java.io.IOException;
           
           import io.realm.log.RealmLog;
          @@ -29,7 +30,10 @@
            * temp directory & start a sync server on it for each unit test.
            */
           public class HttpUtils {
          -    private final static OkHttpClient client = new OkHttpClient();
          +    private final static OkHttpClient client = new OkHttpClient.Builder()
          +            .retryOnConnectionFailure(true)
          +            .build();
          +
               // adb reverse tcp:8888 tcp:8888
               // will forward this query to the host, running the integration test server on 8888
               private final static String START_SERVER = "http://127.0.0.1:8888/start";
          @@ -59,21 +63,32 @@ public static void startSyncServer() throws Exception {
           
               // Checking the server
               private static boolean waitAuthServerReady() throws InterruptedException {
          -        int retryTimes = 50;
          +        int retryTimes = 20;
          +
          +        // Dummy invalid request, which will trigger a 400 (BAD REQUEST), but indicate the auth
          +        // server is responsive
                   Request request = new Request.Builder()
                           .url(Constants.AUTH_SERVER_URL)
                           .build();
           
                   while (retryTimes != 0) {
          +            Response response = null;
                       try {
          -                Response response = client.newCall(request).execute();
          +                response = client.newCall(request).execute();
                           if (response.isSuccessful()) {
                               return true;
                           }
                           RealmLog.error("Error response from auth server: %s", response.toString());
                       } catch (IOException e) {
          +                // TODO As long as the auth server hasn't started yet, OKHttp cannot parse the response
          +                // correctly. At this point it is unknown weather is a bug in OKHttp or an
          +                // unknown host is reported. This can cause a lot of "false" errors in the log.
                           RealmLog.error(e);
          -                Thread.sleep(100);
          +                Thread.sleep(500);
          +            } finally {
          +                if (response != null) {
          +                    response.close();
          +                }
                       }
                       retryTimes--;
                   }
          diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java
          index 1ebb2d6ab5..91129ff4ca 100644
          --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java
          +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java
          @@ -16,25 +16,20 @@
           
           package io.realm.objectserver.utils;
           
          -import java.net.URI;
          -import java.net.URISyntaxException;
          -
          +import io.realm.SyncCredentials;
           import io.realm.SyncUser;
          -import io.realm.objectserver.utils.Constants;
           
           // Must be in `io.realm.objectserver` to work around package protected methods.
           public class UserFactory {
          -    // FIXME: Not working right now.
          -    /*
          -    public static User createDefaultUser(String SERVER_URL, String USER_TOKEN) {
          -        try {
          -            User user = User.createLocal();
           
          -            user.addAccessToken(new URI(SERVER_URL), USER_TOKEN);
          -            return user;
          -        } catch (URISyntaxException e) {
          -            throw new RuntimeException(e);
          -        }
          +    public static SyncUser createDefaultUser(String authUrl) {
          +        SyncCredentials credentials = SyncCredentials.usernamePassword("test-user", "myPassw0rd", true);
          +        return SyncUser.login(credentials, authUrl);
          +    }
          +
          +    public static SyncUser createAdminUser(String authUrl) {
          +        // `admin` required as user identifier to be granted admin rights.
          +        SyncCredentials credentials = SyncCredentials.custom("admin", "debug", null);
          +        return SyncUser.login(credentials, authUrl);
               }
          -    */
           }
          diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile
          index f733015ca4..cddec14681 100644
          --- a/tools/sync_test_server/Dockerfile
          +++ b/tools/sync_test_server/Dockerfile
          @@ -5,7 +5,8 @@ ARG ROS_DE_VERSION
           # Add realm repo
           RUN apt-get update -qq \
               && apt-get install -y curl npm \
          -    && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash
          +    # && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh \
          +    && curl -s https://packagecloud.io/install/repositories/realm/realm-testing/script.deb.sh | bash
           
           # ROS npm dependencies
           RUN npm init -y
          @@ -13,6 +14,7 @@ RUN npm install winston temp httpdispatcher@1.0.0
           
           COPY keys/private.pem keys/public.pem configuration.yml /
           COPY ros-testing-server.js /usr/bin/
          +
           # Install realm object server
           RUN apt-get update -qq \
               && apt-get install -y realm-object-server-developer=$ROS_DE_VERSION \
          diff --git a/tools/sync_test_server/configuration.yml b/tools/sync_test_server/configuration.yml
          index 6080a3beda..702e4c336a 100644
          --- a/tools/sync_test_server/configuration.yml
          +++ b/tools/sync_test_server/configuration.yml
          @@ -19,7 +19,7 @@
           storage:
             ## The directory in which the realm server will store all its data files.
             ## This configuration option is MANDATORY.
          -  root_path: /var/realm/sync-services
          +  root_path: '/var/realm/sync-services'
           
           ## ----------------------------------------------------------------------------
           
          @@ -27,25 +27,84 @@ auth:
             ## The path to the public and private keys (in PEM format) that will be used
             ## to validate identity tokens sent by clients.
             ## These configuration options are MANDATORY.
          -  public_key_path: /public.pem
          -  private_key_path: /private.pem
          -
          -  database:
          -    ## The path for the administration database synchronisation endpoint. Do NOT
          -    ## change this unless asked by Realm Support.
          -    # sync_uri_path: '/public/admin'
          +  public_key_path: '/public.pem'
          +  private_key_path: '/private.pem'
          +
          +  sync_hosts:
          +    ## The hosts for which the authentication service will consider itself
          +    ## authoritative. It will decline to process any kind of requests for Realm
          +    ## files at other URLs. Addresses specified here must include host and port
          +    ## (authority part of the URL according to RFC 3986) on which the sync
          +    ## server is externally reachable. In addition to hosts configured here,
          +    ## the authentication service will always accept the following hosts:
          +    # - localhost:27800
          +    #
          +    # Additionally if a proxy server for the given protocol is configured, it
          +    # will also accept requests for Realm files at these hosts:
          +    # - ${proxy:http:listen_address}:${proxy:http:listen_port}
          +    # - ${proxy:https:listen_address}:${proxy:https:listen_port}
          +    #
          +    # The derived hosts will also include aliases for local addresses
          +    # with the following host names: '127.0.0.1', 'localhost' and '::'.
           
             ttls:
          -    ## The validity duration for Refresh Tokens. This should be a fairly high
          -    ## value, typically ranging 12 hours - 3 days. This value is represented in
          -    ## seconds. Default: 24 hours.
          -    # refresh_token: 86400
          +    ## The validity duration for Refresh Tokens. This can be a fairly high
          +    ## value, ranging from a single day to multiple years, depending on
          +    ## individual needs. Whenever the Refresh Token expires, clients will be
          +    ## forced to delegate again to the authorizing party. If the credentials
          +    ## there can be revoked by the user or are not opaquely managed by the
          +    ## client, then this would force the user to manual intervention after the
          +    ## expiration. Depending on the use case, this can be either desired or
          +    ## should be prevented. This value is represented in seconds.
          +    ## Default: 10 years.
          +    # refresh_token: 315360000
           
               ## The validity duration for Access Tokens. This should be a fairly small
               ## number, especially if you are concerned with revocations being applied
               ## quickly. This value is represented in seconds. Default: 1 minute.
               # access_token: 60
           
          +  providers:
          +    ## Providers of authentication tokens. Each provider has a configuration
          +    ## object associated with it. If a provider is included here and its
          +    ## configuration is valid, it will be enabled.
          +
          +    ## Possible providers: cloudkit, debug, google, facebook, realm, password
          +    ## Providers 'realm' and 'password' are always enabled:
          +    ## - The 'realm' provider is used to derive access tokens from a refresh token.
          +    ## - The 'password' provider is required for the dashboard to work. It supports
          +    ##   authentication through username/password and uses a PBKDF2 implementation.
          +
          +    ## This enables login via CloudKit's user record name.
          +    # cloudkit:
          +      ## The key ID retrieved when adding the public key derived from the
          +      ## specified private_key_path in CloudKit's Server-to-Server Keys,
          +      ## available through the API Access settings in the CloudKit dashboard.
          +      # key_id: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
          +
          +      ## The path to the certificate.
          +      # private_key_path: 'cloudkit_eckey.pem'
          +
          +      ## The container identifier in reverse domain name notation.
          +      # container: "iCloud.io.realm.exampleApp.ios"
          +
          +      ## The environment in which CloudKit should be used. The default is
          +      ## 'development'. For the production deployment for apps on the AppStore
          +      ## you must specify 'production'.
          +      # environment: 'development'
          +
          +    ## This enables authentication via a Google Sign-In access token for a
          +    ## specific app.
          +    # google:
          +      ## The client ID as retrieved when setting up the app in the Google
          +      ## Developer Console.
          +      # clientId: '012345678901-abcdefghijklmnopqrstvuvwxyz01234.apps.googleusercontent.com'
          +
          +    ## This enables authentication via a Facebook access token for a specific app.
          +    ## This provider needs no configuration (uncommenting the next line enables it).
          +    # facebook: {}
          +    debug: {}
          +
           ## ----------------------------------------------------------------------------
           
           proxy:
          @@ -95,7 +154,7 @@ proxy:
               ## The address/interface on which the HTTP proxy module should listen. This defaults
               ## to 127.0.0.1. If you wish to listen on all available interfaces,
               ## uncomment the following line.
          -    listen_address: '0.0.0.0'
          +    listen_address: '::'
           
               ## The port that the HTTP proxy module should bind to.
               # listen_port: 9080
          @@ -116,7 +175,7 @@ proxy:
               ## The address/interface on which the HTTPS proxy module should listen. This defaults
               ## to 127.0.0.1. If you wish to listen on all available interfaces,
               ## uncomment the following line.
          -    # listen_address: '0.0.0.0'
          +    # listen_address: '::'
           
               ## The port that the HTTPS proxy module should bind to.
               # listen_port: 9443
          @@ -128,68 +187,46 @@ network:
             ## the proxy module. The proxy module will automatically forward traffic to the
             ## internal modules on the ports they are configured to listen on in this section.
           
          -  sync:
          -    ## The address/interface on which the server should listen. This defaults
          -    ## to 127.0.0.1. If you wish to listen on all available interfaces,
          -    ## uncomment the following line.
          -    listen_address: '0.0.0.0'
          -
          -    ## The port on which to listen. The Realm sync server uses port 27800 by
          -    ## default. For most deployments, there should not be a need to change this.
          -    listen_port: 7800
          -
             http:
               ## The address/interface on which the server should listen for HTTP
               ## services. This includes Dashboard and Authentication APIs.
               ## This defaults to 127.0.0.1. If you wish to listen on all available
               ## interfaces, uncomment the following line.
          -    listen_address: '0.0.0.0'
          +    # listen_address: '0.0.0.0'
           
               ## The port on which to listen for incoming requests to the Dashboard
               ## and authentication APIs. This defaults to 27080.
          -    listen_port: 8080
          +    # listen_port: 27080
           
           ## ----------------------------------------------------------------------------
           
          -  providers:
          -    ## Providers of authentication tokens. Each provider has a configuration
          -    ## object associated with it. If a provider is included here and its
          -    ## configuration is valid, it will be enabled.
          -
          -    ## Possible providers: cloudkit, debug, facebook, realm, password
          -    ## Providers 'realm' and 'password' are always enabled:
          -    ## - The 'realm' provider is used to derive access tokens from a refresh token.
          -    ## - The 'password' provider is required for the dashboard to work. It supports
          -    ##   authentication through username/password and uses a PBKDF2 implementation.
          -
          -    ## This enables login via CloudKit's user record name.
          -    # cloudkit:
          -      ## The key ID retrieved when adding the public key derived from the
          -      ## specified private_key_path in CloudKit's Server-to-Server Keys,
          -      ## available through the API Access settings in the CloudKit dashboard.
          -      # key_id: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
          -
          -      ## The path to the certificate.
          -      # private_key_path: 'cloudkit_eckey.pem'
          -
          -      ## The container identifier in reverse domain name notation.
          -      # container: "iCloud.io.realm.exampleApp.ios"
          -
          -      ## The environment in which CloudKit should be used. The default is
          -      ## 'development'. For the production deployment for apps on the AppStore
          -      ## you must specify 'production'.
          -      # environment: 'development'
          -
          -    ## This enables authentication via a Google Sign-In access token for a
          -    ## specific app.
          -    # google:
          -      ## The client ID as retrieved when setting up the app in the Google
          -      ## Developer Console.
          -      # clientId: '012345678901-abcdefghijklmnopqrstvuvwxyz01234.apps.googleusercontent.com'
          -
          -    ## This enables authentication via a Facebook access token for a specific app.
          -    ## This provider needs no configuration (uncommenting the next line enables it).
          -    # facebook: {}
          +sync:
          +  ## Synchronization service settings, including clustering and load balancing.
          +
          +  servers:
          +    ## An array of entries describing the cluster configuration.
          +    ##
          +    ## If no servers are configured, a default entry is inserted with the
          +    ## following settings:
          +    ##   - id: '0'
          +    ##     address: '0.0.0.0'
          +    ##     port: 27800
          +    ##
          +    ## Each entry must contain the following entries:
          +    ##
          +    ##    'id': A unique string ID used to distinguish between backend servers.
          +    ##          This must remain stable, even if the particular backend server
          +    ##          is moved to a different address or port.
          +    ##
          +    ##    'address': The address of the cluster participant. If '0.0.0.0' or
          +    ##               '::', a sync server will be started on localhost (listening
          +    ##               on '127.0.0.1' or '::1', respectively). Otherwise, it is
          +    ##               assumed that the sync server is an external process,
          +    ##               potentially on a separate machine.
          +    ##
          +    ##    'port': The port on which to connect to the particular cluster node.
          +    ##            If address was '0.0.0.0' or '::', this is also the port number
          +    ##            on which the local cluster node will listen for connections.
           
           ## ----------------------------------------------------------------------------
           
          @@ -210,12 +247,12 @@ logging:
             ##   error
             ##   fatal
             ##   off: all output suppressed
          -  level: 'all'
          +  # level: 'info'
           
             ## The file to which the synchronisation server should log. This should
             ## be a writable path from the perspective of the user under which the
             ## server runs. If no path is specified, the server will log to stdout.
          -  path: '/tmp/realm-sync.log'
          +  # path: '/var/log/realm-object-server.log'
           
           ## ----------------------------------------------------------------------------
           
          @@ -225,3 +262,35 @@ performance:
             ## Only change this option if directed to by Realm support.
             # max_open_files: 256
           
          +## ----------------------------------------------------------------------------
          +
          +backup:
          +  ## The backup is a server that delivers continuous backup of the Realms in
          +  ## storage.root_path specified above. The backup is delivered to all connected
          +  ## backup clients. Backup clients must be started separately with network
          +  ## configuration parameters matching those of the server.
          +
          +  enable:
          +    ## Whether or not to enable the backup server.
          +    # enable: true
          +
          +  network:
          +    ## The address/interface on which the backup server should listen. This
          +    ## defaults to 127.0.0.1. If you wish to listen on all available interfaces,
          +    ## uncomment the following line.
          +    # listen_address: '0.0.0.0'
          +
          +    ## The port on which to listen. The backup server uses port 27810 by
          +    ## default. For most deployments, there should not be a need to change this.
          +    # listen_port: 27810
          +
          +  logging:
          +    ## The logging level of the backup server.
          +    ## The values are identical to the logging levels described above.
          +    ## The default level is 'info'.
          +    # level: 'info'
          +
          +    ## The file to which the synchronisation server should log. This should
          +    ## be a writable path from the perspective of the user under which the
          +    ## server runs. If no path is specified, the server will log to stdout.
          +    # path: '/var/log/realm-object-server-backup.log'
          diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js
          index c182652c39..c9ca2c2c5d 100755
          --- a/tools/sync_test_server/ros-testing-server.js
          +++ b/tools/sync_test_server/ros-testing-server.js
          @@ -1,6 +1,6 @@
           #!/usr/bin/env nodejs
           
          -var winston = require('winston');//logging
          +var winston = require('winston'); //logging
           const temp = require('temp');
           const spawn = require('child_process').spawn;
           var http = require('http');
          @@ -23,7 +23,7 @@ function handleRequest(request, response) {
               try {
                   //log the request on console
                   winston.log(request.url);
          -        //Disptach
          +        //Dispatch
                   dispatcher.dispatch(request, response);
               } catch(err) {
                   console.log(err);
          @@ -37,9 +37,13 @@ function startRealmObjectServer() {
               temp.mkdir('ros', function(err, path) {
                   if (!err) {
                       winston.info("Starting sync server in ", path);
          +            var env = Object.create( process.env );
          +            winston.info(env.NODE_ENV);
          +            env.NODE_ENV = 'development';
                       syncServerChildProcess = spawn('realm-object-server',
                               ['--root', path,
          -                    '--configuration', '/configuration.yml']);
          +                    '--configuration', '/configuration.yml'],
          +                    { env: env });
                       // local config:
                       syncServerChildProcess.stdout.on('data', (data) => {
                           winston.info(`stdout: ${data}`);
          diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh
          index 00930f5eb1..cf49b3247f 100755
          --- a/tools/sync_test_server/start_server.sh
          +++ b/tools/sync_test_server/start_server.sh
          @@ -7,11 +7,10 @@ ROS_DE_VERSION=$(grep REALM_OBJECT_SERVER_DE_VERSION $DOCKERFILE_DIR/../../depen
           
           TMP_DIR=$(mktemp -d /tmp/sync-test.XXXX) || { echo "Failed to mktemp $TEST_TEMP_DIR" ; exit 1 ; }
           
          -adb reverse tcp:7800 tcp:7800 && \
          -adb reverse tcp:8080 tcp:8080 && \
          +adb reverse tcp:9080 tcp:9080 && \
           adb reverse tcp:8888 tcp:8888 || { echo "Failed to reverse adb port." ; exit 1 ; }
           
           docker build $DOCKERFILE_DIR --build-arg ROS_DE_VERSION=$ROS_DE_VERSION -t sync-test-server || { echo "Failed to build Docker image." ; exit 1 ; }
           
           echo "See log files in $TMP_DIR"
          -docker run -p 8080:8080 -p 7800:7800 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server
          +docker run -p 9080:9080 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server
          
          From cc8f67073b5938f5c630500ef30901fbb0290689 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Tue, 17 Jan 2017 17:52:56 +0800
          Subject: [PATCH 0429/2110] More descriptions in the changelog
          
          ---
           CHANGELOG.md | 12 +++++++++++-
           1 file changed, 11 insertions(+), 1 deletion(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index b1b030a68e..bf0321eb01 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -2,13 +2,23 @@
           
           ### Breaking changes
           
          -* `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object.
          +* `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object (#2947).
          +
          +### Enhancements
          +
          +* Add support for sorting by link's field (#672).
           
           ### Object Server API Changes (In Beta)
           
           * Add a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`).
           * Change the order of arguments to SyncCredentials.custom to match iOS: token, provider, userInfo
           
          +### Internal
          +
          +* Use Object Store's `Results` as the backend for `RealmResults` (#3372).
          +  - Use Object Store's notification mechanism to trigger listeners.
          +  - Local commit triggers Realm global listener and `RealmObject` listener on current thread immediately instead of in the next event loop.
          +
           ## 2.2.3
           
           ### Bug fixes
          
          From 29c81649c720bbba522ca1ebb0954b356d21613b Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Tue, 17 Jan 2017 18:19:17 +0800
          Subject: [PATCH 0430/2110] Fix an incomplete test
          
          ---
           .../java/io/realm/internal/RealmNotifierTests.java        | 8 ++++++--
           1 file changed, 6 insertions(+), 2 deletions(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java
          index edae90e47a..39e550701e 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java
          @@ -24,6 +24,7 @@
           import org.junit.Test;
           import org.junit.runner.RunWith;
           
          +import java.util.concurrent.atomic.AtomicBoolean;
           import java.util.concurrent.atomic.AtomicInteger;
           
           import io.realm.RealmChangeListener;
          @@ -34,6 +35,7 @@
           import io.realm.rule.TestRealmConfigurationFactory;
           
           import static junit.framework.Assert.assertEquals;
          +import static junit.framework.Assert.assertFalse;
           import static junit.framework.Assert.fail;
           
           @RunWith(AndroidJUnit4.class)
          @@ -99,22 +101,24 @@ public void run() {
                   });
               }
           
          +    // Callback is immediately called when commitTransaction for local changes.
               @Test
               @RunTestInLooperThread
               public void addChangeListener_byLocalChanges() {
          +        final AtomicBoolean commitReturns = new AtomicBoolean(false);
                   SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration);
                   sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() {
                       @Override
                       public void onChange(SharedRealm sharedRealm) {
                           // Transaction has been committed in core, but commitTransaction hasn't returned in java.
          -                // Need a flag in java.
          -                //assertTrue(sharedRealm.isInTransaction());
          +                assertFalse(commitReturns.get());
                           looperThread.testComplete();
                           sharedRealm.close();
                       }
                   });
                   sharedRealm.beginTransaction();
                   sharedRealm.commitTransaction();
          +        commitReturns.set(true);
               }
           
               private void makeRemoteChanges(final RealmConfiguration config) {
          
          From c63dfe62d7c68e9f8386cb4608acb46eae3bf304 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Tue, 17 Jan 2017 19:54:32 +0800
          Subject: [PATCH 0431/2110] Fix various minor issues
          
          ---
           .../internal/TableIndexAndDistinctTest.java      | 16 ----------------
           realm/realm-library/src/main/cpp/util.hpp        |  4 ++--
           .../src/main/java/io/realm/BaseRealm.java        |  2 --
           .../main/java/io/realm/internal/Collection.java  |  2 +-
           .../main/java/io/realm/internal/TableQuery.java  |  1 -
           5 files changed, 3 insertions(+), 22 deletions(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java
          index d2a64b2e2a..8e285856bb 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java
          @@ -39,22 +39,6 @@ void init() {
                   assertEquals(7, table.size());
               }
           
          -    // FIXME: Check or delete this.
          -// TODO: parametric test
          -/*    *//**
          -     * Should throw exception if trying to get distinct on columns where index has not been set
          -     * @param index
          -     *//*
          -
          -    @Test(expectedExceptions = UnsupportedOperationException.class, dataProvider = "columnIndex")
          -    public void shouldTestDistinctErrorWhenNoIndex(Long index) {
          -
          -        //Get a table with all available column types
          -        Table t = TestHelper.getTableWithAllColumnTypes();
          -
          -        TableView view = table.getDistinctView(1);
          -    }*/
          -
               /**
                * Check that Index can be set on multiple columns, with the String
                * @param
          diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp
          index 38d7c1157b..e437ee3429 100644
          --- a/realm/realm-library/src/main/cpp/util.hpp
          +++ b/realm/realm-library/src/main/cpp/util.hpp
          @@ -556,9 +556,9 @@ class JniArrayOfArrays {
               JniArrayOfArrays(JNIEnv* env, jobjectArray javaArray)
                       : m_env(env)
                       , m_javaArray(javaArray)
          -            , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray))
          +            , m_arrayLength(javaArray == nullptr ? 0 : env->GetArrayLength(javaArray))
               {
          -        for (int i = 0; i < m_arrayLength; i++) {
          +        for (int i = 0; i < m_arrayLength; ++i) {
                       // No type checking. Internal use only.
                       J j_array = static_cast(env->GetObjectArrayElement(m_javaArray, i));
                       m_array.push_back(T(env, j_array));
          diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          index 49ebc4b91c..0f17d8f59b 100644
          --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          @@ -245,8 +245,6 @@ public boolean waitForChange() {
                   if (hasChanged) {
                       // Since this Realm instance has been waiting for change, advance realm & refresh realm.
                       sharedRealm.refresh();
          -            // FIXME: CHECK THIS!!! Maybe call OS SharedRealm.refresh()?
          -            //handlerController.refreshSynchronousTableViews();
                   }
                   return hasChanged;
               }
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          index 6e54703fca..0212edc975 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          @@ -25,7 +25,7 @@
           import io.realm.RealmChangeListener;
           
           /**
          - * Java wrapper of OS Results class.
          + * Java wrapper of Object Store Results class.
            * It is the backend of binding's query results, link list and back links.
            */
           @Keep
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java
          index 739b26d257..41218e642a 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java
          @@ -56,7 +56,6 @@ public long getNativeFinalizerPtr() {
                   return nativeFinalizerPtr;
               }
           
          -    // FIXME: Hide this?
               public Table getTable() {
                   return table;
               }
          
          From f2dcbaf16490a7d86f7a5bb9e8222aa47203b761 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Tue, 17 Jan 2017 20:05:25 +0800
          Subject: [PATCH 0432/2110] Remove a testing method
          
          ---
           .../src/main/java/io/realm/internal/Collection.java       | 6 +++---
           .../src/main/java/io/realm/internal/UncheckedRow.java     | 8 --------
           2 files changed, 3 insertions(+), 11 deletions(-)
          
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          index 0212edc975..d2ee720cfa 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          @@ -200,15 +200,15 @@ public long getNativeFinalizerPtr() {
               }
           
               public UncheckedRow getUncheckedRow(int index) {
          -        return UncheckedRow.getByRowPointer(table, nativeGetRow(nativePtr, index));
          +        return table.getUncheckedRowByPointer(nativeGetRow(nativePtr, index));
               }
           
               public UncheckedRow firstUncheckedRow() {
          -        return UncheckedRow.getByRowPointer(table, nativeFirstRow(nativePtr));
          +        return table.getUncheckedRowByPointer(nativeFirstRow(nativePtr));
               }
           
               public UncheckedRow lastUncheckedRow() {
          -        return UncheckedRow.getByRowPointer(table, nativeLastRow(nativePtr));
          +        return table.getUncheckedRowByPointer(nativeLastRow(nativePtr));
               }
           
               public Table getTable() {
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java
          index 5b5b441218..ff192e02cd 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java
          @@ -87,14 +87,6 @@ static UncheckedRow getByRowPointer(Context context, Table table, long nativeRow
                   return new UncheckedRow(context, table, nativeRowPointer);
               }
           
          -    // FIXME: Testing code
          -    public static UncheckedRow getByRowPointer(Table table, long nativeRowPointer) {
          -        if (nativeRowPointer != 0) {
          -            return new UncheckedRow(table.context, table, nativeRowPointer);
          -        }
          -        return null;
          -    }
          -
               /**
                * Gets the row object associated to an index in a LinkView.
                *
          
          From 171565219ebda900cf7ad4c25afb4990d6a8f01f Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Tue, 17 Jan 2017 20:07:32 +0800
          Subject: [PATCH 0433/2110] Remove set_auto_refresh FIXME
          
          It is not necessary. The only place we might need it is the async
          transaction. But as long as we close the SharedRealm instance
          immediately, there is no difference by setting it or not.
          ---
           .../src/main/cpp/io_realm_internal_SharedRealm.cpp             | 3 ---
           1 file changed, 3 deletions(-)
          
          diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp
          index 188adf856f..1897c5e7c8 100644
          --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp
          +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp
          @@ -101,9 +101,6 @@ Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlo
               try {
                   auto shared_realm = Realm::get_shared_realm(*config);
                   shared_realm->m_binding_context = JavaBindingContext::create(env, realm_notifier);
          -        // FIXME: Disabled for the collection notifications. There might be some places still need it.
          -        // advance_read needs to be handled by Java because of async query.
          -        //shared_realm->set_auto_refresh(false);
                   return reinterpret_cast(new SharedRealm(std::move(shared_realm)));
               } CATCH_STD()
               return static_cast(NULL);
          
          From 461c157134c9921effcf53b929bcc8ad5358b02e Mon Sep 17 00:00:00 2001
          From: Nabil Hachicha 
          Date: Wed, 18 Jan 2017 14:44:41 +0000
          Subject: [PATCH 0434/2110] Nh/init metadata (#4053)
          
          * init ObjectStore metadata, to store File Action
          
          * avoid null pointer exception, by passing empty strings
          ---
           realm/realm-library/src/main/cpp/CMakeLists.txt     |  6 ++++--
           .../src/main/cpp/io_realm_SyncManager.cpp           | 13 +++++++++++++
           realm/realm-library/src/main/cpp/object-store       |  2 +-
           .../src/main/cpp/objectserver_shared.hpp            |  4 ++--
           .../objectServer/java/io/realm/ObjectServer.java    |  5 +++++
           .../src/objectServer/java/io/realm/SyncManager.java |  3 ++-
           6 files changed, 27 insertions(+), 6 deletions(-)
          
          diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt
          index 8ba8566b5b..01b97067a2 100644
          --- a/realm/realm-library/src/main/cpp/CMakeLists.txt
          +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt
          @@ -170,8 +170,10 @@ file(GLOB objectstore_SRC
           # Sync needed Object Store files
           if (build_SYNC)
               file(GLOB objectstore_sync_SRC
          -        "object-store/src/sync/*"
          -        "object-store/src/sync/impl/*")
          +        "object-store/src/results.cpp"
          +        "object-store/src/impl/results_notifier.cpp"
          +        "object-store/src/sync/*.cpp"
          +        "object-store/src/sync/impl/*.cpp")
           endif()
           
           add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC})
          diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp
          index 17406c075b..fb45d1936f 100644
          --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp
          +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp
          @@ -31,6 +31,9 @@
           
           #include "jni_util/log.hpp"
           #include "jni_util/jni_utils.hpp"
          +#include "sync/sync_manager.hpp"
          +#include "sync/sync_user.hpp"
          +#include "util.hpp"
           
           using namespace realm;
           using namespace realm::sync;
          @@ -59,3 +62,13 @@ Java_io_realm_SyncManager_nativeRunClient(JNIEnv *env, jclass)
                   sync_client->run();
               } CATCH_STD()
           }
          +
          +JNIEXPORT void JNICALL
          +Java_io_realm_SyncManager_nativeConfigureMetaDataSystem(JNIEnv *env, jclass,
          +                                                        jstring baseFile) {
          +    TR_ENTER()
          +    try {
          +        JStringAccessor base_file_path(env, baseFile); // throws
          +        SyncManager::shared().configure_file_system(base_file_path, SyncManager::MetadataMode::NoEncryption);
          +    } CATCH_STD()
          +}
          diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store
          index 163c1e8fb0..ac2f607264 160000
          --- a/realm/realm-library/src/main/cpp/object-store
          +++ b/realm/realm-library/src/main/cpp/object-store
          @@ -1 +1 @@
          -Subproject commit 163c1e8fb026a05d281e82096e64622d2e735f17
          +Subproject commit ac2f60726434654cace0bb5e57a92df7555c8f1f
          diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp
          index bbafb6061f..2efb7c8b74 100644
          --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp
          +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp
          @@ -101,8 +101,8 @@ class JniSession {
                               realm::SyncFileActionMetadata(manager,
                                                             realm::SyncFileActionMetadata::Action::HandleRealmForClientReset,
                                                             original_path,
          -                                                  nullptr,
          -                                                  nullptr,
          +                                                  "",
          +                                                  "",
                                                             realm::util::Optional(
                                                                     std::move(recovery_path)));
                           });
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java
          index 38b0c0c1cb..53900c6d63 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java
          @@ -46,5 +46,10 @@ public static void init(Context context) {
                   UserStore userStore = new SharedPrefsUserStore(context);
           
                   SyncManager.init(appId, userStore);
          +
          +        // init the "sync_manager.cpp" metadata Realm, this is also needed later, when re try
          +        // to schedule a client reset. in realm-java#master this is already done, when initialising
          +        // the RealmFileUserStore (not available now on releases)
          +        SyncManager.nativeConfigureMetaDataSystem(context.getFilesDir().getPath());
               }
           }
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java
          index f3fe17bc42..e4fcc30509 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java
          @@ -221,5 +221,6 @@ static void notifyUserLoggedOut(SyncUser user) {
           
               private static native void nativeInitializeSyncClient();
               private static native void nativeRunClient();
          -
          +    // init and load the Metadata Realm containing SyncUsers
          +    protected static native void nativeConfigureMetaDataSystem(String baseFile);
           }
          
          From af7f660e13ae3ae7f139e8bcf11f27730e8a66a0 Mon Sep 17 00:00:00 2001
          From: Makoto Yamazaki 
          Date: Thu, 19 Jan 2017 01:41:47 +0900
          Subject: [PATCH 0435/2110] moved changelog entry of like predicate to 2.3
           (#4064)
          
          ---
           CHANGELOG.md | 5 ++++-
           1 file changed, 4 insertions(+), 1 deletion(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index ef458f263e..7dc20ef37a 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -6,6 +6,10 @@
           * Change the order of arguments to SyncCredentials.custom to match iOS: token, provider, userInfo
           * `SyncUser.all()` now returns Map instead of List.
           
          +### Enhancements
          +
          +* Add `like` predicate for String fields (#3752).
          +
           ## 2.2.3
           
           ### Bug fixes
          @@ -45,7 +49,6 @@
           * All major public classes are now non-final. This is mostly a compromise to support Mockito. All protected fields/methods are still not considered part of the public API and can change without notice (#3869).
           * All Realm instances share a single notification daemon thread.
           * Fixed Java lint warnings with generated proxy classes (#2929).
          -* Add 'like' predicate for String fields (#3752)
           
           ### Internal
           
          
          From 5342b0725d2fda79b572e070a1337ed6f19ce6c2 Mon Sep 17 00:00:00 2001
          From: "G. Blake Meike" 
          Date: Wed, 18 Jan 2017 13:56:10 -0800
          Subject: [PATCH 0436/2110] Update the documentation for downloading the NDK.
           (#4061)
          
          * Update the documentation for downloading the NDK.
          Fixed #4057
          
          * Respond to comments
          ---
           README.md | 19 ++++++++-----------
           1 file changed, 8 insertions(+), 11 deletions(-)
          
          diff --git a/README.md b/README.md
          index 3e1b64441d..b4dc035638 100644
          --- a/README.md
          +++ b/README.md
          @@ -61,26 +61,23 @@ In case you don't want to use the precompiled version, you can build Realm yours
           
            * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it.
            * Download & install the Android SDK **Build-Tools 24.0.0**, **Android N (API 24)** (for example through Android Studio’s **Android SDK Manager**).
          - * Download the **Android NDK (= r10e)** for [OS X](http://dl.google.com/android/ndk/android-ndk-r10e-darwin-x86_64.bin) or [Linux](http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin).
            * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake").
          - * Or you can use [Hombrew-versions](https://github.com/Homebrew/homebrew-versions) to install Android NDK for Mac:
           
          -    ```
          -    brew tap homebrew/versions
          -    brew install android-ndk-r10e
          -    ```
          + * Realm currently requires version r10e of the NDK.  Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html).
          +You may unzip the file wherever you choose.  For OSX, a suggested location is `~/Library/Android`.  The download will unzip as the directory `android-ndk-r10e`.
           
          - * Add two environment variables to your profile (presuming you used brew to install the NDK):
          + * If you will be building with Android Studio, you will need to tell it to use the correct NDK.  To do this, define the variable `ndk.dir` in `realm/local.properties` and assign it the full path name of the directory that you unzipped above.  Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited.
           
               ```
          -    export ANDROID_HOME=~/Library/Android/sdk
          -    export ANDROID_NDK_HOME=/usr/local/Cellar/android-ndk-r10e/r10e
          +    ndk.dir=/Users/brian/Library/Android/android-ndk-r10e/r10e
          +
               ```
           
          - * If you want to build with Android Studio, `ndk.dir` has to be defined in the `realm/local.properties` as well.  Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited.  Again, presuming you used brew to install the NDK:
          + * Add two environment variables to your profile (presuming you installed the NDK in `~/Library/android-ndk-r10e`):
           
               ```
          -    ndk.dir=/usr/local/Cellar/android-ndk-r10e/r10e
          +    export ANDROID_HOME=~/Library/Android/sdk
          +    export ANDROID_NDK_HOME=~/Library/Android/android-ndk-r10e
               ```
           
            * If you will be launching Android Studio from the OS X Finder, you should also run the following two commands:
          
          From c6e4bf1e57d862a65cc0f892f0671913cee54ef6 Mon Sep 17 00:00:00 2001
          From: Makoto Yamazaki 
          Date: Thu, 19 Jan 2017 07:48:04 +0900
          Subject: [PATCH 0437/2110] Add multiple users support to UserStore (#4056)
          
          ---
           CHANGELOG.md                                  |  9 +++--
           .../java/io/realm/SyncManagerTests.java       |  9 ++++-
           .../main/cpp/io_realm_RealmFileUserStore.cpp  | 38 +++++++++++++------
           .../java/io/realm/RealmFileUserStore.java     | 34 ++++++++++++-----
           .../objectServer/java/io/realm/SyncUser.java  |  4 +-
           .../objectServer/java/io/realm/UserStore.java | 28 ++++++++------
           6 files changed, 83 insertions(+), 39 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 7dc20ef37a..e91c1556f6 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -2,9 +2,10 @@
           
           ### Object Server API Changes (In Beta)
           
          -* Add a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`).
          -* Change the order of arguments to SyncCredentials.custom to match iOS: token, provider, userInfo
          -* `SyncUser.all()` now returns Map instead of List.
          +* Added a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`).
          +* Added multi-user support to `UserStore`. Added `get(String)` and `remove(String)`, removed `remove()` and renamed `get()` to `getCurrent()`.
          +* Changed the order of arguments to `SyncCredentials.custom()` to match iOS: token, provider, userInfo.
          +* `SyncUser.all()` now returns `Map` instead of `List`.
           
           ### Enhancements
           
          @@ -27,7 +28,7 @@
           ### Internal
           
           * Updated Realm Sync to 1.0.0-BETA-7.2.
          -* Add a Realm backup when receiving a Sync client reset message from the server.
          +* Added a Realm backup when receiving a Sync client reset message from the server.
           
           ## 2.2.2
           
          diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java
          index 510bfca5aa..47fb8dd437 100644
          --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java
          +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java
          @@ -51,12 +51,17 @@ public void setUp() {
                       public void put(SyncUser user) {}
           
                       @Override
          -            public SyncUser get() {
          +            public SyncUser getCurrent() {
                           return null;
                       }
           
                       @Override
          -            public void remove() {}
          +            public SyncUser get(String identity) {
          +                return null;
          +            }
          +
          +            @Override
          +            public void remove(String identity) {}
           
                       @Override
                       public Collection allUsers() {
          diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp
          index a3f98fc0ca..e4a0a4a60f 100644
          --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp
          +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp
          @@ -23,20 +23,37 @@
           
           using namespace realm;
           
          -static const char* ERR_NO_LOGGED_IN_USER = "No user logged in yet.";
           static const char* ERR_COULD_NOT_ALLOCATE_MEMORY = "Could not allocate memory to return all users.";
           
          +static jstring
          +to_user_string_or_null (JNIEnv *env, const std::shared_ptr& user)
          +{
          +    if (user) {
          +        return to_jstring(env, user->refresh_token().data());
          +    } else {
          +        return nullptr;
          +    }
          +}
          +
           JNIEXPORT jstring JNICALL
           Java_io_realm_RealmFileUserStore_nativeGetCurrentUser (JNIEnv *env, jclass)
           {
               TR_ENTER()
               try {
          -        const std::shared_ptr &user = SyncManager::shared().get_current_user();
          -        if (user) {
          -            return to_jstring(env, user->refresh_token().data());
          -        } else {
          -            return nullptr;
          -        }
          +        const std::shared_ptr& user = SyncManager::shared().get_current_user();
          +        return to_user_string_or_null(env, user);
          +    } CATCH_STD()
          +    return nullptr;
          +}
          +
          +JNIEXPORT jstring JNICALL
          +Java_io_realm_RealmFileUserStore_nativeGetUser (JNIEnv *env, jclass, jstring identity)
          +{
          +    TR_ENTER()
          +    try {
          +        JStringAccessor id(env, identity); // throws
          +        const std::shared_ptr& user = SyncManager::shared().get_existing_logged_in_user(id);
          +        return to_user_string_or_null(env, user);
               } CATCH_STD()
               return nullptr;
           }
          @@ -55,15 +72,14 @@ Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser (JNIEnv *env, jclass,
           }
           
           JNIEXPORT void JNICALL
          -Java_io_realm_RealmFileUserStore_nativeLogoutCurrentUser (JNIEnv *env, jclass)
          +Java_io_realm_RealmFileUserStore_nativeLogoutUser (JNIEnv *env, jclass, jstring identity)
           {
               TR_ENTER()
               try {
          -        const std::shared_ptr& user = SyncManager::shared().get_current_user();
          +        JStringAccessor id(env, identity); // throws
          +        const std::shared_ptr& user = SyncManager::shared().get_existing_logged_in_user(id);
                   if (user) {
                       user->log_out();
          -        } else {
          -            throw std::runtime_error(ERR_NO_LOGGED_IN_USER);
                   }
               } CATCH_STD()
           }
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java
          index 0d89455e30..9ba14bff4e 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java
          @@ -42,20 +42,26 @@ public void put(SyncUser user) {
                * {@inheritDoc}
                */
               @Override
          -    public SyncUser get() {
          +    public SyncUser getCurrent() {
                   String userJson = nativeGetCurrentUser();
          -        if (userJson != null) {
          -            return SyncUser.fromJson(userJson);
          -        }
          -        return null;
          +        return toSyncUserOrNull(userJson);
               }
           
               /**
                * {@inheritDoc}
                */
               @Override
          -    public void remove() {
          -        nativeLogoutCurrentUser();
          +    public SyncUser get(String identity) {
          +        String userJson = nativeGetUser(identity);
          +        return toSyncUserOrNull(userJson);
          +    }
          +
          +    /**
          +     * {@inheritDoc}
          +     */
          +    @Override
          +    public void remove(String identity) {
          +        nativeLogoutUser(identity);
               }
           
               /**
          @@ -74,17 +80,27 @@ public Collection allUsers() {
                   return Collections.emptyList();
               }
           
          +    private static SyncUser toSyncUserOrNull(String userJson) {
          +        if (userJson == null) {
          +            return null;
          +        }
          +        return SyncUser.fromJson(userJson);
          +    }
          +
               // init and load the Metadata Realm containing SyncUsers
               protected static native void nativeConfigureMetaDataSystem(String baseFile);
           
          -    // return json data (token) of the current logged in user
          +    // returns json data (token) of the current logged in user
               protected static native String nativeGetCurrentUser();
           
          +    // returns json data (token) of the specified user
          +    protected static native String nativeGetUser(String identity);
          +
               protected static native String[] nativeGetAllUsers();
           
               protected static native void nativeUpdateOrCreateUser(String identity, String jsonToken, String url);
           
          -    protected static native void nativeLogoutCurrentUser();
          +    protected static native void nativeLogoutUser(String identity);
           
               // Should only be called for tests
               static native void nativeResetForTesting();
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          index 8d64d7da16..91f85f79a1 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          @@ -96,7 +96,7 @@ private SyncUser(ObjectServerUser user) {
                * @throws IllegalStateException if multiple users are logged in.
                */
               public static SyncUser currentUser() {
          -        SyncUser user = SyncManager.getUserStore().get();
          +        SyncUser user = SyncManager.getUserStore().getCurrent();
                   if (user != null && user.isValid()) {
                       return user;
                   }
          @@ -299,7 +299,7 @@ public void logout() {
                       // FIXME We still need to cache the user token so it can be revoked.
                       syncUser.clearTokens();
           
          -            SyncManager.getUserStore().remove();
          +            SyncManager.getUserStore().remove(syncUser.getIdentity());
           
                       // Delete all Realms if needed.
                       for (ObjectServerUser.AccessDescription desc : syncUser.getRealms()) {
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java
          index 052f3759b9..7e566b3942 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java
          @@ -43,22 +43,28 @@ public interface UserStore {
           
               /**
                * Retrieves the current {@link SyncUser}.
          +     * 

          + * This method will throw an exception if more than one valid, logged in users exist. + * @return {@link SyncUser} object or {@code null} if not found. + */ + SyncUser getCurrent(); + + /** + * Retrieves specified {@link SyncUser}. * - * For now, current User cannot be called if more that one valid, logged in user - * exists, it will throw an exception. + * @param identity identity of the user. + * @return {@link SyncUser} object or {@code null} if not found. */ - //TODO when ObjectStore integration of SyncManager is completed & multiple - // users are allowed, consider passing the User identity to lookup apply - // the operation to a particular user. - SyncUser get(); + SyncUser get(String identity); /** - * Removes the current user from the store. + * Removes the user from the store. + *

          + * If the user is not found, this method does nothing. + * + * @param identity identity of the user. */ - //TODO when ObjectStore integration of SyncManager is completed & multiple - // users are allowed, consider passing the User identity to lookup apply - // the operation to a particular user. - void remove(); + void remove(String identity); /** * Returns a collection of all users saved in the User store. From 149c70af044d21ab9bfae0350e56a1d603974695 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 19 Jan 2017 06:38:18 +0100 Subject: [PATCH 0438/2110] Adding temp. solution for issue #3651 (#4067) --- CHANGELOG.md | 1 + realm/realm-library/src/main/cpp/object-store | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac97e470b..e107a611ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Activated Realm's annotation processor on connectedTest when the project is using kapt (#4008). * Fixed bug, preventing Sync client to renew the access token (#4038) (#4039). * Fixed "too many open files" issue (#4002). +* Added temporary work-around for bug crashing Samsung Tab 3 devices on startup (#3651). ### Object Server API Changes (In Beta) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index ac2f607264..3ee17d16b4 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit ac2f60726434654cace0bb5e57a92df7555c8f1f +Subproject commit 3ee17d16b4f484a8cb673d8e40477f14f50649d8 From 8b362779fc7da2680031705171e636a4a57b8081 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 19 Jan 2017 16:00:24 +0800 Subject: [PATCH 0439/2110] Update Object Store for client reset issue Update it to ebadc31fd5 For #4053 --- realm/realm-library/src/main/cpp/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 3ee17d16b4..ebadc31fd5 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 3ee17d16b4f484a8cb673d8e40477f14f50649d8 +Subproject commit ebadc31fd59c51f4eb6f86f974c0e4d915ed09e6 From c06680409bccd628d8f4543884e0498387ca4ba3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 19 Jan 2017 17:50:07 +0800 Subject: [PATCH 0440/2110] Print the path when get File::AccessError (#4068) --- realm/realm-library/src/main/cpp/util.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 840e0463cf..ee2ee17f0d 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include "utf8.hpp" @@ -70,6 +71,10 @@ void ConvertException(JNIEnv* env, const char *file, int line) ss << e.what() << " (" << e.underlying() << ") in " << file << " line " << line; ThrowRealmFileException(env, ss.str(), e.kind()); } + catch (File::AccessError& e) { + ss << e.what() << " (" << e.get_path() << ") in " << file << " line " << line; + ThrowException(env, FatalError, ss.str()); + } catch (InvalidTransactionException& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalState, ss.str()); From 8554e8110b8a8be7b685818c22becd7a35492012 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 19 Jan 2017 12:09:25 +0100 Subject: [PATCH 0441/2110] Preparing 2.3.0 (#4071) * Updating to Realm Sync v1.0.0 * folding unreleased version into 2.3.0 --- CHANGELOG.md | 26 ++++++++----------- dependencies.list | 6 ++--- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/AuthenticationListener.java | 4 --- .../objectServer/java/io/realm/ErrorCode.java | 4 --- .../java/io/realm/ObjectServer.java | 3 --- .../java/io/realm/ObjectServerError.java | 3 --- .../java/io/realm/SessionState.java | 4 --- .../java/io/realm/SyncConfiguration.java | 3 --- .../java/io/realm/SyncCredentials.java | 4 --- .../java/io/realm/SyncManager.java | 3 --- .../java/io/realm/SyncSession.java | 3 --- .../objectServer/java/io/realm/SyncUser.java | 3 --- .../objectServer/java/io/realm/UserStore.java | 4 --- 14 files changed, 15 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d67d75871..1e856a84b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,5 @@ ## 2.3.0 -### Object Server API Changes (In Beta) - -* Added a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`). -* Added multi-user support to `UserStore`. Added `get(String)` and `remove(String)`, removed `remove()` and renamed `get()` to `getCurrent()`. -* Changed the order of arguments to `SyncCredentials.custom()` to match iOS: token, provider, userInfo. -* `SyncUser.all()` now returns `Map` instead of `List`. - -### Enhancements - -* Add `like` predicate for String fields (#3752). - -## 2.2.3 - ### Bug fixes * Fixed native memory leak setting the value of a primary key (#3993). @@ -21,14 +8,23 @@ * Fixed "too many open files" issue (#4002). * Added temporary work-around for bug crashing Samsung Tab 3 devices on startup (#3651). -### Object Server API Changes (In Beta) +### Object Server API Changes +* Realm Sync v1.0.0 has been released, and Realm Mobile Platform is no longer considered in beta. +* Added a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`). +* Added multi-user support to `UserStore`. Added `get(String)` and `remove(String)`, removed `remove()` and renamed `get()` to `getCurrent()`. +* Changed the order of arguments to `SyncCredentials.custom()` to match iOS: token, provider, userInfo. +* `SyncUser.all()` now returns `Map` instead of `List`. * Exceptions thrown in error handlers are ignored but logged (#3559). * Removed unused public constants in `SyncConfiguration` (#4047). +### Enhancements + +* Add `like` predicate for String fields (#3752). + ### Internal -* Updated Realm Sync to 1.0.0-BETA-7.2. +* Updated to Realm Sync v1.0.0. * Added a Realm backup when receiving a Sync client reset message from the server. ## 2.2.2 diff --git a/dependencies.list b/dependencies.list index 20a4a9ecb9..daca48ea06 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,10 +1,10 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.0-BETA-7.2 -REALM_SYNC_SHA256=d2474e81e1e820d19e16c208c410b10cc7e22f3791f9e9d3d0745c259754b238 +REALM_SYNC_VERSION=1.0.0 +REALM_SYNC_SHA256=0e95ee9ed06f1bf66d1531086197e6549ad7ca36da6400b0c5026c5a0c3c4249 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. # https://packagecloud.io/realm/realm?filter=debs # https://packagecloud.io/realm/realm-testing?filter=debs -REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-5.1-74 +REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-6.1-133 diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index ebadc31fd5..0ebca0f031 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit ebadc31fd59c51f4eb6f86f974c0e4d915ed09e6 +Subproject commit 0ebca0f03141f52131d6d9f0e6b2f2e32ab9a56d diff --git a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java b/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java index c769a7d693..3602509d82 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java @@ -16,13 +16,9 @@ package io.realm; -import io.realm.annotations.Beta; - /** - * @Beta * Interface describing events related to Users and their authentication */ -@Beta public interface AuthenticationListener { /** * A user was logged into the Object Server diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index ad4f603734..4c443e314f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -16,13 +16,9 @@ package io.realm; -import io.realm.annotations.Beta; - /** - * @Beta * This class enumerate all potential errors related to using the Object Server or synchronizing data. */ -@Beta public enum ErrorCode { // See https://github.com/realm/realm-sync/blob/master/doc/protocol_16.md diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index 47c86d8c78..5ab8d01656 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -19,17 +19,14 @@ import android.content.Context; import android.content.pm.PackageInfo; -import io.realm.annotations.Beta; import io.realm.internal.Keep; /** - * @Beta * Internal initializer class for the Object Server. * Use to keep the `SyncManager` free from Android dependencies */ @SuppressWarnings("unused") @Keep -@Beta class ObjectServer { public static void init(Context context) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java index 56109037f8..90cdc9c65b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java @@ -16,11 +16,9 @@ package io.realm; -import io.realm.annotations.Beta; import io.realm.internal.Util; /** - * @Beta * This class is a wrapper for all errors happening when communicating with the Realm Object Server. * This include both exceptions and protocol errors. * @@ -30,7 +28,6 @@ * * @see ErrorCode for a list of possible errors. */ -@Beta public class ObjectServerError extends RuntimeException { private final ErrorCode error; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SessionState.java b/realm/realm-library/src/objectServer/java/io/realm/SessionState.java index 9ede02c3fc..d3e167eae7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SessionState.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SessionState.java @@ -16,13 +16,9 @@ package io.realm; -import io.realm.annotations.Beta; - /** - * @Beta * Enum describing the various states the Session Finite-State-Machine can be in. */ -@Beta public enum SessionState { INITIAL, // Initial starting state UNBOUND, // Start done, Realm is unbound. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 3b3b5e5e57..fe7e7ead5d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -29,7 +29,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.RealmProxyMediator; @@ -40,7 +39,6 @@ import io.realm.rx.RxObservableFactory; /** - * @Beta * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm * Object Server. *

          @@ -70,7 +68,6 @@ * Synchronized Realms are created by using {@link Realm#getInstance(RealmConfiguration)} and * {@link Realm#getDefaultInstance()} like ordinary unsynchronized Realms. */ -@Beta public class SyncConfiguration extends RealmConfiguration { // The FAT file system has limitations of length. Also, not all characters are permitted. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index ff97918de8..3df64989ff 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -20,10 +20,7 @@ import java.util.HashMap; import java.util.Map; -import io.realm.annotations.Beta; - /** - * @Beta * Credentials represent a login with a 3rd party login provider in an OAuth2 login flow, and are used by the Realm * Object Server to verify the user and grant access. *

          @@ -63,7 +60,6 @@ * } *

          */ -@Beta public class SyncCredentials { private final String userIdentifier; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index c3c38418ea..10a2e1619c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -22,7 +22,6 @@ import java.util.concurrent.TimeUnit; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.annotations.Beta; import io.realm.internal.Keep; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.OkHttpAuthenticationServer; @@ -31,7 +30,6 @@ import io.realm.log.RealmLog; /** - * @Beta * The SyncManager is the central controller for interacting with the Realm Object Server. * It handles the creation of {@link SyncSession}s and it is possible to configure session defaults and the underlying * network client using this class. @@ -43,7 +41,6 @@ * */ @Keep -@Beta @SuppressFBWarnings("MS_CANNOT_BE_FINAL") public class SyncManager { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 046295c876..ad5ff24e39 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -18,13 +18,11 @@ import java.net.URI; -import io.realm.annotations.Beta; import io.realm.internal.Keep; import io.realm.log.RealmLog; import io.realm.internal.objectserver.ObjectServerSession; /** - * @Beta * This class represents the connection to the Realm Object Server for one {@link SyncConfiguration}. *

          * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening @@ -39,7 +37,6 @@ * @see SessionState */ @Keep -@Beta public class SyncSession { private final ObjectServerSession osSession; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 91f85f79a1..c658dd4c0d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -35,7 +35,6 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; -import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.network.AuthenticateResponse; @@ -48,7 +47,6 @@ import io.realm.permissions.PermissionModule; /** - * @Beta * This class represents a user on the Realm Object Server. The credentials are provided by various 3rd party * providers (Facebook, Google, etc.). *

          @@ -59,7 +57,6 @@ * Persisting a user between sessions, the user's credentials are stored locally on the device, and should be treated * as sensitive data. */ -@Beta public class SyncUser { private static class ManagementConfig { diff --git a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java index 7e566b3942..7a7b488337 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java @@ -18,10 +18,7 @@ import java.util.Collection; -import io.realm.annotations.Beta; - /** - * @Beta * Interface for classes responsible for saving and retrieving Object Server users again. *

          * Any implementation of a User Store is expected to not perform lengthy blocking operations as it might @@ -30,7 +27,6 @@ * @see SyncManager#setUserStore(UserStore) * @see RealmFileUserStore */ -@Beta public interface UserStore { /** From a7a6b423cd040e43753343dcff18c58ab8831e8b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 19 Jan 2017 12:21:48 +0100 Subject: [PATCH 0442/2110] PermissionOffer/PermissionOfferResponse support (#4005) Added support for PermissionOffer and PermissionOfferResponse --- CHANGELOG.md | 25 +-- realm/config/findbugs/findbugs-filter.xml | 7 + .../androidTest/java/io/realm/TestHelper.java | 2 +- .../java/io/realm/rule/RunInLooperThread.java | 13 +- .../java/io/realm/SyncConfigurationTests.java | 35 +++- .../java/io/realm/util/SyncTestUtils.java | 29 ++-- .../java/io/realm/SyncConfiguration.java | 4 +- .../java/io/realm/SyncCredentials.java | 2 +- .../objectServer/java/io/realm/SyncUser.java | 10 +- .../network/OkHttpAuthenticationServer.java | 1 + .../realm/permissions/PermissionModule.java | 2 +- .../io/realm/permissions/PermissionOffer.java | 143 ++++++++++++++++ .../permissions/PermissionOfferResponse.java | 121 +++++++++++++ .../java/io/realm/objectserver/AuthTests.java | 2 +- .../objectserver/ManagementRealmTests.java | 161 ++++++++++++++++++ .../realm/objectserver/utils/HttpUtils.java | 4 + .../realm/objectserver/utils/UserFactory.java | 6 +- 17 files changed, 530 insertions(+), 37 deletions(-) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOfferResponse.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e856a84b2..12dcbd5f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,26 @@ ## 2.3.0 +### Object Server API Changes + +* Realm Sync v1.0.0 has been released, and Realm Mobile Platform is no longer considered in beta. +* Breaking change: Location of Realm files are now placed in `getFilesDir()/` instead of `getFilesDir()/`. + This is done in order to support shared Realms among users, while each user retaining their own local copy. +* Breaking change: `SyncUser.all()` now returns Map instead of List. +* Breaking change: Added a default `UserStore` saving users in a Realm file (`RealmFileUserStore`). +* Breaking change: Added multi-user support to `UserStore`. Added `get(String)` and `remove(String)`, removed `remove()` and renamed `get()` to `getCurrent()`. +* Breaking change: Changed the order of arguments to `SyncCredentials.custom()` to match iOS: token, provider, userInfo. +* Added support for `PermissionOffer` and `PermissionOfferResponse` to `SyncUser.getManagementRealm()`. +* Exceptions thrown in error handlers are ignored but logged (#3559). +* Removed unused public constants in `SyncConfiguration` (#4047). +* Fixed bug, preventing Sync client to renew the access token (#4038) (#4039). + ### Bug fixes * Fixed native memory leak setting the value of a primary key (#3993). * Activated Realm's annotation processor on connectedTest when the project is using kapt (#4008). -* Fixed bug, preventing Sync client to renew the access token (#4038) (#4039). * Fixed "too many open files" issue (#4002). * Added temporary work-around for bug crashing Samsung Tab 3 devices on startup (#3651). -### Object Server API Changes - -* Realm Sync v1.0.0 has been released, and Realm Mobile Platform is no longer considered in beta. -* Added a default `UserStore` based on the Realm Object Store (`ObjectStoreUserStore`). -* Added multi-user support to `UserStore`. Added `get(String)` and `remove(String)`, removed `remove()` and renamed `get()` to `getCurrent()`. -* Changed the order of arguments to `SyncCredentials.custom()` to match iOS: token, provider, userInfo. -* `SyncUser.all()` now returns `Map` instead of `List`. -* Exceptions thrown in error handlers are ignored but logged (#3559). -* Removed unused public constants in `SyncConfiguration` (#4047). - ### Enhancements * Add `like` predicate for String fields (#3752). diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index e28c4a627c..c7079866b1 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -15,4 +15,11 @@ + + + + + + + diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 9c13abf838..0bbf6178a8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -778,7 +778,7 @@ public static void populateForDistinctFieldsOrder(Realm realm, long numberOfBloc } public static void awaitOrFail(CountDownLatch latch) { - awaitOrFail(latch, 10); + awaitOrFail(latch, 60); } public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index bdf074b546..cf4041c3d2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -24,8 +24,9 @@ import java.io.PrintWriter; import java.io.StringWriter; -import java.lang.annotation.Annotation; +import java.util.ArrayList; import java.util.LinkedList; +import java.util.List; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -52,7 +53,7 @@ public class RunInLooperThread extends TestRealmConfigurationFactory { // Default Realm created by this Rule. It is guaranteed to be closed when the test finishes. public Realm realm; // Custom Realm used by the test. Saving the reference here will guarantee the instance is closed when exiting the test. - public Realm testRealm; + public List testRealms = new ArrayList(); public RealmConfiguration realmConfiguration; private CountDownLatch signalTestCompleted; private Handler backgroundHandler; @@ -76,7 +77,7 @@ protected void after() { super.after(); realmConfiguration = null; realm = null; - testRealm = null; + testRealms.clear(); keepStrongReference = null; } @@ -133,8 +134,10 @@ public void run() { if (realm != null) { realm.close(); } - if (testRealm != null) { - testRealm.close(); + if (!testRealms.isEmpty()) { + for (Realm testRealm : testRealms) { + testRealm.close(); + } } signalClosedRealm.countDown(); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index a95159a505..92f70f2f21 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -37,8 +37,9 @@ import io.realm.rule.RunInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; +import static io.realm.util.SyncTestUtils.createNamedTestUser; import static io.realm.util.SyncTestUtils.createTestUser; -import static org.junit.Assert.assertEquals; +import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -94,10 +95,11 @@ public void user_invalidUserThrows() { @Test public void serverUrl_setsFolderAndFileName() { SyncUser user = createTestUser(); + String identity = user.getIdentity(); String[][] validUrls = { // , , - { "realm://objectserver.realm.io/~/default", "realm-object-server/" + user.getIdentity(), "default" }, - { "realm://objectserver.realm.io/~/sub/default", "realm-object-server/" + user.getIdentity() + "/sub", "default" } + { "realm://objectserver.realm.io/~/default", "realm-object-server/" + identity + "/" + identity, "default" }, + { "realm://objectserver.realm.io/~/sub/default", "realm-object-server/" + identity + "/" + identity + "/sub", "default" } }; for (String[] validUrl : validUrls) { @@ -411,4 +413,31 @@ public void compact_NotAllowed() { Realm.compactRealm(config); } + + // Check that it is possible for multiple users to reference the same Realm URL while each user still use their + // own copy on the filesystem. This is e.g. what happens if a Realm is shared using a PermissionOffer. + @Test + public void multipleUsersReferenceSameRealm() { + SyncUser user1 = createNamedTestUser("user1"); + SyncUser user2 = createNamedTestUser("user2"); + String sharedUrl = "realm://ros.realm.io/42/default"; + SyncConfiguration config1 = new SyncConfiguration.Builder(user1, sharedUrl).build(); + Realm realm1 = Realm.getInstance(config1); + SyncConfiguration config2 = new SyncConfiguration.Builder(user2, sharedUrl).build(); + Realm realm2 = null; + + // Verify that two different configurations can be used for the same URL + try { + realm2 = Realm.getInstance(config1); + } finally { + realm1.close(); + if (realm2 != null) { + realm2.close(); + } + } + + // Verify that we actually save two different files + assertNotEquals(config1.getPath(), config2.getPath()); + } + } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index ef3ead5f2f..cb296dc293 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -31,29 +31,38 @@ public class SyncTestUtils { - public static String USER_TOKEN = UUID.randomUUID().toString(); - public static String REALM_TOKEN = UUID.randomUUID().toString(); - public static String DEFAULT_AUTH_URL = "http://objectserver.realm.io/auth"; + public static final String USER_TOKEN = UUID.randomUUID().toString(); + public static final String REALM_TOKEN = UUID.randomUUID().toString(); + public static final String DEFAULT_AUTH_URL = "http://objectserver.realm.io/auth"; + public static final String DEFAULT_USER_IDENTIFIER = "JohnDoe"; public static SyncUser createRandomTestUser() { - return createTestUser(UUID.randomUUID().toString(), UUID.randomUUID().toString(), DEFAULT_AUTH_URL, Long.MAX_VALUE); + return createTestUser(UUID.randomUUID().toString(), + UUID.randomUUID().toString(), + UUID.randomUUID().toString(), + DEFAULT_AUTH_URL, + Long.MAX_VALUE); } public static SyncUser createTestUser() { - return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_AUTH_URL, Long.MAX_VALUE); + return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, Long.MAX_VALUE); } public static SyncUser createTestUser(long expires) { - return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_AUTH_URL, expires); + return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, expires); } public static SyncUser createTestUser(String authUrl) { - return createTestUser(USER_TOKEN, REALM_TOKEN, authUrl, Long.MAX_VALUE); + return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, authUrl, Long.MAX_VALUE); } - public static SyncUser createTestUser(String userTokenValue, String realmTokenValue, String authUrl, long expires) { - Token userToken = new Token(userTokenValue, "JohnDoe", null, expires, null); - Token accessToken = new Token(realmTokenValue, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); + public static SyncUser createNamedTestUser(String userIdentifier) { + return createTestUser(USER_TOKEN, REALM_TOKEN, userIdentifier, DEFAULT_AUTH_URL, Long.MAX_VALUE); + } + + public static SyncUser createTestUser(String userTokenValue, String realmTokenValue, String userIdentifier, String authUrl, long expires) { + Token userToken = new Token(userTokenValue, userIdentifier, null, expires, null); + Token accessToken = new Token(realmTokenValue, userIdentifier, "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); JSONObject obj = new JSONObject(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index fe7e7ead5d..c7804aecf6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -570,10 +570,10 @@ public SyncConfiguration build() { // Determine location on disk // Use the serverUrl + user to create a unique filepath unless it has been explicitly overridden. - // // + // /// URI resolvedServerUrl = resolveServerUrl(serverUrl, user.getIdentity()); File rootDir = overrideDefaultFolder ? directory : defaultFolder; - String realmPathFromRootDir = getServerPath(resolvedServerUrl); + String realmPathFromRootDir = user.getIdentity() + "/" + getServerPath(resolvedServerUrl); File realmFileDirectory = new File(rootDir, realmPathFromRootDir); String realmFileName = overrideDefaultLocalFileName ? fileName : defaultLocalFileName; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 3df64989ff..986e3bfc03 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -133,7 +133,7 @@ public static SyncCredentials usernamePassword(String username, String password) * Creates a custom set of credentials. The behaviour will depend on the type of {@code identityProvider} and * {@code userInfo} used. * - * @param userIdentifier string identifying the user. Usually a username of userIdentifier. + * @param userIdentifier String identifying the user. Usually a username or user token. * @param identityProvider provider used to verify the credentials. * @param userInfo data describing the user further or {@code null} if the user does not have any extra data. The * data will be serialized to JSON, so all values must be mappable to a valid JSON data type. Custom diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index c658dd4c0d..37c304d3e6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -63,10 +63,18 @@ private static class ManagementConfig { private SyncConfiguration managementRealmConfig; synchronized SyncConfiguration initAndGetManagementRealmConfig( - ObjectServerUser syncUser, SyncUser user) { + ObjectServerUser syncUser, final SyncUser user) { if (managementRealmConfig == null) { managementRealmConfig = new SyncConfiguration.Builder( user, getManagementRealmUrl(syncUser.getAuthenticationUrl())) + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + RealmLog.error(String.format("Unexpected error with %s's management Realm: %s", + user.getIdentity(), + error.toString())); + } + }) .modules(new PermissionModule()) .build(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 8661ba3ff4..d7128f1e15 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -88,6 +88,7 @@ private AuthenticateResponse authenticate(URL authenticationUrl, String requestB .addHeader("Accept", "application/json") .post(RequestBody.create(JSON, requestBody)) .build(); + RealmLog.debug("Authenticate: " + requestBody); Call call = client.newCall(request); Response response = call.execute(); return AuthenticateResponse.from(response); diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionModule.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionModule.java index 5c245e3215..319cd2a83a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionModule.java +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionModule.java @@ -18,6 +18,6 @@ import io.realm.annotations.RealmModule; -@RealmModule(library = true, classes = { PermissionChange.class }) +@RealmModule(library = true, classes = { PermissionChange.class, PermissionOffer.class, PermissionOfferResponse.class }) public class PermissionModule { } diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java new file mode 100644 index 0000000000..0405f5ca70 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java @@ -0,0 +1,143 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.permissions; + +import java.util.Date; +import java.util.UUID; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.RealmObject; +import io.realm.annotations.Index; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; + +/** + * This model is used for offering permission changes to other users. + * It should be used in conjunction with an {@link io.realm.SyncUser}'s management Realm. + * + * @see Permissions description for general + * documentation. + */ +public class PermissionOffer extends RealmObject { + + // Base fields + @PrimaryKey + @Required + private String id = UUID.randomUUID().toString(); + @Required + private Date createdAt = new Date(); + @Required + private Date updatedAt = new Date(); + private Integer statusCode; // nil=not processed, 0=success, >0=error + private String statusMessage; + + // Offer fields + @Index + private String token; + @Required + private String realmUrl; + private boolean mayRead; + private boolean mayWrite; + private boolean mayManage; + private Date expiresAt; + + public PermissionOffer() { + // No args constructor required by Realm + } + + /** + * Construct a permission offer object used to offer permission changes to other users. + * + * @param url The URL to the Realm on which to apply these permission changes to, once the offer is accepted. + * @param mayRead Grant or revoke read access. + * @param mayWrite Grant or revoked read-write access. + * @param mayManage Grant or revoke administrative access. + * @param expiresAt When this token will expire and become invalid. Pass {@code null} if this offer should not expire. + */ + @SuppressFBWarnings("EI_EXPOSE_REP2") + public PermissionOffer(String url, boolean mayRead, boolean mayWrite, boolean mayManage, Date expiresAt) { + if (url == null) { + throw new IllegalArgumentException("Non-null 'url' required."); + } + this.realmUrl = url; + this.mayRead = mayRead; + this.mayWrite= mayWrite; + this.mayManage = mayManage; + this.expiresAt = expiresAt; + } + + public String getId() { + return id; + } + + @SuppressFBWarnings("EI_EXPOSE_REP") + public Date getCreatedAt() { + return createdAt; + } + + @SuppressFBWarnings("EI_EXPOSE_REP") + public Date getUpdatedAt() { + return updatedAt; + } + + /** + * Returns the status code for this change. + * + * @return {@code null} if not yet processed. {@code 0} if successful, {@code >0} if an error happened. See {@link #getStatusMessage()}. + */ + public Integer getStatusCode() { + return statusCode; + } + + /** + * Check if the request was successfully handled by the Realm Object Server. + * + * @return {@code true} if request was handled successfully. {@code false} if not. See {@link #getStatusMessage()} + * for the full error message. + */ + public boolean isSuccessful() { + return statusCode != null && statusCode == 0; + } + + public String getStatusMessage() { + return statusMessage; + } + + public String getToken() { + return token; + } + + public String getRealmUrl() { + return realmUrl; + } + + public boolean isMayRead() { + return mayRead; + } + + public boolean isMayWrite() { + return mayWrite; + } + + public boolean isMayManage() { + return mayManage; + } + + @SuppressFBWarnings("EI_EXPOSE_REP") + public Date getExpiresAt() { + return expiresAt; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOfferResponse.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOfferResponse.java new file mode 100644 index 0000000000..e1562a363e --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOfferResponse.java @@ -0,0 +1,121 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.permissions; + +import java.util.Date; +import java.util.UUID; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; + +/** + * This model is used to apply permission changes defined in the permission offer + * object represented by the specified token, which was created by another user's + * {@link PermissionOffer} object. + * + * It should be used in conjunction with an {@link io.realm.SyncUser}'s management Realm. + * + * @see Permissions description for general + * documentation. + */ +public class PermissionOfferResponse extends RealmObject { + + // Base fields + @PrimaryKey + @Required + private String id = UUID.randomUUID().toString(); + @Required + private Date createdAt = new Date(); + @Required + private Date updatedAt = new Date(); + private Integer statusCode; // nil=not processed, 0=success, >0=error + private String statusMessage; + + // Request fields + @Required + private String token; + private String realmUrl; + + public PermissionOfferResponse() { + // No args constructor required by Realm + } + + /** + * Construct a permission offer response object used to apply permission changes + * defined in the permission offer object represented by the specified token, + * which was created by another user's {@link PermissionOffer} object. + * + * @param token The received token which uniquely identifies another user's + * {@link PermissionOffer}. + */ + public PermissionOfferResponse(String token) { + if (token == null) { + throw new IllegalArgumentException("Non-null 'token' required."); + } + this.token = token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getId() { + return id; + } + + @SuppressFBWarnings("EI_EXPOSE_REP") + public Date getCreatedAt() { + return createdAt; + } + + @SuppressFBWarnings("EI_EXPOSE_REP") + public Date getUpdatedAt() { + return updatedAt; + } + + /** + * Returns the status code for this change. + * + * @return {@code null} if not yet processed. {@code 0} if successful, {@code >0} if an error happened. See {@link #getStatusMessage()}. + */ + public Integer getStatusCode() { + return statusCode; + } + + /** + * Check if the request was successfully handled by the Realm Object Server. + * + * @return {@code true} if request was handled successfully. {@code false} if not. See {@link #getStatusMessage()} + * for the full error message. + */ + public boolean isSuccessful() { + return statusCode != null && statusCode == 0; + } + + public String getStatusMessage() { + return statusMessage; + } + + public String getToken() { + return token; + } + + public String getRealmUrl() { + return realmUrl; + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 37fb540706..fb7f68f387 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -75,7 +75,7 @@ public void onError(SyncSession session, ObjectServerError error) { .build(); final Realm realm = Realm.getInstance(config); - looperThread.testRealm = realm; + looperThread.testRealms.add(realm); // FIXME: Right now we have no Java API for detecting when a session is established // So we optimistically assume it has been connected after 1 second. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java new file mode 100644 index 0000000000..d1695c13ba --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicReference; + +import io.realm.ObjectServerError; +import io.realm.Realm; +import io.realm.RealmChangeListener; +import io.realm.RealmResults; +import io.realm.SyncConfiguration; +import io.realm.SyncSession; +import io.realm.SyncUser; +import io.realm.entities.Dog; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.UserFactory; +import io.realm.permissions.PermissionOffer; +import io.realm.permissions.PermissionOfferResponse; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class ManagementRealmTests extends BaseIntegrationTest { + + @Rule + public RunInLooperThread looperThread = new RunInLooperThread(); + + @Ignore("TODO: Test is currently flaky. See https://github.com/realm/realm-java/pull/4066") + @Test + @RunTestInLooperThread + public void create_acceptOffer() { + SyncUser user1 = UserFactory.createUser(Constants.AUTH_URL, "user1"); + final SyncUser user2 = UserFactory.createUser(Constants.AUTH_URL, "user2"); + + // 1. User1 creates Realm that user2 does not have access + final String user1RealmUrl = "realm://127.0.0.1:9080/" + user1.getIdentity() + "/permission-offer-test"; + SyncConfiguration config1 = new SyncConfiguration.Builder(user1, user1RealmUrl). + errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail("Realm 1 unexpected error: " + error); + } + }) + .build(); + final Realm realm1 = Realm.getInstance(config1); + looperThread.testRealms.add(realm1); + realm1.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(Dog.class); + } + }); + + // 2. Create configuration for User2's Realm. + final SyncConfiguration config2 = new SyncConfiguration.Builder(user2, user1RealmUrl).build(); + + // 3. Create PermissionOffer + final AtomicReference offerId = new AtomicReference(null); + final Realm user1ManagementRealm = user1.getManagementRealm(); + looperThread.testRealms.add(user1ManagementRealm); + user1ManagementRealm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + boolean readPermission = true; + boolean readWritePermission = true; + boolean managePermission = false; + Date expiresAt = null; + PermissionOffer offer = new PermissionOffer(user1RealmUrl, readPermission, readWritePermission, managePermission, expiresAt); + offerId.set(offer.getId()); + realm.copyToRealm(offer); + } + }, new Realm.Transaction.OnSuccess() { + @Override + public void onSuccess() { + // 4. Wait for offer to get an token + RealmLog.error("OfferID: " + offerId.get()); + RealmResults offers = user1ManagementRealm.where(PermissionOffer.class) + .equalTo("id", offerId.get()) + .findAllAsync(); + looperThread.keepStrongReference.add(offers); + offers.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults offers) { + final PermissionOffer offer = offers.first(null); + if (offer != null && offer.isSuccessful() && offer.getToken() != null) { + // 5. User2 uses the token to accept the offer + final String offerToken = offer.getToken(); + final AtomicReference offerResponseId = new AtomicReference(); + final Realm user2ManagementRealm = user2.getManagementRealm(); + looperThread.testRealms.add(user2ManagementRealm); + user2ManagementRealm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + PermissionOfferResponse offerResponse = new PermissionOfferResponse(offerToken); + offerResponseId.set(offerResponse.getId()); + realm.copyToRealm(offerResponse); + } + }, new Realm.Transaction.OnSuccess() { + @Override + public void onSuccess() { + // 6. Wait for the offer response to be accepted + RealmResults responses = user2ManagementRealm.where(PermissionOfferResponse.class) + .equalTo("id", offerResponseId.get()) + .findAllAsync(); + looperThread.keepStrongReference.add(responses); + responses.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults responses) { + PermissionOfferResponse response = responses.first(null); + if (response != null && response.isSuccessful() && response.getToken().equals(offerToken)) { + // 7. Response accepted. It should now be possible for user2 to access user1's Realm + Realm realm = Realm.getInstance(config2); + looperThread.testRealms.add(realm); + RealmResults dogs = realm.where(Dog.class).findAll(); + looperThread.keepStrongReference.add(dogs); + dogs.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults element) { + assertEquals(1, element.size()); + looperThread.testComplete(); + } + }); + } + } + }); + } + }); + } + } + }); + } + }); + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index 46bc59994c..b63456b79d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -16,13 +16,17 @@ package io.realm.objectserver.utils; +import android.support.test.InstrumentationRegistry; import java.io.IOException; +import io.realm.Realm; import io.realm.log.RealmLog; import okhttp3.Headers; +import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index 91129ff4ca..1145f8a310 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -23,7 +23,11 @@ public class UserFactory { public static SyncUser createDefaultUser(String authUrl) { - SyncCredentials credentials = SyncCredentials.usernamePassword("test-user", "myPassw0rd", true); + return createUser(authUrl, "test-user"); + } + + public static SyncUser createUser(String authUrl, String userIdentifier) { + SyncCredentials credentials = SyncCredentials.usernamePassword(userIdentifier, "myPassw0rd", true); return SyncUser.login(credentials, authUrl); } From 0d1ea320b6f56915492315e45ff26056a813d1de Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Thu, 19 Jan 2017 03:28:33 -0800 Subject: [PATCH 0443/2110] Expose schemaVersion in SyncConfig (#4058) --- .../rule/TestRealmConfigurationFactory.java | 46 ++-- .../assets/schemaversion_v1.realm | Bin 0 -> 8192 bytes .../assets/versionTest.realm | Bin 0 -> 147456 bytes .../java/io/realm/SyncConfigurationTests.java | 92 +++++++- .../rule/TestSyncConfigurationFactory.java | 31 +++ .../cpp/io_realm_internal_SharedRealm.cpp | 20 +- .../src/main/java/io/realm/BaseRealm.java | 15 +- .../src/main/java/io/realm/Realm.java | 219 +++++++++++------- .../java/io/realm/RealmConfiguration.java | 2 +- .../java/io/realm/internal/SharedRealm.java | 12 +- .../java/io/realm/SyncConfiguration.java | 38 ++- 11 files changed, 359 insertions(+), 116 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/assets/schemaversion_v1.realm create mode 100644 realm/realm-library/src/androidTestObjectServer/assets/versionTest.realm create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/rule/TestSyncConfigurationFactory.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index cb8a1c62a8..8bec3bfa4c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -142,25 +142,39 @@ public RealmConfiguration.Builder createConfigurationBuilder() { } // Copies a Realm file from assets to temp dir - public void copyRealmFromAssets(Context context, String realmPath, String newName) - throws IOException { - // Delete the existing file before copy - RealmConfiguration configToDelete = new RealmConfiguration.Builder() + public void copyRealmFromAssets(Context context, String realmPath, String newName) throws IOException { + RealmConfiguration config = new RealmConfiguration.Builder() .directory(getRoot()) .name(newName) .build(); - Realm.deleteRealm(configToDelete); - - AssetManager assetManager = context.getAssets(); - InputStream is = assetManager.open(realmPath); - File file = new File(getRoot(), newName); - FileOutputStream outputStream = new FileOutputStream(file); - byte[] buf = new byte[1024]; - int bytesRead; - while ((bytesRead = is.read(buf)) > -1) { - outputStream.write(buf, 0, bytesRead); + + copyRealmFromAssets(context, realmPath, config); + } + + public void copyRealmFromAssets(Context context, String realmPath, RealmConfiguration config) throws IOException { + // Delete the existing file before copy + Realm.deleteRealm(config); + + File outFile = new File(config.getRealmDirectory(), config.getRealmFileName()); + + InputStream is = null; + FileOutputStream os = null; + try { + is = context.getAssets().open(realmPath); + os = new FileOutputStream(outFile); + + byte[] buf = new byte[1024]; + int bytesRead; + while ((bytesRead = is.read(buf)) > -1) { + os.write(buf, 0, bytesRead); + } + } finally { + if (is != null) { + try { is.close(); } catch (IOException ignore) {} + } + if (os != null) { + try { os.close(); } catch (IOException ignore) {} + } } - outputStream.close(); - is.close(); } } diff --git a/realm/realm-library/src/androidTestObjectServer/assets/schemaversion_v1.realm b/realm/realm-library/src/androidTestObjectServer/assets/schemaversion_v1.realm new file mode 100644 index 0000000000000000000000000000000000000000..d2dac440609e2a850ed098a11717d7824b3a3505 GIT binary patch literal 8192 zcmeHKJxp6y6h8M}zdwWFkr;wPQ!!eKQdx>Bu~agMv?hwwD$T$aUf{JRxVB}S$fzCM zrDC*HsbXZQ#K_3hB6UclE^K9}RK`vfW4HD@_uhw}UBW;*w9>m0KAiu1zkAMk?^Qz5 z>(J6C_m-Djm&7901d(cO8T}i6?|cz=qK&8%y+MC#y&i3EKV7WXA4V^uCp)cpd&siD zc_fj{TQTzm@U5+<7JQrSu$sP4q6}E20TW{oB=F|Iqe0*SOx_r=9j@<8uy^ zOZLkQmT&a6X1k+#$LJe|Bl0xAw9|>Tnq19)SZ_vY`{m{fdj%%`QnOi)qXt}*wqK5h z{g*ZyQQO>))_?r$dAzOz8vjR|+javkpBp$#OzBe+g`p`xmM;^zj^j)_w=VF>?TbUv zm3_&29~)qbd(;$X5Jz%g&qo^7pI)L*>=HU={cw-kKaqn9j+gL!Kl7@jlKmxeJ^|bD zk(k0xRa8}df(v9mv8!19D207^D39fdJd@}0Pf5yGp_)}?wW?~WrMjx8>zDKI%Uwhr zPgyduJ2ukGWS!l^Sp0e>eB^ z{vS2X)JAt%TqZA@gTN!V=N`B{&&)kOFxx{ub=90W@OV;m-fO>TG_1Yc^A5cPSNN;4 z*mciPfUHOT-qyWb9Jl?*8ys!`wu^B;CurX!4z?q$zm9|2mwG4-_Nf;9ieJURX1l~b zp-3;2`hWbx@o*pTkjGlJv}S3`(ypb(>iWOmfA#g9tNP$ost>q+NBUcoxDSTvcOgje zz_}WvaP}>#g7E;Jit(%=2J?ud=X+|NC}5-)>-pYxgMPrhm$py*V5>&){B8ZGAzZt* zZx1Xzvh>)}*OuC}*Cs!{`sPYpf3dh;J3;1{A&?=EA&?>PRtWHO#KC(=OYj#($teYE z`a>*{&x??%_y9O{_xUwdKX)*?-I4ECy!V}ZqC(+pSPnhB_x0`b*~ZBy@oVn6SMqy# z3Qy8`@=2aDK`eUC8kH~d;hiFX|Ne3g&tIJ%&q3r{4Mb%v?1lSbfPBCIOV6#h0yA^Y h5Xcb75Xcb75Xcb75Xcb75Xcb75Xcb75crQG@Hg55za{_x literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTestObjectServer/assets/versionTest.realm b/realm/realm-library/src/androidTestObjectServer/assets/versionTest.realm new file mode 100644 index 0000000000000000000000000000000000000000..85cd0c426d557e06ebe84c7e48062ae55bd91097 GIT binary patch literal 147456 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 = path; + // config->schema_version = schema_version; TODO: Disabled until we remove version handling from Java config->encryption_key = key_array; config->schema_mode = static_cast(schema_mode); config->in_memory = in_memory; @@ -436,9 +437,24 @@ Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv *env, jclass, jlong try { auto shared_realm = *(reinterpret_cast(nativePtr)); auto *schema = reinterpret_cast(nativeSchemaPtr); - shared_realm->update_schema(*schema, static_cast(version)); + shared_realm->update_schema(*schema, static_cast(version), nullptr, true); } CATCH_STD() } +JNIEXPORT jboolean JNICALL +Java_io_realm_internal_SharedRealm_nativeRequiresMigration(JNIEnv *env, jclass, jlong nativePtr, + jlong nativeSchemaPtr) { + + TR_ENTER() + try { + auto shared_realm = *(reinterpret_cast(nativePtr)); + auto *schema = reinterpret_cast(nativeSchemaPtr); + const std::vector &change_list = shared_realm->schema().compare(*schema); + return static_cast(!change_list.empty()); + } + CATCH_STD() + return JNI_FALSE; +} + diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 0c02c401b3..f03be41aa0 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -29,17 +29,17 @@ import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.ColumnInfo; import io.realm.internal.InvalidRow; +import io.realm.internal.ObjectServerFacade; import io.realm.internal.RealmObjectProxy; -import io.realm.internal.SharedRealm; -import io.realm.internal.ColumnInfo; import io.realm.internal.Row; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.Util; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.RealmLog; -import io.realm.internal.ObjectServerFacade; import rx.Observable; /** @@ -60,7 +60,7 @@ abstract class BaseRealm implements Closeable { private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; - + volatile static Context applicationContext; // Thread pool for all async operations (Query & transaction) @@ -591,7 +591,8 @@ static boolean compactRealm(final RealmConfiguration configuration) { /** * Migrates the Realm file defined by the given configuration using the provided migration block. * - * @param configuration configuration for the Realm that should be migrated. + * @param configuration configuration for the Realm that should be migrated. If this is a SyncConfiguration this + * method does nothing. * @param migration if set, this migration block will override what is set in {@link RealmConfiguration}. * @param callback callback for specific Realm type behaviors. * @param cause which triggers this migration. @@ -600,9 +601,13 @@ static boolean compactRealm(final RealmConfiguration configuration) { protected static void migrateRealm(final RealmConfiguration configuration, final RealmMigration migration, final MigrationCallback callback, final RealmMigrationNeededException cause) throws FileNotFoundException { + if (configuration == null) { throw new IllegalArgumentException("RealmConfiguration must be provided"); } + if (configuration.isSyncConfiguration()) { + return; + } if (migration == null && configuration.getMigration() == null) { throw new RealmMigrationNeededException(configuration.getPath(), "RealmMigration must be provided", cause); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index fffb1f57eb..0152938fd2 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -149,7 +149,7 @@ public Observable asObservable() { /** * Initializes the Realm library and creates a default configuration that is ready to use. It is required to call * this method before interacting with any other of the Realm API's. - * + *

          * A good place is in an {@link android.app.Application} subclass: *

                * {@code
          @@ -162,7 +162,7 @@ public Observable asObservable() {
                * }
                * }
                * 
          - * + *

          * Remember to register it in the {@code AndroidManifest.xml} file: *

                * {@code
          @@ -281,103 +281,147 @@ static Realm createInstance(RealmConfiguration configuration, ColumnIndices[] gl
           
               static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices[] globalCacheArray) {
                   Realm realm = new Realm(configuration);
          -        long currentVersion = realm.getVersion();
          -        long requiredVersion = configuration.getSchemaVersion();
          +
          +        final long currentVersion = realm.getVersion();
          +        final long requiredVersion = configuration.getSchemaVersion();
          +
                   final ColumnIndices columnIndices = RealmCache.findColumnIndices(globalCacheArray, requiredVersion);
          -        if (currentVersion != UNVERSIONED && currentVersion < requiredVersion && columnIndices == null) {
          -            realm.doClose();
          -            throw new RealmMigrationNeededException(configuration.getPath(), String.format("Realm on disk need to migrate from v%s to v%s", currentVersion, requiredVersion));
          -        }
          -        if (currentVersion != UNVERSIONED && requiredVersion < currentVersion && columnIndices == null) {
          -            realm.doClose();
          -            throw new IllegalArgumentException(String.format("Realm on disk is newer than the one specified: v%s vs. v%s", currentVersion, requiredVersion));
          -        }
           
          -        // Initialize Realm schema if needed
          -        if (columnIndices == null) {
          +        if (columnIndices != null) {
          +            // copy global cache as a Realm local indices cache
          +            realm.schema.columnIndices = columnIndices.clone();
          +        } else {
          +            final boolean syncingConfig = configuration.isSyncConfiguration();
          +
          +            if (!syncingConfig && (currentVersion != UNVERSIONED)) {
          +                if (currentVersion < requiredVersion) {
          +                    realm.doClose();
          +                    throw new RealmMigrationNeededException(
          +                            configuration.getPath(),
          +                            String.format("Realm on disk need to migrate from v%s to v%s", currentVersion, requiredVersion));
          +                }
          +                if (requiredVersion < currentVersion) {
          +                    realm.doClose();
          +                    throw new IllegalArgumentException(
          +                            String.format("Realm on disk is newer than the one specified: v%s vs. v%s", currentVersion, requiredVersion));
          +                }
          +            }
          +
          +            // Initialize Realm schema if needed
                       try {
          -                initializeRealm(realm);
          +                if (!syncingConfig) {
          +                    initializeRealm(realm);
          +                } else {
          +                    initializeSyncedRealm(realm);
          +                }
                       } catch (RuntimeException e) {
                           realm.doClose();
                           throw e;
                       }
          -        } else {
          -            // copy global cache as a Realm local indices cache
          -            realm.schema.columnIndices = columnIndices.clone();
                   }
           
                   return realm;
               }
           
          -    @SuppressWarnings("unchecked")
               private static void initializeRealm(Realm realm) {
          -        long version = realm.getVersion();
          -        boolean commitNeeded = false;
          -        boolean syncAvailable = realm.configuration.isSyncConfiguration();
          -
          +        // Everything in this method needs to be behind a transaction lock to prevent multi-process interaction while
          +        // the Realm is initialized.
          +        boolean commitChanges = false;
                   try {
          -            if (!syncAvailable) {
          -                realm.beginTransaction();
          -                if (version == UNVERSIONED) {
          -                    commitNeeded = true;
          -                    realm.setVersion(realm.configuration.getSchemaVersion());
          -                }
          -            }
          +            realm.beginTransaction();
          +            long currentVersion = realm.getVersion();
          +            boolean unversioned = currentVersion == UNVERSIONED;
          +            commitChanges = unversioned;
           
          -            RealmProxyMediator mediator = realm.configuration.getSchemaMediator();
          +            if (unversioned) {
          +                realm.setVersion(realm.configuration.getSchemaVersion());
          +            }
          +            final RealmProxyMediator mediator = realm.configuration.getSchemaMediator();
                       final Set> modelClasses = mediator.getModelClasses();
          -            final Map, ColumnInfo> columnInfoMap;
          -            columnInfoMap = new HashMap, ColumnInfo>(modelClasses.size());
          -            ArrayList realmObjectSchemas = new ArrayList<>();
          -            RealmSchema realmSchemaCache = new RealmSchema();
          +
          +            final Map, ColumnInfo> columnInfoMap = new HashMap<>(modelClasses.size());
                       for (Class modelClass : modelClasses) {
                           // Create and validate table
          -                if (version == UNVERSIONED && !syncAvailable) {
          +                if (unversioned) {
                               mediator.createTable(modelClass, realm.sharedRealm);
                           }
          -                if (syncAvailable) {
          -                    RealmObjectSchema realmObjectSchema = mediator.createRealmObjectSchema(modelClass, realmSchemaCache);
          -                    realmObjectSchemas.add(realmObjectSchema);
          -                } else {
          -                    columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false));
          +                columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false));
          +            }
          +
          +            realm.schema.columnIndices = new ColumnIndices(
          +                    (unversioned) ? realm.configuration.getSchemaVersion() : currentVersion, columnInfoMap);
          +
          +            if (unversioned) {
          +                final Transaction transaction = realm.configuration.getInitialDataTransaction();
          +                if (transaction != null) {
          +                    transaction.execute(realm);
                           }
                       }
          -            if (syncAvailable) {
          -                RealmSchema schema = new RealmSchema(realmObjectSchemas);
          -                // Assumption: when SyncConfiguration then additive schema update mode
          -                realm.sharedRealm.updateSchema(schema, version);
          -                for (Class modelClass : modelClasses) {
          -                    columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false));
          +        } catch (Exception e) {
          +            commitChanges = false;
          +            throw e;
          +        } finally {
          +            if (commitChanges) {
          +                realm.commitTransaction(false);
          +            } else {
          +                realm.cancelTransaction();
          +            }
          +        }
          +    }
          +
          +    private static void initializeSyncedRealm(Realm realm) {
          +        // Everything in this method needs to be behind a transaction lock to prevent multi-process interaction while
          +        // the Realm is initialized.
          +        boolean commitChanges = false;
          +        try {
          +            realm.beginTransaction();
          +            long currentVersion = realm.getVersion();
          +            final boolean unversioned = (currentVersion == UNVERSIONED);
          +
          +            final RealmProxyMediator mediator = realm.configuration.getSchemaMediator();
          +            final Set> modelClasses = mediator.getModelClasses();
          +
          +            final ArrayList realmObjectSchemas = new ArrayList<>();
          +            final RealmSchema realmSchemaCache = new RealmSchema();
          +            for (Class modelClass : modelClasses) {
          +                RealmObjectSchema realmObjectSchema = mediator.createRealmObjectSchema(modelClass, realmSchemaCache);
          +                realmObjectSchemas.add(realmObjectSchema);
          +            }
          +
          +            // Assumption: when SyncConfiguration then additive schema update mode
          +            final RealmSchema schema = new RealmSchema(realmObjectSchemas);
          +            long newVersion = realm.configuration.getSchemaVersion();
          +            if (realm.sharedRealm.requiresMigration(schema)) {
          +                if (currentVersion >= newVersion) {
          +                    throw new IllegalArgumentException(String.format("The schema was changed but the schema version " +
          +                            "was not updated. The configured schema version (%d) must be higher than the one in the Realm " +
          +                            "file (%d) in order to update the schema.", newVersion, currentVersion));
                           }
          +                realm.sharedRealm.updateSchema(schema, newVersion);
          +                // The OS currently does not handle setting the schema version. We have to do it manually.
          +                realm.setVersion(newVersion);
          +                commitChanges = true;
                       }
          -            realm.schema.columnIndices = new ColumnIndices(
          -                    (version == UNVERSIONED) ? realm.configuration.getSchemaVersion() : version,
          -                    columnInfoMap);
           
          -            if (version == UNVERSIONED) {
          -                final Transaction transaction = realm.getConfiguration().getInitialDataTransaction();
          +            final Map, ColumnInfo> columnInfoMap = new HashMap<>(modelClasses.size());
          +            for (Class modelClass : modelClasses) {
          +                columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false));
          +            }
          +
          +            realm.schema.columnIndices = new ColumnIndices((unversioned) ? newVersion : currentVersion, columnInfoMap);
          +
          +            if (unversioned) {
          +                final Transaction transaction = realm.configuration.getInitialDataTransaction();
                           if (transaction != null) {
          -                    if (syncAvailable) {
          -                        realm.executeTransaction(transaction);
          -                        realm.executeTransaction(new Transaction() {
          -                            @Override
          -                            public void execute(Realm realm) {
          -                                realm.setVersion(realm.configuration.getSchemaVersion());
          -                            }
          -                        });
          -                    } else {
          -                        transaction.execute(realm);
          -                    }
          +                    transaction.execute(realm);
                           }
          -
                       }
          +        } catch (Exception e) {
          +            commitChanges = false;
          +            throw e;
                   } finally {
          -            if (!syncAvailable) {
          -                if (commitNeeded) {
          -                    realm.commitTransaction(false);
          -                } else {
          -                    realm.cancelTransaction();
          -                }
          +            if (commitChanges) {
          +                realm.commitTransaction(false);
                       }
                   }
               }
          @@ -798,7 +842,7 @@ private Scanner getFullStringScanner(InputStream in) {
                */
               public  E createObject(Class clazz) {
                   checkIfValid();
          -        return createObjectInternal(clazz, true, Collections. emptyList());
          +        return createObjectInternal(clazz, true, Collections.emptyList());
               }
           
               /**
          @@ -811,9 +855,10 @@ public  E createObject(Class clazz) {
                * @throws RealmException if the primary key is defined in the model class or an object cannot be created.
                */
               // called from proxy classes
          -     E createObjectInternal(Class clazz,
          -                                                            boolean acceptDefaultValue,
          -                                                            List excludeFields) {
          +     E createObjectInternal(
          +            Class clazz,
          +            boolean acceptDefaultValue,
          +            List excludeFields) {
                   Table table = schema.getTable(clazz);
                   // Check and throw the exception earlier for a better exception message.
                   if (table.hasPrimaryKey()) {
          @@ -841,7 +886,7 @@  E createObjectInternal(Class clazz,
                */
               public  E createObject(Class clazz, Object primaryKeyValue) {
                   checkIfValid();
          -        return createObjectInternal(clazz, primaryKeyValue, true, Collections. emptyList());
          +        return createObjectInternal(clazz, primaryKeyValue, true, Collections.emptyList());
               }
           
               /**
          @@ -857,9 +902,11 @@ public  E createObject(Class clazz, Object primaryKeyVa
                * @throws IllegalArgumentException if the {@code primaryKeyValue} doesn't have a value that can be converted to the
                */
               // called from proxy classes
          -     E createObjectInternal(Class clazz, Object primaryKeyValue,
          -                                                            boolean acceptDefaultValue,
          -                                                            List excludeFields) {
          +     E createObjectInternal(
          +            Class clazz,
          +            Object primaryKeyValue,
          +            boolean acceptDefaultValue,
          +            List excludeFields) {
                   Table table = schema.getTable(clazz);
                   long rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue);
                   return get(clazz, rowIndex, acceptDefaultValue, excludeFields);
          @@ -894,7 +941,7 @@ public  E copyToRealm(E object) {
                * @param object {@link io.realm.RealmObject} to copy or update.
                * @return the new or updated RealmObject with all its properties backed by the Realm.
                * @throws java.lang.IllegalArgumentException if the object is {@code null} or doesn't have a Primary key defined
          -     *  or it belongs to a Realm instance in a different thread.
          +     *                                            or it belongs to a Realm instance in a different thread.
                * @see #copyToRealm(RealmModel)
                */
               public  E copyToRealmOrUpdate(E object) {
          @@ -984,9 +1031,9 @@ public void insert(Collection objects) {
                *
                * @param object RealmObjects to insert.
                * @throws IllegalStateException if the corresponding Realm is closed, called from an incorrect thread or not in a
          -     * transaction.
          +     *                                transaction.
                * @throws io.realm.exceptions.RealmPrimaryKeyConstraintException if two objects with the same primary key is
          -     * inserted or if a primary key value already exists in the Realm.
          +     *                                                                inserted or if a primary key value already exists in the Realm.
                * @see #copyToRealm(RealmModel)
                */
               public void insert(RealmModel object) {
          @@ -1021,7 +1068,6 @@ public void insert(RealmModel object) {
                * transaction.
                * @throws io.realm.exceptions.RealmPrimaryKeyConstraintException if two objects with the same primary key is
                * inserted or if a primary key value already exists in the Realm.
          -     *
                * @see #copyToRealmOrUpdate(Iterable)
                */
               public void insertOrUpdate(Collection objects) {
          @@ -1134,7 +1180,7 @@ public  List copyFromRealm(Iterable realmObjects) {
                * @param  type of object.
                * @return an in-memory detached copy of the RealmObjects.
                * @throws IllegalArgumentException if {@code maxDepth < 0}, the RealmObject is no longer accessible or it is a
          -     *         {@link DynamicRealmObject}.
          +     *                                  {@link DynamicRealmObject}.
                * @see #copyToRealmOrUpdate(Iterable)
                */
               public  List copyFromRealm(Iterable realmObjects, int maxDepth) {
          @@ -1192,7 +1238,7 @@ public  E copyFromRealm(E realmObject) {
                * @param  type of object.
                * @return an in-memory detached copy of the managed {@link RealmObject}.
                * @throws IllegalArgumentException if {@code maxDepth < 0}, the RealmObject is no longer accessible or it is a
          -     *         {@link DynamicRealmObject}.
          +     *                                  {@link DynamicRealmObject}.
                * @see #copyToRealmOrUpdate(RealmModel)
                */
               public  E copyFromRealm(E realmObject, int maxDepth) {
          @@ -1327,7 +1373,7 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, fin
           
                   // If the user provided a Callback then we make sure, the current Realm has a Handler
                   // we can use to deliver the result
          -        if ((onSuccess != null || onError != null)  && !hasValidNotifier()) {
          +        if ((onSuccess != null || onError != null) && !hasValidNotifier()) {
                       throw new IllegalStateException("Your Realm is opened from a thread without a Looper" +
                               " and you provided a callback, we need a Handler to invoke your callback");
                   }
          @@ -1576,7 +1622,7 @@ Table getTable(Class clazz) {
                * @param globalCacheArray global cache of column indices. If it contains an entry for current
                *                         schema version, this method only copies the indices information in the entry.
                * @return newly created indices information for current schema version. Or {@code null} if
          -     *          {@code globalCacheArray} already contains the entry for current schema version.
          +     *         {@code globalCacheArray} already contains the entry for current schema version.
                */
               ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) {
                   final long currentSchemaVersion = sharedRealm.getSchemaVersion();
          @@ -1681,6 +1727,7 @@ public interface Transaction {
                    */
                   class Callback {
                       public void onSuccess() {}
          +
                       public void onError(Exception e) {}
                   }
           
          diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java
          index 7cd6d6fcd4..fcec729fb4 100644
          --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java
          +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java
          @@ -66,7 +66,7 @@ public class RealmConfiguration {
               public static final int KEY_LENGTH = 64;
           
               private static final Object DEFAULT_MODULE;
          -    private static final RealmProxyMediator DEFAULT_MODULE_MEDIATOR;
          +    protected static final RealmProxyMediator DEFAULT_MODULE_MEDIATOR;
               private static Boolean rxJavaAvailable;
           
               static {
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java
          index cc3f52827e..82aca6f7b8 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java
          @@ -199,6 +199,7 @@ public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier r
                           rosServerUrl != null ? SchemaMode.SCHEMA_MODE_ADDITIVE.getNativeValue() : SchemaMode.SCHEMA_MODE_MANUAL.getNativeValue(),
                           config.getDurability() == Durability.MEM_ONLY,
                           enable_caching,
          +                config.getSchemaVersion(),
                           disableFormatUpgrade,
                           autoChangeNotifications,
                           rosServerUrl,
          @@ -326,10 +327,18 @@ public boolean compact() {
                   return nativeCompact(nativePtr);
               }
           
          +    /**
          +     * Update the underlying schema based on the schema description.
          +     * Calling this method must be done from inside a write transaction.
          +     */
               public void updateSchema(RealmSchema schema, long version) {
                   nativeUpdateSchema(nativePtr, schema.getNativePtr(), version);
               }
           
          +    public boolean requiresMigration(RealmSchema schema) {
          +        return nativeRequiresMigration(nativePtr, schema.getNativePtr());
          +    }
          +
               @Override
               public void close() {
                   if (realmNotifier != null) {
          @@ -372,7 +381,7 @@ public void invokeSchemaChangeListenerIfSchemaChanged() {
           
               private static native void nativeInit(String temporaryDirectoryPath);
               private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory,
          -                                                  boolean cache, boolean disableFormatUpgrade,
          +                                                  boolean cache, long schemaVersion, boolean disableFormatUpgrade,
                                                             boolean autoChangeNotification,
                                                             String syncServerURL, String syncUserToken);
               private static native void nativeCloseConfig(long nativeConfigPtr);
          @@ -402,4 +411,5 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte
               private static native void nativeStopWaitForChange(long nativeSharedRealmPtr);
               private static native boolean nativeCompact(long nativeSharedRealmPtr);
               private static native void nativeUpdateSchema(long nativePtr, long nativeSchemaPtr, long version);
          +    private static native boolean nativeRequiresMigration(long nativePtr, long nativeSchemaPtr);
           }
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java
          index c7804aecf6..9ee7b0f868 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java
          @@ -25,6 +25,7 @@
           import java.security.MessageDigest;
           import java.security.NoSuchAlgorithmException;
           import java.util.Arrays;
          +import java.util.Collections;
           import java.util.HashSet;
           import java.util.regex.Matcher;
           import java.util.regex.Pattern;
          @@ -238,6 +239,7 @@ public static final class Builder  {
                   private String fileName;
                   private boolean overrideDefaultLocalFileName = false;
                   private byte[] key;
          +        private long schemaVersion = 0;
                   private HashSet modules = new HashSet();
                   private HashSet> debugSchema = new HashSet>();
                   private RxObservableFactory rxFactory;
          @@ -423,6 +425,40 @@ public Builder encryptionKey(byte[] key) {
                       return this;
                   }
           
          +        /**
          +         * DEBUG method. This restricts the Realm schema to only consist of the provided classes without having to
          +         * create a module. These classes must be available in the default module. Calling this will remove any
          +         * previously configured modules.
          +         */
          +        SyncConfiguration.Builder schema(Class firstClass, Class... additionalClasses) {
          +            if (firstClass == null) {
          +                throw new IllegalArgumentException("A non-null class must be provided");
          +            }
          +            modules.clear();
          +            modules.add(DEFAULT_MODULE_MEDIATOR);
          +            debugSchema.add(firstClass);
          +            if (additionalClasses != null) {
          +                Collections.addAll(debugSchema, additionalClasses);
          +            }
          +
          +            return this;
          +        }
          +
          +        /**
          +         * Sets the schema version of the Realm. This must be equal to or higher than the schema version of the existing
          +         * Realm file, if any. If the schema version is higher than the already existing Realm, a migration is needed.
          +         *
          +         * @param schemaVersion the schema version.
          +         * @throws IllegalArgumentException if schema version is invalid.
          +         */
          +        public Builder schemaVersion(long schemaVersion) {
          +            if (schemaVersion < 0) {
          +                throw new IllegalArgumentException("Realm schema version numbers must be 0 (zero) or higher. Yours was: " + schemaVersion);
          +            }
          +            this.schemaVersion = schemaVersion;
          +            return this;
          +        }
          +
                   /**
                    * Replaces the existing module(s) with one or more {@link RealmModule}s. Using this method will replace the
                    * current schema for this Realm with the schema defined by the provided modules.
          @@ -616,7 +652,7 @@ public SyncConfiguration build() {
                               getCanonicalPath(new File(realmFileDirectory, realmFileName)),
                               null, // assetFile not supported by Sync. See https://github.com/realm/realm-sync/issues/241
                               key,
          -                    0,
          +                    schemaVersion,
                               null, // Custom migrations not supported
                               false, // MigrationNeededException is never thrown
                               durability,
          
          From 7bb3d5c3c7f6041564f9dcf8dc072749dda21cd3 Mon Sep 17 00:00:00 2001
          From: Makoto Yamazaki 
          Date: Thu, 19 Jan 2017 20:49:42 +0900
          Subject: [PATCH 0444/2110] Implement global logout (#3642)
          
          ---
           CHANGELOG.md                                  |  1 +
           .../java/io/realm/SyncUserTests.java          |  2 +-
           .../objectserver/ObjectServerUserTests.java   | 75 +++++++++++++++++++
           .../objectServer/java/io/realm/SyncUser.java  | 13 ++--
           .../internal/network/AuthServerResponse.java  |  2 -
           .../network/AuthenticateResponse.java         |  2 +-
           .../network/AuthenticationServer.java         |  2 +-
           .../realm/internal/network/LogoutRequest.java | 28 ++++++-
           .../internal/network/LogoutResponse.java      | 52 ++++++-------
           .../network/OkHttpAuthenticationServer.java   | 38 +++++++++-
           .../objectserver/ObjectServerUser.java        | 13 +++-
           11 files changed, 175 insertions(+), 53 deletions(-)
           create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/internal/objectserver/ObjectServerUserTests.java
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 12dcbd5f5c..b28e4fd851 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -13,6 +13,7 @@
           * Exceptions thrown in error handlers are ignored but logged (#3559).
           * Removed unused public constants in `SyncConfiguration` (#4047).
           * Fixed bug, preventing Sync client to renew the access token (#4038) (#4039).
          +* Now `SyncUser.logout()` properly revoke tokens (#3639).
           
           ### Bug fixes
           
          diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          index 644586ed63..27f92b0dce 100644
          --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          @@ -150,7 +150,7 @@ public void all_validUsers() {
           
                   Map users = SyncUser.all();
                   assertEquals(1, users.size());
          -        assertTrue(users.get(users.keySet().iterator().next()).isValid());
          +        assertTrue(users.entrySet().iterator().next().getValue().isValid());
               }
           
               // Tests that the user store returns the last user to login
          diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/internal/objectserver/ObjectServerUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/internal/objectserver/ObjectServerUserTests.java
          new file mode 100644
          index 0000000000..aa68bf09f7
          --- /dev/null
          +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/internal/objectserver/ObjectServerUserTests.java
          @@ -0,0 +1,75 @@
          +/*
          + * Copyright 2017 Realm Inc.
          + *
          + * 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.
          + */
          +package io.realm.internal.objectserver;
          +
          +import android.support.test.runner.AndroidJUnit4;
          +
          +import org.junit.Test;
          +import org.junit.runner.RunWith;
          +
          +import java.net.MalformedURLException;
          +import java.net.URL;
          +
          +import static org.junit.Assert.assertNotEquals;
          +import static org.junit.Assert.assertTrue;
          +
          +@RunWith(AndroidJUnit4.class)
          +public class ObjectServerUserTests {
          +
          +    private static final URL authUrl;
          +
          +    static {
          +        try {
          +            authUrl = new URL("http://localhost/auth");
          +        } catch (MalformedURLException e) {
          +            throw new ExceptionInInitializerError(e);
          +        }
          +    }
          +
          +    private static ObjectServerUser createFakeUser(String id) {
          +        final Token token = new Token("token_value", id, "path_value", Long.MAX_VALUE, null);
          +        return new ObjectServerUser(token, authUrl);
          +    }
          +
          +    @Test
          +    public void equals_validUser() {
          +        final ObjectServerUser user1 = createFakeUser("id_value");
          +        final ObjectServerUser user2 = createFakeUser("id_value");
          +        assertTrue(user1.equals(user2));
          +    }
          +
          +    @Test
          +    public void equals_loggedOutUser() {
          +        final ObjectServerUser user1 = createFakeUser("id_value");
          +        final ObjectServerUser user2 = createFakeUser("id_value");
          +        user1.clearTokens();
          +        user2.clearTokens();
          +        assertTrue(user1.equals(user2));
          +    }
          +
          +    @Test
          +    public void hashCode_validUser() {
          +        final ObjectServerUser user = createFakeUser("id_value");
          +        assertNotEquals(0, user.hashCode());
          +    }
          +
          +    @Test
          +    public void hashCode_loggedOutUser() {
          +        final ObjectServerUser user = createFakeUser("id_value");
          +        user.clearTokens();
          +        assertNotEquals(0, user.hashCode());
          +    }
          +}
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          index 37c304d3e6..d191e0d278 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          @@ -279,7 +279,7 @@ public void logout() {
                   // Acquire lock to prevent users creating new instances
                   synchronized (Realm.class) {
                       if (!syncUser.isLoggedIn()) {
          -                return; // Already logged out
          +                return; // Already local/global logout status
                       }
           
                       // Ensure that we can log out. If any Realm file is still open we should abort before doing anything
          @@ -300,10 +300,6 @@ public void logout() {
                           session.getOsSession().stop();
                       }
           
          -            // Remove all local tokens, preventing further connections.
          -            // FIXME We still need to cache the user token so it can be revoked.
          -            syncUser.clearTokens();
          -
                       SyncManager.getUserStore().remove(syncUser.getIdentity());
           
                       // Delete all Realms if needed.
          @@ -318,6 +314,11 @@ public void logout() {
                           }
                       }
           
          +            // Remove all local tokens, preventing further connections.
          +            final Token userToken = syncUser.getUserToken();
          +            syncUser.clearTokens();
          +            syncUser.localLogout();
          +
                       // Finally revoke server token. The local user is logged out in any case.
                       final AuthenticationServer server = SyncManager.getAuthServer();
                       ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR;
          @@ -325,7 +326,7 @@ public void logout() {
           
                           @Override
                           protected LogoutResponse execute() {
          -                    return server.logout(SyncUser.this, syncUser.getAuthenticationUrl());
          +                    return server.logout(userToken, syncUser.getAuthenticationUrl());
                           }
           
                           @Override
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java
          index cad9ce933a..a5ad8e9190 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java
          @@ -50,8 +50,6 @@ protected void setError(ObjectServerError error) {
                   this.error = error;
               }
           
          -
          -
               /**
                * Parse an HTTP error from a Realm Authentication Server. The server returns errors following
                * https://tools.ietf.org/html/rfc7807 with an extra "code" field for Realm specific error codes.
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java
          index 4bb65ea1e9..e0140385cb 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java
          @@ -53,7 +53,7 @@ public static AuthenticateResponse from(Response response) {
                       ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e);
                       return new AuthenticateResponse(error);
                   }
          -        if (response.code() != 200) {
          +        if (!response.isSuccessful()) {
                       return new AuthenticateResponse(AuthServerResponse.createError(serverResponse, response.code()));
                   } else {
                       return new AuthenticateResponse(serverResponse);
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java
          index 5cde174353..b217269ad7 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java
          @@ -55,5 +55,5 @@ public interface AuthenticationServer {
                * own refresh token, but if the refresh token for some reason was shared or stolen all these devices will be
                * logged out as well.
                */
          -    LogoutResponse logout(SyncUser user, URL authenticationUrl);
          +    LogoutResponse logout(Token userToken, URL authenticationUrl);
           }
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java
          index c7706c27e5..8cb67b56a9 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java
          @@ -16,17 +16,37 @@
           
           package io.realm.internal.network;
           
          -import io.realm.SyncUser;
          +import org.json.JSONException;
          +import org.json.JSONObject;
          +
          +import io.realm.internal.objectserver.Token;
           
           /**
            * This class encapsulates a request to log out a user on the Realm Authentication Server. It is responsible for
            * constructing the JSON understood by the Realm Authentication Server.
            */
           public class LogoutRequest {
          -    // TODO Endpoint not finished yet
           
          -    LogoutRequest fromUser(SyncUser user) {
          -        return new LogoutRequest();
          +    private final String token;
          +
          +    public static LogoutRequest revoke(Token userToken) {
          +        return new LogoutRequest(userToken.value());
               }
           
          +    private LogoutRequest(String token) {
          +        this.token = token;
          +    }
          +
          +    /**
          +     * Converts the request into a JSON payload.
          +     */
          +    public String toJson() {
          +        try {
          +            JSONObject request = new JSONObject();
          +            request.put("token", token);
          +            return request.toString();
          +        } catch (JSONException e) {
          +            throw new RuntimeException(e);
          +        }
          +    }
           }
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java
          index 5439f9f769..ce356546eb 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java
          @@ -28,29 +28,32 @@
            */
           public class LogoutResponse extends AuthServerResponse {
           
          -    private final ObjectServerError error;
          -
               /**
          -     * Helper method for creating the proper Authenticate response. This method will set the appropriate error
          +     * Helper method for creating the proper Logout response. This method will set the appropriate error
                * depending on any HTTP response codes or I/O errors.
                *
                * @param response the server response.
                * @return the log out response.
                */
          -    static LogoutResponse createFrom(Response response) {
          -        String serverResponse;
          +    static LogoutResponse from(Response response) {
          +        if (response.isSuccessful()) {
          +            // success
          +            return new LogoutResponse();
          +        }
                   try {
          -            serverResponse = response.body().string();
          +            String serverResponse = response.body().string();
          +            return new LogoutResponse(AuthServerResponse.createError(serverResponse, response.code()));
                   } catch (IOException e) {
                       ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e);
                       return new LogoutResponse(error);
                   }
          -        RealmLog.debug("Authenticate response: " + serverResponse);
          -        if (response.code() != 200) {
          -            return new LogoutResponse(AuthServerResponse.createError(serverResponse, response.code()));
          -        } else {
          -            return new LogoutResponse(serverResponse);
          -        }
          +    }
          +
          +    /**
          +     * Helper method for creating a failed response.
          +     */
          +    public static LogoutResponse from(ObjectServerError error) {
          +        return new LogoutResponse(error);
               }
           
               /**
          @@ -60,17 +63,16 @@ static LogoutResponse createFrom(Response response) {
                * @param error an authentication response error.
                */
               private LogoutResponse(ObjectServerError error) {
          -        this.error = error;
          +        RealmLog.debug("Logout response - Error: " + error.getErrorMessage());
          +        setError(error);
               }
           
               /**
          -     * Parses a valid (200) server response.
          -     *
          -     * @param serverResponse the server response.
          +     * Parses a valid (204) server response.
                */
          -    private LogoutResponse(String serverResponse) {
          -        this.error = null;
          -        // TODO endpoint not finalized
          +    private LogoutResponse() {
          +        RealmLog.debug("Logout response - Success");
          +        setError(null);
               }
           
               /**
          @@ -79,16 +81,6 @@ private LogoutResponse(String serverResponse) {
                * @return {@code true} if valid.
                */
               public boolean isValid() {
          -//        return (error == null);
          -        return true;
          -    }
          -
          -    /**
          -     * Returns the error.
          -     *
          -     * @return the error.
          -     */
          -    public ObjectServerError getError() {
          -        return error;
          +        return (error == null) || (error.getErrorCode() == ErrorCode.EXPIRED_REFRESH_TOKEN);
               }
           }
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java
          index d7128f1e15..fe580d438b 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java
          @@ -16,14 +16,14 @@
           
           package io.realm.internal.network;
           
          +import java.net.MalformedURLException;
           import java.net.URI;
           import java.net.URL;
           import java.util.concurrent.TimeUnit;
           
          -import io.realm.SyncCredentials;
           import io.realm.ErrorCode;
           import io.realm.ObjectServerError;
          -import io.realm.SyncUser;
          +import io.realm.SyncCredentials;
           import io.realm.internal.objectserver.Token;
           import io.realm.log.RealmLog;
           import okhttp3.Call;
          @@ -77,8 +77,26 @@ public AuthenticateResponse refreshUser(Token userToken, URL authenticationUrl)
               }
           
               @Override
          -    public LogoutResponse logout(SyncUser user, URL authenticationUrl) {
          -        throw new UnsupportedOperationException("Not yet implemented");
          +    public LogoutResponse logout(Token userToken, URL authenticationUrl) {
          +        try {
          +            String requestBody = LogoutRequest.revoke(userToken).toJson();
          +            return logout(buildLogoutUrl(authenticationUrl), requestBody);
          +        } catch (Exception e) {
          +            return LogoutResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e));
          +        }
          +    }
          +
          +    private static URL buildLogoutUrl(URL authenticationUrl) {
          +        final String baseUrlString = authenticationUrl.toExternalForm();
          +        try {
          +            if (baseUrlString.endsWith("/")) {
          +                return new URL(baseUrlString + "revoke");
          +            } else {
          +                return new URL(baseUrlString + "/revoke");
          +            }
          +        } catch (MalformedURLException e) {
          +            throw new RuntimeException(e);
          +        }
               }
           
               private AuthenticateResponse authenticate(URL authenticationUrl, String requestBody) throws Exception {
          @@ -93,4 +111,16 @@ private AuthenticateResponse authenticate(URL authenticationUrl, String requestB
                   Response response = call.execute();
                   return AuthenticateResponse.from(response);
               }
          +
          +    private LogoutResponse logout(URL logoutUrl, String requestBody) throws Exception {
          +        Request request = new Request.Builder()
          +                .url(logoutUrl)
          +                .addHeader("Content-Type", "application/json")
          +                .addHeader("Accept", "application/json")
          +                .post(RequestBody.create(JSON, requestBody))
          +                .build();
          +        Call call = client.newCall(request);
          +        Response response = call.execute();
          +        return LogoutResponse.from(response);
          +    }
           }
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java
          index f6caa7fe7d..a24e2bb6ce 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java
          @@ -50,11 +50,12 @@ public class ObjectServerUser {
               public ObjectServerUser(Token refreshToken, URL authenticationUrl) {
                   this.identity = refreshToken.identity();
                   this.authenticationUrl = authenticationUrl;
          -        setRefreshToken(refreshToken);
          +        this.refreshToken = refreshToken;
                   this.loggedIn = true;
               }
           
               private void setRefreshToken(final Token refreshToken) {
          +        // TODO Shouldn't we check if the identity of the new refreshToken is the same with previous identity?
                   this.refreshToken = refreshToken; // Replace any existing token. TODO re-save the user with latest token.
               }
           
          @@ -145,6 +146,7 @@ public List getSessions() {
                   return sessions;
               }
           
          +    // TODO merge this method into localLogout(). See https://github.com/realm/realm-java/pull/3642#discussion_r96818800
               public void clearTokens() {
                   realms.clear();
                   refreshToken = null;
          @@ -168,16 +170,19 @@ public boolean equals(Object o) {
                   ObjectServerUser syncUser = (ObjectServerUser) o;
           
                   if (!identity.equals(syncUser.identity)) return false;
          -        if (!refreshToken.equals(syncUser.refreshToken)) return false;
          +        if (refreshToken == null) {
          +            if (syncUser.refreshToken != null) return false;
          +        } else {
          +            if (!refreshToken.equals(syncUser.refreshToken)) return false;
          +        }
                   if (!authenticationUrl.toString().equals(syncUser.authenticationUrl.toString())) return false;
                   return realms.equals(syncUser.realms);
          -
               }
           
               @Override
               public int hashCode() {
                   int result = identity.hashCode();
          -        result = 31 * result + refreshToken.hashCode();
          +        result = 31 * result + (refreshToken == null ? 0 : refreshToken.hashCode());
                   result = 31 * result + authenticationUrl.toString().hashCode();
                   result = 31 * result + realms.hashCode();
                   return result;
          
          From 2bb5ce82b7e9a00ef189518a5fe5c76cc5d2d4ee Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 19 Jan 2017 19:54:11 +0800
          Subject: [PATCH 0445/2110] Release v2.3.0
          
          ---
           version.txt | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/version.txt b/version.txt
          index a9d981d17e..cc6612c36e 100644
          --- a/version.txt
          +++ b/version.txt
          @@ -1 +1 @@
          -2.3.0-SNAPSHOT
          +2.3.0
          \ No newline at end of file
          
          From 6063216ce11723f9bfdd25995f5df51e2fc46610 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 19 Jan 2017 19:54:11 +0800
          Subject: [PATCH 0446/2110] Prepare next release v2.3.1-SNAPSHOT
          
          ---
           version.txt | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/version.txt b/version.txt
          index cc6612c36e..50794f17f1 100644
          --- a/version.txt
          +++ b/version.txt
          @@ -1 +1 @@
          -2.3.0
          \ No newline at end of file
          +2.3.1-SNAPSHOT
          \ No newline at end of file
          
          From dc373a7e1246ab1304d0b7528ad6fd9ad7a9bbcf Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 19 Jan 2017 21:16:04 +0800
          Subject: [PATCH 0447/2110] Prepare for next iteration
          
          ---
           CHANGELOG.md | 2 ++
           version.txt  | 2 +-
           2 files changed, 3 insertions(+), 1 deletion(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index b28e4fd851..db30c45753 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -1,3 +1,5 @@
          +## 2.4.0
          +
           ## 2.3.0
           
           ### Object Server API Changes 
          diff --git a/version.txt b/version.txt
          index 50794f17f1..b4308ebebb 100644
          --- a/version.txt
          +++ b/version.txt
          @@ -1 +1 @@
          -2.3.1-SNAPSHOT
          \ No newline at end of file
          +2.4.0-SNAPSHOT
          
          From 05373e2db3b01c81ed33866f4751fdb85ed8ac99 Mon Sep 17 00:00:00 2001
          From: Emanuele Zattin 
          Date: Thu, 19 Jan 2017 14:30:55 +0100
          Subject: [PATCH 0448/2110] Don't use a fixed name for the ROS container
           (#4074)
          
          This will allow to avoid issues when a previously running container
          has not been closed for whatever reason.
          ---
           Jenkinsfile | 7 +++----
           1 file changed, 3 insertions(+), 4 deletions(-)
          
          diff --git a/Jenkinsfile b/Jenkinsfile
          index e196b273b1..43621b7fc4 100644
          --- a/Jenkinsfile
          +++ b/Jenkinsfile
          @@ -35,8 +35,7 @@ try {
                   rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_DE_VERSION=${rosDeVersion} tools/sync_test_server"
                 }
           
          -      rosContainer = rosEnv.run("-v /tmp=/tmp/.ros " +
          -              "--name ros")
          +      rosContainer = rosEnv.run('-v /tmp=/tmp/.ros')
           
                 try {
                     buildEnv.inside("-e HOME=/tmp " +
          @@ -47,7 +46,7 @@ try {
                             "-v ${env.HOME}/.android:/tmp/.android " +
                             "-v ${env.HOME}/ccache:/tmp/.ccache " +
                             "-v ${env.HOME}/lcache:/tmp/.lcache " +
          -                  "--network container:ros") {
          +                  "--network container:${rosContainer.id}") {
                       stage('JVM tests') {
                         try {
                           withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) {
          @@ -107,7 +106,7 @@ try {
                       }
                     }
                 } finally {
          -          sh "docker logs ros"
          +          sh "docker logs ${rosContainer.id}"
                     rosContainer.stop()
                 }
               }
          
          From 435c6d48fc420b93e41ecdb45b3fba91028d6691 Mon Sep 17 00:00:00 2001
          From: Makoto Yamazaki 
          Date: Thu, 19 Jan 2017 22:59:28 +0900
          Subject: [PATCH 0449/2110] fix transaction error of synced Realm.
          
          ---
           realm/realm-library/src/main/java/io/realm/Realm.java | 2 ++
           1 file changed, 2 insertions(+)
          
          diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java
          index 0152938fd2..56b6f52235 100644
          --- a/realm/realm-library/src/main/java/io/realm/Realm.java
          +++ b/realm/realm-library/src/main/java/io/realm/Realm.java
          @@ -422,6 +422,8 @@ private static void initializeSyncedRealm(Realm realm) {
                   } finally {
                       if (commitChanges) {
                           realm.commitTransaction(false);
          +            } else {
          +                realm.cancelTransaction();
                       }
                   }
               }
          
          From debcb29144de57eda25fb294d0d612a613106b0c Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 19 Jan 2017 22:19:46 +0800
          Subject: [PATCH 0450/2110] Redo 2.3.0 release
          
          ---
           version.txt | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/version.txt b/version.txt
          index 50794f17f1..a9d981d17e 100644
          --- a/version.txt
          +++ b/version.txt
          @@ -1 +1 @@
          -2.3.1-SNAPSHOT
          \ No newline at end of file
          +2.3.0-SNAPSHOT
          
          From 758956057712806936c6ff420a95576d5d72be18 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 19 Jan 2017 22:23:40 +0800
          Subject: [PATCH 0451/2110] Release v2.3.0
          
          ---
           version.txt | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/version.txt b/version.txt
          index a9d981d17e..cc6612c36e 100644
          --- a/version.txt
          +++ b/version.txt
          @@ -1 +1 @@
          -2.3.0-SNAPSHOT
          +2.3.0
          \ No newline at end of file
          
          From 038b7a0bf6aa5dc07b3e3451c477ba3c7f799781 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 19 Jan 2017 22:23:40 +0800
          Subject: [PATCH 0452/2110] Prepare next release v2.3.1-SNAPSHOT
          
          ---
           version.txt | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/version.txt b/version.txt
          index cc6612c36e..50794f17f1 100644
          --- a/version.txt
          +++ b/version.txt
          @@ -1 +1 @@
          -2.3.0
          \ No newline at end of file
          +2.3.1-SNAPSHOT
          \ No newline at end of file
          
          From d2d8855372933b16314fd4cc53e9a9e8f1dfd04e Mon Sep 17 00:00:00 2001
          From: Marc Mettke 
          Date: Thu, 19 Jan 2017 21:53:54 +0100
          Subject: [PATCH 0453/2110] Corrected RealmConfiguration#encryptionKey JavaDoc
           (#4073)
          
          ---
           .../src/main/java/io/realm/RealmConfiguration.java              | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java
          index fcec729fb4..2eab03463b 100644
          --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java
          +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java
          @@ -453,7 +453,7 @@ public Builder directory(File directory) {
                   }
           
                   /**
          -         * Sets the 64 bit key used to encrypt and decrypt the Realm file.
          +         * Sets the 64 byte key used to encrypt and decrypt the Realm file.
                    * Sets the {@value io.realm.RealmConfiguration#KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file.
                    */
                   public Builder encryptionKey(byte[] key) {
          
          From 513a0b53b745145fdf79ca164f458898643322f5 Mon Sep 17 00:00:00 2001
          From: LYK 
          Date: Fri, 20 Jan 2017 08:25:57 +0900
          Subject: [PATCH 0454/2110] Fixed CHANGELOG.md (#4077)
          
          ---
           CHANGELOG.md | 4 ++--
           1 file changed, 2 insertions(+), 2 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index b28e4fd851..feac1407ac 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -13,7 +13,7 @@
           * Exceptions thrown in error handlers are ignored but logged (#3559).
           * Removed unused public constants in `SyncConfiguration` (#4047).
           * Fixed bug, preventing Sync client to renew the access token (#4038) (#4039).
          -* Now `SyncUser.logout()` properly revoke tokens (#3639).
          +* Now `SyncUser.logout()` properly revokes tokens (#3639).
           
           ### Bug fixes
           
          @@ -24,7 +24,7 @@
           
           ### Enhancements
           
          -* Add `like` predicate for String fields (#3752).
          +* Added `like` predicate for String fields (#3752).
           
           ### Internal
           
          
          From 01bd5940ad22aa9d65b1eeb5a59235f7752d02a4 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Sat, 21 Jan 2017 03:03:20 +0800
          Subject: [PATCH 0455/2110] No need to delete ros docker anymore (#4082)
          
          ---
           Jenkinsfile | 3 ---
           1 file changed, 3 deletions(-)
          
          diff --git a/Jenkinsfile b/Jenkinsfile
          index 43621b7fc4..4356ad68aa 100644
          --- a/Jenkinsfile
          +++ b/Jenkinsfile
          @@ -24,9 +24,6 @@ try {
                 def buildEnv
                 def rosEnv
                 stage('Docker build') {
          -        // Clean any potential old containers
          -        sh 'docker rm ros || true' 
          -
                   // Docker image for build
                   buildEnv = docker.build 'realm-java:snapshot'
                   // Docker image for testing Realm Object Server
          
          From 4eec891825e86ca2ce7731d71125e25b7827c199 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Tue, 24 Jan 2017 23:21:47 +0800
          Subject: [PATCH 0456/2110] Free the SharedRealm in phantom daemon (#4096)
          
          Treat the SharedRealm the same as other native objects.
          SharedRealm.close will only call Object Store Realm::close without
          deleting the ShareRealm pointer.
          Then we don't need the finalizer anymore. Fix #3730 .
          This is related with
          https://github.com/realm/realm-object-store/pull/318
          as well. It is possible that java close the Realm in any of the Object Store's
          callbacks. To avoid Object Store operating on a invalid SharedRealm
          pointer, binding should try to make sure after callbacks. However, it
          cannot be totally avoided since user could set the Realm instance to
          null and the instance can be GCed at any time. It is still something
          should be considered in the Object Store implementation.
          ---
           CHANGELOG.md                                  |  6 ++++
           .../cpp/io_realm_internal_SharedRealm.cpp     | 19 ++++++++++--
           .../java/io/realm/internal/SharedRealm.java   | 29 +++++++++----------
           3 files changed, 36 insertions(+), 18 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index feac1407ac..d528c32eb1 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -1,3 +1,9 @@
          +## 2.3.1
          +
          +### Bug fixes
          +
          +* Fixed NPE problem happened in SharedRealm.finalize() (#3730).
          +
           ## 2.3.0
           
           ### Object Server API Changes 
          diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp
          index 76cfd7a2b0..1e320e876b 100644
          --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp
          +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp
          @@ -29,6 +29,8 @@ static_assert(SchemaMode::Additive ==
           static_assert(SchemaMode::Manual ==
                         static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_MANUAL), "");
           
          +static void finalize_shared_realm(jlong ptr);
          +
           JNIEXPORT void JNICALL
           Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv *env, jclass, jstring temporary_directory_path)
           {
          @@ -98,8 +100,9 @@ Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong
           {
               TR_ENTER_PTR(shared_realm_ptr)
           
          -    auto ptr = reinterpret_cast(shared_realm_ptr);
          -    delete ptr;
          +    auto shared_realm = *(reinterpret_cast(shared_realm_ptr));
          +    // Close the SharedRealm only. Let the finalizer daemon thread free the SharedRealm
          +    shared_realm->close();
           }
           
           JNIEXPORT void JNICALL
          @@ -457,4 +460,16 @@ Java_io_realm_internal_SharedRealm_nativeRequiresMigration(JNIEnv *env, jclass,
               return JNI_FALSE;
           }
           
          +static void finalize_shared_realm(jlong ptr)
          +{
          +    TR_ENTER_PTR(ptr)
          +    delete reinterpret_cast(ptr);
          +}
          +
          +JNIEXPORT jlong JNICALL
          +Java_io_realm_internal_SharedRealm_nativeGetFinalizerPtr(JNIEnv*, jclass)
          +{
          +    TR_ENTER()
          +    return reinterpret_cast(&finalize_shared_realm);
          +}
           
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java
          index 82aca6f7b8..9d3868f38e 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java
          @@ -23,7 +23,7 @@
           import io.realm.RealmSchema;
           import io.realm.internal.async.BadVersionException;
           
          -public final class SharedRealm implements Closeable {
          +public final class SharedRealm implements Closeable, NativeObject {
           
               // Const value for RealmFileException conversion
               public static final byte FILE_EXCEPTION_KIND_ACCESS_ERROR = 0;
          @@ -33,6 +33,7 @@ public final class SharedRealm implements Closeable {
               public static final byte FILE_EXCEPTION_KIND_NOT_FOUND = 4;
               public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 5;
               public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 6;
          +    private static final long nativeFinalizerPtr = nativeGetFinalizerPtr();
           
               public static void initialize(File tempDirectory) {
                   if (SharedRealm.temporaryDirectory != null) {
          @@ -175,6 +176,7 @@ private SharedRealm(long nativePtr, RealmConfiguration configuration, RealmNotif
                   this.realmNotifier = notifier;
                   this.schemaChangeListener = schemaVersionListener;
                   context = new Context();
          +        context.addReference(this);
                   this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion();
                   objectServerFacade = null;
               }
          @@ -215,10 +217,6 @@ public static SharedRealm getInstance(RealmConfiguration config, RealmNotifier r
                   }
               }
           
          -    long getNativePtr() {
          -        return nativePtr;
          -    }
          -
               public void beginTransaction() {
                   nativeBeginTransaction(nativePtr);
                   invokeSchemaChangeListenerIfSchemaChanged();
          @@ -347,23 +345,21 @@ public void close() {
                   synchronized (context) {
                       if (nativePtr != 0) {
                           nativeCloseSharedRealm(nativePtr);
          +                // It is OK to clear the nativePtr. It has been saved to the NativeObjectReference when adding to the
          +                // context.
                           nativePtr = 0;
                       }
                   }
               }
           
               @Override
          -    protected void finalize() throws Throwable {
          -        synchronized (context) {
          -            close();
          -            // FIXME: Below is the original implementation of SharedGroup.finalize().
          -            // And actually Context.asyncDisposeSharedGroup will simply call nativeClose which is not asyc at all.
          -            // IMO since this implemented Closeable already, it makes no sense to implement finalize.
          -            // Just keep the logic the same for now and make nativeClose private. Rethink about this when cleaning
          -            // up finalizers.
          -            //context.asyncDisposeSharedRealm(nativePtr);
          -        }
          -        super.finalize();
          +    public long getNativePtr() {
          +        return nativePtr;
          +    }
          +
          +    @Override
          +    public long getNativeFinalizerPtr() {
          +        return nativeFinalizerPtr;
               }
           
               public void invokeSchemaChangeListenerIfSchemaChanged() {
          @@ -412,4 +408,5 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte
               private static native boolean nativeCompact(long nativeSharedRealmPtr);
               private static native void nativeUpdateSchema(long nativePtr, long nativeSchemaPtr, long version);
               private static native boolean nativeRequiresMigration(long nativePtr, long nativeSchemaPtr);
          +    private static native long nativeGetFinalizerPtr();
           }
          
          From f1522f91813273c4c69b85ac2aa2d54c041a19d3 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Wed, 25 Jan 2017 18:24:25 +0800
          Subject: [PATCH 0457/2110] Fix findbugs
          
          ---
           .../src/main/java/io/realm/internal/Collection.java  | 12 ++++++++++--
           1 file changed, 10 insertions(+), 2 deletions(-)
          
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          index d2ee720cfa..bbc582e6c0 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          @@ -204,11 +204,19 @@ public UncheckedRow getUncheckedRow(int index) {
               }
           
               public UncheckedRow firstUncheckedRow() {
          -        return table.getUncheckedRowByPointer(nativeFirstRow(nativePtr));
          +        long rowPtr = nativeFirstRow(nativePtr);
          +        if (rowPtr != 0) {
          +            return table.getUncheckedRowByPointer(rowPtr);
          +        }
          +        return null;
               }
           
               public UncheckedRow lastUncheckedRow() {
          -        return table.getUncheckedRowByPointer(nativeLastRow(nativePtr));
          +        long rowPtr = nativeLastRow(nativePtr);
          +        if (rowPtr != 0) {
          +            return table.getUncheckedRowByPointer(rowPtr);
          +        }
          +        return null;
               }
           
               public Table getTable() {
          
          From 08f3996f7f1cea6dde43e896e77c61e1d8c5ad32 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 26 Jan 2017 11:44:47 +0800
          Subject: [PATCH 0458/2110] Java doc, comments & exception message fix
          
          ---
           CHANGELOG.md                                         |  2 +-
           .../androidTest/java/io/realm/CollectionTests.java   |  2 +-
           .../androidTest/java/io/realm/DynamicRealmTests.java |  1 -
           .../androidTest/java/io/realm/NotificationsTest.java |  3 ++-
           .../java/io/realm/internal/CollectionTests.java      |  2 +-
           .../io/realm/internal/ObserverPairListTests.java     | 12 +++++++++---
           .../java/io/realm/internal/SortDescriptorTests.java  |  2 +-
           .../src/main/cpp/io_realm_internal_Collection.cpp    |  1 -
           .../src/main/cpp/java_sort_descriptor.hpp            |  4 ++--
           .../src/main/java/io/realm/BaseRealm.java            |  4 ++--
           .../realm-library/src/main/java/io/realm/Realm.java  |  7 +++----
           .../src/main/java/io/realm/RealmObject.java          |  1 -
           .../src/main/java/io/realm/internal/Collection.java  |  6 +++---
           .../main/java/io/realm/internal/FieldDescriptor.java |  2 +-
           14 files changed, 26 insertions(+), 23 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 5f244cb383..986ad832df 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -6,7 +6,7 @@
           
           ### Enhancements
           
          -* Add support for sorting by link's field (#672).
          +* Added support for sorting by link's field (#672).
           
           ### Internal
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java
          index 8f63b29412..98143a8d9f 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java
          @@ -91,7 +91,7 @@ protected void populateRealm(Realm realm, int objects) {
                           NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class);
                           nonLatinFieldNames.set델타(i);
                           nonLatinFieldNames.setΔέλτα(i);
          -                // Set the linked object to itself.
          +                // Sets the linked object to itself.
                           obj.setFieldObject(obj);
                       }
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java
          index 7a51be4d72..4e29111071 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java
          @@ -548,7 +548,6 @@ public void accessingDynamicRealmObjectBeforeAsyncQueryCompleted() {
                       dynamicRealmObject.getObject(AllTypes.FIELD_BINARY);
                       fail("trying to access a DynamicRealmObject property should throw");
                   } catch (IllegalStateException ignored) {
          -
                   } finally {
                       dynamicRealm.close();
                       looperThread.testComplete();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java
          index 4b1092de15..05d257c303 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java
          @@ -814,6 +814,7 @@ public void onChange(RealmResults object) {
                   });
               }
           
          +    // TODO: Fix or delete this test after integration of object notification from Object Store
               @Test
               @RunTestInLooperThread
               @Ignore
          @@ -1026,7 +1027,7 @@ public void accessingSyncRealmResultInsideAsyncResultListener() {
           
                   final RealmResults syncResults = realm.where(AllTypes.class).findAll();
           
          -        RealmResults results = realm.where(AllTypes.class).findAll();
          +        RealmResults results = realm.where(AllTypes.class).findAllAsync();
                   looperThread.keepStrongReference.add(results);
                   results.addChangeListener(new RealmChangeListener>() {
                       @Override
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java
          index 05ad2e55ab..ce6af9a2ec 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java
          @@ -1,5 +1,5 @@
           /*
          - * Copyright 2016 Realm Inc.
          + * Copyright 2017 Realm Inc.
            *
            * Licensed under the Apache License, Version 2.0 (the "License");
            * you may not use this file except in compliance with the License.
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java
          index 8862894219..6924d35f12 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java
          @@ -1,5 +1,5 @@
           /*
          - * Copyright 2016 Realm Inc.
          + * Copyright 2017 Realm Inc.
            *
            * Licensed under the Apache License, Version 2.0 (the "License");
            * you may not use this file except in compliance with the License.
          @@ -16,6 +16,7 @@
           
           package io.realm.internal;
           
          +import android.annotation.SuppressLint;
           import android.support.test.runner.AndroidJUnit4;
           
           import org.junit.After;
          @@ -30,6 +31,10 @@
           import static junit.framework.Assert.assertTrue;
           import static junit.framework.Assert.fail;
           
          +/**
          + * We are testing characteristic of the {@link ObserverPairList} here, such as:
          + * Ownership of the listeners, equality of the pair and all public APIs for the class.
          + */
           @RunWith(AndroidJUnit4.class)
           public class ObserverPairListTests {
           
          @@ -39,13 +44,13 @@ void onChange(Integer integer) {
               }
           
               private static class TestObserverPair extends ObserverPairList.ObserverPair  {
          -        public TestObserverPair(Integer observer, TestListener listener) {
          +        TestObserverPair(Integer observer, TestListener listener) {
                       super(observer, listener);
                   }
               }
           
               private ObserverPairList observerPairs;
          -    TestListener testListener = new TestListener();
          +    private TestListener testListener = new TestListener();
           
               private static final Integer ONE = 1;
               private static final Integer TWO = 2;
          @@ -95,6 +100,7 @@ public void add_noDuplicate() {
                   assertEquals(1, observerPairs.size());
               }
           
          +    @SuppressLint("UseValueOf")
               @Test
               public void remove() {
                   TestObserverPair pair = new TestObserverPair(ONE, testListener);
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java
          index 3b82038bdb..4c89367cdc 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java
          @@ -1,5 +1,5 @@
           /*
          - * Copyright 2016 Realm Inc.
          + * Copyright 2017 Realm Inc.
            *
            * Licensed under the Apache License, Version 2.0 (the "License");
            * you may not use this file except in compliance with the License.
          diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp
          index 27f727b293..9c82634047 100644
          --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp
          +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp
          @@ -223,7 +223,6 @@ Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong nat
                           break;
                       case io_realm_internal_Collection_AGGREGATE_FUNCTION_AVERAGE:
                           value = wrapper->get_results().average(index);
          -                // TODO: Align the behavior with ObjectStore.
                           if (!value) {
                               value = Optional(0.0);
                           }
          diff --git a/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp b/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp
          index 8c26543e65..613b16ece7 100644
          --- a/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp
          +++ b/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp
          @@ -25,8 +25,8 @@ namespace _impl {
           
           // For converting a Java SortDescriptor object to realm::SortDescriptor.
           // This class is not designed to be used across JNI calls. So it doesn't acquire a reference to the given Java object.
          -// We don't holding a pointer to the SortDescriptor in the Java object like normally we do is because of the ObjectStore
          -// always consume the SortDescriptor by calling the move constructor. Holding a empty SortDescriptor in Java level
          +// We don't hold a pointer to the SortDescriptor in the Java object like normally we do, because the ObjectStore
          +// always consumes the SortDescriptor by calling the move constructor. Holding an empty SortDescriptor in Java level
           // doesn't make too much sense and causes troubles with memory management.
           class JavaSortDescriptor {
           public:
          diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          index ede80a3b16..7cdaafb34d 100644
          --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          @@ -319,8 +319,8 @@ public void commitTransaction() {
                   checkIfValid();
                   sharedRealm.commitTransaction();
                   if (!isClosed()) {
          -            // The checking is because of the global listener is being called in commitTransaction from object store.
          -            // The Realm could be closed inside the listener. In this case, we have no way to handle it. Moving
          +            // FIXME: The checking is because of the global listener is being called in commitTransaction from object
          +            // store. The Realm could be closed inside the listener. In this case, we have no way to handle it. Moving
                       // SyncManger to Object Store will solve this.
                       ObjectServerFacade.getFacade(configuration.isSyncConfiguration())
                               .notifyCommit(configuration, sharedRealm.getLastSnapshotVersion());
          diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java
          index 588fbe90e8..a312be6836 100644
          --- a/realm/realm-library/src/main/java/io/realm/Realm.java
          +++ b/realm/realm-library/src/main/java/io/realm/Realm.java
          @@ -1272,10 +1272,10 @@ public  RealmQuery where(Class clazz) {
                *
                * @param listener the change listener.
                * @throws IllegalArgumentException if the change listener is {@code null}.
          +     * @throws IllegalStateException if you try to register a listener from a non-Looper or {@link IntentService} thread.
                * @see io.realm.RealmChangeListener
                * @see #removeChangeListener(RealmChangeListener)
                * @see #removeAllChangeListeners()
          -     * @see #waitForChange()
                */
               public void addChangeListener(RealmChangeListener listener) {
                   super.addListener(listener);
          @@ -1379,9 +1379,8 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction,
           
                   // If the user provided a Callback then we have to make sure the current Realm has an events looper to deliver
                   // the results.
          -        if ((onSuccess != null || onError != null)  && !canDeliverNotification) {
          -            throw new IllegalStateException("Your Realm is opened from a thread without an event looper." +
          -                    " The callback cannot be invoked.");
          +        if ((onSuccess != null || onError != null)) {
          +            sharedRealm.capabilities.checkCanDeliverNotification("Callback cannot be delivered on current thread.");
                   }
           
                   // We need to use the same configuration to open a background SharedRealm (i.e Realm)
          diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java
          index d2951115a7..96c2f91b5f 100644
          --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java
          +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java
          @@ -292,7 +292,6 @@ public static  boolean isManaged(E object) {
                * @return {@code true} if it successfully completed the query, {@code false} otherwise.
                */
               public final boolean load() {
          -        //noinspection deprecation
                   return RealmObject.load(this);
               }
           
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          index bbc582e6c0..d97841fa53 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          @@ -1,5 +1,5 @@
           /*
          - * Copyright 2014 Realm Inc.
          + * Copyright 2017 Realm Inc.
            *
            * Licensed under the Apache License, Version 2.0 (the "License");
            * you may not use this file except in compliance with the License.
          @@ -26,7 +26,7 @@
           
           /**
            * Java wrapper of Object Store Results class.
          - * It is the backend of binding's query results, link list and back links.
          + * It is the backend of binding's query results, link lists and back links.
            */
           @Keep
           public class Collection implements NativeObject {
          @@ -81,7 +81,7 @@ public void onCalled(CollectionObserverPair pair, Object observer) {
                               pair.onChange(observer);
                           }
                       };
          -    // Maintain a list of stable iterators. Iterator becomes invalid when the reattaching happens.
          +    // Maintains a list of stable iterators. Iterator becomes invalid when the reattaching happens.
               private final List> stableIterators = new ArrayList>();
           
               // Public for static checking in JNI
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java
          index a1d69da7f9..0db432235e 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java
          @@ -34,7 +34,7 @@ public FieldDescriptor(Table table, String fieldDescription, boolean allowLink,
                       throw new IllegalArgumentException("Illegal field name. It cannot start or end with a '.': " + fieldDescription);
                   }
                   if (fieldDescription.contains(".")) {
          -            // Resolve field description down to last field name
          +            // Resolves field description down to last field name
                       String[] names = fieldDescription.split("\\.");
                       long[] columnIndices = new long[names.length];
                       for (int i = 0; i < names.length - 1; i++) {
          
          From c08a36cc8068992b865c510525554a1eae8b3f2f Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 26 Jan 2017 14:51:32 +0800
          Subject: [PATCH 0459/2110] Fix cleared check in ObserverPairList iteration
          
          ---
           .../io/realm/internal/ObserverPairList.java    | 18 ++++++++++--------
           1 file changed, 10 insertions(+), 8 deletions(-)
          
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java
          index b3d3d2d088..5ac8e05453 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java
          @@ -22,8 +22,8 @@
           import java.util.concurrent.CopyOnWriteArrayList;
           
           /**
          - * An ObserverPairList holds a list of ObserverPairs. An {@link ObserverPair} is pair contains an observer and a
          - * listener. The observer is the object to react to the changes through the listener. The observer is saved as an weak
          + * An ObserverPairList holds a list of ObserverPairs. An {@link ObserverPair} is pair containing an observer and a
          + * listener. The observer is the object to react to the changes through the listener. The observer is saved as a weak
            * reference in the pair to control the life cycle of the listener. When the observer gets GCed, the corresponding pair
            * will be removed from the list. So DO NOT keep a strong reference to the observer in the subclass of listener since it
            * will cause leaks!
          @@ -98,13 +98,15 @@ interface Callback {
                */
               public void foreach(Callback callback) {
                   for (T pair : pairs) {
          -            Object observer = pair.observerRef.get();
          -            if (observer == null) {
          -                pairs.remove(pair);
          -            } else if (cleared) {
          +            if (cleared) {
                           break;
          -            } else if (!pair.removed) {
          -                callback.onCalled(pair, observer);
          +            } else {
          +                Object observer = pair.observerRef.get();
          +                if (observer == null) {
          +                    pairs.remove(pair);
          +                } else if (!pair.removed) {
          +                    callback.onCalled(pair, observer);
          +                }
                       }
                   }
               }
          
          From 9c722d07c83dc74760dcf292c8783e3fefa46b95 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 26 Jan 2017 15:14:11 +0800
          Subject: [PATCH 0460/2110] Fix ObseverPairList add after clear
          
          ---
           .../realm/internal/ObserverPairListTests.java | 23 +++++++++++++++++++
           .../io/realm/internal/ObserverPairList.java   |  2 +-
           2 files changed, 24 insertions(+), 1 deletion(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java
          index 6924d35f12..8fa19fbacf 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java
          @@ -24,6 +24,7 @@
           import org.junit.Test;
           import org.junit.runner.RunWith;
           
          +import java.util.concurrent.atomic.AtomicBoolean;
           import java.util.concurrent.atomic.AtomicInteger;
           
           import static junit.framework.Assert.assertEquals;
          @@ -100,6 +101,28 @@ public void add_noDuplicate() {
                   assertEquals(1, observerPairs.size());
               }
           
          +    // 1. add 2. clear 3. add 4. Check if the last listener can still be called.
          +    @Test
          +    public void add_worksAfterClears() {
          +        final AtomicBoolean foreachCalled = new AtomicBoolean(false);
          +        TestObserverPair pair = new TestObserverPair(ONE, testListener);
          +        observerPairs.add(pair);
          +        assertEquals(1, observerPairs.size());
          +
          +        observerPairs.clear();
          +
          +        observerPairs.add(pair);
          +        assertEquals(1, observerPairs.size());
          +        observerPairs.foreach(new ObserverPairList.Callback() {
          +            @Override
          +            public void onCalled(TestObserverPair pair, Object observer) {
          +                assertEquals(ONE, observer);
          +                foreachCalled.set(true);
          +            }
          +        });
          +        assertTrue(foreachCalled.get());
          +    }
          +
               @SuppressLint("UseValueOf")
               @Test
               public void remove() {
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java
          index 5ac8e05453..e3f0fa717a 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java
          @@ -124,7 +124,7 @@ public void add(T pair) {
                   if (!pairs.contains(pair)) {
                       pairs.add(pair);
                   }
          -        if (!cleared) {
          +        if (cleared) {
                       cleared = false;
                   }
               }
          
          From de9150cc8fa7c40af37fa501f7d9ac8cb5ffa3dd Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Thu, 26 Jan 2017 15:42:57 +0800
          Subject: [PATCH 0461/2110] PR fix
          
          ---
           .../androidTest/java/io/realm/RealmQueryTests.java    |  4 ++--
           .../java/io/realm/TypeBasedNotificationsTests.java    |  2 +-
           .../java/io/realm/internal/RealmNotifierTests.java    | 10 +---------
           .../main/java/io/realm/internal/SortDescriptor.java   | 11 ++++-------
           .../realm/internal/android/AndroidCapabilities.java   |  2 +-
           5 files changed, 9 insertions(+), 20 deletions(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java
          index 15c816303e..9908533d58 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java
          @@ -2712,7 +2712,7 @@ public void findAllSortedAsync_onSubObjectField() {
                   Realm realm = looperThread.realm;
                   populateTestRealm(realm, TEST_DATA_SIZE);
                   RealmResults results = realm.where(AllTypes.class)
          -                .findAllSorted(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE);
          +                .findAllSortedAsync(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE);
                   looperThread.keepStrongReference.add(results);
                   results.addChangeListener(new RealmChangeListener>() {
                       @Override
          @@ -2755,7 +2755,7 @@ public void findAllSortedAsync_listOnSubObjectField() {
           
                   populateTestRealm(realm, TEST_DATA_SIZE);
                   RealmResults results = realm.where(AllTypes.class)
          -                .findAllSorted(fieldNames, sorts);
          +                .findAllSortedAsync(fieldNames, sorts);
                   looperThread.keepStrongReference.add(results);
                   results.addChangeListener(new RealmChangeListener>() {
                       @Override
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java
          index 3fba8aa185..b7043c0dc1 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java
          @@ -491,7 +491,7 @@ public void callback_with_relevant_commit_realmobject_sync() {
                   dog.addChangeListener(new RealmChangeListener() {
                       @Override
                       public void onChange(Dog object) {
          -                // Step 4: Respond to relevant change
          +                // Step 3: Respond to relevant change
                           typebasedCommitInvocations.incrementAndGet();
                           assertEquals("Akamaru", dog.getName());
                           assertEquals(17, dog.getAge());
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java
          index 39e550701e..bb1f9ecc48 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java
          @@ -189,14 +189,6 @@ public void onChange(SharedRealm sharedRealm) {
                   // This should only remove the listeners related with dummyObserver
                   sharedRealm.realmNotifier.removeChangeListeners(dummyObserver);
           
          -        new Thread(new Runnable() {
          -            @Override
          -            public void run() {
          -                SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration);
          -                sharedRealm.beginTransaction();
          -                sharedRealm.commitTransaction();
          -                sharedRealm.close();
          -            }
          -        }).start();
          +        makeRemoteChanges(looperThread.realmConfiguration);
               }
           }
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java
          index 94a0d2aeb2..3d6b6cfc21 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java
          @@ -107,14 +107,11 @@ public static SortDescriptor getInstanceForDistinct(Table table, String[] fieldD
               }
           
               private static void checkFieldTypeForSort(FieldDescriptor descriptor, String fieldDescriptions) {
          -        for (RealmFieldType aValidFieldTypesForSort : validFieldTypesForSort) {
          -            if (aValidFieldTypesForSort == descriptor.getFieldType()) {
          -                return;
          -            }
          +        if (!validFieldTypesForSort.contains(descriptor.getFieldType())) {
          +            throw new IllegalArgumentException(String.format(
          +                    "Sort is not supported on '%s' field '%s' in '%s'.", descriptor.toString(), descriptor.getFieldName(),
          +                    fieldDescriptions));
                   }
          -        throw new IllegalArgumentException(String.format(
          -                "Sort is not supported on '%s' field '%s' in '%s'.", descriptor.toString(), descriptor.getFieldName(),
          -                fieldDescriptions));
               }
           
               private static void checkFieldTypeForDistinct(FieldDescriptor descriptor, String fieldDescriptions) {
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java
          index 1add1cc711..619ae6204e 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java
          @@ -45,7 +45,7 @@ public void checkCanDeliverNotification(String exceptionMessage) {
                   }
                   if (isIntentServiceThread) {
                       throw new IllegalStateException( exceptionMessage == null ? "" : (exceptionMessage + " ") +
          -                    "Realm cannot be automatically updated on a IntentService thread.");
          +                    "Realm cannot be automatically updated on an IntentService thread.");
                   }
               }
           
          
          From 302730daf7a6c9a1a82766986232ea3485d1f86f Mon Sep 17 00:00:00 2001
          From: Craig Russell 
          Date: Thu, 26 Jan 2017 16:26:03 +0000
          Subject: [PATCH 0462/2110] Fix typo on error message
          
          ---
           realm/realm-library/src/main/cpp/util.cpp | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp
          index ee2ee17f0d..e35fbdb309 100644
          --- a/realm/realm-library/src/main/cpp/util.cpp
          +++ b/realm/realm-library/src/main/cpp/util.cpp
          @@ -157,7 +157,7 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla
                       break;
               }
               if (jExceptionClass != NULL) {
          -        Log::e("Exception has been throw: %1", message.c_str());
          +        Log::e("Exception has been thrown: %1", message.c_str());
                   env->ThrowNew(jExceptionClass, message.c_str());
               }
               else {
          
          From 8055b1772f89998a87f3bb9e26ccdd0591615589 Mon Sep 17 00:00:00 2001
          From: Makoto Yamazaki 
          Date: Fri, 27 Jan 2017 17:00:21 +0900
          Subject: [PATCH 0463/2110] remove kapt dependency to
           io.realm:realm-annotations (#4110)
          
          * remove kapt dependency to io.realm:realm-annotations since io.realm:realm-annotations-processor has a transitive dependency to it
          
          This also fixes #4087.
          
          * add Changelog entry
          ---
           CHANGELOG.md                                               | 1 +
           gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy | 6 ------
           2 files changed, 1 insertion(+), 6 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index d528c32eb1..19b02fcdf2 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -3,6 +3,7 @@
           ### Bug fixes
           
           * Fixed NPE problem happened in SharedRealm.finalize() (#3730).
          +* Fixed a build error when the project is using Kotlin (#4087).
           
           ## 2.3.0
           
          diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy
          index 38829e6538..43bf3f89cd 100644
          --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy
          +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy
          @@ -59,20 +59,14 @@ class Realm implements Plugin {
                   project.repositories.add(project.getRepositories().jcenter())
                   project.dependencies.add("compile", "io.realm:realm-annotations:${Version.VERSION}")
                   if (usesAptPlugin) {
          -            project.dependencies.add("apt", "io.realm:realm-annotations:${Version.VERSION}")
                       project.dependencies.add("apt", "io.realm:realm-annotations-processor:${Version.VERSION}")
          -            project.dependencies.add("androidTestApt", "io.realm:realm-annotations:${Version.VERSION}")
                       project.dependencies.add("androidTestApt", "io.realm:realm-annotations-processor:${Version.VERSION}")
                   } else if (isKotlinProject && !preferAptOnKotlinProject) {
          -            project.dependencies.add("kapt", "io.realm:realm-annotations:${Version.VERSION}")
                       project.dependencies.add("kapt", "io.realm:realm-annotations-processor:${Version.VERSION}")
          -            project.dependencies.add("kaptAndroidTest", "io.realm:realm-annotations:${Version.VERSION}")
                       project.dependencies.add("kaptAndroidTest", "io.realm:realm-annotations-processor:${Version.VERSION}")
                   } else {
                       assert hasAnnotationProcessorConfiguration
          -            project.dependencies.add("annotationProcessor", "io.realm:realm-annotations:${Version.VERSION}")
                       project.dependencies.add("annotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}")
          -            project.dependencies.add("androidTestAnnotationProcessor", "io.realm:realm-annotations:${Version.VERSION}")
                       project.dependencies.add("androidTestAnnotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}")
                   }
               }
          
          From ecd5ebffe21b405a94b60f9224d5c884afc3e587 Mon Sep 17 00:00:00 2001
          From: Craig Russell 
          Date: Fri, 27 Jan 2017 11:25:21 +0000
          Subject: [PATCH 0464/2110] Fix typo on error message (#4113)
          
          ---
           realm/realm-library/src/main/cpp/util.cpp | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp
          index ee2ee17f0d..e35fbdb309 100644
          --- a/realm/realm-library/src/main/cpp/util.cpp
          +++ b/realm/realm-library/src/main/cpp/util.cpp
          @@ -157,7 +157,7 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla
                       break;
               }
               if (jExceptionClass != NULL) {
          -        Log::e("Exception has been throw: %1", message.c_str());
          +        Log::e("Exception has been thrown: %1", message.c_str());
                   env->ThrowNew(jExceptionClass, message.c_str());
               }
               else {
          
          From 4b6a1d627d729686b96aabc1a29c691a9612128f Mon Sep 17 00:00:00 2001
          From: Makoto Yamazaki 
          Date: Fri, 27 Jan 2017 23:19:41 +0900
          Subject: [PATCH 0465/2110] update Kotlin used in example to 1.0.6 (#4109)
          
          ---
           examples/kotlinExample/build.gradle | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle
          index 8741e4a82f..8034025e63 100644
          --- a/examples/kotlinExample/build.gradle
          +++ b/examples/kotlinExample/build.gradle
          @@ -1,5 +1,5 @@
           buildscript {
          -    ext.kotlin_version = '1.0.4'
          +    ext.kotlin_version = '1.0.6'
               repositories {
                   jcenter()
                   mavenCentral()
          
          From 094fe49ff189150f8060806a06565f7382f7c1aa Mon Sep 17 00:00:00 2001
          From: Makoto Yamazaki 
          Date: Tue, 31 Jan 2017 15:53:20 +0900
          Subject: [PATCH 0466/2110] Fixed a bug that the classes were replaced with a
           class in Gradle's classpath (#4127)
          
          * Fixed a bug that the classes were replaced with a class in Gradle's classpath (#3568).
          
          * update changelog entry
          ---
           CHANGELOG.md                                                     | 1 +
           .../src/main/groovy/io/realm/transformer/RealmTransformer.groovy | 1 -
           2 files changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 19b02fcdf2..28a03cb0a5 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -4,6 +4,7 @@
           
           * Fixed NPE problem happened in SharedRealm.finalize() (#3730).
           * Fixed a build error when the project is using Kotlin (#4087).
          +* Fixed a bug causing classes to be replaced by classes already in Gradle's classpath (#3568).
           
           ## 2.3.0
           
          diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy
          index 857cd304b2..e6e4813551 100644
          --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy
          +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy
          @@ -195,7 +195,6 @@ class RealmTransformer extends Transform {
                   // will use a cached object and all the classes will be frozen.
                   ClassPool classPool = new ClassPool(null)
                   classPool.appendSystemPath()
          -        classPool.appendClassPath(new LoaderClassPath(getClass().getClassLoader()))
           
                   inputs.each {
                       it.directoryInputs.each {
          
          From f0ee31fe9c225428ca516e10b209330ff9f0f9b9 Mon Sep 17 00:00:00 2001
          From: LYK 
          Date: Tue, 31 Jan 2017 18:08:25 +0900
          Subject: [PATCH 0467/2110] Fix tests and remove unnecessary imports and
           fields. (#4125)
          
          * Fix tests.
          
          * Remove unnecessary imports and fields.
          ---
           .../src/androidTest/java/io/realm/RealmLinkTests.java         | 4 ++--
           .../src/androidTest/java/io/realm/RealmTests.java             | 1 -
           .../java/io/realm/TypeBasedNotificationsTests.java            | 1 -
           .../java/io/realm/UnManagedOrderedRealmCollectionTests.java   | 1 -
           .../src/androidTest/java/io/realm/internal/JNIQueryTest.java  | 1 -
           .../java/io/realm/internal/JNITableInsertTest.java            | 3 ---
           .../java/io/realm/rule/TestRealmConfigurationFactory.java     | 1 -
           .../java/io/realm/services/RemoteProcessService.java          | 1 -
           .../src/main/java/io/realm/internal/RealmCore.java            | 2 --
           9 files changed, 2 insertions(+), 13 deletions(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java
          index fd1af8a287..8e7e01d727 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java
          @@ -296,7 +296,7 @@ public void querySingleRelationString() {
                   assertEquals(0, none1.size());
           
                   RealmResults owners2 = testRealm.where(Owner.class).notEqualTo("cat.name", "Max").findAll();
          -        assertEquals(1, owners1.size());
          +        assertEquals(1, owners2.size());
           
                   RealmResults none2 = testRealm.where(Owner.class).notEqualTo("cat.name", "Blackie").findAll();
                   assertEquals(0, none2.size());
          @@ -465,7 +465,7 @@ public void queryMultipleRelationsString() {
                   assertEquals(0, none1.size());
           
                   RealmResults owners2 = testRealm.where(Owner.class).notEqualTo("dogs.name", "King").findAll();
          -        assertEquals(1, owners1.size());
          +        assertEquals(1, owners2.size());
           
                   RealmResults none2 = testRealm.where(Owner.class).notEqualTo("dogs.name", "Pluto").findAll();
                   assertEquals(0, none1.size());
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java
          index 5c8491f488..d449711600 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java
          @@ -833,7 +833,6 @@ public void unicodeStrings() {
           
                   String test_char = "";
                   String test_char_old = "";
          -        String get_data = "";
           
                   for (int i = 0; i < 1000; i++) {
                       random_value = random.nextInt(25);
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java
          index 5ce7dd5cb8..59d59b1c7c 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java
          @@ -35,7 +35,6 @@
           import java.lang.ref.WeakReference;
           import java.util.Date;
           import java.util.concurrent.CountDownLatch;
          -import java.util.concurrent.TimeUnit;
           import java.util.concurrent.atomic.AtomicInteger;
           
           import io.realm.entities.AllTypes;
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java
          index ea515284b7..97f3326534 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java
          @@ -29,7 +29,6 @@
           import io.realm.entities.AllJavaTypes;
           import io.realm.rule.TestRealmConfigurationFactory;
           
          -import static org.junit.Assert.assertFalse;
           import static org.junit.Assert.assertTrue;
           import static org.junit.Assert.fail;
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java
          index 861d3e660c..8245069cb4 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java
          @@ -23,7 +23,6 @@
           
           import io.realm.Case;
           import io.realm.RealmFieldType;
          -import io.realm.Sort;
           import io.realm.TestHelper;
           
           public class JNIQueryTest extends TestCase {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java
          index 36507e6f58..a7c570267b 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java
          @@ -16,8 +16,6 @@
           
           package io.realm.internal;
           
          -import android.test.MoreAsserts;
          -
           import org.junit.Test;
           import org.junit.runner.RunWith;
           import org.junit.runners.Parameterized;
          @@ -31,7 +29,6 @@
           import io.realm.RealmFieldType;
           import io.realm.TestHelper;
           
          -import static org.junit.Assert.assertEquals;
           import static org.junit.Assert.assertTrue;
           import static org.junit.Assert.fail;
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java
          index 8bec3bfa4c..d02a0ea22b 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java
          @@ -17,7 +17,6 @@
           package io.realm.rule;
           
           import android.content.Context;
          -import android.content.res.AssetManager;
           import android.support.test.InstrumentationRegistry;
           
           import org.junit.rules.TemporaryFolder;
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java
          index 7a841817d0..54c724abd2 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java
          @@ -24,7 +24,6 @@
           import android.os.Message;
           import android.os.Messenger;
           import android.os.RemoteException;
          -import android.util.Log;
           
           import java.util.HashMap;
           import java.util.Map;
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java
          index 496aa18734..f658b1296e 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java
          @@ -21,9 +21,7 @@
           import com.getkeepsafe.relinker.ReLinker;
           
           import java.io.File;
          -import java.lang.reflect.Constructor;
           import java.lang.reflect.Field;
          -import java.lang.reflect.InvocationTargetException;
           import java.util.Locale;
           
           import io.realm.BuildConfig;
          
          From 21759e40ac5569162e33a10b1016be7024fe57a9 Mon Sep 17 00:00:00 2001
          From: Makoto Yamazaki 
          Date: Tue, 31 Jan 2017 21:29:10 +0900
          Subject: [PATCH 0468/2110] allow to skip native build by setting
           buildTargetABIs to empty (#4129)
          
          ---
           realm/realm-library/build.gradle | 5 ++++-
           1 file changed, 4 insertions(+), 1 deletion(-)
          
          diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle
          index 5e1e6be367..ac7f1affbd 100644
          --- a/realm/realm-library/build.gradle
          +++ b/realm/realm-library/build.gradle
          @@ -53,7 +53,7 @@ android {
                                   "-DCMAKE_TOOLCHAIN_FILE=${project.file('src/main/cpp/android.toolchain.cmake').path}"
                           if (project.ccachePath) arguments "-DNDK_CCACHE=$project.ccachePath"
                           if (project.lcachePath) arguments "-DNDK_LCACHE=$project.lcachePath"
          -                if (project.hasProperty('buildTargetABIs')) {
          +                if (project.hasProperty('buildTargetABIs') && !project.getProperty('buildTargetABIs').trim().isEmpty()) {
                               abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*'))
                           } else {
                               // armeabi is not supported anymore.
          @@ -523,6 +523,9 @@ if (project.hasProperty('dontCleanJniFiles')) {
           project.afterEvaluate {
               android.libraryVariants.all { variant ->
                   variant.externalNativeBuildTasks[0].dependsOn(checkNdk)
          +        if (project.hasProperty('buildTargetABIs') && project.getProperty('buildTargetABIs').trim().isEmpty()) {
          +            variant.externalNativeBuildTasks[0].enabled = false
          +        }
               }
           }
           
          
          From 0cfc88c957a00f3292a0441db476f865fd217088 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Wed, 1 Feb 2017 16:38:13 +0800
          Subject: [PATCH 0469/2110] RealmObject could be GCed when
           completedAsyncRealmObject (#4099)
          
          ---
           CHANGELOG.md                                         |  1 +
           .../src/main/java/io/realm/HandlerController.java    | 12 +++++++-----
           2 files changed, 8 insertions(+), 5 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 28a03cb0a5..2b7717d605 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -5,6 +5,7 @@
           * Fixed NPE problem happened in SharedRealm.finalize() (#3730).
           * Fixed a build error when the project is using Kotlin (#4087).
           * Fixed a bug causing classes to be replaced by classes already in Gradle's classpath (#3568).
          +* NullPointerException when notifying a single object that it changed (#4086).
           
           ## 2.3.0
           
          diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java
          index 5f76391b8a..fa24bff167 100644
          --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java
          +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java
          @@ -628,12 +628,14 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) {
                           } else if (compare > 0) {
                               // the caller has advanced we need to
                               // retry against the current version of the caller if it's still empty
          -                    if (RealmObject.isValid(proxy)) { // already completed & has a valid pointer no need to re-run
          -                        RealmLog.trace("[COMPLETED_ASYNC_REALM_OBJECT %s], realm: %s. " +
          -                                "RealmObject is already loaded, just notify it",
          -                                realm, HandlerController.this);
          -                        proxy.realmGet$proxyState().notifyChangeListeners$realm();
          +                    if (RealmObject.isLoaded(proxy)) { // already completed & has a valid pointer no need to re-run
          +                        if (RealmObject.isValid(proxy)) {
          +                            RealmLog.trace("[COMPLETED_ASYNC_REALM_OBJECT %s], realm: %s. " +
          +                                            "RealmObject is already loaded, just notify it",
          +                                    realm, HandlerController.this);
          +                            proxy.realmGet$proxyState().notifyChangeListeners$realm();
           
          +                        }
                               } else {
                                   RealmLog.trace("[COMPLETED_ASYNC_REALM_OBJECT %s, realm: %s. " +
                                           "RealmObject is not loaded yet. Rerun the query.",
          
          From 48355fb87a9ca7eac5f436beeaa86c78e2591495 Mon Sep 17 00:00:00 2001
          From: Christian Melchior 
          Date: Thu, 2 Feb 2017 12:16:25 +0100
          Subject: [PATCH 0470/2110] PR feedback
          
          ---
           .../io/realm/internal/CollectionTests.java    | 14 ++++-----
           .../realm/internal/SortDescriptorTests.java   | 30 +++++++++----------
           .../src/main/java/io/realm/BaseRealm.java     |  2 +-
           .../src/main/java/io/realm/Realm.java         |  1 +
           .../java/io/realm/internal/Capabilities.java  |  2 +-
           .../java/io/realm/internal/Collection.java    |  1 -
           .../io/realm/internal/FieldDescriptor.java    | 10 +++++--
           .../java/io/realm/internal/RealmNotifier.java |  4 +--
           .../android/AndroidRealmNotifier.java         | 12 +++-----
           9 files changed, 37 insertions(+), 39 deletions(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java
          index ce6af9a2ec..bf7d164526 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java
          @@ -162,7 +162,7 @@ public void size() {
               @Test
               public void where() {
                   Collection collection = new Collection(sharedRealm, table.where());
          -        Collection collection2 =new Collection(sharedRealm, collection.where().equalTo(new long[]{0}, "John"));
          +        Collection collection2 = new Collection(sharedRealm, collection.where().equalTo(new long[]{0}, "John"));
                   Collection collection3 =new Collection(sharedRealm, collection2.where().equalTo(new long[]{1}, "Anderson"));
           
                   // A new native Results should be created.
          @@ -179,7 +179,7 @@ public void sort() {
                   Collection collection = new Collection(sharedRealm, table.where().greaterThan(new long[]{2}, 1));
                   SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2});
           
          -        Collection collection2 =collection.sort(sortDescriptor);
          +        Collection collection2 = collection.sort(sortDescriptor);
           
                   // A new native Results should be created.
                   assertTrue(collection.getNativePtr() != collection2.getNativePtr());
          @@ -229,7 +229,7 @@ public void distinct() {
                   Collection collection = new Collection(sharedRealm, table.where().lessThan(new long[]{2}, 4));
           
                   SortDescriptor distinctDescriptor = new SortDescriptor(table, new long[] {2});
          -        Collection collection2 =collection.distinct(distinctDescriptor);
          +        Collection collection2 = collection.distinct(distinctDescriptor);
           
                   // A new native Results should be created.
                   assertTrue(collection.getNativePtr() != collection2.getNativePtr());
          @@ -287,7 +287,7 @@ public void onChange(Collection collection1) {
               public void addListener_shouldBeCalledWhenRefreshAfterLocalCommit() {
                   final CountDownLatch latch = new CountDownLatch(1);
                   Collection collection = new Collection(sharedRealm, table.where());
          -        collection.size();
          +        assertEquals(4, collection.size()); // See `populateData()`
                   collection.addListener(collection, new RealmChangeListener() {
                       @Override
                       public void onChange(Collection element) {
          @@ -496,9 +496,9 @@ public void reattach_looperThread_byLocalTransaction() {
                   assertEquals(collection.size(), 4);
                   collection.addListener(collection, new RealmChangeListener() {
                       @Override
          -            public void onChange(Collection element) {
          -                assertFalse(collection.isDetached());
          -                assertEquals(collection.size(), 5);
          +            public void onChange(Collection col) {
          +                assertFalse(col.isDetached());
          +                assertEquals(col.size(), 5);
                           sharedRealm.close();
                           looperThread.testComplete();
                       }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java
          index 4c89367cdc..b5c26f71fd 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java
          @@ -80,15 +80,25 @@ public void getInstanceForDistinct() {
               }
           
               @Test
          -    public void getInstanceForDistinct_shouldThrowOnLinkField() {
          +    public void getInstanceForDistinct_shouldThrowOnLinkAndListListField() {
                   RealmFieldType type = RealmFieldType.STRING;
                   RealmFieldType objectType = RealmFieldType.OBJECT;
          +        RealmFieldType listType = RealmFieldType.LIST;
                   table.addColumn(type, type.name());
                   table.addColumnLink(objectType, objectType.name(), table);
          +        table.addColumnLink(listType, listType.name(), table);
           
          -        thrown.expect(IllegalArgumentException.class);
          -        thrown.expectMessage("is not a supported link field");
          -        SortDescriptor.getInstanceForDistinct(table, String.format("%s.%s", objectType.name(), type.name()));
          +        try {
          +            SortDescriptor.getInstanceForDistinct(table, String.format("%s.%s", listType.name(), type.name()));
          +            fail();
          +        } catch (IllegalArgumentException ignored) {
          +        }
          +
          +        try {
          +            SortDescriptor.getInstanceForDistinct(table, String.format("%s.%s", objectType.name(), type.name()));
          +            fail();
          +        } catch (IllegalArgumentException ignored) {
          +        }
               }
           
               @Test
          @@ -147,18 +157,6 @@ public void getInstanceForDistinct_shouldThrowOnInvalidField() {
                   }
               }
           
          -    @Test
          -    public void getInstanceForDistinct_shouldThrowOnLinkListField() {
          -        RealmFieldType type = RealmFieldType.STRING;
          -        RealmFieldType listType = RealmFieldType.LIST;
          -        table.addColumn(type, type.name());
          -        table.addColumnLink(listType, listType.name(), table);
          -
          -        thrown.expect(IllegalArgumentException.class);
          -        thrown.expectMessage("is not a supported link field");
          -        SortDescriptor.getInstanceForDistinct(table, String.format("%s.%s", listType.name(), type.name()));
          -    }
          -
               @Test
               public void getInstanceForSort() {
                   for (RealmFieldType type : SortDescriptor.validFieldTypesForSort) {
          diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          index 7cdaafb34d..a1703dc56a 100644
          --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          @@ -319,7 +319,7 @@ public void commitTransaction() {
                   checkIfValid();
                   sharedRealm.commitTransaction();
                   if (!isClosed()) {
          -            // FIXME: The checking is because of the global listener is being called in commitTransaction from object
          +            // FIXME: The checking is because the global listener is being called in commitTransaction from object
                       // store. The Realm could be closed inside the listener. In this case, we have no way to handle it. Moving
                       // SyncManger to Object Store will solve this.
                       ObjectServerFacade.getFacade(configuration.isSyncConfiguration())
          diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java
          index a312be6836..9a62aa8be0 100644
          --- a/realm/realm-library/src/main/java/io/realm/Realm.java
          +++ b/realm/realm-library/src/main/java/io/realm/Realm.java
          @@ -17,6 +17,7 @@
           package io.realm;
           
           import android.annotation.TargetApi;
          +import android.app.IntentService;
           import android.content.Context;
           import android.os.Build;
           import android.util.JsonReader;
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java
          index 6f89710304..5eaa72a770 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java
          @@ -30,7 +30,7 @@ public interface Capabilities {
               boolean canDeliverNotification();
           
               /**
          -     * Throw if this Realm cannot receive notifications.
          +     * Check if a Realm is able to receive a notification. If not, an {@link IllegalStateException} should be be thrown.
                *
                * @param exceptionMessage message which is contained in the exception.
                */
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          index d97841fa53..76a9467099 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java
          @@ -145,7 +145,6 @@ static Mode getByValue(byte value) {
                   }
               }
           
          -
               // neverDetach means the collection won't be detached when local transaction starts. This is useful for the
               // PendingRow implementation.
               public Collection(SharedRealm sharedRealm, TableQuery query,
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java
          index 0db432235e..e910135e5a 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java
          @@ -19,6 +19,10 @@
           
           import io.realm.RealmFieldType;
           
          +
          +/**
          + * Class describing a single field possible several links away.
          + */
           public class FieldDescriptor {
           
               private long[] columnIndices;
          @@ -39,7 +43,7 @@ public FieldDescriptor(Table table, String fieldDescription, boolean allowLink,
                       long[] columnIndices = new long[names.length];
                       for (int i = 0; i < names.length - 1; i++) {
                           long index = table.getColumnIndex(names[i]);
          -                if (index < 0) {
          +                if (index == Table.NO_MATCH) {
                               throw new IllegalArgumentException(
                                       String.format("Invalid field name: '%s' does not refer to a class.", names[i]));
                           }
          @@ -51,7 +55,7 @@ public FieldDescriptor(Table table, String fieldDescription, boolean allowLink,
                               throw new IllegalArgumentException(
                                       String.format("'RealmList' field '%s' is not a supported link field here.", names[i]));
                           } else if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) {
          -                    table = table.getLinkTarget(index);
          +                     table = table.getLinkTarget(index);
                               columnIndices[i] = index;
                           } else {
                               throw new IllegalArgumentException(
          @@ -63,7 +67,7 @@ public FieldDescriptor(Table table, String fieldDescription, boolean allowLink,
                       String columnName = names[names.length - 1];
                       long columnIndex = table.getColumnIndex(columnName);
                       columnIndices[names.length - 1] = columnIndex;
          -            if (columnIndex < 0) {
          +            if (columnIndex == Table.NO_MATCH) {
                           throw new IllegalArgumentException(
                                   String.format("'%s' is not a field name in class '%s'.", columnName, table.getName()));
                       }
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java
          index 96c0a7e678..6ebd3d3706 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java
          @@ -148,14 +148,14 @@ public void addTransactionCallback(Runnable runnable) {
                *
                * @param runnable to be executed at the next event loop.
                */
          -    public abstract void postAtFrontOfQueue(Runnable runnable);
          +    public abstract boolean postAtFrontOfQueue(Runnable runnable);
           
               /**
                * For current implementation of async transaction only. See comments for {@link #transactionCallbacks}.
                *
                * @param runnable to be executed in the following event loop.
                */
          -    public abstract void post(Runnable runnable);
          +    public abstract boolean post(Runnable runnable);
           
               public int getListenersListSize() {
                   return realmObserverPairs.size();
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java
          index 2d74b1f0d2..8b70497c1a 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java
          @@ -25,17 +25,13 @@ public AndroidRealmNotifier(SharedRealm sharedRealm, Capabilities capabilities)
               }
           
               @Override
          -    public void postAtFrontOfQueue(Runnable runnable) {
          -        if (handler != null) {
          -            handler.postAtFrontOfQueue(runnable);
          -        }
          +    public boolean postAtFrontOfQueue(Runnable runnable) {
          +        return handler != null && handler.postAtFrontOfQueue(runnable);
               }
           
               @Override
          -    public void post(Runnable runnable) {
          -        if (handler != null) {
          -            handler.post(runnable);
          -        }
          +    public boolean post(Runnable runnable) {
          +        return handler != null && handler.post(runnable);
               }
           
               @Override
          
          From dd6566ce54d4960e9ca11336bb3ea5b51483fed1 Mon Sep 17 00:00:00 2001
          From: Christian Melchior 
          Date: Thu, 2 Feb 2017 16:20:45 +0100
          Subject: [PATCH 0471/2110] Fix RealmCollection.contains not respecting custom
           equal methods (#4111)
          
          ---
           CHANGELOG.md                                  |  7 +--
           .../java/io/realm/RealmCollectionTests.java   | 46 +++++++++++++++++++
           .../java/io/realm/RealmObjectTests.java       | 21 +--------
           .../java/io/realm/entities/CustomMethods.java | 30 +++++++-----
           .../src/main/java/io/realm/RealmList.java     | 17 +++++--
           .../src/main/java/io/realm/RealmResults.java  | 20 +++++---
           6 files changed, 97 insertions(+), 44 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 2b7717d605..1d8b6d9159 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -2,9 +2,10 @@
           
           ### Bug fixes
           
          -* Fixed NPE problem happened in SharedRealm.finalize() (#3730).
          -* Fixed a build error when the project is using Kotlin (#4087).
          -* Fixed a bug causing classes to be replaced by classes already in Gradle's classpath (#3568).
          +* NPE problem in SharedRealm.finalize() (#3730).
          +* `RealmList.contains()` and `RealmResults.contains()` now correctly use custom `equals()` method on Realm model classes.
          +* Build error when the project is using Kotlin (#4087).
          +* Bug causing classes to be replaced by classes already in Gradle's classpath (#3568).
           * NullPointerException when notifying a single object that it changed (#4086).
           
           ## 2.3.0
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java
          index 1fcc0c64df..e3c1e2f2b6 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java
          @@ -30,6 +30,7 @@
           import java.util.List;
           
           import io.realm.entities.AllJavaTypes;
          +import io.realm.entities.CustomMethods;
           import io.realm.entities.Dog;
           import io.realm.entities.NullTypes;
           import io.realm.rule.TestRealmConfigurationFactory;
          @@ -133,6 +134,38 @@ private RealmCollection createCollection(CollectionClass collectio
                   }
               }
           
          +    private RealmCollection createCustomMethodsCollection(Realm realm, CollectionClass collectionClass) {
          +        switch (collectionClass) {
          +            case MANAGED_REALMLIST:
          +                realm.beginTransaction();
          +                CustomMethods top = realm.createObject(CustomMethods.class);
          +                top.setName("Top");
          +                for (int i = 0; i < TEST_SIZE; i++) {
          +                    top.getMethods().add(new CustomMethods("Child" + i));
          +                }
          +                realm.commitTransaction();
          +                return top.getMethods();
          +
          +            case UNMANAGED_REALMLIST:
          +                RealmList list = new RealmList();
          +                for (int i = 0; i < TEST_SIZE; i++) {
          +                    list.add(new CustomMethods("Child" + i));
          +                }
          +                return list;
          +
          +            case REALMRESULTS:
          +                realm.beginTransaction();
          +                for (int i = 0; i < TEST_SIZE; i++) {
          +                    realm.copyToRealm(new CustomMethods("Child" + i));
          +                }
          +                realm.commitTransaction();
          +                return realm.where(CustomMethods.class).findAll();
          +
          +            default:
          +                throw new AssertionError("Unsupported class: " + collectionClass);
          +        }
          +    }
          +
               private OrderedRealmCollection createEmptyCollection(Realm realm, CollectionClass collectionClass) {
                   switch (collectionClass) {
                       case MANAGED_REALMLIST:
          @@ -181,6 +214,19 @@ public void contains_null() {
                   assertFalse(collection.contains(null));
               }
           
          +    // Test that the custom equal methods is being used when testing if an object is part of the
          +    // collection
          +    @Test
          +    public void contains_customEqualMethod() {
          +        RealmCollection collection = createCustomMethodsCollection(realm, collectionClass);
          +        // This custom equals method will only consider the field `name` when comparing objects.
          +        // So this unmanaged version should be equal to any object with the same value, managed
          +        // or not.
          +        assertTrue(collection.contains(new CustomMethods("Child0")));
          +        assertTrue(collection.contains(new CustomMethods("Child" + (TEST_SIZE - 1))));
          +        assertFalse(collection.contains(new CustomMethods("Child" + TEST_SIZE)));
          +    }
          +
               @Test
               public void containsAll() {
                   Iterator it = collection.iterator();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java
          index 0224d6496c..cba3faf225 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java
          @@ -425,23 +425,6 @@ public void equals_plainCustomMethod() {
                   assertTrue(cm1.equals(cm2));
               }
           
          -    @Test
          -    public void equals_reverseCustomMethod() {
          -        realm.beginTransaction();
          -        CustomMethods cm = realm.createObject(CustomMethods.class);
          -        cm.setName("Foo");
          -        realm.commitTransaction();
          -
          -        CustomMethods cm1 = realm.where(CustomMethods.class).findFirst();
          -        CustomMethods cm2 = realm.where(CustomMethods.class).findFirst();
          -
          -        realm.beginTransaction();
          -        cm1.reverseEquals = true;
          -        realm.commitTransaction();
          -
          -        assertFalse(cm1.equals(cm2));
          -    }
          -
               @Test
               public void equals_unmanagedCustomMethod() {
                   CustomMethods cm1 = new CustomMethods();
          @@ -464,8 +447,8 @@ public void equals_mixedCustomMethod() {
                   realm.commitTransaction();
           
                   CustomMethods cm3 = realm.where(CustomMethods.class).findFirst();
          -        assertFalse(cm3.equals(cm2));
          -        assertTrue(cm3.getName().equals(cm2.getName()));
          +        assertTrue(cm3.equals(cm2));
          +        assertTrue(cm2.equals(cm3));
               }
           
               @Test
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/CustomMethods.java b/realm/realm-library/src/androidTest/java/io/realm/entities/CustomMethods.java
          index 66ad9a11c8..79552b2dbb 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/entities/CustomMethods.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/CustomMethods.java
          @@ -16,6 +16,7 @@
           
           package io.realm.entities;
           
          +import io.realm.RealmList;
           import io.realm.RealmObject;
           import io.realm.annotations.Ignore;
           
          @@ -24,6 +25,14 @@ public class CustomMethods extends RealmObject {
               public static final int HASHCODE = 1;
           
               private String name;
          +    private RealmList methods;
          +
          +    public CustomMethods() {
          +    }
          +
          +    public CustomMethods(String name) {
          +        this.name = name;
          +    }
           
               public String getName() {
                   return name;
          @@ -33,20 +42,19 @@ public void setName(String name) {
                   this.name = name;
               }
           
          -    @Ignore
          -    public boolean reverseEquals;
          +    public RealmList getMethods() {
          +        return methods;
          +    }
           
               @Override
               public boolean equals(Object o) {
          -        if (!(o instanceof CustomMethods)) {
          -            return reverseEquals;
          -        }
          -        CustomMethods other = (CustomMethods) o;
          -        if (isManaged() == other.isManaged() && other.name.equals(name)) {
          -            return !reverseEquals;
          -        } else {
          -            return reverseEquals;
          -        }
          +        if (this == o) return true;
          +        if (o == null || !(o instanceof CustomMethods)) return false;
          +
          +        CustomMethods that = (CustomMethods) o;
          +
          +        // Only compare name. Managed and unmanaged objects will be equal as long as they have the same value
          +        return name != null ? name.equals(that.name) : that.name == null;
               }
           
               @Override
          diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java
          index 1fcb3fedef..80d7947417 100644
          --- a/realm/realm-library/src/main/java/io/realm/RealmList.java
          +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java
          @@ -720,19 +720,26 @@ public boolean load() {
                */
               @Override
               public boolean contains(Object object) {
          -        boolean contains = false;
                   if (managedMode) {
                       realm.checkIfValid();
          +
          +            // Deleted objects can never be part of a RealmList
                       if (object instanceof RealmObjectProxy) {
                           RealmObjectProxy proxy = (RealmObjectProxy) object;
          -                if (proxy.realmGet$proxyState().getRow$realm() != null && realm.getPath().equals(proxy.realmGet$proxyState().getRealm$realm().getPath()) && proxy.realmGet$proxyState().getRow$realm() != InvalidRow.INSTANCE) {
          -                    contains = view.contains(proxy.realmGet$proxyState().getRow$realm().getIndex());
          +                if (proxy.realmGet$proxyState().getRow$realm() == InvalidRow.INSTANCE) {
          +                    return false;
                           }
                       }
          +
          +            for (E e : this) {
          +                if (e.equals(object)) {
          +                    return true;
          +                }
          +            }
          +            return false;
                   } else {
          -            contains = unmanagedList.contains(object);
          +            return unmanagedList.contains(object);
                   }
          -        return contains;
               }
           
               /**
          diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java
          index 6311d45e47..d1e56885a9 100644
          --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java
          +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java
          @@ -189,14 +189,22 @@ public RealmQuery where() {
                */
               @Override
               public boolean contains(Object object) {
          -        boolean contains = false;
          -        if (isLoaded() && object instanceof RealmObjectProxy) {
          -            RealmObjectProxy proxy = (RealmObjectProxy) object;
          -            if (realm.getPath().equals(proxy.realmGet$proxyState().getRealm$realm().getPath()) && proxy.realmGet$proxyState().getRow$realm() != InvalidRow.INSTANCE) {
          -                contains = (table.sourceRowIndex(proxy.realmGet$proxyState().getRow$realm().getIndex()) != TableOrView.NO_MATCH);
          +        if (isLoaded()) {
          +            // Deleted objects can never be part of a RealmResults
          +            if (object instanceof RealmObjectProxy) {
          +                RealmObjectProxy proxy = (RealmObjectProxy) object;
          +                if (proxy.realmGet$proxyState().getRow$realm() == InvalidRow.INSTANCE) {
          +                    return false;
          +                }
          +            }
          +
          +            for (E e : this) {
          +                if (e.equals(object)) {
          +                    return true;
          +                }
                       }
                   }
          -        return contains;
          +        return false;
               }
           
               /**
          
          From f75f61a74844c129c9e82f7bcead3d2abd54e549 Mon Sep 17 00:00:00 2001
          From: Nabil Hachicha 
          Date: Fri, 3 Feb 2017 08:49:05 +0000
          Subject: [PATCH 0472/2110] remove duplicate init of the metadata (#4116)
          
          ---
           .../java/io/realm/SyncUserTests.java                   |  2 +-
           .../src/main/cpp/io_realm_RealmFileUserStore.cpp       | 10 ----------
           .../src/objectServer/java/io/realm/ObjectServer.java   | 10 +++++-----
           .../objectServer/java/io/realm/RealmFileUserStore.java |  6 ------
           4 files changed, 6 insertions(+), 22 deletions(-)
          
          diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          index 27f92b0dce..116fab3a23 100644
          --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java
          @@ -57,7 +57,7 @@ public class SyncUserTests {
               @BeforeClass
               public static void initUserStore() {
                   Realm.init(InstrumentationRegistry.getInstrumentation().getContext());
          -        UserStore userStore = new RealmFileUserStore(InstrumentationRegistry.getTargetContext().getFilesDir().getPath());
          +        UserStore userStore = new RealmFileUserStore();
                   SyncManager.setUserStore(userStore);
               }
           
          diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp
          index e4a0a4a60f..076fb54292 100644
          --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp
          +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp
          @@ -85,16 +85,6 @@ Java_io_realm_RealmFileUserStore_nativeLogoutUser (JNIEnv *env, jclass, jstring
           }
           
           
          -JNIEXPORT void JNICALL
          -Java_io_realm_RealmFileUserStore_nativeConfigureMetaDataSystem (JNIEnv *env, jclass, jstring baseFile)
          -{
          -    TR_ENTER()
          -    try {
          -        JStringAccessor base_file_path(env, baseFile); // throws
          -        SyncManager::shared().configure_file_system(base_file_path, SyncManager::MetadataMode::NoEncryption);
          -    } CATCH_STD()
          -}
          -
           JNIEXPORT jobjectArray JNICALL
           Java_io_realm_RealmFileUserStore_nativeGetAllUsers (JNIEnv *env, jclass)
           {
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java
          index 5ab8d01656..fcf681a3ec 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java
          @@ -38,14 +38,14 @@ public static void init(Context context) {
                   } catch (Exception ignore) {
                   }
           
          -        // Configure default UserStore
          -        UserStore userStore = new RealmFileUserStore(context.getFilesDir().getPath());
          -
          -        SyncManager.init(appId, userStore);
          -
                   // init the "sync_manager.cpp" metadata Realm, this is also needed later, when re try
                   // to schedule a client reset. in realm-java#master this is already done, when initialising
                   // the RealmFileUserStore (not available now on releases)
                   SyncManager.nativeConfigureMetaDataSystem(context.getFilesDir().getPath());
          +
          +        // Configure default UserStore
          +        UserStore userStore = new RealmFileUserStore();
          +
          +        SyncManager.init(appId, userStore);
               }
           }
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java
          index 9ba14bff4e..61a3ad24c5 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java
          @@ -24,9 +24,6 @@
            * A User Store backed by a Realm file to store user.
            */
           public class RealmFileUserStore implements UserStore {
          -    protected RealmFileUserStore(String path) {
          -        nativeConfigureMetaDataSystem(path);
          -    }
           
               /**
                * {@inheritDoc}
          @@ -87,9 +84,6 @@ private static SyncUser toSyncUserOrNull(String userJson) {
                   return SyncUser.fromJson(userJson);
               }
           
          -    // init and load the Metadata Realm containing SyncUsers
          -    protected static native void nativeConfigureMetaDataSystem(String baseFile);
          -
               // returns json data (token) of the current logged in user
               protected static native String nativeGetCurrentUser();
           
          
          From e12ac2eeb901c448904d9fe8e0ce6d511783b68a Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Fri, 3 Feb 2017 19:23:16 +0800
          Subject: [PATCH 0473/2110] Fix checkstyle space before opening parenthesis
          
          It should match:
            if()
          but not:
            ifSomething().
          ---
           realm/config/checkstyle/checkstyle.xml | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/realm/config/checkstyle/checkstyle.xml b/realm/config/checkstyle/checkstyle.xml
          index b3903724c8..3926b73b72 100644
          --- a/realm/config/checkstyle/checkstyle.xml
          +++ b/realm/config/checkstyle/checkstyle.xml
          @@ -26,7 +26,7 @@
           
               
               
          -        
          +        
                   
               
           
          
          From 4417b221a02336721f46b069b8fe0c015f1be2b9 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Sat, 4 Feb 2017 18:13:58 +0800
          Subject: [PATCH 0474/2110] Collection.where() should build query on tableview
          
          ---
           .../src/androidTest/java/io/realm/RealmResultsTests.java    | 6 ++----
           .../src/main/cpp/io_realm_internal_Collection.cpp           | 4 +++-
           2 files changed, 5 insertions(+), 5 deletions(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java
          index 0f92108f5b..e128207490 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java
          @@ -832,8 +832,7 @@ public void onChange(RealmResults dogs) {
                           assertEquals(null, dogs.minDate(Dog.FIELD_BIRTHDAY));
                           assertEquals(null, dogs.maxDate(Dog.FIELD_BIRTHDAY));
           
          -                // FIXME: Enable this when https://github.com/realm/realm-core/issues/2378 fixed.
          -                //assertEquals(0, dogs.where().findAll().size());
          +                assertEquals(0, dogs.where().findAll().size());
           
                           looperThread.testComplete();
                       }
          @@ -899,8 +898,7 @@ public void onChange(RealmResults dogs) {
                           assertEquals(null, dogs.minDate(Dog.FIELD_BIRTHDAY));
                           assertEquals(null, dogs.maxDate(Dog.FIELD_BIRTHDAY));
           
          -                // FIXME: Enable this when https://github.com/realm/realm-core/issues/2378 fixed.
          -                // assertEquals(0, dogs.where().findAll().size());
          +                assertEquals(0, dogs.where().findAll().size());
           
                           looperThread.testComplete();
                       }
          diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp
          index 9c82634047..17a573db50 100644
          --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp
          +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp
          @@ -333,7 +333,9 @@ Java_io_realm_internal_Collection_nativeWhere(JNIEnv *env, jclass, jlong native_
               try {
                   auto wrapper = reinterpret_cast(native_ptr);
           
          -        Query *query = new Query(wrapper->get_original_results().get_query());
          +        auto table_view = wrapper->get_original_results().get_tableview();
          +        Query *query = new Query(table_view.get_parent(),
          +                                 std::unique_ptr(new TableView(std::move(table_view))));
                   return reinterpret_cast(query);
               } CATCH_STD()
               return 0;
          
          From 5d9f0aa6fab66c4415411bf192e2a6d7a4a29afa Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Sat, 4 Feb 2017 18:20:42 +0800
          Subject: [PATCH 0475/2110] Fix warning message in test case
          
          ---
           .../src/androidTest/java/io/realm/RealmAsyncQueryTests.java     | 2 +-
           1 file changed, 1 insertion(+), 1 deletion(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java
          index 69bba49553..c0502c6f01 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java
          @@ -243,7 +243,7 @@ public void onSuccess() {
                       public void onError(Throwable error) {
                           // Ensure we are giving developers quality messages in the logs.
                           assertTrue(testLogger.message.contains(
          -                        "Exception has been throw: Can't commit a non-existing write transaction"));
          +                        "Exception has been thrown: Can't commit a non-existing write transaction"));
                           assertTrue(error instanceof IllegalStateException);
                           RealmLog.remove(testLogger);
                           looperThread.testComplete();
          
          From 88d0ca88d32dbe3e764b42bb0aeef81d93503a8d Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Mon, 6 Feb 2017 09:47:56 +0800
          Subject: [PATCH 0476/2110] Where on async results will load the query
          
          ---
           .../src/androidTest/java/io/realm/RealmAsyncQueryTests.java    | 3 ++-
           1 file changed, 2 insertions(+), 1 deletion(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java
          index c0502c6f01..4ed69b943f 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java
          @@ -696,7 +696,8 @@ public void combiningAsyncAndSync() {
                   final RealmResults allTypesAsync = looperThread.realm.where(AllTypes.class).greaterThan("columnLong", 5).findAllAsync();
                   final RealmResults allTypesSync = allTypesAsync.where().greaterThan("columnLong", 3).findAll();
           
          -        assertEquals(0, allTypesAsync.size());
          +        // Call where() on an async results will load the async query immediately.
          +        assertEquals(4, allTypesAsync.size());
                   assertEquals(4, allTypesSync.size()); // columnLong > 5 && columnLong > 3
                   allTypesAsync.addChangeListener(new RealmChangeListener>() {
                       @Override
          
          From bd4e7d70b29cee1b8e5b1ecc80b12a226cb0d461 Mon Sep 17 00:00:00 2001
          From: Chen Mulong 
          Date: Mon, 6 Feb 2017 11:26:34 +0800
          Subject: [PATCH 0477/2110] Reset handler when close AndroidRealmNotifier
          
          ---
           .../java/io/realm/internal/android/AndroidRealmNotifier.java     | 1 +
           1 file changed, 1 insertion(+)
          
          diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java
          index 8b70497c1a..c33451c82d 100644
          --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java
          +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java
          @@ -39,6 +39,7 @@ public void close() {
                   super.close();
                   if (handler != null) {
                       handler.removeCallbacksAndMessages(null);
          +            handler = null;
                   }
               }
           }
          
          From fce0505d8f07035cb8e3ff2ab14944146ab049b2 Mon Sep 17 00:00:00 2001
          From: "G. Blake Meike" 
          Date: Mon, 6 Feb 2017 13:34:40 -0800
          Subject: [PATCH 0478/2110] Add minVersion and targetVersion to metrics
           collected by the Realm Tranformer (#4143)
          
          Fixes #206
          ---
           .../io/realm/transformer/RealmTransformer.groovy   |  5 ++++-
           .../java/io/realm/transformer/RealmAnalytics.java  | 14 +++++++++++---
           2 files changed, 15 insertions(+), 4 deletions(-)
          
          diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy
          index e6e4813551..94ee5af4f4 100644
          --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy
          +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy
          @@ -174,11 +174,14 @@ class RealmTransformer extends Transform {
                       it.getPackageName()
                   }
           
          +        def targetSdk = project?.android?.defaultConfig?.targetSdkVersion?.mApiLevel as String;
          +        def minSdk = project?.android?.defaultConfig?.minSdkVersion?.mApiLevel as String;
          +
                   def env = System.getenv()
                   def disableAnalytics = env["REALM_DISABLE_ANALYTICS"]
                   if (disableAnalytics == null || disableAnalytics != "true") {
                       boolean sync = project?.realm?.syncEnabled != null && project.realm.syncEnabled
          -            def analytics = new RealmAnalytics(packages as Set, containsKotlin, sync)
          +            def analytics = new RealmAnalytics(packages as Set, containsKotlin, sync, targetSdk, minSdk)
                       analytics.execute()
                   }
               }
          diff --git a/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java b/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java
          index 18dd7b827b..9e0a240aea 100644
          --- a/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java
          +++ b/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java
          @@ -71,7 +71,9 @@ public class RealmAnalytics {
                       + "      \"Realm Version\": \"%REALM_VERSION%\",\n"
                       + "      \"Host OS Type\": \"%OS_TYPE%\",\n"
                       + "      \"Host OS Version\": \"%OS_VERSION%\",\n"
          -            + "      \"Target OS Type\": \"android\"\n"
          +            + "      \"Target OS Type\": \"android\",\n"
          +            + "      \"Target OS Version\": \"%TARGET_SDK%\",\n"
          +            + "      \"Target OS Minimum Version\": \"%MIN_SDK%\"\n"
                       + "   }\n"
                       + "}";
           
          @@ -80,11 +82,15 @@ public class RealmAnalytics {
           
               private boolean usesKotlin;
               private boolean usesSync;
          +    private String targetSdk;
          +    private String minSdk;
           
          -    public RealmAnalytics(Set packages, boolean usesKotlin, boolean usesSync) {
          +    public RealmAnalytics(Set packages, boolean usesKotlin, boolean usesSync, String targetSdk, String minSdk) {
                   this.packages = packages;
                   this.usesKotlin = usesKotlin;
                   this.usesSync = usesSync;
          +        this.targetSdk = targetSdk;
          +        this.minSdk = minSdk;
               }
           
               private void send() {
          @@ -133,7 +139,9 @@ public String generateJson() throws SocketException, NoSuchAlgorithmException {
                           .replaceAll("%SYNC_VERSION%", usesSync ? "\"" + Version.SYNC_VERSION + "\"": "null")
                           .replaceAll("%REALM_VERSION%", Version.VERSION)
                           .replaceAll("%OS_TYPE%", System.getProperty("os.name"))
          -                .replaceAll("%OS_VERSION%", System.getProperty("os.version"));
          +                .replaceAll("%OS_VERSION%", System.getProperty("os.version"))
          +                .replaceAll("%TARGET_SDK%", targetSdk)
          +                .replaceAll("%MIN_SDK%", minSdk);
               }
           
               /**
          
          From a2d591fd59da8230985be574b493537058581294 Mon Sep 17 00:00:00 2001
          From: "G. Blake Meike" 
          Date: Mon, 6 Feb 2017 14:29:59 -0800
          Subject: [PATCH 0479/2110] Add a unit tests for scenarios described in #4093
           (#4142)
          
          Augment @kneth's test with one that verifies int/Long conversion, and indexing
          ---
           .../assets/rename-and-add-indexed.realm       | Bin 0 -> 4096 bytes
           .../androidTest/assets/rename-and-add.realm   | Bin 0 -> 4096 bytes
           .../java/io/realm/RealmMigrationTests.java    |  71 ++++++++++++++++++
           .../migration/MigrationFieldRenameAndAdd.java |  41 ++++++++++
           .../MigrationIndexedFieldRenamed.java         |  29 +++++++
           5 files changed, 141 insertions(+)
           create mode 100644 realm/realm-library/src/androidTest/assets/rename-and-add-indexed.realm
           create mode 100644 realm/realm-library/src/androidTest/assets/rename-and-add.realm
           create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationFieldRenameAndAdd.java
           create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationIndexedFieldRenamed.java
          
          diff --git a/realm/realm-library/src/androidTest/assets/rename-and-add-indexed.realm b/realm/realm-library/src/androidTest/assets/rename-and-add-indexed.realm
          new file mode 100644
          index 0000000000000000000000000000000000000000..851be9725a888bbe239b3c31b68d52bb1bcfd079
          GIT binary patch
          literal 4096
          zcmeHGv2GJV5S^LZvmJs(P+TAxOIB#oAf+P>QXm9D5eXp*x^Qr(V8ux+`-DvA(j{fe
          zlrH%MBpT>argZ5trAywsJKGlv5v4`9&1z=eyqS5kTk$15F1_A8e(}6i5_yJmPsBTp
          z;vfv-AVb~udeS+%ZOS$L(@*W7*E`s69d?7bb$aspB#in|__7tX!?)2%a2$o#@XJPn
          zbHJ&*@8bRvb$j4R=9~HVFy~k%GKGUUNTHfB(MDX?bP8@o8S%hogBUjyK{69
          z2Orx}<}SnTbWb}`H~vbOBQI*$QXOwc-)J9B#@RsyhyD(WNA9VbU
          zo+`7q?Tf5SO{APXjw+>KGdrq^P5PpYTV=M?n%Zh^p>+WR@j%XrmARwe1oVSjf-0BBWf|Vor3WO*hz(N2#u~$5=KG@wk-tp2*4r8JjjQ
          zy@@yVe9ZGJTNvXr9N!UhdFV$y+jOamHs9-RrW5T9zsq@P6aPMkM_t!++wPV7ocssa
          z*jwM__ULV4Z$p>b0;40NqjzmzfH)X7hxZ09ckg@OclV(H*=bl=yxu=Mg(6^!&lZ3+REg(%iI>Y(
          z4M$Ob)s`jw$=$#kkGmH^FZPmP6rKla;9sfG8>&|5w|sv|{X4149$ZF=n;h>|JZ2w|
          zT-V#Y=M=u+8-h^k1=eXR$@v}~oZoX!fG#F+rYFwV^9IpZIFI_>#Je4+yjm}R*pA~G=>Q0}R$;nk2f7QpQRJBS{T$o=cfuz{+u160zhN@KdOVuKr+&&jUX
          zv7r7`&+a9kFeI(#h{CN6u(k^wLKiH@axI(ZnQ5@8+WZ&-hJYbp2p9r}fFWQA7y^cX
          JA@F|*`~bf5gs=br
          
          literal 0
          HcmV?d00001
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java
          index cb26f70fb5..f3690fc04b 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java
          @@ -49,9 +49,11 @@
           import io.realm.entities.StringOnly;
           import io.realm.entities.Thread;
           import io.realm.entities.migration.MigrationClassRenamed;
          +import io.realm.entities.migration.MigrationFieldRenameAndAdd;
           import io.realm.entities.migration.MigrationFieldRenamed;
           import io.realm.entities.migration.MigrationFieldTypeToInt;
           import io.realm.entities.migration.MigrationFieldTypeToInteger;
          +import io.realm.entities.migration.MigrationIndexedFieldRenamed;
           import io.realm.entities.migration.MigrationPosteriorIndexOnly;
           import io.realm.entities.migration.MigrationPriorIndexOnly;
           import io.realm.exceptions.RealmMigrationNeededException;
          @@ -1209,6 +1211,75 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   Realm.migrateRealm(config, migration);
               }
           
          +    @Test
          +    public void renameAndAddField() {
          +        final Class schemaClass = MigrationFieldRenameAndAdd.class;
          +
          +        RealmMigration migration = new RealmMigration() {
          +            @Override
          +            public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          +                realm.getSchema().get(schemaClass.getSimpleName())
          +                        .renameField("string1", "string2")
          +                        .addField("string1", String.class);
          +            }
          +        };
          +
          +        RealmConfiguration config = configFactory.createConfigurationBuilder()
          +                .schema(schemaClass)
          +                .schemaVersion(2)
          +                .migration(migration)
          +                .assetFile("rename-and-add.realm")
          +                .build();
          +        Realm realm = Realm.getInstance(config);
          +
          +        RealmObjectSchema schema = realm.getSchema().get(schemaClass.getSimpleName());
          +        assertTrue(schema.hasField("string1"));
          +        assertTrue(schema.hasField("string2"));
          +        realm.close();
          +    }
          +
          +    @Test
          +    public void renameAndAddIndexedField() {
          +        final Class schemaClass = MigrationIndexedFieldRenamed.class;
          +        final int oldTestVal = 7;
          +        final Long testVal = Long.valueOf(293);
          +
          +        RealmMigration migration = new RealmMigration() {
          +            @Override
          +            public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          +                realm.getSchema().get(schemaClass.getSimpleName())
          +                        .renameField("testField", "oldTestField")
          +                        .addField("testField", Long.class);
          +            }
          +        };
          +
          +        RealmConfiguration config = configFactory.createConfigurationBuilder()
          +                .schema(schemaClass)
          +                .schemaVersion(2)
          +                .migration(migration)
          +                .assetFile("rename-and-add-indexed.realm")
          +                .build();
          +        realm = Realm.getInstance(config);
          +
          +        realm.beginTransaction();
          +        MigrationIndexedFieldRenamed obj = realm.createObject(schemaClass, 2);
          +        obj.oldTestField = oldTestVal;
          +        obj.testField = testVal;
          +        realm.commitTransaction();
          +
          +        RealmObjectSchema schema = realm.getSchema().get(schemaClass.getSimpleName());
          +        assertTrue(schema.hasField("testField"));
          +        assertTrue(schema.hasField("oldTestField"));
          +        assertTrue(schema.hasIndex("oldTestField"));
          +
          +        RealmResults result = realm.where(schemaClass).equalTo("id", 2).findAll();
          +        assertEquals("There should be an object with PK=2", 1, result.size());
          +        assertEquals("Unexpected oldTestField value", oldTestVal, result.first().oldTestField);
          +        assertEquals("Unexpected testField value", testVal, result.first().testField);
          +
          +        realm.close();
          +    }
          +
               // TODO Add unit tests for default nullability
               // TODO Add unit tests for default Indexing for Primary keys
           }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationFieldRenameAndAdd.java b/realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationFieldRenameAndAdd.java
          new file mode 100644
          index 0000000000..bfe41a424b
          --- /dev/null
          +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationFieldRenameAndAdd.java
          @@ -0,0 +1,41 @@
          +/*
          + * Copyright 2017 Realm Inc.
          + *
          + * 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.
          + */
          +
          +package io.realm.entities.migration;
          +
          +import io.realm.RealmObject;
          +
          +
          +public class MigrationFieldRenameAndAdd extends RealmObject {
          +    private String string1; // to be renamed
          +    private String string2;
          +
          +    public String getString1() {
          +        return string1;
          +    }
          +
          +    public void setString1(String string1) {
          +        this.string1 = string1;
          +    }
          +
          +    public String getString2() {
          +        return string2;
          +    }
          +
          +    public void setString2(String string2) {
          +        this.string2 = string2;
          +    }
          +}
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationIndexedFieldRenamed.java b/realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationIndexedFieldRenamed.java
          new file mode 100644
          index 0000000000..7b48bacb02
          --- /dev/null
          +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationIndexedFieldRenamed.java
          @@ -0,0 +1,29 @@
          +/*
          + * Copyright 2017 Realm Inc.
          + *
          + * 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.
          + */
          +
          +package io.realm.entities.migration;
          +
          +import io.realm.RealmObject;
          +import io.realm.annotations.Index;
          +import io.realm.annotations.PrimaryKey;
          +
          +public class MigrationIndexedFieldRenamed extends RealmObject {
          +    @PrimaryKey
          +    public long id;
          +    @Index
          +    public int oldTestField;
          +    public Long testField;
          +}
          
          From 2472dc2901fa0b29590829fc9dc1daa65a1cc4c5 Mon Sep 17 00:00:00 2001
          From: Christian Melchior 
          Date: Tue, 7 Feb 2017 06:07:28 +0100
          Subject: [PATCH 0480/2110] Allow more leniency when defining the server url
           (#4146)
          
          ---
           CHANGELOG.md                                  |  4 ++
           .../java/io/realm/SyncConfigurationTests.java | 49 ++++++++++++++++---
           .../java/io/realm/SyncConfiguration.java      | 45 +++++++++++++++--
           .../objectServer/java/io/realm/SyncUser.java  |  9 ++++
           4 files changed, 94 insertions(+), 13 deletions(-)
          
          diff --git a/CHANGELOG.md b/CHANGELOG.md
          index 1d8b6d9159..38da001d39 100644
          --- a/CHANGELOG.md
          +++ b/CHANGELOG.md
          @@ -1,5 +1,9 @@
           ## 2.3.1
           
          +### Enhancements
          +
          +* [ObjectServer] The `serverUrl` given to `SyncConfiguration.Builder()` is now more lenient and will also accept only paths as argument (#4144).
          +
           ### Bug fixes
           
           * NPE problem in SharedRealm.finalize() (#3730).
          diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java
          index 58203b7461..35038d786a 100644
          --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java
          +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java
          @@ -72,13 +72,6 @@ public void setUp() {
               public void tearDown() throws Exception {
               }
           
          -    @Test
          -    public void user() {
          -//        new SyncConfiguration.Builder(context);
          -        // Check that user can be added
          -        // That the default local path is correct
          -    }
          -
               @Test
               public void user_invalidUserThrows() {
                   try {
          @@ -115,6 +108,47 @@ public void serverUrl_setsFolderAndFileName() {
                   }
               }
           
          +    @Test
          +    public void serverUrl_flexibleInput() {
          +        // Check that the serverUrl accept a wide range of input
          +        Object[][] fuzzyInput = {
          +                // Only path -> Use auth server as basis for server url, but ignore port if set
          +                { createTestUser("http://ros.realm.io/auth"),      "/~/default", "realm://ros.realm.io/~/default" },
          +                { createTestUser("http://ros.realm.io:7777/auth"), "/~/default", "realm://ros.realm.io/~/default" },
          +                { createTestUser("https://ros.realm.io/auth"),     "/~/default", "realms://ros.realm.io/~/default" },
          +                { createTestUser("https://127.0.0.1/auth"),        "/~/default", "realms://127.0.0.1/~/default" },
          +
          +                { createTestUser("http://ros.realm.io/auth"),      "~/default",  "realm://ros.realm.io/~/default" },
          +                { createTestUser("http://ros.realm.io:7777/auth"), "~/default",  "realm://ros.realm.io/~/default" },
          +                { createTestUser("https://ros.realm.io/auth"),     "~/default",  "realms://ros.realm.io/~/default" },
          +                { createTestUser("https://127.0.0.1/auth"),        "~/default",  "realms://127.0.0.1/~/default" },
          +
          +                // Check that the same name used for server and name doesn't crash
          +                { createTestUser("http://ros.realm.io/auth"),      "~/ros.realm.io",  "realm://ros.realm.io/~/ros.realm.io" },
          +
          +                // Forgot schema -> Use the one from the auth url
          +                { createTestUser("http://ros.realm.io/auth"), "ros.realm.io/~/default", "realm://ros.realm.io/~/default" },
          +                { createTestUser("http://ros.realm.io/auth"), "//ros.realm.io/~/default", "realm://ros.realm.io/~/default" },
          +                { createTestUser("https://ros.realm.io/auth"), "ros.realm.io/~/default", "realms://ros.realm.io/~/default" },
          +                { createTestUser("https://ros.realm.io/auth"), "//ros.realm.io/~/default", "realms://ros.realm.io/~/default" },
          +
          +                // Automatically replace http|https with realm|realms
          +                { createTestUser(), "http://ros.realm.io/~/default", "realm://ros.realm.io/~/default" },
          +                { createTestUser(), "https://ros.realm.io/~/default", "realms://ros.realm.io/~/default" }
          +        };
          +
          +        for (Object[] test : fuzzyInput) {
          +            SyncUser user = (SyncUser) test[0];
          +            String serverUrlInput = (String) test[1];
          +            String resolvedServerUrl = ((String) test[2]).replace("~", user.getIdentity());
          +
          +            SyncConfiguration config = new SyncConfiguration.Builder(user, serverUrlInput).build();
          +
          +            assertEquals(String.format("Input '%s' did not resolve correctly.", serverUrlInput),
          +                    resolvedServerUrl, config.getServerUrl().toString());
          +        }
          +    }
          +
               @Test
               public void serverUrl_invalidUrlThrows() {
                   String[] invalidUrls = {
          @@ -130,7 +164,6 @@ public void serverUrl_invalidUrlThrows() {
                       "realm://objectserver.realm.io/~/Αθήνα", // Non-ascii
                       "realm://objectserver.realm.io/~/foo/../bar", // .. is not allowed
                       "realm://objectserver.realm.io/~/foo/./bar", // . is not allowed
          -            "http://objectserver.realm.io/~/default", // wrong scheme
                   };
           
                   for (String invalidUrl : invalidUrls) {
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java
          index 9ee7b0f868..baa076c0f8 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java
          @@ -277,7 +277,8 @@ public static final class Builder  {
                    *
                    * @param user the user for this Realm. An authenticated {@link SyncUser} is required to open any Realm managed
                    *             by a Realm Object Server.
          -         * @param uri URI identifying the Realm.
          +         * @param uri URI identifying the Realm. If only a path like {@code /~/default} is given, the configuration will
          +         *            assume the file is located on the same server returned by {@link SyncUser#getAuthenticationUrl()}.
                    *
                    * @see SyncUser#isValid()
                    */
          @@ -319,10 +320,44 @@ private void validateAndSet(String uri) {
                           throw new IllegalArgumentException("Invalid URI: " + uri, e);
                       }
           
          -            // scheme must be realm or realms
          -            String scheme = serverUrl.getScheme();
          -            if (!scheme.equals("realm") && !scheme.equals("realms")) {
          -                throw new IllegalArgumentException("Invalid scheme: " + scheme);
          +            try {
          +                // Automatically set scheme based on auth server if not set or wrongly set
          +                String serverScheme = serverUrl.getScheme();
          +                if (serverScheme == null) {
          +                    String authProtocol = user.getAuthenticationUrl().getProtocol();
          +                    if (authProtocol.equalsIgnoreCase("https")) {
          +                        serverScheme = "realms";
          +                    } else {
          +                        serverScheme = "realm";
          +                    }
          +                } else if (serverScheme.equalsIgnoreCase("http")) {
          +                    serverScheme = "realm";
          +                } else if (serverScheme.equalsIgnoreCase("https")) {
          +                    serverScheme = "realms";
          +                }
          +
          +                // Automatically set host if one wasn't defined
          +                String host = serverUrl.getHost();
          +                if (host == null) {
          +                    host = user.getAuthenticationUrl().getHost();
          +                }
          +
          +                // Convert relative paths to absolute if required
          +                String path = serverUrl.getPath();
          +                if (path != null && !path.startsWith("/")) {
          +                    path = "/" + path;
          +                }
          +
          +                serverUrl = new URI(serverScheme,
          +                        serverUrl.getUserInfo(),
          +                        host,
          +                        serverUrl.getPort(),
          +                        (path != null) ? path.replace(host + "/", "") : null, // Remove host if it accidentially was interpreted as a path segment
          +                        serverUrl.getQuery(),
          +                        serverUrl.getRawFragment());
          +
          +            } catch (URISyntaxException e) {
          +                throw new IllegalArgumentException("Invalid URI: " + uri, e);
                       }
           
                       // Detect last path segment as it is the default file name
          diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          index d191e0d278..b4e93bcefa 100644
          --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java
          @@ -407,6 +407,15 @@ public Realm getManagementRealm() {
                   return Realm.getInstance(managementConfig.initAndGetManagementRealmConfig(syncUser, this));
               }
           
          +    /**
          +     * Returns the {@link URL} where this user was authenticated.
          +     *
          +     * @return {@link URL} where the user was authenticated.
          +     */
          +    public URL getAuthenticationUrl() {
          +        return syncUser.getAuthenticationUrl();
          +    }
          +
               // Creates the URL to the permission Realm based on the authentication URL.
               private static String getManagementRealmUrl(URL authUrl) {
                   String scheme = "realm";
          
          From 34f2bfb8dedaa339ac2aabe51797a407a936cb8c Mon Sep 17 00:00:00 2001
          From: LYK 
          Date: Tue, 7 Feb 2017 14:29:01 +0900
          Subject: [PATCH 0481/2110] Modify Java docs and comments. (#4124)
          
          * Remove unnecessary an addtional space.
          
          * Modify Java docs and comments.
          
          * PR feedback.
          
          * PR feedback.
          ---
           .../java/io/realm/BulkInsertTests.java        |  26 +-
           .../java/io/realm/CollectionTests.java        |  24 +-
           .../java/io/realm/ColumnIndicesTests.java     |   4 +-
           .../java/io/realm/ColumnInfoTests.java        |   8 +-
           .../io/realm/DynamicRealmObjectTests.java     |  40 +-
           .../java/io/realm/DynamicRealmTests.java      |  16 +-
           .../java/io/realm/IOSRealmTests.java          |   4 +-
           .../ManagedOrderedRealmCollectionTests.java   |  18 +-
           .../io/realm/ManagedRealmCollectionTests.java |  42 +-
           .../java/io/realm/NotificationsTest.java      | 139 ++++---
           .../OrderedRealmCollectionIteratorTests.java  |  60 +--
           .../java/io/realm/RealmAnnotationTests.java   |  18 +-
           .../java/io/realm/RealmAsyncQueryTests.java   | 367 +++++++++---------
           .../java/io/realm/RealmCacheTests.java        |  36 +-
           .../io/realm/RealmConfigurationTests.java     |  62 +--
           .../java/io/realm/RealmInMemoryTest.java      |  38 +-
           .../java/io/realm/RealmInterprocessTest.java  |  28 +-
           .../realm/RealmJsonAbsentPrimaryKeyTests.java |  18 +-
           .../realm/RealmJsonNullPrimaryKeyTests.java   |   8 +-
           .../java/io/realm/RealmJsonTests.java         | 100 ++---
           .../java/io/realm/RealmLinkTests.java         |   2 +-
           .../java/io/realm/RealmListTests.java         |  24 +-
           .../java/io/realm/RealmMigrationTests.java    | 106 ++---
           .../java/io/realm/RealmModelTests.java        |  32 +-
           .../io/realm/RealmNullPrimaryKeyTests.java    |  14 +-
           .../java/io/realm/RealmObjectSchemaTests.java |  16 +-
           .../java/io/realm/RealmObjectTests.java       |  90 ++---
           .../java/io/realm/RealmPrimaryKeyTests.java   |   6 +-
           .../io/realm/RealmProxyMediatorTests.java     |   2 +-
           .../java/io/realm/RealmQueryTests.java        | 164 ++++----
           .../java/io/realm/RealmResultsTests.java      |  62 +--
           .../java/io/realm/RealmSchemaTests.java       |  10 +-
           .../androidTest/java/io/realm/RealmTests.java | 250 ++++++------
           .../java/io/realm/RxJavaTests.java            |  28 +-
           .../androidTest/java/io/realm/SortTest.java   |  24 +-
           .../androidTest/java/io/realm/TestHelper.java |  26 +-
           .../io/realm/TypeBasedNotificationsTests.java | 162 ++++----
           .../UnManagedOrderedRealmCollectionTests.java |   2 +-
           .../realm/UnManagedRealmCollectionTests.java  |   6 +-
           .../instrumentation/MockActivityManager.java  |   4 +-
           .../java/io/realm/internal/JNIQueryTest.java  |  38 +-
           .../io/realm/internal/JNISortedLongTest.java  |  10 +-
           .../io/realm/internal/JNITableInsertTest.java |   6 +-
           .../java/io/realm/internal/JNITableTest.java  |  74 ++--
           .../io/realm/internal/JNITableViewTest.java   |   2 +-
           .../java/io/realm/internal/JNIViewTest.java   |  42 +-
           .../io/realm/internal/PrimaryKeyTests.java    |  14 +-
           .../io/realm/internal/SharedRealmTests.java   |  12 +-
           .../internal/TableIndexAndDistinctTest.java   |  16 +-
           .../realm/internal/android/JsonUtilsTest.java |  18 +-
           .../java/io/realm/rule/RunInLooperThread.java |   4 +-
           .../rule/TestRealmConfigurationFactory.java   |   6 +-
           .../realm/services/RemoteProcessService.java  |   6 +-
           .../java/io/realm/util/ExceptionHolder.java   |   2 +-
           .../benchmarks/config/BenchmarkConfig.java    |   6 +-
           .../benchmarks/config/CSVResultProcessor.java |   2 +-
           .../main/java/io/realm/AndroidNotifier.java   |   2 +-
           .../src/main/java/io/realm/BaseRealm.java     |  12 +-
           .../java/io/realm/DynamicRealmObject.java     |   4 +-
           .../main/java/io/realm/HandlerController.java | 139 ++++---
           .../src/main/java/io/realm/ProxyState.java    |  20 +-
           .../src/main/java/io/realm/Realm.java         |  77 ++--
           .../src/main/java/io/realm/RealmCache.java    |  28 +-
           .../java/io/realm/RealmConfiguration.java     |  20 +-
           .../main/java/io/realm/RealmFieldType.java    |   7 +-
           .../src/main/java/io/realm/RealmList.java     |   5 +-
           .../src/main/java/io/realm/RealmObject.java   |  12 +-
           .../main/java/io/realm/RealmObjectSchema.java |  42 +-
           .../src/main/java/io/realm/RealmQuery.java    | 108 +++---
           .../src/main/java/io/realm/RealmResults.java  |   4 +-
           .../src/main/java/io/realm/RealmSchema.java   |  21 +-
           .../src/main/java/io/realm/Sort.java          |   1 +
           .../realm/exceptions/RealmFileException.java  |   2 +-
           .../java/io/realm/internal/CheckedRow.java    |   2 +-
           .../java/io/realm/internal/ColumnInfo.java    |   2 +-
           .../io/realm/internal/FinalizerRunnable.java  |   2 +-
           .../java/io/realm/internal/IdentitySet.java   |   2 +-
           .../main/java/io/realm/internal/LinkView.java |   2 +-
           .../io/realm/internal/ObjectServerFacade.java |   4 +-
           .../java/io/realm/internal/RealmCore.java     |   6 +-
           .../io/realm/internal/RealmProxyMediator.java |  35 +-
           .../src/main/java/io/realm/internal/Row.java  |   2 +-
           .../java/io/realm/internal/SharedRealm.java   |   2 +-
           .../main/java/io/realm/internal/Table.java    |  50 +--
           .../java/io/realm/internal/TableOrView.java   |   5 +-
           .../java/io/realm/internal/TableQuery.java    |  28 +-
           .../java/io/realm/internal/TableView.java     |   8 +-
           .../java/io/realm/internal/UncheckedRow.java  |   4 +-
           .../src/main/java/io/realm/internal/Util.java |  12 +-
           .../realm/internal/android/ISO8601Utils.java  |  31 +-
           .../io/realm/internal/android/JsonUtils.java  |   8 +-
           .../async/RealmThreadPoolExecutor.java        |  10 +-
           .../internal/modules/FilterableMediator.java  |   2 +-
           .../main/java/io/realm/log/AndroidLogger.java |   8 +-
           .../src/main/java/io/realm/log/RealmLog.java  |   2 +-
           .../io/realm/rx/RealmObservableFactory.java   |  22 +-
           96 files changed, 1555 insertions(+), 1542 deletions(-)
          
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java
          index ee6946c1f9..09b297ae05 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java
          @@ -131,7 +131,7 @@ public void insert() {
                   assertNull(realmTypes.getFieldList().get(0).getFieldIgnored());
           
           
          -        // make sure Dog was not inserted twice in the recursive process
          +        // Makes sure Dog was not inserted twice in the recursive process.
                   assertEquals(2, realm.where(AllJavaTypes.class).findAll().size());
               }
           
          @@ -310,7 +310,7 @@ public void insertOrUpdate_cyclicDependenciesFromOtherRealm() {
                   dog.setOwner(owner);
                   realm1.commitTransaction();
           
          -        //Copy object with relations from realm1 to realm2
          +        // Copies object with relations from realm1 to realm2.
                   realm2.beginTransaction();
                   realm2.insertOrUpdate(owner);
                   realm2.commitTransaction();
          @@ -422,7 +422,7 @@ public void execute(Realm realm) {
                   assertEquals(1, realm.where(AllTypesPrimaryKey.class).count());
                   AllTypesPrimaryKey obj = realm.where(AllTypesPrimaryKey.class).findFirst();
           
          -        // Check that the only element has all its properties updated
          +        // Checks that the only element has all its properties updated.
                   assertNotNull(obj);
                   assertEquals("Bar", obj.getColumnString());
                   assertEquals(1, obj.getColumnLong());
          @@ -471,7 +471,7 @@ public void execute(Realm realm) {
               }
           
               /**
          -     * added to reproduce https://github.com/realm/realm-java/issues/3103
          +     * Added to reproduce https://github.com/realm/realm-java/issues/3103
                */
               @Test
               public void insert_emptyListWithCompositeMediator() {
          @@ -499,7 +499,7 @@ public void execute(Realm realm) {
               }
           
               /**
          -     * added to reproduce https://github.com/realm/realm-java/issues/3103
          +     * Added to reproduce https://github.com/realm/realm-java/issues/3103
                */
               @Test
               public void insert_emptyListWithFilterableMediator() {
          @@ -567,7 +567,7 @@ public void execute(Realm realm) {
               }
           
               /**
          -     * added to reproduce https://github.com/realm/realm-java/issues/3103
          +     * Added to reproduce https://github.com/realm/realm-java/issues/3103
                */
               @Test
               public void insertOrUpdate_emptyListWithCompositeMediator() {
          @@ -595,7 +595,7 @@ public void execute(Realm realm) {
               }
           
               /**
          -     * added to reproduce https://github.com/realm/realm-java/issues/3103
          +     * Added to reproduce https://github.com/realm/realm-java/issues/3103
                */
               @Test
               public void insertOrUpdate_emptyListWithFilterableMediator() {
          @@ -657,7 +657,7 @@ public void insertOrUpdate_mixingPrimaryKeyAndNoPrimaryKeyModels() {
                   assertEquals(42, all.get(0).getColumnInt());
                   assertNotNull(all.get(0).getColumnRealmObjectNoPK());
                   assertEquals("updated B", all.get(0).getColumnRealmObjectNoPK().getColumnString());
          -        // since AllTypes doesn't have a PK we now have two instances
          +        // Since AllTypes doesn't have a PK we now have two instances.
                   assertEquals(2, realm.where(AllTypes.class).findAll().size());
               }
           
          @@ -686,8 +686,8 @@ public void insertOrUpdate_mixingNoPrimaryKeyAndPrimaryKeyModels() {
                   assertEquals(1, realm.where(AllTypesPrimaryKey.class).findAll().size());
           
                   objA_no_pk.setColumnString("different A");
          -        objA_no_pk.setColumnInt(42);//should insert a new instance
          -        // update (since it has a PK) now both AllTypesPrimaryKey points to the same objB_pk instance
          +        objA_no_pk.setColumnInt(42); // Should insert a new instance
          +        // Updates (since it has a PK) now both AllTypesPrimaryKey points to the same objB_pk instance.
                   objB_pk.setColumnString("updated B");
           
                   realm.beginTransaction();
          @@ -749,7 +749,7 @@ public void insertOrUpdate_mixingPrimaryAndNoPrimaryKeyList() {
                   assertEquals(1, realm.where(AllTypesPrimaryKey.class).findAll().size());
               }
           
          -    //any omitted argument should not end in a SIGSEGV but an exception
          +    // Any omitted argument should not end in a SIGSEGV but an exception.
           
               @Test
               public void insert_nullObject() {
          @@ -803,7 +803,7 @@ public void insert_listWithNullElement() {
                   }
               }
           
          -    //Inserting a managed object will result in it being copied or updated again
          +    // Inserting a managed object will result in it being copied or updated again.
               @Test
               public void insertOrUpdate_managedObject() {
                   AllJavaTypes obj = new AllJavaTypes();
          @@ -895,7 +895,7 @@ public void insertOrUpdate_collectionOfManagedObjects() {
                   assertEquals(1, allTypes.getColumnRealmList().size());
               }
           
          -    // To reproduce https://github.com/realm/realm-java/issues/3105
          +    // To reproduce https://github.com/realm/realm-java/issues/3105.
               @Test
               public void insertOrUpdate_shouldNotClearRealmList() {
                   realm.beginTransaction();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java
          index 1ef8aad27b..2c0051d66d 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java
          @@ -32,50 +32,50 @@ public abstract class CollectionTests {
           
               protected final static long YEAR_MILLIS = TimeUnit.DAYS.toMillis(365);
           
          -    // Enumerate all known collection classes from the Realm API.
          +    // Enumerates all known collection classes from the Realm API.
               protected enum CollectionClass {
                   MANAGED_REALMLIST, UNMANAGED_REALMLIST, REALMRESULTS
               }
           
          -    // Enumerate all current supported collections that can be in unmanaged mode.
          +    // Enumerates all current supported collections that can be in unmanaged mode.
               protected enum UnManagedCollection {
                   UNMANAGED_REALMLIST
               }
           
          -    // Enumerate all current supported collections that can be managed by Realm.
          +    // Enumerates all current supported collections that can be managed by Realm.
               protected enum ManagedCollection {
                   MANAGED_REALMLIST, REALMRESULTS
               }
           
          -    // Enumerate all methods from the RealmCollection interface that depend on Realm API's.
          +    // Enumerates all methods from the RealmCollection interface that depend on Realm API's.
               protected enum RealmCollectionMethod {
                   WHERE, MIN, MAX, SUM, AVERAGE, MIN_DATE, MAX_DATE, DELETE_ALL_FROM_REALM, IS_VALID, IS_MANAGED
               }
           
          -    // Enumerate all methods from the Collection interface
          +    // Enumerates all methods from the Collection interface
               protected enum CollectionMethod {
                   ADD_OBJECT, ADD_ALL_OBJECTS, CLEAR, CONTAINS, CONTAINS_ALL, EQUALS, HASHCODE, IS_EMPTY, ITERATOR, REMOVE_OBJECT,
                   REMOVE_ALL, RETAIN_ALL, SIZE, TO_ARRAY, TO_ARRAY_INPUT
               }
           
          -    // Enumerate all methods on the List interface and OrderedRealmCollection interface that doesn't depend on Realm
          +    // Enumerates all methods on the List interface and OrderedRealmCollection interface that doesn't depend on Realm
               // API's.
               protected enum ListMethod {
                   FIRST, LAST, ADD_INDEX, ADD_ALL_INDEX, GET_INDEX, INDEX_OF, LAST_INDEX_OF, LIST_ITERATOR, LIST_ITERATOR_INDEX, REMOVE_INDEX,
                   SET, SUBLIST
               }
           
          -    // Enumerate all methods from the OrderedRealmCollection interface that depend on Realm API's.
          +    // Enumerates all methods from the OrderedRealmCollection interface that depend on Realm API's.
               protected enum OrderedRealmCollectionMethod {
                   DELETE_INDEX, DELETE_FIRST, DELETE_LAST, SORT, SORT_FIELD, SORT_2FIELDS, SORT_MULTI
               }
           
          -    // Enumerate all methods that can mutate a RealmCollection
          +    // Enumerates all methods that can mutate a RealmCollection.
               protected enum CollectionMutatorMethod {
                   DELETE_ALL, ADD_OBJECT, ADD_ALL_OBJECTS, CLEAR, REMOVE_OBJECT, REMOVE_ALL, RETAIN_ALL
               }
           
          -    // Enumerate all methods that can mutate a RealmOrderedCollection
          +    // Enumerates all methods that can mutate a RealmOrderedCollection.
               protected enum OrderedCollectionMutatorMethod {
                   DELETE_INDEX, DELETE_FIRST, DELETE_LAST, ADD_INDEX, ADD_ALL_INDEX, SET, REMOVE_INDEX
               }
          @@ -93,7 +93,7 @@ protected void populateRealm(Realm realm, int objects) {
                           nonLatinFieldNames.setΔέλτα(i);
                       }
           
          -            // Add all items to the RealmList on the first object
          +            // Adds all items to the RealmList on the first object.
                       AllJavaTypes firstObj = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_ID, 0).findFirst();
                       RealmResults listData = realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_ID, Sort.ASCENDING);
                       RealmList list = firstObj.getFieldList();
          @@ -155,7 +155,7 @@ protected OrderedRealmCollection populateCollectionOnDeletedLinkView
                   return result;
               }
           
          -    // Create a number of objects that mix null and real values for number type fields.
          +    // Creates a number of objects that mix null and real values for number type fields.
               protected void populatePartialNullRowsForNumericTesting(Realm realm) {
                   NullTypes nullTypes1 = new NullTypes();
                   nullTypes1.setId(1);
          @@ -185,7 +185,7 @@ protected void populatePartialNullRowsForNumericTesting(Realm realm) {
                   realm.commitTransaction();
               }
           
          -    // Create a list of AllJavaTypes with its `fieldString` field set to a given value.
          +    // Creates a list of AllJavaTypes with its `fieldString` field set to a given value.
               protected OrderedRealmCollection createStringCollection(Realm realm, ManagedCollection collectionClass, String... args) {
                   realm.beginTransaction();
                   realm.deleteAll();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java
          index 0205aa80ce..d8cfe9972b 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java
          @@ -89,7 +89,7 @@ public void copyDeeply() {
                   assertEquals(columnIndices.getColumnIndex(Dog.class, Dog.FIELD_AGE),
                           deepCopy.getColumnIndex(Dog.class, Dog.FIELD_AGE));
           
          -        // check if those are different instance.
          +        // Checks if those are different instance.
                   assertNotSame(columnIndices, deepCopy);
                   assertNotSame(columnIndices.getColumnInfo(Cat.class), deepCopy.getColumnInfo(Cat.class));
                   assertNotSame(columnIndices.getColumnInfo(Dog.class), deepCopy.getColumnInfo(Dog.class));
          @@ -108,7 +108,7 @@ public void copyFrom() {
           
                   catColumnInfoInSource.nameIndex++;
           
          -        // check preconditions
          +        // Checks preconditions.
                   assertNotEquals(catColumnInfoInSource.nameIndex, catColumnInfoInTarget.nameIndex);
                   assertNotSame(catColumnInfoInSource.getIndicesMap(), catColumnInfoInTarget.getIndicesMap());
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java
          index ae20b233a3..618c54d588 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java
          @@ -61,7 +61,7 @@ public void copyColumnInfoFrom_checkIndex() {
                   sourceColumnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false);
                   targetColumnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false);
           
          -        // check precondition
          +        // Checks precondition.
                   assertNotSame(sourceColumnInfo, targetColumnInfo);
                   assertNotSame(sourceColumnInfo.getIndicesMap(), targetColumnInfo.getIndicesMap());
           
          @@ -94,7 +94,7 @@ public void copyColumnInfoFrom_checkIndex() {
                   assertEquals(7, targetColumnInfo.ownerIndex);
                   assertEquals(8, targetColumnInfo.scaredOfDogIndex);
           
          -        // current implementation shares the indices map.
          +        // Current implementation shares the indices map.
                   assertSame(sourceColumnInfo.getIndicesMap(), targetColumnInfo.getIndicesMap());
               }
           
          @@ -115,7 +115,7 @@ public void clone_hasSameValue() {
           
                   CatRealmProxy.CatColumnInfo copy = columnInfo.clone();
           
          -        // modify original object
          +        // Modifies original object.
                   columnInfo.nameIndex = 0;
                   columnInfo.ageIndex = 0;
                   columnInfo.heightIndex = 0;
          @@ -136,7 +136,7 @@ public void clone_hasSameValue() {
                   assertEquals(7, copy.ownerIndex);
                   assertEquals(8, copy.scaredOfDogIndex);
           
          -        // current implementation shares the indices map between copies.
          +        // Current implementation shares the indices map between copies.
                   assertSame(columnInfo.getIndicesMap(), copy.getIndicesMap());
               }
           }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java
          index 9386beb7cb..b6552f7105 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java
          @@ -136,15 +136,15 @@ public void constructor_unmanagedObjectThrows() {
                   new DynamicRealmObject(new AllTypes());
               }
           
          -    // Test that all getters fail if given invalid field name
          +    // Tests that all getters fail if given invalid field name.
               @Test
               public void typedGetter_illegalFieldNameThrows() {
          -        // Set arguments
          +        // Sets arguments.
                   String linkedField = AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_STRING;
                   List arguments = Arrays.asList(null, "foo", AllJavaTypes.FIELD_STRING, linkedField);
                   List stringArguments = Arrays.asList(null, "foo", AllJavaTypes.FIELD_BOOLEAN, linkedField);
           
          -        // Test all getters
          +        // Tests all getters.
                   for (SupportedType type : SupportedType.values()) {
           
                       // We cannot modularize everything, so STRING is a special case with its own set
          @@ -167,7 +167,7 @@ public void typedGetter_illegalFieldNameThrows() {
               public void typedGetter_wrongUnderlyingTypeThrows() {
                   for (SupportedType type : SupportedType.values()) {
                       try {
          -                // Make sure we hit the wrong underlying type for all types.
          +                // Makes sure we hit the wrong underlying type for all types.
                           if (type == SupportedType.DOUBLE) {
                               callGetter(dObjTyped, type, Arrays.asList(AllJavaTypes.FIELD_STRING));
                           } else {
          @@ -177,7 +177,7 @@ public void typedGetter_wrongUnderlyingTypeThrows() {
                       } catch (IllegalArgumentException ignored) {
                       }
                       try {
          -                // Make sure we hit the wrong underlying type for all types.
          +                // Makes sure we hit the wrong underlying type for all types.
                           if (type == SupportedType.DOUBLE) {
                               callGetter(dObjDynamic, type, Arrays.asList(AllJavaTypes.FIELD_STRING));
                           } else {
          @@ -189,7 +189,7 @@ public void typedGetter_wrongUnderlyingTypeThrows() {
                   }
               }
           
          -    // Helper method for calling getters with different field names
          +    // Helper method for calling getters with different field names.
               private static void callGetter(DynamicRealmObject target, SupportedType type, List fieldNames) {
                   for (String fieldName : fieldNames) {
                       switch (type) {
          @@ -211,16 +211,16 @@ private static void callGetter(DynamicRealmObject target, SupportedType type, Li
                   }
               }
           
          -    // Test that all getters fail if given an invalid field name
          +    // Tests that all getters fail if given an invalid field name.
               @Test
               public void typedSetter_illegalFieldNameThrows() {
           
          -        // Set arguments
          +        // Sets arguments.
                   String linkedField = AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_STRING;
                   List arguments = Arrays.asList(null, "foo", AllJavaTypes.FIELD_STRING, linkedField);
                   List stringArguments = Arrays.asList(null, "foo", AllJavaTypes.FIELD_BOOLEAN, linkedField);
           
          -        // Test all getters
          +        // Tests all getters.
                   for (SupportedType type : SupportedType.values()) {
                       List args = (type == SupportedType.STRING) ? stringArguments : arguments;
                       try {
          @@ -241,7 +241,7 @@ public void typedSetter_wrongUnderlyingTypeThrows() {
                   for (SupportedType type : SupportedType.values()) {
                       realm.beginTransaction();
                       try {
          -                // Make sure we hit the wrong underlying type for all types.
          +                // Makes sure we hit the wrong underlying type for all types.
                           if (type == SupportedType.STRING) {
                               callSetter(dObjTyped, type, Arrays.asList(AllJavaTypes.FIELD_BOOLEAN));
                           } else {
          @@ -254,7 +254,7 @@ public void typedSetter_wrongUnderlyingTypeThrows() {
                       }
                       dynamicRealm.beginTransaction();
                       try {
          -                // Make sure we hit the wrong underlying type for all types.
          +                // Makes sure we hit the wrong underlying type for all types.
                           if (type == SupportedType.STRING) {
                               callSetter(dObjDynamic, type, Arrays.asList(AllJavaTypes.FIELD_BOOLEAN));
                           } else {
          @@ -312,7 +312,7 @@ public void typedSetter_changePrimaryKeyThrows() {
                   }
               }
           
          -    // Helper method for calling setters with different field names
          +    // Helper method for calling setters with different field names.
               private static void callSetter(DynamicRealmObject target, SupportedType type, List fieldNames) {
                   for (String fieldName : fieldNames) {
                       switch (type) {
          @@ -334,7 +334,7 @@ private static void callSetter(DynamicRealmObject target, SupportedType type, Li
                   }
               }
           
          -    // Test all typed setters/setters
          +    // Tests all typed setters/setters.
               @Test
               public void typedGettersAndSetters() {
                   realm.beginTransaction();
          @@ -388,7 +388,7 @@ public void typedGettersAndSetters() {
                                   assertEquals(dObj, dObj.getObject(AllJavaTypes.FIELD_OBJECT));
                                   break;
                               case LIST:
          -                        // ignore, see testGetList/testSetList
          +                        // Ignores. See testGetList/testSetList.
                                   break;
                               default:
                                   fail();
          @@ -505,7 +505,7 @@ public void setter_nullOnRequiredFieldsThrows() {
                   }
               }
           
          -    // Test types where you can set null using the typed setter instead of using setNull().
          +    // Tests types where you can set null using the typed setter instead of using setNull().
               @Test
               public void typedSetter_null() {
                   realm.beginTransaction();
          @@ -735,7 +735,7 @@ public void untypedSetter_listMixedTypesThrows() {
                   dObjTyped.set(AllJavaTypes.FIELD_LIST, list);
               }
           
          -    // List is not a simple getter, test separately.
          +    // List is not a simple getter, tests separately.
               @Test
               public void getList() {
                   realm.beginTransaction();
          @@ -921,7 +921,7 @@ public void untypedSetter_illegalImplicitConversionThrows() {
                           } catch (IllegalArgumentException ignored) {
                           } catch (RealmException e) {
                               if (!(e.getCause() instanceof ParseException)) {
          -                        // providing "foo" to the date parser will blow up with a RealmException
          +                        // Providing "foo" to the date parser will blow up with a RealmException
                                   // and the cause will be a ParseException.
                                   fail(type + " failed");
                               }
          @@ -1048,7 +1048,7 @@ public void hashcode() {
           
               @Test
               public void toString_test() {
          -        // Check that toString() doesn't crash. And do simple formatting checks. We cannot compare to a set String as
          +        // Checks that toString() doesn't crash, and does simple formatting checks. We cannot compare to a set String as
                   // eg. the byte array will be allocated each time it is accessed.
                   String str = dObjTyped.toString();
                   assertTrue(str.startsWith("AllJavaTypes = ["));
          @@ -1078,14 +1078,14 @@ public void toString_nullValues() {
           
           
               public void testExceptionMessage() {
          -        // test for https://github.com/realm/realm-java/issues/2141
          +        // Tests for https://github.com/realm/realm-java/issues/2141
                   realm.beginTransaction();
                   AllTypes obj = realm.createObject(AllTypes.class);
                   realm.commitTransaction();
           
                   DynamicRealmObject o = new DynamicRealmObject(obj);
                   try {
          -            o.getFloat("nonExisting"); // Note that "o" does not have "nonExisting" field.
          +            o.getFloat("nonExisting"); // Notes that "o" does not have "nonExisting" field.
                       fail();
                   } catch (IllegalArgumentException e) {
                       assertEquals("Illegal Argument: Field not found: nonExisting", e.getMessage());
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java
          index cca89c96ca..ca45c3923b 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java
          @@ -71,7 +71,7 @@ public class DynamicRealmTests {
               public void setUp() {
                   defaultConfig = configFactory.createConfiguration();
           
          -        // Initialize schema. DynamicRealm will not do that, so let a normal Realm create the file first.
          +        // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first.
                   Realm.getInstance(defaultConfig).close();
                   realm = DynamicRealm.getInstance(defaultConfig);
               }
          @@ -108,7 +108,7 @@ private void populateTestRealm(DynamicRealm realm, int objects) {
                   }
               }
           
          -    // Test that the SharedGroupManager is not reused across Realm/DynamicRealm on the same thread.
          +    // Tests that the SharedGroupManager is not reused across Realm/DynamicRealm on the same thread.
               // This is done by starting a write transaction in one Realm and verifying that none of the data
               // written (but not committed) is available in the other Realm.
               @Test
          @@ -131,7 +131,7 @@ public void separateSharedGroups() {
                   }
               }
           
          -    // Test that Realms can only be deleted after all Typed and Dynamic instances are closed
          +    // Tests that Realms can only be deleted after all Typed and Dynamic instances are closed.
               @Test
               public void deleteRealm_ThrowsIfDynamicRealmIsOpen() {
                   realm.close(); // Close Realm opened in setUp();
          @@ -333,7 +333,7 @@ public void execute(DynamicRealm realm) {
                           }
                       });
                   } catch (RuntimeException ignored) {
          -            // Ensure that we pass a valuable error message to the logger for developers.
          +            // Ensures that we pass a valuable error message to the logger for developers.
                       assertEquals(testLogger.message, "Could not cancel transaction, not currently in a transaction.");
                   } finally {
                       RealmLog.remove(testLogger);
          @@ -410,7 +410,7 @@ public void onChange(RealmResults object) {
                   looperThread.keepStrongReference.add(allTypes);
               }
           
          -    // Initialize a Dynamic Realm used by the *Async tests and keep it ref in the looperThread.
          +    // Initializes a Dynamic Realm used by the *Async tests and keeps it ref in the looperThread.
               private DynamicRealm initializeDynamicRealm() {
                   RealmConfiguration defaultConfig = looperThread.realmConfiguration;
                   final DynamicRealm dynamicRealm = DynamicRealm.getInstance(defaultConfig);
          @@ -439,14 +439,14 @@ public void findAllSortedAsync_usingMultipleFields() {
                   dynamicRealm.commitTransaction();
                   dynamicRealm.setAutoRefresh(true);
           
          -        // Sort first set by using: String[ASC], Long[DESC]
          +        // Sorts first set by using: String[ASC], Long[DESC].
                   final RealmResults realmResults1 = dynamicRealm.where(AllTypes.CLASS_NAME)
                           .findAllSortedAsync(
                                   new String[]{AllTypes.FIELD_STRING, AllTypes.FIELD_LONG},
                                   new Sort[]{Sort.ASCENDING, Sort.DESCENDING}
                           );
           
          -        // Sort second set by using: String[DESC], Long[ASC]
          +        // Sorts second set by using: String[DESC], Long[ASC].
                   final RealmResults realmResults2 = dynamicRealm.where(AllTypes.CLASS_NAME)
                           .between(AllTypes.FIELD_LONG, 0, 5)
                           .findAllSortedAsync(
          @@ -534,7 +534,7 @@ public void accessingDynamicRealmObjectBeforeAsyncQueryCompleted() {
                   final DynamicRealm dynamicRealm = initializeDynamicRealm();
                   final DynamicRealmObject[] dynamicRealmObject = new DynamicRealmObject[1];
           
          -        // Intercept completion of the async DynamicRealmObject query
          +        // Intercepts completion of the async DynamicRealmObject query.
                   Handler handler = new HandlerProxy(dynamicRealm.handlerController) {
                       @Override
                       public boolean onInterceptInMessage(int what) {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java
          index 87d1799643..2278bf240d 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java
          @@ -80,11 +80,11 @@ public void iOSDataTypes() throws IOException {
                               "ios/" + iosVersion + "-alltypes.realm", REALM_NAME);
                       realm = Realm.getDefaultInstance();
                       RealmResults result = realm.where(IOSAllTypes.class).findAllSorted("id", Sort.ASCENDING);
          -            // Verify metadata
          +            // Verifies metadata.
                       Table table = realm.getTable(IOSAllTypes.class);
                       assertTrue(table.hasPrimaryKey());
                       assertTrue(table.hasSearchIndex(table.getColumnIndex("id")));
          -            // iterative check
          +            // Iterative check.
                       for (int i = 0; i < 10; i++) {
                           IOSAllTypes obj = result.get(i);
                           assertTrue(obj.isBoolCol());
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java
          index c903d110c7..908219ccdb 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java
          @@ -275,7 +275,7 @@ private void doTestSortOnColumnWithPartialNullValues(String fieldName,
           
                   RealmResults sortedList = copy.sort(fieldName, Sort.ASCENDING);
                   assertEquals("Should have same size", original.size(), sortedList.size());
          -        // Null should always be the first one in the ascending sorted list
          +        // Null should always be the first one in the ascending sorted list.
                   assertEquals(2, sortedList.first().getId());
                   assertEquals(1, sortedList.last().getId());
           
          @@ -283,11 +283,11 @@ private void doTestSortOnColumnWithPartialNullValues(String fieldName,
                   sortedList = sortedList.sort(fieldName, Sort.DESCENDING);
                   assertEquals("Should have same size", original.size(), sortedList.size());
                   assertEquals(1, sortedList.first().getId());
          -        // Null should always be the last one in the descending sorted list
          +        // Null should always be the last one in the descending sorted list.
                   assertEquals(2, sortedList.last().getId());
               }
           
          -    // Test sort on nullable fields with null values partially
          +    // Tests sort on nullable fields with null values partially.
               @Test
               public void sort_rowsWithPartialNullValues() {
                   populatePartialNullRowsForNumericTesting(realm);
          @@ -410,7 +410,7 @@ public void sort_greekCharacters() {
                   assertEquals("αύριο", collection.get(2).getFieldString());
               }
           
          -    //No sorting order defined. There are Korean, Arabic and Chinese characters.
          +    // No sorting order defined. There are Korean, Arabic and Chinese characters.
               @Test
               public void sort_manyDifferentCharacters() {
                   OrderedRealmCollection collection = createStringCollection(realm, collectionClass,
          @@ -630,8 +630,8 @@ public void deleteLastFromRealm_emptyCollection() {
                   assertEquals(0, collection.size());
               }
           
          -    // Test all methods that mutate data throw correctly if not inside an transaction.
          -    // Due to implementation details both UnsupportedOperation and IllegalState is accepted at this level
          +    // Tests all methods that mutate data throw correctly if not inside an transaction.
          +    // Due to implementation details both UnsupportedOperation and IllegalState is accepted at this level.
               @Test
               public void mutableMethodsOutsideTransactions() {
           
          @@ -648,7 +648,7 @@ public void mutableMethodsOutsideTransactions() {
                                   expected = UnsupportedOperationException.class;
                                   break;
                               default:
          -                        // Use default exception
          +                        // Uses default exception.
                           }
                       }
           
          @@ -715,7 +715,7 @@ private boolean runMethodOnWrongThread(final ListMethod method) throws Execution
                   Future future = executorService.submit(new Callable() {
                       @Override
                       public Boolean call() throws Exception {
          -                // Define expected exception
          +                // Defines expected exception.
                           Class expected = IllegalStateException.class;
                           if (collectionClass == ManagedCollection.REALMRESULTS) {
                               switch (method) {
          @@ -726,7 +726,7 @@ public Boolean call() throws Exception {
                                       expected = UnsupportedOperationException.class;
                                       break;
                                   default:
          -                            // Use default exception
          +                            // Uses default exception.
                               }
                           }
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java
          index 0c40da4b90..fa8e7a8d05 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java
          @@ -241,7 +241,7 @@ public void where_shouldNotContainRemovedItem() {
               }
           
               /**
          -     * Test to see if a particular item that does exist in the same Realm does not
          +     * Tests to see if a particular item that does exist in the same Realm does not
                * exist in the result set of another query.
                */
               @Test
          @@ -267,15 +267,15 @@ public void where_findAll_size() {
                   RealmResults results = realm.where(AllJavaTypes.class).findAll();
                   assertEquals(TEST_SIZE, results.size());
           
          -        // querying a RealmResults should find objects that fulfill the condition
          +        // Querying a RealmResults should find objects that fulfill the condition.
                   RealmResults onedigits = results.where().lessThan(AllJavaTypes.FIELD_LONG, 10).findAll();
                   assertEquals(Math.min(10, TEST_SIZE), onedigits.size());
           
          -        // if no objects fulfill conditions, the result has zero objects
          +        // If no objects fulfill conditions, the result has zero objects.
                   RealmResults none = results.where().greaterThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll();
                   assertEquals(0, none.size());
           
          -        // querying a result with zero objects must give zero objects
          +        // Querying a result with zero objects must give zero objects.
                   RealmResults stillNone = none.where().greaterThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll();
                   assertEquals(0, stillNone.size());
               }
          @@ -330,7 +330,7 @@ public void min() {
                   assertEquals(0, minimum.intValue());
               }
           
          -    // Test min on empty columns
          +    // Tests min on empty columns.
               @Test
               public void min_emptyNonNullFields() {
                   OrderedRealmCollection results = createEmptyCollection(realm, collectionClass);
          @@ -340,7 +340,7 @@ public void min_emptyNonNullFields() {
                   assertNull(results.minDate(NullTypes.FIELD_DATE_NOT_NULL));
               }
           
          -    // Test min on nullable rows with all null values
          +    // Tests min on nullable rows with all null values.
               @Test
               public void min_emptyNullFields() {
                   OrderedRealmCollection results = createAllNullRowsForNumericTesting(realm, collectionClass);
          @@ -350,7 +350,7 @@ public void min_emptyNullFields() {
                   assertNull(results.maxDate(NullTypes.FIELD_DATE_NULL));
               }
           
          -    // Test min on nullable rows with partial null values
          +    // Tests min on nullable rows with partial null values.
               @Test
               public void min_partialNullRows() {
                   OrderedRealmCollection results = createPartialNullRowsForNumericTesting(realm, collectionClass);
          @@ -365,7 +365,7 @@ public void max() {
                   assertEquals(TEST_SIZE - 1, maximum.intValue());
               }
           
          -    // Test max on empty columns
          +    // Tests max on empty columns.
               @Test
               public void max_emptyNonNullFields() {
                   OrderedRealmCollection results = createEmptyCollection(realm, collectionClass);
          @@ -375,7 +375,7 @@ public void max_emptyNonNullFields() {
                   assertNull(results.maxDate(NullTypes.FIELD_DATE_NOT_NULL));
               }
           
          -    // Test max on nullable rows with all null values
          +    // Tests max on nullable rows with all null values.
               @Test
               public void max_emptyNullFields() {
                   OrderedRealmCollection results = createAllNullRowsForNumericTesting(realm, collectionClass);
          @@ -385,7 +385,7 @@ public void max_emptyNullFields() {
                   assertNull(results.maxDate(NullTypes.FIELD_DATE_NULL));
               }
           
          -    // Test max on nullable rows with partial null values
          +    // Tests max on nullable rows with partial null values.
               @Test
               public void max_partialNullRows() {
                   OrderedRealmCollection results = createPartialNullRowsForNumericTesting(realm, collectionClass);
          @@ -401,7 +401,7 @@ public void sum() {
                   assertEquals((TEST_SIZE - 1) * TEST_SIZE / 2, sum.intValue());
               }
           
          -    // Test sum on nullable rows with all null values
          +    // Tests sum on nullable rows with all null values.
               @Test
               public void sum_nullRows() {
                   OrderedRealmCollection resultList = createAllNullRowsForNumericTesting(realm, collectionClass);
          @@ -410,7 +410,7 @@ public void sum_nullRows() {
                   assertEquals(0d, resultList.sum(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d);
               }
           
          -    // Test sum on nullable rows with partial null values
          +    // Tests sum on nullable rows with partial null values.
               @Test
               public void sum_partialNullRows() {
                   OrderedRealmCollection resultList = createPartialNullRowsForNumericTesting(realm, collectionClass);
          @@ -438,7 +438,7 @@ public void avg() {
                   double N = (double) TEST_SIZE;
           
                   // Sum of numbers 1 to M: M*(M+1)/2
          -        // See setUp() for values of fields
          +        // See setUp() for values of fields.
                   // N = TEST_DATA_SIZE
           
                   // Type: double; a = 3.1415
          @@ -461,7 +461,7 @@ public void avg() {
                   assertEquals(1.234567 + 0.5 * (N - 1.0), collection.average(AllJavaTypes.FIELD_FLOAT), 0.0001);
               }
           
          -    // Test average on empty columns
          +    // Tests average on empty columns.
               @Test
               public void avg_emptyNonNullFields() {
                   OrderedRealmCollection resultList = createEmptyCollection(realm, collectionClass);
          @@ -470,7 +470,7 @@ public void avg_emptyNonNullFields() {
                   assertEquals(0d, resultList.average(NullTypes.FIELD_DOUBLE_NOT_NULL), 0d);
               }
           
          -    // Test average on nullable rows with all null values
          +    // Tests average on nullable rows with all null values.
               @Test
               public void avg_emptyNullFields() {
                   OrderedRealmCollection resultList = createEmptyCollection(realm, collectionClass);
          @@ -479,7 +479,7 @@ public void avg_emptyNullFields() {
                   assertEquals(0d, resultList.average(NullTypes.FIELD_DOUBLE_NULL), 0d);
               }
           
          -    // Test average on nullable rows with partial null values
          +    // Tests average on nullable rows with partial null values.
               @Test
               public void avg_partialNullRows() {
                   OrderedRealmCollection resultList = createPartialNullRowsForNumericTesting(realm, collectionClass);
          @@ -576,7 +576,7 @@ public void deleteAllFromRealm() {
                   if (collectionClass == ManagedCollection.MANAGED_REALMLIST) {
                       RealmList list = (RealmList) collection;
                       realm.beginTransaction();
          -            list.remove(0); // Break the cycle
          +            list.remove(0); // Breaks the cycle.
                       realm.commitTransaction();
                       size = TEST_SIZE - 1;
                   }
          @@ -658,13 +658,13 @@ public void equals_sameRealmObjectsDifferentCollection() {
                   assertTrue(collection.equals(createCollection(collectionClass)));
               }
           
          -    // Test all methods that mutate data throw correctly if not inside an transaction.
          -    // Due to implementation details both UnsupportedOperation and IllegalState is accepted at this level
          +    // Tests all methods that mutate data throw correctly if not inside an transaction.
          +    // Due to implementation details both UnsupportedOperation and IllegalState is accepted at this level.
               @Test
               public void mutableMethodsOutsideTransactions() {
                   for (CollectionMutatorMethod method : CollectionMutatorMethod.values()) {
           
          -            // Define expected exception
          +            // Defines expected exception.
                       Class expected = IllegalStateException.class;
                       if (collectionClass == ManagedCollection.REALMRESULTS) {
                           switch (method) {
          @@ -744,7 +744,7 @@ private boolean runMethodOnWrongThread(final CollectionMethod method) throws Exe
                       @Override
                       public Boolean call() throws Exception {
           
          -                // Define expected exception
          +                // Defines expected exception.
                           Class expected = IllegalStateException.class;
                           if (collectionClass == ManagedCollection.REALMRESULTS) {
                               switch (method) {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java
          index 0a8979656f..9ec3db4f47 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java
          @@ -48,8 +48,8 @@
           import io.realm.entities.AllTypes;
           import io.realm.entities.Dog;
           import io.realm.log.LogLevel;
          -import io.realm.log.RealmLogger;
           import io.realm.log.RealmLog;
          +import io.realm.log.RealmLogger;
           import io.realm.rule.RunInLooperThread;
           import io.realm.rule.RunTestInLooperThread;
           import io.realm.rule.TestRealmConfigurationFactory;
          @@ -57,7 +57,6 @@
           import static org.junit.Assert.assertEquals;
           import static org.junit.Assert.assertFalse;
           import static org.junit.Assert.assertNotNull;
          -import static org.junit.Assert.assertNull;
           import static org.junit.Assert.assertTrue;
           import static org.junit.Assert.fail;
           
          @@ -217,13 +216,13 @@ public Boolean call() throws Exception {
                       }
                   });
           
          -        // Wait until the looper in the background thread is started
          +        // Waits until the looper in the background thread is started.
                   while (!isReady.get()) {
                       Thread.sleep(5);
                   }
                   Thread.sleep(100);
           
          -        // Trigger OnRealmChanged on background thread
          +        // Triggers OnRealmChanged on background thread.
                   realm = Realm.getInstance(realmConfig);
                   realm.beginTransaction();
                   Dog dog = realm.createObject(Dog.class);
          @@ -238,7 +237,7 @@ public Boolean call() throws Exception {
                       looper[0].quit();
                   }
           
          -        // Wait until the Looper thread is actually closed
          +        // Waits until the Looper thread is actually closed.
                   while (isRealmOpen.get()) {
                       Thread.sleep(5);
                   }
          @@ -257,18 +256,18 @@ public void closeClearingHandlerMessages() throws InterruptedException, TimeoutE
                   Future future = executorService.submit(new Callable() {
                       @Override
                       public Boolean call() throws Exception {
          -                Looper.prepare(); // Fake background thread with a looper, eg. a IntentService
          +                Looper.prepare(); // Fake background thread with a looper, eg. a IntentService.
                           Realm realm = Realm.getInstance(realmConfig);
                           backgroundLooperStarted.countDown();
           
          -                // Random operation in the client code
          +                // Random operation in the client code.
                           final RealmResults dogs = realm.where(Dog.class).findAll();
                           if (dogs.size() != 0) {
                               return false;
                           }
          -                addHandlerMessages.await(1, TimeUnit.SECONDS); // Wait for main thread to add update messages
          +                addHandlerMessages.await(1, TimeUnit.SECONDS); // Wait for main thread to add update messages.
           
          -                // Create a Handler for the thread now. All message and references for the notification handler will be
          +                // Creates a Handler for the thread now. All message and references for the notification handler will be
                           // cleared once we call close().
                           Handler threadHandler = new Handler(Looper.myLooper());
                           realm.close(); // Close native resources + associated handlers.
          @@ -295,10 +294,10 @@ public void run() {
                       }
                   });
           
          -        // Wait until the looper is started on a background thread
          +        // Waits until the looper is started on a background thread.
                   backgroundLooperStarted.await(1, TimeUnit.SECONDS);
           
          -        // Execute a transaction that will trigger a Realm update
          +        // Executes a transaction that will trigger a Realm update.
                   Realm realm = Realm.getInstance(realmConfig);
                   realm.beginTransaction();
                   for (int i = 0; i < TEST_SIZE; i++) {
          @@ -310,7 +309,7 @@ public void run() {
                   realm.close();
                   addHandlerMessages.countDown();
           
          -        // Check that messages was properly cleared
          +        // Checks that messages was properly cleared.
                   // It looks like getting this future sometimes takes a while for some reason. Setting to
                   // 10s. now.
                   Boolean result = future.get(10, TimeUnit.SECONDS);
          @@ -359,18 +358,18 @@ public void addRemoveListenerConcurrency() {
                   final AtomicInteger counter3 = new AtomicInteger(0);
           
                   // At least we need 2 listeners existing in the list to make sure
          -        // the iterator.next get called
          +        // the iterator.next get called.
           
          -        // This one will be added when listener2's onChange called
          +        // This one will be added when listener2's onChange called.
                   final RealmChangeListener listener1 = new RealmChangeListener() {
                       @Override
                       public void onChange(Realm object) {
          -                // Step 7: Last listener called. Should only be called once
          +                // Step 7: Last listener called. Should only be called once.
                           counter1.incrementAndGet();
           
                           // after listener2.onChange
                           // Since duplicated entries will be ignored, we still have:
          -                // [listener2, listener1]
          +                // [listener2, listener1].
                           assertEquals(1, counter1.get());
                           assertEquals(2, counter2.get());
                           assertEquals(1, counter3.get());
          @@ -378,13 +377,13 @@ public void onChange(Realm object) {
                       }
                   };
           
          -        // This one will be existing in the list all the time
          +        // This one will be existing in the list all the time.
                   final RealmChangeListener listener2 = new RealmChangeListener() {
                       @Override
                       public void onChange(Realm object) {
                           // Step 3: Listener2 called
          -                // Listener state [listener2, listener3, listener1]
          -                // Listener 1 will not be called this time around
          +                // Listener state [listener2, listener3, listener1].
          +                // Listener 1 will not be called this time around.
                           counter2.incrementAndGet();
                           realm.addChangeListener(listener1);
                       }
          @@ -395,29 +394,29 @@ public void onChange(Realm object) {
                       @Override
                       public void onChange(Realm object) {
                           // Step 4: Listener3 called
          -                // Listener state [listener2, listener1]
          +                // Listener state [listener2, listener1].
                           counter3.incrementAndGet();
                           realm.removeChangeListener(this);
           
          -                // Step 5: Assert proper state
          -                // [listener2, listener1]
          +                // Step 5: Asserts proper state
          +                // [listener2, listener1].
                           assertEquals(0, counter1.get());
                           assertEquals(1, counter2.get());
                           assertEquals(1, counter3.get());
           
          -                // Step 6: Trigger next round of changes on [listener2, listener1]
          +                // Step 6: Triggers next round of changes on [listener2, listener1].
                           realm.beginTransaction();
                           realm.createObject(AllTypes.class);
                           realm.commitTransaction();
                       }
                   };
           
          -        // Step 1: Add initial listeners
          -        // Listener state [listener2, listener3]
          +        // Step 1: Adds initial listeners
          +        // Listener state [listener2, listener3].
                   realm.addChangeListener(listener2);
                   realm.addChangeListener(listener3);
           
          -        // Step 2: Trigger change listeners
          +        // Step 2: Triggers change listeners.
                   realm.beginTransaction();
                   realm.createObject(AllTypes.class);
                   realm.commitTransaction();
          @@ -431,7 +430,7 @@ public void weakReferenceListener() throws InterruptedException {
           
                   final Realm realm = looperThread.realm;
           
          -        // Setup weak listener
          +        // Setups weak listener.
                   RealmChangeListener weakListener = new RealmChangeListener() {
                       @Override
                       public void onChange(Realm object) {
          @@ -464,14 +463,14 @@ public void onChange(Realm object) {
                       weakRef.clear();
                   }
           
          -        // Trigger change listeners
          +        // Triggers change listeners.
                   realm.beginTransaction();
                   realm.createObject(AllTypes.class);
                   realm.commitTransaction();
               }
           
           
          -    // Test that that a WeakReferenceListener can be removed.
          +    // Tests that that a WeakReferenceListener can be removed.
               // This test is not a proper GC test, but just ensures that listeners can be removed from the list of weak listeners
               // without throwing an exception.
               @Test
          @@ -533,7 +532,7 @@ public void onChange(Realm object) {
                       public void onChange(Realm object) {
                           listenerBCalled.incrementAndGet();
                           if (listenerACalled.get() == 1) {
          -                    // 2. Reverse order
          +                    // 2. Reverse order.
                               realm.removeAllChangeListeners();
                               realm.addChangeListener(this);
                               realm.addChangeListener(listenerA);
          @@ -544,7 +543,7 @@ public void onChange(Realm object) {
                       }
                   };
           
          -        // 1. Add initial ordering
          +        // 1. Adds initial ordering.
                   realm.addChangeListener(listenerA);
                   realm.addChangeListener(listenerB);
           
          @@ -560,7 +559,7 @@ public void doNotUseClosedHandler() throws InterruptedException {
                   final CountDownLatch backgroundThread1Started = new CountDownLatch(1);
                   final CountDownLatch backgroundThread2Closed = new CountDownLatch(1);
           
          -        // Create Handler on Thread1 by opening a Realm instance
          +        // Creates Handler on Thread1 by opening a Realm instance.
                   new Thread("thread1") {
           
                       @Override
          @@ -580,7 +579,7 @@ public void onChange(Realm object) {
                       }
                   }.start();
           
          -        // Create Handler on Thread2 for the same Realm path and close the Realm instance again.
          +        // Creates Handler on Thread2 for the same Realm path and closes the Realm instance again.
                   new Thread("thread2") {
                       @Override
                       public void run() {
          @@ -605,7 +604,7 @@ public void onChange(Realm object) {
                   Realm realm = Realm.getInstance(realmConfig);
                   realm.beginTransaction();
                   realm.commitTransaction();
          -        // Any REALM_CHANGED message should now only reach the open Handler on Thread1
          +        // Any REALM_CHANGED message should now only reach the open Handler on Thread1.
                   try {
                       // TODO: Waiting 5 seconds is not a reliable condition. Figure out a better way for this.
                       if (!handlerNotified.await(5, TimeUnit.SECONDS)) {
          @@ -616,7 +615,7 @@ public void onChange(Realm object) {
                   }
               }
           
          -    // Test that we handle a Looper thread quiting it's looper before it is done executing the current loop ( = Realm.close()
          +    // Tests that we handle a Looper thread quiting it's looper before it is done executing the current loop ( = Realm.close()
               // isn't called yet).
               @Test
               public void looperThreadQuitsLooperEarly() throws InterruptedException {
          @@ -624,12 +623,12 @@ public void looperThreadQuitsLooperEarly() throws InterruptedException {
                   final CountDownLatch mainThreadCommitCompleted = new CountDownLatch(1);
                   final CountDownLatch backgroundThreadStopped = new CountDownLatch(1);
           
          -        // Start background looper and let it hang
          +        // Starts background looper and let it hang.
                   ExecutorService executorService = Executors.newSingleThreadExecutor();
                   executorService.submit(new Runnable() {
                       @Override
                       public void run() {
          -                Looper.prepare(); // Fake background thread with a looper, eg. a IntentService
          +                Looper.prepare(); // Fake background thread with a looper, eg. a IntentService.
           
                           Realm realm = Realm.getInstance(realmConfig);
                           realm.setAutoRefresh(false);
          @@ -645,7 +644,7 @@ public void run() {
                       }
                   });
           
          -        // Create a commit on another thread
          +        // Creates a commit on another thread.
                   TestHelper.awaitOrFail(backgroundLooperStartedAndStopped);
                   Realm realm = Realm.getInstance(realmConfig);
                   RealmLogger logger = TestHelper.getFailureLogger(Log.WARN);
          @@ -689,7 +688,7 @@ public void onChange(Realm object) {
                       }
                   });
                   TestHelper.awaitOrFail(backgroundThreadReady);
          -        // At this point the background thread started & registered the listener
          +        // At this point the background thread started & registered the listener.
           
                   Realm realm = Realm.getInstance(realmConfig);
                   realm.beginTransaction();
          @@ -748,7 +747,7 @@ public void onChange(Realm object) {
           
               // The presence of async RealmResults block any `REALM_CHANGE` notification causing historically the Realm
               // to advance to the latest version. We make sure in this test that all Realm listeners will be notified
          -    // regardless of the presence of an async RealmResults that will delay the `REALM_CHANGE` sometimes
          +    // regardless of the presence of an async RealmResults that will delay the `REALM_CHANGE` sometimes.
               @Test
               @RunTestInLooperThread
               public void asyncRealmResultsShouldNotBlockBackgroundCommitNotification() {
          @@ -764,7 +763,7 @@ public void asyncRealmResultsShouldNotBlockBackgroundCommitNotification() {
                       @Override
                       public void onChange(RealmResults results) {
                           if (dogs.size() == 2) {
          -                    // Results has the latest changes
          +                    // Results has the latest changes.
                               resultsListenerDone.set(true);
                               if (realmListenerDone.get()) {
                                   looperThread.testComplete();
          @@ -777,7 +776,7 @@ public void onChange(RealmResults results) {
                       @Override
                       public void onChange(Realm element) {
                           if (dogs.size() == 1) {
          -                    // Step 2. Create the second dog
          +                    // Step 2. Creates the second dog.
                               realm.executeTransactionAsync(new Realm.Transaction() {
                                   @Override
                                   public void execute(Realm realm) {
          @@ -785,7 +784,7 @@ public void execute(Realm realm) {
                                   }
                               });
                           } else if (dogs.size() == 2) {
          -                    // Realm listener can see the latest changes
          +                    // Realm listener can see the latest changes.
                               realmListenerDone.set(true);
                               if (resultsListenerDone.get()) {
                                   looperThread.testComplete();
          @@ -794,7 +793,7 @@ public void execute(Realm realm) {
                       }
                   });
           
          -        // Step 1. Create the first dog
          +        // Step 1. Creates the first dog.
                   realm.executeTransactionAsync(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
          @@ -818,7 +817,7 @@ public void asyncRealmObjectShouldNotBlockBackgroundCommitNotification() {
                       public void onChange(final Realm realm) {
                           switch (numberOfRealmCallbackInvocation.incrementAndGet()) {
                               case 1: {
          -                        // first commit
          +                        // First commit.
                                   Dog dog = realm.where(Dog.class).findFirstAsync();
                                   assertTrue(dog.load());
                                   dog.addChangeListener(new RealmChangeListener() {
          @@ -841,7 +840,7 @@ public void run() {
                                   break;
                               }
                               case 2: {
          -                        // finish test
          +                        // Finishes test.
                                   TestHelper.awaitOrFail(signalClosedRealm);
                                   looperThread.testComplete();
                                   break;
          @@ -948,7 +947,7 @@ public void execute(Realm realm) {
                   realm.addChangeListener(new RealmChangeListener() {
                       @Override
                       public void onChange(Realm element) {
          -                // Change event triggered by deletion in async transaction.
          +                // Changes event triggered by deletion in async transaction.
                           assertEquals(0, realm.where(AllTypes.class).count());
                           assertEquals(0, results.size());
                           looperThread.testComplete();
          @@ -977,7 +976,7 @@ public void callingOrdersOfListeners() {
                           new RealmChangeListener>() {
                               @Override
                               public void onChange(RealmResults element) {
          -                        // First called
          +                        // First called.
                                   assertEquals(0, count.getAndIncrement());
                               }
                           };
          @@ -985,14 +984,14 @@ public void onChange(RealmResults element) {
                   final RealmChangeListener syncedObjectListener = new RealmChangeListener() {
                       @Override
                       public void onChange(AllTypes element) {
          -                // Second called
          +                // Second called.
                           assertEquals(1, count.getAndIncrement());
                       }
                   };
                   final RealmChangeListener globalListener = new RealmChangeListener() {
                       @Override
                       public void onChange(Realm element) {
          -                // third called
          +                // Third called.
                           assertEquals(2, count.getAndIncrement());
                           looperThread.testComplete();
                       }
          @@ -1003,23 +1002,23 @@ public void onChange(Realm element) {
                   final AllTypes allTypes = realm.createObject(AllTypes.class);
                   realm.commitTransaction();
           
          -        // We need to create one objects first and let the pass the first change event
          +        // We need to create one objects first and let the pass the first change event.
                   final RealmChangeListener initListener = new RealmChangeListener() {
                       @Override
                       public void onChange(Realm element) {
                           looperThread.postRunnable(new Runnable() {
                               @Override
                               public void run() {
          -                        // Clear the change listeners
          +                        // Clears the change listeners.
                                   realm.removeAllChangeListeners();
           
          -                        // Now we can start testing
          +                        // Now we can start testing.
                                   allTypes.addChangeListener(syncedObjectListener);
                                   RealmResults results = realm.where(AllTypes.class).findAll();
                                   results.addChangeListener(syncedResultsListener);
                                   realm.addChangeListener(globalListener);
           
          -                        // Now we trigger those listeners
          +                        // Now we trigger those listeners.
                                   realm.executeTransactionAsync(new Realm.Transaction() {
                                       @Override
                                       public void execute(Realm realm) {
          @@ -1035,11 +1034,11 @@ public void execute(Realm realm) {
                   realm.addChangeListener(initListener);
               }
           
          -    // See https://github.com/realm/realm-android-adapters/issues/48
          -    // Step 1: Populate the db
          -    // Step 2: Post a runnable to caller thread.
          +    // See https://github.com/realm/realm-android-adapters/issues/48.
          +    // Step 1: Populates the db.
          +    // Step 2: Posts a runnable to caller thread.
               //         Event Queue: |Posted Runnable| <- TOP
          -    // Step 3: Delete object which will make the results contain an invalid object at this moment
          +    // Step 3: Deletes object which will make the results contain an invalid object at this moment.
               //         Right Event Queue: |LOCAL_COMMIT   |   Wrong Event Queue: |Posted Runnable           |  <- TOP
               //                            |Posted Runnable|                      |REALM_CHANGED/LOCAL_COMMIT|
               // Step 4: Posted runnable called.
          @@ -1075,12 +1074,12 @@ public void execute(Realm realm) {
                   });
               }
           
          -    // See https://github.com/realm/realm-android-adapters/issues/48
          -    // Step 1: Populate the db
          -    // Step 2: Create a async query, and wait until it finishes
          -    // Step 3: Post a runnable to caller thread.
          +    // See https://github.com/realm/realm-android-adapters/issues/48.
          +    // Step 1: Populates the db.
          +    // Step 2: Creates a async query, and waits until it finishes.
          +    // Step 3: Posts a runnable to caller thread.
               //         Event Queue: |Posted Runnable| <- TOP
          -    // Step 4: Delete object which will make the results contain a invalid object at this moment
          +    // Step 4: Deletes object which will make the results contain a invalid object at this moment.
               //         Right Event Queue: |LOCAL_COMMIT   |   Wrong Event Queue: |Posted Runnable           |  <- TOP
               //                            |Posted Runnable|                      |REALM_CHANGED/LOCAL_COMMIT|
               // Step 5: Posted runnable called.
          @@ -1128,12 +1127,12 @@ public void execute(Realm realm) {
                   });
               }
           
          -    // See https://github.com/realm/realm-android-adapters/issues/48
          -    // Step 1: Populate the db
          -    // Step 2: Create a async query, and pause it
          -    // Step 3: Post a runnable to caller thread.
          +    // See https://github.com/realm/realm-android-adapters/issues/48.
          +    // Step 1: Populates the db.
          +    // Step 2: Creates a async query, and pauses it.
          +    // Step 3: Posts a runnable to caller thread.
               //         Event Queue: |Posted Runnable| <- TOP
          -    // Step 4: Delete object which will make the results contain a invalid object at this moment
          +    // Step 4: Deletes object which will make the results contain a invalid object at this moment.
               //         Right Event Queue: |LOCAL_COMMIT   |   Wrong Event Queue: |Posted Runnable           |  <- TOP
               //                            |Posted Runnable|                      |REALM_CHANGED/LOCAL_COMMIT|
               // Step 5: Posted runnable called.
          @@ -1221,7 +1220,7 @@ public void accessingSyncRealmResultInsideAsyncResultListener() {
                       public void onChange(RealmResults results) {
                           switch (asyncResultCallback.incrementAndGet()) {
                               case 1:
          -                        // Called when first async query completes
          +                        // Called when first async query completes.
                                   assertEquals(0, results.size());
                                   realm.executeTransactionAsync(new Realm.Transaction() {
                                       @Override
          @@ -1233,7 +1232,7 @@ public void execute(Realm realm) {
           
                               case 2:
                                   // Called after async transaction completes, A REALM_CHANGED event has been triggered,
          -                        // async queries have rerun, and listeners are triggered again
          +                        // async queries have rerun, and listeners are triggered again.
                                   assertEquals(1, results.size());
                                   assertEquals(1, syncResults.size()); // If syncResults is not in sync yet, this will fail.
                                   looperThread.testComplete();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java
          index 0643ba9316..3484cb2c1f 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java
          @@ -258,7 +258,7 @@ public void iterator_remove() {
                   try {
                       it.remove();
                   } catch (UnsupportedOperationException e) {
          -            // RealmResults doesn't support remove
          +            // RealmResults doesn't support remove.
                       assertEquals(CollectionClass.REALMRESULTS, collectionClass);
                       return;
                   }
          @@ -301,7 +301,7 @@ public void iterator_deleteManagedObjectIndirectly() {
               @Test
               public void iterator_removeCalledTwice() {
                   if (skipTest(CollectionClass.REALMRESULTS)) {
          -            return; // remove() not supported by RealmResults
          +            return; // remove() not supported by RealmResults.
                   }
           
                   Iterator it = collection.iterator();
          @@ -341,13 +341,13 @@ public void listIterator_oneElement() {
                   collection = createCollection(realm, collectionClass, 1);
                   ListIterator it = collection.listIterator();
           
          -        // Test beginning of the list
          +        // Tests beginning of the list.
                   assertFalse(it.hasPrevious());
                   assertTrue(it.hasNext());
                   assertEquals(-1, it.previousIndex());
                   assertEquals(0, it.nextIndex());
           
          -        // Test end of the list
          +        // Tests end of the list.
                   AllJavaTypes firstObject = it.next();
                   assertEquals(0, firstObject.getFieldLong());
                   assertTrue(it.hasPrevious());
          @@ -360,19 +360,19 @@ public void listIterator_oneElement() {
               public void listIterator_manyElements() {
                   ListIterator it = collection.listIterator();
           
          -        // Test beginning of the list
          +        // Tests beginning of the list.
                   assertFalse(it.hasPrevious());
                   assertTrue(it.hasNext());
                   assertEquals(-1, it.previousIndex());
                   assertEquals(0, it.nextIndex());
           
          -        // Test 1st element in the list
          +        // Tests 1st element in the list.
                   AllJavaTypes firstObject = it.next();
                   assertEquals(0, firstObject.getFieldLong());
                   assertTrue(it.hasPrevious());
                   assertEquals(0, it.previousIndex());
           
          -        // Move to second last element
          +        // Moves to second last element.
                   for (int i = 1; i < TEST_SIZE - 1; i++) {
                       it.next();
                   }
          @@ -380,7 +380,7 @@ public void listIterator_manyElements() {
                   assertTrue(it.hasNext());
                   assertEquals(TEST_SIZE - 1, it.nextIndex());
           
          -        // Test end of the list
          +        // Tests end of the list.
                   AllJavaTypes lastObject = it.next();
                   assertEquals(TEST_SIZE - 1, lastObject.getFieldLong());
                   assertTrue(it.hasPrevious());
          @@ -439,7 +439,7 @@ public void listIterator_remove_calledTwice() {
                           break;
                       case REALMRESULTS:
                           try {
          -                    it.remove(); // Method not supported
          +                    it.remove(); // Method not supported.
                               fail();
                           } catch (UnsupportedOperationException ignored) {
                           }
          @@ -458,7 +458,7 @@ public void listIterator_transactionBeforeNextItem() {
                       assertEquals("Failed at index: " + i, i, item.getFieldLong());
                       i++;
           
          -            // Committing transactions while iterating should not effect the current iterator if on a looper thread
          +            // Committing transactions while iterating should not effect the current iterator if on a looper thread.
                       createNewObject();
                   }
               }
          @@ -531,7 +531,7 @@ public void listIterator_deleteManagedObjectIndirectly() {
                   }
                   it = collection.listIterator();
                   it.next();
          -        AllJavaTypes types = it.next(); // Iterator can still access the deleted object
          +        AllJavaTypes types = it.next(); // Iterator can still access the deleted object.
           
                   //noinspection SimplifiableConditionalExpression
                   assertTrue(collectionClass == CollectionClass.MANAGED_REALMLIST ? types.isValid() : !types.isValid());
          @@ -563,7 +563,7 @@ public void listIterator_set() {
                   realm.beginTransaction();
                   ListIterator it = collection.listIterator();
           
          -        // Calling set() before next() should throw
          +        // Calling set() before next() should throw.
                   try {
                       it.set(new AllJavaTypes());
                       fail();
          @@ -588,7 +588,7 @@ public void listIterator_add() {
                   realm.beginTransaction();
                   ListIterator it = collection.listIterator();
           
          -        // Calling set() before next() should throw
          +        // Calling set() before next() should throw.
                   try {
                       it.add(new AllJavaTypes());
                       fail();
          @@ -639,7 +639,7 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification() {
                       return;
                   }
           
          -        // Test all standard collection methods
          +        // Tests all standard collection methods.
                   for (CollectionMethod method : CollectionMethod.values()) {
                       collection = createCollection(realm, collectionClass, TEST_SIZE);
                       realm.beginTransaction();
          @@ -652,7 +652,7 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification() {
                           case REMOVE_ALL: collection.removeAll(Collections.singletonList(collection.get(0))); break;
                           case RETAIN_ALL: collection.retainAll(Collections.singletonList(collection.get(0))); break;
           
          -                // Does not impact size, so does not trigger ConcurrentModificationException
          +                // Does not impact size, so does not trigger ConcurrentModificationException.
                           case CONTAINS:
                           case CONTAINS_ALL:
                           case EQUALS:
          @@ -679,7 +679,7 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification() {
                           case ADD_ALL_INDEX: collection.addAll(0, Collections.singleton(new AllJavaTypes(TEST_SIZE))); break;
                           case REMOVE_INDEX: collection.remove(0); break;
           
          -                // Does not impact size, so does not trigger ConcurrentModificationException
          +                // Does not impact size, so does not trigger ConcurrentModificationException.
                           case FIRST:
                           case LAST:
                           case GET_INDEX:
          @@ -705,17 +705,17 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification_managedColl
                       return;
                   }
           
          -        // Test all RealmCollection methods
          +        // Tests all RealmCollection methods.
                   for (RealmCollectionMethod method : RealmCollectionMethod.values()) {
                       collection = createCollection(realm, collectionClass, TEST_SIZE);
                       realm.beginTransaction();
          -            collection.remove(0); // Remove object creating circular dependency which will crash deleteAll.
          +            collection.remove(0); // Removes object creating circular dependency which will crash deleteAll.
                       Iterator it = collection.iterator();
                       switch (method) {
                           case DELETE_ALL_FROM_REALM:
                               collection.deleteAllFromRealm(); break;
           
          -                // Does not impact size, so does not trigger ConcurrentModificationException
          +                // Does not impact size, so does not trigger ConcurrentModificationException.
                           case WHERE:
                           case MIN:
                           case MAX:
          @@ -733,7 +733,7 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification_managedColl
                       checkIteratorThrowsConcurrentModification(realm, method.toString(), it);
                   }
           
          -        // Test all OrderedRealmCollection methods
          +        // Tests all OrderedRealmCollection methods.
                   for (OrderedRealmCollectionMethod method : OrderedRealmCollectionMethod.values()) {
                       collection = createCollection(realm, collectionClass, TEST_SIZE);
                       realm.beginTransaction();
          @@ -743,7 +743,7 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification_managedColl
                           case DELETE_FIRST: collection.deleteFirstFromRealm(); break;
                           case DELETE_LAST: collection.deleteLastFromRealm(); break;
           
          -                // Does not impact size, so does not trigger ConcurrentModificationException
          +                // Does not impact size, so does not trigger ConcurrentModificationException.
                           case SORT:
                           case SORT_FIELD:
                           case SORT_2FIELDS:
          @@ -777,7 +777,7 @@ public void iterator_realmResultsThrowConcurrentModification() {
                       return;
                   }
           
          -        // Verify that ConcurrentModification is correctly detected on non-looper threads
          +        // Verifies that ConcurrentModification is correctly detected on non-looper threads.
                   Iterator it = collection.iterator();
                   final CountDownLatch bgDone = new CountDownLatch(1);
                   new Thread(new Runnable() {
          @@ -815,7 +815,7 @@ public void useCase_simpleIterator_modifyQueryResult_innerTransaction() {
                       realm.commitTransaction();
                   }
           
          -        // Verify that all elements were modified
          +        // Verifies that all elements were modified.
                   assertEquals(0, realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).count());
               }
           
          @@ -834,7 +834,7 @@ public void useCase_simpleIterator_modifyQueryResult_outerTransaction() {
                   }
                   realm.commitTransaction();
           
          -        // Verify that all elements were modified
          +        // Verifies that all elements were modified.
                   assertEquals(0, realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).count());
               }
           
          @@ -852,7 +852,7 @@ public void useCase_forEachIterator_modifyQueryResult_innerTransaction() {
                       realm.commitTransaction();
                   }
           
          -        // Verify that all elements were modified
          +        // Verifies that all elements were modified.
                   assertEquals(0, realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).count());
               }
           
          @@ -870,7 +870,7 @@ public void useCase_forEachIterator_modifyQueryResult_outerTransaction() {
                   }
                   realm.commitTransaction();
           
          -        // Verify that all elements were modified
          +        // Verifies that all elements were modified.
                   assertEquals(0, realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).count());
               }
           
          @@ -890,7 +890,7 @@ public void useCase_simpleIterator_modifyQueryResult_innerTransaction_looperThre
                       realm.commitTransaction();
                   }
           
          -        // Verify that all elements were modified
          +        // Verifies that all elements were modified.
                   assertEquals(0, realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).count());
               }
           
          @@ -910,7 +910,7 @@ public void useCase_simpleIterator_modifyQueryResult_outerTransaction_looperThre
                   }
                   realm.commitTransaction();
           
          -        // Verify that all elements were modified
          +        // Verifies that all elements were modified.
                   assertEquals(0, realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).count());
               }
           
          @@ -929,7 +929,7 @@ public void useCase_forEachIterator_modifyQueryResult_innerTransaction_looperThr
                       realm.commitTransaction();
                   }
           
          -        // Verify that all elements were modified
          +        // Verifies that all elements were modified.
                   assertEquals(0, realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).count());
               }
           
          @@ -948,7 +948,7 @@ public void useCase_forEachIterator_modifyQueryResult_outerTransaction_looperThr
                   }
                   realm.commitTransaction();
           
          -        // Verify that all elements were modified
          +        // Verifies that all elements were modified.
                   assertEquals(0, realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).count());
               }
           }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java
          index 6677482d9c..5b5084f004 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java
          @@ -70,7 +70,7 @@ public void ignore() {
                   assertEquals(-1, table.getColumnIndex("ignoreString"));
               }
           
          -    // Test if "index" annotation works with supported types
          +    // Tests if "index" annotation works with supported types.
               @Test
               public void index() {
                   Table table = realm.getTable(AnnotationIndexTypes.class);
          @@ -97,7 +97,7 @@ public void index() {
                   assertFalse(table.hasSearchIndex(table.getColumnIndex("notIndexDate")));
               }
           
          -    // Test migrating primary key from string to long with existing data
          +    // Tests migrating primary key from string to long with existing data.
               @Test
               public void primaryKey_migration_long() {
                   realm.beginTransaction();
          @@ -112,13 +112,13 @@ public void primaryKey_migration_long() {
                   realm.cancelTransaction();
               }
           
          -    // Test migrating primary key from string to long with existing data
          +    // Tests migrating primary key from string to long with existing data.
               @Test
               public void primaryKey_migration_longDuplicateValues() {
                   realm.beginTransaction();
                   for (int i = 1; i <= 2; i++) {
                       PrimaryKeyAsString obj = realm.createObject(PrimaryKeyAsString.class, "String" + i);
          -            obj.setId(1); // Create duplicate values
          +            obj.setId(1); // Creates duplicate values.
                   }
           
                   Table table = realm.getTable(PrimaryKeyAsString.class);
          @@ -132,7 +132,7 @@ public void primaryKey_migration_longDuplicateValues() {
                   }
               }
           
          -    // Test migrating primary key from long to str with existing data
          +    // Tests migrating primary key from long to str with existing data.
               @Test
               public void primaryKey_migration_string() {
                   realm.beginTransaction();
          @@ -147,13 +147,13 @@ public void primaryKey_migration_string() {
                   realm.cancelTransaction();
               }
           
          -    // Test migrating primary key from long to str with existing data
          +    // Tests migrating primary key from long to str with existing data.
               @Test
               public void primaryKey_migration_stringDuplicateValues() {
                   realm.beginTransaction();
                   for (int i = 1; i <= 2; i++) {
                       PrimaryKeyAsLong obj = realm.createObject(PrimaryKeyAsLong.class, i);
          -            obj.setName("String"); // Create duplicate values
          +            obj.setName("String"); // Creates duplicate values.
                   }
           
                   Table table = realm.getTable(PrimaryKeyAsLong.class);
          @@ -203,8 +203,8 @@ public void primaryKey_isIndexed() {
                   assertTrue(table.hasSearchIndex(table.getColumnIndex("id")));
               }
           
          -    // Annotation processor honors common naming conventions
          -    // We check if setters and getters are generated and working
          +    // Annotation processor honors common naming conventions.
          +    // We check if setters and getters are generated and working.
               @Test
               public void namingConvention() {
                   realm.beginTransaction();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java
          index 9375d6efd8..3e2b84a346 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java
          @@ -74,7 +74,7 @@ public class RealmAsyncQueryTests {
               // ****  Async transaction  ***
               // ****************************
           
          -    // start asynchronously a transaction to insert one element
          +    // Starts asynchronously a transaction to insert one element.
               @Test
               @RunTestInLooperThread
               public void executeTransactionAsync() throws Throwable {
          @@ -168,7 +168,7 @@ public void onChange(Realm object) {
                   });
               }
           
          -    // Test that an async transaction that throws an exception propagate it properly to the user.
          +    // Tests that an async transaction that throws an exception propagate it properly to the user.
               @Test
               @RunTestInLooperThread
               public void executeTransactionAsync_exceptionHandling() throws Throwable {
          @@ -184,7 +184,7 @@ public void executeTransactionAsync_exceptionHandling() throws Throwable {
                       public void execute(Realm realm) {
                           Owner owner = realm.createObject(Owner.class);
                           owner.setName("Owner");
          -                realm.cancelTransaction(); // Cancel the transaction then throw
          +                realm.cancelTransaction(); // Cancels the transaction then throw.
                           throw new RuntimeException("Boom");
                       }
                   }, new Realm.Transaction.OnSuccess() {
          @@ -195,7 +195,7 @@ public void onSuccess() {
                   }, new Realm.Transaction.OnError() {
                       @Override
                       public void onError(Throwable error) {
          -                // Ensure we are giving developers quality messages in the logs.
          +                // Ensures we are giving developers quality messages in the logs.
                           assertEquals("Could not cancel transaction, not currently in a transaction.", testLogger.message);
                           RealmLog.remove(testLogger);
                           looperThread.testComplete();
          @@ -203,7 +203,7 @@ public void onError(Throwable error) {
                   });
               }
           
          -    // Test if the background Realm is closed when transaction success returned.
          +    // Tests if the background Realm is closed when transaction success returned.
               @Test
               @RunTestInLooperThread
               public void executeTransactionAsync_realmClosedOnSuccess() {
          @@ -224,7 +224,7 @@ public void onResult(int count) {
                       public void onSuccess() {
                           RealmCache.invokeWithGlobalRefCount(realm.getConfiguration(), cacheCallback);
                           if (counter.get() == 0) {
          -                    // Finish testing
          +                    // Finishes testing.
                               return;
                           }
                           realm.executeTransactionAsync(new Realm.Transaction() {
          @@ -242,7 +242,7 @@ public void execute(Realm realm) {
                   }, transactionCallback);
               }
           
          -    // Test if the background Realm is closed when transaction error returned.
          +    // Tests if the background Realm is closed when transaction error returned.
               @Test
               @RunTestInLooperThread
               public void executeTransaction_async_realmClosedOnError() {
          @@ -263,7 +263,7 @@ public void onResult(int count) {
                       public void onError(Throwable error) {
                           RealmCache.invokeWithGlobalRefCount(realm.getConfiguration(), cacheCallback);
                           if (counter.get() == 0) {
          -                    // Finish testing
          +                    // Finishes testing.
                               return;
                           }
                           realm.executeTransactionAsync(new Realm.Transaction() {
          @@ -284,7 +284,7 @@ public void execute(Realm realm) {
               }
           
               // Test case for https://github.com/realm/realm-java/issues/1893
          -    // Ensure that onSuccess is called with the correct Realm version for async transaction.
          +    // Ensures that onSuccess is called with the correct Realm version for async transaction.
               @Test
               @RunTestInLooperThread
               public void executeTransactionAsync_asyncQuery() {
          @@ -316,7 +316,7 @@ public void onError(Throwable error) {
               // *** promises based async queries ***
               // ************************************
           
          -    // finding element [0-4] asynchronously then wait for the promise to be loaded.
          +    // Finds element [0-4] asynchronously then waits for the promise to be loaded.
               @Test
               @RunTestInLooperThread
               public void findAllAsync() throws Throwable {
          @@ -395,7 +395,7 @@ public void findAllAsync_reusingQuery() throws Throwable {
                   assertTrue(allAsync.load());
                   assertEquals(allAsync, queryAllSync);
           
          -        // the RealmQuery already has an argumentHolder, can't reuse it
          +        // The RealmQuery already has an argumentHolder, can't reuse it.
                   try {
                       query.findAllSorted("columnLong");
                       fail("Should throw an exception, can not reuse RealmQuery");
          @@ -404,8 +404,8 @@ public void findAllAsync_reusingQuery() throws Throwable {
                   }
               }
           
          -    // finding elements [0-4] asynchronously then wait for the promise to be loaded
          -    // using a callback to be notified when the data is loaded
          +    // Finds elements [0-4] asynchronously then waits for the promise to be loaded
          +    // using a callback to be notified when the data is loaded.
               @Test
               @RunTestInLooperThread
               public void findAllAsync_withNotification() throws Throwable {
          @@ -430,8 +430,8 @@ public void onChange(RealmResults object) {
                   assertEquals(0, results.size());
               }
           
          -    // transforming an async query into sync by calling load to force
          -    // the blocking behaviour
          +    // Transforms an async query into sync by calling load to force
          +    // the blocking behaviour.
               @Test
               @RunTestInLooperThread
               public void findAllAsync_forceLoad() throws Throwable {
          @@ -442,7 +442,7 @@ public void findAllAsync_forceLoad() throws Throwable {
                           .findAllAsync();
           
                   looperThread.keepStrongReference.add(realmResults);
          -        // notification should be called as well
          +        // Notification should be called as well.
                   realmResults.addChangeListener(new RealmChangeListener>() {
                       @Override
                       public void onChange(RealmResults object) {
          @@ -464,14 +464,14 @@ public void onChange(RealmResults object) {
               }
           
               // UC:
          -    //   1- insert 10 objects
          -    //   2- start an async query to find object [0-4]
          -    //   3- assert current RealmResults is empty (Worker Thread didn't complete)
          -    //   4- when the worker thread complete, advance the Realm
          -    //   5- the caller thread is ahead of the result provided by the worker thread
          -    //   6- retry automatically the async query
          -    //   7- the returned RealmResults is now in the same version as the caller thread
          -    //   8- the notification should be called once (when we retry automatically we shouldn't
          +    //   1- Inserts 10 objects.
          +    //   2- Starts an async query to find object [0-4].
          +    //   3- Asserts current RealmResults is empty (Worker Thread didn't complete).
          +    //   4- When the worker thread completes, advances the Realm.
          +    //   5- The caller thread is ahead of the result provided by the worker thread.
          +    //   6- Retries automatically the async query.
          +    //   7- The returned RealmResults is now in the same version as the caller thread.
          +    //   8- The notification should be called once (when we retry automatically we shouldn't
               //      notify the user).
               @Test
               @RunTestInLooperThread
          @@ -480,23 +480,23 @@ public void findAllAsync_retry() throws Throwable {
                   final AtomicInteger numberOfInvocation = new AtomicInteger(0);
                   final Realm realm = looperThread.realm;
           
          -        // 1. Populate initial data
          +        // 1. Populates initial data.
                   realm.setAutoRefresh(false);
                   populateTestRealm(realm, 10);
                   realm.setAutoRefresh(true);
           
          -        // 2. Configure handler interceptor
          +        // 2. Configures handler interceptor.
                   final Handler handler = new HandlerProxy(realm.handlerController) {
                       @Override
                       public boolean onInterceptInMessage(int what) {
          -                // Intercepts in order: [QueryComplete, RealmChanged, QueryUpdated]
          +                // Intercepts in order: [QueryComplete, RealmChanged, QueryUpdated].
                           int intercepts = numberOfIntercept.incrementAndGet();
                           switch (what) {
          -                    // 5. Intercept all messages from other threads. On the first complete, we advance the tread
          +                    // 5. Intercepts all messages from other threads. On the first complete, we advance the tread
                               // which will cause the async query to rerun instead of triggering the change listener.
                               case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS:
                                   if (intercepts == 1) {
          -                            // We advance the Realm so we can simulate a retry
          +                            // We advance the Realm so we can simulate a retry.
                                       realm.beginTransaction();
                                       realm.delete(AllTypes.class);
                                       realm.commitTransaction();
          @@ -507,16 +507,16 @@ public boolean onInterceptInMessage(int what) {
                   };
                   realm.setHandler(handler);
           
          -        // 3. Create a async query
          +        // 3. Creates a async query.
                   final RealmResults realmResults = realm.where(AllTypes.class)
                           .between("columnLong", 0, 4)
                           .findAllAsync();
           
          -        // 4. Ensure that query isn't loaded yet
          +        // 4. Ensures that query isn't loaded yet.
                   assertFalse(realmResults.isLoaded());
                   assertEquals(0, realmResults.size());
           
          -        // 6. Callback triggered after retry has completed
          +        // 6. Callback triggered after retry has completed.
                   looperThread.keepStrongReference.add(realmResults);
                   realmResults.addChangeListener(new RealmChangeListener>() {
                       @Override
          @@ -531,13 +531,13 @@ public void onChange(RealmResults object) {
               }
           
               // UC:
          -    //   1- insert 10 objects
          -    //   2- start 2 async queries to find all objects [0-9] & objects[0-4]
          -    //   3- assert both RealmResults are empty (Worker Thread didn't complete)
          -    //   4- the queries will complete with the same version as the caller thread
          -    //   5- using a background thread update the Realm
          -    //   6- now REALM_CHANGED will trigger a COMPLETED_UPDATE_ASYNC_QUERIES that should update all queries
          -    //   7- callbacks are notified with the latest results (called twice overall)
          +    //   1- Inserts 10 objects.
          +    //   2- Starts 2 async queries to find all objects [0-9] & objects[0-4].
          +    //   3- Asserts both RealmResults are empty (Worker Thread didn't complete).
          +    //   4- The queries will complete with the same version as the caller thread.
          +    //   5- Using a background thread update the Realm.
          +    //   6- Now REALM_CHANGED will trigger a COMPLETED_UPDATE_ASYNC_QUERIES that should update all queries.
          +    //   7- Callbacks are notified with the latest results (called twice overall).
               @Test
               @RunTestInLooperThread
               public void findAllAsync_batchUpdate() throws Throwable {
          @@ -547,7 +547,7 @@ public void findAllAsync_batchUpdate() throws Throwable {
                   final Realm realm = looperThread.realm;
                   populateTestRealm(realm, 10);
           
          -        // 1. Configure Handler interceptor
          +        // 1. Configures Handler interceptor.
                   Handler handler = new HandlerProxy(realm.handlerController) {
                       @Override
                       public boolean onInterceptInMessage(int what) {
          @@ -575,7 +575,7 @@ public void doInBackground(Realm realm) {
                   };
                   realm.setHandler(handler);
           
          -        // 2. Create 2 async queries and check they are not loaded
          +        // 2. Creates 2 async queries and check they are not loaded.
                   final RealmResults realmResults1 = realm.where(AllTypes.class).findAllAsync();
                   final RealmResults realmResults2 = realm.where(AllTypes.class).between("columnLong", 0, 4).findAllAsync();
           
          @@ -607,13 +607,13 @@ public void run() {
                       @Override
                       public void onChange(RealmResults object) {
                           switch (numberOfNotificationsQuery1.incrementAndGet()) {
          -                    case 1: // first callback invocation
          +                    case 1: // First callback invocation
                                   assertTrue(realmResults1.isLoaded());
                                   assertEquals(10, realmResults1.size());
                                   assertEquals("test data 4", realmResults1.get(4).getColumnString());
                                   break;
           
          -                    case 2: // second callback
          +                    case 2: // Second callback
                                   assertTrue(realmResults1.isLoaded());
                                   assertEquals(12, realmResults1.size());
                                   assertEquals("modified", realmResults1.get(4).getColumnString());
          @@ -628,13 +628,13 @@ public void onChange(RealmResults object) {
                       @Override
                       public void onChange(RealmResults object) {
                           switch (numberOfNotificationsQuery2.incrementAndGet()) {
          -                    case 1: // first callback invocation
          +                    case 1: // First callback invocation
                                   assertTrue(realmResults2.isLoaded());
                                   assertEquals(5, realmResults2.size());
                                   assertEquals("test data 4", realmResults2.get(4).getColumnString());
                                   break;
           
          -                    case 2: // second callback
          +                    case 2: // Second callback
                                   assertTrue(realmResults2.isLoaded());
                                   assertEquals(7, realmResults2.size());
                                   assertEquals("modified", realmResults2.get(4).getColumnString());
          @@ -645,9 +645,9 @@ public void onChange(RealmResults object) {
                   });
               }
           
          -    // simulate a use case, when the caller thread advance read, while the background thread
          +    // Simulates a use case, when the caller thread advance read, while the background thread
               // is operating on a previous version, this should retry the query on the worker thread
          -    // to deliver the results once (using the latest version of the Realm)
          +    // to deliver the results once (using the latest version of the Realm).
               @Test
               @RunTestInLooperThread
               public void findAllAsync_callerIsAdvanced() throws Throwable {
          @@ -655,15 +655,15 @@ public void findAllAsync_callerIsAdvanced() throws Throwable {
                   final Realm realm = looperThread.realm;
                   populateTestRealm(realm, 10);
           
          -        // Configure handler interceptor
          +        // Configures handler interceptor.
                   final Handler handler = new HandlerProxy(realm.handlerController) {
                       @Override
                       public boolean onInterceptInMessage(int what) {
          -                // Intercepts in order [QueryCompleted, RealmChanged, QueryUpdated]
          +                // Intercepts in order [QueryCompleted, RealmChanged, QueryUpdated].
                           int intercepts = numberOfIntercept.incrementAndGet();
                           switch (what) {
                               case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: {
          -                        // we advance the Realm so we can simulate a retry
          +                        // We advance the Realm so we can simulate a retry.
                                   if (intercepts == 1) {
                                       realm.beginTransaction();
                                       realm.createObject(AllTypes.class).setColumnLong(0);
          @@ -676,7 +676,7 @@ public boolean onInterceptInMessage(int what) {
                   };
                   realm.setHandler(handler);
           
          -        // Create async query and verify it has not been loaded.
          +        // Creates async query and verify it has not been loaded.
                   final RealmResults realmResults = realm.where(AllTypes.class)
                           .between("columnLong", 0, 4)
                           .findAllAsync();
          @@ -686,7 +686,7 @@ public boolean onInterceptInMessage(int what) {
           
                   looperThread.keepStrongReference.add(realmResults);
           
          -        // Add change listener that should only be called once
          +        // Adds change listener that should only be called once.
                   realmResults.addChangeListener(new RealmChangeListener>() {
                       @Override
                       public void onChange(RealmResults object) {
          @@ -699,14 +699,14 @@ public void onChange(RealmResults object) {
               }
           
               // UC:
          -    //   1- insert 10 objects
          -    //   2- start 2 async queries to find all objects [0-9] & objects[0-4]
          -    //   3- assert both RealmResults are empty (Worker Thread didn't complete)
          -    //   4- start a third thread to insert 2 more elements
          -    //   5- the third thread signal a REALM_CHANGE that should update all async queries
          -    //   6- when the results from step [2] completes they should be ignored, since a pending
          -    //      update (using the latest realm) for all async queries is in progress
          -    //   7- onChange notification will be triggered once
          +    //   1- Inserts 10 objects.
          +    //   2- Starts 2 async queries to find all objects [0-9] & objects[0-4].
          +    //   3- Asserts both RealmResults are empty (Worker Thread didn't complete).
          +    //   4- Starts a third thread to insert 2 more elements.
          +    //   5- The third thread signal a REALM_CHANGE that should update all async queries.
          +    //   6- When the results from step [2] completes they should be ignored, since a pending
          +    //      update (using the latest realm) for all async queries is in progress.
          +    //   7- onChange notification will be triggered once.
               @Test
               @RunTestInLooperThread
               public void findAllAsync_callerThreadBehind() throws Throwable {
          @@ -717,25 +717,25 @@ public void findAllAsync_callerThreadBehind() throws Throwable {
                   final Realm realm = looperThread.realm;
                   populateTestRealm(realm, 10);
           
          -        // Configure Handler Interceptor
          +        // Configures Handler Interceptor.
                   final Handler handler = new HandlerProxy(realm.handlerController) {
                       @Override
                       public boolean onInterceptInMessage(int what) {
                           switch (what) {
                               case HandlerControllerConstants.REALM_CHANGED: {
          -                        // should only intercept the first REALM_CHANGED coming from the
          -                        // background update thread
          +                        // Should only intercept the first REALM_CHANGED coming from the
          +                        // background update thread.
           
          -                        // swallow this message, so the caller thread
          -                        // remain behind the worker thread. This has as
          -                        // a consequence to ignore the delivered result & wait for the
          -                        // upcoming REALM_CHANGED to batch update all async queries
          +                        // Swallows this message, so the caller thread
          +                        // remains behind the worker thread. This has as
          +                        // a consequence to ignore the delivered result & waits for the
          +                        // upcoming REALM_CHANGED to batch update all async queries.
                                   return numberOfInterceptedChangeMessage.getAndIncrement() == 0;
                               }
                               case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: {
                                   if (numberOfCompletedAsyncQuery.incrementAndGet() == 2) {
          -                            // both queries have completed now (& their results should be ignored)
          -                            // now send the REALM_CHANGED event that should batch update all queries
          +                            // Both queries have completed now (& their results should be ignored)
          +                            // now sends the REALM_CHANGED event that should batch update all queries.
                                       sendEmptyMessage(HandlerControllerConstants.REALM_CHANGED);
                                   }
                               }
          @@ -746,7 +746,7 @@ public boolean onInterceptInMessage(int what) {
                   realm.setHandler(handler);
                   Realm.asyncTaskExecutor.pause();
           
          -        // Create async queries and check they haven't completed
          +        // Creates async queries and checks they haven't completed.
                   final RealmResults realmResults1 = realm.where(AllTypes.class)
                           .findAllAsync();
                   final RealmResults realmResults2 = realm.where(AllTypes.class)
          @@ -757,7 +757,7 @@ public boolean onInterceptInMessage(int what) {
                   assertEquals(0, realmResults1.size());
                   assertEquals(0, realmResults2.size());
           
          -        // advance the Realm from a background thread
          +        // Advances the Realm from a background thread.
                   new RealmBackgroundTask(looperThread.realmConfiguration) {
                       @Override
                       public void doInBackground(Realm realm) {
          @@ -770,7 +770,7 @@ public void doInBackground(Realm realm) {
                   }.awaitOrFail();
                   Realm.asyncTaskExecutor.resume();
           
          -        // Setup change listeners
          +        // Setups change listeners.
                   final Runnable signalCallbackDone = new Runnable() {
                       private AtomicInteger signalCallbackFinished = new AtomicInteger(2);
                       @Override
          @@ -813,7 +813,7 @@ public void onChange(RealmResults object) {
               // *** 'findFirst' async queries  ***
               // **********************************
           
          -    // similar UC as #testFindAllAsync using 'findFirst'
          +    // Similar UC as #testFindAllAsync using 'findFirst'.
               @Test
               @RunTestInLooperThread
               public void findFirstAsync() {
          @@ -888,8 +888,8 @@ public void onChange(AllTypes object) {
                   looperThread.realm.commitTransaction();
               }
           
          -    // finding elements [0-4] asynchronously then wait for the promise to be loaded
          -    // using a callback to be notified when the data is loaded
          +    // Finds elements [0-4] asynchronously then waits for the promise to be loaded
          +    // using a callback to be notified when the data is loaded.
               @Test
               @RunTestInLooperThread
               public void findFirstAsync_withNotification() throws Throwable {
          @@ -919,7 +919,7 @@ public void onChange(AllTypes object) {
                   }
               }
           
          -    // similar UC as #testForceLoadAsync using 'findFirst'
          +    // Similar UC as #testForceLoadAsync using 'findFirst'.
               @Test
               @RunTestInLooperThread
               public void findFirstAsync_forceLoad() throws Throwable {
          @@ -938,16 +938,16 @@ public void findFirstAsync_forceLoad() throws Throwable {
                   looperThread.testComplete();
               }
           
          -    // similar UC as #testFindAllAsyncRetry using 'findFirst'
          +    // Similar UC as #testFindAllAsyncRetry using 'findFirst'.
               // UC:
          -    //   1- insert 10 objects
          -    //   2- start an async query to find object [0-4]
          -    //   3- assert current RealmResults is empty (Worker Thread didn't complete)
          -    //   4- when the worker thread complete, advance the Realm
          -    //   5- the caller thread is ahead of the result provided by the worker thread
          -    //   6- retry automatically the async query
          -    //   7- the returned RealmResults is now in the same version as the caller thread
          -    //   8- the notification should be called once (when we retry automatically we shouldn't
          +    //   1- Inserts 10 objects.
          +    //   2- Starts an async query to find object [0-4].
          +    //   3- Asserts current RealmResults is empty (Worker Thread didn't complete).
          +    //   4- When the worker thread completes, advances the Realm.
          +    //   5- The caller thread is ahead of the result provided by the worker thread.
          +    //   6- Retries automatically the async query.
          +    //   7- The returned RealmResults is now in the same version as the caller thread.
          +    //   8- The notification should be called once (when we retry automatically we shouldn't
               //      notify the user).
               @Test
               @RunTestInLooperThread
          @@ -956,7 +956,7 @@ public void findFirstAsync_retry() throws Throwable {
                   final Realm realm = looperThread.realm;
                   populateTestRealm(realm, 10);
           
          -        // Configure interceptor handler
          +        // Configures interceptor handler.
                   final Handler handler = new HandlerProxy(realm.handlerController) {
                       @Override
                       public boolean onInterceptInMessage(int what) {
          @@ -964,7 +964,7 @@ public boolean onInterceptInMessage(int what) {
                           switch (what) {
                               case HandlerControllerConstants.COMPLETED_ASYNC_REALM_OBJECT: {
                                   if (intercepts == 1) {
          -                            // we advance the Realm so we can simulate a retry
          +                            // We advance the Realm so we can simulate a retry.
                                       realm.beginTransaction();
                                       realm.delete(AllTypes.class);
                                       AllTypes object = realm.createObject(AllTypes.class);
          @@ -979,7 +979,7 @@ public boolean onInterceptInMessage(int what) {
                   };
                   realm.setHandler(handler);
           
          -        // Create a async query and verify it is not still loaded.
          +        // Creates a async query and verifies it is not still loaded.
                   final AllTypes realmResults = realm.where(AllTypes.class)
                           .between("columnLong", 4, 6)
                           .findFirstAsync();
          @@ -992,7 +992,7 @@ public boolean onInterceptInMessage(int what) {
                   } catch (IllegalStateException ignored) {
                   }
           
          -        // Add change listener that should only be called once after the retry completed.
          +        // Adds change listener that should only be called once after the retry completed.
                   looperThread.keepStrongReference.add(realmResults);
                   realmResults.addChangeListener(new RealmChangeListener() {
                       @Override
          @@ -1010,7 +1010,7 @@ public void onChange(AllTypes object) {
               // *** 'findAllSorted' async queries  ***
               // **************************************
           
          -    // similar UC as #testFindAllAsync using 'findAllSorted'
          +    // Similar UC as #testFindAllAsync using 'findAllSorted'.
               @Test
               @RunTestInLooperThread
               public void findAllSortedAsync() throws Throwable {
          @@ -1040,24 +1040,24 @@ public void onChange(RealmResults object) {
               }
           
           
          -    // finding elements [4-8] asynchronously then wait for the promise to be loaded
          -    // using a callback to be notified when the data is loaded
          +    // Finds elements [4-8] asynchronously then waits for the promise to be loaded
          +    // using a callback to be notified when the data is loaded.
               @Test
               @RunTestInLooperThread
               public void findAllSortedAsync_retry() throws Throwable {
                   final AtomicInteger numberOfIntercept = new AtomicInteger(0);
                   final Realm realm = looperThread.realm;
           
          -        // 1. Populate the Realm without triggering a RealmChangeEvent.
          +        // 1. Populates the Realm without triggering a RealmChangeEvent.
                   realm.setAutoRefresh(false);
                   populateTestRealm(realm, 10);
                   realm.setAutoRefresh(true);
           
          -        // 2. Configure proxy handler to intercept messages
          +        // 2. Configures proxy handler to intercept messages.
                   final Handler handler = new HandlerProxy(realm.handlerController) {
                       @Override
                       public boolean onInterceptInMessage(int what) {
          -                // In order [QueryCompleted, RealmChanged, QueryUpdated]
          +                // In order [QueryCompleted, RealmChanged, QueryUpdated].
                           int intercepts = numberOfIntercept.incrementAndGet();
                           switch (what) {
                               case HandlerControllerConstants.COMPLETED_ASYNC_REALM_RESULTS: {
          @@ -1076,7 +1076,7 @@ public boolean onInterceptInMessage(int what) {
                   };
                   realm.setHandler(handler);
           
          -        // 3. This will add a task to the paused asyncTaskExecutor
          +        // 3. This will add a task to the paused asyncTaskExecutor.
                   final RealmResults realmResults = realm.where(AllTypes.class)
                           .between("columnLong", 4, 8)
                           .findAllSortedAsync("columnString", Sort.ASCENDING);
          @@ -1097,15 +1097,15 @@ public void onChange(RealmResults object) {
                   });
               }
           
          -    // similar UC as #testFindAllAsyncBatchUpdate using 'findAllSorted'
          +    // Similar UC as #testFindAllAsyncBatchUpdate using 'findAllSorted'.
               // UC:
          -    //   1- insert 10 objects
          -    //   2- start 2 async queries to find all objects [0-9] & objects[0-4]
          -    //   3- assert both RealmResults are empty (Worker Thread didn't complete)
          -    //   4- the queries will complete with the same version as the caller thread
          -    //   5- using a background thread update the Realm
          -    //   6- now REALM_CHANGED will trigger a COMPLETED_UPDATE_ASYNC_QUERIES that should update all queries
          -    //   7- callbacks are notified with the latest results (called twice overall)
          +    //   1- Inserts 10 objects.
          +    //   2- Starts 2 async queries to find all objects [0-9] & objects[0-4].
          +    //   3- Asserts both RealmResults are empty (Worker Thread didn't complete).
          +    //   4- The queries will complete with the same version as the caller thread.
          +    //   5- Using a background thread update the Realm.
          +    //   6- Now REALM_CHANGED will trigger a COMPLETED_UPDATE_ASYNC_QUERIES that should update all queries.
          +    //   7- Callbacks are notified with the latest results (called twice overall).
               @Test
               @RunTestInLooperThread
               public void findAllSortedAsync_batchUpdate() {
          @@ -1114,12 +1114,12 @@ public void findAllSortedAsync_batchUpdate() {
                   final AtomicInteger numberOfIntercept = new AtomicInteger(0);
                   Realm realm = looperThread.realm;
           
          -        // 1. Add initial 10 objects
          +        // 1. Adds initial 10 objects.
                   realm.setAutoRefresh(false);
                   populateTestRealm(realm, 10);
                   realm.setAutoRefresh(true);
           
          -        // 2. Configure interceptor
          +        // 2. Configures interceptor.
                   final Handler handler = new HandlerProxy(realm.handlerController) {
                       @Override
                       public boolean onInterceptInMessage(int what) {
          @@ -1153,21 +1153,21 @@ public void run() {
                   };
                   realm.setHandler(handler);
           
          -        // 3. Create 2 async queries
          +        // 3. Creates 2 async queries.
                   final RealmResults realmResults1 = realm.where(AllTypes.class)
                           .findAllSortedAsync("columnString", Sort.ASCENDING);
                   final RealmResults realmResults2 = realm.where(AllTypes.class)
                           .between("columnLong", 0, 4)
                           .findAllSortedAsync("columnString", Sort.DESCENDING);
           
          -        // 4. Assert that queries have not finished
          +        // 4. Asserts that queries have not finished.
                   assertFalse(realmResults1.isLoaded());
                   assertFalse(realmResults2.isLoaded());
                   assertEquals(0, realmResults1.size());
                   assertEquals(0, realmResults2.size());
           
          -        // 5. Change listeners will be called twice. Once when the first query completely and then
          -        // when the background thread has completed, notifying this thread to rerun and then receive
          +        // 5. Change listeners will be called twice. Once when the first query has completed and then
          +        // when the background thread has completed, notifies this thread to rerun and then receives
                   // the updated results.
                   final Runnable signalCallbackDone = new Runnable() {
                       private AtomicInteger signalCallbackFinished = new AtomicInteger(2);
          @@ -1188,13 +1188,13 @@ public void run() {
                       @Override
                       public void onChange(RealmResults object) {
                           switch (numberOfNotificationsQuery1.incrementAndGet()) {
          -                    case 1: { // first callback invocation
          +                    case 1: { // First callback invocation
                                   assertTrue(realmResults1.isLoaded());
                                   assertEquals(10, realmResults1.size());
                                   assertEquals("test data 4", realmResults1.get(4).getColumnString());
                                   break;
                               }
          -                    case 2: { // second callback
          +                    case 2: { // Second callback
                                   assertTrue(realmResults1.isLoaded());
                                   assertEquals(12, realmResults1.size());
                                   assertEquals("modified", realmResults1.get(2).getColumnString());
          @@ -1209,13 +1209,13 @@ public void onChange(RealmResults object) {
                       @Override
                       public void onChange(RealmResults object) {
                           switch (numberOfNotificationsQuery2.incrementAndGet()) {
          -                    case 1: { // first callback invocation
          +                    case 1: { // First callback invocation
                                   assertTrue(realmResults2.isLoaded());
                                   assertEquals(5, realmResults2.size());
                                   assertEquals("test data 4", realmResults2.get(0).getColumnString());
                                   break;
                               }
          -                    case 2: { // second callback
          +                    case 2: { // Second callback
                                   assertTrue(realmResults2.isLoaded());
                                   assertEquals(7, realmResults2.size());
                                   assertEquals("modified", realmResults2.get(4).getColumnString());
          @@ -1227,15 +1227,15 @@ public void onChange(RealmResults object) {
                   });
               }
           
          -    // similar UC as #testFindAllAsyncBatchUpdate using 'findAllSortedMulti'
          +    // Similar UC as #testFindAllAsyncBatchUpdate using 'findAllSortedMulti'.
               // UC:
          -    //   1- insert 10 objects
          -    //   2- start 2 async queries to find all objects [0-9] & objects[0-4]
          -    //   3- assert both RealmResults are empty (Worker Thread didn't complete)
          -    //   4- the queries will complete with the same version as the caller thread
          -    //   5- using a background thread update the Realm
          -    //   6- now REALM_CHANGED will trigger a COMPLETED_UPDATE_ASYNC_QUERIES that should update all queries
          -    //   7- callbacks are notified with the latest results (called twice overall)
          +    //   1- Inserts 10 objects.
          +    //   2- Starts 2 async queries to find all objects [0-9] & objects[0-4].
          +    //   3- Asserts both RealmResults are empty (Worker Thread didn't complete).
          +    //   4- The queries will complete with the same version as the caller thread.
          +    //   5- Using a background thread updates the Realm.
          +    //   6- Now REALM_CHANGED will trigger a COMPLETED_UPDATE_ASYNC_QUERIES that should update all queries.
          +    //   7- Callbacks are notified with the latest results (called twice overall).
               @Test
               @RunTestInLooperThread
               public void findAllSortedAsync_multipleFields_batchUpdate() throws Throwable {
          @@ -1244,7 +1244,7 @@ public void findAllSortedAsync_multipleFields_batchUpdate() throws Throwable {
                   final AtomicInteger numberOfIntercept = new AtomicInteger(0);
                   Realm realm = looperThread.realm;
           
          -        // 1. Add initial objects
          +        // 1. Adds initial objects.
                   realm.setAutoRefresh(false);
                   realm.beginTransaction();
                   for (int i = 0; i < 5; ) {
          @@ -1259,7 +1259,7 @@ public void findAllSortedAsync_multipleFields_batchUpdate() throws Throwable {
                   realm.commitTransaction();
                   realm.setAutoRefresh(true);
           
          -        // 2. Configure interceptor
          +        // 2. Configures interceptor.
                   final Handler handler = new HandlerProxy(realm.handlerController) {
                       @Override
                       public boolean onInterceptInMessage(int what) {
          @@ -1292,14 +1292,14 @@ public void doInBackground(Realm realm) {
                   };
                   realm.setHandler(handler);
           
          -        // 3. Create 2 async queries
          +        // 3. Creates 2 async queries.
                   final RealmResults realmResults1 = realm.where(AllTypes.class)
                           .findAllSortedAsync("columnString", Sort.ASCENDING, "columnLong", Sort.DESCENDING);
                   final RealmResults realmResults2 = realm.where(AllTypes.class)
                           .between("columnLong", 0, 5)
                           .findAllSortedAsync("columnString", Sort.DESCENDING, "columnLong", Sort.ASCENDING);
           
          -        // 4. Assert that queries have not finished
          +        // 4. Asserts that queries have not finished.
                   assertFalse(realmResults1.isLoaded());
                   assertFalse(realmResults2.isLoaded());
                   assertEquals(0, realmResults1.size());
          @@ -1309,8 +1309,8 @@ public void doInBackground(Realm realm) {
                   assertEquals(0, realmResults1.size());
                   assertEquals(0, realmResults2.size());
           
          -        // 5. Change listeners will be called twice. Once when the first query completely and then
          -        // when the background thread has completed, notifying this thread to rerun and then receive
          +        // 5. Changes listeners will be called twice. Once when the first query has completed and then
          +        // when the background thread has completed, notifies this thread to rerun and then receives
                   // the updated results.
                   final Runnable signalCallbackDone = new Runnable() {
                       private AtomicInteger signalCallbackFinished = new AtomicInteger(2);
          @@ -1332,7 +1332,7 @@ public void run() {
                       @Override
                       public void onChange(RealmResults object) {
                           switch (numberOfNotificationsQuery1.incrementAndGet()) {
          -                    case 1: // first callback invocation
          +                    case 1: // First callback invocation
                                   assertTrue(realmResults1.isLoaded());
                                   assertEquals(10, realmResults1.size());
           
          @@ -1360,14 +1360,14 @@ public void onChange(RealmResults object) {
                                   assertEquals(1, realmResults1.get(9).getColumnLong());
                                   break;
           
          -                    case 2: // second callback
          +                    case 2: // Second callback
                                   assertTrue(realmResults1.isLoaded());
                                   assertEquals(12, realmResults1.size());
          -                        //first
          +                        // First
                                   assertEquals("data 0", realmResults1.get(0).getColumnString());
                                   assertEquals(3, realmResults1.get(0).getColumnLong());
           
          -                        //last
          +                        // Last
                                   assertEquals("data 5", realmResults1.get(11).getColumnString());
                                   assertEquals(0, realmResults1.get(11).getColumnLong());
           
          @@ -1381,7 +1381,7 @@ public void onChange(RealmResults object) {
                       @Override
                       public void onChange(RealmResults object) {
                           switch (numberOfNotificationsQuery2.incrementAndGet()) {
          -                    case 1: // first callback invocation
          +                    case 1: // First callback invocation
                                   assertTrue(realmResults2.isLoaded());
                                   assertEquals(10, realmResults2.size());
           
          @@ -1409,7 +1409,7 @@ public void onChange(RealmResults object) {
                                   assertEquals(3, realmResults2.get(9).getColumnLong());
                                   break;
           
          -                    case 2: // second callback
          +                    case 2: // Second callback
                                   assertTrue(realmResults2.isLoaded());
                                   assertEquals(12, realmResults2.size());
           
          @@ -1429,25 +1429,25 @@ public void onChange(RealmResults object) {
                   });
               }
           
          -    // make sure the notification listener does not leak the enclosing class
          +    // Makes sure the notification listener does not leak the enclosing class
               // if unregistered properly.
               @Test
               @RunTestInLooperThread
               public void listenerShouldNotLeak() {
                   populateTestRealm(looperThread.realm, 10);
           
          -        // simulate the ActivityManager by creating 1 instance responsible
          -        // of attaching an onChange listener, then simulate a configuration
          +        // Simulates the ActivityManager by creating 1 instance responsible
          +        // of attaching an onChange listener, then simulates a configuration
                   // change (ex: screen rotation), this change will create a new instance.
          -        // we make sure that the GC enqueue the reference of the destroyed instance
          -        // which indicate no memory leak
          +        // We make sure that the GC enqueues the reference of the destroyed instance
          +        // which indicate no memory leak.
                   MockActivityManager mockActivityManager =
                           MockActivityManager.newInstance(looperThread.realm.getConfiguration());
           
                   mockActivityManager.sendConfigurationChange();
           
                   assertEquals(1, mockActivityManager.numberOfInstances());
          -        // remove GC'd reference & assert that one instance should remain
          +        // Removes GC'd reference & asserts that one instance should remain.
                   Iterator>, RealmQuery>> iterator =
                           looperThread.realm.handlerController.asyncRealmResults.entrySet().iterator();
                   while (iterator.hasNext()) {
          @@ -1459,7 +1459,7 @@ public void listenerShouldNotLeak() {
                   }
           
                   assertEquals(1, looperThread.realm.handlerController.asyncRealmResults.size());
          -        mockActivityManager.onStop();// to close the Realm
          +        mockActivityManager.onStop();// To close the Realm.
                   looperThread.testComplete();
               }
           
          @@ -1486,8 +1486,8 @@ public void onChange(RealmResults object) {
                   looperThread.keepStrongReference.add(allTypesAsync);
               }
           
          -    // keep advancing the Realm by sending 1 commit for each frame (16ms)
          -    // the async queries should keep up with the modification
          +    // Keeps advancing the Realm by sending 1 commit for each frame (16ms).
          +    // The async queries should keep up with the modification.
               @Test
               @RunTestInLooperThread
               public void stressTestBackgroundCommits() throws Throwable {
          @@ -1496,7 +1496,7 @@ public void stressTestBackgroundCommits() throws Throwable {
                   final long[] latestLongValue = new long[1];
                   final float[] latestFloatValue = new float[1];
           
          -        // start a background thread that pushes a commit every 16ms
          +        // Starts a background thread that pushes a commit every 16ms.
                   final Thread backgroundThread = new Thread() {
                       @Override
                       public void run() {
          @@ -1511,7 +1511,7 @@ public void run() {
                               object.setColumnLong(latestLongValue[0]);
                               backgroundThreadRealm.commitTransaction();
           
          -                    // Wait 16ms. before adding the next commit.
          +                    // Waits 16ms. Before adding the next commit.
                               SystemClock.sleep(16);
                           }
                           backgroundThreadRealm.close();
          @@ -1550,7 +1550,7 @@ public void run() {
               public void distinctAsync() throws Throwable {
                   Realm realm = looperThread.realm;
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctAsync("indexBoolean");
          @@ -1651,7 +1651,7 @@ public void onChange(RealmResults results) {
               public void distinctAsync_notIndexedFields() throws Throwable {
                   Realm realm = looperThread.realm;
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   for (String fieldName : new String[]{"Boolean", "Long", "Date", "String"}) {
          @@ -1670,7 +1670,7 @@ public void distinctAsync_notIndexedFields() throws Throwable {
               public void distinctAsync_noneExistingField() throws Throwable {
                   Realm realm = looperThread.realm;
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   try {
          @@ -1696,7 +1696,7 @@ public void batchUpdateDifferentTypeOfQueries() {
                       allTypes.setColumnString("data " + (++i % 3));
                   }
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   realm.commitTransaction();
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
          @@ -1791,8 +1791,8 @@ public void onChange(RealmResults object) {
                       }
                   });
           
          -        // wait for the queries to completes then send a commit from
          -        // another thread to trigger a batch update of the 4 queries
          +        // Waits for the queries to complete then sends a commit from
          +        // another thread to trigger a batch update of the 4 queries.
                   new Thread() {
                       @Override
                       public void run() {
          @@ -1814,7 +1814,7 @@ public void run() {
                   }.start();
               }
           
          -    // this test make sure that Async queries update when using link
          +    // This test makes sure that Async queries update when using link.
               @Test
               @RunTestInLooperThread
               public void queryingLinkHandover() throws Throwable {
          @@ -1864,15 +1864,15 @@ public void doInBackground(Realm realm) {
                   });
               }
           
          -    // Make sure we don't get the run into the IllegalStateException
          +    // Makes sure we don't get the run into the IllegalStateException.
               // (Caller thread behind the worker thread)
               // Scenario:
          -    // - Caller thread is in version 1, start an asyncFindFirst
          -    // - Another thread advance the Realm, now the latest version = 2
          -    // - The worker thread should query against version 1 not version 2
          -    // otherwise the caller thread wouldn't be able to import the result
          +    // - Caller thread is in version 1, starts an asyncFindFirst.
          +    // - Another thread advances the Realm, now the latest version = 2.
          +    // - The worker thread should query against version 1 not version 2.
          +    // Otherwise the caller thread wouldn't be able to import the result.
               // - The notification mechanism will guarantee that the REALM_CHANGE triggered by
          -    // the background thread, will update the caller thread (advancing it to version 2)
          +    // the background thread, will update the caller thread (advancing it to version 2).
               @Test
               @RunTestInLooperThread
               public void testFindFirstUsesCallerThreadVersion() throws Throwable {
          @@ -1892,12 +1892,12 @@ public void onChange(AllTypes object) {
                       }
                   });
           
          -        // advance the background Realm
          +        // Advances the background Realm.
                   new Thread() {
                       @Override
                       public void run() {
                           Realm bgRealm = Realm.getInstance(looperThread.realmConfiguration);
          -                // Advancing the Realm without generating notifications
          +                // Advances the Realm without generating notifications.
                           bgRealm.sharedRealm.beginTransaction();
                           bgRealm.sharedRealm.commitTransaction();
                           Realm.asyncTaskExecutor.resume();
          @@ -1908,7 +1908,7 @@ public void run() {
               }
           
               // Test case for https://github.com/realm/realm-java/issues/2417
          -    // Ensure that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault.
          +    // Ensures that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault.
               @Test
               @UiThreadTest
               public void badVersion_findAll() throws NoSuchFieldException, IllegalAccessException {
          @@ -1943,7 +1943,7 @@ public void execute(Realm realm) {
               }
           
               // Test case for https://github.com/realm/realm-java/issues/2417
          -    // Ensure that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault.
          +    // Ensures that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault.
               @Test
               @UiThreadTest
               public void badVersion_findAllSortedAsync() throws NoSuchFieldException, IllegalAccessException {
          @@ -1976,7 +1976,7 @@ public void execute(Realm realm) {
               }
           
               // Test case for https://github.com/realm/realm-java/issues/2417
          -    // Ensure that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault.
          +    // Ensures that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault.
               @Test
               @UiThreadTest
               public void badVersion_distinct() throws NoSuchFieldException, IllegalAccessException {
          @@ -2010,14 +2010,14 @@ public void execute(Realm realm) {
               }
           
               // Test case for https://github.com/realm/realm-java/issues/2417
          -    // Ensure that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault.
          +    // Ensures that a UnreachableVersion exception during handover doesn't crash the app or cause a segfault.
               @Test
               @RunTestInLooperThread
               public void badVersion_syncTransaction() throws NoSuchFieldException, IllegalAccessException {
                   TestHelper.replaceRealmThreadExecutor(RealmThreadPoolExecutor.newSingleThreadExecutor());
                   Realm realm = looperThread.realm;
           
          -        // 1. Make sure that async query is not started
          +        // 1. Makes sure that async query is not started.
                   final RealmResults result = realm.where(AllTypes.class).findAllSortedAsync(AllTypes.FIELD_STRING);
                   looperThread.keepStrongReference.add(result);
                   result.addChangeListener(new RealmChangeListener>() {
          @@ -2025,7 +2025,6 @@ public void badVersion_syncTransaction() throws NoSuchFieldException, IllegalAcc
                       public void onChange(RealmResults object) {
                           // 4. The commit in #2, should result in a refresh being triggered, which means this callback will
                           // be notified once the updated async queries has run.
          -                // with the correct
                           assertTrue(result.isValid());
                           assertTrue(result.isLoaded());
                           assertEquals(1, result.size());
          @@ -2033,18 +2032,18 @@ public void onChange(RealmResults object) {
                       }
                   });
           
          -        // 2. Advance the calle Realm, invalidating the version in the handover object
          +        // 2. Advances the callee Realm, invalidating the version in the handover object.
                   realm.beginTransaction();
                   realm.createObject(AllTypes.class);
                   realm.commitTransaction();
           
          -        // 3. The async query should now (hopefully) fail with a BadVersion
          +        // 3. The async query should now (hopefully) fail with a BadVersion.
                   result.load();
                   TestHelper.resetRealmThreadExecutor();
               }
           
          -    // handlerController#emptyAsyncRealmObject is accessed from different threads
          -    // make sure that we iterate over it safely without any race condition (ConcurrentModification)
          +    // handlerController#emptyAsyncRealmObject is accessed from different threads.
          +    // Makes sure that we iterate over it safely without any race condition (ConcurrentModification).
               @Test
               @UiThreadTest
               public void concurrentModificationEmptyAsyncRealmObject() {
          @@ -2065,8 +2064,8 @@ public void concurrentModificationEmptyAsyncRealmObject() {
                   final WeakReference weakReference2 = new WeakReference((RealmObjectProxy)dog2);
           
                   final RealmQuery dummyQuery = RealmQuery.createQuery(realm, Dog.class);
          -        // Initialize the emptyAsyncRealmObject map, to make sure that iterating is safe
          -        // even if we modify the map from a background thread (in case of an empty findFirstAsync)
          +        // Initializes the emptyAsyncRealmObject map, to make sure that iterating is safe
          +        // even if we modify the map from a background thread (in case of an empty findFirstAsync).
                   realm.handlerController.emptyAsyncRealmObject.put(weakReference1, dummyQuery);
           
                   final CountDownLatch dogAddFromBg = new CountDownLatch(1);
          @@ -2074,13 +2073,13 @@ public void concurrentModificationEmptyAsyncRealmObject() {
                   AtomicBoolean fireOnce = new AtomicBoolean(true);
                   while (iterator.hasNext()) {
                       Dog next = (Dog) iterator.next().getKey().get();
          -            // add a new Dog from a background thread
          +            // Adds a new Dog from a background thread.
                       if (fireOnce.compareAndSet(true, false)) {
                           new Thread() {
                               @Override
                               public void run() {
          -                        // add a WeakReference to simulate an empty row using a findFirstAsync
          -                        // this is added on an Executor thread, hence the dedicated thread
          +                        // Adds a WeakReference to simulate an empty row using a findFirstAsync.
          +                        // This is added on an Executor thread, hence the dedicated thread.
                                   realm.handlerController.emptyAsyncRealmObject.put(weakReference2, dummyQuery);
                                   dogAddFromBg.countDown();
                               }
          @@ -2093,8 +2092,8 @@ public void run() {
                   realm.close();
               }
           
          -    // handlerController#realmObjects is accessed from different threads
          -    // make sure that we iterate over it safely without any race condition (ConcurrentModification)
          +    // handlerController#realmObjects is accessed from different threads.
          +    // Makes sure that we iterate over it safely without any race condition (ConcurrentModification).
               @Test
               @UiThreadTest
               public void concurrentModificationRealmObjects() {
          @@ -2121,7 +2120,7 @@ public void concurrentModificationRealmObjects() {
                   AtomicBoolean fireOnce = new AtomicBoolean(true);
                   while (iterator.hasNext()) {
                       Dog next = (Dog) iterator.next().getKey().get();
          -            // add a new Dog from a background thread
          +            // Adds a new Dog from a background thread.
                       if (fireOnce.compareAndSet(true, false)) {
                           new Thread() {
                               @Override
          @@ -2139,14 +2138,14 @@ public void run() {
                   realm.close();
               }
           
          -    // This test reproduce the issue in https://secure.helpscout.net/conversation/244053233/6163/?folderId=366141
          -    // First it creates 512 async queries, then trigger a transaction to make the queries gets update with
          +    // This test reproduces the issue in https://secure.helpscout.net/conversation/244053233/6163/?folderId=366141
          +    // First it creates 512 async queries, then triggers a transaction to make the queries gets update with
               // nativeBatchUpdateQueries. It should not exceed the limits of local ref map size in JNI.
               @Test
               @RunTestInLooperThread
               public void batchUpdate_localRefIsDeletedInLoopOfNativeBatchUpdateQueries() {
                   final Realm realm = looperThread.realm;
          -        // For Android, the size of local ref map is 512. Use 1024 for more pressure.
          +        // For Android, the size of local ref map is 512. Uses 1024 for more pressure.
                   final int TEST_COUNT = 1024;
                   final AtomicBoolean updatesTriggered = new AtomicBoolean(false);
                   // The first time onChange gets called for every results.
          @@ -2167,7 +2166,7 @@ public void onChange(RealmResults element) {
                           } else {
                               int count  = firstOnChangeCounter.addAndGet(1);
                               if (count == TEST_COUNT) {
          -                        // Step 3: Commit the transaction to trigger queries updates.
          +                        // Step 3: Commits the transaction to trigger queries updates.
                                   updatesTriggered.set(true);
                                   realm.executeTransactionAsync(new Realm.Transaction() {
                                       @Override
          @@ -2176,7 +2175,7 @@ public void execute(Realm realm) {
                                       }
                                   });
                               } else {
          -                        // Step 2: Create 2nd - TEST_COUNT queries.
          +                        // Step 2: Creates 2nd - TEST_COUNT queries.
                                   RealmResults results = realm.where(AllTypes.class).findAllAsync();
                                   results.addChangeListener(this);
                                   looperThread.keepStrongReference.add(results);
          @@ -2184,7 +2183,7 @@ public void execute(Realm realm) {
                           }
                       }
                   };
          -        // Step 1. Create first async to kick the test start.
          +        // Step 1. Creates first async to kick the test start.
                   RealmResults results = realm.where(AllTypes.class).findAllAsync();
                   results.addChangeListener(listener);
                   looperThread.keepStrongReference.add(results);
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java
          index c8213e7d9c..99375d1b2d 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java
          @@ -56,7 +56,7 @@ public void setUp() {
                   context = InstrumentationRegistry.getInstrumentation().getContext();
               }
           
          -    // Test that the closed Realm isn't kept in the Realm instance cache
          +    // Tests that the closed Realm isn't kept in the Realm instance cache.
               @Test
               public void typedRealmCacheIsCleared() {
                   Realm typedRealm = Realm.getInstance(defaultConfig);
          @@ -67,7 +67,7 @@ public void typedRealmCacheIsCleared() {
           
                   Realm typedRealm1 = Realm.getInstance(defaultConfig);
                   try {
          -            assertFalse(typedRealm == typedRealm1); // Must be different instance
          +            assertFalse(typedRealm == typedRealm1); // Must be different instance.
                       // If cache isn't cleared this would crash because of a closed shared group.
                       assertEquals(0, typedRealm1.where(AllTypes.class).count());
                   } finally {
          @@ -75,7 +75,7 @@ public void typedRealmCacheIsCleared() {
                   }
               }
           
          -    // Test that the closed DynamicRealms isn't kept in the DynamicRealm instance cache
          +    // Tests that the closed DynamicRealms isn't kept in the DynamicRealm instance cache.
               @Test
               public void dynamicRealmCacheIsCleared() {
                   DynamicRealm dynamicRealm = DynamicRealm.getInstance(defaultConfig);
          @@ -102,13 +102,13 @@ public void getInstanceClearsCacheWhenFailed() {
                   RealmConfiguration configB = configFactory.createConfiguration(REALM_NAME,
                           TestHelper.getRandomKey(43));
           
          -        Realm realm = Realm.getInstance(configA); // Create starting Realm with key1
          +        Realm realm = Realm.getInstance(configA); // Creates starting Realm with key 1.
                   realm.close();
                   try {
          -            Realm.getInstance(configB); // Try to open with key 2
          +            Realm.getInstance(configB); // Tries to open with key 2.
                   } catch (RealmFileException expected) {
                       assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR);
          -            // Delete Realm so key 2 works. This should work as a Realm shouldn't be cached
          +            // Deletes Realm so key 2 works. This should work as a Realm shouldn't be cached
                       // if initialization failed.
                       assertTrue(Realm.deleteRealm(configA));
                       realm = Realm.getInstance(configB);
          @@ -128,7 +128,7 @@ public void realmCache() {
                   }
               }
           
          -    // We should not cache wrong configurations
          +    // We should not cache wrong configurations.
               @Test
               public void dontCacheWrongConfigurations() throws IOException {
                   Realm testRealm;
          @@ -150,7 +150,7 @@ public void dontCacheWrongConfigurations() throws IOException {
                           .schema(StringOnly.class)
                           .build();
           
          -        // Open Realm with wrong key
          +        // Opens Realm with wrong key.
                   try {
                       Realm.getInstance(wrongConfig);
                       fail();
          @@ -158,7 +158,7 @@ public void dontCacheWrongConfigurations() throws IOException {
                       assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR);
                   }
           
          -        // Try again with proper key
          +        // Tries again with proper key.
                   testRealm = Realm.getInstance(rightConfig);
                   assertNotNull(testRealm);
                   testRealm.close();
          @@ -180,7 +180,7 @@ public void deletingRealmAlsoClearsConfigurationCache() throws IOException {
                           .schema(StringOnly.class)
                           .build();
           
          -        // 1. Write a copy of the encrypted Realm to a new file
          +        // 1. Writes a copy of the encrypted Realm to a new file.
                   Realm testRealm = Realm.getInstance(config);
                   File copiedRealm = new File(config.getRealmDirectory(), "encrypted-copy.realm");
                   if (copiedRealm.exists()) {
          @@ -189,13 +189,13 @@ public void deletingRealmAlsoClearsConfigurationCache() throws IOException {
                   testRealm.writeEncryptedCopyTo(copiedRealm, newPassword);
                   testRealm.close();
           
          -        // 2. Delete the old Realm.
          +        // 2. Deletes the old Realm.
                   Realm.deleteRealm(config);
           
          -        // 3. Rename the new file to the old file name.
          +        // 3. Renames the new file to the old file name.
                   assertTrue(copiedRealm.renameTo(new File(config.getRealmDirectory(), REALM_NAME)));
           
          -        // 4. Try to open the file again with the new password
          +        // 4. Tries to open the file again with the new password.
                   // If the configuration cache wasn't cleared this would fail as we would detect two
                   // configurations with 2 different passwords pointing to the same file.
                   RealmConfiguration newConfig = configFactory.createConfigurationBuilder()
          @@ -262,17 +262,17 @@ public void run() {
           
               @Test
               public void releaseCacheInOneThread() {
          -        // Test release typed Realm instance
          +        // Tests release typed Realm instance.
                   Realm realmA = RealmCache.createRealmOrGetFromCache(defaultConfig, Realm.class);
                   Realm realmB = RealmCache.createRealmOrGetFromCache(defaultConfig, Realm.class);
                   RealmCache.release(realmA);
                   assertNotNull(realmA.sharedRealm);
                   RealmCache.release(realmB);
                   assertNull(realmB.sharedRealm);
          -        // No crash but warning in the log
          +        // No crash but warning in the log.
                   RealmCache.release(realmB);
           
          -        // Test release dynamic Realm instance
          +        // Tests release dynamic Realm instance.
                   DynamicRealm dynamicRealmA = RealmCache.createRealmOrGetFromCache(defaultConfig,
                           DynamicRealm.class);
                   DynamicRealm dynamicRealmB = RealmCache.createRealmOrGetFromCache(defaultConfig,
          @@ -281,10 +281,10 @@ public void releaseCacheInOneThread() {
                   assertNotNull(dynamicRealmA.sharedRealm);
                   RealmCache.release(dynamicRealmB);
                   assertNull(dynamicRealmB.sharedRealm);
          -        // No crash but warning in the log
          +        // No crash but warning in the log.
                   RealmCache.release(dynamicRealmB);
           
          -        // Test both typed Realm and dynamic Realm in same thread
          +        // Tests both typed Realm and dynamic Realm in same thread.
                   realmA = RealmCache.createRealmOrGetFromCache(defaultConfig, Realm.class);
                   dynamicRealmA = RealmCache.createRealmOrGetFromCache(defaultConfig, DynamicRealm.class);
                   RealmCache.release(realmA);
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java
          index 2763ab5b16..0986180f0c 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java
          @@ -225,7 +225,7 @@ public void constructBuilder_versionLessThanDiscVersionThrows() {
           
               @Test
               public void constructBuilder_versionEqualWhenSchemaChangesThrows() {
          -        // Create initial Realm
          +        // Creates initial Realm.
                   RealmConfiguration config = new RealmConfiguration.Builder(context)
                           .directory(configFactory.getRoot())
                           .schemaVersion(42)
          @@ -233,7 +233,7 @@ public void constructBuilder_versionEqualWhenSchemaChangesThrows() {
                           .build();
                   Realm.getInstance(config).close();
           
          -        // Create new instance with a configuration containing another schema
          +        // Creates new instance with a configuration containing another schema.
                   try {
                       config = new RealmConfiguration.Builder(context)
                               .directory(configFactory.getRoot())
          @@ -271,14 +271,14 @@ public void migration_nullThrows() {
           
               @Test
               public void modules_nonRealmModulesThrows() {
          -        // Test first argument
          +        // Tests first argument.
                   try {
                       new RealmConfiguration.Builder(context).modules(new Object());
                       fail();
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // Test second argument
          +        // Tests second argument.
                   try {
                       new RealmConfiguration.Builder(context).modules(Realm.getDefaultModule(), new Object());
                       fail();
          @@ -334,7 +334,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
           
               @Test
               public void deleteRealmIfMigrationNeeded() {
          -        // Populate v0 of a Realm with an object
          +        // Populates v0 of a Realm with an object.
                   RealmConfiguration config = new RealmConfiguration.Builder(context)
                           .directory(configFactory.getRoot())
                           .schema(Dog.class)
          @@ -348,7 +348,7 @@ public void deleteRealmIfMigrationNeeded() {
                   assertEquals(1, realm.where(Dog.class).count());
                   realm.close();
           
          -        // Change schema and verify that Realm has been cleared
          +        // Changes schema and verifies that Realm has been cleared.
                   config = new RealmConfiguration.Builder(context)
                           .directory(configFactory.getRoot())
                           .schema(Owner.class, Dog.class)
          @@ -363,7 +363,7 @@ public void deleteRealmIfMigrationNeeded() {
               public void deleteRealmIfMigrationNeeded_failsWhenAssetFileProvided() {
                   Context context = InstrumentationRegistry.getInstrumentation().getContext();
           
          -        // have a builder instance to isolate codepath
          +        // Has a builder instance to isolate codepath.
                   RealmConfiguration.Builder builder = new RealmConfiguration.Builder(context);
                   try {
                       builder
          @@ -404,7 +404,7 @@ public void equals() {
           
               @Test
               public void equalsWhenRxJavaUnavailable() {
          -        // test for https://github.com/realm/realm-java/issues/2416
          +        // Test for https://github.com/realm/realm-java/issues/2416
                   RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build();
                   TestHelper.emulateRxJavaUnavailable(config1);
                   RealmConfiguration config2 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build();
          @@ -535,7 +535,7 @@ public void schema_differentSchemasThrows() {
                   }
               }
           
          -    // Creating Realm instances with same name but different durabilities is not allowed.
          +    // Creates Realm instances with same name but different durabilities is not allowed.
               @Test
               public void inMemory_differentDurabilityThrows() {
                   RealmConfiguration config1 = new RealmConfiguration.Builder(context)
          @@ -546,7 +546,7 @@ public void inMemory_differentDurabilityThrows() {
                           .directory(configFactory.getRoot())
                           .build();
           
          -        // Create In-memory Realm first.
          +        // Creates In-memory Realm first.
                   Realm realm1 = Realm.getInstance(config1);
                   try {
                       // On-disk Realm then. Not allowed!
          @@ -557,7 +557,7 @@ public void inMemory_differentDurabilityThrows() {
                       realm1.close();
                   }
           
          -        // Create on-disk Realm first.
          +        // Creates on-disk Realm first.
                   realm1 = Realm.getInstance(config2);
                   try {
                       // In-memory Realm then. Not allowed!
          @@ -569,7 +569,7 @@ public void inMemory_differentDurabilityThrows() {
                   }
               }
           
          -    // It is allowed to create multiple Realm with same name but in different directory
          +    // It is allowed to create multiple Realm with same name but in different directory.
               @Test
               public void constructBuilder_differentDirSameName() throws IOException {
                   RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build();
          @@ -583,7 +583,7 @@ public void constructBuilder_differentDirSameName() throws IOException {
           
               @Test
               public void encryptionKey_keyStorage() throws Exception {
          -        // Generate a key and use it in a RealmConfiguration
          +        // Generates a key and uses it in a RealmConfiguration.
                   byte[] oldKey = TestHelper.getRandomKey(12345);
                   byte[] key = oldKey;
                   RealmConfiguration config = new RealmConfiguration.Builder(context)
          @@ -591,13 +591,13 @@ public void encryptionKey_keyStorage() throws Exception {
                           .encryptionKey(key)
                           .build();
           
          -        // Generate a different key and assign it to the same variable
          +        // Generates a different key and assigns it to the same variable.
                   byte[] newKey = TestHelper.getRandomKey(67890);
                   MoreAsserts.assertNotEqual(key, newKey);
                   key = newKey;
                   MoreAsserts.assertEquals(key, newKey);
           
          -        // Ensure that the stored key did not change
          +        // Ensures that the stored key did not change.
                   MoreAsserts.assertEquals(oldKey, config.getEncryptionKey());
               }
           
          @@ -609,7 +609,7 @@ public void modelClassesForDefaultMediator() throws Exception {
           
                   assertTrue(realmClasses.contains(AllTypes.class));
           
          -        // tests returned Set is unmodifiable.
          +        // Tests returned Set is unmodifiable.
                   try {
                       realmClasses.add(AllTypes.class);
                       fail();
          @@ -631,7 +631,7 @@ public void modelClasses_forGeneratedMediator() throws Exception {
                   assertTrue(realmClasses.contains(CatOwner.class));
                   assertFalse(realmClasses.contains(Cat.class));
           
          -        // tests returned Set is unmodifiable.
          +        // Tests returned Set is unmodifiable.
                   try {
                       realmClasses.add(AllTypes.class);
                       fail();
          @@ -653,7 +653,7 @@ public void modelClasses_forCompositeMediator() throws Exception {
                   assertTrue(realmClasses.contains(CatOwner.class));
                   assertTrue(realmClasses.contains(Cat.class));
           
          -        // tests returned Set is unmodifiable.
          +        // Tests returned Set is unmodifiable.
                   try {
                       realmClasses.add(AllTypes.class);
                       fail();
          @@ -676,7 +676,7 @@ public void modelClasses_forFilterableMediator() throws Exception {
                   assertTrue(realmClasses.contains(CatOwner.class));
                   assertFalse(realmClasses.contains(Cat.class));
           
          -        // tests returned Set is unmodifiable.
          +        // Tests returned Set is unmodifiable.
                   try {
                       realmClasses.add(AllTypes.class);
                       fail();
          @@ -774,7 +774,7 @@ public void initialDataTransactionNull() {
           
               @Test
               public void initialDataTransactionNotNull() {
          -        // Remove default instance
          +        // Removes default instance.
                   Realm.deleteRealm(defaultConfig);
           
                   RealmConfiguration configuration = configFactory.createConfigurationBuilder()
          @@ -788,7 +788,7 @@ public void execute(final Realm realm) {
           
                   realm = Realm.getInstance(configuration);
           
          -        // First time check for initial data
          +        // First time check for initial data.
                   assertEquals(1, realm.where(AllTypes.class).count());
                   assertEquals(1, realm.where(Owner.class).count());
                   assertEquals(1, realm.where(Cat.class).count());
          @@ -801,7 +801,7 @@ public void execute(final Realm realm) {
           
                   realm.close();
                   realm = Realm.getInstance(configuration);
          -        // Check if there is still the same data
          +        // Checks if there is still the same data.
                   assertEquals(0, realm.where(AllTypes.class).count());
                   assertEquals(1, realm.where(Owner.class).count());
                   assertEquals(1, realm.where(Cat.class).count());
          @@ -809,7 +809,7 @@ public void execute(final Realm realm) {
           
               @Test
               public void initialDataTransactionExecutionCount() {
          -        // Remove default instance
          +        // Removes default instance.
                   Realm.deleteRealm(defaultConfig);
           
                   Realm.Transaction transaction = mock(Realm.Transaction.class);
          @@ -828,7 +828,7 @@ public void initialDataTransactionExecutionCount() {
           
               @Test
               public void initialDataTransactionAssetFile() throws IOException {
          -        // Remove default instance
          +        // Removes default instance.
                   Realm.deleteRealm(defaultConfig);
           
                   Context context = InstrumentationRegistry.getInstrumentation().getContext();
          @@ -863,7 +863,7 @@ public void assetFileNullAndEmptyFileName() {
           
               @Test
               public void assetFileWithInMemoryConfig() {
          -        // Ensure that there is no data
          +        // Ensures that there is no data.
                   Realm.deleteRealm(new RealmConfiguration.Builder(context).build());
           
                   try {
          @@ -875,7 +875,7 @@ public void assetFileWithInMemoryConfig() {
           
               @Test
               public void assetFileFakeFile() {
          -        // Ensure that there is no data
          +        // Ensures that there is no data.
                   Realm.deleteRealm(new RealmConfiguration.Builder(context).build());
           
                   RealmConfiguration configuration = new RealmConfiguration.Builder(context).assetFile("no_file").build();
          @@ -889,7 +889,7 @@ public void assetFileFakeFile() {
           
               @Test
               public void assetFileValidFile() throws IOException {
          -        // Ensure that there is no data
          +        // Ensures that there is no data.
                   Realm.deleteRealm(new RealmConfiguration.Builder(context).build());
           
                   RealmConfiguration configuration = new RealmConfiguration
          @@ -905,13 +905,13 @@ public void assetFileValidFile() throws IOException {
                   realm = Realm.getInstance(configuration);
                   assertTrue(realmFile.exists());
           
          -        // Asset file has 10 Owners and 10 Cats, check if data is present
          +        // Asset file has 10 Owners and 10 Cats, checks if data is present.
                   assertEquals(10, realm.where(Owner.class).count());
                   assertEquals(10, realm.where(Cat.class).count());
           
                   realm.close();
           
          -        // Copy original file to another location
          +        // Copies original file to another location.
                   configFactory.copyRealmFromAssets(context, "asset_file.realm", "asset_file_copy.realm");
                   File copyFromAsset = new File(configFactory.getRoot(), "asset_file_copy.realm");
                   assertTrue(copyFromAsset.exists());
          @@ -924,7 +924,7 @@ public void assetFileValidFile() throws IOException {
               public void assetFile_failsWhenDeleteRealmIfMigrationNeededConfigured() {
                   Context context = InstrumentationRegistry.getInstrumentation().getContext();
           
          -        // have a builder instance to isolate codepath
          +        // Has a builder instance to isolate codepath.
                   RealmConfiguration.Builder builder = new RealmConfiguration.Builder(context);
                   try {
                       builder
          @@ -940,7 +940,7 @@ public void assetFile_failsWhenDeleteRealmIfMigrationNeededConfigured() {
               private static class MigrationWithNoEquals implements RealmMigration {
                   @Override
                   public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          -            // Do nothing
          +            // Does nothing.
                   }
               }
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java
          index b273290f41..8fd06f3310 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java
          @@ -60,7 +60,7 @@ public void setUp() {
                           .inMemory()
                           .build();
           
          -        // Delete the same name Realm file just in case
          +        // Deletes the same name Realm file just in case.
                   Realm.deleteRealm(onDiskConf);
                   testRealm = Realm.getInstance(inMemConf);
               }
          @@ -72,7 +72,7 @@ public void tearDown() {
                   }
               }
           
          -    // Testing the in-memory Realm by Creating one instance, adding a record, then close the instance.
          +    // Tests the in-memory Realm by creating one instance, adding a record, then closes the instance.
               // By the next time in-memory Realm instance with the same name created, it should be empty.
               @Test
               public void inMemoryRealm() {
          @@ -99,7 +99,7 @@ public void inMemoryRealmWithDifferentNames() {
                   dog.setName("DinoDog");
                   testRealm.commitTransaction();
           
          -        // Create the 2nd in-memory Realm with a different name. To make sure they are not affecting each other.
          +        // Creates the 2nd in-memory Realm with a different name. To make sure they are not affecting each other.
                   RealmConfiguration inMemConf2 = configFactory.createConfigurationBuilder()
                           .name(IDENTIFIER + "2")
                           .inMemory()
          @@ -120,7 +120,7 @@ public void inMemoryRealmWithDifferentNames() {
                   testRealm2.close();
               }
           
          -    // Test deleteRealm called on a in-memory Realm instance
          +    // Tests deleteRealm called on a in-memory Realm instance.
               @Test
               public void delete() {
                   RealmConfiguration configuration = testRealm.getConfiguration();
          @@ -136,7 +136,7 @@ public void delete() {
                   assertTrue(Realm.deleteRealm(configuration));
               }
           
          -    // Test if an in-memory Realm can be written to disk with/without encryption
          +    // Tests if an in-memory Realm can be written to disk with/without encryption.
               @Test
               public void writeCopyTo() {
                   byte[] key = TestHelper.getRandomKey();
          @@ -158,18 +158,18 @@ public void writeCopyTo() {
                   dog.setName("DinoDog");
                   testRealm.commitTransaction();
           
          -        // Test a normal Realm file
          +        // Tests a normal Realm file.
                   testRealm.writeCopyTo(new File(configFactory.getRoot(), fileName));
                   Realm onDiskRealm = Realm.getInstance(conf);
                   assertEquals(onDiskRealm.where(Dog.class).count(), 1);
                   onDiskRealm.close();
           
          -        // Test a encrypted Realm file
          +        // Tests a encrypted Realm file.
                   testRealm.writeEncryptedCopyTo(new File(configFactory.getRoot(), encFileName), key);
                   onDiskRealm = Realm.getInstance(encConf);
                   assertEquals(onDiskRealm.where(Dog.class).count(), 1);
                   onDiskRealm.close();
          -        // Test with a wrong key to see if it fails as expected.
          +        // Tests with a wrong key to see if it fails as expected.
                   try {
                       RealmConfiguration wrongKeyConf = configFactory.createConfigurationBuilder()
                               .name(encFileName)
          @@ -183,11 +183,11 @@ public void writeCopyTo() {
               }
           
               // Test below scenario:
          -    // 1. Create a in-memory Realm instance in the main thread.
          -    // 2. Create a in-memory Realm with same name in another thread.
          -    // 3. Close the in-memory Realm instance in the main thread and the Realm data should not be released since
          +    // 1. Creates a in-memory Realm instance in the main thread.
          +    // 2. Creates a in-memory Realm with same name in another thread.
          +    // 3. Closes the in-memory Realm instance in the main thread and the Realm data should not be released since
               //    another instance is still held by the other thread.
          -    // 4. Close the in-memory Realm instance and the Realm data should be released since no more instance with the
          +    // 4. Closes the in-memory Realm instance and the Realm data should be released since no more instance with the
               //    specific name exists.
               @Test
               public void multiThread() throws InterruptedException, ExecutionException {
          @@ -215,7 +215,7 @@ public void run() {
                           }
                           workerCommittedLatch.countDown();
           
          -                // Wait until Realm instance closed in main thread
          +                // Waits until Realm instance closed in main thread.
                           try {
                               realmInMainClosedLatch.await(3, TimeUnit.SECONDS);
                           } catch (InterruptedException e) {
          @@ -231,20 +231,20 @@ public void run() {
                   workerThread.start();
           
           
          -        // Wait until the worker thread started
          +        // Waits until the worker thread started.
                   workerCommittedLatch.await(3, TimeUnit.SECONDS);
                   if (threadError[0] != null) { throw threadError[0]; }
           
          -        // refresh will be ran in the next loop, manually refresh it here.
          +        // Refreshes will be ran in the next loop, manually refreshes it here.
                   testRealm.waitForChange();
                   assertEquals(testRealm.where(Dog.class).count(), 1);
           
                   // Step 3.
          -        // Release the main thread Realm reference, and the worker thread hold the reference still
          +        // Releases the main thread Realm reference, and the worker thread holds the reference still.
                   testRealm.close();
           
                   // Step 4.
          -        // Create a new Realm reference in main thread and checking the data.
          +        // Creates a new Realm reference in main thread and checks the data.
                   testRealm = Realm.getInstance(inMemConf);
                   assertEquals(testRealm.where(Dog.class).count(), 1);
                   testRealm.close();
          @@ -252,11 +252,11 @@ public void run() {
                   // Let the worker thread continue.
                   realmInMainClosedLatch.countDown();
           
          -        // Wait until the worker thread finished
          +        // Waits until the worker thread finished.
                   workerClosedLatch.await(3, TimeUnit.SECONDS);
                   if (threadError[0] != null) { throw threadError[0]; }
           
          -        // Since all previous Realm instances has been closed before, below will create a fresh new in-mem-realm instance
          +        // Since all previous Realm instances has been closed before, below will create a fresh new in-mem-realm instance.
                   testRealm = Realm.getInstance(inMemConf);
                   assertEquals(testRealm.where(Dog.class).count(), 0);
               }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java
          index 1b80bb1b0f..c4e597eb45 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java
          @@ -134,9 +134,9 @@ protected void done() {
                   public InterprocessHandler(Runnable startRunnable) {
                       super(Looper.myLooper());
                       localMessenger = new Messenger(this);
          -            // To have the first step from main process run
          +            // To have the first step from main process run.
                       post(startRunnable);
          -            // Start watchdog
          +            // Starts watchdog.
                       postDelayed(timeoutRunnable, timeout);
                   }
           
          @@ -145,7 +145,7 @@ public void handleMessage(Message msg) {
                       Bundle bundle = msg.getData();
                       String error = bundle.getString(RemoteProcessService.BUNDLE_KEY_ERROR);
                       if (error != null) {
          -                // Assert and show error from service process
          +                // Asserts and shows error from service process.
                           assertTrue(error, false);
                       }
                   }
          @@ -157,7 +157,7 @@ protected void setUp() throws Exception {
           
                   Realm.deleteRealm(new RealmConfiguration.Builder(getContext()).build());
           
          -        // Start the testing service
          +        // Starts the testing service.
                   serviceStartLatch = new CountDownLatch(1);
                   Intent intent = new Intent(getContext(), RemoteProcessService.class);
                   getContext().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
          @@ -174,7 +174,7 @@ protected void tearDown() throws Exception {
                   getContext().unbindService(serviceConnection);
                   remoteMessenger = null;
           
          -        // Kill the remote process.
          +        // Kills the remote process.
                   ActivityManager.RunningAppProcessInfo info = getRemoteProcessInfo();
                   if (info != null) {
                       android.os.Process.killProcess(info.pid);
          @@ -189,7 +189,7 @@ protected void tearDown() throws Exception {
                   super.tearDown();
               }
           
          -    // Call this to trigger the next step of service process
          +    // Calls this to trigger the next step of service process.
               private void triggerServiceStep(RemoteProcessService.Step step) {
                   Message msg = Message.obtain(null, step.message);
                   msg.replyTo = localMessenger;
          @@ -200,8 +200,8 @@ private void triggerServiceStep(RemoteProcessService.Step step) {
                   }
               }
           
          -    // Return the service info if it is alive.
          -    // When this method return null, it doesn't mean the remote process is not existed. An 'empty' process could
          +    // Returns the service info if it is alive.
          +    // When this method returns null, it doesn't mean the remote process is not existed. An 'empty' process could
               // be retained by the system to be used next time.
               // Use getRemoteProcessInfo if you want to check the existence of remote process.
               private ActivityManager.RunningServiceInfo getServiceInfo() {
          @@ -215,7 +215,7 @@ private ActivityManager.RunningServiceInfo getServiceInfo() {
                   return null;
               }
           
          -    // Get the remote process info if it is alive.
          +    // Gets the remote process info if it is alive.
               private ActivityManager.RunningAppProcessInfo getRemoteProcessInfo() {
                   ActivityManager manager = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
                   List processInfoList = manager.getRunningAppProcesses();
          @@ -228,8 +228,8 @@ private ActivityManager.RunningAppProcessInfo getRemoteProcessInfo() {
                   return null;
               }
           
          -    // A. Open a realm, close it, then call Runtime.getRuntime().exit(0).
          -    // 1. Wait 3 seconds to see if the service process existed.
          +    // A. Opens a realm, closes it, then calls Runtime.getRuntime().exit(0).
          +    // 1. Waits 3 seconds to see if the service process existed.
               public void testExitProcess() {
                   new InterprocessHandler(new Runnable() {
                       @Override
          @@ -257,7 +257,7 @@ public void handleMessage(Message msg) {
                                           // The process is still alive.
                                           assertTrue(false);
                                       } else if (processInfo == null || processInfo.pid != servicePid) {
          -                                // The process is gone
          +                                // The process is gone.
                                           break;
                                       }
                                       Thread.sleep(500, 0);
          @@ -273,8 +273,8 @@ public void handleMessage(Message msg) {
                   Looper.loop();
               }
           
          -    // 1. Main process create Realm, write one object.
          -    // A. Service process open Realm, check if there is one and only one object.
          +    // 1. Main process creates Realm, write one object.
          +    // A. Service process opens Realm, check if there is one and only one object.
               public void testCreateInitialRealm() throws InterruptedException {
                   new InterprocessHandler(new Runnable() {
                       @Override
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java
          index 58fa4950b3..1e023847cd 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java
          @@ -64,7 +64,7 @@ public void tearDown() {
                   }
               }
           
          -    // parameters for testing absent primary key value. PrimaryKey field is absent.
          +    // Parameters for testing absent primary key value. PrimaryKey field is absent.
               @Parameterized.Parameters
               public static Iterable data() {
                   return Arrays.asList(new Object[][]{
          @@ -84,7 +84,7 @@ public RealmJsonAbsentPrimaryKeyTests(Class clazz, String
                   this.clazz = clazz;
               }
           
          -    // Testing absent primary key value for createObjectFromJson()
          +    // Tests absent primary key value for createObjectFromJson().
               @Test
               public void createObjectFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException {
                   realm.beginTransaction();
          @@ -93,7 +93,7 @@ public void createObjectFromJson_primaryKey_isAbsent_fromJsonObject() throws JSO
                   realm.commitTransaction();
               }
           
          -    // Testing absent primary key value for createOrUpdateObjectFromJson()
          +    // Tests absent primary key value for createOrUpdateObjectFromJson().
               @Test
               public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException {
                   realm.beginTransaction();
          @@ -102,7 +102,7 @@ public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonObject() th
                   realm.commitTransaction();
               }
           
          -    // Testing absent primary key value for createAllFromJson()
          +    // Tests absent primary key value for createAllFromJson().
               @Test
               public void createAllFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException {
                   JSONArray jsonArray = new JSONArray();
          @@ -113,7 +113,7 @@ public void createAllFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONEx
                   realm.commitTransaction();
               }
           
          -    // Testing absent primary key value for createOrUpdateAllFromJson()
          +    // Tests absent primary key value for createOrUpdateAllFromJson().
               @Test
               public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonObject() throws JSONException {
                   JSONArray jsonArray = new JSONArray();
          @@ -124,7 +124,7 @@ public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonObject() throw
                   realm.commitTransaction();
               }
           
          -    // Testing absent primary key value for createObjectFromJson() stream version
          +    // Tests absent primary key value for createObjectFromJson() stream version.
               @Test
               public void createObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException {
                   assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB));
          @@ -135,7 +135,7 @@ public void createObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSO
                   realm.commitTransaction();
               }
           
          -    // Testing absent primary key value for createOrUpdateObjectFromJson() stream version
          +    // Tests absent primary key value for createOrUpdateObjectFromJson() stream version.
               @Test
               public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException {
                   realm.beginTransaction();
          @@ -144,7 +144,7 @@ public void createOrUpdateObjectFromJson_primaryKey_isAbsent_fromJsonStream() th
                   realm.commitTransaction();
               }
           
          -    // Testing absent primary key value for createAllFromJson() stream version
          +    // Tests absent primary key value for createAllFromJson() stream version.
               @Test
               public void createAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException {
                   assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB));
          @@ -157,7 +157,7 @@ public void createAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONEx
                   realm.commitTransaction();
               }
           
          -    // Testing absent primary key value for createOrUpdateAllFromJson() stream version
          +    // Tests absent primary key value for createOrUpdateAllFromJson() stream version.
               @Test
               public void createOrUpdateAllFromJson_primaryKey_isAbsent_fromJsonStream() throws JSONException, IOException {
                   assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB));
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java
          index f0345f07bd..78d8c7f56e 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java
          @@ -57,7 +57,7 @@ public void tearDown() {
                   }
               }
           
          -    // parameters for testing null primary key value. PrimaryKey field is explicitly null
          +    // Parameters for testing null primary key value. PrimaryKey field is explicitly null.
               @Parameterized.Parameters
               public static Iterable data() {
                   return Arrays.asList(new Object[][]{
          @@ -79,7 +79,7 @@ public RealmJsonNullPrimaryKeyTests(Class clazz, String s
                   this.clazz = clazz;
               }
           
          -    // Testing null primary key value for createObjectFromJson()
          +    // Tests null primary key value for createObjectFromJson().
               @Test
               public void createObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONException {
                   realm.beginTransaction();
          @@ -102,7 +102,7 @@ public void createObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONE
                   }
               }
           
          -    // Testing null primary key value for createOrUpdateObjectFromJson()
          +    // Tests null primary key value for createOrUpdateObjectFromJson().
               @Test
               public void createOrUpdateObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONException {
                   realm.beginTransaction();
          @@ -125,7 +125,7 @@ public void createOrUpdateObjectFromJson_primaryKey_isNull_fromJsonObject() thro
                   }
               }
           
          -    // Testing null primary key value for createObject() -> createOrUpdateObjectFromJson()
          +    // Tests null primary key value for createObject() -> createOrUpdateObjectFromJson().
               @Test
               public void createOrUpdateObjectFromJson_primaryKey_isNull_updateFromJsonObject() throws JSONException {
                   realm.beginTransaction();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java
          index 01733ddd9e..718abc0f41 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java
          @@ -90,7 +90,7 @@ private InputStream convertJsonObjectToStream(JSONObject obj) {
                   return new ByteArrayInputStream(obj.toString().getBytes());
               }
           
          -    // Assert that the list of AllTypesPrimaryKey objects where inserted and updated properly.
          +    // Asserts that the list of AllTypesPrimaryKey objects where inserted and updated properly.
               private void assertAllTypesPrimaryKeyUpdated() {
                   assertEquals(1, realm.where(AllTypesPrimaryKey.class).count());
                   AllTypesPrimaryKey obj = realm.where(AllTypesPrimaryKey.class).findFirst();
          @@ -105,7 +105,7 @@ private void assertAllTypesPrimaryKeyUpdated() {
                   assertEquals("Dog5", obj.getColumnRealmList().get(0).getName());
               }
           
          -    // Check the imported object from nulltyps.json[0].
          +    // Checks the imported object from nulltyps.json[0].
               private void checkNullableValuesAreNull(NullTypes nullTypes1) {
                   // 1 String
                   assertNull(nullTypes1.getFieldStringNull());
          @@ -141,7 +141,7 @@ private void checkNullableValuesAreNull(NullTypes nullTypes1) {
                   assertNull(nullTypes1.getFieldObjectNull());
               }
           
          -    // Check the imported object from nulltyps.json[1].
          +    // Checks the imported object from nulltyps.json[1].
               private void checkNullableValuesAreNotNull(NullTypes nullTypes2) {
                   // 1 String
                   assertEquals("", nullTypes2.getFieldStringNull());
          @@ -205,7 +205,7 @@ public void createObjectFromJson_allSimpleObjectAllTypes() throws JSONException
                   realm.commitTransaction();
                   AllTypes obj = realm.where(AllTypes.class).findFirst();
           
          -        // Check that all primitive types are imported correctly
          +        // Checks that all primitive types are imported correctly.
                   assertEquals("String", obj.getColumnString());
                   assertEquals(1L, obj.getColumnLong());
                   assertEquals(1.23F, obj.getColumnFloat(), 0F);
          @@ -217,7 +217,7 @@ public void createObjectFromJson_allSimpleObjectAllTypes() throws JSONException
               @Test
               public void createObjectFromJson_dateAsLong() throws JSONException {
                   JSONObject json = new JSONObject();
          -        json.put("columnDate", 1000L); // Realm operates at seconds level granularity
          +        json.put("columnDate", 1000L); // Realm operates at seconds level granularity.
           
                   realm.beginTransaction();
                   realm.createObjectFromJson(AllTypes.class, json);
          @@ -375,16 +375,16 @@ public void createAllFromJson_jsonArray() throws JSONException {
               public void createFromJson_respectDefaultValues() throws JSONException {
                   final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1;
           
          -        // Step 1: Prepare almost empty JSON
          +        // Step 1: Prepares almost empty JSON.
                   final JSONObject json = new JSONObject();
                   json.put(DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, fieldLongPrimaryKeyValue);
           
          -        // Step 2: Update with almost empty JSONObject
          +        // Step 2: Updates with almost empty JSONObject.
                   realm.beginTransaction();
                   final DefaultValueOfField managedObj = realm.createOrUpdateObjectFromJson(DefaultValueOfField.class, json);
                   realm.commitTransaction();
           
          -        // Step 3: Check that default values are applied
          +        // Step 3: Checks that default values are applied.
                   assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE,
                           managedObj.getFieldIgnored());
                   assertEquals(DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE, managedObj.getFieldString());
          @@ -403,7 +403,7 @@ public void createFromJson_respectDefaultValues() throws JSONException {
                   assertEquals(1, managedObj.getFieldList().size());
                   assertEquals(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE, managedObj.getFieldList().first().getFieldInt());
           
          -        // make sure that excess object by default value is not created.
          +        // Makes sure that excess object by default value is not created.
                   assertEquals(2, realm.where(RandomPrimaryKey.class).count());
               }
           
          @@ -411,7 +411,7 @@ public void createFromJson_respectDefaultValues() throws JSONException {
               public void createFromJson_defaultValuesAreIgnored() throws JSONException {
                   final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1;
           
          -        // Step 1: Prepare JSON
          +        // Step 1: Prepares JSON.
                   final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified";
                   final String fieldStringValue = DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified";
                   final String fieldRandomStringValue = "non-random";
          @@ -441,12 +441,12 @@ public void createFromJson_defaultValuesAreIgnored() throws JSONException {
                   json.put(DefaultValueOfField.FIELD_BOOLEAN, fieldBooleanValue);
                   json.put(DefaultValueOfField.FIELD_DATE, ISO8601Utils.format(fieldDateValue, true));
                   json.put(DefaultValueOfField.FIELD_BINARY, Base64.encodeToString(fieldBinaryValue, Base64.DEFAULT));
          -        // value for 'fieldObject'
          +        // Value for 'fieldObject'
                   final JSONObject fieldObjectJson = new JSONObject();
                   fieldObjectJson.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY, "pk of fieldObject");
                   fieldObjectJson.put(RandomPrimaryKey.FIELD_INT, fieldObjectIntValue);
                   json.put(DefaultValueOfField.FIELD_OBJECT, fieldObjectJson);
          -        // value for 'fieldList'
          +        // Value for 'fieldList'
                   final JSONArray fieldListArrayJson = new JSONArray();
                   final JSONObject fieldListItem0Json = new JSONObject();
                   fieldListItem0Json.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY, "pk1 of fieldList");
          @@ -458,13 +458,13 @@ public void createFromJson_defaultValuesAreIgnored() throws JSONException {
                   fieldListArrayJson.put(fieldListItem1Json);
                   json.put(DefaultValueOfField.FIELD_LIST, fieldListArrayJson);
           
          -        // Step 3: Update with JSONObject
          +        // Step 3: Updates with JSONObject.
                   realm.beginTransaction();
                   final DefaultValueOfField managedObj = realm.createOrUpdateObjectFromJson(DefaultValueOfField.class, json);
                   realm.commitTransaction();
           
          -        // Step 4: Check that properly created
          -        assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE/*not fieldIgnoredValue*/,
          +        // Step 4: Checks that properly created.
          +        assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE /* not fieldIgnoredValue */,
                           managedObj.getFieldIgnored());
                   assertEquals(fieldStringValue, managedObj.getFieldString());
                   assertEquals(fieldRandomStringValue, managedObj.getFieldRandomString());
          @@ -489,7 +489,7 @@ public void createFromJson_defaultValuesAreIgnored() throws JSONException {
                           managedObj.getFieldList().get(1).getFieldRandomPrimaryKey());
                   assertEquals(fieldListIntValue + 1, managedObj.getFieldList().get(1).getFieldInt());
           
          -        // make sure that excess object by default value is not created.
          +        // Makes sure that excess object by default value is not created.
                   assertEquals(3, realm.where(RandomPrimaryKey.class).count());
               }
           
          @@ -497,14 +497,14 @@ public void createFromJson_defaultValuesAreIgnored() throws JSONException {
               public void updateFromJson_defaultValuesAreIgnored() throws JSONException {
                   final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1;
           
          -        // Step 1: Create an object with default values
          +        // Step 1: Creates an object with default values.
                   final DefaultValueOfField original;
                   realm.beginTransaction(); {
                       original = realm.createObject(DefaultValueOfField.class, fieldLongPrimaryKeyValue);
                   }
                   realm.commitTransaction();
           
          -        // Step 2: Prepare JSON
          +        // Step 2: Prepares JSON.
                   final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified";
                   final String fieldStringValue = DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified";
                   final String fieldRandomStringValue = "non-random";
          @@ -540,26 +540,26 @@ public void updateFromJson_defaultValuesAreIgnored() throws JSONException {
                           original.getFieldObject().getFieldRandomPrimaryKey());
                   fieldObjectJson.put(RandomPrimaryKey.FIELD_INT, fieldObjectIntValue);
                   json.put(DefaultValueOfField.FIELD_OBJECT, fieldObjectJson);
          -        // value for 'fieldList'
          +        // Value for 'fieldList'
                   final JSONArray fieldListArrayJson = new JSONArray();
          -        final JSONObject fieldListItem0Json = new JSONObject(); // to be added
          +        final JSONObject fieldListItem0Json = new JSONObject(); // To be added.
                   fieldListItem0Json.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY,  "unique value");
                   fieldListItem0Json.put(RandomPrimaryKey.FIELD_INT, fieldListIntValue);
                   fieldListArrayJson.put(fieldListItem0Json);
          -        final JSONObject fieldListItem1Json = new JSONObject(); // to be updated
          +        final JSONObject fieldListItem1Json = new JSONObject(); // To be updated.
                   fieldListItem1Json.put(RandomPrimaryKey.FIELD_RANDOM_PRIMARY_KEY,
                           original.getFieldList().first().getFieldRandomPrimaryKey());
                   fieldListItem1Json.put(RandomPrimaryKey.FIELD_INT, fieldListIntValue + 1);
                   fieldListArrayJson.put(fieldListItem1Json);
                   json.put(DefaultValueOfField.FIELD_LIST, fieldListArrayJson);
           
          -        // Step 3: Update with JSONObject
          +        // Step 3: Updates with JSONObject.
                   realm.beginTransaction();
                   final DefaultValueOfField managedObj = realm.createOrUpdateObjectFromJson(DefaultValueOfField.class, json);
                   realm.commitTransaction();
           
          -        // Step 4: Check that properly updated
          -        assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE/*not fieldIgnoredValue*/,
          +        // Step 4: Checks that properly updated.
          +        assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE /* not fieldIgnoredValue */,
                           managedObj.getFieldIgnored());
                   assertEquals(fieldStringValue, managedObj.getFieldString());
                   assertEquals(fieldRandomStringValue, managedObj.getFieldRandomString());
          @@ -581,11 +581,11 @@ public void updateFromJson_defaultValuesAreIgnored() throws JSONException {
                           managedObj.getFieldList().get(1).getFieldRandomPrimaryKey());
                   assertEquals(fieldListIntValue + 1, managedObj.getFieldList().get(1).getFieldInt());
           
          -        // make sure that excess object by default value is not created.
          +        // Makes sure that excess object by default value is not created.
                   assertEquals(3/* 2 updated + 1 added*/, realm.where(RandomPrimaryKey.class).count());
               }
           
          -    // Test if Json object doesn't have the field, then the field should have default value.
          +    // Tests if Json object doesn't have the field, then the field should have default value.
               @Test
               public void createObjectFromJson_noValues() throws JSONException {
                   JSONObject json = new JSONObject();
          @@ -595,7 +595,7 @@ public void createObjectFromJson_noValues() throws JSONException {
                   realm.createObjectFromJson(AllTypes.class, json);
                   realm.commitTransaction();
           
          -        // Check that all primitive types are imported correctly
          +        // Checks that all primitive types are imported correctly.
                   AllTypes obj = realm.where(AllTypes.class).findFirst();
                   assertEquals("", obj.getColumnString());
                   assertEquals(0L, obj.getColumnLong());
          @@ -608,7 +608,7 @@ public void createObjectFromJson_noValues() throws JSONException {
                   assertEquals(0, obj.getColumnRealmList().size());
               }
           
          -    // Test that given an exception everything up to the exception is saved
          +    // Tests that given an exception everything up to the exception is saved.
               @Test
               public void createObjectFromJson_jsonException() throws JSONException {
                   JSONObject json = new JSONObject();
          @@ -712,7 +712,7 @@ public void createObjectFromJson_streamAllSimpleTypes() throws IOException {
                   realm.commitTransaction();
                   in.close();
           
          -        // Check that all primitive types are imported correctly
          +        // Checks that all primitive types are imported correctly.
                   AllTypes obj = realm.where(AllTypes.class).findFirst();
                   assertEquals("String", obj.getColumnString());
                   assertEquals(1L, obj.getColumnLong());
          @@ -732,7 +732,7 @@ public void createObjectFromJson_streamDateAsLong() throws IOException {
                   realm.commitTransaction();
                   in.close();
           
          -        // Check that all primitive types are imported correctly
          +        // Checks that all primitive types are imported correctly.
                   AllTypes obj = realm.where(AllTypes.class).findFirst();
                   assertEquals(new Date(1000), obj.getColumnDate());
               }
          @@ -747,7 +747,7 @@ public void createObjectFromJson_streamDateAsString() throws IOException {
                   realm.commitTransaction();
                   in.close();
           
          -        // Check that all primitive types are imported correctly
          +        // Checks that all primitive types are imported correctly.
                   AllTypes obj = realm.where(AllTypes.class).findFirst();
                   assertEquals(new Date(1000), obj.getColumnDate());
               }
          @@ -767,7 +767,7 @@ public void createObjectFromJson_streamDateAsISO8601String() throws IOException
                   cal.set(Calendar.MILLISECOND, 789);
                   Date date = cal.getTime();
           
          -        // Check that all primitive types are imported correctly
          +        // Checks that all primitive types are imported correctly.
                   AllTypes obj = realm.where(AllTypes.class).findFirst();
                   assertEquals(date, obj.getColumnDate());
               }
          @@ -828,7 +828,7 @@ public void createAllFromJson_streamArray() throws IOException {
               }
           
           
          -    // Test if Json object doesn't have the field, then the field should have default value. Stream version.
          +    // Tests if Json object doesn't have the field, then the field should have default value. Stream version.
               @Test
               public void createObjectFromJson_streamNoValues() throws IOException {
                   assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB));
          @@ -839,7 +839,7 @@ public void createObjectFromJson_streamNoValues() throws IOException {
                   realm.commitTransaction();
                   in.close();
           
          -        // Check that all primitive types are imported correctly
          +        // Checks that all primitive types are imported correctly.
                   AllTypes obj = realm.where(AllTypes.class).findFirst();
                   assertEquals("", obj.getColumnString());
                   assertEquals(0L, obj.getColumnLong());
          @@ -889,7 +889,7 @@ public void createObjectFromJson_streamNullInputStream() throws IOException {
               }
           
               /**
          -     * Test update a existing object with JSON stream. Only primary key in JSON.
          +     * Tests updating a existing object with JSON stream. Only primary key in JSON.
                * No value should be changed.
                */
               @Test
          @@ -916,7 +916,7 @@ public void createOrUpdateObjectFromJson_streamNullValues() throws IOException {
                   realm.commitTransaction();
                   in.close();
           
          -        // Check that all primitive types are imported correctly
          +        // Checks that all primitive types are imported correctly.
                   obj = realm.where(AllTypesPrimaryKey.class).findFirst();
                   assertEquals("1", obj.getColumnString());
                   assertEquals(1L, obj.getColumnLong());
          @@ -992,7 +992,7 @@ public void createOrUpdateObjectFromJson_streamIgnoreUnsetProperties() throws IO
                   realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, TestHelper.loadJsonFromAssets(context, "list_alltypes_primarykey.json"));
                   realm.commitTransaction();
           
          -        // No-op as no properties should be updated
          +        // No-op as no properties should be updated.
                   realm.beginTransaction();
                   realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, TestHelper.stringToStream("{ \"columnLong\":1 }"));
                   realm.commitTransaction();
          @@ -1020,8 +1020,8 @@ public void createOrUpdateObjectFromJson_inputStream() throws IOException {
               }
           
               /**
          -     * Check that using createOrUpdateObject will set the primary key directly instead of first setting
          -     * it to the default value (which can fail)
          +     * Checks that using createOrUpdateObject will set the primary key directly instead of first setting
          +     * it to the default value (which can fail).
                */
               @Test
               public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException {
          @@ -1039,7 +1039,7 @@ public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFro
                   assertEquals("bar", owners.get(1).getName());
               }
           
          -    // Test update a existing object with JSON object with only primary key.
          +    // Tests updating a existing object with JSON object with only primary key.
               // No value should be changed.
               @Test
               public void createOrUpdateObjectFromJson_objectNullValues() throws IOException {
          @@ -1062,7 +1062,7 @@ public void createOrUpdateObjectFromJson_objectNullValues() throws IOException {
                   realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, json);
                   realm.commitTransaction();
           
          -        // Check that all primitive types are imported correctly
          +        // Checks that all primitive types are imported correctly.
                   obj = realm.where(AllTypesPrimaryKey.class).findFirst();
                   assertEquals("1", obj.getColumnString());
                   assertEquals(1L, obj.getColumnLong());
          @@ -1220,8 +1220,8 @@ public void createOrUpdateObjectFromJson_invalidJsonObject() throws JSONExceptio
               }
           
               /**
          -     * Check that using createOrUpdateObject will set the primary key directly instead of first setting
          -     * it to the default value (which can fail)
          +     * Checks that using createOrUpdateObject will set the primary key directly instead of first setting
          +     * it to the default value (which can fail).
                */
               @Test
               public void createOrUpdateObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromJsonObject() throws JSONException {
          @@ -1357,7 +1357,7 @@ public void createOrUpdateAllFromJson_inputString() throws IOException {
                   assertAllTypesPrimaryKeyUpdated();
               }
           
          -    // Testing create objects from Json, all nullable fields with null values or non-null values
          +    // Tests creating objects from Json, all nullable fields with null values or non-null values.
               @Test
               public void createAllFromJson_nullTypesJsonWithNulls() throws IOException, JSONException {
                   String json = TestHelper.streamToString(TestHelper.loadJsonFromAssets(context, "nulltypes.json"));
          @@ -1376,7 +1376,7 @@ public void createAllFromJson_nullTypesJsonWithNulls() throws IOException, JSONE
                   checkNullableValuesAreNotNull(nullTypes2);
               }
           
          -    // Test creating objects form JSON stream, all nullable fields with null values or non-null values
          +    // Tests creating objects form JSON stream, all nullable fields with null values or non-null values.
               @Test
               public void createAllFromJson_nullTypesStreamJSONWithNulls() throws IOException {
                   assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB));
          @@ -1396,7 +1396,7 @@ public void createAllFromJson_nullTypesStreamJSONWithNulls() throws IOException
               }
           
               /**
          -     * Test a nullable field already has a non-null value, update it through JSON with null value
          +     * Tests a nullable field already has a non-null value, update it through JSON with null value
                * of the corresponding field.
                */
               @Test
          @@ -1416,7 +1416,7 @@ public void createObjectFromJson_updateNullTypesJSONWithNulls() throws IOExcepti
                   assertEquals(2, nullTypesRealmResults.size());
                   checkNullableValuesAreNotNull(nullTypesRealmResults.where().equalTo("id", 1).findFirst());
           
          -        // Update object with id 1, nullable fields should have null values
          +        // Updates object with id 1, nullable fields should have null values.
                   JSONArray array = new JSONArray(json);
                   realm.beginTransaction();
                   realm.createOrUpdateAllFromJson(NullTypes.class, array);
          @@ -1636,8 +1636,8 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE
               }
           
               /**
          -     * Check that using createOrUpdateObject will set the primary key directly instead of first setting
          -     * it to the default value (which can fail)
          +     * Checks that using createOrUpdateObject will set the primary key directly instead of first setting
          +     * it to the default value (which can fail).
                */
               @Test
               public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromJsonObject() throws JSONException {
          @@ -1663,7 +1663,7 @@ public void createObjectFromJson_objectNullClass() throws JSONException {
           
               /**
                * createObject using primary keys doesn't work if the Check that using createOrUpdateObject
          -     * will set the primary key directly instead of first setting it to the default value (which can fail)
          +     * will set the primary key directly instead of first setting it to the default value (which can fail).
                */
               @Test
               public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream() throws JSONException, IOException {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java
          index 8e7e01d727..2af194d3ef 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java
          @@ -552,7 +552,7 @@ public void linkIsNotNull() {
               @Test
               public void isNullWrongType() {
                   try {
          -            // AllTypes.columnFloat is not nullable
          +            // AllTypes.columnFloat is not nullable.
                       testRealm.where(AllTypes.class).isNull("columnFloat").findAll();
                       fail();
                   } catch (IllegalArgumentException ignored) {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java
          index 7dc7177144..5106b86cb8 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java
          @@ -104,9 +104,9 @@ private RealmList createDeletedRealmList() {
                   return dogs;
               }
           
          -            //noinspection TryWithIdenticalCatches
          +    //noinspection TryWithIdenticalCatches
               /*********************************************************
          -     * Unmanaged mode tests                                *
          +     * Unmanaged mode tests                                  *
                *********************************************************/
           
               @Test(expected = IllegalArgumentException.class)
          @@ -252,7 +252,7 @@ public void remove_unmanagedMode() {
                   assertEquals(object1, object2);
               }
           
          -    // Test move where oldPosition > newPosition
          +    // Tests move where oldPosition > newPosition.
               @Test
               public void move_down() {
                   Owner owner = realm.where(Owner.class).findFirst();
          @@ -264,7 +264,7 @@ public void move_down() {
                   assertEquals(0, owner.getDogs().indexOf(dog1));
               }
           
          -    // Test move where oldPosition < newPosition
          +    // Tests move where oldPosition < newPosition.
               @Test
               public void move_up() {
                   Owner owner = realm.where(Owner.class).findFirst();
          @@ -279,7 +279,7 @@ public void move_up() {
                   assertEquals(newIndex, owner.getDogs().indexOf(dog));
               }
           
          -    // Test move where oldPosition > newPosition
          +    // Tests move where oldPosition > newPosition.
               @Test
               public void move_downInUnmanagedMode() {
                   RealmList dogs = createUnmanagedDogList();
          @@ -289,7 +289,7 @@ public void move_downInUnmanagedMode() {
                   assertEquals(0, dogs.indexOf(dog1));
               }
           
          -    // Test move where oldPosition < newPosition
          +    // Tests move where oldPosition < newPosition.
               @Test
               public void move_upInUnmanagedMode() {
                   RealmList dogs = createUnmanagedDogList();
          @@ -371,7 +371,7 @@ public void add_managedObjectToManagedList() {
                   assertEquals(1, realm.where(Owner.class).findFirst().getDogs().size());
               }
           
          -    // Test that add correctly uses Realm.copyToRealm() on unmanaged objects.
          +    // Tests that add correctly uses Realm.copyToRealm() on unmanaged objects.
               @Test
               public void add_unmanagedObjectToManagedList() {
                   realm.beginTransaction();
          @@ -382,7 +382,7 @@ public void add_unmanagedObjectToManagedList() {
                   assertEquals(1, realm.where(CyclicType.class).findFirst().getObjects().size());
               }
           
          -    // Make sure that unmanaged objects with a primary key are added using copyToRealmOrUpdate
          +    // Makes sure that unmanaged objects with a primary key are added using copyToRealmOrUpdate.
               @Test
               public void add_unmanagedPrimaryKeyObjectToManagedList() {
                   realm.beginTransaction();
          @@ -395,7 +395,7 @@ public void add_unmanagedPrimaryKeyObjectToManagedList() {
                   assertEquals("new", realm.where(CyclicTypePrimaryKey.class).equalTo("id", 2).findFirst().getName());
               }
           
          -    // Test that set correctly uses Realm.copyToRealm() on unmanaged objects.
          +    // Tests that set correctly uses Realm.copyToRealm() on unmanaged objects.
               @Test
               public void set_unmanagedObjectToManagedList() {
                   realm.beginTransaction();
          @@ -413,7 +413,7 @@ public void set_unmanagedObjectToManagedList() {
                   assertEquals(5, realm.where(CyclicType.class).count());
               }
           
          -    // Test that set correctly uses Realm.copyToRealmOrUpdate() on unmanaged objects with a primary key.
          +    // Tests that set correctly uses Realm.copyToRealmOrUpdate() on unmanaged objects with a primary key.
               @Test
               public void set_unmanagedPrimaryKeyObjectToManagedList() {
                   realm.beginTransaction();
          @@ -723,8 +723,8 @@ public void realmMethods_onDeletedLinkView() {
                               case MIN_DATE: results.minDate(CyclicType.FIELD_DATE); break;
                               case MAX_DATE: results.maxDate(CyclicType.FIELD_DATE); break;
                               case DELETE_ALL_FROM_REALM: results.deleteAllFromRealm(); break;
          -                    case IS_VALID: continue; // Does not throw
          -                    case IS_MANAGED: continue; // Does not throw
          +                    case IS_VALID: continue; // Does not throw.
          +                    case IS_MANAGED: continue; // Does not throw.
                           }
                           fail(method + " should have thrown an Exception.");
                       } catch (IllegalStateException ignored) {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java
          index cb26f70fb5..754fdbe620 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java
          @@ -97,10 +97,10 @@ public void getInstance_realmClosedAfterMigrationException() throws IOException
                       Realm.getInstance(realmConfig);
                       fail("A migration should be triggered");
                   } catch (RealmMigrationNeededException expected) {
          -            Realm.deleteRealm(realmConfig); // Delete old realm
          +            Realm.deleteRealm(realmConfig); // Deletes old realm.
                   }
           
          -        // This should recreate the Realm with proper schema
          +        // This should recreate the Realm with proper schema.
                   Realm realm = Realm.getInstance(realmConfig);
                   int result = realm.where(AllTypes.class).equalTo("columnString", "Foo").findAll().size();
                   assertEquals(0, result);
          @@ -114,7 +114,7 @@ public void localColumnIndices() throws IOException {
                   String MIGRATED_REALM = "migrated.realm";
                   String NEW_REALM = "new.realm";
           
          -        // Migrate old Realm to proper schema
          +        // Migrates old Realm to proper schema.
           
                   // V1 config
                   RealmConfiguration v1Config = configFactory.createConfigurationBuilder()
          @@ -144,8 +144,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                           .build();
                   oldRealm = Realm.getInstance(v2Config);
           
          -        // Create new Realm which will cause column indices to be recalculated based on the order in the java file
          -        // instead of the migration
          +        // Creates new Realm which will cause column indices to be recalculated based on the order in the java file
          +        // instead of the migration.
                   RealmConfiguration newConfig = configFactory.createConfigurationBuilder()
                           .name(NEW_REALM)
                           .schemaVersion(2)
          @@ -154,7 +154,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   Realm newRealm = Realm.getInstance(newConfig);
                   newRealm.close();
           
          -        // Try to query migrated realm. With local column indices this will work. With global it will fail.
          +        // Tries to query migrated realm. With local column indices this will work. With global it will fail.
                   assertEquals(0, oldRealm.where(FieldOrder.class).equalTo("field1", true).findAll().size());
                   oldRealm.close();
               }
          @@ -162,20 +162,20 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
               @Test
               public void notSettingIndexThrows() {
           
          -        // Create v0 of the Realm
          +        // Creates v0 of the Realm.
                   RealmConfiguration originalConfig = configFactory.createConfigurationBuilder()
                           .schema(AllTypes.class)
                           .build();
                   Realm.getInstance(originalConfig).close();
           
          -        // Create v1 of the Realm
          +        // Creates v1 of the Realm.
                   RealmMigration migration = new RealmMigration() {
                       @Override
                       public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                           RealmSchema schema = realm.getSchema();
                           schema.create("AnnotationTypes")
                                   .addField("id", long.class, FieldAttribute.PRIMARY_KEY)
          -                        .addField("indexString", String.class) // Forget to set @Index
          +                        .addField("indexString", String.class) // Forgets to set @Index.
                                   .addField("notIndexString", String.class);
                       }
                   };
          @@ -199,7 +199,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
               @Test
               public void addingPrimaryKeyThrows() {
           
          -        // Create v0 of the Realm
          +        // Creates v0 of the Realm.
                   RealmConfiguration originalConfig = configFactory.createConfigurationBuilder()
                           .schema(Thread.class)
                           .build();
          @@ -210,13 +210,13 @@ public void addingPrimaryKeyThrows() {
                       public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                           RealmSchema schema = realm.getSchema();
                           schema.create("AnnotationTypes")
          -                        .addField("id", long.class) // Forget to set @PrimaryKey
          +                        .addField("id", long.class) // Forgets to set @PrimaryKey.
                                   .addField("indexString", String.class, FieldAttribute.INDEXED)
                                   .addField("notIndexString", String.class);
                       }
                   };
           
          -        // Create v1 of the Realm
          +        // Creates v1 of the Realm.
                   RealmConfiguration realmConfig = configFactory.createConfigurationBuilder()
                           .schemaVersion(1)
                           .schema(Thread.class, AnnotationTypes.class)
          @@ -239,7 +239,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
               @Test
               public void removingPrimaryKeyThrows() {
           
          -        // Create v0 of the Realm
          +        // Creates v0 of the Realm.
                   RealmConfiguration originalConfig = configFactory.createConfigurationBuilder()
                           .schema(Thread.class)
                           .build();
          @@ -254,7 +254,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                       }
                   };
           
          -        // Create v1 of the Realm
          +        // Creates v1 of the Realm.
                   RealmConfiguration realmConfig = configFactory.createConfigurationBuilder()
                           .schemaVersion(1)
                           .schema(Thread.class, StringOnly.class)
          @@ -277,7 +277,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
               @Test
               public void changingPrimaryKeyThrows() {
           
          -        // Create v0 of the Realm
          +        // Creates v0 of the Realm.
                   RealmConfiguration originalConfig = configFactory.createConfigurationBuilder()
                           .schema(Thread.class)
                           .build();
          @@ -288,12 +288,12 @@ public void changingPrimaryKeyThrows() {
                       public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                           RealmSchema schema = realm.getSchema();
                           schema.create("PrimaryKeyAsString")
          -                        .addField("id", long.class, FieldAttribute.PRIMARY_KEY) // initial @PrimaryKey is on the int
          +                        .addField("id", long.class, FieldAttribute.PRIMARY_KEY) // Initial @PrimaryKey is on the int.
                                   .addField("name", String.class);
                       }
                   };
           
          -        // Create v1 of the Realm
          +        // Creates v1 of the Realm.
                   RealmConfiguration realmConfig = configFactory.createConfigurationBuilder()
                           .schemaVersion(1)
                           .schema(Thread.class, PrimaryKeyAsString.class)
          @@ -328,9 +328,9 @@ private void buildInitialMigrationSchema(final String className, final boolean c
                   realm.executeTransaction(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
          -                // first, remove an existing schema
          +                // First, removes an existing schema.
                           realm.getSchema().remove(className);
          -                // then recreate the deleted schema or build a base schema
          +                // Then recreates the deleted schema or builds a base schema.
                           realm.getSchema()
                                   .create(createBase ? MigrationPrimaryKey.CLASS_NAME : className)
                                   .addField(MigrationPrimaryKey.FIELD_FIRST,   Byte.class)
          @@ -343,7 +343,7 @@ public void execute(Realm realm) {
                   realm.close();
               }
           
          -    // Test to show renaming a class does not hinder its PK field's attribute
          +    // Tests to show renaming a class does not hinder its PK field's attribute.
               @Test
               public void renameClassTransferPrimaryKey() {
                   buildInitialMigrationSchema(MigrationClassRenamed.CLASS_NAME, true);
          @@ -367,7 +367,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   assertEquals(MigrationClassRenamed.DEFAULT_FIELDS_COUNT, table.getColumnCount());
                   assertEquals(MigrationClassRenamed.DEFAULT_PRIMARY_INDEX, table.getPrimaryKey());
                   assertEquals(MigrationClassRenamed.FIELD_PRIMARY, table.getColumnName(table.getPrimaryKey()));
          -        //old schema does not exist
          +        // Old schema does not exist.
                   assertNull(realm.getSchema().get(MigrationPrimaryKey.CLASS_NAME));
               }
           
          @@ -382,7 +382,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                           realm.getSchema()
                                   .rename(MigrationPrimaryKey.CLASS_NAME, MigrationClassRenamed.CLASS_NAME);
           
          -                // Then recreate the original schema to see if Realm is going to get confused.
          +                // Then recreates the original schema to see if Realm is going to get confused.
                           // Unlike the first time with buildInitialMigrationSchema(), we will not have a primary key.
                           realm.getSchema()
                                   .create(MigrationPrimaryKey.CLASS_NAME)
          @@ -404,7 +404,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   assertFalse(realm.getSchema().get(MigrationPrimaryKey.CLASS_NAME).hasPrimaryKey());
               }
           
          -    // Test to show that renaming a class does not effect the primary key
          +    // Test to show that renaming a class does not effect the primary key.
               @Test
               public void setClassName_transferPrimaryKey() {
                   buildInitialMigrationSchema(MigrationClassRenamed.CLASS_NAME, true);
          @@ -429,7 +429,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   assertEquals(MigrationClassRenamed.DEFAULT_FIELDS_COUNT, table.getColumnCount());
                   assertEquals(MigrationClassRenamed.DEFAULT_PRIMARY_INDEX, table.getPrimaryKey());
                   assertEquals(MigrationClassRenamed.FIELD_PRIMARY, table.getColumnName(table.getPrimaryKey()));
          -        //old schema does not exist
          +        // Old schema does not exist.
                   assertNull(realm.getSchema().get(MigrationPrimaryKey.CLASS_NAME));
               }
           
          @@ -440,12 +440,12 @@ public void setClassName_noSimilarPrimaryKeyWithOldSchema() {
                   RealmMigration migration = new RealmMigration() {
                       @Override
                       public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          -                // Let us set a new class name
          +                // Let us set a new class name.
                           realm.getSchema()
                                   .get(MigrationPrimaryKey.CLASS_NAME)
                                   .setClassName(MigrationClassRenamed.CLASS_NAME);
           
          -                // Then recreate the original schema to see if Realm is going to get confused.
          +                // Then recreates the original schema to see if Realm is going to get confused.
                           // Unlike the first time with buildInitialMigrationSchema(), we will not have a primary key.
                           realm.getSchema()
                                   .create(MigrationPrimaryKey.CLASS_NAME)
          @@ -469,7 +469,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
           
               @Test
               public void setClassName_throwOnLongClassName() {
          -        // create the first version of schema
          +        // Creates the first version of schema.
                   Realm realm = Realm.getInstance(configFactory.createConfigurationBuilder().build());
                   realm.executeTransaction(new Realm.Transaction() {
                       @Override
          @@ -479,7 +479,7 @@ public void execute(Realm realm) {
                   });
                   realm.close();
           
          -        // get ready for the 2nd version migration
          +        // Gets ready for the 2nd version migration.
                   RealmMigration migration = new RealmMigration() {
                       @Override
                       public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          @@ -494,7 +494,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                           .migration(migration)
                           .build();
           
          -        // create Realm instance fails
          +        // Creating Realm instance fails.
                   try {
                       Realm.getInstance(realmConfig);
                       fail();
          @@ -504,7 +504,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   }
               }
           
          -    // Removing fields before a pk field does not affect the pk
          +    // Removing fields before a pk field does not affect the pk.
               @Test
               public void removeFieldsBeforePrimaryKey() {
                   buildInitialMigrationSchema(MigrationPosteriorIndexOnly.CLASS_NAME, false);
          @@ -531,7 +531,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   assertEquals(MigrationPosteriorIndexOnly.FIELD_PRIMARY, table.getColumnName(table.getPrimaryKey()));
               }
           
          -    // Removing fields after a pk field does not affect the pk
          +    // Removing fields after a pk field does not affect the pk.
               @Test
               public void removeFieldsAfterPrimaryKey() {
                   buildInitialMigrationSchema(MigrationPriorIndexOnly.CLASS_NAME, false);
          @@ -558,7 +558,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   assertEquals(MigrationPriorIndexOnly.FIELD_PRIMARY, table.getColumnName(table.getPrimaryKey()));
               }
           
          -    // Renaming the class should also rename the the class entry in the pk metadata table that tracks primary keys
          +    // Renaming the class should also rename the the class entry in the pk metadata table that tracks primary keys.
               @Test
               public void renamePrimaryKeyFieldInMigration() {
                   buildInitialMigrationSchema(MigrationFieldRenamed.CLASS_NAME, false);
          @@ -604,7 +604,7 @@ public void execute(DynamicRealm realm) {
                   }
               }
           
          -    // This is to test how PK type can change to non-nullable int in migration
          +    // This is to test how PK type can change to non-nullable int in migration.
               @Test
               public void modifyPrimaryKeyFieldTypeToIntInMigration() {
                   final String TEMP_FIELD_ID = "temp_id";
          @@ -655,12 +655,12 @@ public void apply(DynamicRealmObject obj) {
                   assertEquals(12, realm.where(MigrationFieldTypeToInt.class).findFirst().fieldIntPrimary);
               }
           
          -    // This is to test how PK type can change to nullable Integer in migration
          +    // This is to test how PK type can change to nullable Integer in migration.
               @Test
               public void modifyPrimaryKeyFieldTypeToIntegerInMigration() {
                   final String TEMP_FIELD_ID = "temp_id";
                   buildInitialMigrationSchema(MigrationFieldTypeToInteger.CLASS_NAME, false);
          -        // create objects with the schema provided
          +        // Creates objects with the schema provided.
                   createObjectsWithOldPrimaryKey(MigrationFieldTypeToInteger.CLASS_NAME, true);
           
                   RealmMigration migration = new RealmMigration() {
          @@ -714,7 +714,7 @@ public void apply(DynamicRealmObject obj) {
           
               @Test
               public void settingPrimaryKeyWithObjectSchema() {
          -        // Create v0 of the Realm
          +        // Creates v0 of the Realm.
                   RealmConfiguration originalConfig = configFactory.createConfigurationBuilder()
                           .schema(AllTypes.class)
                           .build();
          @@ -726,14 +726,14 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                           RealmSchema schema = realm.getSchema();
                           schema.create("AnnotationTypes")
                                   .addField("id", long.class)
          -                        .addPrimaryKey("id")    // use addPrimaryKey() instead of adding FieldAttribute.PrimaryKey
          +                        .addPrimaryKey("id")    // Uses addPrimaryKey() instead of adding FieldAttribute.PrimaryKey.
                                   .addField("indexString", String.class)
          -                        .addIndex("indexString") // use addIndex() instead of FieldAttribute.Index
          +                        .addIndex("indexString") // Uses addIndex() instead of FieldAttribute.Index.
                                   .addField("notIndexString", String.class);
                       }
                   };
           
          -        // Create v1 of the Realm
          +        // Creates v1 of the Realm.
                   RealmConfiguration realmConfig = configFactory.createConfigurationBuilder()
                           .schemaVersion(1)
                           .schema(AllTypes.class, AnnotationTypes.class)
          @@ -747,7 +747,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   realm.close();
               }
           
          -    // adding search index is idempotent
          +    // Adding search index is idempotent.
               @Test
               public void addingSearchIndexTwice() throws IOException {
                   final Class[] classes = {PrimaryKeyAsLong.class, PrimaryKeyAsString.class};
          @@ -785,7 +785,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
               @Test
               public void setAnnotations() {
           
          -        // Create v0 of the Realm
          +        // Creates v0 of the Realm.
                   RealmConfiguration originalConfig = configFactory.createConfigurationBuilder()
                           .schema(AllTypes.class)
                           .build();
          @@ -851,7 +851,7 @@ public void openPreNullRealmRequiredMissing() throws IOException {
                   RealmMigration realmMigration = new RealmMigration() {
                       @Override
                       public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          -                // intentionally left empty
          +                // Intentionally lefts empty.
                       }
                   };
           
          @@ -923,7 +923,7 @@ public void openPreNullWithRequired() throws IOException {
                   realm.close();
               }
           
          -    // If a required field was nullable before, a RealmMigrationNeededException should be thrown
          +    // If a required field was nullable before, a RealmMigrationNeededException should be thrown.
               @Test
               public void notSettingRequiredForNotNullableThrows() {
                   String[] notNullableFields = {NullTypes.FIELD_STRING_NOT_NULL, NullTypes.FIELD_BYTES_NOT_NULL,
          @@ -975,7 +975,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                               .migration(migration)
                               .build();
                       Realm.deleteRealm(realmConfig);
          -            // Prepare the version 0 db
          +            // Prepares the version 0 db.
                       DynamicRealm dynamicRealm = DynamicRealm.getInstance(realmConfig);
                       TestHelper.initNullTypesTableExcludes(dynamicRealm, field);
                       dynamicRealm.close();
          @@ -992,7 +992,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   }
               }
           
          -    // If a field is not required but was not nullable before, a RealmMigrationNeededException should be thrown
          +    // If a field is not required but was not nullable before, a RealmMigrationNeededException should be thrown.
               @Test
               public void settingRequiredForNullableThrows() {
                   String[] notNullableFields = {NullTypes.FIELD_STRING_NULL, NullTypes.FIELD_BYTES_NULL,
          @@ -1043,7 +1043,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                               .migration(migration)
                               .build();
                       Realm.deleteRealm(realmConfig);
          -            // Prepare the version 0 db
          +            // Prepares the version 0 db.
                       DynamicRealm dynamicRealm = DynamicRealm.getInstance(realmConfig);
                       TestHelper.initNullTypesTableExcludes(dynamicRealm, field);
                       dynamicRealm.close();
          @@ -1066,7 +1066,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   }
               }
           
          -    // Testing older Realms for setting Boxed type primary keys fields nullable in migration process to support Realm Version 0.89+
          +    // Tests older Realms for setting Boxed type primary keys fields nullable in migration process to support Realm Version 0.89+.
               @Test
               public void settingNullableToPrimaryKey() throws IOException {
                   final long SCHEMA_VERSION = 67;
          @@ -1106,7 +1106,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   }
               }
           
          -    // Not-setting older boxed type PrimaryKey field nullable to see if migration fails in order to support Realm version 0.89+
          +    // Not-setting older boxed type PrimaryKey field nullable to see if migration fails in order to support Realm version 0.89+.
               @Test
               public void notSettingNullableToPrimaryKeyThrows() throws IOException {
                   configFactory.copyRealmFromAssets(context, "default-notnullable-primarykey.realm", Realm.DEFAULT_REALM_NAME);
          @@ -1119,7 +1119,7 @@ public void notSettingNullableToPrimaryKeyThrows() throws IOException {
                                   .migration(new RealmMigration() {
                                       @Override
                                       public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          -                                // intentionally left empty to preserve not-nullablility of PrimaryKey on old schema.
          +                                // Intentionally lefts empty to preserve not-nullablility of PrimaryKey on old schema.
                                       }
                                   })
                                   .build();
          @@ -1138,7 +1138,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                   }
               }
           
          -    // Migrate a nullable field containing null value to non-nullable PrimaryKey field throws Realm version 0.89+
          +    // Migrates a nullable field containing null value to non-nullable PrimaryKey field throws Realm version 0.89+.
               @Test
               public void migrating_nullableField_toward_notNullable_PrimaryKeyThrows() throws IOException {
                   configFactory.copyRealmFromAssets(context, "default-nullable-primarykey.realm", Realm.DEFAULT_REALM_NAME);
          @@ -1151,7 +1151,7 @@ public void migrating_nullableField_toward_notNullable_PrimaryKeyThrows() throws
                                   .migration(new RealmMigration() {
                                       @Override
                                       public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          -                                // intentionally left empty to demonstrate incompatibilities between nullable/not-nullable PrimaryKeys.
          +                                // intentionally lefts empty to demonstrate incompatibilities between nullable/not-nullable PrimaryKeys.
                                       }
                                   })
                                   .build();
          @@ -1171,12 +1171,12 @@ public void realmOpenBeforeMigrationThrows() throws FileNotFoundException {
                   realm = Realm.getInstance(config);
           
                   try {
          -            // Trigger manual migration. This can potentially change the schema, so should only be allowed when
          +            // Triggers manual migration. This can potentially change the schema, so should only be allowed when
                       // no-one else is working on the Realm.
                       Realm.migrateRealm(config, new RealmMigration() {
                           @Override
                           public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          -                    // Do nothing
          +                    // Does nothing.
                           }
                       });
                       fail();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java
          index 95f063b2f2..8447b2ce2e 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java
          @@ -51,7 +51,7 @@
           import static org.junit.Assert.assertTrue;
           import static org.junit.Assume.assumeThat;
           
          -// tests API methods when using a model class implementing RealmModel instead
          +// Tests API methods when using a model class implementing RealmModel instead
           // of extending RealmObject.
           @RunWith(AndroidJUnit4.class)
           public class RealmModelTests {
          @@ -102,8 +102,8 @@ private void populateTestRealm(Realm realm, int objects) {
           
               @Test
               public void createObject() {
          -        for (int i = 1; i < 43; i++) { // using i = 0 as PK will crash subsequent createObject
          -                                       // since createObject uses default values
          +        for (int i = 1; i < 43; i++) { // Using i = 0 as PK will crash subsequent createObject
          +                                       // since createObject uses default values.
                       realm.beginTransaction();
                       realm.createObject(AllTypesRealmModel.class, i);
                       realm.commitTransaction();
          @@ -183,7 +183,7 @@ public void createOrUpdateAllFromJson() throws IOException {
                   assertEquals("Dog5", obj.columnRealmList.get(0).getName());
               }
           
          -    // where with filed selection
          +    // 'where' with filed selection.
               @Test
               public void query() {
                   populateTestRealm(realm, TEST_DATA_SIZE);
          @@ -191,7 +191,7 @@ public void query() {
                   assertEquals(5, realm.where(AllTypesRealmModel.class).greaterThanOrEqualTo(AllTypesRealmModel.FIELD_DOUBLE, 8.1415).count());
               }
           
          -    // async where with filed selection
          +    // Async where with filed selection.
               @Test
               @RunTestInLooperThread
               public void async_query() {
          @@ -258,9 +258,9 @@ public void dynamicRealm() {
                   looperThread.testComplete();
               }
           
          -    // exception expected when using in schema model not annotated
          -    // a valid model need to implement the interface RealmModel and annotate the class with @RealmClass
          -    // we expect in this test a runtime exception 'InvalidRealmModel is not part of the schema for this Realm.'
          +    // Exception expected when using in schema model not annotated.
          +    // A valid model need to implement the interface RealmModel and annotate the class with @RealmClass.
          +    // We expect in this test a runtime exception 'InvalidRealmModel is not part of the schema for this Realm.'.
               @Test(expected = RealmException.class)
               public void invalidModelDefinition() {
                   realm.beginTransaction();
          @@ -268,8 +268,8 @@ public void invalidModelDefinition() {
                   realm.commitTransaction();
               }
           
          -    // Test the behaviour of a RealmModel, containing a RealmList
          -    // of other RealmModel, in managed and unmanaged mode
          +    // Tests the behaviour of a RealmModel, containing a RealmList
          +    // of other RealmModel, in managed and unmanaged mode.
               @Test
               public void realmModelWithRealmListOfRealmModel() {
                   RealmList allTypesRealmModels = new RealmList();
          @@ -297,8 +297,8 @@ public void realmModelWithRealmListOfRealmModel() {
                   assertEquals(1, all.first().getColumnRealmList().first().columnLong);
               }
           
          -    // Test the behaviour of a RealmModel, containing a RealmList
          -    // of RealmObject, in managed and unmanaged mode
          +    // Tests the behaviour of a RealmModel, containing a RealmList
          +    // of RealmObject, in managed and unmanaged mode.
               @Test
               public void realmModelWithRealmListOfRealmObject() {
                   RealmList allTypes = new RealmList();
          @@ -326,8 +326,8 @@ public void realmModelWithRealmListOfRealmObject() {
                   assertEquals(1, all.first().getColumnRealmList().first().getColumnLong());
               }
           
          -    // Test the behaviour of a RealmObject, containing a RealmList
          -    // of RealmModel, in managed and unmanaged mode
          +    // Tests the behaviour of a RealmObject, containing a RealmList
          +    // of RealmModel, in managed and unmanaged mode.
               @Test
               public void realmObjectWithRealmListOfRealmModel() {
                   RealmList allTypesRealmModel = new RealmList();
          @@ -355,7 +355,7 @@ public void realmObjectWithRealmListOfRealmModel() {
                   assertEquals(1, all.first().getColumnRealmList().first().columnLong);
               }
           
          -    // Test the behaviour of a RealmModel, containing a RealmModel field
          +    // Tests the behaviour of a RealmModel, containing a RealmModel field.
               @Test
               public void realmModelWithRealmModelField() {
                   RealmModelWithRealmModelField realmModelWithRealmModelField = new RealmModelWithRealmModelField();
          @@ -372,7 +372,7 @@ public void realmModelWithRealmModelField() {
                   assertEquals(42, all.first().getAllTypesRealmModel().columnLong);
               }
           
          -    // Test the behaviour of a RealmObject, containing a RealmModel field
          +    // Tests the behaviour of a RealmObject, containing a RealmModel field.
               @Test
               public void realmObjectWithRealmModelField() {
                   RealmObjectWithRealmModelField realmObjectWithRealmModelField = new RealmObjectWithRealmModelField();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java
          index 91f85404b5..dc37e6c8ad 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java
          @@ -98,7 +98,7 @@ private RealmObject addPrimaryKeyObjectToTestRealm(Realm testRealm) throws NoSuc
                   return obj;
               }
           
          -    // create a RealmObject with null primarykey
          +    // Creates a RealmObject with null primarykey.
               private void createNullPrimaryKeyObjectFromTestRealm(Realm testRealm) {
                   testRealm.beginTransaction();
           
          @@ -112,7 +112,7 @@ private void createNullPrimaryKeyObjectFromTestRealm(Realm testRealm) {
                   testRealm.commitTransaction();
               }
           
          -    // update existing null PrimaryKey object with a new updating value.
          +    // Updates existing null PrimaryKey object with a new updating value.
               private void updatePrimaryKeyObject(Realm testRealm, RealmObject realmObject) {
                   if (testClazz.equals(PrimaryKeyAsString.class)) {
                       ((PrimaryKeyAsString) realmObject).setId((long) updatingFieldValue);
          @@ -125,7 +125,7 @@ private void updatePrimaryKeyObject(Realm testRealm, RealmObject realmObject) {
                   testRealm.commitTransaction();
               }
           
          -    // @PrimaryKey annotation accept null value properly as a primary key value for Realm version 0.89.1+
          +    // @PrimaryKey annotation accept null value properly as a primary key value for Realm version 0.89.1+.
               @Test
               public void copyToRealm_primaryKeyIsNull() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
                   addPrimaryKeyObjectToTestRealm(realm);
          @@ -144,7 +144,7 @@ public void copyToRealm_primaryKeyIsNull() throws NoSuchMethodException, Instant
                   }
               }
           
          -    // @PrimaryKey annotation accept & update null value properly as a primary key value for Realm version 0.89.1+
          +    // @PrimaryKey annotation accept & update null value properly as a primary key value for Realm version 0.89.1+.
               @Test
               public void copyToRealmOrUpdate_primaryKeyFieldIsNull() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
                   RealmObject obj = addPrimaryKeyObjectToTestRealm(realm);
          @@ -163,7 +163,7 @@ public void copyToRealmOrUpdate_primaryKeyFieldIsNull() throws NoSuchMethodExcep
           
                   }
           
          -        // commit to the Realm
          +        // Commits to the Realm.
                   updatePrimaryKeyObject(realm, obj);
           
                   if (testClazz.equals(PrimaryKeyAsString.class)) {
          @@ -173,7 +173,7 @@ public void copyToRealmOrUpdate_primaryKeyFieldIsNull() throws NoSuchMethodExcep
                   }
               }
           
          -    // @PrimaryKey annotation creates null value properly as a primary key value for Realm version 0.89.1+
          +    // @PrimaryKey annotation creates null value properly as a primary key value for Realm version 0.89.1+.
               @Test
               public void createObject_primaryKeyFieldIsNull() {
                   createNullPrimaryKeyObjectFromTestRealm(realm);
          @@ -192,7 +192,7 @@ public void createObject_primaryKeyFieldIsNull() {
                   }
               }
           
          -    // @PrimaryKey annotation checked duplicated null value properly as a primary key value for Realm version 0.89.1+
          +    // @PrimaryKey annotation checked duplicated null value properly as a primary key value for Realm version 0.89.1+.
               @Test
               public void createObject_duplicatedNullPrimaryKeyThrows() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
                   addPrimaryKeyObjectToTestRealm(realm);
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java
          index efd306014f..a29dbee7d4 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java
          @@ -53,7 +53,7 @@ public class RealmObjectSchemaTests {
               @Before
               public void setUp() {
                   RealmConfiguration realmConfig = configFactory.createConfiguration();
          -        Realm.getInstance(realmConfig).close(); // Create Schema
          +        Realm.getInstance(realmConfig).close(); // Creates Schema.
                   realm = DynamicRealm.getInstance(realmConfig);
                   realmSchema = realm.getSchema();
                   DOG_SCHEMA = realmSchema.get("Dog");
          @@ -200,7 +200,7 @@ public void addRemoveField() {
                   }
               }
           
          -    // Check that field is actually added and that it can be removed again.
          +    // Checks that field is actually added and that it can be removed again.
               private void checkAddedAndRemovable(String fieldName) {
                   assertTrue(schema.hasField(fieldName));
                   schema.removeField(fieldName);
          @@ -276,8 +276,8 @@ public void requiredFieldAttribute() {
                   for (FieldType fieldType : FieldType.values()) {
                       String fieldName = "foo";
                       switch (fieldType) {
          -                case OBJECT: continue; // Not possible
          -                case LIST: continue; // Not possible
          +                case OBJECT: continue; // Not possible.
          +                case LIST: continue; // Not possible.
                           default:
                               // All simple types
                               schema.addField(fieldName, fieldType.getType(), FieldAttribute.REQUIRED);
          @@ -387,7 +387,7 @@ public void addPrimaryKeyFieldModifier_duplicateValues() {
                       final String fieldName = "foo";
                       schema.addField(fieldName, fieldType.getType());
           
          -            // create multiple objects with same values.
          +            // Creates multiple objects with same values.
                       realm.createObject(schema.getClassName());
                       realm.createObject(schema.getClassName());
           
          @@ -395,7 +395,7 @@ public void addPrimaryKeyFieldModifier_duplicateValues() {
                           schema.addPrimaryKey(fieldName);
                           fail();
                       } catch (IllegalArgumentException e) {
          -                // check if message reports correct field name.
          +                // Checks if message reports correct field name.
                           assertTrue(e.getMessage().contains("\"" + fieldName + "\""));
                       }
                       schema.removeField(fieldName);
          @@ -461,7 +461,7 @@ public void setRemoveNullable() {
                               }
                               break;
                           default:
          -                    // All simple types
          +                    // All simple types.
                               schema.addField(fieldName, fieldType.getType());
                               assertEquals(fieldType.isNullable(), schema.isNullable(fieldName));
                               schema.setNullable(fieldName, !fieldType.isNullable());
          @@ -497,7 +497,7 @@ public void setRemoveRequired() {
                               }
                               break;
                           default:
          -                    // All simple types
          +                    // All simple types.
                               schema.addField(fieldName, fieldType.getType());
                               assertEquals(!fieldType.isNullable(), schema.isRequired(fieldName));
                               schema.setRequired(fieldName, fieldType.isNullable());
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java
          index cba3faf225..c8d3a4025f 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java
          @@ -140,8 +140,8 @@ public void stringEncoding() {
                   }
               }
           
          -    // invalid surrogate pairs:
          -    // both high and low should lead to an IllegalArgumentException
          +    // Invalid surrogate pairs:
          +    // Both high and low should lead to an IllegalArgumentException.
               @Test
               public void invalidSurrogates() {
                   String high = "Invalid high surrogate \uD83C\uD83C\uDF51";
          @@ -168,7 +168,7 @@ public void invalidSurrogates() {
                   realm.cancelTransaction();
               }
           
          -    // removing original object and see if has been removed
          +    // Removes original object and sees if has been removed.
               @Test
               public void deleteFromRealm() {
                   realm.beginTransaction();
          @@ -195,7 +195,7 @@ public void deleteFromRealm() {
                       fail();
                   } catch (IllegalStateException ignored) {}
           
          -        // deleting rex twice should fail
          +        // Deleting rex twice should fail.
                   realm.beginTransaction();
                   try {
                       rex.deleteFromRealm();
          @@ -236,7 +236,7 @@ public void deleteFromRealm_throwOnUnmanagedObject() {
                   }
               }
           
          -    // query for an object, remove it and see it has been removed from realm
          +    // Queries for an object, removes it and sees it has been removed from realm.
               @Test
               public void deleteFromRealm_removedFromResults() {
                   realm.beginTransaction();
          @@ -270,7 +270,7 @@ public void deleteFromRealm_removedFromResults() {
               }
           
               private void removeOneByOne(boolean removeFromFront) {
          -        // Create test data
          +        // Creates test data.
                   realm.beginTransaction();
                   realm.delete(Dog.class);
                   for (int i = 0; i < TEST_SIZE; i++) {
          @@ -278,11 +278,11 @@ private void removeOneByOne(boolean removeFromFront) {
                   }
                   realm.commitTransaction();
           
          -        // Check initial size
          +        // Checks initial size.
                   RealmResults dogs = realm.where(Dog.class).findAll();
                   assertEquals(TEST_SIZE, dogs.size());
           
          -        // Check that calling deleteFromRealm doesn't remove the object from the RealmResult
          +        // Checks that calling deleteFromRealm doesn't remove the object from the RealmResult.
                   realm.beginTransaction();
                   for (int i = 0; i < TEST_SIZE; i++) {
                       dogs.get(removeFromFront ? i : TEST_SIZE - 1 - i).deleteFromRealm();
          @@ -293,7 +293,7 @@ private void removeOneByOne(boolean removeFromFront) {
                   assertEquals(0, realm.where(Dog.class).count());
               }
           
          -    // Tests calling deleteFromRealm on a RealmResults instead of RealmResults.remove()
          +    // Tests calling deleteFromRealm on a RealmResults instead of RealmResults.remove().
               @Test
               public void deleteFromRealm_atPosition() {
                   removeOneByOne(REMOVE_FIRST);
          @@ -476,11 +476,11 @@ public void hashCode_cyclicObject() {
                   final CyclicType foo = createCyclicData();
                   realm.commitTransaction();
           
          -        // Check that the hash code is always the same between multiple calls.
          +        // Checks that the hash code is always the same between multiple calls.
                   assertEquals(foo.hashCode(), foo.hashCode());
          -        // Check that the hash code is the same among same object
          +        // Checks that the hash code is the same among same object.
                   assertEquals(foo.hashCode(), realm.where(CyclicType.class).equalTo("name", foo.getName()).findFirst().hashCode());
          -        // hash code is different from other objects.
          +        // Hash code is different from other objects.
                   assertNotEquals(foo.getObject().hashCode(), foo.hashCode());
           
                   final int originalHashCode = foo.hashCode();
          @@ -490,10 +490,10 @@ public void execute(Realm realm) {
                           foo.setName(foo.getName() + "1234");
                       }
                   });
          -        // Check that Updating the value of its field does not affect the hash code.
          +        // Checks that Updating the value of its field does not affect the hash code.
                   assertEquals(originalHashCode, foo.hashCode());
           
          -        // Check the hash code of the object from a Realm in different file name.
          +        // Checks the hash code of the object from a Realm in different file name.
                   RealmConfiguration realmConfig_differentName = configFactory.createConfiguration(
                           "another_" + realmConfig.getRealmFileName());
                   Realm realm_differentName = Realm.getInstance(realmConfig_differentName);
          @@ -508,7 +508,7 @@ public void execute(Realm realm) {
                       realm_differentName.close();
                   }
           
          -        // Check the hash code of the object from a Realm in different directory.
          +        // Checks the hash code of the object from a Realm in different directory.
                   RealmConfiguration realmConfig_differentPath = configFactory.createConfiguration(
                           "anotherDir", realmConfig.getRealmFileName());
                   Realm realm_differentPath = Realm.getInstance(realmConfig_differentPath);
          @@ -539,7 +539,7 @@ private CyclicType createCyclicData(Realm realm) {
                   CyclicType bar = realm.createObject(CyclicType.class);
                   bar.setName("Bar");
           
          -        // Setup cycle on normal object references
          +        // Setups cycle on normal object references.
                   foo.setObject(bar);
                   bar.setObject(foo);
                   return foo;
          @@ -708,7 +708,7 @@ public void setter_link_objectFromAnotherThread() throws InterruptedException {
                       public void run() {
                           Realm realm = Realm.getInstance(realmConfig);
           
          -                // 1. create an object
          +                // 1. Creates an object.
                           realm.beginTransaction();
                           objFromAnotherThread.set(realm.createObject(CyclicType.class));
                           realm.commitTransaction();
          @@ -719,14 +719,14 @@ public void run() {
                           } catch (InterruptedException ignored) {
                           }
           
          -                // 3. close Realm in this thread and finish.
          +                // 3. Closes Realm in this thread and finishes.
                           realm.close();
                       }
                   };
                   thread.start();
           
                   createLatch.await();
          -        // 2. set created object to target
          +        // 2. Sets created object to target.
                   realm.beginTransaction();
                   try {
                       CyclicType target = realm.createObject(CyclicType.class);
          @@ -740,7 +740,7 @@ public void run() {
                       realm.cancelTransaction();
                   }
           
          -        // wait for finishing the thread
          +        // Waits for finishing the thread.
                   thread.join();
               }
           
          @@ -778,7 +778,7 @@ public void setter_list_withDeletedObject() {
           
                       RealmList list = new RealmList<>();
                       list.add(realm.createObject(CyclicType.class));
          -            list.add(removed); // List contains a deleted object
          +            list.add(removed); // List contains a deleted object.
                       list.add(realm.createObject(CyclicType.class));
           
                       try {
          @@ -806,7 +806,7 @@ public void setter_list_withClosedObject() {
           
                       RealmList list = new RealmList<>();
                       list.add(realm.createObject(CyclicType.class));
          -            list.add(closed); // List contains a closed object
          +            list.add(closed); // List contains a closed object.
                       list.add(realm.createObject(CyclicType.class));
           
                       try {
          @@ -835,7 +835,7 @@ public void setter_list_withObjectFromAnotherRealm() {
           
                           RealmList list = new RealmList<>();
                           list.add(realm.createObject(CyclicType.class));
          -                list.add(objFromAnotherRealm); // List contains an object from another Realm
          +                list.add(objFromAnotherRealm); // List contains an object from another Realm.
                           list.add(realm.createObject(CyclicType.class));
           
                           try {
          @@ -863,7 +863,7 @@ public void setter_list_withObjectFromAnotherThread() throws InterruptedExceptio
                       public void run() {
                           Realm realm = Realm.getInstance(realmConfig);
           
          -                // 1. create an object
          +                // 1. Creates an object.
                           realm.beginTransaction();
                           objFromAnotherThread.set(realm.createObject(CyclicType.class));
                           realm.commitTransaction();
          @@ -874,14 +874,14 @@ public void run() {
                           } catch (InterruptedException ignored) {
                           }
           
          -                // 3. close Realm in this thread and finish.
          +                // 3. Close Realm in this thread and finishes.
                           realm.close();
                       }
                   };
                   thread.start();
           
                   createLatch.await();
          -        // 2. set created object to target
          +        // 2. Sets created object to target.
                   realm.beginTransaction();
                   try {
                       CyclicType target = realm.createObject(CyclicType.class);
          @@ -901,7 +901,7 @@ public void run() {
                       realm.cancelTransaction();
                   }
           
          -        // wait for finishing the thread
          +        // Waits for finishing the thread.
                   thread.join();
               }
           
          @@ -952,7 +952,7 @@ public void isValid_managedObject() {
                   assertTrue(allTypes.isValid());
               }
           
          -    // store and retrieve null values for nullable fields
          +    // Stores and retrieves null values for nullable fields.
               @Test
               public void set_get_nullOnNullableFields() {
                   realm.beginTransaction();
          @@ -1002,7 +1002,7 @@ public void set_get_nullOnNullableFields() {
                   assertNull(nullTypes.getFieldDateNull());
               }
           
          -    // store and retrieve non-null values when field can contain null strings
          +    // Stores and retrieves non-null values when field can contain null strings.
               @Test
               public void get_set_nonNullValueOnNullableFields() {
                   final String testString = "FooBar";
          @@ -1055,7 +1055,7 @@ public void get_set_nonNullValueOnNullableFields() {
                   assertEquals(testDate.getTime(), nullTypes.getFieldDateNull().getTime());
               }
           
          -    // try to store null values in non-nullable fields
          +    // Tries to store null values in non-nullable fields.
               @Test
               public void set_nullValuesToNonNullableFields() {
                   try {
          @@ -1187,7 +1187,7 @@ public void run() {
                   TestHelper.awaitOrFail(bgRealmDone);
                   realm.waitForChange();
           
          -        // Object should no longer be available
          +        // Object should no longer be available.
                   assertFalse(obj.isValid());
                   try {
                       obj.getColumnLong();
          @@ -1227,7 +1227,7 @@ public void isManaged_unmanagedObject() {
                   assertFalse(dog.isManaged());
               }
           
          -    // Test NaN value on float and double columns
          +    // Tests NaN value on float and double columns.
               @Test
               public void float_double_NaN() {
                   realm.beginTransaction();
          @@ -1242,7 +1242,7 @@ public void float_double_NaN() {
                   assertEquals(0, realm.where(AllTypes.class).equalTo("columnDouble", Double.NaN).count());
               }
           
          -    // Test max value on float and double columns
          +    // Tests max value on float and double columns.
               @Test
               public void float_double_maxValue() {
                   realm.beginTransaction();
          @@ -1256,7 +1256,7 @@ public void float_double_maxValue() {
                   assertEquals(1, realm.where(AllTypes.class).equalTo("columnDouble", Double.MAX_VALUE).count());
               }
           
          -    // Test min normal value on float and double columns
          +    // Tests min normal value on float and double columns.
               @Test
               public void float_double_minNormal() {
                   realm.beginTransaction();
          @@ -1270,7 +1270,7 @@ public void float_double_minNormal() {
                   assertEquals(1, realm.where(AllTypes.class).equalTo("columnDouble", Double.MIN_NORMAL).count());
               }
           
          -    // Test min value on float and double columns
          +    // Tests min value on float and double columns.
               @Test
               public void float_double_minValue() {
                   realm.beginTransaction();
          @@ -1284,7 +1284,7 @@ public void float_double_minValue() {
                   assertEquals(1, realm.where(AllTypes.class).equalTo("columnDouble", Double.MIN_VALUE).count());
               }
           
          -    // Test negative infinity value on float and double columns
          +    // Tests negative infinity value on float and double columns.
               @Test
               public void float_double_negativeInfinity() {
                   realm.beginTransaction();
          @@ -1298,7 +1298,7 @@ public void float_double_negativeInfinity() {
                   assertEquals(1, realm.where(AllTypes.class).equalTo("columnDouble", Double.NEGATIVE_INFINITY).count());
               }
           
          -    // Test positive infinity value on float and double columns
          +    // Tests positive infinity value on float and double columns.
               @Test
               public void float_double_positiveInfinity() {
                   realm.beginTransaction();
          @@ -1328,7 +1328,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                                   }
           
                                   final long newStrIndex;
          -                        // swap column indices
          +                        // Swaps column indices.
                                   if (strIndex < numberIndex) {
                                       table.addColumn(RealmFieldType.INTEGER, "number");
                                       newStrIndex = table.addColumn(RealmFieldType.STRING, "str");
          @@ -1346,7 +1346,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                           .migration(new RealmMigration() {
                               @Override
                               public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
          -                        // Do nothing
          +                        // Does nothing.
                               }
                           })
                           .schemaVersion(1L)
          @@ -1362,7 +1362,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
               public void realmProxy_columnIndex() throws FileNotFoundException {
                   final RealmConfiguration configForSwapped = prepareColumnSwappedRealm();
           
          -        // open swapped Realm in order to load column index
          +        // Opens swapped Realm in order to load column index.
                   Realm.getInstance(configForSwapped).close();
           
                   realm.executeTransaction(new Realm.Transaction() {
          @@ -1396,7 +1396,7 @@ public void execute(Realm realm) {
                       }
                   });
           
          -        // tests those values are persisted
          +        // Tests those values are persisted.
                   final ConflictingFieldName managed = realm.where(ConflictingFieldName.class).findFirst();
                   assertEquals("realm", managed.getRealm());
                   assertEquals("row", managed.getRow());
          @@ -1405,7 +1405,7 @@ public void execute(Realm realm) {
                   assertEquals("pendingQuery", managed.getPendingQuery());
                   assertEquals("currentTableVersion", managed.getCurrentTableVersion());
           
          -        // tests those values can be updated
          +        // Tests those values can be updated.
                   realm.executeTransaction(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
          @@ -1426,7 +1426,7 @@ public void execute(Realm realm) {
                   assertEquals("currentTableVersion_updated", managed.getCurrentTableVersion());
               }
           
          -    // Setting a not-nullable field to null is an error
          +    // Setting a not-nullable field to null is an error.
               // TODO Move this to RealmObjectTests?
               @Test
               public void setter_nullValueInRequiredField() {
          @@ -1506,7 +1506,7 @@ public void setter_nullValueInRequiredField() {
                   }
               }
           
          -    // Setting a nullable field to null is not an error
          +    // Setting a nullable field to null is not an error.
               // TODO Move this to RealmObjectsTest?
               @Test
               public void setter_nullValueInNullableField() {
          @@ -1773,7 +1773,7 @@ public void addChangeListener_shouldNotAddDupEntriesToHandlerRealmObjects() {
                       assertFalse(ref.get() == allTypesPrimaryKey);
                   }
           
          -        // Add different listeners twice
          +        // Adds different listeners twice.
                   allTypesPrimaryKey.addChangeListener(new RealmChangeListener() {
                       @Override
                       public void onChange(AllTypesPrimaryKey element) {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmPrimaryKeyTests.java
          index 155607bbfe..9484310bd4 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmPrimaryKeyTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmPrimaryKeyTests.java
          @@ -90,7 +90,7 @@ public RealmPrimaryKeyTests(Class testClazz, Class primar
                   this.secondaryFieldValue = secondaryFieldValue;
               }
           
          -    // @PrimaryKey + @Required annotation accept not-null value properly as a primary key value for Realm version 0.89.1+
          +    // @PrimaryKey + @Required annotation accept not-null value properly as a primary key value for Realm version 0.89.1+.
               @Test
               public void copyToRealmOrUpdate_requiredPrimaryKey() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
                   RealmObject obj = (RealmObject)testClazz.getConstructor(primaryKeyFieldType, secondaryFieldType).newInstance(primaryKeyFieldValue, secondaryFieldValue);
          @@ -104,7 +104,7 @@ public void copyToRealmOrUpdate_requiredPrimaryKey() throws NoSuchMethodExceptio
                   assertEquals(secondaryFieldValue, ((NullPrimaryKey)results.first()).getName());
               }
           
          -    // @PrimaryKey + @Required annotation does accept null as a primary key value for Realm version 0.89.1+
          +    // @PrimaryKey + @Required annotation does accept null as a primary key value for Realm version 0.89.1+.
               @Test
               public void copyToRealmOrUpdate_requiredPrimaryKeyThrows() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
                   RealmObject obj = (RealmObject)testClazz.getConstructor(primaryKeyFieldType, secondaryFieldType).newInstance(null, null);
          @@ -125,7 +125,7 @@ public void copyToRealmOrUpdate_requiredPrimaryKeyThrows() throws NoSuchMethodEx
                   }
               }
           
          -    // @PrimaryKey + @Required annotation does not accept null as a primary key value for Realm version 0.89.1+
          +    // @PrimaryKey + @Required annotation does not accept null as a primary key value for Realm version 0.89.1+.
               @Test
               public void createObject_nullPrimaryKeyValueThrows() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
                   realm.beginTransaction();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java
          index 9facd69f7f..1546c13524 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java
          @@ -95,7 +95,7 @@ public void validateTable_noDuplicateIndexInIndicesMap() {
                   final Set indexSet = new HashSet<>();
                   int indexCount = 0;
           
          -        // get index for each field and then put into set
          +        // Gets index for each field and then put into set.
                   for (Field field : Cat.class.getDeclaredFields()) {
                       if (Modifier.isStatic(field.getModifiers())) {
                           continue;
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java
          index 6ef934579b..b1dbf6c3f4 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java
          @@ -267,11 +267,11 @@ public void or() {
               public void not() {
                   populateTestRealm(); // create TEST_DATA_SIZE objects
           
          -        // only one object with value 5 -> TEST_DATA_SIZE-1 object with value "not 5"
          +        // Only one object with value 5 -> TEST_DATA_SIZE-1 object with value "not 5".
                   RealmResults list1 = realm.where(AllTypes.class).not().equalTo(AllTypes.FIELD_LONG, 5).findAll();
                   assertEquals(TEST_DATA_SIZE - 1, list1.size());
           
          -        // not().greater() and lessThenOrEqual() must be the same
          +        // not().greater() and lessThenOrEqual() must be the same.
                   RealmResults list2 = realm.where(AllTypes.class).not().greaterThan(AllTypes.FIELD_LONG, 5).findAll();
                   RealmResults list3 = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_LONG, 5).findAll();
                   assertEquals(list2.size(), list3.size());
          @@ -933,16 +933,16 @@ public void like_caseSensitiveWithNonLatinCharacters() {
                   assertEquals(0, resultList.size());
           
                   resultList = realm.where(AllTypes.class).like("columnString", "*Α*", Case.INSENSITIVE).findAll();
          -        //without ASCII-only limitation A matches α
          -        //assertEquals(3, resultList.size());
          +        // without ASCII-only limitation A matches α
          +        // assertEquals(3, resultList.size());
                   assertEquals(1, resultList.size());
           
                   resultList = realm.where(AllTypes.class).like("columnString", "*λ*", Case.INSENSITIVE).findAll();
                   assertEquals(2, resultList.size());
           
                   resultList = realm.where(AllTypes.class).like("columnString", "*Δ*", Case.INSENSITIVE).findAll();
          -        //without ASCII-only limitation Δ matches δ
          -        //assertEquals(1, resultList.size());
          +        // without ASCII-only limitation Δ matches δ
          +        // assertEquals(1, resultList.size());
                   assertEquals(0, resultList.size());
           
                   resultList = realm.where(AllTypes.class).like("columnString", "?λ*").findAll();
          @@ -997,14 +997,14 @@ public void queryLink() {
           
               @Test
               public void findAllSorted_multiFailures() {
          -        // zero fields specified
          +        // Zero fields specified.
                   try {
                       realm.where(AllTypes.class).findAllSorted(new String[]{}, new Sort[]{});
                       fail();
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // number of fields and sorting orders don't match
          +        // Number of fields and sorting orders don't match.
                   try {
                       realm.where(AllTypes.class).findAllSorted(new String[]{AllTypes.FIELD_STRING},
                               new Sort[]{Sort.ASCENDING, Sort.ASCENDING});
          @@ -1012,7 +1012,7 @@ public void findAllSorted_multiFailures() {
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // null is not allowed
          +        // Null is not allowed.
                   try {
                       realm.where(AllTypes.class).findAllSorted((String[]) null, null);
                       fail();
          @@ -1024,7 +1024,7 @@ public void findAllSorted_multiFailures() {
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // non-existing field name
          +        // Non-existing field name.
                   try {
                       realm.where(AllTypes.class)
                               .findAllSorted(new String[]{AllTypes.FIELD_STRING, "do-not-exist"},
          @@ -1115,7 +1115,7 @@ public void georgian() {
                   }
               }
           
          -    // Querying a non-nullable field with null is an error
          +    // Quering a non-nullable field with null is an error.
               @Test
               public void equalTo_notNullableFields() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1183,7 +1183,7 @@ public void equalTo_notNullableFields() {
                   }
               }
           
          -    // Querying a non-nullable field with null is an error
          +    // Querying a non-nullable field with null is an error.
               @Test
               public void isNull_notNullableFields() {
                   // 1 String
          @@ -1248,12 +1248,12 @@ public void isNull_notNullableFields() {
                   }
               }
           
          -    // Querying nullable PrimaryKey
          +    // Queries nullable PrimaryKey.
               @Test
               public void equalTo_nullPrimaryKeys() {
                   final long SECONDARY_FIELD_NUMBER = 49992417L;
                   final String SECONDARY_FIELD_STRING = "Realm is a mobile database hundreds of millions of people rely on.";
          -        // fill up a Realm with one user PrimaryKey value and 9 numeric values, starting from -5
          +        // Fills up a Realm with one user PrimaryKey value and 9 numeric values, starting from -5.
                   TestHelper.populateTestRealmWithStringPrimaryKey(realm,  (String) null,  SECONDARY_FIELD_NUMBER, 10, -5);
                   TestHelper.populateTestRealmWithBytePrimaryKey(realm,    (Byte) null,    SECONDARY_FIELD_STRING, 10, -5);
                   TestHelper.populateTestRealmWithShortPrimaryKey(realm,   (Short) null,   SECONDARY_FIELD_STRING, 10, -5);
          @@ -1276,7 +1276,7 @@ public void equalTo_nullPrimaryKeys() {
               public void isNull_nullPrimaryKeys() {
                   final long SECONDARY_FIELD_NUMBER = 49992417L;
                   final String SECONDARY_FIELD_STRING = "Realm is a mobile database hundreds of millions of people rely on.";
          -        // fill up a realm with one user PrimaryKey value and 9 numeric values, starting from -5
          +        // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5.
                   TestHelper.populateTestRealmWithStringPrimaryKey(realm,  (String) null,  SECONDARY_FIELD_NUMBER, 10, -5);
                   TestHelper.populateTestRealmWithBytePrimaryKey(realm,    (Byte) null,    SECONDARY_FIELD_STRING, 10, -5);
                   TestHelper.populateTestRealmWithShortPrimaryKey(realm,   (Short) null,   SECONDARY_FIELD_STRING, 10, -5);
          @@ -1299,7 +1299,7 @@ public void isNull_nullPrimaryKeys() {
               public void notEqualTo_nullPrimaryKeys() {
                   final long SECONDARY_FIELD_NUMBER = 49992417L;
                   final String SECONDARY_FIELD_STRING = "Realm is a mobile database hundreds of millions of people rely on.";
          -        // fill up a realm with one user PrimaryKey value and one numeric values, starting from -1
          +        // Fills up a realm with one user PrimaryKey value and one numeric values, starting from -1.
                   TestHelper.populateTestRealmWithStringPrimaryKey(realm,  (String) null,  SECONDARY_FIELD_NUMBER, 2, -1);
                   TestHelper.populateTestRealmWithBytePrimaryKey(realm,    (Byte) null,    SECONDARY_FIELD_STRING, 2, -1);
                   TestHelper.populateTestRealmWithShortPrimaryKey(realm,   (Short) null,   SECONDARY_FIELD_STRING, 2, -1);
          @@ -1355,7 +1355,7 @@ public void like_nullStringPrimaryKey() {
           
               @Test
               public void between_nullPrimaryKeysIsNotZero() {
          -        // fill up a realm with one user PrimaryKey value and 9 numeric values, starting from -5
          +        // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5.
                   TestHelper.populateTestRealmWithBytePrimaryKey(realm,    (Byte) null,    (String) null, 10, -5);
                   TestHelper.populateTestRealmWithShortPrimaryKey(realm,   (Short) null,   (String) null, 10, -5);
                   TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5);
          @@ -1373,7 +1373,7 @@ public void between_nullPrimaryKeysIsNotZero() {
           
               @Test
               public void greaterThan_nullPrimaryKeysIsNotZero() {
          -        // fill up a realm with one user PrimaryKey value and 9 numeric values, starting from -5
          +        // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5.
                   TestHelper.populateTestRealmWithBytePrimaryKey(realm,    (Byte) null,    (String) null, 10, -5);
                   TestHelper.populateTestRealmWithShortPrimaryKey(realm,   (Short) null,   (String) null, 10, -5);
                   TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5);
          @@ -1391,7 +1391,7 @@ public void greaterThan_nullPrimaryKeysIsNotZero() {
           
               @Test
               public void greaterThanOrEqualTo_nullPrimaryKeysIsNotZero() {
          -        // fill up a realm with one user PrimaryKey value and 9 numeric values, starting from -5
          +        // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5.
                   TestHelper.populateTestRealmWithBytePrimaryKey(realm,    (Byte) null,    (String) null, 10, -5);
                   TestHelper.populateTestRealmWithShortPrimaryKey(realm,   (Short) null,   (String) null, 10, -5);
                   TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5);
          @@ -1409,7 +1409,7 @@ public void greaterThanOrEqualTo_nullPrimaryKeysIsNotZero() {
           
               @Test
               public void lessThan_nullPrimaryKeysIsNotZero() {
          -        // fill up a realm with one user PrimaryKey value and 9 numeric values, starting from -5
          +        // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5.
                   TestHelper.populateTestRealmWithBytePrimaryKey(realm,    (Byte) null,    (String) null, 10, -5);
                   TestHelper.populateTestRealmWithShortPrimaryKey(realm,   (Short) null,   (String) null, 10, -5);
                   TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5);
          @@ -1427,7 +1427,7 @@ public void lessThan_nullPrimaryKeysIsNotZero() {
           
               @Test
               public void lessThanOrEqualTo_nullPrimaryKeysIsNotZero() {
          -        // fill up a realm with one user PrimaryKey value and 9 numeric values, starting from -5
          +        // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5.
                   TestHelper.populateTestRealmWithBytePrimaryKey(realm,    (Byte) null,    (String) null, 10, -5);
                   TestHelper.populateTestRealmWithShortPrimaryKey(realm,   (Short) null,   (String) null, 10, -5);
                   TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5);
          @@ -1443,7 +1443,7 @@ public void lessThanOrEqualTo_nullPrimaryKeysIsNotZero() {
                   assertEquals(7, realm.where(PrimaryKeyAsBoxedLong.class).lessThanOrEqualTo(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY,       1).count());
               }
           
          -    // Querying nullable fields, querying with equalTo null
          +    // Queries nullable fields with equalTo null.
               @Test
               public void equalTo_nullableFields() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1496,7 +1496,7 @@ public void equalTo_nullableFields() {
                   // 11 Object skipped, doesn't support equalTo query
               }
           
          -    // Querying nullable field for null
          +    // Queries nullable field for null.
               @Test
               public void isNull_nullableFields() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1525,7 +1525,7 @@ public void isNull_nullableFields() {
                   assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_OBJECT_NULL).count());
               }
           
          -    // Querying nullable field for not null
          +    // Queries nullable field for not null.
               @Test
               public void notEqualTo_nullableFields() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1562,7 +1562,7 @@ public void notEqualTo_nullableFields() {
                   // 11 Object skipped, doesn't support notEqualTo query
               }
           
          -    // Querying nullable field for not null
          +    // Queries nullable field for not null.
               @Test
               public void isNotNull_nullableFields() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1591,7 +1591,7 @@ public void isNotNull_nullableFields() {
                   assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_OBJECT_NULL).count());
               }
           
          -    // Querying nullable field with beginsWith - all strings begin with null
          +    // Queries nullable field with beginsWith - all strings begin with null.
               @Test
               public void beginWith_nullForNullableStrings() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1599,7 +1599,7 @@ public void beginWith_nullForNullableStrings() {
                           (String) null).findFirst().getFieldStringNotNull());
               }
           
          -    // Querying nullable field with contains - all strings contain null
          +    // Queries nullable field with contains - all strings contain null.
               @Test
               public void contains_nullForNullableStrings() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1607,7 +1607,7 @@ public void contains_nullForNullableStrings() {
                           (String) null).findFirst().getFieldStringNotNull());
               }
           
          -    // Querying nullable field with endsWith - all strings end with null
          +    // Queries nullable field with endsWith - all strings end with null.
               @Test
               public void endsWith_nullForNullableStrings() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1615,7 +1615,7 @@ public void endsWith_nullForNullableStrings() {
                           (String) null).findFirst().getFieldStringNotNull());
               }
           
          -    // Querying nullable field with like - nulls do not match either '?' or '*'
          +    // Queries nullable field with like - nulls do not match either '?' or '*'.
               @Test
               public void like_nullForNullableStrings() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1627,7 +1627,7 @@ public void like_nullForNullableStrings() {
                   assertEquals(0, resultList.size());
               }
           
          -    // Querying with between and table has null values in row.
          +    // Queries with between and table has null values in row.
               @Test
               public void between_nullValuesInRow() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1645,7 +1645,7 @@ public void between_nullValuesInRow() {
                           new Date(20000)).count());
               }
           
          -    // Querying with greaterThan and table has null values in row.
          +    // Queries with greaterThan and table has null values in row.
               @Test
               public void greaterThan_nullValuesInRow() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1663,7 +1663,7 @@ public void greaterThan_nullValuesInRow() {
                           new Date(5000)).count());
               }
           
          -    // Querying with greaterThanOrEqualTo and table has null values in row.
          +    // Queries with greaterThanOrEqualTo and table has null values in row.
               @Test
               public void greaterThanOrEqualTo_nullValuesInRow() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1681,7 +1681,7 @@ public void greaterThanOrEqualTo_nullValuesInRow() {
                           new Date(10000)).count());
               }
           
          -    // Querying with lessThan and table has null values in row.
          +    // Queries with lessThan and table has null values in row.
               @Test
               public void lessThan_nullValuesInRow() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1700,7 +1700,7 @@ public void lessThan_nullValuesInRow() {
           
               }
           
          -    // Querying with lessThanOrEqualTo and table has null values in row.
          +    // Queries with lessThanOrEqualTo and table has null values in row.
               @Test
               public void lessThanOrEqual_nullValuesInRow() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -1723,7 +1723,7 @@ public void lessThanOrEqual_nullValuesInRow() {
               @Test
               public void buildQueryFromResultsGC() {
                   // According to the testing, setting this to 10 can almost certainly trigger the GC.
          -        // Use 30 here can ensure GC happen. (Tested with 4.3 1G Ram and 5.0 3G Ram)
          +        // Uses 30 here can ensure GC happen. (Tested with 4.3 1G Ram and 5.0 3G Ram)
                   final int count = 30;
                   RealmResults results = realm.where(CatOwner.class).findAll();
           
          @@ -1731,7 +1731,7 @@ public void buildQueryFromResultsGC() {
                       @SuppressWarnings({"unused"})
                       byte garbage[] = TestHelper.allocGarbage(0);
                       results = results.where().findAll();
          -            System.gc(); // if a native resource has a reference count = 0, doing GC here might lead to a crash
          +            System.gc(); // If a native resource has a reference count = 0, doing GC here might lead to a crash.
                   }
               }
           
          @@ -1815,7 +1815,7 @@ public void notEqualTo_binary_multiFailures() {
                   }
               }
           
          -    // Test min on empty columns
          +    // Tests min on empty columns.
               @Test
               public void min_emptyColumns() {
                   RealmQuery query = realm.where(NullTypes.class);
          @@ -1825,7 +1825,7 @@ public void min_emptyColumns() {
                   assertNull(query.minimumDate(NullTypes.FIELD_DATE_NOT_NULL));
               }
           
          -    // Test min on columns with all null rows
          +    // Tests min on columns with all null rows.
               @Test
               public void min_allNullColumns() {
                   TestHelper.populateAllNullRowsForNumericTesting(realm);
          @@ -1837,7 +1837,7 @@ public void min_allNullColumns() {
                   assertNull(query.minimumDate(NullTypes.FIELD_DATE_NULL));
               }
           
          -    // Test min on columns with all non-null rows
          +    // Tests min on columns with all non-null rows.
               @Test
               public void min_allNonNullRows() {
                   TestHelper.populateAllNonNullRowsForNumericTesting(realm);
          @@ -1849,7 +1849,7 @@ public void min_allNonNullRows() {
                   assertEquals(-2000, query.minimumDate(NullTypes.FIELD_DATE_NULL).getTime());
               }
           
          -    // Test min on columns with partial null rows
          +    // Tests min on columns with partial null rows.
               @Test
               public void min_partialNullRows() {
                   TestHelper.populatePartialNullRowsForNumericTesting(realm);
          @@ -1870,7 +1870,7 @@ public void max_emptyColumns() {
                   assertNull(query.maximumDate(NullTypes.FIELD_DATE_NOT_NULL));
               }
           
          -    // Test max on columns with all null rows
          +    // Tests max on columns with all null rows.
               @Test
               public void max_allNullColumns() {
                   TestHelper.populateAllNullRowsForNumericTesting(realm);
          @@ -1882,7 +1882,7 @@ public void max_allNullColumns() {
                   assertNull(query.maximumDate(NullTypes.FIELD_DATE_NULL));
               }
           
          -    // Test max on columns with all non-null rows
          +    // Tests max on columns with all non-null rows.
               @Test
               public void max_allNonNullRows() {
                   TestHelper.populateAllNonNullRowsForNumericTesting(realm);
          @@ -1894,7 +1894,7 @@ public void max_allNonNullRows() {
                   assertEquals(12345, query.maximumDate(NullTypes.FIELD_DATE_NULL).getTime());
               }
           
          -    // Test max on columns with partial null rows
          +    // Tests max on columns with partial null rows.
               @Test
               public void max_partialNullRows() {
                   TestHelper.populatePartialNullRowsForNumericTesting(realm);
          @@ -1906,7 +1906,7 @@ public void max_partialNullRows() {
                   assertEquals(12345, query.maximumDate(NullTypes.FIELD_DATE_NULL).getTime());
               }
           
          -    // Test average on empty columns
          +    // Tests average on empty columns.
               @Test
               public void average_emptyColumns() {
                   RealmQuery query = realm.where(NullTypes.class);
          @@ -1915,7 +1915,7 @@ public void average_emptyColumns() {
                   assertEquals(0d, query.average(NullTypes.FIELD_DOUBLE_NULL), 0d);
               }
           
          -    // Test average on columns with all null rows
          +    // Tests average on columns with all null rows.
               @Test
               public void average_allNullColumns() {
                   TestHelper.populateAllNullRowsForNumericTesting(realm);
          @@ -1926,7 +1926,7 @@ public void average_allNullColumns() {
                   assertEquals(0d, query.average(NullTypes.FIELD_DOUBLE_NULL), 0d);
               }
           
          -    // Test average on columns with all non-null rows
          +    // Tests average on columns with all non-null rows.
               @Test
               public void average_allNonNullRows() {
                   TestHelper.populateAllNonNullRowsForNumericTesting(realm);
          @@ -1937,7 +1937,7 @@ public void average_allNonNullRows() {
                   assertEquals(8.0 / 3, query.average(NullTypes.FIELD_DOUBLE_NULL), 0.001d);
               }
           
          -    // Test average on columns with partial null rows
          +    // Tests average on columns with partial null rows.
               @Test
               public void average_partialNullRows() {
                   TestHelper.populatePartialNullRowsForNumericTesting(realm);
          @@ -1948,7 +1948,7 @@ public void average_partialNullRows() {
                   assertEquals(5.5, query.average(NullTypes.FIELD_DOUBLE_NULL), 0d);
               }
           
          -    // Test sum on empty columns
          +    // Tests sum on empty columns.
               @Test
               public void sum_emptyColumns() {
                   RealmQuery query = realm.where(NullTypes.class);
          @@ -1957,7 +1957,7 @@ public void sum_emptyColumns() {
                   assertEquals(0d, query.sum(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d);
               }
           
          -    // Test sum on columns with all null rows
          +    // Tests sum on columns with all null rows.
               @Test
               public void sum_allNullColumns() {
                   TestHelper.populateAllNullRowsForNumericTesting(realm);
          @@ -1968,7 +1968,7 @@ public void sum_allNullColumns() {
                   assertEquals(0d, query.sum(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d);
               }
           
          -    // Test sum on columns with all non-null rows
          +    // Tests sum on columns with all non-null rows.
               @Test
               public void sum_allNonNullRows() {
                   TestHelper.populateAllNonNullRowsForNumericTesting(realm);
          @@ -1979,7 +1979,7 @@ public void sum_allNonNullRows() {
                   assertEquals(8d, query.sum(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d);
               }
           
          -    // Test sum on columns with partial null rows
          +    // Tests sum on columns with partial null rows.
               @Test
               public void sum_partialNullRows() {
                   TestHelper.populatePartialNullRowsForNumericTesting(realm);
          @@ -1996,7 +1996,7 @@ public void count() {
                   assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).count());
               }
           
          -    // Test isNull on link's nullable field.
          +    // Tests isNull on link's nullable field.
               @Test
               public void isNull_linkField() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -2044,7 +2044,7 @@ public void isNull_linkField() {
                   }
               }
           
          -    // Test isNull on link's not-nullable field. should throw
          +    // Tests isNull on link's not-nullable field. Should throw.
               @Test
               public void isNull_linkFieldNotNullable() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -2122,7 +2122,7 @@ public void isNull_linkFieldNotNullable() {
                   // 11 Object skipped, doesn't support equalTo query
               }
           
          -    // Test isNotNull on link's nullable field.
          +    // Tests isNotNull on link's nullable field.
               @Test
               public void isNotNull_linkField() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -2169,7 +2169,7 @@ public void isNotNull_linkField() {
                   }
               }
           
          -    // Test isNotNull on link's not-nullable field. should throw
          +    // Tests isNotNull on link's not-nullable field. Should throw.
               @Test
               public void isNotNull_linkFieldNotNullable() {
                   TestHelper.populateTestRealmForNullTests(realm);
          @@ -2247,7 +2247,7 @@ public void isNotNull_linkFieldNotNullable() {
                   // 11 Object skipped, RealmObject is always nullable.
               }
           
          -    // Calling isNull on fields with the RealmList type will trigger an exception
          +    // Calling isNull on fields with the RealmList type will trigger an exception.
               @Test
               public void isNull_listFieldThrows() {
                   try {
          @@ -2265,7 +2265,7 @@ public void isNull_listFieldThrows() {
                   }
               }
           
          -    // Calling isNotNull on fields with the RealmList type will trigger an exception
          +    // Calling isNotNull on fields with the RealmList type will trigger an exception.
               @Test
               public void isNotNull_listFieldThrows() {
                   try {
          @@ -2328,7 +2328,7 @@ public void isValid_tableQuery() {
           
                   assertTrue(query.isValid());
                   populateTestRealm(realm, 1);
          -        // still valid if result changed
          +        // Still valid if result changed.
                   assertTrue(query.isValid());
           
                   realm.close();
          @@ -2343,14 +2343,14 @@ public void isValid_tableViewQuery() {
                   assertTrue(query.isValid());
           
                   populateTestRealm(realm, 1);
          -        // still valid if table view changed
          +        // Still valid if table view changed.
                   assertTrue(query.isValid());
           
                   realm.close();
                   assertFalse(query.isValid());
               }
           
          -    // test for https://github.com/realm/realm-java/issues/1905
          +    // Test for https://github.com/realm/realm-java/issues/1905
               @Test
               public void resultOfTableViewQuery() {
                   populateTestRealm();
          @@ -2378,7 +2378,7 @@ public void isValid_linkViewQuery() {
                   list.add(dog);
                   realm.commitTransaction();
           
          -        // still valid if base view changed
          +        // Still valid if base view changed.
                   assertEquals(listLength + 1, query.count());
                   assertTrue(query.isValid());
           
          @@ -2397,7 +2397,7 @@ public void isValid_removedParent() {
                   obj.deleteFromRealm();
                   realm.commitTransaction();
           
          -        // invalid if parent has been removed
          +        // Invalid if parent has been removed.
                   assertFalse(query.isValid());
               }
           
          @@ -2524,7 +2524,7 @@ public void isEmpty_invalidFieldNameThrows() {
                   }
               }
           
          -    // not-empty test harnesses
          +    // Not-empty test harnesses.
               private static final List SUPPORTED_IS_NOT_EMPTY_TYPES = Arrays.asList(
                       RealmFieldType.STRING,
                       RealmFieldType.BINARY,
          @@ -2647,15 +2647,15 @@ public void isNotEmpty_invalidFieldNameThrows() {
                   }
               }
           
          -    // Test that deep queries work on a lot of data
          +    // Tests that deep queries work on a lot of data.
               @Test
               public void deepLinkListQuery() {
                   realm.executeTransaction(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
           
          -                // Crash with i == 1000, 500, 100, 89, 85, 84
          -                // Doesn't crash for i == 10, 50, 75, 82, 83
          +                // Crashes with i == 1000, 500, 100, 89, 85, 84.
          +                // Doesn't crash for i == 10, 50, 75, 82, 83.
                           for (int i = 0; i < 84; i++) {
                               AllJavaTypes obj = realm.createObject(AllJavaTypes.class, i + 1);
                               obj.setFieldBoolean(i % 2 == 0);
          @@ -2714,7 +2714,7 @@ public void findAllSortedAsync_listOnSubObjectFieldThrows() {
                   realm.where(AllTypes.class).findAllSortedAsync(fieldNames, sorts);
               }
           
          -    // RealmQuery.distinct(): requires indexing, and type = boolean, integer, date, string
          +    // RealmQuery.distinct(): requires indexing, and type = boolean, integer, date, string.
               private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOfObjects, boolean withNull) {
                   realm.beginTransaction();
                   for (int i = 0; i < numberOfObjects * numberOfBlocks; i++) {
          @@ -2747,7 +2747,7 @@ private void populateForDistinctInvalidTypesLinked(Realm realm) {
               @Test
               public void distinct() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL);
          @@ -2788,7 +2788,7 @@ public void distinct_notIndexedFields() {
               @Test
               public void distinct_doesNotExist() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   try {
          @@ -2863,7 +2863,7 @@ public void distinctAsync() throws Throwable {
                   final AtomicInteger changeListenerCalled = new AtomicInteger(4);
                   final Realm realm = looperThread.realm;
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL);
          @@ -2951,7 +2951,7 @@ public void run() {
                               Realm.asyncTaskExecutor.pause();
                               asyncRealm = openRealmInstance("testDistinctAsyncQueryWithNull");
                               final long numberOfBlocks = 25;
          -                    final long numberOfObjects = 10; // must be greater than 1
          +                    final long numberOfObjects = 10; // Must be greater than 1
                               populateForDistinct(asyncRealm, numberOfBlocks, numberOfObjects, true);
           
                               final RealmResults distinctDate = asyncRealm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE);
          @@ -3070,7 +3070,7 @@ public void distinctAsync_notIndexedLinkedFields() {
               @Test
               public void distinctMultiArgs() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   RealmQuery query = realm.where(AnnotationIndexTypes.class);
          @@ -3083,7 +3083,7 @@ public void distinctMultiArgs_switchedFieldsOrder() {
                   final long numberOfBlocks = 25;
                   TestHelper.populateForDistinctFieldsOrder(realm, numberOfBlocks);
           
          -        // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples
          +        // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples.
                   RealmQuery query = realm.where(AnnotationIndexTypes.class);
                   RealmResults distinctStringLong = query.distinct(AnnotationIndexTypes.FIELD_INDEX_STRING, AnnotationIndexTypes.FIELD_INDEX_LONG);
                   RealmResults distinctLongString = query.distinct(AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_STRING);
          @@ -3099,47 +3099,47 @@ public void distinctMultiArgs_emptyField() {
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   RealmQuery query = realm.where(AnnotationIndexTypes.class);
          -        // an empty string field in the middle
          +        // An empty string field in the middle.
                   try {
                       query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, "", AnnotationIndexTypes.FIELD_INDEX_INT);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // an empty string field at the end
          +        // An empty string field at the end.
                   try {
                       query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, "");
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // a null string field in the middle
          +        // A null string field in the middle.
                   try {
                       query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null, AnnotationIndexTypes.FIELD_INDEX_INT);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // a null string field at the end
          +        // A null string field at the end.
                   try {
                       query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, (String) null);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // (String)null makes varargs a null array.
          +        // (String) Null makes varargs a null array.
                   try {
                       query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // Two (String)null for first and varargs fields
          +        // Two (String) null for first and varargs fields.
                   try {
                       query.distinct((String) null, (String) null);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // "" & (String)null combination
          +        // "" & (String) null combination.
                   try {
                       query.distinct("", (String) null);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // "" & (String)null combination
          +        // "" & (String) null combination.
                   try {
                       query.distinct((String) null, "");
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // Two empty fields tests
          +        // Two empty fields tests.
                   try {
                       query.distinct("", "");
                   } catch (IllegalArgumentException ignored) {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java
          index cbce2dfd79..312b93fe66 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java
          @@ -135,7 +135,7 @@ public void unsupportedMethods() {
                               case REMOVE_ALL: collection.removeAll(Collections.singletonList(new AllTypes())); break;
                               case RETAIN_ALL: collection.retainAll(Collections.singletonList(new AllTypes())); break;
           
          -                    // Supported methods
          +                    // Supported methods.
                               case DELETE_ALL:
                                   continue;
                           }
          @@ -152,7 +152,7 @@ public void unsupportedMethods() {
                               case SET: collection.set(0, new AllTypes()); break;
                               case REMOVE_INDEX: collection.remove(0); break;
           
          -                    // Supported methods
          +                    // Supported methods.
                               case DELETE_INDEX:
                               case DELETE_FIRST:
                               case DELETE_LAST:
          @@ -164,7 +164,7 @@ public void unsupportedMethods() {
                   }
               }
           
          -    // Triggered an ARM bug
          +    // Triggers an ARM bug.
               @Test
               public void verifyArmComparisons() {
                   realm.beginTransaction();
          @@ -185,7 +185,7 @@ public void verifyArmComparisons() {
                   assertEquals(10, realm.where(AllTypes.class).lessThan(AllTypes.FIELD_LONG, 0).findAll().size());
               }
           
          -    // RealmResults.distinct(): requires indexing, and type = boolean, integer, date, string
          +    // RealmResults.distinct(): requires indexing, and type = boolean, integer, date, string.
               private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOfObjects, boolean withNull) {
                   realm.beginTransaction();
                   for (int i = 0; i < numberOfObjects * numberOfBlocks; i++) {
          @@ -217,7 +217,7 @@ private void populateForDistinctInvalidTypesLinked(Realm realm) {
               @Test
               public void distinct() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll().distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL);
          @@ -234,16 +234,16 @@ public void distinct_restrictedByPreviousDistinct() {
                   final long numberOfObjects = 10;
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
          -        // all objects
          +        // All objects
                   RealmResults allResults = realm.where(AnnotationIndexTypes.class).findAll();
                   assertEquals("All Objects Count", numberOfBlocks * numberOfBlocks * numberOfObjects, allResults.size());
          -        // distinctive dates
          +        // Distinctive dates
                   RealmResults distinctDates = allResults.distinct(AnnotationIndexTypes.FIELD_INDEX_DATE);
                   assertEquals("Distinctive Dates", numberOfBlocks, distinctDates.size());
          -        // distinctive Booleans
          +        // Distinctive Booleans
                   RealmResults distinctBooleans = distinctDates.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL);
                   assertEquals("Distinctive Booleans", 2, distinctBooleans.size());
          -        // all three results are the same object
          +        // All three results are the same object
                   assertTrue(allResults == distinctDates);
                   assertTrue(allResults == distinctBooleans);
               }
          @@ -251,7 +251,7 @@ public void distinct_restrictedByPreviousDistinct() {
               @Test
               public void distinct_withNullValues() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, true);
           
                   for (String field : new String[]{AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) {
          @@ -263,7 +263,7 @@ public void distinct_withNullValues() {
               @Test
               public void distinct_notIndexedFields() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) {
          @@ -278,7 +278,7 @@ public void distinct_notIndexedFields() {
               @Test
               public void distinct_noneExistingField() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   try {
          @@ -304,7 +304,7 @@ public void distinct_invalidTypes() {
               @Test
               public void distinct_indexedLinkedFields() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, true);
           
                   for (String field : AnnotationIndexTypes.INDEX_FIELDS) {
          @@ -319,7 +319,7 @@ public void distinct_indexedLinkedFields() {
               @Test
               public void distinct_notIndexedLinkedFields() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, true);
           
                   for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) {
          @@ -351,7 +351,7 @@ public void changeListener_syncIfNeeded_updatedFromOtherThread() {
                   final RealmResults results = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_LONG, 10).findAll();
                   assertEquals(10, results.size());
           
          -        // 1. Delete first object from another thread.
          +        // 1. Deletes first object from another thread.
                   realm.executeTransactionAsync(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
          @@ -360,7 +360,7 @@ public void execute(Realm realm) {
                   }, new Realm.Transaction.OnSuccess() {
                       @Override
                       public void onSuccess() {
          -                // 2. RealmResults are refreshed before onSuccess is called
          +                // 2. RealmResults are refreshed before onSuccess is called.
                           assertEquals(9, results.size());
                           realm.close();
                           looperThread.testComplete();
          @@ -420,7 +420,7 @@ public void distinctAsync() throws Throwable {
                   final AtomicInteger changeListenerCalled = new AtomicInteger(4);
                   final Realm realm = looperThread.realm;
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL);
          @@ -496,7 +496,7 @@ public void distinctAsync_withNullValues() throws Throwable {
                   final AtomicInteger changeListenerCalled = new AtomicInteger(2);
                   final Realm realm = looperThread.realm;
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, true);
           
                   final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE);
          @@ -605,7 +605,7 @@ public void distinctAsync_notIndexedLinkedFields() {
               @Test
               public void distinctMultiArgs() {
                   final long numberOfBlocks = 25;
          -        final long numberOfObjects = 10; // must be greater than 1
          +        final long numberOfObjects = 10; // Must be greater than 1
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   RealmResults results = realm.where(AnnotationIndexTypes.class).findAll();
          @@ -618,7 +618,7 @@ public void distinctMultiArgs_switchedFieldsOrder() {
                   final long numberOfBlocks = 25;
                   TestHelper.populateForDistinctFieldsOrder(realm, numberOfBlocks);
           
          -        // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples
          +        // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples.
                   RealmResults results = realm.where(AnnotationIndexTypes.class).findAll();
                   RealmResults distinctStringLong = results.distinct(AnnotationIndexTypes.FIELD_INDEX_STRING, AnnotationIndexTypes.FIELD_INDEX_LONG);
                   RealmResults distinctLongString = results.distinct(AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_STRING);
          @@ -634,47 +634,47 @@ public void distinctMultiArgs_emptyField() {
                   populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
           
                   RealmResults results = realm.where(AnnotationIndexTypes.class).findAll();
          -        // an empty string field in the middle
          +        // An empty string field in the middle.
                   try {
                       results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, "", AnnotationIndexTypes.FIELD_INDEX_INT);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // an empty string field at the end
          +        // An empty string field at the end.
                   try {
                       results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, "");
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // a null string field in the middle
          +        // A null string field in the middle.
                   try {
                       results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, null, AnnotationIndexTypes.FIELD_INDEX_INT);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // a null string field at the end
          +        // A null string field at the end.
                   try {
                       results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, null);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // (String)null makes varargs a null array.
          +        // (String) Null makes varargs a null array.
                   try {
                       results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String)null);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // Two (String)null for first and varargs fields
          +        // Two (String) null for first and varargs fields.
                   try {
                       results.distinct(null, (String) null);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // "" & (String)null combination
          +        // "" & (String)null combination.
                   try {
                       results.distinct("", (String) null);
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // "" & (String)null combination
          +        // "" & (String)null combination.
                   try {
                       results.distinct(null, "");
                   } catch (IllegalArgumentException ignored) {
                   }
          -        // Two empty fields tests
          +        // Two empty fields tests.
                   try {
                       results.distinct("", "");
                   } catch (IllegalArgumentException ignored) {
          @@ -1029,12 +1029,12 @@ public void deleteAndDeleteAll() {
                   RealmResults stringOnlies = realm.where(StringOnly.class).findAll();
           
                   realm.beginTransaction();
          -        // remove one object
          +        // Removes one object.
                   stringOnlies.get(0).deleteFromRealm();
                   realm.commitTransaction();
           
                   realm.beginTransaction();
          -        // remove the rest
          +        // Removes the rest.
                   stringOnlies.deleteAllFromRealm();
                   realm.commitTransaction();
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java
          index 018f063f37..da49a66229 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java
          @@ -122,7 +122,7 @@ public void rename() {
               public void rename_invalidArgumentThrows() {
                   String[] illegalNames = new String[] { null, "" };
           
          -        // Test as first parameter
          +        // Tests as first parameter.
                   for (String illegalName : illegalNames) {
                       try {
                           realmSchema.rename(illegalName, AllJavaTypes.CLASS_NAME);
          @@ -131,7 +131,7 @@ public void rename_invalidArgumentThrows() {
                       }
                   }
           
          -        // Test as last parameters
          +        // Tests as last parameters.
                   for (String illegalName : illegalNames) {
                       try {
                           realmSchema.rename(AllJavaTypes.CLASS_NAME, illegalName);
          @@ -152,12 +152,12 @@ public void rename_shouldChangeInfoInPKTable() {
           
                   assertEquals(PrimaryKeyAsString.FIELD_PRIMARY_KEY, objectSchema.getPrimaryKey());
           
          -        // Create an object with the old name, and the PK should not exist after created.
          +        // Creates an object with the old name, and the PK should not exist after created.
                   RealmObjectSchema oldObjectSchema = realmSchema.create(PrimaryKeyAsString.CLASS_NAME);
                   oldObjectSchema.addField(PrimaryKeyAsString.FIELD_PRIMARY_KEY, String.class);
           
                   try {
          -            // It should not have primary key anymore at this point
          +            // It should not have primary key anymore at this point.
                       oldObjectSchema.getPrimaryKey();
                       fail();
                   } catch (IllegalStateException ignored) {
          @@ -217,7 +217,7 @@ public void remove_shouldRemoveInfoFromPKTable() {
                   objectSchema.addField(PrimaryKeyAsString.FIELD_PRIMARY_KEY, String.class);
           
                   try {
          -            // It should not have primary key anymore at this point
          +            // It should not have primary key anymore at this point.
                       objectSchema.getPrimaryKey();
                       fail();
                   } catch (IllegalStateException ignored) {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java
          index d449711600..4337317fcc 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java
          @@ -222,7 +222,7 @@ public void getInstance_writeProtectedFileWithContext() throws IOException {
           
               @Test
               public void getInstance_twiceWhenRxJavaUnavailable() {
          -        // test for https://github.com/realm/realm-java/issues/2416
          +        // Test for https://github.com/realm/realm-java/issues/2416
           
                   // Though it's not a recommended way to create multiple configuration instance with the same parameter, it's legal.
                   final RealmConfiguration configuration1 = configFactory.createConfiguration("no_RxJava.realm");
          @@ -693,7 +693,7 @@ public void execute(Realm realm) {
                           }
                       });
                   } catch (RuntimeException ignored) {
          -            // Ensure that we pass a valuable error message to the logger for developers.
          +            // Ensures that we pass a valuable error message to the logger for developers.
                       assertEquals(testLogger.message, "Could not cancel transaction, not currently in a transaction.");
                   } finally {
                       RealmLog.remove(testLogger);
          @@ -703,30 +703,30 @@ public void execute(Realm realm) {
           
               @Test
               public void delete_type() {
          -        // ** delete non existing table should succeed
          +        // ** Deletes non existing table should succeed.
                   realm.beginTransaction();
                   realm.delete(AllTypes.class);
                   realm.commitTransaction();
           
          -        // ** delete existing class, but leave other classes classes
          +        // ** Deletes existing class, but leaves other classes classes.
           
          -        // Add two classes
          +        // Adds two classes.
                   populateTestRealm();
                   realm.beginTransaction();
                   Dog dog = realm.createObject(Dog.class);
                   dog.setName("Castro");
                   realm.commitTransaction();
          -        // Clear
          +        // Clears.
                   realm.beginTransaction();
                   realm.delete(Dog.class);
                   realm.commitTransaction();
          -        // Check one class is cleared but other class is still there
          +        // Checks one class is cleared but other class is still there.
                   RealmResults resultListTypes = realm.where(AllTypes.class).findAll();
                   assertEquals(TEST_DATA_SIZE, resultListTypes.size());
                   RealmResults resultListDogs = realm.where(Dog.class).findAll();
                   assertEquals(0, resultListDogs.size());
           
          -        // ** delete() must throw outside a transaction
          +        // ** delete() must throw outside a transaction.
                   try {
                       realm.delete(AllTypes.class);
                       fail("Expected exception");
          @@ -856,19 +856,19 @@ public void unicodeStrings() {
           
               @Test
               public void getInstance_referenceCounting() {
          -        // At this point reference count should be one because of the setUp method
          +        // At this point reference count should be one because of the setUp method.
                   try {
                       realm.where(AllTypes.class).count();
                   } catch (IllegalStateException e) {
                       fail();
                   }
           
          -        // Make sure the reference counter is per realm file
          +        // Makes sure the reference counter is per realm file.
                   RealmConfiguration anotherConfig = configFactory.createConfiguration("anotherRealm.realm");
                   Realm.deleteRealm(anotherConfig);
                   Realm otherRealm = Realm.getInstance(anotherConfig);
           
          -        // Raise the reference
          +        // Raises the reference.
                   Realm realm = null;
                   try {
                       realm = Realm.getInstance(configFactory.createConfiguration());
          @@ -877,7 +877,7 @@ public void getInstance_referenceCounting() {
                   }
           
                   try {
          -            // This should not fail because the reference is now 1
          +            // This should not fail because the reference is now 1.
                       if (realm != null) {
                           realm.where(AllTypes.class).count();
                       }
          @@ -910,14 +910,14 @@ public void getInstance_referenceCounting() {
               @Test
               public void getInstance_referenceCounting_doubleClose() {
                   realm.close();
          -        realm.close(); // Count down once too many. Counter is now potentially negative
          +        realm.close(); // Counts down once too many. Counter is now potentially negative.
                   realm = Realm.getInstance(configFactory.createConfiguration());
                   realm.beginTransaction();
                   AllTypes allTypes = realm.createObject(AllTypes.class);
                   RealmResults queryResult = realm.where(AllTypes.class).findAll();
                   assertEquals(allTypes, queryResult.get(0));
                   realm.commitTransaction();
          -        realm.close(); // This might not close the Realm if the reference count is wrong
          +        realm.close(); // This might not close the Realm if the reference count is wrong.
           
                   // This should now fail due to the Realm being fully closed.
                   thrown.expect(IllegalStateException.class);
          @@ -946,14 +946,14 @@ public void writeCopyTo() throws IOException {
                       }
                   }
           
          -        // Copy is compacted i.e. smaller than original
          +        // Copy is compacted i.e. smaller than original.
                   File file1 = new File(configA.getPath());
                   File file2 = new File(configB.getPath());
                   assertTrue(file1.length() >= file2.length());
           
                   Realm realm2 = null;
                   try {
          -            // Contents is copied too
          +            // Contents is copied too.
                       realm2 = Realm.getInstance(configB);
                       RealmResults results = realm2.where(AllTypes.class).findAll();
                       assertEquals(1, results.size());
          @@ -1070,8 +1070,8 @@ public void copyToRealm_fromOtherRealm() {
                   AllTypes copiedAllTypes = otherRealm.copyToRealm(allTypes);
                   otherRealm.commitTransaction();
           
          -        assertNotSame(allTypes, copiedAllTypes); // Same object in different Realms is not the same
          -        assertEquals(allTypes.getColumnString(), copiedAllTypes.getColumnString()); // But data is still the same
          +        assertNotSame(allTypes, copiedAllTypes); // Same object in different Realms is not the same.
          +        assertEquals(allTypes.getColumnString(), copiedAllTypes.getColumnString()); // But data is still the same.
                   otherRealm.close();
               }
           
          @@ -1098,8 +1098,8 @@ public void copyToRealm() {
                   AllTypes realmTypes = realm.copyToRealm(allTypes);
                   realm.commitTransaction();
           
          -        assertNotSame(allTypes, realmTypes); // Objects should not be considered equal
          -        assertEquals(allTypes.getColumnString(), realmTypes.getColumnString()); // But they contain the same data
          +        assertNotSame(allTypes, realmTypes); // Objects should not be considered equal.
          +        assertEquals(allTypes.getColumnString(), realmTypes.getColumnString()); // But they contain the same data.
                   assertEquals(allTypes.getColumnLong(), realmTypes.getColumnLong());
                   assertEquals(allTypes.getColumnFloat(), realmTypes.getColumnFloat(), 0);
                   assertEquals(allTypes.getColumnDouble(), realmTypes.getColumnDouble(), 0);
          @@ -1128,8 +1128,8 @@ public void copyToRealm_cyclicObjectReferences() {
                   assertEquals("Two", realmObject.getObject().getName());
                   assertEquals(2, realm.where(CyclicType.class).count());
           
          -        // testing copyToRealm overload that uses the Iterator
          -        // making sure we reuse the same graph cache Map to avoid duplicates
          +        // Tests copyToRealm overload that uses the Iterator.
          +        // Makes sure we reuse the same graph cache Map to avoid duplicates.
                   realm.beginTransaction();
                   realm.deleteAll();
                   realm.commitTransaction();
          @@ -1160,8 +1160,8 @@ public void copyToRealm_cyclicObjectReferencesWithPK() {
                   assertEquals("Two", realmObject.getObject().getName());
                   assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count());
           
          -        // testing copyToRealm overload that uses the Iterator
          -        // making sure we reuse the same graph cache Map to avoid duplicates
          +        // Tests copyToRealm overload that uses the Iterator.
          +        // Makes sure we reuse the same graph cache Map to avoid duplicates.
                   realm.beginTransaction();
                   realm.deleteAll();
                   realm.commitTransaction();
          @@ -1194,7 +1194,7 @@ public void copyToRealm_cyclicListReferences() {
                   assertEquals(2, realm.where(CyclicType.class).count());
               }
           
          -    // Check that if a field has a null value it gets converted to the default value for that type
          +    // Checks that if a field has a null value, it gets converted to the default value for that type.
               @Test
               public void copyToRealm_convertsNullToDefaultValue() {
                   realm.beginTransaction();
          @@ -1290,11 +1290,11 @@ public void copyToRealm_duplicatedNullPrimaryKeyThrows() {
               public void copyToRealm_doNotCopyReferencedObjectIfManaged() {
                   realm.beginTransaction();
           
          -        // Child object is managed by Realm
          +        // Child object is managed by Realm.
                   CyclicTypePrimaryKey childObj = realm.createObject(CyclicTypePrimaryKey.class, 1);
                   childObj.setName("Child");
           
          -        // Parent object is an unmanaged object
          +        // Parent object is an unmanaged object.
                   CyclicTypePrimaryKey parentObj = new CyclicTypePrimaryKey(2);
                   parentObj.setObject(childObj);
           
          @@ -1370,7 +1370,7 @@ public void copyToRealmOrUpdate_stringPrimaryKeyFieldIsNull() {
                   assertEquals(null, result.first().getName());
                   assertEquals(SECONDARY_FIELD_VALUE, result.first().getId());
           
          -        // update objects
          +        // Updates objects.
                   realm.beginTransaction();
                   nullPrimaryKeyObj.setId(SECONDARY_FIELD_UPDATED);
                   realm.copyToRealmOrUpdate(nullPrimaryKeyObj);
          @@ -1390,7 +1390,7 @@ public void copyToRealmOrUpdate_boxedBytePrimaryKeyFieldIsNull() {
                   assertEquals(SECONDARY_FIELD_VALUE, result.first().getName());
                   assertEquals(null, result.first().getId());
           
          -        // update objects
          +        // Updates objects.
                   realm.beginTransaction();
                   nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED);
                   realm.copyToRealmOrUpdate(nullPrimaryKeyObj);
          @@ -1410,7 +1410,7 @@ public void copyToRealmOrUpdate_boxedShortPrimaryKeyFieldIsNull() {
                   assertEquals(SECONDARY_FIELD_VALUE, result.first().getName());
                   assertEquals(null, result.first().getId());
           
          -        // update objects
          +        // Updates objects.
                   realm.beginTransaction();
                   nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED);
                   realm.copyToRealmOrUpdate(nullPrimaryKeyObj);
          @@ -1430,7 +1430,7 @@ public void copyToRealmOrUpdate_boxedIntegerPrimaryKeyFieldIsNull() {
                   assertEquals(SECONDARY_FIELD_VALUE, result.first().getName());
                   assertEquals(null, result.first().getId());
           
          -        // update objects
          +        // Updates objects.
                   realm.beginTransaction();
                   nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED);
                   realm.copyToRealmOrUpdate(nullPrimaryKeyObj);
          @@ -1450,7 +1450,7 @@ public void copyToRealmOrUpdate_boxedLongPrimaryKeyFieldIsNull() {
                   assertEquals(SECONDARY_FIELD_VALUE, result.first().getName());
                   assertEquals(null, result.first().getId());
           
          -        // update objects
          +        // Updates objects.
                   realm.beginTransaction();
                   nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED);
                   realm.copyToRealmOrUpdate(nullPrimaryKeyObj);
          @@ -1522,7 +1522,7 @@ public void execute(Realm realm) {
                   assertEquals(1, realm.where(AllTypesPrimaryKey.class).count());
                   AllTypesPrimaryKey obj = realm.where(AllTypesPrimaryKey.class).findFirst();
           
          -        // Check that the the only element has all its properties updated
          +        // Checks that the the only element has all its properties updated.
                   assertEquals("Bar", obj.getColumnString());
                   assertEquals(1, obj.getColumnLong());
                   assertEquals(2.23F, obj.getColumnFloat(), 0);
          @@ -1560,7 +1560,7 @@ public void copyToRealmOrUpdate_cyclicObject() {
               }
           
           
          -    // Checks that an unmanaged object with only default values can override data
          +    // Checks that an unmanaged object with only default values can override data.
               @Test
               public void copyToRealmOrUpdate_defaultValuesOverrideExistingData() {
                   realm.executeTransaction(new Realm.Transaction() {
          @@ -1599,7 +1599,7 @@ public void execute(Realm realm) {
               }
           
           
          -    // Tests that if references to objects are removed, the objects are still in the Realm
          +    // Tests that if references to objects are removed, the objects are still in the Realm.
               @Test
               public void copyToRealmOrUpdate_referencesNotDeleted() {
                   realm.executeTransaction(new Realm.Transaction() {
          @@ -1766,7 +1766,7 @@ public void getInstance_differentEncryptionKeys() {
                   byte[] key1 = TestHelper.getRandomKey(42);
                   byte[] key2 = TestHelper.getRandomKey(42);
           
          -        // Make sure the key is the same, but in two different instances
          +        // Makes sure the key is the same, but in two different instances.
                   assertArrayEquals(key1, key2);
                   assertTrue(key1 != key2);
           
          @@ -1797,7 +1797,7 @@ public void writeEncryptedCopyTo() throws Exception {
                   long before = realm.where(AllTypes.class).count();
                   assertEquals(TEST_DATA_SIZE, before);
           
          -        // Configure test realms
          +        // Configures test realms.
                   final String ENCRYPTED_REALM_FILE_NAME = "encryptedTestRealm.realm";
                   final String RE_ENCRYPTED_REALM_FILE_NAME = "reEncryptedTestRealm.realm";
                   final String DECRYPTED_REALM_FILE_NAME = "decryptedTestRealm.realm";
          @@ -1810,21 +1810,21 @@ public void writeEncryptedCopyTo() throws Exception {
           
                   RealmConfiguration decryptedRealmConfig = configFactory.createConfiguration(DECRYPTED_REALM_FILE_NAME);
           
          -        // Write encrypted copy from a unencrypted Realm
          +        // Writes encrypted copy from a unencrypted Realm.
                   File destination = new File(encryptedRealmConfig.getPath());
                   realm.writeEncryptedCopyTo(destination, encryptedRealmConfig.getEncryptionKey());
           
                   Realm encryptedRealm = null;
                   try {
           
          -            // Verify encrypted Realm and write new encrypted copy with a new key
          +            // Verifies encrypted Realm and writes new encrypted copy with a new key.
                       encryptedRealm = Realm.getInstance(encryptedRealmConfig);
                       assertEquals(TEST_DATA_SIZE, encryptedRealm.where(AllTypes.class).count());
           
                       destination = new File(reEncryptedRealmConfig.getPath());
                       encryptedRealm.writeEncryptedCopyTo(destination, reEncryptedRealmConfig.getEncryptionKey());
           
          -            // Verify re-encrypted copy
          +            // Verifies re-encrypted copy.
                       Realm reEncryptedRealm = null;
                       try {
                           reEncryptedRealm = Realm.getInstance(reEncryptedRealmConfig);
          @@ -1838,11 +1838,11 @@ public void writeEncryptedCopyTo() throws Exception {
                           }
                       }
           
          -            // Write non-encrypted copy from the encrypted version
          +            // Writes non-encrypted copy from the encrypted version.
                       destination = new File(decryptedRealmConfig.getPath());
                       encryptedRealm.writeEncryptedCopyTo(destination, null);
           
          -            // Verify decrypted Realm and cleanup
          +            // Verifies decrypted Realm and cleans up.
                       Realm decryptedRealm = null;
                       try {
                           decryptedRealm = Realm.getInstance(decryptedRealmConfig);
          @@ -1880,24 +1880,24 @@ public void deleteRealm_failures() {
                   RealmConfiguration configA = configFactory.createConfiguration();
                   RealmConfiguration configB = configFactory.createConfiguration(OTHER_REALM_NAME);
           
          -        // This instance is already cached because of the setUp() method so this deletion should throw
          +        // This instance is already cached because of the setUp() method so this deletion should throw.
                   try {
                       Realm.deleteRealm(configA);
                       fail();
                   } catch (IllegalStateException ignored) {
                   }
           
          -        // Create a new Realm file
          +        // Creates a new Realm file.
                   Realm yetAnotherRealm = Realm.getInstance(configB);
           
          -        // Deleting it should fail
          +        // Deleting it should fail.
                   try {
                       Realm.deleteRealm(configB);
                       fail();
                   } catch (IllegalStateException ignored) {
                   }
           
          -        // But now that we close it deletion should work
          +        // But now that we close it deletion should work.
                   yetAnotherRealm.close();
                   try {
                       Realm.deleteRealm(configB);
          @@ -1911,7 +1911,7 @@ public void deleteRealm_failures() {
               public void setter_updateField() throws Exception {
                   realm.beginTransaction();
           
          -        // Create an owner with two dogs
          +        // Creates an owner with two dogs.
                   OwnerPrimaryKey owner = realm.createObject(OwnerPrimaryKey.class, 1);
                   owner.setName("Jack");
                   Dog rex = realm.createObject(Dog.class);
          @@ -1922,11 +1922,11 @@ public void setter_updateField() throws Exception {
                   owner.getDogs().add(fido);
                   assertEquals(2, owner.getDogs().size());
           
          -        // Changing the name of the owner should not affect the number of dogs
          +        // Changing the name of the owner should not affect the number of dogs.
                   owner.setName("Peter");
                   assertEquals(2, owner.getDogs().size());
           
          -        // Updating the user should not affect it either. This is actually a no-op since owner is a Realm backed object
          +        // Updating the user should not affect it either. This is actually a no-op since owner is a Realm backed object.
                   OwnerPrimaryKey owner2 = realm.copyToRealmOrUpdate(owner);
                   assertEquals(2, owner.getDogs().size());
                   assertEquals(2, owner2.getDogs().size());
          @@ -1949,7 +1949,7 @@ public void deleteRealm() throws InterruptedException {
                   final CountDownLatch closedLatch = new CountDownLatch(1);
           
                   Realm realm = Realm.getInstance(configuration);
          -        // Create another Realm to ensure the log files are generated
          +        // Creates another Realm to ensure the log files are generated.
                   new Thread(new Runnable() {
                       @Override
                       public void run() {
          @@ -1968,7 +1968,7 @@ public void run() {
                   realm.createObject(AllTypes.class);
                   realm.commitTransaction();
           
          -        // Wait for bg thread's opening the same Realm.
          +        // Waits for bg thread's opening the same Realm.
                   TestHelper.awaitOrFail(bgThreadReadyLatch);
           
                   // A core upgrade might change the location of the files
          @@ -1982,19 +1982,19 @@ public void run() {
           
                   assertTrue(Realm.deleteRealm(configuration));
           
          -        // Directory should be empty now
          +        // Directory should be empty now.
                   assertEquals(0, tempDir.listFiles().length);
               }
           
          -    // Test that all methods that require a transaction (ie. any function that mutates Realm data)
          +    // Tests that all methods that require a transaction. (ie. any function that mutates Realm data)
               @Test
               public void callMutableMethodOutsideTransaction() throws JSONException, IOException {
           
          -        // Prepare unmanaged object data
          +        // Prepares unmanaged object data.
                   AllTypesPrimaryKey t = new AllTypesPrimaryKey();
                   List ts = Arrays.asList(t, t);
           
          -        // Prepare JSON data
          +        // Prepares JSON data.
                   String jsonObjStr = "{ \"columnLong\" : 1 }";
                   JSONObject jsonObj = new JSONObject(jsonObjStr);
                   InputStream jsonObjStream = TestHelper.stringToStream(jsonObjStr);
          @@ -2005,7 +2005,7 @@ public void callMutableMethodOutsideTransaction() throws JSONException, IOExcept
                   InputStream jsonArrStream = TestHelper.stringToStream(jsonArrStr);
                   InputStream jsonArrStream2 = TestHelper.stringToStream(jsonArrStr);
           
          -        // Test all methods that should require a transaction
          +        // Tests all methods that should require a transaction.
                   try { realm.createObject(AllTypes.class);   fail(); } catch (IllegalStateException expected) {}
                   try { realm.copyToRealm(t);                 fail(); } catch (IllegalStateException expected) {}
                   try { realm.copyToRealm(ts);                fail(); } catch (IllegalStateException expected) {}
          @@ -2224,7 +2224,7 @@ public void createObject_defaultValueFromModelField() {
                   realm.executeTransaction(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
          -                // create a DefaultValueOfField with non-default primary key value
          +                // Creates a DefaultValueOfField with non-default primary key value.
                           realm.createObject(DefaultValueOfField.class,
                                   DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3);
                       }
          @@ -2241,7 +2241,7 @@ public void execute(Realm realm) {
                   testOneObjectFound(realm, DefaultValueOfField.class,
                           DefaultValueOfField.FIELD_INT,
                           DefaultValueOfField.FIELD_INT_DEFAULT_VALUE);
          -        // default value for pk must be ignored
          +        // Default value for pk must be ignored.
                   testNoObjectFound(realm, DefaultValueOfField.class,
                           DefaultValueOfField.FIELD_LONG_PRIMARY_KEY,
                           DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE);
          @@ -2292,7 +2292,7 @@ public void createObject_defaultValueFromModelConstructor() {
                   realm.executeTransaction(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
          -                // create a DefaultValueConstructor with non-default primary key value
          +                // Creates a DefaultValueConstructor with non-default primary key value.
                           realm.createObject(DefaultValueConstructor.class,
                                   DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3);
                       }
          @@ -2311,7 +2311,7 @@ public void execute(Realm realm) {
                   testOneObjectFound(realm, DefaultValueConstructor.class,
                           DefaultValueConstructor.FIELD_INT,
                           DefaultValueConstructor.FIELD_INT_DEFAULT_VALUE);;
          -        // default value for pk must be ignored
          +        // Default value for pk must be ignored.
                   testNoObjectFound(realm, DefaultValueConstructor.class,
                           DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY,
                                   DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE);
          @@ -2351,7 +2351,7 @@ public void createObject_defaultValueSetterInConstructor() {
                   realm.executeTransaction(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
          -                // create a DefaultValueSetter with non-default primary key value
          +                // Creates a DefaultValueSetter with non-default primary key value.
                           realm.createObject(DefaultValueSetter.class,
                                   DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3);
                       }
          @@ -2370,7 +2370,7 @@ public void execute(Realm realm) {
                   testOneObjectFound(realm, DefaultValueSetter.class,
                           DefaultValueSetter.FIELD_INT,
                           DefaultValueSetter.FIELD_INT_DEFAULT_VALUE);
          -        // default value for pk must be ignored
          +        // Default value for pk must be ignored.
                   testNoObjectFound(realm, DefaultValueSetter.class,
                           DefaultValueSetter.FIELD_LONG_PRIMARY_KEY,
                                   DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE);
          @@ -2476,7 +2476,7 @@ public void copyToRealm_defaultValuesAreIgnored() {
                   assertEquals(1, managedObj.getFieldList().size());
                   assertEquals(fieldListIntValue, managedObj.getFieldList().first().getFieldInt());
           
          -        // make sure that excess object by default value is not created.
          +        // Makes sure that excess object by default value is not created.
                   assertEquals(2, realm.where(RandomPrimaryKey.class).count());
               }
           
          @@ -2533,7 +2533,7 @@ public void copyFromRealm_defaultValuesAreIgnored() {
                   assertEquals(managedObj.getFieldList().first().getFieldInt(), copy.getFieldList().first().getFieldInt());
               }
           
          -    // Test close Realm in another thread different from where it is created.
          +    // Tests close Realm in another thread different from where it is created.
               @Test
               public void close_differentThread() throws InterruptedException {
                   final CountDownLatch latch = new CountDownLatch(1);
          @@ -2553,7 +2553,7 @@ public void run() {
                   });
                   thatThread.start();
           
          -        // Timeout should never happen
          +        // Timeout should never happen.
                   latch.await();
                   if (threadAssertionError[0] != null) {
                       throw threadAssertionError[0];
          @@ -2571,7 +2571,7 @@ public void isClosed() {
                   assertTrue(realm.isClosed());
               }
           
          -    // Test Realm#isClosed() in another thread different from where it is created.
          +    // Tests Realm#isClosed() in another thread different from where it is created.
               @Test
               public void isClosed_differentThread() throws InterruptedException {
                   final CountDownLatch latch = new CountDownLatch(1);
          @@ -2591,7 +2591,7 @@ public void run() {
                   });
                   thatThread.start();
           
          -        // Timeout should never happen
          +        // Timeout should never happen.
                   latch.await();
                   if (threadAssertionError[0] != null) {
                       throw threadAssertionError[0];
          @@ -2604,7 +2604,7 @@ public void run() {
           
               // Realm validation & initialization is done once, still ColumnIndices
               // should be populated for the subsequent Realm sharing the same configuration
          -    // even if we skip initialization & validation
          +    // even if we skip initialization & validation.
               @Test
               public void columnIndicesIsPopulatedWhenSkippingInitialization() throws Throwable {
                   final RealmConfiguration realmConfiguration = configFactory.createConfiguration("columnIndices");
          @@ -2616,7 +2616,7 @@ public void columnIndicesIsPopulatedWhenSkippingInitialization() throws Throwabl
                   new Thread(new Runnable() {
                       @Override
                       public void run() {
          -                Realm realm = Realm.getInstance(realmConfiguration); // This will populate columnIndices
          +                Realm realm = Realm.getInstance(realmConfiguration); // This will populate columnIndices.
                           try {
                               bgRealmOpened.countDown();
                               TestHelper.awaitOrFail(mainThreadRealmDone);
          @@ -2634,7 +2634,7 @@ public void run() {
           
                   TestHelper.awaitOrFail(bgRealmOpened);
                   Realm realm = Realm.getInstance(realmConfiguration);
          -        realm.where(AllTypes.class).equalTo("columnString", "Foo").findAll(); // This would crash if columnIndices == null
          +        realm.where(AllTypes.class).equalTo("columnString", "Foo").findAll(); // This would crash if columnIndices == null.
                   realm.close();
                   mainThreadRealmDone.countDown();
                   TestHelper.awaitOrFail(bgRealmClosed);
          @@ -2656,7 +2656,7 @@ public void isInTransaction() {
                   assertFalse(realm.isInTransaction());
               }
           
          -    // test for https://github.com/realm/realm-java/issues/1646
          +    // Test for https://github.com/realm/realm-java/issues/1646
               @Test
               public void closingRealmWhileOtherThreadIsOpeningRealm() throws Exception {
                   final CountDownLatch startLatch = new CountDownLatch(1);
          @@ -2686,11 +2686,11 @@ public void run() {
                       }
                   }.start();
           
          -        // prevent for another thread to enter Realm.createAndValidate().
          +        // Prevents for another thread to enter Realm.createAndValidate().
                   synchronized (BaseRealm.class) {
                       startLatch.countDown();
           
          -            // wait for another thread's entering Realm.createAndValidate().
          +            // Waits for another thread's entering Realm.createAndValidate().
                       SystemClock.sleep(100L);
           
                       realm.close();
          @@ -2720,7 +2720,7 @@ public void openRealmWhileTransactionInAnotherThread() throws Exception {
                   Thread thread = new Thread(new Runnable() {
                       @Override
                       public void run() {
          -                // Step 2: Open realm in background thread.
          +                // Step 2: Opens realm in background thread.
                           Realm realm = Realm.getInstance(realmConfig);
                           realmOpenedInBgLatch.countDown();
                           try {
          @@ -2731,7 +2731,7 @@ public void run() {
                               return;
                           }
           
          -                // Step 4: Start transaction in background
          +                // Step 4: Starts transaction in background.
                           realm.beginTransaction();
                           transBeganInBgLatch.countDown();
                           try {
          @@ -2739,7 +2739,7 @@ public void run() {
                           } catch (InterruptedException e) {
                               exception.add(e);
                           }
          -                // Step 6: Cancel Transaction and close realm in background
          +                // Step 6: Cancels Transaction and closes realm in background.
                           realm.cancelTransaction();
                           realm.close();
                           bgFinishedLatch.countDown();
          @@ -2748,12 +2748,12 @@ public void run() {
                   thread.start();
           
                   realmOpenedInBgLatch.await();
          -        // Step 3: Close all realm instances in foreground thread.
          +        // Step 3: Closes all realm instances in foreground thread.
                   realm.close();
                   realmClosedInFgLatch.countDown();
                   transBeganInBgLatch.await();
           
          -        // Step 5: Get a new Realm instance in foreground
          +        // Step 5: Gets a new Realm instance in foreground.
                   realm = Realm.getInstance(realmConfig);
                   fgFinishedLatch.countDown();
                   bgFinishedLatch.await();
          @@ -2845,10 +2845,10 @@ public void copyFromRealm_newCopyEachTime() {
                   assertNotSame(unmanagedObject1, unmanagedObject2);
               }
           
          -    // Test that the object graph is copied as it is and no extra copies are made
          +    // Tests that the object graph is copied as it is and no extra copies are made.
               // 1) (A -> B/[B,C])
               // 2) (C -> B/[B,A])
          -    // A copy should result in only 3 distinct objects
          +    // A copy should result in only 3 distinct objects.
               @Test
               public void copyFromRealm_cyclicObjectGraph() {
                   realm.beginTransaction();
          @@ -2874,14 +2874,14 @@ public void copyFromRealm_cyclicObjectGraph() {
                   assertEquals("B", copyB.getName());
                   assertEquals("C", copyC.getName());
           
          -        // Assert object equality on the object graph
          +        // Asserts object equality on the object graph.
                   assertTrue(copyA.getObject() == copyC.getObject());
                   assertTrue(copyA.getObjects().get(0) == copyC.getObjects().get(0));
                   assertTrue(copyA == copyC.getObjects().get(1));
                   assertTrue(copyC == copyA.getObjects().get(1));
               }
           
          -    // Test that for (A -> B -> C) for maxDepth = 1, result is (A -> B -> null)
          +    // Tests that for (A -> B -> C) for maxDepth = 1, result is (A -> B -> null).
               @Test
               public void copyFromRealm_checkMaxDepth() {
                   realm.beginTransaction();
          @@ -2902,7 +2902,7 @@ public void copyFromRealm_checkMaxDepth() {
                   assertNull(copyA.getObject().getObject());
               }
           
          -    // Test that depth restriction is calculated from the top-most encountered object, i.e. it is possible for some
          +    // Tests that depth restriction is calculated from the top-most encountered object, i.e. it is possible for some
               // objects to exceed the depth limit.
               // A -> B -> C -> D -> E
               // A -> D -> E
          @@ -2927,8 +2927,8 @@ public void copyFromRealm_sameObjectDifferentDepths() {
                   objA.setOtherObject(objD);
                   realm.commitTransaction();
           
          -        // object is filled before otherObject (because of field order - WARNING: Not guaranteed)
          -        // this means that the object will be encountered first time at max depth, so E will not be copied.
          +        // Object is filled before otherObject. (because of field order - WARNING: Not guaranteed)
          +        // This means that the object will be encountered first time at max depth, so E will not be copied.
                   // If the object cache does not handle this, otherObject will be wrong.
                   CyclicType copyA = realm.copyFromRealm(objA, 3);
                   assertEquals("E", copyA.getOtherObject().getObject().getName());
          @@ -2953,7 +2953,7 @@ public void copyFromRealm_list_invalidDepthThrows() {
                   realm.copyFromRealm(results, -1);
               }
           
          -    // Test that the same Realm objects in a list result in the same Java in-memory copy.
          +    // Tests that the same Realm objects in a list result in the same Java in-memory copy.
               // List: A -> [(B -> C), (B -> C)] should result in only 2 copied objects A and B and not A1, B1, A2, B2
               @Test
               public void copyFromRealm_list_sameElements() {
          @@ -3006,7 +3006,7 @@ public void copyFromRealm_dynamicRealmListThrows() {
                   }
               }
           
          -    // Test if close can be called from Realm change listener when there is no other listeners
          +    // Tests if close can be called from Realm change listener when there is no other listeners.
               @Test
               public void closeRealmInChangeListener() {
                   realm.close();
          @@ -3042,7 +3042,7 @@ public void execute(Realm realm) {
                   TestHelper.awaitOrFail(signalTestFinished);
               }
           
          -    // Test if close can be called from Realm change listener when there is a listener on empty Realm Object
          +    // Tests if close can be called from Realm change listener when there is a listener on empty Realm Object.
               @Test
               @RunTestInLooperThread
               public void closeRealmInChangeListenerWhenThereIsListenerOnEmptyObject() {
          @@ -3083,7 +3083,7 @@ public void execute(Realm realm) {
                   });
               }
           
          -    // Test if close can be called from Realm change listener when there is an listener on non-empty Realm Object
          +    // Tests if close can be called from Realm change listener when there is an listener on non-empty Realm Object.
               @Test
               @RunTestInLooperThread
               public void closeRealmInChangeListenerWhenThereIsListenerOnObject() {
          @@ -3100,7 +3100,7 @@ public void onChange(Realm object) {
                               realm.removeChangeListener(this);
                               realm.close();
           
          -                    // End test after next looper event to ensure that all listeners were called.
          +                    // Ends test after next looper event to ensure that all listeners were called.
                               looperThread.postRunnable(new Runnable() {
                                   @Override
                                   public void run() {
          @@ -3117,7 +3117,7 @@ public void run() {
                   realm.createObject(AllTypes.class);
                   realm.commitTransaction();
           
          -        // Step 1: Change listener on Realm Object
          +        // Change listener on Realm Object.
                   final AllTypes allTypes = realm.where(AllTypes.class).findFirst();
                   allTypes.addChangeListener(dummyListener);
                   realm.executeTransactionAsync(new Realm.Transaction() {
          @@ -3128,7 +3128,7 @@ public void execute(Realm realm) {
                   });
               }
           
          -    // Test if close can be called from Realm change listener when there is an listener on RealmResults
          +    // Tests if close can be called from Realm change listener when there is an listener on RealmResults.
               @Test
               @RunTestInLooperThread
               public void closeRealmInChangeListenerWhenThereIsListenerOnResults() {
          @@ -3156,7 +3156,7 @@ public void run() {
           
                   realm.addChangeListener(listener);
           
          -        // Step 1: Change listener on Realm results
          +        // Change listener on Realm results.
                   RealmResults results = realm.where(AllTypes.class).findAll();
                   results.addChangeListener(dummyListener);
           
          @@ -3318,7 +3318,7 @@ public void waitForChange_emptyDataChange() throws InterruptedException {
                   final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false);
                   final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0);
           
          -        // wait in background
          +        // Waits in background.
                   final CountDownLatch signalTestFinished = new CountDownLatch(1);
                   Thread thread = new Thread(new Runnable() {
                       @Override
          @@ -3348,7 +3348,7 @@ public void waitForChange_withDataChange() throws InterruptedException {
                   final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false);
                   final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0);
           
          -        // wait in background
          +        // Waits in background.
                   final CountDownLatch signalTestFinished = new CountDownLatch(1);
                   Thread thread = new Thread(new Runnable() {
                       @Override
          @@ -3377,14 +3377,14 @@ public void waitForChange_syncBackgroundRealmResults() throws InterruptedExcepti
                   final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false);
                   final AtomicLong bgRealmResultSize = new AtomicLong(0);
           
          -        // wait in background
          +        // Wait in background
                   final CountDownLatch signalTestFinished = new CountDownLatch(1);
                   Thread thread = new Thread(new Runnable() {
                       @Override
                       public void run() {
                           Realm realm = Realm.getInstance(realmConfig);
                           RealmResults results = realm.where(AllTypes.class).findAll();
          -                // first make sure the results is empty
          +                // First makes sure the results is empty.
                           bgRealmResultSize.set(results.size());
                           bgRealmOpened.countDown();
                           bgRealmChangeResult.set(realm.waitForChange());
          @@ -3396,12 +3396,12 @@ public void run() {
                   thread.start();
           
                   TestHelper.awaitOrFail(bgRealmOpened);
          -        // background result should be empty
          +        // Background result should be empty.
                   assertEquals(0, bgRealmResultSize.get());
                   populateTestRealm();
                   TestHelper.awaitOrFail(bgRealmClosed);
                   assertTrue(bgRealmChangeResult.get());
          -        // Once RealmResults are synchronized after waitForChange, the result size should be what we expect
          +        // Once RealmResults are synchronized after waitForChange, the result size should be what we expect.
                   assertEquals(TEST_DATA_SIZE, bgRealmResultSize.get());
               }
           
          @@ -3412,7 +3412,7 @@ public void stopWaitForChange() throws InterruptedException {
                   final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(true);
                   final AtomicReference bgRealm = new AtomicReference();
           
          -        // wait in background
          +        // Waits in background.
                   new Thread(new Runnable() {
                       @Override
                       public void run() {
          @@ -3432,7 +3432,7 @@ public void run() {
                   assertFalse(bgRealmChangeResult.get());
               }
           
          -    // Test if waitForChange doesn't blocks once stopWaitForChange has been called before.
          +    // Tests if waitForChange doesn't blocks once stopWaitForChange has been called before.
               @Test
               public void waitForChange_stopWaitForChangeDisablesWaiting() throws InterruptedException {
                   final CountDownLatch bgRealmOpened = new CountDownLatch(1);
          @@ -3442,7 +3442,7 @@ public void waitForChange_stopWaitForChangeDisablesWaiting() throws InterruptedE
                   final AtomicBoolean bgRealmSecondWaitResult = new AtomicBoolean(false);
                   final AtomicReference bgRealm = new AtomicReference();
           
          -        // wait in background
          +        // Waits in background.
                   new Thread(new Runnable() {
                       @Override
                       public void run() {
          @@ -3465,7 +3465,7 @@ public void run() {
                   assertFalse(bgRealmSecondWaitResult.get());
               }
           
          -    // Test if waitForChange still blocks if stopWaitForChange has been called for a realm in a different thread.
          +    // Tests if waitForChange still blocks if stopWaitForChange has been called for a realm in a different thread.
               @Test
               public void waitForChange_blockSpecificThreadOnly() throws InterruptedException {
                   final CountDownLatch bgRealmsOpened = new CountDownLatch(2);
          @@ -3475,7 +3475,7 @@ public void waitForChange_blockSpecificThreadOnly() throws InterruptedException
                   final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0);
                   final AtomicReference bgRealm = new AtomicReference();
           
          -        // wait in background
          +        // Waits in background.
                   Thread thread1 = new Thread(new Runnable() {
                       @Override
                       public void run() {
          @@ -3504,7 +3504,7 @@ public void run() {
           
                   TestHelper.awaitOrFail(bgRealmsOpened);
                   bgRealm.get().stopWaitForChange();
          -        // wait for Thread 2 to wait
          +        // Waits for Thread 2 to wait.
                   Thread.sleep(500);
                   populateTestRealm();
                   TestHelper.awaitOrFail(bgRealmsClosed);
          @@ -3513,7 +3513,7 @@ public void run() {
                   assertEquals(TEST_DATA_SIZE, bgRealmWaitForChangeResult.get());
               }
           
          -    // Check if waitForChange() does not respond to Thread.interrupt().
          +    // Checks if waitForChange() does not respond to Thread.interrupt().
               @Test
               public void waitForChange_interruptingThread() throws InterruptedException {
                   final CountDownLatch bgRealmOpened = new CountDownLatch(1);
          @@ -3521,7 +3521,7 @@ public void waitForChange_interruptingThread() throws InterruptedException {
                   final AtomicReference bgRealmWaitResult = new AtomicReference();
                   final AtomicReference bgRealm = new AtomicReference();
           
          -        // wait in background
          +        // Waits in background.
                   Thread thread = new Thread(new Runnable() {
                       @Override
                       public void run() {
          @@ -3536,14 +3536,14 @@ public void run() {
                   thread.start();
           
                   TestHelper.awaitOrFail(bgRealmOpened);
          -        // make sure background thread goes to wait
          +        // Makes sure background thread goes to wait.
                   Thread.sleep(500);
          -        // interrupting a thread should neither cause any side effect nor terminate the Background Realm from waiting.
          +        // Interrupting a thread should neither cause any side effect nor terminate the Background Realm from waiting.
                   thread.interrupt();
                   assertTrue(thread.isInterrupted());
                   assertEquals(null, bgRealmWaitResult.get());
           
          -        // now we'll stop realm from waiting
          +        // Now we'll stop realm from waiting.
                   bgRealm.get().stopWaitForChange();
                   TestHelper.awaitOrFail(bgRealmClosed);
                   assertFalse(bgRealmWaitResult.get().booleanValue());
          @@ -3579,7 +3579,7 @@ public void run() {
                   assertEquals(IllegalStateException.class, bgError.getException().getClass());
               }
           
          -    // Cannot wait inside of a transaction
          +    // Cannot wait inside of a transaction.
               @Test(expected= IllegalStateException.class)
               public void waitForChange_illegalWaitInsideTransaction() {
                   realm.beginTransaction();
          @@ -3668,7 +3668,7 @@ public void schemaIndexCacheIsUpdatedAfterSchemaChange() {
                   final long nameIndex = catColumnInfo.nameIndex;
                   final AtomicLong nameIndexNew = new AtomicLong(-1L);
           
          -        // change column index of "name"
          +        // Changes column index of "name".
                   realm.executeTransaction(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
          @@ -3683,17 +3683,17 @@ public void execute(Realm realm) {
                           nameIndexNew.set(newIndex);
                       }
                   });
          -        // we need ↓ to update index cache if the schema version was changed in the same thread.
          +        // We need to update index cache if the schema version was changed in the same thread.
                   realm.sharedRealm.invokeSchemaChangeListenerIfSchemaChanged();
           
          -        // check if the index was changed
          +        // Checks if the index was changed.
                   assertNotEquals(nameIndex, nameIndexNew);
           
          -        // check if index in the ColumnInfo is updated
          +        // Checks if index in the ColumnInfo is updated.
                   assertEquals(nameIndexNew.get(), catColumnInfo.nameIndex);
                   assertEquals(nameIndexNew.get(), (long) catColumnInfo.getIndicesMap().get(Cat.FIELD_NAME));
           
          -        // check by actual get and set
          +        // Checks by actual get and set.
                   realm.executeTransaction(new Realm.Transaction() {
                       @Override
                       public void execute(Realm realm) {
          @@ -3712,15 +3712,15 @@ public void getGlobalInstanceCount() {
                   final RealmConfiguration config = configFactory.createConfiguration("globalCountTest");
                   assertEquals(0, Realm.getGlobalInstanceCount(config));
           
          -        // Open thread local Realm
          +        // Opens thread local Realm.
                   Realm realm = Realm.getInstance(config);
                   assertEquals(1, Realm.getGlobalInstanceCount(config));
           
          -        // Open thread local DynamicRealm
          +        // Opens thread local DynamicRealm.
                   DynamicRealm dynRealm = DynamicRealm.getInstance(config);
                   assertEquals(2, Realm.getGlobalInstanceCount(config));
           
          -        // Open Realm in another thread
          +        // Opens Realm in another thread.
                   new Thread(new Runnable() {
                       @Override
                       public void run() {
          @@ -3744,11 +3744,11 @@ public void getLocalInstanceCount() {
                   final RealmConfiguration config = configFactory.createConfiguration("localInstanceCount");
                   assertEquals(0, Realm.getLocalInstanceCount(config));
           
          -        // Open thread local Realm
          +        // Opens thread local Realm.
                   Realm realm = Realm.getInstance(config);
                   assertEquals(1, Realm.getLocalInstanceCount(config));
           
          -        // Open thread local DynamicRealm
          +        // Opens thread local DynamicRealm.
                   DynamicRealm dynRealm = DynamicRealm.getInstance(config);
                   assertEquals(2, Realm.getLocalInstanceCount(config));
           
          @@ -3761,7 +3761,7 @@ public void getLocalInstanceCount() {
               @Test
               public void namedPipeDirForExternalStorage() {
           
          -        // test for https://github.com/realm/realm-java/issues/3140
          +        // Test for https://github.com/realm/realm-java/issues/3140
                   realm.close();
                   realm = null;
           
          @@ -3778,7 +3778,7 @@ public void namedPipeDirForExternalStorage() {
                           .build();
                   Realm.deleteRealm(config);
           
          -        // test if it works when the namedPipeDir is empty.
          +        // Test if it works when the namedPipeDir is empty.
                   Realm realmOnExternalStorage = Realm.getInstance(config);
                   realmOnExternalStorage.close();
           
          @@ -3786,7 +3786,7 @@ public void namedPipeDirForExternalStorage() {
           
                   Assume.assumeTrue("SELinux is not enforced on this device.", TestHelper.isSelinuxEnforcing());
           
          -        // Only check the fifo file created by call, since all Realm instances share the same fifo created by
          +        // Only checks the fifo file created by call, since all Realm instances share the same fifo created by
                   // external_commit_helper which might not be created in the newly created dir if there are Realm instances
                   // are not deleted when TestHelper.deleteRecursively(namedPipeDir) called.
                   File[] files = namedPipeDir.listFiles(new FilenameFilter() {
          @@ -3797,7 +3797,7 @@ public boolean accept(File dir, String name) {
                   });
                   assertEquals(1, files.length);
           
          -        // test if it works when the namedPipeDir and the named pipe files already exist.
          +        // Tests if it works when the namedPipeDir and the named pipe files already exist.
                   realmOnExternalStorage = Realm.getInstance(config);
                   realmOnExternalStorage.close();
               }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java
          index ba4898e4a3..09b5ebba2f 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java
          @@ -68,13 +68,13 @@ public void looperTearDown() {
           
               @Before
               public void setUp() throws Exception {
          -        // For non-LooperThread tests
          +        // For non-LooperThread tests.
                   realm = Realm.getInstance(configFactory.createConfiguration());
               }
           
               @After
               public void tearDown() throws Exception {
          -        // For non-LooperThread tests
          +        // For non-LooperThread tests.
                   if (realm != null) {
                       realm.close();
                   }
          @@ -600,7 +600,7 @@ public void realmResults_gcStressTest() {
                   realm.commitTransaction();
           
                   for (int i = 0; i < TEST_SIZE; i++) {
          -            // Don't keep a reference to the Observable
          +            // Doesn't keep a reference to the Observable.
                       realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, i).findAllAsync().asObservable()
                               .filter(new Func1, Boolean>() {
                                   @Override
          @@ -608,11 +608,11 @@ public Boolean call(RealmResults results) {
                                       return results.isLoaded();
                                   }
                               })
          -                    .take(1) // Unsubscribes from Realm
          +                    .take(1) // Unsubscribes from Realm.
                               .subscribe(new Action1>() {
                                   @Override
                                   public void call(RealmResults result) {
          -                            // Not guaranteed, but can result in the GC of other RealmResults waiting for a result
          +                            // Not guaranteed, but can result in the GC of other RealmResults waiting for a result.
                                       Runtime.getRuntime().gc();
                                       if (innerCounter.incrementAndGet() == TEST_SIZE) {
                                           looperThread.testComplete();
          @@ -643,7 +643,7 @@ public void dynamicRealmResults_gcStressTest() {
                   realm.commitTransaction();
           
                   for (int i = 0; i < TEST_SIZE; i++) {
          -            // Don't keep a reference to the Observable
          +            // Doesn't keep a reference to the Observable.
                       realm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findAllAsync().asObservable()
                               .filter(new Func1, Boolean>() {
                                   @Override
          @@ -651,11 +651,11 @@ public Boolean call(RealmResults results) {
                                       return results.isLoaded();
                                   }
                               })
          -                    .take(1) // Unsubscribes from Realm
          +                    .take(1) // Unsubscribes from Realm.
                               .subscribe(new Action1>() {
                                   @Override
                                   public void call(RealmResults result) {
          -                            // Not guaranteed, but can result in the GC of other RealmResults waiting for a result
          +                            // Not guaranteed, but can result in the GC of other RealmResults waiting for a result.
                                       Runtime.getRuntime().gc();
                                       if (innerCounter.incrementAndGet() == TEST_SIZE) {
                                           realm.close();
          @@ -687,7 +687,7 @@ public void realmObject_gcStressTest() {
                   realm.commitTransaction();
           
                   for (int i = 0; i < TEST_SIZE; i++) {
          -            // Don't keep a reference to the Observable
          +            // Doesn't keep a reference to the Observable.
                       realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, i).findFirstAsync().asObservable()
                               .filter(new Func1() {
                                   @Override
          @@ -695,11 +695,11 @@ public Boolean call(AllTypes obj) {
                                       return obj.isLoaded();
                                   }
                               })
          -                    .take(1) // Unsubscribes from Realm
          +                    .take(1) // Unsubscribes from Realm.
                               .subscribe(new Action1() {
                                   @Override
                                   public void call(AllTypes result) {
          -                            // Not guaranteed, but can result in the GC of other RealmResults waiting for a result
          +                            // Not guaranteed, but can result in the GC of other RealmResults waiting for a result.
                                       Runtime.getRuntime().gc();
                                       if (innerCounter.incrementAndGet() == TEST_SIZE) {
                                           looperThread.testComplete();
          @@ -730,7 +730,7 @@ public void dynamicRealmObject_gcStressTest() {
                   realm.commitTransaction();
           
                   for (int i = 0; i < TEST_SIZE; i++) {
          -            // Don't keep a reference to the Observable
          +            // Doesn't keep a reference to the Observable.
                       realm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findFirstAsync().asObservable()
                               .filter(new Func1() {
                                   @Override
          @@ -738,11 +738,11 @@ public Boolean call(DynamicRealmObject obj) {
                                       return obj.isLoaded();
                                   }
                               })
          -                    .take(1) // Unsubscribes from Realm
          +                    .take(1) // Unsubscribes from Realm.
                               .subscribe(new Action1() {
                                   @Override
                                   public void call(DynamicRealmObject result) {
          -                            // Not guaranteed, but can result in the GC of other RealmResults waiting for a result
          +                            // Not guaranteed, but can result in the GC of other RealmResults waiting for a result.
                                       Runtime.getRuntime().gc();
                                       if (innerCounter.incrementAndGet() == TEST_SIZE) {
                                           realm.close();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java
          index bb2f61f4cb..0dabb5911e 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java
          @@ -108,21 +108,21 @@ public void tearDown() {
               public void sortMultiFailures() {
                   RealmResults allTypes = realm.where(AllTypes.class).findAll();
           
          -        // zero fields specified
          +        // Zero fields specified.
                   try {
                       allTypes.sort(new String[]{}, new Sort[]{});
                       fail();
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // number of fields and sorting orders don't match
          +        // Number of fields and sorting orders don't match.
                   try {
                       allTypes.sort(new String[]{FIELD_STRING}, ORDER_ASC_ASC);
                       fail();
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // null is not allowed
          +        // Null is not allowed.
                   try {
                       allTypes.sort(null, (Sort[]) null);
                       fail();
          @@ -134,7 +134,7 @@ public void sortMultiFailures() {
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // non-existing field name
          +        // Non-existing field name.
                   try {
                       allTypes.sort(new String[]{FIELD_STRING, "dont-exist"}, ORDER_ASC_ASC);
                       fail();
          @@ -169,7 +169,7 @@ private void checkSortTwoFieldsStringAscendingIntAscending(RealmResults results) {
          -        // Sorted Long (ascending), String (descending)
          +        // Sorted Long (ascending), String (descending).
                   // Expected output:
                   // (4, "Adam"), row index = 2
                   // (4, "Brian"), row index = 1
          @@ -195,7 +195,7 @@ private void checkSortTwoFieldsIntString(RealmResults results) {
               }
           
               private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) {
          -        // Sorted Long (ascending), String (descending)
          +        // Sorted Long (ascending), String (descending).
                   // Expected output:
                   // (4, "Brian"), row index = 1
                   // (4, "Adam"), row index = 2
          @@ -221,7 +221,7 @@ private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) {
          -        // Sorted String (ascending), Long (descending)
          +        // Sorted String (ascending), Long (descending).
                   // Expected output:
                   // (5, "Adam"), row index = 0 - stable sort!
                   // (5, "Adam"), row index = 3
          @@ -303,14 +303,14 @@ public void realmSortTwoFields() {
               public void realmSortMultiFailures() {
                   RealmResults allTypes = realm.where(AllTypes.class).findAll();
           
          -        // zero fields specified
          +        // Zero fields specified.
                   try {
                       realm.where(AllTypes.class).findAll().sort(new String[]{}, new Sort[]{});
                       fail();
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // number of fields and sorting orders don't match
          +        // Number of fields and sorting orders don't match.
                   try {
                       realm.where(AllTypes.class).findAll().
                               sort(new String[]{FIELD_STRING}, ORDER_ASC_ASC);
          @@ -318,7 +318,7 @@ public void realmSortMultiFailures() {
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // null is not allowed
          +        // Null is not allowed.
                   try {
                       realm.where(AllTypes.class).findAll().sort(null, (Sort[]) null);
                       fail();
          @@ -330,7 +330,7 @@ public void realmSortMultiFailures() {
                   } catch (IllegalArgumentException ignored) {
                   }
           
          -        // non-existing field name
          +        // Non-existing field name.
                   try {
                       realm.where(AllTypes.class).findAll().
                               sort(new String[]{FIELD_STRING, "dont-exist"}, ORDER_ASC_ASC);
          @@ -369,7 +369,7 @@ public void run() {
                   rr0.addChangeListener(new RealmChangeListener>() {
                       @Override
                       public void onChange(RealmResults element) {
          -                // After commit: [0, 1, 2, 3, 4] - most likely as order isn't guaranteed
          +                // After commit: [0, 1, 2, 3, 4] - most likely as order isn't guaranteed.
                           assertEquals(5, element.size());
                           endTest.run();
                       }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java
          index 0bbf6178a8..72de604064 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java
          @@ -101,7 +101,7 @@ public static RealmFieldType getColumnType(Object o) {
               }
           
               /**
          -     * Creates an empty table with 1 column of all our supported column types, currently 9 columns
          +     * Creates an empty table with 1 column of all our supported column types, currently 9 columns.
                *
                * @return
                */
          @@ -141,7 +141,7 @@ public static InputStream stringToStream(String str) {
                   return new ByteArrayInputStream(str.getBytes(Charset.forName("UTF-8")));
               }
           
          -    // Creates a simple migration step in order to support null
          +    // Creates a simple migration step in order to support null.
               // FIXME: generate a new encrypted.realm will null support
               public static RealmMigration prepareMigrationToNullSupportStep() {
                   RealmMigration realmMigration = new RealmMigration() {
          @@ -231,7 +231,7 @@ public int read() throws IOException {
                   }
               }
           
          -    // Alloc as much garbage as we can. Pass maxSize = 0 to use it.
          +    // Allocs as much garbage as we can. Pass maxSize = 0 to use it.
               public static byte[] allocGarbage(int garbageSize) {
                   if (garbageSize == 0) {
                       long maxMemory = Runtime.getRuntime().maxMemory();
          @@ -479,7 +479,7 @@ public static void populateTestRealmWithLongPrimaryKey(Realm testRealm, Long pri
           
               public static void populateTestRealmForNullTests(Realm testRealm) {
           
          -        // Create 3 NullTypes objects. The objects are self-referenced (link) in
          +        // Creates 3 NullTypes objects. The objects are self-referenced (link) in
                   // order to test link queries.
                   //
                   // +-+--------+------+---------+--------+--------------------+
          @@ -784,7 +784,7 @@ public static void awaitOrFail(CountDownLatch latch) {
               public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) {
                   try {
                       if (android.os.Debug.isDebuggerConnected()) {
          -                // If we are debugging the tests, just wait without a timeout. In case we are stopping at a break point
          +                // If we are debugging the tests, just waits without a timeout. In case we are stopping at a break point
                           // and timeout happens.
                           latch.await();
                       } else if (!latch.await(numberOfSeconds, TimeUnit.SECONDS)) {
          @@ -795,14 +795,14 @@ public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) {
                   }
               }
           
          -    // clean resource, shutdown the executor service & throw any background exception
          +    // Cleans resource, shutdowns the executor service and throws any background exception.
               public static void exitOrThrow(final ExecutorService executorService,
                                              final CountDownLatch signalTestFinished,
                                              final CountDownLatch signalClosedRealm,
                                              final Looper[] looper,
                                              final Throwable[] throwable) throws Throwable {
           
          -        // wait for the signal indicating the test's use case is done
          +        // Waits for the signal indicating the test's use case is done.
                   try {
                       // Even if this fails we want to try as hard as possible to cleanup. If we fail to close all resources
                       // properly, the `after()` method will most likely throw as well because it tries do delete any Realms
          @@ -810,19 +810,19 @@ public static void exitOrThrow(final ExecutorService executorService,
                       TestHelper.awaitOrFail(signalTestFinished);
                   } finally {
                       if (looper[0] != null) {
          -                // failing to quit the looper will not execute the finally block responsible
          -                // of closing the Realm
          +                // Failing to quit the looper will not execute the finally block responsible
          +                // of closing the Realm.
                           looper[0].quit();
                       }
           
          -            // wait for the finally block to execute & close the Realm
          +            // Waits for the finally block to execute and closes the Realm.
                       TestHelper.awaitOrFail(signalClosedRealm);
          -            // Close the executor.
          +            // Closes the executor.
                       // This needs to be called after waiting since it might interrupt waitRealmThreadExecutorFinish().
                       executorService.shutdownNow();
           
                       if (throwable[0] != null) {
          -                // throw any assertion errors happened in the background thread
          +                // Throws any assertion errors happened in the background thread.
                           throw throwable[0];
                       }
                   }
          @@ -958,7 +958,7 @@ public static void resetRealmThreadExecutor() throws NoSuchFieldException, Illeg
               }
           
               /**
          -     * Wait and check if all tasks in BaseRealm.asyncTaskExecutor can be finished in 5 seconds, otherwise fail the test.
          +     * Waits and checks if all tasks in BaseRealm.asyncTaskExecutor can be finished in 5 seconds, otherwise fails the test.
                */
               public static void waitRealmThreadExecutorFinish() {
                   int counter = 50;
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java
          index 59d59b1c7c..54fb15250f 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java
          @@ -78,11 +78,11 @@ public void setUp() {
           
               // ****************************************************************************************** //
               // UC 0.
          -    // Callback should be notified if we create a RealmObject without the async mechanism
          +    // Callback should be notified if we create a RealmObject without the async mechanism.
               // ex: using (createObject, copyOrUpdate, createObjectFromJson etc.)
               // ***************************************************************************************** //
           
          -    //UC 0 using Realm.createObject
          +    //UC 0 Uses Realm.createObject.
               @Test
               @RunTestInLooperThread
               public void callback_should_trigger_for_createObject() {
          @@ -159,7 +159,7 @@ public void onChange(DynamicRealmObject object) {
                   realm.commitTransaction();
               }
           
          -    //UC 0 using Realm.copyToRealm
          +    //UC 0 Uses Realm.copyToRealm.
               @Test
               @RunTestInLooperThread
               public void callback_should_trigger_for_copyToRealm() {
          @@ -199,7 +199,7 @@ public void onChange(Dog object) {
                   realm.commitTransaction();
               }
           
          -    //UC 0 using Realm.copyToRealmOrUpdate
          +    //UC 0 Uses Realm.copyToRealmOrUpdate.
               @Test
               @RunTestInLooperThread
               public void callback_should_trigger_for_copyToRealmOrUpdate() {
          @@ -248,7 +248,7 @@ public void onChange(PrimaryKeyAsLong object) {
                   assertEquals(primaryKeyAsLong, primaryKeyAsLong2);
               }
           
          -    //UC 0 using Realm.copyToRealmOrUpdate
          +    //UC 0 Uses Realm.copyToRealmOrUpdate.
               @Test
               @RunTestInLooperThread
               public void callback_should_trigger_for_createObjectFromJson() {
          @@ -300,7 +300,7 @@ public void onChange(AllTypes object) {
                   }
               }
           
          -    //UC 0 using Realm.copyToRealmOrUpdate
          +    //UC 0 Uses Realm.copyToRealmOrUpdate.
               @Test
               @RunTestInLooperThread
               public void callback_should_trigger_for_createObjectFromJson_from_JSONObject() {
          @@ -356,7 +356,7 @@ public void onChange(AllTypes object) {
                   }
               }
           
          -    //UC 0 using Realm.createOrUpdateObjectFromJson
          +    //UC 0 Uses Realm.createOrUpdateObjectFromJson.
               @Test
               @RunTestInLooperThread
               public void callback_should_trigger_for_createOrUpdateObjectFromJson() {
          @@ -424,7 +424,7 @@ public void onChange(AllTypesPrimaryKey object) {
                   }
               }
           
          -    //UC 0 using Realm.copyToRealmOrUpdate
          +    //UC 0 Uses Realm.copyToRealmOrUpdate.
               @Test
               @RunTestInLooperThread
               public void callback_should_trigger_for_createOrUpdateObjectFromJson_from_JSONObject() throws JSONException {
          @@ -477,8 +477,8 @@ public void onChange(AllTypesPrimaryKey object) {
           
               // ********************************************************************************* //
               // UC 1.
          -    // Callback should be invoked after a relevant commit (one that should impact the
          -    // query from which we obtained our RealmObject or RealmResults)
          +    // Callback should be invoked after a relevant commit. (one that should impact the
          +    // query from which we obtained our RealmObject or RealmResults.)
               // ********************************************************************************* //
               // UC 1 for Sync RealmObject
               @Test
          @@ -486,7 +486,7 @@ public void onChange(AllTypesPrimaryKey object) {
               public void callback_with_relevant_commit_realmobject_sync() {
                   final Realm realm = looperThread.realm;
           
          -        // Step 1: Trigger global Realm change listener
          +        // Step 1: Triggers global Realm change listener.
                   realm.beginTransaction();
                   final Dog akamaru = realm.createObject(Dog.class);
                   akamaru.setName("Akamaru");
          @@ -497,7 +497,7 @@ public void callback_with_relevant_commit_realmobject_sync() {
                   dog.addChangeListener(new RealmChangeListener() {
                       @Override
                       public void onChange(Dog object) {
          -                // Step 4: Respond to relevant change
          +                // Step 4: Responds to relevant change.
                           typebasedCommitInvocations.incrementAndGet();
                           assertEquals("Akamaru", dog.getName());
                           assertEquals(17, dog.getAge());
          @@ -510,20 +510,20 @@ public void onChange(Realm object) {
                           int commits = globalCommitInvocations.incrementAndGet();
                           switch (commits) {
                               case 1:
          -                        // Step 2: Trigger non-related commit
          +                        // Step 2: Triggers non-related commit.
                                   realm.beginTransaction();
                                   realm.commitTransaction();
                                   break;
           
                               case 2:
          -                        // Step 3: Trigger related commit
          +                        // Step 3: Triggers related commit.
                                   realm.beginTransaction();
                                   akamaru.setAge(17);
                                   realm.commitTransaction();
                                   break;
           
                               case 3:
          -                        // Step 5: Complete test
          +                        // Step 5: Completes test.
                                   looperThread.postRunnable(new Runnable() {
                                       @Override
                                       public void run() {
          @@ -537,13 +537,13 @@ public void run() {
                   });
               }
           
          -    // UC 1 Async RealmObject
          +    // UC 1 Async RealmObject.
               @Test
               @RunTestInLooperThread
               public void callback_with_relevant_commit_realmobject_async() {
                   final Realm realm = looperThread.realm;
           
          -        // Step 1: Trigger global Realm change listener
          +        // Step 1: Triggers global Realm change listener.
                   realm.beginTransaction();
                   final Dog akamaru = realm.createObject(Dog.class);
                   akamaru.setName("Akamaru");
          @@ -563,7 +563,7 @@ public void onChange(Dog object) {
                                   break;
           
                               case 2:
          -                        // Step 4: Respond to relevant change
          +                        // Step 4: Responds to relevant change.
                                   assertEquals(17, dog.getAge());
                                   break;
                           }
          @@ -576,20 +576,20 @@ public void onChange(Realm object) {
                           int commits = globalCommitInvocations.incrementAndGet();
                           switch (commits) {
                               case 1:
          -                        // Step 2: Trigger non-related commit
          +                        // Step 2: Triggers non-related commit.
                                   realm.beginTransaction();
                                   realm.commitTransaction();
                                   break;
           
                               case 2:
          -                        // Step 3: Trigger related commit
          +                        // Step 3: Triggers related commit.
                                   realm.beginTransaction();
                                   akamaru.setAge(17);
                                   realm.commitTransaction();
                                   break;
           
                               case 3:
          -                        // Step 5: Complete test
          +                        // Step 5: Completes test.
                                   looperThread.postRunnable(new Runnable() {
                                       @Override
                                       public void run() {
          @@ -604,7 +604,7 @@ public void run() {
                   });
               }
           
          -    // UC 1 Async RealmObject
          +    // UC 1 Async RealmObject.
               @Test
               @RunTestInLooperThread
               public void callback_with_relevant_commit_from_different_looper_realmobject_async() {
          @@ -635,7 +635,7 @@ public void onChange(Realm object) {
                       @Override
                       public void onChange(Dog object) {
                           switch (typebasedCommitInvocations.incrementAndGet()) {
          -                    case 1: // triggered by COMPLETED_ASYNC_REALM_OBJECT from calling dog.load()
          +                    case 1: // Triggered by COMPLETED_ASYNC_REALM_OBJECT from calling dog.load().
                                   assertTrue(dog.isLoaded());
                                   assertFalse(dog.isValid());
           
          @@ -650,7 +650,7 @@ public void run() {
                                       }
                                   });
                                   break;
          -                    case 2: // triggered by the irrelevant commit (not affecting Dog table) from LooperThread1
          +                    case 2: // Triggered by the irrelevant commit (not affecting Dog table) from LooperThread1.
                                   assertTrue(dog.isLoaded());
                                   assertFalse(dog.isValid());
           
          @@ -658,7 +658,7 @@ public void run() {
                                       @Override
                                       public void run() {
                                           Realm realmLooperThread2 = Realm.getInstance(realm.getConfiguration());
          -                                // trigger first callback invocation
          +                                // Triggers first callback invocation.
                                           realmLooperThread2.beginTransaction();
                                           Dog dog = realmLooperThread2.createObject(Dog.class);
                                           dog.setName("Akamaru");
          @@ -669,12 +669,12 @@ public void run() {
                                   });
                                   break;
           
          -                    case 3: // triggered by relevant commit from LooperThread2
          +                    case 3: // Triggered by relevant commit from LooperThread2.
                                   assertEquals("Akamaru", dog.getName());
                                   looperThread.postRunnable(new Runnable() {
                                       @Override
                                       public void run() {
          -                                // trigger second callback invocation
          +                                // Triggers second callback invocation.
                                           looperHandler3.post(new Runnable() {
                                               @Override
                                               public void run() {
          @@ -692,9 +692,9 @@ public void run() {
                               case 4:
                                   assertEquals("Akamaru", dog.getName());
                                   assertEquals(17, dog.getAge());
          -                        // posting as an event will give the handler a chance
          -                        // to deliver the notification for globalCommitInvocations
          -                        // otherwise, test will exit before the callback get a chance to be invoked
          +                        // Posting as an event will give the handler a chance
          +                        // to deliver the notification for globalCommitInvocations.
          +                        // Otherwise, test will exit before the callback get a chance to be invoked.
                                   looperThread.postRunnable(new Runnable() {
                                       @Override
                                       public void run() {
          @@ -716,7 +716,7 @@ public void run() {
           
               }
           
          -    // UC 1 Async RealmObject
          +    // UC 1 Async RealmObject.
               @Test
               @RunTestInLooperThread
               public void callback_with_relevant_commit_from_different_non_looper_realmobject_async() throws Throwable {
          @@ -736,7 +736,7 @@ public void onChange(Realm object) {
                       @Override
                       public void onChange(Dog object) {
                           switch (typebasedCommitInvocations.incrementAndGet()) {
          -                    case 1:  // triggered by COMPLETED_ASYNC_REALM_OBJECT
          +                    case 1:  // Triggered by COMPLETED_ASYNC_REALM_OBJECT.
                                   new RealmBackgroundTask(realm.configuration) {
                                       @Override
                                       protected void doInBackground(Realm realm) {
          @@ -746,7 +746,7 @@ protected void doInBackground(Realm realm) {
                                   }.awaitOrFail();
                                   break;
           
          -                    case 2: {// triggered by the irrelevant commit (not affecting Dog table)
          +                    case 2: {// Triggered by the irrelevant commit (not affecting Dog table).
                                   assertTrue(dog.isLoaded());
                                   assertFalse(dog.isValid());
                                   new RealmBackgroundTask(realm.configuration) {
          @@ -765,7 +765,7 @@ protected void doInBackground(Realm realm) {
                                   looperThread.postRunnable(new Runnable() {
                                       @Override
                                       public void run() {
          -                                // trigger second callback invocation
          +                                // Triggers second callback invocation.
                                           new Thread() {
                                               @Override
                                               public void run() {
          @@ -784,9 +784,9 @@ public void run() {
                               case 4: {
                                   assertEquals("Akamaru", dog.getName());
                                   assertEquals(17, dog.getAge());
          -                        // posting as an event will give the handler a chance
          -                        // to deliver the notification for globalCommitInvocations
          -                        // otherwise, test will exit before the callback get a chance to be invoked
          +                        // Posting as an event will give the handler a chance
          +                        // to deliver the notification for globalCommitInvocations.
          +                        // Otherwise, test will exit before the callback get a chance to be invoked.
                                   looperThread.postRunnable(new Runnable() {
                                       @Override
                                       public void run() {
          @@ -803,13 +803,13 @@ public void run() {
                   });
               }
           
          -    // UC 1 Sync RealmResults
          +    // UC 1 Sync RealmResults.
               @Test
               @RunTestInLooperThread
               public void callback_with_relevant_commit_realmresults_sync() {
                   final Realm realm = looperThread.realm;
           
          -        // Step 1: Trigger global Realm change listener
          +        // Step 1: Triggers global Realm change listener.
                   realm.beginTransaction();
                   final Dog akamaru = realm.createObject(Dog.class);
                   akamaru.setName("Akamaru");
          @@ -820,7 +820,7 @@ public void callback_with_relevant_commit_realmresults_sync() {
                   dogs.addChangeListener(new RealmChangeListener>() {
                       @Override
                       public void onChange(RealmResults object) {
          -                // Step 4: Respond to relevant change
          +                // Step 4: Responds to relevant change.
                           typebasedCommitInvocations.incrementAndGet();
                           assertEquals(1, dogs.size());
                           assertEquals("Akamaru", dogs.get(0).getName());
          @@ -834,20 +834,20 @@ public void onChange(Realm object) {
                           int commits = globalCommitInvocations.incrementAndGet();
                           switch (commits) {
                               case 1:
          -                        // Step 2: Trigger non-related commit
          +                        // Step 2: Triggers non-related commit.
                                   realm.beginTransaction();
                                   realm.commitTransaction();
                                   break;
           
                               case 2:
          -                        // Step 3: Trigger related commit
          +                        // Step 3: Triggers related commit.
                                   realm.beginTransaction();
                                   akamaru.setAge(17);
                                   realm.commitTransaction();
                                   break;
           
                               case 3:
          -                        // Step 5: Complete test
          +                        // Step 5: Completes test.
                                   looperThread.postRunnable(new Runnable() {
                                       @Override
                                       public void run() {
          @@ -860,13 +860,13 @@ public void run() {
                   });
               }
           
          -    // UC 1 Async RealmResults
          +    // UC 1 Async RealmResults.
               @Test
               @RunTestInLooperThread
               public void callback_with_relevant_commit_realmresults_async() {
                   final Realm realm = looperThread.realm;
           
          -        // Step 1: Trigger global Realm change listener
          +        // Step 1: Triggers global Realm change listener.
                   realm.beginTransaction();
                   final Dog akamaru = realm.createObject(Dog.class);
                   akamaru.setName("Akamaru");
          @@ -878,7 +878,7 @@ public void callback_with_relevant_commit_realmresults_async() {
                   dogs.addChangeListener(new RealmChangeListener>() {
                       @Override
                       public void onChange(RealmResults object) {
          -                // Step 4: Respond to relevant change
          +                // Step 4: Responds to relevant change.
                           int commits = typebasedCommitInvocations.incrementAndGet();
                           switch (commits) {
                               case 2:
          @@ -897,20 +897,20 @@ public void onChange(Realm object) {
                           int commits = globalCommitInvocations.incrementAndGet();
                           switch (commits) {
                               case 1:
          -                        // Step 2: Trigger non-related commit
          +                        // Step 2: Triggers non-related commit.
                                   realm.beginTransaction();
                                   realm.commitTransaction();
                                   break;
           
                               case 2:
          -                        // Step 3: Trigger related commit
          +                        // Step 3: Triggers related commit.
                                   realm.beginTransaction();
                                   akamaru.setAge(17);
                                   realm.commitTransaction();
                                   break;
           
                               case 3:
          -                        // Step 5: Complete test
          +                        // Step 5: Completes test.
                                   looperThread.postRunnable(new Runnable() {
                                       @Override
                                       public void run() {
          @@ -925,9 +925,9 @@ public void run() {
           
               // ********************************************************************************* //
               // UC 2.
          -    // Multiple callbacks should be invoked after a relevant commit
          +    // Multiple callbacks should be invoked after a relevant commit.
               // ********************************************************************************* //
          -    // UC 2 for Sync RealmObject
          +    // UC 2 for Sync RealmObject.
               @Test
               @RunTestInLooperThread
               public void multiple_callbacks_should_be_invoked_realmobject_sync() {
          @@ -966,7 +966,7 @@ public void onChange(Dog object) {
                   realm.commitTransaction();
               }
           
          -    // UC 2 Async RealmObject
          +    // UC 2 Async RealmObject.
               @Test
               @RunTestInLooperThread
               public void multiple_callbacks_should_be_invoked_realmobject_async() {
          @@ -1008,7 +1008,7 @@ public void onChange(Dog object) {
                   realm.commitTransaction();
               }
           
          -    // UC 2 Sync RealmResults
          +    // UC 2 Sync RealmResults.
               @Test
               @RunTestInLooperThread
               public void multiple_callbacks_should_be_invoked_realmresults_sync() {
          @@ -1047,7 +1047,7 @@ public void onChange(RealmResults object) {
                   realm.commitTransaction();
               }
           
          -    // UC 2 Async RealmResults
          +    // UC 2 Async RealmResults.
               @Test
               @RunTestInLooperThread
               public void multiple_callbacks_should_be_invoked_realmresults_async() {
          @@ -1090,12 +1090,12 @@ public void onChange(RealmResults object) {
           
               // ********************************************************************************* //
               // UC 3.
          -    // Callback should be invoked when a non Looper thread commits
          +    // Callback should be invoked when a non Looper thread commits.
               // ********************************************************************************* //
           
          -    // UC 3 for Sync RealmObject
          -    // 1. Add listener to RealmObject which is queried synchronized.
          -    // 2. Commit transaction in another non-looper thread
          +    // UC 3 for Sync RealmObject.
          +    // 1. Adds listener to RealmObject which is queried synchronized.
          +    // 2. Commits transaction in another non-looper thread.
               // 3. Listener on the RealmObject gets triggered.
               @Test
               @RunTestInLooperThread
          @@ -1147,9 +1147,9 @@ public void run() {
                   }
               }
           
          -    // UC 3 Async RealmObject
          -    // 1. Create RealmObject async query
          -    // 2. Wait COMPLETED_ASYNC_REALM_OBJECT then commit transaction in another non-looper thread
          +    // UC 3 Async RealmObject.
          +    // 1. Creates RealmObject async query.
          +    // 2. Waits COMPLETED_ASYNC_REALM_OBJECT then commits transaction in another non-looper thread.
               // 3. Listener on the RealmObject gets triggered again.
               @Test
               @RunTestInLooperThread
          @@ -1158,7 +1158,7 @@ public void non_looper_thread_commit_realmobject_async() {
                   realm.addChangeListener(new RealmChangeListener() {
                       @Override
                       public void onChange(Realm object) {
          -                // Check if the 2nd transaction is committed.
          +                // Checks if the 2nd transaction is committed.
                           if (realm.where(Dog.class).count() == 2) {
                               looperThread.postRunnable(new Runnable() {
                                   @Override
          @@ -1213,9 +1213,9 @@ public void onChange(Dog object) {
                   thread.start();
               }
           
          -    // UC 3 Sync RealmResults
          -    // 1. Add listener to RealmResults which is queried synchronized.
          -    // 2. Commit transaction in another non-looper thread
          +    // UC 3 Sync RealmResults.
          +    // 1. Adds listener to RealmResults which is queried synchronized.
          +    // 2. Commits transaction in another non-looper thread.
               // 3. Listener on the RealmResults gets triggered.
               @Test
               @RunTestInLooperThread
          @@ -1268,9 +1268,9 @@ public void run() {
                   }
               }
           
          -    // UC 3 Async RealmResults
          -    // 1. Create RealmResults async query
          -    // 2. Wait COMPLETED_ASYNC_REALM_RESULTS then commit transaction in another non-looper thread
          +    // UC 3 Async RealmResults.
          +    // 1. Creates RealmResults async query.
          +    // 2. Waits COMPLETED_ASYNC_REALM_RESULTS then commits transaction in another non-looper thread.
               // 3. Listener on the RealmResults gets triggered again.
               @Test
               @RunTestInLooperThread
          @@ -1313,7 +1313,7 @@ public void run() {
                       public void onChange(RealmResults object) {
                           typebasedCommitInvocations.incrementAndGet();
                           if (typebasedCommitInvocations.get() == 1) {
          -                    // COMPLETED_ASYNC_REALM_RESULTS arrived
          +                    // COMPLETED_ASYNC_REALM_RESULTS arrived.
                               thread.start();
                               try {
                                   thread.join();
          @@ -1328,7 +1328,7 @@ public void onChange(RealmResults object) {
               // ****************************************************************************************** //
               // UC 4.
               // Callback should throw if registered on a non Looper thread.
          -    // no tests for async RealmObject & RealmResults, since those already require a Looper thread
          +    // No tests for async RealmObject & RealmResults, since those already require a Looper thread.
               // ***************************************************************************************** //
           
               // UC 4 for Realm
          @@ -1362,7 +1362,7 @@ public void onChange(Realm object) {
                   TestHelper.awaitOrFail(signalTestFinished);
               }
           
          -    // UC 4 for RealmObject
          +    // UC 4 for RealmObject.
               @Test
               public void should_throw_on_non_looper_thread_realmobject() {
                   final CountDownLatch signalTestFinished = new CountDownLatch(1);
          @@ -1394,7 +1394,7 @@ public void onChange(Dog object) {
                   TestHelper.awaitOrFail(signalTestFinished);
               }
           
          -    // UC 4 RealmObject
          +    // UC 4 RealmObject.
               @Test
               public void should_throw_on_non_looper_thread_realmresults() {
                   final CountDownLatch signalTestFinished = new CountDownLatch(1);
          @@ -1426,14 +1426,14 @@ public void onChange(RealmResults object) {
                   TestHelper.awaitOrFail(signalTestFinished);
               }
           
          -    // Test modifying syncRealmResults in RealmResults's change listener
          +    // Tests modifying syncRealmResults in RealmResults's change listener.
               @Test
               @RunTestInLooperThread
               public void change_realm_results_map_in_listener() throws InterruptedException {
                   final CountDownLatch finishedLatch = new CountDownLatch(2);
           
                   final Realm realm = looperThread.realm;
          -        // Two results needed to make sure list modification happen while iterating
          +        // Two results needed to make sure list modification happen while iterating.
                   RealmResults results1 = realm.where(Owner.class).findAll();
                   RealmResults results2 = realm.where(Cat.class).findAll();
                   RealmChangeListener listener = new RealmChangeListener() {
          @@ -1441,8 +1441,8 @@ public void change_realm_results_map_in_listener() throws InterruptedException {
                       public void onChange(Object object) {
                           RealmResults results = realm.where(Owner.class).findAll();
                           boolean foundKey = false;
          -                // Check if the results has been added to the syncRealmResults in case of the behaviour of
          -                // allObjects changes
          +                // Checks if the results has been added to the syncRealmResults in case of the behaviour of
          +                // allObjects changes.
                           for (WeakReference> weakReference :
                                   realm.handlerController.syncRealmResults.keySet()) {
                               if (weakReference.get() == results) {
          @@ -1465,8 +1465,8 @@ public void onChange(Object object) {
                   realm.commitTransaction();
               }
           
          -    // Build a RealmResults from a RealmList, and delete the RealmList. Test the behavior of ChangeListener on the
          -// "invalid" RealmResults.
          +    // Builds a RealmResults from a RealmList, and deletes the RealmList. Tests the behavior of ChangeListener on the
          +    // "invalid" RealmResults.
               @Test
               @RunTestInLooperThread
               public void changeListener_onResultsBuiltOnDeletedLinkView() {
          @@ -1495,16 +1495,16 @@ public void onChange(RealmResults object) {
                       }
                   });
           
          -        // Trigger the listener at the first time.
          +        // Triggers the listener at the first time.
                   realm.beginTransaction();
                   allTypes.deleteFromRealm();
                   realm.commitTransaction();
           
          -        // Try to trigger the listener second time.
          +        // Tries to trigger the listener second time.
                   realm.beginTransaction();
                   realm.commitTransaction();
           
          -        // Close the realm and finish the test. This needs to follow the REALM_CHANGED in the queue.
          +        // Closes the realm and finishes the test. This needs to follow the REALM_CHANGED in the queue.
                   looperThread.postRunnable(new Runnable() {
                       @Override
                       public void run() {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java
          index 97f3326534..20df1cb38a 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java
          @@ -142,7 +142,7 @@ public void tearDown() {
           
               @Test
               public void unsupportedMethods_unManagedCollections() {
          -        // RealmCollection methods
          +        // RealmCollection methods.
                   for (OrderedRealmCollectionMethod method : OrderedRealmCollectionMethod.values()) {
                       try {
                           switch (method) {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java
          index d74ed31181..48e5847ef3 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java
          @@ -84,11 +84,11 @@ public void tearDown() {
           
               @Test
               public void unsupportedMethods_unManagedCollections() {
          -        // RealmCollection methods
          +        // RealmCollection methods.
                   for (RealmCollectionMethod method : RealmCollectionMethod.values()) {
                       try {
                           switch (method) {
          -                    // Unsupported methods
          +                    // Unsupported methods.
                               case WHERE: collection.where(); break;
                               case MIN: collection.min(AllJavaTypes.FIELD_LONG); break;
                               case MAX: collection.max(AllJavaTypes.FIELD_LONG); break;
          @@ -98,7 +98,7 @@ public void unsupportedMethods_unManagedCollections() {
                               case MAX_DATE: collection.maxDate(AllJavaTypes.FIELD_DATE); break;
                               case DELETE_ALL_FROM_REALM: collection.deleteAllFromRealm(); break;
           
          -                    // Supported methods
          +                    // Supported methods.
                               case IS_VALID: assertTrue(collection.isValid()); continue;
                               case IS_MANAGED: assertFalse(collection.isManaged()); continue;
                           }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java b/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java
          index 93372b8aad..8dfdc64114 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java
          @@ -45,11 +45,11 @@ public static MockActivityManager newInstance (RealmConfiguration realmConfigura
                   return new MockActivityManager(realmConfiguration);
               }
           
          -    // simulate a configuration change, that should trigger
          +    // simulates a configuration change, that should trigger
               // to recreate the Lifecycle component
               public void sendConfigurationChange () {
                   instance.onStop();
          -        // create a new instance
          +        // creates a new instance
                   instance = LifecycleComponentFactory.newInstance(realmConfiguration);
                   references.add(new WeakReference(instance, queue));
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java
          index 8245069cb4..e7d133b948 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java
          @@ -381,14 +381,14 @@ public void testNullInputQuery() {
           
           
               public void testShouldFind() {
          -        // Create a table
          +        // Creates a table.
                   Table table = new Table();
           
                   table.addColumn(RealmFieldType.STRING, "username");
                   table.addColumn(RealmFieldType.INTEGER, "score");
                   table.addColumn(RealmFieldType.BOOLEAN, "completed");
           
          -        // Insert some values
          +        // Inserts some values.
                   table.add("Arnold", 420, false);    // 0
                   table.add("Jane", 770, false);      // 1 *
                   table.add("Erik", 600, false);      // 2
          @@ -398,26 +398,26 @@ public void testShouldFind() {
           
                   TableQuery query = table.where().greaterThan(new long[]{1}, 600);
           
          -        // find first match
          +        // Finds first match.
                   assertEquals(1, query.find());
                   assertEquals(1, query.find());
                   assertEquals(1, query.find(0));
                   assertEquals(1, query.find(1));
          -        // find next
          +        // Finds next.
                   assertEquals(3, query.find(2));
                   assertEquals(3, query.find(3));
          -        // find next
          +        // Finds next.
                   assertEquals(5, query.find(4));
                   assertEquals(5, query.find(5));
           
          -        // test backwards
          +        // Tests backwards.
                   assertEquals(5, query.find(4));
                   assertEquals(3, query.find(3));
                   assertEquals(3, query.find(2));
                   assertEquals(1, query.find(1));
                   assertEquals(1, query.find(0));
           
          -        // test out of range
          +        // Tests out of range.
                   assertEquals(-1, query.find(6));
                   try {  query.find(7);  fail("Exception expected");  } catch (ArrayIndexOutOfBoundsException e) {  }
               }
          @@ -442,10 +442,10 @@ public void testQueryWithWrongDataType() {
           
                   Table table = TestHelper.getTableWithAllColumnTypes();
           
          -        // Query the table
          +        // Queries the table.
                   TableQuery query = table.where();
           
          -        // Compare strings in non string columns
          +        // Compares strings in non string columns.
                   for (int i = 0; i <= 6; i++) {
                       try { query.equalTo(new long[]{i}, "string");                 assert(false); } catch(IllegalArgumentException e) {}
                       try { query.notEqualTo(new long[]{i}, "string");              assert(false); } catch(IllegalArgumentException e) {}
          @@ -455,7 +455,7 @@ public void testQueryWithWrongDataType() {
                       try { query.contains(new long[]{i}, "string");                assert(false); } catch(IllegalArgumentException e) {}
                   }
           
          -        // Compare integer in non integer columns
          +        // Compares integer in non integer columns.
                   for (int i = 0; i <= 6; i++) {
                       if (i != 5) {
                           try { query.equalTo(new long[]{i}, 123);                      assert(false); } catch(IllegalArgumentException e) {}
          @@ -468,7 +468,7 @@ public void testQueryWithWrongDataType() {
                       }
                   }
           
          -        // Compare float in non float columns
          +        // Compares float in non float columns.
                   for (int i = 0; i <= 6; i++) {
                       if (i != 4) {
                           try { query.equalTo(new long[]{i}, 123F);                     assert(false); } catch(IllegalArgumentException e) {}
          @@ -481,7 +481,7 @@ public void testQueryWithWrongDataType() {
                       }
                   }
           
          -        // Compare double in non double columns
          +        // Compares double in non double columns.
                   for (int i = 0; i <= 6; i++) {
                       if (i != 3) {
                           try { query.equalTo(new long[]{i}, 123D);                     assert(false); } catch(IllegalArgumentException e) {}
          @@ -494,14 +494,14 @@ public void testQueryWithWrongDataType() {
                       }
                   }
           
          -        // Compare boolean in non boolean columns
          +        // Compares boolean in non boolean columns.
                   for (int i = 0; i <= 6; i++) {
                       if (i != 1) {
                         try { query.equalTo(new long[]{i}, true);                       assert(false); } catch(IllegalArgumentException e) {}
                       }
                   }
           
          -        // Compare date
          +        // Compares date.
                   /* TODO:
                   for (int i = 0; i <= 8; i++) {
                       if (i != 2) {
          @@ -520,7 +520,7 @@ public void testQueryWithWrongDataType() {
               public void testColumnIndexOutOfBounds() {
                   Table table = TestHelper.getTableWithAllColumnTypes();
           
          -        // Query the table
          +        // Queries the table.
                   TableQuery query = table.where();
           
                   try { query.minimumInt(0);                 assert(false); } catch(IllegalArgumentException e) {}
          @@ -621,12 +621,12 @@ public void testColumnIndexOutOfBounds() {
               public void testQueryOnView() {
                   Table table = new Table();
           
          -        // Specify the column types and names
          +        // Specifies the column types and names.
                   table.addColumn(RealmFieldType.STRING, "firstName");
                   table.addColumn(RealmFieldType.STRING, "lastName");
                   table.addColumn(RealmFieldType.INTEGER, "salary");
           
          -        // Add data to the table
          +        // Adds data to the table.
                   table.add("John", "Lee", 10000);
                   table.add("Jane", "Lee", 15000);
                   table.add("John", "Anderson", 20000);
          @@ -648,12 +648,12 @@ public void testQueryOnView() {
               public void testQueryOnViewWithAlreadyQueriedTable() {
                   Table table = new Table();
           
          -        // Specify the column types and names
          +        // Specifies the column types and names.
                   table.addColumn(RealmFieldType.STRING, "firstName");
                   table.addColumn(RealmFieldType.STRING, "lastName");
                   table.addColumn(RealmFieldType.INTEGER, "salary");
           
          -        // Add data to the table
          +        // Adds data to the table.
                   table.add("John", "Lee", 10000);
                   table.add("Jane", "Lee", 15000);
                   table.add("John", "Anderson", 20000);
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java
          index 4272b7599e..ae15cbc29d 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java
          @@ -49,23 +49,23 @@ void init() {
               public void testShouldTestSortedIntTable() {
                   init();
           
          -        // before first entry
          +        // Before first entry.
                   assertEquals(0, table.lowerBoundLong(0, 0));
                   assertEquals(0, table.upperBoundLong(0, 0));
           
          -        // find middle match
          +        // Finds middle match.
                   assertEquals(4, table.lowerBoundLong(0, 40));
                   assertEquals(5, table.upperBoundLong(0, 40));
           
          -        // find middle (nonexisting)
          +        // Finds middle (nonexisting).
                   assertEquals(5, table.lowerBoundLong(0, 41));
                   assertEquals(5, table.upperBoundLong(0, 41));
           
          -        // beyond last entry
          +        // Beyond last entry.
                   assertEquals(8, table.lowerBoundLong(0, 100));
                   assertEquals(8, table.upperBoundLong(0, 100));
           
          -        // find last match (duplicated)
          +        // Finds last match (duplicated).
                   assertEquals(6, table.lowerBoundLong(0, 60));
                   assertEquals(8, table.upperBoundLong(0, 60));
           
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java
          index a7c570267b..1241e4ba64 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java
          @@ -84,14 +84,14 @@ public void testGenericAddOnTable() {
           
                           Table t = new Table();
           
          -                //If the objects matches no exception will be thrown
          +                // If the objects matches no exception will be thrown.
                           if (value.get(i).getClass().equals(value.get(j).getClass())) {
                               assertTrue(true);
           
                           } else {
          -                    //Add column
          +                    // Adds column.
                               t.addColumn(TestHelper.getColumnType(value.get(j)), value.get(j).getClass().getSimpleName());
          -                    //Add value
          +                    // Adds value.
                               try {
                                   t.add(value.get(i));
                                   fail("No matching type");
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java
          index 057b2f3009..ed9f4c41a7 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java
          @@ -74,11 +74,11 @@ public void tableToString() {
               public void rowOperationsOnZeroRow(){
           
                   Table t = new Table();
          -        // Remove rows without columns
          +        // Removes rows without columns.
                   try { t.remove(0);  fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {}
                   try { t.remove(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {}
           
          -        // Column added, remove rows again
          +        // Column added, remove rows again.
                   t.addColumn(RealmFieldType.STRING, "");
                   try { t.remove(0);  fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {}
                   try { t.remove(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {}
          @@ -89,7 +89,7 @@ public void rowOperationsOnZeroRow(){
               public void zeroColOperations() {
                   Table tableZeroCols = new Table();
           
          -        // Add rows
          +        // Adds rows.
                   try { tableZeroCols.add("val");         fail("No columns in table"); } catch (IndexOutOfBoundsException ignored) {}
                   try { tableZeroCols.addEmptyRow();      fail("No columns in table"); } catch (IndexOutOfBoundsException ignored) {}
                   try { tableZeroCols.addEmptyRows(10);   fail("No columns in table"); } catch (IndexOutOfBoundsException ignored) {}
          @@ -215,7 +215,7 @@ public void getName() {
                   //noinspection TryFinallyCanBeTryWithResources
                   try {
           
          -            // Write transaction must be run so we are sure a db exists with the correct table
          +            // Writes transaction must be run so we are sure a db exists with the correct table.
                       sharedRealm.beginTransaction();
                       sharedRealm.getTable(TABLE_NAME);
                       sharedRealm.commitTransaction();
          @@ -231,14 +231,14 @@ public void getName() {
               public void shouldThrowWhenSetIndexOnWrongRealmFieldType() {
                   for (long colIndex = 0; colIndex < t.getColumnCount(); colIndex++) {
           
          -            // All types supported addSearchIndex and removeSearchIndex
          +            // All types supported addSearchIndex and removeSearchIndex.
                       boolean exceptionExpected = (
                                       t.getColumnType(colIndex) != RealmFieldType.STRING &&
                                       t.getColumnType(colIndex) != RealmFieldType.INTEGER &&
                                       t.getColumnType(colIndex) != RealmFieldType.BOOLEAN &&
                                       t.getColumnType(colIndex) != RealmFieldType.DATE);
           
          -            // Try to addSearchIndex()
          +            // Tries to addSearchIndex().
                       try {
                           t.addSearchIndex(colIndex);
                           if (exceptionExpected) {
          @@ -247,9 +247,9 @@ public void shouldThrowWhenSetIndexOnWrongRealmFieldType() {
                       } catch (IllegalArgumentException ignored) {
                       }
           
          -            // Try to removeSearchIndex()
          +            // Tries to removeSearchIndex().
                       try {
          -                // Currently core will do nothing if the column doesn't have a search index
          +                // Currently core will do nothing if the column doesn't have a search index.
                           t.removeSearchIndex(colIndex);
                           if (exceptionExpected) {
                               fail("Expected exception for colIndex " + colIndex);
          @@ -258,7 +258,7 @@ public void shouldThrowWhenSetIndexOnWrongRealmFieldType() {
                       }
           
           
          -            // Try to hasSearchIndex() for all columnTypes
          +            // Tries to hasSearchIndex() for all columnTypes.
                       t.hasSearchIndex(colIndex);
                   }
               }
          @@ -278,17 +278,17 @@ public void tableNumbers() {
                   t.addColumn(RealmFieldType.FLOAT, "floatCol");
                   t.addColumn(RealmFieldType.STRING, "StringCol");
           
          -        // Add 3 rows of data with same values in each column
          +        // Adds 3 rows of data with same values in each column.
                   t.add(1, 2.0d, 3.0f, "s1");
                   t.add(1, 2.0d, 3.0f, "s1");
                   t.add(1, 2.0d, 3.0f, "s1");
           
          -        // Add other values
          +        // Adds other values.
                   t.add(10, 20.0d, 30.0f, "s10");
                   t.add(100, 200.0d, 300.0f, "s100");
                   t.add(1000, 2000.0d, 3000.0f, "s1000");
           
          -        // Count instances of values added in the first 3 rows
          +        // Counts instances of values added in the first 3 rows.
                   assertEquals(3, t.count(0, 1));
                   assertEquals(3, t.count(1, 2.0d));
                   assertEquals(3, t.count(2, 3.0f));
          @@ -297,20 +297,20 @@ public void tableNumbers() {
                   assertEquals(3, t.findAllDouble(1, 2.0d).size());
                   assertEquals(3, t.findAllFloat(2, 3.0f).size());
           
          -        assertEquals(3, t.findFirstDouble(1, 20.0d)); // Find rows index for first double value of 20.0 in column 1
          -        assertEquals(4, t.findFirstFloat(2, 300.0f)); // Find rows index for first float value of 300.0 in column 2
          +        assertEquals(3, t.findFirstDouble(1, 20.0d)); // Find rows index for first double value of 20.0 in column 1.
          +        assertEquals(4, t.findFirstFloat(2, 300.0f)); // Find rows index for first float value of 300.0 in column 2.
           
          -        // Set double and float
          +        // Sets double and float.
                   t.setDouble(1, 2, -2.0d, false);
                   t.setFloat(2, 2, -3.0f, false);
           
          -        // Get double tests
          +        // Gets double tests.
                   assertEquals(-2.0d, t.getDouble(1, 2));
                   assertEquals(20.0d, t.getDouble(1, 3));
                   assertEquals(200.0d, t.getDouble(1, 4));
                   assertEquals(2000.0d, t.getDouble(1, 5));
           
          -        // Get float test
          +        // Gets float test.
                   assertEquals(-3.0f, t.getFloat(2, 2));
                   assertEquals(30.0f, t.getFloat(2, 3));
                   assertEquals(300.0f, t.getFloat(2, 4));
          @@ -345,13 +345,13 @@ public void minimumDate() {
           
               }
           
          -    // testing the migration of a string column to be nullable.
          +    // Tests the migration of a string column to be nullable.
               @Test
               public void convertToNullable() {
                   RealmFieldType[] columnTypes = {RealmFieldType.BOOLEAN, RealmFieldType.DATE, RealmFieldType.DOUBLE,
                           RealmFieldType.FLOAT, RealmFieldType.INTEGER, RealmFieldType.BINARY, RealmFieldType.STRING};
                   for (RealmFieldType columnType : columnTypes) {
          -            // testing various combinations of column names and nullability
          +            // Tests various combinations of column names and nullability.
                       String[] columnNames = {"foobar", "__TMP__0"};
                       for (boolean nullable : new boolean[]{Table.NOT_NULLABLE, Table.NULLABLE}) {
                           for (String columnName : columnNames) {
          @@ -427,7 +427,7 @@ public void convertToNotNullable() {
                   RealmFieldType[] columnTypes = {RealmFieldType.BOOLEAN, RealmFieldType.DATE, RealmFieldType.DOUBLE,
                           RealmFieldType.FLOAT, RealmFieldType.INTEGER, RealmFieldType.BINARY, RealmFieldType.STRING};
                   for (RealmFieldType columnType : columnTypes) {
          -            // testing various combinations of column names and nullability
          +            // Tests various combinations of column names and nullability.
                       String[] columnNames = {"foobar", "__TMP__0"};
                       for (boolean nullable : new boolean[]{Table.NOT_NULLABLE, Table.NULLABLE}) {
                           for (String columnName : columnNames) {
          @@ -513,7 +513,7 @@ else if (columnType == RealmFieldType.INTEGER)
                   }
               }
           
          -    // add column and read back if it is nullable or not
          +    // Adds column and read back if it is nullable or not.
               @Test
               public void isNullable() {
                   Table table = new Table();
          @@ -526,7 +526,7 @@ public void isNullable() {
           
               @Test
               public void defaultValue_setAndGet() {
          -        // t is not used in this test
          +        // t is not used in this test.
                   t = null;
                   final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration());
                   //noinspection TryFinallyCanBeTryWithResources
          @@ -544,8 +544,8 @@ public void defaultValue_setAndGet() {
                               new Pair(RealmFieldType.FLOAT, 1.234f),
                               new Pair(RealmFieldType.DOUBLE, Math.PI),
                               new Pair(RealmFieldType.OBJECT, 0L)
          -                    // currently, LIST does not support default value
          -                    //new Pair(RealmFieldType.LIST, )
          +                    // Currently, LIST does not support default value.
          +                    // new Pair(RealmFieldType.LIST, )
                       );
           
                       for (Pair columnInfo : columnInfoList) {
          @@ -605,7 +605,7 @@ public void defaultValue_setAndGet() {
                       }
                       sharedRealm.commitTransaction();
           
          -            // check if the value can be read after committing transaction
          +            // Checks if the value can be read after committing transaction.
                       it = columnInfoList.listIterator();
                       for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) {
                           Pair columnInfo = it.next();
          @@ -649,7 +649,7 @@ public void defaultValue_setAndGet() {
           
               @Test
               public void defaultValue_setMultipleTimes() {
          -        // t is not used in this test
          +        // t is not used in this test.
                   t = null;
                   final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration());
                   //noinspection TryFinallyCanBeTryWithResources
          @@ -667,8 +667,8 @@ public void defaultValue_setMultipleTimes() {
                               new Pair(RealmFieldType.FLOAT, new Float[] {1.234f, 100f}),
                               new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}),
                               new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L})
          -                    // currently, LIST does not support default value
          -                    //new Pair(RealmFieldType.LIST, )
          +                    // Currently, LIST does not support default value.
          +                    // new Pair(RealmFieldType.LIST, )
                       );
           
                       for (Pair columnInfo : columnInfoList) {
          @@ -682,7 +682,7 @@ public void defaultValue_setMultipleTimes() {
           
                       sharedRealm.beginTransaction();
                       table.addEmptyRow();
          -            table.addEmptyRow(); // for link field update
          +            table.addEmptyRow(); // For link field update.
           
                       ListIterator> it = columnInfoList.listIterator();
                       for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) {
          @@ -738,7 +738,7 @@ public void defaultValue_setMultipleTimes() {
                       }
                       sharedRealm.commitTransaction();
           
          -            // check if the value can be read after committing transaction
          +            // Checks if the value can be read after committing transaction.
                       it = columnInfoList.listIterator();
                       for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) {
                           Pair columnInfo = it.next();
          @@ -781,7 +781,7 @@ public void defaultValue_setMultipleTimes() {
           
               @Test
               public void defaultValue_overwrittenByNonDefault() {
          -        // t is not used in this test
          +        // t is not used in this test.
                   t = null;
                   final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration());
                   //noinspection TryFinallyCanBeTryWithResources
          @@ -799,8 +799,8 @@ public void defaultValue_overwrittenByNonDefault() {
                               new Pair(RealmFieldType.FLOAT, new Float[] {1.234f, 100f}),
                               new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}),
                               new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L})
          -                    // currently, LIST does not support default value
          -                    //new Pair(RealmFieldType.LIST, )
          +                    // Currently, LIST does not support default value.
          +                    // new Pair(RealmFieldType.LIST, )
                       );
           
                       for (Pair columnInfo : columnInfoList) {
          @@ -814,9 +814,9 @@ public void defaultValue_overwrittenByNonDefault() {
           
                       sharedRealm.beginTransaction();
                       table.addEmptyRow();
          -            table.addEmptyRow(); // for link field update
          +            table.addEmptyRow(); // For link field update.
           
          -            // set as default
          +            // Sets as default.
                       ListIterator> it = columnInfoList.listIterator();
                       for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) {
                           Pair columnInfo = it.next();
          @@ -854,7 +854,7 @@ public void defaultValue_overwrittenByNonDefault() {
                       }
                       sharedRealm.commitTransaction();
           
          -            // update as non default
          +            // Updates as non default.
                       sharedRealm.beginTransaction();
                       it = columnInfoList.listIterator();
                       for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) {
          @@ -901,7 +901,7 @@ public void defaultValue_overwrittenByNonDefault() {
                       }
                       sharedRealm.commitTransaction();
           
          -            // check if the value was overwritten
          +            // Checks if the value was overwritten.
                       it = columnInfoList.listIterator();
                       for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) {
                           Pair columnInfo = it.next();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java
          index 47ed416aaa..fa366ed50d 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableViewTest.java
          @@ -98,7 +98,7 @@ public void setNull() {
                   }
                   sharedRealm.commitTransaction();
           
          -        // check if TableView#setNull() worked as expected
          +        // Checks if TableView#setNull() worked as expected
                   for (int i = 0; i < table.size(); i++) {
                       assertEquals("index: " + i, isOdd(i), table.isNull(STRING_COLUMN_INDEX, i));
                   }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java
          index 661e6c1a6d..8ab65d077b 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIViewTest.java
          @@ -36,14 +36,14 @@ public class JNIViewTest extends TestCase {
           
               @Override
               public void setUp() {
          -        //Specify table
          +        // Specifies table.
                   t = new Table();
                   t.addColumn(RealmFieldType.STRING, "Name");
                   t.addColumn(RealmFieldType.BOOLEAN,   "Study");
                   t.addColumn(RealmFieldType.INTEGER,    "Age");
                   t.addColumn(RealmFieldType.DATE,   "Birthday");
           
          -        //Add data
          +        // Adds data.
                   t.add("cc", true,  24, date1);
                   t.add("dd", false, 35, date2);
                   t.add("bb", true,  22, date3);
          @@ -56,7 +56,7 @@ public void setUp() {
               }
           
               public void testUnimplementedMethodsShouldFail() {
          -        //Get a view containing all rows in table since you can only sort views currently.
          +        // Gets a view containing all rows in table since you can only sort views currently.
                   TableView view = t.where().findAll();
           
                   try { view.upperBoundLong(0, 0); fail("Not implemented yet"); } catch (RuntimeException e ) { }
          @@ -67,10 +67,10 @@ public void testUnimplementedMethodsShouldFail() {
           
           
               public void testShouldSortViewDate() {
          -        //Get a view containing all rows in table since you can only sort views currently.
          +        // Gets a view containing all rows in table since you can only sort views currently.
                   TableView view = t.where().findAll();
           
          -        //Sort without specifying the order, should default to ascending.
          +        // Sorts without specifying the order, should default to ascending.
                   view.sort(3);
                   assertEquals(date3, view.getDate(3, 0));
                   assertEquals(date2, view.getDate(3, 1));
          @@ -81,10 +81,10 @@ public void testShouldSortViewDate() {
           
           
               public void testShouldSortViewIntegers() {
          -        //Get a view containing all rows in table since you can only sort views currently.
          +        // Gets a view containing all rows in table since you can only sort views currently.
                   TableView view = t.where().findAll();
           
          -        //Sort without specifying the order, should default to ascending.
          +        // Sorts without specifying the order, should default to ascending.
                   view.sort(2);
                   assertEquals(22, view.getLong(2, 0));
                   assertEquals(22, view.getLong(2, 1));
          @@ -92,7 +92,7 @@ public void testShouldSortViewIntegers() {
                   assertEquals(35, view.getLong(2, 3));
                   assertEquals("dd", view.getString(0, 3));
           
          -        //Sort descending - creating a new view
          +        // Sorts descending - creating a new view.
                   view.sort(2, Sort.DESCENDING);
                   assertEquals(35, view.getLong(2, 0));
                   assertEquals(24, view.getLong(2, 1));
          @@ -100,7 +100,7 @@ public void testShouldSortViewIntegers() {
                   assertEquals(22, view.getLong(2, 3));
                   assertEquals("dd", view.getString(0, 0));
           
          -        //Sort ascending.
          +        // Sorts ascending.
                   TableView view2 = t.where().findAll();
                   view2.sort(2, Sort.ASCENDING);
                   assertEquals(22, view2.getLong(2, 0));
          @@ -109,7 +109,7 @@ public void testShouldSortViewIntegers() {
                   assertEquals(35, view2.getLong(2, 3));
                   assertEquals("dd", view2.getString(0, 3));
           
          -        // Check that old view is still the same
          +        // Checks that old view is still the same.
                   assertEquals(35, view.getLong(2, 0));
                   assertEquals(24, view.getLong(2, 1));
                   assertEquals(22, view.getLong(2, 2));
          @@ -221,10 +221,10 @@ public void testGetSourceRowNoRows() {
                   t.addColumn(RealmFieldType.STRING, "");
                   t.addColumn(RealmFieldType.INTEGER, "");
                   t.addColumn(RealmFieldType.BOOLEAN, "");
          -        // No data is added
          +        // No data is added.
                   TableView v = t.where().findAll();
           
          -        // Out of bound
          +        // Out of bound.
                   try { assertEquals(0, v.getSourceRowIndex(0));      fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { }
                   try { assertEquals(0, v.getSourceRowIndex(1));      fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { }
               }
          @@ -232,20 +232,20 @@ public void testGetSourceRowNoRows() {
           
               public void testGetSourceRowEmptyTable() {
                   Table t = new Table();
          -        // No columns
          +        // No columns.
                   TableView v = t.where().findAll();
           
          -        // Out of bound
          +        // Out of bound.
                   try { assertEquals(0, v.getSourceRowIndex(0));      fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { }
                   try { assertEquals(0, v.getSourceRowIndex(1));      fail("index ot of bounds"); } catch (IndexOutOfBoundsException e) { }
               }
           
           
               public void testShouldSortViewBool() {
          -        //Get a view containing all rows in table since you can only sort views currently.
          +        // Gets a view containing all rows in table since you can only sort views currently.
                   TableView view = t.where().findAll();
           
          -        //Sort without specifying the order, should default to ascending.
          +        // Sorts without specifying the order, should default to ascending.
                   view.sort(1);
                   assertEquals(false, view.getBoolean(1, 0));
                   assertEquals(false, view.getBoolean(1, 1));
          @@ -368,22 +368,22 @@ public void testViewShouldInvalidate() {
                   t.add(3);
           
                   TableView view = t.where().equalTo(new long[]{0}, 2).findAll();
          -        // access view is ok.
          +        // Access view is ok.
                   assertEquals(1, view.size());
           
          -        // access view after change in value is ok
          +        // Access view after change in value is ok.
                   t.setLong(0, 0, 3, false);
                   accessingViewOk(view);
           
          -        // access view after additions to table must fail
          +        // Access view after additions to table must fail.
                   t.add(4);
                   accessingViewMustThrow(view);
           
          -        // recreate view to access again
          +        // Recreates view to access again.
                   view = t.where().equalTo(new long[]{0}, 2).findAll();
                   accessingViewOk(view);
           
          -        // Removing any row in Table should invalidate view
          +        // Removing any row in Table should invalidate view.
                   t.remove(3);
                   accessingViewMustThrow(view);
               }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java
          index 3ba437ac44..cbf00b2ceb 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java
          @@ -84,7 +84,7 @@ private Table getTableWithIntegerPrimaryKey() {
                   return t;
               }
           
          -    // Test that primary key constraints are actually removed
          +    // Tests that primary key constraints are actually removed.
               @Test
               public void removingPrimaryKeyRemovesConstraint_typeSetters() {
                   RealmConfiguration config = configFactory.createConfigurationBuilder()
          @@ -96,14 +96,14 @@ public void removingPrimaryKeyRemovesConstraint_typeSetters() {
                   tbl.addColumn(RealmFieldType.STRING, "name");
                   tbl.setPrimaryKey("name");
           
          -        // Create first entry with name "Foo"
          +        // Creates first entry with name "Foo".
                   tbl.setString(0, tbl.addEmptyRow(), "Foo", false);
           
                   long rowIndex = tbl.addEmptyRow();
                   try {
          -            tbl.setString(0, rowIndex, "Foo", false); // Try to create 2nd entry with name Foo
          +            tbl.setString(0, rowIndex, "Foo", false); // Tries to create 2nd entry with name Foo.
                   } catch (RealmPrimaryKeyConstraintException e1) {
          -            tbl.setPrimaryKey(""); // Primary key check worked, now remove it and try again.
          +            tbl.setPrimaryKey(""); // Primary key check worked, now removes it and tries again.
                       try {
                           tbl.setString(0, rowIndex, "Foo", false);
                           return;
          @@ -190,7 +190,7 @@ public void migratePrimaryKeyTableIfNeeded_second() throws IOException {
                   assertEquals("AnnotationTypes", sharedRealm.getTable("pk").getString(0, 0));
               }
           
          -    // See https://github.com/realm/realm-java/issues/1775 .
          +    // See https://github.com/realm/realm-java/issues/1775
               // Before 0.84.2, pk table added prefix "class_" to every class's name.
               // After 0.84.2, the pk table should be migrated automatically to remove the "class_".
               // In 0.84.2, the class names in pk table has been renamed to some incorrect names like "Thclass", "Mclass",
          @@ -236,14 +236,14 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() {
                   long classColumn = pkTable.getColumnIndex("pk_table");
                   pkTable.removeSearchIndex(classColumn);
           
          -        // Try to add a pk for another table
          +        // Tries to add a pk for another table.
                   Table table2 = sharedRealm.getTable("TestTable2");
                   long column2 = table2.addColumn(RealmFieldType.INTEGER, "PKColumn");
                   table2.addSearchIndex(column2);
                   try {
                       table2.setPrimaryKey(column2);
                   } catch (RealmError ignored) {
          -            // Column has no search index
          +            // Column has no search index.
                   }
           
                   assertFalse(pkTable.hasSearchIndex(classColumn));
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java
          index b0b339e230..074f535542 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java
          @@ -175,16 +175,16 @@ public void onSchemaVersionChanged(long currentVersion) {
           
                   sharedRealm.beginTransaction();
                   try {
          -            // listener is not called if there was no schema change
          +            // Listener is not called if there was no schema change.
                       assertFalse(listenerCalled.get());
           
          -            // change the schema version
          +            // Changes the schema version.
                       sharedRealm.setSchemaVersion(before + 1);
                   } finally {
                       sharedRealm.commitTransaction();
                   }
           
          -        // listener is not yet called
          +        // Listener is not yet called.
                   assertFalse(listenerCalled.get());
           
                   sharedRealm.beginTransaction();
          @@ -213,18 +213,18 @@ public void onSchemaVersionChanged(long currentVersion) {
                   final long before = sharedRealm.getSchemaVersion();
           
                   sharedRealm.refresh();
          -        // listener is not called if there was no schema change
          +        // Listener is not called if there was no schema change.
                   assertFalse(listenerCalled.get());
           
                   sharedRealm.beginTransaction();
                   try {
          -            // change the schema version
          +            // Changes the schema version.
                       sharedRealm.setSchemaVersion(before + 1);
                   } finally {
                       sharedRealm.commitTransaction();
                   }
           
          -        // listener is not yet called
          +        // Listener is not yet called.
                   assertFalse(listenerCalled.get());
           
                   sharedRealm.refresh();
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java
          index 751158bc98..ab4261f680 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java
          @@ -42,7 +42,7 @@ void init() {
               public void testShouldTestDistinct() {
                   init();
           
          -        // Must set index before using distinct()
          +        // Must set index before using distinct().
                   table.addSearchIndex(1);
                   assertEquals(true, table.hasSearchIndex(1));
           
          @@ -56,14 +56,14 @@ public void testShouldTestDistinct() {
           
           // TODO: parametric test
           /*    *//**
          -     * Should throw exception if trying to get distinct on columns where index has not been set
          +     * Should throw exception if trying to get distinct on columns where index has not been set.
                * @param index
                *//*
           
               @Test(expectedExceptions = UnsupportedOperationException.class, dataProvider = "columnIndex")
               public void shouldTestDistinctErrorWhenNoIndex(Long index) {
           
          -        //Get a table with all available column types
          +        // Gets a table with all available column types.
                   Table t = TestHelper.getTableWithAllColumnTypes();
           
                   TableView view = table.getDistinctView(1);
          @@ -81,12 +81,12 @@ public void testShouldTestDistinctErrorWhenIndexOutOfBounds() {
               }
           
               /**
          -     * Check that Index can be set on multiple columns, with the String
          +     * Checks that Index can be set on multiple columns, with the String.
                * @param
                */
               public void testShouldTestSettingIndexOnMultipleColumns() {
           
          -        //Create a table only with String type columns
          +        // Creates a table only with String type columns
                   Table t = new Table();
                   t.addColumn(RealmFieldType.STRING, "col1");
                   t.addColumn(RealmFieldType.STRING, "col2");
          @@ -115,10 +115,10 @@ public void testShouldTestSettingIndexOnMultipleColumns() {
               @Test(expectedExceptions = IllegalArgumentException.class, dataProvider = "columnIndex")
               public void shouldTestIndexOnWrongColumnType(Long index) {
           
          -        //Get a table with all available column types
          +        // Gets a table with all available column types.
                   Table t = TestHelper.getTableWithAllColumnTypes();
           
          -        //If column type is String, then throw the excepted exception
          +        // If column type is String, then throw the excepted exception.
                   if (t.getColumnType(index).equals(RealmFieldType.STRING)){
                       throw new IllegalArgumentException();
                   }
          @@ -155,7 +155,7 @@ public void testRemoveSearchIndexNoop() {
                   init();
                   assertEquals(false, table.hasSearchIndex(1));
           
          -        // remove index from non-indexed column is a no-op
          +        // Removes index from non-indexed column is a no-op.
                   table.removeSearchIndex(1);
                   assertEquals(false, table.hasSearchIndex(1));
               }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java
          index ce52c272cb..42958d93fc 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java
          @@ -89,40 +89,40 @@ public void testParseISO8601Dates() throws ParseException {
                   Date dateZeroMillis = cal.getTime();
                   cal.set(Calendar.SECOND, 0);
           
          -        // Parse date with short time and decimal second
          +        // Parses date with short time and decimal second.
                   Date d = JsonUtils.stringToDate("2007-08-13T195123.789Z");
                   assertEquals(date, d);
           
          -        // Short time without decimal second
          +        // Short time without decimal second.
                   d = JsonUtils.stringToDate("2007-08-13T195123Z");
                   assertEquals(dateZeroMillis, d);
           
          -        // GMT+2 with decimal second
          +        // GMT+2 with decimal second.
                   d = JsonUtils.stringToDate("2007-08-13T215123.789+02:00");
                   assertEquals(date, d);
           
          -        // Tests without time
          +        // Tests without time.
                   cal = new GregorianCalendar(2007, 8 - 1, 13, 0, 0, 0);
                   cal.set(Calendar.MILLISECOND, 0);
                   cal.setTimeZone(TimeZone.getTimeZone("GMT"));
                   Date dateWithoutTime = cal.getTime();
           
          -        // Date only with hyphens
          +        // Date only with hyphens.
                   d = JsonUtils.stringToDate("2007-08-13Z");
                   assertEquals(dateWithoutTime, d);
           
          -        // Date, no hyphens
          +        // Date, no hyphens.
                   d = JsonUtils.stringToDate("20070813Z");
                   assertEquals(dateWithoutTime, d);
           
          -        // Hyphenated Date with empty time
          +        // Hyphenated Date with empty time.
                   d = JsonUtils.stringToDate("2007-08-13+00:00");
                   assertEquals(dateWithoutTime, d);
           
          -        // Non-hyphenated date with empty time
          +        // Non-hyphenated date with empty time.
                   d = JsonUtils.stringToDate("20070813+00:00");
                   assertEquals(dateWithoutTime, d);
           
          -        // Please see the ISO8601UtilsTest.java file for a full suite of ISO8601 tests
          +        // Please see the ISO8601UtilsTest.java file for a full suite of ISO8601 tests.
               }
           }
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java
          index cf4041c3d2..b81238326a 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java
          @@ -148,7 +148,7 @@ public void run() {
                               // These exceptions should only come from TestHelper.awaitOrFail()
                               testException = error;
                           } finally {
          -                    // Try as hard as possible to close down gracefully, while still keeping all exceptions intact.
          +                    // Tries as hard as possible to close down gracefully, while still keeping all exceptions intact.
                               try {
                                   after();
                               } catch (Throwable e) {
          @@ -220,7 +220,7 @@ public void postRunnableDelayed(Runnable runnable, long delayMillis) {
               }
           
               /**
          -     * Tear down logic which is guaranteed to run after the looper test has either completed or failed.
          +     * Tears down logic which is guaranteed to run after the looper test has either completed or failed.
                * This will run on the same thread as the looper test.
                */
               public void looperTearDown() {
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java
          index d02a0ea22b..65e81bdea4 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java
          @@ -75,7 +75,7 @@ protected void before() throws Throwable {
           
               @Override
               protected void after() {
          -        // Wait all async tasks done to ensure successful deleteRealm call.
          +        // Waits all async tasks done to ensure successful deleteRealm call.
                   // This will throw when timeout. And the reason of timeout needs to be solved properly.
                   TestHelper.waitRealmThreadExecutorFinish();
           
          @@ -84,7 +84,7 @@ protected void after() {
                           Realm.deleteRealm(configuration);
                       }
                   } catch (IllegalStateException e) {
          -            // Only throw the exception caused by deleting the opened Realm if the test case itself doesn't throw.
          +            // Only throws the exception caused by deleting the opened Realm if the test case itself doesn't throw.
                       if (!unitTestFailed) {
                           throw e;
                       }
          @@ -151,7 +151,7 @@ public void copyRealmFromAssets(Context context, String realmPath, String newNam
               }
           
               public void copyRealmFromAssets(Context context, String realmPath, RealmConfiguration config) throws IOException {
          -        // Delete the existing file before copy
          +        // Deletes the existing file before copy
                   Realm.deleteRealm(config);
           
                   File outFile = new File(config.getRealmDirectory(), config.getRealmFileName());
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java
          index 54c724abd2..78c7c18575 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java
          @@ -46,8 +46,8 @@ private Step(int message) {
           
                   abstract void run();
           
          -        // Pass a null to tell main process that everything is OK.
          -        // Otherwise, pass a error String which will be used by assertion in main process.
          +        // Passes a null to tell main process that everything is OK.
          +        // Otherwise, passes a error String which will be used by assertion in main process.
                   protected void response(String error) {
                       try {
                           Message msg = Message.obtain(null, message);
          @@ -117,7 +117,7 @@ public void handleMessage(Message msg) {
                   }
               }
           
          -    // Call this function to return the String of current class and line number.
          +    // Calls this function to return the String of current class and line number.
               private static String currentLine() {
                   StackTraceElement element = new Throwable().getStackTrace()[1];
                   return element.getClassName() + " line " + element.getLineNumber() + ": ";
          diff --git a/realm/realm-library/src/androidTest/java/io/realm/util/ExceptionHolder.java b/realm/realm-library/src/androidTest/java/io/realm/util/ExceptionHolder.java
          index f630cb8a4a..65005f63c6 100644
          --- a/realm/realm-library/src/androidTest/java/io/realm/util/ExceptionHolder.java
          +++ b/realm/realm-library/src/androidTest/java/io/realm/util/ExceptionHolder.java
          @@ -51,7 +51,7 @@ public class ExceptionHolder {
               private Throwable exception;
           
               /**
          -     * Sets the exception held by this container. This is a one-shot operation.
          +     * Sets the exception held by this container. This is an one-shot operation.
                *
                * @param throwable error to save.
                * @throws IllegalStateException if an exception have already been put into this holder.
          diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java
          index da65aab166..5515532e22 100644
          --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java
          +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java
          @@ -48,7 +48,7 @@ public static SpannerConfig getConfiguration(String className) {
                   ResultProcessor csvResultProcessor = new CSVResultProcessor(csvFile);
           
                   // General configuration for running benchmarks.
          -        // Always save result files. CI will determine if it wants to store them.
          +        // Always saves result files. CI will determine if it wants to store them.
                   SpannerConfig.Builder builder = new SpannerConfig.Builder()
                           .saveResults(resultsDir, className + ".json")
                           .trialsPrExperiment(1)
          @@ -62,10 +62,10 @@ public static SpannerConfig getConfiguration(String className) {
                           )
                           .addResultProcessor(csvResultProcessor);
           
          -        // Only use baseline file if it exists
          +        // Only uses baseline file if it exists.
                   if (baselineFile.exists()) {
                       builder.useBaseline(baselineFile);
          -            // Test that 25. , 50. and 75. percentile don't change by more than 15%
          +            // Tests that 25. , 50. and 75. percentile doesn't change by more than 15%.
                       builder.percentileFailureLimit(25f, 0.15f);
                       builder.percentileFailureLimit(50f, 0.15f);
                       builder.percentileFailureLimit(75f, 0.15f);
          diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java
          index 9a11c14fb2..f8737f2fb2 100644
          --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java
          +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java
          @@ -31,7 +31,7 @@
            * Converts the result of a benchmark to CSV for easier processing by other data/graph programs.
            *
            * Output is the following.
          - * methodname, trialNumber, params, measurements, min, max, average, 25pct, 50pct, 75pct
          + * methodname, trialNumber, params, measurements, min, max, average, 25pct, 50pct, 75pct.
            */
           public class CSVResultProcessor implements ResultProcessor {
           
          diff --git a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java
          index b0f1c194ae..f54d8bc330 100644
          --- a/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java
          +++ b/realm/realm-library/src/main/java/io/realm/AndroidNotifier.java
          @@ -44,7 +44,7 @@ public void notifyCommitByLocalThread() {
                       return;
                   }
           
          -        // Force any updates on the current thread to the front the queue. Doing this is mostly
          +        // Forces any updates on the current thread to the front the queue. Doing this is mostly
                   // relevant on the UI thread where it could otherwise process a motion event before the
                   // REALM_CHANGED event. This could in turn cause a UI component like ListView to crash. See
                   // https://github.com/realm/realm-android-adapters/issues/11 for such a case.
          diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          index f03be41aa0..d3f1a7f70c 100644
          --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java
          @@ -281,7 +281,7 @@ public void stopWaitForChange() {
                   RealmCache.invokeWithLock(new RealmCache.Callback0() {
                       @Override
                       public void onCall() {
          -                // Check if the Realm instance has been closed
          +                // Checks if the Realm instance has been closed.
                           if (sharedRealm == null || sharedRealm.isClosed()) {
                               throw new IllegalStateException(BaseRealm.CLOSED_REALM_MESSAGE);
                           }
          @@ -378,7 +378,7 @@ protected void checkIfValid() {
                       throw new IllegalStateException(BaseRealm.CLOSED_REALM_MESSAGE);
                   }
           
          -        // Check if we are in the right thread
          +        // Checks if we are in the right thread.
                   if (threadId != Thread.currentThread().getId()) {
                       throw new IllegalStateException(BaseRealm.INCORRECT_THREAD_MESSAGE);
                   }
          @@ -391,7 +391,7 @@ protected void checkIfInTransaction() {
               }
           
               /**
          -     * Check if the Realm is valid and in a transaction.
          +     * Checks if the Realm is valid and in a transaction.
                */
               protected void checkIfValidAndInTransaction() {
                   if (!isInTransaction()) {
          @@ -400,7 +400,7 @@ protected void checkIfValidAndInTransaction() {
               }
           
               /**
          -     * Check if the Realm is not built with a SyncRealmConfiguration
          +     * Checks if the Realm is not built with a SyncRealmConfiguration.
                */
               void checkNotInSync() {
                   if (configuration.isSyncConfiguration()) {
          @@ -657,7 +657,7 @@ public void onResult(int count) {
                   }
               }
           
          -    // Return true if this Realm can receive notifications.
          +    // Returns true if this Realm can receive notifications.
               boolean hasValidNotifier() {
                   return sharedRealm.realmNotifier != null && sharedRealm.realmNotifier.isValid();
               }
          @@ -673,7 +673,7 @@ protected void finalize() throws Throwable {
                   super.finalize();
               }
           
          -    // Internal delegate for migrations
          +    // Internal delegate for migrations.
               protected interface MigrationCallback {
                   void migrationComplete();
               }
          diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java
          index 619b92ca60..1d35df1b8c 100644
          --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java
          +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java
          @@ -402,7 +402,7 @@ public void set(String fieldName, Object value) {
                   boolean isString = (value instanceof String);
                   String strValue = isString ? (String) value : null;
           
          -        // Do implicit conversion if needed
          +        // Does implicit conversion if needed.
                   long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName);
                   RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex);
                   if (isString && type != RealmFieldType.STRING) {
          @@ -426,7 +426,7 @@ public void set(String fieldName, Object value) {
                   }
               }
           
          -    // Automatically finds the appropriate setter based on the objects type
          +    // Automatically finds the appropriate setter based on the objects type.
               private void setValue(String fieldName, Object value) {
                   Class valueClass = value.getClass();
                   if (valueClass == Boolean.class) {
          diff --git a/realm/realm-library/src/main/java/io/realm/HandlerController.java b/realm/realm-library/src/main/java/io/realm/HandlerController.java
          index fa24bff167..db6c1d3f1b 100644
          --- a/realm/realm-library/src/main/java/io/realm/HandlerController.java
          +++ b/realm/realm-library/src/main/java/io/realm/HandlerController.java
          @@ -53,19 +53,19 @@ final class HandlerController implements Handler.Callback {
           
               private final static Boolean NO_REALM_QUERY = Boolean.TRUE;
           
          -    // Keep a strong reference to the registered RealmChangeListener
          -    // user should unregister those listeners
          +    // Keeps a strong reference to the registered RealmChangeListener.
          +    // User should unregister those listeners.
               final CopyOnWriteArrayList> changeListeners = new CopyOnWriteArrayList>();
           
          -    // Keep a weak reference to the registered RealmChangeListener those are Weak since
          -    // for some UC (ex: RealmBaseAdapter) we don't know when it's the best time to unregister the listener
          +    // Keeps a weak reference to the registered RealmChangeListener those are Weak since
          +    // for some UC (ex: RealmBaseAdapter) we don't know when it's the best time to unregister the listener.
               final List>> weakChangeListeners =
                       new CopyOnWriteArrayList>>();
           
               final BaseRealm realm;
               private boolean autoRefresh; // Requires a Looper thread to be true.
           
          -    // pending update of async queries
          +    // Pending update of async queries.
               private Future updateAsyncQueriesTask;
           
               private final ReferenceQueue> referenceQueueAsyncRealmResults =
          @@ -73,19 +73,19 @@ final class HandlerController implements Handler.Callback {
               private final ReferenceQueue> referenceQueueSyncRealmResults =
                       new ReferenceQueue>();
               final ReferenceQueue referenceQueueRealmObject = new ReferenceQueue();
          -    // keep a WeakReference list to RealmResults obtained asynchronously in order to update them
          +    // Keeps a WeakReference list to RealmResults obtained asynchronously in order to update them
               // RealmQuery is not WeakReferenced to prevent it from being GC'd. RealmQuery should be
               // cleaned if RealmResults is cleaned. we need to keep RealmQuery because it contains the query
               // pointer (to handover for each update) + all the arguments necessary to rerun the query:
               // sorting orders, soring columns, type (findAll, findFirst, findAllSorted etc.)
               final Map>, RealmQuery> asyncRealmResults =
                       new IdentityHashMap>, RealmQuery>();
          -    // Keep a WeakReference to the currently empty RealmObjects obtained asynchronously. We need to keep re-running
          +    // Keeps a WeakReference to the currently empty RealmObjects obtained asynchronously. We need to keep re-running
               // the query in the background for each commit, until we got a valid Row (pointer)
               final Map, RealmQuery> emptyAsyncRealmObject =
                       new ConcurrentHashMap, RealmQuery>();
           
          -    // Keep a reference to the list of sync RealmResults, we'll use it
          +    // Keeps a reference to the list of sync RealmResults, we'll use it
               // to deliver type based notification once the shared_group advance
               final IdentitySet>> syncRealmResults =
                       new IdentitySet>>();
          @@ -133,13 +133,13 @@ public boolean handleMessage(Message message) {
                               break;
           
                           case HandlerControllerConstants.COMPLETED_UPDATE_ASYNC_QUERIES:
          -                    // this is called once the background thread completed the update of the async queries
          +                    // This is called once the background thread completed the update of the async queries.
                               result = (QueryUpdateTask.Result) message.obj;
                               completedAsyncQueriesUpdate(result);
                               break;
           
                           case HandlerControllerConstants.REALM_ASYNC_BACKGROUND_EXCEPTION:
          -                    // Don't fail silently in the background in case of Core exception
          +                    // Doesn't fail silently in the background in case of Core exception.
                               throw (Error) message.obj;
           
                           default:
          @@ -160,7 +160,7 @@ public boolean handleMessage(Message message) {
                * @param onSuccess onSuccess callback to run for the async transaction that completed.
                */
               public void handleAsyncTransactionCompleted(Runnable onSuccess) {
          -        // Same reason as handleMessage()
          +        // Same reason as handleMessage().
                   if (realm.sharedRealm != null) {
                       if (onSuccess != null) {
                           pendingOnSuccessAsyncTransactionCallbacks.add(onSuccess);
          @@ -189,7 +189,7 @@ void addChangeListenerAsWeakReference(RealmChangeListener l
                       WeakReference> weakRef = iterator.next();
                       RealmChangeListener weakListener = weakRef.get();
           
          -            // Collect all listeners that are GC'ed
          +            // Collects all listeners that are GC'ed.
                       if (weakListener == null) {
                           if (toRemoveList == null) {
                               toRemoveList = new ArrayList>>(weakChangeListeners.size());
          @@ -197,7 +197,7 @@ void addChangeListenerAsWeakReference(RealmChangeListener l
                           toRemoveList.add(weakRef);
                       }
           
          -            // Check if Listener already exists
          +            // Checks if Listener already exists.
                       if (weakListener == listener) {
                           addListener = false;
                       }
          @@ -217,7 +217,7 @@ void removeWeakChangeListener(RealmChangeListener listener)
                       WeakReference> weakRef = weakChangeListeners.get(i);
                       RealmChangeListener weakListener = weakRef.get();
           
          -            // Collect all listeners that are GC'ed or we need to remove
          +            // Collects all listeners that are GC'ed or we need to remove.
                       if (weakListener == null || weakListener == listener) {
                           if (toRemoveList == null) {
                               toRemoveList = new ArrayList>>(weakChangeListeners.size());
          @@ -241,13 +241,13 @@ void removeAllChangeListeners() {
                * NOTE: Should only be called from {@link #notifyAllListeners(List)}.
                */
               private void notifyGlobalListeners() {
          -        // notify strong reference listener
          +        // Notifies strong reference listener.
                   Iterator> iteratorStrongListeners = changeListeners.iterator();
          -        while (!realm.isClosed() && iteratorStrongListeners.hasNext()) { // every callback could close the realm
          +        while (!realm.isClosed() && iteratorStrongListeners.hasNext()) { // Every callback could close the realm.
                       RealmChangeListener listener = iteratorStrongListeners.next();
                       listener.onChange(realm);
                   }
          -        // notify weak reference listener (internals)
          +        // Notifies weak reference listener (internals).
                   Iterator>> iteratorWeakListeners = weakChangeListeners.iterator();
                   List>> toRemoveList = null;
                   while (!realm.isClosed() && iteratorWeakListeners.hasNext()) {
          @@ -300,25 +300,25 @@ private void updateAsyncEmptyRealmObject() {
                */
               void notifyAllListeners(List> realmResultsToBeNotified) {
           
          -        // Notify all RealmResults (async and synchronous).
          +        // Notifies all RealmResults (async and synchronous).
                   for (Iterator> it = realmResultsToBeNotified.iterator(); !realm.isClosed() && it.hasNext(); ) {
                       RealmResults realmResults = it.next();
                       realmResults.notifyChangeListeners(false);
                   }
           
          -        // Notify all loaded RealmObjects
          +        // Notifies all loaded RealmObjects.
                   notifyRealmObjectCallbacks();
           
          -        // Re-run any async single objects that are still not loaded.
          +        // Re-runs any async single objects that are still not loaded.
                   // TODO: Why is this here? This was not called in `completedAsyncQueriesUpdate()`. Problem?
                   if (!realm.isClosed() && threadContainsAsyncEmptyRealmObject()) {
                       updateAsyncEmptyRealmObject();
                   }
           
          -        // Notify any completed async transactions
          +        // Notifies any completed async transactions.
                   notifyAsyncTransactionCallbacks();
           
          -        // Trigger global listeners last.
          +        // Triggers global listeners last.
                   // Note that NotificationTest.callingOrdersOfListeners will fail if orders change.
                   notifyGlobalListeners();
               }
          @@ -340,7 +340,7 @@ private void collectRealmResultsCallbacks(Iterator>, RealmQuery>> iterator = asyncRealmResults.entrySet().iterator();
                   while (iterator.hasNext()) {
                       Map.Entry>, RealmQuery> entry = iterator.next();
                       WeakReference> weakReference = entry.getKey();
                       RealmResults realmResults = weakReference.get();
                       if (realmResults == null) {
          -                // GC'd instance remove from the list
          +                // GC'd instance remove from the list.
                           iterator.remove();
           
                       } else {
          @@ -410,12 +410,12 @@ private void updateAsyncQueries() {
                                   entry.getValue().getArgument());
                       }
           
          -            // Note: we're passing an WeakRef of a RealmResults to another thread
          -            //       this is safe as long as we don't invoke any of the RealmResults methods.
          -            //       we're just using it as a Key in an IdentityHashMap (i.e doesn't call
          +            // Note: We're passing an WeakRef of a RealmResults to another thread.
          +            //       This is safe as long as we don't invoke any of the RealmResults methods.
          +            //       We're just using it as a Key in an IdentityHashMap (i.e doesn't call
                       //       AbstractList's hashCode, that require accessing objects from another thread)
                       //
          -            //       watch out when you debug, as you're IDE try to evaluate RealmResults
          +            //       Watch out when you debug, as you're IDE try to evaluate RealmResults
                       //       which break the Thread confinement constraints.
                   }
                   if (realmResultsQueryStep != null) {
          @@ -432,7 +432,7 @@ private void realmChanged(boolean localCommit) {
                   deleteWeakReferences();
                   boolean threadContainsAsyncQueries = threadContainsAsyncQueries();
           
          -        // Mixing local transactions and async queries has unavoidable race conditions
          +        // Mixing local transactions and async queries has unavoidable race conditions.
                   if (localCommit && threadContainsAsyncQueries) {
                       RealmLog.warn("Mixing asynchronous queries with local writes should be avoided. " +
                               "Realm will convert any async queries to synchronous in order to remain consistent. Use " +
          @@ -441,11 +441,11 @@ private void realmChanged(boolean localCommit) {
                   }
           
                   if (!localCommit && threadContainsAsyncQueries) {
          -            // For changes from other threads, swallow the change and re-run async queries first.
          +            // For changes from other threads, swallows the change and re-runs async queries first.
                       updateAsyncQueries();
                   } else {
                       // Following cases handled by this:
          -            // localCommit && threadContainsAsyncQueries (this is the case the warning above is about)
          +            // localCommit && threadContainsAsyncQueries (This is the case the warning above is about.)
                       // localCommit && !threadContainsAsyncQueries
                       // !localCommit && !threadContainsAsyncQueries
                       realm.sharedRealm.refresh();
          @@ -471,14 +471,14 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) {
                           SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID();
                           int compare = callerVersionID.compareTo(result.versionID);
                           if (compare == 0) {
          -                    // if the RealmResults is empty (has not completed yet) then use the value
          -                    // otherwise a task (grouped update) has already updated this RealmResults
          +                    // If the RealmResults is empty (has not completed yet) then uses the value,
          +                    // Otherwise a task (grouped update) has already updated this RealmResults.
                               if (!realmResults.isLoaded()) {
                                   RealmLog.trace("[COMPLETED_ASYNC_REALM_RESULTS %s] , realm: %s same versions, using results (RealmResults is not loaded)",
                                           weakRealmResults, HandlerController.this);
          -                        // swap pointer
          +                        // Swaps pointer.
                                   realmResults.swapTableViewPointer(result.updatedTableViews.get(weakRealmResults));
          -                        // notify callbacks
          +                        // Notifies callbacks.
                                   realmResults.syncIfNeeded();
                                   realmResults.notifyChangeListeners(false);
                               } else {
          @@ -487,14 +487,14 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) {
                               }
           
                           } else if (compare > 0) {
          -                    // we have two use cases:
          -                    // 1- this RealmResults is not empty, this means that after we started the async
          +                    // We have two use cases:
          +                    // 1- This RealmResults is not empty, this means that after we started the async
                               //    query, we received a REALM_CHANGE that triggered an update of all async queries
                               //    including the last async submitted, so no need to use the provided TableView pointer
          -                    //    (or the user forced the sync behaviour .load())
          -                    // 2- This RealmResults is still empty but this caller thread is advanced than the worker thread
          -                    //    this could happen if the current thread advanced the shared_group (via a write or refresh)
          -                    //    this means that we need to rerun the query against a newer worker thread.
          +                    //    (or the user forced the sync behaviour .load()).
          +                    // 2- This RealmResults is still empty but this caller thread is advanced than the worker thread.
          +                    //    This could happen if the current thread advanced the shared_group (via a write or refresh).
          +                    //    This means that we need to rerun the query against a newer worker thread.
           
                               if (!realmResults.isLoaded()) { // UC2
                                   // UC covered by this test: RealmAsyncQueryTests#testFindAllAsyncRetry
          @@ -518,7 +518,7 @@ private void completedAsyncRealmResults(QueryUpdateTask.Result result) {
                               }
           
                           } else {
          -                    // the caller thread is behind the worker thread,
          +                    // The caller thread is behind the worker thread,
                               // no need to rerun the query, since we're going to receive the update signal
                               // & batch update all async queries including this one
                               // UC covered by this test: RealmAsyncQueryTests#testFindAllCallerThreadBehind
          @@ -532,24 +532,24 @@ private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) {
                   SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID();
                   int compare = callerVersionID.compareTo(result.versionID);
                   if (compare > 0) {
          -            // if the caller thread is more advanced than the worker thread, it means it did a local commit.
          +            // If the caller thread is more advanced than the worker thread, it means it did a local commit.
                       // This should also have put a REALM_CHANGED event on the Looper queue, so ignoring this result should
                       // be safe as all async queries will be rerun when processing the REALM_CHANGED event.
                       RealmLog.trace("COMPLETED_UPDATE_ASYNC_QUERIES %s caller is more advanced, Looper will updates queries", HandlerController.this);
           
                   } else {
          -            // We're behind or on the same version as the worker thread
          +            // We're behind or on the same version as the worker thread.
           
          -            // only advance if we're behind
          +            // Only advances if we're behind.
                       if (compare != 0) {
          -                // no need to remove old pointers from TableView, since they're
          -                // imperative TV, they will not rerun if the SharedGroup advance
          +                // No need to remove old pointers from TableView, since they're
          +                // imperative TV, they will not rerun if the SharedGroup advance.
           
                           // UC covered by this test: RealmAsyncQueryTests#testFindAllCallerThreadBehind
                           RealmLog.trace("COMPLETED_UPDATE_ASYNC_QUERIES %s caller is behind advance_read", HandlerController.this);
          -                // refresh the Realm to the version provided by the worker thread
          +                // Refreshes the Realm to the version provided by the worker thread
                           // (advanceRead to the latest version may cause a version mismatch error) preventing us
          -                // from importing correctly the handover table view
          +                // from importing correctly the handover table view.
                           try {
                               realm.sharedRealm.refresh(result.versionID);
                           } catch (BadVersionException e) {
          @@ -567,11 +567,11 @@ private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) {
                           WeakReference> weakRealmResults = query.getKey();
                           RealmResults realmResults = weakRealmResults.get();
                           if (realmResults == null) {
          -                    // don't update GC'd instance
          +                    // Doesn't update GC'd instance.
                               asyncRealmResults.remove(weakRealmResults);
           
                           } else {
          -                    // update the instance with the new pointer
          +                    // Updates the instance with the new pointer.
                               realmResults.swapTableViewPointer(query.getValue());
                               realmResults.syncIfNeeded();
                               resultsToBeNotified.add(realmResults);
          @@ -590,7 +590,7 @@ private void completedAsyncQueriesUpdate(QueryUpdateTask.Result result) {
               }
           
               /**
          -     * Trigger onSuccess for all completed async transaction.
          +     * Triggers onSuccess for all completed async transaction.
                * 

          * NOTE: Should only be called from {@link #notifyAllListeners(List)}. */ @@ -613,12 +613,12 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) { if (proxy != null) { SharedRealm.VersionID callerVersionID = realm.sharedRealm.getVersionID(); int compare = callerVersionID.compareTo(result.versionID); - // we always query on the same version - // only two use cases could happen 1. we're on the same version or 2. the caller has advanced in the meanwhile - if (compare == 0) { //same version import the handover + // We always query on the same version. + // Only two use cases could happen 1. We're on the same version or 2. The caller has advanced in the meanwhile. + if (compare == 0) { // Same version import the handover. long rowPointer = result.updatedRow.get(realmObjectWeakReference); if (rowPointer != 0 && emptyAsyncRealmObject.containsKey(realmObjectWeakReference)) { - // cleanup a previously empty async RealmObject + // Cleanups a previously empty async RealmObject. emptyAsyncRealmObject.remove(realmObjectWeakReference); realmObjects.put(realmObjectWeakReference, NO_REALM_QUERY); } @@ -626,15 +626,14 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) { proxy.realmGet$proxyState().notifyChangeListeners$realm(); } else if (compare > 0) { - // the caller has advanced we need to - // retry against the current version of the caller if it's still empty - if (RealmObject.isLoaded(proxy)) { // already completed & has a valid pointer no need to re-run + // The caller has advanced we need to + // retry against the current version of the caller if it's still empty. + if (RealmObject.isValid(proxy)) { // Already completed & has a valid pointer no need to re-run. if (RealmObject.isValid(proxy)) { RealmLog.trace("[COMPLETED_ASYNC_REALM_OBJECT %s], realm: %s. " + "RealmObject is already loaded, just notify it", realm, HandlerController.this); proxy.realmGet$proxyState().notifyChangeListeners$realm(); - } } else { RealmLog.trace("[COMPLETED_ASYNC_REALM_OBJECT %s, realm: %s. " + @@ -642,7 +641,7 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) { proxy, HandlerController.this); Object value = realmObjects.get(realmObjectWeakReference); RealmQuery realmQuery; - if (value == null || value == NO_REALM_QUERY) { // this is a retry of an empty RealmObject + if (value == null || value == NO_REALM_QUERY) { // This is a retry of an empty RealmObject. realmQuery = emptyAsyncRealmObject.get(realmObjectWeakReference); } else { @@ -661,11 +660,11 @@ private void completedAsyncRealmObject(QueryUpdateTask.Result result) { Realm.asyncTaskExecutor.submitQueryUpdate(queryUpdateTask); } } else { - // should not happen, since the the background thread position itself against the provided version - // and the caller thread can only go forward (advance_read) + // Should not happen, since the the background thread position itself against the provided version + // and the caller thread can only go forward (advance_read). throw new IllegalStateException("Caller thread behind the Worker thread"); } - } // else: element GC'd in the meanwhile + } // else: Element GC'd in the meanwhile. } } @@ -739,8 +738,8 @@ void addToRealmResults(RealmResults realmResults) { syncRealmResults.add(realmResultsWeakReference); } - // Add to the list of RealmObject to be notified after a commit. - // This method will check if the object exists in the list. It won't add the same object multiple times + // Adds to the list of RealmObject to be notified after a commit. + // This method will check if the object exists in the list. It won't add the same object multiple times. void addToRealmObjects(E realmObject) { for (WeakReference ref : realmObjects.keySet()) { if (ref.get() == realmObject) { diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index af3d807c7a..188753d0c2 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -67,12 +67,12 @@ public ProxyState(Class clazzName, E model) { public void setPendingQuery$realm(Future pendingQuery) { this.pendingQuery = pendingQuery; if (isLoaded()) { - // the query completed before RealmQuery + // The query completed before RealmQuery // had a chance to call setPendingQuery to register the pendingQuery (used btw - // to determine isLoaded behaviour) + // to determine isLoaded behaviour). onCompleted$realm(); - } // else, it will be handled by the Realm#handler + } // Else, it will be handled by the Realm#handler. } public BaseRealm getRealm$realm() { @@ -124,10 +124,10 @@ public ProxyState(Class clazzName, E model) { try { Long handoverResult = pendingQuery.get();// make the query blocking if (handoverResult != 0) { - // this may fail with BadVersionException if the caller and/or the worker thread + // This may fail with BadVersionException if the caller and/or the worker thread // are not in sync (same shared_group version). - // COMPLETED_ASYNC_REALM_OBJECT will be fired by the worker thread - // this should handle more complex use cases like retry, ignore etc + // COMPLETED_ASYNC_REALM_OBJECT will be fired by the worker thread. + // This should handle more complex use cases like retry, ignore etc. onCompleted$realm(handoverResult); notifyChangeListeners$realm(); } else { @@ -146,8 +146,8 @@ public ProxyState(Class clazzName, E model) { public void onCompleted$realm(long handoverRowPointer) { if (handoverRowPointer == 0) { - // we'll retry later to update the row pointer, but we consider - // the query done + // We'll retry later to update the row pointer, but we consider + // the query done. isCompleted = true; } else if (!isCompleted || row == Row.EMPTY_ROW) { @@ -155,7 +155,7 @@ public ProxyState(Class clazzName, E model) { long nativeRowPointer = TableQuery.importHandoverRow(handoverRowPointer, realm.sharedRealm); Table table = getTable(); this.row = table.getUncheckedRowByPointer(nativeRowPointer); - }// else: already loaded query no need to import again the pointer + } // else: Already loaded query no need to import again the pointer. } /** @@ -204,7 +204,7 @@ public boolean isUnderConstruction() { public void setConstructionFinished() { underConstruction = false; - // only used while construction. + // Only used while construction. excludeFields = null; } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 56b6f52235..2f483be270 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -270,7 +270,7 @@ static Realm createInstance(RealmConfiguration configuration, ColumnIndices[] gl try { migrateRealm(configuration, e); } catch (FileNotFoundException fileNotFoundException) { - // Should never happen + // Should never happen. throw new RealmFileException(RealmFileException.Kind.NOT_FOUND, fileNotFoundException); } } @@ -288,7 +288,7 @@ static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices[] final ColumnIndices columnIndices = RealmCache.findColumnIndices(globalCacheArray, requiredVersion); if (columnIndices != null) { - // copy global cache as a Realm local indices cache + // Copies global cache as a Realm local indices cache. realm.schema.columnIndices = columnIndices.clone(); } else { final boolean syncingConfig = configuration.isSyncConfiguration(); @@ -307,7 +307,7 @@ static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices[] } } - // Initialize Realm schema if needed + // Initializes Realm schema if needed. try { if (!syncingConfig) { initializeRealm(realm); @@ -341,7 +341,7 @@ private static void initializeRealm(Realm realm) { final Map, ColumnInfo> columnInfoMap = new HashMap<>(modelClasses.size()); for (Class modelClass : modelClasses) { - // Create and validate table + // Creates and validates table. if (unversioned) { mediator.createTable(modelClass, realm.sharedRealm); } @@ -388,7 +388,7 @@ private static void initializeSyncedRealm(Realm realm) { realmObjectSchemas.add(realmObjectSchema); } - // Assumption: when SyncConfiguration then additive schema update mode + // Assumption: When SyncConfiguration then additive schema update mode. final RealmSchema schema = new RealmSchema(realmObjectSchemas); long newVersion = realm.configuration.getSchemaVersion(); if (realm.sharedRealm.requiresMigration(schema)) { @@ -602,7 +602,7 @@ public void createOrUpdateAllFromJson(Class clazz, Inp checkHasPrimaryKey(clazz); // As we need the primary key value we have to first parse the entire input stream as in the general - // case that value might be the last property :( + // case that value might be the last property. :( Scanner scanner = null; try { scanner = getFullStringScanner(in); @@ -759,7 +759,7 @@ public E createObjectFromJson(Class clazz, InputStream Table table = schema.getTable(clazz); if (table.hasPrimaryKey()) { // As we need the primary key value we have to first parse the entire input stream as in the general - // case that value might be the last property :( + // case that value might be the last property. :( Scanner scanner = null; try { scanner = getFullStringScanner(inputStream); @@ -811,7 +811,7 @@ public E createOrUpdateObjectFromJson(Class clazz, Inp checkHasPrimaryKey(clazz); // As we need the primary key value we have to first parse the entire input stream as in the general - // case that value might be the last property :( + // case that value might be the last property. :( Scanner scanner = null; try { scanner = getFullStringScanner(in); @@ -856,13 +856,13 @@ public E createObject(Class clazz) { * @return the new object. * @throws RealmException if the primary key is defined in the model class or an object cannot be created. */ - // called from proxy classes + // Called from proxy classes. E createObjectInternal( Class clazz, boolean acceptDefaultValue, List excludeFields) { Table table = schema.getTable(clazz); - // Check and throw the exception earlier for a better exception message. + // Checks and throws the exception earlier for a better exception message. if (table.hasPrimaryKey()) { throw new RealmException(String.format("'%s' has a primary key, use" + " 'createObject(Class, Object)' instead.", Table.tableNameToClassName(table.getName()))); @@ -884,7 +884,7 @@ E createObjectInternal( * @throws RealmException if object could not be created due to the primary key being invalid. * @throws IllegalStateException if the model class does not have an primary key defined. * @throws IllegalArgumentException if the {@code primaryKeyValue} doesn't have a value that can be converted to the - * expected value. + * expected value. */ public E createObject(Class clazz, Object primaryKeyValue) { checkIfValid(); @@ -902,8 +902,9 @@ public E createObject(Class clazz, Object primaryKeyVa * @throws RealmException if object could not be created due to the primary key being invalid. * @throws IllegalStateException if the model class does not have an primary key defined. * @throws IllegalArgumentException if the {@code primaryKeyValue} doesn't have a value that can be converted to the + * expected value. */ - // called from proxy classes + // Called from proxy classes. E createObjectInternal( Class clazz, Object primaryKeyValue, @@ -943,7 +944,7 @@ public E copyToRealm(E object) { * @param object {@link io.realm.RealmObject} to copy or update. * @return the new or updated RealmObject with all its properties backed by the Realm. * @throws java.lang.IllegalArgumentException if the object is {@code null} or doesn't have a Primary key defined - * or it belongs to a Realm instance in a different thread. + * or it belongs to a Realm instance in a different thread. * @see #copyToRealm(RealmModel) */ public E copyToRealmOrUpdate(E object) { @@ -980,7 +981,7 @@ public List copyToRealm(Iterable objects) { } /** - * Insert a list of an unmanaged RealmObjects. This is generally faster than {@link #copyToRealm(Iterable)} since it + * Inserts a list of an unmanaged RealmObjects. This is generally faster than {@link #copyToRealm(Iterable)} since it * doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original objects will not be persisted. *

          @@ -1014,7 +1015,7 @@ public void insert(Collection objects) { } /** - * Insert an unmanaged RealmObject. This is generally faster than {@link #copyToRealm(RealmModel)} since it + * Inserts an unmanaged RealmObject. This is generally faster than {@link #copyToRealm(RealmModel)} since it * doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original object will not be persisted. *

          @@ -1035,7 +1036,7 @@ public void insert(Collection objects) { * @throws IllegalStateException if the corresponding Realm is closed, called from an incorrect thread or not in a * transaction. * @throws io.realm.exceptions.RealmPrimaryKeyConstraintException if two objects with the same primary key is - * inserted or if a primary key value already exists in the Realm. + * inserted or if a primary key value already exists in the Realm. * @see #copyToRealm(RealmModel) */ public void insert(RealmModel object) { @@ -1048,8 +1049,9 @@ public void insert(RealmModel object) { } /** - * Insert or update a list of unmanaged RealmObjects. This is generally faster than {@link #copyToRealmOrUpdate(Iterable)} since it - * doesn't return the inserted elements, and performs minimum allocations and checks. + * Inserts or updates a list of unmanaged RealmObjects. This is generally faster than + * {@link #copyToRealmOrUpdate(Iterable)} since it doesn't return the inserted elements, and performs minimum + * allocations and checks. * After being inserted any changes to the original objects will not be persisted. *

          * Please note: @@ -1084,8 +1086,9 @@ public void insertOrUpdate(Collection objects) { } /** - * Insert or update an unmanaged RealmObject. This is generally faster than {@link #copyToRealmOrUpdate(RealmModel)} since it - * doesn't return the inserted elements, and performs minimum allocations and checks. + * Inserts or updates an unmanaged RealmObject. This is generally faster than + * {@link #copyToRealmOrUpdate(RealmModel)} since it doesn't return the inserted elements, and performs minimum + * allocations and checks. * After being inserted any changes to the original object will not be persisted. *

          * Please note: @@ -1117,8 +1120,8 @@ public void insertOrUpdate(RealmModel object) { /** * Updates a list of existing RealmObjects that is identified by their {@link io.realm.annotations.PrimaryKey} or - * creates a new copy if no existing object could be found. This is a deep copy or update i.e., all referenced objects - * will be either copied or updated. + * creates a new copy if no existing object could be found. This is a deep copy or update i.e., all referenced + * objects will be either copied or updated. *

          * Please note, copying an object will copy all field values. Any unset field in the objects and child objects will be * set to their default value if not provided. @@ -1150,9 +1153,9 @@ public List copyToRealmOrUpdate(Iterable objects) { * The copied objects are all detached from Realm and they will no longer be automatically updated. This means * that the copied objects might contain data that are no longer consistent with other managed Realm objects. *

          - * *WARNING*: Any changes to copied objects can be merged back into Realm using {@link #copyToRealmOrUpdate(RealmModel)}, - * but all fields will be overridden, not just those that were changed. This includes references to other objects, - * and can potentially override changes made by other threads. + * *WARNING*: Any changes to copied objects can be merged back into Realm using + * {@link #copyToRealmOrUpdate(RealmModel)}, but all fields will be overridden, not just those that were changed. + * This includes references to other objects, and can potentially override changes made by other threads. * * @param realmObjects RealmObjects to copy. * @param type of object. @@ -1171,10 +1174,10 @@ public List copyFromRealm(Iterable realmObjects) { * The copied objects are all detached from Realm and they will no longer be automatically updated. This means * that the copied objects might contain data that are no longer consistent with other managed Realm objects. *

          - * *WARNING*: Any changes to copied objects can be merged back into Realm using {@link #copyToRealmOrUpdate(Iterable)}, - * but all fields will be overridden, not just those that were changed. This includes references to other objects - * even though they might be {@code null} due to {@code maxDepth} being reached. This can also potentially override - * changes made by other threads. + * *WARNING*: Any changes to copied objects can be merged back into Realm using + * {@link #copyToRealmOrUpdate(Iterable)}, but all fields will be overridden, not just those that were changed. + * This includes references to other objects even though they might be {@code null} due to {@code maxDepth} being + * reached. This can also potentially override changes made by other threads. * * @param realmObjects RealmObjects to copy. * @param maxDepth limit of the deep copy. All references after this depth will be {@code null}. Starting depth is @@ -1182,7 +1185,7 @@ public List copyFromRealm(Iterable realmObjects) { * @param type of object. * @return an in-memory detached copy of the RealmObjects. * @throws IllegalArgumentException if {@code maxDepth < 0}, the RealmObject is no longer accessible or it is a - * {@link DynamicRealmObject}. + * {@link DynamicRealmObject}. * @see #copyToRealmOrUpdate(Iterable) */ public List copyFromRealm(Iterable realmObjects, int maxDepth) { @@ -1240,7 +1243,7 @@ public E copyFromRealm(E realmObject) { * @param type of object. * @return an in-memory detached copy of the managed {@link RealmObject}. * @throws IllegalArgumentException if {@code maxDepth < 0}, the RealmObject is no longer accessible or it is a - * {@link DynamicRealmObject}. + * {@link DynamicRealmObject}. * @see #copyToRealmOrUpdate(RealmModel) */ public E copyFromRealm(E realmObject, int maxDepth) { @@ -1419,7 +1422,7 @@ public void run() { } final Throwable backgroundException = exception[0]; - // Send response as the final step to ensure the bg thread quit before others get the response! + // Sends response as the final step to ensure the bg thread quit before others get the response! if (hasValidNotifier() && !Thread.currentThread().isInterrupted()) { if (transactionCommitted) { @@ -1437,7 +1440,7 @@ public void run() { }); } - // Send errors directly to the looper, so they don't get intercepted by the HandlerController. + // Sends errors directly to the looper, so they don't get intercepted by the HandlerController. if (backgroundException != null) { if (onError != null) { sharedRealm.realmNotifier.post(new Runnable() { @@ -1463,7 +1466,7 @@ public void run() { } } else { - // Throw exception in the worker thread if the caller thread terminated + // Throws exception in the worker thread if the caller thread terminated. if (backgroundException != null) { if (backgroundException instanceof RuntimeException) { //noinspection ThrowFromFinallyBlock @@ -1623,8 +1626,8 @@ Table getTable(Class clazz) { * * @param globalCacheArray global cache of column indices. If it contains an entry for current * schema version, this method only copies the indices information in the entry. - * @return newly created indices information for current schema version. Or {@code null} if - * {@code globalCacheArray} already contains the entry for current schema version. + * @return newly created indices information for current schema version. Or {@code null} if {@code globalCacheArray} + * already contains the entry for current schema version. */ ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) { final long currentSchemaVersion = sharedRealm.getSchemaVersion(); @@ -1638,7 +1641,7 @@ ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) { ColumnIndices cacheForCurrentVersion = RealmCache.findColumnIndices(globalCacheArray, currentSchemaVersion); if (cacheForCurrentVersion == null) { - // not found in global cache. create it. + // Not found in global cache. create it. final Set> modelClasses = mediator.getModelClasses(); final Map, ColumnInfo> map; map = new HashMap, ColumnInfo>(modelClasses.size()); diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 980218b28f..0fa7fbf371 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -107,14 +107,14 @@ static synchronized E createRealmOrGetFromCache(RealmConfi boolean isCacheInMap = true; RealmCache cache = cachesMap.get(configuration.getPath()); if (cache == null) { - // Create a new cache + // Creates a new cache. cache = new RealmCache(configuration); // The new cache should be added to the map later. isCacheInMap = false; copyAssetFileIfNeeded(configuration); } else { - // Throw the exception if validation failed. + // Throws the exception if validation failed. cache.validateConfiguration(configuration); } @@ -134,7 +134,7 @@ static synchronized E createRealmOrGetFromCache(RealmConfi } if (refAndCount.localRealm.get() == null) { - // Create a new local Realm instance + // Creates a new local Realm instance BaseRealm realm; @@ -161,7 +161,7 @@ static synchronized E createRealmOrGetFromCache(RealmConfi if (refCount == 0) { if (realmClass == Realm.class && refAndCount.globalCount == 0) { final BaseRealm realm = refAndCount.localRealm.get(); - // store a copy of local ColumnIndices as a global cache. + // Stores a copy of local ColumnIndices as a global cache. RealmCache.storeColumnIndices(cache.typedColumnIndicesArray, realm.schema.columnIndices.clone()); } // This is the first instance in current thread, increase the global count. @@ -172,7 +172,7 @@ static synchronized E createRealmOrGetFromCache(RealmConfi @SuppressWarnings("unchecked") E realm = (E) refAndCount.localRealm.get(); - // Notify SyncPolicy that the Realm has been opened for the first time + // Notifies SyncPolicy that the Realm has been opened for the first time if (refAndCount.globalCount == 1) { ObjectServerFacade.getFacade(configuration.isSyncConfiguration()).realmOpened(configuration); } @@ -204,16 +204,16 @@ static synchronized void release(BaseRealm realm) { return; } - // Decrease the local counter. + // Decreases the local counter. refCount -= 1; if (refCount == 0) { // The last instance in this thread. - // Clear local ref & counter + // Clears local ref & counter. refAndCount.localCount.set(null); refAndCount.localRealm.set(null); - // Clear global counter + // Clears global counter. refAndCount.globalCount--; if (refAndCount.globalCount < 0) { // Should never happen. @@ -221,9 +221,9 @@ static synchronized void release(BaseRealm realm) { " got corrupted."); } - // Clear the column indices cache if needed + // Clears the column indices cache if needed. if (realm instanceof Realm && refAndCount.globalCount == 0) { - // All typed Realm instances of this file are cleared from cache + // All typed Realm instances of this file are cleared from cache. Arrays.fill(cache.typedColumnIndicesArray, null); } @@ -255,16 +255,16 @@ static synchronized void release(BaseRealm realm) { */ private void validateConfiguration(RealmConfiguration newConfiguration) { if (configuration.equals(newConfiguration)) { - // Same configuration objects + // Same configuration objects. return; } - // Check that encryption keys aren't different. key is not in RealmConfiguration's toString. + // Checks that encryption keys aren't different. key is not in RealmConfiguration's toString. if (!Arrays.equals(configuration.getEncryptionKey(), newConfiguration.getEncryptionKey())) { throw new IllegalArgumentException(DIFFERENT_KEY_MESSAGE); } else { // A common problem is that people are forgetting to override `equals` in their custom migration class. - // Try to detect this problem specifically so we can throw a better error message. + // Tries to detect this problem specifically so we can throw a better error message. RealmMigration newMigration = newConfiguration.getMigration(); RealmMigration oldMigration = configuration.getMigration(); if (oldMigration != null @@ -380,7 +380,7 @@ private static void copyAssetFileIfNeeded(RealmConfiguration configuration) { try { outputStream.close(); } catch (IOException e) { - // Ignore this one if there was an exception when close inputStream. + // Ignores this one if there was an exception when close inputStream. if (exceptionWhenClose == null) { exceptionWhenClose = e; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 2eab03463b..0a006b559b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -260,21 +260,21 @@ public int hashCode() { return result; } - // Creates the mediator that defines the current schema + // Creates the mediator that defines the current schema. protected static RealmProxyMediator createSchemaMediator(Set modules, Set> debugSchema) { - // If using debug schema, use special mediator + // If using debug schema, uses special mediator. if (debugSchema.size() > 0) { return new FilterableMediator(DEFAULT_MODULE_MEDIATOR, debugSchema); } - // If only one module, use that mediator directly + // If only one module, uses that mediator directly. if (modules.size() == 1) { return getModuleMediator(modules.iterator().next().getClass().getCanonicalName()); } - // Otherwise combine all mediators + // Otherwise combines all mediators. RealmProxyMediator[] mediators = new RealmProxyMediator[modules.size()]; int i = 0; for (Object module : modules) { @@ -284,7 +284,7 @@ protected static RealmProxyMediator createSchemaMediator(Set modules, return new CompositeMediator(mediators); } - // Finds the mediator associated with a given module + // Finds the mediator associated with a given module. private static RealmProxyMediator getModuleMediator(String fullyQualifiedModuleClassName) { String[] moduleNameParts = fullyQualifiedModuleClassName.split("\\."); String moduleSimpleName = moduleNameParts[moduleNameParts.length - 1]; @@ -349,7 +349,7 @@ static synchronized boolean isRxJavaAvailable() { return rxJavaAvailable; } - // Get the canonical path for a given file + // Gets the canonical path for a given file. protected static String getCanonicalPath(File realmFile) { try { return realmFile.getCanonicalPath(); @@ -360,7 +360,7 @@ protected static String getCanonicalPath(File realmFile) { } } - // Check if this configuration is a SyncConfiguration instance. + // Checks if this configuration is a SyncConfiguration instance. boolean isSyncConfiguration() { return false; } @@ -369,7 +369,7 @@ boolean isSyncConfiguration() { * RealmConfiguration.Builder used to construct instances of a RealmConfiguration in a fluent manner. */ public static class Builder { - // IMPORTANT: When adding any new methods to this class also add them to SyncConfiguration. + // IMPORTANT: When adding any new methods to this class also add them to SyncConfiguration. private File directory; private String fileName; private String assetFilePath; @@ -402,7 +402,7 @@ public Builder() { initializeBuilder(context); } - // Setup builder in its initial state + // Setups builder in its initial state. private void initializeBuilder(Context context) { this.directory = context.getFilesDir(); this.fileName = Realm.DEFAULT_REALM_NAME; @@ -429,7 +429,7 @@ public Builder name(String filename) { } /** - * Specify the directory where the Realm file will be saved. The default value is {@code context.getFiles()}. + * Specifies the directory where the Realm file will be saved. The default value is {@code context.getFiles()}. * If the directory does not exist, it will be created. * * @param directory the directory to save the Realm file in. Directory must be writable. diff --git a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java index df514821ef..7adb5b9ae4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java +++ b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java @@ -29,7 +29,7 @@ */ @Keep public enum RealmFieldType { - // Make sure numbers match with + // Makes sure numbers match with . INTEGER(0), BOOLEAN(1), STRING(2), @@ -42,7 +42,7 @@ public enum RealmFieldType { DOUBLE(10), OBJECT(12), LIST(13); - // BACKLINK(14); Not exposed until needed + // BACKLINK(14); Not exposed until needed. // Primitive array for fast mapping between between native values and their Realm type. private static RealmFieldType[] typeList = new RealmFieldType[15]; @@ -71,6 +71,7 @@ public int getNativeValue() { /** * Checks if the given Java object can be converted to the underlying Realm type. + * * @param obj object to test compatibility on. * @return {@code true} if object can be converted to the Realm type, {@code false} otherwise. */ @@ -81,7 +82,7 @@ public boolean isValid(Object obj) { case 2: return (obj instanceof String); case 4: return (obj instanceof byte[] || obj instanceof ByteBuffer); case 5: return (obj == null || obj instanceof Object[][]); - case 7: return (obj instanceof java.util.Date); // the unused DateTime + case 7: return (obj instanceof java.util.Date); // The unused DateTime. case 8: return (obj instanceof java.util.Date); case 9: return (obj instanceof Float); case 10: return (obj instanceof Double); diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 80d7947417..d909155433 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -214,6 +214,7 @@ public boolean add(E object) { * In that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} or * {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key. * + * * @param location the index at which to put the specified object. * @param object the object to add. * @return the previous element at the index. @@ -826,14 +827,14 @@ private class RealmItr implements Iterator { /** * Index of element returned by most recent call to next or - * previous. Reset to -1 if this element is deleted by a call + * previous. Resets to -1 if this element is deleted by a call * to remove. */ int lastRet = -1; /** * The modCount value that the iterator believes that the backing - * List should have. If this expectation is violated, the iterator + * List should have. If this expectation is violated, the iterator * has detected concurrent modification. */ int expectedModCount = modCount; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index ed03ec6e4b..f9ff0a7490 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -153,15 +153,16 @@ public static boolean isValid(E object) { /** * Checks if the query used to find this RealmObject has completed. * - * Async methods like {@link RealmQuery#findFirstAsync()} return an {@link RealmObject} that represents the future result - * of the {@link RealmQuery}. It can be considered similar to a {@link java.util.concurrent.Future} in this regard. + * Async methods like {@link RealmQuery#findFirstAsync()} return an {@link RealmObject} that represents the future + * result of the {@link RealmQuery}. It can be considered similar to a {@link java.util.concurrent.Future} in this + * regard. * * Once {@code isLoaded()} returns {@code true}, the object represents the query result even if the query * didn't find any object matching the query parameters. In this case the {@link RealmObject} will * become a "null" object. * - * "Null" objects represents {@code null}. An exception is throw if any accessor is called, so it is important to also - * check {@link #isValid()} before calling any methods. A common pattern is: + * "Null" objects represents {@code null}. An exception is throw if any accessor is called, so it is important to + * also check {@link #isValid()} before calling any methods. A common pattern is: * *
                * {@code
          @@ -224,7 +225,6 @@ public final boolean isLoaded() {
                * Synchronous RealmObjects are by definition blocking hence this method will always return {@code true} for them.
                * This method will return {@code true} if called on an unmanaged object (created outside of Realm).
                *
          -     *
                * @param object RealmObject to check.
                * @return {@code true} if the query has completed, {@code false} if the query is in
                * progress.
          @@ -360,7 +360,7 @@ public static  void addChangeListener(E object, RealmChang
                           listeners.add(listener);
                       }
                       if (isLoaded(proxy)) {
          -                // Try to add this object to the realmObjects if it has already been loaded.
          +                // Tries to add this object to the realmObjects if it has already been loaded.
                           // For newly created async objects, it will be handled in RealmQuery.findFirstAsync &
                           // HandlerController.completedAsyncRealmObject.
                           realm.handlerController.addToRealmObjects(proxy);
          diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java
          index e4d8115237..3f48e27087 100644
          --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java
          +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java
          @@ -143,11 +143,12 @@ public String getClassName() {
               }
           
               /**
          -     * Sets a new name for this RealmObject class. This is equivalent to renaming it. When {@link RealmObjectSchema#table}
          -     * has a primary key, this will transfer the primary key for the new class name.
          +     * Sets a new name for this RealmObject class. This is equivalent to renaming it. When
          +     * {@link RealmObjectSchema#table} has a primary key, this will transfer the primary key for the new class name.
                *
                * @param className the new name for this class.
          -     * @throws IllegalArgumentException if className is {@code null} or an empty string, or its length exceeds 56 characters.
          +     * @throws IllegalArgumentException if className is {@code null} or an empty string, or its length exceeds 56
          +     * characters.
                * @see RealmSchema#rename(String, String)
                */
               public RealmObjectSchema setClassName(String className) {
          @@ -182,12 +183,13 @@ public RealmObjectSchema setClassName(String className) {
               }
           
               /**
          -     * Adds a new simple field to the RealmObject class. The type must be one supported by Realm. See {@link RealmObject}
          -     * for the list of supported types. If the field should allow {@code null} values use the boxed type instead e.g.,
          -     * {@code Integer.class} instead of {@code int.class}.
          +     * Adds a new simple field to the RealmObject class. The type must be one supported by Realm. See
          +     * {@link RealmObject} for the list of supported types. If the field should allow {@code null} values use the boxed
          +     * type instead e.g., {@code Integer.class} instead of {@code int.class}.
                * 

          - * To add fields that reference other RealmObjects or RealmLists use {@link #addRealmObjectField(String, RealmObjectSchema)} - * or {@link #addRealmListField(String, RealmObjectSchema)} instead. + * To add fields that reference other RealmObjects or RealmLists use + * {@link #addRealmObjectField(String, RealmObjectSchema)} or {@link #addRealmListField(String, RealmObjectSchema)} + * instead. * * @param fieldName name of the field to add. * @param fieldType type of field to add. See {@link RealmObject} for the full list. @@ -307,7 +309,7 @@ public RealmObjectSchema removeField(String fieldName) { * Renames a field from one name to another. * * @param currentFieldName field name to rename. - * @param newFieldName the new field name. + * @param newFieldName the new field name. * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist or if the new field name already exists. */ @@ -336,8 +338,8 @@ public boolean hasField(String fieldName) { } /** - * Adds an index to a given field. This is the equivalent of adding the {@link io.realm.annotations.Index} annotation - * on the field. + * Adds an index to a given field. This is the equivalent of adding the {@link io.realm.annotations.Index} + * annotation on the field. * * @param fieldName field to add index to. * @return the updated schema. @@ -378,7 +380,7 @@ public boolean hasIndex(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or the field doesn't have an index. */ public RealmObjectSchema removeIndex(String fieldName) { - realm.checkNotInSync(); // destructive modifications are not permitted + realm.checkNotInSync(); // Destructive modifications are not permitted. checkLegalName(fieldName); checkFieldExists(fieldName); long columnIndex = getColumnIndex(fieldName); @@ -391,7 +393,8 @@ public RealmObjectSchema removeIndex(String fieldName) { /** * Adds a primary key to a given field. This is the same as adding the {@link io.realm.annotations.PrimaryKey} - * annotation on the field. Further, this implicitly adds {@link io.realm.annotations.Index} annotation to the field as well. + * annotation on the field. Further, this implicitly adds {@link io.realm.annotations.Index} annotation to the field + * as well. * * @param fieldName field to set as primary key. * @return the updated schema. @@ -415,13 +418,14 @@ public RealmObjectSchema addPrimaryKey(String fieldName) { /** * Removes the primary key from this class. This is the same as removing the {@link io.realm.annotations.PrimaryKey} - * annotation from the class. Further, this implicitly removes {@link io.realm.annotations.Index} annotation from the field as well. + * annotation from the class. Further, this implicitly removes {@link io.realm.annotations.Index} annotation from + * the field as well. * * @return the updated schema. * @throws IllegalArgumentException if the class doesn't have a primary key defined. */ public RealmObjectSchema removePrimaryKey() { - realm.checkNotInSync(); // destructive modifications are not permitted + realm.checkNotInSync(); // Destructive modifications are not permitted. if (!table.hasPrimaryKey()) { throw new IllegalStateException(getClassName() + " doesn't have a primary key."); } @@ -596,7 +600,7 @@ private void addModifiers(String fieldName, FieldAttribute[] attributes) { // REQUIRED is being handled when adding the column using addField through the nullable parameter. } } catch (Exception e) { - // If something went wrong, revert all attributes + // If something went wrong, revert all attributes. long columnIndex = getColumnIndex(fieldName); if (indexAdded) { table.removeSearchIndex(columnIndex); @@ -679,7 +683,7 @@ long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTy Table table = this.table; boolean checkColumnType = validColumnTypes != null && validColumnTypes.length > 0; if (fieldDescription.contains(".")) { - // Resolve field description down to last field name + // Resolves field description down to last field name. String[] names = fieldDescription.split("\\."); long[] columnIndices = new long[names.length]; for (int i = 0; i < names.length - 1; i++) { @@ -696,7 +700,7 @@ long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTy } } - // Check if last field name is a valid field + // Checks if last field name is a valid field. String columnName = names[names.length - 1]; long columnIndex = table.getColumnIndex(columnName); columnIndices[names.length - 1] = columnIndex; @@ -774,7 +778,7 @@ public interface Function { void apply(DynamicRealmObject obj); } - // Tuple containing data about each supported Java type + // Tuple containing data about each supported Java type. private static class FieldMetaData { public final RealmFieldType realmType; public final boolean defaultNullable; diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index f42c9734df..29e6f09d4b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -216,7 +216,7 @@ public boolean isValid() { public RealmQuery isNull(String fieldName) { long columnIndices[] = schema.getColumnIndices(fieldName); - // checking that fieldName has the correct type is done in C++ + // Checks that fieldName has the correct type is done in C++. this.query.isNull(columnIndices); return this; } @@ -232,7 +232,7 @@ public RealmQuery isNull(String fieldName) { public RealmQuery isNotNull(String fieldName) { long columnIndices[] = schema.getColumnIndices(fieldName); - // checking that fieldName has the correct type is done in C++ + // Checks that fieldName has the correct type is done in C++. this.query.isNotNull(columnIndices); return this; } @@ -431,7 +431,8 @@ public RealmQuery equalTo(String fieldName, Date value) { * @param fieldName the field to compare. * @param values array of values to compare with and it cannot be null or empty. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a String field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a String field or {@code values} is {@code null} or + * empty. */ public RealmQuery in(String fieldName, String[] values) { return in(fieldName, values, Case.SENSITIVE); @@ -444,7 +445,8 @@ public RealmQuery in(String fieldName, String[] values) { * @param values array of values to compare with and it cannot be null or empty. * @param casing how casing is handled. {@link Case#INSENSITIVE} works only for the Latin-1 characters. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a String field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a String field or {@code values} is {@code null} or + * empty. */ public RealmQuery in(String fieldName, String[] values, Case casing) { if (values == null || values.length == 0) { @@ -463,7 +465,8 @@ public RealmQuery in(String fieldName, String[] values, Case casing) { * @param fieldName the field to compare. * @param values array of values to compare with and it cannot be null or empty. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Byte field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Byte field or {@code values} is {@code null} or + * empty. */ public RealmQuery in(String fieldName, Byte[] values) { if (values == null || values.length == 0) { @@ -482,7 +485,8 @@ public RealmQuery in(String fieldName, Byte[] values) { * @param fieldName the field to compare. * @param values array of values to compare with and it cannot be null or empty. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Short field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Short field or {@code values} is {@code null} or + * empty. */ public RealmQuery in(String fieldName, Short[] values) { if (values == null || values.length == 0) { @@ -501,7 +505,8 @@ public RealmQuery in(String fieldName, Short[] values) { * @param fieldName the field to compare. * @param values array of values to compare with and it cannot be null or empty. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Integer field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Integer field or {@code values} is {@code null} + * or empty. */ public RealmQuery in(String fieldName, Integer[] values) { if (values == null || values.length == 0) { @@ -520,7 +525,8 @@ public RealmQuery in(String fieldName, Integer[] values) { * @param fieldName the field to compare. * @param values array of values to compare with and it cannot be null or empty. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Long field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Long field or {@code values} is {@code null} or + * empty. */ public RealmQuery in(String fieldName, Long[] values) { if (values == null || values.length == 0) { @@ -539,7 +545,8 @@ public RealmQuery in(String fieldName, Long[] values) { * @param fieldName the field to compare. * @param values array of values to compare with and it cannot be null or empty. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Double field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Double field or {@code values} is {@code null} or + * empty. */ public RealmQuery in(String fieldName, Double[] values) { if (values == null || values.length == 0) { @@ -558,7 +565,8 @@ public RealmQuery in(String fieldName, Double[] values) { * @param fieldName the field to compare. * @param values array of values to compare with and it cannot be null or empty. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Float field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Float field or {@code values} is {@code null} or + * empty. */ public RealmQuery in(String fieldName, Float[] values) { if (values == null || values.length == 0) { @@ -577,7 +585,8 @@ public RealmQuery in(String fieldName, Float[] values) { * @param fieldName the field to compare. * @param values array of values to compare with and it cannot be null or empty. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Boolean field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Boolean field or {@code values} is {@code null} + * or empty. */ public RealmQuery in(String fieldName, Boolean[] values) { if (values == null || values.length == 0) { @@ -596,7 +605,8 @@ public RealmQuery in(String fieldName, Boolean[] values) { * @param fieldName the field to compare. * @param values array of values to compare with and it cannot be null or empty. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Date field or {@code values} is {@code null} or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Date field or {@code values} is {@code null} or + * empty. */ public RealmQuery in(String fieldName, Date[] values) { if (values == null || values.length == 0) { @@ -1401,18 +1411,18 @@ public RealmResults distinctAsync(String fieldName) { final long columnIndex = getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); final WeakReference weakNotifier = getWeakReferenceNotifier(); - // handover the query (to be used by a worker thread) + // Handovers the query (to be used by a worker thread). final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - // save query arguments (for future update) + // Saves query arguments (for future update). argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_DISTINCT); argumentsHolder.columnIndex = columnIndex; - // we need to use the same configuration to open a background SharedRealm (i.e Realm) - // to perform the query + // We need to use the same configuration to open a background SharedRealm (i.e Realm) + // to perform the query. final RealmConfiguration realmConfiguration = realm.getConfiguration(); - // prepare an empty reference of the RealmResults, so we can return it immediately (promise) + // Prepares an empty reference of the RealmResults, so we can return it immediately (promise) // then update it once the query completes in the background. RealmResults realmResults; if (isDynamicQuery()) { @@ -1466,22 +1476,22 @@ public Long call() throws Exception { return realmResults; } - // Find and validate the column index for the field name used to create a distinctive TableView. + // Finds and validates the column index for the field name used to create a distinctive TableView. static long getAndValidateDistinctColumnIndex(String fieldName, Table table) { - // Check empty field name + // Checks empty field name. if (fieldName == null || fieldName.isEmpty()) { throw new IllegalArgumentException("Non-empty field name must be provided."); } long columnIndex = table.getColumnIndex(fieldName); - // Check if field exists + // Checks if field exists. if (columnIndex == -1) { throw new IllegalArgumentException(String.format("Field name '%s' does not exist.", fieldName)); } - // Check linked fields + // Checks linked fields. if (fieldName.contains(".")) { throw new IllegalArgumentException("Distinct operation on linked properties is not supported: " + fieldName); } - // check if the field is indexed + // Checks if the field is indexed. if (!table.hasSearchIndex(columnIndex)) { throw new IllegalArgumentException(String.format("Field name '%s' must be indexed in order to use it for distinct queries.", fieldName)); } @@ -1516,13 +1526,13 @@ public RealmResults distinct(String firstFieldName, String... remainingFieldN return realmResults; } - // find and validate the column indices of fields for building a distinctive TableView with multi-args + // Finds and validates the column indices of fields for building a distinctive TableView with multi-args. static List getValidatedColumIndexes(Table table, String firstFieldName, String... remainingFieldNames) { List columnIndexes = new ArrayList(); - // find the first index + // Finds the first index. long firstIndex = getAndValidateDistinctColumnIndex(firstFieldName, table); columnIndexes.add(firstIndex); - // add remaining of indexes + // Adds remaining of indexes. if (remainingFieldNames != null && 0 < remainingFieldNames.length) { for (String field : remainingFieldNames) { long index = getAndValidateDistinctColumnIndex(field, table); @@ -1705,17 +1715,17 @@ public RealmResults findAllAsync() { checkQueryIsNotReused(); final WeakReference weakNotifier = getWeakReferenceNotifier(); - // handover the query (to be used by a worker thread) + // Handovers the query (to be used by a worker thread). final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - // save query arguments (for future update) + // Saves query arguments (for future update). argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_ALL); - // we need to use the same configuration to open a background SharedRealm (i.e Realm) - // to perform the query + // We need to use the same configuration to open a background SharedRealm (i.e Realm) + // to perform the query. final RealmConfiguration realmConfiguration = realm.getConfiguration(); - // prepare an empty reference of the RealmResults, so we can return it immediately (promise) + // Prepares an empty reference of the RealmResults, so we can return it immediately (promise) // then update it once the query completes in the background. RealmResults realmResults; if (isDynamicQuery()) { @@ -1736,7 +1746,7 @@ public Long call() throws Exception { try { sharedRealm = SharedRealm.getInstance(realmConfiguration); - // Run the query & handover the table view for the caller thread + // Runs the query & handover the table view for the caller thread. // Note: the handoverQueryPointer contains the versionID needed by the SG in order // to import it. long handoverTableViewPointer = TableQuery.findAllWithHandover(sharedRealm, @@ -1818,17 +1828,17 @@ public RealmResults findAllSortedAsync(final String fieldName, final Sort sor checkQueryIsNotReused(); long columnIndex = getColumnIndexForSort(fieldName); - // capture the query arguments for future retries & update + // Captures the query arguments for future retries & updates. argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_ALL_SORTED); argumentsHolder.sortOrder = sortOrder; argumentsHolder.columnIndex = columnIndex; final WeakReference weakNotifier = getWeakReferenceNotifier(); - // handover the query (to be used by a worker thread) + // Handovers the query (to be used by a worker thread). final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - // we need to use the same configuration to open a background SharedRealm to perform the query + // We need to use the same configuration to open a background SharedRealm to perform the query. final RealmConfiguration realmConfiguration = realm.getConfiguration(); RealmResults realmResults; @@ -1853,7 +1863,7 @@ public Long call() throws Exception { long columnIndex = getColumnIndexForSort(fieldName); - // run the query & handover the table view for the caller thread + // Runs the query & handover the table view for the caller thread. long handoverTableViewPointer = TableQuery.findAllSortedWithHandover(sharedRealm, handoverQueryPointer, columnIndex, sortOrder); @@ -1985,10 +1995,10 @@ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sort } else { final WeakReference weakNotifier = getWeakReferenceNotifier(); - // Handover the query (to be used by a worker thread) + // Handovers the query (to be used by a worker thread). final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - // We need to use the same configuration to open a background SharedRealm to perform the query + // We need to use the same configuration to open a background SharedRealm to perform the query. final RealmConfiguration realmConfiguration = realm.getConfiguration(); final long indices[] = new long[fieldNames.length]; @@ -1998,12 +2008,12 @@ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sort indices[i] = columnIndex; } - // capture the query arguments for future retries & update + // Captures the query arguments for future retries & update. argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_ALL_MULTI_SORTED); argumentsHolder.sortOrders = sortOrders; argumentsHolder.columnIndices = indices; - // prepare the promise result + // Prepares the promise result. RealmResults realmResults; if (isDynamicQuery()) { //noinspection unchecked @@ -2023,7 +2033,7 @@ public Long call() throws Exception { try { sharedRealm = SharedRealm.getInstance(realmConfiguration); - // run the query & handover the table view for the caller thread + // Runs the query & handover the table view for the caller thread. long handoverTableViewPointer = TableQuery.findAllMultiSortedWithHandover(sharedRealm, handoverQueryPointer, indices, sortOrders); @@ -2127,15 +2137,15 @@ public E findFirstAsync() { checkQueryIsNotReused(); final WeakReference weakNotifier = getWeakReferenceNotifier(); - // handover the query (to be used by a worker thread) + // Handovers the query (to be used by a worker thread). final long handoverQueryPointer = query.handoverQuery(realm.sharedRealm); - // save query arguments (for future update) + // Saves query arguments (for future update). argumentsHolder = new ArgumentsHolder(ArgumentsHolder.TYPE_FIND_FIRST); final RealmConfiguration realmConfiguration = realm.getConfiguration(); - // prepare an empty reference of the RealmObject, so we can return it immediately (promise) + // Prepares an empty reference of the RealmObject, so we can return it immediately (promise) // then update it once the query complete in the background. final E result; if (isDynamicQuery()) { @@ -2160,7 +2170,7 @@ public Long call() throws Exception { sharedRealm = SharedRealm.getInstance(realmConfiguration); long handoverRowPointer = TableQuery.findWithHandover(sharedRealm, handoverQueryPointer); - if (handoverRowPointer == 0) { // empty row + if (handoverRowPointer == 0) { // Empty row. realm.handlerController.addToEmptyAsyncRealmObject(realmObjectWeakReference, RealmQuery.this); realm.handlerController.removeFromAsyncRealmObject(realmObjectWeakReference); } @@ -2175,7 +2185,7 @@ public Long call() throws Exception { } catch (Throwable e) { RealmLog.error(e); - // handler can't throw a checked exception need to wrap it into unchecked Exception + // Handler can't throw a checked exception need to wrap it into unchecked Exception. closeSharedRealmAndSendEventToNotifier(sharedRealm, weakNotifier, QueryUpdateTask.NotifyEvent.THROW_BACKGROUND_EXCEPTION, e); } finally { @@ -2214,7 +2224,7 @@ private WeakReference getWeakReferenceNotifier() { throw new IllegalStateException("Your Realm is opened from a thread without a Looper." + " Async queries need a Handler to send results of your query"); } - return new WeakReference(realm.sharedRealm.realmNotifier); // use caller Realm's Looper + return new WeakReference(realm.sharedRealm.realmNotifier); // Uses caller Realm's Looper. } // The shared group needs to be closed before sending the message to other threads to avoid timing problems. @@ -2245,8 +2255,8 @@ private void closeSharedRealmAndSendEventToNotifier(SharedRealm sharedRealm, } } - // We need to prevent the user from using the query again (mostly for async) - // Ex: if the first query fail with findFirstAsync, if the user reuse the same RealmQuery + // We need to prevent the user from using the query again (mostly for async). + // Ex: If the first query fail with findFirstAsync, if the user reuse the same RealmQuery // with findAllSorted, argumentsHolder of the first query will be overridden, // which cause any retry to use the findAllSorted argumentsHolder. private void checkQueryIsNotReused() { @@ -2259,7 +2269,7 @@ private long getSourceRowIndexForFirstObject() { long tableRowIndex = this.query.find(); return tableRowIndex; } - // Get the column index for sorting related functions. A proper exception will be thrown if the field doesn't exist + // Gets the column index for sorting related functions. A proper exception will be thrown if the field doesn't exist // or it belongs to the child object. private long getColumnIndexForSort(String fieldName) { if (fieldName == null || fieldName.isEmpty()) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index d1e56885a9..069a5c4044 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -85,8 +85,8 @@ public class RealmResults extends AbstractList implemen private final List>> listeners = new CopyOnWriteArrayList>>(); private Future pendingQuery; private boolean asyncQueryCompleted = false; - // Keep track of changes to the RealmResult. Is updated after a call to `syncIfNeeded()`. Calling notifyListeners will - // clear it. + // Keeps track of changes to the RealmResult. Is updated after a call to `syncIfNeeded()`. Calling notifyListeners + // will clear it. private boolean viewUpdated = false; diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 7713b75854..a5d05387f4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -102,7 +102,6 @@ public void close() { * * @param className name of the class * @return schema object for that class or {@code null} if the class doesn't exists. - * */ public RealmObjectSchema get(String className) { checkEmpty(className, EMPTY_STRING_MSG); @@ -160,7 +159,7 @@ public Set getAll() { * @return a Realm schema object for that class. */ public RealmObjectSchema create(String className) { - // adding a class is always permitted + // Adding a class is always permitted. checkEmpty(className, EMPTY_STRING_MSG); if (realm == null) { RealmObjectSchema realmObjectSchema = new RealmObjectSchema(className); @@ -182,12 +181,12 @@ public RealmObjectSchema create(String className) { /** * Removes a class from the Realm. All data will be removed. Removing a class while other classes point - * to it will throw an {@link IllegalStateException}. Remove those classes or fields first. + * to it will throw an {@link IllegalStateException}. Removes those classes or fields first. * * @param className name of the class to remove. */ public void remove(String className) { - realm.checkNotInSync(); // destructive modifications are not permitted + realm.checkNotInSync(); // Destructive modifications are not permitted. checkEmpty(className, EMPTY_STRING_MSG); String internalTableName = TABLE_PREFIX + className; checkHasTable(className, "Cannot remove class because it is not in this Realm: " + className); @@ -206,7 +205,7 @@ public void remove(String className) { * @return a schema object for renamed class. */ public RealmObjectSchema rename(String oldClassName, String newClassName) { - realm.checkNotInSync(); // destructive modifications are not permitted + realm.checkNotInSync(); // Destructive modifications are not permitted. checkEmpty(oldClassName, "Class names cannot be empty or null"); checkEmpty(newClassName, "Class names cannot be empty or null"); String oldInternalName = TABLE_PREFIX + oldClassName; @@ -216,7 +215,7 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { throw new IllegalArgumentException(oldClassName + " cannot be renamed because the new class already exists: " + newClassName); } - // Check if there is a primary key defined for the old class. + // Checks if there is a primary key defined for the old class. Table oldTable = getTable(oldClassName); String pkField = null; if (oldTable.hasPrimaryKey()) { @@ -227,7 +226,7 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { realm.sharedRealm.renameTable(oldInternalName, newInternalName); Table table = realm.sharedRealm.getTable(newInternalName); - // Set the primary key for the new class if necessary + // Sets the primary key for the new class if necessary. if (pkField != null) { table.setPrimaryKey(pkField); } @@ -289,7 +288,7 @@ Table getTable(Class clazz) { if (table == null) { Class originalClass = Util.getOriginalModelClass(clazz); if (isProxyClass(originalClass, clazz)) { - // if passed 'clazz' is the proxy, try again with model class + // If passed 'clazz' is the proxy, try again with model class. table = classToTable.get(originalClass); } if (table == null) { @@ -297,7 +296,7 @@ Table getTable(Class clazz) { classToTable.put(originalClass, table); } if (isProxyClass(originalClass, clazz)) { - // 'clazz' is the proxy class for 'originalClass' + // 'clazz' is the proxy class for 'originalClass'. classToTable.put(clazz, table); } } @@ -309,7 +308,7 @@ RealmObjectSchema getSchemaForClass(Class clazz) { if (classSchema == null) { Class originalClass = Util.getOriginalModelClass(clazz); if (isProxyClass(originalClass, clazz)) { - // if passed 'clazz' is the proxy, try again with model class + // If passed 'clazz' is the proxy, try again with model class. classSchema = classToSchema.get(originalClass); } if (classSchema == null) { @@ -318,7 +317,7 @@ RealmObjectSchema getSchemaForClass(Class clazz) { classToSchema.put(originalClass, classSchema); } if (isProxyClass(originalClass, clazz)) { - // 'clazz' is the proxy class for 'originalClass' + // 'clazz' is the proxy class for 'originalClass'. classToSchema.put(clazz, classSchema); } } diff --git a/realm/realm-library/src/main/java/io/realm/Sort.java b/realm/realm-library/src/main/java/io/realm/Sort.java index ae187baff1..5d7c3d6f5d 100644 --- a/realm/realm-library/src/main/java/io/realm/Sort.java +++ b/realm/realm-library/src/main/java/io/realm/Sort.java @@ -33,6 +33,7 @@ public enum Sort { /** * Returns the value for this setting that is used by the underlying query engine. + * * @return the value used by the underlying query engine to indicate this value. */ public boolean getValue() { diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java index e4bca48d1b..353dabf128 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java @@ -33,7 +33,7 @@ public enum Kind { */ ACCESS_ERROR, /** - * Thrown if the history type of the on-disk Realm is unexpected or incompatible. + * Thrown if the history type of the on-disk Realm is unexpected or incompatible. */ BAD_HISTORY, /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index 75acb7df6b..7c36e57512 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -92,7 +92,7 @@ public boolean isNull(long columnIndex) { } /** - * Set null to a row pointer with checking if a column is nullable, except when the column type + * Sets null to a row pointer with checking if a column is nullable, except when the column type * is binary. * * @param columnIndex 0 based index value of the cell column. diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java index 3846930296..87ee853674 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java @@ -50,7 +50,7 @@ protected final void setIndicesMap(Map indicesMap) { /** * Copies the column index value from other {@link ColumnInfo} object. * - * @param other The class of {@code other} must be exactly the same as this instance. + * @param other the class of {@code other} must be exactly the same as this instance. * It must not be {@code null}. * @throws IllegalArgumentException if {@code other} has different class than this. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java b/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java index 2e8db788c3..c712fab530 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java +++ b/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java @@ -36,7 +36,7 @@ public void run() { NativeObjectReference reference = (NativeObjectReference) referenceQueue.remove(); reference.cleanup(); } catch (InterruptedException e) { - // Restore the interrupted status + // Restores the interrupted status. Thread.currentThread().interrupt(); RealmLog.fatal("The FinalizerRunnable thread has been interrupted." + diff --git a/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java b/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java index 2df4ead5d8..127d971012 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java @@ -18,7 +18,7 @@ import java.util.IdentityHashMap; /** - * Identity based Set, that guarantee store & retrieve in O(1) + * Identity based Set, that guarantees store & retrieve in O(1) * without a huge overhead in space complexity. */ public class IdentitySet extends IdentityHashMap { diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index f6718b4083..a368e4193d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -141,7 +141,7 @@ public Table getTable() { } /** - * Remove all target rows pointed to by links in this link view, and clear this link view. + * Removes all target rows pointed to by links in this link view, and clear this link view. */ public void removeAllTargetRows() { checkImmutable(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 8bb11bf554..651562b6d3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -44,14 +44,14 @@ public class ObjectServerFacade { } /** - * Initialize the Object Server library + * Initializes the Object Server library * @param context */ public void init(Context context) { } /** - * Notify the session for this configuration that a local commit was made. + * Notifies the session for this configuration that a local commit was made. */ public void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java index f658b1296e..e4b6760ecc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java @@ -70,7 +70,7 @@ private static String loadLibraryWindows() { // Above can't be used on Android. } //*/ - // Load debug library first - if available + // Loads debug library first - if available. String jnilib; jnilib = loadCorrectLibrary("realm_jni32d", "realm_jni64d"); if (jnilib != null) { @@ -110,10 +110,10 @@ public static void addNativeLibraryPath(String path) { // The ClassLoader has a static field (sys_paths) that contains the paths. // If that field is set to null, it is initialized automatically. // Therefore forcing that field to null will result into the reevaluation of the library path - // as soon as loadLibrary() is called + // as soon as loadLibrary() is called. private static void resetLibraryPath() { try { - // reset the library path (a hack) + // Resets the library path (a hack). Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); fieldSysPath.setAccessible(true); fieldSysPath.set(null, null); diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index bd72e4f7b8..7d0f38f253 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -34,7 +34,7 @@ import io.realm.exceptions.RealmException; /** - * Superclass for the RealmProxyMediator class'. This class contain all static methods introduced by the annotation + * Superclass for the RealmProxyMediator class. This class contains all static methods introduced by the annotation * processor as part of the RealmProxy classes. * * Classes extending this class act as binders between the static methods inside each RealmProxy and the code at @@ -45,11 +45,11 @@ public abstract class RealmProxyMediator { /** - * Create a object schema for the given RealmObject class. + * Creates a object schema for the given RealmObject class. * * @param clazz the {@link RealmObject} model class to create object schema for. * @param realmSchema the {@link RealmSchema} to associate the object schema with. - * @return The object schema. + * @return the object schema. */ public abstract RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema realmSchema); @@ -83,7 +83,7 @@ public abstract ColumnInfo validateTable(Class clazz, public abstract List getFieldNames(Class clazz); /** - * Returns the name that Realm should use for all it's internal tables. This is the un-obfuscated name of the + * Returns the name that Realm should use for all its internal tables. This is the un-obfuscated name of the * class. * * @param clazz the {@link RealmObject} class reference. @@ -98,9 +98,9 @@ public abstract ColumnInfo validateTable(Class clazz, * @param clazz the {@link RealmObject} to create {@link RealmObjectProxy} for. * @param acceptDefaultValue {@code true} to accept the values set in the constructor, {@code false} otherwise. * @param excludeFields the column names whose default value will be ignored if the {@code acceptDefaultValue} - * is {@code true}. Only {@link io.realm.RealmModel} and {@link io.realm.RealmList} - * column will respect this. - * No effects if the {@code acceptDefaultValue} is {@code false}. + * is {@code true}. Only {@link io.realm.RealmModel} and {@link io.realm.RealmList} + * column will respect this. + * No effects if the {@code acceptDefaultValue} is {@code false}. * @return created {@link RealmObjectProxy} object. */ public abstract E newInstance(Class clazz, @@ -121,18 +121,18 @@ public abstract E newInstance(Class clazz, * Copies an unmanaged {@link RealmObject} or a RealmObject from another Realm to this Realm. After being copied * any changes to the original object will not be persisted. * - * @param realm reference to the {@link Realm} where the object will be copied. + * @param realm the reference to the {@link Realm} where the object will be copied. * @param object the object to copy properties from. * @param update {@code true} if object has a primary key and should try to update already existing data, - * {@code false} otherwise. + * {@code false} otherwise. * @param cache the cache for mapping between unmanaged objects and their {@link RealmObjectProxy} representation. * @return the managed Realm object. */ public abstract E copyOrUpdate(Realm realm, E object, boolean update, Map cache); /** - * Insert an unmanaged RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} since it - * doesn't return the inserted elements, and performs minimum allocations and checks. + * Inserts an unmanaged RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} + * since it doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original object will not be persisted. * * @param realm reference to the {@link Realm} where the object will be inserted. @@ -142,8 +142,8 @@ public abstract E newInstance(Class clazz, public abstract void insert(Realm realm, RealmModel object, Map cache); /** - * Insert or update a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} since it - * doesn't return the inserted elements, and performs minimum allocations and checks. + * Inserts or updates a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} + * since it doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original object will not be persisted. * * @param realm reference to the {@link Realm} where the objecs will be inserted. @@ -153,8 +153,8 @@ public abstract E newInstance(Class clazz, public abstract void insertOrUpdate(Realm realm, RealmModel object, Map cache); /** - * Insert or update a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} since it - * doesn't return the inserted elements, and performs minimum allocations and checks. + * Inserts or updates a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} + * since it doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original objects will not be persisted. * * @param realm reference to the {@link Realm} where the objects will be inserted. @@ -163,8 +163,9 @@ public abstract E newInstance(Class clazz, public abstract void insertOrUpdate(Realm realm, Collection objects); /** - * Insert a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} since it - * doesn't return the inserted elements, and performs minimum allocations and checks. After being inserted any changes to the original objects will not be persisted. + * Inserts a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} since + * it doesn't return the inserted elements, and performs minimum allocations and checks. + * After being inserted any changes to the original objects will not be persisted. * * @param realm reference to the {@link Realm} where the objects will be inserted. * @param objects Collection of {@link RealmObject} to insert or update. This must not be empty. diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index b63f85fd34..023e923bff 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -107,7 +107,7 @@ public interface Row { /** * Returns {@code true} if the field name exists. * - * @param fieldName Field name to check. + * @param fieldName field name to check. * @return {@code true} if field name exists, {@code false} otherwise. */ boolean hasColumn(String fieldName); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 9d3868f38e..5037f13d12 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -326,7 +326,7 @@ public boolean compact() { } /** - * Update the underlying schema based on the schema description. + * Updates the underlying schema based on the schema description. * Calling this method must be done from inside a write transaction. */ public void updateSchema(RealmSchema schema, long version) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index b5c210ba29..e518baf8ca 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -146,7 +146,7 @@ public long addColumnLink (RealmFieldType type, String name, Table table) { } /** - * Removes a column in the table dynamically. if {@code columnIndex} is smaller than the primary + * Removes a column in the table dynamically. If {@code columnIndex} is smaller than the primary * key column index, {@link #invalidateCachedPrimaryKeyIndex()} will be called to recalculate the * primary key column index. * @@ -157,15 +157,15 @@ public long addColumnLink (RealmFieldType type, String name, Table table) { */ @Override public void removeColumn(long columnIndex) { - // Check the PK column index before removing a column. We don't know if we're hitting a PK col, + // Checks the PK column index before removing a column. We don't know if we're hitting a PK col, // but it should be noted that once a column is removed, there is no way we can find whether // a PK exists or not. final long oldPkColumnIndex = getPrimaryKey(); - // firstly remove a column. If there is no error, we can proceed. Otherwise, it will stop here. + // First removes a column. If there is no error, we can proceed. Otherwise, it will stop here. nativeRemoveColumn(nativePtr, columnIndex); - // Check if a PK exists and take actions if there is. This is same as hasPrimaryKey(), but + // Checks if a PK exists and takes actions if there is. This is same as hasPrimaryKey(), but // this relies on the local cache. if (oldPkColumnIndex >= 0) { @@ -195,16 +195,16 @@ public void removeColumn(long columnIndex) { @Override public void renameColumn(long columnIndex, String newName) { verifyColumnName(newName); - // get the old column name. We'll assume that the old column name is *NOT* an empty string. + // Gets the old column name. We'll assume that the old column name is *NOT* an empty string. final String oldName = nativeGetColumnName(nativePtr, columnIndex); - // also old pk index. Once a column name changes, there is no way you can find the column name + // Also old pk index. Once a column name changes, there is no way you can find the column name // by old name. final long oldPkColumnIndex = getPrimaryKey(); - // then let's try to rename a column. If an error occurs for some reasons, we'll throw. + // Then let's try to rename a column. If an error occurs for some reasons, we'll throw. nativeRenameColumn(nativePtr, columnIndex, newName); - // Rename a primary key. At this point, renaming the column name should have been fine. + // Renames a primary key. At this point, renaming the column name should have been fine. if (oldPkColumnIndex == columnIndex) { try { String className = tableNameToClassName(getName()); @@ -220,7 +220,7 @@ public void renameColumn(long columnIndex, String newName) { throw new IllegalStateException("Non-existent PrimaryKey column cannot be renamed"); } } catch (Exception e) { - // we failed to rename the pk meta table. roll back the column name, not pk meta table + // We failed to rename the pk meta table. roll back the column name, not pk meta table // then rethrow. nativeRenameColumn(nativePtr, columnIndex, oldName); throw e; @@ -365,7 +365,7 @@ public void moveLastOver(long rowIndex) { } /** - * Add an empty row to the table which doesn't have a primary key defined. + * Adds an empty row to the table which doesn't have a primary key defined. *

          * NOTE: To add a table with a primary key defined, use {@link #addEmptyRowWithPrimaryKey(Object)} instead. This * won't check if this table has a primary key. @@ -378,7 +378,7 @@ public long addEmptyRow() { } /** - * Add an empty row to the table and set the primary key with the given value. Equivalent to call + * Adds an empty row to the table and set the primary key with the given value. Equivalent to call * {@link #addEmptyRowWithPrimaryKey(Object, boolean)} with {@code validation = true}. * * @param primaryKeyValue the primary key value @@ -389,7 +389,7 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { } /** - * Add an empty row to the table and set the primary key with the given value. + * Adds an empty row to the table and set the primary key with the given value. * * @param primaryKeyValue the primary key value. * @param validation set to {@code false} to skip all validations. This is currently used by bulk insert which @@ -406,7 +406,7 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation RealmFieldType type = getColumnType(primaryKeyColumnIndex); long rowIndex; - // Add with primary key initially set + // Adds with primary key initially set. if (primaryKeyValue == null) { switch (type) { case STRING: @@ -481,7 +481,7 @@ public long addEmptyRows(long rows) { * @param values values. * @return the row index of the appended row. * @deprecated Remove this functions since it doesn't seem to be useful. And this function does deal with tables - * withprimary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row + * with primary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row * added. */ protected long add(Object... values) { @@ -489,7 +489,7 @@ protected long add(Object... values) { checkImmutable(); - // Check values types + // Checks values types. int columns = (int)getColumnCount(); if (columns != values.length) { throw new IllegalArgumentException("The number of value parameters (" + @@ -503,7 +503,7 @@ protected long add(Object... values) { RealmFieldType colType = getColumnType(columnIndex); colTypes[columnIndex] = colType; if (!colType.isValid(value)) { - //String representation of the provided value type + // String representation of the provided value type. String providedType; if (value == null) { providedType = "null"; @@ -516,7 +516,7 @@ protected long add(Object... values) { } } - // Insert values + // Inserts values. for (long columnIndex = 0; columnIndex < columns; columnIndex++) { Object value = values[(int)columnIndex]; switch (colTypes[(int)columnIndex]) { @@ -583,7 +583,7 @@ public long getPrimaryKey() { } else { Table pkTable = getPrimaryKeyTable(); if (pkTable == null) { - return NO_PRIMARY_KEY; // Free table = No primary key + return NO_PRIMARY_KEY; // Free table = No primary key. } String className = tableNameToClassName(getName()); @@ -636,7 +636,7 @@ void checkIntValueIsLegal(long columnIndex, long rowToUpdate, long value) { } } - // check if it is ok to use null value for given row and column. + // Checks if it is ok to use null value for given row and column. void checkDuplicatedNullForPrimaryKeyValue(long columnIndex, long rowToUpdate) { if (isPrimaryKeyColumn(columnIndex)) { RealmFieldType type = getColumnType(columnIndex); @@ -695,7 +695,7 @@ public Date getDate(long columnIndex, long rowIndex) { } /** - * Gets the value of a (string )cell. + * Gets the value of a (string) cell. * * @param columnIndex 0 based index value of the column * @param rowIndex 0 based index of the row. @@ -718,7 +718,7 @@ public long getLink(long columnIndex, long rowIndex) { public Table getLinkTarget(long columnIndex) { long nativeTablePointer = nativeGetLinkTarget(nativePtr, columnIndex); - // Copy context reference from parent + // Copies context reference from parent. Table table = new Table(this.sharedRealm, nativeTablePointer); return table; } @@ -801,7 +801,7 @@ public void setDate(long columnIndex, long rowIndex, Date date, boolean isDefaul } /** - * Set a String value to a cell of Table, pointed by column and row index. + * Sets a String value to a cell of Table, pointed by column and row index. * * @param columnIndex 0 based index value of the cell column. * @param rowIndex 0 based index value of the cell row. @@ -883,7 +883,7 @@ private Table getPrimaryKeyTable() { } /** - * Invalidating a cached primary key column index for the table. + * Invalidates a cached primary key column index for the table. */ private void invalidateCachedPrimaryKeyIndex() { cachedPrimaryKeyColumnIndex = NO_MATCH; @@ -935,7 +935,7 @@ boolean isImmutable() { return sharedRealm != null && !sharedRealm.isInTransaction(); } - // This checking should be moved to SharedRealm level + // This checking should be moved to SharedRealm level. void checkImmutable() { if (isImmutable()) { throwImmutable(); @@ -1055,7 +1055,7 @@ public long count(long columnIndex, String value) { @Override public TableQuery where() { long nativeQueryPtr = nativeWhere(nativePtr); - // Copy context reference from parent + // Copies context reference from parent. return new TableQuery(this.context, this, nativeQueryPtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java b/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java index 58404ee5bf..b9e6aa7a27 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableOrView.java @@ -131,8 +131,6 @@ public interface TableOrView { * @param rowIndex * @return */ - //ByteBuffer getBinaryByteBuffer(long columnIndex, long rowIndex); - byte[] getBinaryByteArray(long columnIndex, long rowIndex); /** @@ -196,9 +194,8 @@ public interface TableOrView { * @param columnIndex * @param rowIndex * @param data + * @param isDefault */ - //void setBinaryByteBuffer(long columnIndex, long rowIndex, ByteBuffer data); - void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data, boolean isDefault); void setDate(long columnIndex, long rowIndex, Date date, boolean isDefault); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 05cdae38d5..902f4445da 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -76,7 +76,7 @@ public long getNativeFinalizerPtr() { * Checks in core if query syntax is valid. Throws exception, if not. */ private void validateQuery() { - if (! queryValidated) { // If not yet validated, check if syntax is valid + if (!queryValidated) { // If not yet validated, checks if syntax is valid String invalidMessage = nativeValidateQuery(nativePtr); if (invalidMessage.equals("")) queryValidated = true; // If empty string error message, query is valid @@ -85,7 +85,7 @@ private void validateQuery() { } } - // Query TableView + // Query TableView. public TableQuery tableview(TableView tv) { nativeTableview(nativePtr, tv.nativePtr); return this; @@ -117,7 +117,7 @@ public TableQuery not() { return this; } - // Query for integer values. + // Queries for integer values. public TableQuery equalTo(long columnIndexes[], long value) { nativeEqual(nativePtr, columnIndexes, value); @@ -161,7 +161,7 @@ public TableQuery between(long columnIndex[], long value1, long value2) { return this; } - // Query for float values. + // Queries for float values. public TableQuery equalTo(long columnIndex[], float value) { nativeEqual(nativePtr, columnIndex, value); @@ -205,7 +205,7 @@ public TableQuery between(long columnIndex[], float value1, float value2) { return this; } - // Query for double values. + // Queries for double values. public TableQuery equalTo(long columnIndex[], double value) { nativeEqual(nativePtr, columnIndex, value); @@ -257,7 +257,7 @@ public TableQuery equalTo(long columnIndex[], boolean value) { return this; } - // Query for Date values + // Queries for Date values. private final static String DATE_NULL_ERROR_MESSAGE = "Date value in query criteria must not be null."; @@ -319,7 +319,7 @@ public TableQuery between(long columnIndex[], Date value1, Date value2){ return this; } - // Query for Binary values. + // Queries for Binary values. public TableQuery equalTo(long[] columnIndices, byte[] value) { nativeEqual(nativePtr, columnIndices, value); @@ -337,7 +337,7 @@ public TableQuery notEqualTo(long[] columnIndices, byte[] value) { private final static String STRING_NULL_ERROR_MESSAGE = "String value in query criteria must not be null."; - // Equal + // Equals public TableQuery equalTo(long[] columnIndexes, String value, Case caseSensitive) { nativeEqual(nativePtr, columnIndexes, value, caseSensitive.getValue()); queryValidated = false; @@ -350,7 +350,7 @@ public TableQuery equalTo(long[] columnIndexes, String value) { return this; } - // Not Equal + // Not Equals public TableQuery notEqualTo(long columnIndex[], String value, Case caseSensitive) { nativeNotEqual(nativePtr, columnIndex, value, caseSensitive.getValue()); queryValidated = false; @@ -462,7 +462,7 @@ public TableView findAll() { return new TableView(this.context, this.table, nativeViewPtr, this); } - // handover find* methods + // Handovers find* methods. // this will use a background SharedGroup to import the query (using the handover object) // run the query, and return the table view to the caller SharedGroup using the handover object. public static long findAllWithHandover(SharedRealm sharedRealm, long ptrQuery) throws BadVersionException { @@ -564,7 +564,7 @@ public double averageInt(long columnIndex) { return nativeAverageInt(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); } - // float aggregation + // Float aggregation public double sumFloat(long columnIndex, long start, long end, long limit) { validateQuery(); @@ -602,7 +602,7 @@ public double averageFloat(long columnIndex) { return nativeAverageFloat(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); } - // double aggregation + // Double aggregation public double sumDouble(long columnIndex, long start, long end, long limit) { validateQuery(); @@ -640,7 +640,7 @@ public double averageDouble(long columnIndex) { return nativeAverageDouble(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); } - // date aggregation + // Date aggregation public Date maximumDate(long columnIndex, long start, long end, long limit) { validateQuery(); @@ -689,7 +689,7 @@ public TableQuery isNotNull(long columnIndices[]) { return this; } - // count + // Count // TODO: Rename all start, end parameter names to firstRow, lastRow public long count(long start, long end, long limit) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableView.java b/realm/realm-library/src/main/java/io/realm/internal/TableView.java index 9c352f08bc..5bf309d3c7 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableView.java @@ -426,7 +426,7 @@ public void removeLast() { } } - // Search for first match + // Searches for first match @Override public long findFirstLong(long columnIndex, long value){ return nativeFindFirstInt(nativePtr, columnIndex, value); @@ -459,7 +459,7 @@ public long findFirstString(long columnIndex, String value){ return nativeFindFirstString(nativePtr, columnIndex, value); } - // Search for all matches + // Searches for all matches // TODO.. @Override @@ -618,7 +618,7 @@ public Date minimumDate(long columnIndex) { return new Date(result); } - // Sorting + // Sortings public void sort(long columnIndex, Sort sortOrder) { // Don't check for immutable. Sorting does not modify original table nativeSort(nativePtr, columnIndex, sortOrder.getValue()); @@ -723,7 +723,7 @@ public void distinct(long columnIndex) { } /** - * If two rows are indentical (for the given set of distinct-columns), then the last row is + * If two rows are identical (for the given set of distinct-columns), then the last row is * removed unless sorted, in which case the first object is returned. * Each time distinct() gets called, it will first fetch the full original TableView contents * and then apply distinct() on that, invalidating previous distinct(). diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index b5b963f072..7a18693df0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -225,7 +225,7 @@ public void setDate(long columnIndex, Date date) { } /** - * Set a string value to a row pointer. + * Sets a string value to a row pointer. * * @param columnIndex 0 based index value of the cell column. * @param value the value to to a row @@ -266,7 +266,7 @@ public boolean isNull(long columnIndex) { } /** - * Set null to a row pointer. + * Sets null to a row pointer. * * @param columnIndex 0 based index value of the cell column. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index d262452289..46de7e107e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -36,7 +36,7 @@ public static long getNativeMemUsage() { } static native long nativeGetMemUsage(); - // Called by JNI. Do not remove + // Called by JNI. Do not remove. static void javaPrint(String txt) { System.out.print(txt); } @@ -51,8 +51,8 @@ public static String getTablePrefix() { * was a RealmProxy class. */ public static Class getOriginalModelClass(Class clazz) { - //This cast is correct because 'clazz' is either the type - //generated by RealmProxy or the original type extending directly from RealmObject + // This cast is correct because 'clazz' is either the type + // generated by RealmProxy or the original type extending directly from RealmObject. @SuppressWarnings("unchecked") Class superclass = (Class) clazz.getSuperclass(); @@ -102,8 +102,8 @@ public static boolean deleteRealm(String canonicalPath, File realmFolder, String final String management = ".management"; File managementFolder = new File(realmFolder, realmFileName + management); - // delete files in management directory and the directory - // there is no subfolders in the management directory + // Deletes files in management directory and the directory. + // There is no subfolders in the management directory. File[] files = managementFolder.listFiles(); if (files != null) { for (File file : files) { @@ -112,7 +112,7 @@ public static boolean deleteRealm(String canonicalPath, File realmFolder, String } realmDeleted = realmDeleted && managementFolder.delete(); - // delete specific files in root directory + // Deletes specific files in root directory. return realmDeleted && deletes(canonicalPath, realmFolder, realmFileName); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java b/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java index bfdd9bb032..5d9b90636b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java @@ -70,27 +70,27 @@ public static Date parse(String date, ParsePosition pos) throws ParseException { try { int offset = pos.getIndex(); - // extract year + // Extracts year. int year = parseInt(date, offset, offset += 4); if (checkOffset(date, offset, '-')) { offset += 1; } - // extract month + // Extracts month. int month = parseInt(date, offset, offset += 2); if (checkOffset(date, offset, '-')) { offset += 1; } - // extract day + // Extracts day. int day = parseInt(date, offset, offset += 2); - // default time value + // Default time value. int hour = 0; int minutes = 0; int seconds = 0; - int milliseconds = 0; // always use 0 otherwise returned date will include millis of current time + int milliseconds = 0; // Always use 0 otherwise returned date will include millis of current time. - // if the value has no time component (and no time zone), we are done + // If the value has no time component (and no time zone), we are done. boolean hasT = checkOffset(date, offset, 'T'); if (!hasT && (date.length() <= offset)) { @@ -102,7 +102,7 @@ public static Date parse(String date, ParsePosition pos) throws ParseException { if (hasT) { - // extract hours, minutes, seconds and milliseconds + // Extracts hours, minutes, seconds and milliseconds. hour = parseInt(date, offset += 1, offset += 2); if (checkOffset(date, offset, ':')) { offset += 1; @@ -112,20 +112,20 @@ public static Date parse(String date, ParsePosition pos) throws ParseException { if (checkOffset(date, offset, ':')) { offset += 1; } - // second and milliseconds can be optional + // Second and milliseconds can be optional. if (date.length() > offset) { char c = date.charAt(offset); if (c != 'Z' && c != '+' && c != '-') { seconds = parseInt(date, offset, offset += 2); - if (seconds > 59 && seconds < 63) seconds = 59; // truncate up to 3 leap seconds - // milliseconds can be optional in the format + if (seconds > 59 && seconds < 63) seconds = 59; // Truncates up to 3 leap seconds. + // Milliseconds can be optional in the format. if (checkOffset(date, offset, '.')) { offset += 1; - int endOffset = indexOfNonDigit(date, offset + 1); // assume at least one digit - int parseEndOffset = Math.min(endOffset, offset + 3); // parse up to 3 digits + int endOffset = indexOfNonDigit(date, offset + 1); // Assumes at least one digit. + int parseEndOffset = Math.min(endOffset, offset + 3); // Parses up to 3 digits. int fraction = parseInt(date, offset, parseEndOffset); - // compensate for "missing" digits - switch (parseEndOffset - offset) { // number of digits parsed + // Compensates for "missing" digits. + switch (parseEndOffset - offset) { // Number of digits parsed. case 2: milliseconds = fraction * 10; break; @@ -141,7 +141,7 @@ public static Date parse(String date, ParsePosition pos) throws ParseException { } } - // extract timezone + // Extracts timezone. if (date.length() <= offset) { throw new IllegalArgumentException("No time zone indicator"); } @@ -164,7 +164,6 @@ public static Date parse(String date, ParsePosition pos) throws ParseException { // `java.util.TimeZone` specifically instruct use of GMT as base for // custom timezones... odd. String timezoneId = "GMT" + timezoneOffset; -// String timezoneId = "UTC" + timezoneOffset; timezone = TimeZone.getTimeZone(timezoneId); diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/JsonUtils.java b/realm/realm-library/src/main/java/io/realm/internal/android/JsonUtils.java index 3bc9da984a..b25cd67328 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/JsonUtils.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/JsonUtils.java @@ -44,14 +44,14 @@ public class JsonUtils { public static Date stringToDate(String date) { if (date == null || date.length() == 0) return null; - // Check for JSON date + // Checks for JSON date. Matcher matcher = jsonDate.matcher(date); if (matcher.find()) { String dateMatch = matcher.group(1); return new Date(Long.parseLong(dateMatch)); } - // Check for millisecond based date + // Checks for millisecond based date. if (numericOnly.matcher(date).matches()) { try { return new Date(Long.parseLong(date)); @@ -60,9 +60,9 @@ public static Date stringToDate(String date) { } } - // Try for ISO8601 date + // Tries for ISO8601 date. try { - parsePosition.setIndex(0); // reset the position each time + parsePosition.setIndex(0); // Resets the position each time. return ISO8601Utils.parse(date, parsePosition); } catch (ParseException e) { throw new RealmException(e.getMessage(), e); diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java index 7d943b06a1..4f387d012d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java @@ -37,7 +37,7 @@ public class RealmThreadPoolExecutor extends ThreadPoolExecutor { private static final String SYS_CPU_DIR = "/sys/devices/system/cpu/"; - // reduce context switching by using a number of thread proportionate to the number of cores + // Reduces context switching by using a number of thread proportionate to the number of cores. private static final int CORE_POOL_SIZE = calculateCorePoolSize(); private static final int QUEUE_SIZE = 100; @@ -60,7 +60,7 @@ public static RealmThreadPoolExecutor newSingleThreadExecutor() { } /** - * Try using the number of files named 'cpuNN' in sysfs to figure out the number of + * Tries using the number of files named 'cpuNN' in sysfs to figure out the number of * processors on this device. `Runtime.getRuntime().availableProcessors()` may return * a smaller number when the device is sleeping. * @@ -76,8 +76,8 @@ private static int calculateCorePoolSize() { } /** - * @param dirPath A directory path - * @param pattern A regex + * @param dirPath a directory path + * @param pattern a regex * @return the number of files, in the `dirPath` directory, whose names match `pattern` */ private static int countFilesInDir(String dirPath, String pattern) { @@ -174,7 +174,7 @@ public void pause() { } /** - * Resume executing any scheduled tasks. + * Resumes executing any scheduled tasks. */ public void resume() { pauseLock.lock(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index 3374d337be..e2cee9d70f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -178,7 +178,7 @@ public boolean transformerApplied() { return originalMediator.transformerApplied(); } - // Validate if a model class (not RealmProxy) is part of this Schema. + // Validates if a model class (not RealmProxy) is part of this Schema. private void checkSchemaHasClass(Class clazz) { if (!allowedClasses.contains(clazz)) { throw new IllegalArgumentException(clazz.getSimpleName() + " is not part of the schema for this Realm"); diff --git a/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java b/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java index d39f536486..87a1a98094 100644 --- a/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java +++ b/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java @@ -69,7 +69,7 @@ public void setTag(String tag) { @Override public int getMinimumNativeDebugLevel() { - // Map Android log level to Realms log levels + // Maps Android log level to Realms log levels. switch (minimumLogLevel) { case Log.VERBOSE: return LogLevel.TRACE; case Log.DEBUG: return LogLevel.DEBUG; @@ -89,7 +89,7 @@ private void log(int androidLogLevel, Throwable t, String message, Object... arg } if (message == null) { if (t == null) { - return; // Ignore event if message is null and there's no throwable. + return; // Ignores event if message is null and there's no throwable. } message = getStackTraceString(t); } else { @@ -101,14 +101,14 @@ private void log(int androidLogLevel, Throwable t, String message, Object... arg } } - // Message fit one line. Just print and exit + // Message fits one line. Just prints and exits. if (message.length() < LOG_ENTRY_MAX_LENGTH) { Log.println(androidLogLevel, logTag, message); return; } // Message does not fit one line. - // Split by line, then ensure each line can fit into Log's maximum length. + // Splits by line, then ensures each line can fit into Log's maximum length. for (int i = 0, length = message.length(); i < length; i++) { int newline = message.indexOf('\n', i); newline = newline != -1 ? newline : length; diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index e16d1be4b6..877e8d9e0e 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -353,7 +353,7 @@ public static void fatal(Throwable throwable, String message, Object... args) { log(LogLevel.FATAL, throwable, message, args); } - // Format the message, parse the stacktrace of given throwable and pass them to nativeLog. + // Formats the message, parses the stacktrace of given throwable and passes them to nativeLog. private static void log(int level, Throwable throwable, String message, Object... args) { StringBuilder stringBuilder = new StringBuilder(); if (args != null && args.length > 0) { diff --git a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java index 7c89ecd45a..2a0a090e61 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java @@ -67,7 +67,7 @@ public Observable from(Realm realm) { return Observable.create(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { - // Get instance to make sure that the Realm is open for as long as the + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); final RealmChangeListener listener = new RealmChangeListener() { @@ -97,7 +97,7 @@ public Observable from(DynamicRealm realm) { return Observable.create(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { - // Get instance to make sure that the Realm is open for as long as the + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); final RealmChangeListener listener = new RealmChangeListener() { @@ -117,7 +117,7 @@ public void call() { } })); - // Immediately call onNext with the current value, as due to Realm's auto-update, it will be the latest + // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest // value. subscriber.onNext(observableRealm); } @@ -131,7 +131,7 @@ public Observable> from(final Realm realm return Observable.create(new Observable.OnSubscribe>() { @Override public void call(final Subscriber> subscriber) { - // Get instance to make sure that the Realm is open for as long as the + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); resultsRefs.get().acquireReference(results); @@ -154,7 +154,7 @@ public void call() { } })); - // Immediately call onNext with the current value, as due to Realm's auto-update, it will be the latest + // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest // value. subscriber.onNext(results); } @@ -167,7 +167,7 @@ public Observable> from(DynamicRealm realm, fin return Observable.create(new Observable.OnSubscribe>() { @Override public void call(final Subscriber> subscriber) { - // Get instance to make sure that the Realm is open for as long as the + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); resultsRefs.get().acquireReference(results); @@ -190,7 +190,7 @@ public void call() { } })); - // Immediately call onNext with the current value, as due to Realm's auto-update, it will be the latest + // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest // value. subscriber.onNext(results); } @@ -217,7 +217,7 @@ public Observable from(final Realm realm, final E obje return Observable.create(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { - // Get instance to make sure that the Realm is open for as long as the + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); objectRefs.get().acquireReference(object); @@ -240,7 +240,7 @@ public void call() { } })); - // Immediately call onNext with the current value, as due to Realm's auto-update, it will be the latest + // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest // value. subscriber.onNext(object); } @@ -253,7 +253,7 @@ public Observable from(DynamicRealm realm, final DynamicReal return Observable.create(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { - // Get instance to make sure that the Realm is open for as long as the + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); objectRefs.get().acquireReference(object); @@ -276,7 +276,7 @@ public void call() { } })); - // Immediately call onNext with the current value, as due to Realm's auto-update, it will be the latest + // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest // value. subscriber.onNext(object); } From 0e3e1ac98ec728b266b13d6f97ed3d40cbb0c7ed Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Tue, 7 Feb 2017 16:51:21 -0800 Subject: [PATCH 0482/2110] Release v2.3.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 50794f17f1..a6254504e4 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.1-SNAPSHOT \ No newline at end of file +2.3.1 \ No newline at end of file From 7c92d7075906bd00332834e890df564094146a0d Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Tue, 7 Feb 2017 16:51:21 -0800 Subject: [PATCH 0483/2110] Prepare next release v2.3.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a6254504e4..0c3a5eaf45 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.1 \ No newline at end of file +2.3.2-SNAPSHOT \ No newline at end of file From 97fdf6864c67e98e711ffb97dabd70a6e26ca72a Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 8 Feb 2017 13:04:16 +0000 Subject: [PATCH 0484/2110] Nh/refresh access token (#4147) * Add a timer to refresh the access_token before it expires --- CHANGELOG.md | 6 +- .../io/realm/AuthenticateRequestTests.java | 9 +- .../src/main/java/io/realm/RealmResults.java | 2 +- .../objectServer/java/io/realm/SyncUser.java | 2 +- .../internal/network/AuthenticateRequest.java | 4 +- .../network/AuthenticationServer.java | 2 +- .../network/ExponentialBackoffTask.java | 2 +- .../network/OkHttpAuthenticationServer.java | 4 +- .../objectserver/ObjectServerSession.java | 108 +++++++++++++++++- 9 files changed, 125 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38da001d39..09a1ceb77c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,13 @@ * Bug causing classes to be replaced by classes already in Gradle's classpath (#3568). * NullPointerException when notifying a single object that it changed (#4086). +### Enhancements + +* [ObjectServer] Add a timer to refresh periodically the access_token. + ## 2.3.0 -### Object Server API Changes +### Object Server API Changes * Realm Sync v1.0.0 has been released, and Realm Mobile Platform is no longer considered in beta. * Breaking change: Location of Realm files are now placed in `getFilesDir()/` instead of `getFilesDir()/`. diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index a9646a0bea..54a87b468c 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.when; @@ -39,10 +40,10 @@ public void setUp() { @Test public void realmLogin() throws URISyntaxException, JSONException { Token t = SyncTestUtils.createTestUser().getSyncUser().getUserToken(); - AuthenticateRequest request = AuthenticateRequest.realmLogin(t, new URI("realm://objectserver/" + t.value() + "/default")); + AuthenticateRequest request = AuthenticateRequest.realmLogin(t, new URI("realm://objectserver/" + t.identity() + "/default")); JSONObject obj = new JSONObject(request.toJson()); - assertEquals("/" + t.value() + "/default", obj.get("path")); + assertEquals("/" + t.identity() + "/default", obj.get("path")); assertEquals(t.value(), obj.get("data")); assertEquals("realm", obj.get("provider")); } @@ -60,10 +61,10 @@ public void userLogin() throws URISyntaxException, JSONException { @Test public void userRefresh() throws URISyntaxException, JSONException { Token t = SyncTestUtils.createTestUser().getSyncUser().getUserToken(); - AuthenticateRequest request = AuthenticateRequest.userRefresh(t); + AuthenticateRequest request = AuthenticateRequest.userRefresh(t, new URI("realm://objectserver/" + t.identity() + "/default")); JSONObject obj = new JSONObject(request.toJson()); - assertFalse(obj.has("path")); + assertTrue(obj.has("path")); assertEquals(t.value(), obj.get("data")); assertEquals("realm", obj.get("provider")); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 069a5c4044..0624795949 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -789,7 +789,7 @@ public void remove() { throw new UnsupportedOperationException("remove() is not supported by RealmResults iterators."); } - protected void checkRealmIsStable() { + void checkRealmIsStable() { long version = table.getVersion(); // Any change within a write transaction will immediately update the table version. This means that we // cannot depend on the tableVersion heuristic in that case. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index b4e93bcefa..eac47154eb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -361,7 +361,7 @@ public String toJson() { * Returns {@code true} if the user is logged into the Realm Object Server. If this method returns {@code true} it * implies that the user has valid credentials that have not expired. *

          - * The user might still be have been logged out by the Realm Object Server which will not be detected before the + * The user might still have been logged out by the Realm Object Server which will not be detected before the * user tries to actively synchronize a Realm. If a logged out user tries to synchronize a Realm, an error will be * reported to the {@link SyncSession.ErrorHandler} defined by * {@link SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler)}. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java index 6b27dfc7d7..e921daec6c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java @@ -56,11 +56,11 @@ public static AuthenticateRequest userLogin(SyncCredentials credentials) { /** * Generates a request for refreshing a user token. */ - public static AuthenticateRequest userRefresh(Token userToken) { + public static AuthenticateRequest userRefresh(Token userToken, URI serverUrl) { return new AuthenticateRequest("realm", userToken.value(), SyncManager.APP_ID, - null, + serverUrl.getPath(), Collections.emptyMap() ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java index b217269ad7..3b74558cc6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java @@ -48,7 +48,7 @@ public interface AuthenticationServer { * Before it expires, the client should try to refresh the token, effectively keeping the user logged in on the * Object Server. Failing to do so will cause a "soft logout", where the User will have limited access rights. */ - AuthenticateResponse refreshUser(Token userToken, URL authenticationUrl); + AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL authenticationUrl); /** * Logs out the user on the Object Server by invalidating the refresh token. Each device should be given their diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java index aa613c8357..ab53fff060 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java @@ -44,7 +44,7 @@ protected boolean shouldAbortTask(T response) { } } - // Callback when task is have succeeded + // Callback when task have succeeded protected abstract void onSuccess(T response); // Callback when task has failed diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index fe580d438b..b4bbb18cde 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -67,9 +67,9 @@ public AuthenticateResponse loginToRealm(Token refreshToken, URI serverUrl, URL } @Override - public AuthenticateResponse refreshUser(Token userToken, URL authenticationUrl) { + public AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL authenticationUrl) { try { - String requestBody = AuthenticateRequest.userRefresh(userToken).toJson(); + String requestBody = AuthenticateRequest.userRefresh(userToken, serverUrl).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java index 3d78d24aa5..615e23b5f5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java @@ -19,6 +19,9 @@ import java.net.URI; import java.util.HashMap; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import io.realm.ErrorCode; import io.realm.ObjectServerError; @@ -99,6 +102,8 @@ public final class ObjectServerSession { private long nativeSessionPointer; private final ObjectServerUser user; RealmAsyncTask networkRequest; + private RealmAsyncTask refreshTokenTask; + private RealmAsyncTask refreshTokenNetworkRequest; NetworkStateReceiver.ConnectionListener networkListener; private SyncPolicy syncPolicy; @@ -106,7 +111,9 @@ public final class ObjectServerSession { private SessionState currentStateDescription; private FsmState currentState; private SyncSession userSession; - private SyncSession publicSession; + + private final static ScheduledThreadPoolExecutor REFRESH_TOKENS_EXECUTOR = new ScheduledThreadPoolExecutor(1); + private final static long REFRESH_MARGIN_DELAY = TimeUnit.SECONDS.toMillis(10); /** * Creates a new Object Server Session. @@ -166,6 +173,8 @@ public synchronized void start() { * Stops the session. The session can no longer be used. */ public synchronized void stop() { + // tries to stop any scheduled access_token refresh + clearScheduledAccessTokenRefresh(); currentState.onStop(); } @@ -238,6 +247,25 @@ void stopNativeSession() { nativeUnbind(nativeSessionPointer); nativeSessionPointer = 0; } + clearScheduledAccessTokenRefresh(); + } + + // It is an error to call this function before calling Client::bind() state + private boolean updateSessionAccessToken(String userToken) { + if (nativeSessionPointer != 0 && isBound()) { + nativeRefresh(nativeSessionPointer, userToken); + return true; + } + return false; + } + + private void clearScheduledAccessTokenRefresh() { + if (refreshTokenTask != null) { + refreshTokenTask.cancel(); + } + if (refreshTokenNetworkRequest != null) { + refreshTokenNetworkRequest.cancel(); + } } void removeAccessToken() { @@ -260,6 +288,10 @@ void authenticateRealm(final Runnable onSuccess, final SyncSession.ErrorHandler if (networkRequest != null) { networkRequest.cancel(); } + // clear any previously scheduled refresh access_token + // since we're going to obtain a new refresh_token + clearScheduledAccessTokenRefresh(); + // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { @Override @@ -279,6 +311,8 @@ protected void onSuccess(AuthenticateResponse response) { configuration.shouldDeleteRealmOnLogout() ); user.addRealm(configuration.getServerUrl(), desc); + // schedule a token refresh before it expires + scheduleRefreshAccessToken(response.getAccessToken().expiresMs()); onSuccess.run(); } @@ -290,6 +324,78 @@ protected void onError(AuthenticateResponse response) { networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); } + private void scheduleRefreshAccessToken(long expireDateInMs) { + // calculate the delay time before which we should refresh the access_token, + // we adjust to 10 second to proactively refresh the access_token before the session + // hit the expire date on the token + long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; + if (refreshAfter < 0) { + // Token already expired + RealmLog.debug("Expires time already reached for the access token, refreshing now"); + refreshAccessToken(); + + } else { + RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); + if (refreshTokenTask != null) { + refreshTokenTask.cancel(); + } + + ScheduledFuture task = REFRESH_TOKENS_EXECUTOR.schedule(new Runnable() { + @Override + public void run() { + refreshAccessToken(); + } + }, refreshAfter, TimeUnit.MILLISECONDS); + refreshTokenTask = new RealmAsyncTaskImpl(task, REFRESH_TOKENS_EXECUTOR); + } + } + + // Authenticate by getting access tokens for the specific Realm + private void refreshAccessToken() { + // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. + if (refreshTokenNetworkRequest != null) { + refreshTokenNetworkRequest.cancel(); + } + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { + @Override + protected AuthenticateResponse execute() { + return authServer.refreshUser(user.getUserToken(), configuration.getServerUrl(), user.getAuthenticationUrl()); + } + + @Override + protected void onSuccess(AuthenticateResponse response) { + synchronized (ObjectServerSession.this) { + RealmLog.debug("Access Token refreshed successfully"); + if (updateSessionAccessToken(response.getAccessToken().value())) { + RealmLog.debug("Token applied"); + // only schedule an update if the token was updated. + // The callback might return will the session state is not BOUND + // in this case we'll wait for the new session state to transition to + // BOUND, which will schedule a refresh in the process + + // this will also avoid updating a stopped session + + // replaced the user old access_token + ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription( + response.getAccessToken(), + configuration.getPath(), + configuration.shouldDeleteRealmOnLogout() + ); + user.addRealm(configuration.getServerUrl(), desc); + // schedule the next refresh + scheduleRefreshAccessToken(response.getAccessToken().expiresMs()); + } + } + } + + @Override + protected void onError(AuthenticateResponse response) { + RealmLog.error("Unrecoverable error, while refreshing the access Token (" + response.getError().toString() + ") reschedule will not happen"); + } + }); + refreshTokenNetworkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); + } + /** * Checks if a user has valid credentials for accessing this Realm. * From e318dca510b807495550820643fa5c0938e13acd Mon Sep 17 00:00:00 2001 From: Realm CI Date: Thu, 9 Feb 2017 00:01:22 +0100 Subject: [PATCH 0485/2110] Fix merge from 97fdf6 to master (#4156) * Release v2.3.1 * Prepare next release v2.3.2-SNAPSHOT * Nh/refresh access token (#4147) * Add a timer to refresh the access_token before it expires --- CHANGELOG.md | 6 +- .../io/realm/AuthenticateRequestTests.java | 9 +- .../src/main/java/io/realm/RealmResults.java | 2 +- .../objectServer/java/io/realm/SyncUser.java | 2 +- .../internal/network/AuthenticateRequest.java | 4 +- .../network/AuthenticationServer.java | 2 +- .../network/ExponentialBackoffTask.java | 2 +- .../network/OkHttpAuthenticationServer.java | 4 +- .../objectserver/ObjectServerSession.java | 108 +++++++++++++++++- version.txt | 1 + 10 files changed, 126 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f6eaa4093..fd20927796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,13 @@ * Bug causing classes to be replaced by classes already in Gradle's classpath (#3568). * NullPointerException when notifying a single object that it changed (#4086). +### Enhancements + +* [ObjectServer] Add a timer to refresh periodically the access_token. + ## 2.3.0 -### Object Server API Changes +### Object Server API Changes * Realm Sync v1.0.0 has been released, and Realm Mobile Platform is no longer considered in beta. * Breaking change: Location of Realm files are now placed in `getFilesDir()/` instead of `getFilesDir()/`. diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index a9646a0bea..54a87b468c 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.when; @@ -39,10 +40,10 @@ public void setUp() { @Test public void realmLogin() throws URISyntaxException, JSONException { Token t = SyncTestUtils.createTestUser().getSyncUser().getUserToken(); - AuthenticateRequest request = AuthenticateRequest.realmLogin(t, new URI("realm://objectserver/" + t.value() + "/default")); + AuthenticateRequest request = AuthenticateRequest.realmLogin(t, new URI("realm://objectserver/" + t.identity() + "/default")); JSONObject obj = new JSONObject(request.toJson()); - assertEquals("/" + t.value() + "/default", obj.get("path")); + assertEquals("/" + t.identity() + "/default", obj.get("path")); assertEquals(t.value(), obj.get("data")); assertEquals("realm", obj.get("provider")); } @@ -60,10 +61,10 @@ public void userLogin() throws URISyntaxException, JSONException { @Test public void userRefresh() throws URISyntaxException, JSONException { Token t = SyncTestUtils.createTestUser().getSyncUser().getUserToken(); - AuthenticateRequest request = AuthenticateRequest.userRefresh(t); + AuthenticateRequest request = AuthenticateRequest.userRefresh(t, new URI("realm://objectserver/" + t.identity() + "/default")); JSONObject obj = new JSONObject(request.toJson()); - assertFalse(obj.has("path")); + assertTrue(obj.has("path")); assertEquals(t.value(), obj.get("data")); assertEquals("realm", obj.get("provider")); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 069a5c4044..0624795949 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -789,7 +789,7 @@ public void remove() { throw new UnsupportedOperationException("remove() is not supported by RealmResults iterators."); } - protected void checkRealmIsStable() { + void checkRealmIsStable() { long version = table.getVersion(); // Any change within a write transaction will immediately update the table version. This means that we // cannot depend on the tableVersion heuristic in that case. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index b4e93bcefa..eac47154eb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -361,7 +361,7 @@ public String toJson() { * Returns {@code true} if the user is logged into the Realm Object Server. If this method returns {@code true} it * implies that the user has valid credentials that have not expired. *

          - * The user might still be have been logged out by the Realm Object Server which will not be detected before the + * The user might still have been logged out by the Realm Object Server which will not be detected before the * user tries to actively synchronize a Realm. If a logged out user tries to synchronize a Realm, an error will be * reported to the {@link SyncSession.ErrorHandler} defined by * {@link SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler)}. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java index 6b27dfc7d7..e921daec6c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java @@ -56,11 +56,11 @@ public static AuthenticateRequest userLogin(SyncCredentials credentials) { /** * Generates a request for refreshing a user token. */ - public static AuthenticateRequest userRefresh(Token userToken) { + public static AuthenticateRequest userRefresh(Token userToken, URI serverUrl) { return new AuthenticateRequest("realm", userToken.value(), SyncManager.APP_ID, - null, + serverUrl.getPath(), Collections.emptyMap() ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java index b217269ad7..3b74558cc6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java @@ -48,7 +48,7 @@ public interface AuthenticationServer { * Before it expires, the client should try to refresh the token, effectively keeping the user logged in on the * Object Server. Failing to do so will cause a "soft logout", where the User will have limited access rights. */ - AuthenticateResponse refreshUser(Token userToken, URL authenticationUrl); + AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL authenticationUrl); /** * Logs out the user on the Object Server by invalidating the refresh token. Each device should be given their diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java index aa613c8357..ab53fff060 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java @@ -44,7 +44,7 @@ protected boolean shouldAbortTask(T response) { } } - // Callback when task is have succeeded + // Callback when task have succeeded protected abstract void onSuccess(T response); // Callback when task has failed diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index fe580d438b..b4bbb18cde 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -67,9 +67,9 @@ public AuthenticateResponse loginToRealm(Token refreshToken, URI serverUrl, URL } @Override - public AuthenticateResponse refreshUser(Token userToken, URL authenticationUrl) { + public AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL authenticationUrl) { try { - String requestBody = AuthenticateRequest.userRefresh(userToken).toJson(); + String requestBody = AuthenticateRequest.userRefresh(userToken, serverUrl).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java index 3d78d24aa5..615e23b5f5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java @@ -19,6 +19,9 @@ import java.net.URI; import java.util.HashMap; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import io.realm.ErrorCode; import io.realm.ObjectServerError; @@ -99,6 +102,8 @@ public final class ObjectServerSession { private long nativeSessionPointer; private final ObjectServerUser user; RealmAsyncTask networkRequest; + private RealmAsyncTask refreshTokenTask; + private RealmAsyncTask refreshTokenNetworkRequest; NetworkStateReceiver.ConnectionListener networkListener; private SyncPolicy syncPolicy; @@ -106,7 +111,9 @@ public final class ObjectServerSession { private SessionState currentStateDescription; private FsmState currentState; private SyncSession userSession; - private SyncSession publicSession; + + private final static ScheduledThreadPoolExecutor REFRESH_TOKENS_EXECUTOR = new ScheduledThreadPoolExecutor(1); + private final static long REFRESH_MARGIN_DELAY = TimeUnit.SECONDS.toMillis(10); /** * Creates a new Object Server Session. @@ -166,6 +173,8 @@ public synchronized void start() { * Stops the session. The session can no longer be used. */ public synchronized void stop() { + // tries to stop any scheduled access_token refresh + clearScheduledAccessTokenRefresh(); currentState.onStop(); } @@ -238,6 +247,25 @@ void stopNativeSession() { nativeUnbind(nativeSessionPointer); nativeSessionPointer = 0; } + clearScheduledAccessTokenRefresh(); + } + + // It is an error to call this function before calling Client::bind() state + private boolean updateSessionAccessToken(String userToken) { + if (nativeSessionPointer != 0 && isBound()) { + nativeRefresh(nativeSessionPointer, userToken); + return true; + } + return false; + } + + private void clearScheduledAccessTokenRefresh() { + if (refreshTokenTask != null) { + refreshTokenTask.cancel(); + } + if (refreshTokenNetworkRequest != null) { + refreshTokenNetworkRequest.cancel(); + } } void removeAccessToken() { @@ -260,6 +288,10 @@ void authenticateRealm(final Runnable onSuccess, final SyncSession.ErrorHandler if (networkRequest != null) { networkRequest.cancel(); } + // clear any previously scheduled refresh access_token + // since we're going to obtain a new refresh_token + clearScheduledAccessTokenRefresh(); + // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { @Override @@ -279,6 +311,8 @@ protected void onSuccess(AuthenticateResponse response) { configuration.shouldDeleteRealmOnLogout() ); user.addRealm(configuration.getServerUrl(), desc); + // schedule a token refresh before it expires + scheduleRefreshAccessToken(response.getAccessToken().expiresMs()); onSuccess.run(); } @@ -290,6 +324,78 @@ protected void onError(AuthenticateResponse response) { networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); } + private void scheduleRefreshAccessToken(long expireDateInMs) { + // calculate the delay time before which we should refresh the access_token, + // we adjust to 10 second to proactively refresh the access_token before the session + // hit the expire date on the token + long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; + if (refreshAfter < 0) { + // Token already expired + RealmLog.debug("Expires time already reached for the access token, refreshing now"); + refreshAccessToken(); + + } else { + RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); + if (refreshTokenTask != null) { + refreshTokenTask.cancel(); + } + + ScheduledFuture task = REFRESH_TOKENS_EXECUTOR.schedule(new Runnable() { + @Override + public void run() { + refreshAccessToken(); + } + }, refreshAfter, TimeUnit.MILLISECONDS); + refreshTokenTask = new RealmAsyncTaskImpl(task, REFRESH_TOKENS_EXECUTOR); + } + } + + // Authenticate by getting access tokens for the specific Realm + private void refreshAccessToken() { + // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. + if (refreshTokenNetworkRequest != null) { + refreshTokenNetworkRequest.cancel(); + } + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { + @Override + protected AuthenticateResponse execute() { + return authServer.refreshUser(user.getUserToken(), configuration.getServerUrl(), user.getAuthenticationUrl()); + } + + @Override + protected void onSuccess(AuthenticateResponse response) { + synchronized (ObjectServerSession.this) { + RealmLog.debug("Access Token refreshed successfully"); + if (updateSessionAccessToken(response.getAccessToken().value())) { + RealmLog.debug("Token applied"); + // only schedule an update if the token was updated. + // The callback might return will the session state is not BOUND + // in this case we'll wait for the new session state to transition to + // BOUND, which will schedule a refresh in the process + + // this will also avoid updating a stopped session + + // replaced the user old access_token + ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription( + response.getAccessToken(), + configuration.getPath(), + configuration.shouldDeleteRealmOnLogout() + ); + user.addRealm(configuration.getServerUrl(), desc); + // schedule the next refresh + scheduleRefreshAccessToken(response.getAccessToken().expiresMs()); + } + } + } + + @Override + protected void onError(AuthenticateResponse response) { + RealmLog.error("Unrecoverable error, while refreshing the access Token (" + response.getError().toString() + ") reschedule will not happen"); + } + }); + refreshTokenNetworkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); + } + /** * Checks if a user has valid credentials for accessing this Realm. * diff --git a/version.txt b/version.txt index b4308ebebb..1097648139 100644 --- a/version.txt +++ b/version.txt @@ -1 +1,2 @@ + 2.4.0-SNAPSHOT From f6343b09e9f97556121e22f159c18a24a12ac43d Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Thu, 9 Feb 2017 09:45:31 +0100 Subject: [PATCH 0486/2110] Use transitive dependencies in CMake (#4158) --- realm/realm-library/src/main/cpp/CMakeLists.txt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index dadf5648c1..5b84b34a76 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -77,7 +77,11 @@ if (NOT EXISTS ${core_lib_PATH}) endif() add_library(lib_realm_core STATIC IMPORTED) -set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_PATH}) + +# -latomic is not set by default for mips and armv5. +# See https://code.google.com/p/android/issues/detail?id=182094 +set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_PATH} + IMPORTED_LINK_INTERFACE_LIBRARIES atomic) # Sync static library set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-${ANDROID_ABI}.a) @@ -94,7 +98,8 @@ if (NOT EXISTS ${sync_lib_PATH}) endif() endif() add_library(lib_realm_sync STATIC IMPORTED) -set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_PATH}) +set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_PATH} + IMPORTED_LINK_INTERFACE_LIBRARIES lib_realm_core) # build application's shared lib include_directories(${REALM_CORE_DIST_DIR}/include @@ -172,12 +177,11 @@ endif() add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) add_dependencies(realm-jni jni_headers) -# -latomic is not set by default for mips. See https://code.google.com/p/android/issues/detail?id=182094 + if (build_SYNC) -# FIXME: The order matters! lib_realm_sync needs to be in front of lib_realm_core!! Find out why!! -target_link_libraries(realm-jni log android atomic lib_realm_sync lib_realm_core) + target_link_libraries(realm-jni log android lib_realm_sync) else() -target_link_libraries(realm-jni log android atomic lib_realm_core) + target_link_libraries(realm-jni log android lib_realm_core) endif() # Strip the release so files and backup the unstripped versions From 5c0f0b0ad6fc63ae3600bde9ccfc96ac44aecd48 Mon Sep 17 00:00:00 2001 From: Realm CI Date: Thu, 9 Feb 2017 09:47:59 +0100 Subject: [PATCH 0487/2110] Fix merge from f6343b to master (#4161) * Release v2.3.1 * Prepare next release v2.3.2-SNAPSHOT * Nh/refresh access token (#4147) * Add a timer to refresh the access_token before it expires * Use transitive dependencies in CMake (#4158) --- realm/realm-library/src/main/cpp/CMakeLists.txt | 16 ++++++++++------ version.txt | 3 +-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index dadf5648c1..5b84b34a76 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -77,7 +77,11 @@ if (NOT EXISTS ${core_lib_PATH}) endif() add_library(lib_realm_core STATIC IMPORTED) -set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_PATH}) + +# -latomic is not set by default for mips and armv5. +# See https://code.google.com/p/android/issues/detail?id=182094 +set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_PATH} + IMPORTED_LINK_INTERFACE_LIBRARIES atomic) # Sync static library set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-${ANDROID_ABI}.a) @@ -94,7 +98,8 @@ if (NOT EXISTS ${sync_lib_PATH}) endif() endif() add_library(lib_realm_sync STATIC IMPORTED) -set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_PATH}) +set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_PATH} + IMPORTED_LINK_INTERFACE_LIBRARIES lib_realm_core) # build application's shared lib include_directories(${REALM_CORE_DIST_DIR}/include @@ -172,12 +177,11 @@ endif() add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) add_dependencies(realm-jni jni_headers) -# -latomic is not set by default for mips. See https://code.google.com/p/android/issues/detail?id=182094 + if (build_SYNC) -# FIXME: The order matters! lib_realm_sync needs to be in front of lib_realm_core!! Find out why!! -target_link_libraries(realm-jni log android atomic lib_realm_sync lib_realm_core) + target_link_libraries(realm-jni log android lib_realm_sync) else() -target_link_libraries(realm-jni log android atomic lib_realm_core) + target_link_libraries(realm-jni log android lib_realm_core) endif() # Strip the release so files and backup the unstripped versions diff --git a/version.txt b/version.txt index 1097648139..855ff9501e 100644 --- a/version.txt +++ b/version.txt @@ -1,2 +1 @@ - -2.4.0-SNAPSHOT +2.4.0-SNAPSHOT \ No newline at end of file From 4131635e4a0825794d01a6663e1252781cc14aae Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 9 Feb 2017 19:17:37 +0800 Subject: [PATCH 0488/2110] Don't remove callback in AndroidRealmNotifier We still have to ensure the async transaction callbacks delivered. --- .../src/main/java/io/realm/internal/RealmNotifier.java | 6 ++++-- .../src/main/java/io/realm/internal/SharedRealm.java | 3 +++ .../io/realm/internal/android/AndroidRealmNotifier.java | 9 --------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 6ebd3d3706..4635589afe 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -59,12 +59,14 @@ private void onChange(T observer) { } private ObserverPairList realmObserverPairs = new ObserverPairList(); - private final static ObserverPairList.Callback onChangeCallBack = + private final ObserverPairList.Callback onChangeCallBack = new ObserverPairList.Callback() { @Override public void onCalled(RealmObserverPair pair, Object observer) { //noinspection unchecked - pair.onChange(observer); + if (sharedRealm != null && !sharedRealm.isClosed()) { + pair.onChange(observer); + } } }; diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 8fcf7c94b5..5daa762ef9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -422,6 +422,9 @@ private void detachCollections() { // 2) It happens before Object Store async callbacks since the Object Store event_loop_signal might use a different // event queue. This is guaranteed by call this function in the binding_context::before_notify callback. void reattachCollections() { + if (isClosed()) { + return; + } if (isInTransaction()) { // This should never happen. throw new IllegalStateException( "Collection cannot be reattached if the Realm is in transaction." + diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java index c33451c82d..4eed44df0b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java @@ -33,13 +33,4 @@ public boolean postAtFrontOfQueue(Runnable runnable) { public boolean post(Runnable runnable) { return handler != null && handler.post(runnable); } - - @Override - public void close() { - super.close(); - if (handler != null) { - handler.removeCallbacksAndMessages(null); - handler = null; - } - } } From 7f50cf009ae4da19561b0673665141f7b6a045c9 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 9 Feb 2017 20:01:38 +0800 Subject: [PATCH 0489/2110] Fix flaky test --- .../androidTest/java/io/realm/internal/RealmNotifierTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index bb1f9ecc48..e4f25104bf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -145,6 +145,7 @@ public void addChangeListener_byRemoteChanges() { looperThread.realm.close(); SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); + looperThread.keepStrongReference.add(sharedRealm); sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { From 94a8422b91e0551757ea2d16e75b8964f4f6d1ab Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 8 Feb 2017 12:25:59 +0800 Subject: [PATCH 0490/2110] RealmResults is always live-to-updated This is the precondition of fine grained notifications. OS will trigger the collection notification immediately when transaction begins on the local thread to compute the change set if there is any. This conflicts with Java's original RealmResults behavior -- the original RealmResults would only be synced in the next event loop. Also, there are some edge cases don't work well with the original RealmResults behavior, see details in #3833. So: - RealmResults becomes always up-to-date again which means it will never contains a invalid row. - Behavior of iteration on a RealmResults still just works, it will just iterate on snapshot of collection. This means user can still delete elements from a RealmResults inside iteration. - Deletion & Modification on RealmResults in simple-for-loop won't work as expected if the changes will impact the order/elements of the results. This could be solved by the future new Collection type RealmCollectionSnapshot. - Add Collection.load() and Collection.isLoaded() to support java sync queries. - Test fix. - https://github.com/realm/realm-android-adapters/issues/48 won't be an issue anymore since the RealmResults is always up to date and it won't contain any invalid rows. So remove the related tests. - Failure tests caused by listener being triggred with beginTransaction() - Remove realmResultsListenerAddedAfterCommit. when add listener to the OS Results after commit transaction, the OS CollectionNotifier will be created at the SharedGroup version of transaction committed. So the listener won't be called anymore since the all changes already exist in current SharedGroup. --- CHANGELOG.md | 1 + .../java/io/realm/NotificationsTest.java | 158 ---------- .../OrderedRealmCollectionIteratorTests.java | 19 +- .../java/io/realm/RealmAsyncQueryTests.java | 30 +- .../java/io/realm/RealmObjectTests.java | 2 + .../java/io/realm/RealmQueryTests.java | 4 +- .../java/io/realm/RealmResultsTests.java | 10 +- .../io/realm/TypeBasedNotificationsTests.java | 2 +- .../io/realm/internal/CollectionTests.java | 275 ++++++------------ .../io/realm/internal/RealmNotifierTests.java | 19 -- .../main/cpp/io_realm_internal_Collection.cpp | 158 +++------- .../src/main/java/io/realm/RealmResults.java | 115 +------- .../java/io/realm/internal/Collection.java | 233 +++++++++++---- .../java/io/realm/internal/PendingRow.java | 2 +- .../java/io/realm/internal/RealmNotifier.java | 9 +- .../java/io/realm/internal/SharedRealm.java | 77 ++--- .../android/AndroidRealmNotifier.java | 5 - 17 files changed, 391 insertions(+), 728 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e1589f462..477e43ac64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Breaking changes * `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object (#2947). +* `RealmResults` is auto-updated all the time. Any transaction on the caller thread which may have impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. Iterator behavior of `RealmResults` stays the same, transaction inside the iterating still works as expected. ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 0f276e8dd9..ebfbb16f79 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -796,24 +796,6 @@ public void run() { }); } - @Test - @RunTestInLooperThread - public void realmResultsListenerAddedAfterCommit() { - Realm realm = looperThread.realm; - RealmResults results = realm.where(AllTypes.class).findAll(); - realm.beginTransaction(); - realm.createObject(AllTypes.class); - realm.commitTransaction(); - - looperThread.keepStrongReference.add(results); - results.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - looperThread.testComplete(); - } - }); - } - // TODO: Fix or delete this test after integration of object notification from Object Store @Test @RunTestInLooperThread @@ -879,146 +861,6 @@ public void onChange(Realm element) { }); } - - // See https://github.com/realm/realm-android-adapters/issues/48. - // Step 1: Populates the db. - // Step 2: Posts a runnable to caller thread. - // Event Queue: |Posted Runnable| <- TOP - // Step 3: Deletes object which will make the results contain an invalid object at this moment - // Right Event Queue: |Reattach | Wrong Event Queue: |Posted Runnable | <- TOP - // |Posted Runnable| |REALM_CHANGED/Reattach| - // Step 4: Posted runnable called. - @Test - @RunTestInLooperThread(/*step1*/ before = PopulateOneAllTypes.class) - public void realmListener_reattachResultsShouldHappenFirst() { - final Realm realm = looperThread.realm; - final RealmResults results = realm.where(AllTypes.class).findAll(); - assertEquals(1, results.size()); - - // Step 2 - // The transaction later will trigger the results sync, and it should be run before this runnable. - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - // Step 4 - assertEquals(0, results.size()); - realm.close(); - looperThread.testComplete(); - } - }); - - // Step 3 - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - AllTypes allTypes = realm.where(AllTypes.class).findFirst(); - assertNotNull(allTypes); - allTypes.deleteFromRealm(); - assertEquals(0, realm.where(AllTypes.class).count()); - assertFalse(results.get(0).isValid()); - } - }); - } - - // See https://github.com/realm/realm-android-adapters/issues/48. - // Step 1: Populates the db. - // Step 2: Creates a async query, and waits until it finishes. - // Step 3: Posts a runnable to caller thread. - // Event Queue: |Posted Runnable| <- TOP - // Step 4: Deletes object which will make the results contain a invalid object at this moment - // Right Event Queue: |Reattach | Wrong Event Queue: |Posted Runnable | <- TOP - // |Posted Runnable| |REALM_CHANGED/Reattach| - // Step 5: Posted runnable called. - @Test - @RunTestInLooperThread(/*step1*/before = PopulateOneAllTypes.class) - public void realmListener_reattachResultsShouldHappenFirstWithReturnedAsync() { - final AtomicBoolean changedFirstTime = new AtomicBoolean(false); - final Realm realm = looperThread.realm; - final RealmResults asyncResults = realm.where(AllTypes.class).findAll(); - final RealmResults results = realm.where(AllTypes.class).findAll(); - - assertEquals(1, results.size()); - - looperThread.keepStrongReference.add(asyncResults); - asyncResults.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults element) { - if (!changedFirstTime.get()) { - // Step 2 - // The transaction later will trigger the results sync, and it should be run before this runnable. - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - // Step 5 - assertEquals(0, asyncResults.size()); - assertEquals(0, results.size()); - looperThread.testComplete(); - } - }); - - // Step 3 - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - AllTypes allTypes = realm.where(AllTypes.class).findFirst(); - assertNotNull(allTypes); - allTypes.deleteFromRealm(); - assertEquals(0, realm.where(AllTypes.class).count()); - assertFalse(results.get(0).isValid()); - } - }); - changedFirstTime.set(true); - } - } - }); - } - - // See https://github.com/realm/realm-android-adapters/issues/48 - // Step 1: Populate the db - // Step 2: Create a async query - // Step 3: Add listener to the async results - // Event Queue: |async callback| <- TOP - // Step 4: Deletes object which will make the results contain a invalid object at this moment - // Right calling order: |Reattach | Wrong order: |async callback| <- TOP - // |async callback| |Reattach | - // Step 5: Posted runnable called. - // - @Test - @RunTestInLooperThread(/*step1*/before = PopulateOneAllTypes.class) - public void realmListener_reattachResultsShouldHappenFirstNonReturnedAsync() { - final Realm realm = looperThread.realm; - - // Step 2 - final RealmResults asyncResults = realm.where(AllTypes.class).findAll(); - final RealmResults results = realm.where(AllTypes.class).findAll(); - - assertEquals(1, results.size()); - - // Step 3 - looperThread.keepStrongReference.add(asyncResults); - asyncResults.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults element) { - // Step 5 - assertEquals(0, asyncResults.size()); - assertEquals(0, results.size()); - looperThread.testComplete(); - } - }); - - // Step 4 - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - AllTypes allTypes = realm.where(AllTypes.class).findFirst(); - assertNotNull(allTypes); - allTypes.deleteFromRealm(); - assertEquals(0, realm.where(AllTypes.class).count()); - assertFalse(results.get(0).isValid()); - } - }); - } - @Test @RunTestInLooperThread public void accessingSyncRealmResultInsideAsyncResultListener() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index 3484cb2c1f..8bb6e551aa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -21,6 +21,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -283,13 +284,12 @@ public void iterator_deleteManagedObjectIndirectly() { // Managed RealmLists are directly associated with their table. Thus any indirect deletion will // also remove it from the LinkView. case MANAGED_REALMLIST: + case REALMRESULTS: assertEquals(TEST_SIZE - 1, collection.size()); break; - // Unmanaged collections are not affected by changes to Realm and RealmResult should maintain a stable - // view until next time sync_if_needed is called. + // Unmanaged collections are not affected by changes to Realm. case UNMANAGED_REALMLIST: - case REALMRESULTS: assertEquals(TEST_SIZE, collection.size()); break; @@ -515,23 +515,22 @@ public void listIterator_closedRealm_methods() { @Test public void listIterator_deleteManagedObjectIndirectly() { realm.beginTransaction(); - Iterator it = collection.iterator(); + ListIterator it = collection.listIterator(); it.next(); it.next().deleteFromRealm(); realm.commitTransaction(); switch (collectionClass) { case MANAGED_REALMLIST: + case REALMRESULTS: assertEquals(TEST_SIZE - 1, collection.size()); break; case UNMANAGED_REALMLIST: - case REALMRESULTS: assertEquals(TEST_SIZE, collection.size()); break; } - it = collection.listIterator(); - it.next(); - AllJavaTypes types = it.next(); // Iterator can still access the deleted object. + it.previous(); + AllJavaTypes types = it.next(); // Iterator can still access the deleted object //noinspection SimplifiableConditionalExpression assertTrue(collectionClass == CollectionClass.MANAGED_REALMLIST ? types.isValid() : !types.isValid()); @@ -801,6 +800,7 @@ public void run() { } @Test + @Ignore("Enable this test when support RealmCollectionSnapshot") public void useCase_simpleIterator_modifyQueryResult_innerTransaction() { if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { return; @@ -820,6 +820,7 @@ public void useCase_simpleIterator_modifyQueryResult_innerTransaction() { } @Test + @Ignore("Enable this test when support RealmCollectionSnapshot") public void useCase_simpleIterator_modifyQueryResult_outerTransaction() { if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { return; @@ -876,6 +877,7 @@ public void useCase_forEachIterator_modifyQueryResult_outerTransaction() { @Test @UiThreadTest + @Ignore("Enable this test when support RealmCollectionSnapshot") public void useCase_simpleIterator_modifyQueryResult_innerTransaction_looperThread() { if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { return; @@ -896,6 +898,7 @@ public void useCase_simpleIterator_modifyQueryResult_innerTransaction_looperThre @Test @UiThreadTest + @Ignore("Enable this test when support RealmCollectionSnapshot") public void useCase_simpleIterator_modifyQueryResult_outerTransaction_looperThread() { if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { return; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index c69b4130ef..db5285ecce 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -696,8 +696,9 @@ public void combiningAsyncAndSync() { final RealmResults allTypesAsync = looperThread.realm.where(AllTypes.class).greaterThan("columnLong", 5).findAllAsync(); final RealmResults allTypesSync = allTypesAsync.where().greaterThan("columnLong", 3).findAll(); - // Call where() on an async results will load the async query immediately. - assertEquals(4, allTypesAsync.size()); + // Call where() on an async results will load query. But to maintain the original behaviour of + // RealmResults.load(), we still treat it as a not loaded results. + assertEquals(0, allTypesAsync.size()); assertEquals(4, allTypesSync.size()); // columnLong > 5 && columnLong > 3 allTypesAsync.addChangeListener(new RealmChangeListener>() { @Override @@ -1095,7 +1096,7 @@ public void doInBackground(Realm realm) { @Test @RunTestInLooperThread public void badVersion_syncTransaction() throws NoSuchFieldException, IllegalAccessException { - TestHelper.replaceRealmThreadExecutor(RealmThreadPoolExecutor.newSingleThreadExecutor()); + final AtomicInteger listenerCount = new AtomicInteger(0); Realm realm = looperThread.realm; // 1. Makes sure that async query is not started. @@ -1104,23 +1105,36 @@ public void badVersion_syncTransaction() throws NoSuchFieldException, IllegalAcc result.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { - // 4. The commit in #2, should result in a refresh being triggered, which means this callback will - // be notified once the updated async queries has run. assertTrue(result.isValid()); assertTrue(result.isLoaded()); - assertEquals(1, result.size()); - looperThread.testComplete(); + switch (listenerCount.getAndIncrement()) { + case 0: + // Triggered by beginTransaction + assertEquals(0, result.size()); + break; + case 1: + // 4. The commit in #2, should result in a refresh being triggered, which means this callback will + // be notified once the updated async queries has run. + assertEquals(1, result.size()); + looperThread.testComplete(); + break; + default: + fail(); + break; + } } }); // 2. Advances the caller Realm, invalidating the version in the handover object. realm.beginTransaction(); + assertTrue(result.isLoaded()); realm.createObject(AllTypes.class); realm.commitTransaction(); // 3. The async query should now (hopefully) fail with a BadVersion. + // NOTE: Step 3 is from the original test. After integration of Object Store Results, it has been loaded already + // when beginTransaction. result.load(); - TestHelper.resetRealmThreadExecutor(); } // This test reproduces the issue in https://secure.helpscout.net/conversation/244053233/6163/?folderId=366141 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 7fb3542813..8e8f6a383e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -22,6 +22,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -295,6 +296,7 @@ private void removeOneByOne(boolean removeFromFront) { // Tests calling deleteFromRealm on a RealmResults instead of RealmResults.remove(). @Test + @Ignore("Enable this test when implementing RealmCollectionSnapshot") public void deleteFromRealm_atPosition() { removeOneByOne(REMOVE_FIRST); removeOneByOne(REMOVE_LAST); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 2d335a1750..f70b39ab96 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2989,9 +2989,9 @@ public void distinctAsync_withNullValues() throws Throwable { populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); + .distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING); + .distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); final Runnable endTest = new Runnable() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 4e1629d64a..a6757ee1a5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -104,8 +104,8 @@ public void findFirst() { public void size_returns_Integer_MAX_VALUE_for_huge_results() { final Collection collection = Mockito.mock(Collection.class); final RealmResults targetResult = TestHelper.newRealmResults(realm, collection, AllTypes.class); - targetResult.load(); + Mockito.when(collection.isLoaded()).thenReturn(true); Mockito.when(collection.size()).thenReturn(((long) Integer.MAX_VALUE) - 1); assertEquals(Integer.MAX_VALUE - 1, targetResult.size()); Mockito.when(collection.size()).thenReturn(((long) Integer.MAX_VALUE)); @@ -848,13 +848,7 @@ public void execute(Realm realm) { // Step 3 assertEquals(true, dogs.isValid()); - assertEquals(5, dogs.size()); - assertEquals("name_0", dogs.first().getName()); - assertEquals("name_4", dogs.last().getName()); - assertEquals(0, dogs.min(Dog.FIELD_AGE).intValue()); - assertEquals(4, dogs.max(Dog.FIELD_AGE).intValue()); - assertEquals(new Date(0), dogs.minDate(Dog.FIELD_BIRTHDAY)); - assertEquals(new Date(4), dogs.maxDate(Dog.FIELD_BIRTHDAY)); + assertEquals(0, dogs.size()); // The link view has been deleted. assertEquals(0, dogs.where().findAll().size()); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index a950f32014..d6af71de24 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -624,7 +624,7 @@ public void callback_with_relevant_commit_realmresults_async() { akamaru.setName("Akamaru"); realm.commitTransaction(); - final RealmResults dogs = realm.where(Dog.class).findAll(); + final RealmResults dogs = realm.where(Dog.class).findAllAsync(); looperThread.keepStrongReference.add(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index bf7d164526..a8473b1255 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -26,8 +26,11 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.lang.ref.WeakReference; +import java.util.ConcurrentModificationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import io.realm.RealmChangeListener; import io.realm.RealmConfiguration; @@ -40,6 +43,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -285,13 +289,21 @@ public void onChange(Collection collection1) { @Test public void addListener_shouldBeCalledWhenRefreshAfterLocalCommit() { - final CountDownLatch latch = new CountDownLatch(1); - Collection collection = new Collection(sharedRealm, table.where()); + final CountDownLatch latch = new CountDownLatch(2); + final Collection collection = new Collection(sharedRealm, table.where()); assertEquals(4, collection.size()); // See `populateData()` collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection element) { - assertEquals(latch.getCount(), 1); + if (latch.getCount() == 2) { + // triggered by beginTransaction + assertEquals(4, collection.size()); + } else if (latch.getCount() == 1) { + // triggered by refresh + assertEquals(5, collection.size()); + } else { + fail(); + } latch.countDown(); } }); @@ -302,8 +314,9 @@ public void onChange(Collection element) { TestHelper.awaitOrFail(latch); } + // Local commit will trigger the listener first when beginTransaction gets called then again when call refresh. @Test - public void addListener_shouldBeCalledByWaitForChangeThenRefresh() { + public void addListener_triggeredByRefresh() { final CountDownLatch latch = new CountDownLatch(1); Collection collection = new Collection(sharedRealm, table.where()); collection.size(); @@ -365,249 +378,143 @@ public void onChange(Collection collection1) { addRowAsync(); } - // The query has not been executed. - // Local commit won't trigger the listener immediately. Instead, the notification comes after the background commit. + // Local commit will trigger the listener first when beginTransaction gets called then again in the next event loop. @Test @RunTestInLooperThread - public void addListener_queryNotReturnedLocalAndRemoteCommit() { + public void addListener_triggeredByLocalCommit() { final SharedRealm sharedRealm = getSharedRealm(); Table table = sharedRealm.getTable("test_table"); + final AtomicInteger listenerCounter = new AtomicInteger(0); final Collection collection = new Collection(sharedRealm, table.where()); looperThread.keepStrongReference.add(collection); collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { - assertEquals(collection1, collection); - assertEquals(collection1.size(), 6); - sharedRealm.close(); - looperThread.testComplete(); + switch (listenerCounter.getAndIncrement()) { + case 0: + assertEquals(collection1.size(), 4); + break; + case 1: + assertEquals(collection1.size(), 5); + sharedRealm.close(); + looperThread.testComplete(); + break; + } } }); addRow(sharedRealm); - addRowAsync(); + assertEquals(collection.size(), 5); } - // The query has not been executed. - // Local commit will trigger the listener in following event loops. - @Test - @RunTestInLooperThread - public void addListener_queryNotReturnedLocalCommitOnly() { - final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); + private static class TestIterator extends Collection.Iterator { + public TestIterator(Collection collection) { + super(collection); + } - final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); - collection.addListener(collection, new RealmChangeListener() { - @Override - public void onChange(Collection collection1) { - assertEquals(collection1, collection); - assertEquals(collection1.size(), 5); - sharedRealm.close(); - looperThread.testComplete(); - } - }); - addRow(sharedRealm); - } - - // The query has been executed. - // Local commit will trigger the listener in following event loops. - @Test - @RunTestInLooperThread - public void addListener_queryReturnedLocalCommitOnly() { - final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); + @Override + protected Integer convertRowToObject(UncheckedRow row) { + return null; + } - final Collection collection = new Collection(sharedRealm, table.where()); - assertEquals(collection.size(), 4); // Trigger the query to run. - looperThread.keepStrongReference.add(collection); - collection.addListener(collection, new RealmChangeListener() { - @Override - public void onChange(Collection collection1) { - assertEquals(collection1, collection); - assertEquals(collection1.size(), 5); - sharedRealm.close(); - looperThread.testComplete(); + boolean isDetached(SharedRealm sharedRealm) { + for (WeakReference iteratorRef : sharedRealm.iterators) { + Collection.Iterator iterator = iteratorRef.get(); + if (iterator == this) { + return false; + } } - }); - addRow(sharedRealm); - assertEquals(collection.size(), 4); + return true; + } } @Test - public void detach_byBeginTransaction() { - final Collection collection = new Collection(sharedRealm, table.where()); - assertFalse(collection.isDetached()); - assertEquals(collection.size(), 4); - addRowAsync(); - // beginTransaction will do advance read, but the table view should stay without changes. - sharedRealm.beginTransaction(); - assertTrue(collection.isDetached()); - assertEquals(collection.size(), 4); - } - - @Test - public void detach_newCollectionCreatedInTransaction() { - sharedRealm.beginTransaction(); - final Collection collection = new Collection(sharedRealm, table.where()); - assertTrue(collection.isDetached()); - } - - @Test - public void detach_commitTransactionWontReattach() { + public void collectionIterator_detach_byBeginTransaction() { final Collection collection = new Collection(sharedRealm, table.where()); + TestIterator iterator = new TestIterator(collection); + assertFalse(iterator.isDetached(sharedRealm)); sharedRealm.beginTransaction(); + assertTrue(iterator.isDetached(sharedRealm)); sharedRealm.commitTransaction(); - assertTrue(collection.isDetached()); - assertEquals(collection.size(), 4); + assertTrue(iterator.isDetached(sharedRealm)); } @Test - public void detach_cancelTransactionWontReattach() { - final Collection collection = new Collection(sharedRealm, table.where()); + public void collectionIterator_detach_createdInTransaction() { sharedRealm.beginTransaction(); - sharedRealm.cancelTransaction(); - assertTrue(collection.isDetached()); - assertEquals(collection.size(), 4); - } - - @Test - public void reattach_nonLooperThread_byRefresh() { final Collection collection = new Collection(sharedRealm, table.where()); - assertEquals(collection.size(), 4); - addRow(sharedRealm); - // The results is backed by snapshot now. - assertTrue(collection.isDetached()); - assertEquals(collection.size(), 4); - sharedRealm.refresh(); - // The results is switched back to the original Results. - assertFalse(collection.isDetached()); - assertEquals(collection.size(), 5); + TestIterator iterator = new TestIterator(collection); + assertTrue(iterator.isDetached(sharedRealm)); } @Test - @RunTestInLooperThread - public void reattach_looperThread_byLocalTransaction() { - final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); + public void collectionIterator_invalid_nonLooperThread_byRefresh() { final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); - assertFalse(collection.isDetached()); - assertEquals(collection.size(), 4); - collection.addListener(collection, new RealmChangeListener() { - @Override - public void onChange(Collection col) { - assertFalse(col.isDetached()); - assertEquals(col.size(), 5); - sharedRealm.close(); - looperThread.testComplete(); - } - }); - addRow(sharedRealm); - // The results is backed by snapshot now. - assertTrue(collection.isDetached()); - assertEquals(collection.size(), 4); + TestIterator iterator = new TestIterator(collection); + assertFalse(iterator.isDetached(sharedRealm)); + sharedRealm.refresh(); + thrown.expect(ConcurrentModificationException.class); + iterator.checkValid(); } @Test @RunTestInLooperThread - public void reattach_looperThread_byRemoteTransaction() { + public void collectionIterator_invalid_looperThread_byRemoteTransaction() { final SharedRealm sharedRealm = getSharedRealm(); Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); + final TestIterator iterator = new TestIterator(collection); looperThread.keepStrongReference.add(collection); - assertFalse(collection.isDetached()); - assertEquals(collection.size(), 4); + assertFalse(iterator.isDetached(sharedRealm)); collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection element) { - assertFalse(collection.isDetached()); - assertEquals(collection.size(), 5); + try { + iterator.checkValid(); + fail(); + } catch (ConcurrentModificationException ignored) { + } sharedRealm.close(); looperThread.testComplete(); } }); - sharedRealm.beginTransaction(); - sharedRealm.commitTransaction(); - // The results is backed by snapshot now. - assertTrue(collection.isDetached()); - assertEquals(collection.size(), 4); addRowAsync(); } @Test - @RunTestInLooperThread - public void reattach_looperThread_shouldHappenBeforeAnyOtherLoopEventWithEmptyLocalTransaction() { - final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); - final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); - assertEquals(collection.size(), 4); - looperThread.postRunnable(new Runnable() { + public void getMode() { + Collection collection = new Collection(sharedRealm, table.where()); + assertTrue(Collection.Mode.QUERY == collection.getMode()); + collection.firstUncheckedRow(); // Run the query + assertTrue(Collection.Mode.TABLEVIEW == collection.getMode()); + } + + @Test + public void createSnapshot() { + Collection collection = new Collection(sharedRealm, table.where()); + Collection snapshot = collection.createSnapshot(); + assertTrue(Collection.Mode.TABLEVIEW == snapshot.getMode()); + thrown.expect(IllegalStateException.class); + snapshot.addListener(snapshot, new RealmChangeListener() { @Override - public void run() { - // The results is switched back to the original Results. - assertFalse(collection.isDetached()); - assertEquals(collection.size(), 4); - sharedRealm.close(); - looperThread.testComplete(); + public void onChange(Collection element) { } }); - sharedRealm.beginTransaction(); - sharedRealm.commitTransaction(); - // The results is backed by snapshot now. - assertTrue(collection.isDetached()); - assertEquals(collection.size(), 4); } @Test @RunTestInLooperThread - public void reattach_looperThread_shouldHappenBeforeAnyOtherLoopEventWithLocalTransactionCanceled() { - final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); - final Collection[] collections = new Collection[2]; - collections[0] = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collections[0]); - assertEquals(collections[0].size(), 4); - looperThread.postRunnable(new Runnable() { + public void load() { + final Collection collection = new Collection(sharedRealm, table.where()); + collection.addListener(collection, new RealmChangeListener() { @Override - public void run() { - // The results is switched back to the original Results. - assertFalse(collections[0].isDetached()); - assertEquals(collections[0].size(), 4); - assertFalse(collections[1].isDetached()); - assertEquals(collections[1].size(), 4); - - sharedRealm.close(); + public void onChange(Collection element) { + assertTrue(collection.isLoaded()); looperThread.testComplete(); } }); - sharedRealm.beginTransaction(); - // The results is backed by snapshot now. - assertTrue(collections[0].isDetached()); - assertEquals(collections[0].size(), 4); - - table.addEmptyRow(); - collections[1] = new Collection(sharedRealm, table.where()); - UncheckedRow row = collections[1].getUncheckedRow(4); - assertTrue(row.isAttached()); - assertEquals(collections[1].size(), 5); - sharedRealm.cancelTransaction(); - - // The results is still backed by snapshot. - assertTrue(collections[0].isDetached()); - assertEquals(collections[0].size(), 4); - assertEquals(collections[1].size(), 5); - row = collections[1].getUncheckedRow(4); - assertFalse(row.isAttached()); - } - - @Test - public void getMode() { - Collection collection = new Collection(sharedRealm, table.where()); - assertTrue(Collection.Mode.QUERY == collection.getMode()); - collection.firstUncheckedRow(); // Run the query - assertTrue(Collection.Mode.TABLEVIEW == collection.getMode()); + assertFalse(collection.isLoaded()); + collection.load(); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index e4f25104bf..1dfc102df6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -82,25 +82,6 @@ public void run() { }); } - @Test - @RunTestInLooperThread - public void postAtFrontOfQueue() { - final RealmNotifier notifier = new AndroidRealmNotifier(null, capabilitiesCanDeliver); - notifier.post(new Runnable() { - @Override - public void run() { - fail(); - } - }); - notifier.postAtFrontOfQueue(new Runnable() { - @Override - public void run() { - looperThread.testComplete(); - notifier.close(); - } - }); - } - // Callback is immediately called when commitTransaction for local changes. @Test @RunTestInLooperThread diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 17a573db50..1d25c3b913 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -17,8 +17,6 @@ #include #include "io_realm_internal_Collection.h" -#include - #include #include @@ -37,9 +35,10 @@ using namespace realm::_impl; struct ResultsWrapper { JavaGlobalWeakRef m_collection_weak_ref; NotificationToken m_notification_token; + Results m_results; - ResultsWrapper(Results&& results) - : m_collection_weak_ref(), m_notification_token(), m_results(results), m_snapshot() {} + ResultsWrapper(Results& results) + : m_collection_weak_ref(), m_notification_token(), m_results(std::move(results)) {} ResultsWrapper(ResultsWrapper&&) = delete; ResultsWrapper& operator=(ResultsWrapper&&) = delete; @@ -48,50 +47,6 @@ struct ResultsWrapper { ResultsWrapper& operator=(ResultsWrapper const&) = delete; ~ResultsWrapper() {} - - inline Results& get_original_results() - { - return m_results; - } - - inline Results& get_results() - { - if (m_snapshot.get_mode() == Results::Mode::Empty) { - return m_results; - } else { - return m_snapshot; - } - } - - inline void switch_to_snapshot() - { - if (m_snapshot.get_mode() == Results::Mode::Empty) { - m_snapshot = m_results.snapshot(); - } - } - - inline void switch_to_origin() - { - if (m_snapshot.get_mode() != Results::Mode::Empty) { - m_snapshot = Results(); - } - } - - // TODO: This is for deletion related APIs on the Collection. This is not efficient at all since it moves and - // and creates TableView. Expose the reference of snapshot's TableView from Results and use that instead. - inline void refresh_snapshot() - { - m_snapshot = m_results.snapshot(); - } - - inline bool is_detached() - { - return m_snapshot.get_mode() != Results::Mode::Empty; - } - -private: - Results m_results; - Results m_snapshot; }; static void finalize_results(jlong ptr); @@ -117,16 +72,26 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong Results results(shared_realm, *query, SortDescriptor(JavaSortDescriptor(env, sort_desc)), SortDescriptor(JavaSortDescriptor(env, distinct_desc))); - auto wrapper = new ResultsWrapper(std::move(results)); - if (shared_realm->is_in_transaction()) { - wrapper->switch_to_snapshot(); - } + auto wrapper = new ResultsWrapper(results); return reinterpret_cast(wrapper); } CATCH_STD() return reinterpret_cast(nullptr); } +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr); + try { + auto wrapper = reinterpret_cast(native_ptr); + auto snapshot_results = wrapper->m_results.snapshot(); + auto snapshot_wrapper = new ResultsWrapper(snapshot_results); + return reinterpret_cast(snapshot_wrapper); + } CATCH_STD(); + return reinterpret_cast(nullptr); +} + JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeContains(JNIEnv *env, jclass, jlong native_ptr, jlong native_row_ptr) { @@ -134,7 +99,7 @@ Java_io_realm_internal_Collection_nativeContains(JNIEnv *env, jclass, jlong nati try { auto wrapper = reinterpret_cast(native_ptr); auto row = reinterpret_cast(native_row_ptr); - size_t index = wrapper->get_results().index_of(*row); + size_t index = wrapper->m_results.index_of(*row); return to_jbool(index != not_found); } CATCH_STD(); return JNI_FALSE; @@ -146,7 +111,7 @@ Java_io_realm_internal_Collection_nativeGetRow(JNIEnv *env, jclass, jlong native TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->get_results().get(static_cast(index)); + auto row = wrapper->m_results.get(static_cast(index)); return reinterpret_cast(new Row(std::move(row))); } CATCH_STD() return reinterpret_cast(nullptr); @@ -158,7 +123,7 @@ Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv *env, jclass, jlong nati TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto optional_row = wrapper->get_results().first(); + auto optional_row = wrapper->m_results.first(); if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } @@ -173,7 +138,7 @@ Java_io_realm_internal_Collection_nativeLastRow(JNIEnv *env, jclass, jlong nativ TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto optional_row = wrapper->get_results().last(); + auto optional_row = wrapper->m_results.last(); if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } @@ -187,9 +152,7 @@ Java_io_realm_internal_Collection_nativeClear(JNIEnv *env, jclass, jlong native_ TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - wrapper->get_results().clear(); - // Refresh snapshot - wrapper->refresh_snapshot(); + wrapper->m_results.clear(); } CATCH_STD() } @@ -199,7 +162,7 @@ Java_io_realm_internal_Collection_nativeSize(JNIEnv *env, jclass, jlong native_p TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - return static_cast(wrapper->get_results().size()); + return static_cast(wrapper->m_results.size()); } CATCH_STD() return 0; } @@ -216,19 +179,19 @@ Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong nat Optional value; switch (agg_func) { case io_realm_internal_Collection_AGGREGATE_FUNCTION_MINIMUM: - value = wrapper->get_results().min(index); + value = wrapper->m_results.min(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_MAXIMUM: - value = wrapper->get_results().max(index); + value = wrapper->m_results.max(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_AVERAGE: - value = wrapper->get_results().average(index); + value = wrapper->m_results.average(index); if (!value) { value = Optional(0.0); } break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_SUM: - value = wrapper->get_results().sum(index); + value = wrapper->m_results.sum(index); break; default: REALM_UNREACHABLE(); @@ -261,8 +224,8 @@ Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_p TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto sorted_result = wrapper->get_results().sort(JavaSortDescriptor(env, sort_desc)); - return reinterpret_cast(new ResultsWrapper(std::move(sorted_result))); + auto sorted_result = wrapper->m_results.sort(JavaSortDescriptor(env, sort_desc)); + return reinterpret_cast(new ResultsWrapper(sorted_result)); } CATCH_STD() return reinterpret_cast(nullptr); } @@ -272,8 +235,8 @@ Java_io_realm_internal_Collection_nativeDistinct(JNIEnv *env, jclass, jlong nati TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto distinct_result = wrapper->get_results().distinct(JavaSortDescriptor(env, distinct_desc)); - return reinterpret_cast(new ResultsWrapper(std::move(distinct_result))); + auto distinct_result = wrapper->m_results.distinct(JavaSortDescriptor(env, distinct_desc)); + return reinterpret_cast(new ResultsWrapper(distinct_result)); } CATCH_STD() return reinterpret_cast(nullptr); } @@ -296,15 +259,12 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; - // It should have been reattached before the callback. - REALM_ASSERT_DEBUG(!wrapper->is_detached()); - wrapper->m_collection_weak_ref.call_with_local_ref(env, [&] (JNIEnv* local_env, jobject collection_obj) { local_env->CallVoidMethod(collection_obj, notify_change_listeners, changes.empty()); }); }; - wrapper->m_notification_token = wrapper->get_original_results().add_notification_callback(cb); + wrapper->m_notification_token = wrapper->m_results.add_notification_callback(cb); } CATCH_STD() } @@ -333,7 +293,7 @@ Java_io_realm_internal_Collection_nativeWhere(JNIEnv *env, jclass, jlong native_ try { auto wrapper = reinterpret_cast(native_ptr); - auto table_view = wrapper->get_original_results().get_tableview(); + auto table_view = wrapper->m_results.get_tableview(); Query *query = new Query(table_view.get_parent(), std::unique_ptr(new TableView(std::move(table_view)))); return reinterpret_cast(query); @@ -349,7 +309,7 @@ Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv *env, jclass, jlong nativ auto wrapper = reinterpret_cast(native_ptr); auto row = reinterpret_cast(row_native_ptr); - return static_cast(wrapper->get_results().index_of(*row)); + return static_cast(wrapper->m_results.index_of(*row)); } CATCH_STD() return npos; } @@ -363,50 +323,20 @@ Java_io_realm_internal_Collection_nativeIndexOfBySourceRowIndex(JNIEnv *env, jcl auto wrapper = reinterpret_cast(native_ptr); auto index = static_cast(source_row_index); - return static_cast(wrapper->get_results().index_of(index)); + return static_cast(wrapper->m_results.index_of(index)); } CATCH_STD() return npos; } -JNIEXPORT void JNICALL -Java_io_realm_internal_Collection_nativeDetach(JNIEnv *env, jclass, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - auto wrapper = reinterpret_cast(native_ptr); - wrapper->switch_to_snapshot(); - } CATCH_STD() -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_Collection_nativeReattach(JNIEnv *env, jclass, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - try { - auto wrapper = reinterpret_cast(native_ptr); - wrapper->switch_to_origin(); - } CATCH_STD() -} - -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_Collection_nativeIsDetached(JNIEnv *, jclass, jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - auto wrapper = reinterpret_cast(native_ptr); - return wrapper->is_detached(); -} - JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv *env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - if (wrapper->get_results().size() > 0) { - wrapper->get_results().get_tableview().remove_last(); - // Refresh snapshot - wrapper->refresh_snapshot(); + if (wrapper->m_results.size() > 0) { + wrapper->m_results.get_tableview().remove_last(); return JNI_TRUE; } } CATCH_STD() @@ -420,10 +350,8 @@ Java_io_realm_internal_Collection_nativeDeleteFirst(JNIEnv *env, jclass, jlong n try { auto wrapper = reinterpret_cast(native_ptr); - if (wrapper->get_results().size() > 0) { - wrapper->get_results().get_tableview().remove(0); - // Refresh snapshot - wrapper->refresh_snapshot(); + if (wrapper->m_results.size() > 0) { + wrapper->m_results.get_tableview().remove(0); return JNI_TRUE; } } CATCH_STD() @@ -437,14 +365,12 @@ Java_io_realm_internal_Collection_nativeDelete(JNIEnv *env, jclass, jlong native try { auto wrapper = reinterpret_cast(native_ptr); - auto view = wrapper->get_results().get_tableview(); + auto view = wrapper->m_results.get_tableview(); size_t size = view.size(); if (index < 0 || index >= static_cast(size)) { throw Results::OutOfBoundsIndexException{static_cast(index), size}; } view.remove(static_cast(index)); - // Refresh snapshot - wrapper->refresh_snapshot(); } CATCH_STD() } @@ -454,7 +380,7 @@ Java_io_realm_internal_Collection_nativeIsValid(JNIEnv *env, jclass, jlong nativ TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - return wrapper->get_results().is_valid(); + return wrapper->m_results.is_valid(); } CATCH_STD() return JNI_FALSE; } @@ -465,7 +391,7 @@ Java_io_realm_internal_Collection_nativeGetMode(JNIEnv *env, jclass, jlong nativ TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - switch (wrapper->get_original_results().get_mode()) { + switch (wrapper->m_results.get_mode()) { case Results::Mode::Empty: return io_realm_internal_Collection_MODE_EMPTY; case Results::Mode::Table: diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index b10ee34953..a4f30d479c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -25,11 +25,9 @@ import java.util.Date; import java.util.Iterator; import java.util.ListIterator; -import java.util.NoSuchElementException; import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; -import io.realm.internal.Row; import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.Collection; @@ -72,7 +70,6 @@ public class RealmResults extends AbstractList implemen String className; // Class name used by DynamicRealmObjects private final Collection collection; - private boolean loadedManually = false; RealmResults(BaseRealm realm, Collection collection, Class clazz) { this.realm = realm; @@ -102,7 +99,7 @@ public boolean isValid() { } /** - * A {@link RealmResults} is always a managed collection. + * A {@link RealmResults} is always a managed iteratorCollection. * * @return {@code true}. * @see RealmCollection#isManaged() @@ -615,114 +612,25 @@ public boolean addAll(@SuppressWarnings("NullableProblems") java.util.Collection // Custom RealmResults iterator. It ensures that we only iterate on a Realm that hasn't changed. private class RealmResultsIterator extends Collection.Iterator { - int pos = -1; - RealmResultsIterator() { - super(collection); - } - - /** - * {@inheritDoc} - */ - public boolean hasNext() { - return pos + 1 < size(); - } - - /** - * {@inheritDoc} - */ - public E next() { - realm.checkIfValid(); - checkRealmIsStable(); - pos++; - if (pos >= size()) { - throw new NoSuchElementException("Cannot access index " + pos + " when size is " + size() + ". Remember to check hasNext() before using next()."); - } - return get(pos); + super(RealmResults.this.collection); } - /** - * Not supported by RealmResults iterators. - * - * @throws UnsupportedOperationException - */ - @Deprecated - public void remove() { - throw new UnsupportedOperationException("remove() is not supported by RealmResults iterators."); + @Override + protected E convertRowToObject(UncheckedRow row) { + return realm.get(classSpec, className, row); } } // Custom RealmResults list iterator. - private class RealmResultsListIterator extends RealmResultsIterator implements ListIterator { - + private class RealmResultsListIterator extends Collection.ListIterator { RealmResultsListIterator(int start) { - if (start >= 0 && start <= size()) { - pos = start - 1; - } else { - throw new IndexOutOfBoundsException("Starting location must be a valid index: [0, " + (size() - 1) + "]. Yours was " + start); - } + super(RealmResults.this.collection, start); } - /** - * Unsupported by RealmResults iterators. - * - * @throws UnsupportedOperationException - */ @Override - @Deprecated - public void add(E object) { - throw new UnsupportedOperationException("Adding an element is not supported. Use Realm.createObject() instead."); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean hasPrevious() { - return pos >= 0; - } - - /** - * {@inheritDoc} - */ - @Override - public int nextIndex() { - return pos + 1; - } - - /** - * {@inheritDoc} - */ - @Override - public E previous() { - realm.checkIfValid(); - checkRealmIsStable(); - try { - E obj = get(pos); - pos--; - return obj; - } catch (IndexOutOfBoundsException e) { - throw new NoSuchElementException("Cannot access index less than zero. This was " + pos + ". Remember to check hasPrevious() before using previous()."); - } - } - - /** - * {@inheritDoc} - */ - @Override - public int previousIndex() { - return pos; - } - - /** - * Unsupported by RealmResults iterators. - * - * @throws UnsupportedOperationException - */ - @Override - @Deprecated - public void set(E object) { - throw new UnsupportedOperationException("Replacing and element is not supported."); + protected E convertRowToObject(UncheckedRow row) { + return realm.get(classSpec, className, row); } } @@ -734,7 +642,7 @@ public void set(E object) { */ public boolean isLoaded() { realm.checkIfValid(); - return loadedManually || collection.getMode() == Collection.Mode.TABLEVIEW; + return collection.isLoaded(); } /** @@ -748,7 +656,8 @@ public boolean load() { // Instead, accessing the Collection will just trigger the execution of query if needed. We add this flag is // only to keep the original behavior of those APIs. eg.: For a async RealmResults, before query returns, the // size() call should return 0 instead of running the query get the real size. - loadedManually = true; + realm.checkIfValid(); + collection.load(); return true; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 76a9467099..777918b0b5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -16,11 +16,9 @@ package io.realm.internal; -import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Date; -import java.util.List; +import java.util.NoSuchElementException; import io.realm.RealmChangeListener; @@ -42,27 +40,158 @@ public void onChange(T observer) { } // Custom Collection iterator. It ensures that we only iterate on a Realm collection that hasn't changed. - // TODO: Consider to replace RealmResultsIterator implementation by this since it could be shared by the RealmList. public static abstract class Iterator implements java.util.Iterator { - private final WeakReference collectionWeakReference; + Collection iteratorCollection; + protected int pos = -1; public Iterator(Collection collection) { - collectionWeakReference = new WeakReference(collection); - collection.stableIterators.add(new WeakReference(this)); + this.iteratorCollection = collection; + + if (collection.sharedRealm.isInTransaction()) { + detach(); + } else { + iteratorCollection.sharedRealm.addIterator(this); + } } - protected void checkRealmIsStable() { - Collection collection = collectionWeakReference.get(); - if (collection != null) { - for (WeakReference it : collection.stableIterators) { - if (it.get() == this) { - return; - } - } + /** + * {@inheritDoc} + */ + @Override + public boolean hasNext() { + checkValid(); + return pos + 1 < iteratorCollection.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public T next() { + checkValid(); + pos++; + if (pos >= iteratorCollection.size()) { + throw new NoSuchElementException("Cannot access index " + pos + " when size is " + iteratorCollection.size() + + ". Remember to check hasNext() before using next()."); } - throw new ConcurrentModificationException( - "No outside changes to a Realm is allowed while iterating a RealmResults." + - " Don't call Realm.refresh() while iterating."); + return get(pos); + } + + /** + * Not supported by Realm collection iterators. + * + * @throws UnsupportedOperationException + */ + @Deprecated + public void remove() { + throw new UnsupportedOperationException("remove() is not supported by RealmResults iterators."); + } + + void detach() { + iteratorCollection = iteratorCollection.createSnapshot(); + } + + // The iterator become invalid after receiving a remote change notification. In Java, the destruction of + // iterator totally depends on GC. If we just detach those iterators when remote change notification received + // like what realm-cocoa does, we will have a massive overhead since all the iterators created in the previous + // event loop need to be detached. + void invalidate() { + iteratorCollection = null; + } + + void checkValid() { + if (iteratorCollection == null) { + throw new ConcurrentModificationException( + "No outside changes to a Realm is allowed while iterating a RealmResults." + + " Don't call Realm.refresh() while iterating or use iterators across event loops."); + } + } + + T get(int pos) { + return convertRowToObject(iteratorCollection.getUncheckedRow(pos)); + } + + // Returns the RealmModel by given row in this list. This has to be implemented in the upper layer since + // we don't have information about the object types in the internal package. + protected abstract T convertRowToObject(UncheckedRow row); + } + + // Custom Realm collection list iterator. + public static abstract class ListIterator extends Iterator implements java.util.ListIterator { + + public ListIterator(Collection collection, int start) { + super(collection); + if (start >= 0 && start <= iteratorCollection.size()) { + pos = start - 1; + } else { + throw new IndexOutOfBoundsException("Starting location must be a valid index: [0, " + + (iteratorCollection.size() - 1) + "]. Yours was " + start); + } + } + + /** + * Unsupported by Realm collection iterators. + * + * @throws UnsupportedOperationException + */ + @Override + @Deprecated + public void add(T object) { + throw new UnsupportedOperationException("Adding an element is not supported. Use Realm.createObject() instead."); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasPrevious() { + checkValid(); + return pos >= 0; + } + + /** + * {@inheritDoc} + */ + @Override + public int nextIndex() { + checkValid(); + return pos + 1; + } + + /** + * {@inheritDoc} + */ + @Override + public T previous() { + checkValid(); + try { + T obj = get(pos); + pos--; + return obj; + } catch (IndexOutOfBoundsException e) { + throw new NoSuchElementException("Cannot access index less than zero. This was " + pos + + ". Remember to check hasPrevious() before using previous()."); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int previousIndex() { + checkValid(); + return pos; + } + + /** + * Unsupported by RealmResults iterators. + * + * @throws UnsupportedOperationException + */ + @Override + @Deprecated + public void set(T object) { + throw new UnsupportedOperationException("Replacing and element is not supported."); } } @@ -71,6 +200,7 @@ protected void checkRealmIsStable() { private final SharedRealm sharedRealm; private final Context context; private final Table table; + private boolean loaded = false; private final ObserverPairList observerPairs = new ObserverPairList(); private static final ObserverPairList.Callback onChangeCallback = @@ -81,8 +211,6 @@ public void onCalled(CollectionObserverPair pair, Object observer) { pair.onChange(observer); } }; - // Maintains a list of stable iterators. Iterator becomes invalid when the reattaching happens. - private final List> stableIterators = new ArrayList>(); // Public for static checking in JNI @SuppressWarnings("WeakerAccess") @@ -145,11 +273,8 @@ static Mode getByValue(byte value) { } } - // neverDetach means the collection won't be detached when local transaction starts. This is useful for the - // PendingRow implementation. public Collection(SharedRealm sharedRealm, TableQuery query, - SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor, - boolean neverDetach) { + SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { query.validateQuery(); this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), @@ -160,14 +285,6 @@ public Collection(SharedRealm sharedRealm, TableQuery query, this.context = sharedRealm.context; this.table = query.getTable(); this.context.addReference(this); - if (!neverDetach) { - sharedRealm.addCollection(this); - } - } - - public Collection(SharedRealm sharedRealm, TableQuery query, - SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { - this(sharedRealm, query, sortDescriptor, distinctDescriptor, false); } public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor) { @@ -185,7 +302,10 @@ private Collection(SharedRealm sharedRealm, Table table, long nativePtr) { this.nativePtr = nativePtr; this.context.addReference(this); - sharedRealm.addCollection(this); + } + + public Collection createSnapshot() { + return new Collection(sharedRealm, table, nativeCreateSnapshot(nativePtr)); } @Override @@ -305,7 +425,13 @@ public boolean isValid() { // Called by JNI @SuppressWarnings("unused") private void notifyChangeListeners(boolean emptyChanges) { - if (isDetached()) return; + if (emptyChanges && isLoaded()) { + return; + } + loaded = true; + // TODO: For the fine grained notification, remember to call the callback with empty change set if the + // isLoaded() returns false even when the change set is not empty. Since in that case, it is the first time + // the listener gets called to indicate async query returns. observerPairs.foreach(onChangeCallback); } @@ -313,26 +439,32 @@ public Mode getMode() { return Mode.getByValue(nativeGetMode(nativePtr)); } - // Turns this collection to be backed by a snapshot results. A snapshot results will never be auto-updated. - void detach() { - nativeDetach(nativePtr); - } - - // Turns this collection to be backed by the original results to enable the auto-updating again. - void reattach() { - // Invalidate all current iterators. - stableIterators.clear(); - nativeReattach(nativePtr); - } - - // Return true if this is backed by a snapshot results. - boolean isDetached() { - return nativeIsDetached(nativePtr); + // The Results of Object Store will be queried asynchronously by nature. But we do have to support "sync" query by + // Java like RealmQuery.findAll(). + // The flag is used for following cases: + // 1. For sync query, the loaded will be set to true when collection created. So we will bypass the first trigger of + // listener if it comes with empty change set from Object Store since we assume user already get the query + // result. + // 2. For async query, when load() gets called with loaded not set, the listener should be triggered with empty + // change set since it is considered as query first returned. + // 3. If the listener triggered with empty change set after load() called for async queries, it is treated as the + // same case as 1). + // TODO: Results built from a LinkView has not been considered yet. Maybe it should bet set as loaded when create. + public boolean isLoaded() { + return loaded; + } + + public void load() { + if (loaded) { + return; + } + notifyChangeListeners(true); } private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, SortDescriptor sortDesc, SortDescriptor distinctDesc); + private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); private static native long nativeFirstRow(long nativePtr); private static native long nativeLastRow(long nativePtr); @@ -351,9 +483,6 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native long nativeWhere(long nativePtr); private static native long nativeIndexOf(long nativePtr, long rowNativePtr); private static native long nativeIndexOfBySourceRowIndex(long nativePtr, long sourceRowIndex); - private static native void nativeDetach(long nativePtr); - private static native void nativeReattach(long nativePtr); - private static native boolean nativeIsDetached(long nativePtr); private static native boolean nativeIsValid(long nativePtr); private static native byte nativeGetMode(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 79a3f5a950..8370b03aa2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -35,7 +35,7 @@ public interface FrontEnd { public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, final boolean returnCheckedRow) { - pendingCollection = new Collection(sharedRealm, query, sortDescriptor, null, true); + pendingCollection = new Collection(sharedRealm, query, sortDescriptor, null); listener = new RealmChangeListener() { @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 4635589afe..90ca9af48f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -110,7 +110,7 @@ void didChange() { @SuppressWarnings("unused") void beforeNotify() { // For the stable iteration. - sharedRealm.reattachCollections(); + sharedRealm.invalidateIterators(); } /** @@ -145,13 +145,6 @@ public void addTransactionCallback(Runnable runnable) { transactionCallbacks.add(runnable); } - /** - * Post a runnable to be executed at the very next event loop. Used by current stable Collection iterator. - * - * @param runnable to be executed at the next event loop. - */ - public abstract boolean postAtFrontOfQueue(Runnable runnable); - /** * For current implementation of async transaction only. See comments for {@link #transactionCallbacks}. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 5daa762ef9..8648224ee4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -19,6 +19,7 @@ import java.io.Closeable; import java.io.File; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -111,9 +112,8 @@ public byte getNativeValue() { public final ObjectServerFacade objectServerFacade; public final List> collections = new CopyOnWriteArrayList>(); public final Capabilities capabilities; - - // To prevent overflow the message queue. - public boolean reattachCollectionsPosted = false; + public final List> iterators = + new ArrayList>(); public static class VersionID implements Comparable { public final long version; @@ -233,19 +233,17 @@ public static SharedRealm getInstance(RealmConfiguration config, SchemaVersionLi } public void beginTransaction() { - detachCollections(); + detachIterators(); nativeBeginTransaction(nativePtr); invokeSchemaChangeListenerIfSchemaChanged(); } public void commitTransaction() { nativeCommitTransaction(nativePtr); - postToReattachCollections(); } public void cancelTransaction() { nativeCancelTransaction(nativePtr); - postToReattachCollections(); } public boolean isInTransaction() { @@ -393,65 +391,34 @@ public void invokeSchemaChangeListenerIfSchemaChanged() { } } - // addCollection(), detachCollections(), reattachCollections() and postToReattachCollections() are used to make - // RealmResults stable iterators work. When a Collection is detached from a living OS Results, it won't receive - // notifications and its elements won't be changed. + // addIterator(), detachIterators() and invalidateIterators() are used to make RealmResults stable iterators work. + // The iterator will iterate on a snapshot Results if it is accessed inside a transaction. // See https://github.com/realm/realm-java/issues/3883 for more information. - // Should only be called by Collection's constructor. - void addCollection(Collection collection) { - if (realmNotifier != null) { - collections.add(new WeakReference(collection)); - } + // Should only be called by Iterator's constructor. + void addIterator(Collection.Iterator iterator) { + iterators.add(new WeakReference(iterator)); } // The detaching should happen before transaction begins. - private void detachCollections() { - for (WeakReference collectionRef : collections) { - Collection collection = collectionRef.get(); - if (collection == null) { - collections.remove(collectionRef); - } else { - collection.detach(); + void detachIterators() { + for (WeakReference iteratorRef : iterators) { + Collection.Iterator iterator = iteratorRef.get(); + if (iterator != null) { + iterator.detach(); } } + iterators.clear(); } - // Ideally the reattaching should happen at the very end of the event loop, but it is impossible for most event - // framework. We need to ensure: - // 1) It happens before any other coming events get handled (eg: UI redraw event). - // 2) It happens before Object Store async callbacks since the Object Store event_loop_signal might use a different - // event queue. This is guaranteed by call this function in the binding_context::before_notify callback. - void reattachCollections() { - if (isClosed()) { - return; - } - if (isInTransaction()) { - // This should never happen. - throw new IllegalStateException( "Collection cannot be reattached if the Realm is in transaction." + - " Please remember to commit or cancel transaction before finishing the current event loop."); - } - for (WeakReference collectionRef : collections) { - Collection collection = collectionRef.get(); - if (collection == null) { - collections.remove(collectionRef); - } else { - collection.reattach(); + // Invalidates all iterators when a remote change notification is received. + void invalidateIterators() { + for (WeakReference iteratorRef : iterators) { + Collection.Iterator iterator = iteratorRef.get(); + if (iterator != null) { + iterator.invalidate(); } } - } - - // To handle the point 1) in the reattachCollections comments. - private void postToReattachCollections() { - if (realmNotifier != null && !collections.isEmpty() && !reattachCollectionsPosted) { - reattachCollectionsPosted = true; - realmNotifier.postAtFrontOfQueue(new Runnable() { - @Override - public void run() { - reattachCollectionsPosted = false; - reattachCollections(); - } - }); - } + iterators.clear(); } private static native void nativeInit(String temporaryDirectoryPath); diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java index 4eed44df0b..4fc88b8cde 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java @@ -24,11 +24,6 @@ public AndroidRealmNotifier(SharedRealm sharedRealm, Capabilities capabilities) } } - @Override - public boolean postAtFrontOfQueue(Runnable runnable) { - return handler != null && handler.postAtFrontOfQueue(runnable); - } - @Override public boolean post(Runnable runnable) { return handler != null && handler.post(runnable); From 1ad27e27b4c4d6ed5c78c557d2b9849514015fa9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 10 Feb 2017 13:00:42 +0100 Subject: [PATCH 0491/2110] Upgrading to Realm Sync 1.0.4 (#4168) --- CHANGELOG.md | 8 ++++++++ dependencies.list | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a1ceb77c..0499ddeab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.3.2 + +### Internal + +* Updated to Realm Sync v1.0.4. +* Updated to Realm Core v2.3.1. + + ## 2.3.1 ### Enhancements diff --git a/dependencies.list b/dependencies.list index daca48ea06..ef11c2e0f6 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.0 -REALM_SYNC_SHA256=0e95ee9ed06f1bf66d1531086197e6549ad7ca36da6400b0c5026c5a0c3c4249 +REALM_SYNC_VERSION=1.0.4 +REALM_SYNC_SHA256=a1d00577219b7c2749a0b4baa8b07ead2380bc47907fb2ce4b13cf59d26ca463 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. From 0ad7b47a9cfe3cfecd2d59f29a4eaa8483aa25c0 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Sun, 12 Feb 2017 00:03:07 +0800 Subject: [PATCH 0492/2110] Fix typo and exception message --- realm/realm-library/src/main/java/io/realm/RealmResults.java | 2 +- .../src/main/java/io/realm/internal/Collection.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index a4f30d479c..90c412a6b6 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -99,7 +99,7 @@ public boolean isValid() { } /** - * A {@link RealmResults} is always a managed iteratorCollection. + * A {@link RealmResults} is always a managed collection. * * @return {@code true}. * @see RealmCollection#isManaged() diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 777918b0b5..d34f4f0d8f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -102,8 +102,7 @@ void invalidate() { void checkValid() { if (iteratorCollection == null) { throw new ConcurrentModificationException( - "No outside changes to a Realm is allowed while iterating a RealmResults." + - " Don't call Realm.refresh() while iterating or use iterators across event loops."); + "No outside changes to a Realm is allowed while iterating a living Realm collection."); } } From 3befd223783954b4276d79873124512d8a15bfdc Mon Sep 17 00:00:00 2001 From: Craig Russell Date: Mon, 13 Feb 2017 16:56:37 +0000 Subject: [PATCH 0493/2110] Fix error message to show correct character limit and minor grammar fix --- .../src/androidTest/java/io/realm/RealmMigrationTests.java | 4 ++-- .../src/main/java/io/realm/RealmObjectSchema.java | 2 +- realm/realm-library/src/main/java/io/realm/RealmSchema.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index cf493b1873..6789108cd1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -488,7 +488,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm.getSchema() .get(MigrationPrimaryKey.CLASS_NAME) // 57 characters - .setClassName("MigrationNameIsLongerThan56charThisShouldThrowAnException"); + .setClassName("MigrationNameIsLongerThan56CharThisShouldThrowAnException"); } }; RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() @@ -501,7 +501,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Realm.getInstance(realmConfig); fail(); } catch (IllegalArgumentException expected) { - assertEquals("Class name is to long. Limit is 56 characters: 'MigrationNameIsLongerThan56charThisShouldThrowAnException' (57)", + assertEquals("Class name is too long. Limit is 56 characters: 'MigrationNameIsLongerThan56CharThisShouldThrowAnException' (57)", expected.getMessage()); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 3f48e27087..9e2322d723 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -156,7 +156,7 @@ public RealmObjectSchema setClassName(String className) { checkEmpty(className); String internalTableName = Table.TABLE_PREFIX + className; if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { - throw new IllegalArgumentException("Class name is to long. Limit is 56 characters: \'" + className + "\' (" + Integer.toString(className.length()) + ")"); + throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: \'" + className + "\' (" + Integer.toString(className.length()) + ")"); } if (realm.sharedRealm.hasTable(internalTableName)) { throw new IllegalArgumentException("Class already exists: " + className); diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index a5d05387f4..2584b4278f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -168,7 +168,7 @@ public RealmObjectSchema create(String className) { } else { String internalTableName = TABLE_PREFIX + className; if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { - throw new IllegalArgumentException("Class name is to long. Limit is 57 characters: " + className.length()); + throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: " + className.length()); } if (realm.sharedRealm.hasTable(internalTableName)) { throw new IllegalArgumentException("Class already exists: " + className); From 5e83b0684ff6dc4482bc076896d23461b1310bca Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 16 Feb 2017 13:01:35 +0800 Subject: [PATCH 0494/2110] Fix the broken kotlin example --- .../examples/kotlin/KotlinExampleActivity.kt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index d903e5a208..73f0663136 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -51,17 +51,17 @@ class KotlinExampleActivity : Activity() { // Open the realm for the UI thread. realm = Realm.getDefaultInstance() - basicCRUD(realm) - basicQuery(realm) - basicLinkQuery(realm) - // Delete all persons // Using executeTransaction with a lambda reduces code size and makes it impossible // to forget to commit the transaction. realm.executeTransaction { - realm.delete(Person::class.java) + realm.deleteAll() } + basicCRUD(realm) + basicQuery(realm) + basicLinkQuery(realm) + // More complex operations can be executed on another thread, for example using // Anko's async extension method. async() { @@ -93,7 +93,7 @@ class KotlinExampleActivity : Activity() { // All writes must be wrapped in a transaction to facilitate safe multi threading realm.executeTransaction { // Add a person - val person = realm.createObject(Person::class.java, 1) + val person = realm.createObject(Person::class.java, 0) person.name = "Young Person" person.age = 14 } @@ -139,9 +139,8 @@ class KotlinExampleActivity : Activity() { realm.executeTransaction { val fido = realm.createObject(Dog::class.java) fido.name = "fido" - for (i in 0..9) { - val person = realm.createObject(Person::class.java) - person.id = i.toLong() + for (i in 1..9) { + val person = realm.createObject(Person::class.java, i.toLong()) person.name = "Person no. $i" person.age = i person.dog = fido @@ -177,7 +176,6 @@ class KotlinExampleActivity : Activity() { // Sorting val sortedPersons = realm.where(Person::class.java).findAllSorted("age", Sort.DESCENDING) - check(realm.where(Person::class.java).findAll().last().name == sortedPersons.first().name) status += "\nSorting ${sortedPersons.last().name} == ${realm.where(Person::class.java).findAll().first().name}" realm.close() From 2a3ca6667f3d33c43b8dd959479071417c0c17c3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 14 Feb 2017 16:37:06 +0800 Subject: [PATCH 0495/2110] Print path for RealmFileException --- realm/realm-library/src/main/cpp/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index ee2ee17f0d..875f9289a8 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -68,7 +68,7 @@ void ConvertException(JNIEnv* env, const char *file, int line) ThrowException(env, IllegalArgument, ss.str()); } catch (RealmFileException& e) { - ss << e.what() << " (" << e.underlying() << ") in " << file << " line " << line; + ss << e.what() << " (" << e.underlying() << ") (" << e.path() << ") in " << file << " line " << line; ThrowRealmFileException(env, ss.str(), e.kind()); } catch (File::AccessError& e) { From 8b3baac6f33d4a493acf2785243c606a1b3c5481 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 17 Feb 2017 12:50:05 +0800 Subject: [PATCH 0496/2110] Fix changelog and typos --- CHANGELOG.md | 2 +- .../src/androidTest/java/io/realm/RealmAsyncQueryTests.java | 2 +- .../src/main/java/io/realm/internal/Collection.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 477e43ac64..39ca1ddf3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Breaking changes * `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object (#2947). -* `RealmResults` is auto-updated all the time. Any transaction on the caller thread which may have impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. Iterator behavior of `RealmResults` stays the same, transaction inside the iterating still works as expected. +* `RealmResults` is auto-updated continuously. Any transaction on the caller thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. Iterator behavior of `RealmResults` stays the same, transactions inside the iterating still works as expected. ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index db5285ecce..ee1c90b67d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -696,7 +696,7 @@ public void combiningAsyncAndSync() { final RealmResults allTypesAsync = looperThread.realm.where(AllTypes.class).greaterThan("columnLong", 5).findAllAsync(); final RealmResults allTypesSync = allTypesAsync.where().greaterThan("columnLong", 3).findAll(); - // Call where() on an async results will load query. But to maintain the original behaviour of + // Call where() on an async results will load query. But to maintain the pre version 2.4.0 behaviour of // RealmResults.load(), we still treat it as a not loaded results. assertEquals(0, allTypesAsync.size()); assertEquals(4, allTypesSync.size()); // columnLong > 5 && columnLong > 3 diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index d34f4f0d8f..a6a933f7e5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -438,11 +438,11 @@ public Mode getMode() { return Mode.getByValue(nativeGetMode(nativePtr)); } - // The Results of Object Store will be queried asynchronously by nature. But we do have to support "sync" query by + // The Results of Object Store will be queried asynchronously in nature. But we do have to support "sync" query by // Java like RealmQuery.findAll(). // The flag is used for following cases: - // 1. For sync query, the loaded will be set to true when collection created. So we will bypass the first trigger of - // listener if it comes with empty change set from Object Store since we assume user already get the query + // 1. For sync query, loaded will be set to true when collection is created. So we will bypass the first trigger of + // listener if it comes with empty change set from Object Store since we assume user already got the query // result. // 2. For async query, when load() gets called with loaded not set, the listener should be triggered with empty // change set since it is considered as query first returned. From aecb9f631f49458f1c8922ed0d069720f034271c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 17 Feb 2017 13:14:08 +0800 Subject: [PATCH 0497/2110] Use move_last_over to do deletion for Collection --- .../ManagedOrderedRealmCollectionTests.java | 2 +- .../main/cpp/io_realm_internal_Collection.cpp | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index d1cb287a4a..5050885a5f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -592,7 +592,7 @@ private OrderedRealmCollection createNonCyclicCollection(Realm realm, Manag dog.setName("Dog " + i); } realm.commitTransaction(); - return realm.where(Dog.class).findAll(); + return realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); default: throw new AssertionError("Unknown collection class: " + collectionClass); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 1d25c3b913..18abccab0f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -335,8 +335,9 @@ Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv *env, jclass, jlong na TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - if (wrapper->m_results.size() > 0) { - wrapper->m_results.get_tableview().remove_last(); + auto row = wrapper->m_results.last(); + if (row && row->is_attached()) { + row->move_last_over(); return JNI_TRUE; } } CATCH_STD() @@ -350,8 +351,9 @@ Java_io_realm_internal_Collection_nativeDeleteFirst(JNIEnv *env, jclass, jlong n try { auto wrapper = reinterpret_cast(native_ptr); - if (wrapper->m_results.size() > 0) { - wrapper->m_results.get_tableview().remove(0); + auto row = wrapper->m_results.first(); + if (row && row->is_attached()) { + row->move_last_over(); return JNI_TRUE; } } CATCH_STD() @@ -365,12 +367,10 @@ Java_io_realm_internal_Collection_nativeDelete(JNIEnv *env, jclass, jlong native try { auto wrapper = reinterpret_cast(native_ptr); - auto view = wrapper->m_results.get_tableview(); - size_t size = view.size(); - if (index < 0 || index >= static_cast(size)) { - throw Results::OutOfBoundsIndexException{static_cast(index), size}; + auto row = wrapper->m_results.get(index); + if (row.is_attached()) { + row.move_last_over(); } - view.remove(static_cast(index)); } CATCH_STD() } From 39ac72c96f37af3901bdc4cc3d2f6ba2e16cfae8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 17 Feb 2017 12:42:32 +0800 Subject: [PATCH 0498/2110] Update Object Store to 2950979535 --- realm/realm-library/src/main/cpp/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 0ebca0f031..2950979535 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 0ebca0f03141f52131d6d9f0e6b2f2e32ab9a56d +Subproject commit 29509795357df374f88950ee471a57900b97ecdf From 7b4cb92b81b42b87b48d759cdc7c33adf3ed08e2 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 17 Feb 2017 20:51:58 +0800 Subject: [PATCH 0499/2110] Fix typo and changelog --- CHANGELOG.md | 2 +- .../src/main/java/io/realm/internal/Collection.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39ca1ddf3d..d2a329f8c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Breaking changes * `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object (#2947). -* `RealmResults` is auto-updated continuously. Any transaction on the caller thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. Iterator behavior of `RealmResults` stays the same, transactions inside the iterating still works as expected. +* `RealmResults` is auto-updated continuously. Any transaction on the current thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. The standard `RealmResults.iterator()` will continue to work as normal, which means that you can still delete or modify elements without impacting the iterator. The same is not true for simple for-loops. In some cases a simple for-loop will not work (https://realm.io/docs/java/2.3.1/api/io/realm/OrderedRealmCollection.html#loops), and you must use the new createSnapshot() method. ### Enhancements diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index a6a933f7e5..d2a3622cdc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -91,7 +91,7 @@ void detach() { iteratorCollection = iteratorCollection.createSnapshot(); } - // The iterator become invalid after receiving a remote change notification. In Java, the destruction of + // The iterator becomes invalid after receiving a remote change notification. In Java, the destruction of // iterator totally depends on GC. If we just detach those iterators when remote change notification received // like what realm-cocoa does, we will have a massive overhead since all the iterators created in the previous // event loop need to be detached. From f1783f91e7c5a84f12588c32ccde0d29fe7ce6ef Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 20 Feb 2017 19:22:55 +0800 Subject: [PATCH 0500/2110] Add OrderedRealmCollectionSnapshot (#4172) Introduce OrderedRealmCollectionSnapshot as another type of OrderedRealmCollection. A snapshot can be created from RealmResults or RealmList. A snapshot is backed by a snapshot of OS Results. So the snapshot itself won't be updated. The size and order stay the same forever (elements inside are still live objects.). Since the RealmResults is auto-updated all the time, snapshot will be usefull when changing the results in a simple loops. This commit also moves the common code from RealmResults to OrderedRealmCollectionImpl since those can be shared with snapshot implementation. Implement #3883 --- CHANGELOG.md | 1 + .../java/io/realm/CollectionTests.java | 35 +- .../ManagedOrderedRealmCollectionTests.java | 160 ++++- .../io/realm/ManagedRealmCollectionTests.java | 125 +++- .../OrderedRealmCollectionIteratorTests.java | 157 +++-- .../OrderedRealmCollectionSnapshotTests.java | 105 +++ .../io/realm/OrderedRealmCollectionTests.java | 82 ++- .../java/io/realm/RealmCollectionTests.java | 45 +- .../java/io/realm/RealmListTests.java | 1 + .../UnManagedOrderedRealmCollectionTests.java | 1 + .../main/cpp/io_realm_internal_Collection.cpp | 17 + .../java/io/realm/OrderedRealmCollection.java | 83 +++ .../io/realm/OrderedRealmCollectionImpl.java | 603 ++++++++++++++++++ .../realm/OrderedRealmCollectionSnapshot.java | 215 +++++++ .../src/main/java/io/realm/RealmList.java | 20 + .../src/main/java/io/realm/RealmResults.java | 562 +--------------- .../java/io/realm/internal/Collection.java | 25 +- 17 files changed, 1541 insertions(+), 696 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionSnapshotTests.java create mode 100644 realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java create mode 100644 realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 627c3282b7..20b65690b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Enhancements * Added support for sorting by link's field (#672). +* Added `OrderedRealmCollectionSnapshot` class and `OrderedRealmCollection.createSnapshot()` method. `OrderedRealmCollectionSnapshot` is useful when changing `RealmResults` or `RealmList` in simple loops. ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index 78c41ee057..3f99fdf619 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -34,7 +34,9 @@ public abstract class CollectionTests { // Enumerates all known collection classes from the Realm API. protected enum CollectionClass { - MANAGED_REALMLIST, UNMANAGED_REALMLIST, REALMRESULTS + MANAGED_REALMLIST, UNMANAGED_REALMLIST, REALMRESULTS, + REALMRESULTS_SNAPSHOT_RESULTS_BASE, REALMRESULTS_SNAPSHOT_LIST_BASE + } // Enumerates all current supported collections that can be in unmanaged mode. @@ -44,12 +46,11 @@ protected enum UnManagedCollection { // Enumerates all current supported collections that can be managed by Realm. protected enum ManagedCollection { - MANAGED_REALMLIST, REALMRESULTS + MANAGED_REALMLIST, REALMRESULTS, REALMRESULTS_SNAPSHOT_RESULTS_BASE, REALMRESULTS_SNAPSHOT_LIST_BASE } // Enumerates all methods from the RealmCollection interface that depend on Realm API's. - protected enum RealmCollectionMethod { - WHERE, MIN, MAX, SUM, AVERAGE, MIN_DATE, MAX_DATE, DELETE_ALL_FROM_REALM, IS_VALID, IS_MANAGED + protected enum RealmCollectionMethod { WHERE, MIN, MAX, SUM, AVERAGE, MIN_DATE, MAX_DATE, DELETE_ALL_FROM_REALM, IS_VALID, IS_MANAGED } // Enumerates all methods from the Collection interface @@ -67,7 +68,7 @@ protected enum ListMethod { // Enumerates all methods from the OrderedRealmCollection interface that depend on Realm API's. protected enum OrderedRealmCollectionMethod { - DELETE_INDEX, DELETE_FIRST, DELETE_LAST, SORT, SORT_FIELD, SORT_2FIELDS, SORT_MULTI + DELETE_INDEX, DELETE_FIRST, DELETE_LAST, SORT, SORT_FIELD, SORT_2FIELDS, SORT_MULTI, CREATE_SNAPSHOT } // Enumerates all methods that can mutate a RealmCollection. @@ -191,7 +192,9 @@ protected void populatePartialNullRowsForNumericTesting(Realm realm) { protected OrderedRealmCollection createStringCollection(Realm realm, ManagedCollection collectionClass, String... args) { realm.beginTransaction(); realm.deleteAll(); + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: int id = 0; for (String arg : args) { @@ -199,8 +202,10 @@ protected OrderedRealmCollection createStringCollection(Realm real obj.setFieldString(arg); } realm.commitTransaction(); - return realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_STRING); + orderedCollection = realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_STRING); + break; + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: AllJavaTypes first = realm.createObject(AllJavaTypes.class, 0); first.setFieldString(args[0]); @@ -211,11 +216,27 @@ protected OrderedRealmCollection createStringCollection(Realm real first.getFieldList().add(obj); } realm.commitTransaction(); - return first.getFieldList(); + orderedCollection = first.getFieldList(); + break; default: throw new AssertionError("Unknown collection: " + collectionClass); } + + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + + return orderedCollection; + } + + boolean isSnapshot(ManagedCollection collectionClass) { + return collectionClass == ManagedCollection.REALMRESULTS_SNAPSHOT_LIST_BASE || + collectionClass == ManagedCollection.REALMRESULTS_SNAPSHOT_RESULTS_BASE; } + boolean isSnapshot(CollectionClass collectionClass) { + return collectionClass == CollectionClass.REALMRESULTS_SNAPSHOT_LIST_BASE || + collectionClass == CollectionClass.REALMRESULTS_SNAPSHOT_RESULTS_BASE; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index 5050885a5f..bb873d1956 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -34,7 +34,6 @@ import java.util.concurrent.Future; import io.realm.entities.AllJavaTypes; -import io.realm.entities.Cat; import io.realm.entities.Dog; import io.realm.entities.NullTypes; import io.realm.entities.Owner; @@ -151,38 +150,60 @@ public void tearDown() { } OrderedRealmCollection createCollection(ManagedCollection collectionClass) { + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: - return realm.where(AllJavaTypes.class) + orderedCollection = realm.where(AllJavaTypes.class) .equalTo(AllJavaTypes.FIELD_LONG, 0) .findFirst() .getFieldList(); + break; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: - return realm.where(AllJavaTypes.class).findAll(); + orderedCollection = realm.where(AllJavaTypes.class).findAll(); + break; default: throw new AssertionError("Unsupported class: " + collectionClass); } + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } private OrderedRealmCollection createEmptyCollection(Realm realm, ManagedCollection collectionClass) { + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: realm.beginTransaction(); NullTypes obj = realm.createObject(NullTypes.class, 0); realm.commitTransaction(); - return obj.getFieldListNull(); + orderedCollection = obj.getFieldListNull(); + break; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: - return realm.where(NullTypes.class).findAll(); + orderedCollection = realm.where(NullTypes.class).findAll(); + break; + default: + throw new AssertionError("Unknown collection: " + collectionClass); } - throw new AssertionError("Unknown collection: " + collectionClass); + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } @Test public void sort_twoFields() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection sortedList = collection.sort(AllJavaTypes.FIELD_BOOLEAN, Sort.ASCENDING, AllJavaTypes.FIELD_LONG, Sort.DESCENDING); AllJavaTypes obj = sortedList.first(); assertFalse(obj.isFieldBoolean()); @@ -191,6 +212,9 @@ public void sort_twoFields() { @Test public void sort_boolean() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection sortedList = collection.sort(AllJavaTypes.FIELD_BOOLEAN, Sort.DESCENDING); assertEquals(TEST_SIZE, sortedList.size()); assertEquals(false, sortedList.last().isFieldBoolean()); @@ -212,6 +236,9 @@ public void sort_boolean() { @Test public void sort_string() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection resultList = collection; OrderedRealmCollection sortedList = createCollection(collectionClass); sortedList = sortedList.sort(AllJavaTypes.FIELD_STRING, Sort.DESCENDING); @@ -235,6 +262,9 @@ public void sort_string() { @Test public void sort_double() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection resultList = collection; OrderedRealmCollection sortedList = createCollection(collectionClass); sortedList = sortedList.sort(AllJavaTypes.FIELD_DOUBLE, Sort.DESCENDING); @@ -253,6 +283,9 @@ public void sort_double() { @Test public void sort_float() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection resultList = collection; OrderedRealmCollection sortedList = createCollection(collectionClass); sortedList = sortedList.sort(AllJavaTypes.FIELD_FLOAT, Sort.DESCENDING); @@ -290,10 +323,14 @@ private void doTestSortOnColumnWithPartialNullValues(String fieldName, // Tests sort on nullable fields with null values partially. @Test public void sort_rowsWithPartialNullValues() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } populatePartialNullRowsForNumericTesting(realm); OrderedRealmCollection original; OrderedRealmCollection copy; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: realm.beginTransaction(); RealmResults objects = realm.where(NullTypes.class).findAll(); @@ -309,6 +346,7 @@ public void sort_rowsWithPartialNullValues() { copy = parent.getFieldListNull(); break; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: original = realm.where(NullTypes.class).findAll(); copy = realm.where(NullTypes.class).findAll(); @@ -318,6 +356,10 @@ public void sort_rowsWithPartialNullValues() { throw new AssertionError("Unknown collection class: " + collectionClass); } + if (isSnapshot(collectionClass)) { + copy = copy.createSnapshot(); + } + // 1 String doTestSortOnColumnWithPartialNullValues(NullTypes.FIELD_STRING_NULL, original, copy); @@ -339,13 +381,19 @@ public void sort_rowsWithPartialNullValues() { @Test public void sort_nonExistingColumn() { - RealmResults resultList = realm.where(AllJavaTypes.class).findAll(); - thrown.expect(IllegalArgumentException.class); - resultList.sort("Non-existing"); + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } else { + thrown.expect(IllegalArgumentException.class); + } + collection.sort("Non-existing"); } @Test public void sort_danishCharacters() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection collection = createStringCollection(realm, collectionClass, "Æble", "Øl", @@ -368,6 +416,9 @@ public void sort_danishCharacters() { @Test public void sort_russianCharacters() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection collection = createStringCollection(realm, collectionClass, "Санкт-Петербург", "Москва", @@ -390,6 +441,9 @@ public void sort_russianCharacters() { @Test public void sort_greekCharacters() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection collection = createStringCollection(realm, collectionClass, "αύριο", "ημέρες", @@ -413,6 +467,9 @@ public void sort_greekCharacters() { // No sorting order defined. There are Korean, Arabic and Chinese characters. @Test public void sort_manyDifferentCharacters() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection collection = createStringCollection(realm, collectionClass, "단위", "테스트", @@ -433,6 +490,9 @@ public void sort_manyDifferentCharacters() { @Test public void sort_twoLanguages() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection collection = createStringCollection(realm, collectionClass, "test", "αύριο", @@ -448,6 +508,9 @@ public void sort_twoLanguages() { @Test public void sort_usingChildObject() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection resultList = collection; OrderedRealmCollection sortedList = createCollection(collectionClass); sortedList = sortedList.sort(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LONG, Sort.DESCENDING); @@ -466,6 +529,9 @@ public void sort_usingChildObject() { @Test public void sort_nullArguments() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection result = collection; try { result.sort((String) null); @@ -481,6 +547,9 @@ public void sort_nullArguments() { @Test public void sort_emptyResults() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection collection = createEmptyCollection(realm, collectionClass); assertEquals(0, collection.size()); collection.sort(NullTypes.FIELD_STRING_NULL); @@ -489,6 +558,9 @@ public void sort_emptyResults() { @Test public void sort_singleField() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } RealmResults sortedList = collection.sort(new String[]{AllJavaTypes.FIELD_LONG}, new Sort[]{Sort.DESCENDING}); assertEquals(TEST_SIZE, sortedList.size()); assertEquals(TEST_SIZE - 1, sortedList.first().getFieldLong()); @@ -497,6 +569,9 @@ public void sort_singleField() { @Test public void sort_date() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection resultList = collection; OrderedRealmCollection sortedList = createCollection(collectionClass); sortedList = sortedList.sort(AllJavaTypes.FIELD_DATE, Sort.DESCENDING); @@ -515,6 +590,9 @@ public void sort_date() { @Test public void sort_long() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } OrderedRealmCollection resultList = collection; OrderedRealmCollection sortedList = createCollection(collectionClass); sortedList = sortedList.sort(AllJavaTypes.FIELD_LONG, Sort.DESCENDING); @@ -538,8 +616,13 @@ public void deleteFromRealm() { realm.beginTransaction(); collection.deleteFromRealm(0); realm.commitTransaction(); - assertEquals(TEST_SIZE - 1, collection.size()); - assertEquals(2, collection.get(1).getAge()); + if (isSnapshot(collectionClass)) { + assertEquals(TEST_SIZE, collection.size()); + assertFalse(collection.get(0).isValid()); + } else { + assertEquals(TEST_SIZE - 1, collection.size()); + assertEquals(2, collection.get(1).getAge()); + } } @Test @@ -565,14 +648,21 @@ public void deleteFirstFromRealm() { realm.beginTransaction(); assertTrue(collection.deleteFirstFromRealm()); realm.commitTransaction(); - assertEquals(TEST_SIZE - 1, collection.size()); - assertEquals(1, collection.get(0).getAge()); + if (isSnapshot(collectionClass)) { + assertEquals(TEST_SIZE, collection.size()); + assertFalse(collection.first().isValid()); + } else { + assertEquals(TEST_SIZE - 1, collection.size()); + assertEquals(1, collection.get(0).getAge()); + } } private OrderedRealmCollection createNonCyclicCollection(Realm realm, ManagedCollection collectionClass) { realm.beginTransaction(); realm.deleteAll(); + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case MANAGED_REALMLIST: Owner owner = realm.createObject(Owner.class); RealmList dogs = owner.getDogs(); @@ -583,8 +673,10 @@ private OrderedRealmCollection createNonCyclicCollection(Realm realm, Manag dogs.add(dog); } realm.commitTransaction(); - return dogs; + orderedCollection = dogs; + break; + case REALMRESULTS_SNAPSHOT_LIST_BASE: case REALMRESULTS: for (int i = 0; i < TEST_SIZE; i++) { Dog dog = realm.createObject(Dog.class); @@ -592,12 +684,16 @@ private OrderedRealmCollection createNonCyclicCollection(Realm realm, Manag dog.setName("Dog " + i); } realm.commitTransaction(); - return realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); + orderedCollection = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); + break; default: throw new AssertionError("Unknown collection class: " + collectionClass); } - + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } @Test @@ -615,8 +711,13 @@ public void deleteLastFromRealm() { realm.beginTransaction(); assertTrue(collection.deleteLastFromRealm()); realm.commitTransaction(); - assertEquals(TEST_SIZE - 1, collection.size()); - assertEquals(TEST_SIZE - 2, collection.last().getFieldLong()); + if (isSnapshot(collectionClass)) { + assertEquals(TEST_SIZE, collection.size()); + assertFalse(collection.last().isValid()); + } else { + assertEquals(TEST_SIZE - 1, collection.size()); + assertEquals(TEST_SIZE - 2, collection.last().getFieldLong()); + } } @Test @@ -637,7 +738,7 @@ public void mutableMethodsOutsideTransactions() { // Define expected exception Class expected = IllegalStateException.class; - if (collectionClass == ManagedCollection.REALMRESULTS) { + if (collectionClass == ManagedCollection.REALMRESULTS || isSnapshot(collectionClass)) { switch (method) { case ADD_INDEX: case ADD_ALL_INDEX: @@ -686,6 +787,20 @@ private boolean runMethodOnWrongThread(final OrderedRealmCollectionMethod method Future future = executorService.submit(new Callable() { @Override public Boolean call() throws Exception { + // Defines expected exception. + Class expected = IllegalStateException.class; + if (isSnapshot(collectionClass)) { + switch (method) { + case SORT: + case SORT_FIELD: + case SORT_2FIELDS: + case SORT_MULTI: + expected = UnsupportedOperationException.class; + default: + break; + } + } + try { switch (method) { case DELETE_INDEX: collection.deleteFromRealm(0); break; @@ -695,10 +810,11 @@ public Boolean call() throws Exception { case SORT_FIELD: collection.sort(AllJavaTypes.FIELD_STRING, Sort.ASCENDING); break; case SORT_2FIELDS: collection.sort(AllJavaTypes.FIELD_STRING, Sort.ASCENDING, AllJavaTypes.FIELD_LONG, Sort.DESCENDING); break; case SORT_MULTI: collection.sort(new String[] { AllJavaTypes.FIELD_STRING }, new Sort[] { Sort.ASCENDING }); break; + case CREATE_SNAPSHOT: collection.createSnapshot(); break; } return false; - } catch (IllegalStateException ignored) { - return true; + } catch (Throwable t) { + return t.getClass().equals(expected); } } }); @@ -715,7 +831,7 @@ private boolean runMethodOnWrongThread(final ListMethod method) throws Execution public Boolean call() throws Exception { // Defines expected exception. Class expected = IllegalStateException.class; - if (collectionClass == ManagedCollection.REALMRESULTS) { + if (collectionClass == ManagedCollection.REALMRESULTS || isSnapshot(collectionClass)) { switch (method) { case ADD_INDEX: case ADD_ALL_INDEX: diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 658314fcd8..809dbdf5f7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -20,6 +20,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -91,6 +92,8 @@ public class ManagedRealmCollectionTests extends CollectionTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); private final ManagedCollection collectionClass; private Realm realm; @@ -120,39 +123,61 @@ public void tearDown() { } private OrderedRealmCollection createCollection(ManagedCollection collectionClass) { + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: - return realm.where(AllJavaTypes.class) + orderedCollection = realm.where(AllJavaTypes.class) .equalTo(AllJavaTypes.FIELD_LONG, 0) .findFirst() .getFieldList(); + break; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: - return realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + orderedCollection = realm.where(AllJavaTypes.class) + .findAllSorted(AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + break; default: throw new AssertionError("Unsupported class: " + collectionClass); } + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } private OrderedRealmCollection createEmptyCollection(Realm realm, ManagedCollection collectionClass) { + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: realm.beginTransaction(); NullTypes obj = realm.createObject(NullTypes.class, 0); realm.commitTransaction(); - return obj.getFieldListNull(); + orderedCollection = obj.getFieldListNull(); + break; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: - return realm.where(NullTypes.class).findAll(); + orderedCollection = realm.where(NullTypes.class).findAll(); + break; + default: + throw new AssertionError("Unknown collection: " + collectionClass); } - throw new AssertionError("Unknown collection: " + collectionClass); + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } private OrderedRealmCollection createAllNullRowsForNumericTesting(Realm realm, ManagedCollection collectionClass) { TestHelper.populateAllNullRowsForNumericTesting(realm); + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: RealmResults results = realm.where(NullTypes.class).findAll(); RealmList list = results.get(0).getFieldListNull(); @@ -161,17 +186,29 @@ private OrderedRealmCollection createAllNullRowsForNumericTesting(Rea list.add(results.get(i)); } realm.commitTransaction(); - return list; + orderedCollection = list; + break; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: - return realm.where(NullTypes.class).findAll(); + orderedCollection = realm.where(NullTypes.class).findAll(); + break; + default: + throw new AssertionError("Unknown collection: " + collectionClass); + } + + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); } - throw new AssertionError("Unknown collection: " + collectionClass); + + return orderedCollection; } private OrderedRealmCollection createPartialNullRowsForNumericTesting(Realm realm, ManagedCollection collectionClass) { populatePartialNullRowsForNumericTesting(realm); + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: RealmResults results = realm.where(NullTypes.class).findAll(); RealmList list = results.get(0).getFieldListNull(); @@ -181,18 +218,28 @@ private OrderedRealmCollection createPartialNullRowsForNumericTesting list.add(results.get(i)); } realm.commitTransaction(); - return list; + orderedCollection = list; + break; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: - return realm.where(NullTypes.class).findAll(); + orderedCollection = realm.where(NullTypes.class).findAll(); + break; + default: + throw new AssertionError("Unknown collection: " + collectionClass); } - throw new AssertionError("Unknown collection: " + collectionClass); + + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } // PRE-CONDITION: populateRealm() was called as part of setUp() private OrderedRealmCollection createNonLatinCollection(Realm realm, ManagedCollection collectionClass) { + OrderedRealmCollection orderedCollection; switch (collectionClass) { - + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: realm.beginTransaction(); RealmResults results = realm.where(NonLatinFieldNames.class).findAll(); @@ -201,18 +248,29 @@ private OrderedRealmCollection createNonLatinCollection(Real list.add(results.get(i)); } realm.commitTransaction(); - return list; + orderedCollection = list; + break; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: - return realm.where(NonLatinFieldNames.class).findAll(); + orderedCollection = realm.where(NonLatinFieldNames.class).findAll(); + break; default: throw new AssertionError("Unknown collection: " + collectionClass); } + + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } @Test public void where() { + if (isSnapshot(collectionClass)) { + thrown.expect(UnsupportedOperationException.class); + } RealmResults results = collection.where().findAll(); assertEquals(TEST_SIZE, results.size()); } @@ -500,6 +558,29 @@ public void minDate() { assertEquals(new Date(-YEAR_MILLIS * 20 * TEST_SIZE / 2), collection.minDate(AllJavaTypes.FIELD_DATE)); } + // Deletes the last row in the collection then tests the aggregates methods. + // Since deletion will turn the corresponding object into invalid for collection snapshot, this tests if the + // aggregates methods ignore the invalid rows and return the correct result. + @Test + public void aggregates_deleteLastRow() { + assertTrue(TEST_SIZE > 3); + assertEquals(TEST_SIZE, collection.size()); + realm.beginTransaction(); + realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_LONG, TEST_SIZE - 1).findFirst().deleteFromRealm(); + realm.commitTransaction(); + + int sizeAfterRemove = TEST_SIZE - 1; + + assertEquals(0, collection.min(AllJavaTypes.FIELD_LONG).intValue()); + assertEquals(sizeAfterRemove - 1, collection.max(AllJavaTypes.FIELD_LONG).intValue()); + // Sum of numbers 0 to M-1: (M-1)*M/2 + assertEquals((sizeAfterRemove - 1) * sizeAfterRemove / 2, collection.sum(AllJavaTypes.FIELD_LONG).intValue()); + double average = 3.1415 + (sizeAfterRemove - 1.0) * 0.5; + assertEquals(average, collection.average(AllJavaTypes.FIELD_DOUBLE), 0.0001); + assertEquals(new Date(YEAR_MILLIS * 20 * (sizeAfterRemove / 2 - 1)), collection.maxDate(AllJavaTypes.FIELD_DATE)); + assertEquals(new Date(-YEAR_MILLIS * 20 * TEST_SIZE / 2), collection.minDate(AllJavaTypes.FIELD_DATE)); + } + @Test public void realmMethods_invalidFieldNames() { String[] fieldNames = new String[] { @@ -585,7 +666,11 @@ public void deleteAllFromRealm() { realm.beginTransaction(); assertTrue(collection.deleteAllFromRealm()); realm.commitTransaction(); - assertEquals(0, collection.size()); + if (isSnapshot(collectionClass)) { + assertEquals(TEST_SIZE, collection.size()); + } else { + assertEquals(0, collection.size()); + } } @Test(expected = IllegalStateException.class) @@ -666,7 +751,7 @@ public void mutableMethodsOutsideTransactions() { // Defines expected exception. Class expected = IllegalStateException.class; - if (collectionClass == ManagedCollection.REALMRESULTS) { + if (collectionClass == ManagedCollection.REALMRESULTS || isSnapshot(collectionClass)) { switch (method) { case ADD_OBJECT: case ADD_ALL_OBJECTS: @@ -733,6 +818,8 @@ public Boolean call() throws Exception { return false; } catch (IllegalStateException ignored) { return true; + } catch (UnsupportedOperationException ignored) { + return (method == RealmCollectionMethod.WHERE && isSnapshot(collectionClass)); } } }); @@ -751,7 +838,7 @@ public Boolean call() throws Exception { // Defines expected exception. Class expected = IllegalStateException.class; - if (collectionClass == ManagedCollection.REALMRESULTS) { + if (collectionClass == ManagedCollection.REALMRESULTS || isSnapshot(collectionClass)) { switch (method) { case ADD_OBJECT: case ADD_ALL_OBJECTS: @@ -770,7 +857,9 @@ public Boolean call() throws Exception { case CLEAR: collection.clear(); case CONTAINS: case CONTAINS_ALL: collection.containsAll(Collections.singletonList(tempObject)); break; - case EQUALS: collection.equals(createCollection(collectionClass)); break; + case EQUALS: + //noinspection ResultOfMethodCallIgnored + collection.equals(createCollection(collectionClass)); break; case HASHCODE: //noinspection ResultOfMethodCallIgnored collection.hashCode(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index 8bb6e551aa..ac34cedc51 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -21,7 +21,6 @@ import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -83,15 +82,19 @@ public void tearDown() { } private OrderedRealmCollection createCollection(Realm realm, CollectionClass collectionClass, int sampleSize) { + OrderedRealmCollection orderedCollection; + switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: boolean isEmpty = (sampleSize == 0); int newSampleSize = (isEmpty) ? 2 : sampleSize; populateRealm(realm, newSampleSize); - return realm.where(AllJavaTypes.class) - .equalTo(AllJavaTypes.FIELD_LONG, isEmpty ? 1 : 0) - .findFirst() - .getFieldList(); + orderedCollection = realm.where(AllJavaTypes.class) + .equalTo(AllJavaTypes.FIELD_LONG, isEmpty ? 1 : 0) + .findFirst() + .getFieldList(); + break; case UNMANAGED_REALMLIST: populateRealm(realm, sampleSize); @@ -100,30 +103,20 @@ private OrderedRealmCollection createCollection(Realm realm, Colle inMemoryList.addAll(objects); return inMemoryList; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: populateRealm(realm, sampleSize); - return realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + orderedCollection = realm.where(AllJavaTypes.class) + .findAllSorted(AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + break; default: throw new AssertionError("Unsupported class: " + collectionClass); } - } - - private void appendElementToCollection(Realm realm, CollectionClass collection) { - realm.beginTransaction(); - switch (collectionClass) { - case MANAGED_REALMLIST: - realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_LONG, 0).findFirst().getFieldList().add(new AllJavaTypes(TEST_SIZE + 1)); - break; - - case UNMANAGED_REALMLIST: - case REALMRESULTS: - realm.createObject(AllJavaTypes.class, TEST_SIZE + 1); - break; - default: - fail("Unknown class: " + collection); + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); } - realm.commitTransaction(); + return orderedCollection; } private void createNewObject() { @@ -154,6 +147,19 @@ private boolean skipTest(CollectionClass... unsupportedTypes) { return false; } + private void assertResultsOrSnapshot() { + if (collectionClass != CollectionClass.REALMRESULTS && !isSnapshot(collectionClass)) { + fail("Collection class " + collectionClass + "is not results or snapshot."); + } + } + + private void assertRealmList() { + if (collectionClass != CollectionClass.UNMANAGED_REALMLIST && + collectionClass != CollectionClass.MANAGED_REALMLIST) { + fail("Collection class " + collectionClass + "is not RealmList."); + } + } + @Test public void iterator() { Iterator it = collection.iterator(); @@ -230,7 +236,7 @@ public void iterator_closedRealm_methodsThrows() { } catch (IllegalStateException e) { assertEquals(CollectionClass.MANAGED_REALMLIST, collectionClass); } catch (UnsupportedOperationException e) { - assertEquals(CollectionClass.REALMRESULTS, collectionClass); + assertResultsOrSnapshot(); } } @@ -243,9 +249,9 @@ public void iterator_remove_beforeNext() { it.remove(); fail(); } catch (UnsupportedOperationException e) { - assertEquals(CollectionClass.REALMRESULTS, collectionClass); + assertResultsOrSnapshot(); } catch (IllegalStateException ignored) { - assertNotEquals(CollectionClass.REALMRESULTS, collectionClass); + assertRealmList(); } } @@ -260,7 +266,7 @@ public void iterator_remove() { it.remove(); } catch (UnsupportedOperationException e) { // RealmResults doesn't support remove. - assertEquals(CollectionClass.REALMRESULTS, collectionClass); + assertResultsOrSnapshot(); return; } @@ -281,6 +287,11 @@ public void iterator_deleteManagedObjectIndirectly() { realm.commitTransaction(); switch (collectionClass) { + // Snapshot + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: + case REALMRESULTS_SNAPSHOT_LIST_BASE: + assertFalse(collection.get(1).isValid()); + break; // Managed RealmLists are directly associated with their table. Thus any indirect deletion will // also remove it from the LinkView. case MANAGED_REALMLIST: @@ -300,7 +311,8 @@ public void iterator_deleteManagedObjectIndirectly() { @Test public void iterator_removeCalledTwice() { - if (skipTest(CollectionClass.REALMRESULTS)) { + if (skipTest(CollectionClass.REALMRESULTS, CollectionClass.REALMRESULTS_SNAPSHOT_LIST_BASE, + CollectionClass.REALMRESULTS_SNAPSHOT_RESULTS_BASE)) { return; // remove() not supported by RealmResults. } @@ -418,9 +430,9 @@ public void listIterator_remove_beforeNext() { try { it.remove(); } catch (IllegalStateException e) { - assertNotEquals(CollectionClass.REALMRESULTS, collectionClass); + assertRealmList(); } catch (UnsupportedOperationException e) { - assertEquals(CollectionClass.REALMRESULTS, collectionClass); + assertResultsOrSnapshot(); } } @@ -437,6 +449,8 @@ public void listIterator_remove_calledTwice() { thrown.expect(IllegalStateException.class); it.remove(); break; + case REALMRESULTS_SNAPSHOT_LIST_BASE: + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: try { it.remove(); // Method not supported. @@ -506,9 +520,9 @@ public void listIterator_closedRealm_methods() { it.remove(); fail(); } catch (IllegalStateException e) { - assertNotEquals(CollectionClass.REALMRESULTS, collectionClass); + assertRealmList(); } catch (UnsupportedOperationException ignored) { - assertEquals(CollectionClass.REALMRESULTS, collectionClass); + assertResultsOrSnapshot(); } } @@ -537,25 +551,36 @@ public void listIterator_deleteManagedObjectIndirectly() { } @Test - public void listIterator_remove_doesNotDeleteObject() { + public void listIterator_remove_realmList_doesNotDeleteObject() { + if (skipTest(CollectionClass.REALMRESULTS, CollectionClass.REALMRESULTS_SNAPSHOT_LIST_BASE, + CollectionClass.REALMRESULTS_SNAPSHOT_RESULTS_BASE)) { + return; + } ListIterator it = collection.listIterator(); AllJavaTypes obj = it.next(); assertEquals("test data 0", obj.getFieldString()); realm.beginTransaction(); - try { - it.remove(); - if (collectionClass == CollectionClass.REALMRESULTS) { - fail(); - } - assertTrue(obj.isValid()); - } catch (UnsupportedOperationException e) { - assertEquals(CollectionClass.REALMRESULTS, collectionClass); + it.remove(); + assertTrue(obj.isValid()); + } + + @Test + public void listIterator_remove_nonRealmList_throwUnsupported() { + if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { + return; } + ListIterator it = collection.listIterator(); + AllJavaTypes obj = it.next(); + assertEquals("test data 0", obj.getFieldString()); + realm.beginTransaction(); + thrown.expect(UnsupportedOperationException.class); + it.remove(); } @Test public void listIterator_set() { - if (skipTest(CollectionClass.REALMRESULTS)) { + if (skipTest(CollectionClass.REALMRESULTS, CollectionClass.REALMRESULTS_SNAPSHOT_RESULTS_BASE, + CollectionClass.REALMRESULTS_SNAPSHOT_LIST_BASE)) { return; } @@ -608,33 +633,34 @@ public void listIterator_unsupportedMethods() { it.remove(); fail(); } catch (UnsupportedOperationException e) { - assertEquals(CollectionClass.REALMRESULTS, collectionClass); + assertResultsOrSnapshot(); } catch (IllegalStateException e) { - assertNotEquals(CollectionClass.REALMRESULTS, collectionClass); + assertRealmList(); } try { it.add(null); fail(); } catch (UnsupportedOperationException e) { - assertEquals(CollectionClass.REALMRESULTS, collectionClass); + assertResultsOrSnapshot(); } catch (IllegalArgumentException e) { - assertNotEquals(CollectionClass.REALMRESULTS, collectionClass); + assertRealmList(); } try { it.set(new AllJavaTypes()); fail(); } catch (UnsupportedOperationException e) { - assertEquals(CollectionClass.REALMRESULTS, collectionClass); + assertResultsOrSnapshot(); } catch (IllegalStateException e) { - assertNotEquals(CollectionClass.REALMRESULTS, collectionClass); + assertRealmList(); } } @Test public void iterator_outsideChangeToSizeThrowsConcurrentModification() { - if (skipTest(CollectionClass.REALMRESULTS)) { + if (skipTest(CollectionClass.REALMRESULTS, CollectionClass.REALMRESULTS_SNAPSHOT_RESULTS_BASE, + CollectionClass.REALMRESULTS_SNAPSHOT_LIST_BASE)) { return; } @@ -700,7 +726,8 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification() { @Test public void iterator_outsideChangeToSizeThrowsConcurrentModification_managedCollection() { - if (skipTest(CollectionClass.REALMRESULTS, CollectionClass.UNMANAGED_REALMLIST)) { + if (skipTest(CollectionClass.REALMRESULTS, CollectionClass.UNMANAGED_REALMLIST, + CollectionClass.REALMRESULTS_SNAPSHOT_LIST_BASE, CollectionClass.REALMRESULTS_SNAPSHOT_RESULTS_BASE)) { return; } @@ -747,6 +774,7 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification_managedColl case SORT_FIELD: case SORT_2FIELDS: case SORT_MULTI: + case CREATE_SNAPSHOT: realm.cancelTransaction(); continue; default: @@ -770,8 +798,10 @@ private void checkIteratorThrowsConcurrentModification(Realm realm, String metho } } + // Accessing RealmResults iterator after receving remote change notification will throw. + // But it is valid operation for snapshot. @Test - public void iterator_realmResultsThrowConcurrentModification() { + public void iterator_realmResultsThrowConcurrentModification_snapshotJustWorks() { if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { return; } @@ -794,19 +824,19 @@ public void run() { realm.waitForChange(); try { it.next(); - fail(); + assertEquals(TEST_SIZE, collection.size()); } catch (ConcurrentModificationException ignored) { + assertEquals(collectionClass, CollectionClass.REALMRESULTS); } } @Test - @Ignore("Enable this test when support RealmCollectionSnapshot") public void useCase_simpleIterator_modifyQueryResult_innerTransaction() { - if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { + if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST, + CollectionClass.REALMRESULTS)) { return; } - collection = realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll(); assertEquals(TEST_SIZE, collection.size()); for (int i = 0; i < collection.size(); i++) { realm.beginTransaction(); @@ -820,13 +850,12 @@ public void useCase_simpleIterator_modifyQueryResult_innerTransaction() { } @Test - @Ignore("Enable this test when support RealmCollectionSnapshot") public void useCase_simpleIterator_modifyQueryResult_outerTransaction() { - if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { + if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST, + CollectionClass.REALMRESULTS)) { return; } - collection = realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll(); assertEquals(TEST_SIZE, collection.size()); realm.beginTransaction(); for (int i = 0; i < collection.size(); i++) { @@ -845,7 +874,6 @@ public void useCase_forEachIterator_modifyQueryResult_innerTransaction() { return; } - collection = realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll(); assertEquals(TEST_SIZE, collection.size()); for (AllJavaTypes obj : collection) { realm.beginTransaction(); @@ -863,7 +891,6 @@ public void useCase_forEachIterator_modifyQueryResult_outerTransaction() { return; } - collection = realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll(); assertEquals(TEST_SIZE, collection.size()); realm.beginTransaction(); for (AllJavaTypes obj : collection) { @@ -877,13 +904,12 @@ public void useCase_forEachIterator_modifyQueryResult_outerTransaction() { @Test @UiThreadTest - @Ignore("Enable this test when support RealmCollectionSnapshot") public void useCase_simpleIterator_modifyQueryResult_innerTransaction_looperThread() { - if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { + if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST, + CollectionClass.REALMRESULTS)) { return; } - collection = realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll(); assertEquals(TEST_SIZE, collection.size()); for (int i = 0; i < collection.size(); i++) { realm.beginTransaction(); @@ -898,13 +924,12 @@ public void useCase_simpleIterator_modifyQueryResult_innerTransaction_looperThre @Test @UiThreadTest - @Ignore("Enable this test when support RealmCollectionSnapshot") public void useCase_simpleIterator_modifyQueryResult_outerTransaction_looperThread() { - if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST)) { + if (skipTest(CollectionClass.MANAGED_REALMLIST, CollectionClass.UNMANAGED_REALMLIST, + CollectionClass.REALMRESULTS)) { return; } - collection = realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll(); assertEquals(TEST_SIZE, collection.size()); realm.beginTransaction(); for (int i = 0; i < collection.size(); i++) { @@ -924,7 +949,6 @@ public void useCase_forEachIterator_modifyQueryResult_innerTransaction_looperThr return; } - collection = realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll(); assertEquals(TEST_SIZE, collection.size()); for (AllJavaTypes obj : collection) { realm.beginTransaction(); @@ -943,7 +967,6 @@ public void useCase_forEachIterator_modifyQueryResult_outerTransaction_looperThr return; } - collection = realm.where(AllJavaTypes.class).lessThan(AllJavaTypes.FIELD_LONG, TEST_SIZE).findAll(); assertEquals(TEST_SIZE, collection.size()); realm.beginTransaction(); for (AllJavaTypes obj : collection) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionSnapshotTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionSnapshotTests.java new file mode 100644 index 0000000000..6d43158ba5 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionSnapshotTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import io.realm.entities.AllTypes; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +/** + * Unit tests specific for {@link OrderedRealmCollectionSnapshot} that cannot be covered by + * {@link OrderedRealmCollectionTests}, {@link ManagedRealmCollectionTests}, {@link UnManagedRealmCollectionTests} or + * {@link RealmCollectionTests}. + */ +@RunWith(AndroidJUnit4.class) +public class OrderedRealmCollectionSnapshotTests { + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private static final int TEST_SIZE = 10; + private Realm realm; + private OrderedRealmCollection snapshot; + + @Before + public void setup() { + realm = Realm.getInstance(configFactory.createConfiguration()); + populateRealm(realm, TEST_SIZE); + snapshot = realm.where(AllTypes.class).findAll().createSnapshot(); + } + + @After + public void tearDown() { + realm.close(); + } + + private void populateRealm(Realm realm, int testSize) { + realm.beginTransaction(); + for (int i = 0; i < testSize; i++) { + AllTypes allTypes = realm.createObject(AllTypes.class); + allTypes.setColumnLong(i); + } + realm.commitTransaction(); + } + + @Test + public void deleteFromRealm_twice() { + realm.beginTransaction(); + snapshot.deleteFromRealm(0); + snapshot.deleteFromRealm(0); + realm.commitTransaction(); + assertFalse(snapshot.get(0).isValid()); + } + + @Test + public void deleteFirstFromRealm_twice() { + realm.beginTransaction(); + assertTrue(snapshot.deleteFirstFromRealm()); + assertFalse(snapshot.deleteFirstFromRealm()); + realm.commitTransaction(); + } + + @Test + public void deleteLastFromRealm_twice() { + realm.beginTransaction(); + assertTrue(snapshot.deleteLastFromRealm()); + assertFalse(snapshot.deleteLastFromRealm()); + realm.commitTransaction(); + } + + @Test + public void deleteAllFromRealmTwice() { + realm.beginTransaction(); + assertTrue(snapshot.deleteAllFromRealm()); + assertTrue(snapshot.deleteAllFromRealm()); + realm.commitTransaction(); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java index 8bee6afa70..d722f024de 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java @@ -34,6 +34,7 @@ import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; /** @@ -43,15 +44,16 @@ * * # RealmOrderedCollection * - * + E first() - * + E last() - * - void sort(String field) - * - void sort(String field, Sort sortOrder) - * - void sort(String field1, Sort sortOrder1, String field2, Sort sortOrder2) - * - void sort(String[] fields, Sort[] sortOrders) - * - void deleteFromRealm(int location) - * - void deleteFirstFromRealm() + * + E first(); + * + E last(); + * - void sort(String field); + * - void sort(String field, Sort sortOrder); + * - void sort(String field1, Sort sortOrder1, String field2, Sort sortOrder2); + * - void sort(String[] fields, Sort[] sortOrders); + * - void deleteFromRealm(int location); + * - void deleteFirstFromRealm(); * - void deleteLastFromRealm(); + * - OrderedRealmCollectionSnapshot createSnapshot(); * * # List * @@ -138,49 +140,69 @@ public void tearDown() { } private OrderedRealmCollection createCollection(Realm realm, CollectionClass collectionClass) { + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: populateRealm(realm, TEST_SIZE); - return realm.where(AllJavaTypes.class) + orderedCollection = realm.where(AllJavaTypes.class) .equalTo(AllJavaTypes.FIELD_LONG, 0) .findFirst() .getFieldList(); + break; case UNMANAGED_REALMLIST: return populateInMemoryList(TEST_SIZE); + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: populateRealm(realm, TEST_SIZE); - return realm.where(AllJavaTypes.class).findAll(); + orderedCollection = realm.where(AllJavaTypes.class).findAll(); + break; default: throw new AssertionError("Unsupported class: " + collectionClass); } + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } private OrderedRealmCollection createEmptyCollection(Realm realm, CollectionClass collectionClass) { + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: - return realm.where(AllJavaTypes.class) + orderedCollection = realm.where(AllJavaTypes.class) .equalTo(AllJavaTypes.FIELD_LONG, 1) .findFirst() .getFieldList(); + break; case UNMANAGED_REALMLIST: return new RealmList(); + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: - return realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_LONG, -1).findAll(); + orderedCollection = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_LONG, -1).findAll(); + break; default: throw new AssertionError("Unsupported class: " + collectionClass); } + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } private Pair> createCollectionWithMultipleCopies(Realm realm, CollectionClass collectionClass) { + OrderedRealmCollection orderedCollection; AllJavaTypes obj; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: obj = realm.where(AllJavaTypes.class) .equalTo(AllJavaTypes.FIELD_LONG, 1) @@ -189,20 +211,29 @@ private Pair> createCollectio realm.beginTransaction(); list.add(obj); realm.commitTransaction(); - return new Pair>(obj, list); + orderedCollection = list; + break; case UNMANAGED_REALMLIST: obj = new AllJavaTypes(1); return new Pair>(obj, new RealmList(obj, obj)); + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: RealmResults result = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_LONG, 1).findAll(); obj = result.first(); - return new Pair>(obj, result); + orderedCollection = result; + break; default: throw new AssertionError("Unsupported class: " + collectionClass); } + + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + + return new Pair>(obj, orderedCollection); } @Test @@ -325,7 +356,7 @@ public void subList_invalidEnd() { collection.subList(0, TEST_SIZE + 1); } - // Check that all releveant methods throw a correct IndexOutOfBounds + // Checks that all relevant methods throw a correct IndexOutOfBounds @Test public void methods_indexOutOfBounds() { collection = createEmptyCollection(realm, collectionClass); @@ -371,6 +402,7 @@ public void methods_indexOutOfBounds() { case SORT_FIELD: case SORT_2FIELDS: case SORT_MULTI: + case CREATE_SNAPSHOT: continue; } fail(method + " did not throw an exception"); @@ -382,4 +414,24 @@ public void methods_indexOutOfBounds() { } } + @Test + public void createSnapshot() { + if (collectionClass == CollectionClass.UNMANAGED_REALMLIST) { + thrown.expect(UnsupportedOperationException.class); + } + OrderedRealmCollectionSnapshot snapshot = collection.createSnapshot(); + switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: + // Creating snapshot from a snapshot will just return the same object. + assertSame(collection, snapshot); + break; + case MANAGED_REALMLIST: + case REALMRESULTS: + assertEquals(collection.size(), snapshot.size()); + break; + default: + break; + } + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java index e3c1e2f2b6..f72137c2c2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java @@ -114,28 +114,40 @@ public void tearDown() { } private RealmCollection createCollection(CollectionClass collectionClass) { + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: populateRealm(realm, TEST_SIZE); - return realm.where(AllJavaTypes.class) + orderedCollection = realm.where(AllJavaTypes.class) .equalTo(AllJavaTypes.FIELD_LONG, 0) .findFirst() .getFieldList(); + break; case UNMANAGED_REALMLIST: return populateInMemoryList(TEST_SIZE); + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: populateRealm(realm, TEST_SIZE); - return realm.where(AllJavaTypes.class).findAll(); + orderedCollection = realm.where(AllJavaTypes.class).findAll(); + break; default: throw new AssertionError("Unsupported class: " + collectionClass); } + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } private RealmCollection createCustomMethodsCollection(Realm realm, CollectionClass collectionClass) { + OrderedRealmCollection orderedCollection; + switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: realm.beginTransaction(); CustomMethods top = realm.createObject(CustomMethods.class); @@ -144,7 +156,8 @@ private RealmCollection createCustomMethodsCollection(Realm realm top.getMethods().add(new CustomMethods("Child" + i)); } realm.commitTransaction(); - return top.getMethods(); + orderedCollection = top.getMethods(); + break; case UNMANAGED_REALMLIST: RealmList list = new RealmList(); @@ -153,35 +166,53 @@ private RealmCollection createCustomMethodsCollection(Realm realm } return list; + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { realm.copyToRealm(new CustomMethods("Child" + i)); } realm.commitTransaction(); - return realm.where(CustomMethods.class).findAll(); + orderedCollection = realm.where(CustomMethods.class).findAll(); + break; default: throw new AssertionError("Unsupported class: " + collectionClass); } + + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } private OrderedRealmCollection createEmptyCollection(Realm realm, CollectionClass collectionClass) { + OrderedRealmCollection orderedCollection; switch (collectionClass) { + case REALMRESULTS_SNAPSHOT_LIST_BASE: case MANAGED_REALMLIST: realm.beginTransaction(); NullTypes obj = realm.createObject(NullTypes.class, 0); realm.commitTransaction(); - return obj.getFieldListNull(); + orderedCollection = obj.getFieldListNull(); + break; case UNMANAGED_REALMLIST: return new RealmList(); + case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: - return realm.where(NullTypes.class).findAll(); + orderedCollection = realm.where(NullTypes.class).findAll(); + break; + + default: + throw new AssertionError("Unknown collection: " + collectionClass); } - throw new AssertionError("Unknown collection: " + collectionClass); + if (isSnapshot(collectionClass)) { + orderedCollection = orderedCollection.createSnapshot(); + } + return orderedCollection; } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 5106b86cb8..9b23775a87 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -742,6 +742,7 @@ public void realmMethods_onDeletedLinkView() { case SORT_FIELD: results.sort(CyclicType.FIELD_NAME, Sort.ASCENDING); break; case SORT_2FIELDS: results.sort(CyclicType.FIELD_NAME, Sort.ASCENDING, CyclicType.FIELD_DATE, Sort.DESCENDING); break; case SORT_MULTI: results.sort(new String[] { CyclicType.FIELD_NAME, CyclicType.FIELD_DATE }, new Sort[] { Sort.ASCENDING, Sort.DESCENDING}); + case CREATE_SNAPSHOT: results.createSnapshot(); } fail(method + " should have thrown an Exception"); } catch (IllegalStateException ignored) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java index 20df1cb38a..e45adfa721 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/UnManagedOrderedRealmCollectionTests.java @@ -153,6 +153,7 @@ public void unsupportedMethods_unManagedCollections() { case SORT_FIELD: collection.sort(AllJavaTypes.FIELD_STRING, Sort.ASCENDING); break; case SORT_2FIELDS: collection.sort(AllJavaTypes.FIELD_STRING, Sort.ASCENDING, AllJavaTypes.FIELD_LONG, Sort.DESCENDING); break; case SORT_MULTI: collection.sort(new String[] { AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_LONG }, new Sort[] { Sort.ASCENDING, Sort.DESCENDING }); break; + case CREATE_SNAPSHOT: collection.createSnapshot(); } fail(method + " should have thrown an exception."); } catch (UnsupportedOperationException ignored) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 18abccab0f..a7350f43a4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -79,6 +79,23 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong return reinterpret_cast(nullptr); } +JNIEXPORT jlong JNICALL +Java_io_realm_internal_Collection_nativeCreateResultsFromLinkView(JNIEnv* env, jclass, jlong shared_realm_ptr, + jlong link_view_ptr, jobject sort_desc) +{ + TR_ENTER() + try { + auto link_view_ref = reinterpret_cast(link_view_ptr); + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + Results results(shared_realm, *link_view_ref, util::none, + SortDescriptor(JavaSortDescriptor(env, sort_desc))); + auto wrapper = new ResultsWrapper(results); + + return reinterpret_cast(wrapper); + } CATCH_STD() + return reinterpret_cast(nullptr); +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) { diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java index 724ec56cf5..a05a132c8b 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java @@ -23,6 +23,79 @@ * element in the {@code OrderedRealmCollection} has an index. Each element can thus be accessed by its * index, with the first index being zero. Normally, {@code OrderedRealmCollection}s allow duplicate * elements, as compared to Sets, where elements have to be unique. + * + *

          + * + * There are three types of {@link OrderedRealmCollection}. {@link RealmResults} and {@link RealmList} are live + * collections. They are up-to-date all the time and they will never contain an invalid {@link RealmObject}. + * {@link OrderedRealmCollectionSnapshot} is different. An {@link OrderedRealmCollectionSnapshot} can be created from + * another {@link OrderedRealmCollection}. Its size and elements order stay the same as the original collection's when + * it was created. {@link OrderedRealmCollectionSnapshot} may contain invalid {@link RealmObject}s if the objects get + * deleted. + * + *

          + * + *

          + * Using iterators to iterate on {@link OrderedRealmCollection} will always work. You can delete or modify the elements + * without impacting the iterator. See below example: + * + *
          + * {@code
          + * RealmResults dogs = realm.where(Dog.class).findAll();
          + * int s = dogs.size(); // 10
          + * realm.beginTransaction();
          + * for (Dog dog : dogs) {
          + *     dog.deleteFromRealm();
          + *     s = dogs.size(); // This will be decreased by 1 every time after a dog is removed.
          + * }
          + * realm.commitTransaction();
          + * s = dogs.size(); // 0
          + * }
          + * 
          + * + * An iterator created from a live collection will create a stable view when the iterator is created, allowing you to + * delete and modify elements while iterating without impacting the iterator. However, the {@code RealmResults} backing + * the iterator will still be live updated meaning that size and order of elements can change when iterating. + * {@link RealmList} has the same behaviour as {@link RealmResults} since they are both live collections. + * + *

          + * + * A simple for-loop is different. See below example: + * + *

          + * {@code
          + * RealmResults dogs = realm.where(Dog.class).findAll();
          + * realm.beginTransaction();
          + * for (int i = 0; i < dogs.size(); i++) {
          + *     dogs.get(i).deleteFromRealm();
          + * }
          + * realm.commitTransaction();
          + * s = dogs.size(); // 5
          + * }
          + * 
          + * + * The above example only deletes half of elements in the {@link RealmResults}. This is because of {@code dogs.size()} + * decreased by 1 for every loop. The deletion happens in the loop will immediately impact the size of + * {@code RealmResults}. To solve this problem, you can create a {@link OrderedRealmCollectionSnapshot} from the + * {@link RealmResults} or {@link RealmList} and do simple for-loop on that instead: + * + *
          + * {@code
          + * RealmResults dogs = realm.where(Dog.class).findAll();
          + * OrderedRealmCollectionSnapshot snapshot = dogs.createSnapshot();
          + * // dogs.size() == 10 && snapshot.size() == 10
          + * realm.beginTransaction();
          + * for (int i = 0; i < snapshot.size(); i++) {
          + *     snapshot.get(0).deleteFromRealm();
          + *     // snapshot.get(0).isValid() == false
          + * }
          + * realm.commitTransaction();
          + * // dogs.size() == 0 && snapshot.size() == 10
          + * }
          + * 
          + * + * As you can see, after deletion, the size and elements order of snapshot stay the same as before. But the element at + * the position becomes invalid. */ public interface OrderedRealmCollection extends List, RealmCollection { @@ -137,4 +210,14 @@ public interface OrderedRealmCollection extends List, R * @throws UnsupportedOperationException if the collection is unmanaged. */ boolean deleteLastFromRealm(); + + /** + * Creates a snapshot from this {@link OrderedRealmCollection}. + * + * @return the snapshot of this collection. + * @see OrderedRealmCollectionSnapshot + * @throws java.lang.IllegalStateException if the Realm is closed or the method is called from the wrong thread. + * @throws UnsupportedOperationException if the collection is unmanaged. + */ + OrderedRealmCollectionSnapshot createSnapshot(); } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java new file mode 100644 index 0000000000..80cb91a7f2 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -0,0 +1,603 @@ +package io.realm; + +import java.util.AbstractList; +import java.util.ConcurrentModificationException; +import java.util.Date; +import java.util.Iterator; +import java.util.ListIterator; + +import io.realm.internal.Collection; +import io.realm.internal.InvalidRow; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.SortDescriptor; +import io.realm.internal.Table; +import io.realm.internal.UncheckedRow; + +/** + * General implementation for {@link OrderedRealmCollection} which is based on the {@code Collection}. + * Currently only {@link RealmResults} and {@link OrderedRealmCollectionSnapshot} extend this class. But + * {@link RealmList} could also extend this to share the same iterator implementation. + */ +abstract class OrderedRealmCollectionImpl + extends AbstractList implements OrderedRealmCollection { + private final static String NOT_SUPPORTED_MESSAGE = "This method is not supported by 'RealmResults' or" + + " 'OrderedRealmCollectionSnapshot'."; + + final BaseRealm realm; + Class classSpec; // Return type + String className; // Class name used by DynamicRealmObjects + + final Collection collection; + + OrderedRealmCollectionImpl(BaseRealm realm, Collection collection, Class clazz) { + this.realm = realm; + this.classSpec = clazz; + this.collection = collection; + } + + OrderedRealmCollectionImpl(BaseRealm realm, Collection collection, String className) { + this.realm = realm; + this.className = className; + this.collection = collection; + } + + Table getTable() { + return collection.getTable(); + } + + Collection getCollection() { + return collection; + } + + /** + * {@inheritDoc} + */ + public boolean isValid() { + return collection.isValid(); + } + + /** + * A {@link RealmResults} or a {@link OrderedRealmCollectionSnapshot} is always a managed collection. + * + * @return {@code true}. + * @see RealmCollection#isManaged() + */ + public boolean isManaged() { + return true; + } + + /** + * Searches this {@link OrderedRealmCollection} for the specified object. + * + * @param object the object to search for. + * @return {@code true} if {@code object} is an element of this {@code OrderedRealmCollection}, + * {@code false} otherwise. + */ + @Override + public boolean contains(Object object) { + if (isLoaded()) { + // Deleted objects can never be part of a RealmResults + if (object instanceof RealmObjectProxy) { + RealmObjectProxy proxy = (RealmObjectProxy) object; + if (proxy.realmGet$proxyState().getRow$realm() == InvalidRow.INSTANCE) { + return false; + } + } + + for (E e : this) { + if (e.equals(object)) { + return true; + } + } + } + return false; + } + + /** + * Returns the element at the specified location in this list. + * + * @param location the index of the element to return. + * @return the element at the specified index. + * @throws IndexOutOfBoundsException if {@code location < 0 || location >= size()}. + */ + @Override + public E get(int location) { + realm.checkIfValid(); + return realm.get(classSpec, className, collection.getUncheckedRow(location)); + } + + /** + * {@inheritDoc} + */ + @Override + public E first() { + return firstImpl(true, null); + } + + /** + * {@inheritDoc} + */ + @Override + public E first(E defaultValue) { + return firstImpl(false, defaultValue); + } + + private E firstImpl(boolean shouldThrow, E defaultValue) { + UncheckedRow row = collection.firstUncheckedRow(); + + if (row != null) { + return realm.get(classSpec, className, row); + } else { + if (shouldThrow) { + throw new IndexOutOfBoundsException("No results were found."); + } else { + return defaultValue; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public E last() { + return lastImpl(true, null); + } + + /** + * {@inheritDoc} + */ + @Override + public E last(E defaultValue) { + return lastImpl(false, defaultValue); + + } + + private E lastImpl(boolean shouldThrow, E defaultValue) { + UncheckedRow row = collection.lastUncheckedRow(); + + if (row != null) { + return realm.get(classSpec, className, row); + } else { + if (shouldThrow) { + throw new IndexOutOfBoundsException("No results were found."); + } else { + return defaultValue; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void deleteFromRealm(int location) { + // TODO: Implement the delete in OS level and do check there! + realm.checkIfValidAndInTransaction(); + collection.delete(location); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean deleteAllFromRealm() { + realm.checkIfValid(); + if (size() > 0) { + collection.clear(); + return true; + } + return false; + } + + /** + * Returns an iterator for the results of a query. Any change to Realm while iterating will cause this iterator to + * throw a {@link ConcurrentModificationException} if accessed. + * + * @return an iterator on the elements of this list. + * @see Iterator + */ + @SuppressWarnings("NullableProblems") + @Override + public Iterator iterator() { + return new RealmCollectionIterator(); + } + + /** + * Returns a list iterator for the results of a query. Any change to Realm while iterating will cause the iterator + * to throw a {@link ConcurrentModificationException} if accessed. + * + * @return a ListIterator on the elements of this list. + * @see ListIterator + */ + @Override + public ListIterator listIterator() { + return new RealmCollectionListIterator(0); + } + + /** + * Returns a list iterator on the results of a query. Any change to Realm while iterating will cause the iterator to + * throw a {@link ConcurrentModificationException} if accessed. + * + * @param location the index at which to start the iteration. + * @return a ListIterator on the elements of this list. + * @throws IndexOutOfBoundsException if {@code location < 0 || location > size()}. + * @see ListIterator + */ + @SuppressWarnings("NullableProblems") + @Override + public ListIterator listIterator(int location) { + return new RealmCollectionListIterator(location); + } + + // Sorting + + // aux. method used by sort methods + private long getColumnIndexForSort(String fieldName) { + if (fieldName == null || fieldName.isEmpty()) { + throw new IllegalArgumentException("Non-empty field name required."); + } + if (fieldName.contains(".")) { + throw new IllegalArgumentException("Aggregates on child object fields are not supported: " + fieldName); + } + long columnIndex = collection.getTable().getColumnIndex(fieldName); + if (columnIndex < 0) { + throw new IllegalArgumentException(String.format("Field '%s' does not exist.", fieldName)); + } + return columnIndex; + } + + /** + * {@inheritDoc} + */ + @Override + public RealmResults sort(String fieldName) { + SortDescriptor sortDescriptor = + SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, Sort.ASCENDING); + + Collection sortedCollection = collection.sort(sortDescriptor); + return createLoadedResults(sortedCollection); + } + + /** + * {@inheritDoc} + */ + @Override + public RealmResults sort(String fieldName, Sort sortOrder) { + SortDescriptor sortDescriptor = + SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, sortOrder); + + Collection sortedCollection = collection.sort(sortDescriptor); + return createLoadedResults(sortedCollection); + } + + /** + * {@inheritDoc} + */ + @Override + public RealmResults sort(String fieldNames[], Sort sortOrders[]) { + SortDescriptor sortDescriptor = + SortDescriptor.getInstanceForSort(collection.getTable(), fieldNames, sortOrders); + + Collection sortedCollection = collection.sort(sortDescriptor); + return createLoadedResults(sortedCollection); + } + + /** + * {@inheritDoc} + */ + @Override + public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { + return sort(new String[]{fieldName1, fieldName2}, new Sort[]{sortOrder1, sortOrder2}); + } + + // Aggregates + + /** + * Returns the number of elements in this query result. + * + * @return the number of elements in this query result. + */ + @Override + public int size() { + if (isLoaded()) { + long size = collection.size(); + return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; + } + return 0; + } + + /** + * {@inheritDoc} + */ + public Number min(String fieldName) { + realm.checkIfValid(); + long columnIndex = getColumnIndexForSort(fieldName); + return collection.aggregateNumber(io.realm.internal.Collection.Aggregate.MINIMUM, columnIndex); + } + + /** + * {@inheritDoc} + */ + public Date minDate(String fieldName) { + realm.checkIfValid(); + long columnIndex = getColumnIndexForSort(fieldName); + return collection.aggregateDate(Collection.Aggregate.MINIMUM, columnIndex); + } + + /** + * {@inheritDoc} + */ + public Number max(String fieldName) { + realm.checkIfValid(); + long columnIndex = getColumnIndexForSort(fieldName); + return collection.aggregateNumber(Collection.Aggregate.MAXIMUM, columnIndex); + } + + /** + * Finds the maximum date. + * + * @param fieldName the field to look for the maximum date. If fieldName is not of Date type, an exception is + * thrown. + * @return if no objects exist or they all have {@code null} as the value for the given date field, {@code null} + * will be returned. Otherwise the maximum date is returned. When determining the maximum date, objects with + * {@code null} values are ignored. + * @throws IllegalArgumentException if fieldName is not a Date field. + */ + public Date maxDate(String fieldName) { + realm.checkIfValid(); + long columnIndex = getColumnIndexForSort(fieldName); + return collection.aggregateDate(Collection.Aggregate.MAXIMUM, columnIndex); + } + + + /** + * {@inheritDoc} + */ + public Number sum(String fieldName) { + realm.checkIfValid(); + long columnIndex = getColumnIndexForSort(fieldName); + return collection.aggregateNumber(Collection.Aggregate.SUM, columnIndex); + } + + /** + * {@inheritDoc} + */ + public double average(String fieldName) { + realm.checkIfValid(); + long columnIndex = getColumnIndexForSort(fieldName); + + Number avg = collection.aggregateNumber(Collection.Aggregate.AVERAGE, columnIndex); + return avg.doubleValue(); + } + + /** + * Returns a distinct set of objects of a specific class. If the result is sorted, the first + * object will be returned in case of multiple occurrences, otherwise it is undefined which + * object is returned. + * + * @param fieldName the field name. + * @return a new non-null {@link RealmResults} containing the distinct objects. + * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, + * is not indexed, or points to linked fields. + */ + public RealmResults distinct(String fieldName) { + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(collection.getTable(), fieldName); + Collection distinctCollection = collection.distinct(distinctDescriptor); + return createLoadedResults(distinctCollection); + } + + /** + * Asynchronously returns a distinct set of objects of a specific class. If the result is + * sorted, the first object will be returned in case of multiple occurrences, otherwise it is + * undefined which object is returned. + * + * @param fieldName the field name. + * @return immediately a {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the + * query completes. + * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, + * is not indexed, or points to linked fields. + */ + public RealmResults distinctAsync(String fieldName) { + realm.sharedRealm.capabilities.checkCanDeliverNotification(RealmQuery.ASYNC_QUERY_WRONG_THREAD_MESSAGE); + return where().distinctAsync(fieldName); + } + + /** + * Returns a distinct set of objects from a specific class. When multiple distinct fields are + * given, all unique combinations of values in the fields will be returned. In case of multiple + * matches, it is undefined which object is returned. Unless the result is sorted, then the + * first object will be returned. + * + * @param firstFieldName first field name to use when finding distinct objects. + * @param remainingFieldNames remaining field names when determining all unique combinations of field values. + * @return a non-null {@link RealmResults} containing the distinct objects. + * @throws IllegalArgumentException if field names is empty or {@code null}, does not exist, + * is an unsupported type, or points to a linked field. + */ + public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { + return where().distinct(firstFieldName, remainingFieldNames); + } + + // Deleting + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException + */ + @Deprecated + @Override + public E remove(int index) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException + */ + @Deprecated + @Override + public boolean remove(Object object) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException + */ + @Deprecated + @Override + public boolean removeAll(@SuppressWarnings("NullableProblems") java.util.Collection collection) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException + */ + @Deprecated + @Override + public E set(int location, E object) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException + */ + @Deprecated + @Override + public boolean retainAll(@SuppressWarnings("NullableProblems") java.util.Collection collection) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + /** + * Removes the last object in the list. This also deletes the object from the underlying Realm. + * + * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. + */ + @Override + public boolean deleteLastFromRealm() { + // TODO: Implement the deleteLast in OS level and do check there! + realm.checkIfValidAndInTransaction(); + return collection.deleteLast(); + } + + /** + * Removes the first object in the list. This also deletes the object from the underlying Realm. + * + * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. + */ + @Override + public boolean deleteFirstFromRealm() { + // TODO: Implement the deleteLast in OS level and do check there! + realm.checkIfValidAndInTransaction(); + return collection.deleteFirst(); + } + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException always. + */ + @Override + @Deprecated + public void clear() { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException always. + */ + @Override + @Deprecated + public boolean add(E element) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException always. + */ + @Override + @Deprecated + public void add(int index, E element) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException always. + */ + @Override + @Deprecated + public boolean addAll(int location, + @SuppressWarnings("NullableProblems") java.util.Collection collection) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + /** + * Not supported by {@link RealmResults} and {@link OrderedRealmCollectionSnapshot}. + * + * @throws UnsupportedOperationException always. + */ + @Deprecated + @Override + public boolean addAll(@SuppressWarnings("NullableProblems") java.util.Collection collection) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + // Custom RealmResults iterator. It ensures that we only iterate on a Realm that hasn't changed. + private class RealmCollectionIterator extends Collection.Iterator { + RealmCollectionIterator() { + super(OrderedRealmCollectionImpl.this.collection); + } + + @Override + protected E convertRowToObject(UncheckedRow row) { + return realm.get(classSpec, className, row); + } + } + + @Override + public OrderedRealmCollectionSnapshot createSnapshot() { + if (className != null) { + return new OrderedRealmCollectionSnapshot(realm, collection, className); + } else { + return new OrderedRealmCollectionSnapshot(realm, collection, classSpec); + } + } + + // Custom RealmResults list iterator. + private class RealmCollectionListIterator extends Collection.ListIterator { + RealmCollectionListIterator(int start) { + super(OrderedRealmCollectionImpl.this.collection, start); + } + + @Override + protected E convertRowToObject(UncheckedRow row) { + return realm.get(classSpec, className, row); + } + } + + private RealmResults createLoadedResults(Collection newCollection) { + RealmResults results; + if (className != null) { + results = new RealmResults(realm, newCollection, className); + } else { + results = new RealmResults(realm, newCollection, classSpec); + } + results.load(); + return results; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java new file mode 100644 index 0000000000..5c8067faf1 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java @@ -0,0 +1,215 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import io.realm.internal.Collection; +import io.realm.internal.UncheckedRow; + +/** + * An {@link OrderedRealmCollectionSnapshot} is a special type of {@link OrderedRealmCollection}. It can be created by + * calling {@link OrderedRealmCollection#createSnapshot()}. Unlike {@link RealmResults} and {@link RealmList}, its + * size and order of elements will never be changed after creation. + *

          + * {@link OrderedRealmCollectionSnapshot} is useful when making changes which may impact the size or order of the + * collection in simple loops. For example: + *

          + * {@code
          + * final RealmResults  dogs = realm.where(Dog.class).findAll();
          + * final OrderedRealmCollectionSnapshot snapshot = dogs.createSnapshot();
          + * final int dogsCount = snapshot.size(); // dogs.size() == snapshot.size() == 10
          + * realm.executeTransaction(new Realm.Transaction() {
          + *     /@Override
          + *     public void execute(Realm realm) {
          + *         for (int i = 0; i < dogsCount; i++) {
          + *         // This won't work since RealmResults is always up-to-date, its size gets decreased by 1 after every loop. An
          + *         // IndexOutOfBoundsException will be thrown after 5 loops.
          + *         // dogs.deleteFromRealm(i);
          + *         snapshot.deleteFromRealm(i); // Deletion on OrderedRealmCollectionSnapshot won't change the size of it.
          + *         }
          + *     }
          + * });
          + * }
          + * 
          + */ +public class OrderedRealmCollectionSnapshot extends OrderedRealmCollectionImpl { + + private int size = -1; + + OrderedRealmCollectionSnapshot(BaseRealm realm, Collection collection, Class clazz) { + super(realm, collection.createSnapshot(), clazz); + } + + OrderedRealmCollectionSnapshot(BaseRealm realm, Collection collection, String className) { + super(realm, collection.createSnapshot(), className); + } + + /** + * {@inheritDoc} + */ + @Override + public int size() { + // Optimization for simple loops. The size of snapshot will never be changed. + if (size == -1) { + size = super.size(); + } + return size; + } + + /** + * Not supported by {@link OrderedRealmCollectionSnapshot}. Use 'sort()' on the original + * {@link OrderedRealmCollection} instead. + * + * @throws UnsupportedOperationException + */ + @Override + public RealmResults sort(String fieldName) { + throw getUnsupportedException("sort"); + } + + /** + * Not supported by {@link OrderedRealmCollectionSnapshot}. Use 'sort()' on the original + * {@link OrderedRealmCollection} instead. + * + * @throws UnsupportedOperationException + */ + @Override + public RealmResults sort(String fieldName, Sort sortOrder) { + throw getUnsupportedException("sort"); + } + + /** + * Not supported by {@link OrderedRealmCollectionSnapshot}. Use 'sort()' on the original + * {@link OrderedRealmCollection} instead. + * + * @throws UnsupportedOperationException + */ + @Override + public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { + throw getUnsupportedException("sort"); + } + + /** + * Not supported by {@link OrderedRealmCollectionSnapshot}. Use 'sort()' on the original + * {@link OrderedRealmCollection} instead. + * + * @throws UnsupportedOperationException + */ + @Override + public RealmResults sort(String[] fieldNames, Sort[] sortOrders) { + throw getUnsupportedException("sort"); + } + + /** + * Not supported by {@link OrderedRealmCollectionSnapshot}. Use 'where()' on the original + * {@link OrderedRealmCollection} instead. + * + * @throws UnsupportedOperationException + */ + @Deprecated + @Override + public RealmQuery where() { + throw getUnsupportedException("where"); + } + + private UnsupportedOperationException getUnsupportedException(String methodName) { + return new UnsupportedOperationException( + String.format("'%s()' is not supported by OrderedRealmCollectionSnapshot. " + + "Call '%s()' on the original 'RealmCollection' instead.", methodName, methodName)); + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean isLoaded() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean load() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public OrderedRealmCollectionSnapshot createSnapshot() { + realm.checkIfValid(); + return this; + } + + /** + * Deletes the object at the given index from the Realm. The object at the given index will become invalid. Just + * returns if the object is invalid already. + * + * @param location the array index identifying the object to be removed. + * @throws IndexOutOfBoundsException if {@code location < 0 || location >= size()}. + * @throws java.lang.IllegalStateException if the Realm is closed or the method is called from the wrong thread. + */ + @Override + public void deleteFromRealm(int location) { + realm.checkIfValidAndInTransaction(); + UncheckedRow row = collection.getUncheckedRow(location); + if (row.isAttached()) { + collection.delete(location); + } + } + + /** + * Deletes the first object from the Realm. The first object will become invalid. + * + * @return {@code true} if an object was deleted, {@code false} otherwise. + * @throws java.lang.IllegalStateException if the Realm is closed or the method is called on the wrong thread. + */ + @Override + public boolean deleteFirstFromRealm() { + realm.checkIfValidAndInTransaction(); + UncheckedRow row = collection.firstUncheckedRow(); + return row != null && row.isAttached() && collection.deleteFirst(); + } + + /** + * Deletes the last object from the Realm. The last object will become invalid. + * + * @return {@code true} if an object was deleted, {@code false} otherwise. + * @throws java.lang.IllegalStateException if the Realm is closed or the method is called from the wrong thread. + */ + @Override + public boolean deleteLastFromRealm() { + realm.checkIfValidAndInTransaction(); + UncheckedRow row = collection.lastUncheckedRow(); + return row != null && row.isAttached() && collection.deleteLast(); + } + + /** + * This deletes all objects in the collection from the underlying Realm. All objects in the collection snapshot + * will become invalid. + * + * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. + * @return {@code true} if objects was deleted, {@code false} otherwise. + * @throws java.lang.IllegalStateException if the Realm has been closed or called from an incorrect thread. + */ + @Override + public boolean deleteAllFromRealm() { + return super.deleteAllFromRealm(); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index d909155433..70e6ff7378 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -795,6 +795,26 @@ private void checkValidView() { } } + /** + * {@inheritDoc} + */ + @Override + public OrderedRealmCollectionSnapshot createSnapshot() { + if (!managedMode) { + throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); + } + checkValidView(); + if (className != null) { + return new OrderedRealmCollectionSnapshot(realm, + new io.realm.internal.Collection(realm.sharedRealm, view, null), + className); + } else { + return new OrderedRealmCollectionSnapshot(realm, + new io.realm.internal.Collection(realm.sharedRealm, view, null), + clazz); + } + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 90c412a6b6..bee6375f1c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -61,51 +61,14 @@ * @see RealmQuery#findAll() * @see Realm#executeTransaction(Realm.Transaction) */ -public class RealmResults extends AbstractList implements OrderedRealmCollection { - - private final static String NOT_SUPPORTED_MESSAGE = "This method is not supported by RealmResults."; - - final BaseRealm realm; - Class classSpec; // Return type - String className; // Class name used by DynamicRealmObjects - - private final Collection collection; +public class RealmResults extends OrderedRealmCollectionImpl { RealmResults(BaseRealm realm, Collection collection, Class clazz) { - this.realm = realm; - this.classSpec = clazz; - this.collection = collection; + super(realm, collection, clazz); } RealmResults(BaseRealm realm, Collection collection, String className) { - this.realm = realm; - this.className = className; - this.collection = collection; - } - - Table getTable() { - return collection.getTable(); - } - - Collection getCollection() { - return collection; - } - - /** - * {@inheritDoc} - */ - public boolean isValid() { - return collection.isValid(); - } - - /** - * A {@link RealmResults} is always a managed collection. - * - * @return {@code true}. - * @see RealmCollection#isManaged() - */ - public boolean isManaged() { - return true; + super(realm, collection, className); } /** @@ -117,222 +80,6 @@ public RealmQuery where() { return RealmQuery.createQueryFromResult(this); } - /** - * Searches this {@link RealmResults} for the specified object. - * - * @param object the object to search for. - * @return {@code true} if {@code object} is an element of this {@code RealmResults}, - * {@code false} otherwise - */ - @Override - public boolean contains(Object object) { - if (isLoaded()) { - // Deleted objects can never be part of a RealmResults - if (object instanceof RealmObjectProxy) { - RealmObjectProxy proxy = (RealmObjectProxy) object; - if (proxy.realmGet$proxyState().getRow$realm() == InvalidRow.INSTANCE) { - return false; - } - } - - for (E e : this) { - if (e.equals(object)) { - return true; - } - } - } - return false; - } - - /** - * Returns the element at the specified location in this list. - * - * @param location the index of the element to return. - * @return the element at the specified index. - * @throws IndexOutOfBoundsException if {@code location < 0 || location >= size()}. - */ - @Override - public E get(int location) { - realm.checkIfValid(); - return realm.get(classSpec, className, collection.getUncheckedRow(location)); - } - - /** - * {@inheritDoc} - */ - @Override - public E first() { - return firstImpl(true, null); - } - - /** - * {@inheritDoc} - */ - @Override - public E first(E defaultValue) { - return firstImpl(false, defaultValue); - } - - private E firstImpl(boolean shouldThrow, E defaultValue) { - UncheckedRow row = collection.firstUncheckedRow(); - - if (row != null) { - return realm.get(classSpec, className, row); - } else { - if (shouldThrow) { - throw new IndexOutOfBoundsException("No results were found."); - } else { - return defaultValue; - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public E last() { - return lastImpl(true, null); - } - - /** - * {@inheritDoc} - */ - @Override - public E last(E defaultValue) { - return lastImpl(false, defaultValue); - - } - - private E lastImpl(boolean shouldThrow, E defaultValue) { - UncheckedRow row = collection.lastUncheckedRow(); - - if (row != null) { - return realm.get(classSpec, className, row); - } else { - if (shouldThrow) { - throw new IndexOutOfBoundsException("No results were found."); - } else { - return defaultValue; - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public void deleteFromRealm(int location) { - // TODO: Implement the deleteLast in OS level and do check there! - realm.checkIfValidAndInTransaction(); - collection.delete(location); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean deleteAllFromRealm() { - realm.checkIfValid(); - if (size() > 0) { - collection.clear(); - return true; - } - return false; - } - - /** - * Returns an iterator for the results of a query. Any change to Realm while iterating will cause this iterator to - * throw a {@link ConcurrentModificationException} if accessed. - * - * @return an iterator on the elements of this list. - * @see Iterator - */ - @SuppressWarnings("NullableProblems") - @Override - public Iterator iterator() { - return new RealmResultsIterator(); - } - - /** - * Returns a list iterator for the results of a query. Any change to Realm while iterating will cause the iterator - * to throw a {@link ConcurrentModificationException} if accessed. - * - * @return a ListIterator on the elements of this list. - * @see ListIterator - */ - @Override - public ListIterator listIterator() { - return new RealmResultsListIterator(0); - } - - /** - * Returns a list iterator on the results of a query. Any change to Realm while iterating will cause the iterator to - * throw a {@link ConcurrentModificationException} if accessed. - * - * @param location the index at which to start the iteration. - * @return a ListIterator on the elements of this list. - * @throws IndexOutOfBoundsException if {@code location < 0 || location > size()}. - * @see ListIterator - */ - @SuppressWarnings("NullableProblems") - @Override - public ListIterator listIterator(int location) { - return new RealmResultsListIterator(location); - } - - // Sorting - - // aux. method used by sort methods - private long getColumnIndexForSort(String fieldName) { - if (fieldName == null || fieldName.isEmpty()) { - throw new IllegalArgumentException("Non-empty field name required."); - } - if (fieldName.contains(".")) { - throw new IllegalArgumentException("Sorting using child object fields is not supported: " + fieldName); - } - long columnIndex = collection.getTable().getColumnIndex(fieldName); - if (columnIndex < 0) { - throw new IllegalArgumentException(String.format("Field '%s' does not exist.", fieldName)); - } - return columnIndex; - } - - /** - * {@inheritDoc} - */ - @Override - public RealmResults sort(String fieldName) { - SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, Sort.ASCENDING); - - Collection sortedCollection = collection.sort(sortDescriptor); - return createLoadedResults(sortedCollection); - } - - /** - * {@inheritDoc} - */ - @Override - public RealmResults sort(String fieldName, Sort sortOrder) { - SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, sortOrder); - - Collection sortedCollection = collection.sort(sortDescriptor); - return createLoadedResults(sortedCollection); - } - - /** - * {@inheritDoc} - */ - @Override - public RealmResults sort(String fieldNames[], Sort sortOrders[]) { - SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(collection.getTable(), fieldNames, sortOrders); - - Collection sortedCollection = collection.sort(sortDescriptor); - return createLoadedResults(sortedCollection); - } /** * {@inheritDoc} @@ -342,298 +89,6 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName return sort(new String[]{fieldName1, fieldName2}, new Sort[]{sortOrder1, sortOrder2}); } - // Aggregates - - /** - * Returns the number of elements in this query result. - * - * @return the number of elements in this query result. - */ - @Override - public int size() { - if (isLoaded()) { - long size = collection.size(); - return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; - } - return 0; - } - - /** - * {@inheritDoc} - */ - public Number min(String fieldName) { - realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateNumber(io.realm.internal.Collection.Aggregate.MINIMUM, columnIndex); - } - - /** - * {@inheritDoc} - */ - public Date minDate(String fieldName) { - realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateDate(Collection.Aggregate.MINIMUM, columnIndex); - } - - /** - * {@inheritDoc} - */ - public Number max(String fieldName) { - realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateNumber(Collection.Aggregate.MAXIMUM, columnIndex); - } - - /** - * Finds the maximum date. - * - * @param fieldName the field to look for the maximum date. If fieldName is not of Date type, an exception is - * thrown. - * @return if no objects exist or they all have {@code null} as the value for the given date field, {@code null} - * will be returned. Otherwise the maximum date is returned. When determining the maximum date, objects with - * {@code null} values are ignored. - * @throws IllegalArgumentException if fieldName is not a Date field. - */ - public Date maxDate(String fieldName) { - realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateDate(Collection.Aggregate.MAXIMUM, columnIndex); - } - - - /** - * {@inheritDoc} - */ - public Number sum(String fieldName) { - realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateNumber(Collection.Aggregate.SUM, columnIndex); - } - - /** - * {@inheritDoc} - */ - public double average(String fieldName) { - realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); - - Number avg = collection.aggregateNumber(Collection.Aggregate.AVERAGE, columnIndex); - return avg.doubleValue(); - } - - /** - * Returns a distinct set of objects of a specific class. If the result is sorted, the first - * object will be returned in case of multiple occurrences, otherwise it is undefined which - * object is returned. - * - * @param fieldName the field name. - * @return a new non-null {@link RealmResults} containing the distinct objects. - * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, - * is not indexed, or points to linked fields. - */ - public RealmResults distinct(String fieldName) { - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(collection.getTable(), fieldName); - Collection distinctCollection = collection.distinct(distinctDescriptor); - return createLoadedResults(distinctCollection); - } - - /** - * Asynchronously returns a distinct set of objects of a specific class. If the result is - * sorted, the first object will be returned in case of multiple occurrences, otherwise it is - * undefined which object is returned. - * - * @param fieldName the field name. - * @return immediately a {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the - * query completes. - * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, - * is not indexed, or points to linked fields. - */ - public RealmResults distinctAsync(String fieldName) { - realm.sharedRealm.capabilities.checkCanDeliverNotification(RealmQuery.ASYNC_QUERY_WRONG_THREAD_MESSAGE); - return where().distinctAsync(fieldName); - } - - /** - * Returns a distinct set of objects from a specific class. When multiple distinct fields are - * given, all unique combinations of values in the fields will be returned. In case of multiple - * matches, it is undefined which object is returned. Unless the result is sorted, then the - * first object will be returned. - * - * @param firstFieldName first field name to use when finding distinct objects. - * @param remainingFieldNames remaining field names when determining all unique combinations of field values. - * @return a non-null {@link RealmResults} containing the distinct objects. - * @throws IllegalArgumentException if field names is empty or {@code null}, does not exist, - * is an unsupported type, or points to a linked field. - */ - public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { - return where().distinct(firstFieldName, remainingFieldNames); - } - - // Deleting - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException - */ - @Deprecated - @Override - public E remove(int index) { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException - */ - @Deprecated - @Override - public boolean remove(Object object) { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException - */ - @Deprecated - @Override - public boolean removeAll(@SuppressWarnings("NullableProblems") java.util.Collection collection) { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException - */ - @Deprecated - @Override - public E set(int location, E object) { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException - */ - @Deprecated - @Override - public boolean retainAll(@SuppressWarnings("NullableProblems") java.util.Collection collection) { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - /** - * Removes the last object in the list. This also deletes the object from the underlying Realm. - * - * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. - */ - @Override - public boolean deleteLastFromRealm() { - // TODO: Implement the deleteLast in OS level and do check there! - realm.checkIfValidAndInTransaction(); - return collection.deleteLast(); - } - - /** - * Removes the first object in the list. This also deletes the object from the underlying Realm. - * - * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. - */ - @Override - public boolean deleteFirstFromRealm() { - // TODO: Implement the deleteLast in OS level and do check there! - realm.checkIfValidAndInTransaction(); - return collection.deleteFirst(); - } - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException always. - */ - @Override - @Deprecated - public void clear() { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException always. - */ - @Override - @Deprecated - public boolean add(E element) { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException always. - */ - @Override - @Deprecated - public void add(int index, E element) { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException always. - */ - @Override - @Deprecated - public boolean addAll(int location, - @SuppressWarnings("NullableProblems") java.util.Collection collection) { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - /** - * Not supported by RealmResults. - * - * @throws UnsupportedOperationException always. - */ - @Deprecated - @Override - public boolean addAll(@SuppressWarnings("NullableProblems") java.util.Collection collection) { - throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); - } - - // Custom RealmResults iterator. It ensures that we only iterate on a Realm that hasn't changed. - private class RealmResultsIterator extends Collection.Iterator { - RealmResultsIterator() { - super(RealmResults.this.collection); - } - - @Override - protected E convertRowToObject(UncheckedRow row) { - return realm.get(classSpec, className, row); - } - } - - // Custom RealmResults list iterator. - private class RealmResultsListIterator extends Collection.ListIterator { - RealmResultsListIterator(int start) { - super(RealmResults.this.collection, start); - } - - @Override - protected E convertRowToObject(UncheckedRow row) { - return realm.get(classSpec, className, row); - } - } - /** * Returns {@code false} if the results are not yet loaded, {@code true} if they are loaded. * @@ -743,15 +198,4 @@ public Observable> asObservable() { throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava."); } } - - private RealmResults createLoadedResults(Collection newCollection) { - RealmResults results; - if (className != null) { - results = new RealmResults(realm, newCollection, className); - } else { - results = new RealmResults(realm, newCollection, classSpec); - } - results.load(); - return results; - } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index d2a3622cdc..30e4794e83 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -47,6 +47,11 @@ public static abstract class Iterator implements java.util.Iterator { public Iterator(Collection collection) { this.iteratorCollection = collection; + if (collection.isSnapshot) { + // No need to detach a snapshot. + return; + } + if (collection.sharedRealm.isInTransaction()) { detach(); } else { @@ -200,6 +205,7 @@ public void set(T object) { private final Context context; private final Table table; private boolean loaded = false; + private boolean isSnapshot = false; private final ObserverPairList observerPairs = new ObserverPairList(); private static final ObserverPairList.Callback onChangeCallback = @@ -294,6 +300,16 @@ public Collection(SharedRealm sharedRealm, TableQuery query) { this(sharedRealm, query, null, null); } + public Collection(SharedRealm sharedRealm, LinkView linkView, SortDescriptor sortDescriptor) { + this.nativePtr = nativeCreateResultsFromLinkView(sharedRealm.getNativePtr(), linkView.getNativePtr(), + sortDescriptor); + + this.sharedRealm = sharedRealm; + this.context = sharedRealm.context; + this.table = linkView.getTable(); + this.context.addReference(this); + } + private Collection(SharedRealm sharedRealm, Table table, long nativePtr) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; @@ -304,7 +320,12 @@ private Collection(SharedRealm sharedRealm, Table table, long nativePtr) { } public Collection createSnapshot() { - return new Collection(sharedRealm, table, nativeCreateSnapshot(nativePtr)); + if (isSnapshot) { + return this; + } + Collection collection = new Collection(sharedRealm, table, nativeCreateSnapshot(nativePtr)); + collection.isSnapshot = true; + return collection; } @Override @@ -463,6 +484,8 @@ public void load() { private static native long nativeGetFinalizerPtr(); private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, SortDescriptor sortDesc, SortDescriptor distinctDesc); + private static native long nativeCreateResultsFromLinkView(long sharedRealmNativePtr, long linkViewPtr, + SortDescriptor sortDesc); private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); private static native long nativeFirstRow(long nativePtr); From e7aefeb532757d11bc8aeacc708b33533595992a Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 21 Feb 2017 08:03:35 +0900 Subject: [PATCH 0501/2110] now publishToMavenLocal in realm/realm-library/build.gradle depends on assembleRelease task insteadof assemble task in order to improve build time. (#4207) --- realm/realm-library/build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ac7f1affbd..9b361cc5f4 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -500,8 +500,10 @@ task deployCore(group: 'build setup', description: 'Deploy the latest version of } } -publishToMavenLocal.dependsOn assemble -preBuild.dependsOn deployCore +project.afterEvaluate { + publishToMavenLocal.dependsOn assembleRelease + preBuild.dependsOn deployCore +} if (project.hasProperty('dontCleanJniFiles')) { project.afterEvaluate { From fe403d412daa80a488fb593ebc92c55cb06d1fa1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 21 Feb 2017 13:13:06 +0800 Subject: [PATCH 0502/2110] Fix flaky test There is no guarantee that posted event will be arrived later than Object Store notification. --- .../io/realm/TypeBasedNotificationsTests.java | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index d6af71de24..cbeced6a7b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -754,18 +754,6 @@ public void onChange(Dog object) { public void multiple_callbacks_should_be_invoked_realmresults_sync() { final int NUMBER_OF_LISTENERS = 7; final Realm realm = looperThread.realm; - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(NUMBER_OF_LISTENERS, typebasedCommitInvocations.get()); - looperThread.testComplete(); - } - }); - } - }); realm.beginTransaction(); Dog akamaru = realm.createObject(Dog.class); @@ -776,8 +764,12 @@ public void run() { for (int i = 0; i < NUMBER_OF_LISTENERS; i++) { dogs.addChangeListener(new RealmChangeListener>() { @Override - public void onChange(RealmResults object) { - typebasedCommitInvocations.incrementAndGet(); + public void onChange(RealmResults results) { + assertEquals(17, results.first().getAge()); + if (typebasedCommitInvocations.incrementAndGet() == NUMBER_OF_LISTENERS) { + looperThread.testComplete(); + } + assertTrue(typebasedCommitInvocations.get() <= NUMBER_OF_LISTENERS); } }); } From 8cba9ec0aa03037b664c2e12c6c3ef8ac2d77ca2 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 20 Feb 2017 21:30:51 +0800 Subject: [PATCH 0503/2110] Re-enable tests and checks --- .../androidTest/java/io/realm/RealmObjectTests.java | 12 ++++++------ .../java/io/realm/TypeBasedNotificationsTests.java | 6 ------ .../java/io/realm/internal/RealmNotifierTests.java | 10 +++------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 8e8f6a383e..20395358d1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -281,22 +281,22 @@ private void removeOneByOne(boolean removeFromFront) { // Checks initial size. RealmResults dogs = realm.where(Dog.class).findAll(); - assertEquals(TEST_SIZE, dogs.size()); + OrderedRealmCollectionSnapshot snapshot = dogs.createSnapshot(); + assertEquals(TEST_SIZE, snapshot.size()); // Checks that calling deleteFromRealm doesn't remove the object from the RealmResult. realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { - dogs.get(removeFromFront ? i : TEST_SIZE - 1 - i).deleteFromRealm(); + snapshot.get(removeFromFront ? i : TEST_SIZE - 1 - i).deleteFromRealm(); } realm.commitTransaction(); - assertEquals(TEST_SIZE, dogs.size()); - assertEquals(0, realm.where(Dog.class).count()); + assertEquals(TEST_SIZE, snapshot.size()); + assertEquals(0, dogs.size()); } - // Tests calling deleteFromRealm on a RealmResults instead of RealmResults.remove(). + // Tests calling deleteFromRealm on a OrderedRealmCollectionSnapshot instead of RealmResults.remove(). @Test - @Ignore("Enable this test when implementing RealmCollectionSnapshot") public void deleteFromRealm_atPosition() { removeOneByOne(REMOVE_FIRST); removeOneByOne(REMOVE_LAST); diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index cbeced6a7b..607ce7a705 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -569,8 +569,6 @@ public void execute(Realm realm) { // UC 1 Sync RealmResults. @Test @RunTestInLooperThread - @Ignore("Flaky test because of Object Store always run Results query callbacks even " + - "if the query returned and nothing changes.") public void callback_with_relevant_commit_realmresults_sync() { final Realm realm = looperThread.realm; @@ -581,8 +579,6 @@ public void callback_with_relevant_commit_realmresults_sync() { realm.commitTransaction(); final RealmResults dogs = realm.where(Dog.class).findAll(); - // Execute the query. - dogs.first(); looperThread.keepStrongReference.add(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override @@ -1162,8 +1158,6 @@ public void onChange(RealmResults object) { // "invalid" RealmResults. @Test @RunTestInLooperThread - // FIXME: https://github.com/realm/realm-core/pull/2385 - @Ignore("Enable this after core 2.3.1 released!!") public void changeListener_onResultsBuiltOnDeletedLinkView() { final Realm realm = looperThread.realm; realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 1dfc102df6..6c70f71d1a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -45,8 +45,7 @@ public class RealmNotifierTests { @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); - private RealmConfiguration config; - Capabilities capabilitiesCanDeliver = new Capabilities() { + private Capabilities capabilitiesCanDeliver = new Capabilities() { @Override public boolean canDeliverNotification() { return true; @@ -59,7 +58,6 @@ public void checkCanDeliverNotification(String exceptionMessage) { @Before public void setUp() throws Exception { - config = configFactory.createConfiguration(); } @After @@ -134,8 +132,7 @@ public void onChange(SharedRealm sharedRealm) { int listenerCount = listenerCounter.addAndGet(1); assertEquals(commits, listenerCount); if (commits == TIMES) { - // FIXME: Enable this after https://github.com/realm/realm-object-store/pull/318 fixed - //sharedRealm.close(); + sharedRealm.close(); looperThread.testComplete(); } else { makeRemoteChanges(looperThread.realmConfiguration); @@ -162,8 +159,7 @@ public void onChange(Integer dummy) { sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { - // FIXME: Enable this after https://github.com/realm/realm-object-store/pull/318 fixed - //sharedRealm.close(); + sharedRealm.close(); looperThread.testComplete(); } }); From 941e2d0c97dabab8e4d4d43340702adf3f867904 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 21 Feb 2017 17:39:13 +0900 Subject: [PATCH 0504/2110] Fix log level in jni (#4208) * fix log levels (#4204) * fix unstable test * Revert "fix unstable test" This reverts commit 30f8d5ff24bd04737b97765b8e62096845ed634f. * fix typo --- CHANGELOG.md | 4 ++++ realm/realm-library/src/main/cpp/jni_util/log.hpp | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0499ddeab6..16cce6ab6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 2.3.2 +### Bug fixes + +* Fixed log levels in JNI layer (#4204). + ### Internal * Updated to Realm Sync v1.0.4. diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp index ad29c6ede7..930fe81889 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -87,19 +87,19 @@ class Log { // Helper functions for logging with REALM_JNI tag. inline static void t(const char* message) { - shared().log(error, REALM_JNI_TAG, nullptr, message); + shared().log(trace, REALM_JNI_TAG, nullptr, message); } inline static void d(const char* message) { - shared().log(error, REALM_JNI_TAG, nullptr, message); + shared().log(debug, REALM_JNI_TAG, nullptr, message); } inline static void i(const char* message) { - shared().log(error, REALM_JNI_TAG, nullptr, message); + shared().log(info, REALM_JNI_TAG, nullptr, message); } inline static void w(const char* message) { - shared().log(error, REALM_JNI_TAG, nullptr, message); + shared().log(warn, REALM_JNI_TAG, nullptr, message); } inline static void e(const char* message) { @@ -107,7 +107,7 @@ class Log { } inline static void f(const char* message) { - shared().log(error, REALM_JNI_TAG, nullptr, message); + shared().log(fatal, REALM_JNI_TAG, nullptr, message); } template From 8eb34e8f0537fdf9dd44499b95d5d1ce5cd2c358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=AE=B6?= Date: Tue, 21 Feb 2017 18:15:13 +0800 Subject: [PATCH 0505/2110] fix README format issue. (#4212) * fix README format issue. * Align lines. --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b4dc035638..9c485ca978 100644 --- a/README.md +++ b/README.md @@ -175,11 +175,12 @@ To run these tests you must have a device connected to the build computer and th 1. Connect an Android device and verify that that the command `adb devices` shows a connected device: - ```sh - adb devices - List of devices attached - 004c03eb5615429f device - ``` + ```sh + adb devices + List of devices attached + 004c03eb5615429f device + ``` + 2. Run instrumentation tests: ```sh From 670b42b5362e8c1f3c7017a382a8159c40ffedb8 Mon Sep 17 00:00:00 2001 From: Anas Ambri Date: Tue, 21 Feb 2017 11:34:50 +0100 Subject: [PATCH 0506/2110] Fix documentation for .directory() method (#4211) There is no `context.getFiles()` method. The correct documentation for the `directory()` should read `The default value is {@code context.getFilesDir()}` instead of `The default value is {@code context.getFiles()}` --- .../src/main/java/io/realm/RealmConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 0a006b559b..cb6bddc599 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -429,7 +429,7 @@ public Builder name(String filename) { } /** - * Specifies the directory where the Realm file will be saved. The default value is {@code context.getFiles()}. + * Specifies the directory where the Realm file will be saved. The default value is {@code context.getFilesDir()}. * If the directory does not exist, it will be created. * * @param directory the directory to save the Realm file in. Directory must be writable. From ffe5bdfc7d5f30bde77c23712eda001a8bdbe6f7 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 21 Feb 2017 19:40:02 +0900 Subject: [PATCH 0507/2110] improve performance of getters and setters in proxy classes (#4206) * improve performance of getters and setters in proxy classes This change is a part of fixes of #3809. * removed unused argment * Update CHANGELOG.md --- CHANGELOG.md | 7 +- .../realm/transformer/BytecodeModifier.groovy | 7 + .../realm/transformer/RealmTransformer.groovy | 1 + .../processor/RealmProxyClassGenerator.java | 26 +-- .../io/realm/AllTypesRealmProxy.java | 96 +------- .../io/realm/BooleansRealmProxy.java | 46 +--- .../io/realm/NullTypesRealmProxy.java | 216 +----------------- .../resources/io/realm/SimpleRealmProxy.java | 26 +-- .../java/io/realm/DynamicRealmObject.java | 5 + .../io/realm/internal/RealmObjectProxy.java | 1 + 10 files changed, 29 insertions(+), 402 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16cce6ab6b..1d917f203b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,16 @@ * Updated to Realm Sync v1.0.4. * Updated to Realm Core v2.3.1. +### Enhancements + +* Improved performance of getters and setters in proxy classes. ## 2.3.1 ### Enhancements * [ObjectServer] The `serverUrl` given to `SyncConfiguration.Builder()` is now more lenient and will also accept only paths as argument (#4144). +* [ObjectServer] Add a timer to refresh periodically the access_token. ### Bug fixes @@ -24,9 +28,6 @@ * Bug causing classes to be replaced by classes already in Gradle's classpath (#3568). * NullPointerException when notifying a single object that it changed (#4086). -### Enhancements - -* [ObjectServer] Add a timer to refresh periodically the access_token. ## 2.3.0 diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index 82f3ac292b..4850279490 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -85,6 +85,13 @@ class BytecodeModifier { clazz.addInterface(proxyInterface) } + public static void callInjectObjectContextFromDefaultConstructor(CtClass clazz) { + def defaultConstructor = clazz.getDeclaredConstructor() + defaultConstructor.insertBeforeBody('if ($0 instanceof io.realm.internal.RealmObjectProxy) {' + + ' ((io.realm.internal.RealmObjectProxy) $0).realm$injectObjectContext();' + + ' }') + } + /** * This class goes through all the field access behaviours of a class and replaces field accesses with * the appropriate accessor. diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index e6e4813551..6ab5867bf1 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -131,6 +131,7 @@ class RealmTransformer extends Transform { inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) BytecodeModifier.addRealmProxyInterface(it, classPool) + BytecodeModifier.callInjectObjectContextFromDefaultConstructor(it) } // Use accessors instead of direct field access diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index eb99fbeb6e..08d6b83387 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -228,9 +228,6 @@ private void emitClassFields(JavaWriter writer) throws IOException { private void emitConstructor(JavaWriter writer) throws IOException { // FooRealmProxy(ColumnInfo) writer.beginConstructor(EnumSet.noneOf(Modifier.class)); - writer.beginControlFlow("if (proxyState == null)") - .emitStatement("injectObjectContext()") - .endControlFlow(); writer.emitStatement("proxyState.setConstructionFinished()"); writer.endConstructor(); writer.emitEmptyLine(); @@ -250,7 +247,6 @@ private void emitAccessors(final JavaWriter writer) throws IOException { // Getter writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); - emitCodeForInjectingObjectContext(writer); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here. @@ -276,7 +272,6 @@ private void emitAccessors(final JavaWriter writer) throws IOException { // Setter writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForInjectingObjectContext(writer); emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { @Override public void emit(JavaWriter writer) throws IOException { @@ -331,7 +326,6 @@ public void emit(JavaWriter writer) throws IOException { // Getter writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); - emitCodeForInjectingObjectContext(writer); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.beginControlFlow("if (proxyState.getRow$realm().isNullLink(%s))", fieldIndexVariableReference(field)); writer.emitStatement("return null"); @@ -343,7 +337,6 @@ public void emit(JavaWriter writer) throws IOException { // Setter writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForInjectingObjectContext(writer); emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { @Override public void emit(JavaWriter writer) throws IOException { @@ -395,7 +388,6 @@ public void emit(JavaWriter writer) throws IOException { // Getter writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); - emitCodeForInjectingObjectContext(writer); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.emitSingleLineComment("use the cached value if available"); writer.beginControlFlow("if (" + fieldName + "RealmList != null)"); @@ -412,7 +404,6 @@ public void emit(JavaWriter writer) throws IOException { // Setter writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForInjectingObjectContext(writer); emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { @Override public void emit(JavaWriter writer) throws IOException { @@ -462,17 +453,6 @@ public void emit(JavaWriter writer) throws IOException { } } - private void emitCodeForInjectingObjectContext(JavaWriter writer) throws IOException { - // if invoked from model's constructor, inject BaseRealm and Row - writer.beginControlFlow("if (proxyState == null)"); - { - writer.emitSingleLineComment("Called from model's constructor. Inject context."); - writer.emitStatement("injectObjectContext()"); - } - writer.endControlFlow(); - writer.emitEmptyLine(); - } - private interface CodeEmitter { void emit(JavaWriter writer) throws IOException; } @@ -494,10 +474,11 @@ private void emitCodeForUnderConstruction(JavaWriter writer, boolean isPrimaryKe } private void emitInjectContextMethod(JavaWriter writer) throws IOException { + writer.emitAnnotation("Override"); writer.beginMethod( "void", // Return type - "injectObjectContext", // Method name - EnumSet.of(Modifier.PRIVATE) // Modifiers + "realm$injectObjectContext", // Method name + EnumSet.of(Modifier.PUBLIC) // Modifiers ); // Argument type & argument name writer.emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()"); @@ -512,7 +493,6 @@ private void emitInjectContextMethod(JavaWriter writer) throws IOException { writer.emitEmptyLine(); } - private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); writer.beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 7bf9205056..fe0cd0448b 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -110,13 +110,11 @@ public final AllTypesColumnInfo clone() { } AllTypesRealmProxy() { - if (proxyState == null) { - injectObjectContext(); - } proxyState.setConstructionFinished(); } - private void injectObjectContext() { + @Override + public void realm$injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (AllTypesColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.AllTypes.class, this); @@ -128,21 +126,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public String realmGet$columnString() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.columnStringIndex); } public void realmSet$columnString(String value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { // default value of the primary key is always ignored. return; @@ -154,21 +142,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public long realmGet$columnLong() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (long) proxyState.getRow$realm().getLong(columnInfo.columnLongIndex); } public void realmSet$columnLong(long value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -184,21 +162,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public float realmGet$columnFloat() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (float) proxyState.getRow$realm().getFloat(columnInfo.columnFloatIndex); } public void realmSet$columnFloat(float value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -214,21 +182,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public double realmGet$columnDouble() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (double) proxyState.getRow$realm().getDouble(columnInfo.columnDoubleIndex); } public void realmSet$columnDouble(double value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -244,21 +202,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public boolean realmGet$columnBoolean() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.columnBooleanIndex); } public void realmSet$columnBoolean(boolean value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -274,21 +222,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Date realmGet$columnDate() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.columnDateIndex); } public void realmSet$columnDate(Date value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -310,21 +248,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public byte[] realmGet$columnBinary() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.columnBinaryIndex); } public void realmSet$columnBinary(byte[] value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -345,11 +273,6 @@ private void injectObjectContext() { } public some.test.AllTypes realmGet$columnObject() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNullLink(columnInfo.columnObjectIndex)) { return null; @@ -358,11 +281,6 @@ private void injectObjectContext() { } public void realmSet$columnObject(some.test.AllTypes value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -404,11 +322,6 @@ private void injectObjectContext() { } public RealmList realmGet$columnRealmList() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); // use the cached value if available if (columnRealmListRealmList != null) { @@ -421,11 +334,6 @@ private void injectObjectContext() { } public void realmSet$columnRealmList(RealmList value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index da1971a009..07b6be83b5 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -84,13 +84,11 @@ public final BooleansColumnInfo clone() { } BooleansRealmProxy() { - if (proxyState == null) { - injectObjectContext(); - } proxyState.setConstructionFinished(); } - private void injectObjectContext() { + @Override + public void realm$injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (BooleansColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.Booleans.class, this); @@ -102,21 +100,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public boolean realmGet$done() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.doneIndex); } public void realmSet$done(boolean value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -132,21 +120,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public boolean realmGet$isReady() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.isReadyIndex); } public void realmSet$isReady(boolean value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -162,21 +140,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public boolean realmGet$mCompleted() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.mCompletedIndex); } public void realmSet$mCompleted(boolean value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -192,21 +160,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public boolean realmGet$anotherBoolean() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.anotherBooleanIndex); } public void realmSet$anotherBoolean(boolean value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 88e3de0cca..529599e1a0 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -169,13 +169,11 @@ public final NullTypesColumnInfo clone() { } NullTypesRealmProxy() { - if (proxyState == null) { - injectObjectContext(); - } proxyState.setConstructionFinished(); } - private void injectObjectContext() { + @Override + public void realm$injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (NullTypesColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.NullTypes.class, this); @@ -187,21 +185,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public String realmGet$fieldStringNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNotNullIndex); } public void realmSet$fieldStringNotNull(String value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -223,21 +211,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public String realmGet$fieldStringNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNullIndex); } public void realmSet$fieldStringNull(String value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -261,21 +239,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Boolean realmGet$fieldBooleanNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.fieldBooleanNotNullIndex); } public void realmSet$fieldBooleanNotNull(Boolean value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -297,11 +265,6 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Boolean realmGet$fieldBooleanNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldBooleanNullIndex)) { return null; @@ -310,11 +273,6 @@ private void injectObjectContext() { } public void realmSet$fieldBooleanNull(Boolean value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -338,21 +296,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public byte[] realmGet$fieldBytesNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNotNullIndex); } public void realmSet$fieldBytesNotNull(byte[] value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -374,21 +322,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public byte[] realmGet$fieldBytesNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNullIndex); } public void realmSet$fieldBytesNull(byte[] value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -412,21 +350,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Byte realmGet$fieldByteNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (byte) proxyState.getRow$realm().getLong(columnInfo.fieldByteNotNullIndex); } public void realmSet$fieldByteNotNull(Byte value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -448,11 +376,6 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Byte realmGet$fieldByteNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldByteNullIndex)) { return null; @@ -461,11 +384,6 @@ private void injectObjectContext() { } public void realmSet$fieldByteNull(Byte value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -489,21 +407,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Short realmGet$fieldShortNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (short) proxyState.getRow$realm().getLong(columnInfo.fieldShortNotNullIndex); } public void realmSet$fieldShortNotNull(Short value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -525,11 +433,6 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Short realmGet$fieldShortNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldShortNullIndex)) { return null; @@ -538,11 +441,6 @@ private void injectObjectContext() { } public void realmSet$fieldShortNull(Short value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -566,21 +464,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Integer realmGet$fieldIntegerNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (int) proxyState.getRow$realm().getLong(columnInfo.fieldIntegerNotNullIndex); } public void realmSet$fieldIntegerNotNull(Integer value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -602,11 +490,6 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Integer realmGet$fieldIntegerNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldIntegerNullIndex)) { return null; @@ -615,11 +498,6 @@ private void injectObjectContext() { } public void realmSet$fieldIntegerNull(Integer value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -643,21 +521,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Long realmGet$fieldLongNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (long) proxyState.getRow$realm().getLong(columnInfo.fieldLongNotNullIndex); } public void realmSet$fieldLongNotNull(Long value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -679,11 +547,6 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Long realmGet$fieldLongNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldLongNullIndex)) { return null; @@ -692,11 +555,6 @@ private void injectObjectContext() { } public void realmSet$fieldLongNull(Long value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -720,21 +578,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Float realmGet$fieldFloatNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (float) proxyState.getRow$realm().getFloat(columnInfo.fieldFloatNotNullIndex); } public void realmSet$fieldFloatNotNull(Float value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -756,11 +604,6 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Float realmGet$fieldFloatNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldFloatNullIndex)) { return null; @@ -769,11 +612,6 @@ private void injectObjectContext() { } public void realmSet$fieldFloatNull(Float value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -797,21 +635,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Double realmGet$fieldDoubleNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (double) proxyState.getRow$realm().getDouble(columnInfo.fieldDoubleNotNullIndex); } public void realmSet$fieldDoubleNotNull(Double value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -833,11 +661,6 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Double realmGet$fieldDoubleNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldDoubleNullIndex)) { return null; @@ -846,11 +669,6 @@ private void injectObjectContext() { } public void realmSet$fieldDoubleNull(Double value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -874,21 +692,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Date realmGet$fieldDateNotNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.fieldDateNotNullIndex); } public void realmSet$fieldDateNotNull(Date value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -910,11 +718,6 @@ private void injectObjectContext() { @SuppressWarnings("cast") public Date realmGet$fieldDateNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNull(columnInfo.fieldDateNullIndex)) { return null; @@ -923,11 +726,6 @@ private void injectObjectContext() { } public void realmSet$fieldDateNull(Date value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -950,11 +748,6 @@ private void injectObjectContext() { } public some.test.NullTypes realmGet$fieldObjectNull() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNullLink(columnInfo.fieldObjectNullIndex)) { return null; @@ -963,11 +756,6 @@ private void injectObjectContext() { } public void realmSet$fieldObjectNull(some.test.NullTypes value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index bd50bf8120..f6d219c999 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -74,13 +74,11 @@ public final SimpleColumnInfo clone() { } SimpleRealmProxy() { - if (proxyState == null) { - injectObjectContext(); - } proxyState.setConstructionFinished(); } - private void injectObjectContext() { + @Override + public void realm$injectObjectContext() { final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (SimpleColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.Simple.class, this); @@ -92,21 +90,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public String realmGet$name() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.nameIndex); } public void realmSet$name(String value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -130,21 +118,11 @@ private void injectObjectContext() { @SuppressWarnings("cast") public int realmGet$age() { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - proxyState.getRealm$realm().checkIfValid(); return (int) proxyState.getRow$realm().getLong(columnInfo.ageIndex); } public void realmSet$age(int value) { - if (proxyState == null) { - // Called from model's constructor. Inject context. - injectObjectContext(); - } - if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 1d35df1b8c..7b863b6c28 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -835,6 +835,11 @@ public String toString() { return sb.toString(); } + @Override + public void realm$injectObjectContext() { + // nothing to do for DynamicRealmObject + } + @Override public ProxyState realmGet$proxyState() { return proxyState; diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java b/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java index 69a4b2d71e..ee7c43f962 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java @@ -25,6 +25,7 @@ * Ideally all the static methods was also present here, but that is not supported before Java 8. */ public interface RealmObjectProxy extends RealmModel { + void realm$injectObjectContext(); ProxyState realmGet$proxyState(); /** * Tuple class for saving meta data about a cached RealmObject. From 662d54967208f032c7bd021fa016f21a0b17c5d5 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Feb 2017 10:45:42 +0800 Subject: [PATCH 0508/2110] Fix build base with core only (#4220) - The error message shout be sync lib - The util.hpp contains some sync only headers. It causes problem when build with core only for base flavour. - We have an agreement that use macro to do casting should be avoided since it is less readable. --- realm/realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../io_realm_internal_objectserver_ObjectServerSession.cpp | 6 +++--- realm/realm-library/src/main/cpp/util.hpp | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 5b84b34a76..d460f3b457 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -94,7 +94,7 @@ if (NOT EXISTS ${sync_lib_PATH}) elseif (ARM64_V8A) set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm64.a) else() - message(FATAL_ERROR "Cannot find core lib file: ${core_lib_PATH}") + message(FATAL_ERROR "Cannot find core lib file: ${sync_lib_PATH}") endif() endif() add_library(lib_realm_sync STATIC IMPORTED) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp index 0caa7acccc..39a7b559ba 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp @@ -73,7 +73,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_n (JNIEnv *, jobject, jlong sessionPointer) { TR_ENTER() - JniSession* session = SS(sessionPointer); + JniSession* session = reinterpret_cast(sessionPointer); delete session; // TODO Can we avoid killing the session here? } @@ -82,7 +82,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_n { TR_ENTER() try { - JniSession* session_wrapper = SS(sessionPointer); + JniSession* session_wrapper = reinterpret_cast(sessionPointer); JStringAccessor token_tmp(env, accessToken); // throws StringData access_token = StringData(token_tmp); @@ -97,7 +97,7 @@ Java_io_realm_internal_objectserver_ObjectServerSession_nativeNotifyCommitHappen { TR_ENTER() try { - JniSession* session_wrapper = SS(sessionPointer); + JniSession* session_wrapper = reinterpret_cast(sessionPointer); session_wrapper->get_session()->nonsync_transact_notify(version); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 021752f6c3..46940371a1 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -31,7 +31,6 @@ #include #include #include -#include #include @@ -86,8 +85,6 @@ std::string num_to_string(T pNumber) #define Q(x) reinterpret_cast(x) #define ROW(x) reinterpret_cast(x) #define HO(T, ptr) reinterpret_cast* >(ptr) -#define SC(ptr) reinterpret_cast(ptr) -#define SS(ptr) reinterpret_cast(ptr) // Exception handling enum ExceptionKind { From cc829034b1f2d42f5116a39046d43b3589b445ce Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Feb 2017 11:10:54 +0800 Subject: [PATCH 0509/2110] Implement fine gained notification (#4191) - Add RealmObservable and RealmCollectionObservable interfaces. - Enable detailed change information for RealmResults through OrderedCollectionChange interface. - Fix a bug in the ObserverPairList which could cause the removed listener gets called if it was removed during previous listener iteration. Fix #989 --- CHANGELOG.md | 6 +- .../java/io/realm/NotificationsTest.java | 4 +- .../OrderedCollectionChangeSetTests.java | 357 ++++++++++++++++++ .../java/io/realm/RealmResultsTests.java | 6 +- .../io/realm/internal/CollectionTests.java | 2 +- .../realm/internal/ObserverPairListTests.java | 14 +- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../main/cpp/io_realm_internal_Collection.cpp | 17 +- .../io_realm_internal_CollectionChangeSet.cpp | 127 +++++++ .../src/main/java/io/realm/BaseRealm.java | 4 +- .../src/main/java/io/realm/DynamicRealm.java | 19 +- .../io/realm/OrderedCollectionChangeSet.java | 99 +++++ .../OrderedRealmCollectionChangeListener.java | 40 ++ .../src/main/java/io/realm/Realm.java | 19 +- .../io/realm/RealmCollectionObservable.java | 50 +++ .../main/java/io/realm/RealmObservable.java | 55 +++ .../src/main/java/io/realm/RealmResults.java | 70 ++-- .../java/io/realm/internal/Collection.java | 95 +++-- .../realm/internal/CollectionChangeSet.java | 129 +++++++ .../main/java/io/realm/internal/Context.java | 2 + .../io/realm/internal/ObserverPairList.java | 32 +- .../java/io/realm/internal/RealmNotifier.java | 3 +- 22 files changed, 1057 insertions(+), 95 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_CollectionChangeSet.cpp create mode 100644 realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java create mode 100644 realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java create mode 100644 realm/realm-library/src/main/java/io/realm/RealmCollectionObservable.java create mode 100644 realm/realm-library/src/main/java/io/realm/RealmObservable.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 50570addbf..eeeb0031c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,11 @@ ### Breaking changes * `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object (#2947). -* `RealmResults` is auto-updated continuously. Any transaction on the current thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. The standard `RealmResults.iterator()` will continue to work as normal, which means that you can still delete or modify elements without impacting the iterator. The same is not true for simple for-loops. In some cases a simple for-loop will not work (https://realm.io/docs/java/2.3.1/api/io/realm/OrderedRealmCollection.html#loops), and you must use the new createSnapshot() method. +* `RealmResults` is auto-updated continuously. Any transaction on the current thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. The standard `RealmResults.iterator()` will continue to work as normal, which means that you can still delete or modify elements without impacting the iterator. The same is not true for simple for-loops. In some cases a simple for-loop will not work (https://realm.io/docs/java/3.0.0/api/io/realm/OrderedRealmCollection.html#loops), and you must use the new createSnapshot() method. + +### Deprecated + +* `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index ebfbb16f79..fd7d5d8035 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -465,7 +465,7 @@ public void onChange(Realm object) { @Override public void onChange(Realm object) { listenerBCalled.incrementAndGet(); - if (listenerACalled.get() == 1) { + if (listenerBCalled.get() == 1) { // 2. Reverse order. realm.removeAllChangeListeners(); realm.addChangeListener(this); @@ -476,6 +476,8 @@ public void onChange(Realm object) { public void execute(Realm realm) { } }); + } else if (listenerBCalled.get() == 2) { + assertEquals(1, listenerACalled.get()); } } }; diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java new file mode 100644 index 0000000000..a1134f85b3 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -0,0 +1,357 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; + +import io.realm.entities.AllTypes; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.fail; +import static org.junit.Assert.assertArrayEquals; + +// Tests for the ordered collection fine grained notifications. +// This should be expanded to test the notifications for RealmList as well in the future. +@RunWith(AndroidJUnit4.class) +public class OrderedCollectionChangeSetTests { + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + private void populateData(Realm realm, int testSize) { + realm.beginTransaction(); + for (int i = 0; i < testSize; i++) { + realm.createObject(AllTypes.class).setColumnLong(i); + } + realm.commitTransaction(); + } + + // The args should be [startIndex1, length1, startIndex2, length2, ...] + private void checkRanges(OrderedCollectionChangeSet.Range[] ranges, int... indexAndLen) { + if ((indexAndLen.length % 2 != 0)) { + fail("The 'indexAndLen' array length is not an even number."); + } + if (ranges.length != indexAndLen.length / 2) { + fail("The lengths of 'ranges' and 'indexAndLen' don't match."); + } + for (int i = 0; i < ranges.length; i++) { + OrderedCollectionChangeSet.Range range = ranges[i]; + int startIndex = indexAndLen[i * 2]; + int length = indexAndLen[i * 2 + 1]; + if (range.startIndex != startIndex || range.length != length) { + fail("Range at index " + i + " doesn't match start index " + startIndex + " length " + length + "."); + } + } + } + + // Deletes AllTypes objects which's columnLong is in the indices array. + private void deleteObjects(Realm realm, int... indices) { + for (int index : indices) { + realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, index).findFirst().deleteFromRealm(); + } + } + + // Creates AllTypes objects with columnLong set to the value elements in indices array. + private void createObjects(Realm realm, int... indices) { + for (int index : indices) { + realm.createObject(AllTypes.class).setColumnLong(index); + } + } + + // Modifies AllTypes objects which's columnLong is in the indices array. + private void modifyObjects(Realm realm, int... indices) { + for (int index : indices) { + AllTypes obj = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, index).findFirst(); + assertNotNull(obj); + obj.setColumnString("modified"); + } + } + + @Test + @RunTestInLooperThread + public void deletion() { + Realm realm = looperThread.realm; + populateData(realm, 10); + RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + checkRanges(changeSet.getDeletionRanges(), + 0, 1, + 2, 3, + 8, 2); + assertArrayEquals(changeSet.getDeletions(), new int[]{0, 2, 3, 4, 8, 9}); + assertEquals(0, changeSet.getChangeRanges().length); + assertEquals(0, changeSet.getInsertionRanges().length); + assertEquals(0, changeSet.getChanges().length); + assertEquals(0, changeSet.getInsertions().length); + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + deleteObjects(realm, + 0, + 2, 3, 4, + 8, 9); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread + public void insertion() { + Realm realm = looperThread.realm; + realm.beginTransaction(); + createObjects(realm, 0, 2, 5, 6, 7, 9); + realm.commitTransaction(); + RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + checkRanges(changeSet.getInsertionRanges(), + 1, 1, + 3, 2, + 8, 1); + assertArrayEquals(changeSet.getInsertions(), new int[]{1, 3, 4, 8}); + assertEquals(0, changeSet.getChangeRanges().length); + assertEquals(0, changeSet.getDeletionRanges().length); + assertEquals(0, changeSet.getChanges().length); + assertEquals(0, changeSet.getDeletions().length); + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + createObjects(realm, + 1, + 3, 4, + 8); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread + public void changes() { + Realm realm = looperThread.realm; + populateData(realm, 10); + RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + checkRanges(changeSet.getChangeRanges(), + 0, 1, + 2, 3, + 8, 2); + assertArrayEquals(changeSet.getChanges(), new int[]{0, 2, 3, 4, 8, 9}); + assertEquals(0, changeSet.getInsertionRanges().length); + assertEquals(0, changeSet.getDeletionRanges().length); + assertEquals(0, changeSet.getInsertions().length); + assertEquals(0, changeSet.getDeletions().length); + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + modifyObjects(realm, + 0, + 2, 3, 4, + 8, 9); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread + public void moves() { + Realm realm = looperThread.realm; + populateData(realm, 10); + RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + checkRanges(changeSet.getDeletionRanges(), + 0, 1, + 9, 1); + assertArrayEquals(changeSet.getDeletions(), new int[]{0, 9}); + checkRanges(changeSet.getInsertionRanges(), + 0, 1, + 9, 1); + assertArrayEquals(changeSet.getInsertions(), new int[]{0, 9}); + assertEquals(0, changeSet.getChangeRanges().length); + assertEquals(0, changeSet.getChanges().length); + looperThread.testComplete(); + } + }); + realm.beginTransaction(); + realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 0).findFirst().setColumnLong(10); + realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 9).findFirst().setColumnLong(0); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread + public void mixed_changes() { + Realm realm = looperThread.realm; + populateData(realm, 10); + RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + checkRanges(changeSet.getDeletionRanges(), + 0, 2, + 5, 1); + assertArrayEquals(changeSet.getDeletions(), new int[]{0, 1, 5}); + + checkRanges(changeSet.getInsertionRanges(), + 0, 2, + 9, 2); + assertArrayEquals(changeSet.getInsertions(), new int[]{0, 1, 9, 10}); + + checkRanges(changeSet.getChangeRanges(), + 3, 2, + 8, 1); + assertArrayEquals(changeSet.getChanges(), new int[]{3, 4, 8}); + + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + createObjects(realm, 11, 12, -1, -2); + deleteObjects(realm, 0, 1, 5); + modifyObjects(realm, 12, 3, 4, 9); + realm.commitTransaction(); + // After transaction, '*' means the object has been modified. 12 has been modified as well, but it is created + // and modified in the same transaction, should not be counted in the changes range. + // [-1, -2, 2, *3, *4, 6, 7, 8, *9, 11, 12] + } + + // Change some objects then delete them. Only deletion changes should be sent. + @Test + @RunTestInLooperThread + public void changes_then_delete() { + Realm realm = looperThread.realm; + populateData(realm, 10); + RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + checkRanges(changeSet.getDeletionRanges(), + 0, 2, + 5, 1); + assertArrayEquals(changeSet.getDeletions(), new int[]{0, 1, 5}); + + assertEquals(0, changeSet.getInsertionRanges().length); + assertEquals(0, changeSet.getInsertions().length); + assertEquals(0, changeSet.getChangeRanges().length); + assertEquals(0, changeSet.getChanges().length); + + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + modifyObjects(realm, 0, 1, 5); + deleteObjects(realm, 0, 1, 5); + realm.commitTransaction(); + } + + // Insert some objects then delete them in the same transaction, the listener should not be triggered. + @Test + @RunTestInLooperThread + public void insert_then_delete() { + Realm realm = looperThread.realm; + populateData(realm, 10); + RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + fail("The listener should not be triggered since the collection has no changes compared with before."); + } + }); + + looperThread.postRunnableDelayed(new Runnable() { + @Override + public void run() { + looperThread.testComplete(); + } + }, 1000); + + realm.beginTransaction(); + createObjects(realm, 10, 11); + deleteObjects(realm, 10, 11); + realm.commitTransaction(); + } + + // The change set should empty when the async query returns at the first time. + @Test + @RunTestInLooperThread + public void emptyChangeSet_findAllAsync(){ + Realm realm = looperThread.realm; + populateData(realm, 10); + final RealmResults results = realm.where(AllTypes.class).findAllSortedAsync(AllTypes.FIELD_LONG); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + assertSame(collection, results); + assertEquals(9, collection.size()); + assertNull(changeSet); + looperThread.testComplete(); + } + }); + + final CountDownLatch bgDeletionLatch = new CountDownLatch(1); + // beginTransaction() will make the async query return immediately. So we have to delete an object in another + // thread. Also, the latch has to be counted down after transaction committed so the async query results can + // contain the modification in the background transaction. + new Thread(new Runnable() { + @Override + public void run() { + Realm realm = Realm.getInstance(looperThread.realmConfiguration) ; + realm.beginTransaction(); + realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 0).findFirst().deleteFromRealm(); + realm.commitTransaction(); + realm.close(); + bgDeletionLatch.countDown(); + } + }).start(); + TestHelper.awaitOrFail(bgDeletionLatch); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index a6757ee1a5..78946420d6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -979,7 +979,7 @@ public void run() { @UiThreadTest public void addChangeListener_null() { try { - collection.addChangeListener(null); + collection.addChangeListener((RealmChangeListener>) null); fail(); } catch (IllegalArgumentException ignored) { } @@ -1024,7 +1024,7 @@ public void run() { @UiThreadTest public void removeChangeListener_null() { try { - collection.removeChangeListener(null); + collection.removeChangeListener((RealmChangeListener) null); fail(); } catch (IllegalArgumentException ignored) { } @@ -1053,7 +1053,7 @@ public void onChange(RealmResults object) { looperThread.keepStrongReference.add(collection); collection.addChangeListener(listenerA); collection.addChangeListener(listenerB); - collection.removeChangeListeners(); + collection.removeAllChangeListeners(); realm.beginTransaction(); realm.createObject(AllTypes.class); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index a8473b1255..e63fd0fb1a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -408,7 +408,7 @@ public void onChange(Collection collection1) { } private static class TestIterator extends Collection.Iterator { - public TestIterator(Collection collection) { + TestIterator(Collection collection) { super(collection); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java index 8fa19fbacf..c06aa13900 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java @@ -132,18 +132,15 @@ public void remove() { // Create a new Integer 1 to see if the equality is checked by the same object. //noinspection UnnecessaryBoxing - pair = new TestObserverPair(new Integer(1), testListener); - observerPairs.remove(pair); + observerPairs.remove(new Integer(1), testListener); assertEquals(1, observerPairs.size()); // Different listener - pair = new TestObserverPair(ONE, new TestListener()); - observerPairs.remove(pair); + observerPairs.remove(ONE, new TestListener()); assertEquals(1, observerPairs.size()); // Should remove now - pair = new TestObserverPair(ONE, testListener); - observerPairs.remove(pair); + observerPairs.remove(ONE, testListener); assertEquals(0, observerPairs.size()); } @@ -240,7 +237,8 @@ public void onCalled(TestObserverPair pair, Object observer) { public void foreach_canRemove() { final AtomicInteger count = new AtomicInteger(0); final TestObserverPair pair1 = new TestObserverPair(ONE, new TestListener()); - final TestObserverPair pair2 = new TestObserverPair(TWO, new TestListener()); + final TestListener listener2 = new TestListener(); + final TestObserverPair pair2 = new TestObserverPair(TWO, listener2); final TestObserverPair pair3 = new TestObserverPair(THREE, new TestListener()); observerPairs.add(pair1); observerPairs.add(pair2); @@ -250,7 +248,7 @@ public void foreach_canRemove() { @Override public void onCalled(TestObserverPair pair, Object observer) { assertFalse(((Integer) observer) == 2); - observerPairs.remove(pair2); + observerPairs.remove(TWO, listener2); count.getAndIncrement(); } }); diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 383cfbf477..4aefd1f733 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -38,7 +38,7 @@ set(classes_LIST io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema io.realm.RealmObjectSchema io.realm.internal.Collection - io.realm.internal.NativeObjectReference + io.realm.internal.NativeObjectReference io.realm.internal.CollectionChangeSet ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index a7350f43a4..e17d47f692 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -263,7 +263,7 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst { TR_ENTER_PTR(native_ptr) - static JavaMethod notify_change_listeners(env, instance, "notifyChangeListeners", "(Z)V"); + static JavaMethod notify_change_listeners(env, instance, "notifyChangeListeners", "(J)V"); try { auto wrapper = reinterpret_cast(native_ptr); @@ -271,13 +271,22 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst wrapper->m_collection_weak_ref = JavaGlobalWeakRef(env, instance); } - auto cb = [=](realm::CollectionChangeSet const& changes, - std::exception_ptr /*err*/) { + auto cb = [=](CollectionChangeSet const& changes, std::exception_ptr err) { // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; + if (err) { + try { + std::rethrow_exception(err); + } catch(const std::exception& e) { + realm::jni_util::Log::e("Caught exception in collection change callback %1", e.what()); + return; + } + } + wrapper->m_collection_weak_ref.call_with_local_ref(env, [&] (JNIEnv* local_env, jobject collection_obj) { - local_env->CallVoidMethod(collection_obj, notify_change_listeners, changes.empty()); + local_env->CallVoidMethod(collection_obj, notify_change_listeners, + reinterpret_cast(changes.empty() ? 0 : new CollectionChangeSet(changes))); }); }; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_CollectionChangeSet.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_CollectionChangeSet.cpp new file mode 100644 index 0000000000..450324c171 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_CollectionChangeSet.cpp @@ -0,0 +1,127 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "io_realm_internal_CollectionChangeSet.h" + +#include + +#include "util.hpp" + +using namespace realm; + +static void finalize_changeset(jlong ptr); +static jintArray index_set_to_jint_array(JNIEnv* env, const IndexSet& index_set); +static jintArray index_set_to_indices_array(JNIEnv* env, const IndexSet& index_set); + +static void finalize_changeset(jlong ptr) +{ + TR_ENTER_PTR(ptr); + delete reinterpret_cast(ptr); +} + +static jintArray index_set_to_jint_array(JNIEnv* env, const IndexSet& index_set) +{ + if (index_set.empty()) { + return env->NewIntArray(0); + } + + std::vector ranges_vector; + for (auto& changes : index_set) { + ranges_vector.push_back(changes.first); + ranges_vector.push_back(changes.second - changes.first); + } + + if (ranges_vector.size() > io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH) { + std::ostringstream error_msg; + error_msg << "There are too many ranges changed in this change set. They cannot fit into an array." << + " ranges_vector's size: " << ranges_vector.size() << + " Java array's max size: " << io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH << "."; + ThrowException(env, IllegalState, error_msg.str()); + return nullptr; + } + jintArray jint_array = env->NewIntArray(static_cast(ranges_vector.size())); + env->SetIntArrayRegion(jint_array, 0, ranges_vector.size(), ranges_vector.data()); + return jint_array; +} + +static jintArray index_set_to_indices_array(JNIEnv* env, const IndexSet& index_set) +{ + if (index_set.empty()) { + return env->NewIntArray(0); + } + + std::vector indices_vector; + for (auto index : index_set.as_indexes()) { + indices_vector.push_back(index); + } + if (indices_vector.size() > io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH) { + std::ostringstream error_msg; + error_msg << "There are too many indices in this change set. They cannot fit into an array." << + " indices_vector's size: " << indices_vector.size() << + " Java array's max size: " << io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH << "."; + ThrowException(env, IllegalState, error_msg.str()); + return nullptr; + } + jintArray jint_array = env->NewIntArray(static_cast(indices_vector.size())); + env->SetIntArrayRegion(jint_array, 0, indices_vector.size(), indices_vector.data()); + return jint_array; +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_CollectionChangeSet_nativeGetFinalizerPtr(JNIEnv*, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_changeset); +} + +JNIEXPORT jintArray JNICALL +Java_io_realm_internal_CollectionChangeSet_nativeGetRanges(JNIEnv *env, jclass, jlong native_ptr, jint type) +{ + TR_ENTER_PTR(native_ptr) + // no throws + auto& change_set = *reinterpret_cast(native_ptr); + switch (type) { + case io_realm_internal_CollectionChangeSet_TYPE_DELETION: + return index_set_to_jint_array(env, change_set.deletions); + case io_realm_internal_CollectionChangeSet_TYPE_INSERTION: + return index_set_to_jint_array(env, change_set.insertions); + case io_realm_internal_CollectionChangeSet_TYPE_MODIFICATION: + return index_set_to_jint_array(env, change_set.modifications_new); + default: + REALM_UNREACHABLE(); + break; + } +} + +JNIEXPORT jintArray JNICALL +Java_io_realm_internal_CollectionChangeSet_nativeGetIndices(JNIEnv *env, jclass, jlong native_ptr, jint type) +{ + TR_ENTER_PTR(native_ptr) + // no throws + auto& change_set = *reinterpret_cast(native_ptr); + switch (type) { + case io_realm_internal_CollectionChangeSet_TYPE_DELETION: + return index_set_to_indices_array(env, change_set.deletions); + case io_realm_internal_CollectionChangeSet_TYPE_INSERTION: + return index_set_to_indices_array(env, change_set.insertions); + case io_realm_internal_CollectionChangeSet_TYPE_MODIFICATION: + return index_set_to_indices_array(env, change_set.modifications_new); + default: + REALM_UNREACHABLE(); + break; + } +} + diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 64eb001e0d..9396d46c79 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -141,7 +141,7 @@ protected void addListener(RealmChangeListener listener * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. * @see io.realm.RealmChangeListener */ - public void removeChangeListener(RealmChangeListener listener) { + protected void removeListener(RealmChangeListener listener) { if (listener == null) { throw new IllegalArgumentException("Listener should not be null"); } @@ -177,7 +177,7 @@ public void removeChangeListener(RealmChangeListener li * @throws IllegalStateException if you try to remove listeners from a non-Looper Thread. * @see io.realm.RealmChangeListener */ - public void removeAllChangeListeners() { + protected void removeAllListeners() { checkIfValid(); sharedRealm.capabilities.checkCanDeliverNotification("removeListener cannot be called on current thread."); sharedRealm.realmNotifier.removeChangeListeners(this); diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 1fce306c7e..86c894ca50 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -42,7 +42,7 @@ * @see Realm * @see RealmSchema */ -public class DynamicRealm extends BaseRealm { +public class DynamicRealm extends BaseRealm implements RealmObservable { private DynamicRealm(RealmConfiguration configuration) { super(configuration); @@ -134,10 +134,27 @@ public RealmQuery where(String className) { * @see #removeAllChangeListeners() * @see #waitForChange() */ + @Override public void addChangeListener(RealmChangeListener listener) { super.addListener(listener); } + /** + * {@inheritDoc} + */ + @Override + public void removeChangeListener(RealmChangeListener listener) { + super.removeListener(listener); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeAllChangeListeners() { + super.removeAllListeners(); + } + /** * Deletes all objects of the specified class from the Realm. * diff --git a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java new file mode 100644 index 0000000000..a162848569 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * This interface describes the changes made to a collection during the last update. + *

          + * {@link OrderedCollectionChangeSet} is passed to the {@link OrderedRealmCollectionChangeListener} which is registered + * by {@link RealmResults#addChangeListener(OrderedRealmCollectionChangeListener)}. + *

          + * The change information is available in two formats: a simple array of row indices in the collection for each type of + * change, or an array of {@link Range}s. + */ +public interface OrderedCollectionChangeSet { + /** + * The deleted indices in the previous version of the collection. + * + * @return the indices array. A zero-sized array will be returned if no objects were deleted. + */ + int[] getDeletions(); + + /** + * The inserted indices in the new version of the collection. + * + * @return the indices array. A zero-sized array will be returned if no objects were inserted. + */ + int[] getInsertions(); + + /** + * The modified indices in the new version of the collection. + *

          + * For {@link RealmResults}, this means that one or more of the properties of the object at the given index were + * modified (or an object linked to by that object was modified). + * + * @return the indices array. A zero-sized array will be returned if objects were modified. + */ + int[] getChanges(); + + /** + * The deleted ranges of objects in the previous version of the collection. + * + * @return the {@link Range} array. A zero-sized array will be returned if no objects were deleted. + */ + Range[] getDeletionRanges(); + + /** + * The inserted ranges of objects in the new version of the collection. + * + * @return the {@link Range} array. A zero-sized array will be returned if no objects were inserted. + */ + Range[] getInsertionRanges(); + + /** + * The modified ranges of objects in the new version of the collection. + * + * @return the {@link Range} array. A zero-sized array will be returned if no objects were modified. + */ + Range[] getChangeRanges(); + + /** + * + */ + class Range { + /** + * The start index of this change range. + */ + public final int startIndex; + + /** + * How many elements are inside this range. + */ + public final int length; + + /** + * Creates a {@link Range} with given start index and length. + * + * @param startIndex the start index of this change range. + * @param length how many elements are inside this range. + */ + public Range(int startIndex, int length) { + this.startIndex = startIndex; + this.length = length; + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java new file mode 100644 index 0000000000..8c51f2a570 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * {@link OrderedRealmCollectionChangeListener} can be registered with a {@link RealmResults} to receive a notification + * with a {@link OrderedCollectionChangeSet} to describe the details of what have been changed in the collection from + * last time. + *

          + * Realm instances on a thread without an {@link android.os.Looper} cannot register a + * {@link OrderedRealmCollectionChangeListener}. + *

          + * + * @see RealmResults#addChangeListener(OrderedRealmCollectionChangeListener) + */ +public interface OrderedRealmCollectionChangeListener { + + /** + * This will be called when the async query is finished the first time or the collection of objects has changed. + * + * @param collection the collection this listener is registered to. + * @param changeSet object with information about which rows in the collection were added, removed or modified. + * {@code null} is returned the first time an async query is completed. + */ + void onChange(T collection, OrderedCollectionChangeSet changeSet); +} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index aca42eeb84..746dfb862b 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -124,7 +124,7 @@ * @see ACID * @see Examples using Realm */ -public class Realm extends BaseRealm { +public class Realm extends BaseRealm implements RealmObservable { public static final String DEFAULT_REALM_NAME = RealmConfiguration.DEFAULT_REALM_NAME; @@ -1281,10 +1281,27 @@ public RealmQuery where(Class clazz) { * @see #removeChangeListener(RealmChangeListener) * @see #removeAllChangeListeners() */ + @Override public void addChangeListener(RealmChangeListener listener) { super.addListener(listener); } + /** + * {@inheritDoc} + */ + @Override + public void removeChangeListener(RealmChangeListener listener) { + super.removeListener(listener); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeAllChangeListeners() { + super.removeAllListeners(); + } + /** * Executes a given transaction on the Realm. {@link #beginTransaction()} and {@link #commitTransaction()} will be * called automatically. If any exception is thrown during the transaction {@link #cancelTransaction()} will be diff --git a/realm/realm-library/src/main/java/io/realm/RealmCollectionObservable.java b/realm/realm-library/src/main/java/io/realm/RealmCollectionObservable.java new file mode 100644 index 0000000000..dcb8a5b61e --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/RealmCollectionObservable.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * A collection class implementing this interface is capable of reporting fine-grained notifications about how the + * collection is changed. It will report insertions, deletions and changes, but not how an individual element + * changed. When a change is detected all registered listeners will be triggered. + *

          + * This is often useful when updating UI elements, e.g. {@code RecyclerView.Adapter} can provide nicer animations and + * work more effectively if it knows exactly which elements changed. + * @see RealmObservable for information about more coarse-grained notifications. + * @see Android Adapters + */ +public interface RealmCollectionObservable + extends RealmObservable { + /** + * Adds a change listener to this {@link OrderedRealmCollection}. + * + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to add a listener from a non-Looper or + * {@link android.app.IntentService} thread. + */ + void addChangeListener(S listener); + + /** + * Removes the specified change listener. + * + * @param listener the change listener to be removed. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + * @see io.realm.RealmChangeListener + */ + void removeChangeListener(S listener); +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmObservable.java b/realm/realm-library/src/main/java/io/realm/RealmObservable.java new file mode 100644 index 0000000000..6d9e619b09 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/RealmObservable.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * A class implementing this interface is capable of reporting when the data stored by the class have changed. When that + * happens all registered {@link RealmChangeListener}'s will be triggered. + *

          + * This class will only report that something changed, not what changed. + * @see RealmCollectionObservable for information about more fine-grained collection notifications. + */ +public interface RealmObservable { + /** + * Adds a change listener to this {@link RealmResults}, {@link RealmList}, {@link Realm}, {@link DynamicRealm} or + * {@link RealmObject}. + * + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to add a listener from a non-Looper or + * {@link android.app.IntentService} thread. + */ + void addChangeListener(RealmChangeListener listener); + + /** + * Removes the specified change listener. + * + * @param listener the change listener to be removed. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + * @see io.realm.RealmChangeListener + */ + void removeChangeListener(RealmChangeListener listener); + + /** + * Removes all user-defined change listeners. + * + * @throws IllegalStateException if you try to remove listeners from a non-Looper Thread. + * @see io.realm.RealmChangeListener + */ + void removeAllChangeListeners(); +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index bee6375f1c..70eb49ec00 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -17,21 +17,9 @@ package io.realm; -import android.app.IntentService; import android.os.Looper; -import java.util.AbstractList; -import java.util.ConcurrentModificationException; -import java.util.Date; -import java.util.Iterator; -import java.util.ListIterator; - -import io.realm.internal.InvalidRow; -import io.realm.internal.RealmObjectProxy; -import io.realm.internal.SortDescriptor; -import io.realm.internal.Table; import io.realm.internal.Collection; -import io.realm.internal.UncheckedRow; import rx.Observable; /** @@ -61,7 +49,8 @@ * @see RealmQuery#findAll() * @see Realm#executeTransaction(Realm.Transaction) */ -public class RealmResults extends OrderedRealmCollectionImpl { +public class RealmResults extends OrderedRealmCollectionImpl + implements RealmCollectionObservable, OrderedRealmCollectionChangeListener>> { RealmResults(BaseRealm realm, Collection collection, Class clazz) { super(realm, collection, clazz); @@ -117,44 +106,59 @@ public boolean load() { } /** - * Adds a change listener to this RealmResults. - * - * @param listener the change listener to be notified. - * @throws IllegalArgumentException if the change listener is {@code null}. - * @throws IllegalStateException if you try to add a listener from a non-Looper or {@link IntentService} thread. + * {@inheritDoc} */ + @Override public void addChangeListener(RealmChangeListener> listener) { + checkForAddRemoveListener(listener); + collection.addListener(this, listener); + } + + @Override + public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { + checkForAddRemoveListener(listener); + collection.addListener(this, listener); + } + + private void checkForAddRemoveListener(Object listener) { if (listener == null) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); - collection.addListener(this, listener); } /** - * Removes a previously registered listener. - * - * @param listener the instance to be removed. - * @throws IllegalArgumentException if the change listener is {@code null}. - * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + * {@inheritDoc} */ - public void removeChangeListener(RealmChangeListener listener) { - if (listener == null) { - throw new IllegalArgumentException("Listener should not be null"); - } + @Override + public void removeAllChangeListeners() { realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); - collection.removeListener(this, listener); + collection.removeAllListeners(); } /** - * Removes all registered listeners. + * Use {@link #removeAllChangeListeners()} instead. */ + @Deprecated public void removeChangeListeners() { - realm.checkIfValid(); - realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); - collection.removeAllListeners(); + removeAllChangeListeners(); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeChangeListener(RealmChangeListener listener) { + checkForAddRemoveListener(listener); + collection.removeListener(this, listener); + } + + @Override + public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { + checkForAddRemoveListener(listener); + collection.removeListener(this, listener); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 30e4794e83..358b794cb6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -20,6 +20,8 @@ import java.util.Date; import java.util.NoSuchElementException; +import io.realm.OrderedCollectionChangeSet; +import io.realm.OrderedRealmCollectionChangeListener; import io.realm.RealmChangeListener; /** @@ -29,13 +31,59 @@ @Keep public class Collection implements NativeObject { - private class CollectionObserverPair extends ObserverPairList.ObserverPair> { - public CollectionObserverPair(T observer, RealmChangeListener listener) { + private class CollectionObserverPair extends ObserverPairList.ObserverPair { + public CollectionObserverPair(T observer, Object listener) { super(observer, listener); } - public void onChange(T observer) { - listener.onChange(observer); + public void onChange(T observer, OrderedCollectionChangeSet changes) { + if (listener instanceof OrderedRealmCollectionChangeListener) { + //noinspection unchecked + ((OrderedRealmCollectionChangeListener)listener).onChange(observer, changes); + } else if (listener instanceof RealmChangeListener) { + //noinspection unchecked + ((RealmChangeListener)listener).onChange(observer); + } else { + throw new RuntimeException("Unsupported listener type: " + listener); + } + } + } + + private static class RealmChangeListenerWrapper implements OrderedRealmCollectionChangeListener { + private final RealmChangeListener listener; + + RealmChangeListenerWrapper(RealmChangeListener listener) { + this.listener = listener; + } + + @Override + public void onChange(T collection, OrderedCollectionChangeSet changes) { + listener.onChange(collection); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof RealmChangeListenerWrapper && + listener == ((RealmChangeListenerWrapper) obj).listener; + } + + @Override + public int hashCode() { + return listener.hashCode(); + } + } + + private static class Callback implements ObserverPairList.Callback { + private final OrderedCollectionChangeSet changeSet; + + Callback(OrderedCollectionChangeSet changeSet) { + this.changeSet = changeSet; + } + + @Override + public void onCalled(CollectionObserverPair pair, Object observer) { + //noinspection unchecked + pair.onChange(observer, changeSet); } } @@ -208,14 +256,6 @@ public void set(T object) { private boolean isSnapshot = false; private final ObserverPairList observerPairs = new ObserverPairList(); - private static final ObserverPairList.Callback onChangeCallback = - new ObserverPairList.Callback() { - @Override - public void onCalled(CollectionObserverPair pair, Object observer) { - //noinspection unchecked - pair.onChange(observer); - } - }; // Public for static checking in JNI @SuppressWarnings("WeakerAccess") @@ -417,7 +457,7 @@ public boolean deleteLast() { return nativeDeleteLast(nativePtr); } - public void addListener(T observer, RealmChangeListener listener) { + public void addListener(T observer, OrderedRealmCollectionChangeListener listener) { if (observerPairs.isEmpty()) { nativeStartListening(nativePtr); } @@ -425,14 +465,21 @@ public void addListener(T observer, RealmChangeListener listener) { observerPairs.add(collectionObserverPair); } - public void removeListener(T observer, RealmChangeListener listener) { - CollectionObserverPair collectionObserverPair = new CollectionObserverPair(observer, listener); - observerPairs.remove(collectionObserverPair); + public void addListener(T observer, RealmChangeListener listener) { + addListener(observer, new RealmChangeListenerWrapper(listener)); + } + + public void removeListener(T observer, OrderedRealmCollectionChangeListener listener) { + observerPairs.remove(observer, listener); if (observerPairs.isEmpty()) { nativeStopListening(nativePtr); } } + public void removeListener(T observer, RealmChangeListener listener) { + removeListener(observer, new RealmChangeListenerWrapper(listener)); + } + public void removeAllListeners() { observerPairs.clear(); nativeStopListening(nativePtr); @@ -444,15 +491,17 @@ public boolean isValid() { // Called by JNI @SuppressWarnings("unused") - private void notifyChangeListeners(boolean emptyChanges) { - if (emptyChanges && isLoaded()) { + private void notifyChangeListeners(long nativeChangeSetPtr) { + if (nativeChangeSetPtr == 0 && isLoaded()) { return; } + boolean wasLoaded = loaded; loaded = true; - // TODO: For the fine grained notification, remember to call the callback with empty change set if the - // isLoaded() returns false even when the change set is not empty. Since in that case, it is the first time - // the listener gets called to indicate async query returns. - observerPairs.foreach(onChangeCallback); + // Object Store compute the change set between the SharedGroup versions when the query created and the latest. + // So it is possible it deliver a non-empty change set for the first async query returns. In this case, we + // return an empty change set to user since it is considered as the first time async query returns. + observerPairs.foreach(new Callback(nativeChangeSetPtr == 0 || !wasLoaded ? + null : new CollectionChangeSet(nativeChangeSetPtr))); } public Mode getMode() { @@ -478,7 +527,7 @@ public void load() { if (loaded) { return; } - notifyChangeListeners(true); + notifyChangeListeners(0); } private static native long nativeGetFinalizerPtr(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java new file mode 100644 index 0000000000..96804a1121 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java @@ -0,0 +1,129 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import io.realm.OrderedCollectionChangeSet; + +/** + * Implementation of {@link OrderedCollectionChangeSet}. This class holds a pointer to the Object Store's + * CollectionChangeSet and read from it only when needed. Creating an Java object from JNI when the collection + * notification arrives, is avoided since we also support the collection listeners without a change set parameter, + * parsing the change set may not be necessary all the time. + */ +public class CollectionChangeSet implements OrderedCollectionChangeSet, NativeObject { + + // Used in JNI. + @SuppressWarnings("WeakerAccess") + public static final int TYPE_DELETION = 0; + @SuppressWarnings("WeakerAccess") + public static final int TYPE_INSERTION = 1; + @SuppressWarnings("WeakerAccess") + public static final int TYPE_MODIFICATION = 2; + // Max array length is VM dependent. This is a safe value. + // See http://stackoverflow.com/questions/3038392/do-java-arrays-have-a-maximum-size + @SuppressWarnings({"WeakerAccess", "unused"}) + public static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; + + private static long finalizerPtr = nativeGetFinalizerPtr(); + private final long nativePtr; + + public CollectionChangeSet(long nativePtr) { + this.nativePtr = nativePtr; + Context.dummyContext.addReference(this); + } + + /** + * {@inheritDoc} + */ + @Override + public int[] getDeletions() { + return nativeGetIndices(nativePtr, TYPE_DELETION); + } + + /** + * {@inheritDoc} + */ + @Override + public int[] getInsertions() { + return nativeGetIndices(nativePtr, TYPE_INSERTION); + } + + /** + * {@inheritDoc} + */ + @Override + public int[] getChanges() { + return nativeGetIndices(nativePtr, TYPE_MODIFICATION); + } + + /** + * {@inheritDoc} + */ + @Override + public Range[] getDeletionRanges() { + return longArrayToRangeArray(nativeGetRanges(nativePtr, TYPE_DELETION)); + } + + /** + * {@inheritDoc} + */ + @Override + public Range[] getInsertionRanges() { + return longArrayToRangeArray(nativeGetRanges(nativePtr, TYPE_INSERTION)); + } + + /** + * {@inheritDoc} + */ + @Override + public Range[] getChangeRanges() { + return longArrayToRangeArray(nativeGetRanges(nativePtr, TYPE_MODIFICATION)); + } + + /** + * {@inheritDoc} + */ + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return finalizerPtr; + } + + // Convert long array returned by the nativeGetXxxRanges() to Range array. + private Range[] longArrayToRangeArray(int[] longArray) { + if (longArray == null) { + // Returns a size 0 array so we know JNI gets called. + return new Range[0]; + } + + Range[] ranges = new Range[longArray.length / 2]; + for (int i = 0; i < ranges.length; i++) { + ranges[i] = new Range(longArray[i * 2], longArray[i * 2 + 1]); + } + return ranges; + } + + private native static long nativeGetFinalizerPtr(); + // Returns the ranges as an long array. eg.: [startIndex1, length1, startIndex2, length2, ...] + private native static int[] nativeGetRanges(long nativePtr, int type); + // Returns the indices array. + private native static int[] nativeGetIndices(long nativePtr, int type); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/Context.java b/realm/realm-library/src/main/java/io/realm/internal/Context.java index 9dc084e83f..cb9bb6ece6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Context.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Context.java @@ -28,6 +28,8 @@ public class Context { private final static ReferenceQueue referenceQueue = new ReferenceQueue(); private final static Thread finalizingThread = new Thread(new FinalizerRunnable(referenceQueue)); + // Dummy context which will be used by native objects which's destructors are always thread safe. + final static Context dummyContext = new Context(); static { finalizingThread.setName("RealmFinalizingDaemon"); diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java index e3f0fa717a..aeb5f8cb0b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java @@ -32,25 +32,24 @@ * * @param the type of {@link ObserverPair}. */ -public class ObserverPairList { +class ObserverPairList { /** * @param the type of observer. * @param the type of listener. */ - public abstract static class ObserverPair { - protected final WeakReference observerRef; + abstract static class ObserverPair { + final WeakReference observerRef; protected final S listener; // Should only be set by the outer class. To marked it as removed in case it is removed in foreach callback. boolean removed = false; - public ObserverPair(T observer, S listener) { + ObserverPair(T observer, S listener) { this.listener = listener; this.observerRef = new WeakReference(observer); } - // The two pairs will be treated as the same only when the observers are the same and the listeners are the same - // as well. + // The two pairs will be treated as the same only when the observers are the same and the listeners are equal. @Override public boolean equals(Object obj) { if (this == obj) { @@ -96,7 +95,7 @@ interface Callback { * * @param callback to be executed on the pair. */ - public void foreach(Callback callback) { + void foreach(Callback callback) { for (T pair : pairs) { if (cleared) { break; @@ -123,23 +122,28 @@ public void clear() { public void add(T pair) { if (!pairs.contains(pair)) { pairs.add(pair); + pair.removed = false; } if (cleared) { cleared = false; } } - public void remove(T pair) { - pair.removed = true; - pairs.remove(pair); + public void remove(S observer, U listener) { + for (T pair : pairs) { + if (observer == pair.observerRef.get() && listener.equals(pair.listener)) { + pair.removed = true; + pairs.remove(pair); + break; + } + } } - public void removeByObserver(Object observer) { + void removeByObserver(Object observer) { for (T pair : pairs) { Object object = pair.observerRef.get(); - if (object == null) { - pairs.remove(pair); - } else if (object == observer) { + if (object == null || object == observer) { + pair.removed = true; pairs.remove(pair); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 90ca9af48f..74726c5a05 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -127,8 +127,7 @@ public void addChangeListener(T observer, RealmChangeListener realmChange } public void removeChangeListener(E observer, RealmChangeListener realmChangeListener) { - RealmObserverPair observerPair = new RealmObserverPair(observer, realmChangeListener); - realmObserverPairs.remove(observerPair); + realmObserverPairs.remove(observer, realmChangeListener); } public void removeChangeListeners(E observer) { From 2084c41205de9caac8e54b6b3cf9b21ac5003451 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 23 Feb 2017 20:27:45 +0900 Subject: [PATCH 0510/2110] add release date template --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d917f203b..af1034937c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.3.2 +## 2.3.2 (YYYY-MM-DD) ### Bug fixes From c72f2bda0d75cd8290331bdc6a3a4ae6113cf15f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Feb 2017 22:28:03 +0800 Subject: [PATCH 0511/2110] Cast to int64_t when logging (#4226) Otherwise is will be casted to bool when formatting. Also S64 cast won't work. So casting macros is still a bad idea :) --- realm/realm-library/src/main/cpp/util.hpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 46940371a1..8a7b42e745 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -50,9 +50,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved); } #endif -// Use this macro when logging a pointer using '%p' -#define VOID_PTR(ptr) reinterpret_cast(ptr) - #define STRINGIZE_DETAIL(x) #x #define STRINGIZE(x) STRINGIZE_DETAIL(x) @@ -74,8 +71,9 @@ std::string num_to_string(T pNumber) #define MAX_JINT 0x7FFFFFFFL #define MAX_JSIZE MAX_JINT +// TODO: Clean up those marcos. Casting with marcos reduces the readability, and it is actually breaking the C++ type +// conversion. e.g.: You cannot cast a pointer with S64 below. // Helper macros for better readability -// Use S64() when logging #define S(x) static_cast(x) #define B(x) static_cast(x) #define S64(x) static_cast(x) @@ -184,7 +182,7 @@ inline bool TableIsValid(JNIEnv* env, T* objPtr) } if (!valid) { - realm::jni_util::Log::e("Table %1 is no longer attached!", VOID_PTR(objPtr)); + realm::jni_util::Log::e("Table %1 is no longer attached!", reinterpret_cast(objPtr)); ThrowException(env, IllegalState, "Table is no longer valid to operate on."); } return valid; @@ -194,7 +192,7 @@ inline bool RowIsValid(JNIEnv* env, realm::Row* rowPtr) { bool valid = (rowPtr != NULL && rowPtr->is_attached()); if (!valid) { - realm::jni_util::Log::e("Row %1 is no longer attached!", VOID_PTR(rowPtr)); + realm::jni_util::Log::e("Row %1 is no longer attached!", reinterpret_cast(rowPtr)); ThrowException(env, IllegalState, "Object is no longer valid to operate on. Was it deleted by another thread?"); } return valid; From 8c18a88a180bb2f2d6a2a5a53b1e04feaf7294c2 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 23 Feb 2017 20:21:11 +0100 Subject: [PATCH 0512/2110] Upgrading to sync v1.2.1 and core v2.3.2 --- CHANGELOG.md | 5 +++-- dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af1034937c..884751ce0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,12 @@ ### Bug fixes * Fixed log levels in JNI layer (#4204). +* Fixed a bug in encryption (#4128). ### Internal -* Updated to Realm Sync v1.0.4. -* Updated to Realm Core v2.3.1. +* Updated to Realm Sync v1.2.1. +* Updated to Realm Core v2.3.2. ### Enhancements diff --git a/dependencies.list b/dependencies.list index ef11c2e0f6..f22053bc89 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.0.4 -REALM_SYNC_SHA256=a1d00577219b7c2749a0b4baa8b07ead2380bc47907fb2ce4b13cf59d26ca463 +REALM_SYNC_VERSION=1.2.1 +REALM_SYNC_SHA256=b796433319e2574ea3cdb1b3dcda4e2311a2080f8e1563ea68a59b2cdac1d0a7 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 2950979535..9a8520da95 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 29509795357df374f88950ee471a57900b97ecdf +Subproject commit 9a8520da95fc2505c1634d6d801f12ea73109cac From 5b1a47f0947926b5884e37f6f538fc409f9f60e4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 24 Feb 2017 12:41:52 +0800 Subject: [PATCH 0513/2110] Enable listeners on RealmList (#4216) The RealmList holds a Collection which is used for listeners. Other RealmList APIs are still calling from LinkView. --- CHANGELOG.md | 1 + .../OrderedCollectionChangeSetTests.java | 183 +++++++++++++----- .../java/io/realm/RealmListTests.java | 116 ++++++++++- .../io/realm/OrderedRealmCollectionImpl.java | 2 - .../src/main/java/io/realm/RealmList.java | 135 +++++++++---- .../src/main/java/io/realm/RealmResults.java | 15 +- .../java/io/realm/internal/Collection.java | 8 +- 7 files changed, 358 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0b31cd96b..6487b9e671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * Added support for sorting by link's field (#672). * Added `OrderedRealmCollectionSnapshot` class and `OrderedRealmCollection.createSnapshot()` method. `OrderedRealmCollectionSnapshot` is useful when changing `RealmResults` or `RealmList` in simple loops. +* Added support for adding listeners on `RealmList`. ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java index a1134f85b3..30f6521816 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -16,17 +16,19 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; - import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.CountDownLatch; -import io.realm.entities.AllTypes; +import io.realm.entities.Dog; +import io.realm.entities.Owner; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -38,16 +40,34 @@ import static junit.framework.Assert.fail; import static org.junit.Assert.assertArrayEquals; -// Tests for the ordered collection fine grained notifications. -// This should be expanded to test the notifications for RealmList as well in the future. -@RunWith(AndroidJUnit4.class) +// Tests for the ordered collection fine grained notifications for both RealmResults and RealmList. +@RunWith(Parameterized.class) public class OrderedCollectionChangeSetTests { + private enum ObservablesType { + REALM_RESULTS, REALM_LIST + } + + private interface ChangesCheck { + void check(OrderedCollectionChangeSet changeSet); + } + @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); + private final ObservablesType type; + + @Parameterized.Parameters(name = "{0}") + public static List data() { + return Arrays.asList(ObservablesType.values()); + } + + public OrderedCollectionChangeSetTests(ObservablesType type) { + this.type = type; + } + @Before public void setUp() { } @@ -57,9 +77,17 @@ public void tearDown() { } private void populateData(Realm realm, int testSize) { + Owner owner = null; realm.beginTransaction(); + if (type == ObservablesType.REALM_LIST) { + owner = realm.createObject(Owner.class); + } for (int i = 0; i < testSize; i++) { - realm.createObject(AllTypes.class).setColumnLong(i); + Dog dog = realm.createObject(Dog.class); + dog.setAge(i); + if (type == ObservablesType.REALM_LIST) { + owner.getDogs().add(dog); + } } realm.commitTransaction(); } @@ -82,26 +110,71 @@ private void checkRanges(OrderedCollectionChangeSet.Range[] ranges, int... index } } - // Deletes AllTypes objects which's columnLong is in the indices array. + // Re-adds the dogs so they would be sorted by age in the list. + private void reorderRealmList(Realm realm) { + RealmResults dogs = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); + Owner owner = realm.where(Owner.class).findFirst(); + owner.getDogs().clear(); + for (Dog dog : dogs) { + owner.getDogs().add(dog); + } + } + + // Deletes Dogs objects which's columnLong is in the indices array. private void deleteObjects(Realm realm, int... indices) { for (int index : indices) { - realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, index).findFirst().deleteFromRealm(); + realm.where(Dog.class).equalTo(Dog.FIELD_AGE, index).findFirst().deleteFromRealm(); } } - // Creates AllTypes objects with columnLong set to the value elements in indices array. + // Creates Dogs objects with columnLong set to the value elements in indices array. private void createObjects(Realm realm, int... indices) { for (int index : indices) { - realm.createObject(AllTypes.class).setColumnLong(index); + realm.createObject(Dog.class).setAge(index); + } + if (type == ObservablesType.REALM_LIST) { + reorderRealmList(realm); } } - // Modifies AllTypes objects which's columnLong is in the indices array. + // Modifies Dogs objects which's columnLong is in the indices array. private void modifyObjects(Realm realm, int... indices) { for (int index : indices) { - AllTypes obj = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, index).findFirst(); + Dog obj = realm.where(Dog.class).equalTo(Dog.FIELD_AGE, index).findFirst(); assertNotNull(obj); - obj.setColumnString("modified"); + obj.setName("modified"); + } + } + + private void moveObjects(Realm realm, int originAge, int newAge) { + realm.where(Dog.class).equalTo(Dog.FIELD_AGE, originAge).findFirst().setAge(newAge); + if (type == ObservablesType.REALM_LIST) { + reorderRealmList(realm); + } + } + + private void registerCheckListener(Realm realm, final ChangesCheck changesCheck) { + switch (type) { + case REALM_RESULTS: + RealmResults results = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); + looperThread.keepStrongReference.add(results); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + changesCheck.check(changeSet); + } + }); + break; + case REALM_LIST: + RealmList list = realm.where(Owner.class).findFirst().getDogs(); + looperThread.keepStrongReference.add(list); + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, OrderedCollectionChangeSet changeSet) { + changesCheck.check(changeSet); + } + }); + break; } } @@ -110,10 +183,10 @@ private void modifyObjects(Realm realm, int... indices) { public void deletion() { Realm realm = looperThread.realm; populateData(realm, 10); - RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); - results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + + final ChangesCheck changesCheck = new ChangesCheck() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void check(OrderedCollectionChangeSet changeSet) { checkRanges(changeSet.getDeletionRanges(), 0, 1, 2, 3, @@ -125,7 +198,9 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS assertEquals(0, changeSet.getInsertions().length); looperThread.testComplete(); } - }); + }; + + registerCheckListener(realm, changesCheck); realm.beginTransaction(); deleteObjects(realm, @@ -139,13 +214,14 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS @RunTestInLooperThread public void insertion() { Realm realm = looperThread.realm; + populateData(realm, 0); // We need to create the owner. realm.beginTransaction(); createObjects(realm, 0, 2, 5, 6, 7, 9); realm.commitTransaction(); - RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); - results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + + ChangesCheck changesCheck = new ChangesCheck() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void check(OrderedCollectionChangeSet changeSet) { checkRanges(changeSet.getInsertionRanges(), 1, 1, 3, 2, @@ -157,7 +233,8 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS assertEquals(0, changeSet.getDeletions().length); looperThread.testComplete(); } - }); + }; + registerCheckListener(realm, changesCheck); realm.beginTransaction(); createObjects(realm, @@ -172,10 +249,9 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS public void changes() { Realm realm = looperThread.realm; populateData(realm, 10); - RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); - results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + ChangesCheck changesCheck = new ChangesCheck() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void check(OrderedCollectionChangeSet changeSet) { checkRanges(changeSet.getChangeRanges(), 0, 1, 2, 3, @@ -187,7 +263,9 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS assertEquals(0, changeSet.getDeletions().length); looperThread.testComplete(); } - }); + }; + + registerCheckListener(realm, changesCheck); realm.beginTransaction(); modifyObjects(realm, @@ -202,10 +280,9 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS public void moves() { Realm realm = looperThread.realm; populateData(realm, 10); - RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); - results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + ChangesCheck changesCheck = new ChangesCheck() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void check(OrderedCollectionChangeSet changeSet) { checkRanges(changeSet.getDeletionRanges(), 0, 1, 9, 1); @@ -218,10 +295,12 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS assertEquals(0, changeSet.getChanges().length); looperThread.testComplete(); } - }); + }; + registerCheckListener(realm, changesCheck); + realm.beginTransaction(); - realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 0).findFirst().setColumnLong(10); - realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 9).findFirst().setColumnLong(0); + moveObjects(realm, 0, 10); + moveObjects(realm, 9, 0); realm.commitTransaction(); } @@ -230,10 +309,9 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS public void mixed_changes() { Realm realm = looperThread.realm; populateData(realm, 10); - RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); - results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + ChangesCheck changesCheck = new ChangesCheck() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void check(OrderedCollectionChangeSet changeSet) { checkRanges(changeSet.getDeletionRanges(), 0, 2, 5, 1); @@ -251,7 +329,9 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS looperThread.testComplete(); } - }); + }; + + registerCheckListener(realm, changesCheck); realm.beginTransaction(); createObjects(realm, 11, 12, -1, -2); @@ -269,10 +349,9 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS public void changes_then_delete() { Realm realm = looperThread.realm; populateData(realm, 10); - RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); - results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + ChangesCheck changesCheck = new ChangesCheck() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void check(OrderedCollectionChangeSet changeSet) { checkRanges(changeSet.getDeletionRanges(), 0, 2, 5, 1); @@ -285,7 +364,8 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS looperThread.testComplete(); } - }); + }; + registerCheckListener(realm, changesCheck); realm.beginTransaction(); modifyObjects(realm, 0, 1, 5); @@ -299,13 +379,14 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS public void insert_then_delete() { Realm realm = looperThread.realm; populateData(realm, 10); - RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG); - results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + ChangesCheck changesCheck = new ChangesCheck() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void check(OrderedCollectionChangeSet changeSet) { fail("The listener should not be triggered since the collection has no changes compared with before."); } - }); + }; + + registerCheckListener(realm, changesCheck); looperThread.postRunnableDelayed(new Runnable() { @Override @@ -324,12 +405,16 @@ public void run() { @Test @RunTestInLooperThread public void emptyChangeSet_findAllAsync(){ + if (type == ObservablesType.REALM_LIST) { + looperThread.testComplete(); + return; + } Realm realm = looperThread.realm; populateData(realm, 10); - final RealmResults results = realm.where(AllTypes.class).findAllSortedAsync(AllTypes.FIELD_LONG); - results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + final RealmResults results = realm.where(Dog.class).findAllSortedAsync(Dog.FIELD_AGE); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { assertSame(collection, results); assertEquals(9, collection.size()); assertNull(changeSet); @@ -338,7 +423,7 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS }); final CountDownLatch bgDeletionLatch = new CountDownLatch(1); - // beginTransaction() will make the async query return immediately. So we have to delete an object in another + // beginTransaction() will make the async query return immediately. So we have to create an object in another // thread. Also, the latch has to be counted down after transaction committed so the async query results can // contain the modification in the background transaction. new Thread(new Runnable() { @@ -346,7 +431,7 @@ public void onChange(RealmResults collection, OrderedCollectionChangeS public void run() { Realm realm = Realm.getInstance(looperThread.realmConfiguration) ; realm.beginTransaction(); - realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 0).findFirst().deleteFromRealm(); + realm.where(Dog.class).equalTo(Dog.FIELD_AGE, 0).findFirst().deleteFromRealm(); realm.commitTransaction(); realm.close(); bgDeletionLatch.countDown(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 9b23775a87..a20ff9fe7a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -25,10 +25,10 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; import io.realm.entities.AllTypes; import io.realm.entities.Cat; @@ -37,6 +37,8 @@ import io.realm.entities.Dog; import io.realm.entities.Owner; import io.realm.internal.RealmObjectProxy; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; @@ -58,6 +60,8 @@ public class RealmListTests extends CollectionTests { public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule public ExpectedException thrown = ExpectedException.none(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); private Realm realm; private RealmList collection; @@ -580,7 +584,7 @@ public void remove_objectAfterContainerObjectRemoved() { @Test public void removeAll_managedMode() { realm.beginTransaction(); - List objectsToRemove = Arrays.asList(collection.get(0)); + List objectsToRemove = Collections.singletonList(collection.get(0)); assertTrue(collection.removeAll(objectsToRemove)); assertFalse(collection.contains(objectsToRemove.get(0))); } @@ -974,4 +978,112 @@ public void add_set_dynamicObjectCreatedFromTypedRealm() { dynamicRealm.close(); } + private RealmList prepareRealmListInLooperThread() { + Realm realm = looperThread.realm; + realm.beginTransaction(); + Owner owner = realm.createObject(Owner.class); + owner.setName("Owner"); + for (int i = 0; i < TEST_SIZE; i++) { + Dog dog = realm.createObject(Dog.class); + dog.setName("Dog " + i); + owner.getDogs().add(dog); + } + realm.commitTransaction(); + return owner.getDogs(); + } + + @Test + @RunTestInLooperThread + public void addChangeListener() { + collection = prepareRealmListInLooperThread(); + Realm realm = looperThread.realm; + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + collection.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }); + collection.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, OrderedCollectionChangeSet changes) { + assertEquals(1, listenerCalledCount.getAndIncrement()); + } + }); + realm.beginTransaction(); + collection.get(0).setAge(42); + realm.commitTransaction(); + + // This should trigger the listener. + realm.beginTransaction(); + realm.cancelTransaction(); + assertEquals(2, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void removeAllChangeListeners() { + collection = prepareRealmListInLooperThread(); + Realm realm = looperThread.realm; + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + collection.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + fail(); + } + }); + collection.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, OrderedCollectionChangeSet changes) { + fail(); + } + }); + realm.beginTransaction(); + collection.get(0).setAge(42); + realm.commitTransaction(); + + collection.removeAllChangeListeners(); + + // This should trigger the listener if there is any. + realm.beginTransaction(); + realm.cancelTransaction(); + assertEquals(0, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void removeChangeListener() { + collection = prepareRealmListInLooperThread(); + Realm realm = looperThread.realm; + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + RealmChangeListener> listener1 = new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + fail(); + } + }; + OrderedRealmCollectionChangeListener> listener2 = + new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, OrderedCollectionChangeSet changes) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }; + + collection.addChangeListener(listener1); + collection.addChangeListener(listener2); + realm.beginTransaction(); + collection.get(0).setAge(42); + realm.commitTransaction(); + + collection.removeChangeListener(listener1); + + // This should trigger the listener if there is any. + realm.beginTransaction(); + realm.cancelTransaction(); + assertEquals(1, listenerCalledCount.get()); + looperThread.testComplete(); + } } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index 80cb91a7f2..bd05486043 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -15,8 +15,6 @@ /** * General implementation for {@link OrderedRealmCollection} which is based on the {@code Collection}. - * Currently only {@link RealmResults} and {@link OrderedRealmCollectionSnapshot} extend this class. But - * {@link RealmList} could also extend this to share the same iterator implementation. */ abstract class OrderedRealmCollectionImpl extends AbstractList implements OrderedRealmCollection { diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 70e6ff7378..3d0c9ff7bd 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -49,16 +49,18 @@ * @param the class of objects in list. */ -public class RealmList extends AbstractList implements OrderedRealmCollection { +public class RealmList extends AbstractList + implements OrderedRealmCollection, + RealmCollectionObservable, OrderedRealmCollectionChangeListener>> { private static final String ONLY_IN_MANAGED_MODE_MESSAGE = "This method is only available in managed mode"; private static final String NULL_OBJECTS_NOT_ALLOWED_MESSAGE = "RealmList does not accept null values"; public static final String REMOVE_OUTSIDE_TRANSACTION_ERROR = "Objects can only be removed from inside a write transaction"; - private final boolean managedMode; + private final io.realm.internal.Collection collection; protected Class clazz; protected String className; - protected LinkView view; + final LinkView view; protected BaseRealm realm; private List unmanagedList; @@ -70,7 +72,8 @@ public class RealmList extends AbstractList implements * Use {@link io.realm.Realm#copyToRealm(Iterable)} to properly persist its elements in Realm. */ public RealmList() { - managedMode = false; + collection = null; + view = null; unmanagedList = new ArrayList(); } @@ -87,7 +90,8 @@ public RealmList(E... objects) { if (objects == null) { throw new IllegalArgumentException("The objects argument cannot be null"); } - managedMode = false; + collection = null; + view = null; unmanagedList = new ArrayList(objects.length); Collections.addAll(unmanagedList, objects); } @@ -100,14 +104,14 @@ public RealmList(E... objects) { * @param realm reference to Realm containing the data. */ RealmList(Class clazz, LinkView linkView, BaseRealm realm) { - this.managedMode = true; + this.collection = new io.realm.internal.Collection(realm.sharedRealm, linkView, null); this.clazz = clazz; this.view = linkView; this.realm = realm; } RealmList(String className, LinkView linkView, BaseRealm realm) { - this.managedMode = true; + this.collection = new io.realm.internal.Collection(realm.sharedRealm, linkView, null); this.view = linkView; this.realm = realm; this.className = className; @@ -160,7 +164,7 @@ private boolean isAttached() { @Override public void add(int location, E object) { checkValidObject(object); - if (managedMode) { + if (isManaged()) { checkValidView(); if (location < 0 || location > size()) { throw new IndexOutOfBoundsException("Invalid index " + location + ", size is " + size()); @@ -192,7 +196,7 @@ public void add(int location, E object) { @Override public boolean add(E object) { checkValidObject(object); - if (managedMode) { + if (isManaged()) { checkValidView(); RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded(object); view.add(proxy.realmGet$proxyState().getRow$realm().getIndex()); @@ -225,7 +229,7 @@ public boolean add(E object) { public E set(int location, E object) { checkValidObject(object); E oldObject; - if (managedMode) { + if (isManaged()) { checkValidView(); RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded(object); oldObject = get(location); @@ -293,7 +297,7 @@ private E copyToRealmIfNeeded(E object) { * @throws java.lang.IndexOutOfBoundsException if any position is outside [0, size()]. */ public void move(int oldPos, int newPos) { - if (managedMode) { + if (isManaged()) { checkValidView(); view.move(oldPos, newPos); } else { @@ -318,7 +322,7 @@ public void move(int oldPos, int newPos) { */ @Override public void clear() { - if (managedMode) { + if (isManaged()) { checkValidView(); view.clear(); } else { @@ -338,7 +342,7 @@ public void clear() { @Override public E remove(int location) { E removedItem; - if (managedMode) { + if (isManaged()) { checkValidView(); removedItem = get(location); view.remove(location); @@ -368,7 +372,7 @@ public E remove(int location) { */ @Override public boolean remove(Object object) { - if (managedMode && !realm.isInTransaction()) { + if (isManaged() && !realm.isInTransaction()) { throw new IllegalStateException(REMOVE_OUTSIDE_TRANSACTION_ERROR); } return super.remove(object); @@ -392,7 +396,7 @@ public boolean remove(Object object) { */ @Override public boolean removeAll(Collection collection) { - if (managedMode && !realm.isInTransaction()) { + if (isManaged() && !realm.isInTransaction()) { throw new IllegalStateException(REMOVE_OUTSIDE_TRANSACTION_ERROR); } return super.removeAll(collection); @@ -403,7 +407,7 @@ public boolean removeAll(Collection collection) { */ @Override public boolean deleteFirstFromRealm() { - if (managedMode) { + if (isManaged()) { if (size() > 0) { deleteFromRealm(0); modCount++; @@ -421,7 +425,7 @@ public boolean deleteFirstFromRealm() { */ @Override public boolean deleteLastFromRealm() { - if (managedMode) { + if (isManaged()) { if (size() > 0) { deleteFromRealm(size() - 1); modCount++; @@ -444,7 +448,7 @@ public boolean deleteLastFromRealm() { */ @Override public E get(int location) { - if (managedMode) { + if (isManaged()) { checkValidView(); long rowIndex = view.getTargetRowIndex(location); return realm.get(clazz, className, rowIndex); @@ -468,7 +472,7 @@ public E first(E defaultValue) { } private E firstImpl(boolean shouldThrow, E defaultValue) { - if (managedMode) { + if (isManaged()) { checkValidView(); if (!view.isEmpty()) { return get(0); @@ -499,7 +503,7 @@ public E last(E defaultValue) { } private E lastImpl(boolean shouldThrow, E defaultValue) { - if (managedMode) { + if (isManaged()) { checkValidView(); if (!view.isEmpty()) { return get((int) view.size() - 1); @@ -528,7 +532,7 @@ public RealmResults sort(String fieldName) { */ @Override public RealmResults sort(String fieldName, Sort sortOrder) { - if (managedMode) { + if (isManaged()) { return this.where().findAllSorted(fieldName, sortOrder); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -548,7 +552,7 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName */ @Override public RealmResults sort(String[] fieldNames, Sort[] sortOrders) { - if (managedMode) { + if (isManaged()) { return where().findAllSorted(fieldNames, sortOrders); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -560,7 +564,7 @@ public RealmResults sort(String[] fieldNames, Sort[] sortOrders) { */ @Override public void deleteFromRealm(int location) { - if (managedMode) { + if (isManaged()) { checkValidView(); view.removeTargetRow(location); modCount++; @@ -577,7 +581,7 @@ public void deleteFromRealm(int location) { */ @Override public int size() { - if (managedMode) { + if (isManaged()) { checkValidView(); long size = view.size(); return size < Integer.MAX_VALUE ? (int) size : Integer.MAX_VALUE; @@ -594,7 +598,7 @@ public int size() { * @see io.realm.RealmQuery */ public RealmQuery where() { - if (managedMode) { + if (isManaged()) { checkValidView(); return RealmQuery.createQueryFromList(this); } else { @@ -607,7 +611,7 @@ public RealmQuery where() { */ @Override public Number min(String fieldName) { - if (managedMode) { + if (isManaged()) { return this.where().min(fieldName); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -619,7 +623,7 @@ public Number min(String fieldName) { */ @Override public Number max(String fieldName) { - if (managedMode) { + if (isManaged()) { return this.where().max(fieldName); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -631,7 +635,7 @@ public Number max(String fieldName) { */ @Override public Number sum(String fieldName) { - if (managedMode) { + if (isManaged()) { return this.where().sum(fieldName); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -643,7 +647,7 @@ public Number sum(String fieldName) { */ @Override public double average(String fieldName) { - if (managedMode) { + if (isManaged()) { return this.where().average(fieldName); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -655,7 +659,7 @@ public double average(String fieldName) { */ @Override public Date maxDate(String fieldName) { - if (managedMode) { + if (isManaged()) { return this.where().maximumDate(fieldName); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -667,7 +671,7 @@ public Date maxDate(String fieldName) { */ @Override public Date minDate(String fieldName) { - if (managedMode) { + if (isManaged()) { return this.where().minimumDate(fieldName); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -679,7 +683,7 @@ public Date minDate(String fieldName) { */ @Override public boolean deleteAllFromRealm() { - if (managedMode) { + if (isManaged()) { checkValidView(); if (size() > 0) { view.removeAllTargetRows(); @@ -721,7 +725,7 @@ public boolean load() { */ @Override public boolean contains(Object object) { - if (managedMode) { + if (isManaged()) { realm.checkIfValid(); // Deleted objects can never be part of a RealmList @@ -748,7 +752,7 @@ public boolean contains(Object object) { */ @Override public Iterator iterator() { - if (managedMode) { + if (isManaged()) { return new RealmItr(); } else { return super.iterator(); @@ -768,7 +772,7 @@ public ListIterator listIterator() { */ @Override public ListIterator listIterator(int location) { - if (managedMode) { + if (isManaged()) { return new RealmListItr(location); } else { return super.listIterator(location); @@ -800,7 +804,7 @@ private void checkValidView() { */ @Override public OrderedRealmCollectionSnapshot createSnapshot() { - if (!managedMode) { + if (!isManaged()) { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); } checkValidView(); @@ -818,13 +822,13 @@ public OrderedRealmCollectionSnapshot createSnapshot() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(managedMode ? clazz.getSimpleName() : getClass().getSimpleName()); + sb.append(isManaged() ? clazz.getSimpleName() : getClass().getSimpleName()); sb.append("@["); - if (managedMode && !isAttached()) { + if (isManaged() && !isAttached()) { sb.append("invalid"); } else { for (int i = 0; i < size(); i++) { - if (managedMode) { + if (isManaged()) { sb.append(((RealmObjectProxy) get(i)).realmGet$proxyState().getRow$realm().getIndex()); } else { sb.append(System.identityHashCode(get(i))); @@ -838,6 +842,59 @@ public String toString() { return sb.toString(); } + private void checkForAddRemoveListener(Object listener, boolean checkListener) { + if (checkListener && listener == null) { + throw new IllegalArgumentException("Listener should not be null"); + } + realm.checkIfValid(); + realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); + } + + /** + * {@inheritDoc} + */ + @Override + public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { + checkForAddRemoveListener(listener, true); + collection.addListener(this, listener); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { + checkForAddRemoveListener(listener, true); + collection.removeListener(this, listener); + } + + /** + * {@inheritDoc} + */ + @Override + public void addChangeListener(RealmChangeListener> listener) { + checkForAddRemoveListener(listener, true); + collection.addListener(this, listener); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeChangeListener(RealmChangeListener> listener) { + checkForAddRemoveListener(listener, true); + collection.removeListener(this, listener); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeAllChangeListeners() { + checkForAddRemoveListener(null, false); + collection.removeAllListeners(); + } + // Custom RealmList iterator. private class RealmItr implements Iterator { /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 70eb49ec00..d16810cd74 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -110,18 +110,18 @@ public boolean load() { */ @Override public void addChangeListener(RealmChangeListener> listener) { - checkForAddRemoveListener(listener); + checkForAddRemoveListener(listener, true); collection.addListener(this, listener); } @Override public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { - checkForAddRemoveListener(listener); + checkForAddRemoveListener(listener, true); collection.addListener(this, listener); } - private void checkForAddRemoveListener(Object listener) { - if (listener == null) { + private void checkForAddRemoveListener(Object listener, boolean checkListener) { + if (checkListener && listener == null) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); @@ -133,8 +133,7 @@ private void checkForAddRemoveListener(Object listener) { */ @Override public void removeAllChangeListeners() { - realm.checkIfValid(); - realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); + checkForAddRemoveListener(null, false); collection.removeAllListeners(); } @@ -151,13 +150,13 @@ public void removeChangeListeners() { */ @Override public void removeChangeListener(RealmChangeListener listener) { - checkForAddRemoveListener(listener); + checkForAddRemoveListener(listener, true); collection.removeListener(this, listener); } @Override public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { - checkForAddRemoveListener(listener); + checkForAddRemoveListener(listener, true); collection.removeListener(this, listener); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 358b794cb6..704e8cbdff 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -252,7 +252,7 @@ public void set(T object) { private final SharedRealm sharedRealm; private final Context context; private final Table table; - private boolean loaded = false; + private boolean loaded; private boolean isSnapshot = false; private final ObserverPairList observerPairs = new ObserverPairList(); @@ -330,6 +330,7 @@ public Collection(SharedRealm sharedRealm, TableQuery query, this.context = sharedRealm.context; this.table = query.getTable(); this.context.addReference(this); + this.loaded = false; } public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor) { @@ -348,6 +349,9 @@ public Collection(SharedRealm sharedRealm, LinkView linkView, SortDescriptor sor this.context = sharedRealm.context; this.table = linkView.getTable(); this.context.addReference(this); + // Collection created from LinkView is loaded by default. So that the listener will be triggered first time + // with empty change set. + this.loaded = true; } private Collection(SharedRealm sharedRealm, Table table, long nativePtr) { @@ -355,8 +359,8 @@ private Collection(SharedRealm sharedRealm, Table table, long nativePtr) { this.context = sharedRealm.context; this.table = table; this.nativePtr = nativePtr; - this.context.addReference(this); + this.loaded = false; } public Collection createSnapshot() { From 062af613fc9cb2fe06e917f4520bd434fe1717c4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 24 Feb 2017 09:57:19 +0100 Subject: [PATCH 0514/2110] Deleted RealmObjects are now emitted as well. (#4236) --- CHANGELOG.md | 2 + .../java/io/realm/RealmObjectTests.java | 21 ++++++++++ .../java/io/realm/RxJavaTests.java | 39 +++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e54af08a98..27d4ebd08f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object (#2947). * `RealmResults` is auto-updated continuously. Any transaction on the current thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. The standard `RealmResults.iterator()` will continue to work as normal, which means that you can still delete or modify elements without impacting the iterator. The same is not true for simple for-loops. In some cases a simple for-loop will not work (https://realm.io/docs/java/3.0.0/api/io/realm/OrderedRealmCollection.html#loops), and you must use the new createSnapshot() method. +* `RealmChangeListener` on `RealmObject` will now also be triggered when the object is deleted. Use `RealmObject.isValid()` to check this state(#3138). +* `RealmObject.asObservable()` will now emit the object when it is deleted. Use `RealmObject.isValid()` to check this state (#3138). ### Deprecated diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 20395358d1..2c1badac3d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -1603,6 +1603,27 @@ public void run() throws Exception { }); } + @Test + @RunTestInLooperThread + public void changeListener_triggeredWhenObjectIsdeleted() { + final Realm realm = looperThread.realm; + realm.beginTransaction(); + AllTypes obj = realm.createObject(AllTypes.class); + realm.commitTransaction(); + + obj.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(AllTypes obj) { + assertFalse(obj.isValid()); + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + obj.deleteFromRealm(); + realm.commitTransaction(); + } + @Test @RunTestInLooperThread public void addChangeListener_throwOnUnmanagedObject() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index 7f02803452..ab6ef1e4fd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -43,6 +43,7 @@ import rx.functions.Func1; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -183,6 +184,44 @@ public void call(AllTypes rxObject) { realm.commitTransaction(); } + @Test + @RunTestInLooperThread + public void findFirstAsync_emittedOnDelete() { + final AtomicInteger subscriberCalled = new AtomicInteger(0); + final Realm realm = looperThread.realm; + realm.beginTransaction(); + final AllTypes obj = realm.createObject(AllTypes.class); + realm.commitTransaction(); + + subscription = realm.where(AllTypes.class).findFirstAsync().asObservable().subscribe(new Action1() { + @Override + public void call(final AllTypes rxObject) { + switch (subscriberCalled.incrementAndGet()) { + case 1: + assertFalse(rxObject.isLoaded()); + break; + case 2: + assertTrue(rxObject.isLoaded()); + assertTrue(rxObject.isValid()); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.delete(AllTypes.class); + } + }); + break; + case 3: + assertTrue(rxObject.isLoaded()); + assertFalse(rxObject.isValid()); + looperThread.testComplete(); + break; + default: + fail(); + } + } + }); + } + @Test @UiThreadTest public void realmResults_emittedOnSubscribe() { From 958d31374552c4e174ae98e79246bd512af35522 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 24 Feb 2017 10:40:34 +0100 Subject: [PATCH 0515/2110] RealmObject now uses the same interface methods for RealmObservable without implementing the interface....because compilers and kotlin (#4230) --- CHANGELOG.md | 1 + .../java/io/realm/RealmObjectTests.java | 8 +++---- .../src/main/java/io/realm/RealmObject.java | 23 ++++++++++++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27d4ebd08f..9444387481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Deprecated * `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. +* `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 2c1badac3d..2feb6876e7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -1682,7 +1682,7 @@ public void run() throws Exception { */ @Test @RunTestInLooperThread - public void removeChangeListeners() { + public void removeAllChangeListeners() { final Realm realm = looperThread.realm; realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); @@ -1694,7 +1694,7 @@ public void onChange(Dog object) { assertTrue(false); } }); - dog.removeChangeListeners(); + dog.removeAllChangeListeners(); realm.beginTransaction(); Dog sameDog = realm.where(Dog.class).equalTo(Dog.FIELD_AGE, 13).findFirst(); @@ -1723,11 +1723,11 @@ public void onChange(Dog object) { @Test @RunTestInLooperThread - public void removeChangeListeners_throwOnUnmanagedObject() { + public void removeAllChangeListeners_throwOnUnmanagedObject() { Dog dog = new Dog(); try { - dog.removeChangeListeners(); + dog.removeAllChangeListeners(); fail("Failed to remove null listener."); } catch (IllegalArgumentException ignore) { looperThread.testComplete(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index cc3b05f929..7b942c51ee 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -354,7 +354,6 @@ public static void addChangeListener(E object, RealmChang } } - /** * Removes a previously registered listener. * @@ -396,18 +395,40 @@ public static void removeChangeListener(E object, RealmCh /** * Removes all registered listeners. + * + * @deprecated Use {@link #removeAllChangeListeners()} instead. */ + @Deprecated public final void removeChangeListeners() { RealmObject.removeChangeListeners(this); } + /** + * Removes all registered listeners. + */ + public final void removeAllChangeListeners() { + RealmObject.removeAllChangeListeners(this); + } + /** * Removes all registered listeners from the given RealmObject. * * @param object RealmObject to remove all listeners from. * @throws IllegalArgumentException if object is {@code null} or isn't managed by Realm. + * @deprecated Use {@link RealmObject#removeAllChangeListeners(RealmModel)} instead. */ + @Deprecated public static void removeChangeListeners(E object) { + removeAllChangeListeners(object); + } + + /** + * Removes all registered listeners from the given RealmObject. + * + * @param object RealmObject to remove all listeners from. + * @throws IllegalArgumentException if object is {@code null} or isn't managed by Realm. + */ + public static void removeAllChangeListeners(E object) { if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); From ef8faf8b5021bf51bed75698ca98dc11aaec33e8 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Fri, 24 Feb 2017 02:24:50 -0800 Subject: [PATCH 0516/2110] Fix compacting on external storage Fix #4140. Update Object Store to 37a2ba42b. --- CHANGELOG.md | 1 + .../androidTest/java/io/realm/RealmTests.java | 16 ++++++++++++++++ .../main/cpp/io_realm_internal_SharedRealm.cpp | 3 ++- realm/realm-library/src/main/cpp/object-store | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 884751ce0f..2f82e065f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fixed log levels in JNI layer (#4204). * Fixed a bug in encryption (#4128). +* Fixed "Read-only file system" exception when compacting Realm file on external storage (#4140). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 4337317fcc..40320d971f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -1031,6 +1031,22 @@ public void compactRealm_populatedRealm() throws IOException { assertTrue(before >= after); } + @Test + public void compactRealm_onExternalStorage() { + final File externalFilesDir = context.getExternalFilesDir(null); + final RealmConfiguration config = new RealmConfiguration.Builder() + .directory(externalFilesDir) + .name("external.realm") + .build(); + Realm.deleteRealm(config); + Realm realm = Realm.getInstance(config); + realm.close(); + assertTrue(Realm.compactRealm(config)); + realm = Realm.getInstance(config); + realm.close(); + Realm.deleteRealm(config); + } + @Test public void copyToRealm_null() { realm.beginTransaction(); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 1e320e876b..066b0ed419 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -5,6 +5,7 @@ #endif #include +#include #include "object_store.hpp" #include "shared_realm.hpp" @@ -38,7 +39,7 @@ Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv *env, jclass, jstring tempo try { JStringAccessor path(env, temporary_directory_path); // throws - realm::set_temporary_directory(std::string(path)); // throws + SharedGroupOptions::set_sys_tmp_dir(std::string(path)); // throws } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 9a8520da95..48853a33f6 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 9a8520da95fc2505c1634d6d801f12ea73109cac +Subproject commit 48853a33f61447f8d4b502660c114e5bf7076a6f From bea00e9586cae129b0a75f8e0a1a6a7a38ff3257 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 24 Feb 2017 19:05:20 +0800 Subject: [PATCH 0517/2110] Remove observable interfaces (#4242) It is very difficult to implements RealmObservable for RealmObject because of generics. So implements those methods directly instead. --- .../src/main/java/io/realm/DynamicRealm.java | 17 ++++-- .../src/main/java/io/realm/Realm.java | 17 ++++-- .../io/realm/RealmCollectionObservable.java | 50 ----------------- .../src/main/java/io/realm/RealmList.java | 42 +++++++++----- .../main/java/io/realm/RealmObservable.java | 55 ------------------- .../src/main/java/io/realm/RealmResults.java | 46 ++++++++++++---- 6 files changed, 86 insertions(+), 141 deletions(-) delete mode 100644 realm/realm-library/src/main/java/io/realm/RealmCollectionObservable.java delete mode 100644 realm/realm-library/src/main/java/io/realm/RealmObservable.java diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 86c894ca50..852ef13ef2 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -42,7 +42,7 @@ * @see Realm * @see RealmSchema */ -public class DynamicRealm extends BaseRealm implements RealmObservable { +public class DynamicRealm extends BaseRealm { private DynamicRealm(RealmConfiguration configuration) { super(configuration); @@ -134,23 +134,28 @@ public RealmQuery where(String className) { * @see #removeAllChangeListeners() * @see #waitForChange() */ - @Override public void addChangeListener(RealmChangeListener listener) { super.addListener(listener); } /** - * {@inheritDoc} + * Removes the specified change listener. + * + * @param listener the change listener to be removed. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + * @see io.realm.RealmChangeListener */ - @Override public void removeChangeListener(RealmChangeListener listener) { super.removeListener(listener); } /** - * {@inheritDoc} + * Removes all user-defined change listeners. + * + * @throws IllegalStateException if you try to remove listeners from a non-Looper Thread. + * @see io.realm.RealmChangeListener */ - @Override public void removeAllChangeListeners() { super.removeAllListeners(); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 746dfb862b..dc69b8578b 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -124,7 +124,7 @@ * @see ACID * @see Examples using Realm */ -public class Realm extends BaseRealm implements RealmObservable { +public class Realm extends BaseRealm { public static final String DEFAULT_REALM_NAME = RealmConfiguration.DEFAULT_REALM_NAME; @@ -1281,23 +1281,28 @@ public RealmQuery where(Class clazz) { * @see #removeChangeListener(RealmChangeListener) * @see #removeAllChangeListeners() */ - @Override public void addChangeListener(RealmChangeListener listener) { super.addListener(listener); } /** - * {@inheritDoc} + * Removes the specified change listener. + * + * @param listener the change listener to be removed. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + * @see io.realm.RealmChangeListener */ - @Override public void removeChangeListener(RealmChangeListener listener) { super.removeListener(listener); } /** - * {@inheritDoc} + * Removes all user-defined change listeners. + * + * @throws IllegalStateException if you try to remove listeners from a non-Looper Thread. + * @see io.realm.RealmChangeListener */ - @Override public void removeAllChangeListeners() { super.removeAllListeners(); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmCollectionObservable.java b/realm/realm-library/src/main/java/io/realm/RealmCollectionObservable.java deleted file mode 100644 index dcb8a5b61e..0000000000 --- a/realm/realm-library/src/main/java/io/realm/RealmCollectionObservable.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -/** - * A collection class implementing this interface is capable of reporting fine-grained notifications about how the - * collection is changed. It will report insertions, deletions and changes, but not how an individual element - * changed. When a change is detected all registered listeners will be triggered. - *

          - * This is often useful when updating UI elements, e.g. {@code RecyclerView.Adapter} can provide nicer animations and - * work more effectively if it knows exactly which elements changed. - * @see RealmObservable for information about more coarse-grained notifications. - * @see Android Adapters - */ -public interface RealmCollectionObservable - extends RealmObservable { - /** - * Adds a change listener to this {@link OrderedRealmCollection}. - * - * @param listener the change listener to be notified. - * @throws IllegalArgumentException if the change listener is {@code null}. - * @throws IllegalStateException if you try to add a listener from a non-Looper or - * {@link android.app.IntentService} thread. - */ - void addChangeListener(S listener); - - /** - * Removes the specified change listener. - * - * @param listener the change listener to be removed. - * @throws IllegalArgumentException if the change listener is {@code null}. - * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. - * @see io.realm.RealmChangeListener - */ - void removeChangeListener(S listener); -} diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 3d0c9ff7bd..eb6c7bd0d4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -49,9 +49,7 @@ * @param the class of objects in list. */ -public class RealmList extends AbstractList - implements OrderedRealmCollection, - RealmCollectionObservable, OrderedRealmCollectionChangeListener>> { +public class RealmList extends AbstractList implements OrderedRealmCollection { private static final String ONLY_IN_MANAGED_MODE_MESSAGE = "This method is only available in managed mode"; private static final String NULL_OBJECTS_NOT_ALLOWED_MESSAGE = "RealmList does not accept null values"; @@ -851,45 +849,63 @@ private void checkForAddRemoveListener(Object listener, boolean checkListener) { } /** - * {@inheritDoc} + * Adds a change listener to this {@link RealmList}. + * + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to add a listener from a non-Looper or + * {@link android.app.IntentService} thread. */ - @Override public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); collection.addListener(this, listener); } /** - * {@inheritDoc} + * Removes the specified change listener. + * + * @param listener the change listener to be removed. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + * @see io.realm.RealmChangeListener */ - @Override public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); collection.removeListener(this, listener); } /** - * {@inheritDoc} + * Adds a change listener to this {@link RealmList}. + * + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to add a listener from a non-Looper or + * {@link android.app.IntentService} thread. */ - @Override public void addChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); collection.addListener(this, listener); } /** - * {@inheritDoc} + * Removes the specified change listener. + * + * @param listener the change listener to be removed. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + * @see io.realm.RealmChangeListener */ - @Override public void removeChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); collection.removeListener(this, listener); } /** - * {@inheritDoc} + * Removes all user-defined change listeners. + * + * @throws IllegalStateException if you try to remove listeners from a non-Looper Thread. + * @see io.realm.RealmChangeListener */ - @Override public void removeAllChangeListeners() { checkForAddRemoveListener(null, false); collection.removeAllListeners(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmObservable.java b/realm/realm-library/src/main/java/io/realm/RealmObservable.java deleted file mode 100644 index 6d9e619b09..0000000000 --- a/realm/realm-library/src/main/java/io/realm/RealmObservable.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -/** - * A class implementing this interface is capable of reporting when the data stored by the class have changed. When that - * happens all registered {@link RealmChangeListener}'s will be triggered. - *

          - * This class will only report that something changed, not what changed. - * @see RealmCollectionObservable for information about more fine-grained collection notifications. - */ -public interface RealmObservable { - /** - * Adds a change listener to this {@link RealmResults}, {@link RealmList}, {@link Realm}, {@link DynamicRealm} or - * {@link RealmObject}. - * - * @param listener the change listener to be notified. - * @throws IllegalArgumentException if the change listener is {@code null}. - * @throws IllegalStateException if you try to add a listener from a non-Looper or - * {@link android.app.IntentService} thread. - */ - void addChangeListener(RealmChangeListener listener); - - /** - * Removes the specified change listener. - * - * @param listener the change listener to be removed. - * @throws IllegalArgumentException if the change listener is {@code null}. - * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. - * @see io.realm.RealmChangeListener - */ - void removeChangeListener(RealmChangeListener listener); - - /** - * Removes all user-defined change listeners. - * - * @throws IllegalStateException if you try to remove listeners from a non-Looper Thread. - * @see io.realm.RealmChangeListener - */ - void removeAllChangeListeners(); -} diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index d16810cd74..bdd4e1a6c2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -49,8 +49,7 @@ * @see RealmQuery#findAll() * @see Realm#executeTransaction(Realm.Transaction) */ -public class RealmResults extends OrderedRealmCollectionImpl - implements RealmCollectionObservable, OrderedRealmCollectionChangeListener>> { +public class RealmResults extends OrderedRealmCollectionImpl { RealmResults(BaseRealm realm, Collection collection, Class clazz) { super(realm, collection, clazz); @@ -106,15 +105,26 @@ public boolean load() { } /** - * {@inheritDoc} + * Adds a change listener to this {@link RealmResults}. + * + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to add a listener from a non-Looper or + * {@link android.app.IntentService} thread. */ - @Override public void addChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); collection.addListener(this, listener); } - @Override + /** + * Adds a change listener to this {@link RealmResults}. + * + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to add a listener from a non-Looper or + * {@link android.app.IntentService} thread. + */ public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); collection.addListener(this, listener); @@ -129,9 +139,11 @@ private void checkForAddRemoveListener(Object listener, boolean checkListener) { } /** - * {@inheritDoc} + * Removes all user-defined change listeners. + * + * @throws IllegalStateException if you try to remove listeners from a non-Looper Thread. + * @see io.realm.RealmChangeListener */ - @Override public void removeAllChangeListeners() { checkForAddRemoveListener(null, false); collection.removeAllListeners(); @@ -140,21 +152,33 @@ public void removeAllChangeListeners() { /** * Use {@link #removeAllChangeListeners()} instead. */ + @SuppressWarnings("unused") @Deprecated public void removeChangeListeners() { removeAllChangeListeners(); } /** - * {@inheritDoc} + * Removes the specified change listener. + * + * @param listener the change listener to be removed. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + * @see io.realm.RealmChangeListener */ - @Override - public void removeChangeListener(RealmChangeListener listener) { + public void removeChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); collection.removeListener(this, listener); } - @Override + /** + * Removes the specified change listener. + * + * @param listener the change listener to be removed. + * @throws IllegalArgumentException if the change listener is {@code null}. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + * @see io.realm.RealmChangeListener + */ public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); collection.removeListener(this, listener); From 343f7feb92930b593b40c5267143635d479c71b7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 24 Feb 2017 13:21:20 +0100 Subject: [PATCH 0518/2110] RealmList.asObservable() (#4233) --- CHANGELOG.md | 3 +- .../java/io/realm/RxJavaTests.java | 66 +++++++++++++++ .../src/main/java/io/realm/RealmList.java | 42 ++++++++++ .../io/realm/rx/RealmObservableFactory.java | 83 ++++++++++++++++--- 4 files changed, 183 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 826c83da75..c1182bb0d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ * Added support for sorting by link's field (#672). * Added `OrderedRealmCollectionSnapshot` class and `OrderedRealmCollection.createSnapshot()` method. `OrderedRealmCollectionSnapshot` is useful when changing `RealmResults` or `RealmList` in simple loops. -* Added support for adding listeners on `RealmList`. +* Added support for ChangeListeners on `RealmList`. +* Added `RealmList.asObservable()`. ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index ab6ef1e4fd..f2a3e78586 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -33,6 +33,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.CyclicType; +import io.realm.entities.Dog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -238,6 +239,24 @@ public void call(RealmResults rxResults) { subscription.unsubscribe(); } + @Test + @UiThreadTest + public void realmList_emittedOnSubscribe() { + final AtomicBoolean subscribedNotified = new AtomicBoolean(false); + realm.beginTransaction(); + final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + realm.commitTransaction(); + subscription = list.asObservable().subscribe(new Action1>() { + @Override + public void call(RealmList rxList) { + assertTrue(rxList == list); + subscribedNotified.set(true); + } + }); + assertTrue(subscribedNotified.get()); + subscription.unsubscribe(); + } + @Test @UiThreadTest public void dynamicRealmResults_emittedOnSubscribe() { @@ -279,6 +298,30 @@ public void call(RealmResults allTypes) { realm.commitTransaction(); } + @Test + @RunTestInLooperThread + public void realmList_emittedOnUpdate() { + final AtomicInteger subscriberCalled = new AtomicInteger(0); + Realm realm = looperThread.realm; + realm.beginTransaction(); + final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + realm.commitTransaction(); + + subscription = list.asObservable().subscribe(new Action1>() { + @Override + public void call(RealmList dogs) { + if (subscriberCalled.incrementAndGet() == 2) { + assertEquals(1, list.size()); + looperThread.testComplete(); + } + } + }); + + realm.beginTransaction(); + list.add(new Dog()); + realm.commitTransaction(); + } + @Test @RunTestInLooperThread public void dynamicRealmResults_emittedOnUpdate() { @@ -549,6 +592,29 @@ public void call(RealmResults allTypes) { assertTrue(realm.isClosed()); } + @Test + @UiThreadTest + public void realmList_closeInDoOnUnsubscribe() { + realm.beginTransaction(); + RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + realm.commitTransaction(); + + Observable> observable = list.asObservable().doOnUnsubscribe(new Action0() { + @Override + public void call() { + realm.close(); + } + }); + subscription = observable.subscribe(new Action1>() { + @Override + public void call(RealmList dogs) { + } + }); + + subscription.unsubscribe(); + assertTrue(realm.isClosed()); + } + @Test @UiThreadTest public void dynamicRealmResults_closeInDoOnUnsubscribe() { diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index eb6c7bd0d4..f1211cf44c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -30,6 +30,7 @@ import io.realm.internal.InvalidRow; import io.realm.internal.LinkView; import io.realm.internal.RealmObjectProxy; +import rx.Observable; /** * RealmList is used to model one-to-many relationships in a {@link io.realm.RealmObject}. @@ -840,6 +841,47 @@ public String toString() { return sb.toString(); } + + /** + * Returns an Rx Observable that monitors changes to this RealmList. It will emit the current RealmList when + * subscribed to. RealmList will continually be emitted as the RealmList is updated - + * {@code onComplete} will never be called. + * + * If you would like the {@code asObservable()} to stop emitting items you can instruct RxJava to + * only emit only the first item by using the {@code first()} operator: + * + *

          +     * {@code
          +     * list.asObservable()
          +     *      .first()
          +     *      .subscribe( ... ) // You only get the results once
          +     * }
          +     * 
          + * + *

          Note that when the {@link Realm} is accessed from threads other than where it was created, + * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers + * with {@code subscribeOn()} and {@code observeOn()}. + * + * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. + * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the + * corresponding Realm instance doesn't support RxJava. + * @see RxJava and Realm + */ + @SuppressWarnings("unchecked") + public Observable> asObservable() { + if (realm instanceof Realm) { + return realm.configuration.getRxFactory().from((Realm) realm, this); + } else if (realm instanceof DynamicRealm) { + DynamicRealm dynamicRealm = (DynamicRealm) realm; + RealmList dynamicList = (RealmList) this; + @SuppressWarnings("UnnecessaryLocalVariable") + Observable results = realm.configuration.getRxFactory().from(dynamicRealm, dynamicList); + return results; + } else { + throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava."); + } + } + private void checkForAddRemoveListener(Object listener, boolean checkListener) { if (checkListener && listener == null) { throw new IllegalArgumentException("Listener should not be null"); diff --git a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java index 2a0a090e61..b13ccbc127 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java @@ -45,15 +45,21 @@ */ public class RealmObservableFactory implements RxObservableFactory { - // Maps for storing strong references to RealmResults while they are subscribed to. + // Maps for storing strong references to Realm classes while they are subscribed to. // This is needed if users create Observables without manually maintaining a reference to them. - // In that case RealmObjects/RealmResults might be GC'ed too early. + // In that case RealmObjects/RealmResults/RealmLists might be GC'ed too early. ThreadLocal> resultsRefs = new ThreadLocal>() { @Override protected StrongReferenceCounter initialValue() { return new StrongReferenceCounter(); } }; + ThreadLocal> listRefs = new ThreadLocal>() { + @Override + protected StrongReferenceCounter initialValue() { + return new StrongReferenceCounter(); + } + }; ThreadLocal> objectRefs = new ThreadLocal>() { @Override protected StrongReferenceCounter initialValue() { @@ -127,7 +133,6 @@ public void call() { @Override public Observable> from(final Realm realm, final RealmResults results) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new Observable.OnSubscribe>() { @Override public void call(final Subscriber> subscriber) { @@ -198,17 +203,75 @@ public void call() { } @Override - public Observable> from(Realm realm, RealmList list) { - return getRealmListObservable(); + public Observable> from(Realm realm, final RealmList list) { + final RealmConfiguration realmConfig = realm.getConfiguration(); + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(final Subscriber> subscriber) { + // Gets instance to make sure that the Realm is open for as long as the + // Observable is subscribed to it. + final Realm observableRealm = Realm.getInstance(realmConfig); + listRefs.get().acquireReference(list); + + final RealmChangeListener> listener = new RealmChangeListener>() { + @Override + public void onChange(RealmList result) { + if (!subscriber.isUnsubscribed()) { + subscriber.onNext(list); + } + } + }; + list.addChangeListener(listener); + subscriber.add(Subscriptions.create(new Action0() { + @Override + public void call() { + list.removeChangeListener(listener); + observableRealm.close(); + listRefs.get().releaseReference(list); + } + })); + + // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest + // value. + subscriber.onNext(list); + } + }); } @Override - public Observable> from(DynamicRealm realm, RealmList list) { - return getRealmListObservable(); - } + public Observable> from(DynamicRealm realm, final RealmList list) { + final RealmConfiguration realmConfig = realm.getConfiguration(); + return Observable.create(new Observable.OnSubscribe>() { + @Override + public void call(final Subscriber> subscriber) { + // Gets instance to make sure that the Realm is open for as long as the + // Observable is subscribed to it. + final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); + listRefs.get().acquireReference(list); + + final RealmChangeListener> listener = new RealmChangeListener>() { + @Override + public void onChange(RealmList result) { + if (!subscriber.isUnsubscribed()) { + subscriber.onNext(list); + } + } + }; + list.addChangeListener(listener); + subscriber.add(Subscriptions.create(new Action0() { + @Override + public void call() { + list.removeChangeListener(listener); + observableRealm.close(); + listRefs.get().releaseReference(list); + } + })); - private Observable> getRealmListObservable() { - throw new RuntimeException("RealmList does not support change listeners yet, so cannot create an Observable"); + // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest + // value. + subscriber.onNext(list); + } + }); } @Override From ef15771f6b5e0139ac9d37f8f3e5d4cc9b33e0ae Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 24 Feb 2017 14:02:57 +0100 Subject: [PATCH 0519/2110] Update release date --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f82e065f8..0c854a9a20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ -## 2.3.2 (YYYY-MM-DD) +## 2.3.2 (2017-02-24) ### Bug fixes -* Fixed log levels in JNI layer (#4204). -* Fixed a bug in encryption (#4128). -* Fixed "Read-only file system" exception when compacting Realm file on external storage (#4140). +* Log levels in JNI layer were all reported as "Error" (#4204). +* Encrypted realms can end up corrupted if many threads are reading and writing at the same time (#4128). +* "Read-only file system" exception when compacting Realm file on external storage (#4140). ### Internal From d7905ed2e948ab752598870cd9ecb96c4536500a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Sun, 26 Feb 2017 21:22:48 +0800 Subject: [PATCH 0520/2110] Deprecate distinctXxx methods of RealmResults (#4210) --- CHANGELOG.md | 4 ++ .../java/io/realm/RealmQueryTests.java | 11 +++- .../io/realm/OrderedRealmCollectionImpl.java | 59 +++---------------- .../src/main/java/io/realm/RealmQuery.java | 1 + .../src/main/java/io/realm/RealmResults.java | 30 ++++++++++ 5 files changed, 52 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1182bb0d8..df16a3f192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ * `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. * `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. +### Deprecated + +* `RealmResults.distinct()` and `RealmResults.distinctAsync()`. Use `RealmQuery.distinct()` and `RealmQuery.distinctAsync()` instead. + ### Enhancements * Added support for sorting by link's field (#672). diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index f70b39ab96..67d16e43c2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -16,7 +16,6 @@ package io.realm; -import android.os.Looper; import android.support.test.runner.AndroidJUnit4; import org.junit.After; @@ -31,8 +30,6 @@ import java.util.Date; import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -3023,6 +3020,7 @@ public void onChange(RealmResults object) { } @Test + @RunTestInLooperThread public void distinctAsync_doesNotExist() { final long numberOfBlocks = 25; final long numberOfObjects = 10; @@ -3032,9 +3030,11 @@ public void distinctAsync_doesNotExist() { realm.where(AnnotationIndexTypes.class).distinctAsync("doesNotExist"); } catch (IllegalArgumentException ignored) { } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void distinctAsync_invalidTypes() { populateTestRealm(realm, TEST_DATA_SIZE); @@ -3044,9 +3044,11 @@ public void distinctAsync_invalidTypes() { } catch (IllegalArgumentException ignored) { } } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void distinctAsync_indexedLinkedFields() { final long numberOfBlocks = 25; final long numberOfObjects = 10; @@ -3059,9 +3061,11 @@ public void distinctAsync_indexedLinkedFields() { } catch (IllegalArgumentException ignored) { } } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void distinctAsync_notIndexedLinkedFields() { populateForDistinctInvalidTypesLinked(realm); @@ -3069,6 +3073,7 @@ public void distinctAsync_notIndexedLinkedFields() { realm.where(AllJavaTypes.class).distinctAsync(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); } catch (IllegalArgumentException ignored) { } + looperThread.testComplete(); } @Test diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index bd05486043..63cbb78484 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -50,6 +50,7 @@ Collection getCollection() { /** * {@inheritDoc} */ + @Override public boolean isValid() { return collection.isValid(); } @@ -60,6 +61,7 @@ public boolean isValid() { * @return {@code true}. * @see RealmCollection#isManaged() */ + @Override public boolean isManaged() { return true; } @@ -308,6 +310,7 @@ public int size() { /** * {@inheritDoc} */ + @Override public Number min(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); @@ -317,6 +320,7 @@ public Number min(String fieldName) { /** * {@inheritDoc} */ + @Override public Date minDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); @@ -326,6 +330,7 @@ public Date minDate(String fieldName) { /** * {@inheritDoc} */ + @Override public Number max(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); @@ -342,6 +347,7 @@ public Number max(String fieldName) { * {@code null} values are ignored. * @throws IllegalArgumentException if fieldName is not a Date field. */ + @Override public Date maxDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); @@ -352,6 +358,7 @@ public Date maxDate(String fieldName) { /** * {@inheritDoc} */ + @Override public Number sum(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); @@ -361,6 +368,7 @@ public Number sum(String fieldName) { /** * {@inheritDoc} */ + @Override public double average(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); @@ -369,55 +377,6 @@ public double average(String fieldName) { return avg.doubleValue(); } - /** - * Returns a distinct set of objects of a specific class. If the result is sorted, the first - * object will be returned in case of multiple occurrences, otherwise it is undefined which - * object is returned. - * - * @param fieldName the field name. - * @return a new non-null {@link RealmResults} containing the distinct objects. - * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, - * is not indexed, or points to linked fields. - */ - public RealmResults distinct(String fieldName) { - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(collection.getTable(), fieldName); - Collection distinctCollection = collection.distinct(distinctDescriptor); - return createLoadedResults(distinctCollection); - } - - /** - * Asynchronously returns a distinct set of objects of a specific class. If the result is - * sorted, the first object will be returned in case of multiple occurrences, otherwise it is - * undefined which object is returned. - * - * @param fieldName the field name. - * @return immediately a {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the - * query completes. - * @throws IllegalArgumentException if a field is null, does not exist, is an unsupported type, - * is not indexed, or points to linked fields. - */ - public RealmResults distinctAsync(String fieldName) { - realm.sharedRealm.capabilities.checkCanDeliverNotification(RealmQuery.ASYNC_QUERY_WRONG_THREAD_MESSAGE); - return where().distinctAsync(fieldName); - } - - /** - * Returns a distinct set of objects from a specific class. When multiple distinct fields are - * given, all unique combinations of values in the fields will be returned. In case of multiple - * matches, it is undefined which object is returned. Unless the result is sorted, then the - * first object will be returned. - * - * @param firstFieldName first field name to use when finding distinct objects. - * @param remainingFieldNames remaining field names when determining all unique combinations of field values. - * @return a non-null {@link RealmResults} containing the distinct objects. - * @throws IllegalArgumentException if field names is empty or {@code null}, does not exist, - * is an unsupported type, or points to a linked field. - */ - public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { - return where().distinct(firstFieldName, remainingFieldNames); - } - // Deleting /** @@ -588,7 +547,7 @@ protected E convertRowToObject(UncheckedRow row) { } } - private RealmResults createLoadedResults(Collection newCollection) { + RealmResults createLoadedResults(Collection newCollection) { RealmResults results; if (className != null) { results = new RealmResults(realm, newCollection, className); diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index a1abdc45a1..399a1bb55a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1383,6 +1383,7 @@ public RealmResults distinct(String fieldName) { * is not indexed, or points to linked fields. */ public RealmResults distinctAsync(String fieldName) { + realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(query.getTable(), fieldName); return createRealmResults(query, null, distinctDescriptor, false); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index bdd4e1a6c2..e6a55ab77e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -20,6 +20,7 @@ import android.os.Looper; import io.realm.internal.Collection; +import io.realm.internal.SortDescriptor; import rx.Observable; /** @@ -225,4 +226,33 @@ public Observable> asObservable() { throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava."); } } + + /** + * @deprecated use {@link RealmQuery#distinct(String)} on the return value of {@link #where()} instead. This will + * be removed in coming 3.x.x minor releases. + */ + @Deprecated + public RealmResults distinct(String fieldName) { + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(collection.getTable(), fieldName); + Collection distinctCollection = collection.distinct(distinctDescriptor); + return createLoadedResults(distinctCollection); + } + + /** + * @deprecated use {@link RealmQuery#distinctAsync(String)} on the return value of {@link #where()} instead. This + * will be removed in coming 3.x.x minor releases. + */ + @Deprecated + public RealmResults distinctAsync(String fieldName) { + return where().distinctAsync(fieldName); + } + + /** + * @deprecated use {@link RealmQuery#distinct(String, String...)} on the return value of {@link #where()} instead. + * This will be removed in coming 3.x.x minor releases. + */ + @Deprecated + public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { + return where().distinct(firstFieldName, remainingFieldNames); + } } From e479aa218a929544966fc5ad67db9825e01aac98 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 26 Feb 2017 17:01:33 +0100 Subject: [PATCH 0521/2110] Fix default values crashing if calling another constructor (#4249) --- .../realm/transformer/BytecodeModifier.groovy | 11 +++--- .../realm/transformer/RealmTransformer.groovy | 3 +- .../processor/RealmProxyClassGenerator.java | 3 ++ .../io/realm/AllTypesRealmProxy.java | 3 ++ .../io/realm/BooleansRealmProxy.java | 3 ++ .../io/realm/NullTypesRealmProxy.java | 3 ++ .../resources/io/realm/SimpleRealmProxy.java | 3 ++ .../androidTest/java/io/realm/RealmTests.java | 10 +++++ .../DefaultValueFromOtherConstructor.java | 39 +++++++++++++++++++ 9 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueFromOtherConstructor.java diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index 4850279490..d64572ea3c 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -85,11 +85,12 @@ class BytecodeModifier { clazz.addInterface(proxyInterface) } - public static void callInjectObjectContextFromDefaultConstructor(CtClass clazz) { - def defaultConstructor = clazz.getDeclaredConstructor() - defaultConstructor.insertBeforeBody('if ($0 instanceof io.realm.internal.RealmObjectProxy) {' + - ' ((io.realm.internal.RealmObjectProxy) $0).realm$injectObjectContext();' + - ' }') + public static void callInjectObjectContextFromConstructors(CtClass clazz) { + clazz.getConstructors().each { + it.insertBeforeBody('if ($0 instanceof io.realm.internal.RealmObjectProxy) {' + + ' ((io.realm.internal.RealmObjectProxy) $0).realm$injectObjectContext();' + + ' }') + } } /** diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index 675554324c..d7ca24a76a 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -26,7 +26,6 @@ import io.realm.annotations.Ignore import io.realm.annotations.RealmClass import javassist.ClassPool import javassist.CtClass -import javassist.LoaderClassPath import org.gradle.api.Project import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -131,7 +130,7 @@ class RealmTransformer extends Transform { inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) BytecodeModifier.addRealmProxyInterface(it, classPool) - BytecodeModifier.callInjectObjectContextFromDefaultConstructor(it) + BytecodeModifier.callInjectObjectContextFromConstructors(it) } // Use accessors instead of direct field access diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 294b78af18..6aa0cf7e0e 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -480,6 +480,9 @@ private void emitInjectContextMethod(JavaWriter writer) throws IOException { EnumSet.of(Modifier.PUBLIC) // Modifiers ); // Argument type & argument name + writer.beginControlFlow("if (this.proxyState != null)"); + writer.emitStatement("return"); + writer.endControlFlow(); writer.emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()"); writer.emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()); writer.emitStatement("this.proxyState = new ProxyState<%1$s>(this)", qualifiedClassName); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index dba539a01d..7666014010 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -114,6 +114,9 @@ public final AllTypesColumnInfo clone() { @Override public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (AllTypesColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(this); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 2b6e7c0a69..8906eb90e6 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -88,6 +88,9 @@ public final BooleansColumnInfo clone() { @Override public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (BooleansColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(this); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 7e7c40aa7f..a541004034 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -173,6 +173,9 @@ public final NullTypesColumnInfo clone() { @Override public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (NullTypesColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(this); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index b26a76de01..694515e02c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -78,6 +78,9 @@ public final SimpleColumnInfo clone() { @Override public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (SimpleColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(this); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 40320d971f..37aaa12769 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -67,6 +67,7 @@ import io.realm.entities.CyclicType; import io.realm.entities.CyclicTypePrimaryKey; import io.realm.entities.DefaultValueConstructor; +import io.realm.entities.DefaultValueFromOtherConstructor; import io.realm.entities.DefaultValueOfField; import io.realm.entities.DefaultValueOverwriteNullLink; import io.realm.entities.DefaultValueSetter; @@ -2425,6 +2426,15 @@ public void execute(Realm realm) { RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); } + @Test + public void createObject_defaultValueFromOtherConstructor() { + realm.beginTransaction(); + DefaultValueFromOtherConstructor obj = realm.createObject(DefaultValueFromOtherConstructor.class); + realm.commitTransaction(); + + assertEquals(42, obj.getFieldLong()); + } + @Test public void copyToRealm_defaultValuesAreIgnored() { final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueFromOtherConstructor.java b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueFromOtherConstructor.java new file mode 100644 index 0000000000..3ce4078ad8 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueFromOtherConstructor.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import io.realm.RealmObject; + +public class DefaultValueFromOtherConstructor extends RealmObject { + + public static final String CLASS_NAME = "DefaultValueOfField"; + public static String FIELD_LONG = "fieldLong"; + + private long fieldLong; + + public DefaultValueFromOtherConstructor() { + this(42); + } + + public DefaultValueFromOtherConstructor(long fieldLong) { + this.fieldLong = fieldLong; + } + + public long getFieldLong() { + return fieldLong; + } +} From 87bdb7212395df85fa06f4ee1b1ecd7cf4ac9440 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 26 Feb 2017 17:44:52 +0100 Subject: [PATCH 0522/2110] Crash when on constructor calls another with default values (#4253) --- .../realm/transformer/BytecodeModifier.groovy | 11 +++--- .../realm/transformer/RealmTransformer.groovy | 3 +- .../processor/RealmProxyClassGenerator.java | 3 ++ .../io/realm/AllTypesRealmProxy.java | 3 ++ .../io/realm/BooleansRealmProxy.java | 3 ++ .../io/realm/NullTypesRealmProxy.java | 3 ++ .../resources/io/realm/SimpleRealmProxy.java | 3 ++ .../androidTest/java/io/realm/RealmTests.java | 10 +++++ .../DefaultValueFromOtherConstructor.java | 39 +++++++++++++++++++ 9 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueFromOtherConstructor.java diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index 4850279490..d64572ea3c 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -85,11 +85,12 @@ class BytecodeModifier { clazz.addInterface(proxyInterface) } - public static void callInjectObjectContextFromDefaultConstructor(CtClass clazz) { - def defaultConstructor = clazz.getDeclaredConstructor() - defaultConstructor.insertBeforeBody('if ($0 instanceof io.realm.internal.RealmObjectProxy) {' + - ' ((io.realm.internal.RealmObjectProxy) $0).realm$injectObjectContext();' + - ' }') + public static void callInjectObjectContextFromConstructors(CtClass clazz) { + clazz.getConstructors().each { + it.insertBeforeBody('if ($0 instanceof io.realm.internal.RealmObjectProxy) {' + + ' ((io.realm.internal.RealmObjectProxy) $0).realm$injectObjectContext();' + + ' }') + } } /** diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index 6ab5867bf1..9c9ae5f070 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -26,7 +26,6 @@ import io.realm.annotations.Ignore import io.realm.annotations.RealmClass import javassist.ClassPool import javassist.CtClass -import javassist.LoaderClassPath import org.gradle.api.Project import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -131,7 +130,7 @@ class RealmTransformer extends Transform { inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) BytecodeModifier.addRealmProxyInterface(it, classPool) - BytecodeModifier.callInjectObjectContextFromDefaultConstructor(it) + BytecodeModifier.callInjectObjectContextFromConstructors(it) } // Use accessors instead of direct field access diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 08d6b83387..a8b4b837f1 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -481,6 +481,9 @@ private void emitInjectContextMethod(JavaWriter writer) throws IOException { EnumSet.of(Modifier.PUBLIC) // Modifiers ); // Argument type & argument name + writer.beginControlFlow("if (this.proxyState != null)"); + writer.emitStatement("return"); + writer.endControlFlow(); writer.emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()"); writer.emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()); writer.emitStatement("this.proxyState = new ProxyState<%1$s>(%1$s.class, this)", qualifiedClassName); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index fe0cd0448b..e2a4c7c4f4 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -115,6 +115,9 @@ public final AllTypesColumnInfo clone() { @Override public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (AllTypesColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.AllTypes.class, this); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 07b6be83b5..70690e5ca4 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -89,6 +89,9 @@ public final BooleansColumnInfo clone() { @Override public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (BooleansColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.Booleans.class, this); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 529599e1a0..c6c702d9b7 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -174,6 +174,9 @@ public final NullTypesColumnInfo clone() { @Override public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (NullTypesColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.NullTypes.class, this); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index f6d219c999..eba82b26db 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -79,6 +79,9 @@ public final SimpleColumnInfo clone() { @Override public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (SimpleColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState(some.test.Simple.class, this); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 40320d971f..37aaa12769 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -67,6 +67,7 @@ import io.realm.entities.CyclicType; import io.realm.entities.CyclicTypePrimaryKey; import io.realm.entities.DefaultValueConstructor; +import io.realm.entities.DefaultValueFromOtherConstructor; import io.realm.entities.DefaultValueOfField; import io.realm.entities.DefaultValueOverwriteNullLink; import io.realm.entities.DefaultValueSetter; @@ -2425,6 +2426,15 @@ public void execute(Realm realm) { RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); } + @Test + public void createObject_defaultValueFromOtherConstructor() { + realm.beginTransaction(); + DefaultValueFromOtherConstructor obj = realm.createObject(DefaultValueFromOtherConstructor.class); + realm.commitTransaction(); + + assertEquals(42, obj.getFieldLong()); + } + @Test public void copyToRealm_defaultValuesAreIgnored() { final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueFromOtherConstructor.java b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueFromOtherConstructor.java new file mode 100644 index 0000000000..3ce4078ad8 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueFromOtherConstructor.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import io.realm.RealmObject; + +public class DefaultValueFromOtherConstructor extends RealmObject { + + public static final String CLASS_NAME = "DefaultValueOfField"; + public static String FIELD_LONG = "fieldLong"; + + private long fieldLong; + + public DefaultValueFromOtherConstructor() { + this(42); + } + + public DefaultValueFromOtherConstructor(long fieldLong) { + this.fieldLong = fieldLong; + } + + public long getFieldLong() { + return fieldLong; + } +} From 844511ef1fcec7bfc2dc75fea27b552bba59db4e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 26 Feb 2017 19:50:25 +0100 Subject: [PATCH 0523/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c854a9a20..559e9a4ee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.3.2 (2017-02-24) +## 2.3.2 (2017-02-27) ### Bug fixes From 1532af517e2166692efbc10b4fb4e6fd913f3b3a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 26 Feb 2017 19:52:45 +0100 Subject: [PATCH 0524/2110] Release v2.3.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 0c3a5eaf45..e7034819f6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.2-SNAPSHOT \ No newline at end of file +2.3.2 \ No newline at end of file From bdc34ca3ac04917819dfda9ecfbf5c5ec04458a9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 26 Feb 2017 19:52:46 +0100 Subject: [PATCH 0525/2110] Prepare next release v2.3.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e7034819f6..32ad32ab6c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.2 \ No newline at end of file +2.3.3-SNAPSHOT \ No newline at end of file From 0a0ad19bd6274ad4e125d93f0079289ca2141682 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 27 Feb 2017 12:27:13 +0900 Subject: [PATCH 0526/2110] Revert "now publishToMavenLocal in realm/realm-library/build.gradle depends on assembleRelease task insteadof assemble task in order to improve build time." (#4244) This reverts commit 8829f1d22e0ee1f373395acdb284e056f5cc5b6d. --- realm/realm-library/build.gradle | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 9b361cc5f4..ac7f1affbd 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -500,10 +500,8 @@ task deployCore(group: 'build setup', description: 'Deploy the latest version of } } -project.afterEvaluate { - publishToMavenLocal.dependsOn assembleRelease - preBuild.dependsOn deployCore -} +publishToMavenLocal.dependsOn assemble +preBuild.dependsOn deployCore if (project.hasProperty('dontCleanJniFiles')) { project.afterEvaluate { From 888efeb7388d319c8572cc42ed89cb3eea161717 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 27 Feb 2017 15:33:00 +0900 Subject: [PATCH 0527/2110] stabilize flaky test (#4243) --- .../java/io/realm/TypeBasedNotificationsTests.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index 54fb15250f..a5ddaaf8f1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -1053,23 +1053,24 @@ public void onChange(RealmResults object) { public void multiple_callbacks_should_be_invoked_realmresults_async() { final int NUMBER_OF_LISTENERS = 7; final Realm realm = looperThread.realm; + + realm.beginTransaction(); + Dog akamaru = realm.createObject(Dog.class); + realm.commitTransaction(); + realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { - looperThread.postRunnable(new Runnable() { + looperThread.postRunnableDelayed(new Runnable() { @Override public void run() { assertEquals(NUMBER_OF_LISTENERS, typebasedCommitInvocations.get()); looperThread.testComplete(); } - }); + }, 100L /* wait for listeners in RealmResults. Next run loop is not enough. */); } }); - realm.beginTransaction(); - Dog akamaru = realm.createObject(Dog.class); - realm.commitTransaction(); - RealmResults dogs = realm.where(Dog.class).findAllAsync(); assertTrue(dogs.load()); From 6f71f8a128269c4f186c7d3fd507163a950fb380 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 27 Feb 2017 10:41:35 +0100 Subject: [PATCH 0528/2110] Style fixes --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 679040a94d..e5beb08aae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ -## 3.0.0(YYYY-MM-DD) +## 3.0.0 (YYYY-MM-DD) ### Breaking changes -* `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object. (#2947). +* `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object (#2947). * `RealmResults` is auto-updated continuously. Any transaction on the current thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. The standard `RealmResults.iterator()` will continue to work as normal, which means that you can still delete or modify elements without impacting the iterator. The same is not true for simple for-loops. In some cases a simple for-loop will not work (https://realm.io/docs/java/3.0.0/api/io/realm/OrderedRealmCollection.html#loops), and you must use the new createSnapshot() method. * `RealmChangeListener` on `RealmObject` will now also be triggered when the object is deleted. Use `RealmObject.isValid()` to check this state(#3138). * `RealmObject.asObservable()` will now emit the object when it is deleted. Use `RealmObject.isValid()` to check this state (#3138). From 6133fae417d53a926baddc3f1b2cfc4f9c8bd1b4 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 27 Feb 2017 12:28:19 +0100 Subject: [PATCH 0529/2110] Fixing package name --- .../secureTokenAndroidKeyStore/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml b/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml index 15f02a70aa..57be5d1083 100644 --- a/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml +++ b/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml @@ -4,8 +4,8 @@ - + android:name="io.realm.examples.securetokenandroidkeystore.MyApplication"> + From c18d2d021acfb4e264f98d8d8a5a95c573bfb560 Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Mon, 27 Feb 2017 15:54:02 -0800 Subject: [PATCH 0530/2110] Update CHANGELOG.md --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5beb08aae..536864a3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,6 @@ * `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. * `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. - -### Deprecated - * `RealmResults.distinct()` and `RealmResults.distinctAsync()`. Use `RealmQuery.distinct()` and `RealmQuery.distinctAsync()` instead. ### Enhancements From b9e080c087f066b49f4d63a2c7189c2701f59b67 Mon Sep 17 00:00:00 2001 From: LYK Date: Tue, 28 Feb 2017 12:44:46 +0900 Subject: [PATCH 0531/2110] Remove unnecessary 'super's (#4248) Since this.removeListener and this.removeAllListeners does not exist, super.removeListener and super.removeAllListeners are not necessary. Just use removeListener and removeAllListeners instead. --- realm/realm-library/src/main/java/io/realm/DynamicRealm.java | 4 ++-- realm/realm-library/src/main/java/io/realm/Realm.java | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 852ef13ef2..f1067ed460 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -147,7 +147,7 @@ public void addChangeListener(RealmChangeListener listener) { * @see io.realm.RealmChangeListener */ public void removeChangeListener(RealmChangeListener listener) { - super.removeListener(listener); + removeListener(listener); } /** @@ -157,7 +157,7 @@ public void removeChangeListener(RealmChangeListener listener) { * @see io.realm.RealmChangeListener */ public void removeAllChangeListeners() { - super.removeAllListeners(); + removeAllListeners(); } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index dc69b8578b..53d10e1089 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -47,7 +47,6 @@ import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; -import io.realm.internal.Capabilities; import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; import io.realm.internal.ObjectServerFacade; @@ -1294,7 +1293,7 @@ public void addChangeListener(RealmChangeListener listener) { * @see io.realm.RealmChangeListener */ public void removeChangeListener(RealmChangeListener listener) { - super.removeListener(listener); + removeListener(listener); } /** @@ -1304,7 +1303,7 @@ public void removeChangeListener(RealmChangeListener listener) { * @see io.realm.RealmChangeListener */ public void removeAllChangeListeners() { - super.removeAllListeners(); + removeAllListeners(); } /** From 7438b4dc67d49c744c6a2a8d83154ec32f01c82e Mon Sep 17 00:00:00 2001 From: LYK Date: Tue, 28 Feb 2017 12:45:10 +0900 Subject: [PATCH 0532/2110] Remove unnecessary 'super's (#4247) Since this.addListener doesn't exist, 'super.' is not needed. --- realm/realm-library/src/main/java/io/realm/DynamicRealm.java | 2 +- realm/realm-library/src/main/java/io/realm/Realm.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 6df8fea533..6bd584741a 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -138,7 +138,7 @@ public RealmQuery where(String className) { * @see #removeAllChangeListeners() */ public void addChangeListener(RealmChangeListener listener) { - super.addListener(listener); + addListener(listener); } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 2f483be270..3447dc70b2 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1282,7 +1282,7 @@ public RealmQuery where(Class clazz) { * @see #removeAllChangeListeners() */ public void addChangeListener(RealmChangeListener listener) { - super.addListener(listener); + addListener(listener); } /** From ba8b9f2793d4a5e36dacb2dfc9018ae6bec79802 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 28 Feb 2017 18:45:35 +0900 Subject: [PATCH 0533/2110] Fixed element type checking in DynamicRealmOject#setList(). (#4254) * Fixed element type checking in DynamicRealmOject#setList() (#4252). * Update CHANGELOG.md * PR fixes * PR fixes --- CHANGELOG.md | 6 +++ .../io/realm/DynamicRealmObjectTests.java | 45 +++++++++++++++++++ .../java/io/realm/DynamicRealmObject.java | 37 ++++++++++----- .../src/main/java/io/realm/internal/Row.java | 6 +++ 4 files changed, 82 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 559e9a4ee6..8c66da66e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.3.3 (YYYY-MM-DD) + +### Bug fixes + +* Element type checking in `DynamicRealmObject#setList()` (#4252). + ## 2.3.2 (2017-02-27) ### Bug fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index b6552f7105..ec865ceac6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -661,6 +661,51 @@ public void setList_listWithDynamicRealmObject() { dynamicRealm.close(); } + @Test + public void setList_managedRealmList() { + dynamicRealm.executeTransaction(new DynamicRealm.Transaction() { + @Override + public void execute(DynamicRealm realm) { + realm.deleteAll(); + + DynamicRealmObject allTypes = realm.createObject(AllTypes.CLASS_NAME); + allTypes.setString(AllTypes.FIELD_STRING, "bender"); + + DynamicRealmObject anotherAllTypes; + { + anotherAllTypes = realm.createObject(AllTypes.CLASS_NAME); + anotherAllTypes.setString(AllTypes.FIELD_STRING, "bender2"); + DynamicRealmObject dog = realm.createObject(Dog.CLASS_NAME); + dog.setString(Dog.FIELD_NAME, "nibbler"); + anotherAllTypes.getList(AllTypes.FIELD_REALMLIST).add(dog); + } + + // set managed RealmList + allTypes.setList(AllTypes.FIELD_REALMLIST, anotherAllTypes.getList(AllTypes.FIELD_REALMLIST)); + } + }); + + DynamicRealmObject allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) + .equalTo(AllTypes.FIELD_STRING, "bender") + .findFirst(); + assertEquals(1, allTypes.getList(AllTypes.FIELD_REALMLIST).size()); + assertEquals("nibbler", allTypes.getList(AllTypes.FIELD_REALMLIST).first().get(Dog.FIELD_NAME)); + + // Check if allTypes and anotherAllTypes share the same Dog object. + dynamicRealm.executeTransaction(new DynamicRealm.Transaction() { + @Override + public void execute(DynamicRealm realm) { + DynamicRealmObject anotherAllTypes = dynamicRealm.where(AllTypes.CLASS_NAME) + .equalTo(AllTypes.FIELD_STRING, "bender2") + .findFirst(); + anotherAllTypes.getList(AllTypes.FIELD_REALMLIST).first() + .setString(Dog.FIELD_NAME, "nibbler_modified"); + } + }); + + assertEquals("nibbler_modified", allTypes.getList(AllTypes.FIELD_REALMLIST).first().get(Dog.FIELD_NAME)); + } + @Test public void setList_elementBelongToTypedRealmThrows() { RealmList list = new RealmList(); diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 7b863b6c28..e987896ee5 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -637,26 +637,31 @@ public void setList(String fieldName, RealmList list) { throw new IllegalArgumentException("Null values not allowed for lists"); } - String tableName = proxyState.getRow$realm().getTable().getName(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + LinkView links = proxyState.getRow$realm().getLinkList(columnIndex); + Table linkTargetTable = links.getTargetTable(); + final String linkTargetTableName = Table.tableNameToClassName(linkTargetTable.getName()); + boolean typeValidated; if (list.className == null && list.clazz == null) { // Unmanaged lists don't know anything about the types they contain. They might even hold objects of // multiple types :(, so we have to check each item in the list. typeValidated = false; } else { - String listType = list.className != null ? list.className : proxyState.getRealm$realm().schema.getTable(list.clazz).getName(); - if (!tableName.equals(listType)) { - throw new IllegalArgumentException(String.format("The elements in the list is not the proper type. " + - "Was %s expected %s.", listType, tableName)); + String listType = list.className != null ? list.className + : Table.tableNameToClassName(proxyState.getRealm$realm().schema.getTable(list.clazz).getName()); + if (!linkTargetTableName.equals(listType)) { + throw new IllegalArgumentException(String.format(Locale.ENGLISH, + "The elements in the list are not the proper type. " + + "Was %s expected %s.", listType, linkTargetTableName)); } typeValidated = true; } - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - LinkView links = proxyState.getRow$realm().getLinkList(columnIndex); - links.clear(); - Table linkTargetTable = links.getTargetTable(); - for (int i = 0; i < list.size(); i++) { + final int listLength = list.size(); + final long[] indices = new long[listLength]; + + for (int i = 0; i < listLength; i++) { RealmObjectProxy obj = list.get(i); if (obj.realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { throw new IllegalArgumentException("Each element in 'list' must belong to the same Realm instance."); @@ -664,9 +669,17 @@ public void setList(String fieldName, RealmList list) { if (!typeValidated && !linkTargetTable.hasSameSchema(obj.realmGet$proxyState().getRow$realm().getTable())) { throw new IllegalArgumentException(String.format(Locale.ENGLISH, "Element at index %d is not the proper type. " + - "Was '%s' expected '%s'.", i, obj.realmGet$proxyState().getRow$realm().getTable().getName(), linkTargetTable.getName())); + "Was '%s' expected '%s'.", + i, + Table.tableNameToClassName(obj.realmGet$proxyState().getRow$realm().getTable().getName()), + linkTargetTableName)); } - links.add(obj.realmGet$proxyState().getRow$realm().getIndex()); + indices[i] = obj.realmGet$proxyState().getRow$realm().getIndex(); + } + + links.clear(); + for (int i = 0; i < listLength; i++) { + links.add(indices[i]); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index 023e923bff..5e9a5a3c90 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -22,6 +22,12 @@ /** * Interface for Row objects that act as wrappers around the Realm Core Row object. + *

          + * When the actual class which implements this interface is {@link CheckedRow}, all methods in this + * interface always validate their parameters and throw an appropriate exception if invalid. + * For example, methods which accept a column name check the existence of the column and throw + * {@link IllegalArgumentException} if not found. + * */ public interface Row { From 3b33640041b4a9901aec34900ef2b1eaa1855cf4 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 28 Feb 2017 20:17:08 +0900 Subject: [PATCH 0534/2110] Add thread check to methods in RealmQuery. (#4257) * Throw IllegalStateException instead of process crash when any of thread confined methods in RealmQuery is called from wrong thread (#4228). * fix some bugs in test and remove 'methodParams' * no need to add 'realm.checkIfValid();' to RealmQuery.isValid() * PR fixes * removed section header comments --- CHANGELOG.md | 3 + .../java/io/realm/RealmQueryTests.java | 287 +++++++++++++++++ .../src/main/java/io/realm/RealmQuery.java | 299 ++++++++++++++---- 3 files changed, 533 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c66da66e8..f7528289af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ### Bug fixes * Element type checking in `DynamicRealmObject#setList()` (#4252). +* Throws `IllegalStateException` instead of process crash when any of thread confined methods in `RealmQuery` is called from wrong thread (#4228). + ## 2.3.2 (2017-02-27) @@ -21,6 +23,7 @@ * Improved performance of getters and setters in proxy classes. + ## 2.3.1 ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index b1dbf6c3f4..86793cff0e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -26,6 +26,7 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -35,6 +36,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; @@ -147,6 +149,291 @@ private void populateNoPrimaryKeyNullTypesRows() { populateNoPrimaryKeyNullTypesRows(realm, TEST_NO_PRIMARY_KEY_NULL_TYPES_SIZE); } + private enum ThreadConfinedMethods { + EQUAL_TO_STRING, + EQUAL_TO_STRING_WITH_CASE, + EQUAL_TO_BYTE, + EQUAL_TO_BYTE_ARRAY, + EQUAL_TO_SHORT, + EQUAL_TO_INTEGER, + EQUAL_TO_LONG, + EQUAL_TO_DOUBLE, + EQUAL_TO_FLOAT, + EQUAL_TO_BOOLEAN, + EQUAL_TO_DATE, + + IN_STRING, + IN_STRING_WITH_CASE, + IN_BYTE, + IN_SHORT, + IN_INTEGER, + IN_LONG, + IN_DOUBLE, + IN_FLOAT, + IN_BOOLEAN, + IN_DATE, + + NOT_EQUAL_TO_STRING, + NOT_EQUAL_TO_STRING_WITH_CASE, + NOT_EQUAL_TO_BYTE, + NOT_EQUAL_TO_BYTE_ARRAY, + NOT_EQUAL_TO_SHORT, + NOT_EQUAL_TO_INTEGER, + NOT_EQUAL_TO_LONG, + NOT_EQUAL_TO_DOUBLE, + NOT_EQUAL_TO_FLOAT, + NOT_EQUAL_TO_BOOLEAN, + NOT_EQUAL_TO_DATE, + + GREATER_THAN_INTEGER, + GREATER_THAN_LONG, + GREATER_THAN_DOUBLE, + GREATER_THAN_FLOAT, + GREATER_THAN_DATE, + + GREATER_THAN_OR_EQUAL_TO_INTEGER, + GREATER_THAN_OR_EQUAL_TO_LONG, + GREATER_THAN_OR_EQUAL_TO_DOUBLE, + GREATER_THAN_OR_EQUAL_TO_FLOAT, + GREATER_THAN_OR_EQUAL_TO_DATE, + + LESS_THAN_INTEGER, + LESS_THAN_LONG, + LESS_THAN_DOUBLE, + LESS_THAN_FLOAT, + LESS_THAN_DATE, + + LESS_THAN_OR_EQUAL_TO_INTEGER, + LESS_THAN_OR_EQUAL_TO_LONG, + LESS_THAN_OR_EQUAL_TO_DOUBLE, + LESS_THAN_OR_EQUAL_TO_FLOAT, + LESS_THAN_OR_EQUAL_TO_DATE, + + BETWEEN_INTEGER, + BETWEEN_LONG, + BETWEEN_DOUBLE, + BETWEEN_FLOAT, + BETWEEN_DATE, + + CONTAINS_STRING, + CONTAINS_STRING_WITH_CASE, + + BEGINS_WITH_STRING, + BEGINS_WITH_STRING_WITH_CASE, + + ENDS_WITH_STRING, + ENDS_WITH_STRING_WITH_CASE, + + LIKE_STRING, + LIKE_STRING_WITH_CASE, + + BEGIN_GROUP, + END_GROUP, + OR, + NOT, + IS_NULL, + IS_NOT_NULL, + IS_EMPTY, + IS_NOT_EMPTY, + + IS_VALID, + DISTINCT, + DISTINCT_BY_MULTIPLE_FIELDS, + DISTINCT_ASYNC, + + SUM, + AVERAGE, + MIN, + MINIMUM_DATE, + MAX, + MAXIMUM_DATE, + COUNT, + + FIND_ALL, + FIND_ALL_ASYNC, + FIND_ALL_SORTED, + FIND_ALL_SORTED_ASYNC, + FIND_ALL_SORTED_WITH_ORDER, + FIND_ALL_SORTED_ASYNC_WITH_ORDER, + FIND_ALL_SORTED_WITH_TWO_ORDERS, + FIND_ALL_SORTED_ASYNC_WITH_TWO_ORDERS, + FIND_ALL_SORTED_WITH_MANY_ORDERS, + FIND_ALL_SORTED_ASYNC_WITH_MANY_ORDERS, + + FIND_FIRST, + FIND_FIRST_ASYNC, + } + + private static void callThreadConfinedMethod(RealmQuery query, ThreadConfinedMethods method) { + switch (method) { + case EQUAL_TO_STRING: query.equalTo( AllJavaTypes.FIELD_STRING, "dummy value"); break; + case EQUAL_TO_STRING_WITH_CASE: query.equalTo( AllJavaTypes.FIELD_STRING, "dummy value", Case.INSENSITIVE); break; + case EQUAL_TO_BYTE: query.equalTo( AllJavaTypes.FIELD_BYTE, (byte) 1); break; + case EQUAL_TO_BYTE_ARRAY: query.equalTo( AllJavaTypes.FIELD_BINARY, new byte[] {0, 1, 2}); break; + case EQUAL_TO_SHORT: query.equalTo( AllJavaTypes.FIELD_SHORT, (short) 1); break; + case EQUAL_TO_INTEGER: query.equalTo( AllJavaTypes.FIELD_INT, 1); break; + case EQUAL_TO_LONG: query.equalTo( AllJavaTypes.FIELD_LONG, 1L); break; + case EQUAL_TO_DOUBLE: query.equalTo( AllJavaTypes.FIELD_DOUBLE, 1D); break; + case EQUAL_TO_FLOAT: query.equalTo( AllJavaTypes.FIELD_FLOAT, 1F); break; + case EQUAL_TO_BOOLEAN: query.equalTo( AllJavaTypes.FIELD_BOOLEAN, true); break; + case EQUAL_TO_DATE: query.equalTo( AllJavaTypes.FIELD_DATE, new Date(0L)); break; + + case IN_STRING: query.in( AllJavaTypes.FIELD_STRING, new String[] {"dummy value1", "dummy value2"}); break; + case IN_STRING_WITH_CASE: query.in( AllJavaTypes.FIELD_STRING, new String[] {"dummy value1", "dummy value2"}, Case.INSENSITIVE); break; + case IN_BYTE: query.in( AllJavaTypes.FIELD_BYTE, new Byte[] {1, 2, 3}); break; + case IN_SHORT: query.in( AllJavaTypes.FIELD_SHORT, new Short[] {1, 2, 3}); break; + case IN_INTEGER: query.in( AllJavaTypes.FIELD_INT, new Integer[] {1, 2, 3}); break; + case IN_LONG: query.in( AllJavaTypes.FIELD_LONG, new Long[] {1L, 2L, 3L}); break; + case IN_DOUBLE: query.in( AllJavaTypes.FIELD_DOUBLE, new Double[] {1D, 2D, 3D}); break; + case IN_FLOAT: query.in( AllJavaTypes.FIELD_FLOAT, new Float[] {1F, 2F, 3F}); break; + case IN_BOOLEAN: query.in( AllJavaTypes.FIELD_BOOLEAN, new Boolean[] {true, false}); break; + case IN_DATE: query.in( AllJavaTypes.FIELD_DATE, new Date[] {new Date(0L)}); break; + + case NOT_EQUAL_TO_STRING: query.notEqualTo( AllJavaTypes.FIELD_STRING, "dummy value"); break; + case NOT_EQUAL_TO_STRING_WITH_CASE: query.notEqualTo( AllJavaTypes.FIELD_STRING, "dummy value", Case.INSENSITIVE); break; + case NOT_EQUAL_TO_BYTE: query.notEqualTo( AllJavaTypes.FIELD_BYTE, (byte) 1); break; + case NOT_EQUAL_TO_BYTE_ARRAY: query.notEqualTo( AllJavaTypes.FIELD_BINARY, new byte[] {1,2,3}); break; + case NOT_EQUAL_TO_SHORT: query.notEqualTo( AllJavaTypes.FIELD_SHORT, (short) 1); break; + case NOT_EQUAL_TO_INTEGER: query.notEqualTo( AllJavaTypes.FIELD_INT, 1); break; + case NOT_EQUAL_TO_LONG: query.notEqualTo( AllJavaTypes.FIELD_LONG, 1L); break; + case NOT_EQUAL_TO_DOUBLE: query.notEqualTo( AllJavaTypes.FIELD_DOUBLE, 1D); break; + case NOT_EQUAL_TO_FLOAT: query.notEqualTo( AllJavaTypes.FIELD_FLOAT, 1F); break; + case NOT_EQUAL_TO_BOOLEAN: query.notEqualTo( AllJavaTypes.FIELD_BOOLEAN, true); break; + case NOT_EQUAL_TO_DATE: query.notEqualTo( AllJavaTypes.FIELD_DATE, new Date(0L)); break; + + case GREATER_THAN_INTEGER: query.greaterThan( AllJavaTypes.FIELD_INT, 1); break; + case GREATER_THAN_LONG: query.greaterThan( AllJavaTypes.FIELD_LONG, 1L); break; + case GREATER_THAN_DOUBLE: query.greaterThan( AllJavaTypes.FIELD_DOUBLE, 1D); break; + case GREATER_THAN_FLOAT: query.greaterThan( AllJavaTypes.FIELD_FLOAT, 1F); break; + case GREATER_THAN_DATE: query.greaterThan( AllJavaTypes.FIELD_DATE, new Date(0L)); break; + + case GREATER_THAN_OR_EQUAL_TO_INTEGER: query.greaterThanOrEqualTo( AllJavaTypes.FIELD_INT, 1); break; + case GREATER_THAN_OR_EQUAL_TO_LONG: query.greaterThanOrEqualTo( AllJavaTypes.FIELD_LONG, 1L); break; + case GREATER_THAN_OR_EQUAL_TO_DOUBLE: query.greaterThanOrEqualTo( AllJavaTypes.FIELD_DOUBLE, 1D); break; + case GREATER_THAN_OR_EQUAL_TO_FLOAT: query.greaterThanOrEqualTo( AllJavaTypes.FIELD_FLOAT, 1F); break; + case GREATER_THAN_OR_EQUAL_TO_DATE: query.greaterThanOrEqualTo( AllJavaTypes.FIELD_DATE, new Date(0L)); break; + + case LESS_THAN_INTEGER: query.lessThan( AllJavaTypes.FIELD_INT, 1); break; + case LESS_THAN_LONG: query.lessThan( AllJavaTypes.FIELD_LONG, 1L); break; + case LESS_THAN_DOUBLE: query.lessThan( AllJavaTypes.FIELD_DOUBLE, 1D); break; + case LESS_THAN_FLOAT: query.lessThan( AllJavaTypes.FIELD_FLOAT, 1F); break; + case LESS_THAN_DATE: query.lessThan( AllJavaTypes.FIELD_DATE, new Date(0L)); break; + + case LESS_THAN_OR_EQUAL_TO_INTEGER: query.lessThanOrEqualTo( AllJavaTypes.FIELD_INT, 1); break; + case LESS_THAN_OR_EQUAL_TO_LONG: query.lessThanOrEqualTo( AllJavaTypes.FIELD_LONG, 1L); break; + case LESS_THAN_OR_EQUAL_TO_DOUBLE: query.lessThanOrEqualTo( AllJavaTypes.FIELD_DOUBLE, 1D); break; + case LESS_THAN_OR_EQUAL_TO_FLOAT: query.lessThanOrEqualTo( AllJavaTypes.FIELD_FLOAT, 1F); break; + case LESS_THAN_OR_EQUAL_TO_DATE: query.lessThanOrEqualTo( AllJavaTypes.FIELD_DATE, new Date(0L)); break; + + case BETWEEN_INTEGER: query.between( AllJavaTypes.FIELD_INT, 1, 100); break; + case BETWEEN_LONG: query.between( AllJavaTypes.FIELD_LONG, 1L, 100L); break; + case BETWEEN_DOUBLE: query.between( AllJavaTypes.FIELD_DOUBLE, 1D, 100D); break; + case BETWEEN_FLOAT: query.between( AllJavaTypes.FIELD_FLOAT, 1F, 100F); break; + case BETWEEN_DATE: query.between( AllJavaTypes.FIELD_DATE, new Date(0L), new Date(10000L)); break; + + case CONTAINS_STRING: query.contains( AllJavaTypes.FIELD_STRING, "dummy value"); break; + case CONTAINS_STRING_WITH_CASE: query.contains( AllJavaTypes.FIELD_STRING, "dummy value", Case.INSENSITIVE); break; + + case BEGINS_WITH_STRING: query.beginsWith( AllJavaTypes.FIELD_STRING, "dummy value"); break; + case BEGINS_WITH_STRING_WITH_CASE: query.beginsWith( AllJavaTypes.FIELD_STRING, "dummy value", Case.INSENSITIVE); break; + + case ENDS_WITH_STRING: query.endsWith( AllJavaTypes.FIELD_STRING, "dummy value"); break; + case ENDS_WITH_STRING_WITH_CASE: query.endsWith( AllJavaTypes.FIELD_STRING, "dummy value", Case.INSENSITIVE); break; + + case LIKE_STRING: query.like( AllJavaTypes.FIELD_STRING, "dummy value"); break; + case LIKE_STRING_WITH_CASE: query.like( AllJavaTypes.FIELD_STRING, "dummy value", Case.INSENSITIVE); break; + + case BEGIN_GROUP: query.beginGroup(); break; + case END_GROUP: query.endGroup(); break; + case OR: query.or(); break; + case NOT: query.not(); break; + case IS_NULL: query.isNull( AllJavaTypes.FIELD_DATE); break; + case IS_NOT_NULL: query.isNotNull( AllJavaTypes.FIELD_DATE); break; + case IS_EMPTY: query.isEmpty( AllJavaTypes.FIELD_STRING); break; + case IS_NOT_EMPTY: query.isNotEmpty( AllJavaTypes.FIELD_STRING); break; + + case IS_VALID: query.isValid(); break; + case DISTINCT: query.distinct( AllJavaTypes.FIELD_STRING); break; + case DISTINCT_BY_MULTIPLE_FIELDS: query.distinct( AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID); break; + case DISTINCT_ASYNC: query.distinctAsync( AllJavaTypes.FIELD_STRING); break; + + case SUM: query.sum( AllJavaTypes.FIELD_INT); break; + case AVERAGE: query.average( AllJavaTypes.FIELD_INT); break; + case MIN: query.min( AllJavaTypes.FIELD_INT); break; + case MINIMUM_DATE: query.minimumDate( AllJavaTypes.FIELD_INT); break; + case MAX: query.max( AllJavaTypes.FIELD_INT); break; + case MAXIMUM_DATE: query.maximumDate( AllJavaTypes.FIELD_INT); break; + case COUNT: query.count(); break; + + case FIND_ALL: query.findAll(); break; + case FIND_ALL_ASYNC: query.findAllAsync(); break; + case FIND_ALL_SORTED: query.findAllSorted( AllJavaTypes.FIELD_STRING); break; + case FIND_ALL_SORTED_ASYNC: query.findAllSortedAsync( AllJavaTypes.FIELD_STRING); break; + case FIND_ALL_SORTED_WITH_ORDER: query.findAllSorted( AllJavaTypes.FIELD_STRING, Sort.DESCENDING); break; + case FIND_ALL_SORTED_ASYNC_WITH_ORDER: query.findAllSortedAsync( AllJavaTypes.FIELD_STRING, Sort.DESCENDING); break; + case FIND_ALL_SORTED_WITH_TWO_ORDERS: query.findAllSorted( AllJavaTypes.FIELD_STRING, Sort.DESCENDING, AllJavaTypes.FIELD_ID, Sort.DESCENDING); break; + case FIND_ALL_SORTED_ASYNC_WITH_TWO_ORDERS: query.findAllSortedAsync( AllJavaTypes.FIELD_STRING, Sort.DESCENDING, AllJavaTypes.FIELD_ID, Sort.DESCENDING); break; + case FIND_ALL_SORTED_WITH_MANY_ORDERS: query.findAllSorted( new String[] {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID}, new Sort[] {Sort.DESCENDING, Sort.DESCENDING}); break; + case FIND_ALL_SORTED_ASYNC_WITH_MANY_ORDERS: query.findAllSortedAsync( new String[] {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID}, new Sort[] {Sort.DESCENDING, Sort.DESCENDING}); break; + + case FIND_FIRST: query.findFirst(); break; + case FIND_FIRST_ASYNC: query.findFirstAsync(); break; + + default: + throw new AssertionError("missing case for " + method); + } + } + + @Test + public void callThreadConfinedMethodsFromWrongThread() throws Throwable { + final RealmQuery query = realm.where(AllJavaTypes.class); + + final AtomicReference throwableFromThread = new AtomicReference(); + final CountDownLatch testFinished = new CountDownLatch(1); + + final String expectedMessage; + //noinspection TryWithIdenticalCatches + try { + final Field expectedMessageField = BaseRealm.class.getDeclaredField("INCORRECT_THREAD_MESSAGE"); + expectedMessageField.setAccessible(true); + expectedMessage = (String) expectedMessageField.get(null); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + + final Thread thread = new Thread("callThreadConfinedMethodsFromWrongThread") { + @Override + public void run() { + try { + for (ThreadConfinedMethods method : ThreadConfinedMethods.values()) { + try { + callThreadConfinedMethod(query, method); + fail("IllegalStateException must be thrown."); + } catch (Throwable e) { + if (e instanceof IllegalStateException && expectedMessage.equals(e.getMessage())) { + // expected exception + continue; + } + throwableFromThread.set(e); + return; + } + } + } finally { + testFinished.countDown(); + } + } + }; + thread.start(); + + TestHelper.awaitOrFail(testFinished); + final Throwable throwable = throwableFromThread.get(); + if (throwable != null) { + throw throwable; + } + } + @Test public void between() { final int TEST_OBJECTS_COUNT = 200; diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 29e6f09d4b..041e42e43b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -191,7 +191,7 @@ private RealmQuery(BaseRealm realm, LinkView linkView, String className) { * @return {@code true} if still valid to use, {@code false} otherwise. */ public boolean isValid() { - if (realm == null || realm.isClosed()) { + if (realm == null || realm.isClosed() /* this includes thread checking */) { return false; } @@ -214,6 +214,8 @@ public boolean isValid() { * @see Required for further infomation. */ public RealmQuery isNull(String fieldName) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName); // Checks that fieldName has the correct type is done in C++. @@ -230,6 +232,8 @@ public RealmQuery isNull(String fieldName) { * @see Required for further infomation. */ public RealmQuery isNotNull(String fieldName) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName); // Checks that fieldName has the correct type is done in C++. @@ -237,8 +241,6 @@ public RealmQuery isNotNull(String fieldName) { return this; } - // Equal - /** * Equal-to comparison. * @@ -261,6 +263,12 @@ public RealmQuery equalTo(String fieldName, String value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, String value, Case casing) { + realm.checkIfValid(); + + return equalToWithoutThreadValidation(fieldName, value, casing); + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, String value, Case casing) { long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.equalTo(columnIndices, value, casing); return this; @@ -275,6 +283,12 @@ public RealmQuery equalTo(String fieldName, String value, Case casing) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, Byte value) { + realm.checkIfValid(); + + return equalToWithoutThreadValidation(fieldName, value); + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, Byte value) { long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { this.query.isNull(columnIndices); @@ -293,6 +307,8 @@ public RealmQuery equalTo(String fieldName, Byte value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, byte[] value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.BINARY); if (value == null) { this.query.isNull(columnIndices); @@ -311,6 +327,12 @@ public RealmQuery equalTo(String fieldName, byte[] value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, Short value) { + realm.checkIfValid(); + + return equalToWithoutThreadValidation(fieldName, value); + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, Short value) { long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { this.query.isNull(columnIndices); @@ -329,6 +351,12 @@ public RealmQuery equalTo(String fieldName, Short value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, Integer value) { + realm.checkIfValid(); + + return equalToWithoutThreadValidation(fieldName, value); + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, Integer value) { long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { this.query.isNull(columnIndices); @@ -347,6 +375,12 @@ public RealmQuery equalTo(String fieldName, Integer value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, Long value) { + realm.checkIfValid(); + + return equalToWithoutThreadValidation(fieldName, value); + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, Long value) { long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { this.query.isNull(columnIndices); @@ -355,6 +389,7 @@ public RealmQuery equalTo(String fieldName, Long value) { } return this; } + /** * Equal-to comparison. * @@ -364,6 +399,12 @@ public RealmQuery equalTo(String fieldName, Long value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, Double value) { + realm.checkIfValid(); + + return equalToWithoutThreadValidation(fieldName, value); + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, Double value) { long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); if (value == null) { this.query.isNull(columnIndices); @@ -382,6 +423,12 @@ public RealmQuery equalTo(String fieldName, Double value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, Float value) { + realm.checkIfValid(); + + return equalToWithoutThreadValidation(fieldName, value); + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, Float value) { long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); if (value == null) { this.query.isNull(columnIndices); @@ -400,6 +447,12 @@ public RealmQuery equalTo(String fieldName, Float value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, Boolean value) { + realm.checkIfValid(); + + return equalToWithoutThreadValidation(fieldName, value); + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, Boolean value) { long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.BOOLEAN); if (value == null) { this.query.isNull(columnIndices); @@ -418,13 +471,17 @@ public RealmQuery equalTo(String fieldName, Boolean value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery equalTo(String fieldName, Date value) { + realm.checkIfValid(); + + return equalToWithoutThreadValidation(fieldName, value); + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, Date value) { long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.equalTo(columnIndices, value); return this; } - // In - /** * In comparison. This allows you to test if objects match any value in an array of values. * @@ -449,14 +506,16 @@ public RealmQuery in(String fieldName, String[] values) { * empty. */ public RealmQuery in(String fieldName, String[] values, Case casing) { + realm.checkIfValid(); + if (values == null || values.length == 0) { throw new IllegalArgumentException(EMPTY_VALUES); } - beginGroup().equalTo(fieldName, values[0], casing); + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0], casing); for (int i = 1; i < values.length; i++) { - or().equalTo(fieldName, values[i], casing); + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i], casing); } - return endGroup(); + return endGroupWithoutThreadValidation(); } /** @@ -469,14 +528,16 @@ public RealmQuery in(String fieldName, String[] values, Case casing) { * empty. */ public RealmQuery in(String fieldName, Byte[] values) { + realm.checkIfValid(); + if (values == null || values.length == 0) { throw new IllegalArgumentException(EMPTY_VALUES); } - beginGroup().equalTo(fieldName, values[0]); + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); for (int i = 1; i < values.length; i++) { - or().equalTo(fieldName, values[i]); + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); } - return endGroup(); + return endGroupWithoutThreadValidation(); } /** @@ -489,14 +550,16 @@ public RealmQuery in(String fieldName, Byte[] values) { * empty. */ public RealmQuery in(String fieldName, Short[] values) { + realm.checkIfValid(); + if (values == null || values.length == 0) { throw new IllegalArgumentException(EMPTY_VALUES); } - beginGroup().equalTo(fieldName, values[0]); + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); for (int i = 1; i < values.length; i++) { - or().equalTo(fieldName, values[i]); + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); } - return endGroup(); + return endGroupWithoutThreadValidation(); } /** @@ -509,14 +572,16 @@ public RealmQuery in(String fieldName, Short[] values) { * or empty. */ public RealmQuery in(String fieldName, Integer[] values) { + realm.checkIfValid(); + if (values == null || values.length == 0) { throw new IllegalArgumentException(EMPTY_VALUES); } - beginGroup().equalTo(fieldName, values[0]); + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); for (int i = 1; i < values.length; i++) { - or().equalTo(fieldName, values[i]); + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); } - return endGroup(); + return endGroupWithoutThreadValidation(); } /** @@ -529,14 +594,16 @@ public RealmQuery in(String fieldName, Integer[] values) { * empty. */ public RealmQuery in(String fieldName, Long[] values) { + realm.checkIfValid(); + if (values == null || values.length == 0) { throw new IllegalArgumentException(EMPTY_VALUES); } - beginGroup().equalTo(fieldName, values[0]); + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); for (int i = 1; i < values.length; i++) { - or().equalTo(fieldName, values[i]); + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); } - return endGroup(); + return endGroupWithoutThreadValidation(); } /** @@ -549,14 +616,16 @@ public RealmQuery in(String fieldName, Long[] values) { * empty. */ public RealmQuery in(String fieldName, Double[] values) { + realm.checkIfValid(); + if (values == null || values.length == 0) { throw new IllegalArgumentException(EMPTY_VALUES); } - beginGroup().equalTo(fieldName, values[0]); + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); for (int i = 1; i < values.length; i++) { - or().equalTo(fieldName, values[i]); + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); } - return endGroup(); + return endGroupWithoutThreadValidation(); } /** @@ -569,14 +638,16 @@ public RealmQuery in(String fieldName, Double[] values) { * empty. */ public RealmQuery in(String fieldName, Float[] values) { + realm.checkIfValid(); + if (values == null || values.length == 0) { throw new IllegalArgumentException(EMPTY_VALUES); } - beginGroup().equalTo(fieldName, values[0]); + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); for (int i = 1; i < values.length; i++) { - or().equalTo(fieldName, values[i]); + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); } - return endGroup(); + return endGroupWithoutThreadValidation(); } /** @@ -589,14 +660,16 @@ public RealmQuery in(String fieldName, Float[] values) { * or empty. */ public RealmQuery in(String fieldName, Boolean[] values) { + realm.checkIfValid(); + if (values == null || values.length == 0) { throw new IllegalArgumentException(EMPTY_VALUES); } - beginGroup().equalTo(fieldName, values[0]); + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); for (int i = 1; i < values.length; i++) { - or().equalTo(fieldName, values[i]); + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); } - return endGroup(); + return endGroupWithoutThreadValidation(); } /** @@ -609,18 +682,18 @@ public RealmQuery in(String fieldName, Boolean[] values) { * empty. */ public RealmQuery in(String fieldName, Date[] values) { + realm.checkIfValid(); + if (values == null || values.length == 0) { throw new IllegalArgumentException(EMPTY_VALUES); } - beginGroup().equalTo(fieldName, values[0]); + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); for (int i = 1; i < values.length; i++) { - or().equalTo(fieldName, values[i]); + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); } - return endGroup(); + return endGroupWithoutThreadValidation(); } - // Not Equal - /** * Not-equal-to comparison. * @@ -643,6 +716,8 @@ public RealmQuery notEqualTo(String fieldName, String value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, String value, Case casing) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); if (columnIndices.length > 1 && !casing.getValue()) { throw new IllegalArgumentException("Link queries cannot be case insensitive - coming soon."); @@ -660,6 +735,8 @@ public RealmQuery notEqualTo(String fieldName, String value, Case casing) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, Byte value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { this.query.isNotNull(columnIndices); @@ -678,6 +755,8 @@ public RealmQuery notEqualTo(String fieldName, Byte value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, byte[] value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.BINARY); if (value == null) { this.query.isNotNull(columnIndices); @@ -696,6 +775,8 @@ public RealmQuery notEqualTo(String fieldName, byte[] value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, Short value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { this.query.isNotNull(columnIndices); @@ -714,6 +795,8 @@ public RealmQuery notEqualTo(String fieldName, Short value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, Integer value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { this.query.isNotNull(columnIndices); @@ -732,6 +815,8 @@ public RealmQuery notEqualTo(String fieldName, Integer value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, Long value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { this.query.isNotNull(columnIndices); @@ -750,6 +835,8 @@ public RealmQuery notEqualTo(String fieldName, Long value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, Double value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); if (value == null) { this.query.isNotNull(columnIndices); @@ -768,6 +855,8 @@ public RealmQuery notEqualTo(String fieldName, Double value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, Float value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); if (value == null) { this.query.isNotNull(columnIndices); @@ -786,6 +875,8 @@ public RealmQuery notEqualTo(String fieldName, Float value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, Boolean value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.BOOLEAN); if (value == null) { this.query.isNotNull(columnIndices); @@ -804,6 +895,8 @@ public RealmQuery notEqualTo(String fieldName, Boolean value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery notEqualTo(String fieldName, Date value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); if (value == null) { this.query.isNotNull(columnIndices); @@ -813,8 +906,6 @@ public RealmQuery notEqualTo(String fieldName, Date value) { return this; } - // Greater Than - /** * Greater-than comparison. * @@ -824,6 +915,8 @@ public RealmQuery notEqualTo(String fieldName, Date value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery greaterThan(String fieldName, int value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.greaterThan(columnIndices, value); return this; @@ -838,6 +931,8 @@ public RealmQuery greaterThan(String fieldName, int value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery greaterThan(String fieldName, long value) { + realm.checkIfValid(); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.greaterThan(columnIndices, value); return this; @@ -852,6 +947,8 @@ public RealmQuery greaterThan(String fieldName, long value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery greaterThan(String fieldName, double value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.greaterThan(columnIndices, value); return this; @@ -866,6 +963,8 @@ public RealmQuery greaterThan(String fieldName, double value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery greaterThan(String fieldName, float value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.greaterThan(columnIndices, value); return this; @@ -880,6 +979,8 @@ public RealmQuery greaterThan(String fieldName, float value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery greaterThan(String fieldName, Date value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.greaterThan(columnIndices, value); return this; @@ -894,6 +995,8 @@ public RealmQuery greaterThan(String fieldName, Date value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery greaterThanOrEqualTo(String fieldName, int value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.greaterThanOrEqual(columnIndices, value); return this; @@ -908,6 +1011,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, int value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery greaterThanOrEqualTo(String fieldName, long value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.greaterThanOrEqual(columnIndices, value); return this; @@ -922,6 +1027,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, long value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery greaterThanOrEqualTo(String fieldName, double value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.greaterThanOrEqual(columnIndices, value); return this; @@ -936,6 +1043,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, double value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type */ public RealmQuery greaterThanOrEqualTo(String fieldName, float value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.greaterThanOrEqual(columnIndices, value); return this; @@ -950,13 +1059,13 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, float value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery greaterThanOrEqualTo(String fieldName, Date value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.greaterThanOrEqual(columnIndices, value); return this; } - // Less Than - /** * Less-than comparison. * @@ -966,6 +1075,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, Date value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThan(String fieldName, int value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.lessThan(columnIndices, value); return this; @@ -980,6 +1091,8 @@ public RealmQuery lessThan(String fieldName, int value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThan(String fieldName, long value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.lessThan(columnIndices, value); return this; @@ -994,6 +1107,8 @@ public RealmQuery lessThan(String fieldName, long value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThan(String fieldName, double value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.lessThan(columnIndices, value); return this; @@ -1008,6 +1123,8 @@ public RealmQuery lessThan(String fieldName, double value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThan(String fieldName, float value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.lessThan(columnIndices, value); return this; @@ -1022,6 +1139,8 @@ public RealmQuery lessThan(String fieldName, float value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThan(String fieldName, Date value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.lessThan(columnIndices, value); return this; @@ -1036,6 +1155,8 @@ public RealmQuery lessThan(String fieldName, Date value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThanOrEqualTo(String fieldName, int value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.lessThanOrEqual(columnIndices, value); return this; @@ -1050,6 +1171,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, int value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThanOrEqualTo(String fieldName, long value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.lessThanOrEqual(columnIndices, value); return this; @@ -1064,6 +1187,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, long value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThanOrEqualTo(String fieldName, double value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.lessThanOrEqual(columnIndices, value); return this; @@ -1078,6 +1203,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, double value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThanOrEqualTo(String fieldName, float value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.lessThanOrEqual(columnIndices, value); return this; @@ -1092,13 +1219,13 @@ public RealmQuery lessThanOrEqualTo(String fieldName, float value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery lessThanOrEqualTo(String fieldName, Date value) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.lessThanOrEqual(columnIndices, value); return this; } - // Between - /** * Between condition. * @@ -1109,6 +1236,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, Date value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery between(String fieldName, int from, int to) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.between(columnIndices, from, to); return this; @@ -1124,6 +1253,8 @@ public RealmQuery between(String fieldName, int from, int to) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery between(String fieldName, long from, long to) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.between(columnIndices, from, to); return this; @@ -1139,6 +1270,8 @@ public RealmQuery between(String fieldName, long from, long to) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery between(String fieldName, double from, double to) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.between(columnIndices, from, to); return this; @@ -1154,6 +1287,8 @@ public RealmQuery between(String fieldName, double from, double to) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery between(String fieldName, float from, float to) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.between(columnIndices, from, to); return this; @@ -1169,14 +1304,14 @@ public RealmQuery between(String fieldName, float from, float to) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery between(String fieldName, Date from, Date to) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.between(columnIndices, from, to); return this; } - // Contains - /** * Condition that value of field contains the specified substring. * @@ -1199,6 +1334,8 @@ public RealmQuery contains(String fieldName, String value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery contains(String fieldName, String value, Case casing) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.contains(columnIndices, value, casing); return this; @@ -1226,6 +1363,8 @@ public RealmQuery beginsWith(String fieldName, String value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery beginsWith(String fieldName, String value, Case casing) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.beginsWith(columnIndices, value, casing); return this; @@ -1253,13 +1392,13 @@ public RealmQuery endsWith(String fieldName, String value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery endsWith(String fieldName, String value, Case casing) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.endsWith(columnIndices, value, casing); return this; } - // Like - /** * Condition that the value of field matches with the specified substring, with wildcards: *

            @@ -1290,14 +1429,14 @@ public RealmQuery like(String fieldName, String value) { * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. */ public RealmQuery like(String fieldName, String value, Case casing) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.like(columnIndices, value, casing); return this; } - // Grouping - /** * Begin grouping of conditions ("left parenthesis"). A group must be closed with a call to {@code endGroup()}. * @@ -1305,6 +1444,12 @@ public RealmQuery like(String fieldName, String value, Case casing) { * @see #endGroup() */ public RealmQuery beginGroup() { + realm.checkIfValid(); + + return beginGroupWithoutThreadValidation(); + } + + private RealmQuery beginGroupWithoutThreadValidation() { this.query.group(); return this; } @@ -1316,6 +1461,12 @@ public RealmQuery beginGroup() { * @see #beginGroup() */ public RealmQuery endGroup() { + realm.checkIfValid(); + + return endGroupWithoutThreadValidation(); + } + + private RealmQuery endGroupWithoutThreadValidation() { this.query.endGroup(); return this; } @@ -1326,6 +1477,12 @@ public RealmQuery endGroup() { * @return the query object. */ public RealmQuery or() { + realm.checkIfValid(); + + return orWithoutThreadValidation(); + } + + private RealmQuery orWithoutThreadValidation() { this.query.or(); return this; } @@ -1336,6 +1493,8 @@ public RealmQuery or() { * @return the query object. */ public RealmQuery not() { + realm.checkIfValid(); + this.query.not(); return this; } @@ -1349,6 +1508,8 @@ public RealmQuery not() { * String or byte array. */ public RealmQuery isEmpty(String fieldName) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST); this.query.isEmpty(columnIndices); return this; @@ -1363,6 +1524,8 @@ public RealmQuery isEmpty(String fieldName) { * String or byte array. */ public RealmQuery isNotEmpty(String fieldName) { + realm.checkIfValid(); + long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST); this.query.isNotEmpty(columnIndices); return this; @@ -1379,6 +1542,8 @@ public RealmQuery isNotEmpty(String fieldName) { * is not indexed, or points to linked fields. */ public RealmResults distinct(String fieldName) { + realm.checkIfValid(); + checkQueryIsNotReused(); long columnIndex = getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); TableView tableView = this.query.findAll(); @@ -1407,6 +1572,8 @@ public RealmResults distinct(String fieldName) { * is not indexed, or points to linked fields. */ public RealmResults distinctAsync(String fieldName) { + realm.checkIfValid(); + checkQueryIsNotReused(); final long columnIndex = getAndValidateDistinctColumnIndex(fieldName, this.table.getTable()); final WeakReference weakNotifier = getWeakReferenceNotifier(); @@ -1511,6 +1678,8 @@ static long getAndValidateDistinctColumnIndex(String fieldName, Table table) { * is an unsupported type, or points to a linked field. */ public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { + realm.checkIfValid(); + checkQueryIsNotReused(); List columnIndexes = getValidatedColumIndexes(this.table.getTable(), firstFieldName, remainingFieldNames); TableView tableView = this.query.findAll(); @@ -1542,10 +1711,6 @@ static List getValidatedColumIndexes(Table table, String firstFieldName, S return columnIndexes; } - // Aggregates - - // Sum - /** * Calculates the sum of a given field. * @@ -1556,6 +1721,8 @@ static List getValidatedColumIndexes(Table table, String firstFieldName, S * @throws java.lang.IllegalArgumentException if the field is not a number type. */ public Number sum(String fieldName) { + realm.checkIfValid(); + long columnIndex = schema.getAndCheckFieldIndex(fieldName); switch (table.getColumnType(columnIndex)) { case INTEGER: @@ -1569,8 +1736,6 @@ public Number sum(String fieldName) { } } - // Average - /** * Returns the average of a given field. * @@ -1581,6 +1746,8 @@ public Number sum(String fieldName) { * @throws java.lang.IllegalArgumentException if the field is not a number type. */ public double average(String fieldName) { + realm.checkIfValid(); + long columnIndex = schema.getAndCheckFieldIndex(fieldName); switch (table.getColumnType(columnIndex)) { case INTEGER: @@ -1594,8 +1761,6 @@ public double average(String fieldName) { } } - // Min - /** * Finds the minimum value of a field. * @@ -1607,6 +1772,7 @@ public double average(String fieldName) { */ public Number min(String fieldName) { realm.checkIfValid(); + long columnIndex = schema.getAndCheckFieldIndex(fieldName); switch (table.getColumnType(columnIndex)) { case INTEGER: @@ -1630,12 +1796,12 @@ public Number min(String fieldName) { * @throws java.lang.UnsupportedOperationException if the query is not valid ("syntax error"). */ public Date minimumDate(String fieldName) { + realm.checkIfValid(); + long columnIndex = schema.getAndCheckFieldIndex(fieldName); return this.query.minimumDate(columnIndex); } - // Max - /** * Finds the maximum value of a field. * @@ -1647,6 +1813,7 @@ public Date minimumDate(String fieldName) { */ public Number max(String fieldName) { realm.checkIfValid(); + long columnIndex = schema.getAndCheckFieldIndex(fieldName); switch (table.getColumnType(columnIndex)) { case INTEGER: @@ -1670,6 +1837,8 @@ public Number max(String fieldName) { * @throws java.lang.UnsupportedOperationException if the query is not valid ("syntax error"). */ public Date maximumDate(String fieldName) { + realm.checkIfValid(); + long columnIndex = schema.getAndCheckFieldIndex(fieldName); return this.query.maximumDate(columnIndex); } @@ -1681,6 +1850,8 @@ public Date maximumDate(String fieldName) { * @throws java.lang.UnsupportedOperationException if the query is not valid ("syntax error"). */ public long count() { + realm.checkIfValid(); + return this.query.count(); } @@ -1693,6 +1864,8 @@ public long count() { */ @SuppressWarnings("unchecked") public RealmResults findAll() { + realm.checkIfValid(); + checkQueryIsNotReused(); RealmResults realmResults; if (isDynamicQuery()) { @@ -1712,6 +1885,8 @@ public RealmResults findAll() { * @see io.realm.RealmResults */ public RealmResults findAllAsync() { + realm.checkIfValid(); + checkQueryIsNotReused(); final WeakReference weakNotifier = getWeakReferenceNotifier(); @@ -1801,6 +1976,8 @@ public Long call() throws Exception { */ @SuppressWarnings("unchecked") public RealmResults findAllSorted(String fieldName, Sort sortOrder) { + realm.checkIfValid(); + checkQueryIsNotReused(); TableView tableView = query.findAll(); long columnIndex = getColumnIndexForSort(fieldName); @@ -1825,6 +2002,8 @@ public RealmResults findAllSorted(String fieldName, Sort sortOrder) { * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSortedAsync(final String fieldName, final Sort sortOrder) { + realm.checkIfValid(); + checkQueryIsNotReused(); long columnIndex = getColumnIndexForSort(fieldName); @@ -1945,6 +2124,8 @@ public RealmResults findAllSortedAsync(String fieldName) { */ @SuppressWarnings("unchecked") public RealmResults findAllSorted(String fieldNames[], Sort sortOrders[]) { + realm.checkIfValid(); + checkSortParameters(fieldNames, sortOrders); if (fieldNames.length == 1 && sortOrders.length == 1) { @@ -1986,6 +2167,8 @@ private boolean isDynamicQuery() { * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sortOrders) { + realm.checkIfValid(); + checkQueryIsNotReused(); checkSortParameters(fieldNames, sortOrders); @@ -2112,6 +2295,8 @@ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, * @see io.realm.RealmObject */ public E findFirst() { + realm.checkIfValid(); + checkQueryIsNotReused(); long tableRowIndex = getSourceRowIndexForFirstObject(); if (tableRowIndex >= 0) { @@ -2134,6 +2319,8 @@ public E findFirst() { * {@code false}. */ public E findFirstAsync() { + realm.checkIfValid(); + checkQueryIsNotReused(); final WeakReference weakNotifier = getWeakReferenceNotifier(); From 8c7ac32e6f14cda0a8a2fcd4607adca7594ff35e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 28 Feb 2017 19:27:45 +0800 Subject: [PATCH 0535/2110] Remove deprecated Logger class (#4050) --- CHANGELOG.md | 1 + .../unittesting/ExampleActivityTest.java | 1 - .../java/io/realm/RealmLogTests.java | 62 ------- .../main/java/io/realm/log/AndroidLogger.java | 153 ------------------ .../src/main/java/io/realm/log/Logger.java | 91 ----------- .../src/main/java/io/realm/log/RealmLog.java | 89 +--------- 6 files changed, 2 insertions(+), 395 deletions(-) delete mode 100644 realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java delete mode 100644 realm/realm-library/src/main/java/io/realm/log/Logger.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 536864a3d7..450d5ee302 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * `RealmResults` is auto-updated continuously. Any transaction on the current thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. The standard `RealmResults.iterator()` will continue to work as normal, which means that you can still delete or modify elements without impacting the iterator. The same is not true for simple for-loops. In some cases a simple for-loop will not work (https://realm.io/docs/java/3.0.0/api/io/realm/OrderedRealmCollection.html#loops), and you must use the new createSnapshot() method. * `RealmChangeListener` on `RealmObject` will now also be triggered when the object is deleted. Use `RealmObject.isValid()` to check this state(#3138). * `RealmObject.asObservable()` will now emit the object when it is deleted. Use `RealmObject.isValid()` to check this state (#3138). +* Removed deprecated classes `Logger` and `AndroidLogger` (#4050). ### Deprecated diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java index 97b51b752d..e478db6428 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java @@ -47,7 +47,6 @@ import io.realm.examples.unittesting.model.Person; import io.realm.internal.RealmCore; import io.realm.internal.Util; -import io.realm.log.Logger; import io.realm.log.RealmLog; import static org.hamcrest.CoreMatchers.is; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java index e91ae3ba16..c10ebbbb5b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java @@ -8,7 +8,6 @@ import org.junit.runner.RunWith; import io.realm.log.LogLevel; -import io.realm.log.Logger; import io.realm.log.RealmLog; import static junit.framework.Assert.assertEquals; @@ -92,65 +91,4 @@ public void throwable_passedToTheJavaLogger() { assertTrue(testLogger.message.contains("RealmLogTests.java")); RealmLog.remove(testLogger); } - - static class TestOldLogger implements Logger { - String message; - Throwable throwable; - - @Override - public int getMinimumNativeDebugLevel() { - return 0; - } - - @Override - public void trace(Throwable throwable, String message, Object... args) { - } - - @Override - public void debug(Throwable throwable, String message, Object... args) { - } - - @Override - public void info(Throwable throwable, String message, Object... args) { - } - - @Override - public void warn(Throwable throwable, String message, Object... args) { - } - - @Override - public void error(Throwable throwable, String message, Object... args) { - } - - @Override - public void fatal(Throwable throwable, String message, Object... args) { - this.throwable = throwable; - this.message = message; - } - } - - @Test - public void loggerAdaptor() { - TestOldLogger testLogger = new TestOldLogger(); - RealmLog.add(testLogger); - Throwable throwable; - - try { - throw new RuntimeException("Test exception."); - } catch (RuntimeException e) { - throwable = e; - RealmLog.fatal(e); - } - - // Throwable has been passed. - assertEquals(throwable, testLogger.throwable); - assertTrue(testLogger.message.contains("RealmLogTests.java")); - - RealmLog.remove(testLogger); - RealmLog.fatal("new string"); - - // Logger has been removed, nothing should be changed. - assertEquals(throwable, testLogger.throwable); - assertTrue(testLogger.message.contains("RealmLogTests.java")); - } } diff --git a/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java b/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java deleted file mode 100644 index 87a1a98094..0000000000 --- a/realm/realm-library/src/main/java/io/realm/log/AndroidLogger.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.log; - -import android.util.Log; - -import static android.util.Log.getStackTraceString; - -/** - * Logger implementation outputting to Android LogCat. - * Androids {@link Log}levels are mapped to Realm {@link LogLevel}s using the following table: - * - * - * - * - * - * - * - * - * - * - * - * - *
            {@link LogLevel#ALL}{@link Log#VERBOSE}{@link LogLevel#TRACE}{@link Log#VERBOSE}{@link LogLevel#DEBUG}{@link Log#DEBUG}{@link LogLevel#INFO}{@link Log#INFO}{@link LogLevel#WARN}{@link Log#WARN}{@link LogLevel#ERROR}{@link Log#ERROR}{@link LogLevel#FATAL}{@link Log#ERROR}{@link LogLevel#OFF}Not supported. Remove the logger instead.
            - * - * @deprecated The new {@link RealmLogger} for Android is implemented in native code. This class will be removed in a - * future release. - */ -public class AndroidLogger implements Logger { - - private static final int LOG_ENTRY_MAX_LENGTH = 4000; - private final int minimumLogLevel; - private volatile String logTag = "REALM"; - - /** - * Creates an logger that outputs to logcat. - * - * @param androidLogLevel Android log level - */ - public AndroidLogger(int androidLogLevel) { - if (androidLogLevel < Log.VERBOSE || androidLogLevel > Log.ASSERT) { - throw new IllegalArgumentException("Unknown android log level: " + androidLogLevel); - } - minimumLogLevel = androidLogLevel; - } - - /** - * Sets the logging tag used when outputting to LogCat. The default value is "REALM". - * - * @param tag Logging tag to use for all subsequent logging calls. - */ - public void setTag(String tag) { - logTag = tag; - } - - @Override - public int getMinimumNativeDebugLevel() { - // Maps Android log level to Realms log levels. - switch (minimumLogLevel) { - case Log.VERBOSE: return LogLevel.TRACE; - case Log.DEBUG: return LogLevel.DEBUG; - case Log.INFO: return LogLevel.INFO; - case Log.WARN: return LogLevel.WARN; - case Log.ERROR: return LogLevel.ERROR; - case Log.ASSERT: return LogLevel.FATAL; - default: - throw new IllegalStateException("Unknown log level: " + minimumLogLevel); - } - } - - // Inspired by https://github.com/JakeWharton/timber/blob/master/timber/src/main/java/timber/log/Timber.java - private void log(int androidLogLevel, Throwable t, String message, Object... args) { - if (androidLogLevel < minimumLogLevel) { - return; - } - if (message == null) { - if (t == null) { - return; // Ignores event if message is null and there's no throwable. - } - message = getStackTraceString(t); - } else { - if (args != null && args.length > 0) { - message = String.format(message, args); - } - if (t != null) { - message += "\n" + getStackTraceString(t); - } - } - - // Message fits one line. Just prints and exits. - if (message.length() < LOG_ENTRY_MAX_LENGTH) { - Log.println(androidLogLevel, logTag, message); - return; - } - - // Message does not fit one line. - // Splits by line, then ensures each line can fit into Log's maximum length. - for (int i = 0, length = message.length(); i < length; i++) { - int newline = message.indexOf('\n', i); - newline = newline != -1 ? newline : length; - do { - int end = Math.min(newline, i + LOG_ENTRY_MAX_LENGTH); - String part = message.substring(i, end); - Log.println(androidLogLevel, logTag, part); - i = end; - } while (i < newline); - } - } - - @Override - public void trace(Throwable throwable, String message, Object... args) { - log(Log.VERBOSE, throwable, message, args); - } - - @Override - public void debug(Throwable throwable, String message, Object... args) { - log(Log.DEBUG, throwable, message, args); - } - - @Override - public void info(Throwable throwable, String message, Object... args) { - log(Log.INFO, throwable, message, args); - } - - @Override - public void warn(Throwable throwable, String message, Object... args) { - log(Log.WARN, throwable, message, args); - } - - @Override - public void error(Throwable throwable, String message, Object... args) { - log(Log.ERROR, throwable, message, args); - } - - @Override - public void fatal(Throwable throwable, String message, Object... args) { - log(Log.ASSERT, throwable, message, args); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/log/Logger.java b/realm/realm-library/src/main/java/io/realm/log/Logger.java deleted file mode 100644 index 6e36fed33f..0000000000 --- a/realm/realm-library/src/main/java/io/realm/log/Logger.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.log; - -/** - * Interface for custom loggers that can be registered at {@link RealmLog#add(Logger)}. - * The different log levels are described in {@link LogLevel}. - * @deprecated Use {@link RealmLogger} instead. - */ -public interface Logger { - - /** - * Defines which {@link LogLevel} events this logger cares about from the native components. - *

            - * If multiple loggers are registered, the minimum value among all loggers is used. - *

            - * Note that sending log events from the native layer is relatively expensive, so only set this value to events - * that are truly useful. - * - * @return the minimum {@link LogLevel} native events this logger cares about. - */ - int getMinimumNativeDebugLevel(); - - /** - * Handles a {@link LogLevel#TRACE} event. - * - * @param throwable optional exception to log. - * @param message optional additional message. - * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. - */ - void trace(Throwable throwable, String message, Object... args); - - /** - * Handles a {@link LogLevel#DEBUG} event. - * - * @param throwable optional exception to log. - * @param message optional additional message. - * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. - */ - void debug(Throwable throwable, String message, Object... args); - - /** - * Handles an {@link LogLevel#INFO} event. - * - * @param throwable optional exception to log. - * @param message optional additional message. - * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. - */ - void info(Throwable throwable, String message, Object... args); - - /** - * Handles a {@link LogLevel#WARN} event. - * - * @param throwable optional exception to log. - * @param message optional additional message. - * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. - */ - void warn(Throwable throwable, String message, Object... args); - - /** - * Handles an {@link LogLevel#ERROR} event. - * - * @param throwable optional exception to log. - * @param message optional additional message. - * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. - */ - void error(Throwable throwable, String message, Object... args); - - /** - * Handles a {@link LogLevel#FATAL} event. - * - * @param throwable optional exception to log. - * @param message optional additional message. - * @param args optional arguments used to format the message using {@link String#format(String, Object...)}. - */ - void fatal(Throwable throwable, String message, Object... args); -} diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index 877e8d9e0e..c9218689db 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -18,9 +18,6 @@ import android.util.Log; -import java.util.IdentityHashMap; -import java.util.Map; - /** * Global logger used by all Realm components. * Custom loggers can be added by registering classes implementing {@link RealmLogger}. @@ -30,56 +27,6 @@ public final class RealmLog { @SuppressWarnings("FieldCanBeLocal") private static String REALM_JAVA_TAG = "REALM_JAVA"; - /** - * To convert the old {@link Logger} to the new {@link RealmLogger}. - */ - private static class LoggerAdapter implements RealmLogger { - private Logger logger; - private static final Map loggerMap = new IdentityHashMap(); - - LoggerAdapter(Logger logger) { - this.logger = logger; - if (loggerMap.containsKey(logger)) { - throw new IllegalStateException(String.format("Logger %s exists in the map!", logger.toString())); - } - loggerMap.put(logger, this); - } - - static RealmLogger removeLogger(Logger logger) { - return loggerMap.remove(logger); - } - - static void clear() { - loggerMap.clear(); - } - - @Override - public void log(int level, String tag, Throwable throwable, String message) { - switch (level) { - case LogLevel.TRACE: - logger.trace(throwable, message); - break; - case LogLevel.INFO: - logger.info(throwable, message); - break; - case LogLevel.DEBUG: - logger.debug(throwable, message); - break; - case LogLevel.WARN: - logger.warn(throwable, message); - break; - case LogLevel.ERROR: - logger.error(throwable, message); - break; - case LogLevel.FATAL: - logger.fatal(throwable, message); - break; - default: - throw new IllegalArgumentException("Level: " + level + " cannot be logged."); - } - } - } - /** * Adds a logger implementation that will be notified on log events. * @@ -92,18 +39,6 @@ public static void add(RealmLogger logger) { nativeAddLogger(logger); } - /** - * Adds a logger implementation that will be notified on log events. - * - * @param logger the reference to a {@link Logger} implementation. - * @deprecated use {@link #add(RealmLogger)} instead. - */ - public static void add(Logger logger) { - synchronized (LoggerAdapter.class) { - add(new LoggerAdapter(logger)); - } - } - /** * Sets the current {@link LogLevel}. Setting this will affect all registered loggers. * @@ -135,34 +70,12 @@ public static boolean remove(RealmLogger logger) { return true; } - /** - * Removes the given logger if it is currently added. - * - * @return {@code true} if the logger was removed, {@code false} otherwise. - * @deprecated use {@link #remove(RealmLogger)} instead. - */ - public static boolean remove(Logger logger) { - synchronized (LoggerAdapter.class) { - if (logger == null) { - throw new IllegalArgumentException("A non-null logger has to be provided"); - } - RealmLogger adaptor = LoggerAdapter.removeLogger(logger); - if (adaptor != null) { - nativeRemoveLogger(adaptor); - } - } - return true; - } - /** * Removes all loggers. The default native logger will be removed as well. Use {@link #registerDefaultLogger()} to * add it back. */ public static void clear() { - synchronized (LoggerAdapter.class) { - nativeClearLoggers(); - LoggerAdapter.clear(); - } + nativeClearLoggers(); } /** From 05c35dc7d5b292b771daf161124763690021705e Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Tue, 28 Feb 2017 13:53:24 +0100 Subject: [PATCH 0536/2110] Add a timeout of one hour to CI builds. (#4264) --- Jenkinsfile | 132 ++++++++++++++++++++++++++-------------------------- 1 file changed, 67 insertions(+), 65 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4356ad68aa..f8d9f2dccd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,44 +6,45 @@ def buildSuccess = false def rosContainer try { node('android') { - // Allocate a custom workspace to avoid having % in the path (it breaks ld) - ws('/tmp/realm-java') { - stage('SCM') { - checkout([ - $class: 'GitSCM', - branches: scm.branches, - gitTool: 'native git', - extensions: scm.extensions + [ - [$class: 'CleanCheckout'], - [$class: 'SubmoduleOption', recursiveSubmodules: true] - ], - userRemoteConfigs: scm.userRemoteConfigs - ]) - } - - def buildEnv - def rosEnv - stage('Docker build') { - // Docker image for build - buildEnv = docker.build 'realm-java:snapshot' - // Docker image for testing Realm Object Server - def dependProperties = readProperties file: 'dependencies.list' - def rosDeVersion = dependProperties["REALM_OBJECT_SERVER_DE_VERSION"] - rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_DE_VERSION=${rosDeVersion} tools/sync_test_server" - } - - rosContainer = rosEnv.run('-v /tmp=/tmp/.ros') - - try { + timeout(time: 1, unit: 'HOURS') { + // Allocate a custom workspace to avoid having % in the path (it breaks ld) + ws('/tmp/realm-java') { + stage('SCM') { + checkout([ + $class: 'GitSCM', + branches: scm.branches, + gitTool: 'native git', + extensions: scm.extensions + [ + [$class: 'CleanCheckout'], + [$class: 'SubmoduleOption', recursiveSubmodules: true] + ], + userRemoteConfigs: scm.userRemoteConfigs + ]) + } + + def buildEnv + def rosEnv + stage('Docker build') { + // Docker image for build + buildEnv = docker.build 'realm-java:snapshot' + // Docker image for testing Realm Object Server + def dependProperties = readProperties file: 'dependencies.list' + def rosDeVersion = dependProperties["REALM_OBJECT_SERVER_DE_VERSION"] + rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_DE_VERSION=${rosDeVersion} tools/sync_test_server" + } + + rosContainer = rosEnv.run('-v /tmp=/tmp/.ros') + + try { buildEnv.inside("-e HOME=/tmp " + - "-e _JAVA_OPTIONS=-Duser.home=/tmp " + - "--privileged " + - "-v /dev/bus/usb:/dev/bus/usb " + - "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + - "-v ${env.HOME}/.android:/tmp/.android " + - "-v ${env.HOME}/ccache:/tmp/.ccache " + - "-v ${env.HOME}/lcache:/tmp/.lcache " + - "--network container:${rosContainer.id}") { + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + + "--privileged " + + "-v /dev/bus/usb:/dev/bus/usb " + + "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + + "-v ${env.HOME}/.android:/tmp/.android " + + "-v ${env.HOME}/ccache:/tmp/.ccache " + + "-v ${env.HOME}/lcache:/tmp/.lcache " + + "--network container:${rosContainer.id}") { stage('JVM tests') { try { withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { @@ -63,12 +64,12 @@ try { publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) step([$class: 'CheckStylePublisher', - canComputeNew: false, - defaultEncoding: '', - healthy: '', - pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - unHealthy: '' - ]) + canComputeNew: false, + defaultEncoding: '', + healthy: '', + pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', + unHealthy: '' + ]) } } @@ -102,14 +103,15 @@ try { } } } - } finally { + } finally { sh "docker logs ${rosContainer.id}" rosContainer.stop() + } } } + currentBuild.rawBuild.setResult(Result.SUCCESS) + buildSuccess = true } - currentBuild.rawBuild.setResult(Result.SUCCESS) - buildSuccess = true } catch(Exception e) { currentBuild.rawBuild.setResult(Result.FAILURE) buildSuccess = false @@ -119,14 +121,14 @@ try { node { withCredentials([[$class: 'StringBinding', credentialsId: 'slack-java-url', variable: 'SLACK_URL']]) { def payload = JsonOutput.toJson([ - username: 'Mr. Jenkins', - icon_emoji: ':jenkins:', - attachments: [[ - 'title': "The ${env.BRANCH_NAME} branch is broken!", - 'text': "<${env.BUILD_URL}|Click here> to check the build.", - 'color': "danger" - ]] - ]) + username: 'Mr. Jenkins', + icon_emoji: ':jenkins:', + attachments: [[ + 'title': "The ${env.BRANCH_NAME} branch is broken!", + 'text': "<${env.BUILD_URL}|Click here> to check the build.", + 'color': "danger" + ]] + ]) sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" } } @@ -151,10 +153,10 @@ def stopLogCatCollector(String backgroundPid, boolean archiveLog) { sh "kill ${backgroundPid}" if (archiveLog) { zip([ - 'zipFile': 'logcat.zip', - 'archive': true, - 'glob' : 'logcat.txt' - ]) + 'zipFile': 'logcat.zip', + 'archive': true, + 'glob' : 'logcat.txt' + ]) } sh 'rm logcat.txt' } @@ -173,9 +175,9 @@ def getTagsString(Map tags) { def storeJunitResults(String path) { step([ - $class: 'JUnitResultArchiver', - testResults: path - ]) + $class: 'JUnitResultArchiver', + testResults: path + ]) } def collectAarMetrics() { @@ -198,10 +200,10 @@ def collectAarMetrics() { def soFiles = findFiles(glob: "realm/realm-library/build/outputs/aar/unzipped${flavor}/jni/*/librealm-jni.so") for (def j = 0; j < soFiles.size(); j++) { - def soFile = soFiles[j] - def abiName = soFile.path.tokenize('/')[-2] - def libSize = soFile.length as String - sendMetrics('abi_size', libSize, ['flavor':flavor, 'type':abiName]) + def soFile = soFiles[j] + def abiName = soFile.path.tokenize('/')[-2] + def libSize = soFile.length as String + sendMetrics('abi_size', libSize, ['flavor':flavor, 'type':abiName]) } } } From b10f185dfbfb4cec950d9af5732c7d497655b3cb Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 28 Feb 2017 21:58:22 +0900 Subject: [PATCH 0537/2110] Add thread check to methods in DynamicRealmObject (#4259) * add a simple test case to expose #4258 * update Changelog * fix issue4258 * fix test failure * fix test process crash when RealmListTests#add_set_dynamicObjectFromOtherThread() fails --- CHANGELOG.md | 3 +- .../io/realm/DynamicRealmObjectTests.java | 119 ++++++++++++++++++ .../java/io/realm/RealmListTests.java | 66 ++++++---- .../java/io/realm/DynamicRealmObject.java | 67 +++++++++- .../src/main/java/io/realm/RealmList.java | 2 +- 5 files changed, 227 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7528289af..3f8823478e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ### Bug fixes * Element type checking in `DynamicRealmObject#setList()` (#4252). -* Throws `IllegalStateException` instead of process crash when any of thread confined methods in `RealmQuery` is called from wrong thread (#4228). +* Now throws `IllegalStateException` instead of process crash when any of thread confined methods in `RealmQuery` is called from wrong thread (#4228). +* Now throws `IllegalStateException` when any of thread confined methods in `DynamicRealmObject` is called from wrong thread (#4258). ## 2.3.2 (2017-02-27) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index ec865ceac6..c9449f5a93 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -25,11 +25,13 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.lang.reflect.Field; import java.text.ParseException; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; @@ -112,6 +114,123 @@ private enum SupportedType { BOOLEAN, SHORT, INT, LONG, BYTE, FLOAT, DOUBLE, STRING, BINARY, DATE, OBJECT, LIST } + private enum ThreadConfinedMethods { + GET_BOOLEAN, GET_BYTE, GET_SHORT, GET_INT, GET_LONG, GET_FLOAT, GET_DOUBLE, + GET_BLOB, GET_STRING, GET_DATE, GET_OBJECT, GET_LIST, GET, + + SET_BOOLEAN, SET_BYTE, SET_SHORT, SET_INT, SET_LONG, SET_FLOAT, SET_DOUBLE, + SET_BLOB, SET_STRING, SET_DATE, SET_OBJECT, SET_LIST, SET, + + IS_NULL, SET_NULL, + + HAS_FIELD, GET_FIELD_NAMES, GET_TYPE, GET_FIELD_TYPE, + + HASH_CODE, EQUALS, TO_STRING, + } + + @SuppressWarnings({"ResultOfMethodCallIgnored", "EqualsWithItself"}) + private static void callThreadConfinedMethod(DynamicRealmObject obj, ThreadConfinedMethods method) { + switch (method) { + case GET_BOOLEAN: obj.getBoolean(AllJavaTypes.FIELD_BOOLEAN); break; + case GET_BYTE: obj.getByte(AllJavaTypes.FIELD_BYTE); break; + case GET_SHORT: obj.getShort(AllJavaTypes.FIELD_SHORT); break; + case GET_INT: obj.getInt(AllJavaTypes.FIELD_INT); break; + case GET_LONG: obj.getLong(AllJavaTypes.FIELD_LONG); break; + case GET_FLOAT: obj.getFloat(AllJavaTypes.FIELD_FLOAT); break; + case GET_DOUBLE: obj.getDouble(AllJavaTypes.FIELD_DOUBLE); break; + case GET_BLOB: obj.getBlob(AllJavaTypes.FIELD_BINARY); break; + case GET_STRING: obj.getString(AllJavaTypes.FIELD_STRING); break; + case GET_DATE: obj.getDate(AllJavaTypes.FIELD_DATE); break; + case GET_OBJECT: obj.getObject(AllJavaTypes.FIELD_OBJECT); break; + case GET_LIST: obj.getList(AllJavaTypes.FIELD_LIST); break; + case GET: obj.get(AllJavaTypes.FIELD_LONG); break; + + case SET_BOOLEAN: obj.setBoolean(AllJavaTypes.FIELD_BOOLEAN, true); break; + case SET_BYTE: obj.setByte(AllJavaTypes.FIELD_BYTE, (byte) 1); break; + case SET_SHORT: obj.setShort(AllJavaTypes.FIELD_SHORT, (short) 1); break; + case SET_INT: obj.setInt(AllJavaTypes.FIELD_INT, 1); break; + case SET_LONG: obj.setLong(AllJavaTypes.FIELD_LONG, 1L); break; + case SET_FLOAT: obj.setFloat(AllJavaTypes.FIELD_FLOAT, 1F); break; + case SET_DOUBLE: obj.setDouble(AllJavaTypes.FIELD_DOUBLE, 1D); break; + case SET_BLOB: obj.setBlob(AllJavaTypes.FIELD_BINARY, new byte[] {1, 2, 3}); break; + case SET_STRING: obj.setString(AllJavaTypes.FIELD_STRING, "12345"); break; + case SET_DATE: obj.setDate(AllJavaTypes.FIELD_DATE, new Date(1L)); break; + case SET_OBJECT: obj.setObject(AllJavaTypes.FIELD_OBJECT, obj); break; + case SET_LIST: obj.setList(AllJavaTypes.FIELD_LIST, new RealmList<>(obj)); break; + case SET: obj.set(AllJavaTypes.FIELD_LONG, 1L); break; + + case IS_NULL: obj.isNull(AllJavaTypes.FIELD_OBJECT); break; + case SET_NULL: obj.setNull(AllJavaTypes.FIELD_OBJECT); break; + + case HAS_FIELD: obj.hasField(AllJavaTypes.FIELD_OBJECT); break; + case GET_FIELD_NAMES: obj.getFieldNames(); break; + case GET_TYPE: obj.getType(); break; + case GET_FIELD_TYPE: obj.getFieldType(AllJavaTypes.FIELD_OBJECT); break; + + case HASH_CODE: obj.hashCode(); break; + case EQUALS: obj.equals(obj); break; + case TO_STRING: obj.toString(); break; + + default: + throw new AssertionError("missing case for " + method); + } + } + + @Test + public void callThreadConfinedMethodsFromWrongThread() throws Throwable { + + dynamicRealm.beginTransaction(); + dynamicRealm.deleteAll(); + final DynamicRealmObject obj = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 100L); + dynamicRealm.commitTransaction(); + + final AtomicReference throwableFromThread = new AtomicReference(); + final CountDownLatch testFinished = new CountDownLatch(1); + + final String expectedMessage; + //noinspection TryWithIdenticalCatches + try { + final Field expectedMessageField = BaseRealm.class.getDeclaredField("INCORRECT_THREAD_MESSAGE"); + expectedMessageField.setAccessible(true); + expectedMessage = (String) expectedMessageField.get(null); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + + final Thread thread = new Thread("callThreadConfinedMethodsFromWrongThread") { + @Override + public void run() { + try { + for (ThreadConfinedMethods method : ThreadConfinedMethods.values()) { + try { + callThreadConfinedMethod(obj, method); + fail("IllegalStateException must be thrown."); + } catch (Throwable e) { + if (e instanceof IllegalStateException && expectedMessage.equals(e.getMessage())) { + // expected exception + continue; + } + throwableFromThread.set(e); + return; + } + } + } finally { + testFinished.countDown(); + } + } + }; + thread.start(); + + TestHelper.awaitOrFail(testFinished); + final Throwable throwable = throwableFromThread.get(); + if (throwable != null) { + throw throwable; + } + } + + @Test (expected = IllegalArgumentException.class) public void constructor_nullThrows () { new DynamicRealmObject((RealmObject)null); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 5106b86cb8..84ad6f3db6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllTypes; import io.realm.entities.Cat; @@ -854,49 +855,60 @@ public void run() { } @Test - public void add_set_dynamicObjectFromOtherThread() { + public void add_set_dynamicObjectFromOtherThread() throws Throwable { final CountDownLatch finishedLatch = new CountDownLatch(1); DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); final DynamicRealmObject dynDog = dynamicRealm.where(Dog.CLASS_NAME).findFirst(); final String expectedMsg = "Cannot copy an object to a Realm instance created in another thread."; + final AtomicReference thrownErrorRef = new AtomicReference(); + new Thread(new Runnable() { @Override public void run() { DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); dynamicRealm.beginTransaction(); - RealmList list = dynamicRealm.createObject(Owner.CLASS_NAME) - .getList(Owner.FIELD_DOGS); - list.add(dynamicRealm.createObject(Dog.CLASS_NAME)); - - try { - list.add(dynDog); - fail(); - } catch (IllegalStateException expected) { - assertEquals(expectedMsg, expected.getMessage()); - } - - try { - list.add(0, dynDog); - fail(); - } catch (IllegalStateException expected) { - assertEquals(expectedMsg, expected.getMessage()); - } - try { - list.set(0, dynDog); - fail(); - } catch (IllegalStateException expected) { - assertEquals(expectedMsg, expected.getMessage()); + RealmList list = dynamicRealm.createObject(Owner.CLASS_NAME) + .getList(Owner.FIELD_DOGS); + list.add(dynamicRealm.createObject(Dog.CLASS_NAME)); + + try { + list.add(dynDog); + fail(); + } catch (IllegalStateException expected) { + assertEquals(expectedMsg, expected.getMessage()); + } + + try { + list.add(0, dynDog); + fail(); + } catch (IllegalStateException expected) { + assertEquals(expectedMsg, expected.getMessage()); + } + + try { + list.set(0, dynDog); + fail(); + } catch (IllegalStateException expected) { + assertEquals(expectedMsg, expected.getMessage()); + } + } catch (Throwable throwable) { + thrownErrorRef.set(throwable); + } finally { + dynamicRealm.cancelTransaction(); + dynamicRealm.close(); + finishedLatch.countDown(); } - - dynamicRealm.cancelTransaction(); - dynamicRealm.close(); - finishedLatch.countDown(); } }).start(); TestHelper.awaitOrFail(finishedLatch); dynamicRealm.close(); + + final Throwable thrown = thrownErrorRef.get(); + if (thrown != null) { + throw thrown; + } } @Test diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index e987896ee5..d24e5a6e4c 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -92,6 +92,8 @@ public DynamicRealmObject(RealmModel obj) { */ @SuppressWarnings("unchecked") public E get(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); switch (type) { @@ -123,6 +125,8 @@ public E get(String fieldName) { * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public boolean getBoolean(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getBoolean(columnIndex); @@ -174,6 +178,8 @@ public short getShort(String fieldName) { * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public long getLong(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getLong(columnIndex); @@ -210,6 +216,8 @@ public byte getByte(String fieldName) { * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public float getFloat(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getFloat(columnIndex); @@ -231,6 +239,8 @@ public float getFloat(String fieldName) { * @throws io.realm.exceptions.RealmException if the return value would be {@code null}. */ public double getDouble(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getDouble(columnIndex); @@ -248,6 +258,8 @@ public double getDouble(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain binary data. */ public byte[] getBlob(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getBinaryByteArray(columnIndex); @@ -265,6 +277,8 @@ public byte[] getBlob(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain Strings. */ public String getString(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { return proxyState.getRow$realm().getString(columnIndex); @@ -282,6 +296,8 @@ public String getString(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain Dates. */ public Date getDate(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); checkFieldType(fieldName, columnIndex, RealmFieldType.DATE); if (proxyState.getRow$realm().isNull(columnIndex)) { @@ -299,6 +315,8 @@ public Date getDate(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain links to other objects. */ public DynamicRealmObject getObject(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); checkFieldType(fieldName, columnIndex, RealmFieldType.OBJECT); if (proxyState.getRow$realm().isNullLink(columnIndex)) { @@ -318,6 +336,8 @@ public DynamicRealmObject getObject(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain a list of links. */ public RealmList getList(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { LinkView linkView = proxyState.getRow$realm().getLinkList(columnIndex); @@ -337,6 +357,8 @@ public RealmList getList(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist. */ public boolean isNull(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); switch (type) { @@ -365,6 +387,8 @@ public boolean isNull(String fieldName) { * @return {@code true} if the object has a field with the given name, {@code false} otherwise. */ public boolean hasField(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + //noinspection SimplifiableIfStatement if (fieldName == null || fieldName.isEmpty()) { return false; @@ -378,6 +402,8 @@ public boolean hasField(String fieldName) { * @return list of field names on this objects or the empty list if the object doesn't have any fields. */ public String[] getFieldNames() { + proxyState.getRealm$realm().checkIfValid(); + String[] keys = new String[(int) proxyState.getRow$realm().getColumnCount()]; for (int i = 0; i < keys.length; i++) { keys[i] = proxyState.getRow$realm().getColumnName(i); @@ -399,6 +425,8 @@ public String[] getFieldNames() { */ @SuppressWarnings("unchecked") public void set(String fieldName, Object value) { + proxyState.getRealm$realm().checkIfValid(); + boolean isString = (value instanceof String); String strValue = isString ? (String) value : null; @@ -468,6 +496,8 @@ private void setValue(String fieldName, Object value) { * @throws IllegalArgumentException if field name doesn't exist or field isn't a boolean field. */ public void setBoolean(String fieldName, boolean value) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setBoolean(columnIndex, value); } @@ -481,6 +511,8 @@ public void setBoolean(String fieldName, boolean value) { * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setShort(String fieldName, short value) { + proxyState.getRealm$realm().checkIfValid(); + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); @@ -495,6 +527,8 @@ public void setShort(String fieldName, short value) { * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setInt(String fieldName, int value) { + proxyState.getRealm$realm().checkIfValid(); + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); @@ -509,6 +543,8 @@ public void setInt(String fieldName, int value) { * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setLong(String fieldName, long value) { + proxyState.getRealm$realm().checkIfValid(); + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); @@ -523,6 +559,8 @@ public void setLong(String fieldName, long value) { * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setByte(String fieldName, byte value) { + proxyState.getRealm$realm().checkIfValid(); + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setLong(columnIndex, value); @@ -536,6 +574,8 @@ public void setByte(String fieldName, byte value) { * @throws IllegalArgumentException if field name doesn't exist or field isn't a float field. */ public void setFloat(String fieldName, float value) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setFloat(columnIndex, value); } @@ -548,6 +588,8 @@ public void setFloat(String fieldName, float value) { * @throws IllegalArgumentException if field name doesn't exist or field isn't a double field. */ public void setDouble(String fieldName, double value) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setDouble(columnIndex, value); } @@ -561,6 +603,8 @@ public void setDouble(String fieldName, double value) { * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setString(String fieldName, String value) { + proxyState.getRealm$realm().checkIfValid(); + checkIsPrimaryKey(fieldName); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setString(columnIndex, value); @@ -574,6 +618,8 @@ public void setString(String fieldName, String value) { * @throws IllegalArgumentException if field name doesn't exist or field isn't a binary field. */ public void setBlob(String fieldName, byte[] value) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); proxyState.getRow$realm().setBinaryByteArray(columnIndex, value); } @@ -586,6 +632,8 @@ public void setBlob(String fieldName, byte[] value) { * @throws IllegalArgumentException if field name doesn't exist or field isn't a Date field. */ public void setDate(String fieldName, Date value) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); if (value == null) { proxyState.getRow$realm().setNull(columnIndex); @@ -603,6 +651,8 @@ public void setDate(String fieldName, Date value) { * of DynamicRealmObject doesn't match or it belongs to a different Realm. */ public void setObject(String fieldName, DynamicRealmObject value) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); if (value == null) { proxyState.getRow$realm().nullifyLink(columnIndex); @@ -633,6 +683,8 @@ public void setObject(String fieldName, DynamicRealmObject value) { * different Realm. */ public void setList(String fieldName, RealmList list) { + proxyState.getRealm$realm().checkIfValid(); + if (list == null) { throw new IllegalArgumentException("Null values not allowed for lists"); } @@ -691,6 +743,8 @@ public void setList(String fieldName, RealmList list) { * @throws RealmException if the field is a {@link io.realm.annotations.PrimaryKey} field. */ public void setNull(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); if (type == RealmFieldType.OBJECT) { @@ -708,6 +762,8 @@ public void setNull(String fieldName) { * @return this objects type. */ public String getType() { + proxyState.getRealm$realm().checkIfValid(); + return RealmSchema.getSchemaForTable(proxyState.getRow$realm().getTable()); } @@ -717,6 +773,8 @@ public String getType() { * @return the underlying type used by Realm to represent this field. */ public RealmFieldType getFieldType(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); return proxyState.getRow$realm().getColumnType(columnIndex); } @@ -752,6 +810,8 @@ private void checkFieldType(String fieldName, long columnIndex, RealmFieldType e */ @Override public int hashCode() { + proxyState.getRealm$realm().checkIfValid(); + String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); long rowIndex = proxyState.getRow$realm().getIndex(); @@ -765,12 +825,15 @@ public int hashCode() { @Override public boolean equals(Object o) { + proxyState.getRealm$realm().checkIfValid(); + if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } + DynamicRealmObject other = (DynamicRealmObject) o; String path = proxyState.getRealm$realm().getPath(); @@ -791,7 +854,9 @@ public boolean equals(Object o) { @Override public String toString() { - if (proxyState.getRealm$realm() == null || !proxyState.getRow$realm().isAttached()) { + proxyState.getRealm$realm().checkIfValid(); + + if (!proxyState.getRow$realm().isAttached()) { return "Invalid object"; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index d909155433..6d57c35041 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -244,8 +244,8 @@ private E copyToRealmIfNeeded(E object) { if (proxy instanceof DynamicRealmObject) { String listClassName = RealmSchema.getSchemaForTable(view.getTargetTable()); - String objectClassName = ((DynamicRealmObject) object).getType(); if (proxy.realmGet$proxyState().getRealm$realm() == realm) { + String objectClassName = ((DynamicRealmObject) object).getType(); if (listClassName.equals(objectClassName)) { // Same Realm instance and same target table return object; From e5f383ae781bb01a23d05259196fcb56b9d611d8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 28 Feb 2017 14:54:27 +0100 Subject: [PATCH 0538/2110] Updated release date in changelog --- CHANGELOG.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14908c09ce..a5192893bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ -## 3.0.0 (YYYY-MM-DD) +## 3.0.0 (2017-02-28) -### Breaking changes +### Breaking Changes * `RealmResults.distinct()` returns a new `RealmResults` object instead of filtering on the original object (#2947). * `RealmResults` is auto-updated continuously. Any transaction on the current thread which may have an impact on the order or elements of the `RealmResults` will change the `RealmResults` immediately instead of change it in the next event loop. The standard `RealmResults.iterator()` will continue to work as normal, which means that you can still delete or modify elements without impacting the iterator. The same is not true for simple for-loops. In some cases a simple for-loop will not work (https://realm.io/docs/java/3.0.0/api/io/realm/OrderedRealmCollection.html#loops), and you must use the new createSnapshot() method. @@ -18,9 +18,16 @@ * Added support for sorting by link's field (#672). * Added `OrderedRealmCollectionSnapshot` class and `OrderedRealmCollection.createSnapshot()` method. `OrderedRealmCollectionSnapshot` is useful when changing `RealmResults` or `RealmList` in simple loops. +* Added `OrderedRealmCollectionChangeListener` interface for supporting fine-grained collection notifications. * Added support for ChangeListeners on `RealmList`. * Added `RealmList.asObservable()`. +# Bug Fixes + +* Element type checking in `DynamicRealmObject#setList()` (#4252). +* Now throws `IllegalStateException` instead of process crash when any of thread confined methods in `RealmQuery` is called from wrong thread (#4228). +* Now throws `IllegalStateException` when any of thread confined methods in `DynamicRealmObject` is called from wrong thread (#4258). + ### Internal * Use Object Store's `Results` as the backend for `RealmResults` (#3372). @@ -28,15 +35,6 @@ - Local commits triggers Realm global listener and `RealmObject` listener on current thread immediately instead of in the next event loop. -## 2.3.3 (YYYY-MM-DD) - -### Bug fixes - -* Element type checking in `DynamicRealmObject#setList()` (#4252). -* Now throws `IllegalStateException` instead of process crash when any of thread confined methods in `RealmQuery` is called from wrong thread (#4228). -* Now throws `IllegalStateException` when any of thread confined methods in `DynamicRealmObject` is called from wrong thread (#4258). - - ## 2.3.2 (2017-02-27) ### Bug fixes From 66fb375b32b7db660c1d06fc7e27bf708d8cebab Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 28 Feb 2017 14:56:03 +0100 Subject: [PATCH 0539/2110] Release v3.0.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a86a663118..56fea8a08d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0-SNAPSHOT +3.0.0 \ No newline at end of file From 78e87ae01d999a96de3496e1ffd70cc9efc8b357 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 28 Feb 2017 14:56:03 +0100 Subject: [PATCH 0540/2110] Prepare next release v3.0.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 56fea8a08d..ad121e8340 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0 \ No newline at end of file +3.0.1-SNAPSHOT \ No newline at end of file From 7806d563d018a30c63bfd0139d9bca2c961f3902 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 28 Feb 2017 16:26:11 +0100 Subject: [PATCH 0541/2110] Fix header size --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5192893bf..dc1a5ef341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ * Added support for ChangeListeners on `RealmList`. * Added `RealmList.asObservable()`. -# Bug Fixes +### Bug Fixes * Element type checking in `DynamicRealmObject#setList()` (#4252). * Now throws `IllegalStateException` instead of process crash when any of thread confined methods in `RealmQuery` is called from wrong thread (#4228). From 3906e8569d628556236ce02bfb777eedc8aa7646 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 28 Feb 2017 17:58:54 +0100 Subject: [PATCH 0542/2110] Prepare next dev iteration --- CHANGELOG.md | 2 ++ version.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1a5ef341..85de86d22d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## 3.1.0 (YYYY-MM-DD) + ## 3.0.0 (2017-02-28) ### Breaking Changes diff --git a/version.txt b/version.txt index ad121e8340..0628777500 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.1-SNAPSHOT \ No newline at end of file +3.1.0-SNAPSHOT \ No newline at end of file From 5e9adf7af8be723ad271ead39eb3a0392ceb9ee7 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 2 Mar 2017 12:30:53 +0900 Subject: [PATCH 0543/2110] minimize task dependency --- realm/realm-annotations-processor/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index ac1e0ba50c..373440bdaa 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -38,7 +38,7 @@ sourceSets { } compileJava.dependsOn generateVersionClass -compileTestJava.dependsOn ':realm-library:assemble' +compileTestJava.dependsOn ':realm-library:assembleBaseRelease' task ojoUpload() { dependsOn "artifactoryPublish" From eea13afef8158df3a0d05e671970bec8b04a9498 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 2 Mar 2017 17:02:40 +0900 Subject: [PATCH 0544/2110] update release dates in CHANGELOG.md --- CHANGELOG.md | 121 ++++++++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1a5ef341..809e19b946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 3.0.1 (YYYY-MM-DD) + +### Enhancements + +### Bug Fixes + +### Deprecated + +### Internal + + ## 3.0.0 (2017-02-28) ### Breaking Changes @@ -53,7 +64,7 @@ * Improved performance of getters and setters in proxy classes. -## 2.3.1 +## 2.3.1 (2017-02-07) ### Enhancements @@ -69,7 +80,7 @@ * NullPointerException when notifying a single object that it changed (#4086). -## 2.3.0 +## 2.3.0 (2017-01-19) ### Object Server API Changes @@ -102,7 +113,7 @@ * Updated to Realm Sync v1.0.0. * Added a Realm backup when receiving a Sync client reset message from the server. -## 2.2.2 +## 2.2.2 (2016-01-16) ### Object Server API Changes (In Beta) @@ -128,7 +139,7 @@ * Upgraded Realm Core to 2.3.0. * Upgraded Realm Sync to 1.0.0-BETA-6.5. -## 2.2.1 +## 2.2.1 (2016-11-12) ### Object Server API Changes (In Beta) @@ -139,7 +150,7 @@ * Added version number to the native library, preventing ReLinker from accidentally loading old code (#3775). * `Realm.getLocalInstanceCount(config)` throwing NullPointerException if called after all Realms have been closed (#3791). -## 2.2.0 +## 2.2.0 (2016-11-12) ### Object Server API Changes (In Beta) @@ -155,7 +166,7 @@ * Added support for the `annotationProcessor` configuration provided by Android Gradle Plugin 2.2.0 or later. Realm plugin adds its annotation processor to the `annotationProcessor` configuration instead of `apt` configuration if it is available and the `com.neenbedankt.android-apt` plugin is not used. In Kotlin projects, `kapt` is used instead of the `annotationProcessor` configuration (#3026). -## 2.1.1 +## 2.1.1 (2016-10-27) ### Bug fixes @@ -169,7 +180,7 @@ * ProGuard configuration introduced in 2.1.0 unexpectedly kept classes that did not have the @KeepMember annotation (#3689). -## 2.1.0 +## 2.1.0 (2016-10-25) ### Breaking changes @@ -213,7 +224,7 @@ * Thanks to Max Furman (@maxfurman) for adding support for `first()` and `last()` default values. -## 2.0.2 +## 2.0.2 (2016-10-06) This release is not protocol-compatible with previous versions of the Realm Mobile Platform. The base library is still fully compatible. @@ -226,7 +237,7 @@ This release is not protocol-compatible with previous versions of the Realm Mobi * Upgraded Realm Core to 2.1.0 * Upgraded Realm Sync to 1.0.0-BETA-2.0. -## 2.0.1 +## 2.0.1 (2016-10-05) ### Bug fixes @@ -239,7 +250,7 @@ This release is not protocol-compatible with previous versions of the Realm Mobi * Upgraded to Realm Core 2.0.1 / Realm Sync 1.3-BETA -## 2.0.0 +## 2.0.0 (2016-09-27) This release introduces support for the Realm Mobile Platform! See for an overview of these great new features. @@ -285,7 +296,7 @@ See for an overview o * Updated Realm Core to 2.0.0. * Updated ReLinker to 1.2.2. -## 1.2.0 +## 1.2.0 (2016-08-19) ### Bug fixes @@ -310,7 +321,7 @@ See for an overview o * Thanks to Brenden Kromhout (@bkromhout) for adding binary array support to `equalTo` and `notEqualTo`. -## 1.1.1 +## 1.1.1 (2016-07-01) ### Bug fixes @@ -336,7 +347,7 @@ See for an overview o * Updated Realm Core to 1.4.2. * Improved sorting speed. -## 1.1.0 +## 1.1.0 (2016-06-30) ### Bug fixes @@ -362,7 +373,7 @@ See for an overview o * Updated Realm Core to 1.2.0. -## 1.0.1 +## 1.0.1 (2016-05-25) ### Bug fixes @@ -380,11 +391,11 @@ See for an overview o * Removes RxJava related APIs during bytecode transforming to make RealmObject plays well with reflection when rx.Observable doesn't exist. -## 1.0.0 +## 1.0.0 (2016-05-25) No changes since 0.91.1. -## 0.91.1 +## 0.91.1 (2016-05-25) * Updated Realm Core to 1.0.1. @@ -392,7 +403,7 @@ No changes since 0.91.1. * Fixed a bug when opening a Realm causes a staled memory mapping. Symptoms are error messages like "Bad or incompatible history type", "File format version doesn't match", and "Encrypted interprocess sharing is currently unsupported". -## 0.91.0 +## 0.91.0 (2016-05-20) * Updated Realm Core to 1.0.0. @@ -560,7 +571,7 @@ No changes since 0.91.1. * now DynamicRealmObject.toString() correctly shows null value as "null" and the format is aligned to the String from typed RealmObject (#2439). * Fixed an issue occurring while resolving ReLinker in apps using a library based on Realm (#2415). -## 0.88.0 +## 0.88.0 (2016-03-10) * Updated Realm Core to 0.97.0. @@ -613,16 +624,16 @@ No changes since 0.91.1. * Thanks to Bill Best (@wmbest2) for snapshot testing. * Thanks to Graham Smith (@grahamsmith) for a detailed bug report (#2200). -## 0.87.5 +## 0.87.5 (2016-01-29) * Updated Realm Core to 0.96.2. - IllegalStateException won't be thrown anymore in RealmResults.where() if the RealmList which the RealmResults is created on has been deleted. Instead, the RealmResults will be treated as empty forever. - Fixed a bug causing a bad version exception, when using findFirstAsync (#2115). -## 0.87.4 +## 0.87.4 (2016-01-28) * Updated Realm Core to 0.96.0. - Fixed bug causing BadVersionException or crashing core when running async queries. -## 0.87.3 +## 0.87.3 (2016-01-25) * IllegalArgumentException is now properly thrown when calling Realm.copyFromRealm() with a DynamicRealmObject (#2058). * Fixed a message in IllegalArgumentException thrown by the accessors of DynamicRealmObject (#2141). * Fixed RealmList not returning DynamicRealmObjects of the correct underlying type (#2143). @@ -631,21 +642,21 @@ No changes since 0.91.1. - Fixed a bug where undetected deleted object might lead to seg. fault (#1945). - Better performance when deleting objects (#2015). -## 0.87.2 +## 0.87.2 (2016-01-08) * Removed explicit GC call when committing a transaction (#1925). * Fixed a bug when RealmObjectSchema.addField() was called with the PRIMARY_KEY modifier, the field was not set as a required field (#2001). * Fixed a bug which could throw a ConcurrentModificationException in RealmObject's or RealmResults' change listener (#1970). * Fixed RealmList.set() so it now correctly returns the old element instead of the new (#2044). * Fixed the deployment of source and javadoc jars (#1971). -## 0.87.1 +## 0.87.1 (2015-12-23) * Upgraded to NDK R10e. Using gcc 4.9 for all architectures. * Updated Realm Core to 0.95.6 - Fixed a bug where an async query can be copied incomplete in rare cases (#1717). * Fixed potential memory leak when using async query. * Added a check to prevent removing a RealmChangeListener from a non-Looper thread (#1962). (Thank you @hohnamkung) -## 0.87.0 +## 0.87.0 (2015-12-17) * Added Realm.asObservable(), RealmResults.asObservable(), RealmObject.asObservable(), DynamicRealm.asObservable() and DynamicRealmObject.asObservable(). * Added RealmConfiguration.Builder.rxFactory() and RxObservableFactory for custom RxJava observable factory classes. * Added Realm.copyFromRealm() for creating detached copies of Realm objects (#931). @@ -654,7 +665,7 @@ No changes since 0.91.1. * Added support for ISO8601 based dates for JSON import. If JSON dates are invalid a RealmException will be thrown (#1213). * Added APK splits to gridViewExample (#1834). -## 0.86.1 +## 0.86.1 (2015-12-11) * Improved the performance of removing objects (RealmResults.clear() and RealmResults.remove()). * Updated Realm Core to 0.95.5. * Updated ProGuard configuration (#1904). @@ -666,7 +677,7 @@ No changes since 0.91.1. * Fixed RealmChangeListener never called inside RealmResults (#1894). * Fixed crash when calling clear on a RealmList (#1886). -## 0.86.0 +## 0.86.0 (2015-12-03) * BREAKING CHANGE: The Migration API has been replaced with a new API. * BREAKING CHANGE: RealmResults.SORT_ORDER_ASCENDING and RealmResults.SORT_ORDER_DESCENDING constants have been replaced by Sort.ASCENDING and Sort.DESCENDING enums. * BREAKING CHANGE: RealmQuery.CASE_SENSITIVE and RealmQuery.CASE_INSENSITIVE constants have been replaced by Case.SENSITIVE and Case.INSENSITIVE enums. @@ -683,10 +694,10 @@ No changes since 0.91.1. - Fixed a bug where RealmQuery.average(String) returned a wrong value for a nullable Long/Integer/Short/Byte field (#1803). - Fixed a bug where RealmQuery.average(String) wrongly counted the null value for average calculation (#1854). -## 0.85.1 +## 0.85.1 (2015-11-23) * Fixed a bug which could corrupt primary key information when updating from a Realm version <= 0.84.1 (#1775). -## 0.85.0 +## 0.85.0 (2016-11-19) * BREAKING CHANGE: Removed RealmEncryptionNotSupportedException since the encryption implementation changed in Realm's underlying storage engine. Encryption is now supported on all devices. * BREAKING CHANGE: Realm.executeTransaction() now directly throws any RuntimeException instead of wrapping it in a RealmException (#1682). * BREAKING CHANGE: RealmQuery.isNull() and RealmQuery.isNotNull() now throw IllegalArgumentException instead of RealmError if the fieldname is a linked field and the last element is a link (#1693). @@ -704,7 +715,7 @@ No changes since 0.91.1. * Fixed a memory leak when using relationships (#1285). * Fixed a bug causing cached column indices to be cleared too soon (#1732). -## 0.84.1 +## 0.84.1 (2015-10-28) * Updated Realm Core to 0.94.4. - Fixed a bug that could cause a crash when running the same query multiple times. * Updated ProGuard configuration. See [documentation](https://realm.io/docs/java/latest/#proguard) for more details. @@ -713,7 +724,7 @@ No changes since 0.91.1. * Fixed a bug where simultaneous opening and closing a Realm from different threads might result in a NullPointerException (#1646). * Fixed a bug which made it possible to externally modify the encryption key in a RealmConfiguration (#1678). -## 0.84.0 +## 0.84.0 (2015-10-22) * Added support for async queries and transactions. * Added support for parsing JSON Dates with timezone information. (Thank you @LateralKevin) * Added RealmQuery.isEmpty(). @@ -735,12 +746,12 @@ No changes since 0.91.1. * Fixed a bug that made it possible to migrate open Realms, which could cause undefined behavior when querying, reading or writing data. * Fixed a bug causing column indices to be wrong for some edge cases. See #1611 for details. -## 0.83.1 +## 0.83.1 (2015-10-15) * Updated Realm Core to version 0.94.1. - Fixed a bug when using Realm.compactRealm() which could make it impossible to open the Realm file again. - Fixed a bug, so isNull link queries now always return true if any part is null. -## 0.83 +## 0.83 (2015-10-08) * BREAKING CHANGE: Database file format update. The Realm file created by this version cannot be used by previous versions of Realm. * BREAKING CHANGE: Removed deprecated methods and constructors from the Realm class. * BREAKING CHANGE: Introduced boxed types Boolean, Byte, Short, Integer, Long, Float and Double. Added null support. Introduced annotation @Required to indicate a field is not nullable. String, Date and byte[] became nullable by default which means a RealmMigrationNeededException will be thrown if an previous version of a Realm file is opened. @@ -751,7 +762,7 @@ No changes since 0.91.1. * Opening a Realm file from one thread will no longer be blocked by a transaction from another thread. * Range restrictions of Date fields have been removed. Date fields now accepts any value. Milliseconds are still removed. -## 0.82.2 +## 0.82.2 (2015-09-04) * Fixed a bug which might cause failure when loading the native library. * Fixed a bug which might trigger a timeout in Context.finalize(). * Fixed a bug which might cause RealmObject.isValid() to throw an exception if the object is deleted. @@ -760,12 +771,12 @@ No changes since 0.91.1. - Embedded crypto functions into Realm dynamic lib to avoid random issues on some devices. - Throw RealmEncryptionNotSupportedException if the device doesn't support Realm encryption. At least one device type (HTC One X) contains system bugs that prevents Realm's encryption from functioning properly. This is now detected, and an exception is thrown when trying to open/create an encrypted Realm file. It's up to the application to catch this and decide if it's OK to proceed without encryption instead. -## 0.82.1 +## 0.82.1 (2015-08-06) * Fixed a bug where using the wrong encryption key first caused the right key to be seen as invalid. * Fixed a bug where String fields were ignored when updating objects from JSON with null values. * Fixed a bug when calling System.exit(0), the process might hang. -## 0.82 +## 0.82 (2015-07-28) * BREAKING CHANGE: Fields with annotation @PrimaryKey are indexed automatically now. Older schemas require a migration. * RealmConfiguration.setModules() now accept ignore null values which Realm.getDefaultModule() might return. * Trying to access a deleted Realm object throw throws a proper IllegalStateException. @@ -776,10 +787,10 @@ No changes since 0.91.1. * Fixed a bug where RealmQuery objects are prematurely garbage collected. * Removed RealmQuery.between() for link queries. -## 0.81.1 +## 0.81.1 (2015-06-22) * Fixed memory leak causing Realm to never release Realm objects. -## 0.81 +## 0.81 (2015-06-19) * Introduced RealmModules for working with custom schemas in libraries and apps. * Introduced Realm.getDefaultInstance(), Realm.setDefaultInstance(RealmConfiguration) and Realm.getInstance(RealmConfiguration). * Deprecated most constructors. They have been been replaced by Realm.getInstance(RealmConfiguration) and Realm.getDefaultInstance(). @@ -794,7 +805,7 @@ No changes since 0.91.1. * Cleaned up examples (remove old test project). * Added checking for missing generic type in RealmList fields in annotation processor. -## 0.80.3 +## 0.80.3 (2015-05-22) * Calling Realm.copyToRealmOrUpdate() with an object with a null primary key now throws a proper exception. * Fixed a bug making it impossible to open Realms created by Realm-Cocoa if a model had a primary key defined. * Trying to using Realm.copyToRealmOrUpdate() with an object with a null primary key now throws a proper exception. @@ -807,14 +818,14 @@ No changes since 0.91.1. * Solved ConcurrentModificationException thrown when addChangeListener/removeChangeListener got called in the onChange. (Thanks @beeender) * Fixed duplicated listeners in the same realm instance. Trying to add duplicated listeners is ignored now. (Thanks @beeender) -## 0.80.2 +## 0.80.2 (2015-05-04) * Trying to use Realm.copyToRealmOrUpdate() with an object with a null primary key now throws a proper exception. * RealmMigrationNeedException can now return the path to the Realm that needs to be migrated. * Fixed bug where creating a Realm instance with a hashcode collision no longer returned the wrong Realm instance. * Updated Realm Core to version 0.89.2 - fixed bug causing a crash when opening an encrypted Realm file on ARM64 devices. -## 0.80.1 +## 0.80.1 (2015-04-16) * Realm.createOrUpdateWithJson() no longer resets fields to their default value if they are not found in the JSON input. * Realm.compactRealmFile() now uses Realm Core's compact() method which is more failure resilient. * Realm.copyToRealm() now correctly handles referenced child objects that are already in the Realm. @@ -833,7 +844,7 @@ No changes since 0.91.1. * Added RealmQuery.isNull() and RealmQuery.isNotNull() for querying relationships. * Fixed a potential NPE in the RealmList constructor. -## 0.80 +## 0.80 (2015-03-11) * Queries on relationships can be case sensitive. * Fixed bug when importing JSONObjects containing NULL values. * Fixed crash when trying to remove last element of a RealmList. @@ -843,11 +854,11 @@ No changes since 0.91.1. * Added support for static fields in RealmObjects. * Realm.writeEncryptedCopyTo() has been reenabled. -## 0.79.1 +## 0.79.1 (2015-02-20) * copyToRealm() no longer crashes on cyclic data structures. * Fixed potential crash when using copyToRealmOrUpdate with an object graph containing a mix of elements with and without primary keys. -## 0.79 +## 0.79 (2015-02-16) * Added support for ARM64. * Added RealmQuery.not() to negate a query condition. * Added copyToRealmOrUpdate() and createOrUpdateFromJson() methods, that works for models with primary keys. @@ -862,7 +873,7 @@ No changes since 0.91.1. * Removed methods deprecated in 0.76. Now Realm.allObjectsSorted() and RealmQuery.findAllSorted() need to be used instead. * Reimplemented Realm.allObjectSorted() for better performance. -## 0.78 +## 0.78 (2015-01-22) * Added proper support for encryption. Encryption support is now included by default. Keys are now 64 bytes long. * Added support to write an encrypted copy of a Realm. * Realm no longer incorrectly warns that an instance has been closed too many times. @@ -870,7 +881,7 @@ No changes since 0.91.1. * Fixed bug causing Realms to be cached during a RealmMigration resulting in invalid realms being returned from Realm.getInstance(). * Updated core to 0.88. -## 0.77 +## 0.77 (2015-01-16) * Added Realm.allObjectsSorted() and RealmQuery.findAllSorted() and extending RealmResults.sort() for multi-field sorting. * Added more logging capabilities at the JNI level. * Added proper encryption support. NOTE: The key has been increased from 32 bytes to 64 bytes (see example). @@ -889,7 +900,7 @@ No changes since 0.91.1. * RealmList.remove() now properly returns the removed object. * Calling realm.close() no longer prevent updates to other open realm instances on the same thread. -## 0.76.0 +## 0.76.0 (2014-12-19) * RealmObjects can now be imported using JSON. * Gradle wrapper updated to support Android Studio 1.0. * Fixed bug in RealmObject.equals() so it now correctly compares two objects from the same Realm. @@ -901,13 +912,13 @@ No changes since 0.91.1. * Close the Realm instance after migrations. * Added a check to deny the writing of objects outside of a transaction. -## 0.75.1 (03 December 2014) +## 0.75.1 (2014-12-03) * Changed sort to be an in-place method. * Renamed SORT_ORDER_DECENDING to SORT_ORDER_DESCENDING. * Added sorting functionality to allObjects() and findAll(). * Fixed bug when querying a date column with equalTo(), it would act as lessThan() -## 0.75.0 (28 Nov 2014) +## 0.75.0 (2014-11-28) * Realm now implements Closeable, allowing better cleanup of native resources. * Added writeCopyTo() and compactRealmFile() to write and compact a Realm to a new file. * RealmObject.toString(), equals() and hashCode() now support models with cyclic references. @@ -919,7 +930,7 @@ No changes since 0.91.1. * Fixed bug so Realm no longer throws an Exception when removing the last object. * Fixed bug in RealmResults which prevented sub-querying. -## 0.74.0 (19 Nov 2014) +## 0.74.0 (2014-11-19) * Added support for more field/accessors naming conventions. * Added case sensitive versions of string comparison operators equalTo and notEqualTo. * Added where() to RealmList to initiate queries. @@ -932,17 +943,17 @@ No changes since 0.91.1. * Consistent handling of UTF-8 strings. * removeFromRealm() now calls moveLastOver() which is faster and more reliable when deleting multiple objects. -## 0.73.1 (05 Nov 2014) +## 0.73.1 (2014-11-05) * Fixed a bug that would send infinite notifications in some instances. -## 0.73.0 (04 Nov 2014) +## 0.73.0 (2014-11-04) * Fixed a bug not allowing queries with more than 1024 conditions. * Rewritten the notification system. The API did not change but it's now much more reliable. * Added support for switching auto-refresh on and off (Realm.setAutoRefresh). * Added RealmBaseAdapter and an example using it. * Added deleteFromRealm() method to RealmObject. -## 0.72.0 (27 Oct 2014) +## 0.72.0 (2014-10-27) * Extended sorting support to more types: boolean, byte, short, int, long, float, double, Date, and String fields are now supported. * Better support for Java 7 and 8 in the annotations processor. * Better support for the Eclipse annotations processor. @@ -952,7 +963,7 @@ No changes since 0.91.1. * Faster implementation of RealmQuery.findFirst(). * Upgraded core to 0.85.1 (deep copying of strings in queries; preparation for link queries). -## 0.71.0 (07 Oct 2014) +## 0.71.0 (2014-10-07) * Simplified the release artifact to a single Jar file. * Added support for Eclipse. * Added support for deploying to Maven. @@ -965,9 +976,9 @@ No changes since 0.91.1. * Added a new example about concurrency. * Upgraded to core 0.84.0. -## 0.70.1 (30 Sep 2014) +## 0.70.1 (2014-09-30) * Enabled unit testing for the realm project. * Fixed handling of camel-cased field names. -## 0.70.0 (29 Sep 2014) +## 0.70.0 (2014-09-29) * This is the first public beta release. From 34a27b4ea41407fd078cbede7c2ce10abcedcfcf Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 24 Feb 2017 09:30:17 +0900 Subject: [PATCH 0545/2110] Update android gradle plugin to 2.3.0-rc1 and gradle to 3.3 --- Dockerfile | 14 +++-- README.md | 2 +- examples/build.gradle | 4 +- examples/gradle/wrapper/gradle-wrapper.jar | Bin 53324 -> 52928 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/gradlew | 5 ++ examples/gradlew.bat | 6 --- .../secureTokenAndroidKeyStore/build.gradle | 6 +-- .../gradle/wrapper/gradle-wrapper.jar | Bin 53324 -> 52928 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle-plugin/gradlew | 5 ++ gradle-plugin/gradlew.bat | 6 --- gradle/wrapper/gradle-wrapper.jar | Bin 53324 -> 52928 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 5 ++ gradlew.bat | 6 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 53324 -> 52928 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-annotations/gradlew | 5 ++ realm-annotations/gradlew.bat | 6 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 53324 -> 52928 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-transformer/gradlew | 5 ++ realm-transformer/gradlew.bat | 6 --- realm.properties | 2 +- realm/build.gradle | 2 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 53324 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- realm/gradlew | 22 +++++--- realm/gradlew.bat | 6 --- .../realm-annotations-processor/build.gradle | 2 +- realm/realm-library/build.gradle | 48 +++++++++--------- 32 files changed, 91 insertions(+), 88 deletions(-) diff --git a/Dockerfile b/Dockerfile index b3a8820a23..070335f930 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,9 +48,12 @@ RUN cd /opt && \ # Grab what's needed in the SDK # ↓ updates tools to at least 25.1.7, but that prints 'Nothing was installed' (so I don't check the outputs). +RUN mkdir "${ANDROID_HOME}/licenses" && \ + echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" && \ + echo -en "\nd23d63a1f23e25e2c7a316e29eb60396e7924281" > "${ANDROID_HOME}/licenses/android-sdk-preview-license" RUN echo y | android update sdk --no-ui --all --filter tools > /dev/null RUN echo y | android update sdk --no-ui --all --filter platform-tools | grep 'package installed' -RUN echo y | android update sdk --no-ui --all --filter build-tools-24.0.0 | grep 'package installed' +RUN echo y | android update sdk --no-ui --all --filter build-tools-25.0.2 | grep 'package installed' RUN echo y | android update sdk --no-ui --all --filter extra-android-m2repository | grep 'package installed' RUN echo y | android update sdk --no-ui --all --filter android-24 | grep 'package installed' @@ -67,12 +70,13 @@ RUN mkdir /opt/android-ndk-tmp && \ # Install cmake RUN mkdir /opt/cmake-tmp && \ cd /opt/cmake-tmp && \ - wget -q https://dl.google.com/android/repository/cmake-3.6.3133135-linux-x86_64.zip -O cmake-linux.zip && \ - unzip cmake-linux.zip -d ${ANDROID_HOME}/cmake && \ + wget -q https://dl.google.com/android/repository/cmake-3.6.3155560-linux-x86_64.zip -O cmake-linux.zip && \ + mkdir -p ${ANDROID_HOME}/cmake/3.6.3155560 && \ + unzip cmake-linux.zip -d ${ANDROID_HOME}/cmake/3.6.3155560 && \ rm -rf /opt/cmake-tmp -# Make the SDK universally readable -RUN chmod -R a+rX ${ANDROID_HOME} +# Make the SDK universally writable +RUN chmod -R a+rwX ${ANDROID_HOME} # Install lcache RUN wget -q https://github.com/beeender/lcache/releases/download/v0.0.2/lcache-linux -O /usr/bin/lcache && \ diff --git a/README.md b/README.md index 9c485ca978..f35d59b43a 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install the Android SDK **Build-Tools 24.0.0**, **Android N (API 24)** (for example through Android Studio’s **Android SDK Manager**). + * Download & install the Android SDK **Build-Tools 25.0.2**, **Android N (API 24)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). diff --git a/examples/build.gradle b/examples/build.gradle index 64b863d9f8..0d2f1167ef 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,5 +1,5 @@ project.ext.sdkVersion = 24 -project.ext.buildTools = '24.0.0' +project.ext.buildTools = '25.0.2' // Don't cache SNAPSHOT (changing) dependencies. configurations.all { @@ -16,7 +16,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index 3baa851b28c65f87dd36a6748e1a85cf360c1301..6ffa237849ef3607e39c3b334a92a65367962071 100644 GIT binary patch delta 10253 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 J*Yy|q{{VlTTGIdk diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index f930473763..619351f074 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/examples/gradlew b/examples/gradlew index 27309d9231..9aa616c273 100755 --- a/examples/gradlew +++ b/examples/gradlew @@ -161,4 +161,9 @@ function splitJvmOpts() { eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/examples/gradlew.bat b/examples/gradlew.bat index f6d5974e72..e95643d6a2 100644 --- a/examples/gradlew.bat +++ b/examples/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index 7222838bea..cb4fe8b32a 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -2,8 +2,8 @@ apply plugin: 'com.android.application' apply plugin: 'realm-android' android { - compileSdkVersion 24 - buildToolsVersion "24.0.0" + compileSdkVersion rootProject.sdkVersion + buildToolsVersion rootProject.buildTools defaultConfig { applicationId "io.realm.examples.securetokenandroidkeystore" @@ -35,4 +35,4 @@ dependencies { realm { syncEnabled = true -} \ No newline at end of file +} diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 3baa851b28c65f87dd36a6748e1a85cf360c1301..6ffa237849ef3607e39c3b334a92a65367962071 100644 GIT binary patch delta 10253 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 J*Yy|q{{VlTTGIdk diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 4912622457..e05d79c572 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradle-plugin/gradlew b/gradle-plugin/gradlew index 27309d9231..9aa616c273 100755 --- a/gradle-plugin/gradlew +++ b/gradle-plugin/gradlew @@ -161,4 +161,9 @@ function splitJvmOpts() { eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradle-plugin/gradlew.bat b/gradle-plugin/gradlew.bat index f6d5974e72..e95643d6a2 100644 --- a/gradle-plugin/gradlew.bat +++ b/gradle-plugin/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 3baa851b28c65f87dd36a6748e1a85cf360c1301..6ffa237849ef3607e39c3b334a92a65367962071 100644 GIT binary patch delta 10253 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 J*Yy|q{{VlTTGIdk diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bc6b7c4622..866aa02b6f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 20 14:03:29 CST 2016 +#Mon Jan 16 12:37:33 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradlew b/gradlew index 27309d9231..9aa616c273 100755 --- a/gradlew +++ b/gradlew @@ -161,4 +161,9 @@ function splitJvmOpts() { eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat index f6d5974e72..e95643d6a2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index 3baa851b28c65f87dd36a6748e1a85cf360c1301..6ffa237849ef3607e39c3b334a92a65367962071 100644 GIT binary patch delta 10253 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 J*Yy|q{{VlTTGIdk diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index a18e4af3f9..ebb1e60945 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/realm-annotations/gradlew b/realm-annotations/gradlew index 27309d9231..9aa616c273 100755 --- a/realm-annotations/gradlew +++ b/realm-annotations/gradlew @@ -161,4 +161,9 @@ function splitJvmOpts() { eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/realm-annotations/gradlew.bat b/realm-annotations/gradlew.bat index f6d5974e72..e95643d6a2 100644 --- a/realm-annotations/gradlew.bat +++ b/realm-annotations/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 3baa851b28c65f87dd36a6748e1a85cf360c1301..6ffa237849ef3607e39c3b334a92a65367962071 100644 GIT binary patch delta 10253 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 J*Yy|q{{VlTTGIdk diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index e9bd4ba64f..9297d3852f 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/realm-transformer/gradlew b/realm-transformer/gradlew index 27309d9231..9aa616c273 100755 --- a/realm-transformer/gradlew +++ b/realm-transformer/gradlew @@ -161,4 +161,9 @@ function splitJvmOpts() { eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/realm-transformer/gradlew.bat b/realm-transformer/gradlew.bat index f6d5974e72..e95643d6a2 100644 --- a/realm-transformer/gradlew.bat +++ b/realm-transformer/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/realm.properties b/realm.properties index 1be5af0639..f2ca4ed832 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=2.14.1 +gradleVersion=3.3 ndkVersion=r10e diff --git a/realm/build.gradle b/realm/build.gradle index d520cb8024..120340d00c 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' + classpath 'com.android.tools.build:gradle:2.3.0' classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.novoda:gradle-android-command-plugin:1.3.0' diff --git a/realm/gradle/wrapper/gradle-wrapper.jar b/realm/gradle/wrapper/gradle-wrapper.jar index 3baa851b28c65f87dd36a6748e1a85cf360c1301..172e6b6cc40b2c8cab45824bba4a8cbac29959cf 100644 GIT binary patch delta 21799 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 S8gVG|1v@Jj9C(#*>;C{k4ZhI; diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 897f3bf902..81199ae820 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 20 14:03:59 CST 2016 +#Mon Jan 16 20:15:19 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/realm/gradlew b/realm/gradlew index 27309d9231..4453ccea33 100755 --- a/realm/gradlew +++ b/realm/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/realm/gradlew.bat b/realm/gradlew.bat index f6d5974e72..e95643d6a2 100644 --- a/realm/gradlew.bat +++ b/realm/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 373440bdaa..d4c3c94419 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -11,7 +11,7 @@ dependencies { compile group:'com.squareup', name:'javawriter', version:'2.5.0' compile "io.realm:realm-annotations:${version}" - testCompile files('../realm-library/build/intermediates/bundles/base/release/classes.jar') // Java projects cannot depend on AAR files + testCompile files('../realm-library/build/intermediates/bundles/baseRelease/classes.jar') // Java projects cannot depend on AAR files testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it testCompile group:'junit', name:'junit', version:'4.12' testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ac7f1affbd..db9a4d6ecf 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -34,7 +34,7 @@ ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE android { compileSdkVersion 24 - buildToolsVersion '24.0.0' + buildToolsVersion '25.0.2' defaultConfig { minSdkVersion 9 @@ -529,28 +529,30 @@ project.afterEvaluate { } } -task checkNdk() << { - def ndkPathInEnvVariable = System.env.ANDROID_NDK_HOME - if (!ndkPathInEnvVariable) { - throw new GradleException("The environment variable 'ANDROID_NDK_HOME' must be set.") - } - checkNdk(ndkPathInEnvVariable) - - def localPropFile = rootProject.file('local.properties') - if (!localPropFile.exists()) { - // we can skip the checks since 'ANDROID_NDK_HOME' will be used instead. - } else { - def String ndkPathInLocalProperties = getValueFromPropertiesFile(localPropFile, 'ndk.dir') - if (!ndkPathInLocalProperties) { - throw new GradleException("'ndk.dir' must be set in ${localPropFile.getAbsolutePath()}.") - } - checkNdk(ndkPathInLocalProperties) - if (new File(ndkPathInLocalProperties).getCanonicalPath() - != new File(ndkPathInEnvVariable).getCanonicalPath()) { - throw new GradleException( - "The value of environment variable 'ANDROID_NDK_HOME' (${ndkPathInEnvVariable}) and" - + " 'ndk.dir' in 'local.properties' (${ndkPathInLocalProperties}) " - + ' must point the same directory.') +task checkNdk() { + doLast { + def ndkPathInEnvVariable = System.env.ANDROID_NDK_HOME + if (!ndkPathInEnvVariable) { + throw new GradleException("The environment variable 'ANDROID_NDK_HOME' must be set.") + } + checkNdk(ndkPathInEnvVariable) + + def localPropFile = rootProject.file('local.properties') + if (!localPropFile.exists()) { + // we can skip the checks since 'ANDROID_NDK_HOME' will be used instead. + } else { + def String ndkPathInLocalProperties = getValueFromPropertiesFile(localPropFile, 'ndk.dir') + if (!ndkPathInLocalProperties) { + throw new GradleException("'ndk.dir' must be set in ${localPropFile.getAbsolutePath()}.") + } + checkNdk(ndkPathInLocalProperties) + if (new File(ndkPathInLocalProperties).getCanonicalPath() + != new File(ndkPathInEnvVariable).getCanonicalPath()) { + throw new GradleException( + "The value of environment variable 'ANDROID_NDK_HOME' (${ndkPathInEnvVariable}) and" + + " 'ndk.dir' in 'local.properties' (${ndkPathInLocalProperties}) " + + ' must point the same directory.') + } } } } From 5517f4d0606a7d26e2e21837f1d16dc0fbe26887 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 2 Mar 2017 03:20:34 +0900 Subject: [PATCH 0546/2110] update gadle wrapper to 3.4 --- examples/build.gradle | 10 +++++++++ examples/gradle/wrapper/gradle-wrapper.jar | Bin 52928 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- examples/gradlew | 19 ++++++++++-------- .../gradle/wrapper/gradle-wrapper.jar | Bin 52928 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradle-plugin/gradlew | 19 ++++++++++-------- gradle/wrapper/gradle-wrapper.jar | Bin 52928 -> 54208 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 19 ++++++++++-------- realm-annotations/build.gradle | 12 +++++++++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 52928 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- realm-annotations/gradlew | 19 ++++++++++-------- realm-transformer/build.gradle | 13 ++++++++++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 52928 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- realm-transformer/gradlew | 19 ++++++++++-------- realm.properties | 2 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- 21 files changed, 103 insertions(+), 53 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 0d2f1167ef..34d1f102d4 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -9,6 +9,12 @@ configurations.all { allprojects { def currentVersion = file("${rootDir}/../version.txt").text.trim() + def props = new Properties() + props.load(new FileInputStream("${rootDir}/../realm.properties")) + props.each { key, val -> + project.ext.set(key, val) + } + buildscript { repositories { mavenLocal() @@ -33,3 +39,7 @@ allprojects { jcenter() } } + +task wrapper(type: Wrapper) { + gradleVersion = project.gradleVersion +} diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index 6ffa237849ef3607e39c3b334a92a65367962071..35fb1ae0907527e832aa0839f8b932bc6ff5cab6 100644 GIT binary patch delta 17812 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 delta 16531 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 delta 16531 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 QXc7$t3*vZ5Ex&vJKUMeMMF0Q* diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 866aa02b6f..e330fabc4f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jan 16 12:37:33 JST 2017 +#Thu Mar 02 03:10:56 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip diff --git a/gradlew b/gradlew index 9aa616c273..4453ccea33 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -154,16 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index a372797de3..40213eb437 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -8,6 +8,14 @@ buildscript { } } +allprojects { + def props = new Properties() + props.load(new FileInputStream("${rootDir}/../realm.properties")) + props.each { key, val -> + project.ext.set(key, val) + } +} + apply plugin: 'java' apply plugin: 'maven' apply plugin: 'maven-publish' @@ -103,3 +111,7 @@ artifactory { } } } + +task wrapper(type: Wrapper) { + gradleVersion = project.gradleVersion +} diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index 6ffa237849ef3607e39c3b334a92a65367962071..366e45e87959c7f4d59bfdeb4cc799153b30bc88 100644 GIT binary patch delta 17812 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 delta 16531 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 + project.ext.set(key, val) + } +} + apply plugin: 'groovy' apply plugin: 'java' apply plugin: 'maven' @@ -153,3 +161,8 @@ artifactory { } } } + +task wrapper(type: Wrapper) { + gradleVersion = project.gradleVersion +} + diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 6ffa237849ef3607e39c3b334a92a65367962071..9fe1b93a5f8b4538bed614f2df156a65d8b49695 100644 GIT binary patch delta 17813 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 QXc7$t3*vZ5Ex&vJKUMeMMF0Q* diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 9297d3852f..e5aee03000 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 20 14:33:05 CST 2016 +#Fri Mar 03 05:41:34 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip diff --git a/realm-transformer/gradlew b/realm-transformer/gradlew index 9aa616c273..4453ccea33 100755 --- a/realm-transformer/gradlew +++ b/realm-transformer/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -154,16 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/realm.properties b/realm.properties index f2ca4ed832..c2f4f60551 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=3.3 +gradleVersion=3.4 ndkVersion=r10e diff --git a/realm/gradle/wrapper/gradle-wrapper.jar b/realm/gradle/wrapper/gradle-wrapper.jar index 172e6b6cc40b2c8cab45824bba4a8cbac29959cf..b3558fded17a332ca8a9c0d5f1a145601bec080e 100644 GIT binary patch delta 957 zcmYL{ZAep57{|}uoo!8KP7TyBUp}ma$`Y}fy}!5Fa&3{NPV<_*SY(D{y?iL>TOU$S zM3Pc5Yl2|VqDTa7n|pO`Dq;mi7A4Xb2bBdy-TOacA1=S&^PK-VoO{k0bn=5vUR`G3 zNKBrnN2`yGl zqmh%CuYi=M3@m~qWhi8j^kr5im$Kf;(K(u@Vc~Q6>l367b*`Dh$jyQlmR(+GQKQqm zwLBFPJ2b6=^i_4S%<-Bx5V~2YQn=%6Wz@+L@|Y)N1C86VXfjo}c3li`$S-LgW%9{Z z#bl@bxg0~kua@u--L>u%$mh0fB}8#A#=>tqD9bcH;F;JQ6AEOEJ%8E0%X+u71#dsK zu+GRVOw*YG{K^cRER8HY4f@huwD30QcehSDdRpiy0P8%(V5esb_}*g% zi@XQG8(tIm+G_p=yxxRe;q#mVe?aN3HT3!3w_~?Dm&}bAAK3J8&5646yvq zd76Vp=>Tb0PzO#0t>BIjqa$QOJr}wFHiXUKtFQpB7(M~s8n%F6hU>wDkuzX_gw0PL zIfeSlNFDfbgyjs;Bd8xmS?*W#Bx)hX7>QM(UOmd@-<>Bv#vX3%SS`l=^R;$duN1FJ LFI^C|kMH{jD(q+4 delta 957 zcmYL{YeC0`Y0m6UW+*HDkPv<8L+Xhr zQYy4I5Eiv45<%PMuFlO#S1K|o8GUKgvOuZ#{U5OpFTdaOoc}qT_nb3q=7!CjqD;d{ z(g=~sh`cSgQ;{d*p?)>n9_uX8r=|aBiZ+SJ|IA!#Zl^h$6X0J3{3;M&Q;Ot^1fj+9 zX*5D-`H7 zZ`G$lLi?xXkb&wxmN`=U7D6{0tGF1yO1E*fK%3wANerg1eP`c diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 81199ae820..1f2857b24f 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jan 16 20:15:19 JST 2017 +#Thu Mar 02 03:14:47 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip From 708cbf3d13a62bde5d3cdd85955e9aafcca83ea6 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 3 Mar 2017 17:23:51 +0900 Subject: [PATCH 0547/2110] Update kotlin used in example to 1.1 (#4282) * Update android gradle plugin to 2.3.0-rc1 and gradle to 3.3 * update gadle wrapper to 3.4 * update kotlin to 1.1 --- examples/kotlinExample/build.gradle | 7 +- .../examples/kotlin/KotlinExampleActivity.kt | 81 ++++++++++--------- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 8034025e63..79d9712d8a 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0' repositories { jcenter() mavenCentral() @@ -39,11 +39,6 @@ android { events 2000 } - // Incremental builds currently doesn't work with Kotlin - dexOptions { - incremental false - } - sourceSets { main.java.srcDirs += 'src/main/kotlin' } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index 73f0663136..aba7c85d6e 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -64,9 +64,8 @@ class KotlinExampleActivity : Activity() { // More complex operations can be executed on another thread, for example using // Anko's async extension method. - async() { - var info: String - info = complexReadWrite() + async { + var info = complexReadWrite() info += complexQuery() uiThread { @@ -134,51 +133,53 @@ class KotlinExampleActivity : Activity() { // Open the default realm. All threads must use its own reference to the realm. // Those can not be transferred across threads. val realm = Realm.getDefaultInstance() - - // Add ten persons in one transaction - realm.executeTransaction { - val fido = realm.createObject(Dog::class.java) - fido.name = "fido" - for (i in 1..9) { - val person = realm.createObject(Person::class.java, i.toLong()) - person.name = "Person no. $i" - person.age = i - person.dog = fido - - // The field tempReference is annotated with @Ignore. - // This means setTempReference sets the Person tempReference - // field directly. The tempReference is NOT saved as part of - // the RealmObject: - person.tempReference = 42 - - for (j in 0..i - 1) { - val cat = realm.createObject(Cat::class.java) - cat.name = "Cat_$j" - person.cats.add(cat) + try { + // Add ten persons in one transaction + realm.executeTransaction { + val fido = realm.createObject(Dog::class.java) + fido.name = "fido" + for (i in 1..9) { + val person = realm.createObject(Person::class.java, i.toLong()) + person.name = "Person no. $i" + person.age = i + person.dog = fido + + // The field tempReference is annotated with @Ignore. + // This means setTempReference sets the Person tempReference + // field directly. The tempReference is NOT saved as part of + // the RealmObject: + person.tempReference = 42 + + for (j in 0..i - 1) { + val cat = realm.createObject(Cat::class.java) + cat.name = "Cat_$j" + person.cats.add(cat) + } } } - } - // Implicit read transactions allow you to access your objects - status += "\nNumber of persons: ${realm.where(Person::class.java).count()}" + // Implicit read transactions allow you to access your objects + status += "\nNumber of persons: ${realm.where(Person::class.java).count()}" - // Iterate over all objects - for (person in realm.where(Person::class.java).findAll()) { - val dogName: String = person?.dog?.name ?: "None" + // Iterate over all objects + for (person in realm.where(Person::class.java).findAll()) { + val dogName: String = person?.dog?.name ?: "None" - status += "\n${person.name}: ${person.age} : $dogName : ${person.cats.size}" + status += "\n${person.name}: ${person.age} : $dogName : ${person.cats.size}" - // The field tempReference is annotated with @Ignore - // Though we initially set its value to 42, it has - // not been saved as part of the Person RealmObject: - check(person.tempReference == 0) - } + // The field tempReference is annotated with @Ignore + // Though we initially set its value to 42, it has + // not been saved as part of the Person RealmObject: + check(person.tempReference == 0) + } - // Sorting - val sortedPersons = realm.where(Person::class.java).findAllSorted("age", Sort.DESCENDING) - status += "\nSorting ${sortedPersons.last().name} == ${realm.where(Person::class.java).findAll().first().name}" + // Sorting + val sortedPersons = realm.where(Person::class.java).findAllSorted("age", Sort.DESCENDING) + status += "\nSorting ${sortedPersons.last().name} == ${realm.where(Person::class.java).findAll().first().name}" - realm.close() + } finally { + realm.close() + } return status } From f9cf80e4dfa7cd2ed19da32643e29a71c76ed8f4 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Sat, 4 Mar 2017 00:05:15 +0900 Subject: [PATCH 0548/2110] update compileSdkVersion and targetSdkVersion to 25 (#4281) --- CHANGELOG.md | 2 ++ Dockerfile | 2 +- README.md | 2 +- examples/build.gradle | 2 +- examples/newsreaderExample/build.gradle | 4 ++-- examples/objectServerExample/build.gradle | 4 ++-- examples/secureTokenAndroidKeyStore/build.gradle | 4 ++-- realm/realm-annotations-processor/build.gradle | 2 +- realm/realm-library/build.gradle | 6 +++--- 9 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 809e19b946..88818ba4e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Enhancements +* Now `targetSdkVersion` is 25. + ### Bug Fixes ### Deprecated diff --git a/Dockerfile b/Dockerfile index 070335f930..47bcded27d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,7 +55,7 @@ RUN echo y | android update sdk --no-ui --all --filter tools > /dev/null RUN echo y | android update sdk --no-ui --all --filter platform-tools | grep 'package installed' RUN echo y | android update sdk --no-ui --all --filter build-tools-25.0.2 | grep 'package installed' RUN echo y | android update sdk --no-ui --all --filter extra-android-m2repository | grep 'package installed' -RUN echo y | android update sdk --no-ui --all --filter android-24 | grep 'package installed' +RUN echo y | android update sdk --no-ui --all --filter android-25 | grep 'package installed' # Install the NDK RUN mkdir /opt/android-ndk-tmp && \ diff --git a/README.md b/README.md index f35d59b43a..37ee3b95d1 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install the Android SDK **Build-Tools 25.0.2**, **Android N (API 24)** (for example through Android Studio’s **Android SDK Manager**). + * Download & install the Android SDK **Build-Tools 25.0.2**, **Android N (API 25)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). diff --git a/examples/build.gradle b/examples/build.gradle index 34d1f102d4..32279e957a 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,4 +1,4 @@ -project.ext.sdkVersion = 24 +project.ext.sdkVersion = 25 project.ext.buildTools = '25.0.2' // Don't cache SNAPSHOT (changing) dependencies. diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index 3809773b25..778186e266 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -34,9 +34,9 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) //noinspection GradleDependency - compile 'com.android.support:appcompat-v7:24.0.0' + compile 'com.android.support:appcompat-v7:25.2.0' //noinspection GradleDependency - compile 'com.android.support:design:24.0.0' + compile 'com.android.support:design:25.2.0' compile 'io.reactivex:rxjava:1.1.0' compile 'io.reactivex:rxandroid:1.1.0' compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 52a4c63f4a..8a42675c8a 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -60,8 +60,8 @@ realm { } dependencies { - compile 'com.android.support:support-v4:24.2.0' - compile 'com.android.support:design:24.2.0' + compile 'com.android.support:support-v4:25.2.0' + compile 'com.android.support:design:25.2.0' compile 'com.jakewharton:butterknife:8.3.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.3.0' } diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index cb4fe8b32a..b523a941c8 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -8,7 +8,7 @@ android { defaultConfig { applicationId "io.realm.examples.securetokenandroidkeystore" minSdkVersion 9 - targetSdkVersion 24 + targetSdkVersion 25 versionCode 1 versionName "1.0" @@ -28,7 +28,7 @@ dependencies { androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:24.2.0' + compile 'com.android.support:appcompat-v7:25.2.0' testCompile 'junit:junit:4.12' compile 'io.realm:android-secure-userstore:1.0.0' } diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index d4c3c94419..67d444c432 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -15,7 +15,7 @@ dependencies { testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it testCompile group:'junit', name:'junit', version:'4.12' testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' - testCompile files(file("${System.env.ANDROID_HOME}/platforms/android-24/android.jar")) + testCompile files(file("${System.env.ANDROID_HOME}/platforms/android-25/android.jar")) } // for Ant filter diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index db9a4d6ecf..b7b5097c1f 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -33,12 +33,12 @@ ext.ccachePath = project.findProperty('ccachePath') ?: System.getenv('NDK_CCACHE ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE') android { - compileSdkVersion 24 + compileSdkVersion 25 buildToolsVersion '25.0.2' defaultConfig { minSdkVersion 9 - targetSdkVersion 24 + targetSdkVersion 25 versionName version project.archivesBaseName = "realm-android-library" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -131,7 +131,7 @@ dependencies { compile 'com.getkeepsafe.relinker:relinker:1.2.2' objectServerCompile 'com.squareup.okhttp3:okhttp:3.4.1' androidTestCompile 'io.reactivex:rxjava:1.1.0' - androidTestCompile 'com.android.support:support-annotations:24.0.0' + androidTestCompile 'com.android.support:support-annotations:25.2.0' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' From 9b3362df5a7f646bd035b7978f3c6a77c6569fb9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 8 Mar 2017 13:38:12 +0100 Subject: [PATCH 0549/2110] Fail Realm.migrateRealm() if a SyncConfiguration is used. (#4292) --- CHANGELOG.md | 2 + .../io/realm/SyncedRealmMigrationTests.java | 59 +++++++++++++++++++ .../src/main/java/io/realm/BaseRealm.java | 3 +- 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 88818ba4e5..0da83498e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Bug Fixes +* `Realm.migrateRealm(RealmConfiguration)` now fails correctly with an `IllegalArgumentException` if a `SyncConfiguration` is provided (#4075). + ### Deprecated ### Internal diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java new file mode 100644 index 0000000000..28f2e3984b --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.io.FileNotFoundException; + +import io.realm.rule.TestRealmConfigurationFactory; +import io.realm.rule.TestSyncConfigurationFactory; +import io.realm.util.SyncTestUtils; + +import static org.junit.Assert.fail; + +/** + * Testing methods around migrations for Realms using a {@link SyncConfiguration}. + */ +@RunWith(AndroidJUnit4.class) +public class SyncedRealmMigrationTests { + + @Rule + public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @Test + public void migrateRealm_syncConfigurationThrows() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth").build(); + try { + Realm.migrateRealm(config); + fail(); + } catch (FileNotFoundException e) { + fail(e.toString()); + } catch (IllegalArgumentException ignored) { + } + } + +} diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 9396d46c79..14d3f6e88a 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -586,6 +586,7 @@ static boolean compactRealm(final RealmConfiguration configuration) { * @param callback callback for specific Realm type behaviors. * @param cause which triggers this migration. * @throws FileNotFoundException if the Realm file doesn't exist. + * @throws IllegalArgumentException if the provided configuration is a {@link SyncConfiguration}. */ protected static void migrateRealm(final RealmConfiguration configuration, final RealmMigration migration, final MigrationCallback callback, final RealmMigrationNeededException cause) @@ -595,7 +596,7 @@ protected static void migrateRealm(final RealmConfiguration configuration, final throw new IllegalArgumentException("RealmConfiguration must be provided"); } if (configuration.isSyncConfiguration()) { - return; + throw new IllegalArgumentException("Manual migrations are not supported for synced Realms"); } if (migration == null && configuration.getMigration() == null) { throw new RealmMigrationNeededException(configuration.getPath(), "RealmMigration must be provided", cause); From a137a819f99635962363fd481caabb2509a5a020 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 10 Mar 2017 11:54:56 +0000 Subject: [PATCH 0550/2110] Using ObjectStore SyncManager & Session (#4214) Using ObjectStore's SyncManager & Session --- CHANGELOG.md | 3 +- dependencies.list | 9 +- realm/realm-library/proguard-rules-base.pro | 2 +- .../java/io/realm/SessionTests.java | 26 +- .../java/io/realm/SyncConfigurationTests.java | 1 + .../java/io/realm/SyncManagerTests.java | 22 +- .../java/io/realm/SyncUserTests.java | 3 +- .../realm-library/src/main/cpp/CMakeLists.txt | 4 +- .../main/cpp/io_realm_RealmFileUserStore.cpp | 8 - .../src/main/cpp/io_realm_SyncManager.cpp | 41 +- .../src/main/cpp/io_realm_SyncSession.cpp | 51 ++ .../cpp/io_realm_internal_SharedRealm.cpp | 373 +++++++++----- .../main/cpp/io_realm_internal_TableQuery.cpp | 1 - .../src/main/cpp/io_realm_internal_Util.cpp | 11 +- ...ernal_objectserver_ObjectServerSession.cpp | 105 ---- .../src/main/cpp/jni_util/java_method.cpp | 23 +- .../src/main/cpp/jni_util/java_method.hpp | 4 +- .../src/main/cpp/jni_util/jni_utils.cpp | 3 + .../src/main/cpp/jni_util/jni_utils.hpp | 3 + .../src/main/cpp/jni_util/log.cpp | 1 - realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/cpp/objectserver_shared.hpp | 141 ------ realm/realm-library/src/main/cpp/util.cpp | 5 +- realm/realm-library/src/main/cpp/util.hpp | 7 +- .../src/main/java/io/realm/BaseRealm.java | 8 - .../src/main/java/io/realm/Realm.java | 2 +- .../src/main/java/io/realm/RealmCache.java | 7 +- .../io/realm/internal/ObjectServerFacade.java | 22 +- .../java/io/realm/internal/SharedRealm.java | 26 +- .../objectServer/java/io/realm/ErrorCode.java | 6 +- .../java/io/realm/RealmFileUserStore.java | 4 - .../java/io/realm/SessionState.java | 31 -- .../java/io/realm/SyncConfiguration.java | 35 +- .../java/io/realm/SyncManager.java | 121 +++-- .../java/io/realm/SyncSession.java | 254 ++++++++-- .../objectServer/java/io/realm/SyncUser.java | 11 +- .../SyncObjectServerFacade.java | 75 +-- .../network/ExponentialBackoffTask.java | 2 +- .../network/OkHttpAuthenticationServer.java | 1 - .../objectserver/AuthenticatingState.java | 115 ----- .../internal/objectserver/BindingState.java | 60 --- .../internal/objectserver/BoundState.java | 78 --- .../internal/objectserver/FsmAction.java | 35 -- .../realm/internal/objectserver/FsmState.java | 95 ---- .../internal/objectserver/InitialState.java | 47 -- .../objectserver/ObjectServerSession.java | 475 ------------------ .../objectserver/ObjectServerUser.java | 4 +- .../internal/objectserver/SessionStore.java | 81 --- .../internal/objectserver/StoppedState.java | 67 --- .../internal/objectserver/UnboundState.java | 52 -- .../syncpolicy/AutomaticSyncPolicy.java | 107 ---- .../realm/internal/syncpolicy/SyncPolicy.java | 89 ---- .../java/io/realm/objectserver/AuthTests.java | 26 +- .../realm/objectserver/utils/HttpUtils.java | 5 - 54 files changed, 792 insertions(+), 1998 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp delete mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp delete mode 100644 realm/realm-library/src/main/cpp/objectserver_shared.hpp delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/SessionState.java rename realm/realm-library/src/objectServer/java/io/realm/internal/{objectserver => }/SyncObjectServerFacade.java (59%) delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BindingState.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/InitialState.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/UnboundState.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c184a4bd82..5d909505eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Internal +* Using the Object Store's Session and SyncManager. ## 3.0.0 (2017-02-28) @@ -33,7 +34,7 @@ * Added support for sorting by link's field (#672). * Added `OrderedRealmCollectionSnapshot` class and `OrderedRealmCollection.createSnapshot()` method. `OrderedRealmCollectionSnapshot` is useful when changing `RealmResults` or `RealmList` in simple loops. -* Added `OrderedRealmCollectionChangeListener` interface for supporting fine-grained collection notifications. +* Added `OrderedRealmCollectionChangeListener` interface for supporting fine-grained collection notifications. * Added support for ChangeListeners on `RealmList`. * Added `RealmList.asObservable()`. diff --git a/dependencies.list b/dependencies.list index f22053bc89..538519aa4d 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,10 +1,13 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.2.1 -REALM_SYNC_SHA256=b796433319e2574ea3cdb1b3dcda4e2311a2080f8e1563ea68a59b2cdac1d0a7 +REALM_SYNC_VERSION=1.3.0 +REALM_SYNC_SHA256=0572417435b92e3a9f7de5bd71ed00ca5d4ed24813afeffc7e4e7b7510a9f449 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. # https://packagecloud.io/realm/realm?filter=debs # https://packagecloud.io/realm/realm-testing?filter=debs -REALM_OBJECT_SERVER_DE_VERSION=1.0.0-BETA-6.1-133 +# /tools/sync_test_server/Dockerfile specify which repo (apt) we should +# install/use between 'realm' and 'realm-testing', the version below should +# correspond to an existing version on the *specified* repo. +REALM_OBJECT_SERVER_DE_VERSION=1.2.1-270 diff --git a/realm/realm-library/proguard-rules-base.pro b/realm/realm-library/proguard-rules-base.pro index 26e41702f8..19a3b8d3ba 100644 --- a/realm/realm-library/proguard-rules-base.pro +++ b/realm/realm-library/proguard-rules-base.pro @@ -1,2 +1,2 @@ # It's OK not to exist SyncObjectServerFacade in base library. --dontnote io.realm.internal.objectserver.SyncObjectServerFacade +-dontnote io.realm.internal.SyncObjectServerFacade diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 66a072e05f..537dfb67e5 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -16,32 +16,23 @@ package io.realm; -import android.content.Context; -import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import io.realm.internal.network.AuthenticationServer; -import io.realm.internal.network.OkHttpAuthenticationServer; -import io.realm.internal.objectserver.ObjectServerSession; import io.realm.rule.TestRealmConfigurationFactory; import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; @RunWith(AndroidJUnit4.class) public class SessionTests { private static String REALM_URI = "realm://objectserver.realm.io/~/default"; - private Context context; - private AuthenticationServer authServer; private SyncConfiguration configuration; private SyncUser user; @@ -50,30 +41,15 @@ public class SessionTests { @Before public void setUp() { - context = InstrumentationRegistry.getContext(); user = createTestUser(); - authServer = new OkHttpAuthenticationServer(); configuration = new SyncConfiguration.Builder(user, REALM_URI).build(); } - @After - public void tearDown() throws Exception { - } - @Test public void get_syncValues() { - ObjectServerSession internalSession = new ObjectServerSession( - configuration, - authServer, - configuration.getUser().getSyncUser(), - configuration.getSyncPolicy(), - configuration.getErrorHandler() - ); - SyncSession session = new SyncSession(internalSession); - + SyncSession session = new SyncSession(configuration); assertEquals("realm://objectserver.realm.io/JohnDoe/default", session.getServerUrl().toString()); assertEquals(user, session.getUser()); assertEquals(configuration, session.getConfiguration()); - assertNull(session.getState()); } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 35038d786a..3b7a2a466f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -70,6 +70,7 @@ public void setUp() { @After public void tearDown() throws Exception { + SyncManager.reset(); } @Test diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 47fb8dd437..25caf4fb6a 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -24,8 +24,8 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.io.IOException; import java.util.Collection; -import java.util.Set; import io.realm.rule.TestRealmConfigurationFactory; @@ -71,20 +71,6 @@ public Collection allUsers() { }; } - @Test - public void init() { - // Realm.init() calls SyncManager.init() which will start a thread for the sync client - boolean found = false; - Set threads = Thread.getAllStackTraces().keySet(); - for (Thread thread : threads) { - if (thread.getName().equals("RealmSyncClient")) { - found = true; - break; - } - } - assertTrue(found); - } - @Test public void set_userStore() { SyncManager.setUserStore(userStore); @@ -153,14 +139,14 @@ public void loggedOut(SyncUser user) { assertEquals(0, counter[0]); assertEquals(0, counter[1]); } - @Test - public void session() { + public void session() throws IOException { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url) .build(); - + // This will trigger the creation of the session + Realm.getInstance(config); SyncSession session = SyncManager.getSession(config); assertEquals(user, session.getUser()); // see also SessionTests } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 116fab3a23..3e2c69544c 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -63,7 +63,7 @@ public static void initUserStore() { @After public void tearDown() { - RealmFileUserStore.nativeResetForTesting(); + SyncManager.reset(); } @Test @@ -89,6 +89,7 @@ public void currentUser_throwsIfMultipleUsersLoggedIn() { AuthenticationServer originalAuthServer = SyncManager.getAuthServer(); AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); SyncManager.setAuthServerImpl(authServer); + try { // 1. Login two random users when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenAnswer(new Answer() { diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 4aefd1f733..1b44bb360b 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -45,7 +45,7 @@ set(classes_LIST set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST - io.realm.SyncManager io.realm.internal.objectserver.ObjectServerSession io.realm.RealmFileUserStore) + io.realm.SyncManager io.realm.SyncSession io.realm.RealmFileUserStore) endif() create_javah(TARGET jni_headers CLASSES ${classes_LIST} @@ -154,7 +154,7 @@ file(GLOB jni_SRC if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectserver_ObjectServerSession.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFileUserStore.cpp) endif() diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 076fb54292..348693b7dc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -105,11 +105,3 @@ Java_io_realm_RealmFileUserStore_nativeGetAllUsers (JNIEnv *env, jclass) } return nullptr; } - -JNIEXPORT void JNICALL -Java_io_realm_RealmFileUserStore_nativeResetForTesting (JNIEnv *, jclass) -{ - TR_ENTER(); - SyncManager::shared().reset_for_testing(); -} - diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index fb45d1936f..a4056c74e5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -25,41 +25,60 @@ #include #include -#include "objectserver_shared.hpp" - #include "io_realm_SyncManager.h" -#include "jni_util/log.hpp" -#include "jni_util/jni_utils.hpp" -#include "sync/sync_manager.hpp" -#include "sync/sync_user.hpp" +#include "object-store/src/sync/sync_manager.hpp" + +#include "binding_callback_thread_observer.hpp" #include "util.hpp" +#include "jni_util/jni_utils.hpp" +#include "jni_util/java_method.hpp" + using namespace realm; using namespace realm::sync; using namespace realm::jni_util; std::unique_ptr sync_client; +struct AndroidClientListener : public realm::BindingCallbackThreadObserver { + + void did_create_thread() override { + Log::d("SyncClient thread created"); + // Attach the sync client thread to the JVM so errors can be returned properly + JniUtils::get_env(true); + } + + void will_destroy_thread() override { + Log::d("SyncClient thread destroyed"); + // Failing to detach the JVM before closing the thread will crash on ART + JniUtils::detach_current_thread(); + } +} s_client_thread_listener; + JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient - (JNIEnv *env, jclass) + (JNIEnv* env, jclass) { TR_ENTER() if (sync_client) return; try { + // Setup SyncManager + g_binding_callback_thread_observer = &s_client_thread_listener; + + // Create SyncClient sync::Client::Config config; config.logger = &CoreLoggerBridge::shared(); sync_client = std::make_unique(std::move(config)); // Throws } CATCH_STD() } -// Create the thread from java side to avoid some strange errors when native throws. JNIEXPORT void JNICALL -Java_io_realm_SyncManager_nativeRunClient(JNIEnv *env, jclass) -{ +Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass) { + + TR_ENTER() try { - sync_client->run(); + SyncManager::shared().reset_for_testing(); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp new file mode 100644 index 0000000000..e3a58bbad2 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 + +#include "io_realm_SyncSession.h" + +#include "object-store/src/sync/sync_manager.hpp" +#include "object-store/src/sync/sync_session.hpp" + +#include "util.hpp" + +using namespace std; +using namespace realm; +using namespace sync; + +JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JNIEnv* env, jclass, + jstring localRealmPath, + jstring accessToken, + jstring sync_realm_url) +{ + TR_ENTER() + try { + JStringAccessor local_realm_path(env, localRealmPath); + auto session = SyncManager::shared().get_existing_session(local_realm_path); + if (session) { + JStringAccessor access_token(env, accessToken); + JStringAccessor realm_url(env, sync_realm_url); + session->refresh_access_token(access_token, std::string(realm_url)); + return JNI_TRUE; + } + else { + realm::jni_util::Log::d("no active/inactive session found"); + } + } + CATCH_STD() + return JNI_FALSE; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 09e2b2e0e6..ab6f952cdd 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -15,103 +15,200 @@ */ #include "io_realm_internal_SharedRealm.h" -#ifdef REALM_ENABLE_SYNC +#if REALM_ENABLE_SYNC #include "object-store/src/sync/sync_manager.hpp" #include "object-store/src/sync/sync_config.hpp" +#include "object-store/src/sync/sync_session.hpp" #endif -#include -#include +#include #include "object_store.hpp" -#include "shared_realm.hpp" - #include "java_binding_context.hpp" #include "util.hpp" -#if REALM_ENABLE_SYNC -#include "sync/sync_manager.hpp" -#endif + +#include "jni_util/java_method.hpp" using namespace realm; using namespace realm::_impl; +using namespace realm::jni_util; static_assert(SchemaMode::Automatic == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_AUTOMATIC), ""); + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_AUTOMATIC), + ""); static_assert(SchemaMode::ReadOnly == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_READONLY), ""); + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_READONLY), + ""); static_assert(SchemaMode::ResetFile == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_RESET_FILE), ""); + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_RESET_FILE), + ""); static_assert(SchemaMode::Additive == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_ADDITIVE), ""); -static_assert(SchemaMode::Manual == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_MANUAL), ""); + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_ADDITIVE), + ""); +static_assert(SchemaMode::Manual == static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_MANUAL), + ""); static void finalize_shared_realm(jlong ptr); -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv *env, jclass, jstring temporary_directory_path) +// Wrapper class for SyncConfig. This is required as we need to keep track of the Java session +// object as part of the configuration. +class JniConfigWrapper { + +public: + JniConfigWrapper(const JniConfigWrapper&) = delete; + + JniConfigWrapper& operator=(const JniConfigWrapper&) = delete; + JniConfigWrapper(JniConfigWrapper&&) = delete; + JniConfigWrapper& operator=(JniConfigWrapper&&) = delete; + + // Non-sync constructor + JniConfigWrapper(JNIEnv*, Realm::Config& config) + : m_config(std::move(config)) + { + } + + // Sync constructor + JniConfigWrapper(REALM_UNUSED JNIEnv* env, REALM_UNUSED Realm::Config& config, + REALM_UNUSED jstring sync_realm_url, REALM_UNUSED jstring sync_realm_auth_url, + REALM_UNUSED jstring sync_user_identity, REALM_UNUSED jstring sync_refresh_token) + : m_config(std::move(config)) + { +#if REALM_ENABLE_SYNC + // Doing the methods lookup from the thread that loaded the lib, to avoid + // https://developer.android.com/training/articles/perf-jni.html#faq_FindClass + static JavaMethod java_error_callback_method(env, java_syncmanager, "notifyErrorHandler", + "(ILjava/lang/String;Ljava/lang/String;)V", true); + static JavaMethod java_bind_session_method(env, java_syncmanager, "bindSessionWithConfig", + "(Ljava/lang/String;)Ljava/lang/String;", true); + + // error handler will be called form the sync client thread + auto error_handler = [=](std::shared_ptr session, SyncError error) { + realm::jni_util::Log::d("error_handler lambda invoked"); + + JNIEnv* env = realm::jni_util::JniUtils::get_env(true); + + env->CallStaticVoidMethod(java_syncmanager, java_error_callback_method, error.error_code.value(), + to_jstring(env, error.message), to_jstring(env, session.get()->path())); + }; + + // path on disk of the Realm file. + // the sync configuration object. + // the session which should be bound. + auto bind_handler = [=](const std::string& path, const SyncConfig& syncConfig, + std::shared_ptr session) { + realm::jni_util::Log::d("Callback to Java requesting token for path"); + + JNIEnv* env = realm::jni_util::JniUtils::get_env(true); + + jstring access_token_string = (jstring)env->CallStaticObjectMethod( + java_syncmanager, java_bind_session_method, to_jstring(env, path.c_str())); + if (access_token_string) { + // reusing cached valid token + JStringAccessor access_token(env, access_token_string); + session->refresh_access_token(access_token, realm::util::Optional(syncConfig.realm_url)); + } + }; + // Get logged in user + JStringAccessor user_identity(env, sync_user_identity); + JStringAccessor realm_url(env, sync_realm_url); + std::shared_ptr user = SyncManager::shared().get_existing_logged_in_user(user_identity); + if (!user) { + JStringAccessor realm_auth_url(env, sync_realm_auth_url); + JStringAccessor refresh_token(env, sync_refresh_token); + user = SyncManager::shared().get_user(user_identity, refresh_token, + realm::util::Optional(realm_auth_url)); + } + m_config.sync_config = std::make_shared(SyncConfig{ + user, realm_url, SyncSessionStopPolicy::Immediately, std::move(bind_handler), std::move(error_handler)}); +#else + REALM_UNREACHABLE(); +#endif + } + + inline Realm::Config& get_config() + { + return m_config; + } + + ~JniConfigWrapper() + { + } + +private: + Realm::Config m_config; +}; + + +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv* env, jclass, + jstring temporary_directory_path) { TR_ENTER() try { - JStringAccessor path(env, temporary_directory_path); // throws + JStringAccessor path(env, temporary_directory_path); // throws SharedGroupOptions::set_sys_tmp_dir(std::string(path)); // throws - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstring realm_path, jbyteArray key, - jbyte schema_mode, jboolean in_memory, jboolean cache, jlong /* schema_version */, jboolean disable_format_upgrade, - jboolean auto_change_notification, REALM_UNUSED jstring sync_server_url, jstring /*sync_user_token*/) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateConfig( + JNIEnv* env, jclass, jstring realm_path, jbyteArray key, jbyte schema_mode, jboolean in_memory, jboolean cache, + jlong /* schema_version */, jboolean disable_format_upgrade, jboolean auto_change_notification, + REALM_UNUSED jstring sync_server_url, REALM_UNUSED jstring sync_server_auth_url, + REALM_UNUSED jstring sync_user_identity, REALM_UNUSED jstring sync_refresh_token) { TR_ENTER() try { JStringAccessor path(env, realm_path); // throws JniByteArray key_array(env, key); - Realm::Config *config = new Realm::Config(); - config->path = path; + Realm::Config config; + config.path = path; // config->schema_version = schema_version; TODO: Disabled until we remove version handling from Java - config->encryption_key = key_array; - config->schema_mode = static_cast(schema_mode); - config->in_memory = in_memory; - config->cache = cache; - config->disable_format_upgrade = disable_format_upgrade; - config->automatic_change_notifications = auto_change_notification; + config.encryption_key = key_array; + config.schema_mode = static_cast(schema_mode); + config.in_memory = in_memory; + config.cache = cache; + config.disable_format_upgrade = disable_format_upgrade; + config.automatic_change_notifications = auto_change_notification; if (sync_server_url) { - config->force_sync_history = true; + return reinterpret_cast(new JniConfigWrapper(env, config, sync_server_url, sync_server_auth_url, + sync_user_identity, sync_refresh_token)); + } + else { + return reinterpret_cast(new JniConfigWrapper(env, config)); } - return reinterpret_cast(config); - } CATCH_STD() + } + CATCH_STD() return static_cast(NULL); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv*, jclass, jlong config_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv*, jclass, jlong config_ptr) { TR_ENTER_PTR(config_ptr) - auto config = reinterpret_cast(config_ptr); + auto config = reinterpret_cast(config_ptr); delete config; } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr, jobject realm_notifier) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv* env, jclass, jlong config_ptr, + jobject realm_notifier) { TR_ENTER_PTR(config_ptr) - auto config = reinterpret_cast(config_ptr); + auto config = reinterpret_cast(config_ptr); try { - auto shared_realm = Realm::get_shared_realm(*config); + auto shared_realm = Realm::get_shared_realm(config->get_config()); shared_realm->m_binding_context = JavaBindingContext::create(env, realm_notifier); return reinterpret_cast(new SharedRealm(std::move(shared_realm))); - } CATCH_STD() + } + CATCH_STD() return static_cast(NULL); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -120,42 +217,45 @@ Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong shared_realm->close(); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->begin_transaction(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->commit_transaction(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->cancel_transaction(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv*, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv*, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -163,34 +263,35 @@ Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv*, jclass, jlong return static_cast(shared_realm->is_in_transaction()); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv *env, jclass , jlong shared_realm_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return reinterpret_cast(&shared_realm->read_group()); - } CATCH_STD() + } + CATCH_STD() return static_cast(NULL); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(ObjectStore::get_schema_version(shared_realm->read_group())); - } CATCH_STD() - + } + CATCH_STD() return static_cast(ObjectStore::NotVersioned); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv *env, jclass, jlong shared_realm_ptr, jlong version) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv* env, jclass, + jlong shared_realm_ptr, jlong version) { TR_ENTER_PTR(shared_realm_ptr) @@ -204,34 +305,36 @@ Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv *env, jclass, jlong s } ObjectStore::set_schema_version(shared_realm->read_group(), static_cast(version)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(ObjectStore::is_empty(shared_realm->read_group())); - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeRefresh(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRefresh(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->refresh(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlongArray JNICALL -Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -252,13 +355,13 @@ Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong env->SetLongArrayRegion(version_data, 0, 2, version_array); return version_data; - } CATCH_STD () + } + CATCH_STD() return NULL; } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -267,8 +370,8 @@ Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_ } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv* env, jclass, jlong shared_realm_ptr, + jstring table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -283,13 +386,14 @@ Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv *env, jclass, jlong sha } Table* pTable = LangBindHelper::get_or_add_table(shared_realm->read_group(), name); return reinterpret_cast(pTable); - } CATCH_STD() + } + CATCH_STD() return static_cast(NULL); } -JNIEXPORT jstring JNICALL -Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv *env, jclass, jlong shared_realm_ptr, jint index) +JNIEXPORT jstring JNICALL Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv* env, jclass, + jlong shared_realm_ptr, jint index) { TR_ENTER_PTR(shared_realm_ptr) @@ -297,12 +401,14 @@ Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv *env, jclass, jlong auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return to_jstring(env, shared_realm->read_group().get_table_name(static_cast(index))); - } CATCH_STD() + } + CATCH_STD() return NULL; } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -310,13 +416,15 @@ Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv *env, jclass, jlong sha try { JStringAccessor name(env, table_name); return static_cast(shared_realm->read_group().has_table(name)); - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv *env, jclass, jlong shared_realm_ptr, - jstring old_table_name, jstring new_table_name) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring old_table_name, + jstring new_table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -331,11 +439,13 @@ Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv *env, jclass, jlong } JStringAccessor new_name(env, new_table_name); shared_realm->read_group().rename_table(old_name, new_name); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -349,25 +459,25 @@ Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv *env, jclass, jlong return; } shared_realm->read_group().remove_table(name); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(shared_realm->read_group().size()); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring path, - jbyteArray key) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv* env, jclass, jlong shared_realm_ptr, + jstring path, jbyteArray key) { TR_ENTER_PTR(shared_realm_ptr); @@ -376,11 +486,12 @@ Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv *env, jclass, jlong sh JStringAccessor path_str(env, path); JniByteArray key_buffer(env, key); shared_realm->write_copy(path_str, key_buffer); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr); @@ -388,13 +499,14 @@ Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv *env, jclass, jlon try { using rf = realm::_impl::RealmFriend; return static_cast(rf::get_shared_group(*shared_realm).wait_for_change()); - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr); @@ -402,24 +514,26 @@ Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, try { using rf = realm::_impl::RealmFriend; rf::get_shared_group(*shared_realm).wait_for_change_release(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(shared_realm->compact()); - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -428,31 +542,34 @@ Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, using rf = realm::_impl::RealmFriend; auto& shared_group = rf::get_shared_group(*shared_realm); return LangBindHelper::get_version_of_latest_snapshot(shared_group); - } CATCH_STD () + } + CATCH_STD() return 0; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv *env, jclass, jlong shared_realm_ptr, - jlong schema_ptr, jlong version) { +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv* env, jclass, + jlong shared_realm_ptr, jlong schema_ptr, + jlong version) +{ TR_ENTER_PTR(shared_realm_ptr) try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto *schema = reinterpret_cast(schema_ptr); + auto* schema = reinterpret_cast(schema_ptr); shared_realm->update_schema(*schema, static_cast(version), nullptr, true); } CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeRequiresMigration(JNIEnv *env, jclass, jlong nativePtr, - jlong nativeSchemaPtr) { +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeRequiresMigration(JNIEnv* env, jclass, + jlong nativePtr, + jlong nativeSchemaPtr) +{ TR_ENTER() try { auto shared_realm = *(reinterpret_cast(nativePtr)); - auto *schema = reinterpret_cast(nativeSchemaPtr); - const std::vector &change_list = shared_realm->schema().compare(*schema); + auto* schema = reinterpret_cast(nativeSchemaPtr); + const std::vector& change_list = shared_realm->schema().compare(*schema); return static_cast(!change_list.empty()); } CATCH_STD() @@ -465,30 +582,32 @@ static void finalize_shared_realm(jlong ptr) delete reinterpret_cast(ptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetFinalizerPtr(JNIEnv*, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_shared_realm); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeSetAutoRefresh(JNIEnv *env, jclass, jlong shared_realm_ptr, jboolean enabled) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetAutoRefresh(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jboolean enabled) { TR_ENTER_PTR(shared_realm_ptr) try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); shared_realm->set_auto_refresh(to_bool(enabled)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsAutoRefresh(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsAutoRefresh(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); return to_jbool(shared_realm->auto_refresh()); - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 8d0fbf1cdc..e7583bf7fc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -1513,4 +1513,3 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetFinalizerPtr TR_ENTER() return reinterpret_cast(&finalize_table_query); } - diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index a3878c457a..fbd421bc9b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -16,14 +16,14 @@ #include +#include "jni_util/jni_utils.hpp" + #include #include #include "mem_usage.hpp" #include "util.hpp" -#include "jni_util/jni_utils.hpp" - using std::string; using namespace realm::jni_util; @@ -53,6 +53,10 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); java_util_date = GetClass(env, "java/util/Date"); java_util_date_init = env->GetMethodID(java_util_date, "", "(J)V"); +#if REALM_ENABLE_SYNC + java_syncmanager = GetClass(env, "io/realm/SyncManager"); +#endif + } return JNI_VERSION_1_6; @@ -70,6 +74,9 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) env->DeleteGlobalRef(java_lang_double); env->DeleteGlobalRef(java_util_date); env->DeleteGlobalRef(java_lang_string); +#if REALM_ENABLE_SYNC + env->DeleteGlobalRef(java_syncmanager); +#endif } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp deleted file mode 100644 index 39a7b559ba..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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 - -#include "io_realm_internal_objectserver_ObjectServerSession.h" -#include "objectserver_shared.hpp" -#include "util.hpp" -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace realm; -using namespace sync; - - -JNIEXPORT jlong JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeCreateSession - (JNIEnv *env, jobject obj, jstring localRealmPath) -{ - TR_ENTER() - try { - JStringAccessor local_path(env, localRealmPath); - JniSession* jni_session = new JniSession(env, local_path, obj); - return reinterpret_cast(jni_session); - } CATCH_STD() - return 0; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeBind - (JNIEnv *env, jobject, jlong sessionPointer, jstring remoteUrl, jstring accessToken) -{ - TR_ENTER() - try { - auto *session_wrapper = reinterpret_cast(sessionPointer); - - const char *token_tmp = env->GetStringUTFChars(accessToken, NULL); - std::string access_token(token_tmp); - env->ReleaseStringUTFChars(accessToken, token_tmp); - - JStringAccessor url_tmp(env, remoteUrl); // throws - StringData remote_url = StringData(url_tmp); - - // Bind the local Realm to the remote one - session_wrapper->get_session()->bind(remote_url, access_token); - } CATCH_STD() -} - - -JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeUnbind - (JNIEnv *, jobject, jlong sessionPointer) -{ - TR_ENTER() - JniSession* session = reinterpret_cast(sessionPointer); - delete session; // TODO Can we avoid killing the session here? -} - -JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeRefresh - (JNIEnv *env, jobject, jlong sessionPointer, jstring accessToken) -{ - TR_ENTER() - try { - JniSession* session_wrapper = reinterpret_cast(sessionPointer); - - JStringAccessor token_tmp(env, accessToken); // throws - StringData access_token = StringData(token_tmp); - - session_wrapper->get_session()->refresh(access_token); - } CATCH_STD() -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_objectserver_ObjectServerSession_nativeNotifyCommitHappened - (JNIEnv *env, jobject, jlong sessionPointer, jlong version) -{ - TR_ENTER() - try { - JniSession* session_wrapper = reinterpret_cast(sessionPointer); - session_wrapper->get_session()->nonsync_transact_notify(version); - } CATCH_STD() -} - - diff --git a/realm/realm-library/src/main/cpp/jni_util/java_method.cpp b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp index 61f37748cd..e28bfc184a 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_method.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp @@ -20,13 +20,19 @@ using namespace realm::jni_util; -JavaMethod::JavaMethod(JNIEnv *env, jclass cls, const char* method_name, const char* signature) +JavaMethod::JavaMethod(JNIEnv* env, jclass cls, const char* method_name, const char* signature, bool static_method) { - m_method_id = env->GetMethodID(cls, method_name, signature); + if (static_method) { + m_method_id = env->GetStaticMethodID(cls, method_name, signature); + } + else { + m_method_id = env->GetMethodID(cls, method_name, signature); + } + REALM_ASSERT_DEBUG(m_method_id != nullptr); } -JavaMethod::JavaMethod(JNIEnv *env, jobject obj, const char* method_name, const char* signature) +JavaMethod::JavaMethod(JNIEnv* env, jobject obj, const char* method_name, const char* signature) { jclass cls = env->GetObjectClass(obj); m_method_id = env->GetMethodID(cls, method_name, signature); @@ -34,10 +40,15 @@ JavaMethod::JavaMethod(JNIEnv *env, jobject obj, const char* method_name, const env->DeleteLocalRef(cls); } -JavaMethod::JavaMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature) +JavaMethod::JavaMethod(JNIEnv* env, const char* class_name, const char* method_name, const char* signature, + bool static_method) { jclass cls = env->FindClass(class_name); REALM_ASSERT_DEBUG(cls != nullptr); - m_method_id = env->GetMethodID(cls, method_name, signature); + if (static_method) { + m_method_id = env->GetStaticMethodID(cls, method_name, signature); + } + else { + m_method_id = env->GetMethodID(cls, method_name, signature); + } } - diff --git a/realm/realm-library/src/main/cpp/jni_util/java_method.hpp b/realm/realm-library/src/main/cpp/jni_util/java_method.hpp index 81c3fd64a4..d6829d9871 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_method.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_method.hpp @@ -27,9 +27,9 @@ namespace jni_util { class JavaMethod { public: JavaMethod() : m_method_id(nullptr) {} - JavaMethod(JNIEnv *env, jclass cls, const char* method_name, const char* signature); + JavaMethod(JNIEnv *env, jclass cls, const char* method_name, const char* signature, bool static_method = false); JavaMethod(JNIEnv *env, jobject obj, const char* method_name, const char* signature); - JavaMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature); + JavaMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature, bool static_method = false); JavaMethod(const JavaMethod&) = default; JavaMethod& operator=(const JavaMethod&) = default; diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp index 7db8ce14d9..64088750ca 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp @@ -46,3 +46,6 @@ JNIEnv* JniUtils::get_env(bool attach_if_needed) { return env; } +void JniUtils::detach_current_thread() { + s_instance->m_vm->DetachCurrentThread(); +} diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp index 61d8fbddfb..a8d9303786 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp @@ -32,6 +32,9 @@ class JniUtils { // When attach_if_needed is false, returns the JNIEnv if there is one attached to this thread. Assert if there is // none. When attach_if_needed is true, try to attach and return a JNIEnv if necessary. static JNIEnv* get_env(bool attach_if_needed = false); + // Detach the current thread from the JVM. Only required for C++ threads that where attached in the first place. + // Failing to do so is a resource leak. + static void detach_current_thread(); private: JniUtils(JavaVM* vm, jint vm_version) noexcept : m_vm(vm), m_vm_version(vm_version) {} diff --git a/realm/realm-library/src/main/cpp/jni_util/log.cpp b/realm/realm-library/src/main/cpp/jni_util/log.cpp index d4dfbefbc6..b22b45fd41 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.cpp @@ -17,7 +17,6 @@ #include #include "jni_util/log.hpp" -#include "util/format.hpp" using namespace realm; using namespace realm::jni_util; diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 48853a33f6..14c2c7e703 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 48853a33f61447f8d4b502660c114e5bf7076a6f +Subproject commit 14c2c7e7038850302f60f6fa1e36a22bceb3ab94 diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp deleted file mode 100644 index 2efb7c8b74..0000000000 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ -#ifndef REALM_OBJECTSERVER_SHARED_HPP -#define REALM_OBJECTSERVER_SHARED_HPP - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "util.hpp" -#include "jni_util/jni_utils.hpp" -#include "jni_util/java_global_weak_ref.hpp" -#include "jni_util/java_method.hpp" - - -// Wrapper class for realm::Session. This allows us to manage the C++ session and callback lifecycle correctly. -// TODO Use OS SyncSession instead -class JniSession { - -public: - JniSession(const JniSession&) = delete; - JniSession& operator=(const JniSession&) = delete; - JniSession(JniSession&&) = delete; - JniSession& operator=(JniSession&&) = delete; - - JniSession(JNIEnv* env, std::string local_realm_path, jobject java_session_obj) - : m_java_session_ref(std::make_shared(env, java_session_obj)) - { - extern std::unique_ptr sync_client; - // Get the coordinator for the given path, or null if there is none - m_sync_session = new realm::sync::Session(*sync_client, local_realm_path); - // error_handler could be called after JniSession destructed. So we need to pass a weak ref to lambda to avoid - // the corrupted pointer. - std::weak_ptr weak_session_ref(m_java_session_ref); - auto sync_transact_callback = [local_realm_path](realm::VersionID, realm::VersionID) { - auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator( - realm::StringData(local_realm_path)); - if (coordinator) { - coordinator->wake_up_notifier_worker(); - } - }; - auto error_handler = [weak_session_ref, local_realm_path](std::error_code error_code, bool is_fatal, const std::string message) { - if (error_code.category() != realm::sync::protocol_error_category() && - error_code.category() != realm::sync::client_error_category()) { - // FIXME: Consider below when moving to the OS sync manager. - // Ignore this error since it may cause exceptions in java ErrorCode.fromInt(). Throwing exception there - // will trigger "called with pending exception" later since the thread is created by java, and the - // endless loop is in native code. The java exception will never be thrown because of the endless loop - // will never quit to java land. - realm::jni_util::Log::e("Unhandled sync client error code %1, %2. is_fatal: %3.", - error_code.value(), error_code.message(), is_fatal); - return; - } - - // Handle client reset, without returning to Java - - // we don't have the original SyncError so we can't call SyncError#is_client_reset_requested - // we need to transform the error code to an enum, then do the check manually - using ProtocolError = realm::sync::ProtocolError; - auto protocol_error = static_cast(error_code.value()); - - // Documented here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup - if (protocol_error == ProtocolError::bad_server_file_ident - || protocol_error == ProtocolError::bad_client_file_ident - || protocol_error == ProtocolError::bad_server_version - || protocol_error == ProtocolError::diverging_histories) { - - // Add a SyncFileActionMetadata marking the Realm as needing to be deleted. - auto recovery_path = realm::util::reserve_unique_file_name( - realm::SyncManager::shared().recovery_directory_path(), - realm::util::create_timestamped_template("recovered_realm")); - auto original_path = local_realm_path; - - realm::jni_util::Log::d("A client reset is scheduled for the next app start"); - realm::SyncManager::shared().perform_metadata_update([original_path = std::move(original_path), - recovery_path = std::move(recovery_path)](const auto &manager) { - realm::SyncFileActionMetadata(manager, - realm::SyncFileActionMetadata::Action::HandleRealmForClientReset, - original_path, - "", - "", - realm::util::Optional( - std::move(recovery_path))); - }); - - } else { - auto session_ref = weak_session_ref.lock(); - if (session_ref) { - session_ref.get()->call_with_local_ref([&](JNIEnv* local_env, jobject obj) { - static realm::jni_util::JavaMethod notify_error_handler( - local_env, obj, "notifySessionError", "(ILjava/lang/String;)V"); - local_env->CallVoidMethod( - obj, notify_error_handler, error_code.value(), local_env->NewStringUTF(message.c_str())); - }); - } - } - }; - m_sync_session->set_sync_transact_callback(sync_transact_callback); - m_sync_session->set_error_handler(std::move(error_handler)); - } - - inline realm::sync::Session* get_session() const noexcept - { - return m_sync_session; - } - - ~JniSession() - { - delete m_sync_session; - } - -private: - realm::sync::Session* m_sync_session; - std::shared_ptr m_java_session_ref; -}; - -#endif // REALM_OBJECTSERVER_SHARED_HPP diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index a3df6280ae..93c6875949 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -43,8 +43,9 @@ jclass java_lang_string; jmethodID java_lang_double_init; jclass java_util_date; jmethodID java_util_date_init; -jclass session_class_ref; -jmethodID session_error_handler; +#if REALM_ENABLE_SYNC +jclass java_syncmanager; +#endif void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index d78a81fc52..06c574c3ea 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -698,10 +698,9 @@ extern jclass java_lang_string; extern jmethodID java_lang_double_init; extern jclass java_util_date; extern jmethodID java_util_date_init; - -// FIXME Move to own library -extern jclass session_class_ref; -extern jmethodID session_error_handler; +#if REALM_ENABLE_SYNC +extern jclass java_syncmanager; +#endif inline jobject NewLong(JNIEnv* env, int64_t value) { diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 14d3f6e88a..9bdadab377 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -31,7 +31,6 @@ import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; import io.realm.internal.InvalidRow; -import io.realm.internal.ObjectServerFacade; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -318,13 +317,6 @@ public void beginTransaction() { public void commitTransaction() { checkIfValid(); sharedRealm.commitTransaction(); - if (!isClosed()) { - // FIXME: The checking is because the global listener is being called in commitTransaction from object - // store. The Realm could be closed inside the listener. In this case, we have no way to handle it. Moving - // SyncManger to Object Store will solve this. - ObjectServerFacade.getFacade(configuration.isSyncConfiguration()) - .notifyCommit(configuration, sharedRealm.getLastSnapshotVersion()); - } } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index a61d98c3e1..0369321866 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -280,7 +280,7 @@ static Realm createInstance(RealmConfiguration configuration, ColumnIndices[] gl } } - static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices[] globalCacheArray) { + private static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices[] globalCacheArray) { Realm realm = new Realm(configuration); final long currentVersion = realm.getVersion(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 0fa7fbf371..de3559da42 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -26,10 +26,10 @@ import io.realm.exceptions.RealmFileException; import io.realm.internal.ColumnIndices; +import io.realm.internal.ObjectServerFacade; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.log.RealmLog; -import io.realm.internal.ObjectServerFacade; /** * To cache {@link Realm}, {@link DynamicRealm} instances and related resources. @@ -137,7 +137,6 @@ static synchronized E createRealmOrGetFromCache(RealmConfi // Creates a new local Realm instance BaseRealm realm; - if (realmClass == Realm.class) { // RealmMigrationNeededException might be thrown here. realm = Realm.createInstance(configuration, cache.typedColumnIndicesArray); @@ -172,10 +171,6 @@ static synchronized E createRealmOrGetFromCache(RealmConfi @SuppressWarnings("unchecked") E realm = (E) refAndCount.localRealm.get(); - // Notifies SyncPolicy that the Realm has been opened for the first time - if (refAndCount.globalCount == 1) { - ObjectServerFacade.getFacade(configuration.isSyncConfiguration()).realmOpened(configuration); - } return realm; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 651562b6d3..37ae3b752b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -33,7 +33,7 @@ public class ObjectServerFacade { static { //noinspection TryWithIdenticalCatches try { - Class syncFacadeClass = Class.forName("io.realm.internal.objectserver.SyncObjectServerFacade"); + Class syncFacadeClass = Class.forName("io.realm.internal.SyncObjectServerFacade"); syncFacade = (ObjectServerFacade) syncFacadeClass.newInstance(); } catch (ClassNotFoundException ignored) { } catch (InstantiationException e) { @@ -51,25 +51,13 @@ public void init(Context context) { } /** - * Notifies the session for this configuration that a local commit was made. - */ - public void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { - } - - /** - * The first instance of this Realm was opened. + * The last instance of this Realm was closed (across all Threads). */ public void realmClosed(RealmConfiguration configuration) { } - /** - * The last instance of this Realm was closed. - */ - public void realmOpened(RealmConfiguration configuration) { - } - public String[] getUserAndServerUrl(RealmConfiguration config) { - return new String[2]; + return new String[4]; } public static ObjectServerFacade getFacade(boolean needSyncFacade) { @@ -86,4 +74,8 @@ public static ObjectServerFacade getSyncFacadeIfPossible() { } return nonSyncFacade; } + + // If no session yet exists for this path. Wrap a new Java Session around an existing OS one. + public void wrapObjectStoreSessionIfRequired(RealmConfiguration config) { + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 8648224ee4..194d22ba27 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -109,7 +109,6 @@ public byte getNativeValue() { // JNI will only hold a weak global ref to this. public final RealmNotifier realmNotifier; - public final ObjectServerFacade objectServerFacade; public final List> collections = new CopyOnWriteArrayList>(); public final Capabilities capabilities; public final List> iterators = @@ -193,7 +192,6 @@ private SharedRealm(long nativeConfigPtr, context = new Context(); context.addReference(this); this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion(); - objectServerFacade = null; nativeSetAutoRefresh(nativePtr, capabilities.canDeliverNotification()); } @@ -207,25 +205,31 @@ public static SharedRealm getInstance(RealmConfiguration config) { public static SharedRealm getInstance(RealmConfiguration config, SchemaVersionListener schemaVersionListener, boolean autoChangeNotifications) { - String[] userAndServer = ObjectServerFacade.getSyncFacadeIfPossible().getUserAndServerUrl(config); - String rosServerUrl = userAndServer[0]; - String rosUserToken = userAndServer[1]; + String[] syncUserConf = ObjectServerFacade.getSyncFacadeIfPossible().getUserAndServerUrl(config); + String syncUserIdentifier = syncUserConf[0]; + String syncRealmUrl = syncUserConf[1]; + String syncRealmAuthUrl = syncUserConf[2]; + String syncRefreshToken = syncUserConf[3]; boolean enable_caching = false; // Handled in Java currently boolean disableFormatUpgrade = false; // TODO Double negatives :/ long nativeConfigPtr = nativeCreateConfig( config.getPath(), config.getEncryptionKey(), - rosServerUrl != null ? SchemaMode.SCHEMA_MODE_ADDITIVE.getNativeValue() : SchemaMode.SCHEMA_MODE_MANUAL.getNativeValue(), + syncRealmUrl != null ? SchemaMode.SCHEMA_MODE_ADDITIVE.getNativeValue() : SchemaMode.SCHEMA_MODE_MANUAL.getNativeValue(), config.getDurability() == Durability.MEM_ONLY, enable_caching, config.getSchemaVersion(), disableFormatUpgrade, autoChangeNotifications, - rosServerUrl, - rosUserToken); + syncRealmUrl, + syncRealmAuthUrl, + syncUserIdentifier, + syncRefreshToken); try { + ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(config); + return new SharedRealm(nativeConfigPtr, config, schemaVersionListener); } finally { nativeCloseConfig(nativeConfigPtr); @@ -422,10 +426,14 @@ void invalidateIterators() { } private static native void nativeInit(String temporaryDirectoryPath); + // Keep last session as an 'object' to avoid any reference to sync code private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory, boolean cache, long schemaVersion, boolean disableFormatUpgrade, boolean autoChangeNotification, - String syncServerURL, String syncUserToken); + String syncServerURL, + String syncServerAuthURL, + String syncUserIdentity, + String syncRefreshToken); private static native void nativeCloseConfig(long nativeConfigPtr); private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier); private static native void nativeCloseSharedRealm(long nativeSharedRealmPtr); diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 4c443e314f..191bf89c78 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -104,11 +104,9 @@ public int intValue() { * Errors come in 2 categories: FATAL, RECOVERABLE *

            * FATAL: The session cannot be recovered and needs to be re-created. A likely cause is that the User does not - * have access to this Realm. Check that the {@link SyncConfiguration} is correct. Any fatal error will cause - * the session to be become {@link SessionState#STOPPED}. + * have access to this Realm. Check that the {@link SyncConfiguration} is correct. *

            - * RECOVERABLE: Temporary error. The session becomes {@link SessionState#UNBOUND}, but will automatically try to - * recover as soon as possible. + * RECOVERABLE: Temporary error. The session will automatically try to recover as soon as possible. *

            * * @return the severity of the error. diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java index 61a3ad24c5..e208131397 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java @@ -95,8 +95,4 @@ private static SyncUser toSyncUserOrNull(String userJson) { protected static native void nativeUpdateOrCreateUser(String identity, String jsonToken, String url); protected static native void nativeLogoutUser(String identity); - - // Should only be called for tests - static native void nativeResetForTesting(); - } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SessionState.java b/realm/realm-library/src/objectServer/java/io/realm/SessionState.java deleted file mode 100644 index d3e167eae7..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/SessionState.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -/** - * Enum describing the various states the Session Finite-State-Machine can be in. - */ -public enum SessionState { - INITIAL, // Initial starting state - UNBOUND, // Start done, Realm is unbound. - BINDING, // bind() has been called. Can take a while. - AUTHENTICATING, // Trying to authenticate credentials. Can take a while. - BOUND, // Local realm was successfully bound to the remote Realm. Changes are being synchronized. - STOPPED // Terminal state. Session can no longer be used. -} - - diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index baa076c0f8..9590f702f2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -34,8 +34,6 @@ import io.realm.exceptions.RealmException; import io.realm.internal.RealmProxyMediator; import io.realm.internal.SharedRealm; -import io.realm.internal.syncpolicy.AutomaticSyncPolicy; -import io.realm.internal.syncpolicy.SyncPolicy; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; @@ -73,13 +71,12 @@ public class SyncConfiguration extends RealmConfiguration { // The FAT file system has limitations of length. Also, not all characters are permitted. // https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx - public static final int MAX_FULL_PATH_LENGTH = 256; - public static final int MAX_FILE_NAME_LENGTH = 255; + static final int MAX_FULL_PATH_LENGTH = 256; + static final int MAX_FILE_NAME_LENGTH = 255; private static final char[] INVALID_CHARS = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'}; private final URI serverUrl; private final SyncUser user; - private final SyncPolicy syncPolicy; private final SyncSession.ErrorHandler errorHandler; private final boolean deleteRealmOnLogout; @@ -97,7 +94,6 @@ private SyncConfiguration(File directory, Realm.Transaction initialDataTransaction, SyncUser user, URI serverUrl, - SyncPolicy syncPolicy, SyncSession.ErrorHandler errorHandler, boolean deleteRealmOnLogout ) { @@ -117,7 +113,6 @@ private SyncConfiguration(File directory, this.user = user; this.serverUrl = serverUrl; - this.syncPolicy = syncPolicy; this.errorHandler = errorHandler; this.deleteRealmOnLogout = deleteRealmOnLogout; } @@ -154,7 +149,6 @@ public boolean equals(Object o) { if (deleteRealmOnLogout != that.deleteRealmOnLogout) return false; if (!serverUrl.equals(that.serverUrl)) return false; if (!user.equals(that.user)) return false; - if (!syncPolicy.equals(that.syncPolicy)) return false; if (!errorHandler.equals(that.errorHandler)) return false; return true; } @@ -165,7 +159,6 @@ public int hashCode() { result = 31 * result + serverUrl.hashCode(); result = 31 * result + user.hashCode(); result = 31 * result + (deleteRealmOnLogout ? 1 : 0); - result = 31 * result + syncPolicy.hashCode(); result = 31 * result + errorHandler.hashCode(); return result; } @@ -178,19 +171,12 @@ public String toString() { stringBuilder.append("\n"); stringBuilder.append("user: " + user); stringBuilder.append("\n"); - stringBuilder.append("syncPolicy: " + syncPolicy); - stringBuilder.append("\n"); stringBuilder.append("errorHandler: " + errorHandler); stringBuilder.append("\n"); stringBuilder.append("deleteRealmOnLogout: " + deleteRealmOnLogout); return stringBuilder.toString(); } - // Keeping this package protected for now. The API might still be subject to change. - SyncPolicy getSyncPolicy() { - return syncPolicy; - } - /** * Returns the user. * @@ -246,7 +232,6 @@ public static final class Builder { private Realm.Transaction initialDataTransaction; private URI serverUrl; private SyncUser user = null; - private SyncPolicy syncPolicy = new AutomaticSyncPolicy(); private SyncSession.ErrorHandler errorHandler = SyncManager.defaultSessionErrorHandler; private File defaultFolder; private String defaultLocalFileName; @@ -254,7 +239,6 @@ public static final class Builder { private boolean deleteRealmOnLogout = false; private final Pattern pattern = Pattern.compile("^[A-Za-z0-9_\\-\\.]+$"); // for checking serverUrl - /** * Creates an instance of the Builder for the SyncConfiguration. *

            @@ -556,20 +540,6 @@ public Builder inMemory() { return this; } - /** - * Sets the {@link SyncPolicy} used to control when changes should be synchronized with the remote Realm. - * The default policy is {@link AutomaticSyncPolicy}. - * - * @param syncPolicy policy to use. - * - * @see SyncSession - */ - Builder syncPolicy(SyncPolicy syncPolicy) { - // Package protected until SyncPolicy API is more stable. - this.syncPolicy = syncPolicy; - return this; - } - /** * Sets the error handler used by this configuration. This will override any handler set by calling * {@link SyncManager#setDefaultSessionErrorHandler(SyncSession.ErrorHandler)}. @@ -698,7 +668,6 @@ public SyncConfiguration build() { // Sync Configuration specific user, resolvedServerUrl, - syncPolicy, errorHandler, deleteRealmOnLogout ); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 10a2e1619c..ce463ff77a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -16,6 +16,8 @@ package io.realm; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadPoolExecutor; @@ -25,8 +27,6 @@ import io.realm.internal.Keep; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.OkHttpAuthenticationServer; -import io.realm.internal.objectserver.SessionStore; -import io.realm.internal.objectserver.ObjectServerSession; import io.realm.log.RealmLog; /** @@ -63,7 +63,7 @@ public static class Debug { // Thread pool used when doing network requests against the Realm Authentication Server. // FIXME Set proper parameters - public static final ThreadPoolExecutor NETWORK_POOL_EXECUTOR = new ThreadPoolExecutor( + static final ThreadPoolExecutor NETWORK_POOL_EXECUTOR = new ThreadPoolExecutor( 10, 10, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(100)); private static final SyncSession.ErrorHandler SESSION_NO_OP_ERROR_HANDLER = new SyncSession.ErrorHandler() { @@ -84,7 +84,8 @@ public void onError(SyncSession session, ObjectServerError error) { } } }; - + // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path' + private static Map sessions = new HashMap(); private static CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList(); // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. @@ -93,27 +94,14 @@ public void onError(SyncSession session, ObjectServerError error) { private static volatile UserStore userStore; static volatile SyncSession.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; - @SuppressWarnings("FieldCanBeLocal") - private static Thread clientThread; // Initialize the SyncManager static void init(String appId, UserStore userStore) { - SyncManager.APP_ID = appId; SyncManager.userStore = userStore; // Initialize underlying Sync Network Client nativeInitializeSyncClient(); - - // Create the client thread in java to avoid problems when exceptions are being thrown. We need to attach - // any thread to the JVM anyway in order to send back log events. - SyncManager.clientThread = new Thread(new Runnable() { - @Override - public void run() { - nativeRunClient(); - } - }, "RealmSyncClient"); - SyncManager.clientThread.start(); } /** @@ -178,29 +166,40 @@ public static void setDefaultSessionErrorHandler(SyncSession.ErrorHandler errorH * @throws IllegalArgumentException if syncConfiguration is {@code null}. */ public static synchronized SyncSession getSession(SyncConfiguration syncConfiguration) { + // This will not create a new native (Object Store) session, this will only associate a Realm's path + // with a SyncSession. Object Store's SyncManager is responsible of the life cycle (including creation) + // of the native session, the provided Java wrap, helps interact with the native session, when reporting error + // or requesting an access_token for example. + if (syncConfiguration == null) { throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); } - if (SessionStore.hasSession(syncConfiguration)) { - return SessionStore.getPublicSession(syncConfiguration); - } else { - ObjectServerSession internalSession = new ObjectServerSession( - syncConfiguration, - authServer, - syncConfiguration.getUser().getSyncUser(), - syncConfiguration.getSyncPolicy(), - syncConfiguration.getErrorHandler() - ); - SyncSession publicSession = new SyncSession(internalSession); - SessionStore.addSession(publicSession, internalSession); - syncConfiguration.getUser().getSyncUser().addSession(publicSession); - syncConfiguration.getSyncPolicy().onSessionCreated(internalSession); - return publicSession; + SyncSession session = sessions.get(syncConfiguration.getPath()); + if (session == null) { + session = new SyncSession(syncConfiguration); + sessions.put(syncConfiguration.getPath(), session); + } + + return session; + } + + /** + * Remove the wrapped Java session. + * @param syncConfiguration configuration object for the synchronized Realm. + */ + @SuppressWarnings("unused") + private static synchronized void removeSession(SyncConfiguration syncConfiguration) { + if (syncConfiguration == null) { + throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); + } + SyncSession syncSession = sessions.remove(syncConfiguration.getPath()); + if (syncSession != null) { + syncSession.close(); } } - public static AuthenticationServer getAuthServer() { + static AuthenticationServer getAuthServer() { return authServer; } @@ -230,8 +229,62 @@ static void notifyUserLoggedOut(SyncUser user) { } } + /** + * All errors from native Sync is reported to this method. From the path we can determine which + * session to contact. If {@code path == null} all sessions are effected. + */ + @SuppressWarnings("unused") + private static synchronized void notifyErrorHandler(int errorCode, String errorMessage, String path) { + for (SyncSession syncSession : sessions.values()) { + if (path == null || path.equals(syncSession.getConfiguration().getPath())) { + try { + syncSession.notifySessionError(errorCode, errorMessage); + } catch (Exception exception) { + RealmLog.error(exception); + } + } + } + } + + /** + * This is called from the Object Store (through JNI) to request an {@code access_token} for + * the session specified by sessionPath. + * + * This will also schedule a timer to proactively refresh the {@code access_token} regularly, before + * the {@code access_token} expires. + * + * @throws IllegalStateException if the wrapped Java session is not found. + * @param sessionPath The path to the previously Java wraped session. + * @return a valid cached {@code access_token} if available or null. + */ + @SuppressWarnings("unused") + private synchronized static String bindSessionWithConfig(String sessionPath) { + final SyncSession syncSession = sessions.get(sessionPath); + if (syncSession == null) { + RealmLog.error("Matching Java SyncSession could not be found for: " + sessionPath); + } else { + try { + return syncSession.accessToken(authServer); + } catch (Exception exception) { + RealmLog.error(exception); + } + } + return null; + } + + /** + * Resets the SyncManger and clear all existing users. + * This will also terminate all sessions. + * + * Only call this method when testing. + */ + static synchronized void reset() { + nativeReset(); + sessions.clear(); + } + private static native void nativeInitializeSyncClient(); - private static native void nativeRunClient(); // init and load the Metadata Realm containing SyncUsers protected static native void nativeConfigureMetaDataSystem(String baseFile); + private static native void nativeReset(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index ad5ff24e39..1572c34abc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -17,33 +17,52 @@ package io.realm; import java.net.URI; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import io.realm.internal.Keep; +import io.realm.internal.KeepMember; +import io.realm.internal.SyncObjectServerFacade; +import io.realm.internal.async.RealmAsyncTaskImpl; +import io.realm.internal.network.AuthenticateResponse; +import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.ExponentialBackoffTask; +import io.realm.internal.network.NetworkStateReceiver; +import io.realm.internal.objectserver.ObjectServerUser; +import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; -import io.realm.internal.objectserver.ObjectServerSession; /** * This class represents the connection to the Realm Object Server for one {@link SyncConfiguration}. *

            - * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening - * a Realm instance using that configuration. Once a session has been created, it will continue to exist until the app - * is closed or the {@link SyncConfiguration} is no longer used. + * A Session is created by opening a Realm instance using that configuration. Once a session has been created, + * it will continue to exist until the app is closed or all threads using this {@link SyncConfiguration} closes their respective {@link Realm}s. *

            * A session is fully controlled by Realm, but can provide additional information in case of errors. * It is passed along in all {@link SyncSession.ErrorHandler}s. *

            * This object is thread safe. - * - * @see SessionState */ @Keep public class SyncSession { + private final static ScheduledThreadPoolExecutor REFRESH_TOKENS_EXECUTOR = new ScheduledThreadPoolExecutor(1); + private final static long REFRESH_MARGIN_DELAY = TimeUnit.SECONDS.toMillis(10); - private final ObjectServerSession osSession; + private final SyncConfiguration configuration; + private final ErrorHandler errorHandler; + private RealmAsyncTask networkRequest; + private NetworkStateReceiver.ConnectionListener networkListener; + private RealmAsyncTask refreshTokenTask; + private RealmAsyncTask refreshTokenNetworkRequest; + private AtomicBoolean onGoingAccessTokenQuery = new AtomicBoolean(false); + private volatile boolean isClosed = false; - SyncSession(ObjectServerSession osSession) { - this.osSession = osSession; - osSession.setUserSession(this); + SyncSession(SyncConfiguration configuration) { + this.configuration = configuration; + this.errorHandler = configuration.getErrorHandler(); } /** @@ -52,7 +71,7 @@ public class SyncSession { * @return SyncConfiguration that defines and controls this session. */ public SyncConfiguration getConfiguration() { - return osSession.getConfiguration(); + return configuration; } /** @@ -62,7 +81,7 @@ public SyncConfiguration getConfiguration() { * @return {@link SyncUser} used to authenticate the session on the Realm Object Server. */ public SyncUser getUser() { - return osSession.getConfiguration().getUser(); + return configuration.getUser(); } /** @@ -71,29 +90,24 @@ public SyncUser getUser() { * @return {@link URI} describing the remote Realm. */ public URI getServerUrl() { - return osSession.getConfiguration().getServerUrl(); + return configuration.getServerUrl(); } - /** - * Returns the state of this session. - * - * @return the current {@link SessionState} for this session. - */ - public SessionState getState() { - return osSession.getState(); - } - - ObjectServerSession getOsSession() { - return osSession; + // This callback will happen on the thread running the Sync Client. + @KeepMember + void notifySessionError(int errorCode, String errorMessage) { + ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); + if (errorHandler != null) { + errorHandler.onError(this, error); + } } - @Override - protected void finalize() throws Throwable { - super.finalize(); - if (osSession.getState() != SessionState.STOPPED) { - RealmLog.warn("Session was not closed before being finalized. This is a potential resource leak."); - osSession.stop(); + void close() { + isClosed = true; + if (networkRequest != null) { + networkRequest.cancel(); } + clearScheduledAccessTokenRefresh(); } /** @@ -114,5 +128,185 @@ public interface ErrorHandler { */ void onError(SyncSession session, ObjectServerError error); } + + String accessToken(final AuthenticationServer authServer) { + // check first if there's a valid access_token we can return immediately + if (getUser().getSyncUser().isAuthenticated(configuration)) { + Token accessToken = getUser().getSyncUser().getAccessToken(configuration.getServerUrl()); + // start refreshing this token if a refresh is not going on + if (!onGoingAccessTokenQuery.getAndSet(true)) { + scheduleRefreshAccessToken(authServer, accessToken.expiresMs()); + } + return accessToken.value(); + + } else { + if (!onGoingAccessTokenQuery.getAndSet(true)) { + if (NetworkStateReceiver.isOnline(SyncObjectServerFacade.getApplicationContext())) { + authenticateRealm(authServer); + + } else { + // Wait for connection to become available, before trying again. + // The Session might potentially stay in this state for the lifetime of the application. + // This is acceptable. + networkListener = new NetworkStateReceiver.ConnectionListener() { + @Override + public void onChange(boolean connectionAvailable) { + if (connectionAvailable) { + if (!onGoingAccessTokenQuery.getAndSet(true)) { + authenticateRealm(authServer); + } + NetworkStateReceiver.removeListener(this); + } + } + }; + NetworkStateReceiver.addListener(networkListener); + } + } + } + return null; + } + + // Authenticate by getting access tokens for the specific Realm + private void authenticateRealm(final AuthenticationServer authServer) { + if (networkRequest != null) { + networkRequest.cancel(); + } + clearScheduledAccessTokenRefresh(); + + // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { + @Override + protected AuthenticateResponse execute() { + if (!isClosed && !Thread.currentThread().isInterrupted()) { + return authServer.loginToRealm( + getUser().getAccessToken(),//refresh token in fact + configuration.getServerUrl(), + getUser().getSyncUser().getAuthenticationUrl() + ); + } + return null; + } + + @Override + protected void onSuccess(AuthenticateResponse response) { + RealmLog.debug("Session[%s]: Access token acquired", configuration.getPath()); + if (!isClosed && !Thread.currentThread().isInterrupted()) { + ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription( + response.getAccessToken(), + configuration.getPath(), + configuration.shouldDeleteRealmOnLogout() + ); + getUser().getSyncUser().addRealm(configuration.getServerUrl(), desc); + // schedule a token refresh before it expires + if (nativeRefreshAccessToken(configuration.getPath(), getUser().getSyncUser().getAccessToken(configuration.getServerUrl()).value(), configuration.getServerUrl().toString())) { + scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); + + } else { + // token not applied, no refresh will be scheduled + onGoingAccessTokenQuery.set(false); + } + } + } + + @Override + protected void onError(AuthenticateResponse response) { + onGoingAccessTokenQuery.set(false); + RealmLog.debug("Session[%s]: Failed to get access token (%d)", configuration.getPath(), response.getError().getErrorCode()); + if (!isClosed && !Thread.currentThread().isInterrupted()) { + errorHandler.onError(SyncSession.this, response.getError()); + } + } + }); + networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); + } + + private void scheduleRefreshAccessToken(final AuthenticationServer authServer, long expireDateInMs) { + // calculate the delay time before which we should refresh the access_token, + // we adjust to 10 second to proactively refresh the access_token before the session + // hit the expire date on the token + long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; + if (refreshAfter < 0) { + // Token already expired + RealmLog.debug("Expires time already reached for the access token, refresh as soon as possible"); + // we avoid refreshing directly to avoid an edge case where the client clock is ahead + // of the server, causing all access_token received from the server to be always + // expired, we will flood the server with refresh token requests then, so adding + // a bit of delay is the best effort in this case. + refreshAfter = REFRESH_MARGIN_DELAY; + } + + RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); + + if (refreshTokenTask != null) { + refreshTokenTask.cancel(); + } + + ScheduledFuture task = REFRESH_TOKENS_EXECUTOR.schedule(new Runnable() { + @Override + public void run() { + if (!isClosed && !Thread.currentThread().isInterrupted()) { + refreshAccessToken(authServer); + } + } + }, refreshAfter, TimeUnit.MILLISECONDS); + refreshTokenTask = new RealmAsyncTaskImpl(task, REFRESH_TOKENS_EXECUTOR); + } + + // Authenticate by getting access tokens for the specific Realm + private void refreshAccessToken(final AuthenticationServer authServer) { + // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. + clearScheduledAccessTokenRefresh(); + + Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { + @Override + protected AuthenticateResponse execute() { + if (!isClosed && !Thread.currentThread().isInterrupted()) { + return authServer.refreshUser(getUser().getSyncUser().getUserToken(), configuration.getServerUrl(), getUser().getSyncUser().getAuthenticationUrl()); + } + return null; + } + + @Override + protected void onSuccess(AuthenticateResponse response) { + synchronized (SyncSession.this) { + if (!isClosed && !Thread.currentThread().isInterrupted()) { + RealmLog.debug("Access Token refreshed successfully, Sync URL: " + configuration.getServerUrl()); + if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), configuration.getUser().getAuthenticationUrl().toString())) { + // replaced the user old access_token + ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription( + response.getAccessToken(), + configuration.getPath(), + configuration.shouldDeleteRealmOnLogout() + ); + getUser().getSyncUser().addRealm(configuration.getServerUrl(), desc); + + // schedule the next refresh + scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); + } + } + } + } + + @Override + protected void onError(AuthenticateResponse response) { + if (!isClosed && !Thread.currentThread().isInterrupted()) { + onGoingAccessTokenQuery.set(false); + RealmLog.error("Unrecoverable error, while refreshing the access Token (" + response.getError().toString() + ") reschedule will not happen"); + } + } + }); + refreshTokenNetworkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); + } + + private void clearScheduledAccessTokenRefresh() { + if (refreshTokenTask != null) { + refreshTokenTask.cancel(); + } + if (refreshTokenNetworkRequest != null) { + refreshTokenNetworkRequest.cancel(); + } + } + + private static native boolean nativeRefreshAccessToken(String path, String accessToken, String authURL); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index eac47154eb..f0406cf086 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -293,13 +293,6 @@ public void logout() { } } - // Stop all active sessions immediately. If we waited until after talking to the server - // there is a high chance errors would be reported from the Sync Client first which would - // be confusing. - for (SyncSession session : sessions) { - session.getOsSession().stop(); - } - SyncManager.getUserStore().remove(syncUser.getIdentity()); // Delete all Realms if needed. @@ -390,9 +383,9 @@ public String getIdentity() { * * @return the user's access token. If this user has logged out or the login has expired {@code null} is returned. */ - public String getAccessToken() { + public Token getAccessToken() { Token userToken = syncUser.getUserToken(); - return (userToken != null) ? userToken.value() : null; + return (userToken != null) ? userToken : null; } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java similarity index 59% rename from realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 63e18261d4..c976c0bc08 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.internal.objectserver; +package io.realm.internal; import android.annotation.SuppressLint; import android.content.Context; @@ -25,12 +25,9 @@ import java.lang.reflect.Method; import io.realm.RealmConfiguration; -import io.realm.SyncSession; import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.exceptions.RealmException; -import io.realm.internal.Keep; -import io.realm.internal.ObjectServerFacade; import io.realm.internal.network.NetworkStateReceiver; @SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @@ -41,6 +38,7 @@ public class SyncObjectServerFacade extends ObjectServerFacade { "'configuration' has to be an instance of 'SyncConfiguration'."; @SuppressLint("StaticFieldLeak") // private static Context applicationContext; + private static volatile Method removeSessionMethod; @Override public void init(Context context) { @@ -71,51 +69,64 @@ public void init(Context context) { } @Override - public void notifyCommit(RealmConfiguration configuration, long lastSnapshotVersion) { + public void realmClosed(RealmConfiguration configuration) { + // Last Thread using the specified configuration is closed + // delete the wrapped Java session if (configuration instanceof SyncConfiguration) { - SyncSession publicSession = SyncManager.getSession((SyncConfiguration) configuration); - ObjectServerSession session = SessionStore.getPrivateSession(publicSession); - session.notifyCommit(lastSnapshotVersion); + SyncConfiguration syncConfig = (SyncConfiguration) configuration; + invokeRemoveSession(syncConfig); } else { throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); } } @Override - public void realmClosed(RealmConfiguration configuration) { - if (configuration instanceof SyncConfiguration) { - SyncSession publicSession = SyncManager.getSession((SyncConfiguration) configuration); - ObjectServerSession session = SessionStore.getPrivateSession(publicSession); - session.getSyncPolicy().onRealmClosed(session); + public String[] getUserAndServerUrl(RealmConfiguration config) { + if (config instanceof SyncConfiguration) { + SyncConfiguration syncConfig = (SyncConfiguration) config; + String rosServerUrl = syncConfig.getServerUrl().toString(); + String rosUserIdentity = syncConfig.getUser().getIdentity(); + String syncRealmAuthUrl = syncConfig.getUser().getAuthenticationUrl().toString(); + String rosRefreshToken = syncConfig.getUser().getAccessToken().value(); + return new String[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosRefreshToken}; } else { - throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); + return new String[4]; } } - @Override - public void realmOpened(RealmConfiguration configuration) { - if (configuration instanceof SyncConfiguration) { - SyncSession publicSession = SyncManager.getSession((SyncConfiguration) configuration); - ObjectServerSession session = SessionStore.getPrivateSession(publicSession); - session.getSyncPolicy().onRealmOpened(session); - } else { - throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); - } + public static Context getApplicationContext() { + return applicationContext; } @Override - public String[] getUserAndServerUrl(RealmConfiguration config) { + public void wrapObjectStoreSessionIfRequired(RealmConfiguration config) { if (config instanceof SyncConfiguration) { - SyncConfiguration syncConfig = (SyncConfiguration) config; - String rosServerUrl = syncConfig.getServerUrl().toString(); - String rosUserToken = syncConfig.getUser().getAccessToken(); - return new String[]{rosServerUrl, rosUserToken}; - } else { - return new String[2]; + SyncManager.getSession((SyncConfiguration) config); } } - static Context getApplicationContext() { - return applicationContext; + //FIXME remove this reflection call once we redesign the SyncManager to separate interface + // from implementation to avoid issue like exposing internal method like SyncManager#removeSession + // or SyncSession#close. This happens because SyncObjectServerFacade is internal, whereas + // SyncManager#removeSession or SyncSession#close are package private & should not be public. + private void invokeRemoveSession(SyncConfiguration syncConfig) { + try { + if (removeSessionMethod == null) { + synchronized (SyncObjectServerFacade.class) { + if (removeSessionMethod == null) { + Method removeSession = SyncManager.class.getDeclaredMethod("removeSession", SyncConfiguration.class); + removeSession.setAccessible(true); + removeSessionMethod = removeSession; + } + } + } + removeSessionMethod.invoke(null, syncConfig); + } catch (NoSuchMethodException e) { + throw new RealmException("Could not lookup method to remove session: " + syncConfig.toString(), e); + } catch (InvocationTargetException e) { + throw new RealmException("Could not invoke method to remove session: " + syncConfig.toString(), e); + } catch (IllegalAccessException e) { + throw new RealmException("Could not remove session: " + syncConfig.toString(), e); + } } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java index ab53fff060..1e428dd190 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java @@ -30,7 +30,7 @@ public abstract class ExponentialBackoffTask imple // Check if the task was successful protected boolean isSuccess(T result) { - return result.isValid(); + return result != null && result.isValid(); } // Return true if based on the task result that this task will never complete diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index b4bbb18cde..40148acb8a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -106,7 +106,6 @@ private AuthenticateResponse authenticate(URL authenticationUrl, String requestB .addHeader("Accept", "application/json") .post(RequestBody.create(JSON, requestBody)) .build(); - RealmLog.debug("Authenticate: " + requestBody); Call call = client.newCall(request); Response response = call.execute(); return AuthenticateResponse.from(response); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java deleted file mode 100644 index 9ca21ecf9e..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/AuthenticatingState.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import io.realm.ObjectServerError; -import io.realm.SyncSession; -import io.realm.SessionState; -import io.realm.internal.network.NetworkStateReceiver; -import io.realm.log.RealmLog; - -/** - * AUTHENTICATING State. This step is needed if the user does not have proper access or credentials to access the - * Realm when attempting to bind it. Reasons for not having proper access or invalid credentials include: - * - *

              - *
            1. - * Refresh token has expired: - * This effectively means the user has been logged out from the Realm Object Server and credentials have - * to be re-verified by the Authentication Server. Since verification involves creating a new User object, - * this session will be stopped and an error reported. - *
            2. - *
            3. - * Access token has expired: - * In this case, the token is automatically refreshed and will retry binding the Realm. - *
            4. - *
            5. - * Access token does not exist: - * This state means the user has logged in, but not yet gained a specific access token for the Realm. - * The access token will automatically be fetched and binding the Realm is retried. - *
            6. - *
            - */ -class AuthenticatingState extends FsmState { - - @Override - public void onEnterState() { - if (NetworkStateReceiver.isOnline(SyncObjectServerFacade.getApplicationContext())) { - authenticate(session); - } else { - // Wait for connection to become available, before trying again. - // The Session might potentially stay in this state for the lifetime of the application. - // This is acceptable. - session.networkListener = new NetworkStateReceiver.ConnectionListener() { - @Override - public void onChange(boolean connectionAvailable) { - if (connectionAvailable) { - authenticate(session); - NetworkStateReceiver.removeListener(this); - } - } - }; - NetworkStateReceiver.addListener(session.networkListener); - } - } - - @Override - public void onExitState() { - // Abort any current network request. - if (session.networkRequest != null) { - session.networkRequest.cancel(); - session.networkRequest = null; - } - - // Release listener if we were waiting for network to become available. - if (session.networkListener != null) { - NetworkStateReceiver.removeListener(session.networkListener); - session.networkListener = null; - } - } - - @Override - public void onBind() { - gotoNextState(SessionState.BINDING); // Equivalent to forcing a retry - } - - @Override - public void onUnbind() { - gotoNextState(SessionState.UNBOUND); // Treat this as user wanting to exit a binding in progress. - } - - @Override - public void onStop() { - gotoNextState(SessionState.STOPPED); - } - - private synchronized void authenticate(final ObjectServerSession session) { - session.authenticateRealm(new Runnable() { - @Override - public void run() { - RealmLog.debug("Session[%s]: Access token acquired", session.getConfiguration().getPath()); - gotoNextState(SessionState.BINDING); - } - }, new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession s, ObjectServerError error) { - RealmLog.debug("Session[%s]: Failed to get access token (%d)", session.getConfiguration().getPath(), error.getErrorCode()); - session.onError(error); - } - }); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BindingState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BindingState.java deleted file mode 100644 index fbd3a14f5b..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BindingState.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import io.realm.ObjectServerError; -import io.realm.SessionState; - -/** - * BINDING State. After {@code bind()} is called, the state will attempt to bind the local Realm to the remote. This is an - * asynchronous operation which must be interruptible. - */ -class BindingState extends FsmState { - - @Override - public void onEnterState() { - if (session.isAuthenticated(session.configuration)) { - // FIXME How to handle errors? - session.bindWithTokens(); - gotoNextState(SessionState.BOUND); - } else { - // Not access token available. We need to authenticateUser first. - gotoNextState(SessionState.AUTHENTICATING); - } - } - - @Override - public void onExitState() { - // TODO Abort any async stuff going on, possible in `session.bindWithTokens()` - } - - @Override - public void onBind() { - gotoNextState(SessionState.BINDING); // Will trigger a retry. - } - - @Override - public void onUnbind() { - gotoNextState(SessionState.UNBOUND); - } - - @Override - public void onError(ObjectServerError error) { - // Ignore all errors. This is just a transient state. We are not bound yet, and any error should not - // happen until we are BOUND. - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java deleted file mode 100644 index e3e4aa53b7..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/BoundState.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.SessionState; - -/** - * BOUND State. In this state the local Realm is bound to the remote Realm and changes are sent in both - * directions immediately. - */ -class BoundState extends FsmState { - - @Override - public void onEnterState() { - // Do nothing. If everything is setup correctly. We should now be synchronizing any changes - // between the local and remote Realm. - } - - @Override - public void onExitState() { - // Do nothing. Entry states will stop the session if needed. - } - - @Override - public void onUnbind() { - gotoNextState(SessionState.UNBOUND); - } - - @Override - public void onStop() { - gotoNextState(SessionState.STOPPED); - } - - @Override - public void onError(ObjectServerError error) { - // If a Realms access token has expired, trigger a rebind. If the user is still valid it will automatically - // refresh it. - if (error.getErrorCode() == ErrorCode.TOKEN_EXPIRED) { - // the server can send a 202 (expired access token) even if the client - // still consider this token to be valid (based on timestamps for example) - // - // this may cause the server to send a fatal error (203 bad refresh) if we try to bind - // the session with this token. To be safe we remove the token that has been considered by the - // the server to be invalid. - - // stop the session to avoid sending a bind to the server which will cause it to return - // a fatal 203 (bad refresh) - session.stopNativeSession(); - session.removeAccessToken(); - - // Create a new session & bind it - session.createNativeSession(); - gotoNextState(SessionState.BINDING); - - } else { - switch (error.getCategory()) { - case FATAL: gotoNextState(SessionState.STOPPED); break; - case RECOVERABLE: gotoNextState(SessionState.UNBOUND); break; - } - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java deleted file mode 100644 index a4af906d3b..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmAction.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import io.realm.ObjectServerError; -import io.realm.SyncSession; - -/** - * As {@link SyncSession} is modeled as a state machine, this interface describe all - * possible actions in that machine. - *

            - * All states should implement this interface so all possible permutations of state/actions are covered. - * - */ -interface FsmAction { - void onStart(); - void onBind(); - void onUnbind(); - void onStop(); - void onError(ObjectServerError error); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java deleted file mode 100644 index ee4089a848..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/FsmState.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import io.realm.SyncSession; -import io.realm.ObjectServerError; -import io.realm.SessionState; - -/** - * Abstract class containing shared logic for all {@link SyncSession} states. All states must extend - * this class as it contains the logic for entering and leaving states. - */ -abstract class FsmState implements FsmAction { - - volatile ObjectServerSession session; // This is non-null when this state is active. - private boolean exiting; // TODO: Remind me again what race condition necessitated this. - - /** - * Entry into the state. This method is also responsible for executing any asynchronous work - * this state might run. - * - * This should only be called from {@link SyncSession}. - */ - public void entry(ObjectServerSession session) { - this.session = session; - this.exiting = false; - onEnterState(); - } - - /** - * Called just before leaving the state. Once this method is called no more state changes can be triggered from - * this state until {@link #entry(ObjectServerSession)} has been called again. - *

            - * This should only be called from {@link SyncSession}. - */ - public void exit() { - exiting = true; - onExitState(); - } - - public void gotoNextState(SessionState state) { - if (!exiting) { - session.nextState(state); - } - } - - protected abstract void onEnterState(); - protected abstract void onExitState(); - - @Override - public void onStart() { - // Do nothing - } - - @Override - public void onBind() { - // Do nothing - } - - @Override - public void onUnbind() { - // Do nothing - } - - @Override - public void onStop() { - // Do nothing - } - - @Override - public void onError(ObjectServerError error) { - switch(error.getCategory()) { - case FATAL: - gotoNextState(SessionState.STOPPED); - break; - case RECOVERABLE: - gotoNextState(SessionState.UNBOUND); - break; - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/InitialState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/InitialState.java deleted file mode 100644 index ed13957250..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/InitialState.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import io.realm.ObjectServerError; -import io.realm.SessionState; - -/** - * INITIAL State. Starting point for the Session Finite-State-Machine. - */ -class InitialState extends FsmState { - - @Override - public void onEnterState() { - // Do nothing. We start here - } - - @Override - protected void onExitState() { - // Do nothing. Right now the underlying Realm Core session cannot bound/unbind multiple times, so instead - // we create a new session object each time the Session becomes unbound. - } - - @Override - public void onStart() { - gotoNextState(SessionState.UNBOUND); - } - - @Override - public void onError(ObjectServerError error) { - // Ignore all errors at this state. None of them would have any impact. - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java deleted file mode 100644 index 615e23b5f5..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerSession.java +++ /dev/null @@ -1,475 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import java.net.URI; -import java.util.HashMap; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.RealmAsyncTask; -import io.realm.SessionState; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncUser; -import io.realm.internal.KeepMember; -import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.network.AuthenticationServer; -import io.realm.internal.network.ExponentialBackoffTask; -import io.realm.internal.network.NetworkStateReceiver; -import io.realm.internal.syncpolicy.SyncPolicy; -import io.realm.log.RealmLog; - -/** - * Internal class describing a Realm Object Server Session. - * There is currently a split between the public {@link SyncSession} and this class. - * This class is intended as a wrapper for Object Store's Sync Session, but it is not that yet. - *

            - * A Session is created by either calling {@link SyncManager#getSession(SyncConfiguration)} or by opening - * a Realm instance. Once a session has been created, it will continue to exist until explicitly closed or the - * underlying Realm file is deleted. - *

            - * It is typically not necessary to interact directly with a session. The interaction should be done by the {@code SyncPolicy} - * defined using {@code io.realm.SyncConfiguration.Builder#syncPolicy(SyncPolicy)}. - *

            - * A session has a lifecycle consisting of the following states: - *

            - *

            - *
          • - * INITIAL Initial state when creating the Session object. No connections to the object server have been - * created yet. At this point it is possible to register any relevant error and event listeners. Calling - * {@link #start()} will cause the session to become UNBOUND and notify the {@code SyncPolicy} that the - * session is ready by calling {@code SyncPolicy#onSessionCreated(Session)}. - *
          • - *
          • - * UNBOUND When a session is unbound, no synchronization between the local and remote Realm is taking place. - * Call {@link #bind()} to start synchronizing changes. - *
          • - *
          • - * BINDING A session is in the process of binding a local Realm to a remote one. Calling {@link #unbind()} - * at this stage, will cancel the process. If binding fails, the session will revert to being INBOUND and an error - * will be reported to the error handler. - *
          • - *
          • - * AUTHENTICATING During binding, if a users access has expired, the session will be AUTHENTICATING. - * During this state, Realm will automatically try to acquire new valid credentials. If it succeed BINDING - * will automatically be resumed, if not, the session will become UNBOUND or STOPPED and an - * appropriate error reported. - *
          • - *
          • - * BOUND A bound session has an active connection to the remote Realm and will synchronize any changes - * immediately. - *
          • - *
          • - * STOPPED The session are in an unrecoverable state. Check the error log for additional information, but - * the type of errors is usually wrong credentials for the Realm being accessed or a mismatching Object Server. - * Most problems can be solved by creating a new {@link SyncConfiguration} with a new {@code serverUrl} and - * {@code user}. - *
          • - *
            - * - * This object is thread safe. - */ -@KeepMember -public final class ObjectServerSession { - - private final HashMap FSM = new HashMap(); - - // Variables used by the FSM - final SyncConfiguration configuration; - private final AuthenticationServer authServer; - private final SyncSession.ErrorHandler errorHandler; - private long nativeSessionPointer; - private final ObjectServerUser user; - RealmAsyncTask networkRequest; - private RealmAsyncTask refreshTokenTask; - private RealmAsyncTask refreshTokenNetworkRequest; - NetworkStateReceiver.ConnectionListener networkListener; - private SyncPolicy syncPolicy; - - // Keeping track of current FSM state - private SessionState currentStateDescription; - private FsmState currentState; - private SyncSession userSession; - - private final static ScheduledThreadPoolExecutor REFRESH_TOKENS_EXECUTOR = new ScheduledThreadPoolExecutor(1); - private final static long REFRESH_MARGIN_DELAY = TimeUnit.SECONDS.toMillis(10); - - /** - * Creates a new Object Server Session. - * - * @param syncConfiguration Sync configuration defining this session - * @param authServer Authentication server used to refresh credentials if needed - * @param policy Sync Policy to use by this Session. - */ - public ObjectServerSession(SyncConfiguration syncConfiguration, - AuthenticationServer authServer, - ObjectServerUser user, - SyncPolicy policy, - SyncSession.ErrorHandler errorHandler) { - this.configuration = syncConfiguration; - this.user = user; - this.authServer = authServer; - this.errorHandler = errorHandler; - this.syncPolicy = policy; - setupStateMachine(); - } - - private void setupStateMachine() { - FSM.put(SessionState.INITIAL, new InitialState()); - FSM.put(SessionState.UNBOUND, new UnboundState()); - FSM.put(SessionState.BINDING, new BindingState()); - FSM.put(SessionState.AUTHENTICATING, new AuthenticatingState()); - FSM.put(SessionState.BOUND, new BoundState()); - FSM.put(SessionState.STOPPED, new StoppedState()); - RealmLog.debug("Session started: " + configuration.getServerUrl()); - currentState = FSM.get(SessionState.INITIAL); - currentState.entry(this); - } - - // Goto the next state. The FsmState classes are responsible for calling this method as a reaction to a FsmAction - // being called or an internal action triggering a state transition. - void nextState(SessionState nextStateDescription) { - currentState.exit(); - FsmState nextState = FSM.get(nextStateDescription); - if (nextState == null) { - throw new IllegalStateException("No state was configured to handle: " + nextStateDescription); - } - RealmLog.debug("Session[%s]: %s -> %s", configuration.getServerUrl(), currentStateDescription, nextStateDescription); - currentStateDescription = nextStateDescription; - currentState = nextState; - nextState.entry(this); - } - - /** - * Starts the session. This will cause the session to come UNBOUND. {@link #bind()} must be called to - * actually start synchronizing data. - */ - public synchronized void start() { - currentState.onStart(); - } - - /** - * Stops the session. The session can no longer be used. - */ - public synchronized void stop() { - // tries to stop any scheduled access_token refresh - clearScheduledAccessTokenRefresh(); - currentState.onStop(); - } - - /** - * Binds the local Realm to the remote Realm. Once bound, changes to either the local or Remote Realm will be - * synchronized immediately. - *

            - * While this method will return immediately, binding a Realm is not guaranteed to succeed. Possible reasons for - * failure could be if the device is offline or credentials have expired. Binding is an asynchronous - * operation and all errors will be sent first to {@code SyncPolicy#onError(Session, ObjectServerError)} and if the - * SyncPolicy doesn't handle it, to the {@link SyncSession.ErrorHandler} defined by - * {@link SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler)}. - */ - public synchronized void bind() { - currentState.onBind(); - } - - /** - * Stops a local Realm from synchronizing changes with the remote Realm. - *

            - * It is possible to call {@link #bind()} again after a Realm has been unbound. - */ - public synchronized void unbind() { - currentState.onUnbind(); - } - - /** - * Notify the session that an error has occurred. - * - * @param error the kind of err - */ - public synchronized void onError(ObjectServerError error) { - currentState.onError(error); // FSM needs to respond to the error first, before notifying the User - if (errorHandler != null) { - errorHandler.onError(getUserSession(), error); - } - } - - // Called from JniSession in native code. - // This callback will happen on the thread running the Sync Client. - @SuppressWarnings("unused") - @KeepMember - private void notifySessionError(int errorCode, String errorMessage) { - ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); - onError(error); - } - - /** - * Checks if the local Realm is bound to the remote Realm and can synchronize any changes happening on either - * sides. - * - * @return {@code true} if the local Realm is bound to the remote Realm, {@code false} otherwise. - */ - boolean isBound() { - return currentStateDescription == SessionState.BOUND; - } - - // - // Package protected methods used by the FSM states to manipulate session variables. - // - - // Create a native session. The session abstraction in Realm Core doesn't support multiple calls to bind()/unbind() - // yet, so the Java SyncSession must manually create/and close the native sessions as needed. - void createNativeSession() { - nativeSessionPointer = nativeCreateSession(configuration.getPath()); - } - - void stopNativeSession() { - if (nativeSessionPointer != 0) { - nativeUnbind(nativeSessionPointer); - nativeSessionPointer = 0; - } - clearScheduledAccessTokenRefresh(); - } - - // It is an error to call this function before calling Client::bind() state - private boolean updateSessionAccessToken(String userToken) { - if (nativeSessionPointer != 0 && isBound()) { - nativeRefresh(nativeSessionPointer, userToken); - return true; - } - return false; - } - - private void clearScheduledAccessTokenRefresh() { - if (refreshTokenTask != null) { - refreshTokenTask.cancel(); - } - if (refreshTokenNetworkRequest != null) { - refreshTokenNetworkRequest.cancel(); - } - } - - void removeAccessToken() { - user.removeAccessToken(configuration.getServerUrl()); - } - - // Bind with proper access tokens - // Access tokens are presumed to be present and valid at this point - void bindWithTokens() { - Token accessToken = user.getAccessToken(configuration.getServerUrl()); - if (accessToken == null) { - throw new IllegalStateException("User '" + user.toString() + "' does not have an access token for " - + configuration.getServerUrl()); - } - nativeBind(nativeSessionPointer, configuration.getServerUrl().toString(), accessToken.value()); - } - - // Authenticate by getting access tokens for the specific Realm - void authenticateRealm(final Runnable onSuccess, final SyncSession.ErrorHandler errorHandler) { - if (networkRequest != null) { - networkRequest.cancel(); - } - // clear any previously scheduled refresh access_token - // since we're going to obtain a new refresh_token - clearScheduledAccessTokenRefresh(); - - // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { - @Override - protected AuthenticateResponse execute() { - return authServer.loginToRealm( - user.getUserToken(), - configuration.getServerUrl(), - user.getAuthenticationUrl() - ); - } - - @Override - protected void onSuccess(AuthenticateResponse response) { - ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription( - response.getAccessToken(), - configuration.getPath(), - configuration.shouldDeleteRealmOnLogout() - ); - user.addRealm(configuration.getServerUrl(), desc); - // schedule a token refresh before it expires - scheduleRefreshAccessToken(response.getAccessToken().expiresMs()); - onSuccess.run(); - } - - @Override - protected void onError(AuthenticateResponse response) { - errorHandler.onError(getUserSession(), response.getError()); - } - }); - networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); - } - - private void scheduleRefreshAccessToken(long expireDateInMs) { - // calculate the delay time before which we should refresh the access_token, - // we adjust to 10 second to proactively refresh the access_token before the session - // hit the expire date on the token - long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; - if (refreshAfter < 0) { - // Token already expired - RealmLog.debug("Expires time already reached for the access token, refreshing now"); - refreshAccessToken(); - - } else { - RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); - if (refreshTokenTask != null) { - refreshTokenTask.cancel(); - } - - ScheduledFuture task = REFRESH_TOKENS_EXECUTOR.schedule(new Runnable() { - @Override - public void run() { - refreshAccessToken(); - } - }, refreshAfter, TimeUnit.MILLISECONDS); - refreshTokenTask = new RealmAsyncTaskImpl(task, REFRESH_TOKENS_EXECUTOR); - } - } - - // Authenticate by getting access tokens for the specific Realm - private void refreshAccessToken() { - // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. - if (refreshTokenNetworkRequest != null) { - refreshTokenNetworkRequest.cancel(); - } - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { - @Override - protected AuthenticateResponse execute() { - return authServer.refreshUser(user.getUserToken(), configuration.getServerUrl(), user.getAuthenticationUrl()); - } - - @Override - protected void onSuccess(AuthenticateResponse response) { - synchronized (ObjectServerSession.this) { - RealmLog.debug("Access Token refreshed successfully"); - if (updateSessionAccessToken(response.getAccessToken().value())) { - RealmLog.debug("Token applied"); - // only schedule an update if the token was updated. - // The callback might return will the session state is not BOUND - // in this case we'll wait for the new session state to transition to - // BOUND, which will schedule a refresh in the process - - // this will also avoid updating a stopped session - - // replaced the user old access_token - ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription( - response.getAccessToken(), - configuration.getPath(), - configuration.shouldDeleteRealmOnLogout() - ); - user.addRealm(configuration.getServerUrl(), desc); - // schedule the next refresh - scheduleRefreshAccessToken(response.getAccessToken().expiresMs()); - } - } - } - - @Override - protected void onError(AuthenticateResponse response) { - RealmLog.error("Unrecoverable error, while refreshing the access Token (" + response.getError().toString() + ") reschedule will not happen"); - } - }); - refreshTokenNetworkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); - } - - /** - * Checks if a user has valid credentials for accessing this Realm. - * - * @param configuration the configuration. - * @return {@code true} if credentials are valid, {@code false} otherwise. - */ - boolean isAuthenticated(SyncConfiguration configuration) { - return user.isAuthenticated(configuration); - } - - /** - * Returns the {@link SyncConfiguration} that is responsible for controlling this session. - * - * @return SyncConfiguration that defines and controls this session. - */ - public SyncConfiguration getConfiguration() { - return configuration; - } - - /** - * Returns the {@link SyncUser} defined by the {@link SyncConfiguration} that is used to connect to the - * Realm Object Server. - * - * @return {@link SyncUser} used to authenticate the session on the Realm Object Server. - */ - public SyncUser getUser() { - return configuration.getUser(); - } - - /** - * Returns the {@link URI} describing the remote Realm this session connects to and synchronizes changes with. - * - * @return {@link URI} describing the remote Realm. - */ - public URI getServerUrl() { - return configuration.getServerUrl(); - } - - /** - * Returns the state of this session. - * - * @return The current {@link SessionState} for this session. - */ - public SessionState getState() { - return currentStateDescription; - } - - /** - * Notify session that a commit on the device has happened. - * - * @param version the commit number/version. - */ - public void notifyCommit(long version) { - if (isBound()) { - nativeNotifyCommitHappened(nativeSessionPointer, version); - } - } - - public SyncPolicy getSyncPolicy() { - return syncPolicy; - } - - public SyncSession getUserSession() { - return userSession; - } - - public void setUserSession(SyncSession userSession) { - this.userSession = userSession; - } - - private native long nativeCreateSession(String localRealmPath); - private native void nativeBind(long nativeSessionPointer, String remoteRealmUrl, String userToken); - private native void nativeUnbind(long nativeSessionPointer); - private native void nativeRefresh(long nativeSessionPointer, String userToken); - private native void nativeNotifyCommitHappened(long sessionPointer, long version); -} - diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java index a24e2bb6ce..740125a54b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java @@ -65,7 +65,7 @@ private void setRefreshToken(final Token refreshToken) { * * Authenticating will happen automatically as part of opening a Realm. */ - boolean isAuthenticated(SyncConfiguration configuration) { + public boolean isAuthenticated(SyncConfiguration configuration) { Token token = getAccessToken(configuration.getServerUrl()); return token != null && token.expiresMs() > System.currentTimeMillis(); } @@ -93,7 +93,7 @@ public String getIdentity() { return identity; } - Token getAccessToken(URI serverUrl) { + public Token getAccessToken(URI serverUrl) { AccessDescription accessDescription = realms.get(serverUrl); return (accessDescription != null) ? accessDescription.accessToken : null; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java deleted file mode 100644 index a40fe01fd1..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SessionStore.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import io.realm.SyncSession; -import io.realm.SyncManager; -import io.realm.SyncConfiguration; - -/** - * Private class for keeping track of sessions. - * If {@link SyncSession} and {@link ObjectServerSession} are combined at some point, this class can - * be folded into {@link SyncManager}; - */ -public class SessionStore { - - // Map of between a local Realm path and any associated sessionInfo - private static HashMap sessions = new HashMap(); - private static HashMap privateSessions = new HashMap(); - - static synchronized void removeSession(SyncSession session) { - if (session == null) { - return; - } - - Iterator> it = sessions.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - if (entry.getValue().equals(session)) { - it.remove(); - break; - } - } - } - - public static synchronized void addSession(SyncSession publicSession, ObjectServerSession internalSession) { - String localPath = publicSession.getConfiguration().getPath(); - sessions.put(localPath, publicSession); - privateSessions.put(localPath, internalSession); - } - - public static synchronized boolean hasSession(SyncConfiguration config) { - String localPath = config.getPath(); - return sessions.containsKey(localPath); - } - - public static synchronized SyncSession getPublicSession(SyncConfiguration config) { - String localPath = config.getPath(); - return sessions.get(localPath); - } - - public static synchronized ObjectServerSession getPrivateSession(SyncSession session) { - String localPath = session.getConfiguration().getPath(); - return privateSessions.get(localPath); - } - - public static Collection getAllSessions() { - return privateSessions.values(); - } - -} - - diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java deleted file mode 100644 index f1b58008b0..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/StoppedState.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import io.realm.ObjectServerError; -import io.realm.SyncSession; - -/** - * STOPPED State. This is the final state for a {@link SyncSession}. After this, all actions will throw an - * {@link IllegalStateException}. - */ -class StoppedState extends FsmState { - - @Override - public void onEnterState() { - session.stopNativeSession(); - session.getSyncPolicy().onSessionStopped(session); - } - - @Override - protected void onExitState() { - // Cannot exit this state - } - - @Override - public void onStart() { - // To harsh to to throw here as any SyncPolicy might not have been made aware - // that the Session is stopped. Just ignore the call instead. - } - - @Override - public void onBind() { - // To harsh to to throw here as any SyncPolicy might not have been made aware - // that the Session is stopped. Just ignore the call instead. - } - - @Override - public void onUnbind() { - // To harsh to to throw here as any SyncPolicy might not have been made aware - // that the Session is stopped. Just ignore the call instead. - } - - @Override - public void onStop() { - // To harsh to to throw here as any SyncPolicy might not have been made aware - // that the Session is stopped. Just ignore the call instead. - } - - @Override - public void onError(ObjectServerError error) { - // Ignore all errors at this state. None of them would have any impact. - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/UnboundState.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/UnboundState.java deleted file mode 100644 index 3471357544..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/UnboundState.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectserver; - -import io.realm.ObjectServerError; -import io.realm.SessionState; - -/** - * UNBOUND State. This is the default state after a session has been started and no attempt at binding the local Realm - * has been made. - */ -class UnboundState extends FsmState { - - @Override - public void onEnterState() { - // We can enter this state from multiple states which might have had an active session. - // In those cases cleanup any old native session - session.stopNativeSession(); - - // Create the native session so it is ready to be bound. - session.createNativeSession(); - } - - @Override - protected void onExitState() { - // Do nothing. - } - - @Override - public void onBind() { - gotoNextState(SessionState.BINDING); - } - - @Override - public void onError(ObjectServerError error) { - // Ignore all errors at this state. None of them would have any impact. - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java deleted file mode 100644 index 6f4b784181..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/AutomaticSyncPolicy.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.syncpolicy; - -import io.realm.ObjectServerError; -import io.realm.internal.objectserver.ObjectServerSession; - -/** - * This SyncPolicy will automatically start synchronizing changes to a Realm as soon as it is opened. - */ -public class AutomaticSyncPolicy implements SyncPolicy { - - private Long lastError = null; - private int recurringErrors = 0; - - @Override - public void onRealmOpened(ObjectServerSession session) { - session.bind(); // Bind Realm first time it is opened. - } - - @Override - public void onRealmClosed(ObjectServerSession session) { - // TODO In order to preserve resources we should ideally close the session as well, but first - // we want to make sure that all local changes have been synchronized to the remote Realm. - } - - @Override - public void onSessionCreated(ObjectServerSession session) { - session.start(); - } - - @Override - public void onSessionStopped(ObjectServerSession session) { - // Do nothing - } - - @Override - public boolean onError(ObjectServerSession session, ObjectServerError error) { - switch(error.getCategory()) { - case FATAL: - return false; // Report all fatal errors to the user - case RECOVERABLE: - return rebind(session); - default: - return false; - } - } - - /** - * Returns {@code true} if we decide to rebind, {@code false} if the error was determined to no longer be solvable. - */ - private boolean rebind(ObjectServerSession session) { - // Track all calls to rebind(). If some error reported as RECOVERABLE keeps happening, we need to abort to - // prevent run-away sessions. Right now we treat an error as recurring if it happens within 3 seconds of each - // other. After 5 of such errors we terminate the session. - // - // Standard IO errors are already handled using incremental backoff by e.g the AUTHENTICATING state, so - // re-occurring errors at this level are more serious. - long now = System.currentTimeMillis(); - if (lastError - now < 3000) { - recurringErrors++; - } else { - recurringErrors = 1; - } - lastError = now; - - if (recurringErrors == 5) { - session.stop(); // Abort session, some error that should be temporary keeps happening. - return false; - } else { - session.bind(); - return true; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AutomaticSyncPolicy that = (AutomaticSyncPolicy) o; - - if (recurringErrors != that.recurringErrors) return false; - return lastError != null ? lastError.equals(that.lastError) : that.lastError == null; - } - - @Override - public int hashCode() { - int result = lastError != null ? lastError.hashCode() : 0; - result = 31 * result + recurringErrors; - return result; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java b/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java deleted file mode 100644 index ae14b6af3e..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/syncpolicy/SyncPolicy.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.syncpolicy; - -import io.realm.ObjectServerError; -import io.realm.SyncSession; -import io.realm.SyncConfiguration; -import io.realm.internal.objectserver.ObjectServerSession; - -/** - * Interface describing a given synchronization policy with the Realm Object Server. - *

            - * The sole purpose of classes implementing this interface is to call {@link ObjectServerSession#bind()} and - * {@link ObjectServerSession#unbind()} as needed, which will control when changes are synchronized between a local and - * remote Realm. - * - * The SyncPolicy is not responsible for managing the lifecycle of the {@link ObjectServerSession} in general. So any - * implementation of this class should avoid calling {@link ObjectServerSession#stop()} and - * {@link ObjectServerSession#start()}. - * - * If a session is stopped, {@link ObjectServerSession#unbind()} is automatically called and any further calls to - * {@link ObjectServerSession#bind()} and {@link ObjectServerSession#unbind()} are ignored. - * {@link #onSessionStopped(ObjectServerSession)} ()} will then be called so the sync policy have a chance to clean up - * any resources it might be using. - */ -// Internal until we are sure this is the API we want -public interface SyncPolicy { - - /** - * Called when the session object is created. At this point it is possible to register any relevant error and event - * listeners in either the Android framework or for the session itself. - * - * {@link ObjectServerSession#start()} will be automatically called after this method. - * - * @param session the {@link SyncSession} just created. It has not yet been started. - */ - void onSessionCreated(ObjectServerSession session); - - /** - * The {@link ObjectServerSession} has been stopped and will ignore any further calls to - * {@link ObjectServerSession#bind()} and {@link ObjectServerSession#unbind()}. All external resources should be - * cleaned up. - * - * @param session {@link ObjectServerSession} that has been stopped. - */ - void onSessionStopped(ObjectServerSession session); - - /** - * Called the first time a Realm is opened on any thread. - * - * @param session {@link ObjectServerSession} associated with this Realm. - */ - void onRealmOpened(ObjectServerSession session); - - /** - * Called when the last Realm instance across all threads have been closed. - * - * @param session {@link ObjectServerSession} associated with this Realm. - */ - void onRealmClosed(ObjectServerSession session); - - /** - * Called if an error occurred in the underlying session. In many cases this has caused the session to become - * unbound. - * - * @param error {@link ObjectServerError} object describing the error. - * @return {@code true} if the error was handled, or {@code false} if it should be propagated further out to the - * SyncConfigurations error handler. - * - * This method is always called from a background thread, never the UI thread. - * - * @see SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler) - */ - boolean onError(ObjectServerSession session, ObjectServerError error); -} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index fb7f68f387..ae1cda7ac7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -9,18 +9,20 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.Realm; -import io.realm.SessionState; import io.realm.SyncConfiguration; import io.realm.SyncCredentials; import io.realm.SyncManager; import io.realm.SyncSession; import io.realm.SyncUser; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -61,7 +63,7 @@ public void onError(ObjectServerError error) { @RunTestInLooperThread public void login_withAccessToken() { SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentials = SyncCredentials.accessToken(admin.getAccessToken(), "custom-admin-user"); + SyncCredentials credentials = SyncCredentials.accessToken(admin.getAccessToken().value(), "custom-admin-user"); SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { @@ -82,7 +84,7 @@ public void onError(SyncSession session, ObjectServerError error) { looperThread.postRunnableDelayed(new Runnable() { @Override public void run() { - assertEquals(SessionState.BOUND, SyncManager.getSession(config).getState()); + assertTrue(SyncManager.getSession(config).getUser().isValid()); looperThread.testComplete(); } }, 1000); @@ -100,6 +102,10 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread public void loginAsync_errorHandlerThrows() { + // set log level to info to make sure the IllegalArgumentException + // thrown in the test is visible in Logcat + final int defaultLevel = RealmLog.getLevel(); + RealmLog.setLevel(LogLevel.INFO); SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override @@ -114,12 +120,12 @@ public void onError(ObjectServerError error) { } }); - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - fail(); - } - looperThread.testComplete(); + looperThread.postRunnableDelayed(new Runnable() { + @Override + public void run() { + RealmLog.setLevel(defaultLevel); + looperThread.testComplete(); + } + }, 1000); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index b63456b79d..8770019e59 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -16,17 +16,12 @@ package io.realm.objectserver.utils; -import android.support.test.InstrumentationRegistry; - import java.io.IOException; -import io.realm.Realm; import io.realm.log.RealmLog; import okhttp3.Headers; -import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.RequestBody; import okhttp3.Response; /** From dc09d519a5fa1c5b539a6f43e292ac66f7568a4b Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 10 Mar 2017 13:45:19 +0100 Subject: [PATCH 0551/2110] Use `clang-format` to format C++ code (#4307) * Formatting C++ source code using clang-format using core's rules. * How to format C++ code * Curly braces around single lines --- CONTRIBUTING.md | 2 + .../realm-library/src/main/cpp/.clang-format | 89 ++ .../src/main/cpp/io_realm_Property.cpp | 25 +- .../main/cpp/io_realm_RealmFileUserStore.cpp | 43 +- .../main/cpp/io_realm_RealmObjectSchema.cpp | 29 +- .../src/main/cpp/io_realm_RealmSchema.cpp | 18 +- .../src/main/cpp/io_realm_SyncManager.cpp | 24 +- .../main/cpp/io_realm_internal_CheckedRow.cpp | 162 ++- .../main/cpp/io_realm_internal_Collection.cpp | 194 +-- .../io_realm_internal_CollectionChangeSet.cpp | 26 +- .../main/cpp/io_realm_internal_LinkView.cpp | 168 +-- ...o_realm_internal_NativeObjectReference.cpp | 6 +- .../cpp/io_realm_internal_SharedRealm.cpp | 251 ++-- .../src/main/cpp/io_realm_internal_Table.cpp | 1089 ++++++++++------- .../main/cpp/io_realm_internal_TableQuery.cpp | 1002 ++++++++------- .../main/cpp/io_realm_internal_TestUtil.cpp | 17 +- .../cpp/io_realm_internal_UncheckedRow.cpp | 318 ++--- .../src/main/cpp/io_realm_internal_Util.cpp | 27 +- ...ernal_objectserver_ObjectServerSession.cpp | 44 +- .../src/main/cpp/io_realm_log_RealmLog.cpp | 44 +- .../src/main/cpp/java_binding_context.cpp | 10 +- .../src/main/cpp/java_binding_context.hpp | 12 +- .../src/main/cpp/java_sort_descriptor.cpp | 15 +- .../src/main/cpp/java_sort_descriptor.hpp | 8 +- .../src/main/cpp/jni_impl/android_logger.cpp | 11 +- .../src/main/cpp/jni_impl/android_logger.hpp | 5 +- .../cpp/jni_util/java_global_weak_ref.cpp | 1 - .../cpp/jni_util/java_global_weak_ref.hpp | 22 +- .../src/main/cpp/jni_util/java_local_ref.hpp | 29 +- .../src/main/cpp/jni_util/java_method.cpp | 7 +- .../src/main/cpp/jni_util/java_method.hpp | 27 +- .../src/main/cpp/jni_util/jni_utils.cpp | 10 +- .../src/main/cpp/jni_util/jni_utils.hpp | 12 +- .../src/main/cpp/jni_util/log.cpp | 62 +- .../src/main/cpp/jni_util/log.hpp | 34 +- .../realm-library/src/main/cpp/mem_usage.cpp | 76 +- .../src/main/cpp/objectserver_shared.hpp | 63 +- .../src/main/cpp/tablebase_tpl.hpp | 17 +- realm/realm-library/src/main/cpp/utf8.hpp | 65 +- realm/realm-library/src/main/cpp/util.cpp | 134 +- realm/realm-library/src/main/cpp/util.hpp | 292 ++--- 41 files changed, 2549 insertions(+), 1941 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/.clang-format diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e25a9af5f1..88db58a2b6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,8 @@ Realm welcomes all contributions! The only requirement we have is that, like man While we havn't described our code style yet, please just follow the existing style you see in the files you change. +For source code written in C++, we format it using `clang-format`. You can use the [plugin](https://plugins.jetbrains.com/plugin/8396-clangformatij): mark the entire file and right-click to execute `clang-format` before committing any changes. Of course, if you don't use Android Studio to edit C++ code, run `clang-format` on the command-line. + ### Unit Tests All PR's must be accompanied by related unit tests. All bug fixes must have a unit test proving that the bug is fixed. diff --git a/realm/realm-library/src/main/cpp/.clang-format b/realm/realm-library/src/main/cpp/.clang-format new file mode 100644 index 0000000000..9361184616 --- /dev/null +++ b/realm/realm-library/src/main/cpp/.clang-format @@ -0,0 +1,89 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 118 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +... + diff --git a/realm/realm-library/src/main/cpp/io_realm_Property.cpp b/realm/realm-library/src/main/cpp/io_realm_Property.cpp index b107982b07..3aaedada73 100644 --- a/realm/realm-library/src/main/cpp/io_realm_Property.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_Property.cpp @@ -25,18 +25,18 @@ using namespace realm; -JNIEXPORT jlong JNICALL -Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2IZZZ(JNIEnv *env, jclass, jstring name_, - jint type, jboolean is_primary, jboolean is_indexed, - jboolean is_nullable) { +JNIEXPORT jlong JNICALL Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2IZZZ( + JNIEnv* env, jclass, jstring name_, jint type, jboolean is_primary, jboolean is_indexed, jboolean is_nullable) +{ TR_ENTER() try { JStringAccessor str(env, name_); PropertyType p_type = static_cast(static_cast(type)); - std::unique_ptr property(new Property(str, p_type, "", "", to_bool(is_primary), to_bool(is_indexed), to_bool(is_nullable))); + std::unique_ptr property( + new Property(str, p_type, "", "", to_bool(is_primary), to_bool(is_indexed), to_bool(is_nullable))); if (to_bool(is_indexed) && !property->is_indexable()) { throw std::invalid_argument( - "This field cannot be indexed - Only String/byte/short/int/long/boolean/Date fields are supported."); + "This field cannot be indexed - Only String/byte/short/int/long/boolean/Date fields are supported."); } if (to_bool(is_primary) && p_type != PropertyType::Int && p_type != PropertyType::String) { std::string typ = property->type_string(); @@ -48,10 +48,9 @@ Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2IZZZ(JNIEnv *env return 0; } -JNIEXPORT jlong JNICALL -Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2ILjava_lang_String_2(JNIEnv *env, jclass, - jstring name_, jint type, - jstring linkedToName_) { +JNIEXPORT jlong JNICALL Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2ILjava_lang_String_2( + JNIEnv* env, jclass, jstring name_, jint type, jstring linkedToName_) +{ TR_ENTER() try { JStringAccessor name(env, name_); @@ -65,11 +64,11 @@ Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2ILjava_lang_Stri return 0; } -JNIEXPORT void JNICALL -Java_io_realm_Property_nativeClose(JNIEnv *env, jclass, jlong property_ptr) { +JNIEXPORT void JNICALL Java_io_realm_Property_nativeClose(JNIEnv* env, jclass, jlong property_ptr) +{ TR_ENTER_PTR(property_ptr) try { - Property *property = reinterpret_cast(property_ptr); + Property* property = reinterpret_cast(property_ptr); delete property; } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 076fb54292..9c4d3a49f9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -25,54 +25,55 @@ using namespace realm; static const char* ERR_COULD_NOT_ALLOCATE_MEMORY = "Could not allocate memory to return all users."; -static jstring -to_user_string_or_null (JNIEnv *env, const std::shared_ptr& user) +static jstring to_user_string_or_null(JNIEnv* env, const std::shared_ptr& user) { if (user) { return to_jstring(env, user->refresh_token().data()); - } else { + } + else { return nullptr; } } -JNIEXPORT jstring JNICALL -Java_io_realm_RealmFileUserStore_nativeGetCurrentUser (JNIEnv *env, jclass) +JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetCurrentUser(JNIEnv* env, jclass) { TR_ENTER() try { const std::shared_ptr& user = SyncManager::shared().get_current_user(); return to_user_string_or_null(env, user); - } CATCH_STD() + } + CATCH_STD() return nullptr; } -JNIEXPORT jstring JNICALL -Java_io_realm_RealmFileUserStore_nativeGetUser (JNIEnv *env, jclass, jstring identity) +JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetUser(JNIEnv* env, jclass, jstring identity) { TR_ENTER() try { JStringAccessor id(env, identity); // throws const std::shared_ptr& user = SyncManager::shared().get_existing_logged_in_user(id); return to_user_string_or_null(env, user); - } CATCH_STD() + } + CATCH_STD() return nullptr; } -JNIEXPORT void JNICALL -Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser (JNIEnv *env, jclass, jstring identity, jstring jsonToken, jstring url) +JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser(JNIEnv* env, jclass, + jstring identity, jstring jsonToken, + jstring url) { TR_ENTER() try { - JStringAccessor user_identity(env, identity); // throws + JStringAccessor user_identity(env, identity); // throws JStringAccessor user_json_token(env, jsonToken); // throws - JStringAccessor auth_url(env, url); // throws + JStringAccessor auth_url(env, url); // throws SyncManager::shared().get_user(user_identity, user_json_token, std::string(auth_url)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_RealmFileUserStore_nativeLogoutUser (JNIEnv *env, jclass, jstring identity) +JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeLogoutUser(JNIEnv* env, jclass, jstring identity) { TR_ENTER() try { @@ -81,12 +82,12 @@ Java_io_realm_RealmFileUserStore_nativeLogoutUser (JNIEnv *env, jclass, jstring if (user) { user->log_out(); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jobjectArray JNICALL -Java_io_realm_RealmFileUserStore_nativeGetAllUsers (JNIEnv *env, jclass) +JNIEXPORT jobjectArray JNICALL Java_io_realm_RealmFileUserStore_nativeGetAllUsers(JNIEnv* env, jclass) { TR_ENTER() std::vector> all_users = SyncManager::shared().all_logged_in_users(); @@ -106,10 +107,8 @@ Java_io_realm_RealmFileUserStore_nativeGetAllUsers (JNIEnv *env, jclass) return nullptr; } -JNIEXPORT void JNICALL -Java_io_realm_RealmFileUserStore_nativeResetForTesting (JNIEnv *, jclass) +JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeResetForTesting(JNIEnv*, jclass) { TR_ENTER(); SyncManager::shared().reset_for_testing(); } - diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp index a2227dacad..e6804a0ad5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp @@ -23,12 +23,13 @@ #include "util.hpp" using namespace realm; -JNIEXPORT jlong JNICALL -Java_io_realm_RealmObjectSchema_nativeCreateRealmObjectSchema(JNIEnv *env, jclass, jstring className_) { +JNIEXPORT jlong JNICALL Java_io_realm_RealmObjectSchema_nativeCreateRealmObjectSchema(JNIEnv* env, jclass, + jstring className_) +{ TR_ENTER() try { JStringAccessor name(env, className_); - ObjectSchema *object_schema = new ObjectSchema(); + ObjectSchema* object_schema = new ObjectSchema(); object_schema->name = name; return reinterpret_cast(object_schema); } @@ -36,8 +37,8 @@ Java_io_realm_RealmObjectSchema_nativeCreateRealmObjectSchema(JNIEnv *env, jclas return 0; } -JNIEXPORT void JNICALL -Java_io_realm_RealmObjectSchema_nativeClose(JNIEnv *env, jclass, jlong native_ptr) { +JNIEXPORT void JNICALL Java_io_realm_RealmObjectSchema_nativeClose(JNIEnv* env, jclass, jlong native_ptr) +{ TR_ENTER_PTR(native_ptr) try { ObjectSchema* object_schema = reinterpret_cast(native_ptr); @@ -47,8 +48,9 @@ Java_io_realm_RealmObjectSchema_nativeClose(JNIEnv *env, jclass, jlong native_pt } -JNIEXPORT void JNICALL -Java_io_realm_RealmObjectSchema_nativeAddProperty(JNIEnv *env, jclass, jlong native_ptr, jlong property_ptr) { +JNIEXPORT void JNICALL Java_io_realm_RealmObjectSchema_nativeAddProperty(JNIEnv* env, jclass, jlong native_ptr, + jlong property_ptr) +{ TR_ENTER_PTR(native_ptr) try { ObjectSchema* object_schema = reinterpret_cast(native_ptr); @@ -61,8 +63,8 @@ Java_io_realm_RealmObjectSchema_nativeAddProperty(JNIEnv *env, jclass, jlong nat CATCH_STD() } -JNIEXPORT jstring JNICALL -Java_io_realm_RealmObjectSchema_nativeGetClassName(JNIEnv *env, jclass, jlong nativePtr) { +JNIEXPORT jstring JNICALL Java_io_realm_RealmObjectSchema_nativeGetClassName(JNIEnv* env, jclass, jlong nativePtr) +{ TR_ENTER_PTR(nativePtr) try { ObjectSchema* object_schema = reinterpret_cast(nativePtr); @@ -71,11 +73,11 @@ Java_io_realm_RealmObjectSchema_nativeGetClassName(JNIEnv *env, jclass, jlong na } CATCH_STD() - return NULL; + return nullptr; } -JNIEXPORT jlongArray JNICALL -Java_io_realm_RealmObjectSchema_nativeGetProperties(JNIEnv *env, jclass, jlong nativePtr) { +JNIEXPORT jlongArray JNICALL Java_io_realm_RealmObjectSchema_nativeGetProperties(JNIEnv* env, jclass, jlong nativePtr) +{ TR_ENTER_PTR(nativePtr) try { ObjectSchema* object_schema = reinterpret_cast(nativePtr); @@ -96,6 +98,5 @@ Java_io_realm_RealmObjectSchema_nativeGetProperties(JNIEnv *env, jclass, jlong n } CATCH_STD() - return NULL; + return nullptr; } - diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp index 9c6f1992e3..7bc774c51c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp @@ -25,8 +25,9 @@ using namespace realm; -JNIEXPORT jlong JNICALL -Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv *env, jclass, jlongArray objectSchemaPtrs_) { +JNIEXPORT jlong JNICALL Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv* env, jclass, + jlongArray objectSchemaPtrs_) +{ TR_ENTER() try { std::vector object_schemas; @@ -35,22 +36,22 @@ Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv *env, jclass, jlongArray o ObjectSchema object_schema = *reinterpret_cast(array[i]); object_schemas.push_back(std::move(object_schema)); } - auto *schema = new Schema(object_schemas); + auto* schema = new Schema(object_schemas); return reinterpret_cast(schema); } CATCH_STD() return 0; } -JNIEXPORT void JNICALL -Java_io_realm_RealmSchema_nativeClose(JNIEnv*, jclass, jlong nativePtr) { +JNIEXPORT void JNICALL Java_io_realm_RealmSchema_nativeClose(JNIEnv*, jclass, jlong nativePtr) +{ TR_ENTER_PTR(nativePtr) Schema* schema = reinterpret_cast(nativePtr); delete schema; } -JNIEXPORT jlongArray JNICALL -Java_io_realm_RealmSchema_nativeGetAll(JNIEnv *env, jclass, jlong nativePtr) { +JNIEXPORT jlongArray JNICALL Java_io_realm_RealmSchema_nativeGetAll(JNIEnv* env, jclass, jlong nativePtr) +{ TR_ENTER_PTR(nativePtr) try { Schema* schema = reinterpret_cast(nativePtr); @@ -70,6 +71,5 @@ Java_io_realm_RealmSchema_nativeGetAll(JNIEnv *env, jclass, jlong nativePtr) { return native_ptr_array; } CATCH_STD() - return NULL; + return nullptr; } - diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index fb45d1936f..c0c05c3ee1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -41,34 +41,36 @@ using namespace realm::jni_util; std::unique_ptr sync_client; -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient - (JNIEnv *env, jclass) +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient(JNIEnv* env, jclass) { TR_ENTER() - if (sync_client) return; + if (sync_client) { + return; + } try { sync::Client::Config config; config.logger = &CoreLoggerBridge::shared(); sync_client = std::make_unique(std::move(config)); // Throws - } CATCH_STD() + } + CATCH_STD() } // Create the thread from java side to avoid some strange errors when native throws. -JNIEXPORT void JNICALL -Java_io_realm_SyncManager_nativeRunClient(JNIEnv *env, jclass) +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeRunClient(JNIEnv* env, jclass) { try { sync_client->run(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_SyncManager_nativeConfigureMetaDataSystem(JNIEnv *env, jclass, - jstring baseFile) { +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeConfigureMetaDataSystem(JNIEnv* env, jclass, jstring baseFile) +{ TR_ENTER() try { JStringAccessor base_file_path(env, baseFile); // throws SyncManager::shared().configure_file_system(base_file_path, SyncManager::MetadataMode::NoEncryption); - } CATCH_STD() + } + CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp index b419a697f6..142e64d0c4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp @@ -21,26 +21,28 @@ using namespace realm; -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnCount - (JNIEnv* env, jobject obj, jlong nativeRowPtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnCount(JNIEnv* env, jobject obj, + jlong nativeRowPtr) { - if (!ROW(nativeRowPtr)->is_attached()) + if (!ROW(nativeRowPtr)->is_attached()) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetColumnCount(env, obj, nativeRowPtr); } -JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnName - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnName(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_VALID(env, ROW(nativeRowPtr), columnIndex)) + if (!ROW_AND_COL_INDEX_VALID(env, ROW(nativeRowPtr), columnIndex)) { return NULL; + } return Java_io_realm_internal_UncheckedRow_nativeGetColumnName(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnIndex - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jstring columnName) +JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnIndex(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jstring columnName) { if (!ROW(nativeRowPtr)->is_attached()) return 0; @@ -56,182 +58,206 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnIndex } } -JNIEXPORT jint JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnType - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jint JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnType(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_VALID(env, ROW(nativeRowPtr), columnIndex)) + if (!ROW_AND_COL_INDEX_VALID(env, ROW(nativeRowPtr), columnIndex)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetColumnType(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLong - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLong(JNIEnv* env, jobject obj, jlong nativeRowPtr, + jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Int)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Int)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetLong(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeGetBoolean - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeGetBoolean(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Bool)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Bool)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetBoolean(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jfloat JNICALL Java_io_realm_internal_CheckedRow_nativeGetFloat - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jfloat JNICALL Java_io_realm_internal_CheckedRow_nativeGetFloat(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Float)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Float)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetFloat(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_CheckedRow_nativeGetDouble - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_CheckedRow_nativeGetDouble(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Double)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Double)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetDouble(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetTimestamp - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetTimestamp(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Timestamp)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Timestamp)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetTimestamp(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetString - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetString(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_String)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_String)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetString(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_CheckedRow_nativeGetByteArray - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_CheckedRow_nativeGetByteArray(JNIEnv* env, jobject obj, + jlong nativeRowPtr, + jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Binary)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Binary)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetByteArray(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLink - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLink(JNIEnv* env, jobject obj, jlong nativeRowPtr, + jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetLink(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeIsNullLink - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeIsNullLink(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeIsNullLink(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLinkView - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLinkView(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_LinkList)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_LinkList)) { return 0; + } return Java_io_realm_internal_UncheckedRow_nativeGetLinkView(env, obj, nativeRowPtr, columnIndex); } -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetLong - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetLong(JNIEnv* env, jobject obj, jlong nativeRowPtr, + jlong columnIndex, jlong value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Int)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Int)) { return; + } Java_io_realm_internal_UncheckedRow_nativeSetLong(env, obj, nativeRowPtr, columnIndex, value); } -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetBoolean - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex, jboolean value) +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetBoolean(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex, + jboolean value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Bool)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Bool)) { return; + } Java_io_realm_internal_UncheckedRow_nativeSetBoolean(env, obj, nativeRowPtr, columnIndex, value); } -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetFloat - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex, jfloat value) +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetFloat(JNIEnv* env, jobject obj, jlong nativeRowPtr, + jlong columnIndex, jfloat value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Float)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Float)) { return; + } Java_io_realm_internal_UncheckedRow_nativeSetFloat(env, obj, nativeRowPtr, columnIndex, value); } -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetDouble - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex, jdouble value) +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetDouble(JNIEnv* env, jobject obj, jlong nativeRowPtr, + jlong columnIndex, jdouble value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Double)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Double)) { return; + } Java_io_realm_internal_UncheckedRow_nativeSetDouble(env, obj, nativeRowPtr, columnIndex, value); } -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetTimestamp - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetTimestamp(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex, + jlong value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Timestamp)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Timestamp)) { return; + } Java_io_realm_internal_UncheckedRow_nativeSetTimestamp(env, obj, nativeRowPtr, columnIndex, value); } -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetString - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex, jstring value) +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetString(JNIEnv* env, jobject obj, jlong nativeRowPtr, + jlong columnIndex, jstring value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_String)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_String)) { return; + } Java_io_realm_internal_UncheckedRow_nativeSetString(env, obj, nativeRowPtr, columnIndex, value); } -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetByteArray - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex, jbyteArray value) +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetByteArray(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex, + jbyteArray value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Binary)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Binary)) { return; + } Java_io_realm_internal_UncheckedRow_nativeSetByteArray(env, obj, nativeRowPtr, columnIndex, value); } -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetLink - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetLink(JNIEnv* env, jobject obj, jlong nativeRowPtr, + jlong columnIndex, jlong value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) { return; + } Java_io_realm_internal_UncheckedRow_nativeSetLink(env, obj, nativeRowPtr, columnIndex, value); } -JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeNullifyLink - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeNullifyLink(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnIndex) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) + if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) { return; + } Java_io_realm_internal_UncheckedRow_nativeNullifyLink(env, obj, nativeRowPtr, columnIndex); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index e17d47f692..0c3426811f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -38,7 +38,11 @@ struct ResultsWrapper { Results m_results; ResultsWrapper(Results& results) - : m_collection_weak_ref(), m_notification_token(), m_results(std::move(results)) {} + : m_collection_weak_ref() + , m_notification_token() + , m_results(std::move(results)) + { + } ResultsWrapper(ResultsWrapper&&) = delete; ResultsWrapper& operator=(ResultsWrapper&&) = delete; @@ -46,7 +50,9 @@ struct ResultsWrapper { ResultsWrapper(ResultsWrapper const&) = delete; ResultsWrapper& operator=(ResultsWrapper const&) = delete; - ~ResultsWrapper() {} + ~ResultsWrapper() + { + } }; static void finalize_results(jlong ptr); @@ -57,9 +63,10 @@ static void finalize_results(jlong ptr) delete reinterpret_cast(ptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong query_ptr, - jobject sort_desc, jobject distinct_desc) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, + jlong shared_realm_ptr, jlong query_ptr, + jobject sort_desc, + jobject distinct_desc) { TR_ENTER() try { @@ -69,35 +76,35 @@ Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, jlong } auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - Results results(shared_realm, *query, - SortDescriptor(JavaSortDescriptor(env, sort_desc)), + Results results(shared_realm, *query, SortDescriptor(JavaSortDescriptor(env, sort_desc)), SortDescriptor(JavaSortDescriptor(env, distinct_desc))); auto wrapper = new ResultsWrapper(results); return reinterpret_cast(wrapper); - } CATCH_STD() + } + CATCH_STD() return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeCreateResultsFromLinkView(JNIEnv* env, jclass, jlong shared_realm_ptr, - jlong link_view_ptr, jobject sort_desc) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResultsFromLinkView(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jlong link_view_ptr, + jobject sort_desc) { TR_ENTER() try { auto link_view_ref = reinterpret_cast(link_view_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - Results results(shared_realm, *link_view_ref, util::none, - SortDescriptor(JavaSortDescriptor(env, sort_desc))); + Results results(shared_realm, *link_view_ref, util::none, SortDescriptor(JavaSortDescriptor(env, sort_desc))); auto wrapper = new ResultsWrapper(results); return reinterpret_cast(wrapper); - } CATCH_STD() + } + CATCH_STD() return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr); try { @@ -105,12 +112,13 @@ Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlon auto snapshot_results = wrapper->m_results.snapshot(); auto snapshot_wrapper = new ResultsWrapper(snapshot_results); return reinterpret_cast(snapshot_wrapper); - } CATCH_STD(); + } + CATCH_STD(); return reinterpret_cast(nullptr); } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_Collection_nativeContains(JNIEnv *env, jclass, jlong native_ptr, jlong native_row_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeContains(JNIEnv* env, jclass, jlong native_ptr, + jlong native_row_ptr) { TR_ENTER_PTR(native_ptr); try { @@ -118,24 +126,25 @@ Java_io_realm_internal_Collection_nativeContains(JNIEnv *env, jclass, jlong nati auto row = reinterpret_cast(native_row_ptr); size_t index = wrapper->m_results.index_of(*row); return to_jbool(index != not_found); - } CATCH_STD(); + } + CATCH_STD(); return JNI_FALSE; } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeGetRow(JNIEnv *env, jclass, jlong native_ptr, jint index) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeGetRow(JNIEnv* env, jclass, jlong native_ptr, + jint index) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); auto row = wrapper->m_results.get(static_cast(index)); return reinterpret_cast(new Row(std::move(row))); - } CATCH_STD() + } + CATCH_STD() return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv *env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -144,13 +153,12 @@ Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv *env, jclass, jlong nati if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } - } CATCH_STD() + } + CATCH_STD() return reinterpret_cast(nullptr); - } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeLastRow(JNIEnv *env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeLastRow(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -159,34 +167,34 @@ Java_io_realm_internal_Collection_nativeLastRow(JNIEnv *env, jclass, jlong nativ if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } - } CATCH_STD() + } + CATCH_STD() return reinterpret_cast(nullptr); } -JNIEXPORT void JNICALL -Java_io_realm_internal_Collection_nativeClear(JNIEnv *env, jclass, jlong native_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeClear(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); wrapper->m_results.clear(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeSize(JNIEnv *env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeSize(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); return static_cast(wrapper->m_results.size()); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jobject JNICALL -Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong native_ptr, jlong column_index, - jbyte agg_func) +JNIEXPORT jobject JNICALL Java_io_realm_internal_Collection_nativeAggregate(JNIEnv* env, jclass, jlong native_ptr, + jlong column_index, jbyte agg_func) { TR_ENTER_PTR(native_ptr) try { @@ -231,35 +239,39 @@ Java_io_realm_internal_Collection_nativeAggregate(JNIEnv *env, jclass, jlong nat default: throw std::invalid_argument("Excepted numeric type"); } - } CATCH_STD() + } + CATCH_STD() return static_cast(nullptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeSort(JNIEnv *env, jclass, jlong native_ptr, jobject sort_desc) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeSort(JNIEnv* env, jclass, jlong native_ptr, + jobject sort_desc) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); auto sorted_result = wrapper->m_results.sort(JavaSortDescriptor(env, sort_desc)); return reinterpret_cast(new ResultsWrapper(sorted_result)); - } CATCH_STD() + } + CATCH_STD() return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeDistinct(JNIEnv *env, jclass, jlong native_ptr, jobject distinct_desc) { +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeDistinct(JNIEnv* env, jclass, jlong native_ptr, + jobject distinct_desc) +{ TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); auto distinct_result = wrapper->m_results.distinct(JavaSortDescriptor(env, distinct_desc)); return reinterpret_cast(new ResultsWrapper(distinct_result)); - } CATCH_STD() + } + CATCH_STD() return reinterpret_cast(nullptr); } -JNIEXPORT void JNICALL -Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject instance, jlong native_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject instance, + jlong native_ptr) { TR_ENTER_PTR(native_ptr) @@ -273,62 +285,65 @@ Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject inst auto cb = [=](CollectionChangeSet const& changes, std::exception_ptr err) { // OS will call all notifiers' callback in one run, so check the Java exception first!! - if (env->ExceptionCheck()) return; + if (env->ExceptionCheck()) + return; if (err) { try { std::rethrow_exception(err); - } catch(const std::exception& e) { + } + catch (const std::exception& e) { realm::jni_util::Log::e("Caught exception in collection change callback %1", e.what()); return; } } - wrapper->m_collection_weak_ref.call_with_local_ref(env, [&] (JNIEnv* local_env, jobject collection_obj) { - local_env->CallVoidMethod(collection_obj, notify_change_listeners, - reinterpret_cast(changes.empty() ? 0 : new CollectionChangeSet(changes))); + wrapper->m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv* local_env, jobject collection_obj) { + local_env->CallVoidMethod( + collection_obj, notify_change_listeners, + reinterpret_cast(changes.empty() ? 0 : new CollectionChangeSet(changes))); }); }; - wrapper->m_notification_token = wrapper->m_results.add_notification_callback(cb); - } CATCH_STD() + wrapper->m_notification_token = wrapper->m_results.add_notification_callback(cb); + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_Collection_nativeStopListening(JNIEnv *env, jobject, jlong native_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); wrapper->m_notification_token = {}; - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeGetFinalizerPtr(JNIEnv *, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_results); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeWhere(JNIEnv *env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeWhere(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); auto table_view = wrapper->m_results.get_tableview(); - Query *query = new Query(table_view.get_parent(), - std::unique_ptr(new TableView(std::move(table_view)))); + Query* query = + new Query(table_view.get_parent(), std::unique_ptr(new TableView(std::move(table_view)))); return reinterpret_cast(query); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv *env, jclass, jlong native_ptr, jlong row_native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv* env, jclass, jlong native_ptr, + jlong row_native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -336,13 +351,14 @@ Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv *env, jclass, jlong nativ auto row = reinterpret_cast(row_native_ptr); return static_cast(wrapper->m_results.index_of(*row)); - } CATCH_STD() + } + CATCH_STD() return npos; } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_Collection_nativeIndexOfBySourceRowIndex(JNIEnv *env, jclass, jlong native_ptr, - jlong source_row_index) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeIndexOfBySourceRowIndex(JNIEnv* env, jclass, + jlong native_ptr, + jlong source_row_index) { TR_ENTER_PTR(native_ptr) try { @@ -350,13 +366,12 @@ Java_io_realm_internal_Collection_nativeIndexOfBySourceRowIndex(JNIEnv *env, jcl auto index = static_cast(source_row_index); return static_cast(wrapper->m_results.index_of(index)); - } CATCH_STD() + } + CATCH_STD() return npos; - } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv *env, jclass, jlong native_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -366,12 +381,12 @@ Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv *env, jclass, jlong na row->move_last_over(); return JNI_TRUE; } - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_Collection_nativeDeleteFirst(JNIEnv *env, jclass, jlong native_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteFirst(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) @@ -382,12 +397,13 @@ Java_io_realm_internal_Collection_nativeDeleteFirst(JNIEnv *env, jclass, jlong n row->move_last_over(); return JNI_TRUE; } - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT void JNICALL -Java_io_realm_internal_Collection_nativeDelete(JNIEnv *env, jclass, jlong native_ptr, jlong index) +JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeDelete(JNIEnv* env, jclass, jlong native_ptr, + jlong index) { TR_ENTER_PTR(native_ptr) @@ -397,22 +413,22 @@ Java_io_realm_internal_Collection_nativeDelete(JNIEnv *env, jclass, jlong native if (row.is_attached()) { row.move_last_over(); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_Collection_nativeIsValid(JNIEnv *env, jclass, jlong native_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeIsValid(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); return wrapper->m_results.is_valid(); - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT jbyte JNICALL -Java_io_realm_internal_Collection_nativeGetMode(JNIEnv *env, jclass, jlong native_ptr) +JNIEXPORT jbyte JNICALL Java_io_realm_internal_Collection_nativeGetMode(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -429,7 +445,7 @@ Java_io_realm_internal_Collection_nativeGetMode(JNIEnv *env, jclass, jlong nativ case Results::Mode::TableView: return io_realm_internal_Collection_MODE_TABLEVIEW; } - } CATCH_STD() + } + CATCH_STD() return -1; // Invalid mode value } - diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_CollectionChangeSet.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_CollectionChangeSet.cpp index 450324c171..f85fb853af 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_CollectionChangeSet.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_CollectionChangeSet.cpp @@ -46,9 +46,9 @@ static jintArray index_set_to_jint_array(JNIEnv* env, const IndexSet& index_set) if (ranges_vector.size() > io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH) { std::ostringstream error_msg; - error_msg << "There are too many ranges changed in this change set. They cannot fit into an array." << - " ranges_vector's size: " << ranges_vector.size() << - " Java array's max size: " << io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH << "."; + error_msg << "There are too many ranges changed in this change set. They cannot fit into an array." + << " ranges_vector's size: " << ranges_vector.size() + << " Java array's max size: " << io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH << "."; ThrowException(env, IllegalState, error_msg.str()); return nullptr; } @@ -69,9 +69,9 @@ static jintArray index_set_to_indices_array(JNIEnv* env, const IndexSet& index_s } if (indices_vector.size() > io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH) { std::ostringstream error_msg; - error_msg << "There are too many indices in this change set. They cannot fit into an array." << - " indices_vector's size: " << indices_vector.size() << - " Java array's max size: " << io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH << "."; + error_msg << "There are too many indices in this change set. They cannot fit into an array." + << " indices_vector's size: " << indices_vector.size() + << " Java array's max size: " << io_realm_internal_CollectionChangeSet_MAX_ARRAY_LENGTH << "."; ThrowException(env, IllegalState, error_msg.str()); return nullptr; } @@ -80,15 +80,14 @@ static jintArray index_set_to_indices_array(JNIEnv* env, const IndexSet& index_s return jint_array; } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_CollectionChangeSet_nativeGetFinalizerPtr(JNIEnv*, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_CollectionChangeSet_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_changeset); } -JNIEXPORT jintArray JNICALL -Java_io_realm_internal_CollectionChangeSet_nativeGetRanges(JNIEnv *env, jclass, jlong native_ptr, jint type) +JNIEXPORT jintArray JNICALL Java_io_realm_internal_CollectionChangeSet_nativeGetRanges(JNIEnv* env, jclass, + jlong native_ptr, jint type) { TR_ENTER_PTR(native_ptr) // no throws @@ -102,12 +101,11 @@ Java_io_realm_internal_CollectionChangeSet_nativeGetRanges(JNIEnv *env, jclass, return index_set_to_jint_array(env, change_set.modifications_new); default: REALM_UNREACHABLE(); - break; } } -JNIEXPORT jintArray JNICALL -Java_io_realm_internal_CollectionChangeSet_nativeGetIndices(JNIEnv *env, jclass, jlong native_ptr, jint type) +JNIEXPORT jintArray JNICALL Java_io_realm_internal_CollectionChangeSet_nativeGetIndices(JNIEnv* env, jclass, + jlong native_ptr, jint type) { TR_ENTER_PTR(native_ptr) // no throws @@ -121,7 +119,5 @@ Java_io_realm_internal_CollectionChangeSet_nativeGetIndices(JNIEnv *env, jclass, return index_set_to_indices_array(env, change_set.modifications_new); default: REALM_UNREACHABLE(); - break; } } - diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp index 119cdf30fb..4ad4066411 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_LinkView.cpp @@ -21,204 +21,216 @@ using namespace realm; static void finalize_link_view(jlong ptr); -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetRow - (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) +JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetRow(JNIEnv* env, jobject, jlong nativeLinkViewPtr, + jlong pos) { TR_ENTER_PTR(nativeLinkViewPtr) - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return -1; } try { LinkViewRef lvr = *lv; - Row* row = new Row( (*lvr)[ S(pos) ] ); + Row* row = new Row((*lvr)[S(pos)]); return reinterpret_cast(row); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetRowIndex - (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong linkViewIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetRowIndex(JNIEnv* env, jobject, + jlong nativeLinkViewPtr, + jlong linkViewIndex) { TR_ENTER_PTR(nativeLinkViewPtr) - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, linkViewIndex)) { return -1; } try { LinkViewRef lvr = *lv; return lvr->get(S(linkViewIndex)).get_index(); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeAdd - (JNIEnv* env, jclass, jlong nativeLinkViewPtr, jlong rowIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeAdd(JNIEnv* env, jclass, jlong nativeLinkViewPtr, + jlong rowIndex) { TR_ENTER_PTR(nativeLinkViewPtr) - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); try { LinkViewRef lvr = *lv; - lvr->add( S(rowIndex) ); - } CATCH_STD() + lvr->add(S(rowIndex)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeInsert - (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos, jlong rowIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeInsert(JNIEnv* env, jobject, jlong nativeLinkViewPtr, + jlong pos, jlong rowIndex) { TR_ENTER_PTR(nativeLinkViewPtr) - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); try { LinkViewRef lvr = *lv; - lvr->insert( S(pos), S(rowIndex) ); - } CATCH_STD() + lvr->insert(S(pos), S(rowIndex)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeSet - (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos, jlong rowIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeSet(JNIEnv* env, jobject, jlong nativeLinkViewPtr, + jlong pos, jlong rowIndex) { TR_ENTER_PTR(nativeLinkViewPtr) - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return; } try { LinkViewRef lvr = *lv; - lvr->set( S(pos), S(rowIndex) ); - } CATCH_STD() + lvr->set(S(pos), S(rowIndex)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeMove - (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong old_pos, jlong new_pos) +JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeMove(JNIEnv* env, jobject, jlong nativeLinkViewPtr, + jlong old_pos, jlong new_pos) { TR_ENTER_PTR(nativeLinkViewPtr) try { - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; size_t size = lvr->size(); if (old_pos < 0 || new_pos < 0 || size_t(old_pos) >= size || size_t(new_pos) >= size) { - ThrowException(env, IndexOutOfBounds, - "Indices must be within range [0, " + num_to_string(size) + "[. " + - "Yours were (" + num_to_string(old_pos) + "," + num_to_string(new_pos) + ")"); + ThrowException(env, IndexOutOfBounds, "Indices must be within range [0, " + num_to_string(size) + "[. " + + "Yours were (" + num_to_string(old_pos) + "," + + num_to_string(new_pos) + ")"); return; } - lvr->move( S(old_pos), S(new_pos) ); - } CATCH_STD() + lvr->move(S(old_pos), S(new_pos)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemove - (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) +JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemove(JNIEnv* env, jobject, jlong nativeLinkViewPtr, + jlong pos) { TR_ENTER_PTR(nativeLinkViewPtr) - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); if (!ROW_INDEX_VALID(env, *lv, pos)) { return; } try { LinkViewRef lvr = *lv; - return lvr->remove( S(pos) ); - } CATCH_STD() + return lvr->remove(S(pos)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeClear - (JNIEnv* env, jclass, jlong nativeLinkViewPtr) +JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeClear(JNIEnv* env, jclass, jlong nativeLinkViewPtr) { TR_ENTER_PTR(nativeLinkViewPtr) try { - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; return lvr->clear(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeSize - (JNIEnv* env, jobject, jlong nativeLinkViewPtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeSize(JNIEnv* env, jobject, jlong nativeLinkViewPtr) { - + TR_ENTER_PTR(nativeLinkViewPtr) try { - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; - return lvr->size(); - } CATCH_STD() + return static_cast(lvr->size()); + } + CATCH_STD() return 0; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsEmpty - (JNIEnv* env, jobject, jlong nativeLinkViewPtr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsEmpty(JNIEnv* env, jobject, + jlong nativeLinkViewPtr) { TR_ENTER_PTR(nativeLinkViewPtr) try { - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; - return lvr->is_empty(); - } CATCH_STD() + return to_jbool(lvr->is_empty()); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeWhere - (JNIEnv *env, jobject, jlong nativeLinkViewPtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeWhere(JNIEnv* env, jobject, jlong nativeLinkViewPtr) { TR_ENTER_PTR(nativeLinkViewPtr) try { - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; - Query *queryPtr = new Query(lvr->get_target_table().where(LinkViewRef(lvr))); + Query* queryPtr = new Query(lvr->get_target_table().where(LinkViewRef(lvr))); return reinterpret_cast(queryPtr); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsAttached - (JNIEnv *env, jobject, jlong nativeLinkViewPtr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_LinkView_nativeIsAttached(JNIEnv* env, jobject, + jlong nativeLinkViewPtr) { TR_ENTER_PTR(nativeLinkViewPtr) try { - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; - return lvr->is_attached(); - } CATCH_STD() + return to_jbool(lvr->is_attached()); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeFind - (JNIEnv *env, jobject, jlong nativeLinkViewPtr, jlong targetRowIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeFind(JNIEnv* env, jobject, jlong nativeLinkViewPtr, + jlong targetRowIndex) { TR_ENTER_PTR(nativeLinkViewPtr) try { - LinkViewRef *lv = LV(nativeLinkViewPtr); + LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; if (!ROW_INDEX_VALID(env, &lvr->get_target_table(), targetRowIndex)) { return -1; } - size_t ndx = lvr->find(targetRowIndex); + size_t ndx = lvr->find(static_cast(targetRowIndex)); return to_jlong_or_not_found(ndx); - } CATCH_STD() + } + CATCH_STD() return -1; } -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveAllTargetRows - (JNIEnv *env, jobject, jlong nativeLinkViewPtr) +JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveAllTargetRows(JNIEnv* env, jobject, + jlong nativeLinkViewPtr) { TR_ENTER_PTR(nativeLinkViewPtr) try { LinkViewRef* lv = LV(nativeLinkViewPtr); LinkViewRef lvr = *lv; lvr->remove_all_target_rows(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable - (JNIEnv*, jobject, jlong nativeLinkViewPtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable(JNIEnv*, jobject, + jlong nativeLinkViewPtr) { TR_ENTER_PTR(nativeLinkViewPtr) @@ -230,8 +242,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetTargetTable return reinterpret_cast(pTable); } -JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveTargetRow - (JNIEnv* env, jobject, jlong nativeLinkViewPtr, jlong pos) +JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveTargetRow(JNIEnv* env, jobject, + jlong nativeLinkViewPtr, jlong pos) { TR_ENTER_PTR(nativeLinkViewPtr) LinkViewRef* lv = LV(nativeLinkViewPtr); @@ -240,8 +252,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_LinkView_nativeRemoveTargetRow } try { LinkViewRef lvr = *lv; - return lvr->remove_target_row( S(pos) ); - } CATCH_STD() + return lvr->remove_target_row(S(pos)); + } + CATCH_STD() } static void finalize_link_view(jlong ptr) @@ -250,8 +263,7 @@ static void finalize_link_view(jlong ptr) LangBindHelper::unbind_linklist_ptr(*LV(ptr)); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetFinalizerPtr - (JNIEnv *, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_LinkView_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_link_view); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_NativeObjectReference.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_NativeObjectReference.cpp index ab5de83fc5..134936046d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_NativeObjectReference.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_NativeObjectReference.cpp @@ -18,8 +18,10 @@ typedef void (*FinalizeFunc)(jlong); -JNIEXPORT void JNICALL Java_io_realm_internal_NativeObjectReference_nativeCleanUp -(JNIEnv *, jclass, jlong finalizer_ptr, jlong native_ptr) { +JNIEXPORT void JNICALL Java_io_realm_internal_NativeObjectReference_nativeCleanUp(JNIEnv*, jclass, + jlong finalizer_ptr, + jlong native_ptr) +{ FinalizeFunc finalize_func = reinterpret_cast(finalizer_ptr); finalize_func(native_ptr); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 09e2b2e0e6..ccfa3aa222 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -36,40 +36,45 @@ using namespace realm; using namespace realm::_impl; static_assert(SchemaMode::Automatic == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_AUTOMATIC), ""); + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_AUTOMATIC), + ""); static_assert(SchemaMode::ReadOnly == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_READONLY), ""); + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_READONLY), + ""); static_assert(SchemaMode::ResetFile == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_RESET_FILE), ""); + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_RESET_FILE), + ""); static_assert(SchemaMode::Additive == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_ADDITIVE), ""); -static_assert(SchemaMode::Manual == - static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_MANUAL), ""); + static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_ADDITIVE), + ""); +static_assert(SchemaMode::Manual == static_cast(io_realm_internal_SharedRealm_SCHEMA_MODE_VALUE_MANUAL), + ""); static void finalize_shared_realm(jlong ptr); -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv *env, jclass, jstring temporary_directory_path) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv* env, jclass, + jstring temporary_directory_path) { TR_ENTER() try { - JStringAccessor path(env, temporary_directory_path); // throws + JStringAccessor path(env, temporary_directory_path); // throws SharedGroupOptions::set_sys_tmp_dir(std::string(path)); // throws - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstring realm_path, jbyteArray key, - jbyte schema_mode, jboolean in_memory, jboolean cache, jlong /* schema_version */, jboolean disable_format_upgrade, - jboolean auto_change_notification, REALM_UNUSED jstring sync_server_url, jstring /*sync_user_token*/) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateConfig( + JNIEnv* env, jclass, jstring realm_path, jbyteArray key, jbyte schema_mode, jboolean in_memory, jboolean cache, + jlong /* schema_version */, jboolean disable_format_upgrade, jboolean auto_change_notification, + REALM_UNUSED jstring sync_server_url, jstring /*sync_user_token*/) { TR_ENTER() try { JStringAccessor path(env, realm_path); // throws JniByteArray key_array(env, key); - Realm::Config *config = new Realm::Config(); + Realm::Config* config = new Realm::Config(); config->path = path; // config->schema_version = schema_version; TODO: Disabled until we remove version handling from Java config->encryption_key = key_array; @@ -82,13 +87,13 @@ Java_io_realm_internal_SharedRealm_nativeCreateConfig(JNIEnv *env, jclass, jstri config->force_sync_history = true; } return reinterpret_cast(config); - } CATCH_STD() + } + CATCH_STD() return static_cast(NULL); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv*, jclass, jlong config_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv*, jclass, jlong config_ptr) { TR_ENTER_PTR(config_ptr) @@ -96,8 +101,8 @@ Java_io_realm_internal_SharedRealm_nativeCloseConfig(JNIEnv*, jclass, jlong conf delete config; } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlong config_ptr, jobject realm_notifier) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv* env, jclass, jlong config_ptr, + jobject realm_notifier) { TR_ENTER_PTR(config_ptr) @@ -106,12 +111,13 @@ Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv *env, jclass, jlo auto shared_realm = Realm::get_shared_realm(*config); shared_realm->m_binding_context = JavaBindingContext::create(env, realm_notifier); return reinterpret_cast(new SharedRealm(std::move(shared_realm))); - } CATCH_STD() + } + CATCH_STD() return static_cast(NULL); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -120,42 +126,45 @@ Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong shared_realm->close(); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->begin_transaction(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->commit_transaction(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->cancel_transaction(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv*, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv*, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -163,34 +172,36 @@ Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv*, jclass, jlong return static_cast(shared_realm->is_in_transaction()); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv *env, jclass , jlong shared_realm_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return reinterpret_cast(&shared_realm->read_group()); - } CATCH_STD() + } + CATCH_STD() return static_cast(NULL); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(ObjectStore::get_schema_version(shared_realm->read_group())); - } CATCH_STD() + } + CATCH_STD() return static_cast(ObjectStore::NotVersioned); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv *env, jclass, jlong shared_realm_ptr, jlong version) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv* env, jclass, + jlong shared_realm_ptr, jlong version) { TR_ENTER_PTR(shared_realm_ptr) @@ -204,34 +215,36 @@ Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv *env, jclass, jlong s } ObjectStore::set_schema_version(shared_realm->read_group(), static_cast(version)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { - return static_cast(ObjectStore::is_empty(shared_realm->read_group())); - } CATCH_STD() + return to_jbool(ObjectStore::is_empty(shared_realm->read_group())); + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeRefresh(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRefresh(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->refresh(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlongArray JNICALL -Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -252,13 +265,13 @@ Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv *env, jclass, jlong env->SetLongArrayRegion(version_data, 0, 2, version_array); return version_data; - } CATCH_STD () + } + CATCH_STD() return NULL; } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -267,8 +280,8 @@ Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_ } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv* env, jclass, jlong shared_realm_ptr, + jstring table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -283,13 +296,14 @@ Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv *env, jclass, jlong sha } Table* pTable = LangBindHelper::get_or_add_table(shared_realm->read_group(), name); return reinterpret_cast(pTable); - } CATCH_STD() + } + CATCH_STD() return static_cast(NULL); } -JNIEXPORT jstring JNICALL -Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv *env, jclass, jlong shared_realm_ptr, jint index) +JNIEXPORT jstring JNICALL Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv* env, jclass, + jlong shared_realm_ptr, jint index) { TR_ENTER_PTR(shared_realm_ptr) @@ -297,12 +311,14 @@ Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv *env, jclass, jlong auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return to_jstring(env, shared_realm->read_group().get_table_name(static_cast(index))); - } CATCH_STD() + } + CATCH_STD() return NULL; } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -310,13 +326,15 @@ Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv *env, jclass, jlong sha try { JStringAccessor name(env, table_name); return static_cast(shared_realm->read_group().has_table(name)); - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv *env, jclass, jlong shared_realm_ptr, - jstring old_table_name, jstring new_table_name) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring old_table_name, + jstring new_table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -331,11 +349,13 @@ Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv *env, jclass, jlong } JStringAccessor new_name(env, new_table_name); shared_realm->read_group().rename_table(old_name, new_name); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring table_name) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -349,25 +369,25 @@ Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv *env, jclass, jlong return; } shared_realm->read_group().remove_table(name); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(shared_realm->read_group().size()); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv *env, jclass, jlong shared_realm_ptr, jstring path, - jbyteArray key) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv* env, jclass, jlong shared_realm_ptr, + jstring path, jbyteArray key) { TR_ENTER_PTR(shared_realm_ptr); @@ -376,25 +396,27 @@ Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv *env, jclass, jlong sh JStringAccessor path_str(env, path); JniByteArray key_buffer(env, key); shared_realm->write_copy(path_str, key_buffer); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { using rf = realm::_impl::RealmFriend; - return static_cast(rf::get_shared_group(*shared_realm).wait_for_change()); - } CATCH_STD() + return to_jbool(rf::get_shared_group(*shared_realm).wait_for_change()); + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr); @@ -402,24 +424,26 @@ Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv *env, jclass, try { using rf = realm::_impl::RealmFriend; rf::get_shared_group(*shared_realm).wait_for_change_release(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { - return static_cast(shared_realm->compact()); - } CATCH_STD() + return to_jbool(shared_realm->compact()); + } + CATCH_STD() return JNI_FALSE; } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -427,33 +451,36 @@ Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv *env, jclass, try { using rf = realm::_impl::RealmFriend; auto& shared_group = rf::get_shared_group(*shared_realm); - return LangBindHelper::get_version_of_latest_snapshot(shared_group); - } CATCH_STD () + return static_cast(LangBindHelper::get_version_of_latest_snapshot(shared_group)); + } + CATCH_STD() return 0; } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv *env, jclass, jlong shared_realm_ptr, - jlong schema_ptr, jlong version) { +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv* env, jclass, + jlong shared_realm_ptr, jlong schema_ptr, + jlong version) +{ TR_ENTER_PTR(shared_realm_ptr) try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto *schema = reinterpret_cast(schema_ptr); + auto* schema = reinterpret_cast(schema_ptr); shared_realm->update_schema(*schema, static_cast(version), nullptr, true); } CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeRequiresMigration(JNIEnv *env, jclass, jlong nativePtr, - jlong nativeSchemaPtr) { +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeRequiresMigration(JNIEnv* env, jclass, + jlong nativePtr, + jlong nativeSchemaPtr) +{ TR_ENTER() try { auto shared_realm = *(reinterpret_cast(nativePtr)); - auto *schema = reinterpret_cast(nativeSchemaPtr); - const std::vector &change_list = shared_realm->schema().compare(*schema); - return static_cast(!change_list.empty()); + auto* schema = reinterpret_cast(nativeSchemaPtr); + const std::vector& change_list = shared_realm->schema().compare(*schema); + return to_jbool(!change_list.empty()); } CATCH_STD() return JNI_FALSE; @@ -465,30 +492,32 @@ static void finalize_shared_realm(jlong ptr) delete reinterpret_cast(ptr); } -JNIEXPORT jlong JNICALL -Java_io_realm_internal_SharedRealm_nativeGetFinalizerPtr(JNIEnv*, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_shared_realm); } -JNIEXPORT void JNICALL -Java_io_realm_internal_SharedRealm_nativeSetAutoRefresh(JNIEnv *env, jclass, jlong shared_realm_ptr, jboolean enabled) +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetAutoRefresh(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jboolean enabled) { TR_ENTER_PTR(shared_realm_ptr) try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); shared_realm->set_auto_refresh(to_bool(enabled)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_SharedRealm_nativeIsAutoRefresh(JNIEnv *env, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsAutoRefresh(JNIEnv* env, jclass, + jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) try { auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); return to_jbool(shared_realm->auto_refresh()); - } CATCH_STD() + } + CATCH_STD() return JNI_FALSE; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 60e7bcf85a..eca86a2800 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -25,15 +25,12 @@ using namespace realm; static void finalize_table(jlong ptr); -inline static bool is_allowed_to_index(JNIEnv* env, DataType column_type) { - if (!(column_type == type_String || - column_type == type_Int || - column_type == type_Bool || - column_type == type_Timestamp || - column_type == type_OldDateTime)) { - ThrowException(env, IllegalArgument, - "This field cannot be indexed - " - "Only String/byte/short/int/long/boolean/Date fields are supported."); +inline static bool is_allowed_to_index(JNIEnv* env, DataType column_type) +{ + if (!(column_type == type_String || column_type == type_Int || column_type == type_Bool || + column_type == type_Timestamp || column_type == type_OldDateTime)) { + ThrowException(env, IllegalArgument, "This field cannot be indexed - " + "Only String/byte/short/int/long/boolean/Date fields are supported."); return false; } return true; @@ -43,51 +40,59 @@ inline static bool is_allowed_to_index(JNIEnv* env, DataType column_type) { // A spec is shared on subtables that are not in Mixed columns. // -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumn - (JNIEnv *env, jobject, jlong nativeTablePtr, jint colType, jstring name, jboolean isNullable) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumn(JNIEnv* env, jobject, jlong nativeTablePtr, + jint colType, jstring name, jboolean isNullable) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) + if (!TABLE_VALID(env, TBL(nativeTablePtr))) { return 0; + } if (TBL(nativeTablePtr)->has_shared_type()) { - ThrowException(env, UnsupportedOperation, "Not allowed to add field in subtable. Use getSubtableSchema() on root table instead."); + ThrowException(env, UnsupportedOperation, + "Not allowed to add field in subtable. Use getSubtableSchema() on root table instead."); return 0; } try { JStringAccessor name2(env, name); // throws - bool is_column_nullable = isNullable != 0 ? true : false; + bool is_column_nullable = to_bool(isNullable); DataType dataType = DataType(colType); if (is_column_nullable && dataType == type_LinkList) { - ThrowException(env, IllegalArgument, "List fields cannot be nullable."); + ThrowException(env, IllegalArgument, "List fields cannot be nullable."); } - return TBL(nativeTablePtr)->add_column(dataType, name2, is_column_nullable); - } CATCH_STD() + return static_cast(TBL(nativeTablePtr)->add_column(dataType, name2, is_column_nullable)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumnLink - (JNIEnv* env, jobject, jlong nativeTablePtr, jint colType, jstring name, jlong targetTablePtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumnLink(JNIEnv* env, jobject, jlong nativeTablePtr, + jint colType, jstring name, + jlong targetTablePtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) - return 0; - if (TBL(nativeTablePtr)->has_shared_type()) { - ThrowException(env, UnsupportedOperation, "Not allowed to add field in subtable. Use getSubtableSchema() on root table instead."); - return 0; - } - if (!TBL(targetTablePtr)->is_group_level()) { - ThrowException(env, UnsupportedOperation, "Links can only be made to toplevel tables."); - return 0; - } - try { - JStringAccessor name2(env, name); // throws - return TBL(nativeTablePtr)->add_column_link(DataType(colType), name2, *TBL(targetTablePtr)); - } CATCH_STD() + if (!TABLE_VALID(env, TBL(nativeTablePtr))) { return 0; + } + if (TBL(nativeTablePtr)->has_shared_type()) { + ThrowException(env, UnsupportedOperation, + "Not allowed to add field in subtable. Use getSubtableSchema() on root table instead."); + return 0; + } + if (!TBL(targetTablePtr)->is_group_level()) { + ThrowException(env, UnsupportedOperation, "Links can only be made to toplevel tables."); + return 0; + } + try { + JStringAccessor name2(env, name); // throws + return static_cast(TBL(nativeTablePtr)->add_column_link(DataType(colType), name2, *TBL(targetTablePtr))); + } + CATCH_STD() + return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativePivot -(JNIEnv *env, jobject, jlong dataTablePtr, jlong stringCol, jlong intCol, jint operation, jlong resultTablePtr) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativePivot(JNIEnv* env, jobject, jlong dataTablePtr, + jlong stringCol, jlong intCol, jint operation, + jlong resultTablePtr) { Table* dataTable = TBL(dataTablePtr); Table* resultTable = TBL(resultTablePtr); @@ -115,56 +120,65 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativePivot try { dataTable->aggregate(S(stringCol), S(intCol), pivotOp, *resultTable); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveColumn - (JNIEnv *env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveColumn(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) + if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { return; + } if (TBL(nativeTablePtr)->has_shared_type()) { - ThrowException(env, UnsupportedOperation, "Not allowed to remove field in subtable. Use getSubtableSchema() on root table instead."); + ThrowException(env, UnsupportedOperation, + "Not allowed to remove field in subtable. Use getSubtableSchema() on root table instead."); return; } try { TBL(nativeTablePtr)->remove_column(S(columnIndex)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRenameColumn - (JNIEnv *env, jobject, jlong nativeTablePtr, jlong columnIndex, jstring name) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRenameColumn(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jstring name) { - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) + if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { return; + } if (TBL(nativeTablePtr)->has_shared_type()) { - ThrowException(env, UnsupportedOperation, "Not allowed to rename field in subtable. Use getSubtableSchema() on root table instead."); + ThrowException(env, UnsupportedOperation, + "Not allowed to rename field in subtable. Use getSubtableSchema() on root table instead."); return; } try { JStringAccessor name2(env, name); // throws TBL(nativeTablePtr)->rename_column(S(columnIndex), name2); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable - (JNIEnv *env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable(JNIEnv* env, jobject, + jlong nativeTablePtr, + jlong columnIndex) { - Table *table = TBL(nativeTablePtr); + Table* table = TBL(nativeTablePtr); if (!TBL_AND_COL_INDEX_VALID(env, table, columnIndex)) { - return false; + return JNI_FALSE; } if (table->has_shared_type()) { ThrowException(env, UnsupportedOperation, "Not allowed to convert field in subtable."); - return false; + return JNI_FALSE; } size_t column_index = S(columnIndex); - return table->is_nullable(column_index); + return to_jbool(table->is_nullable(column_index)); } // General comments about the implementation of -// Java_io_realm_internal_Table_nativeConvertColumnToNullable and Java_io_realm_internal_Table_nativeConvertColumnToNotNullable +// Java_io_realm_internal_Table_nativeConvertColumnToNullable and +// Java_io_realm_internal_Table_nativeConvertColumnToNotNullable // // 1. converting a (not-)nullable column is idempotent (and is implemented as a no-op) // 2. not all column types can be converted (cannot be (not-)nullable) @@ -177,10 +191,11 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable // 5. search indexing must be preserved // 6. removing the original column and renaming the temporary column will make it look like original is being modified -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullable - (JNIEnv *env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullable(JNIEnv* env, jobject, + jlong nativeTablePtr, + jlong columnIndex) { - Table *table = TBL(nativeTablePtr); + Table* table = TBL(nativeTablePtr); if (!TBL_AND_COL_INDEX_VALID(env, table, columnIndex)) { return; } @@ -196,9 +211,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl std::string column_name = table->get_column_name(column_index); DataType column_type = table->get_column_type(column_index); - if (column_type == type_Link || - column_type == type_LinkList || - column_type == type_Mixed || + if (column_type == type_Link || column_type == type_LinkList || column_type == type_Mixed || column_type == type_Table) { ThrowException(env, IllegalArgument, "Wrong type - cannot be converted to nullable."); } @@ -221,7 +234,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl for (size_t i = 0; i < table->size(); ++i) { switch (column_type) { - case type_String: { + case type_String: { // Payload copy is needed StringData sd(table->get_string(column_index + 1, i)); table->set_string(column_index, i, sd); @@ -265,13 +278,15 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl } table->remove_column(column_index + 1); table->rename_column(table->get_column_index(tmp_column_name), column_name); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNullable - (JNIEnv *env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNullable(JNIEnv* env, jobject, + jlong nativeTablePtr, + jlong columnIndex) { - Table *table = TBL(nativeTablePtr); + Table* table = TBL(nativeTablePtr); if (!TBL_AND_COL_INDEX_VALID(env, table, columnIndex)) { return; } @@ -287,9 +302,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull std::string column_name = table->get_column_name(column_index); DataType column_type = table->get_column_type(column_index); - if (column_type == type_Link || - column_type == type_LinkList || - column_type == type_Mixed || + if (column_type == type_Link || column_type == type_LinkList || column_type == type_Mixed || column_type == type_Table) { ThrowException(env, IllegalArgument, "Wrong type - cannot be converted to nullable."); } @@ -391,177 +404,199 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull } table->remove_column(column_index + 1); table->rename_column(table->get_column_index(tmp_column_name), column_name); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSize( - JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSize(JNIEnv* env, jobject, jlong nativeTablePtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) + if (!TABLE_VALID(env, TBL(nativeTablePtr))) { return 0; - return TBL(nativeTablePtr)->size(); // noexcept + } + return static_cast(TBL(nativeTablePtr)->size()); // noexcept } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear( - JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear(JNIEnv* env, jobject, jlong nativeTablePtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) + if (!TABLE_VALID(env, TBL(nativeTablePtr))) { return; + } try { TBL(nativeTablePtr)->clear(); - } CATCH_STD() + } + CATCH_STD() } // -------------- Column information -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnCount( - JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnCount(JNIEnv* env, jobject, jlong nativeTablePtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) + if (!TABLE_VALID(env, TBL(nativeTablePtr))) { return 0; - return TBL(nativeTablePtr)->get_column_count(); // noexcept + } + return static_cast(TBL(nativeTablePtr)->get_column_count()); // noexcept } -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetColumnName( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetColumnName(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) - return NULL; + if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { + return nullptr; + } try { - return to_jstring(env, TBL(nativeTablePtr)->get_column_name( S(columnIndex))); - } CATCH_STD(); - return NULL; + return to_jstring(env, TBL(nativeTablePtr)->get_column_name(S(columnIndex))); + } + CATCH_STD(); + REALM_UNREACHABLE(); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnIndex( - JNIEnv* env, jobject, jlong nativeTablePtr, jstring columnName) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnIndex(JNIEnv* env, jobject, jlong nativeTablePtr, + jstring columnName) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) + if (!TABLE_VALID(env, TBL(nativeTablePtr))) { return 0; + } try { - JStringAccessor columnName2(env, columnName); // throws - return to_jlong_or_not_found( TBL(nativeTablePtr)->get_column_index(columnName2) ); // noexcept - } CATCH_STD() + JStringAccessor columnName2(env, columnName); // throws + return to_jlong_or_not_found(TBL(nativeTablePtr)->get_column_index(columnName2)); // noexcept + } + CATCH_STD() return 0; } -JNIEXPORT jint JNICALL Java_io_realm_internal_Table_nativeGetColumnType( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jint JNICALL Java_io_realm_internal_Table_nativeGetColumnType(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) + if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { return 0; + } - return static_cast( TBL(nativeTablePtr)->get_column_type( S(columnIndex)) ); // noexcept + return static_cast(TBL(nativeTablePtr)->get_column_type(S(columnIndex))); // noexcept } // ---------------- Row handling -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddEmptyRow( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong rows) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddEmptyRow(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong rows) { Table* pTable = TBL(nativeTablePtr); - if (!TABLE_VALID(env, pTable)) + if (!TABLE_VALID(env, pTable)) { return 0; - if (pTable->get_column_count() < 1){ + } + if (pTable->get_column_count() < 1) { ThrowException(env, IndexOutOfBounds, concat_stringdata("Table has no columns: ", pTable->get_name())); return 0; } try { - return static_cast( pTable->add_empty_row( S(rows)) ); - } CATCH_STD() + return static_cast(pTable->add_empty_row(S(rows))); + } + CATCH_STD() return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemove( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong rowIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemove(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong rowIndex) { - if (!TBL_AND_ROW_INDEX_VALID(env, TBL(nativeTablePtr), rowIndex)) + if (!TBL_AND_ROW_INDEX_VALID(env, TBL(nativeTablePtr), rowIndex)) { return; + } try { TBL(nativeTablePtr)->remove(S(rowIndex)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveLast( - JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveLast(JNIEnv* env, jobject, jlong nativeTablePtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) + if (!TABLE_VALID(env, TBL(nativeTablePtr))) { return; + } try { TBL(nativeTablePtr)->remove_last(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMoveLastOver - (JNIEnv *env, jobject, jlong nativeTablePtr, jlong rowIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMoveLastOver(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong rowIndex) { - if (!TBL_AND_ROW_INDEX_VALID_OFFSET(env, TBL(nativeTablePtr), rowIndex, false)) + if (!TBL_AND_ROW_INDEX_VALID_OFFSET(env, TBL(nativeTablePtr), rowIndex, false)) { return; + } try { TBL(nativeTablePtr)->move_last_over(S(rowIndex)); - } CATCH_STD() + } + CATCH_STD() } // ----------------- Get cell -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLong( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLong(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) { return 0; - return TBL(nativeTablePtr)->get_int( S(columnIndex), S(rowIndex)); // noexcept + } + return TBL(nativeTablePtr)->get_int(S(columnIndex), S(rowIndex)); // noexcept } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeGetBoolean( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeGetBoolean(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Bool)) - return false; + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Bool)) { + return JNI_FALSE; + } - return TBL(nativeTablePtr)->get_bool( S(columnIndex), S(rowIndex)); // noexcept + return to_jbool(TBL(nativeTablePtr)->get_bool(S(columnIndex), S(rowIndex))); // noexcept } -JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeGetFloat( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeGetFloat(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Float)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Float)) { return 0; + } - return TBL(nativeTablePtr)->get_float( S(columnIndex), S(rowIndex)); // noexcept + return TBL(nativeTablePtr)->get_float(S(columnIndex), S(rowIndex)); // noexcept } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeGetDouble( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeGetDouble(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Double)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Double)) { return 0; + } - return TBL(nativeTablePtr)->get_double( S(columnIndex), S(rowIndex)); // noexcept + return TBL(nativeTablePtr)->get_double(S(columnIndex), S(rowIndex)); // noexcept } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetTimestamp( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetTimestamp(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Timestamp)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Timestamp)) { return 0; + } try { - return to_milliseconds(TBL(nativeTablePtr)->get_timestamp( S(columnIndex), S(rowIndex))); - } CATCH_STD() + return to_milliseconds(TBL(nativeTablePtr)->get_timestamp(S(columnIndex), S(rowIndex))); + } + CATCH_STD() return 0; } -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetString( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetString(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) - return NULL; + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) { + return nullptr; + } try { - return to_jstring(env, TBL(nativeTablePtr)->get_string( S(columnIndex), S(rowIndex))); - } CATCH_STD() - return NULL; + return to_jstring(env, TBL(nativeTablePtr)->get_string(S(columnIndex), S(rowIndex))); + } + CATCH_STD() + return nullptr; } @@ -577,159 +612,191 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_Table_nativeGetByteBuffer( } */ -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_Table_nativeGetByteArray( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_Table_nativeGetByteArray(JNIEnv* env, jobject, + jlong nativeTablePtr, jlong columnIndex, + jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) - return NULL; + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) { + return nullptr; + } - return tbl_GetByteArray(env, nativeTablePtr, columnIndex, rowIndex); // noexcept + return tbl_GetByteArray
            (env, nativeTablePtr, columnIndex, rowIndex); // noexcept } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLink - (JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLink(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) { return 0; - return TBL(nativeTablePtr)->get_link( S(columnIndex), S(rowIndex)); // noexcept + } + return static_cast(TBL(nativeTablePtr)->get_link(S(columnIndex), S(rowIndex))); // noexcept } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLinkView - (JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLinkView(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_LinkList)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_LinkList)) { return 0; + } try { - LinkViewRef* link_view_ptr = new LinkViewRef(TBL(nativeTablePtr)->get_linklist( S(columnIndex), S(rowIndex))); + LinkViewRef* link_view_ptr = new LinkViewRef(TBL(nativeTablePtr)->get_linklist(S(columnIndex), S(rowIndex))); return reinterpret_cast(link_view_ptr); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLinkTarget - (JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLinkTarget(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { try { - Table* pTable = &(*TBL(nativeTablePtr)->get_link_target( S(columnIndex) )); + Table* pTable = &(*TBL(nativeTablePtr)->get_link_target(S(columnIndex))); LangBindHelper::bind_table_ptr(pTable); - return (jlong)pTable; - } CATCH_STD() + return reinterpret_cast(pTable); + } + CATCH_STD() return 0; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNull - (JNIEnv*, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNull(JNIEnv*, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - return TBL(nativeTablePtr)->is_null( S(columnIndex), S(rowIndex)) ? JNI_TRUE : JNI_FALSE; // noexcept + return to_jbool(TBL(nativeTablePtr)->is_null(S(columnIndex), S(rowIndex))); // noexcept } // ----------------- Set cell -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLink - (JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong targetRowIndex, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLink(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, + jlong targetRowIndex, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) + if (!TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) { return; + } try { - TBL(nativeTablePtr)->set_link( S(columnIndex), S(rowIndex), S(targetRowIndex), B(isDefault)); - } CATCH_STD() + TBL(nativeTablePtr)->set_link(S(columnIndex), S(rowIndex), S(targetRowIndex), B(isDefault)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLong( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong value, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLong(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, jlong value, + jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) { return; + } try { - TBL(nativeTablePtr)->set_int( S(columnIndex), S(rowIndex), value, B(isDefault)); - } CATCH_STD() + TBL(nativeTablePtr)->set_int(S(columnIndex), S(rowIndex), value, B(isDefault)); + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_Table_nativeSetLongUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, - jlong rowIndex, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLongUnique(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, + jlong value) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) { return; + } try { - TBL(nativeTablePtr)->set_int_unique( S(columnIndex), S(rowIndex), value); - } CATCH_STD() + TBL(nativeTablePtr)->set_int_unique(S(columnIndex), S(rowIndex), value); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jboolean value, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, + jboolean value, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Bool)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Bool)) { return; + } try { - TBL(nativeTablePtr)->set_bool( S(columnIndex), S(rowIndex), B(value), B(isDefault)); - } CATCH_STD() + TBL(nativeTablePtr)->set_bool(S(columnIndex), S(rowIndex), B(value), B(isDefault)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetFloat( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jfloat value, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetFloat(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, jfloat value, + jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Float)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Float)) { return; + } try { - TBL(nativeTablePtr)->set_float( S(columnIndex), S(rowIndex), value, B(isDefault)); - } CATCH_STD() + TBL(nativeTablePtr)->set_float(S(columnIndex), S(rowIndex), value, B(isDefault)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetDouble( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jdouble value, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetDouble(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, jdouble value, + jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Double)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Double)) { return; + } try { - TBL(nativeTablePtr)->set_double( S(columnIndex), S(rowIndex), value, B(isDefault)); - } CATCH_STD() + TBL(nativeTablePtr)->set_double(S(columnIndex), S(rowIndex), value, B(isDefault)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jstring value, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, jstring value, + jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) { return; + } try { - if (value == NULL) { + if (value == nullptr) { if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { return; } } JStringAccessor value2(env, value); // throws - TBL(nativeTablePtr)->set_string( S(columnIndex), S(rowIndex), value2, B(isDefault)); - } CATCH_STD() + TBL(nativeTablePtr)->set_string(S(columnIndex), S(rowIndex), value2, B(isDefault)); + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_Table_nativeSetStringUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, - jlong rowIndex, jstring value) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetStringUnique(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, + jstring value) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) { return; + } try { - if (value == NULL) { + if (value == nullptr) { if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { return; } TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), null{}); - } else { + } + else { JStringAccessor value2(env, value); // throws TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), value2); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong timestampValue, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, + jlong timestampValue, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Timestamp)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Timestamp)) { return; + } try { - TBL(nativeTablePtr)->set_timestamp( S(columnIndex), S(rowIndex), from_milliseconds(timestampValue), - B(isDefault)); - } CATCH_STD() + TBL(nativeTablePtr) + ->set_timestamp(S(columnIndex), S(rowIndex), from_milliseconds(timestampValue), B(isDefault)); + } + CATCH_STD() } /* @@ -744,443 +811,516 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteBuffer( } */ -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jbyteArray dataArray, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, + jbyteArray dataArray, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) { return; + } try { - if (dataArray == NULL && !TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { - return; + if (dataArray == nullptr && !TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { + return; } JniByteArray byteAccessor(env, dataArray); TBL(nativeTablePtr)->set_binary(S(columnIndex), S(rowIndex), byteAccessor, B(isDefault)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex, + jboolean isDefault) { Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) + if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { return; - if (!TBL_AND_ROW_INDEX_VALID(env, pTable, rowIndex)) + } + if (!TBL_AND_ROW_INDEX_VALID(env, pTable, rowIndex)) { return; - if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) + } + if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) { return; + } + try { pTable->set_null(S(columnIndex), S(rowIndex), B(isDefault)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_Table_nativeSetNullUnique(JNIEnv *env, jclass, jlong nativeTablePtr, jlong columnIndex, - jlong rowIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNullUnique(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) + if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { return; - if (!TBL_AND_ROW_INDEX_VALID(env, pTable, rowIndex)) + } + if (!TBL_AND_ROW_INDEX_VALID(env, pTable, rowIndex)) { return; - if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) + } + if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) { return; + } + try { pTable->set_null_unique(S(columnIndex), S(rowIndex)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetRowPtr - (JNIEnv* env, jobject, jlong nativeTablePtr, jlong index) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetRowPtr(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong index) { try { - Row* row = new Row( (*TBL(nativeTablePtr))[ S(index) ] ); + Row* row = new Row((*TBL(nativeTablePtr))[S(index)]); return reinterpret_cast(row); - } CATCH_STD() - return 0; + } + CATCH_STD() + return reinterpret_cast(nullptr); } //--------------------- Indexing methods: -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeAddSearchIndex( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeAddSearchIndex(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) + if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { return; + } - DataType column_type = pTable->get_column_type (S(columnIndex)); + DataType column_type = pTable->get_column_type(S(columnIndex)); if (!is_allowed_to_index(env, column_type)) { return; } try { - pTable->add_search_index( S(columnIndex)); - } CATCH_STD() + pTable->add_search_index(S(columnIndex)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveSearchIndex( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveSearchIndex(JNIEnv* env, jobject, + jlong nativeTablePtr, jlong columnIndex) { Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) + if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { return; - DataType column_type = pTable->get_column_type (S(columnIndex)); + } + DataType column_type = pTable->get_column_type(S(columnIndex)); if (!is_allowed_to_index(env, column_type)) { return; } try { - pTable->remove_search_index( S(columnIndex)); - } CATCH_STD() + pTable->remove_search_index(S(columnIndex)); + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSearchIndex( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSearchIndex(JNIEnv* env, jobject, + jlong nativeTablePtr, jlong columnIndex) { - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) - return false; + if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { + return JNI_FALSE; + } try { - return TBL(nativeTablePtr)->has_search_index( S(columnIndex)); - } CATCH_STD() - return false; + return to_jbool(TBL(nativeTablePtr)->has_search_index(S(columnIndex))); + } + CATCH_STD() + return JNI_FALSE; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNullLink - (JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNullLink(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) - return 0; + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) { + return JNI_FALSE; + } - return TBL(nativeTablePtr)->is_null_link(S(columnIndex), S(rowIndex)); + return to_jbool(TBL(nativeTablePtr)->is_null_link(S(columnIndex), S(rowIndex))); } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeNullifyLink - (JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeNullifyLink(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) + if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) { return; + } try { TBL(nativeTablePtr)->nullify_link(S(columnIndex), S(rowIndex)); - } CATCH_STD() + } + CATCH_STD() } //---------------------- Aggregate methods for integers -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSumInt( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSumInt(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { return 0; + } try { - return TBL(nativeTablePtr)->sum_int( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->sum_int(S(columnIndex)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMaximumInt( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMaximumInt(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { return 0; + } try { - return TBL(nativeTablePtr)->maximum_int( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->maximum_int(S(columnIndex)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMinimumInt( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMinimumInt(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { return 0; + } try { - return TBL(nativeTablePtr)->minimum_int( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->minimum_int(S(columnIndex)); + } + CATCH_STD() return 0; } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageInt( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageInt(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { return 0; + } try { - return TBL(nativeTablePtr)->average_int( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->average_int(S(columnIndex)); + } + CATCH_STD() return 0; } //--------------------- Aggregate methods for float -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeSumFloat( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeSumFloat(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { return 0; + } try { - return TBL(nativeTablePtr)->sum_float( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->sum_float(S(columnIndex)); + } + CATCH_STD() return 0; } -JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeMaximumFloat( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeMaximumFloat(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { return 0; + } try { - return TBL(nativeTablePtr)->maximum_float( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->maximum_float(S(columnIndex)); + } + CATCH_STD() return 0; } -JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeMinimumFloat( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeMinimumFloat(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { return 0; + } try { - return TBL(nativeTablePtr)->minimum_float( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->minimum_float(S(columnIndex)); + } + CATCH_STD() return 0; } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageFloat( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageFloat(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { return 0; + } try { - return TBL(nativeTablePtr)->average_float( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->average_float(S(columnIndex)); + } + CATCH_STD() return 0; } //--------------------- Aggregate methods for double -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeSumDouble( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeSumDouble(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { return 0; + } try { - return TBL(nativeTablePtr)->sum_double( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->sum_double(S(columnIndex)); + } + CATCH_STD() return 0; } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeMaximumDouble( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeMaximumDouble(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { return 0; + } try { - return TBL(nativeTablePtr)->maximum_double( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->maximum_double(S(columnIndex)); + } + CATCH_STD() return 0; } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeMinimumDouble( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeMinimumDouble(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { return 0; + } try { - return TBL(nativeTablePtr)->minimum_double( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->minimum_double(S(columnIndex)); + } + CATCH_STD() return 0; } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageDouble( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageDouble(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { return 0; + } try { - return TBL(nativeTablePtr)->average_double( S(columnIndex)); - } CATCH_STD() + return TBL(nativeTablePtr)->average_double(S(columnIndex)); + } + CATCH_STD() return 0; } //--------------------- Aggregate methods for date -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMaximumTimestamp( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMaximumTimestamp(JNIEnv* env, jobject, + jlong nativeTablePtr, jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) { return 0; + } try { - return to_milliseconds(TBL(nativeTablePtr)->maximum_timestamp( S(columnIndex))); - } CATCH_STD() + return to_milliseconds(TBL(nativeTablePtr)->maximum_timestamp(S(columnIndex))); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMinimumTimestamp( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMinimumTimestamp(JNIEnv* env, jobject, + jlong nativeTablePtr, jlong columnIndex) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) { return 0; + } try { - return to_milliseconds(TBL(nativeTablePtr)->minimum_timestamp( S(columnIndex))); - } CATCH_STD() + return to_milliseconds(TBL(nativeTablePtr)->minimum_timestamp(S(columnIndex))); + } + CATCH_STD() return 0; } //---------------------- Count -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountLong( - JNIEnv *env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountLong(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { return 0; + } try { - return TBL(nativeTablePtr)->count_int( S(columnIndex), value); - } CATCH_STD() + return static_cast(TBL(nativeTablePtr)->count_int(S(columnIndex), value)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountFloat( - JNIEnv *env, jobject, jlong nativeTablePtr, jlong columnIndex, jfloat value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountFloat(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jfloat value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { return 0; + } try { - return TBL(nativeTablePtr)->count_float( S(columnIndex), value); - } CATCH_STD() + return static_cast(TBL(nativeTablePtr)->count_float(S(columnIndex), value)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountDouble( - JNIEnv *env, jobject, jlong nativeTablePtr, jlong columnIndex, jdouble value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountDouble(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jdouble value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { return 0; + } try { - return TBL(nativeTablePtr)->count_double( S(columnIndex), value); - } CATCH_STD() + return static_cast(TBL(nativeTablePtr)->count_double(S(columnIndex), value)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountString( - JNIEnv *env, jobject, jlong nativeTablePtr, jlong columnIndex, jstring value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountString(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jstring value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_String)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_String)) { return 0; - + } try { JStringAccessor value2(env, value); // throws - return TBL(nativeTablePtr)->count_string( S(columnIndex), value2); - } CATCH_STD() + return static_cast(TBL(nativeTablePtr)->count_string(S(columnIndex), value2)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeWhere( - JNIEnv *env, jobject, jlong nativeTablePtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeWhere(JNIEnv* env, jobject, jlong nativeTablePtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) + if (!TABLE_VALID(env, TBL(nativeTablePtr))) { return 0; + } try { - Query *queryPtr = new Query(TBL(nativeTablePtr)->where()); + Query* queryPtr = new Query(TBL(nativeTablePtr)->where()); return reinterpret_cast(queryPtr); - } CATCH_STD() - return 0; + } + CATCH_STD() + return reinterpret_cast(nullptr); } //----------------------- FindFirst -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstInt( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstInt(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jlong value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { return 0; + } try { - return to_jlong_or_not_found( TBL(nativeTablePtr)->find_first_int( S(columnIndex), value) ); - } CATCH_STD() + return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_int(S(columnIndex), value)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstBool( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jboolean value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstBool(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jboolean value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Bool)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Bool)) { return 0; + } try { - return to_jlong_or_not_found( TBL(nativeTablePtr)->find_first_bool( S(columnIndex), value != 0 ? true : false) ); - } CATCH_STD() + return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_bool(S(columnIndex), to_bool(value))); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstFloat( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jfloat value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstFloat(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jfloat value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { return 0; + } try { - return to_jlong_or_not_found( TBL(nativeTablePtr)->find_first_float( S(columnIndex), value) ); - } CATCH_STD() + return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_float(S(columnIndex), value)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstDouble( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jdouble value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstDouble(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jdouble value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { return 0; + } try { - return to_jlong_or_not_found( TBL(nativeTablePtr)->find_first_double( S(columnIndex), value) ); - } CATCH_STD() + return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_double(S(columnIndex), value)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstTimestamp( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong dateTimeValue) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstTimestamp(JNIEnv* env, jobject, + jlong nativeTablePtr, jlong columnIndex, + jlong dateTimeValue) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) { return 0; + } try { - size_t res = TBL(nativeTablePtr)->find_first_timestamp( S(columnIndex), from_milliseconds(dateTimeValue)); + size_t res = TBL(nativeTablePtr)->find_first_timestamp(S(columnIndex), from_milliseconds(dateTimeValue)); return to_jlong_or_not_found(res); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstString( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jstring value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstString(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex, jstring value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_String)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_String)) { return 0; + } try { JStringAccessor value2(env, value); // throws - return to_jlong_or_not_found( TBL(nativeTablePtr)->find_first_string( S(columnIndex), value2) ); - } CATCH_STD() + return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_string(S(columnIndex), value2)); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull( - JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull(JNIEnv* env, jclass, jlong nativeTablePtr, + jlong columnIndex) { Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) - return jlong(-1); - if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) - return jlong(-1); + if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { + return static_cast(realm::not_found); + } + if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) { + return static_cast(realm::not_found); + } try { - return to_jlong_or_not_found( pTable->find_first_null( S(columnIndex) ) ); - } CATCH_STD() - return jlong(-1); + return to_jlong_or_not_found(pTable->find_first_null(S(columnIndex))); + } + CATCH_STD() + return static_cast(realm::not_found); } // FindAll - - // FIXME: reenable when find_first_timestamp() is implemented /* JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllTimestamp( @@ -1189,7 +1329,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllTimestamp( if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) return 0; try { - TableView* pTableView = new TableView(TBL(nativeTablePtr)->find_all_timestamp(S(columnIndex), from_milliseconds(dateTimeValue))); + TableView* pTableView = new TableView(TBL(nativeTablePtr)->find_all_timestamp(S(columnIndex), +from_milliseconds(dateTimeValue))); return reinterpret_cast(pTableView); } CATCH_STD() return 0; @@ -1197,40 +1338,45 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllTimestamp( */ - // experimental -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeLowerBoundInt( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeLowerBoundInt(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { return 0; + } Table* pTable = TBL(nativeTablePtr); try { - return pTable->lower_bound_int(S(columnIndex), S(value)); - } CATCH_STD() + return static_cast(pTable->lower_bound_int(S(columnIndex), S(value))); + } + CATCH_STD() return 0; } // experimental -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeUpperBoundInt( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeUpperBoundInt(JNIEnv* env, jobject, jlong nativeTablePtr, + jlong columnIndex, jlong value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) + if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { return 0; + } Table* pTable = TBL(nativeTablePtr); try { - return pTable->upper_bound_int(S(columnIndex), S(value)); - } CATCH_STD() + return static_cast(pTable->upper_bound_int(S(columnIndex), S(value))); + } + CATCH_STD() return 0; } // -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti( - JNIEnv *env, jobject, jlong nativeTablePtr, jlongArray columnIndices, jbooleanArray ascending) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti(JNIEnv* env, jobject, + jlong nativeTablePtr, + jlongArray columnIndices, + jbooleanArray ascending) { Table* pTable = TBL(nativeTablePtr); @@ -1256,10 +1402,10 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti( std::vector ascendings(S(arr_len)); for (int i = 0; i < arr_len; ++i) { - if (!TBL_AND_COL_INDEX_VALID(env, pTable, S(long_arr[i]) )) { + if (!TBL_AND_COL_INDEX_VALID(env, pTable, S(long_arr[i]))) { return 0; } - int colType = pTable->get_column_type( S(long_arr[i]) ); + int colType = pTable->get_column_type(S(long_arr[i])); switch (colType) { case type_Int: case type_Bool: @@ -1267,11 +1413,12 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti( case type_Double: case type_Float: case type_Timestamp: - indices[i] = std::vector { S(long_arr[i]) }; + indices[i] = std::vector{S(long_arr[i])}; ascendings[i] = S(bool_arr[i]); break; default: - ThrowException(env, IllegalArgument, "Sort is only support on String, Date, boolean, byte, short, int, long and their boxed variants."); + ThrowException(env, IllegalArgument, "Sort is only support on String, Date, boolean, byte, short, " + "int, long and their boxed variants."); return 0; } } @@ -1279,29 +1426,31 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti( try { TableView* pTableView = new TableView(pTable->get_sorted_view(SortDescriptor(*pTable, indices, ascendings))); return reinterpret_cast(pTableView); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetName( - JNIEnv *env, jobject, jlong nativeTablePtr) +JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetName(JNIEnv* env, jobject, jlong nativeTablePtr) { try { Table* table = TBL(nativeTablePtr); - if (!TABLE_VALID(env, table)) - return NULL; + if (!TABLE_VALID(env, table)) { + return nullptr; + } return to_jstring(env, table->get_name()); - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeToJson( - JNIEnv *env, jobject, jlong nativeTablePtr) +JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeToJson(JNIEnv* env, jobject, jlong nativeTablePtr) { Table* table = TBL(nativeTablePtr); - if (!TABLE_VALID(env, table)) - return NULL; + if (!TABLE_VALID(env, table)) { + return nullptr; + } // Write table to string in JSON format try { @@ -1310,23 +1459,24 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeToJson( table->to_json(ss); const string str = ss.str(); return to_jstring(env, str); - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid( - JNIEnv*, jobject, jlong nativeTablePtr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid(JNIEnv*, jobject, jlong nativeTablePtr) { TR_ENTER_PTR(nativeTablePtr) - return TBL(nativeTablePtr)->is_attached(); // noexcept + return to_jbool(TBL(nativeTablePtr)->is_attached()); // noexcept } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_createNative(JNIEnv *env, jobject) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_createNative(JNIEnv* env, jobject) { TR_ENTER() try { return reinterpret_cast(LangBindHelper::new_table()); - } CATCH_STD() + } + CATCH_STD() return 0; } @@ -1343,7 +1493,7 @@ static bool check_valid_primary_key_column(JNIEnv* env, Table* table, StringData DataType column_type = table->get_column_type(column_index); TableView results = table->get_sorted_view(column_index); - switch(column_type) { + switch (column_type) { case type_Int: if (results.size() > 1) { int64_t val = results.get_int(column_index, 0); @@ -1388,21 +1538,23 @@ static bool check_valid_primary_key_column(JNIEnv* env, Table* table, StringData } } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey( - JNIEnv* env, jobject, jlong nativePrivateKeyTablePtr, jlong nativeTablePtr, jstring columnName) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey(JNIEnv* env, jobject, + jlong nativePrivateKeyTablePtr, + jlong nativeTablePtr, jstring columnName) { try { Table* table = TBL(nativeTablePtr); Table* pk_table = TBL(nativePrivateKeyTablePtr); const std::string table_name(table->get_name().substr(TABLE_PREFIX.length())); // Remove "class_" prefix - size_t row_index = pk_table->find_first_string(io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX, table_name); + size_t row_index = + pk_table->find_first_string(io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX, table_name); if (columnName == NULL || env->GetStringLength(columnName) == 0) { // No primary key provided => remove previous set keys if (row_index != realm::not_found) { pk_table->remove(row_index); } - return jlong(io_realm_internal_Table_NO_PRIMARY_KEY); + return io_realm_internal_Table_NO_PRIMARY_KEY; } else { JStringAccessor new_primary_key_column_name(env, columnName); @@ -1411,24 +1563,29 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey( // No primary key is currently set if (check_valid_primary_key_column(env, table, new_primary_key_column_name)) { row_index = pk_table->add_empty_row(); - pk_table->set_string_unique(io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX, row_index, table_name); - pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, new_primary_key_column_name); + pk_table->set_string_unique(io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX, row_index, + table_name); + pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, + new_primary_key_column_name); } } else { // Primary key already exists // We only wish to check for duplicate values if a column isn't already a primary key - StringData current_primary_key = pk_table->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index); + StringData current_primary_key = + pk_table->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index); if (new_primary_key_column_name != current_primary_key) { if (check_valid_primary_key_column(env, table, new_primary_key_column_name)) { - pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, new_primary_key_column_name); + pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, + new_primary_key_column_name); } } } - return jlong(primary_key_column_index); + return static_cast(primary_key_column_index); } - } CATCH_STD() + } + CATCH_STD() return 0; } @@ -1451,8 +1608,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey( // This methods converts the old (wrong) table format (string, integer) to the right (string,string) format and strips // any class names in the col[0] of their "class_" prefix -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded - (JNIEnv*, jclass, jlong groupNativePtr, jlong privateKeyTableNativePtr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded( + JNIEnv*, jclass, jlong groupNativePtr, jlong privateKeyTableNativePtr) { const size_t CLASS_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX; const size_t FIELD_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX; @@ -1505,7 +1662,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyT } JNIEXPORT jboolean JNICALL -Java_io_realm_internal_Table_nativePrimaryKeyTableNeedsMigration(JNIEnv *, jclass, jlong primaryKeyTableNativePtr) +Java_io_realm_internal_Table_nativePrimaryKeyTableNeedsMigration(JNIEnv*, jclass, jlong primaryKeyTableNativePtr) { const size_t CLASS_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX; @@ -1533,17 +1690,16 @@ Java_io_realm_internal_Table_nativePrimaryKeyTableNeedsMigration(JNIEnv *, jclas return JNI_FALSE; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema - (JNIEnv*, jobject, jlong thisTablePtr, jlong otherTablePtr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema(JNIEnv*, jobject, jlong thisTablePtr, + jlong otherTablePtr) { - return *TBL(thisTablePtr)->get_descriptor() == *TBL(otherTablePtr)->get_descriptor(); + return to_jbool(*TBL(thisTablePtr)->get_descriptor() == *TBL(otherTablePtr)->get_descriptor()); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeVersion( - JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeVersion(JNIEnv* env, jobject, jlong nativeTablePtr) { - bool valid = (TBL(nativeTablePtr) != NULL); + bool valid = (TBL(nativeTablePtr) != nullptr); if (valid) { if (!TBL(nativeTablePtr)->is_attached()) { ThrowException(env, IllegalState, "The Realm has been closed and is no longer accessible."); @@ -1551,8 +1707,9 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeVersion( } } try { - return (jlong) TBL(nativeTablePtr)->get_version_counter(); - } CATCH_STD() + return static_cast(TBL(nativeTablePtr)->get_version_counter()); + } + CATCH_STD() return 0; } @@ -1562,10 +1719,8 @@ static void finalize_table(jlong ptr) LangBindHelper::unbind_table_ptr(TBL(ptr)); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetFinalizerPtr - (JNIEnv *, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_table); } - diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 8d0fbf1cdc..ca9baad7ef 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -29,9 +29,9 @@ using namespace realm; using namespace realm::jni_util; #if 1 -#define QUERY_COL_TYPE_VALID(env, jPtr, col, type) query_col_type_valid(env, jPtr, col, type) +#define QUERY_COL_TYPE_VALID(env, jPtr, col, type) query_col_type_valid(env, jPtr, col, type) #else -#define QUERY_COL_TYPE_VALID(env, jPtr, col, type) (true) +#define QUERY_COL_TYPE_VALID(env, jPtr, col, type) (true) #endif static void finalize_table_query(jlong ptr); @@ -46,22 +46,24 @@ const char* ERR_IMPORT_CLOSED_REALM = "Can not import results from a closed Real const char* ERR_SORT_NOT_SUPPORTED = "Sort is not supported on binary data, object references and RealmList"; //------------------------------------------------------- -JNIEXPORT jstring JNICALL Java_io_realm_internal_TableQuery_nativeValidateQuery -(JNIEnv *env, jobject, jlong nativeQueryPtr) +JNIEXPORT jstring JNICALL Java_io_realm_internal_TableQuery_nativeValidateQuery(JNIEnv* env, jobject, + jlong nativeQueryPtr) { try { const std::string str = Q(nativeQueryPtr)->validate(); StringData sd(str); return to_jstring(env, sd); - } CATCH_STD(); - return NULL; + } + CATCH_STD(); + return nullptr; } // helper functions // Return TableRef used for build link queries -static TableRef getTableForLinkQuery(jlong nativeQueryPtr, JniLongArray& indicesArray) { +static TableRef getTableForLinkQuery(jlong nativeQueryPtr, JniLongArray& indicesArray) +{ TableRef table_ref = Q(nativeQueryPtr)->get_table(); jsize link_element_count = indicesArray.len() - 1; for (int i = 0; i < link_element_count; i++) { @@ -71,7 +73,8 @@ static TableRef getTableForLinkQuery(jlong nativeQueryPtr, JniLongArray& indices } // Return TableRef point to original table or the link table -static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& indicesArray) { +static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& indicesArray) +{ TableRef table_ref = Q(nativeQueryPtr)->get_table(); jsize link_element_count = indicesArray.len() - 1; for (int i = 0; i < link_element_count; i++) { @@ -81,40 +84,47 @@ static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& indicesArray } template -Query numeric_link_equal(TableRef tbl, jlong columnIndex, javatype value) { +Query numeric_link_equal(TableRef tbl, jlong columnIndex, javatype value) +{ return tbl->column(size_t(columnIndex)) == cpptype(value); } template -Query numeric_link_notequal(TableRef tbl, jlong columnIndex, javatype value) { +Query numeric_link_notequal(TableRef tbl, jlong columnIndex, javatype value) +{ return tbl->column(size_t(columnIndex)) != cpptype(value); } template -Query numeric_link_greater(TableRef tbl, jlong columnIndex, javatype value) { +Query numeric_link_greater(TableRef tbl, jlong columnIndex, javatype value) +{ return tbl->column(size_t(columnIndex)) > cpptype(value); } template -Query numeric_link_greaterequal(TableRef tbl, jlong columnIndex, javatype value) { +Query numeric_link_greaterequal(TableRef tbl, jlong columnIndex, javatype value) +{ return tbl->column(size_t(columnIndex)) >= cpptype(value); } template -Query numeric_link_less(TableRef tbl, jlong columnIndex, javatype value) { +Query numeric_link_less(TableRef tbl, jlong columnIndex, javatype value) +{ return tbl->column(size_t(columnIndex)) < cpptype(value); } template -Query numeric_link_lessequal(TableRef tbl, jlong columnIndex, javatype value) { +Query numeric_link_lessequal(TableRef tbl, jlong columnIndex, javatype value) +{ return tbl->column(size_t(columnIndex)) <= cpptype(value); } // Integer -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JJ( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JJ(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -127,13 +137,16 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JJ( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_equal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr)->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JJ( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JJ(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -146,13 +159,16 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual_ } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_notequal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_notequal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JJ( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JJ(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -165,13 +181,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JJ( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_greater(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_greater(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JJ( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JJ(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -184,13 +204,15 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3 } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_greaterequal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_greaterequal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JJ( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -203,13 +225,15 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JJ( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JJ( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JJ(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -222,13 +246,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JJ( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_lessequal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_lessequal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JJJ( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value1, jlong value2) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JJJ(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jlong value1, + jlong value2) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -238,7 +266,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JJJ( } try { Q(nativeQueryPtr)->between(S(arr[0]), static_cast(value1), static_cast(value2)); - } CATCH_STD() + } + CATCH_STD() } else { ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); @@ -247,8 +276,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JJJ( // Float -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JF( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jfloat value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JF(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jfloat value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -261,13 +291,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JF( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_equal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JF( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jfloat value) +JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JF(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jfloat value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -280,13 +314,16 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual_ } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_notequal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_notequal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JF( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jfloat value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JF(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jfloat value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -299,13 +336,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JF( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_greater(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_greater(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JF( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jfloat value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JF(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jfloat value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -318,13 +359,15 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3 } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_greaterequal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_greaterequal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JF( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jfloat value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, jfloat value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -337,13 +380,16 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JF( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JF( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jfloat value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JF(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jfloat value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -356,13 +402,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JF( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_lessequal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_lessequal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JFF( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jfloat value1, jfloat value2) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JFF(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jfloat value1, jfloat value2) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -376,14 +426,16 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JFF( else { ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); } - } CATCH_STD() + } + CATCH_STD() } // Double -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JD( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jdouble value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JD(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jdouble value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -396,13 +448,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JD( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_equal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JD( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jdouble value) +JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JD(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jdouble value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -415,13 +471,16 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual_ } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_notequal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_notequal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JD( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jdouble value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JD(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jdouble value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -434,13 +493,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JD( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_greater(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_greater(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JD( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jdouble value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JD(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jdouble value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -453,13 +516,15 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3 } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_greaterequal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_greaterequal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JD( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jdouble value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, jdouble value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -472,13 +537,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JD( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_less(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JD( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jdouble value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JD(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jdouble value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -491,13 +560,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JD( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_lessequal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_lessequal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JDD( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jdouble value1, jdouble value2) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JDD(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jdouble value1, jdouble value2) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -511,14 +584,16 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JDD( else { ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); } - } CATCH_STD() + } + CATCH_STD() } // Timestamp -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualTimestamp( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualTimestamp(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -531,13 +606,18 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualTimestamp( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_equal(table_ref, arr[arr_len-1], from_milliseconds(value))); + Q(nativeQueryPtr) + ->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], + from_milliseconds(value))); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualTimestamp( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualTimestamp(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -550,13 +630,17 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualT } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_notequal(table_ref, arr[arr_len-1], from_milliseconds(value))); + Q(nativeQueryPtr) + ->and_query(numeric_link_notequal(table_ref, arr[arr_len - 1], + from_milliseconds(value))); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterTimestamp( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterTimestamp(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -569,13 +653,18 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterTimestamp( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_greater(table_ref, arr[arr_len-1], from_milliseconds(value))); + Q(nativeQueryPtr) + ->and_query(numeric_link_greater(table_ref, arr[arr_len - 1], + from_milliseconds(value))); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualTimestamp( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualTimestamp(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -588,13 +677,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualTimes } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_greaterequal(table_ref, arr[arr_len-1], from_milliseconds(value))); + Q(nativeQueryPtr) + ->and_query(numeric_link_greaterequal(table_ref, arr[arr_len - 1], + from_milliseconds(value))); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessTimestamp( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessTimestamp(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -607,13 +700,18 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessTimestamp( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, arr[arr_len-1], from_milliseconds(value))); + Q(nativeQueryPtr) + ->and_query(numeric_link_less(table_ref, arr[arr_len - 1], + from_milliseconds(value))); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualTimestamp( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualTimestamp(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jlong value) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -626,13 +724,18 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualTimestam } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_lessequal(table_ref, arr[arr_len-1], from_milliseconds(value))); + Q(nativeQueryPtr) + ->and_query(numeric_link_lessequal(table_ref, arr[arr_len - 1], + from_milliseconds(value))); } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenTimestamp( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlong value1, jlong value2) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenTimestamp(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, + jlong value1, jlong value2) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -641,21 +744,26 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenTimestamp( if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->greater_equal(S(arr[0]), from_milliseconds(value1)).less_equal(S(arr[0]), from_milliseconds(value2)); + Q(nativeQueryPtr) + ->greater_equal(S(arr[0]), from_milliseconds(value1)) + .less_equal(S(arr[0]), from_milliseconds(value2)); } else { ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); } - } CATCH_STD() + } + CATCH_STD() } // Bool -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JZ( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jboolean value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JZ(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndexes, jboolean value) { JniLongArray arr(env, columnIndexes); - try { jsize arr_len = arr.len(); + try { + jsize arr_len = arr.len(); if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Bool)) { @@ -665,29 +773,26 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JZ( } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_equal(table_ref, arr[arr_len-1], value)); + Q(nativeQueryPtr) + ->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], value)); } - } CATCH_STD() + } + CATCH_STD() } // String -enum StringPredicate { - StringEqual, - StringNotEqual, - StringContains, - StringBeginsWith, - StringEndsWith, - StringLike -}; +enum StringPredicate { StringEqual, StringNotEqual, StringContains, StringBeginsWith, StringEndsWith, StringLike }; -static void TableQuery_StringPredicate(JNIEnv *env, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive, StringPredicate predicate) { +static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, + jboolean caseSensitive, StringPredicate predicate) +{ JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); try { if (value == NULL) { - if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, arr).get(), arr[arr_len-1])) { + if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, arr).get(), arr[arr_len - 1])) { return; } } @@ -698,103 +803,119 @@ static void TableQuery_StringPredicate(JNIEnv *env, jlong nativeQueryPtr, jlongA return; } switch (predicate) { - case StringEqual: - Q(nativeQueryPtr)->equal(S(arr[0]), value2, is_case_sensitive); - break; - case StringNotEqual: - Q(nativeQueryPtr)->not_equal(S(arr[0]), value2, is_case_sensitive); - break; - case StringContains: - Q(nativeQueryPtr)->contains(S(arr[0]), value2, is_case_sensitive); - break; - case StringBeginsWith: - Q(nativeQueryPtr)->begins_with(S(arr[0]), value2, is_case_sensitive); - break; - case StringEndsWith: - Q(nativeQueryPtr)->ends_with(S(arr[0]), value2, is_case_sensitive); - break; - case StringLike: - Q(nativeQueryPtr)->like(S(arr[0]), value2, is_case_sensitive); - break; + case StringEqual: + Q(nativeQueryPtr)->equal(S(arr[0]), value2, is_case_sensitive); + break; + case StringNotEqual: + Q(nativeQueryPtr)->not_equal(S(arr[0]), value2, is_case_sensitive); + break; + case StringContains: + Q(nativeQueryPtr)->contains(S(arr[0]), value2, is_case_sensitive); + break; + case StringBeginsWith: + Q(nativeQueryPtr)->begins_with(S(arr[0]), value2, is_case_sensitive); + break; + case StringEndsWith: + Q(nativeQueryPtr)->ends_with(S(arr[0]), value2, is_case_sensitive); + break; + case StringLike: + Q(nativeQueryPtr)->like(S(arr[0]), value2, is_case_sensitive); + break; } } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); switch (predicate) { - case StringEqual: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])).equal(StringData(value2), is_case_sensitive)); - break; - case StringNotEqual: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])).not_equal(StringData(value2), is_case_sensitive)); - break; - case StringContains: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])).contains(StringData(value2), is_case_sensitive)); - break; - case StringBeginsWith: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])).begins_with(StringData(value2), is_case_sensitive)); - break; - case StringEndsWith: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])).ends_with(StringData(value2), is_case_sensitive)); - break; - case StringLike: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])).like(StringData(value2), is_case_sensitive)); - break; + case StringEqual: + Q(nativeQueryPtr) + ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + .equal(StringData(value2), is_case_sensitive)); + break; + case StringNotEqual: + Q(nativeQueryPtr) + ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + .not_equal(StringData(value2), is_case_sensitive)); + break; + case StringContains: + Q(nativeQueryPtr) + ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + .contains(StringData(value2), is_case_sensitive)); + break; + case StringBeginsWith: + Q(nativeQueryPtr) + ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + .begins_with(StringData(value2), is_case_sensitive)); + break; + case StringEndsWith: + Q(nativeQueryPtr) + ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + .ends_with(StringData(value2), is_case_sensitive)); + break; + case StringLike: + Q(nativeQueryPtr) + ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + .like(StringData(value2), is_case_sensitive)); + break; } } - } CATCH_STD() + } + CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JLjava_lang_String_2Z( - JNIEnv *env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) + JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) { TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringEqual); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JLjava_lang_String_2Z( - JNIEnv *env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) + JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) { TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringNotEqual); } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBeginsWith( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBeginsWith(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, jstring value, + jboolean caseSensitive) { TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringBeginsWith); } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndsWith( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndsWith(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, jstring value, + jboolean caseSensitive) { TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringEndsWith); } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLike( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLike(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, jstring value, + jboolean caseSensitive) { TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringLike); } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeContains( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeContains(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, jstring value, + jboolean caseSensitive) { TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringContains); } // Binary -enum BinaryPredicate { - BinaryEqual, - BinaryNotEqual -}; +enum BinaryPredicate { BinaryEqual, BinaryNotEqual }; -static void TableQuery_BinaryPredicate(JNIEnv *env, jlong nativeQueryPtr, jlongArray columnIndices, jbyteArray value, BinaryPredicate predicate) { +static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnIndices, jbyteArray value, + BinaryPredicate predicate) +{ JniLongArray arr(env, columnIndices); jsize arr_len = arr.len(); try { JniByteArray bytes(env, value); BinaryData value2; if (value == NULL) { - if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, arr).get(), arr[arr_len-1])) { + if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, arr).get(), arr[arr_len - 1])) { return; } value2 = BinaryData(); @@ -812,36 +933,41 @@ static void TableQuery_BinaryPredicate(JNIEnv *env, jlong nativeQueryPtr, jlongA return; } switch (predicate) { - case BinaryEqual: - Q(nativeQueryPtr)->equal(S(arr[0]), value2); - break; - case BinaryNotEqual: - Q(nativeQueryPtr)->not_equal(S(arr[0]), value2); - break; + case BinaryEqual: + Q(nativeQueryPtr)->equal(S(arr[0]), value2); + break; + case BinaryNotEqual: + Q(nativeQueryPtr)->not_equal(S(arr[0]), value2); + break; } } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); switch (predicate) { - case BinaryEqual: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])) == value2); - break; - case BinaryNotEqual: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len-1])) != value2); - break; + case BinaryEqual: + Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len - 1])) == value2); + break; + case BinaryNotEqual: + Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len - 1])) != value2); + break; } } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3B - (JNIEnv *env, jobject, jlong nativeQueryPtr, jlongArray columnIndices, jbyteArray value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3B(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndices, + jbyteArray value) { TableQuery_BinaryPredicate(env, nativeQueryPtr, columnIndices, value, BinaryEqual); } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3B - (JNIEnv *env, jobject, jlong nativeQueryPtr, jlongArray columnIndices, jbyteArray value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3B(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnIndices, + jbyteArray value) { TableQuery_BinaryPredicate(env, nativeQueryPtr, columnIndices, value, BinaryNotEqual); } @@ -852,398 +978,429 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3B // as they are called for each method when building up the query. // Consider to reduce to just the "action" methods on Query -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGroup( - JNIEnv* env, jobject, jlong nativeQueryPtr) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGroup(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) + if (!QUERY_VALID(env, pQuery)) { return; + } try { pQuery->group(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndGroup( - JNIEnv* env, jobject, jlong nativeQueryPtr) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndGroup(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) + if (!QUERY_VALID(env, pQuery)) { return; + } try { pQuery->end_group(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeOr( - JNIEnv* env, jobject, jlong nativeQueryPtr) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeOr(JNIEnv* env, jobject, jlong nativeQueryPtr) { // No verification of parameters needed? Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) + if (!QUERY_VALID(env, pQuery)) { return; + } try { pQuery->Or(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNot( - JNIEnv* env, jobject, jlong nativeQueryPtr) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNot(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) + if (!QUERY_VALID(env, pQuery)) { return; + } try { pQuery->Not(); - } CATCH_STD() + } + CATCH_STD() } // Find -------------------------------------- -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFind( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlong fromTableRow) +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFind(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlong fromTableRow) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery)) + if (!QUERY_VALID(env, pQuery)) { return -1; + } // It's valid to go 1 past the end index if ((fromTableRow < 0) || (S(fromTableRow) > pTable->size())) { // below check will fail with appropriate exception - (void) ROW_INDEX_VALID(env, pTable, fromTableRow); + (void)ROW_INDEX_VALID(env, pTable, fromTableRow); return -1; } try { - size_t r = pQuery->find( S(fromTableRow) ); + size_t r = pQuery->find(S(fromTableRow)); return (r == not_found) ? jlong(-1) : jlong(r); - } CATCH_STD() + } + CATCH_STD() return -1; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlong start, jlong end, jlong limit) +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlong start, jlong end, jlong limit) { TR_ENTER() Query* query = Q(nativeQueryPtr); - TableRef table = query->get_table(); - if (!QUERY_VALID(env, query) || - !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) + TableRef table = query->get_table(); + if (!QUERY_VALID(env, query) || !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) { return -1; + } try { - TableView* tableView = new TableView( query->find_all(S(start), S(end), S(limit)) ); + TableView* tableView = new TableView(query->find_all(S(start), S(end), S(limit))); return reinterpret_cast(tableView); - } CATCH_STD() + } + CATCH_STD() return -1; } // Integer Aggregates -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeSumInt( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeSumInt(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlong columnIndex, jlong start, jlong end, + jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { return 0; + } try { return pQuery->sum_int(S(columnIndex), NULL, S(start), S(end), S(limit)); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumInt( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumInt(JNIEnv* env, jobject, + jlong nativeQueryPtr, jlong columnIndex, + jlong start, jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) - return NULL; + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + return nullptr; + } try { size_t return_ndx; int64_t result = pQuery->maximum_int(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); if (return_ndx != npos) { return NewLong(env, result); } - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumInt( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumInt(JNIEnv* env, jobject, + jlong nativeQueryPtr, jlong columnIndex, + jlong start, jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) - return NULL; + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + return nullptr; + } try { size_t return_ndx; int64_t result = pQuery->minimum_int(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); if (return_ndx != npos) { return NewLong(env, result); } - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageInt( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageInt(JNIEnv* env, jobject, + jlong nativeQueryPtr, jlong columnIndex, + jlong start, jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { return 0; + } try { size_t resultcount; - //TODO: return resultcount? + // TODO: return resultcount? double avg = pQuery->average_int(S(columnIndex), &resultcount, S(start), S(end), S(limit)); - //fprintf(stderr, "!!!Average(%d, %d) = %f (%d results)\n", start, end, avg, resultcount); fflush(stderr); + // fprintf(stderr, "!!!Average(%d, %d) = %f (%d results)\n", start, end, avg, resultcount); fflush(stderr); return avg; - } CATCH_STD() + } + CATCH_STD() return 0; } // float Aggregates -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumFloat( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumFloat(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlong columnIndex, jlong start, jlong end, + jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { return 0; + } try { return pQuery->sum_float(S(columnIndex), NULL, S(start), S(end), S(limit)); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumFloat( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumFloat(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnIndex, jlong start, + jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) - return NULL; + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + return nullptr; + } try { size_t return_ndx; float result = pQuery->maximum_float(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); if (return_ndx != npos) { return NewFloat(env, result); } - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumFloat( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumFloat(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnIndex, jlong start, + jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) - return NULL; + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + return nullptr; + } try { size_t return_ndx; float result = pQuery->minimum_float(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); if (return_ndx != npos) { return NewFloat(env, result); } - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageFloat( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageFloat(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnIndex, jlong start, + jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { return 0; + } try { size_t resultcount; double avg = pQuery->average_float(S(columnIndex), &resultcount, S(start), S(end), S(limit)); return avg; - } CATCH_STD() + } + CATCH_STD() return 0; } // double Aggregates -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumDouble( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumDouble(JNIEnv* env, jobject, + jlong nativeQueryPtr, jlong columnIndex, + jlong start, jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { return 0; + } try { return pQuery->sum_double(S(columnIndex), NULL, S(start), S(end), S(limit)); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumDouble( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumDouble(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnIndex, jlong start, + jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) - return NULL; + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + return nullptr; + } try { size_t return_ndx; double result = pQuery->maximum_double(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); if (return_ndx != npos) { return NewDouble(env, result); } - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumDouble( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumDouble(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnIndex, jlong start, + jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) - return NULL; + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + return nullptr; + } try { size_t return_ndx; double result = pQuery->minimum_double(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); if (return_ndx != npos) { return NewDouble(env, result); } - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageDouble( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageDouble(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnIndex, jlong start, + jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { return 0; + } try { - //TODO: Return resultcount + // TODO: Return resultcount size_t resultcount; double avg = pQuery->average_double(S(columnIndex), &resultcount, S(start), S(end), S(limit)); return avg; - } CATCH_STD() + } + CATCH_STD() return 0; } // date aggregates // FIXME: This is a rough workaround while waiting for https://github.com/realm/realm-core/issues/1745 to be solved -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumTimestamp( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumTimestamp(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnIndex, jlong start, + jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Timestamp) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) - return NULL; + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Timestamp) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + return nullptr; + } try { size_t return_ndx; Timestamp result = pQuery->find_all().maximum_timestamp(S(columnIndex), &return_ndx); if (return_ndx != npos && !result.is_null()) { return NewLong(env, to_milliseconds(result)); } - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } -JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumTimestamp( - JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, jlong limit) +JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumTimestamp(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnIndex, jlong start, + jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Timestamp) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) - return NULL; + if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Timestamp) || + !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + return nullptr; + } try { size_t return_ndx; Timestamp result = pQuery->find_all().minimum_timestamp(S(columnIndex), &return_ndx); if (return_ndx != npos && !result.is_null()) { return NewLong(env, to_milliseconds(result)); } - } CATCH_STD() - return NULL; + } + CATCH_STD() + return nullptr; } // Count, Remove -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeCount( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlong start, jlong end, jlong limit) +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeCount(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlong start, jlong end, jlong limit) { Query* pQuery = Q(nativeQueryPtr); Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) + if (!QUERY_VALID(env, pQuery) || !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { return 0; + } try { - return pQuery->count(S(start), S(end), S(limit)); - } CATCH_STD() + return static_cast(pQuery->count(S(start), S(end), S(limit))); + } + CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeRemove( - JNIEnv* env, jobject, jlong nativeQueryPtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeRemove(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) + if (!QUERY_VALID(env, pQuery)) { return 0; + } try { - return pQuery->remove(); - } CATCH_STD() + return static_cast(pQuery->remove()); + } + CATCH_STD() return 0; } // isNull and isNotNull -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull( - JNIEnv *env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes) { JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -1251,7 +1408,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull( try { TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - jlong column_idx = arr[arr_len-1]; + jlong column_idx = arr[arr_len - 1]; TableRef table_ref = getTableByArray(nativeQueryPtr, arr); if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), column_idx)) { return; @@ -1279,11 +1436,10 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull( Q(nativeQueryPtr)->equal(S(column_idx), realm::null()); break; default: - // this point is unreachable - ThrowException(env, FatalError, "This is not reachable."); - return; + REALM_UNREACHABLE(); } - } else { + } + else { switch (col_type) { case type_Link: ThrowException(env, IllegalArgument, "isNull() by nested query for link field is not supported."); @@ -1314,60 +1470,65 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull( pQuery->and_query(src_table_ref->column(S(column_idx)) == realm::null()); break; default: - // this point is unreachable - ThrowException(env, FatalError, "This is not reachable."); - return; + REALM_UNREACHABLE(); } } - } CATCH_STD() -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRowIntoSharedGroup - (JNIEnv *env, jclass, jlong handoverPtr, jlong callerSharedGrpPtr) - { - TR_ENTER_PTR(handoverPtr) - SharedGroup::Handover *handoverRowPtr = HO(Row, handoverPtr); - std::unique_ptr> handoverRow(handoverRowPtr); - - try { - // import_from_handover will free (delete) the handover - auto sharedRealm = *(reinterpret_cast(callerSharedGrpPtr)); - if (!sharedRealm->is_closed()) { - using rf = realm::_impl::RealmFriend; - auto row = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverRow)); - return reinterpret_cast(row.release()); - } else { - ThrowException(env, RuntimeError, ERR_IMPORT_CLOSED_REALM); - } - } CATCH_STD() - return 0; - } - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery - (JNIEnv* env, jobject, jlong bgSharedRealmPtr, jlong nativeQueryPtr) + } + CATCH_STD() +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRowIntoSharedGroup( + JNIEnv* env, jclass, jlong handoverPtr, jlong callerSharedGrpPtr) +{ + TR_ENTER_PTR(handoverPtr) + SharedGroup::Handover* handoverRowPtr = HO(Row, handoverPtr); + std::unique_ptr> handoverRow(handoverRowPtr); + + try { + // import_from_handover will free (delete) the handover + auto sharedRealm = *(reinterpret_cast(callerSharedGrpPtr)); + if (!sharedRealm->is_closed()) { + using rf = realm::_impl::RealmFriend; + auto row = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverRow)); + return reinterpret_cast(row.release()); + } + else { + ThrowException(env, RuntimeError, ERR_IMPORT_CLOSED_REALM); + } + } + CATCH_STD() + return 0; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery(JNIEnv* env, jobject, + jlong bgSharedRealmPtr, + jlong nativeQueryPtr) { TR_ENTER_PTR(nativeQueryPtr) Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) + if (!QUERY_VALID(env, pQuery)) { return 0; + } try { auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); using rf = realm::_impl::RealmFriend; auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(*pQuery, ConstSourcePayload::Copy); return reinterpret_cast(handover.release()); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull - (JNIEnv *env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes) { +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes) +{ JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); Query* pQuery = Q(nativeQueryPtr); try { TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - jlong column_idx = arr[arr_len-1]; + jlong column_idx = arr[arr_len - 1]; TableRef table_ref = getTableByArray(nativeQueryPtr, arr); if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), column_idx)) { @@ -1396,15 +1557,14 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull pQuery->not_equal(S(column_idx), realm::null()); break; default: - // this point is unreachable - ThrowException(env, FatalError, "This is not reachable."); - return; + REALM_UNREACHABLE(); } } else { switch (col_type) { case type_Link: - ThrowException(env, IllegalArgument, "isNotNull() by nested query for link field is not supported."); + ThrowException(env, IllegalArgument, + "isNotNull() by nested query for link field is not supported."); break; case type_LinkList: // Cannot get here. Exception will be thrown in TBL_AND_COL_NULLABLE @@ -1432,16 +1592,16 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull pQuery->and_query(src_table_ref->column(S(column_idx)) != realm::null()); break; default: - // this point is unreachable - ThrowException(env, FatalError, "This is not reachable."); - return; + REALM_UNREACHABLE(); } } - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty - (JNIEnv *env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes) { +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes) +{ JniLongArray arr(env, columnIndexes); jsize arr_len = arr.len(); @@ -1494,11 +1654,13 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty case type_Double: case type_Timestamp: default: - ThrowException(env, IllegalArgument, "isEmpty() only works on String, byte[] and RealmList across links."); + ThrowException(env, IllegalArgument, + "isEmpty() only works on String, byte[] and RealmList across links."); return; } } - } CATCH_STD() + } + CATCH_STD() } static void finalize_table_query(jlong ptr) @@ -1507,10 +1669,8 @@ static void finalize_table_query(jlong ptr) delete Q(ptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetFinalizerPtr - (JNIEnv *, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_table_query); } - diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp index a5b34275c4..6eab915b78 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp @@ -1,28 +1,25 @@ #include "io_realm_internal_TestUtil.h" #include "util.hpp" -static jstring throwOrGetExpectedMessage(JNIEnv *env, jlong testcase, bool should_throw); +static jstring throwOrGetExpectedMessage(JNIEnv* env, jlong testcase, bool should_throw); -JNIEXPORT jlong JNICALL -Java_io_realm_internal_TestUtil_getMaxExceptionNumber(JNIEnv*, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_TestUtil_getMaxExceptionNumber(JNIEnv*, jclass) { return ExceptionKindMax; } -JNIEXPORT jstring JNICALL -Java_io_realm_internal_TestUtil_getExpectedMessage(JNIEnv *env, jclass, jlong exception_kind) +JNIEXPORT jstring JNICALL Java_io_realm_internal_TestUtil_getExpectedMessage(JNIEnv* env, jclass, + jlong exception_kind) { return throwOrGetExpectedMessage(env, exception_kind, false); } -JNIEXPORT void JNICALL -Java_io_realm_internal_TestUtil_testThrowExceptions(JNIEnv *env, jclass, jlong exception_kind) +JNIEXPORT void JNICALL Java_io_realm_internal_TestUtil_testThrowExceptions(JNIEnv* env, jclass, jlong exception_kind) { throwOrGetExpectedMessage(env, exception_kind, true); } -static jstring -throwOrGetExpectedMessage(JNIEnv *env, jlong testcase, bool should_throw) +static jstring throwOrGetExpectedMessage(JNIEnv* env, jlong testcase, bool should_throw) { std::string expect; @@ -79,7 +76,7 @@ throwOrGetExpectedMessage(JNIEnv *env, jlong testcase, bool should_throw) break; } if (should_throw) { - return NULL; + return nullptr; } return to_jstring(env, expect); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index b55fdc68e2..32145911d9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -21,269 +21,306 @@ using namespace realm; static void finalize_unchecked_row(jlong ptr); -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount - (JNIEnv*, jobject, jlong nativeRowPtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount(JNIEnv*, jobject, jlong nativeRowPtr) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW(nativeRowPtr)->is_attached()) + if (!ROW(nativeRowPtr)->is_attached()) { return 0; + } - return ROW(nativeRowPtr)->get_column_count(); // noexcept + return static_cast(ROW(nativeRowPtr)->get_column_count()); // noexcept } -JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnName - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnName(JNIEnv* env, jobject, + jlong nativeRowPtr, + jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } try { - return to_jstring(env, ROW(nativeRowPtr)->get_column_name( S(columnIndex))); - } CATCH_STD(); + return to_jstring(env, ROW(nativeRowPtr)->get_column_name(S(columnIndex))); + } + CATCH_STD(); return NULL; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex - (JNIEnv* env, jobject, jlong nativeRowPtr, jstring columnName) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex(JNIEnv* env, jobject, + jlong nativeRowPtr, + jstring columnName) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW(nativeRowPtr)->is_attached()) + if (!ROW(nativeRowPtr)->is_attached()) { return 0; + } try { - JStringAccessor columnName2(env, columnName); // throws - return to_jlong_or_not_found( ROW(nativeRowPtr)->get_column_index(columnName2) ); // noexcept - } CATCH_STD() + JStringAccessor columnName2(env, columnName); // throws + return to_jlong_or_not_found(ROW(nativeRowPtr)->get_column_index(columnName2)); // noexcept + } + CATCH_STD() return 0; } -JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType - (JNIEnv*, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType(JNIEnv*, jobject, jlong nativeRowPtr, + jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - return static_cast( ROW(nativeRowPtr)->get_column_type( S(columnIndex)) ); // noexcept + return static_cast(ROW(nativeRowPtr)->get_column_type(S(columnIndex))); // noexcept } -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetIndex - (JNIEnv* env, jobject, jlong nativeRowPtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetIndex(JNIEnv* env, jobject, jlong nativeRowPtr) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } - return ROW(nativeRowPtr)->get_index(); + return static_cast(ROW(nativeRowPtr)->get_index()); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLong - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLong(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } - return ROW(nativeRowPtr)->get_int( S(columnIndex) ); + return ROW(nativeRowPtr)->get_int(S(columnIndex)); } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeGetBoolean - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeGetBoolean(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } - return ROW(nativeRowPtr)->get_bool( S(columnIndex) ); + return to_jbool(ROW(nativeRowPtr)->get_bool(S(columnIndex))); } -JNIEXPORT jfloat JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFloat - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jfloat JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFloat(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } - return ROW(nativeRowPtr)->get_float( S(columnIndex) ); + return ROW(nativeRowPtr)->get_float(S(columnIndex)); } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_UncheckedRow_nativeGetDouble - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_UncheckedRow_nativeGetDouble(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } - return ROW(nativeRowPtr)->get_double( S(columnIndex) ); + return ROW(nativeRowPtr)->get_double(S(columnIndex)); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetTimestamp - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetTimestamp(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } - return to_milliseconds(ROW(nativeRowPtr)->get_timestamp( S(columnIndex) )); + return to_milliseconds(ROW(nativeRowPtr)->get_timestamp(S(columnIndex))); } -JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetString - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetString(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) - return 0; + if (!ROW_VALID(env, ROW(nativeRowPtr))) { + return nullptr; + } try { - StringData value = ROW(nativeRowPtr)->get_string( S(columnIndex) ); - return to_jstring(env, value); - } CATCH_STD() - return NULL; + StringData value = ROW(nativeRowPtr)->get_string(S(columnIndex)); + return to_jstring(env, value); + } + CATCH_STD() + return nullptr; } -JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteArray - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteArray(JNIEnv* env, jobject, + jlong nativeRowPtr, + jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) - return 0; + if (!ROW_VALID(env, ROW(nativeRowPtr))) { + return nullptr; + } - BinaryData bin = ROW(nativeRowPtr)->get_binary( S(columnIndex) ); + BinaryData bin = ROW(nativeRowPtr)->get_binary(S(columnIndex)); if (bin.is_null()) { - return NULL; + return nullptr; } else if (bin.size() <= MAX_JSIZE) { jbyteArray jresult = env->NewByteArray(static_cast(bin.size())); - if (jresult) - env->SetByteArrayRegion(jresult, 0, static_cast(bin.size()), reinterpret_cast(bin.data())); // throws + if (jresult) { + env->SetByteArrayRegion(jresult, 0, static_cast(bin.size()), + reinterpret_cast(bin.data())); // throws + } return jresult; } else { ThrowException(env, IllegalArgument, "Length of ByteArray is larger than an Int."); - return NULL; + return nullptr; } } -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLink - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLink(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } - if (ROW(nativeRowPtr)->is_null_link( S(columnIndex) )) + if (ROW(nativeRowPtr)->is_null_link(S(columnIndex))) { return jlong(-1); + } - return ROW(nativeRowPtr)->get_link( S(columnIndex) ); + return static_cast(ROW(nativeRowPtr)->get_link(S(columnIndex))); } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNullLink - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNullLink(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } - return ROW(nativeRowPtr)->is_null_link( S(columnIndex) ); + return to_jbool(ROW(nativeRowPtr)->is_null_link(S(columnIndex))); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLinkView - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLinkView(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return 0; + } - LinkViewRef* link_view_ptr = const_cast(&(LangBindHelper::get_linklist_ptr(*ROW(nativeRowPtr), S(columnIndex)))); + LinkViewRef* link_view_ptr = + const_cast(&(LangBindHelper::get_linklist_ptr(*ROW(nativeRowPtr), S(columnIndex)))); return reinterpret_cast(link_view_ptr); } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLong - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLong(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex, jlong value) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; + } try { - ROW(nativeRowPtr)->set_int( S(columnIndex), value); - } CATCH_STD() + ROW(nativeRowPtr)->set_int(S(columnIndex), value); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetBoolean - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jboolean value) +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetBoolean(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex, jboolean value) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; + } try { - ROW(nativeRowPtr)->set_bool( S(columnIndex), value); - } CATCH_STD() + ROW(nativeRowPtr)->set_bool(S(columnIndex), value); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetFloat - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jfloat value) +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetFloat(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex, jfloat value) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; + } try { - ROW(nativeRowPtr)->set_float( S(columnIndex), value); - } CATCH_STD() + ROW(nativeRowPtr)->set_float(S(columnIndex), value); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDouble - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jdouble value) +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDouble(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex, jdouble value) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; + } try { - ROW(nativeRowPtr)->set_double( S(columnIndex), value); - } CATCH_STD() + ROW(nativeRowPtr)->set_double(S(columnIndex), value); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetTimestamp - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetTimestamp(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnIndex, + jlong value) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; + } try { - ROW(nativeRowPtr)->set_timestamp( S(columnIndex), from_milliseconds(value)); - } CATCH_STD() + ROW(nativeRowPtr)->set_timestamp(S(columnIndex), from_milliseconds(value)); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetString - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jstring value) +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetString(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex, jstring value) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; + } try { - if ((value == NULL) && !(ROW(nativeRowPtr)->get_table()->is_nullable( S(columnIndex) ))) { + if ((value == nullptr) && !(ROW(nativeRowPtr)->get_table()->is_nullable(S(columnIndex)))) { ThrowNullValueException(env, ROW(nativeRowPtr)->get_table(), S(columnIndex)); return; } JStringAccessor value2(env, value); // throws - ROW(nativeRowPtr)->set_string( S(columnIndex), value2); - } CATCH_STD() + ROW(nativeRowPtr)->set_string(S(columnIndex), value2); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jbyteArray value) +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnIndex, + jbyteArray value) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; + } - jbyte* bytePtr = NULL; + jbyte* bytePtr = nullptr; try { - if (value == NULL) { + if (value == nullptr) { if (!(ROW(nativeRowPtr)->get_table()->is_nullable(S(columnIndex)))) { ThrowNullValueException(env, ROW(nativeRowPtr)->get_table(), S(columnIndex)); return; @@ -297,69 +334,78 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray return; } size_t dataLen = S(env->GetArrayLength(value)); - ROW(nativeRowPtr)->set_binary( S(columnIndex), BinaryData(reinterpret_cast(bytePtr), dataLen)); + ROW(nativeRowPtr)->set_binary(S(columnIndex), BinaryData(reinterpret_cast(bytePtr), dataLen)); } - } CATCH_STD() + } + CATCH_STD() if (bytePtr) { env->ReleaseByteArrayElements(value, bytePtr, JNI_ABORT); } } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLink - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLink(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex, jlong value) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; + } try { - ROW(nativeRowPtr)->set_link( S(columnIndex), value); - } CATCH_STD() + ROW(nativeRowPtr)->set_link(S(columnIndex), value); + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink - (JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; + } try { - ROW(nativeRowPtr)->nullify_link( S(columnIndex) ); - } CATCH_STD() + ROW(nativeRowPtr)->nullify_link(S(columnIndex)); + } + CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsAttached - (JNIEnv*, jobject, jlong nativeRowPtr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsAttached(JNIEnv*, jobject, jlong nativeRowPtr) { TR_ENTER_PTR(nativeRowPtr) - return ROW(nativeRowPtr)->is_attached(); + return to_jbool(ROW(nativeRowPtr)->is_attached()); } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeHasColumn - (JNIEnv* env, jobject obj, jlong nativeRowPtr, jstring columnName) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeHasColumn(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jstring columnName) { jlong ndx = Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex(env, obj, nativeRowPtr, columnName); - return ndx != to_jlong_or_not_found(realm::not_found); + return to_jbool(ndx != to_jlong_or_not_found(realm::not_found)); } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull - (JNIEnv*, jobject, jlong nativeRowPtr, jlong columnIndex) { +JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull(JNIEnv*, jobject, jlong nativeRowPtr, + jlong columnIndex) +{ TR_ENTER_PTR(nativeRowPtr) - return ROW(nativeRowPtr)->is_null(columnIndex); + return to_jbool(ROW(nativeRowPtr)->is_null(columnIndex)); } -JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetNull - (JNIEnv *env, jobject, jlong nativeRowPtr, jlong columnIndex) { +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetNull(JNIEnv* env, jobject, jlong nativeRowPtr, + jlong columnIndex) +{ TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) + if (!ROW_VALID(env, ROW(nativeRowPtr))) { return; - if (!TBL_AND_COL_NULLABLE(env, ROW(nativeRowPtr)->get_table(), columnIndex)) + } + if (!TBL_AND_COL_NULLABLE(env, ROW(nativeRowPtr)->get_table(), columnIndex)) { return; + } try { ROW(nativeRowPtr)->set_null(columnIndex); - } CATCH_STD() + } + CATCH_STD() } static void finalize_unchecked_row(jlong ptr) @@ -368,10 +414,8 @@ static void finalize_unchecked_row(jlong ptr) delete ROW(ptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFinalizerPtr - (JNIEnv *, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_unchecked_row); } - diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index a3878c457a..168dd34835 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -29,7 +29,7 @@ using namespace realm::jni_util; //#define USE_VLD #if defined(_MSC_VER) && defined(_DEBUG) && defined(USE_VLD) - #include "C:\\Program Files (x86)\\Visual Leak Detector\\include\\vld.h" +#include "C:\\Program Files (x86)\\Visual Leak Detector\\include\\vld.h" #endif const string TABLE_PREFIX("class_"); @@ -38,21 +38,21 @@ const string TABLE_PREFIX("class_"); JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { JNIEnv* env; - if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { + if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } else { JniUtils::initialize(vm, JNI_VERSION_1_6); // Loading classes and constructors for later use - used by box typed fields and a few methods' return value - java_lang_long = GetClass(env, "java/lang/Long"); - java_lang_long_init = env->GetMethodID(java_lang_long, "", "(J)V"); - java_lang_float = GetClass(env, "java/lang/Float"); - java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); - java_lang_double = GetClass(env, "java/lang/Double"); - java_lang_string = GetClass(env, "java/lang/String"); + java_lang_long = GetClass(env, "java/lang/Long"); + java_lang_long_init = env->GetMethodID(java_lang_long, "", "(J)V"); + java_lang_float = GetClass(env, "java/lang/Float"); + java_lang_float_init = env->GetMethodID(java_lang_float, "", "(F)V"); + java_lang_double = GetClass(env, "java/lang/Double"); + java_lang_string = GetClass(env, "java/lang/String"); java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); - java_util_date = GetClass(env, "java/util/Date"); - java_util_date_init = env->GetMethodID(java_util_date, "", "(J)V"); + java_util_date = GetClass(env, "java/util/Date"); + java_util_date_init = env->GetMethodID(java_util_date, "", "(J)V"); } return JNI_VERSION_1_6; @@ -61,7 +61,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) { JNIEnv* env; - if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { + if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { return; } else { @@ -76,11 +76,10 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) JNIEXPORT jlong JNICALL Java_io_realm_internal_Util_nativeGetMemUsage(JNIEnv*, jclass) { - return GetMemUsage(); + return static_cast(GetMemUsage()); } -JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeGetTablePrefix( - JNIEnv* env, jclass) +JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeGetTablePrefix(JNIEnv* env, jclass) { realm::StringData sd(TABLE_PREFIX); return to_jstring(env, sd); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp index 39a7b559ba..5d833ca1be 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectserver_ObjectServerSession.cpp @@ -37,26 +37,29 @@ using namespace realm; using namespace sync; -JNIEXPORT jlong JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeCreateSession - (JNIEnv *env, jobject obj, jstring localRealmPath) +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeCreateSession( + JNIEnv* env, jobject obj, jstring localRealmPath) { TR_ENTER() try { JStringAccessor local_path(env, localRealmPath); JniSession* jni_session = new JniSession(env, local_path, obj); return reinterpret_cast(jni_session); - } CATCH_STD() + } + CATCH_STD() return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeBind - (JNIEnv *env, jobject, jlong sessionPointer, jstring remoteUrl, jstring accessToken) +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeBind(JNIEnv* env, jobject, + jlong sessionPointer, + jstring remoteUrl, + jstring accessToken) { TR_ENTER() try { - auto *session_wrapper = reinterpret_cast(sessionPointer); + auto* session_wrapper = reinterpret_cast(sessionPointer); - const char *token_tmp = env->GetStringUTFChars(accessToken, NULL); + const char* token_tmp = env->GetStringUTFChars(accessToken, NULL); std::string access_token(token_tmp); env->ReleaseStringUTFChars(accessToken, token_tmp); @@ -65,20 +68,22 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_n // Bind the local Realm to the remote one session_wrapper->get_session()->bind(remote_url, access_token); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeUnbind - (JNIEnv *, jobject, jlong sessionPointer) +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeUnbind(JNIEnv*, jobject, + jlong sessionPointer) { TR_ENTER() JniSession* session = reinterpret_cast(sessionPointer); delete session; // TODO Can we avoid killing the session here? } -JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeRefresh - (JNIEnv *env, jobject, jlong sessionPointer, jstring accessToken) +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeRefresh(JNIEnv* env, jobject, + jlong sessionPointer, + jstring accessToken) { TR_ENTER() try { @@ -88,18 +93,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_n StringData access_token = StringData(token_tmp); session_wrapper->get_session()->refresh(access_token); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_internal_objectserver_ObjectServerSession_nativeNotifyCommitHappened - (JNIEnv *env, jobject, jlong sessionPointer, jlong version) +JNIEXPORT void JNICALL Java_io_realm_internal_objectserver_ObjectServerSession_nativeNotifyCommitHappened( + JNIEnv* env, jobject, jlong sessionPointer, jlong version) { TR_ENTER() try { JniSession* session_wrapper = reinterpret_cast(sessionPointer); - session_wrapper->get_session()->nonsync_transact_notify(version); - } CATCH_STD() + session_wrapper->get_session()->nonsync_transact_notify(static_cast(version)); + } + CATCH_STD() } - - diff --git a/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp b/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp index 45aa8a72c2..d18455b97b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp @@ -21,64 +21,64 @@ using namespace realm::util; using namespace realm::jni_util; -JNIEXPORT void JNICALL -Java_io_realm_log_RealmLog_nativeAddLogger(JNIEnv *env, jclass, jobject java_logger) +JNIEXPORT void JNICALL Java_io_realm_log_RealmLog_nativeAddLogger(JNIEnv* env, jclass, jobject java_logger) { try { Log::shared().add_java_logger(env, java_logger); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_log_RealmLog_nativeRemoveLogger(JNIEnv *env, jclass, jobject java_logger) +JNIEXPORT void JNICALL Java_io_realm_log_RealmLog_nativeRemoveLogger(JNIEnv* env, jclass, jobject java_logger) { try { Log::shared().remove_java_logger(env, java_logger); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_log_RealmLog_nativeClearLoggers(JNIEnv *env, jclass) +JNIEXPORT void JNICALL Java_io_realm_log_RealmLog_nativeClearLoggers(JNIEnv* env, jclass) { try { Log::shared().clear_loggers(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_log_RealmLog_nativeRegisterDefaultLogger(JNIEnv *env, jclass) +JNIEXPORT void JNICALL Java_io_realm_log_RealmLog_nativeRegisterDefaultLogger(JNIEnv* env, jclass) { try { Log::shared().register_default_logger(); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_log_RealmLog_nativeLog(JNIEnv *env, jclass, jint level, jstring tag, jthrowable throwable, - jstring message) +JNIEXPORT void JNICALL Java_io_realm_log_RealmLog_nativeLog(JNIEnv* env, jclass, jint level, jstring tag, + jthrowable throwable, jstring message) { try { JStringAccessor tag_accessor(env, tag); JStringAccessor message_accessor(env, message); Log::shared().log(static_cast(level), std::string(tag_accessor).c_str(), throwable, std::string(message_accessor).c_str()); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT void JNICALL -Java_io_realm_log_RealmLog_nativeSetLogLevel(JNIEnv *env, jclass, jint level) +JNIEXPORT void JNICALL Java_io_realm_log_RealmLog_nativeSetLogLevel(JNIEnv* env, jclass, jint level) { try { Log::shared().set_level(static_cast(level)); - } CATCH_STD() + } + CATCH_STD() } -JNIEXPORT jint JNICALL -Java_io_realm_log_RealmLog_nativeGetLogLevel(JNIEnv *env, jclass) +JNIEXPORT jint JNICALL Java_io_realm_log_RealmLog_nativeGetLogLevel(JNIEnv* env, jclass) { try { return static_cast(Log::shared().get_level()); - } CATCH_STD() + } + CATCH_STD() return static_cast(Log::Level::all); } diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index 525c70fc36..7c4480d004 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -29,7 +29,7 @@ void JavaBindingContext::before_notify() return; } if (m_java_notifier) { - m_java_notifier.call_with_local_ref([&] (JNIEnv* env, jobject notifier_obj) { + m_java_notifier.call_with_local_ref([&](JNIEnv* env, jobject notifier_obj) { // Method IDs from RealmNotifier implementation. Cache them as member vars. static JavaMethod notify_by_other_method(env, notifier_obj, "beforeNotify", "()V"); env->CallVoidMethod(notifier_obj, notify_by_other_method); @@ -37,9 +37,8 @@ void JavaBindingContext::before_notify() } } -void JavaBindingContext::did_change(std::vector const&, - std::vector const&, - bool version_changed) +void JavaBindingContext::did_change(std::vector const&, std::vector const&, + bool version_changed) { auto env = JniUtils::get_env(); @@ -47,10 +46,9 @@ void JavaBindingContext::did_change(std::vector c return; } if (version_changed) { - m_java_notifier.call_with_local_ref(env, [&] (JNIEnv*, jobject notifier_obj) { + m_java_notifier.call_with_local_ref(env, [&](JNIEnv*, jobject notifier_obj) { static JavaMethod realm_notifier_did_change_method(env, notifier_obj, "didChange", "()V"); env->CallVoidMethod(notifier_obj, realm_notifier_did_change_method); }); } } - diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index a459058c36..de3a15c62c 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -41,14 +41,15 @@ class JavaBindingContext final : public BindingContext { jni_util::JavaGlobalWeakRef m_java_notifier; public: - virtual ~JavaBindingContext() { }; + virtual ~JavaBindingContext(){}; virtual void before_notify(); - virtual void did_change(std::vector const& observers, - std::vector const& invalidated, - bool version_changed=true); + virtual void did_change(std::vector const& observers, std::vector const& invalidated, + bool version_changed = true); explicit JavaBindingContext(const ConcreteJavaBindContext& concrete_context) - : m_java_notifier(concrete_context.jni_env, concrete_context.java_notifier) { } + : m_java_notifier(concrete_context.jni_env, concrete_context.java_notifier) + { + } JavaBindingContext(const JavaBindingContext&) = delete; JavaBindingContext& operator=(const JavaBindingContext&) = delete; JavaBindingContext(JavaBindingContext&&) = delete; @@ -65,4 +66,3 @@ class JavaBindingContext final : public BindingContext { } // namespace realm #endif - diff --git a/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp b/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp index 5c894bc73f..b26c76309b 100644 --- a/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp +++ b/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp @@ -35,9 +35,8 @@ JavaSortDescriptor::operator realm::SortDescriptor() const noexcept static JavaMethod getTablePtr(m_env, m_sort_desc_obj, "getTablePtr", "()J"); jobjectArray column_indices = - static_cast(m_env->CallObjectMethod(m_sort_desc_obj, getColumnIndices)); - jbooleanArray ascendings = - static_cast(m_env->CallObjectMethod(m_sort_desc_obj, getAscendings)); + static_cast(m_env->CallObjectMethod(m_sort_desc_obj, getColumnIndices)); + jbooleanArray ascendings = static_cast(m_env->CallObjectMethod(m_sort_desc_obj, getAscendings)); jlong table_ptr = m_env->CallLongMethod(m_sort_desc_obj, getTablePtr); JniArrayOfArrays arrays(m_env, column_indices); @@ -51,7 +50,7 @@ JavaSortDescriptor::operator realm::SortDescriptor() const noexcept JniLongArray& jni_long_array = arrays[i]; std::vector col_indices; for (int j = 0; j < jni_long_array.len(); ++j) { - col_indices.push_back(static_cast(jni_long_array[j])); + col_indices.push_back(static_cast(jni_long_array[j])); } indices.push_back(std::move(col_indices)); if (ascendings) { @@ -59,9 +58,7 @@ JavaSortDescriptor::operator realm::SortDescriptor() const noexcept } } - return ascendings ? - SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices), std::move(ascending_list)) - : SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices)); + return ascendings + ? SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices), std::move(ascending_list)) + : SortDescriptor(*reinterpret_cast(table_ptr), std::move(indices)); } - - diff --git a/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp b/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp index 613b16ece7..39c175254c 100644 --- a/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp +++ b/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp @@ -30,7 +30,11 @@ namespace _impl { // doesn't make too much sense and causes troubles with memory management. class JavaSortDescriptor { public: - JavaSortDescriptor(JNIEnv* env, jobject sort_desc_obj) : m_env(env), m_sort_desc_obj(sort_desc_obj) {} + JavaSortDescriptor(JNIEnv* env, jobject sort_desc_obj) + : m_env(env) + , m_sort_desc_obj(sort_desc_obj) + { + } JavaSortDescriptor(const JavaSortDescriptor&) = delete; JavaSortDescriptor& operator=(const JavaSortDescriptor&) = delete; @@ -46,4 +50,4 @@ class JavaSortDescriptor { } // namespace _impl } // namespace realm -#endif //JAVA_SORT_DESCRIPTOR_HPP +#endif // JAVA_SORT_DESCRIPTOR_HPP diff --git a/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp b/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp index b50ff1902a..8e3972aea0 100644 --- a/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp +++ b/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp @@ -32,7 +32,8 @@ std::shared_ptr AndroidLogger::shared() return android_logger; } -void AndroidLogger::log(Log::Level level, const char* tag, jthrowable, const char* message) { +void AndroidLogger::log(Log::Level level, const char* tag, jthrowable, const char* message) +{ android_LogPriority android_log_priority; switch (level) { case Log::Level::trace: @@ -53,7 +54,7 @@ void AndroidLogger::log(Log::Level level, const char* tag, jthrowable, const cha case Log::Level::fatal: android_log_priority = ANDROID_LOG_FATAL; break; - default:// Cannot get here. + default: // Cannot get here. throw std::invalid_argument(format("Invalid log level: %1.", level)); } if (message) { @@ -74,7 +75,8 @@ void AndroidLogger::print(android_LogPriority priority, const char* tag, const c __android_log_write(priority, tag, tmp_str.c_str()); start += count; } - } else { + } + else { __android_log_write(priority, tag, log_string); } } @@ -86,8 +88,5 @@ std::shared_ptr get_default_logger() { return std::static_pointer_cast(AndroidLogger::shared()); } - } } - - diff --git a/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp b/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp index 6b90c0f6c6..53bd485407 100644 --- a/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp +++ b/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp @@ -23,7 +23,7 @@ namespace realm { namespace jni_impl { -//Default logger implementation for Android. +// Default logger implementation for Android. class AndroidLogger : public realm::jni_util::JniLogger { public: static std::shared_ptr shared(); @@ -32,11 +32,10 @@ class AndroidLogger : public realm::jni_util::JniLogger { void log(realm::jni_util::Log::Level level, const char* tag, jthrowable throwable, const char* message) override; private: - AndroidLogger() {}; + AndroidLogger(){}; static void print(android_LogPriority priority, const char* tag, const char* log_string); static const size_t LOG_ENTRY_MAX_LENGTH = 4000; }; - } } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp index ceac7d8466..10718df40c 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp @@ -38,4 +38,3 @@ bool JavaGlobalWeakRef::call_with_local_ref(std::function callback) { return call_with_local_ref(JniUtils::get_env(), callback); } - diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp index 59b50c36b0..4e6c60df56 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp @@ -28,8 +28,14 @@ namespace jni_util { // RAII wrapper for weak global ref. class JavaGlobalWeakRef { public: - JavaGlobalWeakRef() : m_weak(nullptr) {} - JavaGlobalWeakRef(JNIEnv* env, jobject obj) : m_weak(obj ? env->NewWeakGlobalRef(obj) : nullptr) { } + JavaGlobalWeakRef() + : m_weak(nullptr) + { + } + JavaGlobalWeakRef(JNIEnv* env, jobject obj) + : m_weak(obj ? env->NewWeakGlobalRef(obj) : nullptr) + { + } ~JavaGlobalWeakRef() { if (m_weak) { @@ -37,10 +43,15 @@ class JavaGlobalWeakRef { } } - JavaGlobalWeakRef(JavaGlobalWeakRef&& rhs) : m_weak(rhs.m_weak) { rhs.m_weak = nullptr; } - JavaGlobalWeakRef& operator=(JavaGlobalWeakRef&& rhs) { + JavaGlobalWeakRef(JavaGlobalWeakRef&& rhs) + : m_weak(rhs.m_weak) + { + rhs.m_weak = nullptr; + } + JavaGlobalWeakRef& operator=(JavaGlobalWeakRef&& rhs) + { this->~JavaGlobalWeakRef(); - new(this) JavaGlobalWeakRef(std::move(rhs)); + new (this) JavaGlobalWeakRef(std::move(rhs)); return *this; } @@ -68,4 +79,3 @@ class JavaGlobalWeakRef { } // namespace realm #endif // REALM_JNI_UTIL_JAVA_GLOBAL_WEAK_REF_HPP - diff --git a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp index 7b38bb69a8..283c948fba 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp @@ -22,29 +22,43 @@ namespace realm { namespace jni_util { -struct NeedToCreateLocalRef {}; +struct NeedToCreateLocalRef { +}; static constexpr NeedToCreateLocalRef need_to_create_local_ref{}; // Wraps jobject and automatically calls DeleteLocalRef when this object is destroyed. // DeleteLocalRef is not necessary to be called in most cases since all local references will be cleaned up when the -// program returns to Java from native. But if the local ref is created in a loop, consider to use this class to wrap it +// program returns to Java from native. But if the local ref is created in a loop, consider to use this class to wrap +// it // because the size of local reference table is relative small (512 bytes on Android). template class JavaLocalRef { public: // need_to_create is useful when acquire a local ref from a global weak ref. - inline JavaLocalRef(JNIEnv* env, T obj) noexcept : m_jobject(obj), m_env(env) {}; + inline JavaLocalRef(JNIEnv* env, T obj) noexcept + : m_jobject(obj) + , m_env(env){}; inline JavaLocalRef(JNIEnv* env, T obj, NeedToCreateLocalRef) noexcept - : m_jobject(env->NewLocalRef(obj)), m_env(env) {}; - inline ~JavaLocalRef() { m_env->DeleteLocalRef(m_jobject); } + : m_jobject(env->NewLocalRef(obj)) + , m_env(env){}; + inline ~JavaLocalRef() + { + m_env->DeleteLocalRef(m_jobject); + } JavaLocalRef(const JavaLocalRef&) = delete; JavaLocalRef& operator=(const JavaLocalRef&) = delete; JavaLocalRef(JavaLocalRef&& rhs) = delete; JavaLocalRef& operator=(JavaLocalRef&& rhs) = delete; - inline operator bool() const noexcept { return m_jobject != nullptr; }; - inline operator T() const noexcept { return m_jobject; } + inline operator bool() const noexcept + { + return m_jobject != nullptr; + }; + inline operator T() const noexcept + { + return m_jobject; + } private: T m_jobject; @@ -54,4 +68,3 @@ class JavaLocalRef { } // namespace realm } // namespace jni_util #endif // REALM_JNI_UTIL_JAVA_LOCAL_REF_HPP - diff --git a/realm/realm-library/src/main/cpp/jni_util/java_method.cpp b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp index 61f37748cd..2c68914b15 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_method.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp @@ -20,13 +20,13 @@ using namespace realm::jni_util; -JavaMethod::JavaMethod(JNIEnv *env, jclass cls, const char* method_name, const char* signature) +JavaMethod::JavaMethod(JNIEnv* env, jclass cls, const char* method_name, const char* signature) { m_method_id = env->GetMethodID(cls, method_name, signature); REALM_ASSERT_DEBUG(m_method_id != nullptr); } -JavaMethod::JavaMethod(JNIEnv *env, jobject obj, const char* method_name, const char* signature) +JavaMethod::JavaMethod(JNIEnv* env, jobject obj, const char* method_name, const char* signature) { jclass cls = env->GetObjectClass(obj); m_method_id = env->GetMethodID(cls, method_name, signature); @@ -34,10 +34,9 @@ JavaMethod::JavaMethod(JNIEnv *env, jobject obj, const char* method_name, const env->DeleteLocalRef(cls); } -JavaMethod::JavaMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature) +JavaMethod::JavaMethod(JNIEnv* env, const char* class_name, const char* method_name, const char* signature) { jclass cls = env->FindClass(class_name); REALM_ASSERT_DEBUG(cls != nullptr); m_method_id = env->GetMethodID(cls, method_name, signature); } - diff --git a/realm/realm-library/src/main/cpp/jni_util/java_method.hpp b/realm/realm-library/src/main/cpp/jni_util/java_method.hpp index 81c3fd64a4..a22b632fc1 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_method.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_method.hpp @@ -26,20 +26,31 @@ namespace jni_util { // safe to have a static JavaMethod object to avoid calling GetMethodID multiple times. class JavaMethod { public: - JavaMethod() : m_method_id(nullptr) {} - JavaMethod(JNIEnv *env, jclass cls, const char* method_name, const char* signature); - JavaMethod(JNIEnv *env, jobject obj, const char* method_name, const char* signature); - JavaMethod(JNIEnv *env, const char* class_name, const char* method_name, const char* signature); + JavaMethod() + : m_method_id(nullptr) + { + } + JavaMethod(JNIEnv* env, jclass cls, const char* method_name, const char* signature); + JavaMethod(JNIEnv* env, jobject obj, const char* method_name, const char* signature); + JavaMethod(JNIEnv* env, const char* class_name, const char* method_name, const char* signature); JavaMethod(const JavaMethod&) = default; JavaMethod& operator=(const JavaMethod&) = default; JavaMethod(JavaMethod&& rhs) = delete; JavaMethod& operator=(JavaMethod&& rhs) = delete; - ~JavaMethod() { } + ~JavaMethod() + { + } - inline operator bool() const noexcept { return m_method_id != nullptr; } - inline operator const jmethodID&() const noexcept { return m_method_id; } + inline operator bool() const noexcept + { + return m_method_id != nullptr; + } + inline operator const jmethodID&() const noexcept + { + return m_method_id; + } private: jmethodID m_method_id; @@ -48,4 +59,4 @@ class JavaMethod { } // namespace realm } // namespace jni_util -#endif //REALM_JNI_UTIL_JAVA_METHOD_HPP +#endif // REALM_JNI_UTIL_JAVA_METHOD_HPP diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp index 7db8ce14d9..73c117d5a0 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp @@ -24,13 +24,15 @@ using namespace realm::jni_util; static std::unique_ptr s_instance; -void JniUtils::initialize(JavaVM *vm, jint vm_version) noexcept { +void JniUtils::initialize(JavaVM* vm, jint vm_version) noexcept +{ REALM_ASSERT_DEBUG(!s_instance); s_instance = std::unique_ptr(new JniUtils(vm, vm_version)); } -JNIEnv* JniUtils::get_env(bool attach_if_needed) { +JNIEnv* JniUtils::get_env(bool attach_if_needed) +{ REALM_ASSERT_DEBUG(s_instance); JNIEnv* env; @@ -38,11 +40,11 @@ JNIEnv* JniUtils::get_env(bool attach_if_needed) { if (attach_if_needed) { jint ret = s_instance->m_vm->AttachCurrentThread(&env, nullptr); REALM_ASSERT_RELEASE(ret == JNI_OK); - } else { + } + else { REALM_ASSERT_RELEASE(false); } } return env; } - diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp index 61d8fbddfb..2ef416e5cf 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp @@ -25,7 +25,9 @@ namespace jni_util { // Util functions for JNI. class JniUtils { public: - ~JniUtils() {} + ~JniUtils() + { + } // Call this only once in JNI_OnLoad. static void initialize(JavaVM* vm, jint vm_version) noexcept; @@ -34,7 +36,11 @@ class JniUtils { static JNIEnv* get_env(bool attach_if_needed = false); private: - JniUtils(JavaVM* vm, jint vm_version) noexcept : m_vm(vm), m_vm_version(vm_version) {} + JniUtils(JavaVM* vm, jint vm_version) noexcept + : m_vm(vm) + , m_vm_version(vm_version) + { + } JavaVM* m_vm; jint m_vm_version; @@ -43,4 +49,4 @@ class JniUtils { } // namespace realm } // namespace jni_util -#endif //REALM_JNI_UTIL_JNI_UTILS_HPP +#endif // REALM_JNI_UTIL_JNI_UTILS_HPP diff --git a/realm/realm-library/src/main/cpp/jni_util/log.cpp b/realm/realm-library/src/main/cpp/jni_util/log.cpp index d4dfbefbc6..d4021e22ba 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.cpp @@ -46,8 +46,8 @@ class JavaLogger : public JniLogger { inline JNIEnv* get_current_env() noexcept { - JNIEnv *env; - if (m_jvm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) { + JNIEnv* env; + if (m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { m_jvm->AttachCurrentThread(&env, nullptr); // Should never fail } return env; @@ -55,17 +55,17 @@ class JavaLogger : public JniLogger { }; JniLogger::JniLogger() - :m_is_java_logger(false) + : m_is_java_logger(false) { } JniLogger::JniLogger(bool is_java_logger) - :m_is_java_logger(is_java_logger) + : m_is_java_logger(is_java_logger) { } JavaLogger::JavaLogger(JNIEnv* env, jobject java_logger) - :JniLogger(true) + : JniLogger(true) { jint ret = env->GetJavaVM(&m_jvm); if (ret != 0) { @@ -83,13 +83,14 @@ JavaLogger::~JavaLogger() void JavaLogger::log(Log::Level level, const char* tag, jthrowable throwable, const char* message) { - JNIEnv *env = get_current_env(); + JNIEnv* env = get_current_env(); // NOTE: If a Java exception has been thrown in native code, the below call will trigger an JNI exception - // "JNI called with pending exception". This is something that should be avoided when printing log in JNI -- Always + // "JNI called with pending exception". This is something that should be avoided when printing log in JNI -- + // Always // print log before calling env->ThrowNew. Doing env->ExceptionCheck() here creates overhead for normal cases. - env->CallVoidMethod(m_java_logger, m_log_method, level, env->NewStringUTF(tag), - throwable, env->NewStringUTF(message)); + env->CallVoidMethod(m_java_logger, m_log_method, level, env->NewStringUTF(tag), throwable, + env->NewStringUTF(message)); } bool JavaLogger::is_same_object(JNIEnv* env, jobject java_logger) @@ -118,9 +119,13 @@ void Log::add_java_logger(JNIEnv* env, const jobject java_logger) void Log::remove_java_logger(JNIEnv* env, const jobject java_logger) { std::lock_guard lock(m_mutex); - m_loggers.erase(std::remove_if(m_loggers.begin(), m_loggers.end(), [&](const auto& obj) { - return obj->m_is_java_logger && std::static_pointer_cast(obj)->is_same_object(env, java_logger); - }), m_loggers.end()); + m_loggers.erase(std::remove_if(m_loggers.begin(), m_loggers.end(), + [&](const auto& obj) { + return obj->m_is_java_logger && + std::static_pointer_cast(obj)->is_same_object(env, + java_logger); + }), + m_loggers.end()); } void Log::add_logger(std::shared_ptr logger) @@ -135,12 +140,13 @@ void Log::remove_logger(std::shared_ptr logger) { std::lock_guard lock(m_mutex); - m_loggers.erase(std::remove_if(m_loggers.begin(), m_loggers.end(), [&](const auto& obj) { - return obj == logger; - }), m_loggers.end()); + m_loggers.erase( + std::remove_if(m_loggers.begin(), m_loggers.end(), [&](const auto& obj) { return obj == logger; }), + m_loggers.end()); } -void Log::register_default_logger() { +void Log::register_default_logger() +{ add_logger(get_default_logger()); } @@ -170,13 +176,25 @@ void CoreLoggerBridge::do_log(realm::util::Logger::Level level, std::string msg) // Ignore the level threshold from the root logger. Log::Level jni_level = Log::all; // Initial value to suppress the false positive compile warning. switch (level) { - case Level::trace: jni_level = Log::trace; break; + case Level::trace: + jni_level = Log::trace; + break; case Level::debug: // Fall through. Map to same level debug. - case Level::detail: jni_level = Log::debug; break; - case Level::info: jni_level = Log::info; break; - case Level::warn: jni_level = Log::warn; break; - case Level::error: jni_level = Log::error; break; - case Level::fatal: jni_level = Log::fatal; break; + case Level::detail: + jni_level = Log::debug; + break; + case Level::info: + jni_level = Log::info; + break; + case Level::warn: + jni_level = Log::warn; + break; + case Level::error: + jni_level = Log::error; + break; + case Level::fatal: + jni_level = Log::fatal; + break; case Level::all: // Fall through. case Level::off: // Fall through. throw std::invalid_argument(format("Invalid log level.")); diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp index 930fe81889..c6b579551e 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -29,13 +29,13 @@ #include "realm/util/logger.hpp" #include "util/format.hpp" -#define TR_ENTER() \ - if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ - realm::jni_util::Log::t(" --> %1", __FUNCTION__); \ +#define TR_ENTER() \ + if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ + realm::jni_util::Log::t(" --> %1", __FUNCTION__); \ } -#define TR_ENTER_PTR(ptr) \ - if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ - realm::jni_util::Log::t(" --> %1 %2", __FUNCTION__, static_cast(ptr)); \ +#define TR_ENTER_PTR(ptr) \ + if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ + realm::jni_util::Log::t(" --> %1 %2", __FUNCTION__, static_cast(ptr)); \ } namespace realm { @@ -73,7 +73,8 @@ class Log { void register_default_logger(); void set_level(Level level); - inline Level get_level() { + inline Level get_level() + { return s_level; }; @@ -110,33 +111,34 @@ class Log { shared().log(fatal, REALM_JNI_TAG, nullptr, message); } - template + template inline static void t(const char* fmt, Args&&... args) { shared().log(trace, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); } - template + template inline static void d(const char* fmt, Args&&... args) { shared().log(debug, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); } - template + template inline static void i(const char* fmt, Args&&... args) { shared().log(info, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); } - template + template inline static void w(const char* fmt, Args&&... args) { shared().log(warn, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); } - template + template inline static void e(const char* fmt, Args&&... args) { shared().log(error, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); } - template - inline static void f(const char* fmt, Args&&... args) { + template + inline static void f(const char* fmt, Args&&... args) + { shared().log(fatal, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); } @@ -147,6 +149,7 @@ class Log { // Accessing to this var won't be thread safe and it is not necessary to be. Changing log level concurrently // won't be a critical issue for commons cases. static Level s_level; + private: Log(); @@ -154,7 +157,6 @@ class Log { std::mutex m_mutex; // Log tag for generic Realm JNI. static const char* REALM_JNI_TAG; - }; // Base Logger class. @@ -182,7 +184,7 @@ class CoreLoggerBridge : public realm::util::RootLogger { static CoreLoggerBridge& shared(); private: - CoreLoggerBridge() {}; + CoreLoggerBridge(){}; // Log tag for Realm core & sync. static const char* TAG; }; diff --git a/realm/realm-library/src/main/cpp/mem_usage.cpp b/realm/realm-library/src/main/cpp/mem_usage.cpp index d4e337d528..21a81ef15a 100644 --- a/realm/realm-library/src/main/cpp/mem_usage.cpp +++ b/realm/realm-library/src/main/cpp/mem_usage.cpp @@ -20,7 +20,7 @@ size_t GetMemUsage() { - return 0; + return 0; } #elif defined(_MSC_VER) // Microsoft Windows @@ -37,16 +37,16 @@ DWORD CalculateWSPrivate(DWORD processID); // Calculate Private Working Set // Source: http://www.codeproject.com/KB/cpp/XPWSPrivate.aspx -int Compare( const void * Val1, const void * Val2 ) +int Compare(const void* Val1, const void* Val2) { - if ( *(PDWORD)Val1 == *(PDWORD)Val2 ) - return 0; + if (*(PDWORD)Val1 == *(PDWORD)Val2) + return 0; return *(PDWORD)Val1 > *(PDWORD)Val2 ? 1 : -1; } -DWORD dWorkingSetPages[ 1024 * 128 ]; // hold the working set - // information get from QueryWorkingSet() +DWORD dWorkingSetPages[1024 * 128]; // hold the working set + // information get from QueryWorkingSet() DWORD dPageSize = 0x1000; DWORD CalculateWSPrivate(DWORD processID) @@ -55,23 +55,20 @@ DWORD CalculateWSPrivate(DWORD processID) DWORD dPrivatePages = 0; DWORD dPageTablePages = 0; - HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | - PROCESS_VM_READ, FALSE, processID ); + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID); - if ( !hProcess ) - return 0; + if (!hProcess) + return 0; - __try - { - if ( !QueryWorkingSet(hProcess, dWorkingSetPages, sizeof(dWorkingSetPages)) ) - __leave; + __try { + if (!QueryWorkingSet(hProcess, dWorkingSetPages, sizeof(dWorkingSetPages))) + __leave; DWORD dPages = dWorkingSetPages[0]; - qsort( &dWorkingSetPages[1], dPages, sizeof(DWORD), Compare ); + qsort(&dWorkingSetPages[1], dPages, sizeof(DWORD), Compare); - for ( DWORD i = 1; i <= dPages; i++ ) - { + for (DWORD i = 1; i <= dPages; i++) { DWORD dCurrentPageStatus = 0; DWORD dCurrentPageAddress; DWORD dNextPageAddress; @@ -79,38 +76,35 @@ DWORD CalculateWSPrivate(DWORD processID) DWORD dPageAddress = dWorkingSetPages[i] & 0xFFFFF000; DWORD dPageFlags = dWorkingSetPages[i] & 0x00000FFF; - while ( i <= dPages ) // iterate all pages + while (i <= dPages) // iterate all pages { dCurrentPageStatus++; - if ( i == dPages ) //if last page - break; + if (i == dPages) // if last page + break; dCurrentPageAddress = dWorkingSetPages[i] & 0xFFFFF000; - dNextPageAddress = dWorkingSetPages[i+1] & 0xFFFFF000; - dNextPageFlags = dWorkingSetPages[i+1] & 0x00000FFF; + dNextPageAddress = dWorkingSetPages[i + 1] & 0xFFFFF000; + dNextPageFlags = dWorkingSetPages[i + 1] & 0x00000FFF; - //decide whether iterate further or exit + // decide whether iterate further or exit //(this is non-contiguous page or have different flags) - if ( (dNextPageAddress == (dCurrentPageAddress + dPageSize)) - && (dNextPageFlags == dPageFlags) ) - { + if ((dNextPageAddress == (dCurrentPageAddress + dPageSize)) && (dNextPageFlags == dPageFlags)) { i++; } else - break; + break; } - if ( (dPageAddress < 0xC0000000) || (dPageAddress > 0xE0000000) ) - { - if ( dPageFlags & 0x100 ) // this is shared one - dSharedPages += dCurrentPageStatus; + if ((dPageAddress < 0xC0000000) || (dPageAddress > 0xE0000000)) { + if (dPageFlags & 0x100) // this is shared one + dSharedPages += dCurrentPageStatus; else // private one - dPrivatePages += dCurrentPageStatus; + dPrivatePages += dCurrentPageStatus; } else - dPageTablePages += dCurrentPageStatus; //page table region + dPageTablePages += dCurrentPageStatus; // page table region } DWORD dTotal = dPages * 4; @@ -119,9 +113,8 @@ DWORD CalculateWSPrivate(DWORD processID) return WSPrivate; } - __finally - { - CloseHandle( hProcess ); + __finally { + CloseHandle(hProcess); } return -1; } @@ -134,7 +127,7 @@ size_t GetMemUsage() } -#elif defined (__APPLE__) // Mac / Darwin +#elif defined(__APPLE__) // Mac / Darwin #include @@ -145,7 +138,8 @@ size_t GetMemUsage() mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; - if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count)) return -1; + if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count)) + return -1; // resident size is in t_info.resident_size; // virtual size is in t_info.virtual_size; @@ -164,9 +158,9 @@ size_t GetMemUsage() size_t GetMemUsage() { - struct proc_t usage; - look_up_our_self(&usage); - return usage.vsize; + struct proc_t usage; + look_up_our_self(&usage); + return usage.vsize; } diff --git a/realm/realm-library/src/main/cpp/objectserver_shared.hpp b/realm/realm-library/src/main/cpp/objectserver_shared.hpp index 2efb7c8b74..329d3c2ce0 100644 --- a/realm/realm-library/src/main/cpp/objectserver_shared.hpp +++ b/realm/realm-library/src/main/cpp/objectserver_shared.hpp @@ -48,7 +48,7 @@ class JniSession { JniSession& operator=(JniSession&&) = delete; JniSession(JNIEnv* env, std::string local_realm_path, jobject java_session_obj) - : m_java_session_ref(std::make_shared(env, java_session_obj)) + : m_java_session_ref(std::make_shared(env, java_session_obj)) { extern std::unique_ptr sync_client; // Get the coordinator for the given path, or null if there is none @@ -57,22 +57,24 @@ class JniSession { // the corrupted pointer. std::weak_ptr weak_session_ref(m_java_session_ref); auto sync_transact_callback = [local_realm_path](realm::VersionID, realm::VersionID) { - auto coordinator = realm::_impl::RealmCoordinator::get_existing_coordinator( - realm::StringData(local_realm_path)); + auto coordinator = + realm::_impl::RealmCoordinator::get_existing_coordinator(realm::StringData(local_realm_path)); if (coordinator) { coordinator->wake_up_notifier_worker(); } }; - auto error_handler = [weak_session_ref, local_realm_path](std::error_code error_code, bool is_fatal, const std::string message) { + auto error_handler = [weak_session_ref, local_realm_path](std::error_code error_code, bool is_fatal, + const std::string message) { if (error_code.category() != realm::sync::protocol_error_category() && - error_code.category() != realm::sync::client_error_category()) { + error_code.category() != realm::sync::client_error_category()) { // FIXME: Consider below when moving to the OS sync manager. - // Ignore this error since it may cause exceptions in java ErrorCode.fromInt(). Throwing exception there + // Ignore this error since it may cause exceptions in java ErrorCode.fromInt(). Throwing exception + // there // will trigger "called with pending exception" later since the thread is created by java, and the // endless loop is in native code. The java exception will never be thrown because of the endless loop // will never quit to java land. - realm::jni_util::Log::e("Unhandled sync client error code %1, %2. is_fatal: %3.", - error_code.value(), error_code.message(), is_fatal); + realm::jni_util::Log::e("Unhandled sync client error code %1, %2. is_fatal: %3.", error_code.value(), + error_code.message(), is_fatal); return; } @@ -84,38 +86,35 @@ class JniSession { auto protocol_error = static_cast(error_code.value()); // Documented here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup - if (protocol_error == ProtocolError::bad_server_file_ident - || protocol_error == ProtocolError::bad_client_file_ident - || protocol_error == ProtocolError::bad_server_version - || protocol_error == ProtocolError::diverging_histories) { + if (protocol_error == ProtocolError::bad_server_file_ident || + protocol_error == ProtocolError::bad_client_file_ident || + protocol_error == ProtocolError::bad_server_version || + protocol_error == ProtocolError::diverging_histories) { // Add a SyncFileActionMetadata marking the Realm as needing to be deleted. auto recovery_path = realm::util::reserve_unique_file_name( - realm::SyncManager::shared().recovery_directory_path(), - realm::util::create_timestamped_template("recovered_realm")); + realm::SyncManager::shared().recovery_directory_path(), + realm::util::create_timestamped_template("recovered_realm")); auto original_path = local_realm_path; realm::jni_util::Log::d("A client reset is scheduled for the next app start"); - realm::SyncManager::shared().perform_metadata_update([original_path = std::move(original_path), - recovery_path = std::move(recovery_path)](const auto &manager) { - realm::SyncFileActionMetadata(manager, - realm::SyncFileActionMetadata::Action::HandleRealmForClientReset, - original_path, - "", - "", - realm::util::Optional( - std::move(recovery_path))); - }); - - } else { + realm::SyncManager::shared().perform_metadata_update( + [ original_path = std::move(original_path), + recovery_path = std::move(recovery_path) ](const auto& manager) { + realm::SyncFileActionMetadata( + manager, realm::SyncFileActionMetadata::Action::HandleRealmForClientReset, original_path, + "", "", realm::util::Optional(std::move(recovery_path))); + }); + } + else { auto session_ref = weak_session_ref.lock(); if (session_ref) { - session_ref.get()->call_with_local_ref([&](JNIEnv* local_env, jobject obj) { - static realm::jni_util::JavaMethod notify_error_handler( - local_env, obj, "notifySessionError", "(ILjava/lang/String;)V"); - local_env->CallVoidMethod( - obj, notify_error_handler, error_code.value(), local_env->NewStringUTF(message.c_str())); - }); + session_ref.get()->call_with_local_ref([&](JNIEnv* local_env, jobject obj) { + static realm::jni_util::JavaMethod notify_error_handler(local_env, obj, "notifySessionError", + "(ILjava/lang/String;)V"); + local_env->CallVoidMethod(obj, notify_error_handler, error_code.value(), + local_env->NewStringUTF(message.c_str())); + }); } } }; diff --git a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp index bf7ea21178..54db725c51 100644 --- a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp +++ b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp @@ -22,22 +22,25 @@ template jbyteArray tbl_GetByteArray(JNIEnv* env, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) { - if (!TBL_AND_INDEX_VALID(env, reinterpret_cast(nativeTablePtr), columnIndex, rowIndex)) - return NULL; + if (!TBL_AND_INDEX_VALID(env, reinterpret_cast(nativeTablePtr), columnIndex, rowIndex)) { + return nullptr; + } - realm::BinaryData bin = reinterpret_cast(nativeTablePtr)->get_binary( S(columnIndex), S(rowIndex)); + realm::BinaryData bin = reinterpret_cast(nativeTablePtr)->get_binary(S(columnIndex), S(rowIndex)); if (bin.is_null()) { - return NULL; + return nullptr; } if (bin.size() <= MAX_JSIZE) { jbyteArray jresult = env->NewByteArray(static_cast(bin.size())); - if (jresult) - env->SetByteArrayRegion(jresult, 0, static_cast(bin.size()), reinterpret_cast(bin.data())); // throws + if (jresult) { + env->SetByteArrayRegion(jresult, 0, static_cast(bin.size()), + reinterpret_cast(bin.data())); // throws + } return jresult; } else { ThrowException(env, IllegalArgument, "Length of ByteArray is larger than an Int."); - return NULL; + return nullptr; } } diff --git a/realm/realm-library/src/main/cpp/utf8.hpp b/realm/realm-library/src/main/cpp/utf8.hpp index 3f76edefe4..5a156f7f08 100644 --- a/realm/realm-library/src/main/cpp/utf8.hpp +++ b/realm/realm-library/src/main/cpp/utf8.hpp @@ -35,7 +35,8 @@ namespace util { /// /// \tparam Traits16 Must define to_int_type() and to_char_type() for /// \a Char16. -template > struct Utf8x16 { +template > +struct Utf8x16 { /// Transcode as much as possible of the specified UTF-8 input, to /// UTF-16. Returns true if all input characters were transcoded, or /// transcoding stopped because the next character did not fit into the @@ -46,14 +47,13 @@ template > struct Utf8x1 /// advanced to the position where transcoding stopped. /// /// Throws only if Traits16::to_char_type() throws. - static size_t to_utf16(const char*& in_begin, const char* in_end, - Char16*& out_begin, Char16* out_end); + static size_t to_utf16(const char*& in_begin, const char* in_end, Char16*& out_begin, Char16* out_end); /// Same as to_utf16(), but in reverse. /// /// Throws only if Traits16::to_int_type() throws. - static size_t to_utf8(const Char16*& in_begin, const Char16* in_end, - char*& out_begin, char* out_end, size_t& error_code); + static size_t to_utf8(const Char16*& in_begin, const Char16* in_end, char*& out_begin, char* out_end, + size_t& error_code); /// Summarize the number of UTF-16 elements needed to hold the result of /// transcoding the specified UTF-8 string. Upon return, if \a in_begin != @@ -76,17 +76,14 @@ template > struct Utf8x1 }; - - - // Implementation: // Adapted from reference implementation. // http://www.unicode.org/resources/utf8.html // http://www.bsdua.org/files/unicode.tar.gz -template -inline size_t Utf8x16::to_utf16(const char*& in_begin, const char* in_end, - Char16*& out_begin, Char16* out_end) +template +inline size_t Utf8x16::to_utf16(const char*& in_begin, const char* in_end, Char16*& out_begin, + Char16* out_end) { using namespace std; typedef char_traits traits8; @@ -119,8 +116,7 @@ inline size_t Utf8x16::to_utf16(const char*& in_begin, const c invalid = 2; break; // Invalid continuation byte } - uint_fast16_t v = uint_fast16_t(((v1 & 0x1F) << 6) | - ((v2 & 0x3F) << 0)); + uint_fast16_t v = uint_fast16_t(((v1 & 0x1F) << 6) | ((v2 & 0x3F) << 0)); if (REALM_UNLIKELY(v < 0x80)) { invalid = 3; break; // Overlong encoding is invalid @@ -141,9 +137,7 @@ inline size_t Utf8x16::to_utf16(const char*& in_begin, const c invalid = true; break; // Invalid continuation byte } - uint_fast16_t v = uint_fast16_t(((v1 & 0x0F) << 12) | - ((v2 & 0x3F) << 6) | - ((v3 & 0x3F) << 0)); + uint_fast16_t v = uint_fast16_t(((v1 & 0x0F) << 12) | ((v2 & 0x3F) << 6) | ((v3 & 0x3F) << 0)); if (REALM_UNLIKELY(v < 0x800)) { invalid = 5; break; // Overlong encoding is invalid @@ -164,21 +158,19 @@ inline size_t Utf8x16::to_utf16(const char*& in_begin, const c invalid = 7; break; // Incomplete UTF-8 sequence } - uint_fast32_t w1 = uint_fast32_t(v1); // 16 bit -> 32 bit + uint_fast32_t w1 = uint_fast32_t(v1); // 16 bit -> 32 bit uint_fast32_t v2 = uint_fast32_t(traits8::to_int_type(in[1])); // 32 bit intended uint_fast16_t v3 = uint_fast16_t(traits8::to_int_type(in[2])); // 16 bit intended uint_fast16_t v4 = uint_fast16_t(traits8::to_int_type(in[3])); // 16 bit intended // UTF-8 layout: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - if (REALM_UNLIKELY((v2 & 0xC0) != 0x80 || (v3 & 0xC0) != 0x80 || - (v4 & 0xC0) != 0x80)) { + if (REALM_UNLIKELY((v2 & 0xC0) != 0x80 || (v3 & 0xC0) != 0x80 || (v4 & 0xC0) != 0x80)) { invalid = 8; break; // Invalid continuation byte } - uint_fast32_t v = - uint_fast32_t(((w1 & 0x07) << 18) | // Parenthesis is 32 bit partial result - ((v2 & 0x3F) << 12) | // Parenthesis is 32 bit partial result - ((v3 & 0x3F) << 6) | // Parenthesis is 16 bit partial result - ((v4 & 0x3F) << 0)); // Parenthesis is 16 bit partial result + uint_fast32_t v = uint_fast32_t(((w1 & 0x07) << 18) | // Parenthesis is 32 bit partial result + ((v2 & 0x3F) << 12) | // Parenthesis is 32 bit partial result + ((v3 & 0x3F) << 6) | // Parenthesis is 16 bit partial result + ((v4 & 0x3F) << 0)); // Parenthesis is 16 bit partial result if (REALM_UNLIKELY(v < 0x10000)) { invalid = 9; break; // Overlong encoding is invalid @@ -198,15 +190,14 @@ inline size_t Utf8x16::to_utf16(const char*& in_begin, const c break; } - in_begin = in; + in_begin = in; out_begin = out; return invalid; } -template -inline std::size_t Utf8x16::find_utf16_buf_size(const char*& in_begin, - const char* in_end, +template +inline std::size_t Utf8x16::find_utf16_buf_size(const char*& in_begin, const char* in_end, size_t& error_code) { using namespace std; @@ -257,18 +248,17 @@ inline std::size_t Utf8x16::find_utf16_buf_size(const char*& i break; } - in_begin = in; + in_begin = in; return num_out; } - // Adapted from reference implementation. // http://www.unicode.org/resources/utf8.html // http://www.bsdua.org/files/unicode.tar.gz -template -inline size_t Utf8x16::to_utf8(const Char16*& in_begin, const Char16* in_end, - char*& out_begin, char* out_end, size_t& error_code) +template +inline size_t Utf8x16::to_utf8(const Char16*& in_begin, const Char16* in_end, char*& out_begin, + char* out_end, size_t& error_code) { using namespace std; typedef char_traits traits8; @@ -343,15 +333,14 @@ inline size_t Utf8x16::to_utf8(const Char16*& in_begin, const in += 2; } - in_begin = in; + in_begin = in; out_begin = out; return !invalid; } -template -inline std::size_t Utf8x16::find_utf8_buf_size(const Char16*& in_begin, - const Char16* in_end, +template +inline std::size_t Utf8x16::find_utf8_buf_size(const Char16*& in_begin, const Char16* in_end, size_t& error_code) { using namespace std; @@ -394,7 +383,7 @@ inline std::size_t Utf8x16::find_utf8_buf_size(const Char16*& } } - in_begin = in; + in_begin = in; return num_out; } } // namespace util diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index a3df6280ae..88855ea0d2 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -48,7 +48,7 @@ jmethodID session_error_handler; void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind); -void ConvertException(JNIEnv* env, const char *file, int line) +void ConvertException(JNIEnv* env, const char* file, int line) { ostringstream ss; try { @@ -71,11 +71,11 @@ void ConvertException(JNIEnv* env, const char *file, int line) ThrowException(env, IllegalArgument, ss.str()); } catch (RealmFileException& e) { - ss << e.what() << " (" << e.underlying() << ") (" << e.path() << ") in " << file << " line " << line; + ss << e.what() << " (" << e.underlying() << ") (" << e.path() << ") in " << file << " line " << line; ThrowRealmFileException(env, ss.str(), e.kind()); } catch (File::AccessError& e) { - ss << e.what() << " (" << e.get_path() << ") in " << file << " line " << line; + ss << e.what() << " (" << e.get_path() << ") in " << file << " line " << line; ThrowException(env, FatalError, ss.str()); } catch (InvalidTransactionException& e) { @@ -87,18 +87,17 @@ void ConvertException(JNIEnv* env, const char *file, int line) ThrowException(env, IllegalArgument, ss.str()); } catch (Results::OutOfBoundsIndexException& e) { - ss << "Out of range in " << file << " line " << line - << "(requested: " << e.requested << " valid: " << e.valid_count << ")"; + ss << "Out of range in " << file << " line " << line << "(requested: " << e.requested + << " valid: " << e.valid_count << ")"; ThrowException(env, IndexOutOfBounds, ss.str()); } catch (Results::IncorrectTableException& e) { - ss << "Incorrect class in " << file << " line " << line - << "(actual: " << e.actual << " expected: " << e.expected << ")"; + ss << "Incorrect class in " << file << " line " << line << "(actual: " << e.actual + << " expected: " << e.expected << ")"; ThrowException(env, IllegalArgument, ss.str()); } catch (Results::UnsupportedColumnTypeException& e) { - ss << "Unsupported type in " << file << " line " << line - << "(field name: " << e.column_name << ")"; + ss << "Unsupported type in " << file << " line " << line << "(field name: " << e.column_name << ")"; ThrowException(env, IllegalArgument, ss.str()); } catch (Results::InvalidatedException& e) { @@ -119,7 +118,7 @@ void ConvertException(JNIEnv* env, const char *file, int line) /* catch (...) is not needed if we only throw exceptions derived from std::exception */ } -void ThrowException(JNIEnv* env, ExceptionKind exception, const char *classStr) +void ThrowException(JNIEnv* env, ExceptionKind exception, const char* classStr) { ThrowException(env, exception, classStr, ""); } @@ -238,17 +237,15 @@ jclass GetClass(JNIEnv* env, const char* classStr) return NULL; } - jclass myClass = reinterpret_cast( env->NewGlobalRef(localRefClass) ); + jclass myClass = reinterpret_cast(env->NewGlobalRef(localRefClass)); env->DeleteLocalRef(localRefClass); return myClass; } -void ThrowNullValueException(JNIEnv* env, Table* table, size_t col_ndx) { +void ThrowNullValueException(JNIEnv* env, Table* table, size_t col_ndx) +{ std::ostringstream ss; - ss << "Trying to set a non-nullable field '" - << table->get_column_name(col_ndx) - << "' in '" - << table->get_name() + ss << "Trying to set a non-nullable field '" << table->get_column_name(col_ndx) << "' in '" << table->get_name() << "' to null."; ThrowException(env, IllegalArgument, ss.str()); } @@ -280,19 +277,36 @@ namespace { // non-sign value bits, that is, an unsigned 16-bit integer, or any // signed or unsigned integer with more than 16 bits. struct JcharTraits { - static jchar to_int_type(jchar c) noexcept { return c; } - static jchar to_char_type(jchar i) noexcept { return i; } + static jchar to_int_type(jchar c) noexcept + { + return c; + } + static jchar to_char_type(jchar i) noexcept + { + return i; + } }; struct JStringCharsAccessor { - JStringCharsAccessor(JNIEnv* e, jstring s): - m_env(e), m_string(s), m_data(e->GetStringChars(s,0)), m_size(get_size(e,s)) {} + JStringCharsAccessor(JNIEnv* e, jstring s) + : m_env(e) + , m_string(s) + , m_data(e->GetStringChars(s, 0)) + , m_size(get_size(e, s)) + { + } ~JStringCharsAccessor() { m_env->ReleaseStringChars(m_string, m_data); } - const jchar* data() const noexcept { return m_data; } - size_t size() const noexcept { return m_size; } + const jchar* data() const noexcept + { + return m_data; + } + size_t size() const noexcept + { + return m_size; + } private: JNIEnv* const m_env; @@ -312,10 +326,11 @@ struct JStringCharsAccessor { } // anonymous namespace static string string_to_hex(const string& message, StringData& str, const char* in_begin, const char* in_end, - jchar* out_curr, jchar* out_end, size_t retcode, size_t error_code) { + jchar* out_curr, jchar* out_end, size_t retcode, size_t error_code) +{ ostringstream ret; - const char *s = str.data(); + const char* s = str.data(); ret << message << " "; ret << "error_code = " << error_code << "; "; ret << "retcode = " << retcode << "; "; @@ -332,17 +347,19 @@ static string string_to_hex(const string& message, StringData& str, const char* return ret.str(); } -static string string_to_hex(const string& message, const jchar *str, size_t size, size_t error_code) { +static string string_to_hex(const string& message, const jchar* str, size_t size, size_t error_code) +{ ostringstream ret; ret << message << "; "; ret << "error_code = " << error_code << "; "; - for (size_t i = 0; i < size; ++i) - ret << " 0x" << std::hex << std::setfill('0') << std::setw(4) << (int)str[i]; + for (size_t i = 0; i < size; ++i) { + ret << " 0x" << std::hex << std::setfill('0') << std::setw(4) << (int) str[i]; + } return ret.str(); } -string concat_stringdata(const char *message, StringData strData) +string concat_stringdata(const char* message, StringData strData) { if (strData.is_null()) { return std::string(message); @@ -367,47 +384,55 @@ jstring to_jstring(JNIEnv* env, StringData str) std::unique_ptr dyn_buf; const char* in_begin = str.data(); - const char* in_end = str.data() + str.size(); + const char* in_end = str.data() + str.size(); jchar* out_begin = stack_buf; - jchar* out_curr = stack_buf; - jchar* out_end = stack_buf + stack_buf_size; + jchar* out_curr = stack_buf; + jchar* out_end = stack_buf + stack_buf_size; typedef Utf8x16 Xcode; if (str.size() <= stack_buf_size) { size_t retcode = Xcode::to_utf16(in_begin, in_end, out_curr, out_end); - if (retcode != 0) - throw runtime_error(string_to_hex("Failure when converting short string to UTF-16", str, in_begin, in_end, out_curr, out_end, size_t(0), retcode)); - if (in_begin == in_end) + if (retcode != 0) { + throw runtime_error(string_to_hex("Failure when converting short string to UTF-16", str, in_begin, in_end, + out_curr, out_end, size_t(0), retcode)); + } + if (in_begin == in_end) { goto transcode_complete; + } } { const char* in_begin2 = in_begin; size_t error_code; size_t size = Xcode::find_utf16_buf_size(in_begin2, in_end, error_code); - if (in_begin2 != in_end) - throw runtime_error(string_to_hex("Failure when computing UTF-16 size", str, in_begin, in_end, out_curr, out_end, size, error_code)); - if (int_add_with_overflow_detect(size, stack_buf_size)) + if (in_begin2 != in_end) { + throw runtime_error(string_to_hex("Failure when computing UTF-16 size", str, in_begin, in_end, out_curr, + out_end, size, error_code)); + } + if (int_add_with_overflow_detect(size, stack_buf_size)) { throw runtime_error("String size overflow"); + } dyn_buf.reset(new jchar[size]); out_curr = copy(out_begin, out_curr, dyn_buf.get()); out_begin = dyn_buf.get(); - out_end = dyn_buf.get() + size; + out_end = dyn_buf.get() + size; size_t retcode = Xcode::to_utf16(in_begin, in_end, out_curr, out_end); - if (retcode != 0) - throw runtime_error(string_to_hex("Failure when converting long string to UTF-16", str, in_begin, in_end, out_curr, out_end, size_t(0), retcode)); + if (retcode != 0) { + throw runtime_error(string_to_hex("Failure when converting long string to UTF-16", str, in_begin, in_end, + out_curr, out_end, size_t(0), retcode)); + } REALM_ASSERT(in_begin == in_end); } - transcode_complete: - { - jsize out_size; - if (int_cast_with_overflow_detect(out_curr - out_begin, out_size)) - throw runtime_error("String size overflow"); - - return env->NewString(out_begin, out_size); +transcode_complete : { + jsize out_size; + if (int_cast_with_overflow_detect(out_curr - out_begin, out_size)) { + throw runtime_error("String size overflow"); } + + return env->NewString(out_begin, out_size); +} } @@ -430,14 +455,14 @@ JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) typedef Utf8x16 Xcode; size_t max_project_size = 48; - REALM_ASSERT(max_project_size <= numeric_limits::max()/4); + REALM_ASSERT(max_project_size <= numeric_limits::max() / 4); size_t buf_size; if (chars.size() <= max_project_size) { buf_size = chars.size() * 4; } else { const jchar* begin = chars.data(); - const jchar* end = begin + chars.size(); + const jchar* end = begin + chars.size(); size_t error_code; buf_size = Xcode::find_utf8_buf_size(begin, end, error_code); } @@ -445,19 +470,20 @@ JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) m_data.reset(tmp_char_array); { const jchar* in_begin = chars.data(); - const jchar* in_end = in_begin + chars.size(); + const jchar* in_end = in_begin + chars.size(); char* out_begin = m_data.get(); - char* out_end = m_data.get() + buf_size; + char* out_end = m_data.get() + buf_size; size_t error_code; if (!Xcode::to_utf8(in_begin, in_end, out_begin, out_end, error_code)) { - throw invalid_argument(string_to_hex("Failure when converting to UTF-8", chars.data(), chars.size(), error_code)); + throw invalid_argument( + string_to_hex("Failure when converting to UTF-8", chars.data(), chars.size(), error_code)); } if (in_begin != in_end) { - throw invalid_argument(string_to_hex("in_begin != in_end when converting to UTF-8", chars.data(), chars.size(), error_code)); + throw invalid_argument( + string_to_hex("in_begin != in_end when converting to UTF-8", chars.data(), chars.size(), error_code)); } m_size = out_begin - m_data.get(); // FIXME: Does this help on string issues? Or does it only help lldb? std::memset(tmp_char_array + m_size, 0, buf_size - m_size); } } - diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index d78a81fc52..000849edf4 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -38,13 +38,13 @@ #include "jni_util/log.hpp" -#define CHECK_PARAMETERS 1 // Check all parameters in API and throw exceptions in java if invalid +#define CHECK_PARAMETERS 1 // Check all parameters in API and throw exceptions in java if invalid #ifdef __cplusplus extern "C" { #endif -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved); +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved); #ifdef __cplusplus } @@ -54,35 +54,36 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved); #define STRINGIZE(x) STRINGIZE_DETAIL(x) // Exception handling -#define CATCH_STD() \ - catch (...) { \ - ConvertException(env, __FILE__, __LINE__); \ +#define CATCH_STD() \ + catch (...) \ + { \ + ConvertException(env, __FILE__, __LINE__); \ } template std::string num_to_string(T pNumber) { - std::ostringstream oOStrStream; - oOStrStream << pNumber; - return oOStrStream.str(); + std::ostringstream oOStrStream; + oOStrStream << pNumber; + return oOStrStream.str(); } -#define MAX_JINT 0x7FFFFFFFL -#define MAX_JSIZE MAX_JINT +#define MAX_JINT 0x7FFFFFFFL +#define MAX_JSIZE MAX_JINT // TODO: Clean up those marcos. Casting with marcos reduces the readability, and it is actually breaking the C++ type // conversion. e.g.: You cannot cast a pointer with S64 below. // Helper macros for better readability -#define S(x) static_cast(x) -#define B(x) static_cast(x) -#define S64(x) static_cast(x) -#define TBL(x) reinterpret_cast(x) -#define TV(x) reinterpret_cast(x) -#define LV(x) reinterpret_cast(x) -#define Q(x) reinterpret_cast(x) -#define ROW(x) reinterpret_cast(x) -#define HO(T, ptr) reinterpret_cast* >(ptr) +#define S(x) static_cast(x) +#define B(x) static_cast(x) +#define S64(x) static_cast(x) +#define TBL(x) reinterpret_cast(x) +#define TV(x) reinterpret_cast(x) +#define LV(x) reinterpret_cast(x) +#define Q(x) reinterpret_cast(x) +#define ROW(x) reinterpret_cast(x) +#define HO(T, ptr) reinterpret_cast*>(ptr) // Exception handling enum ExceptionKind { @@ -103,84 +104,86 @@ enum ExceptionKind { ExceptionKindMax // Always keep this as the last one! }; -void ConvertException(JNIEnv* env, const char *file, int line); -void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& classStr, const std::string& itemStr=""); -void ThrowException(JNIEnv* env, ExceptionKind exception, const char *classStr); -void ThrowNullValueException(JNIEnv* env, realm::Table *table, size_t col_ndx); +void ConvertException(JNIEnv* env, const char* file, int line); +void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& classStr, + const std::string& itemStr = ""); +void ThrowException(JNIEnv* env, ExceptionKind exception, const char* classStr); +void ThrowNullValueException(JNIEnv* env, realm::Table* table, size_t col_ndx); jclass GetClass(JNIEnv* env, const char* classStr); // Check parameters -#define TABLE_VALID(env,ptr) TableIsValid(env, ptr) -#define ROW_VALID(env,ptr) RowIsValid(env, ptr) -#define QUERY_VALID(env, ptr) QueryIsValid(env, ptr) +#define TABLE_VALID(env, ptr) TableIsValid(env, ptr) +#define ROW_VALID(env, ptr) RowIsValid(env, ptr) +#define QUERY_VALID(env, ptr) QueryIsValid(env, ptr) #if CHECK_PARAMETERS -#define ROW_INDEXES_VALID(env,ptr,start,end, range) RowIndexesValid(env, ptr, start, end, range) -#define ROW_INDEX_VALID(env,ptr,row) RowIndexValid(env, ptr, row) -#define ROW_INDEX_VALID_OFFSET(env,ptr,row) RowIndexValid(env, ptr, row, true) -#define TBL_AND_ROW_INDEX_VALID(env,ptr,row) TblRowIndexValid(env, ptr, row) -#define TBL_AND_ROW_INDEX_VALID_OFFSET(env,ptr,row, offset) TblRowIndexValid(env, ptr, row, offset) -#define COL_INDEX_VALID(env,ptr,col) ColIndexValid(env, ptr, col) -#define TBL_AND_COL_INDEX_VALID(env,ptr,col) TblColIndexValid(env, ptr, col) -#define COL_INDEX_AND_TYPE_VALID(env,ptr,col,type) ColIndexAndTypeValid(env, ptr, col, type) -#define TBL_AND_COL_INDEX_AND_TYPE_VALID(env,ptr,col, type) TblColIndexAndTypeValid(env, ptr, col, type) -#define TBL_AND_COL_INDEX_AND_LINK_OR_LINKLIST(env,ptr,col) TblColIndexAndLinkOrLinkList(env, ptr, col) -#define TBL_AND_COL_NULLABLE(env,ptr,col) TblColIndexAndNullable(env, ptr, col) -#define INDEX_VALID(env,ptr,col,row) IndexValid(env, ptr, col, row) -#define TBL_AND_INDEX_VALID(env,ptr,col,row) TblIndexValid(env, ptr, col, row) -#define TBL_AND_INDEX_INSERT_VALID(env,ptr,col,row) TblIndexInsertValid(env, ptr, col, row) -#define INDEX_AND_TYPE_VALID(env,ptr,col,row,type) IndexAndTypeValid(env, ptr, col, row, type) -#define TBL_AND_INDEX_AND_TYPE_VALID(env,ptr,col,row,type) TblIndexAndTypeValid(env, ptr, col, row, type) -#define TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env,ptr,col,row,type) TblIndexAndTypeInsertValid(env, ptr, col, row, type) - -#define ROW_AND_COL_INDEX_AND_TYPE_VALID(env,ptr,col,type) RowColIndexAndTypeValid(env, ptr, col, type) -#define ROW_AND_COL_INDEX_VALID(env,ptr,col) RowColIndexValid(env, ptr, col) +#define ROW_INDEXES_VALID(env, ptr, start, end, range) RowIndexesValid(env, ptr, start, end, range) +#define ROW_INDEX_VALID(env, ptr, row) RowIndexValid(env, ptr, row) +#define ROW_INDEX_VALID_OFFSET(env, ptr, row) RowIndexValid(env, ptr, row, true) +#define TBL_AND_ROW_INDEX_VALID(env, ptr, row) TblRowIndexValid(env, ptr, row) +#define TBL_AND_ROW_INDEX_VALID_OFFSET(env, ptr, row, offset) TblRowIndexValid(env, ptr, row, offset) +#define COL_INDEX_VALID(env, ptr, col) ColIndexValid(env, ptr, col) +#define TBL_AND_COL_INDEX_VALID(env, ptr, col) TblColIndexValid(env, ptr, col) +#define COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) ColIndexAndTypeValid(env, ptr, col, type) +#define TBL_AND_COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) TblColIndexAndTypeValid(env, ptr, col, type) +#define TBL_AND_COL_INDEX_AND_LINK_OR_LINKLIST(env, ptr, col) TblColIndexAndLinkOrLinkList(env, ptr, col) +#define TBL_AND_COL_NULLABLE(env, ptr, col) TblColIndexAndNullable(env, ptr, col) +#define INDEX_VALID(env, ptr, col, row) IndexValid(env, ptr, col, row) +#define TBL_AND_INDEX_VALID(env, ptr, col, row) TblIndexValid(env, ptr, col, row) +#define TBL_AND_INDEX_INSERT_VALID(env, ptr, col, row) TblIndexInsertValid(env, ptr, col, row) +#define INDEX_AND_TYPE_VALID(env, ptr, col, row, type) IndexAndTypeValid(env, ptr, col, row, type) +#define TBL_AND_INDEX_AND_TYPE_VALID(env, ptr, col, row, type) TblIndexAndTypeValid(env, ptr, col, row, type) +#define TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env, ptr, col, row, type) \ + TblIndexAndTypeInsertValid(env, ptr, col, row, type) + +#define ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) RowColIndexAndTypeValid(env, ptr, col, type) +#define ROW_AND_COL_INDEX_VALID(env, ptr, col) RowColIndexValid(env, ptr, col) #else -#define ROW_INDEXES_VALID(env,ptr,start,end, range) (true) -#define ROW_INDEX_VALID(env,ptr,row) (true) -#define ROW_INDEX_VALID_OFFSET(env,ptr,row) (true) -#define TBL_AND_ROW_INDEX_VALID(env,ptr,row) (true) -#define TBL_AND_ROW_INDEX_VALID_OFFSET(env,ptr,row, offset) (true) -#define COL_INDEX_VALID(env,ptr,col) (true) -#define TBL_AND_COL_INDEX_VALID(env,ptr,col) (true) -#define COL_INDEX_AND_TYPE_VALID(env,ptr,col,type) (true) -#define TBL_AND_COL_INDEX_AND_TYPE_VALID(env,ptr,col, type) (true) -#define TBL_AND_COL_INDEX_AND_LINK_OR_LINKLIST(env,ptr,col) (true) -#define TBL_AND_COL_NULLABLE(env,ptr,col) (true) -#define INDEX_VALID(env,ptr,col,row) (true) -#define TBL_AND_INDEX_VALID(env,ptr,col,row) (true) -#define TBL_AND_INDEX_INSERT_VALID(env,ptr,col,row) (true) -#define INDEX_AND_TYPE_VALID(env,ptr,col,row,type) (true) -#define TBL_AND_INDEX_AND_TYPE_VALID(env,ptr,col,row,type) (true) -#define TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env,ptr,col,row,type) (true) - -#define ROW_AND_COL_INDEX_AND_TYPE_VALID(env,ptr,col, type) (true) -#define ROW_AND_COL_INDEX_VALID(env,ptr,col) (true) +#define ROW_INDEXES_VALID(env, ptr, start, end, range) (true) +#define ROW_INDEX_VALID(env, ptr, row) (true) +#define ROW_INDEX_VALID_OFFSET(env, ptr, row) (true) +#define TBL_AND_ROW_INDEX_VALID(env, ptr, row) (true) +#define TBL_AND_ROW_INDEX_VALID_OFFSET(env, ptr, row, offset) (true) +#define COL_INDEX_VALID(env, ptr, col) (true) +#define TBL_AND_COL_INDEX_VALID(env, ptr, col) (true) +#define COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) (true) +#define TBL_AND_COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) (true) +#define TBL_AND_COL_INDEX_AND_LINK_OR_LINKLIST(env, ptr, col) (true) +#define TBL_AND_COL_NULLABLE(env, ptr, col) (true) +#define INDEX_VALID(env, ptr, col, row) (true) +#define TBL_AND_INDEX_VALID(env, ptr, col, row) (true) +#define TBL_AND_INDEX_INSERT_VALID(env, ptr, col, row) (true) +#define INDEX_AND_TYPE_VALID(env, ptr, col, row, type) (true) +#define TBL_AND_INDEX_AND_TYPE_VALID(env, ptr, col, row, type) (true) +#define TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env, ptr, col, row, type) (true) + +#define ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) (true) +#define ROW_AND_COL_INDEX_VALID(env, ptr, col) (true) #endif -inline jlong to_jlong_or_not_found(size_t res) { +inline jlong to_jlong_or_not_found(size_t res) +{ return (res == realm::not_found) ? jlong(-1) : jlong(res); } template inline bool TableIsValid(JNIEnv* env, T* objPtr) { - bool valid = (objPtr != NULL); + bool valid = (objPtr != nullptr); if (valid) { // Check if Table is valid if (std::is_same::value) { valid = TBL(objPtr)->is_attached(); } // TODO: Add check for TableView - } if (!valid) { realm::jni_util::Log::e("Table %1 is no longer attached!", reinterpret_cast(objPtr)); @@ -194,7 +197,8 @@ inline bool RowIsValid(JNIEnv* env, realm::Row* rowPtr) bool valid = (rowPtr != NULL && rowPtr->is_attached()); if (!valid) { realm::jni_util::Log::e("Row %1 is no longer attached!", reinterpret_cast(rowPtr)); - ThrowException(env, IllegalState, "Object is no longer valid to operate on. Was it deleted by another thread?"); + ThrowException(env, IllegalState, + "Object is no longer valid to operate on. Was it deleted by another thread?"); } return valid; } @@ -210,8 +214,9 @@ template bool RowIndexesValid(JNIEnv* env, T* pTable, jlong startIndex, jlong endIndex, jlong range) { size_t maxIndex = pTable->size(); - if (endIndex == -1) + if (endIndex == -1) { endIndex = maxIndex; + } if (startIndex < 0) { realm::jni_util::Log::e("startIndex %1 < 0 - invalid!", S64(startIndex)); ThrowException(env, IndexOutOfBounds, "startIndex < 0."); @@ -229,8 +234,7 @@ bool RowIndexesValid(JNIEnv* env, T* pTable, jlong startIndex, jlong endIndex, j return false; } if (startIndex > endIndex) { - realm::jni_util::Log::e( - "startIndex %1 > endIndex %2 - invalid!", S64(startIndex), S64(endIndex)); + realm::jni_util::Log::e("startIndex %1 > endIndex %2 - invalid!", S64(startIndex), S64(endIndex)); ThrowException(env, IndexOutOfBounds, "startIndex > endIndex."); return false; } @@ -245,31 +249,32 @@ bool RowIndexesValid(JNIEnv* env, T* pTable, jlong startIndex, jlong endIndex, j } template -inline bool RowIndexValid(JNIEnv* env, T pTable, jlong rowIndex, bool offset=false) +inline bool RowIndexValid(JNIEnv* env, T pTable, jlong rowIndex, bool offset = false) { if (rowIndex < 0) { ThrowException(env, IndexOutOfBounds, "rowIndex is less than 0."); return false; } size_t size = pTable->size(); - if (size > 0 && offset) + if (size > 0 && offset) { size -= 1; + } bool rowErr = realm::util::int_greater_than_or_equal(rowIndex, size); if (rowErr) { realm::jni_util::Log::e("rowIndex %1 > %2 - invalid!", S64(rowIndex), S64(size)); ThrowException(env, IndexOutOfBounds, - "rowIndex > available rows: " + - num_to_string(rowIndex) + " > " + num_to_string(size)); + "rowIndex > available rows: " + num_to_string(rowIndex) + " > " + num_to_string(size)); } return !rowErr; } template -inline bool TblRowIndexValid(JNIEnv* env, T* pTable, jlong rowIndex, bool offset=false) +inline bool TblRowIndexValid(JNIEnv* env, T* pTable, jlong rowIndex, bool offset = false) { if (std::is_same::value) { - if (!TableIsValid(env, TBL(pTable))) + if (!TableIsValid(env, TBL(pTable))) { return false; + } } return RowIndexValid(env, pTable, rowIndex, offset); } @@ -283,8 +288,7 @@ inline bool ColIndexValid(JNIEnv* env, T* pTable, jlong columnIndex) } bool colErr = realm::util::int_greater_than_or_equal(columnIndex, pTable->get_column_count()); if (colErr) { - realm::jni_util::Log::e( - "columnIndex %1 > %2 - invalid!", S64(columnIndex), S64(pTable->get_column_count())); + realm::jni_util::Log::e("columnIndex %1 > %2 - invalid!", S64(columnIndex), S64(pTable->get_column_count())); ThrowException(env, IndexOutOfBounds, "columnIndex > available columns."); } return !colErr; @@ -294,8 +298,9 @@ template inline bool TblColIndexValid(JNIEnv* env, T* pTable, jlong columnIndex) { if (std::is_same::value) { - if (!TableIsValid(env, TBL(pTable))) + if (!TableIsValid(env, TBL(pTable))) { return false; + } } return ColIndexValid(env, pTable, columnIndex); } @@ -308,28 +313,26 @@ inline bool RowColIndexValid(JNIEnv* env, realm::Row* pRow, jlong columnIndex) template inline bool IndexValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex) { - return ColIndexValid(env, pTable, columnIndex) - && RowIndexValid(env, pTable, rowIndex); + return ColIndexValid(env, pTable, columnIndex) && RowIndexValid(env, pTable, rowIndex); } template inline bool TblIndexValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex) { - return TableIsValid(env, pTable) - && IndexValid(env, pTable, columnIndex, rowIndex); + return TableIsValid(env, pTable) && IndexValid(env, pTable, columnIndex, rowIndex); } template inline bool TblIndexInsertValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex) { - if (!TblColIndexValid(env, pTable, columnIndex)) + if (!TblColIndexValid(env, pTable, columnIndex)) { return false; - bool rowErr = realm::util::int_greater_than(rowIndex, pTable->size()+1); + } + bool rowErr = realm::util::int_greater_than(rowIndex, pTable->size() + 1); if (rowErr) { realm::jni_util::Log::e("rowIndex %1 > %2 - invalid!", S64(rowIndex), S64(pTable->size())); - ThrowException(env, IndexOutOfBounds, - "rowIndex " + num_to_string(rowIndex) + - " > available rows " + num_to_string(pTable->size()) + "."); + ThrowException(env, IndexOutOfBounds, "rowIndex " + num_to_string(rowIndex) + " > available rows " + + num_to_string(pTable->size()) + "."); } return !rowErr; } @@ -356,8 +359,8 @@ inline bool TypeIsLinkLike(JNIEnv* env, T* pTable, jlong columnIndex) return true; } - realm::jni_util::Log::e( - "Expected columnType %1 or %2, but got %3", realm::type_Link, realm::type_LinkList, colType); + realm::jni_util::Log::e("Expected columnType %1 or %2, but got %3", realm::type_Link, realm::type_LinkList, + colType); ThrowException(env, IllegalArgument, "ColumnType invalid: expected type_Link or type_LinkList"); return false; } @@ -388,41 +391,37 @@ inline bool ColIsNullable(JNIEnv* env, T* pTable, jlong columnIndex) template inline bool ColIndexAndTypeValid(JNIEnv* env, T* pTable, jlong columnIndex, int expectColType) { - return ColIndexValid(env, pTable, columnIndex) - && TypeValid(env, pTable, columnIndex, expectColType); + return ColIndexValid(env, pTable, columnIndex) && TypeValid(env, pTable, columnIndex, expectColType); } template inline bool TblColIndexAndTypeValid(JNIEnv* env, T* pTable, jlong columnIndex, int expectColType) { - return TableIsValid(env, pTable) - && ColIndexAndTypeValid(env, pTable, columnIndex, expectColType); + return TableIsValid(env, pTable) && ColIndexAndTypeValid(env, pTable, columnIndex, expectColType); } template -inline bool TblColIndexAndLinkOrLinkList(JNIEnv* env, T* pTable, jlong columnIndex) { - return TableIsValid(env, pTable) - && TypeIsLinkLike(env, pTable, columnIndex); +inline bool TblColIndexAndLinkOrLinkList(JNIEnv* env, T* pTable, jlong columnIndex) +{ + return TableIsValid(env, pTable) && TypeIsLinkLike(env, pTable, columnIndex); } // FIXME Usually this is called after TBL_AND_INDEX_AND_TYPE_VALID which will validate Table as well. // Try to avoid duplicated checks to improve performance. template -inline bool TblColIndexAndNullable(JNIEnv* env, T* pTable, jlong columnIndex) { - return TableIsValid(env, pTable) - && ColIsNullable(env, pTable, columnIndex); +inline bool TblColIndexAndNullable(JNIEnv* env, T* pTable, jlong columnIndex) +{ + return TableIsValid(env, pTable) && ColIsNullable(env, pTable, columnIndex); } inline bool RowColIndexAndTypeValid(JNIEnv* env, realm::Row* pRow, jlong columnIndex, int expectColType) { - return RowIsValid(env, pRow) - && ColIndexAndTypeValid(env, pRow->get_table(), columnIndex, expectColType); + return RowIsValid(env, pRow) && ColIndexAndTypeValid(env, pRow->get_table(), columnIndex, expectColType); } template inline bool IndexAndTypeValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex, int expectColType) { - return IndexValid(env, pTable, columnIndex, rowIndex) - && TypeValid(env, pTable, columnIndex, expectColType); + return IndexValid(env, pTable, columnIndex, rowIndex) && TypeValid(env, pTable, columnIndex, expectColType); } template inline bool TblIndexAndTypeValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex, int expectColType) @@ -433,8 +432,8 @@ inline bool TblIndexAndTypeValid(JNIEnv* env, T* pTable, jlong columnIndex, jlon template inline bool TblIndexAndTypeInsertValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex, int expectColType) { - return TblIndexInsertValid(env, pTable, columnIndex, rowIndex) - && TypeValid(env, pTable, columnIndex, expectColType); + return TblIndexInsertValid(env, pTable, columnIndex, rowIndex) && + TypeValid(env, pTable, columnIndex, expectColType); } bool GetBinaryData(JNIEnv* env, jobject jByteBuffer, realm::BinaryData& data); @@ -442,7 +441,7 @@ bool GetBinaryData(JNIEnv* env, jobject jByteBuffer, realm::BinaryData& data); // Utility function for appending StringData, which is returned // by a lot of core functions, and might potentially be NULL. -std::string concat_stringdata(const char *message, realm::StringData data); +std::string concat_stringdata(const char* message, realm::StringData data); // Note: JNI offers methods to convert between modified UTF-8 and // UTF-16. Unfortunately these methods are not appropriate in this @@ -459,7 +458,7 @@ jstring to_jstring(JNIEnv*, realm::StringData); class JStringAccessor { public: - JStringAccessor(JNIEnv*, jstring); // throws + JStringAccessor(JNIEnv*, jstring); // throws operator realm::StringData() const noexcept { @@ -492,17 +491,18 @@ class JniLongArray { , m_javaArray(javaArray) , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) , m_array(javaArray == NULL ? NULL : env->GetLongArrayElements(javaArray, NULL)) - , m_releaseMode(JNI_ABORT) { + , m_releaseMode(JNI_ABORT) + { } JniLongArray(JniLongArray& other) = delete; JniLongArray(JniLongArray&& other) - : m_env(other.m_env) - , m_javaArray(other.m_javaArray) - , m_arrayLength(other.m_arrayLength) - , m_array(other.m_array) - , m_releaseMode(other.m_releaseMode) + : m_env(other.m_env) + , m_javaArray(other.m_javaArray) + , m_arrayLength(other.m_arrayLength) + , m_array(other.m_array) + , m_releaseMode(other.m_releaseMode) { other.m_env = nullptr; other.m_javaArray = nullptr; @@ -538,20 +538,20 @@ class JniLongArray { } private: - JNIEnv* m_env; + JNIEnv* m_env; jlongArray m_javaArray; - jsize m_arrayLength; - jlong* m_array; - jint m_releaseMode; + jsize m_arrayLength; + jlong* m_array; + jint m_releaseMode; }; template class JniArrayOfArrays { public: JniArrayOfArrays(JNIEnv* env, jobjectArray javaArray) - : m_env(env) - , m_javaArray(javaArray) - , m_arrayLength(javaArray == nullptr ? 0 : env->GetArrayLength(javaArray)) + : m_env(env) + , m_javaArray(javaArray) + , m_arrayLength(javaArray == nullptr ? 0 : env->GetArrayLength(javaArray)) { for (int i = 0; i < m_arrayLength; ++i) { // No type checking. Internal use only. @@ -588,10 +588,12 @@ class JniByteArray { , m_javaArray(javaArray) , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) , m_array(javaArray == NULL ? NULL : env->GetByteArrayElements(javaArray, NULL)) - , m_releaseMode(JNI_ABORT) { + , m_releaseMode(JNI_ABORT) + { if (m_javaArray != nullptr && m_array == nullptr) { // javaArray is not null but GetByteArrayElements returns null, something is really wrong. - throw std::runtime_error(realm::util::format("GetByteArrayElements failed on byte array %x", m_javaArray)); + throw std::runtime_error( + realm::util::format("GetByteArrayElements failed on byte array %x", m_javaArray)); } } @@ -617,11 +619,13 @@ class JniByteArray { return m_array[index]; } - inline operator realm::BinaryData() const noexcept { - return realm::BinaryData(reinterpret_cast(m_array), m_arrayLength); + inline operator realm::BinaryData() const noexcept + { + return realm::BinaryData(reinterpret_cast(m_array), m_arrayLength); } - inline operator std::vector() const noexcept { + inline operator std::vector() const noexcept + { if (m_array == nullptr) { return {}; } @@ -637,11 +641,11 @@ class JniByteArray { } private: - JNIEnv* const m_env; + JNIEnv* const m_env; jbyteArray const m_javaArray; - jsize const m_arrayLength; - jbyte* const m_array; - jint m_releaseMode; + jsize const m_arrayLength; + jbyte* const m_array; + jint m_releaseMode; }; class JniBooleanArray { @@ -651,7 +655,8 @@ class JniBooleanArray { , m_javaArray(javaArray) , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) , m_array(javaArray == NULL ? NULL : env->GetBooleanArrayElements(javaArray, NULL)) - , m_releaseMode(JNI_ABORT) { + , m_releaseMode(JNI_ABORT) + { } ~JniBooleanArray() @@ -682,11 +687,11 @@ class JniBooleanArray { } private: - JNIEnv* const m_env; + JNIEnv* const m_env; jbooleanArray const m_javaArray; - jsize const m_arrayLength; - jboolean* const m_array; - jint m_releaseMode; + jsize const m_arrayLength; + jboolean* const m_array; + jint m_releaseMode; }; extern jclass java_lang_long; @@ -736,18 +741,21 @@ inline realm::Timestamp from_milliseconds(jlong milliseconds) return realm::Timestamp(seconds, nanoseconds); } -inline jobject NewDate(JNIEnv* env, const realm::Timestamp& ts) { +inline jobject NewDate(JNIEnv* env, const realm::Timestamp& ts) +{ return env->NewObject(java_util_date, java_util_date_init, to_milliseconds(ts)); } extern const std::string TABLE_PREFIX; -static inline bool to_bool(jboolean b) { +static inline bool to_bool(jboolean b) +{ return b == JNI_TRUE; } -static inline jboolean to_jbool(bool b) { - return b?JNI_TRUE:JNI_FALSE; +static inline jboolean to_jbool(bool b) +{ + return b ? JNI_TRUE : JNI_FALSE; } #endif // REALM_JAVA_UTIL_HPP From 47adef18250eb0a302daa22bad20d3ede96277e8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 13 Mar 2017 15:08:39 +0100 Subject: [PATCH 0552/2110] Proper RealmMigrationNeededException is now thrown. (#4304) --- CHANGELOG.md | 1 + .../io/realm/RealmConfigurationTests.java | 2 +- .../java/io/realm/RealmMigrationTests.java | 21 +++++++++++++++++++ .../src/main/java/io/realm/Realm.java | 4 +++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d909505eb..3b036ef916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Enhancements * Now `targetSdkVersion` is 25. +* The real `RealmMigrationNeededException` is now thrown instead of `IllegalArgumentException` if no migration is provided for a Realm that requires it. ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 0986180f0c..7b191920be 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -391,7 +391,7 @@ public void upgradeVersionWithNoMigration() { fail(); } catch (RealmMigrationNeededException expected) { // And it should come with a cause. - assertNotNull(expected.getCause()); + assertEquals("Realm on disk need to migrate from v0 to v42", expected.getMessage()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 6789108cd1..7a5d21a4e5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -1280,6 +1280,27 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm.close(); } + // Tests that if a migration is required and no migration block was provided, then the + // original RealmMigrationNeededException is thrown instead of IllegalArgumentException + @Test + public void migrationRequired_throwsOriginalException() { + RealmConfiguration config = configFactory.createConfigurationBuilder() + // .migration() No migration block provided, but one is required + .assetFile("default0.realm") // This Realm does not have the correct schema + .build(); + + Realm realm = null; + try { + realm = Realm.getInstance(config); + fail(); + } catch (RealmMigrationNeededException ignored) { + } finally { + if (realm != null) { + realm.close(); + } + } + } + // TODO Add unit tests for default nullability // TODO Add unit tests for default Indexing for Primary keys } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 0369321866..7c604b59b9 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -269,7 +269,9 @@ static Realm createInstance(RealmConfiguration configuration, ColumnIndices[] gl deleteRealm(configuration); } else { try { - migrateRealm(configuration, e); + if (configuration.getMigration() != null) { + migrateRealm(configuration, e); + } } catch (FileNotFoundException fileNotFoundException) { // Should never happen. throw new RealmFileException(RealmFileException.Kind.NOT_FOUND, fileNotFoundException); From 45a0909d19e2fac877f1e2d3f9bf93c131e77625 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 14 Mar 2017 09:00:40 +0100 Subject: [PATCH 0553/2110] Upgrading to Realm Sync 1.3.2 (#4300) * sync 1.3.2 is released with core 2.4.0. * Fix the test since another fifo is used when open SharedGroup, see https://github.com/realm/realm-core/pull/2402 --- CHANGELOG.md | 4 ++++ dependencies.list | 4 ++-- .../src/androidTest/java/io/realm/RealmTests.java | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da83498e4..3cc3d0bbb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,15 @@ ### Bug Fixes * `Realm.migrateRealm(RealmConfiguration)` now fails correctly with an `IllegalArgumentException` if a `SyncConfiguration` is provided (#4075). +* Fixed a potential cause for Realm file corruptions (never reported). ### Deprecated ### Internal +* Upgraded to Realm Sync 1.3.2. +* Upgraded to Realm Core 2.4.0. + ## 3.0.0 (2017-02-28) diff --git a/dependencies.list b/dependencies.list index f22053bc89..ae8e1c3bb2 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.2.1 -REALM_SYNC_SHA256=b796433319e2574ea3cdb1b3dcda4e2311a2080f8e1563ea68a59b2cdac1d0a7 +REALM_SYNC_VERSION=1.3.2 +REALM_SYNC_SHA256=be79d334ca8d87785a91fa5d68264bc62de49271936d5c19b6473f34cd47f9f0 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 37aaa12769..a4ddd7fb10 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3821,7 +3821,7 @@ public boolean accept(File dir, String name) { return name.matches("realm_.*cv"); } }); - assertEquals(1, files.length); + assertEquals(2, files.length); // Tests if it works when the namedPipeDir and the named pipe files already exist. realmOnExternalStorage = Realm.getInstance(config); From a005e96dda2dfb90569e44099ec4e6bb73c8d7d6 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Tue, 14 Mar 2017 13:10:26 -0700 Subject: [PATCH 0554/2110] Backlinks (#4219) * Document multiple links to the same object * Fix non-object field reference bug * Load all tables before validating any * Update to gradle 3.4.1 * Fix Documentation * Fix Asynchronous UTs * Ignore, instead of throwing on, attempts to load Backlink fields w/JSON * Fix documentation and add notification unit tests * Require that backlink fields be final. * Address PR comments * Add tests for notification and distinct * Add interface methods and most of table validation * Check @LinkingObjects fields on JSON load * Fix fails in RealmTests * Respond to comments * Compile time type checking Refactor annotation handler * Improved error messages * Add Unit tests * Renamed Backlink to LinkingObjects. Added annotation processor tests. * Added annotation processor unit tests. * Add Backlink annotation --- CHANGELOG.md | 6 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../io/realm/annotations/LinkingObjects.java | 102 ++ .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../java/io/realm/processor/Backlink.java | 241 ++++ .../io/realm/processor/ClassMetaData.java | 510 ++++--- .../java/io/realm/processor/Constants.java | 2 + .../io/realm/processor/ModuleMetaData.java | 28 +- .../realm/processor/RealmJsonTypeHelper.java | 9 +- .../io/realm/processor/RealmProcessor.java | 101 +- .../processor/RealmProxyClassGenerator.java | 1197 +++++++++-------- .../RealmProxyInterfaceGenerator.java | 15 +- .../realm/processor/RealmVersionChecker.java | 58 +- .../main/java/io/realm/processor/Utils.java | 62 +- .../realm/processor/RealmProcessorTest.java | 105 +- .../io/realm/AllTypesRealmProxy.java | 279 ++-- .../io/realm/BooleansRealmProxy.java | 128 +- .../io/realm/NullTypesRealmProxy.java | 442 +++--- .../resources/io/realm/SimpleRealmProxy.java | 92 +- .../test/resources/some/test/AllTypes.java | 5 + .../resources/some/test/BacklinkTarget.java | 12 + .../test/resources/some/test/Backlinks.java | 16 + .../some/test/Backlinks_Ignored.java | 15 + .../some/test/Backlinks_InvalidFieldType.java | 13 + .../some/test/Backlinks_LinkedFields.java | 14 + .../some/test/Backlinks_MissingGeneric.java | 13 + .../some/test/Backlinks_MissingParameter.java | 13 + .../some/test/Backlinks_NotFinal.java | 29 + .../some/test/Backlinks_NotFound.java | 29 + .../some/test/Backlinks_Required.java | 15 + .../some/test/Backlinks_WrongType.java | 30 + .../some/test/FieldRealmResults.java | 10 + ...InvalidAllTypesModuleMixedParameters.java} | 0 ...va => InvalidAllTypesModuleWrongType.java} | 0 .../some/test/InvalidModelRealmModel_3.java | 2 +- realm/realm-library/build.gradle | 9 +- .../assets/backlinks-fieldInUse.realm | Bin 0 -> 4096 bytes .../assets/backlinks-missingSourceClass.realm | Bin 0 -> 4096 bytes .../assets/backlinks-missingSourceField.realm | Bin 0 -> 4096 bytes .../backlinks-sourceFieldWrongType.realm | Bin 0 -> 4096 bytes .../java/io/realm/BulkInsertTests.java | 2 +- .../java/io/realm/DynamicRealmTests.java | 2 +- .../io/realm/LinkingObjectsDynamicTests.java | 61 + .../io/realm/LinkingObjectsManagedTests.java | 853 ++++++++++++ .../realm/LinkingObjectsUnmanagedTests.java | 125 ++ .../java/io/realm/RealmModelTests.java | 2 +- .../java/io/realm/RealmObjectTests.java | 14 +- .../io/realm/RealmProxyMediatorTests.java | 4 +- .../java/io/realm/RealmResultsTests.java | 2 +- .../androidTest/java/io/realm/RealmTests.java | 8 +- .../java/io/realm/entities/AllJavaTypes.java | 45 +- .../io/realm/entities/BacklinksSource.java | 30 + .../io/realm/entities/BacklinksTarget.java | 39 + .../rule/TestRealmConfigurationFactory.java | 78 +- .../java/io/realm/CredentialsTests.java | 4 +- .../main/cpp/io_realm_internal_Collection.cpp | 23 + .../src/main/java/io/realm/Realm.java | 9 +- .../main/java/io/realm/RealmObjectSchema.java | 6 +- .../src/main/java/io/realm/RealmResults.java | 15 + .../src/main/java/io/realm/RealmSchema.java | 4 +- .../java/io/realm/internal/Collection.java | 16 +- .../java/io/realm/internal/ColumnIndices.java | 2 +- .../main/java/io/realm/internal/Table.java | 2 +- .../java/io/realm/internal/UncheckedRow.java | 10 +- .../internal/modules/CompositeMediator.java | 2 +- .../backlinks-missing-field-source.jar | Bin 0 -> 12289 bytes .../backlinks-missing-field-target.jar | Bin 0 -> 11892 bytes .../testLibs/backlinks-wrong-type-source.jar | Bin 0 -> 13346 bytes .../testLibs/backlinks-wrong-type-target.jar | Bin 0 -> 11940 bytes .../entities/BacklinksMissingFieldSource.java | 30 + .../BacklinksMissingFieldSourceModule.java | 22 + .../entities/BacklinksMissingFieldTarget.java | 39 + .../BacklinksMissingFieldTargetModule.java | 22 + .../entities/BacklinksMissingFieldSource.java | 30 + .../BacklinksMissingFieldSourceModule.java | 22 + .../entities/BacklinksMissingFieldTarget.java | 39 + .../BacklinksMissingFieldTargetModule.java | 22 + .../entities/BacklinksWrongTypeSource.java | 40 + .../BacklinksWrongTypeSourceModule.java | 22 + .../entities/BacklinksWrongTypeTarget.java | 39 + .../BacklinksWrongTypeTargetModule.java | 22 + .../entities/BacklinksWrongTypeSource.java | 40 + .../BacklinksWrongTypeSourceModule.java | 22 + .../entities/BacklinksWrongTypeTarget.java | 39 + .../BacklinksWrongTypeTargetModule.java | 22 + realm/tools/bin/cgen | 55 + 90 files changed, 4132 insertions(+), 1367 deletions(-) create mode 100644 realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/BacklinkTarget.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_Ignored.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_InvalidFieldType.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_LinkedFields.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_MissingGeneric.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_MissingParameter.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_NotFinal.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_NotFound.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_Required.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_WrongType.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/FieldRealmResults.java rename realm/realm-annotations-processor/src/test/resources/some/test/{InvalidAppModuleMixedParameters.java => InvalidAllTypesModuleMixedParameters.java} (100%) rename realm/realm-annotations-processor/src/test/resources/some/test/{InvalidAppModuleWrongType.java => InvalidAllTypesModuleWrongType.java} (100%) create mode 100644 realm/realm-library/src/androidTest/assets/backlinks-fieldInUse.realm create mode 100644 realm/realm-library/src/androidTest/assets/backlinks-missingSourceClass.realm create mode 100644 realm/realm-library/src/androidTest/assets/backlinks-missingSourceField.realm create mode 100644 realm/realm-library/src/androidTest/assets/backlinks-sourceFieldWrongType.realm create mode 100644 realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java create mode 100644 realm/realm-library/testLibs/backlinks-missing-field-source.jar create mode 100644 realm/realm-library/testLibs/backlinks-missing-field-target.jar create mode 100644 realm/realm-library/testLibs/backlinks-wrong-type-source.jar create mode 100644 realm/realm-library/testLibs/backlinks-wrong-type-target.jar create mode 100644 realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldSource.java create mode 100644 realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldSourceModule.java create mode 100644 realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldTarget.java create mode 100644 realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldTargetModule.java create mode 100644 realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldSource.java create mode 100644 realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldSourceModule.java create mode 100644 realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldTarget.java create mode 100644 realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldTargetModule.java create mode 100644 realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeSource.java create mode 100644 realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeSourceModule.java create mode 100644 realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeTarget.java create mode 100644 realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeTargetModule.java create mode 100644 realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeSource.java create mode 100644 realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeSourceModule.java create mode 100644 realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeTarget.java create mode 100644 realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeTargetModule.java create mode 100755 realm/tools/bin/cgen diff --git a/CHANGELOG.md b/CHANGELOG.md index e4cb2f1217..4f69349c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,13 @@ ### Enhancements * Now `targetSdkVersion` is 25. +* Now using Gradle 3.4.1 * The real `RealmMigrationNeededException` is now thrown instead of `IllegalArgumentException` if no migration is provided for a Realm that requires it. +* Partial implementation of `LinkingObjects`. There is documentation in `io.realm.annotations.LinkingObjects`. Internal docs are in `io.realm.processor.Backlink`. + * Queries on linking objects do not work. Queries like `were(...).equalTo("field.linkingObjects.id", 7).findAll()` are not yet supported. + * Linking objects are not yet supported on dynamic objects + * Migration for linking objects is not yet supported. + * Backlink verification is incomplete. Evil code can cause native crashes. ### Bug Fixes diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index c7fdbdb551..ec898ce9bb 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 1e575420eb..fddc506432 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e330fabc4f..0c43fa77cc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 767373dd38..b4fb3b92dc 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java new file mode 100644 index 0000000000..4c79b44550 --- /dev/null +++ b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for defining a reverse relationship from one class to another. This annotation can + * only be added to a field of the type {@code RealmResults}. + *
            + * To expose reverse relationships for use, create a declaration as follows:
            + * {@code
            + *
            + * public class Person extends RealmObject {
            + *   String name;
            + *   Dog dog; // Normal relation
            + * }
            + *
            + * public class Dog extends RealmObject {
            + *   // This holds all Person objects with a relation to this Dog object (= linking objects)
            + *   \@LinkingObjects("dog")
            + *   final RealmResults>Person< owners = null;
            + * }
            + *
            + * // Find all Dogs with at least one owner named John
            + * realm.where(Dog.class).equalTo("owners.name", "John").findAll();
            + * }
            + * 
            + * In the above example `Person` is related to `Dog` through the field `dog`. + * This in turn means that an implict reverse relationship exists between the class `Dog` + * and the class `Person`. This inverse relationship is made public and queryable by the `RealmResults` + * field annotated with `@LinkingObject`. This makes it possible to query properties of the dogs owner + * without having to manually maintain a "owner" field in the `Dog` class. + *

            + * Linking objects have the following properties: + *

              + *
            • The link is maintained by Realm and only works for managed objects.
            • + *
            • They can be queried just like normal relation.
            • + *
            • They can be followed just like normal relation.
            • + *
            • They are ignored when doing a `copyToRealm().`
            • + *
            • They are ignored when doing a `copyFromRealm().`
            • + *
            • They are ignored when using the various `createObjectFromJson*` and `createAllFromJson*` methods.
            • + *
            + *

            + * In addition, they have the following restrictions: + *

              + *
            • {@literal @}Ignore takes precedence. A {@literal @}LinkingObjects annotation on {@literal @}Ignore field will be ignored.
            • + *
            • The annotated field cannot be {@literal @}Required.
            • + *
            • The annotated field must be `final`.
            • + *
            • The annotation argument (the name of the backlinked field) is required.
            • + *
            • The annotation argument must be a simple field name. It cannot contain periods ('.').
            • + *
            • The annotated field must be of type `RealmResults>T<` where T is concrete class that extends `RealmModel`.
            • + *
            + * + * Note that when the source of the reverse reference (`dog` in the case above) is a `List`, there is a reverse + * reference for each forward reference, even if both forward references are to the same object. + * If the `Person` class above were defined as: + * {@code + * + * public class DogLover extends RealmObject { + * String name; + * List dogs = new ArrayList; + * } + * } + * then the following code executes without error + * {@code + * + * Dog fido = new Dog(); + * DogLover john = new DogLover() + * john.dogs.add(fido); + * john.dogs.add(fido); + * assert john.dogs.size() == 2; + * assert fido.owners.size() == 2; + * } + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.FIELD) +public @interface LinkingObjects { + /** + * The name of a field that contains a relation to an instance of the + * class containing this annotation. If this argument is not provided + * the annotation processor will abort with an {@code IllegalArgumentException}. + */ + String value() default ""; +} diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index e5aee03000..362711c0bf 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 1f2857b24f..31c57c1646 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java new file mode 100644 index 0000000000..16c49d43eb --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java @@ -0,0 +1,241 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.processor; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; + +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.Required; + +/** + * A Backlink is an implicit backwards reference. If field sourceField in instance I + * of type SourceClass holds a reference to instance J of type TargetClass, + * then a "backlink" is the automatically created reference from J to I. + * Backlinks are automatically created and destroyed when the forward references to which they correspond are + * created and destroyed. This can dramatically reduce the complexity of client code. + *

            + * To expose backinks for use, create a declaration as follows: + * + * class TargetClass { + * // ... + * {@literal @}LinkingObjects("sourceField") + * final RealmResults<SourceClass> targetField = null; + * } + * . + *

            + * The targetField, the field annotated with the {@literal @}LinkingObjects annotation must be final. + * Its type must be RealmResults whose generic argument is the SourceClass, + * the class with the sourceField that will hold the forward reference to an instance of + * TargetClass + *

            + * The sourceField must be either of type TargetClass + * or RealmList<TargetClass> + *

            + * In the code link direction is from the perspective of the link, not the backlink: the source is the + * instance to which the backlink points, the target is the instance holding the pointer. + * This is consistent with the use of terms in the Realm Core. + *

            + * As should be obvious, from the declaration, backlinks are useful only on managed objects. + * An unmanaged Model object will have, as the value of its backlink field, the value with which + * the field is initialized (typically null). + */ +final class Backlink { + private final VariableElement backlink; + + /** + * The fully-qualified name of the class containing the targetField, + * the field annotated with the {@literal @}LinkingObjects annotation. + */ + private final String targetClass; + + /** + * The name of the backlink field, in targetClass. + * A RealmResults<> field annotated with a {@literal @}LinkingObjects annotation. + */ + private final String targetField; + + /** + * The fully-qualified name of the class to which the backlinks, from targetField, + * point: The generic argument to the type of the targetField. + */ + private final String sourceClass; + + /** + * The name of the field, in SourceClass that creates the backlink. + * Making this field, in an instance I of SourceClass, + * a reference to an instance J of TargetClass + * will cause the targetField of J to contain a backlink to I. + */ + private final String sourceField; + + + public Backlink(ClassMetaData clazz, VariableElement backlink) { + if ((null == clazz) || (null == backlink)) { + throw new NullPointerException(String.format("null parameter: %s, %s", clazz, backlink)); + } + + this.backlink = backlink; + this.targetClass = clazz.getFullyQualifiedClassName(); + this.targetField = backlink.getSimpleName().toString(); + this.sourceClass = Utils.getRealmResultsType(backlink); + this.sourceField = backlink.getAnnotation(LinkingObjects.class).value(); + } + + public String getTargetClass() { + return targetClass; + } + + public String getTargetField() { + return targetField; + } + + public String getSourceClass() { + return sourceClass; + } + + public String getSourceField() { + return sourceField; + } + + public String getTargetFieldType() { + return backlink.asType().toString(); + } + + public String getSimpleSourceClass() { + return Utils.getFieldTypeSimpleName(Utils.getGenericTypeForContainer(backlink)); + } + + /** + * Validate the source side of the backlink. + * + * @return true if the backlink source looks good. + */ + public boolean validateSource() { + // A @LinkingObjects cannot be @Required + if (backlink.getAnnotation(Required.class) != null) { + Utils.error(String.format( + "The @LinkingObjects field \"%s.%s\" cannot be @Required.", + targetClass, + targetField)); + return false; + } + + // The annotation must have an argument, identifying the linked field + if ((sourceField == null) || sourceField.equals("")) { + Utils.error(String.format( + "The @LinkingObjects annotation for the field \"%s.%s\" must have a parameter identifying the link target.", + targetClass, + targetField)); + return false; + } + + // Using link syntax to try to reference a linked field is not possible. + if (sourceField.contains(".")) { + Utils.error(String.format( + "The parameter to the @LinkingObjects annotation for the field \"%s.%s\" contains a '.'. The use of '.' to specify fields in referenced classes is not supported.", + targetClass, + targetField)); + return false; + } + + // The annotated element must be a RealmResult + if (!Utils.isRealmResults(backlink)) { + Utils.error(String.format( + "The field \"%s.%s\" is a \"%s\". Fields annotated with @LinkingObjects must be RealmResults.", + targetClass, + targetField, + backlink.asType())); + return false; + } + + if (sourceClass == null) { + Utils.error(String.format( + "\"The field \"%s.%s\", annotated with @LinkingObjects, must specify a generic type.", + targetClass, + targetField)); + return false; + } + + // A @LinkingObjects field must be final + if (!backlink.getModifiers().contains(Modifier.FINAL)) { + Utils.error(String.format( + "A @LinkingObjects field \"%s.%s\" must be final.", + targetClass, + targetField)); + return false; + } + + return true; + } + + public boolean validateTarget(ClassMetaData clazz) { + VariableElement field = clazz.getDeclaredField(sourceField); + + if (field == null) { + Utils.error(String.format( + "Field \"%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", does not exist in class \"%s\".", + sourceField, + targetClass, + targetField, + sourceClass)); + return false; + } + + String fieldType = field.asType().toString(); + if (!(targetClass.equals(fieldType) || targetClass.equals(Utils.getRealmListType(field)))) { + Utils.error(String.format( + "Field \"%s.%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", has type \"%s\" instead of \"%3$s\".", + sourceClass, + sourceField, + targetClass, + targetField, + fieldType)); + return false; + } + + return true; + } + + @Override + public String toString() { + return "Backlink{" + sourceClass + "." + sourceField + " ==> " + targetClass + "." + targetField + "}"; + } + + @Override + public boolean equals(Object o) { + if (null == o) { return false; } + if (this == o) { return true; } + + if (!(o instanceof Backlink)) { return false; } + Backlink backlink = (Backlink) o; + + return targetClass.equals(backlink.targetClass) + && targetField.equals(backlink.targetField) + && sourceClass.equals(backlink.sourceClass) + && sourceField.equals(backlink.sourceField); + } + + @Override + public int hashCode() { + int result = targetClass.hashCode(); + result = 31 * result + targetField.hashCode(); + result = 31 * result + sourceClass.hashCode(); + result = 31 * result + sourceField.hashCode(); + return result; + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index de43217461..9c187ca09a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -39,6 +40,7 @@ import io.realm.annotations.Ignore; import io.realm.annotations.Index; +import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; @@ -54,6 +56,7 @@ public class ClassMetaData { private VariableElement primaryKey; // Reference to field used as primary key, if any. private List fields = new ArrayList(); // List of all fields in the class except those @Ignored. private List indexedFields = new ArrayList(); // list of all fields marked @Index. + private Set backlinks = new HashSet(); private Set nullableFields = new HashSet(); // Set of fields which can be nullable private boolean containsToString; private boolean containsEquals; @@ -70,11 +73,11 @@ public ClassMetaData(ProcessingEnvironment env, TypeElement clazz) { elements = env.getElementUtils(); TypeMirror stringType = env.getElementUtils().getTypeElement("java.lang.String").asType(); validPrimaryKeyTypes = Arrays.asList( - stringType, - typeUtils.getPrimitiveType(TypeKind.SHORT), - typeUtils.getPrimitiveType(TypeKind.INT), - typeUtils.getPrimitiveType(TypeKind.LONG), - typeUtils.getPrimitiveType(TypeKind.BYTE) + stringType, + typeUtils.getPrimitiveType(TypeKind.SHORT), + typeUtils.getPrimitiveType(TypeKind.INT), + typeUtils.getPrimitiveType(TypeKind.LONG), + typeUtils.getPrimitiveType(TypeKind.BYTE) ); for (Element element : classType.getEnclosedElements()) { @@ -91,6 +94,131 @@ public ClassMetaData(ProcessingEnvironment env, TypeElement clazz) { } } + @Override + public String toString() { + return "class " + getFullyQualifiedClassName(); + } + + public String getSimpleClassName() { + return className; + } + + public String getPackageName() { + return packageName; + } + + public String getFullyQualifiedClassName() { + return packageName + "." + className; + } + + public List getFields() { + return Collections.unmodifiableList(fields); + } + + public Set getBacklinkFields() { + return backlinks; + } + + public String getInternalGetter(String fieldName) { + return "realmGet$" + fieldName; + } + + public String getInternalSetter(String fieldName) { + return "realmSet$" + fieldName; + } + + public List getIndexedFields() { + return indexedFields; + } + + public boolean hasPrimaryKey() { + return primaryKey != null; + } + + public VariableElement getPrimaryKey() { + return primaryKey; + } + + public String getPrimaryKeyGetter() { + return getInternalGetter(primaryKey.getSimpleName().toString()); + } + + public boolean containsToString() { + return containsToString; + } + + public boolean containsEquals() { + return containsEquals; + } + + public boolean containsHashCode() { + return containsHashCode; + } + + /** + * Checks if a VariableElement is nullable. + * + * @return {@code true} if a VariableElement is nullable type, {@code false} otherwise. + */ + public boolean isNullable(VariableElement variableElement) { + return nullableFields.contains(variableElement); + } + + /** + * Checks if a VariableElement is indexed. + * + * @param variableElement the element/field + * @return {@code true} if a VariableElement is indexed, {@code false} otherwise. + */ + public boolean isIndexed(VariableElement variableElement) { + return indexedFields.contains(variableElement); + } + + /** + * Checks if a VariableElement is a primary key. + * + * @param variableElement the element/field + * @return {@code true} if a VariableElement is primary key, {@code false} otherwise. + */ + public boolean isPrimaryKey(VariableElement variableElement) { + if (primaryKey == null) { + return false; + } + return primaryKey.equals(variableElement); + } + + /** + * Returns {@code true} if the class is considered to be a valid RealmObject class. + * RealmObject and Proxy classes also have the @RealmClass annotation but are not considered valid + * RealmObject classes. + */ + public boolean isModelClass() { + String type = classType.toString(); + if (type.equals("io.realm.DynamicRealmObject")) { + return false; + } + return (!type.endsWith(".RealmObject") && !type.endsWith("RealmProxy")); + } + + /** + * Find the named field in this classes list of fields. + * This method is called only during backlink checking, + * so creating a map, even lazily, doesn't seem like a worthwhile optimization. + * If it gets used more widely, that decision should be revisited. + * + * @param fieldName The name of the sought field + * @return the named field's VariableElement, or null if not found + */ + public VariableElement getDeclaredField(String fieldName) { + if (fieldName == null) { return null; } + for (VariableElement field : fields) { + if (field.getSimpleName().toString().equals(fieldName)) { + return field; + } + } + return null; + } + /** * Builds the meta data structures for this class. Any errors or messages will be * posted on the provided Messager. @@ -101,66 +229,59 @@ public boolean generate() { // Get the package of the class Element enclosingElement = classType.getEnclosingElement(); if (!enclosingElement.getKind().equals(ElementKind.PACKAGE)) { - Utils.error("The RealmClass annotation does not support nested classes", classType); + Utils.error("The RealmClass annotation does not support nested classes.", classType); return false; } TypeElement parentElement = (TypeElement) Utils.getSuperClass(classType); - if (!parentElement.toString().equals("java.lang.Object") && !parentElement.toString().equals("io.realm.RealmObject")) { - Utils.error("Realm model classes must either extend RealmObject or implement RealmModel to be considered a valid model class", classType); - return false; + if (!parentElement.toString().equals("java.lang.Object") && !parentElement.toString().equals("io.realm.RealmObject")) + { + Utils.error("Valid model classes must either extend RealmObject or implement RealmModel.", classType); + return false; } PackageElement packageElement = (PackageElement) enclosingElement; packageName = packageElement.getQualifiedName().toString(); - if (!categorizeClassElements()) return false; - if (!checkListTypes()) return false; - if (!checkReferenceTypes()) return false; - if (!checkDefaultConstructor()) return false; - if (!checkForFinalFields()) return false; - if (!checkForTransientFields()) return false; - if (!checkForVolatileFields()) return false; + if (!categorizeClassElements()) { return false; } + if (!checkListTypes()) { return false; } + if (!checkReferenceTypes()) { return false; } + if (!checkDefaultConstructor()) { return false; } + if (!checkForFinalFields()) { return false; } + if (!checkForTransientFields()) { return false; } + if (!checkForVolatileFields()) { return false; } return true; // Meta data was successfully generated } - private boolean checkForTransientFields() { - for (VariableElement field : fields) { - if (field.getModifiers().contains(Modifier.TRANSIENT)) { - Utils.error("Transient fields are not allowed. Class: " + className + ", Field: " + - field.getSimpleName().toString()); - return false; - } - } - return true; - } + // Iterate through all class elements and add them to the appropriate internal data structures. + // Returns true if all elements could be categorized and false otherwise. + private boolean categorizeClassElements() { + for (Element element : classType.getEnclosedElements()) { + ElementKind elementKind = element.getKind(); + switch (elementKind) { + case CONSTRUCTOR: + if (Utils.isDefaultConstructor(element)) { hasDefaultConstructor = true; } + break; - private boolean checkForVolatileFields() { - for (VariableElement field : fields) { - if (field.getModifiers().contains(Modifier.VOLATILE)) { - Utils.error("Volatile fields are not allowed. Class: " + className + ", Field: " + - field.getSimpleName().toString()); - return false; + case FIELD: + if (!categorizeField(element)) { return false; } + break; + + default: } } - return true; - } - private boolean checkForFinalFields() { - for (VariableElement field : fields) { - if (field.getModifiers().contains(Modifier.FINAL)) { - Utils.error("Final fields are not allowed. Class: " + className + ", Field: " + - field.getSimpleName().toString()); - return false; - } + if (fields.size() == 0) { + Utils.error(String.format("Class \"%s\" must contain at least 1 persistable field.", className)); } + return true; } private boolean checkListTypes() { for (VariableElement field : fields) { - if (Utils.isRealmList(field)) { + if (Utils.isRealmList(field) || Utils.isRealmResults(field)) { // Check for missing generic (default back to Object) if (Utils.getGenericTypeQualifiedName(field) == null) { Utils.error("No generic type supplied for field", field); @@ -173,8 +294,10 @@ private boolean checkListTypes() { String genericCanonicalType = typeArguments.get(0).toString(); TypeElement typeElement = elements.getTypeElement(genericCanonicalType); if (typeElement.getSuperclass().getKind() == TypeKind.NONE) { - Utils.error("Only concrete Realm classes are allowed in RealmLists. Neither " + - "interfaces nor abstract classes can be used.", field); + Utils.error( + "Only concrete Realm classes are allowed in RealmLists. " + + "Neither interfaces nor abstract classes are allowed.", + field); return false; } } @@ -189,8 +312,10 @@ private boolean checkReferenceTypes() { // Check that the referenced type is a concrete class and not an interface TypeElement typeElement = elements.getTypeElement(field.asType().toString()); if (typeElement.getSuperclass().getKind() == TypeKind.NONE) { - Utils.error("Only concrete Realm classes can be referenced in model classes. " + - "Neither interfaces nor abstract classes can be used.", field); + Utils.error( + "Only concrete Realm classes can be referenced from model classes. " + + "Neither interfaces nor abstract classes are allowed.", + field); return false; } } @@ -199,201 +324,170 @@ private boolean checkReferenceTypes() { return true; } - - // Report if the default constructor is missing private boolean checkDefaultConstructor() { if (!hasDefaultConstructor) { - Utils.error("A default public constructor with no argument must be declared in " + className + " if a custom constructor is declared."); + Utils.error(String.format( + "Class \"%s\" must declare a public constructor with no arguments if it contains custom constructors.", + className)); return false; } else { return true; } } - // Iterate through all class elements and add them to the appropriate internal data structures. - // Returns true if all elements could be false if elements could not be categorized, - private boolean categorizeClassElements() { - for (Element element : classType.getEnclosedElements()) { - ElementKind elementKind = element.getKind(); - - if (elementKind.equals(ElementKind.FIELD)) { - VariableElement variableElement = (VariableElement) element; + private boolean checkForFinalFields() { + for (VariableElement field : fields) { + if (field.getModifiers().contains(Modifier.FINAL)) { + Utils.error(String.format( + "Class \"%s\" contains illegal final field \"%s\".", className, field.getSimpleName().toString())); + return false; + } + } + return true; + } - Set modifiers = variableElement.getModifiers(); - if (modifiers.contains(Modifier.STATIC)) { - continue; // completely ignore any static fields - } + private boolean checkForTransientFields() { + for (VariableElement field : fields) { + if (field.getModifiers().contains(Modifier.TRANSIENT)) { + Utils.error(String.format( + "Class \"%s\" contains illegal transient field \"%s\".", + className, + field.getSimpleName().toString())); + return false; + } + } + return true; + } - if (variableElement.getAnnotation(Ignore.class) != null) { - continue; - } + private boolean checkForVolatileFields() { + for (VariableElement field : fields) { + if (field.getModifiers().contains(Modifier.VOLATILE)) { + Utils.error(String.format( + "Class \"%s\" contains illegal volatile field \"%s\".", + className, + field.getSimpleName().toString())); + return false; + } + } + return true; + } - if (variableElement.getAnnotation(Index.class) != null) { - // The field has the @Index annotation. It's only valid for column types: - // STRING, DATE, INTEGER, BOOLEAN - String elementTypeCanonicalName = variableElement.asType().toString(); - String columnType = Constants.JAVA_TO_COLUMN_TYPES.get(elementTypeCanonicalName); - if (columnType != null && (columnType.equals("RealmFieldType.STRING") || - columnType.equals("RealmFieldType.DATE") || - columnType.equals("RealmFieldType.INTEGER") || - columnType.equals("RealmFieldType.BOOLEAN"))) { - indexedFields.add(variableElement); - } else { - Utils.error("@Index is not applicable to this field " + element + "."); - return false; - } - } + private boolean categorizeField(Element element) { + VariableElement variableElement = (VariableElement) element; - if (variableElement.getAnnotation(Required.class) == null) { - // The field doesn't have the @Required annotation. - // Without @Required annotation, boxed types/RealmObject/Date/String/bytes should be added to - // nullableFields. - // RealmList and Primitive types are NOT nullable always. @Required annotation is not supported. - if (!Utils.isPrimitiveType(variableElement) && !Utils.isRealmList(variableElement)) { - nullableFields.add(variableElement); - } - } else { - // The field has the @Required annotation - if (Utils.isPrimitiveType(variableElement)) { - Utils.error("@Required is not needed for field " + element + - " with the type " + element.asType()); - } else if (Utils.isRealmList(variableElement)) { - Utils.error("@Required is invalid for field " + element + - " with the type " + element.asType()); - } else if (Utils.isRealmModel(variableElement)) { - Utils.error("@Required is invalid for field " + element + - " with the type " + element.asType()); - } else { - // Should never get here - user should remove @Required - if (nullableFields.contains(variableElement)) { - Utils.error("Annotated field " + element + " with type " + element.asType() + - " has been added to the nullableFields before. Consider to remove @Required."); - } - } - } + // completely ignore any static fields + if (variableElement.getModifiers().contains(Modifier.STATIC)) { return true; } - if (variableElement.getAnnotation(PrimaryKey.class) != null) { - // The field has the @PrimaryKey annotation. It is only valid for - // String, short, int, long and must only be present one time - if (primaryKey != null) { - Utils.error(String.format("@PrimaryKey cannot be defined more than once. It was found here \"%s\" and here \"%s\"", - primaryKey.getSimpleName().toString(), - variableElement.getSimpleName().toString())); - return false; - } - - TypeMirror fieldType = variableElement.asType(); - if (!isValidPrimaryKeyType(fieldType)) { - Utils.error("\"" + variableElement.getSimpleName().toString() + "\" is not allowed as primary key. See @PrimaryKey for allowed types."); - return false; - } - - primaryKey = variableElement; - - // Also add as index. All types of primary key can be indexed. - if (!indexedFields.contains(variableElement)) { - indexedFields.add(variableElement); - } - } + if (variableElement.getAnnotation(Ignore.class) != null) { return true; } - fields.add(variableElement); - } else if (elementKind.equals(ElementKind.CONSTRUCTOR)) { - hasDefaultConstructor = hasDefaultConstructor || Utils.isDefaultConstructor(element); + if (variableElement.getAnnotation(Index.class) != null) { + if (!categorizeIndexField(element, variableElement)) { return false; } + } + if (variableElement.getAnnotation(Required.class) != null) { + categorizeRequiredField(element, variableElement); + } else { + // The field doesn't have the @Required annotation. + // Without @Required annotation, boxed types/RealmObject/Date/String/bytes should be added to + // nullableFields. + // RealmList and Primitive types are NOT nullable always. @Required annotation is not supported. + if (!Utils.isPrimitiveType(variableElement) && !Utils.isRealmList(variableElement)) { + nullableFields.add(variableElement); } } - if (fields.size() == 0) { - Utils.error(className + " must contain at least 1 persistable field"); + if (variableElement.getAnnotation(PrimaryKey.class) != null) { + if (!categorizePrimaryKeyField(variableElement)) { return false; } } - return true; - } + // Check @LinkingObjects last since it is not allowed to be either @Index, @Required or @PrimaryKey + if (variableElement.getAnnotation(LinkingObjects.class) != null) { + return categorizeBacklinkField(variableElement); + } - public String getSimpleClassName() { - return className; + // Standard field that appear valid (more fine grained checks might fail later). + fields.add(variableElement); + + return true; } - /** - * Returns {@code true} if the class is considered to be a valid RealmObject class. - * RealmObject and Proxy classes also have the @RealmClass annotation but are not considered valid - * RealmObject classes. - */ - public boolean isModelClass() { - String type = classType.toString(); - if (type.equals("io.realm.DynamicRealmObject")) { + private boolean categorizeIndexField(Element element, VariableElement variableElement) { + // The field has the @Index annotation. It's only valid for column types: + // STRING, DATE, INTEGER, BOOLEAN + String elementTypeCanonicalName = variableElement.asType().toString(); + String columnType = Constants.JAVA_TO_COLUMN_TYPES.get(elementTypeCanonicalName); + if (columnType != null && + (columnType.equals("RealmFieldType.STRING") || + columnType.equals("RealmFieldType.DATE") || + columnType.equals("RealmFieldType.INTEGER") || + columnType.equals("RealmFieldType.BOOLEAN"))) + { + indexedFields.add(variableElement); + } else { + Utils.error(String.format("Field \"%s\" of type \"%s\" cannot be an @Index.", element, element.asType())); return false; } - return (!type.endsWith(".RealmObject") && !type.endsWith("RealmProxy")); - } - - public String getPackageName() { - return packageName; - } - - public String getFullyQualifiedClassName() { - return packageName + "." + className; - } - public List getFields() { - return fields; + return true; } - public String getGetter(String fieldName) { - return "realmGet$" + fieldName; + // The field has the @Required annotation + private void categorizeRequiredField(Element element, VariableElement variableElement) { + if (Utils.isPrimitiveType(variableElement)) { + Utils.error(String.format( + "@Required annotation is unnecessary for primitive field \"%s\".", element)); + } else if (Utils.isRealmList(variableElement) || Utils.isRealmModel(variableElement)) { + Utils.error(String.format( + "Field \"%s\" with type \"%s\" cannot be @Required.", element, element.asType())); + } else { + // Should never get here - user should remove @Required + if (nullableFields.contains(variableElement)) { + Utils.error(String.format( + "Field \"%s\" with type \"%s\" appears to be nullable. Consider removing @Required.", + element, + element.asType())); + } + } } - public String getSetter(String fieldName) { - return "realmSet$" + fieldName; - } + // The field has the @PrimaryKey annotation. It is only valid for + // String, short, int, long and must only be present one time + private boolean categorizePrimaryKeyField(VariableElement variableElement) { + if (primaryKey != null) { + Utils.error(String.format( + "A class cannot have more than one @PrimaryKey. Both \"%s\" and \"%s\" are annotated as @PrimaryKey.", + primaryKey.getSimpleName().toString(), + variableElement.getSimpleName().toString())); + return false; + } - public List getIndexedFields() { - return indexedFields; - } + TypeMirror fieldType = variableElement.asType(); + if (!isValidPrimaryKeyType(fieldType)) { + Utils.error(String.format( + "Field \"%s\" with type \"%s\" cannot be used as primary key. See @PrimaryKey for legal types.", + variableElement.getSimpleName().toString(), + fieldType)); + return false; + } - public boolean hasPrimaryKey() { - return primaryKey != null; - } + primaryKey = variableElement; - public VariableElement getPrimaryKey() { - return primaryKey; - } + // Also add as index. All types of primary key can be indexed. + if (!indexedFields.contains(variableElement)) { + indexedFields.add(variableElement); + } - public String getPrimaryKeyGetter() { - return getGetter(primaryKey.getSimpleName().toString()); + return true; } - /** - * Checks if a VariableElement is nullable. - * - * @return {@code true} if a VariableElement is nullable type, {@code false} otherwise. - */ - public boolean isNullable(VariableElement variableElement) { - return nullableFields.contains(variableElement); - } + private boolean categorizeBacklinkField(VariableElement variableElement) { + Backlink backlink = new Backlink(this, variableElement); + if (!backlink.validateSource()) { return false; } - /** - * Checks if a VariableElement is indexed. - * - * @param variableElement the element/field - * @return {@code true} if a VariableElement is indexed, {@code false} otherwise. - */ - public boolean isIndexed(VariableElement variableElement) { - return indexedFields.contains(variableElement); - } + backlinks.add(backlink); - /** - * Checks if a VariableElement is a primary key. - * - * @param variableElement the element/field - * @return {@code true} if a VariableElement is primary key, {@code false} otherwise. - */ - public boolean isPrimaryKey(VariableElement variableElement) { - if (primaryKey == null) { - return false; - } - return primaryKey.equals(variableElement); + return true; } private boolean isValidPrimaryKeyType(TypeMirror type) { @@ -404,17 +498,5 @@ private boolean isValidPrimaryKeyType(TypeMirror type) { } return false; } - - public boolean containsToString() { - return containsToString; - } - - public boolean containsEquals() { - return containsEquals; - } - - public boolean containsHashCode() { - return containsHashCode; - } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java index 791f81df9c..fc6745ed5f 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java @@ -33,6 +33,8 @@ public class Constants { static final String STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED = "throw new io.realm.exceptions.RealmException(\"Primary key field '%s' cannot be changed after object" + " was created.\")"; + static final String STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD = + "throw new io.realm.exceptions.RealmException(\"\\\"%s\\\" field \\\"%s\\\" cannot be loaded from json\")"; static final Map JAVA_TO_REALM_TYPES; static { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java index 1ace559402..f6fda5eb7e 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java @@ -16,12 +16,20 @@ package io.realm.processor; -import io.realm.annotations.RealmModule; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; -import javax.annotation.processing.ProcessingEnvironment; -import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.element.*; -import java.util.*; +import io.realm.annotations.RealmModule; /** * Utility class for holding metadata for the Realm modules. @@ -29,14 +37,12 @@ public class ModuleMetaData { private final Set availableClasses; - private final RoundEnvironment env; private Map> modules = new HashMap>(); private Map> libraryModules = new HashMap>(); private Map classMetaData = new HashMap(); // private boolean shouldCreateDefaultModule; - public ModuleMetaData(RoundEnvironment env, Set availableClasses) { - this.env = env; + public ModuleMetaData(Set availableClasses) { this.availableClasses = availableClasses; for (ClassMetaData classMetaData : availableClasses) { this.classMetaData.put(classMetaData.getFullyQualifiedClassName(), classMetaData); @@ -48,10 +54,10 @@ public ModuleMetaData(RoundEnvironment env, Set availableClasses) * * @return True if meta data was correctly created and processing can continue, false otherwise. */ - public boolean generate(ProcessingEnvironment processingEnv) { + public boolean generate(Set clazzes) { // Check that modules are setup correctly - for (Element classElement : env.getElementsAnnotatedWith(RealmModule.class)) { + for (Element classElement : clazzes) { String classSimpleName = classElement.getSimpleName().toString(); // Check that the annotation is only applied to a class @@ -115,6 +121,7 @@ public boolean generate(ProcessingEnvironment processingEnv) { // Detour needed to access the class elements in the array // See http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ + @SuppressWarnings("unchecked") private Set getClassMetaDataFromModule(Element classElement) { AnnotationMirror annotationMirror = getAnnotationMirror(classElement); AnnotationValue annotationValue = getAnnotationValue(annotationMirror); @@ -129,6 +136,7 @@ private Set getClassMetaDataFromModule(Element classElement) { // Work-around for asking for a Class primitive array which would otherwise throw a TypeMirrorException // https://community.oracle.com/thread/1184190 + @SuppressWarnings("unchecked") private boolean hasCustomClassList(Element classElement) { AnnotationMirror annotationMirror = getAnnotationMirror(classElement); AnnotationValue annotationValue = getAnnotationValue(annotationMirror); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java index af9402ab21..26906a0e7f 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java @@ -150,6 +150,13 @@ public static void emitFillJavaTypeWithJsonValue(String interfaceName, String se } } + public static void emitIllegalJsonValueException(String fieldType, String fieldName, JavaWriter writer) + throws IOException { + writer.beginControlFlow("if (json.has(\"%s\"))", fieldName); + writer.emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD, fieldType, fieldName); + writer.endControlFlow(); + } + public static void emitFillRealmObjectWithJsonValue(String interfaceName, String setter, String fieldName, String qualifiedFieldType, String proxyClass, JavaWriter writer) throws IOException { writer @@ -185,7 +192,7 @@ public static void emitFillRealmListWithJsonValue(String interfaceName, String g public static void emitFillJavaTypeFromStream(String interfaceName, ClassMetaData metaData, String fieldName, String fieldType, JavaWriter writer) throws IOException { - String setter = metaData.getSetter(fieldName); + String setter = metaData.getInternalSetter(fieldName); boolean isPrimaryKey = false; if (metaData.hasPrimaryKey() && metaData.getPrimaryKey().getSimpleName().toString().equals(fieldName)) { isPrimaryKey = true; diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index 59f14327d7..fb3562963c 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -17,10 +17,10 @@ package io.realm.processor; import java.io.IOException; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.TreeSet; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -31,6 +31,7 @@ import javax.lang.model.element.TypeElement; import io.realm.annotations.RealmClass; +import io.realm.annotations.RealmModule; /** * The RealmProcessor is responsible for creating the plumbing that connects the RealmObjects to a Realm. The process @@ -122,12 +123,17 @@ "io.realm.annotations.Required" }) public class RealmProcessor extends AbstractProcessor { - // Don't consume annotations. This allows 3rd party annotation processors to run. private static final boolean CONSUME_ANNOTATIONS = false; - Set classesToValidate = new HashSet(); + + // List of all fields maintained by Realm (RealmResults) + private final Set classesToValidate = new HashSet(); + // List of backlinks + private final Set backlinksToValidate = new HashSet(); + private boolean hasProcessedModules = false; + private int round; @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); @@ -135,42 +141,55 @@ public class RealmProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - // Don't run this processor in subsequent runs. We created everything in the first one. - if (hasProcessedModules) { - return CONSUME_ANNOTATIONS; + round++; + + if (round == 0) { + RealmVersionChecker updateChecker = RealmVersionChecker.getInstance(processingEnv); + updateChecker.executeRealmVersionUpdate(); + } + + if (roundEnv.errorRaised()) { return true; } + + if (!hasProcessedModules) { + Utils.initialize(processingEnv); + + if (!processAnnotations(roundEnv)) { return true; } + + hasProcessedModules = true; + if (!processModules(roundEnv)) { return true; } } - RealmVersionChecker updateChecker = RealmVersionChecker.getInstance(processingEnv); - updateChecker.executeRealmVersionUpdate(); - Utils.initialize(processingEnv); + if (roundEnv.processingOver()) { + if (!validateBacklinks()) { return true; } + } - Set packages = new TreeSet(); + return CONSUME_ANNOTATIONS; + } - // Create all proxy classes + // Create all proxy classes + private boolean processAnnotations(RoundEnvironment roundEnv) { for (Element classElement : roundEnv.getElementsAnnotatedWith(RealmClass.class)) { // The class must either extend RealmObject or implement RealmModel if (!Utils.isImplementingMarkerInterface(classElement)) { - Utils.error("A RealmClass annotated object must implement RealmModel or derive from RealmObject", classElement); + Utils.error("A RealmClass annotated object must implement RealmModel or derive from RealmObject.", classElement); + return false; } // Check the annotation was applied to a Class if (!classElement.getKind().equals(ElementKind.CLASS)) { - Utils.error("The RealmClass annotation can only be applied to classes", classElement); - return true; // Abort processing by claiming all annotations + Utils.error("The RealmClass annotation can only be applied to classes.", classElement); + return false; } ClassMetaData metadata = new ClassMetaData(processingEnv, (TypeElement) classElement); - if (!metadata.isModelClass()) { - continue; - } + if (!metadata.isModelClass()) { continue; } + Utils.note("Processing class " + metadata.getSimpleClassName()); - boolean success = metadata.generate(); - if (!success) { - return true; - } + if (!metadata.generate()) { return false; } + classesToValidate.add(metadata); - packages.add(metadata.getPackageName()); + backlinksToValidate.addAll(metadata.getBacklinkFields()); RealmProxyInterfaceGenerator interfaceGenerator = new RealmProxyInterfaceGenerator(processingEnv, metadata); try { @@ -189,17 +208,13 @@ public boolean process(Set annotations, RoundEnvironment } } - hasProcessedModules = true; - processModules(roundEnv); - - return CONSUME_ANNOTATIONS; + return true; } // Returns true if modules was processed successfully, false otherwise private boolean processModules(RoundEnvironment roundEnv) { - - ModuleMetaData moduleMetaData = new ModuleMetaData(roundEnv, classesToValidate); - if (!moduleMetaData.generate(processingEnv)) { + ModuleMetaData moduleMetaData = new ModuleMetaData(classesToValidate); + if (!moduleMetaData.generate(roundEnv.getElementsAnnotatedWith(RealmModule.class))) { return false; } @@ -245,4 +260,32 @@ private boolean createMediator(String simpleModuleName, Set modul return true; } + + // Because library classes are processed separately, there is no guarantee + // that this method can see all of the classes necessary to completely validate + // all of the backlinks. If it can find the fully-qualified class, though, + // and prove that the class either does not contain the necessary field, or + // that it does contain the field, but the field is of the wrong type, it can + // catch the error at compile time. + // Give all failure messages before failing + private boolean validateBacklinks() { + boolean allValid = true; + + Map realmClasses = new HashMap(classesToValidate.size()); + for (ClassMetaData classData: classesToValidate) { + realmClasses.put(classData.getFullyQualifiedClassName(), classData); + } + + for (Backlink backlink: backlinksToValidate) { + ClassMetaData clazz = realmClasses.get(backlink.getSourceClass()); + + // If the class is not here it might be part of some other compilation unit. + if (clazz == null) { continue; } + + // If the class is here, we can validate it. + if (!backlink.validateTarget(clazz) && allValid) { allValid = false; } + } + + return allValid; + } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 6aa0cf7e0e..24497546e6 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -22,9 +22,10 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.EnumSet; -import java.util.List; +import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Modifier; @@ -34,8 +35,10 @@ import javax.tools.JavaFileObject; public class RealmProxyClassGenerator { - private ProcessingEnvironment processingEnvironment; - private ClassMetaData metadata; + private static final String BACKLINKS_FIELD_EXTENSION = "Backlinks"; + + private final ProcessingEnvironment processingEnvironment; + private final ClassMetaData metadata; private final String simpleClassName; private final String qualifiedClassName; private final String interfaceName; @@ -59,7 +62,7 @@ public void generate() throws IOException, UnsupportedOperationException { writer.setIndent(Constants.INDENT); writer.emitPackage(Constants.REALM_PACKAGE_NAME) - .emitEmptyLine(); + .emitEmptyLine(); ArrayList imports = new ArrayList(); imports.add("android.annotation.TargetApi"); @@ -89,9 +92,8 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("org.json.JSONException"); imports.add("org.json.JSONArray"); - Collections.sort(imports); - writer.emitImports(imports); - writer.emitEmptyLine(); + writer.emitImports(imports) + .emitEmptyLine(); // Begin the class definition writer.beginType( @@ -107,8 +109,10 @@ public void generate() throws IOException, UnsupportedOperationException { emitClassFields(writer); emitConstructor(writer); + emitInjectContextMethod(writer); - emitAccessors(writer); + emitPersistedFieldAccessors(writer); + emitBacklinkFieldAccessors(writer); emitCreateRealmObjectSchemaMethod(writer); emitInitTableMethod(writer); emitValidateTableMethod(writer); @@ -160,17 +164,17 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { final String columnName = variableElement.getSimpleName().toString(); final String columnIndexVarName = columnIndexVarName(variableElement); writer.emitStatement("this.%s = getValidColumnIndex(path, table, \"%s\", \"%s\")", - columnIndexVarName, simpleClassName, columnName); - writer.emitStatement("indicesMap.put(\"%s\", this.%s)", columnName, columnIndexVarName); + columnIndexVarName, simpleClassName, columnName) + .emitStatement("indicesMap.put(\"%s\", this.%s)", columnName, columnIndexVarName); } - writer.emitEmptyLine(); - writer.emitStatement("setIndicesMap(indicesMap)"); - writer.endConstructor(); - writer.emitEmptyLine(); + writer.emitEmptyLine() + .emitStatement("setIndicesMap(indicesMap)"); + writer.endConstructor() + .emitEmptyLine(); // copyColumnInfoFrom method - writer.emitAnnotation("Override"); - writer.beginMethod( + writer.emitAnnotation("Override") + .beginMethod( "void", // return type "copyColumnInfoFrom", // method name EnumSet.of(Modifier.PUBLIC, Modifier.FINAL), // modifiers @@ -182,15 +186,15 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { for (VariableElement variableElement : metadata.getFields()) { writer.emitStatement("this.%1$s = otherInfo.%1$s", columnIndexVarName(variableElement)); } - writer.emitEmptyLine(); - writer.emitStatement("setIndicesMap(otherInfo.getIndicesMap())"); + writer.emitEmptyLine() + .emitStatement("setIndicesMap(otherInfo.getIndicesMap())"); } - writer.endMethod(); - writer.emitEmptyLine(); + writer.endMethod() + .emitEmptyLine(); // clone method - writer.emitAnnotation("Override"); - writer.beginMethod( + writer.emitAnnotation("Override") + .beginMethod( columnInfoClassName(), // return type "clone", // method name EnumSet.of(Modifier.PUBLIC, Modifier.FINAL)) // modifiers @@ -203,8 +207,8 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { } private void emitClassFields(JavaWriter writer) throws IOException { - writer.emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE)); - writer.emitField("ProxyState<" + qualifiedClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)); + writer.emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE)) + .emitField("ProxyState<" + qualifiedClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)); for (VariableElement variableElement : metadata.getFields()) { if (Utils.isRealmList(variableElement)) { @@ -213,243 +217,277 @@ private void emitClassFields(JavaWriter writer) throws IOException { } } - writer.emitField("List", "FIELD_NAMES", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)); - writer.beginInitializer(true); - writer.emitStatement("List fieldNames = new ArrayList()"); + for (Backlink backlink : metadata.getBacklinkFields()) { + writer.emitField( + backlink.getTargetFieldType(), + backlink.getTargetField() + BACKLINKS_FIELD_EXTENSION, + EnumSet.of(Modifier.PRIVATE)); + } + + writer.emitField("List", "FIELD_NAMES", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)) + .beginInitializer(true) + .emitStatement("List fieldNames = new ArrayList()"); for (VariableElement field : metadata.getFields()) { writer.emitStatement("fieldNames.add(\"%s\")", field.getSimpleName().toString()); } - writer.emitStatement("FIELD_NAMES = Collections.unmodifiableList(fieldNames)"); - writer.endInitializer(); - writer.emitEmptyLine(); + writer.emitStatement("FIELD_NAMES = Collections.unmodifiableList(fieldNames)") + .endInitializer() + .emitEmptyLine(); } private void emitConstructor(JavaWriter writer) throws IOException { // FooRealmProxy(ColumnInfo) - writer.beginConstructor(EnumSet.noneOf(Modifier.class)); - writer.emitStatement("proxyState.setConstructionFinished()"); - writer.endConstructor(); - writer.emitEmptyLine(); + writer.beginConstructor(EnumSet.noneOf(Modifier.class)) + .emitStatement("proxyState.setConstructionFinished()") + .endConstructor() + .emitEmptyLine(); } - private void emitAccessors(final JavaWriter writer) throws IOException { + private void emitPersistedFieldAccessors(final JavaWriter writer) throws IOException { for (final VariableElement field : metadata.getFields()) { final String fieldName = field.getSimpleName().toString(); final String fieldTypeCanonicalName = field.asType().toString(); if (Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeCanonicalName)) { - /** - * Primitives and boxed types - */ - final String realmType = Constants.JAVA_TO_REALM_TYPES.get(fieldTypeCanonicalName); - - // Getter - writer.emitAnnotation("SuppressWarnings", "\"cast\""); - writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - - // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here. - if (metadata.isNullable(field) && !Utils.isString(field) && !Utils.isByteArray(field)) { - writer.beginControlFlow("if (proxyState.getRow$realm().isNull(%s))", fieldIndexVariableReference(field)); - writer.emitStatement("return null"); - writer.endControlFlow(); - } + emitPrimitiveType(writer, field, fieldName, fieldTypeCanonicalName); + } else if (Utils.isRealmModel(field)) { + emitRealmModel(writer, field, fieldName, fieldTypeCanonicalName); + } else if (Utils.isRealmList(field)) { + emitRealmList(writer, field, fieldName, fieldTypeCanonicalName); + } else { + throw new UnsupportedOperationException(String.format( + "Field \"%s\" of type \"%s\" is not supported.", fieldName, fieldTypeCanonicalName)); + } - // For Boxed types, this should be the corresponding primitive types. Others remain the same. - String castingBackType; - if (Utils.isBoxedType(fieldTypeCanonicalName)) { - Types typeUtils = processingEnvironment.getTypeUtils(); - castingBackType = typeUtils.unboxedType(field.asType()).toString(); - } else { - castingBackType = fieldTypeCanonicalName; + writer.emitEmptyLine(); + } + } + + /** + * Primitives and boxed types + */ + private void emitPrimitiveType( + JavaWriter writer, + final VariableElement field, + final String fieldName, + String fieldTypeCanonicalName) throws IOException + { + final String realmType = Constants.JAVA_TO_REALM_TYPES.get(fieldTypeCanonicalName); + + // Getter + writer.emitAnnotation("SuppressWarnings", "\"cast\"") + .beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) + .emitStatement("proxyState.getRealm$realm().checkIfValid()"); + + // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here. + if (metadata.isNullable(field) && !Utils.isString(field) && !Utils.isByteArray(field)) { + writer.beginControlFlow("if (proxyState.getRow$realm().isNull(%s))", fieldIndexVariableReference(field)) + .emitStatement("return null") + .endControlFlow(); + } + + // For Boxed types, this should be the corresponding primitive types. Others remain the same. + String castingBackType; + if (Utils.isBoxedType(fieldTypeCanonicalName)) { + Types typeUtils = processingEnvironment.getTypeUtils(); + castingBackType = typeUtils.unboxedType(field.asType()).toString(); + } else { + castingBackType = fieldTypeCanonicalName; + } + writer.emitStatement( + "return (%s) proxyState.getRow$realm().get%s(%s)", + castingBackType, realmType, fieldIndexVariableReference(field)); + writer.endMethod() + .emitEmptyLine(); + + // Setter + writer.beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); + emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { + @Override + public void emit(JavaWriter writer) throws IOException { + // set value as default value + writer.emitStatement("final Row row = proxyState.getRow$realm()"); + + if (metadata.isNullable(field)) { + writer.beginControlFlow("if (value == null)") + .emitStatement("row.getTable().setNull(%s, row.getIndex(), true)", + fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow(); + } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { + writer.beginControlFlow("if (value == null)") + .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + .endControlFlow(); } writer.emitStatement( - "return (%s) proxyState.getRow$realm().get%s(%s)", - castingBackType, realmType, fieldIndexVariableReference(field)); - writer.endMethod(); - writer.emitEmptyLine(); - - // Setter - writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { - @Override - public void emit(JavaWriter writer) throws IOException { - // set value as default value - writer.emitStatement("final Row row = proxyState.getRow$realm()"); - - if (metadata.isNullable(field)) { - writer.beginControlFlow("if (value == null)") - .emitStatement("row.getTable().setNull(%s, row.getIndex(), true)", - fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); - } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { - writer.beginControlFlow("if (value == null)") - .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) - .endControlFlow(); - } - writer.emitStatement( - "row.getTable().set%s(%s, row.getIndex(), value, true)", - realmType, fieldIndexVariableReference(field)); - writer.emitStatement("return"); - } - }); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - // Although setting null value for String and bytes[] can be handled by the JNI code, we still generate the same code here. - // Compared with getter, null value won't trigger more native calls in setter which is relatively cheaper. - if (metadata.isPrimaryKey(field)) { - // Primary key is not allowed to be changed after object created. - writer.emitStatement(Constants.STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED, fieldName); - } else { - if (metadata.isNullable(field)) { - writer.beginControlFlow("if (value == null)") - .emitStatement("proxyState.getRow$realm().setNull(%s)", fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); - } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { - // Same reason, throw IAE earlier. - writer - .beginControlFlow("if (value == null)") - .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) - .endControlFlow(); - } - writer.emitStatement( - "proxyState.getRow$realm().set%s(%s, value)", - realmType, fieldIndexVariableReference(field)); - } - writer.endMethod(); - } else if (Utils.isRealmModel(field)) { - /** - * Links - */ - - // Getter - writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - writer.beginControlFlow("if (proxyState.getRow$realm().isNullLink(%s))", fieldIndexVariableReference(field)); - writer.emitStatement("return null"); - writer.endControlFlow(); - writer.emitStatement("return proxyState.getRealm$realm().get(%s.class, proxyState.getRow$realm().getLink(%s), false, Collections.emptyList())", - fieldTypeCanonicalName, fieldIndexVariableReference(field)); - writer.endMethod(); - writer.emitEmptyLine(); - - // Setter - writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { - @Override - public void emit(JavaWriter writer) throws IOException { - // check excludeFields - writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", - field.getSimpleName().toString()) - .emitStatement("return") - .endControlFlow(); - writer.beginControlFlow("if (value != null && !RealmObject.isManaged(value))") - .emitStatement("value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value)") - .endControlFlow(); + "row.getTable().set%s(%s, row.getIndex(), value, true)", + realmType, fieldIndexVariableReference(field)); + writer.emitStatement("return"); + } + }); + writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); + // Although setting null value for String and bytes[] can be handled by the JNI code, we still generate the same code here. + // Compared with getter, null value won't trigger more native calls in setter which is relatively cheaper. + if (metadata.isPrimaryKey(field)) { + // Primary key is not allowed to be changed after object created. + writer.emitStatement(Constants.STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED, fieldName); + } else { + if (metadata.isNullable(field)) { + writer.beginControlFlow("if (value == null)") + .emitStatement("proxyState.getRow$realm().setNull(%s)", fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow(); + } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { + // Same reason, throw IAE earlier. + writer + .beginControlFlow("if (value == null)") + .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + .endControlFlow(); + } + writer.emitStatement( + "proxyState.getRow$realm().set%s(%s, value)", + realmType, fieldIndexVariableReference(field)); + } + writer.endMethod(); + } - // set value as default value - writer.emitStatement("final Row row = proxyState.getRow$realm()"); - writer.beginControlFlow("if (value == null)") - .emitSingleLineComment("Table#nullifyLink() does not support default value. Just using Row.") - .emitStatement("row.nullifyLink(%s)", fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); - writer.beginControlFlow("if (!RealmObject.isValid(value))") - .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") - .endControlFlow(); - writer.beginControlFlow("if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") - .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") - .endControlFlow(); - writer.emitStatement("row.getTable().setLink(%s, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true)", - fieldIndexVariableReference(field)); - writer.emitStatement("return"); - } - }); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - writer.beginControlFlow("if (value == null)"); - writer.emitStatement("proxyState.getRow$realm().nullifyLink(%s)", fieldIndexVariableReference(field)); - writer.emitStatement("return"); - writer.endControlFlow(); - writer.beginControlFlow("if (!(RealmObject.isManaged(value) && RealmObject.isValid(value)))"); - writer.emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")"); - writer.endControlFlow(); - writer.beginControlFlow("if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())"); - writer.emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")"); - writer.endControlFlow(); - writer.emitStatement("proxyState.getRow$realm().setLink(%s, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex())", fieldIndexVariableReference(field)); - writer.endMethod(); - } else if (Utils.isRealmList(field)) { - /** - * LinkLists - */ - String genericType = Utils.getGenericTypeQualifiedName(field); - - // Getter - writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - writer.emitSingleLineComment("use the cached value if available"); - writer.beginControlFlow("if (" + fieldName + "RealmList != null)"); - writer.emitStatement("return " + fieldName + "RealmList"); - writer.nextControlFlow("else"); - writer.emitStatement("LinkView linkView = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)); - writer.emitStatement(fieldName + "RealmList = new RealmList<%s>(%s.class, linkView, proxyState.getRealm$realm())", - genericType, genericType); - writer.emitStatement("return " + fieldName + "RealmList"); - writer.endControlFlow(); + /** + * Links + */ + private void emitRealmModel( + JavaWriter writer, + final VariableElement field, + String fieldName, + String fieldTypeCanonicalName) throws IOException + { + + // Getter + writer.beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) + .emitStatement("proxyState.getRealm$realm().checkIfValid()") + .beginControlFlow("if (proxyState.getRow$realm().isNullLink(%s))", fieldIndexVariableReference(field)) + .emitStatement("return null") + .endControlFlow() + .emitStatement("return proxyState.getRealm$realm().get(%s.class, proxyState.getRow$realm().getLink(%s), false, Collections.emptyList())", + fieldTypeCanonicalName, fieldIndexVariableReference(field)) + .endMethod() + .emitEmptyLine(); + + // Setter + writer.beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); + emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { + @Override + public void emit(JavaWriter writer) throws IOException { + // check excludeFields + writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", + field.getSimpleName().toString()) + .emitStatement("return") + .endControlFlow(); + writer.beginControlFlow("if (value != null && !RealmObject.isManaged(value))") + .emitStatement("value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value)") + .endControlFlow(); - writer.endMethod(); - writer.emitEmptyLine(); - - // Setter - writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { - @Override - public void emit(JavaWriter writer) throws IOException { - // check excludeFields - writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", - field.getSimpleName().toString()) - .emitStatement("return") - .endControlFlow(); - final String modelFqcn = Utils.getGenericTypeQualifiedName(field); - writer.beginControlFlow("if (value != null && !value.isManaged())") - .emitStatement("final Realm realm = (Realm) proxyState.getRealm$realm()") - .emitStatement("final RealmList<%1$s> original = value", modelFqcn) - .emitStatement("value = new RealmList<%1$s>()", modelFqcn) - .beginControlFlow("for (%1$s item : original)", modelFqcn) - .beginControlFlow("if (item == null || RealmObject.isManaged(item))") - .emitStatement("value.add(item)") - .nextControlFlow("else") - .emitStatement("value.add(realm.copyToRealm(item))") - .endControlFlow() - .endControlFlow() - .endControlFlow(); + // set value as default value + writer.emitStatement("final Row row = proxyState.getRow$realm()"); + writer.beginControlFlow("if (value == null)") + .emitSingleLineComment("Table#nullifyLink() does not support default value. Just using Row.") + .emitStatement("row.nullifyLink(%s)", fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow(); + writer.beginControlFlow("if (!RealmObject.isValid(value))") + .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") + .endControlFlow(); + writer.beginControlFlow("if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") + .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") + .endControlFlow(); + writer.emitStatement("row.getTable().setLink(%s, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true)", + fieldIndexVariableReference(field)); + writer.emitStatement("return"); + } + }); + writer.emitStatement("proxyState.getRealm$realm().checkIfValid()") + .beginControlFlow("if (value == null)") + .emitStatement("proxyState.getRow$realm().nullifyLink(%s)", fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow() + .beginControlFlow("if (!(RealmObject.isManaged(value) && RealmObject.isValid(value)))") + .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") + .endControlFlow() + .beginControlFlow("if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") + .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") + .endControlFlow() + .emitStatement("proxyState.getRow$realm().setLink(%s, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex())", fieldIndexVariableReference(field)) + .endMethod(); + } - // LinkView currently does not support default value feature. Just fallback to normal code. - } - }); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - writer.emitStatement("LinkView links = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)); - writer.emitStatement("links.clear()"); - writer.beginControlFlow("if (value == null)"); - writer.emitStatement("return"); - writer.endControlFlow(); - writer.beginControlFlow("for (RealmModel linkedObject : (RealmList) value)"); - writer.beginControlFlow("if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject)))"); - writer.emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must be a valid managed object.\")"); - writer.endControlFlow(); - writer.beginControlFlow("if (((RealmObjectProxy)linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())"); - writer.emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must belong to the same Realm.\")"); - writer.endControlFlow(); - writer.emitStatement("links.add(((RealmObjectProxy)linkedObject).realmGet$proxyState().getRow$realm().getIndex())"); - writer.endControlFlow(); - writer.endMethod(); - } else { - throw new UnsupportedOperationException( - String.format("Type '%s' of field '%s' is not supported", fieldTypeCanonicalName, fieldName)); + /** + * LinkLists + */ + private void emitRealmList( + JavaWriter writer, + final VariableElement field, + String fieldName, + String fieldTypeCanonicalName) throws IOException + { + String genericType = Utils.getGenericTypeQualifiedName(field); + + // Getter + writer.beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) + .emitStatement("proxyState.getRealm$realm().checkIfValid()") + .emitSingleLineComment("use the cached value if available") + .beginControlFlow("if (" + fieldName + "RealmList != null)") + .emitStatement("return " + fieldName + "RealmList") + .nextControlFlow("else") + .emitStatement("LinkView linkView = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)) + .emitStatement(fieldName + "RealmList = new RealmList<%s>(%s.class, linkView, proxyState.getRealm$realm())", + genericType, genericType) + .emitStatement("return " + fieldName + "RealmList") + .endControlFlow() + .endMethod() + .emitEmptyLine(); + + // Setter + writer.beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); + emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { + @Override + public void emit(JavaWriter writer) throws IOException { + // check excludeFields + writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", + field.getSimpleName().toString()) + .emitStatement("return") + .endControlFlow(); + final String modelFqcn = Utils.getGenericTypeQualifiedName(field); + writer.beginControlFlow("if (value != null && !value.isManaged())") + .emitStatement("final Realm realm = (Realm) proxyState.getRealm$realm()") + .emitStatement("final RealmList<%1$s> original = value", modelFqcn) + .emitStatement("value = new RealmList<%1$s>()", modelFqcn) + .beginControlFlow("for (%1$s item : original)", modelFqcn) + .beginControlFlow("if (item == null || RealmObject.isManaged(item))") + .emitStatement("value.add(item)") + .nextControlFlow("else") + .emitStatement("value.add(realm.copyToRealm(item))") + .endControlFlow() + .endControlFlow() + .endControlFlow(); + + // LinkView currently does not support default value feature. Just fallback to normal code. } - writer.emitEmptyLine(); - } + }); + writer.emitStatement("proxyState.getRealm$realm().checkIfValid()") + .emitStatement("LinkView links = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)) + .emitStatement("links.clear()") + .beginControlFlow("if (value == null)") + .emitStatement("return") + .endControlFlow() + .beginControlFlow("for (RealmModel linkedObject : (RealmList) value)") + .beginControlFlow("if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject)))") + .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must be a valid managed object.\")") + .endControlFlow() + .beginControlFlow("if (((RealmObjectProxy)linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") + .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must belong to the same Realm.\")") + .endControlFlow() + .emitStatement("links.add(((RealmObjectProxy)linkedObject).realmGet$proxyState().getRow$realm().getIndex())") + .endControlFlow() + .endMethod(); } private interface CodeEmitter { @@ -460,47 +498,65 @@ private void emitCodeForUnderConstruction(JavaWriter writer, boolean isPrimaryKe CodeEmitter defaultValueCodeEmitter) throws IOException { writer.beginControlFlow("if (proxyState.isUnderConstruction())"); if (isPrimaryKey) { - writer.emitSingleLineComment("default value of the primary key is always ignored."); - writer.emitStatement("return"); + writer.emitSingleLineComment("default value of the primary key is always ignored.") + .emitStatement("return"); } else { writer.beginControlFlow("if (!proxyState.getAcceptDefaultValue$realm())") .emitStatement("return") .endControlFlow(); defaultValueCodeEmitter.emit(writer); } - writer.endControlFlow(); - writer.emitEmptyLine(); + writer.endControlFlow() + .emitEmptyLine(); } private void emitInjectContextMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); writer.beginMethod( "void", // Return type "realm$injectObjectContext", // Method name EnumSet.of(Modifier.PUBLIC) // Modifiers ); // Argument type & argument name - writer.beginControlFlow("if (this.proxyState != null)"); - writer.emitStatement("return"); - writer.endControlFlow(); - writer.emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()"); - writer.emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()); - writer.emitStatement("this.proxyState = new ProxyState<%1$s>(this)", qualifiedClassName); - writer.emitStatement("proxyState.setRealm$realm(context.getRealm())"); - writer.emitStatement("proxyState.setRow$realm(context.getRow())"); - writer.emitStatement("proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue())"); - writer.emitStatement("proxyState.setExcludeFields$realm(context.getExcludeFields())"); + writer.beginControlFlow("if (this.proxyState != null)") + .emitStatement("return") + .endControlFlow() + .emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()") + .emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()) + .emitStatement("this.proxyState = new ProxyState<%1$s>(this)", qualifiedClassName) + .emitStatement("proxyState.setRealm$realm(context.getRealm())") + .emitStatement("proxyState.setRow$realm(context.getRow())") + .emitStatement("proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue())") + .emitStatement("proxyState.setExcludeFields$realm(context.getExcludeFields())"); + + writer.endMethod() + .emitEmptyLine(); + } - writer.endMethod(); - writer.emitEmptyLine(); + private void emitBacklinkFieldAccessors(JavaWriter writer) throws IOException { + for (Backlink backlink : metadata.getBacklinkFields()) { + String cacheFieldName = backlink.getTargetField() + BACKLINKS_FIELD_EXTENSION; + String realmResultsType = "RealmResults<" + backlink.getSourceClass() + ">"; + + // Getter, no setter + writer.beginMethod(realmResultsType, metadata.getInternalGetter(backlink.getTargetField()), EnumSet.of(Modifier.PUBLIC)) + .emitStatement("BaseRealm realm = proxyState.getRealm$realm()") + .emitStatement("realm.checkIfValid()") + .beginControlFlow("if (" + cacheFieldName + " == null)") + .emitStatement(cacheFieldName + " = RealmResults.createBacklinkResults(realm, proxyState.getRow$realm(), %s.class, \"%s\")", + backlink.getSourceClass(), backlink.getSourceField()) + .endControlFlow() + .emitStatement("return " + cacheFieldName) + .endMethod() + .emitEmptyLine(); + } } private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)); - writer.emitStatement("return proxyState"); - writer.endMethod(); - writer.emitEmptyLine(); + writer.emitAnnotation("Override") + .beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)) + .emitStatement("return proxyState") + .endMethod() + .emitEmptyLine(); } private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOException { @@ -530,25 +586,25 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep indexedFlag, nullableFlag); } else if (Utils.isRealmModel(field)) { - writer.beginControlFlow("if (!realmSchema.contains(\"" + fieldTypeSimpleName + "\"))"); - writer.emitStatement("%s%s.createRealmObjectSchema(realmSchema)", fieldTypeSimpleName, Constants.PROXY_SUFFIX); - writer.endControlFlow(); - writer.emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\")))", + writer.beginControlFlow("if (!realmSchema.contains(\"" + fieldTypeSimpleName + "\"))") + .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", fieldTypeSimpleName, Constants.PROXY_SUFFIX) + .endControlFlow() + .emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\")))", fieldName, fieldTypeSimpleName); } else if (Utils.isRealmList(field)) { String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); - writer.beginControlFlow("if (!realmSchema.contains(\"" + genericTypeSimpleName +"\"))"); - writer.emitStatement("%s%s.createRealmObjectSchema(realmSchema)", genericTypeSimpleName, Constants.PROXY_SUFFIX); - writer.endControlFlow(); - writer.emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\")))", + writer.beginControlFlow("if (!realmSchema.contains(\"" + genericTypeSimpleName +"\"))") + .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", genericTypeSimpleName, Constants.PROXY_SUFFIX) + .endControlFlow() + .emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\")))", fieldName, genericTypeSimpleName); } } writer.emitStatement("return realmObjectSchema"); writer.endControlFlow(); writer.emitStatement("return realmSchema.get(\"" + this.simpleClassName + "\")"); - writer.endMethod(); - writer.emitEmptyLine(); + writer.endMethod() + .emitEmptyLine(); } private void emitInitTableMethod(JavaWriter writer) throws IOException { @@ -578,17 +634,17 @@ private void emitInitTableMethod(JavaWriter writer) throws IOException { Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeCanonicalName), fieldName, nullableFlag); } else if (Utils.isRealmModel(field)) { - writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName); - writer.emitStatement("%s%s.initTable(sharedRealm)", fieldTypeSimpleName, Constants.PROXY_SUFFIX); - writer.endControlFlow(); - writer.emitStatement("table.addColumnLink(RealmFieldType.OBJECT, \"%s\", sharedRealm.getTable(\"%s%s\"))", + writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName) + .emitStatement("%s%s.initTable(sharedRealm)", fieldTypeSimpleName, Constants.PROXY_SUFFIX) + .endControlFlow() + .emitStatement("table.addColumnLink(RealmFieldType.OBJECT, \"%s\", sharedRealm.getTable(\"%s%s\"))", fieldName, Constants.TABLE_PREFIX, fieldTypeSimpleName); } else if (Utils.isRealmList(field)) { String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); - writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName); - writer.emitStatement("%s.initTable(sharedRealm)", Utils.getProxyClassName(genericTypeSimpleName)); - writer.endControlFlow(); - writer.emitStatement("table.addColumnLink(RealmFieldType.LIST, \"%s\", sharedRealm.getTable(\"%s%s\"))", + writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName) + .emitStatement("%s.initTable(sharedRealm)", Utils.getProxyClassName(genericTypeSimpleName)) + .endControlFlow() + .emitStatement("table.addColumnLink(RealmFieldType.LIST, \"%s\", sharedRealm.getTable(\"%s%s\"))", fieldName, Constants.TABLE_PREFIX, genericTypeSimpleName); } } @@ -606,212 +662,292 @@ private void emitInitTableMethod(JavaWriter writer) throws IOException { } writer.emitStatement("return table"); + writer.endControlFlow(); + writer.emitStatement("return sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); - writer.endMethod(); - writer.emitEmptyLine(); + writer.endMethod() + .emitEmptyLine(); } private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.beginMethod( - columnInfoClassName(), // Return type - "validateTable", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "SharedRealm", "sharedRealm", // Argument type & argument name - "boolean", "allowExtraColumns"); + columnInfoClassName(), // Return type + "validateTable", // Method name + EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers + "SharedRealm", "sharedRealm", // Argument type & argument name + "boolean", "allowExtraColumns"); + + writer.beginControlFlow( + "if (!sharedRealm.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); + emitMigrationNeededException(writer, "\"The '%s' class is missing from the schema for this Realm.\")", + metadata.getSimpleClassName()); + writer.endControlFlow(); - writer.beginControlFlow("if (sharedRealm.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); - writer.emitStatement("Table table = sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); + writer.emitStatement( + "Table table = sharedRealm.getTable(\"%s%s\")", + Constants.TABLE_PREFIX, + this.simpleClassName); // verify number of columns writer.emitStatement("final long columnCount = table.getColumnCount()"); writer.beginControlFlow("if (columnCount != %d)", metadata.getFields().size()); - writer.beginControlFlow("if (columnCount < %d)", metadata.getFields().size()); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Field count is less than expected - expected %d but was \" + columnCount)", - metadata.getFields().size()); - writer.endControlFlow(); - writer.beginControlFlow("if (allowExtraColumns)"); - writer.emitStatement("RealmLog.debug(\"Field count is more than expected - expected %d but was %%1$d\", columnCount)", - metadata.getFields().size()); - writer.nextControlFlow("else"); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Field count is more than expected - expected %d but was \" + columnCount)", - metadata.getFields().size()); - writer.endControlFlow(); + writer.beginControlFlow("if (columnCount < %d)", metadata.getFields().size()); + emitMigrationNeededException(writer, "\"Field count is less than expected - expected %d but was \" + columnCount)", + metadata.getFields().size()); + writer.endControlFlow(); + writer.beginControlFlow("if (allowExtraColumns)"); + writer.emitStatement( + "RealmLog.debug(\"Field count is more than expected - expected %d but was %%1$d\", columnCount)", + metadata.getFields().size()); + writer.nextControlFlow("else"); + emitMigrationNeededException(writer, "\"Field count is more than expected - expected %d but was \" + columnCount)", + metadata.getFields().size()); + writer.endControlFlow(); writer.endControlFlow(); // create type dictionary for lookup writer.emitStatement("Map columnTypes = new HashMap()"); - writer.beginControlFlow("for (long i = 0; i < columnCount; i++)"); - writer.emitStatement("columnTypes.put(table.getColumnName(i), table.getColumnType(i))"); - writer.endControlFlow(); - writer.emitEmptyLine(); + writer.beginControlFlow("for (long i = 0; i < columnCount; i++)") + .emitStatement("columnTypes.put(table.getColumnName(i), table.getColumnType(i))") + .endControlFlow() + .emitEmptyLine(); // create an instance of ColumnInfo - writer.emitStatement("final %1$s columnInfo = new %1$s(sharedRealm.getPath(), table)", columnInfoClassName()); - writer.emitEmptyLine(); + writer.emitStatement("final %1$s columnInfo = new %1$s(sharedRealm.getPath(), table)", columnInfoClassName()) + .emitEmptyLine(); // verify primary key definition was not altered if (metadata.hasPrimaryKey()) { // the current model defines a PK, make sure it's defined in the Realm schema String fieldName = metadata.getPrimaryKey().getSimpleName().toString(); - writer.beginControlFlow("if (!table.hasPrimaryKey())") - .emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Primary key not defined for field '%s' in existing Realm file. @PrimaryKey was added.\")", metadata.getPrimaryKey().getSimpleName().toString()) - .nextControlFlow("else") - .beginControlFlow("if (table.getPrimaryKey() != columnInfo.%sIndex)", fieldName) - .emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Primary Key annotation definition was changed, from field \" + table.getColumnName(table.getPrimaryKey()) + \" to field %s\")" ,metadata.getPrimaryKey().getSimpleName().toString()) - .endControlFlow() - .endControlFlow(); - } else { + writer.beginControlFlow("if (!table.hasPrimaryKey())"); + emitMigrationNeededException(writer, "\"Primary key not defined for field '%s' in existing Realm file. @PrimaryKey was added.\")", + metadata.getPrimaryKey().getSimpleName().toString()); + writer.nextControlFlow("else") + .beginControlFlow("if (table.getPrimaryKey() != columnInfo.%sIndex)", fieldName); + emitMigrationNeededException(writer, "\"Primary Key annotation definition was changed, from field \" + table.getColumnName(table.getPrimaryKey()) + \" to field %s\")", + metadata.getPrimaryKey().getSimpleName().toString()); + writer.endControlFlow() + .endControlFlow(); + } + else { // the current model doesn't define a PK, make sure it's not defined in the Realm schema - writer.beginControlFlow("if (table.hasPrimaryKey())") - .emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Primary Key defined for field \" + table.getColumnName(table.getPrimaryKey()) + \" was removed.\")") - .endControlFlow(); + writer.beginControlFlow("if (table.hasPrimaryKey())"); + emitMigrationNeededException(writer, "\"Primary Key defined for field \" + table.getColumnName(table.getPrimaryKey()) + \" was removed.\")"); + writer.endControlFlow(); } writer.emitEmptyLine(); // For each field verify there is a corresponding - long fieldIndex = 0; + long fieldIndex = -1; for (VariableElement field : metadata.getFields()) { + fieldIndex++; String fieldName = field.getSimpleName().toString(); String fieldTypeQualifiedName = Utils.getFieldTypeQualifiedName(field); - String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); - if (Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeQualifiedName)) { - // make sure types align - writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing field '%s' in existing Realm file. " + - "Either remove field or migrate using io.realm.internal.Table.addColumn()." + - "\")", fieldName); - writer.endControlFlow(); - writer.beginControlFlow("if (columnTypes.get(\"%s\") != %s)", - fieldName, Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeQualifiedName)); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid type '%s' for field '%s' in existing Realm file.\")", - fieldTypeSimpleName, fieldName); - writer.endControlFlow(); + emitValidateRealmType(writer, field, fieldName, fieldTypeQualifiedName); + } + else if (Utils.isRealmModel(field)) { // Links + emitValidateRealmModelType(writer, field, fieldIndex, fieldName); + } + else if (Utils.isRealmList(field)) { // Link Lists + emitValidateRealmListType(writer, field, fieldIndex, fieldName); + } + } - // make sure that nullability matches - if (metadata.isNullable(field)) { - writer.beginControlFlow("if (!table.isColumnNullable(%s))", fieldIndexVariableReference(field)); - // Check if the existing PrimaryKey does support null value for String, Byte, Short, Integer, & Long - if (metadata.isPrimaryKey(field)) { - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - "\"@PrimaryKey field '%s' does not support null values in the existing Realm file. " + - "Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required.\")", - fieldName); - // nullability check for boxed types - } else if (Utils.isBoxedType(fieldTypeQualifiedName)) { - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - "\"Field '%s' does not support null values in the existing Realm file. " + - "Either set @Required, use the primitive type for field '%s' " + - "or migrate using RealmObjectSchema.setNullable().\")", - fieldName, fieldName); - } else { - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - " \"Field '%s' is required. Either set @Required to field '%s' " + - "or migrate using RealmObjectSchema.setNullable().\")", - fieldName, fieldName); - } - writer.endControlFlow(); - } else { - // check before migrating a nullable field containing null value to not-nullable PrimaryKey field for Realm version 0.89+ - if (metadata.isPrimaryKey(field)) { - writer - .beginControlFlow("if (table.isColumnNullable(%s) && table.findFirstNull(%s) != Table.NO_MATCH)", - fieldIndexVariableReference(field), fieldIndexVariableReference(field)) - .emitStatement("throw new IllegalStateException(\"Cannot migrate an object with null value in field '%s'." + - " Either maintain the same type for primary key field '%s', or remove the object with null value before migration.\")", - fieldName, fieldName) - .endControlFlow(); - } else { - writer.beginControlFlow("if (table.isColumnNullable(%s))", fieldIndexVariableReference(field)); - if (Utils.isPrimitiveType(fieldTypeQualifiedName)) { - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - " \"Field '%s' does support null values in the existing Realm file. " + - "Use corresponding boxed type for field '%s' or migrate using RealmObjectSchema.setNullable().\")", - fieldName, fieldName); - } else { - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - " \"Field '%s' does support null values in the existing Realm file. " + - "Remove @Required or @PrimaryKey from field '%s' or migrate using RealmObjectSchema.setNullable().\")", - fieldName, fieldName); - } - writer.endControlFlow(); - } - } + // verify the backlinks + Set backlinks = metadata.getBacklinkFields(); + if (backlinks.size() > 0) { + writer.emitEmptyLine() + .emitStatement("long backlinkFieldIndex") + .emitStatement("Table backlinkSourceTable") + .emitStatement("Table backlinkTargetTable") + .emitStatement("RealmFieldType backlinkFieldType"); + for (Backlink backlink : metadata.getBacklinkFields()) { + emitValidateBacklink(writer, backlink); + } + } - // Validate @Index - if (metadata.getIndexedFields().contains(field)) { - writer.beginControlFlow("if (!table.hasSearchIndex(table.getColumnIndex(\"%s\")))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Index not defined for field '%s' in existing Realm file. " + - "Either set @Index or migrate using io.realm.internal.Table.removeSearchIndex().\")", fieldName); - writer.endControlFlow(); - } + writer.emitEmptyLine(); + writer.emitStatement("return %s", "columnInfo"); - } else if (Utils.isRealmModel(field)) { // Links - writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing field '%s' in existing Realm file. " + - "Either remove field or migrate using io.realm.internal.Table.addColumn().\")", fieldName); - writer.endControlFlow(); - writer.beginControlFlow("if (columnTypes.get(\"%s\") != RealmFieldType.OBJECT)", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid type '%s' for field '%s'\")", - fieldTypeSimpleName, fieldName); - writer.endControlFlow(); - writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing class '%s%s' for field '%s'\")", - Constants.TABLE_PREFIX, fieldTypeSimpleName, fieldName); - writer.endControlFlow(); + writer.endMethod(); + writer.emitEmptyLine(); + } - writer.emitStatement("Table table_%d = sharedRealm.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, fieldTypeSimpleName); - writer.beginControlFlow("if (!table.getLinkTarget(%s).hasSameSchema(table_%d))", - fieldIndexVariableReference(field), fieldIndex); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid RealmObject for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", - fieldName, fieldIndexVariableReference(field), fieldIndex); - writer.endControlFlow(); - } else if (Utils.isRealmList(field)) { // Link Lists - String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); - writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing field '%s'\")", fieldName); - writer.endControlFlow(); - writer.beginControlFlow("if (columnTypes.get(\"%s\") != RealmFieldType.LIST)", fieldName); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid type '%s' for field '%s'\")", - genericTypeSimpleName, fieldName); - writer.endControlFlow(); - writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Missing class '%s%s' for field '%s'\")", - Constants.TABLE_PREFIX, genericTypeSimpleName, fieldName); - writer.endControlFlow(); + private void emitValidateRealmType(JavaWriter writer, VariableElement field, String fieldName, String fieldTypeQualifiedName) + throws IOException { + + // make sure types align + writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); + emitMigrationNeededException(writer, "\"Missing field '%s' in existing Realm file. " + + "Either remove field or migrate using io.realm.internal.Table.addColumn()." + + "\")", fieldName); + writer.endControlFlow(); + writer.beginControlFlow("if (columnTypes.get(\"%s\") != %s)", + fieldName, Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeQualifiedName)); + emitMigrationNeededException(writer, "\"Invalid type '%s' for field '%s' in existing Realm file.\")", + Utils.getFieldTypeSimpleName(field), fieldName); + writer.endControlFlow(); - writer.emitStatement("Table table_%d = sharedRealm.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, genericTypeSimpleName); - writer.beginControlFlow("if (!table.getLinkTarget(%s).hasSameSchema(table_%d))", - fieldIndexVariableReference(field), fieldIndex); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"Invalid RealmList type for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", - fieldName, fieldIndexVariableReference(field), fieldIndex); + // make sure that nullability matches + if (metadata.isNullable(field)) { + writer.beginControlFlow("if (!table.isColumnNullable(%s))", fieldIndexVariableReference(field)); + // Check if the existing PrimaryKey does support null value for String, Byte, Short, Integer, & Long + if (metadata.isPrimaryKey(field)) { + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + + "\"@PrimaryKey field '%s' does not support null values in the existing Realm file. " + + "Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required.\")", + fieldName); + // nullability check for boxed types + } else if (Utils.isBoxedType(fieldTypeQualifiedName)) { + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + + "\"Field '%s' does not support null values in the existing Realm file. " + + "Either set @Required, use the primitive type for field '%s' " + + "or migrate using RealmObjectSchema.setNullable().\")", + fieldName, fieldName); + } else { + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + + " \"Field '%s' is required. Either set @Required to field '%s' " + + "or migrate using RealmObjectSchema.setNullable().\")", + fieldName, fieldName); + } + writer.endControlFlow(); + } else { + // check before migrating a nullable field containing null value to not-nullable PrimaryKey field for Realm version 0.89+ + if (metadata.isPrimaryKey(field)) { + writer + .beginControlFlow("if (table.isColumnNullable(%s) && table.findFirstNull(%s) != Table.NO_MATCH)", + fieldIndexVariableReference(field), fieldIndexVariableReference(field)) + .emitStatement("throw new IllegalStateException(\"Cannot migrate an object with null value in field '%s'." + + " Either maintain the same type for primary key field '%s', or remove the object with null value before migration.\")", + fieldName, fieldName) + .endControlFlow(); + } else { + writer.beginControlFlow("if (table.isColumnNullable(%s))", fieldIndexVariableReference(field)); + if (Utils.isPrimitiveType(fieldTypeQualifiedName)) { + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + + " \"Field '%s' does support null values in the existing Realm file. " + + "Use corresponding boxed type for field '%s' or migrate using RealmObjectSchema.setNullable().\")", + fieldName, fieldName); + } else { + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + + " \"Field '%s' does support null values in the existing Realm file. " + + "Remove @Required or @PrimaryKey from field '%s' or migrate using RealmObjectSchema.setNullable().\")", + fieldName, fieldName); + } writer.endControlFlow(); } - fieldIndex++; } - writer.emitStatement("return %s", "columnInfo"); + // Validate @Index + if (metadata.getIndexedFields().contains(field)) { + writer.beginControlFlow("if (!table.hasSearchIndex(table.getColumnIndex(\"%s\")))", fieldName); + emitMigrationNeededException(writer, "\"Index not defined for field '%s' in existing Realm file. " + + "Either set @Index or migrate using io.realm.internal.Table.removeSearchIndex().\")", fieldName); + writer.endControlFlow(); + } + } - writer.nextControlFlow("else"); - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), \"The '%s' class is missing from the schema for this Realm.\")", metadata.getSimpleClassName()); + private void emitValidateRealmModelType(JavaWriter writer, VariableElement field, long fieldIndex, String fieldName) + throws IOException { + String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); + + writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); + emitMigrationNeededException(writer, "\"Missing field '%s' in existing Realm file. " + + "Either remove field or migrate using io.realm.internal.Table.addColumn().\")", fieldName); + writer.endControlFlow(); + writer.beginControlFlow("if (columnTypes.get(\"%s\") != RealmFieldType.OBJECT)", fieldName); + emitMigrationNeededException(writer, "\"Invalid type '%s' for field '%s'\")", + fieldTypeSimpleName, fieldName); + writer.endControlFlow(); + writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName); + emitMigrationNeededException(writer, "\"Missing class '%s%s' for field '%s'\")", + Constants.TABLE_PREFIX, fieldTypeSimpleName, fieldName); + writer.endControlFlow(); + + writer.emitStatement("Table table_%d = sharedRealm.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, fieldTypeSimpleName); + writer.beginControlFlow("if (!table.getLinkTarget(%s).hasSameSchema(table_%d))", + fieldIndexVariableReference(field), fieldIndex); + emitMigrationNeededException(writer, "\"Invalid RealmObject for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", + fieldName, fieldIndexVariableReference(field), fieldIndex); + writer.endControlFlow(); + } + + private void emitValidateRealmListType(JavaWriter writer, VariableElement field, long fieldIndex, String fieldName) + throws IOException + { + String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); + writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); + emitMigrationNeededException(writer, "\"Missing field '%s'\")", fieldName); + writer.endControlFlow(); + writer.beginControlFlow("if (columnTypes.get(\"%s\") != RealmFieldType.LIST)", fieldName); + emitMigrationNeededException(writer, "\"Invalid type '%s' for field '%s'\")", + genericTypeSimpleName, fieldName); + writer.endControlFlow(); + writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName); + emitMigrationNeededException(writer, "\"Missing class '%s%s' for field '%s'\")", + Constants.TABLE_PREFIX, genericTypeSimpleName, fieldName); + writer.endControlFlow(); + + writer.emitStatement("Table table_%d = sharedRealm.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, genericTypeSimpleName); + writer.beginControlFlow("if (!table.getLinkTarget(%s).hasSameSchema(table_%d))", + fieldIndexVariableReference(field), fieldIndex); + emitMigrationNeededException(writer, "\"Invalid RealmList type for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", + fieldName, fieldIndexVariableReference(field), fieldIndex); + writer.endControlFlow(); + } + + private void emitValidateBacklink(JavaWriter writer, Backlink backlink) throws IOException { + String targetField = backlink.getTargetField(); + String targetClass = backlink.getTargetClass(); + + // Preceding code has already verified that the backlink field is not in the table. + // If it were, either the column count would be wrong, or some field would be missing. + + // verify that the source class exists + String sourceClass = backlink.getSimpleSourceClass(); + String fullyQualifiedSourceClass = backlink.getSourceClass(); + writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, sourceClass); + emitMigrationNeededException(writer, "\"Cannot find source class '%s' for @LinkingObjects field '%s.%s'\")", + fullyQualifiedSourceClass, targetClass, targetField); + writer.endControlFlow(); + + // verify that the source class contains the source field + String sourceField = backlink.getSourceField(); + writer.emitStatement("backlinkSourceTable = sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, sourceClass); + writer.emitStatement("backlinkFieldIndex = backlinkSourceTable.getColumnIndex(\"%s\")", sourceField); + writer.beginControlFlow("if (backlinkFieldIndex == Table.NO_MATCH)"); + emitMigrationNeededException(writer, "\"Cannot find source field '%s.%s' for @LinkingObjects field '%s.%s'\")", + fullyQualifiedSourceClass, sourceField, targetClass, targetField); + writer.endControlFlow(); + + // verify that the source field type is target class + writer.emitStatement("backlinkFieldType = backlinkSourceTable.getColumnType(backlinkFieldIndex)"); + writer.beginControlFlow("if ((backlinkFieldType != RealmFieldType.OBJECT) && (backlinkFieldType != RealmFieldType.LIST))"); + emitMigrationNeededException(writer, "\"Source field '%s.%s' for @LinkingObjects field '%s.%s' is not a RealmObject type\")", + fullyQualifiedSourceClass, sourceField, targetClass, targetField); + writer.endControlFlow(); + writer.emitStatement("backlinkTargetTable = backlinkSourceTable.getLinkTarget(backlinkFieldIndex)"); + writer.beginControlFlow("if (!table.hasSameSchema(backlinkTargetTable))"); + emitMigrationNeededException(writer, "\"Source field '%s.%s' for @LinkingObjects field '%s.%s' has wrong type '\" + backlinkTargetTable.getName() + \"'\")", + fullyQualifiedSourceClass, sourceField, targetClass, targetField); writer.endControlFlow(); - writer.endMethod(); - writer.emitEmptyLine(); } private void emitGetTableNameMethod(JavaWriter writer) throws IOException { - writer.beginMethod("String", "getTableName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)); - writer.emitStatement("return \"%s%s\"", Constants.TABLE_PREFIX, simpleClassName); - writer.endMethod(); - writer.emitEmptyLine(); + writer.beginMethod("String", "getTableName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) + .emitStatement("return \"%s%s\"", Constants.TABLE_PREFIX, simpleClassName) + .endMethod() + .emitEmptyLine(); } private void emitGetFieldNamesMethod(JavaWriter writer) throws IOException { - writer.beginMethod("List", "getFieldNames", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)); - writer.emitStatement("return FIELD_NAMES"); - writer.endMethod(); - writer.emitEmptyLine(); + writer.beginMethod("List", "getFieldNames", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) + .emitStatement("return FIELD_NAMES") + .endMethod() + .emitEmptyLine(); } private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { @@ -907,8 +1043,8 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { } writer.endControlFlow(); - writer.endMethod(); - writer.emitEmptyLine(); + writer.endMethod() + .emitEmptyLine(); } private void setTableValues(JavaWriter writer, String fieldType, String fieldName, String interfaceName, String getter, boolean isUpdate) throws IOException { @@ -1039,7 +1175,7 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String fieldType = field.asType().toString(); - String getter = metadata.getGetter(fieldName); + String getter = metadata.getInternalGetter(fieldName); if (Utils.isRealmModel(field)) { writer @@ -1081,8 +1217,8 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { } writer.emitStatement("return rowIndex"); - writer.endMethod(); - writer.emitEmptyLine(); + writer.endMethod() + .emitEmptyLine(); } private void emitInsertListMethod(JavaWriter writer) throws IOException { @@ -1102,8 +1238,8 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { } writer.emitStatement("%s object = null", qualifiedClassName); - writer.beginControlFlow("while (objects.hasNext())"); - writer.emitStatement("object = (%s) objects.next()", qualifiedClassName); + writer.beginControlFlow("while (objects.hasNext())") + .emitStatement("object = (%s) objects.next()", qualifiedClassName); writer.beginControlFlow("if(!cache.containsKey(object))"); writer.beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))"); @@ -1116,7 +1252,7 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String fieldType = field.asType().toString(); - String getter = metadata.getGetter(fieldName); + String getter = metadata.getInternalGetter(fieldName); if (Utils.isRealmModel(field)) { writer @@ -1190,7 +1326,7 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String fieldType = field.asType().toString(); - String getter = metadata.getGetter(fieldName); + String getter = metadata.getInternalGetter(fieldName); if (Utils.isRealmModel(field)) { writer @@ -1236,8 +1372,8 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { writer.emitStatement("return rowIndex"); - writer.endMethod(); - writer.emitEmptyLine(); + writer.endMethod() + .emitEmptyLine(); } private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException { @@ -1270,7 +1406,7 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String fieldType = field.asType().toString(); - String getter = metadata.getGetter(fieldName); + String getter = metadata.getInternalGetter(fieldName); if (Utils.isRealmModel(field)) { writer @@ -1402,8 +1538,8 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String fieldType = field.asType().toString(); - String setter = metadata.getSetter(fieldName); - String getter = metadata.getGetter(fieldName); + String setter = metadata.getInternalSetter(fieldName); + String getter = metadata.getInternalGetter(fieldName); if (metadata.isPrimaryKey(field)) { // PK has been set when creating object. @@ -1489,8 +1625,8 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); - String setter = metadata.getSetter(fieldName); - String getter = metadata.getGetter(fieldName); + String setter = metadata.getInternalSetter(fieldName); + String getter = metadata.getInternalGetter(fieldName); if (Utils.isRealmModel(field)) { writer @@ -1541,8 +1677,8 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); - String setter = metadata.getSetter(fieldName); - String getter = metadata.getGetter(fieldName); + String setter = metadata.getInternalSetter(fieldName); + String getter = metadata.getInternalGetter(fieldName); if (Utils.isRealmModel(field)) { writer .emitStatement("%s %sObj = ((%s) newObject).%s()", @@ -1601,51 +1737,52 @@ private void emitToStringMethod(JavaWriter writer) throws IOException { if (metadata.containsToString()) { return; } - writer.emitAnnotation("Override"); - writer.beginMethod("String", "toString", EnumSet.of(Modifier.PUBLIC)); - writer.beginControlFlow("if (!RealmObject.isValid(this))"); - writer.emitStatement("return \"Invalid object\""); - writer.endControlFlow(); + writer.emitAnnotation("Override") + .beginMethod("String", "toString", EnumSet.of(Modifier.PUBLIC)) + .beginControlFlow("if (!RealmObject.isValid(this))") + .emitStatement("return \"Invalid object\"") + .endControlFlow(); writer.emitStatement("StringBuilder stringBuilder = new StringBuilder(\"%s = [\")", simpleClassName); - List fields = metadata.getFields(); - for (int i = 0; i < fields.size(); i++) { - VariableElement field = fields.get(i); - String fieldName = field.getSimpleName().toString(); + + Collection fields = metadata.getFields(); + int i = fields.size() - 1; + for (VariableElement field: fields) { + String fieldName = field.getSimpleName().toString(); writer.emitStatement("stringBuilder.append(\"{%s:\")", fieldName); if (Utils.isRealmModel(field)) { String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); writer.emitStatement( "stringBuilder.append(%s() != null ? \"%s\" : \"null\")", - metadata.getGetter(fieldName), + metadata.getInternalGetter(fieldName), fieldTypeSimpleName ); } else if (Utils.isRealmList(field)) { String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); writer.emitStatement("stringBuilder.append(\"RealmList<%s>[\").append(%s().size()).append(\"]\")", genericTypeSimpleName, - metadata.getGetter(fieldName)); + metadata.getInternalGetter(fieldName)); } else { if (metadata.isNullable(field)) { writer.emitStatement("stringBuilder.append(%s() != null ? %s() : \"null\")", - metadata.getGetter(fieldName), - metadata.getGetter(fieldName) + metadata.getInternalGetter(fieldName), + metadata.getInternalGetter(fieldName) ); } else { - writer.emitStatement("stringBuilder.append(%s())", metadata.getGetter(fieldName)); + writer.emitStatement("stringBuilder.append(%s())", metadata.getInternalGetter(fieldName)); } } writer.emitStatement("stringBuilder.append(\"}\")"); - if (i < fields.size() - 1) { + if (i-- > 0) { writer.emitStatement("stringBuilder.append(\",\")"); } } writer.emitStatement("stringBuilder.append(\"]\")"); writer.emitStatement("return stringBuilder.toString()"); - writer.endMethod(); - writer.emitEmptyLine(); + writer.endMethod() + .emitEmptyLine(); } /** @@ -1657,19 +1794,19 @@ private void emitHashcodeMethod(JavaWriter writer) throws IOException { if (metadata.containsHashCode()) { return; } - writer.emitAnnotation("Override"); - writer.beginMethod("int", "hashCode", EnumSet.of(Modifier.PUBLIC)); - writer.emitStatement("String realmName = proxyState.getRealm$realm().getPath()"); - writer.emitStatement("String tableName = proxyState.getRow$realm().getTable().getName()"); - writer.emitStatement("long rowIndex = proxyState.getRow$realm().getIndex()"); - writer.emitEmptyLine(); - writer.emitStatement("int result = 17"); - writer.emitStatement("result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0)"); - writer.emitStatement("result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0)"); - writer.emitStatement("result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32))"); - writer.emitStatement("return result"); - writer.endMethod(); - writer.emitEmptyLine(); + writer.emitAnnotation("Override") + .beginMethod("int", "hashCode", EnumSet.of(Modifier.PUBLIC)) + .emitStatement("String realmName = proxyState.getRealm$realm().getPath()") + .emitStatement("String tableName = proxyState.getRow$realm().getTable().getName()") + .emitStatement("long rowIndex = proxyState.getRow$realm().getIndex()") + .emitEmptyLine() + .emitStatement("int result = 17") + .emitStatement("result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0)") + .emitStatement("result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0)") + .emitStatement("result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32))") + .emitStatement("return result") + .endMethod() + .emitEmptyLine(); } private void emitEqualsMethod(JavaWriter writer) throws IOException { @@ -1678,25 +1815,25 @@ private void emitEqualsMethod(JavaWriter writer) throws IOException { } String proxyClassName = Utils.getProxyClassName(simpleClassName); String otherObjectVarName = "a" + simpleClassName; - writer.emitAnnotation("Override"); - writer.beginMethod("boolean", "equals", EnumSet.of(Modifier.PUBLIC), "Object", "o"); - writer.emitStatement("if (this == o) return true"); - writer.emitStatement("if (o == null || getClass() != o.getClass()) return false"); - writer.emitStatement("%s %s = (%s)o", proxyClassName, otherObjectVarName, proxyClassName); // FooRealmProxy aFoo = (FooRealmProxy)o - writer.emitEmptyLine(); - writer.emitStatement("String path = proxyState.getRealm$realm().getPath()"); - writer.emitStatement("String otherPath = %s.proxyState.getRealm$realm().getPath()", otherObjectVarName); - writer.emitStatement("if (path != null ? !path.equals(otherPath) : otherPath != null) return false"); - writer.emitEmptyLine(); - writer.emitStatement("String tableName = proxyState.getRow$realm().getTable().getName()"); - writer.emitStatement("String otherTableName = %s.proxyState.getRow$realm().getTable().getName()", otherObjectVarName); - writer.emitStatement("if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false"); - writer.emitEmptyLine(); - writer.emitStatement("if (proxyState.getRow$realm().getIndex() != %s.proxyState.getRow$realm().getIndex()) return false", otherObjectVarName); - writer.emitEmptyLine(); - writer.emitStatement("return true"); - writer.endMethod(); - writer.emitEmptyLine(); + writer.emitAnnotation("Override") + .beginMethod("boolean", "equals", EnumSet.of(Modifier.PUBLIC), "Object", "o") + .emitStatement("if (this == o) return true") + .emitStatement("if (o == null || getClass() != o.getClass()) return false") + .emitStatement("%s %s = (%s)o", proxyClassName, otherObjectVarName, proxyClassName) // FooRealmProxy aFoo = (FooRealmProxy)o + .emitEmptyLine() + .emitStatement("String path = proxyState.getRealm$realm().getPath()") + .emitStatement("String otherPath = %s.proxyState.getRealm$realm().getPath()", otherObjectVarName) + .emitStatement("if (path != null ? !path.equals(otherPath) : otherPath != null) return false") + .emitEmptyLine() + .emitStatement("String tableName = proxyState.getRow$realm().getTable().getName()") + .emitStatement("String otherTableName = %s.proxyState.getRow$realm().getTable().getName()", otherObjectVarName) + .emitStatement("if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false") + .emitEmptyLine() + .emitStatement("if (proxyState.getRow$realm().getIndex() != %s.proxyState.getRow$realm().getIndex()) return false", otherObjectVarName) + .emitEmptyLine() + .emitStatement("return true") + .endMethod() + .emitEmptyLine(); } private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOException { @@ -1775,7 +1912,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep if (Utils.isRealmModel(field)) { RealmJsonTypeHelper.emitFillRealmObjectWithJsonValue( interfaceName, - metadata.getSetter(fieldName), + metadata.getInternalSetter(fieldName), fieldName, qualifiedFieldType, Utils.getProxyClassSimpleName(field), @@ -1785,8 +1922,8 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep } else if (Utils.isRealmList(field)) { RealmJsonTypeHelper.emitFillRealmListWithJsonValue( interfaceName, - metadata.getGetter(fieldName), - metadata.getSetter(fieldName), + metadata.getInternalGetter(fieldName), + metadata.getInternalSetter(fieldName), fieldName, ((DeclaredType) field.asType()).getTypeArguments().get(0).toString(), Utils.getProxyClassSimpleName(field), @@ -1795,7 +1932,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep } else { RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( interfaceName, - metadata.getSetter(fieldName), + metadata.getInternalSetter(fieldName), fieldName, qualifiedFieldType, writer @@ -1808,7 +1945,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep writer.emitEmptyLine(); } - private void buildExcludeFieldsList(JavaWriter writer, List fields) throws IOException { + private void buildExcludeFieldsList(JavaWriter writer, Collection fields) throws IOException { for (VariableElement field : fields) { if (Utils.isRealmModel(field) || Utils.isRealmList(field)) { final String fieldName = field.getSimpleName().toString(); @@ -1838,22 +1975,18 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer.emitStatement("reader.beginObject()"); writer.beginControlFlow("while (reader.hasNext())"); writer.emitStatement("String name = reader.nextName()"); + writer.beginControlFlow("if (false)"); - List fields = metadata.getFields(); - for (int i = 0; i < fields.size(); i++) { - VariableElement field = fields.get(i); + Collection fields = metadata.getFields(); + for (VariableElement field: fields) { String fieldName = field.getSimpleName().toString(); String qualifiedFieldType = field.asType().toString(); + writer.nextControlFlow("else if (name.equals(\"%s\"))", fieldName); - if (i == 0) { - writer.beginControlFlow("if (name.equals(\"%s\"))", fieldName); - } else { - writer.nextControlFlow("else if (name.equals(\"%s\"))", fieldName); - } if (Utils.isRealmModel(field)) { RealmJsonTypeHelper.emitFillRealmObjectFromStream( interfaceName, - metadata.getSetter(fieldName), + metadata.getInternalSetter(fieldName), fieldName, qualifiedFieldType, Utils.getProxyClassSimpleName(field), @@ -1863,8 +1996,8 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { } else if (Utils.isRealmList(field)) { RealmJsonTypeHelper.emitFillRealmListFromStream( interfaceName, - metadata.getGetter(fieldName), - metadata.getSetter(fieldName), + metadata.getInternalGetter(fieldName), + metadata.getInternalSetter(fieldName), ((DeclaredType) field.asType()).getTypeArguments().get(0).toString(), Utils.getProxyClassSimpleName(field), writer); @@ -1880,23 +2013,27 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { } } - if (fields.size() > 0) { - writer.nextControlFlow("else"); - writer.emitStatement("reader.skipValue()"); - writer.endControlFlow(); - } + writer.nextControlFlow("else"); + writer.emitStatement("reader.skipValue()"); + writer.endControlFlow(); + writer.endControlFlow(); writer.emitStatement("reader.endObject()"); + if (metadata.hasPrimaryKey()) { - writer.beginControlFlow("if (!jsonHasPrimaryKey)"); - writer.emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.getPrimaryKey()); - writer.endControlFlow(); + writer.beginControlFlow("if (!jsonHasPrimaryKey)") + .emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.getPrimaryKey()) + .endControlFlow(); } + writer.emitStatement("obj = realm.copyToRealm(obj)"); writer.emitStatement("return obj"); writer.endMethod(); writer.emitEmptyLine(); + } + private void emitMigrationNeededException(JavaWriter writer, String message, Object... args) throws IOException { + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath(), " + message, args); } private String columnInfoClassName() { @@ -1911,7 +2048,7 @@ private String fieldIndexVariableReference(VariableElement variableElement) { return "columnInfo." + columnIndexVarName(variableElement); } - private static int countModelOrListFields(List fields) { + private static int countModelOrListFields(Collection fields) { int count = 0; for (VariableElement f : fields) { if (Utils.isRealmModel(f) || Utils.isRealmList(f)) { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java index 9613b09df9..b653a27f3b 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java @@ -59,18 +59,29 @@ public void generate() throws IOException { writer .beginMethod( fieldTypeCanonicalName, - metaData.getGetter(fieldName), + metaData.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) .endMethod() .beginMethod( "void", - metaData.getSetter(fieldName), + metaData.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value") .endMethod(); } } + + // backlinks are final and have only a getter. + for (Backlink backlink : metaData.getBacklinkFields()) { + writer + .beginMethod( + backlink.getTargetFieldType(), + metaData.getInternalGetter(backlink.getTargetField()), + EnumSet.of(Modifier.PUBLIC)) + .endMethod(); + } + writer.endType(); writer.close(); } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java index 09b7792bf0..bcf57bf780 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java @@ -16,29 +16,27 @@ package io.realm.processor; -import javax.annotation.processing.ProcessingEnvironment; -import javax.tools.Diagnostic; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; +import javax.annotation.processing.ProcessingEnvironment; +import javax.tools.Diagnostic; + public class RealmVersionChecker { public static final String REALM_ANDROID_DOWNLOAD_URL = "http://static.realm.io/downloads/java/latest"; - private static RealmVersionChecker instance = null; - private static boolean isFirstRound = true; - private static final String VERSION_URL = "http://static.realm.io/update/java?"; private static final String REALM_VERSION = Version.VERSION; private static final String REALM_VERSION_PATTERN = "\\d+\\.\\d+\\.\\d+"; private static final int READ_TIMEOUT = 2000; private static final int CONNECT_TIMEOUT = 4000; - private ProcessingEnvironment processingEnvironment; + private static RealmVersionChecker instance = null; - private RealmVersionChecker(ProcessingEnvironment processingEnvironment) { - this.processingEnvironment = processingEnvironment; - } + private ProcessingEnvironment processingEnvironment; public static RealmVersionChecker getInstance(ProcessingEnvironment processingEnvironment) { if (instance == null) { @@ -47,6 +45,28 @@ public static RealmVersionChecker getInstance(ProcessingEnvironment processingEn return instance; } + private RealmVersionChecker(ProcessingEnvironment processingEnvironment) { + this.processingEnvironment = processingEnvironment; + } + + public void executeRealmVersionUpdate() { + Thread backgroundThread = new Thread(new Runnable() { + @Override + public void run() { + launchRealmCheck(); + } + }); + + backgroundThread.start(); + + try { + backgroundThread.join(CONNECT_TIMEOUT + READ_TIMEOUT); + } + catch (InterruptedException ignore) { + // We ignore this exception on purpose not to break the build system if this class fails + } + } + private void launchRealmCheck() { //Check Realm version server String latestVersionStr = checkLatestVersion(); @@ -55,24 +75,6 @@ private void launchRealmCheck() { } } - public void executeRealmVersionUpdate() { - if (isFirstRound) { - isFirstRound = false; - Thread backgroundThread = new Thread(new Runnable() { - @Override - public void run() { - launchRealmCheck(); - } - }); - backgroundThread.start(); - try { - backgroundThread.join(CONNECT_TIMEOUT + READ_TIMEOUT); - } catch (InterruptedException e) { - // We ignore this exception on purpose not to break the build system if this class fails - } - } - } - private String checkLatestVersion() { String result = REALM_VERSION; try { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java index 9ed30c70eb..d7ee9217e1 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java @@ -10,6 +10,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import javax.tools.Diagnostic; @@ -22,6 +23,7 @@ public class Utils { public static Types typeUtils; private static Messager messager; private static DeclaredType realmList; + private static DeclaredType realmResults; private static DeclaredType markerInterface; private static TypeMirror realmModel; @@ -30,6 +32,8 @@ public static void initialize(ProcessingEnvironment env) { messager = env.getMessager(); realmList = typeUtils.getDeclaredType(env.getElementUtils().getTypeElement("io.realm.RealmList"), typeUtils.getWildcardType(null, null)); + realmResults = typeUtils.getDeclaredType(env.getElementUtils().getTypeElement("io.realm.RealmResults"), + typeUtils.getWildcardType(null, null)); realmModel = env.getElementUtils().getTypeElement("io.realm.RealmModel").asType(); markerInterface = env.getTypeUtils().getDeclaredType(env.getElementUtils().getTypeElement("io.realm.RealmModel")); } @@ -155,6 +159,42 @@ public static boolean isRealmModel(VariableElement field) { return typeUtils.isAssignable(field.asType(), realmModel); } + public static boolean isRealmResults(VariableElement field) { + return typeUtils.isAssignable(field.asType(), realmResults); + } + + // get the fully-qualified type name for the generic type of a RealmResults + public static String getRealmResultsType(VariableElement field) { + if (!Utils.isRealmResults(field)) { return null; } + DeclaredType type = getGenericTypeForContainer(field); + if (null == type) { return null; } + return type.toString(); + } + + // get the fully-qualified type name for the generic type of a RealmList + public static String getRealmListType(VariableElement field) { + if (!Utils.isRealmList(field)) { return null; } + DeclaredType type = getGenericTypeForContainer(field); + if (null == type) { return null; } + return type.toString(); + } + + // Note that, because subclassing subclasses of RealmObject is forbidden, + // there is no need to deal with constructs like: RealmResults<? extends Foos<. + public static DeclaredType getGenericTypeForContainer(VariableElement field) { + TypeMirror fieldType = field.asType(); + TypeKind kind = fieldType.getKind(); + if (kind != TypeKind.DECLARED) { return null; } + + List args = ((DeclaredType) fieldType).getTypeArguments(); + if (args.size() <= 0) { return null; } + + fieldType = args.get(0); + kind = fieldType.getKind(); + if (kind != TypeKind.DECLARED) { return null; } + + return (DeclaredType) fieldType; + } /** * @return the qualified type name for a field. @@ -167,11 +207,24 @@ public static String getFieldTypeQualifiedName(VariableElement field) { * @return the simple type name for a field. */ public static String getFieldTypeSimpleName(VariableElement field) { - String fieldTypeQualifiedName = getFieldTypeQualifiedName(field); - if (!fieldTypeQualifiedName.contains(".")) { - return fieldTypeQualifiedName; + return (null == field) ? null : getFieldTypeSimpleName(getFieldTypeQualifiedName(field)); + } + + /** + * @return the simple type name for a field. + */ + public static String getFieldTypeSimpleName(DeclaredType type) { + return (null == type) ? null : getFieldTypeSimpleName(type.toString()); + } + + /** + * @return the simple type name for a field. + */ + public static String getFieldTypeSimpleName(String fieldTypeQualifiedName) { + if ((null != fieldTypeQualifiedName) && (fieldTypeQualifiedName.contains("."))) { + fieldTypeQualifiedName = fieldTypeQualifiedName.substring(fieldTypeQualifiedName.lastIndexOf('.') + 1); } - return fieldTypeQualifiedName.substring(fieldTypeQualifiedName.lastIndexOf('.') + 1); + return fieldTypeQualifiedName; } /** @@ -231,4 +284,5 @@ public static Element getSuperClass(TypeElement classType) { public static String getProxyInterfaceName(String className) { return className + Constants.INTERFACE_SUFFIX; } + } diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java index d7eff132bd..b888290450 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java @@ -56,6 +56,17 @@ public class RealmProcessorTest { private JavaFileObject UseExtendRealmList = JavaFileObjects.forResource("some/test/UseExtendRealmList.java"); private JavaFileObject SimpleRealmModel = JavaFileObjects.forResource("some/test/SimpleRealmModel.java"); private JavaFileObject customInterface = JavaFileObjects.forResource("some/test/CustomInterface.java"); + private JavaFileObject backlinks = JavaFileObjects.forResource("some/test/Backlinks.java"); + private JavaFileObject backlinksTarget = JavaFileObjects.forResource("some/test/BacklinkTarget.java"); + private JavaFileObject backlinksInvalidField = JavaFileObjects.forResource("some/test/Backlinks_InvalidFieldType.java"); + private JavaFileObject backlinksLinked = JavaFileObjects.forResource("some/test/Backlinks_LinkedFields.java"); + private JavaFileObject backlinksMissingParam = JavaFileObjects.forResource("some/test/Backlinks_MissingParameter.java"); + private JavaFileObject backlinksMissingGeneric = JavaFileObjects.forResource("some/test/Backlinks_MissingGeneric.java"); + private JavaFileObject backlinksRequired = JavaFileObjects.forResource("some/test/Backlinks_Required.java"); + private JavaFileObject backlinksIgnored = JavaFileObjects.forResource("some/test/Backlinks_Ignored.java"); + private JavaFileObject backlinksNotFound = JavaFileObjects.forResource("some/test/Backlinks_NotFound.java"); + private JavaFileObject backlinksNonFinalField = JavaFileObjects.forResource("some/test/Backlinks_NotFinal.java"); + private JavaFileObject backlinksWrongType = JavaFileObjects.forResource("some/test/Backlinks_WrongType.java"); @Test public void compileSimpleFile() { @@ -183,7 +194,8 @@ public void compileLibraryModulesCustomClasses() throws Exception { @Test public void compileAppModuleMixedParametersFail() throws Exception { ASSERT.about(javaSources()) - .that(Arrays.asList(allTypesModel, JavaFileObjects.forResource("some/test/InvalidAppModuleMixedParameters.java"))) + .that(Arrays.asList(allTypesModel, JavaFileObjects.forResource( + "some/test/InvalidAllTypesModuleMixedParameters.java"))) .processedWith(new RealmProcessor()) .failsToCompile(); } @@ -191,7 +203,8 @@ public void compileAppModuleMixedParametersFail() throws Exception { @Test public void compileAppModuleWrongTypeFail() throws Exception { ASSERT.about(javaSources()) - .that(Arrays.asList(allTypesModel, JavaFileObjects.forResource("some/test/InvalidAppModuleWrongType.java"))) + .that(Arrays.asList(allTypesModel, JavaFileObjects.forResource( + "some/test/InvalidAllTypesModuleWrongType.java"))) .processedWith(new RealmProcessor()) .failsToCompile(); } @@ -460,4 +473,92 @@ public void compileWithInterfaceForObject() { .processedWith(new RealmProcessor()) .failsToCompile(); } + + @Test + public void compileBacklinks() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget)) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + @Test + public void failOnLinkingObjectsWithInvalidFieldType() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget, backlinksInvalidField)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("Fields annotated with @LinkingObjects must be RealmResults"); + } + + @Test + public void failOnLinkingObjectsWithNonFinalField() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget, backlinksNonFinalField)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("must be final"); + } + + @Test + public void failsOnLinkingObjectsWithLinkedFields() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget, backlinksLinked)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("The use of '.' to specify fields in referenced classes is not supported"); + } + + @Test + public void failsOnLinkingObjectsMissingFieldName() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget, backlinksMissingParam)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("must have a parameter identifying the link target"); + } + + @Test + public void failsOnLinkingObjectsMissingGeneric() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget, backlinksMissingGeneric)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("must specify a generic type"); + } + + @Test + public void failsOnLinkingObjectsWithRequiredFields() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget, backlinksRequired)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("cannot be @Required"); + } + + @Test + public void failsOnLinkingObjectsWithIgnoreFields() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget, backlinksIgnored)) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + @Test + public void failsOnLinkingObjectsFieldNotFound() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget, backlinksNotFound)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("does not exist in class"); + } + + @Test + public void failsOnLinkingObjectsWithFieldWrongType() { + ASSERT.about(javaSources()) + .that(Arrays.asList(backlinks, backlinksTarget, backlinksWrongType)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("instead of"); + } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 7666014010..049d2e6e67 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -29,10 +29,10 @@ import org.json.JSONObject; public class AllTypesRealmProxy extends some.test.AllTypes - implements RealmObjectProxy, AllTypesRealmProxyInterface { + implements RealmObjectProxy, AllTypesRealmProxyInterface { static final class AllTypesColumnInfo extends ColumnInfo - implements Cloneable { + implements Cloneable { public long columnStringIndex; public long columnLongIndex; @@ -93,6 +93,7 @@ public final AllTypesColumnInfo clone() { private AllTypesColumnInfo columnInfo; private ProxyState proxyState; private RealmList columnRealmListRealmList; + private RealmResults parentObjectsBacklinks; private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -112,7 +113,6 @@ public final AllTypesColumnInfo clone() { proxyState.setConstructionFinished(); } - @Override public void realm$injectObjectContext() { if (this.proxyState != null) { return; @@ -374,6 +374,15 @@ public final AllTypesColumnInfo clone() { } } + public RealmResults realmGet$parentObjects() { + BaseRealm realm = proxyState.getRealm$realm(); + realm.checkIfValid(); + if (parentObjectsBacklinks == null) { + parentObjectsBacklinks = RealmResults.createBacklinkResults(realm, proxyState.getRow$realm(), some.test.AllTypes.class, "columnObject"); + } + return parentObjectsBacklinks; + } + public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { if (!realmSchema.contains("AllTypes")) { RealmObjectSchema realmObjectSchema = realmSchema.create("AllTypes"); @@ -423,130 +432,151 @@ public static Table initTable(SharedRealm sharedRealm) { } public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { - if (sharedRealm.hasTable("class_AllTypes")) { - Table table = sharedRealm.getTable("class_AllTypes"); - final long columnCount = table.getColumnCount(); - if (columnCount != 9) { - if (columnCount < 9) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 9 but was " + columnCount); - } - if (allowExtraColumns) { - RealmLog.debug("Field count is more than expected - expected 9 but was %1$d", columnCount); - } else { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 9 but was " + columnCount); - } - } - Map columnTypes = new HashMap(); - for (long i = 0; i < columnCount; i++) { - columnTypes.put(table.getColumnName(i), table.getColumnType(i)); + if (!sharedRealm.hasTable("class_AllTypes")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'AllTypes' class is missing from the schema for this Realm."); + } + Table table = sharedRealm.getTable("class_AllTypes"); + final long columnCount = table.getColumnCount(); + if (columnCount != 9) { + if (columnCount < 9) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 9 but was " + columnCount); + } + if (allowExtraColumns) { + RealmLog.debug("Field count is more than expected - expected 9 but was %1$d", columnCount); + } else { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 9 but was " + columnCount); } + } + Map columnTypes = new HashMap(); + for (long i = 0; i < columnCount; i++) { + columnTypes.put(table.getColumnName(i), table.getColumnType(i)); + } - final AllTypesColumnInfo columnInfo = new AllTypesColumnInfo(sharedRealm.getPath(), table); + final AllTypesColumnInfo columnInfo = new AllTypesColumnInfo(sharedRealm.getPath(), table); - if (!table.hasPrimaryKey()) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary key not defined for field 'columnString' in existing Realm file. @PrimaryKey was added."); - } else { - if (table.getPrimaryKey() != columnInfo.columnStringIndex) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key annotation definition was changed, from field " + table.getColumnName(table.getPrimaryKey()) + " to field columnString"); - } + if (!table.hasPrimaryKey()) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary key not defined for field 'columnString' in existing Realm file. @PrimaryKey was added."); + } else { + if (table.getPrimaryKey() != columnInfo.columnStringIndex) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key annotation definition was changed, from field " + table.getColumnName(table.getPrimaryKey()) + " to field columnString"); } + } - if (!columnTypes.containsKey("columnString")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnString' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("columnString") != RealmFieldType.STRING) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'columnString' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.columnStringIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(),"@PrimaryKey field 'columnString' does not support null values in the existing Realm file. Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required."); - } - if (!table.hasSearchIndex(table.getColumnIndex("columnString"))) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Index not defined for field 'columnString' in existing Realm file. Either set @Index or migrate using io.realm.internal.Table.removeSearchIndex()."); - } - if (!columnTypes.containsKey("columnLong")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnLong' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("columnLong") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'long' for field 'columnLong' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.columnLongIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnLong' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnLong' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("columnFloat")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnFloat' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("columnFloat") != RealmFieldType.FLOAT) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'float' for field 'columnFloat' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.columnFloatIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnFloat' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnFloat' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("columnDouble")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnDouble' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("columnDouble") != RealmFieldType.DOUBLE) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'double' for field 'columnDouble' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.columnDoubleIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnDouble' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnDouble' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("columnBoolean")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnBoolean' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("columnBoolean") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'columnBoolean' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.columnBooleanIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnBoolean' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnBoolean' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("columnDate")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnDate' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("columnDate") != RealmFieldType.DATE) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Date' for field 'columnDate' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.columnDateIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnDate' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'columnDate' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("columnBinary")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnBinary' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("columnBinary") != RealmFieldType.BINARY) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'byte[]' for field 'columnBinary' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.columnBinaryIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnBinary' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'columnBinary' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("columnObject")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnObject' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("columnObject") != RealmFieldType.OBJECT) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'AllTypes' for field 'columnObject'"); - } - if (!sharedRealm.hasTable("class_AllTypes")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing class 'class_AllTypes' for field 'columnObject'"); - } - Table table_7 = sharedRealm.getTable("class_AllTypes"); - if (!table.getLinkTarget(columnInfo.columnObjectIndex).hasSameSchema(table_7)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid RealmObject for field 'columnObject': '" + table.getLinkTarget(columnInfo.columnObjectIndex).getName() + "' expected - was '" + table_7.getName() + "'"); - } - if (!columnTypes.containsKey("columnRealmList")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnRealmList'"); - } - if (columnTypes.get("columnRealmList") != RealmFieldType.LIST) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'AllTypes' for field 'columnRealmList'"); - } - if (!sharedRealm.hasTable("class_AllTypes")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing class 'class_AllTypes' for field 'columnRealmList'"); - } - Table table_8 = sharedRealm.getTable("class_AllTypes"); - if (!table.getLinkTarget(columnInfo.columnRealmListIndex).hasSameSchema(table_8)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid RealmList type for field 'columnRealmList': '" + table.getLinkTarget(columnInfo.columnRealmListIndex).getName() + "' expected - was '" + table_8.getName() + "'"); - } - return columnInfo; - } else { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'AllTypes' class is missing from the schema for this Realm."); + if (!columnTypes.containsKey("columnString")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnString' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("columnString") != RealmFieldType.STRING) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'columnString' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.columnStringIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(),"@PrimaryKey field 'columnString' does not support null values in the existing Realm file. Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required."); + } + if (!table.hasSearchIndex(table.getColumnIndex("columnString"))) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Index not defined for field 'columnString' in existing Realm file. Either set @Index or migrate using io.realm.internal.Table.removeSearchIndex()."); + } + if (!columnTypes.containsKey("columnLong")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnLong' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("columnLong") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'long' for field 'columnLong' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.columnLongIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnLong' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnLong' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("columnFloat")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnFloat' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("columnFloat") != RealmFieldType.FLOAT) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'float' for field 'columnFloat' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.columnFloatIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnFloat' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnFloat' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("columnDouble")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnDouble' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("columnDouble") != RealmFieldType.DOUBLE) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'double' for field 'columnDouble' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.columnDoubleIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnDouble' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnDouble' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("columnBoolean")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnBoolean' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("columnBoolean") != RealmFieldType.BOOLEAN) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'columnBoolean' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.columnBooleanIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnBoolean' does support null values in the existing Realm file. Use corresponding boxed type for field 'columnBoolean' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("columnDate")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnDate' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } + if (columnTypes.get("columnDate") != RealmFieldType.DATE) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Date' for field 'columnDate' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.columnDateIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnDate' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'columnDate' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("columnBinary")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnBinary' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("columnBinary") != RealmFieldType.BINARY) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'byte[]' for field 'columnBinary' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.columnBinaryIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'columnBinary' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'columnBinary' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("columnObject")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnObject' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("columnObject") != RealmFieldType.OBJECT) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'AllTypes' for field 'columnObject'"); + } + if (!sharedRealm.hasTable("class_AllTypes")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing class 'class_AllTypes' for field 'columnObject'"); + } + Table table_7 = sharedRealm.getTable("class_AllTypes"); + if (!table.getLinkTarget(columnInfo.columnObjectIndex).hasSameSchema(table_7)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid RealmObject for field 'columnObject': '" + table.getLinkTarget(columnInfo.columnObjectIndex).getName() + "' expected - was '" + table_7.getName() + "'"); + } + if (!columnTypes.containsKey("columnRealmList")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'columnRealmList'"); + } + if (columnTypes.get("columnRealmList") != RealmFieldType.LIST) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'AllTypes' for field 'columnRealmList'"); + } + if (!sharedRealm.hasTable("class_AllTypes")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing class 'class_AllTypes' for field 'columnRealmList'"); + } + Table table_8 = sharedRealm.getTable("class_AllTypes"); + if (!table.getLinkTarget(columnInfo.columnRealmListIndex).hasSameSchema(table_8)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid RealmList type for field 'columnRealmList': '" + table.getLinkTarget(columnInfo.columnRealmListIndex).getName() + "' expected - was '" + table_8.getName() + "'"); + } + + long backlinkFieldIndex; + Table backlinkSourceTable; + Table backlinkTargetTable; + RealmFieldType backlinkFieldType; + if (!sharedRealm.hasTable("class_AllTypes")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Cannot find source class 'some.test.AllTypes' for @LinkingObjects field 'some.test.AllTypes.parentObjects'"); + } + backlinkSourceTable = sharedRealm.getTable("class_AllTypes"); + backlinkFieldIndex = backlinkSourceTable.getColumnIndex("columnObject"); + if (backlinkFieldIndex == Table.NO_MATCH) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Cannot find source field 'some.test.AllTypes.columnObject' for @LinkingObjects field 'some.test.AllTypes.parentObjects'"); + } + backlinkFieldType = backlinkSourceTable.getColumnType(backlinkFieldIndex); + if ((backlinkFieldType != RealmFieldType.OBJECT) && (backlinkFieldType != RealmFieldType.LIST)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Source field 'some.test.AllTypes.columnObject' for @LinkingObjects field 'some.test.AllTypes.parentObjects' is not a RealmObject type"); + } + backlinkTargetTable = backlinkSourceTable.getLinkTarget(backlinkFieldIndex); + if (!table.hasSameSchema(backlinkTargetTable)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Source field 'some.test.AllTypes.columnObject' for @LinkingObjects field 'some.test.AllTypes.parentObjects' has wrong type '" + backlinkTargetTable.getName() + "'"); + } + + return columnInfo; } public static String getTableName() { @@ -559,7 +589,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = new ArrayList(2); some.test.AllTypes obj = null; if (update) { @@ -671,13 +701,14 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { boolean jsonHasPrimaryKey = false; some.test.AllTypes obj = new some.test.AllTypes(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); - if (name.equals("columnString")) { + if (false) { + } else if (name.equals("columnString")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); ((AllTypesRealmProxyInterface) obj).realmSet$columnString(null); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 8906eb90e6..31ef063cde 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -29,10 +29,10 @@ import org.json.JSONObject; public class BooleansRealmProxy extends some.test.Booleans - implements RealmObjectProxy, BooleansRealmProxyInterface { + implements RealmObjectProxy, BooleansRealmProxyInterface { static final class BooleansColumnInfo extends ColumnInfo - implements Cloneable { + implements Cloneable { public long doneIndex; public long isReadyIndex; @@ -86,7 +86,6 @@ public final BooleansColumnInfo clone() { proxyState.setConstructionFinished(); } - @Override public void realm$injectObjectContext() { if (this.proxyState != null) { return; @@ -206,70 +205,70 @@ public static Table initTable(SharedRealm sharedRealm) { } public static BooleansColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { - if (sharedRealm.hasTable("class_Booleans")) { - Table table = sharedRealm.getTable("class_Booleans"); - final long columnCount = table.getColumnCount(); - if (columnCount != 4) { - if (columnCount < 4) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 4 but was " + columnCount); - } - if (allowExtraColumns) { - RealmLog.debug("Field count is more than expected - expected 4 but was %1$d", columnCount); - } else { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 4 but was " + columnCount); - } + if (!sharedRealm.hasTable("class_Booleans")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'Booleans' class is missing from the schema for this Realm."); + } + Table table = sharedRealm.getTable("class_Booleans"); + final long columnCount = table.getColumnCount(); + if (columnCount != 4) { + if (columnCount < 4) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 4 but was " + columnCount); } - Map columnTypes = new HashMap(); - for (long i = 0; i < columnCount; i++) { - columnTypes.put(table.getColumnName(i), table.getColumnType(i)); + if (allowExtraColumns) { + RealmLog.debug("Field count is more than expected - expected 4 but was %1$d", columnCount); + } else { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 4 but was " + columnCount); } + } + Map columnTypes = new HashMap(); + for (long i = 0; i < columnCount; i++) { + columnTypes.put(table.getColumnName(i), table.getColumnType(i)); + } - final BooleansColumnInfo columnInfo = new BooleansColumnInfo(sharedRealm.getPath(), table); + final BooleansColumnInfo columnInfo = new BooleansColumnInfo(sharedRealm.getPath(), table); - if (table.hasPrimaryKey()) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); - } + if (table.hasPrimaryKey()) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); + } - if (!columnTypes.containsKey("done")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'done' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("done") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'done' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.doneIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'done' does support null values in the existing Realm file. Use corresponding boxed type for field 'done' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("isReady")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'isReady' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("isReady") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'isReady' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.isReadyIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'isReady' does support null values in the existing Realm file. Use corresponding boxed type for field 'isReady' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("mCompleted")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'mCompleted' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("mCompleted") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'mCompleted' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.mCompletedIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'mCompleted' does support null values in the existing Realm file. Use corresponding boxed type for field 'mCompleted' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("anotherBoolean")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'anotherBoolean' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("anotherBoolean") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'anotherBoolean' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.anotherBooleanIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'anotherBoolean' does support null values in the existing Realm file. Use corresponding boxed type for field 'anotherBoolean' or migrate using RealmObjectSchema.setNullable()."); - } - return columnInfo; - } else { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'Booleans' class is missing from the schema for this Realm."); + if (!columnTypes.containsKey("done")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'done' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("done") != RealmFieldType.BOOLEAN) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'done' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.doneIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'done' does support null values in the existing Realm file. Use corresponding boxed type for field 'done' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("isReady")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'isReady' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } + if (columnTypes.get("isReady") != RealmFieldType.BOOLEAN) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'isReady' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.isReadyIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'isReady' does support null values in the existing Realm file. Use corresponding boxed type for field 'isReady' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("mCompleted")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'mCompleted' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("mCompleted") != RealmFieldType.BOOLEAN) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'mCompleted' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.mCompletedIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'mCompleted' does support null values in the existing Realm file. Use corresponding boxed type for field 'mCompleted' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("anotherBoolean")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'anotherBoolean' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("anotherBoolean") != RealmFieldType.BOOLEAN) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'boolean' for field 'anotherBoolean' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.anotherBooleanIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'anotherBoolean' does support null values in the existing Realm file. Use corresponding boxed type for field 'anotherBoolean' or migrate using RealmObjectSchema.setNullable()."); + } + + return columnInfo; } public static String getTableName() { @@ -282,7 +281,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = Collections. emptyList(); some.test.Booleans obj = realm.createObjectInternal(some.test.Booleans.class, true, excludeFields); if (json.has("done")) { @@ -319,12 +318,13 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { some.test.Booleans obj = new some.test.Booleans(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); - if (name.equals("done")) { + if (false) { + } else if (name.equals("done")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); throw new IllegalArgumentException("Trying to set non-nullable field 'done' to null."); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index a541004034..5eb6e56b8e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -29,10 +29,10 @@ import org.json.JSONObject; public class NullTypesRealmProxy extends some.test.NullTypes - implements RealmObjectProxy, NullTypesRealmProxyInterface { + implements RealmObjectProxy, NullTypesRealmProxyInterface { static final class NullTypesColumnInfo extends ColumnInfo - implements Cloneable { + implements Cloneable { public long fieldStringNotNullIndex; public long fieldStringNullIndex; @@ -171,7 +171,6 @@ public final NullTypesColumnInfo clone() { proxyState.setConstructionFinished(); } - @Override public void realm$injectObjectContext() { if (this.proxyState != null) { return; @@ -864,227 +863,227 @@ public static Table initTable(SharedRealm sharedRealm) { } public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { - if (sharedRealm.hasTable("class_NullTypes")) { - Table table = sharedRealm.getTable("class_NullTypes"); - final long columnCount = table.getColumnCount(); - if (columnCount != 21) { - if (columnCount < 21) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 21 but was " + columnCount); - } - if (allowExtraColumns) { - RealmLog.debug("Field count is more than expected - expected 21 but was %1$d", columnCount); - } else { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 21 but was " + columnCount); - } + if (!sharedRealm.hasTable("class_NullTypes")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'NullTypes' class is missing from the schema for this Realm."); + } + Table table = sharedRealm.getTable("class_NullTypes"); + final long columnCount = table.getColumnCount(); + if (columnCount != 21) { + if (columnCount < 21) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 21 but was " + columnCount); } - Map columnTypes = new HashMap(); - for (long i = 0; i < columnCount; i++) { - columnTypes.put(table.getColumnName(i), table.getColumnType(i)); + if (allowExtraColumns) { + RealmLog.debug("Field count is more than expected - expected 21 but was %1$d", columnCount); + } else { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 21 but was " + columnCount); } + } + Map columnTypes = new HashMap(); + for (long i = 0; i < columnCount; i++) { + columnTypes.put(table.getColumnName(i), table.getColumnType(i)); + } - final NullTypesColumnInfo columnInfo = new NullTypesColumnInfo(sharedRealm.getPath(), table); + final NullTypesColumnInfo columnInfo = new NullTypesColumnInfo(sharedRealm.getPath(), table); - if (table.hasPrimaryKey()) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); - } + if (table.hasPrimaryKey()) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); + } - if (!columnTypes.containsKey("fieldStringNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldStringNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldStringNotNull") != RealmFieldType.STRING) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'fieldStringNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldStringNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldStringNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldStringNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldStringNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldStringNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldStringNull") != RealmFieldType.STRING) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'fieldStringNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldStringNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldStringNull' is required. Either set @Required to field 'fieldStringNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldBooleanNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBooleanNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldBooleanNotNull") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Boolean' for field 'fieldBooleanNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldBooleanNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldBooleanNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldBooleanNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldBooleanNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBooleanNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldBooleanNull") != RealmFieldType.BOOLEAN) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Boolean' for field 'fieldBooleanNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldBooleanNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldBooleanNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldBooleanNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldBytesNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBytesNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldBytesNotNull") != RealmFieldType.BINARY) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'byte[]' for field 'fieldBytesNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldBytesNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldBytesNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldBytesNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldBytesNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBytesNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldBytesNull") != RealmFieldType.BINARY) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'byte[]' for field 'fieldBytesNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldBytesNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldBytesNull' is required. Either set @Required to field 'fieldBytesNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldByteNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldByteNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldByteNotNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Byte' for field 'fieldByteNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldByteNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldByteNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldByteNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldByteNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldByteNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldByteNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Byte' for field 'fieldByteNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldByteNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldByteNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldByteNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldShortNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldShortNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldShortNotNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Short' for field 'fieldShortNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldShortNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldShortNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldShortNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldShortNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldShortNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldShortNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Short' for field 'fieldShortNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldShortNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldShortNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldShortNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldIntegerNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldIntegerNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldIntegerNotNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Integer' for field 'fieldIntegerNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldIntegerNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldIntegerNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldIntegerNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldIntegerNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldIntegerNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldIntegerNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Integer' for field 'fieldIntegerNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldIntegerNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldIntegerNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldIntegerNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldLongNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldLongNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldLongNotNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Long' for field 'fieldLongNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldLongNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldLongNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldLongNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldLongNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldLongNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldLongNull") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Long' for field 'fieldLongNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldLongNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldLongNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldLongNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldFloatNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldFloatNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldFloatNotNull") != RealmFieldType.FLOAT) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Float' for field 'fieldFloatNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldFloatNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldFloatNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldFloatNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldFloatNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldFloatNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldFloatNull") != RealmFieldType.FLOAT) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Float' for field 'fieldFloatNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldFloatNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldFloatNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldFloatNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldDoubleNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDoubleNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldDoubleNotNull") != RealmFieldType.DOUBLE) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Double' for field 'fieldDoubleNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldDoubleNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldDoubleNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldDoubleNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldDoubleNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDoubleNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldDoubleNull") != RealmFieldType.DOUBLE) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Double' for field 'fieldDoubleNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldDoubleNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldDoubleNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldDoubleNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldDateNotNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDateNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldDateNotNull") != RealmFieldType.DATE) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Date' for field 'fieldDateNotNull' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.fieldDateNotNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldDateNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldDateNotNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldDateNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDateNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldDateNull") != RealmFieldType.DATE) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Date' for field 'fieldDateNull' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.fieldDateNullIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldDateNull' is required. Either set @Required to field 'fieldDateNull' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("fieldObjectNull")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldObjectNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("fieldObjectNull") != RealmFieldType.OBJECT) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'NullTypes' for field 'fieldObjectNull'"); - } - if (!sharedRealm.hasTable("class_NullTypes")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing class 'class_NullTypes' for field 'fieldObjectNull'"); - } - Table table_20 = sharedRealm.getTable("class_NullTypes"); - if (!table.getLinkTarget(columnInfo.fieldObjectNullIndex).hasSameSchema(table_20)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid RealmObject for field 'fieldObjectNull': '" + table.getLinkTarget(columnInfo.fieldObjectNullIndex).getName() + "' expected - was '" + table_20.getName() + "'"); - } - return columnInfo; - } else { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'NullTypes' class is missing from the schema for this Realm."); + if (!columnTypes.containsKey("fieldStringNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldStringNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldStringNotNull") != RealmFieldType.STRING) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'fieldStringNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldStringNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldStringNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldStringNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldStringNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldStringNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldStringNull") != RealmFieldType.STRING) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'fieldStringNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldStringNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldStringNull' is required. Either set @Required to field 'fieldStringNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldBooleanNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBooleanNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldBooleanNotNull") != RealmFieldType.BOOLEAN) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Boolean' for field 'fieldBooleanNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldBooleanNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldBooleanNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldBooleanNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldBooleanNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBooleanNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldBooleanNull") != RealmFieldType.BOOLEAN) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Boolean' for field 'fieldBooleanNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldBooleanNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldBooleanNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldBooleanNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldBytesNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBytesNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldBytesNotNull") != RealmFieldType.BINARY) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'byte[]' for field 'fieldBytesNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldBytesNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldBytesNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldBytesNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldBytesNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldBytesNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldBytesNull") != RealmFieldType.BINARY) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'byte[]' for field 'fieldBytesNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldBytesNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldBytesNull' is required. Either set @Required to field 'fieldBytesNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldByteNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldByteNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldByteNotNull") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Byte' for field 'fieldByteNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldByteNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldByteNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldByteNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldByteNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldByteNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldByteNull") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Byte' for field 'fieldByteNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldByteNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldByteNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldByteNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldShortNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldShortNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldShortNotNull") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Short' for field 'fieldShortNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldShortNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldShortNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldShortNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldShortNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldShortNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldShortNull") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Short' for field 'fieldShortNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldShortNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldShortNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldShortNull' or migrate using RealmObjectSchema.setNullable()."); } + if (!columnTypes.containsKey("fieldIntegerNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldIntegerNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldIntegerNotNull") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Integer' for field 'fieldIntegerNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldIntegerNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldIntegerNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldIntegerNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldIntegerNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldIntegerNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldIntegerNull") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Integer' for field 'fieldIntegerNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldIntegerNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldIntegerNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldIntegerNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldLongNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldLongNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldLongNotNull") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Long' for field 'fieldLongNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldLongNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldLongNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldLongNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldLongNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldLongNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldLongNull") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Long' for field 'fieldLongNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldLongNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldLongNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldLongNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldFloatNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldFloatNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldFloatNotNull") != RealmFieldType.FLOAT) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Float' for field 'fieldFloatNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldFloatNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldFloatNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldFloatNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldFloatNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldFloatNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldFloatNull") != RealmFieldType.FLOAT) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Float' for field 'fieldFloatNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldFloatNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldFloatNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldFloatNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldDoubleNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDoubleNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldDoubleNotNull") != RealmFieldType.DOUBLE) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Double' for field 'fieldDoubleNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldDoubleNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldDoubleNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldDoubleNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldDoubleNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDoubleNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldDoubleNull") != RealmFieldType.DOUBLE) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Double' for field 'fieldDoubleNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldDoubleNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(),"Field 'fieldDoubleNull' does not support null values in the existing Realm file. Either set @Required, use the primitive type for field 'fieldDoubleNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldDateNotNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDateNotNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldDateNotNull") != RealmFieldType.DATE) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Date' for field 'fieldDateNotNull' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.fieldDateNotNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldDateNotNull' does support null values in the existing Realm file. Remove @Required or @PrimaryKey from field 'fieldDateNotNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldDateNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldDateNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldDateNull") != RealmFieldType.DATE) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'Date' for field 'fieldDateNull' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.fieldDateNullIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'fieldDateNull' is required. Either set @Required to field 'fieldDateNull' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("fieldObjectNull")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'fieldObjectNull' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("fieldObjectNull") != RealmFieldType.OBJECT) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'NullTypes' for field 'fieldObjectNull'"); + } + if (!sharedRealm.hasTable("class_NullTypes")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing class 'class_NullTypes' for field 'fieldObjectNull'"); + } + Table table_20 = sharedRealm.getTable("class_NullTypes"); + if (!table.getLinkTarget(columnInfo.fieldObjectNullIndex).hasSameSchema(table_20)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid RealmObject for field 'fieldObjectNull': '" + table.getLinkTarget(columnInfo.fieldObjectNullIndex).getName() + "' expected - was '" + table_20.getName() + "'"); + } + + return columnInfo; } public static String getTableName() { @@ -1097,7 +1096,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = new ArrayList(1); if (json.has("fieldObjectNull")) { excludeFields.add("fieldObjectNull"); @@ -1267,12 +1266,13 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { some.test.NullTypes obj = new some.test.NullTypes(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); - if (name.equals("fieldStringNotNull")) { + if (false) { + } else if (name.equals("fieldStringNotNull")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); ((NullTypesRealmProxyInterface) obj).realmSet$fieldStringNotNull(null); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 694515e02c..cf028d5130 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -29,10 +29,10 @@ import org.json.JSONObject; public class SimpleRealmProxy extends some.test.Simple - implements RealmObjectProxy, SimpleRealmProxyInterface { + implements RealmObjectProxy, SimpleRealmProxyInterface { static final class SimpleColumnInfo extends ColumnInfo - implements Cloneable { + implements Cloneable { public long nameIndex; public long ageIndex; @@ -76,7 +76,6 @@ public final SimpleColumnInfo clone() { proxyState.setConstructionFinished(); } - @Override public void realm$injectObjectContext() { if (this.proxyState != null) { return; @@ -160,52 +159,52 @@ public static Table initTable(SharedRealm sharedRealm) { } public static SimpleColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { - if (sharedRealm.hasTable("class_Simple")) { - Table table = sharedRealm.getTable("class_Simple"); - final long columnCount = table.getColumnCount(); - if (columnCount != 2) { - if (columnCount < 2) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 2 but was " + columnCount); - } - if (allowExtraColumns) { - RealmLog.debug("Field count is more than expected - expected 2 but was %1$d", columnCount); - } else { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 2 but was " + columnCount); - } + if (!sharedRealm.hasTable("class_Simple")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'Simple' class is missing from the schema for this Realm."); + } + Table table = sharedRealm.getTable("class_Simple"); + final long columnCount = table.getColumnCount(); + if (columnCount != 2) { + if (columnCount < 2) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is less than expected - expected 2 but was " + columnCount); } - Map columnTypes = new HashMap(); - for (long i = 0; i < columnCount; i++) { - columnTypes.put(table.getColumnName(i), table.getColumnType(i)); + if (allowExtraColumns) { + RealmLog.debug("Field count is more than expected - expected 2 but was %1$d", columnCount); + } else { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field count is more than expected - expected 2 but was " + columnCount); } + } + Map columnTypes = new HashMap(); + for (long i = 0; i < columnCount; i++) { + columnTypes.put(table.getColumnName(i), table.getColumnType(i)); + } - final SimpleColumnInfo columnInfo = new SimpleColumnInfo(sharedRealm.getPath(), table); + final SimpleColumnInfo columnInfo = new SimpleColumnInfo(sharedRealm.getPath(), table); - if (table.hasPrimaryKey()) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); - } + if (table.hasPrimaryKey()) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); + } - if (!columnTypes.containsKey("name")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'name' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("name") != RealmFieldType.STRING) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'name' in existing Realm file."); - } - if (!table.isColumnNullable(columnInfo.nameIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'name' is required. Either set @Required to field 'name' or migrate using RealmObjectSchema.setNullable()."); - } - if (!columnTypes.containsKey("age")) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'age' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); - } - if (columnTypes.get("age") != RealmFieldType.INTEGER) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'int' for field 'age' in existing Realm file."); - } - if (table.isColumnNullable(columnInfo.ageIndex)) { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'age' does support null values in the existing Realm file. Use corresponding boxed type for field 'age' or migrate using RealmObjectSchema.setNullable()."); - } - return columnInfo; - } else { - throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'Simple' class is missing from the schema for this Realm."); + if (!columnTypes.containsKey("name")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'name' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); } + if (columnTypes.get("name") != RealmFieldType.STRING) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'String' for field 'name' in existing Realm file."); + } + if (!table.isColumnNullable(columnInfo.nameIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'name' is required. Either set @Required to field 'name' or migrate using RealmObjectSchema.setNullable()."); + } + if (!columnTypes.containsKey("age")) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Missing field 'age' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn()."); + } + if (columnTypes.get("age") != RealmFieldType.INTEGER) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Invalid type 'int' for field 'age' in existing Realm file."); + } + if (table.isColumnNullable(columnInfo.ageIndex)) { + throw new RealmMigrationNeededException(sharedRealm.getPath(), "Field 'age' does support null values in the existing Realm file. Use corresponding boxed type for field 'age' or migrate using RealmObjectSchema.setNullable()."); + } + + return columnInfo; } public static String getTableName() { @@ -218,7 +217,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = Collections. emptyList(); some.test.Simple obj = realm.createObjectInternal(some.test.Simple.class, true, excludeFields); if (json.has("name")) { @@ -241,12 +240,13 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { some.test.Simple obj = new some.test.Simple(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); - if (name.equals("name")) { + if (false) { + } else if (name.equals("name")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); ((SimpleRealmProxyInterface) obj).realmSet$name(null); diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java index b0961f76de..80d74d91b9 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java @@ -20,12 +20,15 @@ import io.realm.RealmList; import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; public class AllTypes extends RealmObject { public static final String TAG = "AllTypes"; + public static final String FIELD_PARENTS = "columnObject"; @PrimaryKey private String columnString; @@ -39,6 +42,8 @@ public class AllTypes extends RealmObject { private byte[] columnBinary; private AllTypes columnObject; private RealmList columnRealmList; + @LinkingObjects(FIELD_PARENTS) + private final RealmResults parentObjects = null; public String getColumnString() { return realmGet$columnString(); diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkTarget.java b/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkTarget.java new file mode 100644 index 0000000000..40632c1a7d --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkTarget.java @@ -0,0 +1,12 @@ +package some.test; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class BacklinkTarget extends RealmObject { + private String id; + private Backlinks child; + private RealmList children; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks.java new file mode 100644 index 0000000000..8ad6f6e435 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks.java @@ -0,0 +1,16 @@ +package some.test; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class Backlinks extends RealmObject { + private int id; + + @LinkingObjects("child") + private final RealmResults simpleParents = null; + + @LinkingObjects("children") + private final RealmResults listParents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_Ignored.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_Ignored.java new file mode 100644 index 0000000000..acceae1ffb --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_Ignored.java @@ -0,0 +1,15 @@ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.Ignore; + +public class Backlinks_Ignored extends RealmObject { + private int id; + + // An @Ignored, backlinked field is completely ignored + @Ignore + @LinkingObjects("foo") + private int parents = 0; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_InvalidFieldType.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_InvalidFieldType.java new file mode 100644 index 0000000000..4c6afb1098 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_InvalidFieldType.java @@ -0,0 +1,13 @@ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class Backlinks_InvalidFieldType extends RealmObject { + private int id; + + // Backlinks must be RealmResults + @LinkingObjects("child") + private final BacklinkTarget parents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_LinkedFields.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_LinkedFields.java new file mode 100644 index 0000000000..d2522269e4 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_LinkedFields.java @@ -0,0 +1,14 @@ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class Backlinks_LinkedFields extends RealmObject { + private int id; + + // Defining a backlink more than one levels back is not supported. + // It can be queried though: `equalTo("selectedFieldParents.selectedFieldParents") + @LinkingObjects("child.id") + private final RealmResults parents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_MissingGeneric.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_MissingGeneric.java new file mode 100644 index 0000000000..ababc626a7 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_MissingGeneric.java @@ -0,0 +1,13 @@ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class Backlinks_MissingGeneric extends RealmObject { + private int id; + + // Forgot to specify the backlink generic param + @LinkingObjects("child") + private final RealmResults parents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_MissingParameter.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_MissingParameter.java new file mode 100644 index 0000000000..dec9d6a6ec --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_MissingParameter.java @@ -0,0 +1,13 @@ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class Backlinks_MissingParameter extends RealmObject { + private int id; + + // Forgot to specify the backlinked field + @LinkingObjects + private final RealmResults parents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_NotFinal.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_NotFinal.java new file mode 100644 index 0000000000..97c406b82d --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_NotFinal.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class Backlinks_NotFinal extends RealmObject { + private int id; + + // The field named in the @LinkingObjects annotation must be final + @LinkingObjects("child") + private RealmResults simpleParents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_NotFound.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_NotFound.java new file mode 100644 index 0000000000..05692a71fb --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_NotFound.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class Backlinks_NotFound extends RealmObject { + private int id; + + // The argument to the @LinkingObjects annotation must name a field in the target class + @LinkingObjects("xxx") + private final RealmResults parents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_Required.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_Required.java new file mode 100644 index 0000000000..5f70fcbbfc --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_Required.java @@ -0,0 +1,15 @@ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.Required; + +public class Backlinks_Required extends RealmObject { + private int id; + + // A backlinked field may not be @Required + @Required + @LinkingObjects("child") + private final RealmResults parents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_WrongType.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_WrongType.java new file mode 100644 index 0000000000..a09363cab4 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks_WrongType.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class Backlinks_WrongType extends RealmObject { + private int id; + + // The type of the field named in the @LinkingObjects annotation must match + // the generic type of the annotated field + @LinkingObjects("child") + private final RealmResults parents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/FieldRealmResults.java b/realm/realm-annotations-processor/src/test/resources/some/test/FieldRealmResults.java new file mode 100644 index 0000000000..95c68945f7 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/FieldRealmResults.java @@ -0,0 +1,10 @@ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; + +public class FieldRealmResults extends RealmObject { + + // RealmResults should only be allowed if combined with a @LinkingObjects annotation + private RealmResults results; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/InvalidAppModuleMixedParameters.java b/realm/realm-annotations-processor/src/test/resources/some/test/InvalidAllTypesModuleMixedParameters.java similarity index 100% rename from realm/realm-annotations-processor/src/test/resources/some/test/InvalidAppModuleMixedParameters.java rename to realm/realm-annotations-processor/src/test/resources/some/test/InvalidAllTypesModuleMixedParameters.java diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/InvalidAppModuleWrongType.java b/realm/realm-annotations-processor/src/test/resources/some/test/InvalidAllTypesModuleWrongType.java similarity index 100% rename from realm/realm-annotations-processor/src/test/resources/some/test/InvalidAppModuleWrongType.java rename to realm/realm-annotations-processor/src/test/resources/some/test/InvalidAllTypesModuleWrongType.java diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/InvalidModelRealmModel_3.java b/realm/realm-annotations-processor/src/test/resources/some/test/InvalidModelRealmModel_3.java index d4adc862a8..9e0b5130eb 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/InvalidModelRealmModel_3.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/InvalidModelRealmModel_3.java @@ -22,6 +22,6 @@ // Invalid POJO, you can't extends from another class besides RealmObject @RealmClass -public class ValidModelPojo_3 extends Booleans implements RealmModel { +public class InvalidModelRealmModel_3 extends Booleans implements RealmModel { public String id; } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index b7b5097c1f..6d22e5dda3 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -124,12 +124,18 @@ repositories { } dependencies { - objectServerAnnotationProcessor project(':realm-annotations-processor') + provided 'io.reactivex:rxjava:1.1.0' provided 'com.google.code.findbugs:findbugs-annotations:3.0.1' + compile "io.realm:realm-annotations:${version}" compile 'com.getkeepsafe.relinker:relinker:1.2.2' + + objectServerAnnotationProcessor project(':realm-annotations-processor') objectServerCompile 'com.squareup.okhttp3:okhttp:3.4.1' + + androidTestAnnotationProcessor project(':realm-annotations-processor') + androidTestCompile fileTree(dir: 'testLibs', include: ['*.jar']) androidTestCompile 'io.reactivex:rxjava:1.1.0' androidTestCompile 'com.android.support:support-annotations:25.2.0' androidTestCompile 'com.android.support.test:runner:0.5' @@ -139,7 +145,6 @@ dependencies { androidTestCompile 'org.hamcrest:hamcrest-library:1.3' androidTestCompile 'com.opencsv:opencsv:3.4' androidTestCompile 'dk.ilios:spanner:0.6.0' - androidTestAnnotationProcessor project(':realm-annotations-processor') } task sourcesJar(type: Jar) { diff --git a/realm/realm-library/src/androidTest/assets/backlinks-fieldInUse.realm b/realm/realm-library/src/androidTest/assets/backlinks-fieldInUse.realm new file mode 100644 index 0000000000000000000000000000000000000000..a55bd44ac33947e12b85b187d8f5b4f35d711137 GIT binary patch literal 4096 zcmeHFu}&L75S^LzVTa&|2&^DQNNJF0BBi7>BuMmtLeo#%D!AnMd6T_jpp&DV77CvYSs=W9GUH9Mq`~ z((hUr#9P z4Y{%P?}>ivXExj$4T#ogov2T^4+r?Ua1tRBa+|X^S!(8etdU!;FCRp$>q`0HwU#E(JP#LMCMaG zj=Iq}%B{D4K2LkM#<+l&oqm*M#|KfT*G~q$>|K&&$>8K|68F3Bho48C*!8~ozPkc{ z6pc>e@lA|-v#UJdf>o|Veu+m}G8|Zb%KJkiFW`NC!8vcB5^woY9PHe3?9e6Tw)IiF zK)>twZ$M9b$K&Wjzih7#Jsl0xcr^a1ozIP`9h;*^KZ$E4qdpX%Cg)3LvX{@clecqWi%5=zVZtE=L0 z~-dE0X8=9ReC)@wzJC_TY%IsRc5P%}D%w@5(Yu=x7xy%dQ zq}-GNAk6NBf3g zm4`%kcBtubaOE}3j`30d0!yJs?dYEQ(d0>~!(ZdtQ{XYzmT~66dXEGQ>@~mCoLZ!s z5eIWQTjHSGxwOr>-G9vG0h+b+qyIumP4}}X&(;@1pPHtWI3N3-Vgo|FXdcz~sm(Kd zgsNi^JpB9Iy3qfH;o#Dc-}LL~e{S;sQQ=mJih+uOih+uOih+uOih+uOih=)vfj{OW B%)tNv literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/assets/backlinks-sourceFieldWrongType.realm b/realm/realm-library/src/androidTest/assets/backlinks-sourceFieldWrongType.realm new file mode 100644 index 0000000000000000000000000000000000000000..0cc76c03f2d062fbc243d3060b154df9833133e5 GIT binary patch literal 4096 zcmeHGJ#Q015S^LbvkgwbC;==XqDX+0CQ=%tlt~~`=pqpbI_I!Yv1MbQY=e@{bd)YB zU8Z!Ik_IUyWy+K(DP8hrZ_ha;@;|sOJbLr7^X|R%r6qSROcIYa-|rO)M7ubTi0s#B z7&gLTm{jlmK9Ae?a@oYodM6wVYI|Y5-D!2(gHQcl_vGj-jz0Fj_3IJwqWV$TKZ%CK z$^#d=xPJM7IuH%>H_leAe&glXP?Z+o^Nf*iMpAJJjT#}Ri@1!B{Xn*^bDz1%r+yAt z4tV4`wGY;SZ*mkv2dOt(oyLa-xd^Oze7968w|(!h?YvN z6>q?FJzi(lxKH$;N)zGrv6+~u3Gf{wn3VQG1^H2-R~gLSmbu?o=EhC<`{4J?WPEaa zcCr8NT-C1Qxg~tS1AcAUbPm}0RkAzoe*(YO@afJLsNtg&mYCQ$-IIGF>^Zh$PUiyr zFJ>Ie4K^u1#aq thrownException = new AtomicReference<>(null); + final AtomicReference thrownException = new AtomicReference(null); assertEquals(0, realm.where(Owner.CLASS_NAME).count()); try { diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java new file mode 100644 index 0000000000..766d85799e --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.realm.rule.TestRealmConfigurationFactory; + +@RunWith(AndroidJUnit4.class) +public class LinkingObjectsDynamicTests { + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + private Realm realm; + + @Before + public void setUp() { + RealmConfiguration realmConfig = configFactory.createConfiguration(); + realm = Realm.getInstance(realmConfig); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + @Test + public void dynamicQuery_invalidSyntax() { + String[] invalidBacklinks = new String[] { + "linkingObject(x", + "linkingObject(x.y", + "linkingObject(x.y)", + "linkingObject(x.y).", + "linkingObject(x.y)..z", + "linkingObject(x.y).linkingObjects(x1.y1).z" + }; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java new file mode 100644 index 0000000000..729a6b3d71 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -0,0 +1,853 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; + +import io.realm.entities.AllJavaTypes; +import io.realm.entities.BacklinksMissingFieldSourceModule; +import io.realm.entities.BacklinksMissingFieldTargetModule; +import io.realm.entities.BacklinksSource; +import io.realm.entities.BacklinksTarget; +import io.realm.entities.BacklinksWrongTypeSourceModule; +import io.realm.entities.BacklinksWrongTypeTargetModule; +import io.realm.exceptions.RealmException; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.TestRealmConfigurationFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class LinkingObjectsManagedTests { + private interface PostConditions { + void run(Realm realm); + } + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Realm realm; + private Context context; + + @Before + public void setUp() { + context = InstrumentationRegistry.getInstrumentation().getContext(); + + RealmConfiguration realmConfig = configFactory.createConfiguration(); + realm = Realm.getInstance(realmConfig); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + // Setting the linked object field creates the correct backlink + @Test + public void basic_singleBacklinkObject() { + realm.beginTransaction(); + AllJavaTypes child = realm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); + parent.setFieldObject(child); + realm.commitTransaction(); + + assertEquals(1, child.getObjectParents().size()); + assertTrue(child.getObjectParents().contains(parent)); + } + + // Setting a linked list field creates the correct backlink + @Test + public void basic_singleBacklinkList() { + realm.beginTransaction(); + AllJavaTypes child = realm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); + parent.getFieldList().add(child); + realm.commitTransaction(); + + assertEquals(1, child.getListParents().size()); + assertTrue(child.getListParents().contains(parent)); + } + + // Setting multiple object links creates multiple backlinks + @Test + public void basic_multipleBacklinksObject() { + realm.beginTransaction(); + AllJavaTypes child = realm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent1 = realm.createObject(AllJavaTypes.class, 2); + AllJavaTypes parent2 = realm.createObject(AllJavaTypes.class, 3); + parent1.setFieldObject(child); + parent2.setFieldObject(child); + realm.commitTransaction(); + assertEquals(2, child.getObjectParents().size()); + } + + // Setting multiple list links creates multiple backlinks + @Test + public void basic_multipleBacklinksList() { + realm.beginTransaction(); + AllJavaTypes child = realm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent1 = realm.createObject(AllJavaTypes.class, 2); + AllJavaTypes parent2 = realm.createObject(AllJavaTypes.class, 3); + parent1.getFieldList().add(child); + parent2.getFieldList().add(child); + realm.commitTransaction(); + assertEquals(2, child.getListParents().size()); + } + + // Adding multiple list links creates multiple backlinks, + // even if the links are to a single object + @Test + public void basic_multipleReferencesFromParentList() { + realm.beginTransaction(); + AllJavaTypes child = realm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); + parent.getFieldList().add(child); + parent.getFieldList().add(child); + realm.commitTransaction(); + + // One entry for each reference, so two references from a LinkList will + // result in two backlinks. + assertEquals(2, child.getListParents().size()); + assertEquals(parent, child.getListParents().first()); + assertEquals(parent, child.getListParents().last()); + } + + // A listener registered on the backlinked object should be called when a commit adds a backlink + @Test + @RunTestInLooperThread + public void notification_onCommitModelObject() { + final Realm looperThreadRealm = looperThread.realm; + + looperThreadRealm.beginTransaction(); + AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); + looperThreadRealm.commitTransaction(); + + final AtomicInteger counter = new AtomicInteger(0); + RealmChangeListener listener = new RealmChangeListener() { + @Override + public void onChange(AllJavaTypes object) { + counter.incrementAndGet(); + } + }; + child.addChangeListener(listener); + + looperThreadRealm.beginTransaction(); + AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); + parent.setFieldObject(child); + looperThreadRealm.commitTransaction(); + + verifyPostConditions( + looperThreadRealm, + new PostConditions() { + public void run(Realm realm) { + assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + assertEquals(1, counter.get()); + } + }, + child, parent); + } + + + + // A listener registered on the backlinked object should not be called after the listener is removed + @Test + @RunTestInLooperThread + public void notification_notSentAfterUnregisterListenerModelObject() { + final Realm looperThreadRealm = looperThread.realm; + + looperThreadRealm.beginTransaction(); + AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); + looperThreadRealm.commitTransaction(); + + RealmChangeListener listener = new RealmChangeListener() { + @Override + public void onChange(AllJavaTypes object) { + fail("Not expecting notification after unregister"); + } + }; + child.addChangeListener(listener); + child.removeChangeListener(listener); + + looperThreadRealm.beginTransaction(); + AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); + parent.setFieldObject(child); + looperThreadRealm.commitTransaction(); + + verifyPostConditions( + looperThreadRealm, + new PostConditions() { + public void run(Realm realm) { + assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + } + }, + child, parent); + } + + // A listener registered on the backlinked object should be called when a backlinked object is deleted + @Test + @RunTestInLooperThread + public void notification_onDeleteModelObject() { + final Realm looperThreadRealm = looperThread.realm; + + looperThreadRealm.beginTransaction(); + AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); + AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); + parent.setFieldObject(child); + looperThreadRealm.commitTransaction(); + + final AtomicInteger counter = new AtomicInteger(0); + RealmChangeListener listener = new RealmChangeListener() { + @Override + public void onChange(AllJavaTypes object) { + counter.incrementAndGet(); + } + }; + child.addChangeListener(listener); + + looperThreadRealm.beginTransaction(); + looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll().deleteAllFromRealm(); + looperThreadRealm.commitTransaction(); + + verifyPostConditions( + looperThreadRealm, + new PostConditions() { + public void run(Realm realm) { + assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + assertEquals(1, counter.get()); + } + }, + child, parent); + } + + // A listener registered on the backlinked object is called + // for an unrelated change on the an object of the same type!! + // This test exists only to document existing (but odd) behavior. + @Test + @RunTestInLooperThread + public void notification_notSentOnUnrelatedChangeModelObject() { + final Realm looperThreadRealm = looperThread.realm; + + looperThreadRealm.beginTransaction(); + AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); + AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); + looperThreadRealm.commitTransaction(); + + final AtomicInteger counter = new AtomicInteger(0); + RealmChangeListener listener = new RealmChangeListener() { + @Override + public void onChange(AllJavaTypes object) { + counter.incrementAndGet(); + } + }; + child.addChangeListener(listener); + + looperThreadRealm.beginTransaction(); + looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll().deleteAllFromRealm(); + looperThreadRealm.commitTransaction(); + + verifyPostConditions( + looperThreadRealm, + new PostConditions() { + public void run(Realm realm) { + assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + assertEquals(1, counter.get()); + } + }, + child, parent); + } + + // A listener registered on the backlinked field should be called when a commit adds a backlink + @Test + @RunTestInLooperThread + public void notification_onCommitRealmResults() { + final Realm looperThreadRealm = looperThread.realm; + + looperThreadRealm.beginTransaction(); + AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); + looperThreadRealm.commitTransaction(); + + final AtomicInteger counter = new AtomicInteger(0); + RealmChangeListener> listener = new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + counter.incrementAndGet(); + } + }; + child.getObjectParents().addChangeListener(listener); + + looperThreadRealm.beginTransaction(); + AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); + parent.setFieldObject(child); + looperThreadRealm.commitTransaction(); + + verifyPostConditions( + looperThreadRealm, + new PostConditions() { + public void run(Realm realm) { + assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + assertEquals(1, counter.get()); + } + }, + child, parent); + } + + // A listener registered on the backlinked field should not be called after the listener is removed + @Test + @RunTestInLooperThread + public void notification_notSentAfterUnregisterListenerRealmResults() { + final Realm looperThreadRealm = looperThread.realm; + + looperThreadRealm.beginTransaction(); + AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); + looperThreadRealm.commitTransaction(); + + RealmChangeListener> listener = new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + fail("Not expecting notification after unregister"); + } + }; + RealmResults objParents = child.getObjectParents(); + objParents.addChangeListener(listener); + objParents.removeChangeListener(listener); + + looperThreadRealm.beginTransaction(); + AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); + parent.setFieldObject(child); + looperThreadRealm.commitTransaction(); + + verifyPostConditions( + looperThreadRealm, + new PostConditions() { + public void run(Realm realm) { + assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + } + }, + child, parent); + } + + // A listener registered on the backlinked object should be called when a backlinked object is deleted + @Test + @RunTestInLooperThread + public void notification_onDeleteRealmResults() { + final Realm looperThreadRealm = looperThread.realm; + + looperThreadRealm.beginTransaction(); + AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); + AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); + parent.setFieldObject(child); + looperThreadRealm.commitTransaction(); + + final AtomicInteger counter = new AtomicInteger(0); + RealmChangeListener> listener = new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + counter.incrementAndGet(); + } + }; + child.getObjectParents().addChangeListener(listener); + + looperThreadRealm.beginTransaction(); + looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll().deleteAllFromRealm(); + looperThreadRealm.commitTransaction(); + + verifyPostConditions( + looperThreadRealm, + new PostConditions() { + public void run(Realm realm) { + assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + assertEquals(1, counter.get()); + } + }, + child, parent); + } + + // A listener registered on the backlinked object should not called for an unrelated change + @Test + @RunTestInLooperThread + public void notification_notSentOnUnrelatedChangeRealmResults() { + final Realm looperThreadRealm = looperThread.realm; + + looperThreadRealm.beginTransaction(); + AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); + AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); + looperThreadRealm.commitTransaction(); + + RealmChangeListener> listener = new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + fail("Not expecting notification after unregister"); + } + }; + child.getObjectParents().addChangeListener(listener); + + looperThreadRealm.beginTransaction(); + looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll().deleteAllFromRealm(); + looperThreadRealm.commitTransaction(); + + verifyPostConditions( + looperThreadRealm, + new PostConditions() { + public void run(Realm realm) { + assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + } + }, + child, parent); + } + + // Fields annotated with @LinkingObjects should not be affected by JSON updates + @Test + public void json_updateObject() { + realm.beginTransaction(); + AllJavaTypes child = realm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); + parent.setFieldObject(child); + realm.commitTransaction(); + + RealmResults parents = child.getObjectParents(); + assertNotNull(parents); + assertEquals(1, parents.size()); + assertTrue(parents.contains(parent)); + + realm.beginTransaction(); + try { + realm.createOrUpdateAllFromJson(AllJavaTypes.class, "[{ \"fieldId\" : 1, \"objectParents\" : null }]"); + } catch (RealmException e) { + fail("Failed loading JSON" + e); + } + realm.commitTransaction(); + + parents = child.getObjectParents(); + assertNotNull(parents); + assertEquals(1, parents.size()); + assertTrue(parents.contains(parent)); + } + + // Fields annotated with @LinkingObjects should not be affected by JSON updates + @Test + public void json_updateList() { + realm.beginTransaction(); + AllJavaTypes child = realm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); + parent.getFieldList().add(child); + realm.commitTransaction(); + + RealmResults parents = child.getListParents(); + assertNotNull(parents); + assertEquals(1, parents.size()); + assertTrue(parents.contains(parent)); + + realm.beginTransaction(); + try { + realm.createOrUpdateAllFromJson(AllJavaTypes.class, "[{ \"fieldId\" : 1, \"listParents\" : null }]"); + } catch (RealmException e) { + fail("Failed loading JSON" + e); + } + realm.commitTransaction(); + + parents = child.getListParents(); + assertNotNull(parents); + assertEquals(1, parents.size()); + assertTrue(parents.contains(parent)); + } + + // A JSON update should generate a notifcation + @Test + @RunTestInLooperThread + public void json_jsonUpdateCausesNotification() { + final Realm looperThreadRealm = looperThread.realm; + + looperThreadRealm.beginTransaction(); + AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 2); + parent.setFieldObject(child); + looperThreadRealm.commitTransaction(); + + RealmResults results = looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll(); + assertNotNull(results); + assertEquals(results.size(), 1); + child = results.first(); + + RealmResults parents = child.getObjectParents(); + assertNotNull(parents); + assertEquals(1, parents.size()); + + final AtomicInteger counter = new AtomicInteger(0); + RealmChangeListener listener = new RealmChangeListener() { + @Override + public void onChange(AllJavaTypes object) { + counter.incrementAndGet(); + } + }; + child.addChangeListener(listener); + + looperThreadRealm.beginTransaction(); + try { + looperThreadRealm.createOrUpdateAllFromJson(AllJavaTypes.class, "[{ \"fieldId\" : 2, \"fieldObject\" : null }]"); + } catch (RealmException e) { + fail("Failed loading JSON" + e); + } + looperThreadRealm.commitTransaction(); + + verifyPostConditions( + looperThreadRealm, + new PostConditions() { + public void run(Realm realm) { + RealmResults results = looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll(); + assertNotNull(results); + assertEquals(results.size(), 1); + AllJavaTypes child = results.first(); + + RealmResults parents = child.getObjectParents(); + assertNotNull(parents); + assertEquals(0, parents.size()); + assertEquals(1, counter.get()); + } + }, + child, parent); + } + + /** + * Table validation should fail if the backinked column already exists in the target table. + * The realm `backlinks-fieldInUse.realm` contains the classes `BacklinksSource` and `BacklinksTarget` + * except that in the definition of `BacklinksTarget`, the field parent is defined as: + *

            +     * {@code
            +     *     private RealmList parents;
            +     * }
            +     * 
            + */ + @Test + public void migration_backlinkedFieldInUse() { + final String realmName = "backlinks-fieldInUse.realm"; + + RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() + .name(realmName) + .schema(BacklinksSource.class, BacklinksTarget.class) + .build(); + + try { + configFactory.copyRealmFromAssets(context, realmName, realmName); + + Realm localRealm = Realm.getInstance(realmConfig); + localRealm.close(); + fail("A migration should have been required"); + } catch (IOException e) { + fail("Failed copying realm"); + } catch (RealmMigrationNeededException expected) { + assertTrue(expected.getMessage().contains("Field count is")); + } finally { + Realm.deleteRealm(realmConfig); + } + } + + /** + * Table validation should fail if the backinked column points to a non-existent class. + * The realm `backlinks-missingSourceClass.realm` contains two tables very like those + * defined by `BacklinksSource` and `BacklinksTarget`. In it, though, the source class + * is named XXXBacklinksSource, like so: + *
            +     * {@code
            +     * @LinkingObjects("child")
            +     *     private final RealmResults parents = null;
            +     * }
            +     * 
            + * If the both classes were used in the configuration, the test would fail because of the + * missing class. Since the configuration contains only the single class `BacklinksTarget`, + * basic validation passes. Backlink validation, however, should fail, seeking the + * `BacklinksSource` table. + */ + @Test + public void migration_backlinkedSourceClassDoesntExist() throws IOException { + final String realmName = "backlinks-missingSourceClass.realm"; + + RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() + .name(realmName) + .schema(BacklinksTarget.class) + .build(); + + try { + configFactory.copyRealmFromAssets(context, realmName, realmName); + + Realm localRealm = Realm.getInstance(realmConfig); + localRealm.close(); + fail("A migration should have been required"); + } catch (IOException e) { + fail("Failed copying realm"); + } catch (RealmMigrationNeededException expected) { + assertTrue(expected.getMessage().contains("Cannot find source class")); + } finally { + Realm.deleteRealm(realmConfig); + } + } + + /** + * Table validation should fail if the backlinked column points to a non-existent field in the source class. + * This test is quite a chore to construct! + * The realm `backlinks-missingSourceField.realm` was constructed with classes `BacklinksMissingFieldTarget` + * and `BacklinksMissingFieldSource`. They are identical in their definitions to `BacklinkSource` and + * `BacklinkTarget` except for their names. The library `backlinks-missing-field-source.jar` contains + * the class `BacklinksMissingFieldSource` and all of its annotation generated code. The library + * `backlinks-missing-field-target.jar` however, contains a version of `BacklinksMissingFieldTarget` that + * was compiled with its backlink field referring to a field in `BacklinksMissingFieldSource`, called + * `xxxchild`. Clearly, in order to compile successfully, the definition of `BacklinksMissingFieldSource` + * had to be changed accordingly. The modified version, however, is *NOT* the version that is in + * `backlinks-missing-field-source.jar`! + * So, now, the proxy in `backlinks-missing-field-source.jar` will correctly validate the its table + * (it generated it!). Similarly, the proxy in `backlinks-missing-field-target.jar` will successfully + * validate its table. If we have been living clean lives, though, the validator for + * `BacklinksMissingFieldTarget` should notice that there is no field named `BacklinksMissingFieldSource.xxxchild`. + */ + @Test + public void migration_backlinkedSourceFieldDoesntExist() { + final String realmName = "backlinks-missingSourceField.realm"; + + RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() + .name(realmName) + .modules(new BacklinksMissingFieldSourceModule(), new BacklinksMissingFieldTargetModule()) + .build(); + + try { + configFactory.copyRealmFromAssets(context, realmName, realmName); + + Realm localRealm = Realm.getInstance(realmConfig); + localRealm.close(); + fail("A migration should have been required"); + } catch (IOException e) { + fail("Failed copying realm"); + } catch (RealmMigrationNeededException expected) { + assertTrue(expected.getMessage().contains("Cannot find source field")); + } finally { + Realm.deleteRealm(realmConfig); + } + } + + /** + * Table validation should fail if the backinked column points to a field of the wrong type. + * This test is built in almost exactly the way as was `migration_backlinkedSourceFieldDoesntExist` + * The realm `backlinks-sourceFieldWrongType.realm` was constructed with classes `BacklinksWrongTypeTarget` + * and `BacklinksWrongTypeSource`. Again, these two classes are nearly identical in their counterparts + * `BacklinkSource` and `BacklinkTarget` except for their names. Unlike `BacklinkSource`, + * `BacklinksWrongTypeSource` has two fields, `child` and `childId`. The first is exactly as it is in + * `BacklinkSource`, the second is of type `Integer`. To construct `backlinks-wrong-type-target.jar` + * I reversed the names of the two fields in `BacklinkSource`, and made then adjusted `parents` in + * `BacklinkTarget` to point to `childId`. + * All of the proxies in in the two jars should correctly validate their tables. The backlink validation + * for `BacklinksWrongTypeTarget` should notice, though, that its `parents` field points to an object + * of the wrong type, `Integer`, instead of `BacklinksWrongTypeSource`. + */ + @Test + public void migration_backlinkedSourceFieldWrongType() { + final String realmName = "backlinks-sourceFieldWrongType.realm"; + + RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() + .name(realmName) + .modules(new BacklinksWrongTypeSourceModule(), new BacklinksWrongTypeTargetModule()) + .build(); + + try { + configFactory.copyRealmFromAssets(context, realmName, realmName); + + Realm localRealm = Realm.getInstance(realmConfig); + localRealm.close(); + fail("A migration should have been required"); + } catch (IOException e) { + fail("Failed copying realm"); + } catch (RealmMigrationNeededException expected) { + assertTrue(expected.getMessage().contains("is not a RealmObject type")); + } finally { + Realm.deleteRealm(realmConfig); + } + } + + // Distinct works for backlinks + @Test + public void query_multipleReferencesWithDistinct() { + realm.beginTransaction(); + AllJavaTypes child = realm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); + parent.getFieldList().add(child); + parent.getFieldList().add(child); + realm.commitTransaction(); + + assertEquals(2, child.getListParents().size()); + + RealmResults distinctParents = child.getListParents().where().distinct("fieldId"); + assertEquals(1, distinctParents.size()); + assertTrue(child.getListParents().contains(parent)); + } + + // Query on a field descriptor starting with a backlink + // The test objects are: + // gen1 + // / \ + // gen2A gen2B + // \\ // + // gen3 + // / = object ref + // // = list ref + @Test + @Ignore + public void query_startWithBacklink() { + realm.beginTransaction(); + AllJavaTypes gen1 = realm.createObject(AllJavaTypes.class, 10); + + AllJavaTypes gen2A = realm.createObject(AllJavaTypes.class, 1); + gen2A.setFieldObject(gen1); + + AllJavaTypes gen2B = realm.createObject(AllJavaTypes.class, 2); + gen2B.setFieldObject(gen1); + + AllJavaTypes gen3 = realm.createObject(AllJavaTypes.class, 3); + RealmList parents = gen3.getFieldList(); + parents.add(gen2A); + parents.add(gen2B); + + realm.commitTransaction(); + + RealmResults result = realm.where(AllJavaTypes.class) + .greaterThan("objectParents.fieldId", 1) + .findAll(); + assertEquals(1, result.size()); + assertTrue(result.contains(gen2B)); + } + + // Query on a field descriptor that ends with a backlink + // The test objects are: + // gen1 + // / \ + // gen2A gen2B + // \\ // + // gen3 + // / = object ref + // // = list ref + @Test + @Ignore + public void query_endWithBacklink() { + realm.beginTransaction(); + AllJavaTypes gen1 = realm.createObject(AllJavaTypes.class, 10); + + AllJavaTypes gen2A = realm.createObject(AllJavaTypes.class, 1); + gen2A.setFieldObject(gen1); + + AllJavaTypes gen2B = realm.createObject(AllJavaTypes.class, 2); + gen2B.setFieldObject(gen1); + + AllJavaTypes gen3 = realm.createObject(AllJavaTypes.class, 3); + RealmList parents = gen3.getFieldList(); + parents.add(gen2A); + parents.add(gen2B); + + realm.commitTransaction(); + + RealmResults result = realm.where(AllJavaTypes.class) + .isNotNull("objectParents.listParents") + .findAll(); + assertEquals(2, result.size()); + assertTrue(result.contains(gen2A)); + assertTrue(result.contains(gen2B)); + } + + // Query on a field descriptor that has a backlink in the middle + // The test objects are: + // gen1 + // / \ + // gen2A gen2B + // \\ // + // gen3 + // / = object ref + // // = list ref + @Test + @Ignore + public void query_backlinkInMiddle() { + realm.beginTransaction(); + AllJavaTypes gen1 = realm.createObject(AllJavaTypes.class, 10); + + AllJavaTypes gen2A = realm.createObject(AllJavaTypes.class, 1); + gen2A.setFieldObject(gen1); + + AllJavaTypes gen2B = realm.createObject(AllJavaTypes.class, 2); + gen2B.setFieldObject(gen1); + + AllJavaTypes gen3 = realm.createObject(AllJavaTypes.class, 3); + RealmList parents = gen3.getFieldList(); + parents.add(gen2A); + parents.add(gen2B); + + realm.commitTransaction(); + + RealmResults result = realm.where(AllJavaTypes.class) + .lessThan("objectParents.listParents.fieldId", 4) + .findAll(); + assertEquals(2, result.size()); + } + + // Based on a quick conversation with Christian Melchior and Mark Rowe, + // it appears that notifications are enqueued, briefly, on a non-Java + // thread. That makes their delivery onto the looper thread unpredictable. + // Fortunately, it appears that beginning a transaction forces the delivery of + // any outstanding notifications. + // The closure passed to this method will be run *after* the body of the test + // completes, and *after* the notifications have been delivered. Because the + // test method has been popped off the stack any objects referenced only from + // the stack are subject to GC. To hang on to them, until the test completes, + // just pass them to the final vararg to this method. + // @zaki50 has some evidence that notifications are delivered on a commit. + // If that is the case, we may be able to eliminate the ugly begin-commit + // that is the prologue to this method. + private void verifyPostConditions(final Realm realm, final PostConditions test, final Object... refs) { + realm.beginTransaction(); + realm.commitTransaction(); + + // Runnable is guaranteed to be enqueued on the Looper queue, after the notifications + looperThread.keepStrongReference.addAll(Arrays.asList(refs)); + looperThread.postRunnable( + new Runnable() { + @Override + public void run() { + test.run(realm); + looperThread.testComplete(); + } + }); + } +} + diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java new file mode 100644 index 0000000000..94fc826d44 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import dk.ilios.spanner.All; +import io.realm.entities.AllJavaTypes; +import io.realm.rule.TestRealmConfigurationFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@RunWith(AndroidJUnit4.class) +public class LinkingObjectsUnmanagedTests { + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + private Realm realm; + + @Before + public void setUp() { + RealmConfiguration realmConfig = configFactory.createConfiguration(); + realm = Realm.getInstance(realmConfig); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + // When unmanaged, an object's backlinks fields have their initialized value (probably null). + @Test + public void copyFromRealm() { + realm.beginTransaction(); + AllJavaTypes child = realm.createObject(AllJavaTypes.class, 1); + AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); + parent.setFieldObject(child); + realm.commitTransaction(); + assertEquals(1, child.getObjectParents().size()); + assertEquals(parent, child.getObjectParents().first()); + + AllJavaTypes unmanagedChild = realm.copyFromRealm(child); + assertEquals(new AllJavaTypes().getObjectParents(), unmanagedChild.getObjectParents()); + } + + // When managed, an object's backlinks fields get live. + @Test + public void copyToRealm() { + AllJavaTypes unmanagedChild = new AllJavaTypes(1); + + realm.beginTransaction(); + AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); + realm.commitTransaction(); + assertEquals(new AllJavaTypes().getObjectParents(), unmanagedChild.getObjectParents()); + + realm.beginTransaction(); + AllJavaTypes child = realm.copyToRealm(unmanagedChild); + parent.setFieldObject(child); + realm.commitTransaction(); + + RealmResults parents = child.getObjectParents(); + assertNotNull(parents); + assertEquals(1, parents.size()); + assertEquals(parent, parents.first()); + } + + // Test round-trip + @Test + public void copyToAndFromRealm() { + AllJavaTypes unmanagedChild = new AllJavaTypes(1); + + realm.beginTransaction(); + AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); + realm.commitTransaction(); + assertEquals(new AllJavaTypes().getObjectParents(), unmanagedChild.getObjectParents()); + + realm.beginTransaction(); + AllJavaTypes child = realm.copyToRealm(unmanagedChild); + parent.setFieldObject(child); + realm.commitTransaction(); + + RealmResults parents = child.getObjectParents(); + assertNotNull(parents); + assertEquals(1, parents.size()); + assertEquals(parent, parents.first()); + + unmanagedChild = realm.copyFromRealm(child); + assertEquals(unmanagedChild.getFieldId(), 1); + assertEquals(new AllJavaTypes().getObjectParents(), unmanagedChild.getObjectParents()); + + RealmResults queryResults = realm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll(); + assertEquals(1, queryResults.size()); + + child = queryResults.first(); + parents = child.getObjectParents(); + assertNotNull(parents); + assertEquals(1, parents.size()); + assertEquals(parent, parents.first()); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index 8447b2ce2e..387f098153 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -94,7 +94,7 @@ private void populateTestRealm(Realm realm, int objects) { allTypes.columnDouble = 3.1415 + i; allTypes.columnFloat = 1.234567f; allTypes.columnString = "test data "; - allTypes.columnByte = 0b0010_1010; + allTypes.columnByte = 0x2A; realm.copyToRealm(allTypes); } realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 2feb6876e7..4df192b9a3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -703,7 +703,7 @@ public void setter_link_objectFromAnotherThread() throws InterruptedException { final CountDownLatch createLatch = new CountDownLatch(1); final CountDownLatch testEndLatch = new CountDownLatch(1); - final AtomicReference objFromAnotherThread = new AtomicReference<>(); + final AtomicReference objFromAnotherThread = new AtomicReference(); java.lang.Thread thread = new java.lang.Thread() { @Override @@ -754,7 +754,7 @@ public void setter_list_withUnmanagedObject() { try { CyclicType target = realm.createObject(CyclicType.class); - RealmList list = new RealmList<>(); + RealmList list = new RealmList(); list.add(realm.createObject(CyclicType.class)); list.add(unmanaged); // List contains an unmanaged object list.add(realm.createObject(CyclicType.class)); @@ -778,7 +778,7 @@ public void setter_list_withDeletedObject() { CyclicType removed = realm.createObject(CyclicType.class); removed.deleteFromRealm(); - RealmList list = new RealmList<>(); + RealmList list = new RealmList(); list.add(realm.createObject(CyclicType.class)); list.add(removed); // List contains a deleted object. list.add(realm.createObject(CyclicType.class)); @@ -806,7 +806,7 @@ public void setter_list_withClosedObject() { try { CyclicType target = realm.createObject(CyclicType.class); - RealmList list = new RealmList<>(); + RealmList list = new RealmList(); list.add(realm.createObject(CyclicType.class)); list.add(closed); // List contains a closed object. list.add(realm.createObject(CyclicType.class)); @@ -835,7 +835,7 @@ public void setter_list_withObjectFromAnotherRealm() { try { CyclicType target = realm.createObject(CyclicType.class); - RealmList list = new RealmList<>(); + RealmList list = new RealmList(); list.add(realm.createObject(CyclicType.class)); list.add(objFromAnotherRealm); // List contains an object from another Realm. list.add(realm.createObject(CyclicType.class)); @@ -858,7 +858,7 @@ public void setter_list_withObjectFromAnotherThread() throws InterruptedExceptio final CountDownLatch createLatch = new CountDownLatch(1); final CountDownLatch testEndLatch = new CountDownLatch(1); - final AtomicReference objFromAnotherThread = new AtomicReference<>(); + final AtomicReference objFromAnotherThread = new AtomicReference(); java.lang.Thread thread = new java.lang.Thread() { @Override @@ -888,7 +888,7 @@ public void run() { try { CyclicType target = realm.createObject(CyclicType.class); - RealmList list = new RealmList<>(); + RealmList list = new RealmList(); list.add(realm.createObject(CyclicType.class)); list.add(objFromAnotherThread.get()); // List contains an object from another thread. list.add(realm.createObject(CyclicType.class)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java index 1546c13524..023cca9f73 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java @@ -63,7 +63,7 @@ public void validateTable_noDuplicateIndexInIndexFields() { CatRealmProxy.CatColumnInfo columnInfo; columnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); - final Set indexSet = new HashSet<>(); + final Set indexSet = new HashSet(); int indexCount = 0; indexSet.add(columnInfo.nameIndex); @@ -92,7 +92,7 @@ public void validateTable_noDuplicateIndexInIndicesMap() { CatRealmProxy.CatColumnInfo columnInfo; columnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); - final Set indexSet = new HashSet<>(); + final Set indexSet = new HashSet(); int indexCount = 0; // Gets index for each field and then put into set. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 78946420d6..4b30879628 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1136,7 +1136,7 @@ public void execute(Realm realm) { fieldObjectValue.setFieldInt(fieldObjectIntValue); obj.setFieldObject(fieldObjectValue); - final RealmList list = new RealmList<>(); + final RealmList list = new RealmList(); final RandomPrimaryKey listItem = new RandomPrimaryKey(); listItem.setFieldInt(fieldListIntValue); list.add(listItem); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index a4ddd7fb10..b57f4aa755 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -658,7 +658,7 @@ public void execute(Realm realm) { @Test public void executeTransaction_canceled() { - final AtomicReference thrownException = new AtomicReference<>(null); + final AtomicReference thrownException = new AtomicReference(null); assertEquals(0, realm.where(Owner.class).count()); try { @@ -1795,7 +1795,7 @@ public void getInstance_differentEncryptionKeys() { try { realm2 = Realm.getInstance(configFactory.createConfiguration(ENCRYPTED_REALM, key2)); } catch (Exception e) { - fail(); + fail("Unexpected exception: " + e); } finally { if (realm2 != null) { realm2.close(); @@ -2474,7 +2474,7 @@ public void copyToRealm_defaultValuesAreIgnored() { fieldObjectValue.setFieldInt(fieldObjectIntValue); obj.setFieldObject(fieldObjectValue); - final RealmList list = new RealmList<>(); + final RealmList list = new RealmList(); final RandomPrimaryKey listItem = new RandomPrimaryKey(); listItem.setFieldInt(fieldListIntValue); list.add(listItem); @@ -2529,7 +2529,7 @@ public void copyFromRealm_defaultValuesAreIgnored() { fieldObjectValue.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); obj.setFieldObject(fieldObjectValue); - final RealmList list = new RealmList<>(); + final RealmList list = new RealmList(); final RandomPrimaryKey listItem = new RandomPrimaryKey(); listItem.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2); list.add(listItem); diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java index 515eea403c..38e3f84443 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java @@ -20,27 +20,30 @@ import io.realm.RealmList; import io.realm.RealmObject; +import io.realm.RealmResults; import io.realm.annotations.Ignore; import io.realm.annotations.Index; +import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; public class AllJavaTypes extends RealmObject { public static final String CLASS_NAME = "AllJavaTypes"; - public static String FIELD_IGNORED = "fieldIgnored"; - public static String FIELD_STRING = "fieldString"; - public static String FIELD_SHORT = "fieldShort"; - public static String FIELD_INT = "fieldInt"; - public static String FIELD_LONG = "fieldLong"; - public static String FIELD_ID = "fieldId"; - public static String FIELD_BYTE = "fieldByte"; - public static String FIELD_FLOAT = "fieldFloat"; - public static String FIELD_DOUBLE = "fieldDouble"; - public static String FIELD_BOOLEAN = "fieldBoolean"; - public static String FIELD_DATE = "fieldDate"; - public static String FIELD_BINARY = "fieldBinary"; - public static String FIELD_OBJECT = "fieldObject"; - public static String FIELD_LIST = "fieldList"; + + public static final String FIELD_IGNORED = "fieldIgnored"; + public static final String FIELD_STRING = "fieldString"; + public static final String FIELD_SHORT = "fieldShort"; + public static final String FIELD_INT = "fieldInt"; + public static final String FIELD_LONG = "fieldLong"; + public static final String FIELD_ID = "fieldId"; + public static final String FIELD_BYTE = "fieldByte"; + public static final String FIELD_FLOAT = "fieldFloat"; + public static final String FIELD_DOUBLE = "fieldDouble"; + public static final String FIELD_BOOLEAN = "fieldBoolean"; + public static final String FIELD_DATE = "fieldDate"; + public static final String FIELD_BINARY = "fieldBinary"; + public static final String FIELD_OBJECT = "fieldObject"; + public static final String FIELD_LIST = "fieldList"; public static final String INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT = AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY; public static final String[] INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT = new String[]{FIELD_OBJECT + "." + FIELD_BINARY, FIELD_OBJECT + "." + FIELD_OBJECT, FIELD_OBJECT + "." + FIELD_LIST}; @@ -60,6 +63,12 @@ public class AllJavaTypes extends RealmObject { private AllJavaTypes fieldObject; private RealmList fieldList; + @LinkingObjects("fieldObject") + private final RealmResults objectParents = null; + + @LinkingObjects("fieldList") + private final RealmResults listParents = null; + public AllJavaTypes() { } @@ -180,4 +189,12 @@ public RealmList getFieldList() { public void setFieldList(RealmList columnRealmList) { this.fieldList = columnRealmList; } + + public RealmResults getObjectParents() { + return objectParents; + } + + public RealmResults getListParents() { + return listParents; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java new file mode 100644 index 0000000000..1419d5368b --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; + +public class BacklinksSource extends RealmObject { + private BacklinksTarget child; + + public BacklinksTarget getChild() { + return child; + } + + public void setChild(BacklinksTarget child) { + this.child = child; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java new file mode 100644 index 0000000000..ddb0ea6f80 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class BacklinksTarget extends RealmObject { + private int id; + + @LinkingObjects("child") + private final RealmResults parents = null; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public RealmResults getParents() { + return parents; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index 65e81bdea4..979717e96a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -32,9 +32,13 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import io.realm.DynamicRealm; import io.realm.Realm; import io.realm.RealmConfiguration; +import io.realm.RealmMigration; +import io.realm.RealmObject; import io.realm.TestHelper; +import io.realm.annotations.RealmModule; import static org.junit.Assert.assertTrue; @@ -45,8 +49,8 @@ * The temp directory will be deleted regardless if the {@link Realm#deleteRealm(RealmConfiguration)} fails or not. */ public class TestRealmConfigurationFactory extends TemporaryFolder { - private Map map = new ConcurrentHashMap(); - private Set configurations = Collections.newSetFromMap(map); + private final Map map = new ConcurrentHashMap(); + private final Set configurations = Collections.newSetFromMap(map); protected boolean unitTestFailed = false; @Override @@ -94,50 +98,58 @@ protected void after() { } } + // This builder creates a configuration that is *NOT* managed. + // You have to delete it yourself. + public RealmConfiguration.Builder createConfigurationBuilder() { + return new RealmConfiguration.Builder().directory(getRoot()); + } + public RealmConfiguration createConfiguration() { - RealmConfiguration configuration = new RealmConfiguration.Builder() - .directory(getRoot()) - .build(); + return createConfiguration(null); + } - configurations.add(configuration); - return configuration; + public RealmConfiguration createConfiguration(String name) { + return createConfiguration(null, name); } public RealmConfiguration createConfiguration(String subDir, String name) { - final File folder = new File(getRoot(), subDir); - assertTrue(folder.mkdirs()); - RealmConfiguration configuration = new RealmConfiguration.Builder() - .directory(folder) - .name(name) - .build(); - - configurations.add(configuration); - return configuration; + return createConfiguration(subDir, name, null, null); } - public RealmConfiguration createConfiguration(String name) { - RealmConfiguration configuration = new RealmConfiguration.Builder() - .directory(getRoot()) - .name(name) - .build(); + public RealmConfiguration createConfiguration(String name, byte[] key) { + return createConfiguration(null, name, null, key); + } - configurations.add(configuration); - return configuration; + public RealmConfiguration createConfiguration(String name, Object module) { + return createConfiguration(null, name, module, null); } - public RealmConfiguration createConfiguration(String name, byte[] key) { - RealmConfiguration configuration = new RealmConfiguration.Builder() - .directory(getRoot()) - .name(name) - .encryptionKey(key) - .build(); + public RealmConfiguration createConfiguration(String subDir, String name, Object module, byte[] key) { + RealmConfiguration.Builder builder = createConfigurationBuilder(); + + File folder = getRoot(); + if (subDir != null) { + folder = new File(folder, subDir); + assertTrue(folder.mkdirs()); + } + builder.directory(folder); + + if (name != null) { + builder.name(name); + } + if (module != null) { + builder.modules(module); + } + + if (key != null) { + builder.encryptionKey(key); + } + + RealmConfiguration configuration = builder.build(); configurations.add(configuration); - return configuration; - } - public RealmConfiguration.Builder createConfigurationBuilder() { - return new RealmConfiguration.Builder().directory(getRoot()); + return configuration; } // Copies a Realm file from assets to temp dir diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java index 8143418f05..c0a5d4fffc 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java @@ -138,7 +138,7 @@ public void custom() { @Test public void custom_invalidUserName() { - Map userInfo = new HashMap<>(); + Map userInfo = new HashMap(); String[] invalidInput = {null, ""}; for (String username : invalidInput) { @@ -152,7 +152,7 @@ public void custom_invalidUserName() { @Test public void custom_invalidProvider() { - Map userInfo = new HashMap<>(); + Map userInfo = new HashMap(); try { SyncCredentials.custom("foo", null, userInfo); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 0c3426811f..057f7a79ba 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -449,3 +449,26 @@ JNIEXPORT jbyte JNICALL Java_io_realm_internal_Collection_nativeGetMode(JNIEnv* CATCH_STD() return -1; // Invalid mode value } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResultsFromBacklinks(JNIEnv *env, jclass, + jlong shared_realm_ptr, + jlong row_ptr, + jlong src_table_ptr, + jlong src_col_index) +{ + TR_ENTER_PTR(row_ptr) + Row* row = ROW(row_ptr); + if (!ROW_VALID(env, row)) { + return reinterpret_cast(nullptr); + } + try { + Table* src_table = TBL(src_table_ptr); + TableView backlink_view = row->get_table()->get_backlink_view(row->get_index(), src_table, src_col_index); + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + Results results(shared_realm, std::move(backlink_view)); + auto wrapper = new ResultsWrapper(results); + return reinterpret_cast(wrapper); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 7c604b59b9..e48af97fef 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -343,11 +343,14 @@ private static void initializeRealm(Realm realm) { final Set> modelClasses = mediator.getModelClasses(); final Map, ColumnInfo> columnInfoMap = new HashMap<>(modelClasses.size()); - for (Class modelClass : modelClasses) { - // Creates and validates table. - if (unversioned) { + if (unversioned) { + // Create all of the tables. + for (Class modelClass : modelClasses) { mediator.createTable(modelClass, realm.sharedRealm); } + } + for (Class modelClass : modelClasses) { + // Now that they have all been created, validate them. columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index f52e59c2db..03266e7308 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -273,7 +273,7 @@ protected RealmObjectSchema add(Property property) { private Set getProperties() { if (realm == null) { long[] ptrs = nativeGetProperties(nativePtr); - Set properties = new LinkedHashSet<>(ptrs.length); + Set properties = new LinkedHashSet(ptrs.length); for (int i = 0; i < ptrs.length; i++) { properties.add(new Property(ptrs[i])); } @@ -556,7 +556,7 @@ public String getPrimaryKey() { */ public Set getFieldNames() { int columnCount = (int) table.getColumnCount(); - Set columnNames = new LinkedHashSet<>(columnCount); + Set columnNames = new LinkedHashSet(columnCount); for (int i = 0; i < columnCount; i++) { columnNames.add(table.getColumnName(i)); } @@ -604,7 +604,7 @@ private void addModifiers(String fieldName, FieldAttribute[] attributes) { if (indexAdded) { table.removeSearchIndex(columnIndex); } - throw e; + throw (RuntimeException) e; } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index e6a55ab77e..c041e80ff2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -20,7 +20,10 @@ import android.os.Looper; import io.realm.internal.Collection; +import io.realm.internal.Row; import io.realm.internal.SortDescriptor; +import io.realm.internal.Table; +import io.realm.internal.UncheckedRow; import rx.Observable; /** @@ -51,6 +54,18 @@ * @see Realm#executeTransaction(Realm.Transaction) */ public class RealmResults extends OrderedRealmCollectionImpl { + static RealmResults createBacklinkResults(BaseRealm realm, Row row, Class srcTableType, String srcFieldName) { + if (!(row instanceof UncheckedRow)) { + throw new IllegalArgumentException("Row is " + row.getClass()); + } + UncheckedRow uncheckedRow = (UncheckedRow) row; + Table srcTable = realm.getSchema().getTable(srcTableType); + return new RealmResults( + realm, + Collection.createBacklinksCollection(realm.sharedRealm, uncheckedRow, srcTable, srcFieldName), + srcTableType); + } + RealmResults(BaseRealm realm, Collection collection, Class clazz) { super(realm, collection, clazz); diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 2584b4278f..61e067dd23 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -131,14 +131,14 @@ public RealmObjectSchema get(String className) { public Set getAll() { if (realm == null) { long[] ptrs = nativeGetAll(nativePtr); - Set schemas = new LinkedHashSet<>(ptrs.length); + Set schemas = new LinkedHashSet(ptrs.length); for (int i = 0; i < ptrs.length; i++) { schemas.add(new RealmObjectSchema(ptrs[i])); } return schemas; } else { int tableCount = (int) realm.sharedRealm.size(); - Set schemas = new LinkedHashSet<>(tableCount); + Set schemas = new LinkedHashSet(tableCount); for (int i = 0; i < tableCount; i++) { String tableName = realm.sharedRealm.getTableName(i); if (!Table.isModelTable(tableName)) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 704e8cbdff..d2d43f27b8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -318,6 +318,15 @@ static Mode getByValue(byte value) { } } + public static Collection createBacklinksCollection(SharedRealm realm, UncheckedRow row, Table srcTable, String srcFieldName) { + long backlinksPtr = nativeCreateResultsFromBacklinks( + realm.getNativePtr(), + row.getNativePtr(), + srcTable.getNativePtr(), + srcTable.getColumnIndex(srcFieldName)); + return new Collection(realm, row.getTable(), backlinksPtr, true); + } + public Collection(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { query.validateQuery(); @@ -355,12 +364,16 @@ public Collection(SharedRealm sharedRealm, LinkView linkView, SortDescriptor sor } private Collection(SharedRealm sharedRealm, Table table, long nativePtr) { + this(sharedRealm, table, nativePtr, false); + } + + private Collection(SharedRealm sharedRealm, Table table, long nativePtr, boolean loaded) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.table = table; this.nativePtr = nativePtr; this.context.addReference(this); - this.loaded = false; + this.loaded = loaded; } public Collection createSnapshot() { @@ -560,4 +573,5 @@ private static native long nativeCreateResultsFromLinkView(long sharedRealmNativ private static native long nativeIndexOfBySourceRowIndex(long nativePtr, long sourceRowIndex); private static native boolean nativeIsValid(long nativePtr); private static native byte nativeGetMode(long nativePtr); + private static native long nativeCreateResultsFromBacklinks(long sharedRealmNativePtr, long rowNativePtr, long srcTableNativePtr, long srColIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java index 0e89e649ed..8f8dd28694 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java @@ -69,7 +69,7 @@ public ColumnIndices clone() { } private Map, ColumnInfo> duplicateColumnInfoMap() { - final Map, ColumnInfo> copy = new HashMap<>(); + final Map, ColumnInfo> copy = new HashMap, ColumnInfo>(); for (Map.Entry, ColumnInfo> entry : classes.entrySet()) { copy.put(entry.getKey(), entry.getValue().clone()); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index a5b0b5c35c..4a8011dff5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -237,7 +237,7 @@ public void renameColumn(long columnIndex, String newName) { // We failed to rename the pk meta table. roll back the column name, not pk meta table // then rethrow. nativeRenameColumn(nativePtr, columnIndex, oldName); - throw e; + throw new RuntimeException(e); } } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 9c99b7a621..c583278793 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -23,18 +23,18 @@ /** * Wrapper around a Row in Realm Core. * - * IMPORTANT: All access to methods using this class are non-checking. Safety guarantees are given by the annotation - * processor and {@link RealmProxyMediator#validateTable(Class, SharedRealm)} which is called before the typed - * API can be used. + * IMPORTANT: All access to methods using this class are non-checking. Safety guarantees are given by the + * annotation processor and {@link RealmProxyMediator#validateTable(Class, SharedRealm, boolean)} + * which is called before the typed API can be used. * * For low-level access to Row data where error checking is required, use {@link CheckedRow}. */ public class UncheckedRow implements NativeObject, Row { + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - final Context context; // This is only kept because for now it's needed by the constructor of LinkView + private final Context context; // This is only kept because for now it's needed by the constructor of LinkView private final Table parent; private final long nativePtr; - private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); UncheckedRow(Context context, Table parent, long nativePtr) { this.context = context; diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index 0800ae230b..eadf8ba645 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -49,7 +49,7 @@ public class CompositeMediator extends RealmProxyMediator { private final Map, RealmProxyMediator> mediators; public CompositeMediator(RealmProxyMediator... mediators) { - final HashMap, RealmProxyMediator> tempMediators = new HashMap<>(); + final HashMap, RealmProxyMediator> tempMediators = new HashMap, RealmProxyMediator>(); if (mediators != null) { for (RealmProxyMediator mediator : mediators) { for (Class realmClass : mediator.getModelClasses()) { diff --git a/realm/realm-library/testLibs/backlinks-missing-field-source.jar b/realm/realm-library/testLibs/backlinks-missing-field-source.jar new file mode 100644 index 0000000000000000000000000000000000000000..a34490c3caf1c66025dd9d82dd2ae768a37c7750 GIT binary patch literal 12289 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 literal 0 HcmV?d00001 diff --git a/realm/realm-library/testLibs/backlinks-missing-field-target.jar b/realm/realm-library/testLibs/backlinks-missing-field-target.jar new file mode 100644 index 0000000000000000000000000000000000000000..91a513c5b33be31d6ef4a1e740f5d3f33200a819 GIT binary patch literal 11892 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�yx+9wuLp4~0eyWbI@<{{ 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 KvFoXSefuw5Tx|OQ literal 0 HcmV?d00001 diff --git a/realm/realm-library/testLibs/backlinks-wrong-type-source.jar b/realm/realm-library/testLibs/backlinks-wrong-type-source.jar new file mode 100644 index 0000000000000000000000000000000000000000..67cbdbefea564e94427a395bffe7794922812413 GIT binary patch literal 13346 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%0DN;Y$n$;`A!z+8Ji&-&i}m;?LBAsKr1qa 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 literal 0 HcmV?d00001 diff --git a/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldSource.java b/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldSource.java new file mode 100644 index 0000000000..5564843455 --- /dev/null +++ b/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldSource.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; + +public class BacklinksMissingFieldSource extends RealmObject { + private BacklinksMissingFieldTarget child; + + public BacklinksMissingFieldTarget getChild() { + return child; + } + + public void setChild(BacklinksMissingFieldTarget child) { + this.child = child; + } +} diff --git a/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldSourceModule.java b/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldSourceModule.java new file mode 100644 index 0000000000..20c860a076 --- /dev/null +++ b/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldSourceModule.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = {BacklinksMissingFieldSource.class}) +public class BacklinksMissingFieldSourceModule { +} diff --git a/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldTarget.java b/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldTarget.java new file mode 100644 index 0000000000..eeb4884a15 --- /dev/null +++ b/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldTarget.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class BacklinksMissingFieldTarget extends RealmObject { + private int id; + + @LinkingObjects("child") + private final RealmResults parents = null; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public RealmResults getParents() { + return parents; + } +} diff --git a/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldTargetModule.java b/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldTargetModule.java new file mode 100644 index 0000000000..d34d5076d8 --- /dev/null +++ b/realm/tools/backlink-ut-source/missingField/source/io/realm/entities/BacklinksMissingFieldTargetModule.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = {BacklinksMissingFieldTarget.class}) +public class BacklinksMissingFieldTargetModule { +} diff --git a/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldSource.java b/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldSource.java new file mode 100644 index 0000000000..1f10ebc756 --- /dev/null +++ b/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldSource.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; + +public class BacklinksMissingFieldSource extends RealmObject { + private BacklinksMissingFieldTarget xxxchild; + + public BacklinksMissingFieldTarget getChild() { + return xxxchild; + } + + public void setChild(BacklinksMissingFieldTarget child) { + this.xxxchild = child; + } +} diff --git a/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldSourceModule.java b/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldSourceModule.java new file mode 100644 index 0000000000..20c860a076 --- /dev/null +++ b/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldSourceModule.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = {BacklinksMissingFieldSource.class}) +public class BacklinksMissingFieldSourceModule { +} diff --git a/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldTarget.java b/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldTarget.java new file mode 100644 index 0000000000..47c2894d3e --- /dev/null +++ b/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldTarget.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class BacklinksMissingFieldTarget extends RealmObject { + private int id; + + @LinkingObjects("xxxchild") + private final RealmResults parents = null; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public RealmResults getParents() { + return parents; + } +} diff --git a/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldTargetModule.java b/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldTargetModule.java new file mode 100644 index 0000000000..d34d5076d8 --- /dev/null +++ b/realm/tools/backlink-ut-source/missingField/target/io/realm/entities/BacklinksMissingFieldTargetModule.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = {BacklinksMissingFieldTarget.class}) +public class BacklinksMissingFieldTargetModule { +} diff --git a/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeSource.java b/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeSource.java new file mode 100644 index 0000000000..85dc2dd094 --- /dev/null +++ b/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeSource.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; + +public class BacklinksWrongTypeSource extends RealmObject { + private Integer childId; + + private BacklinksWrongTypeTarget child; + + public Integer getChildId() { + return childId; + } + + public void setChildId(Integer childId) { + this.childId = childId; + } + + public BacklinksWrongTypeTarget getChild() { + return child; + } + + public void setChild(BacklinksWrongTypeTarget child) { + this.child = child; + } +} diff --git a/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeSourceModule.java b/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeSourceModule.java new file mode 100644 index 0000000000..8e4a3f4f5b --- /dev/null +++ b/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeSourceModule.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = {BacklinksWrongTypeSource.class}) +public class BacklinksWrongTypeSourceModule { +} diff --git a/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeTarget.java b/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeTarget.java new file mode 100644 index 0000000000..243d25aea4 --- /dev/null +++ b/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeTarget.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class BacklinksWrongTypeTarget extends RealmObject { + private int id; + + @LinkingObjects("child") + private final RealmResults parents = null; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public RealmResults getParents() { + return parents; + } +} diff --git a/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeTargetModule.java b/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeTargetModule.java new file mode 100644 index 0000000000..b9275496a7 --- /dev/null +++ b/realm/tools/backlink-ut-source/wrongType/source/io/realm/entities/BacklinksWrongTypeTargetModule.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = {BacklinksWrongTypeTarget.class}) +public class BacklinksWrongTypeTargetModule { +} diff --git a/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeSource.java b/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeSource.java new file mode 100644 index 0000000000..477c236a6e --- /dev/null +++ b/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeSource.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; + +public class BacklinksWrongTypeSource extends RealmObject { + private Integer child; + + private BacklinksWrongTypeTarget childId; + + public Integer getChildId() { + return child; + } + + public void setChildId(Integer childId) { + this.child = childId; + } + + public BacklinksWrongTypeTarget getChild() { + return childId; + } + + public void setChild(BacklinksWrongTypeTarget child) { + this.childId = childId; + } +} diff --git a/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeSourceModule.java b/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeSourceModule.java new file mode 100644 index 0000000000..8e4a3f4f5b --- /dev/null +++ b/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeSourceModule.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = {BacklinksWrongTypeSource.class}) +public class BacklinksWrongTypeSourceModule { +} diff --git a/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeTarget.java b/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeTarget.java new file mode 100644 index 0000000000..ac6451d267 --- /dev/null +++ b/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeTarget.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class BacklinksWrongTypeTarget extends RealmObject { + private int id; + + @LinkingObjects("childId") + private final RealmResults parents = null; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public RealmResults getParents() { + return parents; + } +} diff --git a/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeTargetModule.java b/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeTargetModule.java new file mode 100644 index 0000000000..b9275496a7 --- /dev/null +++ b/realm/tools/backlink-ut-source/wrongType/target/io/realm/entities/BacklinksWrongTypeTargetModule.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = {BacklinksWrongTypeTarget.class}) +public class BacklinksWrongTypeTargetModule { +} diff --git a/realm/tools/bin/cgen b/realm/tools/bin/cgen new file mode 100755 index 0000000000..806974356c --- /dev/null +++ b/realm/tools/bin/cgen @@ -0,0 +1,55 @@ +#!/bin/bash + +# Customize as necessary +REALM_JAVA=~/Working/java +GRADLE_CACHE=~/.gradle +ANDROID_SDK=~/Library/Android/sdk + +if [ $# -lt 2 ] ; then + echo "Usage: $0 ..." + exit 1 +fi + +TARGET_DIR="$1" +shift + +if [ ! -d "$TARGET_DIR" ] ; then + echo "$TARGET_DIR is not a directory" + exit 1 +fi + +REALM_ANNOTATIONS=`find "$REALM_JAVA/realm-annotations/build" -name 'realm-annotations-*.jar'` +if [ ! -f "$REALM_ANNOTATIONS" ] ; then + echo "Cannot find the Realm Annotations jar in $REALM_JAVA" + exit 1 +fi + +REALM_ANNOTATION_PROCESSOR=`find "$REALM_JAVA/realm/realm-annotations-processor" -name 'realm-annotations-processor-*.jar'` +if [ ! -f "$REALM_ANNOTATION_PROCESSOR" ] ; then + echo "Cannot find the Realm Annotation Processor jar in $REALM_JAVA" + exit 1 +fi + +REALM_CLASSES="$REALM_JAVA/realm/realm-library/build/intermediates/classes/base/release" +if [ ! -d "$REALM_CLASSES" ] ; then + echo "Cannot find the Realm classes in $REALM_JAVA" + exit 1 +fi + +JAVAWRITER=`find "$GRADLE_CACHE/caches/jars"* -name 'javawriter-2.5*.jar' | head -1` +if [ ! -f "$JAVAWRITER" ] ; then + echo "Cannot find JavaWriter jar in $GRADLE_CACHE" + exit 1 +fi + +CLASSPATH="$REALM_ANNOTATION_PROCESSOR":"$JAVAWRITER":"$REALM_ANNOTATIONS":"$REALM_CLASSES" + +javac \ + -d "$TARGET_DIR" \ + -bootclasspath "$ANDROID_SDK/platforms/android-24/android.jar" \ + -source 7 \ + -target 7 \ + -cp "$CLASSPATH" \ + -processor io.realm.processor.RealmProcessor \ + $* + From 47e98a4633ff72ead84fe144ee4d500894e3f24d Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 15 Mar 2017 15:45:05 +0900 Subject: [PATCH 0555/2110] Remove unnecessary array allocations (#4318) --- .../src/main/java/io/realm/Realm.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index a61d98c3e1..7df9649f9e 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1421,8 +1421,8 @@ public void run() { return; } - final SharedRealm.VersionID[] versionID = new SharedRealm.VersionID[1]; - final Throwable[] exception = new Throwable[1]; + SharedRealm.VersionID versionID = null; + Throwable exception = null; final Realm bgRealm = Realm.getInstance(realmConfiguration); bgRealm.beginTransaction(); @@ -1436,18 +1436,19 @@ public void run() { bgRealm.commitTransaction(); // The bgRealm needs to be closed before post event to caller's handler to avoid concurrency // problem. This is currently guaranteed by posting callbacks later below. - versionID[0] = bgRealm.sharedRealm.getVersionID(); + versionID = bgRealm.sharedRealm.getVersionID(); } catch (final Throwable e) { - exception[0] = e; + exception = e; } finally { // SharedGroup::close() will cancel the transaction if needed. bgRealm.close(); } - final Throwable backgroundException = exception[0]; + final Throwable backgroundException = exception; + final SharedRealm.VersionID backgroundVersionID = versionID; // Cannot be interrupted anymore. if (canDeliverNotification ) { - if (versionID[0] != null && onSuccess != null) { + if (backgroundVersionID != null && onSuccess != null) { realmNotifier.post(new Runnable() { @Override public void run() { @@ -1458,7 +1459,7 @@ public void run() { return; } - if (sharedRealm.getVersionID().compareTo(versionID[0]) < 0) { + if (sharedRealm.getVersionID().compareTo(backgroundVersionID) < 0) { sharedRealm.realmNotifier.addTransactionCallback(new Runnable() { @Override public void run() { From 3434e4d684342ce43f315e5a8b1c7caca749afe3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 15 Mar 2017 15:37:04 +0100 Subject: [PATCH 0556/2110] Correctly report Client Reset (#4313) --- CHANGELOG.md | 5 ++ .../java/io/realm/SessionTests.java | 85 +++++++++++++++++- .../java/io/realm/SyncConfigurationTests.java | 10 +++ .../java/io/realm/util/SyncTestUtils.java | 1 + .../realm-library/src/main/cpp/CMakeLists.txt | 8 +- .../main/cpp/io_realm_ClientResetHandler.cpp | 41 +++++++++ .../main/cpp/io_realm_RealmFileUserStore.cpp | 2 +- .../src/main/cpp/io_realm_SyncManager.cpp | 22 ++++- .../cpp/io_realm_internal_SharedRealm.cpp | 16 +++- .../cpp/io_realm_internal_UncheckedRow.cpp | 2 +- .../src/main/cpp/jni_util/jni_utils.cpp | 2 +- .../java/io/realm/ClientResetHandler.java | 86 +++++++++++++++++++ .../objectServer/java/io/realm/ErrorCode.java | 1 + .../java/io/realm/SyncManager.java | 21 +++++ .../java/io/realm/SyncSession.java | 54 +++++++++++- .../objectServer/java/io/realm/SyncUser.java | 5 ++ .../java/io/realm/objectserver/AuthTests.java | 6 ++ .../objectserver/ManagementRealmTests.java | 7 +- .../objectserver/ProcessCommitTests.java | 14 ++- 19 files changed, 370 insertions(+), 18 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/io_realm_ClientResetHandler.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/ClientResetHandler.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f69349c77..42942e622a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 3.1.0 (YYYY-MM-DD) +### Breaking Changes + +* [ObjectServer] Added `onClientResetRequired(SyncSession, ClientResetHandler)` method to the `ErrorHandler` interface (#4080). + ### Enhancements * Now `targetSdkVersion` is 25. @@ -10,6 +14,7 @@ * Linking objects are not yet supported on dynamic objects * Migration for linking objects is not yet supported. * Backlink verification is incomplete. Evil code can cause native crashes. +* [ObjectServer] In case of a Client Reset, information about the location of the backed up Realm file is now reported through the `ErrorHandler` interface (#4080). ### Bug Fixes diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 537dfb67e5..2c53ce2bd5 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -23,10 +23,15 @@ import org.junit.Test; import org.junit.runner.RunWith; -import io.realm.rule.TestRealmConfigurationFactory; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.TestSyncConfigurationFactory; import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) public class SessionTests { @@ -37,7 +42,10 @@ public class SessionTests { private SyncUser user; @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); @Before public void setUp() { @@ -52,4 +60,77 @@ public void get_syncValues() { assertEquals(user, session.getUser()); assertEquals(configuration, session.getConfiguration()); } + + // Check that a Client Reset is correctly reported. + @Test + @RunTestInLooperThread + public void errorHandler_clientResetReported() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user , url) + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail("Wrong error " + error.toString()); + } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + String filePathFromError = handler.getOriginalFile().getAbsolutePath(); + String filePathFromConfig = session.getConfiguration().getPath(); + assertEquals(filePathFromError, filePathFromConfig); + assertFalse(handler.getBackupFile().exists()); + assertTrue(handler.getOriginalFile().exists()); + looperThread.testComplete(); + } + }) + .build(); + + Realm realm = Realm.getInstance(config); + looperThread.testRealms.add(realm); + + // Trigger error + SyncManager.simulateClientReset(SyncManager.getSession(config)); + } + + // Check that we can manually execute the Client Reset. + @Test + @RunTestInLooperThread + public void errorHandler_manualExecuteClientReset() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user , url) + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail("Wrong error " + error.toString()); + } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + try { + handler.executeClientReset(); + fail("All Realms should be closed before executing Client Reset can be allowed"); + } catch(IllegalStateException ignored) { + } + + // Execute Client Reset + looperThread.testRealms.get(0).close(); + handler.executeClientReset(); + + // Validate that files have been moved + assertFalse(handler.getOriginalFile().exists()); + assertTrue(handler.getBackupFile().exists()); + looperThread.testComplete(); + } + }) + .build(); + + Realm realm = Realm.getInstance(config); + looperThread.testRealms.add(realm); + + // Trigger error + SyncManager.simulateClientReset(SyncManager.getSession(config)); + } + } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 3b7a2a466f..2e294ffe75 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -225,6 +225,11 @@ public void errorHandler() { public void onError(SyncSession session, ObjectServerError error) { } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + + } }; SyncConfiguration config = builder.errorHandler(errorHandler).build(); assertEquals(errorHandler, config.getErrorHandler()); @@ -238,6 +243,11 @@ public void errorHandler_fromSyncManager() { public void onError(SyncSession session, ObjectServerError error) { } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + + } }; SyncManager.setDefaultSessionErrorHandler(errorHandler); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index cb296dc293..de368019a5 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -24,6 +24,7 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; +import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.objectserver.ObjectServerUser; diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 1b44bb360b..0b64588552 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -45,7 +45,9 @@ set(classes_LIST set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST - io.realm.SyncManager io.realm.SyncSession io.realm.RealmFileUserStore) + io.realm.ClientResetHandler io.realm.RealmFileUserStore + io.realm.SyncManager io.realm.SyncSession + ) endif() create_javah(TARGET jni_headers CLASSES ${classes_LIST} @@ -153,9 +155,11 @@ file(GLOB jni_SRC # Those source file are only needed for sync. if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetHandler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFileUserStore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFileUserStore.cpp) + ) endif() # Object Store source files diff --git a/realm/realm-library/src/main/cpp/io_realm_ClientResetHandler.cpp b/realm/realm-library/src/main/cpp/io_realm_ClientResetHandler.cpp new file mode 100644 index 0000000000..61a72c1145 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_ClientResetHandler.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 + +#include + +#include "util.hpp" +#include "io_realm_ClientResetHandler.h" + +using namespace realm; + +JNIEXPORT void JNICALL Java_io_realm_ClientResetHandler_nativeExecuteClientReset(JNIEnv* env, jobject, + jstring localRealmPath) +{ + TR_ENTER() + try { + JStringAccessor local_realm_path(env, localRealmPath); + if (!SyncManager::shared().immediately_run_file_actions(std::string(local_realm_path))) { + ThrowException( + env, IllegalState, + concat_stringdata("Realm was not configured correctly. Client Reset could not be run for Realm at: ", + local_realm_path)); + return; + } + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 1a1d131482..313a2d169b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -104,4 +104,4 @@ JNIEXPORT jobjectArray JNICALL Java_io_realm_RealmFileUserStore_nativeGetAllUser return users_token; } return nullptr; -} \ No newline at end of file +} diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index fb0e261c2f..2ac7d2ce07 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -28,6 +28,7 @@ #include "io_realm_SyncManager.h" #include "object-store/src/sync/sync_manager.hpp" +#include "object-store/src/sync/sync_session.hpp" #include "binding_callback_thread_observer.hpp" #include "util.hpp" @@ -79,7 +80,6 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient(JNIE JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass) { - TR_ENTER() try { SyncManager::shared().reset_for_testing(); @@ -96,3 +96,23 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeConfigureMetaDataSystem(J } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeSimulateSyncError(JNIEnv* env, jclass, jstring localRealmPath, + jint errorCode, jstring errorMessage, + jboolean isFatal) +{ + TR_ENTER() + try { + JStringAccessor local_realm_path(env, localRealmPath); + JStringAccessor error_message(env, errorMessage); + + auto session = SyncManager::shared().get_existing_active_session(local_realm_path); + if (!session) { + ThrowException(env, IllegalArgument, concat_stringdata("Session not found: ", local_realm_path)); + return; + } + std::error_code code = std::error_code{static_cast(errorCode), realm::sync::protocol_error_category()}; + SyncSession::OnlyForTesting::handle_error(*session, {code, std::string(error_message), to_bool(isFatal)}); + } + CATCH_STD() +} \ No newline at end of file diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index f89add3683..531611f4d1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -85,10 +85,19 @@ class JniConfigWrapper { auto error_handler = [=](std::shared_ptr session, SyncError error) { realm::jni_util::Log::d("error_handler lambda invoked"); - JNIEnv* env = realm::jni_util::JniUtils::get_env(true); + auto error_message = error.message; + auto error_code = error.error_code.value(); + if (error.is_client_reset_requested()) { + // Hack the error message to send information about the location of the backup. + // If more uses of the user_info map surfaces. Refactor this to send the full + // map instead. + error_message = error.user_info[SyncError::c_recovery_file_path_key]; + error_code = 7; // See ErrorCode.java + } - env->CallStaticVoidMethod(java_syncmanager, java_error_callback_method, error.error_code.value(), - to_jstring(env, error.message), to_jstring(env, session.get()->path())); + JNIEnv* env = realm::jni_util::JniUtils::get_env(true); + env->CallStaticVoidMethod(java_syncmanager, java_error_callback_method, error_code, + to_jstring(env, error_message), to_jstring(env, session.get()->path())); }; // path on disk of the Realm file. @@ -108,6 +117,7 @@ class JniConfigWrapper { session->refresh_access_token(access_token, realm::util::Optional(syncConfig.realm_url)); } }; + // Get logged in user JStringAccessor user_identity(env, sync_user_identity); JStringAccessor realm_url(env, sync_realm_url); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 32145911d9..bdb36b7709 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -169,7 +169,7 @@ JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteAr jbyteArray jresult = env->NewByteArray(static_cast(bin.size())); if (jresult) { env->SetByteArrayRegion(jresult, 0, static_cast(bin.size()), - reinterpret_cast(bin.data())); // throws + reinterpret_cast(bin.data())); // throws } return jresult; } diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp index 66baf5cdb2..88a23ebb01 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp @@ -52,4 +52,4 @@ JNIEnv* JniUtils::get_env(bool attach_if_needed) void JniUtils::detach_current_thread() { s_instance->m_vm->DetachCurrentThread(); -} \ No newline at end of file +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ClientResetHandler.java b/realm/realm-library/src/objectServer/java/io/realm/ClientResetHandler.java new file mode 100644 index 0000000000..1fdc1adf8b --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/ClientResetHandler.java @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import java.io.File; + +/** + * Class encapsulating information needed for handling a Client Reset event. + * + * @see io.realm.SyncSession.ErrorHandler#onClientResetRequired(SyncSession, ClientResetHandler) for more information + * about when and why Client Reset occurs and how to deal with it. + */ +public class ClientResetHandler extends ObjectServerError { + + private final RealmConfiguration configuration; + private final File backupFile; + private final File originalFile; + + public ClientResetHandler(ErrorCode errorCode, String errorMessage, String backupFilePath, RealmConfiguration configuration) { + super(errorCode, errorMessage); + this.configuration = configuration; + this.backupFile = new File(backupFilePath); + this.originalFile = new File(configuration.getPath()); + } + + /** + * Calling this method will execute the Client Reset manually instead of waiting until next app restart. This will + * only be possible if all instances of that Realm have been closed, otherwise a {@link IllegalStateException} will + * be thrown. + *

            + * After this method returns, the backup file can be found in the location returned by {@link #getBackupFile()}. + * The file at {@link #getOriginalFile()} have been deleted, but will be recreated from scratch next time a + * Realm instance is opened. + * + * @throws IllegalStateException if not all instances have been closed. + */ + public void executeClientReset() { + synchronized (Realm.class) { + if (Realm.getGlobalInstanceCount(configuration) > 0) { + throw new IllegalStateException("Realm has not been fully closed. Client Reset cannot run before all " + + "instances have been closed."); + } + nativeExecuteClientReset(configuration.getPath()); + } + } + + /** + * Returns the location of the backed up Realm file. The file will not be present until the Client Reset has been + * fully executed. + * + * @return a reference to the location of the backup file once Client Reset has been executed. + * Use {@code file.exists()} to check if the file exists or not. + * + */ + public File getBackupFile() { + return backupFile; + } + + /** + * Returns the location of the original Realm file. After the Client Reset has completed, the file at this location + * will be deleted. + * + * @return a reference to the location of the original Realm file. After Client Reset has been executed this file + * will no longer exists. Use {@code file.exists()} to check this. + */ + public File getOriginalFile() { + return originalFile; + } + + // PRECONDITION: All Realm instances for this path must have been closed. + private native void nativeExecuteClientReset(String originalPath); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 191bf89c78..a8e4a827ec 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -27,6 +27,7 @@ public enum ErrorCode { UNKNOWN(-1), // Catch-all IO_EXCEPTION(0, Category.RECOVERABLE), // Some IO error while either contacting the server or reading the response JSON_EXCEPTION(1), // JSON input could not be parsed correctly + CLIENT_RESET(7), // Client Reset required. Don't change this value without modifying io_realm_internal_SharedRealm.cpp // Realm Object Server errors (100 - 199) // Connection level and protocol errors. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index ce463ff77a..d92f82828b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -83,6 +83,11 @@ public void onError(SyncSession session, ObjectServerError error) { throw new IllegalArgumentException("Unsupported error category: " + error.getErrorCode().getCategory()); } } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + RealmLog.error("Client Reset required for: " + session.getConfiguration().getPath()); + } }; // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path' private static Map sessions = new HashMap(); @@ -283,8 +288,24 @@ static synchronized void reset() { sessions.clear(); } + /** + * Simulate a Client Reset by triggering the Object Store error handler with Sync Error Code that will be + * converted to a Client Reset (211 - Diverging Histories). + * + * Only call this method when testing. + * + * @param session Session to trigger Client Reset for. + */ + static void simulateClientReset(SyncSession session) { + nativeSimulateSyncError(session.getConfiguration().getPath(), + ErrorCode.DIVERGING_HISTORIES.intValue(), + "Simulate Client Reset", + true); + } + private static native void nativeInitializeSyncClient(); // init and load the Metadata Realm containing SyncUsers protected static native void nativeConfigureMetaDataSystem(String baseFile); private static native void nativeReset(); + private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 1572c34abc..f2c40281ba 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -96,9 +96,17 @@ public URI getServerUrl() { // This callback will happen on the thread running the Sync Client. @KeepMember void notifySessionError(int errorCode, String errorMessage) { - ObjectServerError error = new ObjectServerError(ErrorCode.fromInt(errorCode), errorMessage); - if (errorHandler != null) { - errorHandler.onError(this, error); + if (errorHandler == null) { + return; + } + ErrorCode errCode = ErrorCode.fromInt(errorCode); + if (errCode == ErrorCode.CLIENT_RESET) { + // errorMessage contains the path to the backed up file + errorHandler.onClientResetRequired(this, new ClientResetHandler(errCode, "A Client Reset is required. " + + "Read more here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup.", + errorMessage, getConfiguration())); + } else { + errorHandler.onError(this, new ObjectServerError(errCode, errorMessage)); } } @@ -127,6 +135,46 @@ public interface ErrorHandler { * @param error type of error. */ void onError(SyncSession session, ObjectServerError error); + + /** + * An error that indicates the Realm needs to be reset. + *

            + * A synced Realm may need to be reset because the Realm Object Server encountered an error and had + * to be restored from a backup. If the backup copy of the remote Realm is of an earlier version + * than the local copy of the Realm, the server will ask the client to reset the Realm. + *

            + * The reset process is as follows: the local copy of the Realm is copied into a recovery directory + * for safekeeping, and then deleted from the original location. The next time the Realm for that + * URL is opened, the Realm will automatically be re-downloaded from the Realm Object Server, and + * can be used as normal. + *

            + * Data written to the Realm after the local copy of the Realm diverged from the backup remote copy + * will be present in the local recovery copy of the Realm file. The re-downloaded Realm will + * initially contain only the data present at the time the Realm was backed up on the server. + *

            + * The client reset process can be initiated in one of two ways: + *

              + *
            1. + * Run {@link ClientResetHandler#executeClientReset()} manually. All Realm instances must be + * closed before this method is called. + *
            2. + *
            3. + * If Client Reset isn't executed manually, it will automatically be carried out the next time all + * Realm instances have been closed and re-opened. This will most likely be + * when the app is restarted. + *
            4. + *
            + * + * WARNING: + * Any writes to the Realm file between this callback and Client Reset has been executed, will not be + * synchronized to the Object Server. Those changes will only be present in the backed up file. It is therefore + * recommended to close all open Realm instances as soon as possible. + * + * @param session {@link SyncSession} this error happened on. + * @param handler reference to the specific Client Reset error. + * @see Client Recovery From A Backup + */ + void onClientResetRequired(SyncSession session, ClientResetHandler handler); } String accessToken(final AuthenticationServer authServer) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index f0406cf086..4cf8e236c1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -74,6 +74,11 @@ public void onError(SyncSession session, ObjectServerError error) { user.getIdentity(), error.toString())); } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + RealmLog.error("Client Reset required for users management Realm: " + user.toString()); + } }) .modules(new PermissionModule()) .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index ae1cda7ac7..b25ce7f907 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -6,6 +6,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import io.realm.ClientResetHandler; import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.Realm; @@ -73,6 +74,11 @@ public void onSuccess(SyncUser user) { public void onError(SyncSession session, ObjectServerError error) { fail("Session failed: " + error); } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + fail("Client Reset"); + } }) .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index d1695c13ba..2754115d44 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -26,6 +26,7 @@ import java.util.Date; import java.util.concurrent.atomic.AtomicReference; +import io.realm.ClientResetHandler; import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; @@ -34,7 +35,6 @@ import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.entities.Dog; -import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; @@ -67,6 +67,11 @@ public void create_acceptOffer() { public void onError(SyncSession session, ObjectServerError error) { fail("Realm 1 unexpected error: " + error); } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + fail("Client Reset"); + } }) .build(); final Realm realm1 = Realm.getInstance(config1); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 68ce590f3e..62863c9964 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -22,8 +22,6 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import org.junit.AfterClass; -import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,6 +31,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import io.realm.ClientResetHandler; import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; @@ -45,7 +44,6 @@ import io.realm.objectserver.service.SendOneCommit; import io.realm.objectserver.service.SendsALot; import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.HttpUtils; import io.realm.objectserver.utils.UserFactory; import static org.junit.Assert.assertEquals; @@ -78,6 +76,11 @@ public void run() { public void onError(SyncSession session, ObjectServerError error) { fail("Sync failure: " + error); } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + fail("Client Reset"); + } }) .build(); Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests @@ -138,6 +141,11 @@ public void run() { public void onError(SyncSession session, ObjectServerError error) { fail("Sync failure: " + error); } + + @Override + public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + fail("Client Reset"); + } }) .build(); Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests From 5c3ca8fbb84b8b23aced8381e7060e2d815044b2 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 17 Mar 2017 17:23:17 +0900 Subject: [PATCH 0557/2110] Cancel transaction explicitly (#4319) * cancel transaction explicitly. If `Realm.getInstance()` is called in the 'transaction' object, calling `close()` does not cancel current transaction. To ensure that the transaction is not left open, cancel it explicitly if needed. * use correct Realm object --- realm/realm-library/src/main/java/io/realm/Realm.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 7df9649f9e..eed1ccc85a 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1440,8 +1440,13 @@ public void run() { } catch (final Throwable e) { exception = e; } finally { - // SharedGroup::close() will cancel the transaction if needed. - bgRealm.close(); + try { + if (bgRealm.isInTransaction()) { + bgRealm.cancelTransaction(); + } + } finally { + bgRealm.close(); + } } final Throwable backgroundException = exception; From eebdb0849e08aed5862f2bc0a638274b05e8395a Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 17 Mar 2017 19:13:49 +0900 Subject: [PATCH 0558/2110] stop using deprecated annotation (#4336) --- .../java/io/realm/internal/async/RealmThreadPoolExecutor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java index 4f387d012d..091f954e9f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java @@ -27,7 +27,7 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Pattern; -import edu.umd.cs.findbugs.annotations.SuppressWarnings; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Custom thread pool settings, instances of this executor can be paused, and resumed, this will also set @@ -66,7 +66,7 @@ public static RealmThreadPoolExecutor newSingleThreadExecutor() { * * @return the number of threads to be allocated for the executor pool */ - @SuppressWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME") + @SuppressFBWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME") private static int calculateCorePoolSize() { int cpus = countFilesInDir(SYS_CPU_DIR, "cpu[0-9]+"); if (cpus <= 0) { From 0eba153728c60a32d809a6e07e20bb82239ac0c4 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 17 Mar 2017 20:50:36 +0900 Subject: [PATCH 0559/2110] fix warnings in generated code --- CHANGELOG.md | 1 + .../processor/RealmProxyClassGenerator.java | 8 +++- .../io/realm/AllTypesRealmProxy.java | 20 ++++++++- .../io/realm/BooleansRealmProxy.java | 10 ++++- .../io/realm/NullTypesRealmProxy.java | 44 ++++++++++++++++++- .../resources/io/realm/SimpleRealmProxy.java | 6 ++- 6 files changed, 84 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cc3d0bbb3..98f440a8b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * `Realm.migrateRealm(RealmConfiguration)` now fails correctly with an `IllegalArgumentException` if a `SyncConfiguration` is provided (#4075). * Fixed a potential cause for Realm file corruptions (never reported). +* Add `@Override` annotation to accessors and stop using raw type in proxy classes in order to remove warnings from javac (#4329). ### Deprecated diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 6aa0cf7e0e..aae37a59e4 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -244,6 +244,7 @@ private void emitAccessors(final JavaWriter writer) throws IOException { final String realmType = Constants.JAVA_TO_REALM_TYPES.get(fieldTypeCanonicalName); // Getter + writer.emitAnnotation("Override"); writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); @@ -270,6 +271,7 @@ private void emitAccessors(final JavaWriter writer) throws IOException { writer.emitEmptyLine(); // Setter + writer.emitAnnotation("Override"); writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { @Override @@ -324,6 +326,7 @@ public void emit(JavaWriter writer) throws IOException { */ // Getter + writer.emitAnnotation("Override"); writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.beginControlFlow("if (proxyState.getRow$realm().isNullLink(%s))", fieldIndexVariableReference(field)); @@ -335,6 +338,7 @@ public void emit(JavaWriter writer) throws IOException { writer.emitEmptyLine(); // Setter + writer.emitAnnotation("Override"); writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { @Override @@ -386,6 +390,7 @@ public void emit(JavaWriter writer) throws IOException { String genericType = Utils.getGenericTypeQualifiedName(field); // Getter + writer.emitAnnotation("Override"); writer.beginMethod(fieldTypeCanonicalName, metadata.getGetter(fieldName), EnumSet.of(Modifier.PUBLIC)); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); writer.emitSingleLineComment("use the cached value if available"); @@ -402,6 +407,7 @@ public void emit(JavaWriter writer) throws IOException { writer.emitEmptyLine(); // Setter + writer.emitAnnotation("Override"); writer.beginMethod("void", metadata.getSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { @Override @@ -497,7 +503,7 @@ private void emitInjectContextMethod(JavaWriter writer) throws IOException { private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); - writer.beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)); + writer.beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)); writer.emitStatement("return proxyState"); writer.endMethod(); writer.emitEmptyLine(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 7666014010..44a01b7283 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -126,12 +126,14 @@ public final AllTypesColumnInfo clone() { proxyState.setExcludeFields$realm(context.getExcludeFields()); } + @Override @SuppressWarnings("cast") public String realmGet$columnString() { proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.columnStringIndex); } + @Override public void realmSet$columnString(String value) { if (proxyState.isUnderConstruction()) { // default value of the primary key is always ignored. @@ -142,12 +144,14 @@ public final AllTypesColumnInfo clone() { throw new io.realm.exceptions.RealmException("Primary key field 'columnString' cannot be changed after object was created."); } + @Override @SuppressWarnings("cast") public long realmGet$columnLong() { proxyState.getRealm$realm().checkIfValid(); return (long) proxyState.getRow$realm().getLong(columnInfo.columnLongIndex); } + @Override public void realmSet$columnLong(long value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -162,12 +166,14 @@ public final AllTypesColumnInfo clone() { proxyState.getRow$realm().setLong(columnInfo.columnLongIndex, value); } + @Override @SuppressWarnings("cast") public float realmGet$columnFloat() { proxyState.getRealm$realm().checkIfValid(); return (float) proxyState.getRow$realm().getFloat(columnInfo.columnFloatIndex); } + @Override public void realmSet$columnFloat(float value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -182,12 +188,14 @@ public final AllTypesColumnInfo clone() { proxyState.getRow$realm().setFloat(columnInfo.columnFloatIndex, value); } + @Override @SuppressWarnings("cast") public double realmGet$columnDouble() { proxyState.getRealm$realm().checkIfValid(); return (double) proxyState.getRow$realm().getDouble(columnInfo.columnDoubleIndex); } + @Override public void realmSet$columnDouble(double value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -202,12 +210,14 @@ public final AllTypesColumnInfo clone() { proxyState.getRow$realm().setDouble(columnInfo.columnDoubleIndex, value); } + @Override @SuppressWarnings("cast") public boolean realmGet$columnBoolean() { proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.columnBooleanIndex); } + @Override public void realmSet$columnBoolean(boolean value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -222,12 +232,14 @@ public final AllTypesColumnInfo clone() { proxyState.getRow$realm().setBoolean(columnInfo.columnBooleanIndex, value); } + @Override @SuppressWarnings("cast") public Date realmGet$columnDate() { proxyState.getRealm$realm().checkIfValid(); return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.columnDateIndex); } + @Override public void realmSet$columnDate(Date value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -248,12 +260,14 @@ public final AllTypesColumnInfo clone() { proxyState.getRow$realm().setDate(columnInfo.columnDateIndex, value); } + @Override @SuppressWarnings("cast") public byte[] realmGet$columnBinary() { proxyState.getRealm$realm().checkIfValid(); return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.columnBinaryIndex); } + @Override public void realmSet$columnBinary(byte[] value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -274,6 +288,7 @@ public final AllTypesColumnInfo clone() { proxyState.getRow$realm().setBinaryByteArray(columnInfo.columnBinaryIndex, value); } + @Override public some.test.AllTypes realmGet$columnObject() { proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNullLink(columnInfo.columnObjectIndex)) { @@ -282,6 +297,7 @@ public final AllTypesColumnInfo clone() { return proxyState.getRealm$realm().get(some.test.AllTypes.class, proxyState.getRow$realm().getLink(columnInfo.columnObjectIndex), false, Collections.emptyList()); } + @Override public void realmSet$columnObject(some.test.AllTypes value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -323,6 +339,7 @@ public final AllTypesColumnInfo clone() { proxyState.getRow$realm().setLink(columnInfo.columnObjectIndex, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex()); } + @Override public RealmList realmGet$columnRealmList() { proxyState.getRealm$realm().checkIfValid(); // use the cached value if available @@ -335,6 +352,7 @@ public final AllTypesColumnInfo clone() { } } + @Override public void realmSet$columnRealmList(RealmList value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -1236,7 +1254,7 @@ public String toString() { } @Override - public ProxyState realmGet$proxyState() { + public ProxyState realmGet$proxyState() { return proxyState; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 8906eb90e6..a2b73d4c2f 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -100,12 +100,14 @@ public final BooleansColumnInfo clone() { proxyState.setExcludeFields$realm(context.getExcludeFields()); } + @Override @SuppressWarnings("cast") public boolean realmGet$done() { proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.doneIndex); } + @Override public void realmSet$done(boolean value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -120,12 +122,14 @@ public final BooleansColumnInfo clone() { proxyState.getRow$realm().setBoolean(columnInfo.doneIndex, value); } + @Override @SuppressWarnings("cast") public boolean realmGet$isReady() { proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.isReadyIndex); } + @Override public void realmSet$isReady(boolean value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -140,12 +144,14 @@ public final BooleansColumnInfo clone() { proxyState.getRow$realm().setBoolean(columnInfo.isReadyIndex, value); } + @Override @SuppressWarnings("cast") public boolean realmGet$mCompleted() { proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.mCompletedIndex); } + @Override public void realmSet$mCompleted(boolean value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -160,12 +166,14 @@ public final BooleansColumnInfo clone() { proxyState.getRow$realm().setBoolean(columnInfo.mCompletedIndex, value); } + @Override @SuppressWarnings("cast") public boolean realmGet$anotherBoolean() { proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.anotherBooleanIndex); } + @Override public void realmSet$anotherBoolean(boolean value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -520,7 +528,7 @@ public String toString() { } @Override - public ProxyState realmGet$proxyState() { + public ProxyState realmGet$proxyState() { return proxyState; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index a541004034..9bad83c745 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -185,12 +185,14 @@ public final NullTypesColumnInfo clone() { proxyState.setExcludeFields$realm(context.getExcludeFields()); } + @Override @SuppressWarnings("cast") public String realmGet$fieldStringNotNull() { proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNotNullIndex); } + @Override public void realmSet$fieldStringNotNull(String value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -211,12 +213,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setString(columnInfo.fieldStringNotNullIndex, value); } + @Override @SuppressWarnings("cast") public String realmGet$fieldStringNull() { proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNullIndex); } + @Override public void realmSet$fieldStringNull(String value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -239,12 +243,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setString(columnInfo.fieldStringNullIndex, value); } + @Override @SuppressWarnings("cast") public Boolean realmGet$fieldBooleanNotNull() { proxyState.getRealm$realm().checkIfValid(); return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.fieldBooleanNotNullIndex); } + @Override public void realmSet$fieldBooleanNotNull(Boolean value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -265,6 +271,7 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setBoolean(columnInfo.fieldBooleanNotNullIndex, value); } + @Override @SuppressWarnings("cast") public Boolean realmGet$fieldBooleanNull() { proxyState.getRealm$realm().checkIfValid(); @@ -274,6 +281,7 @@ public final NullTypesColumnInfo clone() { return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.fieldBooleanNullIndex); } + @Override public void realmSet$fieldBooleanNull(Boolean value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -296,12 +304,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setBoolean(columnInfo.fieldBooleanNullIndex, value); } + @Override @SuppressWarnings("cast") public byte[] realmGet$fieldBytesNotNull() { proxyState.getRealm$realm().checkIfValid(); return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNotNullIndex); } + @Override public void realmSet$fieldBytesNotNull(byte[] value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -322,12 +332,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setBinaryByteArray(columnInfo.fieldBytesNotNullIndex, value); } + @Override @SuppressWarnings("cast") public byte[] realmGet$fieldBytesNull() { proxyState.getRealm$realm().checkIfValid(); return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNullIndex); } + @Override public void realmSet$fieldBytesNull(byte[] value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -350,12 +362,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setBinaryByteArray(columnInfo.fieldBytesNullIndex, value); } + @Override @SuppressWarnings("cast") public Byte realmGet$fieldByteNotNull() { proxyState.getRealm$realm().checkIfValid(); return (byte) proxyState.getRow$realm().getLong(columnInfo.fieldByteNotNullIndex); } + @Override public void realmSet$fieldByteNotNull(Byte value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -376,6 +390,7 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setLong(columnInfo.fieldByteNotNullIndex, value); } + @Override @SuppressWarnings("cast") public Byte realmGet$fieldByteNull() { proxyState.getRealm$realm().checkIfValid(); @@ -385,6 +400,7 @@ public final NullTypesColumnInfo clone() { return (byte) proxyState.getRow$realm().getLong(columnInfo.fieldByteNullIndex); } + @Override public void realmSet$fieldByteNull(Byte value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -407,12 +423,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setLong(columnInfo.fieldByteNullIndex, value); } + @Override @SuppressWarnings("cast") public Short realmGet$fieldShortNotNull() { proxyState.getRealm$realm().checkIfValid(); return (short) proxyState.getRow$realm().getLong(columnInfo.fieldShortNotNullIndex); } + @Override public void realmSet$fieldShortNotNull(Short value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -433,6 +451,7 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setLong(columnInfo.fieldShortNotNullIndex, value); } + @Override @SuppressWarnings("cast") public Short realmGet$fieldShortNull() { proxyState.getRealm$realm().checkIfValid(); @@ -442,6 +461,7 @@ public final NullTypesColumnInfo clone() { return (short) proxyState.getRow$realm().getLong(columnInfo.fieldShortNullIndex); } + @Override public void realmSet$fieldShortNull(Short value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -464,12 +484,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setLong(columnInfo.fieldShortNullIndex, value); } + @Override @SuppressWarnings("cast") public Integer realmGet$fieldIntegerNotNull() { proxyState.getRealm$realm().checkIfValid(); return (int) proxyState.getRow$realm().getLong(columnInfo.fieldIntegerNotNullIndex); } + @Override public void realmSet$fieldIntegerNotNull(Integer value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -490,6 +512,7 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setLong(columnInfo.fieldIntegerNotNullIndex, value); } + @Override @SuppressWarnings("cast") public Integer realmGet$fieldIntegerNull() { proxyState.getRealm$realm().checkIfValid(); @@ -499,6 +522,7 @@ public final NullTypesColumnInfo clone() { return (int) proxyState.getRow$realm().getLong(columnInfo.fieldIntegerNullIndex); } + @Override public void realmSet$fieldIntegerNull(Integer value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -521,12 +545,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setLong(columnInfo.fieldIntegerNullIndex, value); } + @Override @SuppressWarnings("cast") public Long realmGet$fieldLongNotNull() { proxyState.getRealm$realm().checkIfValid(); return (long) proxyState.getRow$realm().getLong(columnInfo.fieldLongNotNullIndex); } + @Override public void realmSet$fieldLongNotNull(Long value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -547,6 +573,7 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setLong(columnInfo.fieldLongNotNullIndex, value); } + @Override @SuppressWarnings("cast") public Long realmGet$fieldLongNull() { proxyState.getRealm$realm().checkIfValid(); @@ -556,6 +583,7 @@ public final NullTypesColumnInfo clone() { return (long) proxyState.getRow$realm().getLong(columnInfo.fieldLongNullIndex); } + @Override public void realmSet$fieldLongNull(Long value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -578,12 +606,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setLong(columnInfo.fieldLongNullIndex, value); } + @Override @SuppressWarnings("cast") public Float realmGet$fieldFloatNotNull() { proxyState.getRealm$realm().checkIfValid(); return (float) proxyState.getRow$realm().getFloat(columnInfo.fieldFloatNotNullIndex); } + @Override public void realmSet$fieldFloatNotNull(Float value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -604,6 +634,7 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setFloat(columnInfo.fieldFloatNotNullIndex, value); } + @Override @SuppressWarnings("cast") public Float realmGet$fieldFloatNull() { proxyState.getRealm$realm().checkIfValid(); @@ -613,6 +644,7 @@ public final NullTypesColumnInfo clone() { return (float) proxyState.getRow$realm().getFloat(columnInfo.fieldFloatNullIndex); } + @Override public void realmSet$fieldFloatNull(Float value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -635,12 +667,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setFloat(columnInfo.fieldFloatNullIndex, value); } + @Override @SuppressWarnings("cast") public Double realmGet$fieldDoubleNotNull() { proxyState.getRealm$realm().checkIfValid(); return (double) proxyState.getRow$realm().getDouble(columnInfo.fieldDoubleNotNullIndex); } + @Override public void realmSet$fieldDoubleNotNull(Double value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -661,6 +695,7 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setDouble(columnInfo.fieldDoubleNotNullIndex, value); } + @Override @SuppressWarnings("cast") public Double realmGet$fieldDoubleNull() { proxyState.getRealm$realm().checkIfValid(); @@ -670,6 +705,7 @@ public final NullTypesColumnInfo clone() { return (double) proxyState.getRow$realm().getDouble(columnInfo.fieldDoubleNullIndex); } + @Override public void realmSet$fieldDoubleNull(Double value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -692,12 +728,14 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setDouble(columnInfo.fieldDoubleNullIndex, value); } + @Override @SuppressWarnings("cast") public Date realmGet$fieldDateNotNull() { proxyState.getRealm$realm().checkIfValid(); return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.fieldDateNotNullIndex); } + @Override public void realmSet$fieldDateNotNull(Date value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -718,6 +756,7 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setDate(columnInfo.fieldDateNotNullIndex, value); } + @Override @SuppressWarnings("cast") public Date realmGet$fieldDateNull() { proxyState.getRealm$realm().checkIfValid(); @@ -727,6 +766,7 @@ public final NullTypesColumnInfo clone() { return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.fieldDateNullIndex); } + @Override public void realmSet$fieldDateNull(Date value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -749,6 +789,7 @@ public final NullTypesColumnInfo clone() { proxyState.getRow$realm().setDate(columnInfo.fieldDateNullIndex, value); } + @Override public some.test.NullTypes realmGet$fieldObjectNull() { proxyState.getRealm$realm().checkIfValid(); if (proxyState.getRow$realm().isNullLink(columnInfo.fieldObjectNullIndex)) { @@ -757,6 +798,7 @@ public final NullTypesColumnInfo clone() { return proxyState.getRealm$realm().get(some.test.NullTypes.class, proxyState.getRow$realm().getLink(columnInfo.fieldObjectNullIndex), false, Collections.emptyList()); } + @Override public void realmSet$fieldObjectNull(some.test.NullTypes value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -2137,7 +2179,7 @@ public String toString() { } @Override - public ProxyState realmGet$proxyState() { + public ProxyState realmGet$proxyState() { return proxyState; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 694515e02c..1908abbdab 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -90,12 +90,14 @@ public final SimpleColumnInfo clone() { proxyState.setExcludeFields$realm(context.getExcludeFields()); } + @Override @SuppressWarnings("cast") public String realmGet$name() { proxyState.getRealm$realm().checkIfValid(); return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.nameIndex); } + @Override public void realmSet$name(String value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -118,12 +120,14 @@ public final SimpleColumnInfo clone() { proxyState.getRow$realm().setString(columnInfo.nameIndex, value); } + @Override @SuppressWarnings("cast") public int realmGet$age() { proxyState.getRealm$realm().checkIfValid(); return (int) proxyState.getRow$realm().getLong(columnInfo.ageIndex); } + @Override public void realmSet$age(int value) { if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { @@ -407,7 +411,7 @@ public static some.test.Simple createDetachedCopy(some.test.Simple realmObject, } @Override - public ProxyState realmGet$proxyState() { + public ProxyState realmGet$proxyState() { return proxyState; } From 4cf20b8400ad108250290511f5e8a698ed25bb15 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 17 Mar 2017 21:14:50 +0900 Subject: [PATCH 0560/2110] fix CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98f440a8b3..ec878300cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * `Realm.migrateRealm(RealmConfiguration)` now fails correctly with an `IllegalArgumentException` if a `SyncConfiguration` is provided (#4075). * Fixed a potential cause for Realm file corruptions (never reported). -* Add `@Override` annotation to accessors and stop using raw type in proxy classes in order to remove warnings from javac (#4329). +* Add `@Override` annotation to proxy class accessors and stop using raw type in proxy classes in order to remove warnings from javac (#4329). ### Deprecated From 15441988111c726bdbc8133c93444b8eb86cc0c3 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Sat, 18 Mar 2017 02:44:11 +0900 Subject: [PATCH 0561/2110] Fix warnings from error prone plugin https://github.com/tbroyer/gradle-errorprone-plugin (#4339) This PR does not add the plugin, just fix the warnings. I'll add the plugin in another PR with suppressing some warnings. --- .../io/realm/DynamicRealmObjectTests.java | 2 +- .../androidTest/java/io/realm/TestHelper.java | 22 +++++++++--------- .../io/realm/internal/test/ExtraTests.java | 1 + .../main/java/io/realm/RealmCollection.java | 1 + .../src/main/java/io/realm/RealmList.java | 17 +++++++++++++- .../src/main/java/io/realm/RealmResults.java | 2 ++ .../java/io/realm/internal/CheckedRow.java | 23 +++++++++++++++++++ .../java/io/realm/internal/Collection.java | 3 ++- .../io/realm/internal/ObjectServerFacade.java | 9 +++++++- .../main/java/io/realm/internal/Table.java | 1 + .../internal/async/RealmAsyncTaskImpl.java | 2 ++ 11 files changed, 68 insertions(+), 15 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 44ee9ddb91..e0208b45b5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -1241,7 +1241,7 @@ public void toString_nullValues() { assertTrue(str.contains(NullTypes.FIELD_LIST_NULL + ":RealmList[0]")); } - + @Test public void testExceptionMessage() { // Tests for https://github.com/realm/realm-java/issues/2141 realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 92059c90cc..548515114f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -68,6 +68,8 @@ public class TestHelper { + private static final Charset UTF_8 = Charset.forName("UTF-8"); + public static class ExpectedCountCallback implements RealmCache.Callback { private int expectedCount; @@ -124,7 +126,7 @@ public static String streamToString(InputStream in) throws IOException { StringBuilder sb = new StringBuilder(); String line; try { - br = new BufferedReader(new InputStreamReader(in)); + br = new BufferedReader(new InputStreamReader(in, UTF_8)); while ((line = br.readLine()) != null) { sb.append(line); } @@ -138,7 +140,7 @@ public static String streamToString(InputStream in) throws IOException { } public static InputStream stringToStream(String str) { - return new ByteArrayInputStream(str.getBytes(Charset.forName("UTF-8"))); + return new ByteArrayInputStream(str.getBytes(UTF_8)); } // Creates a simple migration step in order to support null. @@ -256,12 +258,10 @@ public static byte[] allocGarbage(int garbageSize) { public static byte[] SHA512(String str) { try { MessageDigest md = MessageDigest.getInstance("SHA-512"); - md.update(str.getBytes("UTF-8"), 0, str.length()); + md.update(str.getBytes(UTF_8), 0, str.length()); return md.digest(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); } } @@ -368,10 +368,10 @@ public static void populateTestRealmWithBytePrimaryKey(Realm testRealm, Byte pri userObj.setId(primaryFieldValue); userObj.setName(secondaryFieldValue); testRealm.copyToRealm(userObj); - byte idValue = (byte)iteratorBeginValue; + byte idValue = (byte) iteratorBeginValue; for (int i = 0; i < numberOfPopulation - 1; ++i, ++idValue) { PrimaryKeyAsBoxedByte obj = new PrimaryKeyAsBoxedByte(); - obj.setId(new Byte(idValue)); + obj.setId(idValue); obj.setName(String.valueOf(idValue)); testRealm.copyToRealm(obj); } @@ -404,7 +404,7 @@ public static void populateTestRealmWithShortPrimaryKey(Realm testRealm, Short p short idValue = (short)iteratorBeginValue; for (int i = 0; i < numberOfPopulation - 1; ++i, ++idValue) { PrimaryKeyAsBoxedShort obj = new PrimaryKeyAsBoxedShort(); - obj.setId(new Short(idValue)); + obj.setId(idValue); obj.setName(String.valueOf(idValue)); testRealm.copyToRealm(obj); } @@ -437,7 +437,7 @@ public static void populateTestRealmWithIntegerPrimaryKey(Realm testRealm, Integ int idValue = iteratorBeginValue; for (int i = 0; i < numberOfPopulation - 1; ++i, ++idValue) { PrimaryKeyAsBoxedInteger obj = new PrimaryKeyAsBoxedInteger(); - obj.setId(new Integer(idValue)); + obj.setId(idValue); obj.setName(String.valueOf(idValue)); testRealm.copyToRealm(obj); } @@ -470,7 +470,7 @@ public static void populateTestRealmWithLongPrimaryKey(Realm testRealm, Long pri long idValue = iteratorBeginValue; for (long i = 0; i < numberOfPopulation - 1; ++i, ++idValue) { PrimaryKeyAsBoxedLong obj = new PrimaryKeyAsBoxedLong(); - obj.setId(new Long(idValue)); + obj.setId(idValue); obj.setName(String.valueOf(idValue)); testRealm.copyToRealm(obj); } @@ -1047,7 +1047,7 @@ public static boolean isSelinuxEnforcing() { try { final Process process = new ProcessBuilder("/system/bin/getenforce").start(); try { - final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8)); //noinspection TryFinallyCanBeTryWithResources try { return reader.readLine().toLowerCase(Locale.ENGLISH).equals("enforcing"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/test/ExtraTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/test/ExtraTests.java index 3a422eaf09..427b30ad86 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/test/ExtraTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/test/ExtraTests.java @@ -45,6 +45,7 @@ public static void assertDateArrayEquals(Object[] expecteds, Date[] actuals) } private static class ExactComparisonCriteria extends ComparisonCriteria { + @Override protected void assertElementsEqual(Object expected, Object actual) { assertEquals(expected, actual); diff --git a/realm/realm-library/src/main/java/io/realm/RealmCollection.java b/realm/realm-library/src/main/java/io/realm/RealmCollection.java index 722d1253d3..120103c780 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCollection.java @@ -172,5 +172,6 @@ public interface RealmCollection extends Collection { * @throws NullPointerException if the object to look for is {@code null} and this {@code Collection} doesn't * support {@code null} elements. */ + @Override boolean contains(Object object); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index d3ede7d912..04b06fb12f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -119,6 +119,7 @@ public RealmList(E... objects) { /** * {@inheritDoc} */ + @Override public boolean isValid() { if (realm == null) { return true; @@ -133,6 +134,7 @@ public boolean isValid() { /** * {@inheritDoc} */ + @Override public boolean isManaged() { return realm != null; } @@ -459,6 +461,7 @@ public E get(int location) { /** * {@inheritDoc} */ + @Override public E first() { return firstImpl(true, null); } @@ -466,6 +469,7 @@ public E first() { /** * {@inheritDoc} */ + @Override public E first(E defaultValue) { return firstImpl(false, defaultValue); } @@ -490,6 +494,7 @@ private E firstImpl(boolean shouldThrow, E defaultValue) { /** * {@inheritDoc} */ + @Override public E last() { return lastImpl(true, null); } @@ -497,6 +502,7 @@ public E last() { /** * {@inheritDoc} */ + @Override public E last(E defaultValue) { return lastImpl(false, defaultValue); } @@ -596,6 +602,7 @@ public int size() { * @throws IllegalStateException if Realm instance has been closed or parent object has been removed. * @see io.realm.RealmQuery */ + @Override public RealmQuery where() { if (isManaged()) { checkValidView(); @@ -977,6 +984,7 @@ private class RealmItr implements Iterator { /** * {@inheritDoc} */ + @Override public boolean hasNext() { realm.checkIfValid(); checkConcurrentModification(); @@ -986,6 +994,7 @@ public boolean hasNext() { /** * {@inheritDoc} */ + @Override public E next() { realm.checkIfValid(); checkConcurrentModification(); @@ -1004,6 +1013,7 @@ public E next() { /** * {@inheritDoc} */ + @Override public void remove() { realm.checkIfValid(); if (lastRet < 0) { @@ -1049,6 +1059,7 @@ private class RealmListItr extends RealmItr implements ListIterator { /** * {@inheritDoc} */ + @Override public boolean hasPrevious() { return cursor != 0; } @@ -1056,6 +1067,7 @@ public boolean hasPrevious() { /** * {@inheritDoc} */ + @Override public E previous() { checkConcurrentModification(); int i = cursor - 1; @@ -1072,6 +1084,7 @@ public E previous() { /** * {@inheritDoc} */ + @Override public int nextIndex() { return cursor; } @@ -1079,6 +1092,7 @@ public int nextIndex() { /** * {@inheritDoc} */ + @Override public int previousIndex() { return cursor - 1; } @@ -1086,6 +1100,7 @@ public int previousIndex() { /** * {@inheritDoc} */ + @Override public void set(E e) { realm.checkIfValid(); if (lastRet < 0) { @@ -1107,6 +1122,7 @@ public void set(E e) { * * @see #add(RealmModel) */ + @Override public void add(E e) { realm.checkIfValid(); checkConcurrentModification(); @@ -1121,5 +1137,4 @@ public void add(E e) { } } } - } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index e6a55ab77e..4697178f25 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -84,6 +84,7 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName * @return {@code true} if the query has completed and the data is available, {@code false} if the query is still * running in the background. */ + @Override public boolean isLoaded() { realm.checkIfValid(); return collection.isLoaded(); @@ -95,6 +96,7 @@ public boolean isLoaded() { * * @return {@code true} if it successfully completed the query, {@code false} otherwise. */ + @Override public boolean load() { // The Collection doesn't have to be loaded before accessing it if the query has not returned. // Instead, accessing the Collection will just trigger the execution of query if needed. We add this flag is diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index 9e0e762086..ca458a44a0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -107,27 +107,50 @@ public void setNull(long columnIndex) { } } + @Override protected native long nativeGetColumnCount(long nativeTablePtr); + @Override protected native String nativeGetColumnName(long nativeTablePtr, long columnIndex); + @Override protected native long nativeGetColumnIndex(long nativeTablePtr, String columnName); + @Override protected native int nativeGetColumnType(long nativeTablePtr, long columnIndex); + @Override protected native long nativeGetLong(long nativeRowPtr, long columnIndex); + @Override protected native boolean nativeGetBoolean(long nativeRowPtr, long columnIndex); + @Override protected native float nativeGetFloat(long nativeRowPtr, long columnIndex); + @Override protected native double nativeGetDouble(long nativeRowPtr, long columnIndex); + @Override protected native long nativeGetTimestamp(long nativeRowPtr, long columnIndex); + @Override protected native String nativeGetString(long nativePtr, long columnIndex); + @Override protected native boolean nativeIsNullLink(long nativeRowPtr, long columnIndex); + @Override protected native byte[] nativeGetByteArray(long nativePtr, long columnIndex); + @Override protected native long nativeGetLinkView(long nativePtr, long columnIndex); + @Override protected native void nativeSetLong(long nativeRowPtr, long columnIndex, long value); + @Override protected native void nativeSetBoolean(long nativeRowPtr, long columnIndex, boolean value); + @Override protected native void nativeSetFloat(long nativeRowPtr, long columnIndex, float value); + @Override protected native long nativeGetLink(long nativeRowPtr, long columnIndex); + @Override protected native void nativeSetDouble(long nativeRowPtr, long columnIndex, double value); + @Override protected native void nativeSetTimestamp(long nativeRowPtr, long columnIndex, long dateTimeValue); + @Override protected native void nativeSetString(long nativeRowPtr, long columnIndex, String value); + @Override protected native void nativeSetByteArray(long nativePtr, long columnIndex, byte[] data); + @Override protected native void nativeSetLink(long nativeRowPtr, long columnIndex, long value); + @Override protected native void nativeNullifyLink(long nativeRowPtr, long columnIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 704e8cbdff..21282b7146 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -31,7 +31,7 @@ @Keep public class Collection implements NativeObject { - private class CollectionObserverPair extends ObserverPairList.ObserverPair { + private static class CollectionObserverPair extends ObserverPairList.ObserverPair { public CollectionObserverPair(T observer, Object listener) { super(observer, listener); } @@ -135,6 +135,7 @@ public T next() { * * @throws UnsupportedOperationException */ + @Override @Deprecated public void remove() { throw new UnsupportedOperationException("remove() is not supported by RealmResults iterators."); diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 651562b6d3..cdf5b9b0a3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -18,6 +18,8 @@ import android.content.Context; +import java.lang.reflect.InvocationTargetException; + import io.realm.RealmConfiguration; import io.realm.exceptions.RealmException; @@ -34,12 +36,17 @@ public class ObjectServerFacade { //noinspection TryWithIdenticalCatches try { Class syncFacadeClass = Class.forName("io.realm.internal.objectserver.SyncObjectServerFacade"); - syncFacade = (ObjectServerFacade) syncFacadeClass.newInstance(); + //noinspection unchecked + syncFacade = (ObjectServerFacade) syncFacadeClass.getDeclaredConstructor().newInstance(); } catch (ClassNotFoundException ignored) { } catch (InstantiationException e) { throw new RealmException("Failed to init SyncObjectServerFacade", e); } catch (IllegalAccessException e) { throw new RealmException("Failed to init SyncObjectServerFacade", e); + } catch (NoSuchMethodException e) { + throw new RealmException("Failed to init SyncObjectServerFacade", e); + } catch (InvocationTargetException e) { + throw new RealmException("Failed to init SyncObjectServerFacade", e.getTargetException()); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index a5b0b5c35c..15c06109d9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -1101,6 +1101,7 @@ public String toJson() { return nativeToJson(nativePtr); } + @Override public String toString() { long columnCount = getColumnCount(); String name = getName(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java index d523c9ae28..4427f98e77 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java @@ -34,6 +34,7 @@ public RealmAsyncTaskImpl(Future pendingTask, ThreadPoolExecutor service) { /** * {@inheritDoc} */ + @Override public void cancel() { pendingTask.cancel(true); isCancelled = true; @@ -53,6 +54,7 @@ public void cancel() { /** * {@inheritDoc} */ + @Override public boolean isCancelled() { return isCancelled; } From b9cf8810f1fbdbe9ba5179911fd83b27210c398e Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Sat, 18 Mar 2017 09:11:35 +0900 Subject: [PATCH 0562/2110] more fix for ErrorProne warnings in generated code --- .../main/java/io/realm/processor/RealmProxyClassGenerator.java | 1 + .../src/test/resources/io/realm/AllTypesRealmProxy.java | 1 + .../src/test/resources/io/realm/BooleansRealmProxy.java | 1 + .../src/test/resources/io/realm/NullTypesRealmProxy.java | 1 + 4 files changed, 4 insertions(+) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index aae37a59e4..528e3a9213 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -1608,6 +1608,7 @@ private void emitToStringMethod(JavaWriter writer) throws IOException { return; } writer.emitAnnotation("Override"); + writer.emitAnnotation("SuppressWarnings", "\"ArrayToString\""); writer.beginMethod("String", "toString", EnumSet.of(Modifier.PUBLIC)); writer.beginControlFlow("if (!RealmObject.isValid(this))"); writer.emitStatement("return \"Invalid object\""); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 44a01b7283..141f963935 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -1209,6 +1209,7 @@ static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, so } @Override + @SuppressWarnings("ArrayToString") public String toString() { if (!RealmObject.isValid(this)) { return "Invalid object"; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index a2b73d4c2f..824bd1df56 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -503,6 +503,7 @@ public static some.test.Booleans createDetachedCopy(some.test.Booleans realmObje } @Override + @SuppressWarnings("ArrayToString") public String toString() { if (!RealmObject.isValid(this)) { return "Invalid object"; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 9bad83c745..6e65e99547 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -2086,6 +2086,7 @@ public static some.test.NullTypes createDetachedCopy(some.test.NullTypes realmOb } @Override + @SuppressWarnings("ArrayToString") public String toString() { if (!RealmObject.isValid(this)) { return "Invalid object"; From 6d071200d8dba9eee93160bdd001a38de966faee Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 20 Mar 2017 19:47:54 +0900 Subject: [PATCH 0563/2110] fix warnings reported by ErrorProne (#4341) --- .../ManagedOrderedRealmCollectionTests.java | 1 + .../io/realm/ManagedRealmCollectionTests.java | 12 +++- .../java/io/realm/NotificationsTest.java | 3 +- .../OrderedRealmCollectionIteratorTests.java | 8 +++ .../io/realm/OrderedRealmCollectionTests.java | 6 +- .../java/io/realm/RealmCacheTests.java | 1 + .../java/io/realm/RealmJsonTests.java | 7 ++- .../java/io/realm/RealmListTests.java | 4 +- .../java/io/realm/RealmObjectSchemaTests.java | 8 +-- .../java/io/realm/RealmQueryTests.java | 60 +++++++++---------- .../java/io/realm/RealmResultsTests.java | 4 +- .../androidTest/java/io/realm/RealmTests.java | 6 +- .../io/realm/TypeBasedNotificationsTests.java | 6 +- .../java/io/realm/rule/RunInLooperThread.java | 4 +- .../benchmarks/config/CSVResultProcessor.java | 3 +- .../objectServer/java/io/realm/SyncUser.java | 3 +- .../internal/network/LogoutResponse.java | 1 + .../network/NetworkStateReceiver.java | 1 + .../objectserver/ProcessCommitTests.java | 7 ++- 19 files changed, 89 insertions(+), 56 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index bb873d1956..8e81f62878 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -796,6 +796,7 @@ public Boolean call() throws Exception { case SORT_2FIELDS: case SORT_MULTI: expected = UnsupportedOperationException.class; + break; default: break; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 809dbdf5f7..9cd511e157 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -760,13 +760,16 @@ public void mutableMethodsOutsideTransactions() { case REMOVE_ALL: case RETAIN_ALL: expected = UnsupportedOperationException.class; + break; + default: + // use default exception } } try { switch (method) { case DELETE_ALL: collection.deleteAllFromRealm(); break; - case ADD_OBJECT: collection.add(new AllJavaTypes()); + case ADD_OBJECT: collection.add(new AllJavaTypes()); break; case ADD_ALL_OBJECTS: collection.addAll(Collections.singletonList(new AllJavaTypes())); break; case CLEAR: collection.clear(); break; case REMOVE_OBJECT: collection.remove(new AllJavaTypes()); break; @@ -847,6 +850,9 @@ public Boolean call() throws Exception { case REMOVE_ALL: case RETAIN_ALL: expected = UnsupportedOperationException.class; + break; + default: + // use default exception } } @@ -854,8 +860,8 @@ public Boolean call() throws Exception { switch (method) { case ADD_OBJECT: collection.add(new AllJavaTypes()); break; case ADD_ALL_OBJECTS: collection.addAll(Collections.singletonList(new AllJavaTypes())); break; - case CLEAR: collection.clear(); - case CONTAINS: + case CLEAR: collection.clear(); break; + case CONTAINS: collection.contains(tempObject); break; case CONTAINS_ALL: collection.containsAll(Collections.singletonList(tempObject)); break; case EQUALS: //noinspection ResultOfMethodCallIgnored diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index fd7d5d8035..8e3f499af6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -564,7 +564,8 @@ public void looperThreadQuitsLooperEarly() throws InterruptedException { // Starts background looper and let it hang. ExecutorService executorService = Executors.newSingleThreadExecutor(); - executorService.submit(new Runnable() { + //noinspection unused + final Future future = executorService.submit(new Runnable() { @Override public void run() { Looper.prepare(); // Fake background thread with a looper, eg. a IntentService. diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index ac34cedc51..534730bb58 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -528,6 +528,11 @@ public void listIterator_closedRealm_methods() { @Test public void listIterator_deleteManagedObjectIndirectly() { + if (skipTest(CollectionClass.REALMRESULTS_SNAPSHOT_LIST_BASE, + CollectionClass.REALMRESULTS_SNAPSHOT_RESULTS_BASE)) { + return; + } + realm.beginTransaction(); ListIterator it = collection.listIterator(); it.next(); @@ -542,6 +547,9 @@ public void listIterator_deleteManagedObjectIndirectly() { case UNMANAGED_REALMLIST: assertEquals(TEST_SIZE, collection.size()); break; + default: + fail(); + return; } it.previous(); AllJavaTypes types = it.next(); // Iterator can still access the deleted object diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java index d722f024de..5a9ef34cb2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java @@ -369,9 +369,9 @@ public void methods_indexOutOfBounds() { case ADD_ALL_INDEX: collection.addAll(1, Collections.singleton(new AllJavaTypes())); break; case GET_INDEX: collection.get(1); break; case LIST_ITERATOR_INDEX: collection.listIterator(1); break; - case REMOVE_INDEX: collection.remove(1); - case SET: collection.set(1, new AllJavaTypes()); - case SUBLIST: collection.subList(1, 2); + case REMOVE_INDEX: collection.remove(1); break; + case SET: collection.set(1, new AllJavaTypes()); break; + case SUBLIST: collection.subList(1, 2); break; // Cannot fail with IndexOutOfBounds case FIRST: diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index 99375d1b2d..1085a371b6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -106,6 +106,7 @@ public void getInstanceClearsCacheWhenFailed() { realm.close(); try { Realm.getInstance(configB); // Tries to open with key 2. + fail(); } catch (RealmFileException expected) { assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR); // Deletes Realm so key 2 works. This should work as a Realm shouldn't be cached diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index 718abc0f41..351024dcd8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -37,6 +37,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -66,6 +67,8 @@ @RunWith(AndroidJUnit4.class) public class RealmJsonTests { + private static final Charset UTF_8 = Charset.forName("UTF-8"); + @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @@ -87,7 +90,7 @@ public void tearDown() { } private InputStream convertJsonObjectToStream(JSONObject obj) { - return new ByteArrayInputStream(obj.toString().getBytes()); + return new ByteArrayInputStream(obj.toString().getBytes(UTF_8)); } // Asserts that the list of AllTypesPrimaryKey objects where inserted and updated properly. @@ -198,7 +201,7 @@ public void createObjectFromJson_allSimpleObjectAllTypes() throws JSONException json.put("columnFloat", 1.23F); json.put("columnDouble", 1.23D); json.put("columnBoolean", true); - json.put("columnBinary", new String(Base64.encode(new byte[] {1,2,3}, Base64.DEFAULT))); + json.put("columnBinary", new String(Base64.encode(new byte[] {1,2,3}, Base64.DEFAULT), UTF_8)); realm.beginTransaction(); realm.createObjectFromJson(AllTypes.class, json); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index cd9d35516a..cd4e057f84 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -746,8 +746,8 @@ public void realmMethods_onDeletedLinkView() { case SORT: results.sort(CyclicType.FIELD_NAME); break; case SORT_FIELD: results.sort(CyclicType.FIELD_NAME, Sort.ASCENDING); break; case SORT_2FIELDS: results.sort(CyclicType.FIELD_NAME, Sort.ASCENDING, CyclicType.FIELD_DATE, Sort.DESCENDING); break; - case SORT_MULTI: results.sort(new String[] { CyclicType.FIELD_NAME, CyclicType.FIELD_DATE }, new Sort[] { Sort.ASCENDING, Sort.DESCENDING}); - case CREATE_SNAPSHOT: results.createSnapshot(); + case SORT_MULTI: results.sort(new String[] { CyclicType.FIELD_NAME, CyclicType.FIELD_DATE }, new Sort[] { Sort.ASCENDING, Sort.DESCENDING}); break; + case CREATE_SNAPSHOT: results.createSnapshot(); break; } fail(method + " should have thrown an Exception"); } catch (IllegalStateException ignored) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index a29dbee7d4..01745e62f6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -111,7 +111,7 @@ public enum IndexFieldType { BOOLEAN(Boolean.class), PRIMITIVE_BOOLEAN(boolean.class), DATE(Date.class); - Class clazz; + private final Class clazz; public Class getType() { return clazz; @@ -129,7 +129,7 @@ public enum InvalidIndexFieldType { OBJECT(RealmObject.class), LIST(RealmList.class); - Class clazz; + private final Class clazz; public Class getType() { return clazz; @@ -148,7 +148,7 @@ public enum PrimaryKeyFieldType { LONG(Long.class), PRIMITIVE_LONG(long.class), BYTE(Byte.class), PRIMITIVE_BYTE(byte.class); - Class clazz; + private final Class clazz; public Class getType() { return clazz; @@ -168,7 +168,7 @@ public enum InvalidPrimaryKeyFieldType { OBJECT(RealmObject.class), LIST(RealmList.class); - Class clazz; + private final Class clazz; public Class getType() { return clazz; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index c1939d26aa..b10f607f14 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -99,14 +99,14 @@ private void populateTestRealm(Realm testRealm, int dataSize) { allTypes.setColumnBinary(new byte[]{1, 2, 3}); allTypes.setColumnDate(new Date(DECADE_MILLIS * (i - (dataSize / 2)))); allTypes.setColumnDouble(3.1415); - allTypes.setColumnFloat(1.234567f + i); + allTypes.setColumnFloat(1.2345f + i); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); NonLatinFieldNames nonLatinFieldNames = testRealm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); - nonLatinFieldNames.set베타(1.234567f + i); - nonLatinFieldNames.setΒήτα(1.234567f + i); + nonLatinFieldNames.set베타(1.2345f + i); + nonLatinFieldNames.setΒήτα(1.2345f + i); Dog dog = testRealm.createObject(Dog.class); dog.setAge(i); @@ -137,8 +137,8 @@ private void populateNoPrimaryKeyNullTypesRows(Realm testRealm, int dataSize) { noPrimaryKeyNullTypes.setFieldIntegerNotNull(i); noPrimaryKeyNullTypes.setFieldLongNull((i % 3) == 0 ? null : (long) i); noPrimaryKeyNullTypes.setFieldLongNotNull((long) i); - noPrimaryKeyNullTypes.setFieldFloatNull((i % 3) == 0 ? null : 1.234567f + i); - noPrimaryKeyNullTypes.setFieldFloatNotNull(1.234567f + i); + noPrimaryKeyNullTypes.setFieldFloatNull((i % 3) == 0 ? null : 1.2345f + i); + noPrimaryKeyNullTypes.setFieldFloatNotNull(1.2345f + i); noPrimaryKeyNullTypes.setFieldDoubleNull((i % 3) == 0 ? null : 3.1415 + i); noPrimaryKeyNullTypes.setFieldDoubleNotNull(3.1415 + i); noPrimaryKeyNullTypes.setFieldDateNull((i % 3) == 0 ? null : new Date(DECADE_MILLIS * (i - (dataSize / 2)))); @@ -467,14 +467,14 @@ public void greaterThan() { populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class) - .greaterThan(AllTypes.FIELD_FLOAT, 10.234567f).findAll(); + .greaterThan(AllTypes.FIELD_FLOAT, 10.2345f).findAll(); assertEquals(TEST_OBJECTS_COUNT - 10, resultList.size()); resultList = realm.where(AllTypes.class).beginsWith(AllTypes.FIELD_STRING, "test data 1") - .greaterThan(AllTypes.FIELD_FLOAT, 50.234567f).findAll(); - assertEquals(TEST_OBJECTS_COUNT - 100, resultList.size()); + .greaterThan(AllTypes.FIELD_FLOAT, 150.2345f).findAll(); + assertEquals(TEST_OBJECTS_COUNT - 150, resultList.size()); - RealmQuery query = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_FLOAT, 11.234567f); + RealmQuery query = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_FLOAT, 11.2345f); resultList = query.between(AllTypes.FIELD_LONG, 1, 20).findAll(); assertEquals(10, resultList.size()); } @@ -504,15 +504,15 @@ public void greaterThanOrEqualTo() { populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class) - .greaterThanOrEqualTo(AllTypes.FIELD_FLOAT, 10.234567f).findAll(); + .greaterThanOrEqualTo(AllTypes.FIELD_FLOAT, 10.2345f).findAll(); assertEquals(TEST_OBJECTS_COUNT - 9, resultList.size()); resultList = realm.where(AllTypes.class).beginsWith(AllTypes.FIELD_STRING, "test data 1") - .greaterThanOrEqualTo(AllTypes.FIELD_FLOAT, 50.234567f).findAll(); + .greaterThanOrEqualTo(AllTypes.FIELD_FLOAT, 50.2345f).findAll(); assertEquals(TEST_OBJECTS_COUNT - 100, resultList.size()); RealmQuery query = realm.where(AllTypes.class) - .greaterThanOrEqualTo(AllTypes.FIELD_FLOAT, 11.234567f); + .greaterThanOrEqualTo(AllTypes.FIELD_FLOAT, 11.2345f); query = query.between(AllTypes.FIELD_LONG, 1, 20); resultList = query.beginsWith(AllTypes.FIELD_STRING, "test data 15").findAll(); @@ -541,7 +541,7 @@ public void greaterThanOrEqualTo_date() { public void or() { populateTestRealm(realm, 200); - RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 31.234567f); + RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 31.2345f); RealmResults resultList = query.or().between(AllTypes.FIELD_LONG, 1, 20).findAll(); assertEquals(21, resultList.size()); @@ -559,12 +559,12 @@ public void or_missingFilters() { @Test(expected = UnsupportedOperationException.class) public void or_missingFilterBefore() { - realm.where(AllTypes.class).or().equalTo(AllTypes.FIELD_FLOAT, 31.234567f).findAll(); + realm.where(AllTypes.class).or().equalTo(AllTypes.FIELD_FLOAT, 31.2345f).findAll(); } @Test(expected = UnsupportedOperationException.class) public void or_missingFilterAfter() { - realm.where(AllTypes.class).or().equalTo(AllTypes.FIELD_FLOAT, 31.234567f).findAll(); + realm.where(AllTypes.class).or().equalTo(AllTypes.FIELD_FLOAT, 31.2345f).findAll(); } @Test @@ -608,11 +608,11 @@ public void not_aloneThrows() { public void and_implicit() { populateTestRealm(realm, 200); - RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 31.234567f); + RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 31.2345f); RealmResults resultList = query.between(AllTypes.FIELD_LONG, 1, 10).findAll(); assertEquals(0, resultList.size()); - query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 81.234567f); + query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 81.2345f); resultList = query.between(AllTypes.FIELD_LONG, 1, 100).findAll(); assertEquals(1, resultList.size()); } @@ -623,9 +623,9 @@ public void lessThan() { populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class). - lessThan(AllTypes.FIELD_FLOAT, 31.234567f).findAll(); + lessThan(AllTypes.FIELD_FLOAT, 31.2345f).findAll(); assertEquals(30, resultList.size()); - RealmQuery query = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_FLOAT, 31.234567f); + RealmQuery query = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_FLOAT, 31.2345f); resultList = query.between(AllTypes.FIELD_LONG, 1, 10).findAll(); assertEquals(10, resultList.size()); } @@ -654,9 +654,9 @@ public void lessThanOrEqualTo() { populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class) - .lessThanOrEqualTo(AllTypes.FIELD_FLOAT, 31.234567f).findAll(); + .lessThanOrEqualTo(AllTypes.FIELD_FLOAT, 31.2345f).findAll(); assertEquals(31, resultList.size()); - resultList = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_FLOAT, 31.234567f) + resultList = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_FLOAT, 31.2345f) .between(AllTypes.FIELD_LONG, 11, 20).findAll(); assertEquals(10, resultList.size()); } @@ -684,7 +684,7 @@ public void equalTo() { populateTestRealm(realm, 200); RealmResults resultList = realm.where(AllTypes.class) - .equalTo(AllTypes.FIELD_FLOAT, 31.234567f).findAll(); + .equalTo(AllTypes.FIELD_FLOAT, 31.2345f).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_FLOAT, 11.0f) .equalTo(AllTypes.FIELD_LONG, 10).findAll(); @@ -843,13 +843,13 @@ private void doTestForInFloat(String targetField) { fail(); } catch (IllegalArgumentException ignored) { } - RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{1.234567f + 1}).findAll(); + RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{1.2345f + 1}).findAll(); assertEquals(1, resultList.size()); - resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{1.234567f + 2}).findAll(); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{1.2345f + 2}).findAll(); assertEquals(1, resultList.size()); - resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{1.234567f + 1, 1.234567f + 2}).findAll(); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{1.2345f + 1, 1.2345f + 2}).findAll(); assertEquals(2, resultList.size()); - resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Float[]{1.234567f + 1, 1.234567f + 2}).findAll(); + resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Float[]{1.2345f + 1, 1.2345f + 2}).findAll(); assertEquals(198, resultList.size()); } @@ -1013,7 +1013,7 @@ public void in_doubleNull() { public void in_floatNotNull() { doTestForInFloat(NoPrimaryKeyNullTypes.FIELD_FLOAT_NOT_NULL); try { - realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_FLOAT_NOT_NULL, new Float[]{1.234567f + 1, null, 1.234567f + 2}).findAll(); + realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_FLOAT_NOT_NULL, new Float[]{1.2345f + 1, null, 1.2345f + 2}).findAll(); fail(); } catch (IllegalArgumentException ignored) { } @@ -1022,7 +1022,7 @@ public void in_floatNotNull() { @Test public void in_floatNull() { doTestForInFloat(NoPrimaryKeyNullTypes.FIELD_FLOAT_NULL); - RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_FLOAT_NULL, new Float[]{1.234567f + 1, null, 1.234567f + 2}).findAll(); + RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_FLOAT_NULL, new Float[]{1.2345f + 1, null, 1.2345f + 2}).findAll(); assertEquals(131, resultList.size()); } @@ -1103,11 +1103,11 @@ public void notEqualTo() { .notEqualTo(AllTypes.FIELD_LONG, 31).findAll(); assertEquals(TEST_OBJECTS_COUNT - 1, resultList.size()); - resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_FLOAT, 11.234567f) + resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_FLOAT, 11.2345f) .equalTo(AllTypes.FIELD_LONG, 10).findAll(); assertEquals(0, resultList.size()); - resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_FLOAT, 11.234567f) + resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_FLOAT, 11.2345f) .equalTo(AllTypes.FIELD_LONG, 1).findAll(); assertEquals(1, resultList.size()); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 78946420d6..4b4b690e96 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -128,10 +128,10 @@ public void unsupportedMethods() { for (CollectionMutatorMethod method : CollectionMutatorMethod.values()) { try { switch (method) { - case ADD_OBJECT: collection.add(new AllTypes()); + case ADD_OBJECT: collection.add(new AllTypes()); break; case ADD_ALL_OBJECTS: collection.addAll(Collections.singletonList(new AllTypes())); break; case CLEAR: collection.clear(); break; - case REMOVE_OBJECT: collection.remove(new AllTypes()); + case REMOVE_OBJECT: collection.remove(new AllTypes()); break; case REMOVE_ALL: collection.removeAll(Collections.singletonList(new AllTypes())); break; case RETAIN_ALL: collection.retainAll(Collections.singletonList(new AllTypes())); break; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index a4ddd7fb10..4ce3c83145 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -200,6 +200,7 @@ public void getInstance_writeProtectedFile() throws IOException { .directory(folder) .name(REALM_FILE) .build()); + fail(); } catch (RealmFileException expected) { assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); } @@ -216,6 +217,7 @@ public void getInstance_writeProtectedFileWithContext() throws IOException { try { Realm.getInstance(new RealmConfiguration.Builder(context).directory(folder).name(REALM_FILE).build()); + fail(); } catch (RealmFileException expected) { assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); } @@ -3554,7 +3556,7 @@ public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); - bgRealmWaitResult.set(new Boolean(realm.waitForChange())); + bgRealmWaitResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } @@ -3572,7 +3574,7 @@ public void run() { // Now we'll stop realm from waiting. bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmClosed); - assertFalse(bgRealmWaitResult.get().booleanValue()); + assertFalse(bgRealmWaitResult.get()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index c219ab9742..e3150acf8c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -24,13 +24,13 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.Date; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -54,6 +54,8 @@ @RunWith(AndroidJUnit4.class) public class TypeBasedNotificationsTests { + private static final Charset UTF_8 = Charset.forName("UTF-8"); + @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); @Rule @@ -321,7 +323,7 @@ public void run() { json.put("columnFloat", 1.23f); json.put("columnDouble", 1.23d); json.put("columnBoolean", true); - json.put("columnBinary", new String(Base64.encode(new byte[]{1, 2, 3}, Base64.DEFAULT))); + json.put("columnBinary", new String(Base64.encode(new byte[]{1, 2, 3}, Base64.DEFAULT), UTF_8)); realm.beginTransaction(); final AllTypes objectFromJson = realm.createObjectFromJson(AllTypes.class, json); diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index b81238326a..704deb9485 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -31,6 +31,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import io.realm.Realm; @@ -108,7 +109,8 @@ public Thread newThread(Runnable runnable) { return new Thread(runnable, threadName); } }); - executorService.submit(new Runnable() { + //noinspection unused + final Future submit = executorService.submit(new Runnable() { @Override public void run() { Looper.prepare(); diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java index f8737f2fb2..54e69d495d 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.Charset; import java.text.DecimalFormat; import dk.ilios.spanner.model.Trial; @@ -46,7 +47,7 @@ public CSVResultProcessor(File resultFile) { this.resultFile = resultFile; this.workFile = new File(resultFile.getPath() + ".tmp"); try { - writer = new CSVWriter(new FileWriter(resultFile)); + writer = new CSVWriter(Files.newWriter(resultFile, Charset.forName("UTF-8"))); addLabels(); } catch (IOException e) { throw new RuntimeException(e); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index eac47154eb..4b5b83639b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -322,7 +322,8 @@ public void logout() { // Finally revoke server token. The local user is logged out in any case. final AuthenticationServer server = SyncManager.getAuthServer(); ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; - networkPoolExecutor.submit(new ExponentialBackoffTask() { + //noinspection unused + final Future future = networkPoolExecutor.submit(new ExponentialBackoffTask() { @Override protected LogoutResponse execute() { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java index ce356546eb..6a0181ca95 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java @@ -80,6 +80,7 @@ private LogoutResponse() { * * @return {@code true} if valid. */ + @Override public boolean isValid() { return (error == null) || (error.getErrorCode() == ErrorCode.EXPIRED_REFRESH_TOKEN); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java index d7f426d350..5f337b1504 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java @@ -77,6 +77,7 @@ public static boolean isOnline(Context context) { } + @Override public void onReceive(Context context, Intent intent) { boolean connected = isOnline(context); for (ConnectionListener listener : listeners) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 68ce590f3e..d8e3e14523 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -31,6 +31,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import io.realm.ObjectServerError; @@ -62,7 +63,8 @@ public void expectServerCommit() throws Throwable { final Throwable[] exception = new Throwable[1]; final CountDownLatch testFinished = new CountDownLatch(1); ExecutorService service = Executors.newSingleThreadExecutor(); - service.submit(new Runnable() { + //noinspection unused + final Future future = service.submit(new Runnable() { @Override public void run() { try { @@ -122,7 +124,8 @@ public void expectALot() throws Throwable { final Throwable[] exception = new Throwable[1]; final CountDownLatch testFinished = new CountDownLatch(1); ExecutorService service = Executors.newSingleThreadExecutor(); - service.submit(new Runnable() { + //noinspection unused + final Future future = service.submit(new Runnable() { @Override public void run() { try { From 23195123b9a81350383b8be9e860ef8c5d294d8a Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 20 Mar 2017 12:00:13 +0000 Subject: [PATCH 0564/2110] Restoring Sync logging (#4315) * Restoring Sync logging --- .../src/main/cpp/io_realm_SyncManager.cpp | 76 ++++++++----------- .../src/main/cpp/jni_util/log.cpp | 9 +-- .../src/main/cpp/jni_util/log.hpp | 6 +- .../java/io/realm/ObjectServer.java | 2 +- .../java/io/realm/SyncManager.java | 7 +- 5 files changed, 35 insertions(+), 65 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index 2ac7d2ce07..bb9d1dab0d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -14,34 +14,21 @@ * limitations under the License. */ -#include - -#include -#include -#include -#include +#include "io_realm_SyncManager.h" #include -#include -#include -#include "io_realm_SyncManager.h" +#include +#include +#include -#include "object-store/src/sync/sync_manager.hpp" -#include "object-store/src/sync/sync_session.hpp" - -#include "binding_callback_thread_observer.hpp" #include "util.hpp" - #include "jni_util/jni_utils.hpp" #include "jni_util/java_method.hpp" using namespace realm; -using namespace realm::sync; using namespace realm::jni_util; -std::unique_ptr sync_client; - struct AndroidClientListener : public realm::BindingCallbackThreadObserver { void did_create_thread() override @@ -59,24 +46,15 @@ struct AndroidClientListener : public realm::BindingCallbackThreadObserver { } } s_client_thread_listener; -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncClient(JNIEnv* env, jclass) -{ - TR_ENTER() - if (sync_client) { - return; - } - - try { - // Setup SyncManager - g_binding_callback_thread_observer = &s_client_thread_listener; - - // Create SyncClient - sync::Client::Config config; - config.logger = &CoreLoggerBridge::shared(); - sync_client = std::make_unique(std::move(config)); // Throws +struct AndroidSyncLoggerFactory : public realm::SyncLoggerFactory { + std::unique_ptr make_logger(Logger::Level level) override + { + auto logger = std::make_unique(std::string("REALM_SYNC")); + logger->set_level_threshold(level); + // Cast to std::unique_ptr + return std::move(logger); } - CATCH_STD() -} +} s_sync_logger_factory; JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass) { @@ -87,32 +65,38 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeConfigureMetaDataSystem(JNIEnv* env, jclass, jstring baseFile) +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNIEnv* env, jclass, jstring sync_base_dir) { TR_ENTER() try { - JStringAccessor base_file_path(env, baseFile); // throws + JStringAccessor base_file_path(env, sync_base_dir); // throws SyncManager::shared().configure_file_system(base_file_path, SyncManager::MetadataMode::NoEncryption); + + // Register Sync Client thread start/stop callback + g_binding_callback_thread_observer = &s_client_thread_listener; + + // init logger + SyncManager::shared().set_logger_factory(s_sync_logger_factory); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeSimulateSyncError(JNIEnv* env, jclass, jstring localRealmPath, - jint errorCode, jstring errorMessage, - jboolean isFatal) +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeSimulateSyncError(JNIEnv* env, jclass, jstring local_realm_path, + jint err_code, jstring err_message, + jboolean is_fatal) { TR_ENTER() try { - JStringAccessor local_realm_path(env, localRealmPath); - JStringAccessor error_message(env, errorMessage); + JStringAccessor path(env, local_realm_path); + JStringAccessor message(env, err_message); - auto session = SyncManager::shared().get_existing_active_session(local_realm_path); + auto session = SyncManager::shared().get_existing_active_session(path); if (!session) { - ThrowException(env, IllegalArgument, concat_stringdata("Session not found: ", local_realm_path)); + ThrowException(env, IllegalArgument, concat_stringdata("Session not found: ", path)); return; } - std::error_code code = std::error_code{static_cast(errorCode), realm::sync::protocol_error_category()}; - SyncSession::OnlyForTesting::handle_error(*session, {code, std::string(error_message), to_bool(isFatal)}); + std::error_code code = std::error_code{static_cast(err_code), realm::sync::protocol_error_category()}; + SyncSession::OnlyForTesting::handle_error(*session, {code, std::string(message), to_bool(is_fatal)}); } CATCH_STD() -} \ No newline at end of file +} diff --git a/realm/realm-library/src/main/cpp/jni_util/log.cpp b/realm/realm-library/src/main/cpp/jni_util/log.cpp index c24d6cdd41..d8b804549d 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.cpp @@ -22,7 +22,6 @@ using namespace realm; using namespace realm::jni_util; using namespace realm::util; -const char* CoreLoggerBridge::TAG = "REALM_CORE"; const char* Log::REALM_JNI_TAG = "REALM_JNI"; Log::Level Log::s_level = Log::Level::warn; @@ -198,11 +197,5 @@ void CoreLoggerBridge::do_log(realm::util::Logger::Level level, std::string msg) case Level::off: // Fall through. throw std::invalid_argument(format("Invalid log level.")); } - Log::shared().log(jni_level, TAG, msg.c_str()); -} - -CoreLoggerBridge& CoreLoggerBridge::shared() -{ - static CoreLoggerBridge log_bridge; - return log_bridge; + Log::shared().log(jni_level, m_tag.c_str(), msg.c_str()); } diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp index c6b579551e..d138f287d2 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -180,13 +180,11 @@ extern std::shared_ptr get_default_logger(); class CoreLoggerBridge : public realm::util::RootLogger { public: + CoreLoggerBridge(std::string tag) : m_tag(std::move(tag)) {} void do_log(Logger::Level, std::string msg) override; - static CoreLoggerBridge& shared(); private: - CoreLoggerBridge(){}; - // Log tag for Realm core & sync. - static const char* TAG; + const std::string m_tag; }; } // namespace jni_util diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index fcf681a3ec..272ead6c89 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -41,7 +41,7 @@ public static void init(Context context) { // init the "sync_manager.cpp" metadata Realm, this is also needed later, when re try // to schedule a client reset. in realm-java#master this is already done, when initialising // the RealmFileUserStore (not available now on releases) - SyncManager.nativeConfigureMetaDataSystem(context.getFilesDir().getPath()); + SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath()); // Configure default UserStore UserStore userStore = new RealmFileUserStore(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index d92f82828b..691538c59b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -104,9 +104,6 @@ public void onClientResetRequired(SyncSession session, ClientResetHandler handle static void init(String appId, UserStore userStore) { SyncManager.APP_ID = appId; SyncManager.userStore = userStore; - - // Initialize underlying Sync Network Client - nativeInitializeSyncClient(); } /** @@ -303,9 +300,7 @@ static void simulateClientReset(SyncSession session) { true); } - private static native void nativeInitializeSyncClient(); - // init and load the Metadata Realm containing SyncUsers - protected static native void nativeConfigureMetaDataSystem(String baseFile); + protected static native void nativeInitializeSyncManager(String syncBaseDir); private static native void nativeReset(); private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); } From 481c2c9d926e382665c667b7b3aa93ef21d6ece0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 20 Mar 2017 14:06:09 +0100 Subject: [PATCH 0565/2110] Upgrade Kotlin example (#4350) --- examples/kotlinExample/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 79d9712d8a..a548e407de 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.0' + ext.kotlin_version = '1.1.1' repositories { jcenter() mavenCentral() From 721753ab6e73537424e36f33c8d8e407ab72d883 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Mon, 20 Mar 2017 12:34:59 -0700 Subject: [PATCH 0566/2110] Run code formatter over all code (#4351) --- .../java/io/realm/processor/Backlink.java | 73 ++-- .../io/realm/processor/ClassMetaData.java | 75 ++-- .../java/io/realm/processor/Constants.java | 6 +- .../processor/DefaultModuleGenerator.java | 1 + .../io/realm/processor/ModuleMetaData.java | 1 + .../realm/processor/RealmJsonTypeHelper.java | 114 +++--- .../io/realm/processor/RealmProcessor.java | 80 ++-- .../processor/RealmProxyClassGenerator.java | 355 ++++++++++-------- .../RealmProxyInterfaceGenerator.java | 11 +- .../RealmProxyMediatorGenerator.java | 5 +- .../realm/processor/RealmVersionChecker.java | 6 +- .../main/java/io/realm/processor/Utils.java | 3 +- .../src/main/java/io/realm/BaseRealm.java | 26 +- .../src/main/java/io/realm/Case.java | 1 + .../src/main/java/io/realm/DynamicRealm.java | 7 +- .../java/io/realm/DynamicRealmObject.java | 60 ++- .../java/io/realm/OrderedRealmCollection.java | 45 +-- .../io/realm/OrderedRealmCollectionImpl.java | 9 +- .../realm/OrderedRealmCollectionSnapshot.java | 3 +- .../src/main/java/io/realm/Property.java | 6 +- .../src/main/java/io/realm/ProxyState.java | 3 +- .../src/main/java/io/realm/Realm.java | 53 +-- .../src/main/java/io/realm/RealmCache.java | 15 +- .../java/io/realm/RealmChangeListener.java | 5 +- .../main/java/io/realm/RealmCollection.java | 12 +- .../java/io/realm/RealmConfiguration.java | 67 ++-- .../main/java/io/realm/RealmFieldType.java | 41 +- .../src/main/java/io/realm/RealmList.java | 31 +- .../src/main/java/io/realm/RealmModel.java | 4 +- .../src/main/java/io/realm/RealmObject.java | 59 ++- .../main/java/io/realm/RealmObjectSchema.java | 17 +- .../src/main/java/io/realm/RealmQuery.java | 59 +-- .../src/main/java/io/realm/RealmResults.java | 17 +- .../src/main/java/io/realm/RealmSchema.java | 5 +- .../java/io/realm/exceptions/RealmError.java | 1 + .../io/realm/exceptions/RealmException.java | 1 + .../realm/exceptions/RealmFileException.java | 1 + .../RealmMigrationNeededException.java | 3 +- .../RealmPrimaryKeyConstraintException.java | 1 + .../java/io/realm/internal/CheckedRow.java | 23 ++ .../java/io/realm/internal/Collection.java | 52 ++- .../realm/internal/CollectionChangeSet.java | 5 +- .../java/io/realm/internal/ColumnIndices.java | 1 + .../java/io/realm/internal/ColumnInfo.java | 9 +- .../main/java/io/realm/internal/Context.java | 1 + .../io/realm/internal/FieldDescriptor.java | 2 +- .../io/realm/internal/FinalizerRunnable.java | 3 +- .../java/io/realm/internal/IdentitySet.java | 5 +- .../java/io/realm/internal/InvalidRow.java | 1 + .../src/main/java/io/realm/internal/Keep.java | 1 + .../java/io/realm/internal/KeepMember.java | 1 + .../main/java/io/realm/internal/LinkView.java | 19 +- .../realm/internal/NativeObjectReference.java | 7 +- .../io/realm/internal/ObjectServerFacade.java | 2 + .../io/realm/internal/ObserverPairList.java | 1 + .../java/io/realm/internal/PendingRow.java | 3 +- .../java/io/realm/internal/RealmCore.java | 8 +- .../java/io/realm/internal/RealmNotifier.java | 6 +- .../io/realm/internal/RealmObjectProxy.java | 7 +- .../io/realm/internal/RealmProxyMediator.java | 29 +- .../src/main/java/io/realm/internal/Row.java | 2 +- .../java/io/realm/internal/SharedRealm.java | 58 ++- .../io/realm/internal/SortDescriptor.java | 3 +- .../main/java/io/realm/internal/Table.java | 210 ++++++++--- .../java/io/realm/internal/TableQuery.java | 123 ++++-- .../java/io/realm/internal/TableSchema.java | 1 + .../main/java/io/realm/internal/TestUtil.java | 2 + .../java/io/realm/internal/UncheckedRow.java | 33 +- .../src/main/java/io/realm/internal/Util.java | 16 +- .../internal/android/AndroidCapabilities.java | 5 +- .../android/AndroidRealmNotifier.java | 1 + .../realm/internal/android/ISO8601Utils.java | 17 +- .../io/realm/internal/android/JsonUtils.java | 5 +- .../internal/async/BadVersionException.java | 1 + .../internal/async/BgPriorityCallable.java | 1 + .../internal/async/RealmAsyncTaskImpl.java | 1 + .../async/RealmThreadPoolExecutor.java | 3 +- .../internal/modules/CompositeMediator.java | 13 +- .../internal/modules/FilterableMediator.java | 14 +- .../src/main/java/io/realm/log/LogLevel.java | 2 +- .../src/main/java/io/realm/log/RealmLog.java | 9 +- .../main/java/io/realm/log/RealmLogger.java | 1 + .../io/realm/rx/RealmObservableFactory.java | 1 + .../java/io/realm/rx/RxObservableFactory.java | 25 +- 84 files changed, 1252 insertions(+), 763 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java index 16c49d43eb..ceeaff107a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java @@ -22,6 +22,7 @@ import io.realm.annotations.LinkingObjects; import io.realm.annotations.Required; + /** * A Backlink is an implicit backwards reference. If field sourceField in instance I * of type SourceClass holds a reference to instance J of type TargetClass, @@ -32,9 +33,9 @@ * To expose backinks for use, create a declaration as follows: * * class TargetClass { - * // ... - * {@literal @}LinkingObjects("sourceField") - * final RealmResults<SourceClass> targetField = null; + * // ... + * {@literal @}LinkingObjects("sourceField") + * final RealmResults<SourceClass> targetField = null; * } * . *

            @@ -129,54 +130,54 @@ public boolean validateSource() { // A @LinkingObjects cannot be @Required if (backlink.getAnnotation(Required.class) != null) { Utils.error(String.format( - "The @LinkingObjects field \"%s.%s\" cannot be @Required.", - targetClass, - targetField)); + "The @LinkingObjects field \"%s.%s\" cannot be @Required.", + targetClass, + targetField)); return false; } // The annotation must have an argument, identifying the linked field if ((sourceField == null) || sourceField.equals("")) { Utils.error(String.format( - "The @LinkingObjects annotation for the field \"%s.%s\" must have a parameter identifying the link target.", - targetClass, - targetField)); + "The @LinkingObjects annotation for the field \"%s.%s\" must have a parameter identifying the link target.", + targetClass, + targetField)); return false; } // Using link syntax to try to reference a linked field is not possible. if (sourceField.contains(".")) { Utils.error(String.format( - "The parameter to the @LinkingObjects annotation for the field \"%s.%s\" contains a '.'. The use of '.' to specify fields in referenced classes is not supported.", - targetClass, - targetField)); + "The parameter to the @LinkingObjects annotation for the field \"%s.%s\" contains a '.'. The use of '.' to specify fields in referenced classes is not supported.", + targetClass, + targetField)); return false; } // The annotated element must be a RealmResult if (!Utils.isRealmResults(backlink)) { Utils.error(String.format( - "The field \"%s.%s\" is a \"%s\". Fields annotated with @LinkingObjects must be RealmResults.", - targetClass, - targetField, - backlink.asType())); + "The field \"%s.%s\" is a \"%s\". Fields annotated with @LinkingObjects must be RealmResults.", + targetClass, + targetField, + backlink.asType())); return false; } if (sourceClass == null) { Utils.error(String.format( - "\"The field \"%s.%s\", annotated with @LinkingObjects, must specify a generic type.", - targetClass, - targetField)); + "\"The field \"%s.%s\", annotated with @LinkingObjects, must specify a generic type.", + targetClass, + targetField)); return false; } // A @LinkingObjects field must be final if (!backlink.getModifiers().contains(Modifier.FINAL)) { Utils.error(String.format( - "A @LinkingObjects field \"%s.%s\" must be final.", - targetClass, - targetField)); + "A @LinkingObjects field \"%s.%s\" must be final.", + targetClass, + targetField)); return false; } @@ -188,23 +189,23 @@ public boolean validateTarget(ClassMetaData clazz) { if (field == null) { Utils.error(String.format( - "Field \"%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", does not exist in class \"%s\".", - sourceField, - targetClass, - targetField, - sourceClass)); + "Field \"%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", does not exist in class \"%s\".", + sourceField, + targetClass, + targetField, + sourceClass)); return false; } String fieldType = field.asType().toString(); if (!(targetClass.equals(fieldType) || targetClass.equals(Utils.getRealmListType(field)))) { Utils.error(String.format( - "Field \"%s.%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", has type \"%s\" instead of \"%3$s\".", - sourceClass, - sourceField, - targetClass, - targetField, - fieldType)); + "Field \"%s.%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", has type \"%s\" instead of \"%3$s\".", + sourceClass, + sourceField, + targetClass, + targetField, + fieldType)); return false; } @@ -225,9 +226,9 @@ public boolean equals(Object o) { Backlink backlink = (Backlink) o; return targetClass.equals(backlink.targetClass) - && targetField.equals(backlink.targetField) - && sourceClass.equals(backlink.sourceClass) - && sourceField.equals(backlink.sourceField); + && targetField.equals(backlink.targetField) + && sourceClass.equals(backlink.sourceClass) + && sourceField.equals(backlink.sourceField); } @Override diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index 9c187ca09a..02b1e46921 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -44,6 +44,7 @@ import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; + /** * Utility class for holding metadata for RealmProxy classes. */ @@ -73,11 +74,11 @@ public ClassMetaData(ProcessingEnvironment env, TypeElement clazz) { elements = env.getElementUtils(); TypeMirror stringType = env.getElementUtils().getTypeElement("java.lang.String").asType(); validPrimaryKeyTypes = Arrays.asList( - stringType, - typeUtils.getPrimitiveType(TypeKind.SHORT), - typeUtils.getPrimitiveType(TypeKind.INT), - typeUtils.getPrimitiveType(TypeKind.LONG), - typeUtils.getPrimitiveType(TypeKind.BYTE) + stringType, + typeUtils.getPrimitiveType(TypeKind.SHORT), + typeUtils.getPrimitiveType(TypeKind.INT), + typeUtils.getPrimitiveType(TypeKind.LONG), + typeUtils.getPrimitiveType(TypeKind.BYTE) ); for (Element element : classType.getEnclosedElements()) { @@ -234,8 +235,7 @@ public boolean generate() { } TypeElement parentElement = (TypeElement) Utils.getSuperClass(classType); - if (!parentElement.toString().equals("java.lang.Object") && !parentElement.toString().equals("io.realm.RealmObject")) - { + if (!parentElement.toString().equals("java.lang.Object") && !parentElement.toString().equals("io.realm.RealmObject")) { Utils.error("Valid model classes must either extend RealmObject or implement RealmModel.", classType); return false; } @@ -295,9 +295,9 @@ private boolean checkListTypes() { TypeElement typeElement = elements.getTypeElement(genericCanonicalType); if (typeElement.getSuperclass().getKind() == TypeKind.NONE) { Utils.error( - "Only concrete Realm classes are allowed in RealmLists. " - + "Neither interfaces nor abstract classes are allowed.", - field); + "Only concrete Realm classes are allowed in RealmLists. " + + "Neither interfaces nor abstract classes are allowed.", + field); return false; } } @@ -313,9 +313,9 @@ private boolean checkReferenceTypes() { TypeElement typeElement = elements.getTypeElement(field.asType().toString()); if (typeElement.getSuperclass().getKind() == TypeKind.NONE) { Utils.error( - "Only concrete Realm classes can be referenced from model classes. " - + "Neither interfaces nor abstract classes are allowed.", - field); + "Only concrete Realm classes can be referenced from model classes. " + + "Neither interfaces nor abstract classes are allowed.", + field); return false; } } @@ -328,8 +328,8 @@ private boolean checkReferenceTypes() { private boolean checkDefaultConstructor() { if (!hasDefaultConstructor) { Utils.error(String.format( - "Class \"%s\" must declare a public constructor with no arguments if it contains custom constructors.", - className)); + "Class \"%s\" must declare a public constructor with no arguments if it contains custom constructors.", + className)); return false; } else { return true; @@ -340,7 +340,7 @@ private boolean checkForFinalFields() { for (VariableElement field : fields) { if (field.getModifiers().contains(Modifier.FINAL)) { Utils.error(String.format( - "Class \"%s\" contains illegal final field \"%s\".", className, field.getSimpleName().toString())); + "Class \"%s\" contains illegal final field \"%s\".", className, field.getSimpleName().toString())); return false; } } @@ -351,9 +351,9 @@ private boolean checkForTransientFields() { for (VariableElement field : fields) { if (field.getModifiers().contains(Modifier.TRANSIENT)) { Utils.error(String.format( - "Class \"%s\" contains illegal transient field \"%s\".", - className, - field.getSimpleName().toString())); + "Class \"%s\" contains illegal transient field \"%s\".", + className, + field.getSimpleName().toString())); return false; } } @@ -364,9 +364,9 @@ private boolean checkForVolatileFields() { for (VariableElement field : fields) { if (field.getModifiers().contains(Modifier.VOLATILE)) { Utils.error(String.format( - "Class \"%s\" contains illegal volatile field \"%s\".", - className, - field.getSimpleName().toString())); + "Class \"%s\" contains illegal volatile field \"%s\".", + className, + field.getSimpleName().toString())); return false; } } @@ -418,11 +418,10 @@ private boolean categorizeIndexField(Element element, VariableElement variableEl String elementTypeCanonicalName = variableElement.asType().toString(); String columnType = Constants.JAVA_TO_COLUMN_TYPES.get(elementTypeCanonicalName); if (columnType != null && - (columnType.equals("RealmFieldType.STRING") || - columnType.equals("RealmFieldType.DATE") || - columnType.equals("RealmFieldType.INTEGER") || - columnType.equals("RealmFieldType.BOOLEAN"))) - { + (columnType.equals("RealmFieldType.STRING") || + columnType.equals("RealmFieldType.DATE") || + columnType.equals("RealmFieldType.INTEGER") || + columnType.equals("RealmFieldType.BOOLEAN"))) { indexedFields.add(variableElement); } else { Utils.error(String.format("Field \"%s\" of type \"%s\" cannot be an @Index.", element, element.asType())); @@ -436,17 +435,17 @@ private boolean categorizeIndexField(Element element, VariableElement variableEl private void categorizeRequiredField(Element element, VariableElement variableElement) { if (Utils.isPrimitiveType(variableElement)) { Utils.error(String.format( - "@Required annotation is unnecessary for primitive field \"%s\".", element)); + "@Required annotation is unnecessary for primitive field \"%s\".", element)); } else if (Utils.isRealmList(variableElement) || Utils.isRealmModel(variableElement)) { Utils.error(String.format( - "Field \"%s\" with type \"%s\" cannot be @Required.", element, element.asType())); + "Field \"%s\" with type \"%s\" cannot be @Required.", element, element.asType())); } else { // Should never get here - user should remove @Required if (nullableFields.contains(variableElement)) { Utils.error(String.format( - "Field \"%s\" with type \"%s\" appears to be nullable. Consider removing @Required.", - element, - element.asType())); + "Field \"%s\" with type \"%s\" appears to be nullable. Consider removing @Required.", + element, + element.asType())); } } } @@ -456,18 +455,18 @@ private void categorizeRequiredField(Element element, VariableElement variableEl private boolean categorizePrimaryKeyField(VariableElement variableElement) { if (primaryKey != null) { Utils.error(String.format( - "A class cannot have more than one @PrimaryKey. Both \"%s\" and \"%s\" are annotated as @PrimaryKey.", - primaryKey.getSimpleName().toString(), - variableElement.getSimpleName().toString())); + "A class cannot have more than one @PrimaryKey. Both \"%s\" and \"%s\" are annotated as @PrimaryKey.", + primaryKey.getSimpleName().toString(), + variableElement.getSimpleName().toString())); return false; } TypeMirror fieldType = variableElement.asType(); if (!isValidPrimaryKeyType(fieldType)) { Utils.error(String.format( - "Field \"%s\" with type \"%s\" cannot be used as primary key. See @PrimaryKey for legal types.", - variableElement.getSimpleName().toString(), - fieldType)); + "Field \"%s\" with type \"%s\" cannot be used as primary key. See @PrimaryKey for legal types.", + variableElement.getSimpleName().toString(), + fieldType)); return false; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java index fc6745ed5f..4cd364a843 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.Map; + public class Constants { public static final String REALM_PACKAGE_NAME = "io.realm"; public static final String PROXY_SUFFIX = "RealmProxy"; @@ -34,9 +35,10 @@ public class Constants { "throw new io.realm.exceptions.RealmException(\"Primary key field '%s' cannot be changed after object" + " was created.\")"; static final String STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD = - "throw new io.realm.exceptions.RealmException(\"\\\"%s\\\" field \\\"%s\\\" cannot be loaded from json\")"; + "throw new io.realm.exceptions.RealmException(\"\\\"%s\\\" field \\\"%s\\\" cannot be loaded from json\")"; static final Map JAVA_TO_REALM_TYPES; + static { JAVA_TO_REALM_TYPES = new HashMap(); JAVA_TO_REALM_TYPES.put("byte", "Long"); @@ -60,6 +62,7 @@ public class Constants { } static final Map JAVA_TO_COLUMN_TYPES; + static { JAVA_TO_COLUMN_TYPES = new HashMap(); JAVA_TO_COLUMN_TYPES.put("byte", "RealmFieldType.INTEGER"); @@ -82,6 +85,7 @@ public class Constants { } static final Map JAVA_TO_FIELD_SETTER; + static { JAVA_TO_FIELD_SETTER = new HashMap(); JAVA_TO_FIELD_SETTER.put("byte", "setByte"); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java index 7e546127d7..1b177c5183 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java @@ -30,6 +30,7 @@ import io.realm.annotations.RealmModule; + /** * This class is responsible for creating the DefaultRealmModule that contains all known * {@link io.realm.annotations.RealmClass}' known at compile time. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java index f6fda5eb7e..a9df486007 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java @@ -31,6 +31,7 @@ import io.realm.annotations.RealmModule; + /** * Utility class for holding metadata for the Realm modules. */ diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java index 26906a0e7f..2239201e2e 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java @@ -22,11 +22,13 @@ import java.util.HashMap; import java.util.Map; + /** * Helper class for converting between Json types and data types in Java that are supported by Realm. */ public class RealmJsonTypeHelper { private static final Map JAVA_TO_JSON_TYPES; + static { JAVA_TO_JSON_TYPES = new HashMap(); JAVA_TO_JSON_TYPES.put("byte", new SimpleTypeConverter("byte", "Int")); @@ -45,10 +47,10 @@ public class RealmJsonTypeHelper { JAVA_TO_JSON_TYPES.put("java.lang.Boolean", new SimpleTypeConverter("boolean", "Boolean")); JAVA_TO_JSON_TYPES.put("java.lang.String", new SimpleTypeConverter("String", "String")); JAVA_TO_JSON_TYPES.put("java.util.Date", new JsonToRealmFieldTypeConverter() { + // @formatter:off @Override public void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer) - throws IOException { + JavaWriter writer) throws IOException { writer .beginControlFlow("if (json.has(\"%s\"))", fieldName) .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) @@ -65,11 +67,12 @@ public void emitTypeConversion(String interfaceName, String setter, String field .endControlFlow() .endControlFlow(); } + //@formatter:on + // @formatter:off @Override - public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String - fieldType, JavaWriter writer, boolean isPrimaryKey) - throws IOException { + public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, + String fieldType, JavaWriter writer, boolean isPrimaryKey) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") .emitStatement("reader.skipValue()") @@ -84,19 +87,19 @@ public void emitStreamTypeConversion(String interfaceName, String setter, String setter) .endControlFlow(); } + //@formatter:on @Override public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, - String qualifiedRealmObjectProxyClass, - String fieldName, JavaWriter writer) throws IOException { + String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) throws IOException { throw new IllegalArgumentException("'Date' is not allowed as a primary key value."); } }); JAVA_TO_JSON_TYPES.put("byte[]", new JsonToRealmFieldTypeConverter() { + // @formatter:off @Override public void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer) - throws IOException { + JavaWriter writer) throws IOException { writer .beginControlFlow("if (json.has(\"%s\"))", fieldName) .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) @@ -107,11 +110,12 @@ public void emitTypeConversion(String interfaceName, String setter, String field .endControlFlow() .endControlFlow(); } + //@formatter:on + // @formatter:off @Override - public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String - fieldType, JavaWriter writer, boolean isPrimaryKey) - throws IOException { + public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, + String fieldType, JavaWriter writer, boolean isPrimaryKey) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") .emitStatement("reader.skipValue()") @@ -121,29 +125,28 @@ public void emitStreamTypeConversion(String interfaceName, String setter, String setter) .endControlFlow(); } + //@formatter:on @Override public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, - String qualifiedRealmObjectProxyClass, - String fieldName, JavaWriter writer) throws IOException { + String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) throws IOException { throw new IllegalArgumentException("'byte[]' is not allowed as a primary key value."); } }); } public static void emitCreateObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, - String qualifiedRealmObjectProxyClass, - String qualifiedFieldType, - String fieldName, JavaWriter writer) throws IOException { + String qualifiedRealmObjectProxyClass, String qualifiedFieldType, String fieldName, JavaWriter writer) + throws IOException { JsonToRealmFieldTypeConverter typeEmitter = JAVA_TO_JSON_TYPES.get(qualifiedFieldType); if (typeEmitter != null) { - typeEmitter.emitGetObjectWithPrimaryKeyValue(qualifiedRealmObjectClass, qualifiedRealmObjectProxyClass, fieldName, writer); + typeEmitter.emitGetObjectWithPrimaryKeyValue(qualifiedRealmObjectClass, qualifiedRealmObjectProxyClass, + fieldName, writer); } } - public static void emitFillJavaTypeWithJsonValue(String interfaceName, String setter, String fieldName, String - qualifiedFieldType, - JavaWriter writer) throws IOException { + public static void emitFillJavaTypeWithJsonValue(String interfaceName, String setter, String fieldName, + String qualifiedFieldType, JavaWriter writer) throws IOException { JsonToRealmFieldTypeConverter typeEmitter = JAVA_TO_JSON_TYPES.get(qualifiedFieldType); if (typeEmitter != null) { typeEmitter.emitTypeConversion(interfaceName, setter, fieldName, qualifiedFieldType, writer); @@ -151,14 +154,15 @@ public static void emitFillJavaTypeWithJsonValue(String interfaceName, String se } public static void emitIllegalJsonValueException(String fieldType, String fieldName, JavaWriter writer) - throws IOException { + throws IOException { writer.beginControlFlow("if (json.has(\"%s\"))", fieldName); writer.emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD, fieldType, fieldName); writer.endControlFlow(); } - public static void emitFillRealmObjectWithJsonValue(String interfaceName, String setter, String fieldName, String - qualifiedFieldType, String proxyClass, JavaWriter writer) throws IOException { + // @formatter:off + public static void emitFillRealmObjectWithJsonValue(String interfaceName, String setter, String fieldName, + String qualifiedFieldType, String proxyClass, JavaWriter writer) throws IOException { writer .beginControlFlow("if (json.has(\"%s\"))", fieldName) .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) @@ -170,9 +174,11 @@ public static void emitFillRealmObjectWithJsonValue(String interfaceName, String .endControlFlow() .endControlFlow(); } + //@formatter:on - public static void emitFillRealmListWithJsonValue(String interfaceName, String getter, String setter, String - fieldName, String fieldTypeCanonicalName, String proxyClass, JavaWriter writer) throws IOException { + // @formatter:off + public static void emitFillRealmListWithJsonValue(String interfaceName, String getter, String setter, + String fieldName, String fieldTypeCanonicalName, String proxyClass, JavaWriter writer) throws IOException { writer .beginControlFlow("if (json.has(\"%s\"))", fieldName) .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) @@ -188,10 +194,10 @@ public static void emitFillRealmListWithJsonValue(String interfaceName, String g .endControlFlow() .endControlFlow(); } + //@formatter:on - - public static void emitFillJavaTypeFromStream(String interfaceName, ClassMetaData metaData, String fieldName, String - fieldType, JavaWriter writer) throws IOException { + public static void emitFillJavaTypeFromStream(String interfaceName, ClassMetaData metaData, String fieldName, + String fieldType, JavaWriter writer) throws IOException { String setter = metaData.getInternalSetter(fieldName); boolean isPrimaryKey = false; if (metaData.hasPrimaryKey() && metaData.getPrimaryKey().getSimpleName().toString().equals(fieldName)) { @@ -203,8 +209,9 @@ public static void emitFillJavaTypeFromStream(String interfaceName, ClassMetaDat } } - public static void emitFillRealmObjectFromStream(String interfaceName, String setter, String fieldName, String - fieldTypeCanonicalName, String proxyClass, JavaWriter writer) throws IOException { + // @formatter:off + public static void emitFillRealmObjectFromStream(String interfaceName, String setter, String fieldName, + String fieldTypeCanonicalName, String proxyClass, JavaWriter writer) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") .emitStatement("reader.skipValue()") @@ -215,9 +222,11 @@ public static void emitFillRealmObjectFromStream(String interfaceName, String se .emitStatement("((%s) obj).%s(%sObj)", interfaceName, setter, fieldName) .endControlFlow(); } + //@formatter:on - public static void emitFillRealmListFromStream(String interfaceName, String getter, String setter, String - fieldTypeCanonicalName, String proxyClass, JavaWriter writer) throws IOException { + // @formatter:off + public static void emitFillRealmListFromStream(String interfaceName, String getter, String setter, + String fieldTypeCanonicalName, String proxyClass, JavaWriter writer) throws IOException { writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") .emitStatement("reader.skipValue()") @@ -232,6 +241,7 @@ public static void emitFillRealmListFromStream(String interfaceName, String gett .emitStatement("reader.endArray()") .endControlFlow(); } + //@formatter:on private static class SimpleTypeConverter implements JsonToRealmFieldTypeConverter { @@ -239,12 +249,11 @@ private static class SimpleTypeConverter implements JsonToRealmFieldTypeConverte private final String jsonType; /** - * Creates a conversion between simple types which can be expressed as - * RealmObject.setFieldName(() json.get) or - * RealmObject.setFieldName(() reader.next + * Creates a conversion between simple types which can be expressed as RealmObject.setFieldName(() + * json.get) or RealmObject.setFieldName(() reader.next * - * @param castType Java type to cast to. - * @param jsonType JsonType to get data from. + * @param castType Java type to cast to. + * @param jsonType JsonType to get data from. */ private SimpleTypeConverter(String castType, String jsonType) { this.castType = castType; @@ -253,9 +262,7 @@ private SimpleTypeConverter(String castType, String jsonType) { @Override public void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter - writer) - throws IOException { + JavaWriter writer) throws IOException { String statementSetNullOrThrow; if (Utils.isPrimitiveType(fieldType)) { // Only throw exception for primitive types. For boxed types and String, exception will be thrown in @@ -264,6 +271,7 @@ public void emitTypeConversion(String interfaceName, String setter, String field } else { statementSetNullOrThrow = String.format("((%s) obj).%s(null)", interfaceName, setter); } + // @formatter:off writer .beginControlFlow("if (json.has(\"%s\"))", fieldName) .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) @@ -273,12 +281,12 @@ public void emitTypeConversion(String interfaceName, String setter, String field jsonType, fieldName) .endControlFlow() .endControlFlow(); + //@formatter:on } @Override public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer, boolean isPrimaryKey) - throws IOException { + JavaWriter writer, boolean isPrimaryKey) throws IOException { String statementSetNullOrThrow; if (Utils.isPrimitiveType(fieldType)) { // Only throw exception for primitive types. For boxed types and String, exception will be thrown in @@ -287,6 +295,7 @@ public void emitStreamTypeConversion(String interfaceName, String setter, String } else { statementSetNullOrThrow = String.format("((%s) obj).%s(null)", interfaceName, setter); } + // @formatter:off writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") .emitStatement("reader.skipValue()") @@ -297,12 +306,13 @@ public void emitStreamTypeConversion(String interfaceName, String setter, String if (isPrimaryKey) { writer.emitStatement("jsonHasPrimaryKey = true"); } + //@formatter:on } + // @formatter:off @Override public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, - String qualifiedRealmObjectProxyClass, - String fieldName, JavaWriter writer) throws IOException { + String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) throws IOException { // No error checking is done here for valid primary key types. This should be done by the annotation // processor writer @@ -319,14 +329,16 @@ public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, .endControlFlow(); } } + //@formatter:on private interface JsonToRealmFieldTypeConverter { - void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, JavaWriter - writer) throws IOException; + void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, + JavaWriter writer) throws IOException; + void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer, boolean isPrimaryKey) throws IOException; - void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, - String qualifiedRealmObjectProxyClass, - String fieldName, JavaWriter writer) throws IOException; + JavaWriter writer, boolean isPrimaryKey) throws IOException; + + void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, + String fieldName, JavaWriter writer) throws IOException; } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index fb3562963c..e6f4b4d734 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -33,80 +33,81 @@ import io.realm.annotations.RealmClass; import io.realm.annotations.RealmModule; + /** * The RealmProcessor is responsible for creating the plumbing that connects the RealmObjects to a Realm. The process * for doing so is summarized below and then described in more detail. *

            - * + *

            *

            DESIGN GOALS

            - * + *

            * The processor should support the following design goals: *

              - *
            • Minimize reflection.
            • - *
            • Realm code can be obfuscated as much as possible.
            • - *
            • Library projects must be able to use Realm without interfering with app code.
            • - *
            • App code must be able to use RealmObject classes provided by library code.
            • - *
            • It should work for app developers out of the box (ie. put the burden on the library developer)
            • + *
            • Minimize reflection.
            • + *
            • Realm code can be obfuscated as much as possible.
            • + *
            • Library projects must be able to use Realm without interfering with app code.
            • + *
            • App code must be able to use RealmObject classes provided by library code.
            • + *
            • It should work for app developers out of the box (ie. put the burden on the library developer)
            • *
            - * + *

            *

            SUMMARY

            - * + *

            *

              - *
            1. Create proxy classes for all classes marked with @RealmClass. They are named <className>RealmProxy.java
            2. - *
            3. Create a DefaultRealmModule containing all RealmObject classes (if needed).
            4. - *
            5. Create a RealmProxyMediator class for all classes marked with {@code @RealmModule}. They are named {@code Mediator.java}
            6. + *
            7. Create proxy classes for all classes marked with @RealmClass. They are named <className>RealmProxy.java
            8. + *
            9. Create a DefaultRealmModule containing all RealmObject classes (if needed).
            10. + *
            11. Create a RealmProxyMediator class for all classes marked with {@code @RealmModule}. They are named {@code Mediator.java}
            12. *
            - * + *

            *

            WHY

            - * + *

            *

              *
            1. A RealmObjectProxy object is created for each class annotated with {@link io.realm.annotations.RealmClass}. This * proxy extends the original RealmObject class and rewires all field access to point to the native Realm memory instead of * Java memory. It also adds some static helper methods to the class.
            2. - * + *

              *

            3. The annotation processor is either in "library" mode or in "app" mode. This is defined by having a class * annotated with @RealmModule(library = true). It is not allowed to have both a class with library = true and * library = false in the same IntelliJ module and it will cause the annotation processor to throw an exception. If no * library modules are defined, we will create a DefaultRealmModule containing all known RealmObjects and with the * {@code @RealmModule} annotation. Realm automatically knows about this module, but it is still possible for users to create * their own modules with a subset of model classes.
            4. - * + *

              *

            5. For each class annotated with @RealmModule a matching Mediator class is created (including the default one). This * class has an interface that matches the static helper methods for the proxy classes. All access to these static * helper methods should be done through this Mediator.
            6. *
            - * + *

            * This allows ProGuard to obfuscate all RealmObject and proxy classes as all access to the static methods now happens through * the Mediator, and the only requirement is now that only RealmModule and Mediator class names cannot be obfuscated. - * - * + *

            + *

            *

            CREATING A REALM

            - * + *

            * This means the workflow when instantiating a Realm on runtime is the following: - * + *

            *

              - *
            1. Open a Realm.
            2. - *
            3. Assign one or more modules (that are allowed to overlap). If no module is assigned, the default module is used.
            4. - *
            5. The Realm schema is now defined as all RealmObject classes known by these modules.
            6. - *
            7. Each time a static helper method is needed, Realm can now delegate these method calls to the appropriate - * Mediator which in turn will delegate the method call to the appropriate RealmObjectProxy class.
            8. + *
            9. Open a Realm.
            10. + *
            11. Assign one or more modules (that are allowed to overlap). If no module is assigned, the default module is used.
            12. + *
            13. The Realm schema is now defined as all RealmObject classes known by these modules.
            14. + *
            15. Each time a static helper method is needed, Realm can now delegate these method calls to the appropriate + * Mediator which in turn will delegate the method call to the appropriate RealmObjectProxy class.
            16. *
            - * + *

            *

            CREATING A MANAGED RealmObject

            - * + *

            * To allow to specify default values by model's constructor or direct field assignment, * the flow of creating the proxy object is a bit complicated. This section illustrates * how proxy object should be created. - * + *

            *

              - *
            1. Get the thread local {@code io.realm.BaseRealm.RealmObjectContext} instance by {@code BaseRealm.objectContext.get()}
            2. - *
            3. Set the object context information to the {@code RealmObjectContext} those should be set to the creating proxy object.
            4. - *
            5. Create proxy object ({@code new io.realm.FooRealmProxy()}).
            6. - *
            7. Set the object context information to the created proxy when the first access of its accessors (or in its constructor if accessors are not used in the model's constructor).
            8. - *
            9. Clear the object context information in the thread local {@code io.realm.BaseRealm.RealmObjectContext} instance by calling {@code - * #clear()} method.
            10. + *
            11. Get the thread local {@code io.realm.BaseRealm.RealmObjectContext} instance by {@code BaseRealm.objectContext.get()}
            12. + *
            13. Set the object context information to the {@code RealmObjectContext} those should be set to the creating proxy object.
            14. + *
            15. Create proxy object ({@code new io.realm.FooRealmProxy()}).
            16. + *
            17. Set the object context information to the created proxy when the first access of its accessors (or in its constructor if accessors are not used in the model's constructor).
            18. + *
            19. Clear the object context information in the thread local {@code io.realm.BaseRealm.RealmObjectContext} instance by calling {@code + * #clear()} method.
            20. *
            - * + *

            * The reason of this complicated step is that we can't pass these context information * via the constructor of the proxy. It's because the constructor of the proxy is executed * after the constructor of the model class. The access to the fields in the model's @@ -135,7 +136,8 @@ public class RealmProcessor extends AbstractProcessor { private boolean hasProcessedModules = false; private int round; - @Override public SourceVersion getSupportedSourceVersion() { + @Override + public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @@ -272,11 +274,11 @@ private boolean validateBacklinks() { boolean allValid = true; Map realmClasses = new HashMap(classesToValidate.size()); - for (ClassMetaData classData: classesToValidate) { + for (ClassMetaData classData : classesToValidate) { realmClasses.put(classData.getFullyQualifiedClassName(), classData); } - for (Backlink backlink: backlinksToValidate) { + for (Backlink backlink : backlinksToValidate) { ClassMetaData clazz = realmClasses.get(backlink.getSourceClass()); // If the class is not here it might be part of some other compilation unit. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 2a83073af5..b10d5faf2a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -34,6 +34,7 @@ import javax.lang.model.util.Types; import javax.tools.JavaFileObject; + public class RealmProxyClassGenerator { private static final String BACKLINKS_FIELD_EXTENSION = "Backlinks"; @@ -62,7 +63,7 @@ public void generate() throws IOException, UnsupportedOperationException { writer.setIndent(Constants.INDENT); writer.emitPackage(Constants.REALM_PACKAGE_NAME) - .emitEmptyLine(); + .emitEmptyLine(); ArrayList imports = new ArrayList(); imports.add("android.annotation.TargetApi"); @@ -93,7 +94,7 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("org.json.JSONArray"); writer.emitImports(imports) - .emitEmptyLine(); + .emitEmptyLine(); // Begin the class definition writer.beginType( @@ -165,20 +166,21 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { final String columnIndexVarName = columnIndexVarName(variableElement); writer.emitStatement("this.%s = getValidColumnIndex(path, table, \"%s\", \"%s\")", columnIndexVarName, simpleClassName, columnName) - .emitStatement("indicesMap.put(\"%s\", this.%s)", columnName, columnIndexVarName); + .emitStatement("indicesMap.put(\"%s\", this.%s)", columnName, columnIndexVarName); } + writer.emitEmptyLine() - .emitStatement("setIndicesMap(indicesMap)"); + .emitStatement("setIndicesMap(indicesMap)"); writer.endConstructor() - .emitEmptyLine(); + .emitEmptyLine(); // copyColumnInfoFrom method writer.emitAnnotation("Override") - .beginMethod( - "void", // return type - "copyColumnInfoFrom", // method name - EnumSet.of(Modifier.PUBLIC, Modifier.FINAL), // modifiers - "ColumnInfo", "other"); // parameters + .beginMethod( + "void", // return type + "copyColumnInfoFrom", // method name + EnumSet.of(Modifier.PUBLIC, Modifier.FINAL), // modifiers + "ColumnInfo", "other"); // parameters { writer.emitStatement("final %1$s otherInfo = (%1$s) other", columnInfoClassName()); @@ -187,12 +189,13 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { writer.emitStatement("this.%1$s = otherInfo.%1$s", columnIndexVarName(variableElement)); } writer.emitEmptyLine() - .emitStatement("setIndicesMap(otherInfo.getIndicesMap())"); + .emitStatement("setIndicesMap(otherInfo.getIndicesMap())"); } writer.endMethod() - .emitEmptyLine(); + .emitEmptyLine(); // clone method + //@formatter:off writer.emitAnnotation("Override") .beginMethod( columnInfoClassName(), // return type @@ -202,13 +205,14 @@ private void emitColumnIndicesClass(JavaWriter writer) throws IOException { .emitStatement("return (%1$s) super.clone()", columnInfoClassName()) .endMethod() .emitEmptyLine(); + //@formatter:on writer.endType(); } private void emitClassFields(JavaWriter writer) throws IOException { writer.emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE)) - .emitField("ProxyState<" + qualifiedClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)); + .emitField("ProxyState<" + qualifiedClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)); for (VariableElement variableElement : metadata.getFields()) { if (Utils.isRealmList(variableElement)) { @@ -219,11 +223,12 @@ private void emitClassFields(JavaWriter writer) throws IOException { for (Backlink backlink : metadata.getBacklinkFields()) { writer.emitField( - backlink.getTargetFieldType(), - backlink.getTargetField() + BACKLINKS_FIELD_EXTENSION, - EnumSet.of(Modifier.PRIVATE)); + backlink.getTargetFieldType(), + backlink.getTargetField() + BACKLINKS_FIELD_EXTENSION, + EnumSet.of(Modifier.PRIVATE)); } + //@formatter:off writer.emitField("List", "FIELD_NAMES", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)) .beginInitializer(true) .emitStatement("List fieldNames = new ArrayList()"); @@ -233,8 +238,10 @@ private void emitClassFields(JavaWriter writer) throws IOException { writer.emitStatement("FIELD_NAMES = Collections.unmodifiableList(fieldNames)") .endInitializer() .emitEmptyLine(); + //@formatter:on } + //@formatter:off private void emitConstructor(JavaWriter writer) throws IOException { // FooRealmProxy(ColumnInfo) writer.beginConstructor(EnumSet.noneOf(Modifier.class)) @@ -242,6 +249,7 @@ private void emitConstructor(JavaWriter writer) throws IOException { .endConstructor() .emitEmptyLine(); } + //@formatter:on private void emitPersistedFieldAccessors(final JavaWriter writer) throws IOException { for (final VariableElement field : metadata.getFields()) { @@ -256,7 +264,7 @@ private void emitPersistedFieldAccessors(final JavaWriter writer) throws IOExcep emitRealmList(writer, field, fieldName, fieldTypeCanonicalName); } else { throw new UnsupportedOperationException(String.format( - "Field \"%s\" of type \"%s\" is not supported.", fieldName, fieldTypeCanonicalName)); + "Field \"%s\" of type \"%s\" is not supported.", fieldName, fieldTypeCanonicalName)); } writer.emitEmptyLine(); @@ -267,14 +275,14 @@ private void emitPersistedFieldAccessors(final JavaWriter writer) throws IOExcep * Primitives and boxed types */ private void emitPrimitiveType( - JavaWriter writer, - final VariableElement field, - final String fieldName, - String fieldTypeCanonicalName) throws IOException - { + JavaWriter writer, + final VariableElement field, + final String fieldName, + String fieldTypeCanonicalName) throws IOException { final String realmType = Constants.JAVA_TO_REALM_TYPES.get(fieldTypeCanonicalName); // Getter + //@formatter:off writer.emitAnnotation("Override"); writer.emitAnnotation("SuppressWarnings", "\"cast\"") .beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) @@ -286,6 +294,7 @@ private void emitPrimitiveType( .emitStatement("return null") .endControlFlow(); } + //@formatter:on // For Boxed types, this should be the corresponding primitive types. Others remain the same. String castingBackType; @@ -296,10 +305,10 @@ private void emitPrimitiveType( castingBackType = fieldTypeCanonicalName; } writer.emitStatement( - "return (%s) proxyState.getRow$realm().get%s(%s)", - castingBackType, realmType, fieldIndexVariableReference(field)); + "return (%s) proxyState.getRow$realm().get%s(%s)", + castingBackType, realmType, fieldIndexVariableReference(field)); writer.endMethod() - .emitEmptyLine(); + .emitEmptyLine(); // Setter writer.emitAnnotation("Override"); @@ -310,6 +319,7 @@ public void emit(JavaWriter writer) throws IOException { // set value as default value writer.emitStatement("final Row row = proxyState.getRow$realm()"); + //@formatter:off if (metadata.isNullable(field)) { writer.beginControlFlow("if (value == null)") .emitStatement("row.getTable().setNull(%s, row.getIndex(), true)", @@ -321,9 +331,11 @@ public void emit(JavaWriter writer) throws IOException { .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) .endControlFlow(); } + //@formatter:on + writer.emitStatement( - "row.getTable().set%s(%s, row.getIndex(), value, true)", - realmType, fieldIndexVariableReference(field)); + "row.getTable().set%s(%s, row.getIndex(), value, true)", + realmType, fieldIndexVariableReference(field)); writer.emitStatement("return"); } }); @@ -334,6 +346,7 @@ public void emit(JavaWriter writer) throws IOException { // Primary key is not allowed to be changed after object created. writer.emitStatement(Constants.STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED, fieldName); } else { + //@formatter:off if (metadata.isNullable(field)) { writer.beginControlFlow("if (value == null)") .emitStatement("proxyState.getRow$realm().setNull(%s)", fieldIndexVariableReference(field)) @@ -346,9 +359,10 @@ public void emit(JavaWriter writer) throws IOException { .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) .endControlFlow(); } + //@formatter:on writer.emitStatement( - "proxyState.getRow$realm().set%s(%s, value)", - realmType, fieldIndexVariableReference(field)); + "proxyState.getRow$realm().set%s(%s, value)", + realmType, fieldIndexVariableReference(field)); } writer.endMethod(); } @@ -356,6 +370,7 @@ public void emit(JavaWriter writer) throws IOException { /** * Links */ + //@formatter:off private void emitRealmModel( JavaWriter writer, final VariableElement field, @@ -422,10 +437,12 @@ public void emit(JavaWriter writer) throws IOException { .emitStatement("proxyState.getRow$realm().setLink(%s, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex())", fieldIndexVariableReference(field)) .endMethod(); } + //@formatter:on /** * LinkLists */ + //@formatter:off private void emitRealmList( JavaWriter writer, final VariableElement field, @@ -495,17 +512,18 @@ public void emit(JavaWriter writer) throws IOException { .endControlFlow() .endMethod(); } + //@formatter:on private interface CodeEmitter { void emit(JavaWriter writer) throws IOException; } private void emitCodeForUnderConstruction(JavaWriter writer, boolean isPrimaryKey, - CodeEmitter defaultValueCodeEmitter) throws IOException { + CodeEmitter defaultValueCodeEmitter) throws IOException { writer.beginControlFlow("if (proxyState.isUnderConstruction())"); if (isPrimaryKey) { writer.emitSingleLineComment("default value of the primary key is always ignored.") - .emitStatement("return"); + .emitStatement("return"); } else { writer.beginControlFlow("if (!proxyState.getAcceptDefaultValue$realm())") .emitStatement("return") @@ -513,9 +531,10 @@ private void emitCodeForUnderConstruction(JavaWriter writer, boolean isPrimaryKe defaultValueCodeEmitter.emit(writer); } writer.endControlFlow() - .emitEmptyLine(); + .emitEmptyLine(); } + //@formatter:off private void emitInjectContextMethod(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); writer.beginMethod( @@ -538,7 +557,9 @@ private void emitInjectContextMethod(JavaWriter writer) throws IOException { writer.endMethod() .emitEmptyLine(); } + //@formatter:on + //@formatter:off private void emitBacklinkFieldAccessors(JavaWriter writer) throws IOException { for (Backlink backlink : metadata.getBacklinkFields()) { String cacheFieldName = backlink.getTargetField() + BACKLINKS_FIELD_EXTENSION; @@ -557,7 +578,9 @@ private void emitBacklinkFieldAccessors(JavaWriter writer) throws IOException { .emitEmptyLine(); } } + //@formatter:on + //@formatter:off private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOException { writer.emitAnnotation("Override") .beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)) @@ -565,6 +588,7 @@ private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOExce .endMethod() .emitEmptyLine(); } + //@formatter:on private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOException { writer.beginMethod( @@ -595,23 +619,23 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep } else if (Utils.isRealmModel(field)) { writer.beginControlFlow("if (!realmSchema.contains(\"" + fieldTypeSimpleName + "\"))") .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", fieldTypeSimpleName, Constants.PROXY_SUFFIX) - .endControlFlow() - .emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\")))", - fieldName, fieldTypeSimpleName); + .endControlFlow() + .emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\")))", + fieldName, fieldTypeSimpleName); } else if (Utils.isRealmList(field)) { String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); - writer.beginControlFlow("if (!realmSchema.contains(\"" + genericTypeSimpleName +"\"))") + writer.beginControlFlow("if (!realmSchema.contains(\"" + genericTypeSimpleName + "\"))") .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", genericTypeSimpleName, Constants.PROXY_SUFFIX) - .endControlFlow() - .emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\")))", - fieldName, genericTypeSimpleName); + .endControlFlow() + .emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\")))", + fieldName, genericTypeSimpleName); } } writer.emitStatement("return realmObjectSchema"); writer.endControlFlow(); writer.emitStatement("return realmSchema.get(\"" + this.simpleClassName + "\")"); writer.endMethod() - .emitEmptyLine(); + .emitEmptyLine(); } private void emitInitTableMethod(JavaWriter writer) throws IOException { @@ -643,16 +667,16 @@ private void emitInitTableMethod(JavaWriter writer) throws IOException { } else if (Utils.isRealmModel(field)) { writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName) .emitStatement("%s%s.initTable(sharedRealm)", fieldTypeSimpleName, Constants.PROXY_SUFFIX) - .endControlFlow() - .emitStatement("table.addColumnLink(RealmFieldType.OBJECT, \"%s\", sharedRealm.getTable(\"%s%s\"))", - fieldName, Constants.TABLE_PREFIX, fieldTypeSimpleName); + .endControlFlow() + .emitStatement("table.addColumnLink(RealmFieldType.OBJECT, \"%s\", sharedRealm.getTable(\"%s%s\"))", + fieldName, Constants.TABLE_PREFIX, fieldTypeSimpleName); } else if (Utils.isRealmList(field)) { String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName) .emitStatement("%s.initTable(sharedRealm)", Utils.getProxyClassName(genericTypeSimpleName)) - .endControlFlow() - .emitStatement("table.addColumnLink(RealmFieldType.LIST, \"%s\", sharedRealm.getTable(\"%s%s\"))", - fieldName, Constants.TABLE_PREFIX, genericTypeSimpleName); + .endControlFlow() + .emitStatement("table.addColumnLink(RealmFieldType.LIST, \"%s\", sharedRealm.getTable(\"%s%s\"))", + fieldName, Constants.TABLE_PREFIX, genericTypeSimpleName); } } @@ -674,42 +698,42 @@ private void emitInitTableMethod(JavaWriter writer) throws IOException { writer.emitStatement("return sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); writer.endMethod() - .emitEmptyLine(); + .emitEmptyLine(); } private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.beginMethod( - columnInfoClassName(), // Return type - "validateTable", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "SharedRealm", "sharedRealm", // Argument type & argument name - "boolean", "allowExtraColumns"); - - writer.beginControlFlow( - "if (!sharedRealm.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); + columnInfoClassName(), // Return type + "validateTable", // Method name + EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers + "SharedRealm", "sharedRealm", // Argument type & argument name + "boolean", "allowExtraColumns"); + + writer.beginControlFlow( + "if (!sharedRealm.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); emitMigrationNeededException(writer, "\"The '%s' class is missing from the schema for this Realm.\")", - metadata.getSimpleClassName()); + metadata.getSimpleClassName()); writer.endControlFlow(); writer.emitStatement( - "Table table = sharedRealm.getTable(\"%s%s\")", - Constants.TABLE_PREFIX, - this.simpleClassName); + "Table table = sharedRealm.getTable(\"%s%s\")", + Constants.TABLE_PREFIX, + this.simpleClassName); // verify number of columns writer.emitStatement("final long columnCount = table.getColumnCount()"); writer.beginControlFlow("if (columnCount != %d)", metadata.getFields().size()); writer.beginControlFlow("if (columnCount < %d)", metadata.getFields().size()); emitMigrationNeededException(writer, "\"Field count is less than expected - expected %d but was \" + columnCount)", - metadata.getFields().size()); + metadata.getFields().size()); writer.endControlFlow(); writer.beginControlFlow("if (allowExtraColumns)"); writer.emitStatement( - "RealmLog.debug(\"Field count is more than expected - expected %d but was %%1$d\", columnCount)", - metadata.getFields().size()); + "RealmLog.debug(\"Field count is more than expected - expected %d but was %%1$d\", columnCount)", + metadata.getFields().size()); writer.nextControlFlow("else"); emitMigrationNeededException(writer, "\"Field count is more than expected - expected %d but was \" + columnCount)", - metadata.getFields().size()); + metadata.getFields().size()); writer.endControlFlow(); writer.endControlFlow(); @@ -717,12 +741,12 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.emitStatement("Map columnTypes = new HashMap()"); writer.beginControlFlow("for (long i = 0; i < columnCount; i++)") .emitStatement("columnTypes.put(table.getColumnName(i), table.getColumnType(i))") - .endControlFlow() - .emitEmptyLine(); + .endControlFlow() + .emitEmptyLine(); // create an instance of ColumnInfo writer.emitStatement("final %1$s columnInfo = new %1$s(sharedRealm.getPath(), table)", columnInfoClassName()) - .emitEmptyLine(); + .emitEmptyLine(); // verify primary key definition was not altered if (metadata.hasPrimaryKey()) { @@ -732,13 +756,12 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { emitMigrationNeededException(writer, "\"Primary key not defined for field '%s' in existing Realm file. @PrimaryKey was added.\")", metadata.getPrimaryKey().getSimpleName().toString()); writer.nextControlFlow("else") - .beginControlFlow("if (table.getPrimaryKey() != columnInfo.%sIndex)", fieldName); + .beginControlFlow("if (table.getPrimaryKey() != columnInfo.%sIndex)", fieldName); emitMigrationNeededException(writer, "\"Primary Key annotation definition was changed, from field \" + table.getColumnName(table.getPrimaryKey()) + \" to field %s\")", metadata.getPrimaryKey().getSimpleName().toString()); writer.endControlFlow() - .endControlFlow(); - } - else { + .endControlFlow(); + } else { // the current model doesn't define a PK, make sure it's not defined in the Realm schema writer.beginControlFlow("if (table.hasPrimaryKey())"); emitMigrationNeededException(writer, "\"Primary Key defined for field \" + table.getColumnName(table.getPrimaryKey()) + \" was removed.\")"); @@ -754,11 +777,9 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { String fieldTypeQualifiedName = Utils.getFieldTypeQualifiedName(field); if (Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeQualifiedName)) { emitValidateRealmType(writer, field, fieldName, fieldTypeQualifiedName); - } - else if (Utils.isRealmModel(field)) { // Links + } else if (Utils.isRealmModel(field)) { // Links emitValidateRealmModelType(writer, field, fieldIndex, fieldName); - } - else if (Utils.isRealmList(field)) { // Link Lists + } else if (Utils.isRealmList(field)) { // Link Lists emitValidateRealmListType(writer, field, fieldIndex, fieldName); } } @@ -767,10 +788,10 @@ else if (Utils.isRealmList(field)) { // Link Lists Set backlinks = metadata.getBacklinkFields(); if (backlinks.size() > 0) { writer.emitEmptyLine() - .emitStatement("long backlinkFieldIndex") - .emitStatement("Table backlinkSourceTable") - .emitStatement("Table backlinkTargetTable") - .emitStatement("RealmFieldType backlinkFieldType"); + .emitStatement("long backlinkFieldIndex") + .emitStatement("Table backlinkSourceTable") + .emitStatement("Table backlinkTargetTable") + .emitStatement("RealmFieldType backlinkFieldType"); for (Backlink backlink : metadata.getBacklinkFields()) { emitValidateBacklink(writer, backlink); } @@ -784,7 +805,7 @@ else if (Utils.isRealmList(field)) { // Link Lists } private void emitValidateRealmType(JavaWriter writer, VariableElement field, String fieldName, String fieldTypeQualifiedName) - throws IOException { + throws IOException { // make sure types align writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); @@ -795,7 +816,7 @@ private void emitValidateRealmType(JavaWriter writer, VariableElement field, Str writer.beginControlFlow("if (columnTypes.get(\"%s\") != %s)", fieldName, Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeQualifiedName)); emitMigrationNeededException(writer, "\"Invalid type '%s' for field '%s' in existing Realm file.\")", - Utils.getFieldTypeSimpleName(field), fieldName); + Utils.getFieldTypeSimpleName(field), fieldName); writer.endControlFlow(); // make sure that nullability matches @@ -804,20 +825,20 @@ private void emitValidateRealmType(JavaWriter writer, VariableElement field, Str // Check if the existing PrimaryKey does support null value for String, Byte, Short, Integer, & Long if (metadata.isPrimaryKey(field)) { writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - "\"@PrimaryKey field '%s' does not support null values in the existing Realm file. " + - "Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required.\")", + "\"@PrimaryKey field '%s' does not support null values in the existing Realm file. " + + "Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required.\")", fieldName); - // nullability check for boxed types + // nullability check for boxed types } else if (Utils.isBoxedType(fieldTypeQualifiedName)) { writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - "\"Field '%s' does not support null values in the existing Realm file. " + - "Either set @Required, use the primitive type for field '%s' " + - "or migrate using RealmObjectSchema.setNullable().\")", + "\"Field '%s' does not support null values in the existing Realm file. " + + "Either set @Required, use the primitive type for field '%s' " + + "or migrate using RealmObjectSchema.setNullable().\")", fieldName, fieldName); } else { writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - " \"Field '%s' is required. Either set @Required to field '%s' " + - "or migrate using RealmObjectSchema.setNullable().\")", + " \"Field '%s' is required. Either set @Required to field '%s' " + + "or migrate using RealmObjectSchema.setNullable().\")", fieldName, fieldName); } writer.endControlFlow(); @@ -825,23 +846,23 @@ private void emitValidateRealmType(JavaWriter writer, VariableElement field, Str // check before migrating a nullable field containing null value to not-nullable PrimaryKey field for Realm version 0.89+ if (metadata.isPrimaryKey(field)) { writer - .beginControlFlow("if (table.isColumnNullable(%s) && table.findFirstNull(%s) != Table.NO_MATCH)", - fieldIndexVariableReference(field), fieldIndexVariableReference(field)) - .emitStatement("throw new IllegalStateException(\"Cannot migrate an object with null value in field '%s'." + - " Either maintain the same type for primary key field '%s', or remove the object with null value before migration.\")", - fieldName, fieldName) - .endControlFlow(); + .beginControlFlow("if (table.isColumnNullable(%s) && table.findFirstNull(%s) != Table.NO_MATCH)", + fieldIndexVariableReference(field), fieldIndexVariableReference(field)) + .emitStatement("throw new IllegalStateException(\"Cannot migrate an object with null value in field '%s'." + + " Either maintain the same type for primary key field '%s', or remove the object with null value before migration.\")", + fieldName, fieldName) + .endControlFlow(); } else { writer.beginControlFlow("if (table.isColumnNullable(%s))", fieldIndexVariableReference(field)); if (Utils.isPrimitiveType(fieldTypeQualifiedName)) { writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - " \"Field '%s' does support null values in the existing Realm file. " + - "Use corresponding boxed type for field '%s' or migrate using RealmObjectSchema.setNullable().\")", + " \"Field '%s' does support null values in the existing Realm file. " + + "Use corresponding boxed type for field '%s' or migrate using RealmObjectSchema.setNullable().\")", fieldName, fieldName); } else { writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - " \"Field '%s' does support null values in the existing Realm file. " + - "Remove @Required or @PrimaryKey from field '%s' or migrate using RealmObjectSchema.setNullable().\")", + " \"Field '%s' does support null values in the existing Realm file. " + + "Remove @Required or @PrimaryKey from field '%s' or migrate using RealmObjectSchema.setNullable().\")", fieldName, fieldName); } writer.endControlFlow(); @@ -858,51 +879,50 @@ private void emitValidateRealmType(JavaWriter writer, VariableElement field, Str } private void emitValidateRealmModelType(JavaWriter writer, VariableElement field, long fieldIndex, String fieldName) - throws IOException { + throws IOException { String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); emitMigrationNeededException(writer, "\"Missing field '%s' in existing Realm file. " + - "Either remove field or migrate using io.realm.internal.Table.addColumn().\")", fieldName); + "Either remove field or migrate using io.realm.internal.Table.addColumn().\")", fieldName); writer.endControlFlow(); writer.beginControlFlow("if (columnTypes.get(\"%s\") != RealmFieldType.OBJECT)", fieldName); emitMigrationNeededException(writer, "\"Invalid type '%s' for field '%s'\")", - fieldTypeSimpleName, fieldName); + fieldTypeSimpleName, fieldName); writer.endControlFlow(); writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName); emitMigrationNeededException(writer, "\"Missing class '%s%s' for field '%s'\")", - Constants.TABLE_PREFIX, fieldTypeSimpleName, fieldName); + Constants.TABLE_PREFIX, fieldTypeSimpleName, fieldName); writer.endControlFlow(); writer.emitStatement("Table table_%d = sharedRealm.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, fieldTypeSimpleName); writer.beginControlFlow("if (!table.getLinkTarget(%s).hasSameSchema(table_%d))", - fieldIndexVariableReference(field), fieldIndex); + fieldIndexVariableReference(field), fieldIndex); emitMigrationNeededException(writer, "\"Invalid RealmObject for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", - fieldName, fieldIndexVariableReference(field), fieldIndex); + fieldName, fieldIndexVariableReference(field), fieldIndex); writer.endControlFlow(); } private void emitValidateRealmListType(JavaWriter writer, VariableElement field, long fieldIndex, String fieldName) - throws IOException - { + throws IOException { String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); writer.beginControlFlow("if (!columnTypes.containsKey(\"%s\"))", fieldName); emitMigrationNeededException(writer, "\"Missing field '%s'\")", fieldName); writer.endControlFlow(); writer.beginControlFlow("if (columnTypes.get(\"%s\") != RealmFieldType.LIST)", fieldName); emitMigrationNeededException(writer, "\"Invalid type '%s' for field '%s'\")", - genericTypeSimpleName, fieldName); + genericTypeSimpleName, fieldName); writer.endControlFlow(); writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName); emitMigrationNeededException(writer, "\"Missing class '%s%s' for field '%s'\")", - Constants.TABLE_PREFIX, genericTypeSimpleName, fieldName); + Constants.TABLE_PREFIX, genericTypeSimpleName, fieldName); writer.endControlFlow(); writer.emitStatement("Table table_%d = sharedRealm.getTable(\"%s%s\")", fieldIndex, Constants.TABLE_PREFIX, genericTypeSimpleName); writer.beginControlFlow("if (!table.getLinkTarget(%s).hasSameSchema(table_%d))", - fieldIndexVariableReference(field), fieldIndex); + fieldIndexVariableReference(field), fieldIndex); emitMigrationNeededException(writer, "\"Invalid RealmList type for field '%s': '\" + table.getLinkTarget(%s).getName() + \"' expected - was '\" + table_%d.getName() + \"'\")", - fieldName, fieldIndexVariableReference(field), fieldIndex); + fieldName, fieldIndexVariableReference(field), fieldIndex); writer.endControlFlow(); } @@ -918,7 +938,7 @@ private void emitValidateBacklink(JavaWriter writer, Backlink backlink) throws I String fullyQualifiedSourceClass = backlink.getSourceClass(); writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, sourceClass); emitMigrationNeededException(writer, "\"Cannot find source class '%s' for @LinkingObjects field '%s.%s'\")", - fullyQualifiedSourceClass, targetClass, targetField); + fullyQualifiedSourceClass, targetClass, targetField); writer.endControlFlow(); // verify that the source class contains the source field @@ -927,36 +947,41 @@ private void emitValidateBacklink(JavaWriter writer, Backlink backlink) throws I writer.emitStatement("backlinkFieldIndex = backlinkSourceTable.getColumnIndex(\"%s\")", sourceField); writer.beginControlFlow("if (backlinkFieldIndex == Table.NO_MATCH)"); emitMigrationNeededException(writer, "\"Cannot find source field '%s.%s' for @LinkingObjects field '%s.%s'\")", - fullyQualifiedSourceClass, sourceField, targetClass, targetField); + fullyQualifiedSourceClass, sourceField, targetClass, targetField); writer.endControlFlow(); // verify that the source field type is target class writer.emitStatement("backlinkFieldType = backlinkSourceTable.getColumnType(backlinkFieldIndex)"); writer.beginControlFlow("if ((backlinkFieldType != RealmFieldType.OBJECT) && (backlinkFieldType != RealmFieldType.LIST))"); emitMigrationNeededException(writer, "\"Source field '%s.%s' for @LinkingObjects field '%s.%s' is not a RealmObject type\")", - fullyQualifiedSourceClass, sourceField, targetClass, targetField); + fullyQualifiedSourceClass, sourceField, targetClass, targetField); writer.endControlFlow(); writer.emitStatement("backlinkTargetTable = backlinkSourceTable.getLinkTarget(backlinkFieldIndex)"); writer.beginControlFlow("if (!table.hasSameSchema(backlinkTargetTable))"); emitMigrationNeededException(writer, "\"Source field '%s.%s' for @LinkingObjects field '%s.%s' has wrong type '\" + backlinkTargetTable.getName() + \"'\")", - fullyQualifiedSourceClass, sourceField, targetClass, targetField); + fullyQualifiedSourceClass, sourceField, targetClass, targetField); writer.endControlFlow(); } + //@formatter:off private void emitGetTableNameMethod(JavaWriter writer) throws IOException { writer.beginMethod("String", "getTableName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) .emitStatement("return \"%s%s\"", Constants.TABLE_PREFIX, simpleClassName) .endMethod() .emitEmptyLine(); } + //@formatter:on + //@formatter:off private void emitGetFieldNamesMethod(JavaWriter writer) throws IOException { writer.beginMethod("List", "getFieldNames", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) .emitStatement("return FIELD_NAMES") .endMethod() .emitEmptyLine(); } + //@formatter:on + //@formatter:off private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer.beginMethod( qualifiedClassName, // Return type @@ -1053,7 +1078,9 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer.endMethod() .emitEmptyLine(); } + //@formatter:on + //@formatter:off private void setTableValues(JavaWriter writer, String fieldType, String fieldName, String interfaceName, String getter, boolean isUpdate) throws IOException { if ("long".equals(fieldType) || "int".equals(fieldType) @@ -1154,6 +1181,7 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam throw new IllegalStateException("Unsupported type " + fieldType); } } + //@formatter:on private void emitInsertMethod(JavaWriter writer) throws IOException { writer.beginMethod( @@ -1184,6 +1212,7 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { String fieldType = field.asType().toString(); String getter = metadata.getInternalGetter(fieldName); + //@formatter:off if (Utils.isRealmModel(field)) { writer .emitEmptyLine() @@ -1221,11 +1250,12 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { setTableValues(writer, fieldType, fieldName, interfaceName, getter, false); } } + //@formatter:on } writer.emitStatement("return rowIndex"); writer.endMethod() - .emitEmptyLine(); + .emitEmptyLine(); } private void emitInsertListMethod(JavaWriter writer) throws IOException { @@ -1246,16 +1276,17 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { writer.emitStatement("%s object = null", qualifiedClassName); writer.beginControlFlow("while (objects.hasNext())") - .emitStatement("object = (%s) objects.next()", qualifiedClassName); + .emitStatement("object = (%s) objects.next()", qualifiedClassName); writer.beginControlFlow("if(!cache.containsKey(object))"); writer.beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))"); - writer.emitStatement("cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex())") + writer.emitStatement("cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex())") .emitStatement("continue"); writer.endControlFlow(); addPrimaryKeyCheckIfNeeded(metadata, true, writer); + //@formatter:off for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String fieldType = field.asType().toString(); @@ -1299,6 +1330,7 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { } } } + //@formatter:on writer.endControlFlow(); writer.endControlFlow(); @@ -1335,6 +1367,7 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { String fieldType = field.asType().toString(); String getter = metadata.getInternalGetter(fieldName); + //@formatter:off if (Utils.isRealmModel(field)) { writer .emitEmptyLine() @@ -1375,12 +1408,13 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { setTableValues(writer, fieldType, fieldName, interfaceName, getter, true); } } + //@formatter:on } writer.emitStatement("return rowIndex"); writer.endMethod() - .emitEmptyLine(); + .emitEmptyLine(); } private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException { @@ -1405,8 +1439,8 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException writer.beginControlFlow("if(!cache.containsKey(object))"); writer.beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))"); - writer.emitStatement("cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex())") - .emitStatement("continue"); + writer.emitStatement("cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex())") + .emitStatement("continue"); writer.endControlFlow(); addPrimaryKeyCheckIfNeeded(metadata, false, writer); @@ -1415,6 +1449,7 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException String fieldType = field.asType().toString(); String getter = metadata.getInternalGetter(fieldName); + //@formatter:off if (Utils.isRealmModel(field)) { writer .emitEmptyLine() @@ -1455,8 +1490,9 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException setTableValues(writer, fieldType, fieldName, interfaceName, getter, true); } } + //@formatter:on } - writer.endControlFlow(); + writer.endControlFlow(); writer.endControlFlow(); writer.endMethod(); @@ -1468,6 +1504,7 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP String primaryKeyGetter = metadata.getPrimaryKeyGetter(); VariableElement primaryKeyElement = metadata.getPrimaryKey(); if (metadata.isNullable(primaryKeyElement)) { + //@formatter:off if (Utils.isString(primaryKeyElement)) { writer .emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) @@ -1487,6 +1524,7 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP .emitStatement("rowIndex = Table.nativeFindFirstInt(tableNativePtr, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) .endControlFlow(); } + //@formatter:on } else { writer.emitStatement("long rowIndex = Table.NO_MATCH"); writer.emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter); @@ -1530,29 +1568,30 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { writer.emitStatement("RealmObjectProxy cachedRealmObject = cache.get(newObject)"); writer.beginControlFlow("if (cachedRealmObject != null)") - .emitStatement("return (%s) cachedRealmObject", qualifiedClassName) - .nextControlFlow("else"); + .emitStatement("return (%s) cachedRealmObject", qualifiedClassName) + .nextControlFlow("else"); - writer.emitSingleLineComment("rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields."); - if (metadata.hasPrimaryKey()) { - writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, ((%s) newObject).%s(), false, Collections.emptyList())", - qualifiedClassName, qualifiedClassName, interfaceName, metadata.getPrimaryKeyGetter()); - } else { - writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, false, Collections.emptyList())", - qualifiedClassName, qualifiedClassName); + writer.emitSingleLineComment("rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields."); + if (metadata.hasPrimaryKey()) { + writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, ((%s) newObject).%s(), false, Collections.emptyList())", + qualifiedClassName, qualifiedClassName, interfaceName, metadata.getPrimaryKeyGetter()); + } else { + writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, false, Collections.emptyList())", + qualifiedClassName, qualifiedClassName); + } + writer.emitStatement("cache.put(newObject, (RealmObjectProxy) realmObject)"); + for (VariableElement field : metadata.getFields()) { + String fieldName = field.getSimpleName().toString(); + String fieldType = field.asType().toString(); + String setter = metadata.getInternalSetter(fieldName); + String getter = metadata.getInternalGetter(fieldName); + + if (metadata.isPrimaryKey(field)) { + // PK has been set when creating object. + continue; } - writer.emitStatement("cache.put(newObject, (RealmObjectProxy) realmObject)"); - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); - String fieldType = field.asType().toString(); - String setter = metadata.getInternalSetter(fieldName); - String getter = metadata.getInternalGetter(fieldName); - - if (metadata.isPrimaryKey(field)) { - // PK has been set when creating object. - continue; - } + //@formatter:off if (Utils.isRealmModel(field)) { writer .emitEmptyLine() @@ -1597,14 +1636,16 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { writer.emitStatement("((%s) realmObject).%s(((%s) newObject).%s())", interfaceName, setter, interfaceName, getter); } - } + //@formatter:on + } - writer.emitStatement("return realmObject"); - writer.endControlFlow(); + writer.emitStatement("return realmObject"); + writer.endControlFlow(); writer.endMethod(); writer.emitEmptyLine(); } + //@formatter:off private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException { writer.beginMethod( qualifiedClassName, // Return type @@ -1670,6 +1711,7 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException writer.endMethod(); writer.emitEmptyLine(); } + //@formatter:on private void emitUpdateMethod(JavaWriter writer) throws IOException { if (!metadata.hasPrimaryKey()) { @@ -1686,6 +1728,7 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { String fieldName = field.getSimpleName().toString(); String setter = metadata.getInternalSetter(fieldName); String getter = metadata.getInternalGetter(fieldName); + //@formatter:off if (Utils.isRealmModel(field)) { writer .emitStatement("%s %sObj = ((%s) newObject).%s()", @@ -1733,6 +1776,7 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { writer.emitStatement("((%s) realmObject).%s(((%s) newObject).%s())", interfaceName, setter, interfaceName, getter); } + //@formatter:on } writer.emitStatement("return realmObject"); @@ -1746,16 +1790,16 @@ private void emitToStringMethod(JavaWriter writer) throws IOException { } writer.emitAnnotation("Override"); writer.emitAnnotation("SuppressWarnings", "\"ArrayToString\"") - .beginMethod("String", "toString", EnumSet.of(Modifier.PUBLIC)) - .beginControlFlow("if (!RealmObject.isValid(this))") + .beginMethod("String", "toString", EnumSet.of(Modifier.PUBLIC)) + .beginControlFlow("if (!RealmObject.isValid(this))") .emitStatement("return \"Invalid object\"") - .endControlFlow(); + .endControlFlow(); writer.emitStatement("StringBuilder stringBuilder = new StringBuilder(\"%s = [\")", simpleClassName); Collection fields = metadata.getFields(); int i = fields.size() - 1; - for (VariableElement field: fields) { - String fieldName = field.getSimpleName().toString(); + for (VariableElement field : fields) { + String fieldName = field.getSimpleName().toString(); writer.emitStatement("stringBuilder.append(\"{%s:\")", fieldName); if (Utils.isRealmModel(field)) { @@ -1790,7 +1834,7 @@ private void emitToStringMethod(JavaWriter writer) throws IOException { writer.emitStatement("stringBuilder.append(\"]\")"); writer.emitStatement("return stringBuilder.toString()"); writer.endMethod() - .emitEmptyLine(); + .emitEmptyLine(); } /** @@ -1798,6 +1842,7 @@ private void emitToStringMethod(JavaWriter writer) throws IOException { * alternate due to Realm Java using {@code Table#moveLastOver()}. Hash codes should therefore not * be considered stable, i.e. don't save them in a HashSet or use them as a key in a HashMap. */ + //@formatter:off private void emitHashcodeMethod(JavaWriter writer) throws IOException { if (metadata.containsHashCode()) { return; @@ -1816,7 +1861,9 @@ private void emitHashcodeMethod(JavaWriter writer) throws IOException { .endMethod() .emitEmptyLine(); } + //@formatter:on + //@formatter:off private void emitEqualsMethod(JavaWriter writer) throws IOException { if (metadata.containsEquals()) { return; @@ -1843,6 +1890,7 @@ private void emitEqualsMethod(JavaWriter writer) throws IOException { .endMethod() .emitEmptyLine(); } + //@formatter:on private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOException { writer.emitAnnotation("SuppressWarnings", "\"cast\""); @@ -1860,6 +1908,8 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep writer.emitStatement("final List excludeFields = new ArrayList(%1$d)", modelOrListCount); } + + //@formatter:off if (!metadata.hasPrimaryKey()) { buildExcludeFieldsList(writer, metadata.getFields()); writer.emitStatement("%s obj = realm.createObjectInternal(%s.class, true, excludeFields)", @@ -1909,6 +1959,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep primaryKeyFieldType, primaryKeyFieldName, writer); writer.endControlFlow(); } + //@formatter:on for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); @@ -1986,7 +2037,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer.beginControlFlow("if (false)"); Collection fields = metadata.getFields(); - for (VariableElement field: fields) { + for (VariableElement field : fields) { String fieldName = field.getSimpleName().toString(); String qualifiedFieldType = field.asType().toString(); writer.nextControlFlow("else if (name.equals(\"%s\"))", fieldName); @@ -2031,7 +2082,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { if (metadata.hasPrimaryKey()) { writer.beginControlFlow("if (!jsonHasPrimaryKey)") .emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.getPrimaryKey()) - .endControlFlow(); + .endControlFlow(); } writer.emitStatement("obj = realm.copyToRealm(obj)"); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java index b653a27f3b..ed1785e904 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java @@ -28,6 +28,7 @@ import io.realm.annotations.Ignore; + public class RealmProxyInterfaceGenerator { private ProcessingEnvironment processingEnvironment; private ClassMetaData metaData; @@ -75,11 +76,11 @@ public void generate() throws IOException { // backlinks are final and have only a getter. for (Backlink backlink : metaData.getBacklinkFields()) { writer - .beginMethod( - backlink.getTargetFieldType(), - metaData.getInternalGetter(backlink.getTargetField()), - EnumSet.of(Modifier.PUBLIC)) - .endMethod(); + .beginMethod( + backlink.getTargetFieldType(), + metaData.getInternalGetter(backlink.getTargetField()), + EnumSet.of(Modifier.PUBLIC)) + .endMethod(); } writer.endType(); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index e438b10348..f02aefc106 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -35,6 +35,7 @@ import static io.realm.processor.Constants.REALM_PACKAGE_NAME; + public class RealmProxyMediatorGenerator { private final String className; private ProcessingEnvironment processingEnvironment; @@ -42,7 +43,7 @@ public class RealmProxyMediatorGenerator { private List qualifiedProxyClasses = new ArrayList(); public RealmProxyMediatorGenerator(ProcessingEnvironment processingEnvironment, - String className, Set classesToValidate) { + String className, Set classesToValidate) { this.processingEnvironment = processingEnvironment; this.className = className; @@ -262,7 +263,7 @@ private void emitCopyToRealmMethod(JavaWriter writer) throws IOException { " E", "copyOrUpdate", EnumSet.of(Modifier.PUBLIC), - "Realm", "realm", "E", "obj", "boolean", "update", "Map", "cache" + "Realm", "realm", "E", "obj", "boolean", "update", "Map", "cache" ); writer.emitSingleLineComment("This cast is correct because obj is either"); writer.emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject"); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java index bcf57bf780..b0f37fa741 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java @@ -25,6 +25,7 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.tools.Diagnostic; + public class RealmVersionChecker { public static final String REALM_ANDROID_DOWNLOAD_URL = "http://static.realm.io/downloads/java/latest"; @@ -61,8 +62,7 @@ public void run() { try { backgroundThread.join(CONNECT_TIMEOUT + READ_TIMEOUT); - } - catch (InterruptedException ignore) { + } catch (InterruptedException ignore) { // We ignore this exception on purpose not to break the build system if this class fails } } @@ -79,7 +79,7 @@ private String checkLatestVersion() { String result = REALM_VERSION; try { URL url = new URL(VERSION_URL + REALM_VERSION); - HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(CONNECT_TIMEOUT); conn.setReadTimeout(READ_TIMEOUT); BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java index d7ee9217e1..4b157b4404 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java @@ -15,6 +15,7 @@ import javax.lang.model.util.Types; import javax.tools.Diagnostic; + /** * Utility methods working with the Realm processor. */ @@ -207,7 +208,7 @@ public static String getFieldTypeQualifiedName(VariableElement field) { * @return the simple type name for a field. */ public static String getFieldTypeSimpleName(VariableElement field) { - return (null == field) ? null : getFieldTypeSimpleName(getFieldTypeQualifiedName(field)); + return (null == field) ? null : getFieldTypeSimpleName(getFieldTypeQualifiedName(field)); } /** diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 9bdadab377..f3fc9a16b4 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -41,6 +41,7 @@ import io.realm.log.RealmLog; import rx.Observable; + /** * Base class for all Realm instances. * @@ -78,12 +79,12 @@ protected BaseRealm(RealmConfiguration configuration) { this.sharedRealm = SharedRealm.getInstance(configuration, !(this instanceof Realm) ? null : - new SharedRealm.SchemaVersionListener() { - @Override - public void onSchemaVersionChanged(long currentVersion) { - RealmCache.updateSchemaCache((Realm) BaseRealm.this); - } - }, true); + new SharedRealm.SchemaVersionListener() { + @Override + public void onSchemaVersionChanged(long currentVersion) { + RealmCache.updateSchemaCache((Realm) BaseRealm.this); + } + }, true); this.schema = new RealmSchema(this); } @@ -157,7 +158,7 @@ protected void removeListener(RealmChangeListener liste *

            * If you would like the {@code asObservable()} to stop emitting items, you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: - * + *

            *

                  * {@code
                  * realm.asObservable().first().subscribe( ... ) // You only get the results once
            @@ -478,7 +479,7 @@  E get(Class clazz, String dynamicClassName, UncheckedR
                         result = (E) new DynamicRealmObject(this, CheckedRow.getFromRow(row));
                     } else {
                         result = configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz),
            -                    false, Collections. emptyList());
            +                    false, Collections.emptyList());
                     }
                     RealmObjectProxy proxy = (RealmObjectProxy) result;
                     proxy.realmGet$proxyState().setTableVersion$realm();
            @@ -511,7 +512,7 @@  E get(Class clazz, String dynamicClassName, long rowIn
                     } else {
                         result = configuration.getSchemaMediator().newInstance(clazz, this,
                                 (rowIndex != Table.NO_MATCH) ? table.getUncheckedRow(rowIndex) : InvalidRow.INSTANCE,
            -                    schema.getColumnInfo(clazz), false, Collections. emptyList());
            +                    schema.getColumnInfo(clazz), false, Collections.emptyList());
                     }
             
                     RealmObjectProxy proxy = (RealmObjectProxy) result;
            @@ -573,7 +574,7 @@ static boolean compactRealm(final RealmConfiguration configuration) {
                  * Migrates the Realm file defined by the given configuration using the provided migration block.
                  *
                  * @param configuration configuration for the Realm that should be migrated. If this is a SyncConfiguration this
            -     *                      method does nothing.
            +     * method does nothing.
                  * @param migration if set, this migration block will override what is set in {@link RealmConfiguration}.
                  * @param callback callback for specific Realm type behaviors.
                  * @param cause which triggers this migration.
            @@ -581,7 +582,7 @@ static boolean compactRealm(final RealmConfiguration configuration) {
                  * @throws IllegalArgumentException if the provided configuration is a {@link SyncConfiguration}.
                  */
                 protected static void migrateRealm(final RealmConfiguration configuration, final RealmMigration migration,
            -                                       final MigrationCallback callback, final RealmMigrationNeededException cause)
            +            final MigrationCallback callback, final RealmMigrationNeededException cause)
                         throws FileNotFoundException {
             
                     if (configuration == null) {
            @@ -663,7 +664,7 @@ public static final class RealmObjectContext {
                     private List excludeFields;
             
                     public void set(BaseRealm realm, Row row, ColumnInfo columnInfo,
            -                        boolean acceptDefaultValue, List excludeFields) {
            +                boolean acceptDefaultValue, List excludeFields) {
                         this.realm = realm;
                         this.row = row;
                         this.columnInfo = columnInfo;
            @@ -699,6 +700,7 @@ public void clear() {
                         excludeFields = null;
                     }
                 }
            +
                 static final class ThreadLocalRealmObjectContext extends ThreadLocal {
                     @Override
                     protected RealmObjectContext initialValue() {
            diff --git a/realm/realm-library/src/main/java/io/realm/Case.java b/realm/realm-library/src/main/java/io/realm/Case.java
            index 2bb8f18960..b234a13f4c 100644
            --- a/realm/realm-library/src/main/java/io/realm/Case.java
            +++ b/realm/realm-library/src/main/java/io/realm/Case.java
            @@ -36,6 +36,7 @@ public enum Case {
             
                 /**
                  * Returns the value for this setting that is used by the underlying query engine.
            +     *
                  * @return The value used by the underlying query engine to indicate this value.
                  */
                 public boolean getValue() {
            diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java
            index 30d66ad0e5..3a4f79ea94 100644
            --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java
            +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java
            @@ -22,6 +22,7 @@
             import io.realm.log.RealmLog;
             import rx.Observable;
             
            +
             /**
              * DynamicRealm is a dynamic variant of {@link io.realm.Realm}. This means that all access to data and/or queries are
              * done using string based class names instead of class type references.
            @@ -54,9 +55,9 @@ private DynamicRealm(RealmConfiguration configuration) {
                  * DynamicRealm will never trigger a migration.
                  *
                  * @return the DynamicRealm defined by the configuration.
            -     * @see RealmConfiguration for details on how to configure a Realm.
                  * @throws RealmFileException if an error happened when accessing the underlying Realm file.
                  * @throws IllegalArgumentException if {@code configuration} argument is {@code null}.
            +     * @see RealmConfiguration for details on how to configure a Realm.
                  */
                 public static DynamicRealm getInstance(RealmConfiguration configuration) {
                     if (configuration == null) {
            @@ -93,7 +94,7 @@ public DynamicRealmObject createObject(String className) {
                  * @throws RealmException if object could not be created due to the primary key being invalid.
                  * @throws IllegalStateException if the model clazz does not have an primary key defined.
                  * @throws IllegalArgumentException if the {@code primaryKeyValue} doesn't have a value that can be converted to the
            -     *                                  expected value.
            +     * expected value.
                  */
                 public DynamicRealmObject createObject(String className, Object primaryKeyValue) {
                     Table table = schema.getTable(className);
            @@ -106,8 +107,8 @@ public DynamicRealmObject createObject(String className, Object primaryKeyValue)
                  *
                  * @param className the class of the object which is to be queried.
                  * @return a RealmQuery, which can be used to query for specific objects of provided type.
            -     * @see io.realm.RealmQuery
                  * @throws IllegalArgumentException if the class doesn't exist.
            +     * @see io.realm.RealmQuery
                  */
                 public RealmQuery where(String className) {
                     checkIfValid();
            diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java
            index 7488623c72..2728e043a8 100644
            --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java
            +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java
            @@ -28,6 +28,7 @@
             import io.realm.internal.UncheckedRow;
             import io.realm.internal.android.JsonUtils;
             
            +
             /**
              * Class that wraps a normal RealmObject in order to allow dynamic access instead of a typed interface.
              * Using a DynamicRealmObject is slower than using the regular RealmObject class.
            @@ -89,15 +90,24 @@ public  E get(String fieldName) {
                     long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName);
                     RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex);
                     switch (type) {
            -            case BOOLEAN: return (E) Boolean.valueOf(proxyState.getRow$realm().getBoolean(columnIndex));
            -            case INTEGER: return (E) Long.valueOf(proxyState.getRow$realm().getLong(columnIndex));
            -            case FLOAT: return (E) Float.valueOf(proxyState.getRow$realm().getFloat(columnIndex));
            -            case DOUBLE: return (E) Double.valueOf(proxyState.getRow$realm().getDouble(columnIndex));
            -            case STRING: return (E) proxyState.getRow$realm().getString(columnIndex);
            -            case BINARY: return (E) proxyState.getRow$realm().getBinaryByteArray(columnIndex);
            -            case DATE: return (E) proxyState.getRow$realm().getDate(columnIndex);
            -            case OBJECT: return (E) getObject(fieldName);
            -            case LIST: return (E) getList(fieldName);
            +            case BOOLEAN:
            +                return (E) Boolean.valueOf(proxyState.getRow$realm().getBoolean(columnIndex));
            +            case INTEGER:
            +                return (E) Long.valueOf(proxyState.getRow$realm().getLong(columnIndex));
            +            case FLOAT:
            +                return (E) Float.valueOf(proxyState.getRow$realm().getFloat(columnIndex));
            +            case DOUBLE:
            +                return (E) Double.valueOf(proxyState.getRow$realm().getDouble(columnIndex));
            +            case STRING:
            +                return (E) proxyState.getRow$realm().getString(columnIndex);
            +            case BINARY:
            +                return (E) proxyState.getRow$realm().getBinaryByteArray(columnIndex);
            +            case DATE:
            +                return (E) proxyState.getRow$realm().getDate(columnIndex);
            +            case OBJECT:
            +                return (E) getObject(fieldName);
            +            case LIST:
            +                return (E) getList(fieldName);
                         case UNSUPPORTED_TABLE:
                         case UNSUPPORTED_MIXED:
                         default:
            @@ -405,11 +415,11 @@ public String[] getFieldNames() {
             
                 /**
                  * Sets the value for the given field. This method will automatically try to convert numbers and
            -     * booleans that are given as {@code String} to their appropriate type. For example {@code "10"} 
            +     * booleans that are given as {@code String} to their appropriate type. For example {@code "10"}
                  * will be converted to {@code 10} if the field type is {@code int}.
                  * 

            * Using the typed setters will be faster than using this method. - * + * * @throws IllegalArgumentException if field name doesn't exist or if the input value cannot be converted * to the appropriate input type. * @throws NumberFormatException if a String based number cannot be converted properly. @@ -426,12 +436,22 @@ public void set(String fieldName, Object value) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); if (isString && type != RealmFieldType.STRING) { - switch(type) { - case BOOLEAN: value = Boolean.parseBoolean(strValue); break; - case INTEGER: value = Long.parseLong(strValue); break; - case FLOAT: value = Float.parseFloat(strValue); break; - case DOUBLE: value = Double.parseDouble(strValue); break; - case DATE: value = JsonUtils.stringToDate(strValue); break; + switch (type) { + case BOOLEAN: + value = Boolean.parseBoolean(strValue); + break; + case INTEGER: + value = Long.parseLong(strValue); + break; + case FLOAT: + value = Float.parseFloat(strValue); + break; + case DOUBLE: + value = Double.parseDouble(strValue); + break; + case DATE: + value = JsonUtils.stringToDate(strValue); + break; default: throw new IllegalArgumentException(String.format("Field %s is not a String field, " + "and the provide value could not be automatically converted: %s. Use a typed" + @@ -697,7 +717,7 @@ public void setList(String fieldName, RealmList list) { if (!linkTargetTableName.equals(listType)) { throw new IllegalArgumentException(String.format(Locale.ENGLISH, "The elements in the list are not the proper type. " + - "Was %s expected %s.", listType, linkTargetTableName)); + "Was %s expected %s.", listType, linkTargetTableName)); } typeValidated = true; } @@ -797,8 +817,8 @@ private void checkFieldType(String fieldName, long columnIndex, RealmFieldType e * other threads. This means that a hash code value of the object is not stable, and the value * should be neither used as a key in HashMap nor saved in HashSet. * - * @return a hash code value for the object. - * @see #equals + * @return a hash code value for the object. + * @see #equals */ @Override public int hashCode() { diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java index a05a132c8b..896eb28665 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java @@ -18,27 +18,28 @@ import java.util.List; + /** * An {@code OrderedRealmCollection} is a collection which maintains an ordering for its elements. Every * element in the {@code OrderedRealmCollection} has an index. Each element can thus be accessed by its * index, with the first index being zero. Normally, {@code OrderedRealmCollection}s allow duplicate * elements, as compared to Sets, where elements have to be unique. - * *

            - * + *

            + *

            * There are three types of {@link OrderedRealmCollection}. {@link RealmResults} and {@link RealmList} are live * collections. They are up-to-date all the time and they will never contain an invalid {@link RealmObject}. * {@link OrderedRealmCollectionSnapshot} is different. An {@link OrderedRealmCollectionSnapshot} can be created from * another {@link OrderedRealmCollection}. Its size and elements order stay the same as the original collection's when * it was created. {@link OrderedRealmCollectionSnapshot} may contain invalid {@link RealmObject}s if the objects get * deleted. - * *

            - * + *

            + *

            *

            * Using iterators to iterate on {@link OrderedRealmCollection} will always work. You can delete or modify the elements * without impacting the iterator. See below example: - * + *

            *

              * {@code
              * RealmResults dogs = realm.where(Dog.class).findAll();
            @@ -52,16 +53,16 @@
              * s = dogs.size(); // 0
              * }
              * 
            - * + *

            * An iterator created from a live collection will create a stable view when the iterator is created, allowing you to * delete and modify elements while iterating without impacting the iterator. However, the {@code RealmResults} backing * the iterator will still be live updated meaning that size and order of elements can change when iterating. * {@link RealmList} has the same behaviour as {@link RealmResults} since they are both live collections. - * *

            - * + *

            + *

            * A simple for-loop is different. See below example: - * + *

            *

              * {@code
              * RealmResults dogs = realm.where(Dog.class).findAll();
            @@ -73,12 +74,12 @@
              * s = dogs.size(); // 5
              * }
              * 
            - * + *

            * The above example only deletes half of elements in the {@link RealmResults}. This is because of {@code dogs.size()} * decreased by 1 for every loop. The deletion happens in the loop will immediately impact the size of * {@code RealmResults}. To solve this problem, you can create a {@link OrderedRealmCollectionSnapshot} from the * {@link RealmResults} or {@link RealmList} and do simple for-loop on that instead: - * + *

            *

              * {@code
              * RealmResults dogs = realm.where(Dog.class).findAll();
            @@ -93,7 +94,7 @@
              * // dogs.size() == 0 && snapshot.size() == 10
              * }
              * 
            - * + *

            * As you can see, after deletion, the size and elements order of snapshot stay the same as before. But the element at * the position becomes invalid. */ @@ -133,11 +134,11 @@ public interface OrderedRealmCollection extends List, R * Sorts a collection based on the provided field in ascending order. * * @param fieldName the field name to sort by. Only fields of type boolean, short, int, long, float, double, Date, - * and String are supported. + * and String are supported. * @return a new sorted {@link RealmResults} will be created and returned. The original collection stays unchanged. * @throws java.lang.IllegalArgumentException if field name does not exist or it has an invalid type. * @throws java.lang.IllegalStateException if the Realm is closed, called on the wrong thread or the collection is - * an unmanaged collection. + * an unmanaged collection. */ RealmResults sort(String fieldName); @@ -145,12 +146,12 @@ public interface OrderedRealmCollection extends List, R * Sorts a collection based on the provided field and sort order. * * @param fieldName the field name to sort by. Only fields of type boolean, short, int, long, float, double, Date, - * and String are supported. + * and String are supported. * @param sortOrder the direction to sort by. * @return a new sorted {@link RealmResults} will be created and returned. The original collection stays unchanged. * @throws java.lang.IllegalArgumentException if field name does not exist or has an invalid type. * @throws java.lang.IllegalStateException if the Realm is closed, called on the wrong thread or the collection is - * an unmanaged collection. + * an unmanaged collection. */ RealmResults sort(String fieldName, Sort sortOrder); @@ -158,15 +159,15 @@ public interface OrderedRealmCollection extends List, R * Sorts a collection based on the provided fields and sort orders. * * @param fieldName1 first field name. Only fields of type boolean, short, int, long, float, - * double, Date, and String are supported. + * double, Date, and String are supported. * @param sortOrder1 sort order for first field. * @param fieldName2 second field name. Only fields of type boolean, short, int, long, float, - * double, Date, and String are supported. + * double, Date, and String are supported. * @param sortOrder2 sort order for second field. * @return a new sorted {@link RealmResults} will be created and returned. The original collection stays unchanged. * @throws java.lang.IllegalArgumentException if a field name does not exist or has an invalid type. * @throws java.lang.IllegalStateException if the Realm is closed, called on the wrong thread or the collection is - * an unmanaged collection. + * an unmanaged collection. */ RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2); @@ -174,12 +175,12 @@ public interface OrderedRealmCollection extends List, R * Sorts a collection based on the provided fields and sort orders. * * @param fieldNames an array of field names to sort by. Only fields of type boolean, short, int, long, float, - * double, Date, and String are supported. + * double, Date, and String are supported. * @param sortOrders the directions to sort by. * @return a new sorted {@link RealmResults} will be created and returned. The original collection stays unchanged. * @throws java.lang.IllegalArgumentException if a field name does not exist or has an invalid type. * @throws java.lang.IllegalStateException if the Realm is closed, called on the wrong thread or the collection is - * an unmanaged collection. + * an unmanaged collection. */ RealmResults sort(String[] fieldNames, Sort[] sortOrders); @@ -215,9 +216,9 @@ public interface OrderedRealmCollection extends List, R * Creates a snapshot from this {@link OrderedRealmCollection}. * * @return the snapshot of this collection. - * @see OrderedRealmCollectionSnapshot * @throws java.lang.IllegalStateException if the Realm is closed or the method is called from the wrong thread. * @throws UnsupportedOperationException if the collection is unmanaged. + * @see OrderedRealmCollectionSnapshot */ OrderedRealmCollectionSnapshot createSnapshot(); } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index 63cbb78484..e7852490d1 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -13,6 +13,7 @@ import io.realm.internal.Table; import io.realm.internal.UncheckedRow; + /** * General implementation for {@link OrderedRealmCollection} which is based on the {@code Collection}. */ @@ -71,7 +72,7 @@ public boolean isManaged() { * * @param object the object to search for. * @return {@code true} if {@code object} is an element of this {@code OrderedRealmCollection}, - * {@code false} otherwise. + * {@code false} otherwise. */ @Override public boolean contains(Object object) { @@ -288,7 +289,7 @@ public RealmResults sort(String fieldNames[], Sort sortOrders[]) { */ @Override public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { - return sort(new String[]{fieldName1, fieldName2}, new Sort[]{sortOrder1, sortOrder2}); + return sort(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); } // Aggregates @@ -341,7 +342,7 @@ public Number max(String fieldName) { * Finds the maximum date. * * @param fieldName the field to look for the maximum date. If fieldName is not of Date type, an exception is - * thrown. + * thrown. * @return if no objects exist or they all have {@code null} as the value for the given date field, {@code null} * will be returned. Otherwise the maximum date is returned. When determining the maximum date, objects with * {@code null} values are ignored. @@ -499,7 +500,7 @@ public void add(int index, E element) { @Override @Deprecated public boolean addAll(int location, - @SuppressWarnings("NullableProblems") java.util.Collection collection) { + @SuppressWarnings("NullableProblems") java.util.Collection collection) { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java index 5c8067faf1..49124234d0 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java @@ -19,6 +19,7 @@ import io.realm.internal.Collection; import io.realm.internal.UncheckedRow; + /** * An {@link OrderedRealmCollectionSnapshot} is a special type of {@link OrderedRealmCollection}. It can be created by * calling {@link OrderedRealmCollection#createSnapshot()}. Unlike {@link RealmResults} and {@link RealmList}, its @@ -204,8 +205,8 @@ public boolean deleteLastFromRealm() { * This deletes all objects in the collection from the underlying Realm. All objects in the collection snapshot * will become invalid. * - * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. * @return {@code true} if objects was deleted, {@code false} otherwise. + * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. * @throws java.lang.IllegalStateException if the Realm has been closed or called from an incorrect thread. */ @Override diff --git a/realm/realm-library/src/main/java/io/realm/Property.java b/realm/realm-library/src/main/java/io/realm/Property.java index ef9069c81a..02557c6cd6 100644 --- a/realm/realm-library/src/main/java/io/realm/Property.java +++ b/realm/realm-library/src/main/java/io/realm/Property.java @@ -23,8 +23,8 @@ class Property { public static final boolean PRIMARY_KEY = true; - public static final boolean REQUIRED = true; - public static final boolean INDEXED = true; + public static final boolean REQUIRED = true; + public static final boolean INDEXED = true; private final long nativePtr; @@ -52,6 +52,8 @@ public void close() { } private static native long nativeCreateProperty(String name, int type, boolean isPrimary, boolean isIndexed, boolean isNullable); + private static native long nativeCreateProperty(String name, int type, String linkedToName); + private static native void nativeClose(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 843fb229c4..4a5e7dafc9 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -24,6 +24,7 @@ import io.realm.internal.Row; import io.realm.internal.UncheckedRow; + /** * This implements {@code RealmObjectProxy} interface, to eliminate copying logic between * {@link RealmObject} and {@link DynamicRealmObject}. @@ -147,7 +148,7 @@ public void onChange(ProxyState element) { // If the Row gets detached, table version will be -1 and it is different from current value. tableVersion = row.getTable().getVersion(); } - if (currentTableVersion != tableVersion) { + if (currentTableVersion != tableVersion) { currentTableVersion = tableVersion; notifyChangeListeners(); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 63881a0153..49604bb690 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -60,6 +60,7 @@ import io.realm.log.RealmLog; import rx.Observable; + /** * The Realm class is the storage and transactional manager of your object persistent store. It is in charge of creating * instances of your RealmObjects. Objects within a Realm can be queried and read at any time. Creating, modifying, and @@ -200,7 +201,7 @@ public static synchronized void init(Context context) { * @return an instance of the Realm class. * @throws java.lang.NullPointerException if no default configuration has been defined. * @throws RealmMigrationNeededException if no migration has been provided by the default configuration and the - * RealmObject classes or version has has changed so a migration is required. + * RealmObject classes or version has has changed so a migration is required. * @throws RealmFileException if an error happened when accessing the underlying Realm file. */ public static Realm getDefaultInstance() { @@ -216,7 +217,7 @@ public static Realm getDefaultInstance() { * @param configuration {@link RealmConfiguration} used to open the Realm * @return an instance of the Realm class * @throws RealmMigrationNeededException if no migration has been provided by the configuration and the RealmObject - * classes or version has has changed so a migration is required. + * classes or version has has changed so a migration is required. * @throws RealmFileException if an error happened when accessing the underlying Realm file. * @throws IllegalArgumentException if a null {@link RealmConfiguration} is provided. * @see RealmConfiguration for details on how to configure a Realm. @@ -255,9 +256,9 @@ public static void removeDefaultConfiguration() { * * @param configuration {@link RealmConfiguration} used to create the Realm. * @param globalCacheArray if this is not {@code null} and contains an entry for current schema version, - * the {@link BaseRealm#schema#columnIndices} will be initialized with the copy of - * the entry. Otherwise, {@link BaseRealm#schema#columnIndices} will be populated - * from the Realm file. + * the {@link BaseRealm#schema#columnIndices} will be initialized with the copy of + * the entry. Otherwise, {@link BaseRealm#schema#columnIndices} will be populated + * from the Realm file. * @return a {@link Realm} instance. */ static Realm createInstance(RealmConfiguration configuration, ColumnIndices[] globalCacheArray) { @@ -857,7 +858,7 @@ public E createObject(Class clazz) { * * @param clazz the Class of the object to create. * @param acceptDefaultValue if {@code true}, default value of the object will be applied and - * if {@code false}, it will be ignored. + * if {@code false}, it will be ignored. * @return the new object. * @throws RealmException if the primary key is defined in the model class or an object cannot be created. */ @@ -902,7 +903,7 @@ public E createObject(Class clazz, Object primaryKeyVa * @param clazz the Class of the object to create. * @param primaryKeyValue value for the primary key field. * @param acceptDefaultValue if {@code true}, default value of the object will be applied and - * if {@code false}, it will be ignored. + * if {@code false}, it will be ignored. * @return the new object. * @throws RealmException if object could not be created due to the primary key being invalid. * @throws IllegalStateException if the model class does not have an primary key defined. @@ -993,8 +994,8 @@ public List copyToRealm(Iterable objects) { * Please note: *

              *
            • - * We don't check if the provided objects are already managed or not, so inserting a managed object might duplicate it. - * Duplication will only happen if the object doesn't have a primary key. Objects with primary keys will never get duplicated. + * We don't check if the provided objects are already managed or not, so inserting a managed object might duplicate it. + * Duplication will only happen if the object doesn't have a primary key. Objects with primary keys will never get duplicated. *
            • *
            • We don't create (nor return) a managed {@link RealmObject} for each element
            • *
            • Copying an object will copy all field values. Any unset field in the object and child objects will be set to their default value if not provided
            • @@ -1027,8 +1028,8 @@ public void insert(Collection objects) { * Please note: *
                *
              • - * We don't check if the provided objects are already managed or not, so inserting a managed object might duplicate it. - * Duplication will only happen if the object doesn't have a primary key. Objects with primary keys will never get duplicated. + * We don't check if the provided objects are already managed or not, so inserting a managed object might duplicate it. + * Duplication will only happen if the object doesn't have a primary key. Objects with primary keys will never get duplicated. *
              • *
              • We don't create (nor return) a managed {@link RealmObject} for each element
              • *
              • Copying an object will copy all field values. Any unset field in the object and child objects will be set to their default value if not provided
              • @@ -1039,7 +1040,7 @@ public void insert(Collection objects) { * * @param object RealmObjects to insert. * @throws IllegalStateException if the corresponding Realm is closed, called from an incorrect thread or not in a - * transaction. + * transaction. * @throws io.realm.exceptions.RealmPrimaryKeyConstraintException if two objects with the same primary key is * inserted or if a primary key value already exists in the Realm. * @see #copyToRealm(RealmModel) @@ -1062,8 +1063,8 @@ public void insert(RealmModel object) { * Please note: *
                  *
                • - * We don't check if the provided objects are already managed or not, so inserting a managed object might duplicate it. - * Duplication will only happen if the object doesn't have a primary key. Objects with primary keys will never get duplicated. + * We don't check if the provided objects are already managed or not, so inserting a managed object might duplicate it. + * Duplication will only happen if the object doesn't have a primary key. Objects with primary keys will never get duplicated. *
                • *
                • We don't create (nor return) a managed {@link RealmObject} for each element
                • *
                • Copying an object will copy all field values. Any unset field in the object and child objects will be set to their default value if not provided
                • @@ -1099,8 +1100,8 @@ public void insertOrUpdate(Collection objects) { * Please note: *
                    *
                  • - * We don't check if the provided objects are already managed or not, so inserting a managed object might duplicate it. - * Duplication will only happen if the object doesn't have a primary key. Objects with primary keys will never get duplicated. + * We don't check if the provided objects are already managed or not, so inserting a managed object might duplicate it. + * Duplication will only happen if the object doesn't have a primary key. Objects with primary keys will never get duplicated. *
                  • *
                  • We don't create (nor return) a managed {@link RealmObject} for each element
                  • *
                  • Copying an object will copy all field values. Any unset field in the object and child objects will be set to their default value if not provided
                  • @@ -1186,7 +1187,7 @@ public List copyFromRealm(Iterable realmObjects) { * * @param realmObjects RealmObjects to copy. * @param maxDepth limit of the deep copy. All references after this depth will be {@code null}. Starting depth is - * {@code 0}. + * {@code 0}. * @param type of object. * @return an in-memory detached copy of the RealmObjects. * @throws IllegalArgumentException if {@code maxDepth < 0}, the RealmObject is no longer accessible or it is a @@ -1345,7 +1346,7 @@ public void executeTransaction(Transaction transaction) { * @param transaction {@link io.realm.Realm.Transaction} to execute. * @return a {@link RealmAsyncTask} representing a cancellable task. * @throws IllegalArgumentException if the {@code transaction} is {@code null}, or if the Realm is opened from - * another thread. + * another thread. */ public RealmAsyncTask executeTransactionAsync(final Transaction transaction) { return executeTransactionAsync(transaction, null, null); @@ -1358,7 +1359,7 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction) { * @param onSuccess callback invoked when the transaction succeeds. * @return a {@link RealmAsyncTask} representing a cancellable task. * @throws IllegalArgumentException if the {@code transaction} is {@code null}, or if the realm is opened from - * another thread. + * another thread. */ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, final Realm.Transaction.OnSuccess onSuccess) { if (onSuccess == null) { @@ -1375,7 +1376,7 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, fin * @param onError callback invoked when the transaction fails. * @return a {@link RealmAsyncTask} representing a cancellable task. * @throws IllegalArgumentException if the {@code transaction} is {@code null}, or if the realm is opened from - * another thread. + * another thread. */ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, final Realm.Transaction.OnError onError) { if (onError == null) { @@ -1393,11 +1394,11 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, fin * @param onError callback invoked when the transaction fails. * @return a {@link RealmAsyncTask} representing a cancellable task. * @throws IllegalArgumentException if the {@code transaction} is {@code null}, or if the realm is opened from - * another thread. + * another thread. */ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, - final Realm.Transaction.OnSuccess onSuccess, - final Realm.Transaction.OnError onError) { + final Realm.Transaction.OnSuccess onSuccess, + final Realm.Transaction.OnError onError) { checkIfValid(); if (transaction == null) { @@ -1457,7 +1458,7 @@ public void run() { final Throwable backgroundException = exception; final SharedRealm.VersionID backgroundVersionID = versionID; // Cannot be interrupted anymore. - if (canDeliverNotification ) { + if (canDeliverNotification) { if (backgroundVersionID != null && onSuccess != null) { realmNotifier.post(new Runnable() { @Override @@ -1593,7 +1594,7 @@ public void migrationComplete() { * * @param configuration the{@link RealmConfiguration}. * @param migration the {@link RealmMigration} to run on the Realm. This will override any migration set on the - * configuration. + * configuration. * @throws FileNotFoundException if the Realm file doesn't exist. */ public static void migrateRealm(RealmConfiguration configuration, RealmMigration migration) @@ -1646,7 +1647,7 @@ Table getTable(Class clazz) { * Updates own schema cache. * * @param globalCacheArray global cache of column indices. If it contains an entry for current - * schema version, this method only copies the indices information in the entry. + * schema version, this method only copies the indices information in the entry. * @return newly created indices information for current schema version. Or {@code null} if {@code globalCacheArray} * already contains the entry for current schema version. */ diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index de3559da42..ae372e6f37 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -31,6 +31,7 @@ import io.realm.internal.Table; import io.realm.log.RealmLog; + /** * To cache {@link Realm}, {@link DynamicRealm} instances and related resources. * Every thread will share the same {@link Realm} and {@link DynamicRealm} instances which are referred to the same @@ -56,6 +57,7 @@ private static class RefAndCount { // How many threads have instances refer to this configuration. private int globalCount = 0; } + private enum RealmCacheType { TYPED_REALM, DYNAMIC_REALM; @@ -70,6 +72,7 @@ static RealmCacheType valueOf(Class clazz) { throw new IllegalArgumentException(WRONG_REALM_CLASS_MESSAGE); } } + // Separated references and counters for typed Realm and dynamic Realm. private final EnumMap refAndCountMap; @@ -103,7 +106,7 @@ private RealmCache(RealmConfiguration config) { * @return the {@link Realm} or {@link DynamicRealm} instance. */ static synchronized E createRealmOrGetFromCache(RealmConfiguration configuration, - Class realmClass) { + Class realmClass) { boolean isCacheInMap = true; RealmCache cache = cachesMap.get(configuration.getPath()); if (cache == null) { @@ -262,10 +265,10 @@ private void validateConfiguration(RealmConfiguration newConfiguration) { // Tries to detect this problem specifically so we can throw a better error message. RealmMigration newMigration = newConfiguration.getMigration(); RealmMigration oldMigration = configuration.getMigration(); - if (oldMigration != null - && newMigration != null - && oldMigration.getClass().equals(newMigration.getClass()) - && !newMigration.equals(oldMigration)) { + if (oldMigration != null + && newMigration != null + && oldMigration.getClass().equals(newMigration.getClass()) + && !newMigration.equals(oldMigration)) { throw new IllegalArgumentException("Configurations cannot be different if used to open the same file. " + "The most likely cause is that equals() and hashCode() are not overridden in the " + "migration class: " + newConfiguration.getMigration().getClass().getCanonicalName()); @@ -321,7 +324,7 @@ static synchronized void updateSchemaCache(Realm realm) { } } - /** + /** * Runs the callback function with synchronization on {@link RealmCache}. * * @param callback the callback will be executed. diff --git a/realm/realm-library/src/main/java/io/realm/RealmChangeListener.java b/realm/realm-library/src/main/java/io/realm/RealmChangeListener.java index 2e8e7ff7c1..fac6e8f70b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmChangeListener.java +++ b/realm/realm-library/src/main/java/io/realm/RealmChangeListener.java @@ -31,9 +31,8 @@ * deleted, it can be verified by using {@link RealmObject#isValid()}. * * @param The live object being returned - * ({@link Realm}, {@link DynamicRealm}, {@link RealmObject}, {@link RealmResults}, {@link DynamicRealmObject} - * or your model implementing {@link RealmModel}) - * + * ({@link Realm}, {@link DynamicRealm}, {@link RealmObject}, {@link RealmResults}, {@link DynamicRealmObject} + * or your model implementing {@link RealmModel}) * @see Realm#addChangeListener(RealmChangeListener) * @see Realm#removeAllChangeListeners() * @see Realm#removeChangeListener(RealmChangeListener) diff --git a/realm/realm-library/src/main/java/io/realm/RealmCollection.java b/realm/realm-library/src/main/java/io/realm/RealmCollection.java index 120103c780..7d6d866549 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCollection.java @@ -17,9 +17,9 @@ package io.realm; import java.util.Collection; +import java.util.Collections; import java.util.Date; -import java.util.Collections; /** * {@code RealmCollection} is the root of the collection hierarchy that Realm supports. It defines operations on data @@ -93,7 +93,7 @@ public interface RealmCollection extends Collection { * Finds the maximum date. * * @param fieldName the field to look for the maximum date. If fieldName is not of Date type, an exception is - * thrown. + * thrown. * @return if no objects exist or they all have {@code null} as the value for the given date field, {@code null} * will be returned. Otherwise the maximum date is returned. When determining the maximum date, objects with * {@code null} values are ignored. @@ -106,7 +106,7 @@ public interface RealmCollection extends Collection { * Finds the minimum date. * * @param fieldName the field to look for the minimum date. If fieldName is not of Date type, an exception is - * thrown. + * thrown. * @return if no objects exist or they all have {@code null} as the value for the given date field, {@code null} * will be returned. Otherwise the minimum date is returned. When determining the minimum date, objects with * {@code null} values are ignored. @@ -118,8 +118,8 @@ public interface RealmCollection extends Collection { /** * This deletes all objects in the collection from the underlying Realm as well as from the collection. * - * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. * @return {@code true} if objects was deleted, {@code false} otherwise. + * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. * @throws java.lang.IllegalStateException if the Realm has been closed or called from an incorrect thread. */ boolean deleteAllFromRealm(); @@ -152,7 +152,7 @@ public interface RealmCollection extends Collection { * latest data. Managed collections are thread confined so that they cannot be accessed from other threads than the * one that created them. *

                    - * + *

                    * If this method returns {@code false}, the collection is unmanaged. An unmanaged collection is just a normal java * collection, so it will not be live updated. *

                    @@ -170,7 +170,7 @@ public interface RealmCollection extends Collection { * @param object the object to search for. * @return {@code true} if object is an element of this {@code Collection}, {@code false} otherwise. * @throws NullPointerException if the object to look for is {@code null} and this {@code Collection} doesn't - * support {@code null} elements. + * support {@code null} elements. */ @Override boolean contains(Object object); diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index cb6bddc599..29ef417eb7 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -40,6 +40,7 @@ import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; + /** * A RealmConfiguration is used to setup a specific Realm instance. *

                    @@ -100,17 +101,17 @@ public class RealmConfiguration { // We need to enumerate all parameters since SyncConfiguration and RealmConfiguration supports different // subsets of them. protected RealmConfiguration(File realmDirectory, - String realmFileName, - String canonicalPath, - String assetFilePath, - byte[] key, - long schemaVersion, - RealmMigration migration, - boolean deleteRealmIfMigrationNeeded, - SharedRealm.Durability durability, - RealmProxyMediator schemaMediator, - RxObservableFactory rxObservableFactory, - Realm.Transaction initialDataTransaction) { + String realmFileName, + String canonicalPath, + String assetFilePath, + byte[] key, + long schemaVersion, + RealmMigration migration, + boolean deleteRealmIfMigrationNeeded, + SharedRealm.Durability durability, + RealmProxyMediator schemaMediator, + RxObservableFactory rxObservableFactory, + Realm.Transaction initialDataTransaction) { this.realmDirectory = realmDirectory; this.realmFileName = realmFileName; this.canonicalPath = canonicalPath; @@ -206,8 +207,8 @@ public String getPath() { /** * Returns the {@link RxObservableFactory} that is used to create Rx Observables from Realm objects. * - * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath. * @return the factory instance used to create Rx Observables. + * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath. */ public RxObservableFactory getRxFactory() { // Since RxJava doesn't exist, rxObservableFactory is not initialized. @@ -221,35 +222,38 @@ public RxObservableFactory getRxFactory() { @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; + if (this == obj) { return true; } + if (obj == null || getClass() != obj.getClass()) { return false; } RealmConfiguration that = (RealmConfiguration) obj; - if (schemaVersion != that.schemaVersion) return false; - if (deleteRealmIfMigrationNeeded != that.deleteRealmIfMigrationNeeded) return false; - if (!realmDirectory.equals(that.realmDirectory)) return false; - if (!realmFileName.equals(that.realmFileName)) return false; - if (!canonicalPath.equals(that.canonicalPath)) return false; - if (!Arrays.equals(key, that.key)) return false; - if (!durability.equals(that.durability)) return false; - if (migration != null ? !migration.equals(that.migration) : that.migration != null) return false; + if (schemaVersion != that.schemaVersion) { return false; } + if (deleteRealmIfMigrationNeeded != that.deleteRealmIfMigrationNeeded) { return false; } + if (!realmDirectory.equals(that.realmDirectory)) { return false; } + if (!realmFileName.equals(that.realmFileName)) { return false; } + if (!canonicalPath.equals(that.canonicalPath)) { return false; } + if (!Arrays.equals(key, that.key)) { return false; } + if (!durability.equals(that.durability)) { return false; } + if (migration != null ? !migration.equals(that.migration) : that.migration != null) { return false; } //noinspection SimplifiableIfStatement - if (rxObservableFactory != null ? !rxObservableFactory.equals(that.rxObservableFactory) : that.rxObservableFactory != null) return false; - if (initialDataTransaction != null ? !initialDataTransaction.equals(that.initialDataTransaction) : that.initialDataTransaction != null) return false; + if (rxObservableFactory != null ? !rxObservableFactory.equals(that.rxObservableFactory) : that.rxObservableFactory != null) { + return false; + } + if (initialDataTransaction != null ? !initialDataTransaction.equals(that.initialDataTransaction) : that.initialDataTransaction != null) { + return false; + } return schemaMediator.equals(that.schemaMediator); } - @Override public int hashCode() { int result = realmDirectory.hashCode(); result = 31 * result + realmFileName.hashCode(); result = 31 * result + canonicalPath.hashCode(); result = 31 * result + (key != null ? Arrays.hashCode(key) : 0); - result = 31 * result + (int)schemaVersion; + result = 31 * result + (int) schemaVersion; result = 31 * result + (migration != null ? migration.hashCode() : 0); result = 31 * result + (deleteRealmIfMigrationNeeded ? 1 : 0); result = 31 * result + schemaMediator.hashCode(); @@ -262,7 +266,7 @@ public int hashCode() { // Creates the mediator that defines the current schema. protected static RealmProxyMediator createSchemaMediator(Set modules, - Set> debugSchema) { + Set> debugSchema) { // If using debug schema, uses special mediator. if (debugSchema.size() > 0) { @@ -502,10 +506,10 @@ public Builder migration(RealmMigration migration) { * Setting this will change the behavior of how migration exceptions are handled. Instead of throwing a * {@link io.realm.exceptions.RealmMigrationNeededException} the on-disc Realm will be cleared and recreated * with the new Realm schema. - * + *

                    *

                    This cannot be configured to have an asset file at the same time by calling * {@link #assetFile(String)} as the provided asset file will be deleted in migrations. - * + *

                    *

                    WARNING! This will result in loss of data. * * @throws IllegalStateException if configured to use an asset file by calling {@link #assetFile(String)} previously. @@ -547,6 +551,7 @@ public Builder inMemory() { *

                    * {@code builder.modules(Realm.getDefaultMode(), new MyLibraryModule()); } *

                    + * * @param baseModule the first Realm module (required). * @param additionalModules the additional Realm modules * @throws IllegalArgumentException if any of the modules doesn't have the {@link RealmModule} annotation. @@ -591,10 +596,10 @@ public Builder initialData(Realm.Transaction transaction) { *

                    * When opening the Realm for the first time, instead of creating an empty file, * the Realm file will be copied from the provided asset file and used instead. - * + *

                    *

                    This cannot be configured to clear and recreate schema by calling {@link #deleteRealmIfMigrationNeeded()} * at the same time as doing so will delete the copied asset schema. - * + *

                    *

                    * WARNING: This could potentially be a lengthy operation and should ideally be done on a background thread. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java index 7adb5b9ae4..b66639dc4c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java +++ b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java @@ -20,6 +20,7 @@ import io.realm.internal.Keep; + /** * List of the types used by Realm's underlying storage engine. *

                    @@ -46,6 +47,7 @@ public enum RealmFieldType { // Primitive array for fast mapping between between native values and their Realm type. private static RealmFieldType[] typeList = new RealmFieldType[15]; + static { RealmFieldType[] columnTypes = values(); for (int i = 0; i < columnTypes.length; i++) { @@ -77,19 +79,32 @@ public int getNativeValue() { */ public boolean isValid(Object obj) { switch (nativeValue) { - case 0: return (obj instanceof Long || obj instanceof Integer || obj instanceof Short || obj instanceof Byte); - case 1: return (obj instanceof Boolean); - case 2: return (obj instanceof String); - case 4: return (obj instanceof byte[] || obj instanceof ByteBuffer); - case 5: return (obj == null || obj instanceof Object[][]); - case 7: return (obj instanceof java.util.Date); // The unused DateTime. - case 8: return (obj instanceof java.util.Date); - case 9: return (obj instanceof Float); - case 10: return (obj instanceof Double); - case 12: return false; - case 13: return false; - case 14: return false; - default: throw new RuntimeException("Unsupported Realm type: " + this); + case 0: + return (obj instanceof Long || obj instanceof Integer || obj instanceof Short || obj instanceof Byte); + case 1: + return (obj instanceof Boolean); + case 2: + return (obj instanceof String); + case 4: + return (obj instanceof byte[] || obj instanceof ByteBuffer); + case 5: + return (obj == null || obj instanceof Object[][]); + case 7: + return (obj instanceof java.util.Date); // The unused DateTime. + case 8: + return (obj instanceof java.util.Date); + case 9: + return (obj instanceof Float); + case 10: + return (obj instanceof Double); + case 12: + return false; + case 13: + return false; + case 14: + return false; + default: + throw new RuntimeException("Unsupported Realm type: " + this); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 04b06fb12f..891f052eff 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -32,6 +32,7 @@ import io.realm.internal.RealmObjectProxy; import rx.Observable; + /** * RealmList is used to model one-to-many relationships in a {@link io.realm.RealmObject}. * RealmList has two modes: A managed and unmanaged mode. In managed mode all objects are persisted inside a Realm, in @@ -99,7 +100,7 @@ public RealmList(E... objects) { * Creates a RealmList from a LinkView, so its elements are managed by Realm. * * @param clazz type of elements in the Array. - * @param linkView backing LinkView. + * @param linkView backing LinkView. * @param realm reference to Realm containing the data. */ RealmList(Class clazz, LinkView linkView, BaseRealm realm) { @@ -151,7 +152,7 @@ private boolean isAttached() { *

                  • Unmanaged RealmLists: It is possible to add both managed and unmanaged objects. If adding managed * objects to an unmanaged RealmList they will not be copied to the Realm again if using * {@link Realm#copyToRealm(RealmModel)} afterwards.
                  • - * + *

                    *

                  • Managed RealmLists: It is possible to add unmanaged objects to a RealmList that is already managed. In * that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} * or {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key.
                  • @@ -184,7 +185,7 @@ public void add(int location, E object) { *
                  • Unmanaged RealmLists: It is possible to add both managed and unmanaged objects. If adding managed * objects to an unmanaged RealmList they will not be copied to the Realm again if using * {@link Realm#copyToRealm(RealmModel)} afterwards.
                  • - * + *

                    *

                  • Managed RealmLists: It is possible to add unmanaged objects to a RealmList that is already managed. In * that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} * or {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key.
                  • @@ -214,7 +215,7 @@ public boolean add(E object) { *
                  • Unmanaged RealmLists: It is possible to add both managed and unmanaged objects. If adding managed * objects to an unmanaged RealmList they will not be copied to the Realm again if using * {@link Realm#copyToRealm(RealmModel)} afterwards.
                  • - * + *

                    *

                  • Managed RealmLists: It is possible to add unmanaged objects to a RealmList that is already managed. * In that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} or * {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key.
                  • @@ -293,7 +294,7 @@ private E copyToRealmIfNeeded(E object) { * * @param oldPos index of RealmObject to move. * @param newPos target position. If newPos < oldPos the object at the location will be shifted to the right. If - * oldPos < newPos, indexes > oldPos will be shifted once to the left. + * oldPos < newPos, indexes > oldPos will be shifted once to the left. * @throws IllegalStateException if Realm instance has been closed or parent object has been removed. * @throws java.lang.IndexOutOfBoundsException if any position is outside [0, size()]. */ @@ -369,7 +370,7 @@ public E remove(int location) { * @param object the object to remove. * @return {@code true} if this {@code Collection} is modified, {@code false} otherwise. * @throws ClassCastException if the object passed is not of the correct type. - * @throws NullPointerException if {@code object} is {@code null}. + * @throws NullPointerException if {@code object} is {@code null}. */ @Override public boolean remove(Object object) { @@ -467,8 +468,8 @@ public E first() { } /** - * {@inheritDoc} - */ + * {@inheritDoc} + */ @Override public E first(E defaultValue) { return firstImpl(false, defaultValue); @@ -483,7 +484,7 @@ private E firstImpl(boolean shouldThrow, E defaultValue) { } else if (unmanagedList != null && !unmanagedList.isEmpty()) { return unmanagedList.get(0); } - + if (shouldThrow) { throw new IndexOutOfBoundsException("The list is empty."); } else { @@ -549,7 +550,7 @@ public RealmResults sort(String fieldName, Sort sortOrder) { */ @Override public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { - return sort(new String[]{fieldName1, fieldName2}, new Sort[]{sortOrder1, sortOrder2}); + return sort(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); } /** @@ -853,18 +854,18 @@ public String toString() { * Returns an Rx Observable that monitors changes to this RealmList. It will emit the current RealmList when * subscribed to. RealmList will continually be emitted as the RealmList is updated - * {@code onComplete} will never be called. - * + *

                    * If you would like the {@code asObservable()} to stop emitting items you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: - * - *

                    +     * 

                    + *

                          * {@code
                          * list.asObservable()
                          *      .first()
                          *      .subscribe( ... ) // You only get the results once
                          * }
                          * 
                    - * + *

                    *

                    Note that when the {@link Realm} is accessed from threads other than where it was created, * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers * with {@code subscribeOn()} and {@code observeOn()}. @@ -1006,7 +1007,7 @@ public E next() { return next; } catch (IndexOutOfBoundsException e) { checkConcurrentModification(); - throw new NoSuchElementException("Cannot access index " + i + " when size is " + size() + ". Remember to check hasNext() before using next()."); + throw new NoSuchElementException("Cannot access index " + i + " when size is " + size() + ". Remember to check hasNext() before using next()."); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmModel.java b/realm/realm-library/src/main/java/io/realm/RealmModel.java index 0c4c8a1046..fa54c45957 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmModel.java +++ b/realm/realm-library/src/main/java/io/realm/RealmModel.java @@ -23,7 +23,7 @@ * Interface for marking classes as RealmObjects, it can be used instead of extending {@link RealmObject}. *

                    * All helper methods available to classes that extend RealmObject are instead available as static methods: - * + *

                    *

                      * {@code
                      *   Person p = realm.createObject(Person.class);
                    @@ -35,7 +35,7 @@
                      *   p.isValid();
                      * }
                      * 
                    - * + *

                    * Note: Object implementing this interface needs also to be annotated with {@link RealmClass}, so the annotation * processor can generate the underlining proxy class. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 7b942c51ee..e76a0e2746 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -24,6 +24,7 @@ import io.realm.internal.Row; import rx.Observable; + /** * In Realm you define your RealmObject classes by sub-classing RealmObject and adding fields to be persisted. You then * create your objects within a Realm, and use your custom subclasses instead of using the RealmObject class directly. @@ -32,17 +33,17 @@ *

                    * The following field data types are supported: *

                      - *
                    • boolean/Boolean
                    • - *
                    • short/Short
                    • - *
                    • int/Integer
                    • - *
                    • long/Long
                    • - *
                    • float/Float
                    • - *
                    • double/Double
                    • - *
                    • byte[]
                    • - *
                    • String
                    • - *
                    • Date
                    • - *
                    • Any RealmObject subclass
                    • - *
                    • RealmList
                    • + *
                    • boolean/Boolean
                    • + *
                    • short/Short
                    • + *
                    • int/Integer
                    • + *
                    • long/Long
                    • + *
                    • float/Float
                    • + *
                    • double/Double
                    • + *
                    • byte[]
                    • + *
                    • String
                    • + *
                    • Date
                    • + *
                    • Any RealmObject subclass
                    • + *
                    • RealmList
                    • *
                    *

                    * The types short, int, and long are mapped to long when storing @@ -150,18 +151,18 @@ public static boolean isValid(E object) { /** * Checks if the query used to find this RealmObject has completed. - * + *

                    * Async methods like {@link RealmQuery#findFirstAsync()} return an {@link RealmObject} that represents the future * result of the {@link RealmQuery}. It can be considered similar to a {@link java.util.concurrent.Future} in this * regard. - * + *

                    * Once {@code isLoaded()} returns {@code true}, the object represents the query result even if the query * didn't find any object matching the query parameters. In this case the {@link RealmObject} will * become a "null" object. - * + *

                    * "Null" objects represents {@code null}. An exception is throw if any accessor is called, so it is important to * also check {@link #isValid()} before calling any methods. A common pattern is: - * + *

                    *

                          * {@code
                          * Person person = realm.where(Person.class).findFirstAsync();
                    @@ -177,13 +178,12 @@ public static  boolean isValid(E object) {
                          * });
                          * }
                          * 
                    - * + *

                    * Synchronous RealmObjects are by definition blocking hence this method will always return {@code true} for them. * This method will return {@code true} if called on an unmanaged object (created outside of Realm). * * @return {@code true} if the query has completed, {@code false} if the query is in * progress. - * * @see #isValid() */ public final boolean isLoaded() { @@ -193,17 +193,17 @@ public final boolean isLoaded() { /** * Checks if the query used to find this RealmObject has completed. - * + *

                    * Async methods like {@link RealmQuery#findFirstAsync()} return an {@link RealmObject} that represents the future result * of the {@link RealmQuery}. It can be considered similar to a {@link java.util.concurrent.Future} in this regard. - * + *

                    * Once {@code isLoaded()} returns {@code true}, the object represents the query result even if the query * didn't find any object matching the query parameters. In this case the {@link RealmObject} will * become a "null" object. - * + *

                    * "Null" objects represents {@code null}. An exception is throw if any accessor is called, so it is important to also * check {@link #isValid()} before calling any methods. A common pattern is: - * + *

                    *

                          * {@code
                          * Person person = realm.where(Person.class).findFirstAsync();
                    @@ -219,14 +219,13 @@ public final boolean isLoaded() {
                          * });
                          * }
                          * 
                    - * + *

                    * Synchronous RealmObjects are by definition blocking hence this method will always return {@code true} for them. * This method will return {@code true} if called on an unmanaged object (created outside of Realm). * * @param object RealmObject to check. * @return {@code true} if the query has completed, {@code false} if the query is in * progress. - * * @see #isValid(RealmModel) */ public static boolean isLoaded(E object) { @@ -245,12 +244,12 @@ public static boolean isLoaded(E object) { * when changes happen. Managed objects are thread confined so that they cannot be accessed from other threads than * the one that created them. *

                    - * + *

                    * If this method returns {@code false}, the object is unmanaged. An unmanaged object is just a normal Java object, * so it can be parsed freely across threads, but the data in the object is not connected to the underlying Realm, * so it will not be live updated. *

                    - * + *

                    * It is possible to create a managed object from an unmanaged object by using * {@link Realm#copyToRealm(RealmModel)}. An unmanaged object can be created from a managed object by using * {@link Realm#copyFromRealm(RealmModel)}. @@ -268,12 +267,12 @@ public boolean isManaged() { * notified when changes happen. Managed objects are thread confined so that they cannot be accessed from other threads * than the one that created them. *

                    - * + *

                    * If this method returns {@code false}, the object is unmanaged. An unmanaged object is just a normal Java object, * so it can be parsed freely across threads, but the data in the object is not connected to the underlying Realm, * so it will not be live updated. *

                    - * + *

                    * It is possible to create a managed object from an unmanaged object by using * {@link Realm#copyToRealm(RealmModel)}. An unmanaged object can be created from a managed object by using * {@link Realm#copyFromRealm(RealmModel)}. @@ -450,7 +449,7 @@ public static void removeAllChangeListeners(E object) { *

                    * If you would like the {@code asObservable()} to stop emitting items you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: - * + *

                    *

                          * {@code
                          * obj.asObservable()
                    @@ -459,7 +458,7 @@ public static  void removeAllChangeListeners(E object) {
                          *      .subscribe( ... ) // You only get the object once
                          * }
                          * 
                    - * + *

                    *

                    * Note that when the {@link Realm} is accessed from threads other than where it was created, * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers @@ -487,7 +486,7 @@ public final Observable asObservable() { *

                    * If you would like the {@code asObservable()} to stop emitting items you can instruct RxJava to * emit only the first item by using the {@code first()} operator: - * + *

                    *

                          * {@code
                          * obj.asObservable()
                    diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java
                    index 03266e7308..fd71b473d3 100644
                    --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java
                    +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java
                    @@ -27,6 +27,7 @@
                     import io.realm.annotations.Required;
                     import io.realm.internal.Table;
                     
                    +
                     /**
                      * Class for interacting with the schema for a given RealmObject class. This makes it possible to
                      * add, delete or change the fields for given class.
                    @@ -36,6 +37,7 @@
                     public class RealmObjectSchema {
                     
                         private static final Map, FieldMetaData> SUPPORTED_SIMPLE_FIELDS;
                    +
                         static {
                             SUPPORTED_SIMPLE_FIELDS = new HashMap, FieldMetaData>();
                             SUPPORTED_SIMPLE_FIELDS.put(String.class, new FieldMetaData(RealmFieldType.STRING, true));
                    @@ -58,6 +60,7 @@ public class RealmObjectSchema {
                         }
                     
                         private static final Map, FieldMetaData> SUPPORTED_LINKED_FIELDS;
                    +
                         static {
                             SUPPORTED_LINKED_FIELDS = new HashMap, FieldMetaData>();
                             SUPPORTED_LINKED_FIELDS.put(RealmObject.class, new FieldMetaData(RealmFieldType.OBJECT, false));
                    @@ -228,7 +231,7 @@ public RealmObjectSchema addField(String fieldName, Class fieldType, FieldAtt
                         /**
                          * Adds a new field that references another {@link RealmObject}.
                          *
                    -     * @param fieldName  name of the field to add.
                    +     * @param fieldName name of the field to add.
                          * @param objectSchema schema for the Realm type being referenced.
                          * @return the updated schema.
                          * @throws IllegalArgumentException if field name is illegal or a field with that name already exists.
                    @@ -243,7 +246,7 @@ public RealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema
                         /**
                          * Adds a new field that references a {@link RealmList}.
                          *
                    -     * @param fieldName  name of the field to add.
                    +     * @param fieldName name of the field to add.
                          * @param objectSchema schema for the Realm type being referenced.
                          * @return the updated schema.
                          * @throws IllegalArgumentException if the field name is illegal or a field with that name already exists.
                    @@ -441,10 +444,10 @@ public RealmObjectSchema removePrimaryKey() {
                          * between boxed types and their primitive variant e.g., {@code Integer} to {@code int}.
                          *
                          * @param fieldName name of field in the class.
                    -     * @param required  {@code true} if field should be required, {@code false} otherwise.
                    +     * @param required {@code true} if field should be required, {@code false} otherwise.
                          * @return the updated schema.
                          * @throws IllegalArgumentException if the field name doesn't exist, cannot have the {@link Required} annotation or
                    -     *                                  the field already have been set as required.
                    +     * the field already have been set as required.
                          * @see Required
                          */
                         public RealmObjectSchema setRequired(String fieldName, boolean required) {
                    @@ -478,7 +481,7 @@ public RealmObjectSchema setRequired(String fieldName, boolean required) {
                          * between primitive types and their boxed variant e.g., {@code int} to {@code Integer}.
                          *
                          * @param fieldName name of field in the class.
                    -     * @param nullable  {@code true} if field should be nullable, {@code false} otherwise.
                    +     * @param nullable {@code true} if field should be nullable, {@code false} otherwise.
                          * @return the updated schema.
                          * @throws IllegalArgumentException if the field name doesn't exist, or cannot be set as nullable.
                          */
                    @@ -858,8 +861,12 @@ public Collection values() {
                         }
                     
                         static native long nativeCreateRealmObjectSchema(String className);
                    +
                         static native void nativeAddProperty(long nativePtr, long nativePropertyPtr);
                    +
                         static native long[] nativeGetProperties(long nativePtr);
                    +
                         static native void nativeClose(long nativePtr);
                    +
                         static native String nativeGetClassName(long nativePtr);
                     }
                    diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java
                    index 04de504455..38056e37c8 100644
                    --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java
                    +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java
                    @@ -30,6 +30,7 @@
                     import io.realm.internal.Table;
                     import io.realm.internal.TableQuery;
                     
                    +
                     /**
                      * A RealmQuery encapsulates a query on a {@link io.realm.Realm} or a {@link io.realm.RealmResults} using the Builder
                      * pattern. The query is executed using either {@link #findAll()} or {@link #findFirst()}.
                    @@ -64,8 +65,8 @@ public class RealmQuery {
                         /**
                          * Creates a query for objects of a given class from a {@link Realm}.
                          *
                    -     * @param realm  the realm to query within.
                    -     * @param clazz  the class to query.
                    +     * @param realm the realm to query within.
                    +     * @param clazz the class to query.
                          * @return {@link RealmQuery} object. After building the query call one of the {@code find*} methods
                          * to run it.
                          */
                    @@ -76,8 +77,8 @@ public static  RealmQuery createQuery(Realm realm, Clas
                         /**
                          * Creates a query for dynamic objects of a given type from a {@link DynamicRealm}.
                          *
                    -     * @param realm  the realm to query within.
                    -     * @param className  the type to query.
                    +     * @param realm the realm to query within.
                    +     * @param className the type to query.
                          * @return {@link RealmQuery} object. After building the query call one of the {@code find*} methods
                          * to run it.
                          */
                    @@ -88,7 +89,7 @@ public static  RealmQuery createDynamicQuery(DynamicRea
                         /**
                          * Creates a query from an existing {@link RealmResults}.
                          *
                    -     * @param queryResults   an existing @{link io.realm.RealmResults} to query against.
                    +     * @param queryResults an existing @{link io.realm.RealmResults} to query against.
                          * @return {@link RealmQuery} object. After building the query call one of the {@code find*} methods
                          * to run it.
                          */
                    @@ -105,7 +106,7 @@ public static  RealmQuery createQueryFromResult(RealmRe
                         /**
                          * Creates a query from an existing {@link RealmList}.
                          *
                    -     * @param list   an existing @{link io.realm.RealmList} to query against.
                    +     * @param list an existing @{link io.realm.RealmList} to query against.
                          * @return {@link RealmQuery} object. After building the query call one of the {@code find*} methods
                          * to run it.
                          */
                    @@ -244,7 +245,7 @@ public RealmQuery equalTo(String fieldName, String value) {
                          *
                          * @param fieldName the field to compare.
                          * @param value the value to compare with.
                    -     * @param casing     how to handle casing. Setting this to {@link Case#INSENSITIVE} only works for Latin-1 characters.
                    +     * @param casing how to handle casing. Setting this to {@link Case#INSENSITIVE} only works for Latin-1 characters.
                          * @return the query object.
                          * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type.
                          */
                    @@ -697,7 +698,7 @@ public RealmQuery notEqualTo(String fieldName, String value) {
                          *
                          * @param fieldName the field to compare.
                          * @param value the value to compare with.
                    -     * @param casing     how casing is handled. {@link Case#INSENSITIVE} works only for the Latin-1 characters.
                    +     * @param casing how casing is handled. {@link Case#INSENSITIVE} works only for the Latin-1 characters.
                          * @return the query object.
                          * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type.
                          */
                    @@ -1315,7 +1316,7 @@ public RealmQuery contains(String fieldName, String value) {
                          *
                          * @param fieldName the field to compare.
                          * @param value the substring.
                    -     * @param casing     how to handle casing. Setting this to {@link Case#INSENSITIVE} only works for Latin-1 characters.
                    +     * @param casing how to handle casing. Setting this to {@link Case#INSENSITIVE} only works for Latin-1 characters.
                          * @return The query object.
                          * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type.
                          */
                    @@ -1344,7 +1345,7 @@ public RealmQuery beginsWith(String fieldName, String value) {
                          *
                          * @param fieldName the field to compare.
                          * @param value the substring.
                    -     * @param casing     how to handle casing. Setting this to {@link Case#INSENSITIVE} only works for Latin-1 characters.
                    +     * @param casing how to handle casing. Setting this to {@link Case#INSENSITIVE} only works for Latin-1 characters.
                          * @return the query object
                          * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type.
                          */
                    @@ -1388,8 +1389,8 @@ public RealmQuery endsWith(String fieldName, String value, Case casing) {
                         /**
                          * Condition that the value of field matches with the specified substring, with wildcards:
                          * 
                      - *
                    • '*' matches [0, n] unicode chars
                    • - *
                    • '?' matches a single unicode char.
                    • + *
                    • '*' matches [0, n] unicode chars
                    • + *
                    • '?' matches a single unicode char.
                    • *
                    * * @param fieldName the field to compare. @@ -1404,8 +1405,8 @@ public RealmQuery like(String fieldName, String value) { /** * Condition that the value of field matches with the specified substring, with wildcards: *
                      - *
                    • '*' matches [0, n] unicode chars
                    • - *
                    • '?' matches a single unicode char.
                    • + *
                    • '*' matches [0, n] unicode chars
                    • + *
                    • '?' matches a single unicode char.
                    • *
                    * * @param fieldName the field to compare. @@ -1582,8 +1583,8 @@ public RealmResults distinct(String firstFieldName, String... remainingFieldN * * @param fieldName the field to sum. Only number fields are supported. * @return the sum of fields of the matching objects. If no objects exist or they all have {@code null} as the value - * for the given field, {@code 0} will be returned. When computing the sum, objects with {@code null} values - * are ignored. + * for the given field, {@code 0} will be returned. When computing the sum, objects with {@code null} values + * are ignored. * @throws java.lang.IllegalArgumentException if the field is not a number type. */ public Number sum(String fieldName) { @@ -1672,7 +1673,7 @@ public Date minimumDate(String fieldName) { * Finds the maximum value of a field. * * @param fieldName the field to look for a maximum on. Only number fields are supported. - * @return if no objects exist or they all have {@code null} as the value for the given field, {@code null} will be + * @return if no objects exist or they all have {@code null} as the value for the given field, {@code null} will be * returned. Otherwise the maximum value is returned. When determining the maximum value, objects with {@code null} * values are ignored. * @throws java.lang.IllegalArgumentException if the field is not a number type. @@ -1776,7 +1777,7 @@ public RealmResults findAllSorted(String fieldName, Sort sortOrder) { * (need a Realm opened from a looper thread to work). * * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. * @throws java.lang.IllegalArgumentException if field name does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ @@ -1791,7 +1792,7 @@ public RealmResults findAllSortedAsync(final String fieldName, final Sort sor /** * Finds all objects that fulfill the query conditions and sorted by specific field name in ascending order. - * + *

                    * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. * @@ -1827,7 +1828,7 @@ public RealmResults findAllSortedAsync(String fieldName) { * @param fieldNames an array of field names to sort by. * @param sortOrders how to sort the field names. * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero - * objects is returned. + * objects is returned. * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ @@ -1849,9 +1850,9 @@ private boolean isDynamicQuery() { * * @return immediately an empty {@link RealmResults}. Users need to register a listener * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @see io.realm.RealmResults * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. + * @see io.realm.RealmResults */ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sortOrders) { realm.checkIfValid(); @@ -1863,7 +1864,7 @@ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sort /** * Finds all objects that fulfill the query conditions and sorted by specific field names in ascending order. - * + *

                    * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. * @@ -1877,8 +1878,8 @@ public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sort * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, - String fieldName2, Sort sortOrder2) { - return findAllSorted(new String[]{fieldName1, fieldName2}, new Sort[]{sortOrder1, sortOrder2}); + String fieldName2, Sort sortOrder2) { + return findAllSorted(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); } /** @@ -1891,8 +1892,8 @@ public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, * {@link RealmObject} or a child {@link RealmList}. */ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, - String fieldName2, Sort sortOrder2) { - return findAllSortedAsync(new String[]{fieldName1, fieldName2}, new Sort[]{sortOrder1, sortOrder2}); + String fieldName2, Sort sortOrder2) { + return findAllSortedAsync(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); } /** @@ -1961,9 +1962,9 @@ public E findFirstAsync() { } private RealmResults createRealmResults(TableQuery query, - SortDescriptor sortDescriptor, - SortDescriptor distinctDescriptor, - boolean loadResults) { + SortDescriptor sortDescriptor, + SortDescriptor distinctDescriptor, + boolean loadResults) { RealmResults results; Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); if (isDynamicQuery()) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index ff36830844..60c8c3b1fa 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -26,6 +26,7 @@ import io.realm.internal.UncheckedRow; import rx.Observable; + /** * This class holds all the matches of a {@link RealmQuery} for a given Realm. The objects are not copied from * the Realm to the RealmResults list, but are just referenced from the RealmResult instead. This saves memory and @@ -61,9 +62,9 @@ static RealmResults createBacklinkResults(BaseRealm re UncheckedRow uncheckedRow = (UncheckedRow) row; Table srcTable = realm.getSchema().getTable(srcTableType); return new RealmResults( - realm, - Collection.createBacklinksCollection(realm.sharedRealm, uncheckedRow, srcTable, srcFieldName), - srcTableType); + realm, + Collection.createBacklinksCollection(realm.sharedRealm, uncheckedRow, srcTable, srcFieldName), + srcTableType); } @@ -90,7 +91,7 @@ public RealmQuery where() { */ @Override public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { - return sort(new String[]{fieldName1, fieldName2}, new Sort[]{sortOrder1, sortOrder2}); + return sort(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); } /** @@ -206,11 +207,11 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener * If you would like the {@code asObservable()} to stop emitting items you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: - * - *

                    +     * 

                    + *

                          * {@code
                          * realm.where(Foo.class).findAllAsync().asObservable()
                          *      .filter(results -> results.isLoaded())
                    @@ -218,7 +219,7 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener
                    -     *
                    +     * 

                    *

                    Note that when the {@link Realm} is accessed from threads other than where it was created, * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 61e067dd23..631ce02530 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -27,6 +27,7 @@ import io.realm.internal.Table; import io.realm.internal.Util; + /** * Class for interacting with the Realm schema using a dynamic API. This makes it possible * to add, delete and change the classes in the Realm. @@ -325,7 +326,7 @@ RealmObjectSchema getSchemaForClass(Class clazz) { } private static boolean isProxyClass(Class modelClass, - Class testee) { + Class testee) { return modelClass != testee; } @@ -349,6 +350,8 @@ static String getSchemaForTable(Table table) { } static native long nativeCreateFromList(long[] objectSchemaPtrs); + static native void nativeClose(long nativePtr); + static native long[] nativeGetAll(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmError.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmError.java index e75cbbad5c..a08e34305a 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmError.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmError.java @@ -18,6 +18,7 @@ import io.realm.internal.Keep; + /** * RealmError is Realm specific Error used when unrecoverable problems happen in the underlying storage engine. An * RealmError should never be caught or ignored. By doing so, the Realm could possibly get corrupted. diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmException.java index 5cd20f9bff..14f946c11c 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmException.java @@ -18,6 +18,7 @@ import io.realm.internal.Keep; + /** * RealmException is Realm specific exceptions. */ diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java index 353dabf128..bad9719003 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java @@ -18,6 +18,7 @@ import io.realm.internal.Keep; import io.realm.internal.SharedRealm; + /** * Class for reporting problems when accessing the Realm related files. */ diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmMigrationNeededException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmMigrationNeededException.java index cfb7e50fb0..aadcdc7bf7 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmMigrationNeededException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmMigrationNeededException.java @@ -20,6 +20,7 @@ import io.realm.internal.Keep; + @Keep public final class RealmMigrationNeededException extends RuntimeException { @@ -37,7 +38,7 @@ public RealmMigrationNeededException(String canonicalRealmPath, String detailMes /** * Returns the canonical path to the Realm file that needs to be migrated. - * + *

                    * This can be used for easy reference during a migration: * * @return Canonical path to the Realm file. diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmPrimaryKeyConstraintException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmPrimaryKeyConstraintException.java index 151bf264be..ef9164e926 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmPrimaryKeyConstraintException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmPrimaryKeyConstraintException.java @@ -18,6 +18,7 @@ import io.realm.internal.Keep; + /** * Class for reporting problems when the primary key constraint is being broken. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index ca458a44a0..0f62923c49 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -18,6 +18,7 @@ import io.realm.RealmFieldType; + /** * Checked wrapper for Row data in Realm Core. All methods called through this will check that input parameters are * valid or throw an appropriate exception. @@ -109,48 +110,70 @@ public void setNull(long columnIndex) { @Override protected native long nativeGetColumnCount(long nativeTablePtr); + @Override protected native String nativeGetColumnName(long nativeTablePtr, long columnIndex); + @Override protected native long nativeGetColumnIndex(long nativeTablePtr, String columnName); + @Override protected native int nativeGetColumnType(long nativeTablePtr, long columnIndex); + @Override protected native long nativeGetLong(long nativeRowPtr, long columnIndex); + @Override protected native boolean nativeGetBoolean(long nativeRowPtr, long columnIndex); + @Override protected native float nativeGetFloat(long nativeRowPtr, long columnIndex); + @Override protected native double nativeGetDouble(long nativeRowPtr, long columnIndex); + @Override protected native long nativeGetTimestamp(long nativeRowPtr, long columnIndex); + @Override protected native String nativeGetString(long nativePtr, long columnIndex); + @Override protected native boolean nativeIsNullLink(long nativeRowPtr, long columnIndex); + @Override protected native byte[] nativeGetByteArray(long nativePtr, long columnIndex); + @Override protected native long nativeGetLinkView(long nativePtr, long columnIndex); + @Override protected native void nativeSetLong(long nativeRowPtr, long columnIndex, long value); + @Override protected native void nativeSetBoolean(long nativeRowPtr, long columnIndex, boolean value); + @Override protected native void nativeSetFloat(long nativeRowPtr, long columnIndex, float value); + @Override protected native long nativeGetLink(long nativeRowPtr, long columnIndex); + @Override protected native void nativeSetDouble(long nativeRowPtr, long columnIndex, double value); + @Override protected native void nativeSetTimestamp(long nativeRowPtr, long columnIndex, long dateTimeValue); + @Override protected native void nativeSetString(long nativeRowPtr, long columnIndex, String value); + @Override protected native void nativeSetByteArray(long nativePtr, long columnIndex, byte[] data); + @Override protected native void nativeSetLink(long nativeRowPtr, long columnIndex, long value); + @Override protected native void nativeNullifyLink(long nativeRowPtr, long columnIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index 07f19d1a15..aeb8231262 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -24,6 +24,7 @@ import io.realm.OrderedRealmCollectionChangeListener; import io.realm.RealmChangeListener; + /** * Java wrapper of Object Store Results class. * It is the backend of binding's query results, link lists and back links. @@ -39,10 +40,10 @@ public CollectionObserverPair(T observer, Object listener) { public void onChange(T observer, OrderedCollectionChangeSet changes) { if (listener instanceof OrderedRealmCollectionChangeListener) { //noinspection unchecked - ((OrderedRealmCollectionChangeListener)listener).onChange(observer, changes); + ((OrderedRealmCollectionChangeListener) listener).onChange(observer, changes); } else if (listener instanceof RealmChangeListener) { //noinspection unchecked - ((RealmChangeListener)listener).onChange(observer); + ((RealmChangeListener) listener).onChange(observer); } else { throw new RuntimeException("Unsupported listener type: " + listener); } @@ -154,7 +155,7 @@ void invalidate() { } void checkValid() { - if (iteratorCollection == null) { + if (iteratorCollection == null) { throw new ConcurrentModificationException( "No outside changes to a Realm is allowed while iterating a living Realm collection."); } @@ -267,6 +268,7 @@ public void set(T object) { public static final byte AGGREGATE_FUNCTION_AVERAGE = 3; @SuppressWarnings("WeakerAccess") public static final byte AGGREGATE_FUNCTION_SUM = 4; + public enum Aggregate { MINIMUM(AGGREGATE_FUNCTION_MINIMUM), MAXIMUM(AGGREGATE_FUNCTION_MAXIMUM), @@ -294,6 +296,7 @@ public byte getValue() { public static final byte MODE_LINKVIEW = 3; @SuppressWarnings("WeakerAccess") public static final byte MODE_TABLEVIEW = 4; + public enum Mode { EMPTY, // Backed by nothing (for missing tables) TABLE, // Backed directly by a Table @@ -302,9 +305,9 @@ public enum Mode { TABLEVIEW; // Backed by a TableView created from a Query static Mode getByValue(byte value) { - switch (value) { + switch (value) { case MODE_EMPTY: - return EMPTY; + return EMPTY; case MODE_TABLE: return TABLE; case MODE_QUERY: @@ -321,15 +324,15 @@ static Mode getByValue(byte value) { public static Collection createBacklinksCollection(SharedRealm realm, UncheckedRow row, Table srcTable, String srcFieldName) { long backlinksPtr = nativeCreateResultsFromBacklinks( - realm.getNativePtr(), - row.getNativePtr(), - srcTable.getNativePtr(), - srcTable.getColumnIndex(srcFieldName)); + realm.getNativePtr(), + row.getNativePtr(), + srcTable.getNativePtr(), + srcTable.getColumnIndex(srcFieldName)); return new Collection(realm, row.getTable(), backlinksPtr, true); } public Collection(SharedRealm sharedRealm, TableQuery query, - SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { + SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor) { query.validateQuery(); this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), @@ -519,7 +522,7 @@ private void notifyChangeListeners(long nativeChangeSetPtr) { // So it is possible it deliver a non-empty change set for the first async query returns. In this case, we // return an empty change set to user since it is considered as the first time async query returns. observerPairs.foreach(new Callback(nativeChangeSetPtr == 0 || !wasLoaded ? - null : new CollectionChangeSet(nativeChangeSetPtr))); + null : new CollectionChangeSet(nativeChangeSetPtr))); } public Mode getMode() { @@ -549,30 +552,53 @@ public void load() { } private static native long nativeGetFinalizerPtr(); + private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, - SortDescriptor sortDesc, SortDescriptor distinctDesc); + SortDescriptor sortDesc, SortDescriptor distinctDesc); + private static native long nativeCreateResultsFromLinkView(long sharedRealmNativePtr, long linkViewPtr, - SortDescriptor sortDesc); + SortDescriptor sortDesc); + private static native long nativeCreateSnapshot(long nativePtr); + private static native long nativeGetRow(long nativePtr, int index); + private static native long nativeFirstRow(long nativePtr); + private static native long nativeLastRow(long nativePtr); + private static native boolean nativeContains(long nativePtr, long nativeRowPtr); + private static native void nativeClear(long nativePtr); + private static native long nativeSize(long nativePtr); + private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); + private static native long nativeSort(long nativePtr, SortDescriptor sortDesc); + private static native long nativeDistinct(long nativePtr, SortDescriptor distinctDesc); + private static native boolean nativeDeleteFirst(long nativePtr); + private static native boolean nativeDeleteLast(long nativePtr); + private static native void nativeDelete(long nativePtr, long index); + // Non-static, we need this Collection object in JNI. private native void nativeStartListening(long nativePtr); + private native void nativeStopListening(long nativePtr); + private static native long nativeWhere(long nativePtr); + private static native long nativeIndexOf(long nativePtr, long rowNativePtr); + private static native long nativeIndexOfBySourceRowIndex(long nativePtr, long sourceRowIndex); + private static native boolean nativeIsValid(long nativePtr); + private static native byte nativeGetMode(long nativePtr); + private static native long nativeCreateResultsFromBacklinks(long sharedRealmNativePtr, long rowNativePtr, long srcTableNativePtr, long srColIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java index 96804a1121..c4958e92e9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java @@ -18,6 +18,7 @@ import io.realm.OrderedCollectionChangeSet; + /** * Implementation of {@link OrderedCollectionChangeSet}. This class holds a pointer to the Object Store's * CollectionChangeSet and read from it only when needed. Creating an Java object from JNI when the collection @@ -66,7 +67,7 @@ public int[] getInsertions() { * {@inheritDoc} */ @Override - public int[] getChanges() { + public int[] getChanges() { return nativeGetIndices(nativePtr, TYPE_MODIFICATION); } @@ -122,8 +123,10 @@ private Range[] longArrayToRangeArray(int[] longArray) { } private native static long nativeGetFinalizerPtr(); + // Returns the ranges as an long array. eg.: [startIndex1, length1, startIndex2, length2, ...] private native static int[] nativeGetRanges(long nativePtr, int type); + // Returns the indices array. private native static int[] nativeGetIndices(long nativePtr, int type); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java index 8f8dd28694..069e5b0004 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java @@ -21,6 +21,7 @@ import io.realm.RealmModel; + /** * Utility class used to cache the mapping between object field names and their column indices. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java index 87ee853674..fe39118f01 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java @@ -20,11 +20,12 @@ import io.realm.exceptions.RealmMigrationNeededException; + public abstract class ColumnInfo implements Cloneable { private Map indicesMap; protected final long getValidColumnIndex(String realmPath, Table table, - String className, String columnName) { + String className, String columnName) { final long columnIndex = table.getColumnIndex(columnName); if (columnIndex == -1) { throw new RealmMigrationNeededException(realmPath, @@ -51,7 +52,7 @@ protected final void setIndicesMap(Map indicesMap) { * Copies the column index value from other {@link ColumnInfo} object. * * @param other the class of {@code other} must be exactly the same as this instance. - * It must not be {@code null}. + * It must not be {@code null}. * @throws IllegalArgumentException if {@code other} has different class than this. */ public abstract void copyColumnInfoFrom(ColumnInfo other); @@ -68,5 +69,7 @@ public ColumnInfo clone() { } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } - }; + } + + ; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Context.java b/realm/realm-library/src/main/java/io/realm/internal/Context.java index cb9bb6ece6..253396b037 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Context.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Context.java @@ -18,6 +18,7 @@ import java.lang.ref.ReferenceQueue; + // Currently we free native objects in two threads, the SharedGroup is freed in the caller thread, others are freed in // RealmFinalizingDaemon thread. And the destruction in both threads are locked by the corresponding context. // The purpose of locking on Context is: diff --git a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java index e910135e5a..13d815aa42 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java @@ -55,7 +55,7 @@ public FieldDescriptor(Table table, String fieldDescription, boolean allowLink, throw new IllegalArgumentException( String.format("'RealmList' field '%s' is not a supported link field here.", names[i])); } else if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { - table = table.getLinkTarget(index); + table = table.getLinkTarget(index); columnIndices[i] = index; } else { throw new IllegalArgumentException( diff --git a/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java b/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java index c712fab530..857774688d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java +++ b/realm/realm-library/src/main/java/io/realm/internal/FinalizerRunnable.java @@ -21,9 +21,10 @@ import io.realm.log.RealmLog; + // Running in the FinalizingDaemon thread to free native objects. class FinalizerRunnable implements Runnable { - private final ReferenceQueue referenceQueue; + private final ReferenceQueue referenceQueue; FinalizerRunnable(ReferenceQueue referenceQueue) { this.referenceQueue = referenceQueue; diff --git a/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java b/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java index 127d971012..7d46a32524 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java @@ -17,14 +17,15 @@ import java.util.IdentityHashMap; + /** * Identity based Set, that guarantees store & retrieve in O(1) * without a huge overhead in space complexity. */ -public class IdentitySet extends IdentityHashMap { +public class IdentitySet extends IdentityHashMap { private final static Integer PLACE_HOLDER = 0; - public void add(K key) { + public void add(K key) { put(key, PLACE_HOLDER); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index eab9406359..734e7a4261 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -20,6 +20,7 @@ import io.realm.RealmFieldType; + /** * Row wrapper that stubs all access with IllegalStateExceptions except for isAttached. This can be used instead of * adding null checks everywhere when the underlying Row accessor in Realm's underlying storage engine is no longer diff --git a/realm/realm-library/src/main/java/io/realm/internal/Keep.java b/realm/realm-library/src/main/java/io/realm/internal/Keep.java index 598ebccc48..57ddfcf83d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Keep.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Keep.java @@ -21,6 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; + /** * This annotation is used to mark the classes to be kept by ProGuard/DexGuard. * The ProGuard configuration must have '-keep class io.realm.internal.Keep' diff --git a/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java b/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java index e1308a9f21..eb36b05abc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java +++ b/realm/realm-library/src/main/java/io/realm/internal/KeepMember.java @@ -21,6 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; + /** * This annotation is used to mark the fields and methods to be kept by ProGuard/DexGuard. * The ProGuard configuration must have '-keep class io.realm.internal.KeepMember' diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index 8079324ff4..8eb61a3de3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -18,6 +18,7 @@ import io.realm.RealmFieldType; + /** * The LinkView class represents a core {@link RealmFieldType#LIST}. */ @@ -64,7 +65,7 @@ public UncheckedRow getUncheckedRow(long index) { /** * Returns a wrapper for {@link Row} access. All access will be error checked at the JNI layer and will throw an * appropriate {@link RuntimeException} if used incorrectly. - * + *

                    * If error checking is done elsewhere, consider using {@link #getUncheckedRow(long)} for better performance. * * @param index the index of row to fetch. @@ -169,20 +170,36 @@ private void checkImmutable() { } native long nativeGetRow(long nativeLinkViewPtr, long pos); + private native long nativeGetTargetRowIndex(long nativeLinkViewPtr, long linkViewIndex); + public static native void nativeAdd(long nativeLinkViewPtr, long rowIndex); + private native void nativeInsert(long nativeLinkViewPtr, long pos, long rowIndex); + private native void nativeSet(long nativeLinkViewPtr, long pos, long rowIndex); + private native void nativeMove(long nativeLinkViewPtr, long oldPos, long newPos); + private native void nativeRemove(long nativeLinkViewPtr, long pos); + public static native void nativeClear(long nativeLinkViewPtr); + private native long nativeSize(long nativeLinkViewPtr); + private native boolean nativeIsEmpty(long nativeLinkViewPtr); + protected native long nativeWhere(long nativeLinkViewPtr); + private native boolean nativeIsAttached(long nativeLinkViewPtr); + private native long nativeFind(long nativeLinkViewPtr, long targetRowIndex); + private native void nativeRemoveTargetRow(long nativeLinkViewPtr, long rowIndex); + private native void nativeRemoveAllTargetRows(long nativeLinkViewPtr); + private native long nativeGetTargetTable(long nativeLinkViewPtr); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java b/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java index e07a4275e3..dce71e3fdf 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java +++ b/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java @@ -19,6 +19,7 @@ import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; + /** * This class is used for holding the reference to the native pointers present in NativeObjects. * This is required as phantom references cannot access the original objects for this value. @@ -69,8 +70,8 @@ synchronized void remove(NativeObjectReference ref) { private static ReferencePool referencePool = new ReferencePool(); NativeObjectReference(Context context, - NativeObject referent, - ReferenceQueue referenceQueue) { + NativeObject referent, + ReferenceQueue referenceQueue) { super(referent, referenceQueue); this.nativePtr = referent.getNativePtr(); this.nativeFinalizerPtr = referent.getNativeFinalizerPtr(); @@ -90,7 +91,7 @@ void cleanup() { } /** - * Calls the native finalizer function to free the given native pointer. + * Calls the native finalizer function to free the given native pointer. */ private static native void nativeCleanUp(long nativeFinalizer, long nativePointer); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index d21f2e971f..0ce999707a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -23,6 +23,7 @@ import io.realm.RealmConfiguration; import io.realm.exceptions.RealmException; + /** * Class acting as an mediator between the basic Realm APIs and the Object Server APIs. * This breaks the cyclic dependency between ObjectServer and Realm code. @@ -52,6 +53,7 @@ public class ObjectServerFacade { /** * Initializes the Object Server library + * * @param context */ public void init(Context context) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java index aeb5f8cb0b..7e726a0df3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; + /** * An ObserverPairList holds a list of ObserverPairs. An {@link ObserverPair} is pair containing an observer and a * listener. The observer is the object to react to the changes through the listener. The observer is saved as a weak diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 8370b03aa2..ece3a697ed 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -6,6 +6,7 @@ import io.realm.RealmChangeListener; import io.realm.RealmFieldType; + /** * A PendingRow is a row relies on a pending async query. * Before the query returns, calling any accessors will immediately throw. In this case run {@link #executeQuery()} to @@ -34,7 +35,7 @@ public interface FrontEnd { private boolean returnCheckedRow; public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, - final boolean returnCheckedRow) { + final boolean returnCheckedRow) { pendingCollection = new Collection(sharedRealm, query, sortDescriptor, null); listener = new RealmChangeListener() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java index e4b6760ecc..f2e3eb300d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java @@ -26,6 +26,7 @@ import io.realm.BuildConfig; + /** * Utility methods for Realm Core. */ @@ -48,7 +49,7 @@ public static boolean osIsWindows() { * can be damaged or missing. This happens for the Android installer, especially when apps are installed * through other means than the official Play store. In this case, the .so file can be found in the .apk. * In other to access the .apk, an {@link android.content.Context} must be provided. - * + *

                    * Although loadLibrary is synchronized internally from AOSP 4.3, for compatibility reasons, * KEEP synchronized here for old devices! */ @@ -65,8 +66,7 @@ private static String loadLibraryWindows() { try { addNativeLibraryPath(BINARIES_PATH); resetLibraryPath(); - } - catch (Throwable e) { + } catch (Throwable e) { // Above can't be used on Android. } //*/ @@ -80,7 +80,7 @@ private static String loadLibraryWindows() { if (jnilib == null) { System.err.println("Searched java.library.path=" + System.getProperty("java.library.path")); throw new RuntimeException("Couldn't load the Realm JNI library 'realm_jni32.dll or realm_jni64.dll" + - "'. Please include the directory to the library in java.library.path."); + "'. Please include the directory to the library in java.library.path."); } } return jnilib; diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 74726c5a05..3493dd4707 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -22,6 +22,7 @@ import io.realm.RealmChangeListener; + /** * This interface needs to be implemented by Java and pass to Realm Object Store in order to get notifications when * other thread/process changes the Realm file. @@ -92,7 +93,8 @@ protected RealmNotifier(SharedRealm sharedRealm) { // - A committed local transaction, called directly from commitTransaction instead of next event. // loop. // Package protected to avoid finding class by name in JNI. - @SuppressWarnings("unused") // called from java_binding_context.cpp + @SuppressWarnings("unused") + // called from java_binding_context.cpp void didChange() { realmObserverPairs.foreach(onChangeCallBack); for (Runnable runnable : transactionCallbacks) { @@ -131,7 +133,7 @@ public void removeChangeListener(E observer, RealmChangeListener realmCha } public void removeChangeListeners(E observer) { - realmObserverPairs.removeByObserver(observer); + realmObserverPairs.removeByObserver(observer); } // Since RealmObject is using this notifier as well, use removeChangeListeners to remove all listeners by the given diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java b/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java index ee7c43f962..e3e5845669 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java @@ -19,14 +19,17 @@ import io.realm.ProxyState; import io.realm.RealmModel; + /** * Interface making it easy to determine if an object is the generated RealmProxy class or the original class. - * + *

                    * Ideally all the static methods was also present here, but that is not supported before Java 8. */ - public interface RealmObjectProxy extends RealmModel { +public interface RealmObjectProxy extends RealmModel { void realm$injectObjectContext(); + ProxyState realmGet$proxyState(); + /** * Tuple class for saving meta data about a cached RealmObject. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index 7d0f38f253..d2c7319f5c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -33,10 +33,11 @@ import io.realm.RealmSchema; import io.realm.exceptions.RealmException; + /** * Superclass for the RealmProxyMediator class. This class contains all static methods introduced by the annotation * processor as part of the RealmProxy classes. - * + *

                    * Classes extending this class act as binders between the static methods inside each RealmProxy and the code at * runtime. We cannot rely on using reflection as the RealmProxies are generated by the annotation processor before * ProGuard is run. So after ProGuard has run there is a mismatch between the name of the RealmProxy and the original @@ -67,12 +68,12 @@ public abstract class RealmProxyMediator { * @param clazz the {@link RealmObject} model class to validate. * @param sharedRealm the wrapper object of underlying native database to validate against. * @param allowExtraColumns if {@code} false, {@link io.realm.exceptions.RealmMigrationNeededException} - * is thrown when the column count it more than expected. + * is thrown when the column count it more than expected. * @return the field indices map. */ public abstract ColumnInfo validateTable(Class clazz, - SharedRealm sharedRealm, - boolean allowExtraColumns); + SharedRealm sharedRealm, + boolean allowExtraColumns); /** * Returns a map of non-obfuscated object field names to their internal Realm name. @@ -98,17 +99,17 @@ public abstract ColumnInfo validateTable(Class clazz, * @param clazz the {@link RealmObject} to create {@link RealmObjectProxy} for. * @param acceptDefaultValue {@code true} to accept the values set in the constructor, {@code false} otherwise. * @param excludeFields the column names whose default value will be ignored if the {@code acceptDefaultValue} - * is {@code true}. Only {@link io.realm.RealmModel} and {@link io.realm.RealmList} - * column will respect this. - * No effects if the {@code acceptDefaultValue} is {@code false}. + * is {@code true}. Only {@link io.realm.RealmModel} and {@link io.realm.RealmList} + * column will respect this. + * No effects if the {@code acceptDefaultValue} is {@code false}. * @return created {@link RealmObjectProxy} object. */ public abstract E newInstance(Class clazz, - Object baseRealm, - Row row, - ColumnInfo columnInfo, - boolean acceptDefaultValue, - List excludeFields); + Object baseRealm, + Row row, + ColumnInfo columnInfo, + boolean acceptDefaultValue, + List excludeFields); /** * Returns the list of RealmObject classes that can be saved in this Realm. @@ -124,7 +125,7 @@ public abstract E newInstance(Class clazz, * @param realm the reference to the {@link Realm} where the object will be copied. * @param object the object to copy properties from. * @param update {@code true} if object has a primary key and should try to update already existing data, - * {@code false} otherwise. + * {@code false} otherwise. * @param cache the cache for mapping between unmanaged objects and their {@link RealmObjectProxy} representation. * @return the managed Realm object. */ @@ -179,7 +180,7 @@ public abstract E newInstance(Class clazz, * @param realm the reference to {@link Realm} where to create the object. * @param json the JSON data * @param update {@code true} if Realm should try to update a existing object. This requires that the RealmObject - * class has a @PrimaryKey. + * class has a @PrimaryKey. * @return RealmObject that has been created or updated. * @throws JSONException if the JSON mapping doesn't match the expected class. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index c4db3d31b5..e3c81dc4ee 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -20,6 +20,7 @@ import io.realm.RealmFieldType; + /** * Interface for Row objects that act as wrappers around the Realm Core Row object. *

                    @@ -27,7 +28,6 @@ * interface always validate their parameters and throw an appropriate exception if invalid. * For example, methods which accept a column name check the existence of the column and throw * {@link IllegalArgumentException} if not found. - * */ public interface Row { diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 194d22ba27..814af27fb8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -28,6 +28,7 @@ import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; + public final class SharedRealm implements Closeable, NativeObject { // Const value for RealmFileException conversion @@ -89,6 +90,7 @@ public enum Durability { public static final byte SCHEMA_MODE_VALUE_ADDITIVE = 3; @SuppressWarnings("WeakerAccess") public static final byte SCHEMA_MODE_VALUE_MANUAL = 4; + @SuppressWarnings("WeakerAccess") public enum SchemaMode { SCHEMA_MODE_AUTOMATIC(SCHEMA_MODE_VALUE_AUTOMATIC), @@ -98,8 +100,9 @@ public enum SchemaMode { SCHEMA_MODE_MANUAL(SCHEMA_MODE_VALUE_MANUAL); final byte value; + SchemaMode(byte value) { - this .value = value; + this.value = value; } public byte getNativeValue() { @@ -178,8 +181,8 @@ public interface SchemaVersionListener { private final SchemaVersionListener schemaChangeListener; private SharedRealm(long nativeConfigPtr, - RealmConfiguration configuration, - SchemaVersionListener schemaVersionListener) { + RealmConfiguration configuration, + SchemaVersionListener schemaVersionListener) { Capabilities capabilities = new AndroidCapabilities(); RealmNotifier realmNotifier = new AndroidRealmNotifier(this, capabilities); @@ -204,7 +207,7 @@ public static SharedRealm getInstance(RealmConfiguration config) { public static SharedRealm getInstance(RealmConfiguration config, SchemaVersionListener schemaVersionListener, - boolean autoChangeNotifications) { + boolean autoChangeNotifications) { String[] syncUserConf = ObjectServerFacade.getSyncFacadeIfPossible().getUserAndServerUrl(config); String syncUserIdentifier = syncUserConf[0]; String syncRealmUrl = syncUserConf[1]; @@ -305,7 +308,7 @@ public void refresh() { } public SharedRealm.VersionID getVersionID() { - long[] versionId = nativeGetVersionID (nativePtr); + long[] versionId = nativeGetVersionID(nativePtr); return new SharedRealm.VersionID(versionId[0], versionId[1]); } @@ -426,42 +429,73 @@ void invalidateIterators() { } private static native void nativeInit(String temporaryDirectoryPath); + // Keep last session as an 'object' to avoid any reference to sync code private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory, - boolean cache, long schemaVersion, boolean disableFormatUpgrade, - boolean autoChangeNotification, - String syncServerURL, - String syncServerAuthURL, - String syncUserIdentity, - String syncRefreshToken); + boolean cache, long schemaVersion, boolean disableFormatUpgrade, + boolean autoChangeNotification, + String syncServerURL, + String syncServerAuthURL, + String syncUserIdentity, + String syncRefreshToken); + private static native void nativeCloseConfig(long nativeConfigPtr); + private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier); + private static native void nativeCloseSharedRealm(long nativeSharedRealmPtr); + private static native boolean nativeIsClosed(long nativeSharedRealmPtr); + private static native void nativeBeginTransaction(long nativeSharedRealmPtr); + private static native void nativeCommitTransaction(long nativeSharedRealmPtr); + private static native void nativeCancelTransaction(long nativeSharedRealmPtr); + private static native boolean nativeIsInTransaction(long nativeSharedRealmPtr); + private static native long nativeGetVersion(long nativeSharedRealmPtr); + private static native long nativeGetSnapshotVersion(long nativeSharedRealmPtr); + private static native void nativeSetVersion(long nativeSharedRealmPtr, long version); + private static native long nativeReadGroup(long nativeSharedRealmPtr); + private static native boolean nativeIsEmpty(long nativeSharedRealmPtr); + private static native void nativeRefresh(long nativeSharedRealmPtr); - private static native long[] nativeGetVersionID(long nativeSharedRealmPtr); + + private static native long[] nativeGetVersionID(long nativeSharedRealmPtr); + private static native long nativeGetTable(long nativeSharedRealmPtr, String tableName); + private static native String nativeGetTableName(long nativeSharedRealmPtr, int index); + private static native boolean nativeHasTable(long nativeSharedRealmPtr, String tableName); + private static native void nativeRenameTable(long nativeSharedRealmPtr, String oldTableName, String newTableName); + private static native void nativeRemoveTable(long nativeSharedRealmPtr, String tableName); + private static native long nativeSize(long nativeSharedRealmPtr); + private static native void nativeWriteCopy(long nativeSharedRealmPtr, String path, byte[] key); + private static native boolean nativeWaitForChange(long nativeSharedRealmPtr); + private static native void nativeStopWaitForChange(long nativeSharedRealmPtr); + private static native boolean nativeCompact(long nativeSharedRealmPtr); + private static native void nativeUpdateSchema(long nativePtr, long nativeSchemaPtr, long version); + private static native void nativeSetAutoRefresh(long nativePtr, boolean enabled); + private static native boolean nativeIsAutoRefresh(long nativePtr); + private static native boolean nativeRequiresMigration(long nativePtr, long nativeSchemaPtr); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java index 3d6b6cfc21..181df8820c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java @@ -22,6 +22,7 @@ import io.realm.RealmFieldType; import io.realm.Sort; + /** * Java class to present the same name core class in Java. This can be converted to a cpp realm::SortDescriptor object * through realm::_impl::JavaSortDescriptor. @@ -143,6 +144,6 @@ boolean[] getAscendings() { @KeepMember @SuppressWarnings("unused") private long getTablePtr() { - return table.getNativePtr(); + return table.getNativePtr(); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 5f29de65fa..707272d4cd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -154,7 +154,7 @@ public long addColumn(RealmFieldType type, String name) { * * @return the index of the new column. */ - public long addColumnLink (RealmFieldType type, String name, Table table) { + public long addColumnLink(RealmFieldType type, String name, Table table) { verifyColumnName(name); return nativeAddColumnLink(nativePtr, type.getNativeValue(), name, table.nativePtr); } @@ -163,7 +163,7 @@ public long addColumnLink (RealmFieldType type, String name, Table table) { * Removes a column in the table dynamically. If {@code columnIndex} is smaller than the primary * key column index, {@link #invalidateCachedPrimaryKeyIndex()} will be called to recalculate the * primary key column index. - * + *

                    *

                    It should be noted if {@code columnIndex} is the same as the primary key column index, * the primary key column is removed from the meta table. * @@ -189,8 +189,8 @@ public void removeColumn(long columnIndex) { if (oldPkColumnIndex == columnIndex) { setPrimaryKey(null); - // But if you remove a column with a smaller index than that of PK column, you need to - // recalculate the PK column index as core could have changed its column index. + // But if you remove a column with a smaller index than that of PK column, you need to + // recalculate the PK column index as core could have changed its column index. } else if (oldPkColumnIndex > columnIndex) { invalidateCachedPrimaryKeyIndex(); } @@ -272,6 +272,7 @@ public void convertColumnToNotNullable(long columnIndex) { // Table Size and deletion. AutoGenerated subclasses are nothing to do with this // class. + /** * Gets the number of entries/rows of this table. * @@ -299,6 +300,7 @@ public void clear() { } // Column Information. + /** * Returns the number of columns in the table. * @@ -346,7 +348,6 @@ public RealmFieldType getColumnType(long columnIndex) { * Removes a row from the specific index. As of now the entry is simply removed from the table. * * @param rowIndex the row index (starting with 0) - * */ public void remove(long rowIndex) { checkImmutable(); @@ -397,7 +398,7 @@ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { * * @param primaryKeyValue the primary key value. * @param validation set to {@code false} to skip all validations. This is currently used by bulk insert which - * has its own validations. + * has its own validations. * @return the row index. */ public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation) { @@ -471,10 +472,10 @@ public long addEmptyRows(long rows) { throw new IllegalArgumentException("'rows' must be > 0."); } if (hasPrimaryKey()) { - if (rows > 1) { - throw new RealmException("Multiple empty rows cannot be created if a primary key is defined for the table."); - } - return addEmptyRow(); + if (rows > 1) { + throw new RealmException("Multiple empty rows cannot be created if a primary key is defined for the table."); + } + return addEmptyRow(); } return nativeAddEmptyRow(nativePtr, rows); } @@ -494,7 +495,7 @@ protected long add(Object... values) { checkImmutable(); // Checks values types. - int columns = (int)getColumnCount(); + int columns = (int) getColumnCount(); if (columns != values.length) { throw new IllegalArgumentException("The number of value parameters (" + String.valueOf(values.length) + @@ -522,51 +523,49 @@ protected long add(Object... values) { // Inserts values. for (long columnIndex = 0; columnIndex < columns; columnIndex++) { - Object value = values[(int)columnIndex]; - switch (colTypes[(int)columnIndex]) { - case BOOLEAN: - nativeSetBoolean(nativePtr, columnIndex, rowIndex, (Boolean)value, false); - break; - case INTEGER: - if (value == null) { - checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); - nativeSetNull(nativePtr, columnIndex, rowIndex, false); - } else { - long intValue = ((Number) value).longValue(); - checkIntValueIsLegal(columnIndex, rowIndex, intValue); - nativeSetLong(nativePtr, columnIndex, rowIndex, intValue, false); - } - break; - case FLOAT: - nativeSetFloat(nativePtr, columnIndex, rowIndex, (Float) value, false); - break; - case DOUBLE: - nativeSetDouble(nativePtr, columnIndex, rowIndex, (Double) value, false); - break; - case STRING: - if (value == null) { - checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); - nativeSetNull(nativePtr, columnIndex, rowIndex, false); - } else { - String stringValue = (String) value; - checkStringValueIsLegal(columnIndex, rowIndex, stringValue); - nativeSetString(nativePtr, columnIndex, rowIndex, (String) value, false); - } - break; - case DATE: - if (value == null) - throw new IllegalArgumentException("Null Date is not allowed."); - nativeSetTimestamp(nativePtr, columnIndex, rowIndex, ((Date) value).getTime(), false); - break; - case BINARY: - if (value == null) - throw new IllegalArgumentException("Null Array is not allowed"); - nativeSetByteArray(nativePtr, columnIndex, rowIndex, (byte[])value, false); - break; - case UNSUPPORTED_MIXED: - case UNSUPPORTED_TABLE: - default: - throw new RuntimeException("Unexpected columnType: " + String.valueOf(colTypes[(int)columnIndex])); + Object value = values[(int) columnIndex]; + switch (colTypes[(int) columnIndex]) { + case BOOLEAN: + nativeSetBoolean(nativePtr, columnIndex, rowIndex, (Boolean) value, false); + break; + case INTEGER: + if (value == null) { + checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); + nativeSetNull(nativePtr, columnIndex, rowIndex, false); + } else { + long intValue = ((Number) value).longValue(); + checkIntValueIsLegal(columnIndex, rowIndex, intValue); + nativeSetLong(nativePtr, columnIndex, rowIndex, intValue, false); + } + break; + case FLOAT: + nativeSetFloat(nativePtr, columnIndex, rowIndex, (Float) value, false); + break; + case DOUBLE: + nativeSetDouble(nativePtr, columnIndex, rowIndex, (Double) value, false); + break; + case STRING: + if (value == null) { + checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); + nativeSetNull(nativePtr, columnIndex, rowIndex, false); + } else { + String stringValue = (String) value; + checkStringValueIsLegal(columnIndex, rowIndex, stringValue); + nativeSetString(nativePtr, columnIndex, rowIndex, (String) value, false); + } + break; + case DATE: + if (value == null) { throw new IllegalArgumentException("Null Date is not allowed."); } + nativeSetTimestamp(nativePtr, columnIndex, rowIndex, ((Date) value).getTime(), false); + break; + case BINARY: + if (value == null) { throw new IllegalArgumentException("Null Array is not allowed"); } + nativeSetByteArray(nativePtr, columnIndex, rowIndex, (byte[]) value, false); + break; + case UNSUPPORTED_MIXED: + case UNSUPPORTED_TABLE: + default: + throw new RuntimeException("Unexpected columnType: " + String.valueOf(colTypes[(int) columnIndex])); } } return rowIndex; @@ -748,7 +747,7 @@ public UncheckedRow getUncheckedRowByPointer(long nativeRowPointer) { /** * Returns a wrapper around Row access. All access will be error checked in JNI and will throw an appropriate * {@link RuntimeException} if used incorrectly. - * + *

                    * If error checking is done elsewhere, consider using {@link #getUncheckedRow(long)} for better performance. * * @param index the index of row to fetch. @@ -784,8 +783,7 @@ public void setDouble(long columnIndex, long rowIndex, double value, boolean isD } public void setDate(long columnIndex, long rowIndex, Date date, boolean isDefault) { - if (date == null) - throw new IllegalArgumentException("Null Date is not allowed."); + if (date == null) { throw new IllegalArgumentException("Null Date is not allowed."); } checkImmutable(); nativeSetTimestamp(nativePtr, columnIndex, rowIndex, date.getTime(), isDefault); } @@ -838,7 +836,7 @@ public void removeSearchIndex(long columnIndex) { * Defines a primary key for this table. This needs to be called manually before inserting data into the table. * * @param columnName the name of the field that will function primary key. "" or {@code null} will remove any - * previous set magic key. + * previous set magic key. * @throws io.realm.exceptions.RealmException if it is not possible to set the primary key due to the column * not having distinct values (i.e. violating the primary key constraint). */ @@ -1072,15 +1070,18 @@ public long findFirstNull(long columnIndex) { public long lowerBoundLong(long columnIndex, long value) { return nativeLowerBoundInt(nativePtr, columnIndex, value); } + public long upperBoundLong(long columnIndex, long value) { return nativeUpperBoundInt(nativePtr, columnIndex, value); } public Table pivot(long stringCol, long intCol, PivotType pivotType) { - if (! this.getColumnType(stringCol).equals(RealmFieldType.STRING )) + if (!this.getColumnType(stringCol).equals(RealmFieldType.STRING)) { throw new UnsupportedOperationException("Group by column must be of type String"); - if (! this.getColumnType(intCol).equals(RealmFieldType.INTEGER )) + } + if (!this.getColumnType(intCol).equals(RealmFieldType.INTEGER)) { throw new UnsupportedOperationException("Aggregation column must be of type Int"); + } Table result = new Table(); nativePivot(nativePtr, stringCol, intCol, pivotType.value, result.nativePtr); return result; @@ -1176,92 +1177,177 @@ public static String tableNameToClassName(String tableName) { } protected native long createNative(); + private native boolean nativeIsValid(long nativeTablePtr); + private native long nativeAddColumn(long nativeTablePtr, int type, String name, boolean isNullable); + private native long nativeAddColumnLink(long nativeTablePtr, int type, String name, long targetTablePtr); + private native void nativeRenameColumn(long nativeTablePtr, long columnIndex, String name); + private native void nativeRemoveColumn(long nativeTablePtr, long columnIndex); + private native boolean nativeIsColumnNullable(long nativePtr, long columnIndex); + private native void nativeConvertColumnToNullable(long nativeTablePtr, long columnIndex); + private native void nativeConvertColumnToNotNullable(long nativePtr, long columnIndex); + private native long nativeSize(long nativeTablePtr); + private native void nativeClear(long nativeTablePtr); + private native long nativeGetColumnCount(long nativeTablePtr); + private native String nativeGetColumnName(long nativeTablePtr, long columnIndex); + private native long nativeGetColumnIndex(long nativeTablePtr, String columnName); + private native int nativeGetColumnType(long nativeTablePtr, long columnIndex); + private native void nativeRemove(long nativeTablePtr, long rowIndex); + private native void nativeRemoveLast(long nativeTablePtr); + private native void nativeMoveLastOver(long nativeTablePtr, long rowIndex); + public static native long nativeAddEmptyRow(long nativeTablePtr, long rows); + private native long nativeGetSortedViewMulti(long nativeTableViewPtr, long[] columnIndices, boolean[] ascending); + private native long nativeGetLong(long nativeTablePtr, long columnIndex, long rowIndex); + private native boolean nativeGetBoolean(long nativeTablePtr, long columnIndex, long rowIndex); + private native float nativeGetFloat(long nativeTablePtr, long columnIndex, long rowIndex); + private native double nativeGetDouble(long nativeTablePtr, long columnIndex, long rowIndex); + private native long nativeGetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex); + private native String nativeGetString(long nativePtr, long columnIndex, long rowIndex); + private native byte[] nativeGetByteArray(long nativePtr, long columnIndex, long rowIndex); + private native long nativeGetLink(long nativePtr, long columnIndex, long rowIndex); + public static native long nativeGetLinkView(long nativePtr, long columnIndex, long rowIndex); + private native long nativeGetLinkTarget(long nativePtr, long columnIndex); + private native boolean nativeIsNull(long nativePtr, long columnIndex, long rowIndex); + native long nativeGetRowPtr(long nativePtr, long index); + public static native void nativeSetLong(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); + public static native void nativeSetLongUnique(long nativeTablePtr, long columnIndex, long rowIndex, long value); + public static native void nativeSetBoolean(long nativeTablePtr, long columnIndex, long rowIndex, boolean value, boolean isDefault); + public static native void nativeSetFloat(long nativeTablePtr, long columnIndex, long rowIndex, float value, boolean isDefault); + public static native void nativeSetDouble(long nativeTablePtr, long columnIndex, long rowIndex, double value, boolean isDefault); + public static native void nativeSetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex, long dateTimeValue, boolean isDefault); + public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value, boolean isDefault); + public static native void nativeSetStringUnique(long nativeTablePtr, long columnIndex, long rowIndex, String value); + public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex, boolean isDefault); + // Use nativeSetStringUnique(null) for String column! public static native void nativeSetNullUnique(long nativeTablePtr, long columnIndex, long rowIndex); + public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data, boolean isDefault); + public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); + private native long nativeSetPrimaryKey(long privateKeyTableNativePtr, long nativePtr, String columnName); + private static native boolean nativeMigratePrimaryKeyTableIfNeeded(long groupNativePtr, long primaryKeyTableNativePtr); + private static native boolean nativePrimaryKeyTableNeedsMigration(long primaryKeyTableNativePtr); + private native void nativeAddSearchIndex(long nativePtr, long columnIndex); + private native void nativeRemoveSearchIndex(long nativePtr, long columnIndex); + private native boolean nativeHasSearchIndex(long nativePtr, long columnIndex); + private native boolean nativeIsNullLink(long nativePtr, long columnIndex, long rowIndex); + public static native void nativeNullifyLink(long nativePtr, long columnIndex, long rowIndex); + private native long nativeSumInt(long nativePtr, long columnIndex); + private native long nativeMaximumInt(long nativePtr, long columnIndex); + private native long nativeMinimumInt(long nativePtr, long columnIndex); + private native double nativeAverageInt(long nativePtr, long columnIndex); + private native double nativeSumFloat(long nativePtr, long columnIndex); + private native float nativeMaximumFloat(long nativePtr, long columnIndex); + private native float nativeMinimumFloat(long nativePtr, long columnIndex); + private native double nativeAverageFloat(long nativePtr, long columnIndex); + private native double nativeSumDouble(long nativePtr, long columnIndex); + private native double nativeMaximumDouble(long nativePtr, long columnIndex); + private native double nativeMinimumDouble(long nativePtr, long columnIndex); + private native double nativeAverageDouble(long nativePtr, long columnIndex); + private native long nativeMaximumTimestamp(long nativePtr, long columnIndex); + private native long nativeMinimumTimestamp(long nativePtr, long columnIndex); + private native long nativeCountLong(long nativePtr, long columnIndex, long value); + private native long nativeCountFloat(long nativePtr, long columnIndex, float value); + private native long nativeCountDouble(long nativePtr, long columnIndex, double value); + private native long nativeCountString(long nativePtr, long columnIndex, String value); + private native long nativeWhere(long nativeTablePtr); + public static native long nativeFindFirstInt(long nativeTablePtr, long columnIndex, long value); + private native long nativeFindFirstBool(long nativePtr, long columnIndex, boolean value); + private native long nativeFindFirstFloat(long nativePtr, long columnIndex, float value); + private native long nativeFindFirstDouble(long nativePtr, long columnIndex, double value); + private native long nativeFindFirstTimestamp(long nativeTablePtr, long columnIndex, long dateTimeValue); + public static native long nativeFindFirstString(long nativeTablePtr, long columnIndex, String value); + public static native long nativeFindFirstNull(long nativeTablePtr, long columnIndex); + // FIXME: Disabled in cpp code, see comments there // private native long nativeFindAllTimestamp(long nativePtr, long columnIndex, long dateTimeValue); private native long nativeLowerBoundInt(long nativePtr, long columnIndex, long value); + private native long nativeUpperBoundInt(long nativePtr, long columnIndex, long value); + private native void nativePivot(long nativeTablePtr, long stringCol, long intCol, int pivotType, long resultPtr); + private native String nativeGetName(long nativeTablePtr); + private native String nativeToJson(long nativeTablePtr); + private native boolean nativeHasSameSchema(long thisTable, long otherTable); + private native long nativeVersion(long nativeTablePtr); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 76051a843b..ad81a72afd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -20,7 +20,7 @@ import io.realm.Case; import io.realm.Sort; -import io.realm.internal.async.BadVersionException; + public class TableQuery implements NativeObject { protected boolean DEBUG = false; @@ -64,12 +64,11 @@ public Table getTable() { * Checks in core if query syntax is valid. Throws exception, if not. */ void validateQuery() { - if (! queryValidated) { // If not yet validated, checks if syntax is valid + if (!queryValidated) { // If not yet validated, checks if syntax is valid String invalidMessage = nativeValidateQuery(nativePtr); - if (invalidMessage.equals("")) + if (invalidMessage.equals("")) { queryValidated = true; // If empty string error message, query is valid - else - throw new UnsupportedOperationException(invalidMessage); + } else { throw new UnsupportedOperationException(invalidMessage); } } } @@ -243,7 +242,7 @@ public TableQuery equalTo(long columnIndex[], boolean value) { private final static String DATE_NULL_ERROR_MESSAGE = "Date value in query criteria must not be null."; - public TableQuery equalTo(long columnIndex[], Date value){ + public TableQuery equalTo(long columnIndex[], Date value) { if (value == null) { nativeIsNull(nativePtr, columnIndex); } else { @@ -253,49 +252,45 @@ public TableQuery equalTo(long columnIndex[], Date value){ return this; } - public TableQuery notEqualTo(long columnIndex[], Date value){ - if (value == null) - throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); + public TableQuery notEqualTo(long columnIndex[], Date value) { + if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeNotEqualTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery greaterThan(long columnIndex[], Date value){ - if (value == null) - throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); + public TableQuery greaterThan(long columnIndex[], Date value) { + if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeGreaterTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long columnIndex[], Date value){ - if (value == null) - throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); + public TableQuery greaterThanOrEqual(long columnIndex[], Date value) { + if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeGreaterEqualTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery lessThan(long columnIndex[], Date value){ - if (value == null) - throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); + public TableQuery lessThan(long columnIndex[], Date value) { + if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeLessTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long columnIndex[], Date value){ - if (value == null) - throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); + public TableQuery lessThanOrEqual(long columnIndex[], Date value) { + if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeLessEqualTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery between(long columnIndex[], Date value1, Date value2){ - if (value1 == null || value2 == null) + public TableQuery between(long columnIndex[], Date value1, Date value2) { + if (value1 == null || value2 == null) { throw new IllegalArgumentException("Date values in query criteria must not be null."); // Different text + } nativeBetweenTimestamp(nativePtr, columnIndex, value1.getTime(), value2.getTime()); queryValidated = false; return this; @@ -338,6 +333,7 @@ public TableQuery notEqualTo(long columnIndex[], String value, Case caseSensitiv queryValidated = false; return this; } + public TableQuery notEqualTo(long columnIndex[], String value) { nativeNotEqual(nativePtr, columnIndex, value, true); queryValidated = false; @@ -449,6 +445,7 @@ public long sumInt(long columnIndex, long start, long end, long limit) { validateQuery(); return nativeSumInt(nativePtr, columnIndex, start, end, limit); } + public long sumInt(long columnIndex) { validateQuery(); return nativeSumInt(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -458,6 +455,7 @@ public Long maximumInt(long columnIndex, long start, long end, long limit) { validateQuery(); return nativeMaximumInt(nativePtr, columnIndex, start, end, limit); } + public Long maximumInt(long columnIndex) { validateQuery(); return nativeMaximumInt(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -467,6 +465,7 @@ public Long minimumInt(long columnIndex, long start, long end, long limit) { validateQuery(); return nativeMinimumInt(nativePtr, columnIndex, start, end, limit); } + public Long minimumInt(long columnIndex) { validateQuery(); return nativeMinimumInt(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -476,6 +475,7 @@ public double averageInt(long columnIndex, long start, long end, long limit) { validateQuery(); return nativeAverageInt(nativePtr, columnIndex, start, end, limit); } + public double averageInt(long columnIndex) { validateQuery(); return nativeAverageInt(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -487,6 +487,7 @@ public double sumFloat(long columnIndex, long start, long end, long limit) { validateQuery(); return nativeSumFloat(nativePtr, columnIndex, start, end, limit); } + public double sumFloat(long columnIndex) { validateQuery(); return nativeSumFloat(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -496,6 +497,7 @@ public Float maximumFloat(long columnIndex, long start, long end, long limit) { validateQuery(); return nativeMaximumFloat(nativePtr, columnIndex, start, end, limit); } + public Float maximumFloat(long columnIndex) { validateQuery(); return nativeMaximumFloat(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -505,6 +507,7 @@ public Float minimumFloat(long columnIndex, long start, long end, long limit) { validateQuery(); return nativeMinimumFloat(nativePtr, columnIndex, start, end, limit); } + public Float minimumFloat(long columnIndex) { validateQuery(); return nativeMinimumFloat(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -514,6 +517,7 @@ public double averageFloat(long columnIndex, long start, long end, long limit) { validateQuery(); return nativeAverageFloat(nativePtr, columnIndex, start, end, limit); } + public double averageFloat(long columnIndex) { validateQuery(); return nativeAverageFloat(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -525,6 +529,7 @@ public double sumDouble(long columnIndex, long start, long end, long limit) { validateQuery(); return nativeSumDouble(nativePtr, columnIndex, start, end, limit); } + public double sumDouble(long columnIndex) { validateQuery(); return nativeSumDouble(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -534,6 +539,7 @@ public Double maximumDouble(long columnIndex, long start, long end, long limit) validateQuery(); return nativeMaximumDouble(nativePtr, columnIndex, start, end, limit); } + public Double maximumDouble(long columnIndex) { validateQuery(); return nativeMaximumDouble(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -543,6 +549,7 @@ public Double minimumDouble(long columnIndex, long start, long end, long limit) validateQuery(); return nativeMinimumDouble(nativePtr, columnIndex, start, end, limit); } + public Double minimumDouble(long columnIndex) { validateQuery(); return nativeMinimumDouble(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -552,6 +559,7 @@ public double averageDouble(long columnIndex, long start, long end, long limit) validateQuery(); return nativeAverageDouble(nativePtr, columnIndex, start, end, limit); } + public double averageDouble(long columnIndex) { validateQuery(); return nativeAverageDouble(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -567,6 +575,7 @@ public Date maximumDate(long columnIndex, long start, long end, long limit) { } return null; } + public Date maximumDate(long columnIndex) { validateQuery(); Long result = nativeMaximumTimestamp(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -584,6 +593,7 @@ public Date minimumDate(long columnIndex, long start, long end, long limit) { } return null; } + public Date minimumDate(long columnIndex) { validateQuery(); Long result = nativeMinimumTimestamp(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); @@ -621,7 +631,7 @@ public long count() { public long remove() { validateQuery(); - if (table.isImmutable()) throwImmutable(); + if (table.isImmutable()) { throwImmutable(); } return nativeRemove(nativePtr); } @@ -641,69 +651,134 @@ private void throwImmutable() { } private native String nativeValidateQuery(long nativeQueryPtr); + private native void nativeGroup(long nativeQueryPtr); + private native void nativeEndGroup(long nativeQueryPtr); + private native void nativeOr(long nativeQueryPtr); + private native void nativeNot(long nativeQueryPtr); + private native void nativeEqual(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeNotEqual(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeGreater(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeGreaterEqual(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeLess(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeLessEqual(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeBetween(long nativeQueryPtr, long columnIndex[], long value1, long value2); + private native void nativeEqual(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeNotEqual(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeGreater(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeGreaterEqual(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeLess(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeLessEqual(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeBetween(long nativeQueryPtr, long columnIndex[], float value1, float value2); + private native void nativeEqual(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeNotEqual(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeGreater(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeGreaterEqual(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeLess(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeLessEqual(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeBetween(long nativeQueryPtr, long columnIndex[], double value1, double value2); + private native void nativeEqual(long nativeQueryPtr, long columnIndex[], boolean value); + private native void nativeEqualTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeNotEqualTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeGreaterTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeGreaterEqualTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeLessTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeLessEqualTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeBetweenTimestamp(long nativeQueryPtr, long columnIndex[], long value1, long value2); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndices, byte[] value); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndices, byte[] value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndexes, String value, boolean caseSensitive); + private native void nativeNotEqual(long nativeQueryPtr, long columnIndex[], String value, boolean caseSensitive); + private native void nativeBeginsWith(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); + private native void nativeEndsWith(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); + private native void nativeLike(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); + private native void nativeContains(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); + private native void nativeIsEmpty(long nativePtr, long[] columnIndices); + private native long nativeFind(long nativeQueryPtr, long fromTableRow); + private native long nativeFindAll(long nativeQueryPtr, long start, long end, long limit); + private native long nativeSumInt(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Long nativeMaximumInt(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Long nativeMinimumInt(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeAverageInt(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeSumFloat(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Float nativeMaximumFloat(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Float nativeMinimumFloat(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeAverageFloat(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeSumDouble(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Double nativeMaximumDouble(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Double nativeMinimumDouble(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeAverageDouble(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Long nativeMaximumTimestamp(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Long nativeMinimumTimestamp(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native void nativeIsNull(long nativePtr, long columnIndices[]); + private native void nativeIsNotNull(long nativePtr, long columnIndices[]); + private native long nativeCount(long nativeQueryPtr, long start, long end, long limit); + private native long nativeRemove(long nativeQueryPtr); + private native long nativeHandoverQuery(long callerSharedRealmPtr, long nativeQueryPtr); + private static native long nativeImportHandoverRowIntoSharedGroup(long handoverRowPtr, long callerSharedRealmPtr); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableSchema.java b/realm/realm-library/src/main/java/io/realm/internal/TableSchema.java index 1a0f2528ae..47200f336d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableSchema.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableSchema.java @@ -19,6 +19,7 @@ import io.realm.RealmFieldType; + public interface TableSchema { long addColumn(RealmFieldType type, String name); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java index 275cecfb04..48cdee87fb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java @@ -19,6 +19,8 @@ class TestUtil { public native static long getMaxExceptionNumber(); + public native static String getExpectedMessage(long exceptionKind); + public native static void testThrowExceptions(long exceptionKind); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index c583278793..c018cc5cfe 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -20,13 +20,14 @@ import io.realm.RealmFieldType; + /** * Wrapper around a Row in Realm Core. - * + *

                    * IMPORTANT: All access to methods using this class are non-checking. Safety guarantees are given by the * annotation processor and {@link RealmProxyMediator#validateTable(Class, SharedRealm, boolean)} * which is called before the typed API can be used. - * + *

                    * For low-level access to Row data where error checking is required, use {@link CheckedRow}. */ public class UncheckedRow implements NativeObject, Row { @@ -297,32 +298,60 @@ public boolean hasColumn(String fieldName) { } protected native long nativeGetColumnCount(long nativeTablePtr); + protected native String nativeGetColumnName(long nativeTablePtr, long columnIndex); + protected native long nativeGetColumnIndex(long nativeTablePtr, String columnName); + protected native int nativeGetColumnType(long nativeTablePtr, long columnIndex); + protected native long nativeGetIndex(long nativeRowPtr); + protected native long nativeGetLong(long nativeRowPtr, long columnIndex); + protected native boolean nativeGetBoolean(long nativeRowPtr, long columnIndex); + protected native float nativeGetFloat(long nativeRowPtr, long columnIndex); + protected native double nativeGetDouble(long nativeRowPtr, long columnIndex); + protected native long nativeGetTimestamp(long nativeRowPtr, long columnIndex); + protected native String nativeGetString(long nativePtr, long columnIndex); + protected native boolean nativeIsNullLink(long nativeRowPtr, long columnIndex); + protected native byte[] nativeGetByteArray(long nativePtr, long columnIndex); + protected native long nativeGetLinkView(long nativePtr, long columnIndex); + protected native void nativeSetLong(long nativeRowPtr, long columnIndex, long value); + protected native void nativeSetBoolean(long nativeRowPtr, long columnIndex, boolean value); + protected native void nativeSetFloat(long nativeRowPtr, long columnIndex, float value); + protected native long nativeGetLink(long nativeRowPtr, long columnIndex); + protected native void nativeSetDouble(long nativeRowPtr, long columnIndex, double value); + protected native void nativeSetTimestamp(long nativeRowPtr, long columnIndex, long dateTimeValue); + protected native void nativeSetString(long nativeRowPtr, long columnIndex, String value); + protected native void nativeSetByteArray(long nativePtr, long columnIndex, byte[] data); + protected native void nativeSetLink(long nativeRowPtr, long columnIndex, long value); + protected native void nativeNullifyLink(long nativeRowPtr, long columnIndex); + protected native boolean nativeIsAttached(long nativeRowPtr); + protected native boolean nativeHasColumn(long nativeRowPtr, String columnName); + protected native boolean nativeIsNull(long nativeRowPtr, long columnIndex); + protected native void nativeSetNull(long nativeRowPtr, long columnIndex); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 46de7e107e..1e89b70597 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -29,11 +29,13 @@ import io.realm.RealmObject; import io.realm.log.RealmLog; + public class Util { public static long getNativeMemUsage() { return nativeGetMemUsage(); } + static native long nativeGetMemUsage(); // Called by JNI. Do not remove. @@ -44,6 +46,7 @@ static void javaPrint(String txt) { public static String getTablePrefix() { return nativeGetTablePrefix(); } + static native String nativeGetTablePrefix(); /** @@ -64,26 +67,27 @@ public static Class getOriginalModelClass(ClassGets the stack trace from a Throwable as a String.

                    - * + *

                    *

                    The result of this method vary by JDK version as this method * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. * On JDK1.3 and earlier, the cause exception will not be shown * unless the specified throwable alters printStackTrace.

                    * - * @param throwable the Throwable to be examined + * @param throwable the Throwable to be examined * @return the stack trace as generated by the exception's - * printStackTrace(PrintWriter) method - * + * printStackTrace(PrintWriter) method + *

                    * Credit: https://commons.apache.org/proper/commons-lang/apidocs/src-html/org/apache/commons/lang3/exception/ExceptionUtils.html */ - public static String getStackTrace(final Throwable throwable) { + public static String getStackTrace(final Throwable throwable) { final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw, true); throwable.printStackTrace(pw); return sw.getBuffer().toString(); - } + } // Credit: http://stackoverflow.com/questions/2799097/how-can-i-detect-when-an-android-application-is-running-in-the-emulator public static boolean isEmulator() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java index 619ae6204e..78ea5a92dc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java @@ -19,6 +19,7 @@ import io.realm.internal.Capabilities; + /** * Realm capabilities for Android. */ @@ -40,11 +41,11 @@ public boolean canDeliverNotification() { @Override public void checkCanDeliverNotification(String exceptionMessage) { if (!hasLooper) { - throw new IllegalStateException( exceptionMessage == null ? "" : (exceptionMessage + " ") + + throw new IllegalStateException(exceptionMessage == null ? "" : (exceptionMessage + " ") + "Realm cannot be automatically updated on a thread without a looper."); } if (isIntentServiceThread) { - throw new IllegalStateException( exceptionMessage == null ? "" : (exceptionMessage + " ") + + throw new IllegalStateException(exceptionMessage == null ? "" : (exceptionMessage + " ") + "Realm cannot be automatically updated on an IntentService thread."); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java index 4fc88b8cde..c17e2fbb65 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java @@ -8,6 +8,7 @@ import io.realm.internal.RealmNotifier; import io.realm.internal.SharedRealm; + /** * {@link RealmNotifier} implementation for Android. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java b/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java index 5d9b90636b..1a2c0fba4c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java @@ -24,10 +24,11 @@ import java.util.GregorianCalendar; import java.util.TimeZone; + /** * Utilities methods for manipulating dates in iso8601 format. This is much much faster and GC friendly than using SimpleDateFormat so * highly suitable if you (un)serialize lots of date objects. - * + *

                    * Supported parse format: [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh[:]mm]] * * @see this specification @@ -117,7 +118,9 @@ public static Date parse(String date, ParsePosition pos) throws ParseException { char c = date.charAt(offset); if (c != 'Z' && c != '+' && c != '-') { seconds = parseInt(date, offset, offset += 2); - if (seconds > 59 && seconds < 63) seconds = 59; // Truncates up to 3 leap seconds. + if (seconds > 59 && seconds < 63) { + seconds = 59; // Truncates up to 3 leap seconds. + } // Milliseconds can be optional in the format. if (checkOffset(date, offset, '.')) { offset += 1; @@ -176,13 +179,13 @@ public static Date parse(String date, ParsePosition pos) throws ParseException { */ String cleaned = act.replace(":", ""); if (!cleaned.equals(timezoneId)) { - throw new IndexOutOfBoundsException("Mismatching time zone indicator: "+timezoneId+" given, resolves to " - +timezone.getID()); + throw new IndexOutOfBoundsException("Mismatching time zone indicator: " + timezoneId + " given, resolves to " + + timezone.getID()); } } } } else { - throw new IndexOutOfBoundsException("Invalid time zone indicator '" + timezoneIndicator+"'"); + throw new IndexOutOfBoundsException("Invalid time zone indicator '" + timezoneIndicator + "'"); } Calendar calendar = new GregorianCalendar(timezone); @@ -209,7 +212,7 @@ public static Date parse(String date, ParsePosition pos) throws ParseException { String input = (date == null) ? null : ('"' + date + "'"); String msg = fail.getMessage(); if (msg == null || msg.isEmpty()) { - msg = "("+fail.getClass().getName()+")"; + msg = "(" + fail.getClass().getName() + ")"; } ParseException ex = new ParseException("Failed to parse date [" + input + "]: " + msg, pos.getIndex()); ex.initCause(fail); @@ -270,7 +273,7 @@ private static int parseInt(String value, int beginIndex, int endIndex) throws N private static int indexOfNonDigit(String string, int offset) { for (int i = offset; i < string.length(); i++) { char c = string.charAt(i); - if (c < '0' || c > '9') return i; + if (c < '0' || c > '9') { return i; } } return string.length(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/JsonUtils.java b/realm/realm-library/src/main/java/io/realm/internal/android/JsonUtils.java index b25cd67328..0e46e4c077 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/JsonUtils.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/JsonUtils.java @@ -26,6 +26,7 @@ import io.realm.exceptions.RealmException; + public class JsonUtils { private static Pattern jsonDate = Pattern.compile("/Date\\((\\d*)(?:[+-]\\d*)?\\)/"); @@ -42,7 +43,7 @@ public class JsonUtils { * @throws NumberFormatException if date is not a proper long or has an illegal format. */ public static Date stringToDate(String date) { - if (date == null || date.length() == 0) return null; + if (date == null || date.length() == 0) { return null; } // Checks for JSON date. Matcher matcher = jsonDate.matcher(date); @@ -76,7 +77,7 @@ public static Date stringToDate(String date) { * @return the Byte array or empty byte array. */ public static byte[] stringToBytes(String str) { - if (str == null || str.length() == 0) return new byte[0]; + if (str == null || str.length() == 0) { return new byte[0]; } return Base64.decode(str, Base64.DEFAULT); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/BadVersionException.java b/realm/realm-library/src/main/java/io/realm/internal/async/BadVersionException.java index 294d309e90..34d92975d6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/BadVersionException.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/BadVersionException.java @@ -18,6 +18,7 @@ import io.realm.internal.Keep; + /** * Triggered from JNI level when the result of a query (from a different thread) could not be used against the current * state of the Realm which might be more up-to-date than the provided results or vice versa. diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/BgPriorityCallable.java b/realm/realm-library/src/main/java/io/realm/internal/async/BgPriorityCallable.java index 22af6503ad..b7be8c1ba8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/BgPriorityCallable.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/BgPriorityCallable.java @@ -18,6 +18,7 @@ import java.util.concurrent.Callable; + /** * Decorator to set the thread priority according to * Androids recommendation. diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java index 4427f98e77..cd1b62161b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java @@ -21,6 +21,7 @@ import io.realm.RealmAsyncTask; + public final class RealmAsyncTaskImpl implements RealmAsyncTask { private final Future pendingTask; private final ThreadPoolExecutor service; diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java index 091f954e9f..2d5ceee378 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java @@ -29,6 +29,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Custom thread pool settings, instances of this executor can be paused, and resumed, this will also set * appropriate number of Threads & wrap submitted tasks to set the thread priority according to @@ -153,7 +154,7 @@ protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); pauseLock.lock(); try { - while (isPaused) unpaused.await(); + while (isPaused) { unpaused.await(); } } catch (InterruptedException ie) { t.interrupt(); } finally { diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index eadf8ba645..dc410380d8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -41,6 +41,7 @@ import io.realm.internal.Table; import io.realm.internal.Util; + /** * This class is able to merge different RealmProxyMediators, so they look like one. */ @@ -74,7 +75,7 @@ public Table createTable(Class clazz, SharedRealm sharedRe @Override public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm, - boolean allowExtraColumns) { + boolean allowExtraColumns) { RealmProxyMediator mediator = getMediator(clazz); return mediator.validateTable(clazz, sharedRealm, allowExtraColumns); } @@ -93,11 +94,11 @@ public String getTableName(Class clazz) { @Override public E newInstance(Class clazz, - Object baseRealm, - Row row, - ColumnInfo columnInfo, - boolean acceptDefaultValue, - List excludeFields) { + Object baseRealm, + Row row, + ColumnInfo columnInfo, + boolean acceptDefaultValue, + List excludeFields) { RealmProxyMediator mediator = getMediator(clazz); return mediator.newInstance(clazz, baseRealm, row, columnInfo, acceptDefaultValue, excludeFields); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index e2cee9d70f..f0cdbf22b8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -41,6 +41,7 @@ import io.realm.internal.Table; import io.realm.internal.Util; + /** * Specialized version of a {@link RealmProxyMediator} that can further filter the available classes based on provided * filter. @@ -80,6 +81,7 @@ public RealmObjectSchema createRealmObjectSchema(Class cla checkSchemaHasClass(clazz); return originalMediator.createRealmObjectSchema(clazz, schema); } + @Override public Table createTable(Class clazz, SharedRealm sharedRealm) { checkSchemaHasClass(clazz); @@ -88,7 +90,7 @@ public Table createTable(Class clazz, SharedRealm sharedRe @Override public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm, - boolean allowExtraColumns) { + boolean allowExtraColumns) { checkSchemaHasClass(clazz); return originalMediator.validateTable(clazz, sharedRealm, allowExtraColumns); } @@ -107,11 +109,11 @@ public String getTableName(Class clazz) { @Override public E newInstance(Class clazz, - Object baseRealm, - Row row, - ColumnInfo columnInfo, - boolean acceptDefaultValue, - List excludeFields) { + Object baseRealm, + Row row, + ColumnInfo columnInfo, + boolean acceptDefaultValue, + List excludeFields) { checkSchemaHasClass(clazz); return originalMediator.newInstance(clazz, baseRealm, row, columnInfo, acceptDefaultValue, excludeFields); } diff --git a/realm/realm-library/src/main/java/io/realm/log/LogLevel.java b/realm/realm-library/src/main/java/io/realm/log/LogLevel.java index 2811a67b93..e0fbe087d4 100644 --- a/realm/realm-library/src/main/java/io/realm/log/LogLevel.java +++ b/realm/realm-library/src/main/java/io/realm/log/LogLevel.java @@ -18,7 +18,7 @@ /** * The Log levels defined and used by Realm when logging events in the API. - * + *

                    * Realm uses the log levels defined by Log4J: * https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Level.html * diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index c9218689db..984a06f2c0 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -18,6 +18,7 @@ import android.util.Log; + /** * Global logger used by all Realm components. * Custom loggers can be added by registering classes implementing {@link RealmLogger}. @@ -281,14 +282,20 @@ private static void log(int level, Throwable throwable, String message, Object.. } stringBuilder.append(message); } - nativeLog(level,REALM_JAVA_TAG, throwable, stringBuilder.toString()); + nativeLog(level, REALM_JAVA_TAG, throwable, stringBuilder.toString()); } private static native void nativeAddLogger(RealmLogger logger); + private static native void nativeRemoveLogger(RealmLogger logger); + private static native void nativeClearLoggers(); + private static native void nativeRegisterDefaultLogger(); + private static native void nativeLog(int level, String tag, Throwable throwable, String message); + private static native void nativeSetLogLevel(int level); + private static native int nativeGetLogLevel(); } diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java index 5c7b0e4f08..2fbd6d7a97 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java @@ -18,6 +18,7 @@ import io.realm.internal.Keep; + /** * Interface for custom loggers that can be registered at {@link RealmLog#add(RealmLogger)}. * The different log levels are described in {@link LogLevel}. diff --git a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java index b13ccbc127..72fd5ac68e 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java @@ -34,6 +34,7 @@ import rx.functions.Action0; import rx.subscriptions.Subscriptions; + /** * Factory class for creating Observables for RxJava (<=1.1.*). * diff --git a/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java index 535de3837d..efe20fbe92 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java @@ -26,6 +26,7 @@ import io.realm.RealmResults; import rx.Observable; + /** * Factory interface for creating Rx Observables for Realm classes. */ @@ -34,7 +35,7 @@ public interface RxObservableFactory { /** * Creates an Observable for a {@link Realm}. It should emit the initial state of the Realm when subscribed to and * on each subsequent update of the Realm. - * + *

                    * Realm observables are hot observables as Realms are automatically kept up to date. * * @param realm {@link Realm} to listen to changes for. @@ -45,7 +46,7 @@ public interface RxObservableFactory { /** * Creates an Observable for a {@link DynamicRealm}. It should emit the initial state of the Realm when subscribed * to and on each subsequent update of the Realm. - * + *

                    * DynamicRealm observables are hot observables as DynamicRealms are automatically kept up to date. * * @param realm {@link DynamicRealm} to listen to changes for. @@ -56,7 +57,7 @@ public interface RxObservableFactory { /** * Creates an Observable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and * on each subsequent update of the RealmResults. - * + *

                    * RealmResults observables are hot observables as RealmResults are automatically kept up to date. * * @param results {@link RealmResults} to listen to changes for. @@ -69,7 +70,7 @@ public interface RxObservableFactory { /** * Creates an Observable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and * on each subsequent update of the RealmResults. - * + *

                    * Realm observables are hot observables as RealmResults are automatically kept up to date. * * @param results {@link RealmResults} to listen to changes for. @@ -81,9 +82,9 @@ public interface RxObservableFactory { /** * Creates an Observable for a {@link RealmList}. It should emit the initial list when subscribed to and on each * subsequent update of the RealmList. - * + *

                    * RealmList observables are hot observables as RealmLists are automatically kept up to date. - * + *

                    * Note: {@link io.realm.RealmChangeListener} is currently not supported on RealmLists. * * @param list RealmObject to listen to changes for. @@ -95,9 +96,9 @@ public interface RxObservableFactory { /** * Creates an Observable for a {@link RealmList}. It should emit the initial list when subscribed to and on each * subsequent update of the RealmList. - * + *

                    * RealmList observables are hot observables as RealmLists are automatically kept up to date. - * + *

                    * Note: {@link io.realm.RealmChangeListener} is currently not supported on RealmLists. * * @param list RealmList to listen to changes for. @@ -108,7 +109,7 @@ public interface RxObservableFactory { /** * Creates an Observable for a {@link RealmObject}. It should emit the initial object when subscribed to and on each * subsequent update of the object. - * + *

                    * RealmObject observables are hot observables as RealmObjects are automatically kept up to date. * * @param object RealmObject to listen to changes for. @@ -120,7 +121,7 @@ public interface RxObservableFactory { /** * Creates an Observable for a {@link DynamicRealmObject}. It should emit the initial object when subscribed to and * on each subsequent update of the object. - * + *

                    * DynamicRealmObject observables are hot observables as DynamicRealmObjects automatically are kept up to date. * * @param object DynamicRealmObject to listen to changes for. @@ -130,7 +131,7 @@ public interface RxObservableFactory { /** * Creates an Observable from a {@link RealmQuery}. It should emit the query and then complete. - * + *

                    * A RealmQuery observable is cold. * * @param query RealmQuery to emit. @@ -141,7 +142,7 @@ public interface RxObservableFactory { /** * Creates an Observable from a {@link RealmQuery}. It should emit the query and then complete. - * + *

                    * A RealmQuery observable is cold. * * @param query RealmObject to listen to changes for. From 52ba434e3dc57072ef93df0d4a6c936f7682e790 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 21 Mar 2017 04:39:47 +0900 Subject: [PATCH 0567/2110] Introduce ErrorProne plugin (#4342) --- realm/build.gradle | 2 ++ realm/realm-library/build.gradle | 7 +++++++ .../androidTest/java/io/realm/DynamicRealmObjectTests.java | 2 +- .../src/androidTest/java/io/realm/IOSRealmTests.java | 2 ++ .../androidTest/java/io/realm/RealmCollectionTests.java | 1 + .../src/androidTest/java/io/realm/RealmListTests.java | 2 ++ .../src/androidTest/java/io/realm/RealmQueryTests.java | 1 + .../src/androidTest/java/io/realm/RealmResultsTests.java | 1 + .../src/androidTest/java/io/realm/RxJavaTests.java | 4 ++++ .../src/androidTest/java/io/realm/TestHelper.java | 1 + .../java/io/realm/internal/ObserverPairListTests.java | 2 +- .../androidTest/java/io/realm/rule/RunInLooperThread.java | 1 + .../src/main/java/io/realm/DynamicRealmObject.java | 2 +- .../src/main/java/io/realm/RealmConfiguration.java | 1 + .../main/java/io/realm/internal/ObjectServerFacade.java | 1 + 15 files changed, 27 insertions(+), 3 deletions(-) diff --git a/realm/build.gradle b/realm/build.gradle index 120340d00c..53a4573382 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -3,6 +3,7 @@ buildscript { mavenLocal() jcenter() maven { url 'https://jitpack.io' } + maven { url "https://plugins.gradle.org/m2/" } } dependencies { @@ -15,6 +16,7 @@ buildscript { classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:3.1.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" + classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.9' } } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index b7b5097c1f..208d17e788 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -9,6 +9,7 @@ apply plugin: 'pmd' apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' +apply plugin: 'net.ltgt.errorprone' def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) @@ -113,6 +114,12 @@ android { } } +project.afterEvaluate { + tasks.withType(JavaCompile) { + options.compilerArgs << '-Werror' + } +} + coveralls.jacocoReportPath = "${buildDir}/reports/coverage/debug/report.xml" import io.realm.transformer.RealmTransformer diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index e0208b45b5..fac6bd2193 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -128,7 +128,7 @@ private enum ThreadConfinedMethods { HASH_CODE, EQUALS, TO_STRING, } - @SuppressWarnings({"ResultOfMethodCallIgnored", "EqualsWithItself"}) + @SuppressWarnings({"ResultOfMethodCallIgnored", "EqualsWithItself", "SelfEquals"}) private static void callThreadConfinedMethod(DynamicRealmObject obj, ThreadConfinedMethods method) { switch (method) { case GET_BOOLEAN: obj.getBoolean(AllJavaTypes.FIELD_BOOLEAN); break; diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index 2278bf240d..1ea36519e9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -146,6 +146,7 @@ public void iOSDataTypesNullValues() throws IOException { } @Test + @SuppressWarnings("ConstantOverflow") public void iOSDataTypesMinimumValues() throws IOException { for (String iosVersion : IOS_VERSIONS) { configFactory.copyRealmFromAssets(context, @@ -167,6 +168,7 @@ public void iOSDataTypesMinimumValues() throws IOException { } @Test + @SuppressWarnings("ConstantOverflow") public void iOSDataTypesMaximumValues() throws IOException { for (String iosVersion : IOS_VERSIONS) { configFactory.copyRealmFromAssets(context, diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java index f72137c2c2..7e3c7a9590 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCollectionTests.java @@ -235,6 +235,7 @@ public void contains_realmObjectFromOtherRealm() { } @Test + @SuppressWarnings("CollectionIncompatibleType") public void contains_wrongType() { //noinspection SuspiciousMethodCalls assertFalse(collection.contains(new Dog())); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index cd4e057f84..78fdc0e8df 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -591,6 +591,7 @@ public void removeAll_managedMode() { } @Test + @SuppressWarnings("CollectionIncompatibleType") public void removeAll_managedMode_wrongClass() { realm.beginTransaction(); //noinspection SuspiciousMethodCalls @@ -598,6 +599,7 @@ public void removeAll_managedMode_wrongClass() { } @Test + @SuppressWarnings("CollectionIncompatibleType") public void removeAll_unmanaged_wrongClass() { RealmList list = createUnmanagedDogList(); //noinspection SuspiciousMethodCalls diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index b10f607f14..27f6cae43e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2606,6 +2606,7 @@ public void largeRealmMultipleThreads() throws InterruptedException { Thread thread = new Thread( new Runnable() { @Override + @SuppressWarnings("ElementsCountedInLoop") public void run() { RealmConfiguration realmConfig = configFactory.createConfiguration(); Realm realm = Realm.getInstance(realmConfig); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 4b4b690e96..2a996bf726 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -229,6 +229,7 @@ public void distinct() { } @Test + @SuppressWarnings("ReferenceEquality") public void distinct_restrictedByPreviousDistinct() { final long numberOfBlocks = 25; final long numberOfObjects = 10; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index f2a3e78586..e306343516 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -230,6 +230,7 @@ public void realmResults_emittedOnSubscribe() { final RealmResults results = realm.where(AllTypes.class).findAll(); subscription = results.asObservable().subscribe(new Action1>() { @Override + @SuppressWarnings("ReferenceEquality") public void call(RealmResults rxResults) { assertTrue(rxResults == results); subscribedNotified.set(true); @@ -248,6 +249,7 @@ public void realmList_emittedOnSubscribe() { realm.commitTransaction(); subscription = list.asObservable().subscribe(new Action1>() { @Override + @SuppressWarnings("ReferenceEquality") public void call(RealmList rxList) { assertTrue(rxList == list); subscribedNotified.set(true); @@ -265,6 +267,7 @@ public void dynamicRealmResults_emittedOnSubscribe() { final RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); subscription = results.asObservable().subscribe(new Action1>() { @Override + @SuppressWarnings("ReferenceEquality") public void call(RealmResults rxResults) { assertTrue(rxResults == results); subscribedNotified.set(true); @@ -353,6 +356,7 @@ public void findAllAsync_emittedOnSubscribe() { final RealmResults results = realm.where(AllTypes.class).findAllAsync(); subscription = results.asObservable().subscribe(new Action1>() { @Override + @SuppressWarnings("ReferenceEquality") public void call(RealmResults rxResults) { assertTrue(rxResults == results); subscribedNotified.set(true); diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 548515114f..064af91f02 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -796,6 +796,7 @@ public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { } // Cleans resource, shutdowns the executor service and throws any background exception. + @SuppressWarnings("Finally") public static void exitOrThrow(final ExecutorService executorService, final CountDownLatch signalTestFinished, final CountDownLatch signalClosedRealm, diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java index c06aa13900..a36ec5b8df 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java @@ -123,7 +123,7 @@ public void onCalled(TestObserverPair pair, Object observer) { assertTrue(foreachCalled.get()); } - @SuppressLint("UseValueOf") + @SuppressLint({"UseValueOf", "BoxedPrimitiveConstructor"}) @Test public void remove() { TestObserverPair pair = new TestObserverPair(ONE, testListener); diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index 704deb9485..186040bdec 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -92,6 +92,7 @@ public Statement apply(final Statement base, Description description) { private Throwable testException; @Override + @SuppressWarnings({"ClassNewInstance", "Finally"}) public void evaluate() throws Throwable { before(); final String threadName = annotation.threadName(); diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 7488623c72..7d85e9a5be 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -82,7 +82,7 @@ public DynamicRealmObject(RealmModel obj) { * @return the field value. * @throws ClassCastException if the field doesn't contain a field of the defined return type. */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) public E get(String fieldName) { proxyState.getRealm$realm().checkIfValid(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index cb6bddc599..aec0408271 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -337,6 +337,7 @@ public String toString() { * * @return {@code true} if RxJava dependency exist, {@code false} otherwise. */ + @SuppressWarnings("LiteralClassName") static synchronized boolean isRxJavaAvailable() { if (rxJavaAvailable == null) { try { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index cdf5b9b0a3..1862aecdf1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -35,6 +35,7 @@ public class ObjectServerFacade { static { //noinspection TryWithIdenticalCatches try { + @SuppressWarnings("LiteralClassName") Class syncFacadeClass = Class.forName("io.realm.internal.objectserver.SyncObjectServerFacade"); //noinspection unchecked syncFacade = (ObjectServerFacade) syncFacadeClass.getDeclaredConstructor().newInstance(); From 624564e5061bf026786ac9c8adfe1edccd5ee1d0 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 21 Mar 2017 14:47:26 +0900 Subject: [PATCH 0568/2110] fix build error in Javadoc task (#4356) --- realm/realm-library/src/main/java/io/realm/RealmList.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 891f052eff..549908bb4f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -152,7 +152,6 @@ private boolean isAttached() { *

                  • Unmanaged RealmLists: It is possible to add both managed and unmanaged objects. If adding managed * objects to an unmanaged RealmList they will not be copied to the Realm again if using * {@link Realm#copyToRealm(RealmModel)} afterwards.
                  • - *

                    *

                  • Managed RealmLists: It is possible to add unmanaged objects to a RealmList that is already managed. In * that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} * or {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key.
                  • @@ -185,7 +184,6 @@ public void add(int location, E object) { *
                  • Unmanaged RealmLists: It is possible to add both managed and unmanaged objects. If adding managed * objects to an unmanaged RealmList they will not be copied to the Realm again if using * {@link Realm#copyToRealm(RealmModel)} afterwards.
                  • - *

                    *

                  • Managed RealmLists: It is possible to add unmanaged objects to a RealmList that is already managed. In * that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} * or {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key.
                  • @@ -215,7 +213,6 @@ public boolean add(E object) { *
                  • Unmanaged RealmLists: It is possible to add both managed and unmanaged objects. If adding managed * objects to an unmanaged RealmList they will not be copied to the Realm again if using * {@link Realm#copyToRealm(RealmModel)} afterwards.
                  • - *

                    *

                  • Managed RealmLists: It is possible to add unmanaged objects to a RealmList that is already managed. * In that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} or * {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key.
                  • From 92d04563bdd2a71c408c0c28d8ccf9de09f998d1 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 21 Mar 2017 15:32:37 +0900 Subject: [PATCH 0569/2110] add @Override annotation to internal backlinks getters. (#4357) --- .../main/java/io/realm/processor/RealmProxyClassGenerator.java | 1 + .../src/test/resources/io/realm/AllTypesRealmProxy.java | 1 + 2 files changed, 2 insertions(+) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index b10d5faf2a..328426dc3a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -566,6 +566,7 @@ private void emitBacklinkFieldAccessors(JavaWriter writer) throws IOException { String realmResultsType = "RealmResults<" + backlink.getSourceClass() + ">"; // Getter, no setter + writer.emitAnnotation("Override"); writer.beginMethod(realmResultsType, metadata.getInternalGetter(backlink.getTargetField()), EnumSet.of(Modifier.PUBLIC)) .emitStatement("BaseRealm realm = proxyState.getRealm$realm()") .emitStatement("realm.checkIfValid()") diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 18aa93ce89..f8cb971a58 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -393,6 +393,7 @@ public final AllTypesColumnInfo clone() { } } + @Override public RealmResults realmGet$parentObjects() { BaseRealm realm = proxyState.getRealm$realm(); realm.checkIfValid(); From f96ea990e58c437d4931f4c7d427554229178008 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 21 Mar 2017 16:36:45 +0800 Subject: [PATCH 0570/2110] findFirstAsync returns invalid row if no object Fix #4352 The findFirstAsync()'s behavior doesn't match the javadoc from the first day the API was introduced. It kept running the query until it could find a row match the query condition. This behavior create difficulties if user want to check if there is no object in the db. Also it was not consistent with the behavior of findFirst() which will return an invalid row in the same condition. --- CHANGELOG.md | 1 + .../java/io/realm/RealmAsyncQueryTests.java | 16 ++++------------ .../src/main/java/io/realm/ProxyState.java | 10 ++++++---- .../main/java/io/realm/internal/PendingRow.java | 8 ++++---- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec878300cc..67fdd9e1d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * `Realm.migrateRealm(RealmConfiguration)` now fails correctly with an `IllegalArgumentException` if a `SyncConfiguration` is provided (#4075). * Fixed a potential cause for Realm file corruptions (never reported). * Add `@Override` annotation to proxy class accessors and stop using raw type in proxy classes in order to remove warnings from javac (#4329). +* `findFirstAsync()` now returns an invalid object if there is no object matches the query condition instead of running the query repeatedly until it can find one (#4352). ### Deprecated diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index ee1c90b67d..a9608250db 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -551,34 +551,26 @@ public void onChange(AllTypes object) { }); } + // When there is no object match the query condition, findFirstAsync should return with an invalid row. @Test @RunTestInLooperThread - public void findFirstAsync_initalEmptyRow() throws Throwable { + public void findFirstAsync_initialEmptyRow() throws Throwable { Realm realm = looperThread.realm; final AllTypes firstAsync = realm.where(AllTypes.class).findFirstAsync(); looperThread.keepStrongReference.add(firstAsync); firstAsync.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { - assertTrue(firstAsync.load()); assertTrue(firstAsync.isLoaded()); - assertTrue(firstAsync.isValid()); - assertEquals(0, firstAsync.getColumnLong()); + assertFalse(firstAsync.isValid()); looperThread.testComplete(); } }); - - realm.beginTransaction(); - realm.createObject(AllTypes.class).setColumnLong(0); - realm.commitTransaction(); - - assertTrue(firstAsync.load()); - assertTrue(firstAsync.isLoaded()); } @Test @RunTestInLooperThread - public void findFirstAsync_updatedIfsyncRealmObjectIsUpdated() throws Throwable { + public void findFirstAsync_updatedIfSyncRealmObjectIsUpdated() throws Throwable { populateTestRealm(looperThread.realm, 1); AllTypes firstSync = looperThread.realm.where(AllTypes.class).findFirst(); assertEquals(0, firstSync.getColumnLong()); diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 843fb229c4..2e46b093d1 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -135,7 +135,7 @@ public void setConstructionFinished() { } private void registerToRealmNotifier() { - if (realm.sharedRealm == null || realm.sharedRealm.isClosed()) { + if (realm.sharedRealm == null || realm.sharedRealm.isClosed() || !row.isAttached()) { return; } @@ -172,9 +172,11 @@ public void load() { @Override public void onQueryFinished(Row row) { this.row = row; - // getTable should return a non-null table since the row should always be valid here. - currentTableVersion = row.getTable().getVersion(); notifyChangeListeners(); - registerToRealmNotifier(); + if (row.isAttached()) { + // getTable should return a non-null table since the row should always be valid here. + currentTableVersion = row.getTable().getVersion(); + registerToRealmNotifier(); + } } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 8370b03aa2..9f11f6122b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -58,12 +58,12 @@ public void onChange(PendingRow pendingRow) { Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; // Ask the front end to reset the row and stop async query. frontEnd.onQueryFinished(row); - clearPendingCollection(); + } else { + frontEnd.onQueryFinished(InvalidRow.INSTANCE); } - } else { - // The Realm is closed. Do nothing then. - clearPendingCollection(); } + + clearPendingCollection(); } }; pendingCollection.addListener(this, listener); From cd1027e01bfa380821563a728ec3e467f459ac09 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 21 Mar 2017 17:53:41 +0800 Subject: [PATCH 0571/2110] Fix code comments --- .../src/main/java/io/realm/internal/PendingRow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 9f11f6122b..16e63b10ea 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -53,7 +53,7 @@ public void onChange(PendingRow pendingRow) { if (pendingCollection.isValid()) { // PendingRow will always get the first Row of the query since we only support findFirst. UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); - // If no rows returned by the query, just wait for the query updates until it returns a valid row. + // If no rows returned by the query, notify the frontend with an invalid row. if (uncheckedRow != null) { Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; // Ask the front end to reset the row and stop async query. From 3d7f8f06ad210842a6c1e86bac427faf945a9e0e Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 23 Mar 2017 18:34:19 +0900 Subject: [PATCH 0572/2110] update gradle wrapper to 3.4.1 (#4362) --- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54208 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- realm.properties | 2 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54208 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index 35fb1ae0907527e832aa0839f8b932bc6ff5cab6..1149f4ca38ceccebfd469e125e6c56f0c5eb7617 100644 GIT binary patch delta 957 zcmYL{YeR2CHVzW*ck;pO*xp7TG4^PY1CUEH9HQ W zNfse;Icc<++LS6e4{ad-!Jf7Bh6q8rarwW|H+HV0j3|Ya>p&C#Ws03>QEdMxQMy&|y!OEZ(bO+19*`N;G6*>fVgjoLP z9F1YUaDcQstN|y&7I1rnu`6OgJsUX>Hb#x$%cvDx9y<=+ikZMKu?Fx!{504fXYM*v<~CJxmq=*mGf7n L6)uR{$M*gMo(gDQ delta 957 zcmYL{ZAep57{||com-QcQ-d_jRIr5160w@S&?~Xoa?Qw6r+LlZ$qZ$s9}=Q3eaLwt zij;y`8wf@%ip0>i>E4{1O0QI8QX+k6lv$wEz5g@p!{zsTp7TG4bI&;gHg3SisY-NQ zOfn$~1yQzSSyb5y0oqq{tuvOQvgDK>4Uq;3`Jb6@&a&7tI1zzWz^}C;961u@B2j3e zate(VaXE5GabkZgBtAthg)CiWVsatvLkv2HGSw`6CTG1&>`>d935;CLYh&5v`K2m! z8n>1uLBji|l#t%aE|xh`{T4zu^A+5Tuu2ENTBIYuf09Ca>LSv3p`*>b=sVmlBjlEf zwpy3a`kpvP@Pu84kojyk(lonaS}le2CglwRZrZk#B<&K@_y%OXXH;yuy zY^r3k!~7%$Lvt-f97J=;o(TEUp00q%Z--gn3WVvGU{lhCsgb=mxmFOhWWU}xy=rd=*!rP!TPK|i(PO`ya&Ke zFU$Xq(&*ER2S~eo8gRmA0=N4at$qXQS^qikRKN(n2=L&_;iKTS;ZpGHa2>cWcoOUj zviS)k$5A(r)PkQ!SWXu@i27cL<^F_@q2|Mk!Ehz&+)+0FW|Z6*d$=`YH5m6q>+Er@ OLbxiectO-WzULo&3YA;{ diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index c7fdbdb551..cbb9ce3c56 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Mar 03 05:45:58 JST 2017 +#Wed Mar 22 16:44:51 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index d8af58cce028cc089108a63cebd70d0f3cd05ef3..1deb4fd325a1899e8601c746857df88a8585ac34 100644 GIT binary patch delta 957 zcmYL{YeO<;@ z$Wkh1O%N8eC=x;2=C1DIM7ltcMTzu9p|YT;_x&HS4==yp^PK-VocEkF=;Q{SoLr~j zQWS(prKHJbY?BvDd8qvb4|cUxm=qmT)@iGV{LfVL8D}9UAhHbju}XkjDU!_>gqF)D z&?rb#N+6Y41M?v1izH%5&SEo@^Lg)5&^c6;$-<|VYf^;{b*>!8$n}y|mR(Y6l%vzK z$&?LA?4OW9`f9pa=1AQe2;E?*<_!EQotEVS-4V+NF~rq4El%e<>Y@vw!|kbrbjaxy z$3oidN^?g~^rRwWKhujeEw)Z(3gOOE)p4hYkVhOLYw5Htk7iJbd;7Vd2>AsqBTPQo zYM5-ZKTE;T?@JXNM0L3*3-Y-=UkZ`jO|bCWPRcUP_c3bKFSuX>(A$~@7CbATkFMy82BGuP4)I%`X`y|_6_XB1SSa&`K!QMKg&P%n^4OFTCggh273aP;7mXR?g$rv_=5sbC3}C1N$Z(3RM1X){`>)12AGB3mdc{g4oS=|k#? zC{hZhZ6GXaQ6z%4&0XC^r7IPglt^EaS{5kvzW*ck;pO*xp7TG4^PY2ttlW^5Q&}o|lA@lh@q-m~cS}BCP&Q?aOB0?T;glwiW%^5U_N*#MH`b5YtZ5m}V z*<8hBxA|!thUQv}If&|tEgABqGgAhU-i@;GyDrKyjrTbw&6bx^WVC&M*}hBdJK2Ia zAD6Svw`s;qealBKfA_m8fn5JHsvSE><-Q_14fpwFF!i*JL*oGRhyX@M&rtaBBC-LCE6 zN0$yPbRPh(y34>fZUeYw*a+Smt_DS(3b5M4@((?F)N-#DtnjKqo3{j<^=iPqzQbU* zkL7Lr@342pYlFBPYNcBjw=Nk$Ui8=nOaz zV)K*2r%<%h-pmeWLzpze*Z+@Huv)W#@dC|ZSj-6)%Xdx6{-d$_e@wHWs<)Vbqo O8Glu3;ex1neE&bjvXqVh diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 1e575420eb..7dd58c667b 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 02 03:11:23 JST 2017 +#Wed Mar 22 16:44:52 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 48a802eecf31c2234238b4a20391779745fb609d..34c574f227cc8d4e1dbbefc91d650437ac252ac0 100644 GIT binary patch delta 26 gcmX@GocX|V<_*z@m>0K}ZH_x6BnV|d%xRkXiy{%UZSLvZRHO$KS(HfsC{z{{b?^6x{c-txe!ufQhjY(415R$h$tlZp zTtYe_fBBR30LSaw;VQHf6T zmWnh;{NSVl(p%ldGRJD)K%X+r41#dqw zvd-71Q%qhxpJo#7rEJ04zHAQrZV7(7vr$fnLHyCRwA_|S|BPn3ef_&Jfhs+EuJeYK znmn3Z>5_S2>%Q@fG(w0<{K|APc{-VY8uW#yX#Q=`?;eeG^t8}h0M>hp!4B_c@V(an z7Wwvr*L~&SYo7_+FlYwv57vM(eNoj`4lGe+aps8@}!`FH2Yjk1SZH(H1Bz+9a_rWM4i M(n=RZZDV`?0jghUYybcN delta 957 zcmYL{Yec=`RF=lsv%yyu*LJJ)aLlqEVY zMn#BRP85yVEy^6Z0PU;Ursh(YI2RuT)JQLm2`JJh~r3?tX_npt*vzDbEr z{npYHNOb>%0@72_$udW(-a_bRzMPvDR%zo`i*$zgPf|#C?UXb@=xDPp1rE2y5^`Hf zn>v=zy6$*q_=F=CA=|kwq-l<2GD!@#oi2~qC4@ZS2-!fVtm!n7%A7kd_$A0Mt{-MH zZmnRl!}c@=L$i%V97J>3kqr6LnjweC?nGJmT^nVYx_cZGYsX6&GWxE+Y~Ll_+t`9P z9-CO_+mlHqub+Kq66~RD!K&U24*PEQ|Gc?LN{CVT(RI{dOQUlmX--eyZcLy`m!3PD z%Tu#UlNl#mvP?ey{?XMGLP(PEmFOf=6Ji011r5O|Ik~CTH(`!Wj-zF@D+nIJ{`Ere*kRv zv;6OQ8UuRq0BL7H1C9mE;PxP6N6>(JCU_n^6*7V^LOi%~@F;kF&;))RtOfUlPlCN+ zHa~IbIO>L>8u0TF%jqHqQQwcS+@HuX)O?gN9IZf|JIv0H|ZH_x6BnV Date: Fri, 24 Mar 2017 17:45:44 +0800 Subject: [PATCH 0573/2110] Add JNI helper class JavaGlabalRef and JavaClass (#4365) - Added two class to help manage the jobject lifecycle. - Use a static local JavaClass for SyncManager. --- .../cpp/io_realm_internal_SharedRealm.cpp | 10 +-- .../src/main/cpp/io_realm_internal_Util.cpp | 7 +- .../src/main/cpp/jni_util/java_class.cpp | 42 ++++++++++++ .../src/main/cpp/jni_util/java_class.hpp | 62 ++++++++++++++++++ .../src/main/cpp/jni_util/java_global_ref.cpp | 36 +++++++++++ .../src/main/cpp/jni_util/java_global_ref.hpp | 64 +++++++++++++++++++ .../src/main/cpp/jni_util/jni_utils.cpp | 12 ++++ .../src/main/cpp/jni_util/jni_utils.hpp | 9 +++ 8 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_class.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_class.hpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 531611f4d1..1c5fcc2148 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -28,6 +28,7 @@ #include "util.hpp" #include "jni_util/java_method.hpp" +#include "jni_util/java_class.hpp" using namespace realm; using namespace realm::_impl; @@ -74,11 +75,12 @@ class JniConfigWrapper { : m_config(std::move(config)) { #if REALM_ENABLE_SYNC + static JavaClass sync_manager_class(env, "io/realm/SyncManager"); // Doing the methods lookup from the thread that loaded the lib, to avoid // https://developer.android.com/training/articles/perf-jni.html#faq_FindClass - static JavaMethod java_error_callback_method(env, java_syncmanager, "notifyErrorHandler", + static JavaMethod java_error_callback_method(env, sync_manager_class, "notifyErrorHandler", "(ILjava/lang/String;Ljava/lang/String;)V", true); - static JavaMethod java_bind_session_method(env, java_syncmanager, "bindSessionWithConfig", + static JavaMethod java_bind_session_method(env, sync_manager_class, "bindSessionWithConfig", "(Ljava/lang/String;)Ljava/lang/String;", true); // error handler will be called form the sync client thread @@ -96,7 +98,7 @@ class JniConfigWrapper { } JNIEnv* env = realm::jni_util::JniUtils::get_env(true); - env->CallStaticVoidMethod(java_syncmanager, java_error_callback_method, error_code, + env->CallStaticVoidMethod(sync_manager_class, java_error_callback_method, error_code, to_jstring(env, error_message), to_jstring(env, session.get()->path())); }; @@ -110,7 +112,7 @@ class JniConfigWrapper { JNIEnv* env = realm::jni_util::JniUtils::get_env(true); jstring access_token_string = (jstring)env->CallStaticObjectMethod( - java_syncmanager, java_bind_session_method, to_jstring(env, path.c_str())); + sync_manager_class, java_bind_session_method, to_jstring(env, path.c_str())); if (access_token_string) { // reusing cached valid token JStringAccessor access_token(env, access_token_string); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 070de208b9..48e47cf726 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -53,9 +53,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); java_util_date = GetClass(env, "java/util/Date"); java_util_date_init = env->GetMethodID(java_util_date, "", "(J)V"); -#if REALM_ENABLE_SYNC - java_syncmanager = GetClass(env, "io/realm/SyncManager"); -#endif } return JNI_VERSION_1_6; @@ -73,9 +70,7 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) env->DeleteGlobalRef(java_lang_double); env->DeleteGlobalRef(java_util_date); env->DeleteGlobalRef(java_lang_string); -#if REALM_ENABLE_SYNC - env->DeleteGlobalRef(java_syncmanager); -#endif + JniUtils::release(); } } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_class.cpp b/realm/realm-library/src/main/cpp/jni_util/java_class.cpp new file mode 100644 index 0000000000..f0bb7530c5 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_class.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "java_class.hpp" +#include "jni_utils.hpp" + +#include + +using namespace realm::jni_util; + +JavaClass::JavaClass(JNIEnv* env, const char* class_name, bool free_on_unload) + : m_ref_owner(get_jclass(env, class_name)) + , m_class(reinterpret_cast(m_ref_owner.get())) +{ + if (free_on_unload) { + // Move the ownership of global ref to JNIUtils which will be released when JNI_OnUnload. + JniUtils::keep_global_ref(m_ref_owner); + } +} + +JavaGlobalRef JavaClass::get_jclass(JNIEnv* env, const char* class_name) +{ + jclass cls = env->FindClass(class_name); + REALM_ASSERT_DEBUG(cls); + + JavaGlobalRef cls_ref(env, cls); + env->DeleteLocalRef(cls); + return cls_ref; +} diff --git a/realm/realm-library/src/main/cpp/jni_util/java_class.hpp b/realm/realm-library/src/main/cpp/jni_util/java_class.hpp new file mode 100644 index 0000000000..c95dce29d4 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_class.hpp @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_JAVA_CLASS_HPP +#define REALM_JNI_UTIL_JAVA_CLASS_HPP + +#include + +#include "java_global_ref.hpp" + +namespace realm { +namespace jni_util { + +// To find the jclass and manage the lifecycle for the jclass's global ref. +class JavaClass { +public: + // when free_on_unload is true, the jclass's global ref will be released when JNI_OnUnload called. This is useful + // when the JavaClass instance is static. Otherwise the jclass's global ref will be released when this object is + // deleted. + JavaClass(JNIEnv* env, const char* class_name, bool free_on_unload = true); + ~JavaClass() + { + } + + inline jclass get() noexcept + { + return m_class; + } + + inline operator jclass() const noexcept + { + return m_class; + } + + // Not implemented for now. + JavaClass(JavaClass&&) = delete; + JavaClass(JavaClass&) = delete; + JavaClass& operator=(JavaClass&&) = delete; + +private: + JavaGlobalRef m_ref_owner; + jclass m_class; + static JavaGlobalRef get_jclass(JNIEnv* env, const char* class_name); +}; + +} // jni_util +} // realm + +#endif diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp new file mode 100644 index 0000000000..fd6036cf95 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "java_global_ref.hpp" +#include "jni_utils.hpp" + +#include + +using namespace realm::jni_util; + +JavaGlobalRef::~JavaGlobalRef() +{ + if (m_ref) { + JniUtils::get_env()->DeleteGlobalRef(m_ref); + } +} + +JavaGlobalRef& JavaGlobalRef::operator=(JavaGlobalRef&& rhs) +{ + this->~JavaGlobalRef(); + new (this) JavaGlobalRef(std::move(rhs)); + return *this; +} diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp new file mode 100644 index 0000000000..a08aad4bd6 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_JAVA_GLOBAL_REF_HPP +#define REALM_JNI_UTIL_JAVA_GLOBAL_REF_HPP + +#include + +namespace realm { +namespace jni_util { + +// Manage the lifecycle of jobject's global ref. +class JavaGlobalRef { +public: + JavaGlobalRef() + : m_ref(nullptr) + { + } + JavaGlobalRef(JNIEnv* env, jobject obj) + : m_ref(obj ? env->NewGlobalRef(obj) : nullptr) + { + } + JavaGlobalRef(JavaGlobalRef&& rhs) + : m_ref(rhs.m_ref) + { + rhs.m_ref = nullptr; + } + ~JavaGlobalRef(); + + JavaGlobalRef& operator=(JavaGlobalRef&& rhs); + + inline operator bool() const noexcept + { + return m_ref != nullptr; + } + + inline jobject get() noexcept + { + return m_ref; + } + + // Not implemented for now. + JavaGlobalRef(JavaGlobalRef&) = delete; + +private: + jobject m_ref; +}; +} +} + +#endif // REALM_JNI_UTIL_JAVA_GLOBAL_REF_HPP diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp index 88a23ebb01..0ca6f526a3 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp @@ -31,6 +31,12 @@ void JniUtils::initialize(JavaVM* vm, jint vm_version) noexcept s_instance = std::unique_ptr(new JniUtils(vm, vm_version)); } +void JniUtils::release() +{ + REALM_ASSERT_DEBUG(s_instance); + s_instance.release(); +} + JNIEnv* JniUtils::get_env(bool attach_if_needed) { REALM_ASSERT_DEBUG(s_instance); @@ -53,3 +59,9 @@ void JniUtils::detach_current_thread() { s_instance->m_vm->DetachCurrentThread(); } + +void JniUtils::keep_global_ref(JavaGlobalRef& ref) +{ + s_instance->m_global_refs.push_back(std::move(ref)); +} + diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp index 7d2bcf4b9d..689aa1232c 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp @@ -19,6 +19,10 @@ #include +#include + +#include "java_global_ref.hpp" + namespace realm { namespace jni_util { @@ -31,12 +35,16 @@ class JniUtils { // Call this only once in JNI_OnLoad. static void initialize(JavaVM* vm, jint vm_version) noexcept; + // Call this in JNI_OnUnload. + static void release(); // When attach_if_needed is false, returns the JNIEnv if there is one attached to this thread. Assert if there is // none. When attach_if_needed is true, try to attach and return a JNIEnv if necessary. static JNIEnv* get_env(bool attach_if_needed = false); // Detach the current thread from the JVM. Only required for C++ threads that where attached in the first place. // Failing to do so is a resource leak. static void detach_current_thread(); + // Keep the given global reference until JNI_OnUnload is called. + static void keep_global_ref(JavaGlobalRef& ref); private: JniUtils(JavaVM* vm, jint vm_version) noexcept @@ -47,6 +55,7 @@ class JniUtils { JavaVM* m_vm; jint m_vm_version; + std::vector m_global_refs; }; } // namespace realm From d79de2d4b0b062c7dd922a7347a80899616e83f5 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 24 Mar 2017 18:36:52 +0800 Subject: [PATCH 0574/2110] Javadoc for findFirstAsync (#4367) Fix #4360 --- .../src/main/java/io/realm/RealmQuery.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 38056e37c8..1cdb37a1a2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1915,15 +1915,14 @@ public E findFirst() { } /** - * Similar to {@link #findFirst()} but runs asynchronously on a worker thread - * This method is only available from a Looper thread. - * - * @return immediately an empty {@link RealmObject}. Trying to access any field on the returned object - * before it is loaded will throw an {@code IllegalStateException}. Use {@link RealmObject#isLoaded()} to check if - * the object is fully loaded or register a listener {@link io.realm.RealmObject#addChangeListener} - * to be notified when the query completes. If no RealmObject was found after the query completed, the returned - * RealmObject will have {@link RealmObject#isLoaded()} set to {@code true} and {@link RealmObject#isValid()} set to - * {@code false}. + * Similar to {@link #findFirst()} but runs asynchronously on a worker thread. An listener should be registered to + * the returned {@link RealmObject} to get the notification when query completes. The registered listener will also + * be triggered if there are changes made to the queried {@link RealmObject}. If the {@link RealmObject} is deleted, + * the listener will be called one last time and then stop. The query will not be re-run. + * + * @return immediately an empty {@link RealmObject} with {@code isLoaded() == false}. Trying to access any field on + * the returned object before it is loaded will throw an {@code IllegalStateException}. + * @throws IllegalStateException if this is called on a non-looper thread. */ public E findFirstAsync() { realm.checkIfValid(); From 4ed4cd0382bf70b4907fbc7ca6ccd2a142211521 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Fri, 24 Mar 2017 11:53:35 -0700 Subject: [PATCH 0575/2110] Enable checkstyle (#4373) Not currently failing on errors --- realm/config/checkstyle/checkstyle.xml | 52 +++++++++++++++++--------- realm/realm-library/build.gradle | 27 ++++++++----- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/realm/config/checkstyle/checkstyle.xml b/realm/config/checkstyle/checkstyle.xml index 3926b73b72..1dada765c3 100644 --- a/realm/config/checkstyle/checkstyle.xml +++ b/realm/config/checkstyle/checkstyle.xml @@ -22,7 +22,8 @@ - --> + + --> @@ -38,14 +39,14 @@ - + + --> @@ -59,7 +60,8 @@ - --> + + --> @@ -72,13 +74,14 @@ + + - - + + --> @@ -92,7 +95,8 @@ - --> + + --> @@ -100,11 +104,13 @@ - + + + + @@ -112,25 +118,29 @@ + + + + --> + @@ -140,15 +150,21 @@ - --> - + + --> + + + + - - + + diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 7041fd0919..3e1b2dbc2b 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -236,23 +236,32 @@ task pmd(type: Pmd) { } } +// Configure Checkstyle +// Android sourceSets are not sourceSets, so we can't confgure this with the DSL. task checkstyle(type: Checkstyle) { - group = 'Test' + group = 'Verification' source 'src' - include '**/*.java' - exclude '**/gen/**' - exclude '**/R.java' - exclude '**/BuildConfig.java' + include '*/java/**/*.java' + exclude 'benchmarks/**' + // Ingore tests for now. + exclude '*Test*/**' - def configProps = ['proj.module.dir': projectDir.absolutePath] - configProperties configProps + // empty classpath + classpath = files() +} + +checkstyle { + toolVersion ="7.6" configFile = file("${projectDir}/../config/checkstyle/checkstyle.xml") - // empty classpath - classpath = files() + def configProps = ['proj.module.dir': projectDir.absolutePath] + configProperties configProps + + ignoreFailures = true } +check.dependsOn tasks.checkstyle // Configuration options can be found here: // http://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html From 7ac0a905a017bd2d773db4dcbd3ef2c408c26324 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 27 Mar 2017 09:35:58 +0800 Subject: [PATCH 0576/2110] Trigger collection listeners when commit (#4368) - The listeners on RealmList/RealmResults will be tiggered immediately when the transaction committed on the same thread if the collections are changed or the async query should return. - Listeners on the RealmObject will have same behaviour after #4331 merged. This is to solve the problem for predictive UI animations and local transactions. Fix #4245 --- CHANGELOG.md | 1 + .../java/io/realm/RealmListTests.java | 27 +++++++++++-------- .../io/realm/internal/CollectionTests.java | 10 ++++--- .../cpp/io_realm_internal_SharedRealm.cpp | 6 +++++ 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67fdd9e1d4..edef77f589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Enhancements * Now `targetSdkVersion` is 25. +* Listeners on `RealmList` and `RealmResults` will be triggered immediately when the transaction is committed on the same thread (#4245). ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 78fdc0e8df..0e195f1343 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -1053,17 +1053,24 @@ public void onChange(RealmList collection, OrderedCollectionChangeSet chang fail(); } }); - realm.beginTransaction(); - collection.get(0).setAge(42); - realm.commitTransaction(); collection.removeAllChangeListeners(); + // This one is added after removal, so it should be triggered. + collection.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + listenerCalledCount.incrementAndGet(); + looperThread.testComplete(); + } + }); + // This should trigger the listener if there is any. realm.beginTransaction(); - realm.cancelTransaction(); - assertEquals(0, listenerCalledCount.get()); - looperThread.testComplete(); + collection.get(0).setAge(42); + realm.commitTransaction(); + + assertEquals(1, listenerCalledCount.get()); } @Test @@ -1083,21 +1090,19 @@ public void onChange(RealmList element) { @Override public void onChange(RealmList collection, OrderedCollectionChangeSet changes) { assertEquals(0, listenerCalledCount.getAndIncrement()); + looperThread.testComplete(); } }; collection.addChangeListener(listener1); collection.addChangeListener(listener2); - realm.beginTransaction(); - collection.get(0).setAge(42); - realm.commitTransaction(); collection.removeChangeListener(listener1); // This should trigger the listener if there is any. realm.beginTransaction(); - realm.cancelTransaction(); + collection.get(0).setAge(42); + realm.commitTransaction(); assertEquals(1, listenerCalledCount.get()); - looperThread.testComplete(); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index e63fd0fb1a..fa33ca5ad9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -378,7 +378,8 @@ public void onChange(Collection collection1) { addRowAsync(); } - // Local commit will trigger the listener first when beginTransaction gets called then again in the next event loop. + // Local commit will trigger the listener first when beginTransaction gets called then again when transaction + // committed. @Test @RunTestInLooperThread public void addListener_triggeredByLocalCommit() { @@ -398,13 +399,16 @@ public void onChange(Collection collection1) { case 1: assertEquals(collection1.size(), 5); sharedRealm.close(); - looperThread.testComplete(); + break; + default: + fail(); break; } } }); addRow(sharedRealm); - assertEquals(collection.size(), 5); + assertEquals(2, listenerCounter.get()); + looperThread.testComplete(); } private static class TestIterator extends Collection.Iterator { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index ccfa3aa222..b15798df00 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -146,6 +146,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCommitTransactio auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->commit_transaction(); + // Realm could be closed in the RealmNotifier.didChange(). + if (!shared_realm->is_closed()) { + // To trigger async queries, so the UI can be refreshed immediately to avoid inconsistency. + // See more discussion on https://github.com/realm/realm-java/issues/4245 + shared_realm->refresh(); + } } CATCH_STD() } From 829ece76843723dd37868c9485d3604e7afcdafc Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 27 Mar 2017 11:17:34 +0800 Subject: [PATCH 0577/2110] Fix date in changelog Problem was found by xiaolongyuan. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edef77f589..be1d551273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,7 +124,7 @@ * Updated to Realm Sync v1.0.0. * Added a Realm backup when receiving a Sync client reset message from the server. -## 2.2.2 (2016-01-16) +## 2.2.2 (2017-01-16) ### Object Server API Changes (In Beta) From 23a34cc4736cd525341345b62c7029f707d96d85 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 29 Mar 2017 11:01:21 +0800 Subject: [PATCH 0578/2110] Remove useless code These two line are supposed to be removed in #4365. --- realm/realm-library/src/main/cpp/util.cpp | 3 --- realm/realm-library/src/main/cpp/util.hpp | 3 --- 2 files changed, 6 deletions(-) diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index a2f4516ede..75e2598b12 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -43,9 +43,6 @@ jclass java_lang_string; jmethodID java_lang_double_init; jclass java_util_date; jmethodID java_util_date_init; -#if REALM_ENABLE_SYNC -jclass java_syncmanager; -#endif void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 76378a8e42..c0ec632f62 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -703,9 +703,6 @@ extern jclass java_lang_string; extern jmethodID java_lang_double_init; extern jclass java_util_date; extern jmethodID java_util_date_init; -#if REALM_ENABLE_SYNC -extern jclass java_syncmanager; -#endif inline jobject NewLong(JNIEnv* env, int64_t value) { From 186e9e607b38641fd8aa161dd8605774c1956729 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 29 Mar 2017 21:32:12 +0800 Subject: [PATCH 0579/2110] Set log level for core logger bridge (#4389) Set the all core logger bridges log level with the global log level. Fix #4337 --- CHANGELOG.md | 1 + .../io/realm/{ => log}/RealmLogTests.java | 28 ++++++++-- .../src/main/cpp/io_realm_SyncManager.cpp | 4 +- .../src/main/cpp/io_realm_log_RealmLog.cpp | 19 +++++++ .../src/main/cpp/jni_util/log.cpp | 52 +++++++++++++++++++ .../src/main/cpp/jni_util/log.hpp | 17 +++++- .../src/main/java/io/realm/log/RealmLog.java | 7 +++ 7 files changed, 120 insertions(+), 8 deletions(-) rename realm/realm-library/src/androidTest/java/io/realm/{ => log}/RealmLogTests.java (74%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 246247198a..6367c7091b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ * Fixed a potential cause for Realm file corruptions (never reported). * Add `@Override` annotation to proxy class accessors and stop using raw type in proxy classes in order to remove warnings from javac (#4329). * `findFirstAsync()` now returns an invalid object if there is no object matches the query condition instead of running the query repeatedly until it can find one (#4352). +* [ObjectServer] Changing the log level after starting a session now works correctly (#4337). ### Deprecated diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java b/realm/realm-library/src/androidTest/java/io/realm/log/RealmLogTests.java similarity index 74% rename from realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java rename to realm/realm-library/src/androidTest/java/io/realm/log/RealmLogTests.java index c10ebbbb5b..8584214176 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmLogTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/log/RealmLogTests.java @@ -1,4 +1,4 @@ -package io.realm; +package io.realm.log; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; @@ -7,10 +7,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; +import io.realm.Realm; +import io.realm.TestHelper; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; @@ -27,10 +28,8 @@ public void add_remove() { TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); RealmLog.add(testLogger); RealmLog.fatal("TEST"); - assertEquals("TEST", testLogger.message); RealmLog.remove(testLogger); RealmLog.fatal("TEST_AGAIN"); - assertEquals("TEST", testLogger.message); } @Test @@ -91,4 +90,23 @@ public void throwable_passedToTheJavaLogger() { assertTrue(testLogger.message.contains("RealmLogTests.java")); RealmLog.remove(testLogger); } + + @Test + public void coreLoggerBridge() { + TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); + RealmLog.setLevel(LogLevel.INFO); + RealmLog.add(testLogger); + + long ptr = RealmLog.nativeCreateCoreLoggerBridge("TEST"); + RealmLog.nativeLogToCoreLoggerBridge(ptr, LogLevel.INFO, "42"); + assertTrue(testLogger.message.equals("42")); + + RealmLog.setLevel(LogLevel.FATAL); + RealmLog.nativeLogToCoreLoggerBridge(ptr, LogLevel.INFO, "44"); + assertTrue(testLogger.message.equals("42")); + assertFalse(testLogger.message.equals("44")); + RealmLog.nativeLogToCoreLoggerBridge(ptr, LogLevel.FATAL, "45"); + assertTrue(testLogger.message.equals("45")); + RealmLog.nativeCloseCoreLoggerBridge(ptr); + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index bb9d1dab0d..2e80fe147e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -47,10 +47,10 @@ struct AndroidClientListener : public realm::BindingCallbackThreadObserver { } s_client_thread_listener; struct AndroidSyncLoggerFactory : public realm::SyncLoggerFactory { - std::unique_ptr make_logger(Logger::Level level) override + // The level param is ignored. Use the global RealmLog.setLevel() to control all log levels. + std::unique_ptr make_logger(Logger::Level) override { auto logger = std::make_unique(std::string("REALM_SYNC")); - logger->set_level_threshold(level); // Cast to std::unique_ptr return std::move(logger); } diff --git a/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp b/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp index d18455b97b..4c6a16a095 100644 --- a/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_log_RealmLog.cpp @@ -82,3 +82,22 @@ JNIEXPORT jint JNICALL Java_io_realm_log_RealmLog_nativeGetLogLevel(JNIEnv* env, return static_cast(Log::Level::all); } + +// Methods for testing only. +JNIEXPORT jlong JNICALL Java_io_realm_log_RealmLog_nativeCreateCoreLoggerBridge(JNIEnv* env, jclass, jstring tag) +{ + return reinterpret_cast(new CoreLoggerBridge(JStringAccessor(env, tag))); +} + +JNIEXPORT void JNICALL Java_io_realm_log_RealmLog_nativeCloseCoreLoggerBridge(JNIEnv*, jclass, jlong native_ptr) +{ + delete reinterpret_cast(native_ptr); +} + +JNIEXPORT void JNICALL Java_io_realm_log_RealmLog_nativeLogToCoreLoggerBridge(JNIEnv* env, jclass, jlong native_ptr, + jint level, jstring msg) +{ + CoreLoggerBridge* bridge = reinterpret_cast(native_ptr); + std::string message = JStringAccessor(env, msg); + bridge->log(Log::convert_to_core_log_level(static_cast(level)), message.c_str()); +} diff --git a/realm/realm-library/src/main/cpp/jni_util/log.cpp b/realm/realm-library/src/main/cpp/jni_util/log.cpp index d8b804549d..c90a4d5e25 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.cpp @@ -16,6 +16,8 @@ #include +#include + #include "jni_util/log.hpp" using namespace realm; @@ -24,6 +26,8 @@ using namespace realm::util; const char* Log::REALM_JNI_TAG = "REALM_JNI"; Log::Level Log::s_level = Log::Level::warn; +std::vector CoreLoggerBridge::s_bridges; +std::mutex CoreLoggerBridge::s_mutex; // Native wrapper for Java RealmLogger class class JavaLogger : public JniLogger { @@ -157,6 +161,7 @@ void Log::clear_loggers() void Log::set_level(Level level) { s_level = level; + CoreLoggerBridge::set_levels(level); } void Log::log(Level level, const char* tag, jthrowable throwable, const char* message) @@ -169,6 +174,53 @@ void Log::log(Level level, const char* tag, jthrowable throwable, const char* me } } +realm::util::RootLogger::Level Log::convert_to_core_log_level(Level level) +{ + switch (level) { + case Log::trace: + return RootLogger::Level::trace; + case Log::debug: + return RootLogger::Level::debug; + case Log::info: + return RootLogger::Level::info; + case Log::warn: + return RootLogger::Level::warn; + case Log::error: + return RootLogger::Level::error; + case Log::fatal: + return RootLogger::Level::fatal; + case Log::all: + return RootLogger::Level::all; + case Log::off: + return RootLogger::Level::off; + default: + break; + } + REALM_UNREACHABLE(); +} + +CoreLoggerBridge::CoreLoggerBridge(std::string tag) + : m_tag(std::move(tag)) +{ + std::lock_guard lock(s_mutex); + s_bridges.push_back(this); + set_level_threshold(Log::convert_to_core_log_level(Log::shared().get_level())); +} + +CoreLoggerBridge::~CoreLoggerBridge() +{ + std::lock_guard lock(s_mutex); + s_bridges.erase(std::remove(s_bridges.begin(), s_bridges.end(), this), s_bridges.end()); +} + +void CoreLoggerBridge::set_levels(Log::Level level) +{ + std::lock_guard lock(s_mutex); + for (auto bridge : s_bridges) { + bridge->set_level_threshold(Log::convert_to_core_log_level(level)); + } +} + void CoreLoggerBridge::do_log(realm::util::Logger::Level level, std::string msg) { // Ignore the level threshold from the root logger. diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp index d138f287d2..98cc58d1c9 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -142,6 +142,8 @@ class Log { shared().log(fatal, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); } + static realm::util::RootLogger::Level convert_to_core_log_level(Level level); + // Get the shared Log instance. static Log& shared(); @@ -178,13 +180,26 @@ class JniLogger { // Implement this function to return the default logger which will be registered during initialization. extern std::shared_ptr get_default_logger(); +// Do NOT call set_level_threshold on the bridge to set the log level. Instead, call the Log::set_level which will +// set all logger levels. class CoreLoggerBridge : public realm::util::RootLogger { public: - CoreLoggerBridge(std::string tag) : m_tag(std::move(tag)) {} + CoreLoggerBridge(std::string tag); + ~CoreLoggerBridge(); + CoreLoggerBridge(CoreLoggerBridge&&) = delete; + CoreLoggerBridge(CoreLoggerBridge&) = delete; + CoreLoggerBridge operator=(CoreLoggerBridge&&) = delete; + CoreLoggerBridge operator=(CoreLoggerBridge&) = delete; void do_log(Logger::Level, std::string msg) override; private: + // Set log level for all logger bridges. + static void set_levels(Log::Level level); + friend class Log; + const std::string m_tag; + static std::vector s_bridges; + static std::mutex s_mutex; }; } // namespace jni_util diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index 984a06f2c0..e24211efca 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -298,4 +298,11 @@ private static void log(int level, Throwable throwable, String message, Object.. private static native void nativeSetLogLevel(int level); private static native int nativeGetLogLevel(); + + // Methods below are used for testing core logger bridge only. + static native long nativeCreateCoreLoggerBridge(String tag); + + static native void nativeCloseCoreLoggerBridge(long nativePtr); + + static native void nativeLogToCoreLoggerBridge(long nativePtr, int level, String message); } From f182c8673e106e89dd8711988362bf2e5c5d92ce Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 29 Mar 2017 15:35:18 +0200 Subject: [PATCH 0580/2110] Automatically append `/auth` to authentication URL if needed (#4395) --- CHANGELOG.md | 1 + .../java/io/realm/SyncUserTests.java | 32 +++++++++++++++++++ .../objectServer/java/io/realm/SyncUser.java | 6 +++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6367c7091b..46411e8f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * Migration for linking objects is not yet supported. * Backlink verification is incomplete. Evil code can cause native crashes. * [ObjectServer] In case of a Client Reset, information about the location of the backed up Realm file is now reported through the `ErrorHandler` interface (#4080). +* [ObjectServer] Authentication URLs now automatically append `/auth` if no other path segment is set (#4370). ### Bug Fixes diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 3e2c69544c..0f4fa8b0b9 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -36,6 +36,7 @@ import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.network.AuthenticationServer; +import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.util.SyncTestUtils; @@ -213,4 +214,35 @@ public void login_withAccessToken() { SyncManager.setAuthServerImpl(originalServer); } } + + // Checks that `/auth` is correctly added to any URL without a path + @Test + public void login_appendAuthSegment() { + AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + AuthenticationServer originalServer = SyncManager.getAuthServer(); + SyncManager.setAuthServerImpl(authServer); + String[][] urls = { + {"http://ros.realm.io", "http://ros.realm.io/auth"}, + {"http://ros.realm.io:8080", "http://ros.realm.io:8080/auth"}, + {"http://ros.realm.io/", "http://ros.realm.io/"}, + {"http://ros.realm.io/?foo=bar", "http://ros.realm.io/?foo=bar"}, + {"http://ros.realm.io/auth", "http://ros.realm.io/auth"}, + {"http://ros.realm.io/auth/", "http://ros.realm.io/auth/"}, + {"http://ros.realm.io/custom-path/", "http://ros.realm.io/custom-path/"} + }; + + try { + for (String[] url : urls) { + RealmLog.error(url[0]); + String input = url[0]; + String normalizedInput = url[1]; + SyncCredentials credentials = SyncCredentials.accessToken("token", UUID.randomUUID().toString()); + SyncUser user = SyncUser.login(credentials, input); + assertEquals(normalizedInput, user.getAuthenticationUrl().toString()); + user.logout(); + } + } finally { + SyncManager.setAuthServerImpl(originalServer); + } + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 899411cd70..734ad92d99 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -172,9 +172,13 @@ public static SyncUser fromJson(String user) { * @throws IllegalArgumentException if the URL is malformed. */ public static SyncUser login(final SyncCredentials credentials, final String authenticationUrl) throws ObjectServerError { - final URL authUrl; + URL authUrl; try { authUrl = new URL(authenticationUrl); + // If no path segment is provided append `/auth` which is the standard location. + if (authUrl.getPath().equals("")) { + authUrl = new URL(authUrl.toString() + "/auth"); + } } catch (MalformedURLException e) { throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e); } From 6361326acceab4e5f6c6d710202cece91e1da31c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 30 Mar 2017 10:59:32 +0800 Subject: [PATCH 0581/2110] Add detailed notification for RealmObject (#4331) See #4101 - Add ObjectChangeSet & RealmObjectChangeListener. - Add OsObject to wrap ObjectStore's Object for notifications. - No more false positive notifications for RealmObject. - Use ObserverPairList in ProxyState instead of normal list to solve the potential listener removal problems which is handled well by the ObserverPairList. - Fix tests. --- CHANGELOG.md | 2 + .../io/realm/annotations/LinkingObjects.java | 4 +- .../io/realm/LinkingObjectsManagedTests.java | 170 --------- .../java/io/realm/NotificationsTest.java | 23 -- .../java/io/realm/ObjectChangeSetTests.java | 340 ++++++++++++++++++ .../java/io/realm/RealmAsyncQueryTests.java | 14 +- .../java/io/realm/RealmObjectTests.java | 186 +++++++--- .../io/realm/TypeBasedNotificationsTests.java | 156 ++------ .../io/realm/internal/PrimaryKeyTests.java | 2 +- .../realm-library/src/main/cpp/CMakeLists.txt | 1 + .../main/cpp/io_realm_internal_OsObject.cpp | 203 +++++++++++ realm/realm-library/src/main/cpp/util.cpp | 3 + .../src/main/java/io/realm/BaseRealm.java | 9 - .../main/java/io/realm/ObjectChangeSet.java | 45 +++ .../src/main/java/io/realm/ProxyState.java | 130 ++++--- .../src/main/java/io/realm/RealmObject.java | 72 +++- .../io/realm/RealmObjectChangeListener.java | 58 +++ .../io/realm/internal/ObserverPairList.java | 8 +- .../main/java/io/realm/internal/OsObject.java | 157 ++++++++ .../java/io/realm/internal/PendingRow.java | 65 ++-- .../java/io/realm/internal/SharedRealm.java | 35 +- 21 files changed, 1210 insertions(+), 473 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp create mode 100644 realm/realm-library/src/main/java/io/realm/ObjectChangeSet.java create mode 100644 realm/realm-library/src/main/java/io/realm/RealmObjectChangeListener.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/OsObject.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 46411e8f0d..6f9cdce124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ * Backlink verification is incomplete. Evil code can cause native crashes. * [ObjectServer] In case of a Client Reset, information about the location of the backed up Realm file is now reported through the `ErrorHandler` interface (#4080). * [ObjectServer] Authentication URLs now automatically append `/auth` if no other path segment is set (#4370). +* The listener on `RealmObject` will only be triggered if the object changes (#3894). +* Added `RealmObjectChangeListener` to get detailed information about `RealmObject` changes. ### Bug Fixes diff --git a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java index 4c79b44550..f9b130d0ca 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java +++ b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java @@ -44,7 +44,7 @@ * } *
                    * In the above example `Person` is related to `Dog` through the field `dog`. - * This in turn means that an implict reverse relationship exists between the class `Dog` + * This in turn means that an implicit reverse relationship exists between the class `Dog` * and the class `Person`. This inverse relationship is made public and queryable by the `RealmResults` * field annotated with `@LinkingObject`. This makes it possible to query properties of the dogs owner * without having to manually maintain a "owner" field in the `Dog` class. @@ -57,6 +57,8 @@ *
                  • They are ignored when doing a `copyToRealm().`
                  • *
                  • They are ignored when doing a `copyFromRealm().`
                  • *
                  • They are ignored when using the various `createObjectFromJson*` and `createAllFromJson*` methods.
                  • + *
                  • Listeners on an object with a `@LinkingObject` field will not be triggered if the linking objects change, + * e.g: if another object drops a reference to this object.
                  • * *

                    * In addition, they have the following restrictions: diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 302c3952e8..9c75ee8629 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -151,44 +151,6 @@ public void basic_multipleReferencesFromParentList() { assertEquals(parent, child.getListParents().last()); } - // A listener registered on the backlinked object should be called when a commit adds a backlink - @Test - @RunTestInLooperThread - public void notification_onCommitModelObject() { - final Realm looperThreadRealm = looperThread.realm; - - looperThreadRealm.beginTransaction(); - AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); - looperThreadRealm.commitTransaction(); - - final AtomicInteger counter = new AtomicInteger(0); - RealmChangeListener listener = new RealmChangeListener() { - @Override - public void onChange(AllJavaTypes object) { - counter.incrementAndGet(); - } - }; - child.addChangeListener(listener); - - looperThreadRealm.beginTransaction(); - AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); - parent.setFieldObject(child); - looperThreadRealm.commitTransaction(); - - verifyPostConditions( - looperThreadRealm, - new PostConditions() { - @Override - public void run(Realm realm) { - assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); - assertEquals(1, counter.get()); - } - }, - child, parent); - } - - - // A listener registered on the backlinked object should not be called after the listener is removed @Test @RunTestInLooperThread @@ -224,81 +186,6 @@ public void run(Realm realm) { child, parent); } - // A listener registered on the backlinked object should be called when a backlinked object is deleted - @Test - @RunTestInLooperThread - public void notification_onDeleteModelObject() { - final Realm looperThreadRealm = looperThread.realm; - - looperThreadRealm.beginTransaction(); - AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); - AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); - parent.setFieldObject(child); - looperThreadRealm.commitTransaction(); - - final AtomicInteger counter = new AtomicInteger(0); - RealmChangeListener listener = new RealmChangeListener() { - @Override - public void onChange(AllJavaTypes object) { - counter.incrementAndGet(); - } - }; - child.addChangeListener(listener); - - looperThreadRealm.beginTransaction(); - looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll().deleteAllFromRealm(); - looperThreadRealm.commitTransaction(); - - verifyPostConditions( - looperThreadRealm, - new PostConditions() { - @Override - public void run(Realm realm) { - assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); - assertEquals(1, counter.get()); - } - }, - child, parent); - } - - // A listener registered on the backlinked object is called - // for an unrelated change on the an object of the same type!! - // This test exists only to document existing (but odd) behavior. - @Test - @RunTestInLooperThread - public void notification_notSentOnUnrelatedChangeModelObject() { - final Realm looperThreadRealm = looperThread.realm; - - looperThreadRealm.beginTransaction(); - AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); - AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 1); - looperThreadRealm.commitTransaction(); - - final AtomicInteger counter = new AtomicInteger(0); - RealmChangeListener listener = new RealmChangeListener() { - @Override - public void onChange(AllJavaTypes object) { - counter.incrementAndGet(); - } - }; - child.addChangeListener(listener); - - looperThreadRealm.beginTransaction(); - looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll().deleteAllFromRealm(); - looperThreadRealm.commitTransaction(); - - verifyPostConditions( - looperThreadRealm, - new PostConditions() { - @Override - public void run(Realm realm) { - assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); - assertEquals(1, counter.get()); - } - }, - child, parent); - } - // A listener registered on the backlinked field should be called when a commit adds a backlink @Test @RunTestInLooperThread @@ -498,63 +385,6 @@ public void json_updateList() { assertTrue(parents.contains(parent)); } - // A JSON update should generate a notifcation - @Test - @RunTestInLooperThread - public void json_jsonUpdateCausesNotification() { - final Realm looperThreadRealm = looperThread.realm; - - looperThreadRealm.beginTransaction(); - AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 1); - AllJavaTypes parent = looperThreadRealm.createObject(AllJavaTypes.class, 2); - parent.setFieldObject(child); - looperThreadRealm.commitTransaction(); - - RealmResults results = looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll(); - assertNotNull(results); - assertEquals(results.size(), 1); - child = results.first(); - - RealmResults parents = child.getObjectParents(); - assertNotNull(parents); - assertEquals(1, parents.size()); - - final AtomicInteger counter = new AtomicInteger(0); - RealmChangeListener listener = new RealmChangeListener() { - @Override - public void onChange(AllJavaTypes object) { - counter.incrementAndGet(); - } - }; - child.addChangeListener(listener); - - looperThreadRealm.beginTransaction(); - try { - looperThreadRealm.createOrUpdateAllFromJson(AllJavaTypes.class, "[{ \"fieldId\" : 2, \"fieldObject\" : null }]"); - } catch (RealmException e) { - fail("Failed loading JSON" + e); - } - looperThreadRealm.commitTransaction(); - - verifyPostConditions( - looperThreadRealm, - new PostConditions() { - @Override - public void run(Realm realm) { - RealmResults results = looperThreadRealm.where(AllJavaTypes.class).equalTo("fieldId", 1).findAll(); - assertNotNull(results); - assertEquals(results.size(), 1); - AllJavaTypes child = results.first(); - - RealmResults parents = child.getObjectParents(); - assertNotNull(parents); - assertEquals(0, parents.size()); - assertEquals(1, counter.get()); - } - }, - child, parent); - } - /** * Table validation should fail if the backinked column already exists in the target table. * The realm `backlinks-fieldInUse.realm` contains the classes `BacklinksSource` and `BacklinksTarget` diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 8e3f499af6..6bd270bab7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -28,7 +28,6 @@ import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -799,28 +798,6 @@ public void run() { }); } - // TODO: Fix or delete this test after integration of object notification from Object Store - @Test - @RunTestInLooperThread - @Ignore - public void realmObjectListenerAddedAfterCommit() { - Realm realm = looperThread.realm; - realm.beginTransaction(); - AllTypes obj = realm.createObject(AllTypes.class); - realm.commitTransaction(); - - realm.beginTransaction(); - obj.setColumnLong(42); - realm.commitTransaction(); - - obj.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(AllTypes object) { - looperThread.testComplete(); - } - }); - } - public static class PopulateOneAllTypes implements RunInLooperThread.RunnableBefore { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java new file mode 100644 index 0000000000..942e7cb848 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java @@ -0,0 +1,340 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import io.realm.entities.AllTypes; +import io.realm.entities.Dog; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.fail; +import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + + +// Tests for detailed change notification on RealmObject. +@RunWith(AndroidJUnit4.class) +public class ObjectChangeSetTests { + + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + public static class PopulateOneAllTypes implements RunInLooperThread.RunnableBefore { + + @Override + public void run(RealmConfiguration realmConfig) { + Realm realm = Realm.getInstance(realmConfig); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + AllTypes allTypes = realm.createObject(AllTypes.class); + allTypes.setColumnRealmObject(realm.createObject(Dog.class)); + allTypes.getColumnRealmList().add(realm.createObject(Dog.class)); + } + }); + realm.close(); + } + } + + private void checkDeleted(AllTypes allTypes) { + allTypes.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(AllTypes object, ObjectChangeSet changeSet) { + assertEquals(0, changeSet.getChangedFields().length); + assertFalse(object.isValid()); + assertTrue(changeSet.isDeleted()); + looperThread.testComplete(); + } + }); + looperThread.keepStrongReference.add(allTypes); + } + + private void checkChangedField(AllTypes allTypes, final String... fieldNames) { + assertNotNull(fieldNames); + allTypes.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(RealmModel object, ObjectChangeSet changeSet) { + assertEquals(fieldNames.length, changeSet.getChangedFields().length); + List changedFields = Arrays.asList(changeSet.getChangedFields()); + for (String name : fieldNames) { + assertTrue(changeSet.isFieldChanged(name)); + assertFalse(changeSet.isFieldChanged(name + "NotThere")); + if (!changedFields.contains(name)) { + fail("Cannot find field " + name + " in field changes."); + } + } + looperThread.testComplete(); + } + }); + looperThread.keepStrongReference.add(allTypes); + } + + private void listenerShouldNotBeCalled(AllTypes allTypes) { + allTypes.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(RealmModel object, ObjectChangeSet changeSet) { + fail(); + } + }); + looperThread.postRunnableDelayed(new Runnable() { + @Override + public void run() { + looperThread.testComplete(); + } + }, 100); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void objectDeleted() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkDeleted(allTypes); + realm.beginTransaction(); + allTypes.deleteFromRealm(); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeLongField() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_LONG); + realm.beginTransaction(); + allTypes.setColumnLong(42); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeStringField() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_STRING); + realm.beginTransaction(); + allTypes.setColumnString("42"); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeFloatField() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_FLOAT); + realm.beginTransaction(); + allTypes.setColumnFloat(42.0f); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeDoubleField() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_DOUBLE); + realm.beginTransaction(); + allTypes.setColumnDouble(42.0d); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeBooleanField() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_BOOLEAN); + realm.beginTransaction(); + allTypes.setColumnBoolean(true); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeDateField() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_DATE); + realm.beginTransaction(); + allTypes.setColumnDate(new Date()); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeBinaryField() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_BINARY); + realm.beginTransaction(); + allTypes.setColumnBinary(new byte[] { 42 }); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeLinkFieldSetNewObject() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_REALMOBJECT); + realm.beginTransaction(); + allTypes.setColumnRealmObject(realm.createObject(Dog.class)); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeLinkFieldSetNull() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_REALMOBJECT); + realm.beginTransaction(); + allTypes.setColumnRealmObject(null); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeLinkFieldRemoveObject() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_REALMOBJECT); + realm.beginTransaction(); + allTypes.getColumnRealmObject().deleteFromRealm(); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeLinkFieldOriginalObjectChanged_notTrigger() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + listenerShouldNotBeCalled(allTypes); + realm.beginTransaction(); + allTypes.getColumnRealmObject().setAge(42); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeLinkListAddObject() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_REALMLIST); + realm.beginTransaction(); + allTypes.getColumnRealmList().add(realm.createObject(Dog.class)); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeLinkListClear() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_REALMLIST); + realm.beginTransaction(); + allTypes.getColumnRealmList().clear(); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeAllFields() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + checkChangedField(allTypes, AllTypes.FIELD_LONG, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_REALMOBJECT, + AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT, AllTypes.FIELD_STRING, AllTypes.FIELD_BOOLEAN, + AllTypes.FIELD_BINARY, AllTypes.FIELD_DATE); + realm.beginTransaction(); + allTypes.setColumnLong(42); + allTypes.getColumnRealmList().add(realm.createObject(Dog.class)); + allTypes.setColumnRealmObject(realm.createObject(Dog.class)); + allTypes.setColumnDouble(42.0d); + allTypes.setColumnFloat(42.0f); + allTypes.setColumnString("42"); + allTypes.setColumnBoolean(true); + allTypes.setColumnBinary(new byte[] { 42 }); + allTypes.setColumnDate(new Date()); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void findFirstAsync_changeSetIsNullWhenQueryReturns() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); + allTypes.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(AllTypes object, ObjectChangeSet changeSet) { + assertTrue(object.isValid()); + assertNull(changeSet); + looperThread.testComplete(); + } + }); + } + + // Due to the fact that Object Store disallow adding notification block inside a transaction, the pending query + // for findFirstAsync needs to be executed first then move the listener from collection to the object before begin + // transaction. + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void findFirstAsync_queryExecutedByLocalCommit() { + Realm realm = looperThread.realm; + final AtomicInteger listenerCounter = new AtomicInteger(0); + final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); + allTypes.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(AllTypes object, ObjectChangeSet changeSet) { + int counter = listenerCounter.getAndIncrement(); + switch (counter) { + case 0: + assertTrue(object.isValid()); + assertNull(changeSet); + break; + case 1: + assertFalse(object.isValid()); + assertTrue(changeSet.isDeleted()); + assertEquals(0, changeSet.getChangedFields().length); + looperThread.testComplete(); + break; + default: + fail(); + } + } + }); + realm.beginTransaction(); + allTypes.deleteFromRealm(); + realm.commitTransaction(); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index a9608250db..e7d7aa1dd0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -37,7 +37,6 @@ import io.realm.entities.Dog; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.Owner; -import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; @@ -628,10 +627,11 @@ public void onChange(AllTypes object) { } } - // Similar UC as #testForceLoadAsync using 'findFirst'. + // load should trigger the listener with empty change set. @Test @RunTestInLooperThread public void findFirstAsync_forceLoad() throws Throwable { + final AtomicBoolean listenerCalled = new AtomicBoolean(false); Realm Realm = looperThread.realm; populateTestRealm(Realm, 10); final AllTypes realmResults = Realm.where(AllTypes.class) @@ -640,10 +640,20 @@ public void findFirstAsync_forceLoad() throws Throwable { assertFalse(realmResults.isLoaded()); + realmResults.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(RealmModel object, ObjectChangeSet changeSet) { + assertNull(changeSet); + assertFalse(listenerCalled.get()); + listenerCalled.set(true); + } + }); + assertTrue(realmResults.load()); assertTrue(realmResults.isLoaded()); assertEquals("test data 4", realmResults.getColumnString()); + assertTrue(listenerCalled.get()); looperThread.testComplete(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 4df192b9a3..e5e80ec6b4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -16,13 +16,11 @@ package io.realm; -import android.support.test.annotation.UiThreadTest; import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -32,7 +30,6 @@ import java.util.Calendar; import java.util.Date; import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -67,6 +64,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + @RunWith(AndroidJUnit4.class) public class RealmObjectTests { @@ -1019,9 +1017,9 @@ public void get_set_nonNullValueOnNullableFields() { // 3 Boolean nullTypes.setFieldBooleanNull(true); // 4 Byte - nullTypes.setFieldByteNull((byte)42); + nullTypes.setFieldByteNull((byte) 42); // 5 Short - nullTypes.setFieldShortNull((short)42); + nullTypes.setFieldShortNull((short) 42); // 6 Integer nullTypes.setFieldIntegerNull(42); // 7 Long @@ -1042,9 +1040,9 @@ public void get_set_nonNullValueOnNullableFields() { // 3 Boolean assertTrue(nullTypes.getFieldBooleanNull()); // 4 Byte - assertEquals((byte)42, (byte)nullTypes.getFieldByteNull().intValue()); + assertEquals((byte) 42, (byte) nullTypes.getFieldByteNull().intValue()); // 5 Short - assertEquals((short)42, (short)nullTypes.getFieldShortNull().intValue()); + assertEquals((short) 42, (short) nullTypes.getFieldShortNull().intValue()); // 6 Integer assertEquals(42, nullTypes.getFieldIntegerNull().intValue()); // 7 Long @@ -1575,37 +1573,40 @@ public void addChangeListener_throwOnAddingNullListenerFromLooperThread() { Dog dog = createManagedDogObjectFromRealmInstance(realm); try { - dog.addChangeListener(null); + dog.addChangeListener((RealmChangeListener) null); + fail("adding null change listener must throw an exception."); + } catch (IllegalArgumentException ignore) { + } + + try { + dog.addChangeListener((RealmObjectChangeListener) null); fail("adding null change listener must throw an exception."); } catch (IllegalArgumentException ignore) { - } finally { - looperThread.testComplete(); } + + looperThread.testComplete(); } @Test public void addChangeListener_throwOnAddingNullListenerFromNonLooperThread() throws Throwable { - TestHelper.executeOnNonLooperThread(new TestHelper.Task() { - @Override - public void run() throws Exception { - final Realm realm = Realm.getInstance(realmConfig); - final Dog dog = createManagedDogObjectFromRealmInstance(realm); + final Dog dog = createManagedDogObjectFromRealmInstance(realm); - //noinspection TryFinallyCanBeTryWithResources - try { - dog.addChangeListener(null); - fail("adding null change listener must throw an exception."); - } catch (IllegalArgumentException ignore) { - } finally { - realm.close(); - } - } - }); + try { + dog.addChangeListener((RealmChangeListener) null); + fail("adding null change listener must throw an exception."); + } catch (IllegalArgumentException ignore) { + } + + try { + dog.addChangeListener((RealmObjectChangeListener) null); + fail("adding null change listener must throw an exception."); + } catch (IllegalArgumentException ignore) { + } } @Test @RunTestInLooperThread - public void changeListener_triggeredWhenObjectIsdeleted() { + public void changeListener_triggeredWhenObjectIsDeleted() { final Realm realm = looperThread.realm; realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); @@ -1637,9 +1638,51 @@ public void onChange(Dog object) { }); fail("adding change listener on unmanaged object must throw an exception."); } catch (IllegalArgumentException ignore) { - } finally { - looperThread.testComplete(); } + + try { + dog.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(Dog object, ObjectChangeSet changeSet) { + } + }); + fail("adding change listener on unmanaged object must throw an exception."); + } catch (IllegalArgumentException ignore) { + } + + looperThread.testComplete(); + } + + // Object Store will throw when adding change listener inside a transaction. + @Test + @RunTestInLooperThread + public void addChangeListener_throwInsiderTransaction() { + Realm realm = looperThread.realm; + + realm.beginTransaction(); + Dog dog = realm.createObject(Dog.class); + try { + dog.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(Dog element) { + fail(); + } + }); + } catch (IllegalStateException ignored) { + } + + try { + dog.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(Dog object, ObjectChangeSet changeSet) { + fail(); + } + }); + } catch (IllegalStateException ignored) { + } + realm.cancelTransaction(); + + looperThread.testComplete(); } @Test @@ -1649,32 +1692,61 @@ public void removeChangeListener_throwOnRemovingNullListenerFromLooperThread() { Dog dog = createManagedDogObjectFromRealmInstance(realm); try { - dog.removeChangeListener(null); + dog.removeChangeListener((RealmChangeListener) null); fail("removing null change listener must throw an exception."); } catch (IllegalArgumentException ignore) { - } finally { - looperThread.testComplete(); } + + try { + dog.removeChangeListener((RealmObjectChangeListener) null); + fail("removing null change listener must throw an exception."); + } catch (IllegalArgumentException ignore) { + } + + looperThread.testComplete(); } @Test public void removeChangeListener_throwOnRemovingNullListenerFromNonLooperThread() throws Throwable { - TestHelper.executeOnNonLooperThread(new TestHelper.Task() { - @Override - public void run() throws Exception { - final Realm realm = Realm.getInstance(realmConfig); - final Dog dog = createManagedDogObjectFromRealmInstance(realm); + final Dog dog = createManagedDogObjectFromRealmInstance(realm); - //noinspection TryFinallyCanBeTryWithResources - try { - dog.removeChangeListener(null); - fail("removing null change listener must throw an exception."); - } catch (IllegalArgumentException ignore) { - } finally { - realm.close(); - } + try { + dog.removeChangeListener((RealmChangeListener) null); + fail("removing null change listener must throw an exception."); + } catch (IllegalArgumentException ignore) { + } + + try { + dog.removeChangeListener((RealmObjectChangeListener) null); + fail("removing null change listener must throw an exception."); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + @RunTestInLooperThread + public void removeChangeListener_insideTransaction() { + Realm realm = looperThread.realm; + final Dog dog = createManagedDogObjectFromRealmInstance(realm); + RealmChangeListener realmChangeListener = new RealmChangeListener() { + @Override + public void onChange(Dog element) { } - }); + }; + RealmObjectChangeListener realmObjectChangeListener = new RealmObjectChangeListener() { + @Override + public void onChange(Dog object, ObjectChangeSet changeSet) { + } + }; + + dog.addChangeListener(realmChangeListener); + dog.addChangeListener(realmObjectChangeListener); + + realm.beginTransaction(); + dog.removeChangeListener(realmChangeListener); + dog.removeChangeListener(realmObjectChangeListener); + realm.cancelTransaction(); + looperThread.testComplete(); } /** @@ -1691,7 +1763,13 @@ public void removeAllChangeListeners() { dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { - assertTrue(false); + fail(); + } + }); + dog.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(Dog object, ObjectChangeSet changeSet) { + fail(); } }); dog.removeAllChangeListeners(); @@ -1700,6 +1778,8 @@ public void onChange(Dog object) { Dog sameDog = realm.where(Dog.class).equalTo(Dog.FIELD_AGE, 13).findFirst(); sameDog.setName("Jesper"); realm.commitTransaction(); + // Try to trigger the listeners. + realm.sharedRealm.refresh(); looperThread.testComplete(); } @@ -1712,13 +1792,25 @@ public void removeChangeListener_throwOnUnmanagedObject() { public void onChange(Dog object) { } }; + RealmObjectChangeListener objectChangeListener = new RealmObjectChangeListener() { + @Override + public void onChange(Dog object, ObjectChangeSet changeSet) { + } + }; try { dog.removeChangeListener(listener); fail("Failed to remove a listener from null Realm."); } catch (IllegalArgumentException ignore) { - looperThread.testComplete(); } + + try { + dog.removeChangeListener(objectChangeListener); + fail("Failed to remove a listener from null Realm."); + } catch (IllegalArgumentException ignore) { + } + + looperThread.testComplete(); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index e3150acf8c..73bf9731c5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -251,21 +251,6 @@ public void callback_should_trigger_for_createObjectFromJson() { assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); final Realm realm = looperThread.realm; - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - if (globalCommitInvocations.incrementAndGet() == 1) { - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(1, typebasedCommitInvocations.get()); - looperThread.testComplete(); - } - }); - } - } - }); - try { InputStream in = TestHelper.loadJsonFromAssets(InstrumentationRegistry.getTargetContext(), "all_simple_types.json"); realm.beginTransaction(); @@ -283,7 +268,7 @@ public void onChange(AllTypes object) { assertEquals(1.23D, objectFromJson.getColumnDouble(), 0D); assertEquals(true, objectFromJson.isColumnBoolean()); assertArrayEquals(new byte[]{1, 2, 3}, objectFromJson.getColumnBinary()); - typebasedCommitInvocations.incrementAndGet(); + looperThread.testComplete(); } }); @@ -301,20 +286,6 @@ public void onChange(AllTypes object) { @RunTestInLooperThread public void callback_should_trigger_for_createObjectFromJson_from_JSONObject() { final Realm realm = looperThread.realm; - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - if (globalCommitInvocations.incrementAndGet() == 1) { - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(1, typebasedCommitInvocations.get()); - looperThread.testComplete(); - } - }); - } - } - }); try { JSONObject json = new JSONObject(); @@ -339,7 +310,7 @@ public void onChange(AllTypes object) { assertEquals(1.23D, objectFromJson.getColumnDouble(), 0D); assertEquals(true, objectFromJson.isColumnBoolean()); assertArrayEquals(new byte[]{1, 2, 3}, objectFromJson.getColumnBinary()); - typebasedCommitInvocations.incrementAndGet(); + looperThread.testComplete(); } }); @@ -710,20 +681,6 @@ public void onChange(Dog object) { public void multiple_callbacks_should_be_invoked_realmobject_async() { final int NUMBER_OF_LISTENERS = 7; final Realm realm = looperThread.realm; - RealmChangeListener listener = new RealmChangeListener() { - @Override - public void onChange(Realm object) { - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(NUMBER_OF_LISTENERS, typebasedCommitInvocations.get()); - looperThread.testComplete(); - } - }); - } - }; - - realm.addChangeListener(listener); realm.beginTransaction(); Dog akamaru = realm.createObject(Dog.class); @@ -737,6 +694,17 @@ public void run() { @Override public void onChange(Dog object) { typebasedCommitInvocations.incrementAndGet(); + if (typebasedCommitInvocations.get() > NUMBER_OF_LISTENERS) { + fail(); + } else if (typebasedCommitInvocations.get() == NUMBER_OF_LISTENERS) { + // Delayed post in case the listener gets triggered more time than expected. + looperThread.postRunnableDelayed(new Runnable() { + @Override + public void run() { + looperThread.testComplete(); + } + }, 500); + } } }); } @@ -832,20 +800,6 @@ public void onChange(RealmResults object) { @RunTestInLooperThread public void non_looper_thread_commit_realmobject_sync() { final Realm realm = looperThread.realm; - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - if (realm.where(Dog.class).count() == 2) { - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(1, typebasedCommitInvocations.get()); - looperThread.testComplete(); - } - }); - } - } - }); realm.beginTransaction(); realm.createObject(Dog.class); @@ -856,92 +810,56 @@ public void run() { dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { - typebasedCommitInvocations.incrementAndGet(); + assertEquals(17, object.getAge()); + looperThread.testComplete(); } }); - Thread thread = new Thread() { + realm.executeTransactionAsync(new Realm.Transaction() { @Override - public void run() { - Realm bgRealm = Realm.getInstance(realm.getConfiguration()); - bgRealm.beginTransaction(); - bgRealm.createObject(Dog.class); - bgRealm.commitTransaction(); - bgRealm.close(); + public void execute(Realm realm) { + realm.where(Dog.class).findFirst().setAge(17); } - }; - thread.start(); - try { - thread.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } + }); } // UC 3 Async RealmObject. // 1. Creates RealmObject async query. - // 2. Waits COMPLETED_ASYNC_REALM_OBJECT then commits transaction in another non-looper thread. + // 2. Waits async returns then change the object. // 3. Listener on the RealmObject gets triggered again. @Test @RunTestInLooperThread public void non_looper_thread_commit_realmobject_async() { final Realm realm = looperThread.realm; - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - // Checks if the 2nd transaction is committed. - if (realm.where(Dog.class).count() == 2) { - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertEquals(2, typebasedCommitInvocations.get()); - looperThread.testComplete(); - } - }); - } - } - }); realm.beginTransaction(); - realm.createObject(Dog.class); + realm.createObject(Dog.class).setAge(1); realm.commitTransaction(); - final Thread thread = new Thread() { - @Override - public void run() { - if (typebasedCommitInvocations.get() != 1) { - try { - Thread.sleep(200); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - } - Realm bgRealm = Realm.getInstance(realm.getConfiguration()); - bgRealm.beginTransaction(); - bgRealm.createObject(Dog.class); - bgRealm.commitTransaction(); - bgRealm.close(); - } - }; - Dog dog = realm.where(Dog.class).findFirstAsync(); looperThread.keepStrongReference.add(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { - typebasedCommitInvocations.incrementAndGet(); - - if (typebasedCommitInvocations.get() == 1) { - try { - thread.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } + switch (typebasedCommitInvocations.incrementAndGet()) { + case 1: + assertEquals(1, object.getAge()); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.where(Dog.class).findFirst().setAge(17); + } + }); + break; + case 2: + assertEquals(17, object.getAge()); + looperThread.testComplete(); + break; + default: + fail(); } } }); - - thread.start(); } // UC 3 Sync RealmResults. diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index cbf00b2ceb..e04e8b4d4f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -242,7 +242,7 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { table2.addSearchIndex(column2); try { table2.setPrimaryKey(column2); - } catch (RealmError ignored) { + } catch (IllegalStateException ignored) { // Column has no search index. } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 0b64588552..09eda113f2 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -39,6 +39,7 @@ set(classes_LIST io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema io.realm.RealmObjectSchema io.realm.internal.Collection io.realm.internal.NativeObjectReference io.realm.internal.CollectionChangeSet + io.realm.internal.OsObject ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp new file mode 100644 index 0000000000..abe2e666d3 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -0,0 +1,203 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "io_realm_internal_OsObject.h" + +#include +#include +#include + +#include "util.hpp" + +#include "jni_util/java_global_weak_ref.hpp" +#include "jni_util/java_method.hpp" + +using namespace realm; +using namespace realm::jni_util; + +// We need to control the life cycle of Object, weak ref of Java OsObject and the NotificationToken. +// Wrap all three together, so when the Java object gets GCed, all three of them will be invalidated. +struct ObjectWrapper { + JavaGlobalWeakRef m_row_object_weak_ref; + NotificationToken m_notification_token; + realm::Object m_object; + + ObjectWrapper(realm::Object& object) + : m_row_object_weak_ref() + , m_notification_token() + , m_object(std::move(object)) + { + } + + ObjectWrapper(ObjectWrapper&&) = delete; + ObjectWrapper& operator=(ObjectWrapper&&) = delete; + + ObjectWrapper(ObjectWrapper const&) = delete; + ObjectWrapper& operator=(ObjectWrapper const&) = delete; + + ~ObjectWrapper() + { + } +}; + +struct ChangeCallback { + ChangeCallback(ObjectWrapper* wrapper) + : m_wrapper(wrapper) + { + } + + void parse_fields(JNIEnv* env, CollectionChangeSet const& change_set) + { + if (m_field_names_array) { + return; + } + + if (!change_set.deletions.empty()) { + m_deleted = true; + return; + } + + std::vector field_names; + auto table = m_wrapper->m_object.row().get_table(); + for (size_t i = 0; i < change_set.columns.size(); ++i) { + if (change_set.columns[i].empty()) { + continue; + } + // FIXME: After full integration of the OS schema, parse the column name from + // wrapper->m_object.get_object_schema() will be faster. + field_names.push_back(to_jstring(env, table->get_column_name(i))); + } + m_field_names_array = env->NewObjectArray(field_names.size(), java_lang_string, 0); + for (size_t i = 0; i < field_names.size(); ++i) { + env->SetObjectArrayElement(m_field_names_array, i, field_names[i]); + } + } + + JNIEnv* check_env() + { + JNIEnv* env = JniUtils::get_env(false); + if (!env || env->ExceptionCheck()) { + // JVM detached or java exception has been thrown before. + return nullptr; + } + return env; + } + + void before(CollectionChangeSet const& change_set) + { + JNIEnv* env = check_env(); + if (!env) { + return; + } + + parse_fields(env, change_set); + } + + void after(CollectionChangeSet const& change_set) + { + JNIEnv* env = check_env(); + if (!env) { + return; + } + if (change_set.empty()) { + return; + } + + parse_fields(env, change_set); + + m_wrapper->m_row_object_weak_ref.call_with_local_ref(env, [&](JNIEnv*, jobject row_obj) { + static JavaMethod notify_change_listeners(env, row_obj, "notifyChangeListeners", + "([Ljava/lang/String;)V"); + env->CallVoidMethod(row_obj, notify_change_listeners, m_field_names_array); + }); + } + + void error(std::exception_ptr err) + { + if (err) { + try { + std::rethrow_exception(err); + } + catch (const std::exception& e) { + Log::e("Caught exception in object change callback %1", e.what()); + } + } + } + +private: + ObjectWrapper* m_wrapper; + bool m_deleted = false; + jobjectArray m_field_names_array = nullptr; +}; + +static void finalize_object(jlong ptr) +{ + TR_ENTER_PTR(ptr); + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeGetFinalizerPtr(JNIEnv*, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_object); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreate(JNIEnv*, jclass, jlong shared_realm_ptr, + jlong row_ptr) +{ + TR_ENTER_PTR(row_ptr) + + // FIXME: Currently OsObject is only used for object notifications. Since the Object Store's schema has not been + // fully integrated with realm-java, we pass a dummy ObjectSchema to create Object. + static const ObjectSchema dummy_object_schema; + + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& row = *(reinterpret_cast(row_ptr)); + Object object(shared_realm, dummy_object_schema, row); // no throw + auto wrapper = new ObjectWrapper(object); // no throw + + return reinterpret_cast(wrapper); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsObject_nativeStartListening(JNIEnv* env, jobject instance, + jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + + try { + auto wrapper = reinterpret_cast(native_ptr); + if (!wrapper->m_row_object_weak_ref) { + wrapper->m_row_object_weak_ref = JavaGlobalWeakRef(env, instance); + } + + // The wrapper pointer will be used in the callback. But it should never become an invalid pointer when the + // notification block gets called. This should be guaranteed by the Object Store that after the notification + // token is destroyed, the block shouldn't be called. + wrapper->m_notification_token = wrapper->m_object.add_notification_block(ChangeCallback(wrapper)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsObject_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + + try { + auto wrapper = reinterpret_cast(native_ptr); + wrapper->m_notification_token = {}; + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 75e2598b12..e562c12564 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -106,6 +106,9 @@ void ConvertException(JNIEnv* env, const char* file, int line) ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalState, ss.str()); } + catch (realm::LogicError e) { + ThrowException(env, IllegalState, e.what()); + } catch (std::logic_error e) { ThrowException(env, IllegalState, e.what()); } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index f3fc9a16b4..7bdd156b74 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -481,8 +481,6 @@ E get(Class clazz, String dynamicClassName, UncheckedR result = configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz), false, Collections.emptyList()); } - RealmObjectProxy proxy = (RealmObjectProxy) result; - proxy.realmGet$proxyState().setTableVersion$realm(); return result; } @@ -491,8 +489,6 @@ E get(Class clazz, long rowIndex, boolean acceptDefaul UncheckedRow row = table.getUncheckedRow(rowIndex); E result = configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz), acceptDefaultValue, excludeFields); - RealmObjectProxy proxy = (RealmObjectProxy) result; - proxy.realmGet$proxyState().setTableVersion$realm(); return result; } @@ -515,11 +511,6 @@ E get(Class clazz, String dynamicClassName, long rowIn schema.getColumnInfo(clazz), false, Collections.emptyList()); } - RealmObjectProxy proxy = (RealmObjectProxy) result; - if (rowIndex != Table.NO_MATCH) { - proxy.realmGet$proxyState().setTableVersion$realm(); - } - return result; } diff --git a/realm/realm-library/src/main/java/io/realm/ObjectChangeSet.java b/realm/realm-library/src/main/java/io/realm/ObjectChangeSet.java new file mode 100644 index 0000000000..1c5277c903 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/ObjectChangeSet.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * Information about the changes made to an object. + * + * @see RealmObject#addChangeListener(RealmObjectChangeListener) . + */ +public interface ObjectChangeSet { + + /** + * @return true if the object has been deleted from the Realm. + */ + boolean isDeleted(); + + /** + * @return the names of changed fields if the object still exists and there are field changes. Returns an empty + * {@code String[]} if the object has been deleted. + */ + String[] getChangedFields(); + + /** + * Checks if a given field has been changed. + * + * @param fieldName to be checked if its value has been changed. + * @return {@code true} if the field has been changed. It returns {@code false} if the object is deleted, the field + * cannot be found or the field hasn't been changed. + */ + boolean isFieldChanged(String fieldName); +} diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 74b72fb127..58ef76c7ec 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -17,11 +17,11 @@ package io.realm; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import io.realm.internal.InvalidRow; +import io.realm.internal.ObserverPairList; import io.realm.internal.PendingRow; import io.realm.internal.Row; +import io.realm.internal.OsObject; import io.realm.internal.UncheckedRow; @@ -30,18 +30,57 @@ * {@link RealmObject} and {@link DynamicRealmObject}. */ public final class ProxyState implements PendingRow.FrontEnd { + + static class RealmChangeListenerWrapper implements RealmObjectChangeListener { + private final RealmChangeListener listener; + + RealmChangeListenerWrapper(RealmChangeListener listener) { + if (listener == null) { + throw new IllegalArgumentException("Listener should not be null"); + } + this.listener = listener; + } + + @Override + public void onChange(T object, ObjectChangeSet changes) { + listener.onChange(object); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof RealmChangeListenerWrapper && + listener == ((RealmChangeListenerWrapper) obj).listener; + } + + @Override + public int hashCode() { + return listener.hashCode(); + } + } + + private static class QueryCallback implements ObserverPairList.Callback { + + @Override + public void onCalled(OsObject.ObjectObserverPair pair, Object observer) { + //noinspection unchecked + pair.onChange((RealmModel) observer, null); + } + } + private E model; // true only while executing the constructor of the enclosing proxy object private boolean underConstruction = true; private Row row; + private OsObject osObject; private BaseRealm realm; private boolean acceptDefaultValue; private List excludeFields; - private final List> listeners = new CopyOnWriteArrayList>(); - protected long currentTableVersion = -1; + private ObserverPairList observerPairs = + new ObserverPairList(); + private static QueryCallback queryCallback = new QueryCallback(); public ProxyState() {} @@ -84,44 +123,34 @@ public ProxyState(E model) { /** * Notifies all registered listeners. */ - private void notifyChangeListeners() { - if (!listeners.isEmpty()) { - for (RealmChangeListener listener : listeners) { - if (realm.sharedRealm == null || realm.sharedRealm.isClosed()) { - return; - } - listener.onChange(model); - } - } + private void notifyQueryFinished() { + observerPairs.foreach(queryCallback); } - public void addChangeListener(RealmChangeListener listener) { - if (!listeners.contains(listener)) { - listeners.add(listener); - } - // this might be called after query returns. So it is still necessary to register. - if (row instanceof UncheckedRow) { - registerToRealmNotifier(); + public void addChangeListener(RealmObjectChangeListener listener) { + if (row instanceof PendingRow) { + observerPairs.add(new OsObject.ObjectObserverPair(model, listener)); + } else if (row instanceof UncheckedRow) { + registerToObjectNotifier(); + if (osObject != null) { + osObject.addListener(model, listener); + } } } - public void removeChangeListener(RealmChangeListener listener) { - listeners.remove(listener); - if (listeners.isEmpty() && row instanceof UncheckedRow) { - realm.sharedRealm.realmNotifier.removeChangeListeners(this); + public void removeChangeListener(RealmObjectChangeListener listener) { + if (osObject != null) { + osObject.removeListener(model, listener); + } else { + observerPairs.remove(model, listener); } } public void removeAllChangeListeners() { - listeners.clear(); - if (row instanceof UncheckedRow) { - realm.sharedRealm.realmNotifier.removeChangeListeners(this); - } - } - - public void setTableVersion$realm() { - if (row.getTable() != null) { - currentTableVersion = row.getTable().getVersion(); + if (osObject != null) { + osObject.removeListener(model); + } else { + observerPairs.clear(); } } @@ -135,25 +164,17 @@ public void setConstructionFinished() { excludeFields = null; } - private void registerToRealmNotifier() { + private void registerToObjectNotifier() { if (realm.sharedRealm == null || realm.sharedRealm.isClosed() || !row.isAttached()) { return; } - realm.sharedRealm.realmNotifier.addChangeListener(this, new RealmChangeListener>() { - @Override - public void onChange(ProxyState element) { - long tableVersion = -1; - if (row.isAttached()) { - // If the Row gets detached, table version will be -1 and it is different from current value. - tableVersion = row.getTable().getVersion(); - } - if (currentTableVersion != tableVersion) { - currentTableVersion = tableVersion; - notifyChangeListeners(); - } - } - }); + if (osObject == null) { + osObject = new OsObject(realm.sharedRealm, (UncheckedRow) row); + osObject.setObserverPairs(observerPairs); + // We should never need observerPairs after pending row returns. + observerPairs = null; + } } public boolean isLoaded() { @@ -162,22 +183,17 @@ public boolean isLoaded() { public void load() { if (row instanceof PendingRow) { - row = ((PendingRow) row).executeQuery(); - if (!(row instanceof InvalidRow)) { - registerToRealmNotifier(); - } - notifyChangeListeners(); + ((PendingRow) row).executeQuery(); } } @Override public void onQueryFinished(Row row) { this.row = row; - notifyChangeListeners(); + // getTable should return a non-null table since the row should always be valid here. + notifyQueryFinished(); if (row.isAttached()) { - // getTable should return a non-null table since the row should always be valid here. - currentTableVersion = row.getTable().getVersion(); - registerToRealmNotifier(); + registerToObjectNotifier(); } } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index e76a0e2746..74025c2544 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -313,12 +313,28 @@ public static boolean load(E object) { } /** - * Adds a change listener to this RealmObject. + * Adds a change listener to this RealmObject to get detailed information about changes. The listener will be + * triggered if any value field or referenced RealmObject field is changed, or the RealmList field itself is + * changed. + * + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the change listener is {@code null} or the object is an unmanaged object. + * @throws IllegalStateException if you try to add a listener from a non-Looper or {@link IntentService} thread. + * @throws IllegalStateException if you try to add a listener inside a transaction. + */ + public final void addChangeListener(RealmObjectChangeListener listener) { + //noinspection unchecked + RealmObject.addChangeListener((E) this, listener); + } + + /** + * Adds a change listener to this RealmObject that will be triggered if any value field or referenced RealmObject + * field is changed, or the RealmList field itself is changed. * * @param listener the change listener to be notified. * @throws IllegalArgumentException if the change listener is {@code null} or the object is an unmanaged object. - * @throws IllegalArgumentException if object is an unmanaged RealmObject. * @throws IllegalStateException if you try to add a listener from a non-Looper or {@link IntentService} thread. + * @throws IllegalStateException if you try to add a listener inside a transaction. */ public final void addChangeListener(RealmChangeListener listener) { //noinspection unchecked @@ -326,15 +342,18 @@ public final void addChangeListener(RealmChangeListener void addChangeListener(E object, RealmChangeListener listener) { + public static void addChangeListener(E object, RealmObjectChangeListener listener) { if (object == null) { throw new IllegalArgumentException("Object should not be null"); } @@ -353,6 +372,32 @@ public static void addChangeListener(E object, RealmChang } } + /** + * Adds a change listener to a RealmObject that will be triggered if any value field or referenced RealmObject field + * is changed, or the RealmList field itself is changed. + * + * @param object RealmObject to add listener to. + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the {@code object} is {@code null} or an unmanaged object, or the change + * listener is {@code null}. + * @throws IllegalStateException if you try to add a listener from a non-Looper or {@link IntentService} thread. + * @throws IllegalStateException if you try to add a listener inside a transaction. + */ + public static void addChangeListener(E object, RealmChangeListener listener) { + addChangeListener(object, new ProxyState.RealmChangeListenerWrapper(listener)); + } + + /** + * Removes a previously registered listener. + * + * @param listener the instance to be removed. + * @throws IllegalArgumentException if the change listener is {@code null} or the object is an unmanaged object. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + */ + public final void removeChangeListener(RealmObjectChangeListener listener) { + RealmObject.removeChangeListener(this, listener); + } + /** * Removes a previously registered listener. * @@ -373,7 +418,7 @@ public final void removeChangeListener(RealmChangeListener listener) { * @throws IllegalArgumentException if object is an unmanaged RealmObject. * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. */ - public static void removeChangeListener(E object, RealmChangeListener listener) { + public static void removeChangeListener(E object, RealmObjectChangeListener listener) { if (object == null) { throw new IllegalArgumentException("Object should not be null"); } @@ -392,6 +437,19 @@ public static void removeChangeListener(E object, RealmCh } } + /** + * Removes a previously registered listener on the given RealmObject. + * + * @param object RealmObject to remove listener from. + * @param listener the instance to be removed. + * @throws IllegalArgumentException if the {@code object} or the change listener is {@code null}. + * @throws IllegalArgumentException if object is an unmanaged RealmObject. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + */ + public static void removeChangeListener(E object, RealmChangeListener listener) { + removeChangeListener(object, new ProxyState.RealmChangeListenerWrapper(listener)); + } + /** * Removes all registered listeners. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectChangeListener.java b/realm/realm-library/src/main/java/io/realm/RealmObjectChangeListener.java new file mode 100644 index 0000000000..af172a0e16 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectChangeListener.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import io.realm.annotations.LinkingObjects; + +/** + * {@code RealmObjectChangeListener} can be registered on a {@link RealmModel} or {@link RealmObject} to receive + * detailed notifications when an object changes. + *

                    + * Realm instances on a thread without an {@link android.os.Looper} cannot register a {@code RealmObjectChangeListener}. + *

                    + * Listener cannot be registered inside a transaction. + * + * @param The type of {@link RealmModel} on which your listener will be registered. + * @see Realm#addChangeListener(RealmChangeListener) + * @see Realm#removeAllChangeListeners() + * @see Realm#removeChangeListener(RealmChangeListener) + */ +public interface RealmObjectChangeListener { + + /** + * When this gets called to return the results of an asynchronous query made by {@link RealmQuery#findFirstAsync()}, + * {@code changeSet} will be {@code null}. + *

                    + * When this gets called because the object was deleted, {@code changeSet.isDeleted()} will return {@code true} + * and {@code changeSet.getFieldChanges()} will return {@code null}. + *

                    + * When this gets called because the object was modified, {@code changeSet.isDeleted()} will return {@code false} + * and {@code changeSet.getFieldChanges()} will return the detailed information about the fields' changes. + *

                    + * If a field points to another RealmObject this listener will only be triggered if the field is set to a new object + * or null. Updating the referenced RealmObject will not trigger this listener. + *

                    + * If a field points to a RealmList, this listener will only be triggered if one or multiple objects are inserted, + * removed or moved within the List. Updating the objects in the RealmList will not trigger this listener. + *

                    + * Changes to {@link LinkingObjects} annotated {@link RealmResults} fields will not be monitored, nor reported + * through this change listener. + * @param object the {@code RealmObject} this listener is registered to. + * @param changeSet the detailed information about the changes. + */ + void onChange(T object, ObjectChangeSet changeSet); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java index 7e726a0df3..876b031b9d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java @@ -33,13 +33,13 @@ * * @param the type of {@link ObserverPair}. */ -class ObserverPairList { +public class ObserverPairList { /** * @param the type of observer. * @param the type of listener. */ - abstract static class ObserverPair { + public abstract static class ObserverPair { final WeakReference observerRef; protected final S listener; // Should only be set by the outer class. To marked it as removed in case it is removed in foreach callback. @@ -81,7 +81,7 @@ public int hashCode() { * * @param type of ObserverPair. */ - interface Callback { + public interface Callback { void onCalled(T pair, Object observer); } @@ -96,7 +96,7 @@ interface Callback { * * @param callback to be executed on the pair. */ - void foreach(Callback callback) { + public void foreach(Callback callback) { for (T pair : pairs) { if (cleared) { break; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java new file mode 100644 index 0000000000..5cf394d4ad --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -0,0 +1,157 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import io.realm.ObjectChangeSet; +import io.realm.RealmModel; +import io.realm.RealmObjectChangeListener; + + +/** + * Java wrapper for Object Store's {@code Object} class. Currently it is only used for object notifications. + */ +public class OsObject implements NativeObject { + + private static class OsObjectChangeSet implements ObjectChangeSet { + final String[] changedFields; + final boolean deleted; + + OsObjectChangeSet(String[] changedFields, boolean deleted) { + this.changedFields = changedFields; + this.deleted = deleted; + } + + @Override + public boolean isDeleted() { + return deleted; + } + + @Override + public String[] getChangedFields() { + return changedFields; + } + + @Override + public boolean isFieldChanged(String fieldName) { + for (String name : changedFields) { + if (name.equals(fieldName)) { + return true; + } + } + return false; + } + } + + public static class ObjectObserverPair + extends ObserverPairList.ObserverPair> { + public ObjectObserverPair(T observer, RealmObjectChangeListener listener) { + super(observer, listener); + } + + public void onChange(T observer, ObjectChangeSet changeSet) { + listener.onChange(observer, changeSet); + } + } + + private static class Callback implements ObserverPairList.Callback { + private final String[] changedFields; + + Callback(String[] changedFields) { + this.changedFields = changedFields; + } + + private ObjectChangeSet createChangeSet() { + boolean isDeleted = changedFields == null; + return new OsObjectChangeSet(isDeleted ? new String[0] : changedFields, isDeleted); + } + + @Override + public void onCalled(ObjectObserverPair pair, Object observer) { + //noinspection unchecked + pair.onChange((RealmModel) observer, createChangeSet()); + } + } + + private final long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); + + private ObserverPairList observerPairs = new ObserverPairList(); + + public OsObject(SharedRealm sharedRealm, UncheckedRow row) { + nativePtr = nativeCreate(sharedRealm.getNativePtr(), row.getNativePtr()); + sharedRealm.context.addReference(this); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + public void addListener(T observer, RealmObjectChangeListener listener) { + if (observerPairs.isEmpty()) { + nativeStartListening(nativePtr); + } + ObjectObserverPair pair = new ObjectObserverPair(observer, listener); + observerPairs.add(pair); + } + + public void removeListener(T observer) { + observerPairs.removeByObserver(observer); + if (observerPairs.isEmpty()) { + nativeStopListening(nativePtr); + } + } + + public void removeListener(T observer, RealmObjectChangeListener listener) { + observerPairs.remove(observer, listener); + if (observerPairs.isEmpty()) { + nativeStopListening(nativePtr); + } + } + + // Set the ObserverPairList. This is useful for the findAllAsync. When the pendingRow returns the results, the whole + // listener list has to be moved from ProxyState to here. + public void setObserverPairs(ObserverPairList pairs) { + if (!observerPairs.isEmpty()) { + throw new IllegalStateException("'observerPairs' is not empty. Listeners have been added before."); + } + + observerPairs = pairs; + if (!pairs.isEmpty()) { + nativeStartListening(nativePtr); + } + } + + // Called by JNI + @SuppressWarnings("unused") + private void notifyChangeListeners(String[] changedFields) { + observerPairs.foreach(new Callback(changedFields)); + } + + private static native long nativeGetFinalizerPtr(); + + private static native long nativeCreate(long shared_realm_ptr, long rowPtr); + + private native void nativeStartListening(long nativePtr); + + private native void nativeStopListening(long nativePtr); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 90c1dbc74e..65bff1f38e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -29,6 +29,7 @@ public interface FrontEnd { private static final String QUERY_EXECUTED_MESSAGE = "The query has been executed. This 'PendingRow' is not valid anymore."; + private SharedRealm sharedRealm; private Collection pendingCollection; private RealmChangeListener listener; private WeakReference frontEndRef; @@ -36,39 +37,18 @@ public interface FrontEnd { public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, final boolean returnCheckedRow) { + this.sharedRealm = sharedRealm; pendingCollection = new Collection(sharedRealm, query, sortDescriptor, null); listener = new RealmChangeListener() { @Override public void onChange(PendingRow pendingRow) { - if (frontEndRef == null) { - throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); - } - FrontEnd frontEnd = frontEndRef.get(); - if (frontEnd == null) { - // The front end is GCed. - clearPendingCollection(); - return; - } - - if (pendingCollection.isValid()) { - // PendingRow will always get the first Row of the query since we only support findFirst. - UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); - // If no rows returned by the query, notify the frontend with an invalid row. - if (uncheckedRow != null) { - Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; - // Ask the front end to reset the row and stop async query. - frontEnd.onQueryFinished(row); - } else { - frontEnd.onQueryFinished(InvalidRow.INSTANCE); - } - } - - clearPendingCollection(); + notifyFrontEnd(); } }; pendingCollection.addListener(this, listener); this.returnCheckedRow = returnCheckedRow; + sharedRealm.addPendingRow(this); } // To set the front end of this PendingRow. @@ -225,22 +205,43 @@ private void clearPendingCollection() { pendingCollection.removeListener(this, listener); pendingCollection = null; listener = null; + sharedRealm.removePendingRow(this); } - public Row executeQuery() { - if (pendingCollection == null) { - throw new IllegalStateException(QUERY_EXECUTED_MESSAGE); - } + private void notifyFrontEnd() { if (frontEndRef == null) { throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); } + FrontEnd frontEnd = frontEndRef.get(); + if (frontEnd == null) { + // The front end is GCed. + clearPendingCollection(); + return; + } + + if (pendingCollection.isValid()) { + // PendingRow will always get the first Row of the query since we only support findFirst. + UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); + // If no rows returned by the query, notify the frontend with an invalid row. + if (uncheckedRow != null) { + Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; + // Ask the front end to reset the row and stop async query. + frontEnd.onQueryFinished(row); + } else { + // No row matches the query, return a invalid row. + frontEnd.onQueryFinished(InvalidRow.INSTANCE); + } + } - UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); clearPendingCollection(); + } - if (uncheckedRow == null) { - return InvalidRow.INSTANCE; + // Execute the query immediately and call frontend's onQueryFinished(). + public void executeQuery() { + if (pendingCollection == null) { + throw new IllegalStateException(QUERY_EXECUTED_MESSAGE); } - return returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; + + notifyFrontEnd(); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 814af27fb8..5580c65e64 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -112,10 +112,10 @@ public byte getNativeValue() { // JNI will only hold a weak global ref to this. public final RealmNotifier realmNotifier; - public final List> collections = new CopyOnWriteArrayList>(); public final Capabilities capabilities; public final List> iterators = new ArrayList>(); + private final List> pendingRows = new CopyOnWriteArrayList>(); public static class VersionID implements Comparable { public final long version; @@ -241,6 +241,7 @@ public static SharedRealm getInstance(RealmConfiguration config, SchemaVersionLi public void beginTransaction() { detachIterators(); + executePendingRowQueries(); nativeBeginTransaction(nativePtr); invokeSchemaChangeListenerIfSchemaChanged(); } @@ -428,6 +429,38 @@ void invalidateIterators() { iterators.clear(); } + // addPendingRow, removePendingRow and executePendingRow queries are to solve that the listener cannot be added + // inside a transaction. For the findFirstAsync(), listener is registered on an Object Store Results first, then move + // the listeners to the Object when the query for Results returns. When beginTransaction() called, all listeners' + // on the results will be triggered first, that leads to the registration of listeners on the Object which will + // throw because of the transaction has already begun. So here we execute all PendingRow queries first before + // calling the Object Store begin_transaction to avoid the problem. + // Add pending row to the list when it is created. It should be called in the PendingRow constructor. + void addPendingRow(PendingRow pendingRow) { + pendingRows.add(new WeakReference(pendingRow)); + } + + // Remove pending row from the list. It should be called when pending row's query finished. + void removePendingRow(PendingRow pendingRow) { + for (WeakReference ref : pendingRows) { + PendingRow row = ref.get(); + if (row == null || row == pendingRow) { + pendingRows.remove(ref); + } + } + } + + // Execute all pending row queries. + private void executePendingRowQueries() { + for (WeakReference ref : pendingRows) { + PendingRow row = ref.get(); + if (row != null) { + row.executeQuery(); + } + } + pendingRows.clear(); + } + private static native void nativeInit(String temporaryDirectoryPath); // Keep last session as an 'object' to avoid any reference to sync code From 7a6f2410aec7e553b521c48cb1112ab4733a4e99 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 30 Mar 2017 08:29:55 +0200 Subject: [PATCH 0582/2110] Updating to Realm Sync 1.5.0 and Realm Core 2.5.1 (#4397) * Updating to Realm Sync 1.5.0, Realm Core 2.5.1, and Realm Object Server 1.3.0-294 --- CHANGELOG.md | 4 ++++ dependencies.list | 6 +++--- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f9cdce124..ad37eeeab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Breaking Changes +* Updated file format of Realm files. Existing Realm files will automatically be migrated to the new format when they are opened. +* [ObjectServer] Due to file format changes, Realm Object Server 1.3.0 or later is required. * [ObjectServer] Added `onClientResetRequired(SyncSession, ClientResetHandler)` method to the `ErrorHandler` interface (#4080). ### Enhancements @@ -24,6 +26,8 @@ ### Internal * Using the Object Store's Session and SyncManager. +* Upgraded to Realm Sync 1.5.0. +* Upgraded to Realm Core 2.5.1. ## 3.0.1 (YYYY-MM-DD) diff --git a/dependencies.list b/dependencies.list index 857165a0d4..408853f4de 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.3.2 -REALM_SYNC_SHA256=be79d334ca8d87785a91fa5d68264bc62de49271936d5c19b6473f34cd47f9f0 +REALM_SYNC_VERSION=1.5.0 +REALM_SYNC_SHA256=2da0de557182e7d717d74808bf6d3f1e5f18d5c082744d29b96dca80555733a7 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. @@ -10,4 +10,4 @@ REALM_SYNC_SHA256=be79d334ca8d87785a91fa5d68264bc62de49271936d5c19b6473f34cd47f9 # /tools/sync_test_server/Dockerfile specify which repo (apt) we should # install/use between 'realm' and 'realm-testing', the version below should # correspond to an existing version on the *specified* repo. -REALM_OBJECT_SERVER_DE_VERSION=1.2.1-270 +REALM_OBJECT_SERVER_DE_VERSION=1.3.0-294 diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 14c2c7e703..3eada170f3 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 14c2c7e7038850302f60f6fa1e36a22bceb3ab94 +Subproject commit 3eada170f380174992b47b6023f375f3f372a9d2 From 8ba5ddd4d6a5ff3cc4e5ee246b7553e298ccb35d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 30 Mar 2017 12:57:17 +0800 Subject: [PATCH 0583/2110] Enable build from core source Fix #4130 - Fix the problem with build.gradle's coreSourcePath. - Build with output in the core source dir instead of deploy the tarball. - Skip objectServer flavor when build from core source. - Build sync from source code is still not supported. There are still a lot of improvements can be done in the future: - Build specific ABI only when build core. - Skip core tarball generation. But it would be a good idea to do above after core switches to cmake. Our current build system sucks. --- realm/realm-library/build.gradle | 102 ++++++++++-------- .../realm-library/src/main/cpp/CMakeLists.txt | 32 +++--- 2 files changed, 76 insertions(+), 58 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 208d17e788..eaba3c7d9c 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -29,7 +29,9 @@ if (!ext.coreArchiveDir) { } ext.coreArchiveFile = rootProject.file("${ext.coreArchiveDir}/realm-sync-android-${project.coreVersion}.tar.gz") ext.coreDistributionDir = file("${projectDir}/distribution/realm-core/") -ext.coreDir = file("${project.coreDistributionDir.getAbsolutePath()}/core-${project.coreVersion}") +ext.coreDir = file(project.coreSourcePath ? + "${project.coreSourcePath}/android-lib" : + "${project.coreDistributionDir.getAbsolutePath()}/core-${project.coreVersion}") ext.ccachePath = project.findProperty('ccachePath') ?: System.getenv('NDK_CCACHE') ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE') @@ -46,11 +48,11 @@ android { externalNativeBuild { cmake { arguments "-DREALM_CORE_DIST_DIR:STRING=${project.coreDir.getAbsolutePath()}", - // FIXME: - // This is copied from https://dl.google.com/android/repository/cmake-3.4.2909474-linux-x86_64.zip - // because of the android.toolchain.cmake shipped with Android SDK CMake 3.6 doesn't work with our - // JNI build currently (lack of lto linking support). - // This file should be removed and use the one from Android SDK cmake package when it supports lto. + // FIXME: + // This is copied from https://dl.google.com/android/repository/cmake-3.4.2909474-linux-x86_64.zip + // because of the android.toolchain.cmake shipped with Android SDK CMake 3.6 doesn't work with our + // JNI build currently (lack of lto linking support). + // This file should be removed and use the one from Android SDK cmake package when it supports lto. "-DCMAKE_TOOLCHAIN_FILE=${project.file('src/main/cpp/android.toolchain.cmake').path}" if (project.ccachePath) arguments "-DNDK_CCACHE=$project.ccachePath" if (project.lcachePath) arguments "-DNDK_LCACHE=$project.lcachePath" @@ -112,6 +114,15 @@ android { consumerProguardFiles 'proguard-rules-common.pro', 'proguard-rules-objectServer.pro' } } + + variantFilter { variant -> + def names = variant.flavors*.name + + // Ignore the objectServer flavour when building from core source. + if (coreSourcePath && names.contains("objectServer")) { + variant.ignore = true + } + } } project.afterEvaluate { @@ -178,7 +189,7 @@ task javadoc(type: Javadoc) { links "http://reactivex.io/RxJava/javadoc/" linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" - tags = [ betaTag ] + tags = [betaTag] } exclude '**/internal/**' exclude '**/BuildConfig.java' @@ -252,7 +263,7 @@ task checkstyle(type: Checkstyle) { // Configuration options can be found here: // http://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html task connectedBenchmarks(type: GradleBuild) { - description = 'Run all benchmarks on connected devices' + description = 'Run all benchmarks on connected devices' group = 'Verification' buildFile = file("${projectDir}/build.gradle") startParameter.getProjectProperties().put('android.testInstrumentationRunnerArguments.package', 'io.realm.benchmarks') @@ -260,7 +271,7 @@ task connectedBenchmarks(type: GradleBuild) { } task connectedUnitTests(type: GradleBuild) { - description = 'Run all unit tests on connected devices' + description = 'Run all unit tests on connected devices' group = 'Verification' buildFile = file("${projectDir}/build.gradle") startParameter.getProjectProperties().put('android.testInstrumentationRunnerArguments.notPackage', 'io.realm.benchmarks') @@ -358,7 +369,7 @@ publishing { accessKey project.hasProperty('s3AccessKey') ? s3AccessKey : 'noAccessKey' secretKey project.hasProperty('s3SecretKey') ? s3SecretKey : 'noSecretKey' } - if(project.version.endsWith('-SNAPSHOT')) { + if (project.version.endsWith('-SNAPSHOT')) { url "s3://realm-ci-artifacts/maven/snapshots/" } else { url "s3://realm-ci-artifacts/maven/releases/" @@ -398,7 +409,7 @@ task downloadCore() { return project.hasProperty('coreSha256Hash') && !project.coreSha256Hash.empty } - def calcSha256Hash = {File targetFile -> + def calcSha256Hash = { File targetFile -> MessageDigest sha = MessageDigest.getInstance("SHA-256") Formatter hexHash = new Formatter() sha.digest(targetFile.bytes).each { b -> hexHash.format('%02x', b) } @@ -482,9 +493,14 @@ task deployCore(group: 'build setup', description: 'Deploy the latest version of coreSourcePath ? compileCore : downloadCore } + // Build with the output from core source dir. No need to deploy anything. + onlyIf { + return !coreSourcePath + } + outputs.upToDateWhen { - // Clean up the coreDir if it is newly downloaded or compiled from source - if (coreDownloaded || coreSourcePath) { + // Clean up the coreDir if it is newly downloaded + if (coreDownloaded) { return false } @@ -575,52 +591,52 @@ android.productFlavors.all { flavor -> dependsOn "assemble${flavor.name.capitalize()}" group = 'Publishing' commandLine 'curl', - '-X', - 'PUT', - '-T', - "${buildDir}/outputs/aar/realm-android-library-${flavor.name}-release.aar", - '-u', - "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.aar?publish=0" + '-X', + 'PUT', + '-T', + "${buildDir}/outputs/aar/realm-android-library-${flavor.name}-release.aar", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.aar?publish=0" } task("bintraySources${flavor.name.capitalize()}", type: Exec) { dependsOn sourcesJar group = 'Publishing' commandLine 'curl', - '-X', - 'PUT', - '-T', - "${buildDir}/libs/realm-android-library-${project.version}-sources.jar", - '-u', - "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-sources.jar?publish=0" + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-android-library-${project.version}-sources.jar", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-sources.jar?publish=0" } task("bintrayJavadoc${flavor.name.capitalize()}", type: Exec) { dependsOn javadocJar group = 'Publishing' commandLine 'curl', - '-X', - 'PUT', - '-T', - "${buildDir}/libs/realm-android-library-${project.version}-javadoc.jar", - '-u', - "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-javadoc.jar?publish=0" + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-android-library-${project.version}-javadoc.jar", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-javadoc.jar?publish=0" } task("bintrayPom${flavor.name.capitalize()}", type: Exec) { dependsOn "publish${flavor.name.capitalize()}PublicationPublicationToMavenLocal" group = 'Publishing' commandLine 'curl', - '-X', - 'PUT', - '-T', - "${buildDir}/publications/${flavor.name}Publication/pom-default.xml", - '-u', - "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.pom?publish=0" + '-X', + 'PUT', + '-T', + "${buildDir}/publications/${flavor.name}Publication/pom-default.xml", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.pom?publish=0" } // OJO @@ -724,11 +740,11 @@ def checkNdk(String ndkPath) { } if (detectedNdkVersion != project.ndkVersion) { throw new GradleException("Your NDK version: ${detectedNdkVersion}." - +" Realm JNI must be compiled with the version ${project.ndkVersion} of NDK.") + + " Realm JNI must be compiled with the version ${project.ndkVersion} of NDK.") } } -def getValueFromPropertiesFile(File propFile, String key) { +static def getValueFromPropertiesFile(File propFile, String key) { if (!propFile.isFile() || !propFile.canRead()) { return null } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 4aefd1f733..5b97a894cb 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -84,23 +84,25 @@ add_library(lib_realm_core STATIC IMPORTED) set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_PATH} IMPORTED_LINK_INTERFACE_LIBRARIES atomic) -# Sync static library -set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-${ANDROID_ABI}.a) -# Workaround for old core's funny ABI nicknames -if (NOT EXISTS ${sync_lib_PATH}) - if (ARMEABI) - set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm.a) - elseif (ARMEABI_V7A) - set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm-v7a.a) - elseif (ARM64_V8A) - set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm64.a) - else() - message(FATAL_ERROR "Cannot find core lib file: ${sync_lib_PATH}") +if (build_SYNC) + # Sync static library + set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-${ANDROID_ABI}.a) + # Workaround for old core's funny ABI nicknames + if (NOT EXISTS ${sync_lib_PATH}) + if (ARMEABI) + set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm.a) + elseif (ARMEABI_V7A) + set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm-v7a.a) + elseif (ARM64_V8A) + set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm64.a) + else() + message(FATAL_ERROR "Cannot find sync lib file: ${sync_lib_PATH}") + endif() endif() + add_library(lib_realm_sync STATIC IMPORTED) + set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_PATH} + IMPORTED_LINK_INTERFACE_LIBRARIES lib_realm_core) endif() -add_library(lib_realm_sync STATIC IMPORTED) -set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_PATH} - IMPORTED_LINK_INTERFACE_LIBRARIES lib_realm_core) # build application's shared lib include_directories(${REALM_CORE_DIST_DIR}/include From 2de57819bfe30fc3bbfd4537a85e40fcbcff1ad9 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 30 Mar 2017 16:49:18 +0800 Subject: [PATCH 0584/2110] Enable distinct on unindexed fields (#4390) Fix #2285 --- CHANGELOG.md | 1 + .../java/io/realm/RealmAsyncQueryTests.java | 75 +++++++++++++-- .../java/io/realm/RealmQueryTests.java | 13 +-- .../java/io/realm/RealmResultsTests.java | 91 ++++++++++++++++--- .../realm/internal/SortDescriptorTests.java | 10 -- .../src/main/java/io/realm/RealmQuery.java | 11 ++- .../io/realm/internal/SortDescriptor.java | 5 - 7 files changed, 161 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be1d551273..2f56ed24d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Now `targetSdkVersion` is 25. * Listeners on `RealmList` and `RealmResults` will be triggered immediately when the transaction is committed on the same thread (#4245). +* `RealmQuery.distinct()` can be performed on unindexed fields (#2285). ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index a9608250db..32451147b5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -871,15 +871,76 @@ public void distinctAsync_notIndexedFields() throws Throwable { final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - for (String fieldName : new String[]{"Boolean", "Long", "Date", "String"}) { - try { - realm.where(AnnotationIndexTypes.class).distinctAsync("notIndex" + fieldName); - fail("notIndex" + fieldName); - } catch (IllegalArgumentException ignored) { + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) + .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class) + .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_LONG); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) + .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_DATE); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) + .distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); + + assertFalse(distinctBool.isLoaded()); + assertTrue(distinctBool.isValid()); + assertTrue(distinctBool.isEmpty()); + + assertFalse(distinctLong.isLoaded()); + assertTrue(distinctLong.isValid()); + assertTrue(distinctLong.isEmpty()); + + assertFalse(distinctDate.isLoaded()); + assertTrue(distinctDate.isValid()); + assertTrue(distinctDate.isEmpty()); + + assertFalse(distinctString.isLoaded()); + assertTrue(distinctString.isValid()); + assertTrue(distinctString.isEmpty()); + + final Runnable changeListenerDone = new Runnable() { + final AtomicInteger signalCallbackFinished = new AtomicInteger(4); + @Override + public void run() { + if (signalCallbackFinished.decrementAndGet() == 0) { + looperThread.testComplete(); + } } - } + }; - looperThread.testComplete(); + looperThread.keepStrongReference.add(distinctBool); + looperThread.keepStrongReference.add(distinctLong); + looperThread.keepStrongReference.add(distinctDate); + looperThread.keepStrongReference.add(distinctString); + distinctBool.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(2, distinctBool.size()); + changeListenerDone.run(); + } + }); + + distinctLong.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(numberOfBlocks, distinctLong.size()); + changeListenerDone.run(); + } + }); + + distinctDate.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(numberOfBlocks, distinctDate.size()); + changeListenerDone.run(); + } + }); + + distinctString.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(numberOfBlocks, distinctString.size()); + changeListenerDone.run(); + } + }); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 27f6cae43e..53642936a0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -3114,12 +3114,13 @@ public void distinct_notIndexedFields() { final long numberOfObjects = 10; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).distinct(field); - fail(field); - } catch (IllegalArgumentException ignored) { - } + RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) + .distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL); + assertEquals(2, distinctBool.size()); + for (String field : new String[]{AnnotationIndexTypes.FIELD_NOT_INDEX_LONG, + AnnotationIndexTypes.FIELD_NOT_INDEX_DATE, AnnotationIndexTypes.FIELD_NOT_INDEX_STRING}) { + RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field); + assertEquals(field, numberOfBlocks, distinct.size()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 2a996bf726..d6ad811457 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -267,12 +267,14 @@ public void distinct_notIndexedFields() { final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).findAll().distinct(field); - fail(field); - } catch (IllegalArgumentException ignored) { - } + RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) + .findAll().distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL); + assertEquals(2, distinctBool.size()); + for (String field : new String[]{AnnotationIndexTypes.FIELD_NOT_INDEX_LONG, + AnnotationIndexTypes.FIELD_NOT_INDEX_DATE, AnnotationIndexTypes.FIELD_NOT_INDEX_STRING}) { + RealmResults distinct = realm.where(AnnotationIndexTypes.class).findAll() + .distinct(field); + assertEquals(field, numberOfBlocks, distinct.size()); } } @@ -542,18 +544,81 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void distinctAsync_notIndexedFields() { + final AtomicInteger changeListenerCalled = new AtomicInteger(4); + Realm realm = looperThread.realm; final long numberOfBlocks = 25; final long numberOfObjects = 10; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(field); - fail(field); - } catch (IllegalArgumentException ignored) { + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll() + .distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).findAll() + .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_LONG); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll() + .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_DATE); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll() + .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_STRING); + + assertFalse(distinctBool.isLoaded()); + assertTrue(distinctBool.isValid()); + assertTrue(distinctBool.isEmpty()); + + assertFalse(distinctLong.isLoaded()); + assertTrue(distinctLong.isValid()); + assertTrue(distinctLong.isEmpty()); + + assertFalse(distinctDate.isLoaded()); + assertTrue(distinctDate.isValid()); + assertTrue(distinctDate.isEmpty()); + + assertFalse(distinctString.isLoaded()); + assertTrue(distinctString.isValid()); + assertTrue(distinctString.isEmpty()); + + final Runnable endTest = new Runnable() { + @Override + public void run() { + if (changeListenerCalled.decrementAndGet() == 0) { + looperThread.testComplete(); + } } - } - looperThread.testComplete(); + }; + + looperThread.keepStrongReference.add(distinctBool); + looperThread.keepStrongReference.add(distinctLong); + looperThread.keepStrongReference.add(distinctDate); + looperThread.keepStrongReference.add(distinctString); + distinctBool.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(2, distinctBool.size()); + endTest.run(); + } + }); + + distinctLong.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(numberOfBlocks, distinctLong.size()); + endTest.run(); + } + }); + + distinctDate.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(numberOfBlocks, distinctDate.size()); + endTest.run(); + } + }); + + distinctString.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults object) { + assertEquals(numberOfBlocks, distinctString.size()); + endTest.run(); + } + }); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java index b5c26f71fd..1212415ce0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java @@ -120,16 +120,6 @@ public void getInstanceForDistinct_multipleFields() { assertEquals(intColumn, sortDescriptor.getColumnIndices()[1][0]); } - @Test - public void getInstanceForDistinct_shouldThrowIfNoSearchIndex() { - RealmFieldType type = RealmFieldType.STRING; - table.addColumn(type, type.name()); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("must be indexed"); - SortDescriptor.getInstanceForDistinct(table, type.name()); - } - @Test public void getInstanceForDistinct_shouldThrowOnInvalidField() { List types = new ArrayList(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 04de504455..5b8f7e1b1e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1521,11 +1521,13 @@ public RealmQuery isNotEmpty(String fieldName) { * Returns a distinct set of objects of a specific class. If the result is sorted, the first * object will be returned in case of multiple occurrences, otherwise it is undefined which * object is returned. + *

                    + * Adding {@link io.realm.annotations.Index} to the corresponding field will make this operation much faster. * * @param fieldName the field name. * @return a non-null {@link RealmResults} containing the distinct objects. - * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, - * is not indexed, or points to linked fields. + * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, or points + * to linked fields. */ public RealmResults distinct(String fieldName) { realm.checkIfValid(); @@ -1538,13 +1540,14 @@ public RealmResults distinct(String fieldName) { * Asynchronously returns a distinct set of objects of a specific class. If the result is * sorted, the first object will be returned in case of multiple occurrences, otherwise it is * undefined which object is returned. + * Adding {@link io.realm.annotations.Index} to the corresponding field will make this operation much faster. * * @param fieldName the field name. * @return immediately a {@link RealmResults}. Users need to register a listener * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the * query completes. - * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, - * is not indexed, or points to linked fields. + * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, or points + * to linked fields. */ public RealmResults distinctAsync(String fieldName) { realm.checkIfValid(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java index 3d6b6cfc21..e72e44c65f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java @@ -120,11 +120,6 @@ private static void checkFieldTypeForDistinct(FieldDescriptor descriptor, String "Distinct is not supported on '%s' field '%s' in '%s'.", descriptor.getFieldType().toString(), descriptor.getFieldName(), fieldDescriptions)); } - if (!descriptor.hasSearchIndex()) { - throw new IllegalArgumentException(String.format( - "Field '%s' in '%s' must be indexed in order to use it for distinct queries.", - descriptor.getFieldName(), fieldDescriptions)); - } } // Called by JNI. From 3b3695ce5881452bf0698b6da311a8f39967f721 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 30 Mar 2017 12:03:32 +0200 Subject: [PATCH 0585/2110] Sync Progress Notifications (#4349) --- CHANGELOG.md | 1 + examples/objectServerExample/build.gradle | 2 + .../objectserver/CounterActivity.java | 60 ++++ .../src/main/res/layout/activity_counter.xml | 15 +- .../src/main/res/values/realm_colors.xml | 5 + .../java/io/realm/ProgressTests.java | 72 ++++ .../java/io/realm/SessionTests.java | 48 +++ .../src/main/cpp/io_realm_SyncSession.cpp | 75 +++- .../src/main/cpp/io_realm_internal_Util.cpp | 7 + .../src/main/cpp/jni_util/java_local_ref.hpp | 3 +- realm/realm-library/src/main/cpp/util.cpp | 4 + realm/realm-library/src/main/cpp/util.hpp | 4 + .../java/io/realm/internal/util/Pair.java | 88 +++++ .../objectServer/java/io/realm/Progress.java | 131 +++++++ .../java/io/realm/ProgressListener.java | 56 +++ .../java/io/realm/ProgressMode.java | 47 +++ .../java/io/realm/SyncManager.java | 22 +- .../java/io/realm/SyncSession.java | 122 +++++++ .../BaseIntegrationTest.java | 36 +- .../java/io/realm/objectserver/AuthTests.java | 1 + .../objectserver/ManagementRealmTests.java | 1 + .../objectserver/ProcessCommitTests.java | 1 + .../objectserver/ProgressListenerTests.java | 338 ++++++++++++++++++ .../realm/objectserver/utils/Constants.java | 5 +- 24 files changed, 1130 insertions(+), 14 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/util/Pair.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/Progress.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java rename realm/realm-library/src/syncIntegrationTest/java/io/realm/{objectserver => }/BaseIntegrationTest.java (59%) create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 76a2b9a1b7..5403963378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * Linking objects are not yet supported on dynamic objects * Migration for linking objects is not yet supported. * Backlink verification is incomplete. Evil code can cause native crashes. +* [ObjectServer] Added support for Sync Progress Notifications through `SyncSession.addDownloadProgressListener(ProgressMode, ProgressListener)` and `SyncSession.addUploadProgressListener(ProgressMode, ProgressListener)` (#4104). * [ObjectServer] In case of a Client Reset, information about the location of the backed up Realm file is now reported through the `ErrorHandler` interface (#4080). * [ObjectServer] Authentication URLs now automatically append `/auth` if no other path segment is set (#4370). * The listener on `RealmObject` will only be triggered if the object changes (#3894). diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 8a42675c8a..db813f22ad 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -61,7 +61,9 @@ realm { dependencies { compile 'com.android.support:support-v4:25.2.0' + compile 'com.android.support:appcompat-v7:25.2.0' compile 'com.android.support:design:25.2.0' + compile 'me.zhanghai.android.materialprogressbar:library:1.3.0' compile 'com.jakewharton:butterknife:8.3.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.3.0' } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 97c67d2443..536b24ef90 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -17,32 +17,67 @@ package io.realm.examples.objectserver; import android.content.Intent; +import android.graphics.PorterDuff; import android.os.Bundle; +import android.support.annotation.ColorRes; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.widget.TextView; import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; +import io.realm.Progress; +import io.realm.ProgressListener; +import io.realm.ProgressMode; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.examples.objectserver.model.CRDTCounter; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; public class CounterActivity extends AppCompatActivity { private static final String REALM_URL = "realm://" + BuildConfig.OBJECT_SERVER_IP + ":9080/~/default"; private Realm realm; + private SyncSession session; private CRDTCounter counter; private SyncUser user; + private AtomicBoolean downloadingChanges = new AtomicBoolean(false); + private AtomicBoolean uploadingChanges = new AtomicBoolean(false); + private ProgressListener downloadListener = new ProgressListener() { + @Override + public void onChange(Progress progress) { + downloadingChanges.set(!progress.isTransferComplete()); + runOnUiThread(updateProgressBar); + } + }; + private ProgressListener uploadListener = new ProgressListener() { + @Override + public void onChange(Progress progress) { + uploadingChanges.set(!progress.isTransferComplete()); + runOnUiThread(updateProgressBar); + } + }; + private Runnable updateProgressBar = new Runnable() { + @Override + public void run() { + updateProgressBar(downloadingChanges.get(), uploadingChanges.get()); + } + }; + @BindView(R.id.text_counter) TextView counterView; + @BindView(R.id.progressbar) MaterialProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { @@ -86,12 +121,21 @@ public void onChange(CRDTCounter counter) { } }); counterView.setText("0"); + + // Setup progress listeners for indeterminate progress bars + session = SyncManager.getSession(config); + session.addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener); + session.addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener); } } @Override protected void onStop() { super.onStop(); + if (session != null) { + session.removeProgressListener(downloadListener); + session.removeProgressListener(uploadListener); + } closeRealm(); user = null; } @@ -132,6 +176,22 @@ public void decrementCounter() { adjustCounter(-1); } + private void updateProgressBar(boolean downloading, boolean uploading) { + @ColorRes int color = android.R.color.black; + int visibility = View.VISIBLE; + if (downloading && uploading) { + color = R.color.progress_both; + } else if (downloading) { + color = R.color.progress_download; + } else if (uploading) { + color = R.color.progress_upload; + } else { + visibility = View.GONE; + } + progressBar.getIndeterminateDrawable().setColorFilter(getResources().getColor(color), PorterDuff.Mode.SRC_IN); + progressBar.setVisibility(visibility); + } + private void adjustCounter(final int adjustment) { // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI // thread is HIGHLY discouraged as it might block longer than intended. Only use async transactions. diff --git a/examples/objectServerExample/src/main/res/layout/activity_counter.xml b/examples/objectServerExample/src/main/res/layout/activity_counter.xml index 62127eca0d..df73031aa7 100644 --- a/examples/objectServerExample/src/main/res/layout/activity_counter.xml +++ b/examples/objectServerExample/src/main/res/layout/activity_counter.xml @@ -1,7 +1,9 @@ + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + diff --git a/examples/objectServerExample/src/main/res/values/realm_colors.xml b/examples/objectServerExample/src/main/res/values/realm_colors.xml index aada8ea195..8fec3b9456 100644 --- a/examples/objectServerExample/src/main/res/values/realm_colors.xml +++ b/examples/objectServerExample/src/main/res/values/realm_colors.xml @@ -20,4 +20,9 @@ #d64881 #dadada + // Progress bar colors + #EF5350 + #9CCC65 + #FFA726 + \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java new file mode 100644 index 0000000000..22c31bc2a5 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class ProgressTests { + + @Test + public void getFractionTransferred() { + Object[][] testData = { + { 0L, 0L, 1.0D }, + { 0L, 1L, 0.0D }, + { 1L, 1L, 1.0D }, + { 1L, 2L, 0.5D } + }; + + for (Object[] test : testData) { + long transferredBytes = (long) test[0]; + long transferableBytes = (long) test[1]; + double fraction = (double) test[2]; + Progress progress = new Progress(transferredBytes, transferableBytes); + String errorMessage = String.format(Locale.US, "Failed with: (%d, %d)", transferredBytes, transferableBytes); + assertEquals(errorMessage, fraction, progress.getFractionTransferred(), 0.0D); + } + } + + @Test + public void getTransferredBytes () { + long[] testData = { 0, Long.MAX_VALUE }; + + for (long transferredBytes : testData) { + String errorMessage = String.format(Locale.US, "Failed with: %d", transferredBytes); + Progress progress = new Progress(transferredBytes, Long.MAX_VALUE); + assertEquals(errorMessage, transferredBytes, progress.getTransferredBytes()); + } + } + + @Test + public void getTransferableBytes () { + long[] testData = { 0, Long.MAX_VALUE }; + + for (long transferableBytes : testData) { + String errorMessage = String.format(Locale.US, "Failed with: %d", transferableBytes); + Progress progress = new Progress(0, transferableBytes); + assertEquals(errorMessage, transferableBytes, progress.getTransferableBytes()); + } + } + +} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 2c53ce2bd5..3376d0a512 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -61,6 +61,54 @@ public void get_syncValues() { assertEquals(configuration, session.getConfiguration()); } + @Test + public void addDownloadProgressListener_nullThrows() { + SyncSession session = SyncManager.getSession(configuration); + try { + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void addUploadProgressListener_nullThrows() { + SyncSession session = SyncManager.getSession(configuration); + try { + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void removeProgressListener() { + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getSession(configuration); + ProgressListener[] listeners = new ProgressListener[] { + null, + new ProgressListener() { + @Override + public void onChange(Progress progress) { + // Listener 1, not present + } + }, + new ProgressListener() { + @Override + public void onChange(Progress progress) { + // Listener 2, present + } + } + }; + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, listeners[2]); + + // Check that remove works unconditionally for all input + for (ProgressListener listener : listeners) { + session.removeProgressListener(listener); + } + realm.close(); + } + // Check that a Client Reset is correctly reported. @Test @RunTestInLooperThread diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index e3a58bbad2..88e80e151f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "io_realm_SyncSession.h" @@ -22,22 +23,22 @@ #include "object-store/src/sync/sync_session.hpp" #include "util.hpp" +#include "jni_util/jni_utils.hpp" -using namespace std; using namespace realm; using namespace sync; JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JNIEnv* env, jclass, - jstring localRealmPath, - jstring accessToken, + jstring j_local_realm_path, + jstring j_access_token, jstring sync_realm_url) { TR_ENTER() try { - JStringAccessor local_realm_path(env, localRealmPath); + JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); if (session) { - JStringAccessor access_token(env, accessToken); + JStringAccessor access_token(env, j_access_token); JStringAccessor realm_url(env, sync_realm_url); session->refresh_access_token(access_token, std::string(realm_url)); return JNI_TRUE; @@ -49,3 +50,67 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JN CATCH_STD() return JNI_FALSE; } + + +JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIEnv* env, jclass, + jstring j_local_realm_path, + jlong listener_id, jint direction, + jboolean is_streaming) +{ + try { + // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda + realm::StringData local_realm_path(JStringAccessor(env, j_local_realm_path)); + std::shared_ptr session = SyncManager::shared().get_existing_active_session(local_realm_path); + if (!session) { + // FIXME: We should lift this restriction + ThrowException(env, IllegalState, + "Cannot register a progress listener before a session is " + "created. A session will be created after the first call to Realm.getInstance()."); + return static_cast(0); + } + + SyncSession::NotifierType type = + (direction == 1) ? SyncSession::NotifierType::download : SyncSession::NotifierType::upload; + + std::function callback = [local_realm_path, listener_id]( + uint64_t transferred, uint64_t transferrable) { + JNIEnv* local_env = jni_util::JniUtils::get_env(true); + + auto path = to_jstring(local_env, local_realm_path); + local_env->CallStaticVoidMethod(java_syncmanager_class, java_notify_progress_listener, path, listener_id, + static_cast(transferred), static_cast(transferrable)); + + // All exceptions will be caught on the Java side of handlers, but errors will still end + // up here, so we need to do something sensible with them. + // Throwing a C++ exception will terminate the sync thread and cause the pending Java + // exception to become visible. For some (unknown) reason Logcat will not see the C++ + // exception, only the Java one. + if (local_env->ExceptionCheck()) { + local_env->ExceptionDescribe(); + throw std::runtime_error("An unexpected Error was thrown from Java. See LogCat"); + } + + // Callback happens on a thread not controlled by the JVM. So manual cleanup is + // required. + local_env->DeleteLocalRef(path); + }; + uint64_t token = session->register_progress_notifier(callback, type, to_bool(is_streaming)); + return static_cast(token); + } + CATCH_STD() + return static_cast(0); +} + +JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveProgressListener(JNIEnv* env, jclass, + jstring j_local_realm_path, + jlong listener_token) +{ + try { + JStringAccessor local_realm_path(env, j_local_realm_path); + std::shared_ptr session = SyncManager::shared().get_existing_active_session(local_realm_path); + if (session) { + session->unregister_progress_notifier(static_cast(listener_token)); + } + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 48e47cf726..7946856b1c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -53,6 +53,10 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); java_util_date = GetClass(env, "java/util/Date"); java_util_date_init = env->GetMethodID(java_util_date, "", "(J)V"); +#if REALM_ENABLE_SYNC + java_syncmanager_class = GetClass(env, "io/realm/SyncManager"); + java_notify_progress_listener = env->GetStaticMethodID(java_syncmanager_class, "notifyProgressListener", "(Ljava/lang/String;JJJ)V"); +#endif } return JNI_VERSION_1_6; @@ -70,6 +74,9 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) env->DeleteGlobalRef(java_lang_double); env->DeleteGlobalRef(java_util_date); env->DeleteGlobalRef(java_lang_string); + #if REALM_ENABLE_SYNC + env->DeleteGlobalRef(java_syncmanager_class); + #endif JniUtils::release(); } } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp index 283c948fba..f29c5a3335 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp @@ -29,8 +29,7 @@ static constexpr NeedToCreateLocalRef need_to_create_local_ref{}; // Wraps jobject and automatically calls DeleteLocalRef when this object is destroyed. // DeleteLocalRef is not necessary to be called in most cases since all local references will be cleaned up when the // program returns to Java from native. But if the local ref is created in a loop, consider to use this class to wrap -// it -// because the size of local reference table is relative small (512 bytes on Android). +// it because the size of local reference table is relative small (512 bytes on Android). template class JavaLocalRef { public: diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index e562c12564..8908d1ae3e 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -43,6 +43,10 @@ jclass java_lang_string; jmethodID java_lang_double_init; jclass java_util_date; jmethodID java_util_date_init; +#if REALM_ENABLE_SYNC +jclass java_syncmanager_class; +jmethodID java_notify_progress_listener; +#endif void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index c0ec632f62..615887dfa7 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -703,6 +703,10 @@ extern jclass java_lang_string; extern jmethodID java_lang_double_init; extern jclass java_util_date; extern jmethodID java_util_date_init; +#if REALM_ENABLE_SYNC +extern jclass java_syncmanager_class; +extern jmethodID java_notify_progress_listener; +#endif inline jobject NewLong(JNIEnv* env, int64_t value) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java new file mode 100644 index 0000000000..cac85f8077 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2009 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. + */ + +package io.realm.internal.util; + +/** + * Copy from the Android framework to avoid the dependency on Android classes + slight adjustment + * to support older versions of Android. + * + * Container to ease passing around a tuple of two objects. This object provides a sensible + * implementation of equals(), returning true if equals() is true on each of the contained + * objects. + */ +public class Pair { + public F first; + public S second; + + /** + * Constructor for a Pair. + * + * @param first the first object in the Pair. + * @param second the second object in the pair. + */ + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + /** + * Checks the two objects for equality by delegating to their respective + * {@link Object#equals(Object)} methods. + * + * @param o the {@link Pair} to which this one is to be checked for equality. + * @return true if the underlying objects of the Pair are both considered + * equal. + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Pair)) { + return false; + } + Pair p = (Pair) o; + return equals(p.first, first) && (equals(p.second, second)); + } + + private boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); + } + + /** + * Compute a hash code using the hash codes of the underlying objects. + * + * @return a hashcode of the Pair. + */ + @Override + public int hashCode() { + return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); + } + + @Override + public String toString() { + return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}"; + } + + /** + * Convenience method for creating an appropriately typed pair. + * + * @param a the first object in the Pair. + * @param b the second object in the pair. + * @return a Pair that is templatized with the types of a and b. + */ + public static Pair create(A a, B b) { + return new Pair(a, b); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/Progress.java b/realm/realm-library/src/objectServer/java/io/realm/Progress.java new file mode 100644 index 0000000000..db4e7cdc9a --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/Progress.java @@ -0,0 +1,131 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * Class used to encapsulate progress notifications when either downloading or uploading Realm data. + * Each instance of this class is an immutable snapshot of the current progress. + *

                    + * If the {@link ProgressListener} was registered with {@link ProgressMode#INDEFINITELY}, the progress reported by + * {@link #getFractionTransferred()} can both increase and decrease since more changes might be added while + * the progres listener is registered. This means it is possible for one notification to report + * {@code true} for {@link #isTransferComplete()}, and then on the next event report {@code false}. + *

                    + * If the {@link ProgressListener} was registered with {@link ProgressMode#CURRENT_CHANGES}, progress can only ever + * increase, and once {@link #isTransferComplete()} returns {@code true}, no further events will be generated. + * + * @see SyncSession#addDownloadProgressListener(ProgressMode, ProgressListener) + * @see SyncSession#addUploadProgressListener(ProgressMode, ProgressListener) + */ +public class Progress { + + private final long transferredBytes; + private final long transferableBytes; + + /** + * Creates a snapshot of the current progress when downloading or uploading changes. + * + * @param transferredBytes number of bytes transferred. + * @param transferableBytes total number of bytes that needs to be transferred (including those already transferred). + */ + Progress(long transferredBytes, long transferableBytes) { + this.transferredBytes = transferredBytes; + this.transferableBytes = transferableBytes; + } + + /** + * Returns the total number of bytes that has been transferred since the {@link ProgressListener} was added. + * + * @return the total number of bytes transferred since the {@link ProgressListener} was added. + */ + public long getTransferredBytes() { + return transferredBytes; + } + + /** + * Returns the total number of transferable bytes (bytes that have been transferred + bytes pending transfer). + *

                    + * If the {@link ProgressListener} is tracking downloads, this number represents the size of the changesets + * generated by all other clients using the Realm. + *

                    + * If the {@link ProgressListener} is tracking uploads, this number represents the size of changesets created + * locally. + * + * @return the total number of bytes that has been transferred + number of bytes still pending transfer. + */ + public long getTransferableBytes() { + return transferableBytes; + } + + /** + * The fraction of bytes transferred out of all transferable bytes. Counting from since the {@link ProgressListener} + * was added. + * + * @return a number between {@code 0.0} and {@code 1.0}, where {@code 0.0} represents that no data has been + * transferred yet, and {@code 1.0} that all data has been transferred. + */ + public double getFractionTransferred() { + if (transferableBytes == 0) { + return 1.0D; + } else { + double percentage = (double) transferredBytes / (double) transferableBytes; + return percentage > 1.0D ? 1.0D : percentage; + } + } + + /** + * Returns {@code true} when all pending bytes have been transferred. + *

                    + * If the {@link ProgressListener} was registered with {@link ProgressMode#INDEFINITELY}, this method can return + * {@code false} for subsequent events after returning {@code true}. + *

                    + * If the {@link ProgressListener} was registered with {@link ProgressMode#CURRENT_CHANGES}, when this method + * returns {@code true}, no more progress events will be sent. + * + * @return {@code true} if all changes have been transferred, {@code false} otherwise. + */ + public boolean isTransferComplete() { + return transferredBytes >= transferableBytes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Progress progress = (Progress) o; + + if (transferredBytes != progress.transferredBytes) return false; + return transferableBytes == progress.transferableBytes; + + } + + @Override + public int hashCode() { + int result = (int) (transferredBytes ^ (transferredBytes >>> 32)); + result = 31 * result + (int) (transferableBytes ^ (transferableBytes >>> 32)); + return result; + } + + @Override + public String toString() { + return "Progress{" + + "transferredBytes=" + transferredBytes + + ", transferableBytes=" + transferableBytes + + '}'; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java b/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java new file mode 100644 index 0000000000..5b5798f2d4 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * Interface used when interested in updates on data either being uploaded to or downloaded from + * a Realm Object Server. + */ +public interface ProgressListener { + /** + * This method will be called periodically from the underlying Object Server Client responsible + * for uploading and downloading changes from the remote Object Server. + *

                    + * This callback will not happen on the UI thread, but on the worker thread controlling + * the Object Server Client. Use {@code Activity.runOnUiThread(Runnable)} or similar to update + * any UI elements. + *

                    + *

                    +     * {@code
                    +     * // Adding an upload progress listener that completes when all known changes have been
                    +     * // uploaded.
                    +     * session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() {
                    +     *   \@Override
                    +     *    public void onChange(Progress progress) {
                    +     *      activity.runOnUiThread(new Runnable() {
                    +     *        \@Override
                    +     *         public void run() {
                    +     *           updateProgressBar(progress);
                    +     *         }
                    +     *      });
                    +     *      if (progress.isTransferComplete() {
                    +     *        session.removeProgressListener(this);
                    +     *      }
                    +     *    }
                    +     * });
                    +     * }
                    +     * 
                    + * + * @param progress an immutable progress change event with information about current progress. This object is thread safe. + */ + void onChange(Progress progress); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java b/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java new file mode 100644 index 0000000000..f80f63150d --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * Enum describing how to listen to progress changes. + */ +public enum ProgressMode { + /** + * When registering the {@link ProgressListener}, it will record the current size of changes, and will only + * continue to report progress updates until those changes have been either downloaded or uploaded. After that + * the progress listener will not report any further changes. + *

                    + * This means that listeners registered in this mode should be done before changes are written to + * the Realm. + *

                    + * Progress reported in this mode will only ever increase. + *

                    + * This is useful when e.g. reporting progress when downloading a Realm for the first time. + */ + CURRENT_CHANGES, + + /** + * A {@link ProgressListener} registered in this mode, will continue to report progress changes, even + * if changes are being added after the listener was registered. + *

                    + * Progress reported in this mode can both increase and decrease, e.g. if large amounts of data is + * written after registering the listener. + *

                    + * This is useful when you want to track if all changes have been uploaded to the server from the device. + */ + INDEFINITELY +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 691538c59b..fe72ce86fe 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -16,9 +16,9 @@ package io.realm; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -90,7 +90,7 @@ public void onClientResetRequired(SyncSession session, ClientResetHandler handle } }; // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path' - private static Map sessions = new HashMap(); + private static Map sessions = new ConcurrentHashMap<>(); private static CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList(); // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. @@ -248,6 +248,24 @@ private static synchronized void notifyErrorHandler(int errorCode, String errorM } } + /** + * All progress listener events from native Sync are reported to this method. + * It costs 2 HashMap lookups for each listener triggered (one to find the session, one to + * find the progress listener), but it means we don't have to cache anything on the C++ side which + * can leak since we don't have control over the session lifecycle. + */ + @SuppressWarnings("unused") + private static synchronized void notifyProgressListener(String localRealmPath, long listenerId, long transferedBytes, long transferableBytes) { + SyncSession session = sessions.get(localRealmPath); + if (session != null) { + try { + session.notifyProgressListener(listenerId, transferedBytes, transferableBytes); + } catch (Exception exception) { + RealmLog.error(exception); + } + } + } + /** * This is called from the Object Store (through JNI) to request an {@code access_token} for * the session specified by sessionPath. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index f2c40281ba..75736ce98f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -17,11 +17,16 @@ package io.realm; import java.net.URI; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import io.realm.internal.Keep; import io.realm.internal.KeepMember; @@ -33,6 +38,7 @@ import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; +import io.realm.internal.util.Pair; import io.realm.log.RealmLog; /** @@ -50,6 +56,8 @@ public class SyncSession { private final static ScheduledThreadPoolExecutor REFRESH_TOKENS_EXECUTOR = new ScheduledThreadPoolExecutor(1); private final static long REFRESH_MARGIN_DELAY = TimeUnit.SECONDS.toMillis(10); + private final static int DIRECTION_DOWNLOAD = 1; + private final static int DIRECTION_UPLOAD = 2; private final SyncConfiguration configuration; private final ErrorHandler errorHandler; @@ -60,6 +68,19 @@ public class SyncSession { private AtomicBoolean onGoingAccessTokenQuery = new AtomicBoolean(false); private volatile boolean isClosed = false; + // We need JavaId -> Listener so C++ can trigger callbacks without keeping a reference to the + // jobject, which would require a similar map on the C++ side. + // We need Listener -> Token map in order to remove the progress listener in C++ from Java. + private Map> listenerIdToProgressListenerMap = new HashMap<>(); + private Map progressListenerToOsTokenMap = new IdentityHashMap<>(); + // Counter used to assign all ProgressListeners on this session with a unique id. + // ListenerId is created by Java to enable C++ to reference the java listener without holding + // a reference to the actual object. + // ListenerToken is the same concept, but created by OS and represents the listener. + // We can unfortunately not just use the ListenerToken, since we need it to be available before + // we register the listener. + AtomicLong progressListenerId = new AtomicLong(-1); + SyncSession(SyncConfiguration configuration) { this.configuration = configuration; this.errorHandler = configuration.getErrorHandler(); @@ -110,6 +131,105 @@ void notifySessionError(int errorCode, String errorMessage) { } } + // Called from native code + @SuppressWarnings("unused") + @KeepMember + synchronized void notifyProgressListener(long listenerId, long transferredBytes, long transferableBytes) { + Pair listener = listenerIdToProgressListenerMap.get(listenerId); + if (listener != null) { + Progress newProgressNotification = new Progress(transferredBytes, transferableBytes); + if (!newProgressNotification.equals(listener.second)) { + listener.first.onChange(newProgressNotification); + listener.second = newProgressNotification; + } + } else { + RealmLog.debug("Trying unknown listener failed: " + listenerId); + } + } + + /** + * Adds a progress listener tracking changes that need to be downloaded from the Realm Object + * Server. + *

                    + * The {@link ProgressListener} will be triggered immediately when registered, and periodically + * afterwards. + * + * @param mode type of mode used. See {@link ProgressMode} for more information. + * @param listener the listener to register. + */ + public synchronized void addDownloadProgressListener(ProgressMode mode, ProgressListener listener) { + addProgressListener(mode, DIRECTION_DOWNLOAD, listener); + } + + /** + * Adds a progress listener tracking changes that need to be uploaded from the device to the + * Realm Object Server. + *

                    + * The {@link ProgressListener} will be triggered immediately when registered, and periodically + * afterwards. + * + * @param mode type of mode used. See {@link ProgressMode} for more information. + * @param listener the listener to register. + */ + public synchronized void addUploadProgressListener(ProgressMode mode, ProgressListener listener) { + addProgressListener(mode, DIRECTION_UPLOAD, listener); + } + + /** + * Removes a progress listener. If the listener wasn't registered, this method will do nothing. + * + * @param listener listener to remove. + */ + public synchronized void removeProgressListener(ProgressListener listener) { + if (listener == null) { + return; + } + // If an exception is thrown somewhere in here, we will most likely leave the various + // maps in an inconsistent manner. Not much we can do about it. + Long token = progressListenerToOsTokenMap.remove(listener); + if (token != null) { + Iterator>> it = listenerIdToProgressListenerMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry> entry = it.next(); + if (entry.getValue().first.equals(listener)) { + it.remove(); + break; + } + } + nativeRemoveProgressListener(configuration.getPath(), token); + } + } + + private void addProgressListener(ProgressMode mode, int direction, ProgressListener listener) { + checkProgressListenerArguments(mode, listener); + boolean isStreaming = (mode == ProgressMode.INDEFINITELY); + long listenerId = progressListenerId.incrementAndGet(); + + // A listener might be triggered immediately as part of `nativeAddProgressListener`, so + // we need to make sure it can be found by SyncManager.notifyProgressListener() + listenerIdToProgressListenerMap.put(listenerId, new Pair(listener, null)); + long listenerToken = nativeAddProgressListener(configuration.getPath(), listenerId , direction, isStreaming); + if (listenerToken == 0) { + // ObjectStore did not register the listener. This can happen if a + // listener is registered with ProgressMode.CURRENT_CHANGES and no changes actually + // exists. In that case the listener was triggered immediately and we just need + // to clean it up, since it will never be called again. + listenerIdToProgressListenerMap.remove(listenerId); + } else { + // Listener was properly registered. + progressListenerToOsTokenMap.put(listener, listenerToken); + } + } + + private void checkProgressListenerArguments(ProgressMode mode, ProgressListener listener) { + if (listener == null) { + throw new IllegalArgumentException("Non-null 'listener' required."); + } + if (mode == null) { + throw new IllegalArgumentException("Non-null 'mode' required."); + } + } + void close() { isClosed = true; if (networkRequest != null) { @@ -356,5 +476,7 @@ private void clearScheduledAccessTokenRefresh() { } private static native boolean nativeRefreshAccessToken(String path, String accessToken, String authURL); + private static native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); + private static native void nativeRemoveProgressListener(String localRealmPath, long listenerToken); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java similarity index 59% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java rename to realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 764b511ee7..57e50f7616 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -14,19 +14,31 @@ * limitations under the License. */ -package io.realm.objectserver; +package io.realm; import android.support.test.InstrumentationRegistry; import org.junit.AfterClass; import org.junit.BeforeClass; +import java.util.UUID; + +import io.realm.ObjectServerError; import io.realm.Realm; +import io.realm.SyncConfiguration; +import io.realm.SyncCredentials; import io.realm.SyncManager; +import io.realm.SyncSession; +import io.realm.SyncUser; import io.realm.log.RealmLog; +import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.HttpUtils; +import io.realm.objectserver.utils.UserFactory; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; -class BaseIntegrationTest { +public class BaseIntegrationTest { @BeforeClass public static void setUp () throws Exception { @@ -45,8 +57,28 @@ public static void setUp () throws Exception { public static void tearDown () throws Exception { try { HttpUtils.stopSyncServer(); + SyncManager.reset(); } catch (Exception e) { RealmLog.error("Failed to stop Sync Server", e); } } + + /** + * Login the admin user synchronously. + */ + public SyncUser loginAdminUser() { + SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); + SyncCredentials credentials = SyncCredentials.accessToken(admin.getAccessToken().value(), "custom-admin-user"); + return SyncUser.login(credentials, Constants.AUTH_URL); + } + + /** + * Create new random user and log in. + */ + public SyncUser loginUser() { + String id = UUID.randomUUID().toString(); + SyncCredentials credentials = SyncCredentials.usernamePassword(id, "password", true); + return SyncUser.login(credentials, Constants.AUTH_URL); + } + } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index b25ce7f907..89d598e1b8 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -6,6 +6,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import io.realm.BaseIntegrationTest; import io.realm.ClientResetHandler; import io.realm.ErrorCode; import io.realm.ObjectServerError; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index 2754115d44..5963837ab7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -26,6 +26,7 @@ import java.util.Date; import java.util.concurrent.atomic.AtomicReference; +import io.realm.BaseIntegrationTest; import io.realm.ClientResetHandler; import io.realm.ObjectServerError; import io.realm.Realm; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index ffb20cd32c..7d1d8c7a14 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -32,6 +32,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import io.realm.BaseIntegrationTest; import io.realm.ClientResetHandler; import io.realm.ObjectServerError; import io.realm.Realm; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java new file mode 100644 index 0000000000..54473d4ec7 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -0,0 +1,338 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import android.support.annotation.NonNull; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import io.realm.BaseIntegrationTest; +import io.realm.Progress; +import io.realm.ProgressListener; +import io.realm.ProgressMode; +import io.realm.Realm; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.SyncSession; +import io.realm.SyncUser; +import io.realm.TestHelper; +import io.realm.entities.AllTypes; +import io.realm.objectserver.utils.Constants; +import io.realm.rule.TestSyncConfigurationFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class ProgressListenerTests extends BaseIntegrationTest { + + private static final long TEST_SIZE = 10; + @Rule + public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @NonNull + private SyncConfiguration createSyncConfig() { + SyncUser user = loginAdminUser(); + return configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL).build(); + } + + private void writeSampleData(Realm realm) { + realm.beginTransaction(); + for (int i = 0; i < TEST_SIZE; i++) { + AllTypes obj = realm.createObject(AllTypes.class); + obj.setColumnString("Object " + i); + } + realm.commitTransaction(); + } + + private void assertTransferComplete(Progress progress, boolean nonZeroChange) { + assertTrue(progress.isTransferComplete()); + assertEquals(1.0D, progress.getFractionTransferred(), 0.0D); + assertEquals(progress.getTransferableBytes(), progress.getTransferredBytes()); + if (nonZeroChange) { + assertTrue(progress.getTransferredBytes() > 0); + } + } + + // Create remote data for a given user. + private URI createRemoteData(SyncUser user) { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_USER_REALM).build(); + final Realm realm = Realm.getInstance(config); + writeSampleData(realm); + final CountDownLatch changesUploaded = new CountDownLatch(1); + final SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + session.removeProgressListener(this); + changesUploaded.countDown(); + } + } + }); + TestHelper.awaitOrFail(changesUploaded); + realm.close(); + return config.getServerUrl(); + } + + @Test + public void downloadProgressListener_changesOnly() { + final CountDownLatch allChangesDownloaded = new CountDownLatch(1); + SyncUser userWithData = loginUser(); + URI serverUrl = createRemoteData(userWithData); + SyncUser adminUser = loginAdminUser(); + + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()).build(); + Realm realm = Realm.getInstance(config); + SyncSession session = SyncManager.getSession(config); + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + assertTransferComplete(progress, true); + Realm realm = Realm.getInstance(config); + assertEquals(TEST_SIZE, realm.where(AllTypes.class).count()); + realm.close(); + allChangesDownloaded.countDown(); + } + } + }); + TestHelper.awaitOrFail(allChangesDownloaded); + realm.close(); + userWithData.logout(); + adminUser.logout(); + } + + @Test + public void downloadProgressListener_indefinitely() throws InterruptedException { + final AtomicInteger transferCompleted = new AtomicInteger(0); + final CountDownLatch allChangesDownloaded = new CountDownLatch(1); + final CountDownLatch startWorker = new CountDownLatch(1); + final SyncUser userWithData = loginUser(); + + URI serverUrl = createRemoteData(userWithData); + + // Create worker thread that puts data into another Realm. + // This is to avoid blocking one progress listener while waiting for another to complete. + Thread worker = new Thread(new Runnable() { + @Override + public void run() { + TestHelper.awaitOrFail(startWorker); + createRemoteData(userWithData); + } + }); + worker.start(); + + SyncUser adminUser = loginAdminUser(); + final SyncConfiguration adminConfig = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()).build(); + Realm adminRealm = Realm.getInstance(adminConfig); + Realm userRealm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(userWithData, Constants.SYNC_USER_REALM).build()); // Keep session alive + SyncSession session = SyncManager.getSession(adminConfig); + session.addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + switch (transferCompleted.incrementAndGet()) { + case 1: + // Initial trigger when registering + assertTransferComplete(progress, false); + break; + case 2: { + assertTransferComplete(progress, true); + Realm adminRealm = Realm.getInstance(adminConfig); + assertEquals(TEST_SIZE, adminRealm.where(AllTypes.class).count()); + adminRealm.close(); + startWorker.countDown(); + break; + } + case 3: { + assertTransferComplete(progress, true); + Realm adminRealm = Realm.getInstance(adminConfig); + assertEquals(TEST_SIZE * 2, adminRealm.where(AllTypes.class).count()); + adminRealm.close(); + allChangesDownloaded.countDown(); + break; + } + default: + fail(); + } + } + } + }); + TestHelper.awaitOrFail(allChangesDownloaded); + adminRealm.close(); + userRealm.close(); + userWithData.logout(); + adminUser.logout(); + worker.join(); + } + + @Test + public void uploadProgressListener_changesOnly() { + final CountDownLatch allChangeUploaded = new CountDownLatch(1); + SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + + SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + assertTransferComplete(progress, true); + allChangeUploaded.countDown(); + } + } + }); + + TestHelper.awaitOrFail(allChangeUploaded); + realm.close(); + } + + @Test + public void uploadProgressListener_indefinitely() { + final AtomicInteger transferCompleted = new AtomicInteger(0); + final CountDownLatch testDone = new CountDownLatch(1); + final SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + + writeSampleData(realm); // Write first batch of sample data + SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + switch(transferCompleted.incrementAndGet()) { + case 1: + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + realm.close(); + break; + case 2: + assertTransferComplete(progress, true); + testDone.countDown(); + break; + default: + fail("Unsupported number of transfers completed: " + transferCompleted.get()); + } + } + } + }); + + TestHelper.awaitOrFail(testDone); + realm.close(); + } + + @Test + public void addListenerInsideCallback() { + final CountDownLatch allChangeUploaded = new CountDownLatch(1); + final SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + + final SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + realm.close(); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + allChangeUploaded.countDown(); + } + } + }); + } + } + }); + + TestHelper.awaitOrFail(allChangeUploaded); + realm.close(); + } + + @Test + public void addListenerInsideCallback_mixProgressModes() { + final CountDownLatch allChangeUploaded = new CountDownLatch(3); + final AtomicBoolean progressCompletedReported = new AtomicBoolean(false); + final SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + + final SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + allChangeUploaded.countDown(); + if (progressCompletedReported.compareAndSet(false, true)) { + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + realm.close(); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + allChangeUploaded.countDown(); + } + } + }); + } + } + } + }); + + TestHelper.awaitOrFail(allChangeUploaded); + realm.close(); + } + + @Test + public void addProgressListener_triggerImmediatelyWhenRegistered() { + final SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + SyncSession session = SyncManager.getSession(config); + + checkListener(session, ProgressMode.INDEFINITELY); + checkListener(session, ProgressMode.CURRENT_CHANGES); + + realm.close(); + } + + private void checkListener(SyncSession session, ProgressMode progressMode) { + final CountDownLatch listenerCalled = new CountDownLatch(1); + session.addDownloadProgressListener(progressMode, new ProgressListener() { + @Override + public void onChange(Progress progress) { + listenerCalled.countDown(); + } + }); + TestHelper.awaitOrFail(listenerCalled); + } + +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java index e5347effc8..a4c11802dd 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -18,8 +18,9 @@ public class Constants { - public static String SYNC_SERVER_URL = "realm://127.0.0.1/tests"; - public static String SYNC_SERVER_URL_2 = "realm://127.0.0.1/tests2"; + public static String SYNC_USER_REALM = "realm://127.0.0.1:9080/~/tests"; + public static String SYNC_SERVER_URL = "realm://127.0.0.1:9080/tests"; + public static String SYNC_SERVER_URL_2 = "realm://127.0.0.1:9080/tests2"; public static String AUTH_SERVER_URL = "http://127.0.0.1:9080/"; public static String AUTH_URL = AUTH_SERVER_URL + "auth"; From c48de88ab577be80f68e43229f296e3f556fc60d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 30 Mar 2017 15:39:24 +0200 Subject: [PATCH 0586/2110] Mark @LinkingObjects as beta (#4404) --- .../src/main/java/io/realm/annotations/LinkingObjects.java | 1 + 1 file changed, 1 insertion(+) diff --git a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java index f9b130d0ca..d79258c51d 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java +++ b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java @@ -94,6 +94,7 @@ */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) +@Beta public @interface LinkingObjects { /** * The name of a field that contains a relation to an instance of the From 67c5bb4db9a9080130614773eb328fae19f4c153 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Thu, 30 Mar 2017 12:27:13 -0700 Subject: [PATCH 0587/2110] Refactor Schemas into separate native and realm-based implementations (#4382) * Refactor Schemas into separate native and realm-based implementations * Fix all tests * Respond to PR comments * Temporary (git add .) kludge to fix visiblity problem --- .../java/io/realm/RealmMigrationTests.java | 2 +- .../java/io/realm/RealmObjectSchemaTests.java | 2 +- .../java/io/realm/RealmSchemaTests.java | 2 +- .../androidTest/java/io/realm/RealmTests.java | 20 +- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- ...mSchema.cpp => io_realm_OsRealmSchema.cpp} | 8 +- .../src/main/java/io/realm/BaseRealm.java | 14 +- .../java/io/realm/DynamicRealmObject.java | 4 +- .../src/main/java/io/realm/OsRealmSchema.java | 158 +++++++++ .../src/main/java/io/realm/Realm.java | 58 ++-- .../src/main/java/io/realm/RealmCache.java | 10 +- .../src/main/java/io/realm/RealmList.java | 12 +- .../main/java/io/realm/RealmObjectSchema.java | 51 +-- .../src/main/java/io/realm/RealmQuery.java | 37 +-- .../src/main/java/io/realm/RealmSchema.java | 308 ++---------------- .../java/io/realm/StandardRealmSchema.java | 274 ++++++++++++++++ .../java/io/realm/internal/SharedRealm.java | 21 +- 17 files changed, 590 insertions(+), 393 deletions(-) rename realm/realm-library/src/main/cpp/{io_realm_RealmSchema.cpp => io_realm_OsRealmSchema.cpp} (86%) create mode 100644 realm/realm-library/src/main/java/io/realm/OsRealmSchema.java create mode 100644 realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 7a5d21a4e5..1e04dca3e3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -1300,7 +1300,7 @@ public void migrationRequired_throwsOriginalException() { } } } - + // TODO Add unit tests for default nullability // TODO Add unit tests for default Indexing for Primary keys } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 01745e62f6..2f5a772106 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -48,7 +48,7 @@ public class RealmObjectSchemaTests { private RealmObjectSchema DOG_SCHEMA; private DynamicRealm realm; private RealmObjectSchema schema; - private RealmSchema realmSchema; + private StandardRealmSchema realmSchema; @Before public void setUp() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index da49a66229..86e5985fc8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -47,7 +47,7 @@ public class RealmSchemaTests { public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); private DynamicRealm realm; - private RealmSchema realmSchema; + private StandardRealmSchema realmSchema; @Before public void setUp() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 92774d8fee..243ef1c246 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3690,34 +3690,34 @@ public void run(Realm realm) { @Test public void schemaIndexCacheIsUpdatedAfterSchemaChange() { - final CatRealmProxy.CatColumnInfo catColumnInfo; - catColumnInfo = (CatRealmProxy.CatColumnInfo) realm.schema.columnIndices.getColumnInfo(Cat.class); + final AtomicLong nameIndexNew = new AtomicLong(-1L); + // get the pre-update index for the "name" column. + CatRealmProxy.CatColumnInfo catColumnInfo + = (CatRealmProxy.CatColumnInfo) realm.schema.getColumnIndices().getColumnInfo(Cat.class); final long nameIndex = catColumnInfo.nameIndex; - final AtomicLong nameIndexNew = new AtomicLong(-1L); - // Changes column index of "name". + // Change the index of the column "name". realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { final Table catTable = realm.getSchema().getTable(Cat.CLASS_NAME); final long nameIndex = catTable.getColumnIndex(Cat.FIELD_NAME); catTable.removeColumn(nameIndex); - final long newIndex = catTable.addColumn(RealmFieldType.STRING, - Cat.FIELD_NAME, true); - + final long newIndex = catTable.addColumn(RealmFieldType.STRING, Cat.FIELD_NAME, true); realm.setVersion(realm.getConfiguration().getSchemaVersion() + 1); - nameIndexNew.set(newIndex); } }); + // We need to update index cache if the schema version was changed in the same thread. realm.sharedRealm.invokeSchemaChangeListenerIfSchemaChanged(); - // Checks if the index was changed. + // Verify that the index has changed. assertNotEquals(nameIndex, nameIndexNew); - // Checks if index in the ColumnInfo is updated. + // Verify that the index in the ColumnInfo has been updated. + catColumnInfo = (CatRealmProxy.CatColumnInfo) realm.schema.getColumnIndices().getColumnInfo(Cat.class); assertEquals(nameIndexNew.get(), catColumnInfo.nameIndex); assertEquals(nameIndexNew.get(), (long) catColumnInfo.getIndicesMap().get(Cat.FIELD_NAME)); diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 58fe40e91a..96b1eec789 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -36,7 +36,7 @@ set(classes_LIST io.realm.internal.Table io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil - io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.RealmSchema + io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.OsRealmSchema io.realm.RealmObjectSchema io.realm.internal.Collection io.realm.internal.NativeObjectReference io.realm.internal.CollectionChangeSet io.realm.internal.OsObject diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp similarity index 86% rename from realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp rename to realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp index 7bc774c51c..5d78cae226 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp @@ -15,7 +15,7 @@ */ #include -#include "io_realm_RealmSchema.h" +#include "io_realm_OsRealmSchema.h" #include #include @@ -25,7 +25,7 @@ using namespace realm; -JNIEXPORT jlong JNICALL Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv* env, jclass, +JNIEXPORT jlong JNICALL Java_io_realm_OsRealmSchema_nativeCreateFromList(JNIEnv* env, jclass, jlongArray objectSchemaPtrs_) { TR_ENTER() @@ -43,14 +43,14 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmSchema_nativeCreateFromList(JNIEnv* e return 0; } -JNIEXPORT void JNICALL Java_io_realm_RealmSchema_nativeClose(JNIEnv*, jclass, jlong nativePtr) +JNIEXPORT void JNICALL Java_io_realm_OsRealmSchema_nativeClose(JNIEnv*, jclass, jlong nativePtr) { TR_ENTER_PTR(nativePtr) Schema* schema = reinterpret_cast(nativePtr); delete schema; } -JNIEXPORT jlongArray JNICALL Java_io_realm_RealmSchema_nativeGetAll(JNIEnv* env, jclass, jlong nativePtr) +JNIEXPORT jlongArray JNICALL Java_io_realm_OsRealmSchema_nativeGetAll(JNIEnv* env, jclass, jlong nativePtr) { TR_ENTER_PTR(nativePtr) try { diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 7bdd156b74..acae142705 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -68,10 +68,10 @@ abstract class BaseRealm implements Closeable { static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); final long threadId; - protected RealmConfiguration configuration; + protected final RealmConfiguration configuration; protected SharedRealm sharedRealm; - RealmSchema schema; + protected final StandardRealmSchema schema; protected BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); @@ -85,7 +85,7 @@ public void onSchemaVersionChanged(long currentVersion) { RealmCache.updateSchemaCache((Realm) BaseRealm.this); } }, true); - this.schema = new RealmSchema(this); + this.schema = new StandardRealmSchema(this); } /** @@ -464,7 +464,7 @@ void setVersion(long version) { * * @return The {@link RealmSchema} for this Realm. */ - public RealmSchema getSchema() { + public StandardRealmSchema getSchema() { return schema; } @@ -642,6 +642,10 @@ protected void finalize() throws Throwable { super.finalize(); } + public SharedRealm getSharedRealm() { + return sharedRealm; + } + // Internal delegate for migrations. protected interface MigrationCallback { void migrationComplete(); @@ -663,7 +667,7 @@ public void set(BaseRealm realm, Row row, ColumnInfo columnInfo, this.excludeFields = excludeFields; } - public BaseRealm getRealm() { + BaseRealm getRealm() { return realm; } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 862255f0b5..0d1d518201 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -344,7 +344,7 @@ public RealmList getList(String fieldName) { try { LinkView linkView = proxyState.getRow$realm().getLinkList(columnIndex); String className = RealmSchema.getSchemaForTable(linkView.getTargetTable()); - return new RealmList(className, linkView, proxyState.getRealm$realm()); + return new RealmList<>(className, linkView, proxyState.getRealm$realm()); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.LIST); throw e; @@ -713,7 +713,7 @@ public void setList(String fieldName, RealmList list) { typeValidated = false; } else { String listType = list.className != null ? list.className - : Table.tableNameToClassName(proxyState.getRealm$realm().schema.getTable(list.clazz).getName()); + : Table.tableNameToClassName(proxyState.getRealm$realm().getSchema().getTable(list.clazz).getName()); if (!linkTargetTableName.equals(listType)) { throw new IllegalArgumentException(String.format(Locale.ENGLISH, "The elements in the list are not the proper type. " + diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java new file mode 100644 index 0000000000..9b7e100d95 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -0,0 +1,158 @@ +/* + * Copyright 2015 Realm Inc. + * + * 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. + */ + +package io.realm; + +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + + +/** + * Class for interacting with the Realm schema using a dynamic API. This makes it possible + * to add, delete and change the classes in the Realm. + *

                    + * All changes must happen inside a write transaction for the particular Realm. + * + * @see RealmMigration + */ +class OsRealmSchema extends RealmSchema { + static final class Creator extends RealmSchema { + private final Map schema = new HashMap<>(); + + @Override + public void close() { } + + @Override + public RealmObjectSchema get(String className) { + checkEmpty(className); + return (!contains(className)) ? null : schema.get(className); + } + + @Override + public Set getAll() { + return new LinkedHashSet<>(schema.values()); + } + + @Override + public RealmObjectSchema create(String className) { + checkEmpty(className); + RealmObjectSchema realmObjectSchema = new RealmObjectSchema(className); + schema.put(className, realmObjectSchema); + return realmObjectSchema; + } + + @Override + public boolean contains(String className) { + return schema.containsKey(className); + } + } + + private final Map dynamicClassToSchema = new HashMap<>(); + + private final long nativePtr; + + OsRealmSchema(Creator creator) { + Set realmObjectSchemas = creator.getAll(); + long[] schemaNativePointers = new long[realmObjectSchemas.size()]; + int i = 0; + for (RealmObjectSchema schema : realmObjectSchemas) { + schemaNativePointers[i++] = schema.getNativePtr(); + } + this.nativePtr = nativeCreateFromList(schemaNativePointers); + } + + public long getNativePtr() { + return this.nativePtr; + } + + // THIS IS NEVER CALLED! + // See BaseRealm uses a StandardRealmSchema, not a OsRealmSchema. + @Override + public void close() { + Set schemas = getAll(); + for (RealmObjectSchema schema : schemas) { + schema.close(); + } + nativeClose(nativePtr); + } + + /** + * Returns the Realm schema for a given class. + * + * @param className name of the class + * @return schema object for that class or {@code null} if the class doesn't exists. + */ + @Override + public RealmObjectSchema get(String className) { + checkEmpty(className); + return (!contains(className)) ? null : dynamicClassToSchema.get(className); + } + + /** + * Returns the {@link RealmObjectSchema} for all RealmObject classes that can be saved in this Realm. + * + * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. + */ + @Override + public Set getAll() { + long[] ptrs = nativeGetAll(nativePtr); + Set schemas = new LinkedHashSet<>(ptrs.length); + for (int i = 0; i < ptrs.length; i++) { + schemas.add(new RealmObjectSchema(ptrs[i])); + } + return schemas; + } + + /** + * Adds a new class to the Realm. + * + * @param className name of the class. + * @return a Realm schema object for that class. + */ + @Override + public RealmObjectSchema create(String className) { + // Adding a class is always permitted. + checkEmpty(className); + RealmObjectSchema realmObjectSchema = new RealmObjectSchema(className); + dynamicClassToSchema.put(className, realmObjectSchema); + return realmObjectSchema; + } + + /** + * Checks if a given class already exists in the schema. + * + * @param className class name to check. + * @return {@code true} if the class already exists. {@code false} otherwise. + */ + @Override + public boolean contains(String className) { + return dynamicClassToSchema.containsKey(className); + } + + static void checkEmpty(String str) { + if (str == null || str.isEmpty()) { + throw new IllegalArgumentException("Null or empty class names are not allowed"); + } + } + + static native long nativeCreateFromList(long[] objectSchemaPtrs); + + static native void nativeClose(long nativePtr); + + static native long[] nativeGetAll(long nativePtr); +} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 49604bb690..857ec5f406 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -293,7 +293,7 @@ private static Realm createAndValidate(RealmConfiguration configuration, ColumnI if (columnIndices != null) { // Copies global cache as a Realm local indices cache. - realm.schema.columnIndices = columnIndices.clone(); + realm.schema.setColumnIndices(columnIndices); } else { final boolean syncingConfig = configuration.isSyncConfiguration(); @@ -355,8 +355,9 @@ private static void initializeRealm(Realm realm) { columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } - realm.schema.columnIndices = new ColumnIndices( - (unversioned) ? realm.configuration.getSchemaVersion() : currentVersion, columnInfoMap); + realm.schema.setColumnIndices( + (unversioned) ? realm.configuration.getSchemaVersion() : currentVersion, + columnInfoMap); if (unversioned) { final Transaction transaction = realm.configuration.getInitialDataTransaction(); @@ -388,23 +389,24 @@ private static void initializeSyncedRealm(Realm realm) { final RealmProxyMediator mediator = realm.configuration.getSchemaMediator(); final Set> modelClasses = mediator.getModelClasses(); - final ArrayList realmObjectSchemas = new ArrayList<>(); - final RealmSchema realmSchemaCache = new RealmSchema(); + final OsRealmSchema.Creator schemaCreator = new OsRealmSchema.Creator(); for (Class modelClass : modelClasses) { - RealmObjectSchema realmObjectSchema = mediator.createRealmObjectSchema(modelClass, realmSchemaCache); - realmObjectSchemas.add(realmObjectSchema); + mediator.createRealmObjectSchema(modelClass, schemaCreator); } // Assumption: When SyncConfiguration then additive schema update mode. - final RealmSchema schema = new RealmSchema(realmObjectSchemas); + final OsRealmSchema schema = new OsRealmSchema(schemaCreator); long newVersion = realm.configuration.getSchemaVersion(); - if (realm.sharedRealm.requiresMigration(schema)) { + // !!! FIXME: This appalling kludge is necessitated by current package structure/visiblity constraints. + // It absolutely breaks encapsulation and needs to be fixed! + long schemaNativePointer = schema.getNativePtr(); + if (realm.sharedRealm.requiresMigration(schemaNativePointer)) { if (currentVersion >= newVersion) { throw new IllegalArgumentException(String.format("The schema was changed but the schema version " + "was not updated. The configured schema version (%d) must be higher than the one in the Realm " + "file (%d) in order to update the schema.", newVersion, currentVersion)); } - realm.sharedRealm.updateSchema(schema, newVersion); + realm.sharedRealm.updateSchema(schemaNativePointer, newVersion); // The OS currently does not handle setting the schema version. We have to do it manually. realm.setVersion(newVersion); commitChanges = true; @@ -415,7 +417,7 @@ private static void initializeSyncedRealm(Realm realm) { columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } - realm.schema.columnIndices = new ColumnIndices((unversioned) ? newVersion : currentVersion, columnInfoMap); + realm.getSchema().setColumnIndices((unversioned) ? newVersion : currentVersion, columnInfoMap); if (unversioned) { final Transaction transaction = realm.configuration.getInitialDataTransaction(); @@ -601,7 +603,7 @@ public void createAllFromJson(Class clazz, InputStream * @see #createOrUpdateAllFromJson(Class, java.io.InputStream) */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public void createOrUpdateAllFromJson(Class clazz, InputStream in) throws IOException { + public void createOrUpdateAllFromJson(Class clazz, InputStream in) { if (clazz == null || in == null) { return; } @@ -809,7 +811,7 @@ public E createObjectFromJson(Class clazz, InputStream * @see #createObjectFromJson(Class, java.io.InputStream) */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public E createOrUpdateObjectFromJson(Class clazz, InputStream in) throws IOException { + public E createOrUpdateObjectFromJson(Class clazz, InputStream in) { if (clazz == null || in == null) { return null; } @@ -974,10 +976,10 @@ public E copyToRealmOrUpdate(E object) { */ public List copyToRealm(Iterable objects) { if (objects == null) { - return new ArrayList(); + return new ArrayList<>(); } - Map cache = new HashMap(); - ArrayList realmObjects = new ArrayList(); + Map cache = new HashMap<>(); + ArrayList realmObjects = new ArrayList<>(); for (E object : objects) { checkNotNullObject(object); realmObjects.add(copyOrUpdate(object, false, cache)); @@ -1050,7 +1052,7 @@ public void insert(RealmModel object) { if (object == null) { throw new IllegalArgumentException("Null object cannot be inserted into Realm."); } - Map cache = new HashMap(); + Map cache = new HashMap<>(); configuration.getSchemaMediator().insert(this, object, cache); } @@ -1120,7 +1122,7 @@ public void insertOrUpdate(RealmModel object) { if (object == null) { throw new IllegalArgumentException("Null object cannot be inserted into Realm."); } - Map cache = new HashMap(); + Map cache = new HashMap<>(); configuration.getSchemaMediator().insertOrUpdate(this, object, cache); } @@ -1139,11 +1141,11 @@ public void insertOrUpdate(RealmModel object) { */ public List copyToRealmOrUpdate(Iterable objects) { if (objects == null) { - return new ArrayList(0); + return new ArrayList<>(0); } - Map cache = new HashMap(); - ArrayList realmObjects = new ArrayList(); + Map cache = new HashMap<>(); + ArrayList realmObjects = new ArrayList<>(); for (E object : objects) { checkNotNullObject(object); realmObjects.add(copyOrUpdate(object, true, cache)); @@ -1197,11 +1199,11 @@ public List copyFromRealm(Iterable realmObjects) { public List copyFromRealm(Iterable realmObjects, int maxDepth) { checkMaxDepth(maxDepth); if (realmObjects == null) { - return new ArrayList(0); + return new ArrayList<>(0); } - ArrayList unmanagedObjects = new ArrayList(); - Map> listCache = new HashMap>(); + ArrayList unmanagedObjects = new ArrayList<>(); + Map> listCache = new HashMap<>(); for (E object : realmObjects) { checkValidObjectForDetach(object); unmanagedObjects.add(createDetachedCopy(object, maxDepth, listCache)); @@ -1653,7 +1655,7 @@ Table getTable(Class clazz) { */ ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) { final long currentSchemaVersion = sharedRealm.getSchemaVersion(); - final long cacheSchemaVersion = schema.columnIndices.getSchemaVersion(); + final long cacheSchemaVersion = schema.getSchemaVersion(); if (currentSchemaVersion == cacheSchemaVersion) { return null; } @@ -1666,7 +1668,7 @@ ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) { // Not found in global cache. create it. final Set> modelClasses = mediator.getModelClasses(); final Map, ColumnInfo> map; - map = new HashMap, ColumnInfo>(modelClasses.size()); + map = new HashMap<>(modelClasses.size()); try { for (Class clazz : modelClasses) { final ColumnInfo columnInfo = mediator.validateTable(clazz, sharedRealm, true); @@ -1678,7 +1680,7 @@ ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) { cacheForCurrentVersion = createdGlobalCache = new ColumnIndices(currentSchemaVersion, map); } - schema.columnIndices.copyFrom(cacheForCurrentVersion, mediator); + schema.setColumnIndices(cacheForCurrentVersion, mediator); return createdGlobalCache; } @@ -1755,7 +1757,7 @@ public interface Transaction { class Callback { public void onSuccess() {} - public void onError(Exception e) {} + public void onError(Exception ignore) {} } /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index ae372e6f37..27a63ac04a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -51,9 +51,9 @@ interface Callback0 { private static class RefAndCount { // The Realm instance in this thread. - private final ThreadLocal localRealm = new ThreadLocal(); + private final ThreadLocal localRealm = new ThreadLocal<>(); // How many references to this Realm instance in this thread. - private final ThreadLocal localCount = new ThreadLocal(); + private final ThreadLocal localCount = new ThreadLocal<>(); // How many threads have instances refer to this configuration. private int globalCount = 0; } @@ -85,14 +85,14 @@ static RealmCacheType valueOf(Class clazz) { // Realm path will be used as the key to store different RealmCaches. Different Realm configurations with same path // are not allowed and an exception will be thrown when trying to add it to the cache map. - private static Map cachesMap = new HashMap(); + private static final Map cachesMap = new HashMap<>(); private static final String DIFFERENT_KEY_MESSAGE = "Wrong key used to decrypt Realm."; private static final String WRONG_REALM_CLASS_MESSAGE = "The type of Realm class must be Realm or DynamicRealm."; private RealmCache(RealmConfiguration config) { configuration = config; - refAndCountMap = new EnumMap(RealmCacheType.class); + refAndCountMap = new EnumMap<>(RealmCacheType.class); for (RealmCacheType type : RealmCacheType.values()) { refAndCountMap.put(type, new RefAndCount()); } @@ -164,7 +164,7 @@ static synchronized E createRealmOrGetFromCache(RealmConfi if (realmClass == Realm.class && refAndCount.globalCount == 0) { final BaseRealm realm = refAndCount.localRealm.get(); // Stores a copy of local ColumnIndices as a global cache. - RealmCache.storeColumnIndices(cache.typedColumnIndicesArray, realm.schema.columnIndices.clone()); + RealmCache.storeColumnIndices(cache.typedColumnIndicesArray, realm.schema.getColumnIndices()); } // This is the first instance in current thread, increase the global count. refAndCount.globalCount++; diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 549908bb4f..ec33d39900 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -74,7 +74,7 @@ public class RealmList extends AbstractList implements public RealmList() { collection = null; view = null; - unmanagedList = new ArrayList(); + unmanagedList = new ArrayList<>(); } /** @@ -92,7 +92,7 @@ public RealmList(E... objects) { } collection = null; view = null; - unmanagedList = new ArrayList(objects.length); + unmanagedList = new ArrayList<>(objects.length); Collections.addAll(unmanagedList, objects); } @@ -246,7 +246,7 @@ private E copyToRealmIfNeeded(E object) { RealmObjectProxy proxy = (RealmObjectProxy) object; if (proxy instanceof DynamicRealmObject) { - String listClassName = RealmSchema.getSchemaForTable(view.getTargetTable()); + String listClassName = StandardRealmSchema.getSchemaForTable(view.getTargetTable()); if (proxy.realmGet$proxyState().getRealm$realm() == realm) { String objectClassName = ((DynamicRealmObject) object).getType(); if (listClassName.equals(objectClassName)) { @@ -813,11 +813,13 @@ public OrderedRealmCollectionSnapshot createSnapshot() { } checkValidView(); if (className != null) { - return new OrderedRealmCollectionSnapshot(realm, + return new OrderedRealmCollectionSnapshot<>( + realm, new io.realm.internal.Collection(realm.sharedRealm, view, null), className); } else { - return new OrderedRealmCollectionSnapshot(realm, + return new OrderedRealmCollectionSnapshot<>( + realm, new io.realm.internal.Collection(realm.sharedRealm, view, null), clazz); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index fd71b473d3..7e6f77b43f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; @@ -39,32 +40,34 @@ public class RealmObjectSchema { private static final Map, FieldMetaData> SUPPORTED_SIMPLE_FIELDS; static { - SUPPORTED_SIMPLE_FIELDS = new HashMap, FieldMetaData>(); - SUPPORTED_SIMPLE_FIELDS.put(String.class, new FieldMetaData(RealmFieldType.STRING, true)); - SUPPORTED_SIMPLE_FIELDS.put(short.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - SUPPORTED_SIMPLE_FIELDS.put(Short.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - SUPPORTED_SIMPLE_FIELDS.put(int.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - SUPPORTED_SIMPLE_FIELDS.put(Integer.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - SUPPORTED_SIMPLE_FIELDS.put(long.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - SUPPORTED_SIMPLE_FIELDS.put(Long.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - SUPPORTED_SIMPLE_FIELDS.put(float.class, new FieldMetaData(RealmFieldType.FLOAT, false)); - SUPPORTED_SIMPLE_FIELDS.put(Float.class, new FieldMetaData(RealmFieldType.FLOAT, true)); - SUPPORTED_SIMPLE_FIELDS.put(double.class, new FieldMetaData(RealmFieldType.DOUBLE, false)); - SUPPORTED_SIMPLE_FIELDS.put(Double.class, new FieldMetaData(RealmFieldType.DOUBLE, true)); - SUPPORTED_SIMPLE_FIELDS.put(boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, false)); - SUPPORTED_SIMPLE_FIELDS.put(Boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, true)); - SUPPORTED_SIMPLE_FIELDS.put(byte.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - SUPPORTED_SIMPLE_FIELDS.put(Byte.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - SUPPORTED_SIMPLE_FIELDS.put(byte[].class, new FieldMetaData(RealmFieldType.BINARY, true)); - SUPPORTED_SIMPLE_FIELDS.put(Date.class, new FieldMetaData(RealmFieldType.DATE, true)); + Map, FieldMetaData> m = new HashMap<>(); + m.put(String.class, new FieldMetaData(RealmFieldType.STRING, true)); + m.put(short.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Short.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(int.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Integer.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(long.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Long.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(float.class, new FieldMetaData(RealmFieldType.FLOAT, false)); + m.put(Float.class, new FieldMetaData(RealmFieldType.FLOAT, true)); + m.put(double.class, new FieldMetaData(RealmFieldType.DOUBLE, false)); + m.put(Double.class, new FieldMetaData(RealmFieldType.DOUBLE, true)); + m.put(boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, false)); + m.put(Boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, true)); + m.put(byte.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Byte.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(byte[].class, new FieldMetaData(RealmFieldType.BINARY, true)); + m.put(Date.class, new FieldMetaData(RealmFieldType.DATE, true)); + SUPPORTED_SIMPLE_FIELDS = Collections.unmodifiableMap(m); } private static final Map, FieldMetaData> SUPPORTED_LINKED_FIELDS; static { - SUPPORTED_LINKED_FIELDS = new HashMap, FieldMetaData>(); - SUPPORTED_LINKED_FIELDS.put(RealmObject.class, new FieldMetaData(RealmFieldType.OBJECT, false)); - SUPPORTED_LINKED_FIELDS.put(RealmList.class, new FieldMetaData(RealmFieldType.LIST, false)); + Map, FieldMetaData> m = new HashMap<>(); + m.put(RealmObject.class, new FieldMetaData(RealmFieldType.OBJECT, false)); + m.put(RealmList.class, new FieldMetaData(RealmFieldType.LIST, false)); + SUPPORTED_LINKED_FIELDS = Collections.unmodifiableMap(m); } private final BaseRealm realm; @@ -151,7 +154,7 @@ public String getClassName() { * @param className the new name for this class. * @throws IllegalArgumentException if className is {@code null} or an empty string, or its length exceeds 56 * characters. - * @see RealmSchema#rename(String, String) + * @see StandardRealmSchema#rename(String, String) */ public RealmObjectSchema setClassName(String className) { realm.checkNotInSync(); // renaming a table is not permitted @@ -276,7 +279,7 @@ protected RealmObjectSchema add(Property property) { private Set getProperties() { if (realm == null) { long[] ptrs = nativeGetProperties(nativePtr); - Set properties = new LinkedHashSet(ptrs.length); + Set properties = new LinkedHashSet<>(ptrs.length); for (int i = 0; i < ptrs.length; i++) { properties.add(new Property(ptrs[i])); } @@ -559,7 +562,7 @@ public String getPrimaryKey() { */ public Set getFieldNames() { int columnCount = (int) table.getColumnCount(); - Set columnNames = new LinkedHashSet(columnCount); + Set columnNames = new LinkedHashSet<>(columnCount); for (int i = 0; i < columnCount; i++) { columnNames.add(table.getColumnName(i)); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 3d52b9bfdb..0f2a19e70c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -51,13 +51,13 @@ */ public class RealmQuery { - private BaseRealm realm; + private final Table table; + private final BaseRealm realm; + private final TableQuery query; + private final RealmObjectSchema schema; private Class clazz; private String className; - private Table table; - private RealmObjectSchema schema; private LinkView linkView; - private TableQuery query; private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; static final String ASYNC_QUERY_WRONG_THREAD_MESSAGE = "Async query cannot be created on current thread."; @@ -71,7 +71,7 @@ public class RealmQuery { * to run it. */ public static RealmQuery createQuery(Realm realm, Class clazz) { - return new RealmQuery(realm, clazz); + return new RealmQuery<>(realm, clazz); } /** @@ -83,7 +83,7 @@ public static RealmQuery createQuery(Realm realm, Clas * to run it. */ public static RealmQuery createDynamicQuery(DynamicRealm realm, String className) { - return new RealmQuery(realm, className); + return new RealmQuery<>(realm, className); } /** @@ -97,7 +97,7 @@ public static RealmQuery createDynamicQuery(DynamicRea @SuppressWarnings("unchecked") public static RealmQuery createQueryFromResult(RealmResults queryResults) { if (queryResults.classSpec != null) { - return new RealmQuery(queryResults, queryResults.classSpec); + return new RealmQuery<>(queryResults, queryResults.classSpec); } else { return new RealmQuery(queryResults, queryResults.className); } @@ -122,7 +122,7 @@ public static RealmQuery createQueryFromList(RealmList private RealmQuery(Realm realm, Class clazz) { this.realm = realm; this.clazz = clazz; - this.schema = realm.schema.getSchemaForClass(clazz); + this.schema = realm.getSchema().getSchemaForClass(clazz); this.table = schema.table; this.linkView = null; this.query = table.where(); @@ -131,7 +131,7 @@ private RealmQuery(Realm realm, Class clazz) { private RealmQuery(RealmResults queryResults, Class clazz) { this.realm = queryResults.realm; this.clazz = clazz; - this.schema = realm.schema.getSchemaForClass(clazz); + this.schema = realm.getSchema().getSchemaForClass(clazz); this.table = queryResults.getTable(); this.linkView = null; this.query = queryResults.getCollection().where(); @@ -140,7 +140,7 @@ private RealmQuery(RealmResults queryResults, Class clazz) { private RealmQuery(BaseRealm realm, LinkView linkView, Class clazz) { this.realm = realm; this.clazz = clazz; - this.schema = realm.schema.getSchemaForClass(clazz); + this.schema = realm.getSchema().getSchemaForClass(clazz); this.table = schema.table; this.linkView = linkView; this.query = linkView.where(); @@ -149,7 +149,7 @@ private RealmQuery(BaseRealm realm, LinkView linkView, Class clazz) { private RealmQuery(BaseRealm realm, String className) { this.realm = realm; this.className = className; - this.schema = realm.schema.getSchemaForClass(className); + this.schema = realm.getSchema().getSchemaForClass(className); this.table = schema.table; this.query = table.where(); } @@ -157,7 +157,7 @@ private RealmQuery(BaseRealm realm, String className) { private RealmQuery(RealmResults queryResults, String className) { this.realm = queryResults.realm; this.className = className; - this.schema = realm.schema.getSchemaForClass(className); + this.schema = realm.getSchema().getSchemaForClass(className); this.table = schema.table; this.query = queryResults.getCollection().where(); } @@ -165,7 +165,7 @@ private RealmQuery(RealmResults queryResults, String classNa private RealmQuery(BaseRealm realm, LinkView linkView, String className) { this.realm = realm; this.className = className; - this.schema = realm.schema.getSchemaForClass(className); + this.schema = realm.getSchema().getSchemaForClass(className); this.table = schema.table; this.linkView = linkView; this.query = linkView.where(); @@ -1909,12 +1909,7 @@ public E findFirst() { realm.checkIfValid(); long tableRowIndex = getSourceRowIndexForFirstObject(); - if (tableRowIndex >= 0) { - E realmObject = realm.get(clazz, className, tableRowIndex); - return realmObject; - } else { - return null; - } + return (tableRowIndex < 0) ? null : realm.get(clazz, className, tableRowIndex); } /** @@ -1970,9 +1965,9 @@ private RealmResults createRealmResults(TableQuery query, RealmResults results; Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); if (isDynamicQuery()) { - results = new RealmResults(realm, collection, className); + results = new RealmResults<>(realm, collection, className); } else { - results = new RealmResults(realm, collection, clazz); + results = new RealmResults<>(realm, collection, clazz); } if (loadResults) { results.load(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 631ce02530..a340686cf5 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -16,16 +16,13 @@ package io.realm; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; +import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; -import io.realm.internal.Util; /** @@ -34,69 +31,15 @@ *

                    * All changes must happen inside a write transaction for the particular Realm. * - * @see io.realm.RealmMigration + * @see RealmMigration */ -public class RealmSchema { - - private static final String TABLE_PREFIX = Table.TABLE_PREFIX; - private static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; - - // Caches Dynamic Class objects given as Strings to Realm Tables - private final Map dynamicClassToTable = new HashMap(); - // Caches Class objects (both model classes and proxy classes) to Realm Tables - private final Map, Table> classToTable = new HashMap, Table>(); - // Caches Class objects (both model classes and proxy classes) to their Schema object - private final Map, RealmObjectSchema> classToSchema = new HashMap, RealmObjectSchema>(); - // Caches Class Strings to their Schema object - private final Map dynamicClassToSchema = new HashMap(); - - private final BaseRealm realm; - private long nativePtr; - ColumnIndices columnIndices; // Cached field look up - - /** - * Creates a wrapper to easily manipulate the current schema of a Realm. - */ - RealmSchema(BaseRealm realm) { - this.realm = realm; - this.nativePtr = 0; - } +public abstract class RealmSchema { + private ColumnIndices columnIndices; // Cached field look up /** - * Creates a wrappor to easily manipulate Object Store schemas. This constructor should only be called by - * proxy classes during validation of schema. + * Release the schema and any of native resources it might hold. */ - RealmSchema() { - // This is the case where the schema is created from the proxy classes. - // dynamicClassToSchema is used to keep track of which model classes have been processed. - this.realm = null; - this.nativePtr = 0; - // TODO: create a Object Store realm::Schema object and store the native pointer - } - - - RealmSchema(ArrayList realmObjectSchemas) { - long list[] = new long[realmObjectSchemas.size()]; - for (int i = 0; i < realmObjectSchemas.size(); i++) { - list[i] = realmObjectSchemas.get(i).getNativePtr(); - } - this.nativePtr = nativeCreateFromList(list); - this.realm = null; - } - - public long getNativePtr() { - return this.nativePtr; - } - - public void close() { - if (nativePtr != 0) { - Set schemas = getAll(); - for (RealmObjectSchema schema : schemas) { - schema.close(); - } - nativeClose(nativePtr); - } - } + public abstract void close(); /** * Returns the Realm schema for a given class. @@ -104,54 +47,14 @@ public void close() { * @param className name of the class * @return schema object for that class or {@code null} if the class doesn't exists. */ - public RealmObjectSchema get(String className) { - checkEmpty(className, EMPTY_STRING_MSG); - if (realm == null) { - if (contains(className)) { - return dynamicClassToSchema.get(className); - } else { - return null; - } - } else { - String internalClassName = TABLE_PREFIX + className; - if (realm.sharedRealm.hasTable(internalClassName)) { - Table table = realm.sharedRealm.getTable(internalClassName); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - return new RealmObjectSchema(realm, table, columnIndices); - } else { - return null; - } - } - } + public abstract RealmObjectSchema get(String className); /** - * Returns the {@link RealmObjectSchema} for all RealmObject classes that can be saved in this Realm. + * Returns the {@link RealmObjectSchema}s for all RealmObject classes that can be saved in this Realm. * * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ - public Set getAll() { - if (realm == null) { - long[] ptrs = nativeGetAll(nativePtr); - Set schemas = new LinkedHashSet(ptrs.length); - for (int i = 0; i < ptrs.length; i++) { - schemas.add(new RealmObjectSchema(ptrs[i])); - } - return schemas; - } else { - int tableCount = (int) realm.sharedRealm.size(); - Set schemas = new LinkedHashSet(tableCount); - for (int i = 0; i < tableCount; i++) { - String tableName = realm.sharedRealm.getTableName(i); - if (!Table.isModelTable(tableName)) { - continue; - } - Table table = realm.sharedRealm.getTable(tableName); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - schemas.add(new RealmObjectSchema(realm, table, columnIndices)); - } - return schemas; - } - } + public abstract Set getAll(); /** * Adds a new class to the Realm. @@ -159,82 +62,7 @@ public Set getAll() { * @param className name of the class. * @return a Realm schema object for that class. */ - public RealmObjectSchema create(String className) { - // Adding a class is always permitted. - checkEmpty(className, EMPTY_STRING_MSG); - if (realm == null) { - RealmObjectSchema realmObjectSchema = new RealmObjectSchema(className); - dynamicClassToSchema.put(className, realmObjectSchema); - return realmObjectSchema; - } else { - String internalTableName = TABLE_PREFIX + className; - if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { - throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: " + className.length()); - } - if (realm.sharedRealm.hasTable(internalTableName)) { - throw new IllegalArgumentException("Class already exists: " + className); - } - Table table = realm.sharedRealm.getTable(internalTableName); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - return new RealmObjectSchema(realm, table, columnIndices); - } - } - - /** - * Removes a class from the Realm. All data will be removed. Removing a class while other classes point - * to it will throw an {@link IllegalStateException}. Removes those classes or fields first. - * - * @param className name of the class to remove. - */ - public void remove(String className) { - realm.checkNotInSync(); // Destructive modifications are not permitted. - checkEmpty(className, EMPTY_STRING_MSG); - String internalTableName = TABLE_PREFIX + className; - checkHasTable(className, "Cannot remove class because it is not in this Realm: " + className); - Table table = getTable(className); - if (table.hasPrimaryKey()) { - table.setPrimaryKey(null); - } - realm.sharedRealm.removeTable(internalTableName); - } - - /** - * Renames a class already in the Realm. - * - * @param oldClassName old class name. - * @param newClassName new class name. - * @return a schema object for renamed class. - */ - public RealmObjectSchema rename(String oldClassName, String newClassName) { - realm.checkNotInSync(); // Destructive modifications are not permitted. - checkEmpty(oldClassName, "Class names cannot be empty or null"); - checkEmpty(newClassName, "Class names cannot be empty or null"); - String oldInternalName = TABLE_PREFIX + oldClassName; - String newInternalName = TABLE_PREFIX + newClassName; - checkHasTable(oldClassName, "Cannot rename class because it doesn't exist in this Realm: " + oldClassName); - if (realm.sharedRealm.hasTable(newInternalName)) { - throw new IllegalArgumentException(oldClassName + " cannot be renamed because the new class already exists: " + newClassName); - } - - // Checks if there is a primary key defined for the old class. - Table oldTable = getTable(oldClassName); - String pkField = null; - if (oldTable.hasPrimaryKey()) { - pkField = oldTable.getColumnName(oldTable.getPrimaryKey()); - oldTable.setPrimaryKey(null); - } - - realm.sharedRealm.renameTable(oldInternalName, newInternalName); - Table table = realm.sharedRealm.getTable(newInternalName); - - // Sets the primary key for the new class if necessary. - if (pkField != null) { - table.setPrimaryKey(pkField); - } - - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - return new RealmObjectSchema(realm, table, columnIndices); - } + public abstract RealmObjectSchema create(String className); /** * Checks if a given class already exists in the schema. @@ -242,116 +70,46 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { * @param className class name to check. * @return {@code true} if the class already exists. {@code false} otherwise. */ - public boolean contains(String className) { - if (realm == null) { - return dynamicClassToSchema.containsKey(className); - } else { - return realm.sharedRealm.hasTable(Table.TABLE_PREFIX + className); - } - } + public abstract boolean contains(String className); - private void checkEmpty(String str, String error) { - if (str == null || str.isEmpty()) { - throw new IllegalArgumentException(error); - } + final void setColumnIndices(ColumnIndices columnIndices) { + this.columnIndices = columnIndices.clone(); } - private void checkHasTable(String className, String errorMsg) { - String internalTableName = TABLE_PREFIX + className; - if (!realm.sharedRealm.hasTable(internalTableName)) { - throw new IllegalArgumentException(errorMsg); - } + final void setColumnIndices(long version, Map, ColumnInfo> columnInfoMap) { + columnIndices = new ColumnIndices(version, columnInfoMap); } - ColumnInfo getColumnInfo(Class clazz) { - final ColumnInfo columnInfo = columnIndices.getColumnInfo(clazz); - if (columnInfo == null) { - throw new IllegalStateException("No validated schema information found for " + realm.configuration.getSchemaMediator().getTableName(clazz)); - } - return columnInfo; - } - - Table getTable(String className) { - className = Table.TABLE_PREFIX + className; - Table table = dynamicClassToTable.get(className); - if (table == null) { - if (!realm.sharedRealm.hasTable(className)) { - throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); - } - table = realm.sharedRealm.getTable(className); - dynamicClassToTable.put(className, table); - } - return table; + void setColumnIndices(ColumnIndices cacheForCurrentVersion, RealmProxyMediator mediator) { + columnIndices.copyFrom(cacheForCurrentVersion, mediator); } - Table getTable(Class clazz) { - Table table = classToTable.get(clazz); - if (table == null) { - Class originalClass = Util.getOriginalModelClass(clazz); - if (isProxyClass(originalClass, clazz)) { - // If passed 'clazz' is the proxy, try again with model class. - table = classToTable.get(originalClass); - } - if (table == null) { - table = realm.sharedRealm.getTable(realm.configuration.getSchemaMediator().getTableName(originalClass)); - classToTable.put(originalClass, table); - } - if (isProxyClass(originalClass, clazz)) { - // 'clazz' is the proxy class for 'originalClass'. - classToTable.put(clazz, table); - } - } - return table; + final ColumnIndices getColumnIndices() { + checkIndices(); + return columnIndices.clone(); } - RealmObjectSchema getSchemaForClass(Class clazz) { - RealmObjectSchema classSchema = classToSchema.get(clazz); - if (classSchema == null) { - Class originalClass = Util.getOriginalModelClass(clazz); - if (isProxyClass(originalClass, clazz)) { - // If passed 'clazz' is the proxy, try again with model class. - classSchema = classToSchema.get(originalClass); - } - if (classSchema == null) { - Table table = getTable(clazz); - classSchema = new RealmObjectSchema(realm, table, columnIndices.getColumnInfo(originalClass).getIndicesMap()); - classToSchema.put(originalClass, classSchema); - } - if (isProxyClass(originalClass, clazz)) { - // 'clazz' is the proxy class for 'originalClass'. - classToSchema.put(clazz, classSchema); - } - } - return classSchema; + final ColumnInfo getColumnInfo(Class clazz) { + checkIndices(); + return columnIndices.getColumnInfo(clazz); } - private static boolean isProxyClass(Class modelClass, - Class testee) { - return modelClass != testee; + final long getSchemaVersion() { + checkIndices(); + return this.columnIndices.getSchemaVersion(); } - RealmObjectSchema getSchemaForClass(String className) { - className = Table.TABLE_PREFIX + className; - RealmObjectSchema dynamicSchema = dynamicClassToSchema.get(className); - if (dynamicSchema == null) { - if (!realm.sharedRealm.hasTable(className)) { - throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); - } - Table table = realm.sharedRealm.getTable(className); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - dynamicSchema = new RealmObjectSchema(realm, table, columnIndices); - dynamicClassToSchema.put(className, dynamicSchema); - } - return dynamicSchema; + final boolean isProxyClass(Class modelClass, Class testee) { + return modelClass.equals(testee); } static String getSchemaForTable(Table table) { return table.getName().substring(Table.TABLE_PREFIX.length()); } - static native long nativeCreateFromList(long[] objectSchemaPtrs); - - static native void nativeClose(long nativePtr); - - static native long[] nativeGetAll(long nativePtr); + private void checkIndices() { + if (this.columnIndices == null) { + throw new IllegalStateException("Attempt to use column index before set."); + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java new file mode 100644 index 0000000000..9b224c9d44 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java @@ -0,0 +1,274 @@ +/* + * Copyright 2015 Realm Inc. + * + * 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. + */ + +package io.realm; + +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import io.realm.internal.Table; +import io.realm.internal.Util; + + +/** + * Class for interacting with the Realm schema using a dynamic API. This makes it possible + * to add, delete and change the classes in the Realm. + *

                    + * All changes must happen inside a write transaction for the particular Realm. + * + * @see io.realm.RealmMigration + */ +public class StandardRealmSchema extends RealmSchema { + + private static final String TABLE_PREFIX = Table.TABLE_PREFIX; + private static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; + + // Caches Dynamic Class objects given as Strings to Realm Tables + private final Map dynamicClassToTable = new HashMap<>(); + // Caches Class objects (both model classes and proxy classes) to Realm Tables + private final Map, Table> classToTable = new HashMap<>(); + // Caches Class objects (both model classes and proxy classes) to their Schema object + private final Map, RealmObjectSchema> classToSchema = new HashMap<>(); + // Caches Class Strings to their Schema object + private final Map dynamicClassToSchema = new HashMap<>(); + + private final BaseRealm realm; + + /** + * Creates a wrapper to easily manipulate the current schema of a Realm. + */ + StandardRealmSchema(BaseRealm realm) { + this.realm = realm; + } + + @Override + public void close() { } + + /** + * Returns the Realm schema for a given class. + * + * @param className name of the class + * @return schema object for that class or {@code null} if the class doesn't exists. + */ + @Override + public RealmObjectSchema get(String className) { + checkEmpty(className, EMPTY_STRING_MSG); + + String internalClassName = TABLE_PREFIX + className; + if (!realm.getSharedRealm().hasTable(internalClassName)) { return null; } + + Table table = realm.getSharedRealm().getTable(internalClassName); + RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); + return new RealmObjectSchema(realm, table, columnIndices); + } + + /** + * Returns the {@link RealmObjectSchema} for all RealmObject classes that can be saved in this Realm. + * + * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. + */ + @Override + public Set getAll() { + int tableCount = (int) realm.getSharedRealm().size(); + Set schemas = new LinkedHashSet<>(tableCount); + for (int i = 0; i < tableCount; i++) { + String tableName = realm.getSharedRealm().getTableName(i); + if (!Table.isModelTable(tableName)) { + continue; + } + Table table = realm.getSharedRealm().getTable(tableName); + RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); + schemas.add(new RealmObjectSchema(realm, table, columnIndices)); + } + return schemas; + } + + /** + * Adds a new class to the Realm. + * + * @param className name of the class. + * @return a Realm schema object for that class. + */ + @Override + public RealmObjectSchema create(String className) { + // Adding a class is always permitted. + checkEmpty(className, EMPTY_STRING_MSG); + + String internalTableName = TABLE_PREFIX + className; + if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { + throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: " + className.length()); + } + if (realm.getSharedRealm().hasTable(internalTableName)) { + throw new IllegalArgumentException("Class already exists: " + className); + } + Table table = realm.getSharedRealm().getTable(internalTableName); + RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); + return new RealmObjectSchema(realm, table, columnIndices); + } + + /** + * Checks if a given class already exists in the schema. + * + * @param className class name to check. + * @return {@code true} if the class already exists. {@code false} otherwise. + */ + @Override + public boolean contains(String className) { + return realm.getSharedRealm().hasTable(Table.TABLE_PREFIX + className); + } + + /** + * Removes a class from the Realm. All data will be removed. Removing a class while other classes point + * to it will throw an {@link IllegalStateException}. Removes those classes or fields first. + * + * @param className name of the class to remove. + */ + public void remove(String className) { + realm.checkNotInSync(); // Destructive modifications are not permitted. + checkEmpty(className, EMPTY_STRING_MSG); + String internalTableName = TABLE_PREFIX + className; + checkHasTable(className, "Cannot remove class because it is not in this Realm: " + className); + Table table = getTable(className); + if (table.hasPrimaryKey()) { + table.setPrimaryKey(null); + } + realm.getSharedRealm().removeTable(internalTableName); + } + + /** + * Renames a class already in the Realm. + * + * @param oldClassName old class name. + * @param newClassName new class name. + * @return a schema object for renamed class. + */ + public RealmObjectSchema rename(String oldClassName, String newClassName) { + realm.checkNotInSync(); // Destructive modifications are not permitted. + checkEmpty(oldClassName, "Class names cannot be empty or null"); + checkEmpty(newClassName, "Class names cannot be empty or null"); + String oldInternalName = TABLE_PREFIX + oldClassName; + String newInternalName = TABLE_PREFIX + newClassName; + checkHasTable(oldClassName, "Cannot rename class because it doesn't exist in this Realm: " + oldClassName); + if (realm.getSharedRealm().hasTable(newInternalName)) { + throw new IllegalArgumentException(oldClassName + " cannot be renamed because the new class already exists: " + newClassName); + } + + // Checks if there is a primary key defined for the old class. + Table oldTable = getTable(oldClassName); + String pkField = null; + if (oldTable.hasPrimaryKey()) { + pkField = oldTable.getColumnName(oldTable.getPrimaryKey()); + oldTable.setPrimaryKey(null); + } + + realm.getSharedRealm().renameTable(oldInternalName, newInternalName); + Table table = realm.getSharedRealm().getTable(newInternalName); + + // Sets the primary key for the new class if necessary. + if (pkField != null) { + table.setPrimaryKey(pkField); + } + + RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); + return new RealmObjectSchema(realm, table, columnIndices); + } + + private void checkEmpty(String str, String error) { + if (str == null || str.isEmpty()) { + throw new IllegalArgumentException(error); + } + } + + private void checkHasTable(String className, String errorMsg) { + String internalTableName = TABLE_PREFIX + className; + if (!realm.getSharedRealm().hasTable(internalTableName)) { + throw new IllegalArgumentException(errorMsg); + } + } + + Table getTable(String className) { + className = Table.TABLE_PREFIX + className; + Table table = dynamicClassToTable.get(className); + if (table != null) { return table; } + + if (!realm.getSharedRealm().hasTable(className)) { + throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); + } + table = realm.getSharedRealm().getTable(className); + dynamicClassToTable.put(className, table); + + return table; + } + + Table getTable(Class clazz) { + Table table = classToTable.get(clazz); + if (table != null) { return table; } + + Class originalClass = Util.getOriginalModelClass(clazz); + if (isProxyClass(originalClass, clazz)) { + // If passed 'clazz' is the proxy, try again with model class. + table = classToTable.get(originalClass); + } + if (table == null) { + table = realm.getSharedRealm().getTable(realm.getConfiguration().getSchemaMediator().getTableName(originalClass)); + classToTable.put(originalClass, table); + } + if (isProxyClass(originalClass, clazz)) { + // 'clazz' is the proxy class for 'originalClass'. + classToTable.put(clazz, table); + } + + return table; + } + + RealmObjectSchema getSchemaForClass(Class clazz) { + RealmObjectSchema classSchema = classToSchema.get(clazz); + if (classSchema != null) { return classSchema; } + + Class originalClass = Util.getOriginalModelClass(clazz); + if (isProxyClass(originalClass, clazz)) { + // If passed 'clazz' is the proxy, try again with model class. + classSchema = classToSchema.get(originalClass); + } + if (classSchema == null) { + Table table = getTable(clazz); + classSchema = new RealmObjectSchema(realm, table, getColumnInfo(originalClass).getIndicesMap()); + classToSchema.put(originalClass, classSchema); + } + if (isProxyClass(originalClass, clazz)) { + // 'clazz' is the proxy class for 'originalClass'. + classToSchema.put(clazz, classSchema); + } + return classSchema; + } + + RealmObjectSchema getSchemaForClass(String className) { + className = Table.TABLE_PREFIX + className; + RealmObjectSchema dynamicSchema = dynamicClassToSchema.get(className); + if (dynamicSchema == null) { + if (!realm.getSharedRealm().hasTable(className)) { + throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); + } + Table table = realm.getSharedRealm().getTable(className); + RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); + dynamicSchema = new RealmObjectSchema(realm, table, columnIndices); + dynamicClassToSchema.put(className, dynamicSchema); + } + return dynamicSchema; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 5580c65e64..0abfa51f0f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -24,7 +24,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import io.realm.RealmConfiguration; -import io.realm.RealmSchema; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; @@ -110,12 +109,13 @@ public byte getNativeValue() { } } + private final List> pendingRows = new CopyOnWriteArrayList<>(); + public final List> collections = new CopyOnWriteArrayList<>(); + public final List> iterators = new ArrayList<>(); + // JNI will only hold a weak global ref to this. public final RealmNotifier realmNotifier; public final Capabilities capabilities; - public final List> iterators = - new ArrayList>(); - private final List> pendingRows = new CopyOnWriteArrayList>(); public static class VersionID implements Comparable { public final long version; @@ -174,8 +174,9 @@ public interface SchemaVersionListener { void onSchemaVersionChanged(long currentVersion); } + private final RealmConfiguration configuration; + private long nativePtr; - private RealmConfiguration configuration; final Context context; private long lastSchemaVersion; private final SchemaVersionListener schemaChangeListener; @@ -344,8 +345,8 @@ public boolean compact() { * Updates the underlying schema based on the schema description. * Calling this method must be done from inside a write transaction. */ - public void updateSchema(RealmSchema schema, long version) { - nativeUpdateSchema(nativePtr, schema.getNativePtr(), version); + public void updateSchema(long schemaNativePointer, long version) { + nativeUpdateSchema(nativePtr, schemaNativePointer, version); } public void setAutoRefresh(boolean enabled) { @@ -357,8 +358,8 @@ public boolean isAutoRefresh() { return nativeIsAutoRefresh(nativePtr); } - public boolean requiresMigration(RealmSchema schema) { - return nativeRequiresMigration(nativePtr, schema.getNativePtr()); + public boolean requiresMigration(long schemaNativePointer) { + return nativeRequiresMigration(nativePtr, schemaNativePointer); } @Override @@ -404,7 +405,7 @@ public void invokeSchemaChangeListenerIfSchemaChanged() { // See https://github.com/realm/realm-java/issues/3883 for more information. // Should only be called by Iterator's constructor. void addIterator(Collection.Iterator iterator) { - iterators.add(new WeakReference(iterator)); + iterators.add(new WeakReference<>(iterator)); } // The detaching should happen before transaction begins. From c1aa195b5b59113a434c4992bb848edf7b472db5 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 31 Mar 2017 19:01:01 +0900 Subject: [PATCH 0588/2110] remove breaking changes in SyncSession.ErrorHandler (#4408) * remove breaking changes in SyncSession.ErrorHandler * update CHANGELOG * update JNI file * update CMakeLists.txt * address review comments --- CHANGELOG.md | 1 - .../java/io/realm/SessionTests.java | 18 +++++++++-------- .../java/io/realm/SyncConfigurationTests.java | 10 ---------- .../realm-library/src/main/cpp/CMakeLists.txt | 4 ++-- ... => io_realm_ClientResetRequiredError.cpp} | 4 ++-- ...ler.java => ClientResetRequiredError.java} | 6 +++--- .../objectServer/java/io/realm/ErrorCode.java | 4 ++-- .../java/io/realm/SyncManager.java | 10 +++++----- .../java/io/realm/SyncSession.java | 20 ++++++++----------- .../objectServer/java/io/realm/SyncUser.java | 15 +++++++------- .../java/io/realm/objectserver/AuthTests.java | 6 ------ .../objectserver/ManagementRealmTests.java | 6 ------ .../objectserver/ProcessCommitTests.java | 11 ---------- 13 files changed, 39 insertions(+), 76 deletions(-) rename realm/realm-library/src/main/cpp/{io_realm_ClientResetHandler.cpp => io_realm_ClientResetRequiredError.cpp} (89%) rename realm/realm-library/src/objectServer/java/io/realm/{ClientResetHandler.java => ClientResetRequiredError.java} (91%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5403963378..5f9c327261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ * Updated file format of Realm files. Existing Realm files will automatically be migrated to the new format when they are opened. * [ObjectServer] Due to file format changes, Realm Object Server 1.3.0 or later is required. -* [ObjectServer] Added `onClientResetRequired(SyncSession, ClientResetHandler)` method to the `ErrorHandler` interface (#4080). ### Enhancements diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 3376d0a512..7ee77dfa61 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -119,11 +119,12 @@ public void errorHandler_clientResetReported() { .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { - fail("Wrong error " + error.toString()); - } + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; + } - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + final ClientResetRequiredError handler = (ClientResetRequiredError) error; String filePathFromError = handler.getOriginalFile().getAbsolutePath(); String filePathFromConfig = session.getConfiguration().getPath(); assertEquals(filePathFromError, filePathFromConfig); @@ -151,11 +152,12 @@ public void errorHandler_manualExecuteClientReset() { .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { - fail("Wrong error " + error.toString()); - } + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; + } - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { + final ClientResetRequiredError handler = (ClientResetRequiredError) error; try { handler.executeClientReset(); fail("All Realms should be closed before executing Client Reset can be allowed"); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 2e294ffe75..3b7a2a466f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -225,11 +225,6 @@ public void errorHandler() { public void onError(SyncSession session, ObjectServerError error) { } - - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { - - } }; SyncConfiguration config = builder.errorHandler(errorHandler).build(); assertEquals(errorHandler, config.getErrorHandler()); @@ -243,11 +238,6 @@ public void errorHandler_fromSyncManager() { public void onError(SyncSession session, ObjectServerError error) { } - - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { - - } }; SyncManager.setDefaultSessionErrorHandler(errorHandler); diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 96b1eec789..cb5f0a97d6 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -46,7 +46,7 @@ set(classes_LIST set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST - io.realm.ClientResetHandler io.realm.RealmFileUserStore + io.realm.ClientResetRequiredError io.realm.RealmFileUserStore io.realm.SyncManager io.realm.SyncSession ) endif() @@ -158,7 +158,7 @@ file(GLOB jni_SRC # Those source file are only needed for sync. if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetHandler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetRequiredError.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFileUserStore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_ClientResetHandler.cpp b/realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp similarity index 89% rename from realm/realm-library/src/main/cpp/io_realm_ClientResetHandler.cpp rename to realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp index 61a72c1145..39e5484af4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_ClientResetHandler.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp @@ -19,11 +19,11 @@ #include #include "util.hpp" -#include "io_realm_ClientResetHandler.h" +#include "io_realm_ClientResetRequiredError.h" using namespace realm; -JNIEXPORT void JNICALL Java_io_realm_ClientResetHandler_nativeExecuteClientReset(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_ClientResetRequiredError_nativeExecuteClientReset(JNIEnv* env, jobject, jstring localRealmPath) { TR_ENTER() diff --git a/realm/realm-library/src/objectServer/java/io/realm/ClientResetHandler.java b/realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java similarity index 91% rename from realm/realm-library/src/objectServer/java/io/realm/ClientResetHandler.java rename to realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java index 1fdc1adf8b..f9a2fbe1e1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ClientResetHandler.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java @@ -21,16 +21,16 @@ /** * Class encapsulating information needed for handling a Client Reset event. * - * @see io.realm.SyncSession.ErrorHandler#onClientResetRequired(SyncSession, ClientResetHandler) for more information + * @see io.realm.SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more information * about when and why Client Reset occurs and how to deal with it. */ -public class ClientResetHandler extends ObjectServerError { +public class ClientResetRequiredError extends ObjectServerError { private final RealmConfiguration configuration; private final File backupFile; private final File originalFile; - public ClientResetHandler(ErrorCode errorCode, String errorMessage, String backupFilePath, RealmConfiguration configuration) { + public ClientResetRequiredError(ErrorCode errorCode, String errorMessage, String backupFilePath, RealmConfiguration configuration) { super(errorCode, errorMessage); this.configuration = configuration; this.backupFile = new File(backupFilePath); diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index a8e4a827ec..1a5438ba92 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -127,8 +127,8 @@ public static ErrorCode fromInt(int errorCode) { throw new IllegalArgumentException("Unknown error code: " + errorCode); } -public enum Category { + public enum Category { FATAL, // Abort session as soon as possible - RECOVERABLE // Still possible to recover the session by either rebinding or providing the required information. + RECOVERABLE, // Still possible to recover the session by either rebinding or providing the required information. } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index fe72ce86fe..abf5951752 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -69,6 +69,11 @@ public static class Debug { private static final SyncSession.ErrorHandler SESSION_NO_OP_ERROR_HANDLER = new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { + if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { + RealmLog.error("Client Reset required for: " + session.getConfiguration().getServerUrl()); + return; + } + String errorMsg = String.format("Session Error[%s]: %s", session.getConfiguration().getServerUrl(), error.toString()); @@ -83,11 +88,6 @@ public void onError(SyncSession session, ObjectServerError error) { throw new IllegalArgumentException("Unsupported error category: " + error.getErrorCode().getCategory()); } } - - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { - RealmLog.error("Client Reset required for: " + session.getConfiguration().getPath()); - } }; // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path' private static Map sessions = new ConcurrentHashMap<>(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 75736ce98f..e301800027 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -123,7 +123,7 @@ void notifySessionError(int errorCode, String errorMessage) { ErrorCode errCode = ErrorCode.fromInt(errorCode); if (errCode == ErrorCode.CLIENT_RESET) { // errorMessage contains the path to the backed up file - errorHandler.onClientResetRequired(this, new ClientResetHandler(errCode, "A Client Reset is required. " + + errorHandler.onError(this, new ClientResetRequiredError(errCode, "A Client Reset is required. " + "Read more here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup.", errorMessage, getConfiguration())); } else { @@ -251,13 +251,9 @@ public interface ErrorHandler { * When an exception is thrown in the error handler, the occurrence will be logged and the exception * will be ignored. * - * @param session {@link SyncSession} this error happened on. - * @param error type of error. - */ - void onError(SyncSession session, ObjectServerError error); - - /** - * An error that indicates the Realm needs to be reset. + *

                    + * When the {@code error.getErrorCode()} returns {@link ErrorCode#CLIENT_RESET}, it indicates the Realm + * needs to be reset and the {@code error} can be cast to {@link ClientResetRequiredError}. *

                    * A synced Realm may need to be reset because the Realm Object Server encountered an error and had * to be restored from a backup. If the backup copy of the remote Realm is of an earlier version @@ -275,7 +271,7 @@ public interface ErrorHandler { * The client reset process can be initiated in one of two ways: *

                      *
                    1. - * Run {@link ClientResetHandler#executeClientReset()} manually. All Realm instances must be + * Run {@link ClientResetRequiredError#executeClientReset()} manually. All Realm instances must be * closed before this method is called. *
                    2. *
                    3. @@ -290,11 +286,11 @@ public interface ErrorHandler { * synchronized to the Object Server. Those changes will only be present in the backed up file. It is therefore * recommended to close all open Realm instances as soon as possible. * + * * @param session {@link SyncSession} this error happened on. - * @param handler reference to the specific Client Reset error. - * @see Client Recovery From A Backup + * @param error type of error. */ - void onClientResetRequired(SyncSession session, ClientResetHandler handler); + void onError(SyncSession session, ObjectServerError error); } String accessToken(final AuthenticationServer authServer) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 734ad92d99..b1a78314af 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -70,14 +70,13 @@ user, getManagementRealmUrl(syncUser.getAuthenticationUrl())) .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { - RealmLog.error(String.format("Unexpected error with %s's management Realm: %s", - user.getIdentity(), - error.toString())); - } - - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { - RealmLog.error("Client Reset required for users management Realm: " + user.toString()); + if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { + RealmLog.error("Client Reset required for user's management Realm: " + user.toString()); + } else { + RealmLog.error(String.format("Unexpected error with %s's management Realm: %s", + user.getIdentity(), + error.toString())); + } } }) .modules(new PermissionModule()) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 89d598e1b8..7bbf233fde 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -7,7 +7,6 @@ import org.junit.runner.RunWith; import io.realm.BaseIntegrationTest; -import io.realm.ClientResetHandler; import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.Realm; @@ -75,11 +74,6 @@ public void onSuccess(SyncUser user) { public void onError(SyncSession session, ObjectServerError error) { fail("Session failed: " + error); } - - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { - fail("Client Reset"); - } }) .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index 5963837ab7..bfe2e752f1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -27,7 +27,6 @@ import java.util.concurrent.atomic.AtomicReference; import io.realm.BaseIntegrationTest; -import io.realm.ClientResetHandler; import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; @@ -68,11 +67,6 @@ public void create_acceptOffer() { public void onError(SyncSession session, ObjectServerError error) { fail("Realm 1 unexpected error: " + error); } - - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { - fail("Client Reset"); - } }) .build(); final Realm realm1 = Realm.getInstance(config1); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 7d1d8c7a14..56ba8589e5 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -33,7 +33,6 @@ import java.util.concurrent.TimeUnit; import io.realm.BaseIntegrationTest; -import io.realm.ClientResetHandler; import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; @@ -79,11 +78,6 @@ public void run() { public void onError(SyncSession session, ObjectServerError error) { fail("Sync failure: " + error); } - - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { - fail("Client Reset"); - } }) .build(); Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests @@ -145,11 +139,6 @@ public void run() { public void onError(SyncSession session, ObjectServerError error) { fail("Sync failure: " + error); } - - @Override - public void onClientResetRequired(SyncSession session, ClientResetHandler handler) { - fail("Client Reset"); - } }) .build(); Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests From 2125557f085c8ef709e1e00d30e23fa7b6226bde Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 3 Apr 2017 18:07:09 +0900 Subject: [PATCH 0589/2110] correct expected and actual in assertion (#4412) --- .../io/realm/internal/CollectionTests.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index fa33ca5ad9..e9c7a2d0f1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -196,12 +196,12 @@ public void sort() { @Test public void clear() { - assertEquals(table.size(), 4); + assertEquals(4, table.size()); Collection collection = new Collection(sharedRealm, table.where()); sharedRealm.beginTransaction(); collection.clear(); sharedRealm.commitTransaction(); - assertEquals(table.size(), 0); + assertEquals(0, table.size()); } @Test @@ -217,7 +217,7 @@ public void indexOf() { Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); UncheckedRow row = table.getUncheckedRow(0); - assertEquals(collection.indexOf(row), 3); + assertEquals(3, collection.indexOf(row)); } @Test @@ -225,7 +225,7 @@ public void indexOf_long() { SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); - assertEquals(collection.indexOf(0), 3); + assertEquals(3, collection.indexOf(0)); } @Test @@ -240,8 +240,8 @@ public void distinct() { assertEquals(3, collection.size()); assertEquals(2, collection2.size()); - assertEquals(collection2.getUncheckedRow(0).getLong(2), 3); - assertEquals(collection2.getUncheckedRow(1).getLong(2), 1); + assertEquals(3, collection2.getUncheckedRow(0).getLong(2)); + assertEquals(1, collection2.getUncheckedRow(1).getLong(2)); } // 1. Create a results and add listener. @@ -257,8 +257,8 @@ public void addListener_shouldBeCalledToReturnTheQueryResults() { collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { - assertEquals(collection1, collection); - assertEquals(collection1.size(), 4); + assertEquals(collection, collection1); + assertEquals(4, collection1.size()); sharedRealm.close(); looperThread.testComplete(); } @@ -277,8 +277,8 @@ public void addListener_shouldBeCalledWhenRefreshToReturnTheQueryResults() { collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { - assertEquals(collection1, collection); - assertEquals(collection1.size(), 4); + assertEquals(collection, collection1); + assertEquals(4, collection1.size()); sharedRealm.close(); onChangeCalled.set(true); } @@ -323,7 +323,7 @@ public void addListener_triggeredByRefresh() { collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection element) { - assertEquals(latch.getCount(), 1); + assertEquals(1, latch.getCount()); latch.countDown(); } }); @@ -346,8 +346,8 @@ public void addListener_queryNotReturned() { collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { - assertEquals(collection1, collection); - assertEquals(collection1.size(), 5); + assertEquals(collection, collection1); + assertEquals(5, collection1.size()); sharedRealm.close(); looperThread.testComplete(); } @@ -368,8 +368,8 @@ public void addListener_queryReturned() { collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { - assertEquals(collection1, collection); - assertEquals(collection1.size(), 5); + assertEquals(collection, collection1); + assertEquals(5, collection1.size()); sharedRealm.close(); looperThread.testComplete(); } @@ -394,10 +394,10 @@ public void addListener_triggeredByLocalCommit() { public void onChange(Collection collection1) { switch (listenerCounter.getAndIncrement()) { case 0: - assertEquals(collection1.size(), 4); + assertEquals(4, collection1.size()); break; case 1: - assertEquals(collection1.size(), 5); + assertEquals(5, collection1.size()); sharedRealm.close(); break; default: From 6ebcede20984fb46ba807502a716ec0099079ecd Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 3 Apr 2017 12:36:19 +0200 Subject: [PATCH 0590/2110] Revert progress listeners (#4414) --- CHANGELOG.md | 1 - examples/objectServerExample/build.gradle | 2 - .../objectserver/CounterActivity.java | 60 ---- .../src/main/res/layout/activity_counter.xml | 15 +- .../src/main/res/values/realm_colors.xml | 5 - .../java/io/realm/ProgressTests.java | 72 ---- .../java/io/realm/SessionTests.java | 48 --- .../src/main/cpp/io_realm_SyncSession.cpp | 75 +--- .../src/main/cpp/io_realm_internal_Util.cpp | 7 - .../src/main/cpp/jni_util/java_local_ref.hpp | 3 +- realm/realm-library/src/main/cpp/util.cpp | 4 - realm/realm-library/src/main/cpp/util.hpp | 4 - .../java/io/realm/internal/util/Pair.java | 88 ----- .../objectServer/java/io/realm/Progress.java | 131 ------- .../java/io/realm/ProgressListener.java | 56 --- .../java/io/realm/ProgressMode.java | 47 --- .../java/io/realm/SyncManager.java | 22 +- .../java/io/realm/SyncSession.java | 122 ------- .../java/io/realm/objectserver/AuthTests.java | 1 - .../BaseIntegrationTest.java | 36 +- .../objectserver/ManagementRealmTests.java | 1 - .../objectserver/ProcessCommitTests.java | 1 - .../objectserver/ProgressListenerTests.java | 338 ------------------ .../realm/objectserver/utils/Constants.java | 5 +- 24 files changed, 14 insertions(+), 1130 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/util/Pair.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/Progress.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java rename realm/realm-library/src/syncIntegrationTest/java/io/realm/{ => objectserver}/BaseIntegrationTest.java (59%) delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f9c327261..8f4f95d32a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ * Linking objects are not yet supported on dynamic objects * Migration for linking objects is not yet supported. * Backlink verification is incomplete. Evil code can cause native crashes. -* [ObjectServer] Added support for Sync Progress Notifications through `SyncSession.addDownloadProgressListener(ProgressMode, ProgressListener)` and `SyncSession.addUploadProgressListener(ProgressMode, ProgressListener)` (#4104). * [ObjectServer] In case of a Client Reset, information about the location of the backed up Realm file is now reported through the `ErrorHandler` interface (#4080). * [ObjectServer] Authentication URLs now automatically append `/auth` if no other path segment is set (#4370). * The listener on `RealmObject` will only be triggered if the object changes (#3894). diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index db813f22ad..8a42675c8a 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -61,9 +61,7 @@ realm { dependencies { compile 'com.android.support:support-v4:25.2.0' - compile 'com.android.support:appcompat-v7:25.2.0' compile 'com.android.support:design:25.2.0' - compile 'me.zhanghai.android.materialprogressbar:library:1.3.0' compile 'com.jakewharton:butterknife:8.3.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.3.0' } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 536b24ef90..97c67d2443 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -17,67 +17,32 @@ package io.realm.examples.objectserver; import android.content.Intent; -import android.graphics.PorterDuff; import android.os.Bundle; -import android.support.annotation.ColorRes; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; -import android.view.View; import android.widget.TextView; import java.util.Locale; -import java.util.concurrent.atomic.AtomicBoolean; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import io.realm.Progress; -import io.realm.ProgressListener; -import io.realm.ProgressMode; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.examples.objectserver.model.CRDTCounter; -import me.zhanghai.android.materialprogressbar.MaterialProgressBar; public class CounterActivity extends AppCompatActivity { private static final String REALM_URL = "realm://" + BuildConfig.OBJECT_SERVER_IP + ":9080/~/default"; private Realm realm; - private SyncSession session; private CRDTCounter counter; private SyncUser user; - private AtomicBoolean downloadingChanges = new AtomicBoolean(false); - private AtomicBoolean uploadingChanges = new AtomicBoolean(false); - private ProgressListener downloadListener = new ProgressListener() { - @Override - public void onChange(Progress progress) { - downloadingChanges.set(!progress.isTransferComplete()); - runOnUiThread(updateProgressBar); - } - }; - private ProgressListener uploadListener = new ProgressListener() { - @Override - public void onChange(Progress progress) { - uploadingChanges.set(!progress.isTransferComplete()); - runOnUiThread(updateProgressBar); - } - }; - private Runnable updateProgressBar = new Runnable() { - @Override - public void run() { - updateProgressBar(downloadingChanges.get(), uploadingChanges.get()); - } - }; - @BindView(R.id.text_counter) TextView counterView; - @BindView(R.id.progressbar) MaterialProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { @@ -121,21 +86,12 @@ public void onChange(CRDTCounter counter) { } }); counterView.setText("0"); - - // Setup progress listeners for indeterminate progress bars - session = SyncManager.getSession(config); - session.addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener); - session.addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener); } } @Override protected void onStop() { super.onStop(); - if (session != null) { - session.removeProgressListener(downloadListener); - session.removeProgressListener(uploadListener); - } closeRealm(); user = null; } @@ -176,22 +132,6 @@ public void decrementCounter() { adjustCounter(-1); } - private void updateProgressBar(boolean downloading, boolean uploading) { - @ColorRes int color = android.R.color.black; - int visibility = View.VISIBLE; - if (downloading && uploading) { - color = R.color.progress_both; - } else if (downloading) { - color = R.color.progress_download; - } else if (uploading) { - color = R.color.progress_upload; - } else { - visibility = View.GONE; - } - progressBar.getIndeterminateDrawable().setColorFilter(getResources().getColor(color), PorterDuff.Mode.SRC_IN); - progressBar.setVisibility(visibility); - } - private void adjustCounter(final int adjustment) { // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI // thread is HIGHLY discouraged as it might block longer than intended. Only use async transactions. diff --git a/examples/objectServerExample/src/main/res/layout/activity_counter.xml b/examples/objectServerExample/src/main/res/layout/activity_counter.xml index df73031aa7..62127eca0d 100644 --- a/examples/objectServerExample/src/main/res/layout/activity_counter.xml +++ b/examples/objectServerExample/src/main/res/layout/activity_counter.xml @@ -1,9 +1,7 @@ - + android:layout_height="match_parent"> - - diff --git a/examples/objectServerExample/src/main/res/values/realm_colors.xml b/examples/objectServerExample/src/main/res/values/realm_colors.xml index 8fec3b9456..aada8ea195 100644 --- a/examples/objectServerExample/src/main/res/values/realm_colors.xml +++ b/examples/objectServerExample/src/main/res/values/realm_colors.xml @@ -20,9 +20,4 @@ #d64881 #dadada - // Progress bar colors - #EF5350 - #9CCC65 - #FFA726 - \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java deleted file mode 100644 index 22c31bc2a5..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Locale; - -import static org.junit.Assert.assertEquals; - -@RunWith(AndroidJUnit4.class) -public class ProgressTests { - - @Test - public void getFractionTransferred() { - Object[][] testData = { - { 0L, 0L, 1.0D }, - { 0L, 1L, 0.0D }, - { 1L, 1L, 1.0D }, - { 1L, 2L, 0.5D } - }; - - for (Object[] test : testData) { - long transferredBytes = (long) test[0]; - long transferableBytes = (long) test[1]; - double fraction = (double) test[2]; - Progress progress = new Progress(transferredBytes, transferableBytes); - String errorMessage = String.format(Locale.US, "Failed with: (%d, %d)", transferredBytes, transferableBytes); - assertEquals(errorMessage, fraction, progress.getFractionTransferred(), 0.0D); - } - } - - @Test - public void getTransferredBytes () { - long[] testData = { 0, Long.MAX_VALUE }; - - for (long transferredBytes : testData) { - String errorMessage = String.format(Locale.US, "Failed with: %d", transferredBytes); - Progress progress = new Progress(transferredBytes, Long.MAX_VALUE); - assertEquals(errorMessage, transferredBytes, progress.getTransferredBytes()); - } - } - - @Test - public void getTransferableBytes () { - long[] testData = { 0, Long.MAX_VALUE }; - - for (long transferableBytes : testData) { - String errorMessage = String.format(Locale.US, "Failed with: %d", transferableBytes); - Progress progress = new Progress(0, transferableBytes); - assertEquals(errorMessage, transferableBytes, progress.getTransferableBytes()); - } - } - -} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 7ee77dfa61..a360d94fed 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -61,54 +61,6 @@ public void get_syncValues() { assertEquals(configuration, session.getConfiguration()); } - @Test - public void addDownloadProgressListener_nullThrows() { - SyncSession session = SyncManager.getSession(configuration); - try { - session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, null); - fail(); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void addUploadProgressListener_nullThrows() { - SyncSession session = SyncManager.getSession(configuration); - try { - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, null); - fail(); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void removeProgressListener() { - Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getSession(configuration); - ProgressListener[] listeners = new ProgressListener[] { - null, - new ProgressListener() { - @Override - public void onChange(Progress progress) { - // Listener 1, not present - } - }, - new ProgressListener() { - @Override - public void onChange(Progress progress) { - // Listener 2, present - } - } - }; - session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, listeners[2]); - - // Check that remove works unconditionally for all input - for (ProgressListener listener : listeners) { - session.removeProgressListener(listener); - } - realm.close(); - } - // Check that a Client Reset is correctly reported. @Test @RunTestInLooperThread diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 88e80e151f..e3a58bbad2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -15,7 +15,6 @@ */ #include -#include #include "io_realm_SyncSession.h" @@ -23,22 +22,22 @@ #include "object-store/src/sync/sync_session.hpp" #include "util.hpp" -#include "jni_util/jni_utils.hpp" +using namespace std; using namespace realm; using namespace sync; JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JNIEnv* env, jclass, - jstring j_local_realm_path, - jstring j_access_token, + jstring localRealmPath, + jstring accessToken, jstring sync_realm_url) { TR_ENTER() try { - JStringAccessor local_realm_path(env, j_local_realm_path); + JStringAccessor local_realm_path(env, localRealmPath); auto session = SyncManager::shared().get_existing_session(local_realm_path); if (session) { - JStringAccessor access_token(env, j_access_token); + JStringAccessor access_token(env, accessToken); JStringAccessor realm_url(env, sync_realm_url); session->refresh_access_token(access_token, std::string(realm_url)); return JNI_TRUE; @@ -50,67 +49,3 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JN CATCH_STD() return JNI_FALSE; } - - -JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIEnv* env, jclass, - jstring j_local_realm_path, - jlong listener_id, jint direction, - jboolean is_streaming) -{ - try { - // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda - realm::StringData local_realm_path(JStringAccessor(env, j_local_realm_path)); - std::shared_ptr session = SyncManager::shared().get_existing_active_session(local_realm_path); - if (!session) { - // FIXME: We should lift this restriction - ThrowException(env, IllegalState, - "Cannot register a progress listener before a session is " - "created. A session will be created after the first call to Realm.getInstance()."); - return static_cast(0); - } - - SyncSession::NotifierType type = - (direction == 1) ? SyncSession::NotifierType::download : SyncSession::NotifierType::upload; - - std::function callback = [local_realm_path, listener_id]( - uint64_t transferred, uint64_t transferrable) { - JNIEnv* local_env = jni_util::JniUtils::get_env(true); - - auto path = to_jstring(local_env, local_realm_path); - local_env->CallStaticVoidMethod(java_syncmanager_class, java_notify_progress_listener, path, listener_id, - static_cast(transferred), static_cast(transferrable)); - - // All exceptions will be caught on the Java side of handlers, but errors will still end - // up here, so we need to do something sensible with them. - // Throwing a C++ exception will terminate the sync thread and cause the pending Java - // exception to become visible. For some (unknown) reason Logcat will not see the C++ - // exception, only the Java one. - if (local_env->ExceptionCheck()) { - local_env->ExceptionDescribe(); - throw std::runtime_error("An unexpected Error was thrown from Java. See LogCat"); - } - - // Callback happens on a thread not controlled by the JVM. So manual cleanup is - // required. - local_env->DeleteLocalRef(path); - }; - uint64_t token = session->register_progress_notifier(callback, type, to_bool(is_streaming)); - return static_cast(token); - } - CATCH_STD() - return static_cast(0); -} - -JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveProgressListener(JNIEnv* env, jclass, - jstring j_local_realm_path, - jlong listener_token) -{ - try { - JStringAccessor local_realm_path(env, j_local_realm_path); - std::shared_ptr session = SyncManager::shared().get_existing_active_session(local_realm_path); - if (session) { - session->unregister_progress_notifier(static_cast(listener_token)); - } - } - CATCH_STD() -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 7946856b1c..48e47cf726 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -53,10 +53,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) java_lang_double_init = env->GetMethodID(java_lang_double, "", "(D)V"); java_util_date = GetClass(env, "java/util/Date"); java_util_date_init = env->GetMethodID(java_util_date, "", "(J)V"); -#if REALM_ENABLE_SYNC - java_syncmanager_class = GetClass(env, "io/realm/SyncManager"); - java_notify_progress_listener = env->GetStaticMethodID(java_syncmanager_class, "notifyProgressListener", "(Ljava/lang/String;JJJ)V"); -#endif } return JNI_VERSION_1_6; @@ -74,9 +70,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) env->DeleteGlobalRef(java_lang_double); env->DeleteGlobalRef(java_util_date); env->DeleteGlobalRef(java_lang_string); - #if REALM_ENABLE_SYNC - env->DeleteGlobalRef(java_syncmanager_class); - #endif JniUtils::release(); } } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp index f29c5a3335..283c948fba 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp @@ -29,7 +29,8 @@ static constexpr NeedToCreateLocalRef need_to_create_local_ref{}; // Wraps jobject and automatically calls DeleteLocalRef when this object is destroyed. // DeleteLocalRef is not necessary to be called in most cases since all local references will be cleaned up when the // program returns to Java from native. But if the local ref is created in a loop, consider to use this class to wrap -// it because the size of local reference table is relative small (512 bytes on Android). +// it +// because the size of local reference table is relative small (512 bytes on Android). template class JavaLocalRef { public: diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 8908d1ae3e..e562c12564 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -43,10 +43,6 @@ jclass java_lang_string; jmethodID java_lang_double_init; jclass java_util_date; jmethodID java_util_date_init; -#if REALM_ENABLE_SYNC -jclass java_syncmanager_class; -jmethodID java_notify_progress_listener; -#endif void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 615887dfa7..c0ec632f62 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -703,10 +703,6 @@ extern jclass java_lang_string; extern jmethodID java_lang_double_init; extern jclass java_util_date; extern jmethodID java_util_date_init; -#if REALM_ENABLE_SYNC -extern jclass java_syncmanager_class; -extern jmethodID java_notify_progress_listener; -#endif inline jobject NewLong(JNIEnv* env, int64_t value) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java deleted file mode 100644 index cac85f8077..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2009 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. - */ - -package io.realm.internal.util; - -/** - * Copy from the Android framework to avoid the dependency on Android classes + slight adjustment - * to support older versions of Android. - * - * Container to ease passing around a tuple of two objects. This object provides a sensible - * implementation of equals(), returning true if equals() is true on each of the contained - * objects. - */ -public class Pair { - public F first; - public S second; - - /** - * Constructor for a Pair. - * - * @param first the first object in the Pair. - * @param second the second object in the pair. - */ - public Pair(F first, S second) { - this.first = first; - this.second = second; - } - - /** - * Checks the two objects for equality by delegating to their respective - * {@link Object#equals(Object)} methods. - * - * @param o the {@link Pair} to which this one is to be checked for equality. - * @return true if the underlying objects of the Pair are both considered - * equal. - */ - @Override - public boolean equals(Object o) { - if (!(o instanceof Pair)) { - return false; - } - Pair p = (Pair) o; - return equals(p.first, first) && (equals(p.second, second)); - } - - private boolean equals(Object a, Object b) { - return (a == b) || (a != null && a.equals(b)); - } - - /** - * Compute a hash code using the hash codes of the underlying objects. - * - * @return a hashcode of the Pair. - */ - @Override - public int hashCode() { - return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); - } - - @Override - public String toString() { - return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}"; - } - - /** - * Convenience method for creating an appropriately typed pair. - * - * @param a the first object in the Pair. - * @param b the second object in the pair. - * @return a Pair that is templatized with the types of a and b. - */ - public static Pair create(A a, B b) { - return new Pair(a, b); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/Progress.java b/realm/realm-library/src/objectServer/java/io/realm/Progress.java deleted file mode 100644 index db4e7cdc9a..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/Progress.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -/** - * Class used to encapsulate progress notifications when either downloading or uploading Realm data. - * Each instance of this class is an immutable snapshot of the current progress. - *

                      - * If the {@link ProgressListener} was registered with {@link ProgressMode#INDEFINITELY}, the progress reported by - * {@link #getFractionTransferred()} can both increase and decrease since more changes might be added while - * the progres listener is registered. This means it is possible for one notification to report - * {@code true} for {@link #isTransferComplete()}, and then on the next event report {@code false}. - *

                      - * If the {@link ProgressListener} was registered with {@link ProgressMode#CURRENT_CHANGES}, progress can only ever - * increase, and once {@link #isTransferComplete()} returns {@code true}, no further events will be generated. - * - * @see SyncSession#addDownloadProgressListener(ProgressMode, ProgressListener) - * @see SyncSession#addUploadProgressListener(ProgressMode, ProgressListener) - */ -public class Progress { - - private final long transferredBytes; - private final long transferableBytes; - - /** - * Creates a snapshot of the current progress when downloading or uploading changes. - * - * @param transferredBytes number of bytes transferred. - * @param transferableBytes total number of bytes that needs to be transferred (including those already transferred). - */ - Progress(long transferredBytes, long transferableBytes) { - this.transferredBytes = transferredBytes; - this.transferableBytes = transferableBytes; - } - - /** - * Returns the total number of bytes that has been transferred since the {@link ProgressListener} was added. - * - * @return the total number of bytes transferred since the {@link ProgressListener} was added. - */ - public long getTransferredBytes() { - return transferredBytes; - } - - /** - * Returns the total number of transferable bytes (bytes that have been transferred + bytes pending transfer). - *

                      - * If the {@link ProgressListener} is tracking downloads, this number represents the size of the changesets - * generated by all other clients using the Realm. - *

                      - * If the {@link ProgressListener} is tracking uploads, this number represents the size of changesets created - * locally. - * - * @return the total number of bytes that has been transferred + number of bytes still pending transfer. - */ - public long getTransferableBytes() { - return transferableBytes; - } - - /** - * The fraction of bytes transferred out of all transferable bytes. Counting from since the {@link ProgressListener} - * was added. - * - * @return a number between {@code 0.0} and {@code 1.0}, where {@code 0.0} represents that no data has been - * transferred yet, and {@code 1.0} that all data has been transferred. - */ - public double getFractionTransferred() { - if (transferableBytes == 0) { - return 1.0D; - } else { - double percentage = (double) transferredBytes / (double) transferableBytes; - return percentage > 1.0D ? 1.0D : percentage; - } - } - - /** - * Returns {@code true} when all pending bytes have been transferred. - *

                      - * If the {@link ProgressListener} was registered with {@link ProgressMode#INDEFINITELY}, this method can return - * {@code false} for subsequent events after returning {@code true}. - *

                      - * If the {@link ProgressListener} was registered with {@link ProgressMode#CURRENT_CHANGES}, when this method - * returns {@code true}, no more progress events will be sent. - * - * @return {@code true} if all changes have been transferred, {@code false} otherwise. - */ - public boolean isTransferComplete() { - return transferredBytes >= transferableBytes; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Progress progress = (Progress) o; - - if (transferredBytes != progress.transferredBytes) return false; - return transferableBytes == progress.transferableBytes; - - } - - @Override - public int hashCode() { - int result = (int) (transferredBytes ^ (transferredBytes >>> 32)); - result = 31 * result + (int) (transferableBytes ^ (transferableBytes >>> 32)); - return result; - } - - @Override - public String toString() { - return "Progress{" + - "transferredBytes=" + transferredBytes + - ", transferableBytes=" + transferableBytes + - '}'; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java b/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java deleted file mode 100644 index 5b5798f2d4..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -/** - * Interface used when interested in updates on data either being uploaded to or downloaded from - * a Realm Object Server. - */ -public interface ProgressListener { - /** - * This method will be called periodically from the underlying Object Server Client responsible - * for uploading and downloading changes from the remote Object Server. - *

                      - * This callback will not happen on the UI thread, but on the worker thread controlling - * the Object Server Client. Use {@code Activity.runOnUiThread(Runnable)} or similar to update - * any UI elements. - *

                      - *

                      -     * {@code
                      -     * // Adding an upload progress listener that completes when all known changes have been
                      -     * // uploaded.
                      -     * session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() {
                      -     *   \@Override
                      -     *    public void onChange(Progress progress) {
                      -     *      activity.runOnUiThread(new Runnable() {
                      -     *        \@Override
                      -     *         public void run() {
                      -     *           updateProgressBar(progress);
                      -     *         }
                      -     *      });
                      -     *      if (progress.isTransferComplete() {
                      -     *        session.removeProgressListener(this);
                      -     *      }
                      -     *    }
                      -     * });
                      -     * }
                      -     * 
                      - * - * @param progress an immutable progress change event with information about current progress. This object is thread safe. - */ - void onChange(Progress progress); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java b/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java deleted file mode 100644 index f80f63150d..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -/** - * Enum describing how to listen to progress changes. - */ -public enum ProgressMode { - /** - * When registering the {@link ProgressListener}, it will record the current size of changes, and will only - * continue to report progress updates until those changes have been either downloaded or uploaded. After that - * the progress listener will not report any further changes. - *

                      - * This means that listeners registered in this mode should be done before changes are written to - * the Realm. - *

                      - * Progress reported in this mode will only ever increase. - *

                      - * This is useful when e.g. reporting progress when downloading a Realm for the first time. - */ - CURRENT_CHANGES, - - /** - * A {@link ProgressListener} registered in this mode, will continue to report progress changes, even - * if changes are being added after the listener was registered. - *

                      - * Progress reported in this mode can both increase and decrease, e.g. if large amounts of data is - * written after registering the listener. - *

                      - * This is useful when you want to track if all changes have been uploaded to the server from the device. - */ - INDEFINITELY -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index abf5951752..0d37ce79eb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -16,9 +16,9 @@ package io.realm; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -90,7 +90,7 @@ public void onError(SyncSession session, ObjectServerError error) { } }; // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path' - private static Map sessions = new ConcurrentHashMap<>(); + private static Map sessions = new HashMap(); private static CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList(); // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. @@ -248,24 +248,6 @@ private static synchronized void notifyErrorHandler(int errorCode, String errorM } } - /** - * All progress listener events from native Sync are reported to this method. - * It costs 2 HashMap lookups for each listener triggered (one to find the session, one to - * find the progress listener), but it means we don't have to cache anything on the C++ side which - * can leak since we don't have control over the session lifecycle. - */ - @SuppressWarnings("unused") - private static synchronized void notifyProgressListener(String localRealmPath, long listenerId, long transferedBytes, long transferableBytes) { - SyncSession session = sessions.get(localRealmPath); - if (session != null) { - try { - session.notifyProgressListener(listenerId, transferedBytes, transferableBytes); - } catch (Exception exception) { - RealmLog.error(exception); - } - } - } - /** * This is called from the Object Store (through JNI) to request an {@code access_token} for * the session specified by sessionPath. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index e301800027..b456318617 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -17,16 +17,11 @@ package io.realm; import java.net.URI; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; import io.realm.internal.Keep; import io.realm.internal.KeepMember; @@ -38,7 +33,6 @@ import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; -import io.realm.internal.util.Pair; import io.realm.log.RealmLog; /** @@ -56,8 +50,6 @@ public class SyncSession { private final static ScheduledThreadPoolExecutor REFRESH_TOKENS_EXECUTOR = new ScheduledThreadPoolExecutor(1); private final static long REFRESH_MARGIN_DELAY = TimeUnit.SECONDS.toMillis(10); - private final static int DIRECTION_DOWNLOAD = 1; - private final static int DIRECTION_UPLOAD = 2; private final SyncConfiguration configuration; private final ErrorHandler errorHandler; @@ -68,19 +60,6 @@ public class SyncSession { private AtomicBoolean onGoingAccessTokenQuery = new AtomicBoolean(false); private volatile boolean isClosed = false; - // We need JavaId -> Listener so C++ can trigger callbacks without keeping a reference to the - // jobject, which would require a similar map on the C++ side. - // We need Listener -> Token map in order to remove the progress listener in C++ from Java. - private Map> listenerIdToProgressListenerMap = new HashMap<>(); - private Map progressListenerToOsTokenMap = new IdentityHashMap<>(); - // Counter used to assign all ProgressListeners on this session with a unique id. - // ListenerId is created by Java to enable C++ to reference the java listener without holding - // a reference to the actual object. - // ListenerToken is the same concept, but created by OS and represents the listener. - // We can unfortunately not just use the ListenerToken, since we need it to be available before - // we register the listener. - AtomicLong progressListenerId = new AtomicLong(-1); - SyncSession(SyncConfiguration configuration) { this.configuration = configuration; this.errorHandler = configuration.getErrorHandler(); @@ -131,105 +110,6 @@ void notifySessionError(int errorCode, String errorMessage) { } } - // Called from native code - @SuppressWarnings("unused") - @KeepMember - synchronized void notifyProgressListener(long listenerId, long transferredBytes, long transferableBytes) { - Pair listener = listenerIdToProgressListenerMap.get(listenerId); - if (listener != null) { - Progress newProgressNotification = new Progress(transferredBytes, transferableBytes); - if (!newProgressNotification.equals(listener.second)) { - listener.first.onChange(newProgressNotification); - listener.second = newProgressNotification; - } - } else { - RealmLog.debug("Trying unknown listener failed: " + listenerId); - } - } - - /** - * Adds a progress listener tracking changes that need to be downloaded from the Realm Object - * Server. - *

                      - * The {@link ProgressListener} will be triggered immediately when registered, and periodically - * afterwards. - * - * @param mode type of mode used. See {@link ProgressMode} for more information. - * @param listener the listener to register. - */ - public synchronized void addDownloadProgressListener(ProgressMode mode, ProgressListener listener) { - addProgressListener(mode, DIRECTION_DOWNLOAD, listener); - } - - /** - * Adds a progress listener tracking changes that need to be uploaded from the device to the - * Realm Object Server. - *

                      - * The {@link ProgressListener} will be triggered immediately when registered, and periodically - * afterwards. - * - * @param mode type of mode used. See {@link ProgressMode} for more information. - * @param listener the listener to register. - */ - public synchronized void addUploadProgressListener(ProgressMode mode, ProgressListener listener) { - addProgressListener(mode, DIRECTION_UPLOAD, listener); - } - - /** - * Removes a progress listener. If the listener wasn't registered, this method will do nothing. - * - * @param listener listener to remove. - */ - public synchronized void removeProgressListener(ProgressListener listener) { - if (listener == null) { - return; - } - // If an exception is thrown somewhere in here, we will most likely leave the various - // maps in an inconsistent manner. Not much we can do about it. - Long token = progressListenerToOsTokenMap.remove(listener); - if (token != null) { - Iterator>> it = listenerIdToProgressListenerMap.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry> entry = it.next(); - if (entry.getValue().first.equals(listener)) { - it.remove(); - break; - } - } - nativeRemoveProgressListener(configuration.getPath(), token); - } - } - - private void addProgressListener(ProgressMode mode, int direction, ProgressListener listener) { - checkProgressListenerArguments(mode, listener); - boolean isStreaming = (mode == ProgressMode.INDEFINITELY); - long listenerId = progressListenerId.incrementAndGet(); - - // A listener might be triggered immediately as part of `nativeAddProgressListener`, so - // we need to make sure it can be found by SyncManager.notifyProgressListener() - listenerIdToProgressListenerMap.put(listenerId, new Pair(listener, null)); - long listenerToken = nativeAddProgressListener(configuration.getPath(), listenerId , direction, isStreaming); - if (listenerToken == 0) { - // ObjectStore did not register the listener. This can happen if a - // listener is registered with ProgressMode.CURRENT_CHANGES and no changes actually - // exists. In that case the listener was triggered immediately and we just need - // to clean it up, since it will never be called again. - listenerIdToProgressListenerMap.remove(listenerId); - } else { - // Listener was properly registered. - progressListenerToOsTokenMap.put(listener, listenerToken); - } - } - - private void checkProgressListenerArguments(ProgressMode mode, ProgressListener listener) { - if (listener == null) { - throw new IllegalArgumentException("Non-null 'listener' required."); - } - if (mode == null) { - throw new IllegalArgumentException("Non-null 'mode' required."); - } - } - void close() { isClosed = true; if (networkRequest != null) { @@ -472,7 +352,5 @@ private void clearScheduledAccessTokenRefresh() { } private static native boolean nativeRefreshAccessToken(String path, String accessToken, String authURL); - private static native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); - private static native void nativeRemoveProgressListener(String localRealmPath, long listenerToken); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 7bbf233fde..ae1cda7ac7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -6,7 +6,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import io.realm.BaseIntegrationTest; import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.Realm; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java similarity index 59% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java rename to realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java index 57e50f7616..764b511ee7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java @@ -14,31 +14,19 @@ * limitations under the License. */ -package io.realm; +package io.realm.objectserver; import android.support.test.InstrumentationRegistry; import org.junit.AfterClass; import org.junit.BeforeClass; -import java.util.UUID; - -import io.realm.ObjectServerError; import io.realm.Realm; -import io.realm.SyncConfiguration; -import io.realm.SyncCredentials; import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncUser; import io.realm.log.RealmLog; -import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.HttpUtils; -import io.realm.objectserver.utils.UserFactory; - -import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; -public class BaseIntegrationTest { +class BaseIntegrationTest { @BeforeClass public static void setUp () throws Exception { @@ -57,28 +45,8 @@ public static void setUp () throws Exception { public static void tearDown () throws Exception { try { HttpUtils.stopSyncServer(); - SyncManager.reset(); } catch (Exception e) { RealmLog.error("Failed to stop Sync Server", e); } } - - /** - * Login the admin user synchronously. - */ - public SyncUser loginAdminUser() { - SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentials = SyncCredentials.accessToken(admin.getAccessToken().value(), "custom-admin-user"); - return SyncUser.login(credentials, Constants.AUTH_URL); - } - - /** - * Create new random user and log in. - */ - public SyncUser loginUser() { - String id = UUID.randomUUID().toString(); - SyncCredentials credentials = SyncCredentials.usernamePassword(id, "password", true); - return SyncUser.login(credentials, Constants.AUTH_URL); - } - } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index bfe2e752f1..f78c34acfe 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -26,7 +26,6 @@ import java.util.Date; import java.util.concurrent.atomic.AtomicReference; -import io.realm.BaseIntegrationTest; import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 56ba8589e5..dfd7b31859 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -32,7 +32,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import io.realm.BaseIntegrationTest; import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java deleted file mode 100644 index 54473d4ec7..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.objectserver; - -import android.support.annotation.NonNull; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.net.URI; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import io.realm.BaseIntegrationTest; -import io.realm.Progress; -import io.realm.ProgressListener; -import io.realm.ProgressMode; -import io.realm.Realm; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncUser; -import io.realm.TestHelper; -import io.realm.entities.AllTypes; -import io.realm.objectserver.utils.Constants; -import io.realm.rule.TestSyncConfigurationFactory; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class ProgressListenerTests extends BaseIntegrationTest { - - private static final long TEST_SIZE = 10; - @Rule - public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - - @NonNull - private SyncConfiguration createSyncConfig() { - SyncUser user = loginAdminUser(); - return configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL).build(); - } - - private void writeSampleData(Realm realm) { - realm.beginTransaction(); - for (int i = 0; i < TEST_SIZE; i++) { - AllTypes obj = realm.createObject(AllTypes.class); - obj.setColumnString("Object " + i); - } - realm.commitTransaction(); - } - - private void assertTransferComplete(Progress progress, boolean nonZeroChange) { - assertTrue(progress.isTransferComplete()); - assertEquals(1.0D, progress.getFractionTransferred(), 0.0D); - assertEquals(progress.getTransferableBytes(), progress.getTransferredBytes()); - if (nonZeroChange) { - assertTrue(progress.getTransferredBytes() > 0); - } - } - - // Create remote data for a given user. - private URI createRemoteData(SyncUser user) { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_USER_REALM).build(); - final Realm realm = Realm.getInstance(config); - writeSampleData(realm); - final CountDownLatch changesUploaded = new CountDownLatch(1); - final SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - session.removeProgressListener(this); - changesUploaded.countDown(); - } - } - }); - TestHelper.awaitOrFail(changesUploaded); - realm.close(); - return config.getServerUrl(); - } - - @Test - public void downloadProgressListener_changesOnly() { - final CountDownLatch allChangesDownloaded = new CountDownLatch(1); - SyncUser userWithData = loginUser(); - URI serverUrl = createRemoteData(userWithData); - SyncUser adminUser = loginAdminUser(); - - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()).build(); - Realm realm = Realm.getInstance(config); - SyncSession session = SyncManager.getSession(config); - session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - assertTransferComplete(progress, true); - Realm realm = Realm.getInstance(config); - assertEquals(TEST_SIZE, realm.where(AllTypes.class).count()); - realm.close(); - allChangesDownloaded.countDown(); - } - } - }); - TestHelper.awaitOrFail(allChangesDownloaded); - realm.close(); - userWithData.logout(); - adminUser.logout(); - } - - @Test - public void downloadProgressListener_indefinitely() throws InterruptedException { - final AtomicInteger transferCompleted = new AtomicInteger(0); - final CountDownLatch allChangesDownloaded = new CountDownLatch(1); - final CountDownLatch startWorker = new CountDownLatch(1); - final SyncUser userWithData = loginUser(); - - URI serverUrl = createRemoteData(userWithData); - - // Create worker thread that puts data into another Realm. - // This is to avoid blocking one progress listener while waiting for another to complete. - Thread worker = new Thread(new Runnable() { - @Override - public void run() { - TestHelper.awaitOrFail(startWorker); - createRemoteData(userWithData); - } - }); - worker.start(); - - SyncUser adminUser = loginAdminUser(); - final SyncConfiguration adminConfig = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()).build(); - Realm adminRealm = Realm.getInstance(adminConfig); - Realm userRealm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(userWithData, Constants.SYNC_USER_REALM).build()); // Keep session alive - SyncSession session = SyncManager.getSession(adminConfig); - session.addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - switch (transferCompleted.incrementAndGet()) { - case 1: - // Initial trigger when registering - assertTransferComplete(progress, false); - break; - case 2: { - assertTransferComplete(progress, true); - Realm adminRealm = Realm.getInstance(adminConfig); - assertEquals(TEST_SIZE, adminRealm.where(AllTypes.class).count()); - adminRealm.close(); - startWorker.countDown(); - break; - } - case 3: { - assertTransferComplete(progress, true); - Realm adminRealm = Realm.getInstance(adminConfig); - assertEquals(TEST_SIZE * 2, adminRealm.where(AllTypes.class).count()); - adminRealm.close(); - allChangesDownloaded.countDown(); - break; - } - default: - fail(); - } - } - } - }); - TestHelper.awaitOrFail(allChangesDownloaded); - adminRealm.close(); - userRealm.close(); - userWithData.logout(); - adminUser.logout(); - worker.join(); - } - - @Test - public void uploadProgressListener_changesOnly() { - final CountDownLatch allChangeUploaded = new CountDownLatch(1); - SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - - SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - assertTransferComplete(progress, true); - allChangeUploaded.countDown(); - } - } - }); - - TestHelper.awaitOrFail(allChangeUploaded); - realm.close(); - } - - @Test - public void uploadProgressListener_indefinitely() { - final AtomicInteger transferCompleted = new AtomicInteger(0); - final CountDownLatch testDone = new CountDownLatch(1); - final SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - - writeSampleData(realm); // Write first batch of sample data - SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - switch(transferCompleted.incrementAndGet()) { - case 1: - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - realm.close(); - break; - case 2: - assertTransferComplete(progress, true); - testDone.countDown(); - break; - default: - fail("Unsupported number of transfers completed: " + transferCompleted.get()); - } - } - } - }); - - TestHelper.awaitOrFail(testDone); - realm.close(); - } - - @Test - public void addListenerInsideCallback() { - final CountDownLatch allChangeUploaded = new CountDownLatch(1); - final SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - - final SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - realm.close(); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - allChangeUploaded.countDown(); - } - } - }); - } - } - }); - - TestHelper.awaitOrFail(allChangeUploaded); - realm.close(); - } - - @Test - public void addListenerInsideCallback_mixProgressModes() { - final CountDownLatch allChangeUploaded = new CountDownLatch(3); - final AtomicBoolean progressCompletedReported = new AtomicBoolean(false); - final SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - - final SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - allChangeUploaded.countDown(); - if (progressCompletedReported.compareAndSet(false, true)) { - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - realm.close(); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - allChangeUploaded.countDown(); - } - } - }); - } - } - } - }); - - TestHelper.awaitOrFail(allChangeUploaded); - realm.close(); - } - - @Test - public void addProgressListener_triggerImmediatelyWhenRegistered() { - final SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - SyncSession session = SyncManager.getSession(config); - - checkListener(session, ProgressMode.INDEFINITELY); - checkListener(session, ProgressMode.CURRENT_CHANGES); - - realm.close(); - } - - private void checkListener(SyncSession session, ProgressMode progressMode) { - final CountDownLatch listenerCalled = new CountDownLatch(1); - session.addDownloadProgressListener(progressMode, new ProgressListener() { - @Override - public void onChange(Progress progress) { - listenerCalled.countDown(); - } - }); - TestHelper.awaitOrFail(listenerCalled); - } - -} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java index a4c11802dd..e5347effc8 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -18,9 +18,8 @@ public class Constants { - public static String SYNC_USER_REALM = "realm://127.0.0.1:9080/~/tests"; - public static String SYNC_SERVER_URL = "realm://127.0.0.1:9080/tests"; - public static String SYNC_SERVER_URL_2 = "realm://127.0.0.1:9080/tests2"; + public static String SYNC_SERVER_URL = "realm://127.0.0.1/tests"; + public static String SYNC_SERVER_URL_2 = "realm://127.0.0.1/tests2"; public static String AUTH_SERVER_URL = "http://127.0.0.1:9080/"; public static String AUTH_URL = AUTH_SERVER_URL + "auth"; From 5b55c6d382a848a10ab01cc00b2d7b1b1c4a7833 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 4 Apr 2017 15:38:33 +0800 Subject: [PATCH 0591/2110] Update object store (#4420) to 3b6c0f6110. Fix #4369 --- CHANGELOG.md | 2 ++ realm/realm-library/src/main/cpp/object-store | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f4f95d32a..ea90d0b865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ ### Bug Fixes +* Crash with `LogicError` with `Bad version number` on notifier thread (#4369). + ### Deprecated ### Internal diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 3eada170f3..3b6c0f6110 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 3eada170f380174992b47b6023f375f3f372a9d2 +Subproject commit 3b6c0f611061dddabc489f0b9f264306893c96c8 From 207f0903af5d9bf0c4cebb8941982a50a0d228c5 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 4 Apr 2017 16:40:44 +0800 Subject: [PATCH 0592/2110] Acquire a global ref of jstring for field changes (#4421) When there are more than 512 fields change, the JNI local ref table size limitation may be reached. Fix #4378 --- .../java/io/realm/ObjectChangeSetTests.java | 34 +++++++++++++++++++ .../main/cpp/io_realm_internal_OsObject.cpp | 7 ++-- .../src/main/cpp/jni_util/java_global_ref.hpp | 6 +++- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java index 942e7cb848..c36aef4b56 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java @@ -337,4 +337,38 @@ public void onChange(AllTypes object, ObjectChangeSet changeSet) { allTypes.deleteFromRealm(); realm.commitTransaction(); } + + // When there are more than 512 fields change, the JNI local ref table size limitation may be reached. + @Test + @RunTestInLooperThread + public void moreFieldsChangedThanLocalRefTableSize() { + final String CLASS_NAME = "ManyFields"; + final int FIELD_COUNT = 1024; + RealmConfiguration config = looperThread.createConfiguration("many_fields"); + final DynamicRealm realm = DynamicRealm.getInstance(config); + + realm.beginTransaction(); + RealmSchema schema = realm.getSchema(); + RealmObjectSchema objectSchema = schema.create(CLASS_NAME); + for (int i = 0; i < FIELD_COUNT; i++) { + objectSchema.addField("field" + i, int.class); + } + DynamicRealmObject obj = realm.createObject(CLASS_NAME); + realm.commitTransaction(); + + obj.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(DynamicRealmObject object, ObjectChangeSet changeSet) { + assertEquals(FIELD_COUNT, changeSet.getChangedFields().length); + realm.close(); + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + for (int i = 0; i < FIELD_COUNT; i++) { + obj.setInt("field" + i, 42); + } + realm.commitTransaction(); + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index abe2e666d3..c35c042672 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -70,7 +70,8 @@ struct ChangeCallback { return; } - std::vector field_names; + // The local ref of jstring needs to be released to avoid reach the local ref table size limitation. + std::vector field_names; auto table = m_wrapper->m_object.row().get_table(); for (size_t i = 0; i < change_set.columns.size(); ++i) { if (change_set.columns[i].empty()) { @@ -78,11 +79,11 @@ struct ChangeCallback { } // FIXME: After full integration of the OS schema, parse the column name from // wrapper->m_object.get_object_schema() will be faster. - field_names.push_back(to_jstring(env, table->get_column_name(i))); + field_names.push_back(JavaGlobalRef(env, to_jstring(env, table->get_column_name(i)), true)); } m_field_names_array = env->NewObjectArray(field_names.size(), java_lang_string, 0); for (size_t i = 0; i < field_names.size(); ++i) { - env->SetObjectArrayElement(m_field_names_array, i, field_names[i]); + env->SetObjectArrayElement(m_field_names_array, i, field_names[i].get()); } } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp index a08aad4bd6..f2d0c3320d 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp @@ -29,9 +29,13 @@ class JavaGlobalRef { : m_ref(nullptr) { } - JavaGlobalRef(JNIEnv* env, jobject obj) + // Acquire a global ref on the given jobject. The local ref will be released if given release_local_ref is true. + JavaGlobalRef(JNIEnv* env, jobject obj, bool release_local_ref = false) : m_ref(obj ? env->NewGlobalRef(obj) : nullptr) { + if (release_local_ref) { + env->DeleteLocalRef(obj); + } } JavaGlobalRef(JavaGlobalRef&& rhs) : m_ref(rhs.m_ref) From 4fce41319de8e69302e84e018e13c27ff5dd51c5 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Tue, 4 Apr 2017 05:44:31 -0700 Subject: [PATCH 0593/2110] Feature/backlinks (#4406) * Refactor RealmObjectSchema Clean up Proxy generation Comments addressed * Fix Test, Checkstyle and Findbugs errors * Respond to comments --- .../processor/RealmProxyClassGenerator.java | 70 +- .../RealmProxyMediatorGenerator.java | 63 +- .../io/realm/AllTypesRealmProxy.java | 43 +- .../io/realm/BooleansRealmProxy.java | 21 +- .../io/realm/NullTypesRealmProxy.java | 75 +- .../io/realm/RealmDefaultModuleMediator.java | 38 +- .../resources/io/realm/SimpleRealmProxy.java | 15 +- .../java/io/realm/BulkInsertTests.java | 2 +- .../java/io/realm/CollectionTests.java | 2 +- .../java/io/realm/DynamicRealmTests.java | 4 +- .../java/io/realm/IOSRealmTests.java | 2 +- .../io/realm/ManagedRealmCollectionTests.java | 10 +- .../java/io/realm/RealmAsyncQueryTests.java | 6 +- .../java/io/realm/RealmLinkTests.java | 8 +- .../java/io/realm/RealmModelTests.java | 4 +- .../java/io/realm/RealmObjectSchemaTests.java | 2 +- .../java/io/realm/RealmQueryTests.java | 22 +- .../java/io/realm/RealmResultsTests.java | 4 +- .../androidTest/java/io/realm/RealmTests.java | 10 +- .../androidTest/java/io/realm/TestHelper.java | 3 +- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- ...a.cpp => io_realm_OsRealmObjectSchema.cpp} | 14 +- .../src/main/cpp/io_realm_internal_Table.cpp | 4 +- .../src/main/java/io/realm/BaseRealm.java | 4 +- .../java/io/realm/OsRealmObjectSchema.java | 202 +++++ .../src/main/java/io/realm/OsRealmSchema.java | 42 +- .../src/main/java/io/realm/Property.java | 7 +- .../src/main/java/io/realm/Realm.java | 44 +- .../main/java/io/realm/RealmObjectSchema.java | 658 +------------- .../src/main/java/io/realm/RealmQuery.java | 84 +- .../src/main/java/io/realm/RealmSchema.java | 19 +- .../io/realm/StandardRealmObjectSchema.java | 827 ++++++++++++++++++ .../java/io/realm/StandardRealmSchema.java | 44 +- .../io/realm/internal/RealmProxyMediator.java | 8 - .../java/io/realm/internal/TableQuery.java | 158 ++-- .../main/java/io/realm/internal/TestUtil.java | 6 +- .../internal/modules/CompositeMediator.java | 9 +- .../internal/modules/FilterableMediator.java | 9 +- 38 files changed, 1428 insertions(+), 1117 deletions(-) rename realm/realm-library/src/main/cpp/{io_realm_RealmObjectSchema.cpp => io_realm_OsRealmObjectSchema.cpp} (81%) create mode 100644 realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java create mode 100644 realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 328426dc3a..0dc79ea020 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -115,7 +115,6 @@ public void generate() throws IOException, UnsupportedOperationException { emitPersistedFieldAccessors(writer); emitBacklinkFieldAccessors(writer); emitCreateRealmObjectSchemaMethod(writer); - emitInitTableMethod(writer); emitValidateTableMethod(writer); emitGetTableNameMethod(writer); emitGetFieldNamesMethod(writer); @@ -611,7 +610,7 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep String nullableFlag = (metadata.isNullable(field) ? "!" : "") + "Property.REQUIRED"; String indexedFlag = (metadata.isIndexed(field) ? "" : "!") + "Property.INDEXED"; String primaryKeyFlag = (metadata.isPrimaryKey(field) ? "" : "!") + "Property.PRIMARY_KEY"; - writer.emitStatement("realmObjectSchema.add(new Property(\"%s\", %s, %s, %s, %s))", + writer.emitStatement("realmObjectSchema.add(\"%s\", %s, %s, %s, %s)", fieldName, Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeCanonicalName), primaryKeyFlag, @@ -621,14 +620,14 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep writer.beginControlFlow("if (!realmSchema.contains(\"" + fieldTypeSimpleName + "\"))") .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", fieldTypeSimpleName, Constants.PROXY_SUFFIX) .endControlFlow() - .emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\")))", + .emitStatement("realmObjectSchema.add(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\"))", fieldName, fieldTypeSimpleName); } else if (Utils.isRealmList(field)) { String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); writer.beginControlFlow("if (!realmSchema.contains(\"" + genericTypeSimpleName + "\"))") .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", genericTypeSimpleName, Constants.PROXY_SUFFIX) .endControlFlow() - .emitStatement("realmObjectSchema.add(new Property(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\")))", + .emitStatement("realmObjectSchema.add(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\"))", fieldName, genericTypeSimpleName); } } @@ -639,69 +638,6 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep .emitEmptyLine(); } - private void emitInitTableMethod(JavaWriter writer) throws IOException { - writer.beginMethod( - "Table", // Return type - "initTable", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "SharedRealm", "sharedRealm"); // Argument type & argument name - - writer.beginControlFlow("if (!sharedRealm.hasTable(\"" + Constants.TABLE_PREFIX + this.simpleClassName + "\"))"); - writer.emitStatement("Table table = sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); - - // For each field generate corresponding table index constant - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); - String fieldTypeCanonicalName = field.asType().toString(); - String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); - - if (Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeCanonicalName)) { - String nullableFlag; - if (metadata.isNullable(field)) { - nullableFlag = "Table.NULLABLE"; - } else { - nullableFlag = "Table.NOT_NULLABLE"; - } - writer.emitStatement("table.addColumn(%s, \"%s\", %s)", - Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeCanonicalName), - fieldName, nullableFlag); - } else if (Utils.isRealmModel(field)) { - writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, fieldTypeSimpleName) - .emitStatement("%s%s.initTable(sharedRealm)", fieldTypeSimpleName, Constants.PROXY_SUFFIX) - .endControlFlow() - .emitStatement("table.addColumnLink(RealmFieldType.OBJECT, \"%s\", sharedRealm.getTable(\"%s%s\"))", - fieldName, Constants.TABLE_PREFIX, fieldTypeSimpleName); - } else if (Utils.isRealmList(field)) { - String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); - writer.beginControlFlow("if (!sharedRealm.hasTable(\"%s%s\"))", Constants.TABLE_PREFIX, genericTypeSimpleName) - .emitStatement("%s.initTable(sharedRealm)", Utils.getProxyClassName(genericTypeSimpleName)) - .endControlFlow() - .emitStatement("table.addColumnLink(RealmFieldType.LIST, \"%s\", sharedRealm.getTable(\"%s%s\"))", - fieldName, Constants.TABLE_PREFIX, genericTypeSimpleName); - } - } - - for (VariableElement field : metadata.getIndexedFields()) { - String fieldName = field.getSimpleName().toString(); - writer.emitStatement("table.addSearchIndex(table.getColumnIndex(\"%s\"))", fieldName); - } - - if (metadata.hasPrimaryKey()) { - String fieldName = metadata.getPrimaryKey().getSimpleName().toString(); - writer.emitStatement("table.setPrimaryKey(\"%s\")", fieldName); - } else { - writer.emitStatement("table.setPrimaryKey(\"\")"); - } - - writer.emitStatement("return table"); - - writer.endControlFlow(); - - writer.emitStatement("return sharedRealm.getTable(\"%s%s\")", Constants.TABLE_PREFIX, this.simpleClassName); - writer.endMethod() - .emitEmptyLine(); - } - private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.beginMethod( columnInfoClassName(), // Return type diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index f02aefc106..65f4e7a843 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -38,9 +38,9 @@ public class RealmProxyMediatorGenerator { private final String className; - private ProcessingEnvironment processingEnvironment; - private List qualifiedModelClasses = new ArrayList(); - private List qualifiedProxyClasses = new ArrayList(); + private final ProcessingEnvironment processingEnvironment; + private final List qualifiedModelClasses = new ArrayList(); + private final List qualifiedProxyClasses = new ArrayList(); public RealmProxyMediatorGenerator(ProcessingEnvironment processingEnvironment, String className, Set classesToValidate) { @@ -96,7 +96,6 @@ public void generate() throws IOException { writer.emitEmptyLine(); emitFields(writer); - emitCreateTableMethod(writer); emitCreateRealmObjectSchema(writer); emitValidateTableMethod(writer); emitGetFieldNamesMethod(writer); @@ -135,7 +134,7 @@ private void emitCreateRealmObjectSchema(JavaWriter writer) throws IOException { EnumSet.of(Modifier.PUBLIC), "Class", "clazz", "RealmSchema", "realmSchema" ); - emitMediatorSwitch(new ProxySwitchStatement() { + emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitStatement("return %s.createRealmObjectSchema(realmSchema)", qualifiedProxyClasses.get(i)); @@ -145,24 +144,6 @@ public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitEmptyLine(); } - private void emitCreateTableMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "Table", - "createTable", - EnumSet.of(Modifier.PUBLIC), - "Class", "clazz", "SharedRealm", "sharedRealm" - ); - emitMediatorSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return %s.initTable(sharedRealm)", qualifiedProxyClasses.get(i)); - } - }, writer); - writer.endMethod(); - writer.emitEmptyLine(); - } - private void emitValidateTableMethod(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); writer.beginMethod( @@ -173,7 +154,7 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { "SharedRealm", "sharedRealm", "boolean", "allowExtraColumns" ); - emitMediatorSwitch(new ProxySwitchStatement() { + emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitStatement("return %s.validateTable(sharedRealm, allowExtraColumns)", @@ -192,7 +173,7 @@ private void emitGetFieldNamesMethod(JavaWriter writer) throws IOException { EnumSet.of(Modifier.PUBLIC), "Class", "clazz" ); - emitMediatorSwitch(new ProxySwitchStatement() { + emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitStatement("return %s.getFieldNames()", qualifiedProxyClasses.get(i)); @@ -210,7 +191,7 @@ private void emitGetTableNameMethod(JavaWriter writer) throws IOException { EnumSet.of(Modifier.PUBLIC), "Class", "clazz" ); - emitMediatorSwitch(new ProxySwitchStatement() { + emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitStatement("return %s.getTableName()", qualifiedProxyClasses.get(i)); @@ -236,7 +217,7 @@ private void emitNewInstanceMethod(JavaWriter writer) throws IOException { writer.emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()"); writer.beginControlFlow("try") .emitStatement("objectContext.set((BaseRealm) baseRealm, row, columnInfo, acceptDefaultValue, excludeFields)"); - emitMediatorSwitch(new ProxySwitchStatement() { + emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitStatement("return clazz.cast(new %s())", qualifiedProxyClasses.get(i)); @@ -269,7 +250,7 @@ private void emitCopyToRealmMethod(JavaWriter writer) throws IOException { writer.emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject"); writer.emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass())"); writer.emitEmptyLine(); - emitMediatorSwitch(new ProxySwitchStatement() { + emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitStatement("return clazz.cast(%s.copyOrUpdate(realm, (%s) obj, update, cache))", qualifiedProxyClasses.get(i), qualifiedModelClasses.get(i)); @@ -412,7 +393,7 @@ private void emitCreteOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcept Arrays.asList("Class", "clazz", "Realm", "realm", "JSONObject", "json", "boolean", "update"), Arrays.asList("JSONException") ); - emitMediatorSwitch(new ProxySwitchStatement() { + emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitStatement("return clazz.cast(%s.createOrUpdateUsingJsonObject(realm, json, update))", qualifiedProxyClasses.get(i)); @@ -431,7 +412,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { Arrays.asList("Class", "clazz", "Realm", "realm", "JsonReader", "reader"), Arrays.asList("java.io.IOException") ); - emitMediatorSwitch(new ProxySwitchStatement() { + emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitStatement("return clazz.cast(%s.createUsingJsonStream(realm, reader))", qualifiedProxyClasses.get(i)); @@ -453,7 +434,7 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException writer.emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject"); writer.emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) realmObject.getClass().getSuperclass()"); writer.emitEmptyLine(); - emitMediatorSwitch(new ProxySwitchStatement() { + emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitStatement("return clazz.cast(%s.createDetachedCopy((%s) realmObject, 0, maxDepth, cache))", @@ -492,6 +473,26 @@ private void emitMediatorSwitch(ProxySwitchStatement statement, JavaWriter write } } + // Identical to the above, but eliminates the un-needed "else" clauses for, e.g., return statements + private void emitMediatorShortCircuitSwitch(ProxySwitchStatement statement, JavaWriter writer) throws IOException { + emitMediatorShortCircuitSwitch(statement, writer, true); + } + + private void emitMediatorShortCircuitSwitch(ProxySwitchStatement statement, JavaWriter writer, boolean nullPointerCheck) + throws IOException { + if (nullPointerCheck) { + writer.emitStatement("checkClass(clazz)"); + writer.emitEmptyLine(); + } + for (int i = 0; i < qualifiedModelClasses.size(); i++) { + writer.beginControlFlow("if (clazz.equals(%s.class))", qualifiedModelClasses.get(i)); + statement.emitStatement(i, writer); + writer.endControlFlow(); + } + writer.emitStatement("throw getMissingProxyClassException(clazz)"); + } + + private String getProxyClassName(String clazz) { return clazz + Constants.PROXY_SUFFIX; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index f8cb971a58..aaadd945d1 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -406,51 +406,26 @@ public final AllTypesColumnInfo clone() { public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { if (!realmSchema.contains("AllTypes")) { RealmObjectSchema realmObjectSchema = realmSchema.create("AllTypes"); - realmObjectSchema.add(new Property("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("columnDouble", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("columnBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnDouble", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); if (!realmSchema.contains("AllTypes")) { AllTypesRealmProxy.createRealmObjectSchema(realmSchema); } - realmObjectSchema.add(new Property("columnObject", RealmFieldType.OBJECT, realmSchema.get("AllTypes"))); + realmObjectSchema.add("columnObject", RealmFieldType.OBJECT, realmSchema.get("AllTypes")); if (!realmSchema.contains("AllTypes")) { AllTypesRealmProxy.createRealmObjectSchema(realmSchema); } - realmObjectSchema.add(new Property("columnRealmList", RealmFieldType.LIST, realmSchema.get("AllTypes"))); + realmObjectSchema.add("columnRealmList", RealmFieldType.LIST, realmSchema.get("AllTypes")); return realmObjectSchema; } return realmSchema.get("AllTypes"); } - public static Table initTable(SharedRealm sharedRealm) { - if (!sharedRealm.hasTable("class_AllTypes")) { - Table table = sharedRealm.getTable("class_AllTypes"); - table.addColumn(RealmFieldType.STRING, "columnString", Table.NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "columnLong", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.FLOAT, "columnFloat", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.DOUBLE, "columnDouble", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.BOOLEAN, "columnBoolean", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.DATE, "columnDate", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.BINARY, "columnBinary", Table.NOT_NULLABLE); - if (!sharedRealm.hasTable("class_AllTypes")) { - AllTypesRealmProxy.initTable(sharedRealm); - } - table.addColumnLink(RealmFieldType.OBJECT, "columnObject", sharedRealm.getTable("class_AllTypes")); - if (!sharedRealm.hasTable("class_AllTypes")) { - AllTypesRealmProxy.initTable(sharedRealm); - } - table.addColumnLink(RealmFieldType.LIST, "columnRealmList", sharedRealm.getTable("class_AllTypes")); - table.addSearchIndex(table.getColumnIndex("columnString")); - table.setPrimaryKey("columnString"); - return table; - } - return sharedRealm.getTable("class_AllTypes"); - } - public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { if (!sharedRealm.hasTable("class_AllTypes")) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'AllTypes' class is missing from the schema for this Realm."); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index c630f1f176..5a07ed0d92 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -191,28 +191,15 @@ public final BooleansColumnInfo clone() { public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { if (!realmSchema.contains("Booleans")) { RealmObjectSchema realmObjectSchema = realmSchema.create("Booleans"); - realmObjectSchema.add(new Property("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("anotherBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("anotherBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); return realmObjectSchema; } return realmSchema.get("Booleans"); } - public static Table initTable(SharedRealm sharedRealm) { - if (!sharedRealm.hasTable("class_Booleans")) { - Table table = sharedRealm.getTable("class_Booleans"); - table.addColumn(RealmFieldType.BOOLEAN, "done", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.BOOLEAN, "isReady", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.BOOLEAN, "mCompleted", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.BOOLEAN, "anotherBoolean", Table.NOT_NULLABLE); - table.setPrimaryKey(""); - return table; - } - return sharedRealm.getTable("class_Booleans"); - } - public static BooleansColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { if (!sharedRealm.hasTable("class_Booleans")) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'Booleans' class is missing from the schema for this Realm."); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index fa041cfb80..c91b122f0c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -843,68 +843,35 @@ public final NullTypesColumnInfo clone() { public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { if (!realmSchema.contains("NullTypes")) { RealmObjectSchema realmObjectSchema = realmSchema.create("NullTypes"); - realmObjectSchema.add(new Property("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldBooleanNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldBytesNotNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldBytesNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldByteNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldByteNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldShortNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldShortNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldIntegerNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldIntegerNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldLongNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldLongNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldFloatNotNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldFloatNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldDoubleNotNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldDoubleNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); - realmObjectSchema.add(new Property("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); + realmObjectSchema.add("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldBooleanNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldBytesNotNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldBytesNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldByteNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldByteNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldShortNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldShortNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldIntegerNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldIntegerNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldLongNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldLongNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldFloatNotNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldFloatNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldDoubleNotNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldDoubleNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); if (!realmSchema.contains("NullTypes")) { NullTypesRealmProxy.createRealmObjectSchema(realmSchema); } - realmObjectSchema.add(new Property("fieldObjectNull", RealmFieldType.OBJECT, realmSchema.get("NullTypes"))); + realmObjectSchema.add("fieldObjectNull", RealmFieldType.OBJECT, realmSchema.get("NullTypes")); return realmObjectSchema; } return realmSchema.get("NullTypes"); } - public static Table initTable(SharedRealm sharedRealm) { - if (!sharedRealm.hasTable("class_NullTypes")) { - Table table = sharedRealm.getTable("class_NullTypes"); - table.addColumn(RealmFieldType.STRING, "fieldStringNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.STRING, "fieldStringNull", Table.NULLABLE); - table.addColumn(RealmFieldType.BOOLEAN, "fieldBooleanNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.BOOLEAN, "fieldBooleanNull", Table.NULLABLE); - table.addColumn(RealmFieldType.BINARY, "fieldBytesNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.BINARY, "fieldBytesNull", Table.NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "fieldByteNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "fieldByteNull", Table.NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "fieldShortNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "fieldShortNull", Table.NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "fieldIntegerNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "fieldIntegerNull", Table.NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "fieldLongNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "fieldLongNull", Table.NULLABLE); - table.addColumn(RealmFieldType.FLOAT, "fieldFloatNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.FLOAT, "fieldFloatNull", Table.NULLABLE); - table.addColumn(RealmFieldType.DOUBLE, "fieldDoubleNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.DOUBLE, "fieldDoubleNull", Table.NULLABLE); - table.addColumn(RealmFieldType.DATE, "fieldDateNotNull", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.DATE, "fieldDateNull", Table.NULLABLE); - if (!sharedRealm.hasTable("class_NullTypes")) { - NullTypesRealmProxy.initTable(sharedRealm); - } - table.addColumnLink(RealmFieldType.OBJECT, "fieldObjectNull", sharedRealm.getTable("class_NullTypes")); - table.setPrimaryKey(""); - return table; - } - return sharedRealm.getTable("class_NullTypes"); - } - public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { if (!sharedRealm.hasTable("class_NullTypes")) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'NullTypes' class is missing from the schema for this Realm."); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index e72e461844..4460594efa 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -31,26 +31,14 @@ class DefaultRealmModuleMediator extends RealmProxyMediator { MODEL_CLASSES = Collections.unmodifiableSet(modelClasses); } - @Override - public Table createTable(Class clazz, SharedRealm sharedRealm) { - checkClass(clazz); - - if (clazz.equals(some.test.AllTypes.class)) { - return io.realm.AllTypesRealmProxy.initTable(sharedRealm); - } else { - throw getMissingProxyClassException(clazz); - } - } - @Override public RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema realmSchema) { checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { return io.realm.AllTypesRealmProxy.createRealmObjectSchema(realmSchema); - } else { - throw getMissingProxyClassException(clazz); } + throw getMissingProxyClassException(clazz); } @Override @@ -59,9 +47,8 @@ public ColumnInfo validateTable(Class clazz, SharedRealm s if (clazz.equals(some.test.AllTypes.class)) { return io.realm.AllTypesRealmProxy.validateTable(sharedRealm, allowExtraColumns); - } else { - throw getMissingProxyClassException(clazz); } + throw getMissingProxyClassException(clazz); } @Override @@ -70,9 +57,8 @@ public List getFieldNames(Class clazz) { if (clazz.equals(some.test.AllTypes.class)) { return io.realm.AllTypesRealmProxy.getFieldNames(); - } else { - throw getMissingProxyClassException(clazz); } + throw getMissingProxyClassException(clazz); } @Override @@ -81,9 +67,8 @@ public String getTableName(Class clazz) { if (clazz.equals(some.test.AllTypes.class)) { return io.realm.AllTypesRealmProxy.getTableName(); - } else { - throw getMissingProxyClassException(clazz); } + throw getMissingProxyClassException(clazz); } @Override @@ -95,9 +80,8 @@ public E newInstance(Class clazz, Object baseRealm, Ro if (clazz.equals(some.test.AllTypes.class)) { return clazz.cast(new io.realm.AllTypesRealmProxy()); - } else { - throw getMissingProxyClassException(clazz); } + throw getMissingProxyClassException(clazz); } finally { objectContext.clear(); } @@ -116,9 +100,8 @@ public E copyOrUpdate(Realm realm, E obj, boolean update, if (clazz.equals(some.test.AllTypes.class)) { return clazz.cast(io.realm.AllTypesRealmProxy.copyOrUpdate(realm, (some.test.AllTypes) obj, update, cache)); - } else { - throw getMissingProxyClassException(clazz); } + throw getMissingProxyClassException(clazz); } @Override @@ -208,9 +191,8 @@ public E createOrUpdateUsingJsonObject(Class clazz, Re if (clazz.equals(some.test.AllTypes.class)) { return clazz.cast(io.realm.AllTypesRealmProxy.createOrUpdateUsingJsonObject(realm, json, update)); - } else { - throw getMissingProxyClassException(clazz); } + throw getMissingProxyClassException(clazz); } @Override @@ -220,9 +202,8 @@ public E createUsingJsonStream(Class clazz, Realm real if (clazz.equals(some.test.AllTypes.class)) { return clazz.cast(io.realm.AllTypesRealmProxy.createUsingJsonStream(realm, reader)); - } else { - throw getMissingProxyClassException(clazz); } + throw getMissingProxyClassException(clazz); } @Override @@ -233,9 +214,8 @@ public E createDetachedCopy(E realmObject, int maxDepth, if (clazz.equals(some.test.AllTypes.class)) { return clazz.cast(io.realm.AllTypesRealmProxy.createDetachedCopy((some.test.AllTypes) realmObject, 0, maxDepth, cache)); - } else { - throw getMissingProxyClassException(clazz); } + throw getMissingProxyClassException(clazz); } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 8336e88e4b..030081db38 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -145,24 +145,13 @@ public final SimpleColumnInfo clone() { public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { if (!realmSchema.contains("Simple")) { RealmObjectSchema realmObjectSchema = realmSchema.create("Simple"); - realmObjectSchema.add(new Property("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)); - realmObjectSchema.add(new Property("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED)); + realmObjectSchema.add("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); return realmObjectSchema; } return realmSchema.get("Simple"); } - public static Table initTable(SharedRealm sharedRealm) { - if (!sharedRealm.hasTable("class_Simple")) { - Table table = sharedRealm.getTable("class_Simple"); - table.addColumn(RealmFieldType.STRING, "name", Table.NULLABLE); - table.addColumn(RealmFieldType.INTEGER, "age", Table.NOT_NULLABLE); - table.setPrimaryKey(""); - return table; - } - return sharedRealm.getTable("class_Simple"); - } - public static SimpleColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { if (!sharedRealm.hasTable("class_Simple")) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "The 'Simple' class is missing from the schema for this Realm."); diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java index ba33c31d0c..f79aacad6f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java @@ -142,7 +142,7 @@ public void insert_realmModel() { allTypes.columnBoolean = false; allTypes.columnBinary = new byte[]{1, 2, 3}; allTypes.columnDate = new Date(); - allTypes.columnDouble = 3.1415; + allTypes.columnDouble = Math.PI; allTypes.columnFloat = 1.234567f; allTypes.columnString = "test data"; allTypes.columnByte = 0x2A; diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index 3f99fdf619..9a38ec50b1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -123,7 +123,7 @@ private void fillObject(int index, int totalObjects, AllJavaTypes obj) { obj.setFieldBoolean(((index % 2) == 0)); obj.setFieldBinary(new byte[]{1, 2, 3}); obj.setFieldDate(new Date(YEAR_MILLIS * 20 * (index - totalObjects / 2))); - obj.setFieldDouble(3.1415 + index); + obj.setFieldDouble(Math.PI + index); obj.setFieldFloat(1.234567f + index); obj.setFieldString("test data " + index); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index b5b1202400..1bec5cfa80 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -93,7 +93,7 @@ private void populateTestRealm(DynamicRealm realm, int objects) { allTypes.setBoolean(AllTypes.FIELD_BOOLEAN, (i % 3) == 0); allTypes.setBlob(AllTypes.FIELD_BINARY, new byte[]{1, 2, 3}); allTypes.setDate(AllTypes.FIELD_DATE, new Date()); - allTypes.setDouble(AllTypes.FIELD_DOUBLE, 3.1415D + i); + allTypes.setDouble(AllTypes.FIELD_DOUBLE, Math.PI + i); allTypes.setFloat(AllTypes.FIELD_FLOAT, 1.234567F + i); allTypes.setString(AllTypes.FIELD_STRING, "test data " + i); allTypes.setLong(AllTypes.FIELD_LONG, i); @@ -675,7 +675,7 @@ public void equalTo_noFieldObjectShouldThrow() { dynamicRealm.commitTransaction(); thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Field 'nonExisting' does not exist."); + thrown.expectMessage("Invalid query: field 'nonExisting' does not exist in table 'NoField'."); dynamicRealm.where(className).equalTo("nonExisting", 1); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index 1ea36519e9..0ec3cd03fe 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -96,7 +96,7 @@ public void iOSDataTypes() throws IOException { assertEquals(1.234D + (double)i, obj.getDoubleCol(), 0D); assertArrayEquals(new byte[]{1, 2, 3}, obj.getByteCol()); assertEquals("String " + Integer.toString(i), obj.getStringCol()); - assertEquals(new Date((1000 + i) * 1000), obj.getDateCol()); + assertEquals(new Date((1000L + i) * 1000), obj.getDateCol()); assertEquals("Foo", result.get(i).getChild().getName()); assertEquals(10, result.get(i).getChildren().size()); for (int j = 0; j < 10; j++) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 9cd511e157..387920dc58 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -499,11 +499,11 @@ public void avg() { // See setUp() for values of fields. // N = TEST_DATA_SIZE - // Type: double; a = 3.1415 + // Type: double; a = Math.PI // a, a+1, ..., a+i, ..., a+N-1 - // sum = 3.1415*N + N*(N-1)/2 - // average = sum/N = 3.1415+(N-1)/2 - double average = 3.1415 + (N - 1.0) * 0.5; + // sum = Math.PI*N + N*(N-1)/2 + // average = sum/N = Math.PI+(N-1)/2 + double average = Math.PI + (N - 1.0) * 0.5; assertEquals(average, collection.average(AllJavaTypes.FIELD_DOUBLE), 0.0001); // Type: long @@ -575,7 +575,7 @@ public void aggregates_deleteLastRow() { assertEquals(sizeAfterRemove - 1, collection.max(AllJavaTypes.FIELD_LONG).intValue()); // Sum of numbers 0 to M-1: (M-1)*M/2 assertEquals((sizeAfterRemove - 1) * sizeAfterRemove / 2, collection.sum(AllJavaTypes.FIELD_LONG).intValue()); - double average = 3.1415 + (sizeAfterRemove - 1.0) * 0.5; + double average = Math.PI + (sizeAfterRemove - 1.0) * 0.5; assertEquals(average, collection.average(AllJavaTypes.FIELD_DOUBLE), 0.0001); assertEquals(new Date(YEAR_MILLIS * 20 * (sizeAfterRemove / 2 - 1)), collection.maxDate(AllJavaTypes.FIELD_DATE)); assertEquals(new Date(-YEAR_MILLIS * 20 * TEST_SIZE / 2), collection.minDate(AllJavaTypes.FIELD_DATE)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 44e32c935d..b49618fab1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -1264,7 +1264,7 @@ private void populateTestRealm(final Realm testRealm, int objects) { allTypes.setColumnBoolean((i % 3) == 0); allTypes.setColumnBinary(new byte[]{1, 2, 3}); allTypes.setColumnDate(new Date()); - allTypes.setColumnDouble(3.1415); + allTypes.setColumnDouble(Math.PI); allTypes.setColumnFloat(1.234567f + i); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); @@ -1285,11 +1285,11 @@ private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOf AnnotationIndexTypes obj = realm.createObject(AnnotationIndexTypes.class); obj.setIndexBoolean(j % 2 == 0); obj.setIndexLong(j); - obj.setIndexDate(withNull ? null : new Date(1000 * j)); + obj.setIndexDate(withNull ? null : new Date(1000L * j)); obj.setIndexString(withNull ? null : "Test " + j); obj.setNotIndexBoolean(j % 2 == 0); obj.setNotIndexLong(j); - obj.setNotIndexDate(withNull ? null : new Date(1000 * j)); + obj.setNotIndexDate(withNull ? null : new Date(1000L * j)); obj.setNotIndexString(withNull ? null : "Test " + j); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java index 2af194d3ef..2e802eb7f2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java @@ -484,22 +484,22 @@ public void queryMultipleRelationsString() { public void queryShouldFail() { try { RealmResults owners = testRealm.where(Owner.class).equalTo("cat..hasTail", true).findAll(); - fail("Should throw Exception"); + fail("Should throw Exception (double dot)"); } catch (IllegalArgumentException ignored) { } try { RealmResults owners = testRealm.where(Owner.class).equalTo(".cat.hasTail", true).findAll(); - fail("Should throw Exception"); + fail("Should throw Exception (initial dot)"); } catch (IllegalArgumentException ignored) { } try { RealmResults owners = testRealm.where(Owner.class).equalTo("cat.hasTail.", true).findAll(); - fail("Should throw Exception"); + fail("Should throw Exception (final dot)"); } catch (IllegalArgumentException ignored) { } try { RealmResults owners = testRealm.where(Owner.class).equalTo("not.there", true).findAll(); - fail("Should throw Exception"); + fail("Should throw Exception (non-existent column)"); } catch (IllegalArgumentException ignored) { } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index 387f098153..6d20ffcec0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -91,7 +91,7 @@ private void populateTestRealm(Realm realm, int objects) { allTypes.columnBoolean = (i % 3) == 0; allTypes.columnBinary = new byte[]{1, 2, 3}; allTypes.columnDate = new Date(); - allTypes.columnDouble = 3.1415 + i; + allTypes.columnDouble = Math.PI + i; allTypes.columnFloat = 1.234567f; allTypes.columnString = "test data "; allTypes.columnByte = 0x2A; @@ -251,7 +251,7 @@ public void dynamicRealm() { RealmResults results = dynamicRealm.where(AllTypesRealmModel.CLASS_NAME).findAll(); assertEquals(TEST_DATA_SIZE, results.size()); for (int i = 0; i < TEST_DATA_SIZE; i++) { - assertEquals(3.1415 + i, results.get(i).getDouble(AllTypesRealmModel.FIELD_DOUBLE), 0.0000001); + assertEquals(Math.PI + i, results.get(i).getDouble(AllTypesRealmModel.FIELD_DOUBLE), 0.0000001); assertEquals((i % 3) == 0, results.get(i).getBoolean(AllTypesRealmModel.FIELD_BOOLEAN)); } dynamicRealm.close(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 2f5a772106..e69e20613f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -738,7 +738,7 @@ public void getFieldIndex() { RealmConfiguration emptyConfig = configFactory.createConfiguration("empty"); DynamicRealm dynamicRealm = DynamicRealm.getInstance(emptyConfig); dynamicRealm.beginTransaction(); - RealmObjectSchema objectSchema = dynamicRealm.getSchema().create(className); + StandardRealmObjectSchema objectSchema = (StandardRealmObjectSchema) dynamicRealm.getSchema().create(className); assertNull(objectSchema.getFieldIndex(fieldName)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 53642936a0..3017d94dfc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -98,7 +98,7 @@ private void populateTestRealm(Realm testRealm, int dataSize) { allTypes.setColumnBoolean((i % 3) == 0); allTypes.setColumnBinary(new byte[]{1, 2, 3}); allTypes.setColumnDate(new Date(DECADE_MILLIS * (i - (dataSize / 2)))); - allTypes.setColumnDouble(3.1415); + allTypes.setColumnDouble(Math.PI); allTypes.setColumnFloat(1.2345f + i); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); @@ -139,8 +139,8 @@ private void populateNoPrimaryKeyNullTypesRows(Realm testRealm, int dataSize) { noPrimaryKeyNullTypes.setFieldLongNotNull((long) i); noPrimaryKeyNullTypes.setFieldFloatNull((i % 3) == 0 ? null : 1.2345f + i); noPrimaryKeyNullTypes.setFieldFloatNotNull(1.2345f + i); - noPrimaryKeyNullTypes.setFieldDoubleNull((i % 3) == 0 ? null : 3.1415 + i); - noPrimaryKeyNullTypes.setFieldDoubleNotNull(3.1415 + i); + noPrimaryKeyNullTypes.setFieldDoubleNull((i % 3) == 0 ? null : Math.PI + i); + noPrimaryKeyNullTypes.setFieldDoubleNotNull(Math.PI + i); noPrimaryKeyNullTypes.setFieldDateNull((i % 3) == 0 ? null : new Date(DECADE_MILLIS * (i - (dataSize / 2)))); noPrimaryKeyNullTypes.setFieldDateNotNull(new Date(DECADE_MILLIS * (i - (dataSize / 2)))); } @@ -821,13 +821,13 @@ private void doTestForInDouble(String targetField) { fail(); } catch (IllegalArgumentException ignored) { } - RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{3.1415d + 1}).findAll(); + RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{Math.PI + 1}).findAll(); assertEquals(1, resultList.size()); - resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{3.1415d + 2}).findAll(); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{Math.PI + 2}).findAll(); assertEquals(1, resultList.size()); - resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{3.1415d + 1, 3.1415d + 2}).findAll(); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{Math.PI + 1, Math.PI + 2}).findAll(); assertEquals(2, resultList.size()); - resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Double[]{3.1415d + 1, 3.1415d + 2}).findAll(); + resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Double[]{Math.PI + 1, Math.PI + 2}).findAll(); assertEquals(198, resultList.size()); } @@ -996,7 +996,7 @@ public void in_dateNull() { public void in_doubleNotNull() { doTestForInDouble(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NOT_NULL); try { - realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NOT_NULL, new Double[]{3.1415d + 1, null, 3.1415d + 2}).findAll(); + realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NOT_NULL, new Double[]{Math.PI + 1, null, Math.PI + 2}).findAll(); fail(); } catch (IllegalArgumentException ignored) { } @@ -1005,7 +1005,7 @@ public void in_doubleNotNull() { @Test public void in_doubleNull() { doTestForInDouble(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NULL); - RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NULL, new Double[]{3.1415d + 1, null, 3.1415d + 2}).findAll(); + RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NULL, new Double[]{Math.PI + 1, null, Math.PI + 2}).findAll(); assertEquals(131, resultList.size()); } @@ -3060,11 +3060,11 @@ private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOf AnnotationIndexTypes obj = realm.createObject(AnnotationIndexTypes.class); obj.setIndexBoolean(j % 2 == 0); obj.setIndexLong(j); - obj.setIndexDate(withNull ? null : new Date(1000 * j)); + obj.setIndexDate(withNull ? null : new Date(1000L * j)); obj.setIndexString(withNull ? null : "Test " + j); obj.setNotIndexBoolean(j % 2 == 0); obj.setNotIndexLong(j); - obj.setNotIndexDate(withNull ? null : new Date(1000 * j)); + obj.setNotIndexDate(withNull ? null : new Date(1000L * j)); obj.setNotIndexString(withNull ? null : "Test " + j); obj.setFieldObject(obj); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 88dd2d6bf5..4c4069a9e8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -380,7 +380,7 @@ private void populateTestRealm(int objects) { allTypes.setColumnBoolean((i % 2) == 0); allTypes.setColumnBinary(new byte[]{1, 2, 3}); allTypes.setColumnDate(new Date(YEAR_MILLIS * (i - objects / 2))); - allTypes.setColumnDouble(3.1415 + i); + allTypes.setColumnDouble(Math.PI + i); allTypes.setColumnFloat(1.234567f + i); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); @@ -404,7 +404,7 @@ private void populateTestRealm(Realm testRealm, int objects) { allTypes.setColumnBoolean((i % 3) == 0); allTypes.setColumnBinary(new byte[]{1, 2, 3}); allTypes.setColumnDate(new Date(DECADE_MILLIS * (i - (objects / 2)))); - allTypes.setColumnDouble(3.1415); + allTypes.setColumnDouble(Math.PI); allTypes.setColumnFloat(1.234567f + i); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 243ef1c246..aaf75da32c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -168,7 +168,7 @@ private void populateTestRealm(Realm realm, int objects) { allTypes.setColumnBoolean((i % 3) == 0); allTypes.setColumnBinary(new byte[]{1, 2, 3}); allTypes.setColumnDate(new Date()); - allTypes.setColumnDouble(3.1415); + allTypes.setColumnDouble(Math.PI); allTypes.setColumnFloat(1.234567f + i); allTypes.setColumnString("test data " + i); @@ -370,13 +370,13 @@ public void where_equalTo_invalidFieldName() throws IOException { } try { - realm.where(AllTypes.class).equalTo("invalidcolumnname", 3.1415d).findAll(); + realm.where(AllTypes.class).equalTo("invalidcolumnname", Math.PI).findAll(); fail("Invalid field name"); } catch (Exception ignored) { } try { - realm.where(AllTypes.class).equalTo("invalidcolumnname", 3.1415f).findAll(); + realm.where(AllTypes.class).equalTo("invalidcolumnname", Math.PI).findAll(); fail("Invalid field name"); } catch (Exception ignored) { } @@ -455,7 +455,7 @@ public void beginTransaction() throws IOException { realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); - allTypes.setColumnFloat(3.1415f); + allTypes.setColumnFloat(3.14F); allTypes.setColumnString("a unique string"); realm.commitTransaction(); @@ -464,7 +464,7 @@ public void beginTransaction() throws IOException { resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "a unique string").findAll(); assertEquals(1, resultList.size()); - resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 3.1415f).findAll(); + resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 3.14F).findAll(); assertEquals(1, resultList.size()); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 064af91f02..731c21d3a1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -69,6 +69,7 @@ public class TestHelper { private static final Charset UTF_8 = Charset.forName("UTF-8"); + private static final Random RANDOM = new Random(); public static class ExpectedCountCallback implements RealmCache.Callback { @@ -159,7 +160,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { // Returns a random key used by encrypted Realms. public static byte[] getRandomKey() { byte[] key = new byte[64]; - new Random().nextBytes(key); + RANDOM.nextBytes(key); return key; } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index cb5f0a97d6..5913e1872e 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -37,7 +37,7 @@ set(classes_LIST io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.OsRealmSchema - io.realm.RealmObjectSchema io.realm.internal.Collection + io.realm.OsRealmObjectSchema io.realm.internal.Collection io.realm.internal.NativeObjectReference io.realm.internal.CollectionChangeSet io.realm.internal.OsObject ) diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp similarity index 81% rename from realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp rename to realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp index e6804a0ad5..bbbcbe2879 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmObjectSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp @@ -15,7 +15,7 @@ */ #include -#include "io_realm_RealmObjectSchema.h" +#include "io_realm_OsRealmObjectSchema.h" #include #include @@ -23,8 +23,8 @@ #include "util.hpp" using namespace realm; -JNIEXPORT jlong JNICALL Java_io_realm_RealmObjectSchema_nativeCreateRealmObjectSchema(JNIEnv* env, jclass, - jstring className_) +JNIEXPORT jlong JNICALL Java_io_realm_OsRealmObjectSchema_nativeCreateRealmObjectSchema(JNIEnv* env, jclass, + jstring className_) { TR_ENTER() try { @@ -37,7 +37,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmObjectSchema_nativeCreateRealmObjectS return 0; } -JNIEXPORT void JNICALL Java_io_realm_RealmObjectSchema_nativeClose(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT void JNICALL Java_io_realm_OsRealmObjectSchema_nativeClose(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -48,7 +48,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmObjectSchema_nativeClose(JNIEnv* env, } -JNIEXPORT void JNICALL Java_io_realm_RealmObjectSchema_nativeAddProperty(JNIEnv* env, jclass, jlong native_ptr, +JNIEXPORT void JNICALL Java_io_realm_OsRealmObjectSchema_nativeAddProperty(JNIEnv* env, jclass, jlong native_ptr, jlong property_ptr) { TR_ENTER_PTR(native_ptr) @@ -63,7 +63,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmObjectSchema_nativeAddProperty(JNIEnv* CATCH_STD() } -JNIEXPORT jstring JNICALL Java_io_realm_RealmObjectSchema_nativeGetClassName(JNIEnv* env, jclass, jlong nativePtr) +JNIEXPORT jstring JNICALL Java_io_realm_OsRealmObjectSchema_nativeGetClassName(JNIEnv* env, jclass, jlong nativePtr) { TR_ENTER_PTR(nativePtr) try { @@ -76,7 +76,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmObjectSchema_nativeGetClassName(JNI return nullptr; } -JNIEXPORT jlongArray JNICALL Java_io_realm_RealmObjectSchema_nativeGetProperties(JNIEnv* env, jclass, jlong nativePtr) +JNIEXPORT jlongArray JNICALL Java_io_realm_OsRealmObjectSchema_nativeGetProperties(JNIEnv* env, jclass, jlong nativePtr) { TR_ENTER_PTR(nativePtr) try { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index eca86a2800..42bd1567e8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1533,7 +1533,9 @@ static bool check_valid_primary_key_column(JNIEnv* env, Table* table, StringData return true; default: - ThrowException(env, IllegalArgument, "Invalid primary key type: " + column_type); + std::ostringstream error_msg; + error_msg << "Invalid primary key type for column: " << column_name; + ThrowException(env, IllegalArgument, error_msg.str()); return false; } } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index acae142705..c2330bf87d 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -62,7 +62,7 @@ abstract class BaseRealm implements Closeable { static final String LISTENER_NOT_ALLOWED_MESSAGE = "Listeners cannot be used on current thread."; - volatile static Context applicationContext; + static volatile Context applicationContext; // Thread pool for all async operations (Query & transaction) static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); @@ -642,7 +642,7 @@ protected void finalize() throws Throwable { super.finalize(); } - public SharedRealm getSharedRealm() { + SharedRealm getSharedRealm() { return sharedRealm; } diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java new file mode 100644 index 0000000000..922e28aba6 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java @@ -0,0 +1,202 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm; + +import java.util.LinkedHashSet; +import java.util.Set; + +class OsRealmObjectSchema extends RealmObjectSchema { + private final long nativePtr; + + /** + * Creates a schema object using object store. This constructor is intended to be used by + * the validation of schema, object schemas and properties through the object store. Even though the constructor + * is public, there is never a purpose which justifies calling it! + * + * @param className name of the class + */ + OsRealmObjectSchema(String className) { + this.nativePtr = nativeCreateRealmObjectSchema(className); + } + + OsRealmObjectSchema(long nativePtr) { + this.nativePtr = nativePtr; + } + + @Override + public void close() { + Set properties = getProperties(); + for (Property property : properties) { + property.close(); + } + nativeClose(nativePtr); + } + + @Override + public String getClassName() { + return nativeGetClassName(nativePtr); + } + + @Override + public OsRealmObjectSchema setClassName(String className) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema removeField(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema renameField(String currentFieldName, String newFieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasField(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema addIndex(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasIndex(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema removeIndex(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema addPrimaryKey(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema removePrimaryKey() { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema setRequired(String fieldName, boolean required) { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema setNullable(String fieldName, boolean nullable) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isRequired(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNullable(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isPrimaryKey(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasPrimaryKey() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPrimaryKey() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getFieldNames() { + throw new UnsupportedOperationException(); + } + + @Override + public OsRealmObjectSchema transform(Function function) { + throw new UnsupportedOperationException(); + } + + @Override + public RealmFieldType getFieldType(String fieldName) { + throw new UnsupportedOperationException(); + } + + @Override + long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes) { + throw new UnsupportedOperationException(); + } + + @Override + OsRealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { + nativeAddProperty(nativePtr, new Property(name, type, primary, indexed, required).getNativePtr()); + return this; + } + + @Override + OsRealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linkedTo) { + nativeAddProperty(nativePtr, new Property(name, type, linkedTo).getNativePtr()); + return this; + } + + long getNativePtr() { + return nativePtr; + } + + private Set getProperties() { + long[] ptrs = nativeGetProperties(nativePtr); + Set properties = new LinkedHashSet<>(ptrs.length); + for (int i = 0; i < ptrs.length; i++) { + properties.add(new Property(ptrs[i])); + } + return properties; + } + + static native long nativeCreateRealmObjectSchema(String className); + + static native void nativeAddProperty(long nativePtr, long nativePropertyPtr); + + static native long[] nativeGetProperties(long nativePtr); + + static native void nativeClose(long nativePtr); + + static native String nativeGetClassName(long nativePtr); +} diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java index 9b7e100d95..f173f4da32 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Realm Inc. + * Copyright 2017 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ */ class OsRealmSchema extends RealmSchema { static final class Creator extends RealmSchema { - private final Map schema = new HashMap<>(); + private final Map schema = new HashMap<>(); @Override public void close() { } @@ -44,14 +44,14 @@ public RealmObjectSchema get(String className) { } @Override - public Set getAll() { + public Set getAll() { return new LinkedHashSet<>(schema.values()); } @Override public RealmObjectSchema create(String className) { checkEmpty(className); - RealmObjectSchema realmObjectSchema = new RealmObjectSchema(className); + OsRealmObjectSchema realmObjectSchema = new OsRealmObjectSchema(className); schema.put(className, realmObjectSchema); return realmObjectSchema; } @@ -60,6 +60,16 @@ public RealmObjectSchema create(String className) { public boolean contains(String className) { return schema.containsKey(className); } + + @Override + public void remove(String className) { + throw new UnsupportedOperationException(); + } + + @Override + public RealmObjectSchema rename(String oldClassName, String newClassName) { + throw new UnsupportedOperationException(); + } } private final Map dynamicClassToSchema = new HashMap<>(); @@ -67,10 +77,10 @@ public boolean contains(String className) { private final long nativePtr; OsRealmSchema(Creator creator) { - Set realmObjectSchemas = creator.getAll(); + Set realmObjectSchemas = creator.getAll(); long[] schemaNativePointers = new long[realmObjectSchemas.size()]; int i = 0; - for (RealmObjectSchema schema : realmObjectSchemas) { + for (OsRealmObjectSchema schema : realmObjectSchemas) { schemaNativePointers[i++] = schema.getNativePtr(); } this.nativePtr = nativeCreateFromList(schemaNativePointers); @@ -84,7 +94,7 @@ public long getNativePtr() { // See BaseRealm uses a StandardRealmSchema, not a OsRealmSchema. @Override public void close() { - Set schemas = getAll(); + Set schemas = getAll(); for (RealmObjectSchema schema : schemas) { schema.close(); } @@ -109,11 +119,11 @@ public RealmObjectSchema get(String className) { * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ @Override - public Set getAll() { + public Set getAll() { long[] ptrs = nativeGetAll(nativePtr); - Set schemas = new LinkedHashSet<>(ptrs.length); + Set schemas = new LinkedHashSet<>(ptrs.length); for (int i = 0; i < ptrs.length; i++) { - schemas.add(new RealmObjectSchema(ptrs[i])); + schemas.add(new OsRealmObjectSchema(ptrs[i])); } return schemas; } @@ -128,11 +138,21 @@ public Set getAll() { public RealmObjectSchema create(String className) { // Adding a class is always permitted. checkEmpty(className); - RealmObjectSchema realmObjectSchema = new RealmObjectSchema(className); + OsRealmObjectSchema realmObjectSchema = new OsRealmObjectSchema(className); dynamicClassToSchema.put(className, realmObjectSchema); return realmObjectSchema; } + @Override + public void remove(String className) { + throw new UnsupportedOperationException(); + } + + @Override + public RealmObjectSchema rename(String oldClassName, String newClassName) { + throw new UnsupportedOperationException(); + } + /** * Checks if a given class already exists in the schema. * diff --git a/realm/realm-library/src/main/java/io/realm/Property.java b/realm/realm-library/src/main/java/io/realm/Property.java index 02557c6cd6..c96fbfe6c7 100644 --- a/realm/realm-library/src/main/java/io/realm/Property.java +++ b/realm/realm-library/src/main/java/io/realm/Property.java @@ -28,13 +28,12 @@ class Property { private final long nativePtr; - public Property(String name, RealmFieldType type, boolean isPrimary, boolean isIndexed, boolean isRequired) { + Property(String name, RealmFieldType type, boolean isPrimary, boolean isIndexed, boolean isRequired) { this.nativePtr = nativeCreateProperty(name, type.getNativeValue(), isPrimary, isIndexed, !isRequired); } - public Property(String name, RealmFieldType type, RealmObjectSchema linkedTo) { - String linkedToName = linkedTo.getClassName(); - this.nativePtr = nativeCreateProperty(name, type.getNativeValue(), linkedToName); + Property(String name, RealmFieldType type, RealmObjectSchema linkedTo) { + this.nativePtr = nativeCreateProperty(name, type.getNativeValue(), linkedTo.getClassName()); } protected Property(long nativePtr) { diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 857ec5f406..06302c0736 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -337,30 +337,34 @@ private static void initializeRealm(Realm realm) { boolean unversioned = currentVersion == UNVERSIONED; commitChanges = unversioned; + RealmConfiguration configuration = realm.getConfiguration(); + if (unversioned) { - realm.setVersion(realm.configuration.getSchemaVersion()); + realm.setVersion(configuration.getSchemaVersion()); } - final RealmProxyMediator mediator = realm.configuration.getSchemaMediator(); + + final RealmProxyMediator mediator = configuration.getSchemaMediator(); final Set> modelClasses = mediator.getModelClasses(); - final Map, ColumnInfo> columnInfoMap = new HashMap<>(modelClasses.size()); if (unversioned) { // Create all of the tables. for (Class modelClass : modelClasses) { - mediator.createTable(modelClass, realm.sharedRealm); + mediator.createRealmObjectSchema(modelClass, realm.getSchema()); } } + + final Map, ColumnInfo> columnInfoMap = new HashMap<>(modelClasses.size()); for (Class modelClass : modelClasses) { // Now that they have all been created, validate them. columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } - realm.schema.setColumnIndices( - (unversioned) ? realm.configuration.getSchemaVersion() : currentVersion, + realm.getSchema().setColumnIndices( + (unversioned) ? configuration.getSchemaVersion() : currentVersion, columnInfoMap); if (unversioned) { - final Transaction transaction = realm.configuration.getInitialDataTransaction(); + final Transaction transaction = configuration.getInitialDataTransaction(); if (transaction != null) { transaction.execute(realm); } @@ -377,16 +381,18 @@ private static void initializeRealm(Realm realm) { } } + // Everything in this method needs to be behind a transaction lock + // to prevent multi-process interaction while the Realm is initialized. private static void initializeSyncedRealm(Realm realm) { - // Everything in this method needs to be behind a transaction lock to prevent multi-process interaction while - // the Realm is initialized. boolean commitChanges = false; try { realm.beginTransaction(); long currentVersion = realm.getVersion(); - final boolean unversioned = (currentVersion == UNVERSIONED); + final boolean unversioned = currentVersion == UNVERSIONED; - final RealmProxyMediator mediator = realm.configuration.getSchemaMediator(); + RealmConfiguration configuration = realm.getConfiguration(); + + final RealmProxyMediator mediator = configuration.getSchemaMediator(); final Set> modelClasses = mediator.getModelClasses(); final OsRealmSchema.Creator schemaCreator = new OsRealmSchema.Creator(); @@ -396,15 +402,17 @@ private static void initializeSyncedRealm(Realm realm) { // Assumption: When SyncConfiguration then additive schema update mode. final OsRealmSchema schema = new OsRealmSchema(schemaCreator); - long newVersion = realm.configuration.getSchemaVersion(); + long newVersion = configuration.getSchemaVersion(); // !!! FIXME: This appalling kludge is necessitated by current package structure/visiblity constraints. // It absolutely breaks encapsulation and needs to be fixed! long schemaNativePointer = schema.getNativePtr(); if (realm.sharedRealm.requiresMigration(schemaNativePointer)) { if (currentVersion >= newVersion) { - throw new IllegalArgumentException(String.format("The schema was changed but the schema version " + - "was not updated. The configured schema version (%d) must be higher than the one in the Realm " + - "file (%d) in order to update the schema.", newVersion, currentVersion)); + throw new IllegalArgumentException(String.format( + "The schema was changed but the schema version was not updated. " + + "The configured schema version (%d) must be greater than the version " + + " in the Realm file (%d) in order to update the schema.", + newVersion, currentVersion)); } realm.sharedRealm.updateSchema(schemaNativePointer, newVersion); // The OS currently does not handle setting the schema version. We have to do it manually. @@ -417,10 +425,12 @@ private static void initializeSyncedRealm(Realm realm) { columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } - realm.getSchema().setColumnIndices((unversioned) ? newVersion : currentVersion, columnInfoMap); + realm.getSchema().setColumnIndices( + (unversioned) ? newVersion : currentVersion, + columnInfoMap); if (unversioned) { - final Transaction transaction = realm.configuration.getInitialDataTransaction(); + final Transaction transaction = configuration.getInitialDataTransaction(); if (transaction != null) { transaction.execute(realm); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 7e6f77b43f..e575615082 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -1,5 +1,6 @@ +package io.realm; /* - * Copyright 2015 Realm Inc. + * Copyright 2017 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +15,10 @@ * limitations under the License. */ -package io.realm; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; import java.util.Set; import io.realm.annotations.Required; -import io.realm.internal.Table; /** @@ -35,99 +27,12 @@ * * @see io.realm.RealmMigration */ -public class RealmObjectSchema { - - private static final Map, FieldMetaData> SUPPORTED_SIMPLE_FIELDS; - - static { - Map, FieldMetaData> m = new HashMap<>(); - m.put(String.class, new FieldMetaData(RealmFieldType.STRING, true)); - m.put(short.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Short.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(int.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Integer.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(long.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Long.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(float.class, new FieldMetaData(RealmFieldType.FLOAT, false)); - m.put(Float.class, new FieldMetaData(RealmFieldType.FLOAT, true)); - m.put(double.class, new FieldMetaData(RealmFieldType.DOUBLE, false)); - m.put(Double.class, new FieldMetaData(RealmFieldType.DOUBLE, true)); - m.put(boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, false)); - m.put(Boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, true)); - m.put(byte.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Byte.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(byte[].class, new FieldMetaData(RealmFieldType.BINARY, true)); - m.put(Date.class, new FieldMetaData(RealmFieldType.DATE, true)); - SUPPORTED_SIMPLE_FIELDS = Collections.unmodifiableMap(m); - } - - private static final Map, FieldMetaData> SUPPORTED_LINKED_FIELDS; - - static { - Map, FieldMetaData> m = new HashMap<>(); - m.put(RealmObject.class, new FieldMetaData(RealmFieldType.OBJECT, false)); - m.put(RealmList.class, new FieldMetaData(RealmFieldType.LIST, false)); - SUPPORTED_LINKED_FIELDS = Collections.unmodifiableMap(m); - } - - private final BaseRealm realm; - final Table table; - private final Map columnIndices; - private final long nativePtr; +public abstract class RealmObjectSchema { /** - * Creates a schema object for a given Realm class. - * - * @param realm Realm holding the objects. - * @param table table representation of the Realm class - * @param columnIndices mapping between field names and column indexes for the given table + * Release the object schema and any of native resources it might hold. */ - RealmObjectSchema(BaseRealm realm, Table table, Map columnIndices) { - this.realm = realm; - this.table = table; - this.columnIndices = columnIndices; - this.nativePtr = 0; - } - - /** - * Creates a schema object using object store. This constructor is intended to be used by - * the validation of schema, object schemas and prorperties through the object store. Even though the constructor - * is public, there is never a purpose which justifies calling it! - * - * @param className name of the class - */ - RealmObjectSchema(String className) { - this.realm = null; - this.table = null; - this.columnIndices = null; - this.nativePtr = nativeCreateRealmObjectSchema(className); - } - - protected RealmObjectSchema(long nativePtr) { - this.realm = null; - this.table = null; - this.columnIndices = null; - this.nativePtr = nativePtr; - } - - /** - * Closes/frees native resource. Even though the method is public, there is never a purpose which justifies calling - * it! - */ - public void close() { - if (nativePtr != 0) { - Set properties = getProperties(); - for (Property property : properties) { - property.close(); - } - nativeClose(nativePtr); - } - } - - - protected long getNativePtr() { - return nativePtr; - } + public abstract void close(); /** * Returns the name of the RealmObject class being represented by this schema. @@ -139,53 +44,17 @@ protected long getNativePtr() { * * @return the name of the RealmObject class represented by this schema. */ - public String getClassName() { - if (realm == null) { - return nativeGetClassName(nativePtr); - } else { - return table.getName().substring(Table.TABLE_PREFIX.length()); - } - } + public abstract String getClassName(); /** - * Sets a new name for this RealmObject class. This is equivalent to renaming it. When - * {@link RealmObjectSchema#table} has a primary key, this will transfer the primary key for the new class name. + * Sets a new name for this RealmObject class. This is equivalent to renaming it. * * @param className the new name for this class. * @throws IllegalArgumentException if className is {@code null} or an empty string, or its length exceeds 56 * characters. * @see StandardRealmSchema#rename(String, String) */ - public RealmObjectSchema setClassName(String className) { - realm.checkNotInSync(); // renaming a table is not permitted - checkEmpty(className); - String internalTableName = Table.TABLE_PREFIX + className; - if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { - throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: \'" + className + "\' (" + Integer.toString(className.length()) + ")"); - } - if (realm.sharedRealm.hasTable(internalTableName)) { - throw new IllegalArgumentException("Class already exists: " + className); - } - // in case this table has a primary key, we need to transfer it after renaming the table. - String oldTableName = null; - String pkField = null; - if (table.hasPrimaryKey()) { - oldTableName = table.getName(); - pkField = getPrimaryKey(); - table.setPrimaryKey(null); - } - realm.sharedRealm.renameTable(table.getName(), internalTableName); - if (pkField != null && !pkField.isEmpty()) { - try { - table.setPrimaryKey(pkField); - } catch (Exception e) { - // revert the table name back when something goes wrong - realm.sharedRealm.renameTable(table.getName(), oldTableName); - throw e; - } - } - return this; - } + public abstract RealmObjectSchema setClassName(String className); /** * Adds a new simple field to the RealmObject class. The type must be one supported by Realm. See @@ -203,33 +72,7 @@ public RealmObjectSchema setClassName(String className) { * @throws IllegalArgumentException if the type isn't supported, field name is illegal or a field with that name * already exists. */ - public RealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes) { - FieldMetaData metadata = SUPPORTED_SIMPLE_FIELDS.get(fieldType); - if (metadata == null) { - if (SUPPORTED_LINKED_FIELDS.containsKey(fieldType)) { - throw new IllegalArgumentException("Use addRealmObjectField() instead to add fields that link to other RealmObjects: " + fieldName); - } else { - throw new IllegalArgumentException(String.format("Realm doesn't support this field type: %s(%s)", - fieldName, fieldType)); - } - } - - checkNewFieldName(fieldName); - boolean nullable = metadata.defaultNullable; - if (containsAttribute(attributes, FieldAttribute.REQUIRED)) { - nullable = false; - } - - long columnIndex = table.addColumn(metadata.realmType, fieldName, nullable); - try { - addModifiers(fieldName, attributes); - } catch (Exception e) { - // Modifiers have been removed by the addModifiers method() - table.removeColumn(columnIndex); - throw e; - } - return this; - } + public abstract RealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes); /** * Adds a new field that references another {@link RealmObject}. @@ -239,12 +82,7 @@ public RealmObjectSchema addField(String fieldName, Class fieldType, FieldAtt * @return the updated schema. * @throws IllegalArgumentException if field name is illegal or a field with that name already exists. */ - public RealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema) { - checkLegalName(fieldName); - checkFieldNameIsAvailable(fieldName); - table.addColumnLink(RealmFieldType.OBJECT, fieldName, realm.sharedRealm.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); - return this; - } + public abstract RealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema); /** * Adds a new field that references a {@link RealmList}. @@ -254,40 +92,7 @@ public RealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema * @return the updated schema. * @throws IllegalArgumentException if the field name is illegal or a field with that name already exists. */ - public RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema) { - checkLegalName(fieldName); - checkFieldNameIsAvailable(fieldName); - table.addColumnLink(RealmFieldType.LIST, fieldName, realm.sharedRealm.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); - return this; - } - - /** - * Adds a property to an object schema. This method should only be used by proxy classes to set up a schema. - * - * @param property the property to add. - * @return the updated schema. - * @throws IllegalArgumentException if the method is called after opening a Realm. - */ - protected RealmObjectSchema add(Property property) { - if (realm != null && nativePtr == 0) { - throw new IllegalArgumentException("Don't use this method."); - } - nativeAddProperty(nativePtr, property.getNativePtr()); - return this; - } - - private Set getProperties() { - if (realm == null) { - long[] ptrs = nativeGetProperties(nativePtr); - Set properties = new LinkedHashSet<>(ptrs.length); - for (int i = 0; i < ptrs.length; i++) { - properties.add(new Property(ptrs[i])); - } - return properties; - } else { - throw new IllegalArgumentException("Not possible"); - } - } + public abstract RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema); /** * Removes a field from the class. @@ -296,19 +101,7 @@ private Set getProperties() { * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist. */ - public RealmObjectSchema removeField(String fieldName) { - realm.checkNotInSync(); // destructive modification of a schema is not permitted - checkLegalName(fieldName); - if (!hasField(fieldName)) { - throw new IllegalStateException(fieldName + " does not exist."); - } - long columnIndex = getColumnIndex(fieldName); - if (table.getPrimaryKey() == columnIndex) { - table.setPrimaryKey(null); - } - table.removeColumn(columnIndex); - return this; - } + public abstract RealmObjectSchema removeField(String fieldName); /** * Renames a field from one name to another. @@ -318,19 +111,7 @@ public RealmObjectSchema removeField(String fieldName) { * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist or if the new field name already exists. */ - public RealmObjectSchema renameField(String currentFieldName, String newFieldName) { - realm.checkNotInSync(); // destructive modification of a schema is not permitted - checkLegalName(currentFieldName); - checkFieldExists(currentFieldName); - checkLegalName(newFieldName); - checkFieldNameIsAvailable(newFieldName); - long columnIndex = getColumnIndex(currentFieldName); - table.renameColumn(columnIndex, newFieldName); - - // ATTENTION: We don't need to re-set the PK table here since the column index won't be changed when renaming. - - return this; - } + public abstract RealmObjectSchema renameField(String currentFieldName, String newFieldName); /** * Tests if the class has field defined with the given name. @@ -338,9 +119,7 @@ public RealmObjectSchema renameField(String currentFieldName, String newFieldNam * @param fieldName field name to test. * @return {@code true} if the field exists, {@code false} otherwise. */ - public boolean hasField(String fieldName) { - return table.getColumnIndex(fieldName) != Table.NO_MATCH; - } + public abstract boolean hasField(String fieldName); /** * Adds an index to a given field. This is the equivalent of adding the {@link io.realm.annotations.Index} @@ -351,16 +130,7 @@ public boolean hasField(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist, the field cannot be indexed or it already has a * index defined. */ - public RealmObjectSchema addIndex(String fieldName) { - checkLegalName(fieldName); - checkFieldExists(fieldName); - long columnIndex = getColumnIndex(fieldName); - if (table.hasSearchIndex(columnIndex)) { - throw new IllegalStateException(fieldName + " already has an index."); - } - table.addSearchIndex(columnIndex); - return this; - } + public abstract RealmObjectSchema addIndex(String fieldName); /** * Checks if a given field has an index defined. @@ -370,12 +140,7 @@ public RealmObjectSchema addIndex(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist. * @see io.realm.annotations.Index */ - public boolean hasIndex(String fieldName) { - checkLegalName(fieldName); - checkFieldExists(fieldName); - return table.hasSearchIndex(table.getColumnIndex(fieldName)); - } - + public abstract boolean hasIndex(String fieldName); /** * Removes an index from a given field. This is the same as removing the {@code @Index} annotation on the field. @@ -384,17 +149,7 @@ public boolean hasIndex(String fieldName) { * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist or the field doesn't have an index. */ - public RealmObjectSchema removeIndex(String fieldName) { - realm.checkNotInSync(); // Destructive modifications are not permitted. - checkLegalName(fieldName); - checkFieldExists(fieldName); - long columnIndex = getColumnIndex(fieldName); - if (!table.hasSearchIndex(columnIndex)) { - throw new IllegalStateException("Field is not indexed: " + fieldName); - } - table.removeSearchIndex(columnIndex); - return this; - } + public abstract RealmObjectSchema removeIndex(String fieldName); /** * Adds a primary key to a given field. This is the same as adding the {@link io.realm.annotations.PrimaryKey} @@ -406,20 +161,7 @@ public RealmObjectSchema removeIndex(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist, the field cannot be a primary key or it already * has a primary key defined. */ - public RealmObjectSchema addPrimaryKey(String fieldName) { - checkLegalName(fieldName); - checkFieldExists(fieldName); - if (table.hasPrimaryKey()) { - throw new IllegalStateException("A primary key is already defined"); - } - table.setPrimaryKey(fieldName); - long columnIndex = getColumnIndex(fieldName); - if (!table.hasSearchIndex(columnIndex)) { - // No exception will be thrown since adding PrimaryKey implies the column has an index. - table.addSearchIndex(columnIndex); - } - return this; - } + public abstract RealmObjectSchema addPrimaryKey(String fieldName); /** * Removes the primary key from this class. This is the same as removing the {@link io.realm.annotations.PrimaryKey} @@ -429,18 +171,7 @@ public RealmObjectSchema addPrimaryKey(String fieldName) { * @return the updated schema. * @throws IllegalArgumentException if the class doesn't have a primary key defined. */ - public RealmObjectSchema removePrimaryKey() { - realm.checkNotInSync(); // Destructive modifications are not permitted. - if (!table.hasPrimaryKey()) { - throw new IllegalStateException(getClassName() + " doesn't have a primary key."); - } - long columnIndex = table.getPrimaryKey(); - if (table.hasSearchIndex(columnIndex)) { - table.removeSearchIndex(columnIndex); - } - table.setPrimaryKey(""); - return this; - } + public abstract RealmObjectSchema removePrimaryKey(); /** * Sets a field to be required i.e., it is not allowed to hold {@code null} values. This is equivalent to switching @@ -453,31 +184,7 @@ public RealmObjectSchema removePrimaryKey() { * the field already have been set as required. * @see Required */ - public RealmObjectSchema setRequired(String fieldName, boolean required) { - long columnIndex = table.getColumnIndex(fieldName); - boolean currentColumnRequired = isRequired(fieldName); - RealmFieldType type = table.getColumnType(columnIndex); - - if (type == RealmFieldType.OBJECT) { - throw new IllegalArgumentException("Cannot modify the required state for RealmObject references: " + fieldName); - } - if (type == RealmFieldType.LIST) { - throw new IllegalArgumentException("Cannot modify the required state for RealmList references: " + fieldName); - } - if (required && currentColumnRequired) { - throw new IllegalStateException("Field is already required: " + fieldName); - } - if (!required && !currentColumnRequired) { - throw new IllegalStateException("Field is already nullable: " + fieldName); - } - - if (required) { - table.convertColumnToNotNullable(columnIndex); - } else { - table.convertColumnToNullable(columnIndex); - } - return this; - } + public abstract RealmObjectSchema setRequired(String fieldName, boolean required); /** * Sets a field to be nullable i.e., it should be able to hold {@code null} values. This is equivalent to switching @@ -488,10 +195,7 @@ public RealmObjectSchema setRequired(String fieldName, boolean required) { * @return the updated schema. * @throws IllegalArgumentException if the field name doesn't exist, or cannot be set as nullable. */ - public RealmObjectSchema setNullable(String fieldName, boolean nullable) { - setRequired(fieldName, !nullable); - return this; - } + public abstract RealmObjectSchema setNullable(String fieldName, boolean nullable); /** * Checks if a given field is required i.e., it is not allowed to contain {@code null} values. @@ -501,10 +205,7 @@ public RealmObjectSchema setNullable(String fieldName, boolean nullable) { * @throws IllegalArgumentException if field name doesn't exist. * @see #setRequired(String, boolean) */ - public boolean isRequired(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return !table.isColumnNullable(columnIndex); - } + public abstract boolean isRequired(String fieldName); /** * Checks if a given field is nullable i.e., it is allowed to contain {@code null} values. @@ -514,10 +215,7 @@ public boolean isRequired(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist. * @see #setNullable(String, boolean) */ - public boolean isNullable(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return table.isColumnNullable(columnIndex); - } + public abstract boolean isNullable(String fieldName); /** * Checks if a given field is the primary key field. @@ -527,10 +225,7 @@ public boolean isNullable(String fieldName) { * @throws IllegalArgumentException if field name doesn't exist. * @see #addPrimaryKey(String) */ - public boolean isPrimaryKey(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return columnIndex == table.getPrimaryKey(); - } + public abstract boolean isPrimaryKey(String fieldName); /** * Checks if the class has a primary key defined. @@ -538,9 +233,7 @@ public boolean isPrimaryKey(String fieldName) { * @return {@code true} if a primary key is defined, {@code false} otherwise. * @see io.realm.annotations.PrimaryKey */ - public boolean hasPrimaryKey() { - return table.hasPrimaryKey(); - } + public abstract boolean hasPrimaryKey(); /** * Returns the name of the primary key field. @@ -548,26 +241,14 @@ public boolean hasPrimaryKey() { * @return the name of the primary key field. * @throws IllegalStateException if the class doesn't have a primary key defined. */ - public String getPrimaryKey() { - if (!table.hasPrimaryKey()) { - throw new IllegalStateException(getClassName() + " doesn't have a primary key."); - } - return table.getColumnName(table.getPrimaryKey()); - } + public abstract String getPrimaryKey(); /** * Returns all fields in this class. * * @return a list of all the fields in this class. */ - public Set getFieldNames() { - int columnCount = (int) table.getColumnCount(); - Set columnNames = new LinkedHashSet<>(columnCount); - for (int i = 0; i < columnCount; i++) { - columnNames.add(table.getColumnName(i)); - } - return columnNames; - } + public abstract Set getFieldNames(); /** * Runs a transformation function on each RealmObject instance of the current class. The object will be represented @@ -575,204 +256,20 @@ public Set getFieldNames() { * * @return this schema. */ - public RealmObjectSchema transform(Function function) { - if (function != null) { - long size = table.size(); - for (long i = 0; i < size; i++) { - function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i))); - } - } - - return this; - } - - // Invariant: Field was just added. This method is responsible for cleaning up attributes if it fails. - private void addModifiers(String fieldName, FieldAttribute[] attributes) { - boolean indexAdded = false; - try { - if (attributes != null && attributes.length > 0) { - if (containsAttribute(attributes, FieldAttribute.INDEXED)) { - addIndex(fieldName); - indexAdded = true; - } - - if (containsAttribute(attributes, FieldAttribute.PRIMARY_KEY)) { - // Note : adding primary key implies application of FieldAttribute.INDEXED attribute. - addPrimaryKey(fieldName); - indexAdded = true; - } - - // REQUIRED is being handled when adding the column using addField through the nullable parameter. - } - } catch (Exception e) { - // If something went wrong, revert all attributes. - long columnIndex = getColumnIndex(fieldName); - if (indexAdded) { - table.removeSearchIndex(columnIndex); - } - throw (RuntimeException) e; - } - } - - private boolean containsAttribute(FieldAttribute[] attributeList, FieldAttribute attribute) { - if (attributeList == null || attributeList.length == 0) { - return false; - } - for (int i = 0; i < attributeList.length; i++) { - if (attributeList[i] == attribute) { - return true; - } - } - return false; - } - - private void checkNewFieldName(String fieldName) { - checkLegalName(fieldName); - checkFieldNameIsAvailable(fieldName); - } - - private void checkLegalName(String fieldName) { - if (fieldName == null || fieldName.isEmpty()) { - throw new IllegalArgumentException("Field name can not be null or empty"); - } - if (fieldName.contains(".")) { - throw new IllegalArgumentException("Field name can not contain '.'"); - } - } - - private void checkFieldNameIsAvailable(String fieldName) { - if (table.getColumnIndex(fieldName) != Table.NO_MATCH) { - throw new IllegalArgumentException("Field already exists in '" + getClassName() + "': " + fieldName); - } - } - - private void checkFieldExists(String fieldName) { - if (table.getColumnIndex(fieldName) == Table.NO_MATCH) { - throw new IllegalArgumentException("Field name doesn't exist on object '" + getClassName() + "': " + fieldName); - } - } - - private long getColumnIndex(String fieldName) { - long columnIndex = table.getColumnIndex(fieldName); - if (columnIndex == -1) { - throw new IllegalArgumentException( - String.format("Field name '%s' does not exist on schema for '%s", - fieldName, getClassName() - )); - } - return columnIndex; - } - - private void checkEmpty(String str) { - if (str == null || str.isEmpty()) { - throw new IllegalArgumentException("Null or empty class names are not allowed"); - } - } + public abstract RealmObjectSchema transform(Function function); /** - * Returns the column indices for the given field name. If a linked field is defined, the column index for - * each field is returned. + * Returns the type used by the underlying storage engine to represent this field. * - * @param fieldDescription fieldName or link path to a field name. - * @param validColumnTypes valid field type for the last field in a linked field - * @return list of column indices. + * @return the underlying type used by Realm to represent this field. */ - // TODO: consider another caching strategy so linked classes are included in the cache. - long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes) { - if (fieldDescription == null || fieldDescription.equals("")) { - throw new IllegalArgumentException("Non-empty fieldname must be provided"); - } - if (fieldDescription.startsWith(".") || fieldDescription.endsWith(".")) { - throw new IllegalArgumentException("Illegal field name. It cannot start or end with a '.': " + fieldDescription); - } - Table table = this.table; - boolean checkColumnType = validColumnTypes != null && validColumnTypes.length > 0; - if (fieldDescription.contains(".")) { - // Resolves field description down to last field name. - String[] names = fieldDescription.split("\\."); - long[] columnIndices = new long[names.length]; - for (int i = 0; i < names.length - 1; i++) { - long index = table.getColumnIndex(names[i]); - if (index < 0) { - throw new IllegalArgumentException("Invalid query: " + names[i] + " does not refer to a class."); - } - RealmFieldType type = table.getColumnType(index); - if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { - table = table.getLinkTarget(index); - columnIndices[i] = index; - } else { - throw new IllegalArgumentException("Invalid query: " + names[i] + " does not refer to a class."); - } - } - - // Checks if last field name is a valid field. - String columnName = names[names.length - 1]; - long columnIndex = table.getColumnIndex(columnName); - columnIndices[names.length - 1] = columnIndex; - if (columnIndex < 0) { - throw new IllegalArgumentException(columnName + " is not a field name in class " + table.getName()); - } - if (checkColumnType && !isValidType(table.getColumnType(columnIndex), validColumnTypes)) { - throw new IllegalArgumentException(String.format("Field '%s': type mismatch.", names[names.length - 1])); - } - return columnIndices; - } else { - Long fieldIndex = getFieldIndex(fieldDescription); - if (fieldIndex == null) { - throw new IllegalArgumentException(String.format("Field '%s' does not exist.", fieldDescription)); - } - RealmFieldType tableColumnType = table.getColumnType(fieldIndex); - if (checkColumnType && !isValidType(tableColumnType, validColumnTypes)) { - throw new IllegalArgumentException(String.format("Field '%s': type mismatch. Was %s, expected %s.", - fieldDescription, tableColumnType, Arrays.toString(validColumnTypes))); - } - return new long[] {fieldIndex}; - } - } + public abstract RealmFieldType getFieldType(String fieldName); - private boolean isValidType(RealmFieldType columnType, RealmFieldType[] validColumnTypes) { - for (int i = 0; i < validColumnTypes.length; i++) { - if (validColumnTypes[i] == columnType) { - return true; - } - } - return false; - } - - /** - * Returns the column index in the underlying table for the given field name. - * - * @param fieldName field name to find index for. - * @return column index or null if it doesn't exists. - */ - Long getFieldIndex(String fieldName) { - return columnIndices.get(fieldName); - } + abstract long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes); - /** - * Returns the column index in the underlying table for the given field name. - * - * @param fieldName field name to find index for. - * @return column index. - * @throws IllegalArgumentException if the field does not exists. - */ - long getAndCheckFieldIndex(String fieldName) { - Long index = columnIndices.get(fieldName); - if (index == null) { - throw new IllegalArgumentException("Field does not exist: " + fieldName); - } - return index; - } + abstract RealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required); - /** - * Returns the type used by the underlying storage engine to represent this field. - * - * @return the underlying type used by Realm to represent this field. - */ - public RealmFieldType getFieldType(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return table.getColumnType(columnIndex); - } + abstract RealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linkedTo); /** * Function interface, used when traversing all objects of the current class and apply a function on each. @@ -784,92 +281,13 @@ public interface Function { } // Tuple containing data about each supported Java type. - private static class FieldMetaData { - public final RealmFieldType realmType; - public final boolean defaultNullable; + protected static class FieldMetaData { + protected final RealmFieldType realmType; + protected final boolean defaultNullable; - public FieldMetaData(RealmFieldType realmType, boolean defaultNullable) { + protected FieldMetaData(RealmFieldType realmType, boolean defaultNullable) { this.realmType = realmType; this.defaultNullable = defaultNullable; } } - - static final class DynamicColumnMap implements Map { - private final Table table; - - public DynamicColumnMap(Table table) { - this.table = table; - } - - @Override - public Long get(Object key) { - long ret = table.getColumnIndex((String) key); - return ret < 0 ? null : ret; - } - - @Override - public void clear() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean containsKey(Object key) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean containsValue(Object value) { - throw new UnsupportedOperationException(); - } - - @Override - public Set> entrySet() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isEmpty() { - throw new UnsupportedOperationException(); - } - - @Override - public Set keySet() { - throw new UnsupportedOperationException(); - } - - @Override - public Long put(String key, Long value) { - throw new UnsupportedOperationException(); - } - - @Override - public void putAll(Map map) { - throw new UnsupportedOperationException(); - } - - @Override - public Long remove(Object key) { - throw new UnsupportedOperationException(); - } - - @Override - public int size() { - throw new UnsupportedOperationException(); - } - - @Override - public Collection values() { - throw new UnsupportedOperationException(); - } - } - - static native long nativeCreateRealmObjectSchema(String className); - - static native void nativeAddProperty(long nativePtr, long nativePropertyPtr); - - static native long[] nativeGetProperties(long nativePtr); - - static native void nativeClose(long nativePtr); - - static native String nativeGetClassName(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 0f2a19e70c..7f3a19197d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -54,7 +54,7 @@ public class RealmQuery { private final Table table; private final BaseRealm realm; private final TableQuery query; - private final RealmObjectSchema schema; + private final StandardRealmObjectSchema schema; private Class clazz; private String className; private LinkView linkView; @@ -123,7 +123,7 @@ private RealmQuery(Realm realm, Class clazz) { this.realm = realm; this.clazz = clazz; this.schema = realm.getSchema().getSchemaForClass(clazz); - this.table = schema.table; + this.table = schema.getTable(); this.linkView = null; this.query = table.where(); } @@ -141,7 +141,7 @@ private RealmQuery(BaseRealm realm, LinkView linkView, Class clazz) { this.realm = realm; this.clazz = clazz; this.schema = realm.getSchema().getSchemaForClass(clazz); - this.table = schema.table; + this.table = schema.getTable(); this.linkView = linkView; this.query = linkView.where(); } @@ -150,7 +150,7 @@ private RealmQuery(BaseRealm realm, String className) { this.realm = realm; this.className = className; this.schema = realm.getSchema().getSchemaForClass(className); - this.table = schema.table; + this.table = schema.getTable(); this.query = table.where(); } @@ -158,7 +158,7 @@ private RealmQuery(RealmResults queryResults, String classNa this.realm = queryResults.realm; this.className = className; this.schema = realm.getSchema().getSchemaForClass(className); - this.table = schema.table; + this.table = schema.getTable(); this.query = queryResults.getCollection().where(); } @@ -166,7 +166,7 @@ private RealmQuery(BaseRealm realm, LinkView linkView, String className) { this.realm = realm; this.className = className; this.schema = realm.getSchema().getSchemaForClass(className); - this.table = schema.table; + this.table = schema.getTable(); this.linkView = linkView; this.query = linkView.where(); } @@ -203,7 +203,7 @@ public boolean isValid() { public RealmQuery isNull(String fieldName) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName); + long[] columnIndices = schema.getColumnIndices(fieldName); // Checks that fieldName has the correct type is done in C++. this.query.isNull(columnIndices); @@ -221,7 +221,7 @@ public RealmQuery isNull(String fieldName) { public RealmQuery isNotNull(String fieldName) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName); + long[] columnIndices = schema.getColumnIndices(fieldName); // Checks that fieldName has the correct type is done in C++. this.query.isNotNull(columnIndices); @@ -256,7 +256,7 @@ public RealmQuery equalTo(String fieldName, String value, Case casing) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, String value, Case casing) { - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.equalTo(columnIndices, value, casing); return this; } @@ -464,7 +464,7 @@ public RealmQuery equalTo(String fieldName, Date value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, Date value) { - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.equalTo(columnIndices, value); return this; } @@ -705,7 +705,7 @@ public RealmQuery notEqualTo(String fieldName, String value) { public RealmQuery notEqualTo(String fieldName, String value, Case casing) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); if (columnIndices.length > 1 && !casing.getValue()) { throw new IllegalArgumentException("Link queries cannot be case insensitive - coming soon."); } @@ -936,7 +936,7 @@ public RealmQuery greaterThan(String fieldName, long value) { public RealmQuery greaterThan(String fieldName, double value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.greaterThan(columnIndices, value); return this; } @@ -952,7 +952,7 @@ public RealmQuery greaterThan(String fieldName, double value) { public RealmQuery greaterThan(String fieldName, float value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.greaterThan(columnIndices, value); return this; } @@ -968,7 +968,7 @@ public RealmQuery greaterThan(String fieldName, float value) { public RealmQuery greaterThan(String fieldName, Date value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.greaterThan(columnIndices, value); return this; } @@ -984,7 +984,7 @@ public RealmQuery greaterThan(String fieldName, Date value) { public RealmQuery greaterThanOrEqualTo(String fieldName, int value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.greaterThanOrEqual(columnIndices, value); return this; } @@ -1000,7 +1000,7 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, int value) { public RealmQuery greaterThanOrEqualTo(String fieldName, long value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.greaterThanOrEqual(columnIndices, value); return this; } @@ -1016,7 +1016,7 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, long value) { public RealmQuery greaterThanOrEqualTo(String fieldName, double value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.greaterThanOrEqual(columnIndices, value); return this; } @@ -1032,7 +1032,7 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, double value) { public RealmQuery greaterThanOrEqualTo(String fieldName, float value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.greaterThanOrEqual(columnIndices, value); return this; } @@ -1048,7 +1048,7 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, float value) { public RealmQuery greaterThanOrEqualTo(String fieldName, Date value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.greaterThanOrEqual(columnIndices, value); return this; } @@ -1064,7 +1064,7 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, Date value) { public RealmQuery lessThan(String fieldName, int value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.lessThan(columnIndices, value); return this; } @@ -1080,7 +1080,7 @@ public RealmQuery lessThan(String fieldName, int value) { public RealmQuery lessThan(String fieldName, long value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.lessThan(columnIndices, value); return this; } @@ -1096,7 +1096,7 @@ public RealmQuery lessThan(String fieldName, long value) { public RealmQuery lessThan(String fieldName, double value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.lessThan(columnIndices, value); return this; } @@ -1112,7 +1112,7 @@ public RealmQuery lessThan(String fieldName, double value) { public RealmQuery lessThan(String fieldName, float value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.lessThan(columnIndices, value); return this; } @@ -1128,7 +1128,7 @@ public RealmQuery lessThan(String fieldName, float value) { public RealmQuery lessThan(String fieldName, Date value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.lessThan(columnIndices, value); return this; } @@ -1144,7 +1144,7 @@ public RealmQuery lessThan(String fieldName, Date value) { public RealmQuery lessThanOrEqualTo(String fieldName, int value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.lessThanOrEqual(columnIndices, value); return this; } @@ -1160,7 +1160,7 @@ public RealmQuery lessThanOrEqualTo(String fieldName, int value) { public RealmQuery lessThanOrEqualTo(String fieldName, long value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.lessThanOrEqual(columnIndices, value); return this; } @@ -1176,7 +1176,7 @@ public RealmQuery lessThanOrEqualTo(String fieldName, long value) { public RealmQuery lessThanOrEqualTo(String fieldName, double value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.lessThanOrEqual(columnIndices, value); return this; } @@ -1192,7 +1192,7 @@ public RealmQuery lessThanOrEqualTo(String fieldName, double value) { public RealmQuery lessThanOrEqualTo(String fieldName, float value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.lessThanOrEqual(columnIndices, value); return this; } @@ -1208,7 +1208,7 @@ public RealmQuery lessThanOrEqualTo(String fieldName, float value) { public RealmQuery lessThanOrEqualTo(String fieldName, Date value) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.lessThanOrEqual(columnIndices, value); return this; } @@ -1225,7 +1225,7 @@ public RealmQuery lessThanOrEqualTo(String fieldName, Date value) { public RealmQuery between(String fieldName, int from, int to) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.between(columnIndices, from, to); return this; } @@ -1242,7 +1242,7 @@ public RealmQuery between(String fieldName, int from, int to) { public RealmQuery between(String fieldName, long from, long to) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); this.query.between(columnIndices, from, to); return this; } @@ -1259,7 +1259,7 @@ public RealmQuery between(String fieldName, long from, long to) { public RealmQuery between(String fieldName, double from, double to) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); this.query.between(columnIndices, from, to); return this; } @@ -1276,7 +1276,7 @@ public RealmQuery between(String fieldName, double from, double to) { public RealmQuery between(String fieldName, float from, float to) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); this.query.between(columnIndices, from, to); return this; } @@ -1293,7 +1293,7 @@ public RealmQuery between(String fieldName, float from, float to) { public RealmQuery between(String fieldName, Date from, Date to) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); this.query.between(columnIndices, from, to); return this; } @@ -1323,7 +1323,7 @@ public RealmQuery contains(String fieldName, String value) { public RealmQuery contains(String fieldName, String value, Case casing) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.contains(columnIndices, value, casing); return this; } @@ -1352,7 +1352,7 @@ public RealmQuery beginsWith(String fieldName, String value) { public RealmQuery beginsWith(String fieldName, String value, Case casing) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.beginsWith(columnIndices, value, casing); return this; } @@ -1381,7 +1381,7 @@ public RealmQuery endsWith(String fieldName, String value) { public RealmQuery endsWith(String fieldName, String value, Case casing) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.endsWith(columnIndices, value, casing); return this; } @@ -1418,7 +1418,7 @@ public RealmQuery like(String fieldName, String value) { public RealmQuery like(String fieldName, String value, Case casing) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); this.query.like(columnIndices, value, casing); return this; } @@ -1497,7 +1497,7 @@ public RealmQuery not() { public RealmQuery isEmpty(String fieldName) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST); this.query.isEmpty(columnIndices); return this; } @@ -1513,7 +1513,7 @@ public RealmQuery isEmpty(String fieldName) { public RealmQuery isNotEmpty(String fieldName) { realm.checkIfValid(); - long columnIndices[] = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST); + long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST); this.query.isNotEmpty(columnIndices); return this; } @@ -1835,7 +1835,7 @@ public RealmResults findAllSortedAsync(String fieldName) { * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ - public RealmResults findAllSorted(String fieldNames[], Sort sortOrders[]) { + public RealmResults findAllSorted(String[] fieldNames, Sort[] sortOrders) { realm.checkIfValid(); SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldNames, sortOrders); @@ -1857,7 +1857,7 @@ private boolean isDynamicQuery() { * {@link RealmObject} or a child {@link RealmList}. * @see io.realm.RealmResults */ - public RealmResults findAllSortedAsync(String fieldNames[], final Sort[] sortOrders) { + public RealmResults findAllSortedAsync(String[] fieldNames, final Sort[] sortOrders) { realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index a340686cf5..e7a42bbc7e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -54,7 +54,7 @@ public abstract class RealmSchema { * * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ - public abstract Set getAll(); + public abstract Set getAll(); /** * Adds a new class to the Realm. @@ -64,6 +64,23 @@ public abstract class RealmSchema { */ public abstract RealmObjectSchema create(String className); + /** + * Removes a class from the Realm. All data will be removed. Removing a class while other classes point + * to it will throw an {@link IllegalStateException}. Removes those classes or fields first. + * + * @param className name of the class to remove. + */ + public abstract void remove(String className); + + /** + * Renames a class already in the Realm. + * + * @param oldClassName old class name. + * @param newClassName new class name. + * @return a schema object for renamed class. + */ + public abstract RealmObjectSchema rename(String oldClassName, String newClassName); + /** * Checks if a given class already exists in the schema. * diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java new file mode 100644 index 0000000000..cced1c6d23 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java @@ -0,0 +1,827 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import io.realm.annotations.Required; +import io.realm.internal.RealmProxyMediator; +import io.realm.internal.Table; + + +class StandardRealmObjectSchema extends RealmObjectSchema { + + private static final Map, FieldMetaData> SUPPORTED_SIMPLE_FIELDS; + + static { + Map, FieldMetaData> m = new HashMap<>(); + m.put(String.class, new FieldMetaData(RealmFieldType.STRING, true)); + m.put(short.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Short.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(int.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Integer.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(long.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Long.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(float.class, new FieldMetaData(RealmFieldType.FLOAT, false)); + m.put(Float.class, new FieldMetaData(RealmFieldType.FLOAT, true)); + m.put(double.class, new FieldMetaData(RealmFieldType.DOUBLE, false)); + m.put(Double.class, new FieldMetaData(RealmFieldType.DOUBLE, true)); + m.put(boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, false)); + m.put(Boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, true)); + m.put(byte.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Byte.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(byte[].class, new FieldMetaData(RealmFieldType.BINARY, true)); + m.put(Date.class, new FieldMetaData(RealmFieldType.DATE, true)); + SUPPORTED_SIMPLE_FIELDS = Collections.unmodifiableMap(m); + } + + private static final Map, FieldMetaData> SUPPORTED_LINKED_FIELDS; + + static { + Map, FieldMetaData> m = new HashMap<>(); + m.put(RealmObject.class, new FieldMetaData(RealmFieldType.OBJECT, false)); + m.put(RealmList.class, new FieldMetaData(RealmFieldType.LIST, false)); + SUPPORTED_LINKED_FIELDS = Collections.unmodifiableMap(m); + } + + private final BaseRealm realm; + private final Map columnIndices; + private final Table table; + + /** + * Creates a schema object for a given Realm class. + * + * @param realm Realm holding the objects. + * @param table table representation of the Realm class + * @param columnIndices mapping between field names and column indexes for the given table + */ + StandardRealmObjectSchema(BaseRealm realm, Table table, Map columnIndices) { + this.realm = realm; + this.table = table; + this.columnIndices = columnIndices; + } + + public Table getTable() { + return table; + } + + /** + * There are no resources here that need closing. + */ + @Override + public void close() { } + + /** + * Returns the name of the RealmObject class being represented by this schema. + *

                      + *

                        + *
                      • When using a typed {@link Realm} this name is the same as the {@link RealmObject} class.
                      • + *
                      • When using a {@link DynamicRealm} this is the name used in all API methods requiring a class name.
                      • + *
                      + * + * @return the name of the RealmObject class represented by this schema. + */ + @Override + public String getClassName() { + return table.getName().substring(Table.TABLE_PREFIX.length()); + } + + /** + * Sets a new name for this RealmObject class. This is equivalent to renaming it. When + * {@link StandardRealmObjectSchema#table} has a primary key, this will transfer the primary key for the new class name. + * + * @param className the new name for this class. + * @throws IllegalArgumentException if className is {@code null} or an empty string, or its length exceeds 56 + * characters. + * @see StandardRealmSchema#rename(String, String) + */ + @Override + public StandardRealmObjectSchema setClassName(String className) { + realm.checkNotInSync(); // renaming a table is not permitted + checkEmpty(className); + String internalTableName = Table.TABLE_PREFIX + className; + if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { + throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: \'" + className + "\' (" + Integer.toString(className.length()) + ")"); + } + if (realm.sharedRealm.hasTable(internalTableName)) { + throw new IllegalArgumentException("Class already exists: " + className); + } + // in case this table has a primary key, we need to transfer it after renaming the table. + String oldTableName = null; + String pkField = null; + if (table.hasPrimaryKey()) { + oldTableName = table.getName(); + pkField = getPrimaryKey(); + table.setPrimaryKey(null); + } + realm.sharedRealm.renameTable(table.getName(), internalTableName); + if (pkField != null && !pkField.isEmpty()) { + try { + table.setPrimaryKey(pkField); + } catch (Exception e) { + // revert the table name back when something goes wrong + realm.sharedRealm.renameTable(table.getName(), oldTableName); + throw e; + } + } + return this; + } + + /** + * Adds a new simple field to the RealmObject class. The type must be one supported by Realm. See + * {@link RealmObject} for the list of supported types. If the field should allow {@code null} values use the boxed + * type instead e.g., {@code Integer.class} instead of {@code int.class}. + *

                      + * To add fields that reference other RealmObjects or RealmLists use + * {@link #addRealmObjectField(String, RealmObjectSchema)} or {@link #addRealmListField(String, RealmObjectSchema)} + * instead. + * + * @param fieldName name of the field to add. + * @param fieldType type of field to add. See {@link RealmObject} for the full list. + * @param attributes set of attributes for this field. + * @return the updated schema. + * @throws IllegalArgumentException if the type isn't supported, field name is illegal or a field with that name + * already exists. + */ + @Override + public StandardRealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes) { + FieldMetaData metadata = SUPPORTED_SIMPLE_FIELDS.get(fieldType); + if (metadata == null) { + if (SUPPORTED_LINKED_FIELDS.containsKey(fieldType)) { + throw new IllegalArgumentException("Use addRealmObjectField() instead to add fields that link to other RealmObjects: " + fieldName); + } else { + throw new IllegalArgumentException(String.format("Realm doesn't support this field type: %s(%s)", + fieldName, fieldType)); + } + } + + checkNewFieldName(fieldName); + boolean nullable = metadata.defaultNullable; + if (containsAttribute(attributes, FieldAttribute.REQUIRED)) { + nullable = false; + } + + long columnIndex = table.addColumn(metadata.realmType, fieldName, nullable); + try { + addModifiers(fieldName, attributes); + } catch (Exception e) { + // Modifiers have been removed by the addModifiers method() + table.removeColumn(columnIndex); + throw e; + } + return this; + } + + /** + * Adds a new field that references another {@link RealmObject}. + * + * @param fieldName name of the field to add. + * @param objectSchema schema for the Realm type being referenced. + * @return the updated schema. + * @throws IllegalArgumentException if field name is illegal or a field with that name already exists. + */ + @Override + public StandardRealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema) { + checkLegalName(fieldName); + checkFieldNameIsAvailable(fieldName); + table.addColumnLink(RealmFieldType.OBJECT, fieldName, realm.sharedRealm.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); + return this; + } + + /** + * Adds a new field that references a {@link RealmList}. + * + * @param fieldName name of the field to add. + * @param objectSchema schema for the Realm type being referenced. + * @return the updated schema. + * @throws IllegalArgumentException if the field name is illegal or a field with that name already exists. + */ + @Override + public StandardRealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema) { + checkLegalName(fieldName); + checkFieldNameIsAvailable(fieldName); + table.addColumnLink(RealmFieldType.LIST, fieldName, realm.sharedRealm.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); + return this; + } + + /** + * Removes a field from the class. + * + * @param fieldName field name to remove. + * @return the updated schema. + * @throws IllegalArgumentException if field name doesn't exist. + */ + @Override + public StandardRealmObjectSchema removeField(String fieldName) { + realm.checkNotInSync(); // destructive modification of a schema is not permitted + checkLegalName(fieldName); + if (!hasField(fieldName)) { + throw new IllegalStateException(fieldName + " does not exist."); + } + long columnIndex = getColumnIndex(fieldName); + if (table.getPrimaryKey() == columnIndex) { + table.setPrimaryKey(null); + } + table.removeColumn(columnIndex); + return this; + } + + /** + * Renames a field from one name to another. + * + * @param currentFieldName field name to rename. + * @param newFieldName the new field name. + * @return the updated schema. + * @throws IllegalArgumentException if field name doesn't exist or if the new field name already exists. + */ + @Override + public StandardRealmObjectSchema renameField(String currentFieldName, String newFieldName) { + realm.checkNotInSync(); // destructive modification of a schema is not permitted + checkLegalName(currentFieldName); + checkFieldExists(currentFieldName); + checkLegalName(newFieldName); + checkFieldNameIsAvailable(newFieldName); + long columnIndex = getColumnIndex(currentFieldName); + table.renameColumn(columnIndex, newFieldName); + + // ATTENTION: We don't need to re-set the PK table here since the column index won't be changed when renaming. + + return this; + } + + /** + * Tests if the class has field defined with the given name. + * + * @param fieldName field name to test. + * @return {@code true} if the field exists, {@code false} otherwise. + */ + @Override + public boolean hasField(String fieldName) { + return table.getColumnIndex(fieldName) != Table.NO_MATCH; + } + + /** + * Adds an index to a given field. This is the equivalent of adding the {@link io.realm.annotations.Index} + * annotation on the field. + * + * @param fieldName field to add index to. + * @return the updated schema. + * @throws IllegalArgumentException if field name doesn't exist, the field cannot be indexed or it already has a + * index defined. + */ + @Override + public StandardRealmObjectSchema addIndex(String fieldName) { + checkLegalName(fieldName); + checkFieldExists(fieldName); + long columnIndex = getColumnIndex(fieldName); + if (table.hasSearchIndex(columnIndex)) { + throw new IllegalStateException(fieldName + " already has an index."); + } + table.addSearchIndex(columnIndex); + return this; + } + + /** + * Checks if a given field has an index defined. + * + * @param fieldName existing field name to check. + * @return {@code true} if field is indexed, {@code false} otherwise. + * @throws IllegalArgumentException if field name doesn't exist. + * @see io.realm.annotations.Index + */ + @Override + public boolean hasIndex(String fieldName) { + checkLegalName(fieldName); + checkFieldExists(fieldName); + return table.hasSearchIndex(table.getColumnIndex(fieldName)); + } + + + /** + * Removes an index from a given field. This is the same as removing the {@code @Index} annotation on the field. + * + * @param fieldName field to remove index from. + * @return the updated schema. + * @throws IllegalArgumentException if field name doesn't exist or the field doesn't have an index. + */ + @Override + public StandardRealmObjectSchema removeIndex(String fieldName) { + realm.checkNotInSync(); // Destructive modifications are not permitted. + checkLegalName(fieldName); + checkFieldExists(fieldName); + long columnIndex = getColumnIndex(fieldName); + if (!table.hasSearchIndex(columnIndex)) { + throw new IllegalStateException("Field is not indexed: " + fieldName); + } + table.removeSearchIndex(columnIndex); + return this; + } + + /** + * Adds a primary key to a given field. This is the same as adding the {@link io.realm.annotations.PrimaryKey} + * annotation on the field. Further, this implicitly adds {@link io.realm.annotations.Index} annotation to the field + * as well. + * + * @param fieldName field to set as primary key. + * @return the updated schema. + * @throws IllegalArgumentException if field name doesn't exist, the field cannot be a primary key or it already + * has a primary key defined. + */ + @Override + public StandardRealmObjectSchema addPrimaryKey(String fieldName) { + checkLegalName(fieldName); + checkFieldExists(fieldName); + if (table.hasPrimaryKey()) { + throw new IllegalStateException("A primary key is already defined"); + } + table.setPrimaryKey(fieldName); + long columnIndex = getColumnIndex(fieldName); + if (!table.hasSearchIndex(columnIndex)) { + // No exception will be thrown since adding PrimaryKey implies the column has an index. + table.addSearchIndex(columnIndex); + } + return this; + } + + /** + * Removes the primary key from this class. This is the same as removing the {@link io.realm.annotations.PrimaryKey} + * annotation from the class. Further, this implicitly removes {@link io.realm.annotations.Index} annotation from + * the field as well. + * + * @return the updated schema. + * @throws IllegalArgumentException if the class doesn't have a primary key defined. + */ + @Override + public StandardRealmObjectSchema removePrimaryKey() { + realm.checkNotInSync(); // Destructive modifications are not permitted. + if (!table.hasPrimaryKey()) { + throw new IllegalStateException(getClassName() + " doesn't have a primary key."); + } + long columnIndex = table.getPrimaryKey(); + if (table.hasSearchIndex(columnIndex)) { + table.removeSearchIndex(columnIndex); + } + table.setPrimaryKey(""); + return this; + } + + /** + * Sets a field to be required i.e., it is not allowed to hold {@code null} values. This is equivalent to switching + * between boxed types and their primitive variant e.g., {@code Integer} to {@code int}. + * + * @param fieldName name of field in the class. + * @param required {@code true} if field should be required, {@code false} otherwise. + * @return the updated schema. + * @throws IllegalArgumentException if the field name doesn't exist, cannot have the {@link Required} annotation or + * the field already have been set as required. + * @see Required + */ + @Override + public StandardRealmObjectSchema setRequired(String fieldName, boolean required) { + long columnIndex = table.getColumnIndex(fieldName); + boolean currentColumnRequired = isRequired(fieldName); + RealmFieldType type = table.getColumnType(columnIndex); + + if (type == RealmFieldType.OBJECT) { + throw new IllegalArgumentException("Cannot modify the required state for RealmObject references: " + fieldName); + } + if (type == RealmFieldType.LIST) { + throw new IllegalArgumentException("Cannot modify the required state for RealmList references: " + fieldName); + } + if (required && currentColumnRequired) { + throw new IllegalStateException("Field is already required: " + fieldName); + } + if (!required && !currentColumnRequired) { + throw new IllegalStateException("Field is already nullable: " + fieldName); + } + + if (required) { + table.convertColumnToNotNullable(columnIndex); + } else { + table.convertColumnToNullable(columnIndex); + } + return this; + } + + /** + * Sets a field to be nullable i.e., it should be able to hold {@code null} values. This is equivalent to switching + * between primitive types and their boxed variant e.g., {@code int} to {@code Integer}. + * + * @param fieldName name of field in the class. + * @param nullable {@code true} if field should be nullable, {@code false} otherwise. + * @return the updated schema. + * @throws IllegalArgumentException if the field name doesn't exist, or cannot be set as nullable. + */ + @Override + public StandardRealmObjectSchema setNullable(String fieldName, boolean nullable) { + setRequired(fieldName, !nullable); + return this; + } + + /** + * Checks if a given field is required i.e., it is not allowed to contain {@code null} values. + * + * @param fieldName field to check. + * @return {@code true} if it is required, {@code false} otherwise. + * @throws IllegalArgumentException if field name doesn't exist. + * @see #setRequired(String, boolean) + */ + @Override + public boolean isRequired(String fieldName) { + long columnIndex = getColumnIndex(fieldName); + return !table.isColumnNullable(columnIndex); + } + + /** + * Checks if a given field is nullable i.e., it is allowed to contain {@code null} values. + * + * @param fieldName field to check. + * @return {@code true} if it is required, {@code false} otherwise. + * @throws IllegalArgumentException if field name doesn't exist. + * @see #setNullable(String, boolean) + */ + @Override + public boolean isNullable(String fieldName) { + long columnIndex = getColumnIndex(fieldName); + return table.isColumnNullable(columnIndex); + } + + /** + * Checks if a given field is the primary key field. + * + * @param fieldName field to check. + * @return {@code true} if it is the primary key field, {@code false} otherwise. + * @throws IllegalArgumentException if field name doesn't exist. + * @see #addPrimaryKey(String) + */ + @Override + public boolean isPrimaryKey(String fieldName) { + long columnIndex = getColumnIndex(fieldName); + return columnIndex == table.getPrimaryKey(); + } + + /** + * Checks if the class has a primary key defined. + * + * @return {@code true} if a primary key is defined, {@code false} otherwise. + * @see io.realm.annotations.PrimaryKey + */ + @Override + public boolean hasPrimaryKey() { + return table.hasPrimaryKey(); + } + + /** + * Returns the name of the primary key field. + * + * @return the name of the primary key field. + * @throws IllegalStateException if the class doesn't have a primary key defined. + */ + @Override + public String getPrimaryKey() { + if (!table.hasPrimaryKey()) { + throw new IllegalStateException(getClassName() + " doesn't have a primary key."); + } + return table.getColumnName(table.getPrimaryKey()); + } + + /** + * Returns all fields in this class. + * + * @return a list of all the fields in this class. + */ + @Override + public Set getFieldNames() { + int columnCount = (int) table.getColumnCount(); + Set columnNames = new LinkedHashSet<>(columnCount); + for (int i = 0; i < columnCount; i++) { + columnNames.add(table.getColumnName(i)); + } + return columnNames; + } + + /** + * Runs a transformation function on each RealmObject instance of the current class. The object will be represented + * as a {@link DynamicRealmObject}. + * + * @return this schema. + */ + @Override + public StandardRealmObjectSchema transform(Function function) { + if (function != null) { + long size = table.size(); + for (long i = 0; i < size; i++) { + function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i))); + } + } + + return this; + } + + /** + * Returns the type used by the underlying storage engine to represent this field. + * + * @return the underlying type used by Realm to represent this field. + */ + @Override + public RealmFieldType getFieldType(String fieldName) { + long columnIndex = getColumnIndex(fieldName); + return table.getColumnType(columnIndex); + } + + @Override + StandardRealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { + long columnIndex = table.addColumn(type, name, (required) ? Table.NOT_NULLABLE : Table.NULLABLE); + + if (indexed) { table.addSearchIndex(columnIndex); } + + if (primary) { table.setPrimaryKey(name); } + + return this; + } + + @Override + StandardRealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linkedTo) { + table.addColumnLink( + type, + name, + realm.getSharedRealm().getTable(StandardRealmSchema.TABLE_PREFIX + linkedTo.getClassName())); + return this; + } + + /** + * Returns the column indices for the given field name. If a linked field is defined, the column index for + * each field is returned. + * + * @param fieldDescription fieldName or link path to a field name. + * @param validColumnTypes valid field type for the last field in a linked field + * @return list of column indices. + */ + // TODO: consider another caching strategy so linked classes are included in the cache. + @Override + long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes) { + if (fieldDescription == null || fieldDescription.equals("")) { + throw new IllegalArgumentException("Invalid query: field name is empty"); + } + if (fieldDescription.endsWith(".")) { + throw new IllegalArgumentException("Invalid query: field name must not end with a period ('.')"); + } + String[] names = fieldDescription.split("\\."); + + //final RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); + + long[] columnIndices = new long[names.length]; + Table currentTable = table; + RealmFieldType columnType; + String columnName; + String tableName; + for (int i = 0; /* loop exits in the middle */ ; i++) { + columnName = names[i]; + if (columnName.length() <= 0) { + throw new IllegalArgumentException(String.format( + "Invalid query: empty column name in field '%s'. " + + "A field name must not begin with, end with, or contain adjacent periods ('.').", + fieldDescription)); + } + + tableName = getTableName(currentTable); + long index = currentTable.getColumnIndex(columnName); + if (index < 0) { + throw new IllegalArgumentException( + String.format("Invalid query: field '%s' does not exist in table '%s'.", + columnName, tableName)); + } + columnIndices[i] = index; + + columnType = currentTable.getColumnType(index); + + if (i >= names.length - 1) { break; } + + if ((columnType != RealmFieldType.OBJECT) && (columnType != RealmFieldType.LIST)) { + throw new IllegalArgumentException( + String.format("Invalid query: field '%s' in table '%s' is of type '%s'. It must be a LIST or OBJECT type.", + columnName, tableName, columnType.toString())); + } + + currentTable = currentTable.getLinkTarget(index); + } + + if ((validColumnTypes != null) && (validColumnTypes.length > 0) && !isValidType(columnType, validColumnTypes)) { + throw new IllegalArgumentException( + String.format("Invalid query: field '%s' in table '%s' is of invalid type '%s'.", + columnName, tableName, columnType.toString())); + } + + return columnIndices; + } + + /** + * Returns the column index in the underlying table for the given field name. + * + * @param fieldName field name to find index for. + * @return column index or null if it doesn't exists. + */ + Long getFieldIndex(String fieldName) { + return columnIndices.get(fieldName); + } + + /** + * Returns the column index in the underlying table for the given field name. + * + * @param fieldName field name to find index for. + * @return column index. + * @throws IllegalArgumentException if the field does not exists. + */ + long getAndCheckFieldIndex(String fieldName) { + Long index = columnIndices.get(fieldName); + if (index == null) { + throw new IllegalArgumentException("Field does not exist: " + fieldName); + } + return index; + } + + private String getTableName(Table table) { + return table.getName().substring(StandardRealmSchema.TABLE_PREFIX.length()); + } + + // Invariant: Field was just added. This method is responsible for cleaning up attributes if it fails. + private void addModifiers(String fieldName, FieldAttribute[] attributes) { + boolean indexAdded = false; + try { + if (attributes != null && attributes.length > 0) { + if (containsAttribute(attributes, FieldAttribute.INDEXED)) { + addIndex(fieldName); + indexAdded = true; + } + + if (containsAttribute(attributes, FieldAttribute.PRIMARY_KEY)) { + // Note : adding primary key implies application of FieldAttribute.INDEXED attribute. + addPrimaryKey(fieldName); + indexAdded = true; + } + + // REQUIRED is being handled when adding the column using addField through the nullable parameter. + } + } catch (Exception e) { + // If something went wrong, revert all attributes. + long columnIndex = getColumnIndex(fieldName); + if (indexAdded) { + table.removeSearchIndex(columnIndex); + } + throw (RuntimeException) e; + } + } + + private boolean containsAttribute(FieldAttribute[] attributeList, FieldAttribute attribute) { + if (attributeList == null || attributeList.length == 0) { + return false; + } + for (int i = 0; i < attributeList.length; i++) { + if (attributeList[i] == attribute) { + return true; + } + } + return false; + } + + private void checkNewFieldName(String fieldName) { + checkLegalName(fieldName); + checkFieldNameIsAvailable(fieldName); + } + + private void checkLegalName(String fieldName) { + if (fieldName == null || fieldName.isEmpty()) { + throw new IllegalArgumentException("Field name can not be null or empty"); + } + if (fieldName.contains(".")) { + throw new IllegalArgumentException("Field name can not contain '.'"); + } + } + + private void checkFieldNameIsAvailable(String fieldName) { + if (table.getColumnIndex(fieldName) != Table.NO_MATCH) { + throw new IllegalArgumentException("Field already exists in '" + getClassName() + "': " + fieldName); + } + } + + private void checkFieldExists(String fieldName) { + if (table.getColumnIndex(fieldName) == Table.NO_MATCH) { + throw new IllegalArgumentException("Field name doesn't exist on object '" + getClassName() + "': " + fieldName); + } + } + + private long getColumnIndex(String fieldName) { + long columnIndex = table.getColumnIndex(fieldName); + if (columnIndex == -1) { + throw new IllegalArgumentException( + String.format("Field name '%s' does not exist on schema for '%s", + fieldName, getClassName() + )); + } + return columnIndex; + } + + private void checkEmpty(String str) { + if (str == null || str.isEmpty()) { + throw new IllegalArgumentException("Null or empty class names are not allowed"); + } + } + + private boolean isValidType(RealmFieldType columnType, RealmFieldType[] validColumnTypes) { + for (int i = 0; i < validColumnTypes.length; i++) { + if (validColumnTypes[i] == columnType) { + return true; + } + } + return false; + } + + public static final class DynamicColumnMap implements Map { + private final Table table; + + DynamicColumnMap(Table table) { + this.table = table; + } + + @Override + public Long get(Object key) { + long ret = table.getColumnIndex((String) key); + return ret < 0 ? null : ret; + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsKey(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + throw new UnsupportedOperationException(); + } + + @Override + public Long put(String key, Long value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public Long remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java index 9b224c9d44..3d3933d451 100644 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Realm Inc. + * Copyright 2017 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,9 +33,9 @@ * * @see io.realm.RealmMigration */ -public class StandardRealmSchema extends RealmSchema { +class StandardRealmSchema extends RealmSchema { - private static final String TABLE_PREFIX = Table.TABLE_PREFIX; + static final String TABLE_PREFIX = Table.TABLE_PREFIX; private static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; // Caches Dynamic Class objects given as Strings to Realm Tables @@ -43,9 +43,9 @@ public class StandardRealmSchema extends RealmSchema { // Caches Class objects (both model classes and proxy classes) to Realm Tables private final Map, Table> classToTable = new HashMap<>(); // Caches Class objects (both model classes and proxy classes) to their Schema object - private final Map, RealmObjectSchema> classToSchema = new HashMap<>(); + private final Map, StandardRealmObjectSchema> classToSchema = new HashMap<>(); // Caches Class Strings to their Schema object - private final Map dynamicClassToSchema = new HashMap<>(); + private final Map dynamicClassToSchema = new HashMap<>(); private final BaseRealm realm; @@ -73,12 +73,12 @@ public RealmObjectSchema get(String className) { if (!realm.getSharedRealm().hasTable(internalClassName)) { return null; } Table table = realm.getSharedRealm().getTable(internalClassName); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - return new RealmObjectSchema(realm, table, columnIndices); + StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); + return new StandardRealmObjectSchema(realm, table, columnIndices); } /** - * Returns the {@link RealmObjectSchema} for all RealmObject classes that can be saved in this Realm. + * Returns the {@link StandardRealmObjectSchema} for all RealmObject classes that can be saved in this Realm. * * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ @@ -92,8 +92,8 @@ public Set getAll() { continue; } Table table = realm.getSharedRealm().getTable(tableName); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - schemas.add(new RealmObjectSchema(realm, table, columnIndices)); + StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); + schemas.add(new StandardRealmObjectSchema(realm, table, columnIndices)); } return schemas; } @@ -117,8 +117,8 @@ public RealmObjectSchema create(String className) { throw new IllegalArgumentException("Class already exists: " + className); } Table table = realm.getSharedRealm().getTable(internalTableName); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - return new RealmObjectSchema(realm, table, columnIndices); + StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); + return new StandardRealmObjectSchema(realm, table, columnIndices); } /** @@ -138,6 +138,7 @@ public boolean contains(String className) { * * @param className name of the class to remove. */ + @Override public void remove(String className) { realm.checkNotInSync(); // Destructive modifications are not permitted. checkEmpty(className, EMPTY_STRING_MSG); @@ -157,6 +158,7 @@ public void remove(String className) { * @param newClassName new class name. * @return a schema object for renamed class. */ + @Override public RealmObjectSchema rename(String oldClassName, String newClassName) { realm.checkNotInSync(); // Destructive modifications are not permitted. checkEmpty(oldClassName, "Class names cannot be empty or null"); @@ -184,8 +186,8 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { table.setPrimaryKey(pkField); } - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - return new RealmObjectSchema(realm, table, columnIndices); + StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); + return new StandardRealmObjectSchema(realm, table, columnIndices); } private void checkEmpty(String str, String error) { @@ -236,8 +238,8 @@ Table getTable(Class clazz) { return table; } - RealmObjectSchema getSchemaForClass(Class clazz) { - RealmObjectSchema classSchema = classToSchema.get(clazz); + StandardRealmObjectSchema getSchemaForClass(Class clazz) { + StandardRealmObjectSchema classSchema = classToSchema.get(clazz); if (classSchema != null) { return classSchema; } Class originalClass = Util.getOriginalModelClass(clazz); @@ -247,7 +249,7 @@ RealmObjectSchema getSchemaForClass(Class clazz) { } if (classSchema == null) { Table table = getTable(clazz); - classSchema = new RealmObjectSchema(realm, table, getColumnInfo(originalClass).getIndicesMap()); + classSchema = new StandardRealmObjectSchema(realm, table, getColumnInfo(originalClass).getIndicesMap()); classToSchema.put(originalClass, classSchema); } if (isProxyClass(originalClass, clazz)) { @@ -257,16 +259,16 @@ RealmObjectSchema getSchemaForClass(Class clazz) { return classSchema; } - RealmObjectSchema getSchemaForClass(String className) { + StandardRealmObjectSchema getSchemaForClass(String className) { className = Table.TABLE_PREFIX + className; - RealmObjectSchema dynamicSchema = dynamicClassToSchema.get(className); + StandardRealmObjectSchema dynamicSchema = dynamicClassToSchema.get(className); if (dynamicSchema == null) { if (!realm.getSharedRealm().hasTable(className)) { throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); } Table table = realm.getSharedRealm().getTable(className); - RealmObjectSchema.DynamicColumnMap columnIndices = new RealmObjectSchema.DynamicColumnMap(table); - dynamicSchema = new RealmObjectSchema(realm, table, columnIndices); + StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); + dynamicSchema = new StandardRealmObjectSchema(realm, table, columnIndices); dynamicClassToSchema.put(className, dynamicSchema); } return dynamicSchema; diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index d2c7319f5c..50c2c72983 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -54,14 +54,6 @@ public abstract class RealmProxyMediator { */ public abstract RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema realmSchema); - /** - * Creates the backing table in Realm for the given RealmObject class. - * - * @param clazz the {@link RealmObject} model class to create backing table for. - * @param sharedRealm the wrapper object of underlying native database. - */ - public abstract Table createTable(Class clazz, SharedRealm sharedRealm); - /** * Validates the backing table in Realm for the given RealmObject class. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index ad81a72afd..6021c88720 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -100,43 +100,43 @@ public TableQuery not() { // Queries for integer values. - public TableQuery equalTo(long columnIndexes[], long value) { + public TableQuery equalTo(long[] columnIndexes, long value) { nativeEqual(nativePtr, columnIndexes, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long columnIndex[], long value) { + public TableQuery notEqualTo(long[] columnIndex, long value) { nativeNotEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery greaterThan(long columnIndex[], long value) { + public TableQuery greaterThan(long[] columnIndex, long value) { nativeGreater(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long columnIndex[], long value) { + public TableQuery greaterThanOrEqual(long[] columnIndex, long value) { nativeGreaterEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery lessThan(long columnIndex[], long value) { + public TableQuery lessThan(long[] columnIndex, long value) { nativeLess(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long columnIndex[], long value) { + public TableQuery lessThanOrEqual(long[] columnIndex, long value) { nativeLessEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery between(long columnIndex[], long value1, long value2) { + public TableQuery between(long[] columnIndex, long value1, long value2) { nativeBetween(nativePtr, columnIndex, value1, value2); queryValidated = false; return this; @@ -144,43 +144,43 @@ public TableQuery between(long columnIndex[], long value1, long value2) { // Queries for float values. - public TableQuery equalTo(long columnIndex[], float value) { + public TableQuery equalTo(long[] columnIndex, float value) { nativeEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long columnIndex[], float value) { + public TableQuery notEqualTo(long[] columnIndex, float value) { nativeNotEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery greaterThan(long columnIndex[], float value) { + public TableQuery greaterThan(long[] columnIndex, float value) { nativeGreater(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long columnIndex[], float value) { + public TableQuery greaterThanOrEqual(long[] columnIndex, float value) { nativeGreaterEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery lessThan(long columnIndex[], float value) { + public TableQuery lessThan(long[] columnIndex, float value) { nativeLess(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long columnIndex[], float value) { + public TableQuery lessThanOrEqual(long[] columnIndex, float value) { nativeLessEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery between(long columnIndex[], float value1, float value2) { + public TableQuery between(long[] columnIndex, float value1, float value2) { nativeBetween(nativePtr, columnIndex, value1, value2); queryValidated = false; return this; @@ -188,43 +188,43 @@ public TableQuery between(long columnIndex[], float value1, float value2) { // Queries for double values. - public TableQuery equalTo(long columnIndex[], double value) { + public TableQuery equalTo(long[] columnIndex, double value) { nativeEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long columnIndex[], double value) { + public TableQuery notEqualTo(long[] columnIndex, double value) { nativeNotEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery greaterThan(long columnIndex[], double value) { + public TableQuery greaterThan(long[] columnIndex, double value) { nativeGreater(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long columnIndex[], double value) { + public TableQuery greaterThanOrEqual(long[] columnIndex, double value) { nativeGreaterEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery lessThan(long columnIndex[], double value) { + public TableQuery lessThan(long[] columnIndex, double value) { nativeLess(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long columnIndex[], double value) { + public TableQuery lessThanOrEqual(long[] columnIndex, double value) { nativeLessEqual(nativePtr, columnIndex, value); queryValidated = false; return this; } - public TableQuery between(long columnIndex[], double value1, double value2) { + public TableQuery between(long[] columnIndex, double value1, double value2) { nativeBetween(nativePtr, columnIndex, value1, value2); queryValidated = false; return this; @@ -232,7 +232,7 @@ public TableQuery between(long columnIndex[], double value1, double value2) { // Query for boolean values. - public TableQuery equalTo(long columnIndex[], boolean value) { + public TableQuery equalTo(long[] columnIndex, boolean value) { nativeEqual(nativePtr, columnIndex, value); queryValidated = false; return this; @@ -240,9 +240,9 @@ public TableQuery equalTo(long columnIndex[], boolean value) { // Queries for Date values. - private final static String DATE_NULL_ERROR_MESSAGE = "Date value in query criteria must not be null."; + private static final String DATE_NULL_ERROR_MESSAGE = "Date value in query criteria must not be null."; - public TableQuery equalTo(long columnIndex[], Date value) { + public TableQuery equalTo(long[] columnIndex, Date value) { if (value == null) { nativeIsNull(nativePtr, columnIndex); } else { @@ -252,42 +252,42 @@ public TableQuery equalTo(long columnIndex[], Date value) { return this; } - public TableQuery notEqualTo(long columnIndex[], Date value) { + public TableQuery notEqualTo(long[] columnIndex, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeNotEqualTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery greaterThan(long columnIndex[], Date value) { + public TableQuery greaterThan(long[] columnIndex, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeGreaterTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long columnIndex[], Date value) { + public TableQuery greaterThanOrEqual(long[] columnIndex, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeGreaterEqualTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery lessThan(long columnIndex[], Date value) { + public TableQuery lessThan(long[] columnIndex, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeLessTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long columnIndex[], Date value) { + public TableQuery lessThanOrEqual(long[] columnIndex, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } nativeLessEqualTimestamp(nativePtr, columnIndex, value.getTime()); queryValidated = false; return this; } - public TableQuery between(long columnIndex[], Date value1, Date value2) { + public TableQuery between(long[] columnIndex, Date value1, Date value2) { if (value1 == null || value2 == null) { throw new IllegalArgumentException("Date values in query criteria must not be null."); // Different text } @@ -312,7 +312,7 @@ public TableQuery notEqualTo(long[] columnIndices, byte[] value) { // Query for String values. - private final static String STRING_NULL_ERROR_MESSAGE = "String value in query criteria must not be null."; + private static final String STRING_NULL_ERROR_MESSAGE = "String value in query criteria must not be null."; // Equals public TableQuery equalTo(long[] columnIndexes, String value, Case caseSensitive) { @@ -328,61 +328,61 @@ public TableQuery equalTo(long[] columnIndexes, String value) { } // Not Equals - public TableQuery notEqualTo(long columnIndex[], String value, Case caseSensitive) { + public TableQuery notEqualTo(long[] columnIndex, String value, Case caseSensitive) { nativeNotEqual(nativePtr, columnIndex, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery notEqualTo(long columnIndex[], String value) { + public TableQuery notEqualTo(long[] columnIndex, String value) { nativeNotEqual(nativePtr, columnIndex, value, true); queryValidated = false; return this; } - public TableQuery beginsWith(long columnIndices[], String value, Case caseSensitive) { + public TableQuery beginsWith(long[] columnIndices, String value, Case caseSensitive) { nativeBeginsWith(nativePtr, columnIndices, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery beginsWith(long columnIndices[], String value) { + public TableQuery beginsWith(long[] columnIndices, String value) { nativeBeginsWith(nativePtr, columnIndices, value, true); queryValidated = false; return this; } - public TableQuery endsWith(long columnIndices[], String value, Case caseSensitive) { + public TableQuery endsWith(long[] columnIndices, String value, Case caseSensitive) { nativeEndsWith(nativePtr, columnIndices, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery endsWith(long columnIndices[], String value) { + public TableQuery endsWith(long[] columnIndices, String value) { nativeEndsWith(nativePtr, columnIndices, value, true); queryValidated = false; return this; } - public TableQuery like(long columnIndices[], String value, Case caseSensitive) { + public TableQuery like(long[] columnIndices, String value, Case caseSensitive) { nativeLike(nativePtr, columnIndices, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery like(long columnIndices[], String value) { + public TableQuery like(long[] columnIndices, String value) { nativeLike(nativePtr, columnIndices, value, true); queryValidated = false; return this; } - public TableQuery contains(long columnIndices[], String value, Case caseSensitive) { + public TableQuery contains(long[] columnIndices, String value, Case caseSensitive) { nativeContains(nativePtr, columnIndices, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery contains(long columnIndices[], String value) { + public TableQuery contains(long[] columnIndices, String value) { nativeContains(nativePtr, columnIndices, value, true); queryValidated = false; return this; @@ -604,13 +604,13 @@ public Date minimumDate(long columnIndex) { } // isNull and isNotNull - public TableQuery isNull(long columnIndices[]) { + public TableQuery isNull(long[] columnIndices) { nativeIsNull(nativePtr, columnIndices); queryValidated = false; return this; } - public TableQuery isNotNull(long columnIndices[]) { + public TableQuery isNotNull(long[] columnIndices) { nativeIsNotNull(nativePtr, columnIndices); queryValidated = false; return this; @@ -660,63 +660,63 @@ private void throwImmutable() { private native void nativeNot(long nativeQueryPtr); - private native void nativeEqual(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeNotEqual(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeGreater(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeGreater(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeGreaterEqual(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeGreaterEqual(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeLess(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeLess(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeLessEqual(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeLessEqual(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeBetween(long nativeQueryPtr, long columnIndex[], long value1, long value2); + private native void nativeBetween(long nativeQueryPtr, long[] columnIndex, long value1, long value2); - private native void nativeEqual(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, float value); - private native void nativeNotEqual(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, float value); - private native void nativeGreater(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeGreater(long nativeQueryPtr, long[] columnIndex, float value); - private native void nativeGreaterEqual(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeGreaterEqual(long nativeQueryPtr, long[] columnIndex, float value); - private native void nativeLess(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeLess(long nativeQueryPtr, long[] columnIndex, float value); - private native void nativeLessEqual(long nativeQueryPtr, long columnIndex[], float value); + private native void nativeLessEqual(long nativeQueryPtr, long[] columnIndex, float value); - private native void nativeBetween(long nativeQueryPtr, long columnIndex[], float value1, float value2); + private native void nativeBetween(long nativeQueryPtr, long[] columnIndex, float value1, float value2); - private native void nativeEqual(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, double value); - private native void nativeNotEqual(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, double value); - private native void nativeGreater(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeGreater(long nativeQueryPtr, long[] columnIndex, double value); - private native void nativeGreaterEqual(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeGreaterEqual(long nativeQueryPtr, long[] columnIndex, double value); - private native void nativeLess(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeLess(long nativeQueryPtr, long[] columnIndex, double value); - private native void nativeLessEqual(long nativeQueryPtr, long columnIndex[], double value); + private native void nativeLessEqual(long nativeQueryPtr, long[] columnIndex, double value); - private native void nativeBetween(long nativeQueryPtr, long columnIndex[], double value1, double value2); + private native void nativeBetween(long nativeQueryPtr, long[] columnIndex, double value1, double value2); - private native void nativeEqual(long nativeQueryPtr, long columnIndex[], boolean value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, boolean value); - private native void nativeEqualTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeNotEqualTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeNotEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeGreaterTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeGreaterTimestamp(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeGreaterEqualTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeGreaterEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeLessTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeLessTimestamp(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeLessEqualTimestamp(long nativeQueryPtr, long columnIndex[], long value); + private native void nativeLessEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long value); - private native void nativeBetweenTimestamp(long nativeQueryPtr, long columnIndex[], long value1, long value2); + private native void nativeBetweenTimestamp(long nativeQueryPtr, long[] columnIndex, long value1, long value2); private native void nativeEqual(long nativeQueryPtr, long[] columnIndices, byte[] value); @@ -724,15 +724,15 @@ private void throwImmutable() { private native void nativeEqual(long nativeQueryPtr, long[] columnIndexes, String value, boolean caseSensitive); - private native void nativeNotEqual(long nativeQueryPtr, long columnIndex[], String value, boolean caseSensitive); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, String value, boolean caseSensitive); - private native void nativeBeginsWith(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); + private native void nativeBeginsWith(long nativeQueryPtr, long[] columnIndices, String value, boolean caseSensitive); - private native void nativeEndsWith(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); + private native void nativeEndsWith(long nativeQueryPtr, long[] columnIndices, String value, boolean caseSensitive); - private native void nativeLike(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); + private native void nativeLike(long nativeQueryPtr, long[] columnIndices, String value, boolean caseSensitive); - private native void nativeContains(long nativeQueryPtr, long columnIndices[], String value, boolean caseSensitive); + private native void nativeContains(long nativeQueryPtr, long[] columnIndices, String value, boolean caseSensitive); private native void nativeIsEmpty(long nativePtr, long[] columnIndices); @@ -768,9 +768,9 @@ private void throwImmutable() { private native Long nativeMinimumTimestamp(long nativeQueryPtr, long columnIndex, long start, long end, long limit); - private native void nativeIsNull(long nativePtr, long columnIndices[]); + private native void nativeIsNull(long nativePtr, long[] columnIndices); - private native void nativeIsNotNull(long nativePtr, long columnIndices[]); + private native void nativeIsNotNull(long nativePtr, long[] columnIndices); private native long nativeCount(long nativeQueryPtr, long start, long end, long limit); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java index 48cdee87fb..c611cb6674 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java @@ -18,9 +18,9 @@ class TestUtil { - public native static long getMaxExceptionNumber(); + public static native long getMaxExceptionNumber(); - public native static String getExpectedMessage(long exceptionKind); + public static native String getExpectedMessage(long exceptionKind); - public native static void testThrowExceptions(long exceptionKind); + public static native void testThrowExceptions(long exceptionKind); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index dc410380d8..81218ae82e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -38,7 +38,6 @@ import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; import io.realm.internal.SharedRealm; -import io.realm.internal.Table; import io.realm.internal.Util; @@ -50,7 +49,7 @@ public class CompositeMediator extends RealmProxyMediator { private final Map, RealmProxyMediator> mediators; public CompositeMediator(RealmProxyMediator... mediators) { - final HashMap, RealmProxyMediator> tempMediators = new HashMap, RealmProxyMediator>(); + final HashMap, RealmProxyMediator> tempMediators = new HashMap<>(); if (mediators != null) { for (RealmProxyMediator mediator : mediators) { for (Class realmClass : mediator.getModelClasses()) { @@ -67,12 +66,6 @@ public RealmObjectSchema createRealmObjectSchema(Class cla return mediator.createRealmObjectSchema(clazz, schema); } - @Override - public Table createTable(Class clazz, SharedRealm sharedRealm) { - RealmProxyMediator mediator = getMediator(clazz); - return mediator.createTable(clazz, sharedRealm); - } - @Override public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm, boolean allowExtraColumns) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index f0cdbf22b8..3f4f6fa471 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -38,7 +38,6 @@ import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; import io.realm.internal.SharedRealm; -import io.realm.internal.Table; import io.realm.internal.Util; @@ -60,7 +59,7 @@ public class FilterableMediator extends RealmProxyMediator { public FilterableMediator(RealmProxyMediator originalMediator, Collection> allowedClasses) { this.originalMediator = originalMediator; - Set> tempAllowedClasses = new HashSet>(); + Set> tempAllowedClasses = new HashSet<>(); if (originalMediator != null) { Set> originalClasses = originalMediator.getModelClasses(); for (Class clazz : allowedClasses) { @@ -82,12 +81,6 @@ public RealmObjectSchema createRealmObjectSchema(Class cla return originalMediator.createRealmObjectSchema(clazz, schema); } - @Override - public Table createTable(Class clazz, SharedRealm sharedRealm) { - checkSchemaHasClass(clazz); - return originalMediator.createTable(clazz, sharedRealm); - } - @Override public ColumnInfo validateTable(Class clazz, SharedRealm sharedRealm, boolean allowExtraColumns) { From 6507daa0a74d631a2f33a6e469cce443a50328e4 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Tue, 4 Apr 2017 05:48:30 -0700 Subject: [PATCH 0594/2110] Resume sending update messages (#4419) Fixes #4418 --- .../src/main/java/io/realm/processor/RealmProcessor.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index e6f4b4d734..b4b9663ba6 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -134,7 +134,7 @@ public class RealmProcessor extends AbstractProcessor { private final Set backlinksToValidate = new HashSet(); private boolean hasProcessedModules = false; - private int round; + private int round = -1; @Override public SourceVersion getSupportedSourceVersion() { @@ -146,8 +146,7 @@ public boolean process(Set annotations, RoundEnvironment round++; if (round == 0) { - RealmVersionChecker updateChecker = RealmVersionChecker.getInstance(processingEnv); - updateChecker.executeRealmVersionUpdate(); + RealmVersionChecker.getInstance(processingEnv).executeRealmVersionUpdate(); } if (roundEnv.errorRaised()) { return true; } From 0aa7e531d30db2f03e88e37503cfad5a835b316f Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 5 Apr 2017 13:24:18 +0900 Subject: [PATCH 0595/2110] refactor internal method name in RealmSchema (#4429) --- .../androidTest/java/io/realm/RealmTests.java | 4 +-- .../src/main/java/io/realm/Realm.java | 8 +++--- .../src/main/java/io/realm/RealmCache.java | 2 +- .../src/main/java/io/realm/RealmSchema.java | 26 +++++++++++++++---- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index aaf75da32c..f95147ab65 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3694,7 +3694,7 @@ public void schemaIndexCacheIsUpdatedAfterSchemaChange() { // get the pre-update index for the "name" column. CatRealmProxy.CatColumnInfo catColumnInfo - = (CatRealmProxy.CatColumnInfo) realm.schema.getColumnIndices().getColumnInfo(Cat.class); + = (CatRealmProxy.CatColumnInfo) realm.schema.getColumnInfo(Cat.class); final long nameIndex = catColumnInfo.nameIndex; // Change the index of the column "name". @@ -3717,7 +3717,7 @@ public void execute(Realm realm) { assertNotEquals(nameIndex, nameIndexNew); // Verify that the index in the ColumnInfo has been updated. - catColumnInfo = (CatRealmProxy.CatColumnInfo) realm.schema.getColumnIndices().getColumnInfo(Cat.class); + catColumnInfo = (CatRealmProxy.CatColumnInfo) realm.schema.getColumnInfo(Cat.class); assertEquals(nameIndexNew.get(), catColumnInfo.nameIndex); assertEquals(nameIndexNew.get(), (long) catColumnInfo.getIndicesMap().get(Cat.FIELD_NAME)); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 06302c0736..4d870133bb 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -293,7 +293,7 @@ private static Realm createAndValidate(RealmConfiguration configuration, ColumnI if (columnIndices != null) { // Copies global cache as a Realm local indices cache. - realm.schema.setColumnIndices(columnIndices); + realm.schema.setInitialColumnIndices(columnIndices); } else { final boolean syncingConfig = configuration.isSyncConfiguration(); @@ -359,7 +359,7 @@ private static void initializeRealm(Realm realm) { columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } - realm.getSchema().setColumnIndices( + realm.getSchema().setInitialColumnIndices( (unversioned) ? configuration.getSchemaVersion() : currentVersion, columnInfoMap); @@ -425,7 +425,7 @@ private static void initializeSyncedRealm(Realm realm) { columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } - realm.getSchema().setColumnIndices( + realm.getSchema().setInitialColumnIndices( (unversioned) ? newVersion : currentVersion, columnInfoMap); @@ -1690,7 +1690,7 @@ ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) { cacheForCurrentVersion = createdGlobalCache = new ColumnIndices(currentSchemaVersion, map); } - schema.setColumnIndices(cacheForCurrentVersion, mediator); + schema.updateColumnIndices(cacheForCurrentVersion, mediator); return createdGlobalCache; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 27a63ac04a..b4cab8a8ec 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -164,7 +164,7 @@ static synchronized E createRealmOrGetFromCache(RealmConfi if (realmClass == Realm.class && refAndCount.globalCount == 0) { final BaseRealm realm = refAndCount.localRealm.get(); // Stores a copy of local ColumnIndices as a global cache. - RealmCache.storeColumnIndices(cache.typedColumnIndicesArray, realm.schema.getColumnIndices()); + RealmCache.storeColumnIndices(cache.typedColumnIndicesArray, realm.schema.cloneColumnIndices()); } // This is the first instance in current thread, increase the global count. refAndCount.globalCount++; diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index e7a42bbc7e..ea53642122 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -89,19 +89,35 @@ public abstract class RealmSchema { */ public abstract boolean contains(String className); - final void setColumnIndices(ColumnIndices columnIndices) { + final void setInitialColumnIndices(ColumnIndices columnIndices) { + if (this.columnIndices != null) { + throw new IllegalStateException("An instance of ColumnIndices is already set."); + } this.columnIndices = columnIndices.clone(); } - final void setColumnIndices(long version, Map, ColumnInfo> columnInfoMap) { + final void setInitialColumnIndices(long version, Map, ColumnInfo> columnInfoMap) { + if (this.columnIndices != null) { + throw new IllegalStateException("An instance of ColumnIndices is already set."); + } columnIndices = new ColumnIndices(version, columnInfoMap); } - void setColumnIndices(ColumnIndices cacheForCurrentVersion, RealmProxyMediator mediator) { - columnIndices.copyFrom(cacheForCurrentVersion, mediator); + /** + * Updates all {@link ColumnInfo} elements in {@code columnIndices}. + * + *

                      + * The ColumnInfo elements are shared between all {@link RealmObject}s created by the Realm instance + * which owns this RealmSchema. Updating them also means updating indices information in those {@link RealmObject}s. + * + * @param schemaVersion new schema version. + * @param mediator mediator for the Realm. + */ + void updateColumnIndices(ColumnIndices schemaVersion, RealmProxyMediator mediator) { + columnIndices.copyFrom(schemaVersion, mediator); } - final ColumnIndices getColumnIndices() { + final ColumnIndices cloneColumnIndices() { checkIndices(); return columnIndices.clone(); } From 013aaa935fcefa7f1a17d33438af8d838a0319fd Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 5 Apr 2017 15:17:18 +0900 Subject: [PATCH 0596/2110] update android gradle plugin to 2.3.1 (#4431) --- examples/build.gradle | 2 +- realm/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 32279e957a..af3fe297c8 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' diff --git a/realm/build.gradle b/realm/build.gradle index 53a4573382..b7c98a3e74 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.1' classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.novoda:gradle-android-command-plugin:1.3.0' From 4c9932f9e1ca1c6b24b8754f48a027137ccd508c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 5 Apr 2017 15:59:29 +0800 Subject: [PATCH 0597/2110] Fix OsRealmSchema leak (#4422) --- .../realm-library/src/main/java/io/realm/OsRealmSchema.java | 1 - realm/realm-library/src/main/java/io/realm/Realm.java | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java index f173f4da32..13097cf10a 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -90,7 +90,6 @@ public long getNativePtr() { return this.nativePtr; } - // THIS IS NEVER CALLED! // See BaseRealm uses a StandardRealmSchema, not a OsRealmSchema. @Override public void close() { diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 4d870133bb..6e747797eb 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -385,6 +385,7 @@ private static void initializeRealm(Realm realm) { // to prevent multi-process interaction while the Realm is initialized. private static void initializeSyncedRealm(Realm realm) { boolean commitChanges = false; + OsRealmSchema schema = null; try { realm.beginTransaction(); long currentVersion = realm.getVersion(); @@ -401,7 +402,7 @@ private static void initializeSyncedRealm(Realm realm) { } // Assumption: When SyncConfiguration then additive schema update mode. - final OsRealmSchema schema = new OsRealmSchema(schemaCreator); + schema = new OsRealmSchema(schemaCreator); long newVersion = configuration.getSchemaVersion(); // !!! FIXME: This appalling kludge is necessitated by current package structure/visiblity constraints. // It absolutely breaks encapsulation and needs to be fixed! @@ -439,6 +440,9 @@ private static void initializeSyncedRealm(Realm realm) { commitChanges = false; throw e; } finally { + if (schema != null) { + schema.close(); + } if (commitChanges) { realm.commitTransaction(); } else { From 5f0b522a31f054f3a0255c720687b66c88d10ac6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 5 Apr 2017 10:25:25 +0200 Subject: [PATCH 0598/2110] Fix changelog + set release date --- CHANGELOG.md | 50 +++++++++++++++----------------------------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea90d0b865..9c30235dce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,60 +1,40 @@ -## 3.1.0 (YYYY-MM-DD) +## 3.1.0 (2017-04-05) ### Breaking Changes -* Updated file format of Realm files. Existing Realm files will automatically be migrated to the new format when they are opened. +* Updated file format of Realm files. Existing Realm files will automatically be migrated to the new format when they are opened, but older versions of Realm cannot open these files. * [ObjectServer] Due to file format changes, Realm Object Server 1.3.0 or later is required. ### Enhancements -* The real `RealmMigrationNeededException` is now thrown instead of `IllegalArgumentException` if no migration is provided for a Realm that requires it. -* Partial implementation of `LinkingObjects`. There is documentation in `io.realm.annotations.LinkingObjects`. Internal docs are in `io.realm.processor.Backlink`. - * Queries on linking objects do not work. Queries like `were(...).equalTo("field.linkingObjects.id", 7).findAll()` are not yet supported. - * Linking objects are not yet supported on dynamic objects - * Migration for linking objects is not yet supported. +* Added support for reverse relationships through the `@LinkingObjects` annotation. See `io.realm.annotations.LinkingObjects` for documentation. + * This feature is in `@Beta`. + * Queries on linking objects do not work. Queries like `where(...).equalTo("field.linkingObjects.id", 7).findAll()` are not yet supported. * Backlink verification is incomplete. Evil code can cause native crashes. -* [ObjectServer] In case of a Client Reset, information about the location of the backed up Realm file is now reported through the `ErrorHandler` interface (#4080). -* [ObjectServer] Authentication URLs now automatically append `/auth` if no other path segment is set (#4370). * The listener on `RealmObject` will only be triggered if the object changes (#3894). -* Added `RealmObjectChangeListener` to get detailed information about `RealmObject` changes. - -### Bug Fixes - -* Crash with `LogicError` with `Bad version number` on notifier thread (#4369). - -### Deprecated - -### Internal - -* Using the Object Store's Session and SyncManager. -* Upgraded to Realm Sync 1.5.0. -* Upgraded to Realm Core 2.5.1. - - -## 3.0.1 (YYYY-MM-DD) - -### Enhancements - -* Now using Gradle 3.4.1 -* Now `targetSdkVersion` is 25. +* Added `RealmObjectChangeListener` interface that provide detailed information about `RealmObject` field changes. * Listeners on `RealmList` and `RealmResults` will be triggered immediately when the transaction is committed on the same thread (#4245). +* The real `RealmMigrationNeededException` is now thrown instead of `IllegalArgumentException` if no migration is provided for a Realm that requires it. * `RealmQuery.distinct()` can be performed on unindexed fields (#2285). +* `targetSdkVersion` is now 25. +* [ObjectServer] In case of a Client Reset, information about the location of the backed up Realm file is now reported through the `ErrorHandler` interface (#4080). +* [ObjectServer] Authentication URLs now automatically append `/auth` if no other path segment is set (#4370). ### Bug Fixes +* Crash with `LogicError` with `Bad version number` on notifier thread (#4369). * `Realm.migrateRealm(RealmConfiguration)` now fails correctly with an `IllegalArgumentException` if a `SyncConfiguration` is provided (#4075). * Fixed a potential cause for Realm file corruptions (never reported). * Add `@Override` annotation to proxy class accessors and stop using raw type in proxy classes in order to remove warnings from javac (#4329). * `findFirstAsync()` now returns an invalid object if there is no object matches the query condition instead of running the query repeatedly until it can find one (#4352). * [ObjectServer] Changing the log level after starting a session now works correctly (#4337). -### Deprecated - ### Internal -* Upgraded to Realm Sync 1.3.2. -* Upgraded to Realm Core 2.4.0. - +* Using the Object Store's Session and SyncManager. +* Upgraded to Realm Sync 1.5.0. +* Upgraded to Realm Core 2.5.1. +* Upgraded Gradle to 3.4.1 ## 3.0.0 (2017-02-28) From bace9419ac32ba528ca171a016ad22af8256e748 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 5 Apr 2017 10:27:08 +0200 Subject: [PATCH 0599/2110] Release v3.1.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 0628777500..a0cd9f0ccb 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-SNAPSHOT \ No newline at end of file +3.1.0 \ No newline at end of file From 5468ee03963dae8daefd173274a5459e60507a3c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 5 Apr 2017 10:27:08 +0200 Subject: [PATCH 0600/2110] Prepare next release v3.1.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a0cd9f0ccb..dde25ef08e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0 \ No newline at end of file +3.1.1-SNAPSHOT \ No newline at end of file From eddd4b83c4cb340674a1261cfc57e83a4f58d7fd Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 5 Apr 2017 17:59:01 +0900 Subject: [PATCH 0601/2110] remove com.neenbedankt.android-apt plugin from example projects. (#4432) Users don't need to use it anymore. --- examples/build.gradle | 1 - examples/jsonExample/build.gradle | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index af3fe297c8..df34fdd8a0 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -26,7 +26,6 @@ allprojects { classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' - classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } } diff --git a/examples/jsonExample/build.gradle b/examples/jsonExample/build.gradle index 1b9bd2a6a7..c46861f443 100644 --- a/examples/jsonExample/build.gradle +++ b/examples/jsonExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'android-command' apply plugin: 'realm-android' @@ -29,5 +28,5 @@ android { dependencies { provided 'org.projectlombok:lombok:1.16.6' - apt 'org.projectlombok:lombok:1.16.6' + annotationProcessor 'org.projectlombok:lombok:1.16.6' } From 69c741fab24e9242e792d2679474dffaca6283d1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 5 Apr 2017 11:45:22 +0200 Subject: [PATCH 0602/2110] Fix distribution package build issues. --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 870ada9c85..ddc7ff1dc6 100644 --- a/build.gradle +++ b/build.gradle @@ -195,6 +195,7 @@ task distributionPackage(type:Zip) { from('changelog.txt') from('LICENSE') from('version.txt') + from('realm.properties') from('realm/realm-library/build/libs') { include 'realm-android-${currentVersion}-javadoc.jar' into 'docs' From 981748636e5f867ae58342f87260c396d6be0414 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 5 Apr 2017 12:02:46 +0200 Subject: [PATCH 0603/2110] Prepare next dev iteration --- CHANGELOG.md | 3 +++ version.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c30235dce..ef54a97dd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 3.2.0 (YYYY-MM-DD) + + ## 3.1.0 (2017-04-05) ### Breaking Changes diff --git a/version.txt b/version.txt index dde25ef08e..4395ff5923 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.1-SNAPSHOT \ No newline at end of file +3.2.0-SNAPSHOT \ No newline at end of file From 15484f3aecf086933697ee81d9fc9d7b986c3b23 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 7 Apr 2017 16:35:19 +0800 Subject: [PATCH 0604/2110] Stale local ref crash with Object listener (#4442) The changed field string array needs to be reset after sending notifications. Fix #4437 --- CHANGELOG.md | 6 +++ .../java/io/realm/ObjectChangeSetTests.java | 50 ++++++++++++++++++- .../main/cpp/io_realm_internal_OsObject.cpp | 4 +- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c30235dce..5f450096c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.1.1 (YYYY-MM-DD) + +### Bug Fixes + +* Crash caused by Listeners on `RealmObject` getting triggered the 2nd time with different changed field (#4437). + ## 3.1.0 (2017-04-05) ### Breaking Changes diff --git a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java index c36aef4b56..947b197eea 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java @@ -18,8 +18,6 @@ import android.support.test.runner.AndroidJUnit4; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,6 +25,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import io.realm.entities.AllTypes; @@ -289,6 +288,53 @@ public void changeAllFields() { realm.commitTransaction(); } + // Relevant to https://github.com/realm/realm-java/issues/4437 + // When the object listener triggered at the 2nd time, the local ref m_field_names_array has not been reset and it + // contains an invalid local ref which has been released before. + @Test + @RunTestInLooperThread(before = PopulateOneAllTypes.class) + public void changeDifferentFieldOneAfterAnother() { + Realm realm = looperThread.realm; + AllTypes allTypes = realm.where(AllTypes.class).findFirst(); + final AtomicBoolean stringChanged = new AtomicBoolean(false); + final AtomicBoolean longChanged = new AtomicBoolean(false); + final AtomicBoolean floatChanged = new AtomicBoolean(false); + + allTypes.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(RealmModel object, ObjectChangeSet changeSet) { + assertEquals(1, changeSet.getChangedFields().length); + if (changeSet.isFieldChanged(AllTypes.FIELD_STRING)) { + assertFalse(stringChanged.get()); + stringChanged.set(true); + } else if (changeSet.isFieldChanged(AllTypes.FIELD_LONG)) { + assertFalse(longChanged.get()); + longChanged.set(true); + } else if (changeSet.isFieldChanged(AllTypes.FIELD_FLOAT)) { + assertTrue(stringChanged.get()); + assertTrue(longChanged.get()); + assertFalse(floatChanged.get()); + floatChanged.set(true); + looperThread.testComplete(); + } else { + fail(); + } + } + }); + + realm.beginTransaction(); + allTypes.setColumnString("42"); + realm.commitTransaction(); + + realm.beginTransaction(); + allTypes.setColumnLong(42); + realm.commitTransaction(); + + realm.beginTransaction(); + allTypes.setColumnFloat(42.0f); + realm.commitTransaction(); + } + @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void findFirstAsync_changeSetIsNullWhenQueryReturns() { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index c35c042672..95c3fa24c5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -122,8 +122,10 @@ struct ChangeCallback { m_wrapper->m_row_object_weak_ref.call_with_local_ref(env, [&](JNIEnv*, jobject row_obj) { static JavaMethod notify_change_listeners(env, row_obj, "notifyChangeListeners", "([Ljava/lang/String;)V"); - env->CallVoidMethod(row_obj, notify_change_listeners, m_field_names_array); + env->CallVoidMethod(row_obj, notify_change_listeners, m_deleted ? nullptr : m_field_names_array); }); + m_field_names_array = nullptr; + m_deleted = false; } void error(std::exception_ptr err) From 66ccab210a2eb34c925f199f8ed95d9d235d15e2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 7 Apr 2017 11:13:00 +0200 Subject: [PATCH 0605/2110] Add support for transient fields (#4436) --- CHANGELOG.md | 4 ++ .../realm/transformer/BytecodeModifier.groovy | 6 ++- .../realm/transformer/RealmTransformer.groovy | 2 +- .../io/realm/processor/ClassMetaData.java | 45 +++++++------------ .../realm/processor/RealmProcessorTest.java | 4 +- .../test/resources/some/test/Transient.java | 38 +--------------- .../java/io/realm/RealmAnnotationTests.java | 4 +- .../io/realm/entities/AnnotationTypes.java | 13 ++++++ .../src/main/java/io/realm/RealmObject.java | 4 +- 9 files changed, 48 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef54a97dd0..339513b318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 3.2.0 (YYYY-MM-DD) +### Enhancements + +* Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore' annotation (#4279). + ## 3.1.0 (2017-04-05) diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index d64572ea3c..2ad7183dd2 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -30,6 +30,10 @@ class BytecodeModifier { private static final Logger logger = LoggerFactory.getLogger('realm-logger') + static boolean isModelField(CtField field) { + return !field.hasAnnotation(Ignore.class) && !Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()) + } + /** * Adds Realm specific accessors to a model class. * All the declared fields will be associated with a getter and a setter. @@ -40,7 +44,7 @@ class BytecodeModifier { logger.debug " Realm: Adding accessors to ${clazz.simpleName}" def methods = clazz.getDeclaredMethods()*.name clazz.declaredFields.each { CtField field -> - if (!Modifier.isStatic(field.getModifiers()) && !field.hasAnnotation(Ignore.class)) { + if (isModelField(field)) { if (!methods.contains("realmGet\$${field.name}".toString())) { clazz.addMethod(CtNewMethod.getter("realmGet\$${field.name}", field)) } diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index d7ca24a76a..b02d1a637b 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -121,7 +121,7 @@ class RealmTransformer extends Transform { def allManagedFields = [] allModelClasses.each { allManagedFields.addAll(it.declaredFields.findAll { - !it.hasAnnotation(Ignore.class) && !Modifier.isStatic(it.getModifiers()) + BytecodeModifier.isModelField(it) }) } logger.debug "Managed Fields: ${allManagedFields*.name}" diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index 02b1e46921..474c39cc8f 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -248,7 +248,6 @@ public boolean generate() { if (!checkReferenceTypes()) { return false; } if (!checkDefaultConstructor()) { return false; } if (!checkForFinalFields()) { return false; } - if (!checkForTransientFields()) { return false; } if (!checkForVolatileFields()) { return false; } return true; // Meta data was successfully generated @@ -347,19 +346,6 @@ private boolean checkForFinalFields() { return true; } - private boolean checkForTransientFields() { - for (VariableElement field : fields) { - if (field.getModifiers().contains(Modifier.TRANSIENT)) { - Utils.error(String.format( - "Class \"%s\" contains illegal transient field \"%s\".", - className, - field.getSimpleName().toString())); - return false; - } - } - return true; - } - private boolean checkForVolatileFields() { for (VariableElement field : fields) { if (field.getModifiers().contains(Modifier.VOLATILE)) { @@ -374,40 +360,43 @@ private boolean checkForVolatileFields() { } private boolean categorizeField(Element element) { - VariableElement variableElement = (VariableElement) element; + VariableElement field = (VariableElement) element; // completely ignore any static fields - if (variableElement.getModifiers().contains(Modifier.STATIC)) { return true; } + if (field.getModifiers().contains(Modifier.STATIC)) { return true; } - if (variableElement.getAnnotation(Ignore.class) != null) { return true; } + // Ignore fields marked with @Ignore or if they are transient + if (field.getAnnotation(Ignore.class) != null || field.getModifiers().contains(Modifier.TRANSIENT)) { + return true; + } - if (variableElement.getAnnotation(Index.class) != null) { - if (!categorizeIndexField(element, variableElement)) { return false; } + if (field.getAnnotation(Index.class) != null) { + if (!categorizeIndexField(element, field)) { return false; } } - if (variableElement.getAnnotation(Required.class) != null) { - categorizeRequiredField(element, variableElement); + if (field.getAnnotation(Required.class) != null) { + categorizeRequiredField(element, field); } else { // The field doesn't have the @Required annotation. // Without @Required annotation, boxed types/RealmObject/Date/String/bytes should be added to // nullableFields. // RealmList and Primitive types are NOT nullable always. @Required annotation is not supported. - if (!Utils.isPrimitiveType(variableElement) && !Utils.isRealmList(variableElement)) { - nullableFields.add(variableElement); + if (!Utils.isPrimitiveType(field) && !Utils.isRealmList(field)) { + nullableFields.add(field); } } - if (variableElement.getAnnotation(PrimaryKey.class) != null) { - if (!categorizePrimaryKeyField(variableElement)) { return false; } + if (field.getAnnotation(PrimaryKey.class) != null) { + if (!categorizePrimaryKeyField(field)) { return false; } } // Check @LinkingObjects last since it is not allowed to be either @Index, @Required or @PrimaryKey - if (variableElement.getAnnotation(LinkingObjects.class) != null) { - return categorizeBacklinkField(variableElement); + if (field.getAnnotation(LinkingObjects.class) != null) { + return categorizeBacklinkField(field); } // Standard field that appear valid (more fine grained checks might fail later). - fields.add(variableElement); + fields.add(field); return true; } diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java index b888290450..1e43cb20b7 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java @@ -392,11 +392,11 @@ public void failOnFinalFields() throws Exception { } @Test - public void failOnTransientFields() throws Exception { + public void compileTransientFields() throws Exception { ASSERT.about(javaSource()) .that(transientModel) .processedWith(new RealmProcessor()) - .failsToCompile(); + .compilesWithoutError(); } @Test diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Transient.java b/realm/realm-annotations-processor/src/test/resources/some/test/Transient.java index c130b20f7f..e54e4a26d4 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/Transient.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/Transient.java @@ -23,18 +23,10 @@ public class Transient extends RealmObject { private int age; public String getName() { - return realmGet$name(); - } - - public void setName(String name) { - realmSet$name(name); - } - - public String realmGet$name() { return name; } - public void realmSet$name(String name) { + public void setName(String name) { this.name = name; } @@ -53,32 +45,4 @@ public void setAge(int age) { public void realmSet$age(int age) { this.age = age; } - - @Override - public String toString() { - return "Simple{" + - "name='" + name + '\'' + - ", age=" + age + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Simple simple = (Simple) o; - - if (age != simple.age) return false; - if (name != null ? !name.equals(simple.name) : simple.name != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + age; - return result; - } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java index 5b5084f004..3e18d70e5a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java @@ -54,6 +54,7 @@ public void setUp() { object.setNotIndexString("String 1"); object.setIndexString("String 2"); object.setIgnoreString("String 3"); + object.setTransientString("String 4"); realm.commitTransaction(); } @@ -67,7 +68,8 @@ public void tearDown() { @Test public void ignore() { Table table = realm.getTable(AnnotationTypes.class); - assertEquals(-1, table.getColumnIndex("ignoreString")); + assertEquals(-1, table.getColumnIndex(AnnotationTypes.FIELD_IGNORE_STRING)); + assertEquals(-1, table.getColumnIndex(AnnotationTypes.FIELD_TRANSIENT_STRING)); } // Tests if "index" annotation works with supported types. diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AnnotationTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AnnotationTypes.java index 9926b7bce6..894804edbb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AnnotationTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AnnotationTypes.java @@ -23,6 +23,11 @@ public class AnnotationTypes extends RealmObject { + public static final String FIELD_INDEX_STRING = "indexString"; + public static final String FIELD_NOT_INDEX_STRING = "notIndexString"; + public static final String FIELD_IGNORE_STRING= "ignoreString"; + public static final String FIELD_TRANSIENT_STRING = "transientString"; + @PrimaryKey private long id; @@ -33,6 +38,8 @@ public class AnnotationTypes extends RealmObject { @Ignore private String ignoreString; + private transient String transientString; + public long getId() { return id; } @@ -65,5 +72,11 @@ public void setIgnoreString(String ignoreString) { this.ignoreString = ignoreString; } + public String getTransientString() { + return transientString; + } + public void setTransientString(String transientString) { + this.transientString = transientString; + } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 74025c2544..0648341216 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -49,9 +49,9 @@ * The types short, int, and long are mapped to long when storing * within a Realm. *

                      - * The only restriction a RealmObject has is that fields are not allowed to be final, transient' or volatile. + * The only restriction a RealmObject has is that fields are not allowed to be final or volatile. * Any method as well as public fields are allowed. When providing custom constructors, a public constructor with - * no arguments must be declared and be empty. + * no arguments must be declared. *

                      * Fields annotated with {@link io.realm.annotations.Ignore} don't have these restrictions and don't require either a * getter or setter. From 3310496bb343042ed06fd55c77d642a67734de58 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 7 Apr 2017 20:26:38 +0900 Subject: [PATCH 0606/2110] Hide StandardRealmSchema class from public API. (#4444) fixes #4443 * add package private methods to RealmSchema instead of BaseRealm.getSchemaInternal(). --- CHANGELOG.md | 8 ++++ .../src/main/java/io/realm/OsRealmSchema.java | 42 +++++++++++++++++++ .../src/main/java/io/realm/RealmSchema.java | 5 +++ .../java/io/realm/StandardRealmSchema.java | 4 ++ 4 files changed, 59 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f450096c4..7fddf1254f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ ## 3.1.1 (YYYY-MM-DD) +### Deprecated + +### Enhancements + ### Bug Fixes * Crash caused by Listeners on `RealmObject` getting triggered the 2nd time with different changed field (#4437). +* Unintentionally exposing `StandardRealmSchema` (#4443). + +### Internal + ## 3.1.0 (2017-04-05) diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java index 13097cf10a..c7908c3765 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -21,6 +21,8 @@ import java.util.Map; import java.util.Set; +import io.realm.internal.Table; + /** * Class for interacting with the Realm schema using a dynamic API. This makes it possible @@ -61,6 +63,26 @@ public boolean contains(String className) { return schema.containsKey(className); } + @Override + Table getTable(Class clazz) { + throw new UnsupportedOperationException(); + } + + @Override + Table getTable(String className) { + throw new UnsupportedOperationException(); + } + + @Override + OsRealmObjectSchema getSchemaForClass(Class clazz) { + throw new UnsupportedOperationException(); + } + + @Override + OsRealmObjectSchema getSchemaForClass(String className) { + throw new UnsupportedOperationException(); + } + @Override public void remove(String className) { throw new UnsupportedOperationException(); @@ -163,6 +185,26 @@ public boolean contains(String className) { return dynamicClassToSchema.containsKey(className); } + @Override + Table getTable(Class clazz) { + throw new UnsupportedOperationException(); + } + + @Override + Table getTable(String className) { + throw new UnsupportedOperationException(); + } + + @Override + OsRealmObjectSchema getSchemaForClass(Class clazz) { + throw new UnsupportedOperationException(); + } + + @Override + OsRealmObjectSchema getSchemaForClass(String className) { + throw new UnsupportedOperationException(); + } + static void checkEmpty(String str) { if (str == null || str.isEmpty()) { throw new IllegalArgumentException("Null or empty class names are not allowed"); diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index ea53642122..4b3dcda6c9 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -145,4 +145,9 @@ private void checkIndices() { throw new IllegalStateException("Attempt to use column index before set."); } } + + abstract Table getTable(Class clazz); + abstract Table getTable(String className); + abstract RealmObjectSchema getSchemaForClass(Class clazz); + abstract RealmObjectSchema getSchemaForClass(String className); } diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java index 3d3933d451..bcec1169fb 100644 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java @@ -203,6 +203,7 @@ private void checkHasTable(String className, String errorMsg) { } } + @Override Table getTable(String className) { className = Table.TABLE_PREFIX + className; Table table = dynamicClassToTable.get(className); @@ -217,6 +218,7 @@ Table getTable(String className) { return table; } + @Override Table getTable(Class clazz) { Table table = classToTable.get(clazz); if (table != null) { return table; } @@ -238,6 +240,7 @@ Table getTable(Class clazz) { return table; } + @Override StandardRealmObjectSchema getSchemaForClass(Class clazz) { StandardRealmObjectSchema classSchema = classToSchema.get(clazz); if (classSchema != null) { return classSchema; } @@ -259,6 +262,7 @@ StandardRealmObjectSchema getSchemaForClass(Class clazz) { return classSchema; } + @Override StandardRealmObjectSchema getSchemaForClass(String className) { className = Table.TABLE_PREFIX + className; StandardRealmObjectSchema dynamicSchema = dynamicClassToSchema.get(className); From 6ad1e1321fdc90705bd9814f29974754a0f78a4b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 7 Apr 2017 19:27:41 +0800 Subject: [PATCH 0607/2110] Work around the memmove bug on Samsung device (#4402) There was a bug for memmove on some Samsung devices. The functions returns "dest + n" instead of "dest". This has been identified earlier by QT. See: https://bugreports.qt.io/browse/QTBUG-34984 The relevant android bug: https://code.google.com/p/android/issues/detail?id=81692 This fix try to test if the device have this issue first, if yes, switch to a own implementation of memmove. This fix works for most cases, but since the memmove bug existing in the system, there would be other lib/system call the buggy version of memmove which could still corrupt the memory -- which means there are still some possibilities that app gets crashed on those devices. --- .../realm-library/src/main/cpp/CMakeLists.txt | 15 ++- .../src/main/cpp/io_realm_internal_Util.cpp | 4 + .../src/main/cpp/jni_util/hack.cpp | 116 ++++++++++++++++++ .../src/main/cpp/jni_util/hack.hpp | 28 +++++ 4 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/jni_util/hack.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/hack.hpp diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 5913e1872e..11c3d43caf 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -17,6 +17,10 @@ set(CMAKE_VERBOSE_MAKEFILE ON) # Generate compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# Initialize common compile & link flags. +set(REALM_LINKER_FLAGS "") +set(REALM_COMMON_CXX_FLAGS "") + # Setup lcache if(NDK_LCACHE) set(CMAKE_CXX_CREATE_SHARED_LIBRARY "${NDK_LCACHE} ${CMAKE_CXX_CREATE_SHARED_LIBRARY}") @@ -123,6 +127,14 @@ elseif (ARMEABI_V7A) set(ABI_CXX_FLAGS "-mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16") endif() +# Hack the memmove bug on Samsung device. +if (ARMEABI OR ARMEABI_V7A) + set(REALM_LINKER_FLAGS "${REALM_LINKER_FLAGS} -Wl,--wrap,memmove -Wl,--wrap,memcpy") + set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_WRAP_MEMMOVE=1") +else() + set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_WRAP_MEMMOVE=0") +endif() + #FIXME uninitialized is reported by query_expression.hpp:1070 # d.init(ValueBase::m_from_link_list, ValueBase::m_values, D{}); #FIXME maybe-uninitialized is reported by table_view.cpp:272:15: @@ -131,7 +143,7 @@ endif() set(WARNING_CXX_FLAGS "-Werror -Wall -Wextra -pedantic -Wmissing-declarations \ -Wempty-body -Wparentheses -Wunknown-pragmas -Wunreachable-code \ -Wno-missing-field-initializers -Wno-maybe-uninitialized -Wno-uninitialized") -set(REALM_COMMON_CXX_FLAGS "-DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14 -fsigned-char") +set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14 -fsigned-char") if (build_SYNC) set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ENABLE_SYNC=1") endif() @@ -143,7 +155,6 @@ set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -Og -DNDEBUG") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${REALM_COMMON_CXX_FLAGS} ${WARNING_CXX_FLAGS} ${ABI_CXX_FLAGS}") # Set link flags -set(REALM_LINKER_FLAGS "") if (build_SYNC) set(REALM_LINKER_FLAGS "${REALM_LINKER_FLAGS} -lz") endif() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index 48e47cf726..ab772f42bd 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -17,6 +17,7 @@ #include #include "jni_util/jni_utils.hpp" +#include "jni_util/hack.hpp" #include #include @@ -37,6 +38,9 @@ const string TABLE_PREFIX("class_"); JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + // Workaround for some known bugs in system calls on specific devices. + hack_init(); + JNIEnv* env; if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; diff --git a/realm/realm-library/src/main/cpp/jni_util/hack.cpp b/realm/realm-library/src/main/cpp/jni_util/hack.cpp new file mode 100644 index 0000000000..1ca6414ee0 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/hack.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "hack.hpp" +#include "log.hpp" + +#include + +#include + +#ifndef REALM_WRAP_MEMMOVE +#error "REALM_WRAP_MEMMOVE is not defined!" +#endif + +#if REALM_WRAP_MEMMOVE +extern "C" { +void* __wrap_memmove(void *dest, const void *src, size_t n); +void* __real_memmove(void *dest, const void *src, size_t n); + +void* __wrap_memcpy(void *dest, const void *src, size_t n); +void* __real_memcpy(void *dest, const void *src, size_t n); +} + +using namespace realm::jni_util; + +typedef void* (*MemMoveFunc)(void *dest, const void *src, size_t n); +static MemMoveFunc s_wrap_memmove_ptr = &__real_memmove; +static MemMoveFunc s_wrap_memcpy_ptr = &__real_memcpy; + +static void* hacked_memmove(void* s1, const void* s2, size_t n) +{ + // adapted from https://github.com/dryc/libc11/blob/master/src/string/memmove.c + char* dest = (char*)s1; + const char* src = (const char*)s2; + if (dest <= src) { + while (n--) { + *dest++ = *src++; + } + } + else { + src += n; + dest += n; + while (n--) { + *--dest = *--src; + } + } + return static_cast(s1); +} + +static void* hacked_memcpy(void* s1, const void* s2, size_t n) +{ + // adapted from https://github.com/dryc/libc11/blob/master/src/string/memcpy.c + char* dest = (char*)s1; + const char* src = (const char*)s2; + while (n--) { + *dest++ = *src++; + } + return static_cast(s1); +} + +void* __wrap_memmove(void *dest, const void *src, size_t n) +{ + return (*s_wrap_memmove_ptr)(dest, src, n); +} + +void* __wrap_memcpy(void *dest, const void *src, size_t n) +{ + return (*s_wrap_memcpy_ptr)(dest, src, n); +} + + +// See https://github.com/realm/realm-java/issues/3651#issuecomment-290290228 +// There is a bug in memmove for some Samsung devices which will return "dest-n" instead of dest. +// The bug was originally found by QT, see https://bugreports.qt.io/browse/QTBUG-34984 . +// To work around it, we use linker's wrap feature to use a pure C implementation of memmove if the device has the +// problem. +static void check_memmove() +{ + char* array = strdup("Foobar"); + size_t len = strlen(array); + void* ptr = __real_memmove(array + 1, array, len - 1); + if (ptr != array + 1 || strncmp(array, "FFooba", len) != 0) { + Log::e("memmove is broken on this device. Switching to the builtin implementation."); + s_wrap_memmove_ptr = &hacked_memmove; + s_wrap_memcpy_ptr = &hacked_memcpy; + } + free(array); +} +#endif + +namespace realm { +namespace jni_util { + +void hack_init() +{ +#if REALM_WRAP_MEMMOVE + check_memmove(); +#endif +} + +} +} + diff --git a/realm/realm-library/src/main/cpp/jni_util/hack.hpp b/realm/realm-library/src/main/cpp/jni_util/hack.hpp new file mode 100644 index 0000000000..62ac347ee5 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/hack.hpp @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_HACK_HPP +#define REALM_JNI_UTIL_HACK_HPP + +namespace realm { +namespace jni_util { + +// Workaround bugs on some devices. +void hack_init(); + +} +} +#endif // REALM_JNI_UTIL_HACK_HPP From 4b59c30aaaa330b19c5e66aa7f58c5b0873d01af Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 7 Apr 2017 19:39:39 +0800 Subject: [PATCH 0608/2110] Add missing changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fddf1254f..736221758a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Crash caused by Listeners on `RealmObject` getting triggered the 2nd time with different changed field (#4437). * Unintentionally exposing `StandardRealmSchema` (#4443). +* Workaround for crashes on specific Samsung devices which are caused by a buggy `memmove` call (#3651). ### Internal From 1a6d445bfe55570002c808897666058176f57568 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 7 Apr 2017 19:40:23 +0800 Subject: [PATCH 0609/2110] Update date in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 736221758a..2305d4b39a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.1.1 (YYYY-MM-DD) +## 3.1.1 (2017-04-07) ### Deprecated From e39b6f11654f6291932c24d646ef9276036b839c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 7 Apr 2017 19:45:13 +0800 Subject: [PATCH 0610/2110] Release v3.1.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index dde25ef08e..50e47c89ca 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.1-SNAPSHOT \ No newline at end of file +3.1.1 \ No newline at end of file From 2697550c45399511b7c092703409226e5a4c2e4f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 7 Apr 2017 19:45:13 +0800 Subject: [PATCH 0611/2110] Prepare next release v3.1.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 50e47c89ca..0f58aa0414 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.1 \ No newline at end of file +3.1.2-SNAPSHOT \ No newline at end of file From f73308716c5b9fcd35fc900d18d676d67cc376d4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 7 Apr 2017 20:21:12 +0800 Subject: [PATCH 0612/2110] Fix flaky test --- .../androidTest/java/io/realm/internal/RealmNotifierTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 6c70f71d1a..0276a8a432 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -150,6 +150,7 @@ public void removeChangeListeners() { SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); Integer dummyObserver = 1; looperThread.keepStrongReference.add(dummyObserver); + looperThread.keepStrongReference.add(sharedRealm); sharedRealm.realmNotifier.addChangeListener(dummyObserver, new RealmChangeListener() { @Override public void onChange(Integer dummy) { From 81bbb1f7e291eedcaa2d6c200b2b649295704a6e Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 16:04:29 +0900 Subject: [PATCH 0613/2110] fix leak when Property is added to OsRealmObjectSchema --- .../main/java/io/realm/OsRealmObjectSchema.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java index 922e28aba6..4cce7b0f7e 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java @@ -167,13 +167,23 @@ long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTy @Override OsRealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { - nativeAddProperty(nativePtr, new Property(name, type, primary, indexed, required).getNativePtr()); + final Property property = new Property(name, type, primary, indexed, required); + try { + nativeAddProperty(nativePtr, property.getNativePtr()); + } finally { + property.close(); + } return this; } @Override OsRealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linkedTo) { - nativeAddProperty(nativePtr, new Property(name, type, linkedTo).getNativePtr()); + final Property property = new Property(name, type, linkedTo); + try { + nativeAddProperty(nativePtr, property.getNativePtr()); + } finally { + property.close(); + } return this; } From 7ae0defe36db8f95e8aa6886364b673598bc64fa Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 16:05:32 +0900 Subject: [PATCH 0614/2110] remove meaningless std::unique_ptr --- realm/realm-library/src/main/cpp/io_realm_Property.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_Property.cpp b/realm/realm-library/src/main/cpp/io_realm_Property.cpp index 3aaedada73..38e9dff137 100644 --- a/realm/realm-library/src/main/cpp/io_realm_Property.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_Property.cpp @@ -57,8 +57,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_Property_nativeCreateProperty__Ljava_lang_ JStringAccessor link_name(env, linkedToName_); PropertyType p_type = static_cast(static_cast(type)); bool is_nullable = (p_type == PropertyType::Object); - std::unique_ptr property(new Property(name, p_type, link_name, "", false, false, is_nullable)); - return reinterpret_cast(property.release()); + return reinterpret_cast(new Property(name, p_type, link_name, "", false, false, is_nullable)); } CATCH_STD() return 0; From b802646079157465e3789a43cd6108af513660c9 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 16:06:04 +0900 Subject: [PATCH 0615/2110] protect Property from double free --- realm/realm-library/src/main/java/io/realm/Property.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/Property.java b/realm/realm-library/src/main/java/io/realm/Property.java index c96fbfe6c7..4285be3db5 100644 --- a/realm/realm-library/src/main/java/io/realm/Property.java +++ b/realm/realm-library/src/main/java/io/realm/Property.java @@ -26,7 +26,7 @@ class Property { public static final boolean REQUIRED = true; public static final boolean INDEXED = true; - private final long nativePtr; + private long nativePtr; Property(String name, RealmFieldType type, boolean isPrimary, boolean isIndexed, boolean isRequired) { this.nativePtr = nativeCreateProperty(name, type.getNativeValue(), isPrimary, isIndexed, !isRequired); @@ -47,6 +47,7 @@ protected long getNativePtr() { public void close() { if (nativePtr != 0) { nativeClose(nativePtr); + nativePtr = 0L; } } From f3d950c862ddc4b95dd689a8ab64989570cbd08c Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 16:25:09 +0900 Subject: [PATCH 0616/2110] directly copy Property object instead of copy then move --- .../src/main/cpp/io_realm_OsRealmObjectSchema.cpp | 5 ++--- .../src/main/java/io/realm/OsRealmObjectSchema.java | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp index bbbcbe2879..62ef7b0137 100644 --- a/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp @@ -76,7 +76,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_OsRealmObjectSchema_nativeGetClassName(J return nullptr; } -JNIEXPORT jlongArray JNICALL Java_io_realm_OsRealmObjectSchema_nativeGetProperties(JNIEnv* env, jclass, jlong nativePtr) +JNIEXPORT jlongArray JNICALL Java_io_realm_OsRealmObjectSchema_nativeCopyProperties(JNIEnv* env, jclass, jlong nativePtr) { TR_ENTER_PTR(nativePtr) try { @@ -87,8 +87,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_OsRealmObjectSchema_nativeGetProperti auto it = object_schema->persisted_properties.begin(); size_t index = 0; while (it != object_schema->persisted_properties.end()) { - Property property = *it; - tmp[index] = reinterpret_cast(new Property(std::move(property))); + tmp[index] = reinterpret_cast(new Property(*it/* do not move*/)); ++index; ++it; } diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java index 4cce7b0f7e..4a729e3f12 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java @@ -38,7 +38,7 @@ class OsRealmObjectSchema extends RealmObjectSchema { @Override public void close() { - Set properties = getProperties(); + Set properties = copyProperties(); for (Property property : properties) { property.close(); } @@ -191,8 +191,8 @@ long getNativePtr() { return nativePtr; } - private Set getProperties() { - long[] ptrs = nativeGetProperties(nativePtr); + private Set copyProperties() { + long[] ptrs = nativeCopyProperties(nativePtr); Set properties = new LinkedHashSet<>(ptrs.length); for (int i = 0; i < ptrs.length; i++) { properties.add(new Property(ptrs[i])); @@ -204,7 +204,7 @@ private Set getProperties() { static native void nativeAddProperty(long nativePtr, long nativePropertyPtr); - static native long[] nativeGetProperties(long nativePtr); + static native long[] nativeCopyProperties(long nativePtr); static native void nativeClose(long nativePtr); From ea42b6587b608604b02abf82237f2ee12ad7664d Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 17:07:43 +0900 Subject: [PATCH 0617/2110] removed unnecessary code --- .../main/cpp/io_realm_OsRealmObjectSchema.cpp | 24 ------------------- .../java/io/realm/OsRealmObjectSchema.java | 15 ------------ 2 files changed, 39 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp index 62ef7b0137..c3a954c8f1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp @@ -75,27 +75,3 @@ JNIEXPORT jstring JNICALL Java_io_realm_OsRealmObjectSchema_nativeGetClassName(J return nullptr; } - -JNIEXPORT jlongArray JNICALL Java_io_realm_OsRealmObjectSchema_nativeCopyProperties(JNIEnv* env, jclass, jlong nativePtr) -{ - TR_ENTER_PTR(nativePtr) - try { - ObjectSchema* object_schema = reinterpret_cast(nativePtr); - size_t size = object_schema->persisted_properties.size(); - jlongArray native_ptr_array = env->NewLongArray(static_cast(size)); - jlong* tmp = new jlong[size]; - auto it = object_schema->persisted_properties.begin(); - size_t index = 0; - while (it != object_schema->persisted_properties.end()) { - tmp[index] = reinterpret_cast(new Property(*it/* do not move*/)); - ++index; - ++it; - } - env->SetLongArrayRegion(native_ptr_array, 0, static_cast(size), tmp); - delete tmp; - return native_ptr_array; - } - CATCH_STD() - - return nullptr; -} diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java index 4a729e3f12..1375b5db5a 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java @@ -38,10 +38,6 @@ class OsRealmObjectSchema extends RealmObjectSchema { @Override public void close() { - Set properties = copyProperties(); - for (Property property : properties) { - property.close(); - } nativeClose(nativePtr); } @@ -191,21 +187,10 @@ long getNativePtr() { return nativePtr; } - private Set copyProperties() { - long[] ptrs = nativeCopyProperties(nativePtr); - Set properties = new LinkedHashSet<>(ptrs.length); - for (int i = 0; i < ptrs.length; i++) { - properties.add(new Property(ptrs[i])); - } - return properties; - } - static native long nativeCreateRealmObjectSchema(String className); static native void nativeAddProperty(long nativePtr, long nativePropertyPtr); - static native long[] nativeCopyProperties(long nativePtr); - static native void nativeClose(long nativePtr); static native String nativeGetClassName(long nativePtr); From cc4864827ec688c42e849222309f2a433ffb05f8 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 17:39:15 +0900 Subject: [PATCH 0618/2110] directly copy ObjectSchema instead of copy then move since ObjectSchema does not have move constructor. --- realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp index 5d78cae226..4a0ccc21a7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp @@ -33,8 +33,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_OsRealmSchema_nativeCreateFromList(JNIEnv* std::vector object_schemas; JniLongArray array(env, objectSchemaPtrs_); for (jsize i = 0; i < array.len(); ++i) { - ObjectSchema object_schema = *reinterpret_cast(array[i]); - object_schemas.push_back(std::move(object_schema)); + object_schemas.push_back(*reinterpret_cast(array[i])); } auto* schema = new Schema(object_schemas); return reinterpret_cast(schema); @@ -61,8 +60,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_OsRealmSchema_nativeGetAll(JNIEnv* en auto it = schema->begin(); size_t index = 0; while (it != schema->end()) { - auto object_schema = *it; - tmp[index] = reinterpret_cast(new ObjectSchema(std::move(object_schema))); + tmp[index] = reinterpret_cast(new ObjectSchema(*it)); ++index; ++it; } From 485ba8284de24ca47f80acde07830654a8f0f808 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 18:05:52 +0900 Subject: [PATCH 0619/2110] protect OsRealmObjectSchema from double free --- .../src/main/java/io/realm/OsRealmObjectSchema.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java index 1375b5db5a..e23f72e589 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java @@ -19,7 +19,7 @@ import java.util.Set; class OsRealmObjectSchema extends RealmObjectSchema { - private final long nativePtr; + private long nativePtr; /** * Creates a schema object using object store. This constructor is intended to be used by @@ -38,7 +38,10 @@ class OsRealmObjectSchema extends RealmObjectSchema { @Override public void close() { - nativeClose(nativePtr); + if (nativePtr != 0L) { + nativeClose(nativePtr); + nativePtr = 0L; + } } @Override From 5adaf3c168505809a7eed872a36d2aa3fc9e8f3f Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 18:29:10 +0900 Subject: [PATCH 0620/2110] protect OsRealmSchema from double free --- .../src/main/java/io/realm/OsRealmSchema.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java index c7908c3765..98f738fe6f 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -96,7 +96,7 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { private final Map dynamicClassToSchema = new HashMap<>(); - private final long nativePtr; + private long nativePtr; OsRealmSchema(Creator creator) { Set realmObjectSchemas = creator.getAll(); @@ -119,7 +119,10 @@ public void close() { for (RealmObjectSchema schema : schemas) { schema.close(); } - nativeClose(nativePtr); + if (nativePtr != 0L) { + nativeClose(nativePtr); + nativePtr = 0L; + } } /** From caea456192d3b12ac15b2f4e7e34908dbf236bae Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 18:38:00 +0900 Subject: [PATCH 0621/2110] dispose OsRealmObjectSchema in OsRealmSchema.Creator --- .../src/main/java/io/realm/OsRealmSchema.java | 7 ++++++- .../src/main/java/io/realm/Realm.java | 17 +++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java index 98f738fe6f..8383a3583e 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -37,7 +37,12 @@ static final class Creator extends RealmSchema { private final Map schema = new HashMap<>(); @Override - public void close() { } + public void close() { + for (Map.Entry entry : schema.entrySet()) { + entry.getValue().close(); + } + schema.clear(); + } @Override public RealmObjectSchema get(String className) { diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 6e747797eb..aeb0042424 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -396,13 +396,18 @@ private static void initializeSyncedRealm(Realm realm) { final RealmProxyMediator mediator = configuration.getSchemaMediator(); final Set> modelClasses = mediator.getModelClasses(); - final OsRealmSchema.Creator schemaCreator = new OsRealmSchema.Creator(); - for (Class modelClass : modelClasses) { - mediator.createRealmObjectSchema(modelClass, schemaCreator); - } + OsRealmSchema.Creator schemaCreator = new OsRealmSchema.Creator(); + try { + for (Class modelClass : modelClasses) { + mediator.createRealmObjectSchema(modelClass, schemaCreator); + } - // Assumption: When SyncConfiguration then additive schema update mode. - schema = new OsRealmSchema(schemaCreator); + // Assumption: When SyncConfiguration then additive schema update mode. + schema = new OsRealmSchema(schemaCreator); + } finally { + schemaCreator.close(); + schemaCreator = null; + } long newVersion = configuration.getSchemaVersion(); // !!! FIXME: This appalling kludge is necessitated by current package structure/visiblity constraints. // It absolutely breaks encapsulation and needs to be fixed! From d6e67f41623280f39d0a9e4041154d161573551e Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 18:48:29 +0900 Subject: [PATCH 0622/2110] removed unnecessary code --- .../src/main/cpp/io_realm_OsRealmSchema.cpp | 23 ------------------- .../src/main/java/io/realm/OsRealmSchema.java | 13 +---------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp index 4a0ccc21a7..20a4852a05 100644 --- a/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp @@ -48,26 +48,3 @@ JNIEXPORT void JNICALL Java_io_realm_OsRealmSchema_nativeClose(JNIEnv*, jclass, Schema* schema = reinterpret_cast(nativePtr); delete schema; } - -JNIEXPORT jlongArray JNICALL Java_io_realm_OsRealmSchema_nativeGetAll(JNIEnv* env, jclass, jlong nativePtr) -{ - TR_ENTER_PTR(nativePtr) - try { - Schema* schema = reinterpret_cast(nativePtr); - size_t size = schema->size(); - jlongArray native_ptr_array = env->NewLongArray(static_cast(size)); - jlong* tmp = new jlong[size]; - auto it = schema->begin(); - size_t index = 0; - while (it != schema->end()) { - tmp[index] = reinterpret_cast(new ObjectSchema(*it)); - ++index; - ++it; - } - env->SetLongArrayRegion(native_ptr_array, 0, static_cast(size), tmp); - delete tmp; - return native_ptr_array; - } - CATCH_STD() - return nullptr; -} diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java index 8383a3583e..ba25b17a67 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -120,10 +120,6 @@ public long getNativePtr() { // See BaseRealm uses a StandardRealmSchema, not a OsRealmSchema. @Override public void close() { - Set schemas = getAll(); - for (RealmObjectSchema schema : schemas) { - schema.close(); - } if (nativePtr != 0L) { nativeClose(nativePtr); nativePtr = 0L; @@ -149,12 +145,7 @@ public RealmObjectSchema get(String className) { */ @Override public Set getAll() { - long[] ptrs = nativeGetAll(nativePtr); - Set schemas = new LinkedHashSet<>(ptrs.length); - for (int i = 0; i < ptrs.length; i++) { - schemas.add(new OsRealmObjectSchema(ptrs[i])); - } - return schemas; + throw new UnsupportedOperationException(); } /** @@ -222,6 +213,4 @@ static void checkEmpty(String str) { static native long nativeCreateFromList(long[] objectSchemaPtrs); static native void nativeClose(long nativePtr); - - static native long[] nativeGetAll(long nativePtr); } From af0ed3330efcc64e1bb6ca3a676f19b9ec652e08 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 19:07:30 +0900 Subject: [PATCH 0623/2110] address findbugs warnings --- realm/realm-library/src/main/java/io/realm/Realm.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index aeb0042424..720aeaccb7 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -441,7 +441,7 @@ private static void initializeSyncedRealm(Realm realm) { transaction.execute(realm); } } - } catch (Exception e) { + } catch (RuntimeException e) { commitChanges = false; throw e; } finally { From 77f1e51ca4afab65563ae4e6dacecaaed33943a1 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 11 Apr 2017 19:48:34 +0900 Subject: [PATCH 0624/2110] Update CHANGELOG.md --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2305d4b39a..34ad85c7ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 3.1.2 (YYYY-MM-DD) + +### Deprecated + +### Enhancements + +### Bug Fixes + +* Memory leaked when synced Realm was initialized (#4465). + +### Internal + + ## 3.1.1 (2017-04-07) ### Deprecated From c9380bdadf8c429f47962d21a8cc392fd4b6fe70 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 11 Apr 2017 17:11:51 +0800 Subject: [PATCH 0625/2110] KeepMember OsObject.notifyChangeListeners - Fix #4461. - Use release assertion when finding java method. --- CHANGELOG.md | 6 ++++++ realm/realm-library/src/main/cpp/jni_util/java_method.cpp | 6 +++--- .../src/main/java/io/realm/internal/OsObject.java | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2305d4b39a..43382fbe7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.1.2 (YYYY-MM-DD) + +### Bug Fixes + +* Crash caused by JNI couldn't find `OsObject.notifyChangeListeners` when ProGurad is enabled (#4461). + ## 3.1.1 (2017-04-07) ### Deprecated diff --git a/realm/realm-library/src/main/cpp/jni_util/java_method.cpp b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp index e28bfc184a..882e4ef38f 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_method.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp @@ -29,14 +29,14 @@ JavaMethod::JavaMethod(JNIEnv* env, jclass cls, const char* method_name, const c m_method_id = env->GetMethodID(cls, method_name, signature); } - REALM_ASSERT_DEBUG(m_method_id != nullptr); + REALM_ASSERT_RELEASE(m_method_id != nullptr); } JavaMethod::JavaMethod(JNIEnv* env, jobject obj, const char* method_name, const char* signature) { jclass cls = env->GetObjectClass(obj); m_method_id = env->GetMethodID(cls, method_name, signature); - REALM_ASSERT_DEBUG(m_method_id != nullptr); + REALM_ASSERT_RELEASE(m_method_id != nullptr); env->DeleteLocalRef(cls); } @@ -44,7 +44,7 @@ JavaMethod::JavaMethod(JNIEnv* env, const char* class_name, const char* method_n bool static_method) { jclass cls = env->FindClass(class_name); - REALM_ASSERT_DEBUG(cls != nullptr); + REALM_ASSERT_RELEASE(cls != nullptr); if (static_method) { m_method_id = env->GetStaticMethodID(cls, method_name, signature); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index 5cf394d4ad..33e1807b04 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -24,6 +24,7 @@ /** * Java wrapper for Object Store's {@code Object} class. Currently it is only used for object notifications. */ +@KeepMember public class OsObject implements NativeObject { private static class OsObjectChangeSet implements ObjectChangeSet { @@ -143,6 +144,7 @@ public void setObserverPairs(ObserverPairList pairs) { // Called by JNI @SuppressWarnings("unused") + @KeepMember private void notifyChangeListeners(String[] changedFields) { observerPairs.foreach(new Callback(changedFields)); } From 196ee58bd536d658867954881902f7f925c9883d Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 12 Apr 2017 04:45:52 +0900 Subject: [PATCH 0626/2110] fix API incompatibility introduced in 3.1.0 (#4455) --- CHANGELOG.md | 8 ++++++++ .../java/io/realm/RealmObjectSchemaTests.java | 2 +- .../java/io/realm/RealmSchemaTests.java | 2 +- .../src/main/java/io/realm/BaseRealm.java | 2 +- .../main/java/io/realm/OsRealmObjectSchema.java | 13 +++++++++++++ .../src/main/java/io/realm/OsRealmSchema.java | 16 ++++++++-------- .../main/java/io/realm/RealmObjectSchema.java | 4 ++++ .../src/main/java/io/realm/RealmQuery.java | 2 +- .../src/main/java/io/realm/RealmSchema.java | 2 +- .../java/io/realm/StandardRealmObjectSchema.java | 4 +++- 10 files changed, 41 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43382fbe7f..41a10e282f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ ## 3.1.2 (YYYY-MM-DD) +### Deprecated + +### Enhancements + ### Bug Fixes * Crash caused by JNI couldn't find `OsObject.notifyChangeListeners` when ProGurad is enabled (#4461). +* Incompatible return type of `RealmSchema.getAll()` and `BaseRealm.getSchema()` (#4443). + +### Internal + ## 3.1.1 (2017-04-07) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index e69e20613f..d89911896d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -48,7 +48,7 @@ public class RealmObjectSchemaTests { private RealmObjectSchema DOG_SCHEMA; private DynamicRealm realm; private RealmObjectSchema schema; - private StandardRealmSchema realmSchema; + private RealmSchema realmSchema; @Before public void setUp() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index 86e5985fc8..da49a66229 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -47,7 +47,7 @@ public class RealmSchemaTests { public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); private DynamicRealm realm; - private StandardRealmSchema realmSchema; + private RealmSchema realmSchema; @Before public void setUp() { diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index c2330bf87d..894e581e0a 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -464,7 +464,7 @@ void setVersion(long version) { * * @return The {@link RealmSchema} for this Realm. */ - public StandardRealmSchema getSchema() { + public RealmSchema getSchema() { return schema; } diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java index 922e28aba6..ff432579b9 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java @@ -18,6 +18,9 @@ import java.util.LinkedHashSet; import java.util.Set; +import io.realm.internal.Table; + + class OsRealmObjectSchema extends RealmObjectSchema { private final long nativePtr; @@ -181,6 +184,16 @@ long getNativePtr() { return nativePtr; } + @Override + Table getTable() { + throw new UnsupportedOperationException(); + } + + @Override + long getAndCheckFieldIndex(String fieldName) { + throw new UnsupportedOperationException(); + } + private Set getProperties() { long[] ptrs = nativeGetProperties(nativePtr); Set properties = new LinkedHashSet<>(ptrs.length); diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java index c7908c3765..6f3fbb0b16 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -34,7 +34,7 @@ */ class OsRealmSchema extends RealmSchema { static final class Creator extends RealmSchema { - private final Map schema = new HashMap<>(); + private final Map schema = new HashMap<>(); @Override public void close() { } @@ -46,7 +46,7 @@ public RealmObjectSchema get(String className) { } @Override - public Set getAll() { + public Set getAll() { return new LinkedHashSet<>(schema.values()); } @@ -99,11 +99,11 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { private final long nativePtr; OsRealmSchema(Creator creator) { - Set realmObjectSchemas = creator.getAll(); + Set realmObjectSchemas = creator.getAll(); long[] schemaNativePointers = new long[realmObjectSchemas.size()]; int i = 0; - for (OsRealmObjectSchema schema : realmObjectSchemas) { - schemaNativePointers[i++] = schema.getNativePtr(); + for (RealmObjectSchema schema : realmObjectSchemas) { + schemaNativePointers[i++] = ((OsRealmObjectSchema) schema).getNativePtr(); } this.nativePtr = nativeCreateFromList(schemaNativePointers); } @@ -115,7 +115,7 @@ public long getNativePtr() { // See BaseRealm uses a StandardRealmSchema, not a OsRealmSchema. @Override public void close() { - Set schemas = getAll(); + Set schemas = getAll(); for (RealmObjectSchema schema : schemas) { schema.close(); } @@ -140,9 +140,9 @@ public RealmObjectSchema get(String className) { * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ @Override - public Set getAll() { + public Set getAll() { long[] ptrs = nativeGetAll(nativePtr); - Set schemas = new LinkedHashSet<>(ptrs.length); + Set schemas = new LinkedHashSet<>(ptrs.length); for (int i = 0; i < ptrs.length; i++) { schemas.add(new OsRealmObjectSchema(ptrs[i])); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index e575615082..bed6061e1f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -19,6 +19,7 @@ import java.util.Set; import io.realm.annotations.Required; +import io.realm.internal.Table; /** @@ -290,4 +291,7 @@ protected FieldMetaData(RealmFieldType realmType, boolean defaultNullable) { this.defaultNullable = defaultNullable; } } + + abstract Table getTable(); + abstract long getAndCheckFieldIndex(String fieldName); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 7f3a19197d..8afbdcaea6 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -54,7 +54,7 @@ public class RealmQuery { private final Table table; private final BaseRealm realm; private final TableQuery query; - private final StandardRealmObjectSchema schema; + private final RealmObjectSchema schema; private Class clazz; private String className; private LinkView linkView; diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 4b3dcda6c9..0968433c78 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -54,7 +54,7 @@ public abstract class RealmSchema { * * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ - public abstract Set getAll(); + public abstract Set getAll(); /** * Adds a new class to the Realm. diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java index cced1c6d23..d46fb453ea 100644 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java @@ -81,7 +81,8 @@ class StandardRealmObjectSchema extends RealmObjectSchema { this.columnIndices = columnIndices; } - public Table getTable() { + @Override + Table getTable() { return table; } @@ -652,6 +653,7 @@ Long getFieldIndex(String fieldName) { * @return column index. * @throws IllegalArgumentException if the field does not exists. */ + @Override long getAndCheckFieldIndex(String fieldName) { Long index = columnIndices.get(fieldName); if (index == null) { From 7dfd3ee6bb7ed5b71700d8ed194d2b347caeef99 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 12 Apr 2017 04:55:53 +0900 Subject: [PATCH 0627/2110] fix typo in CHANGELOG.md (#4476) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41a10e282f..5a921f01a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Bug Fixes -* Crash caused by JNI couldn't find `OsObject.notifyChangeListeners` when ProGurad is enabled (#4461). +* Crash caused by JNI couldn't find `OsObject.notifyChangeListeners` when ProGuard is enabled (#4461). * Incompatible return type of `RealmSchema.getAll()` and `BaseRealm.getSchema()` (#4443). ### Internal From 9a5965847d445ba4ea02dc7adb332214a5897b31 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 12 Apr 2017 05:15:47 +0900 Subject: [PATCH 0628/2110] removed nested try-finally --- .../src/main/java/io/realm/Realm.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 720aeaccb7..b985fb9031 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -386,6 +386,7 @@ private static void initializeRealm(Realm realm) { private static void initializeSyncedRealm(Realm realm) { boolean commitChanges = false; OsRealmSchema schema = null; + OsRealmSchema.Creator schemaCreator = null; try { realm.beginTransaction(); long currentVersion = realm.getVersion(); @@ -396,18 +397,16 @@ private static void initializeSyncedRealm(Realm realm) { final RealmProxyMediator mediator = configuration.getSchemaMediator(); final Set> modelClasses = mediator.getModelClasses(); - OsRealmSchema.Creator schemaCreator = new OsRealmSchema.Creator(); - try { - for (Class modelClass : modelClasses) { - mediator.createRealmObjectSchema(modelClass, schemaCreator); - } - - // Assumption: When SyncConfiguration then additive schema update mode. - schema = new OsRealmSchema(schemaCreator); - } finally { - schemaCreator.close(); - schemaCreator = null; + schemaCreator = new OsRealmSchema.Creator(); + for (Class modelClass : modelClasses) { + mediator.createRealmObjectSchema(modelClass, schemaCreator); } + + // Assumption: When SyncConfiguration then additive schema update mode. + schema = new OsRealmSchema(schemaCreator); + schemaCreator.close(); + schemaCreator = null; + long newVersion = configuration.getSchemaVersion(); // !!! FIXME: This appalling kludge is necessitated by current package structure/visiblity constraints. // It absolutely breaks encapsulation and needs to be fixed! @@ -445,6 +444,10 @@ private static void initializeSyncedRealm(Realm realm) { commitChanges = false; throw e; } finally { + if (schemaCreator != null) { + schemaCreator.close(); + } + if (schema != null) { schema.close(); } From 4b754a8b0f3d9d38010b4ab70671c22eda4f5694 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 12 Apr 2017 12:27:29 +0900 Subject: [PATCH 0629/2110] fix compile eror --- realm/realm-library/src/main/java/io/realm/OsRealmSchema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java index 49ebd499db..7d96117d9d 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -38,7 +38,7 @@ static final class Creator extends RealmSchema { @Override public void close() { - for (Map.Entry entry : schema.entrySet()) { + for (Map.Entry entry : schema.entrySet()) { entry.getValue().close(); } schema.clear(); From 1fe5b9f8100338a9d18d071e7215b26941e71431 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 12 Apr 2017 17:31:25 +0800 Subject: [PATCH 0630/2110] Dont reset SharedRealm ptr (#4478) - Keep the SharedRealm ptr valid when close it. To allow the is_closed checking could throw in the Object Store and it can be converted to a friendly Java exception. - Check if SharedRealm is closed when create collection iterator. Throw an ISE if it is. Fix #4471 . --- CHANGELOG.md | 1 + .../io/realm/internal/CollectionTests.java | 8 ++++++ .../io/realm/internal/SharedRealmTests.java | 26 ++++++++++++++++++- .../cpp/io_realm_internal_SharedRealm.cpp | 4 ++- .../java/io/realm/internal/Collection.java | 7 +++++ .../java/io/realm/internal/SharedRealm.java | 13 ++++------ 6 files changed, 49 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e65e3259e..1e93a656d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Crash caused by JNI couldn't find `OsObject.notifyChangeListeners` when ProGuard is enabled (#4461). * Incompatible return type of `RealmSchema.getAll()` and `BaseRealm.getSchema()` (#4443). * Memory leaked when synced Realm was initialized (#4465). +* An `IllegalStateException` will be thrown when starting iterating `OrderedRealmCollection` if the Realm is closed (#4471). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index e9c7a2d0f1..2592c5f664 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -486,6 +486,14 @@ public void onChange(Collection element) { addRowAsync(); } + @Test + public void collectionIterator_newInstance_throwsWhenSharedRealmIsClosed() { + final Collection collection = new Collection(sharedRealm, table.where()); + sharedRealm.close(); + thrown.expect(IllegalStateException.class); + new TestIterator(collection); + } + @Test public void getMode() { Collection collection = new Collection(sharedRealm, table.where()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java index fde073a78c..c735e2dcf1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java @@ -53,7 +53,9 @@ public void setUp() { @After public void tearDown() { - sharedRealm.close(); + if (sharedRealm != null) { + sharedRealm.close(); + } } @Test @@ -100,6 +102,13 @@ public void isInTransaction() { assertFalse(sharedRealm.isInTransaction()); } + @Test + public void isInTransaction_returnFalseWhenRealmClosed() { + sharedRealm.close(); + assertFalse(sharedRealm.isInTransaction()); + sharedRealm = null; + } + @Test public void removeTable() { sharedRealm.beginTransaction(); @@ -231,4 +240,19 @@ public void onSchemaVersionChanged(long currentVersion) { assertTrue(listenerCalled.get()); assertEquals(before + 1, schemaVersionFromListener.get()); } + + @Test + public void isClosed() { + sharedRealm.close(); + assertTrue(sharedRealm.isClosed()); + sharedRealm = null; + } + + @Test + public void close_twice() { + sharedRealm.close(); + sharedRealm.close(); + assertTrue(sharedRealm.isClosed()); + sharedRealm = null; + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index c93c12d569..f542b345b0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -225,7 +225,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); // Close the SharedRealm only. Let the finalizer daemon thread free the SharedRealm - shared_realm->close(); + if (!shared_realm->is_closed()) { + shared_realm->close(); + } } JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv* env, jclass, diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index aeb8231262..d8d7830bcd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -32,6 +32,9 @@ @Keep public class Collection implements NativeObject { + private static final String CLOSED_REALM_MESSAGE = + "This Realm instance has already been closed, making it unusable."; + private static class CollectionObserverPair extends ObserverPairList.ObserverPair { public CollectionObserverPair(T observer, Object listener) { super(observer, listener); @@ -94,6 +97,10 @@ public static abstract class Iterator implements java.util.Iterator { protected int pos = -1; public Iterator(Collection collection) { + if (collection.sharedRealm.isClosed()) { + throw new IllegalStateException(CLOSED_REALM_MESSAGE); + } + this.iteratorCollection = collection; if (collection.isSnapshot) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 0abfa51f0f..c5e78eb1d8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -176,7 +176,7 @@ public interface SchemaVersionListener { private final RealmConfiguration configuration; - private long nativePtr; + final private long nativePtr; final Context context; private long lastSchemaVersion; private final SchemaVersionListener schemaChangeListener; @@ -319,7 +319,7 @@ public long getLastSnapshotVersion() { } public boolean isClosed() { - return nativePtr == 0 || nativeIsClosed(nativePtr); + return nativeIsClosed(nativePtr); } public void writeCopy(File file, byte[] key) { @@ -368,12 +368,9 @@ public void close() { realmNotifier.close(); } synchronized (context) { - if (nativePtr != 0) { - nativeCloseSharedRealm(nativePtr); - // It is OK to clear the nativePtr. It has been saved to the NativeObjectReference when adding to the - // context. - nativePtr = 0; - } + nativeCloseSharedRealm(nativePtr); + // Don't reset the nativePtr since we still rely on Object Store to check if the given SharedRealm ptr + // is closed or not. } } From 29fd6086e2433526639a542d31b1775827ee25bf Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 12 Apr 2017 17:42:13 +0800 Subject: [PATCH 0631/2110] Update release date --- CHANGELOG.md | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e93a656d4..9b4f92ab2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,4 @@ -## 3.1.2 (YYYY-MM-DD) - -### Deprecated - -### Enhancements +## 3.1.2 (2017-04-12) ### Bug Fixes @@ -11,24 +7,14 @@ * Memory leaked when synced Realm was initialized (#4465). * An `IllegalStateException` will be thrown when starting iterating `OrderedRealmCollection` if the Realm is closed (#4471). -### Internal - - ## 3.1.1 (2017-04-07) -### Deprecated - -### Enhancements - ### Bug Fixes * Crash caused by Listeners on `RealmObject` getting triggered the 2nd time with different changed field (#4437). * Unintentionally exposing `StandardRealmSchema` (#4443). * Workaround for crashes on specific Samsung devices which are caused by a buggy `memmove` call (#3651). -### Internal - - ## 3.1.0 (2017-04-05) ### Breaking Changes From 09096bc05814c325163e5569610520c7694b9622 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 12 Apr 2017 17:42:52 +0800 Subject: [PATCH 0632/2110] Release v3.1.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 0f58aa0414..6ebad14888 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.2-SNAPSHOT \ No newline at end of file +3.1.2 \ No newline at end of file From 65bcd0ed1f10367584aa8345e2faa16bbf670955 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 12 Apr 2017 17:42:52 +0800 Subject: [PATCH 0633/2110] Prepare next release v3.1.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 6ebad14888..dee8a83e4c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.2 \ No newline at end of file +3.1.3-SNAPSHOT \ No newline at end of file From 818c06df6cabd8c89e2192e7a68c74f3f2561339 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 13 Apr 2017 10:12:52 +0100 Subject: [PATCH 0634/2110] Nh/update secure store example (#4482) * update secure-store example --- .../secureTokenAndroidKeyStore/build.gradle | 2 +- .../MainActivity.java | 57 +++++++++---------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index b523a941c8..a15da443ce 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -30,7 +30,7 @@ dependencies { }) compile 'com.android.support:appcompat-v7:25.2.0' testCompile 'junit:junit:4.12' - compile 'io.realm:android-secure-userstore:1.0.0' + compile 'io.realm:secure-userstore:1.0.1' } realm { diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java index c4b618b704..67d71fee30 100644 --- a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java +++ b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java @@ -30,24 +30,24 @@ import java.security.KeyStoreException; import java.util.UUID; -import io.realm.android.CipherClient; -import io.realm.android.SecureUserStore; +import io.realm.Realm; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; import io.realm.SyncUser; import io.realm.android.SecureUserStore; -import io.realm.SyncManager; -import io.realm.SyncConfiguration; -import io.realm.Realm; -import io.realm.internal.objectserver.Token; import io.realm.internal.objectserver.ObjectServerUser; +import io.realm.internal.objectserver.Token; /** * Activity responsible of unlocking the KeyStore - * before using the {@link realm.io.android.SecureUserStore} to encrypt + * before using the {@link io.realm.android.SecureUserStore} to encrypt * the Token we get from the session */ public class MainActivity extends AppCompatActivity { - private CipherClient cryptoClient; private TextView txtKeystoreState; + + private SecureUserStore secureUserStore; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -55,12 +55,14 @@ protected void onCreate(Bundle savedInstanceState) { txtKeystoreState = (TextView) findViewById(R.id.txtLabelKeyStore); try { - cryptoClient = new CipherClient(this); - if (cryptoClient.isKeystoreUnlocked()) { + secureUserStore = new SecureUserStore(this); + SyncManager.setUserStore(secureUserStore); + + if (secureUserStore.isKeystoreUnlocked()) { buildSyncConf(); keystoreUnlockedMessage(); } else { - cryptoClient.unlockKeystore(); + secureUserStore.unlockKeystore(); } } catch (KeyStoreException e) { e.printStackTrace(); @@ -72,11 +74,11 @@ protected void onResume() { super.onResume(); try { // We return to the app after the KeyStore is unlocked or not. - if (cryptoClient.isKeystoreUnlocked()) { + if (secureUserStore.isKeystoreUnlocked()) { buildSyncConf(); - keystoreUnlockedMessage (); + keystoreUnlockedMessage(); } else { - keystoreLockedMessage (); + keystoreLockedMessage(); } } catch (KeyStoreException e) { e.printStackTrace(); @@ -84,27 +86,22 @@ protected void onResume() { } // build SyncConfiguration with a user store to store encrypted Token. - private void buildSyncConf () { - try { - SyncManager.setUserStore(new SecureUserStore(MainActivity.this)); - // the rest of Sync logic ... - SyncUser user = createTestUser(0); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); - Realm realm = Realm.getInstance(secureConfig); - // ... - - } catch (KeyStoreException e) { - e.printStackTrace(); - } + private void buildSyncConf() { + // the rest of Sync logic ... + SyncUser user = createTestUser(Long.MAX_VALUE); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); + Realm realm = Realm.getInstance(secureConfig); + // ... } + // Helpers private final static String USER_TOKEN = UUID.randomUUID().toString(); private final static String REALM_TOKEN = UUID.randomUUID().toString(); private static SyncUser createTestUser(long expires) { Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); - Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); + Token accessToken = new Token(REALM_TOKEN, "JohnDoe", "/foo", expires, new Token.Permission[]{Token.Permission.DOWNLOAD}); ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); JSONObject obj = new JSONObject(); @@ -124,12 +121,12 @@ private static SyncUser createTestUser(long expires) { } } - private void keystoreLockedMessage () { + private void keystoreLockedMessage() { txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorLocked)); txtKeystoreState.setText(R.string.locked_text); } - private void keystoreUnlockedMessage () { + private void keystoreUnlockedMessage() { txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorActivated)); txtKeystoreState.setText(R.string.unlocked_text); } From 2078e27d577490063959de1377116d84325b1e2e Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 13 Apr 2017 14:28:54 +0100 Subject: [PATCH 0635/2110] update to Core 2.6.0 & Sync 1.5.2 (#4486) --- CHANGELOG.md | 6 ++++++ dependencies.list | 4 ++-- .../src/main/cpp/io_realm_internal_TableQuery.cpp | 2 +- realm/realm-library/src/main/cpp/util.hpp | 1 - 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b4f92ab2f..7f5f32b234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.1.3 + +### Internal + +* Upgraded to Realm Sync 1.5.2. + ## 3.1.2 (2017-04-12) ### Bug Fixes diff --git a/dependencies.list b/dependencies.list index 408853f4de..9d4d44080f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.5.0 -REALM_SYNC_SHA256=2da0de557182e7d717d74808bf6d3f1e5f18d5c082744d29b96dca80555733a7 +REALM_SYNC_VERSION=1.5.2 +REALM_SYNC_SHA256=e7a0134b5b69c5a571e3f49901bb8d8ca634b166873c5a422909e7d2016a00e7 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index ca9baad7ef..bfa10d391f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -17,7 +17,7 @@ #include "io_realm_internal_TableQuery.h" #include -#include +#include #include #include diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index c0ec632f62..11d6a91cc4 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include From e512295306b06487da02f62aea953a7ebabe2f8e Mon Sep 17 00:00:00 2001 From: Realm CI Date: Thu, 13 Apr 2017 07:45:57 -0700 Subject: [PATCH 0636/2110] update to Core 2.6.0 & Sync 1.5.2 (#4486) (#4489) --- CHANGELOG.md | 7 +++++++ dependencies.list | 4 ++-- .../src/main/cpp/io_realm_internal_TableQuery.cpp | 2 +- realm/realm-library/src/main/cpp/util.hpp | 1 - 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9d91f8e2..06d64f7bc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ ### Internal +## 3.1.3 (YYYY-MM-DD) + +### Bug Fixes + +### Internal + +* Upgraded to Realm Sync 1.5.2. ## 3.1.2 (2017-04-12) diff --git a/dependencies.list b/dependencies.list index 408853f4de..9d4d44080f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.5.0 -REALM_SYNC_SHA256=2da0de557182e7d717d74808bf6d3f1e5f18d5c082744d29b96dca80555733a7 +REALM_SYNC_VERSION=1.5.2 +REALM_SYNC_SHA256=e7a0134b5b69c5a571e3f49901bb8d8ca634b166873c5a422909e7d2016a00e7 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index ca9baad7ef..bfa10d391f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -17,7 +17,7 @@ #include "io_realm_internal_TableQuery.h" #include -#include +#include #include #include diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index c0ec632f62..11d6a91cc4 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include From 49a5af054d7fba35656ace1c0786ebafb26eaa28 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 14 Apr 2017 00:58:02 +0900 Subject: [PATCH 0637/2110] fix a bug that Collection from backlink holds wrong table pointer. (#4485) * fix a bug that Collection from backlink holds wrong table pointer. * add a test that reproduce https://github.com/realm/realm-java/issues/4487 * add changelog entry * Update CHANGELOG.md * more detail test case name * remove extra line --- CHANGELOG.md | 6 +++++- .../io/realm/LinkingObjectsManagedTests.java | 16 ++++++++++++++++ .../main/java/io/realm/internal/Collection.java | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f5f32b234..c4aa819dc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -## 3.1.3 +## 3.1.3 (YYYY-MM-DD) + +### Bug Fixes + +* `equals()` and `hashCode()` of managed `RealmObject`s that come from linking objects don't work correctly (#4487). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 9c75ee8629..b06320925c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -151,6 +151,22 @@ public void basic_multipleReferencesFromParentList() { assertEquals(parent, child.getListParents().last()); } + // This test reproduces https://github.com/realm/realm-java/issues/4487 + @Test + public void issue4487_checkIfTableIsCorrect() { + realm.beginTransaction(); + final BacklinksTarget target = realm.createObject(BacklinksTarget.class); + target.setId(1); + final BacklinksSource source = realm.createObject(BacklinksSource.class); + source.setChild(target); + realm.commitTransaction(); + + final RealmResults parents = target.getParents(); + final BacklinksSource sourceFromBacklinks = parents.first(); + + assertEquals(source, sourceFromBacklinks); + } + // A listener registered on the backlinked object should not be called after the listener is removed @Test @RunTestInLooperThread diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index d8d7830bcd..bfe8ccaeeb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -335,7 +335,7 @@ public static Collection createBacklinksCollection(SharedRealm realm, UncheckedR row.getNativePtr(), srcTable.getNativePtr(), srcTable.getColumnIndex(srcFieldName)); - return new Collection(realm, row.getTable(), backlinksPtr, true); + return new Collection(realm, srcTable, backlinksPtr, true); } public Collection(SharedRealm sharedRealm, TableQuery query, From bad5b7faf54be1be3ca1d7344bec3ec689b3844b Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 14 Apr 2017 18:35:30 +0900 Subject: [PATCH 0638/2110] added field name information to exception messages (#4491) * add field name to the exception message when null is set to required field (#4484) * fix existing tests * fix cast style * update CHNAGELOG --- CHANGELOG.md | 1 + .../io/realm/DynamicRealmObjectTests.java | 26 +++++++++++-------- .../java/io/realm/RealmJsonTests.java | 19 ++++++++++++++ .../java/io/realm/RealmObjectTests.java | 9 ++++++- .../java/io/realm/RealmQueryTests.java | 8 +++--- realm/realm-library/src/main/cpp/util.hpp | 9 ++++--- 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4aa819dc2..41680d0fe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug Fixes * `equals()` and `hashCode()` of managed `RealmObject`s that come from linking objects don't work correctly (#4487). +* Field name was missing in exception message when `null` was set to required field (#4484). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index fac6bd2193..cdd594baf3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -598,25 +598,29 @@ public void setter_nullOnRequiredFieldsThrows() { DynamicRealmObject dObj = new DynamicRealmObject(obj); try { for (SupportedType type : SupportedType.values()) { + String fieldName = null; try { switch (type) { case OBJECT: continue; // Ignore - case LIST: dObj.setNull(NullTypes.FIELD_LIST_NULL); break; - case BOOLEAN: dObj.setNull(NullTypes.FIELD_BOOLEAN_NOT_NULL); break; - case BYTE: dObj.setNull(NullTypes.FIELD_BYTE_NOT_NULL); break; - case SHORT: dObj.setNull(NullTypes.FIELD_SHORT_NOT_NULL); break; - case INT: dObj.setNull(NullTypes.FIELD_INTEGER_NOT_NULL); break; - case LONG: dObj.setNull(NullTypes.FIELD_LONG_NOT_NULL); break; - case FLOAT: dObj.setNull(NullTypes.FIELD_FLOAT_NOT_NULL); break; - case DOUBLE: dObj.setNull(NullTypes.FIELD_DOUBLE_NOT_NULL); break; - case STRING: dObj.setNull(NullTypes.FIELD_STRING_NOT_NULL); break; - case BINARY: dObj.setNull(NullTypes.FIELD_BYTES_NOT_NULL); break; - case DATE: dObj.setNull(NullTypes.FIELD_DATE_NOT_NULL); break; + case LIST: fieldName = NullTypes.FIELD_LIST_NULL; break; + case BOOLEAN: fieldName = NullTypes.FIELD_BOOLEAN_NOT_NULL; break; + case BYTE: fieldName = NullTypes.FIELD_BYTE_NOT_NULL; break; + case SHORT: fieldName = NullTypes.FIELD_SHORT_NOT_NULL; break; + case INT: fieldName = NullTypes.FIELD_INTEGER_NOT_NULL; break; + case LONG: fieldName = NullTypes.FIELD_LONG_NOT_NULL; break; + case FLOAT: fieldName = NullTypes.FIELD_FLOAT_NOT_NULL; break; + case DOUBLE: fieldName = NullTypes.FIELD_DOUBLE_NOT_NULL; break; + case STRING: fieldName = NullTypes.FIELD_STRING_NOT_NULL; break; + case BINARY: fieldName = NullTypes.FIELD_BYTES_NOT_NULL; break; + case DATE: fieldName = NullTypes.FIELD_DATE_NOT_NULL; break; default: fail("Unknown type: " + type); } + + dObj.setNull(fieldName); fail("Setting value to null should throw: " + type); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(fieldName)); } } } finally { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index 351024dcd8..2d50dacb12 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -1447,6 +1447,7 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti realm.createObjectFromJson(NullTypes.class, array.getJSONObject(0)); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_STRING_NOT_NULL)); } catch (Exception e) { fail("Unexpected exception: " + e); } @@ -1456,6 +1457,7 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti realm.createObjectFromJson(NullTypes.class, array.getJSONObject(1)); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_BYTES_NOT_NULL)); } catch (Exception e) { fail("Unexpected exception: " + e); } @@ -1474,6 +1476,7 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti realm.createObjectFromJson(NullTypes.class, array.getJSONObject(3)); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_BYTE_NOT_NULL)); } catch (Exception e) { fail("Unexpected exception: " + e); } @@ -1483,6 +1486,7 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti realm.createObjectFromJson(NullTypes.class, array.getJSONObject(4)); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_SHORT_NOT_NULL)); } catch (Exception e) { fail("Unexpected exception: " + e); } @@ -1492,6 +1496,7 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti realm.createObjectFromJson(NullTypes.class, array.getJSONObject(5)); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_INTEGER_NOT_NULL)); } catch (Exception e) { fail("Unexpected exception: " + e); } @@ -1501,6 +1506,7 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti realm.createObjectFromJson(NullTypes.class, array.getJSONObject(6)); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_LONG_NOT_NULL)); } catch (Exception e) { fail("Unexpected exception: " + e); } @@ -1510,6 +1516,7 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti realm.createObjectFromJson(NullTypes.class, array.getJSONObject(7)); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_FLOAT_NOT_NULL)); } catch (Exception e) { fail("Unexpected exception: " + e); } @@ -1519,6 +1526,7 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti realm.createObjectFromJson(NullTypes.class, array.getJSONObject(8)); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_DOUBLE_NOT_NULL)); } catch (Exception e) { fail("Unexpected exception: " + e); } @@ -1528,6 +1536,7 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti realm.createObjectFromJson(NullTypes.class, array.getJSONObject(9)); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_DATE_NOT_NULL)); } catch (Exception e) { fail("Unexpected exception: " + e); } @@ -1552,6 +1561,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(0))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_STRING_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1561,6 +1571,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(1))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_BYTES_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1570,6 +1581,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(2))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_BOOLEAN_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1579,6 +1591,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(3))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_BYTE_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1588,6 +1601,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(4))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_SHORT_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1597,6 +1611,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(5))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_INTEGER_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1606,6 +1621,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(6))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_LONG_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1615,6 +1631,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(7))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_FLOAT_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1624,6 +1641,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(8))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_DOUBLE_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1633,6 +1651,7 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(9))); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_DATE_NOT_NULL)); } finally { realm.cancelTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index e5e80ec6b4..54d4e674df 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -1439,6 +1439,7 @@ public void setter_nullValueInRequiredField() { list.first().setFieldStringNotNull(null); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_STRING_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1449,6 +1450,7 @@ public void setter_nullValueInRequiredField() { list.first().setFieldBytesNotNull(null); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_BYTES_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1459,6 +1461,7 @@ public void setter_nullValueInRequiredField() { list.first().setFieldBooleanNotNull(null); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_BOOLEAN_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1466,9 +1469,10 @@ public void setter_nullValueInRequiredField() { // 4 Byte try { realm.beginTransaction(); - list.first().setFieldBytesNotNull(null); + list.first().setFieldByteNotNull(null); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_BYTE_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1481,6 +1485,7 @@ public void setter_nullValueInRequiredField() { list.first().setFieldFloatNotNull(null); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_FLOAT_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1491,6 +1496,7 @@ public void setter_nullValueInRequiredField() { list.first().setFieldDoubleNotNull(null); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_DOUBLE_NOT_NULL)); } finally { realm.cancelTransaction(); } @@ -1501,6 +1507,7 @@ public void setter_nullValueInRequiredField() { list.first().setFieldDateNotNull(null); fail(); } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_DATE_NOT_NULL)); } finally { realm.cancelTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 3017d94dfc..107836df2c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2558,14 +2558,14 @@ public void isNull_listFieldThrows() { realm.where(Owner.class).isNull("dogs"); fail(); } catch (IllegalArgumentException expected) { - assertEquals("Illegal Argument: RealmList is not nullable.", expected.getMessage()); + assertEquals("Illegal Argument: RealmList(dogs) is not nullable.", expected.getMessage()); } try { realm.where(Cat.class).isNull("owner.dogs"); fail(); } catch (IllegalArgumentException expected) { - assertEquals("Illegal Argument: RealmList is not nullable.", expected.getMessage()); + assertEquals("Illegal Argument: RealmList(dogs) is not nullable.", expected.getMessage()); } } @@ -2576,14 +2576,14 @@ public void isNotNull_listFieldThrows() { realm.where(Owner.class).isNotNull("dogs"); fail(); } catch (IllegalArgumentException expected) { - assertEquals("Illegal Argument: RealmList is not nullable.", expected.getMessage()); + assertEquals("Illegal Argument: RealmList(dogs) is not nullable.", expected.getMessage()); } try { realm.where(Cat.class).isNotNull("owner.dogs"); fail(); } catch (IllegalArgumentException expected) { - assertEquals("Illegal Argument: RealmList is not nullable.", expected.getMessage()); + assertEquals("Illegal Argument: RealmList(dogs) is not nullable.", expected.getMessage()); } } diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 11d6a91cc4..29c2813d17 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -343,7 +343,7 @@ inline bool TypeValid(JNIEnv* env, T* pTable, jlong columnIndex, int expectColTy int colType = pTable->get_column_type(col); if (colType != expectColType) { realm::jni_util::Log::e("Expected columnType %1, but got %2.", expectColType, pTable->get_column_type(col)); - ThrowException(env, IllegalArgument, "ColumnType invalid."); + ThrowException(env, IllegalArgument, "ColumnType of '" + std::string(pTable->get_column_name(col)) + "' is invalid."); return false; } return true; @@ -360,7 +360,8 @@ inline bool TypeIsLinkLike(JNIEnv* env, T* pTable, jlong columnIndex) realm::jni_util::Log::e("Expected columnType %1 or %2, but got %3", realm::type_Link, realm::type_LinkList, colType); - ThrowException(env, IllegalArgument, "ColumnType invalid: expected type_Link or type_LinkList"); + ThrowException(env, IllegalArgument, "ColumnType of '" + std::string(pTable->get_column_name(col)) + "' is invalid:" + " expected type_Link or type_LinkList"); return false; } @@ -374,7 +375,7 @@ inline bool ColIsNullable(JNIEnv* env, T* pTable, jlong columnIndex) } if (colType == realm::type_LinkList) { - ThrowException(env, IllegalArgument, "RealmList is not nullable."); + ThrowException(env, IllegalArgument, "RealmList(" + std::string(pTable->get_column_name(col)) + ") is not nullable."); return false; } @@ -383,7 +384,7 @@ inline bool ColIsNullable(JNIEnv* env, T* pTable, jlong columnIndex) } realm::jni_util::Log::e("Expected nullable column type"); - ThrowException(env, IllegalArgument, "This field is not nullable."); + ThrowException(env, IllegalArgument, "This field(" + std::string(pTable->get_column_name(col)) + ") is not nullable."); return false; } From bcdc277e238df2f10fde148a0a535fd9d28e84d8 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 17 Apr 2017 07:43:39 +0900 Subject: [PATCH 0639/2110] Fix exception thrown from backlinks field (#4500) * add test case that reproduce #4499 * now backlinks geters throw IllegalStateException if the object is deleted or not yet loaded. * update CHANGELOG * add test * update a test * modify method name Row.checkIfBacklinkAvailable() to Row.checkIfAttached() * update variable names in test --- CHANGELOG.md | 1 + .../processor/RealmProxyClassGenerator.java | 1 + .../io/realm/AllTypesRealmProxy.java | 1 + .../io/realm/LinkingObjectsManagedTests.java | 100 ++++++++++++++++++ .../io/realm/entities/BacklinksSource.java | 3 + .../io/realm/entities/BacklinksTarget.java | 4 + .../java/io/realm/internal/InvalidRow.java | 5 + .../java/io/realm/internal/PendingRow.java | 5 + .../src/main/java/io/realm/internal/Row.java | 5 + .../java/io/realm/internal/UncheckedRow.java | 7 ++ 10 files changed, 132 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41680d0fe3..0095d539b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * `equals()` and `hashCode()` of managed `RealmObject`s that come from linking objects don't work correctly (#4487). * Field name was missing in exception message when `null` was set to required field (#4484). +* Now throws `IllegalStateException` when a getter of linking objects is called against deleted or not yet loaded `RealmObject`s (#4499). ### Internal diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 0dc79ea020..44772ce258 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -569,6 +569,7 @@ private void emitBacklinkFieldAccessors(JavaWriter writer) throws IOException { writer.beginMethod(realmResultsType, metadata.getInternalGetter(backlink.getTargetField()), EnumSet.of(Modifier.PUBLIC)) .emitStatement("BaseRealm realm = proxyState.getRealm$realm()") .emitStatement("realm.checkIfValid()") + .emitStatement("proxyState.getRow$realm().checkIfAttached()") .beginControlFlow("if (" + cacheFieldName + " == null)") .emitStatement(cacheFieldName + " = RealmResults.createBacklinkResults(realm, proxyState.getRow$realm(), %s.class, \"%s\")", backlink.getSourceClass(), backlink.getSourceField()) diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index aaadd945d1..8eeb68202c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -397,6 +397,7 @@ public final AllTypesColumnInfo clone() { public RealmResults realmGet$parentObjects() { BaseRealm realm = proxyState.getRealm$realm(); realm.checkIfValid(); + proxyState.getRow$realm().checkIfAttached(); if (parentObjectsBacklinks == null) { parentObjectsBacklinks = RealmResults.createBacklinkResults(realm, proxyState.getRow$realm(), some.test.AllTypes.class, "columnObject"); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index b06320925c..7174a91870 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -46,6 +46,7 @@ import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -401,6 +402,105 @@ public void json_updateList() { assertTrue(parents.contains(parent)); } + @Test + @RunTestInLooperThread + public void linkingObjects_IllegalStateException_ifNotYetLoaded() { + final Realm realm = looperThread.realm; + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final BacklinksTarget target = realm.createObject(BacklinksTarget.class); + target.setId(1); + + final BacklinksSource source = realm.createObject(BacklinksSource.class); + source.setChild(target); + } + }); + + + final BacklinksTarget targetAsync = realm.where(BacklinksTarget.class) + .equalTo(BacklinksTarget.FIELD_ID, 1L).findFirstAsync(); + // precondition + assertFalse(targetAsync.isLoaded()); + + thrown.expect(IllegalStateException.class); + //noinspection ResultOfMethodCallIgnored + targetAsync.getParents(); + fail(); + } + + @Test + @RunTestInLooperThread + public void linkingObjects_IllegalStateException_ifDeleted() { + final Realm realm = looperThread.realm; + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final BacklinksTarget target = realm.createObject(BacklinksTarget.class); + target.setId(1); + + final BacklinksSource source = realm.createObject(BacklinksSource.class); + source.setChild(target); + } + }); + + final BacklinksTarget target = realm.where(BacklinksTarget.class) + .equalTo(BacklinksTarget.FIELD_ID, 1L).findFirst(); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + target.deleteFromRealm(); + } + }); + + // precondition + assertFalse(target.isValid()); + + thrown.expect(IllegalStateException.class); + //noinspection ResultOfMethodCallIgnored + target.getParents(); + fail(); + } + + @Test + @RunTestInLooperThread + public void linkingObjects_IllegalStateException_ifDeletedIndirectly() { + final Realm realm = looperThread.realm; + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final BacklinksTarget target1 = realm.createObject(BacklinksTarget.class); + target1.setId(1); + + final BacklinksSource source = realm.createObject(BacklinksSource.class); + source.setChild(target1); + } + }); + + final BacklinksTarget target = realm.where(BacklinksTarget.class) + .equalTo(BacklinksTarget.FIELD_ID, 1L).findFirst(); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + // delete target object indirectly + realm.where(BacklinksTarget.class).findAll().deleteAllFromRealm(); + } + }); + + // precondition + assertFalse(target.isValid()); + + thrown.expect(IllegalStateException.class); + //noinspection ResultOfMethodCallIgnored + target.getParents(); + fail(); + } + /** * Table validation should fail if the backinked column already exists in the target table. * The realm `backlinks-fieldInUse.realm` contains the classes `BacklinksSource` and `BacklinksTarget` diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java index 1419d5368b..ece9bca332 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java @@ -18,6 +18,9 @@ import io.realm.RealmObject; public class BacklinksSource extends RealmObject { + public static final String CLASS_NAME = "BacklinksSource"; + public static final String FIELD_CHILD = "child"; + private BacklinksTarget child; public BacklinksTarget getChild() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java index ddb0ea6f80..da3ee95be2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java @@ -20,6 +20,10 @@ import io.realm.annotations.LinkingObjects; public class BacklinksTarget extends RealmObject { + public static final String CLASS_NAME = "BacklinksTarget"; + public static final String FIELD_ID = "id"; + public static final String FIELD_PARENTS = "parents"; + private int id; @LinkingObjects("child") diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index 734e7a4261..a6819bdba4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -169,6 +169,11 @@ public boolean isAttached() { return false; } + @Override + public void checkIfAttached() { + throw getStubException(); + } + @Override public boolean hasColumn(String fieldName) { throw getStubException(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 65bff1f38e..8e99df9ca8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -196,6 +196,11 @@ public boolean isAttached() { return false; } + @Override + public void checkIfAttached() { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public boolean hasColumn(String fieldName) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index e3c81dc4ee..1e448d2058 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -110,6 +110,11 @@ public interface Row { */ boolean isAttached(); + /** + * Throws {@link IllegalStateException} if the row is not attached. + */ + void checkIfAttached(); + /** * Returns {@code true} if the field name exists. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index c018cc5cfe..82c0821f30 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -292,6 +292,13 @@ public boolean isAttached() { return nativePtr != 0 && nativeIsAttached(nativePtr); } + @Override + public void checkIfAttached() { + if (!isAttached()) { + throw new IllegalStateException("Object is no longer managed by Realm. Has it been deleted?"); + } + } + @Override public boolean hasColumn(String fieldName) { return nativeHasColumn(nativePtr, fieldName); From 857b16aebfc75eed58b65608f125f1161254e850 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 18 Apr 2017 11:23:06 +0200 Subject: [PATCH 0640/2110] Added support for SyncUser.isAdmin() (#4427) --- CHANGELOG.md | 3 +- .../java/io/realm/SyncUserTests.java | 25 +++++++- .../java/io/realm/util/SyncTestUtils.java | 26 +++++---- .../main/cpp/io_realm_RealmFileUserStore.cpp | 9 +-- .../java/io/realm/RealmFileUserStore.java | 4 +- .../java/io/realm/SyncCredentials.java | 43 ++++++++++---- .../objectServer/java/io/realm/SyncUser.java | 14 ++++- .../network/AuthenticateResponse.java | 4 +- .../objectserver/ObjectServerUser.java | 4 ++ .../io/realm/internal/objectserver/Token.java | 58 +++++++++++++------ .../java/io/realm/objectserver/AuthTests.java | 33 ++++++++++- 11 files changed, 169 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c0ea33a2c..98dac5cb21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Enhancements -* Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore' annotation (#4279). +* [ObjectServer] Added support for `SyncUser.isAdmin()` (#4353). +* Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore` annotation (#4279). ### Bug Fixes diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 0f4fa8b0b9..fe69cfff36 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -40,8 +40,10 @@ import io.realm.rule.RunInLooperThread; import io.realm.util.SyncTestUtils; +import static io.realm.util.SyncTestUtils.createTestAdminUser; import static io.realm.util.SyncTestUtils.createTestUser; import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -117,7 +119,7 @@ public AuthenticateResponse answer(InvocationOnMock invocationOnMock) throws Thr private AuthenticateResponse getNewRandomUser() { String identity = UUID.randomUUID().toString(); String userTokenValue = UUID.randomUUID().toString(); - return SyncTestUtils.createLoginResponse(userTokenValue, identity, Long.MAX_VALUE); + return SyncTestUtils.createLoginResponse(userTokenValue, identity, Long.MAX_VALUE, false); } // Test that current user is cleared if it is logged out @@ -155,6 +157,27 @@ public void all_validUsers() { assertTrue(users.entrySet().iterator().next().getValue().isValid()); } + @Test + public void isAdmin() { + SyncUser user1 = createTestUser(); + assertFalse(user1.isAdmin()); + + SyncUser user2 = createTestAdminUser(); + assertTrue(user2.isAdmin()); + } + + @Test + public void isAdmin_allUsers() { + UserStore userStore = SyncManager.getUserStore(); + SyncUser user = SyncTestUtils.createTestAdminUser(); + assertTrue(user.isAdmin()); + userStore.put(user); + + Map users = SyncUser.all(); + assertEquals(1, users.size()); + assertTrue(users.entrySet().iterator().next().getValue().isAdmin()); + } + // Tests that the user store returns the last user to login /* FIXME: This test fails because of wrong JSON string. @Test diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index de368019a5..20228694a1 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -24,7 +24,6 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; -import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.objectserver.ObjectServerUser; @@ -42,27 +41,32 @@ public static SyncUser createRandomTestUser() { UUID.randomUUID().toString(), UUID.randomUUID().toString(), DEFAULT_AUTH_URL, - Long.MAX_VALUE); + Long.MAX_VALUE, + false); + } + + public static SyncUser createTestAdminUser() { + return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, Long.MAX_VALUE, true); } public static SyncUser createTestUser() { - return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, Long.MAX_VALUE); + return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, Long.MAX_VALUE, false); } public static SyncUser createTestUser(long expires) { - return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, expires); + return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, expires, false); } public static SyncUser createTestUser(String authUrl) { - return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, authUrl, Long.MAX_VALUE); + return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, authUrl, Long.MAX_VALUE, false); } public static SyncUser createNamedTestUser(String userIdentifier) { - return createTestUser(USER_TOKEN, REALM_TOKEN, userIdentifier, DEFAULT_AUTH_URL, Long.MAX_VALUE); + return createTestUser(USER_TOKEN, REALM_TOKEN, userIdentifier, DEFAULT_AUTH_URL, Long.MAX_VALUE, false); } - public static SyncUser createTestUser(String userTokenValue, String realmTokenValue, String userIdentifier, String authUrl, long expires) { - Token userToken = new Token(userTokenValue, userIdentifier, null, expires, null); + public static SyncUser createTestUser(String userTokenValue, String realmTokenValue, String userIdentifier, String authUrl, long expires, boolean isAdmin) { + Token userToken = new Token(userTokenValue, userIdentifier, null, expires, null, isAdmin); Token accessToken = new Token(realmTokenValue, userIdentifier, "/foo", expires, new Token.Permission[] {Token.Permission.DOWNLOAD }); ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription(accessToken, "/data/data/myapp/files/default", false); @@ -84,12 +88,12 @@ public static SyncUser createTestUser(String userTokenValue, String realmTokenVa } public static AuthenticateResponse createLoginResponse(long expires) { - return createLoginResponse(USER_TOKEN, "JohnDoe", expires); + return createLoginResponse(USER_TOKEN, "JohnDoe", expires, false); } - public static AuthenticateResponse createLoginResponse(String userTokenValue, String userIdentity, long expires) { + public static AuthenticateResponse createLoginResponse(String userTokenValue, String userIdentity, long expires, boolean isAdmin) { try { - Token userToken = new Token(userTokenValue, userIdentity, null, expires, null); + Token userToken = new Token(userTokenValue, userIdentity, null, expires, null, isAdmin); JSONObject response = new JSONObject(); response.put("refresh_token", userToken.toJson()); return AuthenticateResponse.from(response.toString()); diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 313a2d169b..148c0c8914 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -59,16 +59,17 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetUser(JNIEnv* } JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser(JNIEnv* env, jclass, - jstring identity, jstring jsonToken, - jstring url) + jstring identity, jstring json_token, + jstring url, jboolean is_admin) { TR_ENTER() try { JStringAccessor user_identity(env, identity); // throws - JStringAccessor user_json_token(env, jsonToken); // throws + JStringAccessor user_json_token(env, json_token); // throws JStringAccessor auth_url(env, url); // throws - SyncManager::shared().get_user(user_identity, user_json_token, std::string(auth_url)); + SyncUser::TokenType token_type = (is_admin) ? SyncUser::TokenType::Admin : SyncUser::TokenType::Normal; + SyncManager::shared().get_user(user_identity, user_json_token, std::string(auth_url), token_type); } CATCH_STD() } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java index e208131397..6b03bf8573 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java @@ -32,7 +32,7 @@ public class RealmFileUserStore implements UserStore { public void put(SyncUser user) { String userJson = user.toJson(); // create or update token (userJson) using identity - nativeUpdateOrCreateUser(user.getIdentity(), userJson, user.getSyncUser().getAuthenticationUrl().toString()); + nativeUpdateOrCreateUser(user.getIdentity(), userJson, user.getSyncUser().getAuthenticationUrl().toString(), user.isAdmin()); } /** @@ -92,7 +92,7 @@ private static SyncUser toSyncUserOrNull(String userJson) { protected static native String[] nativeGetAllUsers(); - protected static native void nativeUpdateOrCreateUser(String identity, String jsonToken, String url); + protected static native void nativeUpdateOrCreateUser(String identity, String jsonToken, String url, boolean isAdmin); protected static native void nativeLogoutUser(String identity); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 986e3bfc03..02738e9eb1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -73,7 +73,7 @@ public class SyncCredentials { * * @param facebookToken a facebook userIdentifier acquired by logging into Facebook. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static SyncCredentials facebook(String facebookToken) { @@ -86,7 +86,7 @@ public static SyncCredentials facebook(String facebookToken) { * * @param googleToken a google userIdentifier acquired by logging into Google. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static SyncCredentials google(String googleToken) { @@ -101,10 +101,10 @@ public static SyncCredentials google(String googleToken) { * @param username username of the user. * @param password the users password. * @param createUser {@code true} if the user should be created, {@code false} otherwise. It is not possible to - * create a user twice when logging in, so this flag should only be set to {@code true} the first - * time a users log in. + * create a user twice when logging in, so this flag should only be set to {@code true} the first + * time a users log in. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static SyncCredentials usernamePassword(String username, String password, boolean createUser) { @@ -122,7 +122,7 @@ public static SyncCredentials usernamePassword(String username, String password, * @param username username of the user. * @param password the users password. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static SyncCredentials usernamePassword(String username, String password) { @@ -136,10 +136,10 @@ public static SyncCredentials usernamePassword(String username, String password) * @param userIdentifier String identifying the user. Usually a username or user token. * @param identityProvider provider used to verify the credentials. * @param userInfo data describing the user further or {@code null} if the user does not have any extra data. The - * data will be serialized to JSON, so all values must be mappable to a valid JSON data type. Custom - * classes will be converted using {@code toString()}. + * data will be serialized to JSON, so all values must be mappable to a valid JSON data type. Custom + * classes will be converted using {@code toString()}. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if any parameter is either {@code null} or empty. */ public static SyncCredentials custom(String userIdentifier, String identityProvider, Map userInfo) { @@ -154,17 +154,38 @@ public static SyncCredentials custom(String userIdentifier, String identityProvi /** * Creates credentials from an existing access token. Since an access token is the proof that a user already * has logged in. Credentials created this way are automatically assumed to have successfully logged in. - * This means that providing this credential to {@link SyncUser#login(SyncCredentials, String)} will always + * This means that providing these credentials to {@link SyncUser#login(SyncCredentials, String)} will always * succeed, but accessing any Realm after might fail if the token is no longer valid. + *

                      + * It is assumed that this user is not an administrator. Otherwise use {@link #accessToken(String, String, boolean)}. * * @param accessToken user's access token. * @param identifier user identifier. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} */ public static SyncCredentials accessToken(String accessToken, String identifier) { + return accessToken(accessToken, identifier, false); + } + + /** + * Creates credentials from an existing access token. Since an access token is the proof that a user already + * has logged in. Credentials created this way are automatically assumed to have successfully logged in. + * This means that providing these credentials to {@link SyncUser#login(SyncCredentials, String)} will always + * succeed, but accessing any Realm after might fail if the token is no longer valid. + * + * @param accessToken user's access token. + * @param identifier user identifier. + * @param isAdmin {@code true} if the access token is an administrator's token, {@code false} if it is a + * non-privileged users. It is to not possible to upgrade a non-admin token to an admin token by setting this + * value. It is purely informational. + * @return a set of credentials that can be used to log into the Object Server using + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} + */ + public static SyncCredentials accessToken(String accessToken, String identifier, boolean isAdmin) { HashMap userInfo = new HashMap(); userInfo.put("_token", accessToken); + userInfo.put("_isAdmin", isAdmin); return new SyncCredentials(identifier, IdentityProvider.ACCESS_TOKEN, userInfo); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index b1a78314af..ec1d2f0ba2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -191,7 +191,8 @@ public static SyncUser login(final SyncCredentials credentials, final String aut // the JSON response expected from the server. String userIdentifier = credentials.getUserIdentifier(); String token = (String) credentials.getUserInfo().get("_token"); - result = AuthenticateResponse.createValidResponseWithUser(userIdentifier, token); + boolean isAdmin = (Boolean) credentials.getUserInfo().get("_isAdmin"); + result = AuthenticateResponse.createValidResponseWithUser(userIdentifier, token, isAdmin); } else { final AuthenticationServer server = SyncManager.getAuthServer(); result = server.loginUser(credentials, authUrl); @@ -375,6 +376,17 @@ public boolean isValid() { return syncUser.isLoggedIn() && userToken != null && userToken.expiresMs() > System.currentTimeMillis(); } + /** + * Returns {@code true} if this user is an administrator on the Realm Object Server, {@code false} otherwise. + *

                      + * Administrators can access all Realms on the server as well as change the permissions of the Realms. + * + * @return {@code true} if the user is an administrator on the Realm Object Server, {@code false} otherwise. + */ + public boolean isAdmin() { + return syncUser.isAdmin(); + } + /** * Returns the identity of this user on the Realm Object Server. The identity is a guaranteed to be unique * among all users on the Realm Object Server. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index e0140385cb..6f45d1146f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -81,10 +81,10 @@ public static AuthenticateResponse from(ObjectServerError error) { * @param identifier user identifier. * @param refreshToken user's refresh token. */ - public static AuthenticateResponse createValidResponseWithUser(String identifier, String refreshToken) { + public static AuthenticateResponse createValidResponseWithUser(String identifier, String refreshToken, boolean isAdmin) { try { JSONObject response = new JSONObject(); - response.put(JSON_FIELD_REFRESH_TOKEN, new Token(refreshToken, identifier, null, Long.MAX_VALUE, Token.Permission.ALL).toJson()); + response.put(JSON_FIELD_REFRESH_TOKEN, new Token(refreshToken, identifier, null, Long.MAX_VALUE, Token.Permission.ALL, isAdmin).toJson()); return new AuthenticateResponse(response.toString()); } catch (JSONException e) { throw new RuntimeException(e); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java index 740125a54b..235c032a9b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java @@ -192,6 +192,10 @@ public Collection getRealms() { return realms.values(); } + public boolean isAdmin() { + return refreshToken.isAdmin(); + } + // Wrapper for all Realm data needed by a User that might get serialized. public static class AccessDescription { public Token accessToken; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java index 1d45632b40..3d642ef43c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/Token.java @@ -30,20 +30,29 @@ */ public class Token { + private static final String KEY_TOKEN = "token"; + private static final String KEY_TOKEN_DATA = "token_data"; + private static final String KEY_IDENTITY = "identity"; + private static final String KEY_PATH = "path"; + private static final String KEY_EXPIRES = "expires"; + private static final String KEY_ACCESS = "access"; + private static final String KEY_IS_ADMIN = "is_admin"; + private final String value; private final long expiresSec; private final Permission[] permissions; private final String identity; private final String path; + private final boolean isAdmin; public static Token from(JSONObject token) throws JSONException { - String value = token.getString("token"); - JSONObject tokenData = token.getJSONObject("token_data"); - String identity = tokenData.getString("identity"); - String path = tokenData.optString("path"); - long expiresSec = tokenData.getLong("expires"); + String value = token.getString(KEY_TOKEN); + JSONObject tokenData = token.getJSONObject(KEY_TOKEN_DATA); + String identity = tokenData.getString(KEY_IDENTITY); + String path = tokenData.optString(KEY_PATH); + long expiresSec = tokenData.getLong(KEY_EXPIRES); Permission[] permissions; - JSONArray access = tokenData.getJSONArray("access"); + JSONArray access = tokenData.getJSONArray(KEY_ACCESS); if (access != null) { permissions = new Permission[access.length()]; for (int i = 0; i < access.length(); i++) { @@ -56,11 +65,16 @@ public static Token from(JSONObject token) throws JSONException { } else { permissions = new Permission[0]; } + boolean isAdmin = tokenData.optBoolean(KEY_IS_ADMIN); - return new Token(value, identity, path, expiresSec, permissions); + return new Token(value, identity, path, expiresSec, permissions, isAdmin); } public Token(String value, String identity, String path, long expiresSec, Permission[] permissions) { + this(value, identity, path, expiresSec, permissions, false); + } + + public Token(String value, String identity, String path, long expiresSec, Permission[] permissions, boolean isAdmin) { this.value = value; this.identity = identity; this.path = path; @@ -70,6 +84,7 @@ public Token(String value, String identity, String path, long expiresSec, Permis } else { this.permissions = new Permission[0]; } + this.isAdmin = isAdmin; } public String value() { @@ -80,6 +95,8 @@ public String value() { public String path() { return path; } + public boolean isAdmin() { return isAdmin; } + /** * Returns when this token expires. Timestamp is in UTC seconds. */ @@ -107,17 +124,18 @@ public Permission[] permissions() { public JSONObject toJson() { JSONObject obj = new JSONObject(); try { - obj.put("token", value); + obj.put(KEY_TOKEN, value); JSONObject tokenData = new JSONObject(); - tokenData.put("identity", identity); - tokenData.put("path", path); - tokenData.put("expires", expiresSec); + tokenData.put(KEY_IDENTITY, identity); + tokenData.put(KEY_PATH, path); + tokenData.put(KEY_EXPIRES, expiresSec); JSONArray perms = new JSONArray(); for (int i = 0; i < permissions.length; i++) { perms.put(permissions[i].toString().toLowerCase(Locale.US)); } - tokenData.put("access", perms); - obj.put("token_data", tokenData); + tokenData.put(KEY_ACCESS, perms); + tokenData.put(KEY_IS_ADMIN, isAdmin); + obj.put(KEY_TOKEN_DATA, tokenData); return obj; } catch (JSONException e) { throw new RuntimeException("Could not convert Token to JSON.", e); @@ -126,15 +144,16 @@ public JSONObject toJson() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } Token token = (Token) o; - if (expiresSec != token.expiresSec) return false; - if (!value.equals(token.value)) return false; - if (!Arrays.equals(permissions, token.permissions)) return false; - if (!identity.equals(token.identity)) return false; + if (expiresSec != token.expiresSec) { return false; } + if (isAdmin != token.isAdmin) { return false; } + if (!value.equals(token.value)) { return false; } + if (!Arrays.equals(permissions, token.permissions)) { return false; } + if (!identity.equals(token.identity)) { return false; } return path != null ? path.equals(token.path) : token.path == null; } @@ -145,6 +164,7 @@ public int hashCode() { result = 31 * result + Arrays.hashCode(permissions); result = 31 * result + identity.hashCode(); result = 31 * result + (path != null ? path.hashCode() : 0); + result = 31 * result + (isAdmin ? 1 : 0); return result; } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index ae1cda7ac7..6ebb8efed5 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -6,6 +6,9 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.net.MalformedURLException; +import java.net.URL; + import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.Realm; @@ -24,6 +27,8 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +import static org.junit.Assert.assertFalse; + @RunWith(AndroidJUnit4.class) public class AuthTests extends BaseIntegrationTest { @@ -59,14 +64,38 @@ public void onError(ObjectServerError error) { }); } + @Test + @RunTestInLooperThread + public void login_newUser() { + SyncCredentials credentials = SyncCredentials.usernamePassword("myUser", "password", true); + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser user) { + assertFalse(user.isAdmin()); + try { + assertEquals(new URL(Constants.AUTH_URL), user.getAuthenticationUrl()); + } catch (MalformedURLException e) { + fail(e.toString()); + } + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + @Test @RunTestInLooperThread public void login_withAccessToken() { - SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentials = SyncCredentials.accessToken(admin.getAccessToken().value(), "custom-admin-user"); + SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + SyncCredentials credentials = SyncCredentials.accessToken(adminUser.getAccessToken().value(), "custom-admin-user", adminUser.isAdmin()); SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { + assertTrue(user.isAdmin()); final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL) .errorHandler(new SyncSession.ErrorHandler() { @Override From af6734c6b64010d88489217a6d1d7c812f152e40 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 18 Apr 2017 17:58:34 +0800 Subject: [PATCH 0641/2110] Clear pending collection first for findFirstAsync (#4497) Otherwise the pending query will be executed again if there is a local transaction in the listener. That would cause a infinite recursion or NPE (like #4495). Fix #4495 --- CHANGELOG.md | 1 + .../java/io/realm/RealmAsyncQueryTests.java | 38 +++++++++++++++++-- .../java/io/realm/internal/PendingRow.java | 6 ++- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0095d539b2..dca0ebcc46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * `equals()` and `hashCode()` of managed `RealmObject`s that come from linking objects don't work correctly (#4487). * Field name was missing in exception message when `null` was set to required field (#4484). * Now throws `IllegalStateException` when a getter of linking objects is called against deleted or not yet loaded `RealmObject`s (#4499). +* `NullPointerException` caused by local transaction inside the listener of `findFirstAsync()`'s results (#4495). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index b49618fab1..24bd82fda5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -632,9 +632,9 @@ public void onChange(AllTypes object) { @RunTestInLooperThread public void findFirstAsync_forceLoad() throws Throwable { final AtomicBoolean listenerCalled = new AtomicBoolean(false); - Realm Realm = looperThread.realm; - populateTestRealm(Realm, 10); - final AllTypes realmResults = Realm.where(AllTypes.class) + Realm realm = looperThread.realm; + populateTestRealm(realm, 10); + final AllTypes realmResults = realm.where(AllTypes.class) .between("columnLong", 4, 9) .findFirstAsync(); @@ -657,6 +657,38 @@ public void onChange(RealmModel object, ObjectChangeSet changeSet) { looperThread.testComplete(); } + // For issue https://github.com/realm/realm-java/issues/4495 + @Test + @RunTestInLooperThread + public void findFirstAsync_twoListenersOnSameInvalidObjectsCauseNPE() { + final Realm realm = looperThread.realm; + final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); + final AtomicBoolean firstListenerCalled = new AtomicBoolean(false); + + allTypes.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(AllTypes element) { + allTypes.removeChangeListener(this); + assertFalse(firstListenerCalled.getAndSet(true)); + if (!element.isValid()) { + realm.beginTransaction(); + realm.createObject(AllTypes.class); + realm.commitTransaction(); + } + } + }); + + allTypes.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(AllTypes element) { + allTypes.removeChangeListener(this); + assertTrue(firstListenerCalled.get()); + assertFalse(element.isValid()); + looperThread.testComplete(); + } + }); + } + // ************************************** // *** 'findAllSorted' async queries *** // ************************************** diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 8e99df9ca8..63a86d2bb0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -227,6 +227,9 @@ private void notifyFrontEnd() { if (pendingCollection.isValid()) { // PendingRow will always get the first Row of the query since we only support findFirst. UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); + // Clear the pending collection immediately in case beginTransaction is called in the listener which will + // execute the query again. + clearPendingCollection(); // If no rows returned by the query, notify the frontend with an invalid row. if (uncheckedRow != null) { Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; @@ -236,9 +239,10 @@ private void notifyFrontEnd() { // No row matches the query, return a invalid row. frontEnd.onQueryFinished(InvalidRow.INSTANCE); } + } else { + clearPendingCollection(); } - clearPendingCollection(); } // Execute the query immediately and call frontend's onQueryFinished(). From 4405f6eb869124caec5ece120f68bde8c745fc50 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 18 Apr 2017 12:45:04 +0800 Subject: [PATCH 0642/2110] Fix return values in JNI unchecked row Fix #4505 --- .../src/main/cpp/io_realm_internal_CheckedRow.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp index 142e64d0c4..b584438ada 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp @@ -35,7 +35,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnName( jlong nativeRowPtr, jlong columnIndex) { if (!ROW_AND_COL_INDEX_VALID(env, ROW(nativeRowPtr), columnIndex)) { - return NULL; + return nullptr; } return Java_io_realm_internal_UncheckedRow_nativeGetColumnName(env, obj, nativeRowPtr, columnIndex); @@ -82,7 +82,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeGetBoolean(JN jlong nativeRowPtr, jlong columnIndex) { if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Bool)) { - return 0; + return JNI_FALSE; } return Java_io_realm_internal_UncheckedRow_nativeGetBoolean(env, obj, nativeRowPtr, columnIndex); @@ -122,7 +122,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetString(JNIE jlong nativeRowPtr, jlong columnIndex) { if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_String)) { - return 0; + return nullptr; } return Java_io_realm_internal_UncheckedRow_nativeGetString(env, obj, nativeRowPtr, columnIndex); @@ -133,7 +133,7 @@ JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_CheckedRow_nativeGetByteArra jlong columnIndex) { if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Binary)) { - return 0; + return nullptr; } return Java_io_realm_internal_UncheckedRow_nativeGetByteArray(env, obj, nativeRowPtr, columnIndex); @@ -153,7 +153,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeIsNullLink(JN jlong nativeRowPtr, jlong columnIndex) { if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) { - return 0; + return JNI_FALSE; } return Java_io_realm_internal_UncheckedRow_nativeIsNullLink(env, obj, nativeRowPtr, columnIndex); From 9122c0a9255ce4a3f545619707d1af08457a0caa Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 18 Apr 2017 21:43:27 +0800 Subject: [PATCH 0643/2110] Update Object Store and test cases (#4507) Update OS to 7a1924b4cf Fix #4502 --- CHANGELOG.md | 1 + .../java/io/realm/RealmObjectTests.java | 29 +++++++++++++++++++ .../java/io/realm/RealmResultsTests.java | 27 +++++++++++++++++ .../main/cpp/io_realm_internal_OsObject.cpp | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dca0ebcc46..33d25a7954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Field name was missing in exception message when `null` was set to required field (#4484). * Now throws `IllegalStateException` when a getter of linking objects is called against deleted or not yet loaded `RealmObject`s (#4499). * `NullPointerException` caused by local transaction inside the listener of `findFirstAsync()`'s results (#4495). +* Native crash when adding listeners to `RealmObject` after removing listeners from the same `RealmObject` before (#4502). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 54d4e674df..d92e76a252 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -1790,6 +1790,35 @@ public void onChange(Dog object, ObjectChangeSet changeSet) { looperThread.testComplete(); } + @Test + @RunTestInLooperThread + public void removeAllChangeListeners_thenAdd() { + final Realm realm = looperThread.realm; + realm.beginTransaction(); + Dog dog = realm.createObject(Dog.class); + dog.setAge(13); + realm.commitTransaction(); + dog.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(Dog object) { + fail(); + } + }); + dog.removeAllChangeListeners(); + + dog.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(Dog dog) { + assertEquals(14, dog.getAge()); + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + dog.setAge(14); + realm.commitTransaction(); + } + @Test @RunTestInLooperThread public void removeChangeListener_throwOnUnmanagedObject() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 4c4069a9e8..6eefd9023f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1138,6 +1138,33 @@ public void run() { }); } + @Test + @RunTestInLooperThread + public void removeAllChangeListeners_thenAdd() { + final Realm realm = looperThread.realm; + RealmResults collection = realm.where(AllTypes.class).findAll(); + + collection.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults element) { + fail(); + } + }); + collection.removeAllChangeListeners(); + + collection.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults results) { + assertEquals(1, results.size()); + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + realm.createObject(AllTypes.class); + realm.commitTransaction(); + } + @Test public void deleteAndDeleteAll() { realm.beginTransaction(); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 95c3fa24c5..62887708c9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -189,7 +189,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObject_nativeStartListening(JNIE // The wrapper pointer will be used in the callback. But it should never become an invalid pointer when the // notification block gets called. This should be guaranteed by the Object Store that after the notification // token is destroyed, the block shouldn't be called. - wrapper->m_notification_token = wrapper->m_object.add_notification_block(ChangeCallback(wrapper)); + wrapper->m_notification_token = wrapper->m_object.add_notification_callback(ChangeCallback(wrapper)); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 3b6c0f6110..7a1924b4cf 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 3b6c0f611061dddabc489f0b9f264306893c96c8 +Subproject commit 7a1924b4cf24f823825e9bc32b236b8a67ad52fe From 4b728ff2906e583a56c306e020dd9e97e0bdf450 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 20 Apr 2017 15:11:34 +0800 Subject: [PATCH 0644/2110] Update realm sync to 1.6.0 (#4523) core to 2.6.1 Fix #4461 --- CHANGELOG.md | 4 +++- dependencies.list | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33d25a7954..06153c68ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ * Now throws `IllegalStateException` when a getter of linking objects is called against deleted or not yet loaded `RealmObject`s (#4499). * `NullPointerException` caused by local transaction inside the listener of `findFirstAsync()`'s results (#4495). * Native crash when adding listeners to `RealmObject` after removing listeners from the same `RealmObject` before (#4502). +* Native crash with "Invalid argument" error happened on some Android 7.1.1 devices when opening Realm on external storage (#4461). ### Internal -* Upgraded to Realm Sync 1.5.2. +* Upgraded to Realm Sync 1.6.0. +* Upgraded to Realm Core 2.6.1. ## 3.1.2 (2017-04-12) diff --git a/dependencies.list b/dependencies.list index 9d4d44080f..06db1eeb3f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.5.2 -REALM_SYNC_SHA256=e7a0134b5b69c5a571e3f49901bb8d8ca634b166873c5a422909e7d2016a00e7 +REALM_SYNC_VERSION=1.6.0 +REALM_SYNC_SHA256=e8a973dbe6ab33ac49d3d0e45d6b63d69cec8d1d87d9a2311fcdd02767f76cf8 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. From ff1d01ab0f5a3956d0d948b36d0acc483e187251 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 20 Apr 2017 15:58:39 +0800 Subject: [PATCH 0645/2110] Update Object Store (#4522) To dfddfa7f7bf5 Fix #4474 --- CHANGELOG.md | 1 + .../java/io/realm/ObjectChangeSetTests.java | 46 +++++++++++++++++++ .../java/io/realm/entities/Dog.java | 1 + realm/realm-library/src/main/cpp/object-store | 2 +- 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06153c68ce..baa13d9b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * `NullPointerException` caused by local transaction inside the listener of `findFirstAsync()`'s results (#4495). * Native crash when adding listeners to `RealmObject` after removing listeners from the same `RealmObject` before (#4502). * Native crash with "Invalid argument" error happened on some Android 7.1.1 devices when opening Realm on external storage (#4461). +* `OrderedRealmCollectionChangeListener` didn't report change ranges correctly when circular link's field changed (#4474). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java index 947b197eea..2069ec6eee 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java @@ -30,6 +30,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.Dog; +import io.realm.entities.Owner; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; @@ -417,4 +418,49 @@ public void onChange(DynamicRealmObject object, ObjectChangeSet changeSet) { } realm.commitTransaction(); } + + // For https://github.com/realm/realm-java/issues/4474 + @Test + @RunTestInLooperThread + public void allParentObjectShouldBeInChangeSet() { + Realm realm = looperThread.realm; + + realm.beginTransaction(); + Owner owner = realm.createObject(Owner.class); + Dog dog1 = realm.createObject(Dog.class); + dog1.setOwner(owner); + dog1.setHasTail(true); + owner.getDogs().add(dog1); + Dog dog2 = realm.createObject(Dog.class); + dog2.setOwner(owner); + dog2.setHasTail(true); + owner.getDogs().add(dog2); + Dog dog3 = realm.createObject(Dog.class); + dog3.setOwner(owner); + dog3.setHasTail(true); + owner.getDogs().add(dog3); + + realm.commitTransaction(); + + RealmResults dogs = realm.where(Dog.class).equalTo(Dog.FIELD_HAS_TAIL, true).findAll(); + looperThread.keepStrongReference.add(dogs); + dogs.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + assertEquals(1, changeSet.getDeletions().length); + assertEquals(0, changeSet.getInsertions().length); + + assertEquals(1, changeSet.getChangeRanges().length); + assertEquals(0, changeSet.getChangeRanges()[0].startIndex); + assertEquals(2, changeSet.getChangeRanges()[0].length); + + looperThread.testComplete(); + } + }); + + realm.beginTransaction(); + dog3.setHasTail(false); + realm.commitTransaction(); + looperThread.testComplete(); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/Dog.java b/realm/realm-library/src/androidTest/java/io/realm/entities/Dog.java index d6b2046a9f..4064e2b395 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/Dog.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/Dog.java @@ -30,6 +30,7 @@ public class Dog extends RealmObject { public static final String FIELD_HEIGHT = "height"; public static final String FIELD_WEIGHT = "weight"; public static final String FIELD_BIRTHDAY = "birthday"; + public static final String FIELD_HAS_TAIL = "hasTail"; @Index private String name; diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 7a1924b4cf..dfddfa7f7b 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 7a1924b4cf24f823825e9bc32b236b8a67ad52fe +Subproject commit dfddfa7f7bf564619e2257243c252ffad6d5c9c3 From 005c1fdd070a586d731b4266cc8d13214cbffb40 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 20 Apr 2017 10:13:32 +0100 Subject: [PATCH 0646/2110] Sync reconnect (#4498) * calling Sync reconnect when network is back --- CHANGELOG.md | 4 +++ .../src/main/cpp/io_realm_SyncManager.cpp | 9 ++++++ .../java/io/realm/SyncManager.java | 31 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index baa13d9b88..38133bc6bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 3.1.3 (YYYY-MM-DD) +### Enhancements + +* [ObjectServer] Resume synchronization as soon as the connectivity is back (#4141). + ### Bug Fixes * `equals()` and `hashCode()` of managed `RealmObject`s that come from linking objects don't work correctly (#4487). diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index 2e80fe147e..d9f15dd859 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -100,3 +100,12 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeSimulateSyncError(JNIEnv* } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReconnect(JNIEnv* env, jclass) +{ + TR_ENTER() + try { + SyncManager::shared().reconnect(); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 0d37ce79eb..8ef5862f5f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -26,6 +26,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.Keep; import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.network.OkHttpAuthenticationServer; import io.realm.log.RealmLog; @@ -98,6 +99,19 @@ public void onError(SyncSession session, ObjectServerError error) { private static volatile AuthenticationServer authServer = new OkHttpAuthenticationServer(); private static volatile UserStore userStore; + private static NetworkStateReceiver.ConnectionListener networkListener = new NetworkStateReceiver.ConnectionListener() { + @Override + public void onChange(boolean connectionAvailable) { + if (connectionAvailable) { + RealmLog.debug("NetworkListener: Connection available"); + // notify all sessions + notifyNetworkIsBack(); + } else { + RealmLog.debug("NetworkListener: Connection lost"); + } + } + }; + static volatile SyncSession.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; // Initialize the SyncManager @@ -181,6 +195,10 @@ public static synchronized SyncSession getSession(SyncConfiguration syncConfigur if (session == null) { session = new SyncSession(syncConfiguration); sessions.put(syncConfiguration.getPath(), session); + if (sessions.size() == 1) { + RealmLog.debug("first session created add network listener"); + NetworkStateReceiver.addListener(networkListener); + } } return session; @@ -199,6 +217,10 @@ private static synchronized void removeSession(SyncConfiguration syncConfigurati if (syncSession != null) { syncSession.close(); } + if (sessions.isEmpty()) { + RealmLog.debug("last session dropped, remove network listener"); + NetworkStateReceiver.removeListener(networkListener); + } } static AuthenticationServer getAuthServer() { @@ -248,6 +270,14 @@ private static synchronized void notifyErrorHandler(int errorCode, String errorM } } + private static synchronized void notifyNetworkIsBack() { + try { + nativeReconnect(); + } catch (Exception exception) { + RealmLog.error(exception); + } + } + /** * This is called from the Object Store (through JNI) to request an {@code access_token} for * the session specified by sessionPath. @@ -303,4 +333,5 @@ static void simulateClientReset(SyncSession session) { protected static native void nativeInitializeSyncManager(String syncBaseDir); private static native void nativeReset(); private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); + private static native void nativeReconnect(); } From d220eb8deffae3d8ee803baa27983e091b19c0e7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 20 Apr 2017 17:41:28 +0800 Subject: [PATCH 0647/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38133bc6bd..df5dcbf3ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.1.3 (YYYY-MM-DD) +## 3.1.3 (2017-04-20) ### Enhancements From 01522412121e8932d792f97bff9bd09f5bc84973 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 20 Apr 2017 17:41:29 +0800 Subject: [PATCH 0648/2110] Release v3.1.3 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index dee8a83e4c..711ee4f504 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.3-SNAPSHOT \ No newline at end of file +3.1.3 \ No newline at end of file From 592204339f29d203cb6395028f777e419c931373 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 20 Apr 2017 17:41:29 +0800 Subject: [PATCH 0649/2110] Prepare next release v3.1.4-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 711ee4f504..d66c3337d0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.3 \ No newline at end of file +3.1.4-SNAPSHOT \ No newline at end of file From a25c9b5c4f47fd51588617cb51e2676c24ebf82f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 20 Apr 2017 16:12:53 +0200 Subject: [PATCH 0650/2110] Re-add Realm.refresh() (#4515) --- CHANGELOG.md | 1 + .../androidTest/java/io/realm/RealmTests.java | 105 ++++++++++++++++++ .../src/main/java/io/realm/BaseRealm.java | 18 +++ 3 files changed, 124 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c010a792..9134f567ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * [ObjectServer] Added support for `SyncUser.isAdmin()` (#4353). * Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore` annotation (#4279). +* Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index f95147ab65..22ad7a1231 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -57,6 +57,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -3829,4 +3830,108 @@ public boolean accept(File dir, String name) { realmOnExternalStorage = Realm.getInstance(config); realmOnExternalStorage.close(); } + + @Test + @RunTestInLooperThread + public void refresh_triggerNotifications() { + final CountDownLatch bgThreadDone = new CountDownLatch(1); + final AtomicBoolean listenerCalled = new AtomicBoolean(false); + Realm realm = looperThread.realm; + RealmResults results = realm.where(AllTypes.class).findAll(); + assertEquals(0, results.size()); + results.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults results) { + assertEquals(1, results.size()); + listenerCalled.set(true); + } + }); + + // Advance the Realm on a background while blocking this thread. When we refresh, it should trigger + // the listener. + new Thread(new Runnable() { + @Override + public void run() { + Realm realm = Realm.getInstance(looperThread.realmConfiguration); + realm.beginTransaction(); + realm.createObject(AllTypes.class); + realm.commitTransaction(); + realm.close(); + bgThreadDone.countDown(); + } + }).run(); + TestHelper.awaitOrFail(bgThreadDone); + + realm.refresh(); + assertTrue(listenerCalled.get()); + looperThread.testComplete(); + } + + @Test + public void refresh_nonLooperThreadAdvances() { + final CountDownLatch bgThreadDone = new CountDownLatch(1); + RealmResults results = realm.where(AllTypes.class).findAll(); + assertEquals(0, results.size()); + + new Thread(new Runnable() { + @Override + public void run() { + Realm realm = Realm.getInstance(RealmTests.this.realm.getConfiguration()); + realm.beginTransaction(); + realm.createObject(AllTypes.class); + realm.commitTransaction(); + realm.close(); + bgThreadDone.countDown(); + } + }).run(); + TestHelper.awaitOrFail(bgThreadDone); + + realm.refresh(); + assertEquals(1, results.size()); + } + + @Test + @RunTestInLooperThread + public void refresh_forceSynchronousNotifications() { + final CountDownLatch bgThreadDone = new CountDownLatch(1); + final AtomicBoolean listenerCalled = new AtomicBoolean(false); + Realm realm = looperThread.realm; + RealmResults results = realm.where(AllTypes.class).findAllAsync(); + results.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults results) { + // Will be forced synchronous + assertEquals(1, results.size()); + listenerCalled.set(true); + } + }); + + new Thread(new Runnable() { + @Override + public void run() { + Realm realm = Realm.getInstance(looperThread.realmConfiguration); + realm.beginTransaction(); + realm.createObject(AllTypes.class); + realm.commitTransaction(); + realm.close(); + bgThreadDone.countDown(); + } + }).start(); + TestHelper.awaitOrFail(bgThreadDone); + + realm.refresh(); + assertTrue(listenerCalled.get()); + looperThread.testComplete(); + } + + @Test + public void refresh_insideTransactionThrows() { + realm.beginTransaction(); + try { + realm.refresh(); + fail(); + } catch (IllegalStateException ignored) { + } + realm.cancelTransaction(); + } } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 894e581e0a..047a29377a 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -113,6 +113,24 @@ public boolean isAutoRefresh() { return sharedRealm.isAutoRefresh(); } + /** + * Refreshes the Realm instance and all the RealmResults and RealmObjects instances coming from it. + * It also calls any listeners associated with the Realm if neeeded. + *

                      + * WARNING: Calling this on a thread with async queries will turn those queries into synchronous queries. + * In most cases it is better to use {@link RealmChangeListener}s to be notified about changes to the + * Realm on a given thread than it is to use this method. + * + * @throws IllegalStateException if attempting to refresh from within a transaction. + */ + public void refresh() { + checkIfValid(); + if (isInTransaction()) { + throw new IllegalStateException("Cannot refresh a Realm instance inside a transaction."); + } + sharedRealm.refresh(); + } + /** * Checks if the Realm is currently in a transaction. * From 6211660ba8a1aacd38ab53d3c79a576da24dab74 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 24 Apr 2017 15:54:30 +0800 Subject: [PATCH 0651/2110] Script for doing release locally (#4525) There are still more work needed to do release on Jenkins. Before that a script to help release locally is useful. This script currently only handle the patch release. --- tools/release.sh | 239 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100755 tools/release.sh diff --git a/tools/release.sh b/tools/release.sh new file mode 100755 index 0000000000..33a01831b7 --- /dev/null +++ b/tools/release.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env bash + +# Script to make release on the local machine. +# See https://github.com/realm/realm-wiki/wiki/Java-Release-Checklist for more details. +# FIXME: Only patch release is supported now. + +set -euo pipefail +IFS=$'\n\t' + +usage() { +cat < +EOF +} + +###################################### +# Input Validation +###################################### + +if [ "$#" -eq 0 ] || [ "$#" -gt 1 ] ; then + usage + exit 1 +fi + +###################################### +# Variables +###################################### + +BRANCH_TO_RELEASE="$1" +VERSION="" +REALM_IO_PATH=${REALM_IO_PATH:-} +REALM_JAVA_PATH=$(pwd) + +check_adb_device() { + if ! adb get-state 1>/dev/null 2>&1 ; then + read -n 1 -s -p -r "Attach a test device or start the emulator then press any key to continue..." + echo "" + check_adb_device + fi +} + +check_env() { + echo "Checking environment..." + + # Try to find s3cmd + path_to_s3cmd=$(which s3cmd) + if [[ ! -x "$path_to_s3cmd" ]] ; then + echo "Cannot find executable file 's3cmd'." + exit -1 + fi + if [[ ! -e "$HOME/.s3cfg" ]] ; then + echo "'$HOME/.s3cfg' cannot be found." + exit -1 + fi + + # Check BinTray credentials + if ! grep "bintrayUser=realm" "$HOME/.gradle/gradle.properties" > /dev/null ; then + echo "'bintrayUser' is not set in the '$HOME/.gradle/gradle.properties'." + exit -1 + fi + + if ! grep "bintrayKey=.*" "$HOME/.gradle/gradle.properties" > /dev/null; then + echo "'bintrayKey' is not set in the '$HOME/.gradle/gradle.properties'." + exit -1 + fi + + # Check gradle params + if grep buildTargetABIs "$HOME/.gradle/gradle.properties" | grep -v "^#" > /dev/null ; then + echo "'buildTargetABIs' should be disabled in the '$HOME/.gradle/gradle.properties'." + exit -1 + fi + if grep ccachePath "$HOME/.gradle/gradle.properties" | grep -v "^#" > /dev/null ; then + echo "'ccachePath' should be disabled in the '$HOME/.gradle/gradle.properties'." + exit -1 + fi + if grep lcachePath "$HOME/.gradle/gradle.properties" | grep -v "^#" > /dev/null ; then + echo "'lcachePath' should be disabled in the '$HOME/.gradle/gradle.properties'." + exit -1 + fi + + if [[ -z ${REALM_IO_PATH} ]] ; then + REALM_IO_PATH="$(pwd)/../realm.io" + fi + if [[ ! -e ${REALM_IO_PATH} ]] ; then + echo "Please set 'REALM_IO_PATH' to the 'realm.io' repository path to publish javadoc." + exit -1 + fi +} + +prepare_branch() { + echo "Preparing release branch..." + + git fetch --all + git checkout releases + git reset --hard origin/releases + if [[ "$BRANCH_TO_RELEASE" != "releases" ]] ; then + echo "Releasing from other branches than 'releases' is not supported right now." + exit -1 + fi + + git clean -xfd + git submodule update --init --recursive + + if ! grep -q "SNAPSHOT" version.txt ; then + echo "'version.txt' doesn't contain 'SNAPSHOT'." + exit -1 + fi + + version_in_changelog=$(head -1 CHANGELOG.md | grep -o "[0-9]*\.[0-9]*\.[0-9]*") + VERSION=$(grep -o "[0-9]*\.[0-9]*\.[0-9]*" version.txt) + if [[ "${VERSION}" != "${version_in_changelog}" ]] ; then + echo "'version.txt' doens't match the entry in 'CHANGELOG.md'. ${VERSION} vs ${version_in_changelog}." + exit -1 + fi + + # Check if tag exists in remote + if git ls-remote --tags origin | grep "v${VERSION}" > /dev/null ; then + echo "Tag 'v${VERSION}' exists in remote!" + exit -1 + fi + if git tag | grep "v${VERSION}" > /dev/null ; then + git tag -d "v${VERSION}" + fi + + # Update date in change log + cur_date=$(date "+%F") + sed -i "1 s/YYYY-MM-DD/${cur_date}/" CHANGELOG.md + git add CHANGELOG.md + git commit -m "Update changelog date" + + # This will create 2 new commits to change the version.txt. The top one is the next release version + SNAPSHOT. + ./gradlew release + # Checkout the one with current version number. + git checkout HEAD~1 +} + +build() { + echo "Building..." + + ./gradlew assemble + + echo "Verifying examples..." + + check_adb_device + + # Verify examples + (cd examples && ./gradlew uninstallAll && ./gradlew monkeyDebug) +} + +upload_to_bintray() { + echo "Uploading artifacts to Bintray..." + # Upload to bintray + ./gradlew bintrayUpload + + echo "Done." + echo "1. Log into BinTray(https://bintray.com) with the Realm account;" + echo "2. Goto https://bintray.com/realm/maven and check if there are 16 artifacts to publish." + echo "3. Press 'Publish'." + while true + do + read -r -p "Have you published 16 artifacts on Bintray? Type 'Yes' to continue... " input + + case "$input" in + [yY][eE][sS]) + break + ;; + esac + done +} + +publish_distribution() { + echo "Publishing distribution package..." + + # Create distribution package + ./gradlew distributionPackage + pushd build/outputs/distribution + unzip "realm-java-${VERSION}.zip" + + # Test + check_adb_device + pushd examples/ + ./gradlew uninstallAll + ./gradlew monkeyRelease + popd + popd + ./gradlew distribute +} + +push_release() { + echo "Pushing releases branch to origin..." + + # Push branch & tag + git checkout releases + git push origin releases + git push origin "v${VERSION}" +} + +publish_javadoc() { + echo "Publishing javadoc..." + cd "${REALM_IO_PATH}" + git fetch --all + branch_name=publish_java_doc/${VERSION} + git checkout origin/master -b "${branch_name}" + + while true + do + read -r -p "Type 'Yes' to clean uncommitted files in 'source/en/docs/java'... " input + + case "$input" in + [yY][eE][sS]) + break + ;; + esac + done + git clean -xfd ./source/en/docs/java/ + bundle exec rake generate:java_docs[$VERSION] + cp -R "${REALM_JAVA_PATH}/realm/realm-library/build/docs/javadoc/*" ./source/en/docs/java/latest/api/ + bundle exec rake generate:inject_ga_latest_java_api + git add ./source/en/docs/java/ + git commit -m "Release realm-java doc ${VERSION}" + git push origin "${branch_name}" + path_to_hub=$(which hub) + if [[ ! -x "$path_to_hub" ]] ; then + echo "'hub' cannot be found in the executable path." + echo "Please create a pull request manually in realm.io repo with branch ${branch_name}." + exit -1 + else + hub pull-request + echo "A pull request has been created for branch ${branch_name}." + fi +} + +check_env +prepare_branch +build +upload_to_bintray +publish_distribution +push_release +publish_javadoc From 4ef4b875955eb74d494c3758ea4f5001f065575d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 25 Apr 2017 09:58:31 +0800 Subject: [PATCH 0652/2110] Fix flaky test caused by non-closed Realm (#4528) When the case is using ExpectedException, TestRealmConfigurationFactory won't be able to detect the Realm instance is not closed. --- .../java/io/realm/RealmAsyncQueryTests.java | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 24bd82fda5..4d4c051258 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -137,6 +137,7 @@ public void onSuccess() { Realm newRealm = Realm.getInstance(looperThread.realmConfiguration); assertEquals(1, newRealm.where(Owner.class).count()); assertEquals("Owner", newRealm.where(Owner.class).findFirst().getName()); + newRealm.close(); looperThread.testComplete(); } }); @@ -186,6 +187,7 @@ public void onError(Throwable error) { assertEquals(0, newRealm.where(Owner.class).count()); assertNull(newRealm.where(Owner.class).findFirst()); assertEquals(runtimeException, error); + newRealm.close(); looperThread.testComplete(); } }); @@ -368,32 +370,40 @@ public void onError(Throwable error) { public void executeTransactionAsync_onSuccessOnNonLooperThreadThrows() { Realm realm = Realm.getInstance(configFactory.createConfiguration()); thrown.expect(IllegalStateException.class); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { + try { + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { - } - }, new Realm.Transaction.OnSuccess() { - @Override - public void onSuccess() { - } - }); + } + }, new Realm.Transaction.OnSuccess() { + @Override + public void onSuccess() { + } + }); + } finally { + realm.close(); + } } @Test public void executeTransactionAsync_onErrorOnNonLooperThreadThrows() { Realm realm = Realm.getInstance(configFactory.createConfiguration()); thrown.expect(IllegalStateException.class); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { + try { + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { - } - }, new Realm.Transaction.OnError() { - @Override - public void onError(Throwable error) { - } - }); + } + }, new Realm.Transaction.OnError() { + @Override + public void onError(Throwable error) { + } + }); + } finally { + realm.close(); + } } // ************************************ From ced5dedc93c3924c1f5514443795d75c9d662dc8 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 25 Apr 2017 14:16:34 +0200 Subject: [PATCH 0653/2110] Validate how letters are sorted. (#4527) --- .../androidTest/java/io/realm/SortTest.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 6e95a08106..e20ec38079 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger; import io.realm.entities.AllTypes; +import io.realm.entities.StringOnly; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -58,6 +59,9 @@ public class SortTest { private final static Sort[] ORDER_ASC_ASC = {Sort.ASCENDING, Sort.ASCENDING}; private final static Sort[] ORDER_ASC_DES = {Sort.ASCENDING, Sort.DESCENDING}; + private static String chars; + private int numberOfPermutations; + private void populateRealm(Realm realm) { realm.beginTransaction(); realm.delete(AllTypes.class); @@ -516,4 +520,61 @@ public void onChange(RealmResults element) { allTypes.setColumnDate(new Date(TEST_SIZE)); realm.commitTransaction(); } + + private void createAndTest(String str) { + realm.beginTransaction(); + realm.delete(StringOnly.class); + for (int i = 0; i < str.length(); i++) { + StringOnly stringOnly = realm.createObject(StringOnly.class); + stringOnly.setChars(str.substring(i, i + 1)); + } + realm.commitTransaction(); + RealmResults stringOnlies = realm.where(StringOnly.class).findAllSorted("chars"); + for (int i = 0; i < chars.length(); i++) { + assertEquals(chars.substring(i, i + 1), stringOnlies.get(i).getChars()); + } + } + + // permute and swap: http://www.geeksforgeeks.org/write-a-c-program-to-print-all-permutations-of-a-given-string/ + private void permute(String str, int l, int r) { + if (l == r) { + numberOfPermutations++; + createAndTest(str); + } else { + for (int i = l; i <= r; i++) { + str = swap(str,l,i); + permute(str, l+1, r); + str = swap(str,l,i); + } + } + } + + private String swap(String a, int i, int j) { + char temp; + char[] charArray = a.toCharArray(); + temp = charArray[i] ; + charArray[i] = charArray[j]; + charArray[j] = temp; + return String.valueOf(charArray); + } + + private int factorial(int n) { + int fac = 1; + for(int i = 1; i <= n; i++) { + fac *= i; + } + return fac; + } + + @Test + public void sortCaseSensitive() { + chars = "'- !\"#$%&()*,./:;?_+<=>123aAbBcCxXyYzZ"; + createAndTest(new StringBuffer(chars).reverse().toString()); + + // try all permutations - keep the list short + chars = "12aAbB"; + numberOfPermutations = 0; + permute(chars, 0, chars.length()-1); + assertEquals(numberOfPermutations, factorial(chars.length())); + } } From fe90488c38586457ada1e8f8d537d969ac606529 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 25 Apr 2017 16:12:13 +0200 Subject: [PATCH 0654/2110] Fixes #4540 (#4548) --- CHANGELOG.md | 6 ++++++ .../src/main/cpp/io_realm_internal_UncheckedRow.cpp | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df5dcbf3ce..c3e2b6909e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.1.4 + +## Bug fixes + +* Added missing row validation check in certain cases on invalidated/deleted objects (#4540). + ## 3.1.3 (2017-04-20) ### Enhancements diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index bdb36b7709..8b491db0b4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -385,11 +385,19 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeHasColumn(J return to_jbool(ndx != to_jlong_or_not_found(realm::not_found)); } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull(JNIEnv*, jobject, jlong nativeRowPtr, +JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull(JNIEnv* env, jobject, jlong nativeRowPtr, jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - return to_jbool(ROW(nativeRowPtr)->is_null(columnIndex)); + if (!ROW_VALID(env, ROW(nativeRowPtr))) { + return JNI_FALSE; + } + + try { + return to_jbool(ROW(nativeRowPtr)->is_null(columnIndex)); + } + CATCH_STD() + return JNI_FALSE; } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetNull(JNIEnv* env, jobject, jlong nativeRowPtr, From ba092529f6d31b44ed126b368e1c3790f6629469 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 26 Apr 2017 15:00:22 +0800 Subject: [PATCH 0655/2110] Remove some useless code for Table There are more could be removed if we remove JNITableTest. --- .../java/io/realm/internal/JNITableTest.java | 40 +--- .../src/main/cpp/io_realm_internal_Table.cpp | 215 ------------------ .../src/main/java/io/realm/RealmQuery.java | 4 +- .../main/java/io/realm/internal/Table.java | 123 +--------- 4 files changed, 11 insertions(+), 371 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index bbe3cbfa19..e387e92c1f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -75,13 +75,13 @@ public void rowOperationsOnZeroRow(){ Table t = new Table(); // Removes rows without columns. - try { t.remove(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - try { t.remove(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} + try { t.moveLastOver(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} + try { t.moveLastOver(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} // Column added, remove rows again. t.addColumn(RealmFieldType.STRING, ""); - try { t.remove(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - try { t.remove(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} + try { t.moveLastOver(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} + try { t.moveLastOver(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} } @@ -315,34 +315,6 @@ public void tableNumbers() { assertEquals(3000.0f, t.getFloat(2, 5)); } - @Test - public void maximumDate() { - - Table table = new Table(); - table.addColumn(RealmFieldType.DATE, "date"); - - table.add(new Date(0)); - table.add(new Date(10000)); - table.add(new Date(1000)); - - assertEquals(new Date(10000), table.maximumDate(0)); - - } - - @Test - public void minimumDate() { - - Table table = new Table(); - table.addColumn(RealmFieldType.DATE, "date"); - - table.add(new Date(10000)); - table.add(new Date(0)); - table.add(new Date(1000)); - - assertEquals(new Date(0), table.minimumDate(0)); - - } - // Tests the migration of a string column to be nullable. @Test public void convertToNullable() { @@ -387,7 +359,7 @@ public void convertToNullable() { } } catch (IllegalArgumentException ignored) { } - table.removeLast(); + table.moveLastOver(table.size() - 1); assertEquals(1, table.size()); table.convertColumnToNullable(colIndex); @@ -485,7 +457,7 @@ else if (columnType == RealmFieldType.STRING) } } catch (IllegalArgumentException ignored) { } - table.removeLast(); + table.moveLastOver(table.size() -1); assertEquals(2, table.size()); if (columnType == RealmFieldType.BINARY) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 42bd1567e8..2a2c339ebc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -497,29 +497,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddEmptyRow(JNIEnv* e return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemove(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong rowIndex) -{ - if (!TBL_AND_ROW_INDEX_VALID(env, TBL(nativeTablePtr), rowIndex)) { - return; - } - try { - TBL(nativeTablePtr)->remove(S(rowIndex)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveLast(JNIEnv* env, jobject, jlong nativeTablePtr) -{ - if (!TABLE_VALID(env, TBL(nativeTablePtr))) { - return; - } - try { - TBL(nativeTablePtr)->remove_last(); - } - CATCH_STD() -} - JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMoveLastOver(JNIEnv* env, jobject, jlong nativeTablePtr, jlong rowIndex) { @@ -956,198 +933,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeNullifyLink(JNIEnv* en CATCH_STD() } -//---------------------- Aggregate methods for integers - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSumInt(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { - return 0; - } - try { - return TBL(nativeTablePtr)->sum_int(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMaximumInt(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { - return 0; - } - try { - return TBL(nativeTablePtr)->maximum_int(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMinimumInt(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { - return 0; - } - try { - return TBL(nativeTablePtr)->minimum_int(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageInt(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { - return 0; - } - try { - return TBL(nativeTablePtr)->average_int(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -//--------------------- Aggregate methods for float - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeSumFloat(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { - return 0; - } - try { - return TBL(nativeTablePtr)->sum_float(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeMaximumFloat(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { - return 0; - } - try { - return TBL(nativeTablePtr)->maximum_float(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeMinimumFloat(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { - return 0; - } - try { - return TBL(nativeTablePtr)->minimum_float(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageFloat(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { - return 0; - } - try { - return TBL(nativeTablePtr)->average_float(S(columnIndex)); - } - CATCH_STD() - return 0; -} - - -//--------------------- Aggregate methods for double - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeSumDouble(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { - return 0; - } - try { - return TBL(nativeTablePtr)->sum_double(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeMaximumDouble(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { - return 0; - } - try { - return TBL(nativeTablePtr)->maximum_double(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeMinimumDouble(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { - return 0; - } - try { - return TBL(nativeTablePtr)->minimum_double(S(columnIndex)); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeAverageDouble(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { - return 0; - } - try { - return TBL(nativeTablePtr)->average_double(S(columnIndex)); - } - CATCH_STD() - return 0; -} - - -//--------------------- Aggregate methods for date - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMaximumTimestamp(JNIEnv* env, jobject, - jlong nativeTablePtr, jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) { - return 0; - } - try { - return to_milliseconds(TBL(nativeTablePtr)->maximum_timestamp(S(columnIndex))); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeMinimumTimestamp(JNIEnv* env, jobject, - jlong nativeTablePtr, jlong columnIndex) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) { - return 0; - } - try { - return to_milliseconds(TBL(nativeTablePtr)->minimum_timestamp(S(columnIndex))); - } - CATCH_STD() - return 0; -} - //---------------------- Count JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountLong(JNIEnv* env, jobject, jlong nativeTablePtr, diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 8afbdcaea6..e9fbfdd5cb 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -185,7 +185,7 @@ public boolean isValid() { if (linkView != null) { return linkView.isAttached(); } - return table != null && table.getTable().isValid(); + return table != null && table.isValid(); } /** @@ -1577,7 +1577,7 @@ public RealmResults distinct(String firstFieldName, String... remainingFieldN fieldNames[0] = firstFieldName; System.arraycopy(remainingFieldNames, 0, fieldNames, 1, remainingFieldNames.length); - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table.getTable(), fieldNames); + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table, fieldNames); return createRealmResults(query, null, distinctDescriptor, true); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 707272d4cd..20430bf48b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -102,10 +102,6 @@ public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } - public Table getTable() { - return this; - } - public long getNativeTablePointer() { return nativePtr; } @@ -343,27 +339,12 @@ public RealmFieldType getColumnType(long columnIndex) { return RealmFieldType.fromNativeValue(nativeGetColumnType(nativePtr, columnIndex)); } - /** - * Removes a row from the specific index. As of now the entry is simply removed from the table. + * Removes a row from the specific index. If it is not the last row in the table, it then moves the last row into + * the vacated slot. * * @param rowIndex the row index (starting with 0) */ - public void remove(long rowIndex) { - checkImmutable(); - nativeRemove(nativePtr, rowIndex); - } - - public void removeFirst() { - checkImmutable(); - remove(0); - } - - public void removeLast() { - checkImmutable(); - nativeRemoveLast(nativePtr); - } - public void moveLastOver(long rowIndex) { checkImmutable(); nativeMoveLastOver(nativePtr, rowIndex); @@ -714,8 +695,7 @@ public long getLink(long columnIndex, long rowIndex) { public Table getLinkTarget(long columnIndex) { long nativeTablePointer = nativeGetLinkTarget(nativePtr, columnIndex); // Copies context reference from parent. - Table table = new Table(this.sharedRealm, nativeTablePointer); - return table; + return new Table(this.sharedRealm, nativeTablePointer); } public boolean isNull(long columnIndex, long rowIndex) { @@ -931,71 +911,6 @@ private void checkHasPrimaryKey() { } } - // - // Aggregate functions - // - - // Integers - public long sumLong(long columnIndex) { - return nativeSumInt(nativePtr, columnIndex); - } - - public Long maximumLong(long columnIndex) { - return nativeMaximumInt(nativePtr, columnIndex); - } - - public Long minimumLong(long columnIndex) { - return nativeMinimumInt(nativePtr, columnIndex); - } - - public double averageLong(long columnIndex) { - return nativeAverageInt(nativePtr, columnIndex); - } - - // Floats - public double sumFloat(long columnIndex) { - return nativeSumFloat(nativePtr, columnIndex); - } - - public Float maximumFloat(long columnIndex) { - return nativeMaximumFloat(nativePtr, columnIndex); - } - - public Float minimumFloat(long columnIndex) { - return nativeMinimumFloat(nativePtr, columnIndex); - } - - public double averageFloat(long columnIndex) { - return nativeAverageFloat(nativePtr, columnIndex); - } - - // Doubles - public double sumDouble(long columnIndex) { - return nativeSumDouble(nativePtr, columnIndex); - } - - public Double maximumDouble(long columnIndex) { - return nativeMaximumDouble(nativePtr, columnIndex); - } - - public Double minimumDouble(long columnIndex) { - return nativeMinimumDouble(nativePtr, columnIndex); - } - - public double averageDouble(long columnIndex) { - return nativeAverageDouble(nativePtr, columnIndex); - } - - // Date aggregates - - public Date maximumDate(long columnIndex) { - return new Date(nativeMaximumTimestamp(nativePtr, columnIndex)); - } - - public Date minimumDate(long columnIndex) { - return new Date(nativeMinimumTimestamp(nativePtr, columnIndex)); - } - // // Count // @@ -1206,10 +1121,6 @@ public static String tableNameToClassName(String tableName) { private native int nativeGetColumnType(long nativeTablePtr, long columnIndex); - private native void nativeRemove(long nativeTablePtr, long rowIndex); - - private native void nativeRemoveLast(long nativeTablePtr); - private native void nativeMoveLastOver(long nativeTablePtr, long rowIndex); public static native long nativeAddEmptyRow(long nativeTablePtr, long rows); @@ -1281,34 +1192,6 @@ public static String tableNameToClassName(String tableName) { public static native void nativeNullifyLink(long nativePtr, long columnIndex, long rowIndex); - private native long nativeSumInt(long nativePtr, long columnIndex); - - private native long nativeMaximumInt(long nativePtr, long columnIndex); - - private native long nativeMinimumInt(long nativePtr, long columnIndex); - - private native double nativeAverageInt(long nativePtr, long columnIndex); - - private native double nativeSumFloat(long nativePtr, long columnIndex); - - private native float nativeMaximumFloat(long nativePtr, long columnIndex); - - private native float nativeMinimumFloat(long nativePtr, long columnIndex); - - private native double nativeAverageFloat(long nativePtr, long columnIndex); - - private native double nativeSumDouble(long nativePtr, long columnIndex); - - private native double nativeMaximumDouble(long nativePtr, long columnIndex); - - private native double nativeMinimumDouble(long nativePtr, long columnIndex); - - private native double nativeAverageDouble(long nativePtr, long columnIndex); - - private native long nativeMaximumTimestamp(long nativePtr, long columnIndex); - - private native long nativeMinimumTimestamp(long nativePtr, long columnIndex); - private native long nativeCountLong(long nativePtr, long columnIndex, long value); private native long nativeCountFloat(long nativePtr, long columnIndex, float value); From 507db074250d6402b7d14953e0d5f85b69a122f7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 27 Apr 2017 09:02:01 +0200 Subject: [PATCH 0656/2110] Fix schemaVersion docs for synced Realms (#4547) --- .../java/io/realm/SyncConfiguration.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 9590f702f2..e2eb671721 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -60,7 +60,6 @@ * *

                        *
                      • {@code deleteRealmIfMigrationNeeded()}
                      • - *
                      • {@code schemaVersion(long version)}
                      • *
                      • {@code migration(Migration)}
                      • *
                      * @@ -464,8 +463,25 @@ SyncConfiguration.Builder schema(Class firstClass, Class + * While synced Realms only support additive schema changes which can be applied without requiring a manual + * migration, the schema version must still be incremented as an indication to Realm that the change was + * intentional. + *

                      + * Failing to increment the schema version will cause Realm to throw a {@link io.realm.exceptions.RealmMigrationNeededException} + * when the Realm is opened and the changed schema will not be applied. + *

                      + * WARNING: There is no guarantee that the value inserted here is the same returned by {@link Realm#getVersion()}. + * Due to the nature of synced Realms, the value can both be higher and lower. + *

                        + *
                      • It will be lower if another client with a lesser {@code schemaVersion} connected to the server for + * the first time after this schemaVersion was used. + *
                      • + *
                      • It will be higher if another client with a higher {@code schemaVersion} connected to the server after + * this Realm was created. + *
                      • + *
                      * * @param schemaVersion the schema version. * @throws IllegalArgumentException if schema version is invalid. From 7ae1eeb763628bc55a02ce7125b293ba800d28da Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 27 Apr 2017 09:45:34 +0200 Subject: [PATCH 0657/2110] Try to create getFilesDir if it doesn't exist on startup. (#4559) --- CHANGELOG.md | 1 + .../androidTest/java/io/realm/RealmTests.java | 43 +++++++++++++++ .../src/main/java/io/realm/Realm.java | 55 +++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e2b6909e..66dc3029b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Bug fixes * Added missing row validation check in certain cases on invalidated/deleted objects (#4540). +* Initializing Realm is now more resilient if `Context.getFilesDir()` isn't working correctly (#4493). ## 3.1.3 (2017-04-20) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index f95147ab65..f536b32074 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -38,12 +38,17 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -116,6 +121,9 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + @RunWith(AndroidJUnit4.class) public class RealmTests { @@ -128,6 +136,8 @@ public class RealmTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule + public final TemporaryFolder tmpFolder = new TemporaryFolder(); + @Rule public final ExpectedException thrown = ExpectedException.none(); private Context context; @@ -3829,4 +3839,37 @@ public boolean accept(File dir, String name) { realmOnExternalStorage = Realm.getInstance(config); realmOnExternalStorage.close(); } + + // Verify that the logic for waiting for the users file dir to be come available isn't totally broken + // This is pretty hard to test, so forced to break encapsulation in this case. + @Test + public void init_waitForFilesDir() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { + java.lang.reflect.Method m = Realm.class.getDeclaredMethod("checkFilesDirAvailable", Context.class); + m.setAccessible(true); + + // A) Check it fails if getFilesDir is never created + Context mockContext = mock(Context.class); + when(mockContext.getFilesDir()).thenReturn(null); + + try { + m.invoke(null, mockContext); + fail(); + } catch (InvocationTargetException e) { + assertEquals(IllegalStateException.class, e.getCause().getClass()); + } + + // B) Check we return if the filesDir becomes available after a while + mockContext = mock(Context.class); + when(mockContext.getFilesDir()).then(new Answer() { + int calls = 0; + File userFolder = tmpFolder.newFolder(); + @Override + public File answer(InvocationOnMock invocationOnMock) throws Throwable { + calls++; + return (calls > 5) ? userFolder : null; // Start returning the correct folder after 5 attempts + } + }); + + assertNull(m.invoke(null, mockContext)); + } } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index b985fb9031..c57121d600 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -20,6 +20,7 @@ import android.app.IntentService; import android.content.Context; import android.os.Build; +import android.os.SystemClock; import android.util.JsonReader; import org.json.JSONArray; @@ -179,6 +180,7 @@ public Observable asObservable() { * * @param context the Application Context. * @throws IllegalArgumentException if a {@code null} context is provided. + * @throws IllegalStateException if {@link Context#getFilesDir()} could not be found. * @see #getDefaultInstance() */ public static synchronized void init(Context context) { @@ -186,6 +188,7 @@ public static synchronized void init(Context context) { if (context == null) { throw new IllegalArgumentException("Non-null context required."); } + checkFilesDirAvailable(context); RealmCore.loadLibrary(context); defaultConfiguration = new RealmConfiguration.Builder(context).build(); ObjectServerFacade.getSyncFacadeIfPossible().init(context); @@ -194,6 +197,58 @@ public static synchronized void init(Context context) { } } + /** + * In some cases, Context.getFilesDir() is not available when the app launches the first time. + * This should never happen according to the official Android documentation, but the race condition wasn't fixed + * until Android 4.4. + *

                      + * This method attempts to fix that situation. If this doesn't work an {@link IllegalStateException} will be + * thrown. + *

                      + * See these links for further details: + * https://issuetracker.google.com/issues/36918154 + * https://github.com/realm/realm-java/issues/4493#issuecomment-295349044 + */ + private static void checkFilesDirAvailable(Context context) { + File filesDir = context.getFilesDir(); + if (filesDir != null) { + if (filesDir.exists()) { + return; // Everything is fine. Escape as soon as possible + } else { + try { + // This was reported as working on some devices, which I really hope is just the race condition + // kicking in, otherwise something is seriously wrong with the permission system on those devices. + // We will try it anyway, since starting a loop will be slower by many magnitudes. + filesDir.mkdirs(); + } catch (SecurityException ignored) { + } + } + } + if (filesDir == null || !filesDir.exists()) { + // Wait a "reasonable" amount of time before quitting. + // In this case we define reasonable as 200 ms (~12 dropped frames) before giving up (which most likely + // will result in the app crashing). This lag would only be seen in worst case scenarios, and then, only + // when the app is started the first time. + long[] timeoutsMs = new long[]{1, 2, 5, 10, 16}; // Exponential waits, capped at 16 ms; + long maxTotalWaitMs = 200; + long currentTotalWaitMs = 0; + int waitIndex = -1; + while (context.getFilesDir() == null || !context.getFilesDir().exists()) { + long waitMs = timeoutsMs[Math.min(++waitIndex, timeoutsMs.length - 1)]; + SystemClock.sleep(waitMs); + currentTotalWaitMs += waitMs; + if (currentTotalWaitMs > maxTotalWaitMs) { + break; + } + } + } + + // One final check before giving up + if (context.getFilesDir() == null || !context.getFilesDir().exists()) { + throw new IllegalStateException("Context.getFilesDir() returns " + context.getFilesDir() + " which is not an existing directory. See https://issuetracker.google.com/issues/36918154"); + } + } + /** * Realm static constructor that returns the Realm instance defined by the {@link io.realm.RealmConfiguration} set * by {@link #setDefaultConfiguration(RealmConfiguration)} From cf6b81fd42392063658073afdf2368d5abd9e85d Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Thu, 27 Apr 2017 08:33:21 -0700 Subject: [PATCH 0658/2110] Fix threading bugs in RunInLooperThread rule (#4563) * Fix threading bugs in RunInLooperThread rule * Respond to comments Fix spelling errors Clean up multi-error recovery. --- .../java/io/realm/DynamicRealmTests.java | 14 +- .../io/realm/LinkingObjectsManagedTests.java | 20 +- .../java/io/realm/NotificationsTest.java | 30 +- .../java/io/realm/ObjectChangeSetTests.java | 44 +- .../OrderedCollectionChangeSetTests.java | 28 +- .../java/io/realm/RealmAsyncQueryTests.java | 132 ++--- .../io/realm/RealmChangeListenerTests.java | 34 +- .../java/io/realm/RealmListTests.java | 8 +- .../java/io/realm/RealmModelTests.java | 8 +- .../java/io/realm/RealmObjectTests.java | 22 +- .../java/io/realm/RealmQueryTests.java | 24 +- .../java/io/realm/RealmResultsTests.java | 54 +- .../androidTest/java/io/realm/RealmTests.java | 18 +- .../java/io/realm/RxJavaTests.java | 26 +- .../androidTest/java/io/realm/SortTest.java | 12 +- .../androidTest/java/io/realm/TestHelper.java | 28 +- .../io/realm/TypeBasedNotificationsTests.java | 86 ++-- .../io/realm/internal/CollectionTests.java | 10 +- .../io/realm/internal/RealmNotifierTests.java | 20 +- .../java/io/realm/rule/RunInLooperThread.java | 483 ++++++++++++------ .../rule/TestRealmConfigurationFactory.java | 15 +- .../java/io/realm/SessionTests.java | 7 +- .../java/io/realm/objectserver/AuthTests.java | 2 +- .../objectserver/ManagementRealmTests.java | 14 +- 24 files changed, 678 insertions(+), 461 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 1bec5cfa80..5d5b6ed371 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -356,7 +356,7 @@ public void findFirstAsync() { .between(AllTypes.FIELD_LONG, 4, 9) .findFirstAsync(); assertFalse(allTypes.isLoaded()); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); allTypes.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealmObject object) { @@ -389,7 +389,7 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); } @Test @@ -414,15 +414,15 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); } // Initializes a Dynamic Realm used by the *Async tests and keeps it ref in the looperThread. private DynamicRealm initializeDynamicRealm() { - RealmConfiguration defaultConfig = looperThread.realmConfiguration; + RealmConfiguration defaultConfig = looperThread.getConfiguration(); final DynamicRealm dynamicRealm = DynamicRealm.getInstance(defaultConfig); populateTestRealm(dynamicRealm, 10); - looperThread.keepStrongReference.add(dynamicRealm); + looperThread.keepStrongReference(dynamicRealm); return dynamicRealm; } @@ -531,8 +531,8 @@ public void onChange(RealmResults object) { signalCallbackDone.run(); } }); - looperThread.keepStrongReference.add(realmResults1); - looperThread.keepStrongReference.add(realmResults2); + looperThread.keepStrongReference(realmResults1); + looperThread.keepStrongReference(realmResults2); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 7174a91870..b729034259 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -172,7 +172,7 @@ public void issue4487_checkIfTableIsCorrect() { @Test @RunTestInLooperThread public void notification_notSentAfterUnregisterListenerModelObject() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -207,7 +207,7 @@ public void run(Realm realm) { @Test @RunTestInLooperThread public void notification_onCommitRealmResults() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -243,7 +243,7 @@ public void run(Realm realm) { @Test @RunTestInLooperThread public void notification_notSentAfterUnregisterListenerRealmResults() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -279,7 +279,7 @@ public void run(Realm realm) { @Test @RunTestInLooperThread public void notification_onDeleteRealmResults() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -316,7 +316,7 @@ public void run(Realm realm) { @Test @RunTestInLooperThread public void notification_notSentOnUnrelatedChangeRealmResults() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -405,7 +405,7 @@ public void json_updateList() { @Test @RunTestInLooperThread public void linkingObjects_IllegalStateException_ifNotYetLoaded() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.executeTransaction(new Realm.Transaction() { @Override @@ -433,7 +433,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void linkingObjects_IllegalStateException_ifDeleted() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.executeTransaction(new Realm.Transaction() { @Override @@ -468,7 +468,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void linkingObjects_IllegalStateException_ifDeletedIndirectly() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.executeTransaction(new Realm.Transaction() { @Override @@ -794,7 +794,9 @@ private void verifyPostConditions(final Realm realm, final PostConditions test, realm.commitTransaction(); // Runnable is guaranteed to be enqueued on the Looper queue, after the notifications - looperThread.keepStrongReference.addAll(Arrays.asList(refs)); + for (Object ref : refs) { + looperThread.keepStrongReference(ref); + } looperThread.postRunnable( new Runnable() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 6bd270bab7..580eda8f48 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -162,7 +162,7 @@ public void onChange(Realm object) { } }; - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(listener); realm.addChangeListener(listener); realm.addChangeListener(new RealmChangeListener() { @@ -317,7 +317,7 @@ public void run() { @RunTestInLooperThread public void globalListener_looperThread_triggeredByLocalCommit() { final AtomicInteger success = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -335,7 +335,7 @@ public void onChange(Realm object) { @RunTestInLooperThread public void globalListener_looperThread_triggeredByRemoteCommit() { final AtomicInteger success = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -361,7 +361,7 @@ public void onChange(Realm object) { looperThread.testComplete(); } }; - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(listener); realm.beginTransaction(); realm.commitTransaction(); @@ -370,7 +370,7 @@ public void onChange(Realm object) { @Test @RunTestInLooperThread public void addRemoveListenerConcurrency() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final AtomicInteger counter1 = new AtomicInteger(0); final AtomicInteger counter2 = new AtomicInteger(0); final AtomicInteger counter3 = new AtomicInteger(0); @@ -447,7 +447,7 @@ public void realmNotificationOrder() { // Test both ways to check accidental ordering from unordered collections. final AtomicInteger listenerACalled = new AtomicInteger(0); final AtomicInteger listenerBCalled = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmChangeListener listenerA = new RealmChangeListener() { @@ -690,12 +690,12 @@ public void onChange(Realm object) { @Test @RunTestInLooperThread public void asyncRealmResultsShouldNotBlockBackgroundCommitNotification() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults dogs = realm.where(Dog.class).findAllAsync(); final AtomicBoolean resultsListenerDone = new AtomicBoolean(false); final AtomicBoolean realmListenerDone = new AtomicBoolean(false); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); assertTrue(dogs.load()); assertEquals(0, dogs.size()); dogs.addChangeListener(new RealmChangeListener>() { @@ -750,7 +750,7 @@ public void execute(Realm realm) { public void asyncRealmObjectShouldNotBlockBackgroundCommitNotification() { final AtomicInteger numberOfRealmCallbackInvocation = new AtomicInteger(0); final CountDownLatch signalClosedRealm = new CountDownLatch(1); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(final Realm realm) { @@ -816,7 +816,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void realmListener_realmResultShouldBeSynced() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(1, results.size()); @@ -844,13 +844,13 @@ public void onChange(Realm element) { @Test @RunTestInLooperThread public void accessingSyncRealmResultInsideAsyncResultListener() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final AtomicInteger asyncResultCallback = new AtomicInteger(0); final RealmResults syncResults = realm.where(AllTypes.class).findAll(); RealmResults results = realm.where(AllTypes.class).findAllAsync(); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { @@ -884,11 +884,11 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void accessingSyncRealmResultsInsideAnotherResultListener() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults syncResults1 = realm.where(AllTypes.class).findAll(); final RealmResults syncResults2 = realm.where(AllTypes.class).findAll(); - looperThread.keepStrongReference.add(syncResults1); + looperThread.keepStrongReference(syncResults1); syncResults1.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -906,7 +906,7 @@ public void onChange(RealmResults element) { @Test @RunTestInLooperThread(threadName = "IntentService[1]") public void listenersNotAllowedOnIntentServiceThreads() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java index 2069ec6eee..2755132a69 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java @@ -76,7 +76,7 @@ public void onChange(AllTypes object, ObjectChangeSet changeSet) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); } private void checkChangedField(AllTypes allTypes, final String... fieldNames) { @@ -96,7 +96,7 @@ public void onChange(RealmModel object, ObjectChangeSet changeSet) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); } private void listenerShouldNotBeCalled(AllTypes allTypes) { @@ -117,7 +117,7 @@ public void run() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void objectDeleted() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkDeleted(allTypes); realm.beginTransaction(); @@ -128,7 +128,7 @@ public void objectDeleted() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLongField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_LONG); realm.beginTransaction(); @@ -139,7 +139,7 @@ public void changeLongField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeStringField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_STRING); realm.beginTransaction(); @@ -150,7 +150,7 @@ public void changeStringField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeFloatField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_FLOAT); realm.beginTransaction(); @@ -161,7 +161,7 @@ public void changeFloatField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeDoubleField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_DOUBLE); realm.beginTransaction(); @@ -172,7 +172,7 @@ public void changeDoubleField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeBooleanField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_BOOLEAN); realm.beginTransaction(); @@ -183,7 +183,7 @@ public void changeBooleanField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeDateField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_DATE); realm.beginTransaction(); @@ -194,7 +194,7 @@ public void changeDateField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeBinaryField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_BINARY); realm.beginTransaction(); @@ -205,7 +205,7 @@ public void changeBinaryField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkFieldSetNewObject() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMOBJECT); realm.beginTransaction(); @@ -216,7 +216,7 @@ public void changeLinkFieldSetNewObject() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkFieldSetNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMOBJECT); realm.beginTransaction(); @@ -227,7 +227,7 @@ public void changeLinkFieldSetNull() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkFieldRemoveObject() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMOBJECT); realm.beginTransaction(); @@ -238,7 +238,7 @@ public void changeLinkFieldRemoveObject() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkFieldOriginalObjectChanged_notTrigger() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); listenerShouldNotBeCalled(allTypes); realm.beginTransaction(); @@ -249,7 +249,7 @@ public void changeLinkFieldOriginalObjectChanged_notTrigger() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkListAddObject() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMLIST); realm.beginTransaction(); @@ -260,7 +260,7 @@ public void changeLinkListAddObject() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkListClear() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMLIST); realm.beginTransaction(); @@ -271,7 +271,7 @@ public void changeLinkListClear() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeAllFields() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_LONG, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT, AllTypes.FIELD_STRING, AllTypes.FIELD_BOOLEAN, @@ -295,7 +295,7 @@ public void changeAllFields() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeDifferentFieldOneAfterAnother() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); final AtomicBoolean stringChanged = new AtomicBoolean(false); final AtomicBoolean longChanged = new AtomicBoolean(false); @@ -339,7 +339,7 @@ public void onChange(RealmModel object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void findFirstAsync_changeSetIsNullWhenQueryReturns() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); allTypes.addChangeListener(new RealmObjectChangeListener() { @Override @@ -357,7 +357,7 @@ public void onChange(AllTypes object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void findFirstAsync_queryExecutedByLocalCommit() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AtomicInteger listenerCounter = new AtomicInteger(0); final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); allTypes.addChangeListener(new RealmObjectChangeListener() { @@ -423,7 +423,7 @@ public void onChange(DynamicRealmObject object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void allParentObjectShouldBeInChangeSet() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); Owner owner = realm.createObject(Owner.class); @@ -443,7 +443,7 @@ public void allParentObjectShouldBeInChangeSet() { realm.commitTransaction(); RealmResults dogs = realm.where(Dog.class).equalTo(Dog.FIELD_HAS_TAIL, true).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java index 30f6521816..fa0036e267 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -16,6 +16,8 @@ package io.realm; +import android.util.Log; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -157,7 +159,7 @@ private void registerCheckListener(Realm realm, final ChangesCheck changesCheck) switch (type) { case REALM_RESULTS: RealmResults results = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { @@ -167,7 +169,7 @@ public void onChange(RealmResults collection, OrderedCollectionChangeSet ch break; case REALM_LIST: RealmList list = realm.where(Owner.class).findFirst().getDogs(); - looperThread.keepStrongReference.add(list); + looperThread.keepStrongReference(list); list.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmList collection, OrderedCollectionChangeSet changeSet) { @@ -181,7 +183,7 @@ public void onChange(RealmList collection, OrderedCollectionChangeSet chang @Test @RunTestInLooperThread public void deletion() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); final ChangesCheck changesCheck = new ChangesCheck() { @@ -213,7 +215,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void insertion() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 0); // We need to create the owner. realm.beginTransaction(); createObjects(realm, 0, 2, 5, 6, 7, 9); @@ -247,7 +249,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void changes() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -278,7 +280,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void moves() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -307,7 +309,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void mixed_changes() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -347,7 +349,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void changes_then_delete() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -377,7 +379,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void insert_then_delete() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -409,7 +411,10 @@ public void emptyChangeSet_findAllAsync(){ looperThread.testComplete(); return; } - Realm realm = looperThread.realm; + + Log.d("####", "test running on thread: " + Thread.currentThread()); + + Realm realm = looperThread.getRealm(); populateData(realm, 10); final RealmResults results = realm.where(Dog.class).findAllSortedAsync(Dog.FIELD_AGE); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @@ -429,7 +434,8 @@ public void onChange(RealmResults collection, OrderedCollectionChangeSet ch new Thread(new Runnable() { @Override public void run() { - Realm realm = Realm.getInstance(looperThread.realmConfiguration) ; + Log.d("####", "runnable running on thread: " + Thread.currentThread()); + Realm realm = Realm.getInstance(looperThread.getConfiguration()) ; realm.beginTransaction(); realm.where(Dog.class).equalTo(Dog.FIELD_AGE, 0).findFirst().deleteFromRealm(); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 4d4c051258..546c1eb423 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -71,7 +71,7 @@ public class RealmAsyncQueryTests { @Test @RunTestInLooperThread public void executeTransactionAsync() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); realm.executeTransactionAsync(new Realm.Transaction() { @@ -99,7 +99,7 @@ public void onError(Throwable error) { @Test @RunTestInLooperThread public void executeTransactionAsync_onSuccess() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); realm.executeTransactionAsync(new Realm.Transaction() { @@ -121,7 +121,7 @@ public void onSuccess() { @Test @RunTestInLooperThread public void executeTransactionAsync_onSuccessCallerRealmClosed() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); realm.executeTransactionAsync(new Realm.Transaction() { @@ -134,7 +134,7 @@ public void execute(Realm realm) { @Override public void onSuccess() { assertTrue(realm.isClosed()); - Realm newRealm = Realm.getInstance(looperThread.realmConfiguration); + Realm newRealm = Realm.getInstance(looperThread.getConfiguration()); assertEquals(1, newRealm.where(Owner.class).count()); assertEquals("Owner", newRealm.where(Owner.class).findFirst().getName()); newRealm.close(); @@ -147,7 +147,7 @@ public void onSuccess() { @Test @RunTestInLooperThread public void executeTransactionAsync_onError() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RuntimeException runtimeException = new RuntimeException("Oh! What a Terrible Failure"); assertEquals(0, realm.where(Owner.class).count()); @@ -170,7 +170,7 @@ public void onError(Throwable error) { @Test @RunTestInLooperThread public void executeTransactionAsync_onErrorCallerRealmClosed() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RuntimeException runtimeException = new RuntimeException("Oh! What a Terrible Failure"); assertEquals(0, realm.where(Owner.class).count()); @@ -183,7 +183,7 @@ public void execute(Realm realm) { @Override public void onError(Throwable error) { assertTrue(realm.isClosed()); - Realm newRealm = Realm.getInstance(looperThread.realmConfiguration); + Realm newRealm = Realm.getInstance(looperThread.getConfiguration()); assertEquals(0, newRealm.where(Owner.class).count()); assertNull(newRealm.where(Owner.class).findFirst()); assertEquals(runtimeException, error); @@ -197,7 +197,7 @@ public void onError(Throwable error) { @Test @RunTestInLooperThread public void executeTransactionAsync_NoCallbacks() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); realm.executeTransactionAsync(new Realm.Transaction() { @@ -223,7 +223,7 @@ public void executeTransactionAsync_cancelTransactionInside() throws Throwable { final TestHelper.TestLogger testLogger = new TestHelper.TestLogger(LogLevel.DEBUG); RealmLog.add(testLogger); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); @@ -257,7 +257,7 @@ public void onError(Throwable error) { @RunTestInLooperThread public void executeTransactionAsync_realmClosedOnSuccess() { final AtomicInteger counter = new AtomicInteger(100); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmCache.Callback cacheCallback = new RealmCache.Callback() { @Override public void onResult(int count) { @@ -296,7 +296,7 @@ public void execute(Realm realm) { @RunTestInLooperThread public void executeTransaction_async_realmClosedOnError() { final AtomicInteger counter = new AtomicInteger(100); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmCache.Callback cacheCallback = new RealmCache.Callback() { @Override public void onResult(int count) { @@ -337,7 +337,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void executeTransactionAsync_asyncQuery() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults results = realm.where(AllTypes.class).findAllAsync(); assertEquals(0, results.size()); @@ -414,7 +414,7 @@ public void onError(Throwable error) { @Test @RunTestInLooperThread public void findAllAsync() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults results = realm.where(AllTypes.class) .between("columnLong", 0, 4) @@ -423,7 +423,7 @@ public void findAllAsync() throws Throwable { assertFalse(results.isLoaded()); assertEquals(0, results.size()); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -438,7 +438,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void accessingRealmListOnUnloadedRealmObjectShouldThrow() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final AllTypes results = realm.where(AllTypes.class) .equalTo("columnLong", 0) @@ -481,7 +481,7 @@ public void findAllAsync_throwsOnNonLooperThread() throws Throwable { @Test @RunTestInLooperThread public void findAllAsync_withNotification() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults results = realm.where(AllTypes.class) .between("columnLong", 0, 4) @@ -496,7 +496,7 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); assertFalse(results.isLoaded()); assertEquals(0, results.size()); @@ -507,13 +507,13 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void findAllAsync_forceLoad() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults realmResults = realm.where(AllTypes.class) .between("columnLong", 0, 4) .findAllAsync(); - looperThread.keepStrongReference.add(realmResults); + looperThread.keepStrongReference(realmResults); // Notification should be called as well. realmResults.addChangeListener(new RealmChangeListener>() { @Override @@ -543,13 +543,13 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void findFirstAsync() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final AllTypes asyncObj = realm.where(AllTypes.class).findFirstAsync(); assertFalse(asyncObj.isValid()); assertFalse(asyncObj.isLoaded()); - looperThread.keepStrongReference.add(asyncObj); + looperThread.keepStrongReference(asyncObj); asyncObj.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -564,9 +564,9 @@ public void onChange(AllTypes object) { @Test @RunTestInLooperThread public void findFirstAsync_initialEmptyRow() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AllTypes firstAsync = realm.where(AllTypes.class).findFirstAsync(); - looperThread.keepStrongReference.add(firstAsync); + looperThread.keepStrongReference(firstAsync); firstAsync.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -580,19 +580,19 @@ public void onChange(AllTypes object) { @Test @RunTestInLooperThread public void findFirstAsync_updatedIfSyncRealmObjectIsUpdated() throws Throwable { - populateTestRealm(looperThread.realm, 1); - AllTypes firstSync = looperThread.realm.where(AllTypes.class).findFirst(); + populateTestRealm(looperThread.getRealm(), 1); + AllTypes firstSync = looperThread.getRealm().where(AllTypes.class).findFirst(); assertEquals(0, firstSync.getColumnLong()); assertEquals("test data 0", firstSync.getColumnString()); - final AllTypes firstAsync = looperThread.realm.where(AllTypes.class).findFirstAsync(); + final AllTypes firstAsync = looperThread.getRealm().where(AllTypes.class).findFirstAsync(); assertTrue(firstAsync.load()); assertTrue(firstAsync.isLoaded()); assertTrue(firstAsync.isValid()); assertEquals(0, firstAsync.getColumnLong()); assertEquals("test data 0", firstAsync.getColumnString()); - looperThread.keepStrongReference.add(firstAsync); + looperThread.keepStrongReference(firstAsync); firstAsync.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -601,9 +601,9 @@ public void onChange(AllTypes object) { } }); - looperThread.realm.beginTransaction(); + looperThread.getRealm().beginTransaction(); firstSync.setColumnString("Galacticon"); - looperThread.realm.commitTransaction(); + looperThread.getRealm().commitTransaction(); } // Finds elements [0-4] asynchronously then waits for the promise to be loaded @@ -611,13 +611,13 @@ public void onChange(AllTypes object) { @Test @RunTestInLooperThread public void findFirstAsync_withNotification() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final AllTypes realmResults = realm.where(AllTypes.class) .between("columnLong", 4, 9) .findFirstAsync(); - looperThread.keepStrongReference.add(realmResults); + looperThread.keepStrongReference(realmResults); realmResults.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -642,7 +642,7 @@ public void onChange(AllTypes object) { @RunTestInLooperThread public void findFirstAsync_forceLoad() throws Throwable { final AtomicBoolean listenerCalled = new AtomicBoolean(false); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final AllTypes realmResults = realm.where(AllTypes.class) .between("columnLong", 4, 9) @@ -671,7 +671,7 @@ public void onChange(RealmModel object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void findFirstAsync_twoListenersOnSameInvalidObjectsCauseNPE() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); final AtomicBoolean firstListenerCalled = new AtomicBoolean(false); @@ -707,7 +707,7 @@ public void onChange(AllTypes element) { @Test @RunTestInLooperThread public void findAllSortedAsync() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults results = realm.where(AllTypes.class) @@ -717,7 +717,7 @@ public void findAllSortedAsync() throws Throwable { assertFalse(results.isLoaded()); assertEquals(0, results.size()); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -735,9 +735,9 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void combiningAsyncAndSync() { - populateTestRealm(looperThread.realm, 10); + populateTestRealm(looperThread.getRealm(), 10); - final RealmResults allTypesAsync = looperThread.realm.where(AllTypes.class).greaterThan("columnLong", 5).findAllAsync(); + final RealmResults allTypesAsync = looperThread.getRealm().where(AllTypes.class).greaterThan("columnLong", 5).findAllAsync(); final RealmResults allTypesSync = allTypesAsync.where().greaterThan("columnLong", 3).findAll(); // Call where() on an async results will load query. But to maintain the pre version 2.4.0 behaviour of @@ -752,7 +752,7 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypesAsync); + looperThread.keepStrongReference(allTypesAsync); } // Keeps advancing the Realm by sending 1 commit for each frame (16ms). @@ -770,7 +770,7 @@ public void stressTestBackgroundCommits() throws Throwable { @Override public void run() { Random random = new Random(System.currentTimeMillis()); - Realm backgroundThreadRealm = Realm.getInstance(looperThread.realm.getConfiguration()); + Realm backgroundThreadRealm = Realm.getInstance(looperThread.getRealm().getConfiguration()); for (int i = 0; i < NUMBER_OF_COMMITS; i++) { backgroundThreadRealm.beginTransaction(); AllTypes object = backgroundThreadRealm.createObject(AllTypes.class); @@ -788,13 +788,13 @@ public void run() { } }; - final RealmResults allAsync = looperThread.realm.where(AllTypes.class).findAllAsync(); + final RealmResults allAsync = looperThread.getRealm().where(AllTypes.class).findAllAsync(); allAsync.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { assertTrue(allAsync.isLoaded()); if (allAsync.size() == NUMBER_OF_COMMITS) { - AllTypes lastInserted = looperThread.realm.where(AllTypes.class) + AllTypes lastInserted = looperThread.getRealm().where(AllTypes.class) .equalTo("columnLong", latestLongValue[0]) .equalTo("columnFloat", latestFloatValue[0]) .findFirst(); @@ -804,7 +804,7 @@ public void onChange(RealmResults object) { } } }); - looperThread.keepStrongReference.add(allAsync); + looperThread.keepStrongReference(allAsync); looperThread.postRunnableDelayed(new Runnable() { @Override @@ -817,7 +817,7 @@ public void run() { @Test @RunTestInLooperThread public void distinctAsync() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -853,10 +853,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -893,7 +893,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread() public void distinctAsync_rememberQueryParams() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); final int TEST_SIZE = 10; for (int i = 0; i < TEST_SIZE; i++) { @@ -918,7 +918,7 @@ public void onChange(RealmResults results) { @Test @RunTestInLooperThread public void distinctAsync_notIndexedFields() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -958,10 +958,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -998,7 +998,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void distinctAsync_noneExistingField() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -1014,7 +1014,7 @@ public void distinctAsync_noneExistingField() throws Throwable { @Test @RunTestInLooperThread public void batchUpdateDifferentTypeOfQueries() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); for (int i = 0; i < 5; ) { AllTypes allTypes = realm.createObject(AllTypes.class); @@ -1036,10 +1036,10 @@ public void batchUpdateDifferentTypeOfQueries() { new Sort[]{Sort.ASCENDING, Sort.DESCENDING}); RealmResults findDistinct = realm.where(AnnotationIndexTypes.class).distinctAsync("indexString"); - looperThread.keepStrongReference.add(findAllAsync); - looperThread.keepStrongReference.add(findAllSorted); - looperThread.keepStrongReference.add(findAllSortedMulti); - looperThread.keepStrongReference.add(findDistinct); + looperThread.keepStrongReference(findAllAsync); + looperThread.keepStrongReference(findAllSorted); + looperThread.keepStrongReference(findAllSortedMulti); + looperThread.keepStrongReference(findDistinct); final CountDownLatch queriesCompleted = new CountDownLatch(4); final CountDownLatch bgRealmClosedLatch = new CountDownLatch(1); @@ -1149,10 +1149,10 @@ public void run() { @RunTestInLooperThread public void queryingLinkHandover() throws Throwable { final AtomicInteger numberOfInvocations = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults allAsync = realm.where(Dog.class).equalTo("owner.name", "kiba").findAllAsync(); - looperThread.keepStrongReference.add(allAsync); + looperThread.keepStrongReference(allAsync); allAsync.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -1202,11 +1202,11 @@ public void doInBackground(Realm realm) { @RunTestInLooperThread public void badVersion_syncTransaction() throws NoSuchFieldException, IllegalAccessException { final AtomicInteger listenerCount = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); // 1. Makes sure that async query is not started. final RealmResults result = realm.where(AllTypes.class).findAllSortedAsync(AllTypes.FIELD_STRING); - looperThread.keepStrongReference.add(result); + looperThread.keepStrongReference(result); result.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -1250,7 +1250,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void batchUpdate_localRefIsDeletedInLoopOfNativeBatchUpdateQueries() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // For Android, the size of local ref map is 512. Uses 1024 for more pressure. final int TEST_COUNT = 1024; final AtomicBoolean updatesTriggered = new AtomicBoolean(false); @@ -1284,7 +1284,7 @@ public void execute(Realm realm) { // Step 2: Creates 2nd - TEST_COUNT queries. RealmResults results = realm.where(AllTypes.class).findAllAsync(); results.addChangeListener(this); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); } } } @@ -1292,7 +1292,7 @@ public void execute(Realm realm) { // Step 1. Creates first async to kick the test start. RealmResults results = realm.where(AllTypes.class).findAllAsync(); results.addChangeListener(listener); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); } // *** Helper methods *** diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java index b058f3357a..b44bcc868f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java @@ -63,7 +63,7 @@ public void tearDown() { @Test @RunTestInLooperThread public void returnedRealmIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm realm) { @@ -79,7 +79,7 @@ public void onChange(Realm realm) { @Test @RunTestInLooperThread public void returnedDynamicRealmIsNotNull() { - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); dynamicRealm.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealm dynRealm) { @@ -96,9 +96,9 @@ public void onChange(DynamicRealm dynRealm) { @Test @RunTestInLooperThread public void returnedRealmResultsIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults cats = realm.where(Cat.class).findAll(); - looperThread.keepStrongReference.add(cats); + looperThread.keepStrongReference(cats); cats.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults result) { @@ -115,9 +115,9 @@ public void onChange(RealmResults result) { @Test @RunTestInLooperThread public void returnedRealmResultsOfModelIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults alltypes = realm.where(AllTypesRealmModel.class).findAll(); - looperThread.keepStrongReference.add(alltypes); + looperThread.keepStrongReference(alltypes); alltypes.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults result) { @@ -136,12 +136,12 @@ public void onChange(RealmResults result) { @Test @RunTestInLooperThread public void returnedRealmObjectIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); - Cat cat = looperThread.realm.createObject(Cat.class); + Cat cat = realm.createObject(Cat.class); realm.commitTransaction(); - looperThread.keepStrongReference.add(cat); + looperThread.keepStrongReference(cat); cat.addChangeListener(new RealmChangeListener() { @Override public void onChange(Cat object) { @@ -158,12 +158,12 @@ public void onChange(Cat object) { @Test @RunTestInLooperThread public void returnedRealmModelIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypesRealmModel model = realm.createObject(AllTypesRealmModel.class, 0); realm.commitTransaction(); - looperThread.keepStrongReference.add(model); + looperThread.keepStrongReference(model); RealmObject.addChangeListener(model, new RealmChangeListener() { @Override public void onChange(AllTypesRealmModel object) { @@ -180,15 +180,15 @@ public void onChange(AllTypesRealmModel object) { @Test @RunTestInLooperThread public void returnedDynamicRealmObjectIsNotNull() { - Realm realm = Realm.getInstance(looperThread.realmConfiguration); + Realm realm = Realm.getInstance(looperThread.getConfiguration()); realm.close(); - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); dynamicRealm.beginTransaction(); DynamicRealmObject allTypes = dynamicRealm.createObject(AllTypes.CLASS_NAME); dynamicRealm.commitTransaction(); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); allTypes.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealmObject object) { @@ -205,12 +205,12 @@ public void onChange(DynamicRealmObject object) { @Test @RunTestInLooperThread public void returnedDynamicRealmResultsIsNotNull() { - Realm realm = Realm.getInstance(looperThread.realmConfiguration); + Realm realm = Realm.getInstance(looperThread.getConfiguration()); realm.close(); - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); RealmResults all = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); - looperThread.keepStrongReference.add(all); + looperThread.keepStrongReference(all); all.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults result) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 0e195f1343..c6b2ea957f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -993,7 +993,7 @@ public void add_set_dynamicObjectCreatedFromTypedRealm() { } private RealmList prepareRealmListInLooperThread() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); Owner owner = realm.createObject(Owner.class); owner.setName("Owner"); @@ -1010,7 +1010,7 @@ private RealmList prepareRealmListInLooperThread() { @RunTestInLooperThread public void addChangeListener() { collection = prepareRealmListInLooperThread(); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AtomicInteger listenerCalledCount = new AtomicInteger(0); collection.addChangeListener(new RealmChangeListener>() { @Override @@ -1039,7 +1039,7 @@ public void onChange(RealmList collection, OrderedCollectionChangeSet chang @RunTestInLooperThread public void removeAllChangeListeners() { collection = prepareRealmListInLooperThread(); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AtomicInteger listenerCalledCount = new AtomicInteger(0); collection.addChangeListener(new RealmChangeListener>() { @Override @@ -1077,7 +1077,7 @@ public void onChange(RealmList element) { @RunTestInLooperThread public void removeChangeListener() { collection = prepareRealmListInLooperThread(); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AtomicInteger listenerCalledCount = new AtomicInteger(0); RealmChangeListener> listener1 = new RealmChangeListener>() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index 6d20ffcec0..1bf59fdf2a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -195,11 +195,11 @@ public void query() { @Test @RunTestInLooperThread public void async_query() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, TEST_DATA_SIZE); final RealmResults allTypesRealmModels = realm.where(AllTypesRealmModel.class).distinctAsync(AllTypesRealmModel.FIELD_STRING); - looperThread.keepStrongReference.add(allTypesRealmModels); + looperThread.keepStrongReference(allTypesRealmModels); allTypesRealmModels.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -231,8 +231,8 @@ public void dynamicObject() { @Test @RunTestInLooperThread public void dynamicRealm() { - populateTestRealm(looperThread.realm, TEST_DATA_SIZE); - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + populateTestRealm(looperThread.getRealm(), TEST_DATA_SIZE); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); dynamicRealm.beginTransaction(); DynamicRealmObject dog = dynamicRealm.createObject(AllTypesRealmModel.CLASS_NAME, 42); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index d92e76a252..c3b2944f1b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -1576,7 +1576,7 @@ public void setter_changePrimaryKeyThrows() { @Test @RunTestInLooperThread public void addChangeListener_throwOnAddingNullListenerFromLooperThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); Dog dog = createManagedDogObjectFromRealmInstance(realm); try { @@ -1614,7 +1614,7 @@ public void addChangeListener_throwOnAddingNullListenerFromNonLooperThread() thr @Test @RunTestInLooperThread public void changeListener_triggeredWhenObjectIsDeleted() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -1664,7 +1664,7 @@ public void onChange(Dog object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void addChangeListener_throwInsiderTransaction() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); @@ -1695,7 +1695,7 @@ public void onChange(Dog object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void removeChangeListener_throwOnRemovingNullListenerFromLooperThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); Dog dog = createManagedDogObjectFromRealmInstance(realm); try { @@ -1733,7 +1733,7 @@ public void removeChangeListener_throwOnRemovingNullListenerFromNonLooperThread( @Test @RunTestInLooperThread public void removeChangeListener_insideTransaction() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final Dog dog = createManagedDogObjectFromRealmInstance(realm); RealmChangeListener realmChangeListener = new RealmChangeListener() { @Override @@ -1762,7 +1762,7 @@ public void onChange(Dog object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void removeAllChangeListeners() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setAge(13); @@ -1793,7 +1793,7 @@ public void onChange(Dog object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void removeAllChangeListeners_thenAdd() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setAge(13); @@ -1866,7 +1866,7 @@ public void removeAllChangeListeners_throwOnUnmanagedObject() { @Test @RunTestInLooperThread public void addChangeListener_returnedObjectOfCopyToRealmOrUpdate() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); realm.createObject(AllTypesPrimaryKey.class, 1); @@ -1876,7 +1876,7 @@ public void addChangeListener_returnedObjectOfCopyToRealmOrUpdate() { allTypesPrimaryKey = realm.copyToRealmOrUpdate(allTypesPrimaryKey); realm.commitTransaction(); - looperThread.keepStrongReference.add(allTypesPrimaryKey); + looperThread.keepStrongReference(allTypesPrimaryKey); allTypesPrimaryKey.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypesPrimaryKey element) { @@ -1898,14 +1898,14 @@ public void onChange(AllTypesPrimaryKey element) { @RunTestInLooperThread public void addChangeListener_listenerShouldBeCalledIfObjectChangesAfterAsyncReturn() { final AtomicInteger listenerCounter = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); realm.createObject(AllTypesPrimaryKey.class, 1); realm.commitTransaction(); // Step 1 final AllTypesPrimaryKey allTypesPrimaryKey = realm.where(AllTypesPrimaryKey.class).findFirstAsync(); - looperThread.keepStrongReference.add(allTypesPrimaryKey); + looperThread.keepStrongReference(allTypesPrimaryKey); allTypesPrimaryKey.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypesPrimaryKey element) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 107836df2c..e11ad67955 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2994,11 +2994,11 @@ public void findAllSorted_onSubObjectField() { @Test @RunTestInLooperThread public void findAllSortedAsync_onSubObjectField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) .findAllSortedAsync(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { @@ -3029,7 +3029,7 @@ public void findAllSorted_listOnSubObjectField() { @Test @RunTestInLooperThread public void findAllSortedAsync_listOnSubObjectField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); String[] fieldNames = new String[2]; fieldNames[0] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; fieldNames[1] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; @@ -3041,7 +3041,7 @@ public void findAllSortedAsync_listOnSubObjectField() { populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) .findAllSortedAsync(fieldNames, sorts); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { @@ -3193,7 +3193,7 @@ public void distinct_invalidTypesLinkedFields() { @RunTestInLooperThread public void distinctAsync() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -3228,10 +3228,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -3269,7 +3269,7 @@ public void onChange(RealmResults object) { @RunTestInLooperThread public void distinctAsync_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); @@ -3288,8 +3288,8 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctDate.addChangeListener(new RealmChangeListener>() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 6eefd9023f..b4ead0c1bf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -348,7 +348,7 @@ public void distinct_invalidTypesLinkedFields() { @Test @RunTestInLooperThread public void changeListener_syncIfNeeded_updatedFromOtherThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults results = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_LONG, 10).findAll(); @@ -421,7 +421,7 @@ private void populateTestRealm(Realm testRealm, int objects) { @RunTestInLooperThread public void distinctAsync() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -456,10 +456,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -497,7 +497,7 @@ public void onChange(RealmResults object) { @RunTestInLooperThread public void distinctAsync_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); @@ -522,8 +522,8 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctDate.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -545,7 +545,7 @@ public void onChange(RealmResults object) { @RunTestInLooperThread public void distinctAsync_notIndexedFields() { final AtomicInteger changeListenerCalled = new AtomicInteger(4); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -584,10 +584,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -869,10 +869,10 @@ private RealmResults populateRealmResultsOnLinkView(Realm realm) { @Test @RunTestInLooperThread public void accessors_resultsBuiltOnDeletedLinkView_deletionAsALocalCommit() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); // Step 1 RealmResults dogs = populateRealmResultsOnLinkView(realm); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults dogs) { @@ -930,9 +930,9 @@ public void execute(Realm realm) { @RunTestInLooperThread public void accessors_resultsBuiltOnDeletedLinkView_deletionAsARemoteCommit() { // Step 1 - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults dogs = populateRealmResultsOnLinkView(realm); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults dogs) { @@ -983,10 +983,10 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void addChangeListener() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -1003,7 +1003,7 @@ public void onChange(RealmResults object) { @RunTestInLooperThread public void addChangeListener_twice() { final AtomicInteger listenersTriggered = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); RealmChangeListener> listener = new RealmChangeListener>() { @@ -1031,7 +1031,7 @@ public void run() { }); // Adding it twice will be ignored, so removing it will not cause the listener to be triggered. - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addChangeListener(listener); collection.addChangeListener(listener); collection.removeChangeListener(listener); @@ -1055,7 +1055,7 @@ public void addChangeListener_null() { @RunTestInLooperThread public void removeChangeListener() { final AtomicInteger listenersTriggered = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); RealmChangeListener> listener = new RealmChangeListener>() { @@ -1065,7 +1065,7 @@ public void onChange(RealmResults object) { } }; - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addChangeListener(listener); collection.removeChangeListener(listener); @@ -1100,7 +1100,7 @@ public void removeChangeListener_null() { @RunTestInLooperThread public void removeAllChangeListeners() { final AtomicInteger listenersTriggered = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); RealmChangeListener> listenerA = new RealmChangeListener>() { @@ -1116,7 +1116,7 @@ public void onChange(RealmResults object) { } }; - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addChangeListener(listenerA); collection.addChangeListener(listenerB); collection.removeAllChangeListeners(); @@ -1141,7 +1141,7 @@ public void run() { @Test @RunTestInLooperThread public void removeAllChangeListeners_thenAdd() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); collection.addChangeListener(new RealmChangeListener>() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 06094f3552..3bbe104e97 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3085,7 +3085,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void closeRealmInChangeListenerWhenThereIsListenerOnEmptyObject() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmChangeListener dummyListener = new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -3126,7 +3126,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void closeRealmInChangeListenerWhenThereIsListenerOnObject() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmChangeListener dummyListener = new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -3171,7 +3171,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void closeRealmInChangeListenerWhenThereIsListenerOnResults() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmChangeListener> dummyListener = new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -3210,7 +3210,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void addChangeListener_throwOnAddingNullListenerFromLooperThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); try { realm.addChangeListener(null); @@ -3243,7 +3243,7 @@ public void run() throws Exception { @Test @RunTestInLooperThread public void removeChangeListener_throwOnRemovingNullListenerFromLooperThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); try { realm.removeChangeListener(null); @@ -3879,7 +3879,7 @@ public File answer(InvocationOnMock invocationOnMock) throws Throwable { public void refresh_triggerNotifications() { final CountDownLatch bgThreadDone = new CountDownLatch(1); final AtomicBoolean listenerCalled = new AtomicBoolean(false); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(0, results.size()); results.addChangeListener(new RealmChangeListener>() { @@ -3895,7 +3895,7 @@ public void onChange(RealmResults results) { new Thread(new Runnable() { @Override public void run() { - Realm realm = Realm.getInstance(looperThread.realmConfiguration); + Realm realm = Realm.getInstance(looperThread.getConfiguration()); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -3938,7 +3938,7 @@ public void run() { public void refresh_forceSynchronousNotifications() { final CountDownLatch bgThreadDone = new CountDownLatch(1); final AtomicBoolean listenerCalled = new AtomicBoolean(false); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults results = realm.where(AllTypes.class).findAllAsync(); results.addChangeListener(new RealmChangeListener>() { @Override @@ -3952,7 +3952,7 @@ public void onChange(RealmResults results) { new Thread(new Runnable() { @Override public void run() { - Realm realm = Realm.getInstance(looperThread.realmConfiguration); + Realm realm = Realm.getInstance(looperThread.getConfiguration()); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index e306343516..eafe61d82d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -105,7 +105,7 @@ public void call(AllTypes rxObject) { @RunTestInLooperThread public void realmObject_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); final AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -167,7 +167,7 @@ public void call(AllTypes rxObject) { @RunTestInLooperThread public void findFirstAsync_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -189,7 +189,7 @@ public void call(AllTypes rxObject) { @RunTestInLooperThread public void findFirstAsync_emittedOnDelete() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); final AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -282,7 +282,7 @@ public void call(RealmResults rxResults) { @RunTestInLooperThread public void realmResults_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); RealmResults results = realm.where(AllTypes.class).findAll(); realm.commitTransaction(); @@ -305,7 +305,7 @@ public void call(RealmResults allTypes) { @RunTestInLooperThread public void realmList_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); realm.commitTransaction(); @@ -329,7 +329,7 @@ public void call(RealmList dogs) { @RunTestInLooperThread public void dynamicRealmResults_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); dynamicRealm.beginTransaction(); RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); dynamicRealm.commitTransaction(); @@ -370,7 +370,7 @@ public void call(RealmResults rxResults) { @RunTestInLooperThread public void findAllAsync_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); subscription = realm.where(AllTypes.class).findAllAsync().asObservable().subscribe(new Action1>() { @Override public void call(RealmResults rxResults) { @@ -404,7 +404,7 @@ public void call(Realm rxRealm) { @RunTestInLooperThread public void realm_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); subscription = realm.asObservable().subscribe(new Action1() { @Override public void call(Realm rxRealm) { @@ -445,7 +445,7 @@ public void call(Throwable throwable) { @Test @RunTestInLooperThread public void dynamicRealm_emittedOnUpdate() { - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); final AtomicInteger subscriberCalled = new AtomicInteger(0); subscription = dynamicRealm.asObservable().subscribe(new Action1() { @Override @@ -700,7 +700,7 @@ public void call(DynamicRealmObject obj) { public void realmResults_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { @@ -743,7 +743,7 @@ public void call(Throwable throwable) { public void dynamicRealmResults_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final DynamicRealm realm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { @@ -787,7 +787,7 @@ public void call(Throwable throwable) { public void realmObject_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { @@ -830,7 +830,7 @@ public void call(Throwable throwable) { public void dynamicRealmObject_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final DynamicRealm realm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index e20ec38079..396969108f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -347,7 +347,7 @@ public void realmSortMultiFailures() { public void resorting() throws InterruptedException { final AtomicInteger changeListenerCalled = new AtomicInteger(4); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.setAutoRefresh(true); final Runnable endTest = new Runnable() { @@ -368,7 +368,7 @@ public void run() { // rr0: [0, 1, 2, 3] final RealmResults rr0 = realm.where(AllTypes.class).findAll(); - looperThread.keepStrongReference.add(rr0); + looperThread.keepStrongReference(rr0); rr0.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -380,7 +380,7 @@ public void onChange(RealmResults element) { // rr1: [1, 2, 0, 3] final RealmResults rr1 = realm.where(AllTypes.class).findAll().sort(FIELD_LONG, Sort.ASCENDING); - looperThread.keepStrongReference.add(rr1); + looperThread.keepStrongReference(rr1); rr1.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -396,7 +396,7 @@ public void onChange(RealmResults element) { // rr2: [0, 3, 1, 2] final RealmResults rr2 = realm.where(AllTypes.class).findAll().sort(FIELD_LONG, Sort.DESCENDING); - looperThread.keepStrongReference.add(rr2); + looperThread.keepStrongReference(rr2); rr2.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -485,7 +485,7 @@ public void run() { RealmResults objectsAscending = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_DATE, Sort.ASCENDING); assertEquals(TEST_SIZE, objectsAscending.size()); - looperThread.keepStrongReference.add(objectsAscending); + looperThread.keepStrongReference(objectsAscending); objectsAscending.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -501,7 +501,7 @@ public void onChange(RealmResults element) { RealmResults objectsDescending = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_DATE, Sort.DESCENDING); assertEquals(TEST_SIZE, objectsDescending.size()); - looperThread.keepStrongReference.add(objectsDescending); + looperThread.keepStrongReference(objectsDescending); objectsDescending.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 731c21d3a1..090f048cb8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -779,7 +779,7 @@ public static void populateForDistinctFieldsOrder(Realm realm, long numberOfBloc } public static void awaitOrFail(CountDownLatch latch) { - awaitOrFail(latch, 60); + awaitOrFail(latch, 300); } public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { @@ -796,36 +796,40 @@ public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { } } + public interface LooperTest { + CountDownLatch getRealmClosedSignal(); + Looper getLooper(); + Throwable getAssertionError(); + } + // Cleans resource, shutdowns the executor service and throws any background exception. @SuppressWarnings("Finally") - public static void exitOrThrow(final ExecutorService executorService, - final CountDownLatch signalTestFinished, - final CountDownLatch signalClosedRealm, - final Looper[] looper, - final Throwable[] throwable) throws Throwable { + public static void exitOrThrow(ExecutorService executorService, CountDownLatch testFinishedSignal, LooperTest test) throws Throwable { // Waits for the signal indicating the test's use case is done. try { // Even if this fails we want to try as hard as possible to cleanup. If we fail to close all resources // properly, the `after()` method will most likely throw as well because it tries do delete any Realms // used. Any exception in the `after()` code will mask the original error. - TestHelper.awaitOrFail(signalTestFinished); + TestHelper.awaitOrFail(testFinishedSignal); } finally { - if (looper[0] != null) { + Looper looper = test.getLooper(); + if (looper != null) { // Failing to quit the looper will not execute the finally block responsible // of closing the Realm. - looper[0].quit(); + looper.quit(); } // Waits for the finally block to execute and closes the Realm. - TestHelper.awaitOrFail(signalClosedRealm); + TestHelper.awaitOrFail(test.getRealmClosedSignal()); // Closes the executor. // This needs to be called after waiting since it might interrupt waitRealmThreadExecutorFinish(). executorService.shutdownNow(); - if (throwable[0] != null) { + Throwable fault = test.getAssertionError(); + if (fault != null) { // Throws any assertion errors happened in the background thread. - throw throwable[0]; + throw fault; } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index 73bf9731c5..aa4c01fc54 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -82,7 +82,7 @@ public void setUp() { @Test @RunTestInLooperThread public void callback_should_trigger_for_createObject() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -102,7 +102,7 @@ public void run() { final Dog dog = realm.createObject(Dog.class); realm.commitTransaction(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -119,8 +119,8 @@ public void onChange(Dog object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_createObject_dynamic_realm() { - final DynamicRealm realm = DynamicRealm.getInstance(looperThread.realmConfiguration); - looperThread.keepStrongReference.add(realm); + final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); + looperThread.keepStrongReference(realm); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealm object) { @@ -141,7 +141,7 @@ public void run() { final DynamicRealmObject dog = realm.createObject("Dog"); realm.commitTransaction(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealmObject object) { @@ -159,7 +159,7 @@ public void onChange(DynamicRealmObject object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_copyToRealm() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -181,7 +181,7 @@ public void run() { final Dog dog = realm.copyToRealm(akamaru); realm.commitTransaction(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -199,7 +199,7 @@ public void onChange(Dog object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_copyToRealmOrUpdate() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -223,7 +223,7 @@ public void run() { final PrimaryKeyAsLong primaryKeyAsLong = realm.copyToRealmOrUpdate(obj); realm.commitTransaction(); - looperThread.keepStrongReference.add(primaryKeyAsLong); + looperThread.keepStrongReference(primaryKeyAsLong); primaryKeyAsLong.addChangeListener(new RealmChangeListener() { @Override public void onChange(PrimaryKeyAsLong object) { @@ -250,7 +250,7 @@ public void onChange(PrimaryKeyAsLong object) { public void callback_should_trigger_for_createObjectFromJson() { assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); try { InputStream in = TestHelper.loadJsonFromAssets(InstrumentationRegistry.getTargetContext(), "all_simple_types.json"); realm.beginTransaction(); @@ -258,7 +258,7 @@ public void callback_should_trigger_for_createObjectFromJson() { realm.commitTransaction(); in.close(); - looperThread.keepStrongReference.add(objectFromJson); + looperThread.keepStrongReference(objectFromJson); objectFromJson.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -285,7 +285,7 @@ public void onChange(AllTypes object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_createObjectFromJson_from_JSONObject() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); try { JSONObject json = new JSONObject(); @@ -300,7 +300,7 @@ public void callback_should_trigger_for_createObjectFromJson_from_JSONObject() { final AllTypes objectFromJson = realm.createObjectFromJson(AllTypes.class, json); realm.commitTransaction(); - looperThread.keepStrongReference.add(objectFromJson); + looperThread.keepStrongReference(objectFromJson); objectFromJson.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -329,7 +329,7 @@ public void onChange(AllTypes object) { public void callback_should_trigger_for_createOrUpdateObjectFromJson() { assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -366,7 +366,7 @@ public void run() { realm.commitTransaction(); in.close(); - looperThread.keepStrongReference.add(objectFromJson); + looperThread.keepStrongReference(objectFromJson); objectFromJson.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypesPrimaryKey object) { @@ -395,7 +395,7 @@ public void onChange(AllTypesPrimaryKey object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_createOrUpdateObjectFromJson_from_JSONObject() throws JSONException { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -426,7 +426,7 @@ public void run() { final AllTypesPrimaryKey newObj = realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, json); realm.commitTransaction(); - looperThread.keepStrongReference.add(newObj); + looperThread.keepStrongReference(newObj); newObj.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypesPrimaryKey object) { @@ -451,7 +451,7 @@ public void onChange(AllTypesPrimaryKey object) { @Test @RunTestInLooperThread public void callback_with_relevant_commit_realmobject_sync() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // Step 1: Creates object realm.beginTransaction(); @@ -460,7 +460,7 @@ public void callback_with_relevant_commit_realmobject_sync() { realm.commitTransaction(); final Dog dog = realm.where(Dog.class).findFirst(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -491,7 +491,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void callback_with_relevant_commit_realmobject_async() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // Step 1: Creates object. realm.beginTransaction(); @@ -501,7 +501,7 @@ public void callback_with_relevant_commit_realmobject_async() { final Dog dog = realm.where(Dog.class).findFirstAsync(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -543,7 +543,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void callback_with_relevant_commit_realmresults_sync() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // Step 1: Creates object. realm.beginTransaction(); @@ -552,7 +552,7 @@ public void callback_with_relevant_commit_realmresults_sync() { realm.commitTransaction(); final RealmResults dogs = realm.where(Dog.class).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -585,7 +585,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void callback_with_relevant_commit_realmresults_async() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // Step 1: Creates object. realm.beginTransaction(); @@ -594,7 +594,7 @@ public void callback_with_relevant_commit_realmresults_async() { realm.commitTransaction(); final RealmResults dogs = realm.where(Dog.class).findAllAsync(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -641,7 +641,7 @@ public void execute(Realm realm) { @RunTestInLooperThread public void multiple_callbacks_should_be_invoked_realmobject_sync() { final int NUMBER_OF_LISTENERS = 7; - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -660,7 +660,7 @@ public void run() { realm.commitTransaction(); Dog dog = realm.where(Dog.class).findFirst(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); for (int i = 0; i < NUMBER_OF_LISTENERS; i++) { dog.addChangeListener(new RealmChangeListener() { @Override @@ -680,7 +680,7 @@ public void onChange(Dog object) { @RunTestInLooperThread public void multiple_callbacks_should_be_invoked_realmobject_async() { final int NUMBER_OF_LISTENERS = 7; - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog akamaru = realm.createObject(Dog.class); @@ -688,7 +688,7 @@ public void multiple_callbacks_should_be_invoked_realmobject_async() { Dog dog = realm.where(Dog.class).findFirstAsync(); assertTrue(dog.load()); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); for (int i = 0; i < NUMBER_OF_LISTENERS; i++) { dog.addChangeListener(new RealmChangeListener() { @Override @@ -719,14 +719,14 @@ public void run() { @RunTestInLooperThread public void multiple_callbacks_should_be_invoked_realmresults_sync() { final int NUMBER_OF_LISTENERS = 7; - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog akamaru = realm.createObject(Dog.class); realm.commitTransaction(); RealmResults dogs = realm.where(Dog.class).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); for (int i = 0; i < NUMBER_OF_LISTENERS; i++) { dogs.addChangeListener(new RealmChangeListener>() { @Override @@ -750,7 +750,7 @@ public void onChange(RealmResults results) { @RunTestInLooperThread public void multiple_callbacks_should_be_invoked_realmresults_async() { final int NUMBER_OF_LISTENERS = 7; - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog akamaru = realm.createObject(Dog.class); @@ -772,7 +772,7 @@ public void run() { RealmResults dogs = realm.where(Dog.class).findAllAsync(); assertTrue(dogs.load()); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); for (int i = 0; i < NUMBER_OF_LISTENERS; i++) { dogs.addChangeListener(new RealmChangeListener>() { @Override @@ -799,14 +799,14 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void non_looper_thread_commit_realmobject_sync() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); realm.createObject(Dog.class); realm.commitTransaction(); Dog dog = realm.where(Dog.class).findFirst(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -830,14 +830,14 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void non_looper_thread_commit_realmobject_async() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); realm.createObject(Dog.class).setAge(1); realm.commitTransaction(); Dog dog = realm.where(Dog.class).findFirstAsync(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -869,7 +869,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void non_looper_thread_commit_realmresults_sync() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -890,7 +890,7 @@ public void run() { realm.commitTransaction(); final RealmResults dogs = realm.where(Dog.class).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -924,7 +924,7 @@ public void run() { @Test @RunTestInLooperThread public void non_looper_thread_commit_realmresults_async() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -956,7 +956,7 @@ public void run() { }; final RealmResults dogs = realm.where(Dog.class).findAllAsync(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -1080,7 +1080,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void changeListener_onResultsBuiltOnDeletedLinkView() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); for (int i = 0; i < 10; i++) { @@ -1092,7 +1092,7 @@ public void changeListener_onResultsBuiltOnDeletedLinkView() { final RealmResults dogs = allTypes.getColumnRealmList().where().equalTo(Dog.FIELD_NAME, "name_0").findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 2592c5f664..83ca6db974 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -253,7 +253,7 @@ public void addListener_shouldBeCalledToReturnTheQueryResults() { Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { @@ -342,7 +342,7 @@ public void addListener_queryNotReturned() { Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { @@ -363,7 +363,7 @@ public void addListener_queryReturned() { Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); assertEquals(collection.size(), 4); // Trigger the query to run. collection.addListener(collection, new RealmChangeListener() { @Override @@ -388,7 +388,7 @@ public void addListener_triggeredByLocalCommit() { final AtomicInteger listenerCounter = new AtomicInteger(0); final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { @@ -468,7 +468,7 @@ public void collectionIterator_invalid_looperThread_byRemoteTransaction() { Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); final TestIterator iterator = new TestIterator(collection); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); assertFalse(iterator.isDetached(sharedRealm)); collection.addListener(collection, new RealmChangeListener() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 0276a8a432..0516f0e115 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -85,7 +85,7 @@ public void run() { @RunTestInLooperThread public void addChangeListener_byLocalChanges() { final AtomicBoolean commitReturns = new AtomicBoolean(false); - SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); + SharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { @@ -121,10 +121,10 @@ public void addChangeListener_byRemoteChanges() { final AtomicInteger commitCounter = new AtomicInteger(0); final AtomicInteger listenerCounter = new AtomicInteger(0); - looperThread.realm.close(); + looperThread.getRealm().close(); - SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); - looperThread.keepStrongReference.add(sharedRealm); + SharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); + looperThread.keepStrongReference(sharedRealm); sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { @@ -135,22 +135,22 @@ public void onChange(SharedRealm sharedRealm) { sharedRealm.close(); looperThread.testComplete(); } else { - makeRemoteChanges(looperThread.realmConfiguration); + makeRemoteChanges(looperThread.getConfiguration()); commitCounter.getAndIncrement(); } } }); - makeRemoteChanges(looperThread.realmConfiguration); + makeRemoteChanges(looperThread.getConfiguration()); commitCounter.getAndIncrement(); } @Test @RunTestInLooperThread public void removeChangeListeners() { - SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); + SharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); Integer dummyObserver = 1; - looperThread.keepStrongReference.add(dummyObserver); - looperThread.keepStrongReference.add(sharedRealm); + looperThread.keepStrongReference(dummyObserver); + looperThread.keepStrongReference(sharedRealm); sharedRealm.realmNotifier.addChangeListener(dummyObserver, new RealmChangeListener() { @Override public void onChange(Integer dummy) { @@ -168,6 +168,6 @@ public void onChange(SharedRealm sharedRealm) { // This should only remove the listeners related with dummyObserver sharedRealm.realmNotifier.removeChangeListeners(dummyObserver); - makeRemoteChanges(looperThread.realmConfiguration); + makeRemoteChanges(looperThread.getConfiguration()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index 186040bdec..49d1ececa6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -20,11 +20,12 @@ import android.os.Looper; import org.junit.runner.Description; +import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; -import java.io.PrintWriter; -import java.io.StringWriter; +import java.io.PrintStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -38,188 +39,222 @@ import io.realm.RealmConfiguration; import io.realm.TestHelper; -import static org.junit.Assert.fail; /** * Rule that runs the test inside a worker looper thread. This rule is responsible - * of creating a temp directory containing a Realm instance then delete it, once the test finishes. - * + * of creating a temp directory containing a Realm instance then deleting it, once the test finishes. + *

                      * All Realms used in a method method annotated with {@code @RunTestInLooperThread } should use - * {@link RunInLooperThread#createConfiguration()} and friends to create their configurations. Failing to do so can - * result in the test failing because the Realm could not be deleted (Reason is that {@link TestRealmConfigurationFactory} - * and this class does not agree in which order to delete all open Realms. + * {@link RunInLooperThread#createConfiguration()} and friends to create their configurations. + * Failing to do so can result in the test failing because the Realm could not be deleted + * (this class and {@link TestRealmConfigurationFactory} do not agree in which order to delete + * the open Realms). */ public class RunInLooperThread extends TestRealmConfigurationFactory { + private static final long WAIT_TIMEOUT_MS = 60 * 1000; + + // lock protecting objects shared with the test thread + private final Object lock = new Object(); + + // Thread safe + private final CountDownLatch signalTestCompleted = new CountDownLatch(1); + + // Access guarded by 'lock' + private RealmConfiguration realmConfiguration; // Default Realm created by this Rule. It is guaranteed to be closed when the test finishes. - public Realm realm; - // Custom Realm used by the test. Saving the reference here will guarantee the instance is closed when exiting the test. - public List testRealms = new ArrayList(); - public RealmConfiguration realmConfiguration; - private CountDownLatch signalTestCompleted; + // Access guarded by 'lock' + private Realm realm; + + // Access guarded by 'lock' private Handler backgroundHandler; // the variables created inside the test are local and eligible for GC. // but sometimes we need the variables to survive across different Looper // events (Callbacks happening in the future), so we add a strong reference // to them for the duration of the test. - public LinkedList keepStrongReference; + // Access guarded by 'lock' + private LinkedList keepStrongReference; - @Override - protected void before() throws Throwable { - super.before(); - realmConfiguration = createConfiguration(UUID.randomUUID().toString()); - signalTestCompleted = new CountDownLatch(1); - keepStrongReference = new LinkedList(); - } + // Custom Realm used by the test. Saving the reference here will guarantee + // that the instance is closed when exiting the test. + // Access guarded by 'lock' + private List testRealms; - @Override - protected void after() { - super.after(); - realmConfiguration = null; - realm = null; - testRealms.clear(); - keepStrongReference = null; + /** + * Get the configuration for the test realm. + *

                      + * Set on main thread, accessed from test thread. + * Valid after {@code before}. + * + * @return the test realm configuration. + */ + public RealmConfiguration getConfiguration() { + synchronized (lock) { + return realmConfiguration; + } } - @Override - public Statement apply(final Statement base, Description description) { - final RunTestInLooperThread annotation = description.getAnnotation(RunTestInLooperThread.class); - if (annotation == null) { - return base; - } - return new Statement() { - private Throwable testException; - - @Override - @SuppressWarnings({"ClassNewInstance", "Finally"}) - public void evaluate() throws Throwable { - before(); - final String threadName = annotation.threadName(); - Class runnableBefore = annotation.before(); - if (!runnableBefore.isInterface()) { - runnableBefore.newInstance().run(realmConfiguration); - } + /** + * Get the test realm. + *

                      + * Set on test thread, accessed from main thread. + * Valid only after the test thread has started. + * + * @return the test realm. + */ + public Realm getRealm() { + synchronized (lock) { + while (backgroundHandler == null) { try { - final CountDownLatch signalClosedRealm = new CountDownLatch(1); - final Throwable[] threadAssertionError = new Throwable[1]; - final Looper[] backgroundLooper = new Looper[1]; - final ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable runnable) { - return new Thread(runnable, threadName); - } - }); - //noinspection unused - final Future submit = executorService.submit(new Runnable() { - @Override - public void run() { - Looper.prepare(); - backgroundLooper[0] = Looper.myLooper(); - backgroundHandler = new Handler(backgroundLooper[0]); - try { - realm = Realm.getInstance(realmConfiguration); - base.evaluate(); - Looper.loop(); - } catch (Throwable e) { - threadAssertionError[0] = e; - unitTestFailed = true; - } finally { - try { - looperTearDown(); - } catch (Throwable t) { - if (threadAssertionError[0] == null) { - threadAssertionError[0] = t; - } - unitTestFailed = true; - } - signalTestCompleted.countDown(); - if (realm != null) { - realm.close(); - } - if (!testRealms.isEmpty()) { - for (Realm testRealm : testRealms) { - testRealm.close(); - } - } - signalClosedRealm.countDown(); - } - } - }); - TestHelper.exitOrThrow(executorService, signalTestCompleted, signalClosedRealm, backgroundLooper, threadAssertionError); - } catch (Throwable error) { - // These exceptions should only come from TestHelper.awaitOrFail() - testException = error; - } finally { - // Tries as hard as possible to close down gracefully, while still keeping all exceptions intact. - try { - after(); - } catch (Throwable e) { - if (testException != null) { - // Both TestHelper.awaitOrFail() and after() threw an exception. Make sure we are aware of - // that fact by printing both exceptions. - StringWriter testStackTrace = new StringWriter(); - testException.printStackTrace(new PrintWriter(testStackTrace)); - - StringWriter afterStackTrace = new StringWriter(); - e.printStackTrace(new PrintWriter(afterStackTrace)); - - StringBuilder errorMessage = new StringBuilder() - .append("after() threw an error that shadows a test case error") - .append('\n') - .append("== Test case exception ==\n") - .append(testStackTrace.toString()) - .append('\n') - .append("== after() exception ==\n") - .append(afterStackTrace.toString()); - fail(errorMessage.toString()); - } else { - // Only after() threw an exception - throw e; - } - } - - // Only TestHelper.awaitOrFail() threw an exception - if (testException != null) { - //noinspection ThrowFromFinallyBlock - throw testException; - } + lock.wait(WAIT_TIMEOUT_MS); + } catch (InterruptedException ignore) { + break; } } - }; + return realm; + } } /** - * Signal that the test has completed. + * Hold a reference to an object, to prevent it from being GCed, + * until after the test completes. + *

                      + * Accessed only from the main thread, here, but synchronized in case it is called from within a test. + * Valid after {@code before}. */ - public void testComplete() { - signalTestCompleted.countDown(); + public void keepStrongReference(Object obj) { + synchronized (lock) { + keepStrongReference.add(obj); + } } /** - * Signal that the test has completed. - * - * @param latches additional latches to wait before set the test completed flag. + * Add a Realm to be closed when test is complete. + *

                      + * Accessed from both test and main threads. + * Valid after {@code before}. */ - public void testComplete(CountDownLatch... latches) { - for (CountDownLatch latch : latches) { - TestHelper.awaitOrFail(latch); + public void addTestRealm(Realm realm) { + synchronized (lock) { + testRealms.add(realm); } - signalTestCompleted.countDown(); } /** - * Posts a runnable to this worker threads looper. + * Explicitly close all held realms. + *

                      + * 'testRealms' is accessed from both test and main threads. + * 'testRealms' is valid after {@code before}. + */ + public void closeTestRealms() { + List realms = new ArrayList<>(); + synchronized (lock) { + List tmp = testRealms; + testRealms = realms; + realms = tmp; + } + + for (Realm testRealm : realms) { + testRealm.close(); + } + } + + /** + * Posts a runnable to the currently running looper. */ public void postRunnable(Runnable runnable) { - backgroundHandler.post(runnable); + getBackgroundHandler().post(runnable); } /** * Posts a runnable to this worker threads looper with a delay in milli second. */ public void postRunnableDelayed(Runnable runnable, long delayMillis) { - backgroundHandler.postDelayed(runnable, delayMillis); + getBackgroundHandler().postDelayed(runnable, delayMillis); + } + + /** + * Signal that the test has completed. + *

                      + * Used on both the main and test threads. + * Valid after {@code before}. + */ + public void testComplete() { + signalTestCompleted.countDown(); + } + + /** + * Signal that the test has completed, after waiting for any additional latches. + * + * @param latches additional latches to wait on, before setting the test completed flag. + */ + public void testComplete(CountDownLatch... latches) { + for (CountDownLatch latch : latches) { + TestHelper.awaitOrFail(latch); + } + testComplete(); + } + + // Accessed from both test and main threads + // Valid after the test thread has started. + private Handler getBackgroundHandler() { + synchronized (lock) { + while (backgroundHandler == null) { + try { + lock.wait(WAIT_TIMEOUT_MS); + } catch (InterruptedException ignore) { + break; + } + } + return this.backgroundHandler; + } + } + + // Accessed from both test and main threads + // Storing the handler is the gate that indicates that the test thread has started. + void setBackgroundHandler(Handler backgroundHandler) { + synchronized (lock) { + this.backgroundHandler = backgroundHandler; + lock.notifyAll(); + } + } + + @Override + protected void before() throws Throwable { + super.before(); + + RealmConfiguration config = createConfiguration(UUID.randomUUID().toString()); + LinkedList refs = new LinkedList<>(); + List realms = new LinkedList<>(); + + synchronized (lock) { + realmConfiguration = config; + realm = null; + backgroundHandler = null; + keepStrongReference = refs; + testRealms = realms; + } + } + + @Override + protected void after() { + super.after(); + + // probably belt *and* suspenders... + synchronized (lock) { + backgroundHandler = null; + keepStrongReference = null; + } + } + + @Override + public Statement apply(Statement base, Description description) { + final RunTestInLooperThread annotation = description.getAnnotation(RunTestInLooperThread.class); + if (annotation == null) { + return base; + } + return new RunInLooperThreadStatement(annotation, base); } /** @@ -229,6 +264,28 @@ public void postRunnableDelayed(Runnable runnable, long delayMillis) { public void looperTearDown() { } + private void initRealm() { + synchronized (lock) { + realm = Realm.getInstance(realmConfiguration); + } + } + + private void closeRealms() { + closeTestRealms(); + + Realm oldRealm; + synchronized (lock) { + oldRealm = realm; + + realm = null; + realmConfiguration = null; + } + + if (oldRealm != null) { + oldRealm.close(); + } + } + /** * If an implementation of this is supplied with the annotation, the {@link RunnableBefore#run(RealmConfiguration)} * will be executed before the looper thread starts. It is normally for populating the Realm before the test. @@ -236,4 +293,144 @@ public void looperTearDown() { public interface RunnableBefore { void run(RealmConfiguration realmConfig); } + + private class RunInLooperThreadStatement extends Statement { + private final RunTestInLooperThread annotation; + private final Statement base; + + RunInLooperThreadStatement(RunTestInLooperThread annotation, Statement base) { + this.annotation = annotation; + this.base = base; + } + + @Override + @SuppressWarnings("ClassNewInstance") + public void evaluate() throws Throwable { + before(); + + Class runnableBefore = annotation.before(); + if (!runnableBefore.isInterface()) { + // this is dangerous: newInstance can throw checked exceptions. + // this is dangerous: config is mutable. + runnableBefore.newInstance().run(getConfiguration()); + } + + runTest(annotation.threadName()); + } + + private void runTest(final String threadName) throws Throwable { + Throwable failure = null; + + try { + ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { return new Thread(runnable, threadName); } + }); + + TestThread test = new TestThread(base); + + @SuppressWarnings({"UnusedAssignment", "unused"}) + Future ignored = executorService.submit(test); + + TestHelper.exitOrThrow(executorService, signalTestCompleted, test); + } catch (Throwable testfailure) { + // These exceptions should only come from TestHelper.awaitOrFail() + failure = testfailure; + } finally { + // Tries as hard as possible to close down gracefully, while still keeping all exceptions intact. + failure = cleanUp(failure); + } + if (failure != null) { + throw failure; + } + } + + private Throwable cleanUp(Throwable testfailure) { + try { + after(); + return testfailure; + } catch (Throwable afterFailure) { + if (testfailure == null) { + // Only after() threw an exception + return afterFailure; + } + + // Both TestHelper.awaitOrFail() and after() threw exceptions + return new MultipleFailureException(Arrays.asList(testfailure, afterFailure)) { + @Override + public void printStackTrace(PrintStream out) { + int i = 0; + for (Throwable t : getFailures()) { + out.println("Error " + i + ": " + t.getMessage()); + t.printStackTrace(out); + out.println(); + i++; + } + } + }; + } + } + } + + private class TestThread implements Runnable, TestHelper.LooperTest { + private final CountDownLatch signalClosedRealm = new CountDownLatch(1); + private final Statement base; + private Looper looper; + private Throwable threadAssertionError; + + TestThread(Statement base) { + this.base = base; + } + + @Override + public CountDownLatch getRealmClosedSignal() { + return signalClosedRealm; + } + + @Override + public synchronized Looper getLooper() { + return looper; + } + + private synchronized void setLooper(Looper looper) { + this.looper = looper; + setBackgroundHandler(new Handler(looper)); + } + + @Override + public synchronized Throwable getAssertionError() { + return threadAssertionError; + } + + // Only record the first error + private synchronized void setAssertionError(Throwable threadAssertionError) { + if (this.threadAssertionError == null) { + this.threadAssertionError = threadAssertionError; + } + } + + @Override + public void run() { + Looper.prepare(); + try { + initRealm(); + setLooper(Looper.myLooper()); + base.evaluate(); + Looper.loop(); + } catch (Throwable t) { + setAssertionError(t); + setUnitTestFailed(); + } finally { + try { + looperTearDown(); + } catch (Throwable t) { + setAssertionError(t); + setUnitTestFailed(); + } + testComplete(); + closeRealms(); + signalClosedRealm.countDown(); + } + } + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index 979717e96a..04bc9a4f6d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -51,7 +51,8 @@ public class TestRealmConfigurationFactory extends TemporaryFolder { private final Map map = new ConcurrentHashMap(); private final Set configurations = Collections.newSetFromMap(map); - protected boolean unitTestFailed = false; + + private boolean unitTestFailed = false; @Override public Statement apply(final Statement base, Description description) { @@ -62,7 +63,7 @@ public void evaluate() throws Throwable { try { base.evaluate(); } catch (Throwable throwable) { - unitTestFailed = true; + setUnitTestFailed(); throw throwable; } finally { after(); @@ -89,7 +90,7 @@ protected void after() { } } catch (IllegalStateException e) { // Only throws the exception caused by deleting the opened Realm if the test case itself doesn't throw. - if (!unitTestFailed) { + if (!isUnitTestFailed()) { throw e; } } finally { @@ -98,6 +99,14 @@ protected void after() { } } + public synchronized void setUnitTestFailed() { + this.unitTestFailed = true; + } + + private synchronized boolean isUnitTestFailed() { + return this.unitTestFailed; + } + // This builder creates a configuration that is *NOT* managed. // You have to delete it yourself. public RealmConfiguration.Builder createConfigurationBuilder() { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index a360d94fed..d5c949a1bf 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -88,7 +88,7 @@ public void onError(SyncSession session, ObjectServerError error) { .build(); Realm realm = Realm.getInstance(config); - looperThread.testRealms.add(realm); + looperThread.addTestRealm(realm); // Trigger error SyncManager.simulateClientReset(SyncManager.getSession(config)); @@ -117,7 +117,7 @@ public void onError(SyncSession session, ObjectServerError error) { } // Execute Client Reset - looperThread.testRealms.get(0).close(); + looperThread.closeTestRealms(); handler.executeClientReset(); // Validate that files have been moved @@ -129,10 +129,9 @@ public void onError(SyncSession session, ObjectServerError error) { .build(); Realm realm = Realm.getInstance(config); - looperThread.testRealms.add(realm); + looperThread.addTestRealm(realm); // Trigger error SyncManager.simulateClientReset(SyncManager.getSession(config)); } - } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 6ebb8efed5..354e365a09 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -106,7 +106,7 @@ public void onError(SyncSession session, ObjectServerError error) { .build(); final Realm realm = Realm.getInstance(config); - looperThread.testRealms.add(realm); + looperThread.addTestRealm(realm); // FIXME: Right now we have no Java API for detecting when a session is established // So we optimistically assume it has been connected after 1 second. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index f78c34acfe..0ad9688104 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -69,7 +69,7 @@ public void onError(SyncSession session, ObjectServerError error) { }) .build(); final Realm realm1 = Realm.getInstance(config1); - looperThread.testRealms.add(realm1); + looperThread.addTestRealm(realm1); realm1.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { @@ -83,7 +83,7 @@ public void execute(Realm realm) { // 3. Create PermissionOffer final AtomicReference offerId = new AtomicReference(null); final Realm user1ManagementRealm = user1.getManagementRealm(); - looperThread.testRealms.add(user1ManagementRealm); + looperThread.addTestRealm(user1ManagementRealm); user1ManagementRealm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { @@ -103,7 +103,7 @@ public void onSuccess() { RealmResults offers = user1ManagementRealm.where(PermissionOffer.class) .equalTo("id", offerId.get()) .findAllAsync(); - looperThread.keepStrongReference.add(offers); + looperThread.keepStrongReference(offers); offers.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults offers) { @@ -113,7 +113,7 @@ public void onChange(RealmResults offers) { final String offerToken = offer.getToken(); final AtomicReference offerResponseId = new AtomicReference(); final Realm user2ManagementRealm = user2.getManagementRealm(); - looperThread.testRealms.add(user2ManagementRealm); + looperThread.addTestRealm(user2ManagementRealm); user2ManagementRealm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { @@ -128,7 +128,7 @@ public void onSuccess() { RealmResults responses = user2ManagementRealm.where(PermissionOfferResponse.class) .equalTo("id", offerResponseId.get()) .findAllAsync(); - looperThread.keepStrongReference.add(responses); + looperThread.keepStrongReference(responses); responses.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults responses) { @@ -136,9 +136,9 @@ public void onChange(RealmResults responses) { if (response != null && response.isSuccessful() && response.getToken().equals(offerToken)) { // 7. Response accepted. It should now be possible for user2 to access user1's Realm Realm realm = Realm.getInstance(config2); - looperThread.testRealms.add(realm); + looperThread.addTestRealm(realm); RealmResults dogs = realm.where(Dog.class).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { From a7cb800710a236500703ba3a278ffeb23186206e Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 28 Apr 2017 00:57:14 +0900 Subject: [PATCH 0659/2110] update build-tools to 25.0.3 (#4560) --- Dockerfile | 2 +- README.md | 2 +- examples/build.gradle | 2 +- realm/realm-library/build.gradle | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 47bcded27d..450ccf5cff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,7 +53,7 @@ RUN mkdir "${ANDROID_HOME}/licenses" && \ echo -en "\nd23d63a1f23e25e2c7a316e29eb60396e7924281" > "${ANDROID_HOME}/licenses/android-sdk-preview-license" RUN echo y | android update sdk --no-ui --all --filter tools > /dev/null RUN echo y | android update sdk --no-ui --all --filter platform-tools | grep 'package installed' -RUN echo y | android update sdk --no-ui --all --filter build-tools-25.0.2 | grep 'package installed' +RUN echo y | android update sdk --no-ui --all --filter build-tools-25.0.3 | grep 'package installed' RUN echo y | android update sdk --no-ui --all --filter extra-android-m2repository | grep 'package installed' RUN echo y | android update sdk --no-ui --all --filter android-25 | grep 'package installed' diff --git a/README.md b/README.md index 37ee3b95d1..fcd8d6c7f3 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install the Android SDK **Build-Tools 25.0.2**, **Android N (API 25)** (for example through Android Studio’s **Android SDK Manager**). + * Download & install the Android SDK **Build-Tools 25.0.3**, **Android N (API 25)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). diff --git a/examples/build.gradle b/examples/build.gradle index df34fdd8a0..8fd93d0f63 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,5 +1,5 @@ project.ext.sdkVersion = 25 -project.ext.buildTools = '25.0.2' +project.ext.buildTools = '25.0.3' // Don't cache SNAPSHOT (changing) dependencies. configurations.all { diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 8322220938..8d8616a30d 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -37,7 +37,7 @@ ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE android { compileSdkVersion 25 - buildToolsVersion '25.0.2' + buildToolsVersion '25.0.3' defaultConfig { minSdkVersion 9 From 8a25e80e4dd674b7152d8e3175cf375aa1ca4335 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 28 Apr 2017 12:24:18 +0900 Subject: [PATCH 0660/2110] Add manualClean task, which works without requiring any dependency artifacts (#4549) * Add manualClean task, which works without requiring any dependency artifacts * update description * manualClean task removes ${System.env.HOME}/.m2/repository/io/realm as well --- build.gradle | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/build.gradle b/build.gradle index ddc7ff1dc6..3eac28d01d 100644 --- a/build.gradle +++ b/build.gradle @@ -267,6 +267,37 @@ task clean { dependsOn cleanLocalMavenRepos } +task manualClean { + description = 'Clean build files without using clean tasks defined in sub projects' + group = 'Clean' + + doLast { + // clean 'build' directories + exec { + workingDir "${rootDir}" + commandLine 'find', '.', '-type', 'd', '-name', 'build', '-print', '-exec', 'rm', '-rf', '{}', ';', '-prune' + } + + // clean '.externalNativeBuild' directories + exec { + workingDir "${rootDir}" + commandLine 'find', '.', '-type', 'd', '-name', '.externalNativeBuild', '-print', '-exec', 'rm', '-rf', '{}', ';', '-prune' + } + + // clean '.gradle' directories except one in the root + exec { + workingDir "${rootDir}" + commandLine 'find', '.', '-mindepth', '2', '-type', 'd', '-name', '.gradle', '-print', '-exec', 'rm', '-rf', '{}', ';', '-prune' + } + + // clean ${System.env.HOME}/.m2/repository/io/realm + exec { + workingDir "${rootDir}" + commandLine 'sh', '-c', "echo \"${System.env.HOME}/.m2/repository/io/realm\" && rm -rf \"${System.env.HOME}/.m2/repository/io/realm\"" + } + } +} + task uploadDistributionPackage { group = 'Release' description = 'Upload the distribution package to S3' From 15371fe3ae64687eaa1f07178afff8ec5d670c81 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 28 Apr 2017 12:20:46 +0800 Subject: [PATCH 0661/2110] Use target table to create snapshot from LinkView (#4556) - Fix #4554 . - Remove useless confusing `LinkView.getTable()`. --- CHANGELOG.md | 1 + .../androidTest/java/io/realm/RealmListTests.java | 15 +++++++++++++++ .../main/java/io/realm/internal/Collection.java | 2 +- .../src/main/java/io/realm/internal/LinkView.java | 10 +--------- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66dc3029b9..9f1d21e7b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Added missing row validation check in certain cases on invalidated/deleted objects (#4540). * Initializing Realm is now more resilient if `Context.getFilesDir()` isn't working correctly (#4493). +* `OrderedRealmCollectionSnapshot.get()` returned a wrong object (#4554). ## 3.1.3 (2017-04-20) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 0e195f1343..c19bcb35e9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -38,6 +38,7 @@ import io.realm.entities.Dog; import io.realm.entities.Owner; import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Table; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -1105,4 +1106,18 @@ public void onChange(RealmList collection, OrderedCollectionChangeSet chang realm.commitTransaction(); assertEquals(1, listenerCalledCount.get()); } + + // https://github.com/realm/realm-java/issues/4554 + @Test + public void createSnapshot_shouldUseTargetTable() { + int sizeBefore = collection.size(); + OrderedRealmCollectionSnapshot snapshot = collection.createSnapshot(); + realm.beginTransaction(); + snapshot.get(0).deleteFromRealm(); + realm.commitTransaction(); + assertEquals(sizeBefore - 1, collection.size()); + + assertNotNull(collection.view); + assertEquals(collection.view.getTargetTable().getName(), snapshot.getTable().getName()); + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index bfe8ccaeeb..dcdf1ce6ec 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -367,7 +367,7 @@ public Collection(SharedRealm sharedRealm, LinkView linkView, SortDescriptor sor this.sharedRealm = sharedRealm; this.context = sharedRealm.context; - this.table = linkView.getTable(); + this.table = linkView.getTargetTable(); this.context.addReference(this); // Collection created from LinkView is loaded by default. So that the listener will be triggered first time // with empty change set. diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index 8eb61a3de3..03af9ff392 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -134,13 +134,6 @@ public boolean isAttached() { return nativeIsAttached(nativePtr); } - /** - * Returns the {@link Table} which all links point to. - */ - public Table getTable() { - return parent; - } - /** * Removes all target rows pointed to by links in this link view, and clear this link view. */ @@ -159,8 +152,7 @@ public void removeTargetRow(int index) { public Table getTargetTable() { long nativeTablePointer = nativeGetTargetTable(nativePtr); - Table table = new Table(this.parent, nativeTablePointer); - return table; + return new Table(this.parent, nativeTablePointer); } private void checkImmutable() { From 1ed82bf99f4a50670100cc8d2f0a3a07cfa9dbe0 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 1 May 2017 13:20:45 +0300 Subject: [PATCH 0662/2110] Removed io.realm.internal.Util#javaPrint(String) (#4574) * remove io.realm.internal.Util#javaPrint(String) * remove Util.getNativeMemUsage() --- .../src/main/cpp/io_realm_internal_Util.cpp | 7 - .../realm-library/src/main/cpp/mem_usage.cpp | 167 ------------------ .../realm-library/src/main/cpp/mem_usage.hpp | 26 --- .../src/main/java/io/realm/internal/Util.java | 11 -- 4 files changed, 211 deletions(-) delete mode 100644 realm/realm-library/src/main/cpp/mem_usage.cpp delete mode 100644 realm/realm-library/src/main/cpp/mem_usage.hpp diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp index ab772f42bd..2c26299cbe 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Util.cpp @@ -22,7 +22,6 @@ #include #include -#include "mem_usage.hpp" #include "util.hpp" using std::string; @@ -78,12 +77,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) } } - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Util_nativeGetMemUsage(JNIEnv*, jclass) -{ - return static_cast(GetMemUsage()); -} - JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeGetTablePrefix(JNIEnv* env, jclass) { realm::StringData sd(TABLE_PREFIX); diff --git a/realm/realm-library/src/main/cpp/mem_usage.cpp b/realm/realm-library/src/main/cpp/mem_usage.cpp deleted file mode 100644 index 21a81ef15a..0000000000 --- a/realm/realm-library/src/main/cpp/mem_usage.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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 "mem_usage.hpp" - -#ifndef REALM_ENABLE_MEM_USAGE - -size_t GetMemUsage() -{ - return 0; -} - -#elif defined(_MSC_VER) // Microsoft Windows - - -#include -#include - -namespace { - -// Pre-declarations -DWORD CalculateWSPrivate(DWORD processID); - -// Calculate Private Working Set -// Source: http://www.codeproject.com/KB/cpp/XPWSPrivate.aspx - -int Compare(const void* Val1, const void* Val2) -{ - if (*(PDWORD)Val1 == *(PDWORD)Val2) - return 0; - - return *(PDWORD)Val1 > *(PDWORD)Val2 ? 1 : -1; -} - -DWORD dWorkingSetPages[1024 * 128]; // hold the working set - // information get from QueryWorkingSet() -DWORD dPageSize = 0x1000; - -DWORD CalculateWSPrivate(DWORD processID) -{ - DWORD dSharedPages = 0; - DWORD dPrivatePages = 0; - DWORD dPageTablePages = 0; - - HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID); - - if (!hProcess) - return 0; - - __try { - if (!QueryWorkingSet(hProcess, dWorkingSetPages, sizeof(dWorkingSetPages))) - __leave; - - DWORD dPages = dWorkingSetPages[0]; - - qsort(&dWorkingSetPages[1], dPages, sizeof(DWORD), Compare); - - for (DWORD i = 1; i <= dPages; i++) { - DWORD dCurrentPageStatus = 0; - DWORD dCurrentPageAddress; - DWORD dNextPageAddress; - DWORD dNextPageFlags; - DWORD dPageAddress = dWorkingSetPages[i] & 0xFFFFF000; - DWORD dPageFlags = dWorkingSetPages[i] & 0x00000FFF; - - while (i <= dPages) // iterate all pages - { - dCurrentPageStatus++; - - if (i == dPages) // if last page - break; - - dCurrentPageAddress = dWorkingSetPages[i] & 0xFFFFF000; - dNextPageAddress = dWorkingSetPages[i + 1] & 0xFFFFF000; - dNextPageFlags = dWorkingSetPages[i + 1] & 0x00000FFF; - - // decide whether iterate further or exit - //(this is non-contiguous page or have different flags) - if ((dNextPageAddress == (dCurrentPageAddress + dPageSize)) && (dNextPageFlags == dPageFlags)) { - i++; - } - else - break; - } - - if ((dPageAddress < 0xC0000000) || (dPageAddress > 0xE0000000)) { - if (dPageFlags & 0x100) // this is shared one - dSharedPages += dCurrentPageStatus; - - else // private one - dPrivatePages += dCurrentPageStatus; - } - else - dPageTablePages += dCurrentPageStatus; // page table region - } - - DWORD dTotal = dPages * 4; - DWORD dShared = dSharedPages * 4; - DWORD WSPrivate = dTotal - dShared; - - return WSPrivate; - } - __finally { - CloseHandle(hProcess); - } - return -1; -} - -} // anonymous namespace - -size_t GetMemUsage() -{ - return CalculateWSPrivate(GetCurrentProcessId()); -} - - -#elif defined(__APPLE__) // Mac / Darwin - - -#include - -size_t GetMemUsage() -{ - struct task_basic_info t_info; - - mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; - - if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count)) - return -1; - - // resident size is in t_info.resident_size; - // virtual size is in t_info.virtual_size; - return t_info.resident_size; -} - - -#else // POSIX - - -#include -// Debian package: libproc-dev -// Linker flag : -lproc -// Documentation : /usr/include/proc/readproc.h -#include - -size_t GetMemUsage() -{ - struct proc_t usage; - look_up_our_self(&usage); - return usage.vsize; -} - - -#endif diff --git a/realm/realm-library/src/main/cpp/mem_usage.hpp b/realm/realm-library/src/main/cpp/mem_usage.hpp deleted file mode 100644 index e3935c6360..0000000000 --- a/realm/realm-library/src/main/cpp/mem_usage.hpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -#ifndef __SUPPORT_MEM__ -#define __SUPPORT_MEM__ - -#include // size_t - -/// This function requires that REALM_ENABLE_MEM_USAGE is specified -/// during building. Otherwise it always returns zero. -size_t GetMemUsage(); - -#endif //__SUPPORT_MEM__ diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 1e89b70597..2fc7778786 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -32,17 +32,6 @@ public class Util { - public static long getNativeMemUsage() { - return nativeGetMemUsage(); - } - - static native long nativeGetMemUsage(); - - // Called by JNI. Do not remove. - static void javaPrint(String txt) { - System.out.print(txt); - } - public static String getTablePrefix() { return nativeGetTablePrefix(); } From d8ade20a616e576a5598d112acb92d1d72b56937 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 2 May 2017 16:25:02 +0900 Subject: [PATCH 0663/2110] Update Kotlin to 1.1.2 (#4582) --- examples/kotlinExample/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index a548e407de..737177a801 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.1' + ext.kotlin_version = '1.1.2-2' repositories { jcenter() mavenCentral() From f3f8ab2e1ad8cd911e785a05964649e0eea50d59 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Tue, 2 May 2017 00:26:52 -0700 Subject: [PATCH 0664/2110] Backport Unit Test PRs to releases (#4581) --- .../java/io/realm/DynamicRealmTests.java | 14 +- .../io/realm/LinkingObjectsManagedTests.java | 20 +- .../java/io/realm/NotificationsTest.java | 63 +-- .../java/io/realm/ObjectChangeSetTests.java | 44 +- .../OrderedCollectionChangeSetTests.java | 23 +- .../java/io/realm/RealmAsyncQueryTests.java | 156 +++--- .../java/io/realm/RealmCacheTests.java | 2 +- .../io/realm/RealmChangeListenerTests.java | 34 +- .../java/io/realm/RealmInMemoryTest.java | 6 +- .../java/io/realm/RealmInterprocessTest.java | 16 +- .../java/io/realm/RealmListTests.java | 8 +- .../java/io/realm/RealmModelTests.java | 8 +- .../java/io/realm/RealmObjectTests.java | 36 +- .../java/io/realm/RealmQueryTests.java | 32 +- .../java/io/realm/RealmResultsTests.java | 54 +- .../androidTest/java/io/realm/RealmTests.java | 36 +- .../java/io/realm/RxJavaTests.java | 26 +- .../androidTest/java/io/realm/SortTest.java | 12 +- .../androidTest/java/io/realm/TestHelper.java | 37 +- .../io/realm/TypeBasedNotificationsTests.java | 86 ++-- .../io/realm/internal/CollectionTests.java | 10 +- .../io/realm/internal/RealmNotifierTests.java | 20 +- .../java/io/realm/rule/RunInLooperThread.java | 483 ++++++++++++------ .../rule/TestRealmConfigurationFactory.java | 15 +- .../io/realm/util/RealmBackgroundTask.java | 4 +- .../java/io/realm/SessionTests.java | 7 +- .../java/io/realm/objectserver/AuthTests.java | 2 +- .../objectserver/ManagementRealmTests.java | 14 +- .../realm/objectserver/utils/Constants.java | 10 +- 29 files changed, 740 insertions(+), 538 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 1bec5cfa80..5d5b6ed371 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -356,7 +356,7 @@ public void findFirstAsync() { .between(AllTypes.FIELD_LONG, 4, 9) .findFirstAsync(); assertFalse(allTypes.isLoaded()); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); allTypes.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealmObject object) { @@ -389,7 +389,7 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); } @Test @@ -414,15 +414,15 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); } // Initializes a Dynamic Realm used by the *Async tests and keeps it ref in the looperThread. private DynamicRealm initializeDynamicRealm() { - RealmConfiguration defaultConfig = looperThread.realmConfiguration; + RealmConfiguration defaultConfig = looperThread.getConfiguration(); final DynamicRealm dynamicRealm = DynamicRealm.getInstance(defaultConfig); populateTestRealm(dynamicRealm, 10); - looperThread.keepStrongReference.add(dynamicRealm); + looperThread.keepStrongReference(dynamicRealm); return dynamicRealm; } @@ -531,8 +531,8 @@ public void onChange(RealmResults object) { signalCallbackDone.run(); } }); - looperThread.keepStrongReference.add(realmResults1); - looperThread.keepStrongReference.add(realmResults2); + looperThread.keepStrongReference(realmResults1); + looperThread.keepStrongReference(realmResults2); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 7174a91870..b729034259 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -172,7 +172,7 @@ public void issue4487_checkIfTableIsCorrect() { @Test @RunTestInLooperThread public void notification_notSentAfterUnregisterListenerModelObject() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -207,7 +207,7 @@ public void run(Realm realm) { @Test @RunTestInLooperThread public void notification_onCommitRealmResults() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -243,7 +243,7 @@ public void run(Realm realm) { @Test @RunTestInLooperThread public void notification_notSentAfterUnregisterListenerRealmResults() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -279,7 +279,7 @@ public void run(Realm realm) { @Test @RunTestInLooperThread public void notification_onDeleteRealmResults() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -316,7 +316,7 @@ public void run(Realm realm) { @Test @RunTestInLooperThread public void notification_notSentOnUnrelatedChangeRealmResults() { - final Realm looperThreadRealm = looperThread.realm; + final Realm looperThreadRealm = looperThread.getRealm(); looperThreadRealm.beginTransaction(); AllJavaTypes child = looperThreadRealm.createObject(AllJavaTypes.class, 10); @@ -405,7 +405,7 @@ public void json_updateList() { @Test @RunTestInLooperThread public void linkingObjects_IllegalStateException_ifNotYetLoaded() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.executeTransaction(new Realm.Transaction() { @Override @@ -433,7 +433,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void linkingObjects_IllegalStateException_ifDeleted() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.executeTransaction(new Realm.Transaction() { @Override @@ -468,7 +468,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void linkingObjects_IllegalStateException_ifDeletedIndirectly() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.executeTransaction(new Realm.Transaction() { @Override @@ -794,7 +794,9 @@ private void verifyPostConditions(final Realm realm, final PostConditions test, realm.commitTransaction(); // Runnable is guaranteed to be enqueued on the Looper queue, after the notifications - looperThread.keepStrongReference.addAll(Arrays.asList(refs)); + for (Object ref : refs) { + looperThread.keepStrongReference(ref); + } looperThread.postRunnable( new Runnable() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 6bd270bab7..9c0ed01755 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -162,7 +162,7 @@ public void onChange(Realm object) { } }; - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(listener); realm.addChangeListener(listener); realm.addChangeListener(new RealmChangeListener() { @@ -180,10 +180,10 @@ public void onChange(Realm object) { @Test public void notificationsNumber() throws InterruptedException, ExecutionException { + final CountDownLatch isReady = new CountDownLatch(1); + final CountDownLatch isRealmOpen = new CountDownLatch(1); final AtomicInteger counter = new AtomicInteger(0); - final AtomicBoolean isReady = new AtomicBoolean(false); final Looper[] looper = new Looper[1]; - final AtomicBoolean isRealmOpen = new AtomicBoolean(true); final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -201,12 +201,12 @@ public Boolean call() throws Exception { looper[0] = Looper.myLooper(); realm = Realm.getInstance(realmConfig); realm.addChangeListener(listener); - isReady.set(true); + isReady.countDown(); Looper.loop(); } finally { if (realm != null) { realm.close(); - isRealmOpen.set(false); + isRealmOpen.countDown(); } } return true; @@ -214,10 +214,7 @@ public Boolean call() throws Exception { }); // Waits until the looper in the background thread is started. - while (!isReady.get()) { - Thread.sleep(5); - } - Thread.sleep(100); + TestHelper.awaitOrFail(isReady); // Triggers OnRealmChanged on background thread. realm = Realm.getInstance(realmConfig); @@ -235,9 +232,7 @@ public Boolean call() throws Exception { } // Waits until the Looper thread is actually closed. - while (isRealmOpen.get()) { - Thread.sleep(5); - } + TestHelper.awaitOrFail(isRealmOpen); assertEquals(1, counter.get()); RealmCache.invokeWithGlobalRefCount(realmConfig, new TestHelper.ExpectedCountCallback(0)); @@ -262,7 +257,8 @@ public Boolean call() throws Exception { if (dogs.size() != 0) { return false; } - addHandlerMessages.await(1, TimeUnit.SECONDS); // Wait for main thread to add update messages. + // Wait for main thread to add update messages. + addHandlerMessages.await(TestHelper.VERY_SHORT_WAIT_SECS, TimeUnit.SECONDS); // Creates a Handler for the thread now. All message and references for the notification handler will be // cleared once we call close(). @@ -292,7 +288,7 @@ public void run() { }); // Waits until the looper is started on a background thread. - backgroundLooperStarted.await(1, TimeUnit.SECONDS); + backgroundLooperStarted.await(TestHelper.VERY_SHORT_WAIT_SECS, TimeUnit.SECONDS); // Executes a transaction that will trigger a Realm update. Realm realm = Realm.getInstance(realmConfig); @@ -317,7 +313,7 @@ public void run() { @RunTestInLooperThread public void globalListener_looperThread_triggeredByLocalCommit() { final AtomicInteger success = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -335,7 +331,7 @@ public void onChange(Realm object) { @RunTestInLooperThread public void globalListener_looperThread_triggeredByRemoteCommit() { final AtomicInteger success = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -361,7 +357,7 @@ public void onChange(Realm object) { looperThread.testComplete(); } }; - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(listener); realm.beginTransaction(); realm.commitTransaction(); @@ -370,7 +366,7 @@ public void onChange(Realm object) { @Test @RunTestInLooperThread public void addRemoveListenerConcurrency() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final AtomicInteger counter1 = new AtomicInteger(0); final AtomicInteger counter2 = new AtomicInteger(0); final AtomicInteger counter3 = new AtomicInteger(0); @@ -447,7 +443,7 @@ public void realmNotificationOrder() { // Test both ways to check accidental ordering from unordered collections. final AtomicInteger listenerACalled = new AtomicInteger(0); final AtomicInteger listenerBCalled = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmChangeListener listenerA = new RealmChangeListener() { @@ -544,8 +540,8 @@ public void onChange(Realm object) { realm.commitTransaction(); // Any REALM_CHANGED message should now only reach the open Handler on Thread1. try { - // TODO: Waiting 5 seconds is not a reliable condition. Figure out a better way for this. - if (!handlerNotified.await(5, TimeUnit.SECONDS)) { + // TODO: Waiting a few seconds is not a reliable condition. Figure out a better way for this. + if (!handlerNotified.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS)) { fail("Handler didn't receive message"); } } finally { @@ -573,11 +569,8 @@ public void run() { realm.setAutoRefresh(false); TestHelper.quitLooperOrFail(); backgroundLooperStartedAndStopped.countDown(); - try { - mainThreadCommitCompleted.await(); - } catch (InterruptedException e) { - fail("Thread interrupted"); // This will prevent backgroundThreadStopped from being called. - } + // This will prevent backgroundThreadStopped from being called. + TestHelper.awaitOrFail(mainThreadCommitCompleted); realm.close(); backgroundThreadStopped.countDown(); } @@ -690,12 +683,12 @@ public void onChange(Realm object) { @Test @RunTestInLooperThread public void asyncRealmResultsShouldNotBlockBackgroundCommitNotification() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults dogs = realm.where(Dog.class).findAllAsync(); final AtomicBoolean resultsListenerDone = new AtomicBoolean(false); final AtomicBoolean realmListenerDone = new AtomicBoolean(false); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); assertTrue(dogs.load()); assertEquals(0, dogs.size()); dogs.addChangeListener(new RealmChangeListener>() { @@ -750,7 +743,7 @@ public void execute(Realm realm) { public void asyncRealmObjectShouldNotBlockBackgroundCommitNotification() { final AtomicInteger numberOfRealmCallbackInvocation = new AtomicInteger(0); final CountDownLatch signalClosedRealm = new CountDownLatch(1); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(final Realm realm) { @@ -816,7 +809,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void realmListener_realmResultShouldBeSynced() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(1, results.size()); @@ -844,13 +837,13 @@ public void onChange(Realm element) { @Test @RunTestInLooperThread public void accessingSyncRealmResultInsideAsyncResultListener() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final AtomicInteger asyncResultCallback = new AtomicInteger(0); final RealmResults syncResults = realm.where(AllTypes.class).findAll(); RealmResults results = realm.where(AllTypes.class).findAllAsync(); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { @@ -884,11 +877,11 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void accessingSyncRealmResultsInsideAnotherResultListener() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults syncResults1 = realm.where(AllTypes.class).findAll(); final RealmResults syncResults2 = realm.where(AllTypes.class).findAll(); - looperThread.keepStrongReference.add(syncResults1); + looperThread.keepStrongReference(syncResults1); syncResults1.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -906,7 +899,7 @@ public void onChange(RealmResults element) { @Test @RunTestInLooperThread(threadName = "IntentService[1]") public void listenersNotAllowedOnIntentServiceThreads() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java index 2069ec6eee..2755132a69 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java @@ -76,7 +76,7 @@ public void onChange(AllTypes object, ObjectChangeSet changeSet) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); } private void checkChangedField(AllTypes allTypes, final String... fieldNames) { @@ -96,7 +96,7 @@ public void onChange(RealmModel object, ObjectChangeSet changeSet) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); } private void listenerShouldNotBeCalled(AllTypes allTypes) { @@ -117,7 +117,7 @@ public void run() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void objectDeleted() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkDeleted(allTypes); realm.beginTransaction(); @@ -128,7 +128,7 @@ public void objectDeleted() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLongField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_LONG); realm.beginTransaction(); @@ -139,7 +139,7 @@ public void changeLongField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeStringField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_STRING); realm.beginTransaction(); @@ -150,7 +150,7 @@ public void changeStringField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeFloatField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_FLOAT); realm.beginTransaction(); @@ -161,7 +161,7 @@ public void changeFloatField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeDoubleField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_DOUBLE); realm.beginTransaction(); @@ -172,7 +172,7 @@ public void changeDoubleField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeBooleanField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_BOOLEAN); realm.beginTransaction(); @@ -183,7 +183,7 @@ public void changeBooleanField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeDateField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_DATE); realm.beginTransaction(); @@ -194,7 +194,7 @@ public void changeDateField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeBinaryField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_BINARY); realm.beginTransaction(); @@ -205,7 +205,7 @@ public void changeBinaryField() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkFieldSetNewObject() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMOBJECT); realm.beginTransaction(); @@ -216,7 +216,7 @@ public void changeLinkFieldSetNewObject() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkFieldSetNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMOBJECT); realm.beginTransaction(); @@ -227,7 +227,7 @@ public void changeLinkFieldSetNull() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkFieldRemoveObject() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMOBJECT); realm.beginTransaction(); @@ -238,7 +238,7 @@ public void changeLinkFieldRemoveObject() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkFieldOriginalObjectChanged_notTrigger() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); listenerShouldNotBeCalled(allTypes); realm.beginTransaction(); @@ -249,7 +249,7 @@ public void changeLinkFieldOriginalObjectChanged_notTrigger() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkListAddObject() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMLIST); realm.beginTransaction(); @@ -260,7 +260,7 @@ public void changeLinkListAddObject() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeLinkListClear() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_REALMLIST); realm.beginTransaction(); @@ -271,7 +271,7 @@ public void changeLinkListClear() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeAllFields() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); checkChangedField(allTypes, AllTypes.FIELD_LONG, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT, AllTypes.FIELD_STRING, AllTypes.FIELD_BOOLEAN, @@ -295,7 +295,7 @@ public void changeAllFields() { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void changeDifferentFieldOneAfterAnother() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirst(); final AtomicBoolean stringChanged = new AtomicBoolean(false); final AtomicBoolean longChanged = new AtomicBoolean(false); @@ -339,7 +339,7 @@ public void onChange(RealmModel object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void findFirstAsync_changeSetIsNullWhenQueryReturns() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); allTypes.addChangeListener(new RealmObjectChangeListener() { @Override @@ -357,7 +357,7 @@ public void onChange(AllTypes object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread(before = PopulateOneAllTypes.class) public void findFirstAsync_queryExecutedByLocalCommit() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AtomicInteger listenerCounter = new AtomicInteger(0); final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); allTypes.addChangeListener(new RealmObjectChangeListener() { @@ -423,7 +423,7 @@ public void onChange(DynamicRealmObject object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void allParentObjectShouldBeInChangeSet() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); Owner owner = realm.createObject(Owner.class); @@ -443,7 +443,7 @@ public void allParentObjectShouldBeInChangeSet() { realm.commitTransaction(); RealmResults dogs = realm.where(Dog.class).equalTo(Dog.FIELD_HAS_TAIL, true).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java index 30f6521816..f05ca4163a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -157,7 +157,7 @@ private void registerCheckListener(Realm realm, final ChangesCheck changesCheck) switch (type) { case REALM_RESULTS: RealmResults results = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { @@ -167,7 +167,7 @@ public void onChange(RealmResults collection, OrderedCollectionChangeSet ch break; case REALM_LIST: RealmList list = realm.where(Owner.class).findFirst().getDogs(); - looperThread.keepStrongReference.add(list); + looperThread.keepStrongReference(list); list.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmList collection, OrderedCollectionChangeSet changeSet) { @@ -181,7 +181,7 @@ public void onChange(RealmList collection, OrderedCollectionChangeSet chang @Test @RunTestInLooperThread public void deletion() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); final ChangesCheck changesCheck = new ChangesCheck() { @@ -213,7 +213,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void insertion() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 0); // We need to create the owner. realm.beginTransaction(); createObjects(realm, 0, 2, 5, 6, 7, 9); @@ -247,7 +247,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void changes() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -278,7 +278,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void moves() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -307,7 +307,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void mixed_changes() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -347,7 +347,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void changes_then_delete() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -377,7 +377,7 @@ public void check(OrderedCollectionChangeSet changeSet) { @Test @RunTestInLooperThread public void insert_then_delete() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateData(realm, 10); ChangesCheck changesCheck = new ChangesCheck() { @Override @@ -409,7 +409,8 @@ public void emptyChangeSet_findAllAsync(){ looperThread.testComplete(); return; } - Realm realm = looperThread.realm; + + Realm realm = looperThread.getRealm(); populateData(realm, 10); final RealmResults results = realm.where(Dog.class).findAllSortedAsync(Dog.FIELD_AGE); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @@ -429,7 +430,7 @@ public void onChange(RealmResults collection, OrderedCollectionChangeSet ch new Thread(new Runnable() { @Override public void run() { - Realm realm = Realm.getInstance(looperThread.realmConfiguration) ; + Realm realm = Realm.getInstance(looperThread.getConfiguration()) ; realm.beginTransaction(); realm.where(Dog.class).equalTo(Dog.FIELD_AGE, 0).findFirst().deleteFromRealm(); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 4d4c051258..d04ed040bf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -71,7 +71,7 @@ public class RealmAsyncQueryTests { @Test @RunTestInLooperThread public void executeTransactionAsync() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); realm.executeTransactionAsync(new Realm.Transaction() { @@ -99,7 +99,7 @@ public void onError(Throwable error) { @Test @RunTestInLooperThread public void executeTransactionAsync_onSuccess() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); realm.executeTransactionAsync(new Realm.Transaction() { @@ -121,7 +121,7 @@ public void onSuccess() { @Test @RunTestInLooperThread public void executeTransactionAsync_onSuccessCallerRealmClosed() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); realm.executeTransactionAsync(new Realm.Transaction() { @@ -134,7 +134,7 @@ public void execute(Realm realm) { @Override public void onSuccess() { assertTrue(realm.isClosed()); - Realm newRealm = Realm.getInstance(looperThread.realmConfiguration); + Realm newRealm = Realm.getInstance(looperThread.getConfiguration()); assertEquals(1, newRealm.where(Owner.class).count()); assertEquals("Owner", newRealm.where(Owner.class).findFirst().getName()); newRealm.close(); @@ -147,7 +147,7 @@ public void onSuccess() { @Test @RunTestInLooperThread public void executeTransactionAsync_onError() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RuntimeException runtimeException = new RuntimeException("Oh! What a Terrible Failure"); assertEquals(0, realm.where(Owner.class).count()); @@ -170,7 +170,7 @@ public void onError(Throwable error) { @Test @RunTestInLooperThread public void executeTransactionAsync_onErrorCallerRealmClosed() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RuntimeException runtimeException = new RuntimeException("Oh! What a Terrible Failure"); assertEquals(0, realm.where(Owner.class).count()); @@ -183,7 +183,7 @@ public void execute(Realm realm) { @Override public void onError(Throwable error) { assertTrue(realm.isClosed()); - Realm newRealm = Realm.getInstance(looperThread.realmConfiguration); + Realm newRealm = Realm.getInstance(looperThread.getConfiguration()); assertEquals(0, newRealm.where(Owner.class).count()); assertNull(newRealm.where(Owner.class).findFirst()); assertEquals(runtimeException, error); @@ -197,7 +197,7 @@ public void onError(Throwable error) { @Test @RunTestInLooperThread public void executeTransactionAsync_NoCallbacks() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); realm.executeTransactionAsync(new Realm.Transaction() { @@ -223,7 +223,7 @@ public void executeTransactionAsync_cancelTransactionInside() throws Throwable { final TestHelper.TestLogger testLogger = new TestHelper.TestLogger(LogLevel.DEBUG); RealmLog.add(testLogger); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); assertEquals(0, realm.where(Owner.class).count()); @@ -257,7 +257,7 @@ public void onError(Throwable error) { @RunTestInLooperThread public void executeTransactionAsync_realmClosedOnSuccess() { final AtomicInteger counter = new AtomicInteger(100); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmCache.Callback cacheCallback = new RealmCache.Callback() { @Override public void onResult(int count) { @@ -296,7 +296,7 @@ public void execute(Realm realm) { @RunTestInLooperThread public void executeTransaction_async_realmClosedOnError() { final AtomicInteger counter = new AtomicInteger(100); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmCache.Callback cacheCallback = new RealmCache.Callback() { @Override public void onResult(int count) { @@ -337,7 +337,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void executeTransactionAsync_asyncQuery() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults results = realm.where(AllTypes.class).findAllAsync(); assertEquals(0, results.size()); @@ -414,7 +414,7 @@ public void onError(Throwable error) { @Test @RunTestInLooperThread public void findAllAsync() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults results = realm.where(AllTypes.class) .between("columnLong", 0, 4) @@ -423,7 +423,7 @@ public void findAllAsync() throws Throwable { assertFalse(results.isLoaded()); assertEquals(0, results.size()); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -438,7 +438,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void accessingRealmListOnUnloadedRealmObjectShouldThrow() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final AllTypes results = realm.where(AllTypes.class) .equalTo("columnLong", 0) @@ -481,7 +481,7 @@ public void findAllAsync_throwsOnNonLooperThread() throws Throwable { @Test @RunTestInLooperThread public void findAllAsync_withNotification() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults results = realm.where(AllTypes.class) .between("columnLong", 0, 4) @@ -496,7 +496,7 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); assertFalse(results.isLoaded()); assertEquals(0, results.size()); @@ -507,13 +507,13 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void findAllAsync_forceLoad() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults realmResults = realm.where(AllTypes.class) .between("columnLong", 0, 4) .findAllAsync(); - looperThread.keepStrongReference.add(realmResults); + looperThread.keepStrongReference(realmResults); // Notification should be called as well. realmResults.addChangeListener(new RealmChangeListener>() { @Override @@ -543,13 +543,13 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void findFirstAsync() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final AllTypes asyncObj = realm.where(AllTypes.class).findFirstAsync(); assertFalse(asyncObj.isValid()); assertFalse(asyncObj.isLoaded()); - looperThread.keepStrongReference.add(asyncObj); + looperThread.keepStrongReference(asyncObj); asyncObj.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -564,9 +564,9 @@ public void onChange(AllTypes object) { @Test @RunTestInLooperThread public void findFirstAsync_initialEmptyRow() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AllTypes firstAsync = realm.where(AllTypes.class).findFirstAsync(); - looperThread.keepStrongReference.add(firstAsync); + looperThread.keepStrongReference(firstAsync); firstAsync.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -580,19 +580,19 @@ public void onChange(AllTypes object) { @Test @RunTestInLooperThread public void findFirstAsync_updatedIfSyncRealmObjectIsUpdated() throws Throwable { - populateTestRealm(looperThread.realm, 1); - AllTypes firstSync = looperThread.realm.where(AllTypes.class).findFirst(); + populateTestRealm(looperThread.getRealm(), 1); + AllTypes firstSync = looperThread.getRealm().where(AllTypes.class).findFirst(); assertEquals(0, firstSync.getColumnLong()); assertEquals("test data 0", firstSync.getColumnString()); - final AllTypes firstAsync = looperThread.realm.where(AllTypes.class).findFirstAsync(); + final AllTypes firstAsync = looperThread.getRealm().where(AllTypes.class).findFirstAsync(); assertTrue(firstAsync.load()); assertTrue(firstAsync.isLoaded()); assertTrue(firstAsync.isValid()); assertEquals(0, firstAsync.getColumnLong()); assertEquals("test data 0", firstAsync.getColumnString()); - looperThread.keepStrongReference.add(firstAsync); + looperThread.keepStrongReference(firstAsync); firstAsync.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -601,9 +601,9 @@ public void onChange(AllTypes object) { } }); - looperThread.realm.beginTransaction(); + looperThread.getRealm().beginTransaction(); firstSync.setColumnString("Galacticon"); - looperThread.realm.commitTransaction(); + looperThread.getRealm().commitTransaction(); } // Finds elements [0-4] asynchronously then waits for the promise to be loaded @@ -611,13 +611,13 @@ public void onChange(AllTypes object) { @Test @RunTestInLooperThread public void findFirstAsync_withNotification() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final AllTypes realmResults = realm.where(AllTypes.class) .between("columnLong", 4, 9) .findFirstAsync(); - looperThread.keepStrongReference.add(realmResults); + looperThread.keepStrongReference(realmResults); realmResults.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -642,7 +642,7 @@ public void onChange(AllTypes object) { @RunTestInLooperThread public void findFirstAsync_forceLoad() throws Throwable { final AtomicBoolean listenerCalled = new AtomicBoolean(false); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final AllTypes realmResults = realm.where(AllTypes.class) .between("columnLong", 4, 9) @@ -671,7 +671,7 @@ public void onChange(RealmModel object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void findFirstAsync_twoListenersOnSameInvalidObjectsCauseNPE() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); final AtomicBoolean firstListenerCalled = new AtomicBoolean(false); @@ -707,7 +707,7 @@ public void onChange(AllTypes element) { @Test @RunTestInLooperThread public void findAllSortedAsync() throws Throwable { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults results = realm.where(AllTypes.class) @@ -717,7 +717,7 @@ public void findAllSortedAsync() throws Throwable { assertFalse(results.isLoaded()); assertEquals(0, results.size()); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -735,9 +735,9 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void combiningAsyncAndSync() { - populateTestRealm(looperThread.realm, 10); + populateTestRealm(looperThread.getRealm(), 10); - final RealmResults allTypesAsync = looperThread.realm.where(AllTypes.class).greaterThan("columnLong", 5).findAllAsync(); + final RealmResults allTypesAsync = looperThread.getRealm().where(AllTypes.class).greaterThan("columnLong", 5).findAllAsync(); final RealmResults allTypesSync = allTypesAsync.where().greaterThan("columnLong", 3).findAll(); // Call where() on an async results will load query. But to maintain the pre version 2.4.0 behaviour of @@ -752,7 +752,7 @@ public void onChange(RealmResults object) { looperThread.testComplete(); } }); - looperThread.keepStrongReference.add(allTypesAsync); + looperThread.keepStrongReference(allTypesAsync); } // Keeps advancing the Realm by sending 1 commit for each frame (16ms). @@ -770,7 +770,7 @@ public void stressTestBackgroundCommits() throws Throwable { @Override public void run() { Random random = new Random(System.currentTimeMillis()); - Realm backgroundThreadRealm = Realm.getInstance(looperThread.realm.getConfiguration()); + Realm backgroundThreadRealm = Realm.getInstance(looperThread.getRealm().getConfiguration()); for (int i = 0; i < NUMBER_OF_COMMITS; i++) { backgroundThreadRealm.beginTransaction(); AllTypes object = backgroundThreadRealm.createObject(AllTypes.class); @@ -788,13 +788,13 @@ public void run() { } }; - final RealmResults allAsync = looperThread.realm.where(AllTypes.class).findAllAsync(); + final RealmResults allAsync = looperThread.getRealm().where(AllTypes.class).findAllAsync(); allAsync.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { assertTrue(allAsync.isLoaded()); if (allAsync.size() == NUMBER_OF_COMMITS) { - AllTypes lastInserted = looperThread.realm.where(AllTypes.class) + AllTypes lastInserted = looperThread.getRealm().where(AllTypes.class) .equalTo("columnLong", latestLongValue[0]) .equalTo("columnFloat", latestFloatValue[0]) .findFirst(); @@ -804,7 +804,7 @@ public void onChange(RealmResults object) { } } }); - looperThread.keepStrongReference.add(allAsync); + looperThread.keepStrongReference(allAsync); looperThread.postRunnableDelayed(new Runnable() { @Override @@ -817,7 +817,7 @@ public void run() { @Test @RunTestInLooperThread public void distinctAsync() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -853,10 +853,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -893,7 +893,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread() public void distinctAsync_rememberQueryParams() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); final int TEST_SIZE = 10; for (int i = 0; i < TEST_SIZE; i++) { @@ -918,7 +918,7 @@ public void onChange(RealmResults results) { @Test @RunTestInLooperThread public void distinctAsync_notIndexedFields() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -958,10 +958,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -998,7 +998,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void distinctAsync_noneExistingField() throws Throwable { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -1014,7 +1014,7 @@ public void distinctAsync_noneExistingField() throws Throwable { @Test @RunTestInLooperThread public void batchUpdateDifferentTypeOfQueries() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); for (int i = 0; i < 5; ) { AllTypes allTypes = realm.createObject(AllTypes.class); @@ -1036,10 +1036,10 @@ public void batchUpdateDifferentTypeOfQueries() { new Sort[]{Sort.ASCENDING, Sort.DESCENDING}); RealmResults findDistinct = realm.where(AnnotationIndexTypes.class).distinctAsync("indexString"); - looperThread.keepStrongReference.add(findAllAsync); - looperThread.keepStrongReference.add(findAllSorted); - looperThread.keepStrongReference.add(findAllSortedMulti); - looperThread.keepStrongReference.add(findDistinct); + looperThread.keepStrongReference(findAllAsync); + looperThread.keepStrongReference(findAllSorted); + looperThread.keepStrongReference(findAllSortedMulti); + looperThread.keepStrongReference(findDistinct); final CountDownLatch queriesCompleted = new CountDownLatch(4); final CountDownLatch bgRealmClosedLatch = new CountDownLatch(1); @@ -1126,20 +1126,16 @@ public void onChange(RealmResults object) { new Thread() { @Override public void run() { - try { - queriesCompleted.await(); - Realm bgRealm = Realm.getInstance(realm.getConfiguration()); - - bgRealm.beginTransaction(); - bgRealm.createObject(AllTypes.class); - bgRealm.createObject(AnnotationIndexTypes.class); - bgRealm.commitTransaction(); - - bgRealm.close(); - bgRealmClosedLatch.countDown(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } + TestHelper.awaitOrFail(queriesCompleted); + Realm bgRealm = Realm.getInstance(realm.getConfiguration()); + + bgRealm.beginTransaction(); + bgRealm.createObject(AllTypes.class); + bgRealm.createObject(AnnotationIndexTypes.class); + bgRealm.commitTransaction(); + + bgRealm.close(); + bgRealmClosedLatch.countDown(); } }.start(); } @@ -1149,10 +1145,10 @@ public void run() { @RunTestInLooperThread public void queryingLinkHandover() throws Throwable { final AtomicInteger numberOfInvocations = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmResults allAsync = realm.where(Dog.class).equalTo("owner.name", "kiba").findAllAsync(); - looperThread.keepStrongReference.add(allAsync); + looperThread.keepStrongReference(allAsync); allAsync.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -1202,11 +1198,11 @@ public void doInBackground(Realm realm) { @RunTestInLooperThread public void badVersion_syncTransaction() throws NoSuchFieldException, IllegalAccessException { final AtomicInteger listenerCount = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); // 1. Makes sure that async query is not started. final RealmResults result = realm.where(AllTypes.class).findAllSortedAsync(AllTypes.FIELD_STRING); - looperThread.keepStrongReference.add(result); + looperThread.keepStrongReference(result); result.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -1250,7 +1246,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void batchUpdate_localRefIsDeletedInLoopOfNativeBatchUpdateQueries() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // For Android, the size of local ref map is 512. Uses 1024 for more pressure. final int TEST_COUNT = 1024; final AtomicBoolean updatesTriggered = new AtomicBoolean(false); @@ -1284,7 +1280,7 @@ public void execute(Realm realm) { // Step 2: Creates 2nd - TEST_COUNT queries. RealmResults results = realm.where(AllTypes.class).findAllAsync(); results.addChangeListener(this); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); } } } @@ -1292,7 +1288,7 @@ public void execute(Realm realm) { // Step 1. Creates first async to kick the test start. RealmResults results = realm.where(AllTypes.class).findAllAsync(); results.addChangeListener(listener); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); } // *** Helper methods *** diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index 1085a371b6..b0ca7ffce1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -255,7 +255,7 @@ public void run() { }); thread.start(); - closeLatch.await(); + TestHelper.awaitOrFail(closeLatch); RealmCache.invokeWithGlobalRefCount(defaultConfig, new TestHelper.ExpectedCountCallback(1)); realmA.close(); RealmCache.invokeWithGlobalRefCount(defaultConfig, new TestHelper.ExpectedCountCallback(0)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java index b058f3357a..b44bcc868f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java @@ -63,7 +63,7 @@ public void tearDown() { @Test @RunTestInLooperThread public void returnedRealmIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm realm) { @@ -79,7 +79,7 @@ public void onChange(Realm realm) { @Test @RunTestInLooperThread public void returnedDynamicRealmIsNotNull() { - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); dynamicRealm.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealm dynRealm) { @@ -96,9 +96,9 @@ public void onChange(DynamicRealm dynRealm) { @Test @RunTestInLooperThread public void returnedRealmResultsIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults cats = realm.where(Cat.class).findAll(); - looperThread.keepStrongReference.add(cats); + looperThread.keepStrongReference(cats); cats.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults result) { @@ -115,9 +115,9 @@ public void onChange(RealmResults result) { @Test @RunTestInLooperThread public void returnedRealmResultsOfModelIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults alltypes = realm.where(AllTypesRealmModel.class).findAll(); - looperThread.keepStrongReference.add(alltypes); + looperThread.keepStrongReference(alltypes); alltypes.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults result) { @@ -136,12 +136,12 @@ public void onChange(RealmResults result) { @Test @RunTestInLooperThread public void returnedRealmObjectIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); - Cat cat = looperThread.realm.createObject(Cat.class); + Cat cat = realm.createObject(Cat.class); realm.commitTransaction(); - looperThread.keepStrongReference.add(cat); + looperThread.keepStrongReference(cat); cat.addChangeListener(new RealmChangeListener() { @Override public void onChange(Cat object) { @@ -158,12 +158,12 @@ public void onChange(Cat object) { @Test @RunTestInLooperThread public void returnedRealmModelIsNotNull() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypesRealmModel model = realm.createObject(AllTypesRealmModel.class, 0); realm.commitTransaction(); - looperThread.keepStrongReference.add(model); + looperThread.keepStrongReference(model); RealmObject.addChangeListener(model, new RealmChangeListener() { @Override public void onChange(AllTypesRealmModel object) { @@ -180,15 +180,15 @@ public void onChange(AllTypesRealmModel object) { @Test @RunTestInLooperThread public void returnedDynamicRealmObjectIsNotNull() { - Realm realm = Realm.getInstance(looperThread.realmConfiguration); + Realm realm = Realm.getInstance(looperThread.getConfiguration()); realm.close(); - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); dynamicRealm.beginTransaction(); DynamicRealmObject allTypes = dynamicRealm.createObject(AllTypes.CLASS_NAME); dynamicRealm.commitTransaction(); - looperThread.keepStrongReference.add(allTypes); + looperThread.keepStrongReference(allTypes); allTypes.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealmObject object) { @@ -205,12 +205,12 @@ public void onChange(DynamicRealmObject object) { @Test @RunTestInLooperThread public void returnedDynamicRealmResultsIsNotNull() { - Realm realm = Realm.getInstance(looperThread.realmConfiguration); + Realm realm = Realm.getInstance(looperThread.getConfiguration()); realm.close(); - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); RealmResults all = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); - looperThread.keepStrongReference.add(all); + looperThread.keepStrongReference(all); all.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults result) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index 8fd06f3310..1b41af1114 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -217,7 +217,7 @@ public void run() { // Waits until Realm instance closed in main thread. try { - realmInMainClosedLatch.await(3, TimeUnit.SECONDS); + realmInMainClosedLatch.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { threadError[0] = new AssertionFailedError("Worker thread was interrupted."); realm.close(); @@ -232,7 +232,7 @@ public void run() { // Waits until the worker thread started. - workerCommittedLatch.await(3, TimeUnit.SECONDS); + workerCommittedLatch.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS); if (threadError[0] != null) { throw threadError[0]; } // Refreshes will be ran in the next loop, manually refreshes it here. @@ -253,7 +253,7 @@ public void run() { realmInMainClosedLatch.countDown(); // Waits until the worker thread finished. - workerClosedLatch.await(3, TimeUnit.SECONDS); + workerClosedLatch.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS); if (threadError[0] != null) { throw threadError[0]; } // Since all previous Realm instances has been closed before, below will create a fresh new in-mem-realm instance. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java index c4e597eb45..76c5c4c377 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java @@ -38,6 +38,7 @@ import io.realm.entities.AllTypes; import io.realm.services.RemoteProcessService; + // This is built for testing multi processes related cases. // To build a test case, create an InterprocessHandler in your test case. This handler will run in the newly // created thread's Looper. Remember to call Looper.loop() to start handling messages. @@ -97,7 +98,7 @@ public void run() { }); thread.start(); - latch.await(); + TestHelper.awaitOrFail(latch); if (throwableArray[0] != null) { throw throwableArray[0]; @@ -161,7 +162,7 @@ protected void setUp() throws Exception { serviceStartLatch = new CountDownLatch(1); Intent intent = new Intent(getContext(), RemoteProcessService.class); getContext().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); - assertTrue(serviceStartLatch.await(10, TimeUnit.SECONDS)); + assertTrue(serviceStartLatch.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS)); } @Override @@ -205,7 +206,7 @@ private void triggerServiceStep(RemoteProcessService.Step step) { // be retained by the system to be used next time. // Use getRemoteProcessInfo if you want to check the existence of remote process. private ActivityManager.RunningServiceInfo getServiceInfo() { - ActivityManager manager = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager manager = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); List serviceInfoList = manager.getRunningServices(Integer.MAX_VALUE); for (ActivityManager.RunningServiceInfo service : serviceInfoList) { if (RemoteProcessService.class.getName().equals(service.service.getClassName())) { @@ -217,7 +218,7 @@ private ActivityManager.RunningServiceInfo getServiceInfo() { // Gets the remote process info if it is alive. private ActivityManager.RunningAppProcessInfo getRemoteProcessInfo() { - ActivityManager manager = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager manager = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); List processInfoList = manager.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo info : processInfoList) { if (info.processName.equals(getContext().getPackageName() + ":remote")) { @@ -255,12 +256,12 @@ public void handleMessage(Message msg) { ActivityManager.RunningAppProcessInfo processInfo = getRemoteProcessInfo(); if (processInfo != null && processInfo.pid == servicePid && i >= 6) { // The process is still alive. - assertTrue(false); + fail("Process is still alive"); } else if (processInfo == null || processInfo.pid != servicePid) { // The process is gone. break; } - Thread.sleep(500, 0); + Thread.sleep(500); } } catch (InterruptedException e) { e.printStackTrace(); @@ -288,7 +289,8 @@ public void run() { // Step A triggerServiceStep(RemoteProcessService.stepCreateInitialRealm_A); - }}) { + } + }) { @Override public void handleMessage(Message msg) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index c19bcb35e9..038ccc905d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -994,7 +994,7 @@ public void add_set_dynamicObjectCreatedFromTypedRealm() { } private RealmList prepareRealmListInLooperThread() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); Owner owner = realm.createObject(Owner.class); owner.setName("Owner"); @@ -1011,7 +1011,7 @@ private RealmList prepareRealmListInLooperThread() { @RunTestInLooperThread public void addChangeListener() { collection = prepareRealmListInLooperThread(); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AtomicInteger listenerCalledCount = new AtomicInteger(0); collection.addChangeListener(new RealmChangeListener>() { @Override @@ -1040,7 +1040,7 @@ public void onChange(RealmList collection, OrderedCollectionChangeSet chang @RunTestInLooperThread public void removeAllChangeListeners() { collection = prepareRealmListInLooperThread(); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AtomicInteger listenerCalledCount = new AtomicInteger(0); collection.addChangeListener(new RealmChangeListener>() { @Override @@ -1078,7 +1078,7 @@ public void onChange(RealmList element) { @RunTestInLooperThread public void removeChangeListener() { collection = prepareRealmListInLooperThread(); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final AtomicInteger listenerCalledCount = new AtomicInteger(0); RealmChangeListener> listener1 = new RealmChangeListener>() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index 6d20ffcec0..1bf59fdf2a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -195,11 +195,11 @@ public void query() { @Test @RunTestInLooperThread public void async_query() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, TEST_DATA_SIZE); final RealmResults allTypesRealmModels = realm.where(AllTypesRealmModel.class).distinctAsync(AllTypesRealmModel.FIELD_STRING); - looperThread.keepStrongReference.add(allTypesRealmModels); + looperThread.keepStrongReference(allTypesRealmModels); allTypesRealmModels.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -231,8 +231,8 @@ public void dynamicObject() { @Test @RunTestInLooperThread public void dynamicRealm() { - populateTestRealm(looperThread.realm, TEST_DATA_SIZE); - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + populateTestRealm(looperThread.getRealm(), TEST_DATA_SIZE); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); dynamicRealm.beginTransaction(); DynamicRealmObject dog = dynamicRealm.createObject(AllTypesRealmModel.CLASS_NAME, 42); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index d92e76a252..f99ae25334 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -714,10 +714,7 @@ public void run() { realm.commitTransaction(); createLatch.countDown(); - try { - testEndLatch.await(); - } catch (InterruptedException ignored) { - } + TestHelper.awaitOrFail(testEndLatch); // 3. Closes Realm in this thread and finishes. realm.close(); @@ -725,7 +722,7 @@ public void run() { }; thread.start(); - createLatch.await(); + TestHelper.awaitOrFail(createLatch); // 2. Sets created object to target. realm.beginTransaction(); try { @@ -869,10 +866,7 @@ public void run() { realm.commitTransaction(); createLatch.countDown(); - try { - testEndLatch.await(); - } catch (InterruptedException ignored) { - } + TestHelper.awaitOrFail(testEndLatch); // 3. Close Realm in this thread and finishes. realm.close(); @@ -880,7 +874,7 @@ public void run() { }; thread.start(); - createLatch.await(); + TestHelper.awaitOrFail(createLatch); // 2. Sets created object to target. realm.beginTransaction(); try { @@ -1576,7 +1570,7 @@ public void setter_changePrimaryKeyThrows() { @Test @RunTestInLooperThread public void addChangeListener_throwOnAddingNullListenerFromLooperThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); Dog dog = createManagedDogObjectFromRealmInstance(realm); try { @@ -1614,7 +1608,7 @@ public void addChangeListener_throwOnAddingNullListenerFromNonLooperThread() thr @Test @RunTestInLooperThread public void changeListener_triggeredWhenObjectIsDeleted() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -1664,7 +1658,7 @@ public void onChange(Dog object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void addChangeListener_throwInsiderTransaction() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); @@ -1695,7 +1689,7 @@ public void onChange(Dog object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void removeChangeListener_throwOnRemovingNullListenerFromLooperThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); Dog dog = createManagedDogObjectFromRealmInstance(realm); try { @@ -1733,7 +1727,7 @@ public void removeChangeListener_throwOnRemovingNullListenerFromNonLooperThread( @Test @RunTestInLooperThread public void removeChangeListener_insideTransaction() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final Dog dog = createManagedDogObjectFromRealmInstance(realm); RealmChangeListener realmChangeListener = new RealmChangeListener() { @Override @@ -1762,7 +1756,7 @@ public void onChange(Dog object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void removeAllChangeListeners() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setAge(13); @@ -1793,7 +1787,7 @@ public void onChange(Dog object, ObjectChangeSet changeSet) { @Test @RunTestInLooperThread public void removeAllChangeListeners_thenAdd() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setAge(13); @@ -1866,7 +1860,7 @@ public void removeAllChangeListeners_throwOnUnmanagedObject() { @Test @RunTestInLooperThread public void addChangeListener_returnedObjectOfCopyToRealmOrUpdate() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); realm.createObject(AllTypesPrimaryKey.class, 1); @@ -1876,7 +1870,7 @@ public void addChangeListener_returnedObjectOfCopyToRealmOrUpdate() { allTypesPrimaryKey = realm.copyToRealmOrUpdate(allTypesPrimaryKey); realm.commitTransaction(); - looperThread.keepStrongReference.add(allTypesPrimaryKey); + looperThread.keepStrongReference(allTypesPrimaryKey); allTypesPrimaryKey.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypesPrimaryKey element) { @@ -1898,14 +1892,14 @@ public void onChange(AllTypesPrimaryKey element) { @RunTestInLooperThread public void addChangeListener_listenerShouldBeCalledIfObjectChangesAfterAsyncReturn() { final AtomicInteger listenerCounter = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); realm.createObject(AllTypesPrimaryKey.class, 1); realm.commitTransaction(); // Step 1 final AllTypesPrimaryKey allTypesPrimaryKey = realm.where(AllTypesPrimaryKey.class).findFirstAsync(); - looperThread.keepStrongReference.add(allTypesPrimaryKey); + looperThread.keepStrongReference(allTypesPrimaryKey); allTypesPrimaryKey.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypesPrimaryKey element) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 107836df2c..8be7cc00fa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -70,12 +70,12 @@ public class RealmQueryTests { @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); - protected final static int TEST_DATA_SIZE = 10; - protected final static int TEST_NO_PRIMARY_KEY_NULL_TYPES_SIZE = 200; + private final static int TEST_DATA_SIZE = 10; + private final static int TEST_NO_PRIMARY_KEY_NULL_TYPES_SIZE = 200; private final static long DECADE_MILLIS = 10 * TimeUnit.DAYS.toMillis(365); - protected Realm realm; + private Realm realm; @Before public void setUp() throws Exception { @@ -2624,7 +2624,7 @@ public void run() { thread.start(); } - latch.await(); + TestHelper.awaitOrFail(latch); } @Test @@ -2994,11 +2994,11 @@ public void findAllSorted_onSubObjectField() { @Test @RunTestInLooperThread public void findAllSortedAsync_onSubObjectField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) .findAllSortedAsync(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { @@ -3029,7 +3029,7 @@ public void findAllSorted_listOnSubObjectField() { @Test @RunTestInLooperThread public void findAllSortedAsync_listOnSubObjectField() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); String[] fieldNames = new String[2]; fieldNames[0] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; fieldNames[1] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; @@ -3041,7 +3041,7 @@ public void findAllSortedAsync_listOnSubObjectField() { populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) .findAllSortedAsync(fieldNames, sorts); - looperThread.keepStrongReference.add(results); + looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { @@ -3193,7 +3193,7 @@ public void distinct_invalidTypesLinkedFields() { @RunTestInLooperThread public void distinctAsync() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -3228,10 +3228,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -3269,7 +3269,7 @@ public void onChange(RealmResults object) { @RunTestInLooperThread public void distinctAsync_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); @@ -3288,8 +3288,8 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctDate.addChangeListener(new RealmChangeListener>() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 6eefd9023f..b4ead0c1bf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -348,7 +348,7 @@ public void distinct_invalidTypesLinkedFields() { @Test @RunTestInLooperThread public void changeListener_syncIfNeeded_updatedFromOtherThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults results = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_LONG, 10).findAll(); @@ -421,7 +421,7 @@ private void populateTestRealm(Realm testRealm, int objects) { @RunTestInLooperThread public void distinctAsync() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -456,10 +456,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -497,7 +497,7 @@ public void onChange(RealmResults object) { @RunTestInLooperThread public void distinctAsync_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); @@ -522,8 +522,8 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctDate.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -545,7 +545,7 @@ public void onChange(RealmResults object) { @RunTestInLooperThread public void distinctAsync_notIndexedFields() { final AtomicInteger changeListenerCalled = new AtomicInteger(4); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); @@ -584,10 +584,10 @@ public void run() { } }; - looperThread.keepStrongReference.add(distinctBool); - looperThread.keepStrongReference.add(distinctLong); - looperThread.keepStrongReference.add(distinctDate); - looperThread.keepStrongReference.add(distinctString); + looperThread.keepStrongReference(distinctBool); + looperThread.keepStrongReference(distinctLong); + looperThread.keepStrongReference(distinctDate); + looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -869,10 +869,10 @@ private RealmResults populateRealmResultsOnLinkView(Realm realm) { @Test @RunTestInLooperThread public void accessors_resultsBuiltOnDeletedLinkView_deletionAsALocalCommit() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); // Step 1 RealmResults dogs = populateRealmResultsOnLinkView(realm); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults dogs) { @@ -930,9 +930,9 @@ public void execute(Realm realm) { @RunTestInLooperThread public void accessors_resultsBuiltOnDeletedLinkView_deletionAsARemoteCommit() { // Step 1 - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults dogs = populateRealmResultsOnLinkView(realm); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults dogs) { @@ -983,10 +983,10 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void addChangeListener() { - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -1003,7 +1003,7 @@ public void onChange(RealmResults object) { @RunTestInLooperThread public void addChangeListener_twice() { final AtomicInteger listenersTriggered = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); RealmChangeListener> listener = new RealmChangeListener>() { @@ -1031,7 +1031,7 @@ public void run() { }); // Adding it twice will be ignored, so removing it will not cause the listener to be triggered. - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addChangeListener(listener); collection.addChangeListener(listener); collection.removeChangeListener(listener); @@ -1055,7 +1055,7 @@ public void addChangeListener_null() { @RunTestInLooperThread public void removeChangeListener() { final AtomicInteger listenersTriggered = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); RealmChangeListener> listener = new RealmChangeListener>() { @@ -1065,7 +1065,7 @@ public void onChange(RealmResults object) { } }; - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addChangeListener(listener); collection.removeChangeListener(listener); @@ -1100,7 +1100,7 @@ public void removeChangeListener_null() { @RunTestInLooperThread public void removeAllChangeListeners() { final AtomicInteger listenersTriggered = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); RealmChangeListener> listenerA = new RealmChangeListener>() { @@ -1116,7 +1116,7 @@ public void onChange(RealmResults object) { } }; - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addChangeListener(listenerA); collection.addChangeListener(listenerB); collection.removeAllChangeListeners(); @@ -1141,7 +1141,7 @@ public void run() { @Test @RunTestInLooperThread public void removeAllChangeListeners_thenAdd() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); RealmResults collection = realm.where(AllTypes.class).findAll(); collection.addChangeListener(new RealmChangeListener>() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index f536b32074..b4bce17087 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -61,6 +61,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -1984,10 +1985,7 @@ public void deleteRealm() throws InterruptedException { public void run() { Realm realm = Realm.getInstance(configuration); bgThreadReadyLatch.countDown(); - try { - readyToCloseLatch.await(); - } catch (InterruptedException ignored) { - } + TestHelper.awaitOrFail(readyToCloseLatch); realm.close(); closedLatch.countDown(); } @@ -2005,7 +2003,7 @@ public void run() { readyToCloseLatch.countDown(); realm.close(); - closedLatch.await(); + TestHelper.awaitOrFail(closedLatch); // Now we get log files back! assertTrue(tempDirRenamed.renameTo(tempDir)); @@ -2592,7 +2590,7 @@ public void run() { thatThread.start(); // Timeout should never happen. - latch.await(); + TestHelper.awaitOrFail(latch); if (threadAssertionError[0] != null) { throw threadAssertionError[0]; } @@ -2630,7 +2628,7 @@ public void run() { thatThread.start(); // Timeout should never happen. - latch.await(); + TestHelper.awaitOrFail(latch); if (threadAssertionError[0] != null) { throw threadAssertionError[0]; } @@ -2706,7 +2704,7 @@ public void closingRealmWhileOtherThreadIsOpeningRealm() throws Exception { @Override public void run() { try { - startLatch.await(); + startLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); return; @@ -2735,7 +2733,7 @@ public void run() { realm = null; } - endLatch.await(); + TestHelper.awaitOrFail(endLatch); if (!exception.isEmpty()) { throw exception.get(0); @@ -2762,7 +2760,7 @@ public void run() { Realm realm = Realm.getInstance(realmConfig); realmOpenedInBgLatch.countDown(); try { - realmClosedInFgLatch.await(); + realmClosedInFgLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); realm.close(); @@ -2773,7 +2771,7 @@ public void run() { realm.beginTransaction(); transBeganInBgLatch.countDown(); try { - fgFinishedLatch.await(); + fgFinishedLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); } @@ -2785,16 +2783,16 @@ public void run() { }); thread.start(); - realmOpenedInBgLatch.await(); + TestHelper.awaitOrFail(realmOpenedInBgLatch); // Step 3: Closes all realm instances in foreground thread. realm.close(); realmClosedInFgLatch.countDown(); - transBeganInBgLatch.await(); + TestHelper.awaitOrFail(transBeganInBgLatch); // Step 5: Gets a new Realm instance in foreground. realm = Realm.getInstance(realmConfig); fgFinishedLatch.countDown(); - bgFinishedLatch.await(); + TestHelper.awaitOrFail(bgFinishedLatch); if (!exception.isEmpty()) { throw exception.get(0); @@ -3084,7 +3082,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void closeRealmInChangeListenerWhenThereIsListenerOnEmptyObject() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmChangeListener dummyListener = new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -3125,7 +3123,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void closeRealmInChangeListenerWhenThereIsListenerOnObject() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmChangeListener dummyListener = new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -3170,7 +3168,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void closeRealmInChangeListenerWhenThereIsListenerOnResults() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); final RealmChangeListener> dummyListener = new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -3209,7 +3207,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void addChangeListener_throwOnAddingNullListenerFromLooperThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); try { realm.addChangeListener(null); @@ -3242,7 +3240,7 @@ public void run() throws Exception { @Test @RunTestInLooperThread public void removeChangeListener_throwOnRemovingNullListenerFromLooperThread() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); try { realm.removeChangeListener(null); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index e306343516..eafe61d82d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -105,7 +105,7 @@ public void call(AllTypes rxObject) { @RunTestInLooperThread public void realmObject_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); final AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -167,7 +167,7 @@ public void call(AllTypes rxObject) { @RunTestInLooperThread public void findFirstAsync_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -189,7 +189,7 @@ public void call(AllTypes rxObject) { @RunTestInLooperThread public void findFirstAsync_emittedOnDelete() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); final AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); @@ -282,7 +282,7 @@ public void call(RealmResults rxResults) { @RunTestInLooperThread public void realmResults_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); RealmResults results = realm.where(AllTypes.class).findAll(); realm.commitTransaction(); @@ -305,7 +305,7 @@ public void call(RealmResults allTypes) { @RunTestInLooperThread public void realmList_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); realm.beginTransaction(); final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); realm.commitTransaction(); @@ -329,7 +329,7 @@ public void call(RealmList dogs) { @RunTestInLooperThread public void dynamicRealmResults_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); dynamicRealm.beginTransaction(); RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); dynamicRealm.commitTransaction(); @@ -370,7 +370,7 @@ public void call(RealmResults rxResults) { @RunTestInLooperThread public void findAllAsync_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); subscription = realm.where(AllTypes.class).findAllAsync().asObservable().subscribe(new Action1>() { @Override public void call(RealmResults rxResults) { @@ -404,7 +404,7 @@ public void call(Realm rxRealm) { @RunTestInLooperThread public void realm_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.realm; + Realm realm = looperThread.getRealm(); subscription = realm.asObservable().subscribe(new Action1() { @Override public void call(Realm rxRealm) { @@ -445,7 +445,7 @@ public void call(Throwable throwable) { @Test @RunTestInLooperThread public void dynamicRealm_emittedOnUpdate() { - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); final AtomicInteger subscriberCalled = new AtomicInteger(0); subscription = dynamicRealm.asObservable().subscribe(new Action1() { @Override @@ -700,7 +700,7 @@ public void call(DynamicRealmObject obj) { public void realmResults_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { @@ -743,7 +743,7 @@ public void call(Throwable throwable) { public void dynamicRealmResults_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final DynamicRealm realm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { @@ -787,7 +787,7 @@ public void call(Throwable throwable) { public void realmObject_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { @@ -830,7 +830,7 @@ public void call(Throwable throwable) { public void dynamicRealmObject_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final DynamicRealm realm = DynamicRealm.getInstance(looperThread.realmConfiguration); + final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 6e95a08106..06f6bd6b49 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -343,7 +343,7 @@ public void realmSortMultiFailures() { public void resorting() throws InterruptedException { final AtomicInteger changeListenerCalled = new AtomicInteger(4); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.setAutoRefresh(true); final Runnable endTest = new Runnable() { @@ -364,7 +364,7 @@ public void run() { // rr0: [0, 1, 2, 3] final RealmResults rr0 = realm.where(AllTypes.class).findAll(); - looperThread.keepStrongReference.add(rr0); + looperThread.keepStrongReference(rr0); rr0.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -376,7 +376,7 @@ public void onChange(RealmResults element) { // rr1: [1, 2, 0, 3] final RealmResults rr1 = realm.where(AllTypes.class).findAll().sort(FIELD_LONG, Sort.ASCENDING); - looperThread.keepStrongReference.add(rr1); + looperThread.keepStrongReference(rr1); rr1.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -392,7 +392,7 @@ public void onChange(RealmResults element) { // rr2: [0, 3, 1, 2] final RealmResults rr2 = realm.where(AllTypes.class).findAll().sort(FIELD_LONG, Sort.DESCENDING); - looperThread.keepStrongReference.add(rr2); + looperThread.keepStrongReference(rr2); rr2.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -481,7 +481,7 @@ public void run() { RealmResults objectsAscending = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_DATE, Sort.ASCENDING); assertEquals(TEST_SIZE, objectsAscending.size()); - looperThread.keepStrongReference.add(objectsAscending); + looperThread.keepStrongReference(objectsAscending); objectsAscending.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { @@ -497,7 +497,7 @@ public void onChange(RealmResults element) { RealmResults objectsDescending = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_DATE, Sort.DESCENDING); assertEquals(TEST_SIZE, objectsDescending.size()); - looperThread.keepStrongReference.add(objectsDescending); + looperThread.keepStrongReference(objectsDescending); objectsDescending.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 731c21d3a1..a688249e8c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -31,7 +31,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -67,6 +66,10 @@ import static junit.framework.Assert.fail; public class TestHelper { + public static final int VERY_SHORT_WAIT_SECS = 1; + public static final int SHORT_WAIT_SECS = 10; + public static final int STANDARD_WAIT_SECS = 100; + public static final int LONG_WAIT_SECS = 1000; private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final Random RANDOM = new Random(); @@ -779,14 +782,14 @@ public static void populateForDistinctFieldsOrder(Realm realm, long numberOfBloc } public static void awaitOrFail(CountDownLatch latch) { - awaitOrFail(latch, 60); + awaitOrFail(latch, STANDARD_WAIT_SECS); } public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { try { if (android.os.Debug.isDebuggerConnected()) { - // If we are debugging the tests, just waits without a timeout. In case we are stopping at a break point - // and timeout happens. + // If we are debugging the tests, just waits without a timeout. + // Don't want a timeout while we are stopped at a break point. latch.await(); } else if (!latch.await(numberOfSeconds, TimeUnit.SECONDS)) { fail("Test took longer than " + numberOfSeconds + " seconds"); @@ -796,36 +799,40 @@ public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { } } + public interface LooperTest { + CountDownLatch getRealmClosedSignal(); + Looper getLooper(); + Throwable getAssertionError(); + } + // Cleans resource, shutdowns the executor service and throws any background exception. @SuppressWarnings("Finally") - public static void exitOrThrow(final ExecutorService executorService, - final CountDownLatch signalTestFinished, - final CountDownLatch signalClosedRealm, - final Looper[] looper, - final Throwable[] throwable) throws Throwable { + public static void exitOrThrow(ExecutorService executorService, CountDownLatch testFinishedSignal, LooperTest test) throws Throwable { // Waits for the signal indicating the test's use case is done. try { // Even if this fails we want to try as hard as possible to cleanup. If we fail to close all resources // properly, the `after()` method will most likely throw as well because it tries do delete any Realms // used. Any exception in the `after()` code will mask the original error. - TestHelper.awaitOrFail(signalTestFinished); + TestHelper.awaitOrFail(testFinishedSignal); } finally { - if (looper[0] != null) { + Looper looper = test.getLooper(); + if (looper != null) { // Failing to quit the looper will not execute the finally block responsible // of closing the Realm. - looper[0].quit(); + looper.quit(); } // Waits for the finally block to execute and closes the Realm. - TestHelper.awaitOrFail(signalClosedRealm); + TestHelper.awaitOrFail(test.getRealmClosedSignal()); // Closes the executor. // This needs to be called after waiting since it might interrupt waitRealmThreadExecutorFinish(). executorService.shutdownNow(); - if (throwable[0] != null) { + Throwable fault = test.getAssertionError(); + if (fault != null) { // Throws any assertion errors happened in the background thread. - throw throwable[0]; + throw fault; } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index 73bf9731c5..aa4c01fc54 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -82,7 +82,7 @@ public void setUp() { @Test @RunTestInLooperThread public void callback_should_trigger_for_createObject() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -102,7 +102,7 @@ public void run() { final Dog dog = realm.createObject(Dog.class); realm.commitTransaction(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -119,8 +119,8 @@ public void onChange(Dog object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_createObject_dynamic_realm() { - final DynamicRealm realm = DynamicRealm.getInstance(looperThread.realmConfiguration); - looperThread.keepStrongReference.add(realm); + final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); + looperThread.keepStrongReference(realm); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealm object) { @@ -141,7 +141,7 @@ public void run() { final DynamicRealmObject dog = realm.createObject("Dog"); realm.commitTransaction(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(DynamicRealmObject object) { @@ -159,7 +159,7 @@ public void onChange(DynamicRealmObject object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_copyToRealm() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -181,7 +181,7 @@ public void run() { final Dog dog = realm.copyToRealm(akamaru); realm.commitTransaction(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -199,7 +199,7 @@ public void onChange(Dog object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_copyToRealmOrUpdate() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -223,7 +223,7 @@ public void run() { final PrimaryKeyAsLong primaryKeyAsLong = realm.copyToRealmOrUpdate(obj); realm.commitTransaction(); - looperThread.keepStrongReference.add(primaryKeyAsLong); + looperThread.keepStrongReference(primaryKeyAsLong); primaryKeyAsLong.addChangeListener(new RealmChangeListener() { @Override public void onChange(PrimaryKeyAsLong object) { @@ -250,7 +250,7 @@ public void onChange(PrimaryKeyAsLong object) { public void callback_should_trigger_for_createObjectFromJson() { assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); try { InputStream in = TestHelper.loadJsonFromAssets(InstrumentationRegistry.getTargetContext(), "all_simple_types.json"); realm.beginTransaction(); @@ -258,7 +258,7 @@ public void callback_should_trigger_for_createObjectFromJson() { realm.commitTransaction(); in.close(); - looperThread.keepStrongReference.add(objectFromJson); + looperThread.keepStrongReference(objectFromJson); objectFromJson.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -285,7 +285,7 @@ public void onChange(AllTypes object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_createObjectFromJson_from_JSONObject() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); try { JSONObject json = new JSONObject(); @@ -300,7 +300,7 @@ public void callback_should_trigger_for_createObjectFromJson_from_JSONObject() { final AllTypes objectFromJson = realm.createObjectFromJson(AllTypes.class, json); realm.commitTransaction(); - looperThread.keepStrongReference.add(objectFromJson); + looperThread.keepStrongReference(objectFromJson); objectFromJson.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypes object) { @@ -329,7 +329,7 @@ public void onChange(AllTypes object) { public void callback_should_trigger_for_createOrUpdateObjectFromJson() { assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -366,7 +366,7 @@ public void run() { realm.commitTransaction(); in.close(); - looperThread.keepStrongReference.add(objectFromJson); + looperThread.keepStrongReference(objectFromJson); objectFromJson.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypesPrimaryKey object) { @@ -395,7 +395,7 @@ public void onChange(AllTypesPrimaryKey object) { @Test @RunTestInLooperThread public void callback_should_trigger_for_createOrUpdateObjectFromJson_from_JSONObject() throws JSONException { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -426,7 +426,7 @@ public void run() { final AllTypesPrimaryKey newObj = realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, json); realm.commitTransaction(); - looperThread.keepStrongReference.add(newObj); + looperThread.keepStrongReference(newObj); newObj.addChangeListener(new RealmChangeListener() { @Override public void onChange(AllTypesPrimaryKey object) { @@ -451,7 +451,7 @@ public void onChange(AllTypesPrimaryKey object) { @Test @RunTestInLooperThread public void callback_with_relevant_commit_realmobject_sync() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // Step 1: Creates object realm.beginTransaction(); @@ -460,7 +460,7 @@ public void callback_with_relevant_commit_realmobject_sync() { realm.commitTransaction(); final Dog dog = realm.where(Dog.class).findFirst(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -491,7 +491,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void callback_with_relevant_commit_realmobject_async() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // Step 1: Creates object. realm.beginTransaction(); @@ -501,7 +501,7 @@ public void callback_with_relevant_commit_realmobject_async() { final Dog dog = realm.where(Dog.class).findFirstAsync(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -543,7 +543,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void callback_with_relevant_commit_realmresults_sync() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // Step 1: Creates object. realm.beginTransaction(); @@ -552,7 +552,7 @@ public void callback_with_relevant_commit_realmresults_sync() { realm.commitTransaction(); final RealmResults dogs = realm.where(Dog.class).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -585,7 +585,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void callback_with_relevant_commit_realmresults_async() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); // Step 1: Creates object. realm.beginTransaction(); @@ -594,7 +594,7 @@ public void callback_with_relevant_commit_realmresults_async() { realm.commitTransaction(); final RealmResults dogs = realm.where(Dog.class).findAllAsync(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -641,7 +641,7 @@ public void execute(Realm realm) { @RunTestInLooperThread public void multiple_callbacks_should_be_invoked_realmobject_sync() { final int NUMBER_OF_LISTENERS = 7; - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -660,7 +660,7 @@ public void run() { realm.commitTransaction(); Dog dog = realm.where(Dog.class).findFirst(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); for (int i = 0; i < NUMBER_OF_LISTENERS; i++) { dog.addChangeListener(new RealmChangeListener() { @Override @@ -680,7 +680,7 @@ public void onChange(Dog object) { @RunTestInLooperThread public void multiple_callbacks_should_be_invoked_realmobject_async() { final int NUMBER_OF_LISTENERS = 7; - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog akamaru = realm.createObject(Dog.class); @@ -688,7 +688,7 @@ public void multiple_callbacks_should_be_invoked_realmobject_async() { Dog dog = realm.where(Dog.class).findFirstAsync(); assertTrue(dog.load()); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); for (int i = 0; i < NUMBER_OF_LISTENERS; i++) { dog.addChangeListener(new RealmChangeListener() { @Override @@ -719,14 +719,14 @@ public void run() { @RunTestInLooperThread public void multiple_callbacks_should_be_invoked_realmresults_sync() { final int NUMBER_OF_LISTENERS = 7; - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog akamaru = realm.createObject(Dog.class); realm.commitTransaction(); RealmResults dogs = realm.where(Dog.class).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); for (int i = 0; i < NUMBER_OF_LISTENERS; i++) { dogs.addChangeListener(new RealmChangeListener>() { @Override @@ -750,7 +750,7 @@ public void onChange(RealmResults results) { @RunTestInLooperThread public void multiple_callbacks_should_be_invoked_realmresults_async() { final int NUMBER_OF_LISTENERS = 7; - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); Dog akamaru = realm.createObject(Dog.class); @@ -772,7 +772,7 @@ public void run() { RealmResults dogs = realm.where(Dog.class).findAllAsync(); assertTrue(dogs.load()); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); for (int i = 0; i < NUMBER_OF_LISTENERS; i++) { dogs.addChangeListener(new RealmChangeListener>() { @Override @@ -799,14 +799,14 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void non_looper_thread_commit_realmobject_sync() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); realm.createObject(Dog.class); realm.commitTransaction(); Dog dog = realm.where(Dog.class).findFirst(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -830,14 +830,14 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void non_looper_thread_commit_realmobject_async() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); realm.createObject(Dog.class).setAge(1); realm.commitTransaction(); Dog dog = realm.where(Dog.class).findFirstAsync(); - looperThread.keepStrongReference.add(dog); + looperThread.keepStrongReference(dog); dog.addChangeListener(new RealmChangeListener() { @Override public void onChange(Dog object) { @@ -869,7 +869,7 @@ public void execute(Realm realm) { @Test @RunTestInLooperThread public void non_looper_thread_commit_realmresults_sync() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -890,7 +890,7 @@ public void run() { realm.commitTransaction(); final RealmResults dogs = realm.where(Dog.class).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -924,7 +924,7 @@ public void run() { @Test @RunTestInLooperThread public void non_looper_thread_commit_realmresults_async() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.addChangeListener(new RealmChangeListener() { @Override public void onChange(Realm object) { @@ -956,7 +956,7 @@ public void run() { }; final RealmResults dogs = realm.where(Dog.class).findAllAsync(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { @@ -1080,7 +1080,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void changeListener_onResultsBuiltOnDeletedLinkView() { - final Realm realm = looperThread.realm; + final Realm realm = looperThread.getRealm(); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); for (int i = 0; i < 10; i++) { @@ -1092,7 +1092,7 @@ public void changeListener_onResultsBuiltOnDeletedLinkView() { final RealmResults dogs = allTypes.getColumnRealmList().where().equalTo(Dog.FIELD_NAME, "name_0").findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 2592c5f664..83ca6db974 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -253,7 +253,7 @@ public void addListener_shouldBeCalledToReturnTheQueryResults() { Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { @@ -342,7 +342,7 @@ public void addListener_queryNotReturned() { Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { @@ -363,7 +363,7 @@ public void addListener_queryReturned() { Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); assertEquals(collection.size(), 4); // Trigger the query to run. collection.addListener(collection, new RealmChangeListener() { @Override @@ -388,7 +388,7 @@ public void addListener_triggeredByLocalCommit() { final AtomicInteger listenerCounter = new AtomicInteger(0); final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { @@ -468,7 +468,7 @@ public void collectionIterator_invalid_looperThread_byRemoteTransaction() { Table table = sharedRealm.getTable("test_table"); final Collection collection = new Collection(sharedRealm, table.where()); final TestIterator iterator = new TestIterator(collection); - looperThread.keepStrongReference.add(collection); + looperThread.keepStrongReference(collection); assertFalse(iterator.isDetached(sharedRealm)); collection.addListener(collection, new RealmChangeListener() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 0276a8a432..0516f0e115 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -85,7 +85,7 @@ public void run() { @RunTestInLooperThread public void addChangeListener_byLocalChanges() { final AtomicBoolean commitReturns = new AtomicBoolean(false); - SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); + SharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { @@ -121,10 +121,10 @@ public void addChangeListener_byRemoteChanges() { final AtomicInteger commitCounter = new AtomicInteger(0); final AtomicInteger listenerCounter = new AtomicInteger(0); - looperThread.realm.close(); + looperThread.getRealm().close(); - SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); - looperThread.keepStrongReference.add(sharedRealm); + SharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); + looperThread.keepStrongReference(sharedRealm); sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override public void onChange(SharedRealm sharedRealm) { @@ -135,22 +135,22 @@ public void onChange(SharedRealm sharedRealm) { sharedRealm.close(); looperThread.testComplete(); } else { - makeRemoteChanges(looperThread.realmConfiguration); + makeRemoteChanges(looperThread.getConfiguration()); commitCounter.getAndIncrement(); } } }); - makeRemoteChanges(looperThread.realmConfiguration); + makeRemoteChanges(looperThread.getConfiguration()); commitCounter.getAndIncrement(); } @Test @RunTestInLooperThread public void removeChangeListeners() { - SharedRealm sharedRealm = getSharedRealm(looperThread.realmConfiguration); + SharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); Integer dummyObserver = 1; - looperThread.keepStrongReference.add(dummyObserver); - looperThread.keepStrongReference.add(sharedRealm); + looperThread.keepStrongReference(dummyObserver); + looperThread.keepStrongReference(sharedRealm); sharedRealm.realmNotifier.addChangeListener(dummyObserver, new RealmChangeListener() { @Override public void onChange(Integer dummy) { @@ -168,6 +168,6 @@ public void onChange(SharedRealm sharedRealm) { // This should only remove the listeners related with dummyObserver sharedRealm.realmNotifier.removeChangeListeners(dummyObserver); - makeRemoteChanges(looperThread.realmConfiguration); + makeRemoteChanges(looperThread.getConfiguration()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index 186040bdec..49d1ececa6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -20,11 +20,12 @@ import android.os.Looper; import org.junit.runner.Description; +import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; -import java.io.PrintWriter; -import java.io.StringWriter; +import java.io.PrintStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -38,188 +39,222 @@ import io.realm.RealmConfiguration; import io.realm.TestHelper; -import static org.junit.Assert.fail; /** * Rule that runs the test inside a worker looper thread. This rule is responsible - * of creating a temp directory containing a Realm instance then delete it, once the test finishes. - * + * of creating a temp directory containing a Realm instance then deleting it, once the test finishes. + *

                      * All Realms used in a method method annotated with {@code @RunTestInLooperThread } should use - * {@link RunInLooperThread#createConfiguration()} and friends to create their configurations. Failing to do so can - * result in the test failing because the Realm could not be deleted (Reason is that {@link TestRealmConfigurationFactory} - * and this class does not agree in which order to delete all open Realms. + * {@link RunInLooperThread#createConfiguration()} and friends to create their configurations. + * Failing to do so can result in the test failing because the Realm could not be deleted + * (this class and {@link TestRealmConfigurationFactory} do not agree in which order to delete + * the open Realms). */ public class RunInLooperThread extends TestRealmConfigurationFactory { + private static final long WAIT_TIMEOUT_MS = 60 * 1000; + + // lock protecting objects shared with the test thread + private final Object lock = new Object(); + + // Thread safe + private final CountDownLatch signalTestCompleted = new CountDownLatch(1); + + // Access guarded by 'lock' + private RealmConfiguration realmConfiguration; // Default Realm created by this Rule. It is guaranteed to be closed when the test finishes. - public Realm realm; - // Custom Realm used by the test. Saving the reference here will guarantee the instance is closed when exiting the test. - public List testRealms = new ArrayList(); - public RealmConfiguration realmConfiguration; - private CountDownLatch signalTestCompleted; + // Access guarded by 'lock' + private Realm realm; + + // Access guarded by 'lock' private Handler backgroundHandler; // the variables created inside the test are local and eligible for GC. // but sometimes we need the variables to survive across different Looper // events (Callbacks happening in the future), so we add a strong reference // to them for the duration of the test. - public LinkedList keepStrongReference; + // Access guarded by 'lock' + private LinkedList keepStrongReference; - @Override - protected void before() throws Throwable { - super.before(); - realmConfiguration = createConfiguration(UUID.randomUUID().toString()); - signalTestCompleted = new CountDownLatch(1); - keepStrongReference = new LinkedList(); - } + // Custom Realm used by the test. Saving the reference here will guarantee + // that the instance is closed when exiting the test. + // Access guarded by 'lock' + private List testRealms; - @Override - protected void after() { - super.after(); - realmConfiguration = null; - realm = null; - testRealms.clear(); - keepStrongReference = null; + /** + * Get the configuration for the test realm. + *

                      + * Set on main thread, accessed from test thread. + * Valid after {@code before}. + * + * @return the test realm configuration. + */ + public RealmConfiguration getConfiguration() { + synchronized (lock) { + return realmConfiguration; + } } - @Override - public Statement apply(final Statement base, Description description) { - final RunTestInLooperThread annotation = description.getAnnotation(RunTestInLooperThread.class); - if (annotation == null) { - return base; - } - return new Statement() { - private Throwable testException; - - @Override - @SuppressWarnings({"ClassNewInstance", "Finally"}) - public void evaluate() throws Throwable { - before(); - final String threadName = annotation.threadName(); - Class runnableBefore = annotation.before(); - if (!runnableBefore.isInterface()) { - runnableBefore.newInstance().run(realmConfiguration); - } + /** + * Get the test realm. + *

                      + * Set on test thread, accessed from main thread. + * Valid only after the test thread has started. + * + * @return the test realm. + */ + public Realm getRealm() { + synchronized (lock) { + while (backgroundHandler == null) { try { - final CountDownLatch signalClosedRealm = new CountDownLatch(1); - final Throwable[] threadAssertionError = new Throwable[1]; - final Looper[] backgroundLooper = new Looper[1]; - final ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable runnable) { - return new Thread(runnable, threadName); - } - }); - //noinspection unused - final Future submit = executorService.submit(new Runnable() { - @Override - public void run() { - Looper.prepare(); - backgroundLooper[0] = Looper.myLooper(); - backgroundHandler = new Handler(backgroundLooper[0]); - try { - realm = Realm.getInstance(realmConfiguration); - base.evaluate(); - Looper.loop(); - } catch (Throwable e) { - threadAssertionError[0] = e; - unitTestFailed = true; - } finally { - try { - looperTearDown(); - } catch (Throwable t) { - if (threadAssertionError[0] == null) { - threadAssertionError[0] = t; - } - unitTestFailed = true; - } - signalTestCompleted.countDown(); - if (realm != null) { - realm.close(); - } - if (!testRealms.isEmpty()) { - for (Realm testRealm : testRealms) { - testRealm.close(); - } - } - signalClosedRealm.countDown(); - } - } - }); - TestHelper.exitOrThrow(executorService, signalTestCompleted, signalClosedRealm, backgroundLooper, threadAssertionError); - } catch (Throwable error) { - // These exceptions should only come from TestHelper.awaitOrFail() - testException = error; - } finally { - // Tries as hard as possible to close down gracefully, while still keeping all exceptions intact. - try { - after(); - } catch (Throwable e) { - if (testException != null) { - // Both TestHelper.awaitOrFail() and after() threw an exception. Make sure we are aware of - // that fact by printing both exceptions. - StringWriter testStackTrace = new StringWriter(); - testException.printStackTrace(new PrintWriter(testStackTrace)); - - StringWriter afterStackTrace = new StringWriter(); - e.printStackTrace(new PrintWriter(afterStackTrace)); - - StringBuilder errorMessage = new StringBuilder() - .append("after() threw an error that shadows a test case error") - .append('\n') - .append("== Test case exception ==\n") - .append(testStackTrace.toString()) - .append('\n') - .append("== after() exception ==\n") - .append(afterStackTrace.toString()); - fail(errorMessage.toString()); - } else { - // Only after() threw an exception - throw e; - } - } - - // Only TestHelper.awaitOrFail() threw an exception - if (testException != null) { - //noinspection ThrowFromFinallyBlock - throw testException; - } + lock.wait(WAIT_TIMEOUT_MS); + } catch (InterruptedException ignore) { + break; } } - }; + return realm; + } } /** - * Signal that the test has completed. + * Hold a reference to an object, to prevent it from being GCed, + * until after the test completes. + *

                      + * Accessed only from the main thread, here, but synchronized in case it is called from within a test. + * Valid after {@code before}. */ - public void testComplete() { - signalTestCompleted.countDown(); + public void keepStrongReference(Object obj) { + synchronized (lock) { + keepStrongReference.add(obj); + } } /** - * Signal that the test has completed. - * - * @param latches additional latches to wait before set the test completed flag. + * Add a Realm to be closed when test is complete. + *

                      + * Accessed from both test and main threads. + * Valid after {@code before}. */ - public void testComplete(CountDownLatch... latches) { - for (CountDownLatch latch : latches) { - TestHelper.awaitOrFail(latch); + public void addTestRealm(Realm realm) { + synchronized (lock) { + testRealms.add(realm); } - signalTestCompleted.countDown(); } /** - * Posts a runnable to this worker threads looper. + * Explicitly close all held realms. + *

                      + * 'testRealms' is accessed from both test and main threads. + * 'testRealms' is valid after {@code before}. + */ + public void closeTestRealms() { + List realms = new ArrayList<>(); + synchronized (lock) { + List tmp = testRealms; + testRealms = realms; + realms = tmp; + } + + for (Realm testRealm : realms) { + testRealm.close(); + } + } + + /** + * Posts a runnable to the currently running looper. */ public void postRunnable(Runnable runnable) { - backgroundHandler.post(runnable); + getBackgroundHandler().post(runnable); } /** * Posts a runnable to this worker threads looper with a delay in milli second. */ public void postRunnableDelayed(Runnable runnable, long delayMillis) { - backgroundHandler.postDelayed(runnable, delayMillis); + getBackgroundHandler().postDelayed(runnable, delayMillis); + } + + /** + * Signal that the test has completed. + *

                      + * Used on both the main and test threads. + * Valid after {@code before}. + */ + public void testComplete() { + signalTestCompleted.countDown(); + } + + /** + * Signal that the test has completed, after waiting for any additional latches. + * + * @param latches additional latches to wait on, before setting the test completed flag. + */ + public void testComplete(CountDownLatch... latches) { + for (CountDownLatch latch : latches) { + TestHelper.awaitOrFail(latch); + } + testComplete(); + } + + // Accessed from both test and main threads + // Valid after the test thread has started. + private Handler getBackgroundHandler() { + synchronized (lock) { + while (backgroundHandler == null) { + try { + lock.wait(WAIT_TIMEOUT_MS); + } catch (InterruptedException ignore) { + break; + } + } + return this.backgroundHandler; + } + } + + // Accessed from both test and main threads + // Storing the handler is the gate that indicates that the test thread has started. + void setBackgroundHandler(Handler backgroundHandler) { + synchronized (lock) { + this.backgroundHandler = backgroundHandler; + lock.notifyAll(); + } + } + + @Override + protected void before() throws Throwable { + super.before(); + + RealmConfiguration config = createConfiguration(UUID.randomUUID().toString()); + LinkedList refs = new LinkedList<>(); + List realms = new LinkedList<>(); + + synchronized (lock) { + realmConfiguration = config; + realm = null; + backgroundHandler = null; + keepStrongReference = refs; + testRealms = realms; + } + } + + @Override + protected void after() { + super.after(); + + // probably belt *and* suspenders... + synchronized (lock) { + backgroundHandler = null; + keepStrongReference = null; + } + } + + @Override + public Statement apply(Statement base, Description description) { + final RunTestInLooperThread annotation = description.getAnnotation(RunTestInLooperThread.class); + if (annotation == null) { + return base; + } + return new RunInLooperThreadStatement(annotation, base); } /** @@ -229,6 +264,28 @@ public void postRunnableDelayed(Runnable runnable, long delayMillis) { public void looperTearDown() { } + private void initRealm() { + synchronized (lock) { + realm = Realm.getInstance(realmConfiguration); + } + } + + private void closeRealms() { + closeTestRealms(); + + Realm oldRealm; + synchronized (lock) { + oldRealm = realm; + + realm = null; + realmConfiguration = null; + } + + if (oldRealm != null) { + oldRealm.close(); + } + } + /** * If an implementation of this is supplied with the annotation, the {@link RunnableBefore#run(RealmConfiguration)} * will be executed before the looper thread starts. It is normally for populating the Realm before the test. @@ -236,4 +293,144 @@ public void looperTearDown() { public interface RunnableBefore { void run(RealmConfiguration realmConfig); } + + private class RunInLooperThreadStatement extends Statement { + private final RunTestInLooperThread annotation; + private final Statement base; + + RunInLooperThreadStatement(RunTestInLooperThread annotation, Statement base) { + this.annotation = annotation; + this.base = base; + } + + @Override + @SuppressWarnings("ClassNewInstance") + public void evaluate() throws Throwable { + before(); + + Class runnableBefore = annotation.before(); + if (!runnableBefore.isInterface()) { + // this is dangerous: newInstance can throw checked exceptions. + // this is dangerous: config is mutable. + runnableBefore.newInstance().run(getConfiguration()); + } + + runTest(annotation.threadName()); + } + + private void runTest(final String threadName) throws Throwable { + Throwable failure = null; + + try { + ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { return new Thread(runnable, threadName); } + }); + + TestThread test = new TestThread(base); + + @SuppressWarnings({"UnusedAssignment", "unused"}) + Future ignored = executorService.submit(test); + + TestHelper.exitOrThrow(executorService, signalTestCompleted, test); + } catch (Throwable testfailure) { + // These exceptions should only come from TestHelper.awaitOrFail() + failure = testfailure; + } finally { + // Tries as hard as possible to close down gracefully, while still keeping all exceptions intact. + failure = cleanUp(failure); + } + if (failure != null) { + throw failure; + } + } + + private Throwable cleanUp(Throwable testfailure) { + try { + after(); + return testfailure; + } catch (Throwable afterFailure) { + if (testfailure == null) { + // Only after() threw an exception + return afterFailure; + } + + // Both TestHelper.awaitOrFail() and after() threw exceptions + return new MultipleFailureException(Arrays.asList(testfailure, afterFailure)) { + @Override + public void printStackTrace(PrintStream out) { + int i = 0; + for (Throwable t : getFailures()) { + out.println("Error " + i + ": " + t.getMessage()); + t.printStackTrace(out); + out.println(); + i++; + } + } + }; + } + } + } + + private class TestThread implements Runnable, TestHelper.LooperTest { + private final CountDownLatch signalClosedRealm = new CountDownLatch(1); + private final Statement base; + private Looper looper; + private Throwable threadAssertionError; + + TestThread(Statement base) { + this.base = base; + } + + @Override + public CountDownLatch getRealmClosedSignal() { + return signalClosedRealm; + } + + @Override + public synchronized Looper getLooper() { + return looper; + } + + private synchronized void setLooper(Looper looper) { + this.looper = looper; + setBackgroundHandler(new Handler(looper)); + } + + @Override + public synchronized Throwable getAssertionError() { + return threadAssertionError; + } + + // Only record the first error + private synchronized void setAssertionError(Throwable threadAssertionError) { + if (this.threadAssertionError == null) { + this.threadAssertionError = threadAssertionError; + } + } + + @Override + public void run() { + Looper.prepare(); + try { + initRealm(); + setLooper(Looper.myLooper()); + base.evaluate(); + Looper.loop(); + } catch (Throwable t) { + setAssertionError(t); + setUnitTestFailed(); + } finally { + try { + looperTearDown(); + } catch (Throwable t) { + setAssertionError(t); + setUnitTestFailed(); + } + testComplete(); + closeRealms(); + signalClosedRealm.countDown(); + } + } + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index 979717e96a..04bc9a4f6d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -51,7 +51,8 @@ public class TestRealmConfigurationFactory extends TemporaryFolder { private final Map map = new ConcurrentHashMap(); private final Set configurations = Collections.newSetFromMap(map); - protected boolean unitTestFailed = false; + + private boolean unitTestFailed = false; @Override public Statement apply(final Statement base, Description description) { @@ -62,7 +63,7 @@ public void evaluate() throws Throwable { try { base.evaluate(); } catch (Throwable throwable) { - unitTestFailed = true; + setUnitTestFailed(); throw throwable; } finally { after(); @@ -89,7 +90,7 @@ protected void after() { } } catch (IllegalStateException e) { // Only throws the exception caused by deleting the opened Realm if the test case itself doesn't throw. - if (!unitTestFailed) { + if (!isUnitTestFailed()) { throw e; } } finally { @@ -98,6 +99,14 @@ protected void after() { } } + public synchronized void setUnitTestFailed() { + this.unitTestFailed = true; + } + + private synchronized boolean isUnitTestFailed() { + return this.unitTestFailed; + } + // This builder creates a configuration that is *NOT* managed. // You have to delete it yourself. public RealmConfiguration.Builder createConfigurationBuilder() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/util/RealmBackgroundTask.java b/realm/realm-library/src/androidTest/java/io/realm/util/RealmBackgroundTask.java index cc570efb8d..fc6a06f19b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/util/RealmBackgroundTask.java +++ b/realm/realm-library/src/androidTest/java/io/realm/util/RealmBackgroundTask.java @@ -21,6 +21,8 @@ import io.realm.Realm; import io.realm.RealmConfiguration; +import io.realm.TestHelper; + /** * Utility class for running a task on a non-looper background thread. @@ -62,7 +64,7 @@ public void run() { }, "RealmBackgroundTask").start(); try { - if (!jobDone.await(10, TimeUnit.SECONDS)) { + if (!jobDone.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS)) { exceptionHolder.setError("Job timed out!"); } } catch (InterruptedException e) { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index a360d94fed..d5c949a1bf 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -88,7 +88,7 @@ public void onError(SyncSession session, ObjectServerError error) { .build(); Realm realm = Realm.getInstance(config); - looperThread.testRealms.add(realm); + looperThread.addTestRealm(realm); // Trigger error SyncManager.simulateClientReset(SyncManager.getSession(config)); @@ -117,7 +117,7 @@ public void onError(SyncSession session, ObjectServerError error) { } // Execute Client Reset - looperThread.testRealms.get(0).close(); + looperThread.closeTestRealms(); handler.executeClientReset(); // Validate that files have been moved @@ -129,10 +129,9 @@ public void onError(SyncSession session, ObjectServerError error) { .build(); Realm realm = Realm.getInstance(config); - looperThread.testRealms.add(realm); + looperThread.addTestRealm(realm); // Trigger error SyncManager.simulateClientReset(SyncManager.getSession(config)); } - } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index ae1cda7ac7..c4364b53f0 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -77,7 +77,7 @@ public void onError(SyncSession session, ObjectServerError error) { .build(); final Realm realm = Realm.getInstance(config); - looperThread.testRealms.add(realm); + looperThread.addTestRealm(realm); // FIXME: Right now we have no Java API for detecting when a session is established // So we optimistically assume it has been connected after 1 second. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index f78c34acfe..0ad9688104 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -69,7 +69,7 @@ public void onError(SyncSession session, ObjectServerError error) { }) .build(); final Realm realm1 = Realm.getInstance(config1); - looperThread.testRealms.add(realm1); + looperThread.addTestRealm(realm1); realm1.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { @@ -83,7 +83,7 @@ public void execute(Realm realm) { // 3. Create PermissionOffer final AtomicReference offerId = new AtomicReference(null); final Realm user1ManagementRealm = user1.getManagementRealm(); - looperThread.testRealms.add(user1ManagementRealm); + looperThread.addTestRealm(user1ManagementRealm); user1ManagementRealm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { @@ -103,7 +103,7 @@ public void onSuccess() { RealmResults offers = user1ManagementRealm.where(PermissionOffer.class) .equalTo("id", offerId.get()) .findAllAsync(); - looperThread.keepStrongReference.add(offers); + looperThread.keepStrongReference(offers); offers.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults offers) { @@ -113,7 +113,7 @@ public void onChange(RealmResults offers) { final String offerToken = offer.getToken(); final AtomicReference offerResponseId = new AtomicReference(); final Realm user2ManagementRealm = user2.getManagementRealm(); - looperThread.testRealms.add(user2ManagementRealm); + looperThread.addTestRealm(user2ManagementRealm); user2ManagementRealm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { @@ -128,7 +128,7 @@ public void onSuccess() { RealmResults responses = user2ManagementRealm.where(PermissionOfferResponse.class) .equalTo("id", offerResponseId.get()) .findAllAsync(); - looperThread.keepStrongReference.add(responses); + looperThread.keepStrongReference(responses); responses.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults responses) { @@ -136,9 +136,9 @@ public void onChange(RealmResults responses) { if (response != null && response.isSuccessful() && response.getToken().equals(offerToken)) { // 7. Response accepted. It should now be possible for user2 to access user1's Realm Realm realm = Realm.getInstance(config2); - looperThread.testRealms.add(realm); + looperThread.addTestRealm(realm); RealmResults dogs = realm.where(Dog.class).findAll(); - looperThread.keepStrongReference.add(dogs); + looperThread.keepStrongReference(dogs); dogs.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults element) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java index e5347effc8..02d9ddf64c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -18,9 +18,11 @@ public class Constants { - public static String SYNC_SERVER_URL = "realm://127.0.0.1/tests"; - public static String SYNC_SERVER_URL_2 = "realm://127.0.0.1/tests2"; + public static final String SYNC_SERVER_URL = "realm://127.0.0.1/tests"; + public static final String SYNC_SERVER_URL_2 = "realm://127.0.0.1/tests2"; - public static String AUTH_SERVER_URL = "http://127.0.0.1:9080/"; - public static String AUTH_URL = AUTH_SERVER_URL + "auth"; + public static final String AUTH_SERVER_URL = "http://127.0.0.1:9080/"; + public static final String AUTH_URL = AUTH_SERVER_URL + "auth"; + + public static final long TEST_TIMEOUT_SECS = 300; } From d5f721c5370d13ddea267e1dd9d805cc239295c2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 2 May 2017 12:30:22 +0200 Subject: [PATCH 0665/2110] Add support for changing a users password. (#4538) --- CHANGELOG.md | 1 + dependencies.list | 2 +- .../java/io/realm/SyncUserTests.java | 44 ++++ .../objectServer/java/io/realm/SyncUser.java | 190 +++++++++++++----- .../internal/network/AuthServerResponse.java | 2 +- .../network/AuthenticationServer.java | 5 + .../network/ChangePasswordRequest.java | 55 +++++ .../network/ChangePasswordResponse.java | 55 +++++ .../realm/internal/network/LogoutRequest.java | 2 +- .../network/OkHttpAuthenticationServer.java | 58 ++++-- .../java/io/realm/objectserver/AuthTests.java | 101 +++++++--- .../objectserver/BaseIntegrationTest.java | 6 + 12 files changed, 418 insertions(+), 103 deletions(-) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7474350814..4129e7d5b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements * [ObjectServer] Added support for `SyncUser.isAdmin()` (#4353). +* [ObjectServer] Added support for changing passwords through `SyncUser.changePassword()` (#4423). * Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore` annotation (#4279). * Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). diff --git a/dependencies.list b/dependencies.list index 06db1eeb3f..7a9a09482c 100644 --- a/dependencies.list +++ b/dependencies.list @@ -10,4 +10,4 @@ REALM_SYNC_SHA256=e8a973dbe6ab33ac49d3d0e45d6b63d69cec8d1d87d9a2311fcdd02767f76c # /tools/sync_test_server/Dockerfile specify which repo (apt) we should # install/use between 'realm' and 'realm-testing', the version below should # correspond to an existing version on the *specified* repo. -REALM_OBJECT_SERVER_DE_VERSION=1.3.0-294 +REALM_OBJECT_SERVER_DE_VERSION=1.4.0-302 diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index fe69cfff36..487a95ff54 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -17,12 +17,14 @@ package io.realm; import android.support.test.InstrumentationRegistry; +import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -38,6 +40,7 @@ import io.realm.internal.network.AuthenticationServer; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; import io.realm.util.SyncTestUtils; import static io.realm.util.SyncTestUtils.createTestAdminUser; @@ -57,6 +60,12 @@ public class SyncUserTests { @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Rule + public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); + @BeforeClass public static void initUserStore() { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); @@ -268,4 +277,39 @@ public void login_appendAuthSegment() { SyncManager.setAuthServerImpl(originalServer); } } + + @Test + public void changePassword_nullThrows() { + SyncUser user = createTestUser(); + + thrown.expect(IllegalArgumentException.class); + user.changePassword(null); + } + + @Test + public void changePasswordAsync_nonLooperThreadThrows() { + SyncUser user = createTestUser(); + + thrown.expect(IllegalStateException.class); + user.changePasswordAsync(null, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser user) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + fail(); + } + }); + } + + @Test + @RunTestInLooperThread + public void changePasswordAsync_nullCallbackThrows() { + SyncUser user = createTestUser(); + + thrown.expect(IllegalArgumentException.class); + user.changePasswordAsync("new-password", null); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index ec1d2f0ba2..9dcbcfd680 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -35,10 +35,14 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; +import io.realm.internal.RealmNotifier; import io.realm.internal.Util; +import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.ChangePasswordResponse; import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.LogoutResponse; import io.realm.internal.objectserver.ObjectServerUser; @@ -46,6 +50,7 @@ import io.realm.log.RealmLog; import io.realm.permissions.PermissionModule; + /** * This class represents a user on the Realm Object Server. The credentials are provided by various 3rd party * providers (Facebook, Google, etc.). @@ -101,7 +106,7 @@ private SyncUser(ObjectServerUser user) { * A user is invalidated when he/she logs out or the user's access token expires. * * @return current {@link SyncUser} that has logged in and is still valid. {@code null} if no user is logged in or the user has - * expired. + * expired. * @throws IllegalStateException if multiple users are logged in. */ public static SyncUser currentUser() { @@ -134,7 +139,6 @@ public static Map all() { * Loads a user that has previously been serialized using {@link #toJson()}. * * @param user JSON string representing the user. - * * @return the user object. * @throws IllegalArgumentException if the JSON couldn't be converted to a valid {@link SyncUser} object. */ @@ -221,55 +225,18 @@ public static SyncUser login(final SyncCredentials credentials, final String aut * @param credentials credentials to use. * @param authenticationUrl server that the user is authenticated against. * @param callback callback when login has completed or failed. The callback will always happen on the same thread - * as this this method is called on. + * as this this method is called on. + * @return representation of the async task that can be used to cancel it if needed. * @throws IllegalArgumentException if not on a Looper thread. */ public static RealmAsyncTask loginAsync(final SyncCredentials credentials, final String authenticationUrl, final Callback callback) { - if (Looper.myLooper() == null) { - throw new IllegalStateException("Asynchronous login is only possible from looper threads."); - } - final Handler handler = new Handler(Looper.myLooper()); - ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; - Future authenticateRequest = networkPoolExecutor.submit(new Runnable() { + checkLooperThread("Asynchronous login is only possible from looper threads."); + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { @Override - public void run() { - try { - SyncUser user = login(credentials, authenticationUrl); - postSuccess(user); - } catch (ObjectServerError e) { - postError(e); - } - } - - private void postError(final ObjectServerError error) { - if (callback != null) { - handler.post(new Runnable() { - @Override - public void run() { - try { - callback.onError(error); - } catch (Exception e) { - RealmLog.info("onError has thrown an exception but is ignoring it: %s", - Util.getStackTrace(e)); - } - } - }); - } + public SyncUser run() throws ObjectServerError { + return login(credentials, authenticationUrl); } - - private void postSuccess(final SyncUser user) { - if (callback != null) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onSuccess(user); - } - }); - } - } - }); - - return new RealmAsyncTaskImpl(authenticateRequest, networkPoolExecutor); + }.start(); } /** @@ -277,7 +244,7 @@ public void run() { * {@link AuthenticationListener} will be notified and user credentials will be deleted from this device. * * @throws IllegalStateException if any Realms owned by this user is still open. They should be closed before - * logging out. + * logging out. */ /* FIXME: Add this back to the javadoc when enable SyncConfiguration.Builder#deleteRealmOnLogout()

                      @@ -329,7 +296,7 @@ public void logout() { @Override protected LogoutResponse execute() { - return server.logout(userToken, syncUser.getAuthenticationUrl()); + return server.logout(userToken, getAuthenticationUrl()); } @Override @@ -345,6 +312,58 @@ protected void onError(LogoutResponse response) { } } + /** + * Changes this user's password. This is done synchronously and involves the network, so calling this method on the + * Android UI thread will always crash. + *

                      + * WARNING: Changing a users password using an authentication server that doesn't use HTTPS is a major + * security flaw, and should only be done while testing. + * + * @param newPassword the user's new password. + * @throws ObjectServerError if the password could not be changed. + */ + public void changePassword(String newPassword) throws ObjectServerError { + if (newPassword == null) { + throw new IllegalArgumentException("Not-null 'newPassword' required."); + } + AuthenticationServer authServer = SyncManager.getAuthServer(); + ChangePasswordResponse response = authServer.changePassword(getSyncUser().getUserToken(), newPassword, getAuthenticationUrl()); + if (!response.isValid()) { + throw response.getError(); + } + } + + /** + * Changes this user's password asynchronously. + *

                      + * WARNING: Changing a users password using an authentication server that doesn't use HTTPS is a major + * security flaw, and should only be done while testing. + * + * @param newPassword the user's new password. + * @param callback callback when login has completed or failed. The callback will always happen on the same thread + * as this method is called on. + * @return representation of the async task that can be used to cancel it if needed. + * @throws IllegalArgumentException if not on a Looper thread. + */ + public RealmAsyncTask changePasswordAsync(final String newPassword, final Callback callback) { + checkLooperThread("Asynchronous changing password is only possible from looper threads."); + if (callback == null) { + throw new IllegalArgumentException("Non-null 'callback' required."); + } + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public SyncUser run() { + changePassword(newPassword); + return SyncUser.this; + } + }.start(); + } + + private static void checkLooperThread(String errorMessage) { + AndroidCapabilities capabilities = new AndroidCapabilities(); + capabilities.checkCanDeliverNotification(errorMessage); + } + /** * Returns a JSON token representing this user. *

                      @@ -352,8 +371,7 @@ protected void onError(LogoutResponse response) { * should be treated as sensitive data. * * @return JSON string representing this user. It can be converted back into a real user object using - * {@link #fromJson(String)}. - * + * {@link #fromJson(String)}. * @see #fromJson(String) */ public String toJson() { @@ -392,7 +410,7 @@ public boolean isAdmin() { * among all users on the Realm Object Server. * * @return identity of the user on the Realm Object Server. If the user has logged out or the login has expired - * {@code null} is returned. + * {@code null} is returned. */ public String getIdentity() { return syncUser.getIdentity(); @@ -446,8 +464,8 @@ private static String getManagementRealmUrl(URL authUrl) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } SyncUser user = (SyncUser) o; @@ -464,7 +482,7 @@ public int hashCode() { public String toString() { StringBuilder sb = new StringBuilder("{"); sb.append("UserId: ").append(syncUser.getIdentity()); - sb.append(", AuthUrl: ").append(syncUser.getAuthenticationUrl()); + sb.append(", AuthUrl: ").append(getAuthenticationUrl()); sb.append(", IsValid: ").append(isValid()); sb.append(", Sessions: ").append(syncUser.getSessions().size()); sb.append("}"); @@ -476,8 +494,72 @@ ObjectServerUser getSyncUser() { return syncUser; } + // Class wrapping requests made against the auth server. Is also responsible for calling with success/error on the + // correct thread. + private static abstract class Request { + + private final Callback callback; + private final RealmNotifier handler; + private final ThreadPoolExecutor networkPoolExecutor; + + public Request(ThreadPoolExecutor networkPoolExecutor, Callback callback) { + this.callback = callback; + this.handler = new AndroidRealmNotifier(null, new AndroidCapabilities()); + this.networkPoolExecutor = networkPoolExecutor; + } + + // Implements the request. Return the current sync user if the request succeeded. Otherwise throw an error. + public abstract SyncUser run() throws ObjectServerError; + + // Start the request + public RealmAsyncTask start() { + Future authenticateRequest = networkPoolExecutor.submit(new Runnable() { + @Override + public void run() { + try { + postSuccess(Request.this.run()); + } catch (ObjectServerError e) { + postError(e); + } catch (Throwable e) { + postError(new ObjectServerError(ErrorCode.UNKNOWN, "Unexpected error", e)); + } + } + }); + return new RealmAsyncTaskImpl(authenticateRequest, networkPoolExecutor); + } + + private void postError(final ObjectServerError error) { + boolean errorHandled = false; + if (callback != null) { + Runnable action = new Runnable() { + @Override + public void run() { + callback.onError(error); + } + }; + errorHandled = handler.post(action); + } + + if (!errorHandled) { + RealmLog.error(error, "An error was thrown, but could not be handled."); + } + } + + private void postSuccess(final SyncUser user) { + if (callback != null) { + handler.post(new Runnable() { + @Override + public void run() { + callback.onSuccess(user); + } + }); + } + } + } + public interface Callback { void onSuccess(SyncUser user); + void onError(ObjectServerError error); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java index a5ad8e9190..c6d2c47045 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java @@ -24,7 +24,7 @@ /** * Base class for all response types from the Realm Authentication Server. */ -public class AuthServerResponse { +public abstract class AuthServerResponse { protected ObjectServerError error; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java index 3b74558cc6..9b9c24c25e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java @@ -56,4 +56,9 @@ public interface AuthenticationServer { * logged out as well. */ LogoutResponse logout(Token userToken, URL authenticationUrl); + + /** + * Changes a user's password. + */ + ChangePasswordResponse changePassword(Token userToken, String newPassword, URL authenticationUrl); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java new file mode 100644 index 0000000000..65f4886f47 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import io.realm.internal.objectserver.Token; + +/** + * This class encapsulates a request to change the password for a user on the Realm Authentication Server. It is + * responsible for constructing the JSON understood by the Realm Authentication Server. + */ +public class ChangePasswordRequest { + + private final String token; + private final String newPassword; + + public static ChangePasswordRequest create(Token userToken, String newPassword) { + return new ChangePasswordRequest(userToken.value(), newPassword); + } + + private ChangePasswordRequest(String token, String newPassword) { + this.token = token; + this.newPassword = newPassword; + } + + /** + * Converts the request into a JSON payload. + */ + public String toJson() { + try { + JSONObject request = new JSONObject(); + request.put("token", token); + request.put("password", newPassword); + return request.toString(); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java new file mode 100644 index 0000000000..4fc951d3e5 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import java.io.IOException; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.log.RealmLog; +import okhttp3.Response; + +/** + * Class wrapping the response from `/auth/password` + */ +public class ChangePasswordResponse extends AuthServerResponse { + + public static ChangePasswordResponse from(Response response) { + if (response.isSuccessful()) { + return new ChangePasswordResponse(); + } + try { + String serverResponse = response.body().string(); + return new ChangePasswordResponse(AuthServerResponse.createError(serverResponse, response.code())); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new ChangePasswordResponse(error); + } + } + + public static ChangePasswordResponse createFailure(ObjectServerError objectServerError) { + return new ChangePasswordResponse(objectServerError); + } + + private ChangePasswordResponse() { + this.error = null; + } + + private ChangePasswordResponse(ObjectServerError error) { + this.error = error; + } + +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java index 8cb67b56a9..49a30ade75 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java @@ -29,7 +29,7 @@ public class LogoutRequest { private final String token; - public static LogoutRequest revoke(Token userToken) { + public static LogoutRequest create(Token userToken) { return new LogoutRequest(userToken.value()); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 40148acb8a..46afe6e82c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -36,6 +36,8 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static final String ACTION_LOGOUT = "revoke"; // Auth end point for logging out users + private static final String ACTION_CHANGE_PASSWORD = "password"; // Auth end point for changing passwords private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) @@ -79,47 +81,63 @@ public AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL auth @Override public LogoutResponse logout(Token userToken, URL authenticationUrl) { try { - String requestBody = LogoutRequest.revoke(userToken).toJson(); - return logout(buildLogoutUrl(authenticationUrl), requestBody); + String requestBody = LogoutRequest.create(userToken).toJson(); + return logout(buildActionUrl(authenticationUrl, ACTION_LOGOUT), requestBody); } catch (Exception e) { return LogoutResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); } } - private static URL buildLogoutUrl(URL authenticationUrl) { + @Override + public ChangePasswordResponse changePassword(Token userToken, String newPassword, URL authenticationUrl) { + try { + String requestBody = ChangePasswordRequest.create(userToken, newPassword).toJson(); + return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD), requestBody); + } catch (Throwable e) { + return ChangePasswordResponse.createFailure(new ObjectServerError(ErrorCode.UNKNOWN, e)); + } + } + + // Builds the URL for a specific auth endpoint + private static URL buildActionUrl(URL authenticationUrl, String action) { final String baseUrlString = authenticationUrl.toExternalForm(); try { - if (baseUrlString.endsWith("/")) { - return new URL(baseUrlString + "revoke"); - } else { - return new URL(baseUrlString + "/revoke"); - } + String separator = baseUrlString.endsWith("/") ? "" : "/"; + return new URL(baseUrlString + separator + action); } catch (MalformedURLException e) { throw new RuntimeException(e); } } private AuthenticateResponse authenticate(URL authenticationUrl, String requestBody) throws Exception { - Request request = new Request.Builder() - .url(authenticationUrl) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .post(RequestBody.create(JSON, requestBody)) - .build(); + RealmLog.debug("Network request (authenticate): " + authenticationUrl); + Request request = newAuthRequest(authenticationUrl).post(RequestBody.create(JSON, requestBody)).build(); Call call = client.newCall(request); Response response = call.execute(); return AuthenticateResponse.from(response); } private LogoutResponse logout(URL logoutUrl, String requestBody) throws Exception { - Request request = new Request.Builder() - .url(logoutUrl) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .post(RequestBody.create(JSON, requestBody)) - .build(); + RealmLog.debug("Network request (logout): " + logoutUrl); + Request request = newAuthRequest(logoutUrl).post(RequestBody.create(JSON, requestBody)).build(); Call call = client.newCall(request); Response response = call.execute(); return LogoutResponse.from(response); } + + private ChangePasswordResponse changePassword(URL changePasswordUrl, String requestBody) throws Exception { + RealmLog.debug("Network request (changePassword): " + changePasswordUrl); + Request request = newAuthRequest(changePasswordUrl).put(RequestBody.create(JSON, requestBody)).build(); + Call call = client.newCall(request); + Response response = call.execute(); + return ChangePasswordResponse.from(response); + } + + private Request.Builder newAuthRequest(URL url) { + return new Request.Builder() + .url(url) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json"); + } + } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 354e365a09..6e3373d29a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -1,13 +1,20 @@ package io.realm.objectserver; +import android.os.Handler; +import android.os.Looper; import android.support.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import java.net.MalformedURLException; import java.net.URL; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import io.realm.ErrorCode; import io.realm.ObjectServerError; @@ -17,8 +24,6 @@ import io.realm.SyncManager; import io.realm.SyncSession; import io.realm.SyncUser; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunInLooperThread; @@ -35,6 +40,9 @@ public class AuthTests extends BaseIntegrationTest { @Rule public RunInLooperThread looperThread = new RunInLooperThread(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + @Test public void login_userNotExist() { SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); @@ -126,35 +134,76 @@ public void onError(ObjectServerError error) { }); } - // The error handler throws an exception but it is ignored (but logged). That means, this test should not - // pass and not be stopped by an IllegalArgumentException. @Test - @RunTestInLooperThread - public void loginAsync_errorHandlerThrows() { - // set log level to info to make sure the IllegalArgumentException - // thrown in the test is visible in Logcat - final int defaultLevel = RealmLog.getLevel(); - RealmLog.setLevel(LogLevel.INFO); - SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - fail(); - } + public void loginAsync_errorHandlerThrows() throws InterruptedException { + final AtomicBoolean errorThrown = new AtomicBoolean(false); + // Create custom Looper thread to be able to check for errors thrown when processing Looper events. + Thread t = new Thread(new Runnable() { + private volatile Handler handler; @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.INVALID_CREDENTIALS, error.getErrorCode()); - throw new IllegalArgumentException("BOOM"); + public void run() { + Looper.prepare(); + try { + handler = new Handler(); + handler.post(new Runnable() { + @Override + public void run() { + SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser user) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.INVALID_CREDENTIALS, error.getErrorCode()); + throw new IllegalArgumentException("BOOM"); + } + }); + } + }); + Looper.loop(); // + } catch (IllegalArgumentException e) { + errorThrown.set(true); + } } }); + t.start(); + t.join(TimeUnit.SECONDS.toMillis(10)); + assertTrue(errorThrown.get()); + } - looperThread.postRunnableDelayed(new Runnable() { - @Override - public void run() { - RealmLog.setLevel(defaultLevel); - looperThread.testComplete(); - } - }, 1000); + @Test + public void changePassword() { + String username = UUID.randomUUID().toString(); + String originalPassword = "password"; + SyncCredentials credentials = SyncCredentials.usernamePassword(username, originalPassword, true); + SyncUser userOld = SyncUser.login(credentials, Constants.AUTH_URL); + assertTrue(userOld.isValid()); + + // Change password and try to log in with new password + String newPassword = "new-password"; + userOld.changePassword(newPassword); + userOld.logout(); + credentials = SyncCredentials.usernamePassword(username, newPassword, false); + SyncUser userNew = SyncUser.login(credentials, Constants.AUTH_URL); + + assertTrue(userNew.isValid()); + assertEquals(userOld.getIdentity(), userNew.getIdentity()); + } + + @Test + public void changePassword_throwWhenUserIsLoggedOut() { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + user.logout(); + + thrown.expect(ObjectServerError.class); + user.changePassword("new-password"); } + } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java index 764b511ee7..79081d405a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java @@ -23,16 +23,21 @@ import io.realm.Realm; import io.realm.SyncManager; +import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.objectserver.utils.HttpUtils; class BaseIntegrationTest { + private static int originalLogLevel; + @BeforeClass public static void setUp () throws Exception { SyncManager.Debug.skipOnlineChecking = true; try { Realm.init(InstrumentationRegistry.getContext()); + originalLogLevel = RealmLog.getLevel(); + RealmLog.setLevel(LogLevel.DEBUG); HttpUtils.startSyncServer(); } catch (Exception e) { // Throwing an exception from this method will crash JUnit. Instead just log it. @@ -45,6 +50,7 @@ public static void setUp () throws Exception { public static void tearDown () throws Exception { try { HttpUtils.stopSyncServer(); + RealmLog.setLevel(originalLogLevel); } catch (Exception e) { RealmLog.error("Failed to stop Sync Server", e); } From 5daf145b3b4749c0ff4fe86bba43b565caa3d3ea Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 2 May 2017 20:07:24 +0900 Subject: [PATCH 0666/2110] update butterknife to 8.5.1 (#4587) --- examples/newsreaderExample/build.gradle | 3 ++- .../newsreader/ui/details/DetailsActivity.java | 10 +++++----- .../examples/newsreader/ui/main/MainActivity.java | 12 ++++++------ examples/objectServerExample/build.gradle | 4 ++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index 778186e266..dcc0165a06 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -43,6 +43,7 @@ dependencies { compile 'com.squareup.retrofit:converter-jackson:2.0.0-beta2' compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' compile 'com.jakewharton.timber:timber:4.1.0' - compile 'com.jakewharton:butterknife:7.0.1' + compile 'com.jakewharton:butterknife:8.5.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' compile 'me.zhanghai.android.materialprogressbar:library:1.1.4' } diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsActivity.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsActivity.java index 2356f04a76..5222153ecf 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsActivity.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsActivity.java @@ -27,7 +27,7 @@ import android.widget.ProgressBar; import android.widget.TextView; -import butterknife.Bind; +import butterknife.BindView; import butterknife.ButterKnife; import io.realm.examples.newsreader.R; import io.realm.examples.newsreader.model.Model; @@ -37,10 +37,10 @@ public class DetailsActivity extends AppCompatActivity { private static final String KEY_STORY_ID = "key.storyId"; - @Bind(R.id.details_text) TextView detailsView; - @Bind(R.id.read_text) TextView readView; - @Bind(R.id.date_text) TextView dateView; - @Bind(R.id.loader_view) ProgressBar loaderView; + @BindView(R.id.details_text) TextView detailsView; + @BindView(R.id.read_text) TextView readView; + @BindView(R.id.date_text) TextView dateView; + @BindView(R.id.loader_view) ProgressBar loaderView; private Toolbar toolbar; private DetailsPresenter presenter; diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainActivity.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainActivity.java index b7c81a6c88..fd6030a7c0 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainActivity.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainActivity.java @@ -34,7 +34,7 @@ import java.util.List; -import butterknife.Bind; +import butterknife.BindView; import butterknife.ButterKnife; import io.realm.examples.newsreader.R; import io.realm.examples.newsreader.model.Model; @@ -43,10 +43,10 @@ public class MainActivity extends AppCompatActivity { - @Bind(R.id.refresh_view) SwipeRefreshLayout refreshView; - @Bind(R.id.list_view) ListView listView; - @Bind(R.id.progressbar) MaterialProgressBar progressBar; - @Bind(R.id.spinner) Spinner spinner; + @BindView(R.id.refresh_view) SwipeRefreshLayout refreshView; + @BindView(R.id.list_view) ListView listView; + @BindView(R.id.progressbar) MaterialProgressBar progressBar; + @BindView(R.id.spinner) Spinner spinner; MainPresenter presenter = new MainPresenter(this, Model.getInstance()); private ArrayAdapter adapter; @@ -169,7 +169,7 @@ public View getView(int position, View convertView, ViewGroup parent) { } static class ViewHolder { - @Bind(android.R.id.text1) TextView titleView; + @BindView(android.R.id.text1) TextView titleView; public ViewHolder(View view) { ButterKnife.bind(this, view); } diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 8a42675c8a..e3080cda9f 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -62,6 +62,6 @@ realm { dependencies { compile 'com.android.support:support-v4:25.2.0' compile 'com.android.support:design:25.2.0' - compile 'com.jakewharton:butterknife:8.3.0' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.3.0' + compile 'com.jakewharton:butterknife:8.5.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' } From 523ba0469e3e0a42be49b811a225f034befd614f Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 2 May 2017 21:42:42 +0900 Subject: [PATCH 0667/2110] Fix Context leak warning in threadExample (#4580) (#4586) * fix Context leak warning in threadExample (#4580) * cancel task in onStop() * add check if the fragment is attached to the UI * add check if the fragment is attached to the UI * cancel the task if parent fragment is detached --- .../examples/threads/AsyncTaskFragment.java | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncTaskFragment.java b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncTaskFragment.java index c3b567d105..79148eacc4 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncTaskFragment.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncTaskFragment.java @@ -28,6 +28,8 @@ import android.widget.SeekBar; import android.widget.TextView; +import java.lang.ref.WeakReference; + import io.realm.Realm; import io.realm.examples.threads.model.Score; @@ -56,7 +58,7 @@ public void onClick(View v) { asyncTask.cancel(true); } - asyncTask = new ImportAsyncTask(); + asyncTask = new ImportAsyncTask(AsyncTaskFragment.this); asyncTask.execute(); } }); @@ -64,6 +66,14 @@ public void onClick(View v) { return rootView; } + @Override + public void onStop() { + super.onStop(); + if (asyncTask != null) { + asyncTask.cancel(false); + } + } + private void showStatus(String txt) { Log.i(TAG, txt); TextView tv = new TextView(getActivity()); @@ -80,7 +90,13 @@ private void showStatus(String txt) { // UI thread. This means that it is not possible to reuse RealmObjects or RealmResults created // in doInBackground() in the other methods. Nor is it possible to use RealmObjects as Progress // or Result objects. - private class ImportAsyncTask extends AsyncTask { + private static class ImportAsyncTask extends AsyncTask { + + private final WeakReference fragmentRef; + + ImportAsyncTask(AsyncTaskFragment outerFragment) { + fragmentRef = new WeakReference(outerFragment); + } @Override protected Integer doInBackground(Void... params) { @@ -106,16 +122,25 @@ public void execute(Realm realm) { @Override protected void onPreExecute() { - logsView.removeAllViews(); - progressView.setVisibility(View.VISIBLE); - showStatus("Starting import"); + final AsyncTaskFragment fragment = fragmentRef.get(); + if (fragment == null || fragment.isDetached()) { + cancel(false); + return; + } + fragment.logsView.removeAllViews(); + fragment.progressView.setVisibility(View.VISIBLE); + fragment.showStatus("Starting import"); } @Override protected void onPostExecute(Integer sum) { - progressView.setVisibility(View.GONE); - showStatus(TEST_OBJECTS + " objects imported."); - showStatus("The total score is : " + sum); + final AsyncTaskFragment fragment = fragmentRef.get(); + if (fragment == null || fragment.isDetached()) { + return; + } + fragment.progressView.setVisibility(View.GONE); + fragment.showStatus(TEST_OBJECTS + " objects imported."); + fragment.showStatus("The total score is : " + sum); } } } From 72b1df1cff469ffebb1c87209e9c3f4276b08264 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 2 May 2017 22:32:42 +0800 Subject: [PATCH 0668/2110] Fine grained locks for RealmCache (#4551) - Separated lock for different RealmConfiguration instead of one lock on the RealmCache class. So Opening Realm instances from different configurations won't block each other. - DynamicRealm which is created during opening type Realm will not be associated to any RealmCache to avoid recursive locks and multiple times initial block. (Also make the code easier.) This is for #4536 and part of implementation of #2299 . --- CHANGELOG.md | 2 + .../java/io/realm/RealmCacheTests.java | 89 ++++++- .../src/main/java/io/realm/BaseRealm.java | 57 ++-- .../src/main/java/io/realm/DynamicRealm.java | 13 + .../src/main/java/io/realm/Realm.java | 27 +- .../src/main/java/io/realm/RealmCache.java | 252 +++++++++++------- 6 files changed, 309 insertions(+), 131 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4129e7d5b5..a8b2312880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ ### Internal +* Use separated locks for different `RealmCache`s ($4551). + ## 3.1.4 ## Bug fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index b0ca7ffce1..a02c2b9006 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -191,7 +191,7 @@ public void deletingRealmAlsoClearsConfigurationCache() throws IOException { testRealm.close(); // 2. Deletes the old Realm. - Realm.deleteRealm(config); + assertTrue(Realm.deleteRealm(config)); // 3. Renames the new file to the old file name. assertTrue(copiedRealm.renameTo(new File(config.getRealmDirectory(), REALM_NAME))); @@ -261,36 +261,107 @@ public void run() { RealmCache.invokeWithGlobalRefCount(defaultConfig, new TestHelper.ExpectedCountCallback(0)); } + @Test + public void getInstance_differentConfigurationsShouldNotBlockEachOther() throws InterruptedException { + final CountDownLatch bgThreadStarted = new CountDownLatch(1); + final CountDownLatch realm2CreatedLatch = new CountDownLatch(1); + + final RealmConfiguration config1 = configFactory.createConfigurationBuilder() + .name("config1.realm") + .initialData(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + bgThreadStarted.countDown(); + TestHelper.awaitOrFail(realm2CreatedLatch); + } + }) + .build(); + + RealmConfiguration config2 = configFactory.createConfigurationBuilder() + .name("config2.realm") + .build(); + + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + Realm realm = Realm.getInstance(config1); + realm.close(); + } + }); + thread.start(); + + TestHelper.awaitOrFail(bgThreadStarted); + Realm realm = Realm.getInstance(config2); + realm2CreatedLatch.countDown(); + realm.close(); + thread.join(); + } + @Test public void releaseCacheInOneThread() { // Tests release typed Realm instance. Realm realmA = RealmCache.createRealmOrGetFromCache(defaultConfig, Realm.class); Realm realmB = RealmCache.createRealmOrGetFromCache(defaultConfig, Realm.class); - RealmCache.release(realmA); + realmA.close(); assertNotNull(realmA.sharedRealm); - RealmCache.release(realmB); + realmB.close(); assertNull(realmB.sharedRealm); // No crash but warning in the log. - RealmCache.release(realmB); + realmB.close(); // Tests release dynamic Realm instance. DynamicRealm dynamicRealmA = RealmCache.createRealmOrGetFromCache(defaultConfig, DynamicRealm.class); DynamicRealm dynamicRealmB = RealmCache.createRealmOrGetFromCache(defaultConfig, DynamicRealm.class); - RealmCache.release(dynamicRealmA); + dynamicRealmA.close(); assertNotNull(dynamicRealmA.sharedRealm); - RealmCache.release(dynamicRealmB); + dynamicRealmB.close(); assertNull(dynamicRealmB.sharedRealm); // No crash but warning in the log. - RealmCache.release(dynamicRealmB); + dynamicRealmB.close(); // Tests both typed Realm and dynamic Realm in same thread. realmA = RealmCache.createRealmOrGetFromCache(defaultConfig, Realm.class); dynamicRealmA = RealmCache.createRealmOrGetFromCache(defaultConfig, DynamicRealm.class); - RealmCache.release(realmA); + realmA.close(); assertNull(realmA.sharedRealm); - RealmCache.release(dynamicRealmA); + dynamicRealmA.close(); assertNull(realmA.sharedRealm); } + + // The DynamicRealm and Realm with the same Realm path should share the same RealmCache + @Test + public void typedRealmAndDynamicRealmShareTheSameCache() { + final String DB_NAME = "same_name.realm"; + RealmConfiguration config1 = configFactory.createConfigurationBuilder() + .name(DB_NAME) + .build(); + + RealmConfiguration config2 = configFactory.createConfigurationBuilder() + .name(DB_NAME) + .initialData(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + // Because of config1 doesn't have initialData block, these two configurations are not the same. + // So if a Realm is created with config1, then create another Realm with config2 should just + // fail before executing this block. + fail(); + } + }) + .build(); + + DynamicRealm dynamicRealm = DynamicRealm.getInstance(config1); + Realm realm = null; + try { + realm = Realm.getInstance(config2); + fail(); + } catch (IllegalArgumentException ignored) { + } finally { + dynamicRealm.close(); + if (realm != null) { + realm.close(); + } + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 047a29377a..54aa59d27f 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -31,7 +31,6 @@ import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; import io.realm.internal.InvalidRow; -import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; @@ -69,20 +68,33 @@ abstract class BaseRealm implements Closeable { final long threadId; protected final RealmConfiguration configuration; + // Which RealmCache is this Realm associated to. It is null if the Realm instance is opened without being put into a + // cache. It is also null if the Realm is closed. + private RealmCache realmCache; protected SharedRealm sharedRealm; protected final StandardRealmSchema schema; - protected BaseRealm(RealmConfiguration configuration) { + // Create a realm instance and associate it to a RealmCache. + BaseRealm(RealmCache cache) { + this(cache.getConfiguration()); + this.realmCache = cache; + } + + // Create a realm instance without associating it to any RealmCache. + BaseRealm(RealmConfiguration configuration) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; + this.realmCache = null; this.sharedRealm = SharedRealm.getInstance(configuration, !(this instanceof Realm) ? null : new SharedRealm.SchemaVersionListener() { @Override public void onSchemaVersionChanged(long currentVersion) { - RealmCache.updateSchemaCache((Realm) BaseRealm.this); + if (realmCache != null) { + realmCache.updateSchemaCache((Realm) BaseRealm.this); + } } }, true); this.schema = new StandardRealmSchema(this); @@ -277,16 +289,20 @@ public boolean waitForChange() { * @throws IllegalStateException if the {@link io.realm.Realm} instance has already been closed. */ public void stopWaitForChange() { - RealmCache.invokeWithLock(new RealmCache.Callback0() { - @Override - public void onCall() { - // Checks if the Realm instance has been closed. - if (sharedRealm == null || sharedRealm.isClosed()) { - throw new IllegalStateException(BaseRealm.CLOSED_REALM_MESSAGE); + if (realmCache != null) { + realmCache.invokeWithLock(new RealmCache.Callback0() { + @Override + public void onCall() { + // Checks if the Realm instance has been closed. + if (sharedRealm == null || sharedRealm.isClosed()) { + throw new IllegalStateException(BaseRealm.CLOSED_REALM_MESSAGE); + } + sharedRealm.stopWaitForChange(); } - sharedRealm.stopWaitForChange(); - } - }); + }); + } else { + throw new IllegalStateException(BaseRealm.CLOSED_REALM_MESSAGE); + } } /** @@ -432,13 +448,18 @@ public void close() { throw new IllegalStateException(INCORRECT_THREAD_CLOSE_MESSAGE); } - RealmCache.release(this); + if (realmCache != null) { + realmCache.release(this); + } else { + doClose(); + } } /** * Closes the Realm instances and all its resources without checking the {@link RealmCache}. */ void doClose() { + realmCache = null; if (sharedRealm != null) { sharedRealm.close(); sharedRealm = null; @@ -505,9 +526,8 @@ E get(Class clazz, String dynamicClassName, UncheckedR E get(Class clazz, long rowIndex, boolean acceptDefaultValue, List excludeFields) { Table table = schema.getTable(clazz); UncheckedRow row = table.getUncheckedRow(rowIndex); - E result = configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz), + return configuration.getSchemaMediator().newInstance(clazz, this, row, schema.getColumnInfo(clazz), acceptDefaultValue, excludeFields); - return result; } // Used by RealmList/RealmResults @@ -623,7 +643,9 @@ public void onResult(int count) { RealmMigration realmMigration = (migration == null) ? configuration.getMigration() : migration; DynamicRealm realm = null; try { - realm = DynamicRealm.getInstance(configuration); + // Create a DynamicRealm WITHOUT putting it into a RealmCache to avoid recursive locks and call init + // steps multiple times (copy asset file / initialData transaction). + realm = DynamicRealm.createInstance(configuration); realm.beginTransaction(); long currentVersion = realm.getVersion(); realmMigration.migrate(realm, currentVersion, configuration.getSchemaVersion()); @@ -656,6 +678,9 @@ protected void finalize() throws Throwable { "Realm %s is being finalized without being closed, " + "this can lead to running out of native memory.", configuration.getPath() ); + if (realmCache != null) { + realmCache.leak(); + } } super.finalize(); } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 3a4f79ea94..92284eb561 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -45,6 +45,10 @@ */ public class DynamicRealm extends BaseRealm { + private DynamicRealm(RealmCache cache) { + super(cache); + } + private DynamicRealm(RealmConfiguration configuration) { super(configuration); } @@ -204,6 +208,15 @@ public void executeTransaction(Transaction transaction) { * * @return a {@link DynamicRealm} instance. */ + static DynamicRealm createInstance(RealmCache cache) { + return new DynamicRealm(cache); + } + + /** + * Create a {@link DynamicRealm} instance without associating it to any RealmCache. + * + * @return a {@link DynamicRealm} instance. + */ static DynamicRealm createInstance(RealmConfiguration configuration) { return new DynamicRealm(configuration); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index c57121d600..6853f95759 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -134,11 +134,11 @@ public class Realm extends BaseRealm { /** * The constructor is private to enforce the use of the static one. * - * @param configuration the {@link RealmConfiguration} used to open the Realm. + * @param cache the {@link RealmCache} associated to this Realm instance. * @throws IllegalArgumentException if trying to open an encrypted Realm with the wrong key. */ - Realm(RealmConfiguration configuration) { - super(configuration); + private Realm(RealmCache cache) { + super(cache); } /** @@ -309,16 +309,13 @@ public static void removeDefaultConfiguration() { /** * Creates a {@link Realm} instance without checking the existence in the {@link RealmCache}. * - * @param configuration {@link RealmConfiguration} used to create the Realm. - * @param globalCacheArray if this is not {@code null} and contains an entry for current schema version, - * the {@link BaseRealm#schema#columnIndices} will be initialized with the copy of - * the entry. Otherwise, {@link BaseRealm#schema#columnIndices} will be populated - * from the Realm file. + * @param cache the {@link RealmCache} where to create the realm in. * @return a {@link Realm} instance. */ - static Realm createInstance(RealmConfiguration configuration, ColumnIndices[] globalCacheArray) { + static Realm createInstance(RealmCache cache) { + RealmConfiguration configuration = cache.getConfiguration(); try { - return createAndValidate(configuration, globalCacheArray); + return createAndValidateFromCache(cache); } catch (RealmMigrationNeededException e) { if (configuration.shouldDeleteRealmIfMigrationNeeded()) { @@ -334,17 +331,19 @@ static Realm createInstance(RealmConfiguration configuration, ColumnIndices[] gl } } - return createAndValidate(configuration, globalCacheArray); + return createAndValidateFromCache(cache); } } - private static Realm createAndValidate(RealmConfiguration configuration, ColumnIndices[] globalCacheArray) { - Realm realm = new Realm(configuration); + private static Realm createAndValidateFromCache(RealmCache cache) { + Realm realm = new Realm(cache); + RealmConfiguration configuration = realm.configuration; final long currentVersion = realm.getVersion(); final long requiredVersion = configuration.getSchemaVersion(); - final ColumnIndices columnIndices = RealmCache.findColumnIndices(globalCacheArray, requiredVersion); + final ColumnIndices columnIndices = RealmCache.findColumnIndices(cache.getTypedColumnIndicesArray(), + requiredVersion); if (columnIndices != null) { // Copies global cache as a Realm local indices cache. diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index b4cab8a8ec..dc00ba5032 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -19,10 +19,15 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.WeakReference; import java.util.Arrays; +import java.util.Collection; import java.util.EnumMap; -import java.util.HashMap; -import java.util.Map; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import io.realm.exceptions.RealmFileException; import io.realm.internal.ColumnIndices; @@ -76,28 +81,67 @@ static RealmCacheType valueOf(Class clazz) { // Separated references and counters for typed Realm and dynamic Realm. private final EnumMap refAndCountMap; - final private RealmConfiguration configuration; + // Path to the Realm file to identify this cache. + private final String realmPath; + + // This will be only valid if getTotalGlobalRefCount() > 0. + // NOTE: We do reset this when globalCount reaches 0, but if exception thrown in doCreateRealmOrGetFromCache at the + // first time when globalCount == 0, this could have a non-null value but it will be reset when the next + // doCreateRealmOrGetFromCache is called with globalCount == 0. + private RealmConfiguration configuration; // Column indices are cached to speed up opening typed Realm. If a Realm instance is created in one thread, creating // Realm instances in other threads doesn't have to initialize the column indices again. private static final int MAX_ENTRIES_IN_TYPED_COLUMN_INDICES_ARRAY = 4; private final ColumnIndices[] typedColumnIndicesArray = new ColumnIndices[MAX_ENTRIES_IN_TYPED_COLUMN_INDICES_ARRAY]; - // Realm path will be used as the key to store different RealmCaches. Different Realm configurations with same path - // are not allowed and an exception will be thrown when trying to add it to the cache map. - private static final Map cachesMap = new HashMap<>(); + // Realm path will be used to identify different RealmCaches. Different Realm configurations with same path + // are not allowed and an exception will be thrown when trying to add it to the cache list. + // A weak ref is used to hold the RealmCache instance. The weak ref entry will be cleared if and only if there + // is no Realm instance holding a strong ref to it and there is no Realm instance associated it is BEING created. + private static final List> cachesList = new LinkedList>(); + + // See leak() + // isLeaked flag is used to avoid adding strong ref multiple times without iterating the list. + private final AtomicBoolean isLeaked = new AtomicBoolean(false); + // Keep strong ref to the leaked RealmCache + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private static final Collection leakedCaches = new ConcurrentLinkedQueue(); private static final String DIFFERENT_KEY_MESSAGE = "Wrong key used to decrypt Realm."; private static final String WRONG_REALM_CLASS_MESSAGE = "The type of Realm class must be Realm or DynamicRealm."; - private RealmCache(RealmConfiguration config) { - configuration = config; + private RealmCache(String path) { + realmPath = path; refAndCountMap = new EnumMap<>(RealmCacheType.class); for (RealmCacheType type : RealmCacheType.values()) { refAndCountMap.put(type, new RefAndCount()); } } + private static RealmCache getCache(String realmPath, boolean createIfNotExist) { + RealmCache cacheToReturn = null; + synchronized (cachesList) { + Iterator> it = cachesList.iterator(); + + while (it.hasNext()) { + RealmCache cache = it.next().get(); + if (cache == null) { + // Clear the entry if there is no one holding the RealmCache. + it.remove(); + } else if (cache.realmPath.equals(realmPath)) { + cacheToReturn = cache; + } + } + + if (cacheToReturn == null && createIfNotExist) { + cacheToReturn = new RealmCache(realmPath); + cachesList.add(new WeakReference(cacheToReturn)); + } + } + return cacheToReturn; + } + /** * Creates a new Realm instance or get an existing instance for current thread. * @@ -105,35 +149,44 @@ private RealmCache(RealmConfiguration config) { * @param realmClass class of {@link Realm} or {@link DynamicRealm} to be created in or gotten from the cache. * @return the {@link Realm} or {@link DynamicRealm} instance. */ - static synchronized E createRealmOrGetFromCache(RealmConfiguration configuration, + static E createRealmOrGetFromCache(RealmConfiguration configuration, + Class realmClass) { + RealmCache cache = getCache(configuration.getPath(), true); + + return cache.doCreateRealmOrGetFromCache(configuration, realmClass); + } + + private synchronized E doCreateRealmOrGetFromCache(RealmConfiguration configuration, Class realmClass) { - boolean isCacheInMap = true; - RealmCache cache = cachesMap.get(configuration.getPath()); - if (cache == null) { - // Creates a new cache. - cache = new RealmCache(configuration); - // The new cache should be added to the map later. - isCacheInMap = false; + RefAndCount refAndCount = refAndCountMap.get(RealmCacheType.valueOf(realmClass)); + + if (getTotalGlobalRefCount() == 0) { copyAssetFileIfNeeded(configuration); - } else { - // Throws the exception if validation failed. - cache.validateConfiguration(configuration); - } - RefAndCount refAndCount = cache.refAndCountMap.get(RealmCacheType.valueOf(realmClass)); + SharedRealm sharedRealm = null; + try { + sharedRealm = SharedRealm.getInstance(configuration); + if (Table.primaryKeyTableNeedsMigration(sharedRealm)) { + sharedRealm.beginTransaction(); + if (Table.migratePrimaryKeyTableIfNeeded(sharedRealm)) { + sharedRealm.commitTransaction(); + } else { + sharedRealm.cancelTransaction(); + } + } - if (refAndCount.globalCount == 0) { - SharedRealm sharedRealm = SharedRealm.getInstance(configuration); - if (Table.primaryKeyTableNeedsMigration(sharedRealm)) { - sharedRealm.beginTransaction(); - if (Table.migratePrimaryKeyTableIfNeeded(sharedRealm)) { - sharedRealm.commitTransaction(); - } else { - sharedRealm.cancelTransaction(); + } finally { + if (sharedRealm != null) { + sharedRealm.close(); } } - sharedRealm.close(); + + // We are holding the lock, and we can set the invalidated configuration since there is no global ref to it. + this.configuration = configuration; + } else { + // Throws exception if validation failed. + validateConfiguration(configuration); } if (refAndCount.localRealm.get() == null) { @@ -142,39 +195,30 @@ static synchronized E createRealmOrGetFromCache(RealmConfi if (realmClass == Realm.class) { // RealmMigrationNeededException might be thrown here. - realm = Realm.createInstance(configuration, cache.typedColumnIndicesArray); + realm = Realm.createInstance(this); } else if (realmClass == DynamicRealm.class) { - realm = DynamicRealm.createInstance(configuration); + realm = DynamicRealm.createInstance(this); } else { throw new IllegalArgumentException(WRONG_REALM_CLASS_MESSAGE); } // The Realm instance has been created without exceptions. Cache and reference count can be updated now. - - // The cache is not in the map yet. Add it to the map after the Realm instance created successfully. - if (!isCacheInMap) { - cachesMap.put(configuration.getPath(), cache); - } refAndCount.localRealm.set(realm); refAndCount.localCount.set(0); - } - Integer refCount = refAndCount.localCount.get(); - if (refCount == 0) { if (realmClass == Realm.class && refAndCount.globalCount == 0) { - final BaseRealm realm = refAndCount.localRealm.get(); // Stores a copy of local ColumnIndices as a global cache. - RealmCache.storeColumnIndices(cache.typedColumnIndicesArray, realm.schema.cloneColumnIndices()); + RealmCache.storeColumnIndices(typedColumnIndicesArray, realm.schema.cloneColumnIndices()); } // This is the first instance in current thread, increase the global count. refAndCount.globalCount++; } - refAndCount.localCount.set(refCount + 1); - @SuppressWarnings("unchecked") - E realm = (E) refAndCount.localRealm.get(); + Integer refCount = refAndCount.localCount.get(); + refAndCount.localCount.set(refCount + 1); - return realm; + //noinspection unchecked + return (E) refAndCount.localRealm.get(); } /** @@ -183,22 +227,16 @@ static synchronized E createRealmOrGetFromCache(RealmConfi * * @param realm Realm instance to be released from cache. */ - static synchronized void release(BaseRealm realm) { + synchronized void release(BaseRealm realm) { String canonicalPath = realm.getPath(); - RealmCache cache = cachesMap.get(canonicalPath); - Integer refCount = null; - RefAndCount refAndCount = null; - - if (cache != null) { - refAndCount = cache.refAndCountMap.get(RealmCacheType.valueOf(realm.getClass())); - refCount = refAndCount.localCount.get(); - } + RefAndCount refAndCount = refAndCountMap.get(RealmCacheType.valueOf(realm.getClass())); + Integer refCount = refAndCount.localCount.get(); if (refCount == null) { refCount = 0; } if (refCount <= 0) { - RealmLog.warn("%s has been closed already.", canonicalPath); + RealmLog.warn("%s has been closed already. refCount is %s", canonicalPath, refCount); return; } @@ -222,20 +260,18 @@ static synchronized void release(BaseRealm realm) { // Clears the column indices cache if needed. if (realm instanceof Realm && refAndCount.globalCount == 0) { // All typed Realm instances of this file are cleared from cache. - Arrays.fill(cache.typedColumnIndicesArray, null); - } - - int totalRefCount = 0; - for (RealmCacheType type : RealmCacheType.values()) { - totalRefCount += cache.refAndCountMap.get(type).globalCount; + Arrays.fill(typedColumnIndicesArray, null); } // No more local reference to this Realm in current thread, close the instance. realm.doClose(); - // No more instance of typed Realm and dynamic Realm. Remove the configuration from cache. - if (totalRefCount == 0) { - cachesMap.remove(canonicalPath); + // No more instance of typed Realm and dynamic Realm. + if (getTotalGlobalRefCount() == 0) { + // We keep the cache in the caches list even when its global counter reaches 0. It will be reused when + // next time a Realm instance with the same path is opened. By not removing it, the lock on + // cachesList is not needed here. + configuration = null; ObjectServerFacade.getFacade(realm.getConfiguration().isSyncConfiguration()) .realmClosed(realm.getConfiguration()); } @@ -287,17 +323,23 @@ private void validateConfiguration(RealmConfiguration newConfiguration) { * @param configuration the {@link RealmConfiguration} of {@link Realm} or {@link DynamicRealm}. * @param callback the callback will be executed with the global reference count. */ - static synchronized void invokeWithGlobalRefCount(RealmConfiguration configuration, Callback callback) { - RealmCache cache = cachesMap.get(configuration.getPath()); - if (cache == null) { - callback.onResult(0); - return; - } - int totalRefCount = 0; - for (RealmCacheType type : RealmCacheType.values()) { - totalRefCount += cache.refAndCountMap.get(type).globalCount; + static void invokeWithGlobalRefCount(RealmConfiguration configuration, Callback callback) { + // NOTE: Although getCache is locked on the cacheMap, this whole method needs to be lock with it as + // well. Since we need to ensure there is no Realm instance can be opened when this method is called (for + // deleteRealm). + // Recursive lock cannot be avoided here. + synchronized (cachesList) { + RealmCache cache = getCache(configuration.getPath(), false); + if (cache == null) { + callback.onResult(0); + return; + } + cache.doInvokeWithGlobalRefCount(callback); } - callback.onResult(totalRefCount); + } + + private synchronized void doInvokeWithGlobalRefCount(Callback callback) { + callback.onResult(getTotalGlobalRefCount()); } /** @@ -305,19 +347,14 @@ static synchronized void invokeWithGlobalRefCount(RealmConfiguration configurati * * @param realm the instance that contains the schema cache to be updated. */ - static synchronized void updateSchemaCache(Realm realm) { - final RealmCache cache = cachesMap.get(realm.getPath()); - if (cache == null) { - // Called during initialization. just skip it. - return; - } - final RefAndCount refAndCount = cache.refAndCountMap.get(RealmCacheType.TYPED_REALM); + synchronized void updateSchemaCache(Realm realm) { + final RefAndCount refAndCount = refAndCountMap.get(RealmCacheType.TYPED_REALM); if (refAndCount.localRealm.get() == null) { // Called during initialization. just skip it. // We can reach here if the DynamicRealm instance is initialized first. return; } - final ColumnIndices[] globalCacheArray = cache.typedColumnIndicesArray; + final ColumnIndices[] globalCacheArray = typedColumnIndicesArray; final ColumnIndices createdCacheEntry = realm.updateSchemaCache(globalCacheArray); if (createdCacheEntry != null) { RealmCache.storeColumnIndices(globalCacheArray, createdCacheEntry); @@ -329,7 +366,7 @@ static synchronized void updateSchemaCache(Realm realm) { * * @param callback the callback will be executed. */ - static synchronized void invokeWithLock(Callback0 callback) { + synchronized void invokeWithLock(Callback0 callback) { callback.onCall(); } @@ -394,17 +431,18 @@ private static void copyAssetFileIfNeeded(RealmConfiguration configuration) { } static int getLocalThreadCount(RealmConfiguration configuration) { - RealmCache cache = cachesMap.get(configuration.getPath()); + RealmCache cache = getCache(configuration.getPath(), false); if (cache == null) { return 0; - } else { - int totalRefCount = 0; - for (RealmCacheType type : RealmCacheType.values()) { - Integer localCount = cache.refAndCountMap.get(type).localCount.get(); - totalRefCount += (localCount != null) ? localCount : 0; - } - return totalRefCount; } + + // Access local ref count only, no need to by synchronized. + int totalRefCount = 0; + for (RefAndCount refAndCount : cache.refAndCountMap.values()) { + Integer localCount = refAndCount.localCount.get(); + totalRefCount += (localCount != null) ? localCount : 0; + } + return totalRefCount; } /** @@ -453,4 +491,34 @@ private static int storeColumnIndices(ColumnIndices[] array, ColumnIndices colum array[candidateIndex] = columnIndices; return candidateIndex; } + + public RealmConfiguration getConfiguration() { + return configuration; + } + + public ColumnIndices[] getTypedColumnIndicesArray() { + return typedColumnIndicesArray; + } + + /** + * @return the total global ref count. + */ + private int getTotalGlobalRefCount() { + int totalRefCount = 0; + for (RefAndCount refAndCount : refAndCountMap.values()) { + totalRefCount += refAndCount.globalCount; + } + + return totalRefCount; + } + + /** + * If a Realm instance is GCed but `Realm.close()` is not called before, we still want to track the cache for + * debugging. Adding them to the list to keep the strong ref of the cache to prevent the cache gets GCed. + */ + void leak() { + if (!isLeaked.getAndSet(true)) { + leakedCaches.add(this); + } + } } From e232544c6686f10ed1939ce7f8868401fe2538ca Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 3 May 2017 19:13:45 +0800 Subject: [PATCH 0669/2110] Implement getInstanceAsync (#4570) Fix #2299 - Add APIs to get Realm instance asynchronously. - Remove some useless methods. There some work need to be done before create the first Realm instance in the process, like creating schema table, doing migration, etc.. Those could block the UI thread quite badly. This commit tries to do those initialization work in the background and hold a Realm instance in the background until the 2nd instance created in the caller thread. A better solution than this would be do initialization in the background and only deliver a column indices cache to caller thread without holding a Realm instance in the background. But that is not possible since from the current database design, we cannot know if the schema changes compared with the last time it was opened. Also create a SharedGroup in the background and handover it to the caller thread is not ideal as well. That not only requires some design changes in the Object Store RealmCoordinator, but also is a very special use case of SharedGroup which core is not designed for. SharedGroup Leaking during the handover is another flaw for this solution -- we can only rely on the GC to collect the leaked SharedGroup during handover then. --- CHANGELOG.md | 1 + .../java/io/realm/DynamicRealmTests.java | 29 +++ .../java/io/realm/RealmCacheTests.java | 185 ++++++++++++++++++ .../androidTest/java/io/realm/RealmTests.java | 30 ++- .../java/io/realm/rule/RunInLooperThread.java | 4 + .../rule/TestRealmConfigurationFactory.java | 4 - .../src/main/java/io/realm/BaseRealm.java | 83 ++++++++ .../src/main/java/io/realm/DynamicRealm.java | 40 ++++ .../src/main/java/io/realm/Realm.java | 44 ++++- .../src/main/java/io/realm/RealmCache.java | 120 +++++++++++- .../async/RealmThreadPoolExecutor.java | 31 --- 11 files changed, 533 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8b2312880..176394b8fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * [ObjectServer] Added support for changing passwords through `SyncUser.changePassword()` (#4423). * Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore` annotation (#4279). * Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). +* Added `Realm.getInstanceAsync()` and `DynamicRealm.getInstanceAsync()` (#2299). ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 5d5b6ed371..a7c99b4fb4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -678,4 +678,33 @@ public void equalTo_noFieldObjectShouldThrow() { thrown.expectMessage("Invalid query: field 'nonExisting' does not exist in table 'NoField'."); dynamicRealm.where(className).equalTo("nonExisting", 1); } + + @Test(expected = IllegalStateException.class) + public void getInstanceAsync_nonLooperThreadShouldThrow() { + DynamicRealm.getInstanceAsync(defaultConfig, new DynamicRealm.Callback() { + @Override + public void onSuccess(DynamicRealm realm) { + fail(); + } + }); + } + + @Test + @RunTestInLooperThread + public void getInstanceAsync_nullConfigShouldThrow() { + thrown.expect(IllegalArgumentException.class); + DynamicRealm.getInstanceAsync(null, new DynamicRealm.Callback() { + @Override + public void onSuccess(DynamicRealm realm) { + fail(); + } + }); + } + + @Test + @RunTestInLooperThread + public void getInstanceAsync_nullCallbackShouldThrow() { + thrown.expect(IllegalArgumentException.class); + DynamicRealm.getInstanceAsync(defaultConfig, null); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index a02c2b9006..a6b842bd3c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -28,22 +28,29 @@ import java.io.File; import java.io.IOException; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; import io.realm.exceptions.RealmFileException; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) public class RealmCacheTests { + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @@ -330,6 +337,184 @@ public void releaseCacheInOneThread() { assertNull(realmA.sharedRealm); } + @Test + @RunTestInLooperThread + public void getInstanceAsync_typedRealm() { + final RealmConfiguration configuration = looperThread.createConfiguration(); + final AtomicBoolean realmCreated = new AtomicBoolean(false); + Realm.getInstanceAsync(configuration, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + realmCreated.set(true); + assertEquals(1, Realm.getLocalInstanceCount(configuration)); + realm.close(); + looperThread.testComplete(); + } + }); + assertFalse(realmCreated.get()); + } + + @Test + @RunTestInLooperThread + public void getInstanceAsync_dynamicRealm() { + final RealmConfiguration configuration = looperThread.createConfiguration(); + final AtomicBoolean realmCreated = new AtomicBoolean(false); + DynamicRealm.getInstanceAsync(configuration, new DynamicRealm.Callback() { + @Override + public void onSuccess(DynamicRealm realm) { + realmCreated.set(true); + assertEquals(1, Realm.getLocalInstanceCount(configuration)); + realm.close(); + looperThread.testComplete(); + } + }); + assertFalse(realmCreated.get()); + } + + @Test + @RunTestInLooperThread + public void getInstanceAsync_callbackDeliveredInFollowingEventLoopWhenLocalCacheExist() { + final RealmConfiguration configuration = looperThread.createConfiguration(); + final AtomicBoolean realmCreated = new AtomicBoolean(false); + final Realm localRealm = Realm.getInstance(configuration); + Realm.getInstanceAsync(configuration, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + realmCreated.set(true); + assertEquals(2, Realm.getLocalInstanceCount(configuration)); + assertSame(realm, localRealm); + realm.close(); + localRealm.close(); + looperThread.testComplete(); + } + }); + assertFalse(realmCreated.get()); + } + + @Test + @RunTestInLooperThread + public void getInstanceAsync_callbackDeliveredInFollowingEventLoopWhenGlobalCacheExist() throws InterruptedException { + final RealmConfiguration configuration = looperThread.createConfiguration(); + final AtomicBoolean realmCreated = new AtomicBoolean(false); + final CountDownLatch globalRealmCreated = new CountDownLatch(1); + final CountDownLatch getAsyncFinishedLatch = new CountDownLatch(1); + + final Thread thread = new Thread(new Runnable() { + @Override + public void run() { + Realm realm = Realm.getInstance(configuration); + globalRealmCreated.countDown(); + TestHelper.awaitOrFail(getAsyncFinishedLatch); + realm.close(); + } + }); + thread.start(); + + TestHelper.awaitOrFail(globalRealmCreated); + Realm.getInstanceAsync(configuration, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + realmCreated.set(true); + assertEquals(1, Realm.getLocalInstanceCount(configuration)); + realm.close(); + getAsyncFinishedLatch.countDown(); + try { + thread.join(); + } catch (InterruptedException e) { + fail(); + } + looperThread.testComplete(); + } + }); + assertFalse(realmCreated.get()); + } + + @Test + @RunTestInLooperThread + public void getInstanceAsync_typedRealmShouldStillBeInitializedInBGIfOnlyDynamicRealmExists() { + final RealmConfiguration configuration = looperThread.createConfiguration(); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(configuration); + final AtomicBoolean realmCreated = new AtomicBoolean(false); + + Realm.getInstanceAsync(configuration, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + realmCreated.set(false); + assertEquals(2, Realm.getLocalInstanceCount(configuration)); + dynamicRealm.close(); + realm.close(); + looperThread.testComplete(); + } + }); + // Callback should not be called immediately since we need to create column indices cache in bg thread. + // Only a local dynamic Realm instance existing at this time. + assertFalse(realmCreated.get()); + assertEquals(1, Realm.getLocalInstanceCount(configuration)); + } + + @Test + @RunTestInLooperThread + public void getInstanceAsync_onError() { + final RealmConfiguration configuration = + looperThread.createConfigurationBuilder() + .assetFile("NotExistingFile") + .build(); + Realm.getInstanceAsync(configuration, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + fail(); + } + + @Override + public void onError(Throwable exception) { + assertTrue(exception instanceof RealmFileException); + looperThread.testComplete(); + } + }); + } + + // If the async task is canceled before the posted event to create Realm instance in caller thread, the event should + // just be ignored. + @Test + @RunTestInLooperThread + public void getInstanceAsync_cancelBeforePostShouldNotCreateRealmInstanceOnTheCallerThread() { + final AtomicReference realmAsyncTasks = new AtomicReference<>(); + final Runnable finishedRunnable = new Runnable() { + @Override + public void run() { + looperThread.testComplete(); + } + }; + final RealmConfiguration configuration = looperThread.createConfigurationBuilder() + .name("will_be_canceled") + .initialData(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + // The BG thread started to initial the first Realm instance. Post an event to the caller's + // queue to cancel the task before the event to create the Realm instance in caller thread. + looperThread.postRunnable(new Runnable() { + @Override + public void run() { + assertNotNull(realmAsyncTasks.get()); + realmAsyncTasks.get().cancel(); + // Wait the async task to be terminated. + TestHelper.waitRealmThreadExecutorFinish(); + // Finish the test. + looperThread.postRunnable(finishedRunnable); + } + }); + } + }) + .build(); + + realmAsyncTasks.set(Realm.getInstanceAsync(configuration, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + fail(); + } + })); + } + // The DynamicRealm and Realm with the same Realm path should share the same RealmCache @Test public void typedRealmAndDynamicRealmShareTheSameCache() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 53517f3c8c..262b6717fc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -63,7 +63,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -3839,6 +3838,35 @@ public boolean accept(File dir, String name) { realmOnExternalStorage.close(); } + @Test(expected = IllegalStateException.class) + public void getInstanceAsync_nonLooperThreadShouldThrow() { + Realm.getInstanceAsync(realmConfig, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + fail(); + } + }); + } + + @Test + @RunTestInLooperThread + public void getInstanceAsync_nullConfigShouldThrow() { + thrown.expect(IllegalArgumentException.class); + Realm.getInstanceAsync(null, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + fail(); + } + }); + } + + @Test + @RunTestInLooperThread + public void getInstanceAsync_nullCallbackShouldThrow() { + thrown.expect(IllegalArgumentException.class); + Realm.getInstanceAsync(realmConfig, null); + } + // Verify that the logic for waiting for the users file dir to be come available isn't totally broken // This is pretty hard to test, so forced to break encapsulation in this case. @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index 49d1ececa6..33e699995e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -239,6 +239,10 @@ protected void before() throws Throwable { @Override protected void after() { + // Wait for all async tasks to have completed to ensure a successful deleteRealm call. + // If it times out, it will throw. + TestHelper.waitRealmThreadExecutorFinish(); + super.after(); // probably belt *and* suspenders... diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index 04bc9a4f6d..6cb628e5c1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -80,10 +80,6 @@ protected void before() throws Throwable { @Override protected void after() { - // Waits all async tasks done to ensure successful deleteRealm call. - // This will throw when timeout. And the reason of timeout needs to be solved properly. - TestHelper.waitRealmThreadExecutorFinish(); - try { for (RealmConfiguration configuration : configurations) { Realm.deleteRealm(configuration); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 54aa59d27f..5b7993e62a 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.CheckedRow; @@ -747,4 +748,86 @@ protected RealmObjectContext initialValue() { } public static final ThreadLocalRealmObjectContext objectContext = new ThreadLocalRealmObjectContext(); + + /** + * The Callback used when reporting back the result of loading a Realm asynchronously using either + * {@link Realm#getInstanceAsync(RealmConfiguration, Realm.Callback)} or + * {@link DynamicRealm#getInstanceAsync(RealmConfiguration, DynamicRealm.Callback)}. + *

                      + * Before creating the first Realm instance in a process, there are some initialization work that need to be done + * such as creating or validating schemas, running migration if needed, + * copy asset file if {@link RealmConfiguration.Builder#assetFile(String)} is supplied and execute the + * {@link RealmConfiguration.Builder#initialData(Realm.Transaction)} if necessary. This work may take time + * and block the caller thread for a while. To avoid the {@code getInstance()} call blocking the main thread, the + * {@code getInstanceAsync()} can be used instead to do the initialization work in the background thread and + * deliver a Realm instance to the caller thread. + *

                      + * In general, this method is mostly useful on the UI thread since that should be blocked as little as possible. On + * any other Looper threads or other threads that don't support callbacks, using the standard {@code getInstance()} + * should be fine. + *

                      + * Here is an example of using {@code getInstanceAsync()} when the app starts the first activity: + *

                      +     * public class MainActivity extends Activity {
                      +     *
                      +     *   private Realm realm = null;
                      +     *   private RealmAsyncTask realmAsyncTask;
                      +     *
                      +     *   \@Override
                      +     *   protected void onCreate(Bundle savedInstanceState) {
                      +     *     super.onCreate(savedInstanceState);
                      +     *     setContentView(R.layout.layout_main);
                      +     *     realmAsyncTask = Realm.getDefaultInstanceAsync(new Callback() {
                      +     *         \@Override
                      +     *         public void onSuccess(Realm realm) {
                      +     *             if (isDestroyed()) {
                      +     *                 // If the activity is destroyed, the Realm instance should be closed immediately to avoid leaks.
                      +     *                 // Or you can call realmAsyncTask.cancel() in onDestroy() to stop callback delivery.
                      +     *                 realm.close();
                      +     *             } else {
                      +     *                 MainActivity.this.realm = realm;
                      +     *                 // Remove the spinner and start the real UI.
                      +     *             }
                      +     *         }
                      +     *     });
                      +     *
                      +     *     // Show a spinner before Realm instance returned by the callback.
                      +     *   }
                      +     *
                      +     *   \@Override
                      +     *   protected void onDestroy() {
                      +     *     super.onDestroy();
                      +     *     if (realm != null) {
                      +     *         realm.close();
                      +     *         realm = null;
                      +     *     } else {
                      +     *         // Calling cancel() on the thread where getInstanceAsync was called on to stop the callback delivery.
                      +     *         // Otherwise you need to check if the activity is destroyed to close in the onSuccess() properly.
                      +     *         realmAsyncTask.cancel();
                      +     *     }
                      +     *   }
                      +     * }
                      +     * 
                      + * + * @param {@link Realm} or {@link DynamicRealm}. + */ + public abstract static class InstanceCallback { + + /** + * Deliver a Realm instance to the caller thread. + * + * @param realm the Realm instance for the caller thread. + */ + public abstract void onSuccess(T realm); + + /** + * Deliver an error happens when creating the Realm instance to the caller thread. The default implementation + * will throw an exception on the caller thread. + * + * @param exception happened while initializing Realm on a background thread. + */ + public void onError(Throwable exception) { + throw new RealmException("Exception happens when initializing Realm in the background thread.", exception); + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 92284eb561..17c5a861f4 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -70,6 +70,27 @@ public static DynamicRealm getInstance(RealmConfiguration configuration) { return RealmCache.createRealmOrGetFromCache(configuration, DynamicRealm.class); } + /** + * The creation of the first Realm instance per {@link RealmConfiguration} in a process can take some time as all + * initialization code need to run at that point (Setting up the Realm, validating schemas and creating initial + * data). This method places the initialization work in a background thread and deliver the Realm instance + * to the caller thread asynchronously after the initialization is finished. + * + * @param configuration {@link RealmConfiguration} used to open the Realm. + * @param callback invoked to return the results. + * @throws IllegalArgumentException if a null {@link RealmConfiguration} or a null {@link Callback} is provided. + * @throws IllegalStateException if it is called from a non-Looper or {@link android.app.IntentService} thread. + * @return a {@link RealmAsyncTask} representing a cancellable task. + * @see Callback for more details. + */ + public static RealmAsyncTask getInstanceAsync(RealmConfiguration configuration, + Callback callback) { + if (configuration == null) { + throw new IllegalArgumentException("A non-null RealmConfiguration must be provided"); + } + return RealmCache.createRealmOrGetFromCacheAsync(configuration, callback, DynamicRealm.class); + } + /** * Instantiates and adds a new object to the Realm. * @@ -239,5 +260,24 @@ public Observable asObservable() { public interface Transaction { void execute(DynamicRealm realm); } + + /** + * {@inheritDoc} + */ + public static abstract class Callback extends InstanceCallback { + /** + * {@inheritDoc} + */ + @Override + public abstract void onSuccess(DynamicRealm realm); + + /** + * {@inheritDoc} + */ + @Override + public void onError(Throwable exception) { + super.onError(exception); + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 6853f95759..10068d8fb0 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -127,6 +127,8 @@ */ public class Realm extends BaseRealm { + private static final String NULL_CONFIG_MSG = "A non-null RealmConfiguration must be provided"; + public static final String DEFAULT_REALM_NAME = RealmConfiguration.DEFAULT_REALM_NAME; private static RealmConfiguration defaultConfiguration; @@ -279,11 +281,32 @@ public static Realm getDefaultInstance() { */ public static Realm getInstance(RealmConfiguration configuration) { if (configuration == null) { - throw new IllegalArgumentException("A non-null RealmConfiguration must be provided"); + throw new IllegalArgumentException(NULL_CONFIG_MSG); } return RealmCache.createRealmOrGetFromCache(configuration, Realm.class); } + /** + * The creation of the first Realm instance per {@link RealmConfiguration} in a process can take some time as all + * initialization code need to run at that point (setting up the Realm, validating schemas and creating initial + * data). This method places the initialization work in a background thread and deliver the Realm instance + * to the caller thread asynchronously after the initialization is finished. + * + * @param configuration {@link RealmConfiguration} used to open the Realm. + * @param callback invoked to return the results. + * @throws IllegalArgumentException if a null {@link RealmConfiguration} or a null {@link Callback} is provided. + * @throws IllegalStateException if it is called from a non-Looper or {@link IntentService} thread. + * @return a {@link RealmAsyncTask} representing a cancellable task. + * @see Callback for more details. + */ + public static RealmAsyncTask getInstanceAsync(RealmConfiguration configuration, + Callback callback) { + if (configuration == null) { + throw new IllegalArgumentException(NULL_CONFIG_MSG); + } + return RealmCache.createRealmOrGetFromCacheAsync(configuration, callback, Realm.class); + } + /** * Sets the {@link io.realm.RealmConfiguration} used when calling {@link #getDefaultInstance()}. * @@ -1852,4 +1875,23 @@ interface OnError { void onError(Throwable error); } } + + /** + * {@inheritDoc} + */ + public static abstract class Callback extends InstanceCallback { + /** + * {@inheritDoc} + */ + @Override + public abstract void onSuccess(Realm realm); + + /** + * {@inheritDoc} + */ + @Override + public void onError(Throwable exception) { + super.onError(exception); + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index dc00ba5032..8b158e4ba1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -24,16 +24,24 @@ import java.util.Collection; import java.util.EnumMap; import java.util.Iterator; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import io.realm.exceptions.RealmFileException; +import io.realm.internal.Capabilities; import io.realm.internal.ColumnIndices; import io.realm.internal.ObjectServerFacade; +import io.realm.internal.RealmNotifier; import io.realm.internal.SharedRealm; import io.realm.internal.Table; +import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.android.AndroidRealmNotifier; +import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; @@ -78,6 +86,92 @@ static RealmCacheType valueOf(Class clazz) { } } + private static class CreateRealmRunnable implements Runnable { + private RealmConfiguration configuration; + private BaseRealm.InstanceCallback callback; + private Class realmClass; + private CountDownLatch canReleaseBackgroundInstanceLatch = new CountDownLatch(1); + private RealmNotifier notifier; + // The Future this runnable belongs to. + private Future future; + + CreateRealmRunnable(RealmNotifier notifier, RealmConfiguration configuration, + BaseRealm.InstanceCallback callback, Class realmClass) { + this.configuration = configuration; + this.realmClass = realmClass; + this.callback = callback; + this.notifier = notifier; + } + + public void setFuture(Future future) { + this.future = future; + } + + @Override + public void run() { + T instance = null; + try { + instance = createRealmOrGetFromCache(configuration, realmClass); + boolean results = notifier.post(new Runnable() { + @Override + public void run() { + // If the RealmAsyncTask.cancel() is called before, we just return without creating the Realm + // instance on the caller thread. + // Thread.isInterrupted() cannot be used for checking here since CountDownLatch.await() will + // will clear interrupted status. + // Using the future to check which this runnable belongs to is to ensure if it is canceled from + // the caller thread before, the callback will never be delivered. + if (future == null || future.isCancelled()) { + canReleaseBackgroundInstanceLatch.countDown(); + return; + } + T instanceToReturn = null; + Throwable throwable = null; + try { + instanceToReturn = createRealmOrGetFromCache(configuration, realmClass); + } catch (Throwable e) { + throwable = e; + } finally { + canReleaseBackgroundInstanceLatch.countDown(); + } + if (instanceToReturn != null) { + callback.onSuccess(instanceToReturn); + } else { + callback.onError(throwable); + } + } + }); + if (!results) { + canReleaseBackgroundInstanceLatch.countDown(); + } + // There is a small chance that the posted runnable cannot be executed because of the thread terminated + // before the runnable gets fetched from the event queue. + if (!canReleaseBackgroundInstanceLatch.await(2, TimeUnit.SECONDS)) { + RealmLog.warn("Timeout for creating Realm instance in foreground thread in `CreateRealmRunnable` "); + } + } catch (InterruptedException e) { + RealmLog.warn(e, "`CreateRealmRunnable` has been interrupted."); + } catch (final Throwable e) { + RealmLog.error(e, "`CreateRealmRunnable` failed."); + notifier.post(new Runnable() { + @Override + public void run() { + callback.onError(e); + } + }); + } finally { + if (instance != null) { + instance.close(); + } + } + } + } + + private static final String ASYNC_NOT_ALLOWED_MSG = + "Realm instances cannot be loaded asynchronously on a non-looper thread."; + private static final String ASYNC_CALLBACK_NULL_MSG = + "The callback cannot be null."; + // Separated references and counters for typed Realm and dynamic Realm. private final EnumMap refAndCountMap; @@ -142,6 +236,30 @@ private static RealmCache getCache(String realmPath, boolean createIfNotExist) { return cacheToReturn; } + static RealmAsyncTask createRealmOrGetFromCacheAsync( + RealmConfiguration configuration, BaseRealm.InstanceCallback callback, Class realmClass) { + RealmCache cache = getCache(configuration.getPath(), true); + return cache.doCreateRealmOrGetFromCacheAsync(configuration, callback, realmClass); + } + + private synchronized RealmAsyncTask doCreateRealmOrGetFromCacheAsync( + RealmConfiguration configuration, BaseRealm.InstanceCallback callback, Class realmClass) { + Capabilities capabilities = new AndroidCapabilities(); + capabilities.checkCanDeliverNotification(ASYNC_NOT_ALLOWED_MSG); + if (callback == null) { + throw new IllegalArgumentException(ASYNC_CALLBACK_NULL_MSG); + } + + // Always create a Realm instance in the background thread even when there are instances existing on current + // thread. This to ensure that onSuccess will always be called in the following event loop but not current one. + CreateRealmRunnable createRealmRunnable = new CreateRealmRunnable( + new AndroidRealmNotifier(null, capabilities), configuration, callback, realmClass); + Future future = BaseRealm.asyncTaskExecutor.submitTransaction(createRealmRunnable); + createRealmRunnable.setFuture(future); + + return new RealmAsyncTaskImpl(future, BaseRealm.asyncTaskExecutor); + } + /** * Creates a new Realm instance or get an existing instance for current thread. * @@ -452,7 +570,7 @@ static int getLocalThreadCount(RealmConfiguration configuration) { * @param schemaVersion requested version of the schema. * @return {@link ColumnIndices} instance for specified schema version. {@code null} if not found. */ - public static ColumnIndices findColumnIndices(ColumnIndices[] array, long schemaVersion) { + static ColumnIndices findColumnIndices(ColumnIndices[] array, long schemaVersion) { for (int i = array.length - 1; 0 <= i; i--) { final ColumnIndices candidate = array[i]; if (candidate != null && candidate.getSchemaVersion() == schemaVersion) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java index 2d5ceee378..397b170b7f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmThreadPoolExecutor.java @@ -19,7 +19,6 @@ import java.io.File; import java.io.FileFilter; import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -113,36 +112,6 @@ public Future submitTransaction(Runnable task) { return future; } - /** - * Submits a runnable for updating a query. - * - * @param task the task to submit - * @return a future representing pending completion of the task - */ - public Future submitQueryUpdate(Runnable task) { - return super.submit(new BgPriorityRunnable(task)); - } - - /** - * Submits a runnable for executing a query. - * - * @param task the task to submit - * @return a future representing pending completion of the task - */ - public Future submitQuery(Callable task) { - return super.submit(new BgPriorityCallable(task)); - } - - /** - * Submits a runnable for executing a network request. - * - * @param task the task to submit - * @return a future representing pending completion of the task - */ - public Future submitNetworkRequest(Runnable task) { - return super.submit(new BgPriorityRunnable(task)); - } - /** * Method invoked prior to executing the given Runnable to pause execution of the thread. * From 4e64cb9e6b464f1889aa367c256dbcb1c4e2c8e7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 3 May 2017 20:07:26 +0200 Subject: [PATCH 0670/2110] Rename Context to NativeContext (#4597) --- .../src/main/java/io/realm/internal/CheckedRow.java | 6 +++--- .../src/main/java/io/realm/internal/Collection.java | 2 +- .../java/io/realm/internal/CollectionChangeSet.java | 2 +- .../src/main/java/io/realm/internal/LinkView.java | 4 ++-- .../internal/{Context.java => NativeContext.java} | 6 +++--- .../java/io/realm/internal/NativeObjectReference.java | 4 ++-- .../src/main/java/io/realm/internal/SharedRealm.java | 4 ++-- .../src/main/java/io/realm/internal/Table.java | 4 ++-- .../src/main/java/io/realm/internal/TableQuery.java | 4 ++-- .../src/main/java/io/realm/internal/UncheckedRow.java | 10 +++++----- 10 files changed, 23 insertions(+), 23 deletions(-) rename realm/realm-library/src/main/java/io/realm/internal/{Context.java => NativeContext.java} (92%) diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index 0f62923c49..a3d474beaf 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -33,7 +33,7 @@ public class CheckedRow extends UncheckedRow { @SuppressWarnings({"unused", "FieldCanBeLocal"}) private UncheckedRow originalRow; - private CheckedRow(Context context, Table parent, long nativePtr) { + private CheckedRow(NativeContext context, Table parent, long nativePtr) { super(context, parent, nativePtr); } @@ -50,7 +50,7 @@ private CheckedRow(UncheckedRow row) { * @param index the index of the row. * @return an instance of Row for the table and index specified. */ - public static CheckedRow get(Context context, Table table, long index) { + public static CheckedRow get(NativeContext context, Table table, long index) { long nativeRowPointer = table.nativeGetRowPtr(table.getNativePtr(), index); return new CheckedRow(context, table, nativeRowPointer); } @@ -63,7 +63,7 @@ public static CheckedRow get(Context context, Table table, long index) { * @param index the index of the row. * @return a checked instance of {@link Row} for the {@link LinkView} and index specified. */ - public static CheckedRow get(Context context, LinkView linkView, long index) { + public static CheckedRow get(NativeContext context, LinkView linkView, long index) { long nativeRowPointer = linkView.nativeGetRow(linkView.getNativePtr(), index); return new CheckedRow(context, linkView.getTargetTable(), nativeRowPointer); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index dcdf1ce6ec..635a6e52a9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -259,7 +259,7 @@ public void set(T object) { private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final SharedRealm sharedRealm; - private final Context context; + private final NativeContext context; private final Table table; private boolean loaded; private boolean isSnapshot = false; diff --git a/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java index c4958e92e9..9064f8fcde 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CollectionChangeSet.java @@ -44,7 +44,7 @@ public class CollectionChangeSet implements OrderedCollectionChangeSet, NativeOb public CollectionChangeSet(long nativePtr) { this.nativePtr = nativePtr; - Context.dummyContext.addReference(this); + NativeContext.dummyContext.addReference(this); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java index 03af9ff392..2562bf88dc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/LinkView.java +++ b/realm/realm-library/src/main/java/io/realm/internal/LinkView.java @@ -24,13 +24,13 @@ */ public class LinkView implements NativeObject { - private final Context context; + private final NativeContext context; final Table parent; final long columnIndexInParent; private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - public LinkView(Context context, Table parent, long columnIndexInParent, long nativeLinkViewPtr) { + public LinkView(NativeContext context, Table parent, long columnIndexInParent, long nativeLinkViewPtr) { this.context = context; this.parent = parent; this.columnIndexInParent = columnIndexInParent; diff --git a/realm/realm-library/src/main/java/io/realm/internal/Context.java b/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java similarity index 92% rename from realm/realm-library/src/main/java/io/realm/internal/Context.java rename to realm/realm-library/src/main/java/io/realm/internal/NativeContext.java index 253396b037..f559678b76 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Context.java +++ b/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java @@ -21,16 +21,16 @@ // Currently we free native objects in two threads, the SharedGroup is freed in the caller thread, others are freed in // RealmFinalizingDaemon thread. And the destruction in both threads are locked by the corresponding context. -// The purpose of locking on Context is: +// The purpose of locking on NativeContext is: // Destruction of SharedGroup (and hence Group and Table) is currently not thread-safe with respect to destruction of // other accessors, you have to ensure mutual exclusion. This is also illustrated by the use of locks in the test // test_destructor_thread_safety.cpp. Explicit call of SharedGroup::close() or Table::detach() is also not thread-safe // with respect to destruction of other accessors. -public class Context { +public class NativeContext { private final static ReferenceQueue referenceQueue = new ReferenceQueue(); private final static Thread finalizingThread = new Thread(new FinalizerRunnable(referenceQueue)); // Dummy context which will be used by native objects which's destructors are always thread safe. - final static Context dummyContext = new Context(); + final static NativeContext dummyContext = new NativeContext(); static { finalizingThread.setName("RealmFinalizingDaemon"); diff --git a/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java b/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java index dce71e3fdf..28a6493868 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java +++ b/realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java @@ -63,13 +63,13 @@ synchronized void remove(NativeObjectReference ref) { private final long nativePtr; // The pointer to the native finalize function private final long nativeFinalizerPtr; - private final Context context; + private final NativeContext context; private NativeObjectReference prev; private NativeObjectReference next; private static ReferencePool referencePool = new ReferencePool(); - NativeObjectReference(Context context, + NativeObjectReference(NativeContext context, NativeObject referent, ReferenceQueue referenceQueue) { super(referent, referenceQueue); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index c5e78eb1d8..21b041baec 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -177,7 +177,7 @@ public interface SchemaVersionListener { private final RealmConfiguration configuration; final private long nativePtr; - final Context context; + final NativeContext context; private long lastSchemaVersion; private final SchemaVersionListener schemaChangeListener; @@ -193,7 +193,7 @@ private SharedRealm(long nativeConfigPtr, this.capabilities = capabilities; this.realmNotifier = realmNotifier; this.schemaChangeListener = schemaVersionListener; - context = new Context(); + context = new NativeContext(); context.addReference(this); this.lastSchemaVersion = schemaVersionListener == null ? -1L : getSchemaVersion(); nativeSetAutoRefresh(nativePtr, capabilities.canDeliverNotification()); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 20430bf48b..67e744ca3e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -60,7 +60,7 @@ enum PivotType { private long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - final Context context; + final NativeContext context; private final SharedRealm sharedRealm; private long cachedPrimaryKeyColumnIndex = NO_MATCH; @@ -69,7 +69,7 @@ enum PivotType { * allowed only for empty tables. It creates a native reference of the object and keeps a reference to it. */ public Table() { - this.context = new Context(); + this.context = new NativeContext(); // Native methods work will be initialized here. Generated classes will // have nothing to do with the native functions. Generated Java Table // classes will work as a wrapper on top of table. diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 6021c88720..8d0be81e47 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -28,7 +28,7 @@ public class TableQuery implements NativeObject { protected long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); protected final Table table; - private final Context context; + private final NativeContext context; // All actions (find(), findAll(), sum(), etc.) must call validateQuery() before performing // the actual action. The other methods must set queryValidated to false in order to enforce @@ -36,7 +36,7 @@ public class TableQuery implements NativeObject { private boolean queryValidated = true; // TODO: Can we protect this? - public TableQuery(Context context, Table table, long nativeQueryPtr) { + public TableQuery(NativeContext context, Table table, long nativeQueryPtr) { if (DEBUG) { System.err.println("++++++ new TableQuery, ptr= " + nativeQueryPtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 82c0821f30..48f2e77233 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -33,11 +33,11 @@ public class UncheckedRow implements NativeObject, Row { private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - private final Context context; // This is only kept because for now it's needed by the constructor of LinkView + private final NativeContext context; // This is only kept because for now it's needed by the constructor of LinkView private final Table parent; private final long nativePtr; - UncheckedRow(Context context, Table parent, long nativePtr) { + UncheckedRow(NativeContext context, Table parent, long nativePtr) { this.context = context; this.parent = parent; this.nativePtr = nativePtr; @@ -71,7 +71,7 @@ public long getNativeFinalizerPtr() { * @param index the index of the row. * @return an instance of Row for the table and index specified. */ - static UncheckedRow getByRowIndex(Context context, Table table, long index) { + static UncheckedRow getByRowIndex(NativeContext context, Table table, long index) { long nativeRowPointer = table.nativeGetRowPtr(table.getNativePtr(), index); return new UncheckedRow(context, table, nativeRowPointer); } @@ -84,7 +84,7 @@ static UncheckedRow getByRowIndex(Context context, Table table, long index) { * @param nativeRowPointer pointer of a row. * @return an instance of Row for the table and row specified. */ - static UncheckedRow getByRowPointer(Context context, Table table, long nativeRowPointer) { + static UncheckedRow getByRowPointer(NativeContext context, Table table, long nativeRowPointer) { return new UncheckedRow(context, table, nativeRowPointer); } @@ -96,7 +96,7 @@ static UncheckedRow getByRowPointer(Context context, Table table, long nativeRow * @param index the index of the row. * @return an instance of Row for the LinkView and index specified. */ - static UncheckedRow getByRowIndex(Context context, LinkView linkView, long index) { + static UncheckedRow getByRowIndex(NativeContext context, LinkView linkView, long index) { long nativeRowPointer = linkView.nativeGetRow(linkView.getNativePtr(), index); return new UncheckedRow(context, linkView.getTargetTable(), nativeRowPointer); } From 4469f7aecfea87db5a9f4e9effd45f7c0309225a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 4 May 2017 15:29:46 +0800 Subject: [PATCH 0671/2110] Remove onSuccess callback before calling it (#4596) Otherwise it will cause infinite recursion if a transaction is committed in the callback. Fix #4595 --- CHANGELOG.md | 1 + .../java/io/realm/RealmAsyncQueryTests.java | 54 +++++++++++++++++++ .../java/io/realm/internal/RealmNotifier.java | 12 +++-- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1d21e7b2..38ff94224f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Added missing row validation check in certain cases on invalidated/deleted objects (#4540). * Initializing Realm is now more resilient if `Context.getFilesDir()` isn't working correctly (#4493). * `OrderedRealmCollectionSnapshot.get()` returned a wrong object (#4554). +* `onSuccess` callback got triggered infinitely if a synced transaction was committed in the async transaction's `onSuccess` callback (#4594). ## 3.1.3 (2017-04-20) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index d04ed040bf..6bd6945524 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -406,6 +406,60 @@ public void onError(Throwable error) { } } + // https://github.com/realm/realm-java/issues/4595#issuecomment-298830411 + // onSuccess might commit another transaction which will call didChange. So before calling async transaction + // callbacks, the callback should be cleared. + @Test + @RunTestInLooperThread + public void executeTransactionAsync_callbacksShouldBeClearedBeforeCalling() { + final AtomicInteger callbackCounter = new AtomicInteger(0); + final Realm foregroundRealm = looperThread.getRealm(); + + // To reproduce the issue, the posted callback needs to arrived before the Object Store did_change called. + // We just disable the auto refresh here then the did_change won't be called. + foregroundRealm.setAutoRefresh(false); + foregroundRealm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class); + } + }, new Realm.Transaction.OnSuccess() { + @Override + public void onSuccess() { + // This will be called first and only once + assertEquals(0, callbackCounter.getAndIncrement()); + + // This transaction should never trigger the onSuccess. + foregroundRealm.beginTransaction(); + foregroundRealm.createObject(AllTypes.class); + foregroundRealm.commitTransaction(); + } + }); + + foregroundRealm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class); + // Delay to post this to ensure the async transaction posted callback will arrive first. + looperThread.postRunnableDelayed(new Runnable() { + @Override + public void run() { + // Manually call refresh, so the did_change will be triggered. + foregroundRealm.sharedRealm.refresh(); + foregroundRealm.setAutoRefresh(true); + } + }, 50); + } + }, new Realm.Transaction.OnSuccess() { + @Override + public void onSuccess() { + // This will be called 2nd and only once + assertEquals(1, callbackCounter.getAndIncrement()); + looperThread.testComplete(); + } + }); + } + // ************************************ // *** promises based async queries *** // ************************************ diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 3493dd4707..279c559606 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -97,10 +97,16 @@ protected RealmNotifier(SharedRealm sharedRealm) { // called from java_binding_context.cpp void didChange() { realmObserverPairs.foreach(onChangeCallBack); - for (Runnable runnable : transactionCallbacks) { - runnable.run(); + + if (!transactionCallbacks.isEmpty()) { + // The callback list needs to be cleared before calling to avoid synchronized transactions in the callback + // triggers it recursively. + List callbacks = transactionCallbacks; + transactionCallbacks = new ArrayList(); + for (Runnable runnable : callbacks) { + runnable.run(); + } } - transactionCallbacks.clear(); } // Called from JavaBindingContext::before_notify. From ce096c357d68a691de9b077d11c7896c66006cd6 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 4 May 2017 17:12:10 +0900 Subject: [PATCH 0672/2110] Introduce DynamicRealmObject#linkingObjects(String srcClassName, String srcFieldName) (#4492) --- CHANGELOG.md | 1 + .../processor/RealmProxyClassGenerator.java | 5 +- .../io/realm/AllTypesRealmProxy.java | 3 +- .../io/realm/LinkingObjectsDynamicTests.java | 436 ++++++++++++++++++ .../realm/LinkingObjectsUnmanagedTests.java | 29 +- .../io/realm/entities/BacklinksSource.java | 12 + .../io/realm/entities/BacklinksTarget.java | 2 +- .../java/io/realm/DynamicRealmObject.java | 48 +- .../src/main/java/io/realm/RealmResults.java | 16 +- .../io/realm/StandardRealmObjectSchema.java | 2 +- .../java/io/realm/StandardRealmSchema.java | 2 +- 11 files changed, 518 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d564599bf8..563f2e3c1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore` annotation (#4279). * Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). * Added `Realm.getInstanceAsync()` and `DynamicRealm.getInstanceAsync()` (#2299). +* Added `DynamicRealmObject#linkingObjects(String,String) to support linking objects on `DynamicRealm` (#4492). ### Bug Fixes diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 44772ce258..6cb95cc33d 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -78,6 +78,9 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("io.realm.internal.Row"); imports.add("io.realm.internal.Table"); imports.add("io.realm.internal.SharedRealm"); + if (!metadata.getBacklinkFields().isEmpty()) { + imports.add("io.realm.internal.UncheckedRow"); + } imports.add("io.realm.internal.LinkView"); imports.add("io.realm.internal.android.JsonUtils"); imports.add("io.realm.log.RealmLog"); @@ -571,7 +574,7 @@ private void emitBacklinkFieldAccessors(JavaWriter writer) throws IOException { .emitStatement("realm.checkIfValid()") .emitStatement("proxyState.getRow$realm().checkIfAttached()") .beginControlFlow("if (" + cacheFieldName + " == null)") - .emitStatement(cacheFieldName + " = RealmResults.createBacklinkResults(realm, proxyState.getRow$realm(), %s.class, \"%s\")", + .emitStatement(cacheFieldName + " = RealmResults.createBacklinkResults((Realm) realm, (UncheckedRow) proxyState.getRow$realm(), %s.class, \"%s\")", backlink.getSourceClass(), backlink.getSourceField()) .endControlFlow() .emitStatement("return " + cacheFieldName) diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 8eeb68202c..6a101e7c84 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -14,6 +14,7 @@ import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; +import io.realm.internal.UncheckedRow; import io.realm.internal.android.JsonUtils; import io.realm.log.RealmLog; import java.io.IOException; @@ -399,7 +400,7 @@ public final AllTypesColumnInfo clone() { realm.checkIfValid(); proxyState.getRow$realm().checkIfAttached(); if (parentObjectsBacklinks == null) { - parentObjectsBacklinks = RealmResults.createBacklinkResults(realm, proxyState.getRow$realm(), some.test.AllTypes.class, "columnObject"); + parentObjectsBacklinks = RealmResults.createBacklinkResults((Realm) realm, (UncheckedRow) proxyState.getRow$realm(), some.test.AllTypes.class, "columnObject"); } return parentObjectsBacklinks; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index 766d85799e..c11178f0eb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -22,22 +22,46 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.util.Locale; + +import io.realm.entities.AllJavaTypes; +import io.realm.entities.BacklinksSource; +import io.realm.entities.BacklinksTarget; +import io.realm.entities.Cat; +import io.realm.entities.Owner; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + + @RunWith(AndroidJUnit4.class) public class LinkingObjectsDynamicTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private Realm realm; + private DynamicRealm dynamicRealm; @Before public void setUp() { RealmConfiguration realmConfig = configFactory.createConfiguration(); realm = Realm.getInstance(realmConfig); + dynamicRealm = DynamicRealm.getInstance(realmConfig); } @After @@ -45,6 +69,418 @@ public void tearDown() { if (realm != null) { realm.close(); } + + if (dynamicRealm != null) { + dynamicRealm.close(); + } + } + + @Test + public void linkingObjects_classIsNull() throws Exception { + dynamicRealm.beginTransaction(); + final DynamicRealmObject object = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1L); + dynamicRealm.commitTransaction(); + + try { + object.linkingObjects(null, AllJavaTypes.FIELD_INT); + fail(); + } catch (IllegalArgumentException expected) { + assertEquals(StandardRealmSchema.EMPTY_STRING_MSG, expected.getMessage()); + } + } + + @Test + public void linkingObjects_fieldIsNull() throws Exception { + dynamicRealm.beginTransaction(); + final DynamicRealmObject object = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1L); + dynamicRealm.commitTransaction(); + + try { + object.linkingObjects(AllJavaTypes.CLASS_NAME, null); + fail(); + } catch (IllegalArgumentException expected) { + assertEquals("Non-null 'srcFieldName' required.", expected.getMessage()); + } + } + + @Test + public void linkingObjects_nonExistentClass() { + dynamicRealm.beginTransaction(); + final DynamicRealmObject object = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1L); + dynamicRealm.commitTransaction(); + + try { + object.linkingObjects("ThisClassDoesNotExist", AllJavaTypes.FIELD_INT); + fail(); + } catch (IllegalArgumentException expected) { + assertTrue(expected.getMessage().startsWith("Class not found")); + } + } + + @Test + public void linkingObjects_nonExistentField() { + dynamicRealm.beginTransaction(); + final DynamicRealmObject object = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1L); + dynamicRealm.commitTransaction(); + + try { + object.linkingObjects(AllJavaTypes.CLASS_NAME, "fieldNotExist"); + fail(); + } catch (IllegalArgumentException expected) { + final String expectedMessage = String.format(Locale.ENGLISH, + "Field name '%s' does not exist on schema for '%s'", + "fieldNotExist", AllJavaTypes.CLASS_NAME); + assertEquals(expectedMessage, expected.getMessage()); + } + } + + @Test + public void linkingObjects_ignoredExistentField() { + dynamicRealm.beginTransaction(); + final DynamicRealmObject object = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1L); + dynamicRealm.commitTransaction(); + + try { + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_IGNORED); + fail(); + } catch (IllegalArgumentException expected) { + final String expectedMessage = String.format(Locale.ENGLISH, + "Field name '%s' does not exist on schema for '%s'", + AllJavaTypes.FIELD_IGNORED, AllJavaTypes.CLASS_NAME); + assertEquals(expectedMessage, expected.getMessage()); + } + } + + @Test + public void linkingObjects_linkQueryNotSupported() throws Exception { + dynamicRealm.beginTransaction(); + final DynamicRealmObject object = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1L); + dynamicRealm.commitTransaction(); + + try { + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_OBJECT); + fail(); + } catch (IllegalArgumentException expected) { + assertEquals(DynamicRealmObject.MSG_LINK_QUERY_NOT_SUPPORTED, expected.getMessage()); + } + } + + @Test + public void linkingObjects_invalidFieldType() { + dynamicRealm.beginTransaction(); + final DynamicRealmObject object = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1L); + dynamicRealm.commitTransaction(); + + for (RealmFieldType fieldType : RealmFieldType.values()) { + try { + switch (fieldType) { + // skip valid types + case OBJECT: // fall-through + case LIST: + continue; + // skip unsupported types + case UNSUPPORTED_TABLE: // fall-through + case UNSUPPORTED_MIXED: // fall-through + case UNSUPPORTED_DATE: + continue; + case INTEGER: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_INT); + break; + case BOOLEAN: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_BOOLEAN); + break; + case STRING: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_STRING); + break; + case BINARY: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_BINARY); + break; + case DATE: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DATE); + break; + case FLOAT: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_FLOAT); + break; + case DOUBLE: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE); + break; + default: + fail("unknown type: " + fieldType); + break; + } + fail(); + } catch (IllegalArgumentException expected) { + assertTrue(expected.getMessage().startsWith("Unexpected field type")); + } + } + } + + @Test + public void linkingObjects_linkedByOBJECT_backlinksDefinedInModel() { + final int numSourceOfTarget1 = 3; + final int numSourceOfTarget2 = 2; + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final BacklinksTarget target1 = realm.createObject(BacklinksTarget.class); + target1.setId(1); + + // create sources of target1 + for (int i = 0; i < numSourceOfTarget1; i++) { + final BacklinksSource source = realm.createObject(BacklinksSource.class); + source.setName("source" + i + "_target1"); + source.setChild(target1); + } + + final BacklinksTarget target2 = realm.createObject(BacklinksTarget.class); + target2.setId(2); + + // create sources of target2 + for (int i = 0; i < numSourceOfTarget2; i++) { + final BacklinksSource source = realm.createObject(BacklinksSource.class); + source.setName("source" + i + "_target2"); + source.setChild(target2); + } + + // target3 has no owner + final BacklinksTarget target3 = realm.createObject(BacklinksTarget.class); + target3.setId(3); + } + }); + + final DynamicRealmObject target1 = dynamicRealm.where(BacklinksTarget.CLASS_NAME).equalTo(BacklinksTarget.FIELD_ID, 1).findFirst(); + final RealmResults target1Sources = target1.linkingObjects(BacklinksSource.CLASS_NAME, BacklinksSource.FIELD_CHILD); + assertNotNull(target1Sources); + assertEquals(numSourceOfTarget1, target1Sources.size()); + for (DynamicRealmObject target1Source : target1Sources) { + assertEquals(BacklinksSource.CLASS_NAME, target1Source.getType()); + assertTrue(target1Source.getString(BacklinksSource.FIELD_NAME).endsWith("_target1")); + assertEquals(target1, target1Source.getObject(BacklinksSource.FIELD_CHILD)); + } + + final DynamicRealmObject target2 = dynamicRealm.where(BacklinksTarget.CLASS_NAME).equalTo(BacklinksTarget.FIELD_ID, 2).findFirst(); + final RealmResults target2Sources = target2.linkingObjects(BacklinksSource.CLASS_NAME, BacklinksSource.FIELD_CHILD); + assertNotNull(target2Sources); + assertEquals(numSourceOfTarget2, target2Sources.size()); + for (DynamicRealmObject target2Source : target2Sources) { + assertEquals(BacklinksSource.CLASS_NAME, target2Source.getType()); + assertTrue(target2Source.getString(BacklinksSource.FIELD_NAME).endsWith("_target2")); + assertEquals(target2, target2Source.getObject(BacklinksSource.FIELD_CHILD)); + } + + final DynamicRealmObject target3 = dynamicRealm.where(BacklinksTarget.CLASS_NAME).equalTo(BacklinksTarget.FIELD_ID, 3).findFirst(); + final RealmResults target3Sources = target3.linkingObjects(BacklinksSource.CLASS_NAME, BacklinksSource.FIELD_CHILD); + assertNotNull(target3Sources); + assertTrue(target3Sources.isEmpty()); + } + + @Test + public void linkingObjects_linkedByOBJECT_backlinksNotDefinedInModel() { + final int numOwnersOfCat1 = 3; + final int numOwnersOfCat2 = 2; + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final Cat cat1 = realm.createObject(Cat.class); + cat1.setName("cat1"); + + // create owners of cat1 + for (int i = 0; i < numOwnersOfCat1; i++) { + final Owner owner = realm.createObject(Owner.class); + owner.setName("owner" + i + "_cat1"); + owner.setCat(cat1); + } + + final Cat cat2 = realm.createObject(Cat.class); + cat2.setName("cat2"); + + // create owners of cat2 + for (int i = 0; i < numOwnersOfCat2; i++) { + final Owner owner = realm.createObject(Owner.class); + owner.setName("owner" + i + "_cat2"); + owner.setCat(cat2); + } + + // cat3 has no owner + final Cat cat3 = realm.createObject(Cat.class); + cat3.setName("cat3"); + } + }); + + final DynamicRealmObject cat1 = dynamicRealm.where(Cat.CLASS_NAME).equalTo(Cat.FIELD_NAME, "cat1").findFirst(); + final RealmResults cat1Owners = cat1.linkingObjects(Owner.CLASS_NAME, Owner.FIELD_CAT); + assertNotNull(cat1Owners); + assertEquals(numOwnersOfCat1, cat1Owners.size()); + for (DynamicRealmObject cat1Owner : cat1Owners) { + assertEquals(Owner.CLASS_NAME, cat1Owner.getType()); + assertTrue(cat1Owner.getString(Owner.FIELD_NAME).endsWith("_cat1")); + assertEquals(cat1, cat1Owner.getObject(Owner.FIELD_CAT)); + } + + final DynamicRealmObject cat2 = dynamicRealm.where(Cat.CLASS_NAME).equalTo(Cat.FIELD_NAME, "cat2").findFirst(); + final RealmResults cat2Owners = cat2.linkingObjects(Owner.CLASS_NAME, Owner.FIELD_CAT); + assertNotNull(cat2Owners); + assertEquals(numOwnersOfCat2, cat2Owners.size()); + for (DynamicRealmObject cat2Owner : cat2Owners) { + assertEquals(Owner.CLASS_NAME, cat2Owner.getType()); + assertTrue(cat2Owner.getString(Owner.FIELD_NAME).endsWith("_cat2")); + assertEquals(cat2, cat2Owner.getObject(Owner.FIELD_CAT)); + } + + final DynamicRealmObject cat3 = dynamicRealm.where(Cat.CLASS_NAME).equalTo(Cat.FIELD_NAME, "cat3").findFirst(); + final RealmResults cat3Owners = cat3.linkingObjects(Owner.CLASS_NAME, Owner.FIELD_CAT); + assertNotNull(cat3Owners); + assertTrue(cat3Owners.isEmpty()); + } + + @Test + public void linkingObjects_linkedByLIST() { + // source100 source200 source300 + // // \\ \\ || // + // target1 target2 target2 + // + // // = list ref + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final AllJavaTypes target1 = realm.createObject(AllJavaTypes.class, 1L); + final AllJavaTypes target2 = realm.createObject(AllJavaTypes.class, 2L); + final AllJavaTypes target3 = realm.createObject(AllJavaTypes.class, 3L); + + final AllJavaTypes source100 = realm.createObject(AllJavaTypes.class, 100L); + source100.getFieldList().add(target1); + source100.getFieldList().add(target2); + + // list contains three target2s + final AllJavaTypes source200 = realm.createObject(AllJavaTypes.class, 200L); + source200.getFieldList().add(target2); + source200.getFieldList().add(target2); + source200.getFieldList().add(target2); + } + }); + + final DynamicRealmObject target1 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 1L).findFirst(); + final DynamicRealmObject target2 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 2L).findFirst(); + final DynamicRealmObject target3 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 3L).findFirst(); + + // tests sources of target1 + final RealmResults target1Sources = target1.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_LIST); + assertNotNull(target1Sources); + assertEquals(1, target1Sources.size()); + assertEquals(AllJavaTypes.CLASS_NAME, target1Sources.first().getType()); + assertEquals(100L, target1Sources.first().getLong(AllJavaTypes.FIELD_ID)); + assertTrue(target1Sources.first().getList(AllJavaTypes.FIELD_LIST).contains(target1)); + assertTrue(target1Sources.first().getList(AllJavaTypes.FIELD_LIST).contains(target2)); + assertFalse(target1Sources.first().getList(AllJavaTypes.FIELD_LIST).contains(target3)); + + // tests sources of target2 + final RealmResults target2Sources = target2.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_LIST); + assertNotNull(target2Sources); + // if a source (in this test, source200) contains multiple references to a target in one RealmList, those must not be aggregated. + assertEquals(4, target2Sources.size()); + boolean source100Found = false; + boolean source200Found = false; + for (DynamicRealmObject target2Source : target2Sources) { + final long idValue = target2Source.getLong(AllJavaTypes.FIELD_ID); + if (idValue == 100L) { + source100Found = true; + } else if (idValue == 200L) { + source200Found = true; + } else { + fail("unexpected id value: " + idValue); + } + + assertEquals(AllJavaTypes.CLASS_NAME, target2Source.getType()); + assertTrue(target2Source.getList(AllJavaTypes.FIELD_LIST).contains(target2)); + assertFalse(target2Source.getList(AllJavaTypes.FIELD_LIST).contains(target3)); + } + assertTrue(source100Found); + assertTrue(source200Found); + + // tests sources of target3 + final RealmResults target3Sources = target3.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_LIST); + assertNotNull(target3Sources); + assertTrue(target3Sources.isEmpty()); + + dynamicRealm.executeTransaction(new DynamicRealm.Transaction() { + @Override + public void execute(DynamicRealm realm) { + final DynamicRealmObject source200 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 200L).findFirst(); + // remove last reference in the list + source200.getList(AllJavaTypes.FIELD_LIST).remove(2); + } + }); + + // backlinks are also updated + assertEquals(3, target2Sources.size()); + } + + @Test + @RunTestInLooperThread + public void linkingObjects_IllegalStateException_ifNotYetLoaded() { + final Realm realm = looperThread.getRealm(); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final BacklinksTarget target1 = realm.createObject(BacklinksTarget.class); + target1.setId(1); + + final BacklinksSource source = realm.createObject(BacklinksSource.class); + source.setChild(target1); + } + }); + + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + try { + final DynamicRealmObject targetAsync = dynamicRealm.where(BacklinksTarget.CLASS_NAME) + .equalTo(BacklinksTarget.FIELD_ID, 1L).findFirstAsync(); + // precondition + assertFalse(targetAsync.isLoaded()); + + thrown.expect(IllegalStateException.class); + targetAsync.linkingObjects(BacklinksSource.CLASS_NAME, BacklinksSource.FIELD_CHILD); + } finally { + dynamicRealm.close(); + } + } + + @Test + @RunTestInLooperThread + public void linkingObjects_IllegalStateException_ifDeleted() { + final Realm realm = looperThread.getRealm(); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final BacklinksTarget target1 = realm.createObject(BacklinksTarget.class); + target1.setId(1); + + final BacklinksSource source = realm.createObject(BacklinksSource.class); + source.setChild(target1); + } + }); + + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + try { + final DynamicRealmObject target = dynamicRealm.where(BacklinksTarget.CLASS_NAME) + .equalTo(BacklinksTarget.FIELD_ID, 1L).findFirst(); + + dynamicRealm.executeTransaction(new DynamicRealm.Transaction() { + @Override + public void execute(DynamicRealm realm) { + target.deleteFromRealm(); + } + }); + + // precondition + assertFalse(target.isValid()); + + thrown.expect(IllegalStateException.class); + target.linkingObjects(BacklinksSource.CLASS_NAME, BacklinksSource.FIELD_CHILD); + } finally { + dynamicRealm.close(); + } } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java index 94fc826d44..c89c7666f2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java @@ -65,45 +65,22 @@ public void copyFromRealm() { assertEquals(parent, child.getObjectParents().first()); AllJavaTypes unmanagedChild = realm.copyFromRealm(child); - assertEquals(new AllJavaTypes().getObjectParents(), unmanagedChild.getObjectParents()); - } - - // When managed, an object's backlinks fields get live. - @Test - public void copyToRealm() { - AllJavaTypes unmanagedChild = new AllJavaTypes(1); - - realm.beginTransaction(); - AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); - realm.commitTransaction(); - assertEquals(new AllJavaTypes().getObjectParents(), unmanagedChild.getObjectParents()); - - realm.beginTransaction(); - AllJavaTypes child = realm.copyToRealm(unmanagedChild); - parent.setFieldObject(child); - realm.commitTransaction(); - - RealmResults parents = child.getObjectParents(); - assertNotNull(parents); - assertEquals(1, parents.size()); - assertEquals(parent, parents.first()); + assertNull(unmanagedChild.getObjectParents()); } // Test round-trip @Test public void copyToAndFromRealm() { AllJavaTypes unmanagedChild = new AllJavaTypes(1); + assertNull(unmanagedChild.getObjectParents()); realm.beginTransaction(); AllJavaTypes parent = realm.createObject(AllJavaTypes.class, 2); - realm.commitTransaction(); - assertEquals(new AllJavaTypes().getObjectParents(), unmanagedChild.getObjectParents()); - - realm.beginTransaction(); AllJavaTypes child = realm.copyToRealm(unmanagedChild); parent.setFieldObject(child); realm.commitTransaction(); + // When managed, an object's backlinks fields get live. RealmResults parents = child.getObjectParents(); assertNotNull(parents); assertEquals(1, parents.size()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java index ece9bca332..1fecbb552d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java @@ -19,10 +19,22 @@ public class BacklinksSource extends RealmObject { public static final String CLASS_NAME = "BacklinksSource"; + public static final String FIELD_NAME = "name"; public static final String FIELD_CHILD = "child"; + + private String name; + private BacklinksTarget child; + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public BacklinksTarget getChild() { return child; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java index da3ee95be2..ca20d5d42c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java @@ -26,7 +26,7 @@ public class BacklinksTarget extends RealmObject { private int id; - @LinkingObjects("child") + @LinkingObjects(BacklinksSource.FIELD_CHILD) private final RealmResults parents = null; public int getId() { diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 0d1d518201..e8dcd1b15a 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -35,6 +35,7 @@ */ @SuppressWarnings("WeakerAccess") public class DynamicRealmObject extends RealmObject implements RealmObjectProxy { + static final String MSG_LINK_QUERY_NOT_SUPPORTED = "Queries across relationships are not supported"; private final ProxyState proxyState = new ProxyState(this); @@ -69,7 +70,7 @@ public DynamicRealmObject(RealmModel obj) { proxyState.setConstructionFinished(); } - // row must not be an instance of UncheckedRow + // row must be an instance of CheckedRow or InvalidRow DynamicRealmObject(BaseRealm realm, Row row) { proxyState.setRealm$realm(realm); proxyState.setRow$realm(row); @@ -925,6 +926,51 @@ public String toString() { return sb.toString(); } + /** + * Returns {@link RealmResults} containing all {@code srcClassName} class objects that have a relationship + * to this object from {@code srcFieldName} field. + *

                      + * An entry is added for each reference, e.g. if the same reference is in a list multiple times, + * the src object will show up here multiple times. + * + * @param srcClassName name of the class returned objects belong to. + * @param srcFieldName name of the field in the source class that holds a reference to this object. + * Field type must be either {@code io.realm.RealmFieldType.OBJECT} or {@code io.realm.RealmFieldType.LIST}. + * @return the result. + * @throws IllegalArgumentException if the {@code srcClassName} is {@code null} or does not exist, + * the {@code srcFieldName} is {@code null} or does not exist, + * type of the source field is not supported. + */ + public RealmResults linkingObjects(String srcClassName, String srcFieldName) { + final DynamicRealm realm = (DynamicRealm) proxyState.getRealm$realm(); + realm.checkIfValid(); + proxyState.getRow$realm().checkIfAttached(); + + final RealmSchema schema = realm.getSchema(); + final RealmObjectSchema realmObjectSchema = schema.get(srcClassName); + if (realmObjectSchema == null) { + throw new IllegalArgumentException("Class not found: " + srcClassName); + } + + if (srcFieldName == null) { + throw new IllegalArgumentException("Non-null 'srcFieldName' required."); + } + if (srcFieldName.contains(".")) { + throw new IllegalArgumentException(MSG_LINK_QUERY_NOT_SUPPORTED); + } + + final RealmFieldType fieldType = realmObjectSchema.getFieldType(srcFieldName); // throws IAE if not found + if (fieldType != RealmFieldType.OBJECT && fieldType != RealmFieldType.LIST) { + throw new IllegalArgumentException(String.format(Locale.ENGLISH, + "Unexpected field type: %1$s. Field type should be either %2$s.%3$s or %2$s.%4$s.", + fieldType.name(), + RealmFieldType.class.getSimpleName(), + RealmFieldType.OBJECT.name(), RealmFieldType.LIST.name())); + } + + return RealmResults.createBacklinkResults(realm, (CheckedRow) proxyState.getRow$realm(), realmObjectSchema.getTable(), srcFieldName); + } + @Override public void realm$injectObjectContext() { // nothing to do for DynamicRealmObject diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 60c8c3b1fa..6eae995442 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -19,6 +19,7 @@ import android.os.Looper; +import io.realm.internal.CheckedRow; import io.realm.internal.Collection; import io.realm.internal.Row; import io.realm.internal.SortDescriptor; @@ -55,18 +56,21 @@ * @see Realm#executeTransaction(Realm.Transaction) */ public class RealmResults extends OrderedRealmCollectionImpl { - static RealmResults createBacklinkResults(BaseRealm realm, Row row, Class srcTableType, String srcFieldName) { - if (!(row instanceof UncheckedRow)) { - throw new IllegalArgumentException("Row is " + row.getClass()); - } - UncheckedRow uncheckedRow = (UncheckedRow) row; + static RealmResults createBacklinkResults(Realm realm, UncheckedRow row, Class srcTableType, String srcFieldName) { Table srcTable = realm.getSchema().getTable(srcTableType); return new RealmResults( realm, - Collection.createBacklinksCollection(realm.sharedRealm, uncheckedRow, srcTable, srcFieldName), + Collection.createBacklinksCollection(realm.sharedRealm, row, srcTable, srcFieldName), srcTableType); } + static RealmResults createBacklinkResults(DynamicRealm realm, CheckedRow row, Table srcTable, String srcFieldName) { + return new RealmResults<>( + realm, + Collection.createBacklinksCollection(realm.sharedRealm, row, srcTable, srcFieldName), + Table.tableNameToClassName(srcTable.getName())); + } + RealmResults(BaseRealm realm, Collection collection, Class clazz) { super(realm, collection, clazz); diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java index d46fb453ea..3591817aed 100644 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java @@ -736,7 +736,7 @@ private long getColumnIndex(String fieldName) { long columnIndex = table.getColumnIndex(fieldName); if (columnIndex == -1) { throw new IllegalArgumentException( - String.format("Field name '%s' does not exist on schema for '%s", + String.format("Field name '%s' does not exist on schema for '%s'", fieldName, getClassName() )); } diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java index bcec1169fb..280309bb0d 100644 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java @@ -36,7 +36,7 @@ class StandardRealmSchema extends RealmSchema { static final String TABLE_PREFIX = Table.TABLE_PREFIX; - private static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; + static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; // Caches Dynamic Class objects given as Strings to Realm Tables private final Map dynamicClassToTable = new HashMap<>(); From 0cfe94bec64a0e0bfc1f9de176dcaf81d5909e4e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 4 May 2017 17:26:49 +0800 Subject: [PATCH 0673/2110] Date placeholder is needed for release script Otherwise the release script won't work. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38ff94224f..fe6130d699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.1.4 +## 3.1.4 (YYYY-MM-DD) ## Bug fixes From 9bf79f4b6f623c49b31f9d0cf8ce7ab9be045c69 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 4 May 2017 17:29:58 +0800 Subject: [PATCH 0674/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe6130d699..2d12cd03c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.1.4 (YYYY-MM-DD) +## 3.1.4 (2017-05-04) ## Bug fixes From ee10d19c991ec758a548ae34f75116f50641948a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 4 May 2017 17:29:59 +0800 Subject: [PATCH 0675/2110] Release v3.1.4 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index d66c3337d0..b532f3dc33 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.4-SNAPSHOT \ No newline at end of file +3.1.4 \ No newline at end of file From 52d6b7d5a3b3ab9d5b980b4aabf50485488d3ffa Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 4 May 2017 17:29:59 +0800 Subject: [PATCH 0676/2110] Prepare next release v3.1.5-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b532f3dc33..2110940c21 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.4 \ No newline at end of file +3.1.5-SNAPSHOT \ No newline at end of file From 291fe409eb87fb7a6373657e8a23eb0bc2d4efa8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 4 May 2017 18:22:54 +0800 Subject: [PATCH 0677/2110] Fix release script for java doc release --- tools/release.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/release.sh b/tools/release.sh index 33a01831b7..e355ee2744 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -213,8 +213,9 @@ publish_javadoc() { esac done git clean -xfd ./source/en/docs/java/ + bundle update bundle exec rake generate:java_docs[$VERSION] - cp -R "${REALM_JAVA_PATH}/realm/realm-library/build/docs/javadoc/*" ./source/en/docs/java/latest/api/ + cp -R "${REALM_JAVA_PATH}"/realm/realm-library/build/docs/javadoc/* ./source/en/docs/java/latest/api/ bundle exec rake generate:inject_ga_latest_java_api git add ./source/en/docs/java/ git commit -m "Release realm-java doc ${VERSION}" From b7b00a3809029f9cf3ea477cfe1c52eb97b5d759 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 4 May 2017 17:19:21 +0800 Subject: [PATCH 0678/2110] Fix flaky test --- .../androidTest/java/io/realm/RealmTests.java | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index b4bce17087..384cc60de5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3044,38 +3044,28 @@ public void copyFromRealm_dynamicRealmListThrows() { // Tests if close can be called from Realm change listener when there is no other listeners. @Test + @RunTestInLooperThread public void closeRealmInChangeListener() { - realm.close(); - final CountDownLatch signalTestFinished = new CountDownLatch(1); - HandlerThread handlerThread = new HandlerThread("background"); - handlerThread.start(); - final Handler handler = new Handler(handlerThread.getLooper()); - handler.post(new Runnable() { + final Realm realm = looperThread.getRealm(); + final RealmChangeListener listener = new RealmChangeListener() { @Override - public void run() { - final Realm realm = Realm.getInstance(realmConfig); - final RealmChangeListener listener = new RealmChangeListener() { - @Override - public void onChange(Realm object) { - if (realm.where(AllTypes.class).count() == 1) { - realm.removeChangeListener(this); - realm.close(); - signalTestFinished.countDown(); - } - } - }; + public void onChange(Realm object) { + if (realm.where(AllTypes.class).count() == 1) { + realm.removeChangeListener(this); + realm.close(); + looperThread.testComplete(); + } + } + }; - realm.addChangeListener(listener); + realm.addChangeListener(listener); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObject(AllTypes.class); - } - }); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class); } }); - TestHelper.awaitOrFail(signalTestFinished); } // Tests if close can be called from Realm change listener when there is a listener on empty Realm Object. From f4b8bfbaf177b2af98ab17c0fba3fb8c304cddcc Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 8 May 2017 12:34:01 +0800 Subject: [PATCH 0679/2110] Wait for async tasks finish to solve flaky test (#4603) Apparently waiting some random millisecond is never a good idea. --- .../java/io/realm/RealmAsyncQueryTests.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 6bd6945524..4caa6d94c1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -37,6 +37,7 @@ import io.realm.entities.Dog; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.Owner; +import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; @@ -411,10 +412,14 @@ public void onError(Throwable error) { // callbacks, the callback should be cleared. @Test @RunTestInLooperThread - public void executeTransactionAsync_callbacksShouldBeClearedBeforeCalling() { + public void executeTransactionAsync_callbacksShouldBeClearedBeforeCalling() + throws NoSuchFieldException, IllegalAccessException { final AtomicInteger callbackCounter = new AtomicInteger(0); final Realm foregroundRealm = looperThread.getRealm(); + // Use single thread executor + TestHelper.replaceRealmThreadExecutor(RealmThreadPoolExecutor.newSingleThreadExecutor()); + // To reproduce the issue, the posted callback needs to arrived before the Object Store did_change called. // We just disable the auto refresh here then the did_change won't be called. foregroundRealm.setAutoRefresh(false); @@ -440,15 +445,6 @@ public void onSuccess() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); - // Delay to post this to ensure the async transaction posted callback will arrive first. - looperThread.postRunnableDelayed(new Runnable() { - @Override - public void run() { - // Manually call refresh, so the did_change will be triggered. - foregroundRealm.sharedRealm.refresh(); - foregroundRealm.setAutoRefresh(true); - } - }, 50); } }, new Realm.Transaction.OnSuccess() { @Override @@ -458,6 +454,17 @@ public void onSuccess() { looperThread.testComplete(); } }); + + // Wait for all async tasks finish to ensure the async transaction posted callback will arrive first. + TestHelper.resetRealmThreadExecutor(); + looperThread.postRunnable(new Runnable() { + @Override + public void run() { + // Manually call refresh, so the did_change will be triggered. + foregroundRealm.sharedRealm.refresh(); + foregroundRealm.setAutoRefresh(true); + } + }); } // ************************************ From 5deb09976b788a8eb3800c0aac897fdcf2b8e9d1 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 8 May 2017 18:36:58 +0900 Subject: [PATCH 0680/2110] fix typo in CHANGELOG.md (#4608) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7528cf2b67..8d74041d3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ * Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore` annotation (#4279). * Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). * Added `Realm.getInstanceAsync()` and `DynamicRealm.getInstanceAsync()` (#2299). -* Added `DynamicRealmObject#linkingObjects(String,String) to support linking objects on `DynamicRealm` (#4492). +* Added `DynamicRealmObject#linkingObjects(String,String)` to support linking objects on `DynamicRealm` (#4492). ### Bug Fixes From c1ecb99ffd4b81e677f8b61e1ce7ecd5792feb6a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 9 May 2017 09:53:11 +0200 Subject: [PATCH 0681/2110] Fixed @LinkingObjects notation for Kotlin (#4613) --- CHANGELOG.md | 7 +++++++ examples/kotlinExample/build.gradle | 2 +- .../examples/kotlin/KotlinExampleActivity.kt | 7 +++---- .../kotlin/io/realm/examples/kotlin/model/Cat.kt | 2 +- .../kotlin/io/realm/examples/kotlin/model/Dog.kt | 6 +++++- .../io/realm/examples/kotlin/model/Person.kt | 15 +++++++-------- .../java/io/realm/annotations/LinkingObjects.java | 2 +- 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d12cd03c7..ba95af38a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 3.1.5 (YYYY-MM-DD) + +## Bug fixes + +* `@LinkingObjects` annotation now also works with Kotlin (#4611). + + ## 3.1.4 (2017-05-04) ## Bug fixes diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 737177a801..fdaceba7dd 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -46,5 +46,5 @@ android { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" - compile 'org.jetbrains.anko:anko-sdk15:0.8.2' + compile 'org.jetbrains.anko:anko-sdk15:0.9.1' } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index aba7c85d6e..d54d096e1f 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -26,7 +26,7 @@ import io.realm.Sort import io.realm.examples.kotlin.model.Cat import io.realm.examples.kotlin.model.Dog import io.realm.examples.kotlin.model.Person -import org.jetbrains.anko.async +import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread import kotlin.properties.Delegates @@ -63,11 +63,10 @@ class KotlinExampleActivity : Activity() { basicLinkQuery(realm) // More complex operations can be executed on another thread, for example using - // Anko's async extension method. - async { + // Anko's doAsync extension method. + doAsync { var info = complexReadWrite() info += complexQuery() - uiThread { showStatus(info) } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Cat.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Cat.kt index 803eb7fcef..60e21b81b5 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Cat.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Cat.kt @@ -19,5 +19,5 @@ package io.realm.examples.kotlin.model import io.realm.RealmObject open class Cat : RealmObject() { - open var name: String? = null + var name: String? = null } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Dog.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Dog.kt index dd4b8452e8..17d8b5fd75 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Dog.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Dog.kt @@ -17,7 +17,11 @@ package io.realm.examples.kotlin.model import io.realm.RealmObject +import io.realm.RealmResults +import io.realm.annotations.LinkingObjects open class Dog : RealmObject() { - open var name: String? = null + var name: String? = null + @LinkingObjects("dog") + val owners: RealmResults? = null } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Person.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Person.kt index c408a12472..829fb1b8cd 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Person.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Person.kt @@ -21,8 +21,7 @@ import io.realm.RealmObject import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey -// Your model has to extend RealmObject. Furthermore, the class and all of the -// properties must be annotated with open (Kotlin classes and methods are final +// Your model has to extend RealmObject. Furthermore, the class must be annotated with open (Kotlin classes are final // by default). open class Person( // You can put properties in the constructor as long as all of them are initialized with @@ -30,20 +29,20 @@ open class Person( // All properties are by default persisted. // Properties can be annotated with PrimaryKey or Index. // If you use non-nullable types, properties must be initialized with non-null values. - @PrimaryKey open var id: Long = 0, + @PrimaryKey var id: Long = 0, - open var name: String = "", + var name: String = "", - open var age: Int = 0, + var age: Int = 0, // Other objects in a one-to-one relation must also subclass RealmObject - open var dog: Dog? = null, + var dog: Dog? = null, // One-to-many relations is simply a RealmList of the objects which also subclass RealmObject - open var cats: RealmList = RealmList(), + var cats: RealmList = RealmList(), // You can instruct Realm to ignore a field and not persist it. - @Ignore open var tempReference: Int = 0 + @Ignore var tempReference: Int = 0 ) : RealmObject() { // The Kotlin compiler generates standard getters and setters. diff --git a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java index d79258c51d..3eb4afa3b9 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java +++ b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java @@ -92,7 +92,7 @@ * assert fido.owners.size() == 2; * } */ -@Retention(RetentionPolicy.SOURCE) +@Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) @Beta public @interface LinkingObjects { From 375c68c193e52c25f05386a903aed282deb36ed1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 9 May 2017 09:57:03 +0200 Subject: [PATCH 0682/2110] Use shorthand for generic arguments (#4609) IntelliJ will auto-expand them to a camel-cased version of the generic argument. --- CHANGELOG.md | 1 + .../java/io/realm/OrderedRealmCollectionChangeListener.java | 4 ++-- .../src/main/java/io/realm/RealmChangeListener.java | 2 +- .../src/main/java/io/realm/RealmObjectChangeListener.java | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d74041d3a..0ed3af7122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). * Added `Realm.getInstanceAsync()` and `DynamicRealm.getInstanceAsync()` (#2299). * Added `DynamicRealmObject#linkingObjects(String,String)` to support linking objects on `DynamicRealm` (#4492). +* Changelisteners will now auto-expand variable names to be more descriptive when using Android Studio. ### Bug Fixes diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java index 8c51f2a570..b9a5261c85 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java @@ -32,9 +32,9 @@ public interface OrderedRealmCollectionChangeListener { /** * This will be called when the async query is finished the first time or the collection of objects has changed. * - * @param collection the collection this listener is registered to. + * @param t the collection this listener is registered to. * @param changeSet object with information about which rows in the collection were added, removed or modified. * {@code null} is returned the first time an async query is completed. */ - void onChange(T collection, OrderedCollectionChangeSet changeSet); + void onChange(T t, OrderedCollectionChangeSet changeSet); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmChangeListener.java b/realm/realm-library/src/main/java/io/realm/RealmChangeListener.java index fac6e8f70b..fed06316bb 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmChangeListener.java +++ b/realm/realm-library/src/main/java/io/realm/RealmChangeListener.java @@ -42,6 +42,6 @@ public interface RealmChangeListener { /** * Called when a transaction is committed. */ - void onChange(T element); + void onChange(T t); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectChangeListener.java b/realm/realm-library/src/main/java/io/realm/RealmObjectChangeListener.java index af172a0e16..4ab96596aa 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectChangeListener.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectChangeListener.java @@ -51,8 +51,8 @@ public interface RealmObjectChangeListener { *

                      * Changes to {@link LinkingObjects} annotated {@link RealmResults} fields will not be monitored, nor reported * through this change listener. - * @param object the {@code RealmObject} this listener is registered to. + * @param t the {@code RealmObject} this listener is registered to. * @param changeSet the detailed information about the changes. */ - void onChange(T object, ObjectChangeSet changeSet); + void onChange(T t, ObjectChangeSet changeSet); } From 321ba60c6f7feb39f45dba0fdace3a6301915c96 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 12 Apr 2017 14:15:55 +0800 Subject: [PATCH 0683/2110] Clean up SharedRealm source - Remove unused methods. - Fix double negatives. - Use auto& to avoid creating temp objects. --- .../cpp/io_realm_internal_SharedRealm.cpp | 71 ++++++++----------- .../java/io/realm/internal/SharedRealm.java | 20 +++--- 2 files changed, 37 insertions(+), 54 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index f542b345b0..09af1fe85e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -164,7 +164,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv* env JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateConfig( JNIEnv* env, jclass, jstring realm_path, jbyteArray key, jbyte schema_mode, jboolean in_memory, jboolean cache, - jlong /* schema_version */, jboolean disable_format_upgrade, jboolean auto_change_notification, + jlong /* schema_version */, jboolean enable_format_upgrade, jboolean auto_change_notification, REALM_UNUSED jstring sync_server_url, REALM_UNUSED jstring sync_server_auth_url, REALM_UNUSED jstring sync_user_identity, REALM_UNUSED jstring sync_refresh_token) { @@ -180,7 +180,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateConfig( config.schema_mode = static_cast(schema_mode); config.in_memory = in_memory; config.cache = cache; - config.disable_format_upgrade = disable_format_upgrade; + config.disable_format_upgrade = !enable_format_upgrade; config.automatic_change_notifications = auto_change_notification; if (sync_server_url) { return reinterpret_cast(new JniConfigWrapper(env, config, sync_server_url, sync_server_auth_url, @@ -223,7 +223,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); // Close the SharedRealm only. Let the finalizer daemon thread free the SharedRealm if (!shared_realm->is_closed()) { shared_realm->close(); @@ -235,7 +235,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeBeginTransaction { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->begin_transaction(); } @@ -247,7 +247,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCommitTransactio { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->commit_transaction(); // Realm could be closed in the RealmNotifier.didChange(). @@ -265,7 +265,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCancelTransactio { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->cancel_transaction(); } @@ -278,7 +278,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsInTransact { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return static_cast(shared_realm->is_in_transaction()); } @@ -287,7 +287,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEn { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return reinterpret_cast(&shared_realm->read_group()); } @@ -301,7 +301,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIE { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(ObjectStore::get_schema_version(shared_realm->read_group())); } @@ -314,7 +314,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEn { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { if (!shared_realm->is_in_transaction()) { std::ostringstream ss; @@ -333,7 +333,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIE { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(ObjectStore::is_empty(shared_realm->read_group())); } @@ -345,7 +345,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRefresh(JNIEnv* { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->refresh(); } @@ -357,7 +357,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersion { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { using rf = realm::_impl::RealmFriend; SharedGroup::VersionID version_id = rf::get_shared_group(*shared_realm).get_version_of_current_transaction(); @@ -384,7 +384,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsClosed(JNI { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return static_cast(shared_realm->is_closed()); } @@ -396,7 +396,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv try { JStringAccessor name(env, table_name); // throws - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); if (!shared_realm->read_group().has_table(name) && !shared_realm->is_in_transaction()) { std::ostringstream ss; ss << "Class " << name << " doesn't exist and the shared Realm is not in transaction."; @@ -417,7 +417,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_SharedRealm_nativeGetTableName( TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return to_jstring(env, shared_realm->read_group().get_table_name(static_cast(index))); } @@ -431,7 +431,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeHasTable(JNI { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { JStringAccessor name(env, table_name); return static_cast(shared_realm->read_group().has_table(name)); @@ -447,7 +447,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIE { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { JStringAccessor old_name(env, old_table_name); if (!shared_realm->is_in_transaction()) { @@ -468,7 +468,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIE { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { JStringAccessor name(env, table_name); if (!shared_realm->is_in_transaction()) { @@ -486,7 +486,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv* en { TR_ENTER_PTR(shared_realm_ptr) - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(shared_realm->read_group().size()); } @@ -500,7 +500,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv { TR_ENTER_PTR(shared_realm_ptr); - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { JStringAccessor path_str(env, path); JniByteArray key_buffer(env, key); @@ -514,7 +514,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeWaitForChang { TR_ENTER_PTR(shared_realm_ptr); - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { using rf = realm::_impl::RealmFriend; return static_cast(rf::get_shared_group(*shared_realm).wait_for_change()); @@ -529,7 +529,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeStopWaitForChang { TR_ENTER_PTR(shared_realm_ptr); - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { using rf = realm::_impl::RealmFriend; rf::get_shared_group(*shared_realm).wait_for_change_release(); @@ -542,7 +542,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeCompact(JNIE { TR_ENTER_PTR(shared_realm_ptr); - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(shared_realm->compact()); } @@ -551,28 +551,13 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeCompact(JNIE return JNI_FALSE; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSnapshotVersion(JNIEnv* env, jclass, - jlong shared_realm_ptr) -{ - TR_ENTER_PTR(shared_realm_ptr) - - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - try { - using rf = realm::_impl::RealmFriend; - auto& shared_group = rf::get_shared_group(*shared_realm); - return LangBindHelper::get_version_of_latest_snapshot(shared_group); - } - CATCH_STD() - return 0; -} - JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong schema_ptr, jlong version) { TR_ENTER_PTR(shared_realm_ptr) try { - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); auto* schema = reinterpret_cast(schema_ptr); shared_realm->update_schema(*schema, static_cast(version), nullptr, true); } @@ -586,7 +571,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeRequiresMigr TR_ENTER() try { - auto shared_realm = *(reinterpret_cast(nativePtr)); + auto& shared_realm = *(reinterpret_cast(nativePtr)); auto* schema = reinterpret_cast(nativeSchemaPtr); const std::vector& change_list = shared_realm->schema().compare(*schema); return static_cast(!change_list.empty()); @@ -613,7 +598,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetAutoRefresh(J { TR_ENTER_PTR(shared_realm_ptr) try { - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); shared_realm->set_auto_refresh(to_bool(enabled)); } CATCH_STD() @@ -624,7 +609,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsAutoRefres { TR_ENTER_PTR(shared_realm_ptr) try { - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return to_jbool(shared_realm->auto_refresh()); } CATCH_STD() diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 21b041baec..70acf9617e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -128,6 +128,7 @@ public static class VersionID implements Comparable { @Override public int compareTo(@SuppressWarnings("NullableProblems") VersionID another) { + //noinspection ConstantConditions if (another == null) { throw new IllegalArgumentException("Version cannot be compared to a null value."); } @@ -214,17 +215,18 @@ public static SharedRealm getInstance(RealmConfiguration config, SchemaVersionLi String syncRealmUrl = syncUserConf[1]; String syncRealmAuthUrl = syncUserConf[2]; String syncRefreshToken = syncUserConf[3]; - boolean enable_caching = false; // Handled in Java currently - boolean disableFormatUpgrade = false; // TODO Double negatives :/ + + final boolean enableCaching = false; // Handled in Java currently + final boolean enableFormatUpgrade = true; long nativeConfigPtr = nativeCreateConfig( config.getPath(), config.getEncryptionKey(), syncRealmUrl != null ? SchemaMode.SCHEMA_MODE_ADDITIVE.getNativeValue() : SchemaMode.SCHEMA_MODE_MANUAL.getNativeValue(), config.getDurability() == Durability.MEM_ONLY, - enable_caching, + enableCaching, config.getSchemaVersion(), - disableFormatUpgrade, + enableFormatUpgrade, autoChangeNotifications, syncRealmUrl, syncRealmAuthUrl, @@ -314,10 +316,6 @@ public SharedRealm.VersionID getVersionID() { return new SharedRealm.VersionID(versionId[0], versionId[1]); } - public long getLastSnapshotVersion() { - return nativeGetSnapshotVersion(nativePtr); - } - public boolean isClosed() { return nativeIsClosed(nativePtr); } @@ -463,7 +461,9 @@ private void executePendingRowQueries() { // Keep last session as an 'object' to avoid any reference to sync code private static native long nativeCreateConfig(String realmPath, byte[] key, byte schemaMode, boolean inMemory, - boolean cache, long schemaVersion, boolean disableFormatUpgrade, + boolean cache, + long schemaVersion, + boolean enabledFormatUpgrade, boolean autoChangeNotification, String syncServerURL, String syncServerAuthURL, @@ -488,8 +488,6 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte private static native long nativeGetVersion(long nativeSharedRealmPtr); - private static native long nativeGetSnapshotVersion(long nativeSharedRealmPtr); - private static native void nativeSetVersion(long nativeSharedRealmPtr, long version); private static native long nativeReadGroup(long nativeSharedRealmPtr); From a31f0a4b1b01dbeb059d213e7cea9422a71e4ab2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 9 May 2017 12:34:17 +0200 Subject: [PATCH 0684/2110] Add support for SyncConfiguration.waitForServerChanges() (#4536) --- CHANGELOG.md | 1 + .../io/realm/internal/RealmNotifierTests.java | 5 + .../src/main/cpp/io_realm_SyncSession.cpp | 43 +++- .../src/main/cpp/jni_util/java_class.cpp | 3 +- .../src/main/cpp/jni_util/java_global_ref.cpp | 5 + .../src/main/cpp/jni_util/java_global_ref.hpp | 6 +- .../src/main/cpp/jni_util/java_local_ref.hpp | 24 ++- .../src/main/java/io/realm/Realm.java | 4 + .../src/main/java/io/realm/RealmCache.java | 51 ++++- .../java/io/realm/RealmConfiguration.java | 17 ++ .../java/io/realm/internal/Capabilities.java | 6 + .../io/realm/internal/ObjectServerFacade.java | 17 ++ .../internal/android/AndroidCapabilities.java | 17 +- .../java/io/realm/SyncConfiguration.java | 43 +++- .../java/io/realm/SyncSession.java | 115 +++++++++++ .../DownloadingRealmInterruptedException.java | 31 +++ .../internal/SyncObjectServerFacade.java | 22 +++ .../realm/objectserver/SyncedRealmTests.java | 185 ++++++++++++++++++ .../realm/objectserver/utils/Constants.java | 1 + 19 files changed, 569 insertions(+), 27 deletions(-) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncedRealmTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 01386b7cde..79dc58fcf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * [ObjectServer] Added support for `SyncUser.isAdmin()` (#4353). * [ObjectServer] Added support for changing passwords through `SyncUser.changePassword()` (#4423). +* [ObjectServer] Added support for `SyncConfigration.Builder.waitForInitialRemoteData()` (#4270). * Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore` annotation (#4279). * Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). * Added `Realm.getInstanceAsync()` and `DynamicRealm.getInstanceAsync()` (#2299). diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 0516f0e115..ace2f24229 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -54,6 +54,11 @@ public boolean canDeliverNotification() { @Override public void checkCanDeliverNotification(String exceptionMessage) { } + + @Override + public boolean isMainThread() { + return false; + } }; @Before diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index e3a58bbad2..8165ea5cb5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -22,9 +22,15 @@ #include "object-store/src/sync/sync_session.hpp" #include "util.hpp" +#include "jni_util/java_global_ref.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/java_class.hpp" +#include "jni_util/java_local_ref.hpp" +#include "jni_util/jni_utils.hpp" using namespace std; using namespace realm; +using namespace jni_util; using namespace sync; JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JNIEnv* env, jclass, @@ -43,7 +49,42 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JN return JNI_TRUE; } else { - realm::jni_util::Log::d("no active/inactive session found"); + Log::d("no active/inactive session found"); + } + } + CATCH_STD() + return JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadCompletion(JNIEnv* env, + jobject session_object, + jstring localRealmPath) +{ + TR_ENTER() + try { + JStringAccessor local_realm_path(env, localRealmPath); + auto session = SyncManager::shared().get_existing_session(local_realm_path); + + if (session) { + static JavaClass java_sync_session_class(env, "io/realm/SyncSession"); + static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesDownloaded", + "(Ljava/lang/Long;Ljava/lang/String;)V"); + JavaGlobalRef java_session_object_ref(env, session_object); + + bool listener_registered = + session->wait_for_download_completion([java_session_object_ref](std::error_code error) { + JNIEnv* env = JniUtils::get_env(true); + jobject java_error_code = nullptr; + jstring java_error_message = nullptr; + if (error != std::error_code{}) { + java_error_code = NewLong(env, error.value()); + java_error_message = env->NewStringUTF(error.message().c_str()); + } + env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, java_error_code, + java_error_message); + }); + + return to_jbool(listener_registered); } } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/jni_util/java_class.cpp b/realm/realm-library/src/main/cpp/jni_util/java_class.cpp index f0bb7530c5..eaf526e75c 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_class.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_class.cpp @@ -36,7 +36,6 @@ JavaGlobalRef JavaClass::get_jclass(JNIEnv* env, const char* class_name) jclass cls = env->FindClass(class_name); REALM_ASSERT_DEBUG(cls); - JavaGlobalRef cls_ref(env, cls); - env->DeleteLocalRef(cls); + JavaGlobalRef cls_ref(env, cls, true); return cls_ref; } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp index fd6036cf95..68d9fd99de 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp @@ -34,3 +34,8 @@ JavaGlobalRef& JavaGlobalRef::operator=(JavaGlobalRef&& rhs) new (this) JavaGlobalRef(std::move(rhs)); return *this; } + +JavaGlobalRef::JavaGlobalRef(JavaGlobalRef& rhs) + : m_ref(rhs.m_ref ? jni_util::JniUtils::get_env(true)->NewGlobalRef(rhs.m_ref) : nullptr) +{ +} diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp index f2d0c3320d..1b118074e3 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp @@ -45,20 +45,18 @@ class JavaGlobalRef { ~JavaGlobalRef(); JavaGlobalRef& operator=(JavaGlobalRef&& rhs); + JavaGlobalRef(JavaGlobalRef&); inline operator bool() const noexcept { return m_ref != nullptr; } - inline jobject get() noexcept + inline jobject get() const noexcept { return m_ref; } - // Not implemented for now. - JavaGlobalRef(JavaGlobalRef&) = delete; - private: jobject m_ref; }; diff --git a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp index 283c948fba..3026ee5a08 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp @@ -41,15 +41,24 @@ class JavaLocalRef { inline JavaLocalRef(JNIEnv* env, T obj, NeedToCreateLocalRef) noexcept : m_jobject(env->NewLocalRef(obj)) , m_env(env){}; + inline ~JavaLocalRef() { m_env->DeleteLocalRef(m_jobject); } - JavaLocalRef(const JavaLocalRef&) = delete; - JavaLocalRef& operator=(const JavaLocalRef&) = delete; - JavaLocalRef(JavaLocalRef&& rhs) = delete; - JavaLocalRef& operator=(JavaLocalRef&& rhs) = delete; + JavaLocalRef& operator=(JavaLocalRef&& rhs) + { + this->~JavaLocalRef(); + new (this) JavaLocalRef(std::move(rhs)); + return *this; + } + + inline JavaLocalRef(JavaLocalRef&& rhs) + : m_env(rhs.m_env), m_jobject(rhs.m_jobject) + { + rhs.m_jobject = nullptr; + } inline operator bool() const noexcept { @@ -59,6 +68,13 @@ class JavaLocalRef { { return m_jobject; } + inline T get() const noexcept + { + return m_jobject; + }; + + JavaLocalRef(const JavaLocalRef&) = delete; + JavaLocalRef& operator=(const JavaLocalRef&) = delete; private: T m_jobject; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 10068d8fb0..1ee29a820d 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -260,6 +260,8 @@ private static void checkFilesDirAvailable(Context context) { * @throws RealmMigrationNeededException if no migration has been provided by the default configuration and the * RealmObject classes or version has has changed so a migration is required. * @throws RealmFileException if an error happened when accessing the underlying Realm file. + * @throws io.realm.exceptions.DownloadingRealmInterruptedException if {@link SyncConfiguration.Builder#waitForInitialRemoteData()} + * was set and the thread opening the Realm was interrupted while the download was in progress. */ public static Realm getDefaultInstance() { if (defaultConfiguration == null) { @@ -277,6 +279,8 @@ public static Realm getDefaultInstance() { * classes or version has has changed so a migration is required. * @throws RealmFileException if an error happened when accessing the underlying Realm file. * @throws IllegalArgumentException if a null {@link RealmConfiguration} is provided. + * @throws io.realm.exceptions.DownloadingRealmInterruptedException if {@link SyncConfiguration.Builder#waitForInitialRemoteData()} + * was set and the thread opening the Realm was interrupted while the download was in progress. * @see RealmConfiguration for details on how to configure a Realm. */ public static Realm getInstance(RealmConfiguration configuration) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 8b158e4ba1..6d479323a8 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -24,12 +24,12 @@ import java.util.Collection; import java.util.EnumMap; import java.util.Iterator; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import io.realm.exceptions.RealmFileException; @@ -111,6 +111,7 @@ public void setFuture(Future future) { public void run() { T instance = null; try { + // First call that will run all schema validation, migrations or initial transactions. instance = createRealmOrGetFromCache(configuration, realmClass); boolean results = notifier.post(new Runnable() { @Override @@ -128,6 +129,9 @@ public void run() { T instanceToReturn = null; Throwable throwable = null; try { + // This will run on the caller thread, but since the first `createRealmOrGetFromCache` + // should have completed at this point, all expensive initializer functions have already + // run. instanceToReturn = createRealmOrGetFromCache(configuration, realmClass); } catch (Throwable e) { throwable = e; @@ -152,13 +156,18 @@ public void run() { } catch (InterruptedException e) { RealmLog.warn(e, "`CreateRealmRunnable` has been interrupted."); } catch (final Throwable e) { - RealmLog.error(e, "`CreateRealmRunnable` failed."); - notifier.post(new Runnable() { - @Override - public void run() { - callback.onError(e); - } - }); + // DownloadingRealmInterruptedException is treated specially. + // It async open is canceled, this could interrupt the download, but the user should + // not care in this case, so just ignore it. + if (!ObjectServerFacade.getSyncFacadeIfPossible().wasDownloadInterrupted(e)) { + RealmLog.error(e, "`CreateRealmRunnable` failed."); + notifier.post(new Runnable() { + @Override + public void run() { + callback.onError(e); + } + }); + } } finally { if (instance != null) { instance.close(); @@ -281,10 +290,30 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo if (getTotalGlobalRefCount() == 0) { copyAssetFileIfNeeded(configuration); + boolean fileExists = configuration.realmExists(); SharedRealm sharedRealm = null; try { sharedRealm = SharedRealm.getInstance(configuration); + + // If waitForInitialRemoteData() was enabled, we need to make sure that all data is downloaded + // before proceeding. We need to open the Realm instance first to start any potential underlying + // SyncSession so this will work. TODO: This needs to be decoupled. + if (!fileExists) { + try { + ObjectServerFacade.getSyncFacadeIfPossible().downloadRemoteChanges(configuration); + } catch (Throwable t) { + // If an error happened while downloading initial data, we need to reset the file so we can + // download it again on the next attempt. + // Realm.deleteRealm() is under the same lock as this method and globalCount is still 0, so + // this should be safe. + sharedRealm.close(); + sharedRealm = null; + Realm.deleteRealm(configuration); + throw t; + } + } + if (Table.primaryKeyTableNeedsMigration(sharedRealm)) { sharedRealm.beginTransaction(); if (Table.migratePrimaryKeyTableIfNeeded(sharedRealm)) { @@ -492,6 +521,8 @@ synchronized void invokeWithLock(Callback0 callback) { * Copies Realm database file from Android asset directory to the directory given in the {@link RealmConfiguration}. * Copy is performed only at the first time when there is no Realm database file. * + * WARNING: This method is not thread-safe so external synchronization is required before using it. + * * @param configuration configuration object for Realm instance. * @throws RealmFileException if copying the file fails. */ diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index fd8312fde2..064a656a37 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -200,10 +200,27 @@ public Set> getRealmObjectClasses() { return schemaMediator.getModelClasses(); } + /** + * Returns the absolute path to where the Realm file will be saved. + * + * @return the absolute path to the Realm file defined by this configuration. + */ public String getPath() { return canonicalPath; } + /** + * Checks if the Realm file defined by this configuration already exists. + * + * WARNING: This method is just a point-in-time check. Unless protected by external synchronization another + * thread or process might have created or deleted the Realm file right after this method has returned. + * + * @return {@code true} if the Realm file exists, {@code false} otherwise. + */ + boolean realmExists() { + return new File(canonicalPath).exists(); + } + /** * Returns the {@link RxObservableFactory} that is used to create Rx Observables from Realm objects. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java index 5eaa72a770..062fdcd285 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Capabilities.java @@ -35,4 +35,10 @@ public interface Capabilities { * @param exceptionMessage message which is contained in the exception. */ void checkCanDeliverNotification(String exceptionMessage); + + /** + * Multiple threads might be able to deliver notifications, but the Main thread in GUI applications often have + * special rules that need to be enforced. + */ + boolean isMainThread(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 7493da59d9..253694a12b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -88,4 +88,21 @@ public static ObjectServerFacade getSyncFacadeIfPossible() { // If no session yet exists for this path. Wrap a new Java Session around an existing OS one. public void wrapObjectStoreSessionIfRequired(RealmConfiguration config) { } + + /** + * Block until all latest changes have been downloaded from the server. + * + * @throws {@code DownloadingRealmInterruptedException} if the thread was interrupted while blocked waiting for + * this to complete. + */ + public void downloadRemoteChanges(RealmConfiguration config) { + // Do nothing + } + + /** + * Check if an exception is a {@code DownloadingRealmInterruptedException} + */ + public boolean wasDownloadInterrupted(Throwable throwable) { + return false; + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java index 78ea5a92dc..c7f08f7ae5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java @@ -25,22 +25,22 @@ */ public class AndroidCapabilities implements Capabilities { - private final boolean hasLooper; + private final Looper looper; private final boolean isIntentServiceThread; public AndroidCapabilities() { - hasLooper = Looper.myLooper() != null; + looper = Looper.myLooper(); isIntentServiceThread = isIntentServiceThread(); } @Override public boolean canDeliverNotification() { - return hasLooper && !isIntentServiceThread; + return hasLooper() && !isIntentServiceThread; } @Override public void checkCanDeliverNotification(String exceptionMessage) { - if (!hasLooper) { + if (!hasLooper()) { throw new IllegalStateException(exceptionMessage == null ? "" : (exceptionMessage + " ") + "Realm cannot be automatically updated on a thread without a looper."); } @@ -50,6 +50,15 @@ public void checkCanDeliverNotification(String exceptionMessage) { } } + @Override + public boolean isMainThread() { + return looper != null && looper == Looper.getMainLooper(); + } + + private boolean hasLooper() { + return looper != null; + } + private static boolean isIntentServiceThread() { // Tries to determine if a thread is an IntentService thread. No public API can detect this, // so use the thread name as a heuristic: diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index e2eb671721..160c506996 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -78,6 +78,7 @@ public class SyncConfiguration extends RealmConfiguration { private final SyncUser user; private final SyncSession.ErrorHandler errorHandler; private final boolean deleteRealmOnLogout; + private final boolean waitForInitialData; private SyncConfiguration(File directory, String filename, @@ -94,7 +95,9 @@ private SyncConfiguration(File directory, SyncUser user, URI serverUrl, SyncSession.ErrorHandler errorHandler, - boolean deleteRealmOnLogout + boolean deleteRealmOnLogout, + boolean waitForInitialData + ) { super(directory, filename, @@ -114,6 +117,7 @@ private SyncConfiguration(File directory, this.serverUrl = serverUrl; this.errorHandler = errorHandler; this.deleteRealmOnLogout = deleteRealmOnLogout; + this.waitForInitialData = waitForInitialData; } static URI resolveServerUrl(URI serverUrl, String userIdentifier) { @@ -149,6 +153,7 @@ public boolean equals(Object o) { if (!serverUrl.equals(that.serverUrl)) return false; if (!user.equals(that.user)) return false; if (!errorHandler.equals(that.errorHandler)) return false; + if (waitForInitialData != that.waitForInitialData) return false; return true; } @@ -159,6 +164,7 @@ public int hashCode() { result = 31 * result + user.hashCode(); result = 31 * result + (deleteRealmOnLogout ? 1 : 0); result = 31 * result + errorHandler.hashCode(); + result = 31 * result + (waitForInitialData ? 1 : 0); return result; } @@ -173,6 +179,8 @@ public String toString() { stringBuilder.append("errorHandler: " + errorHandler); stringBuilder.append("\n"); stringBuilder.append("deleteRealmOnLogout: " + deleteRealmOnLogout); + stringBuilder.append("\n"); + stringBuilder.append("waitForInitialRemoteData: " + waitForInitialData); return stringBuilder.toString(); } @@ -209,6 +217,18 @@ public boolean shouldDeleteRealmOnLogout() { return deleteRealmOnLogout; } + + /** + * Returns {@code true} if the Realm will download all known changes from the remote server before being opened the + * first time. + * + * @return {@code true} if all remote changes will be downloaded before the Realm can be opened. {@code false} if + * the Realm can be opened immediately. + */ + public boolean shouldWaitForInitialRemoteData() { + return waitForInitialData; + } + @Override boolean isSyncConfiguration() { return true; @@ -237,6 +257,7 @@ public static final class Builder { private SharedRealm.Durability durability = SharedRealm.Durability.FULL; private boolean deleteRealmOnLogout = false; private final Pattern pattern = Pattern.compile("^[A-Za-z0-9_\\-\\.]+$"); // for checking serverUrl + private boolean waitForServerChanges = false; /** * Creates an instance of the Builder for the SyncConfiguration. @@ -573,6 +594,23 @@ public Builder errorHandler(SyncSession.ErrorHandler errorHandler) { return this; } + /** + * Setting this will cause the Realm to download all known changes from the server the first time a Realm is + * opened. The Realm will not open until all the data has been downloaded. This means that if a device is + * offline the Realm will not open. + *

                      + * Since downloading all changes can be an lengthy operation that might block the UI thread, Realms with this + * setting enabled should only be opened on background threads or with + * {@link Realm#getInstanceAsync(RealmConfiguration, Realm.Callback)} on the UI thread. + *

                      + * This check is only enforced the first time a Realm is created. If you otherwise want to make sure a Realm + * has the latest changes, use {@link SyncSession#downloadAllServerChanges()}. + */ + public Builder waitForInitialRemoteData() { + this.waitForServerChanges = true; + return this; + } + private String MD5(String in) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); @@ -685,7 +723,8 @@ public SyncConfiguration build() { user, resolvedServerUrl, errorHandler, - deleteRealmOnLogout + deleteRealmOnLogout, + waitForServerChanges ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index b456318617..b008a2fae3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -17,15 +17,18 @@ package io.realm; import java.net.URI; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import io.realm.internal.Keep; import io.realm.internal.KeepMember; import io.realm.internal.SyncObjectServerFacade; +import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.network.AuthenticationServer; @@ -59,6 +62,8 @@ public class SyncSession { private RealmAsyncTask refreshTokenNetworkRequest; private AtomicBoolean onGoingAccessTokenQuery = new AtomicBoolean(false); private volatile boolean isClosed = false; + private final AtomicReference waitingForServerChanges = new AtomicReference<>(null); + private final Object waitForChangesMutex = new Object(); SyncSession(SyncConfiguration configuration) { this.configuration = configuration; @@ -118,6 +123,70 @@ void close() { clearScheduledAccessTokenRefresh(); } + // This method will be called once all changes have been downloaded. + // This method might be called on another thread than the one that called `downloadAllServerChanges`. + // Be very careful with synchronized blocks. + // If the native listener was successfully registered, Object Store guarantees that this method will be called at + // least once, even if the session is closed. + @SuppressWarnings("unused") + private void notifyAllChangesDownloaded(Long errorcode, String errorMessage) { + WaitForServerChangesWrapper wrapper = waitingForServerChanges.get(); + if (wrapper != null) { + wrapper.handleResult(errorcode, errorMessage); + } + } + + /** + * Calling this method will block until all known remote changes have been downloaded and applied to the Realm. + * This will involve network access, so calling this method should only be done from a non-UI thread. + *

                      + * If the device is offline, this method might never return. + *

                      + * This method cannot be called before the session has been started. + * + * @throws IllegalStateException if called on the Android main thread. + * @throws InterruptedException if the thread was interrupted while downloading was in progress. + */ + public void downloadAllServerChanges() throws InterruptedException { + checkIfNotOnMainThread("downloadAllServerChanges() cannot be called from the main thread."); + + // Blocking only happens at the Java layer. To prevent deadlocking the underlying SyncSession we register + // an async listener there and let it callback to the Java Session when done. This feels icky at best, but + // since all operations on the SyncSession operate under a shared mutex, we would prevent all other actions on the + // session, including trying to stop it. + // In Java we cannot lock on the Session object either since it will prevent any attempt at modifying the + // lifecycle while it is in a waiting state. Thus we use a specialised mutex. + synchronized (waitForChangesMutex) { + if (!isClosed) { + WaitForServerChangesWrapper wrapper = new WaitForServerChangesWrapper(); + waitingForServerChanges.set(wrapper); + boolean listenerRegistered = nativeWaitForDownloadCompletion(configuration.getPath()); + if (!listenerRegistered) { + waitingForServerChanges.set(null); + throw new ObjectServerError(ErrorCode.UNKNOWN, "It was not possible to download all changes. Has the SyncClient been started?"); + } + wrapper.waitForServerChanges(); + + // This might return after the session was closed. In that case, just ignore any result + try { + if (!isClosed) { + if (!wrapper.isSuccess()) { + wrapper.throwExceptionIfNeeded(); + } + } + } finally { + waitingForServerChanges.set(null); + } + } + } + } + + private void checkIfNotOnMainThread(String errorMessage) { + if (new AndroidCapabilities().isMainThread()) { + throw new IllegalStateException(errorMessage); + } + } + /** * Interface used to report any session errors. * @@ -351,6 +420,52 @@ private void clearScheduledAccessTokenRefresh() { } } + // Wrapper class for handling the async operations of the underlying SyncSession calling `async_wait_for_download_completion` + private static class WaitForServerChangesWrapper { + + private final CountDownLatch waitForChanges = new CountDownLatch(1); + private volatile boolean resultReceived = false; + private Long errorCode = null; + private String errorMessage; + + /** + * Block until the wait either completes or is terminated for other reasons. + */ + public void waitForServerChanges() throws InterruptedException { + if (!resultReceived) { + waitForChanges.await(); + } + } + + /** + * Process the result of a waiting action. This will also unblock anyone who called {@link #waitForChanges}. + * + * @param errorCode error code if an error occurred, {@code null} if changes were successfully downloaded. + * @param errorMessage error message (if any). + */ + public void handleResult(Long errorCode, String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + this.resultReceived = true; + waitForChanges.countDown(); + } + + public boolean isSuccess() { + return resultReceived && errorCode == null; + } + + /** + * Will throw an exception if the wait was terminated with an error. If it was canceled, this method will + * do nothing. + */ + public void throwExceptionIfNeeded() { + if (resultReceived && errorCode != null) { + throw new ObjectServerError(ErrorCode.UNKNOWN, String.format("Internal error (%d): %s", errorCode, errorMessage)); + } + } + } + private static native boolean nativeRefreshAccessToken(String path, String accessToken, String authURL); + private native boolean nativeWaitForDownloadCompletion(String path); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java b/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java new file mode 100644 index 0000000000..0460d297d5 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.exceptions; + +import io.realm.SyncConfiguration; + + +/** + * Exception class used when a Realm was interrupted while downloading the initial data set. + * This can only happen if {@link SyncConfiguration.Builder#waitForInitialRemoteData()} is set. + */ +public class DownloadingRealmInterruptedException extends RuntimeException { + public DownloadingRealmInterruptedException(SyncConfiguration syncConfig, Throwable exception) { + super("Realm was interrupted while downloading the latest changes from the server: " + syncConfig.getPath(), + exception); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index c976c0bc08..4bc4eff614 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -27,6 +27,8 @@ import io.realm.RealmConfiguration; import io.realm.SyncConfiguration; import io.realm.SyncManager; +import io.realm.SyncSession; +import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; import io.realm.internal.network.NetworkStateReceiver; @@ -129,4 +131,24 @@ private void invokeRemoveSession(SyncConfiguration syncConfig) { throw new RealmException("Could not remove session: " + syncConfig.toString(), e); } } + + @Override + public void downloadRemoteChanges(RealmConfiguration config) { + if (config instanceof SyncConfiguration) { + SyncConfiguration syncConfig = (SyncConfiguration) config; + if (syncConfig.shouldWaitForInitialRemoteData()) { + SyncSession session = SyncManager.getSession(syncConfig); + try { + session.downloadAllServerChanges(); + } catch (InterruptedException e) { + throw new DownloadingRealmInterruptedException(syncConfig, e); + } + } + } + } + + @Override + public boolean wasDownloadInterrupted(Throwable throwable) { + return (throwable instanceof DownloadingRealmInterruptedException); + } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncedRealmTests.java new file mode 100644 index 0000000000..4881139b82 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncedRealmTests.java @@ -0,0 +1,185 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.test.annotation.UiThreadTest; +import android.support.test.rule.UiThreadTestRule; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import io.realm.Realm; +import io.realm.RealmAsyncTask; +import io.realm.SyncConfiguration; +import io.realm.SyncCredentials; +import io.realm.SyncUser; +import io.realm.TestHelper; +import io.realm.exceptions.DownloadingRealmInterruptedException; +import io.realm.objectserver.utils.Constants; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + + +/** + * Catch all class for tests that not naturally fit anywhere else. + */ +public class SyncedRealmTests extends BaseIntegrationTest { + + @Rule + public RunInLooperThread looperThread = new RunInLooperThread(); + + @Rule + public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); + + @Test + @UiThreadTest + public void waitForInitialRemoteData_mainThreadThrows() { + final SyncUser user = loginUser(); + + SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .build(); + + Realm realm = null; + try { + realm = Realm.getInstance(config); + fail(); + } catch (IllegalStateException ignored) { + } finally { + if (realm != null) { + realm.close(); + } + } + } + + // Login user on a worker thread, so this method can be used from both UI and non-ui threads. + @NonNull + private SyncUser loginUser() { + final CountDownLatch userReady = new CountDownLatch(1); + final AtomicReference user = new AtomicReference<>(); + new Thread(new Runnable() { + @Override + public void run() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + user.set(SyncUser.login(credentials, Constants.AUTH_URL)); + userReady.countDown(); + } + }).start(); + TestHelper.awaitOrFail(userReady); + return user.get(); + } + + @Test + public void waitForInitialRemoteData() { + // TODO We can improve this test once we got Sync Progress Notifications. Right now we cannot detect + // when a Realm has been uploaded. + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .build(); + + Realm realm = null; + try { + realm = Realm.getInstance(config); + assertTrue(realm.isEmpty()); + } finally { + if (realm != null) { + realm.close(); + } + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .build(); + + for (int i = 0; i < 10; i++) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + Realm realm = null; + try { + // This will cause the download latch called later to immediately throw an InterruptedException. + Thread.currentThread().interrupt(); + realm = Realm.getInstance(config); + } catch (DownloadingRealmInterruptedException ignored) { + assertFalse(new File(config.getPath()).exists()); + } finally { + if (realm != null) { + realm.close(); + Realm.deleteRealm(config); + } + } + } + }); + t.start(); + t.join(); + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + @RunTestInLooperThread + public void waitForInitialData_resilientInCaseOfRetriesAsync() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .build(); + Random randomizer = new Random(); + + for (int i = 0; i < 10; i++) { + final int iteration = i; + RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + fail(); + } + + @Override + public void onError(Throwable exception) { + fail(exception.toString()); + } + }); + SystemClock.sleep(randomizer.nextInt(5)); + task.cancel(); + } + looperThread.testComplete(); + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java index 02d9ddf64c..71f273f865 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -18,6 +18,7 @@ public class Constants { + public static final String USER_REALM = "realm://127.0.0.1:9080/~/tests"; public static final String SYNC_SERVER_URL = "realm://127.0.0.1/tests"; public static final String SYNC_SERVER_URL_2 = "realm://127.0.0.1/tests2"; From 28f267132c23c64b9a7251da2ac068a5f75f914b Mon Sep 17 00:00:00 2001 From: LYK Date: Tue, 9 May 2017 22:50:12 +0900 Subject: [PATCH 0685/2110] Fix typo in CHANGELOG.md (#4617) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79dc58fcf5..5055d2304c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,12 @@ * [ObjectServer] Added support for `SyncUser.isAdmin()` (#4353). * [ObjectServer] Added support for changing passwords through `SyncUser.changePassword()` (#4423). -* [ObjectServer] Added support for `SyncConfigration.Builder.waitForInitialRemoteData()` (#4270). +* [ObjectServer] Added support for `SyncConfiguration.Builder.waitForInitialRemoteData()` (#4270). * Transient fields are now allowed in model classes, but are implicitly treated as having the `@Ignore` annotation (#4279). * Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). * Added `Realm.getInstanceAsync()` and `DynamicRealm.getInstanceAsync()` (#2299). * Added `DynamicRealmObject#linkingObjects(String,String)` to support linking objects on `DynamicRealm` (#4492). -* Changelisteners will now auto-expand variable names to be more descriptive when using Android Studio. +* Change listeners will now auto-expand variable names to be more descriptive when using Android Studio. ### Bug Fixes From deb25a87d08d93ebc77c5c8d9e81b8a632485b83 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Wed, 10 May 2017 11:25:15 -0700 Subject: [PATCH 0686/2110] LinkingObject queries (#4519) Refactor ColumnInfo and ColumnIndices for better control Refactor ColumnIndices to support new schema Refactor ColumnInfor to support new schema Refactor ProxyGeneration to support new Schema Refactor RealmQuery to support backlinked queries Code complete Static analysis tests passing Fix copy bug in ColumnIndices All non-backlink tests passing; All non-test FIXMEs gone Refactor all field parsing into the FieldDescriptor class Fix threading bugs in RunInLooperThread rule Standardize test timeouts Native isEmpty and isNotEmpty need to be taught about backlinks I believe all remaining work is C/C++ Disable Backlink Queries Respond to comments Revert createDynamicBacklinkResults signature Remove single leading space on line 1206 ins RealmQueryTests.java --- .../io/realm/processor/ClassMetaData.java | 48 +- .../java/io/realm/processor/Constants.java | 125 +-- .../processor/RealmProxyClassGenerator.java | 955 +++++++++--------- .../realm/processor/RealmProcessorTest.java | 85 +- .../io/realm/AllTypesRealmProxy.java | 106 +- .../io/realm/BooleansRealmProxy.java | 64 +- .../io/realm/NullTypesRealmProxy.java | 187 ++-- .../resources/io/realm/SimpleRealmProxy.java | 50 +- realm/realm-library/build.gradle | 2 +- .../java/io/realm/ColumnIndicesTests.java | 26 +- .../java/io/realm/ColumnInfoTests.java | 74 +- .../java/io/realm/DynamicRealmTests.java | 2 +- .../io/realm/LinkingObjectsDynamicTests.java | 33 +- .../io/realm/LinkingObjectsManagedTests.java | 233 ++--- .../io/realm/LinkingObjectsQueryTests.java | 572 +++++++++++ .../OrderedCollectionChangeSetTests.java | 30 +- .../androidTest/java/io/realm/QueryTests.java | 129 +++ .../java/io/realm/RealmObjectSchemaTests.java | 4 +- .../io/realm/RealmProxyMediatorTests.java | 5 +- .../java/io/realm/RealmQueryTests.java | 110 +- .../java/io/realm/RealmSchemaTests.java | 4 +- .../androidTest/java/io/realm/RealmTests.java | 197 ++-- .../androidTest/java/io/realm/TestHelper.java | 5 +- .../java/io/realm/entities/AllJavaTypes.java | 33 +- .../java/io/realm/entities/NullTypes.java | 69 +- .../io/realm/internal/CollectionTests.java | 20 +- .../java/io/realm/internal/JNIQueryTest.java | 452 ++++----- .../java/io/realm/internal/JNITableTest.java | 290 ++++-- .../realm/internal/SortDescriptorTests.java | 99 +- .../java/io/realm/SyncConfigurationTests.java | 8 +- .../java/io/realm/SyncUserTests.java | 10 +- .../main/cpp/io_realm_internal_TableQuery.cpp | 605 ++++++----- .../src/main/java/io/realm/BaseRealm.java | 5 +- .../src/main/java/io/realm/DynamicRealm.java | 2 +- .../java/io/realm/DynamicRealmObject.java | 27 +- .../io/realm/OrderedRealmCollectionImpl.java | 25 +- .../java/io/realm/OsRealmObjectSchema.java | 22 +- .../src/main/java/io/realm/OsRealmSchema.java | 16 +- .../src/main/java/io/realm/Realm.java | 22 +- .../src/main/java/io/realm/RealmCache.java | 16 +- .../main/java/io/realm/RealmFieldType.java | 9 +- .../src/main/java/io/realm/RealmList.java | 2 +- .../main/java/io/realm/RealmObjectSchema.java | 44 +- .../src/main/java/io/realm/RealmQuery.java | 289 +++--- .../src/main/java/io/realm/RealmResults.java | 20 +- .../src/main/java/io/realm/RealmSchema.java | 67 +- .../main/java/io/realm/SchemaConnector.java | 55 + .../io/realm/StandardRealmObjectSchema.java | 207 ++-- .../java/io/realm/StandardRealmSchema.java | 58 +- .../java/io/realm/internal/ColumnIndices.java | 148 ++- .../java/io/realm/internal/ColumnInfo.java | 249 ++++- .../io/realm/internal/FieldDescriptor.java | 106 -- .../java/io/realm/internal/NativeContext.java | 6 +- .../java/io/realm/internal/NativeObject.java | 4 +- .../java/io/realm/internal/SharedRealm.java | 38 +- .../io/realm/internal/SortDescriptor.java | 119 +-- .../main/java/io/realm/internal/Table.java | 46 +- .../java/io/realm/internal/TableQuery.java | 263 ++--- .../fields/CachedFieldDescriptor.java | 94 ++ .../fields/DynamicFieldDescriptor.java | 81 ++ .../internal/fields/FieldDescriptor.java | 287 ++++++ .../objectserver/ProcessCommitTests.java | 17 +- 62 files changed, 4239 insertions(+), 2737 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/QueryTests.java create mode 100644 realm/realm-library/src/main/java/io/realm/SchemaConnector.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index 474c39cc8f..b0ad1ef915 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -51,14 +51,14 @@ public class ClassMetaData { private final TypeElement classType; // Reference to model class. - private String className; // Model class simple name. + private final String className; // Model class simple name. + private final List fields = new ArrayList(); // List of all fields in the class except those @Ignored. + private final List indexedFields = new ArrayList(); // list of all fields marked @Index. + private final Set backlinks = new HashSet(); + private final Set nullableFields = new HashSet(); // Set of fields which can be nullable private String packageName; // package name for model class. private boolean hasDefaultConstructor; // True if model has a public no-arg constructor. private VariableElement primaryKey; // Reference to field used as primary key, if any. - private List fields = new ArrayList(); // List of all fields in the class except those @Ignored. - private List indexedFields = new ArrayList(); // list of all fields marked @Index. - private Set backlinks = new HashSet(); - private Set nullableFields = new HashSet(); // Set of fields which can be nullable private boolean containsToString; private boolean containsEquals; private boolean containsHashCode; @@ -117,7 +117,7 @@ public List getFields() { } public Set getBacklinkFields() { - return backlinks; + return Collections.unmodifiableSet(backlinks); } public String getInternalGetter(String fieldName) { @@ -129,7 +129,7 @@ public String getInternalSetter(String fieldName) { } public List getIndexedFields() { - return indexedFields; + return Collections.unmodifiableList(indexedFields); } public boolean hasPrimaryKey() { @@ -182,10 +182,7 @@ public boolean isIndexed(VariableElement variableElement) { * @return {@code true} if a VariableElement is primary key, {@code false} otherwise. */ public boolean isPrimaryKey(VariableElement variableElement) { - if (primaryKey == null) { - return false; - } - return primaryKey.equals(variableElement); + return primaryKey != null && primaryKey.equals(variableElement); } /** @@ -195,10 +192,7 @@ public boolean isPrimaryKey(VariableElement variableElement) { */ public boolean isModelClass() { String type = classType.toString(); - if (type.equals("io.realm.DynamicRealmObject")) { - return false; - } - return (!type.endsWith(".RealmObject") && !type.endsWith("RealmProxy")); + return !type.equals("io.realm.DynamicRealmObject") && !type.endsWith(".RealmObject") && !type.endsWith("RealmProxy"); } /** @@ -404,20 +398,20 @@ private boolean categorizeField(Element element) { private boolean categorizeIndexField(Element element, VariableElement variableElement) { // The field has the @Index annotation. It's only valid for column types: // STRING, DATE, INTEGER, BOOLEAN - String elementTypeCanonicalName = variableElement.asType().toString(); - String columnType = Constants.JAVA_TO_COLUMN_TYPES.get(elementTypeCanonicalName); - if (columnType != null && - (columnType.equals("RealmFieldType.STRING") || - columnType.equals("RealmFieldType.DATE") || - columnType.equals("RealmFieldType.INTEGER") || - columnType.equals("RealmFieldType.BOOLEAN"))) { - indexedFields.add(variableElement); - } else { - Utils.error(String.format("Field \"%s\" of type \"%s\" cannot be an @Index.", element, element.asType())); - return false; + Constants.RealmFieldType realmType = Constants.JAVA_TO_REALM_TYPES.get(variableElement.asType().toString()); + if (realmType != null) { + switch (realmType) { + case STRING: + case DATE: + case INTEGER: + case BOOLEAN: + indexedFields.add(variableElement); + return true; + } } - return true; + Utils.error(String.format("Field \"%s\" of type \"%s\" cannot be an @Index.", element, element.asType())); + return false; } // The field has the @Required annotation diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java index 4cd364a843..2daa18769a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java @@ -16,6 +16,7 @@ package io.realm.processor; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -37,73 +38,75 @@ public class Constants { static final String STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD = "throw new io.realm.exceptions.RealmException(\"\\\"%s\\\" field \\\"%s\\\" cannot be loaded from json\")"; - static final Map JAVA_TO_REALM_TYPES; - static { - JAVA_TO_REALM_TYPES = new HashMap(); - JAVA_TO_REALM_TYPES.put("byte", "Long"); - JAVA_TO_REALM_TYPES.put("short", "Long"); - JAVA_TO_REALM_TYPES.put("int", "Long"); - JAVA_TO_REALM_TYPES.put("long", "Long"); - JAVA_TO_REALM_TYPES.put("float", "Float"); - JAVA_TO_REALM_TYPES.put("double", "Double"); - JAVA_TO_REALM_TYPES.put("boolean", "Boolean"); - JAVA_TO_REALM_TYPES.put("java.lang.Byte", "Long"); - JAVA_TO_REALM_TYPES.put("java.lang.Short", "Long"); - JAVA_TO_REALM_TYPES.put("java.lang.Integer", "Long"); - JAVA_TO_REALM_TYPES.put("java.lang.Long", "Long"); - JAVA_TO_REALM_TYPES.put("java.lang.Float", "Float"); - JAVA_TO_REALM_TYPES.put("java.lang.Double", "Double"); - JAVA_TO_REALM_TYPES.put("java.lang.Boolean", "Boolean"); - JAVA_TO_REALM_TYPES.put("java.lang.String", "String"); - JAVA_TO_REALM_TYPES.put("java.util.Date", "Date"); - JAVA_TO_REALM_TYPES.put("byte[]", "BinaryByteArray"); - // TODO: add support for char and Char - } + /** + * Realm types and their corresponding Java types + */ + public enum RealmFieldType { + NOTYPE(null, "Void"), + INTEGER("INTEGER", "Long"), + FLOAT("FLOAT", "Float"), + DOUBLE("DOUBLE", "Double"), + BOOLEAN("BOOLEAN", "Boolean"), + STRING("STRING", "String"), + DATE("DATE", "Date"), + BINARY("BINARY", "BinaryByteArray"), + OBJECT("OBJECT", "Object"), + LIST("LIST", "List"), + BACKLINK("BACKLINK", null); - static final Map JAVA_TO_COLUMN_TYPES; + private final String realmType; + private final String javaType; - static { - JAVA_TO_COLUMN_TYPES = new HashMap(); - JAVA_TO_COLUMN_TYPES.put("byte", "RealmFieldType.INTEGER"); - JAVA_TO_COLUMN_TYPES.put("short", "RealmFieldType.INTEGER"); - JAVA_TO_COLUMN_TYPES.put("int", "RealmFieldType.INTEGER"); - JAVA_TO_COLUMN_TYPES.put("long", "RealmFieldType.INTEGER"); - JAVA_TO_COLUMN_TYPES.put("float", "RealmFieldType.FLOAT"); - JAVA_TO_COLUMN_TYPES.put("double", "RealmFieldType.DOUBLE"); - JAVA_TO_COLUMN_TYPES.put("boolean", "RealmFieldType.BOOLEAN"); - JAVA_TO_COLUMN_TYPES.put("java.lang.Byte", "RealmFieldType.INTEGER"); - JAVA_TO_COLUMN_TYPES.put("java.lang.Short", "RealmFieldType.INTEGER"); - JAVA_TO_COLUMN_TYPES.put("java.lang.Integer", "RealmFieldType.INTEGER"); - JAVA_TO_COLUMN_TYPES.put("java.lang.Long", "RealmFieldType.INTEGER"); - JAVA_TO_COLUMN_TYPES.put("java.lang.Float", "RealmFieldType.FLOAT"); - JAVA_TO_COLUMN_TYPES.put("java.lang.Double", "RealmFieldType.DOUBLE"); - JAVA_TO_COLUMN_TYPES.put("java.lang.Boolean", "RealmFieldType.BOOLEAN"); - JAVA_TO_COLUMN_TYPES.put("java.lang.String", "RealmFieldType.STRING"); - JAVA_TO_COLUMN_TYPES.put("java.util.Date", "RealmFieldType.DATE"); - JAVA_TO_COLUMN_TYPES.put("byte[]", "RealmFieldType.BINARY"); + /** + * @param realmType The simple name of the Enum type used in the Java bindings, to represent this type. + * @param javaType The simple name of the Java type needed to store this Realm Type + */ + RealmFieldType(String realmType, String javaType) { + this.realmType = "RealmFieldType." + realmType; + this.javaType = javaType; + } + + /** + * Get the name of the enum, used in the Java bindings, used to represent the corresponding type. + * @return the name of the enum used to represent this Realm Type + */ + public String getRealmType() { + return realmType; + } + + /** + * Get the name of the Java type needed to store this Realm Type + * @return the simple name for the corresponding Java type + */ + public String getJavaType() { + return javaType; + } } - static final Map JAVA_TO_FIELD_SETTER; + + static final Map JAVA_TO_REALM_TYPES; static { - JAVA_TO_FIELD_SETTER = new HashMap(); - JAVA_TO_FIELD_SETTER.put("byte", "setByte"); - JAVA_TO_FIELD_SETTER.put("short", "setShort"); - JAVA_TO_FIELD_SETTER.put("int", "setInt"); - JAVA_TO_FIELD_SETTER.put("long", "setLong"); - JAVA_TO_FIELD_SETTER.put("float", "setFloat"); - JAVA_TO_FIELD_SETTER.put("double", "setDouble"); - JAVA_TO_FIELD_SETTER.put("boolean", "setBoolean"); - JAVA_TO_FIELD_SETTER.put("java.lang.Byte", "set"); - JAVA_TO_FIELD_SETTER.put("java.lang.Short", "set"); - JAVA_TO_FIELD_SETTER.put("java.lang.Integer", "set"); - JAVA_TO_FIELD_SETTER.put("java.lang.Long", "set"); - JAVA_TO_FIELD_SETTER.put("java.lang.Float", "set"); - JAVA_TO_FIELD_SETTER.put("java.lang.Double", "set"); - JAVA_TO_FIELD_SETTER.put("java.lang.Boolean", "set"); - JAVA_TO_FIELD_SETTER.put("java.lang.String", "set"); - JAVA_TO_FIELD_SETTER.put("java.util.Date", "set"); - JAVA_TO_FIELD_SETTER.put("byte[]", "set"); + Map m = new HashMap(); + m.put("byte", RealmFieldType.INTEGER); + m.put("short", RealmFieldType.INTEGER); + m.put("int", RealmFieldType.INTEGER); + m.put("long", RealmFieldType.INTEGER); + m.put("float", RealmFieldType.FLOAT); + m.put("double", RealmFieldType.DOUBLE); + m.put("boolean", RealmFieldType.BOOLEAN); + m.put("java.lang.Byte", RealmFieldType.INTEGER); + m.put("java.lang.Short", RealmFieldType.INTEGER); + m.put("java.lang.Integer", RealmFieldType.INTEGER); + m.put("java.lang.Long", RealmFieldType.INTEGER); + m.put("java.lang.Float", RealmFieldType.FLOAT); + m.put("java.lang.Double", RealmFieldType.DOUBLE); + m.put("java.lang.Boolean", RealmFieldType.BOOLEAN); + m.put("java.lang.String", RealmFieldType.STRING); + m.put("java.util.Date", RealmFieldType.DATE); + m.put("byte[]", RealmFieldType.BINARY); + // TODO: add support for char and Char + JAVA_TO_REALM_TYPES = Collections.unmodifiableMap(m); } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 6cb95cc33d..aff0f62628 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -109,7 +109,7 @@ public void generate() throws IOException, UnsupportedOperationException { interfaceName) .emitEmptyLine(); - emitColumnIndicesClass(writer); + emitColumnInfoClass(writer); emitClassFields(writer); emitConstructor(writer); @@ -141,75 +141,73 @@ public void generate() throws IOException, UnsupportedOperationException { writer.close(); } - private void emitColumnIndicesClass(JavaWriter writer) throws IOException { + private void emitColumnInfoClass(JavaWriter writer) throws IOException { writer.beginType( columnInfoClassName(), // full qualified name of the item to generate "class", // the type of the item EnumSet.of(Modifier.STATIC, Modifier.FINAL), // modifiers to apply - "ColumnInfo", // base class - "Cloneable") // interfaces - .emitEmptyLine(); + "ColumnInfo"); // base class // fields for (VariableElement variableElement : metadata.getFields()) { - writer.emitField("long", columnIndexVarName(variableElement), - EnumSet.of(Modifier.PUBLIC)); + writer.emitField("long", columnIndexVarName(variableElement)); } writer.emitEmptyLine(); - // constructor - writer.beginConstructor(EnumSet.noneOf(Modifier.class), - "String", "path", - "Table", "table"); - writer.emitStatement("final Map indicesMap = new HashMap(%s)", - metadata.getFields().size()); - for (VariableElement variableElement : metadata.getFields()) { - final String columnName = variableElement.getSimpleName().toString(); - final String columnIndexVarName = columnIndexVarName(variableElement); - writer.emitStatement("this.%s = getValidColumnIndex(path, table, \"%s\", \"%s\")", - columnIndexVarName, simpleClassName, columnName) - .emitStatement("indicesMap.put(\"%s\", this.%s)", columnName, columnIndexVarName); + // constructor #1 + writer.beginConstructor( + EnumSet.noneOf(Modifier.class), + "SharedRealm", "realm", "Table", "table"); + writer.emitStatement("super(%s)", metadata.getFields().size()); + for (VariableElement field : metadata.getFields()) { + writer.emitStatement( + "this.%1$sIndex = addColumnDetails(table, \"%1$s\", %2$s)", + field.getSimpleName().toString(), getRealmTypeChecked(field).getRealmType()); } + for (Backlink backlink : metadata.getBacklinkFields()) { + writer.emitStatement( + "addBacklinkDetails(realm, \"%1$s\", \"%2$s\", \"%3$s\")", + backlink.getTargetField(), Utils.stripPackage(backlink.getSourceClass()), backlink.getSourceField()); + } + writer.endConstructor() + .emitEmptyLine(); - writer.emitEmptyLine() - .emitStatement("setIndicesMap(indicesMap)"); + // constructor #2 + writer.beginConstructor( + EnumSet.noneOf(Modifier.class), + "ColumnInfo", "src", "boolean", "mutable"); + writer.emitStatement("super(src, mutable)") + .emitStatement("copy(src, this)"); writer.endConstructor() .emitEmptyLine(); - // copyColumnInfoFrom method + // no-args copy method writer.emitAnnotation("Override") .beginMethod( - "void", // return type - "copyColumnInfoFrom", // method name - EnumSet.of(Modifier.PUBLIC, Modifier.FINAL), // modifiers - "ColumnInfo", "other"); // parameters - { - writer.emitStatement("final %1$s otherInfo = (%1$s) other", columnInfoClassName()); - - // copy field values - for (VariableElement variableElement : metadata.getFields()) { - writer.emitStatement("this.%1$s = otherInfo.%1$s", columnIndexVarName(variableElement)); - } - writer.emitEmptyLine() - .emitStatement("setIndicesMap(otherInfo.getIndicesMap())"); - } + "ColumnInfo", // return type + "copy", // method name + EnumSet.of(Modifier.PROTECTED, Modifier.FINAL), // modifiers + "boolean", "mutable"); // parameters + writer.emitStatement("return new %s(this, mutable)", columnInfoClassName()); writer.endMethod() .emitEmptyLine(); - // clone method - //@formatter:off + // copy method writer.emitAnnotation("Override") - .beginMethod( - columnInfoClassName(), // return type - "clone", // method name - EnumSet.of(Modifier.PUBLIC, Modifier.FINAL)) // modifiers - // method body - .emitStatement("return (%1$s) super.clone()", columnInfoClassName()) - .endMethod() - .emitEmptyLine(); - //@formatter:on + .beginMethod( + "void", // return type + "copy", // method name + EnumSet.of(Modifier.PROTECTED, Modifier.FINAL), // modifiers + "ColumnInfo", "rawSrc", "ColumnInfo", "rawDst"); // parameters + writer.emitStatement("final %1$s src = (%1$s) rawSrc", columnInfoClassName()); + writer.emitStatement("final %1$s dst = (%1$s) rawDst", columnInfoClassName()); + for (VariableElement variableElement : metadata.getFields()) { + writer.emitStatement("dst.%1$s = src.%1$s", columnIndexVarName(variableElement)); + } + writer.endMethod(); - writer.endType(); + writer.endType() + .emitEmptyLine(); } private void emitClassFields(JavaWriter writer) throws IOException { @@ -232,14 +230,14 @@ private void emitClassFields(JavaWriter writer) throws IOException { //@formatter:off writer.emitField("List", "FIELD_NAMES", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)) - .beginInitializer(true) - .emitStatement("List fieldNames = new ArrayList()"); + .beginInitializer(true) + .emitStatement("List fieldNames = new ArrayList()"); for (VariableElement field : metadata.getFields()) { writer.emitStatement("fieldNames.add(\"%s\")", field.getSimpleName().toString()); } writer.emitStatement("FIELD_NAMES = Collections.unmodifiableList(fieldNames)") - .endInitializer() - .emitEmptyLine(); + .endInitializer() + .emitEmptyLine(); //@formatter:on } @@ -247,9 +245,9 @@ private void emitClassFields(JavaWriter writer) throws IOException { private void emitConstructor(JavaWriter writer) throws IOException { // FooRealmProxy(ColumnInfo) writer.beginConstructor(EnumSet.noneOf(Modifier.class)) - .emitStatement("proxyState.setConstructionFinished()") - .endConstructor() - .emitEmptyLine(); + .emitStatement("proxyState.setConstructionFinished()") + .endConstructor() + .emitEmptyLine(); } //@formatter:on @@ -281,20 +279,21 @@ private void emitPrimitiveType( final VariableElement field, final String fieldName, String fieldTypeCanonicalName) throws IOException { - final String realmType = Constants.JAVA_TO_REALM_TYPES.get(fieldTypeCanonicalName); + + final String fieldJavaType = getRealmTypeChecked(field).getJavaType(); // Getter //@formatter:off writer.emitAnnotation("Override"); writer.emitAnnotation("SuppressWarnings", "\"cast\"") - .beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) - .emitStatement("proxyState.getRealm$realm().checkIfValid()"); + .beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) + .emitStatement("proxyState.getRealm$realm().checkIfValid()"); // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here. if (metadata.isNullable(field) && !Utils.isString(field) && !Utils.isByteArray(field)) { writer.beginControlFlow("if (proxyState.getRow$realm().isNull(%s))", fieldIndexVariableReference(field)) - .emitStatement("return null") - .endControlFlow(); + .emitStatement("return null") + .endControlFlow(); } //@formatter:on @@ -308,7 +307,7 @@ private void emitPrimitiveType( } writer.emitStatement( "return (%s) proxyState.getRow$realm().get%s(%s)", - castingBackType, realmType, fieldIndexVariableReference(field)); + castingBackType, fieldJavaType, fieldIndexVariableReference(field)); writer.endMethod() .emitEmptyLine(); @@ -324,20 +323,20 @@ public void emit(JavaWriter writer) throws IOException { //@formatter:off if (metadata.isNullable(field)) { writer.beginControlFlow("if (value == null)") - .emitStatement("row.getTable().setNull(%s, row.getIndex(), true)", - fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); + .emitStatement("row.getTable().setNull(%s, row.getIndex(), true)", + fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow(); } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { writer.beginControlFlow("if (value == null)") - .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) - .endControlFlow(); + .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + .endControlFlow(); } //@formatter:on writer.emitStatement( "row.getTable().set%s(%s, row.getIndex(), value, true)", - realmType, fieldIndexVariableReference(field)); + fieldJavaType, fieldIndexVariableReference(field)); writer.emitStatement("return"); } }); @@ -351,20 +350,20 @@ public void emit(JavaWriter writer) throws IOException { //@formatter:off if (metadata.isNullable(field)) { writer.beginControlFlow("if (value == null)") - .emitStatement("proxyState.getRow$realm().setNull(%s)", fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); + .emitStatement("proxyState.getRow$realm().setNull(%s)", fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow(); } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { // Same reason, throw IAE earlier. writer - .beginControlFlow("if (value == null)") - .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) - .endControlFlow(); + .beginControlFlow("if (value == null)") + .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + .endControlFlow(); } //@formatter:on writer.emitStatement( "proxyState.getRow$realm().set%s(%s, value)", - realmType, fieldIndexVariableReference(field)); + fieldJavaType, fieldIndexVariableReference(field)); } writer.endMethod(); } @@ -374,23 +373,22 @@ public void emit(JavaWriter writer) throws IOException { */ //@formatter:off private void emitRealmModel( - JavaWriter writer, - final VariableElement field, - String fieldName, - String fieldTypeCanonicalName) throws IOException - { + JavaWriter writer, + final VariableElement field, + String fieldName, + String fieldTypeCanonicalName) throws IOException { // Getter writer.emitAnnotation("Override"); writer.beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) - .emitStatement("proxyState.getRealm$realm().checkIfValid()") - .beginControlFlow("if (proxyState.getRow$realm().isNullLink(%s))", fieldIndexVariableReference(field)) + .emitStatement("proxyState.getRealm$realm().checkIfValid()") + .beginControlFlow("if (proxyState.getRow$realm().isNullLink(%s))", fieldIndexVariableReference(field)) .emitStatement("return null") - .endControlFlow() - .emitStatement("return proxyState.getRealm$realm().get(%s.class, proxyState.getRow$realm().getLink(%s), false, Collections.emptyList())", - fieldTypeCanonicalName, fieldIndexVariableReference(field)) - .endMethod() - .emitEmptyLine(); + .endControlFlow() + .emitStatement("return proxyState.getRealm$realm().get(%s.class, proxyState.getRow$realm().getLink(%s), false, Collections.emptyList())", + fieldTypeCanonicalName, fieldIndexVariableReference(field)) + .endMethod() + .emitEmptyLine(); // Setter writer.emitAnnotation("Override"); @@ -400,44 +398,44 @@ fieldTypeCanonicalName, fieldIndexVariableReference(field)) public void emit(JavaWriter writer) throws IOException { // check excludeFields writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", - field.getSimpleName().toString()) - .emitStatement("return") - .endControlFlow(); + field.getSimpleName().toString()) + .emitStatement("return") + .endControlFlow(); writer.beginControlFlow("if (value != null && !RealmObject.isManaged(value))") - .emitStatement("value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value)") - .endControlFlow(); + .emitStatement("value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value)") + .endControlFlow(); // set value as default value writer.emitStatement("final Row row = proxyState.getRow$realm()"); writer.beginControlFlow("if (value == null)") - .emitSingleLineComment("Table#nullifyLink() does not support default value. Just using Row.") - .emitStatement("row.nullifyLink(%s)", fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); + .emitSingleLineComment("Table#nullifyLink() does not support default value. Just using Row.") + .emitStatement("row.nullifyLink(%s)", fieldIndexVariableReference(field)) + .emitStatement("return") + .endControlFlow(); writer.beginControlFlow("if (!RealmObject.isValid(value))") - .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") - .endControlFlow(); + .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") + .endControlFlow(); writer.beginControlFlow("if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") - .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") - .endControlFlow(); + .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") + .endControlFlow(); writer.emitStatement("row.getTable().setLink(%s, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true)", - fieldIndexVariableReference(field)); + fieldIndexVariableReference(field)); writer.emitStatement("return"); } }); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()") - .beginControlFlow("if (value == null)") + .beginControlFlow("if (value == null)") .emitStatement("proxyState.getRow$realm().nullifyLink(%s)", fieldIndexVariableReference(field)) .emitStatement("return") - .endControlFlow() - .beginControlFlow("if (!(RealmObject.isManaged(value) && RealmObject.isValid(value)))") + .endControlFlow() + .beginControlFlow("if (!(RealmObject.isManaged(value) && RealmObject.isValid(value)))") .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") - .endControlFlow() - .beginControlFlow("if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") + .endControlFlow() + .beginControlFlow("if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") - .endControlFlow() - .emitStatement("proxyState.getRow$realm().setLink(%s, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex())", fieldIndexVariableReference(field)) - .endMethod(); + .endControlFlow() + .emitStatement("proxyState.getRow$realm().setLink(%s, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex())", fieldIndexVariableReference(field)) + .endMethod(); } //@formatter:on @@ -446,28 +444,27 @@ public void emit(JavaWriter writer) throws IOException { */ //@formatter:off private void emitRealmList( - JavaWriter writer, - final VariableElement field, - String fieldName, - String fieldTypeCanonicalName) throws IOException - { + JavaWriter writer, + final VariableElement field, + String fieldName, + String fieldTypeCanonicalName) throws IOException { String genericType = Utils.getGenericTypeQualifiedName(field); // Getter writer.emitAnnotation("Override"); writer.beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) - .emitStatement("proxyState.getRealm$realm().checkIfValid()") - .emitSingleLineComment("use the cached value if available") - .beginControlFlow("if (" + fieldName + "RealmList != null)") + .emitStatement("proxyState.getRealm$realm().checkIfValid()") + .emitSingleLineComment("use the cached value if available") + .beginControlFlow("if (" + fieldName + "RealmList != null)") .emitStatement("return " + fieldName + "RealmList") - .nextControlFlow("else") + .nextControlFlow("else") .emitStatement("LinkView linkView = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)) .emitStatement(fieldName + "RealmList = new RealmList<%s>(%s.class, linkView, proxyState.getRealm$realm())", - genericType, genericType) + genericType, genericType) .emitStatement("return " + fieldName + "RealmList") - .endControlFlow() - .endMethod() - .emitEmptyLine(); + .endControlFlow() + .endMethod() + .emitEmptyLine(); // Setter writer.emitAnnotation("Override"); @@ -486,33 +483,33 @@ public void emit(JavaWriter writer) throws IOException { .emitStatement("final RealmList<%1$s> original = value", modelFqcn) .emitStatement("value = new RealmList<%1$s>()", modelFqcn) .beginControlFlow("for (%1$s item : original)", modelFqcn) - .beginControlFlow("if (item == null || RealmObject.isManaged(item))") - .emitStatement("value.add(item)") - .nextControlFlow("else") - .emitStatement("value.add(realm.copyToRealm(item))") - .endControlFlow() + .beginControlFlow("if (item == null || RealmObject.isManaged(item))") + .emitStatement("value.add(item)") + .nextControlFlow("else") + .emitStatement("value.add(realm.copyToRealm(item))") .endControlFlow() - .endControlFlow(); + .endControlFlow() + .endControlFlow(); // LinkView currently does not support default value feature. Just fallback to normal code. } }); writer.emitStatement("proxyState.getRealm$realm().checkIfValid()") - .emitStatement("LinkView links = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)) - .emitStatement("links.clear()") - .beginControlFlow("if (value == null)") + .emitStatement("LinkView links = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)) + .emitStatement("links.clear()") + .beginControlFlow("if (value == null)") .emitStatement("return") - .endControlFlow() - .beginControlFlow("for (RealmModel linkedObject : (RealmList) value)") + .endControlFlow() + .beginControlFlow("for (RealmModel linkedObject : (RealmList) value)") .beginControlFlow("if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject)))") - .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must be a valid managed object.\")") + .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must be a valid managed object.\")") .endControlFlow() .beginControlFlow("if (((RealmObjectProxy)linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") - .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must belong to the same Realm.\")") + .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must belong to the same Realm.\")") .endControlFlow() .emitStatement("links.add(((RealmObjectProxy)linkedObject).realmGet$proxyState().getRow$realm().getIndex())") - .endControlFlow() - .endMethod(); + .endControlFlow() + .endMethod(); } //@formatter:on @@ -543,21 +540,21 @@ private void emitInjectContextMethod(JavaWriter writer) throws IOException { "void", // Return type "realm$injectObjectContext", // Method name EnumSet.of(Modifier.PUBLIC) // Modifiers - ); // Argument type & argument name + ); // Argument type & argument name writer.beginControlFlow("if (this.proxyState != null)") .emitStatement("return") - .endControlFlow() - .emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()") - .emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()) - .emitStatement("this.proxyState = new ProxyState<%1$s>(this)", qualifiedClassName) - .emitStatement("proxyState.setRealm$realm(context.getRealm())") - .emitStatement("proxyState.setRow$realm(context.getRow())") - .emitStatement("proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue())") - .emitStatement("proxyState.setExcludeFields$realm(context.getExcludeFields())"); + .endControlFlow() + .emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()") + .emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()) + .emitStatement("this.proxyState = new ProxyState<%1$s>(this)", qualifiedClassName) + .emitStatement("proxyState.setRealm$realm(context.getRealm())") + .emitStatement("proxyState.setRow$realm(context.getRow())") + .emitStatement("proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue())") + .emitStatement("proxyState.setExcludeFields$realm(context.getExcludeFields())"); writer.endMethod() - .emitEmptyLine(); + .emitEmptyLine(); } //@formatter:on @@ -570,16 +567,16 @@ private void emitBacklinkFieldAccessors(JavaWriter writer) throws IOException { // Getter, no setter writer.emitAnnotation("Override"); writer.beginMethod(realmResultsType, metadata.getInternalGetter(backlink.getTargetField()), EnumSet.of(Modifier.PUBLIC)) - .emitStatement("BaseRealm realm = proxyState.getRealm$realm()") - .emitStatement("realm.checkIfValid()") - .emitStatement("proxyState.getRow$realm().checkIfAttached()") - .beginControlFlow("if (" + cacheFieldName + " == null)") - .emitStatement(cacheFieldName + " = RealmResults.createBacklinkResults((Realm) realm, (UncheckedRow) proxyState.getRow$realm(), %s.class, \"%s\")", - backlink.getSourceClass(), backlink.getSourceField()) - .endControlFlow() - .emitStatement("return " + cacheFieldName) - .endMethod() - .emitEmptyLine(); + .emitStatement("BaseRealm realm = proxyState.getRealm$realm()") + .emitStatement("realm.checkIfValid()") + .emitStatement("proxyState.getRow$realm().checkIfAttached()") + .beginControlFlow("if (" + cacheFieldName + " == null)") + .emitStatement(cacheFieldName + " = RealmResults.createBacklinkResults(realm, proxyState.getRow$realm(), %s.class, \"%s\")", + backlink.getSourceClass(), backlink.getSourceField()) + .endControlFlow() + .emitStatement("return " + cacheFieldName) + .endMethod() + .emitEmptyLine(); } } //@formatter:on @@ -587,10 +584,10 @@ private void emitBacklinkFieldAccessors(JavaWriter writer) throws IOException { //@formatter:off private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOException { writer.emitAnnotation("Override") - .beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)) + .beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)) .emitStatement("return proxyState") - .endMethod() - .emitEmptyLine(); + .endMethod() + .emitEmptyLine(); } //@formatter:on @@ -607,32 +604,41 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep // For each field generate corresponding table index constant for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); - String fieldTypeCanonicalName = field.asType().toString(); String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); - if (Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeCanonicalName)) { - String nullableFlag = (metadata.isNullable(field) ? "!" : "") + "Property.REQUIRED"; - String indexedFlag = (metadata.isIndexed(field) ? "" : "!") + "Property.INDEXED"; - String primaryKeyFlag = (metadata.isPrimaryKey(field) ? "" : "!") + "Property.PRIMARY_KEY"; - writer.emitStatement("realmObjectSchema.add(\"%s\", %s, %s, %s, %s)", - fieldName, - Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeCanonicalName), - primaryKeyFlag, - indexedFlag, - nullableFlag); - } else if (Utils.isRealmModel(field)) { - writer.beginControlFlow("if (!realmSchema.contains(\"" + fieldTypeSimpleName + "\"))") - .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", fieldTypeSimpleName, Constants.PROXY_SUFFIX) - .endControlFlow() - .emitStatement("realmObjectSchema.add(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\"))", - fieldName, fieldTypeSimpleName); - } else if (Utils.isRealmList(field)) { - String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); - writer.beginControlFlow("if (!realmSchema.contains(\"" + genericTypeSimpleName + "\"))") - .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", genericTypeSimpleName, Constants.PROXY_SUFFIX) - .endControlFlow() - .emitStatement("realmObjectSchema.add(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\"))", - fieldName, genericTypeSimpleName); + Constants.RealmFieldType fieldType = getRealmType(field); + switch (fieldType) { + case NOTYPE: + // Perhaps this should fail quickly? + break; + + case OBJECT: + writer.beginControlFlow("if (!realmSchema.contains(\"" + fieldTypeSimpleName + "\"))") + .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", fieldTypeSimpleName, Constants.PROXY_SUFFIX) + .endControlFlow() + .emitStatement("realmObjectSchema.add(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\"))", + fieldName, fieldTypeSimpleName); + break; + + case LIST: + String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); + writer.beginControlFlow("if (!realmSchema.contains(\"" + genericTypeSimpleName + "\"))") + .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", genericTypeSimpleName, Constants.PROXY_SUFFIX) + .endControlFlow() + .emitStatement("realmObjectSchema.add(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\"))", + fieldName, genericTypeSimpleName); + break; + + default: + String nullableFlag = (metadata.isNullable(field) ? "!" : "") + "Property.REQUIRED"; + String indexedFlag = (metadata.isIndexed(field) ? "" : "!") + "Property.INDEXED"; + String primaryKeyFlag = (metadata.isPrimaryKey(field) ? "" : "!") + "Property.PRIMARY_KEY"; + writer.emitStatement("realmObjectSchema.add(\"%s\", %s, %s, %s, %s)", + fieldName, + fieldType.getRealmType(), + primaryKeyFlag, + indexedFlag, + nullableFlag); } } writer.emitStatement("return realmObjectSchema"); @@ -686,7 +692,7 @@ private void emitValidateTableMethod(JavaWriter writer) throws IOException { .emitEmptyLine(); // create an instance of ColumnInfo - writer.emitStatement("final %1$s columnInfo = new %1$s(sharedRealm.getPath(), table)", columnInfoClassName()) + writer.emitStatement("final %1$s columnInfo = new %1$s(sharedRealm, table)", columnInfoClassName()) .emitEmptyLine(); // verify primary key definition was not altered @@ -755,7 +761,7 @@ private void emitValidateRealmType(JavaWriter writer, VariableElement field, Str "\")", fieldName); writer.endControlFlow(); writer.beginControlFlow("if (columnTypes.get(\"%s\") != %s)", - fieldName, Constants.JAVA_TO_COLUMN_TYPES.get(fieldTypeQualifiedName)); + fieldName, getRealmTypeChecked(field).getRealmType()); emitMigrationNeededException(writer, "\"Invalid type '%s' for field '%s' in existing Realm file.\")", Utils.getFieldTypeSimpleName(field), fieldName); writer.endControlFlow(); @@ -907,18 +913,18 @@ private void emitValidateBacklink(JavaWriter writer, Backlink backlink) throws I //@formatter:off private void emitGetTableNameMethod(JavaWriter writer) throws IOException { writer.beginMethod("String", "getTableName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) - .emitStatement("return \"%s%s\"", Constants.TABLE_PREFIX, simpleClassName) - .endMethod() - .emitEmptyLine(); + .emitStatement("return \"%s%s\"", Constants.TABLE_PREFIX, simpleClassName) + .endMethod() + .emitEmptyLine(); } //@formatter:on //@formatter:off private void emitGetFieldNamesMethod(JavaWriter writer) throws IOException { writer.beginMethod("List", "getFieldNames", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) - .emitStatement("return FIELD_NAMES") - .endMethod() - .emitEmptyLine(); + .emitStatement("return FIELD_NAMES") + .endMethod() + .emitEmptyLine(); } //@formatter:on @@ -932,16 +938,16 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { ); writer - .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId)") + .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId)") .emitStatement("throw new IllegalArgumentException(\"Objects which belong to Realm instances in other" + " threads cannot be copied into this Realm instance.\")") - .endControlFlow(); + .endControlFlow(); // If object is already in the Realm there is nothing to update writer - .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") + .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") .emitStatement("return object") - .endControlFlow(); + .endControlFlow(); writer.emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()"); @@ -950,74 +956,74 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { .emitStatement("return (%s) cachedRealmObject", qualifiedClassName) .nextControlFlow("else"); - if (!metadata.hasPrimaryKey()) { - writer.emitStatement("return copy(realm, object, update, cache)"); - } else { - writer + if (!metadata.hasPrimaryKey()) { + writer.emitStatement("return copy(realm, object, update, cache)"); + } else { + writer .emitStatement("%s realmObject = null", qualifiedClassName) .emitStatement("boolean canUpdate = update") .beginControlFlow("if (canUpdate)") - .emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName) - .emitStatement("long pkColumnIndex = table.getPrimaryKey()"); + .emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName) + .emitStatement("long pkColumnIndex = table.getPrimaryKey()"); - String primaryKeyGetter = metadata.getPrimaryKeyGetter(); - VariableElement primaryKeyElement = metadata.getPrimaryKey(); - if (metadata.isNullable(primaryKeyElement)) { - if (Utils.isString(primaryKeyElement)) { - writer + String primaryKeyGetter = metadata.getPrimaryKeyGetter(); + VariableElement primaryKeyElement = metadata.getPrimaryKey(); + if (metadata.isNullable(primaryKeyElement)) { + if (Utils.isString(primaryKeyElement)) { + writer .emitStatement("String value = ((%s) object).%s()", interfaceName, primaryKeyGetter) .emitStatement("long rowIndex = Table.NO_MATCH") .beginControlFlow("if (value == null)") - .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") + .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") .nextControlFlow("else") - .emitStatement("rowIndex = table.findFirstString(pkColumnIndex, value)") + .emitStatement("rowIndex = table.findFirstString(pkColumnIndex, value)") .endControlFlow(); - } else { - writer + } else { + writer .emitStatement("Number value = ((%s) object).%s()", interfaceName, primaryKeyGetter) .emitStatement("long rowIndex = Table.NO_MATCH") .beginControlFlow("if (value == null)") - .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") + .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") .nextControlFlow("else") - .emitStatement("rowIndex = table.findFirstLong(pkColumnIndex, value.longValue())") + .emitStatement("rowIndex = table.findFirstLong(pkColumnIndex, value.longValue())") .endControlFlow(); - } - } else { - String pkType = Utils.isString(metadata.getPrimaryKey()) ? "String" : "Long"; - writer.emitStatement("long rowIndex = table.findFirst%s(pkColumnIndex, ((%s) object).%s())", - pkType, interfaceName, primaryKeyGetter); } + } else { + String pkType = Utils.isString(metadata.getPrimaryKey()) ? "String" : "Long"; + writer.emitStatement("long rowIndex = table.findFirst%s(pkColumnIndex, ((%s) object).%s())", + pkType, interfaceName, primaryKeyGetter); + } - writer + writer .beginControlFlow("if (rowIndex != Table.NO_MATCH)") - .beginControlFlow("try") - .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex)," + - " realm.schema.getColumnInfo(%s.class)," + - " false, Collections. emptyList())", qualifiedClassName) - .emitStatement("realmObject = new %s()", qualifiedGeneratedClassName) - .emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") - .nextControlFlow("finally") - .emitStatement("objectContext.clear()") - .endControlFlow() + .beginControlFlow("try") + .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex)," + + " realm.schema.getColumnInfo(%s.class)," + + " false, Collections. emptyList())", qualifiedClassName) + .emitStatement("realmObject = new %s()", qualifiedGeneratedClassName) + .emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") + .nextControlFlow("finally") + .emitStatement("objectContext.clear()") + .endControlFlow() .nextControlFlow("else") - .emitStatement("canUpdate = false") + .emitStatement("canUpdate = false") .endControlFlow(); - writer.endControlFlow(); + writer.endControlFlow(); - writer + writer .emitEmptyLine() .beginControlFlow("if (canUpdate)") - .emitStatement("return update(realm, realmObject, object, cache)") + .emitStatement("return update(realm, realmObject, object, cache)") .nextControlFlow("else") - .emitStatement("return copy(realm, object, update, cache)") + .emitStatement("return copy(realm, object, update, cache)") .endControlFlow(); - } + } writer.endControlFlow(); writer.endMethod() - .emitEmptyLine(); + .emitEmptyLine(); } //@formatter:on @@ -1036,26 +1042,26 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer .emitStatement("Number %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); + .emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter); + if (isUpdate) { + writer.nextControlFlow("else") + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); + } + writer.endControlFlow(); } else if ("double".equals(fieldType)) { writer.emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); - } else if("java.lang.Double".equals(fieldType)) { + } else if ("java.lang.Double".equals(fieldType)) { writer .emitStatement("Double %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); + .emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); + if (isUpdate) { + writer.nextControlFlow("else") + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); + } + writer.endControlFlow(); } else if ("float".equals(fieldType)) { writer.emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); @@ -1064,12 +1070,12 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer .emitStatement("Float %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); + .emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); + if (isUpdate) { + writer.nextControlFlow("else") + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); + } + writer.endControlFlow(); } else if ("boolean".equals(fieldType)) { writer.emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); @@ -1078,46 +1084,46 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer .emitStatement("Boolean %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); + .emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); + if (isUpdate) { + writer.nextControlFlow("else") + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); + } + writer.endControlFlow(); } else if ("byte[]".equals(fieldType)) { writer .emitStatement("byte[] %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); + .emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); + if (isUpdate) { + writer.nextControlFlow("else") + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); + } + writer.endControlFlow(); } else if ("java.util.Date".equals(fieldType)) { writer .emitStatement("java.util.Date %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.getTime(), false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); + .emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.getTime(), false)", fieldName, getter); + if (isUpdate) { + writer.nextControlFlow("else") + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); + } + writer.endControlFlow(); } else if ("java.lang.String".equals(fieldType)) { writer .emitStatement("String %s = ((%s)object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); + .emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); + if (isUpdate) { + writer.nextControlFlow("else") + .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); + } + writer.endControlFlow(); } else { throw new IllegalStateException("Unsupported type " + fieldType); } @@ -1139,7 +1145,7 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { .endControlFlow(); writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); - writer.emitStatement("long tableNativePtr = table.getNativeTablePointer()"); + writer.emitStatement("long tableNativePtr = table.getNativePtr()"); writer.emitStatement("%s columnInfo = (%s) realm.schema.getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedClassName); @@ -1159,14 +1165,14 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { .emitEmptyLine() .emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) - .beginControlFlow("if (cache%s == null)", fieldName) - .emitStatement("cache%s = %s.insert(realm, %sObj, cache)", - fieldName, - Utils.getProxyClassSimpleName(field), - fieldName) - .endControlFlow() - .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) + .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) + .beginControlFlow("if (cache%s == null)", fieldName) + .emitStatement("cache%s = %s.insert(realm, %sObj, cache)", + fieldName, + Utils.getProxyClassSimpleName(field), + fieldName) + .endControlFlow() + .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .endControlFlow(); } else if (Utils.isRealmList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); @@ -1175,14 +1181,14 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("long %1$sNativeLinkViewPtr = Table.nativeGetLinkView(tableNativePtr, columnInfo.%1$sIndex, rowIndex)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insert(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) - .endControlFlow() + .emitStatement("long %1$sNativeLinkViewPtr = Table.nativeGetLinkView(tableNativePtr, columnInfo.%1$sIndex, rowIndex)", fieldName) + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) + .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + .emitStatement("cacheItemIndex%1$s = %2$s.insert(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) + .endControlFlow() .endControlFlow() .emitEmptyLine(); @@ -1208,7 +1214,7 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { ); writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); - writer.emitStatement("long tableNativePtr = table.getNativeTablePointer()"); + writer.emitStatement("long tableNativePtr = table.getNativePtr()"); writer.emitStatement("%s columnInfo = (%s) realm.schema.getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedClassName); if (metadata.hasPrimaryKey()) { @@ -1238,13 +1244,13 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { .emitEmptyLine() .emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) - .beginControlFlow("if (cache%s == null)", fieldName) - .emitStatement("cache%s = %s.insert(realm, %sObj, cache)", - fieldName, - Utils.getProxyClassSimpleName(field), - fieldName) - .endControlFlow() + .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) + .beginControlFlow("if (cache%s == null)", fieldName) + .emitStatement("cache%s = %s.insert(realm, %sObj, cache)", + fieldName, + Utils.getProxyClassSimpleName(field), + fieldName) + .endControlFlow() .emitStatement("table.setLink(columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .endControlFlow(); } else if (Utils.isRealmList(field)) { @@ -1254,12 +1260,12 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("long %1$sNativeLinkViewPtr = Table.nativeGetLinkView(tableNativePtr, columnInfo.%1$sIndex, rowIndex)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insert(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() + .emitStatement("long %1$sNativeLinkViewPtr = Table.nativeGetLinkView(tableNativePtr, columnInfo.%1$sIndex, rowIndex)", fieldName) + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) + .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + .emitStatement("cacheItemIndex%1$s = %2$s.insert(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) .endControlFlow() .endControlFlow() @@ -1294,7 +1300,7 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { .endControlFlow(); writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); - writer.emitStatement("long tableNativePtr = table.getNativeTablePointer()"); + writer.emitStatement("long tableNativePtr = table.getNativePtr()"); writer.emitStatement("%s columnInfo = (%s) realm.schema.getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedClassName); @@ -1314,16 +1320,16 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { .emitEmptyLine() .emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) - .beginControlFlow("if (cache%s == null)", fieldName) - .emitStatement("cache%1$s = %2$s.insertOrUpdate(realm, %1$sObj, cache)", - fieldName, - Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) + .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) + .beginControlFlow("if (cache%s == null)", fieldName) + .emitStatement("cache%1$s = %2$s.insertOrUpdate(realm, %1$sObj, cache)", + fieldName, + Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .nextControlFlow("else") - // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. + .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) .endControlFlow(); } else if (Utils.isRealmList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); @@ -1334,13 +1340,13 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) - .endControlFlow() + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) + .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) + .endControlFlow() .endControlFlow() .emitEmptyLine(); @@ -1367,7 +1373,7 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException ); writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); - writer.emitStatement("long tableNativePtr = table.getNativeTablePointer()"); + writer.emitStatement("long tableNativePtr = table.getNativePtr()"); writer.emitStatement("%s columnInfo = (%s) realm.schema.getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedClassName); if (metadata.hasPrimaryKey()) { @@ -1396,16 +1402,16 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException .emitEmptyLine() .emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) - .beginControlFlow("if (cache%s == null)", fieldName) - .emitStatement("cache%1$s = %2$s.insertOrUpdate(realm, %1$sObj, cache)", - fieldName, - Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) + .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) + .beginControlFlow("if (cache%s == null)", fieldName) + .emitStatement("cache%1$s = %2$s.insertOrUpdate(realm, %1$sObj, cache)", + fieldName, + Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .nextControlFlow("else") - // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. + .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) .endControlFlow(); } else if (Utils.isRealmList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); @@ -1416,13 +1422,13 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) - .endControlFlow() + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) + .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) + .endControlFlow() .endControlFlow() .emitEmptyLine(); @@ -1448,22 +1454,22 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP //@formatter:off if (Utils.isString(primaryKeyElement)) { writer - .emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = Table.NO_MATCH") - .beginControlFlow("if (primaryKeyValue == null)") - .emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") - .nextControlFlow("else") - .emitStatement("rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue)") - .endControlFlow(); + .emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) + .emitStatement("long rowIndex = Table.NO_MATCH") + .beginControlFlow("if (primaryKeyValue == null)") + .emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") + .nextControlFlow("else") + .emitStatement("rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue)") + .endControlFlow(); } else { writer - .emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = Table.NO_MATCH") - .beginControlFlow("if (primaryKeyValue == null)") - .emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") - .nextControlFlow("else") - .emitStatement("rowIndex = Table.nativeFindFirstInt(tableNativePtr, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) - .endControlFlow(); + .emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) + .emitStatement("long rowIndex = Table.NO_MATCH") + .beginControlFlow("if (primaryKeyValue == null)") + .emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") + .nextControlFlow("else") + .emitStatement("rowIndex = Table.nativeFindFirstInt(tableNativePtr, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) + .endControlFlow(); } //@formatter:on } else { @@ -1533,51 +1539,51 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { } //@formatter:off - if (Utils.isRealmModel(field)) { - writer + if (Utils.isRealmModel(field)) { + writer .emitEmptyLine() .emitStatement("%s %sObj = ((%s) newObject).%s()", fieldType, fieldName, interfaceName, getter) .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("((%s) realmObject).%s(cache%s)", interfaceName, setter, fieldName) - .nextControlFlow("else") - .emitStatement("((%s) realmObject).%s(%s.copyOrUpdate(realm, %sObj, update, cache))", - interfaceName, - setter, - Utils.getProxyClassSimpleName(field), - fieldName) - .endControlFlow() + .emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) + .beginControlFlow("if (cache%s != null)", fieldName) + .emitStatement("((%s) realmObject).%s(cache%s)", interfaceName, setter, fieldName) + .nextControlFlow("else") + .emitStatement("((%s) realmObject).%s(%s.copyOrUpdate(realm, %sObj, update, cache))", + interfaceName, + setter, + Utils.getProxyClassSimpleName(field), + fieldName) + .endControlFlow() .nextControlFlow("else") - // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .emitStatement("((%s) realmObject).%s(null)", interfaceName, setter) + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. + .emitStatement("((%s) realmObject).%s(null)", interfaceName, setter) .endControlFlow(); - } else if (Utils.isRealmList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - writer + } else if (Utils.isRealmList(field)) { + final String genericType = Utils.getGenericTypeQualifiedName(field); + writer .emitEmptyLine() .emitStatement("RealmList<%s> %sList = ((%s) newObject).%s()", genericType, fieldName, interfaceName, getter) .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("RealmList<%s> %sRealmList = ((%s) realmObject).%s()", - genericType, fieldName, interfaceName, getter) - .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - .emitStatement("%s %sItem = %sList.get(i)", genericType, fieldName, fieldName) - .emitStatement("%s cache%s = (%s) cache.get(%sItem)", genericType, fieldName, genericType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%sRealmList.add(cache%s)", fieldName, fieldName) - .nextControlFlow("else") - .emitStatement("%sRealmList.add(%s.copyOrUpdate(realm, %sList.get(i), update, cache))", fieldName, Utils.getProxyClassSimpleName(field), fieldName) - .endControlFlow() - .endControlFlow() + .emitStatement("RealmList<%s> %sRealmList = ((%s) realmObject).%s()", + genericType, fieldName, interfaceName, getter) + .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) + .emitStatement("%s %sItem = %sList.get(i)", genericType, fieldName, fieldName) + .emitStatement("%s cache%s = (%s) cache.get(%sItem)", genericType, fieldName, genericType, fieldName) + .beginControlFlow("if (cache%s != null)", fieldName) + .emitStatement("%sRealmList.add(cache%s)", fieldName, fieldName) + .nextControlFlow("else") + .emitStatement("%sRealmList.add(%s.copyOrUpdate(realm, %sList.get(i), update, cache))", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + .endControlFlow() + .endControlFlow() .endControlFlow() .emitEmptyLine(); - } else { - writer.emitStatement("((%s) realmObject).%s(((%s) newObject).%s())", - interfaceName, setter, interfaceName, getter); - } - //@formatter:on + } else { + writer.emitStatement("((%s) realmObject).%s(((%s) newObject).%s())", + interfaceName, setter, interfaceName, getter); + } + //@formatter:on } writer.emitStatement("return realmObject"); @@ -1594,23 +1600,23 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers qualifiedClassName, "realmObject", "int", "currentDepth", "int", "maxDepth", "Map>", "cache"); writer - .beginControlFlow("if (currentDepth > maxDepth || realmObject == null)") + .beginControlFlow("if (currentDepth > maxDepth || realmObject == null)") .emitStatement("return null") - .endControlFlow() - .emitStatement("CacheData cachedObject = cache.get(realmObject)") - .emitStatement("%s unmanagedObject", qualifiedClassName) - .beginControlFlow("if (cachedObject != null)") + .endControlFlow() + .emitStatement("CacheData cachedObject = cache.get(realmObject)") + .emitStatement("%s unmanagedObject", qualifiedClassName) + .beginControlFlow("if (cachedObject != null)") .emitSingleLineComment("Reuse cached object or recreate it because it was encountered at a lower depth.") .beginControlFlow("if (currentDepth >= cachedObject.minDepth)") - .emitStatement("return (%s)cachedObject.object", qualifiedClassName) + .emitStatement("return (%s)cachedObject.object", qualifiedClassName) .nextControlFlow("else") - .emitStatement("unmanagedObject = (%s)cachedObject.object", qualifiedClassName) - .emitStatement("cachedObject.minDepth = currentDepth") + .emitStatement("unmanagedObject = (%s)cachedObject.object", qualifiedClassName) + .emitStatement("cachedObject.minDepth = currentDepth") .endControlFlow() - .nextControlFlow("else") + .nextControlFlow("else") .emitStatement("unmanagedObject = new %s()", qualifiedClassName) .emitStatement("cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject))") - .endControlFlow(); + .endControlFlow(); for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); @@ -1619,29 +1625,29 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException if (Utils.isRealmModel(field)) { writer - .emitEmptyLine() - .emitSingleLineComment("Deep copy of %s", fieldName) - .emitStatement("((%s) unmanagedObject).%s(%s.createDetachedCopy(((%s) realmObject).%s(), currentDepth + 1, maxDepth, cache))", + .emitEmptyLine() + .emitSingleLineComment("Deep copy of %s", fieldName) + .emitStatement("((%s) unmanagedObject).%s(%s.createDetachedCopy(((%s) realmObject).%s(), currentDepth + 1, maxDepth, cache))", interfaceName, setter, Utils.getProxyClassSimpleName(field), interfaceName, getter); } else if (Utils.isRealmList(field)) { writer - .emitEmptyLine() - .emitSingleLineComment("Deep copy of %s", fieldName) - .beginControlFlow("if (currentDepth == maxDepth)") + .emitEmptyLine() + .emitSingleLineComment("Deep copy of %s", fieldName) + .beginControlFlow("if (currentDepth == maxDepth)") .emitStatement("((%s) unmanagedObject).%s(null)", interfaceName, setter) - .nextControlFlow("else") + .nextControlFlow("else") .emitStatement("RealmList<%s> managed%sList = ((%s) realmObject).%s()", - Utils.getGenericTypeQualifiedName(field), fieldName, interfaceName, getter) + Utils.getGenericTypeQualifiedName(field), fieldName, interfaceName, getter) .emitStatement("RealmList<%1$s> unmanaged%2$sList = new RealmList<%1$s>()", Utils.getGenericTypeQualifiedName(field), fieldName) .emitStatement("((%s) unmanagedObject).%s(unmanaged%sList)", interfaceName, setter, fieldName) .emitStatement("int nextDepth = currentDepth + 1") .emitStatement("int size = managed%sList.size()", fieldName) .beginControlFlow("for (int i = 0; i < size; i++)") - .emitStatement("%s item = %s.createDetachedCopy(managed%sList.get(i), nextDepth, maxDepth, cache)", - Utils.getGenericTypeQualifiedName(field), Utils.getProxyClassSimpleName(field), fieldName) - .emitStatement("unmanaged%sList.add(item)", fieldName) + .emitStatement("%s item = %s.createDetachedCopy(managed%sList.get(i), nextDepth, maxDepth, cache)", + Utils.getGenericTypeQualifiedName(field), Utils.getProxyClassSimpleName(field), fieldName) + .emitStatement("unmanaged%sList.add(item)", fieldName) .endControlFlow() - .endControlFlow(); + .endControlFlow(); } else { writer.emitStatement("((%s) unmanagedObject).%s(((%s) realmObject).%s())", interfaceName, setter, interfaceName, getter); @@ -1672,43 +1678,43 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { //@formatter:off if (Utils.isRealmModel(field)) { writer - .emitStatement("%s %sObj = ((%s) newObject).%s()", - Utils.getFieldTypeQualifiedName(field), fieldName, interfaceName, getter) - .beginControlFlow("if (%sObj != null)", fieldName) + .emitStatement("%s %sObj = ((%s) newObject).%s()", + Utils.getFieldTypeQualifiedName(field), fieldName, interfaceName, getter) + .beginControlFlow("if (%sObj != null)", fieldName) .emitStatement("%s cache%s = (%s) cache.get(%sObj)", Utils.getFieldTypeQualifiedName(field), fieldName, Utils.getFieldTypeQualifiedName(field), fieldName) .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("((%s) realmObject).%s(cache%s)", interfaceName, setter, fieldName) + .emitStatement("((%s) realmObject).%s(cache%s)", interfaceName, setter, fieldName) .nextControlFlow("else") - .emitStatement("((%s) realmObject).%s(%s.copyOrUpdate(realm, %sObj, true, cache))", - interfaceName, - setter, - Utils.getProxyClassSimpleName(field), - fieldName - ) + .emitStatement("((%s) realmObject).%s(%s.copyOrUpdate(realm, %sObj, true, cache))", + interfaceName, + setter, + Utils.getProxyClassSimpleName(field), + fieldName + ) .endControlFlow() - .nextControlFlow("else") + .nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. .emitStatement("((%s) realmObject).%s(null)", interfaceName, setter) - .endControlFlow(); + .endControlFlow(); } else if (Utils.isRealmList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer - .emitStatement("RealmList<%s> %sList = ((%s) newObject).%s()", - genericType, fieldName, interfaceName, getter) - .emitStatement("RealmList<%s> %sRealmList = ((%s) realmObject).%s()", - genericType, fieldName, interfaceName, getter) - .emitStatement("%sRealmList.clear()", fieldName) - .beginControlFlow("if (%sList != null)", fieldName) + .emitStatement("RealmList<%s> %sList = ((%s) newObject).%s()", + genericType, fieldName, interfaceName, getter) + .emitStatement("RealmList<%s> %sRealmList = ((%s) realmObject).%s()", + genericType, fieldName, interfaceName, getter) + .emitStatement("%sRealmList.clear()", fieldName) + .beginControlFlow("if (%sList != null)", fieldName) .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - .emitStatement("%s %sItem = %sList.get(i)", genericType, fieldName, fieldName) - .emitStatement("%s cache%s = (%s) cache.get(%sItem)", genericType, fieldName, genericType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%sRealmList.add(cache%s)", fieldName, fieldName) - .nextControlFlow("else") - .emitStatement("%sRealmList.add(%s.copyOrUpdate(realm, %sList.get(i), true, cache))", fieldName, Utils.getProxyClassSimpleName(field), fieldName) - .endControlFlow() + .emitStatement("%s %sItem = %sList.get(i)", genericType, fieldName, fieldName) + .emitStatement("%s cache%s = (%s) cache.get(%sItem)", genericType, fieldName, genericType, fieldName) + .beginControlFlow("if (cache%s != null)", fieldName) + .emitStatement("%sRealmList.add(cache%s)", fieldName, fieldName) + .nextControlFlow("else") + .emitStatement("%sRealmList.add(%s.copyOrUpdate(realm, %sList.get(i), true, cache))", fieldName, Utils.getProxyClassSimpleName(field), fieldName) .endControlFlow() - .endControlFlow(); + .endControlFlow() + .endControlFlow(); } else { if (field == metadata.getPrimaryKey()) { @@ -1789,7 +1795,7 @@ private void emitHashcodeMethod(JavaWriter writer) throws IOException { return; } writer.emitAnnotation("Override") - .beginMethod("int", "hashCode", EnumSet.of(Modifier.PUBLIC)) + .beginMethod("int", "hashCode", EnumSet.of(Modifier.PUBLIC)) .emitStatement("String realmName = proxyState.getRealm$realm().getPath()") .emitStatement("String tableName = proxyState.getRow$realm().getTable().getName()") .emitStatement("long rowIndex = proxyState.getRow$realm().getIndex()") @@ -1799,8 +1805,8 @@ private void emitHashcodeMethod(JavaWriter writer) throws IOException { .emitStatement("result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0)") .emitStatement("result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32))") .emitStatement("return result") - .endMethod() - .emitEmptyLine(); + .endMethod() + .emitEmptyLine(); } //@formatter:on @@ -1812,7 +1818,7 @@ private void emitEqualsMethod(JavaWriter writer) throws IOException { String proxyClassName = Utils.getProxyClassName(simpleClassName); String otherObjectVarName = "a" + simpleClassName; writer.emitAnnotation("Override") - .beginMethod("boolean", "equals", EnumSet.of(Modifier.PUBLIC), "Object", "o") + .beginMethod("boolean", "equals", EnumSet.of(Modifier.PUBLIC), "Object", "o") .emitStatement("if (this == o) return true") .emitStatement("if (o == null || getClass() != o.getClass()) return false") .emitStatement("%s %s = (%s)o", proxyClassName, otherObjectVarName, proxyClassName) // FooRealmProxy aFoo = (FooRealmProxy)o @@ -1828,8 +1834,8 @@ private void emitEqualsMethod(JavaWriter writer) throws IOException { .emitStatement("if (proxyState.getRow$realm().getIndex() != %s.proxyState.getRow$realm().getIndex()) return false", otherObjectVarName) .emitEmptyLine() .emitStatement("return true") - .endMethod() - .emitEmptyLine(); + .endMethod() + .emitEmptyLine(); } //@formatter:on @@ -1858,39 +1864,39 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep } else { String pkType = Utils.isString(metadata.getPrimaryKey()) ? "String" : "Long"; writer - .emitStatement("%s obj = null", qualifiedClassName) - .beginControlFlow("if (update)") + .emitStatement("%s obj = null", qualifiedClassName) + .beginControlFlow("if (update)") .emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName) .emitStatement("long pkColumnIndex = table.getPrimaryKey()") .emitStatement("long rowIndex = Table.NO_MATCH"); if (metadata.isNullable(metadata.getPrimaryKey())) { writer - .beginControlFlow("if (json.isNull(\"%s\"))", metadata.getPrimaryKey().getSimpleName()) + .beginControlFlow("if (json.isNull(\"%s\"))", metadata.getPrimaryKey().getSimpleName()) .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") - .nextControlFlow("else") + .nextControlFlow("else") .emitStatement("rowIndex = table.findFirst%s(pkColumnIndex, json.get%s(\"%s\"))", pkType, pkType, metadata.getPrimaryKey().getSimpleName()) - .endControlFlow(); + .endControlFlow(); } else { writer - .beginControlFlow("if (!json.isNull(\"%s\"))", metadata.getPrimaryKey().getSimpleName()) - .emitStatement("rowIndex = table.findFirst%s(pkColumnIndex, json.get%s(\"%s\"))", - pkType, pkType, metadata.getPrimaryKey().getSimpleName()) - .endControlFlow(); + .beginControlFlow("if (!json.isNull(\"%s\"))", metadata.getPrimaryKey().getSimpleName()) + .emitStatement("rowIndex = table.findFirst%s(pkColumnIndex, json.get%s(\"%s\"))", + pkType, pkType, metadata.getPrimaryKey().getSimpleName()) + .endControlFlow(); } writer .beginControlFlow("if (rowIndex != Table.NO_MATCH)") - .emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") - .beginControlFlow("try") - .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex)," + - " realm.schema.getColumnInfo(%s.class)," + - " false, Collections. emptyList())", qualifiedClassName) - .emitStatement("obj = new %s()", qualifiedGeneratedClassName) - .nextControlFlow("finally") - .emitStatement("objectContext.clear()") - .endControlFlow() + .emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") + .beginControlFlow("try") + .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex)," + + " realm.schema.getColumnInfo(%s.class)," + + " false, Collections. emptyList())", qualifiedClassName) + .emitStatement("obj = new %s()", qualifiedGeneratedClassName) + .nextControlFlow("finally") + .emitStatement("objectContext.clear()") .endControlFlow() - .endControlFlow(); + .endControlFlow() + .endControlFlow(); writer.beginControlFlow("if (obj == null)"); buildExcludeFieldsList(writer, metadata.getFields()); @@ -2057,4 +2063,27 @@ private static int countModelOrListFields(Collection fields) { } return count; } + + private Constants.RealmFieldType getRealmType(VariableElement field) { + String fieldTypeCanonicalName = field.asType().toString(); + Constants.RealmFieldType type = Constants.JAVA_TO_REALM_TYPES.get(fieldTypeCanonicalName); + if (type != null) { + return type; + } + if (Utils.isRealmModel(field)) { + return Constants.RealmFieldType.OBJECT; + } + if (Utils.isRealmList(field)) { + return Constants.RealmFieldType.LIST; + } + return Constants.RealmFieldType.NOTYPE; + } + + private Constants.RealmFieldType getRealmTypeChecked(VariableElement field) { + Constants.RealmFieldType type = getRealmType(field); + if (type == Constants.RealmFieldType.NOTYPE) { + throw new IllegalStateException("Unsupported type " + field.asType().toString()); + } + return type; + } } diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java index 1e43cb20b7..2f84e1f916 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java @@ -18,6 +18,7 @@ import com.google.testing.compile.JavaFileObjects; +import org.junit.Ignore; import org.junit.Test; import java.io.IOException; @@ -29,6 +30,7 @@ import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; import static org.truth0.Truth.ASSERT; + public class RealmProcessorTest { private JavaFileObject simpleModel = JavaFileObjects.forResource("some/test/Simple.java"); @@ -195,7 +197,7 @@ public void compileLibraryModulesCustomClasses() throws Exception { public void compileAppModuleMixedParametersFail() throws Exception { ASSERT.about(javaSources()) .that(Arrays.asList(allTypesModel, JavaFileObjects.forResource( - "some/test/InvalidAllTypesModuleMixedParameters.java"))) + "some/test/InvalidAllTypesModuleMixedParameters.java"))) .processedWith(new RealmProcessor()) .failsToCompile(); } @@ -204,7 +206,7 @@ public void compileAppModuleMixedParametersFail() throws Exception { public void compileAppModuleWrongTypeFail() throws Exception { ASSERT.about(javaSources()) .that(Arrays.asList(allTypesModel, JavaFileObjects.forResource( - "some/test/InvalidAllTypesModuleWrongType.java"))) + "some/test/InvalidAllTypesModuleWrongType.java"))) .processedWith(new RealmProcessor()) .failsToCompile(); } @@ -265,7 +267,8 @@ public void compileMissingGenericType() { .failsToCompile(); } - // Disabled because it does not seem to find the generated Interface file @Test + @Test + @Ignore("Disabled because it does not find the generated Interface file") public void compileFieldNamesFiles() { ASSERT.about(javaSource()) .that(fieldNamesModel) @@ -477,88 +480,88 @@ public void compileWithInterfaceForObject() { @Test public void compileBacklinks() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget)) - .processedWith(new RealmProcessor()) - .compilesWithoutError(); + .that(Arrays.asList(backlinks, backlinksTarget)) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); } @Test public void failOnLinkingObjectsWithInvalidFieldType() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget, backlinksInvalidField)) - .processedWith(new RealmProcessor()) - .failsToCompile() - .withErrorContaining("Fields annotated with @LinkingObjects must be RealmResults"); + .that(Arrays.asList(backlinks, backlinksTarget, backlinksInvalidField)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("Fields annotated with @LinkingObjects must be RealmResults"); } @Test public void failOnLinkingObjectsWithNonFinalField() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget, backlinksNonFinalField)) - .processedWith(new RealmProcessor()) - .failsToCompile() - .withErrorContaining("must be final"); + .that(Arrays.asList(backlinks, backlinksTarget, backlinksNonFinalField)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("must be final"); } @Test public void failsOnLinkingObjectsWithLinkedFields() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget, backlinksLinked)) - .processedWith(new RealmProcessor()) - .failsToCompile() - .withErrorContaining("The use of '.' to specify fields in referenced classes is not supported"); + .that(Arrays.asList(backlinks, backlinksTarget, backlinksLinked)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("The use of '.' to specify fields in referenced classes is not supported"); } @Test public void failsOnLinkingObjectsMissingFieldName() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget, backlinksMissingParam)) - .processedWith(new RealmProcessor()) - .failsToCompile() - .withErrorContaining("must have a parameter identifying the link target"); + .that(Arrays.asList(backlinks, backlinksTarget, backlinksMissingParam)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("must have a parameter identifying the link target"); } @Test public void failsOnLinkingObjectsMissingGeneric() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget, backlinksMissingGeneric)) - .processedWith(new RealmProcessor()) - .failsToCompile() - .withErrorContaining("must specify a generic type"); + .that(Arrays.asList(backlinks, backlinksTarget, backlinksMissingGeneric)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("must specify a generic type"); } @Test public void failsOnLinkingObjectsWithRequiredFields() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget, backlinksRequired)) - .processedWith(new RealmProcessor()) - .failsToCompile() - .withErrorContaining("cannot be @Required"); + .that(Arrays.asList(backlinks, backlinksTarget, backlinksRequired)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("cannot be @Required"); } @Test public void failsOnLinkingObjectsWithIgnoreFields() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget, backlinksIgnored)) - .processedWith(new RealmProcessor()) - .compilesWithoutError(); + .that(Arrays.asList(backlinks, backlinksTarget, backlinksIgnored)) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); } @Test public void failsOnLinkingObjectsFieldNotFound() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget, backlinksNotFound)) - .processedWith(new RealmProcessor()) - .failsToCompile() - .withErrorContaining("does not exist in class"); + .that(Arrays.asList(backlinks, backlinksTarget, backlinksNotFound)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("does not exist in class"); } @Test public void failsOnLinkingObjectsWithFieldWrongType() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget, backlinksWrongType)) - .processedWith(new RealmProcessor()) - .failsToCompile() - .withErrorContaining("instead of"); + .that(Arrays.asList(backlinks, backlinksTarget, backlinksWrongType)) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("instead of"); } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 6a101e7c84..00265d3828 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -32,65 +32,57 @@ public class AllTypesRealmProxy extends some.test.AllTypes implements RealmObjectProxy, AllTypesRealmProxyInterface { - static final class AllTypesColumnInfo extends ColumnInfo - implements Cloneable { - - public long columnStringIndex; - public long columnLongIndex; - public long columnFloatIndex; - public long columnDoubleIndex; - public long columnBooleanIndex; - public long columnDateIndex; - public long columnBinaryIndex; - public long columnObjectIndex; - public long columnRealmListIndex; - - AllTypesColumnInfo(String path, Table table) { - final Map indicesMap = new HashMap(9); - this.columnStringIndex = getValidColumnIndex(path, table, "AllTypes", "columnString"); - indicesMap.put("columnString", this.columnStringIndex); - this.columnLongIndex = getValidColumnIndex(path, table, "AllTypes", "columnLong"); - indicesMap.put("columnLong", this.columnLongIndex); - this.columnFloatIndex = getValidColumnIndex(path, table, "AllTypes", "columnFloat"); - indicesMap.put("columnFloat", this.columnFloatIndex); - this.columnDoubleIndex = getValidColumnIndex(path, table, "AllTypes", "columnDouble"); - indicesMap.put("columnDouble", this.columnDoubleIndex); - this.columnBooleanIndex = getValidColumnIndex(path, table, "AllTypes", "columnBoolean"); - indicesMap.put("columnBoolean", this.columnBooleanIndex); - this.columnDateIndex = getValidColumnIndex(path, table, "AllTypes", "columnDate"); - indicesMap.put("columnDate", this.columnDateIndex); - this.columnBinaryIndex = getValidColumnIndex(path, table, "AllTypes", "columnBinary"); - indicesMap.put("columnBinary", this.columnBinaryIndex); - this.columnObjectIndex = getValidColumnIndex(path, table, "AllTypes", "columnObject"); - indicesMap.put("columnObject", this.columnObjectIndex); - this.columnRealmListIndex = getValidColumnIndex(path, table, "AllTypes", "columnRealmList"); - indicesMap.put("columnRealmList", this.columnRealmListIndex); - - setIndicesMap(indicesMap); + static final class AllTypesColumnInfo extends ColumnInfo { + long columnStringIndex; + long columnLongIndex; + long columnFloatIndex; + long columnDoubleIndex; + long columnBooleanIndex; + long columnDateIndex; + long columnBinaryIndex; + long columnObjectIndex; + long columnRealmListIndex; + + AllTypesColumnInfo(SharedRealm realm, Table table) { + super(9); + this.columnStringIndex = addColumnDetails(table, "columnString", RealmFieldType.STRING); + this.columnLongIndex = addColumnDetails(table, "columnLong", RealmFieldType.INTEGER); + this.columnFloatIndex = addColumnDetails(table, "columnFloat", RealmFieldType.FLOAT); + this.columnDoubleIndex = addColumnDetails(table, "columnDouble", RealmFieldType.DOUBLE); + this.columnBooleanIndex = addColumnDetails(table, "columnBoolean", RealmFieldType.BOOLEAN); + this.columnDateIndex = addColumnDetails(table, "columnDate", RealmFieldType.DATE); + this.columnBinaryIndex = addColumnDetails(table, "columnBinary", RealmFieldType.BINARY); + this.columnObjectIndex = addColumnDetails(table, "columnObject", RealmFieldType.OBJECT); + this.columnRealmListIndex = addColumnDetails(table, "columnRealmList", RealmFieldType.LIST); + addBacklinkDetails(realm, "parentObjects", "AllTypes", "columnObject"); + } + + AllTypesColumnInfo(ColumnInfo src, boolean mutable) { + super(src, mutable); + copy(src, this); } @Override - public final void copyColumnInfoFrom(ColumnInfo other) { - final AllTypesColumnInfo otherInfo = (AllTypesColumnInfo) other; - this.columnStringIndex = otherInfo.columnStringIndex; - this.columnLongIndex = otherInfo.columnLongIndex; - this.columnFloatIndex = otherInfo.columnFloatIndex; - this.columnDoubleIndex = otherInfo.columnDoubleIndex; - this.columnBooleanIndex = otherInfo.columnBooleanIndex; - this.columnDateIndex = otherInfo.columnDateIndex; - this.columnBinaryIndex = otherInfo.columnBinaryIndex; - this.columnObjectIndex = otherInfo.columnObjectIndex; - this.columnRealmListIndex = otherInfo.columnRealmListIndex; - - setIndicesMap(otherInfo.getIndicesMap()); + protected final ColumnInfo copy(boolean mutable) { + return new AllTypesColumnInfo(this, mutable); } @Override - public final AllTypesColumnInfo clone() { - return (AllTypesColumnInfo) super.clone(); + protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { + final AllTypesColumnInfo src = (AllTypesColumnInfo) rawSrc; + final AllTypesColumnInfo dst = (AllTypesColumnInfo) rawDst; + dst.columnStringIndex = src.columnStringIndex; + dst.columnLongIndex = src.columnLongIndex; + dst.columnFloatIndex = src.columnFloatIndex; + dst.columnDoubleIndex = src.columnDoubleIndex; + dst.columnBooleanIndex = src.columnBooleanIndex; + dst.columnDateIndex = src.columnDateIndex; + dst.columnBinaryIndex = src.columnBinaryIndex; + dst.columnObjectIndex = src.columnObjectIndex; + dst.columnRealmListIndex = src.columnRealmListIndex; } - } + private AllTypesColumnInfo columnInfo; private ProxyState proxyState; private RealmList columnRealmListRealmList; @@ -400,7 +392,7 @@ public final AllTypesColumnInfo clone() { realm.checkIfValid(); proxyState.getRow$realm().checkIfAttached(); if (parentObjectsBacklinks == null) { - parentObjectsBacklinks = RealmResults.createBacklinkResults((Realm) realm, (UncheckedRow) proxyState.getRow$realm(), some.test.AllTypes.class, "columnObject"); + parentObjectsBacklinks = RealmResults.createBacklinkResults(realm, proxyState.getRow$realm(), some.test.AllTypes.class, "columnObject"); } return parentObjectsBacklinks; } @@ -449,7 +441,7 @@ public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } - final AllTypesColumnInfo columnInfo = new AllTypesColumnInfo(sharedRealm.getPath(), table); + final AllTypesColumnInfo columnInfo = new AllTypesColumnInfo(sharedRealm, table); if (!table.hasPrimaryKey()) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary key not defined for field 'columnString' in existing Realm file. @PrimaryKey was added."); @@ -888,7 +880,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, Map cache) { Table table = realm.getTable(some.test.AllTypes.class); - long tableNativePtr = table.getNativeTablePointer(); + long tableNativePtr = table.getNativePtr(); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.schema.getColumnInfo(some.test.AllTypes.class); long pkColumnIndex = table.getPrimaryKey(); some.test.AllTypes object = null; @@ -1010,7 +1002,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map objects, Map cache) { Table table = realm.getTable(some.test.AllTypes.class); - long tableNativePtr = table.getNativeTablePointer(); + long tableNativePtr = table.getNativePtr(); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.schema.getColumnInfo(some.test.AllTypes.class); long pkColumnIndex = table.getPrimaryKey(); some.test.AllTypes object = null; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 5a07ed0d92..cb4122faff 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -31,45 +31,41 @@ public class BooleansRealmProxy extends some.test.Booleans implements RealmObjectProxy, BooleansRealmProxyInterface { - static final class BooleansColumnInfo extends ColumnInfo - implements Cloneable { + static final class BooleansColumnInfo extends ColumnInfo { + long doneIndex; + long isReadyIndex; + long mCompletedIndex; + long anotherBooleanIndex; - public long doneIndex; - public long isReadyIndex; - public long mCompletedIndex; - public long anotherBooleanIndex; - - BooleansColumnInfo(String path, Table table) { - final Map indicesMap = new HashMap(4); - this.doneIndex = getValidColumnIndex(path, table, "Booleans", "done"); - indicesMap.put("done", this.doneIndex); - this.isReadyIndex = getValidColumnIndex(path, table, "Booleans", "isReady"); - indicesMap.put("isReady", this.isReadyIndex); - this.mCompletedIndex = getValidColumnIndex(path, table, "Booleans", "mCompleted"); - indicesMap.put("mCompleted", this.mCompletedIndex); - this.anotherBooleanIndex = getValidColumnIndex(path, table, "Booleans", "anotherBoolean"); - indicesMap.put("anotherBoolean", this.anotherBooleanIndex); + BooleansColumnInfo(SharedRealm realm, Table table) { + super(4); + this.doneIndex = addColumnDetails(table, "done", RealmFieldType.BOOLEAN); + this.isReadyIndex = addColumnDetails(table, "isReady", RealmFieldType.BOOLEAN); + this.mCompletedIndex = addColumnDetails(table, "mCompleted", RealmFieldType.BOOLEAN); + this.anotherBooleanIndex = addColumnDetails(table, "anotherBoolean", RealmFieldType.BOOLEAN); + } - setIndicesMap(indicesMap); + BooleansColumnInfo(ColumnInfo src, boolean mutable) { + super(src, mutable); + copy(src, this); } @Override - public final void copyColumnInfoFrom(ColumnInfo other) { - final BooleansColumnInfo otherInfo = (BooleansColumnInfo) other; - this.doneIndex = otherInfo.doneIndex; - this.isReadyIndex = otherInfo.isReadyIndex; - this.mCompletedIndex = otherInfo.mCompletedIndex; - this.anotherBooleanIndex = otherInfo.anotherBooleanIndex; - - setIndicesMap(otherInfo.getIndicesMap()); + protected final ColumnInfo copy(boolean mutable) { + return new BooleansColumnInfo(this, mutable); } @Override - public final BooleansColumnInfo clone() { - return (BooleansColumnInfo) super.clone(); + protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { + final BooleansColumnInfo src = (BooleansColumnInfo) rawSrc; + final BooleansColumnInfo dst = (BooleansColumnInfo) rawDst; + dst.doneIndex = src.doneIndex; + dst.isReadyIndex = src.isReadyIndex; + dst.mCompletedIndex = src.mCompletedIndex; + dst.anotherBooleanIndex = src.anotherBooleanIndex; } - } + private BooleansColumnInfo columnInfo; private ProxyState proxyState; private static final List FIELD_NAMES; @@ -221,7 +217,7 @@ public static BooleansColumnInfo validateTable(SharedRealm sharedRealm, boolean columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } - final BooleansColumnInfo columnInfo = new BooleansColumnInfo(sharedRealm.getPath(), table); + final BooleansColumnInfo columnInfo = new BooleansColumnInfo(sharedRealm, table); if (table.hasPrimaryKey()) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); @@ -394,7 +390,7 @@ public static long insert(Realm realm, some.test.Booleans object, Map objects, Map cache) { Table table = realm.getTable(some.test.Booleans.class); - long tableNativePtr = table.getNativeTablePointer(); + long tableNativePtr = table.getNativePtr(); BooleansColumnInfo columnInfo = (BooleansColumnInfo) realm.schema.getColumnInfo(some.test.Booleans.class); some.test.Booleans object = null; while (objects.hasNext()) { @@ -432,7 +428,7 @@ public static long insertOrUpdate(Realm realm, some.test.Booleans object, Map objects, Map cache) { Table table = realm.getTable(some.test.Booleans.class); - long tableNativePtr = table.getNativeTablePointer(); + long tableNativePtr = table.getNativePtr(); BooleansColumnInfo columnInfo = (BooleansColumnInfo) realm.schema.getColumnInfo(some.test.Booleans.class); some.test.Booleans object = null; while (objects.hasNext()) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index c91b122f0c..5fc1a098a3 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -31,113 +31,92 @@ public class NullTypesRealmProxy extends some.test.NullTypes implements RealmObjectProxy, NullTypesRealmProxyInterface { - static final class NullTypesColumnInfo extends ColumnInfo - implements Cloneable { - - public long fieldStringNotNullIndex; - public long fieldStringNullIndex; - public long fieldBooleanNotNullIndex; - public long fieldBooleanNullIndex; - public long fieldBytesNotNullIndex; - public long fieldBytesNullIndex; - public long fieldByteNotNullIndex; - public long fieldByteNullIndex; - public long fieldShortNotNullIndex; - public long fieldShortNullIndex; - public long fieldIntegerNotNullIndex; - public long fieldIntegerNullIndex; - public long fieldLongNotNullIndex; - public long fieldLongNullIndex; - public long fieldFloatNotNullIndex; - public long fieldFloatNullIndex; - public long fieldDoubleNotNullIndex; - public long fieldDoubleNullIndex; - public long fieldDateNotNullIndex; - public long fieldDateNullIndex; - public long fieldObjectNullIndex; - - NullTypesColumnInfo(String path, Table table) { - final Map indicesMap = new HashMap(21); - this.fieldStringNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldStringNotNull"); - indicesMap.put("fieldStringNotNull", this.fieldStringNotNullIndex); - this.fieldStringNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldStringNull"); - indicesMap.put("fieldStringNull", this.fieldStringNullIndex); - this.fieldBooleanNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldBooleanNotNull"); - indicesMap.put("fieldBooleanNotNull", this.fieldBooleanNotNullIndex); - this.fieldBooleanNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldBooleanNull"); - indicesMap.put("fieldBooleanNull", this.fieldBooleanNullIndex); - this.fieldBytesNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldBytesNotNull"); - indicesMap.put("fieldBytesNotNull", this.fieldBytesNotNullIndex); - this.fieldBytesNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldBytesNull"); - indicesMap.put("fieldBytesNull", this.fieldBytesNullIndex); - this.fieldByteNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldByteNotNull"); - indicesMap.put("fieldByteNotNull", this.fieldByteNotNullIndex); - this.fieldByteNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldByteNull"); - indicesMap.put("fieldByteNull", this.fieldByteNullIndex); - this.fieldShortNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldShortNotNull"); - indicesMap.put("fieldShortNotNull", this.fieldShortNotNullIndex); - this.fieldShortNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldShortNull"); - indicesMap.put("fieldShortNull", this.fieldShortNullIndex); - this.fieldIntegerNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldIntegerNotNull"); - indicesMap.put("fieldIntegerNotNull", this.fieldIntegerNotNullIndex); - this.fieldIntegerNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldIntegerNull"); - indicesMap.put("fieldIntegerNull", this.fieldIntegerNullIndex); - this.fieldLongNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldLongNotNull"); - indicesMap.put("fieldLongNotNull", this.fieldLongNotNullIndex); - this.fieldLongNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldLongNull"); - indicesMap.put("fieldLongNull", this.fieldLongNullIndex); - this.fieldFloatNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldFloatNotNull"); - indicesMap.put("fieldFloatNotNull", this.fieldFloatNotNullIndex); - this.fieldFloatNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldFloatNull"); - indicesMap.put("fieldFloatNull", this.fieldFloatNullIndex); - this.fieldDoubleNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldDoubleNotNull"); - indicesMap.put("fieldDoubleNotNull", this.fieldDoubleNotNullIndex); - this.fieldDoubleNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldDoubleNull"); - indicesMap.put("fieldDoubleNull", this.fieldDoubleNullIndex); - this.fieldDateNotNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldDateNotNull"); - indicesMap.put("fieldDateNotNull", this.fieldDateNotNullIndex); - this.fieldDateNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldDateNull"); - indicesMap.put("fieldDateNull", this.fieldDateNullIndex); - this.fieldObjectNullIndex = getValidColumnIndex(path, table, "NullTypes", "fieldObjectNull"); - indicesMap.put("fieldObjectNull", this.fieldObjectNullIndex); - - setIndicesMap(indicesMap); + static final class NullTypesColumnInfo extends ColumnInfo { + long fieldStringNotNullIndex; + long fieldStringNullIndex; + long fieldBooleanNotNullIndex; + long fieldBooleanNullIndex; + long fieldBytesNotNullIndex; + long fieldBytesNullIndex; + long fieldByteNotNullIndex; + long fieldByteNullIndex; + long fieldShortNotNullIndex; + long fieldShortNullIndex; + long fieldIntegerNotNullIndex; + long fieldIntegerNullIndex; + long fieldLongNotNullIndex; + long fieldLongNullIndex; + long fieldFloatNotNullIndex; + long fieldFloatNullIndex; + long fieldDoubleNotNullIndex; + long fieldDoubleNullIndex; + long fieldDateNotNullIndex; + long fieldDateNullIndex; + long fieldObjectNullIndex; + + NullTypesColumnInfo(SharedRealm realm, Table table) { + super(21); + this.fieldStringNotNullIndex = addColumnDetails(table, "fieldStringNotNull", RealmFieldType.STRING); + this.fieldStringNullIndex = addColumnDetails(table, "fieldStringNull", RealmFieldType.STRING); + this.fieldBooleanNotNullIndex = addColumnDetails(table, "fieldBooleanNotNull", RealmFieldType.BOOLEAN); + this.fieldBooleanNullIndex = addColumnDetails(table, "fieldBooleanNull", RealmFieldType.BOOLEAN); + this.fieldBytesNotNullIndex = addColumnDetails(table, "fieldBytesNotNull", RealmFieldType.BINARY); + this.fieldBytesNullIndex = addColumnDetails(table, "fieldBytesNull", RealmFieldType.BINARY); + this.fieldByteNotNullIndex = addColumnDetails(table, "fieldByteNotNull", RealmFieldType.INTEGER); + this.fieldByteNullIndex = addColumnDetails(table, "fieldByteNull", RealmFieldType.INTEGER); + this.fieldShortNotNullIndex = addColumnDetails(table, "fieldShortNotNull", RealmFieldType.INTEGER); + this.fieldShortNullIndex = addColumnDetails(table, "fieldShortNull", RealmFieldType.INTEGER); + this.fieldIntegerNotNullIndex = addColumnDetails(table, "fieldIntegerNotNull", RealmFieldType.INTEGER); + this.fieldIntegerNullIndex = addColumnDetails(table, "fieldIntegerNull", RealmFieldType.INTEGER); + this.fieldLongNotNullIndex = addColumnDetails(table, "fieldLongNotNull", RealmFieldType.INTEGER); + this.fieldLongNullIndex = addColumnDetails(table, "fieldLongNull", RealmFieldType.INTEGER); + this.fieldFloatNotNullIndex = addColumnDetails(table, "fieldFloatNotNull", RealmFieldType.FLOAT); + this.fieldFloatNullIndex = addColumnDetails(table, "fieldFloatNull", RealmFieldType.FLOAT); + this.fieldDoubleNotNullIndex = addColumnDetails(table, "fieldDoubleNotNull", RealmFieldType.DOUBLE); + this.fieldDoubleNullIndex = addColumnDetails(table, "fieldDoubleNull", RealmFieldType.DOUBLE); + this.fieldDateNotNullIndex = addColumnDetails(table, "fieldDateNotNull", RealmFieldType.DATE); + this.fieldDateNullIndex = addColumnDetails(table, "fieldDateNull", RealmFieldType.DATE); + this.fieldObjectNullIndex = addColumnDetails(table, "fieldObjectNull", RealmFieldType.OBJECT); + } + + NullTypesColumnInfo(ColumnInfo src, boolean mutable) { + super(src, mutable); + copy(src, this); } @Override - public final void copyColumnInfoFrom(ColumnInfo other) { - final NullTypesColumnInfo otherInfo = (NullTypesColumnInfo) other; - this.fieldStringNotNullIndex = otherInfo.fieldStringNotNullIndex; - this.fieldStringNullIndex = otherInfo.fieldStringNullIndex; - this.fieldBooleanNotNullIndex = otherInfo.fieldBooleanNotNullIndex; - this.fieldBooleanNullIndex = otherInfo.fieldBooleanNullIndex; - this.fieldBytesNotNullIndex = otherInfo.fieldBytesNotNullIndex; - this.fieldBytesNullIndex = otherInfo.fieldBytesNullIndex; - this.fieldByteNotNullIndex = otherInfo.fieldByteNotNullIndex; - this.fieldByteNullIndex = otherInfo.fieldByteNullIndex; - this.fieldShortNotNullIndex = otherInfo.fieldShortNotNullIndex; - this.fieldShortNullIndex = otherInfo.fieldShortNullIndex; - this.fieldIntegerNotNullIndex = otherInfo.fieldIntegerNotNullIndex; - this.fieldIntegerNullIndex = otherInfo.fieldIntegerNullIndex; - this.fieldLongNotNullIndex = otherInfo.fieldLongNotNullIndex; - this.fieldLongNullIndex = otherInfo.fieldLongNullIndex; - this.fieldFloatNotNullIndex = otherInfo.fieldFloatNotNullIndex; - this.fieldFloatNullIndex = otherInfo.fieldFloatNullIndex; - this.fieldDoubleNotNullIndex = otherInfo.fieldDoubleNotNullIndex; - this.fieldDoubleNullIndex = otherInfo.fieldDoubleNullIndex; - this.fieldDateNotNullIndex = otherInfo.fieldDateNotNullIndex; - this.fieldDateNullIndex = otherInfo.fieldDateNullIndex; - this.fieldObjectNullIndex = otherInfo.fieldObjectNullIndex; - - setIndicesMap(otherInfo.getIndicesMap()); + protected final ColumnInfo copy(boolean mutable) { + return new NullTypesColumnInfo(this, mutable); } @Override - public final NullTypesColumnInfo clone() { - return (NullTypesColumnInfo) super.clone(); + protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { + final NullTypesColumnInfo src = (NullTypesColumnInfo) rawSrc; + final NullTypesColumnInfo dst = (NullTypesColumnInfo) rawDst; + dst.fieldStringNotNullIndex = src.fieldStringNotNullIndex; + dst.fieldStringNullIndex = src.fieldStringNullIndex; + dst.fieldBooleanNotNullIndex = src.fieldBooleanNotNullIndex; + dst.fieldBooleanNullIndex = src.fieldBooleanNullIndex; + dst.fieldBytesNotNullIndex = src.fieldBytesNotNullIndex; + dst.fieldBytesNullIndex = src.fieldBytesNullIndex; + dst.fieldByteNotNullIndex = src.fieldByteNotNullIndex; + dst.fieldByteNullIndex = src.fieldByteNullIndex; + dst.fieldShortNotNullIndex = src.fieldShortNotNullIndex; + dst.fieldShortNullIndex = src.fieldShortNullIndex; + dst.fieldIntegerNotNullIndex = src.fieldIntegerNotNullIndex; + dst.fieldIntegerNullIndex = src.fieldIntegerNullIndex; + dst.fieldLongNotNullIndex = src.fieldLongNotNullIndex; + dst.fieldLongNullIndex = src.fieldLongNullIndex; + dst.fieldFloatNotNullIndex = src.fieldFloatNotNullIndex; + dst.fieldFloatNullIndex = src.fieldFloatNullIndex; + dst.fieldDoubleNotNullIndex = src.fieldDoubleNotNullIndex; + dst.fieldDoubleNullIndex = src.fieldDoubleNullIndex; + dst.fieldDateNotNullIndex = src.fieldDateNotNullIndex; + dst.fieldDateNullIndex = src.fieldDateNullIndex; + dst.fieldObjectNullIndex = src.fieldObjectNullIndex; } - } + private NullTypesColumnInfo columnInfo; private ProxyState proxyState; private static final List FIELD_NAMES; @@ -893,7 +872,7 @@ public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } - final NullTypesColumnInfo columnInfo = new NullTypesColumnInfo(sharedRealm.getPath(), table); + final NullTypesColumnInfo columnInfo = new NullTypesColumnInfo(sharedRealm, table); if (table.hasPrimaryKey()) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); @@ -1514,7 +1493,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map objects, Map cache) { Table table = realm.getTable(some.test.NullTypes.class); - long tableNativePtr = table.getNativeTablePointer(); + long tableNativePtr = table.getNativePtr(); NullTypesColumnInfo columnInfo = (NullTypesColumnInfo) realm.schema.getColumnInfo(some.test.NullTypes.class); some.test.NullTypes object = null; while (objects.hasNext()) { @@ -1722,7 +1701,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map objects, Map cache) { Table table = realm.getTable(some.test.NullTypes.class); - long tableNativePtr = table.getNativeTablePointer(); + long tableNativePtr = table.getNativePtr(); NullTypesColumnInfo columnInfo = (NullTypesColumnInfo) realm.schema.getColumnInfo(some.test.NullTypes.class); some.test.NullTypes object = null; while (objects.hasNext()) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 030081db38..b5bb229267 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -31,37 +31,35 @@ public class SimpleRealmProxy extends some.test.Simple implements RealmObjectProxy, SimpleRealmProxyInterface { - static final class SimpleColumnInfo extends ColumnInfo - implements Cloneable { + static final class SimpleColumnInfo extends ColumnInfo { + long nameIndex; + long ageIndex; - public long nameIndex; - public long ageIndex; - - SimpleColumnInfo(String path, Table table) { - final Map indicesMap = new HashMap(2); - this.nameIndex = getValidColumnIndex(path, table, "Simple", "name"); - indicesMap.put("name", this.nameIndex); - this.ageIndex = getValidColumnIndex(path, table, "Simple", "age"); - indicesMap.put("age", this.ageIndex); + SimpleColumnInfo(SharedRealm realm, Table table) { + super(2); + this.nameIndex = addColumnDetails(table, "name", RealmFieldType.STRING); + this.ageIndex = addColumnDetails(table, "age", RealmFieldType.INTEGER); + } - setIndicesMap(indicesMap); + SimpleColumnInfo(ColumnInfo src, boolean mutable) { + super(src, mutable); + copy(src, this); } @Override - public final void copyColumnInfoFrom(ColumnInfo other) { - final SimpleColumnInfo otherInfo = (SimpleColumnInfo) other; - this.nameIndex = otherInfo.nameIndex; - this.ageIndex = otherInfo.ageIndex; - - setIndicesMap(otherInfo.getIndicesMap()); + protected final ColumnInfo copy(boolean mutable) { + return new SimpleColumnInfo(this, mutable); } @Override - public final SimpleColumnInfo clone() { - return (SimpleColumnInfo) super.clone(); + protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { + final SimpleColumnInfo src = (SimpleColumnInfo) rawSrc; + final SimpleColumnInfo dst = (SimpleColumnInfo) rawDst; + dst.nameIndex = src.nameIndex; + dst.ageIndex = src.ageIndex; } - } + private SimpleColumnInfo columnInfo; private ProxyState proxyState; private static final List FIELD_NAMES; @@ -173,7 +171,7 @@ public static SimpleColumnInfo validateTable(SharedRealm sharedRealm, boolean al columnTypes.put(table.getColumnName(i), table.getColumnType(i)); } - final SimpleColumnInfo columnInfo = new SimpleColumnInfo(sharedRealm.getPath(), table); + final SimpleColumnInfo columnInfo = new SimpleColumnInfo(sharedRealm, table); if (table.hasPrimaryKey()) { throw new RealmMigrationNeededException(sharedRealm.getPath(), "Primary Key defined for field " + table.getColumnName(table.getPrimaryKey()) + " was removed."); @@ -298,7 +296,7 @@ public static long insert(Realm realm, some.test.Simple object, Map objects, Map cache) { Table table = realm.getTable(some.test.Simple.class); - long tableNativePtr = table.getNativeTablePointer(); + long tableNativePtr = table.getNativePtr(); SimpleColumnInfo columnInfo = (SimpleColumnInfo) realm.schema.getColumnInfo(some.test.Simple.class); some.test.Simple object = null; while (objects.hasNext()) { @@ -338,7 +336,7 @@ public static long insertOrUpdate(Realm realm, some.test.Simple object, Map objects, Map cache) { Table table = realm.getTable(some.test.Simple.class); - long tableNativePtr = table.getNativeTablePointer(); + long tableNativePtr = table.getNativePtr(); SimpleColumnInfo columnInfo = (SimpleColumnInfo) realm.schema.getColumnInfo(some.test.Simple.class); some.test.Simple object = null; while (objects.hasNext()) { diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 8d8616a30d..e27f97fb91 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -155,7 +155,7 @@ dependencies { androidTestAnnotationProcessor project(':realm-annotations-processor') androidTestCompile fileTree(dir: 'testLibs', include: ['*.jar']) androidTestCompile 'io.reactivex:rxjava:1.1.0' - androidTestCompile 'com.android.support:support-annotations:25.2.0' + androidTestCompile 'com.android.support:support-annotations:25.3.1' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java index d8cfe9972b..82f9726ad9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java @@ -39,6 +39,7 @@ import static junit.framework.Assert.assertSame; import static org.junit.Assert.assertNotEquals; + @RunWith(AndroidJUnit4.class) public class ColumnIndicesTests { @Rule @@ -81,18 +82,20 @@ public void copyDeeply() { final long schemaVersion = 100; final ColumnIndices columnIndices = create(schemaVersion); - final ColumnIndices deepCopy = columnIndices.clone(); + final ColumnIndices deepCopy = new ColumnIndices(columnIndices, true); + assertNotSame(columnIndices, deepCopy); assertEquals(schemaVersion, deepCopy.getSchemaVersion()); - assertEquals(columnIndices.getColumnIndex(Cat.class, Cat.FIELD_NAME), - deepCopy.getColumnIndex(Cat.class, Cat.FIELD_NAME)); - assertEquals(columnIndices.getColumnIndex(Dog.class, Dog.FIELD_AGE), - deepCopy.getColumnIndex(Dog.class, Dog.FIELD_AGE)); - // Checks if those are different instance. - assertNotSame(columnIndices, deepCopy); - assertNotSame(columnIndices.getColumnInfo(Cat.class), deepCopy.getColumnInfo(Cat.class)); - assertNotSame(columnIndices.getColumnInfo(Dog.class), deepCopy.getColumnInfo(Dog.class)); + ColumnInfo colInfo = columnIndices.getColumnInfo(Cat.class); + ColumnInfo colInfoCopy = deepCopy.getColumnInfo(Cat.class); + assertNotSame(colInfo, colInfoCopy); + assertEquals(colInfo.getColumnIndex(Cat.FIELD_NAME), colInfoCopy.getColumnIndex(Cat.FIELD_NAME)); + + colInfo = columnIndices.getColumnInfo(Dog.class); + colInfoCopy = deepCopy.getColumnInfo(Dog.class); + assertNotSame(colInfo, colInfoCopy); + assertEquals(colInfo.getColumnIndex(Dog.FIELD_AGE), colInfoCopy.getColumnIndex(Dog.FIELD_AGE)); } @Test @@ -112,10 +115,11 @@ public void copyFrom() { assertNotEquals(catColumnInfoInSource.nameIndex, catColumnInfoInTarget.nameIndex); assertNotSame(catColumnInfoInSource.getIndicesMap(), catColumnInfoInTarget.getIndicesMap()); - target.copyFrom(source, mediator); + target.copyFrom(source); assertEquals(sourceSchemaVersion, target.getSchemaVersion()); assertEquals(catColumnInfoInSource.nameIndex, catColumnInfoInTarget.nameIndex); - assertSame(catColumnInfoInSource.getIndicesMap(), catColumnInfoInTarget.getIndicesMap()); + // update, not replace + assertSame(catColumnInfoInTarget, target.getColumnInfo(Cat.class)); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java index 618c54d588..6fcebce670 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java @@ -30,7 +30,8 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotSame; -import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.fail; + @RunWith(AndroidJUnit4.class) public class ColumnInfoTests { @@ -40,11 +41,13 @@ public class ColumnInfoTests { public final ExpectedException thrown = ExpectedException.none(); private Realm realm; + private RealmProxyMediator mediator; @Before public void setUp() { RealmConfiguration config = configFactory.createConfiguration(); realm = Realm.getInstance(config); + mediator = realm.getConfiguration().getSchemaMediator(); } @After @@ -56,10 +59,10 @@ public void tearDown() { @Test public void copyColumnInfoFrom_checkIndex() { - final RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); - final CatRealmProxy.CatColumnInfo sourceColumnInfo, targetColumnInfo; - sourceColumnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); - targetColumnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + CatRealmProxy.CatColumnInfo sourceColumnInfo + = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + CatRealmProxy.CatColumnInfo targetColumnInfo + = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); // Checks precondition. assertNotSame(sourceColumnInfo, targetColumnInfo); @@ -83,26 +86,22 @@ public void copyColumnInfoFrom_checkIndex() { targetColumnInfo.ownerIndex = 0; targetColumnInfo.scaredOfDogIndex = 0; - targetColumnInfo.copyColumnInfoFrom(sourceColumnInfo); - - assertEquals(1, targetColumnInfo.nameIndex); - assertEquals(2, targetColumnInfo.ageIndex); - assertEquals(3, targetColumnInfo.heightIndex); - assertEquals(4, targetColumnInfo.weightIndex); - assertEquals(5, targetColumnInfo.hasTailIndex); - assertEquals(6, targetColumnInfo.birthdayIndex); - assertEquals(7, targetColumnInfo.ownerIndex); - assertEquals(8, targetColumnInfo.scaredOfDogIndex); + targetColumnInfo.copyFrom(sourceColumnInfo); - // Current implementation shares the indices map. - assertSame(sourceColumnInfo.getIndicesMap(), targetColumnInfo.getIndicesMap()); + assertEquals(sourceColumnInfo.nameIndex, targetColumnInfo.nameIndex); + assertEquals(sourceColumnInfo.ageIndex, targetColumnInfo.ageIndex); + assertEquals(sourceColumnInfo.heightIndex, targetColumnInfo.heightIndex); + assertEquals(sourceColumnInfo.weightIndex, targetColumnInfo.weightIndex); + assertEquals(sourceColumnInfo.hasTailIndex, targetColumnInfo.hasTailIndex); + assertEquals(sourceColumnInfo.birthdayIndex, targetColumnInfo.birthdayIndex); + assertEquals(sourceColumnInfo.ownerIndex, targetColumnInfo.ownerIndex); + assertEquals(sourceColumnInfo.scaredOfDogIndex, targetColumnInfo.scaredOfDogIndex); } @Test - public void clone_hasSameValue() { - final RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); - final CatRealmProxy.CatColumnInfo columnInfo; - columnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + public void copy_differentInstanceSameValues() { + final CatRealmProxy.CatColumnInfo columnInfo + = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); columnInfo.nameIndex = 1; columnInfo.ageIndex = 2; @@ -113,9 +112,21 @@ public void clone_hasSameValue() { columnInfo.ownerIndex = 7; columnInfo.scaredOfDogIndex = 8; - CatRealmProxy.CatColumnInfo copy = columnInfo.clone(); + CatRealmProxy.CatColumnInfo copy = (CatRealmProxy.CatColumnInfo) columnInfo.copy(true); - // Modifies original object. + // verify that the copy is identical + assertNotSame(columnInfo, copy); + assertEquals(columnInfo.getIndicesMap(), copy.getIndicesMap()); + assertEquals(columnInfo.nameIndex, copy.nameIndex); + assertEquals(columnInfo.ageIndex, copy.ageIndex); + assertEquals(columnInfo.heightIndex, copy.heightIndex); + assertEquals(columnInfo.weightIndex, copy.weightIndex); + assertEquals(columnInfo.hasTailIndex, copy.hasTailIndex); + assertEquals(columnInfo.birthdayIndex, copy.birthdayIndex); + assertEquals(columnInfo.ownerIndex, copy.ownerIndex); + assertEquals(columnInfo.scaredOfDogIndex, copy.scaredOfDogIndex); + + // Modify original object columnInfo.nameIndex = 0; columnInfo.ageIndex = 0; columnInfo.heightIndex = 0; @@ -125,8 +136,7 @@ public void clone_hasSameValue() { columnInfo.ownerIndex = 0; columnInfo.scaredOfDogIndex = 0; - assertNotSame(columnInfo, copy); - + // the copy should not change assertEquals(1, copy.nameIndex); assertEquals(2, copy.ageIndex); assertEquals(3, copy.heightIndex); @@ -135,8 +145,18 @@ public void clone_hasSameValue() { assertEquals(6, copy.birthdayIndex); assertEquals(7, copy.ownerIndex); assertEquals(8, copy.scaredOfDogIndex); + } - // Current implementation shares the indices map between copies. - assertSame(columnInfo.getIndicesMap(), copy.getIndicesMap()); + @Test + public void copy_immutableThrows() { + final CatRealmProxy.CatColumnInfo original + = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + + CatRealmProxy.CatColumnInfo copy = (CatRealmProxy.CatColumnInfo) original.copy(false); + try { + copy.copyFrom(original); + fail("Attempt to copy to an immutable ColumnInfo should throwS"); + } catch (UnsupportedOperationException ignore) { + } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index a7c99b4fb4..400f618dc9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -675,7 +675,7 @@ public void equalTo_noFieldObjectShouldThrow() { dynamicRealm.commitTransaction(); thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Invalid query: field 'nonExisting' does not exist in table 'NoField'."); + thrown.expectMessage("Invalid query: field 'nonExisting' not found in table 'NoField'."); dynamicRealm.where(className).equalTo("nonExisting", 1); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index c11178f0eb..1d231e4d23 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -174,14 +174,17 @@ public void linkingObjects_invalidFieldType() { for (RealmFieldType fieldType : RealmFieldType.values()) { try { switch (fieldType) { + // skip unsupported types + case UNSUPPORTED_TABLE: // fall-through + case UNSUPPORTED_MIXED: // fall-through + case UNSUPPORTED_DATE: + continue; // skip valid types case OBJECT: // fall-through case LIST: continue; - // skip unsupported types - case UNSUPPORTED_TABLE: // fall-through - case UNSUPPORTED_MIXED: // fall-through - case UNSUPPORTED_DATE: + // skip special case + case LINKING_OBJECTS: continue; case INTEGER: object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_INT); @@ -213,6 +216,16 @@ public void linkingObjects_invalidFieldType() { assertTrue(expected.getMessage().startsWith("Unexpected field type")); } } + + // Linking Object fields are implicit and do not exist. + for (String field : new String[] {AllJavaTypes.FIELD_LO_OBJECT, AllJavaTypes.FIELD_LO_LIST}) { + try { + object.linkingObjects(AllJavaTypes.CLASS_NAME, field); + fail(); + } catch (IllegalArgumentException expected) { + assertTrue(expected.getMessage().contains("does not exist")); + } + } } @Test @@ -486,12 +499,12 @@ public void execute(DynamicRealm realm) { @Test public void dynamicQuery_invalidSyntax() { String[] invalidBacklinks = new String[] { - "linkingObject(x", - "linkingObject(x.y", - "linkingObject(x.y)", - "linkingObject(x.y).", - "linkingObject(x.y)..z", - "linkingObject(x.y).linkingObjects(x1.y1).z" + "linkingObject(x", + "linkingObject(x.y", + "linkingObject(x.y)", + "linkingObject(x.y).", + "linkingObject(x.y)..z", + "linkingObject(x.y).linkingObjects(x1.y1).z" }; } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index b729034259..2d61e6b637 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -29,7 +29,6 @@ import org.junit.runner.RunWith; import java.io.IOException; -import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import io.realm.entities.AllJavaTypes; @@ -51,6 +50,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + @RunWith(AndroidJUnit4.class) public class LinkingObjectsManagedTests { private interface PostConditions { @@ -148,6 +148,7 @@ public void basic_multipleReferencesFromParentList() { // One entry for each reference, so two references from a LinkList will // result in two backlinks. assertEquals(2, child.getListParents().size()); + assertEquals(parent, child.getListParents().first()); assertEquals(parent, child.getListParents().last()); } @@ -193,14 +194,14 @@ public void onChange(AllJavaTypes object) { looperThreadRealm.commitTransaction(); verifyPostConditions( - looperThreadRealm, - new PostConditions() { - @Override - public void run(Realm realm) { - assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); - } - }, - child, parent); + looperThreadRealm, + new PostConditions() { + @Override + public void run(Realm realm) { + assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + } + }, + child, parent); } // A listener registered on the backlinked field should be called when a commit adds a backlink @@ -228,15 +229,15 @@ public void onChange(RealmResults object) { looperThreadRealm.commitTransaction(); verifyPostConditions( - looperThreadRealm, - new PostConditions() { - @Override - public void run(Realm realm) { - assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); - assertEquals(1, counter.get()); - } - }, - child, parent); + looperThreadRealm, + new PostConditions() { + @Override + public void run(Realm realm) { + assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + assertEquals(1, counter.get()); + } + }, + child, parent); } // A listener registered on the backlinked field should not be called after the listener is removed @@ -265,14 +266,14 @@ public void onChange(RealmResults object) { looperThreadRealm.commitTransaction(); verifyPostConditions( - looperThreadRealm, - new PostConditions() { - @Override - public void run(Realm realm) { - assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); - } - }, - child, parent); + looperThreadRealm, + new PostConditions() { + @Override + public void run(Realm realm) { + assertEquals(2, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + } + }, + child, parent); } // A listener registered on the backlinked object should be called when a backlinked object is deleted @@ -301,15 +302,15 @@ public void onChange(RealmResults object) { looperThreadRealm.commitTransaction(); verifyPostConditions( - looperThreadRealm, - new PostConditions() { - @Override - public void run(Realm realm) { - assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); - assertEquals(1, counter.get()); - } - }, - child, parent); + looperThreadRealm, + new PostConditions() { + @Override + public void run(Realm realm) { + assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + assertEquals(1, counter.get()); + } + }, + child, parent); } // A listener registered on the backlinked object should not called for an unrelated change @@ -336,14 +337,14 @@ public void onChange(RealmResults object) { looperThreadRealm.commitTransaction(); verifyPostConditions( - looperThreadRealm, - new PostConditions() { - @Override - public void run(Realm realm) { - assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); - } - }, - child, parent); + looperThreadRealm, + new PostConditions() { + @Override + public void run(Realm realm) { + assertEquals(1, looperThreadRealm.where(AllJavaTypes.class).findAll().size()); + } + }, + child, parent); } // Fields annotated with @LinkingObjects should not be affected by JSON updates @@ -516,9 +517,9 @@ public void migration_backlinkedFieldInUse() { final String realmName = "backlinks-fieldInUse.realm"; RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() - .name(realmName) - .schema(BacklinksSource.class, BacklinksTarget.class) - .build(); + .name(realmName) + .schema(BacklinksSource.class, BacklinksTarget.class) + .build(); try { configFactory.copyRealmFromAssets(context, realmName, realmName); @@ -551,14 +552,15 @@ public void migration_backlinkedFieldInUse() { * basic validation passes. Backlink validation, however, should fail, seeking the * `BacklinksSource` table. */ + @Ignore("Need to rebuild the test library") @Test public void migration_backlinkedSourceClassDoesntExist() throws IOException { final String realmName = "backlinks-missingSourceClass.realm"; RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() - .name(realmName) - .schema(BacklinksTarget.class) - .build(); + .name(realmName) + .schema(BacklinksTarget.class) + .build(); try { configFactory.copyRealmFromAssets(context, realmName, realmName); @@ -592,14 +594,15 @@ public void migration_backlinkedSourceClassDoesntExist() throws IOException { * validate its table. If we have been living clean lives, though, the validator for * `BacklinksMissingFieldTarget` should notice that there is no field named `BacklinksMissingFieldSource.xxxchild`. */ + @Ignore("Need to rebuild the test library") @Test public void migration_backlinkedSourceFieldDoesntExist() { final String realmName = "backlinks-missingSourceField.realm"; RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() - .name(realmName) - .modules(new BacklinksMissingFieldSourceModule(), new BacklinksMissingFieldTargetModule()) - .build(); + .name(realmName) + .modules(new BacklinksMissingFieldSourceModule(), new BacklinksMissingFieldTargetModule()) + .build(); try { configFactory.copyRealmFromAssets(context, realmName, realmName); @@ -630,14 +633,15 @@ public void migration_backlinkedSourceFieldDoesntExist() { * for `BacklinksWrongTypeTarget` should notice, though, that its `parents` field points to an object * of the wrong type, `Integer`, instead of `BacklinksWrongTypeSource`. */ + @Ignore("Need to rebuild the test library") @Test public void migration_backlinkedSourceFieldWrongType() { final String realmName = "backlinks-sourceFieldWrongType.realm"; RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() - .name(realmName) - .modules(new BacklinksWrongTypeSourceModule(), new BacklinksWrongTypeTargetModule()) - .build(); + .name(realmName) + .modules(new BacklinksWrongTypeSourceModule(), new BacklinksWrongTypeTargetModule()) + .build(); try { configFactory.copyRealmFromAssets(context, realmName, realmName); @@ -671,111 +675,6 @@ public void query_multipleReferencesWithDistinct() { assertTrue(child.getListParents().contains(parent)); } - // Query on a field descriptor starting with a backlink - // The test objects are: - // gen1 - // / \ - // gen2A gen2B - // \\ // - // gen3 - // / = object ref - // // = list ref - @Test - @Ignore - public void query_startWithBacklink() { - realm.beginTransaction(); - AllJavaTypes gen1 = realm.createObject(AllJavaTypes.class, 10); - - AllJavaTypes gen2A = realm.createObject(AllJavaTypes.class, 1); - gen2A.setFieldObject(gen1); - - AllJavaTypes gen2B = realm.createObject(AllJavaTypes.class, 2); - gen2B.setFieldObject(gen1); - - AllJavaTypes gen3 = realm.createObject(AllJavaTypes.class, 3); - RealmList parents = gen3.getFieldList(); - parents.add(gen2A); - parents.add(gen2B); - - realm.commitTransaction(); - - RealmResults result = realm.where(AllJavaTypes.class) - .greaterThan("objectParents.fieldId", 1) - .findAll(); - assertEquals(1, result.size()); - assertTrue(result.contains(gen2B)); - } - - // Query on a field descriptor that ends with a backlink - // The test objects are: - // gen1 - // / \ - // gen2A gen2B - // \\ // - // gen3 - // / = object ref - // // = list ref - @Test - @Ignore - public void query_endWithBacklink() { - realm.beginTransaction(); - AllJavaTypes gen1 = realm.createObject(AllJavaTypes.class, 10); - - AllJavaTypes gen2A = realm.createObject(AllJavaTypes.class, 1); - gen2A.setFieldObject(gen1); - - AllJavaTypes gen2B = realm.createObject(AllJavaTypes.class, 2); - gen2B.setFieldObject(gen1); - - AllJavaTypes gen3 = realm.createObject(AllJavaTypes.class, 3); - RealmList parents = gen3.getFieldList(); - parents.add(gen2A); - parents.add(gen2B); - - realm.commitTransaction(); - - RealmResults result = realm.where(AllJavaTypes.class) - .isNotNull("objectParents.listParents") - .findAll(); - assertEquals(2, result.size()); - assertTrue(result.contains(gen2A)); - assertTrue(result.contains(gen2B)); - } - - // Query on a field descriptor that has a backlink in the middle - // The test objects are: - // gen1 - // / \ - // gen2A gen2B - // \\ // - // gen3 - // / = object ref - // // = list ref - @Test - @Ignore - public void query_backlinkInMiddle() { - realm.beginTransaction(); - AllJavaTypes gen1 = realm.createObject(AllJavaTypes.class, 10); - - AllJavaTypes gen2A = realm.createObject(AllJavaTypes.class, 1); - gen2A.setFieldObject(gen1); - - AllJavaTypes gen2B = realm.createObject(AllJavaTypes.class, 2); - gen2B.setFieldObject(gen1); - - AllJavaTypes gen3 = realm.createObject(AllJavaTypes.class, 3); - RealmList parents = gen3.getFieldList(); - parents.add(gen2A); - parents.add(gen2B); - - realm.commitTransaction(); - - RealmResults result = realm.where(AllJavaTypes.class) - .lessThan("objectParents.listParents.fieldId", 4) - .findAll(); - assertEquals(2, result.size()); - } - // Based on a quick conversation with Christian Melchior and Mark Rowe, // it appears that notifications are enqueued, briefly, on a non-Java // thread. That makes their delivery onto the looper thread unpredictable. @@ -798,13 +697,13 @@ private void verifyPostConditions(final Realm realm, final PostConditions test, looperThread.keepStrongReference(ref); } looperThread.postRunnable( - new Runnable() { - @Override - public void run() { - test.run(realm); - looperThread.testComplete(); - } - }); + new Runnable() { + @Override + public void run() { + test.run(realm); + looperThread.testComplete(); + } + }); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java new file mode 100644 index 0000000000..70d5218ecc --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java @@ -0,0 +1,572 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Date; + +import io.realm.entities.AllJavaTypes; +import io.realm.entities.NullTypes; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@Ignore +@RunWith(AndroidJUnit4.class) +public class LinkingObjectsQueryTests extends QueryTests { + + // All the basic tests for is[Not](Equal|Null) are in RealmQueryTests + + + // Query on a field descriptor starting with a backlink + // Build a simple object graph. + // The test objects are: + // gen1 + // / \ + // gen2A gen2B + // \\ // + // gen3 + // / = object ref + // // = list ref + @Test + public void query_startWithBacklink() { + realm.beginTransaction(); + AllJavaTypes gen1 = realm.createObject(AllJavaTypes.class, 10); + + AllJavaTypes gen2A = realm.createObject(AllJavaTypes.class, 1); + gen2A.setFieldObject(gen1); + + AllJavaTypes gen2B = realm.createObject(AllJavaTypes.class, 2); + gen2B.setFieldObject(gen1); + + AllJavaTypes gen3 = realm.createObject(AllJavaTypes.class, 3); + RealmList parents = gen3.getFieldList(); + parents.add(gen2A); + parents.add(gen2B); + + realm.commitTransaction(); + + RealmResults result = realm.where(AllJavaTypes.class) + .greaterThan(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_ID, 1) + .findAll(); + assertEquals(1, result.size()); + assertTrue(result.contains(gen1)); + } + + // Query on a field descriptor that has a backlink in the middle + // Build a simple object graph. + // The test objects are: + // gen1 + // / \ + // gen2A gen2B + // \\ // + // gen3 + // / = object ref + // // = list ref + @Test + public void query_backlinkInMiddle() { + realm.beginTransaction(); + AllJavaTypes gen1 = realm.createObject(AllJavaTypes.class, 10); + + AllJavaTypes gen2A = realm.createObject(AllJavaTypes.class, 1); + gen2A.setFieldObject(gen1); + + AllJavaTypes gen2B = realm.createObject(AllJavaTypes.class, 2); + gen2B.setFieldObject(gen1); + + AllJavaTypes gen3 = realm.createObject(AllJavaTypes.class, 3); + RealmList parents = gen3.getFieldList(); + parents.add(gen2A); + parents.add(gen2B); + + realm.commitTransaction(); + + // TODO: Explain what this test is doing + RealmResults result = realm.where(AllJavaTypes.class) + .lessThan(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_ID, 2) + .findAll(); + assertEquals(1, result.size()); + assertTrue(result.contains(gen2A)); + } + + // Tests isNotNull on link's nullable field. + @Test + public void isNull_object() { + populateTestRealmForNullTests(realm); + + // 1 String + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_STRING_NULL).count()); + // 2 Bytes + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_BYTES_NULL).count()); + // 3 Boolean + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_BOOLEAN_NULL).count()); + // 4 Byte + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_BYTE_NULL).count()); + // 5 Short + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_SHORT_NULL).count()); + // 6 Integer + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_INTEGER_NULL).count()); + // 7 Long + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_LONG_NULL).count()); + // 8 Float + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_FLOAT_NULL).count()); + // 9 Double + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_DOUBLE_NULL).count()); + // 10 Date + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_DATE_NULL).count()); + } + + // Tests isNull on link's nullable field. + @Test + public void isNull_list() { + populateTestRealmForNullTests(realm); + + // 1 String + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_STRING_NULL).count()); + // 2 Bytes + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_BYTES_NULL).count()); + // 3 Boolean + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_BOOLEAN_NULL).count()); + // 4 Byte + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_BYTE_NULL).count()); + // 5 Short + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_SHORT_NULL).count()); + // 6 Integer + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_INTEGER_NULL).count()); + // 7 Long + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_LONG_NULL).count()); + // 8 Float + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_FLOAT_NULL).count()); + // 9 Double + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_DOUBLE_NULL).count()); + // 10 Date + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_DATE_NULL).count()); + } + + @Test + public void isNull_unsupported() { + long result; + + // Tests for other unsupported null types are in RealmQueryTests + + try { + result = realm.where(NullTypes.class).isNull(NullTypes.FIELD_LO_OBJECT).count(); + fail("isNull should throw on type LINKING_OBJECT(14) targeting an OBJECT"); + } catch (IllegalArgumentException expected) { + assertEquals("Illegal Argument: LinkingObject from field fieldObjectNull is not nullable.", expected.getMessage()); + } + try { + result = realm.where(NullTypes.class).isNull(NullTypes.FIELD_LO_LIST).count(); + fail("isNull should throw on type LINKING_OBJECT(14) targeting a LIST"); + } catch (IllegalArgumentException expected) { + assertEquals("Illegal Argument: LinkingObject from field fieldListNull is not nullable.", expected.getMessage()); + } + } + + @Test + public void isNull_unsupportedLinkedTypes() { + RealmQuery result; + + // Tests for other unsupported null types are in RealmQueryTests + + try { + result = realm.where(NullTypes.class).isNull(NullTypes.FIELD_OBJECT_NULL + "." + NullTypes.FIELD_LO_OBJECT); + fail("isNull should throw on nested linked fields (LINKING_OBJECT => OBJECT)"); + } catch (IllegalArgumentException expected) { + assertEquals("Illegal Argument: LinkingObject from field fieldObjectNull is not nullable.", expected.getMessage()); + } + try { + result = realm.where(NullTypes.class).isNull(NullTypes.FIELD_OBJECT_NULL + "." + NullTypes.FIELD_LO_LIST); + fail("isNull should throw on nested linked fields (LINKING_OBJECT => LIST)"); + } catch (IllegalArgumentException expected) { + assertEquals("Illegal Argument: LinkingObject from field fieldListNull is not nullable.", expected.getMessage()); + } + } + + // Tests isNotNull on link's nullable field. + @Test + public void isNotNull_object() { + populateTestRealmForNullTests(realm); + + // 1 String + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_STRING_NULL).count()); + // 2 Bytes + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_BYTES_NULL).count()); + // 3 Boolean + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_BOOLEAN_NULL).count()); + // 4 Byte + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_BYTE_NULL).count()); + // 5 Short + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_SHORT_NULL).count()); + // 6 Integer + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_INTEGER_NULL).count()); + // 7 Long + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_LONG_NULL).count()); + // 8 Float + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_FLOAT_NULL).count()); + // 9 Double + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_DOUBLE_NULL).count()); + // 10 Date + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_DATE_NULL).count()); + } + + // Tests isNotNull on link's nullable field. + @Test + public void isNotNull_list() { + populateTestRealmForNullTests(realm); + + // 1 String + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_STRING_NULL).count()); + // 2 Bytes + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_BYTES_NULL).count()); + // 3 Boolean + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_BOOLEAN_NULL).count()); + // 4 Byte + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_BYTE_NULL).count()); + // 5 Short + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_SHORT_NULL).count()); + // 6 Integer + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_INTEGER_NULL).count()); + // 7 Long + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_LONG_NULL).count()); + // 8 Float + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_FLOAT_NULL).count()); + // 9 Double + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_DOUBLE_NULL).count()); + // 10 Date + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_DATE_NULL).count()); + } + + @Test + public void isNotNull_unsupported() { + long result; + + // Tests for other unsupported not null types are in RealmQueryTests + + try { + result = realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_LO_OBJECT).count(); + fail("isNotNull should throw on type LINKING_OBJECT(14) targeting an OBJECT"); + } catch (IllegalArgumentException expected) { + assertEquals("Illegal Argument: LinkingObject from field fieldObjectNull is not nullable.", expected.getMessage()); + } + try { + result = realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_LO_LIST).count(); + fail("isNotNull should throw on type LINKING_OBJECT(14) targeting a LIST"); + } catch (IllegalArgumentException expected) { + assertEquals("Illegal Argument: LinkingObject from field fieldListNull is not nullable.", expected.getMessage()); + } + } + + @Test + public void isNotNull_unsupportedLinkedTypes() { + RealmQuery result; + + // Tests for other unsupported not null types are in RealmQueryTests + + try { + result = realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_OBJECT_NULL + "." + NullTypes.FIELD_LO_OBJECT); + fail("isNotNull should throw on nested linked fields (LINKING_OBJECT => OBJECT)"); + } catch (IllegalArgumentException expected) { + assertEquals("Illegal Argument: LinkingObject from field fieldObjectNull is not nullable.", expected.getMessage()); + } + try { + result = realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_OBJECT_NULL + "." + NullTypes.FIELD_LO_LIST); + fail("isNotNull should throw on nested linked fields (LINKING_OBJECT => LIST)"); + } catch (IllegalArgumentException expected) { + assertEquals("Illegal Argument: LinkingObject from field fieldListNull is not nullable.", expected.getMessage()); + } + } + + @Test + public void isEmpty() { + createIsEmptyDataSet(realm); + for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { + switch (type) { + case LINKING_OBJECTS: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST).count()); + break; + default: + // tested in RealmQueryTests + } + } + } + + @Test + public void isEmpty_acrossLink() { + createIsEmptyDataSet(realm); + for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { + switch (type) { + case LINKING_OBJECTS: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); + break; + default: + // tested in RealmQueryTests + } + } + } + + @Test + public void isEmpty_acrossLinkingObjectObjectLink() { + createIsEmptyDataSet(realm); + for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { + switch (type) { + case STRING: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_STRING).count()); + break; + case BINARY: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_BINARY).count()); + break; + case LIST: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LIST).count()); + break; + case LINKING_OBJECTS: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); + break; + default: + fail("Unknown type: " + type); + } + } + } + + @Test + public void isEmpty_acrossLinkingObjectListLink() { + createIsEmptyDataSet(realm); + for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { + switch (type) { + case STRING: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_STRING).count()); + break; + case BINARY: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_BINARY).count()); + break; + case LIST: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LIST).count()); + break; + case LINKING_OBJECTS: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_LIST).count()); + break; + default: + fail("Unknown type: " + type); + } + } + } + + @Test + public void isNotEmpty() { + createIsNotEmptyDataSet(realm); + for (RealmFieldType type : SUPPORTED_IS_NOT_EMPTY_TYPES) { + switch (type) { + case LINKING_OBJECTS: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST).count()); + break; + default: + // tested in RealmQueryTests + } + } + } + + @Test + public void isNotEmpty_acrossLink() { + createIsNotEmptyDataSet(realm); + for (RealmFieldType type : SUPPORTED_IS_NOT_EMPTY_TYPES) { + switch (type) { + case LINKING_OBJECTS: + // tested in LinkingObjectsQueryTests; + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); + break; + default: + // tested in RealmQueryTests + } + } + } + + @Test + public void isNotEmpty_acrossLinkingObjectObjectLink() { + createIsEmptyDataSet(realm); + for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { + switch (type) { + case STRING: + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_STRING).count()); + break; + case BINARY: + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_BINARY).count()); + break; + case LIST: + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LIST).count()); + break; + case LINKING_OBJECTS: + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); + break; + default: + fail("Unknown type: " + type); + } + } + } + + @Test + public void isNotEmpty_acrossLinkingObjectListLink() { + createIsEmptyDataSet(realm); + for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { + switch (type) { + case STRING: + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_STRING).count()); + break; + case BINARY: + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_BINARY).count()); + break; + case LIST: + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LIST).count()); + break; + case LINKING_OBJECTS: + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_LIST).count()); + break; + default: + fail("Unknown type: " + type); + } + } + } + + // Similar to the version in TestHelper, but with more Backlinks + // Creates 3 NullTypes objects. The objects are self-referenced (link) in + // order to test link queries. + // + // +-+--------+------+---------+--------+--------------------+ + // | | string | link | numeric | binary | numeric (not null) | + // +-+--------+------+---------+--------+--------------------+ + // |0| Fish | 0 | 1 | {0} | 1 | + // |1| null | null | null | null | 0 | + // |2| Horse | 1 | 3 | {1,2} | 3 | + // +-+--------+------+---------+--------+--------------------+ + private void populateTestRealmForNullTests(Realm testRealm) { + // 1 String + String[] words = {"Fish", null, "Horse"}; + // 2 Bytes + byte[][] binaries = {new byte[]{0}, null, new byte[]{1, 2}}; + // 3 Boolean + Boolean[] booleans = {false, null, true}; + // Numeric fields will be 1, 0/null, 3 + // 10 Date + Date[] dates = {new Date(0), null, new Date(10000)}; + NullTypes[] nullTypesArray = new NullTypes[3]; + + testRealm.beginTransaction(); + for (int i = 0; i < 3; i++) { + NullTypes nullTypes = new NullTypes(); + nullTypes.setId(i + 1); + // 1 String + nullTypes.setFieldStringNull(words[i]); + if (words[i] != null) { + nullTypes.setFieldStringNotNull(words[i]); + } + // 2 Bytes + nullTypes.setFieldBytesNull(binaries[i]); + if (binaries[i] != null) { + nullTypes.setFieldBytesNotNull(binaries[i]); + } + // 3 Boolean + nullTypes.setFieldBooleanNull(booleans[i]); + if (booleans[i] != null) { + nullTypes.setFieldBooleanNotNull(booleans[i]); + } + if (i != 1) { + int n = i + 1; + // 4 Byte + nullTypes.setFieldByteNull((byte) n); + nullTypes.setFieldByteNotNull((byte) n); + // 5 Short + nullTypes.setFieldShortNull((short) n); + nullTypes.setFieldShortNotNull((short) n); + // 6 Integer + nullTypes.setFieldIntegerNull(n); + nullTypes.setFieldIntegerNotNull(n); + // 7 Long + nullTypes.setFieldLongNull((long) n); + nullTypes.setFieldLongNotNull((long) n); + // 8 Float + nullTypes.setFieldFloatNull((float) n); + nullTypes.setFieldFloatNotNull((float) n); + // 9 Double + nullTypes.setFieldDoubleNull((double) n); + nullTypes.setFieldDoubleNotNull((double) n); + } + // 10 Date + nullTypes.setFieldDateNull(dates[i]); + if (dates[i] != null) { + nullTypes.setFieldDateNotNull(dates[i]); + } + + nullTypesArray[i] = testRealm.copyToRealm(nullTypes); + } + nullTypesArray[0].setFieldObjectNull(nullTypesArray[0]); + nullTypesArray[1].setFieldObjectNull(null); + nullTypesArray[2].getFieldListNull().add(nullTypesArray[1]); + testRealm.commitTransaction(); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java index fa0036e267..2bc3206cf4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -16,8 +16,6 @@ package io.realm; -import android.util.Log; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -42,6 +40,7 @@ import static junit.framework.Assert.fail; import static org.junit.Assert.assertArrayEquals; + // Tests for the ordered collection fine grained notifications for both RealmResults and RealmList. @RunWith(Parameterized.class) public class OrderedCollectionChangeSetTests { @@ -96,7 +95,7 @@ private void populateData(Realm realm, int testSize) { // The args should be [startIndex1, length1, startIndex2, length2, ...] private void checkRanges(OrderedCollectionChangeSet.Range[] ranges, int... indexAndLen) { - if ((indexAndLen.length % 2 != 0)) { + if ((indexAndLen.length % 2 != 0)) { fail("The 'indexAndLen' array length is not an even number."); } if (ranges.length != indexAndLen.length / 2) { @@ -193,7 +192,7 @@ public void check(OrderedCollectionChangeSet changeSet) { 0, 1, 2, 3, 8, 2); - assertArrayEquals(changeSet.getDeletions(), new int[]{0, 2, 3, 4, 8, 9}); + assertArrayEquals(changeSet.getDeletions(), new int[] {0, 2, 3, 4, 8, 9}); assertEquals(0, changeSet.getChangeRanges().length); assertEquals(0, changeSet.getInsertionRanges().length); assertEquals(0, changeSet.getChanges().length); @@ -228,7 +227,7 @@ public void check(OrderedCollectionChangeSet changeSet) { 1, 1, 3, 2, 8, 1); - assertArrayEquals(changeSet.getInsertions(), new int[]{1, 3, 4, 8}); + assertArrayEquals(changeSet.getInsertions(), new int[] {1, 3, 4, 8}); assertEquals(0, changeSet.getChangeRanges().length); assertEquals(0, changeSet.getDeletionRanges().length); assertEquals(0, changeSet.getChanges().length); @@ -258,7 +257,7 @@ public void check(OrderedCollectionChangeSet changeSet) { 0, 1, 2, 3, 8, 2); - assertArrayEquals(changeSet.getChanges(), new int[]{0, 2, 3, 4, 8, 9}); + assertArrayEquals(changeSet.getChanges(), new int[] {0, 2, 3, 4, 8, 9}); assertEquals(0, changeSet.getInsertionRanges().length); assertEquals(0, changeSet.getDeletionRanges().length); assertEquals(0, changeSet.getInsertions().length); @@ -288,11 +287,11 @@ public void check(OrderedCollectionChangeSet changeSet) { checkRanges(changeSet.getDeletionRanges(), 0, 1, 9, 1); - assertArrayEquals(changeSet.getDeletions(), new int[]{0, 9}); + assertArrayEquals(changeSet.getDeletions(), new int[] {0, 9}); checkRanges(changeSet.getInsertionRanges(), 0, 1, 9, 1); - assertArrayEquals(changeSet.getInsertions(), new int[]{0, 9}); + assertArrayEquals(changeSet.getInsertions(), new int[] {0, 9}); assertEquals(0, changeSet.getChangeRanges().length); assertEquals(0, changeSet.getChanges().length); looperThread.testComplete(); @@ -317,17 +316,17 @@ public void check(OrderedCollectionChangeSet changeSet) { checkRanges(changeSet.getDeletionRanges(), 0, 2, 5, 1); - assertArrayEquals(changeSet.getDeletions(), new int[]{0, 1, 5}); + assertArrayEquals(changeSet.getDeletions(), new int[] {0, 1, 5}); checkRanges(changeSet.getInsertionRanges(), 0, 2, 9, 2); - assertArrayEquals(changeSet.getInsertions(), new int[]{0, 1, 9, 10}); + assertArrayEquals(changeSet.getInsertions(), new int[] {0, 1, 9, 10}); checkRanges(changeSet.getChangeRanges(), 3, 2, 8, 1); - assertArrayEquals(changeSet.getChanges(), new int[]{3, 4, 8}); + assertArrayEquals(changeSet.getChanges(), new int[] {3, 4, 8}); looperThread.testComplete(); } @@ -357,7 +356,7 @@ public void check(OrderedCollectionChangeSet changeSet) { checkRanges(changeSet.getDeletionRanges(), 0, 2, 5, 1); - assertArrayEquals(changeSet.getDeletions(), new int[]{0, 1, 5}); + assertArrayEquals(changeSet.getDeletions(), new int[] {0, 1, 5}); assertEquals(0, changeSet.getInsertionRanges().length); assertEquals(0, changeSet.getInsertions().length); @@ -406,14 +405,12 @@ public void run() { // The change set should empty when the async query returns at the first time. @Test @RunTestInLooperThread - public void emptyChangeSet_findAllAsync(){ + public void emptyChangeSet_findAllAsync() { if (type == ObservablesType.REALM_LIST) { looperThread.testComplete(); return; } - Log.d("####", "test running on thread: " + Thread.currentThread()); - Realm realm = looperThread.getRealm(); populateData(realm, 10); final RealmResults results = realm.where(Dog.class).findAllSortedAsync(Dog.FIELD_AGE); @@ -434,8 +431,7 @@ public void onChange(RealmResults collection, OrderedCollectionChangeSet ch new Thread(new Runnable() { @Override public void run() { - Log.d("####", "runnable running on thread: " + Thread.currentThread()); - Realm realm = Realm.getInstance(looperThread.getConfiguration()) ; + Realm realm = Realm.getInstance(looperThread.getConfiguration()); realm.beginTransaction(); realm.where(Dog.class).equalTo(Dog.FIELD_AGE, 0).findFirst().deleteFromRealm(); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java new file mode 100644 index 0000000000..38e53896d9 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.ExpectedException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import io.realm.entities.AllJavaTypes; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.TestRealmConfigurationFactory; + + +public abstract class QueryTests { + public static final int TEST_DATA_SIZE = 10; + public static final int TEST_NO_PRIMARY_KEY_NULL_TYPES_SIZE = 200; + + public static final long DECADE_MILLIS = 10 * TimeUnit.DAYS.toMillis(365); + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + protected static final List SUPPORTED_IS_EMPTY_TYPES; + protected static final List NOT_SUPPORTED_IS_EMPTY_TYPES; + protected static final List SUPPORTED_IS_NOT_EMPTY_TYPES; + protected static final List NOT_SUPPORTED_IS_NOT_EMPTY_TYPES; + + static { + ArrayList list = new ArrayList<>(Arrays.asList( + RealmFieldType.STRING, + RealmFieldType.BINARY, + RealmFieldType.LIST)); + // TODO: LINKING_OBJECTS should be supported + SUPPORTED_IS_EMPTY_TYPES = Collections.unmodifiableList(list); + SUPPORTED_IS_NOT_EMPTY_TYPES = Collections.unmodifiableList(list); + + list = new ArrayList<>(Arrays.asList(RealmFieldType.values())); + list.removeAll(SUPPORTED_IS_EMPTY_TYPES); + list.remove(RealmFieldType.UNSUPPORTED_MIXED); + list.remove(RealmFieldType.UNSUPPORTED_TABLE); + list.remove(RealmFieldType.UNSUPPORTED_DATE); + list.remove(RealmFieldType.LINKING_OBJECTS); + NOT_SUPPORTED_IS_EMPTY_TYPES = Collections.unmodifiableList(list); + NOT_SUPPORTED_IS_NOT_EMPTY_TYPES = Collections.unmodifiableList(list); + } + + protected Realm realm; + + @Before + public void setUp() throws Exception { + RealmConfiguration realmConfig = configFactory.createConfiguration(); + realm = Realm.getInstance(realmConfig); + } + + @After + public void tearDown() throws Exception { + if (realm != null) { + realm.close(); + } + } + + protected final void createIsEmptyDataSet(Realm realm) { + realm.beginTransaction(); + + AllJavaTypes emptyValues = new AllJavaTypes(); + emptyValues.setFieldId(1); + emptyValues.setFieldString(""); + emptyValues.setFieldBinary(new byte[0]); + emptyValues.setFieldObject(emptyValues); + emptyValues.setFieldList(new RealmList()); + realm.copyToRealm(emptyValues); + + AllJavaTypes nonEmpty = new AllJavaTypes(); + nonEmpty.setFieldId(2); + nonEmpty.setFieldString("Foo"); + nonEmpty.setFieldBinary(new byte[] {1, 2, 3}); + nonEmpty.setFieldObject(nonEmpty); + nonEmpty.setFieldList(new RealmList(emptyValues)); + realm.copyToRealmOrUpdate(nonEmpty); + + realm.commitTransaction(); + } + + protected final void createIsNotEmptyDataSet(Realm realm) { + realm.beginTransaction(); + + AllJavaTypes emptyValues = new AllJavaTypes(); + emptyValues.setFieldId(1); + emptyValues.setFieldString(""); + emptyValues.setFieldBinary(new byte[0]); + emptyValues.setFieldObject(emptyValues); + emptyValues.setFieldList(new RealmList()); + realm.copyToRealm(emptyValues); + + AllJavaTypes notEmpty = new AllJavaTypes(); + notEmpty.setFieldId(2); + notEmpty.setFieldString("Foo"); + notEmpty.setFieldBinary(new byte[] {1, 2, 3}); + notEmpty.setFieldObject(notEmpty); + notEmpty.setFieldList(new RealmList(emptyValues)); + realm.copyToRealmOrUpdate(notEmpty); + + realm.commitTransaction(); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index d89911896d..d8fbb9c7bd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -740,14 +740,14 @@ public void getFieldIndex() { dynamicRealm.beginTransaction(); StandardRealmObjectSchema objectSchema = (StandardRealmObjectSchema) dynamicRealm.getSchema().create(className); - assertNull(objectSchema.getFieldIndex(fieldName)); + assertTrue(objectSchema.getFieldIndex(fieldName) < 0); objectSchema.addField(fieldName, long.class); //noinspection ConstantConditions assertTrue(objectSchema.getFieldIndex(fieldName) >= 0); objectSchema.removeField(fieldName); - assertNull(objectSchema.getFieldIndex(fieldName)); + assertTrue(objectSchema.getFieldIndex(fieldName) < 0); dynamicRealm.cancelTransaction(); dynamicRealm.close(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java index 023cca9f73..b3acf5f0e8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java @@ -60,8 +60,7 @@ public void tearDown() { @Test public void validateTable_noDuplicateIndexInIndexFields() { RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); - CatRealmProxy.CatColumnInfo columnInfo; - columnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); + CatRealmProxy.CatColumnInfo columnInfo = (CatRealmProxy.CatColumnInfo) mediator.validateTable(Cat.class, realm.sharedRealm, false); final Set indexSet = new HashSet(); int indexCount = 0; @@ -100,7 +99,7 @@ public void validateTable_noDuplicateIndexInIndicesMap() { if (Modifier.isStatic(field.getModifiers())) { continue; } - indexSet.add(columnInfo.getIndicesMap().get(field.getName())); + indexSet.add(columnInfo.getColumnIndex(field.getName())); indexCount++; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 8be7cc00fa..0a8d50763c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -20,6 +20,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -62,33 +63,7 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) -public class RealmQueryTests { - @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - @Rule - public final ExpectedException thrown = ExpectedException.none(); - @Rule - public final RunInLooperThread looperThread = new RunInLooperThread(); - - private final static int TEST_DATA_SIZE = 10; - private final static int TEST_NO_PRIMARY_KEY_NULL_TYPES_SIZE = 200; - - private final static long DECADE_MILLIS = 10 * TimeUnit.DAYS.toMillis(365); - - private Realm realm; - - @Before - public void setUp() throws Exception { - RealmConfiguration realmConfig = configFactory.createConfiguration(); - realm = Realm.getInstance(realmConfig); - } - - @After - public void tearDown() throws Exception { - if (realm != null) { - realm.close(); - } - } +public class RealmQueryTests extends QueryTests { private void populateTestRealm(Realm testRealm, int dataSize) { testRealm.beginTransaction(); @@ -1228,7 +1203,7 @@ public void like_caseSensitiveWithNonLatinCharacters() { realm.commitTransaction(); RealmResults resultList = realm.where(AllTypes.class).like("columnString", "*Α*").findAll(); - assertEquals(1, resultList.size()); + assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).like("columnString", "*λ*").findAll(); assertEquals(2, resultList.size()); @@ -2587,7 +2562,8 @@ public void isNotNull_listFieldThrows() { } } - // @Test Disabled because of time consuming. + @Ignore("Disabled because it is time consuming") + @Test public void largeRealmMultipleThreads() throws InterruptedException { final int nObjects = 500000; final int nThreads = 3; @@ -2706,44 +2682,6 @@ public void isValid_removedParent() { assertFalse(query.isValid()); } - - private static final List SUPPORTED_IS_EMPTY_TYPES = Arrays.asList( - RealmFieldType.STRING, - RealmFieldType.BINARY, - RealmFieldType.LIST); - - private static final List NOT_SUPPORTED_IS_EMPTY_TYPES; - static { - final ArrayList list = new ArrayList(Arrays.asList(RealmFieldType.values())); - list.removeAll(SUPPORTED_IS_EMPTY_TYPES); - list.remove(RealmFieldType.UNSUPPORTED_MIXED); - list.remove(RealmFieldType.UNSUPPORTED_TABLE); - list.remove(RealmFieldType.UNSUPPORTED_DATE); - NOT_SUPPORTED_IS_EMPTY_TYPES = list; - } - - private void createIsEmptyDataSet(Realm realm) { - realm.beginTransaction(); - - AllJavaTypes emptyValues = new AllJavaTypes(); - emptyValues.setFieldId(1); - emptyValues.setFieldString(""); - emptyValues.setFieldBinary(new byte[0]); - emptyValues.setFieldObject(emptyValues); - emptyValues.setFieldList(new RealmList()); - realm.copyToRealm(emptyValues); - - AllJavaTypes nonEmpty = new AllJavaTypes(); - nonEmpty.setFieldId(2); - nonEmpty.setFieldString("Foo"); - nonEmpty.setFieldBinary(new byte[]{1, 2, 3}); - nonEmpty.setFieldObject(nonEmpty); - nonEmpty.setFieldList(new RealmList(emptyValues)); - realm.copyToRealmOrUpdate(nonEmpty); - - realm.commitTransaction(); - } - @Test public void isEmpty() { createIsEmptyDataSet(realm); @@ -2829,44 +2767,6 @@ public void isEmpty_invalidFieldNameThrows() { } } - // Not-empty test harnesses. - private static final List SUPPORTED_IS_NOT_EMPTY_TYPES = Arrays.asList( - RealmFieldType.STRING, - RealmFieldType.BINARY, - RealmFieldType.LIST); - - private static final List NOT_SUPPORTED_IS_NOT_EMPTY_TYPES; - static { - final ArrayList list = new ArrayList(Arrays.asList(RealmFieldType.values())); - list.removeAll(SUPPORTED_IS_NOT_EMPTY_TYPES); - list.remove(RealmFieldType.UNSUPPORTED_MIXED); - list.remove(RealmFieldType.UNSUPPORTED_TABLE); - list.remove(RealmFieldType.UNSUPPORTED_DATE); - NOT_SUPPORTED_IS_NOT_EMPTY_TYPES = list; - } - - private void createIsNotEmptyDataSet(Realm realm) { - realm.beginTransaction(); - - AllJavaTypes emptyValues = new AllJavaTypes(); - emptyValues.setFieldId(1); - emptyValues.setFieldString(""); - emptyValues.setFieldBinary(new byte[0]); - emptyValues.setFieldObject(emptyValues); - emptyValues.setFieldList(new RealmList()); - realm.copyToRealm(emptyValues); - - AllJavaTypes notEmpty = new AllJavaTypes(); - notEmpty.setFieldId(2); - notEmpty.setFieldString("Foo"); - notEmpty.setFieldBinary(new byte[]{1, 2, 3}); - notEmpty.setFieldObject(notEmpty); - notEmpty.setFieldList(new RealmList(emptyValues)); - realm.copyToRealmOrUpdate(notEmpty); - - realm.commitTransaction(); - } - @Test public void isNotEmpty() { createIsNotEmptyDataSet(realm); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index da49a66229..57fd54362b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -20,6 +20,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -190,8 +191,7 @@ public void remove_invalidArgumentThrows() { // Test that it if { A -> B && B -> A } you should remove the individual fields first before removing the entire // class. This also include transitive dependencies. - // FIXME: Disabled until https://github.com/realm/realm-core/pull/1475#issuecomment-185192434 is fixed. - // @Test + @Test public void remove_classWithReferencesThrows() { try { realmSchema.remove("Cat"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 5497ccf45b..9356203617 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -126,6 +126,7 @@ import static org.mockito.Mockito.when; + @RunWith(AndroidJUnit4.class) public class RealmTests { private final static int TEST_DATA_SIZE = 10; @@ -177,18 +178,18 @@ private void populateTestRealm(Realm realm, int objects) { for (int i = 0; i < objects; ++i) { AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnBoolean((i % 3) == 0); - allTypes.setColumnBinary(new byte[]{1, 2, 3}); + allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnDate(new Date()); allTypes.setColumnDouble(Math.PI); - allTypes.setColumnFloat(1.234567f + i); + allTypes.setColumnFloat(1.234567F + i); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); - nonLatinFieldNames.set베타(1.234567f + i); - nonLatinFieldNames.setΒήτα(1.234567f + i); + nonLatinFieldNames.set베타(1.234567F + i); + nonLatinFieldNames.setΒήτα(1.234567F + i); } realm.commitTransaction(); } @@ -326,7 +327,7 @@ public void where_equalTo_wrongFieldTypeAsInput() throws IOException { } try { - realm.where(AllTypes.class).equalTo(columnData.get(i), 13.37d).findAll(); + realm.where(AllTypes.class).equalTo(columnData.get(i), 13.37D).findAll(); if (i != 2) { fail("Realm.where should fail with illegal argument"); } @@ -334,7 +335,7 @@ public void where_equalTo_wrongFieldTypeAsInput() throws IOException { } try { - realm.where(AllTypes.class).equalTo(columnData.get(i), 13.3711f).findAll(); + realm.where(AllTypes.class).equalTo(columnData.get(i), 13.3711F).findAll(); if (i != 3) { fail("Realm.where should fail with illegal argument"); } @@ -833,21 +834,20 @@ private List getCharacterArray() { return chars_array; } - // This test is slow. Move it to another testsuite that runs once a day on Jenkins. // The test writes and reads random Strings. - // @Test TODO AndroidJUnit4 runner doesn't seem to respect the @Ignore annotation? - @Ignore + @Test + @Ignore("This test is slow. Move it to another testsuite that runs once a day on Jenkins") public void unicodeStrings() { List chars_array = getCharacterArray(); // Change seed value for new random values. long seed = 20; Random random = new Random(seed); - int random_value = 0; String test_char = ""; String test_char_old = ""; + int random_value; for (int i = 0; i < 1000; i++) { random_value = random.nextInt(25); @@ -887,7 +887,7 @@ public void getInstance_referenceCounting() { try { realm = Realm.getInstance(configFactory.createConfiguration()); } finally { - if (realm != null) realm.close(); + if (realm != null) { realm.close(); } } try { @@ -1115,12 +1115,12 @@ public void copyToRealm() { AllTypes allTypes = new AllTypes(); allTypes.setColumnString("String"); - allTypes.setColumnLong(1l); - allTypes.setColumnFloat(1f); - allTypes.setColumnDouble(1d); + allTypes.setColumnLong(1L); + allTypes.setColumnFloat(1F); + allTypes.setColumnDouble(1D); allTypes.setColumnBoolean(true); allTypes.setColumnDate(date); - allTypes.setColumnBinary(new byte[]{1, 2, 3}); + allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnRealmObject(dog); allTypes.setColumnRealmList(list); @@ -1263,16 +1263,16 @@ public void copyToRealm_boxedNumberPrimaryKeyIsNull() { final String SECONDARY_FIELD_VALUE = "nullNumberPrimaryKeyObj"; final Class[] CLASSES = {PrimaryKeyAsBoxedByte.class, PrimaryKeyAsBoxedShort.class, PrimaryKeyAsBoxedInteger.class, PrimaryKeyAsBoxedLong.class}; - TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, SECONDARY_FIELD_VALUE); - TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, SECONDARY_FIELD_VALUE); + TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, SECONDARY_FIELD_VALUE); + TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, SECONDARY_FIELD_VALUE); TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, SECONDARY_FIELD_VALUE); - TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, SECONDARY_FIELD_VALUE); + TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, SECONDARY_FIELD_VALUE); for (Class clazz : CLASSES) { RealmResults results = realm.where(clazz).findAll(); assertEquals(1, results.size()); - assertEquals(null, ((NullPrimaryKey)results.first()).getId()); - assertEquals(SECONDARY_FIELD_VALUE, ((NullPrimaryKey)results.first()).getName()); + assertEquals(null, ((NullPrimaryKey) results.first()).getId()); + assertEquals(SECONDARY_FIELD_VALUE, ((NullPrimaryKey) results.first()).getName()); } } @@ -1280,11 +1280,11 @@ public void copyToRealm_boxedNumberPrimaryKeyIsNull() { public void copyToRealm_duplicatedNullPrimaryKeyThrows() { final String[] PRIMARY_KEY_TYPES = {"String", "BoxedByte", "BoxedShort", "BoxedInteger", "BoxedLong"}; - TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, 0); - TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, (String) null); - TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, (String) null); + TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, 0); + TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, (String) null); + TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, (String) null); TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, (String) null); - TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, (String) null); + TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, (String) null); for (String className : PRIMARY_KEY_TYPES) { try { @@ -1527,7 +1527,7 @@ public void execute(Realm realm) { obj.setColumnFloat(1.23F); obj.setColumnDouble(1.234D); obj.setColumnBoolean(false); - obj.setColumnBinary(new byte[]{1, 2, 3}); + obj.setColumnBinary(new byte[] {1, 2, 3}); obj.setColumnDate(new Date(1000)); obj.setColumnRealmObject(new DogPrimaryKey(1, "Dog1")); obj.setColumnRealmList(new RealmList(new DogPrimaryKey(2, "Dog2"))); @@ -1540,7 +1540,7 @@ public void execute(Realm realm) { obj2.setColumnFloat(2.23F); obj2.setColumnDouble(2.234D); obj2.setColumnBoolean(true); - obj2.setColumnBinary(new byte[]{2, 3, 4}); + obj2.setColumnBinary(new byte[] {2, 3, 4}); obj2.setColumnDate(new Date(2000)); obj2.setColumnRealmObject(new DogPrimaryKey(3, "Dog3")); obj2.setColumnRealmList(new RealmList(new DogPrimaryKey(4, "Dog4"))); @@ -1558,7 +1558,7 @@ public void execute(Realm realm) { assertEquals(2.23F, obj.getColumnFloat(), 0); assertEquals(2.234D, obj.getColumnDouble(), 0); assertEquals(true, obj.isColumnBoolean()); - assertArrayEquals(new byte[]{2, 3, 4}, obj.getColumnBinary()); + assertArrayEquals(new byte[] {2, 3, 4}, obj.getColumnBinary()); assertEquals(new Date(2000), obj.getColumnDate()); assertEquals("Dog3", obj.getColumnRealmObject().getName()); assertEquals(1, obj.getColumnRealmList().size()); @@ -1602,7 +1602,7 @@ public void execute(Realm realm) { obj.setColumnFloat(1.23F); obj.setColumnDouble(1.234D); obj.setColumnBoolean(false); - obj.setColumnBinary(new byte[]{1, 2, 3}); + obj.setColumnBinary(new byte[] {1, 2, 3}); obj.setColumnDate(new Date(1000)); obj.setColumnRealmObject(new DogPrimaryKey(1, "Dog1")); obj.setColumnRealmList(new RealmList(new DogPrimaryKey(2, "Dog2"))); @@ -1897,7 +1897,7 @@ public void writeEncryptedCopyTo() throws Exception { @Test public void writeEncryptedCopyTo_wrongKeyLength() { - byte[] wrongLengthKey = new byte[42]; + byte[] wrongLengthKey = new byte[42]; File destination = new File(configFactory.getRoot(), "wrong_key.realm"); thrown.expect(IllegalArgumentException.class); realm.writeEncryptedCopyTo(destination, wrongLengthKey); @@ -2033,34 +2033,91 @@ public void callMutableMethodOutsideTransaction() throws JSONException, IOExcept InputStream jsonArrStream2 = TestHelper.stringToStream(jsonArrStr); // Tests all methods that should require a transaction. - try { realm.createObject(AllTypes.class); fail(); } catch (IllegalStateException expected) {} - try { realm.copyToRealm(t); fail(); } catch (IllegalStateException expected) {} - try { realm.copyToRealm(ts); fail(); } catch (IllegalStateException expected) {} - try { realm.copyToRealmOrUpdate(t); fail(); } catch (IllegalStateException expected) {} - try { realm.copyToRealmOrUpdate(ts); fail(); } catch (IllegalStateException expected) {} - try { realm.delete(AllTypes.class); fail(); } catch (IllegalStateException expected) {} - try { realm.deleteAll(); fail(); } catch (IllegalStateException expected) {} - - try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} - try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} + try { + realm.createObject(AllTypes.class); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.copyToRealm(t); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.copyToRealm(ts); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.copyToRealmOrUpdate(t); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.copyToRealmOrUpdate(ts); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.delete(AllTypes.class); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.deleteAll(); + fail(); + } catch (IllegalStateException expected) {} + + try { + realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObj); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); + fail(); + } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - try { realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, jsonObjStream); fail(); } catch (IllegalStateException expected) {} + try { + realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, jsonObjStream); + fail(); + } catch (IllegalStateException expected) {} } - try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} - try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} + try { + realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObj); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); + fail(); + } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStream2); fail(); } catch (IllegalStateException expected) {} + try { + realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStream2); + fail(); + } catch (IllegalStateException expected) {} } - try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} - try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} + try { + realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArr); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); + fail(); + } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - try { realm.createAllFromJson(NoPrimaryKeyNullTypes.class, jsonArrStream); fail(); } catch (IllegalStateException expected) {} + try { + realm.createAllFromJson(NoPrimaryKeyNullTypes.class, jsonArrStream); + fail(); + } catch (IllegalStateException expected) {} } - try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} - try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} + try { + realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArr); + fail(); + } catch (IllegalStateException expected) {} + try { + realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); + fail(); + } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStream2);fail(); } catch (IllegalStateException expected) {} + try { + realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStream2); + fail(); + } catch (IllegalStateException expected) {} } } @@ -2263,7 +2320,7 @@ public void execute(Realm realm) { DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_RANDOM_STRING, createdRandomString); - testOneObjectFound(realm, DefaultValueOfField.class,DefaultValueOfField.FIELD_SHORT, + testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_SHORT, DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_INT, @@ -2337,14 +2394,14 @@ public void execute(Realm realm) { DefaultValueConstructor.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_INT, - DefaultValueConstructor.FIELD_INT_DEFAULT_VALUE);; + DefaultValueConstructor.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, - DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); + DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, - DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); + DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG, DefaultValueConstructor.FIELD_LONG_DEFAULT_VALUE); @@ -2367,10 +2424,10 @@ public void execute(Realm realm) { DefaultValueConstructor.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_OBJECT + "." + RandomPrimaryKey.FIELD_INT, - RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, - RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); } @Test @@ -2400,10 +2457,10 @@ public void execute(Realm realm) { // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, - DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); + DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, - DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); + DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG, DefaultValueSetter.FIELD_LONG_DEFAULT_VALUE); @@ -2427,13 +2484,13 @@ public void execute(Realm realm) { DefaultValueSetter.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_OBJECT + "." + RandomPrimaryKey.FIELD_INT, - RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, - RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, - DefaultValueSetter.FIELD_LIST+ "." + RandomPrimaryKey.FIELD_INT, - RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); + DefaultValueSetter.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, + RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); } @Test @@ -2464,7 +2521,8 @@ public void copyToRealm_defaultValuesAreIgnored() { final int fieldListIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2; final DefaultValueOfField managedObj; - realm.beginTransaction(); { + realm.beginTransaction(); + { final DefaultValueOfField obj = new DefaultValueOfField(); obj.setFieldIgnored(fieldIgnoredValue); obj.setFieldString(fieldStringValue); @@ -2503,8 +2561,8 @@ public void copyToRealm_defaultValuesAreIgnored() { assertEquals(fieldLongPrimaryKeyValue, managedObj.getFieldLongPrimaryKey()); assertEquals(fieldLongValue, managedObj.getFieldLong()); assertEquals(fieldByteValue, managedObj.getFieldByte()); - assertEquals(fieldFloatValue, managedObj.getFieldFloat(), 0f); - assertEquals(fieldDoubleValue, managedObj.getFieldDouble(), 0d); + assertEquals(fieldFloatValue, managedObj.getFieldFloat(), 0F); + assertEquals(fieldDoubleValue, managedObj.getFieldDouble(), 0D); assertEquals(fieldBooleanValue, managedObj.isFieldBoolean()); assertEquals(fieldDateValue, managedObj.getFieldDate()); assertTrue(Arrays.equals(fieldBinaryValue, managedObj.getFieldBinary())); @@ -2519,7 +2577,8 @@ public void copyToRealm_defaultValuesAreIgnored() { @Test public void copyFromRealm_defaultValuesAreIgnored() { final DefaultValueOfField managedObj; - realm.beginTransaction(); { + realm.beginTransaction(); + { final DefaultValueOfField obj = new DefaultValueOfField(); obj.setFieldIgnored(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"); obj.setFieldString(DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified"); @@ -2559,8 +2618,8 @@ public void copyFromRealm_defaultValuesAreIgnored() { assertEquals(managedObj.getFieldLongPrimaryKey(), copy.getFieldLongPrimaryKey()); assertEquals(managedObj.getFieldLong(), copy.getFieldLong()); assertEquals(managedObj.getFieldByte(), copy.getFieldByte()); - assertEquals(managedObj.getFieldFloat(), copy.getFieldFloat(), 0f); - assertEquals(managedObj.getFieldDouble(), copy.getFieldDouble(), 0d); + assertEquals(managedObj.getFieldFloat(), copy.getFieldFloat(), 0F); + assertEquals(managedObj.getFieldDouble(), copy.getFieldDouble(), 0D); assertEquals(managedObj.isFieldBoolean(), copy.isFieldBoolean()); assertEquals(managedObj.getFieldDate(), copy.getFieldDate()); assertTrue(Arrays.equals(managedObj.getFieldBinary(), copy.getFieldBinary())); @@ -3606,7 +3665,7 @@ public void run() { } // Cannot wait inside of a transaction. - @Test(expected= IllegalStateException.class) + @Test(expected = IllegalStateException.class) public void waitForChange_illegalWaitInsideTransaction() { realm.beginTransaction(); realm.waitForChange(); @@ -3717,7 +3776,7 @@ public void execute(Realm realm) { // Verify that the index in the ColumnInfo has been updated. catColumnInfo = (CatRealmProxy.CatColumnInfo) realm.schema.getColumnInfo(Cat.class); assertEquals(nameIndexNew.get(), catColumnInfo.nameIndex); - assertEquals(nameIndexNew.get(), (long) catColumnInfo.getIndicesMap().get(Cat.FIELD_NAME)); + assertEquals(nameIndexNew.get(), (long) catColumnInfo.getColumnIndex(Cat.FIELD_NAME)); // Checks by actual get and set. realm.executeTransaction(new Realm.Transaction() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index a688249e8c..97d1a7e677 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -21,7 +21,6 @@ import android.os.Build; import android.os.Looper; import android.support.test.InstrumentationRegistry; -import android.util.Log; import org.junit.Assert; @@ -177,7 +176,7 @@ public static byte[] getRandomKey(long seed) { /** * Returns a RealmLogger that will fail if it is asked to log a message above a certain level. * - * @param failureLevel {@link Log} level from which the unit test will fail. + * @param failureLevel level at which the unit test will fail: {@see Log}. * @return RealmLogger implementation */ public static RealmLogger getFailureLogger(final int failureLevel) { @@ -506,7 +505,7 @@ public static void populateTestRealmForNullTests(Realm testRealm) { NullTypes[] nullTypesArray = new NullTypes[3]; testRealm.beginTransaction(); - for (int i = 0; i < words.length; i++) { + for (int i = 0; i < 3; i++) { NullTypes nullTypes = new NullTypes(); nullTypes.setId(i + 1); // 1 String diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java index 38e3f84443..22e509c956 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java @@ -26,6 +26,7 @@ import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; + public class AllJavaTypes extends RealmObject { public static final String CLASS_NAME = "AllJavaTypes"; @@ -44,13 +45,26 @@ public class AllJavaTypes extends RealmObject { public static final String FIELD_BINARY = "fieldBinary"; public static final String FIELD_OBJECT = "fieldObject"; public static final String FIELD_LIST = "fieldList"; - - public static final String INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT = AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY; - public static final String[] INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT = new String[]{FIELD_OBJECT + "." + FIELD_BINARY, FIELD_OBJECT + "." + FIELD_OBJECT, FIELD_OBJECT + "." + FIELD_LIST}; - - @Ignore private String fieldIgnored; - @Index private String fieldString; - @PrimaryKey private long fieldId; + public static final String FIELD_LO_OBJECT = "objectParents"; + public static final String FIELD_LO_LIST = "listParents"; + + public static final String[] INVALID_FIELDS_FOR_DISTINCT + = new String[] {FIELD_OBJECT, FIELD_LIST, FIELD_DOUBLE, FIELD_FLOAT, FIELD_LO_OBJECT, FIELD_LO_LIST}; + + public static final String INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT + = AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY; + + public static final String[] INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT = new String[] { + FIELD_OBJECT + "." + FIELD_BINARY, + FIELD_OBJECT + "." + FIELD_OBJECT, + FIELD_OBJECT + "." + FIELD_LIST}; + + @Ignore + private String fieldIgnored; + @Index + private String fieldString; + @PrimaryKey + private long fieldId; private long fieldLong; private short fieldShort; private int fieldInt; @@ -63,14 +77,13 @@ public class AllJavaTypes extends RealmObject { private AllJavaTypes fieldObject; private RealmList fieldList; - @LinkingObjects("fieldObject") + @LinkingObjects(FIELD_OBJECT) private final RealmResults objectParents = null; - @LinkingObjects("fieldList") + @LinkingObjects(FIELD_LIST) private final RealmResults listParents = null; public AllJavaTypes() { - } public AllJavaTypes(long fieldLong) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java index 5696701480..db8d4a2dbb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java @@ -20,6 +20,8 @@ import io.realm.RealmList; import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; @@ -37,30 +39,32 @@ // 11 Object public class NullTypes extends RealmObject { - public static String CLASS_NAME = "NullTypes"; - public static String FIELD_ID = "id"; - public static String FIELD_STRING_NOT_NULL = "fieldStringNotNull"; - public static String FIELD_STRING_NULL = "fieldStringNull"; - public static String FIELD_BYTES_NOT_NULL = "fieldBytesNotNull"; - public static String FIELD_BYTES_NULL = "fieldBytesNull"; - public static String FIELD_BOOLEAN_NOT_NULL = "fieldBooleanNotNull"; - public static String FIELD_BOOLEAN_NULL = "fieldBooleanNull"; - public static String FIELD_BYTE_NOT_NULL = "fieldByteNotNull"; - public static String FIELD_BYTE_NULL = "fieldByteNull"; - public static String FIELD_SHORT_NOT_NULL = "fieldShortNotNull"; - public static String FIELD_SHORT_NULL = "fieldShortNull"; - public static String FIELD_INTEGER_NOT_NULL = "fieldIntegerNotNull"; - public static String FIELD_INTEGER_NULL = "fieldIntegerNull"; - public static String FIELD_LONG_NOT_NULL = "fieldLongNotNull"; - public static String FIELD_LONG_NULL = "fieldLongNull"; - public static String FIELD_FLOAT_NOT_NULL = "fieldFloatNotNull"; - public static String FIELD_FLOAT_NULL = "fieldFloatNull"; - public static String FIELD_DOUBLE_NOT_NULL = "fieldDoubleNotNull"; - public static String FIELD_DOUBLE_NULL = "fieldDoubleNull"; - public static String FIELD_DATE_NOT_NULL = "fieldDateNotNull"; - public static String FIELD_DATE_NULL = "fieldDateNull"; - public static String FIELD_OBJECT_NULL = "fieldObjectNull"; - public static String FIELD_LIST_NULL = "fieldListNull"; + public static final String CLASS_NAME = "NullTypes"; + public static final String FIELD_ID = "id"; + public static final String FIELD_STRING_NOT_NULL = "fieldStringNotNull"; + public static final String FIELD_STRING_NULL = "fieldStringNull"; + public static final String FIELD_BYTES_NOT_NULL = "fieldBytesNotNull"; + public static final String FIELD_BYTES_NULL = "fieldBytesNull"; + public static final String FIELD_BOOLEAN_NOT_NULL = "fieldBooleanNotNull"; + public static final String FIELD_BOOLEAN_NULL = "fieldBooleanNull"; + public static final String FIELD_BYTE_NOT_NULL = "fieldByteNotNull"; + public static final String FIELD_BYTE_NULL = "fieldByteNull"; + public static final String FIELD_SHORT_NOT_NULL = "fieldShortNotNull"; + public static final String FIELD_SHORT_NULL = "fieldShortNull"; + public static final String FIELD_INTEGER_NOT_NULL = "fieldIntegerNotNull"; + public static final String FIELD_INTEGER_NULL = "fieldIntegerNull"; + public static final String FIELD_LONG_NOT_NULL = "fieldLongNotNull"; + public static final String FIELD_LONG_NULL = "fieldLongNull"; + public static final String FIELD_FLOAT_NOT_NULL = "fieldFloatNotNull"; + public static final String FIELD_FLOAT_NULL = "fieldFloatNull"; + public static final String FIELD_DOUBLE_NOT_NULL = "fieldDoubleNotNull"; + public static final String FIELD_DOUBLE_NULL = "fieldDoubleNull"; + public static final String FIELD_DATE_NOT_NULL = "fieldDateNotNull"; + public static final String FIELD_DATE_NULL = "fieldDateNull"; + public static final String FIELD_OBJECT_NULL = "fieldObjectNull"; + public static final String FIELD_LIST_NULL = "fieldListNull"; + public static final String FIELD_LO_OBJECT = "objectParents"; + public static final String FIELD_LO_LIST = "listParents"; @PrimaryKey private int id; @@ -107,8 +111,17 @@ public class NullTypes extends RealmObject { private NullTypes fieldObjectNull; + // never nullable private RealmList fieldListNull; + // never nullable + @LinkingObjects(FIELD_OBJECT_NULL) + private final RealmResults objectParents = null; + + // never nullable + @LinkingObjects(FIELD_LIST_NULL) + private final RealmResults listParents = null; + public int getId() { return id; } @@ -292,4 +305,12 @@ public RealmList getFieldListNull() { public void setFieldListNull(RealmList fieldListNull) { this.fieldListNull = fieldListNull; } + + public RealmResults getObjectParents() { + return objectParents; + } + + public RealmResults getListParents() { + return listParents; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 83ca6db974..8a9a492201 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -55,6 +55,8 @@ public class CollectionTests { @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); + private final long[] oneNullTable = new long[] {NativeObject.NULLPTR}; + private RealmConfiguration config; private SharedRealm sharedRealm; private Table table; @@ -130,7 +132,7 @@ private void addRow(SharedRealm sharedRealm) { @Test public void constructor_withDistinct() { - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table, "firstName"); + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(null, table, "firstName"); Collection collection = new Collection(sharedRealm, table.where(), null, distinctDescriptor); assertEquals(collection.size(), 3); @@ -166,8 +168,8 @@ public void size() { @Test public void where() { Collection collection = new Collection(sharedRealm, table.where()); - Collection collection2 = new Collection(sharedRealm, collection.where().equalTo(new long[]{0}, "John")); - Collection collection3 =new Collection(sharedRealm, collection2.where().equalTo(new long[]{1}, "Anderson")); + Collection collection2 = new Collection(sharedRealm, collection.where().equalTo(new long[] {0}, oneNullTable, "John")); + Collection collection3 = new Collection(sharedRealm, collection2.where().equalTo(new long[] {1}, oneNullTable, "Anderson")); // A new native Results should be created. assertTrue(collection.getNativePtr() != collection2.getNativePtr()); @@ -180,8 +182,8 @@ public void where() { @Test public void sort() { - Collection collection = new Collection(sharedRealm, table.where().greaterThan(new long[]{2}, 1)); - SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); + Collection collection = new Collection(sharedRealm, table.where().greaterThan(new long[] {2}, oneNullTable, 1)); + SortDescriptor sortDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); Collection collection2 = collection.sort(sortDescriptor); @@ -213,7 +215,7 @@ public void contains() { @Test public void indexOf() { - SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); + SortDescriptor sortDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); UncheckedRow row = table.getUncheckedRow(0); @@ -222,7 +224,7 @@ public void indexOf() { @Test public void indexOf_long() { - SortDescriptor sortDescriptor = new SortDescriptor(table, new long[] {2}); + SortDescriptor sortDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); assertEquals(3, collection.indexOf(0)); @@ -230,9 +232,9 @@ public void indexOf_long() { @Test public void distinct() { - Collection collection = new Collection(sharedRealm, table.where().lessThan(new long[]{2}, 4)); + Collection collection = new Collection(sharedRealm, table.where().lessThan(new long[] {2}, oneNullTable, 4)); - SortDescriptor distinctDescriptor = new SortDescriptor(table, new long[] {2}); + SortDescriptor distinctDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); Collection collection2 = collection.distinct(distinctDescriptor); // A new native Results should be created. diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index 7f9042057d..6d1e8570f0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -30,7 +30,9 @@ public class JNIQueryTest extends TestCase { - Table table; + private Table table; + private final long[] oneNullTable = new long[]{NativeObject.NULLPTR}; + @Override protected void setUp() throws Exception { @@ -56,7 +58,7 @@ public void testShouldQuery() { init(); TableQuery query = table.where(); - long cnt = query.equalTo(new long[]{1}, "D").count(); + long cnt = query.equalTo(new long[]{1}, oneNullTable, "D").count(); assertEquals(2, cnt); cnt = query.minimumInt(0); @@ -79,9 +81,9 @@ public void testNonCompleteQuery() { init(); // All the following queries are not valid, e.g contain a group but not a closing group, an or() but not a second filter etc - try { table.where().equalTo(new long[]{0}, 1).or().validateQuery(); fail("missing a second filter"); } catch (UnsupportedOperationException ignore) {} + try { table.where().equalTo(new long[]{0}, oneNullTable, 1).or().validateQuery(); fail("missing a second filter"); } catch (UnsupportedOperationException ignore) {} try { table.where().or().validateQuery(); fail("just an or()"); } catch (UnsupportedOperationException ignore) {} - try { table.where().group().equalTo(new long[]{0}, 1).validateQuery(); fail("missing a closing group"); } catch (UnsupportedOperationException ignore) {} + try { table.where().group().equalTo(new long[]{0}, oneNullTable, 1).validateQuery(); fail("missing a closing group"); } catch (UnsupportedOperationException ignore) {} try { table.where().group().count(); fail(); } catch (UnsupportedOperationException ignore) {} try { table.where().group().validateQuery(); fail(); } catch (UnsupportedOperationException ignore) {} @@ -91,12 +93,12 @@ public void testNonCompleteQuery() { try { table.where().group().sumInt(0); fail(); } catch (UnsupportedOperationException ignore) {} try { table.where().group().averageInt(0); fail(); } catch (UnsupportedOperationException ignore) {} - try { table.where().endGroup().equalTo(new long[]{0}, 1).validateQuery(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - try { table.where().equalTo(new long[]{0}, 1).endGroup().validateQuery(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} + try { table.where().endGroup().equalTo(new long[]{0}, oneNullTable, 1).validateQuery(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} + try { table.where().equalTo(new long[]{0}, oneNullTable, 1).endGroup().validateQuery(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - try { table.where().equalTo(new long[]{0}, 1).endGroup().find(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - try { table.where().equalTo(new long[]{0}, 1).endGroup().find(0); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - try { table.where().equalTo(new long[]{0}, 1).endGroup().find(1); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} + try { table.where().equalTo(new long[]{0}, oneNullTable, 1).endGroup().find(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} + try { table.where().equalTo(new long[]{0}, oneNullTable, 1).endGroup().find(0); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} + try { table.where().equalTo(new long[]{0}, oneNullTable, 1).endGroup().find(1); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} } public void testInvalidColumnIndexEqualTo() { @@ -104,45 +106,45 @@ public void testInvalidColumnIndexEqualTo() { TableQuery query = table.where(); // Boolean - try { query.equalTo(new long[]{-1}, true); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, true); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, true); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, true); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, oneNullTable, true); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, oneNullTable, true); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Date - try { query.equalTo(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.equalTo(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.equalTo(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.equalTo(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String - try { query.equalTo(new long[]{-1}, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, oneNullTable, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, oneNullTable, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case true - try { query.equalTo(new long[]{-1}, "a", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, "a", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, oneNullTable, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, oneNullTable, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case false - try { query.equalTo(new long[]{-1}, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{9}, oneNullTable, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } public void testInvalidColumnIndexNotEqualTo() { @@ -151,40 +153,40 @@ public void testInvalidColumnIndexNotEqualTo() { // Date - try { query.notEqualTo(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.notEqualTo(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.notEqualTo(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.notEqualTo(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String - try { query.notEqualTo(new long[]{-1}, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case true - try { query.notEqualTo(new long[]{-1}, "a", Case.SENSITIVE); fail("-1column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, "a", Case.SENSITIVE); fail("-1column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case false - try { query.notEqualTo(new long[]{-1}, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } @@ -193,25 +195,25 @@ public void testInvalidColumnIndexGreaterThan() { TableQuery query = table.where(); // Date - try { query.greaterThan(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.greaterThan(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.greaterThan(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.greaterThan(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } @@ -220,25 +222,25 @@ public void testInvalidColumnIndexGreaterThanOrEqual() { TableQuery query = table.where(); // Date - try { query.greaterThanOrEqual(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.greaterThanOrEqual(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.greaterThanOrEqual(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.greaterThanOrEqual(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } @@ -247,25 +249,25 @@ public void testInvalidColumnIndexLessThan() { TableQuery query = table.where(); // Date - try { query.lessThan(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.lessThan(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.lessThan(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.lessThan(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } public void testInvalidColumnIndexLessThanOrEqual() { @@ -273,25 +275,25 @@ public void testInvalidColumnIndexLessThanOrEqual() { TableQuery query = table.where(); // Date - try { query.lessThanOrEqual(new long[]{-1}, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Double - try { query.lessThanOrEqual(new long[]{-1}, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Float - try { query.lessThanOrEqual(new long[]{-1}, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // Int / long - try { query.lessThanOrEqual(new long[]{-1}, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } @@ -327,19 +329,19 @@ public void testInvalidColumnIndexContains() { TableQuery query = table.where(); // String - try { query.contains(new long[]{-1}, "hey"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{9}, "hey"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{10}, "hey"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{-1}, oneNullTable, "hey"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{9}, oneNullTable, "hey"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{10}, oneNullTable, "hey"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case true - try { query.contains(new long[]{-1}, "hey", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{9}, "hey", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{10}, "hey", Case.SENSITIVE); fail("-0 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{-1}, oneNullTable, "hey", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{9}, oneNullTable, "hey", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{10}, oneNullTable, "hey", Case.SENSITIVE); fail("-0 column index"); } catch (ArrayIndexOutOfBoundsException e) {} // String case false - try { query.contains(new long[]{-1}, "hey", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{9}, "hey", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{10}, "hey", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{-1}, oneNullTable, "hey", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{9}, oneNullTable, "hey", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{10}, oneNullTable, "hey", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} } public void testNullInputQuery() { @@ -348,29 +350,29 @@ public void testNullInputQuery() { t.addColumn(RealmFieldType.STRING, "stringCol"); Date nullDate = null; - try { t.where().equalTo(new long[]{0}, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().notEqualTo(new long[]{0}, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().greaterThan(new long[]{0}, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().greaterThanOrEqual(new long[]{0}, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().lessThan(new long[]{0}, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().lessThanOrEqual(new long[]{0}, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } + try { t.where().equalTo(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } + try { t.where().notEqualTo(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } + try { t.where().greaterThan(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } + try { t.where().greaterThanOrEqual(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } + try { t.where().lessThan(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } + try { t.where().lessThanOrEqual(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } try { t.where().between(new long[]{0}, nullDate, new Date()); fail("Date is null"); } catch (IllegalArgumentException e) { } try { t.where().between(new long[]{0}, new Date(), nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } try { t.where().between(new long[]{0}, nullDate, nullDate); fail("Dates are null"); } catch (IllegalArgumentException e) { } String nullString = null; - try { t.where().equalTo(new long[]{1}, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().equalTo(new long[]{1}, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().notEqualTo(new long[]{1}, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().notEqualTo(new long[]{1}, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().contains(new long[]{1}, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().contains(new long[]{1}, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().beginsWith(new long[]{1}, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().beginsWith(new long[]{1}, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().endsWith(new long[]{1}, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().endsWith(new long[]{1}, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().like(new long[]{1}, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().like(new long[]{1}, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().equalTo(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().equalTo(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().notEqualTo(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().notEqualTo(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().contains(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().contains(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().beginsWith(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().beginsWith(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().endsWith(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().endsWith(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().like(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().like(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } } @@ -391,7 +393,7 @@ public void testShouldFind() { table.add("Bill", 564, true); // 4 table.add("Janet", 875, false); // 5 * - TableQuery query = table.where().greaterThan(new long[]{1}, 600); + TableQuery query = table.where().greaterThan(new long[]{1}, oneNullTable, 600); // Finds first match. assertEquals(1, query.find()); @@ -425,7 +427,7 @@ public void testQueryTestForNoMatches() { t.add(new byte[]{1,2,3}, true, new Date(1384423149761l), 4.5d, 5.7f, 100, "string"); - TableQuery q = t.where().greaterThan(new long[]{5}, 1000); // No matches + TableQuery q = t.where().greaterThan(new long[]{5}, oneNullTable, 1000); // No matches assertEquals(-1, q.find()); assertEquals(-1, q.find(1)); @@ -442,57 +444,57 @@ public void testQueryWithWrongDataType() { // Compares strings in non string columns. for (int i = 0; i <= 6; i++) { - try { query.equalTo(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.notEqualTo(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.beginsWith(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.endsWith(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.like(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.contains(new long[]{i}, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.equalTo(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.notEqualTo(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.beginsWith(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.endsWith(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.like(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} + try { query.contains(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} } // Compares integer in non integer columns. for (int i = 0; i <= 6; i++) { if (i != 5) { - try { query.equalTo(new long[]{i}, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.notEqualTo(new long[]{i}, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThan(new long[]{i}, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThanOrEqual(new long[]{i}, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThan(new long[]{i}, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThanOrEqual(new long[]{i}, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.between(new long[]{i}, 123, 321); assert(false); } catch(IllegalArgumentException e) {} + try { query.equalTo(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} + try { query.notEqualTo(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} + try { query.lessThan(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} + try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} + try { query.greaterThan(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} + try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} + try { query.between(new long[]{i}, 123, 321); assert(false); } catch(IllegalArgumentException e) {} } } // Compares float in non float columns. for (int i = 0; i <= 6; i++) { if (i != 4) { - try { query.equalTo(new long[]{i}, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.notEqualTo(new long[]{i}, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThan(new long[]{i}, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThanOrEqual(new long[]{i}, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThan(new long[]{i}, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThanOrEqual(new long[]{i}, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.between(new long[]{i}, 123F, 321F); assert(false); } catch(IllegalArgumentException e) {} + try { query.equalTo(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} + try { query.notEqualTo(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} + try { query.lessThan(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} + try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} + try { query.greaterThan(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} + try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} + try { query.between(new long[]{i}, 123F, 321F); assert(false); } catch(IllegalArgumentException e) {} } } // Compares double in non double columns. for (int i = 0; i <= 6; i++) { if (i != 3) { - try { query.equalTo(new long[]{i}, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.notEqualTo(new long[]{i}, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThan(new long[]{i}, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThanOrEqual(new long[]{i}, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThan(new long[]{i}, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThanOrEqual(new long[]{i}, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.between(new long[]{i}, 123D, 321D); assert(false); } catch(IllegalArgumentException e) {} + try { query.equalTo(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} + try { query.notEqualTo(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} + try { query.lessThan(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} + try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} + try { query.greaterThan(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} + try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} + try { query.between(new long[]{i}, 123D, 321D); assert(false); } catch(IllegalArgumentException e) {} } } // Compares boolean in non boolean columns. for (int i = 0; i <= 6; i++) { if (i != 1) { - try { query.equalTo(new long[]{i}, true); assert(false); } catch(IllegalArgumentException e) {} + try { query.equalTo(new long[]{i}, oneNullTable, true); assert(false); } catch(IllegalArgumentException e) {} } } @@ -570,46 +572,46 @@ public void testColumnIndexOutOfBounds() { try { query.averageFloat(6); assert(false); } catch(IllegalArgumentException e) {} try { query.averageDouble(6); assert(false); } catch(IllegalArgumentException e) {} // Out of bounds for string - try { query.equalTo(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.beginsWith(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.endsWith(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.like(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{7}, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.beginsWith(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.endsWith(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.like(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} // Out of bounds for integer - try { query.equalTo(new long[]{7}, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{7}, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{7}, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{7}, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{7}, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{7}, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{7}, 123, 321); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{7}, 123, 321); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} // Out of bounds for float - try { query.equalTo(new long[]{7}, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{7}, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{7}, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{7}, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{7}, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{7}, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{7}, 123F, 321F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{7}, 123F, 321F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} // Out of bounds for double - try { query.equalTo(new long[]{7}, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{7}, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{7}, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{7}, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{7}, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{7}, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{7}, 123D, 321D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{7}, 123D, 321D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} // Out of bounds for boolean - try { query.equalTo(new long[]{7}, true); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, true); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} } public void testMaximumDate() { @@ -655,40 +657,40 @@ public void testDateQuery() throws Exception { table.add(past); table.add(distantPast); - assertEquals(1L, table.where().equalTo(new long[]{0}, distantPast).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, distantPast).count()); - assertEquals(0L, table.where().lessThan(new long[]{0}, distantPast).count()); - assertEquals(1L, table.where().lessThanOrEqual(new long[]{0}, distantPast).count()); - assertEquals(6L, table.where().greaterThan(new long[]{0}, distantPast).count()); - assertEquals(7L, table.where().greaterThanOrEqual(new long[]{0}, distantPast).count()); - - assertEquals(1L, table.where().equalTo(new long[]{0}, past).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, past).count()); - assertEquals(1L, table.where().lessThan(new long[]{0}, past).count()); - assertEquals(2L, table.where().lessThanOrEqual(new long[]{0}, past).count()); - assertEquals(5L, table.where().greaterThan(new long[]{0}, past).count()); - assertEquals(6L, table.where().greaterThanOrEqual(new long[]{0}, past).count()); - - assertEquals(1L, table.where().equalTo(new long[]{0}, new Date(0)).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, new Date(0)).count()); - assertEquals(2L, table.where().lessThan(new long[]{0}, new Date(0)).count()); - assertEquals(3L, table.where().lessThanOrEqual(new long[]{0}, new Date(0)).count()); - assertEquals(4L, table.where().greaterThan(new long[]{0}, new Date(0)).count()); - assertEquals(5L, table.where().greaterThanOrEqual(new long[]{0}, new Date(0)).count()); - - assertEquals(1L, table.where().equalTo(new long[]{0}, future).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, future).count()); - assertEquals(5L, table.where().lessThan(new long[]{0}, future).count()); - assertEquals(6L, table.where().lessThanOrEqual(new long[]{0}, future).count()); - assertEquals(1L, table.where().greaterThan(new long[]{0}, future).count()); - assertEquals(2L, table.where().greaterThanOrEqual(new long[]{0}, future).count()); - - assertEquals(1L, table.where().equalTo(new long[]{0}, distantFuture).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, distantFuture).count()); - assertEquals(6L, table.where().lessThan(new long[]{0}, distantFuture).count()); - assertEquals(7L, table.where().lessThanOrEqual(new long[]{0}, distantFuture).count()); - assertEquals(0L, table.where().greaterThan(new long[]{0}, distantFuture).count()); - assertEquals(1L, table.where().greaterThanOrEqual(new long[]{0}, distantFuture).count()); + assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, distantPast).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, distantPast).count()); + assertEquals(0L, table.where().lessThan(new long[]{0}, oneNullTable, distantPast).count()); + assertEquals(1L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, distantPast).count()); + assertEquals(6L, table.where().greaterThan(new long[]{0}, oneNullTable, distantPast).count()); + assertEquals(7L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, distantPast).count()); + + assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, past).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, past).count()); + assertEquals(1L, table.where().lessThan(new long[]{0}, oneNullTable, past).count()); + assertEquals(2L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, past).count()); + assertEquals(5L, table.where().greaterThan(new long[]{0}, oneNullTable, past).count()); + assertEquals(6L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, past).count()); + + assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, new Date(0)).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, new Date(0)).count()); + assertEquals(2L, table.where().lessThan(new long[]{0}, oneNullTable, new Date(0)).count()); + assertEquals(3L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, new Date(0)).count()); + assertEquals(4L, table.where().greaterThan(new long[]{0}, oneNullTable, new Date(0)).count()); + assertEquals(5L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, new Date(0)).count()); + + assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, future).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, future).count()); + assertEquals(5L, table.where().lessThan(new long[]{0}, oneNullTable, future).count()); + assertEquals(6L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, future).count()); + assertEquals(1L, table.where().greaterThan(new long[]{0}, oneNullTable, future).count()); + assertEquals(2L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, future).count()); + + assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, distantFuture).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, distantFuture).count()); + assertEquals(6L, table.where().lessThan(new long[]{0}, oneNullTable, distantFuture).count()); + assertEquals(7L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, distantFuture).count()); + assertEquals(0L, table.where().greaterThan(new long[]{0}, oneNullTable, distantFuture).count()); + assertEquals(1L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, distantFuture).count()); // between @@ -752,12 +754,12 @@ public void testByteArrayQuery() throws Exception { // Equal to - assertEquals(1L, table.where().equalTo(new long[]{0}, binary1).count()); - assertEquals(1L, table.where().equalTo(new long[]{0}, binary3).count()); + assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, binary1).count()); + assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, binary3).count()); // Not equal to - assertEquals(3L, table.where().notEqualTo(new long[]{0}, binary2).count()); - assertEquals(3L, table.where().notEqualTo(new long[]{0}, binary4).count()); + assertEquals(3L, table.where().notEqualTo(new long[]{0}, oneNullTable, binary2).count()); + assertEquals(3L, table.where().notEqualTo(new long[]{0}, oneNullTable, binary4).count()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index e387e92c1f..46411ee027 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -43,6 +43,7 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; + @RunWith(AndroidJUnit4.class) public class JNITableTest { @Rule @@ -71,8 +72,7 @@ public void tableToString() { } @Test - public void rowOperationsOnZeroRow(){ - + public void rowOperationsOnZeroRow() { Table t = new Table(); // Removes rows without columns. try { t.moveLastOver(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} @@ -82,7 +82,6 @@ public void rowOperationsOnZeroRow(){ t.addColumn(RealmFieldType.STRING, ""); try { t.moveLastOver(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} try { t.moveLastOver(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - } @Test @@ -90,27 +89,48 @@ public void zeroColOperations() { Table tableZeroCols = new Table(); // Adds rows. - try { tableZeroCols.add("val"); fail("No columns in table"); } catch (IndexOutOfBoundsException ignored) {} - try { tableZeroCols.addEmptyRow(); fail("No columns in table"); } catch (IndexOutOfBoundsException ignored) {} - try { tableZeroCols.addEmptyRows(10); fail("No columns in table"); } catch (IndexOutOfBoundsException ignored) {} + try { + tableZeroCols.add("val"); + fail("No columns in table"); + } catch (IndexOutOfBoundsException ignored) {} + try { + tableZeroCols.addEmptyRow(); + fail("No columns in table"); + } catch (IndexOutOfBoundsException ignored) {} + try { + tableZeroCols.addEmptyRows(10); + fail("No columns in table"); + } catch (IndexOutOfBoundsException ignored) {} // Col operations - try { tableZeroCols.removeColumn(0); fail("No columns in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - try { tableZeroCols.renameColumn(0, "newName"); fail("No columns in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - try { tableZeroCols.removeColumn(10); fail("No columns in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - try { tableZeroCols.renameColumn(10, "newName"); fail("No columns in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} + try { + tableZeroCols.removeColumn(0); + fail("No columns in table"); + } catch (ArrayIndexOutOfBoundsException ignored) {} + try { + tableZeroCols.renameColumn(0, "newName"); + fail("No columns in table"); + } catch (ArrayIndexOutOfBoundsException ignored) {} + try { + tableZeroCols.removeColumn(10); + fail("No columns in table"); + } catch (ArrayIndexOutOfBoundsException ignored) {} + try { + tableZeroCols.renameColumn(10, "newName"); + fail("No columns in table"); + } catch (ArrayIndexOutOfBoundsException ignored) {} } @Test public void findFirstNonExisting() { Table t = TestHelper.getTableWithAllColumnTypes(); - t.add(new byte[]{1, 2, 3}, true, new Date(1384423149761L), 4.5d, 5.7f, 100, "string"); + t.add(new byte[] {1, 2, 3}, true, new Date(1384423149761L), 4.5D, 5.7F, 100, "string"); assertEquals(-1, t.findFirstBoolean(1, false)); - // FIXME: reenable when core implements find_first_timestamp(): assertEquals(-1, t.findFirstDate(2, new Date(138442314986l))); - assertEquals(-1, t.findFirstDouble(3, 1.0d)); - assertEquals(-1, t.findFirstFloat(4, 1.0f)); + assertEquals(-1, t.findFirstDate(2, new Date(138442314986L))); + assertEquals(-1, t.findFirstDouble(3, 1.0D)); + assertEquals(-1, t.findFirstFloat(4, 1.0F)); assertEquals(-1, t.findFirstLong(5, 50)); } @@ -119,9 +139,9 @@ public void findFirst() { final int TEST_SIZE = 10; Table t = TestHelper.getTableWithAllColumnTypes(); for (int i = 0; i < TEST_SIZE; i++) { - t.add(new byte[]{1,2,3}, true, new Date(i), (double)i, (float)i, i, "string " + i); + t.add(new byte[] {1, 2, 3}, true, new Date(i), (double) i, (float) i, i, "string " + i); } - t.add(new byte[]{1, 2, 3}, true, new Date(TEST_SIZE), (double) TEST_SIZE, (float) TEST_SIZE, TEST_SIZE, ""); + t.add(new byte[] {1, 2, 3}, true, new Date(TEST_SIZE), (double) TEST_SIZE, (float) TEST_SIZE, TEST_SIZE, ""); assertEquals(0, t.findFirstBoolean(1, true)); for (int i = 0; i < TEST_SIZE; i++) { @@ -147,33 +167,96 @@ public void getValuesFromNonExistingColumn() { Table t = TestHelper.getTableWithAllColumnTypes(); t.addEmptyRows(10); - try { t.getBinaryByteArray(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getBinaryByteArray(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getBinaryByteArray(9, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getBinaryByteArray(-1, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getBinaryByteArray(-10, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getBinaryByteArray(9, 0); + fail("Column does not exist"); + } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getBoolean(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getBoolean(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getBoolean(9, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getBoolean(-1, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getBoolean(-10, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getBoolean(9, 0); + fail("Column does not exist"); + } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getDate(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getDate(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getDate(9, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getDate(-1, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getDate(-10, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getDate(9, 0); + fail("Column does not exist"); + } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getDouble(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getDouble(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getDouble(9, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getDouble(-1, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getDouble(-10, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getDouble(9, 0); + fail("Column does not exist"); + } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getFloat(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getFloat(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getFloat(9, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getFloat(-1, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getFloat(-10, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getFloat(9, 0); + fail("Column does not exist"); + } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getLong(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getLong(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getLong(9, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getLong(-1, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getLong(-10, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getLong(9, 0); + fail("Column does not exist"); + } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getString(-1, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getString(-10, 0); fail("Column is less than 0"); } catch (ArrayIndexOutOfBoundsException ignored) { } - try { t.getString(9, 0); fail("Column does not exist"); } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getString(-1, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getString(-10, 0); + fail("Column is less than 0"); + } catch (ArrayIndexOutOfBoundsException ignored) { } + try { + t.getString(9, 0); + fail("Column does not exist"); + } catch (ArrayIndexOutOfBoundsException ignored) { } } @Test @@ -182,7 +265,10 @@ public void getNonExistingColumn() { t.addColumn(RealmFieldType.INTEGER, "int"); assertEquals(-1, t.getColumnIndex("non-existing column")); - try { t.getColumnIndex(null); fail("column name null"); } catch (IllegalArgumentException ignored) { } + try { + t.getColumnIndex(null); + fail("column name null"); + } catch (IllegalArgumentException ignored) { } } @Test @@ -191,10 +277,16 @@ public void setNulls() { t.addColumn(RealmFieldType.STRING, ""); t.addColumn(RealmFieldType.DATE, ""); t.addColumn(RealmFieldType.BINARY, ""); - t.add("String val", new Date(), new byte[]{1, 2, 3}); + t.add("String val", new Date(), new byte[] {1, 2, 3}); - try { t.setString(0, 0, null, false); fail("null string not allowed"); } catch (IllegalArgumentException ignored) { } - try { t.setDate(1, 0, null, false); fail("null Date not allowed"); } catch (IllegalArgumentException ignored) { } + try { + t.setString(0, 0, null, false); + fail("null string not allowed"); + } catch (IllegalArgumentException ignored) { } + try { + t.setDate(1, 0, null, false); + fail("null Date not allowed"); + } catch (IllegalArgumentException ignored) { } } @Test @@ -202,7 +294,10 @@ public void addNegativeEmptyRows() { Table t = new Table(); t.addColumn(RealmFieldType.STRING, "colName"); - try { t.addEmptyRows(-1); fail("Argument is negative"); } catch (IllegalArgumentException ignored) { } + try { + t.addEmptyRows(-1); + fail("Argument is negative"); + } catch (IllegalArgumentException ignored) { } } @Test @@ -233,7 +328,7 @@ public void shouldThrowWhenSetIndexOnWrongRealmFieldType() { // All types supported addSearchIndex and removeSearchIndex. boolean exceptionExpected = ( - t.getColumnType(colIndex) != RealmFieldType.STRING && + t.getColumnType(colIndex) != RealmFieldType.STRING && t.getColumnType(colIndex) != RealmFieldType.INTEGER && t.getColumnType(colIndex) != RealmFieldType.BOOLEAN && t.getColumnType(colIndex) != RealmFieldType.DATE); @@ -266,7 +361,10 @@ public void shouldThrowWhenSetIndexOnWrongRealmFieldType() { @Test public void columnName() { Table t = new Table(); - try { t.addColumn(RealmFieldType.STRING, "I am 64 characters.............................................."); fail("Only 63 characters supported"); } catch (IllegalArgumentException ignored) { } + try { + t.addColumn(RealmFieldType.STRING, "I am 64 characters.............................................."); + fail("Only 63 characters supported"); + } catch (IllegalArgumentException ignored) { } t.addColumn(RealmFieldType.STRING, "I am 63 characters............................................."); } @@ -279,40 +377,40 @@ public void tableNumbers() { t.addColumn(RealmFieldType.STRING, "StringCol"); // Adds 3 rows of data with same values in each column. - t.add(1, 2.0d, 3.0f, "s1"); - t.add(1, 2.0d, 3.0f, "s1"); - t.add(1, 2.0d, 3.0f, "s1"); + t.add(1, 2.0D, 3.0F, "s1"); + t.add(1, 2.0D, 3.0F, "s1"); + t.add(1, 2.0D, 3.0F, "s1"); // Adds other values. - t.add(10, 20.0d, 30.0f, "s10"); - t.add(100, 200.0d, 300.0f, "s100"); - t.add(1000, 2000.0d, 3000.0f, "s1000"); + t.add(10, 20.0D, 30.0F, "s10"); + t.add(100, 200.0D, 300.0F, "s100"); + t.add(1000, 2000.0D, 3000.0F, "s1000"); // Counts instances of values added in the first 3 rows. assertEquals(3, t.count(0, 1)); - assertEquals(3, t.count(1, 2.0d)); - assertEquals(3, t.count(2, 3.0f)); + assertEquals(3, t.count(1, 2.0D)); + assertEquals(3, t.count(2, 3.0F)); assertEquals(3, t.count(3, "s1")); - assertEquals(3, t.findFirstDouble(1, 20.0d)); // Find rows index for first double value of 20.0 in column 1. - assertEquals(4, t.findFirstFloat(2, 300.0f)); // Find rows index for first float value of 300.0 in column 2. + assertEquals(3, t.findFirstDouble(1, 20.0D)); // Find rows index for first double value of 20.0 in column 1. + assertEquals(4, t.findFirstFloat(2, 300.0F)); // Find rows index for first float value of 300.0 in column 2. // Sets double and float. - t.setDouble(1, 2, -2.0d, false); - t.setFloat(2, 2, -3.0f, false); + t.setDouble(1, 2, -2.0D, false); + t.setFloat(2, 2, -3.0F, false); // Gets double tests. - assertEquals(-2.0d, t.getDouble(1, 2)); - assertEquals(20.0d, t.getDouble(1, 3)); - assertEquals(200.0d, t.getDouble(1, 4)); - assertEquals(2000.0d, t.getDouble(1, 5)); + assertEquals(-2.0D, t.getDouble(1, 2)); + assertEquals(20.0D, t.getDouble(1, 3)); + assertEquals(200.0D, t.getDouble(1, 4)); + assertEquals(2000.0D, t.getDouble(1, 5)); // Gets float test. - assertEquals(-3.0f, t.getFloat(2, 2)); - assertEquals(30.0f, t.getFloat(2, 3)); - assertEquals(300.0f, t.getFloat(2, 4)); - assertEquals(3000.0f, t.getFloat(2, 5)); + assertEquals(-3.0F, t.getFloat(2, 2)); + assertEquals(30.0F, t.getFloat(2, 3)); + assertEquals(300.0F, t.getFloat(2, 4)); + assertEquals(3000.0F, t.getFloat(2, 5)); } // Tests the migration of a string column to be nullable. @@ -323,7 +421,7 @@ public void convertToNullable() { for (RealmFieldType columnType : columnTypes) { // Tests various combinations of column names and nullability. String[] columnNames = {"foobar", "__TMP__0"}; - for (boolean nullable : new boolean[]{Table.NOT_NULLABLE, Table.NULLABLE}) { + for (boolean nullable : new boolean[] {Table.NOT_NULLABLE, Table.NULLABLE}) { for (String columnName : columnNames) { Table table = new Table(); long colIndex = table.addColumn(columnType, columnName, nullable); @@ -336,11 +434,11 @@ public void convertToNullable() { } else if (columnType == RealmFieldType.DOUBLE) { table.setDouble(colIndex, 0, 1.0, false); } else if (columnType == RealmFieldType.FLOAT) { - table.setFloat(colIndex, 0, 1.0f, false); + table.setFloat(colIndex, 0, 1.0F, false); } else if (columnType == RealmFieldType.INTEGER) { table.setLong(colIndex, 0, 1, false); } else if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 0, new byte[]{0}, false); + table.setBinaryByteArray(colIndex, 0, new byte[] {0}, false); } else if (columnType == RealmFieldType.STRING) { table.setString(colIndex, 0, "Foo", false); } @@ -399,26 +497,25 @@ public void convertToNotNullable() { for (RealmFieldType columnType : columnTypes) { // Tests various combinations of column names and nullability. String[] columnNames = {"foobar", "__TMP__0"}; - for (boolean nullable : new boolean[]{Table.NOT_NULLABLE, Table.NULLABLE}) { + for (boolean nullable : new boolean[] {Table.NOT_NULLABLE, Table.NULLABLE}) { for (String columnName : columnNames) { Table table = new Table(); long colIndex = table.addColumn(columnType, columnName, nullable); table.addColumn(RealmFieldType.BOOLEAN, "bool"); table.addEmptyRow(); - if (columnType == RealmFieldType.BOOLEAN) + if (columnType == RealmFieldType.BOOLEAN) { table.setBoolean(colIndex, 0, true, false); - else if (columnType == RealmFieldType.DATE) + } else if (columnType == RealmFieldType.DATE) { table.setDate(colIndex, 0, new Date(1), false); - else if (columnType == RealmFieldType.DOUBLE) + } else if (columnType == RealmFieldType.DOUBLE) { table.setDouble(colIndex, 0, 1.0, false); - else if (columnType == RealmFieldType.FLOAT) - table.setFloat(colIndex, 0, 1.0f, false); - else if (columnType == RealmFieldType.INTEGER) + } else if (columnType == RealmFieldType.FLOAT) { + table.setFloat(colIndex, 0, 1.0F, false); + } else if (columnType == RealmFieldType.INTEGER) { table.setLong(colIndex, 0, 1, false); - else if (columnType == RealmFieldType.BINARY) - table.setBinaryByteArray(colIndex, 0, new byte[]{0}, false); - else if (columnType == RealmFieldType.STRING) - table.setString(colIndex, 0, "Foo", false); + } else if (columnType == RealmFieldType.BINARY) { + table.setBinaryByteArray(colIndex, 0, new byte[] {0}, false); + } else if (columnType == RealmFieldType.STRING) { table.setString(colIndex, 0, "Foo", false); } try { table.addEmptyRow(); if (columnType == RealmFieldType.BINARY) { @@ -467,16 +564,17 @@ else if (columnType == RealmFieldType.STRING) assertEquals("", table.getString(colIndex, 1)); } else { assertFalse(table.getUncheckedRow(1).isNull(colIndex)); - if (columnType == RealmFieldType.BOOLEAN) + if (columnType == RealmFieldType.BOOLEAN) { assertEquals(false, table.getBoolean(colIndex, 1)); - else if (columnType == RealmFieldType.DATE) + } else if (columnType == RealmFieldType.DATE) { assertEquals(0, table.getDate(colIndex, 1).getTime()); - else if (columnType == RealmFieldType.DOUBLE) + } else if (columnType == RealmFieldType.DOUBLE) { assertEquals(0.0, table.getDouble(colIndex, 1)); - else if (columnType == RealmFieldType.FLOAT) - assertEquals(0.0f, table.getFloat(colIndex, 1)); - else if (columnType == RealmFieldType.INTEGER) + } else if (columnType == RealmFieldType.FLOAT) { + assertEquals(0.0F, table.getFloat(colIndex, 1)); + } else if (columnType == RealmFieldType.INTEGER) { assertEquals(0, table.getLong(colIndex, 1)); + } } } } @@ -502,19 +600,19 @@ public void defaultValue_setAndGet() { //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); - final Table table = sharedRealm.getTable(Table.TABLE_PREFIX + "DefaultValueTest"); + final Table table = sharedRealm.getTable(Table.getTableNameForClass("DefaultValueTest")); sharedRealm.commitTransaction(); List> columnInfoList = Arrays.asList( new Pair(RealmFieldType.STRING, "string value"), new Pair(RealmFieldType.INTEGER, 100L), new Pair(RealmFieldType.BOOLEAN, true), - new Pair(RealmFieldType.BINARY, new byte[]{123}), + new Pair(RealmFieldType.BINARY, new byte[] {123}), new Pair(RealmFieldType.DATE, new Date(123456)), - new Pair(RealmFieldType.FLOAT, 1.234f), + new Pair(RealmFieldType.FLOAT, 1.234F), new Pair(RealmFieldType.DOUBLE, Math.PI), new Pair(RealmFieldType.OBJECT, 0L) - // Currently, LIST does not support default value. + // FIXME: Currently, LIST does not support default value. // new Pair(RealmFieldType.LIST, ) ); @@ -625,19 +723,19 @@ public void defaultValue_setMultipleTimes() { //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); - final Table table = sharedRealm.getTable(Table.TABLE_PREFIX + "DefaultValueTest"); + final Table table = sharedRealm.getTable(Table.getTableNameForClass("DefaultValueTest")); sharedRealm.commitTransaction(); List> columnInfoList = Arrays.asList( new Pair(RealmFieldType.STRING, new String[] {"string value1", "string value2"}), new Pair(RealmFieldType.INTEGER, new Long[] {100L, 102L}), new Pair(RealmFieldType.BOOLEAN, new Boolean[] {false, true}), - new Pair(RealmFieldType.BINARY, new byte[][] {new byte[]{123}, new byte[]{-123}}), + new Pair(RealmFieldType.BINARY, new byte[][] {new byte[] {123}, new byte[] {-123}}), new Pair(RealmFieldType.DATE, new Date[] {new Date(123456), new Date(13579)}), - new Pair(RealmFieldType.FLOAT, new Float[] {1.234f, 100f}), + new Pair(RealmFieldType.FLOAT, new Float[] {1.234F, 100F}), new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}), new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L}) - // Currently, LIST does not support default value. + // FIXME: Currently, LIST does not support default value. // new Pair(RealmFieldType.LIST, ) ); @@ -757,19 +855,19 @@ public void defaultValue_overwrittenByNonDefault() { //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); - final Table table = sharedRealm.getTable(Table.TABLE_PREFIX + "DefaultValueTest"); + final Table table = sharedRealm.getTable(Table.getTableNameForClass("DefaultValueTest")); sharedRealm.commitTransaction(); List> columnInfoList = Arrays.asList( new Pair(RealmFieldType.STRING, new String[] {"string value1", "string value2"}), new Pair(RealmFieldType.INTEGER, new Long[] {100L, 102L}), new Pair(RealmFieldType.BOOLEAN, new Boolean[] {false, true}), - new Pair(RealmFieldType.BINARY, new byte[][] {new byte[]{123}, new byte[]{-123}}), + new Pair(RealmFieldType.BINARY, new byte[][] {new byte[] {123}, new byte[] {-123}}), new Pair(RealmFieldType.DATE, new Date[] {new Date(123456), new Date(13579)}), - new Pair(RealmFieldType.FLOAT, new Float[] {1.234f, 100f}), + new Pair(RealmFieldType.FLOAT, new Float[] {1.234F, 100F}), new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}), new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L}) - // Currently, LIST does not support default value. + // FIXME: Currently, LIST does not support default value. // new Pair(RealmFieldType.LIST, ) ); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java index 1212415ce0..61f67b779e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java @@ -25,8 +25,8 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import io.realm.RealmConfiguration; import io.realm.RealmFieldType; @@ -39,6 +39,7 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; + @RunWith(AndroidJUnit4.class) public class SortDescriptorTests { @Rule @@ -64,14 +65,14 @@ public void tearDown() { @Test public void getInstanceForDistinct() { - for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { + for (RealmFieldType type : SortDescriptor.DISTINCT_VALID_FIELD_TYPES) { long column = table.addColumn(type, type.name()); table.addSearchIndex(column); } long i = 0; - for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(table, type.name()); + for (RealmFieldType type : SortDescriptor.DISTINCT_VALID_FIELD_TYPES) { + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(null, table, type.name()); assertEquals(1, sortDescriptor.getColumnIndices()[0].length); assertEquals(i, sortDescriptor.getColumnIndices()[0][0]); assertNull(sortDescriptor.getAscendings()); @@ -89,13 +90,13 @@ public void getInstanceForDistinct_shouldThrowOnLinkAndListListField() { table.addColumnLink(listType, listType.name(), table); try { - SortDescriptor.getInstanceForDistinct(table, String.format("%s.%s", listType.name(), type.name())); + SortDescriptor.getInstanceForDistinct(null, table, String.format("%s.%s", listType.name(), type.name())); fail(); } catch (IllegalArgumentException ignored) { } try { - SortDescriptor.getInstanceForDistinct(table, String.format("%s.%s", objectType.name(), type.name())); + SortDescriptor.getInstanceForDistinct(null, table, String.format("%s.%s", objectType.name(), type.name())); fail(); } catch (IllegalArgumentException ignored) { } @@ -110,8 +111,8 @@ public void getInstanceForDistinct_multipleFields() { long intColumn = table.addColumn(intType, intType.name()); table.addSearchIndex(intColumn); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(table, new String[] { - stringType.name(), intType.name()}); + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(null, table, new String[] { + stringType.name(), intType.name()}); assertEquals(2, sortDescriptor.getColumnIndices().length); assertNull(sortDescriptor.getAscendings()); assertEquals(1, sortDescriptor.getColumnIndices()[0].length); @@ -122,24 +123,11 @@ public void getInstanceForDistinct_multipleFields() { @Test public void getInstanceForDistinct_shouldThrowOnInvalidField() { - List types = new ArrayList(); - for (RealmFieldType type : RealmFieldType.values()) { - if (!SortDescriptor.validFieldTypesForDistinct.contains(type) && - type != RealmFieldType.UNSUPPORTED_DATE && - type != RealmFieldType.UNSUPPORTED_TABLE && - type != RealmFieldType.UNSUPPORTED_MIXED) { - if (type == RealmFieldType.LIST || type == RealmFieldType.OBJECT) { - table.addColumnLink(type, type.name(), table); - } else { - table.addColumn(type, type.name()); - } - types.add(type); - } - } + Set types = getValidFieldTypes(SortDescriptor.DISTINCT_VALID_FIELD_TYPES); for (RealmFieldType type : types) { try { - SortDescriptor.getInstanceForDistinct(table, type.name()); + SortDescriptor.getInstanceForDistinct(null, table, type.name()); fail(); } catch (IllegalArgumentException ignored) { assertTrue(ignored.getMessage().contains("Distinct is not supported")); @@ -149,13 +137,13 @@ public void getInstanceForDistinct_shouldThrowOnInvalidField() { @Test public void getInstanceForSort() { - for (RealmFieldType type : SortDescriptor.validFieldTypesForSort) { + for (RealmFieldType type : SortDescriptor.SORT_VALID_FIELD_TYPES) { table.addColumn(type, type.name()); } long i = 0; - for (RealmFieldType type : SortDescriptor.validFieldTypesForSort) { - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(table, type.name(), Sort.DESCENDING); + for (RealmFieldType type : SortDescriptor.SORT_VALID_FIELD_TYPES) { + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(null, table, type.name(), Sort.DESCENDING); assertEquals(1, sortDescriptor.getColumnIndices()[0].length); assertEquals(i, sortDescriptor.getColumnIndices()[0][0]); assertFalse(sortDescriptor.getAscendings()[0]); @@ -165,7 +153,7 @@ public void getInstanceForSort() { @Test public void getInstanceForSort_linkField() { - for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { + for (RealmFieldType type : SortDescriptor.DISTINCT_VALID_FIELD_TYPES) { long column = table.addColumn(type, type.name()); table.addSearchIndex(column); } @@ -173,8 +161,8 @@ public void getInstanceForSort_linkField() { long columnLink = table.addColumnLink(objectType, objectType.name(), table); long i = 0; - for (RealmFieldType type : SortDescriptor.validFieldTypesForDistinct) { - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(table, + for (RealmFieldType type : SortDescriptor.DISTINCT_VALID_FIELD_TYPES) { + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(null, table, String.format("%s.%s", objectType.name(), type.name()), Sort.ASCENDING); assertEquals(2, sortDescriptor.getColumnIndices()[0].length); assertEquals(columnLink, sortDescriptor.getColumnIndices()[0][0]); @@ -191,7 +179,7 @@ public void getInstanceForSort_multipleFields() { RealmFieldType intType = RealmFieldType.INTEGER; long intColumn = table.addColumn(intType, intType.name()); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(table, new String[] { + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(null, table, new String[] { stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING, Sort.DESCENDING}); assertEquals(2, sortDescriptor.getAscendings().length); @@ -216,31 +204,18 @@ public void getInstanceForSort_numOfFeildsAndSortOrdersNotMatch() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Number of fields and sort orders do not match."); - SortDescriptor.getInstanceForSort(table, - new String[] { stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING}); + SortDescriptor.getInstanceForSort(null, table, + new String[] {stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING}); } @Test public void getInstanceForSort_shouldThrowOnInvalidField() { - List types = new ArrayList(); - for (RealmFieldType type : RealmFieldType.values()) { - if (!SortDescriptor.validFieldTypesForSort.contains(type) && - type != RealmFieldType.UNSUPPORTED_DATE && - type != RealmFieldType.UNSUPPORTED_TABLE&& - type != RealmFieldType.UNSUPPORTED_MIXED) { - if (type == RealmFieldType.LIST || type == RealmFieldType.OBJECT) { - table.addColumnLink(type, type.name(), table); - } else { - table.addColumn(type, type.name()); - } - types.add(type); - } - } + Set types = getValidFieldTypes(SortDescriptor.SORT_VALID_FIELD_TYPES); for (RealmFieldType type : types) { try { - SortDescriptor.getInstanceForSort(table, type.name(), Sort.ASCENDING); + SortDescriptor.getInstanceForSort(null, table, type.name(), Sort.ASCENDING); fail(); } catch (IllegalArgumentException ignored) { assertTrue(ignored.getMessage().contains("Sort is not supported")); @@ -256,7 +231,31 @@ public void getInstanceForSort_shouldThrowOnLinkListField() { table.addColumnLink(listType, listType.name(), table); thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("is not a supported link field"); - SortDescriptor.getInstanceForSort(table, String.format("%s.%s", listType.name(), type.name()), Sort.ASCENDING); + thrown.expectMessage("Invalid query: field 'LIST' in table 'test_table' is of invalid type 'LIST'."); + SortDescriptor.getInstanceForSort(null, table, String.format("%s.%s", listType.name(), type.name()), Sort.ASCENDING); + } + + private Set getValidFieldTypes(Set filter) { + Set types = new HashSet<>(); + for (RealmFieldType type : RealmFieldType.values()) { + if (!filter.contains(type)) { + switch (type) { + case UNSUPPORTED_DATE: + case UNSUPPORTED_TABLE: + case UNSUPPORTED_MIXED: + case LINKING_OBJECTS: // TODO: should be supported?s + break; + case LIST: + case OBJECT: + table.addColumnLink(type, type.name(), table); + types.add(type); + break; + default: + table.addColumn(type, type.name()); + types.add(type); + } + } + } + return types; } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 3b7a2a466f..d539d0bd9c 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -22,6 +22,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -377,18 +378,17 @@ public void directory_dirIsAFile() throws IOException { file.delete(); // clean up } - /* FIXME: deleteRealmOnLogout is not supported by now + @Ignore("deleteRealmOnLogout is not supported yet") @Test public void deleteOnLogout() { - User user = createTestUser(); + SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url) - .deleteRealmOnLogout() + //.deleteRealmOnLogout() .build(); assertTrue(config.shouldDeleteRealmOnLogout()); } - */ @Test public void initialData() { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 487a95ff54..754c7f172b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -22,6 +22,7 @@ import org.junit.After; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -188,16 +189,15 @@ public void isAdmin_allUsers() { } // Tests that the user store returns the last user to login - /* FIXME: This test fails because of wrong JSON string. + @Ignore("This test fails because of wrong JSON string.") @Test public void currentUser_returnsUserAfterLogin() { AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); - when(authServer.loginUser(any(Credentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(Long.MAX_VALUE)); + when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(Long.MAX_VALUE)); - User user = User.login(Credentials.facebook("foo"), "http://bar.com/auth"); - assertEquals(user, User.currentUser()); + SyncUser user = SyncUser.login(SyncCredentials.facebook("foo"), "http://bar.com/auth"); + assertEquals(user, SyncUser.currentUser()); } - */ @Test public void getManagementRealm() { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index bfa10d391f..01158eb045 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -62,12 +62,23 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_TableQuery_nativeValidateQuery( // helper functions // Return TableRef used for build link queries -static TableRef getTableForLinkQuery(jlong nativeQueryPtr, JniLongArray& indicesArray) +// Each element in the indicesArray is the index of a column to be used to link to the next TableRef. +// If the corresponding entry in tablesArray is anything other than a nullptr, the link is a backlink. +// In that case, the tablesArray element is the pointer to the backlink source table and the +// indicesArray entry is the source column index in the source table. +// FIXME!!! This doesn't actually seem to be following backlinks. +static TableRef getTableForLinkQuery(jlong nativeQueryPtr, JniLongArray& tablesArray, JniLongArray& indicesArray) { TableRef table_ref = Q(nativeQueryPtr)->get_table(); jsize link_element_count = indicesArray.len() - 1; for (int i = 0; i < link_element_count; i++) { - table_ref->link(size_t(indicesArray[i])); + auto col_index = size_t(indicesArray[i]); + auto table_ptr = TBL(tablesArray[i]); + if (table_ptr == nullptr) { + table_ref->link(col_index); + } else { + table_ref->backlink(*table_ptr, col_index); + } } return table_ref; } @@ -83,6 +94,21 @@ static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& indicesArray return table_ref; } +// FIXME!!! This is a hasty attempt to fix the nullable queries. +// I am not at all sure that it is even the right idea, let alone correct code. --gbm +static bool isNullable(JNIEnv* env, Table* src_table_ptr, TableRef table_ref, jlong column_idx) +{ + // if table_arr is not a nullptr, this is a backlink and not allowed. + if (src_table_ptr != nullptr) { + ThrowException(env, IllegalArgument, "LinkingObject from field " + std::string(src_table_ptr->get_column_name(column_idx)) + " is not nullable."); + return false; + } + if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), column_idx)) { + return false; + } + return true; +} + template Query numeric_link_equal(TableRef tbl, jlong columnIndex, javatype value) { @@ -122,132 +148,144 @@ Query numeric_link_lessequal(TableRef tbl, jlong columnIndex, javatype value) // Integer -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JJ(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jlong value) + jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Int)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], value)); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + Q(nativeQueryPtr)->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JJ(JNIEnv* env, jobject, +JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Int)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->not_equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->not_equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_notequal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_notequal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JJ(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jlong value) + jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Int)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->greater(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greater(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_greater(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JJ(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Int)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->greater_equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater_equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greaterequal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_greaterequal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Int)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->less(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, arr[arr_len - 1], value)); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JJ(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jlong value) + jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Int)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->less_equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less_equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_lessequal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_lessequal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() @@ -276,134 +314,146 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JJJ(J // Float -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JF(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jfloat value) + jlongArray columnIndexes, + jlongArray tablePointers, jfloat value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Float)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JF(JNIEnv* env, jobject, +JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jfloat value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Float)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->not_equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->not_equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_notequal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_notequal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JF(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jfloat value) + jlongArray columnIndexes, + jlongArray tablePointers, jfloat value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Float)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->greater(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greater(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_greater(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JF(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jfloat value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Float)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->greater_equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater_equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greaterequal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_greaterequal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jfloat value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, + jlongArray tablePointers, jfloat value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Float)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->less(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, arr[arr_len - 1], value)); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JF(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jfloat value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Float)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->less_equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less_equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_lessequal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_lessequal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() @@ -433,135 +483,147 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JFF(J // Double -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JD(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jdouble value) + jlongArray columnIndexes, + jlongArray tablePointers, jdouble value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Double)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JD(JNIEnv* env, jobject, +JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jdouble value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Double)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->not_equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->not_equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_notequal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_notequal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3JD(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jdouble value) + jlongArray columnIndexes, + jlongArray tablePointers, jdouble value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Double)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->greater(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greater(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_greater(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3JD(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jdouble value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Double)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->greater_equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater_equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greaterequal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_greaterequal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jdouble value) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, + jlongArray tablePointers, jdouble value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Double)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->less(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_less(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_less(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3JD(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jdouble value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Double)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->less_equal(S(arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less_equal(S(index_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_lessequal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_lessequal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() @@ -593,21 +655,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JDD(J JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jlong value) + jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Timestamp)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->equal(S(arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->equal(S(index_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], + ->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], from_milliseconds(value))); } } @@ -617,21 +681,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualTimestamp(JN JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Timestamp)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->not_equal(S(arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->not_equal(S(index_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_notequal(table_ref, arr[arr_len - 1], + ->and_query(numeric_link_notequal(table_ref, index_arr[arr_len - 1], from_milliseconds(value))); } } @@ -640,21 +706,23 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualT JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jlong value) + jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Timestamp)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->greater(S(arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->greater(S(index_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greater(table_ref, arr[arr_len - 1], + ->and_query(numeric_link_greater(table_ref, index_arr[arr_len - 1], from_milliseconds(value))); } } @@ -664,21 +732,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterTimestamp( JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Timestamp)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->greater_equal(S(arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->greater_equal(S(index_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greaterequal(table_ref, arr[arr_len - 1], + ->and_query(numeric_link_greaterequal(table_ref, index_arr[arr_len - 1], from_milliseconds(value))); } } @@ -687,21 +757,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualTimes JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jlong value) + jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Timestamp)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->less(S(arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->less(S(index_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_less(table_ref, arr[arr_len - 1], + ->and_query(numeric_link_less(table_ref, index_arr[arr_len - 1], from_milliseconds(value))); } } @@ -711,21 +783,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessTimestamp(JNI JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jlong value) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Timestamp)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->less_equal(S(arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->less_equal(S(index_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_lessequal(table_ref, arr[arr_len - 1], + ->and_query(numeric_link_lessequal(table_ref, index_arr[arr_len - 1], from_milliseconds(value))); } } @@ -757,24 +831,25 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenTimestamp( // Bool -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JZ(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JZ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jboolean value) + jlongArray columnIndexes, + jlongArray tablePointers, jboolean value) { - JniLongArray arr(env, columnIndexes); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { - jsize arr_len = arr.len(); - if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Bool)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Bool)) { return; } - Q(nativeQueryPtr)->equal(S(arr[0]), value != 0 ? true : false); + Q(nativeQueryPtr)->equal(S(index_arr[0]), value != 0 ? true : false); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_equal(table_ref, arr[arr_len - 1], value)); + ->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], value)); } } CATCH_STD() @@ -785,75 +860,77 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JZ(JNIE enum StringPredicate { StringEqual, StringNotEqual, StringContains, StringBeginsWith, StringEndsWith, StringLike }; -static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, +static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jstring value, jboolean caseSensitive, StringPredicate predicate) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { if (value == NULL) { - if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, arr).get(), arr[arr_len - 1])) { + if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, index_arr).get(), index_arr[arr_len - 1])) { return; } } bool is_case_sensitive = caseSensitive ? true : false; JStringAccessor value2(env, value); // throws if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_String)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_String)) { return; } switch (predicate) { case StringEqual: - Q(nativeQueryPtr)->equal(S(arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->equal(S(index_arr[0]), value2, is_case_sensitive); break; case StringNotEqual: - Q(nativeQueryPtr)->not_equal(S(arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->not_equal(S(index_arr[0]), value2, is_case_sensitive); break; case StringContains: - Q(nativeQueryPtr)->contains(S(arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->contains(S(index_arr[0]), value2, is_case_sensitive); break; case StringBeginsWith: - Q(nativeQueryPtr)->begins_with(S(arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->begins_with(S(index_arr[0]), value2, is_case_sensitive); break; case StringEndsWith: - Q(nativeQueryPtr)->ends_with(S(arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->ends_with(S(index_arr[0]), value2, is_case_sensitive); break; case StringLike: - Q(nativeQueryPtr)->like(S(arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->like(S(index_arr[0]), value2, is_case_sensitive); break; } } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); switch (predicate) { case StringEqual: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) .equal(StringData(value2), is_case_sensitive)); break; case StringNotEqual: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) .not_equal(StringData(value2), is_case_sensitive)); break; case StringContains: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) .contains(StringData(value2), is_case_sensitive)); break; case StringBeginsWith: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) .begins_with(StringData(value2), is_case_sensitive)); break; case StringEndsWith: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) .ends_with(StringData(value2), is_case_sensitive)); break; case StringLike: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(arr[arr_len - 1])) + ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) .like(StringData(value2), is_case_sensitive)); break; } @@ -862,60 +939,68 @@ static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3JLjava_lang_String_2Z( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JLjava_lang_String_2Z( + JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringEqual); + TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringEqual); } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3JLjava_lang_String_2Z( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jstring value, jboolean caseSensitive) +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3JLjava_lang_String_2Z( + JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringNotEqual); + TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringNotEqual); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBeginsWith(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jstring value, + jlongArray columnIndexes, + jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringBeginsWith); + TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringBeginsWith); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndsWith(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jstring value, + jlongArray columnIndexes, + jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringEndsWith); + TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringEndsWith); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLike(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jstring value, + jlongArray columnIndexes, + jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringLike); + TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringLike); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeContains(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jstring value, + jlongArray columnIndexes, + jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, value, caseSensitive, StringContains); + TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringContains); } // Binary enum BinaryPredicate { BinaryEqual, BinaryNotEqual }; -static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnIndices, jbyteArray value, +static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnIndexes, + jlongArray tablePointers, jbyteArray value, BinaryPredicate predicate) { - JniLongArray arr(env, columnIndices); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); try { JniByteArray bytes(env, value); BinaryData value2; if (value == NULL) { - if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, arr).get(), arr[arr_len - 1])) { + if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, index_arr).get(), index_arr[arr_len - 1])) { return; } value2 = BinaryData(); @@ -929,26 +1014,26 @@ static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA } if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Binary)) { + if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Binary)) { return; } switch (predicate) { case BinaryEqual: - Q(nativeQueryPtr)->equal(S(arr[0]), value2); + Q(nativeQueryPtr)->equal(S(index_arr[0]), value2); break; case BinaryNotEqual: - Q(nativeQueryPtr)->not_equal(S(arr[0]), value2); + Q(nativeQueryPtr)->not_equal(S(index_arr[0]), value2); break; } } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, arr); + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); switch (predicate) { case BinaryEqual: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len - 1])) == value2); + Q(nativeQueryPtr)->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) == value2); break; case BinaryNotEqual: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(arr[arr_len - 1])) != value2); + Q(nativeQueryPtr)->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) != value2); break; } } @@ -956,20 +1041,22 @@ static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3B(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3J_3B(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndices, + jlongArray tablePointers, jbyteArray value) { - TableQuery_BinaryPredicate(env, nativeQueryPtr, columnIndices, value, BinaryEqual); + TableQuery_BinaryPredicate(env, nativeQueryPtr, columnIndices, tablePointers, value, BinaryEqual); } -JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3B(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3J_3B(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndices, + jlongArray tablePointers, jbyteArray value) { - TableQuery_BinaryPredicate(env, nativeQueryPtr, columnIndices, value, BinaryNotEqual); + TableQuery_BinaryPredicate(env, nativeQueryPtr, columnIndices, tablePointers, value, BinaryNotEqual); } // General ---------------------------------------------------- @@ -1400,20 +1487,23 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeRemove(JNIEnv* e // isNull and isNotNull JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes) + jlongArray columnIndexes, + jlongArray tablePointers) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); Query* pQuery = Q(nativeQueryPtr); - try { - TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - jlong column_idx = arr[arr_len - 1]; - TableRef table_ref = getTableByArray(nativeQueryPtr, arr); - if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), column_idx)) { + TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + jlong column_idx = index_arr[arr_len - 1]; + TableRef table_ref = getTableByArray(nativeQueryPtr, index_arr); + + if (!isNullable(env, TBL(table_arr[arr_len - 1]), table_ref, column_idx)) { return; } + // FIXME!!! Support a backlink as the last column in a field descriptor int col_type = table_ref->get_column_type(S(column_idx)); if (arr_len == 1) { switch (col_type) { @@ -1440,6 +1530,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en } } else { + // FIXME!!! Support a backlink as an internal column in a field descriptor switch (col_type) { case type_Link: ThrowException(env, IllegalArgument, "isNull() by nested query for link field is not supported."); @@ -1520,18 +1611,21 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery(JN } + JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes) + jlongArray columnIndexes, + jlongArray tablePointers) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); Query* pQuery = Q(nativeQueryPtr); try { - TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - jlong column_idx = arr[arr_len - 1]; - TableRef table_ref = getTableByArray(nativeQueryPtr, arr); + TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + jlong column_idx = index_arr[arr_len - 1]; + TableRef table_ref = getTableByArray(nativeQueryPtr, index_arr); - if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), column_idx)) { + if (!isNullable(env, TBL(table_arr[arr_len - 1]), table_ref, column_idx)) { return; } @@ -1600,17 +1694,19 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes) + jlongArray columnIndexes, + jlongArray tablePointers) { - - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); Query* pQuery = Q(nativeQueryPtr); try { - TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, arr); - jlong column_idx = arr[arr_len - 1]; - TableRef table_ref = getTableByArray(nativeQueryPtr, arr); + TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + jlong column_idx = index_arr[arr_len - 1]; + TableRef table_ref = getTableByArray(nativeQueryPtr, index_arr); + // FIXME!!! Support a backlink as the last column in a field descriptor int col_type = table_ref->get_column_type(S(column_idx)); if (arr_len == 1) { // Field queries @@ -1637,6 +1733,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e } else { // Linked queries + // FIXME!!! Support a backlink as an internal column in a field descriptor switch (col_type) { case type_Binary: pQuery->and_query(src_table_ref->column(S(column_idx)) == BinaryData("", 0)); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 5b7993e62a..0a5352c989 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -74,7 +74,7 @@ abstract class BaseRealm implements Closeable { private RealmCache realmCache; protected SharedRealm sharedRealm; - protected final StandardRealmSchema schema; + protected final RealmSchema schema; // Create a realm instance and associate it to a RealmCache. BaseRealm(RealmCache cache) { @@ -132,7 +132,7 @@ public boolean isAutoRefresh() { *

                      * WARNING: Calling this on a thread with async queries will turn those queries into synchronous queries. * In most cases it is better to use {@link RealmChangeListener}s to be notified about changes to the - * Realm on a given thread than it is to use this method. + * Realm on a given thread than it is to use this method. * * @throws IllegalStateException if attempting to refresh from within a transaction. */ @@ -740,6 +740,7 @@ public void clear() { } } + // FIXME: This stuff doesn't appear to be used. It should either be explained or deleted. static final class ThreadLocalRealmObjectContext extends ThreadLocal { @Override protected RealmObjectContext initialValue() { diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 17c5a861f4..718b7044c7 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -137,7 +137,7 @@ public DynamicRealmObject createObject(String className, Object primaryKeyValue) */ public RealmQuery where(String className) { checkIfValid(); - if (!sharedRealm.hasTable(Table.TABLE_PREFIX + className)) { + if (!sharedRealm.hasTable(Table.getTableNameForClass(className))) { throw new IllegalArgumentException("Class does not exist in the Realm and cannot be queried: " + className); } return RealmQuery.createDynamicQuery(this, className); diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index e8dcd1b15a..f4b0b3498c 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -37,7 +37,7 @@ public class DynamicRealmObject extends RealmObject implements RealmObjectProxy { static final String MSG_LINK_QUERY_NOT_SUPPORTED = "Queries across relationships are not supported"; - private final ProxyState proxyState = new ProxyState(this); + private final ProxyState proxyState = new ProxyState<>(this); /** * Creates a dynamic Realm object based on an existing object. @@ -344,7 +344,7 @@ public RealmList getList(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { LinkView linkView = proxyState.getRow$realm().getLinkList(columnIndex); - String className = RealmSchema.getSchemaForTable(linkView.getTargetTable()); + String className = linkView.getTargetTable().getClassName(); return new RealmList<>(className, linkView, proxyState.getRealm$realm()); } catch (IllegalArgumentException e) { checkFieldType(fieldName, columnIndex, RealmFieldType.LIST); @@ -705,7 +705,7 @@ public void setList(String fieldName, RealmList list) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); LinkView links = proxyState.getRow$realm().getLinkList(columnIndex); Table linkTargetTable = links.getTargetTable(); - final String linkTargetTableName = Table.tableNameToClassName(linkTargetTable.getName()); + final String linkTargetTableName = linkTargetTable.getClassName(); boolean typeValidated; if (list.className == null && list.clazz == null) { @@ -714,7 +714,7 @@ public void setList(String fieldName, RealmList list) { typeValidated = false; } else { String listType = list.className != null ? list.className - : Table.tableNameToClassName(proxyState.getRealm$realm().getSchema().getTable(list.clazz).getName()); + : proxyState.getRealm$realm().getSchema().getTable(list.clazz).getClassName(); if (!linkTargetTableName.equals(listType)) { throw new IllegalArgumentException(String.format(Locale.ENGLISH, "The elements in the list are not the proper type. " + @@ -736,7 +736,7 @@ public void setList(String fieldName, RealmList list) { "Element at index %d is not the proper type. " + "Was '%s' expected '%s'.", i, - Table.tableNameToClassName(obj.realmGet$proxyState().getRow$realm().getTable().getName()), + obj.realmGet$proxyState().getRow$realm().getTable().getClassName(), linkTargetTableName)); } indices[i] = obj.realmGet$proxyState().getRow$realm().getIndex(); @@ -777,7 +777,7 @@ public void setNull(String fieldName) { public String getType() { proxyState.getRealm$realm().checkIfValid(); - return RealmSchema.getSchemaForTable(proxyState.getRow$realm().getTable()); + return proxyState.getRow$realm().getTable().getClassName(); } /** @@ -873,7 +873,7 @@ public String toString() { return "Invalid object"; } - final String className = Table.tableNameToClassName(proxyState.getRow$realm().getTable().getName()); + final String className = proxyState.getRow$realm().getTable().getClassName(); StringBuilder sb = new StringBuilder(className + " = ["); String[] fields = getFieldNames(); for (String field : fields) { @@ -906,12 +906,11 @@ public String toString() { case OBJECT: sb.append(proxyState.getRow$realm().isNullLink(columnIndex) ? "null" - : Table.tableNameToClassName(proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getName())); + : proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName()); break; case LIST: - final String tableName = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getName(); - String targetType = Table.tableNameToClassName(tableName); - sb.append(String.format("RealmList<%s>[%s]", targetType, proxyState.getRow$realm().getLinkList(columnIndex).size())); + String targetClassName = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName(); + sb.append(String.format("RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getLinkList(columnIndex).size())); break; case UNSUPPORTED_TABLE: case UNSUPPORTED_MIXED: @@ -919,9 +918,9 @@ public String toString() { sb.append("?"); break; } - sb.append("}, "); + sb.append("},"); } - sb.replace(sb.length() - 2, sb.length(), ""); + sb.replace(sb.length() - 1, sb.length(), ""); sb.append("]"); return sb.toString(); } @@ -968,7 +967,7 @@ public RealmResults linkingObjects(String srcClassName, Stri RealmFieldType.OBJECT.name(), RealmFieldType.LIST.name())); } - return RealmResults.createBacklinkResults(realm, (CheckedRow) proxyState.getRow$realm(), realmObjectSchema.getTable(), srcFieldName); + return RealmResults.createDynamicBacklinkResults(realm, (CheckedRow) proxyState.getRow$realm(), realmObjectSchema.getTable(), srcFieldName); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index e7852490d1..194aa13d72 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -23,21 +23,24 @@ abstract class OrderedRealmCollectionImpl " 'OrderedRealmCollectionSnapshot'."; final BaseRealm realm; - Class classSpec; // Return type - String className; // Class name used by DynamicRealmObjects + final Class classSpec; // Return type + final String className; // Class name used by DynamicRealmObjects final Collection collection; OrderedRealmCollectionImpl(BaseRealm realm, Collection collection, Class clazz) { - this.realm = realm; - this.classSpec = clazz; - this.collection = collection; + this(realm, collection, clazz, null); } OrderedRealmCollectionImpl(BaseRealm realm, Collection collection, String className) { + this(realm, collection, null, className); + } + + private OrderedRealmCollectionImpl(BaseRealm realm, Collection collection, Class clazz, String className) { this.realm = realm; - this.className = className; this.collection = collection; + this.classSpec = clazz; + this.className = className; } Table getTable() { @@ -254,7 +257,7 @@ private long getColumnIndexForSort(String fieldName) { @Override public RealmResults sort(String fieldName) { SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, Sort.ASCENDING); + SortDescriptor.getInstanceForSort(getSchemaConnector(), collection.getTable(), fieldName, Sort.ASCENDING); Collection sortedCollection = collection.sort(sortDescriptor); return createLoadedResults(sortedCollection); @@ -266,7 +269,7 @@ public RealmResults sort(String fieldName) { @Override public RealmResults sort(String fieldName, Sort sortOrder) { SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(collection.getTable(), fieldName, sortOrder); + SortDescriptor.getInstanceForSort(getSchemaConnector(), collection.getTable(), fieldName, sortOrder); Collection sortedCollection = collection.sort(sortDescriptor); return createLoadedResults(sortedCollection); @@ -278,7 +281,7 @@ public RealmResults sort(String fieldName, Sort sortOrder) { @Override public RealmResults sort(String fieldNames[], Sort sortOrders[]) { SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(collection.getTable(), fieldNames, sortOrders); + SortDescriptor.getInstanceForSort(getSchemaConnector(), collection.getTable(), fieldNames, sortOrders); Collection sortedCollection = collection.sort(sortDescriptor); return createLoadedResults(sortedCollection); @@ -558,4 +561,8 @@ RealmResults createLoadedResults(Collection newCollection) { results.load(); return results; } + + private SchemaConnector getSchemaConnector() { + return new SchemaConnector(realm.getSchema()); + } } diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java index 6078699ec8..8da935e7a6 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java @@ -15,7 +15,6 @@ */ package io.realm; -import java.util.LinkedHashSet; import java.util.Set; import io.realm.internal.Table; @@ -29,13 +28,15 @@ class OsRealmObjectSchema extends RealmObjectSchema { * the validation of schema, object schemas and properties through the object store. Even though the constructor * is public, there is never a purpose which justifies calling it! * + * @param schema The parent for this schema: the schema to which this object belongs * @param className name of the class */ - OsRealmObjectSchema(String className) { - this.nativePtr = nativeCreateRealmObjectSchema(className); + OsRealmObjectSchema(RealmSchema schema, String className) { + this(schema, nativeCreateRealmObjectSchema(className)); } - OsRealmObjectSchema(long nativePtr) { + private OsRealmObjectSchema(RealmSchema schema, long nativePtr) { + super(schema); this.nativePtr = nativePtr; } @@ -162,11 +163,6 @@ public RealmFieldType getFieldType(String fieldName) { throw new UnsupportedOperationException(); } - @Override - long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes) { - throw new UnsupportedOperationException(); - } - @Override OsRealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { final Property property = new Property(name, type, primary, indexed, required); @@ -189,10 +185,6 @@ OsRealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema link return this; } - long getNativePtr() { - return nativePtr; - } - @Override Table getTable() { throw new UnsupportedOperationException(); @@ -203,6 +195,10 @@ long getAndCheckFieldIndex(String fieldName) { throw new UnsupportedOperationException(); } + long getNativePtr() { + return nativePtr; + } + static native long nativeCreateRealmObjectSchema(String className); static native void nativeAddProperty(long nativePtr, long nativePropertyPtr); diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java index 7d96117d9d..f047e5ec8d 100644 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java @@ -34,11 +34,11 @@ */ class OsRealmSchema extends RealmSchema { static final class Creator extends RealmSchema { - private final Map schema = new HashMap<>(); + private final Map schema = new HashMap<>(); @Override public void close() { - for (Map.Entry entry : schema.entrySet()) { + for (Map.Entry entry : schema.entrySet()) { entry.getValue().close(); } schema.clear(); @@ -52,13 +52,13 @@ public RealmObjectSchema get(String className) { @Override public Set getAll() { - return new LinkedHashSet<>(schema.values()); + return new LinkedHashSet(schema.values()); } @Override public RealmObjectSchema create(String className) { checkEmpty(className); - OsRealmObjectSchema realmObjectSchema = new OsRealmObjectSchema(className); + OsRealmObjectSchema realmObjectSchema = new OsRealmObjectSchema(this, className); schema.put(className, realmObjectSchema); return realmObjectSchema; } @@ -103,6 +103,10 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { private long nativePtr; + // TODO: + // Because making getAll return Set is a breaking change + // Creator.getAll must return Set instead of Set + // That necessitates the cast inside the loop below. OsRealmSchema(Creator creator) { Set realmObjectSchemas = creator.getAll(); long[] schemaNativePointers = new long[realmObjectSchemas.size()]; @@ -158,7 +162,7 @@ public Set getAll() { public RealmObjectSchema create(String className) { // Adding a class is always permitted. checkEmpty(className); - OsRealmObjectSchema realmObjectSchema = new OsRealmObjectSchema(className); + OsRealmObjectSchema realmObjectSchema = new OsRealmObjectSchema(this, className); dynamicClassToSchema.put(className, realmObjectSchema); return realmObjectSchema; } @@ -204,7 +208,7 @@ OsRealmObjectSchema getSchemaForClass(String className) { throw new UnsupportedOperationException(); } - static void checkEmpty(String str) { + private static void checkEmpty(String str) { if (str == null || str.isEmpty()) { throw new IllegalArgumentException("Null or empty class names are not allowed"); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 1ee29a820d..025ec56556 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -489,10 +489,7 @@ private static void initializeSyncedRealm(Realm realm) { schemaCreator = null; long newVersion = configuration.getSchemaVersion(); - // !!! FIXME: This appalling kludge is necessitated by current package structure/visiblity constraints. - // It absolutely breaks encapsulation and needs to be fixed! - long schemaNativePointer = schema.getNativePtr(); - if (realm.sharedRealm.requiresMigration(schemaNativePointer)) { + if (realm.sharedRealm.requiresMigration(schema.getNativePtr())) { if (currentVersion >= newVersion) { throw new IllegalArgumentException(String.format( "The schema was changed but the schema version was not updated. " + @@ -500,7 +497,7 @@ private static void initializeSyncedRealm(Realm realm) { " in the Realm file (%d) in order to update the schema.", newVersion, currentVersion)); } - realm.sharedRealm.updateSchema(schemaNativePointer, newVersion); + realm.sharedRealm.updateSchema(schema.getNativePtr(), newVersion); // The OS currently does not handle setting the schema version. We have to do it manually. realm.setVersion(newVersion); commitChanges = true; @@ -976,7 +973,7 @@ E createObjectInternal( // Checks and throws the exception earlier for a better exception message. if (table.hasPrimaryKey()) { throw new RealmException(String.format("'%s' has a primary key, use" + - " 'createObject(Class, Object)' instead.", Table.tableNameToClassName(table.getName()))); + " 'createObject(Class, Object)' instead.", table.getClassName())); } long rowIndex = table.addEmptyRow(); return get(clazz, rowIndex, acceptDefaultValue, excludeFields); @@ -1601,8 +1598,8 @@ public void run() { } } else { if (backgroundException != null) { - // FIXME: ThreadPoolExecutor will never throw the exception in the background. We need a - // redesign of the async transaction API. + // FIXME: ThreadPoolExecutor will never throw the exception in the background. + // We need a redesign of the async transaction API. // Throw in the worker thread since the caller thread cannot get notifications. throw new RealmException("Async transaction failed", backgroundException); } @@ -1764,14 +1761,19 @@ ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) { } ColumnIndices createdGlobalCache = null; - final RealmProxyMediator mediator = getConfiguration().getSchemaMediator(); ColumnIndices cacheForCurrentVersion = RealmCache.findColumnIndices(globalCacheArray, currentSchemaVersion); if (cacheForCurrentVersion == null) { + final RealmProxyMediator mediator = getConfiguration().getSchemaMediator(); + // Not found in global cache. create it. final Set> modelClasses = mediator.getModelClasses(); final Map, ColumnInfo> map; map = new HashMap<>(modelClasses.size()); + + + // This code may throw a RealmMigrationNeededException + //noinspection CaughtExceptionImmediatelyRethrown try { for (Class clazz : modelClasses) { final ColumnInfo columnInfo = mediator.validateTable(clazz, sharedRealm, true); @@ -1783,7 +1785,7 @@ ColumnIndices updateSchemaCache(ColumnIndices[] globalCacheArray) { cacheForCurrentVersion = createdGlobalCache = new ColumnIndices(currentSchemaVersion, map); } - schema.updateColumnIndices(cacheForCurrentVersion, mediator); + schema.updateColumnIndices(cacheForCurrentVersion); return createdGlobalCache; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 6d479323a8..fcd0145c9b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -87,16 +87,16 @@ static RealmCacheType valueOf(Class clazz) { } private static class CreateRealmRunnable implements Runnable { - private RealmConfiguration configuration; - private BaseRealm.InstanceCallback callback; - private Class realmClass; - private CountDownLatch canReleaseBackgroundInstanceLatch = new CountDownLatch(1); - private RealmNotifier notifier; + private final RealmConfiguration configuration; + private final BaseRealm.InstanceCallback callback; + private final Class realmClass; + private final CountDownLatch canReleaseBackgroundInstanceLatch = new CountDownLatch(1); + private final RealmNotifier notifier; // The Future this runnable belongs to. private Future future; CreateRealmRunnable(RealmNotifier notifier, RealmConfiguration configuration, - BaseRealm.InstanceCallback callback, Class realmClass) { + BaseRealm.InstanceCallback callback, Class realmClass) { this.configuration = configuration; this.realmClass = realmClass; this.callback = callback; @@ -251,7 +251,7 @@ static RealmAsyncTask createRealmOrGetFromCacheAsync( return cache.doCreateRealmOrGetFromCacheAsync(configuration, callback, realmClass); } - private synchronized RealmAsyncTask doCreateRealmOrGetFromCacheAsync( + private synchronized RealmAsyncTask doCreateRealmOrGetFromCacheAsync( RealmConfiguration configuration, BaseRealm.InstanceCallback callback, Class realmClass) { Capabilities capabilities = new AndroidCapabilities(); capabilities.checkCanDeliverNotification(ASYNC_NOT_ALLOWED_MSG); @@ -355,7 +355,7 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo if (realmClass == Realm.class && refAndCount.globalCount == 0) { // Stores a copy of local ColumnIndices as a global cache. - RealmCache.storeColumnIndices(typedColumnIndicesArray, realm.schema.cloneColumnIndices()); + RealmCache.storeColumnIndices(typedColumnIndicesArray, realm.schema.getImmutableColumnIndicies()); } // This is the first instance in current thread, increase the global count. refAndCount.globalCount++; diff --git a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java index b66639dc4c..d235293a7c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java +++ b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java @@ -42,17 +42,16 @@ public enum RealmFieldType { FLOAT(9), DOUBLE(10), OBJECT(12), - LIST(13); - // BACKLINK(14); Not exposed until needed. + LIST(13), + LINKING_OBJECTS(14); // Primitive array for fast mapping between between native values and their Realm type. - private static RealmFieldType[] typeList = new RealmFieldType[15]; + private static final RealmFieldType[] typeList = new RealmFieldType[15]; static { RealmFieldType[] columnTypes = values(); for (int i = 0; i < columnTypes.length; i++) { - int v = columnTypes[i].nativeValue; - typeList[v] = columnTypes[i]; + typeList[columnTypes[i].nativeValue] = columnTypes[i]; } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index ec33d39900..d59362b09e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -246,7 +246,7 @@ private E copyToRealmIfNeeded(E object) { RealmObjectProxy proxy = (RealmObjectProxy) object; if (proxy instanceof DynamicRealmObject) { - String listClassName = StandardRealmSchema.getSchemaForTable(view.getTargetTable()); + String listClassName = view.getTargetTable().getClassName(); if (proxy.realmGet$proxyState().getRealm$realm() == realm) { String objectClassName = ((DynamicRealmObject) object).getType(); if (listClassName.equals(objectClassName)) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index bed6061e1f..f0a029fac9 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -20,6 +20,7 @@ import io.realm.annotations.Required; import io.realm.internal.Table; +import io.realm.internal.fields.FieldDescriptor; /** @@ -29,6 +30,16 @@ * @see io.realm.RealmMigration */ public abstract class RealmObjectSchema { + private final RealmSchema schema; + + /** + * Create a schema. + * + * @param schema The parent for this schema: the schema to which this object belongs + */ + protected RealmObjectSchema(RealmSchema schema) { + this.schema = schema; + } /** * Release the object schema and any of native resources it might hold. @@ -266,12 +277,29 @@ public abstract class RealmObjectSchema { */ public abstract RealmFieldType getFieldType(String fieldName); - abstract long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes); + /** + * Get a parser for a field descriptor. + * + * @param fieldDescription fieldName or link path to a field name. + * @param validColumnTypes valid field type for the last field in a linked field + * @return a FieldDescriptor + */ + protected final FieldDescriptor getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes) { + return FieldDescriptor.createStandardFieldDescriptor(getSchemaConnector(), getTable(), fieldDescription, validColumnTypes); + } abstract RealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required); abstract RealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linkedTo); + abstract long getAndCheckFieldIndex(String fieldName); + + abstract Table getTable(); + + private SchemaConnector getSchemaConnector() { + return new SchemaConnector(schema); + } + /** * Function interface, used when traversing all objects of the current class and apply a function on each. * @@ -280,18 +308,4 @@ public abstract class RealmObjectSchema { public interface Function { void apply(DynamicRealmObject obj); } - - // Tuple containing data about each supported Java type. - protected static class FieldMetaData { - protected final RealmFieldType realmType; - protected final boolean defaultNullable; - - protected FieldMetaData(RealmFieldType realmType, boolean defaultNullable) { - this.realmType = realmType; - this.defaultNullable = defaultNullable; - } - } - - abstract Table getTable(); - abstract long getAndCheckFieldIndex(String fieldName); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index e9fbfdd5cb..f0c45e581f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -29,6 +29,7 @@ import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.TableQuery; +import io.realm.internal.fields.FieldDescriptor; /** @@ -60,7 +61,7 @@ public class RealmQuery { private LinkView linkView; private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; - static final String ASYNC_QUERY_WRONG_THREAD_MESSAGE = "Async query cannot be created on current thread."; + private static final String ASYNC_QUERY_WRONG_THREAD_MESSAGE = "Async query cannot be created on current thread."; /** * Creates a query for objects of a given class from a {@link Realm}. @@ -93,14 +94,11 @@ public static RealmQuery createDynamicQuery(DynamicRea * @return {@link RealmQuery} object. After building the query call one of the {@code find*} methods * to run it. */ - @SuppressWarnings("unchecked") public static RealmQuery createQueryFromResult(RealmResults queryResults) { - if (queryResults.classSpec != null) { - return new RealmQuery<>(queryResults, queryResults.classSpec); - } else { - return new RealmQuery(queryResults, queryResults.className); - } + return (queryResults.classSpec == null) + ? new RealmQuery(queryResults, queryResults.className) + : new RealmQuery<>(queryResults, queryResults.classSpec); } /** @@ -112,11 +110,9 @@ public static RealmQuery createQueryFromResult(RealmRe */ @SuppressWarnings("unchecked") public static RealmQuery createQueryFromList(RealmList list) { - if (list.clazz != null) { - return new RealmQuery(list.realm, list.view, list.clazz); - } else { - return new RealmQuery(list.realm, list.view, list.className); - } + return (list.clazz == null) + ? new RealmQuery(list.realm, list.view, list.className) + : new RealmQuery(list.realm, list.view, list.clazz); } private RealmQuery(Realm realm, Class clazz) { @@ -203,10 +199,10 @@ public boolean isValid() { public RealmQuery isNull(String fieldName) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName); + FieldDescriptor fd = schema.getColumnIndices(fieldName); // Checks that fieldName has the correct type is done in C++. - this.query.isNull(columnIndices); + this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); return this; } @@ -221,10 +217,10 @@ public RealmQuery isNull(String fieldName) { public RealmQuery isNotNull(String fieldName) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName); + FieldDescriptor fd = schema.getColumnIndices(fieldName); // Checks that fieldName has the correct type is done in C++. - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); return this; } @@ -256,8 +252,8 @@ public RealmQuery equalTo(String fieldName, String value, Case casing) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, String value, Case casing) { - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.equalTo(columnIndices, value, casing); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); return this; } @@ -276,11 +272,11 @@ public RealmQuery equalTo(String fieldName, Byte value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, Byte value) { - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNull(columnIndices); + this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.equalTo(columnIndices, value); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -296,11 +292,11 @@ private RealmQuery equalToWithoutThreadValidation(String fieldName, Byte valu public RealmQuery equalTo(String fieldName, byte[] value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.BINARY); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.BINARY); if (value == null) { - this.query.isNull(columnIndices); + this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.equalTo(columnIndices, value); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -320,11 +316,11 @@ public RealmQuery equalTo(String fieldName, Short value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, Short value) { - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNull(columnIndices); + this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.equalTo(columnIndices, value); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -344,11 +340,11 @@ public RealmQuery equalTo(String fieldName, Integer value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, Integer value) { - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNull(columnIndices); + this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.equalTo(columnIndices, value); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -368,11 +364,11 @@ public RealmQuery equalTo(String fieldName, Long value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, Long value) { - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNull(columnIndices); + this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.equalTo(columnIndices, value); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -392,11 +388,11 @@ public RealmQuery equalTo(String fieldName, Double value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, Double value) { - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); if (value == null) { - this.query.isNull(columnIndices); + this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.equalTo(columnIndices, value); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -416,11 +412,11 @@ public RealmQuery equalTo(String fieldName, Float value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, Float value) { - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); if (value == null) { - this.query.isNull(columnIndices); + this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.equalTo(columnIndices, value); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -440,11 +436,11 @@ public RealmQuery equalTo(String fieldName, Boolean value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, Boolean value) { - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.BOOLEAN); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.BOOLEAN); if (value == null) { - this.query.isNull(columnIndices); + this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.equalTo(columnIndices, value); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -464,8 +460,8 @@ public RealmQuery equalTo(String fieldName, Date value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, Date value) { - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.equalTo(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -705,11 +701,11 @@ public RealmQuery notEqualTo(String fieldName, String value) { public RealmQuery notEqualTo(String fieldName, String value, Case casing) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - if (columnIndices.length > 1 && !casing.getValue()) { + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + if (fd.length() > 1 && !casing.getValue()) { throw new IllegalArgumentException("Link queries cannot be case insensitive - coming soon."); } - this.query.notEqualTo(columnIndices, value, casing); + this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); return this; } @@ -724,11 +720,11 @@ public RealmQuery notEqualTo(String fieldName, String value, Case casing) { public RealmQuery notEqualTo(String fieldName, Byte value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(columnIndices, value); + this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -744,11 +740,11 @@ public RealmQuery notEqualTo(String fieldName, Byte value) { public RealmQuery notEqualTo(String fieldName, byte[] value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.BINARY); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.BINARY); if (value == null) { - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(columnIndices, value); + this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -764,11 +760,11 @@ public RealmQuery notEqualTo(String fieldName, byte[] value) { public RealmQuery notEqualTo(String fieldName, Short value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(columnIndices, value); + this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -784,11 +780,11 @@ public RealmQuery notEqualTo(String fieldName, Short value) { public RealmQuery notEqualTo(String fieldName, Integer value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(columnIndices, value); + this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -804,11 +800,11 @@ public RealmQuery notEqualTo(String fieldName, Integer value) { public RealmQuery notEqualTo(String fieldName, Long value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(columnIndices, value); + this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -824,11 +820,11 @@ public RealmQuery notEqualTo(String fieldName, Long value) { public RealmQuery notEqualTo(String fieldName, Double value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); if (value == null) { - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(columnIndices, value); + this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -844,11 +840,11 @@ public RealmQuery notEqualTo(String fieldName, Double value) { public RealmQuery notEqualTo(String fieldName, Float value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); if (value == null) { - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(columnIndices, value); + this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -864,11 +860,11 @@ public RealmQuery notEqualTo(String fieldName, Float value) { public RealmQuery notEqualTo(String fieldName, Boolean value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.BOOLEAN); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.BOOLEAN); if (value == null) { - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.equalTo(columnIndices, !value); + this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), !value); } return this; } @@ -884,11 +880,11 @@ public RealmQuery notEqualTo(String fieldName, Boolean value) { public RealmQuery notEqualTo(String fieldName, Date value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); if (value == null) { - this.query.isNotNull(columnIndices); + this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(columnIndices, value); + this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); } return this; } @@ -904,8 +900,8 @@ public RealmQuery notEqualTo(String fieldName, Date value) { public RealmQuery greaterThan(String fieldName, int value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.greaterThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -920,8 +916,8 @@ public RealmQuery greaterThan(String fieldName, int value) { public RealmQuery greaterThan(String fieldName, long value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.greaterThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -936,8 +932,8 @@ public RealmQuery greaterThan(String fieldName, long value) { public RealmQuery greaterThan(String fieldName, double value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.greaterThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -952,8 +948,8 @@ public RealmQuery greaterThan(String fieldName, double value) { public RealmQuery greaterThan(String fieldName, float value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.greaterThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -968,8 +964,8 @@ public RealmQuery greaterThan(String fieldName, float value) { public RealmQuery greaterThan(String fieldName, Date value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.greaterThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -984,8 +980,8 @@ public RealmQuery greaterThan(String fieldName, Date value) { public RealmQuery greaterThanOrEqualTo(String fieldName, int value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.greaterThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1000,8 +996,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, int value) { public RealmQuery greaterThanOrEqualTo(String fieldName, long value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.greaterThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1016,8 +1012,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, long value) { public RealmQuery greaterThanOrEqualTo(String fieldName, double value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.greaterThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1032,8 +1028,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, double value) { public RealmQuery greaterThanOrEqualTo(String fieldName, float value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.greaterThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1048,8 +1044,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, float value) { public RealmQuery greaterThanOrEqualTo(String fieldName, Date value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.greaterThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1064,8 +1060,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, Date value) { public RealmQuery lessThan(String fieldName, int value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.lessThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1080,8 +1076,8 @@ public RealmQuery lessThan(String fieldName, int value) { public RealmQuery lessThan(String fieldName, long value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.lessThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1096,8 +1092,8 @@ public RealmQuery lessThan(String fieldName, long value) { public RealmQuery lessThan(String fieldName, double value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.lessThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1112,8 +1108,8 @@ public RealmQuery lessThan(String fieldName, double value) { public RealmQuery lessThan(String fieldName, float value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.lessThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1128,8 +1124,8 @@ public RealmQuery lessThan(String fieldName, float value) { public RealmQuery lessThan(String fieldName, Date value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.lessThan(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1144,8 +1140,8 @@ public RealmQuery lessThan(String fieldName, Date value) { public RealmQuery lessThanOrEqualTo(String fieldName, int value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.lessThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1160,8 +1156,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, int value) { public RealmQuery lessThanOrEqualTo(String fieldName, long value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.lessThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1176,8 +1172,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, long value) { public RealmQuery lessThanOrEqualTo(String fieldName, double value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.lessThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1192,8 +1188,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, double value) { public RealmQuery lessThanOrEqualTo(String fieldName, float value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.lessThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1208,8 +1204,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, float value) { public RealmQuery lessThanOrEqualTo(String fieldName, Date value) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.lessThanOrEqual(columnIndices, value); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); return this; } @@ -1225,8 +1221,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, Date value) { public RealmQuery between(String fieldName, int from, int to) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.between(columnIndices, from, to); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.between(fd.getColumnIndices(), from, to); return this; } @@ -1242,8 +1238,8 @@ public RealmQuery between(String fieldName, int from, int to) { public RealmQuery between(String fieldName, long from, long to) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.between(columnIndices, from, to); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + this.query.between(fd.getColumnIndices(), from, to); return this; } @@ -1259,8 +1255,8 @@ public RealmQuery between(String fieldName, long from, long to) { public RealmQuery between(String fieldName, double from, double to) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.between(columnIndices, from, to); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + this.query.between(fd.getColumnIndices(), from, to); return this; } @@ -1276,8 +1272,8 @@ public RealmQuery between(String fieldName, double from, double to) { public RealmQuery between(String fieldName, float from, float to) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.between(columnIndices, from, to); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + this.query.between(fd.getColumnIndices(), from, to); return this; } @@ -1293,8 +1289,8 @@ public RealmQuery between(String fieldName, float from, float to) { public RealmQuery between(String fieldName, Date from, Date to) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.between(columnIndices, from, to); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + this.query.between(fd.getColumnIndices(), from, to); return this; } @@ -1323,8 +1319,8 @@ public RealmQuery contains(String fieldName, String value) { public RealmQuery contains(String fieldName, String value, Case casing) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.contains(columnIndices, value, casing); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + this.query.contains(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); return this; } @@ -1352,8 +1348,8 @@ public RealmQuery beginsWith(String fieldName, String value) { public RealmQuery beginsWith(String fieldName, String value, Case casing) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.beginsWith(columnIndices, value, casing); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + this.query.beginsWith(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); return this; } @@ -1381,8 +1377,8 @@ public RealmQuery endsWith(String fieldName, String value) { public RealmQuery endsWith(String fieldName, String value, Case casing) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.endsWith(columnIndices, value, casing); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + this.query.endsWith(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); return this; } @@ -1418,8 +1414,8 @@ public RealmQuery like(String fieldName, String value) { public RealmQuery like(String fieldName, String value, Case casing) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.like(columnIndices, value, casing); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + this.query.like(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); return this; } @@ -1497,8 +1493,9 @@ public RealmQuery not() { public RealmQuery isEmpty(String fieldName) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST); - this.query.isEmpty(columnIndices); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST, RealmFieldType.LINKING_OBJECTS); + this.query.isEmpty(fd.getColumnIndices(), fd.getNativeTablePointers()); + return this; } @@ -1513,8 +1510,9 @@ public RealmQuery isEmpty(String fieldName) { public RealmQuery isNotEmpty(String fieldName) { realm.checkIfValid(); - long[] columnIndices = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST); - this.query.isNotEmpty(columnIndices); + FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST, RealmFieldType.LINKING_OBJECTS); + this.query.isNotEmpty(fd.getColumnIndices(), fd.getNativeTablePointers()); + return this; } @@ -1533,7 +1531,7 @@ public RealmQuery isNotEmpty(String fieldName) { public RealmResults distinct(String fieldName) { realm.checkIfValid(); - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(query.getTable(), fieldName); + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), query.getTable(), fieldName); return createRealmResults(query, null, distinctDescriptor, true); } @@ -1554,7 +1552,7 @@ public RealmResults distinctAsync(String fieldName) { realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(query.getTable(), fieldName); + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), query.getTable(), fieldName); return createRealmResults(query, null, distinctDescriptor, false); } @@ -1577,7 +1575,7 @@ public RealmResults distinct(String firstFieldName, String... remainingFieldN fieldNames[0] = firstFieldName; System.arraycopy(remainingFieldNames, 0, fieldNames, 1, remainingFieldNames.length); - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(table, fieldNames); + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), table, fieldNames); return createRealmResults(query, null, distinctDescriptor, true); } @@ -1608,6 +1606,7 @@ public Number sum(String fieldName) { /** * Returns the average of a given field. + * Does not support dotted field notation. * * @param fieldName the field to calculate average on. Only number fields are supported. * @return the average for the given field amongst objects in query results. This will be of type double for all @@ -1771,7 +1770,7 @@ public RealmResults findAllAsync() { public RealmResults findAllSorted(String fieldName, Sort sortOrder) { realm.checkIfValid(); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldName, sortOrder); + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldName, sortOrder); return createRealmResults(query, sortDescriptor, null, true); } @@ -1788,7 +1787,7 @@ public RealmResults findAllSortedAsync(final String fieldName, final Sort sor realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldName, sortOrder); + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldName, sortOrder); return createRealmResults(query, sortDescriptor, null, false); } @@ -1838,7 +1837,7 @@ public RealmResults findAllSortedAsync(String fieldName) { public RealmResults findAllSorted(String[] fieldNames, Sort[] sortOrders) { realm.checkIfValid(); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldNames, sortOrders); + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldNames, sortOrders); return createRealmResults(query, sortDescriptor, null, true); } @@ -1861,7 +1860,7 @@ public RealmResults findAllSortedAsync(String[] fieldNames, final Sort[] sort realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(query.getTable(), fieldNames, sortOrders); + SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldNames, sortOrders); return createRealmResults(query, sortDescriptor, null, false); } @@ -1978,4 +1977,8 @@ private RealmResults createRealmResults(TableQuery query, private long getSourceRowIndexForFirstObject() { return this.query.find(); } + + private SchemaConnector getSchemaConnector() { + return new SchemaConnector(realm.getSchema()); + } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 6eae995442..82c7e62893 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -17,7 +17,9 @@ package io.realm; +import android.annotation.SuppressLint; import android.os.Looper; +import android.util.Log; import io.realm.internal.CheckedRow; import io.realm.internal.Collection; @@ -56,22 +58,26 @@ * @see Realm#executeTransaction(Realm.Transaction) */ public class RealmResults extends OrderedRealmCollectionImpl { - static RealmResults createBacklinkResults(Realm realm, UncheckedRow row, Class srcTableType, String srcFieldName) { + + // Called from Realm Proxy classes + @SuppressLint("unused") + static RealmResults createBacklinkResults(BaseRealm realm, Row row, Class srcTableType, String srcFieldName) { + UncheckedRow uncheckedRow = (UncheckedRow) row; Table srcTable = realm.getSchema().getTable(srcTableType); - return new RealmResults( + return new RealmResults<>( realm, - Collection.createBacklinksCollection(realm.sharedRealm, row, srcTable, srcFieldName), + Collection.createBacklinksCollection(realm.sharedRealm, uncheckedRow, srcTable, srcFieldName), srcTableType); } - static RealmResults createBacklinkResults(DynamicRealm realm, CheckedRow row, Table srcTable, String srcFieldName) { + // Abandon typing information, all ye who enter here + static RealmResults createDynamicBacklinkResults(DynamicRealm realm, CheckedRow row, Table srcTable, String srcFieldName) { return new RealmResults<>( realm, Collection.createBacklinksCollection(realm.sharedRealm, row, srcTable, srcFieldName), - Table.tableNameToClassName(srcTable.getName())); + Table.getClassNameForTable(srcTable.getName())); } - RealmResults(BaseRealm realm, Collection collection, Class clazz) { super(realm, collection, clazz); } @@ -255,7 +261,7 @@ public Observable> asObservable() { */ @Deprecated public RealmResults distinct(String fieldName) { - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(collection.getTable(), fieldName); + SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(new SchemaConnector(realm.getSchema()), collection.getTable(), fieldName); Collection distinctCollection = collection.distinct(distinctDescriptor); return createLoadedResults(distinctCollection); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 0968433c78..f23f9a2f4c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -21,7 +21,6 @@ import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; -import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; @@ -89,13 +88,32 @@ public abstract class RealmSchema { */ public abstract boolean contains(String className); + abstract Table getTable(Class clazz); + + abstract Table getTable(String className); + + abstract RealmObjectSchema getSchemaForClass(Class clazz); + + abstract RealmObjectSchema getSchemaForClass(String className); + + /** + * Set the column index cache for this schema. + * + * @param columnIndices the column index cache + */ final void setInitialColumnIndices(ColumnIndices columnIndices) { if (this.columnIndices != null) { throw new IllegalStateException("An instance of ColumnIndices is already set."); } - this.columnIndices = columnIndices.clone(); + this.columnIndices = new ColumnIndices(columnIndices, true); } + /** + * Set the column index cache for this schema. + * + * @param version the schema version + * @param columnInfoMap the column info map + */ final void setInitialColumnIndices(long version, Map, ColumnInfo> columnInfoMap) { if (this.columnIndices != null) { throw new IllegalStateException("An instance of ColumnIndices is already set."); @@ -105,49 +123,54 @@ final void setInitialColumnIndices(long version, Map /** * Updates all {@link ColumnInfo} elements in {@code columnIndices}. - * *

                      * The ColumnInfo elements are shared between all {@link RealmObject}s created by the Realm instance * which owns this RealmSchema. Updating them also means updating indices information in those {@link RealmObject}s. * * @param schemaVersion new schema version. - * @param mediator mediator for the Realm. */ - void updateColumnIndices(ColumnIndices schemaVersion, RealmProxyMediator mediator) { - columnIndices.copyFrom(schemaVersion, mediator); + void updateColumnIndices(ColumnIndices schemaVersion) { + columnIndices.copyFrom(schemaVersion); } - final ColumnIndices cloneColumnIndices() { - checkIndices(); - return columnIndices.clone(); + final boolean isProxyClass(Class modelClass, Class testee) { + return modelClass.equals(testee); } - final ColumnInfo getColumnInfo(Class clazz) { + /** + * Sometimes you need ColumnIndicies that can be passed between threads. + * Setting the mutable flag false creates an instance that is effectively final. + * + * @return a new, thread-safe copy of this Schema's ColumnIndices. + * @see ColumnIndices for the effectively final contract. + */ + final ColumnIndices getImmutableColumnIndicies() { checkIndices(); - return columnIndices.getColumnInfo(clazz); + return new ColumnIndices(columnIndices, false); + } + + final boolean haveColumnInfo() { + return columnIndices != null; } final long getSchemaVersion() { checkIndices(); - return this.columnIndices.getSchemaVersion(); + return columnIndices.getSchemaVersion(); } - final boolean isProxyClass(Class modelClass, Class testee) { - return modelClass.equals(testee); + final ColumnInfo getColumnInfo(Class clazz) { + checkIndices(); + return columnIndices.getColumnInfo(clazz); } - static String getSchemaForTable(Table table) { - return table.getName().substring(Table.TABLE_PREFIX.length()); + protected final ColumnInfo getColumnInfo(String className) { + checkIndices(); + return columnIndices.getColumnInfo(className); } private void checkIndices() { - if (this.columnIndices == null) { + if (!haveColumnInfo()) { throw new IllegalStateException("Attempt to use column index before set."); } } - - abstract Table getTable(Class clazz); - abstract Table getTable(String className); - abstract RealmObjectSchema getSchemaForClass(Class clazz); - abstract RealmObjectSchema getSchemaForClass(String className); } diff --git a/realm/realm-library/src/main/java/io/realm/SchemaConnector.java b/realm/realm-library/src/main/java/io/realm/SchemaConnector.java new file mode 100644 index 0000000000..5a695e474c --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/SchemaConnector.java @@ -0,0 +1,55 @@ +package io.realm; +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + + +import io.realm.internal.ColumnInfo; +import io.realm.internal.fields.FieldDescriptor; + + +/** + * This is a proxy, whose sole reason for existence, is to make package protected + * methods on Schema, visible outside the io.realm package. + * + * The class is in the package, so it has access to package protected methods. + * The class is not outside the package. + * The class implements one or more interfaces visible to package-external clients, that need them. + * + * I suggest creating instances of this through a factory method in the service class. + * That will make it easy to lazily instantiate a singleton should that become advisable. + */ +class SchemaConnector implements FieldDescriptor.SchemaProxy { + private final RealmSchema schema; + + public SchemaConnector(RealmSchema schema) { + this.schema = schema; + } + + @Override + public boolean hasCache() { + return schema.haveColumnInfo(); + } + + @Override + public ColumnInfo getColumnInfo(String tableName) { + return schema.getColumnInfo(tableName); + } + + @Override + public long getNativeTablePtr(String targetTable) { + return schema.getTable(targetTable).getNativePtr(); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java index 3591817aed..e09cc80a7d 100644 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java @@ -16,7 +16,6 @@ package io.realm; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -25,7 +24,7 @@ import java.util.Set; import io.realm.annotations.Required; -import io.realm.internal.RealmProxyMediator; +import io.realm.internal.ColumnInfo; import io.realm.internal.Table; @@ -65,25 +64,31 @@ class StandardRealmObjectSchema extends RealmObjectSchema { } private final BaseRealm realm; - private final Map columnIndices; + private final ColumnInfo columnInfo; private final Table table; + /** + * Creates a dynamic schema object for a given Realm class. + * + * @param realm Realm holding the objects. + * @param table table representation of the Realm class + */ + StandardRealmObjectSchema(BaseRealm realm, StandardRealmSchema schema, Table table) { + this(realm, schema, table, new StandardRealmObjectSchema.DynamicColumnIndices(table)); + } + /** * Creates a schema object for a given Realm class. * * @param realm Realm holding the objects. * @param table table representation of the Realm class - * @param columnIndices mapping between field names and column indexes for the given table + * @param columnInfo mapping between field names and column indexes for the given table */ - StandardRealmObjectSchema(BaseRealm realm, Table table, Map columnIndices) { + StandardRealmObjectSchema(BaseRealm realm, StandardRealmSchema schema, Table table, ColumnInfo columnInfo) { + super(schema); this.realm = realm; this.table = table; - this.columnIndices = columnIndices; - } - - @Override - Table getTable() { - return table; + this.columnInfo = columnInfo; } /** @@ -104,7 +109,7 @@ public void close() { } */ @Override public String getClassName() { - return table.getName().substring(Table.TABLE_PREFIX.length()); + return table.getClassName(); } /** @@ -120,7 +125,7 @@ public String getClassName() { public StandardRealmObjectSchema setClassName(String className) { realm.checkNotInSync(); // renaming a table is not permitted checkEmpty(className); - String internalTableName = Table.TABLE_PREFIX + className; + String internalTableName = Table.getTableNameForClass(className); if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: \'" + className + "\' (" + Integer.toString(className.length()) + ")"); } @@ -205,7 +210,7 @@ public StandardRealmObjectSchema addField(String fieldName, Class fieldType, public StandardRealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema) { checkLegalName(fieldName); checkFieldNameIsAvailable(fieldName); - table.addColumnLink(RealmFieldType.OBJECT, fieldName, realm.sharedRealm.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); + table.addColumnLink(RealmFieldType.OBJECT, fieldName, realm.sharedRealm.getTable(Table.getTableNameForClass(objectSchema.getClassName()))); return this; } @@ -221,7 +226,7 @@ public StandardRealmObjectSchema addRealmObjectField(String fieldName, RealmObje public StandardRealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema) { checkLegalName(fieldName); checkFieldNameIsAvailable(fieldName); - table.addColumnLink(RealmFieldType.LIST, fieldName, realm.sharedRealm.getTable(Table.TABLE_PREFIX + objectSchema.getClassName())); + table.addColumnLink(RealmFieldType.LIST, fieldName, realm.sharedRealm.getTable(Table.getTableNameForClass(objectSchema.getClassName()))); return this; } @@ -550,6 +555,11 @@ public RealmFieldType getFieldType(String fieldName) { return table.getColumnType(columnIndex); } + @Override + Table getTable() { + return table; + } + @Override StandardRealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { long columnIndex = table.addColumn(type, name, (required) ? Table.NOT_NULLABLE : Table.NULLABLE); @@ -566,86 +576,10 @@ StandardRealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchem table.addColumnLink( type, name, - realm.getSharedRealm().getTable(StandardRealmSchema.TABLE_PREFIX + linkedTo.getClassName())); + realm.getSharedRealm().getTable(Table.getTableNameForClass(linkedTo.getClassName()))); return this; } - /** - * Returns the column indices for the given field name. If a linked field is defined, the column index for - * each field is returned. - * - * @param fieldDescription fieldName or link path to a field name. - * @param validColumnTypes valid field type for the last field in a linked field - * @return list of column indices. - */ - // TODO: consider another caching strategy so linked classes are included in the cache. - @Override - long[] getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes) { - if (fieldDescription == null || fieldDescription.equals("")) { - throw new IllegalArgumentException("Invalid query: field name is empty"); - } - if (fieldDescription.endsWith(".")) { - throw new IllegalArgumentException("Invalid query: field name must not end with a period ('.')"); - } - String[] names = fieldDescription.split("\\."); - - //final RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); - - long[] columnIndices = new long[names.length]; - Table currentTable = table; - RealmFieldType columnType; - String columnName; - String tableName; - for (int i = 0; /* loop exits in the middle */ ; i++) { - columnName = names[i]; - if (columnName.length() <= 0) { - throw new IllegalArgumentException(String.format( - "Invalid query: empty column name in field '%s'. " + - "A field name must not begin with, end with, or contain adjacent periods ('.').", - fieldDescription)); - } - - tableName = getTableName(currentTable); - long index = currentTable.getColumnIndex(columnName); - if (index < 0) { - throw new IllegalArgumentException( - String.format("Invalid query: field '%s' does not exist in table '%s'.", - columnName, tableName)); - } - columnIndices[i] = index; - - columnType = currentTable.getColumnType(index); - - if (i >= names.length - 1) { break; } - - if ((columnType != RealmFieldType.OBJECT) && (columnType != RealmFieldType.LIST)) { - throw new IllegalArgumentException( - String.format("Invalid query: field '%s' in table '%s' is of type '%s'. It must be a LIST or OBJECT type.", - columnName, tableName, columnType.toString())); - } - - currentTable = currentTable.getLinkTarget(index); - } - - if ((validColumnTypes != null) && (validColumnTypes.length > 0) && !isValidType(columnType, validColumnTypes)) { - throw new IllegalArgumentException( - String.format("Invalid query: field '%s' in table '%s' is of invalid type '%s'.", - columnName, tableName, columnType.toString())); - } - - return columnIndices; - } - - /** - * Returns the column index in the underlying table for the given field name. - * - * @param fieldName field name to find index for. - * @return column index or null if it doesn't exists. - */ - Long getFieldIndex(String fieldName) { - return columnIndices.get(fieldName); - } - /** * Returns the column index in the underlying table for the given field name. * @@ -655,15 +589,23 @@ Long getFieldIndex(String fieldName) { */ @Override long getAndCheckFieldIndex(String fieldName) { - Long index = columnIndices.get(fieldName); - if (index == null) { + long index = columnInfo.getColumnIndex(fieldName); + if (index < 0) { throw new IllegalArgumentException("Field does not exist: " + fieldName); } return index; } - private String getTableName(Table table) { - return table.getName().substring(StandardRealmSchema.TABLE_PREFIX.length()); + /** + * Returns the column index in the underlying table for the given field name. + * FOR TESTING USE ONLY! + * + * @param fieldName field name to find index for. + * @return column index or -1 if it doesn't exists. + */ + //@VisibleForTesting(otherwise = VisibleForTesting.NONE) + long getFieldIndex(String fieldName) { + return columnInfo.getColumnIndex(fieldName); } // Invariant: Field was just added. This method is responsible for cleaning up attributes if it fails. @@ -749,81 +691,54 @@ private void checkEmpty(String str) { } } - private boolean isValidType(RealmFieldType columnType, RealmFieldType[] validColumnTypes) { - for (int i = 0; i < validColumnTypes.length; i++) { - if (validColumnTypes[i] == columnType) { - return true; - } - } - return false; - } - - public static final class DynamicColumnMap implements Map { + private static final class DynamicColumnIndices extends ColumnInfo { private final Table table; - DynamicColumnMap(Table table) { + DynamicColumnIndices(Table table) { + super(null, false); this.table = table; } @Override - public Long get(Object key) { - long ret = table.getColumnIndex((String) key); - return ret < 0 ? null : ret; + public long getColumnIndex(String columnName) { + return table.getColumnIndex(columnName); } @Override - public void clear() { - throw new UnsupportedOperationException(); + public RealmFieldType getColumnType(String columnName) { + throw new UnsupportedOperationException("DynamicColumnIndices do not support 'getColumnType'"); } @Override - public boolean containsKey(Object key) { - throw new UnsupportedOperationException(); + public String getLinkedTable(String columnName) { + throw new UnsupportedOperationException("DynamicColumnIndices do not support 'getLinkedTable'"); } @Override - public boolean containsValue(Object value) { - throw new UnsupportedOperationException(); + public void copyFrom(ColumnInfo src) { + throw new UnsupportedOperationException("DynamicColumnIndices cannot be copied"); } @Override - public Set> entrySet() { - throw new UnsupportedOperationException(); + protected ColumnInfo copy(boolean immutable) { + throw new UnsupportedOperationException("DynamicColumnIndices cannot be copied"); } - @Override - public boolean isEmpty() { - throw new UnsupportedOperationException(); - } @Override - public Set keySet() { - throw new UnsupportedOperationException(); - } - - @Override - public Long put(String key, Long value) { - throw new UnsupportedOperationException(); - } - - @Override - public void putAll(Map map) { - throw new UnsupportedOperationException(); - } - - @Override - public Long remove(Object key) { - throw new UnsupportedOperationException(); + protected void copy(ColumnInfo src, ColumnInfo dst) { + throw new UnsupportedOperationException("DynamicColumnIndices cannot copy"); } + } - @Override - public int size() { - throw new UnsupportedOperationException(); - } + // Tuple containing data about each supported Java type. + private static final class FieldMetaData { + final RealmFieldType realmType; + final boolean defaultNullable; - @Override - public Collection values() { - throw new UnsupportedOperationException(); + FieldMetaData(RealmFieldType realmType, boolean defaultNullable) { + this.realmType = realmType; + this.defaultNullable = defaultNullable; } } } diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java index 280309bb0d..63bee2608c 100644 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java @@ -34,9 +34,7 @@ * @see io.realm.RealmMigration */ class StandardRealmSchema extends RealmSchema { - - static final String TABLE_PREFIX = Table.TABLE_PREFIX; - static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; + public static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; // Caches Dynamic Class objects given as Strings to Realm Tables private final Map dynamicClassToTable = new HashMap<>(); @@ -69,12 +67,10 @@ public void close() { } public RealmObjectSchema get(String className) { checkEmpty(className, EMPTY_STRING_MSG); - String internalClassName = TABLE_PREFIX + className; + String internalClassName = Table.getTableNameForClass(className); if (!realm.getSharedRealm().hasTable(internalClassName)) { return null; } - Table table = realm.getSharedRealm().getTable(internalClassName); - StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); - return new StandardRealmObjectSchema(realm, table, columnIndices); + return new StandardRealmObjectSchema(realm, this, table); } /** @@ -91,9 +87,7 @@ public Set getAll() { if (!Table.isModelTable(tableName)) { continue; } - Table table = realm.getSharedRealm().getTable(tableName); - StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); - schemas.add(new StandardRealmObjectSchema(realm, table, columnIndices)); + schemas.add(new StandardRealmObjectSchema(realm, this, realm.getSharedRealm().getTable(tableName))); } return schemas; } @@ -109,16 +103,14 @@ public RealmObjectSchema create(String className) { // Adding a class is always permitted. checkEmpty(className, EMPTY_STRING_MSG); - String internalTableName = TABLE_PREFIX + className; + String internalTableName = Table.getTableNameForClass(className); if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: " + className.length()); } if (realm.getSharedRealm().hasTable(internalTableName)) { throw new IllegalArgumentException("Class already exists: " + className); } - Table table = realm.getSharedRealm().getTable(internalTableName); - StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); - return new StandardRealmObjectSchema(realm, table, columnIndices); + return new StandardRealmObjectSchema(realm, this, realm.getSharedRealm().getTable(internalTableName)); } /** @@ -129,7 +121,7 @@ public RealmObjectSchema create(String className) { */ @Override public boolean contains(String className) { - return realm.getSharedRealm().hasTable(Table.TABLE_PREFIX + className); + return realm.getSharedRealm().hasTable(Table.getTableNameForClass(className)); } /** @@ -142,7 +134,7 @@ public boolean contains(String className) { public void remove(String className) { realm.checkNotInSync(); // Destructive modifications are not permitted. checkEmpty(className, EMPTY_STRING_MSG); - String internalTableName = TABLE_PREFIX + className; + String internalTableName = Table.getTableNameForClass(className); checkHasTable(className, "Cannot remove class because it is not in this Realm: " + className); Table table = getTable(className); if (table.hasPrimaryKey()) { @@ -163,8 +155,8 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { realm.checkNotInSync(); // Destructive modifications are not permitted. checkEmpty(oldClassName, "Class names cannot be empty or null"); checkEmpty(newClassName, "Class names cannot be empty or null"); - String oldInternalName = TABLE_PREFIX + oldClassName; - String newInternalName = TABLE_PREFIX + newClassName; + String oldInternalName = Table.getTableNameForClass(oldClassName); + String newInternalName = Table.getTableNameForClass(newClassName); checkHasTable(oldClassName, "Cannot rename class because it doesn't exist in this Realm: " + oldClassName); if (realm.getSharedRealm().hasTable(newInternalName)) { throw new IllegalArgumentException(oldClassName + " cannot be renamed because the new class already exists: " + newClassName); @@ -186,8 +178,7 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { table.setPrimaryKey(pkField); } - StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); - return new StandardRealmObjectSchema(realm, table, columnIndices); + return new StandardRealmObjectSchema(realm, this, table); } private void checkEmpty(String str, String error) { @@ -197,7 +188,7 @@ private void checkEmpty(String str, String error) { } private void checkHasTable(String className, String errorMsg) { - String internalTableName = TABLE_PREFIX + className; + String internalTableName = Table.getTableNameForClass(className); if (!realm.getSharedRealm().hasTable(internalTableName)) { throw new IllegalArgumentException(errorMsg); } @@ -205,15 +196,15 @@ private void checkHasTable(String className, String errorMsg) { @Override Table getTable(String className) { - className = Table.TABLE_PREFIX + className; - Table table = dynamicClassToTable.get(className); + String tableName = Table.getTableNameForClass(className); + Table table = dynamicClassToTable.get(tableName); if (table != null) { return table; } - if (!realm.getSharedRealm().hasTable(className)) { + if (!realm.getSharedRealm().hasTable(tableName)) { throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); } - table = realm.getSharedRealm().getTable(className); - dynamicClassToTable.put(className, table); + table = realm.getSharedRealm().getTable(tableName); + dynamicClassToTable.put(tableName, table); return table; } @@ -252,28 +243,27 @@ StandardRealmObjectSchema getSchemaForClass(Class clazz) { } if (classSchema == null) { Table table = getTable(clazz); - classSchema = new StandardRealmObjectSchema(realm, table, getColumnInfo(originalClass).getIndicesMap()); + classSchema = new StandardRealmObjectSchema(realm, this, table, getColumnInfo(originalClass)); classToSchema.put(originalClass, classSchema); } if (isProxyClass(originalClass, clazz)) { // 'clazz' is the proxy class for 'originalClass'. classToSchema.put(clazz, classSchema); } + return classSchema; } @Override StandardRealmObjectSchema getSchemaForClass(String className) { - className = Table.TABLE_PREFIX + className; - StandardRealmObjectSchema dynamicSchema = dynamicClassToSchema.get(className); + String tableName = Table.getTableNameForClass(className); + StandardRealmObjectSchema dynamicSchema = dynamicClassToSchema.get(tableName); if (dynamicSchema == null) { - if (!realm.getSharedRealm().hasTable(className)) { + if (!realm.getSharedRealm().hasTable(tableName)) { throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); } - Table table = realm.getSharedRealm().getTable(className); - StandardRealmObjectSchema.DynamicColumnMap columnIndices = new StandardRealmObjectSchema.DynamicColumnMap(table); - dynamicSchema = new StandardRealmObjectSchema(realm, table, columnIndices); - dynamicClassToSchema.put(className, dynamicSchema); + dynamicSchema = new StandardRealmObjectSchema(realm, this, realm.getSharedRealm().getTable(tableName)); + dynamicClassToSchema.put(tableName, dynamicSchema); } return dynamicSchema; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java index 069e5b0004..78bbdb0f18 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java @@ -24,68 +24,152 @@ /** * Utility class used to cache the mapping between object field names and their column indices. + *

                      + * This class can be mutated, after construction, in two ways: + *

                        + *
                      • the {@code copyFrom} method
                      • + *
                      • mutating one of the ColumnInfo object to which this instance holds a reference
                      • + *
                      + * Immutable instances of this class protect against the first possiblity by throwing on calls + * to {@code copyFrom}. {@see ColumnInfo} for its mutability contract. + * + * There are two, redundant, lookup methods, for schema members: by Class and by String. + * Query lookups must be done by the name of the class and use the String-keyed lookup table. + * Although it would be possible to use the same table to look up ColumnInfo by Class, the + * class lookup is very fast and on a hot path, so we maintain the redundant table. */ -public final class ColumnIndices implements Cloneable { +public final class ColumnIndices { + private final Map, ColumnInfo> classes; + private final Map classesByName; + private final boolean mutable; private long schemaVersion; - private Map, ColumnInfo> classes; + /** + * Create a mutable ColumnIndices initialized with the ColumnInfo objects in the passed map. + * + * @param schemaVersion the schema version + * @param classes a map of table classes to their column info + * @throws IllegalArgumentException if any of the ColumnInfo object is immutable. + */ public ColumnIndices(long schemaVersion, Map, ColumnInfo> classes) { + this(schemaVersion, new HashMap<>(classes), true); + for (Map.Entry, ColumnInfo> entry : classes.entrySet()) { + ColumnInfo columnInfo = entry.getValue(); + if (mutable != columnInfo.isMutable()) { + throw new IllegalArgumentException("ColumnInfo mutability does not match ColumnIndices"); + } + this.classesByName.put(entry.getKey().getSimpleName(), entry.getValue()); + } + } + + /** + * Create a copy of the passed ColumnIndices with the specified mutablity. + * + * @param other the ColumnIndices object to copy + * @param mutable if false the object is effectively final. + */ + public ColumnIndices(ColumnIndices other, boolean mutable) { + this(other.schemaVersion, new HashMap, ColumnInfo>(other.classes.size()), mutable); + for (Map.Entry, ColumnInfo> entry : other.classes.entrySet()) { + ColumnInfo columnInfo = entry.getValue().copy(mutable); + this.classes.put(entry.getKey(), columnInfo); + this.classesByName.put(entry.getKey().getSimpleName(), columnInfo); + } + } + + private ColumnIndices(long schemaVersion, Map, ColumnInfo> classes, boolean mutable) { this.schemaVersion = schemaVersion; this.classes = classes; + this.mutable = mutable; + this.classesByName = new HashMap<>(classes.size()); } + /** + * Get the schema version. + * + * @return the schema version. + */ public long getSchemaVersion() { return schemaVersion; } /** - * Returns {@link ColumnInfo} for the given class or {@code null} if no mapping exists. + * Returns the {@link ColumnInfo} for the passed class or ({@code null} if there is no such class). + * + * @param clazz the class for which to get the ColumnInfo. + * @return the corresponding {@link ColumnInfo} object, or {@code null} if not found. */ public ColumnInfo getColumnInfo(Class clazz) { return classes.get(clazz); } /** - * Returns the column index for a given field on a clazz or {@code -1} if no such field exists. + * Returns the {@link ColumnInfo} for the passed class ({@code null} if there is no such class). + * + * @param className the simple name of the class for which to get the ColumnInfo. + * @return the corresponding {@link ColumnInfo} object, or {@code null} if not found. */ + public ColumnInfo getColumnInfo(String className) { + return classesByName.get(className); + } + + /** + * Convenience method to return the column index for a given field on a class + * or {@code -1} if no such field exists. + * + * @param clazz the class to search. + * @param fieldName the name of the field whose index is needed. + * @return the index in clazz of the field fieldName. + * @deprecated Use {@code getColumnInfo().getColumnIndex()} instead. + */ + @Deprecated public long getColumnIndex(Class clazz, String fieldName) { - final ColumnInfo columnInfo = classes.get(clazz); - if (columnInfo != null) { - Long index = columnInfo.getIndicesMap().get(fieldName); - return (index != null) ? index : -1; - } else { + final ColumnInfo columnInfo = getColumnInfo(clazz); + if (columnInfo == null) { return -1; } + return columnInfo.getColumnIndex(fieldName); } - @Override - public ColumnIndices clone() { - try { - final ColumnIndices clone = (ColumnIndices) super.clone(); - clone.classes = duplicateColumnInfoMap(); - return clone; - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e); + /** + * Make this instance contain a (non-strict) subset of the data in the passed ColumnIndices. + * The schemaVersion and every ColumnInfo object held by this instance will be updated to be + * the same the corresponding data in the passed instance or IllegalStateException will be thrown. + * It is allowable for the passed ColumnIndices to contain information this instance does not. + *

                      + * NOTE: copying does not change this instance's mutablity state. + * + * @param src the instance to copy. + * @throws UnsupportedOperationException if this instance is immutable. + * @throws IllegalStateException if this object contains information for a table that the source does not. + */ + public void copyFrom(ColumnIndices src) { + if (!mutable) { + throw new UnsupportedOperationException("Attempt to modify immutable cache"); } - } - - private Map, ColumnInfo> duplicateColumnInfoMap() { - final Map, ColumnInfo> copy = new HashMap, ColumnInfo>(); - for (Map.Entry, ColumnInfo> entry : classes.entrySet()) { - copy.put(entry.getKey(), entry.getValue().clone()); + for (Map.Entry entry : classesByName.entrySet()) { + final ColumnInfo otherColumnInfo = src.classesByName.get(entry.getKey()); + if (otherColumnInfo == null) { + throw new IllegalStateException("Failed to copy ColumnIndices cache for class: " + entry.getKey()); + } + entry.getValue().copyFrom(otherColumnInfo); } - return copy; + this.schemaVersion = src.schemaVersion; } - public void copyFrom(ColumnIndices other, RealmProxyMediator mediator) { - for (Map.Entry, ColumnInfo> entry : classes.entrySet()) { - final ColumnInfo otherColumnInfo = other.getColumnInfo(entry.getKey()); - if (otherColumnInfo == null) { - throw new IllegalStateException("Failed to copy ColumnIndices cache: " - + Table.tableNameToClassName(mediator.getTableName(entry.getKey()))); + @Override + public String toString() { + StringBuilder buf = new StringBuilder("ColumnIndices["); + buf.append(schemaVersion).append(","); + buf.append(mutable).append(","); + if (classes != null) { + boolean commaNeeded = false; + for (Map.Entry, ColumnInfo> entry : classes.entrySet()) { + if (commaNeeded) { buf.append(","); } + buf.append(entry.getKey().getSimpleName()).append("->").append(entry.getValue()); + commaNeeded = true; } - entry.getValue().copyColumnInfoFrom(otherColumnInfo); } - this.schemaVersion = other.schemaVersion; + return buf.append("]").toString(); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java index fe39118f01..c612bb6744 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java @@ -16,60 +16,249 @@ package io.realm.internal; +import java.util.HashMap; import java.util.Map; -import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.RealmFieldType; -public abstract class ColumnInfo implements Cloneable { - private Map indicesMap; +/** + * Objects of this class play two roles: + *

                        + *
                      • Subclasses are a fast cache of column indices, for proxy object
                      • + *
                      • They cache table (schema) information used by for StandardRealmObjectSchema and StandardRealmSchema
                      • + *
                      + * The fast cache functionality is implemented in the Proxy classes generated by {@code RealmProxyClassGenerator}. + * Be sure to understand what is going on there, before changing things here. + *

                      + * While the use of the fields in {@code ColumnDetails} is consistent, there are three subtly different cases: + *

                        + *
                      • If the column type is a simple type, the link table field is empty (0L / NULLPTR)
                      • + *
                      • If the column type is OBJECT or LINK, the link table field is the class name of the OBJECT/LINK type
                      • + *
                      • If the column type is LINKING_OBJECT, the link table field is the class name of the backlink source table + * and the column index field is the index of the backlink source field, in the source table
                      • + *
                      + * + *

                      + * Some instances of this class must be thread-safe. The class support effectively-final semantics. + * An instance can be mutated, after construction, in four ways: + *

                        + *
                      • the {@code copyFrom} method
                      • + *
                      • as the dst parameter of the two-argument copy method
                      • + *
                      • using the {@code addColumnDetails} method
                      • + *
                      • using the {@code addBacklinkDetails} method
                      • + *
                      + * Immutable instances of this class protect against the first possibility by throwing on calls + * to {@code copyFrom}. There are no checks against the other three mutations. In order to comply + * with the effectively-final contract: + *
                        + *
                      • the methods {@code addColumnDetails} and {@code addBacklinkDetails} must be called + * only from within instance constructors
                      • + *
                      • an immutable instance must never be the dst parameter of the two-argument copy method
                      • + *
                      + */ +public abstract class ColumnInfo { + + // Immutable column information + private static final class ColumnDetails { + public final long columnIndex; + public final RealmFieldType columnType; + public final String linkTable; - protected final long getValidColumnIndex(String realmPath, Table table, - String className, String columnName) { - final long columnIndex = table.getColumnIndex(columnName); - if (columnIndex == -1) { - throw new RealmMigrationNeededException(realmPath, - "Field '" + columnName + "' not found for type " + className); + ColumnDetails(long columnIndex, RealmFieldType columnType, String srcTable) { + this.columnIndex = columnIndex; + this.columnType = columnType; + this.linkTable = srcTable; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder("ColumnDetails["); + buf.append(columnIndex); + buf.append(", ").append(columnType); + buf.append(", ").append(linkTable); + return buf.append("]").toString(); } - return columnIndex; } + + private final Map indicesMap; + private final boolean mutable; + /** - * Returns a map from column name to column index. + * Create a new, empty instance * - * @return a map from column name to column index. Do not modify returned map because it may be - * shared among other {@link ColumnInfo} instances. + * @param mapSize the expected number of columns in the map. */ - public Map getIndicesMap() { - return indicesMap; + protected ColumnInfo(int mapSize) { + this(mapSize, true); } - protected final void setIndicesMap(Map indicesMap) { - this.indicesMap = indicesMap; + /** + * Create an exact copy of the passed instance. + * + * @param src the instance to copy + * @param mutable false to make this instance effectively final + */ + protected ColumnInfo(ColumnInfo src, boolean mutable) { + this((src == null) ? 0 : src.indicesMap.size(), mutable); + // ColumnDetails are immutable and may be re-used. + if (src != null) { + indicesMap.putAll(src.indicesMap); + } + } + + private ColumnInfo(int mapSize, boolean mutable) { + this.indicesMap = new HashMap<>(mapSize); + this.mutable = mutable; } /** - * Copies the column index value from other {@link ColumnInfo} object. + * Get the mutability state of the instance. * - * @param other the class of {@code other} must be exactly the same as this instance. - * It must not be {@code null}. - * @throws IllegalArgumentException if {@code other} has different class than this. + * @return true if the instance is mutable */ - public abstract void copyColumnInfoFrom(ColumnInfo other); + public final boolean isMutable() { + return mutable; + } /** - * Returns a shallow copy of this instance. + * Returns the index, in the described table, for the named column. * - * @return shallow copy. + * @return column index. + */ + public long getColumnIndex(String columnName) { + ColumnDetails details = indicesMap.get(columnName); + return (details == null) ? -1 : details.columnIndex; + } + + /** + * Returns the Realm Type, in the described table, of the named column. + * + * @return column Realm Type. + */ + public RealmFieldType getColumnType(String columnName) { + ColumnDetails details = indicesMap.get(columnName); + return (details == null) ? RealmFieldType.UNSUPPORTED_TABLE : details.columnType; + } + + /** + * Returns the table linked in the described table, to the named column. + * + * @return the class name of the linked table, or null if the column is a primitive type. + */ + public String getLinkedTable(String columnName) { + ColumnDetails details = indicesMap.get(columnName); + return (details == null) ? null : details.linkTable; + } + + /** + * Makes this ColumnInfo an exact copy of {@code src}. + * + * @param src The source for the copy. This instance will be an exact copy of {@code src} after return. + * {@code src} must not be {@code null}. + * @throws IllegalArgumentException if {@code other} has different class than this. */ + public void copyFrom(ColumnInfo src) { + if (!mutable) { + throw new UnsupportedOperationException("Attempt to modify an immutable ColumnInfo"); + } + if (null == src) { + throw new NullPointerException("Attempt to copy null ColumnInfo"); + } + + indicesMap.clear(); + indicesMap.putAll(src.indicesMap); + copy(src, this); + } + @Override - public ColumnInfo clone() { - try { - return (ColumnInfo) super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e); + public String toString() { + StringBuilder buf = new StringBuilder("ColumnInfo["); + buf.append(mutable).append(","); + if (indicesMap != null) { + boolean commaNeeded = false; + for (Map.Entry entry : indicesMap.entrySet()) { + if (commaNeeded) { buf.append(","); } + buf.append(entry.getKey()).append("->").append(entry.getValue()); + commaNeeded = true; + } + } + return buf.append("]").toString(); + } + + /** + * Create a new object that is an exact copy of {@code src}. + * This is the generic factory for ColumnInfo objects. + * Subclasses are expected to override it with a proxy to a copy constructor. + * + * @param mutable false to make an immutable copy. + */ + protected abstract ColumnInfo copy(boolean mutable); + + /** + * Make {@code dst} into an exact copy of {@code src}. + * Intended for use only by subclasses. + * NOTE: there is no protection against calling this method with an "immutable" instance as dst! + * + * @param src The source for the copy + * @param dst The destination of the copy. Will be an exact copy of src after return. + */ + protected abstract void copy(ColumnInfo src, ColumnInfo dst); + + /** + * Add a new column to the indexMap. + *

                      + * For use only in subclass constructors!. + * Must be called from within the subclass constructor, to maintain the effectively-final contract. + *

                      + * No validation done here. Presuming that all necessary validation takes place in {@code Proxy.validateTable}. + * + * @param table The table to search for the column. + * @param columnName The name of the column whose index is sought. + * @param columnType Type RealmType of the column. + * @return the index of the column in the table + */ + @SuppressWarnings("unused") + protected final long addColumnDetails(Table table, String columnName, RealmFieldType columnType) { + long columnIndex = table.getColumnIndex(columnName); + if (columnIndex >= 0) { + String linkedTableName = ((columnType != RealmFieldType.OBJECT) && (columnType != RealmFieldType.LIST)) + ? null + : table.getLinkTarget(columnIndex).getClassName(); + + indicesMap.put(columnName, new ColumnDetails(columnIndex, columnType, linkedTableName)); } + + return columnIndex; + } + + /** + * Add a new backlink to the indexMap. + * For use only by subclasses!. + * Must be called from within the subclass constructor, to maintain the effectively-final contract. + * + * @param realm The shared realm. + * @param columnName The name of the backlink column. + * @param sourceTableName The name of the backlink source class. + * @param sourceColumnName The name of the backlink source field. + */ + @SuppressWarnings("unused") + protected final void addBacklinkDetails(SharedRealm realm, String columnName, String sourceTableName, String sourceColumnName) { +// Table sourceTable = realm.getTable(Table.getTableNameForClass(sourceTableName)); +// long columnIndex = sourceTable.getColumnIndex(sourceColumnName); +// indicesMap.put(columnName, new ColumnDetails(columnIndex, RealmFieldType.LINKING_OBJECTS, sourceTableName)); } - ; + /** + * Returns the {@link Map} that is the implementation for this object. + * FOR TESTING USE ONLY! + * + * @return the column details map. + */ + @SuppressWarnings("ReturnOfCollectionOrArrayField") + //@VisibleForTesting(otherwise = VisibleForTesting.NONE) + public Map getIndicesMap() { + return indicesMap; + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java deleted file mode 100644 index 13d815aa42..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/FieldDescriptor.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ -package io.realm.internal; - -import java.util.Arrays; - -import io.realm.RealmFieldType; - - -/** - * Class describing a single field possible several links away. - */ -public class FieldDescriptor { - - private long[] columnIndices; - private RealmFieldType fieldType; - private String fieldName; - private boolean searchIndex; - - public FieldDescriptor(Table table, String fieldDescription, boolean allowLink, boolean allowList) { - if (fieldDescription == null || fieldDescription.isEmpty()) { - throw new IllegalArgumentException("Non-empty field name must be provided"); - } - if (fieldDescription.startsWith(".") || fieldDescription.endsWith(".")) { - throw new IllegalArgumentException("Illegal field name. It cannot start or end with a '.': " + fieldDescription); - } - if (fieldDescription.contains(".")) { - // Resolves field description down to last field name - String[] names = fieldDescription.split("\\."); - long[] columnIndices = new long[names.length]; - for (int i = 0; i < names.length - 1; i++) { - long index = table.getColumnIndex(names[i]); - if (index == Table.NO_MATCH) { - throw new IllegalArgumentException( - String.format("Invalid field name: '%s' does not refer to a class.", names[i])); - } - RealmFieldType type = table.getColumnType(index); - if (!allowLink && type == RealmFieldType.OBJECT) { - throw new IllegalArgumentException( - String.format("'RealmObject' field '%s' is not a supported link field here.", names[i])); - } else if (!allowList && type == RealmFieldType.LIST) { - throw new IllegalArgumentException( - String.format("'RealmList' field '%s' is not a supported link field here.", names[i])); - } else if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { - table = table.getLinkTarget(index); - columnIndices[i] = index; - } else { - throw new IllegalArgumentException( - String.format("Invalid field name: '%s' does not refer to a class.", names[i])); - } - } - - // Check if last field name is a valid field - String columnName = names[names.length - 1]; - long columnIndex = table.getColumnIndex(columnName); - columnIndices[names.length - 1] = columnIndex; - if (columnIndex == Table.NO_MATCH) { - throw new IllegalArgumentException( - String.format("'%s' is not a field name in class '%s'.", columnName, table.getName())); - } - - this.fieldType = table.getColumnType(columnIndex); - this.fieldName = columnName; - this.columnIndices = columnIndices; - this.searchIndex = table.hasSearchIndex(columnIndex); - } else { - long fieldIndex = table.getColumnIndex(fieldDescription); - if (fieldIndex == Table.NO_MATCH) { - throw new IllegalArgumentException(String.format("Field '%s' does not exist.", fieldDescription)); - } - this.fieldType = table.getColumnType(fieldIndex); - this.fieldName = fieldDescription; - this.columnIndices = new long[] {fieldIndex}; - this.searchIndex = table.hasSearchIndex(fieldIndex); - } - } - - public long[] getColumnIndices() { - return Arrays.copyOf(columnIndices, columnIndices.length); - } - - public RealmFieldType getFieldType() { - return fieldType; - } - - public String getFieldName() { - return fieldName; - } - - public boolean hasSearchIndex() { - return searchIndex; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java b/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java index f559678b76..be40d7ac3f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java +++ b/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java @@ -27,10 +27,10 @@ // test_destructor_thread_safety.cpp. Explicit call of SharedGroup::close() or Table::detach() is also not thread-safe // with respect to destruction of other accessors. public class NativeContext { - private final static ReferenceQueue referenceQueue = new ReferenceQueue(); - private final static Thread finalizingThread = new Thread(new FinalizerRunnable(referenceQueue)); + private static final ReferenceQueue referenceQueue = new ReferenceQueue(); + private static final Thread finalizingThread = new Thread(new FinalizerRunnable(referenceQueue)); // Dummy context which will be used by native objects which's destructors are always thread safe. - final static NativeContext dummyContext = new NativeContext(); + static final NativeContext dummyContext = new NativeContext(); static { finalizingThread.setName("RealmFinalizingDaemon"); diff --git a/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java b/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java index ce326dbf9b..ac8f32d2c4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/NativeObject.java @@ -21,7 +21,9 @@ * It specifies the operations common to all such objects. * All Java classes wrapping a core class should implement NativeObject. */ -interface NativeObject { +public interface NativeObject { + long NULLPTR = 0L; + /** * Gets the pointer of a native object. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 70acf9617e..a5aab3136a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -65,7 +65,7 @@ public static File getTemporaryDirectory() { return temporaryDirectory; } - private volatile static File temporaryDirectory; + private static volatile File temporaryDirectory; public enum Durability { FULL(0), @@ -175,12 +175,13 @@ public interface SchemaVersionListener { void onSchemaVersionChanged(long currentVersion); } + private final SchemaVersionListener schemaChangeListener; private final RealmConfiguration configuration; + private final long nativePtr; - final private long nativePtr; - final NativeContext context; private long lastSchemaVersion; - private final SchemaVersionListener schemaChangeListener; + + final NativeContext context; private SharedRealm(long nativeConfigPtr, RealmConfiguration configuration, @@ -342,9 +343,17 @@ public boolean compact() { /** * Updates the underlying schema based on the schema description. * Calling this method must be done from inside a write transaction. + *

                      + * TODO: This method should not require the caller to get the native pointer. + * Instead, the signature should be something like: + * public void updateSchema(T schema, long version) + * ... that is: something that is a schema and that wraps a native object. + * + * @param schemaNativePtr the pointer to a native schema object. + * @param version the target version. */ - public void updateSchema(long schemaNativePointer, long version) { - nativeUpdateSchema(nativePtr, schemaNativePointer, version); + public void updateSchema(long schemaNativePtr, long version) { + nativeUpdateSchema(nativePtr, schemaNativePtr, version); } public void setAutoRefresh(boolean enabled) { @@ -356,8 +365,19 @@ public boolean isAutoRefresh() { return nativeIsAutoRefresh(nativePtr); } - public boolean requiresMigration(long schemaNativePointer) { - return nativeRequiresMigration(nativePtr, schemaNativePointer); + /** + * Determine whether the passed schema needs to be updated. + *

                      + * TODO: This method should not require the caller to get the native pointer. + * Instead, the signature should be something like: + * public void updateSchema(T schema, long version) + * ... that is, something that is a schema and that wraps a native object. + * + * @param schemaNativePtr the pointer to a native schema object. + * @return true if it will be necessary to call {@code updateSchema} + */ + public boolean requiresMigration(long schemaNativePtr) { + return nativeRequiresMigration(nativePtr, schemaNativePtr); } @Override @@ -433,7 +453,7 @@ void invalidateIterators() { // calling the Object Store begin_transaction to avoid the problem. // Add pending row to the list when it is created. It should be called in the PendingRow constructor. void addPendingRow(PendingRow pendingRow) { - pendingRows.add(new WeakReference(pendingRow)); + pendingRows.add(new WeakReference(pendingRow)); } // Remove pending row from the list. It should be called when pending row's query finished. diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java index 3df785ef92..273d3ef411 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java @@ -17,10 +17,13 @@ package io.realm.internal; import java.util.Arrays; -import java.util.List; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import io.realm.RealmFieldType; import io.realm.Sort; +import io.realm.internal.fields.FieldDescriptor; /** @@ -30,107 +33,109 @@ * NOTE: Since the column indices are determined when constructing the object with the given table's status, the indices * could be wrong when schema changes. Always create and consume the instance when needed, DON'T store a SortDescriptor * and use it whenever the ShareGroup can be in different versions. + *

                      + * Sort descriptors do not support Linking Objects, either internally or as terminal types. */ @KeepMember public class SortDescriptor { - - private final long[][] columnIndices; - private final boolean[] ascendings; - private final Table table; - - final static List validFieldTypesForSort = Arrays.asList( + //@VisibleForTesting + final static Set SORT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, - RealmFieldType.STRING, RealmFieldType.DATE); - final static List validFieldTypesForDistinct = Arrays.asList( - RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE); - - // Internal use only. For JNI testing. - SortDescriptor(Table table, long[] columnIndices) { - this(table, new long[][] {columnIndices}, null); - } - - private SortDescriptor(Table table, long[][] columnIndices, Sort[] sortOrders) { - if (sortOrders != null) { - ascendings = new boolean[sortOrders.length]; - for (int i = 0; i < sortOrders.length; i++) { - ascendings[i] = sortOrders[i].getValue(); - } - } else { - ascendings = null; - } + RealmFieldType.STRING, RealmFieldType.DATE))); - this.columnIndices = columnIndices; - this.table = table; - } + //@VisibleForTesting + final static Set DISTINCT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE))); - public static SortDescriptor getInstanceForSort(Table table, String fieldDescription, Sort sortOrder) { - return getInstanceForSort(table, new String[] {fieldDescription}, new Sort[] {sortOrder}); + public static SortDescriptor getInstanceForSort(FieldDescriptor.SchemaProxy proxy, Table table, String fieldDescription, Sort sortOrder) { + return getInstanceForSort(proxy, table, new String[] {fieldDescription}, new Sort[] {sortOrder}); } - public static SortDescriptor getInstanceForSort(Table table, String[] fieldDescriptions, Sort[] sortOrders) { - if (fieldDescriptions == null || fieldDescriptions.length == 0) { - throw new IllegalArgumentException("You must provide at least one field name."); - } + public static SortDescriptor getInstanceForSort(FieldDescriptor.SchemaProxy proxy, Table table, String[] fieldDescriptions, Sort[] sortOrders) { if (sortOrders == null || sortOrders.length == 0) { throw new IllegalArgumentException("You must provide at least one sort order."); } if (fieldDescriptions.length != sortOrders.length) { throw new IllegalArgumentException("Number of fields and sort orders do not match."); } + return getInstance(proxy, table, fieldDescriptions, sortOrders, FieldDescriptor.OBJECT_LINK_FIELD_TYPE, SORT_VALID_FIELD_TYPES, "Sort is not supported"); + } - long[][] columnIndices = new long[fieldDescriptions.length][]; - for (int i = 0; i < fieldDescriptions.length; i++) { - FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], true, false); - checkFieldTypeForSort(descriptor, fieldDescriptions[i]); - columnIndices[i] = descriptor.getColumnIndices(); - } - - return new SortDescriptor(table, columnIndices, sortOrders); + public static SortDescriptor getInstanceForDistinct(FieldDescriptor.SchemaProxy proxy, Table table, String fieldDescription) { + return getInstanceForDistinct(proxy, table, new String[] {fieldDescription}); } - public static SortDescriptor getInstanceForDistinct(Table table, String fieldDescription) { - return getInstanceForDistinct(table, new String[] {fieldDescription}); + public static SortDescriptor getInstanceForDistinct(FieldDescriptor.SchemaProxy proxy, Table table, String[] fieldDescriptions) { + return getInstance(proxy, table, fieldDescriptions, null, FieldDescriptor.NO_LINK_FIELD_TYPE, DISTINCT_VALID_FIELD_TYPES, "Distinct is not supported"); } - public static SortDescriptor getInstanceForDistinct(Table table, String[] fieldDescriptions) { + static SortDescriptor getInstance( + FieldDescriptor.SchemaProxy proxy, + Table table, + String[] fieldDescriptions, + Sort[] sortOrders, + Set legalInternalTypes, + Set legalTerminalTypes, + String message) { + if (fieldDescriptions == null || fieldDescriptions.length == 0) { throw new IllegalArgumentException("You must provide at least one field name."); } long[][] columnIndices = new long[fieldDescriptions.length][]; + + // Force aggressive parsing of the FieldDescriptors, so that only valid SortDescriptor objects are created. for (int i = 0; i < fieldDescriptions.length; i++) { - FieldDescriptor descriptor = new FieldDescriptor(table, fieldDescriptions[i], false, false); - checkFieldTypeForDistinct(descriptor, fieldDescriptions[i]); + FieldDescriptor descriptor = FieldDescriptor.createFieldDescriptor(proxy, table, fieldDescriptions[i], legalInternalTypes, null); + checkFieldType(descriptor, legalTerminalTypes, message, fieldDescriptions[i]); columnIndices[i] = descriptor.getColumnIndices(); } - return new SortDescriptor(table, columnIndices, null); + return new SortDescriptor(table, columnIndices, sortOrders); + } + + // Internal use only. For JNI testing. + //@VisibleForTesting + static SortDescriptor getTestInstance(Table table, long[] columnIndices) { + return new SortDescriptor(table, new long[][] {columnIndices}, null); } - private static void checkFieldTypeForSort(FieldDescriptor descriptor, String fieldDescriptions) { - if (!validFieldTypesForSort.contains(descriptor.getFieldType())) { + // could do this in the field descriptor, but this provides a better error message + private static void checkFieldType(FieldDescriptor descriptor, Set legalTerminalTypes, String message, String fieldDescriptions) { + if (!legalTerminalTypes.contains(descriptor.getFinalColumnType())) { throw new IllegalArgumentException(String.format( - "Sort is not supported on '%s' field '%s' in '%s'.", descriptor.toString(), descriptor.getFieldName(), - fieldDescriptions)); + "%s on '%s' field '%s' in '%s'.", message, descriptor.getFinalColumnType(), descriptor.getFinalColumnName(), fieldDescriptions)); } } - private static void checkFieldTypeForDistinct(FieldDescriptor descriptor, String fieldDescriptions) { - if (!validFieldTypesForDistinct.contains(descriptor.getFieldType())) { - throw new IllegalArgumentException(String.format( - "Distinct is not supported on '%s' field '%s' in '%s'.", - descriptor.getFieldType().toString(), descriptor.getFieldName(), fieldDescriptions)); + + private final Table table; + private final long[][] columnIndices; + private final boolean[] ascendings; + + private SortDescriptor(Table table, long[][] columnIndices, Sort[] sortOrders) { + this.table = table; + this.columnIndices = columnIndices; + if (sortOrders != null) { + ascendings = new boolean[sortOrders.length]; + for (int i = 0; i < sortOrders.length; i++) { + ascendings[i] = sortOrders[i].getValue(); + } + } else { + ascendings = null; } } // Called by JNI. @KeepMember + @SuppressWarnings("unused") long[][] getColumnIndices() { return columnIndices; } // Called by JNI. @KeepMember + @SuppressWarnings("unused") boolean[] getAscendings() { return ascendings; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 67e744ca3e..53bde14879 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -45,12 +45,12 @@ enum PivotType { } public static final int TABLE_MAX_LENGTH = 56; // Max length of class names without prefix - public static final String TABLE_PREFIX = Util.getTablePrefix(); public static final long INFINITE = -1; public static final boolean NULLABLE = true; public static final boolean NOT_NULLABLE = false; public static final int NO_MATCH = -1; + private static final String TABLE_PREFIX = Util.getTablePrefix(); private static final String PRIMARY_KEY_TABLE_NAME = "pk"; private static final String PRIMARY_KEY_CLASS_COLUMN_NAME = "pk_table"; private static final long PRIMARY_KEY_CLASS_COLUMN_INDEX = 0; @@ -58,9 +58,11 @@ enum PivotType { private static final long PRIMARY_KEY_FIELD_COLUMN_INDEX = 1; private static final long NO_PRIMARY_KEY = -2; - private long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - final NativeContext context; + + private final long nativePtr; + private final NativeContext context; + private final SharedRealm sharedRealm; private long cachedPrimaryKeyColumnIndex = NO_MATCH; @@ -102,8 +104,8 @@ public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } - public long getNativeTablePointer() { - return nativePtr; + public Table getTable() { + return this; } /* @@ -217,13 +219,12 @@ public void renameColumn(long columnIndex, String newName) { // Renames a primary key. At this point, renaming the column name should have been fine. if (oldPkColumnIndex == columnIndex) { try { - String className = tableNameToClassName(getName()); Table pkTable = getPrimaryKeyTable(); if (pkTable == null) { throw new IllegalStateException( "Table is not created from a SharedRealm, primary key is not available"); } - long pkRowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, className); + long pkRowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, getClassName()); if (pkRowIndex != NO_MATCH) { nativeSetString(pkTable.nativePtr, PRIMARY_KEY_FIELD_COLUMN_INDEX, pkRowIndex, newName, false); } else { @@ -483,7 +484,7 @@ protected long add(Object... values) { ") does not match the number of columns in the table (" + String.valueOf(columns) + ")."); } - RealmFieldType colTypes[] = new RealmFieldType[columns]; + RealmFieldType[] colTypes = new RealmFieldType[columns]; for (int columnIndex = 0; columnIndex < columns; columnIndex++) { Object value = values[columnIndex]; RealmFieldType colType = getColumnType(columnIndex); @@ -570,8 +571,7 @@ public long getPrimaryKey() { return NO_PRIMARY_KEY; // Free table = No primary key. } - String className = tableNameToClassName(getName()); - long rowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, className); + long rowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, getClassName()); if (rowIndex != NO_MATCH) { String pkColumnName = pkTable.getUncheckedRow(rowIndex).getString(PRIMARY_KEY_FIELD_COLUMN_INDEX); cachedPrimaryKeyColumnIndex = getColumnIndex(pkColumnName); @@ -1013,6 +1013,15 @@ public String getName() { return nativeGetName(nativePtr); } + /** + * Returns the class name for the table. + * + * @return Name of the the table or null if it not part of a group. + */ + public String getClassName() { + return getClassNameForTable(getName()); + } + public String toJson() { return nativeToJson(nativePtr); } @@ -1084,11 +1093,20 @@ public long getVersion() { return nativeVersion(nativePtr); } - public static String tableNameToClassName(String tableName) { - if (!tableName.startsWith(Table.TABLE_PREFIX)) { - return tableName; + public static String getClassNameForTable(String name) { + if (name == null) { return null; } + if (!name.startsWith(TABLE_PREFIX)) { + return name; + } + return name.substring(TABLE_PREFIX.length()); + } + + public static String getTableNameForClass(String name) { + if (name == null) { return null; } + if (name.startsWith(TABLE_PREFIX)) { + return name; } - return tableName.substring(Table.TABLE_PREFIX.length()); + return TABLE_PREFIX + name; } protected native long createNative(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 8d0be81e47..f29688f891 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -20,16 +20,21 @@ import io.realm.Case; import io.realm.Sort; +import io.realm.log.RealmLog; public class TableQuery implements NativeObject { - protected boolean DEBUG = false; + private static final boolean DEBUG = false; - protected long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - protected final Table table; + + // See documentation in that NativeContext for an explanation of how this is used + @SuppressWarnings("unused") private final NativeContext context; + private final Table table; + private final long nativePtr; + // All actions (find(), findAll(), sum(), etc.) must call validateQuery() before performing // the actual action. The other methods must set queryValidated to false in order to enforce // the first action to validate the syntax of the query. @@ -38,7 +43,7 @@ public class TableQuery implements NativeObject { // TODO: Can we protect this? public TableQuery(NativeContext context, Table table, long nativeQueryPtr) { if (DEBUG) { - System.err.println("++++++ new TableQuery, ptr= " + nativeQueryPtr); + RealmLog.debug("New TableQuery: ptr=%x", nativeQueryPtr); } this.context = context; this.table = table; @@ -100,38 +105,38 @@ public TableQuery not() { // Queries for integer values. - public TableQuery equalTo(long[] columnIndexes, long value) { - nativeEqual(nativePtr, columnIndexes, value); + public TableQuery equalTo(long[] columnIndexes, long[] tablePtrs, long value) { + nativeEqual(nativePtr, columnIndexes, tablePtrs, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndex, long value) { - nativeNotEqual(nativePtr, columnIndex, value); + public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, long value) { + nativeNotEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThan(long[] columnIndex, long value) { - nativeGreater(nativePtr, columnIndex, value); + public TableQuery greaterThan(long[] columnIndex, long[] tablePtrs, long value) { + nativeGreater(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long[] columnIndex, long value) { - nativeGreaterEqual(nativePtr, columnIndex, value); + public TableQuery greaterThanOrEqual(long[] columnIndex, long[] tablePtrs, long value) { + nativeGreaterEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThan(long[] columnIndex, long value) { - nativeLess(nativePtr, columnIndex, value); + public TableQuery lessThan(long[] columnIndex, long[] tablePtrs, long value) { + nativeLess(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long[] columnIndex, long value) { - nativeLessEqual(nativePtr, columnIndex, value); + public TableQuery lessThanOrEqual(long[] columnIndex, long[] tablePtrs, long value) { + nativeLessEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } @@ -144,38 +149,38 @@ public TableQuery between(long[] columnIndex, long value1, long value2) { // Queries for float values. - public TableQuery equalTo(long[] columnIndex, float value) { - nativeEqual(nativePtr, columnIndex, value); + public TableQuery equalTo(long[] columnIndex, long[] tablePtrs, float value) { + nativeEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndex, float value) { - nativeNotEqual(nativePtr, columnIndex, value); + public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, float value) { + nativeNotEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThan(long[] columnIndex, float value) { - nativeGreater(nativePtr, columnIndex, value); + public TableQuery greaterThan(long[] columnIndex, long[] tablePtrs, float value) { + nativeGreater(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long[] columnIndex, float value) { - nativeGreaterEqual(nativePtr, columnIndex, value); + public TableQuery greaterThanOrEqual(long[] columnIndex, long[] tablePtrs, float value) { + nativeGreaterEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThan(long[] columnIndex, float value) { - nativeLess(nativePtr, columnIndex, value); + public TableQuery lessThan(long[] columnIndex, long[] tablePtrs, float value) { + nativeLess(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long[] columnIndex, float value) { - nativeLessEqual(nativePtr, columnIndex, value); + public TableQuery lessThanOrEqual(long[] columnIndex, long[] tablePtrs, float value) { + nativeLessEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } @@ -188,38 +193,38 @@ public TableQuery between(long[] columnIndex, float value1, float value2) { // Queries for double values. - public TableQuery equalTo(long[] columnIndex, double value) { - nativeEqual(nativePtr, columnIndex, value); + public TableQuery equalTo(long[] columnIndex, long[] tablePtrs, double value) { + nativeEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndex, double value) { - nativeNotEqual(nativePtr, columnIndex, value); + public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, double value) { + nativeNotEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThan(long[] columnIndex, double value) { - nativeGreater(nativePtr, columnIndex, value); + public TableQuery greaterThan(long[] columnIndex, long[] tablePtrs, double value) { + nativeGreater(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long[] columnIndex, double value) { - nativeGreaterEqual(nativePtr, columnIndex, value); + public TableQuery greaterThanOrEqual(long[] columnIndex, long[] tablePtrs, double value) { + nativeGreaterEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThan(long[] columnIndex, double value) { - nativeLess(nativePtr, columnIndex, value); + public TableQuery lessThan(long[] columnIndex, long[] tablePtrs, double value) { + nativeLess(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long[] columnIndex, double value) { - nativeLessEqual(nativePtr, columnIndex, value); + public TableQuery lessThanOrEqual(long[] columnIndex, long[] tablePtrs, double value) { + nativeLessEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } @@ -232,8 +237,8 @@ public TableQuery between(long[] columnIndex, double value1, double value2) { // Query for boolean values. - public TableQuery equalTo(long[] columnIndex, boolean value) { - nativeEqual(nativePtr, columnIndex, value); + public TableQuery equalTo(long[] columnIndex, long[] tablePtrs, boolean value) { + nativeEqual(nativePtr, columnIndex, tablePtrs, value); queryValidated = false; return this; } @@ -242,47 +247,47 @@ public TableQuery equalTo(long[] columnIndex, boolean value) { private static final String DATE_NULL_ERROR_MESSAGE = "Date value in query criteria must not be null."; - public TableQuery equalTo(long[] columnIndex, Date value) { + public TableQuery equalTo(long[] columnIndex, long[] tablePtrs, Date value) { if (value == null) { - nativeIsNull(nativePtr, columnIndex); + nativeIsNull(nativePtr, columnIndex, tablePtrs); } else { - nativeEqualTimestamp(nativePtr, columnIndex, value.getTime()); + nativeEqualTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); } queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndex, Date value) { + public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeNotEqualTimestamp(nativePtr, columnIndex, value.getTime()); + nativeNotEqualTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); queryValidated = false; return this; } - public TableQuery greaterThan(long[] columnIndex, Date value) { + public TableQuery greaterThan(long[] columnIndex, long[] tablePtrs, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeGreaterTimestamp(nativePtr, columnIndex, value.getTime()); + nativeGreaterTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long[] columnIndex, Date value) { + public TableQuery greaterThanOrEqual(long[] columnIndex, long[] tablePtrs, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeGreaterEqualTimestamp(nativePtr, columnIndex, value.getTime()); + nativeGreaterEqualTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); queryValidated = false; return this; } - public TableQuery lessThan(long[] columnIndex, Date value) { + public TableQuery lessThan(long[] columnIndex, long[] tablePtrs, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeLessTimestamp(nativePtr, columnIndex, value.getTime()); + nativeLessTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long[] columnIndex, Date value) { + public TableQuery lessThanOrEqual(long[] columnIndex, long[] tablePtrs, Date value) { if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeLessEqualTimestamp(nativePtr, columnIndex, value.getTime()); + nativeLessEqualTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); queryValidated = false; return this; } @@ -298,104 +303,100 @@ public TableQuery between(long[] columnIndex, Date value1, Date value2) { // Queries for Binary values. - public TableQuery equalTo(long[] columnIndices, byte[] value) { - nativeEqual(nativePtr, columnIndices, value); + public TableQuery equalTo(long[] columnIndices, long[] tablePtrs, byte[] value) { + nativeEqual(nativePtr, columnIndices, tablePtrs, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndices, byte[] value) { - nativeNotEqual(nativePtr, columnIndices, value); + public TableQuery notEqualTo(long[] columnIndices, long[] tablePtrs, byte[] value) { + nativeNotEqual(nativePtr, columnIndices, tablePtrs, value); queryValidated = false; return this; } - // Query for String values. - - private static final String STRING_NULL_ERROR_MESSAGE = "String value in query criteria must not be null."; - // Equals - public TableQuery equalTo(long[] columnIndexes, String value, Case caseSensitive) { - nativeEqual(nativePtr, columnIndexes, value, caseSensitive.getValue()); + public TableQuery equalTo(long[] columnIndexes, long[] tablePtrs, String value, Case caseSensitive) { + nativeEqual(nativePtr, columnIndexes, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery equalTo(long[] columnIndexes, String value) { - nativeEqual(nativePtr, columnIndexes, value, true); + public TableQuery equalTo(long[] columnIndexes, long[] tablePtrs, String value) { + nativeEqual(nativePtr, columnIndexes, tablePtrs, value, true); queryValidated = false; return this; } // Not Equals - public TableQuery notEqualTo(long[] columnIndex, String value, Case caseSensitive) { - nativeNotEqual(nativePtr, columnIndex, value, caseSensitive.getValue()); + public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, String value, Case caseSensitive) { + nativeNotEqual(nativePtr, columnIndex, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndex, String value) { - nativeNotEqual(nativePtr, columnIndex, value, true); + public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, String value) { + nativeNotEqual(nativePtr, columnIndex, tablePtrs, value, true); queryValidated = false; return this; } - public TableQuery beginsWith(long[] columnIndices, String value, Case caseSensitive) { - nativeBeginsWith(nativePtr, columnIndices, value, caseSensitive.getValue()); + public TableQuery beginsWith(long[] columnIndices, long[] tablePtrs, String value, Case caseSensitive) { + nativeBeginsWith(nativePtr, columnIndices, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery beginsWith(long[] columnIndices, String value) { - nativeBeginsWith(nativePtr, columnIndices, value, true); + public TableQuery beginsWith(long[] columnIndices, long[] tablePtrs, String value) { + nativeBeginsWith(nativePtr, columnIndices, tablePtrs, value, true); queryValidated = false; return this; } - public TableQuery endsWith(long[] columnIndices, String value, Case caseSensitive) { - nativeEndsWith(nativePtr, columnIndices, value, caseSensitive.getValue()); + public TableQuery endsWith(long[] columnIndices, long[] tablePtrs, String value, Case caseSensitive) { + nativeEndsWith(nativePtr, columnIndices, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery endsWith(long[] columnIndices, String value) { - nativeEndsWith(nativePtr, columnIndices, value, true); + public TableQuery endsWith(long[] columnIndices, long[] tablePtrs, String value) { + nativeEndsWith(nativePtr, columnIndices, tablePtrs, value, true); queryValidated = false; return this; } - public TableQuery like(long[] columnIndices, String value, Case caseSensitive) { - nativeLike(nativePtr, columnIndices, value, caseSensitive.getValue()); + public TableQuery like(long[] columnIndices, long[] tablePtrs, String value, Case caseSensitive) { + nativeLike(nativePtr, columnIndices, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery like(long[] columnIndices, String value) { - nativeLike(nativePtr, columnIndices, value, true); + public TableQuery like(long[] columnIndices, long[] tablePtrs, String value) { + nativeLike(nativePtr, columnIndices, tablePtrs, value, true); queryValidated = false; return this; } - public TableQuery contains(long[] columnIndices, String value, Case caseSensitive) { - nativeContains(nativePtr, columnIndices, value, caseSensitive.getValue()); + public TableQuery contains(long[] columnIndices, long[] tablePtrs, String value, Case caseSensitive) { + nativeContains(nativePtr, columnIndices, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery contains(long[] columnIndices, String value) { - nativeContains(nativePtr, columnIndices, value, true); + public TableQuery contains(long[] columnIndices, long[] tablePtrs, String value) { + nativeContains(nativePtr, columnIndices, tablePtrs, value, true); queryValidated = false; return this; } - public TableQuery isEmpty(long[] columnIndices) { - nativeIsEmpty(nativePtr, columnIndices); + public TableQuery isEmpty(long[] columnIndices, long[] tablePtrs) { + nativeIsEmpty(nativePtr, columnIndices, tablePtrs); queryValidated = false; return this; } - public TableQuery isNotEmpty(long[] columnIndices) { - return not().isEmpty(columnIndices); + public TableQuery isNotEmpty(long[] columnIndices, long[] tablePtrs) { + return not().isEmpty(columnIndices, tablePtrs); } // Searching methods. @@ -604,14 +605,14 @@ public Date minimumDate(long columnIndex) { } // isNull and isNotNull - public TableQuery isNull(long[] columnIndices) { - nativeIsNull(nativePtr, columnIndices); + public TableQuery isNull(long[] columnIndices, long[] tablePtrs) { + nativeIsNull(nativePtr, columnIndices, tablePtrs); queryValidated = false; return this; } - public TableQuery isNotNull(long[] columnIndices) { - nativeIsNotNull(nativePtr, columnIndices); + public TableQuery isNotNull(long[] columnIndices, long[] tablePtrs) { + nativeIsNotNull(nativePtr, columnIndices, tablePtrs); queryValidated = false; return this; } @@ -660,81 +661,81 @@ private void throwImmutable() { private native void nativeNot(long nativeQueryPtr); - private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeGreater(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeGreater(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeGreaterEqual(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeGreaterEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeLess(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeLess(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeLessEqual(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeLessEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); private native void nativeBetween(long nativeQueryPtr, long[] columnIndex, long value1, long value2); - private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, float value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, float value); - private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, float value); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, float value); - private native void nativeGreater(long nativeQueryPtr, long[] columnIndex, float value); + private native void nativeGreater(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, float value); - private native void nativeGreaterEqual(long nativeQueryPtr, long[] columnIndex, float value); + private native void nativeGreaterEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, float value); - private native void nativeLess(long nativeQueryPtr, long[] columnIndex, float value); + private native void nativeLess(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, float value); - private native void nativeLessEqual(long nativeQueryPtr, long[] columnIndex, float value); + private native void nativeLessEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, float value); private native void nativeBetween(long nativeQueryPtr, long[] columnIndex, float value1, float value2); - private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, double value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, double value); - private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, double value); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, double value); - private native void nativeGreater(long nativeQueryPtr, long[] columnIndex, double value); + private native void nativeGreater(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, double value); - private native void nativeGreaterEqual(long nativeQueryPtr, long[] columnIndex, double value); + private native void nativeGreaterEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, double value); - private native void nativeLess(long nativeQueryPtr, long[] columnIndex, double value); + private native void nativeLess(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, double value); - private native void nativeLessEqual(long nativeQueryPtr, long[] columnIndex, double value); + private native void nativeLessEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, double value); private native void nativeBetween(long nativeQueryPtr, long[] columnIndex, double value1, double value2); - private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, boolean value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, boolean value); - private native void nativeEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeNotEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeNotEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeGreaterTimestamp(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeGreaterTimestamp(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeGreaterEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeGreaterEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeLessTimestamp(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeLessTimestamp(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); - private native void nativeLessEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long value); + private native void nativeLessEqualTimestamp(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long value); private native void nativeBetweenTimestamp(long nativeQueryPtr, long[] columnIndex, long value1, long value2); - private native void nativeEqual(long nativeQueryPtr, long[] columnIndices, byte[] value); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, byte[] value); - private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndices, byte[] value); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, byte[] value); - private native void nativeEqual(long nativeQueryPtr, long[] columnIndexes, String value, boolean caseSensitive); + private native void nativeEqual(long nativeQueryPtr, long[] columnIndexes, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, String value, boolean caseSensitive); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeBeginsWith(long nativeQueryPtr, long[] columnIndices, String value, boolean caseSensitive); + private native void nativeBeginsWith(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeEndsWith(long nativeQueryPtr, long[] columnIndices, String value, boolean caseSensitive); + private native void nativeEndsWith(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeLike(long nativeQueryPtr, long[] columnIndices, String value, boolean caseSensitive); + private native void nativeLike(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeContains(long nativeQueryPtr, long[] columnIndices, String value, boolean caseSensitive); + private native void nativeContains(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeIsEmpty(long nativePtr, long[] columnIndices); + private native void nativeIsEmpty(long nativePtr, long[] columnIndices, long[] tablePtrs); private native long nativeFind(long nativeQueryPtr, long fromTableRow); @@ -768,9 +769,9 @@ private void throwImmutable() { private native Long nativeMinimumTimestamp(long nativeQueryPtr, long columnIndex, long start, long end, long limit); - private native void nativeIsNull(long nativePtr, long[] columnIndices); + private native void nativeIsNull(long nativePtr, long[] columnIndices, long[] tablePtrs); - private native void nativeIsNotNull(long nativePtr, long[] columnIndices); + private native void nativeIsNotNull(long nativePtr, long[] columnIndice, long[] tablePtr); private native long nativeCount(long nativeQueryPtr, long start, long end, long limit); diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java new file mode 100644 index 0000000000..a5b7f6cc70 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java @@ -0,0 +1,94 @@ +package io.realm.internal.fields; +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +import java.util.List; +import java.util.Set; + +import io.realm.RealmFieldType; +import io.realm.internal.ColumnInfo; +import io.realm.internal.NativeObject; + + +/** + * Parses the passed field description (@see parseFieldDescription(String) and returns the information + * necessary for RealmQuery predicates to select the specified records. + * Because the values returned by this method will, immediately, be handed to native code, they are + * in coordinated arrays, not a List<ColumnDeatils> + * There are two kinds of records. If return[1][i] is NativeObject.NULLPTR, return[0][i] contains + * the column index for the i-th element in the dotted field description path. + * If return[1][i] is *not* NativeObject.NULLPTR, it is a pointer to the source table for a backlink + * and return[0][i] is the column index of the source column in that table. + */ +class CachedFieldDescriptor extends FieldDescriptor { + private final SchemaProxy schema; + private final String className; + + /** + * @param schema the associated Realm Schema + * @param className the starting Table: where(Table.class) + * @param fieldDescription fieldName or link path to a field name. + */ + CachedFieldDescriptor(SchemaProxy schema, String className, String fieldDescription, Set validInternalColumnTypes, Set validFinalColumnTypes) { + super(fieldDescription, validInternalColumnTypes, validFinalColumnTypes); + this.className = className; + this.schema = schema; + } + + @Override + protected void compileFieldDescription(List fields) { + final int nFields = fields.size(); + long[] columnIndices = new long[nFields]; + long[] tableNativePointers = new long[nFields]; + String currentTable = className; + + ColumnInfo tableInfo; + String columnName = null; + RealmFieldType columnType = null; + long columnIndex; + for (int i = 0; i < nFields; i++) { + columnName = fields.get(i); + if ((columnName == null) || (columnName.length() <= 0)) { + throw new IllegalArgumentException( + "Invalid query: Field descriptor contains an empty field. A field description may not begin with or contain adjacent periods ('.')."); + } + + tableInfo = schema.getColumnInfo(currentTable); + if (tableInfo == null) { + throw new IllegalArgumentException( + String.format("Invalid query: table '%s' not found in this schema.", currentTable)); + } + + columnIndex = tableInfo.getColumnIndex(columnName); + if (columnIndex < 0) { + throw new IllegalArgumentException( + String.format("Invalid query: field '%s' not found in table '%s'.", columnName, currentTable)); + } + + columnType = tableInfo.getColumnType(columnName); + if (i < nFields - 1) { + verifyInternalColumnType(currentTable, columnName, columnType); + currentTable = tableInfo.getLinkedTable(columnName); + } + columnIndices[i] = columnIndex; + tableNativePointers[i] = (columnType != RealmFieldType.LINKING_OBJECTS) + ? NativeObject.NULLPTR + : schema.getNativeTablePtr(currentTable); + } + + setCompilationResults(className, columnName, columnType, columnIndices, tableNativePointers); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java new file mode 100644 index 0000000000..55995f9355 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java @@ -0,0 +1,81 @@ +package io.realm.internal.fields; +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +import java.util.List; +import java.util.Set; + +import io.realm.RealmFieldType; +import io.realm.internal.Table; + + +/** + * A field descriptor that uses dynamic table lookup. + * Use when cache cannot be trusted... + */ +class DynamicFieldDescriptor extends FieldDescriptor { + private final Table table; + + /** + * Build a dynamic field descriptor for the passed field description string. + * + * @param table the start table. + * @param fieldDescription the field description. + * @param validInternalColumnTypes valid types for the last field in the field description. + * @param validFinalColumnTypes valid types for the last field in the field description. + */ + DynamicFieldDescriptor(Table table, String fieldDescription, Set validInternalColumnTypes, Set validFinalColumnTypes) { + super(fieldDescription, validInternalColumnTypes, validFinalColumnTypes); + this.table = table; + } + + @Override + protected void compileFieldDescription(List fields) { + final int nFields = fields.size(); + long[] columnIndices = new long[nFields]; + Table currentTable = table; + + long columnIndex; + String tableName = null; + String columnName = null; + RealmFieldType columnType = null; + for (int i = 0; i < nFields; i++) { + columnName = fields.get(i); + if ((columnName == null) || (columnName.length() <= 0)) { + throw new IllegalArgumentException( + "Invalid query: Field descriptor contains an empty field. A field description may not begin with or contain adjacent periods ('.')."); + } + + tableName = currentTable.getClassName(); + + columnIndex = currentTable.getColumnIndex(columnName); + if (columnIndex < 0) { + throw new IllegalArgumentException( + String.format("Invalid query: field '%s' not found in table '%s'.", columnName, tableName)); + } + + columnType = currentTable.getColumnType(columnIndex); + if (i < nFields - 1) { + verifyInternalColumnType(tableName, columnName, columnType); + currentTable = currentTable.getLinkTarget(columnIndex); + } + + columnIndices[i] = columnIndex; + } + + setCompilationResults(tableName, columnName, columnType, columnIndices, new long[nFields]); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java new file mode 100644 index 0000000000..6a17af9445 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java @@ -0,0 +1,287 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm.internal.fields; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import io.realm.RealmFieldType; +import io.realm.internal.ColumnInfo; +import io.realm.internal.Table; + + +/** + * Class describing a single field, possibly several links away, e.g.: + *

                        + * "someField" + * "someRealmObjectField.someField" + * "someRealmListField.someField" + * "someLinkingObjectField.someField" + * "someRealmObjectField.someRealmListField.someLinkingObjectField.someField" + *
                      + */ +public abstract class FieldDescriptor { + public interface SchemaProxy { + boolean hasCache(); + + ColumnInfo getColumnInfo(String tableName); + + long getNativeTablePtr(String targetTable); + } + + public static final Set ALL_LINK_FIELD_TYPES; + + static { + Set s = new HashSet<>(3); + s.add(RealmFieldType.OBJECT); + s.add(RealmFieldType.LIST); + s.add(RealmFieldType.LINKING_OBJECTS); + ALL_LINK_FIELD_TYPES = Collections.unmodifiableSet(s); + } + + public static final Set SIMPLE_LINK_FIELD_TYPES; + + static { + Set s = new HashSet<>(2); + s.add(RealmFieldType.OBJECT); + s.add(RealmFieldType.LIST); + SIMPLE_LINK_FIELD_TYPES = Collections.unmodifiableSet(s); + } + + public static final Set LIST_LINK_FIELD_TYPE; + + static { + Set s = new HashSet<>(1); + s.add(RealmFieldType.LIST); + LIST_LINK_FIELD_TYPE = Collections.unmodifiableSet(s); + } + + public static final Set OBJECT_LINK_FIELD_TYPE; + + static { + Set s = new HashSet<>(1); + s.add(RealmFieldType.OBJECT); + OBJECT_LINK_FIELD_TYPE = Collections.unmodifiableSet(s); + } + + public static final Set NO_LINK_FIELD_TYPE = Collections.emptySet(); + + /** + * Convenience method to allow var-arg specification of valid final column types + * + * @param schema Proxy to schema info + * @param table the start table + * @param fieldDescription dot-separated column names + * @param validFinalColumnTypes legal types for the last column + * @return the Field descriptor + */ + public static FieldDescriptor createStandardFieldDescriptor( + SchemaProxy schema, + Table table, + String fieldDescription, + RealmFieldType... validFinalColumnTypes) { + return createFieldDescriptor(schema, table, fieldDescription, null, new HashSet<>(Arrays.asList(validFinalColumnTypes))); + } + + /** + * Factory method for field descriptors. + * + * @param schema Proxy to schema info + * @param table the start table + * @param fieldDescription dot-separated column names + * @param validFinalColumnTypes legal types for the last column + * @return the Field descriptor + *

                      + * TODO: + * I suspect that choosing the parsing strategy based on whether there is a ref to a ColumnIndices + * around or not, is bad architecture. Almost certainly, there should be a schema that has + * ColumnIndices and one that does not and the strategies below should belong to the first + * and second, respectively. --gbm + */ + public static FieldDescriptor createFieldDescriptor( + SchemaProxy schema, + Table table, + String fieldDescription, + Set validInternalColumnTypes, + Set validFinalColumnTypes) { + return ((schema == null) || !schema.hasCache()) + ? new DynamicFieldDescriptor(table, fieldDescription, (null != validInternalColumnTypes) ? validInternalColumnTypes : SIMPLE_LINK_FIELD_TYPES, validFinalColumnTypes) + : new CachedFieldDescriptor(schema, table.getClassName(), fieldDescription, (null != validInternalColumnTypes) ? validInternalColumnTypes : ALL_LINK_FIELD_TYPES, validFinalColumnTypes); + } + + + private final List fields; + private final Set validInternalColumnTypes; + private final Set validFinalColumnTypes; + + private String finalColumnName; + private RealmFieldType finalColumnType; + private long[] columnIndices; + private long[] nativeTablePointers; + + /** + * @param fieldDescription fieldName or link path to a field name. + * @param validInternalColumnTypes valid internal link types. + * @param validFinalColumnTypes valid field types for the last field in a linked field + */ + protected FieldDescriptor( + String fieldDescription, Set + validInternalColumnTypes, + Set validFinalColumnTypes) { + this.fields = parseFieldDescription(fieldDescription); + int nFields = fields.size(); + if (nFields <= 0) { + throw new IllegalArgumentException("Invalid query: Empty field descriptor"); + } + this.validInternalColumnTypes = validInternalColumnTypes; + this.validFinalColumnTypes = validFinalColumnTypes; + } + + /** + * The number of columnNames in the field description. + * The returned number is the size of the array returned by + * {@code getColumnIndices} and {@code getNativeTablePointers} + * + * @return the number of fields. + */ + public final int length() { + return fields.size(); + } + + /** + * Return a java array of column indices for the columns named in the description. + * If the column at ret[i] is a LinkingObjects column, ret[i] (the column index) + * is the index for the source column in the source table. + * + * The return is an array because it will be, immediately, passed to native code + * + * @return an array of column indices. + */ + public final long[] getColumnIndices() { + compileIfNecessary(); + return Arrays.copyOf(columnIndices, columnIndices.length); + } + + /** + * Return a java array of native table pointers. For most columns the table will be identified by + * the type of the column: no further information is needed. In that case, this array will contain + * NativeObject.NULLPTR. If, however, a column is a LinkingObjects column the source table + * cannot be inferred, so the returned array contains the native pointer to it. + * + * The return is an array because it will be, immediately, passed to native code + * + * @return an array of native table pointers. + */ + public final long[] getNativeTablePointers() { + compileIfNecessary(); + return Arrays.copyOf(nativeTablePointers, nativeTablePointers.length); + } + + /** + * Getter for the name of the final column in the descriptor. + * + * @return the name of the final column + */ + public final String getFinalColumnName() { + compileIfNecessary(); + return finalColumnName; + } + + /** + * Getter for the type of the final column in the descriptor. + * + * @return the type of the final column + */ + public final RealmFieldType getFinalColumnType() { + compileIfNecessary(); + return finalColumnType; + } + + /** + * Subclasses implement this method with a compilation strategy. + */ + protected abstract void compileFieldDescription(List fields); + + /** + * Verify that the named link column, in the named table, of the specified type, is one of the legal internal column types. + * + * @param tableName Name of the table containing the column: used in error messages + * @param columnName Name of the column whose type is being tested: used in error messages + * @param columnType The type of the column: examined for validity. + */ + protected final void verifyInternalColumnType(String tableName, String columnName, RealmFieldType columnType) { + verifyColumnType(tableName, columnName, columnType, validInternalColumnTypes); + } + + /** + * Store the results of compiling the field description. + * Subclasses call this as the last action in + * + * @param finalClassName the name of the final table in the field description. + * @param finalColumnName the name of the final column in the field description. + * @param finalColumnType the type of the final column in the field description: MAY NOT BE {@code null}! + * @param columnIndices the array of columnIndices. + * @param nativeTablePointers the array of table pointers + */ + protected final void setCompilationResults( + String finalClassName, + String finalColumnName, + RealmFieldType finalColumnType, + long[] columnIndices, + long[] nativeTablePointers) { + if ((validFinalColumnTypes != null) && (validFinalColumnTypes.size() > 0)) { + verifyColumnType(finalClassName, finalColumnName, finalColumnType, validFinalColumnTypes); + } + this.finalColumnName = finalColumnName; + this.finalColumnType = finalColumnType; + this.columnIndices = columnIndices; + this.nativeTablePointers = nativeTablePointers; + } + + /** + * Parse the passed field description into its components. + * This must be standard across implementations and is, therefore, implemented in the base class. + * + * @param fieldDescription a field description. + * @return the parse tree: a list of column names + */ + private List parseFieldDescription(String fieldDescription) { + if (fieldDescription == null || fieldDescription.equals("")) { + throw new IllegalArgumentException("Invalid query: field name is empty"); + } + if (fieldDescription.endsWith(".")) { + throw new IllegalArgumentException("Invalid query: field name must not end with a period ('.')"); + } + return Arrays.asList(fieldDescription.split("\\.")); + } + + private void verifyColumnType(String tableName, String columnName, RealmFieldType columnType, Set validTypes) { + if (!validTypes.contains(columnType)) { + throw new IllegalArgumentException(String.format( + "Invalid query: field '%s' in table '%s' is of invalid type '%s'.", + columnName, tableName, columnType.toString())); + } + } + + private void compileIfNecessary() { + if (finalColumnType == null) { + compileFieldDescription(fields); + } + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index dfd7b31859..b1469a6a1f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -49,13 +49,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; + @RunWith(AndroidJUnit4.class) public class ProcessCommitTests extends BaseIntegrationTest { - // FIXME: Ignore for now. They do still not work. It might be caused by two processes each creating - // a Sync Client, but it needs to be investigated. @Test - @Ignore + @Ignore("Failure might be caused by two processes each creating a Sync Client: needs investigation") public void expectServerCommit() throws Throwable { final Throwable[] exception = new Throwable[1]; final CountDownLatch testFinished = new CountDownLatch(1); @@ -109,14 +108,12 @@ public void onChange(RealmResults element) { } } - // FIXME: Ignore for now. They do still not work. It might be caused by two processes each creating - // a Sync Client, but it needs to be investigated. - //TODO send string from service and match - // replicate integration tests from Cocoa - // add gradle task to start the sh script automatically (create pid file, ==> run or kill existing process - // check the requirement for the issue again + // TODO: + // - send string from service and match replicate integration tests from Cocoa + // - add gradle task to start the sh script automatically (create pid file, ==> run or kill existing process) + // - check the requirement for the issue again @Test - @Ignore + @Ignore("Failure might be caused by two processes each creating a Sync Client: needs investigation") public void expectALot() throws Throwable { final Throwable[] exception = new Throwable[1]; final CountDownLatch testFinished = new CountDownLatch(1); From 9cea9356930b4357403b46ef4ca9995598a078ce Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 11 May 2017 14:08:19 +0200 Subject: [PATCH 0687/2110] Support readOnly() on Configurations (#4575) --- CHANGELOG.md | 1 + .../src/androidTest/assets/readonly.realm | Bin 0 -> 4096 bytes .../io/realm/RealmConfigurationTests.java | 43 +++ .../androidTest/java/io/realm/RealmTests.java | 42 ++- .../io/realm/entities/StringOnlyReadOnly.java | 36 +++ .../src/main/java/io/realm/BaseRealm.java | 6 +- .../src/main/java/io/realm/Realm.java | 93 +++--- .../java/io/realm/RealmConfiguration.java | 63 +++- .../java/io/realm/internal/SharedRealm.java | 9 + .../java/io/realm/SyncConfiguration.java | 34 +- .../java/io/realm/SyncedRealmTests.java | 302 ++++++++++++++++++ .../objectserver/BaseIntegrationTest.java | 2 +- .../realm/objectserver/SyncedRealmTests.java | 1 - 13 files changed, 576 insertions(+), 56 deletions(-) create mode 100644 realm/realm-library/src/androidTest/assets/readonly.realm create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/StringOnlyReadOnly.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5055d2304c..0e4f427496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Added `Realm.refresh()` and `DynamicRealm.refresh()` (#3476). * Added `Realm.getInstanceAsync()` and `DynamicRealm.getInstanceAsync()` (#2299). * Added `DynamicRealmObject#linkingObjects(String,String)` to support linking objects on `DynamicRealm` (#4492). +* Added support for read only Realms using `RealmConfiguration.Builder.readOnly()` and `SyncConfiguration.Builder.readOnly()`(#1147). * Change listeners will now auto-expand variable names to be more descriptive when using Android Studio. ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/assets/readonly.realm b/realm/realm-library/src/androidTest/assets/readonly.realm new file mode 100644 index 0000000000000000000000000000000000000000..d2d3637a8f60b3cd0cfb1bc5783be3c02a824274 GIT binary patch literal 4096 zcmeHHJ#Q015S`h(^PL?Q1tkJY2yp2pQodsfB=ZrJ27!QrE;*7DiDK;2`4lPLm6Rz> z=bzw`(j_HjN=iyfO5W`4krNR;#ctu}H?y`53BJ;u37aG z_s~RDjf9Ji@AQ+}T> modelClasses = mediator.getModelClasses(); + // Only allow creating the schema if not in read-only mode if (unversioned) { + if (configuration.isReadOnly()) { + throw new IllegalArgumentException("Cannot create the Realm schema in a read-only file."); + } realm.setVersion(configuration.getSchemaVersion()); - } - - final RealmProxyMediator mediator = configuration.getSchemaMediator(); - final Set> modelClasses = mediator.getModelClasses(); - - if (unversioned) { // Create all of the tables. for (Class modelClass : modelClasses) { mediator.createRealmObjectSchema(modelClass, realm.getSchema()); } } + // Now that they have all been created, validate them. final Map, ColumnInfo> columnInfoMap = new HashMap<>(modelClasses.size()); for (Class modelClass : modelClasses) { - // Now that they have all been created, validate them. columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } @@ -444,11 +449,10 @@ private static void initializeRealm(Realm realm) { (unversioned) ? configuration.getSchemaVersion() : currentVersion, columnInfoMap); - if (unversioned) { - final Transaction transaction = configuration.getInitialDataTransaction(); - if (transaction != null) { - transaction.execute(realm); - } + // Finally add any initial data + final Transaction transaction = configuration.getInitialDataTransaction(); + if (transaction != null && unversioned) { + transaction.execute(realm); } } catch (Exception e) { commitChanges = false; @@ -456,7 +460,7 @@ private static void initializeRealm(Realm realm) { } finally { if (commitChanges) { realm.commitTransaction(); - } else { + } else if (realm.isInTransaction()) { realm.cancelTransaction(); } } @@ -469,7 +473,11 @@ private static void initializeSyncedRealm(Realm realm) { OsRealmSchema schema = null; OsRealmSchema.Creator schemaCreator = null; try { - realm.beginTransaction(); + // We need to start a transaction no matter readOnly mode, because it acts as an interprocess lock. + // TODO: For proper inter-process support we also need to move e.g copying the asset file under an + // interprocess lock. This lock can obviously not be created by a Realm instance so we probably need + // to implement it in Object Store. When this happens, the `beginTransaction(true)` can be removed again. + realm.beginTransaction(true); long currentVersion = realm.getVersion(); final boolean unversioned = currentVersion == UNVERSIONED; @@ -478,41 +486,45 @@ private static void initializeSyncedRealm(Realm realm) { final RealmProxyMediator mediator = configuration.getSchemaMediator(); final Set> modelClasses = mediator.getModelClasses(); - schemaCreator = new OsRealmSchema.Creator(); - for (Class modelClass : modelClasses) { - mediator.createRealmObjectSchema(modelClass, schemaCreator); - } + long newVersion = configuration.getSchemaVersion(); - // Assumption: When SyncConfiguration then additive schema update mode. - schema = new OsRealmSchema(schemaCreator); - schemaCreator.close(); - schemaCreator = null; + // Update/create the schema if allowed + if (!configuration.isReadOnly()) { + schemaCreator = new OsRealmSchema.Creator(); + for (Class modelClass : modelClasses) { + mediator.createRealmObjectSchema(modelClass, schemaCreator); + } - long newVersion = configuration.getSchemaVersion(); - if (realm.sharedRealm.requiresMigration(schema.getNativePtr())) { - if (currentVersion >= newVersion) { - throw new IllegalArgumentException(String.format( - "The schema was changed but the schema version was not updated. " + - "The configured schema version (%d) must be greater than the version " + - " in the Realm file (%d) in order to update the schema.", - newVersion, currentVersion)); + // Assumption: When SyncConfiguration then additive schema update mode. + schema = new OsRealmSchema(schemaCreator); + schemaCreator.close(); + schemaCreator = null; + + // !!! FIXME: This appalling kludge is necessitated by current package structure/visiblity constraints. + // It absolutely breaks encapsulation and needs to be fixed! + if (realm.sharedRealm.requiresMigration(schema.getNativePtr())) { + if (currentVersion >= newVersion) { + throw new IllegalArgumentException(String.format( + "The schema was changed but the schema version was not updated. " + + "The configured schema version (%d) must be greater than the version " + + " in the Realm file (%d) in order to update the schema.", + newVersion, currentVersion)); + } + realm.sharedRealm.updateSchema(schema.getNativePtr(), newVersion); + // The OS currently does not handle setting the schema version. We have to do it manually. + realm.setVersion(newVersion); + commitChanges = true; } - realm.sharedRealm.updateSchema(schema.getNativePtr(), newVersion); - // The OS currently does not handle setting the schema version. We have to do it manually. - realm.setVersion(newVersion); - commitChanges = true; } + // Validate the schema in the file final Map, ColumnInfo> columnInfoMap = new HashMap<>(modelClasses.size()); for (Class modelClass : modelClasses) { columnInfoMap.put(modelClass, mediator.validateTable(modelClass, realm.sharedRealm, false)); } + realm.getSchema().setInitialColumnIndices((unversioned) ? newVersion : currentVersion, columnInfoMap); - realm.getSchema().setInitialColumnIndices( - (unversioned) ? newVersion : currentVersion, - columnInfoMap); - - if (unversioned) { + if (unversioned && !configuration.isReadOnly()) { final Transaction transaction = configuration.getInitialDataTransaction(); if (transaction != null) { transaction.execute(realm); @@ -525,7 +537,6 @@ private static void initializeSyncedRealm(Realm realm) { if (schemaCreator != null) { schemaCreator.close(); } - if (schema != null) { schema.close(); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 064a656a37..c12980872f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -97,6 +97,7 @@ public class RealmConfiguration { private final RealmProxyMediator schemaMediator; private final RxObservableFactory rxObservableFactory; private final Realm.Transaction initialDataTransaction; + private final boolean readOnly; // We need to enumerate all parameters since SyncConfiguration and RealmConfiguration supports different // subsets of them. @@ -111,7 +112,8 @@ protected RealmConfiguration(File realmDirectory, SharedRealm.Durability durability, RealmProxyMediator schemaMediator, RxObservableFactory rxObservableFactory, - Realm.Transaction initialDataTransaction) { + Realm.Transaction initialDataTransaction, + boolean readOnly) { this.realmDirectory = realmDirectory; this.realmFileName = realmFileName; this.canonicalPath = canonicalPath; @@ -124,6 +126,7 @@ protected RealmConfiguration(File realmDirectory, this.schemaMediator = schemaMediator; this.rxObservableFactory = rxObservableFactory; this.initialDataTransaction = initialDataTransaction; + this.readOnly = readOnly; } public File getRealmDirectory() { @@ -211,7 +214,7 @@ public String getPath() { /** * Checks if the Realm file defined by this configuration already exists. - * + *

                      * WARNING: This method is just a point-in-time check. Unless protected by external synchronization another * thread or process might have created or deleted the Realm file right after this method has returned. * @@ -237,6 +240,16 @@ public RxObservableFactory getRxFactory() { return rxObservableFactory; } + /** + * Returns whether this Realm is read-only or not. Read-only Realms cannot be modified and will throw an + * {@link IllegalStateException} if {@link Realm#beginTransaction()} is called on it. + * + * @return {@code true} if this Realm is read only, {@code false} if not. + */ + public boolean isReadOnly() { + return readOnly; + } + @Override public boolean equals(Object obj) { if (this == obj) { return true; } @@ -259,6 +272,7 @@ public boolean equals(Object obj) { if (initialDataTransaction != null ? !initialDataTransaction.equals(that.initialDataTransaction) : that.initialDataTransaction != null) { return false; } + if (readOnly != that.readOnly) { return false; } return schemaMediator.equals(that.schemaMediator); } @@ -277,6 +291,7 @@ public int hashCode() { result = 31 * result + durability.hashCode(); result = 31 * result + (rxObservableFactory != null ? rxObservableFactory.hashCode() : 0); result = 31 * result + (initialDataTransaction != null ? initialDataTransaction.hashCode() : 0); + result = 31 * result + (readOnly ? 1 : 0); return result; } @@ -349,6 +364,8 @@ public String toString() { stringBuilder.append("durability: ").append(durability); stringBuilder.append("\n"); stringBuilder.append("schemaMediator: ").append(schemaMediator); + stringBuilder.append("\n"); + stringBuilder.append("readOnly: ").append(readOnly); return stringBuilder.toString(); } @@ -404,6 +421,7 @@ public static class Builder { private HashSet> debugSchema = new HashSet>(); private RxObservableFactory rxFactory; private Realm.Transaction initialDataTransaction; + private boolean readOnly; /** * Creates an instance of the Builder for the RealmConfiguration. @@ -433,6 +451,7 @@ private void initializeBuilder(Context context) { this.migration = null; this.deleteRealmIfMigrationNeeded = false; this.durability = SharedRealm.Durability.FULL; + this.readOnly = false; if (DEFAULT_MODULE != null) { this.modules.add(DEFAULT_MODULE); } @@ -615,16 +634,15 @@ public Builder initialData(Realm.Transaction transaction) { * When opening the Realm for the first time, instead of creating an empty file, * the Realm file will be copied from the provided asset file and used instead. *

                      - *

                      This cannot be configured to clear and recreate schema by calling {@link #deleteRealmIfMigrationNeeded()} - * at the same time as doing so will delete the copied asset schema. - *

                      + * This cannot be combined with {@link #deleteRealmIfMigrationNeeded()} as doing so would just result in the + * copied file being deleted. *

                      * WARNING: This could potentially be a lengthy operation and should ideally be done on a background thread. * * @param assetFile path to the asset database file. * @throws IllegalStateException if this is configured to clear its schema by calling {@link #deleteRealmIfMigrationNeeded()}. */ - public Builder assetFile(final String assetFile) { + public Builder assetFile(String assetFile) { if (TextUtils.isEmpty(assetFile)) { throw new IllegalArgumentException("A non-empty asset file path must be provided"); } @@ -634,12 +652,26 @@ public Builder assetFile(final String assetFile) { if (this.deleteRealmIfMigrationNeeded) { throw new IllegalStateException("Realm cannot use an asset file when previously configured to clear its schema in migration by calling deleteRealmIfMigrationNeeded()."); } - this.assetFilePath = assetFile; return this; } + /** + * Setting this will cause the Realm to become read only and all write transactions made against this Realm will + * fail with an {@link IllegalStateException}. + *

                      + * This in particular mean that {@link #initialData(Realm.Transaction)} will not work in combination with a + * read only Realm and setting this will result in a {@link IllegalStateException} being thrown. + *

                      + * Marking a Realm as read only only applies to the Realm in this process. Other processes can still + * write to the Realm. + */ + public Builder readOnly() { + this.readOnly = true; + return this; + } + private void addModule(Object module) { if (module != null) { checkModule(module); @@ -672,6 +704,20 @@ Builder schema(Class firstClass, Class + * This in particular mean that {@link #initialData(Realm.Transaction)} will not work in combination with a + * read only Realm and setting this will result in a {@link IllegalStateException} being thrown. + *

                      + * Marking a Realm as read only only applies to the Realm in this process. Other processes and devices can still + * write to the Realm. + */ + public SyncConfiguration.Builder readOnly() { + this.readOnly = true; + return this; + } + private String MD5(String in) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); @@ -652,6 +670,19 @@ public SyncConfiguration build() { throw new IllegalStateException("serverUrl() and user() are both required."); } + // Check that readOnly() was applied to legal configuration. Right now it should only be allowd if + // an assetFile is configured + if (readOnly) { + if (initialDataTransaction != null) { + throw new IllegalStateException("This Realm is marked as read-only. " + + "Read-only Realms cannot use initialData(Realm.Transaction)."); + } + if (!waitForServerChanges) { + throw new IllegalStateException("A read-only Realms must be provided by some source. " + + "'waitForInitialRemoteData()' wasn't enabled which is currently the only supported source."); + } + } + // Check if the user has an identifier, if not, it cannot use /~/. if (serverUrl.toString().contains("/~/") && user.getIdentity() == null) { throw new IllegalStateException("The serverUrl contains a /~/, but the user does not have an identity." + @@ -718,6 +749,7 @@ public SyncConfiguration build() { createSchemaMediator(modules, debugSchema), rxFactory, initialDataTransaction, + readOnly, // Sync Configuration specific user, diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java new file mode 100644 index 0000000000..c8d7b1513e --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -0,0 +1,302 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.test.annotation.UiThreadTest; +import android.support.test.rule.UiThreadTestRule; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.realm.entities.StringOnly; +import io.realm.exceptions.DownloadingRealmInterruptedException; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.objectserver.BaseIntegrationTest; +import io.realm.objectserver.utils.Constants; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.TestSyncConfigurationFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + + +/** + * Catch all class for tests that not naturally fit anywhere else. + */ +public class SyncedRealmTests extends BaseIntegrationTest { + + @Rule + public RunInLooperThread looperThread = new RunInLooperThread(); + + @Rule + public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); + + @Rule + public final TestSyncConfigurationFactory configurationFactory = new TestSyncConfigurationFactory(); + + @Test + @UiThreadTest + public void waitForInitialRemoteData_mainThreadThrows() { + final SyncUser user = loginUser(); + + SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .build(); + + Realm realm = null; + try { + realm = Realm.getInstance(config); + fail(); + } catch (IllegalStateException ignored) { + } finally { + if (realm != null) { + realm.close(); + } + } + } + + // Login user on a worker thread, so this method can be used from both UI and non-ui threads. + @NonNull + private SyncUser loginUser() { + final CountDownLatch userReady = new CountDownLatch(1); + final AtomicReference user = new AtomicReference<>(); + new Thread(new Runnable() { + @Override + public void run() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + user.set(SyncUser.login(credentials, Constants.AUTH_URL)); + userReady.countDown(); + } + }).start(); + TestHelper.awaitOrFail(userReady); + return user.get(); + } + + @Test + public void waitForInitialRemoteData() { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly.class) + .build(); + Realm realm = Realm.getInstance(configOld); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + for (int i = 0; i < 10; i++) { + realm.createObject(StringOnly.class).setChars("Foo" + i); + } + } + }); + SystemClock.sleep(TimeUnit.SECONDS.toMillis(10)); // FIXME: Replace with Sync Progress Notifications once available. + realm.close(); + user.logout(); + Realm.deleteRealm(configOld); + + // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should + // download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .schema(StringOnly.class) + .waitForInitialRemoteData() + .build(); + + realm = Realm.getInstance(config); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + for (int i = 0; i < 10; i++) { + realm.createObject(StringOnly.class).setChars("Foo 1" + i); + } + } + }); + try { + assertEquals(20, realm.where(StringOnly.class).count()); + } finally { + realm.close(); + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .build(); + + for (int i = 0; i < 10; i++) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + Realm realm = null; + try { + // This will cause the download latch called later to immediately throw an InterruptedException. + Thread.currentThread().interrupt(); + realm = Realm.getInstance(config); + } catch (DownloadingRealmInterruptedException ignored) { + assertFalse(new File(config.getPath()).exists()); + } finally { + if (realm != null) { + realm.close(); + Realm.deleteRealm(config); + } + } + } + }); + t.start(); + t.join(); + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + @RunTestInLooperThread + public void waitForInitialData_resilientInCaseOfRetriesAsync() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .build(); + Random randomizer = new Random(); + + for (int i = 0; i < 10; i++) { + RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + fail(); + } + + @Override + public void onError(Throwable exception) { + fail(exception.toString()); + } + }); + SystemClock.sleep(randomizer.nextInt(5)); + task.cancel(); + } + looperThread.testComplete(); + } + + @Test + public void waitForInitialRemoteData_readOnlyTrue() { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly.class) + .build(); + Realm realm = Realm.getInstance(configOld); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + for (int i = 0; i < 10; i++) { + realm.createObject(StringOnly.class).setChars("Foo" + i); + } + } + }); + SystemClock.sleep(TimeUnit.SECONDS.toMillis(10)); // FIXME: Replace with Sync Progress Notifications once available. + realm.close(); + user.logout(); + Realm.deleteRealm(configOld); + + // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should + // download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .readOnly() + .schema(StringOnly.class) + .build(); + assertFalse(configNew.realmExists()); + + realm = Realm.getInstance(configNew); + assertEquals(10, realm.where(StringOnly.class).count()); + realm.close(); + user.logout(); + } + + + @Test + public void waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .readOnly() + .schema(StringOnly.class) + .build(); + assertFalse(configNew.realmExists()); + + Realm realm = null; + try { + // This will fail, because the server Realm is completely empty and the Client is not allowed to write the + // schema. + realm = Realm.getInstance(configNew); + fail(); + } catch (RealmMigrationNeededException ignored) { + } finally { + if (realm != null) { + realm.close(); + } + user.logout(); + } + } + + @Test + public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema + .schema(StringOnly.class) // This schema should be written when opening the empty Realm. + .schemaVersion(2) + .build(); + assertFalse(config.realmExists()); + + Realm realm = Realm.getInstance(config); + try { + assertEquals(0, realm.where(StringOnly.class).count()); + } finally { + realm.close(); + user.logout(); + } + } + +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java index 79081d405a..a6cd05356e 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java @@ -27,7 +27,7 @@ import io.realm.log.RealmLog; import io.realm.objectserver.utils.HttpUtils; -class BaseIntegrationTest { +public class BaseIntegrationTest { private static int originalLogLevel; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncedRealmTests.java index 4881139b82..cb82896b72 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncedRealmTests.java @@ -165,7 +165,6 @@ public void waitForInitialData_resilientInCaseOfRetriesAsync() { Random randomizer = new Random(); for (int i = 0; i < 10; i++) { - final int iteration = i; RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { @Override public void onSuccess(Realm realm) { From 6ea96679dcb5f36cad7e0fb0053aa8b75d23e6f9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 11 May 2017 15:28:24 +0200 Subject: [PATCH 0688/2110] Add comment about GC to all relevant change listeners (#4631) --- .../src/main/java/io/realm/RealmList.java | 50 +++++++++ .../src/main/java/io/realm/RealmObject.java | 101 ++++++++++++++++++ .../src/main/java/io/realm/RealmResults.java | 50 +++++++++ 3 files changed, 201 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index ec33d39900..4d3b995942 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -899,6 +899,31 @@ private void checkForAddRemoveListener(Object listener, boolean checkListener) { /** * Adds a change listener to this {@link RealmList}. + *

                      + * Registering a change listener will not prevent the underlying RealmList from being garbage collected. + * If the RealmList is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + *

                      + *

                      +     * {@code
                      +     * public class MyActivity extends Activity {
                      +     *
                      +     *     private RealmList dogs; // Strong reference to keep listeners alive
                      +     *
                      +     *     \@Override
                      +     *     protected void onCreate(Bundle savedInstanceState) {
                      +     *       super.onCreate(savedInstanceState);
                      +     *       dogs = realm.where(Person.class).findFirst().getDogs();
                      +     *       dogs.addChangeListener(new OrderedRealmCollectionChangeListener>() {
                      +     *           \@Override
                      +     *           public void onChange(RealmList dogs, OrderedCollectionChangeSet changeSet) {
                      +     *               // React to change
                      +     *           }
                      +     *       });
                      +     *     }
                      +     * }
                      +     * }
                      +     * 
                      * * @param listener the change listener to be notified. * @throws IllegalArgumentException if the change listener is {@code null}. @@ -925,6 +950,31 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener + * Registering a change listener will not prevent the underlying RealmList from being garbage collected. + * If the RealmList is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + *

                      + *

                      +     * {@code
                      +     * public class MyActivity extends Activity {
                      +     *
                      +     *     private RealmList dogs; // Strong reference to keep listeners alive
                      +     *
                      +     *     \@Override
                      +     *     protected void onCreate(Bundle savedInstanceState) {
                      +     *       super.onCreate(savedInstanceState);
                      +     *       dogs = realm.where(Person.class).findFirst().getDogs();
                      +     *       dogs.addChangeListener(new RealmChangeListener>() {
                      +     *           \@Override
                      +     *           public void onChange(RealmList dogs) {
                      +     *               // React to change
                      +     *           }
                      +     *       });
                      +     *     }
                      +     * }
                      +     * }
                      +     * 
                      * * @param listener the change listener to be notified. * @throws IllegalArgumentException if the change listener is {@code null}. diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 74025c2544..792e118fe9 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -316,6 +316,31 @@ public static boolean load(E object) { * Adds a change listener to this RealmObject to get detailed information about changes. The listener will be * triggered if any value field or referenced RealmObject field is changed, or the RealmList field itself is * changed. + *

                      + * Registering a change listener will not prevent the underlying RealmObject from being garbage collected. + * If the RealmObject is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + *

                      + *

                      +     * {@code
                      +     * public class MyActivity extends Activity {
                      +     *
                      +     *     private Person person; // Strong reference to keep listeners alive
                      +     *
                      +     *     \@Override
                      +     *     protected void onCreate(Bundle savedInstanceState) {
                      +     *       super.onCreate(savedInstanceState);
                      +     *       person = realm.where(Person.class).findFirst();
                      +     *       person.addChangeListener(new RealmObjectChangeListener() {
                      +     *           \@Override
                      +     *           public void onChange(Person person, ObjectChangeSet changeSet) {
                      +     *               // React to change
                      +     *           }
                      +     *       });
                      +     *     }
                      +     * }
                      +     * }
                      +     * 
                      * * @param listener the change listener to be notified. * @throws IllegalArgumentException if the change listener is {@code null} or the object is an unmanaged object. @@ -330,6 +355,31 @@ public final void addChangeListener(RealmObjectChangeList /** * Adds a change listener to this RealmObject that will be triggered if any value field or referenced RealmObject * field is changed, or the RealmList field itself is changed. + *

                      + * Registering a change listener will not prevent the underlying RealmObject from being garbage collected. + * If the RealmObject is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + *

                      + *

                      +     * {@code
                      +     * public class MyActivity extends Activity {
                      +     *
                      +     *     private Person person; // Strong reference to keep listeners alive
                      +     *
                      +     *     \@Override
                      +     *     protected void onCreate(Bundle savedInstanceState) {
                      +     *       super.onCreate(savedInstanceState);
                      +     *       person = realm.where(Person.class).findFirst();
                      +     *       person.addChangeListener(new RealmChangeListener() {
                      +     *           \@Override
                      +     *           public void onChange(Person person) {
                      +     *               // React to change
                      +     *           }
                      +     *       });
                      +     *     }
                      +     * }
                      +     * }
                      +     * 
                      * * @param listener the change listener to be notified. * @throws IllegalArgumentException if the change listener is {@code null} or the object is an unmanaged object. @@ -345,6 +395,32 @@ public final void addChangeListener(RealmChangeListener + * Registering a change listener will not prevent the underlying RealmObject from being garbage collected. + * If the RealmObject is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + *

                      + *

                      +     * {@code
                      +     * public class MyActivity extends Activity {
                      +     *
                      +     *     private Person person; // Strong reference to keep listeners alive
                      +     *
                      +     *     \@Override
                      +     *     protected void onCreate(Bundle savedInstanceState) {
                      +     *       super.onCreate(savedInstanceState);
                      +     *       person = realm.where(Person.class).findFirst();
                      +     *       person.addChangeListener(new RealmObjectChangeListener() {
                      +     *           \@Override
                      +     *           public void onChange(Person person, ObjectChangeSet changeSet) {
                      +     *               // React to change
                      +     *           }
                      +     *       });
                      +     *     }
                      +     * }
                      +     * }
                      +     * 
                      + * * * @param object RealmObject to add listener to. * @param listener the change listener to be notified. @@ -375,6 +451,31 @@ public static void addChangeListener(E object, RealmObjec /** * Adds a change listener to a RealmObject that will be triggered if any value field or referenced RealmObject field * is changed, or the RealmList field itself is changed. + *

                      + * Registering a change listener will not prevent the underlying RealmObject from being garbage collected. + * If the RealmObject is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + *

                      + *

                      +     * {@code
                      +     * public class MyActivity extends Activity {
                      +     *
                      +     *     private Person person; // Strong reference to keep listeners alive
                      +     *
                      +     *     \@Override
                      +     *     protected void onCreate(Bundle savedInstanceState) {
                      +     *       super.onCreate(savedInstanceState);
                      +     *       person = realm.where(Person.class).findFirst();
                      +     *       person.addChangeListener(new RealmChangeListener() {
                      +     *           \@Override
                      +     *           public void onChange(Person person) {
                      +     *               // React to change
                      +     *           }
                      +     *       });
                      +     *     }
                      +     * }
                      +     * }
                      +     * 
                      * * @param object RealmObject to add listener to. * @param listener the change listener to be notified. diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 60c8c3b1fa..c8f047df1c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -125,6 +125,31 @@ public boolean load() { /** * Adds a change listener to this {@link RealmResults}. + *

                      + * Registering a change listener will not prevent the underlying RealmResults from being garbage collected. + * If the RealmResults is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + *

                      + *

                      +     * {@code
                      +     * public class MyActivity extends Activity {
                      +     *
                      +     *     private RealmResults results; // Strong reference to keep listeners alive
                      +     *
                      +     *     \@Override
                      +     *     protected void onCreate(Bundle savedInstanceState) {
                      +     *       super.onCreate(savedInstanceState);
                      +     *       results = realm.where(Person.class).findAllAsync();
                      +     *       results.addChangeListener(new RealmChangeListener>() {
                      +     *           \@Override
                      +     *           public void onChange(RealmResults persons) {
                      +     *               // React to change
                      +     *           }
                      +     *       });
                      +     *     }
                      +     * }
                      +     * }
                      +     * 
                      * * @param listener the change listener to be notified. * @throws IllegalArgumentException if the change listener is {@code null}. @@ -138,6 +163,31 @@ public void addChangeListener(RealmChangeListener> listener) { /** * Adds a change listener to this {@link RealmResults}. + *

                      + * Registering a change listener will not prevent the underlying RealmResults from being garbage collected. + * If the RealmResults is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + *

                      + *

                      +     * {@code
                      +     * public class MyActivity extends Activity {
                      +     *
                      +     *     private RealmResults results; // Strong reference to keep listeners alive
                      +     *
                      +     *     \@Override
                      +     *     protected void onCreate(Bundle savedInstanceState) {
                      +     *       super.onCreate(savedInstanceState);
                      +     *       results = realm.where(Person.class).findAllAsync();
                      +     *       results.addChangeListener(new OrderedRealmCollectionChangeListener>() {
                      +     *           \@Override
                      +     *           public void onChange(RealmResults persons, OrderedCollectionChangeSet changeSet) {
                      +     *               // React to change
                      +     *           }
                      +     *       });
                      +     *     }
                      +     * }
                      +     * }
                      +     * 
                      * * @param listener the change listener to be notified. * @throws IllegalArgumentException if the change listener is {@code null}. From 8d3e1738d087e95e16ee7084a863963b8ceac3b4 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Fri, 12 May 2017 17:12:42 -0700 Subject: [PATCH 0689/2110] Make the proxy toString methods reveal themselves (#4623) * Make the proxy toString methods reveal themselves * Fix preprocessor UTs * Address PR comments --- CHANGELOG.md | 4 ++++ .../java/io/realm/processor/RealmProxyClassGenerator.java | 2 +- .../src/test/resources/io/realm/AllTypesRealmProxy.java | 2 +- .../src/test/resources/io/realm/BooleansRealmProxy.java | 2 +- .../src/test/resources/io/realm/NullTypesRealmProxy.java | 2 +- .../androidTest/java/io/realm/DynamicRealmObjectTests.java | 2 +- .../src/androidTest/java/io/realm/RealmObjectTests.java | 5 +++-- .../src/main/java/io/realm/DynamicRealmObject.java | 6 +++--- 8 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba95af38a4..a1cc8f3842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 3.1.5 (YYYY-MM-DD) +## Enhancements + +* The `toString()` methods for the standard and dynamic proxies now print "proxy", or "dynamic" before the left bracket enclosing the data. + ## Bug fixes * `@LinkingObjects` annotation now also works with Kotlin (#4611). diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 44772ce258..f6cef2e42a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -1732,7 +1732,7 @@ private void emitToStringMethod(JavaWriter writer) throws IOException { .beginControlFlow("if (!RealmObject.isValid(this))") .emitStatement("return \"Invalid object\"") .endControlFlow(); - writer.emitStatement("StringBuilder stringBuilder = new StringBuilder(\"%s = [\")", simpleClassName); + writer.emitStatement("StringBuilder stringBuilder = new StringBuilder(\"%s = proxy[\")", simpleClassName); Collection fields = metadata.getFields(); int i = fields.size() - 1; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 8eeb68202c..301a6ff6aa 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -1223,7 +1223,7 @@ public String toString() { if (!RealmObject.isValid(this)) { return "Invalid object"; } - StringBuilder stringBuilder = new StringBuilder("AllTypes = ["); + StringBuilder stringBuilder = new StringBuilder("AllTypes = proxy["); stringBuilder.append("{columnString:"); stringBuilder.append(realmGet$columnString() != null ? realmGet$columnString() : "null"); stringBuilder.append("}"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 5a07ed0d92..02003b1170 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -496,7 +496,7 @@ public String toString() { if (!RealmObject.isValid(this)) { return "Invalid object"; } - StringBuilder stringBuilder = new StringBuilder("Booleans = ["); + StringBuilder stringBuilder = new StringBuilder("Booleans = proxy["); stringBuilder.append("{done:"); stringBuilder.append(realmGet$done()); stringBuilder.append("}"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index c91b122f0c..76996ed7e7 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -2059,7 +2059,7 @@ public String toString() { if (!RealmObject.isValid(this)) { return "Invalid object"; } - StringBuilder stringBuilder = new StringBuilder("NullTypes = ["); + StringBuilder stringBuilder = new StringBuilder("NullTypes = proxy["); stringBuilder.append("{fieldStringNotNull:"); stringBuilder.append(realmGet$fieldStringNotNull()); stringBuilder.append("}"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index cdd594baf3..8651633893 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -1220,7 +1220,7 @@ public void toString_test() { // Checks that toString() doesn't crash, and does simple formatting checks. We cannot compare to a set String as // eg. the byte array will be allocated each time it is accessed. String str = dObjTyped.toString(); - assertTrue(str.startsWith("AllJavaTypes = [")); + assertTrue(str.startsWith("AllJavaTypes = dynamic[")); assertTrue(str.endsWith("}]")); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index f99ae25334..62a9f3ad0e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -456,8 +456,9 @@ public void toString_cyclicObject() { realm.beginTransaction(); CyclicType foo = createCyclicData(); realm.commitTransaction(); - String expected = "CyclicType = [{id:0},{name:Foo},{date:null},{object:CyclicType},{otherObject:null},{objects:RealmList[0]}]"; - assertEquals(expected, foo.toString()); + assertEquals( + "CyclicType = proxy[{id:0},{name:Foo},{date:null},{object:CyclicType},{otherObject:null},{objects:RealmList[0]}]", + foo.toString()); } @Test diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 0d1d518201..485455ae5c 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -873,7 +873,7 @@ public String toString() { } final String className = Table.tableNameToClassName(proxyState.getRow$realm().getTable().getName()); - StringBuilder sb = new StringBuilder(className + " = ["); + StringBuilder sb = new StringBuilder(className + " = dynamic["); String[] fields = getFieldNames(); for (String field : fields) { long columnIndex = proxyState.getRow$realm().getColumnIndex(field); @@ -918,9 +918,9 @@ public String toString() { sb.append("?"); break; } - sb.append("}, "); + sb.append("},"); } - sb.replace(sb.length() - 2, sb.length(), ""); + sb.replace(sb.length() - 1, sb.length(), ""); sb.append("]"); return sb.toString(); } From e658aebdfed6649c8f69bfe1c46ae7d99ae386d6 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 16 May 2017 13:33:38 +0800 Subject: [PATCH 0690/2110] Merge entries in changelog --- CHANGELOG.md | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 261a1bfd36..88eb9599e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,25 +13,16 @@ * Added `DynamicRealmObject#linkingObjects(String,String)` to support linking objects on `DynamicRealm` (#4492). * Added support for read only Realms using `RealmConfiguration.Builder.readOnly()` and `SyncConfiguration.Builder.readOnly()`(#1147). * Change listeners will now auto-expand variable names to be more descriptive when using Android Studio. +* The `toString()` methods for the standard and dynamic proxies now print "proxy", or "dynamic" before the left bracket enclosing the data. ### Bug Fixes +* `@LinkingObjects` annotation now also works with Kotlin (#4611). + ### Internal * Use separated locks for different `RealmCache`s ($4551). - -# 3.1.5 (YYYY-MM-DD) - -## Enhancements - -* The `toString()` methods for the standard and dynamic proxies now print "proxy", or "dynamic" before the left bracket enclosing the data. - -## Bug fixes - -* `@LinkingObjects` annotation now also works with Kotlin (#4611). - - ## 3.1.4 (2017-05-04) ## Bug fixes From 2031db12007508519a341637b335957d7e121494 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 16 May 2017 13:33:44 +0800 Subject: [PATCH 0691/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88eb9599e9..c699fcdb82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.2.0 (YYYY-MM-DD) +## 3.2.0 (2017-05-16) ### Deprecated From 4e8ab5c01616a84370fb79a258fca50116fdf9c4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 16 May 2017 13:33:45 +0800 Subject: [PATCH 0692/2110] Release v3.2.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e1f3bbde86..a4f52a5dbb 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.0-SNAPSHOT +3.2.0 \ No newline at end of file From 5751aea88c79552d6a5c852ba478a5b23435c2d7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 16 May 2017 13:33:45 +0800 Subject: [PATCH 0693/2110] Prepare next release v3.2.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a4f52a5dbb..14900cee60 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.0 \ No newline at end of file +3.2.1-SNAPSHOT \ No newline at end of file From fef998dcbf100b657d18727bed8f3d8d44e7f1f3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 16 May 2017 15:38:49 +0800 Subject: [PATCH 0694/2110] Prepare for next iteration --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c699fcdb82..46975cdbae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 3.2.1 (YYYY-MM-DD) + +### Deprecated + +### Enhancements + +### Bug Fixes + +### Internal + ## 3.2.0 (2017-05-16) ### Deprecated From ad0e89bf6b3bb02a4728a67934f4c5548b1eaa6b Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 16 May 2017 01:59:58 -0700 Subject: [PATCH 0695/2110] fix ambiguous method calls warned by Java 8 compiler (#4647) * fix ambiguous method calls warned by Java 8 compiler * fix more --- .../io/realm/DynamicRealmObjectTests.java | 12 +++--- .../java/io/realm/DynamicRealmTests.java | 40 +++++++++---------- .../java/io/realm/internal/JNIQueryTest.java | 2 +- .../java/io/realm/internal/JNIRowTest.java | 8 ++-- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 8651633893..3a04a45e27 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -937,27 +937,27 @@ public void untypedGetterSetter() { break; case SHORT: dObj.set(AllJavaTypes.FIELD_SHORT, (short) 42); - assertEquals(Long.parseLong("42"), dObj.get(AllJavaTypes.FIELD_SHORT)); + assertEquals(Long.parseLong("42"), dObj. get(AllJavaTypes.FIELD_SHORT).longValue()); break; case INT: dObj.set(AllJavaTypes.FIELD_INT, 42); - assertEquals(Long.parseLong("42"), dObj.get(AllJavaTypes.FIELD_INT)); + assertEquals(Long.parseLong("42"), dObj. get(AllJavaTypes.FIELD_INT).longValue()); break; case LONG: dObj.set(AllJavaTypes.FIELD_LONG, 42L); - assertEquals(Long.parseLong("42"), dObj.get(AllJavaTypes.FIELD_LONG)); + assertEquals(Long.parseLong("42"), dObj. get(AllJavaTypes.FIELD_LONG).longValue()); break; case BYTE: dObj.set(AllJavaTypes.FIELD_BYTE, (byte) 4); - assertEquals(Long.parseLong("4"), dObj.get(AllJavaTypes.FIELD_BYTE)); + assertEquals(Long.parseLong("4"), dObj. get(AllJavaTypes.FIELD_BYTE).longValue()); break; case FLOAT: dObj.set(AllJavaTypes.FIELD_FLOAT, 1.23f); - assertEquals(Float.parseFloat("1.23"), dObj.get(AllJavaTypes.FIELD_FLOAT)); + assertEquals(Float.parseFloat("1.23"), dObj. get(AllJavaTypes.FIELD_FLOAT), Float.MIN_NORMAL); break; case DOUBLE: dObj.set(AllJavaTypes.FIELD_DOUBLE, 1.234d); - assertEquals(Double.parseDouble("1.234"), dObj.get(AllJavaTypes.FIELD_DOUBLE)); + assertEquals(Double.parseDouble("1.234"), dObj.get(AllJavaTypes.FIELD_DOUBLE), Double.MIN_NORMAL); break; case STRING: dObj.set(AllJavaTypes.FIELD_STRING, "str"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 400f618dc9..b98b5edbfa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -476,27 +476,27 @@ public void run() { @Override public void onChange(RealmResults object) { assertEquals("data 0", realmResults1.get(0).get(AllTypes.FIELD_STRING)); - assertEquals(3L, realmResults1.get(0).get(AllTypes.FIELD_LONG)); + assertEquals(3L, realmResults1.get(0). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 0", realmResults1.get(1).get(AllTypes.FIELD_STRING)); - assertEquals(2L, realmResults1.get(1).get(AllTypes.FIELD_LONG)); + assertEquals(2L, realmResults1.get(1). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 0", realmResults1.get(2).get(AllTypes.FIELD_STRING)); - assertEquals(0L, realmResults1.get(2).get(AllTypes.FIELD_LONG)); + assertEquals(0L, realmResults1.get(2). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 1", realmResults1.get(3).get(AllTypes.FIELD_STRING)); - assertEquals(4L, realmResults1.get(3).get(AllTypes.FIELD_LONG)); + assertEquals(4L, realmResults1.get(3). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 1", realmResults1.get(4).get(AllTypes.FIELD_STRING)); - assertEquals(3L, realmResults1.get(4).get(AllTypes.FIELD_LONG)); + assertEquals(3L, realmResults1.get(4). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 1", realmResults1.get(5).get(AllTypes.FIELD_STRING)); - assertEquals(1L, realmResults1.get(5).get(AllTypes.FIELD_LONG)); + assertEquals(1L, realmResults1.get(5). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 1", realmResults1.get(6).get(AllTypes.FIELD_STRING)); - assertEquals(0L, realmResults1.get(6).get(AllTypes.FIELD_LONG)); + assertEquals(0L, realmResults1.get(6). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 2", realmResults1.get(7).get(AllTypes.FIELD_STRING)); - assertEquals(4L, realmResults1.get(7).get(AllTypes.FIELD_LONG)); + assertEquals(4L, realmResults1.get(7). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 2", realmResults1.get(8).get(AllTypes.FIELD_STRING)); - assertEquals(2L, realmResults1.get(8).get(AllTypes.FIELD_LONG)); + assertEquals(2L, realmResults1.get(8). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 2", realmResults1.get(9).get(AllTypes.FIELD_STRING)); - assertEquals(1L, realmResults1.get(9).get(AllTypes.FIELD_LONG)); + assertEquals(1L, realmResults1.get(9). get(AllTypes.FIELD_LONG).longValue()); signalCallbackDone.run(); } @@ -506,27 +506,27 @@ public void onChange(RealmResults object) { @Override public void onChange(RealmResults object) { assertEquals("data 2", realmResults2.get(0).get(AllTypes.FIELD_STRING)); - assertEquals(1L, realmResults2.get(0).get(AllTypes.FIELD_LONG)); + assertEquals(1L, realmResults2.get(0). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 2", realmResults2.get(1).get(AllTypes.FIELD_STRING)); - assertEquals(2L, realmResults2.get(1).get(AllTypes.FIELD_LONG)); + assertEquals(2L, realmResults2.get(1). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 2", realmResults2.get(2).get(AllTypes.FIELD_STRING)); - assertEquals(4L, realmResults2.get(2).get(AllTypes.FIELD_LONG)); + assertEquals(4L, realmResults2.get(2). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 1", realmResults2.get(3).get(AllTypes.FIELD_STRING)); - assertEquals(0L, realmResults2.get(3).get(AllTypes.FIELD_LONG)); + assertEquals(0L, realmResults2.get(3). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 1", realmResults2.get(4).get(AllTypes.FIELD_STRING)); - assertEquals(1L, realmResults2.get(4).get(AllTypes.FIELD_LONG)); + assertEquals(1L, realmResults2.get(4). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 1", realmResults2.get(5).get(AllTypes.FIELD_STRING)); - assertEquals(3L, realmResults2.get(5).get(AllTypes.FIELD_LONG)); + assertEquals(3L, realmResults2.get(5). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 1", realmResults2.get(6).get(AllTypes.FIELD_STRING)); - assertEquals(4L, realmResults2.get(6).get(AllTypes.FIELD_LONG)); + assertEquals(4L, realmResults2.get(6). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 0", realmResults2.get(7).get(AllTypes.FIELD_STRING)); - assertEquals(0L, realmResults2.get(7).get(AllTypes.FIELD_LONG)); + assertEquals(0L, realmResults2.get(7). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 0", realmResults2.get(8).get(AllTypes.FIELD_STRING)); - assertEquals(2L, realmResults2.get(8).get(AllTypes.FIELD_LONG)); + assertEquals(2L, realmResults2.get(8). get(AllTypes.FIELD_LONG).longValue()); assertEquals("data 0", realmResults2.get(9).get(AllTypes.FIELD_STRING)); - assertEquals(3L, realmResults2.get(9).get(AllTypes.FIELD_LONG)); + assertEquals(3L, realmResults2.get(9). get(AllTypes.FIELD_LONG).longValue()); signalCallbackDone.run(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index 6d1e8570f0..33e47f88a9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -71,7 +71,7 @@ public void testShouldQuery() { assertEquals(14+16, cnt); double avg = query.averageInt(0); - assertEquals(15.0, avg); + assertEquals(15.0, avg, Double.MIN_NORMAL); // TODO: Add tests with all parameters } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java index 8b9c613d0b..3d8902ea90 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java @@ -48,8 +48,8 @@ public void testRow() { assertEquals("abc", row.getString(0)); assertEquals(3, row.getLong(1)); - assertEquals((float) 1.2, row.getFloat(2), 0.0001); - assertEquals(1.3, row.getDouble(3)); + assertEquals(1.2F, row.getFloat(2), Float.MIN_NORMAL); + assertEquals(1.3, row.getDouble(3), Double.MIN_NORMAL); assertEquals(true, row.getBoolean(4)); assertEquals(new Date(0), row.getDate(5)); MoreAsserts.assertEquals(data, row.getBinaryByteArray(6)); @@ -67,8 +67,8 @@ public void testRow() { assertEquals("a", row.getString(0)); assertEquals(1, row.getLong(1)); - assertEquals((float) 8.8, row.getFloat(2), 0.0001); - assertEquals(9.9, row.getDouble(3)); + assertEquals(8.8F, row.getFloat(2), Float.MIN_NORMAL); + assertEquals(9.9, row.getDouble(3), Double.MIN_NORMAL); assertEquals(false, row.getBoolean(4)); assertEquals(new Date(10000), row.getDate(5)); MoreAsserts.assertEquals(newData, row.getBinaryByteArray(6)); From 1852c5949f74d62756ae809917eac72e6ddc138e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 16 May 2017 17:11:24 +0800 Subject: [PATCH 0696/2110] Refactor object creation into OsObject (#4632) - Hide the addEmptyRow from java to support future stable ID. - Add bulk insertion benchmark. This won't be the final design of internal object creation API, when integration of OS object accessor, the internal API might be changed a bit since it doesn't look nice at all -- 5 params for the JNI call to create an object with integer primary key! --- CHANGELOG.md | 2 + .../processor/RealmProxyClassGenerator.java | 9 +- .../io/realm/AllTypesRealmProxy.java | 9 +- .../io/realm/BooleansRealmProxy.java | 9 +- .../io/realm/NullTypesRealmProxy.java | 9 +- .../resources/io/realm/SimpleRealmProxy.java | 9 +- .../io/realm/RealmNullPrimaryKeyTests.java | 2 +- .../androidTest/java/io/realm/RealmTests.java | 2 +- .../io/realm/internal/PrimaryKeyTests.java | 15 +- .../benchmarks/RealmInsertBenchmark.java | 105 +++++++++++ .../main/cpp/io_realm_internal_OsObject.cpp | 166 +++++++++++++++++- .../src/main/java/io/realm/DynamicRealm.java | 10 +- .../src/main/java/io/realm/Realm.java | 14 +- .../main/java/io/realm/internal/OsObject.java | 114 +++++++++++- .../main/java/io/realm/internal/Table.java | 93 +--------- 15 files changed, 437 insertions(+), 131 deletions(-) create mode 100644 realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmInsertBenchmark.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 46975cdbae..404f9ffc0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Enhancements +* Not in transaction illegal state exception message changed to "Cannot modify managed objects outside of a write transaction.". + ### Bug Fixes ### Internal diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 6347a049ed..b22fc4f493 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -77,6 +77,7 @@ public void generate() throws IOException, UnsupportedOperationException { imports.add("io.realm.internal.RealmObjectProxy"); imports.add("io.realm.internal.Row"); imports.add("io.realm.internal.Table"); + imports.add("io.realm.internal.OsObject"); imports.add("io.realm.internal.SharedRealm"); if (!metadata.getBacklinkFields().isEmpty()) { imports.add("io.realm.internal.UncheckedRow"); @@ -1487,9 +1488,11 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP writer.beginControlFlow("if (rowIndex == Table.NO_MATCH)"); if (Utils.isString(metadata.getPrimaryKey())) { - writer.emitStatement("rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false)"); + writer.emitStatement( + "rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue)"); } else { - writer.emitStatement("rowIndex = table.addEmptyRowWithPrimaryKey(((%s) object).%s(), false)", + writer.emitStatement( + "rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, ((%s) object).%s())", interfaceName, primaryKeyGetter); } @@ -1501,7 +1504,7 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP writer.endControlFlow(); writer.emitStatement("cache.put(object, rowIndex)"); } else { - writer.emitStatement("long rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1)"); + writer.emitStatement("long rowIndex = OsObject.createRow(realm.sharedRealm, table)"); writer.emitStatement("cache.put(object, rowIndex)"); } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index ee0ab58e19..5b6b6d8d23 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -10,6 +10,7 @@ import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; +import io.realm.internal.OsObject; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -891,7 +892,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == Table.NO_MATCH) { - rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); + rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue); } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } @@ -1013,7 +1014,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == Table.NO_MATCH) { - rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue, false); + rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue); } cache.put(object, rowIndex); Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong(), false); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 04774d6b8e..c39c1c2632 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -10,6 +10,7 @@ import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; +import io.realm.internal.OsObject; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -392,7 +393,7 @@ public static long insert(Realm realm, some.test.Booleans object, Map objects, M cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); + long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done(), false); Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady(), false); @@ -430,7 +431,7 @@ public static long insertOrUpdate(Realm realm, some.test.Booleans object, Map ob cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); + long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done(), false); Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady(), false); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 7970e35530..2b215a4fe8 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -10,6 +10,7 @@ import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; +import io.realm.internal.OsObject; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -1495,7 +1496,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map objects, M cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); + long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { @@ -1703,7 +1704,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map ob cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); + long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index b5bb229267..af7a9d3b4d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -10,6 +10,7 @@ import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; +import io.realm.internal.OsObject; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -298,7 +299,7 @@ public static long insert(Realm realm, some.test.Simple object, Map objects, M cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); + long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); String realmGet$name = ((SimpleRealmProxyInterface)object).realmGet$name(); if (realmGet$name != null) { @@ -338,7 +339,7 @@ public static long insertOrUpdate(Realm realm, some.test.Simple object, Map ob cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = Table.nativeAddEmptyRow(tableNativePtr, 1); + long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); String realmGet$name = ((SimpleRealmProxyInterface)object).realmGet$name(); if (realmGet$name != null) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java index dc37e6c8ad..74e6df85f3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java @@ -202,7 +202,7 @@ public void createObject_duplicatedNullPrimaryKeyThrows() throws NoSuchMethodExc realm.createObject(testClazz, null); fail("Null value as primary key already exists."); } catch (RealmPrimaryKeyConstraintException expected) { - assertEquals("Value already exists: null", expected.getMessage()); + assertEquals("Primary key value already exists: 'null' .", expected.getMessage()); } finally { realm.cancelTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index abf5b79c5d..df3d0ad061 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -1308,7 +1308,7 @@ public void copyToRealm_duplicatedNullPrimaryKeyThrows() { } fail("Null value as primary key already exists."); } catch (RealmPrimaryKeyConstraintException expected) { - assertEquals("Value already exists: null", expected.getMessage()); + assertEquals("Primary key value already exists: 'null' .", expected.getMessage()); } finally { realm.cancelTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index e04e8b4d4f..e13b17bfca 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -31,7 +31,6 @@ import io.realm.RealmConfiguration; import io.realm.RealmFieldType; -import io.realm.exceptions.RealmError; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.rule.TestRealmConfigurationFactory; @@ -120,7 +119,7 @@ public void removingPrimaryKeyRemovesConstraint_typeSetters() { public void addEmptyRowWithPrimaryKeyWrongTypeStringThrows() { Table t = getTableWithStringPrimaryKey(); try { - t.addEmptyRowWithPrimaryKey(42); + OsObject.createWithPrimaryKey(sharedRealm, t, 42); fail(); } catch (IllegalArgumentException ignored) { } @@ -130,7 +129,7 @@ public void addEmptyRowWithPrimaryKeyWrongTypeStringThrows() { @Test public void addEmptyRowWithPrimaryKeyNullString() { Table t = getTableWithStringPrimaryKey(); - t.addEmptyRowWithPrimaryKey(null); + OsObject.createWithPrimaryKey(sharedRealm, t, null); assertEquals(1, t.size()); sharedRealm.cancelTransaction(); } @@ -139,7 +138,7 @@ public void addEmptyRowWithPrimaryKeyNullString() { public void addEmptyRowWithPrimaryKeyWrongTypeIntegerThrows() { Table t = getTableWithIntegerPrimaryKey(); try { - t.addEmptyRowWithPrimaryKey("Foo"); + OsObject.createWithPrimaryKey(sharedRealm, t, "Foo"); fail(); } catch (IllegalArgumentException ignored) { } @@ -149,18 +148,18 @@ public void addEmptyRowWithPrimaryKeyWrongTypeIntegerThrows() { @Test public void addEmptyRowWithPrimaryKeyString() { Table t = getTableWithStringPrimaryKey(); - long rowIndex = t.addEmptyRowWithPrimaryKey("Foo"); + UncheckedRow row = OsObject.createWithPrimaryKey(sharedRealm, t, "Foo"); assertEquals(1, t.size()); - assertEquals("Foo", t.getUncheckedRow(rowIndex).getString(0)); + assertEquals("Foo", row.getString(0)); sharedRealm.cancelTransaction(); } @Test public void addEmptyRowWithPrimaryKeyLong() { Table t = getTableWithIntegerPrimaryKey(); - long rowIndex = t.addEmptyRowWithPrimaryKey(42); + UncheckedRow row = OsObject.createWithPrimaryKey(sharedRealm, t, 42); assertEquals(1, t.size()); - assertEquals(42L, t.getUncheckedRow(rowIndex).getLong(0)); + assertEquals(42L, row.getLong(0)); sharedRealm.cancelTransaction(); } diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmInsertBenchmark.java b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmInsertBenchmark.java new file mode 100644 index 0000000000..8814a1b280 --- /dev/null +++ b/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmInsertBenchmark.java @@ -0,0 +1,105 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks; + +import android.support.test.InstrumentationRegistry; + +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +import dk.ilios.spanner.AfterExperiment; +import dk.ilios.spanner.BeforeExperiment; +import dk.ilios.spanner.Benchmark; +import dk.ilios.spanner.BenchmarkConfiguration; +import dk.ilios.spanner.SpannerConfig; +import dk.ilios.spanner.junit.SpannerRunner; +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.benchmarks.config.BenchmarkConfig; +import io.realm.entities.AllTypes; +import io.realm.entities.AllTypesPrimaryKey; + +@RunWith(SpannerRunner.class) +public class RealmInsertBenchmark { + + @BenchmarkConfiguration + public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); + + private Realm realm; + private static final int COLLECTION_SIZE = 100; + private List noPkObjects = new ArrayList(COLLECTION_SIZE); + private List pkObjects = new ArrayList(COLLECTION_SIZE); + + @BeforeExperiment + public void before() { + Realm.init(InstrumentationRegistry.getTargetContext()); + RealmConfiguration config = new RealmConfiguration.Builder().build(); + Realm.deleteRealm(config); + realm = Realm.getInstance(config); + + for (int i = 0; i < COLLECTION_SIZE; i++) { + noPkObjects.add(new AllTypes()); + } + + for (int i = 0; i < COLLECTION_SIZE; i++) { + AllTypesPrimaryKey allTypesPrimaryKey = new AllTypesPrimaryKey(); + allTypesPrimaryKey.setColumnLong(i); + pkObjects.add(allTypesPrimaryKey); + } + + realm.beginTransaction(); + } + + @AfterExperiment + public void after() { + realm.cancelTransaction(); + realm.close(); + } + + @Benchmark + public void insertNoPrimaryKey(long reps) { + AllTypes allTypes = new AllTypes(); + for (long i = 0; i < reps; i++) { + realm.insert(allTypes); + } + } + + @Benchmark + public void insertNoPrimaryKeyList(long reps) { + for (long i = 0; i < reps; i++) { + realm.insert(noPkObjects); + } + } + + @Benchmark + public void insertWithPrimaryKey(long reps) { + AllTypesPrimaryKey allTypesPrimaryKey = new AllTypesPrimaryKey(); + for (long i = 0; i < reps; i++) { + allTypesPrimaryKey.setColumnLong(i); + realm.insertOrUpdate(allTypesPrimaryKey); + } + } + + @Benchmark + public void insertOrUpdateWithPrimaryKeyList(long reps) { + for (long i = 0; i < reps; i++) { + realm.insertOrUpdate(pkObjects); + } + } +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 62887708c9..f6b040603f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -19,14 +19,17 @@ #include #include #include +#include #include "util.hpp" #include "jni_util/java_global_weak_ref.hpp" #include "jni_util/java_method.hpp" +#include "jni_util/java_class.hpp" using namespace realm; using namespace realm::jni_util; +using namespace realm::_impl; // We need to control the life cycle of Object, weak ref of Java OsObject and the NotificationToken. // Wrap all three together, so when the Java object gets GCed, all three of them will be invalidated. @@ -152,6 +155,90 @@ static void finalize_object(jlong ptr) delete reinterpret_cast(ptr); } +static inline size_t do_create_row(jlong shared_realm_ptr, jlong table_ptr) +{ + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& table = *(reinterpret_cast(table_ptr)); + shared_realm->verify_in_write(); + return table.add_empty_row(); +} + +template +static void throw_duplicated_primary_key_exception(JNIEnv* env, T value) +{ + static JavaClass dup_pk_exception(env, "io/realm/exceptions/RealmPrimaryKeyConstraintException"); + env->ThrowNew(dup_pk_exception, format("Primary key value already exists: %1 .", value).c_str()); +} + +static inline size_t do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ptr, + jlong pk_column_ndx, jlong pk_value, jboolean is_pk_null) +{ + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& table = *(reinterpret_cast(table_ptr)); + shared_realm->verify_in_write(); // throws + if (is_pk_null && !TBL_AND_COL_NULLABLE(env, &table, pk_column_ndx)) { + return realm::npos; + } + + if (is_pk_null) { + if (table.find_first_null(pk_column_ndx) != realm::npos) { + throw_duplicated_primary_key_exception(env, "'null'"); + return realm::npos; + } + } + else { + if (table.find_first_int(pk_column_ndx, pk_value) != realm::npos) { + throw_duplicated_primary_key_exception(env, reinterpret_cast(pk_value)); + return realm::npos; + } + } + + size_t row_ndx = table.add_empty_row(); + + if (is_pk_null) { + table.set_null_unique(pk_column_ndx, row_ndx); + } + else { + table.set_int_unique(pk_column_ndx, row_ndx, pk_value); + } + return row_ndx; +} + +static inline size_t do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ptr, + jlong pk_column_ndx, jstring pk_value) +{ + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto& table = *(reinterpret_cast(table_ptr)); + JStringAccessor str_accessor(env, pk_value); // throws + shared_realm->verify_in_write(); // throws + if (!pk_value && !TBL_AND_COL_NULLABLE(env, &table, pk_column_ndx)) { + return realm::npos; + } + + if (pk_value) { + if (table.find_first_string(pk_column_ndx, str_accessor) != realm::npos) { + throw_duplicated_primary_key_exception(env, str_accessor.operator std::string()); + return realm::npos; + } + } + else { + if (table.find_first_null(pk_column_ndx) != realm::npos) { + throw_duplicated_primary_key_exception(env, "'null'"); + return realm::npos; + } + } + + size_t row_ndx = table.add_empty_row(); + if (pk_value) { + table.set_string_unique(pk_column_ndx, row_ndx, str_accessor); + } + else { + table.set_string_unique(pk_column_ndx, row_ndx, null{}); + } + + return row_ndx; +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() @@ -159,7 +246,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeGetFinalizerPtr(JN } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreate(JNIEnv*, jclass, jlong shared_realm_ptr, - jlong row_ptr) + jlong row_ptr) { TR_ENTER_PTR(row_ptr) @@ -176,7 +263,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreate(JNIEnv*, jc } JNIEXPORT void JNICALL Java_io_realm_internal_OsObject_nativeStartListening(JNIEnv* env, jobject instance, - jlong native_ptr) + jlong native_ptr) { TR_ENTER_PTR(native_ptr) @@ -204,3 +291,78 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObject_nativeStopListening(JNIEn } CATCH_STD() } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRow(JNIEnv* env, jclass, jlong shared_realm_ptr, + jlong table_ptr) +{ + try { + return do_create_row(shared_realm_ptr, table_ptr); + } + CATCH_STD() + return -1; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObject(JNIEnv* env, jclass, + jlong shared_realm_ptr, jlong table_ptr) +{ + try { + size_t row_ndx = do_create_row(shared_realm_ptr, table_ptr); + auto& table = *(reinterpret_cast(table_ptr)); + return reinterpret_cast(new Row(table[row_ndx])); + } + CATCH_STD() + return 0; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWithLongPrimaryKey( + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong pk_column_ndx, jlong pk_value, + jboolean is_pk_null) +{ + try { + auto& table = *(reinterpret_cast(table_ptr)); + size_t row_ndx = + do_create_row_with_primary_key(env, shared_realm_ptr, table_ptr, pk_column_ndx, pk_value, is_pk_null); + if (row_ndx != realm::npos) { + return reinterpret_cast(new Row(table[row_ndx])); + } + } + CATCH_STD() + return 0; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithLongPrimaryKey( + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong pk_column_ndx, jlong pk_value, + jboolean is_pk_null) +{ + try { + return do_create_row_with_primary_key(env, shared_realm_ptr, table_ptr, pk_column_ndx, pk_value, is_pk_null); + } + CATCH_STD() + return realm::npos; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWithStringPrimaryKey( + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong pk_column_ndx, jstring pk_value) +{ + try { + auto& table = *(reinterpret_cast(table_ptr)); + size_t row_ndx = do_create_row_with_primary_key(env, shared_realm_ptr, table_ptr, pk_column_ndx, pk_value); + if (row_ndx != realm::npos) { + return reinterpret_cast(new Row(table[row_ndx])); + } + } + CATCH_STD() + + return 0; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithStringPrimaryKey( + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong pk_column_ndx, jstring pk_value) +{ + try { + return do_create_row_with_primary_key(env, shared_realm_ptr, table_ptr, pk_column_ndx, pk_value); + } + CATCH_STD() + + return realm::npos; +} diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 718b7044c7..f9b3451d58 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -18,6 +18,8 @@ import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; +import io.realm.internal.CheckedRow; +import io.realm.internal.OsObject; import io.realm.internal.Table; import io.realm.log.RealmLog; import rx.Observable; @@ -106,8 +108,8 @@ public DynamicRealmObject createObject(String className) { throw new RealmException(String.format("'%s' has a primary key, use" + " 'createObject(String, Object)' instead.", className)); } - long rowIndex = table.addEmptyRow(); - return get(DynamicRealmObject.class, className, rowIndex); + + return new DynamicRealmObject(this, CheckedRow.getFromRow(OsObject.create(sharedRealm, table))); } /** @@ -123,8 +125,8 @@ public DynamicRealmObject createObject(String className) { */ public DynamicRealmObject createObject(String className, Object primaryKeyValue) { Table table = schema.getTable(className); - long index = table.addEmptyRowWithPrimaryKey(primaryKeyValue); - return new DynamicRealmObject(this, table.getCheckedRow(index)); + return new DynamicRealmObject(this, + CheckedRow.getFromRow(OsObject.createWithPrimaryKey(sharedRealm, table, primaryKeyValue))); } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 51a2707b1a..c3af8bfb8b 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -52,6 +52,7 @@ import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; import io.realm.internal.ObjectServerFacade; +import io.realm.internal.OsObject; import io.realm.internal.RealmCore; import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; @@ -986,8 +987,10 @@ E createObjectInternal( throw new RealmException(String.format("'%s' has a primary key, use" + " 'createObject(Class, Object)' instead.", table.getClassName())); } - long rowIndex = table.addEmptyRow(); - return get(clazz, rowIndex, acceptDefaultValue, excludeFields); + return configuration.getSchemaMediator().newInstance(clazz, this, + OsObject.create(sharedRealm, table), + schema.getColumnInfo(clazz), + acceptDefaultValue, excludeFields); } /** @@ -1030,8 +1033,11 @@ E createObjectInternal( boolean acceptDefaultValue, List excludeFields) { Table table = schema.getTable(clazz); - long rowIndex = table.addEmptyRowWithPrimaryKey(primaryKeyValue); - return get(clazz, rowIndex, acceptDefaultValue, excludeFields); + + return configuration.getSchemaMediator().newInstance(clazz, this, + OsObject.createWithPrimaryKey(sharedRealm, table, primaryKeyValue), + schema.getColumnInfo(clazz), + acceptDefaultValue, excludeFields); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index 33e1807b04..bcfcaaa48f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -17,12 +17,14 @@ package io.realm.internal; import io.realm.ObjectChangeSet; +import io.realm.RealmFieldType; import io.realm.RealmModel; import io.realm.RealmObjectChangeListener; +import io.realm.exceptions.RealmException; /** - * Java wrapper for Object Store's {@code Object} class. Currently it is only used for object notifications. + * Java wrapper for Object Store's {@code Object} class. */ @KeepMember public class OsObject implements NativeObject { @@ -142,6 +144,91 @@ public void setObserverPairs(ObserverPairList pairs) { } } + // TODO: consider to return a OsObject instead when integrating with Object Store's object accessor. + /** + * Create an object in the given table which doesn't have a primary key column defined. + * + * @return a newly created {@code UncheckedRow}. + */ + public static UncheckedRow create(SharedRealm sharedRealm, Table table) { + return new UncheckedRow(sharedRealm.context, table, + nativeCreateNewObject(sharedRealm.getNativePtr(), table.getNativePtr())); + } + + /** + * Create a row in the given table which doesn't have a primary key column defined. + * This is used for the fast bulk insertion. + * + * @return a newly created row's index. + */ + public static long createRow(SharedRealm sharedRealm, Table table) { + return nativeCreateRow(sharedRealm.getNativePtr(), table.getNativePtr()); + } + + private static long getAndVerifyPrimaryKeyColumnIndex(Table table) { + long primaryKeyColumnIndex = table.getPrimaryKey(); + if (primaryKeyColumnIndex == Table.NO_PRIMARY_KEY) { + throw new IllegalStateException(table.getName() + " has no primary key defined."); + } + return primaryKeyColumnIndex; + } + + // TODO: consider to return a OsObject instead when integrating with Object Store's object accessor. + /** + * Create an object in the given table which has a primary key column defined, and set the primary key with given + * value. + * + * @return a newly created {@code UncheckedRow}. + */ + public static UncheckedRow createWithPrimaryKey(SharedRealm sharedRealm, Table table, Object primaryKeyValue) { + long primaryKeyColumnIndex = getAndVerifyPrimaryKeyColumnIndex(table); + RealmFieldType type = table.getColumnType(primaryKeyColumnIndex); + + if (type == RealmFieldType.STRING) { + if (primaryKeyValue != null && !(primaryKeyValue instanceof String)) { + throw new IllegalArgumentException("Primary key value is not a String: " + primaryKeyValue); + } + return new UncheckedRow(sharedRealm.context, table, + nativeCreateNewObjectWithStringPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), + primaryKeyColumnIndex, (String) primaryKeyValue)); + + } else if (type == RealmFieldType.INTEGER) { + long value = primaryKeyValue == null ? 0 : Long.parseLong(primaryKeyValue.toString()); + return new UncheckedRow(sharedRealm.context, table, + nativeCreateNewObjectWithLongPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), + primaryKeyColumnIndex, value, primaryKeyValue == null)); + } else { + throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); + } + } + + /** + * Create an object in the given table which has a primary key column defined, and set the primary key with given + * value. + * This is used for the fast bulk insertion. + * + * @return a newly created {@code UncheckedRow}. + */ + public static long createRowWithPrimaryKey(SharedRealm sharedRealm, Table table, Object primaryKeyValue) { + long primaryKeyColumnIndex = getAndVerifyPrimaryKeyColumnIndex(table); + RealmFieldType type = table.getColumnType(primaryKeyColumnIndex); + + if (type == RealmFieldType.STRING) { + if (primaryKeyValue != null && !(primaryKeyValue instanceof String)) { + throw new IllegalArgumentException("Primary key value is not a String: " + primaryKeyValue); + } + return nativeCreateRowWithStringPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), + primaryKeyColumnIndex, (String) primaryKeyValue); + + } else if (type == RealmFieldType.INTEGER) { + long value = primaryKeyValue == null ? 0 : Long.parseLong(primaryKeyValue.toString()); + return nativeCreateRowWithLongPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), + primaryKeyColumnIndex, value, primaryKeyValue == null); + } else { + throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); + } + } + // Called by JNI @SuppressWarnings("unused") @KeepMember @@ -156,4 +243,29 @@ private void notifyChangeListeners(String[] changedFields) { private native void nativeStartListening(long nativePtr); private native void nativeStopListening(long nativePtr); + + private static native long nativeCreateNewObject(long sharedRealmPtr, long tablePtr); + + private static native long nativeCreateRow(long sharedRealmPtr, long tablePtr); + + + // Return a pointer to newly created Row. We may need to return a OsObject pointer in the future. + private static native long nativeCreateNewObjectWithLongPrimaryKey(long sharedRealmPtr, + long tablePtr, long pk_column_index, + long primaryKeyValue, boolean isNullValue); + + // Return a index of newly created Row. + private static native long nativeCreateRowWithLongPrimaryKey(long sharedRealmPtr, + long tablePtr, long pk_column_index, + long primaryKeyValue, boolean isNullValue); + + // Return a pointer to newly created Row. We may need to return a OsObject pointer in the future. + private static native long nativeCreateNewObjectWithStringPrimaryKey(long sharedRealmPtr, + long tablePtr, long pk_column_index, + String primaryKeyValue); + + // Return a index of newly created Row. + private static native long nativeCreateRowWithStringPrimaryKey(long sharedRealmPtr, + long tablePtr, long pk_column_index, + String primaryKeyValue); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 53bde14879..97b937a99e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -56,7 +56,7 @@ enum PivotType { private static final long PRIMARY_KEY_CLASS_COLUMN_INDEX = 0; private static final String PRIMARY_KEY_FIELD_COLUMN_NAME = "pk_property"; private static final long PRIMARY_KEY_FIELD_COLUMN_INDEX = 1; - private static final long NO_PRIMARY_KEY = -2; + public static final long NO_PRIMARY_KEY = -2; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); @@ -364,89 +364,6 @@ public long addEmptyRow() { return nativeAddEmptyRow(nativePtr, 1); } - /** - * Adds an empty row to the table and set the primary key with the given value. Equivalent to call - * {@link #addEmptyRowWithPrimaryKey(Object, boolean)} with {@code validation = true}. - * - * @param primaryKeyValue the primary key value - * @return the row index. - */ - public long addEmptyRowWithPrimaryKey(Object primaryKeyValue) { - return addEmptyRowWithPrimaryKey(primaryKeyValue, true); - } - - /** - * Adds an empty row to the table and set the primary key with the given value. - * - * @param primaryKeyValue the primary key value. - * @param validation set to {@code false} to skip all validations. This is currently used by bulk insert which - * has its own validations. - * @return the row index. - */ - public long addEmptyRowWithPrimaryKey(Object primaryKeyValue, boolean validation) { - if (validation) { - checkImmutable(); - checkHasPrimaryKey(); - } - - long primaryKeyColumnIndex = getPrimaryKey(); - RealmFieldType type = getColumnType(primaryKeyColumnIndex); - long rowIndex; - - // Adds with primary key initially set. - if (primaryKeyValue == null) { - switch (type) { - case STRING: - case INTEGER: - if (validation && findFirstNull(primaryKeyColumnIndex) != NO_MATCH) { - throwDuplicatePrimaryKeyException("null"); - } - rowIndex = nativeAddEmptyRow(nativePtr, 1); - if (type == RealmFieldType.STRING) { - nativeSetStringUnique(nativePtr, primaryKeyColumnIndex, rowIndex, null); - } else { - nativeSetNullUnique(nativePtr, primaryKeyColumnIndex, rowIndex); - } - break; - - default: - throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); - } - - } else { - switch (type) { - case STRING: - if (!(primaryKeyValue instanceof String)) { - throw new IllegalArgumentException("Primary key value is not a String: " + primaryKeyValue); - } - if (validation && findFirstString(primaryKeyColumnIndex, (String) primaryKeyValue) != NO_MATCH) { - throwDuplicatePrimaryKeyException(primaryKeyValue); - } - rowIndex = nativeAddEmptyRow(nativePtr, 1); - nativeSetStringUnique(nativePtr, primaryKeyColumnIndex, rowIndex, (String) primaryKeyValue); - break; - - case INTEGER: - long pkValue; - try { - pkValue = Long.parseLong(primaryKeyValue.toString()); - } catch (RuntimeException e) { - throw new IllegalArgumentException("Primary key value is not a long: " + primaryKeyValue); - } - if (validation && findFirstLong(primaryKeyColumnIndex, pkValue) != NO_MATCH) { - throwDuplicatePrimaryKeyException(pkValue); - } - rowIndex = nativeAddEmptyRow(nativePtr, 1); - nativeSetLongUnique(nativePtr, primaryKeyColumnIndex, rowIndex, pkValue); - break; - - default: - throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); - } - } - return rowIndex; - } - @SuppressWarnings("WeakerAccess") public long addEmptyRows(long rows) { checkImmutable(); @@ -905,12 +822,6 @@ void checkImmutable() { } } - private void checkHasPrimaryKey() { - if (!hasPrimaryKey()) { - throw new IllegalStateException(getName() + " has no primary key defined"); - } - } - // // Count // @@ -1059,7 +970,7 @@ public String toString() { } private static void throwImmutable() { - throw new IllegalStateException("Changing Realm data can only be done from inside a transaction."); + throw new IllegalStateException("Cannot modify managed objects outside of a write transaction."); } /** From 8538e97f79910f3c45062342e80a126500408b8f Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 16 May 2017 05:28:52 -0700 Subject: [PATCH 0697/2110] Update grade wrappers and android gradle plugin (#4646) * Add a script file to update gradle wrappers in this reposotory. First, you need to set the version of gradle in realm.properties in the top directory, then execute ./update_gradle_wrapper.sh * update gradle wrappers to 3.5 and errorprone plugin to 0.0.10. * update android gradle plugin to 2.3.2 * move update_gradle_wrapper.sh into tools/ * execute tools/update_gradle_wrapper.sh again * improved regex * revised the script more readable * stop using pushd/popd (no need to do that since the script is executed in a shubshell) --- examples/build.gradle | 2 +- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54783 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- realm.properties | 2 +- realm/build.gradle | 4 ++-- realm/gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- tools/update_gradle_wrapper.sh | 12 ++++++++++++ 16 files changed, 28 insertions(+), 16 deletions(-) create mode 100755 tools/update_gradle_wrapper.sh diff --git a/examples/build.gradle b/examples/build.gradle index 8fd93d0f63..6d5c61e9f7 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index 1149f4ca38ceccebfd469e125e6c56f0c5eb7617..ccad502cf004785505cbd55ed7b64a792cf40536 100644 GIT binary patch delta 6635 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 delta 5986 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 BLQntz diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index cbb9ce3c56..3e88d1e5c4 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 22 16:44:51 JST 2017 +#Tue May 16 03:12:59 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 1deb4fd325a1899e8601c746857df88a8585ac34..c77b099c97f3531be5b7858ea0caa6b3f9d2b015 100644 GIT binary patch delta 6635 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 BLQ((# diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 7dd58c667b..ac21505e86 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 22 16:44:52 JST 2017 +#Tue May 16 03:13:01 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 34c574f227cc8d4e1dbbefc91d650437ac252ac0..967a4da89f43efa38dd93bf6bc6f4d1592bf5313 100644 GIT binary patch delta 6635 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 delta 5986 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 BLSz5{ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a5f34943f0..44bbebba2a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 22 16:45:06 JST 2017 +#Tue May 16 03:13:02 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index 1149be9d869e05873c7f2097c569dbc8408a2a73..bba0767ab164c9662c0aa8d3fd14d6048b6bffb9 100644 GIT binary patch delta 6635 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 delta 5986 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 BLS6s> diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index a5e2a078c3..d5c18857dd 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 22 16:45:01 JST 2017 +#Tue May 16 03:13:05 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 28afc97bba77be7ea341b93e7b0bcb2e4db96c99..b0522cfc8adf15aa69c3421d3197d25c42d81e25 100644 GIT binary patch delta 6635 zcmZ8m2RxPk_rI>l$liPJl@*!E%DBQsglk2Ka4EA~e32FLWUmmi%9feEcgQBn$c{vb z|I@wq|Lg1fdA*+3bMATGXMD~%pYyz)bDQ&V&x&wK^>uJBP(vUD1P};B1rkfj0}?6_ zPr?@8Sa~z*Z|-~UIFSEfUbA07WBd6q_a9>DdxeX@6+AQrycS(VtLb%>^8i?wu0t(d z0UIU`lvD^|@znNno9Evmbsvw?&D>ie+tp;)ff!#W9DkDJx0>Oxqv+*uxcb&r8S-Lp zQiJj;6RA7x1m!zNI~FEFXWmlhS@SE^ZZjIxKEyM$+n3ujkF(xpN~-OLD2AHCuHdy9 z&XIL&%zp~hcRf zt3F6Jx6Vl9ZLYT@Il^jBPm_NqWb)3Qn>&Dzlugmz5p6#2%tvh>R8v?Wmqe zw?%bDom30G2=OD0*j-~jgXY)b3z+fCzl*t;*QQDwLmzSGm2@dtxiBwayp~Gx)zfes zGV}TEmqi^Ms-v@!aqVNcp(T%>1ZBswRSSkxaD?(nhMgs)WJJdjh&VlXQ>cxkr0AnN zbh4XnToGCm_UxlwApYuika-q~qQ{7-B!B#=9pHe8r~ zhHLA_ZPS77ii&{QiD%2haxkh@rL-}6C6%;$@34A_s4j`?2eovLuj>DZG716LU zxx36LB{EX<%DQcZsLQyz5uN!%WU6SjHqMMxnI%M5z20;UGpbeFe)ImiX5e(w zUK{G^#ZJ};k_?8$v1pAz{oeHCG6DPL=NkhLhrCTV?Y*qN6(9S|Y_J+e{<I8oHlipgM7^kMOE2g!@oYsm0YVeBX1AO8$-+r2ua)c zXk8t=iGMQ1ZQ1B34LD}k7uqijcv|gTaY=ShU;nJzSDMml@z4 z;<&<=vTM5a!lZ!7^n8V1bF$%4Q)3m>MbO@9IcjS)sljF@^U-=O3D3g}#q{jz?ODck zm^lUAy{g$^a|E%TZU!F^RhCx&D_F+br{LAA`=VKbt$Ekp)dXLDwbVUWM@ee@D-<;b z^_jSAl`kRCSJM^g@~*^PDq+X0cEEr}H#5Io=S^2haAG*Tnc8&bwm}fbDz7sihm*?# zm*;Q2oe~lvJUC?1g_5LQ>K>MVOy$gYmXx}e=bQ`+M&h(t8b*ifcSUEs4E zp2%`?vg*8nYuA$O;Y{6?5l7bI5VxLF4Bnc&;{kwJkD#+ zvMaIQls?=p4riuQg~B?<6b*t?uDx0eT;ma95wY2f^NfXUKbfTFS_f#_$1@0hzmSgt24Gh*~eTN02;0bf(;_RzR(-KZ9jpRl#){%CO$4KFh>aLrXx5 zZ{$(;HxZQ0yaS;FWK65VAp?4n=J-T`vJr2hkH^MN;dz{gXL!E{-aD+;3!6o5l^a^o$RQlbB zN?lpw+B(sdjih9@FB@=G7ug!AoZ9s_7E@A_=2spRXjdcs>iqoVrt1_NZ~mS!8P{+>1DEI@{n>p2n3%FUx-WOOW>q}wA{wx~!QEI{ zHPU9&P@m}>eoW3{>$BFp(J20ewI!K%qzw)BM)Kl|*0hr@a2D?Kp z1_X2BNqtUh?yGO#t20on0<~^s-0|=39K&=G);|-i$k%gPM!#7J@-G(1+n0WK&g2$s zX)iDnL(TPjOVEbweG|IolHj`$x89Q5mUECNE2yJ3(v(N^VeIW!f?C134|m#|mi>ZE zpQ}C<+~K$1&a7%kI(R2(FIIneAb9^cBTIT~p~EhCf<`Zap4f&L1)~{^nstItiEK=Y zJMK?w(=VHd`DkUno>=?VqM~Iw;KjR|Z|HJ+KAgUJa;#WnBck)vKxS*zH%Hj}ID==o zu)<5it}jK1>PP9_f&K#X>5~oUL~nA-8C&YCYS^1b$P3gBnqhVUY!Cf zg~`uK<5~QQjTI-SsCuzPRvs^#xFFB8U&_FF7Ly4$s;qgKQa!TF1|<@iHoe zytQ+Sy>*Mv^qdY{M{Cy*?A+TWiwhMr7Krs@4wk8!#lD4vN~7tl$9JnsqFGkz{j_*u zd_8Lp-_ojv_vY5zxHtWMMfkllF^z(;4Y7})vkJ70yp*C9AC=aHcL;|RUhXP`OL$Tw z+`Mqu_AUNplmBU74fH*}qu3{j8NBFD3blZRlxokBky?ViAj3!(e>Om)hN_FT*a_5bA zq2zRxJdLSYWpgO)2Q&Px@?Vki6SfyQ?{)BQ8dYx&Y6(x5*WT-Ms2UI)-+v)e&R0U@ zmLQhfVEi;bzInRTpG8{60EMezA)I|E*Uw}Al?HY3$#6LDL!mi`XtB5Ei^zuXv8#7Y zgnoub3x6$ttvoj|UltbXj`T8|wcOk>S<1eeuw=rxdtts9WhAvo2n&Gg=@Q@Q!izCl|bN0K^HGKNWlmt}^j^Ove3@3Dplm)~0_tgO<6Mw)8ge7iHk z8R*8HZC}Pg*==ig(`_KSGx31cGTLRzZfvupWXhb5P~$-OO2>~eBgM$tjmfl*5;nE{ z$gt3wJIEOlXVkN&H6pgZ`a~WLNZ%@Md?Zr!)qKXVp0;LaykV80vgQeN{L_T6XNC!I zIDdOE*o9MAd266EKB4Yrwn*?`7-G%>Q2({R;9srH6uuoIXU`$1$1JpI#KOW<}=j&zDe2btq*dGT^8#b5F7R<$*2R z&!ZZ)f9RX}DJa`0bQk#n*XQA$QQdNS7f2-8xx9(^cBKlGqJbmF(3MrK-)4sIBV?Nb zB#$Tum-=L%zFkzgP0=MI`vaCn^O!h{ot2tbnC4U_C%U>JsAlmg{jmI=O+&ot`0G6) ze|lFgBUT4g(ED@&gzAGBgB?5vhp2{~W_bghXNk2nUWPP1y4Jarm2ULLkzJ~Tt0H--YMQHScYlB360FCYjOX7nu04ire> zA%1Laa}-TG_=jC|NcP7l+KV%$MZU)a*)g)BVyvWFZtHnIa@}oma#FpETX$_t1@Byp zVDNgGguJ*b%mP>6_S>Z}RvY-DcCZ(UmTqbd{w^;CjB32XQU#a`p?9{=S1$nQvvHiw(u2aoS>bi`3yp0}A>6?BC5BWZ8Btxu4CN85f z))7{*uQt7`HbvE*_;3U_>crRZan#Wp-tQ(S{C+s7!Q!r%Pd@lt3Ueu`enEk!$de0x z;w$4f%lU|>;p#t>axHr7l6+ASwbzAv7lJ--?3&BQeVU~4$KBXTKQwxRlXvier)=I>$|3PO*G>D%<<+q) zdkKy=s6x|QJqyZT27YqdYjdX)D_{F3v~~3bl1zKKm8@)wFqkbN(|R&4Q{TJX;MOM& zYuqyLfJ|%QLH@3#ECRpV+|-d9TV>vMum5>A!B8JvP-I{FB-BAs=O3lkwx2l)I~iQg z%ALT@@_ivq5Bx7Rl&y{v%~a!gNbE(@Gl^n#c8-_J>Xch|e__8uO(jvf!~ zUF_X4pO=rGwGaheqgLM$`f#5{t?y8Mc(~4xnE&Wgfu-*Lq_c@^vb7w}XVr5Z zpIk^+zxylT6kpz5^jV|gEh~()=#i})HF59&)LX@E6o@5!60@y^2J?SU|5&AKy=)X8 z?VY%yo8*G}s$o#1Rw1r(Uzn~qu|&(1KRryXPxh{XlUYJ>hjPf}pDW*Z#YjI!acxi% zuvBtA9S-@RbinAj=>MM)O9hOw2_G|5skh95VyF?r6(Y=dMTTev!@A8$ei1`?D&L~Q zP*GwrSOGEyd@RL5%#X$Svdmtf{4>4jCqZb$LLrc2d6yixpAUZ?H0gID~{4 zb4qsR7$V(Hh~kgz^X`KL1yB}_2gV*S@MZ*lk1|%Yzt_Nus(Dvkz&O)a7l(tPK6_f= zfM5SA5KiBo+i{>^G6@ivLj$!JBJXwLY$e zSl&t;D**;~v*;Ado2=QvQZ@~&xM+{loGpg(aUhU9TnI!K*z;ro$bpL$Y98HK@A~Z) zup*n4Ygj729FC=aKbFHfBOj1Yi81GuGlmT*@+B1el>E^!Rv$s?5QYLsTVc3445a{~ zRgM!6O!Gy837=6{DzIun$H3n>P-@8E`5REzD*3+w9PqtW4s8sOY!k&0_HEJ_BBM^E1z^6`L41>6f5o&^unXhy)v;PVCpD`R6n6GOfLJG9{cgbDE4q`Zf@R3XG zf53kRHGk(cf1waaD&hZ6ZGfdNN;LTVDf~BhiSGZwr035h(9%wgmbIJQPCqf~A2zFe;Zn&Ef`hpG3AL`~sE2s&#-pD3`Km_T*>zo34PAm`~c+$;+ z2Cr_KdK7}78Svsf2j&q0!Iv>stUDQ@WTN12TEL%^6nNQ*i3$D#wiFgHXPW{L2&nB} zi%S{{#s@BUV@dQ+;6VqX ztf_#tUTkm|+8Cj@7T9MTXp74s!&a=IEI6%$aDms~MQ#tc^@y|oaqG_l>k7Tduy@SO z75v)n!9@OCw>SsNum@t_O9BD?m`Hf@E*V*an`9)0K;+K>1@3`B27r0sGW#F1Yg{DX zG{MuX1KJip2V8On${d*ig;6d{KA-n9LcLwkJW?;R|5^ImgZ>-yNmmfe|2HzueT>jI zj{pu2E}*E73T<7Zn&5{b*hhN7T%30^#S;x&bif1nhB(k*D>9|g9MDaHi~lY1%f47J zrSN~dfa(bC%2&|BV=xd3=k~|_1TB;^%!k%4;e4xF8`ORQ?57&%fcFB?z(Q9Zz@iVE zWr{&YC~YvBuRq9-3DD0WMyPBQnio*S1UL_3Qc>Mp>X8I0E(8lx@tk707!WJ~kiDY? zSg{?0*@?Dn00hr~V3~7ZkvKH?^(dwfo$JEqfH&hopxpm7YK}N;#(^5sz4Z4YF zjeNcI82?2_7@=Y*Xr9LiCx$mT%m`&l2YFlo$p{OEr$4|5b{;yhe~$nho+e zfnNiderE3NlJo*hRTtQtdCz%hUIn0Dasr=+u!me144h;m$P@ijEbM>EGEzm_`7v0Q z#9->=&Iw;_23Q{{{3$<-<;P$qNLqoUK~1zE*;(5y@ErgFU(67;bApR)0COwH{|bZv l{F?@RnP~s}6#_QDe=YHL;Q0qmQVECz#0uO)MEjp-{|A}qoH76a delta 5986 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 BLSO&@ diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index d24868644c..64338485ea 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 22 16:45:02 JST 2017 +#Tue May 16 03:13:06 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip diff --git a/realm.properties b/realm.properties index aa5f3da99d..b4231e304d 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=3.4.1 +gradleVersion=3.5 ndkVersion=r10e diff --git a/realm/build.gradle b/realm/build.gradle index b7c98a3e74..e35f2580cb 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.novoda:gradle-android-command-plugin:1.3.0' @@ -16,7 +16,7 @@ buildscript { classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:3.1.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" - classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.9' + classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10' } } diff --git a/realm/gradle/wrapper/gradle-wrapper.jar b/realm/gradle/wrapper/gradle-wrapper.jar index 3123a9e7089fccdb3c37b8ccc0163ae39e009d7e..bba0767ab164c9662c0aa8d3fd14d6048b6bffb9 100644 GIT binary patch delta 6635 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 delta 5986 zcmY*d1yq#J*It$mL1`A2QdkxVNtLjr5h-cuZjg?}KvEZY>5^`cE-8Us=~7??r9qTN zNfEzgm;cX?_nbNNoIB5b?wy%CbIv<|iwWEE38^0{6B5ybKxAYfdrvK;Xlfp^lOD~P zDIYDZN5AQ|Fl2Uq1I#iJSK77{$-P$mJBo%rtgmb3vXq<6q;PCyhX7s8%`PPghSR|n^! zo{B%oA9Ge!e-KBHH)#Q_Qo%a1!E62c-jutw72^1Cm+%$~((zJs)hwsAso>KT$+3#) zHY;#1y4lj4vd-~dbX~dQEcM-^B#y(w>vzSCG3KG87MfQCM*lo)T8FZe?Y!h5qIX@X zeE(dw%)g3*)Vy)ciySejzHRex?Rza*&Gd{uKgl<3W(sXe@4CyOnnlW^+xp>G31@d0 zHlDoLe%uwvtT6RO#vr4;{0psb$jEOE3D_0mmsG3=cmBx!ZEJpL<1ATdHY-HPJoeMw zFX6J)W|w*xmlt*P`>8nD(euIqVu3&C`5|y>4c#2bw=OeX2m7yZglAuu^Se8G_1`kc zkON!7F3I*$F~OD3e|wuq$y9HKX6_?Gx}y%V?I?89q($!fCNgi9FRoEH>a&Sn>Tl*# zRFO5o^o+y!^bbsnHLkImd5b>ZM;3nn&Kx#WIkr?(L=U@A;cwcCj!GLgYqcu#vK&a% z&2B|MJ;(}8G{}*!Lfn%^3N&*HIB;Ct4o)Q-sYZvqN7gG8i*dK17%D|9qM$GxxLx_G z*S+b63Oa#$*Kw)abC-qKI*w(u*FWBCSS?_C3O8g;@Kcf$9c#C;?~Wuh7epJp-!Yx| z5rrU3=x$u#QE%hH)GIN5?{q#}XcoSG(^Yy}7$SU$K!whqmJL6FBr(ngSdh%%2O|}b zU|j8eJ)DL!p96uWSU?~yZ0)flgv=dG-QCl4$^Gfa1CGf zO4RR`x00%JOE&JmZjA6AOihlmkGx#cF_~_fM>KCB`Vjj=$mu_iLDHyL#M5i(cI|pc z!EEXA9bzOQ&t4M~CTt3w+mOtduY9^Uqer-ba(DB7JaUxFcHDCdcC6n)37N7sktk!f zdH?$yw%TJqaKv%Q1W_^WnZQxV>Uvn#ey?j zm^Aa4O5VdC)uyv`mE@Sw81XSWCZZL%(<0>6QZ~yv^1``i_Wt#wX0{fd5a-@mX}A3{ z@p+CXN7+xg{MA0}c^uv9pgFgS(v&(@yzy5r<|rcdPchNrG!+}Vb!DH1@Nd}Pk*#S+ z;HA1_`44}8>{q+e z;rG}@>9SitmoZ(@7037H)?2dYJG_RH>)U#6z*Y_(jlC%s^?9d$hp#-#%ikmevATZm zNb6=@Z5`PQ(Sg25Rd@=51nQONhpx#J_1q&K?^$%H^Lq$gryHtguU&FTzT!O~C$MNUxWt3K`L1D!j-@B5`1%sRgYex7ryq<7CyJIAMmU7vq1~_B$lf!<>%+ZSRd?-q&oyU^~Kk| z?P(Z@6fYg@R?dt>AkAZcECf6qs}#BDb2DKt?44u3x^EC@7;{cSpG8AYnn#f5{@vtn zD?LYB4ifK{^giy^r!Zogh#H{E!?Qah$ruHLP?NWdxthw&71=`jy^b+XS2PwSpnP26 z*7?w-;he?K@0`95=4pw4#XN#xeh_>1My@d>ec_sX;}QA|jo_CWPak+_;I$R`E3Y(^ z$+l*0q*OH}y4&3%=0>bv-Rt^4@hOVRMIPE(i{-kPz8KwSjuGL8trTC=N@CU`UC`@a zRag{@cSa_d)|a(hwc98$=`FGtcaQac@9Goh^6DCcH`spihtidWsTXN!F^jhMUDf%i zi!aN^J)8ALO^hHGyhl?)zI+l0=1FZzskE#uFA?N*Rp}9N6<$)cxMIys`QhFJY9J<-rD3jDc5GW)A>minV{dQ zN4$Z(^jE{z1o$HwhKgy2(~Ab+OBx_GWwDG=<_zF3?A&p^7^V&sI4;T zY7UG>yE%~GV-)6SHt`weM<uF@o-; z+$5f1e?3!f^i0eTwyOcgg_|YDkcsAlCvuiX1vcsT5SE>ttec^5xkW)_gox&)PI5-- zZr1Dxl@gsecGbufvpcCd@|PuoM|~6zljm}|^(f-)c3FGa`ccVl?eGaL4Q(Sman~OD zd{o*CpQFxO397^2rJrS~C5_ktw)s;cimS&WaBNZ+Ly5C8f5!a09F7w_L#yI=L1V`sY|^)72( z)|V7(%TV?dW{phWtM*zVZ_RU?;*&b|l-8DP3bPca(P01g6Shky6B>T zaGpYnn2C6M)ilRl*Rmf3&mHsqygKa-5oWdC)iJ?ZcUtPp0&s3`Sse-@Ddv!ghU~wE&s%J3x zRs7qtlwB4Rh-uh|T!L3eRC9s+^WK#-T&rQ9N(40x=Z^~J4^W@^?2!~L-nNbheO_r* z^F9WQx04hJ%Ou3|53|1vE`DE>-ulat(xe!gq08b-K9(D=xuh~aIQl#Ou}3BJSFba~ zM-8zPpY_JR{hs6cWwgCE6Tg@fi(bQRBb~jV}ymu}qF z^;}L3u>{J}ukSc;_cyy-_cc7oIk(x689MOie%xtYT?A-m@-Q=#E$| z@_PnN7&2@tN*}VUz9ek!5fk1)sVl}t&VVSdg$+$zAQv!!RyeK=b3ffY41Lu0QXwNq zLCs}nH-2wf*7u{+McT+^Vh5FVJ3DVWIU!R`f^nH=9%{1_n@iwYLpgDlsm7p{bbE<0 z30aFZN&Qzf^?_yIcbs!p^M}t*J!*Re8_jE;Tn=A;wUSJMImW25>*PBUuZ9x6y@f;& zQQB-Ywi^>bk+X;FRmTmygBO?F)wV}&AHg5b1RJ+8>EvZrTGoXGyS4lCupLT-_&PAWnYt%y}R$;{zqJH zQ;6+D6>~P4%Rklc&FmCY!=Cnf0Kkq&5b84}-f1CdjU;6$z521t*tE&^lNSliEW?vM zBDt;NK7jYf&HF}gpK%%KcpgSr=C|s)`4YA~)tQ%|dhO`V-ulwmnv7^m;)sNOsfw1i z@rX|lH4WwO4Ju6-PI3^=P<9F38tQ@n>h1O3D3JD)KM!6 z-NK(@!mnyHsMJ4SgHqO8*01~_lVNG8dEQ8Y+2IR!`l5Q(^W~l)dTIge+y0vZGd)!k zMCi}x66LDX9oqkbJTV6fzax5|Ge2oUtS?*~>RHB*qKSVmrAxBH85q(g9_2?9sFS;& zHLAY8ty!A5ItQxh65YPCfiNII)X{jnq+u9hUAOmV453`+Sf`@t+KC2@ccUe)CC@mi zWhh5+K5nZ>H@S44d|N!-VU=uJy^WLnwJb6d)+NQQC!bI*2hL*mQJWEc4eKx$Wu+S& zR!FN5m%GVpb4bXK3h=OG;b8Ld}h~chrW~s#Z%G4B<&<{$BN2T6P7Gnu;O}UYzO=a65lWT>M z(mbyy-z;J1$fn^O?>AWv$bvWQhU5I0%U^|A1R-y6D+2^J zTXk6{&zC=)G35b*{l3rz#LSB%&I=^wu^30|neJjXL5NnP5*chuQZBey5!q^F#M2iU z5Ynhb6teYT037QALZJ1c+&n8Nt}i&;QvV64oIj9rG-*#I)_-!iEa1R=llbhahUlx*%2499CdFOF|EWM*zE}0g)z_4g^?U z#(^sRglRAy-SFK!MN7^j#CV(M(;~bLX!2Obn~dTP2=GY0ZRixebTT|eS8ZyD@S{O* zqzLh-B`cp6k1oAnC&MGw_Q6w+0?(LJ-tA`aDYC8kM2hFhMIM8xPcptc%((P}5Crn4 z1Rh4hs>}muPUFDSG*8o(O;Y{ZzfgGURkp>m90uX~CYrjZP`< zleZO8vRjIy%yihdk_*`OP?-Qs5Bm2a10;8f{r4mTjCM-lRu`Z{3E{zglms3GqGa(v zwig0?Mlk_Sy*T?aYJtvsDQ?9DqJ3CUIhLnfkhWYPjyb?31cT}a}#T01Wdb`an5yNCM#JK zAP_$@ww-bA(7-u6a5Djj|E=m5?CO;o;q2$ScyR)K=N`*NW6#I|2?!*3MnKC1M|A2X z1&sCR0Kr~-y7R$9#_U)k8@63M<8Z|sc-t$&c~Y1scC0AwqdD#I9nw6e4~i>e;Aj{E zZZQWcK5_%j!=%9N5oVmPG0lyUY^*b!6a*4G8V1@DLnt`}+w3K1>E!N9A$zldC1$lf}-h2YXDU z&j|IW;v6>zNPsC%2EcLfbophm0_GWjTC>o}mf%DTyqywbv1xz6ULj|*vCYO3xdGTG z{A~yr4ne@Ti*drqLwq>rf43Y6By%Q%yJa|{SpeHfbR1cb!R8q$c9Gs;Pqye8a!4hP ztd~Z6vPk&38jV1}Q`I>8$(}w*W(4X-@Itk1?(0?GK_iZ5S0 zw=Iv|UuCS5$Qg&`MyvxH0C8mcSEC}>;@50C6}pJeh6}sz9_rR0 z?jnfej3B5TIO^aM_}^VjhJ78!-q=e2|DML)2mkL=ZwCfPxv51?62opmQTxf*{{h*q BLRbI* diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 783df36eb6..ad3d9f3a11 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 22 16:44:59 JST 2017 +#Tue May 16 03:13:04 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip diff --git a/tools/update_gradle_wrapper.sh b/tools/update_gradle_wrapper.sh new file mode 100755 index 0000000000..ad3a95b7a2 --- /dev/null +++ b/tools/update_gradle_wrapper.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# This script updates Gradle Wrappers in this repository. You need to update the version number in realm.properties first, then execute ./update_gradle_wrapper.sh . + +cd "$(dirname $0)/.." + +for i in $(find $(pwd) -type f -name gradlew); do + cd $(dirname $i) + pwd + ./gradlew wrapper + sed -E -i '' s/-bin\\.zip\$/-all.zip/ gradle/wrapper/gradle-wrapper.properties +done From 0f535b64c5f5db87b1ce28e2a0a127a82e3e68ed Mon Sep 17 00:00:00 2001 From: John Carlson Date: Tue, 16 May 2017 14:03:25 -0500 Subject: [PATCH 0698/2110] Grammer for RealmModule docs (#4652) --- .../src/main/java/io/realm/annotations/RealmModule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmModule.java b/realm-annotations/src/main/java/io/realm/annotations/RealmModule.java index f28f9eb835..c8690de362 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/RealmModule.java +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmModule.java @@ -23,8 +23,8 @@ import java.lang.annotation.Target; /** - * By default a Realm can stores all classes extending RealmObject in a project. However, if you want to restrict a - * Realm to only contain a subset of classes or want to share them between a library project and an app project you must + * By default a Realm can store all classes extending RealmObject in a project. However, if you want to restrict a + * Realm to only contain a subset of classes or want to share them between a library project and an app project, you must * use a RealmModule. *

                      * A RealmModule is a collection of classes extending RealmObject that can be combined with other RealmModules to create From 1f65ecb22392f66ddcc98bbb913c185310cf0a32 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 17 May 2017 10:33:24 +0800 Subject: [PATCH 0699/2110] Support minor release for release script (#4649) --- tools/release.sh | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tools/release.sh b/tools/release.sh index e355ee2744..b0e91f557a 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -2,7 +2,6 @@ # Script to make release on the local machine. # See https://github.com/realm/realm-wiki/wiki/Java-Release-Checklist for more details. -# FIXME: Only patch release is supported now. set -euo pipefail IFS=$'\n\t' @@ -93,14 +92,35 @@ prepare_branch() { git fetch --all git checkout releases git reset --hard origin/releases - if [[ "$BRANCH_TO_RELEASE" != "releases" ]] ; then - echo "Releasing from other branches than 'releases' is not supported right now." - exit -1 - fi - git clean -xfd git submodule update --init --recursive + # Merge the branch to the releases branch and check the CHANGELOG.md + if [[ "$BRANCH_TO_RELEASE" != "releases" ]] ; then + git merge "origin/$BRANCH_TO_RELEASE" + + while true + do + read -r -p "Type the command to edit CHANGELOG.md, default(vim):" editor + if [ -z "$editor" ] ; then + editor="vim" + fi + "$editor" CHANGELOG.md + + read -r -p "Please merge the unreleased entries in the 'CHANGELOG.md' and then press any key to continue..." _ + if [ "$(grep -c "YYYY-MM-DD" CHANGELOG.md)" -eq 1 ] ; then + break + else + echo "There are more than one or none unreleased entries in the 'CHANGELOG.md'." + fi + done + # CHANGELOG.md is modified. + if ! git diff-index --quiet HEAD CHANGELOG.md ; then + git add CHANGELOG.md + git commit -m "Merge entries in changelog" + fi + fi + if ! grep -q "SNAPSHOT" version.txt ; then echo "'version.txt' doesn't contain 'SNAPSHOT'." exit -1 From da3575971169c0482ce984083f3e7769f21c859d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 17 May 2017 14:08:50 +0800 Subject: [PATCH 0700/2110] Helper class for throwing java exception from JNI (#4636) --- .../io/realm/RealmNullPrimaryKeyTests.java | 4 +- .../androidTest/java/io/realm/RealmTests.java | 3 +- .../main/cpp/io_realm_internal_OsObject.cpp | 33 ++++++------ .../src/main/cpp/jni_util/java_class.cpp | 7 +++ .../src/main/cpp/jni_util/java_class.hpp | 3 +- .../cpp/jni_util/java_exception_thrower.cpp | 39 +++++++++++++++ .../cpp/jni_util/java_exception_thrower.hpp | 50 +++++++++++++++++++ realm/realm-library/src/main/cpp/util.cpp | 5 ++ 8 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.hpp diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java index 74e6df85f3..5ad88f013c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmNullPrimaryKeyTests.java @@ -36,6 +36,7 @@ import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(Parameterized.class) @@ -202,7 +203,8 @@ public void createObject_duplicatedNullPrimaryKeyThrows() throws NoSuchMethodExc realm.createObject(testClazz, null); fail("Null value as primary key already exists."); } catch (RealmPrimaryKeyConstraintException expected) { - assertEquals("Primary key value already exists: 'null' .", expected.getMessage()); + assertTrue("Exception message is: " + expected.getMessage(), + expected.getMessage().contains("Primary key value already exists: 'null' .")); } finally { realm.cancelTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index df3d0ad061..a7e3847eae 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -1308,7 +1308,8 @@ public void copyToRealm_duplicatedNullPrimaryKeyThrows() { } fail("Null value as primary key already exists."); } catch (RealmPrimaryKeyConstraintException expected) { - assertEquals("Primary key value already exists: 'null' .", expected.getMessage()); + assertTrue("Exception message is: " + expected.getMessage(), + expected.getMessage().contains("Primary key value already exists: 'null' .")); } finally { realm.cancelTransaction(); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index f6b040603f..4c6da841e1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -26,11 +26,15 @@ #include "jni_util/java_global_weak_ref.hpp" #include "jni_util/java_method.hpp" #include "jni_util/java_class.hpp" +#include "jni_util/java_exception_thrower.hpp" using namespace realm; using namespace realm::jni_util; using namespace realm::_impl; +static const char* PK_CONSTRAINT_EXCEPTION_CLASS = "io/realm/exceptions/RealmPrimaryKeyConstraintException"; +static const char* PK_EXCEPTION_MSG_FORMAT = "Primary key value already exists: %1 ."; + // We need to control the life cycle of Object, weak ref of Java OsObject and the NotificationToken. // Wrap all three together, so when the Java object gets GCed, all three of them will be invalidated. struct ObjectWrapper { @@ -163,13 +167,6 @@ static inline size_t do_create_row(jlong shared_realm_ptr, jlong table_ptr) return table.add_empty_row(); } -template -static void throw_duplicated_primary_key_exception(JNIEnv* env, T value) -{ - static JavaClass dup_pk_exception(env, "io/realm/exceptions/RealmPrimaryKeyConstraintException"); - env->ThrowNew(dup_pk_exception, format("Primary key value already exists: %1 .", value).c_str()); -} - static inline size_t do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ptr, jlong pk_column_ndx, jlong pk_value, jboolean is_pk_null) { @@ -181,15 +178,14 @@ static inline size_t do_create_row_with_primary_key(JNIEnv* env, jlong shared_re } if (is_pk_null) { - if (table.find_first_null(pk_column_ndx) != realm::npos) { - throw_duplicated_primary_key_exception(env, "'null'"); - return realm::npos; + if (table.find_first_null(pk_column_ndx) != npos) { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, "'null'")); } } else { - if (table.find_first_int(pk_column_ndx, pk_value) != realm::npos) { - throw_duplicated_primary_key_exception(env, reinterpret_cast(pk_value)); - return realm::npos; + if (table.find_first_int(pk_column_ndx, pk_value) != npos) { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, + format(PK_EXCEPTION_MSG_FORMAT, reinterpret_cast(pk_value))); } } @@ -216,15 +212,14 @@ static inline size_t do_create_row_with_primary_key(JNIEnv* env, jlong shared_re } if (pk_value) { - if (table.find_first_string(pk_column_ndx, str_accessor) != realm::npos) { - throw_duplicated_primary_key_exception(env, str_accessor.operator std::string()); - return realm::npos; + if (table.find_first_string(pk_column_ndx, str_accessor) != npos) { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, + format(PK_EXCEPTION_MSG_FORMAT, str_accessor.operator std::string())); } } else { - if (table.find_first_null(pk_column_ndx) != realm::npos) { - throw_duplicated_primary_key_exception(env, "'null'"); - return realm::npos; + if (table.find_first_null(pk_column_ndx) != npos) { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, "'null'")); } } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_class.cpp b/realm/realm-library/src/main/cpp/jni_util/java_class.cpp index eaf526e75c..6d5d89dc1c 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_class.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_class.cpp @@ -31,6 +31,13 @@ JavaClass::JavaClass(JNIEnv* env, const char* class_name, bool free_on_unload) } } +JavaClass::JavaClass(JavaClass&& rhs) + : m_ref_owner(std::move(rhs.m_ref_owner)) + , m_class(rhs.m_class) +{ + rhs.m_class = nullptr; +} + JavaGlobalRef JavaClass::get_jclass(JNIEnv* env, const char* class_name) { jclass cls = env->FindClass(class_name); diff --git a/realm/realm-library/src/main/cpp/jni_util/java_class.hpp b/realm/realm-library/src/main/cpp/jni_util/java_class.hpp index c95dce29d4..7aaf3a00ed 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_class.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_class.hpp @@ -35,6 +35,8 @@ class JavaClass { { } + JavaClass(JavaClass&&); + inline jclass get() noexcept { return m_class; @@ -46,7 +48,6 @@ class JavaClass { } // Not implemented for now. - JavaClass(JavaClass&&) = delete; JavaClass(JavaClass&) = delete; JavaClass& operator=(JavaClass&&) = delete; diff --git a/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp new file mode 100644 index 0000000000..63cd8a9ba7 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "java_exception_thrower.hpp" +#include "log.hpp" + +#include + +using namespace realm::util; +using namespace realm::jni_util; + +JavaExceptionThrower::JavaExceptionThrower(JNIEnv* env, const char* class_name, std::string message, + const char* file_path, int line_num) + : std::runtime_error(std::move(message)) + , m_exception_class(env, class_name, false) + , m_file_path(file_path) + , m_line_num(line_num) +{ +} + +void JavaExceptionThrower::throw_java_exception(JNIEnv* env) +{ + std::string message = format("%1\n(%2:%3)", what(), m_file_path, m_line_num); + Log::w(message.c_str()); + env->ThrowNew(m_exception_class, message.c_str()); +} diff --git a/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.hpp b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.hpp new file mode 100644 index 0000000000..1d5bcdde69 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.hpp @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_JAVA_EXCEPTION_THROWER_HPP +#define REALM_JNI_UTIL_JAVA_EXCEPTION_THROWER_HPP + +#include + +#include + +#include "java_class.hpp" + +namespace realm { +namespace jni_util { + +#define THROW_JAVA_EXCEPTION(env, class_name, message) \ + throw realm::jni_util::JavaExceptionThrower(env, class_name, message, __FILE__, __LINE__) + +// Class to help throw a Java exception from JNI code. +// This exception will be called from CATCH_STD and throw a Java exception there. +class JavaExceptionThrower : public std::runtime_error { +public: + JavaExceptionThrower(JNIEnv* env, const char* class_name, std::string message, const char* file_path, + int line_num); + + virtual void throw_java_exception(JNIEnv* env); + +private: + JavaClass m_exception_class; + const char* m_file_path; + int m_line_num; +}; + +} // namespace realm +} // namesapce jni_util + +#endif // REALM_JNI_UTIL_JAVA_EXCEPTION_THROWER_HPP diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index e562c12564..41ab64d1c8 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -28,6 +28,8 @@ #include "shared_realm.hpp" #include "results.hpp" +#include "jni_util/java_exception_thrower.hpp" + using namespace std; using namespace realm; using namespace realm::util; @@ -52,6 +54,9 @@ void ConvertException(JNIEnv* env, const char* file, int line) try { throw; } + catch (JavaExceptionThrower& e) { + e.throw_java_exception(env); + } catch (bad_alloc& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, OutOfMemory, ss.str()); From b7b566e7483aaf6845be7c215fdefc1674e27bab Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 19 May 2017 00:32:40 -0700 Subject: [PATCH 0701/2110] Update Kotlin in example to 1.1.2-4 (#4664) --- examples/kotlinExample/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index fdaceba7dd..d3984d3701 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.2-2' + ext.kotlin_version = '1.1.2-4' repositories { jcenter() mavenCentral() From 91fc2312b4f14f40372e4f0de1f9038c96013939 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 19 May 2017 16:16:38 +0800 Subject: [PATCH 0702/2110] Update sync to 1.8.5 (#4670) And update Object Store to 4330f13eedf --- CHANGELOG.md | 5 +++++ dependencies.list | 4 ++-- .../src/main/cpp/io_realm_internal_OsObject.cpp | 1 + realm/realm-library/src/main/cpp/object-store | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 404f9ffc0c..fc64d1c532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,13 @@ ### Bug Fixes +* Fixed random crashes which were caused by a race condition in encrypted Realm (#4343). + ### Internal +* Upgraded to Realm Sync 1.8.5. +* Upgraded to Realm Core 2.8.0. + ## 3.2.0 (2017-05-16) ### Deprecated diff --git a/dependencies.list b/dependencies.list index 7a9a09482c..f78a30cfc7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.6.0 -REALM_SYNC_SHA256=e8a973dbe6ab33ac49d3d0e45d6b63d69cec8d1d87d9a2311fcdd02767f76cf8 +REALM_SYNC_VERSION=1.8.5 +REALM_SYNC_SHA256=71e70f83b1604672bbfd7c04fcf984ecc8ac683864943ef3de262994c2e7b7fc # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 4c6da841e1..1f28126e1e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "util.hpp" diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index dfddfa7f7b..4330f13eed 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit dfddfa7f7bf564619e2257243c252ffad6d5c9c3 +Subproject commit 4330f13eedfad4f34bcecdca25f71fb949fbc4b2 From 5d6119a2278f016c92f06420fa9afaf9d21cc070 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 19 May 2017 11:39:31 +0200 Subject: [PATCH 0703/2110] SchemaVersion for synced Realms where required by mistake (#4666) --- CHANGELOG.md | 4 +- .../java/io/realm/entities/IndexedFields.java | 31 +++ .../io/realm/entities/PrimaryKeyAsString.java | 1 + .../java/io/realm/entities/StringOnly.java | 3 + .../assets/schemaversion_v1.realm | Bin 8192 -> 0 bytes .../java/io/realm/SyncConfigurationTests.java | 82 ------- .../io/realm/SyncedRealmMigrationTests.java | 220 ++++++++++++++++++ .../src/main/java/io/realm/Realm.java | 18 +- .../java/io/realm/SyncConfiguration.java | 12 +- 9 files changed, 265 insertions(+), 106 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/IndexedFields.java delete mode 100644 realm/realm-library/src/androidTestObjectServer/assets/schemaversion_v1.realm diff --git a/CHANGELOG.md b/CHANGELOG.md index fc64d1c532..cb4d3e5484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bug Fixes +* [ObjectServer] `schemaVersion` was mistakenly required in order to trigger migrations (#4658). +* [ObjectServer] Fields removed from model classes will now correctly be hidden instead of throwing an exception when opening the Realm (#4658). * Fixed random crashes which were caused by a race condition in encrypted Realm (#4343). ### Internal @@ -17,8 +19,6 @@ ## 3.2.0 (2017-05-16) -### Deprecated - ### Enhancements * [ObjectServer] Added support for `SyncUser.isAdmin()` (#4353). diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/IndexedFields.java b/realm/realm-library/src/androidTest/java/io/realm/entities/IndexedFields.java new file mode 100644 index 0000000000..420062c098 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/IndexedFields.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import io.realm.RealmObject; +import io.realm.annotations.Index; + + +public class IndexedFields extends RealmObject { + + public static final String FIELD_INDEXED_STRING = "indexedString"; + public static final String FIELD_NON_INDEXED_STRING = "nonIndexedString"; + + @Index + private String indexedString; + private String nonIndexedString; +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsString.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsString.java index 5e70b431b8..099977dcd9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsString.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsString.java @@ -24,6 +24,7 @@ public class PrimaryKeyAsString extends RealmObject { public static final String CLASS_NAME = "PrimaryKeyAsString"; public static final String FIELD_PRIMARY_KEY = "name"; + public static final String FIELD_ID = "id"; @PrimaryKey private String name; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java index b4183425c9..c8c80a461a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java @@ -19,6 +19,9 @@ import io.realm.RealmObject; public class StringOnly extends RealmObject { + + public static final String FIELD_CHARS = "chars"; + private String chars; public String getChars() { diff --git a/realm/realm-library/src/androidTestObjectServer/assets/schemaversion_v1.realm b/realm/realm-library/src/androidTestObjectServer/assets/schemaversion_v1.realm deleted file mode 100644 index d2dac440609e2a850ed098a11717d7824b3a3505..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 zcmeHKJxp6y6h8M}zdwWFkr;wPQ!!eKQdx>Bu~agMv?hwwD$T$aUf{JRxVB}S$fzCM zrDC*HsbXZQ#K_3hB6UclE^K9}RK`vfW4HD@_uhw}UBW;*w9>m0KAiu1zkAMk?^Qz5 z>(J6C_m-Djm&7901d(cO8T}i6?|cz=qK&8%y+MC#y&i3EKV7WXA4V^uCp)cpd&siD zc_fj{TQTzm@U5+<7JQrSu$sP4q6}E20TW{oB=F|Iqe0*SOx_r=9j@<8uy^ zOZLkQmT&a6X1k+#$LJe|Bl0xAw9|>Tnq19)SZ_vY`{m{fdj%%`QnOi)qXt}*wqK5h z{g*ZyQQO>))_?r$dAzOz8vjR|+javkpBp$#OzBe+g`p`xmM;^zj^j)_w=VF>?TbUv zm3_&29~)qbd(;$X5Jz%g&qo^7pI)L*>=HU={cw-kKaqn9j+gL!Kl7@jlKmxeJ^|bD zk(k0xRa8}df(v9mv8!19D207^D39fdJd@}0Pf5yGp_)}?wW?~WrMjx8>zDKI%Uwhr zPgyduJ2ukGWS!l^Sp0e>eB^ z{vS2X)JAt%TqZA@gTN!V=N`B{&&)kOFxx{ub=90W@OV;m-fO>TG_1Yc^A5cPSNN;4 z*mciPfUHOT-qyWb9Jl?*8ys!`wu^B;CurX!4z?q$zm9|2mwG4-_Nf;9ieJURX1l~b zp-3;2`hWbx@o*pTkjGlJv}S3`(ypb(>iWOmfA#g9tNP$ost>q+NBUcoxDSTvcOgje zz_}WvaP}>#g7E;Jit(%=2J?ud=X+|NC}5-)>-pYxgMPrhm$py*V5>&){B8ZGAzZt* zZx1Xzvh>)}*OuC}*Cs!{`sPYpf3dh;J3;1{A&?=EA&?>PRtWHO#KC(=OYj#($teYE z`a>*{&x??%_y9O{_xUwdKX)*?-I4ECy!V}ZqC(+pSPnhB_x0`b*~ZBy@oVn6SMqy# z3Qy8`@=2aDK`eUC8kH~d;hiFX|Ne3g&tIJ%&q3r{4Mb%v?1lSbfPBCIOV6#h0yA^Y h5Xcb75Xcb75Xcb75Xcb75Xcb75Xcb75crQG@Hg55za{_x diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index d539d0bd9c..0ab6661ff9 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -450,88 +450,6 @@ public void compact_NotAllowed() { Realm.compactRealm(config); } - @Test - public void schemaVersion_throwsIfLessThanCurrentVersion() throws IOException { - SyncUser user = createTestUser(); - String url = "realm://ros.realm.io/~/default"; - @SuppressWarnings("unchecked") - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) - .schema(AllJavaTypes.class, StringOnly.class) - .name("schemaversion_v1.realm") - .schemaVersion(0) - .build(); - - // Add v1 of the Realm to the filsystem - configFactory.copyRealmFromAssets(context, "schemaversion_v1.realm", config); - - // Opening the Realm should throw an exception since the schema version is less than the one in the file. - Realm realm = null; - try { - realm = Realm.getInstance(config); - fail(); - } catch(IllegalArgumentException ignore) { - } finally { - if (realm != null) { - realm.close(); - } - } - } - - @Test - public void schemaVersion_bumpWhenUpgradingSchema() throws IOException { - SyncUser user = createTestUser(); - String url = "realm://ros.realm.io/~/default"; - @SuppressWarnings("unchecked") - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) - .schema(AllJavaTypes.class, StringOnly.class) - .name("schemaversion_v1.realm") - .schemaVersion(2) - .build(); - - // Add v1 of the Realm to the file system. v1 is missing the class `StringOnly` - configFactory.copyRealmFromAssets(context, "schemaversion_v1.realm", config); - - // Opening the Realm should automatically upgrade the schema and version - Realm realm = null; - try { - realm = Realm.getInstance(config); - assertEquals(2, realm.getVersion()); - assertTrue(realm.getSchema().contains(StringOnly.class.getSimpleName())); - } finally { - if (realm != null) { - realm.close(); - } - } - } - - @Test - public void schemaVersion_throwsIfNotUpdatedForSchemaUpgrade() throws IOException { - SyncUser user = createTestUser(); - String url = "realm://ros.realm.io/~/default"; - @SuppressWarnings("unchecked") - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) - .schema(AllJavaTypes.class, StringOnly.class) - .name("schemaversion_v1.realm") - .schemaVersion(1) - .build(); - - // Add v1 of the Realm to the file system. v1 is missing the class `StringOnly` - configFactory.copyRealmFromAssets(context, "schemaversion_v1.realm", config); - - // Opening the Realm should throw an exception since the schema changed, but the provided schema version is - // the same. - Realm realm = null; - try { - realm = Realm.getInstance(config); - fail(); - } catch(IllegalArgumentException ignore) { - } finally { - if (realm != null) { - realm.close(); - } - } - } - // Check that it is possible for multiple users to reference the same Realm URL while each user still use their // own copy on the filesystem. This is e.g. what happens if a Realm is shared using a PermissionOffer. @Test diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index 28f2e3984b..014cae155d 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -29,10 +29,18 @@ import java.io.FileNotFoundException; +import io.realm.entities.IndexedFields; +import io.realm.entities.PrimaryKeyAsString; +import io.realm.entities.StringOnly; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.log.RealmLog; import io.realm.rule.TestRealmConfigurationFactory; import io.realm.rule.TestSyncConfigurationFactory; import io.realm.util.SyncTestUtils; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -56,4 +64,216 @@ public void migrateRealm_syncConfigurationThrows() { } } + // Check that the Realm can still be opened even if the ondisk schema are missing fields. These will be added + // automatically. + @Test + public void addField_worksWithMigrationError() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + .schema(StringOnly.class) + .build(); + + // Setup initial Realm schema (with missing fields) + String className = StringOnly.class.getSimpleName(); + DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); + RealmSchema schema = dynamicRealm.getSchema(); + dynamicRealm.beginTransaction(); + schema.create(className); // Create empty class + dynamicRealm.commitTransaction(); + dynamicRealm.close(); + + // Open typed Realm, which will validate the schema + Realm realm = Realm.getInstance(config); + RealmObjectSchema stringOnlySchema = realm.getSchema().get(className); + try { + assertTrue(stringOnlySchema.hasField(StringOnly.FIELD_CHARS)); // Field has been added + } finally { + realm.close(); + } + } + + // Check that the Realm can still be opened even if the ondisk schema has more fields than in the model class. + // The underlying field should not be deleted, just hidden. + @Test + public void missingFields_hiddenSilently() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + .schema(StringOnly.class) + .build(); + + // Setup initial Realm schema (with too many fields) + String className = StringOnly.class.getSimpleName(); + DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); + RealmSchema schema = dynamicRealm.getSchema(); + dynamicRealm.beginTransaction(); + schema.create(className) + .addField(StringOnly.FIELD_CHARS, String.class) + .addField("newField", String.class); + dynamicRealm.commitTransaction(); + dynamicRealm.close(); + + // Open typed Realm, which will validate the schema + Realm realm = Realm.getInstance(config); + RealmObjectSchema stringOnlySchema = realm.getSchema().get(className); + try { + assertTrue(stringOnlySchema.hasField(StringOnly.FIELD_CHARS)); + // TODO Field is currently hidden, but should the field be visible in the schema + assertFalse(stringOnlySchema.hasField("newField")); + assertEquals(1, stringOnlySchema.getFieldNames().size()); + } finally { + realm.close(); + } + } + + // Check that a Realm cannot be opened if it contain breaking schema changes, like changing a primary key + @Test + public void breakingSchemaChange_throws() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + .schema(PrimaryKeyAsString.class) + .build(); + + // Setup initial Realm schema (with a different primary key) + DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); + RealmSchema schema = dynamicRealm.getSchema(); + dynamicRealm.beginTransaction(); + schema.create(PrimaryKeyAsString.class.getSimpleName()) + .addField(PrimaryKeyAsString.FIELD_PRIMARY_KEY, String.class) + .addField(PrimaryKeyAsString.FIELD_ID, long.class, FieldAttribute.PRIMARY_KEY); + dynamicRealm.commitTransaction(); + dynamicRealm.close(); + + try { + Realm.getInstance(config); + fail(); + } catch (IllegalStateException ignored) { + } + } + + // Check that indexes are not being added if the schema version is the same + @Test + public void sameSchemaVersion_doNotRebuildIndexes() { + + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + .schema(IndexedFields.class) + .schemaVersion(42) + .build(); + + // Setup initial Realm schema (with no indexes) + String className = IndexedFields.class.getSimpleName(); + DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); + RealmSchema schema = dynamicRealm.getSchema(); + dynamicRealm.beginTransaction(); + schema.create(className) + .addField(IndexedFields.FIELD_INDEXED_STRING, String.class) // No index + .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String.class); + dynamicRealm.setVersion(42); + dynamicRealm.commitTransaction(); + dynamicRealm.close(); + + try { + Realm realm = Realm.getInstance(config); // Opening at same schema version (42) will not rebuild indexes + fail(); + } catch (RealmMigrationNeededException ignored) { + } + +// FIXME: This is the intended behaviour +// RealmObjectSchema indexedFieldsSchema = realm.getSchema().get(className); +// try { +// assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_INDEXED_STRING)); +// assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_NON_INDEXED_STRING)); +// } finally { +// realm.close(); +// } + } + + // Check that indexes are being added if the schema version is different + @Test + public void differentSchemaVersions_rebuildIndexes() { + + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + .schema(IndexedFields.class) + .schemaVersion(42) + .build(); + + // Setup initial Realm schema (with no indexes) + String className = IndexedFields.class.getSimpleName(); + DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); + RealmSchema schema = dynamicRealm.getSchema(); + dynamicRealm.beginTransaction(); + schema.create(className) + .addField(IndexedFields.FIELD_INDEXED_STRING, String.class) // No index + .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String.class); + dynamicRealm.setVersion(43); + dynamicRealm.commitTransaction(); + dynamicRealm.close(); + + try { + Realm realm = Realm.getInstance(config); // Opening at different schema version (42) should rebuild indexes + fail(); + } catch (RealmMigrationNeededException ignored) { + } + +// FIXME: This is the intended behaviour +// RealmObjectSchema indexedFieldsSchema = realm.getSchema().get(className); +// try { +// assertTrue(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_INDEXED_STRING)); +// assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_NON_INDEXED_STRING)); +// } finally { +// realm.close(); +// } + } + + // Check that indexes are being added if other fields are being added as well + @Test + public void addingFields_rebuildIndexes() { + + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + .schema(IndexedFields.class) + .schemaVersion(42) + .build(); + + // Setup initial Realm schema (with no indexes) + String className = IndexedFields.class.getSimpleName(); + DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); + RealmSchema schema = dynamicRealm.getSchema(); + dynamicRealm.beginTransaction(); + schema.create(className) + .addField(IndexedFields.FIELD_INDEXED_STRING, String.class); // No index + // .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String.class); // Missing field + dynamicRealm.setVersion(41); + dynamicRealm.commitTransaction(); + dynamicRealm.close(); + + // Opening at different schema version (42) should add field and rebuild indexes + Realm realm = Realm.getInstance(config); + try { + assertTrue(realm.getSchema().get(className).hasField(IndexedFields.FIELD_NON_INDEXED_STRING)); + assertTrue(realm.getSchema().get(className).hasIndex(IndexedFields.FIELD_INDEXED_STRING)); + } finally { + realm.close(); + } + } + + @Test + public void schemaVersionUpgradedWhenMigrating() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + .schemaVersion(42) + .build(); + + // Setup initial Realm schema (with missing fields) + String className = StringOnly.class.getSimpleName(); + DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); + RealmSchema schema = dynamicRealm.getSchema(); + dynamicRealm.beginTransaction(); + schema.create(className); // Create empty class + dynamicRealm.setVersion(1); + dynamicRealm.commitTransaction(); + dynamicRealm.close(); + + // Open typed Realm, which will validate the schema + Realm realm = Realm.getInstance(config); + try { + assertEquals(42, realm.getVersion()); + } finally { + realm.close(); + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index c3af8bfb8b..24291c5741 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -501,21 +501,9 @@ private static void initializeSyncedRealm(Realm realm) { schemaCreator.close(); schemaCreator = null; - // !!! FIXME: This appalling kludge is necessitated by current package structure/visiblity constraints. - // It absolutely breaks encapsulation and needs to be fixed! - if (realm.sharedRealm.requiresMigration(schema.getNativePtr())) { - if (currentVersion >= newVersion) { - throw new IllegalArgumentException(String.format( - "The schema was changed but the schema version was not updated. " + - "The configured schema version (%d) must be greater than the version " + - " in the Realm file (%d) in order to update the schema.", - newVersion, currentVersion)); - } - realm.sharedRealm.updateSchema(schema.getNativePtr(), newVersion); - // The OS currently does not handle setting the schema version. We have to do it manually. - realm.setVersion(newVersion); - commitChanges = true; - } + // Object Store handles all update logic + realm.sharedRealm.updateSchema(schema.getNativePtr(), newVersion); + commitChanges = true; } // Validate the schema in the file diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 19ce6ab69e..124bc62b1f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -489,13 +489,11 @@ SyncConfiguration.Builder schema(Class firstClass, Class - * While synced Realms only support additive schema changes which can be applied without requiring a manual - * migration, the schema version must still be incremented as an indication to Realm that the change was - * intentional. - *

                      - * Failing to increment the schema version will cause Realm to throw a {@link io.realm.exceptions.RealmMigrationNeededException} - * when the Realm is opened and the changed schema will not be applied. - *

                      + * Synced Realms only support additive schema changes which can be applied without requiring a manual + * migration. The schema version will only be used as an indication to the underlying storage layer to remove + * or add indexes. These will be recalculated if the provided schema version differ from the version in the + * Realm file. + * * WARNING: There is no guarantee that the value inserted here is the same returned by {@link Realm#getVersion()}. * Due to the nature of synced Realms, the value can both be higher and lower. *

                      - * Immutable instances of this class protect against the first possiblity by throwing on calls + * Immutable instances of this class protect against the first possibility by throwing on calls * to {@code copyFrom}. {@see ColumnInfo} for its mutability contract. * * There are two, redundant, lookup methods, for schema members: by Class and by String. @@ -39,8 +40,12 @@ * class lookup is very fast and on a hot path, so we maintain the redundant table. */ public final class ColumnIndices { + // MultiKeyMap of -> ColumnInfo + // Right now we maintain 3 copies. One public and 2 internal ones. + private final Map, String>, ColumnInfo> classesToColumnInfo; private final Map, ColumnInfo> classes; private final Map classesByName; + private final boolean mutable; private long schemaVersion; @@ -48,40 +53,45 @@ public final class ColumnIndices { * Create a mutable ColumnIndices initialized with the ColumnInfo objects in the passed map. * * @param schemaVersion the schema version - * @param classes a map of table classes to their column info + * @param classesMap a map of table classes to their column info * @throws IllegalArgumentException if any of the ColumnInfo object is immutable. */ - public ColumnIndices(long schemaVersion, Map, ColumnInfo> classes) { - this(schemaVersion, new HashMap<>(classes), true); - for (Map.Entry, ColumnInfo> entry : classes.entrySet()) { + public ColumnIndices(long schemaVersion, Map, String>, ColumnInfo> classesMap) { + this(schemaVersion, new HashMap<>(classesMap), true); + for (Map.Entry, String>, ColumnInfo> entry : classesMap.entrySet()) { ColumnInfo columnInfo = entry.getValue(); if (mutable != columnInfo.isMutable()) { throw new IllegalArgumentException("ColumnInfo mutability does not match ColumnIndices"); } - this.classesByName.put(entry.getKey().getSimpleName(), entry.getValue()); + Pair, String> classDescription = entry.getKey(); + this.classes.put(classDescription.first, columnInfo); + this.classesByName.put(classDescription.second, columnInfo); } } /** - * Create a copy of the passed ColumnIndices with the specified mutablity. + * Create a copy of the passed ColumnIndices with the specified mutability. * * @param other the ColumnIndices object to copy * @param mutable if false the object is effectively final. */ public ColumnIndices(ColumnIndices other, boolean mutable) { - this(other.schemaVersion, new HashMap, ColumnInfo>(other.classes.size()), mutable); - for (Map.Entry, ColumnInfo> entry : other.classes.entrySet()) { + this(other.schemaVersion, new HashMap, String>, ColumnInfo>(other.classesToColumnInfo.size()), mutable); + for (Map.Entry, String>, ColumnInfo> entry : other.classesToColumnInfo.entrySet()) { ColumnInfo columnInfo = entry.getValue().copy(mutable); - this.classes.put(entry.getKey(), columnInfo); - this.classesByName.put(entry.getKey().getSimpleName(), columnInfo); + Pair, String> key = entry.getKey(); + this.classes.put(key.first, columnInfo); + this.classesByName.put(key.second, columnInfo); + this.classesToColumnInfo.put(key, columnInfo); } } - private ColumnIndices(long schemaVersion, Map, ColumnInfo> classes, boolean mutable) { + private ColumnIndices(long schemaVersion, Map, String>, ColumnInfo> classesMap, boolean mutable) { this.schemaVersion = schemaVersion; - this.classes = classes; + this.classesToColumnInfo = classesMap; this.mutable = mutable; - this.classesByName = new HashMap<>(classes.size()); + this.classes = new HashMap<>(classesMap.size()); + this.classesByName = new HashMap<>(classesMap.size()); } /** @@ -164,9 +174,9 @@ public String toString() { buf.append(mutable).append(","); if (classes != null) { boolean commaNeeded = false; - for (Map.Entry, ColumnInfo> entry : classes.entrySet()) { + for (Map.Entry entry : classesByName.entrySet()) { if (commaNeeded) { buf.append(","); } - buf.append(entry.getKey().getSimpleName()).append("->").append(entry.getValue()); + buf.append(entry.getKey()).append("->").append(entry.getValue()); commaNeeded = true; } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java new file mode 100644 index 0000000000..6990faf5ae --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 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. + */ + +package io.realm.internal.util; + +/** + * Copy from the Android framework to avoid the dependency on Android classes + slight adjustment + * to support older versions of Android. + * + * Original source: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util/Pair.java + * + * Container to ease passing around a tuple of two objects. This object provides a sensible + * implementation of equals(), returning true if equals() is true on each of the contained + * objects. + */ +public class Pair { + public final F first; + public final S second; + + /** + * Constructor for a Pair. + * + * @param first the first object in the Pair. + * @param second the second object in the pair. + */ + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + /** + * Checks the two objects for equality by delegating to their respective + * {@link Object#equals(Object)} methods. + * + * @param o the {@link Pair} to which this one is to be checked for equality. + * @return true if the underlying objects of the Pair are both considered + * equal. + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Pair)) { + return false; + } + Pair p = (Pair) o; + return equals(p.first, first) && (equals(p.second, second)); + } + + private boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); + } + + /** + * Compute a hash code using the hash codes of the underlying objects. + * + * @return a hashcode of the Pair. + */ + @Override + public int hashCode() { + return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); + } + + @Override + public String toString() { + return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}"; + } + + /** + * Convenience method for creating an appropriately typed pair. + * + * @param a the first object in the Pair. + * @param b the second object in the pair. + * @return a Pair that is templatized with the types of a and b. + */ + public static Pair create(A a, B b) { + return new Pair(a, b); + } +} \ No newline at end of file From 64893d94ed138938ad1d844282a78d7e6640b334 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 24 May 2017 14:57:00 +0200 Subject: [PATCH 0711/2110] Set proper release version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 105bb87d77..6d94c9c2e1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.2-SNAPSHOT \ No newline at end of file +3.3.0-SNAPSHOT \ No newline at end of file From 8cc9d22f41d04391692d4ed3da59a082a01bb98f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 24 May 2017 14:58:00 +0200 Subject: [PATCH 0712/2110] Merge entries in changelog --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df44e691a8..4def33e380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,6 @@ * [ObjectServer] Added two options to `SyncConfiguration` to provide a trusted root CA `trustedRootCA` and to disable SSL validation `disableSSLVerification` (#4371). * [ObjectServer] Added support for changing passwords through `SyncUser.changePassword()` using an admin user (#4588). - -## 3.2.2 (YYYY-MM-DD) - ### Bug Fixes * Queries on proguarded Realm model classes, failed with "Table not found" (#4673). From 5415cd3ed4915b6007702c537c2522e846f211f8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 24 May 2017 14:59:01 +0200 Subject: [PATCH 0713/2110] Release v3.3.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 6d94c9c2e1..0fa4ae4890 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.3.0-SNAPSHOT \ No newline at end of file +3.3.0 \ No newline at end of file From b9a78fd21bc625bddfe2f7261ff30a8b61445cb0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 24 May 2017 14:59:02 +0200 Subject: [PATCH 0714/2110] Prepare next release v3.3.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 0fa4ae4890..f4cb97d56c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.3.0 \ No newline at end of file +3.3.1-SNAPSHOT \ No newline at end of file From 29eed06ac06f2369a3f9381659bd1a7be9b60182 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 24 May 2017 21:37:56 +0800 Subject: [PATCH 0715/2110] Split createTable & getTable (#4689) To support Stable IDs, the primary key has to be created while the table is created. This will require a new internal API createTableWithPK in the future. The previous getTable behaviour would be quite confusing to support that. - getTable will only return the table if it exists. - createTable is the one to be used to create tables. - Add JavaExceptionDef to have all common Java exception names in a single place. - Remove useless tests. --- .../java/io/realm/RealmSchemaTests.java | 10 ++ .../io/realm/internal/CollectionTests.java | 2 +- .../java/io/realm/internal/JNILinkTest.java | 121 ------------------ .../java/io/realm/internal/JNITableTest.java | 8 +- .../io/realm/internal/PrimaryKeyTests.java | 10 +- .../io/realm/internal/SharedRealmTests.java | 24 ++-- .../realm/internal/SortDescriptorTests.java | 2 +- .../cpp/io_realm_internal_SharedRealm.cpp | 42 ++++-- .../src/main/cpp/java_exception_def.cpp | 22 ++++ .../src/main/cpp/java_exception_def.hpp | 34 +++++ .../java/io/realm/StandardRealmSchema.java | 8 +- .../java/io/realm/internal/SharedRealm.java | 25 +++- .../main/java/io/realm/internal/Table.java | 6 + 13 files changed, 154 insertions(+), 160 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/JNILinkTest.java create mode 100644 realm/realm-library/src/main/cpp/java_exception_def.cpp create mode 100644 realm/realm-library/src/main/cpp/java_exception_def.hpp diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index 57fd54362b..beb63f1cef 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -23,6 +23,7 @@ import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import java.util.Arrays; @@ -46,6 +47,8 @@ public class RealmSchemaTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); private DynamicRealm realm; private RealmSchema realmSchema; @@ -100,6 +103,13 @@ public void create_invalidNameThrows() { } } + @Test + public void create_duplicatedNameThrows() { + realmSchema.create("Foo"); + thrown.expect(IllegalArgumentException.class); + realmSchema.create("Foo"); + } + @Test public void get() { RealmObjectSchema objectSchema = realmSchema.get(AllJavaTypes.CLASS_NAME); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 8a9a492201..99e06fed58 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -79,7 +79,7 @@ private SharedRealm getSharedRealm() { private void populateData() { sharedRealm.beginTransaction(); - table = sharedRealm.getTable("test_table"); + table = sharedRealm.createTable("test_table"); // Specify the column types and names long columnIdx = table.addColumn(RealmFieldType.STRING, "firstName"); table.addSearchIndex(columnIdx); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNILinkTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNILinkTest.java deleted file mode 100644 index 95499e8a4a..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNILinkTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.realm.RealmConfiguration; -import io.realm.RealmFieldType; -import io.realm.rule.TestRealmConfigurationFactory; - -import static junit.framework.Assert.assertEquals; - -@RunWith(AndroidJUnit4.class) -public class JNILinkTest { - @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - - private SharedRealm sharedRealm; - - @Before - public void setUp() { - RealmConfiguration config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); - sharedRealm.beginTransaction(); - } - - @After - public void tearDown() { - sharedRealm.cancelTransaction(); - sharedRealm.close(); - } - - @Test - public void testLinkColumns() { - Table table1 = sharedRealm.getTable("table1"); - - Table table2 = sharedRealm.getTable("table2"); - table2.addColumn(RealmFieldType.INTEGER, "int"); - table2.addColumn(RealmFieldType.STRING, "string"); - - table2.add(1, "c"); - table2.add(2, "b"); - table2.add(3, "a"); - - table1.addColumnLink(RealmFieldType.OBJECT, "Link", table2); - - table1.addEmptyRow(); - table1.setLink(0, 0, 1, false); - - Table target = table1.getLinkTarget(0); - - System.gc(); - - assertEquals(target.getColumnCount(), 2); - - String test = target.getString(1, table1.getLink(0, 0)); - - assertEquals(test, "b"); - - } - - @Test - public void testLinkList() { - Table table1 = sharedRealm.getTable("table1"); - table1.addColumn(RealmFieldType.INTEGER, "int"); - table1.addColumn(RealmFieldType.STRING, "string"); - table1.add(1, "c"); - table1.add(2, "b"); - table1.add(3, "a"); - - Table table2 = sharedRealm.getTable("table2"); - - table2.addColumnLink(RealmFieldType.LIST, "LinkList", table1); - - table2.addEmptyRow(); - - LinkView links = table2.getUncheckedRow(0).getLinkList(0); - - assertEquals(links.isEmpty(), true); - assertEquals(links.size(), 0); - - links.add(2); - links.add(1); - - assertEquals(links.isEmpty(), false); - assertEquals(links.size(), 2); - - assertEquals(links.getUncheckedRow(0).getColumnName(1), "string"); - - assertEquals(links.getUncheckedRow(0).getString(1), "a"); - - links.move(1, 0); - - assertEquals(links.getUncheckedRow(0).getString(1), "b"); - - links.remove(0); - - assertEquals(links.getUncheckedRow(0).getString(1), "a"); - assertEquals(links.size(), 1); - } -} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index 46411ee027..296f22c764 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -312,7 +312,7 @@ public void getName() { // Writes transaction must be run so we are sure a db exists with the correct table. sharedRealm.beginTransaction(); - sharedRealm.getTable(TABLE_NAME); + sharedRealm.createTable(TABLE_NAME); sharedRealm.commitTransaction(); Table table = sharedRealm.getTable(TABLE_NAME); @@ -600,7 +600,7 @@ public void defaultValue_setAndGet() { //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); - final Table table = sharedRealm.getTable(Table.getTableNameForClass("DefaultValueTest")); + final Table table = sharedRealm.createTable(Table.getTableNameForClass("DefaultValueTest")); sharedRealm.commitTransaction(); List> columnInfoList = Arrays.asList( @@ -723,7 +723,7 @@ public void defaultValue_setMultipleTimes() { //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); - final Table table = sharedRealm.getTable(Table.getTableNameForClass("DefaultValueTest")); + final Table table = sharedRealm.createTable(Table.getTableNameForClass("DefaultValueTest")); sharedRealm.commitTransaction(); List> columnInfoList = Arrays.asList( @@ -855,7 +855,7 @@ public void defaultValue_overwrittenByNonDefault() { //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); - final Table table = sharedRealm.getTable(Table.getTableNameForClass("DefaultValueTest")); + final Table table = sharedRealm.createTable(Table.getTableNameForClass("DefaultValueTest")); sharedRealm.commitTransaction(); List> columnInfoList = Arrays.asList( diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index e13b17bfca..e9b8e4bd87 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -66,7 +66,7 @@ public void tearDown() { private Table getTableWithStringPrimaryKey() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); - Table t = sharedRealm.getTable("TestTable"); + Table t = sharedRealm.createTable("TestTable"); long column = t.addColumn(RealmFieldType.STRING, "colName", true); t.addSearchIndex(column); t.setPrimaryKey("colName"); @@ -76,7 +76,7 @@ private Table getTableWithStringPrimaryKey() { private Table getTableWithIntegerPrimaryKey() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); - Table t = sharedRealm.getTable("TestTable"); + Table t = sharedRealm.createTable("TestTable"); long column = t.addColumn(RealmFieldType.INTEGER, "colName"); t.addSearchIndex(column); t.setPrimaryKey("colName"); @@ -91,7 +91,7 @@ public void removingPrimaryKeyRemovesConstraint_typeSetters() { SharedRealm sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); - Table tbl = sharedRealm.getTable("EmployeeTable"); + Table tbl = sharedRealm.createTable("EmployeeTable"); tbl.addColumn(RealmFieldType.STRING, "name"); tbl.setPrimaryKey("name"); @@ -221,7 +221,7 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableMigratedWithRightName( public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); - Table table = sharedRealm.getTable("TestTable"); + Table table = sharedRealm.createTable("TestTable"); long column = table.addColumn(RealmFieldType.INTEGER, "PKColumn"); table.addSearchIndex(column); table.setPrimaryKey(column); @@ -236,7 +236,7 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { pkTable.removeSearchIndex(classColumn); // Tries to add a pk for another table. - Table table2 = sharedRealm.getTable("TestTable2"); + Table table2 = sharedRealm.createTable("TestTable2"); long column2 = table2.addColumn(RealmFieldType.INTEGER, "PKColumn"); table2.addSearchIndex(column2); try { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java index c735e2dcf1..67a35b5b00 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java @@ -71,26 +71,26 @@ public void getVersionID() { public void hasTable() { assertFalse(sharedRealm.hasTable("MyTable")); sharedRealm.beginTransaction(); - sharedRealm.getTable("MyTable"); + sharedRealm.createTable("MyTable"); sharedRealm.commitTransaction(); assertTrue(sharedRealm.hasTable("MyTable")); } - @Test(expected = IllegalStateException.class) - public void getTable_createNotInTransactionThrows() { - sharedRealm.getTable("NON-EXISTING"); - } - @Test public void getTable() { assertFalse(sharedRealm.hasTable("MyTable")); sharedRealm.beginTransaction(); - sharedRealm.getTable("MyTable"); + sharedRealm.createTable("MyTable"); sharedRealm.commitTransaction(); assertTrue(sharedRealm.hasTable("MyTable")); // Table is existing, no need transaction to create it - sharedRealm.getTable("MyTable"); + assertTrue(sharedRealm.getTable("MyTable").isValid()); + } + + @Test(expected = IllegalArgumentException.class) + public void getTable_throwsIfTableNotExist() { + sharedRealm.getTable("NON_EXISTING"); } @Test @@ -112,7 +112,7 @@ public void isInTransaction_returnFalseWhenRealmClosed() { @Test public void removeTable() { sharedRealm.beginTransaction(); - sharedRealm.getTable("TableToRemove"); + sharedRealm.createTable("TableToRemove"); assertTrue(sharedRealm.hasTable("TableToRemove")); sharedRealm.removeTable("TableToRemove"); assertFalse(sharedRealm.hasTable("TableToRemove")); @@ -122,7 +122,7 @@ public void removeTable() { @Test public void removeTable_notInTransactionThrows() { sharedRealm.beginTransaction(); - sharedRealm.getTable("TableToRemove"); + sharedRealm.createTable("TableToRemove"); sharedRealm.commitTransaction(); thrown.expect(IllegalStateException.class); sharedRealm.removeTable("TableToRemove"); @@ -140,7 +140,7 @@ public void removeTable_tableNotExist() { @Test public void renameTable() { sharedRealm.beginTransaction(); - sharedRealm.getTable("OldTable"); + sharedRealm.createTable("OldTable"); assertTrue(sharedRealm.hasTable("OldTable")); sharedRealm.renameTable("OldTable", "NewTable"); assertFalse(sharedRealm.hasTable("OldTable")); @@ -151,7 +151,7 @@ public void renameTable() { @Test public void renameTable_notInTransactionThrows() { sharedRealm.beginTransaction(); - sharedRealm.getTable("OldTable"); + sharedRealm.createTable("OldTable"); sharedRealm.commitTransaction(); thrown.expect(IllegalStateException.class); sharedRealm.renameTable("OldTable", "NewTable"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java index 61f67b779e..55bac40c4e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java @@ -55,7 +55,7 @@ public void setUp() { RealmConfiguration config = configFactory.createConfiguration(); sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); - table = sharedRealm.getTable("test_table"); + table = sharedRealm.createTable("test_table"); } @After diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 9cd68e3eb6..4738a264d2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -26,9 +26,12 @@ #include "object_store.hpp" #include "java_binding_context.hpp" #include "util.hpp" +#include "java_exception_def.hpp" #include "jni_util/java_method.hpp" #include "jni_util/java_class.hpp" +#include "jni_util/java_exception_thrower.hpp" + using namespace realm; using namespace realm::_impl; @@ -410,18 +413,41 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv try { JStringAccessor name(env, table_name); // throws auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - if (!shared_realm->read_group().has_table(name) && !shared_realm->is_in_transaction()) { - std::ostringstream ss; - ss << "Class " << name << " doesn't exist and the shared Realm is not in transaction."; - ThrowException(env, IllegalState, ss.str()); - return static_cast(NULL); + if (!shared_realm->read_group().has_table(name)) { + std::string name_str = name; + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, + format("The class '%1' doesn't exist in this Realm.", name_str)); } - Table* pTable = LangBindHelper::get_or_add_table(shared_realm->read_group(), name); - return reinterpret_cast(pTable); + Table* table = LangBindHelper::get_table(shared_realm->read_group(), name); + return reinterpret_cast(table); } CATCH_STD() - return static_cast(NULL); + return reinterpret_cast(nullptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateTable(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring table_name) +{ + TR_ENTER_PTR(shared_realm_ptr) + + std::string name_str; + try { + JStringAccessor name(env, table_name); // throws + name_str = name; + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + shared_realm->verify_in_write(); // throws + Table* table = LangBindHelper::add_table(shared_realm->read_group(), name); // throws + return reinterpret_cast(table); + } + catch (TableNameInUse& e) { + // We need to print the table name, so catch the exception here. + ThrowException(env, IllegalArgument, format("Class already exists: '%1'.", name_str)); + } + CATCH_STD() + + return reinterpret_cast(nullptr); } JNIEXPORT jstring JNICALL Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv* env, jclass, diff --git a/realm/realm-library/src/main/cpp/java_exception_def.cpp b/realm/realm-library/src/main/cpp/java_exception_def.cpp new file mode 100644 index 0000000000..81246e4483 --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_exception_def.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "java_exception_def.hpp" + +using namespace realm::_impl; + +const char* JavaExceptionDef::IllegalState = "java/lang/IllegalStateException"; +const char* JavaExceptionDef::IllegalArgument = "java/lang/IllegalArgumentException"; diff --git a/realm/realm-library/src/main/cpp/java_exception_def.hpp b/realm/realm-library/src/main/cpp/java_exception_def.hpp new file mode 100644 index 0000000000..9842d185f4 --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_exception_def.hpp @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_IMPL_EXCEPTION_DEF_HPP +#define REALM_JNI_IMPL_EXCEPTION_DEF_HPP + +namespace realm { +namespace _impl { + +// Definitions of Java exceptions which are used in JNI. +class JavaExceptionDef { +public: + // Class names + static const char* IllegalState; + static const char* IllegalArgument; +}; + +} // namespace realm +} // namespace jni_impl + +#endif diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java index 63bee2608c..e0bbcb7a75 100644 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java @@ -107,10 +107,7 @@ public RealmObjectSchema create(String className) { if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: " + className.length()); } - if (realm.getSharedRealm().hasTable(internalTableName)) { - throw new IllegalArgumentException("Class already exists: " + className); - } - return new StandardRealmObjectSchema(realm, this, realm.getSharedRealm().getTable(internalTableName)); + return new StandardRealmObjectSchema(realm, this, realm.getSharedRealm().createTable(internalTableName)); } /** @@ -200,9 +197,6 @@ Table getTable(String className) { Table table = dynamicClassToTable.get(tableName); if (table != null) { return table; } - if (!realm.getSharedRealm().hasTable(tableName)) { - throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); - } table = realm.getSharedRealm().getTable(tableName); dynamicClassToTable.put(tableName, table); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 303d20dc9f..821d489b3d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -292,8 +292,27 @@ public boolean hasTable(String name) { return nativeHasTable(nativePtr, name); } + /** + * Gets an existing {@link Table} with the given name. + * + * @param name the name of table. + * @return a {@link Table} object. + * @throws IllegalArgumentException if the table doesn't exist. + */ public Table getTable(String name) { - return new Table(this, nativeGetTable(nativePtr, name)); + long tablePtr = nativeGetTable(nativePtr, name); + return new Table(this, tablePtr); + } + + /** + * Creates a {@link Table} with then given name. Native assertion will happen if the table with the same name + * exists. + * + * @param name the name of table. + * @return a created {@link Table} object. + */ + public Table createTable(String name) { + return new Table(this, nativeCreateTable(nativePtr, name)); } public void renameTable(String oldName, String newName) { @@ -533,8 +552,12 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte private static native long[] nativeGetVersionID(long nativeSharedRealmPtr); + // Throw IAE if the table doesn't exist. private static native long nativeGetTable(long nativeSharedRealmPtr, String tableName); + // Throw IAE if the table exists already. + private static native long nativeCreateTable(long nativeSharedRealmPtr, String tableName); + private static native String nativeGetTableName(long nativeSharedRealmPtr, int index); private static native boolean nativeHasTable(long nativeSharedRealmPtr, String tableName); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 97b937a99e..da1feceb15 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -753,6 +753,12 @@ private Table getPrimaryKeyTable() { if (sharedRealm == null) { return null; } + + // FIXME: The PK table creation should be handle by Object Store after integration of OS Schema. + if (!sharedRealm.hasTable(PRIMARY_KEY_TABLE_NAME)) { + sharedRealm.createTable(PRIMARY_KEY_TABLE_NAME); + } + Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); if (pkTable.getColumnCount() == 0) { checkImmutable(); From 50e927cce5df8e6693d56764a51650a71e118dd7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 24 May 2017 15:42:51 +0200 Subject: [PATCH 0716/2110] Prepare next dev iteration --- CHANGELOG.md | 13 ++++++++++++- version.txt | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4def33e380..a137f3e15e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,15 @@ -## 3.3.0 (YYYY-MM-DD) +## 3.4.0 (YYYY-MM-DD) + +### Breaking Changes + +### Enhancements + +### Bug Fixes + +### Internal + + +## 3.3.0 (2017-05-24) ### Enhancements diff --git a/version.txt b/version.txt index f4cb97d56c..875e2d486a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.3.1-SNAPSHOT \ No newline at end of file +3.4.0-SNAPSHOT \ No newline at end of file From 5ab06b86ea60d5080aa7d9d13251528206067a0e Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 26 May 2017 04:51:23 -0700 Subject: [PATCH 0717/2110] Turn off the column # check for synced realms (#4706) --- CHANGELOG.md | 15 +++++++++++- .../io/realm/SyncedRealmMigrationTests.java | 23 +++++++++++++++++++ .../src/main/java/io/realm/Realm.java | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4def33e380..d7ab55b177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,17 @@ -## 3.3.0 (YYYY-MM-DD) +## 3.3.1 (YYYY-MM-DD) + +### Breaking Changes + +### Enhancements + +### Bug Fixes + +* Accept extra columns against synced Realm (#4706). + +### Internal + + +## 3.3.0 (2017-05-24) ### Enhancements diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index 014cae155d..9d3a57694d 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -276,4 +276,27 @@ public void schemaVersionUpgradedWhenMigrating() { realm.close(); } } + + // The remote Realm containing more field than the local typed Realm defined is allowed. + @Test + public void moreFieldsThanExpectedIsAllowed() { + SyncConfiguration config = configFactory + .createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + .schema(StringOnly.class) + .build(); + + // Initialize schema + Realm realm = Realm.getInstance(config); + realm.beginTransaction(); + RealmObjectSchema objectSchema = realm.getSchema().getSchemaForClass(StringOnly.class); + // Add one extra field which doesn't exist in the typed Realm. + objectSchema.addField("oneMoreField", int.class); + realm.commitTransaction(); + // Clear column indices cache. + realm.close(); + + // Verify schema again. + realm = Realm.getInstance(config); + realm.close(); + } } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 6558851abb..35c1f89afc 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -514,7 +514,7 @@ private static void initializeSyncedRealm(Realm realm) { for (Class modelClass : modelClasses) { String className = Table.getClassNameForTable(mediator.getTableName(modelClass)); Pair, String> key = Pair., String>create(modelClass, className); - columnInfoMap.put(key, mediator.validateTable(modelClass, realm.sharedRealm, false)); + columnInfoMap.put(key, mediator.validateTable(modelClass, realm.sharedRealm, true)); } realm.getSchema().setInitialColumnIndices((unversioned) ? newVersion : currentVersion, columnInfoMap); From a9baa20251f0e143afa1ee88fbc87478b2720b79 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 26 May 2017 19:55:55 +0800 Subject: [PATCH 0718/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ab55b177..a0a23383d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.3.1 (YYYY-MM-DD) +## 3.3.1 (2017-05-26) ### Breaking Changes From 829d41e7180edc02a1a9b3a1394076551c84be5e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 26 May 2017 19:55:58 +0800 Subject: [PATCH 0719/2110] Release v3.3.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f4cb97d56c..712bd5a680 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.3.1-SNAPSHOT \ No newline at end of file +3.3.1 \ No newline at end of file From 8723855e57c0880a62212be93c64e28078a0d44b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 26 May 2017 19:55:59 +0800 Subject: [PATCH 0720/2110] Prepare next release v3.3.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 712bd5a680..a652712908 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.3.1 \ No newline at end of file +3.3.2-SNAPSHOT \ No newline at end of file From dcfa5f13e4132a2777aaae2b75aef9d0f65d4273 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 26 May 2017 09:38:43 -0700 Subject: [PATCH 0721/2110] add next release to changelog (#4712) --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a23383d4..e5e63a0798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.3.1 (2017-05-26) +## 3.3.2 (YYYY-MM-DD) ### Breaking Changes @@ -6,11 +6,16 @@ ### Bug Fixes -* Accept extra columns against synced Realm (#4706). - ### Internal +## 3.3.1 (2017-05-26) + +### Bug Fixes + +* Accept extra columns against synced Realm (#4706). + + ## 3.3.0 (2017-05-24) ### Enhancements From da3540aea1f47359a957032d16618839c3997e86 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 27 May 2017 14:24:39 +0200 Subject: [PATCH 0722/2110] Move benchmarks to separate library --- Jenkinsfile | 2 +- build.gradle | 13 +- latest | 0 library-benchmarks/build.gradle | 60 +++++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + library-benchmarks/gradlew | 160 ++++++++++++++++++ library-benchmarks/gradlew.bat | 90 ++++++++++ library-benchmarks/settings.gradle | 1 + .../benchmarks/RealmAllocBenchmarks.java | 6 +- .../io/realm/benchmarks/RealmBenchmarks.java | 3 +- .../benchmarks/RealmInsertBenchmark.java | 9 +- .../benchmarks/RealmObjectReadBenchmarks.java | 3 +- .../RealmObjectWriteBenchmarks.java | 3 +- .../benchmarks/RealmQueryBenchmarks.java | 5 +- .../benchmarks/RealmResultsBenchmarks.java | 3 +- .../benchmarks/config/BenchmarkConfig.java | 0 .../benchmarks/config/CSVResultProcessor.java | 1 - .../realm/benchmarks/entities/AllTypes.java | 125 ++++++++++++++ .../entities/AllTypesPrimaryKey.java | 118 +++++++++++++ .../src/main/AndroidManifest.xml | 13 ++ .../src/main/res/values/strings.xml | 3 + realm/realm-library/build.gradle | 24 --- .../src/androidTest/AndroidManifest.xml | 1 - .../java/io/realm/ColumnIndicesTests.java | 15 +- .../realm/LinkingObjectsUnmanagedTests.java | 1 - .../java/io/realm/RealmJsonTests.java | 15 +- 27 files changed, 626 insertions(+), 54 deletions(-) create mode 100644 latest create mode 100644 library-benchmarks/build.gradle create mode 100644 library-benchmarks/gradle/wrapper/gradle-wrapper.jar create mode 100644 library-benchmarks/gradle/wrapper/gradle-wrapper.properties create mode 100755 library-benchmarks/gradlew create mode 100644 library-benchmarks/gradlew.bat create mode 100644 library-benchmarks/settings.gradle rename {realm/realm-library/src/benchmarks => library-benchmarks/src/androidTest}/java/io/realm/benchmarks/RealmAllocBenchmarks.java (96%) rename {realm/realm-library/src/benchmarks => library-benchmarks/src/androidTest}/java/io/realm/benchmarks/RealmBenchmarks.java (98%) rename {realm/realm-library/src/benchmarks => library-benchmarks/src/androidTest}/java/io/realm/benchmarks/RealmInsertBenchmark.java (92%) rename {realm/realm-library/src/benchmarks => library-benchmarks/src/androidTest}/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java (98%) rename {realm/realm-library/src/benchmarks => library-benchmarks/src/androidTest}/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java (98%) rename {realm/realm-library/src/benchmarks => library-benchmarks/src/androidTest}/java/io/realm/benchmarks/RealmQueryBenchmarks.java (97%) rename {realm/realm-library/src/benchmarks => library-benchmarks/src/androidTest}/java/io/realm/benchmarks/RealmResultsBenchmarks.java (98%) rename {realm/realm-library/src/benchmarks => library-benchmarks/src/androidTest}/java/io/realm/benchmarks/config/BenchmarkConfig.java (100%) rename {realm/realm-library/src/benchmarks => library-benchmarks/src/androidTest}/java/io/realm/benchmarks/config/CSVResultProcessor.java (99%) create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypes.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypesPrimaryKey.java create mode 100644 library-benchmarks/src/main/AndroidManifest.xml create mode 100644 library-benchmarks/src/main/res/values/strings.xml diff --git a/Jenkinsfile b/Jenkinsfile index 67632e3e81..51f049915a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -80,7 +80,7 @@ try { try { backgroundPid = startLogCatCollector() forwardAdbPorts() - gradle('realm', 'connectedUnitTests') + gradle('realm', 'connectedAndroidTest') archiveLog = false; } finally { stopLogCatCollector(backgroundPid, archiveLog) diff --git a/build.gradle b/build.gradle index 3eac28d01d..a641a7f5fd 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,18 @@ task connectedUnitTests(type:GradleBuild) { description = 'Run the Android unit tests of the Realm project' dependsOn installTransformer buildFile = file('realm/build.gradle') - tasks = ['connectedUnitTests'] + tasks = ['connectedAndroidTest'] + if (project.hasProperty('buildTargetABIs')) { + startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] + } +} + +task connectedBenchmarks(type:GradleBuild) { + group = 'Test' + description = 'Run all the benchmark tests for the library ' + dependsOn installTransformer + buildFile = file('library-benchmarks/build.gradle') + tasks = ['connectedAndroidTest'] if (project.hasProperty('buildTargetABIs')) { startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] } diff --git a/latest b/latest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle new file mode 100644 index 0000000000..e185b78386 --- /dev/null +++ b/library-benchmarks/build.gradle @@ -0,0 +1,60 @@ +buildscript { + repositories { + mavenLocal() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.2' + classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" + } +} + +allprojects { + def props = new Properties() + props.load(new FileInputStream("${rootDir}/../realm.properties")) + props.each { key, val -> + project.ext.set(key, val) + } +} + +task wrapper(type: Wrapper) { + gradleVersion = project.gradleVersion +} + +apply plugin: 'com.android.library' +apply plugin: 'realm-android' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.3" + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 22 // Below 23 to avoid new permission system introduced in M + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + debug { + minifyEnabled = false + // Running with DEBUG = true will disable the JIT + debuggable = false + } + } +} + +repositories { + mavenLocal() + jcenter() +} + +dependencies { + androidTestCompile 'com.android.support.test:runner:0.5' + androidTestCompile 'com.android.support.test:rules:0.5' + androidTestCompile 'junit:junit:4.12' + androidTestCompile 'dk.ilios:spanner:0.6.0' + androidTestCompile 'com.opencsv:opencsv:3.4' + androidTestCompile 'junit:junit:4.12' +} \ No newline at end of file diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.jar b/library-benchmarks/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ8zCNKueM5c!$)^mKpZNR!eIlFST57ePGQcqCqedAQ3UaUEzpjM--5V4YO zY22VxQm%$2NDnwfK+jkz=i2>NjAM6&P1DdcO<*Xs1-lzdXWn#LGSxwhPH7N%D8-zCgpFWt@`LgNYI+Fh^~nSiQmwH0^>E>*O$47MqfQza@Ce z1wBw;igLc#V2@y-*~Hp?jA1)+MYYyAt|DV_8RQCrRY@sAviO}wv;3gFdO>TE(=9o? z=S(r=0oT`w24=ihA=~iFV5z$ZG74?rmYn#eanx(!Hkxcr$*^KRFJKYYB&l6$WVsJ^ z-Iz#HYmE)Da@&seqG1fXsTER#adA&OrD2-T(z}Cwby|mQf{0v*v3hq~pzF`U`jenT z=XHXeB|fa?Ws$+9ADO0rco{#~+`VM?IXg7N>M0w1fyW1iiKTA@p$y zSiAJ%-Mg{m>&S4r#Tw@?@7ck}#oFo-iZJCWc`hw_J$=rw?omE{^tc59ftd`xq?jzf zo0bFUI=$>O!45{!c4?0KsJmZ#$vuYpZLo_O^oHTmmLMm0J_a{Nn`q5tG1m=0ecv$T z5H7r0DZGl6be@aJ+;26EGw9JENj0oJ5K0=^f-yBW2I0jqVIU};NBp*gF7_KlQnhB6 z##d$H({^HXj@il`*4^kC42&3)(A|tuhs;LygA-EWFSqpe+%#?6HG6}mE215Z4mjO2 zY2^?5$<8&k`O~#~sSc5Fy`5hg5#e{kG>SAbTxCh{y32fHkNryU_c0_6h&$zbWc63T z7|r?X7_H!9XK!HfZ+r?FvBQ$x{HTGS=1VN<>Ss-7M3z|vQG|N}Frv{h-q623@Jz*@ ziXlZIpAuY^RPlu&=nO)pFhML5=ut~&zWDSsn%>mv)!P1|^M!d5AwmSPIckoY|0u9I zTDAzG*U&5SPf+@c_tE_I!~Npfi$?gX(kn=zZd|tUZ_ez(xP+)xS!8=k(<{9@<+EUx zYQgZhjn(0qA#?~Q+EA9oh_Jx5PMfE3#KIh#*cFIFQGi)-40NHbJO&%ZvL|LAqU=Rw zf?Vr4qkUcKtLr^g-6*N-tfk+v8@#Lpl~SgKyH!+m9?T8B>WDWK22;!i5&_N=%f{__ z-LHb`v-LvKqTJZCx~z|Yg;U_f)VZu~q7trb%C6fOKs#eJosw&b$nmwGwP;Bz`=zK4 z>U3;}T_ptP)w=vJaL8EhW;J#SHA;fr13f=r#{o)`dRMOs-T;lp&Toi@u^oB_^pw=P zp#8Geo2?@!h2EYHY?L;ayT}-Df0?TeUCe8Cto{W0_a>!7Gxmi5G-nIIS;X{flm2De z{SjFG%knZoVa;mtHR_`*6)KEf=dvOT3OgT7C7&-4P#4X^B%VI&_57cBbli()(%zZC?Y0b;?5!f22UleQ=9h4_LkcA!Xsqx@q{ko&tvP_V@7epFs}AIpM{g??PA>U(sk$Gum>2Eu zD{Oy{$OF%~?B6>ixQeK9I}!$O0!T3#Ir8MW)j2V*qyJ z8Bg17L`rg^B_#rkny-=<3fr}Y42+x0@q6POk$H^*p3~Dc@5uYTQ$pfaRnIT}Wxb;- zl!@kkZkS=l)&=y|21veY8yz$t-&7ecA)TR|=51BKh(@n|d$EN>18)9kSQ|GqP?aeM ztXd9C&Md$PPF*FVs*GhoHM2L@D$(Qf%%x zwQBUt!jM~GgwluBcwkgwQ!249uPkNz3u@LSYZgmpHgX|P#8!iKk^vSKZ;?)KE$92d z2U>y}VWJ0&zjrIqddM3dz-nU%>bL&KU%SA|LiiUU7Ka|c=jF|vQ1V)Jz`JZe*j<5U6~RVuBEVJoY~ z&GE+F$f>4lN=X4-|9v*5O*Os>>r87u z!_1NSV?_X&HeFR1fOFb8_P)4lybJ6?1BWK`Tv2;4t|x1<#@17UO|hLGnrB%nu)fDk zfstJ4{X4^Y<8Lj<}g2^kksSefQTMuTo?tJLCh zC~>CR#a0hADw!_Vg*5fJwV{~S(j8)~sn>Oyt(ud2$1YfGck77}xN@3U_#T`q)f9!2 zf>Ia;Gwp2_C>WokU%(z2ec8z94pZyhaK+e>3a9sj^-&*V494;p9-xk+u1Jn#N_&xs z59OI2w=PuTErv|aNcK*>3l^W*p3}fjXJjJAXtBA#%B(-0--s;1U#f8gFYW!JL+iVG zV0SSx5w8eVgE?3Sg@eQv)=x<+-JgpVixZQNaZr}3b8sVyVs$@ndkF5FYKka@b+YAh z#nq_gzlIDKEs_i}H4f)(VQ!FSB}j>5znkVD&W0bOA{UZ7h!(FXrBbtdGA|PE1db>s z$!X)WY)u#7P8>^7Pjjj-kXNBuJX3(pJVetTZRNOnR5|RT5D>xmwxhAn)9KF3J05J; z-Mfb~dc?LUGqozC2p!1VjRqUwwDBnJhOua3vCCB-%ykW_ohSe?$R#dz%@Gym-8-RA zjMa_SJSzIl8{9dV+&63e9$4;{=1}w2=l+_j_Dtt@<(SYMbV-18&%F@Zl7F_5! z@xwJ0wiDdO%{}j9PW1(t+8P7Ud79yjY>x>aZYWJL_NI?bI6Y02`;@?qPz_PRqz(7v``20`- z033Dy|4;y6di|>cz|P-z|6c&3f&g^OAt8aN0Zd&0yZ>dq2aFCsE<~Ucf$v{sL=*++ zBxFSa2lfA+Y%U@B&3D=&CBO&u`#*nNc|PCY7XO<}MnG0VR764XrHtrb5zwC*2F!Lp zE<~Vj0;z!S-|3M4DFxuQ=`ShTf28<9p!81(0hFbGNqF%0gg*orez9!qt8e%o@Yfl@ zhvY}{@3&f??}7<`p>FyU;7?VkKbh8_=csozU=|fH&szgZ{=NDCylQ>EH^x5!K3~-V z)_2Y>0uJ`Z0Pb58y`RL+&n@m9tJ)O<%q#&u#DAIt+-rRt0eSe1MTtMl@W)H$b3D)@ z*A-1bUgZI)>HdcI4&W>P4W5{-j=s5p5`cbQ+{(g0+RDnz!TR^mxSLu_y#SDVKrj8i zA^hi6>jMGM;`$9Vfb-Yf!47b)Ow`2OKtNB=z|Kxa$5O}WPo;(Dc^`q(7X8kkeFyO8 z{XOq^07=u|7*P2`m;>PIFf=i80MKUxsN{d2cX0M+REsE*20+WQ79T9&cqT>=I_U% z{=8~^Isg(Nzo~`4iQfIb_#CVCD>#5h>=-Z#5dH}WxYzn%0)GAm6L2WdUdP=0_h>7f z(jh&7%1i(ZOn+}D8$iGK4Vs{pmHl_w4Qm-46H9>4^{3dz^DZDh+dw)6Xd@CpQNK$j z{CU;-cmpK=egplZ3y3%y=sEnCJ^eYVKXzV8H2_r*fJ*%*B;a1_lOpt6)IT1IAK2eB z{rie|uDJUrbgfUE>~C>@RO|m5ex55F{=~Bb4Cucp{ok7Yf9V}QuZ`#Gc|WaqsQlK- zKaV)iMRR__&Ak2Z=IM9R9g5$WM4u{a^C-7uX*!myEym z#_#p^T!P~#Dx$%^K>Y_nj_3J*E_LwJ60-5Xu=LkJAwcP@|0;a&+|+ZX`Jbj9P5;T% z|KOc}4*#4o{U?09`9Hz`Xo-I!P=9XfIrr*MQ}y=$!qgv?_J38^bNb4kM&_OVg^_=Eu-qG5U(fw0KMgH){C8pazq~51rN97hf#20-7=aK0)N|UM H-+%o-(+5aQ literal 0 HcmV?d00001 diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..cd7e85519c --- /dev/null +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat May 27 11:51:11 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip diff --git a/library-benchmarks/gradlew b/library-benchmarks/gradlew new file mode 100755 index 0000000000..9d82f78915 --- /dev/null +++ b/library-benchmarks/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/library-benchmarks/gradlew.bat b/library-benchmarks/gradlew.bat new file mode 100644 index 0000000000..aec99730b4 --- /dev/null +++ b/library-benchmarks/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/library-benchmarks/settings.gradle b/library-benchmarks/settings.gradle new file mode 100644 index 0000000000..78b0be004f --- /dev/null +++ b/library-benchmarks/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'realm-library-benchmarks' \ No newline at end of file diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmAllocBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.java similarity index 96% rename from realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmAllocBenchmarks.java rename to library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.java index f9f5a62a6b..3365dabc55 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmAllocBenchmarks.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.java @@ -31,8 +31,8 @@ import io.realm.RealmQuery; import io.realm.RealmResults; import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.entities.AllTypes; -import io.realm.entities.Dog; +import io.realm.benchmarks.entities.AllTypes; + @RunWith(SpannerRunner.class) public class RealmAllocBenchmarks { @@ -48,7 +48,7 @@ public void before() { Realm.deleteRealm(config); realm = Realm.getInstance(config); realm.beginTransaction(); - realm.createObject(AllTypes.class).getColumnRealmList().add(realm.createObject(Dog.class)); + realm.createObject(AllTypes.class).getColumnRealmList().add(realm.createObject(AllTypes.class)); realm.commitTransaction(); } diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.java similarity index 98% rename from realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmBenchmarks.java rename to library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.java index cf307a1b5b..a05d2676e8 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmBenchmarks.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.java @@ -29,7 +29,8 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.entities.AllTypes; +import io.realm.benchmarks.entities.AllTypes; + @RunWith(SpannerRunner.class) public class RealmBenchmarks { diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmInsertBenchmark.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.java similarity index 92% rename from realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmInsertBenchmark.java rename to library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.java index 8814a1b280..b8151e9e95 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmInsertBenchmark.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.java @@ -32,8 +32,9 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.entities.AllTypes; -import io.realm.entities.AllTypesPrimaryKey; +import io.realm.benchmarks.entities.AllTypes; +import io.realm.benchmarks.entities.AllTypesPrimaryKey; + @RunWith(SpannerRunner.class) public class RealmInsertBenchmark { @@ -43,8 +44,8 @@ public class RealmInsertBenchmark { private Realm realm; private static final int COLLECTION_SIZE = 100; - private List noPkObjects = new ArrayList(COLLECTION_SIZE); - private List pkObjects = new ArrayList(COLLECTION_SIZE); + private List noPkObjects = new ArrayList<>(COLLECTION_SIZE); + private List pkObjects = new ArrayList<>(COLLECTION_SIZE); @BeforeExperiment public void before() { diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java similarity index 98% rename from realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java rename to library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java index 30719457ff..974fe33815 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java @@ -29,7 +29,8 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.entities.AllTypes; +import io.realm.benchmarks.entities.AllTypes; + @RunWith(SpannerRunner.class) public class RealmObjectReadBenchmarks { diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java similarity index 98% rename from realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java rename to library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java index 68719a222f..3cf64b0d3b 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java @@ -27,7 +27,8 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.entities.AllTypes; +import io.realm.benchmarks.entities.AllTypes; + @RunWith(SpannerRunner.class) public class RealmObjectWriteBenchmarks { diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmQueryBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java similarity index 97% rename from realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmQueryBenchmarks.java rename to library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java index 8487c9d668..f1e3571c5d 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmQueryBenchmarks.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java @@ -16,8 +16,6 @@ package io.realm.benchmarks; -import android.support.test.InstrumentationRegistry; - import org.junit.runner.RunWith; import dk.ilios.spanner.AfterExperiment; @@ -31,7 +29,8 @@ import io.realm.RealmResults; import io.realm.Sort; import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.entities.AllTypes; +import io.realm.benchmarks.entities.AllTypes; + @RunWith(SpannerRunner.class) public class RealmQueryBenchmarks { diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmResultsBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.java similarity index 98% rename from realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmResultsBenchmarks.java rename to library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.java index f4cee113b9..e33aeed563 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/RealmResultsBenchmarks.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.java @@ -30,7 +30,8 @@ import io.realm.RealmConfiguration; import io.realm.RealmResults; import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.entities.AllTypes; +import io.realm.benchmarks.entities.AllTypes; + @RunWith(SpannerRunner.class) public class RealmResultsBenchmarks { diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/BenchmarkConfig.java similarity index 100% rename from realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/BenchmarkConfig.java rename to library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/BenchmarkConfig.java diff --git a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/CSVResultProcessor.java similarity index 99% rename from realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java rename to library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/CSVResultProcessor.java index 54e69d495d..d241934afa 100644 --- a/realm/realm-library/src/benchmarks/java/io/realm/benchmarks/config/CSVResultProcessor.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/CSVResultProcessor.java @@ -20,7 +20,6 @@ import com.opencsv.CSVWriter; import java.io.File; -import java.io.FileWriter; import java.io.IOException; import java.nio.charset.Charset; import java.text.DecimalFormat; diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypes.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypes.java new file mode 100644 index 0000000000..ec212de056 --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypes.java @@ -0,0 +1,125 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks.entities; + +import java.util.Date; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.Required; + + +public class AllTypes extends RealmObject { + + public static final String CLASS_NAME = "AllTypes"; + public static final String FIELD_STRING = "columnString"; + public static final String FIELD_LONG = "columnLong"; + public static final String FIELD_FLOAT = "columnFloat"; + public static final String FIELD_DOUBLE = "columnDouble"; + public static final String FIELD_BOOLEAN = "columnBoolean"; + public static final String FIELD_DATE = "columnDate"; + public static final String FIELD_BINARY = "columnBinary"; + public static final String FIELD_REALMOBJECT = "columnRealmObject"; + public static final String FIELD_REALMLIST = "columnRealmList"; + + public static final String[] INVALID_TYPES_FIELDS_FOR_DISTINCT = new String[]{FIELD_REALMOBJECT, FIELD_REALMLIST, FIELD_DOUBLE, FIELD_FLOAT}; + + @Required + private String columnString = ""; + private long columnLong; + private float columnFloat; + private double columnDouble; + private boolean columnBoolean; + @Required + private Date columnDate = new Date(0); + @Required + private byte[] columnBinary = new byte[0]; + private AllTypes columnRealmObject; + private RealmList columnRealmList; + + public String getColumnString() { + return columnString; + } + + public void setColumnString(String columnString) { + this.columnString = columnString; + } + + public long getColumnLong() { + return columnLong; + } + + public void setColumnLong(long columnLong) { + this.columnLong = columnLong; + } + + public float getColumnFloat() { + return columnFloat; + } + + public void setColumnFloat(float columnFloat) { + this.columnFloat = columnFloat; + } + + public double getColumnDouble() { + return columnDouble; + } + + public void setColumnDouble(double columnDouble) { + this.columnDouble = columnDouble; + } + + public boolean isColumnBoolean() { + return columnBoolean; + } + + public void setColumnBoolean(boolean columnBoolean) { + this.columnBoolean = columnBoolean; + } + + public Date getColumnDate() { + return columnDate; + } + + public void setColumnDate(Date columnDate) { + this.columnDate = columnDate; + } + + public byte[] getColumnBinary() { + return columnBinary; + } + + public void setColumnBinary(byte[] columnBinary) { + this.columnBinary = columnBinary; + } + + public AllTypes getColumnRealmObject() { + return columnRealmObject; + } + + public void setColumnRealmObject(AllTypes columnRealmObject) { + this.columnRealmObject = columnRealmObject; + } + + public RealmList getColumnRealmList() { + return columnRealmList; + } + + public void setColumnRealmList(RealmList columnRealmList) { + this.columnRealmList = columnRealmList; + } +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypesPrimaryKey.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypesPrimaryKey.java new file mode 100644 index 0000000000..71dc3d88ae --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypesPrimaryKey.java @@ -0,0 +1,118 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks.entities; + +import java.util.Date; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + + +public class AllTypesPrimaryKey extends RealmObject { + private String columnString; + @PrimaryKey + private long columnLong; + private float columnFloat; + private double columnDouble; + private boolean columnBoolean; + private Date columnDate; + private byte[] columnBinary; + private AllTypesPrimaryKey columnRealmObject; + private RealmList columnRealmList; + private Boolean columnBoxedBoolean; + + public String getColumnString() { + return columnString; + } + + public void setColumnString(String columnString) { + this.columnString = columnString; + } + + public long getColumnLong() { + return columnLong; + } + + public void setColumnLong(long columnLong) { + this.columnLong = columnLong; + } + + public float getColumnFloat() { + return columnFloat; + } + + public void setColumnFloat(float columnFloat) { + this.columnFloat = columnFloat; + } + + public double getColumnDouble() { + return columnDouble; + } + + public void setColumnDouble(double columnDouble) { + this.columnDouble = columnDouble; + } + + public boolean isColumnBoolean() { + return columnBoolean; + } + + public void setColumnBoolean(boolean columnBoolean) { + this.columnBoolean = columnBoolean; + } + + public Date getColumnDate() { + return columnDate; + } + + public void setColumnDate(Date columnDate) { + this.columnDate = columnDate; + } + + public byte[] getColumnBinary() { + return columnBinary; + } + + public void setColumnBinary(byte[] columnBinary) { + this.columnBinary = columnBinary; + } + + public AllTypesPrimaryKey getColumnRealmObject() { + return columnRealmObject; + } + + public void setColumnRealmObject(AllTypesPrimaryKey columnRealmObject) { + this.columnRealmObject = columnRealmObject; + } + + public RealmList getColumnRealmList() { + return columnRealmList; + } + + public void setColumnRealmList(RealmList columnRealmList) { + this.columnRealmList = columnRealmList; + } + + public Boolean getColumnBoxedBoolean() { + return columnBoxedBoolean; + } + + public void setColumnBoxedBoolean(Boolean columnBoxedBoolean) { + this.columnBoxedBoolean = columnBoxedBoolean; + } +} diff --git a/library-benchmarks/src/main/AndroidManifest.xml b/library-benchmarks/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..f05a423d71 --- /dev/null +++ b/library-benchmarks/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/library-benchmarks/src/main/res/values/strings.xml b/library-benchmarks/src/main/res/values/strings.xml new file mode 100644 index 0000000000..dbe19ccec8 --- /dev/null +++ b/library-benchmarks/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Realm Benchmarks + diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index a948d97b99..e5626b956f 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -79,9 +79,6 @@ android { } sourceSets { - androidTest { - java.srcDirs += 'src/benchmarks/java' - } androidTestObjectServer { java.srcDirs += 'src/syncIntegrationTest/java' assets.srcDirs += ['src/syncIntegrationTest/assets/'] @@ -162,8 +159,6 @@ dependencies { androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'org.hamcrest:hamcrest-library:1.3' - androidTestCompile 'com.opencsv:opencsv:3.4' - androidTestCompile 'dk.ilios:spanner:0.6.0' } task sourcesJar(type: Jar) { @@ -255,7 +250,6 @@ task checkstyle(type: Checkstyle) { source 'src' include '*/java/**/*.java' - exclude 'benchmarks/**' // Ingore tests for now. exclude '*Test*/**' @@ -275,24 +269,6 @@ checkstyle { } check.dependsOn tasks.checkstyle -// Configuration options can be found here: -// http://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html -task connectedBenchmarks(type: GradleBuild) { - description = 'Run all benchmarks on connected devices' - group = 'Verification' - buildFile = file("${projectDir}/build.gradle") - startParameter.getProjectProperties().put('android.testInstrumentationRunnerArguments.package', 'io.realm.benchmarks') - tasks = ['connectedCheck'] -} - -task connectedUnitTests(type: GradleBuild) { - description = 'Run all unit tests on connected devices' - group = 'Verification' - buildFile = file("${projectDir}/build.gradle") - startParameter.getProjectProperties().put('android.testInstrumentationRunnerArguments.notPackage', 'io.realm.benchmarks') - tasks = ['connectedAndroidTest'] -} - install { repositories.mavenInstaller { pom { diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index d9e252dce4..d92707182b 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -8,7 +8,6 @@ - diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java index 1259fbbae9..906f913009 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnIndicesTests.java @@ -18,8 +18,6 @@ import android.support.annotation.NonNull; import android.support.test.runner.AndroidJUnit4; -import com.google.common.collect.ImmutableMap; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -27,6 +25,9 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.util.Collections; +import java.util.HashMap; + import io.realm.entities.Cat; import io.realm.entities.Dog; import io.realm.internal.ColumnIndices; @@ -73,11 +74,11 @@ private ColumnIndices create(long schemaVersion) { dogColumnInfo = (DogRealmProxy.DogColumnInfo) mediator.validateTable(Dog.class, realm.sharedRealm, false); Pair, String> catDesc = Pair., String>create(Cat.class, "Cat"); Pair, String> dogDesc = Pair., String>create(Dog.class, "Dog"); - return new ColumnIndices(schemaVersion, - ImmutableMap.of( - catDesc, catColumnInfo, - dogDesc, dogColumnInfo) - ); + + HashMap, String>, ColumnInfo> map = new HashMap<>(); + map.put(catDesc, catColumnInfo); + map.put(dogDesc, dogColumnInfo); + return new ColumnIndices(schemaVersion, Collections.unmodifiableMap(map)); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java index c89c7666f2..633184a5d8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java @@ -24,7 +24,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import dk.ilios.spanner.All; import io.realm.entities.AllJavaTypes; import io.realm.rule.TestRealmConfigurationFactory; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index c4ebe934fd..c5ea5c07a1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -22,8 +22,6 @@ import android.support.test.runner.AndroidJUnit4; import android.util.Base64; -import com.google.gson.internal.bind.util.ISO8601Utils; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -37,6 +35,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -442,7 +442,7 @@ public void createFromJson_defaultValuesAreIgnored() throws JSONException { json.put(DefaultValueOfField.FIELD_FLOAT, fieldFloatValue); json.put(DefaultValueOfField.FIELD_DOUBLE, fieldDoubleValue); json.put(DefaultValueOfField.FIELD_BOOLEAN, fieldBooleanValue); - json.put(DefaultValueOfField.FIELD_DATE, ISO8601Utils.format(fieldDateValue, true)); + json.put(DefaultValueOfField.FIELD_DATE, getISO8601Date(fieldDateValue)); json.put(DefaultValueOfField.FIELD_BINARY, Base64.encodeToString(fieldBinaryValue, Base64.DEFAULT)); // Value for 'fieldObject' final JSONObject fieldObjectJson = new JSONObject(); @@ -496,6 +496,13 @@ public void createFromJson_defaultValuesAreIgnored() throws JSONException { assertEquals(3, realm.where(RandomPrimaryKey.class).count()); } + private String getISO8601Date(Date date) { + TimeZone tz = TimeZone.getTimeZone("UTC"); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Quoted "Z" to indicate UTC, no timezone offset + df.setTimeZone(tz); + return df.format(date); + } + @Test public void updateFromJson_defaultValuesAreIgnored() throws JSONException { final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1; @@ -535,7 +542,7 @@ public void updateFromJson_defaultValuesAreIgnored() throws JSONException { json.put(DefaultValueOfField.FIELD_FLOAT, fieldFloatValue); json.put(DefaultValueOfField.FIELD_DOUBLE, fieldDoubleValue); json.put(DefaultValueOfField.FIELD_BOOLEAN, fieldBooleanValue); - json.put(DefaultValueOfField.FIELD_DATE, ISO8601Utils.format(fieldDateValue, true)); + json.put(DefaultValueOfField.FIELD_DATE, getISO8601Date(fieldDateValue)); json.put(DefaultValueOfField.FIELD_BINARY, Base64.encodeToString(fieldBinaryValue, Base64.DEFAULT)); // value for 'fieldObject' final JSONObject fieldObjectJson = new JSONObject(); From 3733d5bc7d31a69d172caa2420b405e195cf7896 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 30 May 2017 09:39:32 +0200 Subject: [PATCH 0723/2110] Enable Kotlin for unit tests (#4719) --- realm/build.gradle | 2 + realm/realm-library/build.gradle | 11 ++- .../kotlin/io/realm/KotlinSchemaTests.kt | 71 ++++++++++++++++ .../io/realm/entities/AllKotlinTypes.kt | 80 +++++++++++++++++++ 4 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/KotlinSchemaTests.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/AllKotlinTypes.kt diff --git a/realm/build.gradle b/realm/build.gradle index e35f2580cb..f2eead6c59 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,4 +1,5 @@ buildscript { + ext.kotlin_version = '1.1.2-4' repositories { mavenLocal() jcenter() @@ -17,6 +18,7 @@ buildscript { classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index e5626b956f..d567cbe844 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -1,6 +1,7 @@ import java.security.MessageDigest apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'maven-publish' apply plugin: 'com.jfrog.artifactory' @@ -79,6 +80,9 @@ android { } sourceSets { + androidTest { + java.srcDirs += 'src/androidTest/kotlin' + } androidTestObjectServer { java.srcDirs += 'src/syncIntegrationTest/java' assets.srcDirs += ['src/syncIntegrationTest/assets/'] @@ -137,6 +141,7 @@ android.registerTransform(new RealmTransformer()) repositories { maven { url "https://jitpack.io" } + mavenCentral() } dependencies { @@ -147,10 +152,10 @@ dependencies { compile "io.realm:realm-annotations:${version}" compile 'com.getkeepsafe.relinker:relinker:1.2.2' - objectServerAnnotationProcessor project(':realm-annotations-processor') + kaptObjectServer project(':realm-annotations-processor') objectServerCompile 'com.squareup.okhttp3:okhttp:3.4.1' - androidTestAnnotationProcessor project(':realm-annotations-processor') + kaptAndroidTest project(':realm-annotations-processor') androidTestCompile fileTree(dir: 'testLibs', include: ['*.jar']) androidTestCompile 'io.reactivex:rxjava:1.1.0' androidTestCompile 'com.android.support:support-annotations:25.3.1' @@ -159,6 +164,8 @@ dependencies { androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'org.hamcrest:hamcrest-library:1.3' + androidTestCompile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + androidTestCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } task sourcesJar(type: Jar) { diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/KotlinSchemaTests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/KotlinSchemaTests.kt new file mode 100644 index 0000000000..b851ab45dc --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/KotlinSchemaTests.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm + +import android.support.test.runner.AndroidJUnit4 +import io.realm.entities.AllKotlinTypes +import io.realm.rule.TestRealmConfigurationFactory +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.reflect.full.memberProperties + +/** + * This class tests how Kotlin classes are interpreted by Realm and exposed in the RealmSchema + */ +@RunWith(AndroidJUnit4::class) +class KotlinSchemaTests { + + @get:Rule + val configFactory = TestRealmConfigurationFactory() + + private lateinit var realm: Realm + + @Before + fun setUp() { + realm = Realm.getInstance(configFactory.createConfiguration()) + } + + @After + fun tearDown() { + realm.close() + } + + @Test + fun kotlinTypeNonNull() { + val objSchema = realm.getSchema().get(AllKotlinTypes::class.simpleName) + + // Document current nullability. Ideally all should be non-nullable. This is currently + // not the case. + // TODO We should fix this. Tracked by https://github.com/realm/realm-java/issues/4701 + assertTrue(objSchema.isNullable(AllKotlinTypes::nonNullBinary.name)); + assertFalse(objSchema.isNullable(AllKotlinTypes::nonNullBoolean.name)); + assertTrue(objSchema.isNullable(AllKotlinTypes::nonNullString.name)); + assertFalse(objSchema.isNullable(AllKotlinTypes::nonNullLong.name)); + assertFalse(objSchema.isNullable(AllKotlinTypes::nonNullInt.name)); + assertFalse(objSchema.isNullable(AllKotlinTypes::nonNullShort.name)); + assertFalse(objSchema.isNullable(AllKotlinTypes::nonNullByte.name)); + assertTrue(objSchema.isNullable(AllKotlinTypes::nonNullDate.name)); + assertFalse(objSchema.isNullable(AllKotlinTypes::nonNullDouble.name)); + assertFalse(objSchema.isNullable(AllKotlinTypes::nonNullFloat.name)); + assertFalse(objSchema.isNullable(AllKotlinTypes::nonNullList.name)); + assertTrue(objSchema.isNullable(AllKotlinTypes::nonNullObject.name)); + } +} \ No newline at end of file diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/AllKotlinTypes.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/AllKotlinTypes.kt new file mode 100644 index 0000000000..527f8d9593 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/AllKotlinTypes.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.entities + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.RealmResults +import io.realm.annotations.Ignore +import io.realm.annotations.Index +import io.realm.annotations.LinkingObjects +import io.realm.annotations.PrimaryKey +import java.util.* + +open class AllKotlinTypes : RealmObject() { + + @Ignore + var ignoredString: String = "" + var nullString: String? = null + var nonNullString: String = "" + @Index + var indexedString: String = "" + + var nullLong: Long? = null + @PrimaryKey + var nonNullLong: Long = 0 + + var nullShort : Short? = null + var nonNullShort: Short = 0 + + var nullInt: Int? = null + var nonNullInt: Int = 0 + + var nullByte: Byte? = null + var nonNullByte: Byte = 0 + + var nullFloat: Float? = null + var nonNullFloat: Float = 0F + + var nullDouble: Double? = null + var nonNullDouble: Double = 0.0 // Double by default + + var nullBoolean: Boolean? = null + var nonNullBoolean: Boolean = false + + var nullDate: Date? = null + var nonNullDate: Date = Date() + + var nullBinary: ByteArray? = null + var nonNullBinary: ByteArray = ByteArray(0) + +// This turns into Byte[] which we dont support for some reason? +// var nullBoxedBinary: Array? = null +// var nonNullBoxedBinary: Array = emptyArray() + + var nullObject: AllKotlinTypes? = null + var nonNullObject: AllKotlinTypes = AllKotlinTypes() + + var nullList: RealmList? = null // This should not be allowed + var nonNullList: RealmList = RealmList() + + @LinkingObjects("nonNullObject") + val objectParents: RealmResults? = null; + + @LinkingObjects("nonNullList") + val listParents: RealmResults? = null; +} From c62b8808eebb5fa804f43e1055ffade17f679f8f Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 31 May 2017 18:06:06 +0900 Subject: [PATCH 0724/2110] fix crash when authentication error happens (#4726) (#4732) --- CHANGELOG.md | 6 ++++-- .../src/objectServer/java/io/realm/SyncSession.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e63a0798..53f3fe62ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Bug Fixes +* [ObjectServer] Fixed a crash when an authentication error happend (#4726). + ### Internal @@ -13,7 +15,7 @@ ### Bug Fixes -* Accept extra columns against synced Realm (#4706). +* [ObjectServer] Accepted extra columns against synced Realm (#4706). ## 3.3.0 (2017-05-24) @@ -66,7 +68,7 @@ ### Internal -* Use separated locks for different `RealmCache`s ($4551). +* Use separated locks for different `RealmCache`s (#4551). ## 3.1.4 (2017-05-04) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index b008a2fae3..846e79bd98 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -324,7 +324,8 @@ protected void onSuccess(AuthenticateResponse response) { @Override protected void onError(AuthenticateResponse response) { onGoingAccessTokenQuery.set(false); - RealmLog.debug("Session[%s]: Failed to get access token (%d)", configuration.getPath(), response.getError().getErrorCode()); + RealmLog.debug("Session[%s]: Failed to get access token (%s)", configuration.getPath(), + response.getError().getErrorCode()); if (!isClosed && !Thread.currentThread().isInterrupted()) { errorHandler.onError(SyncSession.this, response.getError()); } From 90c6102de85c62466d4834c6b8874dacfdc0f6da Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Wed, 31 May 2017 10:17:07 -0700 Subject: [PATCH 0725/2110] Factor out ManagedObject Interface (#4715) * Factor out ManagedObject Interface * Add JavaDoc header * Respond to comments --- CHANGELOG.md | 1 + .../main/java/io/realm/RealmCollection.java | 6 ++- .../src/main/java/io/realm/RealmObject.java | 5 ++- .../io/realm/internal/ManagableObject.java | 39 +++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 53f3fe62ba..ae132b6e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Internal +* Factor out internal interface ManagedObject ## 3.3.1 (2017-05-26) diff --git a/realm/realm-library/src/main/java/io/realm/RealmCollection.java b/realm/realm-library/src/main/java/io/realm/RealmCollection.java index 7d6d866549..30dbd66c0d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCollection.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.Date; +import io.realm.internal.ManagableObject; + /** * {@code RealmCollection} is the root of the collection hierarchy that Realm supports. It defines operations on data @@ -31,7 +33,7 @@ * * @param type of {@link RealmObject} stored in the collection. */ -public interface RealmCollection extends Collection { +public interface RealmCollection extends Collection, ManagableObject { /** * Returns a {@link RealmQuery}, which can be used to query for specific objects from this collection. @@ -144,6 +146,7 @@ public interface RealmCollection extends Collection { * * @return {@code true} if it is still valid to use or an unmanaged collection, {@code false} otherwise. */ + @Override boolean isValid(); /** @@ -159,6 +162,7 @@ public interface RealmCollection extends Collection { * * @return {@code true} if this is a managed {@link RealmCollection}, {@code false} otherwise. */ + @Override boolean isManaged(); /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 85199653ac..6652d40d22 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -20,6 +20,7 @@ import io.realm.annotations.RealmClass; import io.realm.internal.InvalidRow; +import io.realm.internal.ManagableObject; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import rx.Observable; @@ -66,7 +67,7 @@ */ @RealmClass -public abstract class RealmObject implements RealmModel { +public abstract class RealmObject implements RealmModel, ManagableObject { /** * Deletes the object from the Realm it is currently associated to. @@ -128,6 +129,7 @@ public static void deleteFromRealm(E object) { * @return {@code true} if the object is still accessible or an unmanaged object, {@code false} otherwise. * @see Examples using Realm with RxJava */ + @Override public final boolean isValid() { return RealmObject.isValid(this); } @@ -256,6 +258,7 @@ public static boolean isLoaded(E object) { * * @return {@code true} if the object is managed, {@code false} if it is unmanaged. */ + @Override public boolean isManaged() { return isManaged(this); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java b/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java new file mode 100644 index 0000000000..884da88dbb --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.internal; + +/** + * This internal interface represents a java object that corresponds to data + * that may be managed in the Realm core. It specifies the operations common to all such objects. + */ +public interface ManagableObject { + + /** + * Checks to see if this object is managed by Realm.. + * + * @return {@code true} if this is a managed Realm object, {@code false} otherwise. + */ + boolean isManaged(); + + /** + * Checks to see if the managed object is still valid to use. + * That is if it that it hasn't been deleted nor has the {@link io.realm.Realm} been closed. + * It will always return {@code true} for unmanaged objects. + * + * @return {@code true} if this object is unmanaged or is still valid for use, {@code false} otherwise. + */ + boolean isValid(); +} From da82568a63b86e611d3a2f53682914b14c983d7f Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 1 Jun 2017 09:49:58 +0900 Subject: [PATCH 0726/2110] add Realm.getDefaultConfiguration() (#4725). --- CHANGELOG.md | 2 + .../io/realm/RealmConfigurationTests.java | 38 ++++++++++++++----- .../src/main/java/io/realm/Realm.java | 36 ++++++++++++++---- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa00936b8d..52017daf83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Enhancements +* Added `Realm.getDefaultConfiguration()` (#4725). + ### Bug Fixes * [ObjectServer] Fixed a crash when an authentication error happend (#4726). diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 4064615145..4030017f58 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -59,6 +59,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -91,15 +92,8 @@ public void tearDown() throws Exception { } } - private void clearDefaultConfiguration() throws NoSuchFieldException, IllegalAccessException { - final Field field = Realm.class.getDeclaredField("defaultConfiguration"); - field.setAccessible(true); - field.set(null, null); - } - @Test - public void setDefaultConfiguration_nullThrows() throws NoSuchFieldException, IllegalAccessException { - clearDefaultConfiguration(); + public void setDefaultConfiguration_nullThrows() { try { Realm.setDefaultConfiguration(null); fail(); @@ -107,6 +101,30 @@ public void setDefaultConfiguration_nullThrows() throws NoSuchFieldException, Il } } + @Test + public void getDefaultConfiguration_returnsTheSameObjectThatSetDefaultConfigurationSet() { + final RealmConfiguration defaultConfiguration = Realm.getDefaultConfiguration(); + try { + final RealmConfiguration config = new RealmConfiguration.Builder().build(); + Realm.setDefaultConfiguration(config); + + assertSame(config, Realm.getDefaultConfiguration()); + } finally { + Realm.setDefaultConfiguration(defaultConfiguration); + } + } + + @Test + public void getDefaultConfiguration_returnsNullAfterRemoveDefaultConfiguration() { + final RealmConfiguration defaultConfiguration = Realm.getDefaultConfiguration(); + try { + Realm.removeDefaultConfiguration(); + + assertNull(Realm.getDefaultConfiguration()); + } finally { + Realm.setDefaultConfiguration(defaultConfiguration); + }} + @Test public void getInstance_nullConfigThrows() { try { @@ -297,8 +315,8 @@ public void modules() { } @Test - public void setDefaultConfiguration() throws NoSuchFieldException, IllegalAccessException { - clearDefaultConfiguration(); + public void setDefaultConfiguration() { + Realm.removeDefaultConfiguration(); Realm.setDefaultConfiguration(defaultConfig); realm = Realm.getDefaultInstance(); assertEquals(realm.getPath(), defaultConfig.getPath()); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 35c1f89afc..9230a14994 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -38,7 +38,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Scanner; @@ -134,6 +133,8 @@ public class Realm extends BaseRealm { public static final String DEFAULT_REALM_NAME = RealmConfiguration.DEFAULT_REALM_NAME; + private static final Object monitorForDefaultConfiguration = new Object(); + // guarded by `monitorForDefaultConfiguration` private static RealmConfiguration defaultConfiguration; /** @@ -195,7 +196,7 @@ public static synchronized void init(Context context) { } checkFilesDirAvailable(context); RealmCore.loadLibrary(context); - defaultConfiguration = new RealmConfiguration.Builder(context).build(); + setDefaultConfiguration(new RealmConfiguration.Builder(context).build()); ObjectServerFacade.getSyncFacadeIfPossible().init(context); BaseRealm.applicationContext = context.getApplicationContext(); SharedRealm.initialize(new File(context.getFilesDir(), ".realm.temp")); @@ -267,10 +268,15 @@ private static void checkFilesDirAvailable(Context context) { * was set and the thread opening the Realm was interrupted while the download was in progress. */ public static Realm getDefaultInstance() { - if (defaultConfiguration == null) { - throw new IllegalStateException("Call `Realm.init(Context)` before calling this method."); + RealmConfiguration configuration = getDefaultConfiguration(); + if (configuration == null) { + if (BaseRealm.applicationContext == null) { + throw new IllegalStateException("Call `Realm.init(Context)` before calling this method."); + } else { + throw new IllegalStateException("Set default configuration by using `Realm.setDefaultConfiguration(RealmConfiguration)`."); + } } - return RealmCache.createRealmOrGetFromCache(defaultConfiguration, Realm.class); + return RealmCache.createRealmOrGetFromCache(configuration, Realm.class); } /** @@ -325,7 +331,21 @@ public static void setDefaultConfiguration(RealmConfiguration configuration) { if (configuration == null) { throw new IllegalArgumentException("A non-null RealmConfiguration must be provided"); } - defaultConfiguration = configuration; + synchronized (monitorForDefaultConfiguration) { + defaultConfiguration = configuration; + } + } + + /** + * Returns the default configuration for {@link #getDefaultInstance()}. + * + * @return default configuration object or {@code null} if {@link #init(Context)} was not called yet + * or {@link #removeDefaultConfiguration()} was called recently. + */ + public static RealmConfiguration getDefaultConfiguration() { + synchronized (monitorForDefaultConfiguration) { + return defaultConfiguration; + } } /** @@ -333,7 +353,9 @@ public static void setDefaultConfiguration(RealmConfiguration configuration) { * fail until a new default configuration has been set using {@link #setDefaultConfiguration(RealmConfiguration)}. */ public static void removeDefaultConfiguration() { - defaultConfiguration = null; + synchronized (monitorForDefaultConfiguration) { + defaultConfiguration = null; + } } /** From db523071d0a7dcceaf62919512a3c8a0b9b73b78 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 1 Jun 2017 09:50:47 +0900 Subject: [PATCH 0727/2110] Revert "add Realm.getDefaultConfiguration() (#4725)." This reverts commit da82568a63b86e611d3a2f53682914b14c983d7f. --- CHANGELOG.md | 2 - .../io/realm/RealmConfigurationTests.java | 38 +++++-------------- .../src/main/java/io/realm/Realm.java | 36 ++++-------------- 3 files changed, 17 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52017daf83..aa00936b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,6 @@ ### Enhancements -* Added `Realm.getDefaultConfiguration()` (#4725). - ### Bug Fixes * [ObjectServer] Fixed a crash when an authentication error happend (#4726). diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 4030017f58..4064615145 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -59,7 +59,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -92,8 +91,15 @@ public void tearDown() throws Exception { } } + private void clearDefaultConfiguration() throws NoSuchFieldException, IllegalAccessException { + final Field field = Realm.class.getDeclaredField("defaultConfiguration"); + field.setAccessible(true); + field.set(null, null); + } + @Test - public void setDefaultConfiguration_nullThrows() { + public void setDefaultConfiguration_nullThrows() throws NoSuchFieldException, IllegalAccessException { + clearDefaultConfiguration(); try { Realm.setDefaultConfiguration(null); fail(); @@ -101,30 +107,6 @@ public void setDefaultConfiguration_nullThrows() { } } - @Test - public void getDefaultConfiguration_returnsTheSameObjectThatSetDefaultConfigurationSet() { - final RealmConfiguration defaultConfiguration = Realm.getDefaultConfiguration(); - try { - final RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.setDefaultConfiguration(config); - - assertSame(config, Realm.getDefaultConfiguration()); - } finally { - Realm.setDefaultConfiguration(defaultConfiguration); - } - } - - @Test - public void getDefaultConfiguration_returnsNullAfterRemoveDefaultConfiguration() { - final RealmConfiguration defaultConfiguration = Realm.getDefaultConfiguration(); - try { - Realm.removeDefaultConfiguration(); - - assertNull(Realm.getDefaultConfiguration()); - } finally { - Realm.setDefaultConfiguration(defaultConfiguration); - }} - @Test public void getInstance_nullConfigThrows() { try { @@ -315,8 +297,8 @@ public void modules() { } @Test - public void setDefaultConfiguration() { - Realm.removeDefaultConfiguration(); + public void setDefaultConfiguration() throws NoSuchFieldException, IllegalAccessException { + clearDefaultConfiguration(); Realm.setDefaultConfiguration(defaultConfig); realm = Realm.getDefaultInstance(); assertEquals(realm.getPath(), defaultConfig.getPath()); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 9230a14994..35c1f89afc 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -38,6 +38,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Scanner; @@ -133,8 +134,6 @@ public class Realm extends BaseRealm { public static final String DEFAULT_REALM_NAME = RealmConfiguration.DEFAULT_REALM_NAME; - private static final Object monitorForDefaultConfiguration = new Object(); - // guarded by `monitorForDefaultConfiguration` private static RealmConfiguration defaultConfiguration; /** @@ -196,7 +195,7 @@ public static synchronized void init(Context context) { } checkFilesDirAvailable(context); RealmCore.loadLibrary(context); - setDefaultConfiguration(new RealmConfiguration.Builder(context).build()); + defaultConfiguration = new RealmConfiguration.Builder(context).build(); ObjectServerFacade.getSyncFacadeIfPossible().init(context); BaseRealm.applicationContext = context.getApplicationContext(); SharedRealm.initialize(new File(context.getFilesDir(), ".realm.temp")); @@ -268,15 +267,10 @@ private static void checkFilesDirAvailable(Context context) { * was set and the thread opening the Realm was interrupted while the download was in progress. */ public static Realm getDefaultInstance() { - RealmConfiguration configuration = getDefaultConfiguration(); - if (configuration == null) { - if (BaseRealm.applicationContext == null) { - throw new IllegalStateException("Call `Realm.init(Context)` before calling this method."); - } else { - throw new IllegalStateException("Set default configuration by using `Realm.setDefaultConfiguration(RealmConfiguration)`."); - } + if (defaultConfiguration == null) { + throw new IllegalStateException("Call `Realm.init(Context)` before calling this method."); } - return RealmCache.createRealmOrGetFromCache(configuration, Realm.class); + return RealmCache.createRealmOrGetFromCache(defaultConfiguration, Realm.class); } /** @@ -331,21 +325,7 @@ public static void setDefaultConfiguration(RealmConfiguration configuration) { if (configuration == null) { throw new IllegalArgumentException("A non-null RealmConfiguration must be provided"); } - synchronized (monitorForDefaultConfiguration) { - defaultConfiguration = configuration; - } - } - - /** - * Returns the default configuration for {@link #getDefaultInstance()}. - * - * @return default configuration object or {@code null} if {@link #init(Context)} was not called yet - * or {@link #removeDefaultConfiguration()} was called recently. - */ - public static RealmConfiguration getDefaultConfiguration() { - synchronized (monitorForDefaultConfiguration) { - return defaultConfiguration; - } + defaultConfiguration = configuration; } /** @@ -353,9 +333,7 @@ public static RealmConfiguration getDefaultConfiguration() { * fail until a new default configuration has been set using {@link #setDefaultConfiguration(RealmConfiguration)}. */ public static void removeDefaultConfiguration() { - synchronized (monitorForDefaultConfiguration) { - defaultConfiguration = null; - } + defaultConfiguration = null; } /** From 41c0c85f8cf0e49c6f99c168f04f3fc724394a0e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 1 Jun 2017 17:19:29 +0800 Subject: [PATCH 0728/2110] Fix typo (#4739) --- .../src/objectServer/java/io/realm/SyncConfiguration.java | 2 +- .../java/io/realm/SSLConfigurationTests.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index b1d1336a8c..4724b9c7d5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -828,7 +828,7 @@ public SyncConfiguration build() { String fileName = serverCertificateAssetName.substring(serverCertificateAssetName.lastIndexOf(File.separatorChar) + 1); serverCertificateFilePath = new File(realmFileDirectory, fileName).getAbsolutePath(); } else { - RealmLog.warn("SSL Verification is disable, server certificate provided will not be used"); + RealmLog.warn("SSL Verification is disabled, the provided server certificate will not be used."); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index b64c5c69cd..82c024fb2f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -182,7 +182,8 @@ public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { .disableSSLVerification() .build(); - assertEquals("SSL Verification is disable, server certificate provided will not be used", testLogger.message); + assertEquals("SSL Verification is disabled, the provided server certificate will not be used.", + testLogger.message); } @Test From d7122effea0856184c99820a9484bec6fc5d5c78 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 1 Jun 2017 14:28:12 +0200 Subject: [PATCH 0729/2110] Backlink queries (#4704) * Fixing unit tests for backlink queries. * Reintroducing of a native implementation of isNotEmpty(). * Moving inverse relationships out of beta stage. --- CHANGELOG.md | 3 + .../io/realm/annotations/LinkingObjects.java | 6 +- .../io/realm/LinkingObjectsQueryTests.java | 101 ++++++++--- .../androidTest/java/io/realm/QueryTests.java | 19 +- .../java/io/realm/RealmQueryTests.java | 40 +++-- .../main/cpp/io_realm_internal_TableQuery.cpp | 162 +++++++++++++----- .../java/io/realm/internal/ColumnInfo.java | 6 +- .../java/io/realm/internal/TableQuery.java | 6 +- 8 files changed, 257 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa00936b8d..5e07758307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Enhancements +* Added support for querying inverse relationships (#2904). +* Moved inverse relationships out of beta stage. + ### Bug Fixes * [ObjectServer] Fixed a crash when an authentication error happend (#4726). diff --git a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java index 3eb4afa3b9..3ccd9c4772 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java +++ b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java @@ -91,10 +91,14 @@ * assert john.dogs.size() == 2; * assert fido.owners.size() == 2; * } + *

                      + * Querying inverse relationship is like querying any {@code RealmResults}. This means that an inverse relationship + * cannot be {@code null} but it can be empty (length is 0). It is possible to query fields in the source class. This is + * equivalent to link queries. Please read {@link https://realm.io/docs/java/latest/#link-queries} for more + * information. */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) -@Beta public @interface LinkingObjects { /** * The name of a field that contains a relation to an instance of the diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java index 70d5218ecc..91155ee79a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java @@ -17,7 +17,6 @@ import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,7 +29,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -@Ignore @RunWith(AndroidJUnit4.class) public class LinkingObjectsQueryTests extends QueryTests { @@ -65,6 +63,11 @@ public void query_startWithBacklink() { realm.commitTransaction(); + // row 0: backlink to rows 1 and 2; row 1 link to row 0, included + // row 1: no backlink, not included + // row 2: no backlink, not included + // row 3: no backlink, not included + // summary: 1 row (gen1) RealmResults result = realm.where(AllJavaTypes.class) .greaterThan(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_ID, 1) .findAll(); @@ -100,12 +103,17 @@ public void query_backlinkInMiddle() { realm.commitTransaction(); - // TODO: Explain what this test is doing + // row 0: no link, not included + // row 1: link to row 0, backlink to rows 1 and 2, row 2 has id < 2, included + // row 2: link to row 0, backlink to rows 1 and 2, row 2 has id < 2, included + // row 3: no link, not included + // summary: 2 rows (gen2A and gen2B) RealmResults result = realm.where(AllJavaTypes.class) .lessThan(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_ID, 2) .findAll(); - assertEquals(1, result.size()); + assertEquals(2, result.size()); assertTrue(result.contains(gen2A)); + assertTrue(result.contains(gen2B)); } // Tests isNotNull on link's nullable field. @@ -342,7 +350,11 @@ public void isEmpty() { for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { switch (type) { case LINKING_OBJECTS: + // Row 0: backlink to row 0; not included + // Row 1: backlink to row 1; not included + // Row 2: no backlink; included assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT).count()); + // Only row 1 has a linklist (and a backlink) assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST).count()); break; default: @@ -357,7 +369,12 @@ public void isEmpty_acrossLink() { for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { switch (type) { case LINKING_OBJECTS: + // Rows 0 and 1 are not included as they are linked to another row through FIELD_OBJECT + // Row 2 is included (no link) assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + // Row 0 has link to row 0 which has a backlink (list); not included + // Row 1 has link to row 1 which has a backlink (list); not included + // Row 2 has no link; included assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); break; default: @@ -378,9 +395,14 @@ public void isEmpty_acrossLinkingObjectObjectLink() { assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_BINARY).count()); break; case LIST: - assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LIST).count()); + // Row 0: backlink to row 0, linklist is empty; included + // Row 1: backlink to row 1, linklist to row 0; not included + // Row 2: no backlink; included + assertEquals(2, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LIST).count()); break; case LINKING_OBJECTS: + // Both row 0 and 1 have a link/backlink; not included + // row 2 has no link/backlink and an empty list; included assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); break; @@ -393,20 +415,33 @@ public void isEmpty_acrossLinkingObjectObjectLink() { @Test public void isEmpty_acrossLinkingObjectListLink() { createIsEmptyDataSet(realm); + assertEquals(3, realm.where(AllJavaTypes.class).findAll().size()); for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { switch (type) { case STRING: + // Row 2 included (has no backlink) assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_STRING).count()); break; case BINARY: + // Row 2 included (has no backlink) assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_BINARY).count()); break; case LIST: assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LIST).count()); break; case LINKING_OBJECTS: - assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); - assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_LIST).count()); + // Row 0: Backlink (list) to row 1, row 1 backlink to row 1; not included + // Row 1: Backlink (list) to row 2, row 2 no backlink; included + // Row 2: No backlink (list); included + assertEquals(2, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + + // Step 1: + // Row 0 skipped; FIELD_LO_LIST.count > 0 + // Row 1 included; FIELD_LO_LIST.count() == 0 + // + // Step 2: now checking Row 2 + // Row 0 included: goes to Row 1 where FIELD_LO_LIST.count() == 0 + assertEquals(2, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_LIST).count()); break; default: fail("Unknown type: " + type); @@ -420,7 +455,8 @@ public void isNotEmpty() { for (RealmFieldType type : SUPPORTED_IS_NOT_EMPTY_TYPES) { switch (type) { case LINKING_OBJECTS: - assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT).count()); + // Row 0 and 1 have a link/backlink so no row is empty + assertEquals(0, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT).count()); assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST).count()); break; default: @@ -436,7 +472,8 @@ public void isNotEmpty_acrossLink() { switch (type) { case LINKING_OBJECTS: // tested in LinkingObjectsQueryTests; - assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + // Row 0 and Row 1 have link/backlink - no empty + assertEquals(0, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); break; default: @@ -451,6 +488,8 @@ public void isNotEmpty_acrossLinkingObjectObjectLink() { for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { switch (type) { case STRING: + // Row 0: Follow link to row 0, and FIELD_STRING is empty ("") + // Row 1: Follow link to row 1, and FIELD_STRING is not empty ("Foo") assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_STRING).count()); break; case BINARY: @@ -460,8 +499,13 @@ public void isNotEmpty_acrossLinkingObjectObjectLink() { assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LIST).count()); break; case LINKING_OBJECTS: - assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); - assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); + // Both row 0 and 1 have a link/backlink + assertEquals(2, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + + // Row 0: Backlink to row 0, backlink list to row 1; included + // Row 1: Backlink to row 1, backlink list to row 2; included + // Row 2: No backlink; not empty + assertEquals(2, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); break; default: fail("Unknown type: " + type); @@ -472,19 +516,30 @@ public void isNotEmpty_acrossLinkingObjectObjectLink() { @Test public void isNotEmpty_acrossLinkingObjectListLink() { createIsEmptyDataSet(realm); + assertEquals(3, realm.where(AllJavaTypes.class).findAll().size()); for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { switch (type) { case STRING: + // Row 0: Backlink list to row 1, string not empty ("Foo"); included + // Row 1: Backlink list to row 2, string is empty; not included + // Row 2: No backlink list; not included assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_STRING).count()); break; case BINARY: assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_BINARY).count()); break; case LIST: - assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LIST).count()); + // Row 0: Backlink list to row 1, list to row 0; included + // Row 1: Backlink list to row 2, list to row 1; included + // Row 2: No backlink list; not included + assertEquals(2, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LIST).count()); break; case LINKING_OBJECTS: assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + + // Row 0: Backlink list to row 1, backlink list to row 2; included + // Row 1: Backlink list to row 2, empty backlink list; not included + // Row 2: Empty backlink list; not included assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST + "." + AllJavaTypes.FIELD_LO_LIST).count()); break; default: @@ -497,13 +552,13 @@ public void isNotEmpty_acrossLinkingObjectListLink() { // Creates 3 NullTypes objects. The objects are self-referenced (link) in // order to test link queries. // - // +-+--------+------+---------+--------+--------------------+ - // | | string | link | numeric | binary | numeric (not null) | - // +-+--------+------+---------+--------+--------------------+ - // |0| Fish | 0 | 1 | {0} | 1 | - // |1| null | null | null | null | 0 | - // |2| Horse | 1 | 3 | {1,2} | 3 | - // +-+--------+------+---------+--------+--------------------+ + // +-+--------+------+---------+--------+--------------------+----------+ + // | | string | link | numeric | binary | numeric (not null) | linklist | + // +-+--------+------+---------+--------+--------------------+----------+ + // |0| Fish | 0 | 1 | {0} | 1 | [0] | + // |1| null | 2 | null | null | 0 | [2] | + // |2| Horse | null | 3 | {1,2} | 3 | null | + // +-+--------+------+---------+--------+--------------------+----------+ private void populateTestRealmForNullTests(Realm testRealm) { // 1 String String[] words = {"Fish", null, "Horse"}; @@ -565,8 +620,12 @@ private void populateTestRealmForNullTests(Realm testRealm) { nullTypesArray[i] = testRealm.copyToRealm(nullTypes); } nullTypesArray[0].setFieldObjectNull(nullTypesArray[0]); - nullTypesArray[1].setFieldObjectNull(null); - nullTypesArray[2].getFieldListNull().add(nullTypesArray[1]); + nullTypesArray[1].setFieldObjectNull(nullTypesArray[2]); + nullTypesArray[2].setFieldObjectNull(null); + + nullTypesArray[0].getFieldListNull().add(nullTypesArray[1]); + nullTypesArray[1].getFieldListNull().add(nullTypesArray[2]); + nullTypesArray[2].getFieldListNull().clear(); // just to be sure testRealm.commitTransaction(); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java index 38e53896d9..976e31c3ea 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java @@ -53,8 +53,8 @@ public abstract class QueryTests { ArrayList list = new ArrayList<>(Arrays.asList( RealmFieldType.STRING, RealmFieldType.BINARY, - RealmFieldType.LIST)); - // TODO: LINKING_OBJECTS should be supported + RealmFieldType.LIST, + RealmFieldType.LINKING_OBJECTS)); SUPPORTED_IS_EMPTY_TYPES = Collections.unmodifiableList(list); SUPPORTED_IS_NOT_EMPTY_TYPES = Collections.unmodifiableList(list); @@ -63,7 +63,6 @@ public abstract class QueryTests { list.remove(RealmFieldType.UNSUPPORTED_MIXED); list.remove(RealmFieldType.UNSUPPORTED_TABLE); list.remove(RealmFieldType.UNSUPPORTED_DATE); - list.remove(RealmFieldType.LINKING_OBJECTS); NOT_SUPPORTED_IS_EMPTY_TYPES = Collections.unmodifiableList(list); NOT_SUPPORTED_IS_NOT_EMPTY_TYPES = Collections.unmodifiableList(list); } @@ -92,15 +91,23 @@ protected final void createIsEmptyDataSet(Realm realm) { emptyValues.setFieldBinary(new byte[0]); emptyValues.setFieldObject(emptyValues); emptyValues.setFieldList(new RealmList()); - realm.copyToRealm(emptyValues); + AllJavaTypes emptyValuesManaged = realm.copyToRealm(emptyValues); AllJavaTypes nonEmpty = new AllJavaTypes(); nonEmpty.setFieldId(2); nonEmpty.setFieldString("Foo"); nonEmpty.setFieldBinary(new byte[] {1, 2, 3}); nonEmpty.setFieldObject(nonEmpty); - nonEmpty.setFieldList(new RealmList(emptyValues)); - realm.copyToRealmOrUpdate(nonEmpty); + nonEmpty.setFieldList(new RealmList(emptyValuesManaged)); + AllJavaTypes nonEmptyManaged = realm.copyToRealmOrUpdate(nonEmpty); + + AllJavaTypes emptyValues2 = new AllJavaTypes(); + emptyValues2.setFieldId(3); + emptyValues2.setFieldString(""); + emptyValues2.setFieldBinary(new byte[0]); + emptyValues2.setFieldObject(null); + emptyValues2.setFieldList(new RealmList(nonEmptyManaged)); + realm.copyToRealm(emptyValues2); realm.commitTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 0a8d50763c..e12978c71a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -18,21 +18,13 @@ import android.support.test.runner.AndroidJUnit4; -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; -import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -52,9 +44,7 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; -import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -2688,14 +2678,19 @@ public void isEmpty() { for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { switch (type) { case STRING: - assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_STRING).count()); + assertEquals(2, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_STRING).count()); break; case BINARY: - assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_BINARY).count()); + assertEquals(2, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_BINARY).count()); break; case LIST: assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LIST).count()); break; + case LINKING_OBJECTS: + // Row 2 does not have a backlink + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_LO_LIST).count()); + break; default: fail("Unknown type: " + type); } @@ -2714,7 +2709,18 @@ public void isEmpty_acrossLink() { assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY).count()); break; case LIST: - assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LIST).count()); + // Row 0: Backlink list to row 1, list to row 0; included + // Row 1: Backlink list to row 2, list to row 1; included + // Row 2: No backlink list; not included + assertEquals(2, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LIST).count()); + break; + case LINKING_OBJECTS: + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); + + // Row 0: Link to row 0, backlink to row 0; not included + // Row 1: Link to row 1m backlink to row 1; not included + // Row 2: Empty link; included + assertEquals(1, realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); break; default: fail("Unknown type: " + type); @@ -2781,6 +2787,10 @@ public void isNotEmpty() { case LIST: assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LIST).count()); break; + case LINKING_OBJECTS: + assertEquals(2, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_OBJECT).count()); + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_LO_LIST).count()); + break; default: fail("Unknown type: " + type); } @@ -2801,6 +2811,10 @@ public void isNotEmpty_acrossLink() { case LIST: assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LIST).count()); break; + case LINKING_OBJECTS: + assertEquals(1, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_LIST).count()); + assertEquals(2, realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_LO_OBJECT).count()); + break; default: fail("Unknown type: " + type); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 01158eb045..56746a003f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -66,17 +67,17 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_TableQuery_nativeValidateQuery( // If the corresponding entry in tablesArray is anything other than a nullptr, the link is a backlink. // In that case, the tablesArray element is the pointer to the backlink source table and the // indicesArray entry is the source column index in the source table. -// FIXME!!! This doesn't actually seem to be following backlinks. static TableRef getTableForLinkQuery(jlong nativeQueryPtr, JniLongArray& tablesArray, JniLongArray& indicesArray) { - TableRef table_ref = Q(nativeQueryPtr)->get_table(); + auto table_ref = reinterpret_cast(nativeQueryPtr)->get_table(); jsize link_element_count = indicesArray.len() - 1; - for (int i = 0; i < link_element_count; i++) { + for (int i = 0; i < link_element_count; ++i) { auto col_index = size_t(indicesArray[i]); - auto table_ptr = TBL(tablesArray[i]); + auto table_ptr = reinterpret_cast

            (tablesArray[i]); if (table_ptr == nullptr) { table_ref->link(col_index); - } else { + } + else { table_ref->backlink(*table_ptr, col_index); } } @@ -84,17 +85,22 @@ static TableRef getTableForLinkQuery(jlong nativeQueryPtr, JniLongArray& tablesA } // Return TableRef point to original table or the link table -static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& indicesArray) +static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& tablesArray, JniLongArray& indicesArray) { - TableRef table_ref = Q(nativeQueryPtr)->get_table(); + auto table_ref = reinterpret_cast(nativeQueryPtr)->get_table(); jsize link_element_count = indicesArray.len() - 1; - for (int i = 0; i < link_element_count; i++) { - table_ref = table_ref->get_link_target(size_t(indicesArray[i])); + for (int i = 0; i < link_element_count; ++i) { + auto table_ptr = reinterpret_cast
            (tablesArray[i]); + if (table_ptr == nullptr) { + table_ref = table_ref->get_link_target(static_cast(indicesArray[i])); + } + else { + table_ref = TableRef(table_ptr); + } } return table_ref; } -// FIXME!!! This is a hasty attempt to fix the nullable queries. // I am not at all sure that it is even the right idea, let alone correct code. --gbm static bool isNullable(JNIEnv* env, Table* src_table_ptr, TableRef table_ref, jlong column_idx) { @@ -844,7 +850,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JZ(J if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Bool)) { return; } - Q(nativeQueryPtr)->equal(S(index_arr[0]), value != 0 ? true : false); + Q(nativeQueryPtr)->equal(S(index_arr[0]), to_bool(value)); } else { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); @@ -868,12 +874,13 @@ static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA JniLongArray index_arr(env, columnIndexes); jsize arr_len = index_arr.len(); try { + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); if (value == NULL) { - if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, index_arr).get(), index_arr[arr_len - 1])) { + if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), index_arr[arr_len - 1])) { return; } } - bool is_case_sensitive = caseSensitive ? true : false; + bool is_case_sensitive = to_bool(caseSensitive); JStringAccessor value2(env, value); // throws if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_String)) { @@ -901,7 +908,6 @@ static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA } } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); switch (predicate) { case StringEqual: Q(nativeQueryPtr) @@ -997,10 +1003,11 @@ static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA JniLongArray index_arr(env, columnIndexes); jsize arr_len = index_arr.len(); try { + TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); JniByteArray bytes(env, value); BinaryData value2; if (value == NULL) { - if (!TBL_AND_COL_NULLABLE(env, getTableByArray(nativeQueryPtr, index_arr).get(), index_arr[arr_len - 1])) { + if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), index_arr[arr_len - 1])) { return; } value2 = BinaryData(); @@ -1027,7 +1034,6 @@ static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA } } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); switch (predicate) { case BinaryEqual: Q(nativeQueryPtr)->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) == value2); @@ -1490,21 +1496,21 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en jlongArray columnIndexes, jlongArray tablePointers) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); - Query* pQuery = Q(nativeQueryPtr); try { - TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); + auto pQuery = reinterpret_cast(nativeQueryPtr); + jlong column_idx = index_arr[arr_len - 1]; - TableRef table_ref = getTableByArray(nativeQueryPtr, index_arr); - if (!isNullable(env, TBL(table_arr[arr_len - 1]), table_ref, column_idx)) { + TableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, index_arr); + if (!isNullable(env, reinterpret_cast
            (table_arr[arr_len - 1]), table_ref, column_idx)) { return; } - // FIXME!!! Support a backlink as the last column in a field descriptor - int col_type = table_ref->get_column_type(S(column_idx)); + TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + int col_type = src_table_ref->get_column_type(S(column_idx)); if (arr_len == 1) { switch (col_type) { case type_Link: @@ -1530,7 +1536,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en } } else { - // FIXME!!! Support a backlink as an internal column in a field descriptor switch (col_type) { case type_Link: ThrowException(env, IllegalArgument, "isNull() by nested query for link field is not supported."); @@ -1621,15 +1626,16 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* jsize arr_len = index_arr.len(); Query* pQuery = Q(nativeQueryPtr); try { - TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); jlong column_idx = index_arr[arr_len - 1]; - TableRef table_ref = getTableByArray(nativeQueryPtr, index_arr); + TableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, index_arr); if (!isNullable(env, TBL(table_arr[arr_len - 1]), table_ref, column_idx)) { return; } - int col_type = table_ref->get_column_type(S(column_idx)); + TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + + int col_type = src_table_ref->get_column_type(S(column_idx)); if (arr_len == 1) { switch (col_type) { case type_Link: @@ -1700,25 +1706,30 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e JniLongArray table_arr(env, tablePointers); JniLongArray index_arr(env, columnIndexes); jsize arr_len = index_arr.len(); - Query* pQuery = Q(nativeQueryPtr); + Query* pQuery = reinterpret_cast(nativeQueryPtr); try { TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - jlong column_idx = index_arr[arr_len - 1]; - TableRef table_ref = getTableByArray(nativeQueryPtr, index_arr); + auto column_idx = static_cast(index_arr[arr_len - 1]); + + // Support a backlink as the last column in a field descriptor + Table* last = TBL(table_arr[arr_len-1]); + if (last != nullptr) { + pQuery->and_query(src_table_ref->column(*last, column_idx).count() == 0); + return; + } - // FIXME!!! Support a backlink as the last column in a field descriptor - int col_type = table_ref->get_column_type(S(column_idx)); + int col_type = src_table_ref->get_column_type(column_idx); if (arr_len == 1) { // Field queries switch (col_type) { case type_Binary: - pQuery->equal(S(column_idx), BinaryData("", 0)); + pQuery->equal(column_idx, BinaryData("", 0)); break; case type_LinkList: - pQuery->and_query(table_ref->column(S(column_idx)).count() == 0); + pQuery->and_query(src_table_ref->column(column_idx).count() == 0); break; case type_String: - pQuery->equal(S(column_idx), ""); + pQuery->equal(column_idx, ""); break; case type_Link: case type_Bool: @@ -1733,16 +1744,15 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e } else { // Linked queries - // FIXME!!! Support a backlink as an internal column in a field descriptor switch (col_type) { case type_Binary: - pQuery->and_query(src_table_ref->column(S(column_idx)) == BinaryData("", 0)); + pQuery->and_query(src_table_ref->column(column_idx) == BinaryData("", 0)); break; case type_LinkList: - pQuery->and_query(src_table_ref->column(S(column_idx)).count() == 0); + pQuery->and_query(src_table_ref->column(column_idx).count() == 0); break; case type_String: - pQuery->and_query(src_table_ref->column(S(column_idx)) == ""); + pQuery->and_query(src_table_ref->column(column_idx) == ""); break; case type_Link: case type_Bool: @@ -1760,6 +1770,76 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e CATCH_STD() } +JNIEXPORT void JNICALL +Java_io_realm_internal_TableQuery_nativeIsNotEmpty(JNIEnv *env, jobject, jlong nativeQueryPtr, + jlongArray columnIndexes, jlongArray tablePointers) { + JniLongArray table_arr(env, tablePointers); + JniLongArray index_arr(env, columnIndexes); + jsize arr_len = index_arr.len(); + Query* pQuery = reinterpret_cast(nativeQueryPtr); + try { + TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + auto column_idx = static_cast(index_arr[arr_len - 1]); + + // Support a backlink as the last column in a field descriptor + auto last = reinterpret_cast
            (table_arr[arr_len-1]); + if (last != nullptr) { + pQuery->and_query(src_table_ref->column(*last, column_idx).count() != 0); + return; + } + + int col_type = src_table_ref->get_column_type(column_idx); + if (arr_len == 1) { + // Field queries + switch (col_type) { + case type_Binary: + pQuery->not_equal(column_idx, BinaryData("", 0)); + break; + case type_LinkList: + pQuery->and_query(src_table_ref->column(column_idx).count() != 0); + break; + case type_String: + pQuery->not_equal(column_idx, ""); + break; + case type_Link: + case type_Bool: + case type_Int: + case type_Float: + case type_Double: + case type_Timestamp: + default: + ThrowException(env, IllegalArgument, "isNotEmpty() only works on String, byte[] and RealmList."); + return; + } + } + else { + // Linked queries + switch (col_type) { + case type_Binary: + pQuery->and_query(src_table_ref->column(column_idx) != BinaryData("", 0)); + break; + case type_LinkList: + pQuery->and_query(src_table_ref->column(column_idx).count() != 0); + break; + case type_String: + pQuery->and_query(src_table_ref->column(column_idx) != ""); + break; + case type_Link: + case type_Bool: + case type_Int: + case type_Float: + case type_Double: + case type_Timestamp: + default: + ThrowException(env, IllegalArgument, + "isNotEmpty() only works on String, byte[] and RealmList across links."); + return; + } + } + } + CATCH_STD() +} + static void finalize_table_query(jlong ptr) { TR_ENTER_PTR(ptr) diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java index c612bb6744..29ac82eb1c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java @@ -245,9 +245,9 @@ protected final long addColumnDetails(Table table, String columnName, RealmField */ @SuppressWarnings("unused") protected final void addBacklinkDetails(SharedRealm realm, String columnName, String sourceTableName, String sourceColumnName) { -// Table sourceTable = realm.getTable(Table.getTableNameForClass(sourceTableName)); -// long columnIndex = sourceTable.getColumnIndex(sourceColumnName); -// indicesMap.put(columnName, new ColumnDetails(columnIndex, RealmFieldType.LINKING_OBJECTS, sourceTableName)); + Table sourceTable = realm.getTable(Table.getTableNameForClass(sourceTableName)); + long columnIndex = sourceTable.getColumnIndex(sourceColumnName); + indicesMap.put(columnName, new ColumnDetails(columnIndex, RealmFieldType.LINKING_OBJECTS, sourceTableName)); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index f29688f891..e953656ce4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -396,7 +396,9 @@ public TableQuery isEmpty(long[] columnIndices, long[] tablePtrs) { } public TableQuery isNotEmpty(long[] columnIndices, long[] tablePtrs) { - return not().isEmpty(columnIndices, tablePtrs); + nativeIsNotEmpty(nativePtr, columnIndices, tablePtrs); + queryValidated = false; + return this; } // Searching methods. @@ -737,6 +739,8 @@ private void throwImmutable() { private native void nativeIsEmpty(long nativePtr, long[] columnIndices, long[] tablePtrs); + private native void nativeIsNotEmpty(long nativePtr, long[] columnIndices, long[] tablePtrs); + private native long nativeFind(long nativeQueryPtr, long fromTableRow); private native long nativeFindAll(long nativeQueryPtr, long start, long end, long limit); From b4b543e4390850130883c587106ca3c37744add1 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 1 Jun 2017 15:54:57 +0200 Subject: [PATCH 0730/2110] quick fix (#4745) --- .../src/main/java/io/realm/annotations/LinkingObjects.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java index 3ccd9c4772..eef7205372 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java +++ b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java @@ -94,8 +94,8 @@ *

            * Querying inverse relationship is like querying any {@code RealmResults}. This means that an inverse relationship * cannot be {@code null} but it can be empty (length is 0). It is possible to query fields in the source class. This is - * equivalent to link queries. Please read {@link https://realm.io/docs/java/latest/#link-queries} for more - * information. + * equivalent to link queries. Please read for more + * information. */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) From a4c2aba0b2e79c33e438ec7e2766f05475149ca1 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 2 Jun 2017 01:07:45 +0900 Subject: [PATCH 0731/2110] Add Realm.getDefaultConfiguration() (#4737) * add Realm.getDefaultConfiguration() (#4725). * monitorForDefaultConfiguration -> defaultConfigurationLock * PR feedbacks * revise javadoc comment of Real.getDefaultConfiguration() --- CHANGELOG.md | 1 + .../io/realm/RealmConfigurationTests.java | 37 +++++++++++++------ .../src/main/java/io/realm/Realm.java | 35 ++++++++++++++---- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e07758307..b4917e5d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Added support for querying inverse relationships (#2904). * Moved inverse relationships out of beta stage. +* Added `Realm.getDefaultConfiguration()` (#4725). ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 4064615145..988975088a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -59,6 +59,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -91,15 +92,8 @@ public void tearDown() throws Exception { } } - private void clearDefaultConfiguration() throws NoSuchFieldException, IllegalAccessException { - final Field field = Realm.class.getDeclaredField("defaultConfiguration"); - field.setAccessible(true); - field.set(null, null); - } - @Test - public void setDefaultConfiguration_nullThrows() throws NoSuchFieldException, IllegalAccessException { - clearDefaultConfiguration(); + public void setDefaultConfiguration_nullThrows() { try { Realm.setDefaultConfiguration(null); fail(); @@ -107,6 +101,26 @@ public void setDefaultConfiguration_nullThrows() throws NoSuchFieldException, Il } } + @Test + public void getDefaultConfiguration_returnsTheSameObjectThatSetDefaultConfigurationSet() { + final RealmConfiguration config = new RealmConfiguration.Builder().build(); + Realm.setDefaultConfiguration(config); + + assertSame(config, Realm.getDefaultConfiguration()); + } + + @Test + public void getDefaultConfiguration_returnsNullAfterRemoveDefaultConfiguration() { + final RealmConfiguration defaultConfiguration = Realm.getDefaultConfiguration(); + try { + Realm.removeDefaultConfiguration(); + + assertNull(Realm.getDefaultConfiguration()); + } finally { + Realm.setDefaultConfiguration(defaultConfiguration); + } + } + @Test public void getInstance_nullConfigThrows() { try { @@ -297,17 +311,16 @@ public void modules() { } @Test - public void setDefaultConfiguration() throws NoSuchFieldException, IllegalAccessException { - clearDefaultConfiguration(); + public void setDefaultConfiguration() { Realm.setDefaultConfiguration(defaultConfig); realm = Realm.getDefaultInstance(); - assertEquals(realm.getPath(), defaultConfig.getPath()); + assertEquals(defaultConfig, realm.getConfiguration()); } @Test public void getInstance() { realm = Realm.getInstance(defaultConfig); - assertEquals(realm.getPath(), defaultConfig.getPath()); + assertEquals(defaultConfig, realm.getConfiguration()); } @Test diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 35c1f89afc..ffd129701f 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -38,7 +38,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Scanner; @@ -134,6 +133,8 @@ public class Realm extends BaseRealm { public static final String DEFAULT_REALM_NAME = RealmConfiguration.DEFAULT_REALM_NAME; + private static final Object defaultConfigurationLock = new Object(); + // guarded by `defaultConfigurationLock` private static RealmConfiguration defaultConfiguration; /** @@ -195,7 +196,7 @@ public static synchronized void init(Context context) { } checkFilesDirAvailable(context); RealmCore.loadLibrary(context); - defaultConfiguration = new RealmConfiguration.Builder(context).build(); + setDefaultConfiguration(new RealmConfiguration.Builder(context).build()); ObjectServerFacade.getSyncFacadeIfPossible().init(context); BaseRealm.applicationContext = context.getApplicationContext(); SharedRealm.initialize(new File(context.getFilesDir(), ".realm.temp")); @@ -267,10 +268,15 @@ private static void checkFilesDirAvailable(Context context) { * was set and the thread opening the Realm was interrupted while the download was in progress. */ public static Realm getDefaultInstance() { - if (defaultConfiguration == null) { - throw new IllegalStateException("Call `Realm.init(Context)` before calling this method."); + RealmConfiguration configuration = getDefaultConfiguration(); + if (configuration == null) { + if (BaseRealm.applicationContext == null) { + throw new IllegalStateException("Call `Realm.init(Context)` before calling this method."); + } else { + throw new IllegalStateException("Set default configuration by using `Realm.setDefaultConfiguration(RealmConfiguration)`."); + } } - return RealmCache.createRealmOrGetFromCache(defaultConfiguration, Realm.class); + return RealmCache.createRealmOrGetFromCache(configuration, Realm.class); } /** @@ -325,7 +331,20 @@ public static void setDefaultConfiguration(RealmConfiguration configuration) { if (configuration == null) { throw new IllegalArgumentException("A non-null RealmConfiguration must be provided"); } - defaultConfiguration = configuration; + synchronized (defaultConfigurationLock) { + defaultConfiguration = configuration; + } + } + + /** + * Returns the default configuration for {@link #getDefaultInstance()}. + * + * @return default configuration object or {@code null} if no default configuration is specified. + */ + public static RealmConfiguration getDefaultConfiguration() { + synchronized (defaultConfigurationLock) { + return defaultConfiguration; + } } /** @@ -333,7 +352,9 @@ public static void setDefaultConfiguration(RealmConfiguration configuration) { * fail until a new default configuration has been set using {@link #setDefaultConfiguration(RealmConfiguration)}. */ public static void removeDefaultConfiguration() { - defaultConfiguration = null; + synchronized (defaultConfigurationLock) { + defaultConfiguration = null; + } } /** From bec89dc94fce8c198eab2e54d314f3278cf8856a Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 1 Jun 2017 10:17:23 -0700 Subject: [PATCH 0732/2110] update license to match changes in realm-cocoa see https://github.com/realm/realm-cocoa/commit/8714b3dc6325d39366335ef3a96c68b3edd82dc6 for original commit with new wording for export compliance. --- LICENSE | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/LICENSE b/LICENSE index 273b4d5f7b..57a0e0b24a 100644 --- a/LICENSE +++ b/LICENSE @@ -181,8 +181,6 @@ TABLE OF CONTENTS incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - END OF TERMS AND CONDITIONS - 2. ------------------------------------------------------------------------------- REALM COMPONENTS @@ -195,7 +193,7 @@ For the Realm Platform Extensions component Realm Platform Extensions License - Copyright (c) 2011-2016 Realm Inc All rights reserved + Copyright (c) 2011-2017 Realm Inc All rights reserved Redistribution and use in binary form, with or without modification, is permitted provided that the following conditions are met: @@ -232,16 +230,19 @@ EXPORT COMPLIANCE You understand that the Software may contain cryptographic functions that may be subject to export restrictions, and you represent and warrant that you are not -located in a country that is subject to United States export restriction or embargo, -including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region, and that you -are not on the Department of Commerce list of Denied Persons, Unverified Parties, -or affiliated with a Restricted Entity. +(i) located in a jurisdiction that is subject to United States economic +sanctions (“Prohibited Jurisdiction”), including Cuba, Iran, North Korea, +Sudan, Syria or the Crimea region, (ii) a person listed on any U.S. government +blacklist (to include the List of Specially Designated Nationals and Blocked +Persons or the Consolidated Sanctions List administered by the U.S. Department +of the Treasury’s Office of Foreign Assets Control, or the Denied Persons List +or Entity List administered by the U.S. Department of Commerce) +(“Sanctioned Person”), or (iii) controlled or 50% or more owned by a Sanctioned +Person. You agree to comply with all export, re-export and import restrictions and -regulations of the Department of Commerce or other agency or authority of the -United States or other applicable countries. You also agree not to transfer, or -authorize the transfer of, directly or indirectly, the Software to any prohibited -country, including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region, -or to any person or organization on or affiliated with the Department of -Commerce lists of Denied Persons, Unverified Parties or Restricted Entities, or -otherwise in violation of any such restrictions or regulations. +regulations of the U.S. Department of Commerce or other agency or authority of +the United States or other applicable countries. You also agree not to transfer, +or authorize the transfer of, directly or indirectly, of the Software to any +Prohibited Jurisdiction, or otherwise in violation of any such restrictions or +regulations. From 277eb0dfaa15fbf0d3efa3e74e1d64e214506443 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 2 Jun 2017 09:23:26 +0200 Subject: [PATCH 0733/2110] Add more capabilities to cleanup RunInLooperThread tests (#4740) --- .../RunTestInLooperThreadLifeCycleTest.java | 113 ++++++++++++++++++ .../java/io/realm/rule/RunInLooperThread.java | 61 ++++++++++ 2 files changed, 174 insertions(+) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java b/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java new file mode 100644 index 0000000000..cc70851ced --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + + +/** + * Meta test. Checking the lifecycle of @RunTestInLooperThreadTest does the right thing. + * + * Current order is: + * - @RunTestInLooperThread(before = ) + * - @Before() + * - @RunTestInLooperThread/@Test + * - @After : This is called when exiting the test method. Warning: Looper test is still running. + * - looperThread.runAfterTest(Runnable) : This is called when the LooperTest either succeed or fails. + */ + +@RunWith(AndroidJUnit4.class) +public class RunTestInLooperThreadLifeCycleTest { + + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + private static AtomicBoolean beforeCalled = new AtomicBoolean(false); + private static AtomicBoolean afterCalled = new AtomicBoolean(false); + private static AtomicBoolean testExited = new AtomicBoolean(false); + private static AtomicBoolean beforeRunnableCalled = new AtomicBoolean(false); + private static AtomicBoolean afterRunnableCalled = new AtomicBoolean(false); + private static AtomicBoolean closableClosed = new AtomicBoolean(false); + + @Before + public void before() { + assertTrue(beforeCalled.compareAndSet(false, true)); + assertTrue(beforeRunnableCalled.get()); + + looperThread.closeAfterTest(new Closeable() { + @Override + public void close() throws IOException { + assertTrue(testExited.get()); + assertFalse(afterRunnableCalled.get()); + assertTrue(closableClosed.compareAndSet(false, true)); + } + }); + looperThread.runAfterTest(new Runnable() { + @Override + public void run() { + assertTrue(testExited.get()); + assertTrue(afterRunnableCalled.compareAndSet(false, true)); + assertTrue(looperThread.isTestComplete()); + } + }); +; } + + @After + public void after() { + assertTrue(afterCalled.compareAndSet(false, true)); + assertTrue(testExited.get()); + assertFalse(looperThread.isTestComplete()); // Beware of this. Use `runAfterTest` for destroying resources used. + } + + @Test + @RunTestInLooperThread(before = PrepareLooperTest.class) + public void looperTest() { + looperThread.postRunnable(new Runnable() { + @Override + public void run() { + assertTrue(afterCalled.get()); + assertFalse(looperThread.isTestComplete()); + looperThread.testComplete(); + } + }); + assertTrue(testExited.compareAndSet(false, true)); + } + + public static class PrepareLooperTest implements RunInLooperThread.RunnableBefore { + @Override + public void run(RealmConfiguration realmConfig) { + assertTrue(beforeRunnableCalled.compareAndSet(false, true)); + assertFalse(beforeCalled.get()); + } + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index 33e699995e..f44a42e56a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -23,6 +23,8 @@ import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; +import java.io.Closeable; +import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; @@ -81,6 +83,14 @@ public class RunInLooperThread extends TestRealmConfigurationFactory { // Access guarded by 'lock' private List testRealms; + // List of closable resources that will be automatically closed when the test finishes. + // Access guarded by 'lock' + private List closableResources; + + // Runnable guaranteed to trigger after the test either succeeded or failed. + // Access guarded by 'lock' + private Runnable runAfterTestIsComplete; + /** * Get the configuration for the test realm. *

            @@ -129,6 +139,33 @@ public void keepStrongReference(Object obj) { } } + /** + * Add a closable resource which this test will guarantee to call {@link Closeable#close()} on + * when the tests is done. + * + * @param closeable {@link Closeable} to close. + */ + public void closeAfterTest(Closeable closeable) { + synchronized (lock) { + closableResources.add(closeable); + } + } + + /** + * Run this task after the unit test either failed or succeeded. + * This is a work-around for the the current @After being triggered right after the unit test method exits, + * but before the @RunTestInLooperThread has determined the test is done + * + * TODO: Consider replacing this pattern with `@AfterLooperTest` annotation. + * + * @param task task to run. Only one task can be provided + */ + public void runAfterTest(Runnable task) { + synchronized (lock) { + runAfterTestIsComplete = task; + } + } + /** * Add a Realm to be closed when test is complete. *

            @@ -227,6 +264,7 @@ protected void before() throws Throwable { RealmConfiguration config = createConfiguration(UUID.randomUUID().toString()); LinkedList refs = new LinkedList<>(); List realms = new LinkedList<>(); + LinkedList closeables = new LinkedList<>(); synchronized (lock) { realmConfiguration = config; @@ -234,6 +272,7 @@ protected void before() throws Throwable { backgroundHandler = null; keepStrongReference = refs; testRealms = realms; + closableResources = closeables; } } @@ -290,6 +329,24 @@ private void closeRealms() { } } + private void closeResources() throws IOException { + synchronized (lock) { + for (Closeable cr : closableResources) { + cr.close(); + } + } + } + + /** + * Checks if the current test is considered completed or not. + * It is completed if either {@link #testComplete()} was called or an uncaught exception was thrown. + */ + public boolean isTestComplete() { + synchronized (lock) { + return signalTestCompleted.getCount() == 0; + } + } + /** * If an implementation of this is supplied with the annotation, the {@link RunnableBefore#run(RealmConfiguration)} * will be executed before the looper thread starts. It is normally for populating the Realm before the test. @@ -427,6 +484,10 @@ public void run() { } finally { try { looperTearDown(); + closeResources(); + if (runAfterTestIsComplete != null) { + runAfterTestIsComplete.run(); + } } catch (Throwable t) { setAssertionError(t); setUnitTestFailed(); From 92ca75b9e1b87b2c60961981b187a6888c8fb923 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 2 Jun 2017 15:16:17 +0800 Subject: [PATCH 0734/2110] Only format log message if the level is enabled fix #4734 --- realm/realm-library/src/main/java/io/realm/log/RealmLog.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index e24211efca..80bde88777 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -269,6 +269,10 @@ public static void fatal(Throwable throwable, String message, Object... args) { // Formats the message, parses the stacktrace of given throwable and passes them to nativeLog. private static void log(int level, Throwable throwable, String message, Object... args) { + if (level < getLevel()) { + return; + } + StringBuilder stringBuilder = new StringBuilder(); if (args != null && args.length > 0) { message = String.format(message, args); From 7e04a9bde2024ef5eebada3d1ed8ddf3675d08a7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 2 Jun 2017 15:02:27 +0100 Subject: [PATCH 0735/2110] Enable encryption with Sync (#4746) --- CHANGELOG.md | 3 +- .../java/io/realm/util/SyncTestUtils.java | 23 ++ .../cpp/io_realm_internal_SharedRealm.cpp | 11 +- .../EncryptedSynchronizedRealmTests.java | 253 ++++++++++++++++++ .../objectserver/utils/StringOnlyModule.java | 8 + 5 files changed, 294 insertions(+), 4 deletions(-) create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/StringOnlyModule.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ae132b6e12..389cca5278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ ### Bug Fixes -* [ObjectServer] Fixed a crash when an authentication error happend (#4726). +* [ObjectServer] Fixed a crash when an authentication error happens (#4726). +* [ObjectServer] Enabled encryption with Sync (#4561). ### Internal diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index 20228694a1..8d0a12d01f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -20,10 +20,13 @@ import org.json.JSONException; import org.json.JSONObject; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.UUID; import io.realm.ErrorCode; import io.realm.ObjectServerError; +import io.realm.SyncManager; import io.realm.SyncUser; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.objectserver.ObjectServerUser; @@ -36,6 +39,16 @@ public class SyncTestUtils { public static final String DEFAULT_AUTH_URL = "http://objectserver.realm.io/auth"; public static final String DEFAULT_USER_IDENTIFIER = "JohnDoe"; + private final static Method SYNC_MANAGER_RESET_METHOD; + static { + try { + SYNC_MANAGER_RESET_METHOD = SyncManager.class.getDeclaredMethod("reset"); + SYNC_MANAGER_RESET_METHOD.setAccessible(true); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + public static SyncUser createRandomTestUser() { return createTestUser(UUID.randomUUID().toString(), UUID.randomUUID().toString(), @@ -116,4 +129,14 @@ public static AuthenticateResponse createRefreshResponse() { public static AuthenticateResponse createErrorResponse(ErrorCode code) { return AuthenticateResponse.from(new ObjectServerError(code, "dummy")); } + + public static void resetSyncMetadata() { + try { + SYNC_MANAGER_RESET_METHOD.invoke(null); + } catch (InvocationTargetException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 4738a264d2..5eb72588d5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -141,11 +141,16 @@ class JniConfigWrapper { ssl_trust_certificate_path = realm::util::Optional(JStringAccessor(env, sync_ssl_trust_certificate_path)); } - m_config.sync_config = std::make_shared(SyncConfig{ - user, realm_url, SyncSessionStopPolicy::Immediately, std::move(bind_handler), std::move(error_handler), - nullptr, util::none, sync_client_validate_ssl, ssl_trust_certificate_path}); + util::Optional> sync_encryption_key(util::none); + if (!m_config.encryption_key.empty()) { + sync_encryption_key = std::array(); + std::copy_n(m_config.encryption_key.begin(), 64, sync_encryption_key->begin()); + } + m_config.sync_config = std::make_shared(SyncConfig{ + user, realm_url, SyncSessionStopPolicy::Immediately, std::move(bind_handler), std::move(error_handler), + nullptr, sync_encryption_key, sync_client_validate_ssl, ssl_trust_certificate_path}); #else REALM_UNREACHABLE(); #endif diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java new file mode 100644 index 0000000000..63bcb72115 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -0,0 +1,253 @@ +package io.realm.objectserver; + +import android.os.SystemClock; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import io.realm.ObjectServerError; +import io.realm.Realm; +import io.realm.RealmResults; +import io.realm.SyncConfiguration; +import io.realm.SyncCredentials; +import io.realm.SyncManager; +import io.realm.SyncSession; +import io.realm.SyncUser; +import io.realm.TestHelper; +import io.realm.entities.StringOnly; +import io.realm.exceptions.RealmFileException; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.StringOnlyModule; +import io.realm.objectserver.utils.UserFactory; +import io.realm.rule.TestSyncConfigurationFactory; +import io.realm.util.SyncTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class EncryptedSynchronizedRealmTests extends BaseIntegrationTest { + @Rule + public Timeout globalTimeout = Timeout.seconds(10); + + @Rule + public final TestSyncConfigurationFactory configurationFactory = new TestSyncConfigurationFactory(); + + @Before + public void before() { + // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' + // causing the SyncUser to remain in memory. + // They're actually not persisted into disk. + // move this call to 'tearDown' to clean in-memory & on-disk users + // once https://github.com/realm/realm-object-store/issues/207 is resolved + SyncTestUtils.resetSyncMetadata(); + } + + // Make sure the encryption is local, i.e after deleting a synced Realm + // re-open it again with no (or different) key, should be possible. + @Test + public void setEncryptionKey_canReOpenRealmWithoutKey() { + + // STEP 1: open a synced Realm using a local encryption key + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + final byte[] randomKey = TestHelper.getRandomKey(); + + SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .modules(new StringOnlyModule()) + .waitForInitialRemoteData() + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail(error.getErrorMessage()); + } + }) + .encryptionKey(randomKey) + .build(); + + Realm realm = Realm.getInstance(configWithEncryption); + assertTrue(realm.isEmpty()); + + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Hi Alice"); + realm.commitTransaction(); + + // STEP 2: make sure the changes gets to the server + SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. + realm.close(); + user.logout(); + Realm.deleteRealm(configWithEncryption); + + // STEP 3: try to open again the Realm without the encryption key should not fail + user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + SyncConfiguration configWithoutEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .modules(new StringOnlyModule()) + .waitForInitialRemoteData() + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail(error.getErrorMessage()); + } + }) + .build(); + + realm = Realm.getInstance(configWithoutEncryption); + RealmResults all = realm.where(StringOnly.class).findAll(); + assertEquals(1, all.size()); + assertEquals("Hi Alice", all.get(0).getChars()); + + realm.close(); + user.logout(); + } + + // If an encrypted synced Realm is re-opened with the wrong key, throw an exception. + // TODO: enable again once https://github.com/realm/realm-java/pull/4707 is merged + @Ignore("This test crash the Sync client thread") + @Test + public void setEncryptionKey_shouldCrashIfKeyNotProvided() { + // STEP 1: open a synced Realm using a local encryption key + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + final byte[] randomKey = TestHelper.getRandomKey(); + + SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .modules(new StringOnlyModule()) + .waitForInitialRemoteData() + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail(error.getErrorMessage()); + } + }) + .encryptionKey(randomKey) + .build(); + + Realm realm = Realm.getInstance(configWithEncryption); + assertTrue(realm.isEmpty()); + + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Hi Alice"); + realm.commitTransaction(); + + // STEP 2: make sure the changes gets to the server + SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. + realm.close(); // Realm is not deleted, just closed + user.logout(); + + // STEP 3: try to open again the Realm without the encryption key should fail + user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + SyncConfiguration configWithoutEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .modules(new StringOnlyModule()) + .waitForInitialRemoteData() + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail(error.getErrorMessage()); + } + }) + .build(); + + try { + realm = Realm.getInstance(configWithoutEncryption); + fail("It should not be possible to open the Realm without the encryption key set previously."); + } catch (RealmFileException ignored) { + } + } + + // If client B encrypts its synced Realm, client A should be able to access that Realm with a different encryption key. + @Test + public void setEncryptionKey_differentClientsWithDifferentKeys() throws InterruptedException { + // STEP 1: prepare a synced Realm for client A + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + final byte[] randomKey = TestHelper.getRandomKey(); + + SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .modules(new StringOnlyModule()) + .waitForInitialRemoteData() + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail(error.getErrorMessage()); + } + }) + .encryptionKey(randomKey) + .build(); + + Realm realm = Realm.getInstance(configWithEncryption); + assertTrue(realm.isEmpty()); + + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Hi Alice"); + realm.commitTransaction(); + + // STEP 2: make sure the changes gets to the server + SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. + realm.close(); + + // STEP 3: prepare a synced Realm for client B (admin user) + SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); + SyncCredentials credentials = SyncCredentials.accessToken(admin.getAccessToken().value(), "custom-admin-user"); + SyncUser adminUser = SyncUser.login(credentials, Constants.AUTH_URL); + + final byte[] adminRandomKey = TestHelper.getRandomKey(); + + SyncConfiguration adminConfigWithEncryption = configurationFactory.createSyncConfigurationBuilder(adminUser, configWithEncryption.getServerUrl().toString()) + .modules(new StringOnlyModule()) + .waitForInitialRemoteData() + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + fail(error.getErrorMessage()); + } + }) + .encryptionKey(adminRandomKey) + .build(); + + Realm adminRealm = Realm.getInstance(adminConfigWithEncryption); + RealmResults all = adminRealm.where(StringOnly.class).findAll(); + assertEquals(1, all.size()); + assertEquals("Hi Alice", all.get(0).getChars()); + + adminRealm.beginTransaction(); + adminRealm.createObject(StringOnly.class).setChars("Hi Bob"); + adminRealm.commitTransaction(); + + SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); + adminRealm.close(); + + // STEP 4: client A can see changes from client B (although they're using different encryption keys) + realm = Realm.getInstance(configWithEncryption); + SyncManager.getSession(configWithEncryption).downloadAllServerChanges();// force download latest commits from ROS + realm.refresh();//FIXME not calling refresh will still point to the previous version of the Realm without the latest admin commit "Hi Bob" + assertEquals(2, realm.where(StringOnly.class).count()); + + adminRealm = Realm.getInstance(adminConfigWithEncryption); + + RealmResults allSorted = realm.where(StringOnly.class).findAllSorted(StringOnly.FIELD_CHARS); + RealmResults allSortedAdmin = adminRealm.where(StringOnly.class).findAllSorted(StringOnly.FIELD_CHARS); + assertEquals("Hi Alice", allSorted.get(0).getChars()); + assertEquals("Hi Bob", allSorted.get(1).getChars()); + + assertEquals("Hi Alice", allSortedAdmin.get(0).getChars()); + assertEquals("Hi Bob", allSortedAdmin.get(1).getChars()); + + adminUser.logout(); + user.logout(); + + realm.close(); + adminRealm.close(); + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/StringOnlyModule.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/StringOnlyModule.java new file mode 100644 index 0000000000..e935a0b1b9 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/StringOnlyModule.java @@ -0,0 +1,8 @@ +package io.realm.objectserver.utils; + +import io.realm.annotations.RealmModule; +import io.realm.entities.StringOnly; + +@RealmModule(classes = { StringOnly.class}) +public class StringOnlyModule { +} From d1775497388bf2dfb1132d4326554938c3c6e461 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 2 Jun 2017 19:27:08 +0800 Subject: [PATCH 0736/2110] Fix test case with missing linking object schema Those tests pass with current schema implementation since currently it will create extra object schemas which are not in the module. This behaviour is not right and will fail those tests in the future schema refactor. So always define all the needed object schemas in the module. --- .../java/io/realm/BulkInsertTests.java | 4 ++-- .../io/realm/RealmConfigurationTests.java | 20 ++++++++++--------- .../java/io/realm/RealmMigrationTests.java | 18 ++++++++--------- .../java/io/realm/RealmSchemaTests.java | 6 +++++- .../java/io/realm/entities/AnimalModule.java | 2 +- .../java/io/realm/entities/HumanModule.java | 2 +- 6 files changed, 29 insertions(+), 23 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java index f79aacad6f..e8c659fdaf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java @@ -505,7 +505,7 @@ public void execute(Realm realm) { public void insert_emptyListWithFilterableMediator() { //noinspection unchecked final RealmConfiguration config = configFactory.createConfigurationBuilder() - .schema(CatOwner.class, Cat.class) + .schema(CatOwner.class, Cat.class, Owner.class, DogPrimaryKey.class, Dog.class) .name("filterable.realm") .build(); Realm.deleteRealm(config); @@ -601,7 +601,7 @@ public void execute(Realm realm) { public void insertOrUpdate_emptyListWithFilterableMediator() { //noinspection unchecked final RealmConfiguration config = configFactory.createConfigurationBuilder() - .schema(CatOwner.class, Cat.class) + .schema(CatOwner.class, Cat.class, Owner.class, DogPrimaryKey.class, Dog.class) .name("filterable.realm") .build(); Realm.deleteRealm(config); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 4064615145..903be6ba5d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -43,6 +43,8 @@ import io.realm.entities.Dog; import io.realm.entities.HumanModule; import io.realm.entities.Owner; +import io.realm.entities.StringAndInt; +import io.realm.entities.StringOnly; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; @@ -229,7 +231,7 @@ public void constructBuilder_versionEqualWhenSchemaChangesThrows() { RealmConfiguration config = new RealmConfiguration.Builder(context) .directory(configFactory.getRoot()) .schemaVersion(42) - .schema(Dog.class) + .schema(StringOnly.class) .build(); Realm.getInstance(config).close(); @@ -238,7 +240,7 @@ public void constructBuilder_versionEqualWhenSchemaChangesThrows() { config = new RealmConfiguration.Builder(context) .directory(configFactory.getRoot()) .schemaVersion(42) - .schema(AllTypesPrimaryKey.class) + .schema(StringAndInt.class) .build(); realm = Realm.getInstance(config); fail("A migration should be required"); @@ -337,26 +339,26 @@ public void deleteRealmIfMigrationNeeded() { // Populates v0 of a Realm with an object. RealmConfiguration config = new RealmConfiguration.Builder(context) .directory(configFactory.getRoot()) - .schema(Dog.class) + .schema(StringOnly.class) .schemaVersion(0) .build(); Realm.deleteRealm(config); realm = Realm.getInstance(config); realm.beginTransaction(); - realm.copyToRealm(new Dog("Foo")); + realm.copyToRealm(new StringOnly()); realm.commitTransaction(); - assertEquals(1, realm.where(Dog.class).count()); + assertEquals(1, realm.where(StringOnly.class).count()); realm.close(); // Changes schema and verifies that Realm has been cleared. config = new RealmConfiguration.Builder(context) .directory(configFactory.getRoot()) - .schema(Owner.class, Dog.class) + .schema(StringOnly.class, StringAndInt.class) .schemaVersion(1) .deleteRealmIfMigrationNeeded() .build(); realm = Realm.getInstance(config); - assertEquals(0, realm.where(Dog.class).count()); + assertEquals(0, realm.where(StringOnly.class).count()); } @Test @@ -526,11 +528,11 @@ public void encryptionKey_differentEncryptionKeysThrows() { public void schema_differentSchemasThrows() { RealmConfiguration config1 = new RealmConfiguration.Builder(context) .directory(configFactory.getRoot()) - .schema(AllTypes.class) + .schema(StringOnly.class) .build(); RealmConfiguration config2 = new RealmConfiguration.Builder(context) .directory(configFactory.getRoot()) - .schema(CyclicType.class).build(); + .schema(StringAndInt.class).build(); Realm realm1 = Realm.getInstance(config1); try { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 1e04dca3e3..22dca370c7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -121,7 +121,7 @@ public void localColumnIndices() throws IOException { // V1 config RealmConfiguration v1Config = configFactory.createConfigurationBuilder() .name(MIGRATED_REALM) - .schema(AllTypes.class) + .schema(StringOnly.class) .schemaVersion(1) .build(); Realm oldRealm = Realm.getInstance(v1Config); @@ -140,7 +140,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { RealmConfiguration v2Config = configFactory.createConfigurationBuilder() .name(MIGRATED_REALM) - .schema(AllTypes.class, FieldOrder.class) + .schema(StringOnly.class, FieldOrder.class) .schemaVersion(2) .migration(migration) .build(); @@ -151,7 +151,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { RealmConfiguration newConfig = configFactory.createConfigurationBuilder() .name(NEW_REALM) .schemaVersion(2) - .schema(AllTypes.class, FieldOrder.class) + .schema(StringOnly.class, FieldOrder.class) .build(); Realm newRealm = Realm.getInstance(newConfig); newRealm.close(); @@ -166,7 +166,7 @@ public void notSettingIndexThrows() { // Creates v0 of the Realm. RealmConfiguration originalConfig = configFactory.createConfigurationBuilder() - .schema(AllTypes.class) + .schema(StringOnly.class) .build(); Realm.getInstance(originalConfig).close(); @@ -184,7 +184,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .schemaVersion(1) - .schema(AllTypes.class, AnnotationTypes.class) + .schema(StringOnly.class, AnnotationTypes.class) .migration(migration) .build(); try { @@ -718,7 +718,7 @@ public void apply(DynamicRealmObject obj) { public void settingPrimaryKeyWithObjectSchema() { // Creates v0 of the Realm. RealmConfiguration originalConfig = configFactory.createConfigurationBuilder() - .schema(AllTypes.class) + .schema(StringOnly.class) .build(); Realm.getInstance(originalConfig).close(); @@ -738,7 +738,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { // Creates v1 of the Realm. RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .schemaVersion(1) - .schema(AllTypes.class, AnnotationTypes.class) + .schema(StringOnly.class, AnnotationTypes.class) .migration(migration) .build(); @@ -789,7 +789,7 @@ public void setAnnotations() { // Creates v0 of the Realm. RealmConfiguration originalConfig = configFactory.createConfigurationBuilder() - .schema(AllTypes.class) + .schema(StringOnly.class) .build(); Realm.getInstance(originalConfig).close(); @@ -806,7 +806,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .schemaVersion(1) - .schema(AllTypes.class, AnnotationTypes.class) + .schema(StringOnly.class, AnnotationTypes.class) .migration(migration) .build(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index beb63f1cef..2c3bd625c8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -31,6 +31,9 @@ import java.util.Set; import io.realm.entities.AllJavaTypes; +import io.realm.entities.Cat; +import io.realm.entities.Dog; +import io.realm.entities.DogPrimaryKey; import io.realm.entities.Owner; import io.realm.entities.PrimaryKeyAsString; import io.realm.rule.TestRealmConfigurationFactory; @@ -56,7 +59,8 @@ public class RealmSchemaTests { @Before public void setUp() { RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() - .schema(AllJavaTypes.class, Owner.class, PrimaryKeyAsString.class) + .schema(AllJavaTypes.class, Owner.class, PrimaryKeyAsString.class, Cat.class, Dog.class, + DogPrimaryKey.class) .build(); Realm.getInstance(realmConfig).close(); // create Schema realm = DynamicRealm.getInstance(realmConfig); diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AnimalModule.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AnimalModule.java index c26f7fd282..54a74eca05 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AnimalModule.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AnimalModule.java @@ -18,6 +18,6 @@ import io.realm.annotations.RealmModule; -@RealmModule(classes = {Dog.class, Cat.class}) +@RealmModule(classes = {Dog.class, Cat.class, DogPrimaryKey.class}) public class AnimalModule { } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/HumanModule.java b/realm/realm-library/src/androidTest/java/io/realm/entities/HumanModule.java index 6810f4288a..12ee54ffc8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/HumanModule.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/HumanModule.java @@ -18,6 +18,6 @@ import io.realm.annotations.RealmModule; -@RealmModule(classes = {CatOwner.class}) +@RealmModule(classes = {CatOwner.class, Owner.class}) public class HumanModule { } From 73a2b56fbdb7c1669dbaae83da8c5fa0e9511e58 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 7 Jun 2017 11:31:00 +0800 Subject: [PATCH 0737/2110] Get more integration test to work (#4066) - Add a new TestRule RunWithRemoteService to allow a test case create a service running in a remote process. This is very much like what we have for the RealmInterProcessTests but with more flexibility, eg.: Separate service for separate test case. - Enabled sync interaction tests: notify by simple commit, notify by lots of commits. - Deprecate RemoteProcessService. - Since we have troubles to peacefully reseting sync server and client, just use a different user for every test. This is achieved by storing a user name inside a UserFactoryStore Realm file and retrieve it from different processes. --- .../src/androidTest/AndroidManifest.xml | 4 +- .../realm/rule/RunTestWithRemoteService.java | 34 +++ .../io/realm/rule/RunWithRemoteService.java | 184 +++++++++++ .../realm/services/RemoteProcessService.java | 1 + .../io/realm/services/RemoteTestService.java | 183 +++++++++++ .../java/io/realm/objectserver/AuthTests.java | 3 + .../objectserver/ManagementRealmTests.java | 5 +- .../objectserver/ProcessCommitTests.java | 286 ++++++++++-------- .../realm/objectserver/model/TestObject.java | 3 + .../objectserver/service/SendOneCommit.java | 61 ---- .../realm/objectserver/service/SendsALot.java | 64 ---- .../realm/objectserver/utils/Constants.java | 2 +- .../realm/objectserver/utils/HttpUtils.java | 2 - .../utils/RemoteIntegrationTestService.java | 28 ++ .../realm/objectserver/utils/UserFactory.java | 73 ++++- .../objectserver/utils/UserFactoryStore.java | 32 ++ 16 files changed, 710 insertions(+), 255 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/services/RemoteTestService.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/RemoteIntegrationTestService.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactoryStore.java diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index d92707182b..bc95bd28c9 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -26,13 +26,13 @@ Figure out why. For now place services here --> diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java new file mode 100644 index 0000000000..8abdea9c40 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.rule; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import io.realm.services.RemoteTestService; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * This should be used along with {@link RunWithRemoteService}. See comments there for usage. + */ +@Target(METHOD) +@Retention(RUNTIME) +public @interface RunTestWithRemoteService { + Class value(); +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java new file mode 100644 index 0000000000..d2b84bba3e --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java @@ -0,0 +1,184 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.rule; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import io.realm.TestHelper; +import io.realm.services.RemoteTestService; + +import static android.support.test.InstrumentationRegistry.getContext; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +/** + * This is a helper {@link TestRule} to do test which needs interaction with a remote process. + * To use this: + * 1. Define a subclass of {@link RemoteTestService} and create steps as static member of it. Those steps should be + * named as "stepA_doXXX", "stepB_doYYY", etc. to indicate the order of them. + * 2. Add a base message id in {@link RemoteTestService}. + * 3. Add the service into the AndroidManifest.xml. And the android:process property must be ":remote". + * 4. Annotate your test case by {@link RunTestWithRemoteService} with your remote service class. + * 5. You also need a looper in your test thread. Normally you can just use {@link RunTestInLooperThread}. + * 6. When your looper thread starts, register the service messenger by calling + * {@link RunWithRemoteService#createHandler(Looper)}. + * 7. Trigger your first step in the remote service process by calling + * {@link RunWithRemoteService#triggerServiceStep(RemoteTestService.Step)}. + * 8. Name steps in the foreground process with step1, step2 ... stepN. + * Name steps in the remote process with stepA, stepB ... stepZ. + * + * See the existing test cases for examples. + */ +public class RunWithRemoteService implements TestRule { + + private class InterprocessHandler extends Handler { + + private InterprocessHandler(Looper looper) { + super(looper); + localMessenger = new Messenger(this); + } + + @Override + public void handleMessage(Message msg) { + Bundle bundle = msg.getData(); + String error = bundle.getString(RemoteTestService.BUNDLE_KEY_ERROR); + if (error != null) { + // Assert and show error from remote process + fail(error); + } + } + } + + private static final String REMOTE_PROCESS_POSTFIX = ":remote"; + + private Messenger remoteMessenger; + private Messenger localMessenger; + private CountDownLatch serviceStartLatch; + + private final ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + remoteMessenger = new Messenger(iBinder); + serviceStartLatch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + if (serviceStartLatch != null && serviceStartLatch.getCount() > 1) { + serviceStartLatch.countDown(); + } + serviceStartLatch = null; + remoteMessenger = null; + } + }; + + private void before(Class serviceClass) throws Throwable { + // Start the testing remote process. + serviceStartLatch = new CountDownLatch(1); + Intent intent = new Intent(getContext(), serviceClass); + getContext().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + TestHelper.awaitOrFail(serviceStartLatch); + } + + public void after() { + getContext().unbindService(serviceConnection); + + // Kill the remote process. + ActivityManager.RunningAppProcessInfo info = getRemoteProcessInfo(); + if (info != null) { + android.os.Process.killProcess(info.pid); + } + int counter = 10; + while (getRemoteProcessInfo() != null) { + if (counter == 0) { + fail("The remote process is still alive."); + } + try { + Thread.sleep(300); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + counter--; + } + } + + @Override + public Statement apply(final Statement base, Description description) { + final RunTestWithRemoteService annotation = description.getAnnotation(RunTestWithRemoteService.class); + if (annotation == null) { + return base; + } + return new Statement() { + @Override + public void evaluate() throws Throwable { + before(annotation.value()); + try { + base.evaluate(); + } finally { + after(); + } + } + }; + } + + public void createHandler(Looper looper) { + new InterprocessHandler(looper); + } + + // Call this to trigger the next step of remote process + public void triggerServiceStep(RemoteTestService.Step step) { + Message msg = Message.obtain(null, step.message); + msg.replyTo = localMessenger; + try { + remoteMessenger.send(msg); + } catch (RemoteException e) { + fail(e.getMessage()); + } + // TODO: Find a way to block caller thread until the service process finishes current step. + } + + // Get the remote process info if it is alive. + private ActivityManager.RunningAppProcessInfo getRemoteProcessInfo() { + ActivityManager manager = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); + List processInfoList = manager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo info : processInfoList) { + if (info.processName.equals(getContext().getPackageName() + REMOTE_PROCESS_POSTFIX)) { + return info; + } + } + + return null; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java index 78c7c18575..4062ee8e38 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java +++ b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java @@ -34,6 +34,7 @@ /** * Helper service for multi-processes support testing. + * @deprecated use {@link RemoteTestService} instead. */ public class RemoteProcessService extends Service { diff --git a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteTestService.java b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteTestService.java new file mode 100644 index 0000000000..46eee6e089 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/services/RemoteTestService.java @@ -0,0 +1,183 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.services; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.os.StrictMode; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import io.realm.Realm; +import io.realm.internal.Util; +import io.realm.log.RealmLog; + +/** + * Helper class for multi-processes support testing. + * @see io.realm.rule.RunWithRemoteService + */ +public abstract class RemoteTestService extends Service { + // There is no easy way to dynamically ensure step IDs have same value for different processes. So, use the stupid + // way. + private static int BASE_MSG_ID = 0; + protected static int BASE_SIMPLE_COMMIT = BASE_MSG_ID; + protected static int BASE_A_LOT_COMMITS = BASE_SIMPLE_COMMIT + 100; + + public static abstract class Step { + public final int message; + + protected Step(int base, int id) { + this.message = base + id; + stepMap.put(this.message, this); + } + + protected abstract void run(); + + protected RemoteTestService getService() { + return RemoteTestService.thiz; + } + + // Pass a null to tell main process that everything is OK. + // Otherwise, pass an error String which will be used by assertion in main process. + private void response(String error) { + try { + Message msg = Message.obtain(null, message); + if (error != null) { + Bundle bundle = new Bundle(); + bundle.putString(BUNDLE_KEY_ERROR, error); + msg.setData(bundle); + } + thiz.client.send(msg); + } catch (RemoteException e) { + RealmLog.error(e); + } + } + } + + public static final String BUNDLE_KEY_ERROR = "error"; + @SuppressLint("UseSparseArrays") + private static Map stepMap = new HashMap(); + public static RemoteTestService thiz; + private final Messenger messenger = new Messenger(new IncomingHandler()); + private Messenger client; + private File rootFolder; + private Realm realm; + + public RemoteTestService() { + if (thiz != null) { + throw new RuntimeException("Only one instance is allowed!"); + } + thiz = this; + } + + @Override + public void onCreate() { + super.onCreate(); + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(policy); + try { + rootFolder = File.createTempFile(this.getClass().getSimpleName(), ""); + } catch (IOException e) { + RealmLog.error(e); + } + //noinspection ResultOfMethodCallIgnored + rootFolder.delete(); + //noinspection ResultOfMethodCallIgnored + rootFolder.mkdir(); + + Realm.init(getApplicationContext()); + } + + public File getRoot() { + return rootFolder; + } + + @Override + public IBinder onBind(Intent intent) { + return messenger.getBinder(); + } + + @Override + public boolean onUnbind(Intent intent) { + stopSelf(); + recursiveDelete(rootFolder); + return super.onUnbind(intent); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return START_NOT_STICKY; + } + + private static class IncomingHandler extends Handler { + @Override + public void handleMessage(Message msg) { + thiz.client = msg.replyTo; + if (thiz.client == null) { + throw new RuntimeException("Message with an empty client."); + } + Step step = stepMap.get(msg.what); + Throwable throwable = null; + if (step != null) { + try { + step.run(); + } catch (Throwable t) { + throwable = t; + } finally { + if (throwable != null) { + step.response(throwable.getMessage() + "\n" + Util.getStackTrace(throwable)); + } else { + step.response(null); + } + } + } else { + throw new RuntimeException("Cannot find corresponding step to message " + msg.what + "."); + } + } + } + + private void recursiveDelete(File file) { + File[] files = file.listFiles(); + if (files != null) { + for (File each : files) { + recursiveDelete(each); + } + } + //noinspection ResultOfMethodCallIgnored + file.delete(); + } + + public Realm getRealm() { + return realm; + } + + public void setRealm(Realm realm) { + this.realm = realm; + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 1cf777cea0..a315e08331 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -4,6 +4,7 @@ import android.os.Looper; import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -94,8 +95,10 @@ public void onError(ObjectServerError error) { }); } + // FIXME: https://github.com/realm/realm-java/issues/4711 @Test @RunTestInLooperThread + @Ignore("This fails expectSimpleCommit for some reasons, needs to be FIXED ASAP.") public void login_withAccessToken() { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); SyncCredentials credentials = SyncCredentials.accessToken(adminUser.getAccessToken().value(), "custom-admin-user", adminUser.isAdmin()); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index 0ad9688104..ecb219cdae 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -41,6 +41,7 @@ import io.realm.permissions.PermissionOfferResponse; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.TestSyncConfigurationFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -55,8 +56,8 @@ public class ManagementRealmTests extends BaseIntegrationTest { @Test @RunTestInLooperThread public void create_acceptOffer() { - SyncUser user1 = UserFactory.createUser(Constants.AUTH_URL, "user1"); - final SyncUser user2 = UserFactory.createUser(Constants.AUTH_URL, "user2"); + SyncUser user1 = UserFactory.createUniqueUser(Constants.AUTH_URL); + final SyncUser user2 = UserFactory.createUniqueUser(Constants.AUTH_URL); // 1. User1 creates Realm that user2 does not have access final String user1RealmUrl = "realm://127.0.0.1:9080/" + user1.getIdentity() + "/permission-offer-test"; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index b1469a6a1f..3f63754def 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -16,159 +16,207 @@ package io.realm.objectserver; -import android.content.Context; -import android.content.Intent; import android.os.Looper; -import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; -import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmResults; import io.realm.SyncConfiguration; -import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.objectserver.model.ProcessInfo; import io.realm.objectserver.model.TestObject; -import io.realm.objectserver.service.SendOneCommit; -import io.realm.objectserver.service.SendsALot; import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.RemoteIntegrationTestService; import io.realm.objectserver.utils.UserFactory; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.RunTestWithRemoteService; +import io.realm.rule.RunWithRemoteService; +import io.realm.rule.TestSyncConfigurationFactory; +import io.realm.services.RemoteTestService; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) public class ProcessCommitTests extends BaseIntegrationTest { + @Rule + public RunInLooperThread looperThread = new RunInLooperThread(); + @Rule + public RunWithRemoteService remoteService = new RunWithRemoteService(); + @Rule + public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @Before + public void before() throws Exception { + UserFactory.resetInstance(); + } + + public static class SimpleCommitRemoteService extends RemoteIntegrationTestService { + private static SyncUser user; + public static final Step stepA_openRealmAndCreateOneObject = new Step(RemoteTestService.BASE_SIMPLE_COMMIT, 1) { + + @Override + protected void run() { + user = UserFactory.getInstance().loginWithDefaultUser(Constants.AUTH_URL); + String realmUrl = Constants.SYNC_SERVER_URL; + + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + .directory(getService().getRoot()) + .build(); + getService().setRealm(Realm.getInstance(syncConfig)); + Realm realm = getService().getRealm(); + + realm.beginTransaction(); + ProcessInfo processInfo = realm.createObject(ProcessInfo.class); + processInfo.setName("Background_Process1"); + processInfo.setPid(android.os.Process.myPid()); + processInfo.setThreadId(Thread.currentThread().getId()); + realm.commitTransaction(); + // FIXME: If we close the Realm here, the data won't be able to synced to the main process. Is it a bug + // in sync client which stops too early? + // Realm is currently configured with stop_immediately. This means the sync session is closed as soon as + // the last realm instance is closed. Not doing this would make the Realm lifecycle really + // unpredictable. We should have an easy way to wait for all changes to be uploaded though. + // Perhaps SyncSession.uploadAllLocalChanges() or something similar to + // SyncSesson.downloadAllServerChanges() + } + }; + + public static final Step stepB_closeRealmAndLogOut = new Step(RemoteTestService.BASE_SIMPLE_COMMIT, 2) { + @Override + protected void run() { + getService().getRealm().close(); + user.logout(); + } + }; + } + + // 1. Open a sync Realm and listen to changes. + // A. Open the same sync Realm and add one object. + // 2. Get the notification, check if the change in A is received. @Test - @Ignore("Failure might be caused by two processes each creating a Sync Client: needs investigation") - public void expectServerCommit() throws Throwable { - final Throwable[] exception = new Throwable[1]; - final CountDownLatch testFinished = new CountDownLatch(1); - ExecutorService service = Executors.newSingleThreadExecutor(); - //noinspection unused - final Future future = service.submit(new Runnable() { + @RunTestWithRemoteService(SimpleCommitRemoteService.class) + @RunTestInLooperThread + public void expectSimpleCommit() { + remoteService.createHandler(Looper.myLooper()); + + final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); + String realmUrl = Constants.SYNC_SERVER_URL; + final SyncConfiguration syncConfig = configFactory.createSyncConfigurationBuilder(user, realmUrl).build(); + final Realm realm = Realm.getInstance(syncConfig); + final RealmResults all = realm.where(ProcessInfo.class).findAll(); + looperThread.keepStrongReference(all); + all.addChangeListener(new RealmChangeListener>() { @Override - public void run() { - try { - Looper.prepare(); - Context targetContext = InstrumentationRegistry.getTargetContext(); - - SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL); - String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) - .name(SendOneCommit.class.getSimpleName()) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail("Sync failure: " + error); - } - }) - .build(); - Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests - final Realm realm = Realm.getInstance(syncConfig); - Intent intent = new Intent(targetContext, SendOneCommit.class); - targetContext.startService(intent); - final RealmResults all = realm.where(ProcessInfo.class).findAll(); - all.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults element) { - assertEquals(1, all.size()); - assertEquals("Background_Process1", all.get(0).getName()); - testFinished.countDown(); - } - }); - - Looper.loop(); - - } catch (Throwable e) { - exception[0] = e; - testFinished.countDown(); - } + public void onChange(RealmResults element) { + assertEquals(1, all.size()); + assertEquals("Background_Process1", all.get(0).getName()); + realm.close(); + user.logout(); + + remoteService.triggerServiceStep(SimpleCommitRemoteService.stepB_closeRealmAndLogOut); + + looperThread.testComplete(); } }); - boolean testTimedOut = testFinished.await(300, TimeUnit.SECONDS); - if (exception[0] != null) { - throw exception[0]; - } else if (!testTimedOut) { - fail("Test timed out "); - } + + remoteService.triggerServiceStep(SimpleCommitRemoteService.stepA_openRealmAndCreateOneObject); } - // TODO: - // - send string from service and match replicate integration tests from Cocoa - // - add gradle task to start the sh script automatically (create pid file, ==> run or kill existing process) - // - check the requirement for the issue again + public static class ALotCommitsRemoteService extends RemoteIntegrationTestService { + private static SyncUser user; + public static final Step stepA_openRealm = new Step(RemoteTestService.BASE_A_LOT_COMMITS, 1) { + + @Override + protected void run() { + user = UserFactory.getInstance().loginWithDefaultUser(Constants.AUTH_URL); + String realmUrl = Constants.SYNC_SERVER_URL; + + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + .directory(getService().getRoot()) + .name(UUID.randomUUID().toString() + ".realm") + .build(); + getService().setRealm(Realm.getInstance(syncConfig)); + } + }; + + public static final Step stepB_createObjects = new Step(RemoteTestService.BASE_A_LOT_COMMITS, 2) { + @Override + protected void run() { + Realm realm = getService().getRealm(); + realm.beginTransaction(); + for (int i = 0; i < 100; i++) { + Number max = realm.where(TestObject.class).findAll().max("intProp"); + int pk = max == null ? 0 : max.intValue() + 1; + TestObject testObject = realm.createObject(TestObject.class, pk); + testObject.setStringProp("Str" + pk); + } + realm.commitTransaction(); + } + }; + + public static final Step stepC_closeRealm = new Step(RemoteTestService.BASE_A_LOT_COMMITS, 3) { + @Override + protected void run() { + getService().getRealm().close(); + user.logout(); + } + }; + } + + // 1. Open a sync Realm and listen to changes. + // A. Open the same sync Realm. + // B. Create 100 objects. + // 2. Check if the 100 objects are received. + // #. Repeat B/2 10 times. @Test - @Ignore("Failure might be caused by two processes each creating a Sync Client: needs investigation") + @RunTestWithRemoteService(ALotCommitsRemoteService.class) + @RunTestInLooperThread public void expectALot() throws Throwable { - final Throwable[] exception = new Throwable[1]; - final CountDownLatch testFinished = new CountDownLatch(1); - ExecutorService service = Executors.newSingleThreadExecutor(); - //noinspection unused - final Future future = service.submit(new Runnable() { + remoteService.createHandler(Looper.myLooper()); + + final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); + String realmUrl = Constants.SYNC_SERVER_URL; + final SyncConfiguration syncConfig = configFactory.createSyncConfigurationBuilder(user, realmUrl).build(); + final Realm realm = Realm.getInstance(syncConfig); + final RealmResults all = realm.where(TestObject.class).findAllSorted("intProp"); + looperThread.keepStrongReference(all); + final AtomicInteger listenerCalledCounter = new AtomicInteger(0); + all.addChangeListener(new RealmChangeListener>() { @Override - public void run() { - try { - Looper.prepare(); - Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL); - String realmUrl = Constants.SYNC_SERVER_URL_2; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) - .name(SendsALot.class.getSimpleName()) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail("Sync failure: " + error); - } - }) - .build(); - Realm.deleteRealm(syncConfig);//TODO do this in Rule as async tests - final Realm realm = Realm.getInstance(syncConfig); - Intent intent = new Intent(targetContext, SendsALot.class); - targetContext.startService(intent); - - final RealmResults all = realm.where(TestObject.class).findAllSorted("intProp"); - all.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults element) { - assertEquals(100, element.size()); - for (int i = 0; i < 100; i++) { - assertEquals(i, element.get(i).getIntProp()); - assertEquals("property " + i, element.get(i).getStringProp()); - } - - testFinished.countDown(); - } - }); - - Looper.loop(); - - } catch (Throwable e) { - exception[0] = e; - testFinished.countDown(); + public void onChange(RealmResults element) { + int counter = listenerCalledCounter.incrementAndGet(); + int size = all.size(); + if (size == 0) { + listenerCalledCounter.decrementAndGet(); + return; + } + assertEquals(0, size % 100); // Added 100 objects every time. + assertEquals(counter * 100 - 1, all.last().getIntProp()); + assertEquals("Str" + (counter * 100 - 1), all.last().getStringProp()); + if (counter == 10) { + remoteService.triggerServiceStep(ALotCommitsRemoteService.stepC_closeRealm); + realm.close(); + user.logout(); + looperThread.testComplete(); + } else { + remoteService.triggerServiceStep(ALotCommitsRemoteService.stepB_createObjects); } } }); - boolean testTimedOut = testFinished.await(30, TimeUnit.SECONDS); - if (exception[0] != null) { - throw exception[0]; - } else if (!testTimedOut) { - fail("Test timed out "); - } + + remoteService.triggerServiceStep(ALotCommitsRemoteService.stepA_openRealm); + remoteService.triggerServiceStep(ALotCommitsRemoteService.stepB_createObjects); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/TestObject.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/TestObject.java index 2bd27a6ef8..43b378d36a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/TestObject.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/TestObject.java @@ -17,9 +17,12 @@ package io.realm.objectserver.model; import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; public class TestObject extends RealmObject { + @PrimaryKey private int intProp; + private String stringProp; public int getIntProp() { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java deleted file mode 100644 index 4653b26211..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendOneCommit.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.objectserver.service; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -import io.realm.Realm; -import io.realm.SyncConfiguration; -import io.realm.SyncUser; -import io.realm.objectserver.model.ProcessInfo; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; - -/** - * Open a sync Realm on a different process, then send one commit. - */ -public class SendOneCommit extends Service { - - @Override - public void onCreate() { - super.onCreate(); - Realm.init(getApplicationContext()); - SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL); - String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) - .name(SendOneCommit.class.getSimpleName()) - .build(); - Realm.deleteRealm(syncConfig); - Realm realm = Realm.getInstance(syncConfig); - - realm.beginTransaction(); - ProcessInfo processInfo = realm.createObject(ProcessInfo.class); - processInfo.setName("Background_Process1"); - processInfo.setPid(android.os.Process.myPid()); - processInfo.setThreadId(Thread.currentThread().getId()); - realm.commitTransaction(); - - realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java deleted file mode 100644 index dca642beb2..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/service/SendsALot.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.objectserver.service; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -import io.realm.Realm; -import io.realm.SyncConfiguration; -import io.realm.SyncUser; -import io.realm.objectserver.model.TestObject; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; - -/** - * Open a sync Realm on a different process, then send one commit. - */ -public class SendsALot extends Service { - - @Override - public void onCreate() { - super.onCreate(); - Realm.init(getApplicationContext()); - SyncUser user = UserFactory.createDefaultUser(Constants.AUTH_URL); - String realmUrl = Constants.SYNC_SERVER_URL_2; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) - .name(SendsALot.class.getSimpleName()) - .build(); - Realm.deleteRealm(syncConfig); - Realm realm = Realm.getInstance(syncConfig); - - realm.beginTransaction(); - - for (int i = 0; i < 100; i++) { - TestObject testObject = realm.createObject(TestObject.class); - testObject.setIntProp(i); - testObject.setStringProp("property " + i); - } - realm.commitTransaction(); - - realm.close();//FIXME the close may not give a chance to the sync client to process/upload the changeset - } - - - @Override - public IBinder onBind(Intent intent) { - return null; - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java index 8d26d58fe4..f1956b5251 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -20,7 +20,7 @@ public class Constants { public static final String USER_REALM = "realm://127.0.0.1:9080/~/tests"; public static final String USER_REALM_SECURE = "realms://127.0.0.1:9443/~/tests"; - public static final String SYNC_SERVER_URL = "realm://127.0.0.1/tests"; + public static final String SYNC_SERVER_URL = "realm://127.0.0.1:9080/~/tests"; public static final String SYNC_SERVER_URL_2 = "realm://127.0.0.1/tests2"; public static final String AUTH_SERVER_URL = "http://127.0.0.1:9080/"; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index 8770019e59..ab909c0770 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -77,12 +77,10 @@ private static boolean waitAuthServerReady() throws InterruptedException { if (response.isSuccessful()) { return true; } - RealmLog.error("Error response from auth server: %s", response.toString()); } catch (IOException e) { // TODO As long as the auth server hasn't started yet, OKHttp cannot parse the response // correctly. At this point it is unknown weather is a bug in OKHttp or an // unknown host is reported. This can cause a lot of "false" errors in the log. - RealmLog.error(e); Thread.sleep(500); } finally { if (response != null) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/RemoteIntegrationTestService.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/RemoteIntegrationTestService.java new file mode 100644 index 0000000000..872b1089ec --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/RemoteIntegrationTestService.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.utils; + +import io.realm.SyncManager; +import io.realm.services.RemoteTestService; + +// Remote test service base class which contains some initialization for sync. +public class RemoteIntegrationTestService extends RemoteTestService { + public RemoteIntegrationTestService() { + super(); + SyncManager.Debug.skipOnlineChecking = true; + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index 1145f8a310..f9851b917d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -16,18 +16,44 @@ package io.realm.objectserver.utils; +import java.util.UUID; + +import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.SyncCredentials; import io.realm.SyncUser; +import io.realm.log.RealmLog; +// Helper class to retrieve users with same IDs even in multi-processes. // Must be in `io.realm.objectserver` to work around package protected methods. +// This require Realm.init() to be called before using this class. public class UserFactory { + private static final String PASSWORD = "myPassw0rd"; + // Since the integration tests need to use the same user for different processes, we create a new user name when the + // test starts and store it in a Realm. Then it can be retrieved for every process. + private String userName; + private static UserFactory instance; + private static RealmConfiguration configuration = new RealmConfiguration.Builder() + .name("user-factory.realm") + .build(); + + private UserFactory(String userName) { + this.userName = userName; + } - public static SyncUser createDefaultUser(String authUrl) { - return createUser(authUrl, "test-user"); + public SyncUser loginWithDefaultUser(String authUrl) { + SyncCredentials credentials = SyncCredentials.usernamePassword(userName, PASSWORD, false); + return SyncUser.login(credentials, authUrl); + } + + public static SyncUser createUniqueUser(String authUrl) { + String uniqueName = UUID.randomUUID().toString(); + SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, PASSWORD, true); + return SyncUser.login(credentials, authUrl); } - public static SyncUser createUser(String authUrl, String userIdentifier) { - SyncCredentials credentials = SyncCredentials.usernamePassword(userIdentifier, "myPassw0rd", true); + public SyncUser createDefaultUser(String authUrl) { + SyncCredentials credentials = SyncCredentials.usernamePassword(userName, PASSWORD, true); return SyncUser.login(credentials, authUrl); } @@ -36,4 +62,43 @@ public static SyncUser createAdminUser(String authUrl) { SyncCredentials credentials = SyncCredentials.custom("admin", "debug", null); return SyncUser.login(credentials, authUrl); } + + // Since we don't have a reliable way to reset the sync server and client, just use a new user factory for every + // test case. + public static void resetInstance() { + instance = null; + Realm realm = Realm.getInstance(configuration); + UserFactoryStore store = realm.where(UserFactoryStore.class).findFirst(); + realm.beginTransaction(); + if (store == null) { + store = realm.createObject(UserFactoryStore.class); + } + store.setUserName(UUID.randomUUID().toString()); + realm.commitTransaction(); + realm.close(); + } + + // The @Before method will be called before the looper tests finished. We need to find a better place to call this. + public static void clearInstance() { + Realm realm = Realm.getInstance(configuration); + realm.beginTransaction(); + realm.delete(UserFactoryStore.class); + realm.commitTransaction(); + realm.close(); + } + + public static synchronized UserFactory getInstance() { + if (instance == null) { + Realm realm = Realm.getInstance(configuration); + UserFactoryStore store = realm.where(UserFactoryStore.class).findFirst(); + if (store == null || store.getUserName() == null) { + throw new IllegalStateException("Current user has not been set. Call resetInstance() first."); + } + + instance = new UserFactory(store.getUserName()); + realm.close(); + } + RealmLog.debug("UserFactory.getInstance, the default user is " + instance.userName + " ."); + return instance; + } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactoryStore.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactoryStore.java new file mode 100644 index 0000000000..80b8bac60c --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactoryStore.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.utils; + +import io.realm.RealmObject; + +// Used by UserFactory. Storing current user name for testing to share the same user name across processes. +public class UserFactoryStore extends RealmObject { + private String userName; + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } +} From a1f30b6a055a0b7e4279b8a62a34aab77aef4162 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 8 Jun 2017 09:22:00 +0200 Subject: [PATCH 0738/2110] Fix admin users not connection correctly to ROS (#4760) --- CHANGELOG.md | 1 + .../main/cpp/io_realm_RealmFileUserStore.cpp | 10 +- .../java/io/realm/RealmFileUserStore.java | 4 +- .../java/io/realm/SyncManager.java | 2 +- .../java/io/realm/SyncSession.java | 75 ++++++------ .../objectserver/ObjectServerUser.java | 5 +- .../java/io/realm/BaseIntegrationTest.java | 107 ++++++++++++++++++ .../java/io/realm/SSLConfigurationTests.java | 1 - .../java/io/realm/SyncedRealmTests.java | 1 - .../java/io/realm/objectserver/AuthTests.java | 1 + .../objectserver/BaseIntegrationTest.java | 58 ---------- .../EncryptedSynchronizedRealmTests.java | 11 +- .../objectserver/ManagementRealmTests.java | 42 ++++++- .../objectserver/ProcessCommitTests.java | 1 + .../realm/objectserver/utils/HttpUtils.java | 18 +-- 15 files changed, 212 insertions(+), 125 deletions(-) create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 389cca5278..5d6b6ffd7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * [ObjectServer] Fixed a crash when an authentication error happens (#4726). * [ObjectServer] Enabled encryption with Sync (#4561). +* [ObjectServer] Admin users did not connect correctly to the server (#4750). ### Internal diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 148c0c8914..e2eaa319cd 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -60,16 +60,14 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetUser(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser(JNIEnv* env, jclass, jstring identity, jstring json_token, - jstring url, jboolean is_admin) + jstring url) { TR_ENTER() try { - JStringAccessor user_identity(env, identity); // throws + JStringAccessor user_identity(env, identity); // throws JStringAccessor user_json_token(env, json_token); // throws - JStringAccessor auth_url(env, url); // throws - - SyncUser::TokenType token_type = (is_admin) ? SyncUser::TokenType::Admin : SyncUser::TokenType::Normal; - SyncManager::shared().get_user(user_identity, user_json_token, std::string(auth_url), token_type); + JStringAccessor auth_url(env, url); // throws + SyncManager::shared().get_user(user_identity, user_json_token, std::string(auth_url)); } CATCH_STD() } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java index 6b03bf8573..e208131397 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java @@ -32,7 +32,7 @@ public class RealmFileUserStore implements UserStore { public void put(SyncUser user) { String userJson = user.toJson(); // create or update token (userJson) using identity - nativeUpdateOrCreateUser(user.getIdentity(), userJson, user.getSyncUser().getAuthenticationUrl().toString(), user.isAdmin()); + nativeUpdateOrCreateUser(user.getIdentity(), userJson, user.getSyncUser().getAuthenticationUrl().toString()); } /** @@ -92,7 +92,7 @@ private static SyncUser toSyncUserOrNull(String userJson) { protected static native String[] nativeGetAllUsers(); - protected static native void nativeUpdateOrCreateUser(String identity, String jsonToken, String url, boolean isAdmin); + protected static native void nativeUpdateOrCreateUser(String identity, String jsonToken, String url); protected static native void nativeLogoutUser(String identity); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 8ef5862f5f..906454f00c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -296,7 +296,7 @@ private synchronized static String bindSessionWithConfig(String sessionPath) { RealmLog.error("Matching Java SyncSession could not be found for: " + sessionPath); } else { try { - return syncSession.accessToken(authServer); + return syncSession.getAccessToken(authServer); } catch (Exception exception) { RealmLog.error(exception); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 846e79bd98..408d1daec2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -242,9 +242,10 @@ public interface ErrorHandler { void onError(SyncSession session, ObjectServerError error); } - String accessToken(final AuthenticationServer authServer) { + // Return the access token for the Realm this Session is connected to. + String getAccessToken(final AuthenticationServer authServer) { // check first if there's a valid access_token we can return immediately - if (getUser().getSyncUser().isAuthenticated(configuration)) { + if (getUser().getSyncUser().isRealmAuthenticated(configuration)) { Token accessToken = getUser().getSyncUser().getAccessToken(configuration.getServerUrl()); // start refreshing this token if a refresh is not going on if (!onGoingAccessTokenQuery.getAndSet(true)) { @@ -292,9 +293,9 @@ private void authenticateRealm(final AuthenticationServer authServer) { protected AuthenticateResponse execute() { if (!isClosed && !Thread.currentThread().isInterrupted()) { return authServer.loginToRealm( - getUser().getAccessToken(),//refresh token in fact + getUser().getAccessToken(), //refresh token in fact configuration.getServerUrl(), - getUser().getSyncUser().getAuthenticationUrl() + getUser().getAuthenticationUrl() ); } return null; @@ -309,9 +310,11 @@ protected void onSuccess(AuthenticateResponse response) { configuration.getPath(), configuration.shouldDeleteRealmOnLogout() ); - getUser().getSyncUser().addRealm(configuration.getServerUrl(), desc); + URI realmUrl = configuration.getServerUrl(); + getUser().getSyncUser().addRealm(realmUrl, desc); + String token = getUser().getSyncUser().getAccessToken(realmUrl).value(); // schedule a token refresh before it expires - if (nativeRefreshAccessToken(configuration.getPath(), getUser().getSyncUser().getAccessToken(configuration.getServerUrl()).value(), configuration.getServerUrl().toString())) { + if (nativeRefreshAccessToken(configuration.getPath(), token, realmUrl.toString())) { scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); } else { @@ -335,35 +338,35 @@ protected void onError(AuthenticateResponse response) { } private void scheduleRefreshAccessToken(final AuthenticationServer authServer, long expireDateInMs) { - // calculate the delay time before which we should refresh the access_token, - // we adjust to 10 second to proactively refresh the access_token before the session - // hit the expire date on the token - long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; - if (refreshAfter < 0) { - // Token already expired - RealmLog.debug("Expires time already reached for the access token, refresh as soon as possible"); - // we avoid refreshing directly to avoid an edge case where the client clock is ahead - // of the server, causing all access_token received from the server to be always - // expired, we will flood the server with refresh token requests then, so adding - // a bit of delay is the best effort in this case. - refreshAfter = REFRESH_MARGIN_DELAY; - } + // calculate the delay time before which we should refresh the access_token, + // we adjust to 10 second to proactively refresh the access_token before the session + // hit the expire date on the token + long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; + if (refreshAfter < 0) { + // Token already expired + RealmLog.debug("Expires time already reached for the access token, refresh as soon as possible"); + // we avoid refreshing directly to avoid an edge case where the client clock is ahead + // of the server, causing all access_token received from the server to be always + // expired, we will flood the server with refresh token requests then, so adding + // a bit of delay is the best effort in this case. + refreshAfter = REFRESH_MARGIN_DELAY; + } - RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); + RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); - if (refreshTokenTask != null) { - refreshTokenTask.cancel(); - } + if (refreshTokenTask != null) { + refreshTokenTask.cancel(); + } - ScheduledFuture task = REFRESH_TOKENS_EXECUTOR.schedule(new Runnable() { - @Override - public void run() { - if (!isClosed && !Thread.currentThread().isInterrupted()) { - refreshAccessToken(authServer); - } + ScheduledFuture task = REFRESH_TOKENS_EXECUTOR.schedule(new Runnable() { + @Override + public void run() { + if (!isClosed && !Thread.currentThread().isInterrupted()) { + refreshAccessToken(authServer); } - }, refreshAfter, TimeUnit.MILLISECONDS); - refreshTokenTask = new RealmAsyncTaskImpl(task, REFRESH_TOKENS_EXECUTOR); + } + }, refreshAfter, TimeUnit.MILLISECONDS); + refreshTokenTask = new RealmAsyncTaskImpl(task, REFRESH_TOKENS_EXECUTOR); } // Authenticate by getting access tokens for the specific Realm @@ -385,14 +388,15 @@ protected void onSuccess(AuthenticateResponse response) { synchronized (SyncSession.this) { if (!isClosed && !Thread.currentThread().isInterrupted()) { RealmLog.debug("Access Token refreshed successfully, Sync URL: " + configuration.getServerUrl()); - if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), configuration.getUser().getAuthenticationUrl().toString())) { + URI realmUrl = configuration.getServerUrl(); + if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), realmUrl.toString())) { // replaced the user old access_token ObjectServerUser.AccessDescription desc = new ObjectServerUser.AccessDescription( response.getAccessToken(), configuration.getPath(), configuration.shouldDeleteRealmOnLogout() ); - getUser().getSyncUser().addRealm(configuration.getServerUrl(), desc); + getUser().getSyncUser().addRealm(realmUrl, desc); // schedule the next refresh scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); @@ -434,7 +438,7 @@ private static class WaitForServerChangesWrapper { */ public void waitForServerChanges() throws InterruptedException { if (!resultReceived) { - waitForChanges.await(); + waitForChanges.await(); } } @@ -466,7 +470,6 @@ public void throwExceptionIfNeeded() { } } - private static native boolean nativeRefreshAccessToken(String path, String accessToken, String authURL); + private static native boolean nativeRefreshAccessToken(String path, String accessToken, String realmUrl); private native boolean nativeWaitForDownloadCompletion(String path); } - diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java index 235c032a9b..a5e731bf34 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/ObjectServerUser.java @@ -65,7 +65,7 @@ private void setRefreshToken(final Token refreshToken) { * * Authenticating will happen automatically as part of opening a Realm. */ - public boolean isAuthenticated(SyncConfiguration configuration) { + public boolean isRealmAuthenticated(SyncConfiguration configuration) { Token token = getAccessToken(configuration.getServerUrl()); return token != null && token.expiresMs() > System.currentTimeMillis(); } @@ -93,6 +93,9 @@ public String getIdentity() { return identity; } + /** + * Return the access token for a given Realm URL or null if none was found + */ public Token getAccessToken(URI serverUrl) { AccessDescription accessDescription = realms.get(serverUrl); return (accessDescription != null) ? accessDescription.accessToken : null; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java new file mode 100644 index 0000000000..f535ba3c22 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.support.test.InstrumentationRegistry; +import android.util.Log; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import io.realm.internal.Util; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; +import io.realm.objectserver.utils.HttpUtils; + +public class BaseIntegrationTest { + + private static int originalLogLevel; + + @BeforeClass + public static void setUp () throws Exception { + SyncManager.Debug.skipOnlineChecking = true; + try { + HttpUtils.startSyncServer(); + } catch (Exception e) { + // Throwing an exception from this method will crash JUnit. Instead just log it. + // If this setup method fails, all unit tests in the class extending it will most likely fail as well. + Log.e(HttpUtils.TAG, "Could not start Sync Server: " + Util.getStackTrace(e)); + } + } + + @AfterClass + public static void tearDown () throws Exception { + try { + HttpUtils.stopSyncServer(); + } catch (Exception e) { + Log.e(HttpUtils.TAG, "Failed to stop Sync Server" + Util.getStackTrace(e)); + } + } + + @Before + public void setupTest() throws IOException { + // TODO We should implement a more consistent reset method for all of Sync that reset + // everything completely including deleting all files. + deleteRosFiles(); + if (BaseRealm.applicationContext != null) { + // Realm was already initialized. Reset all internal state + // in order to be able fully re-initialize. + + // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' + // causing the SyncUser to remain in memory. + // They're actually not persisted into disk. + // move this call to 'tearDown' to clean in-memory & on-disk users + // once https://github.com/realm/realm-object-store/issues/207 is resolved + SyncManager.reset(); + BaseRealm.applicationContext = null; // Required for Realm.init() to work + } + Realm.init(InstrumentationRegistry.getContext()); + originalLogLevel = RealmLog.getLevel(); + RealmLog.setLevel(LogLevel.DEBUG); + } + + @After + public void tearDownTest() throws IOException { + RealmLog.setLevel(originalLogLevel); + } + + + // Cleanup filesystem to make sure nothing lives for the next test. + // Failing to do so might lead to DIVERGENT_HISTORY errors being thrown if Realms from + // previous tests are being accessed. + private static void deleteRosFiles() throws IOException { + File rosFiles = new File(InstrumentationRegistry.getContext().getFilesDir(),"realm-object-server"); + deleteFile(rosFiles); + } + + private static void deleteFile(File file) throws IOException { + if (file.isDirectory()) { + for (File c : file.listFiles()) { + deleteFile(c); + } + } + if (!file.delete()) { + throw new IllegalStateException("Failed to delete file or directory: " + file.getAbsolutePath()); + } + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index 82c024fb2f..46fc2d8a8e 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -31,7 +31,6 @@ import io.realm.exceptions.RealmFileException; import io.realm.log.LogLevel; import io.realm.log.RealmLog; -import io.realm.objectserver.BaseIntegrationTest; import io.realm.objectserver.utils.Constants; import io.realm.rule.TestSyncConfigurationFactory; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index c4b3c28d6d..277aa407cb 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -36,7 +36,6 @@ import io.realm.entities.StringOnly; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmMigrationNeededException; -import io.realm.objectserver.BaseIntegrationTest; import io.realm.objectserver.utils.Constants; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index a315e08331..2555da89da 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import io.realm.BaseIntegrationTest; import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.Realm; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java deleted file mode 100644 index a6cd05356e..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/BaseIntegrationTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.objectserver; - -import android.support.test.InstrumentationRegistry; - -import org.junit.AfterClass; -import org.junit.BeforeClass; - -import io.realm.Realm; -import io.realm.SyncManager; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; -import io.realm.objectserver.utils.HttpUtils; - -public class BaseIntegrationTest { - - private static int originalLogLevel; - - @BeforeClass - public static void setUp () throws Exception { - SyncManager.Debug.skipOnlineChecking = true; - try { - Realm.init(InstrumentationRegistry.getContext()); - originalLogLevel = RealmLog.getLevel(); - RealmLog.setLevel(LogLevel.DEBUG); - HttpUtils.startSyncServer(); - } catch (Exception e) { - // Throwing an exception from this method will crash JUnit. Instead just log it. - // If this setup method fails, all unit tests in the class extending it will most likely fail as well. - RealmLog.error("Could not start Sync Server", e); - } - } - - @AfterClass - public static void tearDown () throws Exception { - try { - HttpUtils.stopSyncServer(); - RealmLog.setLevel(originalLogLevel); - } catch (Exception e) { - RealmLog.error("Failed to stop Sync Server", e); - } - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 63bcb72115..b3880a158b 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -11,6 +11,7 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; +import io.realm.BaseIntegrationTest; import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmResults; @@ -39,16 +40,6 @@ public class EncryptedSynchronizedRealmTests extends BaseIntegrationTest { @Rule public final TestSyncConfigurationFactory configurationFactory = new TestSyncConfigurationFactory(); - @Before - public void before() { - // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' - // causing the SyncUser to remain in memory. - // They're actually not persisted into disk. - // move this call to 'tearDown' to clean in-memory & on-disk users - // once https://github.com/realm/realm-object-store/issues/207 is resolved - SyncTestUtils.resetSyncMetadata(); - } - // Make sure the encryption is local, i.e after deleting a synced Realm // re-open it again with no (or different) key, should be possible. @Test diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index ecb219cdae..bf31f5bd25 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -26,11 +26,13 @@ import java.util.Date; import java.util.concurrent.atomic.AtomicReference; +import io.realm.BaseIntegrationTest; import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmResults; import io.realm.SyncConfiguration; +import io.realm.SyncCredentials; import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.entities.Dog; @@ -41,9 +43,9 @@ import io.realm.permissions.PermissionOfferResponse; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import io.realm.rule.TestSyncConfigurationFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -52,7 +54,43 @@ public class ManagementRealmTests extends BaseIntegrationTest { @Rule public RunInLooperThread looperThread = new RunInLooperThread(); - @Ignore("TODO: Test is currently flaky. See https://github.com/realm/realm-java/pull/4066") + // This is primarily a test making sure that an admin user actually connects correctly to ROS. + // See https://github.com/realm/realm-java/issues/4750 + @Test + @RunTestInLooperThread + public void adminUser_writeInvalidPermissionOffer() { + final SyncUser user = UserFactory.createAdminUser(Constants.AUTH_URL); + assertTrue(user.isValid()); + Realm realm = user.getManagementRealm(); + looperThread.closeAfterTest(realm); + looperThread.runAfterTest(new Runnable() { + @Override + public void run() { + user.logout(); + } + }); + realm.beginTransaction(); + // Invalid Permission offer + realm.copyToRealm(new PermissionOffer("*", true, true, false, null)); + realm.commitTransaction(); + RealmResults results = realm.where(PermissionOffer.class).findAllAsync(); + looperThread.keepStrongReference(results); + results.addChangeListener(new RealmChangeListener >() { + @Override + public void onChange(RealmResults offers) { + if (offers.size() > 0) { + PermissionOffer offer = offers.first(); + Integer statusCode = offer.getStatusCode(); + if (statusCode != null && statusCode > 0) { + assertTrue(offer.getStatusMessage().contains("The path is invalid or current user has no access.")); + looperThread.testComplete(); + } + } + } + }); + } + + @Ignore("Failing due to terminate called after throwing an instance of 'realm::MultipleSyncAgents'. Will be fixed when upgrading to Sync 1.10") @Test @RunTestInLooperThread public void create_acceptOffer() { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 3f63754def..836d3a4eef 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -27,6 +27,7 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; +import io.realm.BaseIntegrationTest; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmResults; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index ab909c0770..fca7147dc5 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -16,9 +16,10 @@ package io.realm.objectserver.utils; +import android.util.Log; + import java.io.IOException; -import io.realm.log.RealmLog; import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -27,6 +28,8 @@ /** * Start and Stop the node server responsible of creating a * temp directory & start a sync server on it for each unit test. + * + * WARNING: This class is called before Realm is initialized, so RealmLog cannot be used. */ public class HttpUtils { private final static OkHttpClient client = new OkHttpClient.Builder() @@ -35,8 +38,9 @@ public class HttpUtils { // adb reverse tcp:8888 tcp:8888 // will forward this query to the host, running the integration test server on 8888 - private final static String START_SERVER = "http://127.0.0.1:8888/start"; - private final static String STOP_SERVER = "http://127.0.0.1:8888/stop"; + private static final String START_SERVER = "http://127.0.0.1:8888/start"; + private static final String STOP_SERVER = "http://127.0.0.1:8888/stop"; + public static final String TAG = "IntegrationTestServer"; public static void startSyncServer() throws Exception { Request request = new Request.Builder() @@ -48,10 +52,10 @@ public static void startSyncServer() throws Exception { Headers responseHeaders = response.headers(); for (int i = 0; i < responseHeaders.size(); i++) { - RealmLog.debug(responseHeaders.name(i) + ": " + responseHeaders.value(i)); + Log.d(TAG, responseHeaders.name(i) + ": " + responseHeaders.value(i)); } - RealmLog.debug(response.body().string()); + Log.d(TAG, response.body().string()); // FIXME: Server ready checking should be done in the control server side! if (!waitAuthServerReady()) { @@ -103,9 +107,9 @@ public static void stopSyncServer() throws Exception { Headers responseHeaders = response.headers(); for (int i = 0; i < responseHeaders.size(); i++) { - RealmLog.debug(responseHeaders.name(i) + ": " + responseHeaders.value(i)); + Log.d(TAG, responseHeaders.name(i) + ": " + responseHeaders.value(i)); } - RealmLog.debug(response.body().string()); + Log.d(TAG, response.body().string()); } } From d6020515a465657860c66e764a04be7838845f38 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 8 Jun 2017 20:34:51 +0800 Subject: [PATCH 0739/2110] Don't strip debug symbols for debug build! From Android Plugin Version 2.2.0, it tries to strip debug symbols of native libs for all build types (even debug build!!??). I don't like something try to be smart, so just let our cmake handler stripping and backup the unstripped libs. --- realm/realm-library/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index d567cbe844..8e72d79a99 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -92,6 +92,9 @@ android { packagingOptions { exclude 'META-INF/NOTICE.txt' exclude 'META-INF/LICENSE.txt' + // We did strip with cmake for release build. + // Please, Gradle, you are not that smart! Pleas DO NOT strip debug symbols for debug build! + doNotStrip "*/*/*.so" } lintOptions { From 860730b99c49fa369aca55d7d54991156709add9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 9 Jun 2017 12:55:57 +0200 Subject: [PATCH 0740/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d6b6ffd7a..d13350215d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.3.2 (YYYY-MM-DD) +## 3.3.2 (2017-06-09) ### Breaking Changes From 9013e59f9b3f748c8e2a0177363887fcc3ff15f4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 9 Jun 2017 12:56:02 +0200 Subject: [PATCH 0741/2110] Release v3.3.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a652712908..5436ea06e3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.3.2-SNAPSHOT \ No newline at end of file +3.3.2 \ No newline at end of file From 5769726dd919d5e001e9fb0fb2825feba359507c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 9 Jun 2017 12:56:02 +0200 Subject: [PATCH 0742/2110] Prepare next release v3.3.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5436ea06e3..e24c1f857e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.3.2 \ No newline at end of file +3.3.3-SNAPSHOT \ No newline at end of file From d03e802bb57dc38611e20e1d526102530481a627 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 9 Jun 2017 13:36:46 +0200 Subject: [PATCH 0743/2110] Upgrade to latest Sync 1.9.1 / Core 2.8.0 (#4765) --- CHANGELOG.md | 2 ++ dependencies.list | 6 +++--- realm/realm-library/src/main/cpp/object-store | 2 +- tools/sync_test_server/Dockerfile | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d601e05b1..fc54b2dd15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ ### Internal * Factor out internal interface ManagedObject +* Upgraded to Realm Sync 1.9.1 +* Upgraded to Realm Core 2.8.0 ## 3.3.1 (2017-05-26) diff --git a/dependencies.list b/dependencies.list index f2613db9bf..70a8b12116 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.8.5 -REALM_SYNC_SHA256=71e70f83b1604672bbfd7c04fcf984ecc8ac683864943ef3de262994c2e7b7fc +REALM_SYNC_VERSION=1.9.1 +REALM_SYNC_SHA256=b1bd4be71c414f17fee01c05888989ecd0c22a57d4c760c6750f6634c2c29ee8 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. @@ -10,4 +10,4 @@ REALM_SYNC_SHA256=71e70f83b1604672bbfd7c04fcf984ecc8ac683864943ef3de262994c2e7b7 # /tools/sync_test_server/Dockerfile specify which repo (apt) we should # install/use between 'realm' and 'realm-testing', the version below should # correspond to an existing version on the *specified* repo. -REALM_OBJECT_SERVER_DE_VERSION=1.6.0-35 +REALM_OBJECT_SERVER_DE_VERSION=1.7.5-180 diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 4330f13eed..f5e1ce7bb5 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 4330f13eedfad4f34bcecdca25f71fb949fbc4b2 +Subproject commit f5e1ce7bb5ceda14dfff9b2412618f8ea9f6be74 diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 6404a0e086..e83815efe0 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -5,8 +5,8 @@ ARG ROS_DE_VERSION # Add realm repo RUN apt-get update -qq \ && apt-get install -y curl npm \ - # && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh \ - && curl -s https://packagecloud.io/install/repositories/realm/realm-testing/script.deb.sh | bash + && curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash + #&& curl -s https://packagecloud.io/install/repositories/realm/realm-testing/script.deb.sh | bash # ROS npm dependencies RUN npm init -y From 5503f20d62ed59943d45856f65f83b246577c126 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 9 Jun 2017 13:44:43 +0200 Subject: [PATCH 0744/2110] Fix release script on mac --- tools/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/release.sh b/tools/release.sh index b0e91f557a..343c4367cc 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -144,7 +144,7 @@ prepare_branch() { # Update date in change log cur_date=$(date "+%F") - sed -i "1 s/YYYY-MM-DD/${cur_date}/" CHANGELOG.md + sed -i .bak "1 s/YYYY-MM-DD/${cur_date}/" CHANGELOG.md git add CHANGELOG.md git commit -m "Update changelog date" From fad2623909eacd1335b5e3ddf90b667a41c7afa2 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 13 Jun 2017 18:33:13 +0900 Subject: [PATCH 0745/2110] update kotlin to 1.1.2-5 and use kotlin-stdlib-jre7 instead of kotlin-stdlib in kotlinExample (#4775) --- examples/kotlinExample/build.gradle | 4 ++-- realm/build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index d3984d3701..763a8291a7 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.2-4' + ext.kotlin_version = '1.1.2-5' repositories { jcenter() mavenCentral() @@ -45,6 +45,6 @@ android { } dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" + compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:${kotlin_version}" compile 'org.jetbrains.anko:anko-sdk15:0.9.1' } diff --git a/realm/build.gradle b/realm/build.gradle index f2eead6c59..fc408fc2ed 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.2-4' + ext.kotlin_version = '1.1.2-5' repositories { mavenLocal() jcenter() From 1e46a87a4987fe1a42ea4aacf336a13857f20a2e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Jun 2017 23:34:52 +0200 Subject: [PATCH 0746/2110] Fix logging for RunTestInLooperThreadTests integration tests. (#4780) Fix logging for RunTestInLooperThreadTests integration tests. --- .../java/io/realm/BaseIntegrationTest.java | 32 +++++++++++++++++-- .../java/io/realm/SSLConfigurationTests.java | 3 -- .../java/io/realm/SyncedRealmTests.java | 13 -------- .../java/io/realm/objectserver/AuthTests.java | 8 ----- .../EncryptedSynchronizedRealmTests.java | 7 +--- .../objectserver/ManagementRealmTests.java | 3 -- .../objectserver/ProcessCommitTests.java | 6 ++-- 7 files changed, 33 insertions(+), 39 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index f535ba3c22..3a46d16bd6 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -17,26 +17,43 @@ package io.realm; import android.support.test.InstrumentationRegistry; +import android.support.test.rule.UiThreadTestRule; import android.util.Log; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.ExpectedException; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import io.realm.internal.Util; import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.objectserver.utils.HttpUtils; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.TestSyncConfigurationFactory; + public class BaseIntegrationTest { private static int originalLogLevel; + @Rule + public final TestSyncConfigurationFactory configurationFactory = new TestSyncConfigurationFactory(); + + @Rule + public RunInLooperThread looperThread = new RunInLooperThread(); + + @Rule + public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + @BeforeClass public static void setUp () throws Exception { SyncManager.Debug.skipOnlineChecking = true; @@ -82,7 +99,18 @@ public void setupTest() throws IOException { @After public void tearDownTest() throws IOException { - RealmLog.setLevel(originalLogLevel); + if (looperThread.isTestComplete()) { + // Non-looper tests can reset here + RealmLog.setLevel(originalLogLevel); + } else { + // Otherwise we need to wait for the test to complete + looperThread.runAfterTest(new Runnable() { + @Override + public void run() { + RealmLog.setLevel(originalLogLevel); + } + }); + } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index 46fc2d8a8e..ebe2663b8d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -44,9 +44,6 @@ public class SSLConfigurationTests extends BaseIntegrationTest { @Rule public Timeout globalTimeout = Timeout.seconds(10); - @Rule - public final TestSyncConfigurationFactory configurationFactory = new TestSyncConfigurationFactory(); - @Test public void trustedRootCA() throws InterruptedException { String username = UUID.randomUUID().toString(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index 277aa407cb..d105b6505d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -19,10 +19,8 @@ import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.test.annotation.UiThreadTest; -import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -37,9 +35,7 @@ import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.objectserver.utils.Constants; -import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import io.realm.rule.TestSyncConfigurationFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -52,15 +48,6 @@ @RunWith(AndroidJUnit4.class) public class SyncedRealmTests extends BaseIntegrationTest { - @Rule - public RunInLooperThread looperThread = new RunInLooperThread(); - - @Rule - public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); - - @Rule - public final TestSyncConfigurationFactory configurationFactory = new TestSyncConfigurationFactory(); - @Test @UiThreadTest public void waitForInitialRemoteData_mainThreadThrows() { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 2555da89da..a2ed00777c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -5,9 +5,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Ignore; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import java.net.MalformedURLException; @@ -27,7 +25,6 @@ import io.realm.SyncUser; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import static junit.framework.Assert.assertEquals; @@ -38,11 +35,6 @@ @RunWith(AndroidJUnit4.class) public class AuthTests extends BaseIntegrationTest { - @Rule - public RunInLooperThread looperThread = new RunInLooperThread(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); @Test public void login_userNotExist() { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index b3880a158b..dc83c2f862 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -2,7 +2,6 @@ import android.os.SystemClock; -import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -26,19 +25,15 @@ import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.TestSyncConfigurationFactory; -import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class EncryptedSynchronizedRealmTests extends BaseIntegrationTest { - @Rule - public Timeout globalTimeout = Timeout.seconds(10); @Rule - public final TestSyncConfigurationFactory configurationFactory = new TestSyncConfigurationFactory(); + public Timeout globalTimeout = Timeout.seconds(10); // Make sure the encryption is local, i.e after deleting a synced Realm // re-open it again with no (or different) key, should be possible. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index bf31f5bd25..ea682925b9 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -51,9 +51,6 @@ @RunWith(AndroidJUnit4.class) public class ManagementRealmTests extends BaseIntegrationTest { - @Rule - public RunInLooperThread looperThread = new RunInLooperThread(); - // This is primarily a test making sure that an admin user actually connects correctly to ROS. // See https://github.com/realm/realm-java/issues/4750 @Test diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 836d3a4eef..1a086c3079 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -55,8 +55,6 @@ public class ProcessCommitTests extends BaseIntegrationTest { public RunInLooperThread looperThread = new RunInLooperThread(); @Rule public RunWithRemoteService remoteService = new RunWithRemoteService(); - @Rule - public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); @Before public void before() throws Exception { @@ -114,7 +112,7 @@ public void expectSimpleCommit() { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = configFactory.createSyncConfigurationBuilder(user, realmUrl).build(); + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, realmUrl).build(); final Realm realm = Realm.getInstance(syncConfig); final RealmResults all = realm.where(ProcessInfo.class).findAll(); looperThread.keepStrongReference(all); @@ -189,7 +187,7 @@ public void expectALot() throws Throwable { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = configFactory.createSyncConfigurationBuilder(user, realmUrl).build(); + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, realmUrl).build(); final Realm realm = Realm.getInstance(syncConfig); final RealmResults all = realm.where(TestObject.class).findAllSorted("intProp"); looperThread.keepStrongReference(all); From 3d1bfc1295195eefd1329e9b7eaf21636f194884 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Wed, 14 Jun 2017 14:21:30 +0200 Subject: [PATCH 0747/2110] Suppress warnings in generated code. (#4779) * Suppress warnings in generated code. * Add compile time flag controlling the SuppressWarnings annotation on generated classes --- .../main/java/io/realm/processor/RealmProcessor.java | 3 +++ .../io/realm/processor/RealmProxyClassGenerator.java | 12 +++++++++++- .../test/resources/io/realm/AllTypesRealmProxy.java | 1 + .../test/resources/io/realm/BooleansRealmProxy.java | 1 + .../test/resources/io/realm/NullTypesRealmProxy.java | 1 + .../test/resources/io/realm/SimpleRealmProxy.java | 1 + realm/realm-library/build.gradle | 10 ++++++++++ 7 files changed, 28 insertions(+), 1 deletion(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index b4b9663ba6..ef75e16923 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -25,6 +25,7 @@ import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -123,7 +124,9 @@ "io.realm.annotations.RealmModule", "io.realm.annotations.Required" }) +@SupportedOptions(value = {"realm.suppressWarnings"}) public class RealmProcessor extends AbstractProcessor { + // Don't consume annotations. This allows 3rd party annotation processors to run. private static final boolean CONSUME_ANNOTATIONS = false; diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index b22fc4f493..fedbb58b78 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -36,6 +36,7 @@ public class RealmProxyClassGenerator { + private static final String OPTION_SUPPRESS_WARNINGS = "realm.suppressWarnings"; private static final String BACKLINKS_FIELD_EXTENSION = "Backlinks"; private final ProcessingEnvironment processingEnvironment; @@ -44,6 +45,7 @@ public class RealmProxyClassGenerator { private final String qualifiedClassName; private final String interfaceName; private final String qualifiedGeneratedClassName; + private final boolean suppressWarnings; public RealmProxyClassGenerator(ProcessingEnvironment processingEnvironment, ClassMetaData metadata) { this.processingEnvironment = processingEnvironment; @@ -53,6 +55,10 @@ public RealmProxyClassGenerator(ProcessingEnvironment processingEnvironment, Cla this.interfaceName = Utils.getProxyInterfaceName(simpleClassName); this.qualifiedGeneratedClassName = String.format("%s.%s", Constants.REALM_PACKAGE_NAME, Utils.getProxyClassName(simpleClassName)); + + // See the configuration for the debug build type, + // in the realm-library project, for an example of how to set this flag. + this.suppressWarnings = !"false".equalsIgnoreCase(processingEnvironment.getOptions().get(OPTION_SUPPRESS_WARNINGS)); } public void generate() throws IOException, UnsupportedOperationException { @@ -101,7 +107,11 @@ public void generate() throws IOException, UnsupportedOperationException { .emitEmptyLine(); // Begin the class definition - writer.beginType( + if (suppressWarnings) { + writer.emitAnnotation("SuppressWarnings(\"all\")"); + } + writer + .beginType( qualifiedGeneratedClassName, // full qualified name of the item to generate "class", // the type of the item EnumSet.of(Modifier.PUBLIC), // modifiers to apply diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 5b6b6d8d23..d033482722 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -30,6 +30,7 @@ import org.json.JSONException; import org.json.JSONObject; +@SuppressWarnings("all") public class AllTypesRealmProxy extends some.test.AllTypes implements RealmObjectProxy, AllTypesRealmProxyInterface { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index c39c1c2632..a44fae7799 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -29,6 +29,7 @@ import org.json.JSONException; import org.json.JSONObject; +@SuppressWarnings("all") public class BooleansRealmProxy extends some.test.Booleans implements RealmObjectProxy, BooleansRealmProxyInterface { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 2b215a4fe8..9c97e3f902 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -29,6 +29,7 @@ import org.json.JSONException; import org.json.JSONObject; +@SuppressWarnings("all") public class NullTypesRealmProxy extends some.test.NullTypes implements RealmObjectProxy, NullTypesRealmProxyInterface { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index af7a9d3b4d..a9a24e1a83 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -29,6 +29,7 @@ import org.json.JSONException; import org.json.JSONObject; +@SuppressWarnings("all") public class SimpleRealmProxy extends some.test.Simple implements RealmObjectProxy, SimpleRealmProxyInterface { diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 8e72d79a99..b137dbb1a3 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -65,6 +65,16 @@ android { } } } + + buildTypes { + debug { + javaCompileOptions { + annotationProcessorOptions { + arguments += [ 'realm.suppressWarnings' : 'false' ] + } + } + } + } } externalNativeBuild { From e6238004387aba46c601eaa1e5dfedcd696bbb88 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Thu, 15 Jun 2017 10:26:11 +0200 Subject: [PATCH 0748/2110] Clean up the Proxies a bit, in prep for RealmInteger mods (#4770) * Clean up the Proxies a bit, in prep for RealmInteger mods --- .../processor/RealmProxyClassGenerator.java | 290 +++---- .../io/realm/AllTypesRealmProxy.java | 482 +++++------ .../io/realm/BooleansRealmProxy.java | 151 ++-- .../io/realm/NullTypesRealmProxy.java | 757 +++++++++--------- .../resources/io/realm/SimpleRealmProxy.java | 135 ++-- 5 files changed, 921 insertions(+), 894 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index fedbb58b78..61b7f10fa0 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -25,6 +25,7 @@ import java.util.Collection; import java.util.Collections; import java.util.EnumSet; +import java.util.List; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; @@ -39,6 +40,37 @@ public class RealmProxyClassGenerator { private static final String OPTION_SUPPRESS_WARNINGS = "realm.suppressWarnings"; private static final String BACKLINKS_FIELD_EXTENSION = "Backlinks"; + private static final List IMPORTS; + static { + List l = Arrays.asList( + "android.annotation.TargetApi", + "android.os.Build", + "android.util.JsonReader", + "android.util.JsonToken", + "io.realm.exceptions.RealmMigrationNeededException", + "io.realm.internal.ColumnInfo", + "io.realm.internal.LinkView", + "io.realm.internal.OsObject", + "io.realm.internal.RealmObjectProxy", + "io.realm.internal.Row", + "io.realm.internal.SharedRealm", + "io.realm.internal.Table", + "io.realm.internal.android.JsonUtils", + "io.realm.log.RealmLog", + "java.io.IOException", + "java.util.ArrayList", + "java.util.Collections", + "java.util.List", + "java.util.Iterator", + "java.util.Date", + "java.util.Map", + "java.util.HashMap", + "org.json.JSONObject", + "org.json.JSONException", + "org.json.JSONArray"); + IMPORTS = Collections.unmodifiableList(l); + } + private final ProcessingEnvironment processingEnvironment; private final ClassMetaData metadata; private final String simpleClassName; @@ -71,38 +103,10 @@ public void generate() throws IOException, UnsupportedOperationException { writer.emitPackage(Constants.REALM_PACKAGE_NAME) .emitEmptyLine(); - ArrayList imports = new ArrayList(); - imports.add("android.annotation.TargetApi"); - imports.add("android.os.Build"); - imports.add("android.util.JsonReader"); - imports.add("android.util.JsonToken"); - imports.add("io.realm.RealmObjectSchema"); - imports.add("io.realm.RealmSchema"); - imports.add("io.realm.exceptions.RealmMigrationNeededException"); - imports.add("io.realm.internal.ColumnInfo"); - imports.add("io.realm.internal.RealmObjectProxy"); - imports.add("io.realm.internal.Row"); - imports.add("io.realm.internal.Table"); - imports.add("io.realm.internal.OsObject"); - imports.add("io.realm.internal.SharedRealm"); + List imports = new ArrayList(IMPORTS); if (!metadata.getBacklinkFields().isEmpty()) { imports.add("io.realm.internal.UncheckedRow"); } - imports.add("io.realm.internal.LinkView"); - imports.add("io.realm.internal.android.JsonUtils"); - imports.add("io.realm.log.RealmLog"); - imports.add("java.io.IOException"); - imports.add("java.util.ArrayList"); - imports.add("java.util.Collections"); - imports.add("java.util.List"); - imports.add("java.util.Iterator"); - imports.add("java.util.Date"); - imports.add("java.util.Map"); - imports.add("java.util.HashMap"); - imports.add("org.json.JSONObject"); - imports.add("org.json.JSONException"); - imports.add("org.json.JSONArray"); - writer.emitImports(imports) .emitEmptyLine(); @@ -177,7 +181,7 @@ private void emitColumnInfoClass(JavaWriter writer) throws IOException { } for (Backlink backlink : metadata.getBacklinkFields()) { writer.emitStatement( - "addBacklinkDetails(realm, \"%1$s\", \"%2$s\", \"%3$s\")", + "addBacklinkDetails(realm, \"%s\", \"%s\", \"%s\")", backlink.getTargetField(), Utils.stripPackage(backlink.getSourceClass()), backlink.getSourceField()); } writer.endConstructor() @@ -442,7 +446,7 @@ public void emit(JavaWriter writer) throws IOException { .beginControlFlow("if (!(RealmObject.isManaged(value) && RealmObject.isValid(value)))") .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") .endControlFlow() - .beginControlFlow("if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") + .beginControlFlow("if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") .endControlFlow() .emitStatement("proxyState.getRow$realm().setLink(%s, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex())", fieldIndexVariableReference(field)) @@ -515,10 +519,10 @@ public void emit(JavaWriter writer) throws IOException { .beginControlFlow("if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject)))") .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must be a valid managed object.\")") .endControlFlow() - .beginControlFlow("if (((RealmObjectProxy)linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") + .beginControlFlow("if (((RealmObjectProxy) linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must belong to the same Realm.\")") .endControlFlow() - .emitStatement("links.add(((RealmObjectProxy)linkedObject).realmGet$proxyState().getRow$realm().getIndex())") + .emitStatement("links.add(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex())") .endControlFlow() .endMethod(); } @@ -609,7 +613,10 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers "RealmSchema", "realmSchema"); // Argument type & argument name - writer.beginControlFlow("if (!realmSchema.contains(\"" + this.simpleClassName + "\"))"); + writer.beginControlFlow("if (realmSchema.contains(\"%s\"))", this.simpleClassName) + .emitStatement("return realmSchema.get(\"%s\")", this.simpleClassName) + .endControlFlow(); + writer.emitStatement("RealmObjectSchema realmObjectSchema = realmSchema.create(\"%s\")", this.simpleClassName); // For each field generate corresponding table index constant @@ -653,8 +660,6 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep } } writer.emitStatement("return realmObjectSchema"); - writer.endControlFlow(); - writer.emitStatement("return realmSchema.get(\"" + this.simpleClassName + "\")"); writer.endMethod() .emitEmptyLine(); } @@ -771,8 +776,7 @@ private void emitValidateRealmType(JavaWriter writer, VariableElement field, Str "Either remove field or migrate using io.realm.internal.Table.addColumn()." + "\")", fieldName); writer.endControlFlow(); - writer.beginControlFlow("if (columnTypes.get(\"%s\") != %s)", - fieldName, getRealmTypeChecked(field).getRealmType()); + writer.beginControlFlow("if (columnTypes.get(\"%s\") != %s)", fieldName, getRealmTypeChecked(field).getRealmType()); emitMigrationNeededException(writer, "\"Invalid type '%s' for field '%s' in existing Realm file.\")", Utils.getFieldTypeSimpleName(field), fieldName); writer.endControlFlow(); @@ -956,7 +960,7 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { // If object is already in the Realm there is nothing to update writer - .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") + .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") .emitStatement("return object") .endControlFlow(); @@ -965,7 +969,8 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer.emitStatement("RealmObjectProxy cachedRealmObject = cache.get(object)"); writer.beginControlFlow("if (cachedRealmObject != null)") .emitStatement("return (%s) cachedRealmObject", qualifiedClassName) - .nextControlFlow("else"); + .endControlFlow() + .emitEmptyLine(); if (!metadata.hasPrimaryKey()) { writer.emitStatement("return copy(realm, object, update, cache)"); @@ -1032,7 +1037,6 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { .endControlFlow(); } - writer.endControlFlow(); writer.endMethod() .emitEmptyLine(); } @@ -1044,14 +1048,14 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam || "int".equals(fieldType) || "short".equals(fieldType) || "byte".equals(fieldType)) { - writer.emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); + writer.emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter); } else if ("java.lang.Long".equals(fieldType) || "java.lang.Integer".equals(fieldType) || "java.lang.Short".equals(fieldType) || "java.lang.Byte".equals(fieldType)) { writer - .emitStatement("Number %s = ((%s)object).%s()", getter, interfaceName, getter) + .emitStatement("Number %s = ((%s) object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) .emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter); if (isUpdate) { @@ -1061,11 +1065,11 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer.endControlFlow(); } else if ("double".equals(fieldType)) { - writer.emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); + writer.emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter); } else if ("java.lang.Double".equals(fieldType)) { writer - .emitStatement("Double %s = ((%s)object).%s()", getter, interfaceName, getter) + .emitStatement("Double %s = ((%s) object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) .emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { @@ -1075,11 +1079,11 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer.endControlFlow(); } else if ("float".equals(fieldType)) { - writer.emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); + writer.emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter); } else if ("java.lang.Float".equals(fieldType)) { writer - .emitStatement("Float %s = ((%s)object).%s()", getter, interfaceName, getter) + .emitStatement("Float %s = ((%s) object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) .emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { @@ -1089,11 +1093,11 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam writer.endControlFlow(); } else if ("boolean".equals(fieldType)) { - writer.emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s)object).%s(), false)", fieldName, interfaceName, getter); + writer.emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter); } else if ("java.lang.Boolean".equals(fieldType)) { writer - .emitStatement("Boolean %s = ((%s)object).%s()", getter, interfaceName, getter) + .emitStatement("Boolean %s = ((%s) object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) .emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { @@ -1104,7 +1108,7 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam } else if ("byte[]".equals(fieldType)) { writer - .emitStatement("byte[] %s = ((%s)object).%s()", getter, interfaceName, getter) + .emitStatement("byte[] %s = ((%s) object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) .emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { @@ -1116,7 +1120,7 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam } else if ("java.util.Date".equals(fieldType)) { writer - .emitStatement("java.util.Date %s = ((%s)object).%s()", getter, interfaceName, getter) + .emitStatement("java.util.Date %s = ((%s) object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) .emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.getTime(), false)", fieldName, getter); if (isUpdate) { @@ -1127,7 +1131,7 @@ private void setTableValues(JavaWriter writer, String fieldType, String fieldNam } else if ("java.lang.String".equals(fieldType)) { writer - .emitStatement("String %s = ((%s)object).%s()", getter, interfaceName, getter) + .emitStatement("String %s = ((%s) object).%s()", getter, interfaceName, getter) .beginControlFlow("if (%s != null)", getter) .emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); if (isUpdate) { @@ -1151,8 +1155,8 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { // If object is already in the Realm there is nothing to update writer - .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") - .emitStatement("return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()") + .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") + .emitStatement("return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()") .endControlFlow(); writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); @@ -1235,10 +1239,12 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { writer.beginControlFlow("while (objects.hasNext())") .emitStatement("object = (%s) objects.next()", qualifiedClassName); - writer.beginControlFlow("if(!cache.containsKey(object))"); + writer.beginControlFlow("if (cache.containsKey(object))") + .emitStatement("continue") + .endControlFlow(); - writer.beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))"); - writer.emitStatement("cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex())") + writer.beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))"); + writer.emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex())") .emitStatement("continue"); writer.endControlFlow(); @@ -1279,8 +1285,7 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { .endControlFlow() .emitStatement("LinkView.nativeAdd(%1$sNativeLinkViewPtr, cacheItemIndex%1$s)", fieldName) .endControlFlow() - .endControlFlow() - .emitEmptyLine(); + .endControlFlow(); } else { if (metadata.getPrimaryKey() != field) { @@ -1290,7 +1295,6 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { } //@formatter:on - writer.endControlFlow(); writer.endControlFlow(); writer.endMethod(); writer.emitEmptyLine(); @@ -1306,8 +1310,8 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { // If object is already in the Realm there is nothing to update writer - .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") - .emitStatement("return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()") + .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") + .emitStatement("return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()") .endControlFlow(); writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); @@ -1394,10 +1398,12 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException writer.beginControlFlow("while (objects.hasNext())"); writer.emitStatement("object = (%s) objects.next()", qualifiedClassName); - writer.beginControlFlow("if(!cache.containsKey(object))"); + writer.beginControlFlow("if (cache.containsKey(object))") + .emitStatement("continue") + .endControlFlow(); - writer.beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))"); - writer.emitStatement("cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex())") + writer.beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))"); + writer.emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex())") .emitStatement("continue"); writer.endControlFlow(); addPrimaryKeyCheckIfNeeded(metadata, false, writer); @@ -1451,7 +1457,6 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException //@formatter:on } writer.endControlFlow(); - writer.endControlFlow(); writer.endMethod(); writer.emitEmptyLine(); @@ -1529,9 +1534,11 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { writer.emitStatement("RealmObjectProxy cachedRealmObject = cache.get(newObject)"); writer.beginControlFlow("if (cachedRealmObject != null)") .emitStatement("return (%s) cachedRealmObject", qualifiedClassName) - .nextControlFlow("else"); + .endControlFlow(); + - writer.emitSingleLineComment("rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields."); + writer.emitEmptyLine() + .emitSingleLineComment("rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields."); if (metadata.hasPrimaryKey()) { writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, ((%s) newObject).%s(), false, Collections.emptyList())", qualifiedClassName, qualifiedClassName, interfaceName, metadata.getPrimaryKeyGetter()); @@ -1540,6 +1547,12 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { qualifiedClassName, qualifiedClassName); } writer.emitStatement("cache.put(newObject, (RealmObjectProxy) realmObject)"); + + writer.emitEmptyLine() + .emitStatement("%1$s realmObjectSource = (%1$s) newObject", interfaceName) + .emitStatement("%1$s realmObjectCopy = (%1$s) realmObject", interfaceName); + + writer.emitEmptyLine(); for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String fieldType = field.asType().toString(); @@ -1553,54 +1566,47 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { //@formatter:off if (Utils.isRealmModel(field)) { - writer - .emitEmptyLine() - .emitStatement("%s %sObj = ((%s) newObject).%s()", fieldType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("((%s) realmObject).%s(cache%s)", interfaceName, setter, fieldName) - .nextControlFlow("else") - .emitStatement("((%s) realmObject).%s(%s.copyOrUpdate(realm, %sObj, update, cache))", - interfaceName, - setter, - Utils.getProxyClassSimpleName(field), - fieldName) - .endControlFlow() + writer.emitEmptyLine() + .emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) + .beginControlFlow("if (%sObj == null)", fieldName) + .emitStatement("realmObjectCopy.%s(null)", setter) .nextControlFlow("else") + .emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) + .beginControlFlow("if (cache%s != null)", fieldName) + .emitStatement("realmObjectCopy.%s(cache%s)", setter, fieldName) + .nextControlFlow("else") + .emitStatement("realmObjectCopy.%s(%s.copyOrUpdate(realm, %sObj, update, cache))", + setter, Utils.getProxyClassSimpleName(field), fieldName) + .endControlFlow() // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .emitStatement("((%s) realmObject).%s(null)", interfaceName, setter) .endControlFlow(); } else if (Utils.isRealmList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); - writer - .emitEmptyLine() - .emitStatement("RealmList<%s> %sList = ((%s) newObject).%s()", - genericType, fieldName, interfaceName, getter) + writer.emitEmptyLine() + .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("RealmList<%s> %sRealmList = ((%s) realmObject).%s()", - genericType, fieldName, interfaceName, getter) - .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - .emitStatement("%s %sItem = %sList.get(i)", genericType, fieldName, fieldName) - .emitStatement("%s cache%s = (%s) cache.get(%sItem)", genericType, fieldName, genericType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%sRealmList.add(cache%s)", fieldName, fieldName) - .nextControlFlow("else") - .emitStatement("%sRealmList.add(%s.copyOrUpdate(realm, %sList.get(i), update, cache))", fieldName, Utils.getProxyClassSimpleName(field), fieldName) - .endControlFlow() - .endControlFlow() + .emitStatement("RealmList<%s> %sRealmList = realmObjectCopy.%s()", + genericType, fieldName, getter) + .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) + .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) + .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) + .beginControlFlow("if (cache%s != null)", fieldName) + .emitStatement("%1$sRealmList.add(cache%1$s)", fieldName) + .nextControlFlow("else") + .emitStatement("%1$sRealmList.add(%2$s.copyOrUpdate(realm, %1$sItem, update, cache))", + fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .endControlFlow() .endControlFlow() .emitEmptyLine(); } else { - writer.emitStatement("((%s) realmObject).%s(((%s) newObject).%s())", - interfaceName, setter, interfaceName, getter); + writer.emitStatement("realmObjectCopy.%s(realmObjectSource.%s())", setter, getter); } //@formatter:on } writer.emitStatement("return realmObject"); - writer.endControlFlow(); writer.endMethod(); writer.emitEmptyLine(); } @@ -1618,19 +1624,21 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException .endControlFlow() .emitStatement("CacheData cachedObject = cache.get(realmObject)") .emitStatement("%s unmanagedObject", qualifiedClassName) - .beginControlFlow("if (cachedObject != null)") + .beginControlFlow("if (cachedObject == null)") + .emitStatement("unmanagedObject = new %s()", qualifiedClassName) + .emitStatement("cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject))") + .nextControlFlow("else") .emitSingleLineComment("Reuse cached object or recreate it because it was encountered at a lower depth.") .beginControlFlow("if (currentDepth >= cachedObject.minDepth)") - .emitStatement("return (%s)cachedObject.object", qualifiedClassName) - .nextControlFlow("else") - .emitStatement("unmanagedObject = (%s)cachedObject.object", qualifiedClassName) - .emitStatement("cachedObject.minDepth = currentDepth") + .emitStatement("return (%s) cachedObject.object", qualifiedClassName) .endControlFlow() - .nextControlFlow("else") - .emitStatement("unmanagedObject = new %s()", qualifiedClassName) - .emitStatement("cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject))") + .emitStatement("unmanagedObject = (%s) cachedObject.object", qualifiedClassName) + .emitStatement("cachedObject.minDepth = currentDepth") .endControlFlow(); + // may cause an unused variable warning if the object contains only null lists + writer.emitStatement("%1$s unmanagedCopy = (%1$s) unmanagedObject", interfaceName); + writer.emitStatement("%1$s realmSource = (%1$s) realmObject", interfaceName); for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String setter = metadata.getInternalSetter(fieldName); @@ -1640,19 +1648,19 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException writer .emitEmptyLine() .emitSingleLineComment("Deep copy of %s", fieldName) - .emitStatement("((%s) unmanagedObject).%s(%s.createDetachedCopy(((%s) realmObject).%s(), currentDepth + 1, maxDepth, cache))", - interfaceName, setter, Utils.getProxyClassSimpleName(field), interfaceName, getter); + .emitStatement("unmanagedCopy.%s(%s.createDetachedCopy(realmSource.%s(), currentDepth + 1, maxDepth, cache))", + setter, Utils.getProxyClassSimpleName(field), getter); } else if (Utils.isRealmList(field)) { writer .emitEmptyLine() .emitSingleLineComment("Deep copy of %s", fieldName) .beginControlFlow("if (currentDepth == maxDepth)") - .emitStatement("((%s) unmanagedObject).%s(null)", interfaceName, setter) + .emitStatement("unmanagedCopy.%s(null)", setter) .nextControlFlow("else") - .emitStatement("RealmList<%s> managed%sList = ((%s) realmObject).%s()", - Utils.getGenericTypeQualifiedName(field), fieldName, interfaceName, getter) + .emitStatement("RealmList<%s> managed%sList = realmSource.%s()", + Utils.getGenericTypeQualifiedName(field), fieldName, getter) .emitStatement("RealmList<%1$s> unmanaged%2$sList = new RealmList<%1$s>()", Utils.getGenericTypeQualifiedName(field), fieldName) - .emitStatement("((%s) unmanagedObject).%s(unmanaged%sList)", interfaceName, setter, fieldName) + .emitStatement("unmanagedCopy.%s(unmanaged%sList)", setter, fieldName) .emitStatement("int nextDepth = currentDepth + 1") .emitStatement("int size = managed%sList.size()", fieldName) .beginControlFlow("for (int i = 0; i < size; i++)") @@ -1662,8 +1670,7 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException .endControlFlow() .endControlFlow(); } else { - writer.emitStatement("((%s) unmanagedObject).%s(((%s) realmObject).%s())", - interfaceName, setter, interfaceName, getter); + writer.emitStatement("unmanagedCopy.%s(realmSource.%s())", setter, getter); } } @@ -1684,6 +1691,10 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { EnumSet.of(Modifier.STATIC), // Modifiers "Realm", "realm", qualifiedClassName, "realmObject", qualifiedClassName, "newObject", "Map", "cache"); // Argument type & argument name + writer + .emitStatement("%1$s realmObjectTarget = (%1$s) realmObject", interfaceName) + .emitStatement("%1$s realmObjectSource = (%1$s) newObject", interfaceName); + for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); String setter = metadata.getInternalSetter(fieldName); @@ -1691,50 +1702,45 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { //@formatter:off if (Utils.isRealmModel(field)) { writer - .emitStatement("%s %sObj = ((%s) newObject).%s()", - Utils.getFieldTypeQualifiedName(field), fieldName, interfaceName, getter) - .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("%s cache%s = (%s) cache.get(%sObj)", Utils.getFieldTypeQualifiedName(field), fieldName, Utils.getFieldTypeQualifiedName(field), fieldName) + .emitStatement("%s %sObj = realmObjectSource.%s()", + Utils.getFieldTypeQualifiedName(field), fieldName, getter) + .beginControlFlow("if (%sObj == null)", fieldName) + .emitStatement("realmObjectTarget.%s(null)", setter) + .nextControlFlow("else") + .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sObj)", + Utils.getFieldTypeQualifiedName(field), fieldName) .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("((%s) realmObject).%s(cache%s)", interfaceName, setter, fieldName) + .emitStatement("realmObjectTarget.%s(cache%s)", setter, fieldName) .nextControlFlow("else") - .emitStatement("((%s) realmObject).%s(%s.copyOrUpdate(realm, %sObj, true, cache))", - interfaceName, - setter, - Utils.getProxyClassSimpleName(field), - fieldName - ) + .emitStatement("realmObjectTarget.%s(%s.copyOrUpdate(realm, %sObj, true, cache))", + setter, Utils.getProxyClassSimpleName(field), fieldName) .endControlFlow() - .nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .emitStatement("((%s) realmObject).%s(null)", interfaceName, setter) .endControlFlow(); } else if (Utils.isRealmList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer - .emitStatement("RealmList<%s> %sList = ((%s) newObject).%s()", - genericType, fieldName, interfaceName, getter) - .emitStatement("RealmList<%s> %sRealmList = ((%s) realmObject).%s()", - genericType, fieldName, interfaceName, getter) + .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) + .emitStatement("RealmList<%s> %sRealmList = realmObjectTarget.%s()", + genericType, fieldName, getter) .emitStatement("%sRealmList.clear()", fieldName) .beginControlFlow("if (%sList != null)", fieldName) .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - .emitStatement("%s %sItem = %sList.get(i)", genericType, fieldName, fieldName) - .emitStatement("%s cache%s = (%s) cache.get(%sItem)", genericType, fieldName, genericType, fieldName) + .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) + .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%sRealmList.add(cache%s)", fieldName, fieldName) + .emitStatement("%1$sRealmList.add(cache%1$s)", fieldName) .nextControlFlow("else") - .emitStatement("%sRealmList.add(%s.copyOrUpdate(realm, %sList.get(i), true, cache))", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + .emitStatement("%1$sRealmList.add(%2$s.copyOrUpdate(realm, %1$sItem, true, cache))", + fieldName, Utils.getProxyClassSimpleName(field)) .endControlFlow() .endControlFlow() .endControlFlow(); } else { - if (field == metadata.getPrimaryKey()) { - continue; + if (field != metadata.getPrimaryKey()) { + writer.emitStatement("realmObjectTarget.%s(realmObjectSource.%s())", setter, getter); } - writer.emitStatement("((%s) realmObject).%s(((%s) newObject).%s())", - interfaceName, setter, interfaceName, getter); } //@formatter:on } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index d033482722..2840c67b83 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -5,8 +5,6 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; -import io.realm.RealmObjectSchema; -import io.realm.RealmSchema; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -32,7 +30,7 @@ @SuppressWarnings("all") public class AllTypesRealmProxy extends some.test.AllTypes - implements RealmObjectProxy, AllTypesRealmProxyInterface { + implements RealmObjectProxy, AllTypesRealmProxyInterface { static final class AllTypesColumnInfo extends ColumnInfo { long columnStringIndex; @@ -329,7 +327,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (!(RealmObject.isManaged(value) && RealmObject.isValid(value))) { throw new IllegalArgumentException("'value' is not a valid managed object."); } - if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { + if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { throw new IllegalArgumentException("'value' belongs to a different Realm."); } proxyState.getRow$realm().setLink(columnInfo.columnObjectIndex, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex()); @@ -381,10 +379,10 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject))) { throw new IllegalArgumentException("Each element of 'value' must be a valid managed object."); } - if (((RealmObjectProxy)linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { + if (((RealmObjectProxy) linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { throw new IllegalArgumentException("Each element of 'value' must belong to the same Realm."); } - links.add(((RealmObjectProxy)linkedObject).realmGet$proxyState().getRow$realm().getIndex()); + links.add(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex()); } } @@ -400,26 +398,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { + if (realmSchema.contains("AllTypes")) { + return realmSchema.get("AllTypes"); + } + RealmObjectSchema realmObjectSchema = realmSchema.create("AllTypes"); + realmObjectSchema.add("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnDouble", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); if (!realmSchema.contains("AllTypes")) { - RealmObjectSchema realmObjectSchema = realmSchema.create("AllTypes"); - realmObjectSchema.add("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnDouble", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - if (!realmSchema.contains("AllTypes")) { - AllTypesRealmProxy.createRealmObjectSchema(realmSchema); - } - realmObjectSchema.add("columnObject", RealmFieldType.OBJECT, realmSchema.get("AllTypes")); - if (!realmSchema.contains("AllTypes")) { - AllTypesRealmProxy.createRealmObjectSchema(realmSchema); - } - realmObjectSchema.add("columnRealmList", RealmFieldType.LIST, realmSchema.get("AllTypes")); - return realmObjectSchema; - } - return realmSchema.get("AllTypes"); + AllTypesRealmProxy.createRealmObjectSchema(realmSchema); + } + realmObjectSchema.add("columnObject", RealmFieldType.OBJECT, realmSchema.get("AllTypes")); + if (!realmSchema.contains("AllTypes")) { + AllTypesRealmProxy.createRealmObjectSchema(realmSchema); + } + realmObjectSchema.add("columnRealmList", RealmFieldType.LIST, realmSchema.get("AllTypes")); + return realmObjectSchema; } public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { @@ -580,7 +578,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = new ArrayList(2); some.test.AllTypes obj = null; if (update) { @@ -692,7 +690,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { boolean jsonHasPrimaryKey = false; some.test.AllTypes obj = new some.test.AllTypes(); reader.beginObject(); @@ -791,95 +789,99 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (some.test.AllTypes) cachedRealmObject; - } else { - some.test.AllTypes realmObject = null; - boolean canUpdate = update; - if (canUpdate) { - Table table = realm.getTable(some.test.AllTypes.class); - long pkColumnIndex = table.getPrimaryKey(); - String value = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = Table.NO_MATCH; - if (value == null) { - rowIndex = table.findFirstNull(pkColumnIndex); - } else { - rowIndex = table.findFirstString(pkColumnIndex, value); - } - if (rowIndex != Table.NO_MATCH) { - try { - objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.schema.getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); - realmObject = new io.realm.AllTypesRealmProxy(); - cache.put(object, (RealmObjectProxy) realmObject); - } finally { - objectContext.clear(); - } - } else { - canUpdate = false; - } - } + } - if (canUpdate) { - return update(realm, realmObject, object, cache); + some.test.AllTypes realmObject = null; + boolean canUpdate = update; + if (canUpdate) { + Table table = realm.getTable(some.test.AllTypes.class); + long pkColumnIndex = table.getPrimaryKey(); + String value = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); + long rowIndex = Table.NO_MATCH; + if (value == null) { + rowIndex = table.findFirstNull(pkColumnIndex); + } else { + rowIndex = table.findFirstString(pkColumnIndex, value); + } + if (rowIndex != Table.NO_MATCH) { + try { + objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.schema.getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); + realmObject = new io.realm.AllTypesRealmProxy(); + cache.put(object, (RealmObjectProxy) realmObject); + } finally { + objectContext.clear(); + } } else { - return copy(realm, object, update, cache); + canUpdate = false; } } + + if (canUpdate) { + return update(realm, realmObject, object, cache); + } else { + return copy(realm, object, update, cache); + } } public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, boolean update, Map cache) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.AllTypes) cachedRealmObject; + } + + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.AllTypes realmObject = realm.createObjectInternal(some.test.AllTypes.class, ((AllTypesRealmProxyInterface) newObject).realmGet$columnString(), false, Collections.emptyList()); + cache.put(newObject, (RealmObjectProxy) realmObject); + + AllTypesRealmProxyInterface realmObjectSource = (AllTypesRealmProxyInterface) newObject; + AllTypesRealmProxyInterface realmObjectCopy = (AllTypesRealmProxyInterface) realmObject; + + realmObjectCopy.realmSet$columnLong(realmObjectSource.realmGet$columnLong()); + realmObjectCopy.realmSet$columnFloat(realmObjectSource.realmGet$columnFloat()); + realmObjectCopy.realmSet$columnDouble(realmObjectSource.realmGet$columnDouble()); + realmObjectCopy.realmSet$columnBoolean(realmObjectSource.realmGet$columnBoolean()); + realmObjectCopy.realmSet$columnDate(realmObjectSource.realmGet$columnDate()); + realmObjectCopy.realmSet$columnBinary(realmObjectSource.realmGet$columnBinary()); + + some.test.AllTypes columnObjectObj = realmObjectSource.realmGet$columnObject(); + if (columnObjectObj == null) { + realmObjectCopy.realmSet$columnObject(null); } else { - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.AllTypes realmObject = realm.createObjectInternal(some.test.AllTypes.class, ((AllTypesRealmProxyInterface) newObject).realmGet$columnString(), false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnLong(((AllTypesRealmProxyInterface) newObject).realmGet$columnLong()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnFloat(((AllTypesRealmProxyInterface) newObject).realmGet$columnFloat()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnDouble(((AllTypesRealmProxyInterface) newObject).realmGet$columnDouble()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnBoolean(((AllTypesRealmProxyInterface) newObject).realmGet$columnBoolean()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnDate(((AllTypesRealmProxyInterface) newObject).realmGet$columnDate()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnBinary(((AllTypesRealmProxyInterface) newObject).realmGet$columnBinary()); - - some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) newObject).realmGet$columnObject(); - if (columnObjectObj != null) { - some.test.AllTypes cachecolumnObject = (some.test.AllTypes) cache.get(columnObjectObj); - if (cachecolumnObject != null) { - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnObject(cachecolumnObject); - } else { - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnObject(AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, update, cache)); - } + some.test.AllTypes cachecolumnObject = (some.test.AllTypes) cache.get(columnObjectObj); + if (cachecolumnObject != null) { + realmObjectCopy.realmSet$columnObject(cachecolumnObject); } else { - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnObject(null); + realmObjectCopy.realmSet$columnObject(AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, update, cache)); } + } - RealmList columnRealmListList = ((AllTypesRealmProxyInterface) newObject).realmGet$columnRealmList(); - if (columnRealmListList != null) { - RealmList columnRealmListRealmList = ((AllTypesRealmProxyInterface) realmObject).realmGet$columnRealmList(); - for (int i = 0; i < columnRealmListList.size(); i++) { - some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); - some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); - if (cachecolumnRealmList != null) { - columnRealmListRealmList.add(cachecolumnRealmList); - } else { - columnRealmListRealmList.add(AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListList.get(i), update, cache)); - } + RealmList columnRealmListList = realmObjectSource.realmGet$columnRealmList(); + if (columnRealmListList != null) { + RealmList columnRealmListRealmList = realmObjectCopy.realmGet$columnRealmList(); + for (int i = 0; i < columnRealmListList.size(); i++) { + some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); + some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); + if (cachecolumnRealmList != null) { + columnRealmListRealmList.add(cachecolumnRealmList); + } else { + columnRealmListRealmList.add(AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, update, cache)); } } - - return realmObject; } + + return realmObject; } public static long insert(Realm realm, some.test.AllTypes object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); } Table table = realm.getTable(some.test.AllTypes.class); long tableNativePtr = table.getNativePtr(); @@ -898,15 +900,15 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M some.test.AllTypes object = null; while (objects.hasNext()) { object = (some.test.AllTypes) objects.next(); - if(!cache.containsKey(object)) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); - continue; - } - String primaryKeyValue = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = Table.NO_MATCH; - if (primaryKeyValue == null) { - rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); - } else { - rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); - } - if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue); - } else { - Table.throwDuplicatePrimaryKeyException(primaryKeyValue); - } - cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnBoolean(), false); - java.util.Date realmGet$columnDate = ((AllTypesRealmProxyInterface)object).realmGet$columnDate(); - if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); - } - byte[] realmGet$columnBinary = ((AllTypesRealmProxyInterface)object).realmGet$columnBinary(); - if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); - } + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + String primaryKeyValue = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); + long rowIndex = Table.NO_MATCH; + if (primaryKeyValue == null) { + rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); + } else { + rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); + } + if (rowIndex == Table.NO_MATCH) { + rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue); + } else { + Table.throwDuplicatePrimaryKeyException(primaryKeyValue); + } + cache.put(object, rowIndex); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + java.util.Date realmGet$columnDate = ((AllTypesRealmProxyInterface) object).realmGet$columnDate(); + if (realmGet$columnDate != null) { + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); + } + byte[] realmGet$columnBinary = ((AllTypesRealmProxyInterface) object).realmGet$columnBinary(); + if (realmGet$columnBinary != null) { + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); + } - some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) object).realmGet$columnObject(); - if (columnObjectObj != null) { - Long cachecolumnObject = cache.get(columnObjectObj); - if (cachecolumnObject == null) { - cachecolumnObject = AllTypesRealmProxy.insert(realm, columnObjectObj, cache); - } - table.setLink(columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); + some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) object).realmGet$columnObject(); + if (columnObjectObj != null) { + Long cachecolumnObject = cache.get(columnObjectObj); + if (cachecolumnObject == null) { + cachecolumnObject = AllTypesRealmProxy.insert(realm, columnObjectObj, cache); } + table.setLink(columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); + } - RealmList columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); - if (columnRealmListList != null) { - long columnRealmListNativeLinkViewPtr = Table.nativeGetLinkView(tableNativePtr, columnInfo.columnRealmListIndex, rowIndex); - for (some.test.AllTypes columnRealmListItem : columnRealmListList) { - Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); - if (cacheItemIndexcolumnRealmList == null) { - cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insert(realm, columnRealmListItem, cache); - } - LinkView.nativeAdd(columnRealmListNativeLinkViewPtr, cacheItemIndexcolumnRealmList); + RealmList columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); + if (columnRealmListList != null) { + long columnRealmListNativeLinkViewPtr = Table.nativeGetLinkView(tableNativePtr, columnInfo.columnRealmListIndex, rowIndex); + for (some.test.AllTypes columnRealmListItem : columnRealmListList) { + Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); + if (cacheItemIndexcolumnRealmList == null) { + cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insert(realm, columnRealmListItem, cache); } + LinkView.nativeAdd(columnRealmListNativeLinkViewPtr, cacheItemIndexcolumnRealmList); } - } } } public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); } Table table = realm.getTable(some.test.AllTypes.class); long tableNativePtr = table.getNativePtr(); @@ -1018,17 +1020,17 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob some.test.AllTypes object = null; while (objects.hasNext()) { object = (some.test.AllTypes) objects.next(); - if(!cache.containsKey(object)) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); - continue; - } - String primaryKeyValue = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = Table.NO_MATCH; - if (primaryKeyValue == null) { - rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); - } else { - rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); - } - if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue); - } - cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface)object).realmGet$columnBoolean(), false); - java.util.Date realmGet$columnDate = ((AllTypesRealmProxyInterface)object).realmGet$columnDate(); - if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnDateIndex, rowIndex, false); - } - byte[] realmGet$columnBinary = ((AllTypesRealmProxyInterface)object).realmGet$columnBinary(); - if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, false); - } + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + String primaryKeyValue = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); + long rowIndex = Table.NO_MATCH; + if (primaryKeyValue == null) { + rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); + } else { + rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); + } + if (rowIndex == Table.NO_MATCH) { + rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue); + } + cache.put(object, rowIndex); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + java.util.Date realmGet$columnDate = ((AllTypesRealmProxyInterface) object).realmGet$columnDate(); + if (realmGet$columnDate != null) { + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.columnDateIndex, rowIndex, false); + } + byte[] realmGet$columnBinary = ((AllTypesRealmProxyInterface) object).realmGet$columnBinary(); + if (realmGet$columnBinary != null) { + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, false); + } - some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) object).realmGet$columnObject(); - if (columnObjectObj != null) { - Long cachecolumnObject = cache.get(columnObjectObj); - if (cachecolumnObject == null) { - cachecolumnObject = AllTypesRealmProxy.insertOrUpdate(realm, columnObjectObj, cache); - } - Table.nativeSetLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); - } else { - Table.nativeNullifyLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex); + some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) object).realmGet$columnObject(); + if (columnObjectObj != null) { + Long cachecolumnObject = cache.get(columnObjectObj); + if (cachecolumnObject == null) { + cachecolumnObject = AllTypesRealmProxy.insertOrUpdate(realm, columnObjectObj, cache); } + Table.nativeSetLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); + } else { + Table.nativeNullifyLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex); + } - long columnRealmListNativeLinkViewPtr = Table.nativeGetLinkView(tableNativePtr, columnInfo.columnRealmListIndex, rowIndex); - LinkView.nativeClear(columnRealmListNativeLinkViewPtr); - RealmList columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); - if (columnRealmListList != null) { - for (some.test.AllTypes columnRealmListItem : columnRealmListList) { - Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); - if (cacheItemIndexcolumnRealmList == null) { - cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); - } - LinkView.nativeAdd(columnRealmListNativeLinkViewPtr, cacheItemIndexcolumnRealmList); + long columnRealmListNativeLinkViewPtr = Table.nativeGetLinkView(tableNativePtr, columnInfo.columnRealmListIndex, rowIndex); + LinkView.nativeClear(columnRealmListNativeLinkViewPtr); + RealmList columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); + if (columnRealmListList != null) { + for (some.test.AllTypes columnRealmListItem : columnRealmListList) { + Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); + if (cacheItemIndexcolumnRealmList == null) { + cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); } + LinkView.nativeAdd(columnRealmListNativeLinkViewPtr, cacheItemIndexcolumnRealmList); } - } + } } @@ -1137,36 +1140,37 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje } CacheData cachedObject = cache.get(realmObject); some.test.AllTypes unmanagedObject; - if (cachedObject != null) { + if (cachedObject == null) { + unmanagedObject = new some.test.AllTypes(); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + } else { // Reuse cached object or recreate it because it was encountered at a lower depth. if (currentDepth >= cachedObject.minDepth) { - return (some.test.AllTypes)cachedObject.object; - } else { - unmanagedObject = (some.test.AllTypes)cachedObject.object; - cachedObject.minDepth = currentDepth; + return (some.test.AllTypes) cachedObject.object; } - } else { - unmanagedObject = new some.test.AllTypes(); - cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); - } - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnString(((AllTypesRealmProxyInterface) realmObject).realmGet$columnString()); - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnLong(((AllTypesRealmProxyInterface) realmObject).realmGet$columnLong()); - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnFloat(((AllTypesRealmProxyInterface) realmObject).realmGet$columnFloat()); - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnDouble(((AllTypesRealmProxyInterface) realmObject).realmGet$columnDouble()); - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnBoolean(((AllTypesRealmProxyInterface) realmObject).realmGet$columnBoolean()); - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnDate(((AllTypesRealmProxyInterface) realmObject).realmGet$columnDate()); - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnBinary(((AllTypesRealmProxyInterface) realmObject).realmGet$columnBinary()); + unmanagedObject = (some.test.AllTypes) cachedObject.object; + cachedObject.minDepth = currentDepth; + } + AllTypesRealmProxyInterface unmanagedCopy = (AllTypesRealmProxyInterface) unmanagedObject; + AllTypesRealmProxyInterface realmSource = (AllTypesRealmProxyInterface) realmObject; + unmanagedCopy.realmSet$columnString(realmSource.realmGet$columnString()); + unmanagedCopy.realmSet$columnLong(realmSource.realmGet$columnLong()); + unmanagedCopy.realmSet$columnFloat(realmSource.realmGet$columnFloat()); + unmanagedCopy.realmSet$columnDouble(realmSource.realmGet$columnDouble()); + unmanagedCopy.realmSet$columnBoolean(realmSource.realmGet$columnBoolean()); + unmanagedCopy.realmSet$columnDate(realmSource.realmGet$columnDate()); + unmanagedCopy.realmSet$columnBinary(realmSource.realmGet$columnBinary()); // Deep copy of columnObject - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnObject(AllTypesRealmProxy.createDetachedCopy(((AllTypesRealmProxyInterface) realmObject).realmGet$columnObject(), currentDepth + 1, maxDepth, cache)); + unmanagedCopy.realmSet$columnObject(AllTypesRealmProxy.createDetachedCopy(realmSource.realmGet$columnObject(), currentDepth + 1, maxDepth, cache)); // Deep copy of columnRealmList if (currentDepth == maxDepth) { - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnRealmList(null); + unmanagedCopy.realmSet$columnRealmList(null); } else { - RealmList managedcolumnRealmListList = ((AllTypesRealmProxyInterface) realmObject).realmGet$columnRealmList(); + RealmList managedcolumnRealmListList = realmSource.realmGet$columnRealmList(); RealmList unmanagedcolumnRealmListList = new RealmList(); - ((AllTypesRealmProxyInterface) unmanagedObject).realmSet$columnRealmList(unmanagedcolumnRealmListList); + unmanagedCopy.realmSet$columnRealmList(unmanagedcolumnRealmListList); int nextDepth = currentDepth + 1; int size = managedcolumnRealmListList.size(); for (int i = 0; i < size; i++) { @@ -1178,25 +1182,27 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje } static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, some.test.AllTypes newObject, Map cache) { - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnLong(((AllTypesRealmProxyInterface) newObject).realmGet$columnLong()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnFloat(((AllTypesRealmProxyInterface) newObject).realmGet$columnFloat()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnDouble(((AllTypesRealmProxyInterface) newObject).realmGet$columnDouble()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnBoolean(((AllTypesRealmProxyInterface) newObject).realmGet$columnBoolean()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnDate(((AllTypesRealmProxyInterface) newObject).realmGet$columnDate()); - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnBinary(((AllTypesRealmProxyInterface) newObject).realmGet$columnBinary()); - some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) newObject).realmGet$columnObject(); - if (columnObjectObj != null) { + AllTypesRealmProxyInterface realmObjectTarget = (AllTypesRealmProxyInterface) realmObject; + AllTypesRealmProxyInterface realmObjectSource = (AllTypesRealmProxyInterface) newObject; + realmObjectTarget.realmSet$columnLong(realmObjectSource.realmGet$columnLong()); + realmObjectTarget.realmSet$columnFloat(realmObjectSource.realmGet$columnFloat()); + realmObjectTarget.realmSet$columnDouble(realmObjectSource.realmGet$columnDouble()); + realmObjectTarget.realmSet$columnBoolean(realmObjectSource.realmGet$columnBoolean()); + realmObjectTarget.realmSet$columnDate(realmObjectSource.realmGet$columnDate()); + realmObjectTarget.realmSet$columnBinary(realmObjectSource.realmGet$columnBinary()); + some.test.AllTypes columnObjectObj = realmObjectSource.realmGet$columnObject(); + if (columnObjectObj == null) { + realmObjectTarget.realmSet$columnObject(null); + } else { some.test.AllTypes cachecolumnObject = (some.test.AllTypes) cache.get(columnObjectObj); if (cachecolumnObject != null) { - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnObject(cachecolumnObject); + realmObjectTarget.realmSet$columnObject(cachecolumnObject); } else { - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnObject(AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, true, cache)); + realmObjectTarget.realmSet$columnObject(AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, true, cache)); } - } else { - ((AllTypesRealmProxyInterface) realmObject).realmSet$columnObject(null); } - RealmList columnRealmListList = ((AllTypesRealmProxyInterface) newObject).realmGet$columnRealmList(); - RealmList columnRealmListRealmList = ((AllTypesRealmProxyInterface) realmObject).realmGet$columnRealmList(); + RealmList columnRealmListList = realmObjectSource.realmGet$columnRealmList(); + RealmList columnRealmListRealmList = realmObjectTarget.realmGet$columnRealmList(); columnRealmListRealmList.clear(); if (columnRealmListList != null) { for (int i = 0; i < columnRealmListList.size(); i++) { @@ -1205,7 +1211,7 @@ static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, so if (cachecolumnRealmList != null) { columnRealmListRealmList.add(cachecolumnRealmList); } else { - columnRealmListRealmList.add(AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListList.get(i), true, cache)); + columnRealmListRealmList.add(AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); } } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index a44fae7799..b1d9ba35ec 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -5,8 +5,6 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; -import io.realm.RealmObjectSchema; -import io.realm.RealmSchema; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -31,7 +29,7 @@ @SuppressWarnings("all") public class BooleansRealmProxy extends some.test.Booleans - implements RealmObjectProxy, BooleansRealmProxyInterface { + implements RealmObjectProxy, BooleansRealmProxyInterface { static final class BooleansColumnInfo extends ColumnInfo { long doneIndex; @@ -187,15 +185,15 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { - if (!realmSchema.contains("Booleans")) { - RealmObjectSchema realmObjectSchema = realmSchema.create("Booleans"); - realmObjectSchema.add("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("anotherBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - return realmObjectSchema; - } - return realmSchema.get("Booleans"); + if (realmSchema.contains("Booleans")) { + return realmSchema.get("Booleans"); + } + RealmObjectSchema realmObjectSchema = realmSchema.create("Booleans"); + realmObjectSchema.add("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("anotherBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + return realmObjectSchema; } public static BooleansColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { @@ -275,7 +273,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = Collections. emptyList(); some.test.Booleans obj = realm.createObjectInternal(some.test.Booleans.class, true, excludeFields); if (json.has("done")) { @@ -312,7 +310,7 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { some.test.Booleans obj = new some.test.Booleans(); reader.beginObject(); while (reader.hasNext()) { @@ -359,47 +357,51 @@ public static some.test.Booleans copyOrUpdate(Realm realm, some.test.Booleans ob if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (some.test.Booleans) cachedRealmObject; - } else { - return copy(realm, object, update, cache); } + + return copy(realm, object, update, cache); } public static some.test.Booleans copy(Realm realm, some.test.Booleans newObject, boolean update, Map cache) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.Booleans) cachedRealmObject; - } else { - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.Booleans realmObject = realm.createObjectInternal(some.test.Booleans.class, false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - ((BooleansRealmProxyInterface) realmObject).realmSet$done(((BooleansRealmProxyInterface) newObject).realmGet$done()); - ((BooleansRealmProxyInterface) realmObject).realmSet$isReady(((BooleansRealmProxyInterface) newObject).realmGet$isReady()); - ((BooleansRealmProxyInterface) realmObject).realmSet$mCompleted(((BooleansRealmProxyInterface) newObject).realmGet$mCompleted()); - ((BooleansRealmProxyInterface) realmObject).realmSet$anotherBoolean(((BooleansRealmProxyInterface) newObject).realmGet$anotherBoolean()); - return realmObject; } + + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.Booleans realmObject = realm.createObjectInternal(some.test.Booleans.class, false, Collections.emptyList()); + cache.put(newObject, (RealmObjectProxy) realmObject); + + BooleansRealmProxyInterface realmObjectSource = (BooleansRealmProxyInterface) newObject; + BooleansRealmProxyInterface realmObjectCopy = (BooleansRealmProxyInterface) realmObject; + + realmObjectCopy.realmSet$done(realmObjectSource.realmGet$done()); + realmObjectCopy.realmSet$isReady(realmObjectSource.realmGet$isReady()); + realmObjectCopy.realmSet$mCompleted(realmObjectSource.realmGet$mCompleted()); + realmObjectCopy.realmSet$anotherBoolean(realmObjectSource.realmGet$anotherBoolean()); + return realmObject; } public static long insert(Realm realm, some.test.Booleans object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); } Table table = realm.getTable(some.test.Booleans.class); long tableNativePtr = table.getNativePtr(); BooleansColumnInfo columnInfo = (BooleansColumnInfo) realm.schema.getColumnInfo(some.test.Booleans.class); long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$anotherBoolean(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); return rowIndex; } @@ -410,34 +412,35 @@ public static void insert(Realm realm, Iterator objects, M some.test.Booleans object = null; while (objects.hasNext()) { object = (some.test.Booleans) objects.next(); - if(!cache.containsKey(object)) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); - continue; - } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); - cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$anotherBoolean(), false); + if (cache.containsKey(object)) { + continue; } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + long rowIndex = OsObject.createRow(realm.sharedRealm, table); + cache.put(object, rowIndex); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); } } public static long insertOrUpdate(Realm realm, some.test.Booleans object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); } Table table = realm.getTable(some.test.Booleans.class); long tableNativePtr = table.getNativePtr(); BooleansColumnInfo columnInfo = (BooleansColumnInfo) realm.schema.getColumnInfo(some.test.Booleans.class); long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$anotherBoolean(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); return rowIndex; } @@ -448,18 +451,19 @@ public static void insertOrUpdate(Realm realm, Iterator ob some.test.Booleans object = null; while (objects.hasNext()) { object = (some.test.Booleans) objects.next(); - if(!cache.containsKey(object)) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); - continue; - } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); - cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface)object).realmGet$anotherBoolean(), false); + if (cache.containsKey(object)) { + continue; } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + long rowIndex = OsObject.createRow(realm.sharedRealm, table); + cache.put(object, rowIndex); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); } } @@ -469,22 +473,23 @@ public static some.test.Booleans createDetachedCopy(some.test.Booleans realmObje } CacheData cachedObject = cache.get(realmObject); some.test.Booleans unmanagedObject; - if (cachedObject != null) { + if (cachedObject == null) { + unmanagedObject = new some.test.Booleans(); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + } else { // Reuse cached object or recreate it because it was encountered at a lower depth. if (currentDepth >= cachedObject.minDepth) { - return (some.test.Booleans)cachedObject.object; - } else { - unmanagedObject = (some.test.Booleans)cachedObject.object; - cachedObject.minDepth = currentDepth; + return (some.test.Booleans) cachedObject.object; } - } else { - unmanagedObject = new some.test.Booleans(); - cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); - } - ((BooleansRealmProxyInterface) unmanagedObject).realmSet$done(((BooleansRealmProxyInterface) realmObject).realmGet$done()); - ((BooleansRealmProxyInterface) unmanagedObject).realmSet$isReady(((BooleansRealmProxyInterface) realmObject).realmGet$isReady()); - ((BooleansRealmProxyInterface) unmanagedObject).realmSet$mCompleted(((BooleansRealmProxyInterface) realmObject).realmGet$mCompleted()); - ((BooleansRealmProxyInterface) unmanagedObject).realmSet$anotherBoolean(((BooleansRealmProxyInterface) realmObject).realmGet$anotherBoolean()); + unmanagedObject = (some.test.Booleans) cachedObject.object; + cachedObject.minDepth = currentDepth; + } + BooleansRealmProxyInterface unmanagedCopy = (BooleansRealmProxyInterface) unmanagedObject; + BooleansRealmProxyInterface realmSource = (BooleansRealmProxyInterface) realmObject; + unmanagedCopy.realmSet$done(realmSource.realmGet$done()); + unmanagedCopy.realmSet$isReady(realmSource.realmGet$isReady()); + unmanagedCopy.realmSet$mCompleted(realmSource.realmGet$mCompleted()); + unmanagedCopy.realmSet$anotherBoolean(realmSource.realmGet$anotherBoolean()); return unmanagedObject; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 9c97e3f902..41ebb879e5 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -5,8 +5,6 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; -import io.realm.RealmObjectSchema; -import io.realm.RealmSchema; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -31,7 +29,7 @@ @SuppressWarnings("all") public class NullTypesRealmProxy extends some.test.NullTypes - implements RealmObjectProxy, NullTypesRealmProxyInterface { + implements RealmObjectProxy, NullTypesRealmProxyInterface { static final class NullTypesColumnInfo extends ColumnInfo { long fieldStringNotNullIndex; @@ -815,42 +813,42 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (!(RealmObject.isManaged(value) && RealmObject.isValid(value))) { throw new IllegalArgumentException("'value' is not a valid managed object."); } - if (((RealmObjectProxy)value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { + if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { throw new IllegalArgumentException("'value' belongs to a different Realm."); } proxyState.getRow$realm().setLink(columnInfo.fieldObjectNullIndex, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex()); } public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { + if (realmSchema.contains("NullTypes")) { + return realmSchema.get("NullTypes"); + } + RealmObjectSchema realmObjectSchema = realmSchema.create("NullTypes"); + realmObjectSchema.add("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldBooleanNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldBytesNotNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldBytesNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldByteNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldByteNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldShortNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldShortNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldIntegerNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldIntegerNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldLongNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldLongNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldFloatNotNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldFloatNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldDoubleNotNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldDoubleNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + realmObjectSchema.add("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); if (!realmSchema.contains("NullTypes")) { - RealmObjectSchema realmObjectSchema = realmSchema.create("NullTypes"); - realmObjectSchema.add("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldBooleanNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldBytesNotNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldBytesNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldByteNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldByteNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldShortNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldShortNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldIntegerNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldIntegerNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldLongNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldLongNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldFloatNotNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldFloatNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldDoubleNotNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldDoubleNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - if (!realmSchema.contains("NullTypes")) { - NullTypesRealmProxy.createRealmObjectSchema(realmSchema); - } - realmObjectSchema.add("fieldObjectNull", RealmFieldType.OBJECT, realmSchema.get("NullTypes")); - return realmObjectSchema; - } - return realmSchema.get("NullTypes"); + NullTypesRealmProxy.createRealmObjectSchema(realmSchema); + } + realmObjectSchema.add("fieldObjectNull", RealmFieldType.OBJECT, realmSchema.get("NullTypes")); + return realmObjectSchema; } public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { @@ -1087,7 +1085,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = new ArrayList(1); if (json.has("fieldObjectNull")) { excludeFields.add("fieldObjectNull"); @@ -1257,7 +1255,7 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { some.test.NullTypes obj = new some.test.NullTypes(); reader.beginObject(); while (reader.hasNext()) { @@ -1434,148 +1432,152 @@ public static some.test.NullTypes copyOrUpdate(Realm realm, some.test.NullTypes if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (some.test.NullTypes) cachedRealmObject; - } else { - return copy(realm, object, update, cache); } + + return copy(realm, object, update, cache); } public static some.test.NullTypes copy(Realm realm, some.test.NullTypes newObject, boolean update, Map cache) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.NullTypes) cachedRealmObject; + } + + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.NullTypes realmObject = realm.createObjectInternal(some.test.NullTypes.class, false, Collections.emptyList()); + cache.put(newObject, (RealmObjectProxy) realmObject); + + NullTypesRealmProxyInterface realmObjectSource = (NullTypesRealmProxyInterface) newObject; + NullTypesRealmProxyInterface realmObjectCopy = (NullTypesRealmProxyInterface) realmObject; + + realmObjectCopy.realmSet$fieldStringNotNull(realmObjectSource.realmGet$fieldStringNotNull()); + realmObjectCopy.realmSet$fieldStringNull(realmObjectSource.realmGet$fieldStringNull()); + realmObjectCopy.realmSet$fieldBooleanNotNull(realmObjectSource.realmGet$fieldBooleanNotNull()); + realmObjectCopy.realmSet$fieldBooleanNull(realmObjectSource.realmGet$fieldBooleanNull()); + realmObjectCopy.realmSet$fieldBytesNotNull(realmObjectSource.realmGet$fieldBytesNotNull()); + realmObjectCopy.realmSet$fieldBytesNull(realmObjectSource.realmGet$fieldBytesNull()); + realmObjectCopy.realmSet$fieldByteNotNull(realmObjectSource.realmGet$fieldByteNotNull()); + realmObjectCopy.realmSet$fieldByteNull(realmObjectSource.realmGet$fieldByteNull()); + realmObjectCopy.realmSet$fieldShortNotNull(realmObjectSource.realmGet$fieldShortNotNull()); + realmObjectCopy.realmSet$fieldShortNull(realmObjectSource.realmGet$fieldShortNull()); + realmObjectCopy.realmSet$fieldIntegerNotNull(realmObjectSource.realmGet$fieldIntegerNotNull()); + realmObjectCopy.realmSet$fieldIntegerNull(realmObjectSource.realmGet$fieldIntegerNull()); + realmObjectCopy.realmSet$fieldLongNotNull(realmObjectSource.realmGet$fieldLongNotNull()); + realmObjectCopy.realmSet$fieldLongNull(realmObjectSource.realmGet$fieldLongNull()); + realmObjectCopy.realmSet$fieldFloatNotNull(realmObjectSource.realmGet$fieldFloatNotNull()); + realmObjectCopy.realmSet$fieldFloatNull(realmObjectSource.realmGet$fieldFloatNull()); + realmObjectCopy.realmSet$fieldDoubleNotNull(realmObjectSource.realmGet$fieldDoubleNotNull()); + realmObjectCopy.realmSet$fieldDoubleNull(realmObjectSource.realmGet$fieldDoubleNull()); + realmObjectCopy.realmSet$fieldDateNotNull(realmObjectSource.realmGet$fieldDateNotNull()); + realmObjectCopy.realmSet$fieldDateNull(realmObjectSource.realmGet$fieldDateNull()); + + some.test.NullTypes fieldObjectNullObj = realmObjectSource.realmGet$fieldObjectNull(); + if (fieldObjectNullObj == null) { + realmObjectCopy.realmSet$fieldObjectNull(null); } else { - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.NullTypes realmObject = realm.createObjectInternal(some.test.NullTypes.class, false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldStringNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldStringNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldStringNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldStringNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldBooleanNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldBooleanNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldBooleanNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldBooleanNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldBytesNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldBytesNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldBytesNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldBytesNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldByteNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldByteNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldByteNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldByteNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldShortNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldShortNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldShortNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldShortNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldIntegerNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldIntegerNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldIntegerNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldIntegerNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldLongNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldLongNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldLongNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldLongNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldFloatNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldFloatNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldFloatNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldFloatNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldDoubleNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldDoubleNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldDoubleNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldDoubleNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldDateNotNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldDateNotNull()); - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldDateNull(((NullTypesRealmProxyInterface) newObject).realmGet$fieldDateNull()); - - some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) newObject).realmGet$fieldObjectNull(); - if (fieldObjectNullObj != null) { - some.test.NullTypes cachefieldObjectNull = (some.test.NullTypes) cache.get(fieldObjectNullObj); - if (cachefieldObjectNull != null) { - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldObjectNull(cachefieldObjectNull); - } else { - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldObjectNull(NullTypesRealmProxy.copyOrUpdate(realm, fieldObjectNullObj, update, cache)); - } + some.test.NullTypes cachefieldObjectNull = (some.test.NullTypes) cache.get(fieldObjectNullObj); + if (cachefieldObjectNull != null) { + realmObjectCopy.realmSet$fieldObjectNull(cachefieldObjectNull); } else { - ((NullTypesRealmProxyInterface) realmObject).realmSet$fieldObjectNull(null); + realmObjectCopy.realmSet$fieldObjectNull(NullTypesRealmProxy.copyOrUpdate(realm, fieldObjectNullObj, update, cache)); } - return realmObject; } + return realmObject; } public static long insert(Realm realm, some.test.NullTypes object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); } Table table = realm.getTable(some.test.NullTypes.class); long tableNativePtr = table.getNativePtr(); NullTypesColumnInfo columnInfo = (NullTypesColumnInfo) realm.schema.getColumnInfo(some.test.NullTypes.class); long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); - String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNotNull(); + String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); } - String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNull(); + String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); } - Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNotNull(); + Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); } - Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNull(); + Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); } - byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNotNull(); + byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); } - byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNull(); + byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); } - Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNotNull(); + Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); } - Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNull(); + Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); } - Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNotNull(); + Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); } - Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNull(); + Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); } - Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNotNull(); + Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); } - Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNull(); + Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); } - Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNotNull(); + Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); } - Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNull(); + Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); } - Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNotNull(); + Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); } - Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNull(); + Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); } - Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNotNull(); + Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); } - Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNull(); + Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); } - java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNotNull(); + java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); } - java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNull(); + java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); } @@ -1598,230 +1600,231 @@ public static void insert(Realm realm, Iterator objects, M some.test.NullTypes object = null; while (objects.hasNext()) { object = (some.test.NullTypes) objects.next(); - if(!cache.containsKey(object)) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); - continue; - } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); - cache.put(object, rowIndex); - String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNotNull(); - if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); - } - String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNull(); - if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); - } - Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNotNull(); - if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); - } - Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNull(); - if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); - } - byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNotNull(); - if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); - } - byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNull(); - if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); - } - Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNotNull(); - if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); - } - Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNull(); - if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); - } - Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNotNull(); - if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); - } - Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNull(); - if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); - } - Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNotNull(); - if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); - } - Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNull(); - if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); - } - Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNotNull(); - if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); - } - Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNull(); - if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); - } - Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNotNull(); - if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); - } - Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNull(); - if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); - } - Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNotNull(); - if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); - } - Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNull(); - if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); - } - java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNotNull(); - if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); - } - java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNull(); - if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); - } + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + long rowIndex = OsObject.createRow(realm.sharedRealm, table); + cache.put(object, rowIndex); + String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); + if (realmGet$fieldStringNotNull != null) { + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); + } + String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); + if (realmGet$fieldStringNull != null) { + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); + } + Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); + if (realmGet$fieldBooleanNotNull != null) { + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); + } + Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); + if (realmGet$fieldBooleanNull != null) { + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); + } + byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); + if (realmGet$fieldBytesNotNull != null) { + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); + } + byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); + if (realmGet$fieldBytesNull != null) { + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); + } + Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); + if (realmGet$fieldByteNotNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); + } + Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); + if (realmGet$fieldByteNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); + } + Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); + if (realmGet$fieldShortNotNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); + } + Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); + if (realmGet$fieldShortNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); + } + Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); + if (realmGet$fieldIntegerNotNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); + } + Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); + if (realmGet$fieldIntegerNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); + } + Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); + if (realmGet$fieldLongNotNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); + } + Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); + if (realmGet$fieldLongNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); + } + Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); + if (realmGet$fieldFloatNotNull != null) { + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); + } + Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); + if (realmGet$fieldFloatNull != null) { + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); + } + Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); + if (realmGet$fieldDoubleNotNull != null) { + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); + } + Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); + if (realmGet$fieldDoubleNull != null) { + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); + } + java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); + if (realmGet$fieldDateNotNull != null) { + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); + } + java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); + if (realmGet$fieldDateNull != null) { + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); + } - some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); - if (fieldObjectNullObj != null) { - Long cachefieldObjectNull = cache.get(fieldObjectNullObj); - if (cachefieldObjectNull == null) { - cachefieldObjectNull = NullTypesRealmProxy.insert(realm, fieldObjectNullObj, cache); - } - table.setLink(columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); + some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); + if (fieldObjectNullObj != null) { + Long cachefieldObjectNull = cache.get(fieldObjectNullObj); + if (cachefieldObjectNull == null) { + cachefieldObjectNull = NullTypesRealmProxy.insert(realm, fieldObjectNullObj, cache); } + table.setLink(columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); } } } public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); } Table table = realm.getTable(some.test.NullTypes.class); long tableNativePtr = table.getNativePtr(); NullTypesColumnInfo columnInfo = (NullTypesColumnInfo) realm.schema.getColumnInfo(some.test.NullTypes.class); long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); - String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNotNull(); + String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, false); } - String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNull(); + String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, false); } - Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNotNull(); + Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, false); } - Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNull(); + Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, false); } - byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNotNull(); + byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, false); } - byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNull(); + byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, false); } - Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNotNull(); + Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, false); } - Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNull(); + Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, false); } - Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNotNull(); + Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, false); } - Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNull(); + Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, false); } - Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNotNull(); + Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, false); } - Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNull(); + Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, false); } - Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNotNull(); + Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, false); } - Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNull(); + Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, false); } - Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNotNull(); + Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, false); } - Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNull(); + Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, false); } - Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNotNull(); + Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, false); } - Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNull(); + Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, false); } - java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNotNull(); + java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, false); } - java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNull(); + java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); } else { @@ -1848,144 +1851,145 @@ public static void insertOrUpdate(Realm realm, Iterator ob some.test.NullTypes object = null; while (objects.hasNext()) { object = (some.test.NullTypes) objects.next(); - if(!cache.containsKey(object)) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); - continue; - } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); - cache.put(object, rowIndex); - String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNotNull(); - if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, false); - } - String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldStringNull(); - if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, false); - } - Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNotNull(); - if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, false); - } - Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBooleanNull(); - if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, false); - } - byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNotNull(); - if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, false); - } - byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldBytesNull(); - if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, false); - } - Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNotNull(); - if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, false); - } - Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldByteNull(); - if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, false); - } - Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNotNull(); - if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, false); - } - Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldShortNull(); - if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, false); - } - Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNotNull(); - if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, false); - } - Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldIntegerNull(); - if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, false); - } - Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNotNull(); - if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, false); - } - Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldLongNull(); - if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, false); - } - Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNotNull(); - if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, false); - } - Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldFloatNull(); - if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, false); - } - Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNotNull(); - if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, false); - } - Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDoubleNull(); - if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, false); - } - java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNotNull(); - if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, false); - } - java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface)object).realmGet$fieldDateNull(); - if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, false); - } + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + long rowIndex = OsObject.createRow(realm.sharedRealm, table); + cache.put(object, rowIndex); + String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); + if (realmGet$fieldStringNotNull != null) { + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, false); + } + String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); + if (realmGet$fieldStringNull != null) { + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, false); + } + Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); + if (realmGet$fieldBooleanNotNull != null) { + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, false); + } + Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); + if (realmGet$fieldBooleanNull != null) { + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, false); + } + byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); + if (realmGet$fieldBytesNotNull != null) { + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, false); + } + byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); + if (realmGet$fieldBytesNull != null) { + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, false); + } + Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); + if (realmGet$fieldByteNotNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, false); + } + Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); + if (realmGet$fieldByteNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, false); + } + Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); + if (realmGet$fieldShortNotNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, false); + } + Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); + if (realmGet$fieldShortNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, false); + } + Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); + if (realmGet$fieldIntegerNotNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, false); + } + Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); + if (realmGet$fieldIntegerNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, false); + } + Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); + if (realmGet$fieldLongNotNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, false); + } + Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); + if (realmGet$fieldLongNull != null) { + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, false); + } + Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); + if (realmGet$fieldFloatNotNull != null) { + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, false); + } + Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); + if (realmGet$fieldFloatNull != null) { + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, false); + } + Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); + if (realmGet$fieldDoubleNotNull != null) { + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, false); + } + Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); + if (realmGet$fieldDoubleNull != null) { + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, false); + } + java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); + if (realmGet$fieldDateNotNull != null) { + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, false); + } + java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); + if (realmGet$fieldDateNull != null) { + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, false); + } - some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); - if (fieldObjectNullObj != null) { - Long cachefieldObjectNull = cache.get(fieldObjectNullObj); - if (cachefieldObjectNull == null) { - cachefieldObjectNull = NullTypesRealmProxy.insertOrUpdate(realm, fieldObjectNullObj, cache); - } - Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); - } else { - Table.nativeNullifyLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex); + some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); + if (fieldObjectNullObj != null) { + Long cachefieldObjectNull = cache.get(fieldObjectNullObj); + if (cachefieldObjectNull == null) { + cachefieldObjectNull = NullTypesRealmProxy.insertOrUpdate(realm, fieldObjectNullObj, cache); } + Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); + } else { + Table.nativeNullifyLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex); } } } @@ -1996,41 +2000,42 @@ public static some.test.NullTypes createDetachedCopy(some.test.NullTypes realmOb } CacheData cachedObject = cache.get(realmObject); some.test.NullTypes unmanagedObject; - if (cachedObject != null) { - // Reuse cached object or recreate it because it was encountered at a lower depth. - if (currentDepth >= cachedObject.minDepth) { - return (some.test.NullTypes)cachedObject.object; - } else { - unmanagedObject = (some.test.NullTypes)cachedObject.object; - cachedObject.minDepth = currentDepth; - } - } else { + if (cachedObject == null) { unmanagedObject = new some.test.NullTypes(); cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); - } - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldStringNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldStringNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldStringNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldStringNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldBooleanNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldBooleanNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldBooleanNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldBooleanNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldBytesNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldBytesNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldBytesNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldBytesNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldByteNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldByteNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldByteNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldByteNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldShortNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldShortNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldShortNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldShortNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldIntegerNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldIntegerNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldIntegerNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldIntegerNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldLongNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldLongNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldLongNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldLongNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldFloatNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldFloatNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldFloatNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldFloatNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldDoubleNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldDoubleNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldDoubleNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldDoubleNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldDateNotNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldDateNotNull()); - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldDateNull(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldDateNull()); + } else { + // Reuse cached object or recreate it because it was encountered at a lower depth. + if (currentDepth >= cachedObject.minDepth) { + return (some.test.NullTypes) cachedObject.object; + } + unmanagedObject = (some.test.NullTypes) cachedObject.object; + cachedObject.minDepth = currentDepth; + } + NullTypesRealmProxyInterface unmanagedCopy = (NullTypesRealmProxyInterface) unmanagedObject; + NullTypesRealmProxyInterface realmSource = (NullTypesRealmProxyInterface) realmObject; + unmanagedCopy.realmSet$fieldStringNotNull(realmSource.realmGet$fieldStringNotNull()); + unmanagedCopy.realmSet$fieldStringNull(realmSource.realmGet$fieldStringNull()); + unmanagedCopy.realmSet$fieldBooleanNotNull(realmSource.realmGet$fieldBooleanNotNull()); + unmanagedCopy.realmSet$fieldBooleanNull(realmSource.realmGet$fieldBooleanNull()); + unmanagedCopy.realmSet$fieldBytesNotNull(realmSource.realmGet$fieldBytesNotNull()); + unmanagedCopy.realmSet$fieldBytesNull(realmSource.realmGet$fieldBytesNull()); + unmanagedCopy.realmSet$fieldByteNotNull(realmSource.realmGet$fieldByteNotNull()); + unmanagedCopy.realmSet$fieldByteNull(realmSource.realmGet$fieldByteNull()); + unmanagedCopy.realmSet$fieldShortNotNull(realmSource.realmGet$fieldShortNotNull()); + unmanagedCopy.realmSet$fieldShortNull(realmSource.realmGet$fieldShortNull()); + unmanagedCopy.realmSet$fieldIntegerNotNull(realmSource.realmGet$fieldIntegerNotNull()); + unmanagedCopy.realmSet$fieldIntegerNull(realmSource.realmGet$fieldIntegerNull()); + unmanagedCopy.realmSet$fieldLongNotNull(realmSource.realmGet$fieldLongNotNull()); + unmanagedCopy.realmSet$fieldLongNull(realmSource.realmGet$fieldLongNull()); + unmanagedCopy.realmSet$fieldFloatNotNull(realmSource.realmGet$fieldFloatNotNull()); + unmanagedCopy.realmSet$fieldFloatNull(realmSource.realmGet$fieldFloatNull()); + unmanagedCopy.realmSet$fieldDoubleNotNull(realmSource.realmGet$fieldDoubleNotNull()); + unmanagedCopy.realmSet$fieldDoubleNull(realmSource.realmGet$fieldDoubleNull()); + unmanagedCopy.realmSet$fieldDateNotNull(realmSource.realmGet$fieldDateNotNull()); + unmanagedCopy.realmSet$fieldDateNull(realmSource.realmGet$fieldDateNull()); // Deep copy of fieldObjectNull - ((NullTypesRealmProxyInterface) unmanagedObject).realmSet$fieldObjectNull(NullTypesRealmProxy.createDetachedCopy(((NullTypesRealmProxyInterface) realmObject).realmGet$fieldObjectNull(), currentDepth + 1, maxDepth, cache)); + unmanagedCopy.realmSet$fieldObjectNull(NullTypesRealmProxy.createDetachedCopy(realmSource.realmGet$fieldObjectNull(), currentDepth + 1, maxDepth, cache)); return unmanagedObject; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index a9a24e1a83..543b16baee 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -5,8 +5,6 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; -import io.realm.RealmObjectSchema; -import io.realm.RealmSchema; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; @@ -31,7 +29,7 @@ @SuppressWarnings("all") public class SimpleRealmProxy extends some.test.Simple - implements RealmObjectProxy, SimpleRealmProxyInterface { + implements RealmObjectProxy, SimpleRealmProxyInterface { static final class SimpleColumnInfo extends ColumnInfo { long nameIndex; @@ -143,13 +141,13 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { - if (!realmSchema.contains("Simple")) { - RealmObjectSchema realmObjectSchema = realmSchema.create("Simple"); - realmObjectSchema.add("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - return realmObjectSchema; + if (realmSchema.contains("Simple")) { + return realmSchema.get("Simple"); } - return realmSchema.get("Simple"); + RealmObjectSchema realmObjectSchema = realmSchema.create("Simple"); + realmObjectSchema.add("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + realmObjectSchema.add("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + return realmObjectSchema; } public static SimpleColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { @@ -211,7 +209,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = Collections. emptyList(); some.test.Simple obj = realm.createObjectInternal(some.test.Simple.class, true, excludeFields); if (json.has("name")) { @@ -234,7 +232,7 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { some.test.Simple obj = new some.test.Simple(); reader.beginObject(); while (reader.hasNext()) { @@ -267,46 +265,50 @@ public static some.test.Simple copyOrUpdate(Realm realm, some.test.Simple object if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (some.test.Simple) cachedRealmObject; - } else { - return copy(realm, object, update, cache); } + + return copy(realm, object, update, cache); } public static some.test.Simple copy(Realm realm, some.test.Simple newObject, boolean update, Map cache) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.Simple) cachedRealmObject; - } else { - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.Simple realmObject = realm.createObjectInternal(some.test.Simple.class, false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - ((SimpleRealmProxyInterface) realmObject).realmSet$name(((SimpleRealmProxyInterface) newObject).realmGet$name()); - ((SimpleRealmProxyInterface) realmObject).realmSet$age(((SimpleRealmProxyInterface) newObject).realmGet$age()); - return realmObject; } + + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.Simple realmObject = realm.createObjectInternal(some.test.Simple.class, false, Collections.emptyList()); + cache.put(newObject, (RealmObjectProxy) realmObject); + + SimpleRealmProxyInterface realmObjectSource = (SimpleRealmProxyInterface) newObject; + SimpleRealmProxyInterface realmObjectCopy = (SimpleRealmProxyInterface) realmObject; + + realmObjectCopy.realmSet$name(realmObjectSource.realmGet$name()); + realmObjectCopy.realmSet$age(realmObjectSource.realmGet$age()); + return realmObject; } public static long insert(Realm realm, some.test.Simple object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); } Table table = realm.getTable(some.test.Simple.class); long tableNativePtr = table.getNativePtr(); SimpleColumnInfo columnInfo = (SimpleColumnInfo) realm.schema.getColumnInfo(some.test.Simple.class); long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); - String realmGet$name = ((SimpleRealmProxyInterface)object).realmGet$name(); + String realmGet$name = ((SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface)object).realmGet$age(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface) object).realmGet$age(), false); return rowIndex; } @@ -317,38 +319,39 @@ public static void insert(Realm realm, Iterator objects, M some.test.Simple object = null; while (objects.hasNext()) { object = (some.test.Simple) objects.next(); - if(!cache.containsKey(object)) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); - continue; - } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); - cache.put(object, rowIndex); - String realmGet$name = ((SimpleRealmProxyInterface)object).realmGet$name(); - if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); - } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface)object).realmGet$age(), false); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; } + long rowIndex = OsObject.createRow(realm.sharedRealm, table); + cache.put(object, rowIndex); + String realmGet$name = ((SimpleRealmProxyInterface) object).realmGet$name(); + if (realmGet$name != null) { + Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); + } + Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface) object).realmGet$age(), false); } } public static long insertOrUpdate(Realm realm, some.test.Simple object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); } Table table = realm.getTable(some.test.Simple.class); long tableNativePtr = table.getNativePtr(); SimpleColumnInfo columnInfo = (SimpleColumnInfo) realm.schema.getColumnInfo(some.test.Simple.class); long rowIndex = OsObject.createRow(realm.sharedRealm, table); cache.put(object, rowIndex); - String realmGet$name = ((SimpleRealmProxyInterface)object).realmGet$name(); + String realmGet$name = ((SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.nameIndex, rowIndex, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface)object).realmGet$age(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface) object).realmGet$age(), false); return rowIndex; } @@ -359,21 +362,22 @@ public static void insertOrUpdate(Realm realm, Iterator ob some.test.Simple object = null; while (objects.hasNext()) { object = (some.test.Simple) objects.next(); - if(!cache.containsKey(object)) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy)object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy)object).realmGet$proxyState().getRow$realm().getIndex()); - continue; - } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); - cache.put(object, rowIndex); - String realmGet$name = ((SimpleRealmProxyInterface)object).realmGet$name(); - if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.nameIndex, rowIndex, false); - } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface)object).realmGet$age(), false); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + long rowIndex = OsObject.createRow(realm.sharedRealm, table); + cache.put(object, rowIndex); + String realmGet$name = ((SimpleRealmProxyInterface) object).realmGet$name(); + if (realmGet$name != null) { + Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.nameIndex, rowIndex, false); } + Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface) object).realmGet$age(), false); } } @@ -383,20 +387,21 @@ public static some.test.Simple createDetachedCopy(some.test.Simple realmObject, } CacheData cachedObject = cache.get(realmObject); some.test.Simple unmanagedObject; - if (cachedObject != null) { + if (cachedObject == null) { + unmanagedObject = new some.test.Simple(); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + } else { // Reuse cached object or recreate it because it was encountered at a lower depth. if (currentDepth >= cachedObject.minDepth) { - return (some.test.Simple)cachedObject.object; - } else { - unmanagedObject = (some.test.Simple)cachedObject.object; - cachedObject.minDepth = currentDepth; + return (some.test.Simple) cachedObject.object; } - } else { - unmanagedObject = new some.test.Simple(); - cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + unmanagedObject = (some.test.Simple) cachedObject.object; + cachedObject.minDepth = currentDepth; } - ((SimpleRealmProxyInterface) unmanagedObject).realmSet$name(((SimpleRealmProxyInterface) realmObject).realmGet$name()); - ((SimpleRealmProxyInterface) unmanagedObject).realmSet$age(((SimpleRealmProxyInterface) realmObject).realmGet$age()); + SimpleRealmProxyInterface unmanagedCopy = (SimpleRealmProxyInterface) unmanagedObject; + SimpleRealmProxyInterface realmSource = (SimpleRealmProxyInterface) realmObject; + unmanagedCopy.realmSet$name(realmSource.realmGet$name()); + unmanagedCopy.realmSet$age(realmSource.realmGet$age()); return unmanagedObject; } From ba40949ad16892d10dd8ef760d4b6afae6663de8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 15 Jun 2017 18:14:52 +0800 Subject: [PATCH 0749/2110] Clean object schema tests --- .../src/androidTest/assets/encrypted.realm | Bin 16384 -> 0 bytes .../java/io/realm/RealmCacheTests.java | 14 +++++--------- .../java/io/realm/RealmMigrationTests.java | 4 ++-- .../androidTest/java/io/realm/TestHelper.java | 13 ------------- .../java/io/realm/entities/StringOnly.java | 1 + 5 files changed, 8 insertions(+), 24 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/assets/encrypted.realm diff --git a/realm/realm-library/src/androidTest/assets/encrypted.realm b/realm/realm-library/src/androidTest/assets/encrypted.realm deleted file mode 100644 index d61e5677e0f521614ab49a6aa8e8f7bfdcc71c7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 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 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index a6b842bd3c..410ce9656f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -141,23 +141,24 @@ public void realmCache() { public void dontCacheWrongConfigurations() throws IOException { Realm testRealm; String REALM_NAME = "encrypted.realm"; - configFactory.copyRealmFromAssets(context, REALM_NAME, REALM_NAME); - RealmMigration realmMigration = TestHelper.prepareMigrationToNullSupportStep(); RealmConfiguration wrongConfig = configFactory.createConfigurationBuilder() .name(REALM_NAME) .encryptionKey(TestHelper.SHA512("foo")) - .migration(realmMigration) .schema(StringOnly.class) .build(); RealmConfiguration rightConfig = configFactory.createConfigurationBuilder() .name(REALM_NAME) .encryptionKey(TestHelper.SHA512("realm")) - .migration(realmMigration) .schema(StringOnly.class) .build(); + // Create the realm with proper key. + testRealm = Realm.getInstance(rightConfig); + assertNotNull(testRealm); + testRealm.close(); + // Opens Realm with wrong key. try { Realm.getInstance(wrongConfig); @@ -178,13 +179,9 @@ public void deletingRealmAlsoClearsConfigurationCache() throws IOException { byte[] oldPassword = TestHelper.SHA512("realm"); byte[] newPassword = TestHelper.SHA512("realm-copy"); - configFactory.copyRealmFromAssets(context, REALM_NAME, REALM_NAME); - RealmMigration realmMigration = TestHelper.prepareMigrationToNullSupportStep(); - RealmConfiguration config = configFactory.createConfigurationBuilder() .name(REALM_NAME) .encryptionKey(oldPassword) - .migration(realmMigration) .schema(StringOnly.class) .build(); @@ -209,7 +206,6 @@ public void deletingRealmAlsoClearsConfigurationCache() throws IOException { RealmConfiguration newConfig = configFactory.createConfigurationBuilder() .name(REALM_NAME) .encryptionKey(newPassword) - .migration(realmMigration) .schema(StringOnly.class) .build(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 22dca370c7..fe03983de6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -882,8 +882,8 @@ public void migratePreNull() throws IOException { RealmMigration migration = new RealmMigration() { @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { - Table table = realm.schema.getTable(StringOnly.class); - table.convertColumnToNullable(table.getColumnIndex("chars")); + RealmObjectSchema objectSchema = realm.getSchema().get(StringOnly.CLASS_NAME); + objectSchema.setRequired(StringOnly.FIELD_CHARS, false); } }; diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 97d1a7e677..b92522931e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -146,19 +146,6 @@ public static InputStream stringToStream(String str) { return new ByteArrayInputStream(str.getBytes(UTF_8)); } - // Creates a simple migration step in order to support null. - // FIXME: generate a new encrypted.realm will null support - public static RealmMigration prepareMigrationToNullSupportStep() { - RealmMigration realmMigration = new RealmMigration() { - @Override - public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { - Table stringOnly = realm.schema.getTable(StringOnly.class); - stringOnly.convertColumnToNullable(stringOnly.getColumnIndex("chars")); - } - }; - return realmMigration; - } - // Returns a random key used by encrypted Realms. public static byte[] getRandomKey() { byte[] key = new byte[64]; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java index c8c80a461a..88bd0419cc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java @@ -20,6 +20,7 @@ public class StringOnly extends RealmObject { + public static final String CLASS_NAME = "StringOnly"; public static final String FIELD_CHARS = "chars"; private String chars; From bac0ccef4ad7d09f192804cc9d4d9622a06dce0f Mon Sep 17 00:00:00 2001 From: abennsir Date: Thu, 15 Jun 2017 18:38:29 +0200 Subject: [PATCH 0750/2110] fix roboelectric unit test #Closes 4698 --- examples/unitTestExample/build.gradle | 10 +++++----- .../examples/unittesting/ExampleActivityTest.java | 13 ++++++++----- .../examples/unittesting/ExampleRealmTest.java | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index a6752c2738..8e4f015499 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -34,14 +34,14 @@ dependencies { // Testing testCompile 'junit:junit:4.12' - testCompile "org.robolectric:robolectric:3.0" + testCompile "org.robolectric:robolectric:3.3.1" testCompile "org.mockito:mockito-core:1.10.19" testCompile 'org.robolectric:shadows-support-v4:3.0' - testCompile "org.powermock:powermock-module-junit4:1.6.4" - testCompile "org.powermock:powermock-module-junit4-rule:1.6.4" - testCompile "org.powermock:powermock-api-mockito:1.6.4" - testCompile "org.powermock:powermock-classloading-xstream:1.6.4" + testCompile "org.powermock:powermock-module-junit4-rule:1.6.5" + testCompile "org.powermock:powermock-module-junit4:1.6.5" + testCompile "org.powermock:powermock-api-mockito:1.6.5" + testCompile "org.powermock:powermock-classloading-xstream:1.6.5" androidTestCompile 'com.android.support.test:runner:0.5' diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java index e478db6428..d2467a2ffd 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java @@ -28,10 +28,12 @@ import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; import org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.Robolectric; -import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.util.ActivityController; @@ -64,14 +66,15 @@ import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.mockito.PowerMockito.whenNew; -@RunWith(RobolectricGradleTestRunner.class) +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) @SuppressStaticInitializationFor("io.realm.internal.Util") @PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class}) -public class ExampleActivityTest { - - // Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock +public class ExampleActivityTest +{ + // Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock @Rule public PowerMockRule rule = new PowerMockRule(); diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java index 147c9aa553..32a62049f5 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java @@ -26,7 +26,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; import org.powermock.modules.junit4.rule.PowerMockRule; -import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import io.realm.Realm; @@ -44,7 +44,7 @@ import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; -@RunWith(RobolectricGradleTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 19) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) @SuppressStaticInitializationFor("io.realm.internal.Util") From 411cba0354a7d52dbbf73792808b4b5e0f59cd78 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Jun 2017 10:27:56 +0800 Subject: [PATCH 0751/2110] BLOB's default value when convert to not-nullable (#4794) "" is a char* type takes 1 byte of memory. --- CHANGELOG.md | 10 ++++- .../java/io/realm/RealmObjectSchemaTests.java | 42 ++++++++++++++++++- .../src/main/cpp/io_realm_internal_Table.cpp | 2 +- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d13350215d..792ba98b32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.3.2 (2017-06-09) +## 3.3.3 (YYYY-MM-DD) ### Breaking Changes @@ -6,6 +6,14 @@ ### Bug Fixes +* When converting nullable BLOB field to required, `null` values should be converted to `byte[0]` instead of `byte[1]`. + +### Internal + +## 3.3.2 (2017-06-09) + +### Bug Fixes + * [ObjectServer] Fixed a crash when an authentication error happens (#4726). * [ObjectServer] Enabled encryption with Sync (#4561). * [ObjectServer] Admin users did not connect correctly to the server (#4750). diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index d8fbb9c7bd..4322557498 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -33,7 +33,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -507,6 +506,47 @@ public void setRemoveRequired() { } } + // When converting a nullable field to required, the null values of the field will be set to the default value + // according to the field type. + @Test + public void setRequired_nullValueBecomesDefaultValue() { + for (FieldType fieldType : FieldType.values()) { + String fieldName = fieldType.name(); + switch (fieldType) { + case OBJECT: + case LIST: + // Skip always nullable fields. + break; + default: + // Skip not-nullable fields . + if (!fieldType.isNullable()) { + break; + } + schema.addField(fieldName, fieldType.getType()); + DynamicRealmObject object = realm.createObject(schema.getClassName()); + assertTrue(object.isNull(fieldName)); + schema.setRequired(fieldName, true); + assertFalse(object.isNull(fieldName)); + if (fieldType == FieldType.BLOB) { + assertEquals(0, object.getBlob(fieldName).length); + } else if (fieldType == FieldType.BOOLEAN) { + assertFalse(object.getBoolean(fieldName)); + } else if (fieldType == FieldType.STRING) { + assertEquals(0, object.getString(fieldName).length()); + } else if (fieldType == FieldType.FLOAT) { + assertEquals(0.0F, object.getFloat(fieldName), 0F); + } else if (fieldType == FieldType.DOUBLE) { + assertEquals(0.0D, object.getDouble(fieldName), 0D); + } else if (fieldType == FieldType.DATE) { + assertEquals(new Date(0), object.getDate(fieldName)); + } else { + assertEquals(0, object.getInt(fieldName)); + } + break; + } + } + } + @Test public void setRemovePrimaryKey() { for (PrimaryKeyFieldType fieldType : PrimaryKeyFieldType.values()) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 2a2c339ebc..7fdfbf87b4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -338,7 +338,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull case type_Binary: { BinaryData bd = table->get_binary(column_index + 1, i); if (bd.is_null()) { - table->set_binary(column_index, i, BinaryData("")); + table->set_binary(column_index, i, BinaryData("", 0)); } else { // Payload copy is needed From e0e891f73c3d6268ef621e5f964843d0a32bf19b Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Fri, 16 Jun 2017 06:42:24 +0200 Subject: [PATCH 0752/2110] Clean up JsonHelper as prep for RealmInteger (#4795) --- .../realm/processor/RealmJsonTypeHelper.java | 290 +++++++++--------- 1 file changed, 149 insertions(+), 141 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java index 2239201e2e..1b93e5b355 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java @@ -46,107 +46,24 @@ public class RealmJsonTypeHelper { JAVA_TO_JSON_TYPES.put("java.lang.Double", new SimpleTypeConverter("double", "Double")); JAVA_TO_JSON_TYPES.put("java.lang.Boolean", new SimpleTypeConverter("boolean", "Boolean")); JAVA_TO_JSON_TYPES.put("java.lang.String", new SimpleTypeConverter("String", "String")); - JAVA_TO_JSON_TYPES.put("java.util.Date", new JsonToRealmFieldTypeConverter() { - // @formatter:off - @Override - public void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer) throws IOException { - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - .emitStatement("((%s) obj).%s(null)", interfaceName, setter) - .nextControlFlow("else") - .emitStatement("Object timestamp = json.get(\"%s\")", fieldName) - .beginControlFlow("if (timestamp instanceof String)") - .emitStatement("((%s) obj).%s(JsonUtils.stringToDate((String) timestamp))", - interfaceName, setter) - .nextControlFlow("else") - .emitStatement("((%s) obj).%s(new Date(json.getLong(\"%s\")))", interfaceName, setter, - fieldName) - .endControlFlow() - .endControlFlow() - .endControlFlow(); - } - //@formatter:on - - // @formatter:off - @Override - public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, - String fieldType, JavaWriter writer, boolean isPrimaryKey) throws IOException { - writer - .beginControlFlow("if (reader.peek() == JsonToken.NULL)") - .emitStatement("reader.skipValue()") - .emitStatement("((%s) obj).%s(null)", interfaceName, setter) - .nextControlFlow("else if (reader.peek() == JsonToken.NUMBER)") - .emitStatement("long timestamp = reader.nextLong()", fieldName) - .beginControlFlow("if (timestamp > -1)") - .emitStatement("((%s) obj).%s(new Date(timestamp))", interfaceName, setter) - .endControlFlow() - .nextControlFlow("else") - .emitStatement("((%s) obj).%s(JsonUtils.stringToDate(reader.nextString()))", interfaceName, - setter) - .endControlFlow(); - } - //@formatter:on - - @Override - public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, - String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) throws IOException { - throw new IllegalArgumentException("'Date' is not allowed as a primary key value."); - } - }); - JAVA_TO_JSON_TYPES.put("byte[]", new JsonToRealmFieldTypeConverter() { - // @formatter:off - @Override - public void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer) throws IOException { - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - .emitStatement("((%s) obj).%s(null)", interfaceName, setter) - .nextControlFlow("else") - .emitStatement("((%s) obj).%s(JsonUtils.stringToBytes(json.getString(\"%s\")))", - interfaceName, setter, fieldName) - .endControlFlow() - .endControlFlow(); - } - //@formatter:on - - // @formatter:off - @Override - public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, - String fieldType, JavaWriter writer, boolean isPrimaryKey) throws IOException { - writer - .beginControlFlow("if (reader.peek() == JsonToken.NULL)") - .emitStatement("reader.skipValue()") - .emitStatement("((%s) obj).%s(null)", interfaceName, setter) - .nextControlFlow("else") - .emitStatement("((%s) obj).%s(JsonUtils.stringToBytes(reader.nextString()))", interfaceName, - setter) - .endControlFlow(); - } - //@formatter:on - - @Override - public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, - String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) throws IOException { - throw new IllegalArgumentException("'byte[]' is not allowed as a primary key value."); - } - }); + JAVA_TO_JSON_TYPES.put("java.util.Date", new DateTypeConverter()); + JAVA_TO_JSON_TYPES.put("byte[]", new ByteArrayTypeConverter()); } - public static void emitCreateObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, - String qualifiedRealmObjectProxyClass, String qualifiedFieldType, String fieldName, JavaWriter writer) + public static void emitCreateObjectWithPrimaryKeyValue( + String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, + String qualifiedFieldType, String fieldName, JavaWriter writer) throws IOException { JsonToRealmFieldTypeConverter typeEmitter = JAVA_TO_JSON_TYPES.get(qualifiedFieldType); if (typeEmitter != null) { - typeEmitter.emitGetObjectWithPrimaryKeyValue(qualifiedRealmObjectClass, qualifiedRealmObjectProxyClass, - fieldName, writer); + typeEmitter.emitGetObjectWithPrimaryKeyValue( + qualifiedRealmObjectClass, qualifiedRealmObjectProxyClass, fieldName, writer); } } - public static void emitFillJavaTypeWithJsonValue(String interfaceName, String setter, String fieldName, - String qualifiedFieldType, JavaWriter writer) throws IOException { + public static void emitFillJavaTypeWithJsonValue( + String interfaceName, String setter, String fieldName, String qualifiedFieldType, JavaWriter writer) + throws IOException { JsonToRealmFieldTypeConverter typeEmitter = JAVA_TO_JSON_TYPES.get(qualifiedFieldType); if (typeEmitter != null) { typeEmitter.emitTypeConversion(interfaceName, setter, fieldName, qualifiedFieldType, writer); @@ -155,26 +72,30 @@ public static void emitFillJavaTypeWithJsonValue(String interfaceName, String se public static void emitIllegalJsonValueException(String fieldType, String fieldName, JavaWriter writer) throws IOException { - writer.beginControlFlow("if (json.has(\"%s\"))", fieldName); - writer.emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD, fieldType, fieldName); - writer.endControlFlow(); + writer + .beginControlFlow("if (json.has(\"%s\"))", fieldName) + .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD, fieldType, fieldName) + .endControlFlow(); } // @formatter:off - public static void emitFillRealmObjectWithJsonValue(String interfaceName, String setter, String fieldName, - String qualifiedFieldType, String proxyClass, JavaWriter writer) throws IOException { + public static void emitFillRealmObjectWithJsonValue( + String interfaceName, String setter, String fieldName, + String qualifiedFieldType, String proxyClass, JavaWriter writer) + throws IOException { writer .beginControlFlow("if (json.has(\"%s\"))", fieldName) .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) .emitStatement("((%s) obj).%s(null)", interfaceName, setter) .nextControlFlow("else") - .emitStatement("%s %sObj = %s.createOrUpdateUsingJsonObject(realm, json.getJSONObject(\"%s\"), update)", + .emitStatement( + "%s %sObj = %s.createOrUpdateUsingJsonObject(realm, json.getJSONObject(\"%s\"), update)", qualifiedFieldType, fieldName, proxyClass, fieldName) .emitStatement("((%s) obj).%s(%sObj)", interfaceName, setter, fieldName) .endControlFlow() .endControlFlow(); } - //@formatter:on + // @formatter:on // @formatter:off public static void emitFillRealmListWithJsonValue(String interfaceName, String getter, String setter, @@ -187,25 +108,24 @@ public static void emitFillRealmListWithJsonValue(String interfaceName, String g .emitStatement("((%s) obj).%s().clear()", interfaceName, getter) .emitStatement("JSONArray array = json.getJSONArray(\"%s\")", fieldName) .beginControlFlow("for (int i = 0; i < array.length(); i++)") - .emitStatement("%s item = %s.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update)", + .emitStatement( + "%s item = %s.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update)", fieldTypeCanonicalName, proxyClass, fieldTypeCanonicalName) .emitStatement("((%s) obj).%s().add(item)", interfaceName, getter) .endControlFlow() .endControlFlow() .endControlFlow(); } - //@formatter:on + // @formatter:on - public static void emitFillJavaTypeFromStream(String interfaceName, ClassMetaData metaData, String fieldName, - String fieldType, JavaWriter writer) throws IOException { + public static void emitFillJavaTypeFromStream( + String interfaceName, ClassMetaData metaData, String fieldName, String fieldType, JavaWriter writer) + throws IOException { String setter = metaData.getInternalSetter(fieldName); - boolean isPrimaryKey = false; - if (metaData.hasPrimaryKey() && metaData.getPrimaryKey().getSimpleName().toString().equals(fieldName)) { - isPrimaryKey = true; - } + boolean isPrimaryKey = metaData.hasPrimaryKey() && metaData.getPrimaryKey().getSimpleName().toString().equals(fieldName); if (JAVA_TO_JSON_TYPES.containsKey(fieldType)) { - JAVA_TO_JSON_TYPES.get(fieldType).emitStreamTypeConversion(interfaceName, setter, fieldName, fieldType, - writer, isPrimaryKey); + JAVA_TO_JSON_TYPES.get(fieldType) + .emitStreamTypeConversion(interfaceName, setter, fieldName, fieldType, writer, isPrimaryKey); } } @@ -217,12 +137,13 @@ public static void emitFillRealmObjectFromStream(String interfaceName, String se .emitStatement("reader.skipValue()") .emitStatement("((%s) obj).%s(null)", interfaceName, setter) .nextControlFlow("else") - .emitStatement("%s %sObj = %s.createUsingJsonStream(realm, reader)", fieldTypeCanonicalName, fieldName, - proxyClass) + .emitStatement( + "%s %sObj = %s.createUsingJsonStream(realm, reader)", + fieldTypeCanonicalName, fieldName, proxyClass) .emitStatement("((%s) obj).%s(%sObj)", interfaceName, setter, fieldName) .endControlFlow(); } - //@formatter:on + // @formatter:on // @formatter:off public static void emitFillRealmListFromStream(String interfaceName, String getter, String setter, @@ -241,10 +162,9 @@ public static void emitFillRealmListFromStream(String interfaceName, String gett .emitStatement("reader.endArray()") .endControlFlow(); } - //@formatter:on + // @formatter:on private static class SimpleTypeConverter implements JsonToRealmFieldTypeConverter { - private final String castType; private final String jsonType; @@ -261,40 +181,38 @@ private SimpleTypeConverter(String castType, String jsonType) { } @Override - public void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer) throws IOException { - String statementSetNullOrThrow; - if (Utils.isPrimitiveType(fieldType)) { - // Only throw exception for primitive types. For boxed types and String, exception will be thrown in - // the setter. - statementSetNullOrThrow = String.format(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName); - } else { - statementSetNullOrThrow = String.format("((%s) obj).%s(null)", interfaceName, setter); - } + public void emitTypeConversion( + String interfaceName, String setter, String fieldName, String fieldType, JavaWriter writer) + throws IOException { + // Only throw exception for primitive types. + // For boxed types and String, exception will be thrown in the setter. + String statementSetNullOrThrow = Utils.isPrimitiveType(fieldType) ? + String.format(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) : + String.format("((%s) obj).%s(null)", interfaceName, setter); // @formatter:off writer .beginControlFlow("if (json.has(\"%s\"))", fieldName) .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) .emitStatement(statementSetNullOrThrow) .nextControlFlow("else") - .emitStatement("((%s) obj).%s((%s) json.get%s(\"%s\"))", interfaceName, setter, castType, - jsonType, fieldName) + .emitStatement( + "((%s) obj).%s((%s) json.get%s(\"%s\"))", + interfaceName, setter, castType, jsonType, fieldName) .endControlFlow() .endControlFlow(); - //@formatter:on + // @formatter:on } @Override - public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, - JavaWriter writer, boolean isPrimaryKey) throws IOException { - String statementSetNullOrThrow; - if (Utils.isPrimitiveType(fieldType)) { - // Only throw exception for primitive types. For boxed types and String, exception will be thrown in - // the setter. - statementSetNullOrThrow = String.format(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName); - } else { - statementSetNullOrThrow = String.format("((%s) obj).%s(null)", interfaceName, setter); - } + public void emitStreamTypeConversion( + String interfaceName, String setter, String fieldName, + String fieldType, JavaWriter writer, boolean isPrimaryKey) + throws IOException { + // Only throw exception for primitive types. For boxed types and String, exception will be thrown in + // the setter. + String statementSetNullOrThrow = (Utils.isPrimitiveType(fieldType)) ? + String.format(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) : + String.format("((%s) obj).%s(null)", interfaceName, setter); // @formatter:off writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") @@ -306,7 +224,7 @@ public void emitStreamTypeConversion(String interfaceName, String setter, String if (isPrimaryKey) { writer.emitStatement("jsonHasPrimaryKey = true"); } - //@formatter:on + // @formatter:on } // @formatter:off @@ -326,10 +244,100 @@ public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, .endControlFlow() .nextControlFlow("else") .emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) - .endControlFlow(); + .endControlFlow(); + } + // @formatter:on + } + + private static class DateTypeConverter implements JsonToRealmFieldTypeConverter { + // @formatter:off + @Override + public void emitTypeConversion( + String interfaceName, String setter, String fieldName, String fieldType, JavaWriter writer) + throws IOException { + writer + .beginControlFlow("if (json.has(\"%s\"))", fieldName) + .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + .emitStatement("((%s) obj).%s(null)", interfaceName, setter) + .nextControlFlow("else") + .emitStatement("Object timestamp = json.get(\"%s\")", fieldName) + .beginControlFlow("if (timestamp instanceof String)") + .emitStatement("((%s) obj).%s(JsonUtils.stringToDate((String) timestamp))", interfaceName, setter) + .nextControlFlow("else") + .emitStatement("((%s) obj).%s(new Date(json.getLong(\"%s\")))", interfaceName, setter, fieldName) + .endControlFlow() + .endControlFlow() + .endControlFlow(); + } + // @formatter:on + + // @formatter:off + @Override + public void emitStreamTypeConversion( + String interfaceName, String setter, String fieldName, String fieldType, JavaWriter writer, boolean isPrimaryKey) + throws IOException { + writer + .beginControlFlow("if (reader.peek() == JsonToken.NULL)") + .emitStatement("reader.skipValue()") + .emitStatement("((%s) obj).%s(null)", interfaceName, setter) + .nextControlFlow("else if (reader.peek() == JsonToken.NUMBER)") + .emitStatement("long timestamp = reader.nextLong()", fieldName) + .beginControlFlow("if (timestamp > -1)") + .emitStatement("((%s) obj).%s(new Date(timestamp))", interfaceName, setter) + .endControlFlow() + .nextControlFlow("else") + .emitStatement("((%s) obj).%s(JsonUtils.stringToDate(reader.nextString()))", interfaceName, setter) + .endControlFlow(); + } + // @formatter:on + + @Override + public void emitGetObjectWithPrimaryKeyValue( + String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) + throws IOException { + throw new IllegalArgumentException("'Date' is not allowed as a primary key value."); + } + } + + private static class ByteArrayTypeConverter implements JsonToRealmFieldTypeConverter { + // @formatter:off + @Override + public void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, + JavaWriter writer) throws IOException { + writer + .beginControlFlow("if (json.has(\"%s\"))", fieldName) + .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + .emitStatement("((%s) obj).%s(null)", interfaceName, setter) + .nextControlFlow("else") + .emitStatement( + "((%s) obj).%s(JsonUtils.stringToBytes(json.getString(\"%s\")))", + interfaceName, setter, fieldName) + .endControlFlow() + .endControlFlow(); + } + // @formatter:on + + // @formatter:off + @Override + public void emitStreamTypeConversion(String interfaceName, String setter, String fieldName, + String fieldType, JavaWriter writer, boolean isPrimaryKey) throws IOException { + writer + .beginControlFlow("if (reader.peek() == JsonToken.NULL)") + .emitStatement("reader.skipValue()") + .emitStatement("((%s) obj).%s(null)", interfaceName, setter) + .nextControlFlow("else") + .emitStatement("((%s) obj).%s(JsonUtils.stringToBytes(reader.nextString()))", interfaceName, setter) + .endControlFlow(); + } + // @formatter:on + + @Override + public void emitGetObjectWithPrimaryKeyValue( + String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) + throws IOException { + throw new IllegalArgumentException("'byte[]' is not allowed as a primary key value."); } } - //@formatter:on private interface JsonToRealmFieldTypeConverter { void emitTypeConversion(String interfaceName, String setter, String fieldName, String fieldType, From fb2966a5eec0dd4d381ba57df9c56b0b5047a130 Mon Sep 17 00:00:00 2001 From: abennsir Date: Fri, 16 Jun 2017 12:08:19 +0200 Subject: [PATCH 0753/2110] migrate deprecated roboelectric ActivityController #Closes 4698 --- .../unittesting/ExampleActivityTest.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java index d2467a2ffd..d4a035cd35 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java @@ -179,16 +179,14 @@ public void setup() throws Exception { } - @Ignore("FIXME: Some problems mocking OKHttp") @Test public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { doCallRealMethod().when(mockRealm).executeTransaction(Mockito.any(Realm.Transaction.class)); // Create activity - ActivityController controller = Robolectric.buildActivity(ExampleActivity.class).setup(); - ExampleActivity activity = controller.get(); + ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get(); - assertThat(activity.getTitle().toString(), is("Unit Test Example")); + assertThat(activity.getTitle().toString(), is("Unit Test Example")); // Verify that two Realm.getInstance() calls took place. verifyStatic(times(2)); @@ -214,7 +212,7 @@ public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { verify(mockRealm, times(2)).delete(Person.class); // Call the destroy method so we can verify that the .close() method was called (below) - controller.destroy(); + activity.onDestroy(); // Verify that the realm got closed 2 separate times. Once in the AsyncTask, once // in onDestroy @@ -225,15 +223,13 @@ public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { * Have to verify the transaction execution in a different test because * of a problem with Powermock: https://github.com/jayway/powermock/issues/649 */ - @Ignore("FIXME: Some problems mocking OKHttp") @Test public void shouldBeAbleToVerifyTransactionCalls() { // Create activity - ActivityController controller = Robolectric.buildActivity(ExampleActivity.class).setup(); - ExampleActivity activity = controller.get(); + ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get(); - assertThat(activity.getTitle().toString(), is("Unit Test Example")); + assertThat(activity.getTitle().toString(), is("Unit Test Example")); // Verify that two Realm.getInstance() calls took place. verifyStatic(times(2)); @@ -251,7 +247,7 @@ public void shouldBeAbleToVerifyTransactionCalls() { verify(mockRealm, times(5)).executeTransaction(Mockito.any(Realm.Transaction.class)); // Call the destroy method so we can verify that the .close() method was called (below) - controller.destroy(); + activity.onDestroy(); // Verify that the realm got closed 2 separate times. Once in the AsyncTask, once // in onDestroy From a1c22f1772ce4afbe4fa2a5df1b7c0da60852255 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 16 Jun 2017 20:05:53 +0200 Subject: [PATCH 0754/2110] Re-add Sync Progress Notifications (#4415) --- CHANGELOG.md | 1 + examples/objectServerExample/build.gradle | 2 + .../objectserver/CounterActivity.java | 60 +++ .../src/main/res/layout/activity_counter.xml | 15 +- .../src/main/res/values/realm_colors.xml | 11 +- .../java/io/realm/ProgressTests.java | 72 ++++ .../java/io/realm/SessionTests.java | 48 +++ .../src/main/cpp/io_realm_SyncSession.cpp | 84 +++- .../src/main/cpp/jni_util/java_local_ref.hpp | 3 +- realm/realm-library/src/main/cpp/util.hpp | 4 + .../java/io/realm/internal/util/Pair.java | 4 +- .../objectServer/java/io/realm/Progress.java | 139 ++++++ .../java/io/realm/ProgressListener.java | 56 +++ .../java/io/realm/ProgressMode.java | 47 ++ .../java/io/realm/SyncManager.java | 24 +- .../java/io/realm/SyncSession.java | 119 ++++++ .../java/io/realm/BaseIntegrationTest.java | 1 - .../java/io/realm/SyncedRealmTests.java | 24 +- .../objectserver/ProgressListenerTests.java | 401 ++++++++++++++++++ 19 files changed, 1073 insertions(+), 42 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/Progress.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 933c05ec62..3dc541ed7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements +* [ObjectServer] Added support for Sync Progress Notifications through `SyncSession.addDownloadProgressListener(ProgressMode, ProgressListener)` and `SyncSession.addUploadProgressListener(ProgressMode, ProgressListener)` (#4104). * Added support for querying inverse relationships (#2904). * Moved inverse relationships out of beta stage. * Added `Realm.getDefaultConfiguration()` (#4725). diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index e3080cda9f..e976f16d01 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -61,7 +61,9 @@ realm { dependencies { compile 'com.android.support:support-v4:25.2.0' + compile 'com.android.support:appcompat-v7:25.2.0' compile 'com.android.support:design:25.2.0' + compile 'me.zhanghai.android.materialprogressbar:library:1.3.0' compile 'com.jakewharton:butterknife:8.5.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 97c67d2443..536b24ef90 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -17,32 +17,67 @@ package io.realm.examples.objectserver; import android.content.Intent; +import android.graphics.PorterDuff; import android.os.Bundle; +import android.support.annotation.ColorRes; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.widget.TextView; import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; +import io.realm.Progress; +import io.realm.ProgressListener; +import io.realm.ProgressMode; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.examples.objectserver.model.CRDTCounter; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; public class CounterActivity extends AppCompatActivity { private static final String REALM_URL = "realm://" + BuildConfig.OBJECT_SERVER_IP + ":9080/~/default"; private Realm realm; + private SyncSession session; private CRDTCounter counter; private SyncUser user; + private AtomicBoolean downloadingChanges = new AtomicBoolean(false); + private AtomicBoolean uploadingChanges = new AtomicBoolean(false); + private ProgressListener downloadListener = new ProgressListener() { + @Override + public void onChange(Progress progress) { + downloadingChanges.set(!progress.isTransferComplete()); + runOnUiThread(updateProgressBar); + } + }; + private ProgressListener uploadListener = new ProgressListener() { + @Override + public void onChange(Progress progress) { + uploadingChanges.set(!progress.isTransferComplete()); + runOnUiThread(updateProgressBar); + } + }; + private Runnable updateProgressBar = new Runnable() { + @Override + public void run() { + updateProgressBar(downloadingChanges.get(), uploadingChanges.get()); + } + }; + @BindView(R.id.text_counter) TextView counterView; + @BindView(R.id.progressbar) MaterialProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { @@ -86,12 +121,21 @@ public void onChange(CRDTCounter counter) { } }); counterView.setText("0"); + + // Setup progress listeners for indeterminate progress bars + session = SyncManager.getSession(config); + session.addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener); + session.addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener); } } @Override protected void onStop() { super.onStop(); + if (session != null) { + session.removeProgressListener(downloadListener); + session.removeProgressListener(uploadListener); + } closeRealm(); user = null; } @@ -132,6 +176,22 @@ public void decrementCounter() { adjustCounter(-1); } + private void updateProgressBar(boolean downloading, boolean uploading) { + @ColorRes int color = android.R.color.black; + int visibility = View.VISIBLE; + if (downloading && uploading) { + color = R.color.progress_both; + } else if (downloading) { + color = R.color.progress_download; + } else if (uploading) { + color = R.color.progress_upload; + } else { + visibility = View.GONE; + } + progressBar.getIndeterminateDrawable().setColorFilter(getResources().getColor(color), PorterDuff.Mode.SRC_IN); + progressBar.setVisibility(visibility); + } + private void adjustCounter(final int adjustment) { // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI // thread is HIGHLY discouraged as it might block longer than intended. Only use async transactions. diff --git a/examples/objectServerExample/src/main/res/layout/activity_counter.xml b/examples/objectServerExample/src/main/res/layout/activity_counter.xml index 62127eca0d..df73031aa7 100644 --- a/examples/objectServerExample/src/main/res/layout/activity_counter.xml +++ b/examples/objectServerExample/src/main/res/layout/activity_counter.xml @@ -1,7 +1,9 @@ + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + diff --git a/examples/objectServerExample/src/main/res/values/realm_colors.xml b/examples/objectServerExample/src/main/res/values/realm_colors.xml index aada8ea195..3d435a5c44 100644 --- a/examples/objectServerExample/src/main/res/values/realm_colors.xml +++ b/examples/objectServerExample/src/main/res/values/realm_colors.xml @@ -1,12 +1,12 @@ - // Grays + #1C233F #9A9BA5 #b1b3bf #EBEBF2 - // Orb colors + #39477F #59569E #9A59A5 @@ -16,8 +16,13 @@ #FC9F95 #FCC397 - // Material adjustments + #d64881 #dadada + + #EF5350 + #9CCC65 + #FFA726 + \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java new file mode 100644 index 0000000000..22c31bc2a5 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class ProgressTests { + + @Test + public void getFractionTransferred() { + Object[][] testData = { + { 0L, 0L, 1.0D }, + { 0L, 1L, 0.0D }, + { 1L, 1L, 1.0D }, + { 1L, 2L, 0.5D } + }; + + for (Object[] test : testData) { + long transferredBytes = (long) test[0]; + long transferableBytes = (long) test[1]; + double fraction = (double) test[2]; + Progress progress = new Progress(transferredBytes, transferableBytes); + String errorMessage = String.format(Locale.US, "Failed with: (%d, %d)", transferredBytes, transferableBytes); + assertEquals(errorMessage, fraction, progress.getFractionTransferred(), 0.0D); + } + } + + @Test + public void getTransferredBytes () { + long[] testData = { 0, Long.MAX_VALUE }; + + for (long transferredBytes : testData) { + String errorMessage = String.format(Locale.US, "Failed with: %d", transferredBytes); + Progress progress = new Progress(transferredBytes, Long.MAX_VALUE); + assertEquals(errorMessage, transferredBytes, progress.getTransferredBytes()); + } + } + + @Test + public void getTransferableBytes () { + long[] testData = { 0, Long.MAX_VALUE }; + + for (long transferableBytes : testData) { + String errorMessage = String.format(Locale.US, "Failed with: %d", transferableBytes); + Progress progress = new Progress(0, transferableBytes); + assertEquals(errorMessage, transferableBytes, progress.getTransferableBytes()); + } + } + +} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index d5c949a1bf..95bb08e745 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -61,6 +61,54 @@ public void get_syncValues() { assertEquals(configuration, session.getConfiguration()); } + @Test + public void addDownloadProgressListener_nullThrows() { + SyncSession session = SyncManager.getSession(configuration); + try { + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void addUploadProgressListener_nullThrows() { + SyncSession session = SyncManager.getSession(configuration); + try { + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void removeProgressListener() { + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getSession(configuration); + ProgressListener[] listeners = new ProgressListener[] { + null, + new ProgressListener() { + @Override + public void onChange(Progress progress) { + // Listener 1, not present + } + }, + new ProgressListener() { + @Override + public void onChange(Progress progress) { + // Listener 2, present + } + } + }; + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, listeners[2]); + + // Check that remove works unconditionally for all input + for (ProgressListener listener : listeners) { + session.removeProgressListener(listener); + } + realm.close(); + } + // Check that a Client Reset is correctly reported. @Test @RunTestInLooperThread diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 8165ea5cb5..37bf4c087a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "io_realm_SyncSession.h" @@ -28,23 +29,22 @@ #include "jni_util/java_local_ref.hpp" #include "jni_util/jni_utils.hpp" -using namespace std; using namespace realm; using namespace jni_util; using namespace sync; JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JNIEnv* env, jclass, - jstring localRealmPath, - jstring accessToken, - jstring sync_realm_url) + jstring j_local_realm_path, + jstring j_access_token, + jstring j_sync_realm_url) { TR_ENTER() try { - JStringAccessor local_realm_path(env, localRealmPath); + JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); if (session) { - JStringAccessor access_token(env, accessToken); - JStringAccessor realm_url(env, sync_realm_url); + JStringAccessor access_token(env, j_access_token); + JStringAccessor realm_url(env, j_sync_realm_url); session->refresh_access_token(access_token, std::string(realm_url)); return JNI_TRUE; } @@ -56,13 +56,79 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JN return JNI_FALSE; } +JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIEnv* env, jclass, + jstring j_local_realm_path, + jlong listener_id, jint direction, + jboolean is_streaming) +{ + try { + // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda + std::string local_realm_path(JStringAccessor(env, j_local_realm_path)); + std::shared_ptr session = SyncManager::shared().get_existing_active_session(local_realm_path); + if (!session) { + // FIXME: We should lift this restriction + ThrowException(env, IllegalState, + "Cannot register a progress listener before a session is " + "created. A session will be created after the first call to Realm.getInstance()."); + return 0; + } + + SyncSession::NotifierType type = + (direction == 1) ? SyncSession::NotifierType::download : SyncSession::NotifierType::upload; + + static JavaClass java_syncmanager_class(env, "io/realm/SyncManager"); + static JavaMethod java_notify_progress_listener(env, java_syncmanager_class, "notifyProgressListener", "(Ljava/lang/String;JJJ)V", true); + + std::function callback = [local_realm_path, listener_id]( + uint64_t transferred, uint64_t transferrable) { + JNIEnv* local_env = jni_util::JniUtils::get_env(true); + + auto path = to_jstring(local_env, local_realm_path); + local_env->CallStaticVoidMethod(java_syncmanager_class, java_notify_progress_listener, path, listener_id, + static_cast(transferred), static_cast(transferrable)); + + // All exceptions will be caught on the Java side of handlers, but Errors will still end + // up here, so we need to do something sensible with them. + // Throwing a C++ exception will terminate the sync thread and cause the pending Java + // exception to become visible. For some (unknown) reason Logcat will not see the C++ + // exception, only the Java one. + if (local_env->ExceptionCheck()) { + local_env->ExceptionDescribe(); + throw std::runtime_error("An unexpected Error was thrown from Java. See LogCat"); + } + + // Callback happens on a thread not controlled by the JVM. So manual cleanup is + // required. + local_env->DeleteLocalRef(path); + }; + uint64_t token = session->register_progress_notifier(callback, type, to_bool(is_streaming)); + return static_cast(token); + } + CATCH_STD() + return 0; +} + +JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveProgressListener(JNIEnv* env, jclass, + jstring j_local_realm_path, + jlong listener_token) +{ + try { + JStringAccessor local_realm_path(env, j_local_realm_path); + std::shared_ptr session = SyncManager::shared().get_existing_active_session(local_realm_path); + if (session) { + session->unregister_progress_notifier(static_cast(listener_token)); + } + } + CATCH_STD() +} + JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadCompletion(JNIEnv* env, jobject session_object, - jstring localRealmPath) + jstring j_local_realm_path) { TR_ENTER() try { - JStringAccessor local_realm_path(env, localRealmPath); + JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); if (session) { diff --git a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp index 3026ee5a08..4a57062471 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp @@ -29,8 +29,7 @@ static constexpr NeedToCreateLocalRef need_to_create_local_ref{}; // Wraps jobject and automatically calls DeleteLocalRef when this object is destroyed. // DeleteLocalRef is not necessary to be called in most cases since all local references will be cleaned up when the // program returns to Java from native. But if the local ref is created in a loop, consider to use this class to wrap -// it -// because the size of local reference table is relative small (512 bytes on Android). +// it because the size of local reference table is relative small (512 bytes on Android). template class JavaLocalRef { public: diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 29c2813d17..2a155f8915 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -703,6 +703,10 @@ extern jclass java_lang_string; extern jmethodID java_lang_double_init; extern jclass java_util_date; extern jmethodID java_util_date_init; +#if REALM_ENABLE_SYNC +extern jclass java_syncmanager_class; +extern jmethodID java_notify_progress_listener; +#endif inline jobject NewLong(JNIEnv* env, int64_t value) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java index 6990faf5ae..86fcd889d5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java +++ b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java @@ -27,8 +27,8 @@ * objects. */ public class Pair { - public final F first; - public final S second; + public F first; + public S second; /** * Constructor for a Pair. diff --git a/realm/realm-library/src/objectServer/java/io/realm/Progress.java b/realm/realm-library/src/objectServer/java/io/realm/Progress.java new file mode 100644 index 0000000000..77a6c01f78 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/Progress.java @@ -0,0 +1,139 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import io.realm.log.RealmLog; + + +/** + * Class used to encapsulate progress notifications when either downloading or uploading Realm data. + * Each instance of this class is an immutable snapshot of the current progress. + *

            + * If the {@link ProgressListener} was registered with {@link ProgressMode#INDEFINITELY}, the progress reported by + * {@link #getFractionTransferred()} can both increase and decrease since more changes might be added while + * the progres listener is registered. This means it is possible for one notification to report + * {@code true} for {@link #isTransferComplete()}, and then on the next event report {@code false}. + *

            + * If the {@link ProgressListener} was registered with {@link ProgressMode#CURRENT_CHANGES}, progress can only ever + * increase, and once {@link #isTransferComplete()} returns {@code true}, no further events will be generated. + * + * @see SyncSession#addDownloadProgressListener(ProgressMode, ProgressListener) + * @see SyncSession#addUploadProgressListener(ProgressMode, ProgressListener) + */ +public class Progress { + + private final long transferredBytes; + private final long transferableBytes; + + /** + * Creates a snapshot of the current progress when downloading or uploading changes. + * + * @param transferredBytes number of bytes transferred. + * @param transferableBytes total number of bytes that needs to be transferred (including those already transferred). + */ + Progress(long transferredBytes, long transferableBytes) { + this.transferredBytes = transferredBytes; + this.transferableBytes = transferableBytes; + } + + /** + * Returns the total number of bytes that has been transferred since the {@link ProgressListener} was added. + * + * @return the total number of bytes transferred since the {@link ProgressListener} was added. + */ + public long getTransferredBytes() { + return transferredBytes; + } + + /** + * Returns the total number of transferable bytes (bytes that have been transferred + bytes pending transfer). + *

            + * If the {@link ProgressListener} is tracking downloads, this number represents the size of the changesets + * generated by all other clients using the Realm. + *

            + * If the {@link ProgressListener} is tracking uploads, this number represents the size of changesets created + * locally. + * + * @return the total number of bytes that has been transferred + number of bytes still pending transfer. + */ + public long getTransferableBytes() { + return transferableBytes; + } + + /** + * The fraction of bytes transferred out of all transferable bytes. Counting from since the {@link ProgressListener} + * was added. + * + * @return a number between {@code 0.0} and {@code 1.0}, where {@code 0.0} represents that no data has been + * transferred yet, and {@code 1.0} that all data has been transferred. + */ + public double getFractionTransferred() { + if (transferableBytes == 0) { + return 1.0D; + } else { + double percentage = (double) transferredBytes / (double) transferableBytes; + if (percentage > 1.0D) { + RealmLog.error("Invalid progress state: %s", this); + return 1.0D; + } else { + return percentage; + } + } + } + + /** + * Returns {@code true} when all pending bytes have been transferred. + *

            + * If the {@link ProgressListener} was registered with {@link ProgressMode#INDEFINITELY}, this method can return + * {@code false} for subsequent events after returning {@code true}. + *

            + * If the {@link ProgressListener} was registered with {@link ProgressMode#CURRENT_CHANGES}, when this method + * returns {@code true}, no more progress events will be sent. + * + * @return {@code true} if all changes have been transferred, {@code false} otherwise. + */ + public boolean isTransferComplete() { + return transferredBytes >= transferableBytes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Progress progress = (Progress) o; + + if (transferredBytes != progress.transferredBytes) return false; + return transferableBytes == progress.transferableBytes; + + } + + @Override + public int hashCode() { + int result = (int) (transferredBytes ^ (transferredBytes >>> 32)); + result = 31 * result + (int) (transferableBytes ^ (transferableBytes >>> 32)); + return result; + } + + @Override + public String toString() { + return "Progress{" + + "transferredBytes=" + transferredBytes + + ", transferableBytes=" + transferableBytes + + '}'; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java b/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java new file mode 100644 index 0000000000..5b5798f2d4 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * Interface used when interested in updates on data either being uploaded to or downloaded from + * a Realm Object Server. + */ +public interface ProgressListener { + /** + * This method will be called periodically from the underlying Object Server Client responsible + * for uploading and downloading changes from the remote Object Server. + *

            + * This callback will not happen on the UI thread, but on the worker thread controlling + * the Object Server Client. Use {@code Activity.runOnUiThread(Runnable)} or similar to update + * any UI elements. + *

            + *

            +     * {@code
            +     * // Adding an upload progress listener that completes when all known changes have been
            +     * // uploaded.
            +     * session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() {
            +     *   \@Override
            +     *    public void onChange(Progress progress) {
            +     *      activity.runOnUiThread(new Runnable() {
            +     *        \@Override
            +     *         public void run() {
            +     *           updateProgressBar(progress);
            +     *         }
            +     *      });
            +     *      if (progress.isTransferComplete() {
            +     *        session.removeProgressListener(this);
            +     *      }
            +     *    }
            +     * });
            +     * }
            +     * 
            + * + * @param progress an immutable progress change event with information about current progress. This object is thread safe. + */ + void onChange(Progress progress); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java b/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java new file mode 100644 index 0000000000..f80f63150d --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * Enum describing how to listen to progress changes. + */ +public enum ProgressMode { + /** + * When registering the {@link ProgressListener}, it will record the current size of changes, and will only + * continue to report progress updates until those changes have been either downloaded or uploaded. After that + * the progress listener will not report any further changes. + *

            + * This means that listeners registered in this mode should be done before changes are written to + * the Realm. + *

            + * Progress reported in this mode will only ever increase. + *

            + * This is useful when e.g. reporting progress when downloading a Realm for the first time. + */ + CURRENT_CHANGES, + + /** + * A {@link ProgressListener} registered in this mode, will continue to report progress changes, even + * if changes are being added after the listener was registered. + *

            + * Progress reported in this mode can both increase and decrease, e.g. if large amounts of data is + * written after registering the listener. + *

            + * This is useful when you want to track if all changes have been uploaded to the server from the device. + */ + INDEFINITELY +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 906454f00c..63ef781fca 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -16,15 +16,16 @@ package io.realm; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.Keep; +import io.realm.internal.KeepMember; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.network.OkHttpAuthenticationServer; @@ -91,7 +92,7 @@ public void onError(SyncSession session, ObjectServerError error) { } }; // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path' - private static Map sessions = new HashMap(); + private static Map sessions = new ConcurrentHashMap<>(); private static CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList(); // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. @@ -278,6 +279,25 @@ private static synchronized void notifyNetworkIsBack() { } } + /** + * All progress listener events from native Sync are reported to this method. + * It costs 2 HashMap lookups for each listener triggered (one to find the session, one to + * find the progress listener), but it means we don't have to cache anything on the C++ side which + * can leak since we don't have control over the session lifecycle. + */ + @SuppressWarnings("unused") + @KeepMember + private static synchronized void notifyProgressListener(String localRealmPath, long listenerId, long transferedBytes, long transferableBytes) { + SyncSession session = sessions.get(localRealmPath); + if (session != null) { + try { + session.notifyProgressListener(listenerId, transferedBytes, transferableBytes); + } catch (Exception exception) { + RealmLog.error(exception); + } + } + } + /** * This is called from the Object Store (through JNI) to request an {@code access_token} for * the session specified by sessionPath. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 408d1daec2..601692f367 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -17,12 +17,17 @@ package io.realm; import java.net.URI; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import io.realm.internal.Keep; @@ -36,6 +41,7 @@ import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; +import io.realm.internal.util.Pair; import io.realm.log.RealmLog; /** @@ -53,6 +59,8 @@ public class SyncSession { private final static ScheduledThreadPoolExecutor REFRESH_TOKENS_EXECUTOR = new ScheduledThreadPoolExecutor(1); private final static long REFRESH_MARGIN_DELAY = TimeUnit.SECONDS.toMillis(10); + private final static int DIRECTION_DOWNLOAD = 1; + private final static int DIRECTION_UPLOAD = 2; private final SyncConfiguration configuration; private final ErrorHandler errorHandler; @@ -65,6 +73,19 @@ public class SyncSession { private final AtomicReference waitingForServerChanges = new AtomicReference<>(null); private final Object waitForChangesMutex = new Object(); + // We need JavaId -> Listener so C++ can trigger callbacks without keeping a reference to the + // jobject, which would require a similar map on the C++ side. + // We need Listener -> Token map in order to remove the progress listener in C++ from Java. + private final Map> listenerIdToProgressListenerMap = new HashMap<>(); + private final Map progressListenerToOsTokenMap = new IdentityHashMap<>(); + // Counter used to assign all ProgressListeners on this session with a unique id. + // ListenerId is created by Java to enable C++ to reference the java listener without holding + // a reference to the actual object. + // ListenerToken is the same concept, but created by OS and represents the listener. + // We can unfortunately not just use the ListenerToken, since we need it to be available before + // we register the listener. + private final AtomicLong progressListenerId = new AtomicLong(-1); + SyncSession(SyncConfiguration configuration) { this.configuration = configuration; this.errorHandler = configuration.getErrorHandler(); @@ -115,6 +136,102 @@ void notifySessionError(int errorCode, String errorMessage) { } } + synchronized void notifyProgressListener(long listenerId, long transferredBytes, long transferableBytes) { + Pair listener = listenerIdToProgressListenerMap.get(listenerId); + if (listener != null) { + Progress newProgressNotification = new Progress(transferredBytes, transferableBytes); + if (!newProgressNotification.equals(listener.second)) { + listener.second = newProgressNotification; + listener.first.onChange(newProgressNotification); + } + } else { + RealmLog.debug("Trying unknown listener failed: " + listenerId); + } + } + + /** + * Adds a progress listener tracking changes that need to be downloaded from the Realm Object + * Server. + *

            + * The {@link ProgressListener} will be triggered immediately when registered, and periodically + * afterwards. + * + * @param mode type of mode used. See {@link ProgressMode} for more information. + * @param listener the listener to register. + */ + public synchronized void addDownloadProgressListener(ProgressMode mode, ProgressListener listener) { + addProgressListener(mode, DIRECTION_DOWNLOAD, listener); + } + + /** + * Adds a progress listener tracking changes that need to be uploaded from the device to the + * Realm Object Server. + *

            + * The {@link ProgressListener} will be triggered immediately when registered, and periodically + * afterwards. + * + * @param mode type of mode used. See {@link ProgressMode} for more information. + * @param listener the listener to register. + */ + public synchronized void addUploadProgressListener(ProgressMode mode, ProgressListener listener) { + addProgressListener(mode, DIRECTION_UPLOAD, listener); + } + + /** + * Removes a progress listener. If the listener wasn't registered, this method will do nothing. + * + * @param listener listener to remove. + */ + public synchronized void removeProgressListener(ProgressListener listener) { + if (listener == null) { + return; + } + // If an exception is thrown somewhere in here, we will most likely leave the various + // maps in an inconsistent manner. Not much we can do about it. + Long token = progressListenerToOsTokenMap.remove(listener); + if (token != null) { + Iterator>> it = listenerIdToProgressListenerMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry> entry = it.next(); + if (entry.getValue().first.equals(listener)) { + it.remove(); + break; + } + } + nativeRemoveProgressListener(configuration.getPath(), token); + } + } + + private void addProgressListener(ProgressMode mode, int direction, ProgressListener listener) { + checkProgressListenerArguments(mode, listener); + boolean isStreaming = (mode == ProgressMode.INDEFINITELY); + long listenerId = progressListenerId.incrementAndGet(); + + // A listener might be triggered immediately as part of `nativeAddProgressListener`, so + // we need to make sure it can be found by SyncManager.notifyProgressListener() + listenerIdToProgressListenerMap.put(listenerId, new Pair(listener, null)); + long listenerToken = nativeAddProgressListener(configuration.getPath(), listenerId , direction, isStreaming); + if (listenerToken == 0) { + // ObjectStore did not register the listener. This can happen if a + // listener is registered with ProgressMode.CURRENT_CHANGES and no changes actually + // exists. In that case the listener was triggered immediately and we just need + // to clean it up, since it will never be called again. + listenerIdToProgressListenerMap.remove(listenerId); + } else { + // Listener was properly registered. + progressListenerToOsTokenMap.put(listener, listenerToken); + } + } + + private void checkProgressListenerArguments(ProgressMode mode, ProgressListener listener) { + if (listener == null) { + throw new IllegalArgumentException("Non-null 'listener' required."); + } + if (mode == null) { + throw new IllegalArgumentException("Non-null 'mode' required."); + } + } + void close() { isClosed = true; if (networkRequest != null) { @@ -470,6 +587,8 @@ public void throwExceptionIfNeeded() { } } + private static native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); + private static native void nativeRemoveProgressListener(String localRealmPath, long listenerToken); private static native boolean nativeRefreshAccessToken(String path, String accessToken, String realmUrl); private native boolean nativeWaitForDownloadCompletion(String path); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 3a46d16bd6..9f86b3200e 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -113,7 +113,6 @@ public void run() { } } - // Cleanup filesystem to make sure nothing lives for the next test. // Failing to do so might lead to DIVERGENT_HISTORY errors being thrown if Realms from // previous tests are being accessed. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index d105b6505d..be5da797a7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -17,7 +17,6 @@ package io.realm; import android.os.SystemClock; -import android.support.annotation.NonNull; import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; @@ -27,15 +26,14 @@ import java.io.File; import java.util.Random; import java.util.UUID; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.StringOnly; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.objectserver.utils.Constants; import io.realm.rule.RunTestInLooperThread; +import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -51,8 +49,7 @@ public class SyncedRealmTests extends BaseIntegrationTest { @Test @UiThreadTest public void waitForInitialRemoteData_mainThreadThrows() { - final SyncUser user = loginUser(); - + final SyncUser user = SyncTestUtils.createTestUser(Constants.AUTH_URL); SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) .waitForInitialRemoteData() .build(); @@ -69,23 +66,6 @@ public void waitForInitialRemoteData_mainThreadThrows() { } } - // Login user on a worker thread, so this method can be used from both UI and non-ui threads. - @NonNull - private SyncUser loginUser() { - final CountDownLatch userReady = new CountDownLatch(1); - final AtomicReference user = new AtomicReference<>(); - new Thread(new Runnable() { - @Override - public void run() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - user.set(SyncUser.login(credentials, Constants.AUTH_URL)); - userReady.countDown(); - } - }).start(); - TestHelper.awaitOrFail(userReady); - return user.get(); - } - @Test public void waitForInitialRemoteData() { String username = UUID.randomUUID().toString(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java new file mode 100644 index 0000000000..dcd1dffb8f --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -0,0 +1,401 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver; + +import android.support.annotation.NonNull; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import io.realm.BaseIntegrationTest; +import io.realm.Progress; +import io.realm.ProgressListener; +import io.realm.ProgressMode; +import io.realm.Realm; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.SyncSession; +import io.realm.SyncUser; +import io.realm.TestHelper; +import io.realm.entities.AllTypes; +import io.realm.log.RealmLog; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.UserFactory; +import io.realm.rule.TestSyncConfigurationFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class ProgressListenerTests extends BaseIntegrationTest { + + private static final long TEST_SIZE = 10; + @Rule + public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @NonNull + private SyncConfiguration createSyncConfig() { + SyncUser user = UserFactory.createAdminUser(Constants.AUTH_URL); + return configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL).build(); + } + + private void writeSampleData(Realm realm) { + realm.beginTransaction(); + for (int i = 0; i < TEST_SIZE; i++) { + AllTypes obj = realm.createObject(AllTypes.class); + obj.setColumnString("Object " + i); + } + realm.commitTransaction(); + } + + private void assertTransferComplete(Progress progress, boolean nonZeroChange) { + assertTrue(progress.isTransferComplete()); + assertEquals(1.0D, progress.getFractionTransferred(), 0.0D); + assertEquals(progress.getTransferableBytes(), progress.getTransferredBytes()); + if (nonZeroChange) { + assertTrue(progress.getTransferredBytes() > 0); + } + } + + // Create remote data for a given user. + private URI createRemoteData(SyncUser user) { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); + final Realm realm = Realm.getInstance(config); + writeSampleData(realm); + final CountDownLatch changesUploaded = new CountDownLatch(1); + final SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + session.removeProgressListener(this); + changesUploaded.countDown(); + } + } + }); + TestHelper.awaitOrFail(changesUploaded); + realm.close(); + return config.getServerUrl(); + } + + @Test + public void downloadProgressListener_changesOnly() { + final CountDownLatch allChangesDownloaded = new CountDownLatch(1); + SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); + URI serverUrl = createRemoteData(userWithData); + SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()).build(); + Realm realm = Realm.getInstance(config); + SyncSession session = SyncManager.getSession(config); + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + assertTransferComplete(progress, true); + Realm realm = Realm.getInstance(config); + assertEquals(TEST_SIZE, realm.where(AllTypes.class).count()); + realm.close(); + allChangesDownloaded.countDown(); + } + } + }); + TestHelper.awaitOrFail(allChangesDownloaded); + realm.close(); + userWithData.logout(); + adminUser.logout(); + } + + @Test + public void downloadProgressListener_indefinitely() throws InterruptedException { + final AtomicInteger transferCompleted = new AtomicInteger(0); + final CountDownLatch allChangesDownloaded = new CountDownLatch(1); + final CountDownLatch startWorker = new CountDownLatch(1); + final SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); + + URI serverUrl = createRemoteData(userWithData); + + // Create worker thread that puts data into another Realm. + // This is to avoid blocking one progress listener while waiting for another to complete. + Thread worker = new Thread(new Runnable() { + @Override + public void run() { + TestHelper.awaitOrFail(startWorker); + createRemoteData(userWithData); + } + }); + worker.start(); + + SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + final SyncConfiguration adminConfig = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()).build(); + Realm adminRealm = Realm.getInstance(adminConfig); + Realm userRealm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM).build()); // Keep session alive + SyncSession session = SyncManager.getSession(adminConfig); + session.addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + switch (transferCompleted.incrementAndGet()) { + case 1: + // Initial trigger when registering + assertTransferComplete(progress, false); + break; + case 2: { + assertTransferComplete(progress, true); + Realm adminRealm = Realm.getInstance(adminConfig); + assertEquals(TEST_SIZE, adminRealm.where(AllTypes.class).count()); + adminRealm.close(); + startWorker.countDown(); + break; + } + case 3: { + assertTransferComplete(progress, true); + Realm adminRealm = Realm.getInstance(adminConfig); + assertEquals(TEST_SIZE * 2, adminRealm.where(AllTypes.class).count()); + adminRealm.close(); + allChangesDownloaded.countDown(); + break; + } + default: + fail(); + } + } + } + }); + TestHelper.awaitOrFail(allChangesDownloaded); + adminRealm.close(); + userRealm.close(); + userWithData.logout(); + adminUser.logout(); + worker.join(); + } + + // Make sure that a ProgressListener continues to report the correct thing, even if it crashed + @Test + public void uploadListener_worksEvenIfCrashed() throws InterruptedException { + final AtomicInteger transferCompleted = new AtomicInteger(0); + final CountDownLatch testDone = new CountDownLatch(1); + final SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + + writeSampleData(realm); // Write first batch of sample data + SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + switch(transferCompleted.incrementAndGet()) { + case 1: + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + realm.close(); + throw new RuntimeException("Crashing the changelistener"); + case 2: + assertTransferComplete(progress, true); + testDone.countDown(); + break; + default: + fail("Unsupported number of transfers completed: " + transferCompleted.get()); + } + } + } + }); + + TestHelper.awaitOrFail(testDone); + realm.close(); + } + + + @Test + public void uploadProgressListener_changesOnly() { + final CountDownLatch allChangeUploaded = new CountDownLatch(1); + SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + + SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + assertTransferComplete(progress, true); + allChangeUploaded.countDown(); + } + } + }); + + TestHelper.awaitOrFail(allChangeUploaded); + realm.close(); + } + + @Test + public void uploadProgressListener_indefinitely() { + final AtomicInteger transferCompleted = new AtomicInteger(0); + final CountDownLatch testDone = new CountDownLatch(1); + final SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + + writeSampleData(realm); // Write first batch of sample data + SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + switch(transferCompleted.incrementAndGet()) { + case 1: + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + realm.close(); + break; + case 2: + assertTransferComplete(progress, true); + testDone.countDown(); + break; + default: + fail("Unsupported number of transfers completed: " + transferCompleted.get()); + } + } + } + }); + + TestHelper.awaitOrFail(testDone); + realm.close(); + } + + @Test + public void addListenerInsideCallback() { + final CountDownLatch allChangeUploaded = new CountDownLatch(1); + final SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + + final SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + realm.close(); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + allChangeUploaded.countDown(); + } + } + }); + } + } + }); + + TestHelper.awaitOrFail(allChangeUploaded); + realm.close(); + } + + @Test + public void addListenerInsideCallback_mixProgressModes() { + final CountDownLatch allChangeUploaded = new CountDownLatch(3); + final AtomicBoolean progressCompletedReported = new AtomicBoolean(false); + final SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + + final SyncSession session = SyncManager.getSession(config); + session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + allChangeUploaded.countDown(); + if (progressCompletedReported.compareAndSet(false, true)) { + Realm realm = Realm.getInstance(config); + writeSampleData(realm); + realm.close(); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + allChangeUploaded.countDown(); + } + } + }); + } + } + } + }); + + TestHelper.awaitOrFail(allChangeUploaded); + realm.close(); + } + + @Test + public void addProgressListener_triggerImmediatelyWhenRegistered() { + final SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + SyncSession session = SyncManager.getSession(config); + + checkListener(session, ProgressMode.INDEFINITELY); + checkListener(session, ProgressMode.CURRENT_CHANGES); + + realm.close(); + } + + @Test + public void uploadListener_keepIncreasingInSize() { + SyncConfiguration config = createSyncConfig(); + Realm realm = Realm.getInstance(config); + SyncSession session = SyncManager.getSession(config); + for (int i = 0; i < 10; i++) { + final CountDownLatch changesUploaded = new CountDownLatch(1); + writeSampleData(realm); + final int testNo = i; + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + RealmLog.info("Test %s -> %s", Integer.toString(testNo), progress.toString()); + if (progress.isTransferComplete()) { + assertTransferComplete(progress, true); + changesUploaded.countDown(); + } + } + }); + TestHelper.awaitOrFail(changesUploaded); + } + + realm.close(); + } + + private void checkListener(SyncSession session, ProgressMode progressMode) { + final CountDownLatch listenerCalled = new CountDownLatch(1); + session.addDownloadProgressListener(progressMode, new ProgressListener() { + @Override + public void onChange(Progress progress) { + listenerCalled.countDown(); + } + }); + TestHelper.awaitOrFail(listenerCalled); + } + +} From 80d3c1f80c4e070356e106ec2722976e11e651ba Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 19 Jun 2017 09:57:05 +0800 Subject: [PATCH 0755/2110] Converting nullable PK fields with null values (#4798) - RealmObjectSchema.setRequired() will throw if the PK field has null values stored. - Call set_xxx_unique when converting nullability on a PK field. - Remove useless check in the proxy generator which caused inconsistency exception. - Add relevant test cases. --- CHANGELOG.md | 1 + .../processor/RealmProxyClassGenerator.java | 33 ++--- .../java/io/realm/RealmMigrationTests.java | 15 +- .../java/io/realm/RealmObjectSchemaTests.java | 138 ++++++++++++++++-- .../src/main/cpp/io_realm_internal_Table.cpp | 93 +++++++++--- .../main/java/io/realm/internal/Table.java | 8 +- 6 files changed, 216 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 792ba98b32..f20859b33a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Bug Fixes * When converting nullable BLOB field to required, `null` values should be converted to `byte[0]` instead of `byte[1]`. +* Fixed a bug which may cause duplicated primary key values when migrating a nullable primary key field to not nullable. `RealmObjectSchema.setRequired()` and `RealmObjectSchema.setNullable()` will throw when converting a nullable primary key field with null values stored to a required primary key field. ### Internal diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 61b7f10fa0..e2eaedd57d 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -805,30 +805,19 @@ private void emitValidateRealmType(JavaWriter writer, VariableElement field, Str } writer.endControlFlow(); } else { - // check before migrating a nullable field containing null value to not-nullable PrimaryKey field for Realm version 0.89+ - if (metadata.isPrimaryKey(field)) { - writer - .beginControlFlow("if (table.isColumnNullable(%s) && table.findFirstNull(%s) != Table.NO_MATCH)", - fieldIndexVariableReference(field), fieldIndexVariableReference(field)) - .emitStatement("throw new IllegalStateException(\"Cannot migrate an object with null value in field '%s'." + - " Either maintain the same type for primary key field '%s', or remove the object with null value before migration.\")", - fieldName, fieldName) - .endControlFlow(); + writer.beginControlFlow("if (table.isColumnNullable(%s))", fieldIndexVariableReference(field)); + if (Utils.isPrimitiveType(fieldTypeQualifiedName)) { + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + + " \"Field '%s' does support null values in the existing Realm file. " + + "Use corresponding boxed type for field '%s' or migrate using RealmObjectSchema.setNullable().\")", + fieldName, fieldName); } else { - writer.beginControlFlow("if (table.isColumnNullable(%s))", fieldIndexVariableReference(field)); - if (Utils.isPrimitiveType(fieldTypeQualifiedName)) { - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - " \"Field '%s' does support null values in the existing Realm file. " + - "Use corresponding boxed type for field '%s' or migrate using RealmObjectSchema.setNullable().\")", - fieldName, fieldName); - } else { - writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + - " \"Field '%s' does support null values in the existing Realm file. " + - "Remove @Required or @PrimaryKey from field '%s' or migrate using RealmObjectSchema.setNullable().\")", - fieldName, fieldName); - } - writer.endControlFlow(); + writer.emitStatement("throw new RealmMigrationNeededException(sharedRealm.getPath()," + + " \"Field '%s' does support null values in the existing Realm file. " + + "Remove @Required or @PrimaryKey from field '%s' or migrate using RealmObjectSchema.setNullable().\")", + fieldName, fieldName); } + writer.endControlFlow(); } // Validate @Index diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index fe03983de6..2f08cfc378 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -20,6 +20,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -64,6 +65,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1148,21 +1150,14 @@ public void migrating_nullableField_toward_notNullable_PrimaryKeyThrows() throws for (final Class clazz : classes) { try { RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() - .schemaVersion(0) .schema(clazz) - .migration(new RealmMigration() { - @Override - public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { - // intentionally lefts empty to demonstrate incompatibilities between nullable/not-nullable PrimaryKeys. - } - }) .build(); Realm realm = Realm.getInstance(realmConfig); realm.close(); fail(); - } catch (IllegalStateException expected) { - assertEquals("Cannot migrate an object with null value in field 'id'. Either maintain the same type for primary key field 'id', or remove the object with null value before migration.", - expected.getMessage()); + } catch (RealmMigrationNeededException expected) { + assertThat(expected.getMessage(), CoreMatchers.containsString( + "Field 'id' does support null values in the existing Realm file.")); } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 4322557498..b5ac52a1d5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -18,6 +18,7 @@ import android.support.test.runner.AndroidJUnit4; +import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -33,6 +34,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -102,22 +104,28 @@ public boolean isNullable() { } public enum IndexFieldType { - STRING(String.class), - SHORT(Short.class), PRIMITIVE_SHORT(short.class), - INT(Integer.class), PRIMITIVE_INT(int.class), - LONG(Long.class), PRIMITIVE_LONG(long.class), - BYTE(Byte.class), PRIMITIVE_BYTE(byte.class), - BOOLEAN(Boolean.class), PRIMITIVE_BOOLEAN(boolean.class), - DATE(Date.class); + STRING(String.class, true), + SHORT(Short.class, true), PRIMITIVE_SHORT(short.class, false), + INT(Integer.class, true), PRIMITIVE_INT(int.class, false), + LONG(Long.class, true), PRIMITIVE_LONG(long.class, false), + BYTE(Byte.class, true), PRIMITIVE_BYTE(byte.class, false), + BOOLEAN(Boolean.class, true), PRIMITIVE_BOOLEAN(boolean.class, false), + DATE(Date.class, true); private final Class clazz; + private final boolean nullable; public Class getType() { return clazz; } - IndexFieldType(Class clazz) { + public boolean isNullable() { + return nullable; + } + + IndexFieldType(Class clazz, boolean nullable) { this.clazz = clazz; + this.nullable = nullable; } } @@ -141,20 +149,26 @@ public Class getType() { // TODO These should also be allowed? BOOLEAN, DATE public enum PrimaryKeyFieldType { - STRING(String.class), - SHORT(Short.class), PRIMITIVE_SHORT(short.class), - INT(Integer.class), PRIMITIVE_INT(int.class), - LONG(Long.class), PRIMITIVE_LONG(long.class), - BYTE(Byte.class), PRIMITIVE_BYTE(byte.class); + STRING(String.class, true), + SHORT(Short.class, true), PRIMITIVE_SHORT(short.class, false), + INT(Integer.class, true), PRIMITIVE_INT(int.class, false), + LONG(Long.class, true), PRIMITIVE_LONG(long.class, false), + BYTE(Byte.class, true), PRIMITIVE_BYTE(byte.class, false); private final Class clazz; + private final boolean nullable; public Class getType() { return clazz; } - PrimaryKeyFieldType(Class clazz) { + public boolean isNullable() { + return nullable; + } + + PrimaryKeyFieldType(Class clazz, boolean nullable) { this.clazz = clazz; + this.nullable = nullable; } } @@ -547,6 +561,102 @@ public void setRequired_nullValueBecomesDefaultValue() { } } + @Test + public void setRequired_true_onPrimaryKeyField_containsNullValues_shouldThrow() { + for (PrimaryKeyFieldType fieldType : PrimaryKeyFieldType.values()) { + String className = fieldType.getType().getSimpleName() + "Class"; + String fieldName = "primaryKey"; + schema = realmSchema.create(className); + if (!fieldType.isNullable()) { + continue; + } + schema.addField(fieldName, fieldType.getType(), FieldAttribute.PRIMARY_KEY); + DynamicRealmObject object = realm.createObject(schema.getClassName(), null); + assertTrue(object.isNull(fieldName)); + try { + schema.setRequired(fieldName, true); + fail(); + } catch (IllegalStateException expected) { + assertThat(expected.getMessage(), + CoreMatchers.containsString("The primary key field 'primaryKey' has 'null' values stored.")); + } + realmSchema.remove(className); + } + } + + private void setRequired_onPrimaryKeyField(boolean isRequired) { + for (PrimaryKeyFieldType fieldType : PrimaryKeyFieldType.values()) { + String className = fieldType.getType().getSimpleName() + "Class"; + String fieldName = "primaryKey"; + schema = realmSchema.create(className); + if (!fieldType.isNullable()) { + continue; + } + if (isRequired) { + schema.addField(fieldName, fieldType.getType(), FieldAttribute.PRIMARY_KEY); + } else { + schema.addField(fieldName, fieldType.getType(), FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED); + } + realm.createObject(schema.getClassName(), "1"); + realm.createObject(schema.getClassName(), "2"); + assertTrue(schema.hasPrimaryKey()); + assertTrue(schema.hasIndex(fieldName)); + + schema.setRequired(fieldName, isRequired); + assertTrue(schema.hasPrimaryKey()); + assertTrue(schema.hasIndex(fieldName)); + + RealmResults results = realm.where(className).findAllSorted(fieldName); + assertEquals(2, results.size()); + if (fieldType == PrimaryKeyFieldType.STRING) { + assertEquals("1", results.get(0).getString(fieldName)); + assertEquals("2", results.get(1).getString(fieldName)); + } else { + assertEquals(1, results.get(0).getLong(fieldName)); + assertEquals(2, results.get(1).getLong(fieldName)); + } + realmSchema.remove(className); + } + } + + @Test + public void setRequired_true_onPrimaryKeyField() { + setRequired_onPrimaryKeyField(true); + } + + @Test + public void setRequired_false_onPrimaryKeyField() { + setRequired_onPrimaryKeyField(false); + } + + private void setRequired_onIndexedField(boolean toRequired) { + String fieldName = "IndexedField"; + for (IndexFieldType fieldType : IndexFieldType.values()) { + if (!fieldType.isNullable()) { + continue; + } + if (toRequired) { + schema.addField(fieldName, fieldType.getType(), FieldAttribute.INDEXED); + } else { + schema.addField(fieldName, fieldType.getType(), FieldAttribute.INDEXED, FieldAttribute.REQUIRED); + } + assertTrue(schema.hasIndex(fieldName)); + schema.setRequired(fieldName, toRequired); + assertTrue(schema.hasIndex(fieldName)); + schema.removeField(fieldName); + } + } + + @Test + public void setRequired_true_onIndexedField() { + setRequired_onIndexedField(true); + } + + @Test + public void setRequired_false_onIndexedField() { + setRequired_onIndexedField(false); + } + @Test public void setRemovePrimaryKey() { for (PrimaryKeyFieldType fieldType : PrimaryKeyFieldType.values()) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 7fdfbf87b4..c99f0ba198 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -20,8 +20,19 @@ #include "io_realm_internal_Table.h" #include "tablebase_tpl.hpp" +#include "util/format.hpp" + +#include "jni_util/java_exception_thrower.hpp" +#include "java_exception_def.hpp" + using namespace std; using namespace realm; +using namespace realm::_impl; +using namespace realm::jni_util; +using namespace realm::util; + +static const char* c_null_values_cannot_set_required_msg = "The primary key field '%1' has 'null' values stored. It " + "cannot be converted to a '@Required' primary key field."; static void finalize_table(jlong ptr); @@ -192,11 +203,12 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable(J // 6. removing the original column and renaming the temporary column will make it look like original is being modified JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullable(JNIEnv* env, jobject, - jlong nativeTablePtr, - jlong columnIndex) + jlong native_table_ptr, + jlong j_column_index, + jboolean is_primary_key) { - Table* table = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, table, columnIndex)) { + Table* table = TBL(native_table_ptr); + if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { return; } if (table->has_shared_type()) { @@ -204,7 +216,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl return; } try { - size_t column_index = S(columnIndex); + size_t column_index = S(j_column_index); if (table->is_nullable(column_index)) { return; // column is already nullable } @@ -232,12 +244,22 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl j++; } + // Search index has too be added first since if it is a PK field, add_xxx_unique will check it. + if (table->has_search_index(column_index + 1)) { + table->add_search_index(column_index); + } + for (size_t i = 0; i < table->size(); ++i) { switch (column_type) { case type_String: { // Payload copy is needed StringData sd(table->get_string(column_index + 1, i)); - table->set_string(column_index, i, sd); + if (is_primary_key) { + table->set_string_unique(column_index, i, sd); + } + else { + table->set_string(column_index, i, sd); + } break; } case type_Binary: { @@ -248,7 +270,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl break; } case type_Int: - table->set_int(column_index, i, table->get_int(column_index + 1, i)); + if (is_primary_key) { + table->set_int_unique(column_index, i, table->get_int(column_index + 1, i)); + } + else { + table->set_int(column_index, i, table->get_int(column_index + 1, i)); + } break; case type_Bool: table->set_bool(column_index, i, table->get_bool(column_index + 1, i)); @@ -273,9 +300,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl return; } } - if (table->has_search_index(column_index + 1)) { - table->add_search_index(column_index); - } table->remove_column(column_index + 1); table->rename_column(table->get_column_index(tmp_column_name), column_name); } @@ -283,11 +307,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNullable(JNIEnv* env, jobject, - jlong nativeTablePtr, - jlong columnIndex) + jlong native_table_ptr, + jlong j_column_index, + jboolean is_primary_key) { - Table* table = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, table, columnIndex)) { + Table* table = TBL(native_table_ptr); + if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { return; } if (table->has_shared_type()) { @@ -295,7 +320,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull return; } try { - size_t column_index = S(columnIndex); + size_t column_index = S(j_column_index); if (!table->is_nullable(column_index)) { return; // column is already not nullable } @@ -322,16 +347,32 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull j++; } + // Search index has too be added first since if it is a PK field, add_xxx_unique will check it. + if (table->has_search_index(column_index + 1)) { + table->add_search_index(column_index); + } + for (size_t i = 0; i < table->size(); ++i) { switch (column_type) { // FIXME: respect user-specified default values case type_String: { StringData sd = table->get_string(column_index + 1, i); if (sd == realm::null()) { - table->set_string(column_index, i, ""); + if (is_primary_key) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, + format(c_null_values_cannot_set_required_msg, column_name)); + } + else { + table->set_string(column_index, i, ""); + } } else { // Payload copy is needed - table->set_string(column_index, i, sd); + if (is_primary_key) { + table->set_string_unique(column_index, i, sd); + } + else { + table->set_string(column_index, i, sd); + } } break; } @@ -349,10 +390,21 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull } case type_Int: if (table->is_null(column_index + 1, i)) { - table->set_int(column_index, i, 0); + if (is_primary_key) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, + format(c_null_values_cannot_set_required_msg, column_name)); + } + else { + table->set_int(column_index, i, 0); + } } else { - table->set_int(column_index, i, table->get_int(column_index + 1, i)); + if (is_primary_key) { + table->set_int_unique(column_index, i, table->get_int(column_index + 1, i)); + } + else { + table->set_int(column_index, i, table->get_int(column_index + 1, i)); + } } break; case type_Bool: @@ -399,9 +451,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull return; } } - if (table->has_search_index(column_index + 1)) { - table->add_search_index(column_index); - } table->remove_column(column_index + 1); table->rename_column(table->get_column_index(tmp_column_name), column_name); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index da1feceb15..5895024bc9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -255,7 +255,7 @@ public boolean isColumnNullable(long columnIndex) { * @param columnIndex the column index. */ public void convertColumnToNullable(long columnIndex) { - nativeConvertColumnToNullable(nativePtr, columnIndex); + nativeConvertColumnToNullable(nativePtr, columnIndex, isPrimaryKey(columnIndex)); } /** @@ -264,7 +264,7 @@ public void convertColumnToNullable(long columnIndex) { * @param columnIndex the column index. */ public void convertColumnToNotNullable(long columnIndex) { - nativeConvertColumnToNotNullable(nativePtr, columnIndex); + nativeConvertColumnToNotNullable(nativePtr, columnIndex, isPrimaryKey(columnIndex)); } // Table Size and deletion. AutoGenerated subclasses are nothing to do with this @@ -1040,9 +1040,9 @@ public static String getTableNameForClass(String name) { private native boolean nativeIsColumnNullable(long nativePtr, long columnIndex); - private native void nativeConvertColumnToNullable(long nativeTablePtr, long columnIndex); + private native void nativeConvertColumnToNullable(long nativeTablePtr, long columnIndex, boolean isPrimaryKey); - private native void nativeConvertColumnToNotNullable(long nativePtr, long columnIndex); + private native void nativeConvertColumnToNotNullable(long nativePtr, long columnIndex, boolean isPrimaryKey); private native long nativeSize(long nativeTablePtr); From 5910bc27d5514a0e88077b1eeaac8b367f9095fe Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 19 Jun 2017 12:34:30 +0900 Subject: [PATCH 0756/2110] update Gradle wrappers to 4.0 (#4801) --- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54783 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54783 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 54783 -> 54783 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- library-benchmarks/gradlew | 68 ++++++++++-------- library-benchmarks/gradlew.bat | 14 ++-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54783 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54783 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- realm.properties | 2 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 54783 -> 54783 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- realm/realm-library/build.gradle | 6 +- 18 files changed, 62 insertions(+), 56 deletions(-) diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index ccad502cf004785505cbd55ed7b64a792cf40536..d43d6b448975acf43bb2ed301c2950c37fe3ad4b 100644 GIT binary patch delta 26 gcmeyrn)&}~<_*?In1h=yZnis8Cbh0ImoOdH?_b diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 3e88d1e5c4..857845b286 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue May 16 03:12:59 PDT 2017 +#Sat Jun 17 16:26:38 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index c77b099c97f3531be5b7858ea0caa6b3f9d2b015..eb16307ec9cdea923e8f8f0c652068b14125c974 100644 GIT binary patch delta 26 gcmeyrn)&}~<_*?Im_wQ`Znis8Cbh0Ip38fB*mh diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index ac21505e86..bb7811d90b 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue May 16 03:13:01 PDT 2017 +#Sat Jun 17 16:26:41 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 967a4da89f43efa38dd93bf6bc6f4d1592bf5313..84a4322125725f5c8b5e6e19badb299c548b4ae9 100644 GIT binary patch delta 26 gcmeyrn)&}~<_*?Im_wT{Znis8Cbh0Ip;Vf&c&j diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 44bbebba2a..63400e843c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue May 16 03:13:02 PDT 2017 +#Sat Jun 17 16:26:42 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.jar b/library-benchmarks/gradle/wrapper/gradle-wrapper.jar index 13372aef5e24af05341d49695ee84e5f9b594659..2def030cb23e6f57c9a99143b7109bc30b17df48 100644 GIT binary patch delta 25521 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 AvH$=8 delta 24397 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/library-benchmarks/gradlew.bat b/library-benchmarks/gradlew.bat index aec99730b4..e95643d6a2 100644 --- a/library-benchmarks/gradlew.bat +++ b/library-benchmarks/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index bba0767ab164c9662c0aa8d3fd14d6048b6bffb9..151c6715d4415452b9f0eec9c74a71094c3b696f 100644 GIT binary patch delta 26 gcmeyrn)&}~<_*?In4_96Znis8Cbh0Iqusga7~l diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index d5c18857dd..3a7251e0c8 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue May 16 03:13:05 PDT 2017 +#Sat Jun 17 16:26:53 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index b0522cfc8adf15aa69c3421d3197d25c42d81e25..ef2c66249d39715445d7a9931a53c4c99592d46b 100644 GIT binary patch delta 26 gcmeyrn)&}~<_*?In4_C7Znis8Cbh0Ire@h5!Hn diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 64338485ea..efc021e34a 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue May 16 03:13:06 PDT 2017 +#Sat Jun 17 16:26:55 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip diff --git a/realm.properties b/realm.properties index b4231e304d..9be96842d2 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=3.5 +gradleVersion=4.0 ndkVersion=r10e diff --git a/realm/gradle/wrapper/gradle-wrapper.jar b/realm/gradle/wrapper/gradle-wrapper.jar index bba0767ab164c9662c0aa8d3fd14d6048b6bffb9..19278fe4f8da8f5515b0229509422db4791e9915 100644 GIT binary patch delta 26 gcmeyrn)&}~<_*?Im?N7nZnis8Cbh0Iqusga7~l diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index ad3d9f3a11..bdb7f28d10 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue May 16 03:13:04 PDT 2017 +#Sat Jun 17 16:26:51 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index b137dbb1a3..01283a6b7a 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -243,10 +243,10 @@ task findbugs(type: FindBugs) { xml.enabled = false html.enabled = true xml { - destination "$project.buildDir/findbugs/findbugs-output.xml" + destination file("$project.buildDir/findbugs/findbugs-output.xml") } html { - destination "$project.buildDir/findbugs/findbugs-output.html" + destination file("$project.buildDir/findbugs/findbugs-output.html") } } } @@ -270,7 +270,7 @@ task checkstyle(type: Checkstyle) { source 'src' include '*/java/**/*.java' - // Ingore tests for now. + // Ignore tests for now. exclude '*Test*/**' // empty classpath From f957d873b18c63aadf6c7275747b257e02b54ce2 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 19 Jun 2017 13:01:55 +0900 Subject: [PATCH 0757/2110] re-generate gradle wrapper to remove extra spaces (#4804) re-generate gradle wrappers to remove extra spaces --- examples/gradlew | 6 +++--- gradle-plugin/gradlew | 6 +++--- gradlew | 6 +++--- library-benchmarks/gradlew | 6 +++--- realm-annotations/gradlew | 6 +++--- realm-transformer/gradlew | 6 +++--- realm/gradlew | 6 +++--- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/examples/gradlew b/examples/gradlew index 4453ccea33..cccdd3d517 100755 --- a/examples/gradlew +++ b/examples/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/gradle-plugin/gradlew b/gradle-plugin/gradlew index 4453ccea33..cccdd3d517 100755 --- a/gradle-plugin/gradlew +++ b/gradle-plugin/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/gradlew b/gradlew index 4453ccea33..cccdd3d517 100755 --- a/gradlew +++ b/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/library-benchmarks/gradlew b/library-benchmarks/gradlew index 4453ccea33..cccdd3d517 100755 --- a/library-benchmarks/gradlew +++ b/library-benchmarks/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/realm-annotations/gradlew b/realm-annotations/gradlew index 4453ccea33..cccdd3d517 100755 --- a/realm-annotations/gradlew +++ b/realm-annotations/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/realm-transformer/gradlew b/realm-transformer/gradlew index 4453ccea33..cccdd3d517 100755 --- a/realm-transformer/gradlew +++ b/realm-transformer/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/realm/gradlew b/realm/gradlew index 4453ccea33..cccdd3d517 100755 --- a/realm/gradlew +++ b/realm/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } From 0f85ff1dacb24468c5056cd29601f45a446ccae7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 19 Jun 2017 09:32:48 +0200 Subject: [PATCH 0758/2110] Correctly reset integration tests. (#4803) --- tools/sync_test_server/ros-testing-server.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index c9ca2c2c5d..ae43a8477a 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -3,6 +3,7 @@ var winston = require('winston'); //logging const temp = require('temp'); const spawn = require('child_process').spawn; +const exec = require('child_process').exec; var http = require('http'); var dispatcher = require('httpdispatcher'); @@ -64,10 +65,16 @@ function stopRealmObjectServer() { if (syncServerChildProcess) { syncServerChildProcess.kill(); syncServerChildProcess = null; + exec('rm -r ' + 'realm-object-server', function (err, stdout, stderr) { + if (err) { + winston.err(err) + } else { + winston.info("realm-object-server directory deleted") + } + }); } } - // start sync server dispatcher.onGet("/start", function(req, res) { startRealmObjectServer(); From 01d4d8d1a73a1d48a8f236366bbaa2fee805c8d1 Mon Sep 17 00:00:00 2001 From: abennsir Date: Fri, 16 Jun 2017 12:09:13 +0200 Subject: [PATCH 0759/2110] upgrade roboelectric version to 3.3.2 --- examples/unitTestExample/build.gradle | 2 +- .../unittesting/ExampleActivityTest.java | 21 +++++++------------ .../unittesting/ExampleRealmTest.java | 1 + 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index 8e4f015499..79231649c1 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -34,7 +34,7 @@ dependencies { // Testing testCompile 'junit:junit:4.12' - testCompile "org.robolectric:robolectric:3.3.1" + testCompile "org.robolectric:robolectric:3.3.2" testCompile "org.mockito:mockito-core:1.10.19" testCompile 'org.robolectric:shadows-support-v4:3.0' diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java index d4a035cd35..6123c0784f 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java @@ -19,24 +19,20 @@ import android.content.Context; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.modules.junit4.PowerMockRunnerDelegate; -import org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.util.ActivityController; import java.util.Arrays; import java.util.List; @@ -48,7 +44,6 @@ import io.realm.RealmResults; import io.realm.examples.unittesting.model.Person; import io.realm.internal.RealmCore; -import io.realm.internal.Util; import io.realm.log.RealmLog; import static org.hamcrest.CoreMatchers.is; @@ -66,15 +61,15 @@ import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.mockito.PowerMockito.whenNew; + @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) @SuppressStaticInitializationFor("io.realm.internal.Util") @PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class}) -public class ExampleActivityTest -{ - // Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock +public class ExampleActivityTest { + // Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock @Rule public PowerMockRule rule = new PowerMockRule(); @@ -184,9 +179,9 @@ public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { doCallRealMethod().when(mockRealm).executeTransaction(Mockito.any(Realm.Transaction.class)); // Create activity - ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get(); + ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get(); - assertThat(activity.getTitle().toString(), is("Unit Test Example")); + assertThat(activity.getTitle().toString(), is("Unit Test Example")); // Verify that two Realm.getInstance() calls took place. verifyStatic(times(2)); @@ -227,9 +222,9 @@ public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { public void shouldBeAbleToVerifyTransactionCalls() { // Create activity - ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get(); + ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get(); - assertThat(activity.getTitle().toString(), is("Unit Test Example")); + assertThat(activity.getTitle().toString(), is("Unit Test Example")); // Verify that two Realm.getInstance() calls took place. verifyStatic(times(2)); @@ -247,7 +242,7 @@ public void shouldBeAbleToVerifyTransactionCalls() { verify(mockRealm, times(5)).executeTransaction(Mockito.any(Realm.Transaction.class)); // Call the destroy method so we can verify that the .close() method was called (below) - activity.onDestroy(); + activity.onDestroy(); // Verify that the realm got closed 2 separate times. Once in the AsyncTask, once // in onDestroy diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java index 32a62049f5..3f1b9c87d3 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java @@ -44,6 +44,7 @@ import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; + @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 19) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) From 938d1adea7af2f534c134ca5567e224ba143b2f7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 19 Jun 2017 23:22:55 +0800 Subject: [PATCH 0760/2110] Add credits --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 765df3a5a5..f81b6b2d12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ * Upgraded to Realm Sync 1.9.1 * Upgraded to Realm Core 2.8.0 +### Credits + +* Thanks to Anis Ben Nsir (@abennsir) for upgrading Roboelectric in the unitTestExample (#4698). + ## 3.3.3 (YYYY-MM-DD) ### Breaking Changes From f7e7901d4191b944d22b4d7f84ab70312c515745 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 20 Jun 2017 00:52:43 +0900 Subject: [PATCH 0761/2110] migrate to sdkmanager (#4809) * migrate to sdkmanager --- Dockerfile | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 450ccf5cff..3d61d47efe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ ENV ANDROID_HOME /opt/android-sdk-linux # Need by cmake ENV ANDROID_NDK_HOME /opt/android-ndk ENV ANDROID_NDK /opt/android-ndk -ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools +ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} ENV NDK_CCACHE /usr/bin/ccache ENV NDK_LCACHE /usr/bin/lcache @@ -42,20 +42,18 @@ RUN DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 \ # Install the Android SDK RUN cd /opt && \ - wget -q https://dl.google.com/android/repository/tools_r25.1.7-linux.zip -O android-tools-linux.zip && \ + wget -q https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip -O android-tools-linux.zip && \ unzip android-tools-linux.zip -d ${ANDROID_HOME} && \ rm -f android-tools-linux.zip # Grab what's needed in the SDK -# ↓ updates tools to at least 25.1.7, but that prints 'Nothing was installed' (so I don't check the outputs). RUN mkdir "${ANDROID_HOME}/licenses" && \ - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" && \ - echo -en "\nd23d63a1f23e25e2c7a316e29eb60396e7924281" > "${ANDROID_HOME}/licenses/android-sdk-preview-license" -RUN echo y | android update sdk --no-ui --all --filter tools > /dev/null -RUN echo y | android update sdk --no-ui --all --filter platform-tools | grep 'package installed' -RUN echo y | android update sdk --no-ui --all --filter build-tools-25.0.3 | grep 'package installed' -RUN echo y | android update sdk --no-ui --all --filter extra-android-m2repository | grep 'package installed' -RUN echo y | android update sdk --no-ui --all --filter android-25 | grep 'package installed' + echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" +RUN sdkmanager --update +RUN sdkmanager 'platform-tools' +RUN sdkmanager 'build-tools;25.0.3' +RUN sdkmanager 'extras;android;m2repository' +RUN sdkmanager 'platforms;android-25' # Install the NDK RUN mkdir /opt/android-ndk-tmp && \ From 51be74df19ec0c7542dcca49edc560964e1f72fa Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 20 Jun 2017 10:48:20 +0800 Subject: [PATCH 0762/2110] Convert exception on sync client to java exception (#4707) - update object-store to 1e3cbb1789 - Convert exception on sync client to java exception This will give us some better information when excpetion happens on the sync client thread. - Update sync to 1.10.1, core to 2.8.4 --- CHANGELOG.md | 6 ++-- dependencies.list | 6 ++-- .../src/main/cpp/io_realm_SyncManager.cpp | 29 +++++++++++++++++-- realm/realm-library/src/main/cpp/object-store | 2 +- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f81b6b2d12..54902fa789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Breaking Changes +* [ObjectServer] Updated protocol version to 18 which is only compatible with ROS > 1.6.0. + ### Enhancements * [ObjectServer] Added support for Sync Progress Notifications through `SyncSession.addDownloadProgressListener(ProgressMode, ProgressListener)` and `SyncSession.addUploadProgressListener(ProgressMode, ProgressListener)` (#4104). @@ -13,8 +15,8 @@ ### Internal -* Upgraded to Realm Sync 1.9.1 -* Upgraded to Realm Core 2.8.0 +* Upgraded to Realm Sync 1.10.1 +* Upgraded to Realm Core 2.8.4 ### Credits diff --git a/dependencies.list b/dependencies.list index 70a8b12116..82afed2cb5 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.9.1 -REALM_SYNC_SHA256=b1bd4be71c414f17fee01c05888989ecd0c22a57d4c760c6750f6634c2c29ee8 +REALM_SYNC_VERSION=1.10.1 +REALM_SYNC_SHA256=b48fd48461b563e2a6b1605ec346aca48b64b64a12d42a0f5a61135906d49074 # Object Server Release used by Integration tests # `realm` is stable releases, `realm-testing` is developer builds. @@ -10,4 +10,4 @@ REALM_SYNC_SHA256=b1bd4be71c414f17fee01c05888989ecd0c22a57d4c760c6750f6634c2c29e # /tools/sync_test_server/Dockerfile specify which repo (apt) we should # install/use between 'realm' and 'realm-testing', the version below should # correspond to an existing version on the *specified* repo. -REALM_OBJECT_SERVER_DE_VERSION=1.7.5-180 +REALM_OBJECT_SERVER_DE_VERSION=1.7.6-62 diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index d9f15dd859..dd391c2b26 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -23,13 +23,19 @@ #include #include "util.hpp" -#include "jni_util/jni_utils.hpp" +#include "jni_util/java_class.hpp" #include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" using namespace realm; using namespace realm::jni_util; +using namespace realm::util; struct AndroidClientListener : public realm::BindingCallbackThreadObserver { + AndroidClientListener(JNIEnv* env) + : m_realm_exception_class(env, "io/realm/exceptions/RealmError") + { + } void did_create_thread() override { @@ -44,7 +50,23 @@ struct AndroidClientListener : public realm::BindingCallbackThreadObserver { // Failing to detach the JVM before closing the thread will crash on ART JniUtils::detach_current_thread(); } -} s_client_thread_listener; + + void handle_error(std::exception const& e) override + { + JNIEnv* env = JniUtils::get_env(true); + std::string msg = format("An exception has been thrown on the sync client thread:\n%1", e.what()); + Log::f(msg.c_str()); + // Since user has no way to handle exceptions thrown on the sync client thread, we just convert it to a Java + // exception to get more debug information for ourself. + // FIXME: We really need to find a universal and clever way to get the native backtrace when exception thrown + env->ThrowNew(m_realm_exception_class, msg.c_str()); + } + +private: + // For some reasons, FindClass() doesn't work in the native thread even when the JVM is attached before. Get the + // RealmError class on a normal JVM thread and throw it later on the sync client thread. + JavaClass m_realm_exception_class; +}; struct AndroidSyncLoggerFactory : public realm::SyncLoggerFactory { // The level param is ignored. Use the global RealmLog.setLevel() to control all log levels. @@ -72,8 +94,9 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNI JStringAccessor base_file_path(env, sync_base_dir); // throws SyncManager::shared().configure_file_system(base_file_path, SyncManager::MetadataMode::NoEncryption); + static AndroidClientListener client_thread_listener(env); // Register Sync Client thread start/stop callback - g_binding_callback_thread_observer = &s_client_thread_listener; + g_binding_callback_thread_observer = &client_thread_listener; // init logger SyncManager::shared().set_logger_factory(s_sync_logger_factory); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index f5e1ce7bb5..1e3cbb1789 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit f5e1ce7bb5ceda14dfff9b2412618f8ea9f6be74 +Subproject commit 1e3cbb178952e26112a474f596aad6bf4f938adf From 23140ecb01d2dbe114645e9e7419f8a5521a5fff Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 20 Jun 2017 15:41:33 +0900 Subject: [PATCH 0763/2110] update android gradle plugin to 2.3.3 (#4816) --- examples/build.gradle | 3 +-- realm/build.gradle | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 6d5c61e9f7..1d0e7df2e9 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,9 +22,8 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' - classpath 'com.github.JakeWharton:sdk-manager-plugin:0ce4cdf08009d79223850a59959d9d6e774d0f77' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } diff --git a/realm/build.gradle b/realm/build.gradle index fc408fc2ed..eed959332f 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -8,13 +8,13 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' - classpath 'com.novoda:gradle-android-command-plugin:1.3.0' + classpath 'com.novoda:gradle-android-command-plugin:1.5.0' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.2' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0' - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:3.1.1' + classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.0.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10' From 54fd41899da733d020e09354264f7cae4a0c93ce Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 20 Jun 2017 16:06:25 +0900 Subject: [PATCH 0764/2110] use fail() instead of assert(false). (#4814) --- .../java/io/realm/internal/JNIQueryTest.java | 229 +++++++++--------- 1 file changed, 116 insertions(+), 113 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index 33e47f88a9..26182e56d7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -444,57 +444,60 @@ public void testQueryWithWrongDataType() { // Compares strings in non string columns. for (int i = 0; i <= 6; i++) { - try { query.equalTo(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.notEqualTo(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.beginsWith(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.endsWith(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.like(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} - try { query.contains(new long[]{i}, oneNullTable, "string"); assert(false); } catch(IllegalArgumentException e) {} + if (i != 6) { + try { query.equalTo(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.notEqualTo(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.beginsWith(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.endsWith(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.like(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.contains(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + } } + // Compares integer in non integer columns. for (int i = 0; i <= 6; i++) { if (i != 5) { - try { query.equalTo(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.notEqualTo(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThan(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThan(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123); assert(false); } catch(IllegalArgumentException e) {} - try { query.between(new long[]{i}, 123, 321); assert(false); } catch(IllegalArgumentException e) {} + try { query.equalTo(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.notEqualTo(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThan(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThan(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.between(new long[]{i}, 123, 321); fail(); } catch(IllegalArgumentException ignore) {} } } // Compares float in non float columns. for (int i = 0; i <= 6; i++) { if (i != 4) { - try { query.equalTo(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.notEqualTo(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThan(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThan(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123F); assert(false); } catch(IllegalArgumentException e) {} - try { query.between(new long[]{i}, 123F, 321F); assert(false); } catch(IllegalArgumentException e) {} + try { query.equalTo(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.notEqualTo(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThan(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThan(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.between(new long[]{i}, 123F, 321F); fail(); } catch(IllegalArgumentException ignore) {} } } // Compares double in non double columns. for (int i = 0; i <= 6; i++) { if (i != 3) { - try { query.equalTo(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.notEqualTo(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThan(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThan(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123D); assert(false); } catch(IllegalArgumentException e) {} - try { query.between(new long[]{i}, 123D, 321D); assert(false); } catch(IllegalArgumentException e) {} + try { query.equalTo(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.notEqualTo(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThan(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThan(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.between(new long[]{i}, 123D, 321D); fail(); } catch(IllegalArgumentException ignore) {} } } // Compares boolean in non boolean columns. for (int i = 0; i <= 6; i++) { if (i != 1) { - try { query.equalTo(new long[]{i}, oneNullTable, true); assert(false); } catch(IllegalArgumentException e) {} + try { query.equalTo(new long[]{i}, oneNullTable, true); fail(); } catch(IllegalArgumentException ignore) {} } } @@ -502,12 +505,12 @@ public void testQueryWithWrongDataType() { /* TODO: for (int i = 0; i <= 8; i++) { if (i != 2) { - try { query.equal(i, new Date()); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThan(i, new Date()); assert(false); } catch(IllegalArgumentException e) {} - try { query.lessThanOrEqual(i, new Date()); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThan(i, new Date()); assert(false); } catch(IllegalArgumentException e) {} - try { query.greaterThanOrEqual(i, new Date()); assert(false); } catch(IllegalArgumentException e) {} - try { query.between(i, new Date(), new Date()); assert(false); } catch(IllegalArgumentException e) {} + try { query.equal(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThan(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThanOrEqual(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThan(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThanOrEqual(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.between(i, new Date(), new Date()); fail(); } catch(IllegalArgumentException ignore) {} } } */ @@ -520,98 +523,98 @@ public void testColumnIndexOutOfBounds() { // Queries the table. TableQuery query = table.where(); - try { query.minimumInt(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumFloat(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumDouble(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumInt(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumFloat(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumDouble(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumInt(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumFloat(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumDouble(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumInt(6); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumFloat(6); assert(false); } catch(IllegalArgumentException e) {} - try { query.minimumDouble(6); assert(false); } catch(IllegalArgumentException e) {} - - try { query.maximumInt(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumFloat(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumDouble(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumInt(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumFloat(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumDouble(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumInt(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumFloat(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumDouble(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumInt(6); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumFloat(6); assert(false); } catch(IllegalArgumentException e) {} - try { query.maximumDouble(6); assert(false); } catch(IllegalArgumentException e) {} - - try { query.sumInt(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumFloat(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumDouble(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumInt(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumFloat(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumDouble(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumInt(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumFloat(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumDouble(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumInt(6); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumFloat(6); assert(false); } catch(IllegalArgumentException e) {} - try { query.sumDouble(6); assert(false); } catch(IllegalArgumentException e) {} - - try { query.averageInt(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageFloat(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageDouble(0); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageInt(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageFloat(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageDouble(1); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageInt(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageFloat(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageDouble(2); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageInt(6); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageFloat(6); assert(false); } catch(IllegalArgumentException e) {} - try { query.averageDouble(6); assert(false); } catch(IllegalArgumentException e) {} + try { query.minimumInt(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumFloat(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumDouble(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumInt(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumFloat(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumDouble(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumInt(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumFloat(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumDouble(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumInt(6); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumFloat(6); fail(); } catch(IllegalArgumentException ignore) {} + try { query.minimumDouble(6); fail(); } catch(IllegalArgumentException ignore) {} + + try { query.maximumInt(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumFloat(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumDouble(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumInt(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumFloat(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumDouble(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumInt(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumFloat(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumDouble(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumInt(6); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumFloat(6); fail(); } catch(IllegalArgumentException ignore) {} + try { query.maximumDouble(6); fail(); } catch(IllegalArgumentException ignore) {} + + try { query.sumInt(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumFloat(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumDouble(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumInt(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumFloat(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumDouble(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumInt(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumFloat(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumDouble(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumInt(6); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumFloat(6); fail(); } catch(IllegalArgumentException ignore) {} + try { query.sumDouble(6); fail(); } catch(IllegalArgumentException ignore) {} + + try { query.averageInt(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageFloat(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageDouble(0); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageInt(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageFloat(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageDouble(1); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageInt(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageFloat(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageDouble(2); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageInt(6); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageFloat(6); fail(); } catch(IllegalArgumentException ignore) {} + try { query.averageDouble(6); fail(); } catch(IllegalArgumentException ignore) {} // Out of bounds for string - try { query.equalTo(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.beginsWith(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.endsWith(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.like(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{7}, oneNullTable, "string"); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.beginsWith(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.endsWith(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.like(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.contains(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} // Out of bounds for integer - try { query.equalTo(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{7}, 123, 321); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{7}, 123, 321); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} // Out of bounds for float - try { query.equalTo(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{7}, 123F, 321F); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{7}, 123F, 321F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} // Out of bounds for double - try { query.equalTo(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{7}, 123D, 321D); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{7}, 123D, 321D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} // Out of bounds for boolean - try { query.equalTo(new long[]{7}, oneNullTable, true); assert(false); } catch(ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{7}, oneNullTable, true); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} } public void testMaximumDate() { From a0a7469efdca47d84f2b53f11fc069e40c7501ec Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 19 Jun 2017 09:32:48 +0200 Subject: [PATCH 0765/2110] Correctly reset integration tests. (#4803) --- tools/sync_test_server/ros-testing-server.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index c9ca2c2c5d..ae43a8477a 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -3,6 +3,7 @@ var winston = require('winston'); //logging const temp = require('temp'); const spawn = require('child_process').spawn; +const exec = require('child_process').exec; var http = require('http'); var dispatcher = require('httpdispatcher'); @@ -64,10 +65,16 @@ function stopRealmObjectServer() { if (syncServerChildProcess) { syncServerChildProcess.kill(); syncServerChildProcess = null; + exec('rm -r ' + 'realm-object-server', function (err, stdout, stderr) { + if (err) { + winston.err(err) + } else { + winston.info("realm-object-server directory deleted") + } + }); } } - // start sync server dispatcher.onGet("/start", function(req, res) { startRealmObjectServer(); From b96ba21cf1e6e69f4f35d0fe04ad21cb7dcfade8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 20 Jun 2017 13:05:43 +0800 Subject: [PATCH 0766/2110] Create test suite for all integration tests --- .../suite/IntegrationTestSuite.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java new file mode 100644 index 0000000000..87b8f5cb8b --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.suite; + + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import io.realm.SSLConfigurationTests; +import io.realm.SyncedRealmTests; +import io.realm.objectserver.AuthTests; +import io.realm.objectserver.EncryptedSynchronizedRealmTests; +import io.realm.objectserver.ManagementRealmTests; +import io.realm.objectserver.ProcessCommitTests; + +// Test suite includes all integration tests. Makes it easy to run all integration tests in the Android Studio. +@RunWith(Suite.class) +@Suite.SuiteClasses({ + SSLConfigurationTests.class, + SyncedRealmTests.class, + AuthTests.class, + EncryptedSynchronizedRealmTests.class, + ManagementRealmTests.class, + ProcessCommitTests.class}) +public class IntegrationTestSuite { +} From 2b3054b7e0eee3044202e74573be861f8391c808 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 20 Jun 2017 09:46:54 +0100 Subject: [PATCH 0767/2110] Exponential Back Off now retry query in case of `ConnectionException` (#4805) * fixes #4310 --- CHANGELOG.md | 1 + .../objectServer/java/io/realm/ErrorCode.java | 12 ++++++++++ .../network/AuthenticateResponse.java | 7 ++++++ .../network/ChangePasswordResponse.java | 22 ++++++++++++++++--- .../internal/network/LogoutResponse.java | 7 ++++++ .../network/OkHttpAuthenticationServer.java | 18 +++++++-------- 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f20859b33a..939739cdf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * When converting nullable BLOB field to required, `null` values should be converted to `byte[0]` instead of `byte[1]`. * Fixed a bug which may cause duplicated primary key values when migrating a nullable primary key field to not nullable. `RealmObjectSchema.setRequired()` and `RealmObjectSchema.setNullable()` will throw when converting a nullable primary key field with null values stored to a required primary key field. +* [ObjectServer] Retrying connections with exponential backoff, when encountering `ConnectException` (#4310). ### Internal diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 1a5438ba92..427e782030 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -16,6 +16,8 @@ package io.realm; +import java.net.ConnectException; + /** * This class enumerate all potential errors related to using the Object Server or synchronizing data. */ @@ -127,6 +129,16 @@ public static ErrorCode fromInt(int errorCode) { throw new IllegalArgumentException("Unknown error code: " + errorCode); } + /** + * Helper method for mapping between {@link Exception} and {@link ErrorCode}. + * @param exception to be mapped as an {@link ErrorCode}. + * @return mapped {@link ErrorCode}. + */ + public static ErrorCode fromException(Exception exception) { + // ConnectException is recoverable (with exponential backoff) + return (exception instanceof ConnectException) ? ErrorCode.IO_EXCEPTION : ErrorCode.UNKNOWN; + } + public enum Category { FATAL, // Abort session as soon as possible RECOVERABLE, // Still possible to recover the session by either rebinding or providing the required information. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index 6f45d1146f..1fdaa9f8cf 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -74,6 +74,13 @@ public static AuthenticateResponse from(ObjectServerError error) { return new AuthenticateResponse(error); } + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static AuthenticateResponse from(Exception exception) { + return AuthenticateResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + /** * Helper method for creating a valid user login response. The user returned will be assumed to have all permissions * and doesn't expire. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java index 4fc951d3e5..777869ab6b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java @@ -19,7 +19,6 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; -import io.realm.log.RealmLog; import okhttp3.Response; /** @@ -27,7 +26,14 @@ */ public class ChangePasswordResponse extends AuthServerResponse { - public static ChangePasswordResponse from(Response response) { + /** + * Helper method for creating the proper change password response. This method will set the appropriate error + * depending on any HTTP response codes or I/O errors. + * + * @param response the server response. + * @return the change password response. + */ + static ChangePasswordResponse from(Response response) { if (response.isSuccessful()) { return new ChangePasswordResponse(); } @@ -40,10 +46,20 @@ public static ChangePasswordResponse from(Response response) { } } - public static ChangePasswordResponse createFailure(ObjectServerError objectServerError) { + /** + * Helper method for creating a failed response. + */ + public static ChangePasswordResponse from(ObjectServerError objectServerError) { return new ChangePasswordResponse(objectServerError); } + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static ChangePasswordResponse from(Exception exception) { + return ChangePasswordResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + private ChangePasswordResponse() { this.error = null; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java index 6a0181ca95..f13fbf6b2e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java @@ -56,6 +56,13 @@ public static LogoutResponse from(ObjectServerError error) { return new LogoutResponse(error); } + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static LogoutResponse from(Exception exception) { + return LogoutResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + /** * Creates an unsuccessful authentication response. This should only happen in case of network or I/O * related issues. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 709544e2f6..1c7bc4afb0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -21,8 +21,6 @@ import java.net.URL; import java.util.concurrent.TimeUnit; -import io.realm.ErrorCode; -import io.realm.ObjectServerError; import io.realm.SyncCredentials; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; @@ -54,7 +52,7 @@ public AuthenticateResponse loginUser(SyncCredentials credentials, URL authentic String requestBody = AuthenticateRequest.userLogin(credentials).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { - return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); + return AuthenticateResponse.from(e); } } @@ -64,7 +62,7 @@ public AuthenticateResponse loginToRealm(Token refreshToken, URI serverUrl, URL String requestBody = AuthenticateRequest.realmLogin(refreshToken, serverUrl).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { - return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); + return AuthenticateResponse.from(e); } } @@ -74,7 +72,7 @@ public AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL auth String requestBody = AuthenticateRequest.userRefresh(userToken, serverUrl).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { - return AuthenticateResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); + return AuthenticateResponse.from(e); } } @@ -84,7 +82,7 @@ public LogoutResponse logout(Token userToken, URL authenticationUrl) { String requestBody = LogoutRequest.create(userToken).toJson(); return logout(buildActionUrl(authenticationUrl, ACTION_LOGOUT), requestBody); } catch (Exception e) { - return LogoutResponse.from(new ObjectServerError(ErrorCode.UNKNOWN, e)); + return LogoutResponse.from(e); } } @@ -93,8 +91,8 @@ public ChangePasswordResponse changePassword(Token userToken, String newPassword try { String requestBody = ChangePasswordRequest.create(userToken, newPassword).toJson(); return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD), requestBody); - } catch (Throwable e) { - return ChangePasswordResponse.createFailure(new ObjectServerError(ErrorCode.UNKNOWN, e)); + } catch (Exception e) { + return ChangePasswordResponse.from(e); } } @@ -103,8 +101,8 @@ public ChangePasswordResponse changePassword(Token adminToken, String userId, St try { String requestBody = ChangePasswordRequest.create(adminToken, userId, newPassword).toJson(); return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD), requestBody); - } catch (Throwable e) { - return ChangePasswordResponse.createFailure(new ObjectServerError(ErrorCode.UNKNOWN, e)); + } catch (Exception e) { + return ChangePasswordResponse.from(e); } } From 95f01fb4a4e24f5f84932984670081375b42fcf6 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 20 Jun 2017 21:12:19 +0900 Subject: [PATCH 0768/2110] Suppress warnings in JNIQueryTest and JNITableInsertTest (#4815) * suppress warnings in JNIQueryTest and JNITableInsertTest * stop suppressing deprecation warnings --- .../java/io/realm/internal/JNIQueryTest.java | 280 +++++++++--------- .../io/realm/internal/JNITableInsertTest.java | 8 +- 2 files changed, 144 insertions(+), 144 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index 26182e56d7..ce7de91091 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -40,7 +40,7 @@ protected void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); } - void init() { + private void init() { table = new Table(); table.addColumn(RealmFieldType.INTEGER, "number"); table.addColumn(RealmFieldType.STRING, "name"); @@ -106,45 +106,45 @@ public void testInvalidColumnIndexEqualTo() { TableQuery query = table.where(); // Boolean - try { query.equalTo(new long[]{-1}, oneNullTable, true); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, oneNullTable, true); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, oneNullTable, true); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, true); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{9}, oneNullTable, true); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{10}, oneNullTable, true); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Date - try { query.equalTo(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Double - try { query.equalTo(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Float - try { query.equalTo(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Int / long - try { query.equalTo(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // String - try { query.equalTo(new long[]{-1}, oneNullTable, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, oneNullTable, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, oneNullTable, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{9}, oneNullTable, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{10}, oneNullTable, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // String case true - try { query.equalTo(new long[]{-1}, oneNullTable, "a", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, oneNullTable, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, oneNullTable, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, "a", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{9}, oneNullTable, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{10}, oneNullTable, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // String case false - try { query.equalTo(new long[]{-1}, oneNullTable, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{9}, oneNullTable, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.equalTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.equalTo(new long[]{-1}, oneNullTable, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{9}, oneNullTable, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.equalTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } public void testInvalidColumnIndexNotEqualTo() { @@ -153,40 +153,40 @@ public void testInvalidColumnIndexNotEqualTo() { // Date - try { query.notEqualTo(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Double - try { query.notEqualTo(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Float - try { query.notEqualTo(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Int / long - try { query.notEqualTo(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // String - try { query.notEqualTo(new long[]{-1}, oneNullTable, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // String case true - try { query.notEqualTo(new long[]{-1}, oneNullTable, "a", Case.SENSITIVE); fail("-1column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, "a", Case.SENSITIVE); fail("-1column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // String case false - try { query.notEqualTo(new long[]{-1}, oneNullTable, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.notEqualTo(new long[]{-1}, oneNullTable, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{9}, oneNullTable, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.notEqualTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } @@ -195,25 +195,25 @@ public void testInvalidColumnIndexGreaterThan() { TableQuery query = table.where(); // Date - try { query.greaterThan(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Double - try { query.greaterThan(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Float - try { query.greaterThan(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Int / long - try { query.greaterThan(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThan(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } @@ -222,25 +222,25 @@ public void testInvalidColumnIndexGreaterThanOrEqual() { TableQuery query = table.where(); // Date - try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Double - try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Float - try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Int / long - try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } @@ -249,25 +249,25 @@ public void testInvalidColumnIndexLessThan() { TableQuery query = table.where(); // Date - try { query.lessThan(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Double - try { query.lessThan(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Float - try { query.lessThan(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Int / long - try { query.lessThan(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThan(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } public void testInvalidColumnIndexLessThanOrEqual() { @@ -275,25 +275,25 @@ public void testInvalidColumnIndexLessThanOrEqual() { TableQuery query = table.where(); // Date - try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Double - try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Float - try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Int / long - try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } @@ -302,25 +302,25 @@ public void testInvalidColumnIndexBetween() { TableQuery query = table.where(); // Date - try { query.between(new long[]{-1}, new Date(), new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{9}, new Date(), new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{10}, new Date(), new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{-1}, new Date(), new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{9}, new Date(), new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{10}, new Date(), new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Double - try { query.between(new long[]{-1}, 4.5d, 6.0d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{9}, 4.5d, 6.0d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{10}, 4.5d, 6.0d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{-1}, 4.5d, 6.0d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{9}, 4.5d, 6.0d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{10}, 4.5d, 6.0d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Float - try { query.between(new long[]{-1}, 1.4f, 5.8f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{9}, 1.4f, 5.8f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{10}, 1.4f, 5.8f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{-1}, 1.4f, 5.8f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{9}, 1.4f, 5.8f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{10}, 1.4f, 5.8f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // Int / long - try { query.between(new long[]{-1}, 1, 10); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{9}, 1, 10); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.between(new long[]{10}, 1, 10); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.between(new long[]{-1}, 1, 10); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{9}, 1, 10); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.between(new long[]{10}, 1, 10); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } @@ -329,50 +329,51 @@ public void testInvalidColumnIndexContains() { TableQuery query = table.where(); // String - try { query.contains(new long[]{-1}, oneNullTable, "hey"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{9}, oneNullTable, "hey"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{10}, oneNullTable, "hey"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{-1}, oneNullTable, "hey"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.contains(new long[]{9}, oneNullTable, "hey"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.contains(new long[]{10}, oneNullTable, "hey"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // String case true - try { query.contains(new long[]{-1}, oneNullTable, "hey", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{9}, oneNullTable, "hey", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{10}, oneNullTable, "hey", Case.SENSITIVE); fail("-0 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{-1}, oneNullTable, "hey", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.contains(new long[]{9}, oneNullTable, "hey", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.contains(new long[]{10}, oneNullTable, "hey", Case.SENSITIVE); fail("-0 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} // String case false - try { query.contains(new long[]{-1}, oneNullTable, "hey", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{9}, oneNullTable, "hey", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException e) {} - try { query.contains(new long[]{10}, oneNullTable, "hey", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException e) {} + try { query.contains(new long[]{-1}, oneNullTable, "hey", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.contains(new long[]{9}, oneNullTable, "hey", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} + try { query.contains(new long[]{10}, oneNullTable, "hey", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } + @SuppressWarnings("ConstantConditions") public void testNullInputQuery() { Table t = new Table(); t.addColumn(RealmFieldType.DATE, "dateCol"); t.addColumn(RealmFieldType.STRING, "stringCol"); Date nullDate = null; - try { t.where().equalTo(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().notEqualTo(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().greaterThan(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().greaterThanOrEqual(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().lessThan(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().lessThanOrEqual(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().between(new long[]{0}, nullDate, new Date()); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().between(new long[]{0}, new Date(), nullDate); fail("Date is null"); } catch (IllegalArgumentException e) { } - try { t.where().between(new long[]{0}, nullDate, nullDate); fail("Dates are null"); } catch (IllegalArgumentException e) { } + try { t.where().equalTo(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().notEqualTo(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().greaterThan(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().greaterThanOrEqual(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().lessThan(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().lessThanOrEqual(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().between(new long[]{0}, nullDate, new Date()); fail("Date is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().between(new long[]{0}, new Date(), nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().between(new long[]{0}, nullDate, nullDate); fail("Dates are null"); } catch (IllegalArgumentException ignore) { } String nullString = null; - try { t.where().equalTo(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().equalTo(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().notEqualTo(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().notEqualTo(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().contains(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().contains(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().beginsWith(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().beginsWith(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().endsWith(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().endsWith(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().like(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException e) { } - try { t.where().like(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException e) { } + try { t.where().equalTo(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().equalTo(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().notEqualTo(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().notEqualTo(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().contains(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().contains(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().beginsWith(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().beginsWith(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().endsWith(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().endsWith(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().like(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } + try { t.where().like(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } } @@ -416,16 +417,15 @@ public void testShouldFind() { // Tests out of range. assertEquals(-1, query.find(6)); - try { query.find(7); fail("Exception expected"); } catch (ArrayIndexOutOfBoundsException e) { } + try { query.find(7); fail("Exception expected"); } catch (ArrayIndexOutOfBoundsException ignore) { } } public void testQueryTestForNoMatches() { - Table t = new Table(); - t = TestHelper.getTableWithAllColumnTypes(); + Table t = TestHelper.getTableWithAllColumnTypes(); - t.add(new byte[]{1,2,3}, true, new Date(1384423149761l), 4.5d, 5.7f, 100, "string"); + t.add(new byte[]{1,2,3}, true, new Date(1384423149761L), 4.5d, 5.7f, 100, "string"); TableQuery q = t.where().greaterThan(new long[]{5}, oneNullTable, 1000); // No matches diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java index 1241e4ba64..32fce953ba 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java @@ -36,11 +36,11 @@ @RunWith(Parameterized.class) public class JNITableInsertTest { - List value = new ArrayList(); + private List value = new ArrayList<>(); @Parameterized.Parameters public static Collection parameters() { - List value = new ArrayList(); + List value = new ArrayList<>(); value.add(0, true); value.add(1, "abc"); value.add(2, 123L); @@ -66,7 +66,7 @@ public void testShouldThrowExceptionWhenColumnNameIsTooLong() { table.addColumn(RealmFieldType.STRING, "THIS STRING HAS 64 CHARACTERS, " + "LONGER THAN THE MAX 63 CHARACTERS"); fail("Too long name"); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException ignore) { } } @@ -95,7 +95,7 @@ public void testGenericAddOnTable() { try { t.add(value.get(i)); fail("No matching type"); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException ignored) { } } } From d14f3f279f097dff0f116a02572778f2ef0cf3a9 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 21 Jun 2017 14:32:34 +0100 Subject: [PATCH 0769/2110] Returning session's state (#4821) * Returning session's state --- CHANGELOG.md | 1 + .../src/main/cpp/io_realm_SyncSession.cpp | 44 +++++++++- .../java/io/realm/internal/SharedRealm.java | 21 ++--- .../java/io/realm/SyncSession.java | 58 ++++++++++++- .../realm/objectserver/SyncSessionTests.java | 82 +++++++++++++++++++ 5 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cb8140005..f03336384a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Enhancements * [ObjectServer] Added support for Sync Progress Notifications through `SyncSession.addDownloadProgressListener(ProgressMode, ProgressListener)` and `SyncSession.addUploadProgressListener(ProgressMode, ProgressListener)` (#4104). +* [ObjectServer] Added `SyncSession.getState()` (#4784). * Added support for querying inverse relationships (#2904). * Moved inverse relationships out of beta stage. * Added `Realm.getDefaultConfiguration()` (#4725). diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 37bf4c087a..8a132d05ea 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -26,13 +26,28 @@ #include "jni_util/java_global_ref.hpp" #include "jni_util/java_method.hpp" #include "jni_util/java_class.hpp" -#include "jni_util/java_local_ref.hpp" #include "jni_util/jni_utils.hpp" using namespace realm; using namespace jni_util; using namespace sync; +static_assert(SyncSession::PublicState::WaitingForAccessToken == + static_cast(io_realm_SyncSession_STATE_VALUE_WAITING_FOR_ACCESS_TOKEN), + ""); +static_assert(SyncSession::PublicState::Active == + static_cast(io_realm_SyncSession_STATE_VALUE_ACTIVE), + ""); +static_assert(SyncSession::PublicState::Dying == + static_cast(io_realm_SyncSession_STATE_VALUE_DYING), + ""); +static_assert(SyncSession::PublicState::Inactive == + static_cast(io_realm_SyncSession_STATE_VALUE_INACTIVE), + ""); +static_assert(SyncSession::PublicState::Error == + static_cast(io_realm_SyncSession_STATE_VALUE_ERROR), + ""); + JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JNIEnv* env, jclass, jstring j_local_realm_path, jstring j_access_token, @@ -156,3 +171,30 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple CATCH_STD() return JNI_FALSE; } + +JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetState(JNIEnv* env, jclass, + jstring j_local_realm_path) +{ + TR_ENTER() + try { + JStringAccessor local_realm_path(env, j_local_realm_path); + auto session = SyncManager::shared().get_existing_session(local_realm_path); + + if (session) { + switch (session->state()) { + case SyncSession::PublicState::WaitingForAccessToken: + return io_realm_SyncSession_STATE_VALUE_WAITING_FOR_ACCESS_TOKEN; + case SyncSession::PublicState::Active: + return io_realm_SyncSession_STATE_VALUE_ACTIVE; + case SyncSession::PublicState::Dying: + return io_realm_SyncSession_STATE_VALUE_DYING; + case SyncSession::PublicState::Inactive: + return io_realm_SyncSession_STATE_VALUE_INACTIVE; + case SyncSession::PublicState::Error: + return io_realm_SyncSession_STATE_VALUE_ERROR; + } + } + } + CATCH_STD() + return -1; +} \ No newline at end of file diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 821d489b3d..98e79f0026 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -78,20 +78,13 @@ public enum Durability { } } - // Public for static checking in JNI - @SuppressWarnings("WeakerAccess") - public static final byte SCHEMA_MODE_VALUE_AUTOMATIC = 0; - @SuppressWarnings("WeakerAccess") - public static final byte SCHEMA_MODE_VALUE_READONLY = 1; - @SuppressWarnings("WeakerAccess") - public static final byte SCHEMA_MODE_VALUE_RESET_FILE = 2; - @SuppressWarnings("WeakerAccess") - public static final byte SCHEMA_MODE_VALUE_ADDITIVE = 3; - @SuppressWarnings("WeakerAccess") - public static final byte SCHEMA_MODE_VALUE_MANUAL = 4; - - @SuppressWarnings("WeakerAccess") - public enum SchemaMode { + private static final byte SCHEMA_MODE_VALUE_AUTOMATIC = 0; + private static final byte SCHEMA_MODE_VALUE_READONLY = 1; + private static final byte SCHEMA_MODE_VALUE_RESET_FILE = 2; + private static final byte SCHEMA_MODE_VALUE_ADDITIVE = 3; + private static final byte SCHEMA_MODE_VALUE_MANUAL = 4; + + private enum SchemaMode { SCHEMA_MODE_AUTOMATIC(SCHEMA_MODE_VALUE_AUTOMATIC), SCHEMA_MODE_READONLY(SCHEMA_MODE_VALUE_READONLY), SCHEMA_MODE_RESET_FILE(SCHEMA_MODE_VALUE_RESET_FILE), diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 601692f367..846f79f290 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -86,6 +86,38 @@ public class SyncSession { // we register the listener. private final AtomicLong progressListenerId = new AtomicLong(-1); + // represent different states as defined in SyncSession::PublicState 'sync_session.hpp' + private static final byte STATE_VALUE_WAITING_FOR_ACCESS_TOKEN = 0; + private static final byte STATE_VALUE_ACTIVE = 1; + private static final byte STATE_VALUE_DYING = 2; + private static final byte STATE_VALUE_INACTIVE = 3; + private static final byte STATE_VALUE_ERROR = 4; + + public enum State { + WAITING_FOR_ACCESS_TOKEN(STATE_VALUE_WAITING_FOR_ACCESS_TOKEN), + ACTIVE(STATE_VALUE_ACTIVE), + DYING(STATE_VALUE_DYING), + INACTIVE(STATE_VALUE_INACTIVE), + ERROR(STATE_VALUE_ERROR); + + final byte value; + + State(byte value) { + this.value = value; + } + + static State fromByte(byte value) { + State[] stateCodes = values(); + for (State state : stateCodes) { + if (state.value == value) { + return state; + } + } + + throw new IllegalArgumentException("Unknown state code: " + value); + } + } + SyncSession(SyncConfiguration configuration) { this.configuration = configuration; this.errorHandler = configuration.getErrorHandler(); @@ -136,6 +168,27 @@ void notifySessionError(int errorCode, String errorMessage) { } } + /** + * Get the current session's state, as defined in {@link SyncSession.State}. + * + * Note that the state may change after this method returns, example: the authentication + * token will expire, causing the session to move to {@link State#WAITING_FOR_ACCESS_TOKEN} + * after it was in {@link State#ACTIVE}. + * + * @return the state of the session. + * @see SyncSession.State + */ + @KeepMember + @SuppressWarnings("unused") + public State getState() { + byte state = nativeGetState(configuration.getPath()); + if (state == -1) { + // session was not found, probably the Realm was closed + throw new IllegalStateException("Could not find session, Realm was probably closed"); + } + return State.fromByte(state); + } + synchronized void notifyProgressListener(long listenerId, long transferredBytes, long transferableBytes) { Pair listener = listenerIdToProgressListenerMap.get(listenerId); if (listener != null) { @@ -589,6 +642,7 @@ public void throwExceptionIfNeeded() { private static native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); private static native void nativeRemoveProgressListener(String localRealmPath, long listenerToken); - private static native boolean nativeRefreshAccessToken(String path, String accessToken, String realmUrl); - private native boolean nativeWaitForDownloadCompletion(String path); + private static native boolean nativeRefreshAccessToken(String localRealmPath, String accessToken, String realmUrl); + private native boolean nativeWaitForDownloadCompletion(String localRealmPath); + private static native byte nativeGetState(String localRealmPath); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java new file mode 100644 index 0000000000..695fd03989 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java @@ -0,0 +1,82 @@ +package io.realm.objectserver; + +import android.os.SystemClock; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; + +import io.realm.BaseIntegrationTest; +import io.realm.Realm; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.SyncSession; +import io.realm.SyncUser; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.UserFactory; +import io.realm.rule.TestSyncConfigurationFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class SyncSessionTests extends BaseIntegrationTest { + @Rule + public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @Test + public void getState_active() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .waitForInitialRemoteData() + .build(); + Realm realm = Realm.getInstance(syncConfiguration); + + SyncSession session = SyncManager.getSession(syncConfiguration); + + // make sure the `access_token` is acquired. otherwise we can still be + // in WAITING_FOR_ACCESS_TOKEN state + SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); + + assertEquals(SyncSession.State.ACTIVE, session.getState()); + realm.close(); + } + + @Test + public void getState_inactive() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .waitForInitialRemoteData() + .build(); + Realm realm = Realm.getInstance(syncConfiguration); + + SyncSession session = SyncManager.getSession(syncConfiguration); + user.logout(); + assertEquals(SyncSession.State.INACTIVE, session.getState()); + + realm.close(); + } + + @Test + public void getState_closedRealm() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .waitForInitialRemoteData() + .build(); + Realm realm = Realm.getInstance(syncConfiguration); + + SyncSession session = SyncManager.getSession(syncConfiguration); + realm.close(); + try { + session.getState(); + fail("Realm was closed, getState should not return"); + } catch (IllegalStateException expected) { + } + } +} From 2552f88e60f5154e67cfd6bedd34ec3256363659 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 22 Jun 2017 15:18:54 +0900 Subject: [PATCH 0770/2110] migrate some test cases to JUnit4 (#4823) --- .../io/realm/internal/JNIColumnInfoTest.java | 19 ++-- .../java/io/realm/internal/JNIQueryTest.java | 87 +++++++++++-------- .../java/io/realm/internal/JNIRowTest.java | 19 +++- .../io/realm/internal/JNISortedLongTest.java | 13 ++- .../java/io/realm/internal/PivotTest.java | 21 +++-- .../internal/TableIndexAndDistinctTest.java | 23 +++-- 6 files changed, 121 insertions(+), 61 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java index a44043f00b..1eaeee2ba7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java @@ -17,17 +17,24 @@ package io.realm.internal; import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import io.realm.Realm; import io.realm.RealmFieldType; -public class JNIColumnInfoTest extends TestCase { +import static junit.framework.TestCase.assertEquals; + + +@RunWith(AndroidJUnit4.class) +public class JNIColumnInfoTest { Table table; - @Override + @Before public void setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); table = new Table(); @@ -35,7 +42,8 @@ public void setUp() { table.addColumn(RealmFieldType.STRING, "lastName"); } - public void testShouldGetColumnInformation() { + @Test + public void shouldGetColumnInformation() { assertEquals(2, table.getColumnCount()); @@ -47,7 +55,8 @@ public void testShouldGetColumnInformation() { } - public void testValidateColumnInfo() { + @Test + public void validateColumnInfo() { assertEquals(2, table.getColumnCount()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index ce7de91091..d77bf67ecd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -17,8 +17,11 @@ package io.realm.internal; import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import java.util.Date; import java.util.concurrent.TimeUnit; @@ -28,15 +31,19 @@ import io.realm.RealmFieldType; import io.realm.TestHelper; -public class JNIQueryTest extends TestCase { +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.fail; + + +@RunWith(AndroidJUnit4.class) +public class JNIQueryTest { private Table table; private final long[] oneNullTable = new long[]{NativeObject.NULLPTR}; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); } @@ -54,7 +61,8 @@ private void init() { assertEquals(6, table.size()); } - public void testShouldQuery() { + @Test + public void shouldQuery() { init(); TableQuery query = table.where(); @@ -77,7 +85,8 @@ public void testShouldQuery() { } - public void testNonCompleteQuery() { + @Test + public void nonCompleteQuery() { init(); // All the following queries are not valid, e.g contain a group but not a closing group, an or() but not a second filter etc @@ -101,7 +110,8 @@ public void testNonCompleteQuery() { try { table.where().equalTo(new long[]{0}, oneNullTable, 1).endGroup().find(1); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} } - public void testInvalidColumnIndexEqualTo() { + @Test + public void invalidColumnIndexEqualTo() { Table table = TestHelper.getTableWithAllColumnTypes(); TableQuery query = table.where(); @@ -147,7 +157,8 @@ public void testInvalidColumnIndexEqualTo() { try { query.equalTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } - public void testInvalidColumnIndexNotEqualTo() { + @Test + public void invalidColumnIndexNotEqualTo() { Table table = TestHelper.getTableWithAllColumnTypes(); TableQuery query = table.where(); @@ -189,8 +200,8 @@ public void testInvalidColumnIndexNotEqualTo() { try { query.notEqualTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } - - public void testInvalidColumnIndexGreaterThan() { + @Test + public void invalidColumnIndexGreaterThan() { Table table = TestHelper.getTableWithAllColumnTypes(); TableQuery query = table.where(); @@ -216,8 +227,8 @@ public void testInvalidColumnIndexGreaterThan() { try { query.greaterThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } - - public void testInvalidColumnIndexGreaterThanOrEqual() { + @Test + public void invalidColumnIndexGreaterThanOrEqual() { Table table = TestHelper.getTableWithAllColumnTypes(); TableQuery query = table.where(); @@ -243,8 +254,8 @@ public void testInvalidColumnIndexGreaterThanOrEqual() { try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } - - public void testInvalidColumnIndexLessThan() { + @Test + public void invalidColumnIndexLessThan() { Table table = TestHelper.getTableWithAllColumnTypes(); TableQuery query = table.where(); @@ -270,7 +281,8 @@ public void testInvalidColumnIndexLessThan() { try { query.lessThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } - public void testInvalidColumnIndexLessThanOrEqual() { + @Test + public void invalidColumnIndexLessThanOrEqual() { Table table = TestHelper.getTableWithAllColumnTypes(); TableQuery query = table.where(); @@ -296,8 +308,8 @@ public void testInvalidColumnIndexLessThanOrEqual() { try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } - - public void testInvalidColumnIndexBetween() { + @Test + public void invalidColumnIndexBetween() { Table table = TestHelper.getTableWithAllColumnTypes(); TableQuery query = table.where(); @@ -323,8 +335,8 @@ public void testInvalidColumnIndexBetween() { try { query.between(new long[]{10}, 1, 10); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} } - - public void testInvalidColumnIndexContains() { + @Test + public void invalidColumnIndexContains() { Table table = TestHelper.getTableWithAllColumnTypes(); TableQuery query = table.where(); @@ -345,7 +357,8 @@ public void testInvalidColumnIndexContains() { } @SuppressWarnings("ConstantConditions") - public void testNullInputQuery() { + @Test + public void nullInputQuery() { Table t = new Table(); t.addColumn(RealmFieldType.DATE, "dateCol"); t.addColumn(RealmFieldType.STRING, "stringCol"); @@ -376,9 +389,8 @@ public void testNullInputQuery() { try { t.where().like(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } } - - - public void testShouldFind() { + @Test + public void shouldFind() { // Creates a table. Table table = new Table(); @@ -420,9 +432,8 @@ public void testShouldFind() { try { query.find(7); fail("Exception expected"); } catch (ArrayIndexOutOfBoundsException ignore) { } } - - - public void testQueryTestForNoMatches() { + @Test + public void queryTestForNoMatches() { Table t = TestHelper.getTableWithAllColumnTypes(); t.add(new byte[]{1,2,3}, true, new Date(1384423149761L), 4.5d, 5.7f, 100, "string"); @@ -433,9 +444,8 @@ public void testQueryTestForNoMatches() { assertEquals(-1, q.find(1)); } - - - public void testQueryWithWrongDataType() { + @Test + public void queryWithWrongDataType() { Table table = TestHelper.getTableWithAllColumnTypes(); @@ -516,8 +526,8 @@ public void testQueryWithWrongDataType() { */ } - - public void testColumnIndexOutOfBounds() { + @Test + public void columnIndexOutOfBounds() { Table table = TestHelper.getTableWithAllColumnTypes(); // Queries the table. @@ -617,7 +627,8 @@ public void testColumnIndexOutOfBounds() { try { query.equalTo(new long[]{7}, oneNullTable, true); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} } - public void testMaximumDate() { + @Test + public void maximumDate() { Table table = new Table(); table.addColumn(RealmFieldType.DATE, "date"); @@ -629,8 +640,8 @@ public void testMaximumDate() { assertEquals(new Date(10000), table.where().maximumDate(0)); } - - public void testMinimumDate() { + @Test + public void minimumDate() { Table table = new Table(); table.addColumn(RealmFieldType.DATE, "date"); @@ -642,7 +653,8 @@ public void testMinimumDate() { assertEquals(new Date(0), table.where().minimumDate(0)); } - public void testDateQuery() throws Exception { + @Test + public void dateQuery() throws Exception { Table table = new Table(); table.addColumn(RealmFieldType.DATE, "date"); @@ -740,7 +752,8 @@ public void testDateQuery() throws Exception { assertEquals(1L, table.where().between(new long[]{0}, distantFuture, distantFuture).count()); } - public void testByteArrayQuery() throws Exception { + @Test + public void byteArrayQuery() throws Exception { Table table = new Table(); table.addColumn(RealmFieldType.BINARY, "binary"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java index 3d8902ea90..12258cb24e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java @@ -16,17 +16,27 @@ package io.realm.internal; +import android.support.test.runner.AndroidJUnit4; import android.test.MoreAsserts; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; import java.util.Date; import io.realm.RealmFieldType; -public class JNIRowTest extends TestCase { +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; - public void testRow() { + +@RunWith(AndroidJUnit4.class) +public class JNIRowTest { + + @Test + public void nonNullValues() { Table table = new Table(); @@ -74,7 +84,8 @@ public void testRow() { MoreAsserts.assertEquals(newData, row.getBinaryByteArray(6)); } - public void testNull() { + @Test + public void nullValues() { Table table = new Table(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java index 6c14e963b4..4145a51749 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java @@ -17,13 +17,19 @@ package io.realm.internal; import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; import io.realm.Realm; import io.realm.RealmFieldType; -public class JNISortedLongTest extends TestCase { +import static org.junit.Assert.assertEquals; + + +@RunWith(AndroidJUnit4.class) +public class JNISortedLongTest { Table table; void init() { @@ -44,7 +50,8 @@ void init() { assertEquals(8, table.size()); } - public void testShouldTestSortedIntTable() { + @Test + public void shouldTestSortedIntTable() { init(); // Before first entry. diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java index 67006b5592..574fcb43c3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java @@ -17,21 +17,29 @@ package io.realm.internal; import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import io.realm.Realm; import io.realm.RealmFieldType; import io.realm.internal.Table.PivotType; -public class PivotTest extends TestCase { +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + + +@RunWith(AndroidJUnit4.class) +public class PivotTest { Table t; long colIndexSex; long colIndexAge; long colIndexHired; - @Override + @Before public void setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); t = new Table(); @@ -45,7 +53,8 @@ public void setUp() { } } - public void testPivotTable(){ + @Test + public void pivotTable(){ Table resultCount = t.pivot(colIndexSex, colIndexAge, PivotType.COUNT); assertEquals(2, resultCount.size()); @@ -60,7 +69,7 @@ public void testPivotTable(){ assertEquals(38, resultMax.getLong(1, 0)); assertEquals(39, resultMax.getLong(1, 1)); - try { t.pivot(colIndexHired, colIndexAge, PivotType.SUM); fail("Group by not a String column"); } catch (UnsupportedOperationException e) { } - try { t.pivot(colIndexSex, colIndexHired, PivotType.SUM); fail("Aggregation not an int column"); } catch (UnsupportedOperationException e) { } + try { t.pivot(colIndexHired, colIndexAge, PivotType.SUM); fail("Group by not a String column"); } catch (UnsupportedOperationException ignore) { } + try { t.pivot(colIndexSex, colIndexHired, PivotType.SUM); fail("Aggregation not an int column"); } catch (UnsupportedOperationException ignore) { } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java index 041c45ced3..fe4fb16267 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java @@ -16,11 +16,18 @@ package io.realm.internal; -import junit.framework.TestCase; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; import io.realm.RealmFieldType; -public class TableIndexAndDistinctTest extends TestCase { +import static org.junit.Assert.assertEquals; + + +@RunWith(AndroidJUnit4.class) +public class TableIndexAndDistinctTest { Table table; void init() { @@ -43,7 +50,8 @@ void init() { * Checks that Index can be set on multiple columns, with the String. * @param */ - public void testShouldTestSettingIndexOnMultipleColumns() { + @Test + public void shouldTestSettingIndexOnMultipleColumns() { // Creates a table only with String type columns Table t = new Table(); @@ -85,12 +93,14 @@ public void shouldTestIndexOnWrongColumnType(Long index) { t.addSearchIndex(index); }*/ - public void testShouldCheckIndexIsOkOnColumn() { + @Test + public void shouldCheckIndexIsOkOnColumn() { init(); table.addSearchIndex(1); } - public void testRemoveSearchIndex() { + @Test + public void removeSearchIndex() { init(); table.addSearchIndex(1); assertEquals(true, table.hasSearchIndex(1)); @@ -99,7 +109,8 @@ public void testRemoveSearchIndex() { assertEquals(false, table.hasSearchIndex(1)); } - public void testRemoveSearchIndexNoop() { + @Test + public void removeSearchIndexNoOp() { init(); assertEquals(false, table.hasSearchIndex(1)); From ae1c94495847b660436a5e887f36357c9a5bbd5d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 21 Jun 2017 21:51:03 +0800 Subject: [PATCH 0771/2110] Fix flaky tests caused by half inited ROS - Hack for detecting if the ROS is fully initialized in the docker sided. - Dockerfile optimization. - Use "0.0.0.0" instead of "::" to solve dns issues inside docker. --- tools/sync_test_server/Dockerfile | 6 ++--- tools/sync_test_server/configuration.yml | 4 ++-- tools/sync_test_server/ros-testing-server.js | 24 ++++++++++++++++---- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 6404a0e086..829ecf5b0c 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -12,12 +12,12 @@ RUN apt-get update -qq \ RUN npm init -y RUN npm install winston temp httpdispatcher@1.0.0 -COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem configuration.yml / -COPY ros-testing-server.js /usr/bin/ - # Install realm object server RUN apt-get update -qq \ && apt-get install -y realm-object-server-developer=$ROS_DE_VERSION \ && apt-get clean +COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem configuration.yml / +COPY ros-testing-server.js /usr/bin/ + CMD /usr/bin/ros-testing-server.js /tmp/ros-testing-server.log diff --git a/tools/sync_test_server/configuration.yml b/tools/sync_test_server/configuration.yml index d9a350fc00..e3440d05bb 100644 --- a/tools/sync_test_server/configuration.yml +++ b/tools/sync_test_server/configuration.yml @@ -154,7 +154,7 @@ proxy: ## The address/interface on which the HTTP proxy module should listen. This defaults ## to 127.0.0.1. If you wish to listen on all available interfaces, ## uncomment the following line. - listen_address: '::' + listen_address: '0.0.0.0' ## The port that the HTTP proxy module should bind to. # listen_port: 9080 @@ -175,7 +175,7 @@ proxy: ## The address/interface on which the HTTPS proxy module should listen. This defaults ## to 127.0.0.1. If you wish to listen on all available interfaces, ## uncomment the following line. - listen_address: '::' + listen_address: '0.0.0.0' ## The port that the HTTPS proxy module should bind to. listen_port: 9443 diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index ae43a8477a..ca7163c46e 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -33,7 +33,13 @@ function handleRequest(request, response) { var syncServerChildProcess = null; -function startRealmObjectServer() { +function startRealmObjectServer(done) { + // Hack for checking the ROS is fully initialized. + // Consider the ROS is initialized fully only if log below shows twice + // "client: Closing Realm file: /tmp/ros117521-7-1eiqt7a/internal_data/permission/__auth.realm" + // https://github.com/realm/realm-object-server/issues/1297 + var logFindingCounter = 2 + stopRealmObjectServer(); temp.mkdir('ros', function(err, path) { if (!err) { @@ -44,9 +50,15 @@ function startRealmObjectServer() { syncServerChildProcess = spawn('realm-object-server', ['--root', path, '--configuration', '/configuration.yml'], - { env: env }); + { env: env}); // local config: syncServerChildProcess.stdout.on('data', (data) => { + if (logFindingCounter != 0 && /client: Closing Realm file: .*__auth.realm/.test(data)) { + if (logFindingCounter == 1) { + done() + } + logFindingCounter-- + } winston.info(`stdout: ${data}`); }); @@ -75,11 +87,13 @@ function stopRealmObjectServer() { } } + // start sync server dispatcher.onGet("/start", function(req, res) { - startRealmObjectServer(); - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('Starting a server'); + startRealmObjectServer(() => { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Starting a server'); + }) }); // stop a previously started sync server From 32bb8d71b5096e192cbc1b037811997a5840a26b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 22 Jun 2017 17:30:48 +0800 Subject: [PATCH 0772/2110] Fix GCed ref caused flaky test (#4827) --- .../src/androidTest/java/io/realm/RealmAsyncQueryTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 4caa6d94c1..12223436d2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -735,6 +735,7 @@ public void findFirstAsync_twoListenersOnSameInvalidObjectsCauseNPE() { final Realm realm = looperThread.getRealm(); final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); final AtomicBoolean firstListenerCalled = new AtomicBoolean(false); + looperThread.keepStrongReference(allTypes); allTypes.addChangeListener(new RealmChangeListener() { @Override From ece57be1dbbe2a042033adb886ba12fbfde718fb Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 22 Jun 2017 18:03:23 +0800 Subject: [PATCH 0773/2110] Local ref needs to be cleaned on client thread (#4830) - Add support for null JavaLocalRef. - Clean the local ref after notifyAllChangesDownloaded. --- CHANGELOG.md | 2 ++ .../src/main/cpp/io_realm_SyncSession.cpp | 26 +++++++++---------- .../src/main/cpp/jni_util/java_local_ref.hpp | 13 +++++++--- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f03336384a..5f2496ee2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ ### Bug Fixes +* [ObjectServer] Fixed a bug which may crash when the JNI local reference limitation was reached on sync client thread. + ### Internal * Upgraded to Realm Sync 1.10.1 diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 8a132d05ea..5dde2c61e5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -24,6 +24,7 @@ #include "util.hpp" #include "jni_util/java_global_ref.hpp" +#include "jni_util/java_local_ref.hpp" #include "jni_util/java_method.hpp" #include "jni_util/java_class.hpp" #include "jni_util/jni_utils.hpp" @@ -98,9 +99,10 @@ JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIE uint64_t transferred, uint64_t transferrable) { JNIEnv* local_env = jni_util::JniUtils::get_env(true); - auto path = to_jstring(local_env, local_realm_path); - local_env->CallStaticVoidMethod(java_syncmanager_class, java_notify_progress_listener, path, listener_id, - static_cast(transferred), static_cast(transferrable)); + JavaLocalRef path(local_env, to_jstring(local_env, local_realm_path)); + local_env->CallStaticVoidMethod(java_syncmanager_class, java_notify_progress_listener, path.get(), + listener_id, static_cast(transferred), + static_cast(transferrable)); // All exceptions will be caught on the Java side of handlers, but Errors will still end // up here, so we need to do something sensible with them. @@ -111,10 +113,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIE local_env->ExceptionDescribe(); throw std::runtime_error("An unexpected Error was thrown from Java. See LogCat"); } - - // Callback happens on a thread not controlled by the JVM. So manual cleanup is - // required. - local_env->DeleteLocalRef(path); }; uint64_t token = session->register_progress_notifier(callback, type, to_bool(is_streaming)); return static_cast(token); @@ -155,14 +153,14 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple bool listener_registered = session->wait_for_download_completion([java_session_object_ref](std::error_code error) { JNIEnv* env = JniUtils::get_env(true); - jobject java_error_code = nullptr; - jstring java_error_message = nullptr; + JavaLocalRef java_error_code; + JavaLocalRef java_error_message; if (error != std::error_code{}) { - java_error_code = NewLong(env, error.value()); - java_error_message = env->NewStringUTF(error.message().c_str()); + java_error_code = JavaLocalRef(env, NewLong(env, error.value())); + java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); } - env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, java_error_code, - java_error_message); + env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, java_error_code.get(), + java_error_message.get()); }); return to_jbool(listener_registered); @@ -197,4 +195,4 @@ JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetState(JNIEnv* env, jc } CATCH_STD() return -1; -} \ No newline at end of file +} diff --git a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp index 4a57062471..0417887449 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_local_ref.hpp @@ -33,17 +33,22 @@ static constexpr NeedToCreateLocalRef need_to_create_local_ref{}; template class JavaLocalRef { public: - // need_to_create is useful when acquire a local ref from a global weak ref. + inline JavaLocalRef() noexcept + : m_jobject(nullptr) + , m_env(nullptr){}; inline JavaLocalRef(JNIEnv* env, T obj) noexcept : m_jobject(obj) , m_env(env){}; + // need_to_create is useful when acquire a local ref from a global weak ref. inline JavaLocalRef(JNIEnv* env, T obj, NeedToCreateLocalRef) noexcept : m_jobject(env->NewLocalRef(obj)) , m_env(env){}; inline ~JavaLocalRef() { - m_env->DeleteLocalRef(m_jobject); + if (m_jobject) { + m_env->DeleteLocalRef(m_jobject); + } } JavaLocalRef& operator=(JavaLocalRef&& rhs) @@ -54,9 +59,11 @@ class JavaLocalRef { } inline JavaLocalRef(JavaLocalRef&& rhs) - : m_env(rhs.m_env), m_jobject(rhs.m_jobject) + : m_jobject(rhs.m_jobject) + , m_env(rhs.m_env) { rhs.m_jobject = nullptr; + rhs.m_env = nullptr; } inline operator bool() const noexcept From 00acc92cb83d67c821319c4eb23bc7d34a18bd45 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 22 Jun 2017 17:27:31 +0800 Subject: [PATCH 0774/2110] Decpreate schema's close() methods --- CHANGELOG.md | 4 ++++ .../src/main/java/io/realm/RealmObjectSchema.java | 3 ++- realm/realm-library/src/main/java/io/realm/RealmSchema.java | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f2496ee2e..7fbfc095e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ * [ObjectServer] Updated protocol version to 18 which is only compatible with ROS > 1.6.0. +### Deprecated + +* `RealmSchema.close()` and `RealmObjectSchema.close()`. They don't need to be closed manually. They were added to the public API by mistake. + ### Enhancements * [ObjectServer] Added support for Sync Progress Notifications through `SyncSession.addDownloadProgressListener(ProgressMode, ProgressListener)` and `SyncSession.addUploadProgressListener(ProgressMode, ProgressListener)` (#4104). diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index f0a029fac9..106d97b4a1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -42,8 +42,9 @@ protected RealmObjectSchema(RealmSchema schema) { } /** - * Release the object schema and any of native resources it might hold. + * @deprecated {@link RealmObjectSchema} doesn't have to be released manually. */ + @Deprecated public abstract void close(); /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index cc7a15bbdb..17049602d4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -37,8 +37,9 @@ public abstract class RealmSchema { private ColumnIndices columnIndices; // Cached field look up /** - * Release the schema and any of native resources it might hold. + * @deprecated {@link RealmSchema} doesn't have to be released manually. */ + @Deprecated public abstract void close(); /** From b99e2f0327437f62ce1476512b798f64dbc482be Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 22 Jun 2017 17:15:29 +0200 Subject: [PATCH 0775/2110] Update changelog --- CHANGELOG.md | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fbfc095e6..c52bd82f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ ### Bug Fixes * [ObjectServer] Fixed a bug which may crash when the JNI local reference limitation was reached on sync client thread. +* [ObjectServer] Retrying connections with exponential backoff, when encountering `ConnectException` (#4310). +* When converting nullable BLOB field to required, `null` values should be converted to `byte[0]` instead of `byte[1]`. +* Fixed a bug which may cause duplicated primary key values when migrating a nullable primary key field to not nullable. `RealmObjectSchema.setRequired()` and `RealmObjectSchema.setNullable()` will throw when converting a nullable primary key field with null values stored to a required primary key field. ### Internal @@ -29,19 +32,6 @@ * Thanks to Anis Ben Nsir (@abennsir) for upgrading Roboelectric in the unitTestExample (#4698). -## 3.3.3 (YYYY-MM-DD) - -### Breaking Changes - -### Enhancements - -### Bug Fixes - -* When converting nullable BLOB field to required, `null` values should be converted to `byte[0]` instead of `byte[1]`. -* Fixed a bug which may cause duplicated primary key values when migrating a nullable primary key field to not nullable. `RealmObjectSchema.setRequired()` and `RealmObjectSchema.setNullable()` will throw when converting a nullable primary key field with null values stored to a required primary key field. -* [ObjectServer] Retrying connections with exponential backoff, when encountering `ConnectException` (#4310). - -### Internal ## 3.3.2 (2017-06-09) From 56e63b1af255b6cad4a8b851fb9e1bbd08e73d2b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 22 Jun 2017 17:18:34 +0200 Subject: [PATCH 0776/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c52bd82f99..7c782cfdd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.4.0 (YYYY-MM-DD) +## 3.4.0 (2017-06-22) ### Breaking Changes From 9999686e9bc15ee3de4174859447e87347863654 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 22 Jun 2017 17:18:37 +0200 Subject: [PATCH 0777/2110] Release v3.4.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 6b970ae4c3..fbcbf73806 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.4.0-SNAPSHOT +3.4.0 \ No newline at end of file From cc184b048481e37dbdbb26882677ac85caad5345 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 22 Jun 2017 17:18:38 +0200 Subject: [PATCH 0778/2110] Prepare next release v3.4.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index fbcbf73806..6a978a6ab2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.4.0 \ No newline at end of file +3.4.1-SNAPSHOT \ No newline at end of file From 49ea05beda6b21535c9abf91e6c7ee372fd5f653 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 22 Jun 2017 18:36:20 +0200 Subject: [PATCH 0779/2110] prepare next dev iteration --- CHANGELOG.md | 15 +++++++++++++++ version.txt | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c782cfdd6..3aefb83c90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 3.5.0 (YYYY-MM-DD) + +### Breaking Changes + +### Deprecated + +### Enhancements + +### Bug Fixes + +### Internal + +### Credits + + ## 3.4.0 (2017-06-22) ### Breaking Changes diff --git a/version.txt b/version.txt index 6a978a6ab2..b9821b826b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.4.1-SNAPSHOT \ No newline at end of file +3.5.0-SNAPSHOT \ No newline at end of file From eec600d4b4668017270416afb08897e278fd4579 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 23 Jun 2017 09:47:26 +0200 Subject: [PATCH 0780/2110] Allow multiple tasks to run after test is complete. (#4833) --- .../androidTest/java/io/realm/rule/RunInLooperThread.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index f44a42e56a..e5b6d65565 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -89,7 +89,7 @@ public class RunInLooperThread extends TestRealmConfigurationFactory { // Runnable guaranteed to trigger after the test either succeeded or failed. // Access guarded by 'lock' - private Runnable runAfterTestIsComplete; + private List runAfterTestIsComplete = new ArrayList<>(); /** * Get the configuration for the test realm. @@ -162,7 +162,7 @@ public void closeAfterTest(Closeable closeable) { */ public void runAfterTest(Runnable task) { synchronized (lock) { - runAfterTestIsComplete = task; + runAfterTestIsComplete.add(task); } } @@ -485,8 +485,8 @@ public void run() { try { looperTearDown(); closeResources(); - if (runAfterTestIsComplete != null) { - runAfterTestIsComplete.run(); + for (Runnable task : runAfterTestIsComplete) { + task.run(); } } catch (Throwable t) { setAssertionError(t); From 41c2234d5c96cc032b8395ce86292839c9af1c9a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Jun 2017 15:38:04 +0800 Subject: [PATCH 0781/2110] Remove outdated addSearchIndexTwice RealmObjectSchema.addIndex() is not idempotent. The test was created in #1544 for issue #1385 which was using Table.addSearchIndex(). RealmObjectSchema.addIndex() will just throw when called twice, it is guaranteed by test addIndexFieldModifier_alreadyIndexedThrows. The test can accidentally pass is just because of default-before-migration.realm was created before 0.82 when the primary key was not auto-indexed yet. --- .../java/io/realm/RealmMigrationTests.java | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 2f08cfc378..cd779ac531 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -751,41 +751,6 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm.close(); } - // Adding search index is idempotent. - @Test - public void addingSearchIndexTwice() throws IOException { - final Class[] classes = {PrimaryKeyAsLong.class, PrimaryKeyAsString.class}; - - for (final Class clazz : classes) { - final AtomicBoolean didMigrate = new AtomicBoolean(false); - - RealmMigration migration = new RealmMigration() { - @Override - public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { - RealmObjectSchema schema = realm.getSchema().getSchemaForClass(clazz.getSimpleName()); - schema.addIndex("id"); - // @PrimaryKey fields in PrimaryKeyAsLong and PrimaryKeyAsString.class should be set 'nullable'. - schema.setNullable("name", true); - didMigrate.set(true); - } - }; - RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() - .schemaVersion(42) - .schema(clazz) - .migration(migration) - .build(); - Realm.deleteRealm(realmConfig); - configFactory.copyRealmFromAssets(context, "default-before-migration.realm", Realm.DEFAULT_REALM_NAME); - Realm.migrateRealm(realmConfig); - realm = Realm.getInstance(realmConfig); - assertEquals(42, realm.getVersion()); - assertTrue(didMigrate.get()); - Table table = realm.getTable(clazz); - assertEquals(true, table.hasSearchIndex(table.getColumnIndex("id"))); - realm.close(); - } - } - @Test public void setAnnotations() { From 5b1ff27ceac42164082d768da32889910398bca7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 23 Jun 2017 17:13:27 +0800 Subject: [PATCH 0782/2110] Separated dir for remote SyncManager (#4808) This may cause some flaky tests on CI. --- .../java/io/realm/ObjectServer.java | 23 ++++++++++++++++++- .../java/io/realm/SyncManager.java | 5 ++++ .../utils/RemoteIntegrationTestService.java | 1 + 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index 272ead6c89..29e7faef4a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -19,6 +19,9 @@ import android.content.Context; import android.content.pm.PackageInfo; +import java.io.File; +import java.io.IOException; + import io.realm.internal.Keep; /** @@ -41,7 +44,25 @@ public static void init(Context context) { // init the "sync_manager.cpp" metadata Realm, this is also needed later, when re try // to schedule a client reset. in realm-java#master this is already done, when initialising // the RealmFileUserStore (not available now on releases) - SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath()); + if (SyncManager.Debug.separatedDirForSyncManager) { + try { + // Files.createTempDirectory is not available on JDK 6. + File dir = File.createTempFile("remote_sync_", "_" + android.os.Process.myPid(), + context.getFilesDir()); + if (!dir.delete()) { + throw new IllegalStateException(String.format("Temp file '%s' cannot be deleted.", dir.getPath())); + } + if (!dir.mkdir()) { + throw new IllegalStateException(String.format("Directory '%s' for SyncManager cannot be created. ", + dir.getPath())); + } + SyncManager.nativeInitializeSyncManager(dir.getPath()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } else { + SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath()); + } // Configure default UserStore UserStore userStore = new RealmFileUserStore(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 63ef781fca..8e070e1dbd 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -56,6 +56,11 @@ public static class Debug { */ public static boolean skipOnlineChecking = false; + /** + * Set this to true to init a SyncManager with a directory named by the process ID. This is useful for + * integration tests which are emulating multiple sync client by using multiple processes. + */ + public static boolean separatedDirForSyncManager = false; } /** diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/RemoteIntegrationTestService.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/RemoteIntegrationTestService.java index 872b1089ec..d80d79abba 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/RemoteIntegrationTestService.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/RemoteIntegrationTestService.java @@ -24,5 +24,6 @@ public class RemoteIntegrationTestService extends RemoteTestService { public RemoteIntegrationTestService() { super(); SyncManager.Debug.skipOnlineChecking = true; + SyncManager.Debug.separatedDirForSyncManager = true; } } From 33a394a1bc2b098d2ca748d05c00242e195ea390 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 23 Jun 2017 18:51:50 +0900 Subject: [PATCH 0783/2110] re-enable RealmTests#unicodeStrings() (#4832) This PR speeds up RealmTests#unicodeStrings() and I think that we can execute it every CI build. On my Galaxy S8, original code took about 6 sec, now it takes 800 msec. --- .../androidTest/java/io/realm/RealmTests.java | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index a7e3847eae..d6ca5add85 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -835,36 +835,32 @@ private List getCharacterArray() { // The test writes and reads random Strings. @Test - @Ignore("This test is slow. Move it to another testsuite that runs once a day on Jenkins") public void unicodeStrings() { - List chars_array = getCharacterArray(); + List charsArray = getCharacterArray(); // Change seed value for new random values. long seed = 20; Random random = new Random(seed); - - String test_char = ""; - String test_char_old = ""; - - int random_value; + StringBuilder testChar = new StringBuilder(); + realm.beginTransaction(); for (int i = 0; i < 1000; i++) { - random_value = random.nextInt(25); + testChar.setLength(0); + int length = random.nextInt(25); - for (int j = 0; j < random_value; j++) { - test_char = test_char_old + chars_array.get(random.nextInt(27261)); - test_char_old = test_char; + for (int j = 0; j < length; j++) { + testChar.append(charsArray.get(random.nextInt(27261))); } - realm.beginTransaction(); StringOnly stringOnly = realm.createObject(StringOnly.class); - stringOnly.setChars(test_char); - realm.commitTransaction(); + // tests setter + stringOnly.setChars(testChar.toString()); + + // tests getter realm.where(StringOnly.class).findFirst().getChars(); - realm.beginTransaction(); realm.delete(StringOnly.class); - realm.commitTransaction(); } + realm.cancelTransaction(); } @Test From ea02c9489cfc22bfc38ddd9f48ed3cff4a22cd53 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 23 Jun 2017 22:47:20 +0900 Subject: [PATCH 0784/2110] Removed unused methods from internal Table class (#4812) --- CHANGELOG.md | 15 + .../processor/RealmProxyClassGenerator.java | 6 +- .../io/realm/AllTypesRealmProxy.java | 14 +- .../io/realm/BooleansRealmProxy.java | 14 +- .../io/realm/NullTypesRealmProxy.java | 14 +- .../resources/io/realm/SimpleRealmProxy.java | 14 +- .../androidTest/java/io/realm/TestHelper.java | 195 ++++++++- .../io/realm/internal/CollectionTests.java | 12 +- .../io/realm/internal/JNIColumnInfoTest.java | 26 +- .../java/io/realm/internal/JNIQueryTest.java | 179 ++++++--- .../java/io/realm/internal/JNIRowTest.java | 65 ++- .../io/realm/internal/JNISortedLongTest.java | 59 ++- .../io/realm/internal/JNITableInsertTest.java | 85 ++-- .../java/io/realm/internal/JNITableTest.java | 369 ++++++++++-------- .../java/io/realm/internal/PivotTest.java | 75 ---- .../io/realm/internal/PrimaryKeyTests.java | 14 +- .../internal/TableIndexAndDistinctTest.java | 100 +++-- .../main/cpp/io_realm_internal_OsObject.cpp | 2 +- .../src/main/cpp/io_realm_internal_Table.cpp | 62 --- .../src/main/java/io/realm/DynamicRealm.java | 4 +- .../src/main/java/io/realm/Realm.java | 4 +- .../main/java/io/realm/internal/OsObject.java | 16 +- .../main/java/io/realm/internal/Table.java | 172 +------- 23 files changed, 818 insertions(+), 698 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c782cfdd6..3fdb9c6338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 3.4.1 (YYYY-MM-DD) + +### Breaking Changes + +### Deprecated + +### Enhancements + +### Bug Fixes + +### Internal + +* Removed `Table#Table()`, `Table#addEmptyRow()`, `Table#addEmptyRows()`, `Table#add(Object...)`, `Table#pivot(long,long,PivotType)` and `Table#createnative()`. + + ## 3.4.0 (2017-06-22) ### Breaking Changes diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index e2eaedd57d..bc16c86a53 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -1493,10 +1493,10 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP writer.beginControlFlow("if (rowIndex == Table.NO_MATCH)"); if (Utils.isString(metadata.getPrimaryKey())) { writer.emitStatement( - "rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue)"); + "rowIndex = OsObject.createRowWithPrimaryKey(table, primaryKeyValue)"); } else { writer.emitStatement( - "rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, ((%s) object).%s())", + "rowIndex = OsObject.createRowWithPrimaryKey(table, ((%s) object).%s())", interfaceName, primaryKeyGetter); } @@ -1508,7 +1508,7 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP writer.endControlFlow(); writer.emitStatement("cache.put(object, rowIndex)"); } else { - writer.emitStatement("long rowIndex = OsObject.createRow(realm.sharedRealm, table)"); + writer.emitStatement("long rowIndex = OsObject.createRow(table)"); writer.emitStatement("cache.put(object, rowIndex)"); } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 2840c67b83..7e51275132 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -30,7 +30,7 @@ @SuppressWarnings("all") public class AllTypesRealmProxy extends some.test.AllTypes - implements RealmObjectProxy, AllTypesRealmProxyInterface { + implements RealmObjectProxy, AllTypesRealmProxyInterface { static final class AllTypesColumnInfo extends ColumnInfo { long columnStringIndex; @@ -578,7 +578,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = new ArrayList(2); some.test.AllTypes obj = null; if (update) { @@ -690,7 +690,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { boolean jsonHasPrimaryKey = false; some.test.AllTypes obj = new some.test.AllTypes(); reader.beginObject(); @@ -895,7 +895,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue); + rowIndex = OsObject.createRowWithPrimaryKey(table, primaryKeyValue); } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } @@ -1017,7 +1017,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(realm.sharedRealm, table, primaryKeyValue); + rowIndex = OsObject.createRowWithPrimaryKey(table, primaryKeyValue); } cache.put(object, rowIndex); Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index b1d9ba35ec..04b7de7941 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -29,7 +29,7 @@ @SuppressWarnings("all") public class BooleansRealmProxy extends some.test.Booleans - implements RealmObjectProxy, BooleansRealmProxyInterface { + implements RealmObjectProxy, BooleansRealmProxyInterface { static final class BooleansColumnInfo extends ColumnInfo { long doneIndex; @@ -273,7 +273,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = Collections. emptyList(); some.test.Booleans obj = realm.createObjectInternal(some.test.Booleans.class, true, excludeFields); if (json.has("done")) { @@ -310,7 +310,7 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { some.test.Booleans obj = new some.test.Booleans(); reader.beginObject(); while (reader.hasNext()) { @@ -396,7 +396,7 @@ public static long insert(Realm realm, some.test.Booleans object, Map objects, M cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); + long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$done(), false); Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$isReady(), false); @@ -435,7 +435,7 @@ public static long insertOrUpdate(Realm realm, some.test.Booleans object, Map ob cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); + long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$done(), false); Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$isReady(), false); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 41ebb879e5..c90fd7dfcc 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -29,7 +29,7 @@ @SuppressWarnings("all") public class NullTypesRealmProxy extends some.test.NullTypes - implements RealmObjectProxy, NullTypesRealmProxyInterface { + implements RealmObjectProxy, NullTypesRealmProxyInterface { static final class NullTypesColumnInfo extends ColumnInfo { long fieldStringNotNullIndex; @@ -1085,7 +1085,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = new ArrayList(1); if (json.has("fieldObjectNull")) { excludeFields.add("fieldObjectNull"); @@ -1255,7 +1255,7 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { some.test.NullTypes obj = new some.test.NullTypes(); reader.beginObject(); while (reader.hasNext()) { @@ -1499,7 +1499,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map objects, M cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); + long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { @@ -1708,7 +1708,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map ob cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); + long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 543b16baee..f8ff4c3b6f 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -29,7 +29,7 @@ @SuppressWarnings("all") public class SimpleRealmProxy extends some.test.Simple - implements RealmObjectProxy, SimpleRealmProxyInterface { + implements RealmObjectProxy, SimpleRealmProxyInterface { static final class SimpleColumnInfo extends ColumnInfo { long nameIndex; @@ -209,7 +209,7 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { + throws JSONException { final List excludeFields = Collections. emptyList(); some.test.Simple obj = realm.createObjectInternal(some.test.Simple.class, true, excludeFields); if (json.has("name")) { @@ -232,7 +232,7 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { some.test.Simple obj = new some.test.Simple(); reader.beginObject(); while (reader.hasNext()) { @@ -302,7 +302,7 @@ public static long insert(Realm realm, some.test.Simple object, Map objects, M cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); + long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); String realmGet$name = ((SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { @@ -343,7 +343,7 @@ public static long insertOrUpdate(Realm realm, some.test.Simple object, Map ob cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - long rowIndex = OsObject.createRow(realm.sharedRealm, table); + long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); String realmGet$name = ((SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index b92522931e..469de59c00 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -33,6 +33,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -53,8 +54,9 @@ import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; -import io.realm.entities.StringOnly; import io.realm.internal.Collection; +import io.realm.internal.OsObject; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.LogLevel; @@ -106,22 +108,185 @@ public static RealmFieldType getColumnType(Object o) { } /** - * Creates an empty table with 1 column of all our supported column types, currently 9 columns. + * Appends the specified row to the end of the table. For internal testing usage only. * - * @return + * @param table the table where the object to be added. + * @param values values. + * @return the row index of the appended row. + * @deprecated Remove this functions since it doesn't seem to be useful. And this function does deal with tables + * with primary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row + * added. */ - public static Table getTableWithAllColumnTypes() { - Table t = new Table(); - - t.addColumn(RealmFieldType.BINARY, "binary"); - t.addColumn(RealmFieldType.BOOLEAN, "boolean"); - t.addColumn(RealmFieldType.DATE, "date"); - t.addColumn(RealmFieldType.DOUBLE, "double"); - t.addColumn(RealmFieldType.FLOAT, "float"); - t.addColumn(RealmFieldType.INTEGER, "long"); - t.addColumn(RealmFieldType.STRING, "string"); - - return t; + public static long addRowWithValues(Table table, Object... values) { + long rowIndex = OsObject.createRow(table); + + // Checks values types. + int columns = (int) table.getColumnCount(); + if (columns != values.length) { + throw new IllegalArgumentException("The number of value parameters (" + + String.valueOf(values.length) + + ") does not match the number of columns in the table (" + + String.valueOf(columns) + ")."); + } + RealmFieldType[] colTypes = new RealmFieldType[columns]; + for (int columnIndex = 0; columnIndex < columns; columnIndex++) { + Object value = values[columnIndex]; + RealmFieldType colType = table.getColumnType(columnIndex); + colTypes[columnIndex] = colType; + if (!colType.isValid(value)) { + // String representation of the provided value type. + String providedType; + if (value == null) { + providedType = "null"; + } else { + providedType = value.getClass().toString(); + } + + throw new IllegalArgumentException("Invalid argument no " + String.valueOf(1 + columnIndex) + + ". Expected a value compatible with column type " + colType + ", but got " + providedType + "."); + } + } + + // Inserts values. + for (long columnIndex = 0; columnIndex < columns; columnIndex++) { + Object value = values[(int) columnIndex]; + switch (colTypes[(int) columnIndex]) { + case BOOLEAN: + if (value == null) { + table.setNull(columnIndex, rowIndex, false); + } else { + table.setBoolean(columnIndex, rowIndex, (Boolean) value, false); + } + break; + case INTEGER: + if (value == null) { + table.setNull(columnIndex, rowIndex, false); + } else { + long longValue = ((Number) value).longValue(); + table.setLong(columnIndex, rowIndex, longValue, false); + } + break; + case FLOAT: + if (value == null) { + table.setNull(columnIndex, rowIndex, false); + } else { + table.setFloat(columnIndex, rowIndex, (Float) value, false); + } + break; + case DOUBLE: + if (value == null) { + table.setNull(columnIndex, rowIndex, false); + } else { + table.setDouble(columnIndex, rowIndex, (Double) value, false); + } + break; + case STRING: + if (value == null) { + table.setNull(columnIndex, rowIndex, false); + } else { + table.setString(columnIndex, rowIndex, (String) value, false); + } + break; + case DATE: + if (value == null) { + table.setNull(columnIndex, rowIndex, false); + } else { + table.setDate(columnIndex, rowIndex, (Date) value, false); + } + break; + case BINARY: + if (value == null) { + table.setNull(columnIndex, rowIndex, false); + } else { + table.setBinaryByteArray(columnIndex, rowIndex, (byte[]) value, false); + } + break; + case UNSUPPORTED_MIXED: + case UNSUPPORTED_TABLE: + default: + throw new RuntimeException("Unexpected columnType: " + String.valueOf(colTypes[(int) columnIndex])); + } + } + return rowIndex; + } + + /** + * Creates an empty table whose name is "temp" with 1 column of all our supported column types, currently 7 columns. + * + * @param sharedRealm A {@link SharedRealm} where the table is created. + * @return created table. + */ + public static Table createTableWithAllColumnTypes(SharedRealm sharedRealm) { + return createTableWithAllColumnTypes(sharedRealm, "temp"); + } + + /** + * Creates an empty table with 1 column of all our supported column types, currently 7 columns. + * + * @param sharedRealm A {@link SharedRealm} where the table is created. + * @param name name of the table. + * @return created table. + */ + @SuppressWarnings("WeakerAccess") + public static Table createTableWithAllColumnTypes(SharedRealm sharedRealm, + @SuppressWarnings("SameParameterValue") String name) { + boolean wasInTransaction = sharedRealm.isInTransaction(); + if (!wasInTransaction) { + sharedRealm.beginTransaction(); + } + try { + Table t = sharedRealm.createTable(name); + + t.addColumn(RealmFieldType.BINARY, "binary"); + t.addColumn(RealmFieldType.BOOLEAN, "boolean"); + t.addColumn(RealmFieldType.DATE, "date"); + t.addColumn(RealmFieldType.DOUBLE, "double"); + t.addColumn(RealmFieldType.FLOAT, "float"); + t.addColumn(RealmFieldType.INTEGER, "long"); + t.addColumn(RealmFieldType.STRING, "string"); + + return t; + } catch (RuntimeException e) { + if (!wasInTransaction) { + sharedRealm.cancelTransaction(); + } + throw e; + } finally { + if (!wasInTransaction && sharedRealm.isInTransaction()) { + sharedRealm.commitTransaction(); + } + } + } + + public static Table createTable(SharedRealm sharedRealm, String name) { + return createTable(sharedRealm, name, null); + } + + public interface AdditionalTableSetup { + void execute(Table table); + } + + public static Table createTable(SharedRealm sharedRealm, String name, AdditionalTableSetup additionalSetup) { + boolean wasInTransaction = sharedRealm.isInTransaction(); + if (!wasInTransaction) { + sharedRealm.beginTransaction(); + } + try { + Table table = sharedRealm.createTable(name); + if (additionalSetup != null) { + additionalSetup.execute(table); + } + return table; + } catch (RuntimeException e) { + if (!wasInTransaction) { + sharedRealm.cancelTransaction(); + } + throw e; + } finally { + if (!wasInTransaction && sharedRealm.isInTransaction()) { + sharedRealm.commitTransaction(); + } + } } public static String streamToString(InputStream in) throws IOException { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 99e06fed58..673bff18d0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -87,22 +87,22 @@ private void populateData() { table.addColumn(RealmFieldType.INTEGER, "age"); // Add data to the table - long row = table.addEmptyRow(); + long row = OsObject.createRow(table); table.setString(0, row, "John", false); table.setString(1, row, "Lee", false); table.setLong(2, row, 4, false); - row = table.addEmptyRow(); + row = OsObject.createRow(table); table.setString(0, row, "John", false); table.setString(1, row, "Anderson", false); table.setLong(2, row, 3, false); - row = table.addEmptyRow(); + row = OsObject.createRow(table); table.setString(0, row, "Erik", false); table.setString(1, row, "Lee", false); table.setLong(2, row, 1, false); - row = table.addEmptyRow(); + row = OsObject.createRow(table); table.setString(0, row, "Henry", false); table.setString(1, row, "Anderson", false); table.setLong(2, row, 1, false); @@ -126,7 +126,7 @@ public void run() { private void addRow(SharedRealm sharedRealm) { sharedRealm.beginTransaction(); table = sharedRealm.getTable("test_table"); - table.addEmptyRow(); + OsObject.createRow(table); sharedRealm.commitTransaction(); } @@ -310,7 +310,7 @@ public void onChange(Collection element) { } }); sharedRealm.beginTransaction(); - table.addEmptyRow(); + OsObject.createRow(table); sharedRealm.commitTransaction(); sharedRealm.refresh(); TestHelper.awaitOrFail(latch); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java index 1eaeee2ba7..74e66ff861 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java @@ -20,11 +20,15 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.RealmFieldType; +import io.realm.TestHelper; +import io.realm.rule.TestRealmConfigurationFactory; import static junit.framework.TestCase.assertEquals; @@ -32,14 +36,28 @@ @RunWith(AndroidJUnit4.class) public class JNIColumnInfoTest { - Table table; + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @SuppressWarnings("FieldCanBeLocal") + private RealmConfiguration config; + @SuppressWarnings("FieldCanBeLocal") + private SharedRealm sharedRealm; + private Table table; @Before public void setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); - table = new Table(); - table.addColumn(RealmFieldType.STRING, "firstName"); - table.addColumn(RealmFieldType.STRING, "lastName"); + config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); + + table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.STRING, "firstName"); + table.addColumn(RealmFieldType.STRING, "lastName"); + } + }); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index d77bf67ecd..6fa6a32ea9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -19,7 +19,9 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,8 +30,10 @@ import io.realm.Case; import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.RealmFieldType; import io.realm.TestHelper; +import io.realm.rule.TestRealmConfigurationFactory; import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.fail; @@ -38,6 +42,12 @@ @RunWith(AndroidJUnit4.class) public class JNIQueryTest { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @SuppressWarnings("FieldCanBeLocal") + private RealmConfiguration config; + private SharedRealm sharedRealm; private Table table; private final long[] oneNullTable = new long[]{NativeObject.NULLPTR}; @@ -45,19 +55,33 @@ public class JNIQueryTest { @Before public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); + config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); + } + + @After + public void tearDown() { + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); + } } private void init() { - table = new Table(); - table.addColumn(RealmFieldType.INTEGER, "number"); - table.addColumn(RealmFieldType.STRING, "name"); - - table.add(10, "A"); - table.add(11, "B"); - table.add(12, "C"); - table.add(13, "B"); - table.add(14, "D"); - table.add(16, "D"); + table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.INTEGER, "number"); + table.addColumn(RealmFieldType.STRING, "name"); + + TestHelper.addRowWithValues(table, 10, "A"); + TestHelper.addRowWithValues(table, 11, "B"); + TestHelper.addRowWithValues(table, 12, "C"); + TestHelper.addRowWithValues(table, 13, "B"); + TestHelper.addRowWithValues(table, 14, "D"); + TestHelper.addRowWithValues(table, 16, "D"); + } + }); + assertEquals(6, table.size()); } @@ -112,7 +136,7 @@ public void nonCompleteQuery() { @Test public void invalidColumnIndexEqualTo() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); TableQuery query = table.where(); // Boolean @@ -159,7 +183,7 @@ public void invalidColumnIndexEqualTo() { @Test public void invalidColumnIndexNotEqualTo() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); TableQuery query = table.where(); @@ -202,7 +226,7 @@ public void invalidColumnIndexNotEqualTo() { @Test public void invalidColumnIndexGreaterThan() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); TableQuery query = table.where(); // Date @@ -229,7 +253,7 @@ public void invalidColumnIndexGreaterThan() { @Test public void invalidColumnIndexGreaterThanOrEqual() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); TableQuery query = table.where(); // Date @@ -256,7 +280,7 @@ public void invalidColumnIndexGreaterThanOrEqual() { @Test public void invalidColumnIndexLessThan() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); TableQuery query = table.where(); // Date @@ -283,7 +307,7 @@ public void invalidColumnIndexLessThan() { @Test public void invalidColumnIndexLessThanOrEqual() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); TableQuery query = table.where(); // Date @@ -310,7 +334,7 @@ public void invalidColumnIndexLessThanOrEqual() { @Test public void invalidColumnIndexBetween() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); TableQuery query = table.where(); // Date @@ -337,7 +361,7 @@ public void invalidColumnIndexBetween() { @Test public void invalidColumnIndexContains() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); TableQuery query = table.where(); // String @@ -359,9 +383,13 @@ public void invalidColumnIndexContains() { @SuppressWarnings("ConstantConditions") @Test public void nullInputQuery() { - Table t = new Table(); - t.addColumn(RealmFieldType.DATE, "dateCol"); - t.addColumn(RealmFieldType.STRING, "stringCol"); + Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table t) { + t.addColumn(RealmFieldType.DATE, "dateCol"); + t.addColumn(RealmFieldType.STRING, "stringCol"); + } + }); Date nullDate = null; try { t.where().equalTo(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } @@ -392,19 +420,22 @@ public void nullInputQuery() { @Test public void shouldFind() { // Creates a table. - Table table = new Table(); - - table.addColumn(RealmFieldType.STRING, "username"); - table.addColumn(RealmFieldType.INTEGER, "score"); - table.addColumn(RealmFieldType.BOOLEAN, "completed"); - - // Inserts some values. - table.add("Arnold", 420, false); // 0 - table.add("Jane", 770, false); // 1 * - table.add("Erik", 600, false); // 2 - table.add("Henry", 601, false); // 3 * - table.add("Bill", 564, true); // 4 - table.add("Janet", 875, false); // 5 * + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.STRING, "username"); + table.addColumn(RealmFieldType.INTEGER, "score"); + table.addColumn(RealmFieldType.BOOLEAN, "completed"); + + // Inserts some values. + TestHelper.addRowWithValues(table, "Arnold", 420, false); // 0 + TestHelper.addRowWithValues(table, "Jane", 770, false); // 1 * + TestHelper.addRowWithValues(table, "Erik", 600, false); // 2 + TestHelper.addRowWithValues(table, "Henry", 601, false); // 3 * + TestHelper.addRowWithValues(table, "Bill", 564, true); // 4 + TestHelper.addRowWithValues(table, "Janet", 875, false); // 5 * + } + }); TableQuery query = table.where().greaterThan(new long[]{1}, oneNullTable, 600); @@ -434,9 +465,11 @@ public void shouldFind() { @Test public void queryTestForNoMatches() { - Table t = TestHelper.getTableWithAllColumnTypes(); + Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); - t.add(new byte[]{1,2,3}, true, new Date(1384423149761L), 4.5d, 5.7f, 100, "string"); + sharedRealm.beginTransaction(); + TestHelper.addRowWithValues(t, new byte[]{1,2,3}, true, new Date(1384423149761L), 4.5d, 5.7f, 100, "string"); + sharedRealm.commitTransaction(); TableQuery q = t.where().greaterThan(new long[]{5}, oneNullTable, 1000); // No matches @@ -447,7 +480,7 @@ public void queryTestForNoMatches() { @Test public void queryWithWrongDataType() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); // Queries the table. TableQuery query = table.where(); @@ -528,7 +561,7 @@ public void queryWithWrongDataType() { @Test public void columnIndexOutOfBounds() { - Table table = TestHelper.getTableWithAllColumnTypes(); + Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); // Queries the table. TableQuery query = table.where(); @@ -630,12 +663,16 @@ public void columnIndexOutOfBounds() { @Test public void maximumDate() { - Table table = new Table(); - table.addColumn(RealmFieldType.DATE, "date"); + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.DATE, "date"); - table.add(new Date(0)); - table.add(new Date(10000)); - table.add(new Date(1000)); + TestHelper.addRowWithValues(table, new Date(0)); + TestHelper.addRowWithValues(table, new Date(10000)); + TestHelper.addRowWithValues(table, new Date(1000)); + } + }); assertEquals(new Date(10000), table.where().maximumDate(0)); } @@ -643,12 +680,16 @@ public void maximumDate() { @Test public void minimumDate() { - Table table = new Table(); - table.addColumn(RealmFieldType.DATE, "date"); + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.DATE, "date"); - table.add(new Date(10000)); - table.add(new Date(0)); - table.add(new Date(1000)); + TestHelper.addRowWithValues(table, new Date(10000)); + TestHelper.addRowWithValues(table, new Date(0)); + TestHelper.addRowWithValues(table, new Date(1000)); + } + }); assertEquals(new Date(0), table.where().minimumDate(0)); } @@ -656,21 +697,25 @@ public void minimumDate() { @Test public void dateQuery() throws Exception { - Table table = new Table(); - table.addColumn(RealmFieldType.DATE, "date"); - final Date past = new Date(TimeUnit.SECONDS.toMillis(Integer.MIN_VALUE - 100L)); final Date future = new Date(TimeUnit.SECONDS.toMillis(Integer.MAX_VALUE + 1L)); final Date distantPast = new Date(Long.MIN_VALUE); final Date distantFuture = new Date(Long.MAX_VALUE); - table.add(new Date(10000)); - table.add(new Date(0)); - table.add(new Date(1000)); - table.add(future); - table.add(distantFuture); - table.add(past); - table.add(distantPast); + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.DATE, "date"); + + TestHelper.addRowWithValues(table, new Date(10000)); + TestHelper.addRowWithValues(table, new Date(0)); + TestHelper.addRowWithValues(table, new Date(1000)); + TestHelper.addRowWithValues(table, future); + TestHelper.addRowWithValues(table, distantFuture); + TestHelper.addRowWithValues(table, past); + TestHelper.addRowWithValues(table, distantPast); + } + }); assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, distantPast).count()); assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, distantPast).count()); @@ -755,18 +800,22 @@ public void dateQuery() throws Exception { @Test public void byteArrayQuery() throws Exception { - Table table = new Table(); - table.addColumn(RealmFieldType.BINARY, "binary"); - final byte[] binary1 = new byte[] {0x01, 0x02, 0x03, 0x04}; final byte[] binary2 = new byte[] {0x05, 0x02, 0x03, 0x08}; final byte[] binary3 = new byte[] {0x09, 0x0a, 0x0b, 0x04}; final byte[] binary4 = new byte[] {0x05, 0x0a, 0x0b, 0x10}; - table.add((Object) binary1); - table.add((Object) binary2); - table.add((Object) binary3); - table.add((Object) binary4); + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.BINARY, "binary"); + + TestHelper.addRowWithValues(table, (Object) binary1); + TestHelper.addRowWithValues(table, (Object) binary2); + TestHelper.addRowWithValues(table, (Object) binary3); + TestHelper.addRowWithValues(table, (Object) binary4); + } + }); // Equal to diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java index 12258cb24e..93bf8332ce 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java @@ -16,15 +16,23 @@ package io.realm.internal; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.MoreAsserts; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Date; +import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.RealmFieldType; +import io.realm.TestHelper; +import io.realm.rule.TestRealmConfigurationFactory; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; @@ -35,24 +43,51 @@ @RunWith(AndroidJUnit4.class) public class JNIRowTest { - @Test - public void nonNullValues() { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - Table table = new Table(); + @SuppressWarnings("FieldCanBeLocal") + private RealmConfiguration config; + private SharedRealm sharedRealm; - table.addColumn(RealmFieldType.STRING, "string"); - table.addColumn(RealmFieldType.INTEGER, "integer"); - table.addColumn(RealmFieldType.FLOAT, "float"); - table.addColumn(RealmFieldType.DOUBLE, "double"); - table.addColumn(RealmFieldType.BOOLEAN, "boolean"); - table.addColumn(RealmFieldType.DATE, "date"); - table.addColumn(RealmFieldType.BINARY, "binary"); + @Before + public void setUp() throws Exception { + Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); + config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); + sharedRealm.beginTransaction(); + } - byte[] data = new byte[2]; + @After + public void tearDown() { + if (sharedRealm != null && sharedRealm.isInTransaction()) { + sharedRealm.cancelTransaction(); + } - table.add("abc", 3, (float) 1.2, 1.3, true, new Date(0), data); + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); + } + } + @Test + public void nonNullValues() { + final byte[] data = new byte[2]; + + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.STRING, "string"); + table.addColumn(RealmFieldType.INTEGER, "integer"); + table.addColumn(RealmFieldType.FLOAT, "float"); + table.addColumn(RealmFieldType.DOUBLE, "double"); + table.addColumn(RealmFieldType.BOOLEAN, "boolean"); + table.addColumn(RealmFieldType.DATE, "date"); + table.addColumn(RealmFieldType.BINARY, "binary"); + + TestHelper.addRowWithValues(table, "abc", 3, (float) 1.2, 1.3, true, new Date(0), data); + } + }); UncheckedRow row = table.getUncheckedRow(0); @@ -64,7 +99,6 @@ public void nonNullValues() { assertEquals(new Date(0), row.getDate(5)); MoreAsserts.assertEquals(data, row.getBinaryByteArray(6)); - row.setString(0, "a"); row.setLong(1, 1); row.setFloat(2, (float) 8.8); @@ -87,8 +121,7 @@ public void nonNullValues() { @Test public void nullValues() { - Table table = new Table(); - + Table table = TestHelper.createTable(sharedRealm, "temp"); long colStringIndex = table.addColumn(RealmFieldType.STRING, "string", true); long colIntIndex = table.addColumn(RealmFieldType.INTEGER, "integer", true); table.addColumn(RealmFieldType.FLOAT, "float"); @@ -96,8 +129,8 @@ public void nullValues() { long colBoolIndex = table.addColumn(RealmFieldType.BOOLEAN, "boolean", true); table.addColumn(RealmFieldType.DATE, "date"); table.addColumn(RealmFieldType.BINARY, "binary"); + long rowIndex = OsObject.createRow(table); - long rowIndex = table.addEmptyRow(); UncheckedRow row = table.getUncheckedRow(rowIndex); row.setString(colStringIndex, "test"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java index 4145a51749..fdfaa4cdf9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java @@ -19,33 +19,64 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.RealmFieldType; +import io.realm.TestHelper; +import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; @RunWith(AndroidJUnit4.class) public class JNISortedLongTest { - Table table; - void init() { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @SuppressWarnings("FieldCanBeLocal") + private RealmConfiguration config; + private SharedRealm sharedRealm; + private Table table; + + @Before + public void setUp() throws Exception { + Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); + config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); + } + + @After + public void tearDown() { + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); + } + } + + private void init() { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); - table = new Table(); - table.addColumn(RealmFieldType.INTEGER, "number"); - table.addColumn(RealmFieldType.STRING, "name"); - - table.add(1, "A"); - table.add(10, "B"); - table.add(20, "C"); - table.add(30, "B"); - table.add(40, "D"); - table.add(50, "D"); - table.add(60, "D"); - table.add(60, "D"); + table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.INTEGER, "number"); + table.addColumn(RealmFieldType.STRING, "name"); + + TestHelper.addRowWithValues(table, 1, "A"); + TestHelper.addRowWithValues(table, 10, "B"); + TestHelper.addRowWithValues(table, 20, "C"); + TestHelper.addRowWithValues(table, 30, "B"); + TestHelper.addRowWithValues(table, 40, "D"); + TestHelper.addRowWithValues(table, 50, "D"); + TestHelper.addRowWithValues(table, 60, "D"); + TestHelper.addRowWithValues(table, 60, "D"); + } + }); assertEquals(8, table.size()); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java index 32fce953ba..c98458c169 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java @@ -16,6 +16,11 @@ package io.realm.internal; +import android.support.test.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -26,8 +31,10 @@ import java.util.Date; import java.util.List; -import io.realm.RealmFieldType; +import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.TestHelper; +import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -36,8 +43,29 @@ @RunWith(Parameterized.class) public class JNITableInsertTest { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @SuppressWarnings("FieldCanBeLocal") + private RealmConfiguration config; + private SharedRealm sharedRealm; + private List value = new ArrayList<>(); + @Before + public void setUp() throws Exception { + Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); + config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); + } + + @After + public void tearDown() { + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); + } + } + @Parameterized.Parameters public static Collection parameters() { List value = new ArrayList<>(); @@ -58,46 +86,31 @@ public JNITableInsertTest(List value) { this.value = value; } - @Test - public void testShouldThrowExceptionWhenColumnNameIsTooLong() { - - Table table = new Table(); - try { - table.addColumn(RealmFieldType.STRING, "THIS STRING HAS 64 CHARACTERS, " - + "LONGER THAN THE MAX 63 CHARACTERS"); - fail("Too long name"); - } catch (IllegalArgumentException ignore) { - } - } - - @Test - public void testWhenColumnNameIsExactly63CharLong() { - - Table table = new Table(); - table.addColumn(RealmFieldType.STRING, "THIS STRING HAS 63 CHARACTERS PERFECT FOR THE MAX 63 CHARACTERS"); - } - @Test public void testGenericAddOnTable() { for (int i = 0; i < value.size(); i++) { for (int j = 0; j < value.size(); j++) { - - Table t = new Table(); - - // If the objects matches no exception will be thrown. - if (value.get(i).getClass().equals(value.get(j).getClass())) { - assertTrue(true); - - } else { - // Adds column. - t.addColumn(TestHelper.getColumnType(value.get(j)), value.get(j).getClass().getSimpleName()); - // Adds value. - try { - t.add(value.get(i)); - fail("No matching type"); - } catch (IllegalArgumentException ignored) { + final Object valueI = value.get(i); + final Object valueJ = value.get(j); + + TestHelper.createTable(sharedRealm, "temp" + i + "_" + j, new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table t) { + // If the objects matches no exception will be thrown. + if (valueI.getClass().equals(valueJ.getClass())) { + assertTrue(true); + } else { + // Adds column. + t.addColumn(TestHelper.getColumnType(valueJ), valueJ.getClass().getSimpleName()); + // Adds value. + try { + TestHelper.addRowWithValues(t, valueI); + fail("No matching type"); + } catch (IllegalArgumentException ignored) { + } + } } - } + }); } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index 296f22c764..fd893254ba 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Locale; +import java.util.concurrent.atomic.AtomicLong; import io.realm.Realm; import io.realm.RealmConfiguration; @@ -49,31 +50,39 @@ public class JNITableTest { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - private Table t; + @SuppressWarnings("FieldCanBeLocal") + private RealmConfiguration config; + private SharedRealm sharedRealm; @Before public void setUp() { - t = new Table(); + config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); } @Test public void tableToString() { - Table t = new Table(); - - t.addColumn(RealmFieldType.STRING, "stringCol"); - t.addColumn(RealmFieldType.INTEGER, "intCol"); - t.addColumn(RealmFieldType.BOOLEAN, "boolCol"); - - t.add("s1", 1, true); - t.add("s2", 2, false); + Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table t) { + t.addColumn(RealmFieldType.STRING, "stringCol"); + t.addColumn(RealmFieldType.INTEGER, "intCol"); + t.addColumn(RealmFieldType.BOOLEAN, "boolCol"); + + TestHelper.addRowWithValues(t, "s1", 1, true); + TestHelper.addRowWithValues(t, "s2", 2, false); + } + }); - String expected = "The Table contains 3 columns: stringCol, intCol, boolCol. And 2 rows."; + String expected = "The Table temp contains 3 columns: stringCol, intCol, boolCol. And 2 rows."; assertEquals(expected, t.toString()); } @Test public void rowOperationsOnZeroRow() { - Table t = new Table(); + Table t = TestHelper.createTable(sharedRealm, "temp"); + + sharedRealm.beginTransaction(); // Removes rows without columns. try { t.moveLastOver(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} try { t.moveLastOver(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} @@ -82,27 +91,14 @@ public void rowOperationsOnZeroRow() { t.addColumn(RealmFieldType.STRING, ""); try { t.moveLastOver(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} try { t.moveLastOver(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} + sharedRealm.commitTransaction(); } @Test public void zeroColOperations() { - Table tableZeroCols = new Table(); - - // Adds rows. - try { - tableZeroCols.add("val"); - fail("No columns in table"); - } catch (IndexOutOfBoundsException ignored) {} - try { - tableZeroCols.addEmptyRow(); - fail("No columns in table"); - } catch (IndexOutOfBoundsException ignored) {} - try { - tableZeroCols.addEmptyRows(10); - fail("No columns in table"); - } catch (IndexOutOfBoundsException ignored) {} - + Table tableZeroCols = TestHelper.createTable(sharedRealm, "temp"); + sharedRealm.beginTransaction(); // Col operations try { tableZeroCols.removeColumn(0); @@ -120,12 +116,15 @@ public void zeroColOperations() { tableZeroCols.renameColumn(10, "newName"); fail("No columns in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} + sharedRealm.commitTransaction(); } @Test public void findFirstNonExisting() { - Table t = TestHelper.getTableWithAllColumnTypes(); - t.add(new byte[] {1, 2, 3}, true, new Date(1384423149761L), 4.5D, 5.7F, 100, "string"); + Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); + sharedRealm.beginTransaction(); + TestHelper.addRowWithValues(t, new byte[] {1, 2, 3}, true, new Date(1384423149761L), 4.5D, 5.7F, 100, "string"); + sharedRealm.commitTransaction(); assertEquals(-1, t.findFirstBoolean(1, false)); assertEquals(-1, t.findFirstDate(2, new Date(138442314986L))); @@ -137,11 +136,13 @@ public void findFirstNonExisting() { @Test public void findFirst() { final int TEST_SIZE = 10; - Table t = TestHelper.getTableWithAllColumnTypes(); + Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); + sharedRealm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { - t.add(new byte[] {1, 2, 3}, true, new Date(i), (double) i, (float) i, i, "string " + i); + TestHelper.addRowWithValues(t, new byte[] {1, 2, 3}, true, new Date(i), (double) i, (float) i, i, "string " + i); } - t.add(new byte[] {1, 2, 3}, true, new Date(TEST_SIZE), (double) TEST_SIZE, (float) TEST_SIZE, TEST_SIZE, ""); + TestHelper.addRowWithValues(t, new byte[] {1, 2, 3}, true, new Date(TEST_SIZE), (double) TEST_SIZE, (float) TEST_SIZE, TEST_SIZE, ""); + sharedRealm.commitTransaction(); assertEquals(0, t.findFirstBoolean(1, true)); for (int i = 0; i < TEST_SIZE; i++) { @@ -164,8 +165,12 @@ public void findFirst() { @Test public void getValuesFromNonExistingColumn() { - Table t = TestHelper.getTableWithAllColumnTypes(); - t.addEmptyRows(10); + Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); + sharedRealm.beginTransaction(); + for (int i = 0; i < 10; i++) { + OsObject.createRow(t); + } + sharedRealm.commitTransaction(); try { t.getBinaryByteArray(-1, 0); @@ -261,8 +266,12 @@ public void getValuesFromNonExistingColumn() { @Test public void getNonExistingColumn() { - Table t = new Table(); - t.addColumn(RealmFieldType.INTEGER, "int"); + Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table t) { + t.addColumn(RealmFieldType.INTEGER, "int"); + } + }); assertEquals(-1, t.getColumnIndex("non-existing column")); try { @@ -273,12 +282,17 @@ public void getNonExistingColumn() { @Test public void setNulls() { - Table t = new Table(); - t.addColumn(RealmFieldType.STRING, ""); - t.addColumn(RealmFieldType.DATE, ""); - t.addColumn(RealmFieldType.BINARY, ""); - t.add("String val", new Date(), new byte[] {1, 2, 3}); + Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table t) { + t.addColumn(RealmFieldType.STRING, ""); + t.addColumn(RealmFieldType.DATE, ""); + t.addColumn(RealmFieldType.BINARY, ""); + TestHelper.addRowWithValues(t, "String val", new Date(), new byte[] {1, 2, 3}); + } + }); + sharedRealm.beginTransaction(); try { t.setString(0, 0, null, false); fail("null string not allowed"); @@ -287,17 +301,7 @@ public void setNulls() { t.setDate(1, 0, null, false); fail("null Date not allowed"); } catch (IllegalArgumentException ignored) { } - } - - @Test - public void addNegativeEmptyRows() { - Table t = new Table(); - t.addColumn(RealmFieldType.STRING, "colName"); - - try { - t.addEmptyRows(-1); - fail("Argument is negative"); - } catch (IllegalArgumentException ignored) { } + sharedRealm.commitTransaction(); } @Test @@ -324,6 +328,7 @@ public void getName() { @Test public void shouldThrowWhenSetIndexOnWrongRealmFieldType() { + Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); for (long colIndex = 0; colIndex < t.getColumnCount(); colIndex++) { // All types supported addSearchIndex and removeSearchIndex. @@ -334,6 +339,7 @@ public void shouldThrowWhenSetIndexOnWrongRealmFieldType() { t.getColumnType(colIndex) != RealmFieldType.DATE); // Tries to addSearchIndex(). + sharedRealm.beginTransaction(); try { t.addSearchIndex(colIndex); if (exceptionExpected) { @@ -341,8 +347,10 @@ public void shouldThrowWhenSetIndexOnWrongRealmFieldType() { } } catch (IllegalArgumentException ignored) { } + sharedRealm.commitTransaction(); // Tries to removeSearchIndex(). + sharedRealm.beginTransaction(); try { // Currently core will do nothing if the column doesn't have a search index. t.removeSearchIndex(colIndex); @@ -351,7 +359,7 @@ public void shouldThrowWhenSetIndexOnWrongRealmFieldType() { } } catch (IllegalArgumentException ignored) { } - + sharedRealm.commitTransaction(); // Tries to hasSearchIndex() for all columnTypes. t.hasSearchIndex(colIndex); @@ -360,31 +368,39 @@ public void shouldThrowWhenSetIndexOnWrongRealmFieldType() { @Test public void columnName() { - Table t = new Table(); - try { - t.addColumn(RealmFieldType.STRING, "I am 64 characters.............................................."); - fail("Only 63 characters supported"); - } catch (IllegalArgumentException ignored) { } - t.addColumn(RealmFieldType.STRING, "I am 63 characters............................................."); + TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table t) { + try { + t.addColumn(RealmFieldType.STRING, "I am 64 characters.............................................."); + fail("Only 63 characters supported"); + } catch (IllegalArgumentException ignored) { } + t.addColumn(RealmFieldType.STRING, "I am 63 characters............................................."); + } + }); } @Test public void tableNumbers() { - Table t = new Table(); - t.addColumn(RealmFieldType.INTEGER, "intCol"); - t.addColumn(RealmFieldType.DOUBLE, "doubleCol"); - t.addColumn(RealmFieldType.FLOAT, "floatCol"); - t.addColumn(RealmFieldType.STRING, "StringCol"); - - // Adds 3 rows of data with same values in each column. - t.add(1, 2.0D, 3.0F, "s1"); - t.add(1, 2.0D, 3.0F, "s1"); - t.add(1, 2.0D, 3.0F, "s1"); - - // Adds other values. - t.add(10, 20.0D, 30.0F, "s10"); - t.add(100, 200.0D, 300.0F, "s100"); - t.add(1000, 2000.0D, 3000.0F, "s1000"); + Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table t) { + t.addColumn(RealmFieldType.INTEGER, "intCol"); + t.addColumn(RealmFieldType.DOUBLE, "doubleCol"); + t.addColumn(RealmFieldType.FLOAT, "floatCol"); + t.addColumn(RealmFieldType.STRING, "StringCol"); + + // Adds 3 rows of data with same values in each column. + TestHelper.addRowWithValues(t, 1, 2.0D, 3.0F, "s1"); + TestHelper.addRowWithValues(t, 1, 2.0D, 3.0F, "s1"); + TestHelper.addRowWithValues(t, 1, 2.0D, 3.0F, "s1"); + + // Adds other values. + TestHelper.addRowWithValues(t, 10, 20.0D, 30.0F, "s10"); + TestHelper.addRowWithValues(t, 100, 200.0D, 300.0F, "s100"); + TestHelper.addRowWithValues(t, 1000, 2000.0D, 3000.0F, "s1000"); + } + }); // Counts instances of values added in the first 3 rows. assertEquals(3, t.count(0, 1)); @@ -397,8 +413,10 @@ public void tableNumbers() { assertEquals(4, t.findFirstFloat(2, 300.0F)); // Find rows index for first float value of 300.0 in column 2. // Sets double and float. + sharedRealm.beginTransaction(); t.setDouble(1, 2, -2.0D, false); t.setFloat(2, 2, -3.0F, false); + sharedRealm.commitTransaction(); // Gets double tests. assertEquals(-2.0D, t.getDouble(1, 2)); @@ -418,56 +436,68 @@ public void tableNumbers() { public void convertToNullable() { RealmFieldType[] columnTypes = {RealmFieldType.BOOLEAN, RealmFieldType.DATE, RealmFieldType.DOUBLE, RealmFieldType.FLOAT, RealmFieldType.INTEGER, RealmFieldType.BINARY, RealmFieldType.STRING}; - for (RealmFieldType columnType : columnTypes) { + int tableIndex = 0; + for (final RealmFieldType columnType : columnTypes) { // Tests various combinations of column names and nullability. String[] columnNames = {"foobar", "__TMP__0"}; - for (boolean nullable : new boolean[] {Table.NOT_NULLABLE, Table.NULLABLE}) { - for (String columnName : columnNames) { - Table table = new Table(); - long colIndex = table.addColumn(columnType, columnName, nullable); - table.addColumn(RealmFieldType.BOOLEAN, "bool"); - table.addEmptyRow(); - if (columnType == RealmFieldType.BOOLEAN) { - table.setBoolean(colIndex, 0, true, false); - } else if (columnType == RealmFieldType.DATE) { - table.setDate(colIndex, 0, new Date(0), false); - } else if (columnType == RealmFieldType.DOUBLE) { - table.setDouble(colIndex, 0, 1.0, false); - } else if (columnType == RealmFieldType.FLOAT) { - table.setFloat(colIndex, 0, 1.0F, false); - } else if (columnType == RealmFieldType.INTEGER) { - table.setLong(colIndex, 0, 1, false); - } else if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 0, new byte[] {0}, false); - } else if (columnType == RealmFieldType.STRING) { - table.setString(colIndex, 0, "Foo", false); - } - try { - table.addEmptyRow(); - if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 1, null, false); - } else if (columnType == RealmFieldType.STRING) { - table.setString(colIndex, 1, null, false); - } else { - table.getCheckedRow(1).setNull(colIndex); + for (final boolean nullable : new boolean[] {Table.NOT_NULLABLE, Table.NULLABLE}) { + for (final String columnName : columnNames) { + final AtomicLong colIndexRef = new AtomicLong(); + Table table = TestHelper.createTable(sharedRealm, "temp" + tableIndex, new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + long colIndex = table.addColumn(columnType, columnName, nullable); + colIndexRef.set(colIndex); + table.addColumn(RealmFieldType.BOOLEAN, "bool"); + OsObject.createRow(table); + if (columnType == RealmFieldType.BOOLEAN) { + table.setBoolean(colIndex, 0, true, false); + } else if (columnType == RealmFieldType.DATE) { + table.setDate(colIndex, 0, new Date(0), false); + } else if (columnType == RealmFieldType.DOUBLE) { + table.setDouble(colIndex, 0, 1.0, false); + } else if (columnType == RealmFieldType.FLOAT) { + table.setFloat(colIndex, 0, 1.0F, false); + } else if (columnType == RealmFieldType.INTEGER) { + table.setLong(colIndex, 0, 1, false); + } else if (columnType == RealmFieldType.BINARY) { + table.setBinaryByteArray(colIndex, 0, new byte[] {0}, false); + } else if (columnType == RealmFieldType.STRING) { + table.setString(colIndex, 0, "Foo", false); + } + try { + OsObject.createRow(table); + if (columnType == RealmFieldType.BINARY) { + table.setBinaryByteArray(colIndex, 1, null, false); + } else if (columnType == RealmFieldType.STRING) { + table.setString(colIndex, 1, null, false); + } else { + table.getCheckedRow(1).setNull(colIndex); + } + + if (!nullable) { + fail(); + } + } catch (IllegalArgumentException ignored) { + } + table.moveLastOver(table.size() - 1); } - - if (!nullable) { - fail(); - } - } catch (IllegalArgumentException ignored) { - } - table.moveLastOver(table.size() - 1); + }); assertEquals(1, table.size()); + long colIndex = colIndexRef.get(); + + sharedRealm.beginTransaction(); table.convertColumnToNullable(colIndex); + sharedRealm.commitTransaction(); assertTrue(table.isColumnNullable(colIndex)); assertEquals(1, table.size()); assertEquals(2, table.getColumnCount()); assertTrue(table.getColumnIndex(columnName) >= 0); assertEquals(colIndex, table.getColumnIndex(columnName)); - table.addEmptyRow(); + sharedRealm.beginTransaction(); + OsObject.createRow(table); if (columnType == RealmFieldType.BINARY) { table.setBinaryByteArray(colIndex, 0, null, false); } else if (columnType == RealmFieldType.STRING) { @@ -475,6 +505,7 @@ public void convertToNullable() { } else { table.getCheckedRow(0).setNull(colIndex); } + sharedRealm.commitTransaction(); assertEquals(2, table.size()); @@ -485,6 +516,7 @@ public void convertToNullable() { } else { assertTrue(table.getUncheckedRow(1).isNull(colIndex)); } + tableIndex++; } } } @@ -494,53 +526,65 @@ public void convertToNullable() { public void convertToNotNullable() { RealmFieldType[] columnTypes = {RealmFieldType.BOOLEAN, RealmFieldType.DATE, RealmFieldType.DOUBLE, RealmFieldType.FLOAT, RealmFieldType.INTEGER, RealmFieldType.BINARY, RealmFieldType.STRING}; - for (RealmFieldType columnType : columnTypes) { + int tableIndex = 0; + for (final RealmFieldType columnType : columnTypes) { // Tests various combinations of column names and nullability. String[] columnNames = {"foobar", "__TMP__0"}; - for (boolean nullable : new boolean[] {Table.NOT_NULLABLE, Table.NULLABLE}) { - for (String columnName : columnNames) { - Table table = new Table(); - long colIndex = table.addColumn(columnType, columnName, nullable); - table.addColumn(RealmFieldType.BOOLEAN, "bool"); - table.addEmptyRow(); - if (columnType == RealmFieldType.BOOLEAN) { - table.setBoolean(colIndex, 0, true, false); - } else if (columnType == RealmFieldType.DATE) { - table.setDate(colIndex, 0, new Date(1), false); - } else if (columnType == RealmFieldType.DOUBLE) { - table.setDouble(colIndex, 0, 1.0, false); - } else if (columnType == RealmFieldType.FLOAT) { - table.setFloat(colIndex, 0, 1.0F, false); - } else if (columnType == RealmFieldType.INTEGER) { - table.setLong(colIndex, 0, 1, false); - } else if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 0, new byte[] {0}, false); - } else if (columnType == RealmFieldType.STRING) { table.setString(colIndex, 0, "Foo", false); } - try { - table.addEmptyRow(); - if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 1, null, false); - } else if (columnType == RealmFieldType.STRING) { - table.setString(colIndex, 1, null, false); - } else { - table.getCheckedRow(1).setNull(colIndex); - } - - if (!nullable) { - fail(); + for (final boolean nullable : new boolean[] {Table.NOT_NULLABLE, Table.NULLABLE}) { + for (final String columnName : columnNames) { + final AtomicLong colIndexRef = new AtomicLong(); + Table table = TestHelper.createTable(sharedRealm, "temp" + tableIndex, new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + long colIndex = table.addColumn(columnType, columnName, nullable); + colIndexRef.set(colIndex); + table.addColumn(RealmFieldType.BOOLEAN, "bool"); + OsObject.createRow(table); + if (columnType == RealmFieldType.BOOLEAN) { + table.setBoolean(colIndex, 0, true, false); + } else if (columnType == RealmFieldType.DATE) { + table.setDate(colIndex, 0, new Date(1), false); + } else if (columnType == RealmFieldType.DOUBLE) { + table.setDouble(colIndex, 0, 1.0, false); + } else if (columnType == RealmFieldType.FLOAT) { + table.setFloat(colIndex, 0, 1.0F, false); + } else if (columnType == RealmFieldType.INTEGER) { + table.setLong(colIndex, 0, 1, false); + } else if (columnType == RealmFieldType.BINARY) { + table.setBinaryByteArray(colIndex, 0, new byte[] {0}, false); + } else if (columnType == RealmFieldType.STRING) { table.setString(colIndex, 0, "Foo", false); } + try { + OsObject.createRow(table); + if (columnType == RealmFieldType.BINARY) { + table.setBinaryByteArray(colIndex, 1, null, false); + } else if (columnType == RealmFieldType.STRING) { + table.setString(colIndex, 1, null, false); + } else { + table.getCheckedRow(1).setNull(colIndex); + } + + if (!nullable) { + fail(); + } + } catch (IllegalArgumentException ignored) { + } } - } catch (IllegalArgumentException ignored) { - } + }); assertEquals(2, table.size()); + long colIndex = colIndexRef.get(); + + sharedRealm.beginTransaction(); table.convertColumnToNotNullable(colIndex); + sharedRealm.commitTransaction(); assertFalse(table.isColumnNullable(colIndex)); assertEquals(2, table.size()); assertEquals(2, table.getColumnCount()); assertTrue(table.getColumnIndex(columnName) >= 0); assertEquals(colIndex, table.getColumnIndex(columnName)); - table.addEmptyRow(); + sharedRealm.beginTransaction(); + OsObject.createRow(table); try { if (columnType == RealmFieldType.BINARY) { table.setBinaryByteArray(colIndex, 0, null, false); @@ -555,6 +599,8 @@ public void convertToNotNullable() { } catch (IllegalArgumentException ignored) { } table.moveLastOver(table.size() -1); + sharedRealm.commitTransaction(); + assertEquals(2, table.size()); if (columnType == RealmFieldType.BINARY) { @@ -576,6 +622,7 @@ public void convertToNotNullable() { assertEquals(0, table.getLong(colIndex, 1)); } } + tableIndex++; } } } @@ -584,9 +631,13 @@ public void convertToNotNullable() { // Adds column and read back if it is nullable or not. @Test public void isNullable() { - Table table = new Table(); - table.addColumn(RealmFieldType.STRING, "string1", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.STRING, "string2", Table.NULLABLE); + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.STRING, "string1", Table.NOT_NULLABLE); + table.addColumn(RealmFieldType.STRING, "string2", Table.NULLABLE); + } + }); assertFalse(table.isColumnNullable(0)); assertTrue(table.isColumnNullable(1)); @@ -594,8 +645,6 @@ public void isNullable() { @Test public void defaultValue_setAndGet() { - // t is not used in this test. - t = null; final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); //noinspection TryFinallyCanBeTryWithResources try { @@ -626,7 +675,7 @@ public void defaultValue_setAndGet() { } sharedRealm.beginTransaction(); - table.addEmptyRow(); + OsObject.createRow(table); ListIterator> it = columnInfoList.listIterator(); for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { @@ -717,8 +766,6 @@ public void defaultValue_setAndGet() { @Test public void defaultValue_setMultipleTimes() { - // t is not used in this test. - t = null; final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); //noinspection TryFinallyCanBeTryWithResources try { @@ -749,8 +796,8 @@ public void defaultValue_setMultipleTimes() { } sharedRealm.beginTransaction(); - table.addEmptyRow(); - table.addEmptyRow(); // For link field update. + OsObject.createRow(table); + OsObject.createRow(table); // For link field update. ListIterator> it = columnInfoList.listIterator(); for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { @@ -849,8 +896,6 @@ public void defaultValue_setMultipleTimes() { @Test public void defaultValue_overwrittenByNonDefault() { - // t is not used in this test. - t = null; final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); //noinspection TryFinallyCanBeTryWithResources try { @@ -881,8 +926,8 @@ public void defaultValue_overwrittenByNonDefault() { } sharedRealm.beginTransaction(); - table.addEmptyRow(); - table.addEmptyRow(); // For link field update. + OsObject.createRow(table); + OsObject.createRow(table); // For link field update. // Sets as default. ListIterator> it = columnInfoList.listIterator(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java deleted file mode 100644 index 574fcb43c3..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PivotTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.realm.Realm; -import io.realm.RealmFieldType; -import io.realm.internal.Table.PivotType; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - - -@RunWith(AndroidJUnit4.class) -public class PivotTest { - - Table t; - long colIndexSex; - long colIndexAge; - long colIndexHired; - - @Before - public void setUp() { - Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); - t = new Table(); - colIndexSex = t.addColumn(RealmFieldType.STRING, "sex"); - colIndexAge = t.addColumn(RealmFieldType.INTEGER, "age"); - colIndexHired = t.addColumn(RealmFieldType.BOOLEAN, "hired"); - - for (long i=0;i<50000;i++){ - String sex = i % 2 == 0 ? "Male" : "Female"; - t.add(sex, 20 + (i%20), true); - } - } - - @Test - public void pivotTable(){ - - Table resultCount = t.pivot(colIndexSex, colIndexAge, PivotType.COUNT); - assertEquals(2, resultCount.size()); - assertEquals(25000, resultCount.getLong(1, 0)); - assertEquals(25000, resultCount.getLong(1, 1)); - - Table resultMin = t.pivot(colIndexSex, colIndexAge, PivotType.MIN); - assertEquals(20, resultMin.getLong(1, 0)); - assertEquals(21, resultMin.getLong(1, 1)); - - Table resultMax = t.pivot(colIndexSex, colIndexAge, PivotType.MAX); - assertEquals(38, resultMax.getLong(1, 0)); - assertEquals(39, resultMax.getLong(1, 1)); - - try { t.pivot(colIndexHired, colIndexAge, PivotType.SUM); fail("Group by not a String column"); } catch (UnsupportedOperationException ignore) { } - try { t.pivot(colIndexSex, colIndexHired, PivotType.SUM); fail("Aggregation not an int column"); } catch (UnsupportedOperationException ignore) { } - } -} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index e9b8e4bd87..f6f9970675 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -96,9 +96,9 @@ public void removingPrimaryKeyRemovesConstraint_typeSetters() { tbl.setPrimaryKey("name"); // Creates first entry with name "Foo". - tbl.setString(0, tbl.addEmptyRow(), "Foo", false); + tbl.setString(0, OsObject.createRow(tbl), "Foo", false); - long rowIndex = tbl.addEmptyRow(); + long rowIndex = OsObject.createRow(tbl); try { tbl.setString(0, rowIndex, "Foo", false); // Tries to create 2nd entry with name Foo. } catch (RealmPrimaryKeyConstraintException e1) { @@ -119,7 +119,7 @@ public void removingPrimaryKeyRemovesConstraint_typeSetters() { public void addEmptyRowWithPrimaryKeyWrongTypeStringThrows() { Table t = getTableWithStringPrimaryKey(); try { - OsObject.createWithPrimaryKey(sharedRealm, t, 42); + OsObject.createWithPrimaryKey(t, 42); fail(); } catch (IllegalArgumentException ignored) { } @@ -129,7 +129,7 @@ public void addEmptyRowWithPrimaryKeyWrongTypeStringThrows() { @Test public void addEmptyRowWithPrimaryKeyNullString() { Table t = getTableWithStringPrimaryKey(); - OsObject.createWithPrimaryKey(sharedRealm, t, null); + OsObject.createWithPrimaryKey(t, null); assertEquals(1, t.size()); sharedRealm.cancelTransaction(); } @@ -138,7 +138,7 @@ public void addEmptyRowWithPrimaryKeyNullString() { public void addEmptyRowWithPrimaryKeyWrongTypeIntegerThrows() { Table t = getTableWithIntegerPrimaryKey(); try { - OsObject.createWithPrimaryKey(sharedRealm, t, "Foo"); + OsObject.createWithPrimaryKey(t, "Foo"); fail(); } catch (IllegalArgumentException ignored) { } @@ -148,7 +148,7 @@ public void addEmptyRowWithPrimaryKeyWrongTypeIntegerThrows() { @Test public void addEmptyRowWithPrimaryKeyString() { Table t = getTableWithStringPrimaryKey(); - UncheckedRow row = OsObject.createWithPrimaryKey(sharedRealm, t, "Foo"); + UncheckedRow row = OsObject.createWithPrimaryKey(t, "Foo"); assertEquals(1, t.size()); assertEquals("Foo", row.getString(0)); sharedRealm.cancelTransaction(); @@ -157,7 +157,7 @@ public void addEmptyRowWithPrimaryKeyString() { @Test public void addEmptyRowWithPrimaryKeyLong() { Table t = getTableWithIntegerPrimaryKey(); - UncheckedRow row = OsObject.createWithPrimaryKey(sharedRealm, t, 42); + UncheckedRow row = OsObject.createWithPrimaryKey(t, 42); assertEquals(1, t.size()); assertEquals(42L, row.getLong(0)); sharedRealm.cancelTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java index fe4fb16267..3efa42c683 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java @@ -16,33 +16,73 @@ package io.realm.internal; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.RealmFieldType; +import io.realm.TestHelper; +import io.realm.rule.TestRealmConfigurationFactory; + import static org.junit.Assert.assertEquals; @RunWith(AndroidJUnit4.class) public class TableIndexAndDistinctTest { - Table table; - - void init() { - table = new Table(); - table.addColumn(RealmFieldType.INTEGER, "number"); - table.addColumn(RealmFieldType.STRING, "name"); - - long i = 0; - table.add(0, "A"); - table.add(1, "B"); - table.add(2, "C"); - table.add(3, "B"); - table.add(4, "D"); - table.add(5, "D"); - table.add(6, "D"); + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @SuppressWarnings("FieldCanBeLocal") + private RealmConfiguration config; + private SharedRealm sharedRealm; + private Table table; + + @Before + public void setUp() throws Exception { + Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); + config = configFactory.createConfiguration(); + sharedRealm = SharedRealm.getInstance(config); + + sharedRealm.beginTransaction(); + } + + @After + public void tearDown() { + if (sharedRealm != null && sharedRealm.isInTransaction()) { + sharedRealm.cancelTransaction(); + } + + if (sharedRealm != null && !sharedRealm.isClosed()) { + sharedRealm.close(); + } + } + + private void init() { + table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + table.addColumn(RealmFieldType.INTEGER, "number"); + table.addColumn(RealmFieldType.STRING, "name"); + + TestHelper.addRowWithValues(table, 0, "A"); + TestHelper.addRowWithValues(table, 1, "B"); + TestHelper.addRowWithValues(table, 2, "C"); + TestHelper.addRowWithValues(table, 3, "B"); + TestHelper.addRowWithValues(table, 4, "D"); + TestHelper.addRowWithValues(table, 5, "D"); + TestHelper.addRowWithValues(table, 6, "D"); + } + }); + assertEquals(7, table.size()); } @@ -54,36 +94,40 @@ void init() { public void shouldTestSettingIndexOnMultipleColumns() { // Creates a table only with String type columns - Table t = new Table(); - t.addColumn(RealmFieldType.STRING, "col1"); - t.addColumn(RealmFieldType.STRING, "col2"); - t.addColumn(RealmFieldType.STRING, "col3"); - t.addColumn(RealmFieldType.STRING, "col4"); - t.addColumn(RealmFieldType.STRING, "col5"); - t.add("row1", "row2", "row3", "row4", "row5"); - t.add("row1", "row2", "row3", "row4", "row5"); - t.add("row1", "row2", "row3", "row4", "row5"); - t.add("row1", "row2", "row3", "row4", "row5"); - t.add("row1", "row2", "row3", "row4", "row5"); + Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table t) { + t.addColumn(RealmFieldType.STRING, "col1"); + t.addColumn(RealmFieldType.STRING, "col2"); + t.addColumn(RealmFieldType.STRING, "col3"); + t.addColumn(RealmFieldType.STRING, "col4"); + t.addColumn(RealmFieldType.STRING, "col5"); + TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); + TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); + TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); + TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); + TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); + } + }); for (long c=0;c(shared_realm_ptr)); auto& table = *(reinterpret_cast(table_ptr)); + shared_realm->verify_in_write(); // throws JStringAccessor str_accessor(env, pk_value); // throws - shared_realm->verify_in_write(); // throws if (!pk_value && !TBL_AND_COL_NULLABLE(env, &table, pk_column_ndx)) { return realm::npos; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index c99f0ba198..c2afd189ce 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -101,40 +101,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumnLink(JNIEnv* } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativePivot(JNIEnv* env, jobject, jlong dataTablePtr, - jlong stringCol, jlong intCol, jint operation, - jlong resultTablePtr) -{ - Table* dataTable = TBL(dataTablePtr); - Table* resultTable = TBL(resultTablePtr); - Table::AggrType pivotOp; - switch (operation) { - case 0: - pivotOp = Table::aggr_count; - break; - case 1: - pivotOp = Table::aggr_sum; - break; - case 2: - pivotOp = Table::aggr_avg; - break; - case 3: - pivotOp = Table::aggr_min; - break; - case 4: - pivotOp = Table::aggr_max; - break; - default: - ThrowException(env, UnsupportedOperation, "No pivot operation specified."); - return; - } - - try { - dataTable->aggregate(S(stringCol), S(intCol), pivotOp, *resultTable); - } - CATCH_STD() -} - JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveColumn(JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex) { @@ -528,24 +494,6 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_Table_nativeGetColumnType(JNIEnv* // ---------------- Row handling -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddEmptyRow(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong rows) -{ - Table* pTable = TBL(nativeTablePtr); - if (!TABLE_VALID(env, pTable)) { - return 0; - } - if (pTable->get_column_count() < 1) { - ThrowException(env, IndexOutOfBounds, concat_stringdata("Table has no columns: ", pTable->get_name())); - return 0; - } - try { - return static_cast(pTable->add_empty_row(S(rows))); - } - CATCH_STD() - return 0; -} - JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMoveLastOver(JNIEnv* env, jobject, jlong nativeTablePtr, jlong rowIndex) { @@ -1304,16 +1252,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid(JNIEnv*, j return to_jbool(TBL(nativeTablePtr)->is_attached()); // noexcept } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_createNative(JNIEnv* env, jobject) -{ - TR_ENTER() - try { - return reinterpret_cast(LangBindHelper::new_table()); - } - CATCH_STD() - return 0; -} - // Checks if the primary key column contains any duplicate values, making it ineligible as a // primary key. static bool check_valid_primary_key_column(JNIEnv* env, Table* table, StringData column_name) // throws diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index f9b3451d58..27dc64e272 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -109,7 +109,7 @@ public DynamicRealmObject createObject(String className) { " 'createObject(String, Object)' instead.", className)); } - return new DynamicRealmObject(this, CheckedRow.getFromRow(OsObject.create(sharedRealm, table))); + return new DynamicRealmObject(this, CheckedRow.getFromRow(OsObject.create(table))); } /** @@ -126,7 +126,7 @@ public DynamicRealmObject createObject(String className) { public DynamicRealmObject createObject(String className, Object primaryKeyValue) { Table table = schema.getTable(className); return new DynamicRealmObject(this, - CheckedRow.getFromRow(OsObject.createWithPrimaryKey(sharedRealm, table, primaryKeyValue))); + CheckedRow.getFromRow(OsObject.createWithPrimaryKey(table, primaryKeyValue))); } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index ffd129701f..babed1e66d 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1002,7 +1002,7 @@ E createObjectInternal( " 'createObject(Class, Object)' instead.", table.getClassName())); } return configuration.getSchemaMediator().newInstance(clazz, this, - OsObject.create(sharedRealm, table), + OsObject.create(table), schema.getColumnInfo(clazz), acceptDefaultValue, excludeFields); } @@ -1049,7 +1049,7 @@ E createObjectInternal( Table table = schema.getTable(clazz); return configuration.getSchemaMediator().newInstance(clazz, this, - OsObject.createWithPrimaryKey(sharedRealm, table, primaryKeyValue), + OsObject.createWithPrimaryKey(table, primaryKeyValue), schema.getColumnInfo(clazz), acceptDefaultValue, excludeFields); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index bcfcaaa48f..56a46768d1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -148,9 +148,11 @@ public void setObserverPairs(ObserverPairList pairs) { /** * Create an object in the given table which doesn't have a primary key column defined. * + * @param table the table where the object is created. This table must be atached to {@link SharedRealm}. * @return a newly created {@code UncheckedRow}. */ - public static UncheckedRow create(SharedRealm sharedRealm, Table table) { + public static UncheckedRow create(Table table) { + final SharedRealm sharedRealm = table.getSharedRealm(); return new UncheckedRow(sharedRealm.context, table, nativeCreateNewObject(sharedRealm.getNativePtr(), table.getNativePtr())); } @@ -159,9 +161,11 @@ public static UncheckedRow create(SharedRealm sharedRealm, Table table) { * Create a row in the given table which doesn't have a primary key column defined. * This is used for the fast bulk insertion. * + * @param table the table where the object is created. * @return a newly created row's index. */ - public static long createRow(SharedRealm sharedRealm, Table table) { + public static long createRow(Table table) { + final SharedRealm sharedRealm = table.getSharedRealm(); return nativeCreateRow(sharedRealm.getNativePtr(), table.getNativePtr()); } @@ -178,11 +182,13 @@ private static long getAndVerifyPrimaryKeyColumnIndex(Table table) { * Create an object in the given table which has a primary key column defined, and set the primary key with given * value. * + * @param table the table where the object is created. This table must be atached to {@link SharedRealm}. * @return a newly created {@code UncheckedRow}. */ - public static UncheckedRow createWithPrimaryKey(SharedRealm sharedRealm, Table table, Object primaryKeyValue) { + public static UncheckedRow createWithPrimaryKey(Table table, Object primaryKeyValue) { long primaryKeyColumnIndex = getAndVerifyPrimaryKeyColumnIndex(table); RealmFieldType type = table.getColumnType(primaryKeyColumnIndex); + final SharedRealm sharedRealm = table.getSharedRealm(); if (type == RealmFieldType.STRING) { if (primaryKeyValue != null && !(primaryKeyValue instanceof String)) { @@ -207,11 +213,13 @@ public static UncheckedRow createWithPrimaryKey(SharedRealm sharedRealm, Table t * value. * This is used for the fast bulk insertion. * + * @param table the table where the object is created. * @return a newly created {@code UncheckedRow}. */ - public static long createRowWithPrimaryKey(SharedRealm sharedRealm, Table table, Object primaryKeyValue) { + public static long createRowWithPrimaryKey(Table table, Object primaryKeyValue) { long primaryKeyColumnIndex = getAndVerifyPrimaryKeyColumnIndex(table); RealmFieldType type = table.getColumnType(primaryKeyColumnIndex); + final SharedRealm sharedRealm = table.getSharedRealm(); if (type == RealmFieldType.STRING) { if (primaryKeyValue != null && !(primaryKeyValue instanceof String)) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 5895024bc9..c50f674eba 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -30,20 +30,6 @@ */ public class Table implements TableSchema, NativeObject { - enum PivotType { - COUNT(0), - SUM(1), - AVG(2), - MIN(3), - MAX(4); - - final int value; // Package protected, accessible from Table - - PivotType(int value) { - this.value = value; - } - } - public static final int TABLE_MAX_LENGTH = 56; // Max length of class names without prefix public static final long INFINITE = -1; public static final boolean NULLABLE = true; @@ -66,23 +52,6 @@ enum PivotType { private final SharedRealm sharedRealm; private long cachedPrimaryKeyColumnIndex = NO_MATCH; - /** - * Constructs a Table base object. It can be used to register columns in this table. Registering into table is - * allowed only for empty tables. It creates a native reference of the object and keeps a reference to it. - */ - public Table() { - this.context = new NativeContext(); - // Native methods work will be initialized here. Generated classes will - // have nothing to do with the native functions. Generated Java Table - // classes will work as a wrapper on top of table. - this.nativePtr = createNative(); - if (nativePtr == 0) { - throw new java.lang.OutOfMemoryError("Out of native memory."); - } - this.sharedRealm = null; - context.addReference(this); - } - Table(Table parent, long nativePointer) { this(parent.sharedRealm, nativePointer); } @@ -351,125 +320,6 @@ public void moveLastOver(long rowIndex) { nativeMoveLastOver(nativePtr, rowIndex); } - /** - * Adds an empty row to the table which doesn't have a primary key defined. - *

            - * NOTE: To add a table with a primary key defined, use {@link #addEmptyRowWithPrimaryKey(Object)} instead. This - * won't check if this table has a primary key. - * - * @return row index. - */ - public long addEmptyRow() { - checkImmutable(); - return nativeAddEmptyRow(nativePtr, 1); - } - - @SuppressWarnings("WeakerAccess") - public long addEmptyRows(long rows) { - checkImmutable(); - if (rows < 1) { - throw new IllegalArgumentException("'rows' must be > 0."); - } - if (hasPrimaryKey()) { - if (rows > 1) { - throw new RealmException("Multiple empty rows cannot be created if a primary key is defined for the table."); - } - return addEmptyRow(); - } - return nativeAddEmptyRow(nativePtr, rows); - } - - /** - * Appends the specified row to the end of the table. For internal testing usage only. - * - * @param values values. - * @return the row index of the appended row. - * @deprecated Remove this functions since it doesn't seem to be useful. And this function does deal with tables - * with primary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row - * added. - */ - protected long add(Object... values) { - long rowIndex = addEmptyRow(); - - checkImmutable(); - - // Checks values types. - int columns = (int) getColumnCount(); - if (columns != values.length) { - throw new IllegalArgumentException("The number of value parameters (" + - String.valueOf(values.length) + - ") does not match the number of columns in the table (" + - String.valueOf(columns) + ")."); - } - RealmFieldType[] colTypes = new RealmFieldType[columns]; - for (int columnIndex = 0; columnIndex < columns; columnIndex++) { - Object value = values[columnIndex]; - RealmFieldType colType = getColumnType(columnIndex); - colTypes[columnIndex] = colType; - if (!colType.isValid(value)) { - // String representation of the provided value type. - String providedType; - if (value == null) { - providedType = "null"; - } else { - providedType = value.getClass().toString(); - } - - throw new IllegalArgumentException("Invalid argument no " + String.valueOf(1 + columnIndex) + - ". Expected a value compatible with column type " + colType + ", but got " + providedType + "."); - } - } - - // Inserts values. - for (long columnIndex = 0; columnIndex < columns; columnIndex++) { - Object value = values[(int) columnIndex]; - switch (colTypes[(int) columnIndex]) { - case BOOLEAN: - nativeSetBoolean(nativePtr, columnIndex, rowIndex, (Boolean) value, false); - break; - case INTEGER: - if (value == null) { - checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); - nativeSetNull(nativePtr, columnIndex, rowIndex, false); - } else { - long intValue = ((Number) value).longValue(); - checkIntValueIsLegal(columnIndex, rowIndex, intValue); - nativeSetLong(nativePtr, columnIndex, rowIndex, intValue, false); - } - break; - case FLOAT: - nativeSetFloat(nativePtr, columnIndex, rowIndex, (Float) value, false); - break; - case DOUBLE: - nativeSetDouble(nativePtr, columnIndex, rowIndex, (Double) value, false); - break; - case STRING: - if (value == null) { - checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); - nativeSetNull(nativePtr, columnIndex, rowIndex, false); - } else { - String stringValue = (String) value; - checkStringValueIsLegal(columnIndex, rowIndex, stringValue); - nativeSetString(nativePtr, columnIndex, rowIndex, (String) value, false); - } - break; - case DATE: - if (value == null) { throw new IllegalArgumentException("Null Date is not allowed."); } - nativeSetTimestamp(nativePtr, columnIndex, rowIndex, ((Date) value).getTime(), false); - break; - case BINARY: - if (value == null) { throw new IllegalArgumentException("Null Array is not allowed"); } - nativeSetByteArray(nativePtr, columnIndex, rowIndex, (byte[]) value, false); - break; - case UNSUPPORTED_MIXED: - case UNSUPPORTED_TABLE: - default: - throw new RuntimeException("Unexpected columnType: " + String.valueOf(colTypes[(int) columnIndex])); - } - } - return rowIndex; - } - private boolean isPrimaryKeyColumn(long columnIndex) { return columnIndex == getPrimaryKey(); } @@ -570,6 +420,10 @@ public static void throwDuplicatePrimaryKeyException(Object value) { // Getters // + SharedRealm getSharedRealm() { + return sharedRealm; + } + public long getLong(long columnIndex, long rowIndex) { return nativeGetLong(nativePtr, columnIndex, rowIndex); } @@ -907,18 +761,6 @@ public long upperBoundLong(long columnIndex, long value) { return nativeUpperBoundInt(nativePtr, columnIndex, value); } - public Table pivot(long stringCol, long intCol, PivotType pivotType) { - if (!this.getColumnType(stringCol).equals(RealmFieldType.STRING)) { - throw new UnsupportedOperationException("Group by column must be of type String"); - } - if (!this.getColumnType(intCol).equals(RealmFieldType.INTEGER)) { - throw new UnsupportedOperationException("Aggregation column must be of type Int"); - } - Table result = new Table(); - nativePivot(nativePtr, stringCol, intCol, pivotType.value, result.nativePtr); - return result; - } - // /** @@ -1026,8 +868,6 @@ public static String getTableNameForClass(String name) { return TABLE_PREFIX + name; } - protected native long createNative(); - private native boolean nativeIsValid(long nativeTablePtr); private native long nativeAddColumn(long nativeTablePtr, int type, String name, boolean isNullable); @@ -1058,8 +898,6 @@ public static String getTableNameForClass(String name) { private native void nativeMoveLastOver(long nativeTablePtr, long rowIndex); - public static native long nativeAddEmptyRow(long nativeTablePtr, long rows); - private native long nativeGetSortedViewMulti(long nativeTableViewPtr, long[] columnIndices, boolean[] ascending); private native long nativeGetLong(long nativeTablePtr, long columnIndex, long rowIndex); @@ -1157,8 +995,6 @@ public static String getTableNameForClass(String name) { private native long nativeUpperBoundInt(long nativePtr, long columnIndex, long value); - private native void nativePivot(long nativeTablePtr, long stringCol, long intCol, int pivotType, long resultPtr); - private native String nativeGetName(long nativeTablePtr); private native String nativeToJson(long nativeTablePtr); From e015fe0413f8bdd9040e42d6c398d35fe176afff Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 23 Jun 2017 16:22:04 +0200 Subject: [PATCH 0785/2110] winston.err did not exist --- tools/sync_test_server/ros-testing-server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index ca7163c46e..9cfdda3a85 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -79,9 +79,9 @@ function stopRealmObjectServer() { syncServerChildProcess = null; exec('rm -r ' + 'realm-object-server', function (err, stdout, stderr) { if (err) { - winston.err(err) + winston.error(err); } else { - winston.info("realm-object-server directory deleted") + winston.info("realm-object-server directory deleted"); } }); } From d6ddda21b539badba1d9f284f8fe746df14655ad Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 26 Jun 2017 17:14:19 +0900 Subject: [PATCH 0786/2110] Simplify code in SyncUser.java (#4846) --- .../realm-library/src/objectServer/java/io/realm/SyncUser.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 242ae883e1..245a287482 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -488,8 +488,7 @@ public String getIdentity() { * @return the user's access token. If this user has logged out or the login has expired {@code null} is returned. */ public Token getAccessToken() { - Token userToken = syncUser.getUserToken(); - return (userToken != null) ? userToken : null; + return syncUser.getUserToken(); } /** From 6b140d7b8f7e13b27167ddc14311522dd463ab62 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 27 Jun 2017 16:49:55 +0900 Subject: [PATCH 0787/2110] Explicitly specify Locale for String.format() instead of implicit current Locale (#4847) * Explicitly specify Locale for String.format() instead of implicit current Locale. * Revert unexpected change * use US locale instead of ENGLISH to follow official documentation https://developer.android.com/reference/java/util/Locale.html#default_locale --- .../src/main/java/io/realm/DynamicRealm.java | 5 ++++- .../java/io/realm/DynamicRealmObject.java | 19 +++++++++++-------- .../io/realm/OrderedRealmCollectionImpl.java | 3 ++- .../realm/OrderedRealmCollectionSnapshot.java | 4 +++- .../src/main/java/io/realm/Realm.java | 7 ++++--- .../java/io/realm/RealmConfiguration.java | 6 ++++-- .../src/main/java/io/realm/RealmList.java | 4 +++- .../main/java/io/realm/RealmMigration.java | 2 +- .../src/main/java/io/realm/RealmQuery.java | 13 +++++++++---- .../io/realm/StandardRealmObjectSchema.java | 7 +++++-- .../realm/exceptions/RealmFileException.java | 4 +++- .../io/realm/internal/SortDescriptor.java | 3 ++- .../fields/CachedFieldDescriptor.java | 5 +++-- .../fields/DynamicFieldDescriptor.java | 3 ++- .../internal/fields/FieldDescriptor.java | 3 ++- .../src/main/java/io/realm/log/RealmLog.java | 4 +++- .../java/io/realm/ObjectServer.java | 7 +++++-- .../java/io/realm/SyncConfiguration.java | 12 ++++++++---- .../java/io/realm/SyncManager.java | 3 ++- .../java/io/realm/SyncSession.java | 4 +++- .../objectServer/java/io/realm/SyncUser.java | 4 +++- .../network/AuthenticateResponse.java | 5 +++-- 22 files changed, 85 insertions(+), 42 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 27dc64e272..f69c1ad399 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -16,6 +16,8 @@ package io.realm; +import java.util.Locale; + import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.internal.CheckedRow; @@ -105,7 +107,8 @@ public DynamicRealmObject createObject(String className) { Table table = schema.getTable(className); // Check and throw the exception earlier for a better exception message. if (table.hasPrimaryKey()) { - throw new RealmException(String.format("'%s' has a primary key, use" + + throw new RealmException(String.format(Locale.US, + "'%s' has a primary key, use" + " 'createObject(String, Object)' instead.", className)); } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index e3b2634a82..edc6f6abf0 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -454,7 +454,8 @@ public void set(String fieldName, Object value) { value = JsonUtils.stringToDate(strValue); break; default: - throw new IllegalArgumentException(String.format("Field %s is not a String field, " + + throw new IllegalArgumentException(String.format(Locale.US, + "Field %s is not a String field, " + "and the provide value could not be automatically converted: %s. Use a typed" + "setter instead", fieldName, value)); } @@ -679,7 +680,8 @@ public void setObject(String fieldName, DynamicRealmObject value) { Table table = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex); Table inputTable = value.proxyState.getRow$realm().getTable(); if (!table.hasSameSchema(inputTable)) { - throw new IllegalArgumentException(String.format("Type of object is wrong. Was %s, expected %s", + throw new IllegalArgumentException(String.format(Locale.US, + "Type of object is wrong. Was %s, expected %s", inputTable.getName(), table.getName())); } proxyState.getRow$realm().setLink(columnIndex, value.proxyState.getRow$realm().getIndex()); @@ -716,7 +718,7 @@ public void setList(String fieldName, RealmList list) { String listType = list.className != null ? list.className : proxyState.getRealm$realm().getSchema().getTable(list.clazz).getClassName(); if (!linkTargetTableName.equals(listType)) { - throw new IllegalArgumentException(String.format(Locale.ENGLISH, + throw new IllegalArgumentException(String.format(Locale.US, "The elements in the list are not the proper type. " + "Was %s expected %s.", listType, linkTargetTableName)); } @@ -732,7 +734,7 @@ public void setList(String fieldName, RealmList list) { throw new IllegalArgumentException("Each element in 'list' must belong to the same Realm instance."); } if (!typeValidated && !linkTargetTable.hasSameSchema(obj.realmGet$proxyState().getRow$realm().getTable())) { - throw new IllegalArgumentException(String.format(Locale.ENGLISH, + throw new IllegalArgumentException(String.format(Locale.US, "Element at index %d is not the proper type. " + "Was '%s' expected '%s'.", i, @@ -803,7 +805,8 @@ private void checkFieldType(String fieldName, long columnIndex, RealmFieldType e if (columnType == RealmFieldType.INTEGER || columnType == RealmFieldType.OBJECT) { columnTypeIndefiniteVowel = "n"; } - throw new IllegalArgumentException(String.format("'%s' is not a%s '%s', but a%s '%s'.", + throw new IllegalArgumentException(String.format(Locale.US, + "'%s' is not a%s '%s', but a%s '%s'.", fieldName, expectedIndefiniteVowel, expectedType, columnTypeIndefiniteVowel, columnType)); } } @@ -910,7 +913,7 @@ public String toString() { break; case LIST: String targetClassName = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName(); - sb.append(String.format("RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getLinkList(columnIndex).size())); + sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getLinkList(columnIndex).size())); break; case UNSUPPORTED_TABLE: case UNSUPPORTED_MIXED: @@ -960,7 +963,7 @@ public RealmResults linkingObjects(String srcClassName, Stri final RealmFieldType fieldType = realmObjectSchema.getFieldType(srcFieldName); // throws IAE if not found if (fieldType != RealmFieldType.OBJECT && fieldType != RealmFieldType.LIST) { - throw new IllegalArgumentException(String.format(Locale.ENGLISH, + throw new IllegalArgumentException(String.format(Locale.US, "Unexpected field type: %1$s. Field type should be either %2$s.%3$s or %2$s.%4$s.", fieldType.name(), RealmFieldType.class.getSimpleName(), @@ -984,7 +987,7 @@ public RealmResults linkingObjects(String srcClassName, Stri private void checkIsPrimaryKey(String fieldName) { RealmObjectSchema objectSchema = proxyState.getRealm$realm().getSchema().getSchemaForClass(getType()); if (objectSchema.hasPrimaryKey() && objectSchema.getPrimaryKey().equals(fieldName)) { - throw new IllegalArgumentException(String.format( + throw new IllegalArgumentException(String.format(Locale.US, "Primary key field '%s' cannot be changed after object was created.", fieldName)); } } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index 194aa13d72..b8725ec8e8 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -5,6 +5,7 @@ import java.util.Date; import java.util.Iterator; import java.util.ListIterator; +import java.util.Locale; import io.realm.internal.Collection; import io.realm.internal.InvalidRow; @@ -246,7 +247,7 @@ private long getColumnIndexForSort(String fieldName) { } long columnIndex = collection.getTable().getColumnIndex(fieldName); if (columnIndex < 0) { - throw new IllegalArgumentException(String.format("Field '%s' does not exist.", fieldName)); + throw new IllegalArgumentException(String.format(Locale.US, "Field '%s' does not exist.", fieldName)); } return columnIndex; } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java index 49124234d0..6a2989647d 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java @@ -16,6 +16,8 @@ package io.realm; +import java.util.Locale; + import io.realm.internal.Collection; import io.realm.internal.UncheckedRow; @@ -128,7 +130,7 @@ public RealmQuery where() { private UnsupportedOperationException getUnsupportedException(String methodName) { return new UnsupportedOperationException( - String.format("'%s()' is not supported by OrderedRealmCollectionSnapshot. " + + String.format(Locale.US, "'%s()' is not supported by OrderedRealmCollectionSnapshot. " + "Call '%s()' on the original 'RealmCollection' instead.", methodName, methodName)); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index babed1e66d..263bfe9311 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -39,6 +39,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Scanner; import java.util.Set; @@ -407,12 +408,12 @@ private static Realm createAndValidateFromCache(RealmCache cache) { realm.doClose(); throw new RealmMigrationNeededException( configuration.getPath(), - String.format("Realm on disk need to migrate from v%s to v%s", currentVersion, requiredVersion)); + String.format(Locale.US, "Realm on disk need to migrate from v%s to v%s", currentVersion, requiredVersion)); } if (requiredVersion < currentVersion) { realm.doClose(); throw new IllegalArgumentException( - String.format("Realm on disk is newer than the one specified: v%s vs. v%s", currentVersion, requiredVersion)); + String.format(Locale.US, "Realm on disk is newer than the one specified: v%s vs. v%s", currentVersion, requiredVersion)); } } @@ -998,7 +999,7 @@ E createObjectInternal( Table table = schema.getTable(clazz); // Checks and throws the exception earlier for a better exception message. if (table.hasPrimaryKey()) { - throw new RealmException(String.format("'%s' has a primary key, use" + + throw new RealmException(String.format(Locale.US, "'%s' has a primary key, use" + " 'createObject(Class, Object)' instead.", table.getClassName())); } return configuration.getSchemaMediator().newInstance(clazz, this, diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 55a3f7c47e..cafcd4a23d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Locale; import java.util.Set; import io.realm.annotations.RealmModule; @@ -322,7 +323,7 @@ protected static RealmProxyMediator createSchemaMediator(Set modules, private static RealmProxyMediator getModuleMediator(String fullyQualifiedModuleClassName) { String[] moduleNameParts = fullyQualifiedModuleClassName.split("\\."); String moduleSimpleName = moduleNameParts[moduleNameParts.length - 1]; - String mediatorName = String.format("io.realm.%s%s", moduleSimpleName, "Mediator"); + String mediatorName = String.format(Locale.US, "io.realm.%s%s", moduleSimpleName, "Mediator"); Class clazz; //noinspection TryWithIdenticalCatches try { @@ -500,7 +501,8 @@ public Builder encryptionKey(byte[] key) { throw new IllegalArgumentException("A non-null key must be provided"); } if (key.length != KEY_LENGTH) { - throw new IllegalArgumentException(String.format("The provided key must be %s bytes. Yours was: %s", + throw new IllegalArgumentException(String.format(Locale.US, + "The provided key must be %s bytes. Yours was: %s", KEY_LENGTH, key.length)); } this.key = Arrays.copyOf(key, key.length); diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 4380fee5ba..12903d09ac 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.Locale; import java.util.NoSuchElementException; import io.realm.internal.InvalidRow; @@ -254,7 +255,8 @@ private E copyToRealmIfNeeded(E object) { return object; } else { // Different target table - throw new IllegalArgumentException(String.format("The object has a different type from list's." + + throw new IllegalArgumentException(String.format(Locale.US, + "The object has a different type from list's." + " Type of the list is '%s', type of object is '%s'.", listClassName, objectClassName)); } } else if (realm.threadId == proxy.realmGet$proxyState().getRealm$realm().threadId) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmMigration.java b/realm/realm-library/src/main/java/io/realm/RealmMigration.java index 5140a3bf6a..44e98f30ca 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmMigration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmMigration.java @@ -42,7 +42,7 @@ * } * * if (oldVersion < newVersion) { - * throw new IllegalStateException(String.format("Migration missing from v%d to v%d", oldVersion, newVersion)); + * throw new IllegalStateException(String.format(Locale.US, "Migration missing from v%d to v%d", oldVersion, newVersion)); * } * } * } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index f0c45e581f..49b4e77fa4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.Date; +import java.util.Locale; import io.realm.annotations.Required; import io.realm.internal.Collection; @@ -1600,7 +1601,8 @@ public Number sum(String fieldName) { case DOUBLE: return query.sumDouble(columnIndex); default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + throw new IllegalArgumentException(String.format(Locale.US, + TYPE_MISMATCH, fieldName, "int, float or double")); } } @@ -1626,7 +1628,8 @@ public double average(String fieldName) { case FLOAT: return query.averageFloat(columnIndex); default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + throw new IllegalArgumentException(String.format(Locale.US, + TYPE_MISMATCH, fieldName, "int, float or double")); } } @@ -1651,7 +1654,8 @@ public Number min(String fieldName) { case DOUBLE: return this.query.minimumDouble(columnIndex); default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + throw new IllegalArgumentException(String.format(Locale.US, + TYPE_MISMATCH, fieldName, "int, float or double")); } } @@ -1692,7 +1696,8 @@ public Number max(String fieldName) { case DOUBLE: return this.query.maximumDouble(columnIndex); default: - throw new IllegalArgumentException(String.format(TYPE_MISMATCH, fieldName, "int, float or double")); + throw new IllegalArgumentException(String.format(Locale.US, + TYPE_MISMATCH, fieldName, "int, float or double")); } } diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java index e09cc80a7d..e22f77351e 100644 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java @@ -20,6 +20,7 @@ import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -176,7 +177,8 @@ public StandardRealmObjectSchema addField(String fieldName, Class fieldType, if (SUPPORTED_LINKED_FIELDS.containsKey(fieldType)) { throw new IllegalArgumentException("Use addRealmObjectField() instead to add fields that link to other RealmObjects: " + fieldName); } else { - throw new IllegalArgumentException(String.format("Realm doesn't support this field type: %s(%s)", + throw new IllegalArgumentException(String.format(Locale.US, + "Realm doesn't support this field type: %s(%s)", fieldName, fieldType)); } } @@ -678,7 +680,8 @@ private long getColumnIndex(String fieldName) { long columnIndex = table.getColumnIndex(fieldName); if (columnIndex == -1) { throw new IllegalArgumentException( - String.format("Field name '%s' does not exist on schema for '%s'", + String.format(Locale.US, + "Field name '%s' does not exist on schema for '%s'", fieldName, getClassName() )); } diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java index bad9719003..07e6b86453 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java @@ -15,6 +15,8 @@ */ package io.realm.exceptions; +import java.util.Locale; + import io.realm.internal.Keep; import io.realm.internal.SharedRealm; @@ -118,6 +120,6 @@ public Kind getKind() { @Override public String toString() { - return String.format("%s Kind: %s.", super.toString(), kind); + return String.format(Locale.US, "%s Kind: %s.", super.toString(), kind); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java index 273d3ef411..3936d36a50 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java @@ -19,6 +19,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Locale; import java.util.Set; import io.realm.RealmFieldType; @@ -103,7 +104,7 @@ static SortDescriptor getTestInstance(Table table, long[] columnIndices) { // could do this in the field descriptor, but this provides a better error message private static void checkFieldType(FieldDescriptor descriptor, Set legalTerminalTypes, String message, String fieldDescriptions) { if (!legalTerminalTypes.contains(descriptor.getFinalColumnType())) { - throw new IllegalArgumentException(String.format( + throw new IllegalArgumentException(String.format(Locale.US, "%s on '%s' field '%s' in '%s'.", message, descriptor.getFinalColumnType(), descriptor.getFinalColumnName(), fieldDescriptions)); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java index a5b7f6cc70..d919d1e8d0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java @@ -16,6 +16,7 @@ */ import java.util.List; +import java.util.Locale; import java.util.Set; import io.realm.RealmFieldType; @@ -69,13 +70,13 @@ protected void compileFieldDescription(List fields) { tableInfo = schema.getColumnInfo(currentTable); if (tableInfo == null) { throw new IllegalArgumentException( - String.format("Invalid query: table '%s' not found in this schema.", currentTable)); + String.format(Locale.US, "Invalid query: table '%s' not found in this schema.", currentTable)); } columnIndex = tableInfo.getColumnIndex(columnName); if (columnIndex < 0) { throw new IllegalArgumentException( - String.format("Invalid query: field '%s' not found in table '%s'.", columnName, currentTable)); + String.format(Locale.US, "Invalid query: field '%s' not found in table '%s'.", columnName, currentTable)); } columnType = tableInfo.getColumnType(columnName); diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java index 55995f9355..80dc911b61 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java @@ -16,6 +16,7 @@ */ import java.util.List; +import java.util.Locale; import java.util.Set; import io.realm.RealmFieldType; @@ -64,7 +65,7 @@ protected void compileFieldDescription(List fields) { columnIndex = currentTable.getColumnIndex(columnName); if (columnIndex < 0) { throw new IllegalArgumentException( - String.format("Invalid query: field '%s' not found in table '%s'.", columnName, tableName)); + String.format(Locale.US, "Invalid query: field '%s' not found in table '%s'.", columnName, tableName)); } columnType = currentTable.getColumnType(columnIndex); diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java index 6a17af9445..4ed1bcac46 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import io.realm.RealmFieldType; @@ -273,7 +274,7 @@ private List parseFieldDescription(String fieldDescription) { private void verifyColumnType(String tableName, String columnName, RealmFieldType columnType, Set validTypes) { if (!validTypes.contains(columnType)) { - throw new IllegalArgumentException(String.format( + throw new IllegalArgumentException(String.format(Locale.US, "Invalid query: field '%s' in table '%s' is of invalid type '%s'.", columnName, tableName, columnType.toString())); } diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java index 80bde88777..f6f33cac71 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLog.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLog.java @@ -18,6 +18,8 @@ import android.util.Log; +import java.util.Locale; + /** * Global logger used by all Realm components. @@ -275,7 +277,7 @@ private static void log(int level, Throwable throwable, String message, Object.. StringBuilder stringBuilder = new StringBuilder(); if (args != null && args.length > 0) { - message = String.format(message, args); + message = String.format(Locale.US, message, args); } if (throwable != null) { stringBuilder.append(Log.getStackTraceString(throwable)); diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index 29e7faef4a..401a01f9af 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.util.Locale; import io.realm.internal.Keep; @@ -50,10 +51,12 @@ public static void init(Context context) { File dir = File.createTempFile("remote_sync_", "_" + android.os.Process.myPid(), context.getFilesDir()); if (!dir.delete()) { - throw new IllegalStateException(String.format("Temp file '%s' cannot be deleted.", dir.getPath())); + throw new IllegalStateException(String.format(Locale.US, + "Temp file '%s' cannot be deleted.", dir.getPath())); } if (!dir.mkdir()) { - throw new IllegalStateException(String.format("Directory '%s' for SyncManager cannot be created. ", + throw new IllegalStateException(String.format(Locale.US, + "Directory '%s' for SyncManager cannot be created. ", dir.getPath())); } SyncManager.nativeInitializeSyncManager(dir.getPath()); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 4724b9c7d5..234a3ec80e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -511,7 +512,8 @@ public Builder encryptionKey(byte[] key) { throw new IllegalArgumentException("A non-null key must be provided"); } if (key.length != KEY_LENGTH) { - throw new IllegalArgumentException(String.format("The provided key must be %s bytes. Yours was: %s", + throw new IllegalArgumentException(String.format(Locale.US, + "The provided key must be %s bytes. Yours was: %s", KEY_LENGTH, key.length)); } this.key = Arrays.copyOf(key, key.length); @@ -720,7 +722,7 @@ private String MD5(String in) { byte[] buf = digest.digest(in.getBytes("UTF-8")); StringBuilder builder = new StringBuilder(); for (byte b : buf) { - builder.append(String.format("%02X", b)); + builder.append(String.format(Locale.US, "%02X", b)); } return builder.toString(); } catch (NoSuchAlgorithmException e) { @@ -799,14 +801,16 @@ public SyncConfiguration build() { realmFileDirectory = new File(rootDir, user.getIdentity()); fullPathName = realmFileDirectory.getAbsolutePath() + File.pathSeparator + realmFileName; if (fullPathName.length() > MAX_FULL_PATH_LENGTH) { // we are out of ideas - throw new IllegalStateException(String.format("Full path name must not exceed %d characters: %s", + throw new IllegalStateException(String.format(Locale.US, + "Full path name must not exceed %d characters: %s", MAX_FULL_PATH_LENGTH, fullPathName)); } } } if (realmFileName.length() > MAX_FILE_NAME_LENGTH) { - throw new IllegalStateException(String.format("File name exceed %d characters: %d", MAX_FILE_NAME_LENGTH, + throw new IllegalStateException(String.format(Locale.US, + "File name exceed %d characters: %d", MAX_FILE_NAME_LENGTH, realmFileName.length())); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 8e070e1dbd..e99a35d903 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -16,6 +16,7 @@ package io.realm; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; @@ -81,7 +82,7 @@ public void onError(SyncSession session, ObjectServerError error) { return; } - String errorMsg = String.format("Session Error[%s]: %s", + String errorMsg = String.format(Locale.US, "Session Error[%s]: %s", session.getConfiguration().getServerUrl(), error.toString()); switch (error.getErrorCode().getCategory()) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 846f79f290..2a0438ff01 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; @@ -635,7 +636,8 @@ public boolean isSuccess() { */ public void throwExceptionIfNeeded() { if (resultReceived && errorCode != null) { - throw new ObjectServerError(ErrorCode.UNKNOWN, String.format("Internal error (%d): %s", errorCode, errorMessage)); + throw new ObjectServerError(ErrorCode.UNKNOWN, + String.format(Locale.US, "Internal error (%d): %s", errorCode, errorMessage)); } } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 245a287482..94ea8c6a68 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; @@ -75,7 +76,8 @@ public void onError(SyncSession session, ObjectServerError error) { if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { RealmLog.error("Client Reset required for user's management Realm: " + user.toString()); } else { - RealmLog.error(String.format("Unexpected error with %s's management Realm: %s", + RealmLog.error(String.format(Locale.US, + "Unexpected error with %s's management Realm: %s", user.getIdentity(), error.toString())); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index 1fdaa9f8cf..cf9c8d85fe 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -20,6 +20,7 @@ import org.json.JSONObject; import java.io.IOException; +import java.util.Locale; import io.realm.ErrorCode; import io.realm.ObjectServerError; @@ -132,14 +133,14 @@ private AuthenticateResponse(String serverResponse) { if (accessToken == null) { message = "accessToken = null"; } else { - message = String.format("Identity %s; Path %s", accessToken.identity(), accessToken.path()); + message = String.format(Locale.US, "Identity %s; Path %s", accessToken.identity(), accessToken.path()); } } catch (JSONException ex) { accessToken = null; refreshToken = null; //noinspection ThrowableInstanceNeverThrown error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, ex); - message = String.format("Error %s", error.getErrorMessage()); + message = String.format(Locale.US, "Error %s", error.getErrorMessage()); } RealmLog.debug("AuthenticateResponse. " + message); setError(error); From 754c5b75f60a068abd371f007015240cd533890e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 27 Jun 2017 17:45:51 +0800 Subject: [PATCH 0788/2110] Clean up pre-null related tests created in 0.83.0 From 0.83.0, we begin to support the nullable fields. But because of a bug, the Realm file which was created in 0.82.2 contains all schemas defined in the project instead of those only defined in the RealmModule. This will cause lots of troubles after we fix this bug and move to Object Store's schema. - Create string-only-pre-null-0.82.2.realm with 0.82.2 which only contains StringOnly class. - Create string-only-required-pre-null-0.82.2.realm with 0.82.2 which only contains StringOnlyRequired class. - Use dynamic API to rewrite migrationException_realmListChanged. - Remove default-before-migration.realm. --- .../assets/default-before-migration.realm | Bin 12288 -> 0 bytes .../assets/string-only-pre-null-0.82.2.realm | Bin 0 -> 4096 bytes ...string-only-required-pre-null-0.82.2.realm | Bin 0 -> 4096 bytes .../java/io/realm/RealmMigrationTests.java | 56 ++++++++++++++---- .../java/io/realm/entities/CatOwner.java | 4 ++ .../io/realm/entities/StringOnlyRequired.java | 35 +++++++++++ 6 files changed, 82 insertions(+), 13 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/assets/default-before-migration.realm create mode 100644 realm/realm-library/src/androidTest/assets/string-only-pre-null-0.82.2.realm create mode 100644 realm/realm-library/src/androidTest/assets/string-only-required-pre-null-0.82.2.realm create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/StringOnlyRequired.java diff --git a/realm/realm-library/src/androidTest/assets/default-before-migration.realm b/realm/realm-library/src/androidTest/assets/default-before-migration.realm deleted file mode 100644 index 7a9ae6075ffd0d936bf897d9f369b9b27da7dc04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeHMO^74M6)vg&)h%_mS|iC>0s`kSVAd!(>8RZqFEZJ9M0O9lgq_}zb|hOPDU#OK z+!~U@aedtHAt zdQ=~F4*cQa4_k+KM)&)@{cwpv`FFbAhlhjKC|bu>Xq3O*-@nEpD=f<2`JmSthDwD& zE|eFD!Z1(yTmD!e3Pa96-0ODsu!{l@4{atN^4Hu{DF5x92V3nmII(bN}ykTHWT( zuz3wcw!eq%VavbnJwhJUADRA9XCj(l@rm|F!W^w&{=L)h-G}Y>h#we&5nICIAL+j{ zqI+58L;P+|0gf-rugEYPxd;V#n83XS{rj!q2!IDtM1Kn*2}!zCln&PEiLyY4b;*)c zk?#Q9 z1@s2mMvu@_^b7P`G$)?JCg$sLVCRgISC0>+@qCJ)Wx2L>Q!Z1P^)y>Iii6LzhY3vs zzP?fjBiU#2L^_jv)Fn7UFmwQdHOddVb1S4r3 z*E9J-UdUlQVM&0f4r&i6y2AO`-j(bq+#V=1k{|u_FAz`QIJiz4d;RXygWgJRVlHnU z<*(E}&cpo%<=^Y}Z>~f+%HQrkjdDknXZr8<``wn0vv;j!nY=&7Xk2g1{@qRw%Jg+M zoO80jAGiGO!Or7%TYGEuG9&QscSdVmgdB#yf%`2$?L#5nfUjIus!CfOsA3$b9|1DS zfW#F>0VUtigAr&nerM{r%JYc|1gJ^|gl|mMnR=|AsHf^&y-=U43$=s#h@N{K??Am$ zg?L+~>32T)<{MA|$q8}SS$F=y!1blcqoJ z49D%J&tswWS8uo!!vRt~fO>!o)3!BRb8ji=P4obE!f=pFT3MTGXBt~i=seGZe~}P;n#imH2sW_V@7} zAnnofAT$3-en9@{|N8tN|HS)cdgY0Jq@U=g#17wI6{`ikw0*1J>VFobyDRbEG%CiaaYoJ^ z#|1!y79hyb#FatiM4Vpsyj7W=|I|ZXq!-50&{IyTm@1_cBn}i|~B)X%SD*9v`&;uTyZp4f}FkS8|1YU78D1w;WP$cphN^yuSK^9{g3xR>i7X zHLGq>f4QF}t6>R#s-@3X>VUj{f4{(AQV+1dp64l^7R4d3rqGO0cL;WTeC?AFao!-!coKneDGqp@T)5xsixtZC`2zZ(a^wIY| z0aVyMYd(f2nyr49=|=jf-5-u4g4XE?Gq?x4@OBxok^TffQAGr;NuHoWg8KMy4DVZl ze)6UiQdD^NUE%MozQ(1fv{m{-Xl>T&)|oWMD9rq0Zveq%YH zC)>`k!~IP{*1(4~dLH-M{t&7gkONGIBSYk*bLY}ofIiVa=u03sqGSD6j_z{(Ae@8` zYeawY34U|_K1z`O2UI^SAof6B+H@;!-KBP@U=Yd^|J^~z$Mc3e3GLmqpSAiD?q}wn zxN~NQ=K<(TTnKcm|IED%^+R!zaD}9e)DMXl3PwyrT*WtQjXjTXA&?I6nAf@FEqgWI zOIOcUv$ZT+2TRHNl^*<(+Sx&N!qyQSvO$*P4me1gAVJ&Bes+4z)+BoUlHvE1w*?Zx z3BDxI73#rukF%%Q#hQN{Kk+%K=tcYDe2|xQ&+$rL*~9z7elN2XkNXXv$mqk!9+Wlt z%U{mF{#n3j$awbaXTN*)r@#OF*&hf^`SRoARsP4%ejjq-d6VHMGka3QYkQ~6KGn*V zNA-h0cVK_dy$kQsdmj2b_ZFeQfX&M96%gDc&o_P_W4nOk$~nL|>bYXBoTGX9APzUg`6{y-k$hZd4|(1Yvby)y9=!4BT0X)a{7P+gCkLQ=UrD#8X&nH-gU$6Y0|M^-xiW{37^?V~Q#)E2)@|BjP zfEm5suiSt6(>%XlxSlE*koX#te2_oNPxG_JYHl zDSXc00|gWXzhX<{E=C|mAVwfYAVwfYAVwfYAVwfYAVwfYAVwfYAVwfYAVwfYAVwfY LAV%Q-6oG#O#ufd~ diff --git a/realm/realm-library/src/androidTest/assets/string-only-pre-null-0.82.2.realm b/realm/realm-library/src/androidTest/assets/string-only-pre-null-0.82.2.realm new file mode 100644 index 0000000000000000000000000000000000000000..9995d001ea4cb1865ede68cd05ac5b0c551c64f4 GIT binary patch literal 4096 zcmeH^zfJ-{5XNT~6=LvTb=TP2NC*|BF`Ce6q6OXM#EW+U;c~Z8>^8BXw4}78w4}7; z5qJbjOG{^V7dWNy0rra9oo{CMoB8cTfd0PFthc8Z=R#`=_5n(dkqqxc8Jg_I-%Gn2 zT2VOe^ujz3Ze*79AFlenmtvCk2LOsCa#v8L^(o5oWY8yRtzS|f3Pv#I%HKME6m+T+ z?=aO>Tt{PwT^n({M{(K>WO&z$tX!rz%?4?dN!{9(y`PRMVBa|`;83$sP|=6hEBl>b z9hZ5IeQa8iiv3ZewC8xfP8?=A)ma}sH~X|P#^E0y(($dJ^Zg3PtNZ!f3|ctBgn%j7 zruxdt(8S+``DwfKEBi1-+?nAV7f9#mQ$WL`r}rj40`Sbs1$iWc=S0Rqz1D2h8Ya*E zRD^FGH7{(6^&Fg6*ed3Aziq+&UUR?V4L@z(#6vhMnHF05{a!urmCs6>--G&&1y+Y* jB*vm_3fQMuAd~{Z5oHYhpS$9coRH5hjGv{MFjxKpX4r~MC(fsCt<%w(ni0eJ|v7_!j->u z{K)83C*EPEs<@7>A@)qf@jk^-$4mYDZeZjB#Zlakf;b(rlsD(oRtfC8fF&F%HWG?z z(`IGA6RhAW&#{X&LsGIoDwJj)->=#BKR%@6IHU9V5-02PdEXrB zIK_~FN!X$K;@VKh-^!aO$ field. - // This is changed to RealmList and getInstance() must throw an exception. + // Check if the RealmList type change can trigger a RealmMigrationNeededException. @Test public void migrationException_realmListChanged() throws IOException { - configFactory.copyRealmFromAssets(context, - "default-before-migration.realm", Realm.DEFAULT_REALM_NAME); + RealmConfiguration config = configFactory.createConfiguration(); + // Initialize the schema with RealmList + Realm.getInstance(configFactory.createConfiguration()).close(); + + DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); + dynamicRealm.beginTransaction(); + // Change the RealmList type to RealmList + RealmObjectSchema dogSchema = dynamicRealm.getSchema().get(Dog.CLASS_NAME); + RealmObjectSchema ownerSchema = dynamicRealm.getSchema().get(CatOwner.CLASS_NAME); + ownerSchema.removeField(CatOwner.FIELD_CATS); + ownerSchema.addRealmListField(CatOwner.FIELD_CATS, dogSchema); + dynamicRealm.commitTransaction(); + dynamicRealm.close(); + try { - realm = Realm.getInstance(configFactory.createConfiguration()); + realm = Realm.getInstance(config); fail(); } catch (RealmMigrationNeededException ignored) { + assertThat(ignored.getMessage(), + CoreMatchers.containsString("Invalid RealmList type for field 'cats': 'class_Dog' expected ")); } } @@ -816,7 +833,7 @@ public void migrationException_realmListChanged() throws IOException { @Test public void openPreNullRealmRequiredMissing() throws IOException { configFactory.copyRealmFromAssets(context, - "default-before-migration.realm", Realm.DEFAULT_REALM_NAME); + "string-only-pre-null-0.82.2.realm", Realm.DEFAULT_REALM_NAME); RealmMigration realmMigration = new RealmMigration() { @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { @@ -844,13 +861,15 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { // old class (without @Required) can be used, @Test public void migratePreNull() throws IOException { + final AtomicBoolean migrationCalled = new AtomicBoolean(false); configFactory.copyRealmFromAssets(context, - "default-before-migration.realm", Realm.DEFAULT_REALM_NAME); + "string-only-pre-null-0.82.2.realm", Realm.DEFAULT_REALM_NAME); RealmMigration migration = new RealmMigration() { @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { RealmObjectSchema objectSchema = realm.getSchema().get(StringOnly.CLASS_NAME); objectSchema.setRequired(StringOnly.FIELD_CHARS, false); + migrationCalled.set(true); } }; @@ -860,8 +879,13 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { .migration(migration) .build(); Realm realm = Realm.getInstance(realmConfig); + assertTrue(migrationCalled.get()); + + StringOnly stringOnly = realm.where(StringOnly.class).findFirst(); + assertNotNull(stringOnly); + // This object was created with 0.82.2 + assertEquals("String_set_with_0.82.2", stringOnly.getChars()); realm.beginTransaction(); - StringOnly stringOnly = realm.createObject(StringOnly.class); stringOnly.setChars(null); realm.commitTransaction(); realm.close(); @@ -873,19 +897,25 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { @Test public void openPreNullWithRequired() throws IOException { configFactory.copyRealmFromAssets(context, - "default-before-migration.realm", Realm.DEFAULT_REALM_NAME); + "string-only-required-pre-null-0.82.2.realm", Realm.DEFAULT_REALM_NAME); RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .schemaVersion(0) - .schema(AllTypes.class) + .schema(StringOnlyRequired.class) .build(); Realm realm = Realm.getInstance(realmConfig); + StringOnlyRequired stringOnlyRequired = realm.where(StringOnlyRequired.class).findFirst(); + assertNotNull(stringOnlyRequired); + // This object was created with 0.82.2 + assertEquals("String_set_with_0.82.2", stringOnlyRequired.getChars()); + realm.beginTransaction(); try { - AllTypes allTypes = realm.createObject(AllTypes.class); - allTypes.setColumnString(null); + stringOnlyRequired.setChars(null); fail(); - } catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException expected) { + assertThat(expected.getMessage(), + CoreMatchers.containsString("Trying to set non-nullable field 'chars' to null.")); } realm.cancelTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/CatOwner.java b/realm/realm-library/src/androidTest/java/io/realm/entities/CatOwner.java index 477ee232fd..577525d0fa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/CatOwner.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/CatOwner.java @@ -21,6 +21,10 @@ import io.realm.annotations.Required; public class CatOwner extends RealmObject { + public static final String CLASS_NAME = "CatOwner"; + public static final String FIELD_NAME = "name"; + public static final String FIELD_CATS = "cats"; + @Required private String name; private RealmList cats; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnlyRequired.java b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnlyRequired.java new file mode 100644 index 0000000000..df6200f92e --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnlyRequired.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import io.realm.RealmObject; +import io.realm.annotations.Required; + +// This class is used for the pre-null testing. Before 0.83.0, without nullable support, String is required by default. +// To use the Realm file created before 0.83.0 without migration, @Required has to be added to the String field. +public class StringOnlyRequired extends RealmObject { + @Required + private String chars; + + public String getChars() { + return chars; + } + + public void setChars(String chars) { + this.chars = chars; + } +} From 3d10a847c1d89d63fcaab922ed0648f645f0b737 Mon Sep 17 00:00:00 2001 From: LYK Date: Wed, 28 Jun 2017 03:32:18 +0900 Subject: [PATCH 0789/2110] OS X -> macOS (#4859) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fcd8d6c7f3..96361c603a 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ You may unzip the file wherever you choose. For OSX, a suggested location is `~ export ANDROID_NDK_HOME=~/Library/Android/android-ndk-r10e ``` - * If you will be launching Android Studio from the OS X Finder, you should also run the following two commands: + * If you will be launching Android Studio from the macOS Finder, you should also run the following two commands: ``` launchctl setenv ANDROID_HOME "$ANDROID_HOME" @@ -93,13 +93,13 @@ You may unzip the file wherever you choose. For OSX, a suggested location is `~ export REALM_CORE_DOWNLOAD_DIR=~/.realmCore ``` - OS X users must also run the following command in order for Android Studio to see this environment variable.. + macOS users must also run the following command in order for Android Studio to see this environment variable.. ``` launchctl setenv REALM_CORE_DOWNLOAD_DIR "$REALM_CORE_DOWNLOAD_DIR" ``` -It would be a good idea to add all of the symbol definitions (and their accompanying `launchctl` commands, if you are using OS X) to your `~/.profile` (or `~/.zprofile` if the login shell is `zsh`) +It would be a good idea to add all of the symbol definitions (and their accompanying `launchctl` commands, if you are using macOS) to your `~/.profile` (or `~/.zprofile` if the login shell is `zsh`) ### Download sources From 3889812ae8aeb622719bde22fc900dd34ee1a63a Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Wed, 28 Jun 2017 13:29:53 +0900 Subject: [PATCH 0790/2110] describe a way to exclude generated files from idexing target. (#4858) * describe a way to exclude generated files from idexing target. * fix typo * OS X -> macOS * address review comments --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 96361c603a..c01716814a 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,12 @@ You may unzip the file wherever you choose. For OSX, a suggested location is `~ It would be a good idea to add all of the symbol definitions (and their accompanying `launchctl` commands, if you are using macOS) to your `~/.profile` (or `~/.zprofile` if the login shell is `zsh`) + * If you develop Realm Java with Android Studio, we recommend you to exclude some directories from indexing target by executing following steps on Android Studio. It really speeds up indexing phase after build. + + - Under `/realm/realm-library/`, select `build`, `.externalNativeBuild` and `distribution` folders in `Project` view. + - Press `Command + Shift + A` to open `Find action` dialog. If you are not using defaut keymap nor using macOS, you can find your shortcut key in `Keymap` preference by searching `Find action`. + - Search `Excluded` (not `Exclude`) action and select it. Selected folder icons should become orange (in default theme). + - Restart Android Studio. ### Download sources From 728a088e4e4ef629053e38d92fefc55024604512 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 28 Jun 2017 14:12:59 +0800 Subject: [PATCH 0791/2110] IOSRealmTests should have deleteIfMigrationNeeded --- .../src/androidTest/java/io/realm/IOSRealmTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index 0ec3cd03fe..555e1c1466 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -59,7 +59,6 @@ public void setUp() { RealmConfiguration defaultConfiguration = configFactory.createConfigurationBuilder() .name(REALM_NAME) .schema(IOSAllTypes.class, IOSChild.class) - .deleteRealmIfMigrationNeeded() .build(); Realm.setDefaultConfiguration(defaultConfiguration); context = InstrumentationRegistry.getInstrumentation().getContext(); From 48f207d29ffb861f95d85903f62db2b3a68ba021 Mon Sep 17 00:00:00 2001 From: LYK Date: Wed, 28 Jun 2017 18:15:39 +0900 Subject: [PATCH 0792/2110] Update README.md (OSX -> macOS) (#4861) There was still OSX. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c01716814a..7c1c74a672 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ In case you don't want to use the precompiled version, you can build Realm yours * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). -You may unzip the file wherever you choose. For OSX, a suggested location is `~/Library/Android`. The download will unzip as the directory `android-ndk-r10e`. +You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library/Android`. The download will unzip as the directory `android-ndk-r10e`. * If you will be building with Android Studio, you will need to tell it to use the correct NDK. To do this, define the variable `ndk.dir` in `realm/local.properties` and assign it the full path name of the directory that you unzipped above. Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited. From b3d9df554eaf44fc1c334171b4db09af7fcc59c8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 28 Jun 2017 13:07:50 +0200 Subject: [PATCH 0793/2110] Prepare next dev iteration --- CHANGELOG.md | 15 +++++++++++++++ version.txt | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a3f6ca335..5fda4fd36f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 4.0.0-BETA1 (YYYY-MM-DD) + +### Breaking Changes + +### Deprecated + +### Enhancements + +### Bug Fixes + +### Internal + +### Credits + + ## 3.5.0 (YYYY-MM-DD) ### Breaking Changes diff --git a/version.txt b/version.txt index b9821b826b..2a07396c60 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.5.0-SNAPSHOT \ No newline at end of file +4.0.0-BETA1-SNAPSHOT \ No newline at end of file From d17d46403bb74436ee5ddc45a3aef44333b9c7af Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 29 Jun 2017 17:53:48 +0800 Subject: [PATCH 0794/2110] Let Object Store handle table creations (#4674) This tries to progressively move more things about schemas to Object Store. First the concept of Schema in Object Store is not the same as what we have in Java. It is very much just a schema information holder and won't take care of the schema modifications. That says it is more like the ColumnIndices cache in the Java binding. So this commit try to: - Instead of inheriting from the RealmSchema, change the OsRealmSchema/OsRealmObjectSchema to OsSchemaInfo/OsObjectSchemaInfo. They behave as a simple Java wrapper to the relevant OS objects. - Add functions to Proxy classes which will create its own OsObjectSchemaInfo and those info can be used to create a OsSchemaInfo through the mediator. - Call `SharedRealm.updateSchema` with the OsSchemaInfo which is got from the proxy interface to do table initialization. - This will also fix a minor bug we have before, All tables are created even if the class is not in the module. - Migration is still handled in the old way, and it will be solved in the future, to let Object Store handle it. - ColumnIndices are still kept for now, but it should be computed from the OsSchemaInfo/OsObjectSchemaInfo in the future. --- CHANGELOG.md | 4 +- .../processor/RealmProxyClassGenerator.java | 59 +++-- .../RealmProxyMediatorGenerator.java | 30 +-- .../io/realm/AllTypesRealmProxy.java | 40 ++-- .../io/realm/BooleansRealmProxy.java | 24 +- .../io/realm/NullTypesRealmProxy.java | 61 ++--- .../io/realm/RealmDefaultModuleMediator.java | 14 +- .../resources/io/realm/SimpleRealmProxy.java | 20 +- .../io/realm/RealmConfigurationTests.java | 9 +- .../realm-library/src/main/cpp/CMakeLists.txt | 4 +- ... io_realm_internal_OsObjectSchemaInfo.cpp} | 24 +- ...cpp => io_realm_internal_OsSchemaInfo.cpp} | 21 +- ...rty.cpp => io_realm_internal_Property.cpp} | 22 +- .../java/io/realm/OsRealmObjectSchema.java | 209 ----------------- .../src/main/java/io/realm/OsRealmSchema.java | 220 ------------------ .../src/main/java/io/realm/Realm.java | 116 +++------ .../io/realm/internal/OsObjectSchemaInfo.java | 133 +++++++++++ .../java/io/realm/internal/OsSchemaInfo.java | 61 +++++ .../io/realm/{ => internal}/Property.java | 27 ++- .../io/realm/internal/RealmProxyMediator.java | 11 +- .../java/io/realm/internal/SharedRealm.java | 13 +- .../internal/modules/CompositeMediator.java | 13 +- .../internal/modules/FilterableMediator.java | 21 +- 23 files changed, 443 insertions(+), 713 deletions(-) rename realm/realm-library/src/main/cpp/{io_realm_OsRealmObjectSchema.cpp => io_realm_internal_OsObjectSchemaInfo.cpp} (71%) rename realm/realm-library/src/main/cpp/{io_realm_OsRealmSchema.cpp => io_realm_internal_OsSchemaInfo.cpp} (69%) rename realm/realm-library/src/main/cpp/{io_realm_Property.cpp => io_realm_internal_Property.cpp} (80%) delete mode 100644 realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java delete mode 100644 realm/realm-library/src/main/java/io/realm/OsRealmSchema.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/OsSchemaInfo.java rename realm/realm-library/src/main/java/io/realm/{ => internal}/Property.java (71%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a3f6ca335..ed9bad7e72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ ### Breaking Changes * [ObjectServer] Updated protocol version to 18 which is only compatible with ROS > 1.6.0. +* An `IllegalStateException` will be thrown if the given `RealmModule` doesn't include all required model classes (#3398). ### Deprecated @@ -74,7 +75,8 @@ ### Internal -* Factor out internal interface ManagedObject +* Factor out internal interface ManagedObject. +* Use Object Store to do table initialization. ## 3.3.1 (2017-05-26) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index bc16c86a53..0e68b14d0c 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -51,6 +51,8 @@ public class RealmProxyClassGenerator { "io.realm.internal.ColumnInfo", "io.realm.internal.LinkView", "io.realm.internal.OsObject", + "io.realm.internal.OsObjectSchemaInfo", + "io.realm.internal.Property", "io.realm.internal.RealmObjectProxy", "io.realm.internal.Row", "io.realm.internal.SharedRealm", @@ -132,7 +134,8 @@ public void generate() throws IOException, UnsupportedOperationException { emitInjectContextMethod(writer); emitPersistedFieldAccessors(writer); emitBacklinkFieldAccessors(writer); - emitCreateRealmObjectSchemaMethod(writer); + emitCreateExpectedObjectSchemaInfo(writer); + emitGetExpectedObjectSchemaInfo(writer); emitValidateTableMethod(writer); emitGetTableNameMethod(writer); emitGetFieldNamesMethod(writer); @@ -227,7 +230,11 @@ private void emitColumnInfoClass(JavaWriter writer) throws IOException { private void emitClassFields(JavaWriter writer) throws IOException { writer.emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE)) - .emitField("ProxyState<" + qualifiedClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)); + .emitField("ProxyState<" + qualifiedClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)) + .emitField("OsObjectSchemaInfo", "expectedObjectSchemaInfo", + EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL), + "createExpectedObjectSchemaInfo()"); + for (VariableElement variableElement : metadata.getFields()) { if (Utils.isRealmList(variableElement)) { @@ -606,23 +613,18 @@ private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOExce } //@formatter:on - private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOException { + private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOException { writer.beginMethod( - "RealmObjectSchema", // Return type - "createRealmObjectSchema", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "RealmSchema", "realmSchema"); // Argument type & argument name + "OsObjectSchemaInfo", // Return type + "createExpectedObjectSchemaInfo", // Method name + EnumSet.of(Modifier.PRIVATE, Modifier.STATIC)); // Modifiers - writer.beginControlFlow("if (realmSchema.contains(\"%s\"))", this.simpleClassName) - .emitStatement("return realmSchema.get(\"%s\")", this.simpleClassName) - .endControlFlow(); - - writer.emitStatement("RealmObjectSchema realmObjectSchema = realmSchema.create(\"%s\")", this.simpleClassName); + writer.emitStatement( + "OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\")", this.simpleClassName); // For each field generate corresponding table index constant for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); - String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); Constants.RealmFieldType fieldType = getRealmType(field); switch (fieldType) { @@ -631,27 +633,22 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep break; case OBJECT: - writer.beginControlFlow("if (!realmSchema.contains(\"" + fieldTypeSimpleName + "\"))") - .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", fieldTypeSimpleName, Constants.PROXY_SUFFIX) - .endControlFlow() - .emitStatement("realmObjectSchema.add(\"%s\", RealmFieldType.OBJECT, realmSchema.get(\"%s\"))", - fieldName, fieldTypeSimpleName); + String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); + writer.emitStatement("builder.addLinkedProperty(\"%s\", RealmFieldType.OBJECT, \"%s\")", + fieldName, fieldTypeSimpleName); break; case LIST: String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); - writer.beginControlFlow("if (!realmSchema.contains(\"" + genericTypeSimpleName + "\"))") - .emitStatement("%s%s.createRealmObjectSchema(realmSchema)", genericTypeSimpleName, Constants.PROXY_SUFFIX) - .endControlFlow() - .emitStatement("realmObjectSchema.add(\"%s\", RealmFieldType.LIST, realmSchema.get(\"%s\"))", - fieldName, genericTypeSimpleName); + writer.emitStatement("builder.addLinkedProperty(\"%s\", RealmFieldType.LIST, \"%s\")", + fieldName, genericTypeSimpleName); break; default: String nullableFlag = (metadata.isNullable(field) ? "!" : "") + "Property.REQUIRED"; String indexedFlag = (metadata.isIndexed(field) ? "" : "!") + "Property.INDEXED"; String primaryKeyFlag = (metadata.isPrimaryKey(field) ? "" : "!") + "Property.PRIMARY_KEY"; - writer.emitStatement("realmObjectSchema.add(\"%s\", %s, %s, %s, %s)", + writer.emitStatement("builder.addProperty(\"%s\", %s, %s, %s, %s)", fieldName, fieldType.getRealmType(), primaryKeyFlag, @@ -659,7 +656,19 @@ private void emitCreateRealmObjectSchemaMethod(JavaWriter writer) throws IOExcep nullableFlag); } } - writer.emitStatement("return realmObjectSchema"); + writer.emitStatement("return builder.build()"); + writer.endMethod() + .emitEmptyLine(); + } + + private void emitGetExpectedObjectSchemaInfo(JavaWriter writer) throws IOException { + writer.beginMethod( + "OsObjectSchemaInfo", // Return type + "getExpectedObjectSchemaInfo", // Method name + EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)); // Modifiers + + writer.emitStatement(" return expectedObjectSchemaInfo"); + writer.endMethod() .emitEmptyLine(); } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index 65f4e7a843..bd3b75d5b5 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -80,7 +80,7 @@ public void generate() throws IOException { "io.realm.internal.RealmProxyMediator", "io.realm.internal.Row", "io.realm.internal.Table", - "io.realm.RealmObjectSchema", + "io.realm.internal.OsObjectSchemaInfo", "org.json.JSONException", "org.json.JSONObject" ); @@ -96,7 +96,7 @@ public void generate() throws IOException { writer.emitEmptyLine(); emitFields(writer); - emitCreateRealmObjectSchema(writer); + emitGetExpectedObjectSchemaInfoMap(writer); emitValidateTableMethod(writer); emitGetFieldNamesMethod(writer); emitGetTableNameMethod(writer); @@ -126,20 +126,22 @@ private void emitFields(JavaWriter writer) throws IOException { writer.emitEmptyLine(); } - private void emitCreateRealmObjectSchema(JavaWriter writer) throws IOException { + private void emitGetExpectedObjectSchemaInfoMap(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); writer.beginMethod( - "RealmObjectSchema", - "createRealmObjectSchema", - EnumSet.of(Modifier.PUBLIC), - "Class", "clazz", "RealmSchema", "realmSchema" - ); - emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return %s.createRealmObjectSchema(realmSchema)", qualifiedProxyClasses.get(i)); - } - }, writer); + "Map, OsObjectSchemaInfo>", + "getExpectedObjectSchemaInfoMap", + EnumSet.of(Modifier.PUBLIC)); + + writer.emitStatement( + "Map, OsObjectSchemaInfo> infoMap = " + + "new HashMap, OsObjectSchemaInfo>()"); + for (int i = 0; i < qualifiedProxyClasses.size(); i++) { + writer.emitStatement("infoMap.put(%s.class, %s.getExpectedObjectSchemaInfo())", + qualifiedModelClasses.get(i), qualifiedProxyClasses.get(i)); + } + writer.emitStatement("return infoMap"); + writer.endMethod(); writer.emitEmptyLine(); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 7e51275132..22dc82ea71 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -9,6 +9,8 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.Property; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -85,6 +87,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private AllTypesColumnInfo columnInfo; private ProxyState proxyState; + private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); private RealmList columnRealmListRealmList; private RealmResults parentObjectsBacklinks; private static final List FIELD_NAMES; @@ -397,27 +400,22 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return parentObjectsBacklinks; } - public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { - if (realmSchema.contains("AllTypes")) { - return realmSchema.get("AllTypes"); - } - RealmObjectSchema realmObjectSchema = realmSchema.create("AllTypes"); - realmObjectSchema.add("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnDouble", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - if (!realmSchema.contains("AllTypes")) { - AllTypesRealmProxy.createRealmObjectSchema(realmSchema); - } - realmObjectSchema.add("columnObject", RealmFieldType.OBJECT, realmSchema.get("AllTypes")); - if (!realmSchema.contains("AllTypes")) { - AllTypesRealmProxy.createRealmObjectSchema(realmSchema); - } - realmObjectSchema.add("columnRealmList", RealmFieldType.LIST, realmSchema.get("AllTypes")); - return realmObjectSchema; + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes"); + builder.addProperty("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, Property.INDEXED, !Property.REQUIRED); + builder.addProperty("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("columnDouble", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("columnBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addLinkedProperty("columnObject", RealmFieldType.OBJECT, "AllTypes"); + builder.addLinkedProperty("columnRealmList", RealmFieldType.LIST, "AllTypes"); + return builder.build(); + } + + public static OsObjectSchemaInfo getExpectedObjectSchemaInfo() { + return expectedObjectSchemaInfo; } public static AllTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 04b7de7941..4248632d3d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -9,6 +9,8 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.Property; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -68,6 +70,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private BooleansColumnInfo columnInfo; private ProxyState proxyState; + private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -184,16 +187,17 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { proxyState.getRow$realm().setBoolean(columnInfo.anotherBooleanIndex, value); } - public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { - if (realmSchema.contains("Booleans")) { - return realmSchema.get("Booleans"); - } - RealmObjectSchema realmObjectSchema = realmSchema.create("Booleans"); - realmObjectSchema.add("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("anotherBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - return realmObjectSchema; + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Booleans"); + builder.addProperty("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("anotherBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + return builder.build(); + } + + public static OsObjectSchemaInfo getExpectedObjectSchemaInfo() { + return expectedObjectSchemaInfo; } public static BooleansColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index c90fd7dfcc..f7395470d9 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -9,6 +9,8 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.Property; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -119,6 +121,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private NullTypesColumnInfo columnInfo; private ProxyState proxyState; + private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -819,36 +822,34 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { proxyState.getRow$realm().setLink(columnInfo.fieldObjectNullIndex, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex()); } - public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { - if (realmSchema.contains("NullTypes")) { - return realmSchema.get("NullTypes"); - } - RealmObjectSchema realmObjectSchema = realmSchema.create("NullTypes"); - realmObjectSchema.add("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldBooleanNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldBytesNotNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldBytesNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldByteNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldByteNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldShortNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldShortNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldIntegerNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldIntegerNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldLongNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldLongNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldFloatNotNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldFloatNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldDoubleNotNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldDoubleNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - realmObjectSchema.add("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - if (!realmSchema.contains("NullTypes")) { - NullTypesRealmProxy.createRealmObjectSchema(realmSchema); - } - realmObjectSchema.add("fieldObjectNull", RealmFieldType.OBJECT, realmSchema.get("NullTypes")); - return realmObjectSchema; + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NullTypes"); + builder.addProperty("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldBooleanNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("fieldBytesNotNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldBytesNull", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("fieldByteNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldByteNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("fieldShortNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldShortNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("fieldIntegerNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldIntegerNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("fieldLongNotNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldLongNull", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("fieldFloatNotNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldFloatNull", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("fieldDoubleNotNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldDoubleNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addProperty("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addLinkedProperty("fieldObjectNull", RealmFieldType.OBJECT, "NullTypes"); + return builder.build(); + } + + public static OsObjectSchemaInfo getExpectedObjectSchemaInfo() { + return expectedObjectSchemaInfo; } public static NullTypesColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 4460594efa..3c3f0c02b1 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -2,8 +2,8 @@ import android.util.JsonReader; -import io.realm.RealmObjectSchema; import io.realm.internal.ColumnInfo; +import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; @@ -32,13 +32,11 @@ class DefaultRealmModuleMediator extends RealmProxyMediator { } @Override - public RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema realmSchema) { - checkClass(clazz); - - if (clazz.equals(some.test.AllTypes.class)) { - return io.realm.AllTypesRealmProxy.createRealmObjectSchema(realmSchema); - } - throw getMissingProxyClassException(clazz); + public Map, OsObjectSchemaInfo> getExpectedObjectSchemaInfoMap() { + Map, OsObjectSchemaInfo> infoMap = + new HashMap, OsObjectSchemaInfo>(); + infoMap.put(some.test.AllTypes.class, io.realm.AllTypesRealmProxy.getExpectedObjectSchemaInfo()); + return infoMap; } @Override diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index f8ff4c3b6f..f7c85cb206 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -9,6 +9,8 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.LinkView; import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.Property; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SharedRealm; @@ -62,6 +64,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private SimpleColumnInfo columnInfo; private ProxyState proxyState; + private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); private static final List FIELD_NAMES; static { List fieldNames = new ArrayList(); @@ -140,14 +143,15 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { proxyState.getRow$realm().setLong(columnInfo.ageIndex, value); } - public static RealmObjectSchema createRealmObjectSchema(RealmSchema realmSchema) { - if (realmSchema.contains("Simple")) { - return realmSchema.get("Simple"); - } - RealmObjectSchema realmObjectSchema = realmSchema.create("Simple"); - realmObjectSchema.add("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); - realmObjectSchema.add("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); - return realmObjectSchema; + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Simple"); + builder.addProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addProperty("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + return builder.build(); + } + + public static OsObjectSchemaInfo getExpectedObjectSchemaInfo() { + return expectedObjectSchemaInfo; } public static SimpleColumnInfo validateTable(SharedRealm sharedRealm, boolean allowExtraColumns) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index f5ecd5cd4d..5166f24a4a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -262,18 +262,15 @@ public void constructBuilder_versionEqualWhenSchemaChangesThrows() { } } + // Only Dog is included in the schema definition, but in order to create Dog, the Owner has to be defined as well. @Test - public void customSchemaDontIncludeLinkedClasses() { + public void schemaDoesNotContainAllDefinedObjectShouldThrow() { RealmConfiguration config = new RealmConfiguration.Builder(context) .directory(configFactory.getRoot()) .schema(Dog.class) .build(); + thrown.expect(IllegalStateException.class); realm = Realm.getInstance(config); - try { - assertEquals(3, realm.getTable(Owner.class).getColumnCount()); - fail("Owner should to be part of the schema"); - } catch (IllegalArgumentException ignored) { - } } @Test diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 11c3d43caf..3d7e3159a5 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -40,8 +40,8 @@ set(classes_LIST io.realm.internal.Table io.realm.internal.CheckedRow io.realm.internal.LinkView io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil - io.realm.log.LogLevel io.realm.log.RealmLog io.realm.Property io.realm.OsRealmSchema - io.realm.OsRealmObjectSchema io.realm.internal.Collection + io.realm.log.LogLevel io.realm.log.RealmLog io.realm.internal.Property io.realm.internal.OsSchemaInfo + io.realm.internal.OsObjectSchemaInfo io.realm.internal.Collection io.realm.internal.NativeObjectReference io.realm.internal.CollectionChangeSet io.realm.internal.OsObject ) diff --git a/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp similarity index 71% rename from realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp index c3a954c8f1..52f1efd343 100644 --- a/realm/realm-library/src/main/cpp/io_realm_OsRealmObjectSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp @@ -15,7 +15,7 @@ */ #include -#include "io_realm_OsRealmObjectSchema.h" +#include "io_realm_internal_OsObjectSchemaInfo.h" #include #include @@ -23,7 +23,13 @@ #include "util.hpp" using namespace realm; -JNIEXPORT jlong JNICALL Java_io_realm_OsRealmObjectSchema_nativeCreateRealmObjectSchema(JNIEnv* env, jclass, +static void finalize_object_schema(jlong ptr) +{ + TR_ENTER_PTR(ptr); + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeCreateRealmObjectSchema(JNIEnv* env, jclass, jstring className_) { TR_ENTER() @@ -37,18 +43,14 @@ JNIEXPORT jlong JNICALL Java_io_realm_OsRealmObjectSchema_nativeCreateRealmObjec return 0; } -JNIEXPORT void JNICALL Java_io_realm_OsRealmObjectSchema_nativeClose(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER_PTR(native_ptr) - try { - ObjectSchema* object_schema = reinterpret_cast(native_ptr); - delete object_schema; - } - CATCH_STD() + TR_ENTER() + return reinterpret_cast(&finalize_object_schema); } -JNIEXPORT void JNICALL Java_io_realm_OsRealmObjectSchema_nativeAddProperty(JNIEnv* env, jclass, jlong native_ptr, +JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeAddProperty(JNIEnv* env, jclass, jlong native_ptr, jlong property_ptr) { TR_ENTER_PTR(native_ptr) @@ -63,7 +65,7 @@ JNIEXPORT void JNICALL Java_io_realm_OsRealmObjectSchema_nativeAddProperty(JNIEn CATCH_STD() } -JNIEXPORT jstring JNICALL Java_io_realm_OsRealmObjectSchema_nativeGetClassName(JNIEnv* env, jclass, jlong nativePtr) +JNIEXPORT jstring JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetClassName(JNIEnv* env, jclass, jlong nativePtr) { TR_ENTER_PTR(nativePtr) try { diff --git a/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp similarity index 69% rename from realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp index 20a4852a05..f52e15831f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_OsRealmSchema.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp @@ -15,18 +15,24 @@ */ #include -#include "io_realm_OsRealmSchema.h" +#include "io_realm_internal_OsSchemaInfo.h" #include #include #include #include "util.hpp" + using namespace realm; +static void finalize_schema(jlong ptr) +{ + TR_ENTER_PTR(ptr); + delete reinterpret_cast(ptr); +} -JNIEXPORT jlong JNICALL Java_io_realm_OsRealmSchema_nativeCreateFromList(JNIEnv* env, jclass, - jlongArray objectSchemaPtrs_) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSchemaInfo_nativeCreateFromList(JNIEnv* env, jclass, + jlongArray objectSchemaPtrs_) { TR_ENTER() try { @@ -35,16 +41,15 @@ JNIEXPORT jlong JNICALL Java_io_realm_OsRealmSchema_nativeCreateFromList(JNIEnv* for (jsize i = 0; i < array.len(); ++i) { object_schemas.push_back(*reinterpret_cast(array[i])); } - auto* schema = new Schema(object_schemas); + auto* schema = new Schema(std::move(object_schemas)); return reinterpret_cast(schema); } CATCH_STD() return 0; } -JNIEXPORT void JNICALL Java_io_realm_OsRealmSchema_nativeClose(JNIEnv*, jclass, jlong nativePtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSchemaInfo_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER_PTR(nativePtr) - Schema* schema = reinterpret_cast(nativePtr); - delete schema; + TR_ENTER() + return reinterpret_cast(&finalize_schema); } diff --git a/realm/realm-library/src/main/cpp/io_realm_Property.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp similarity index 80% rename from realm/realm-library/src/main/cpp/io_realm_Property.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp index 38e9dff137..48f58c7c31 100644 --- a/realm/realm-library/src/main/cpp/io_realm_Property.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp @@ -15,7 +15,7 @@ */ #include -#include "io_realm_Property.h" +#include "io_realm_internal_Property.h" #include #include @@ -25,7 +25,13 @@ using namespace realm; -JNIEXPORT jlong JNICALL Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2IZZZ( +static void finalize_property(jlong ptr) +{ + TR_ENTER_PTR(ptr); + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeCreateProperty__Ljava_lang_String_2IZZZ( JNIEnv* env, jclass, jstring name_, jint type, jboolean is_primary, jboolean is_indexed, jboolean is_nullable) { TR_ENTER() @@ -48,7 +54,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_Property_nativeCreateProperty__Ljava_lang_ return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_Property_nativeCreateProperty__Ljava_lang_String_2ILjava_lang_String_2( +JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeCreateProperty__Ljava_lang_String_2ILjava_lang_String_2( JNIEnv* env, jclass, jstring name_, jint type, jstring linkedToName_) { TR_ENTER() @@ -63,12 +69,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_Property_nativeCreateProperty__Ljava_lang_ return 0; } -JNIEXPORT void JNICALL Java_io_realm_Property_nativeClose(JNIEnv* env, jclass, jlong property_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER_PTR(property_ptr) - try { - Property* property = reinterpret_cast(property_ptr); - delete property; - } - CATCH_STD() + TR_ENTER() + return reinterpret_cast(&finalize_property); } diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java deleted file mode 100644 index 8da935e7a6..0000000000 --- a/realm/realm-library/src/main/java/io/realm/OsRealmObjectSchema.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ -package io.realm; - -import java.util.Set; - -import io.realm.internal.Table; - - -class OsRealmObjectSchema extends RealmObjectSchema { - private long nativePtr; - - /** - * Creates a schema object using object store. This constructor is intended to be used by - * the validation of schema, object schemas and properties through the object store. Even though the constructor - * is public, there is never a purpose which justifies calling it! - * - * @param schema The parent for this schema: the schema to which this object belongs - * @param className name of the class - */ - OsRealmObjectSchema(RealmSchema schema, String className) { - this(schema, nativeCreateRealmObjectSchema(className)); - } - - private OsRealmObjectSchema(RealmSchema schema, long nativePtr) { - super(schema); - this.nativePtr = nativePtr; - } - - @Override - public void close() { - if (nativePtr != 0L) { - nativeClose(nativePtr); - nativePtr = 0L; - } - } - - @Override - public String getClassName() { - return nativeGetClassName(nativePtr); - } - - @Override - public OsRealmObjectSchema setClassName(String className) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema removeField(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema renameField(String currentFieldName, String newFieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasField(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema addIndex(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasIndex(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema removeIndex(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema addPrimaryKey(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema removePrimaryKey() { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema setRequired(String fieldName, boolean required) { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema setNullable(String fieldName, boolean nullable) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isRequired(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isNullable(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isPrimaryKey(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasPrimaryKey() { - throw new UnsupportedOperationException(); - } - - @Override - public String getPrimaryKey() { - throw new UnsupportedOperationException(); - } - - @Override - public Set getFieldNames() { - throw new UnsupportedOperationException(); - } - - @Override - public OsRealmObjectSchema transform(Function function) { - throw new UnsupportedOperationException(); - } - - @Override - public RealmFieldType getFieldType(String fieldName) { - throw new UnsupportedOperationException(); - } - - @Override - OsRealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { - final Property property = new Property(name, type, primary, indexed, required); - try { - nativeAddProperty(nativePtr, property.getNativePtr()); - } finally { - property.close(); - } - return this; - } - - @Override - OsRealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linkedTo) { - final Property property = new Property(name, type, linkedTo); - try { - nativeAddProperty(nativePtr, property.getNativePtr()); - } finally { - property.close(); - } - return this; - } - - @Override - Table getTable() { - throw new UnsupportedOperationException(); - } - - @Override - long getAndCheckFieldIndex(String fieldName) { - throw new UnsupportedOperationException(); - } - - long getNativePtr() { - return nativePtr; - } - - static native long nativeCreateRealmObjectSchema(String className); - - static native void nativeAddProperty(long nativePtr, long nativePropertyPtr); - - static native void nativeClose(long nativePtr); - - static native String nativeGetClassName(long nativePtr); -} diff --git a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java b/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java deleted file mode 100644 index f047e5ec8d..0000000000 --- a/realm/realm-library/src/main/java/io/realm/OsRealmSchema.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import io.realm.internal.Table; - - -/** - * Class for interacting with the Realm schema using a dynamic API. This makes it possible - * to add, delete and change the classes in the Realm. - *

            - * All changes must happen inside a write transaction for the particular Realm. - * - * @see RealmMigration - */ -class OsRealmSchema extends RealmSchema { - static final class Creator extends RealmSchema { - private final Map schema = new HashMap<>(); - - @Override - public void close() { - for (Map.Entry entry : schema.entrySet()) { - entry.getValue().close(); - } - schema.clear(); - } - - @Override - public RealmObjectSchema get(String className) { - checkEmpty(className); - return (!contains(className)) ? null : schema.get(className); - } - - @Override - public Set getAll() { - return new LinkedHashSet(schema.values()); - } - - @Override - public RealmObjectSchema create(String className) { - checkEmpty(className); - OsRealmObjectSchema realmObjectSchema = new OsRealmObjectSchema(this, className); - schema.put(className, realmObjectSchema); - return realmObjectSchema; - } - - @Override - public boolean contains(String className) { - return schema.containsKey(className); - } - - @Override - Table getTable(Class clazz) { - throw new UnsupportedOperationException(); - } - - @Override - Table getTable(String className) { - throw new UnsupportedOperationException(); - } - - @Override - OsRealmObjectSchema getSchemaForClass(Class clazz) { - throw new UnsupportedOperationException(); - } - - @Override - OsRealmObjectSchema getSchemaForClass(String className) { - throw new UnsupportedOperationException(); - } - - @Override - public void remove(String className) { - throw new UnsupportedOperationException(); - } - - @Override - public RealmObjectSchema rename(String oldClassName, String newClassName) { - throw new UnsupportedOperationException(); - } - } - - private final Map dynamicClassToSchema = new HashMap<>(); - - private long nativePtr; - - // TODO: - // Because making getAll return Set is a breaking change - // Creator.getAll must return Set instead of Set - // That necessitates the cast inside the loop below. - OsRealmSchema(Creator creator) { - Set realmObjectSchemas = creator.getAll(); - long[] schemaNativePointers = new long[realmObjectSchemas.size()]; - int i = 0; - for (RealmObjectSchema schema : realmObjectSchemas) { - schemaNativePointers[i++] = ((OsRealmObjectSchema) schema).getNativePtr(); - } - this.nativePtr = nativeCreateFromList(schemaNativePointers); - } - - public long getNativePtr() { - return this.nativePtr; - } - - // See BaseRealm uses a StandardRealmSchema, not a OsRealmSchema. - @Override - public void close() { - if (nativePtr != 0L) { - nativeClose(nativePtr); - nativePtr = 0L; - } - } - - /** - * Returns the Realm schema for a given class. - * - * @param className name of the class - * @return schema object for that class or {@code null} if the class doesn't exists. - */ - @Override - public RealmObjectSchema get(String className) { - checkEmpty(className); - return (!contains(className)) ? null : dynamicClassToSchema.get(className); - } - - /** - * Returns the {@link RealmObjectSchema} for all RealmObject classes that can be saved in this Realm. - * - * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. - */ - @Override - public Set getAll() { - throw new UnsupportedOperationException(); - } - - /** - * Adds a new class to the Realm. - * - * @param className name of the class. - * @return a Realm schema object for that class. - */ - @Override - public RealmObjectSchema create(String className) { - // Adding a class is always permitted. - checkEmpty(className); - OsRealmObjectSchema realmObjectSchema = new OsRealmObjectSchema(this, className); - dynamicClassToSchema.put(className, realmObjectSchema); - return realmObjectSchema; - } - - @Override - public void remove(String className) { - throw new UnsupportedOperationException(); - } - - @Override - public RealmObjectSchema rename(String oldClassName, String newClassName) { - throw new UnsupportedOperationException(); - } - - /** - * Checks if a given class already exists in the schema. - * - * @param className class name to check. - * @return {@code true} if the class already exists. {@code false} otherwise. - */ - @Override - public boolean contains(String className) { - return dynamicClassToSchema.containsKey(className); - } - - @Override - Table getTable(Class clazz) { - throw new UnsupportedOperationException(); - } - - @Override - Table getTable(String className) { - throw new UnsupportedOperationException(); - } - - @Override - OsRealmObjectSchema getSchemaForClass(Class clazz) { - throw new UnsupportedOperationException(); - } - - @Override - OsRealmObjectSchema getSchemaForClass(String className) { - throw new UnsupportedOperationException(); - } - - private static void checkEmpty(String str) { - if (str == null || str.isEmpty()) { - throw new IllegalArgumentException("Null or empty class names are not allowed"); - } - } - - static native long nativeCreateFromList(long[] objectSchemaPtrs); - - static native void nativeClose(long nativePtr); -} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 263bfe9311..f8357eb3c7 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -53,6 +53,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsObject; +import io.realm.internal.OsSchemaInfo; import io.realm.internal.RealmCore; import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; @@ -419,11 +420,7 @@ private static Realm createAndValidateFromCache(RealmCache cache) { // Initializes Realm schema if needed. try { - if (!syncingConfig) { - initializeRealm(realm); - } else { - initializeSyncedRealm(realm); - } + initializeRealm(realm); } catch (RuntimeException e) { realm.doClose(); throw e; @@ -443,23 +440,34 @@ private static void initializeRealm(Realm realm) { // interprocess lock. This lock can obviously not be created by a Realm instance so we probably need // to implement it in Object Store. When this happens, the `beginTransaction(true)` can be removed again. realm.beginTransaction(true); + RealmConfiguration configuration = realm.getConfiguration(); long currentVersion = realm.getVersion(); boolean unversioned = currentVersion == UNVERSIONED; - commitChanges = unversioned; + long newVersion = configuration.getSchemaVersion(); - RealmConfiguration configuration = realm.getConfiguration(); RealmProxyMediator mediator = configuration.getSchemaMediator(); Set> modelClasses = mediator.getModelClasses(); - // Only allow creating the schema if not in read-only mode - if (unversioned) { - if (configuration.isReadOnly()) { - throw new IllegalArgumentException("Cannot create the Realm schema in a read-only file."); + if (configuration.isSyncConfiguration()) { + // Update/create the schema if allowed + if (!configuration.isReadOnly()) { + OsSchemaInfo schema = new OsSchemaInfo(mediator.getExpectedObjectSchemaInfoMap().values()); + + // Object Store handles all update logic + realm.sharedRealm.updateSchema(schema, newVersion); + commitChanges = true; } - realm.setVersion(configuration.getSchemaVersion()); - // Create all of the tables. - for (Class modelClass : modelClasses) { - mediator.createRealmObjectSchema(modelClass, realm.getSchema()); + } else { + // Only allow creating the schema if not in read-only mode + if (unversioned) { + if (configuration.isReadOnly()) { + throw new IllegalArgumentException("Cannot create the Realm schema in a read-only file."); + } + + // Let Object Store initialize all tables + OsSchemaInfo schemaInfo = new OsSchemaInfo(mediator.getExpectedObjectSchemaInfoMap().values()); + realm.sharedRealm.updateSchema(schemaInfo, newVersion); + commitChanges = true; } } @@ -468,11 +476,13 @@ private static void initializeRealm(Realm realm) { for (Class modelClass : modelClasses) { String className = Table.getClassNameForTable(mediator.getTableName(modelClass)); Pair, String> key = Pair., String>create(modelClass, className); - columnInfoMap.put(key, mediator.validateTable(modelClass, realm.sharedRealm, false)); + // More fields in the Realm than defined is allowed for synced Realm. + columnInfoMap.put(key, mediator.validateTable(modelClass, realm.sharedRealm, + configuration.isSyncConfiguration())); } realm.getSchema().setInitialColumnIndices( - (unversioned) ? configuration.getSchemaVersion() : currentVersion, + (unversioned) ? newVersion : currentVersion, columnInfoMap); // Finally add any initial data @@ -492,78 +502,6 @@ private static void initializeRealm(Realm realm) { } } - // Everything in this method needs to be behind a transaction lock - // to prevent multi-process interaction while the Realm is initialized. - private static void initializeSyncedRealm(Realm realm) { - boolean commitChanges = false; - OsRealmSchema schema = null; - OsRealmSchema.Creator schemaCreator = null; - try { - // We need to start a transaction no matter readOnly mode, because it acts as an interprocess lock. - // TODO: For proper inter-process support we also need to move e.g copying the asset file under an - // interprocess lock. This lock can obviously not be created by a Realm instance so we probably need - // to implement it in Object Store. When this happens, the `beginTransaction(true)` can be removed again. - realm.beginTransaction(true); - long currentVersion = realm.getVersion(); - final boolean unversioned = currentVersion == UNVERSIONED; - - RealmConfiguration configuration = realm.getConfiguration(); - - final RealmProxyMediator mediator = configuration.getSchemaMediator(); - final Set> modelClasses = mediator.getModelClasses(); - - long newVersion = configuration.getSchemaVersion(); - - // Update/create the schema if allowed - if (!configuration.isReadOnly()) { - schemaCreator = new OsRealmSchema.Creator(); - for (Class modelClass : modelClasses) { - mediator.createRealmObjectSchema(modelClass, schemaCreator); - } - - // Assumption: When SyncConfiguration then additive schema update mode. - schema = new OsRealmSchema(schemaCreator); - schemaCreator.close(); - schemaCreator = null; - - // Object Store handles all update logic - realm.sharedRealm.updateSchema(schema.getNativePtr(), newVersion); - commitChanges = true; - } - - // Validate the schema in the file - final Map, String>, ColumnInfo> columnInfoMap = new HashMap<>(modelClasses.size()); - for (Class modelClass : modelClasses) { - String className = Table.getClassNameForTable(mediator.getTableName(modelClass)); - Pair, String> key = Pair., String>create(modelClass, className); - columnInfoMap.put(key, mediator.validateTable(modelClass, realm.sharedRealm, true)); - } - realm.getSchema().setInitialColumnIndices((unversioned) ? newVersion : currentVersion, columnInfoMap); - - if (unversioned && !configuration.isReadOnly()) { - final Transaction transaction = configuration.getInitialDataTransaction(); - if (transaction != null) { - transaction.execute(realm); - } - } - } catch (RuntimeException e) { - commitChanges = false; - throw e; - } finally { - if (schemaCreator != null) { - schemaCreator.close(); - } - if (schema != null) { - schema.close(); - } - if (commitChanges) { - realm.commitTransaction(); - } else { - realm.cancelTransaction(); - } - } - } - /** * Creates a Realm object for each object in a JSON array. This must be done within a transaction. *

            diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java new file mode 100644 index 0000000000..55bb98cd7f --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java @@ -0,0 +1,133 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.internal; + + +import java.util.ArrayList; +import java.util.List; + +import io.realm.RealmFieldType; + +/** + * Immutable Java wrapper for Object Store ObjectSchema. + * + * @see OsSchemaInfo + */ +public class OsObjectSchemaInfo implements NativeObject { + + public static class Builder { + private String className; + private List propertyList = new ArrayList(); + + /** + * Creates an empty builder for {@code OsObjectSchemaInfo}. This constructor is intended to be used by + * the validation of schema, object schemas and properties through the object store. + * + * @param className name of the class + */ + public Builder(String className) { + this.className = className; + } + + /** + * Adds a property to this builder. + * + * @param name the name of the property. + * @param type the type of the property. + * @param isPrimaryKey set to true if this property is the primary key. + * @param isIndexed set to true if this property needs an index. + * @param isRequired set to false if this property is not nullable. + * @return this {@code OsObjectSchemaInfo}. + */ + public Builder addProperty(String name, RealmFieldType type, boolean isPrimaryKey, boolean isIndexed, + boolean isRequired) { + final Property property = new Property(name, type, isPrimaryKey, isIndexed, isRequired); + propertyList.add(property); + return this; + } + + /** + * Adds a linked property to this {@code OsObjectSchema}. + * + * @param name the name of the linked property. + * @param type the type of the linked property. + * @return this {@code OsObjectSchemaInfo}. + */ + public Builder addLinkedProperty(String name, RealmFieldType type, String linkedClassName) { + final Property property = new Property(name, type, linkedClassName); + propertyList.add(property); + return this; + } + + public OsObjectSchemaInfo build() { + OsObjectSchemaInfo info = new OsObjectSchemaInfo(className); + for (Property property : propertyList) { + nativeAddProperty(info.nativePtr, property.getNativePtr()); + } + + return info; + } + } + + private long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); + + /** + * Creates an empty schema object using object store. This constructor is intended to be used by + * the validation of schema, object schemas and properties through the object store. + * + * @param className name of the class + */ + private OsObjectSchemaInfo(String className) { + this(nativeCreateRealmObjectSchema(className)); + } + + /** + * Create a java wrapper class for given {@code ObjectSchema} pointer. This java wrapper will take the ownership of + * the object's memory and release it through phantom reference. + * + * @param nativePtr pointer to the {@code ObjectSchema} object. + */ + private OsObjectSchemaInfo(long nativePtr) { + this.nativePtr = nativePtr; + NativeContext.dummyContext.addReference(this); + } + + /** + * @return the class name of this {@code OsObjectSchema} represents for. + */ + public String getClassName() { + return nativeGetClassName(nativePtr); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + private static native long nativeCreateRealmObjectSchema(String className); + + private static native long nativeGetFinalizerPtr(); + + private static native void nativeAddProperty(long nativePtr, long nativePropertyPtr); + + private static native String nativeGetClassName(long nativePtr); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsSchemaInfo.java new file mode 100644 index 0000000000..f59f750010 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSchemaInfo.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +/** + * Java wrapper for the Object Store Schema object. + *

            + * When it is created from java binding, it is used for initializing/validating the schemas through Object Store. It + * won't contain the column indices information. + *

            + * When this is get from the Object Store {@code SharedRealm} instance, this represents the real schema of the Realm + * file. It will contain all the schema information as well as the information about the column indices. + */ +public class OsSchemaInfo implements NativeObject { + private long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); + + /** + * Construct a {@code OsSchemaInfo} object from a given {@code OsObjectSchemaInfo} list. + * + * @param objectSchemaInfoList all the object schemas should be contained in this {@code OsObjectSchemaInfo}. + */ + public OsSchemaInfo(java.util.Collection objectSchemaInfoList) { + long[] schemaNativePointers = new long[objectSchemaInfoList.size()]; + int i = 0; + for (OsObjectSchemaInfo info : objectSchemaInfoList) { + schemaNativePointers[i] = info.getNativePtr(); + i++; + } + this.nativePtr = nativeCreateFromList(schemaNativePointers); + NativeContext.dummyContext.addReference(this); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + private static native long nativeCreateFromList(long[] objectSchemaPtrs); + + private static native long nativeGetFinalizerPtr(); +} diff --git a/realm/realm-library/src/main/java/io/realm/Property.java b/realm/realm-library/src/main/java/io/realm/internal/Property.java similarity index 71% rename from realm/realm-library/src/main/java/io/realm/Property.java rename to realm/realm-library/src/main/java/io/realm/internal/Property.java index 4285be3db5..c2ba207764 100644 --- a/realm/realm-library/src/main/java/io/realm/Property.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Property.java @@ -14,46 +14,51 @@ * limitations under the License. */ -package io.realm; +package io.realm.internal; + + +import io.realm.RealmFieldType; /** * Class for handling properties/fields. */ -class Property { +public class Property implements NativeObject { public static final boolean PRIMARY_KEY = true; public static final boolean REQUIRED = true; public static final boolean INDEXED = true; private long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); Property(String name, RealmFieldType type, boolean isPrimary, boolean isIndexed, boolean isRequired) { this.nativePtr = nativeCreateProperty(name, type.getNativeValue(), isPrimary, isIndexed, !isRequired); + NativeContext.dummyContext.addReference(this); } - Property(String name, RealmFieldType type, RealmObjectSchema linkedTo) { - this.nativePtr = nativeCreateProperty(name, type.getNativeValue(), linkedTo.getClassName()); + Property(String name, RealmFieldType type, String linkedClassName) { + this.nativePtr = nativeCreateProperty(name, type.getNativeValue(), linkedClassName); + NativeContext.dummyContext.addReference(this); } protected Property(long nativePtr) { this.nativePtr = nativePtr; } - protected long getNativePtr() { + @Override + public long getNativePtr() { return nativePtr; } - public void close() { - if (nativePtr != 0) { - nativeClose(nativePtr); - nativePtr = 0L; - } + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; } private static native long nativeCreateProperty(String name, int type, boolean isPrimary, boolean isIndexed, boolean isNullable); private static native long nativeCreateProperty(String name, int type, String linkedToName); - private static native void nativeClose(long nativePtr); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index 50c2c72983..bd92154dfb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -29,8 +29,6 @@ import io.realm.Realm; import io.realm.RealmModel; import io.realm.RealmObject; -import io.realm.RealmObjectSchema; -import io.realm.RealmSchema; import io.realm.exceptions.RealmException; @@ -46,13 +44,12 @@ public abstract class RealmProxyMediator { /** - * Creates a object schema for the given RealmObject class. + * Returns a map of model classes to their schema information which are defined in this mediator. Classes which have + * same class name but in different packages should have different names in the {@code OsObjectSchemaInfo}. * - * @param clazz the {@link RealmObject} model class to create object schema for. - * @param realmSchema the {@link RealmSchema} to associate the object schema with. - * @return the object schema. + * @return the map with classes and their schema information. */ - public abstract RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema realmSchema); + public abstract Map, OsObjectSchemaInfo> getExpectedObjectSchemaInfoMap(); /** * Validates the backing table in Realm for the given RealmObject class. diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 98e79f0026..2408560e85 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -366,19 +366,14 @@ public boolean compact() { } /** - * Updates the underlying schema based on the schema description. + * Initializes the underlying schema based on the schema description. * Calling this method must be done from inside a write transaction. - *

            - * TODO: This method should not require the caller to get the native pointer. - * Instead, the signature should be something like: - * public void updateSchema(T schema, long version) - * ... that is: something that is a schema and that wraps a native object. * - * @param schemaNativePtr the pointer to a native schema object. + * @param schemaInfo the expected schema. * @param version the target version. */ - public void updateSchema(long schemaNativePtr, long version) { - nativeUpdateSchema(nativePtr, schemaNativePtr, version); + public void updateSchema(OsSchemaInfo schemaInfo, long version) { + nativeUpdateSchema(nativePtr, schemaInfo.getNativePtr(), version); } public void setAutoRefresh(boolean enabled) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index 81218ae82e..69bce7a75c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -31,9 +31,8 @@ import io.realm.Realm; import io.realm.RealmModel; -import io.realm.RealmObjectSchema; -import io.realm.RealmSchema; import io.realm.internal.ColumnInfo; +import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; @@ -61,9 +60,13 @@ public CompositeMediator(RealmProxyMediator... mediators) { } @Override - public RealmObjectSchema createRealmObjectSchema(Class clazz, RealmSchema schema) { - RealmProxyMediator mediator = getMediator(clazz); - return mediator.createRealmObjectSchema(clazz, schema); + public Map, OsObjectSchemaInfo> getExpectedObjectSchemaInfoMap() { + Map, OsObjectSchemaInfo> infoMap = + new HashMap, OsObjectSchemaInfo>(); + for (RealmProxyMediator mediator : mediators.values()) { + infoMap.putAll(mediator.getExpectedObjectSchemaInfoMap()); + } + return infoMap; } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index 3f4f6fa471..5bd26677d4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -31,9 +32,8 @@ import io.realm.Realm; import io.realm.RealmModel; -import io.realm.RealmObjectSchema; -import io.realm.RealmSchema; import io.realm.internal.ColumnInfo; +import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; @@ -71,14 +71,17 @@ public FilterableMediator(RealmProxyMediator originalMediator, Collection clazz, RealmSchema schema) { - checkSchemaHasClass(clazz); - return originalMediator.createRealmObjectSchema(clazz, schema); + public Map, OsObjectSchemaInfo> getExpectedObjectSchemaInfoMap() { + Map, OsObjectSchemaInfo> infoMap = + new HashMap, OsObjectSchemaInfo>(); + for (Map.Entry, OsObjectSchemaInfo> entry : + originalMediator.getExpectedObjectSchemaInfoMap().entrySet()) { + if (allowedClasses.contains(entry.getKey())) { + infoMap.put(entry.getKey(), entry.getValue()); + } + } + return infoMap; } @Override From 65ff02466549c72a8628cc6c3b31daa06b603bd7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 29 Jun 2017 18:12:18 +0800 Subject: [PATCH 0795/2110] Merge standard schema classes to their parents Since we changed the architecture, no need for this extra abstraction anymore. --- .../io/realm/LinkingObjectsDynamicTests.java | 2 +- .../java/io/realm/RealmObjectSchemaTests.java | 2 +- .../src/main/java/io/realm/BaseRealm.java | 2 +- .../main/java/io/realm/RealmObjectSchema.java | 498 +++++++++++- .../src/main/java/io/realm/RealmSchema.java | 189 ++++- .../io/realm/StandardRealmObjectSchema.java | 747 ------------------ .../java/io/realm/StandardRealmSchema.java | 264 ------- 7 files changed, 644 insertions(+), 1060 deletions(-) delete mode 100644 realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java delete mode 100644 realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index 1d231e4d23..39c5d346b2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -85,7 +85,7 @@ public void linkingObjects_classIsNull() throws Exception { object.linkingObjects(null, AllJavaTypes.FIELD_INT); fail(); } catch (IllegalArgumentException expected) { - assertEquals(StandardRealmSchema.EMPTY_STRING_MSG, expected.getMessage()); + assertEquals(RealmSchema.EMPTY_STRING_MSG, expected.getMessage()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index b5ac52a1d5..425fafa499 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -888,7 +888,7 @@ public void getFieldIndex() { RealmConfiguration emptyConfig = configFactory.createConfiguration("empty"); DynamicRealm dynamicRealm = DynamicRealm.getInstance(emptyConfig); dynamicRealm.beginTransaction(); - StandardRealmObjectSchema objectSchema = (StandardRealmObjectSchema) dynamicRealm.getSchema().create(className); + RealmObjectSchema objectSchema = dynamicRealm.getSchema().create(className); assertTrue(objectSchema.getFieldIndex(fieldName) < 0); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 383cf39c5c..8679a02d05 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -98,7 +98,7 @@ public void onSchemaVersionChanged(long currentVersion) { } } }, true); - this.schema = new StandardRealmSchema(this); + this.schema = new RealmSchema(this); } /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 106d97b4a1..fb6fe89e10 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -16,9 +16,16 @@ */ +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Map; import java.util.Set; import io.realm.annotations.Required; +import io.realm.internal.ColumnInfo; import io.realm.internal.Table; import io.realm.internal.fields.FieldDescriptor; @@ -29,23 +36,76 @@ * * @see io.realm.RealmMigration */ -public abstract class RealmObjectSchema { +public class RealmObjectSchema { + + private static final Map, FieldMetaData> SUPPORTED_SIMPLE_FIELDS; + + static { + Map, FieldMetaData> m = new HashMap<>(); + m.put(String.class, new FieldMetaData(RealmFieldType.STRING, true)); + m.put(short.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Short.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(int.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Integer.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(long.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Long.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(float.class, new FieldMetaData(RealmFieldType.FLOAT, false)); + m.put(Float.class, new FieldMetaData(RealmFieldType.FLOAT, true)); + m.put(double.class, new FieldMetaData(RealmFieldType.DOUBLE, false)); + m.put(Double.class, new FieldMetaData(RealmFieldType.DOUBLE, true)); + m.put(boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, false)); + m.put(Boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, true)); + m.put(byte.class, new FieldMetaData(RealmFieldType.INTEGER, false)); + m.put(Byte.class, new FieldMetaData(RealmFieldType.INTEGER, true)); + m.put(byte[].class, new FieldMetaData(RealmFieldType.BINARY, true)); + m.put(Date.class, new FieldMetaData(RealmFieldType.DATE, true)); + SUPPORTED_SIMPLE_FIELDS = Collections.unmodifiableMap(m); + } + + private static final Map, FieldMetaData> SUPPORTED_LINKED_FIELDS; + + static { + Map, FieldMetaData> m = new HashMap<>(); + m.put(RealmObject.class, new FieldMetaData(RealmFieldType.OBJECT, false)); + m.put(RealmList.class, new FieldMetaData(RealmFieldType.LIST, false)); + SUPPORTED_LINKED_FIELDS = Collections.unmodifiableMap(m); + } + private final RealmSchema schema; + private final BaseRealm realm; + private final ColumnInfo columnInfo; + private final Table table; + + /** + * Creates a dynamic schema object for a given Realm class. + * + * @param realm Realm holding the objects. + * @param table table representation of the Realm class + */ + RealmObjectSchema(BaseRealm realm, RealmSchema schema, Table table) { + this(realm, schema, table, new DynamicColumnIndices(table)); + } /** - * Create a schema. + * Creates a schema object for a given Realm class. * - * @param schema The parent for this schema: the schema to which this object belongs + * @param realm Realm holding the objects. + * @param table table representation of the Realm class + * @param columnInfo mapping between field names and column indexes for the given table */ - protected RealmObjectSchema(RealmSchema schema) { + RealmObjectSchema(BaseRealm realm, RealmSchema schema, Table table, ColumnInfo columnInfo) { this.schema = schema; + this.realm = realm; + this.table = table; + this.columnInfo = columnInfo; } /** * @deprecated {@link RealmObjectSchema} doesn't have to be released manually. */ @Deprecated - public abstract void close(); + public void close() { + } /** * Returns the name of the RealmObject class being represented by this schema. @@ -57,7 +117,9 @@ protected RealmObjectSchema(RealmSchema schema) { * * @return the name of the RealmObject class represented by this schema. */ - public abstract String getClassName(); + public String getClassName() { + return table.getClassName(); + } /** * Sets a new name for this RealmObject class. This is equivalent to renaming it. @@ -65,9 +127,38 @@ protected RealmObjectSchema(RealmSchema schema) { * @param className the new name for this class. * @throws IllegalArgumentException if className is {@code null} or an empty string, or its length exceeds 56 * characters. - * @see StandardRealmSchema#rename(String, String) + * @see RealmSchema#rename(String, String) */ - public abstract RealmObjectSchema setClassName(String className); + public RealmObjectSchema setClassName(String className) { + realm.checkNotInSync(); // renaming a table is not permitted + checkEmpty(className); + String internalTableName = Table.getTableNameForClass(className); + if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { + throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: \'" + className + "\' (" + Integer.toString(className.length()) + ")"); + } + if (realm.sharedRealm.hasTable(internalTableName)) { + throw new IllegalArgumentException("Class already exists: " + className); + } + // in case this table has a primary key, we need to transfer it after renaming the table. + String oldTableName = null; + String pkField = null; + if (table.hasPrimaryKey()) { + oldTableName = table.getName(); + pkField = getPrimaryKey(); + table.setPrimaryKey(null); + } + realm.sharedRealm.renameTable(table.getName(), internalTableName); + if (pkField != null && !pkField.isEmpty()) { + try { + table.setPrimaryKey(pkField); + } catch (Exception e) { + // revert the table name back when something goes wrong + realm.sharedRealm.renameTable(table.getName(), oldTableName); + throw e; + } + } + return this; + } /** * Adds a new simple field to the RealmObject class. The type must be one supported by Realm. See @@ -85,7 +176,34 @@ protected RealmObjectSchema(RealmSchema schema) { * @throws IllegalArgumentException if the type isn't supported, field name is illegal or a field with that name * already exists. */ - public abstract RealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes); + public RealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes) { + FieldMetaData metadata = SUPPORTED_SIMPLE_FIELDS.get(fieldType); + if (metadata == null) { + if (SUPPORTED_LINKED_FIELDS.containsKey(fieldType)) { + throw new IllegalArgumentException("Use addRealmObjectField() instead to add fields that link to other RealmObjects: " + fieldName); + } else { + throw new IllegalArgumentException(String.format(Locale.US, + "Realm doesn't support this field type: %s(%s)", + fieldName, fieldType)); + } + } + + checkNewFieldName(fieldName); + boolean nullable = metadata.defaultNullable; + if (containsAttribute(attributes, FieldAttribute.REQUIRED)) { + nullable = false; + } + + long columnIndex = table.addColumn(metadata.realmType, fieldName, nullable); + try { + addModifiers(fieldName, attributes); + } catch (Exception e) { + // Modifiers have been removed by the addModifiers method() + table.removeColumn(columnIndex); + throw e; + } + return this; + } /** * Adds a new field that references another {@link RealmObject}. @@ -95,7 +213,12 @@ protected RealmObjectSchema(RealmSchema schema) { * @return the updated schema. * @throws IllegalArgumentException if field name is illegal or a field with that name already exists. */ - public abstract RealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema); + public RealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema) { + checkLegalName(fieldName); + checkFieldNameIsAvailable(fieldName); + table.addColumnLink(RealmFieldType.OBJECT, fieldName, realm.sharedRealm.getTable(Table.getTableNameForClass(objectSchema.getClassName()))); + return this; + } /** * Adds a new field that references a {@link RealmList}. @@ -105,7 +228,12 @@ protected RealmObjectSchema(RealmSchema schema) { * @return the updated schema. * @throws IllegalArgumentException if the field name is illegal or a field with that name already exists. */ - public abstract RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema); + public RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema) { + checkLegalName(fieldName); + checkFieldNameIsAvailable(fieldName); + table.addColumnLink(RealmFieldType.LIST, fieldName, realm.sharedRealm.getTable(Table.getTableNameForClass(objectSchema.getClassName()))); + return this; + } /** * Removes a field from the class. @@ -114,7 +242,19 @@ protected RealmObjectSchema(RealmSchema schema) { * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist. */ - public abstract RealmObjectSchema removeField(String fieldName); + public RealmObjectSchema removeField(String fieldName) { + realm.checkNotInSync(); // destructive modification of a schema is not permitted + checkLegalName(fieldName); + if (!hasField(fieldName)) { + throw new IllegalStateException(fieldName + " does not exist."); + } + long columnIndex = getColumnIndex(fieldName); + if (table.getPrimaryKey() == columnIndex) { + table.setPrimaryKey(null); + } + table.removeColumn(columnIndex); + return this; + } /** * Renames a field from one name to another. @@ -124,7 +264,19 @@ protected RealmObjectSchema(RealmSchema schema) { * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist or if the new field name already exists. */ - public abstract RealmObjectSchema renameField(String currentFieldName, String newFieldName); + public RealmObjectSchema renameField(String currentFieldName, String newFieldName) { + realm.checkNotInSync(); // destructive modification of a schema is not permitted + checkLegalName(currentFieldName); + checkFieldExists(currentFieldName); + checkLegalName(newFieldName); + checkFieldNameIsAvailable(newFieldName); + long columnIndex = getColumnIndex(currentFieldName); + table.renameColumn(columnIndex, newFieldName); + + // ATTENTION: We don't need to re-set the PK table here since the column index won't be changed when renaming. + + return this; + } /** * Tests if the class has field defined with the given name. @@ -132,7 +284,9 @@ protected RealmObjectSchema(RealmSchema schema) { * @param fieldName field name to test. * @return {@code true} if the field exists, {@code false} otherwise. */ - public abstract boolean hasField(String fieldName); + public boolean hasField(String fieldName) { + return table.getColumnIndex(fieldName) != Table.NO_MATCH; + } /** * Adds an index to a given field. This is the equivalent of adding the {@link io.realm.annotations.Index} @@ -143,7 +297,16 @@ protected RealmObjectSchema(RealmSchema schema) { * @throws IllegalArgumentException if field name doesn't exist, the field cannot be indexed or it already has a * index defined. */ - public abstract RealmObjectSchema addIndex(String fieldName); + public RealmObjectSchema addIndex(String fieldName) { + checkLegalName(fieldName); + checkFieldExists(fieldName); + long columnIndex = getColumnIndex(fieldName); + if (table.hasSearchIndex(columnIndex)) { + throw new IllegalStateException(fieldName + " already has an index."); + } + table.addSearchIndex(columnIndex); + return this; + } /** * Checks if a given field has an index defined. @@ -153,7 +316,11 @@ protected RealmObjectSchema(RealmSchema schema) { * @throws IllegalArgumentException if field name doesn't exist. * @see io.realm.annotations.Index */ - public abstract boolean hasIndex(String fieldName); + public boolean hasIndex(String fieldName) { + checkLegalName(fieldName); + checkFieldExists(fieldName); + return table.hasSearchIndex(table.getColumnIndex(fieldName)); + } /** * Removes an index from a given field. This is the same as removing the {@code @Index} annotation on the field. @@ -162,7 +329,17 @@ protected RealmObjectSchema(RealmSchema schema) { * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist or the field doesn't have an index. */ - public abstract RealmObjectSchema removeIndex(String fieldName); + public RealmObjectSchema removeIndex(String fieldName) { + realm.checkNotInSync(); // Destructive modifications are not permitted. + checkLegalName(fieldName); + checkFieldExists(fieldName); + long columnIndex = getColumnIndex(fieldName); + if (!table.hasSearchIndex(columnIndex)) { + throw new IllegalStateException("Field is not indexed: " + fieldName); + } + table.removeSearchIndex(columnIndex); + return this; + } /** * Adds a primary key to a given field. This is the same as adding the {@link io.realm.annotations.PrimaryKey} @@ -174,7 +351,20 @@ protected RealmObjectSchema(RealmSchema schema) { * @throws IllegalArgumentException if field name doesn't exist, the field cannot be a primary key or it already * has a primary key defined. */ - public abstract RealmObjectSchema addPrimaryKey(String fieldName); + public RealmObjectSchema addPrimaryKey(String fieldName) { + checkLegalName(fieldName); + checkFieldExists(fieldName); + if (table.hasPrimaryKey()) { + throw new IllegalStateException("A primary key is already defined"); + } + table.setPrimaryKey(fieldName); + long columnIndex = getColumnIndex(fieldName); + if (!table.hasSearchIndex(columnIndex)) { + // No exception will be thrown since adding PrimaryKey implies the column has an index. + table.addSearchIndex(columnIndex); + } + return this; + } /** * Removes the primary key from this class. This is the same as removing the {@link io.realm.annotations.PrimaryKey} @@ -184,7 +374,18 @@ protected RealmObjectSchema(RealmSchema schema) { * @return the updated schema. * @throws IllegalArgumentException if the class doesn't have a primary key defined. */ - public abstract RealmObjectSchema removePrimaryKey(); + public RealmObjectSchema removePrimaryKey() { + realm.checkNotInSync(); // Destructive modifications are not permitted. + if (!table.hasPrimaryKey()) { + throw new IllegalStateException(getClassName() + " doesn't have a primary key."); + } + long columnIndex = table.getPrimaryKey(); + if (table.hasSearchIndex(columnIndex)) { + table.removeSearchIndex(columnIndex); + } + table.setPrimaryKey(""); + return this; + } /** * Sets a field to be required i.e., it is not allowed to hold {@code null} values. This is equivalent to switching @@ -197,7 +398,31 @@ protected RealmObjectSchema(RealmSchema schema) { * the field already have been set as required. * @see Required */ - public abstract RealmObjectSchema setRequired(String fieldName, boolean required); + public RealmObjectSchema setRequired(String fieldName, boolean required) { + long columnIndex = table.getColumnIndex(fieldName); + boolean currentColumnRequired = isRequired(fieldName); + RealmFieldType type = table.getColumnType(columnIndex); + + if (type == RealmFieldType.OBJECT) { + throw new IllegalArgumentException("Cannot modify the required state for RealmObject references: " + fieldName); + } + if (type == RealmFieldType.LIST) { + throw new IllegalArgumentException("Cannot modify the required state for RealmList references: " + fieldName); + } + if (required && currentColumnRequired) { + throw new IllegalStateException("Field is already required: " + fieldName); + } + if (!required && !currentColumnRequired) { + throw new IllegalStateException("Field is already nullable: " + fieldName); + } + + if (required) { + table.convertColumnToNotNullable(columnIndex); + } else { + table.convertColumnToNullable(columnIndex); + } + return this; + } /** * Sets a field to be nullable i.e., it should be able to hold {@code null} values. This is equivalent to switching @@ -208,7 +433,10 @@ protected RealmObjectSchema(RealmSchema schema) { * @return the updated schema. * @throws IllegalArgumentException if the field name doesn't exist, or cannot be set as nullable. */ - public abstract RealmObjectSchema setNullable(String fieldName, boolean nullable); + public RealmObjectSchema setNullable(String fieldName, boolean nullable) { + setRequired(fieldName, !nullable); + return this; + } /** * Checks if a given field is required i.e., it is not allowed to contain {@code null} values. @@ -218,7 +446,10 @@ protected RealmObjectSchema(RealmSchema schema) { * @throws IllegalArgumentException if field name doesn't exist. * @see #setRequired(String, boolean) */ - public abstract boolean isRequired(String fieldName); + public boolean isRequired(String fieldName) { + long columnIndex = getColumnIndex(fieldName); + return !table.isColumnNullable(columnIndex); + } /** * Checks if a given field is nullable i.e., it is allowed to contain {@code null} values. @@ -228,7 +459,10 @@ protected RealmObjectSchema(RealmSchema schema) { * @throws IllegalArgumentException if field name doesn't exist. * @see #setNullable(String, boolean) */ - public abstract boolean isNullable(String fieldName); + public boolean isNullable(String fieldName) { + long columnIndex = getColumnIndex(fieldName); + return table.isColumnNullable(columnIndex); + } /** * Checks if a given field is the primary key field. @@ -238,7 +472,10 @@ protected RealmObjectSchema(RealmSchema schema) { * @throws IllegalArgumentException if field name doesn't exist. * @see #addPrimaryKey(String) */ - public abstract boolean isPrimaryKey(String fieldName); + public boolean isPrimaryKey(String fieldName) { + long columnIndex = getColumnIndex(fieldName); + return columnIndex == table.getPrimaryKey(); + } /** * Checks if the class has a primary key defined. @@ -246,7 +483,9 @@ protected RealmObjectSchema(RealmSchema schema) { * @return {@code true} if a primary key is defined, {@code false} otherwise. * @see io.realm.annotations.PrimaryKey */ - public abstract boolean hasPrimaryKey(); + public boolean hasPrimaryKey() { + return table.hasPrimaryKey(); + } /** * Returns the name of the primary key field. @@ -254,14 +493,26 @@ protected RealmObjectSchema(RealmSchema schema) { * @return the name of the primary key field. * @throws IllegalStateException if the class doesn't have a primary key defined. */ - public abstract String getPrimaryKey(); + public String getPrimaryKey() { + if (!table.hasPrimaryKey()) { + throw new IllegalStateException(getClassName() + " doesn't have a primary key."); + } + return table.getColumnName(table.getPrimaryKey()); + } /** * Returns all fields in this class. * * @return a list of all the fields in this class. */ - public abstract Set getFieldNames(); + public Set getFieldNames() { + int columnCount = (int) table.getColumnCount(); + Set columnNames = new LinkedHashSet<>(columnCount); + for (int i = 0; i < columnCount; i++) { + columnNames.add(table.getColumnName(i)); + } + return columnNames; + } /** * Runs a transformation function on each RealmObject instance of the current class. The object will be represented @@ -269,14 +520,26 @@ protected RealmObjectSchema(RealmSchema schema) { * * @return this schema. */ - public abstract RealmObjectSchema transform(Function function); + public RealmObjectSchema transform(Function function) { + if (function != null) { + long size = table.size(); + for (long i = 0; i < size; i++) { + function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i))); + } + } + + return this; + } /** * Returns the type used by the underlying storage engine to represent this field. * * @return the underlying type used by Realm to represent this field. */ - public abstract RealmFieldType getFieldType(String fieldName); + public RealmFieldType getFieldType(String fieldName) { + long columnIndex = getColumnIndex(fieldName); + return table.getColumnType(columnIndex); + } /** * Get a parser for a field descriptor. @@ -289,13 +552,35 @@ protected final FieldDescriptor getColumnIndices(String fieldDescription, RealmF return FieldDescriptor.createStandardFieldDescriptor(getSchemaConnector(), getTable(), fieldDescription, validColumnTypes); } - abstract RealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required); + RealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { + long columnIndex = table.addColumn(type, name, (required) ? Table.NOT_NULLABLE : Table.NULLABLE); - abstract RealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linkedTo); + if (indexed) { table.addSearchIndex(columnIndex); } - abstract long getAndCheckFieldIndex(String fieldName); + if (primary) { table.setPrimaryKey(name); } - abstract Table getTable(); + return this; + } + + RealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linkedTo) { + table.addColumnLink( + type, + name, + realm.getSharedRealm().getTable(Table.getTableNameForClass(linkedTo.getClassName()))); + return this; + } + + long getAndCheckFieldIndex(String fieldName) { + long index = columnInfo.getColumnIndex(fieldName); + if (index < 0) { + throw new IllegalArgumentException("Field does not exist: " + fieldName); + } + return index; + } + + Table getTable() { + return table; + } private SchemaConnector getSchemaConnector() { return new SchemaConnector(schema); @@ -309,4 +594,151 @@ private SchemaConnector getSchemaConnector() { public interface Function { void apply(DynamicRealmObject obj); } + + /** + * Returns the column index in the underlying table for the given field name. + * FOR TESTING USE ONLY! + * + * @param fieldName field name to find index for. + * @return column index or -1 if it doesn't exists. + */ + //@VisibleForTesting(otherwise = VisibleForTesting.NONE) + long getFieldIndex(String fieldName) { + return columnInfo.getColumnIndex(fieldName); + } + + // Invariant: Field was just added. This method is responsible for cleaning up attributes if it fails. + private void addModifiers(String fieldName, FieldAttribute[] attributes) { + boolean indexAdded = false; + try { + if (attributes != null && attributes.length > 0) { + if (containsAttribute(attributes, FieldAttribute.INDEXED)) { + addIndex(fieldName); + indexAdded = true; + } + + if (containsAttribute(attributes, FieldAttribute.PRIMARY_KEY)) { + // Note : adding primary key implies application of FieldAttribute.INDEXED attribute. + addPrimaryKey(fieldName); + indexAdded = true; + } + + // REQUIRED is being handled when adding the column using addField through the nullable parameter. + } + } catch (Exception e) { + // If something went wrong, revert all attributes. + long columnIndex = getColumnIndex(fieldName); + if (indexAdded) { + table.removeSearchIndex(columnIndex); + } + throw (RuntimeException) e; + } + } + + private boolean containsAttribute(FieldAttribute[] attributeList, FieldAttribute attribute) { + if (attributeList == null || attributeList.length == 0) { + return false; + } + for (FieldAttribute anAttributeList : attributeList) { + if (anAttributeList == attribute) { + return true; + } + } + return false; + } + + private void checkNewFieldName(String fieldName) { + checkLegalName(fieldName); + checkFieldNameIsAvailable(fieldName); + } + + private void checkLegalName(String fieldName) { + if (fieldName == null || fieldName.isEmpty()) { + throw new IllegalArgumentException("Field name can not be null or empty"); + } + if (fieldName.contains(".")) { + throw new IllegalArgumentException("Field name can not contain '.'"); + } + } + + private void checkFieldNameIsAvailable(String fieldName) { + if (table.getColumnIndex(fieldName) != Table.NO_MATCH) { + throw new IllegalArgumentException("Field already exists in '" + getClassName() + "': " + fieldName); + } + } + + private void checkFieldExists(String fieldName) { + if (table.getColumnIndex(fieldName) == Table.NO_MATCH) { + throw new IllegalArgumentException("Field name doesn't exist on object '" + getClassName() + "': " + fieldName); + } + } + + private long getColumnIndex(String fieldName) { + long columnIndex = table.getColumnIndex(fieldName); + if (columnIndex == -1) { + throw new IllegalArgumentException( + String.format(Locale.US, + "Field name '%s' does not exist on schema for '%s'", + fieldName, getClassName() + )); + } + return columnIndex; + } + + private void checkEmpty(String str) { + if (str == null || str.isEmpty()) { + throw new IllegalArgumentException("Null or empty class names are not allowed"); + } + } + + private static final class DynamicColumnIndices extends ColumnInfo { + private final Table table; + + DynamicColumnIndices(Table table) { + super(null, false); + this.table = table; + } + + @Override + public long getColumnIndex(String columnName) { + return table.getColumnIndex(columnName); + } + + @Override + public RealmFieldType getColumnType(String columnName) { + throw new UnsupportedOperationException("DynamicColumnIndices do not support 'getColumnType'"); + } + + @Override + public String getLinkedTable(String columnName) { + throw new UnsupportedOperationException("DynamicColumnIndices do not support 'getLinkedTable'"); + } + + @Override + public void copyFrom(ColumnInfo src) { + throw new UnsupportedOperationException("DynamicColumnIndices cannot be copied"); + } + + @Override + protected ColumnInfo copy(boolean immutable) { + throw new UnsupportedOperationException("DynamicColumnIndices cannot be copied"); + } + + + @Override + protected void copy(ColumnInfo src, ColumnInfo dst) { + throw new UnsupportedOperationException("DynamicColumnIndices cannot copy"); + } + } + + // Tuple containing data about each supported Java type. + private static final class FieldMetaData { + final RealmFieldType realmType; + final boolean defaultNullable; + + FieldMetaData(RealmFieldType realmType, boolean defaultNullable) { + this.realmType = realmType; + this.defaultNullable = defaultNullable; + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 17049602d4..d7096b3fbf 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -16,12 +16,15 @@ package io.realm; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; import io.realm.internal.Table; +import io.realm.internal.Util; import io.realm.internal.util.Pair; @@ -33,14 +36,35 @@ * * @see RealmMigration */ -public abstract class RealmSchema { - private ColumnIndices columnIndices; // Cached field look up +public class RealmSchema { + static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; + + // Caches Dynamic Class objects given as Strings to Realm Tables + private final Map dynamicClassToTable = new HashMap<>(); + // Caches Class objects (both model classes and proxy classes) to Realm Tables + private final Map, Table> classToTable = new HashMap<>(); + // Caches Class objects (both model classes and proxy classes) to their Schema object + private final Map, RealmObjectSchema> classToSchema = new HashMap<>(); + // Caches Class Strings to their Schema object + private final Map dynamicClassToSchema = new HashMap<>(); + + private final BaseRealm realm; + // Cached field look up + private ColumnIndices columnIndices; + + /** + * Creates a wrapper to easily manipulate the current schema of a Realm. + */ + RealmSchema(BaseRealm realm) { + this.realm = realm; + } /** * @deprecated {@link RealmSchema} doesn't have to be released manually. */ @Deprecated - public abstract void close(); + public void close() { + } /** * Returns the Realm schema for a given class. @@ -48,14 +72,32 @@ public abstract class RealmSchema { * @param className name of the class * @return schema object for that class or {@code null} if the class doesn't exists. */ - public abstract RealmObjectSchema get(String className); + public RealmObjectSchema get(String className) { + checkEmpty(className, EMPTY_STRING_MSG); + + String internalClassName = Table.getTableNameForClass(className); + if (!realm.getSharedRealm().hasTable(internalClassName)) { return null; } + Table table = realm.getSharedRealm().getTable(internalClassName); + return new RealmObjectSchema(realm, this, table); + } /** * Returns the {@link RealmObjectSchema}s for all RealmObject classes that can be saved in this Realm. * * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ - public abstract Set getAll(); + public Set getAll() { + int tableCount = (int) realm.getSharedRealm().size(); + Set schemas = new LinkedHashSet<>(tableCount); + for (int i = 0; i < tableCount; i++) { + String tableName = realm.getSharedRealm().getTableName(i); + if (!Table.isModelTable(tableName)) { + continue; + } + schemas.add(new RealmObjectSchema(realm, this, realm.getSharedRealm().getTable(tableName))); + } + return schemas; + } /** * Adds a new class to the Realm. @@ -63,7 +105,16 @@ public abstract class RealmSchema { * @param className name of the class. * @return a Realm schema object for that class. */ - public abstract RealmObjectSchema create(String className); + public RealmObjectSchema create(String className) { + // Adding a class is always permitted. + checkEmpty(className, EMPTY_STRING_MSG); + + String internalTableName = Table.getTableNameForClass(className); + if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { + throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: " + className.length()); + } + return new RealmObjectSchema(realm, this, realm.getSharedRealm().createTable(internalTableName)); + } /** * Removes a class from the Realm. All data will be removed. Removing a class while other classes point @@ -71,7 +122,17 @@ public abstract class RealmSchema { * * @param className name of the class to remove. */ - public abstract void remove(String className); + public void remove(String className) { + realm.checkNotInSync(); // Destructive modifications are not permitted. + checkEmpty(className, EMPTY_STRING_MSG); + String internalTableName = Table.getTableNameForClass(className); + checkHasTable(className, "Cannot remove class because it is not in this Realm: " + className); + Table table = getTable(className); + if (table.hasPrimaryKey()) { + table.setPrimaryKey(null); + } + realm.getSharedRealm().removeTable(internalTableName); + } /** * Renames a class already in the Realm. @@ -80,7 +141,35 @@ public abstract class RealmSchema { * @param newClassName new class name. * @return a schema object for renamed class. */ - public abstract RealmObjectSchema rename(String oldClassName, String newClassName); + public RealmObjectSchema rename(String oldClassName, String newClassName) { + realm.checkNotInSync(); // Destructive modifications are not permitted. + checkEmpty(oldClassName, "Class names cannot be empty or null"); + checkEmpty(newClassName, "Class names cannot be empty or null"); + String oldInternalName = Table.getTableNameForClass(oldClassName); + String newInternalName = Table.getTableNameForClass(newClassName); + checkHasTable(oldClassName, "Cannot rename class because it doesn't exist in this Realm: " + oldClassName); + if (realm.getSharedRealm().hasTable(newInternalName)) { + throw new IllegalArgumentException(oldClassName + " cannot be renamed because the new class already exists: " + newClassName); + } + + // Checks if there is a primary key defined for the old class. + Table oldTable = getTable(oldClassName); + String pkField = null; + if (oldTable.hasPrimaryKey()) { + pkField = oldTable.getColumnName(oldTable.getPrimaryKey()); + oldTable.setPrimaryKey(null); + } + + realm.getSharedRealm().renameTable(oldInternalName, newInternalName); + Table table = realm.getSharedRealm().getTable(newInternalName); + + // Sets the primary key for the new class if necessary. + if (pkField != null) { + table.setPrimaryKey(pkField); + } + + return new RealmObjectSchema(realm, this, table); + } /** * Checks if a given class already exists in the schema. @@ -88,15 +177,89 @@ public abstract class RealmSchema { * @param className class name to check. * @return {@code true} if the class already exists. {@code false} otherwise. */ - public abstract boolean contains(String className); + public boolean contains(String className) { + return realm.getSharedRealm().hasTable(Table.getTableNameForClass(className)); + } + + private void checkEmpty(String str, String error) { + if (str == null || str.isEmpty()) { + throw new IllegalArgumentException(error); + } + } + + private void checkHasTable(String className, String errorMsg) { + String internalTableName = Table.getTableNameForClass(className); + if (!realm.getSharedRealm().hasTable(internalTableName)) { + throw new IllegalArgumentException(errorMsg); + } + } + + Table getTable(String className) { + String tableName = Table.getTableNameForClass(className); + Table table = dynamicClassToTable.get(tableName); + if (table != null) { return table; } + + table = realm.getSharedRealm().getTable(tableName); + dynamicClassToTable.put(tableName, table); + + return table; + } + + Table getTable(Class clazz) { + Table table = classToTable.get(clazz); + if (table != null) { return table; } - abstract Table getTable(Class clazz); + Class originalClass = Util.getOriginalModelClass(clazz); + if (isProxyClass(originalClass, clazz)) { + // If passed 'clazz' is the proxy, try again with model class. + table = classToTable.get(originalClass); + } + if (table == null) { + table = realm.getSharedRealm().getTable(realm.getConfiguration().getSchemaMediator().getTableName(originalClass)); + classToTable.put(originalClass, table); + } + if (isProxyClass(originalClass, clazz)) { + // 'clazz' is the proxy class for 'originalClass'. + classToTable.put(clazz, table); + } + + return table; + } + + RealmObjectSchema getSchemaForClass(Class clazz) { + RealmObjectSchema classSchema = classToSchema.get(clazz); + if (classSchema != null) { return classSchema; } - abstract Table getTable(String className); + Class originalClass = Util.getOriginalModelClass(clazz); + if (isProxyClass(originalClass, clazz)) { + // If passed 'clazz' is the proxy, try again with model class. + classSchema = classToSchema.get(originalClass); + } + if (classSchema == null) { + Table table = getTable(clazz); + classSchema = new RealmObjectSchema(realm, this, table, getColumnInfo(originalClass)); + classToSchema.put(originalClass, classSchema); + } + if (isProxyClass(originalClass, clazz)) { + // 'clazz' is the proxy class for 'originalClass'. + classToSchema.put(clazz, classSchema); + } - abstract RealmObjectSchema getSchemaForClass(Class clazz); + return classSchema; + } - abstract RealmObjectSchema getSchemaForClass(String className); + RealmObjectSchema getSchemaForClass(String className) { + String tableName = Table.getTableNameForClass(className); + RealmObjectSchema dynamicSchema = dynamicClassToSchema.get(tableName); + if (dynamicSchema == null) { + if (!realm.getSharedRealm().hasTable(tableName)) { + throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); + } + dynamicSchema = new RealmObjectSchema(realm, this, realm.getSharedRealm().getTable(tableName)); + dynamicClassToSchema.put(tableName, dynamicSchema); + } + return dynamicSchema; + } /** * Set the column index cache for this schema. diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java deleted file mode 100644 index e22f77351e..0000000000 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmObjectSchema.java +++ /dev/null @@ -1,747 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import io.realm.annotations.Required; -import io.realm.internal.ColumnInfo; -import io.realm.internal.Table; - - -class StandardRealmObjectSchema extends RealmObjectSchema { - - private static final Map, FieldMetaData> SUPPORTED_SIMPLE_FIELDS; - - static { - Map, FieldMetaData> m = new HashMap<>(); - m.put(String.class, new FieldMetaData(RealmFieldType.STRING, true)); - m.put(short.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Short.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(int.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Integer.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(long.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Long.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(float.class, new FieldMetaData(RealmFieldType.FLOAT, false)); - m.put(Float.class, new FieldMetaData(RealmFieldType.FLOAT, true)); - m.put(double.class, new FieldMetaData(RealmFieldType.DOUBLE, false)); - m.put(Double.class, new FieldMetaData(RealmFieldType.DOUBLE, true)); - m.put(boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, false)); - m.put(Boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, true)); - m.put(byte.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Byte.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(byte[].class, new FieldMetaData(RealmFieldType.BINARY, true)); - m.put(Date.class, new FieldMetaData(RealmFieldType.DATE, true)); - SUPPORTED_SIMPLE_FIELDS = Collections.unmodifiableMap(m); - } - - private static final Map, FieldMetaData> SUPPORTED_LINKED_FIELDS; - - static { - Map, FieldMetaData> m = new HashMap<>(); - m.put(RealmObject.class, new FieldMetaData(RealmFieldType.OBJECT, false)); - m.put(RealmList.class, new FieldMetaData(RealmFieldType.LIST, false)); - SUPPORTED_LINKED_FIELDS = Collections.unmodifiableMap(m); - } - - private final BaseRealm realm; - private final ColumnInfo columnInfo; - private final Table table; - - /** - * Creates a dynamic schema object for a given Realm class. - * - * @param realm Realm holding the objects. - * @param table table representation of the Realm class - */ - StandardRealmObjectSchema(BaseRealm realm, StandardRealmSchema schema, Table table) { - this(realm, schema, table, new StandardRealmObjectSchema.DynamicColumnIndices(table)); - } - - /** - * Creates a schema object for a given Realm class. - * - * @param realm Realm holding the objects. - * @param table table representation of the Realm class - * @param columnInfo mapping between field names and column indexes for the given table - */ - StandardRealmObjectSchema(BaseRealm realm, StandardRealmSchema schema, Table table, ColumnInfo columnInfo) { - super(schema); - this.realm = realm; - this.table = table; - this.columnInfo = columnInfo; - } - - /** - * There are no resources here that need closing. - */ - @Override - public void close() { } - - /** - * Returns the name of the RealmObject class being represented by this schema. - *

            - *

              - *
            • When using a typed {@link Realm} this name is the same as the {@link RealmObject} class.
            • - *
            • When using a {@link DynamicRealm} this is the name used in all API methods requiring a class name.
            • - *
            - * - * @return the name of the RealmObject class represented by this schema. - */ - @Override - public String getClassName() { - return table.getClassName(); - } - - /** - * Sets a new name for this RealmObject class. This is equivalent to renaming it. When - * {@link StandardRealmObjectSchema#table} has a primary key, this will transfer the primary key for the new class name. - * - * @param className the new name for this class. - * @throws IllegalArgumentException if className is {@code null} or an empty string, or its length exceeds 56 - * characters. - * @see StandardRealmSchema#rename(String, String) - */ - @Override - public StandardRealmObjectSchema setClassName(String className) { - realm.checkNotInSync(); // renaming a table is not permitted - checkEmpty(className); - String internalTableName = Table.getTableNameForClass(className); - if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { - throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: \'" + className + "\' (" + Integer.toString(className.length()) + ")"); - } - if (realm.sharedRealm.hasTable(internalTableName)) { - throw new IllegalArgumentException("Class already exists: " + className); - } - // in case this table has a primary key, we need to transfer it after renaming the table. - String oldTableName = null; - String pkField = null; - if (table.hasPrimaryKey()) { - oldTableName = table.getName(); - pkField = getPrimaryKey(); - table.setPrimaryKey(null); - } - realm.sharedRealm.renameTable(table.getName(), internalTableName); - if (pkField != null && !pkField.isEmpty()) { - try { - table.setPrimaryKey(pkField); - } catch (Exception e) { - // revert the table name back when something goes wrong - realm.sharedRealm.renameTable(table.getName(), oldTableName); - throw e; - } - } - return this; - } - - /** - * Adds a new simple field to the RealmObject class. The type must be one supported by Realm. See - * {@link RealmObject} for the list of supported types. If the field should allow {@code null} values use the boxed - * type instead e.g., {@code Integer.class} instead of {@code int.class}. - *

            - * To add fields that reference other RealmObjects or RealmLists use - * {@link #addRealmObjectField(String, RealmObjectSchema)} or {@link #addRealmListField(String, RealmObjectSchema)} - * instead. - * - * @param fieldName name of the field to add. - * @param fieldType type of field to add. See {@link RealmObject} for the full list. - * @param attributes set of attributes for this field. - * @return the updated schema. - * @throws IllegalArgumentException if the type isn't supported, field name is illegal or a field with that name - * already exists. - */ - @Override - public StandardRealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes) { - FieldMetaData metadata = SUPPORTED_SIMPLE_FIELDS.get(fieldType); - if (metadata == null) { - if (SUPPORTED_LINKED_FIELDS.containsKey(fieldType)) { - throw new IllegalArgumentException("Use addRealmObjectField() instead to add fields that link to other RealmObjects: " + fieldName); - } else { - throw new IllegalArgumentException(String.format(Locale.US, - "Realm doesn't support this field type: %s(%s)", - fieldName, fieldType)); - } - } - - checkNewFieldName(fieldName); - boolean nullable = metadata.defaultNullable; - if (containsAttribute(attributes, FieldAttribute.REQUIRED)) { - nullable = false; - } - - long columnIndex = table.addColumn(metadata.realmType, fieldName, nullable); - try { - addModifiers(fieldName, attributes); - } catch (Exception e) { - // Modifiers have been removed by the addModifiers method() - table.removeColumn(columnIndex); - throw e; - } - return this; - } - - /** - * Adds a new field that references another {@link RealmObject}. - * - * @param fieldName name of the field to add. - * @param objectSchema schema for the Realm type being referenced. - * @return the updated schema. - * @throws IllegalArgumentException if field name is illegal or a field with that name already exists. - */ - @Override - public StandardRealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema) { - checkLegalName(fieldName); - checkFieldNameIsAvailable(fieldName); - table.addColumnLink(RealmFieldType.OBJECT, fieldName, realm.sharedRealm.getTable(Table.getTableNameForClass(objectSchema.getClassName()))); - return this; - } - - /** - * Adds a new field that references a {@link RealmList}. - * - * @param fieldName name of the field to add. - * @param objectSchema schema for the Realm type being referenced. - * @return the updated schema. - * @throws IllegalArgumentException if the field name is illegal or a field with that name already exists. - */ - @Override - public StandardRealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema) { - checkLegalName(fieldName); - checkFieldNameIsAvailable(fieldName); - table.addColumnLink(RealmFieldType.LIST, fieldName, realm.sharedRealm.getTable(Table.getTableNameForClass(objectSchema.getClassName()))); - return this; - } - - /** - * Removes a field from the class. - * - * @param fieldName field name to remove. - * @return the updated schema. - * @throws IllegalArgumentException if field name doesn't exist. - */ - @Override - public StandardRealmObjectSchema removeField(String fieldName) { - realm.checkNotInSync(); // destructive modification of a schema is not permitted - checkLegalName(fieldName); - if (!hasField(fieldName)) { - throw new IllegalStateException(fieldName + " does not exist."); - } - long columnIndex = getColumnIndex(fieldName); - if (table.getPrimaryKey() == columnIndex) { - table.setPrimaryKey(null); - } - table.removeColumn(columnIndex); - return this; - } - - /** - * Renames a field from one name to another. - * - * @param currentFieldName field name to rename. - * @param newFieldName the new field name. - * @return the updated schema. - * @throws IllegalArgumentException if field name doesn't exist or if the new field name already exists. - */ - @Override - public StandardRealmObjectSchema renameField(String currentFieldName, String newFieldName) { - realm.checkNotInSync(); // destructive modification of a schema is not permitted - checkLegalName(currentFieldName); - checkFieldExists(currentFieldName); - checkLegalName(newFieldName); - checkFieldNameIsAvailable(newFieldName); - long columnIndex = getColumnIndex(currentFieldName); - table.renameColumn(columnIndex, newFieldName); - - // ATTENTION: We don't need to re-set the PK table here since the column index won't be changed when renaming. - - return this; - } - - /** - * Tests if the class has field defined with the given name. - * - * @param fieldName field name to test. - * @return {@code true} if the field exists, {@code false} otherwise. - */ - @Override - public boolean hasField(String fieldName) { - return table.getColumnIndex(fieldName) != Table.NO_MATCH; - } - - /** - * Adds an index to a given field. This is the equivalent of adding the {@link io.realm.annotations.Index} - * annotation on the field. - * - * @param fieldName field to add index to. - * @return the updated schema. - * @throws IllegalArgumentException if field name doesn't exist, the field cannot be indexed or it already has a - * index defined. - */ - @Override - public StandardRealmObjectSchema addIndex(String fieldName) { - checkLegalName(fieldName); - checkFieldExists(fieldName); - long columnIndex = getColumnIndex(fieldName); - if (table.hasSearchIndex(columnIndex)) { - throw new IllegalStateException(fieldName + " already has an index."); - } - table.addSearchIndex(columnIndex); - return this; - } - - /** - * Checks if a given field has an index defined. - * - * @param fieldName existing field name to check. - * @return {@code true} if field is indexed, {@code false} otherwise. - * @throws IllegalArgumentException if field name doesn't exist. - * @see io.realm.annotations.Index - */ - @Override - public boolean hasIndex(String fieldName) { - checkLegalName(fieldName); - checkFieldExists(fieldName); - return table.hasSearchIndex(table.getColumnIndex(fieldName)); - } - - - /** - * Removes an index from a given field. This is the same as removing the {@code @Index} annotation on the field. - * - * @param fieldName field to remove index from. - * @return the updated schema. - * @throws IllegalArgumentException if field name doesn't exist or the field doesn't have an index. - */ - @Override - public StandardRealmObjectSchema removeIndex(String fieldName) { - realm.checkNotInSync(); // Destructive modifications are not permitted. - checkLegalName(fieldName); - checkFieldExists(fieldName); - long columnIndex = getColumnIndex(fieldName); - if (!table.hasSearchIndex(columnIndex)) { - throw new IllegalStateException("Field is not indexed: " + fieldName); - } - table.removeSearchIndex(columnIndex); - return this; - } - - /** - * Adds a primary key to a given field. This is the same as adding the {@link io.realm.annotations.PrimaryKey} - * annotation on the field. Further, this implicitly adds {@link io.realm.annotations.Index} annotation to the field - * as well. - * - * @param fieldName field to set as primary key. - * @return the updated schema. - * @throws IllegalArgumentException if field name doesn't exist, the field cannot be a primary key or it already - * has a primary key defined. - */ - @Override - public StandardRealmObjectSchema addPrimaryKey(String fieldName) { - checkLegalName(fieldName); - checkFieldExists(fieldName); - if (table.hasPrimaryKey()) { - throw new IllegalStateException("A primary key is already defined"); - } - table.setPrimaryKey(fieldName); - long columnIndex = getColumnIndex(fieldName); - if (!table.hasSearchIndex(columnIndex)) { - // No exception will be thrown since adding PrimaryKey implies the column has an index. - table.addSearchIndex(columnIndex); - } - return this; - } - - /** - * Removes the primary key from this class. This is the same as removing the {@link io.realm.annotations.PrimaryKey} - * annotation from the class. Further, this implicitly removes {@link io.realm.annotations.Index} annotation from - * the field as well. - * - * @return the updated schema. - * @throws IllegalArgumentException if the class doesn't have a primary key defined. - */ - @Override - public StandardRealmObjectSchema removePrimaryKey() { - realm.checkNotInSync(); // Destructive modifications are not permitted. - if (!table.hasPrimaryKey()) { - throw new IllegalStateException(getClassName() + " doesn't have a primary key."); - } - long columnIndex = table.getPrimaryKey(); - if (table.hasSearchIndex(columnIndex)) { - table.removeSearchIndex(columnIndex); - } - table.setPrimaryKey(""); - return this; - } - - /** - * Sets a field to be required i.e., it is not allowed to hold {@code null} values. This is equivalent to switching - * between boxed types and their primitive variant e.g., {@code Integer} to {@code int}. - * - * @param fieldName name of field in the class. - * @param required {@code true} if field should be required, {@code false} otherwise. - * @return the updated schema. - * @throws IllegalArgumentException if the field name doesn't exist, cannot have the {@link Required} annotation or - * the field already have been set as required. - * @see Required - */ - @Override - public StandardRealmObjectSchema setRequired(String fieldName, boolean required) { - long columnIndex = table.getColumnIndex(fieldName); - boolean currentColumnRequired = isRequired(fieldName); - RealmFieldType type = table.getColumnType(columnIndex); - - if (type == RealmFieldType.OBJECT) { - throw new IllegalArgumentException("Cannot modify the required state for RealmObject references: " + fieldName); - } - if (type == RealmFieldType.LIST) { - throw new IllegalArgumentException("Cannot modify the required state for RealmList references: " + fieldName); - } - if (required && currentColumnRequired) { - throw new IllegalStateException("Field is already required: " + fieldName); - } - if (!required && !currentColumnRequired) { - throw new IllegalStateException("Field is already nullable: " + fieldName); - } - - if (required) { - table.convertColumnToNotNullable(columnIndex); - } else { - table.convertColumnToNullable(columnIndex); - } - return this; - } - - /** - * Sets a field to be nullable i.e., it should be able to hold {@code null} values. This is equivalent to switching - * between primitive types and their boxed variant e.g., {@code int} to {@code Integer}. - * - * @param fieldName name of field in the class. - * @param nullable {@code true} if field should be nullable, {@code false} otherwise. - * @return the updated schema. - * @throws IllegalArgumentException if the field name doesn't exist, or cannot be set as nullable. - */ - @Override - public StandardRealmObjectSchema setNullable(String fieldName, boolean nullable) { - setRequired(fieldName, !nullable); - return this; - } - - /** - * Checks if a given field is required i.e., it is not allowed to contain {@code null} values. - * - * @param fieldName field to check. - * @return {@code true} if it is required, {@code false} otherwise. - * @throws IllegalArgumentException if field name doesn't exist. - * @see #setRequired(String, boolean) - */ - @Override - public boolean isRequired(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return !table.isColumnNullable(columnIndex); - } - - /** - * Checks if a given field is nullable i.e., it is allowed to contain {@code null} values. - * - * @param fieldName field to check. - * @return {@code true} if it is required, {@code false} otherwise. - * @throws IllegalArgumentException if field name doesn't exist. - * @see #setNullable(String, boolean) - */ - @Override - public boolean isNullable(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return table.isColumnNullable(columnIndex); - } - - /** - * Checks if a given field is the primary key field. - * - * @param fieldName field to check. - * @return {@code true} if it is the primary key field, {@code false} otherwise. - * @throws IllegalArgumentException if field name doesn't exist. - * @see #addPrimaryKey(String) - */ - @Override - public boolean isPrimaryKey(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return columnIndex == table.getPrimaryKey(); - } - - /** - * Checks if the class has a primary key defined. - * - * @return {@code true} if a primary key is defined, {@code false} otherwise. - * @see io.realm.annotations.PrimaryKey - */ - @Override - public boolean hasPrimaryKey() { - return table.hasPrimaryKey(); - } - - /** - * Returns the name of the primary key field. - * - * @return the name of the primary key field. - * @throws IllegalStateException if the class doesn't have a primary key defined. - */ - @Override - public String getPrimaryKey() { - if (!table.hasPrimaryKey()) { - throw new IllegalStateException(getClassName() + " doesn't have a primary key."); - } - return table.getColumnName(table.getPrimaryKey()); - } - - /** - * Returns all fields in this class. - * - * @return a list of all the fields in this class. - */ - @Override - public Set getFieldNames() { - int columnCount = (int) table.getColumnCount(); - Set columnNames = new LinkedHashSet<>(columnCount); - for (int i = 0; i < columnCount; i++) { - columnNames.add(table.getColumnName(i)); - } - return columnNames; - } - - /** - * Runs a transformation function on each RealmObject instance of the current class. The object will be represented - * as a {@link DynamicRealmObject}. - * - * @return this schema. - */ - @Override - public StandardRealmObjectSchema transform(Function function) { - if (function != null) { - long size = table.size(); - for (long i = 0; i < size; i++) { - function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i))); - } - } - - return this; - } - - /** - * Returns the type used by the underlying storage engine to represent this field. - * - * @return the underlying type used by Realm to represent this field. - */ - @Override - public RealmFieldType getFieldType(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return table.getColumnType(columnIndex); - } - - @Override - Table getTable() { - return table; - } - - @Override - StandardRealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { - long columnIndex = table.addColumn(type, name, (required) ? Table.NOT_NULLABLE : Table.NULLABLE); - - if (indexed) { table.addSearchIndex(columnIndex); } - - if (primary) { table.setPrimaryKey(name); } - - return this; - } - - @Override - StandardRealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linkedTo) { - table.addColumnLink( - type, - name, - realm.getSharedRealm().getTable(Table.getTableNameForClass(linkedTo.getClassName()))); - return this; - } - - /** - * Returns the column index in the underlying table for the given field name. - * - * @param fieldName field name to find index for. - * @return column index. - * @throws IllegalArgumentException if the field does not exists. - */ - @Override - long getAndCheckFieldIndex(String fieldName) { - long index = columnInfo.getColumnIndex(fieldName); - if (index < 0) { - throw new IllegalArgumentException("Field does not exist: " + fieldName); - } - return index; - } - - /** - * Returns the column index in the underlying table for the given field name. - * FOR TESTING USE ONLY! - * - * @param fieldName field name to find index for. - * @return column index or -1 if it doesn't exists. - */ - //@VisibleForTesting(otherwise = VisibleForTesting.NONE) - long getFieldIndex(String fieldName) { - return columnInfo.getColumnIndex(fieldName); - } - - // Invariant: Field was just added. This method is responsible for cleaning up attributes if it fails. - private void addModifiers(String fieldName, FieldAttribute[] attributes) { - boolean indexAdded = false; - try { - if (attributes != null && attributes.length > 0) { - if (containsAttribute(attributes, FieldAttribute.INDEXED)) { - addIndex(fieldName); - indexAdded = true; - } - - if (containsAttribute(attributes, FieldAttribute.PRIMARY_KEY)) { - // Note : adding primary key implies application of FieldAttribute.INDEXED attribute. - addPrimaryKey(fieldName); - indexAdded = true; - } - - // REQUIRED is being handled when adding the column using addField through the nullable parameter. - } - } catch (Exception e) { - // If something went wrong, revert all attributes. - long columnIndex = getColumnIndex(fieldName); - if (indexAdded) { - table.removeSearchIndex(columnIndex); - } - throw (RuntimeException) e; - } - } - - private boolean containsAttribute(FieldAttribute[] attributeList, FieldAttribute attribute) { - if (attributeList == null || attributeList.length == 0) { - return false; - } - for (int i = 0; i < attributeList.length; i++) { - if (attributeList[i] == attribute) { - return true; - } - } - return false; - } - - private void checkNewFieldName(String fieldName) { - checkLegalName(fieldName); - checkFieldNameIsAvailable(fieldName); - } - - private void checkLegalName(String fieldName) { - if (fieldName == null || fieldName.isEmpty()) { - throw new IllegalArgumentException("Field name can not be null or empty"); - } - if (fieldName.contains(".")) { - throw new IllegalArgumentException("Field name can not contain '.'"); - } - } - - private void checkFieldNameIsAvailable(String fieldName) { - if (table.getColumnIndex(fieldName) != Table.NO_MATCH) { - throw new IllegalArgumentException("Field already exists in '" + getClassName() + "': " + fieldName); - } - } - - private void checkFieldExists(String fieldName) { - if (table.getColumnIndex(fieldName) == Table.NO_MATCH) { - throw new IllegalArgumentException("Field name doesn't exist on object '" + getClassName() + "': " + fieldName); - } - } - - private long getColumnIndex(String fieldName) { - long columnIndex = table.getColumnIndex(fieldName); - if (columnIndex == -1) { - throw new IllegalArgumentException( - String.format(Locale.US, - "Field name '%s' does not exist on schema for '%s'", - fieldName, getClassName() - )); - } - return columnIndex; - } - - private void checkEmpty(String str) { - if (str == null || str.isEmpty()) { - throw new IllegalArgumentException("Null or empty class names are not allowed"); - } - } - - private static final class DynamicColumnIndices extends ColumnInfo { - private final Table table; - - DynamicColumnIndices(Table table) { - super(null, false); - this.table = table; - } - - @Override - public long getColumnIndex(String columnName) { - return table.getColumnIndex(columnName); - } - - @Override - public RealmFieldType getColumnType(String columnName) { - throw new UnsupportedOperationException("DynamicColumnIndices do not support 'getColumnType'"); - } - - @Override - public String getLinkedTable(String columnName) { - throw new UnsupportedOperationException("DynamicColumnIndices do not support 'getLinkedTable'"); - } - - @Override - public void copyFrom(ColumnInfo src) { - throw new UnsupportedOperationException("DynamicColumnIndices cannot be copied"); - } - - @Override - protected ColumnInfo copy(boolean immutable) { - throw new UnsupportedOperationException("DynamicColumnIndices cannot be copied"); - } - - - @Override - protected void copy(ColumnInfo src, ColumnInfo dst) { - throw new UnsupportedOperationException("DynamicColumnIndices cannot copy"); - } - } - - // Tuple containing data about each supported Java type. - private static final class FieldMetaData { - final RealmFieldType realmType; - final boolean defaultNullable; - - FieldMetaData(RealmFieldType realmType, boolean defaultNullable) { - this.realmType = realmType; - this.defaultNullable = defaultNullable; - } - } -} diff --git a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java b/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java deleted file mode 100644 index e0bbcb7a75..0000000000 --- a/realm/realm-library/src/main/java/io/realm/StandardRealmSchema.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import io.realm.internal.Table; -import io.realm.internal.Util; - - -/** - * Class for interacting with the Realm schema using a dynamic API. This makes it possible - * to add, delete and change the classes in the Realm. - *

            - * All changes must happen inside a write transaction for the particular Realm. - * - * @see io.realm.RealmMigration - */ -class StandardRealmSchema extends RealmSchema { - public static final String EMPTY_STRING_MSG = "Null or empty class names are not allowed"; - - // Caches Dynamic Class objects given as Strings to Realm Tables - private final Map dynamicClassToTable = new HashMap<>(); - // Caches Class objects (both model classes and proxy classes) to Realm Tables - private final Map, Table> classToTable = new HashMap<>(); - // Caches Class objects (both model classes and proxy classes) to their Schema object - private final Map, StandardRealmObjectSchema> classToSchema = new HashMap<>(); - // Caches Class Strings to their Schema object - private final Map dynamicClassToSchema = new HashMap<>(); - - private final BaseRealm realm; - - /** - * Creates a wrapper to easily manipulate the current schema of a Realm. - */ - StandardRealmSchema(BaseRealm realm) { - this.realm = realm; - } - - @Override - public void close() { } - - /** - * Returns the Realm schema for a given class. - * - * @param className name of the class - * @return schema object for that class or {@code null} if the class doesn't exists. - */ - @Override - public RealmObjectSchema get(String className) { - checkEmpty(className, EMPTY_STRING_MSG); - - String internalClassName = Table.getTableNameForClass(className); - if (!realm.getSharedRealm().hasTable(internalClassName)) { return null; } - Table table = realm.getSharedRealm().getTable(internalClassName); - return new StandardRealmObjectSchema(realm, this, table); - } - - /** - * Returns the {@link StandardRealmObjectSchema} for all RealmObject classes that can be saved in this Realm. - * - * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. - */ - @Override - public Set getAll() { - int tableCount = (int) realm.getSharedRealm().size(); - Set schemas = new LinkedHashSet<>(tableCount); - for (int i = 0; i < tableCount; i++) { - String tableName = realm.getSharedRealm().getTableName(i); - if (!Table.isModelTable(tableName)) { - continue; - } - schemas.add(new StandardRealmObjectSchema(realm, this, realm.getSharedRealm().getTable(tableName))); - } - return schemas; - } - - /** - * Adds a new class to the Realm. - * - * @param className name of the class. - * @return a Realm schema object for that class. - */ - @Override - public RealmObjectSchema create(String className) { - // Adding a class is always permitted. - checkEmpty(className, EMPTY_STRING_MSG); - - String internalTableName = Table.getTableNameForClass(className); - if (internalTableName.length() > Table.TABLE_MAX_LENGTH) { - throw new IllegalArgumentException("Class name is too long. Limit is 56 characters: " + className.length()); - } - return new StandardRealmObjectSchema(realm, this, realm.getSharedRealm().createTable(internalTableName)); - } - - /** - * Checks if a given class already exists in the schema. - * - * @param className class name to check. - * @return {@code true} if the class already exists. {@code false} otherwise. - */ - @Override - public boolean contains(String className) { - return realm.getSharedRealm().hasTable(Table.getTableNameForClass(className)); - } - - /** - * Removes a class from the Realm. All data will be removed. Removing a class while other classes point - * to it will throw an {@link IllegalStateException}. Removes those classes or fields first. - * - * @param className name of the class to remove. - */ - @Override - public void remove(String className) { - realm.checkNotInSync(); // Destructive modifications are not permitted. - checkEmpty(className, EMPTY_STRING_MSG); - String internalTableName = Table.getTableNameForClass(className); - checkHasTable(className, "Cannot remove class because it is not in this Realm: " + className); - Table table = getTable(className); - if (table.hasPrimaryKey()) { - table.setPrimaryKey(null); - } - realm.getSharedRealm().removeTable(internalTableName); - } - - /** - * Renames a class already in the Realm. - * - * @param oldClassName old class name. - * @param newClassName new class name. - * @return a schema object for renamed class. - */ - @Override - public RealmObjectSchema rename(String oldClassName, String newClassName) { - realm.checkNotInSync(); // Destructive modifications are not permitted. - checkEmpty(oldClassName, "Class names cannot be empty or null"); - checkEmpty(newClassName, "Class names cannot be empty or null"); - String oldInternalName = Table.getTableNameForClass(oldClassName); - String newInternalName = Table.getTableNameForClass(newClassName); - checkHasTable(oldClassName, "Cannot rename class because it doesn't exist in this Realm: " + oldClassName); - if (realm.getSharedRealm().hasTable(newInternalName)) { - throw new IllegalArgumentException(oldClassName + " cannot be renamed because the new class already exists: " + newClassName); - } - - // Checks if there is a primary key defined for the old class. - Table oldTable = getTable(oldClassName); - String pkField = null; - if (oldTable.hasPrimaryKey()) { - pkField = oldTable.getColumnName(oldTable.getPrimaryKey()); - oldTable.setPrimaryKey(null); - } - - realm.getSharedRealm().renameTable(oldInternalName, newInternalName); - Table table = realm.getSharedRealm().getTable(newInternalName); - - // Sets the primary key for the new class if necessary. - if (pkField != null) { - table.setPrimaryKey(pkField); - } - - return new StandardRealmObjectSchema(realm, this, table); - } - - private void checkEmpty(String str, String error) { - if (str == null || str.isEmpty()) { - throw new IllegalArgumentException(error); - } - } - - private void checkHasTable(String className, String errorMsg) { - String internalTableName = Table.getTableNameForClass(className); - if (!realm.getSharedRealm().hasTable(internalTableName)) { - throw new IllegalArgumentException(errorMsg); - } - } - - @Override - Table getTable(String className) { - String tableName = Table.getTableNameForClass(className); - Table table = dynamicClassToTable.get(tableName); - if (table != null) { return table; } - - table = realm.getSharedRealm().getTable(tableName); - dynamicClassToTable.put(tableName, table); - - return table; - } - - @Override - Table getTable(Class clazz) { - Table table = classToTable.get(clazz); - if (table != null) { return table; } - - Class originalClass = Util.getOriginalModelClass(clazz); - if (isProxyClass(originalClass, clazz)) { - // If passed 'clazz' is the proxy, try again with model class. - table = classToTable.get(originalClass); - } - if (table == null) { - table = realm.getSharedRealm().getTable(realm.getConfiguration().getSchemaMediator().getTableName(originalClass)); - classToTable.put(originalClass, table); - } - if (isProxyClass(originalClass, clazz)) { - // 'clazz' is the proxy class for 'originalClass'. - classToTable.put(clazz, table); - } - - return table; - } - - @Override - StandardRealmObjectSchema getSchemaForClass(Class clazz) { - StandardRealmObjectSchema classSchema = classToSchema.get(clazz); - if (classSchema != null) { return classSchema; } - - Class originalClass = Util.getOriginalModelClass(clazz); - if (isProxyClass(originalClass, clazz)) { - // If passed 'clazz' is the proxy, try again with model class. - classSchema = classToSchema.get(originalClass); - } - if (classSchema == null) { - Table table = getTable(clazz); - classSchema = new StandardRealmObjectSchema(realm, this, table, getColumnInfo(originalClass)); - classToSchema.put(originalClass, classSchema); - } - if (isProxyClass(originalClass, clazz)) { - // 'clazz' is the proxy class for 'originalClass'. - classToSchema.put(clazz, classSchema); - } - - return classSchema; - } - - @Override - StandardRealmObjectSchema getSchemaForClass(String className) { - String tableName = Table.getTableNameForClass(className); - StandardRealmObjectSchema dynamicSchema = dynamicClassToSchema.get(tableName); - if (dynamicSchema == null) { - if (!realm.getSharedRealm().hasTable(tableName)) { - throw new IllegalArgumentException("The class " + className + " doesn't exist in this Realm."); - } - dynamicSchema = new StandardRealmObjectSchema(realm, this, realm.getSharedRealm().getTable(tableName)); - dynamicClassToSchema.put(tableName, dynamicSchema); - } - return dynamicSchema; - } -} From 65130e164c8a1554dd6f7d2490d487065b5100df Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 29 Jun 2017 22:28:15 +0800 Subject: [PATCH 0796/2110] Remove useless code for the old async query impl (#4873) --- .../main/cpp/io_realm_internal_TableQuery.cpp | 44 ------------------- .../java/io/realm/internal/TableQuery.java | 36 --------------- 2 files changed, 80 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 56746a003f..ed674b6c3a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -1573,50 +1573,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeImportHandoverRowIntoSharedGroup( - JNIEnv* env, jclass, jlong handoverPtr, jlong callerSharedGrpPtr) -{ - TR_ENTER_PTR(handoverPtr) - SharedGroup::Handover* handoverRowPtr = HO(Row, handoverPtr); - std::unique_ptr> handoverRow(handoverRowPtr); - - try { - // import_from_handover will free (delete) the handover - auto sharedRealm = *(reinterpret_cast(callerSharedGrpPtr)); - if (!sharedRealm->is_closed()) { - using rf = realm::_impl::RealmFriend; - auto row = rf::get_shared_group(*sharedRealm).import_from_handover(std::move(handoverRow)); - return reinterpret_cast(row.release()); - } - else { - ThrowException(env, RuntimeError, ERR_IMPORT_CLOSED_REALM); - } - } - CATCH_STD() - return 0; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeHandoverQuery(JNIEnv* env, jobject, - jlong bgSharedRealmPtr, - jlong nativeQueryPtr) -{ - TR_ENTER_PTR(nativeQueryPtr) - Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) { - return 0; - } - try { - auto sharedRealm = *(reinterpret_cast(bgSharedRealmPtr)); - using rf = realm::_impl::RealmFriend; - auto handover = rf::get_shared_group(*sharedRealm).export_for_handover(*pQuery, ConstSourcePayload::Copy); - return reinterpret_cast(handover.release()); - } - CATCH_STD() - return 0; -} - - - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlongArray tablePointers) diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index e953656ce4..92c1725fd4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -417,27 +417,6 @@ public long find() { return nativeFind(nativePtr, 0); } - /** - * Imports a row from a worker thread to the caller thread. - * - * @param handoverRowPtr pointer to the handover row object - * @param sharedRealm the SharedRealm on the caller thread. - * @return the row pointer on the caller thread. - */ - public static long importHandoverRow(long handoverRowPtr, SharedRealm sharedRealm) { - return nativeImportHandoverRowIntoSharedGroup(handoverRowPtr, sharedRealm.getNativePtr()); - } - - /** - * Handovers the query, so it can be used by other SharedGroup (in different thread) - * - * @param sharedRealm the SharedGroup holding the query - * @return native pointer to the handover query - */ - public long handoverQuery(SharedRealm sharedRealm) { - return nativeHandoverQuery(sharedRealm.getNativePtr(), nativePtr); - } - // // Aggregation methods // @@ -638,17 +617,6 @@ public long remove() { return nativeRemove(nativePtr); } - /** - * Converts a list of sort orders to their native values. - */ - public static boolean[] getNativeSortOrderValues(Sort[] sortOrders) { - boolean[] nativeValues = new boolean[sortOrders.length]; - for (int i = 0; i < sortOrders.length; i++) { - nativeValues[i] = sortOrders[i].getValue(); - } - return nativeValues; - } - private void throwImmutable() { throw new IllegalStateException("Mutable method call during read transaction."); } @@ -781,9 +749,5 @@ private void throwImmutable() { private native long nativeRemove(long nativeQueryPtr); - private native long nativeHandoverQuery(long callerSharedRealmPtr, long nativeQueryPtr); - - private static native long nativeImportHandoverRowIntoSharedGroup(long handoverRowPtr, long callerSharedRealmPtr); - private static native long nativeGetFinalizerPtr(); } From f7431383b76f68ee54a6705c16ddad114811077b Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 30 Jun 2017 02:22:22 +0900 Subject: [PATCH 0797/2110] Update android gradle plugin to 3.0.0 (#4663) * update android gradle plugn to 3.0.0-alpha1 * use new dependency configurations * fix errors * implement new dependency configuration support in realm plugin (no old android gradle plugin support for now) * use new scope support in realm transformer(no old android gradle plugin support for now) * fix rwong variable name * revert the change of getReferencedScopes(). We can expect that the next alpha release of AGP 3.0 does not print warnings. * fix dependency configuration * fix dependencies and typo * add org.gradle.caching=true to each gradle.properties * update agp to 3.0.0-alpha2 * update gradle wrapper to 4.0.0-rc-1 * update agp to 3.0.0-alpha3 * rollback gradlw wrapper to 4.0.0-milestone-1 * Update Gradle to 4.0 Official release. Current Android Gradle Plugin (3.0.0-alpha3) has a bug that cause NPE when executing unit test. This bug has been fixed in Google7s internal repository but not yet released. See https://issuetracker.google.com/issues/62331750 * update android gradle plugin to 3.0.0-alpha4 * detect configuration name * use google() insteadof maven { url 'https://maven.google.com' } * remove unnecessary loop * update android gradle plugin in library-benchmarks/build.gradle * add tasks for assemble * update build tools to 26.0.0 * fix merge mistake * disable AAPT2 to work around an issue of Robolectric in unitTestExample * use 'api' configuration instead of 'implementation' when adding Realm to user project dependencies * address review comments * added Android Studio version requirement * add . --- Dockerfile | 2 +- README.md | 3 +- build.gradle | 22 ++++++++++ examples/build.gradle | 6 ++- examples/gradle.properties | 6 ++- examples/gridViewExample/build.gradle | 2 +- examples/jsonExample/build.gradle | 3 +- examples/kotlinExample/build.gradle | 4 +- examples/moduleExample/app/build.gradle | 2 +- examples/newsreaderExample/build.gradle | 22 +++++----- examples/objectServerExample/build.gradle | 10 ++--- examples/rxJavaExample/build.gradle | 8 ++-- .../secureTokenAndroidKeyStore/build.gradle | 10 ++--- examples/threadExample/build.gradle | 2 +- examples/unitTestExample/build.gradle | 24 +++++------ gradle-plugin/gradle.properties | 1 + .../main/groovy/io/realm/gradle/Realm.groovy | 22 +++++++++- .../realm/gradle/RealmPluginExtension.groovy | 8 ++-- library-benchmarks/build.gradle | 20 +++++----- realm-annotations/gradle.properties | 1 + realm-transformer/gradle.properties | 1 + .../realm/transformer/RealmTransformer.groovy | 1 + realm/build.gradle | 4 +- realm/gradle.properties | 1 + realm/realm-library/build.gradle | 40 ++++++++++--------- 25 files changed, 144 insertions(+), 81 deletions(-) create mode 100644 gradle-plugin/gradle.properties create mode 100644 realm-annotations/gradle.properties create mode 100644 realm-transformer/gradle.properties diff --git a/Dockerfile b/Dockerfile index 3d61d47efe..f5a1726651 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,7 +51,7 @@ RUN mkdir "${ANDROID_HOME}/licenses" && \ echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" RUN sdkmanager --update RUN sdkmanager 'platform-tools' -RUN sdkmanager 'build-tools;25.0.3' +RUN sdkmanager 'build-tools;26.0.0' RUN sdkmanager 'extras;android;m2repository' RUN sdkmanager 'platforms;android-25' diff --git a/README.md b/README.md index 7c1c74a672..9c996e8e4a 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,9 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install the Android SDK **Build-Tools 25.0.3**, **Android N (API 25)** (for example through Android Studio’s **Android SDK Manager**). + * Download & install the Android SDK **Build-Tools 26.0.0**, **Android N (API 25)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). + * If you use Android Studio, Android Studio 3.0 or later is required. * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library/Android`. The download will unzip as the directory `android-ndk-r10e`. diff --git a/build.gradle b/build.gradle index a641a7f5fd..a27e1486e0 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,17 @@ task check { dependsOn checkExamples } +task assembleUnitTests(type:GradleBuild) { + group = 'Build' + description = 'Assemble Android unit tests of the Realm project' + dependsOn installTransformer + buildFile = file('realm/build.gradle') + tasks = ['assembleAndroidTest'] + if (project.hasProperty('buildTargetABIs')) { + startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] + } +} + task connectedUnitTests(type:GradleBuild) { group = 'Test' description = 'Run the Android unit tests of the Realm project' @@ -100,6 +111,17 @@ task connectedUnitTests(type:GradleBuild) { } } +task assembleBenchmarks(type:GradleBuild) { + group = 'Build' + description = 'Assemble benchmark tests for the library ' + dependsOn installTransformer + buildFile = file('library-benchmarks/build.gradle') + tasks = ['assembleAndroidTest'] + if (project.hasProperty('buildTargetABIs')) { + startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] + } +} + task connectedBenchmarks(type:GradleBuild) { group = 'Test' description = 'Run all the benchmark tests for the library ' diff --git a/examples/build.gradle b/examples/build.gradle index 1d0e7df2e9..aae1dcefce 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,5 +1,5 @@ project.ext.sdkVersion = 25 -project.ext.buildTools = '25.0.3' +project.ext.buildTools = '26.0.0' // Don't cache SNAPSHOT (changing) dependencies. configurations.all { @@ -17,12 +17,13 @@ allprojects { buildscript { repositories { + google() mavenLocal() jcenter() maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.0.0-alpha4' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" @@ -35,6 +36,7 @@ allprojects { repositories { mavenLocal() jcenter() + google() } } diff --git a/examples/gradle.properties b/examples/gradle.properties index 4a9594aeec..3cae06226c 100644 --- a/examples/gradle.properties +++ b/examples/gradle.properties @@ -1 +1,5 @@ -org.gradle.jvmargs=-Xmx2048M \ No newline at end of file +org.gradle.jvmargs=-Xmx2048M +org.gradle.caching=true + +# disable AAPT2 to work around an issue of Robolectric in unitTestExample https://github.com/robolectric/robolectric/issues/3169 +android.enableAapt2=false diff --git a/examples/gridViewExample/build.gradle b/examples/gridViewExample/build.gradle index 474e8611e1..8d45e3a5cb 100644 --- a/examples/gridViewExample/build.gradle +++ b/examples/gridViewExample/build.gradle @@ -37,5 +37,5 @@ android { } dependencies { - compile 'com.google.code.gson:gson:2.5' + implementation 'com.google.code.gson:gson:2.5' } diff --git a/examples/jsonExample/build.gradle b/examples/jsonExample/build.gradle index c46861f443..cdd8b8aeab 100644 --- a/examples/jsonExample/build.gradle +++ b/examples/jsonExample/build.gradle @@ -12,6 +12,7 @@ android { minSdkVersion 15 versionCode 1 versionName "1.0" + javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true } buildTypes { release { @@ -27,6 +28,6 @@ android { } dependencies { - provided 'org.projectlombok:lombok:1.16.6' + compileOnly 'org.projectlombok:lombok:1.16.6' annotationProcessor 'org.projectlombok:lombok:1.16.6' } diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 763a8291a7..d4a434d370 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -45,6 +45,6 @@ android { } dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:${kotlin_version}" - compile 'org.jetbrains.anko:anko-sdk15:0.9.1' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:${kotlin_version}" + implementation 'org.jetbrains.anko:anko-sdk15:0.9.1' } diff --git a/examples/moduleExample/app/build.gradle b/examples/moduleExample/app/build.gradle index cc80f29f72..fe9fbafe10 100644 --- a/examples/moduleExample/app/build.gradle +++ b/examples/moduleExample/app/build.gradle @@ -37,5 +37,5 @@ android { } dependencies { - compile project(':moduleExample:library') + implementation project(':moduleExample:library') } diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index dcc0165a06..6e08119b9c 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -32,18 +32,18 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(dir: 'libs', include: ['*.jar']) //noinspection GradleDependency - compile 'com.android.support:appcompat-v7:25.2.0' + implementation 'com.android.support:appcompat-v7:25.2.0' //noinspection GradleDependency - compile 'com.android.support:design:25.2.0' - compile 'io.reactivex:rxjava:1.1.0' - compile 'io.reactivex:rxandroid:1.1.0' - compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' - compile 'com.squareup.retrofit:converter-jackson:2.0.0-beta2' - compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' - compile 'com.jakewharton.timber:timber:4.1.0' - compile 'com.jakewharton:butterknife:8.5.1' + implementation 'com.android.support:design:25.2.0' + implementation 'io.reactivex:rxjava:1.1.0' + implementation 'io.reactivex:rxandroid:1.1.0' + implementation 'com.squareup.retrofit:retrofit:2.0.0-beta2' + implementation 'com.squareup.retrofit:converter-jackson:2.0.0-beta2' + implementation 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' + implementation 'com.jakewharton.timber:timber:4.1.0' + implementation 'com.jakewharton:butterknife:8.5.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' - compile 'me.zhanghai.android.materialprogressbar:library:1.1.4' + implementation 'me.zhanghai.android.materialprogressbar:library:1.1.4' } diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index e976f16d01..f16e3db4bb 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -60,10 +60,10 @@ realm { } dependencies { - compile 'com.android.support:support-v4:25.2.0' - compile 'com.android.support:appcompat-v7:25.2.0' - compile 'com.android.support:design:25.2.0' - compile 'me.zhanghai.android.materialprogressbar:library:1.3.0' - compile 'com.jakewharton:butterknife:8.5.1' + implementation 'com.android.support:support-v4:25.2.0' + implementation 'com.android.support:appcompat-v7:25.2.0' + implementation 'com.android.support:design:25.2.0' + implementation 'me.zhanghai.android.materialprogressbar:library:1.3.0' + implementation 'com.jakewharton:butterknife:8.5.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' } diff --git a/examples/rxJavaExample/build.gradle b/examples/rxJavaExample/build.gradle index 9a916ede8a..21c4723cf6 100644 --- a/examples/rxJavaExample/build.gradle +++ b/examples/rxJavaExample/build.gradle @@ -29,8 +29,8 @@ android { } dependencies { - compile 'io.reactivex:rxandroid:1.1.0' - compile 'io.reactivex:rxjava:1.1.0' - compile 'com.jakewharton.rxbinding:rxbinding:0.3.0' - compile 'com.squareup.retrofit:retrofit:1.9.0' + implementation 'io.reactivex:rxandroid:1.1.0' + implementation 'io.reactivex:rxjava:1.1.0' + implementation 'com.jakewharton.rxbinding:rxbinding:0.3.0' + implementation 'com.squareup.retrofit:retrofit:1.9.0' } diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index a15da443ce..2059678cff 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -24,13 +24,13 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + implementation fileTree(dir: 'libs', include: ['*.jar']) + androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:25.2.0' - testCompile 'junit:junit:4.12' - compile 'io.realm:secure-userstore:1.0.1' + implementation 'com.android.support:appcompat-v7:25.2.0' + testImplementation 'junit:junit:4.12' + implementation 'io.realm:secure-userstore:1.0.1' } realm { diff --git a/examples/threadExample/build.gradle b/examples/threadExample/build.gradle index 98ec9f1827..5ebeb267a5 100644 --- a/examples/threadExample/build.gradle +++ b/examples/threadExample/build.gradle @@ -25,5 +25,5 @@ android { dependencies { //noinspection GradleDependency - compile 'com.android.support:appcompat-v7:24.0.0' + implementation 'com.android.support:appcompat-v7:24.0.0' } diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index 79231649c1..2270fa1ab0 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -30,23 +30,23 @@ android { dependencies { - testCompile 'io.reactivex:rxjava:1.1.0' + testImplementation 'io.reactivex:rxjava:1.1.0' // Testing - testCompile 'junit:junit:4.12' - testCompile "org.robolectric:robolectric:3.3.2" - testCompile "org.mockito:mockito-core:1.10.19" - testCompile 'org.robolectric:shadows-support-v4:3.0' + testImplementation 'junit:junit:4.12' + testImplementation "org.robolectric:robolectric:3.3.2" + testImplementation "org.mockito:mockito-core:1.10.19" + testImplementation 'org.robolectric:shadows-support-v4:3.0' - testCompile "org.powermock:powermock-module-junit4-rule:1.6.5" - testCompile "org.powermock:powermock-module-junit4:1.6.5" - testCompile "org.powermock:powermock-api-mockito:1.6.5" - testCompile "org.powermock:powermock-classloading-xstream:1.6.5" + testImplementation "org.powermock:powermock-module-junit4:1.6.5" + testImplementation "org.powermock:powermock-module-junit4-rule:1.6.5" + testImplementation "org.powermock:powermock-api-mockito:1.6.5" + testImplementation "org.powermock:powermock-classloading-xstream:1.6.5" - androidTestCompile 'com.android.support.test:runner:0.5' + androidTestImplementation 'com.android.support.test:runner:0.5' // Set this dependency to use JUnit 4 rules - androidTestCompile 'com.android.support.test:rules:0.5' + androidTestImplementation 'com.android.support.test:rules:0.5' // Set this dependency to build and run Espresso tests - androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2' } diff --git a/gradle-plugin/gradle.properties b/gradle-plugin/gradle.properties new file mode 100644 index 0000000000..160890028a --- /dev/null +++ b/gradle-plugin/gradle.properties @@ -0,0 +1 @@ +org.gradle.caching=true diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 43bf3f89cd..6a24fa434d 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -23,6 +23,7 @@ import io.realm.transformer.RealmTransformer import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.artifacts.UnknownConfigurationException class Realm implements Plugin { @@ -40,7 +41,8 @@ class Realm implements Plugin { } def syncEnabledDefault = false - project.extensions.create('realm', RealmPluginExtension, project, syncEnabledDefault) + def dependencyConfigurationName = getDependencyConfigurationName(project) + project.extensions.create('realm', RealmPluginExtension, project, syncEnabledDefault, dependencyConfigurationName) def usesAptPlugin = project.plugins.findPlugin('com.neenbedankt.android-apt') != null def isKotlinProject = project.plugins.findPlugin('kotlin-android') != null @@ -57,7 +59,7 @@ class Realm implements Plugin { project.android.registerTransform(new RealmTransformer(project)) project.repositories.add(project.getRepositories().jcenter()) - project.dependencies.add("compile", "io.realm:realm-annotations:${Version.VERSION}") + project.dependencies.add(dependencyConfigurationName, "io.realm:realm-annotations:${Version.VERSION}") if (usesAptPlugin) { project.dependencies.add("apt", "io.realm:realm-annotations-processor:${Version.VERSION}") project.dependencies.add("androidTestApt", "io.realm:realm-annotations-processor:${Version.VERSION}") @@ -80,6 +82,22 @@ class Realm implements Plugin { } } + private static String getDependencyConfigurationName(Project project) { + /* + * Dependency configuration name for android gradle plugin 3.0.0-*. + * We need to use 'api' instead of 'implementation' since user's model class + * might be using Realm's classes and annotations. + */ + def newDependencyName = "api" + def oldDependencyName = "compile" + try { + project.getConfigurations().getByName(newDependencyName) + return newDependencyName + } catch (UnknownConfigurationException ignored) { + oldDependencyName + } + } + private static boolean shouldApplyAndroidAptPlugin(boolean usesAptPlugin, boolean isKotlinProject, boolean hasAnnotationProcessorConfiguration, boolean preferAptOnKotlinProject) { diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy index ac2fc4012a..42bd8e5f39 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy @@ -21,9 +21,11 @@ import org.gradle.api.Project class RealmPluginExtension { private Project project def boolean syncEnabled + private String dependencyConfigurationName - RealmPluginExtension(Project project, boolean syncEnabledDefault) { + RealmPluginExtension(Project project, boolean syncEnabledDefault, String dependencyConfigurationName) { this.project = project + this.dependencyConfigurationName = dependencyConfigurationName setSyncEnabled(syncEnabledDefault) } @@ -31,7 +33,7 @@ class RealmPluginExtension { this.syncEnabled = value; // remove realm android library first - def iterator = project.getConfigurations().getByName("compile").getDependencies().iterator(); + def iterator = project.getConfigurations().getByName(dependencyConfigurationName).getDependencies().iterator(); while (iterator.hasNext()) { def item = iterator.next() if (item.group == 'io.realm' && item.name.startsWith('realm-android-library')) { @@ -41,6 +43,6 @@ class RealmPluginExtension { // then add again def artifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" - project.dependencies.add("compile", "io.realm:${artifactName}:${Version.VERSION}") + project.dependencies.add(dependencyConfigurationName, "io.realm:${artifactName}:${Version.VERSION}") } } diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index e185b78386..38703ec6b9 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -1,10 +1,11 @@ buildscript { repositories { mavenLocal() + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:3.0.0-alpha4' classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" } } @@ -26,7 +27,7 @@ apply plugin: 'realm-android' android { compileSdkVersion 25 - buildToolsVersion "25.0.3" + buildToolsVersion "26.0.0" defaultConfig { minSdkVersion 15 @@ -47,14 +48,15 @@ android { repositories { mavenLocal() + google() jcenter() } dependencies { - androidTestCompile 'com.android.support.test:runner:0.5' - androidTestCompile 'com.android.support.test:rules:0.5' - androidTestCompile 'junit:junit:4.12' - androidTestCompile 'dk.ilios:spanner:0.6.0' - androidTestCompile 'com.opencsv:opencsv:3.4' - androidTestCompile 'junit:junit:4.12' -} \ No newline at end of file + androidTestImplementation 'com.android.support.test:runner:0.5' + androidTestImplementation 'com.android.support.test:rules:0.5' + androidTestImplementation 'junit:junit:4.12' + androidTestImplementation 'dk.ilios:spanner:0.6.0' + androidTestImplementation 'com.opencsv:opencsv:3.4' + androidTestImplementation 'junit:junit:4.12' +} diff --git a/realm-annotations/gradle.properties b/realm-annotations/gradle.properties new file mode 100644 index 0000000000..160890028a --- /dev/null +++ b/realm-annotations/gradle.properties @@ -0,0 +1 @@ +org.gradle.caching=true diff --git a/realm-transformer/gradle.properties b/realm-transformer/gradle.properties new file mode 100644 index 0000000000..160890028a --- /dev/null +++ b/realm-transformer/gradle.properties @@ -0,0 +1 @@ +org.gradle.caching=true diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index b02d1a637b..bd99fb271d 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -66,6 +66,7 @@ class RealmTransformer extends Transform { @Override Set getReferencedScopes() { + // Scope.PROJECT_LOCAL_DEPS and Scope.SUB_PROJECTS_LOCAL_DEPS is only for compatibility with AGP 1.x, 2.x return Sets.immutableEnumSet(Scope.EXTERNAL_LIBRARIES, Scope.PROJECT_LOCAL_DEPS, Scope.SUB_PROJECTS, Scope.SUB_PROJECTS_LOCAL_DEPS, Scope.TESTED_CODE) } diff --git a/realm/build.gradle b/realm/build.gradle index eed959332f..37a4993710 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -2,13 +2,14 @@ buildscript { ext.kotlin_version = '1.1.2-5' repositories { mavenLocal() + google() jcenter() maven { url 'https://jitpack.io' } maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.0.0-alpha4' classpath 'de.undercouch:gradle-download-task:3.1.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.novoda:gradle-android-command-plugin:1.5.0' @@ -33,6 +34,7 @@ allprojects { version = file("${rootDir}/../version.txt").text.trim(); repositories { mavenLocal() + google() jcenter() } } diff --git a/realm/gradle.properties b/realm/gradle.properties index f3f16fcaac..0be17a49db 100644 --- a/realm/gradle.properties +++ b/realm/gradle.properties @@ -1 +1,2 @@ org.gradle.jvmargs=-Xms512m -Xmx2048m +org.gradle.caching=true diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 01283a6b7a..f1e2dfedde 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -38,7 +38,7 @@ ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE android { compileSdkVersion 25 - buildToolsVersion '25.0.3' + buildToolsVersion '26.0.0' defaultConfig { minSdkVersion 9 @@ -111,8 +111,11 @@ android { abortOnError false } + flavorDimensions 'api' + productFlavors { base { + dimension 'api' externalNativeBuild { cmake { arguments "-DREALM_FLAVOR=base" @@ -121,6 +124,7 @@ android { consumerProguardFiles 'proguard-rules-common.pro', 'proguard-rules-base.pro' } objectServer { + dimension 'api' externalNativeBuild { cmake { arguments "-DREALM_FLAVOR=objectServer" @@ -159,26 +163,26 @@ repositories { dependencies { - provided 'io.reactivex:rxjava:1.1.0' - provided 'com.google.code.findbugs:findbugs-annotations:3.0.1' + compileOnly 'io.reactivex:rxjava:1.1.0' + compileOnly 'com.google.code.findbugs:findbugs-annotations:3.0.1' - compile "io.realm:realm-annotations:${version}" - compile 'com.getkeepsafe.relinker:relinker:1.2.2' + api "io.realm:realm-annotations:${version}" + implementation 'com.getkeepsafe.relinker:relinker:1.2.2' kaptObjectServer project(':realm-annotations-processor') - objectServerCompile 'com.squareup.okhttp3:okhttp:3.4.1' + objectServerImplementation 'com.squareup.okhttp3:okhttp:3.4.1' kaptAndroidTest project(':realm-annotations-processor') - androidTestCompile fileTree(dir: 'testLibs', include: ['*.jar']) - androidTestCompile 'io.reactivex:rxjava:1.1.0' - androidTestCompile 'com.android.support:support-annotations:25.3.1' - androidTestCompile 'com.android.support.test:runner:0.5' - androidTestCompile 'com.android.support.test:rules:0.5' - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' - androidTestCompile 'org.hamcrest:hamcrest-library:1.3' - androidTestCompile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" - androidTestCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + androidTestImplementation fileTree(dir: 'testLibs', include: ['*.jar']) + androidTestImplementation 'io.reactivex:rxjava:1.1.0' + androidTestImplementation 'com.android.support:support-annotations:25.3.1' + androidTestImplementation 'com.android.support.test:runner:0.5' + androidTestImplementation 'com.android.support.test:rules:0.5' + androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' + androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' + androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' + androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } task sourcesJar(type: Jar) { @@ -360,7 +364,7 @@ publishing { artifact sourcesJar artifact javadocJar - pom.withXml(createPomDependencies(["baseCompile", "compile"])) + pom.withXml(createPomDependencies(["baseImplementation", "implementation", "baseApi", "api"])) } objectServerPublication(MavenPublication) { @@ -371,7 +375,7 @@ publishing { artifact sourcesJar artifact javadocJar - pom.withXml(createPomDependencies(["objectServerCompile", "compile"])) + pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) } } repositories { From 30357a291731bd09ecba3ff8645177756a3fd6ec Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 30 Jun 2017 13:50:28 +0900 Subject: [PATCH 0798/2110] Explicitly specify Locale for String.format() in our annotation processor (#4853) * add Locale on formatting strings in our annotation processor. * fix formatting --- .../java/io/realm/processor/Backlink.java | 14 +++++++-- .../io/realm/processor/ClassMetaData.java | 21 ++++++------- .../processor/DefaultModuleGenerator.java | 3 +- .../realm/processor/RealmJsonTypeHelper.java | 9 +++--- .../processor/RealmProxyClassGenerator.java | 5 ++-- .../RealmProxyInterfaceGenerator.java | 3 +- .../RealmProxyMediatorGenerator.java | 3 +- .../realm/processor/RealmProcessorTest.java | 9 ++++++ ...303\266rf\303\272r\303\263g\303\251p.java" | 30 +++++++++++++++++++ 9 files changed, 75 insertions(+), 22 deletions(-) create mode 100644 "realm/realm-annotations-processor/src/test/resources/some/test/\303\201rv\303\255zt\305\261r\305\221T\303\274k\303\266rf\303\272r\303\263g\303\251p.java" diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java index ceeaff107a..e658f157a8 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java @@ -16,6 +16,8 @@ package io.realm.processor; +import java.util.Locale; + import javax.lang.model.element.Modifier; import javax.lang.model.element.VariableElement; @@ -87,7 +89,7 @@ final class Backlink { public Backlink(ClassMetaData clazz, VariableElement backlink) { if ((null == clazz) || (null == backlink)) { - throw new NullPointerException(String.format("null parameter: %s, %s", clazz, backlink)); + throw new NullPointerException(String.format(Locale.US, "null parameter: %s, %s", clazz, backlink)); } this.backlink = backlink; @@ -130,6 +132,7 @@ public boolean validateSource() { // A @LinkingObjects cannot be @Required if (backlink.getAnnotation(Required.class) != null) { Utils.error(String.format( + Locale.US, "The @LinkingObjects field \"%s.%s\" cannot be @Required.", targetClass, targetField)); @@ -139,6 +142,7 @@ public boolean validateSource() { // The annotation must have an argument, identifying the linked field if ((sourceField == null) || sourceField.equals("")) { Utils.error(String.format( + Locale.US, "The @LinkingObjects annotation for the field \"%s.%s\" must have a parameter identifying the link target.", targetClass, targetField)); @@ -148,6 +152,7 @@ public boolean validateSource() { // Using link syntax to try to reference a linked field is not possible. if (sourceField.contains(".")) { Utils.error(String.format( + Locale.US, "The parameter to the @LinkingObjects annotation for the field \"%s.%s\" contains a '.'. The use of '.' to specify fields in referenced classes is not supported.", targetClass, targetField)); @@ -157,6 +162,7 @@ public boolean validateSource() { // The annotated element must be a RealmResult if (!Utils.isRealmResults(backlink)) { Utils.error(String.format( + Locale.US, "The field \"%s.%s\" is a \"%s\". Fields annotated with @LinkingObjects must be RealmResults.", targetClass, targetField, @@ -166,6 +172,7 @@ public boolean validateSource() { if (sourceClass == null) { Utils.error(String.format( + Locale.US, "\"The field \"%s.%s\", annotated with @LinkingObjects, must specify a generic type.", targetClass, targetField)); @@ -175,6 +182,7 @@ public boolean validateSource() { // A @LinkingObjects field must be final if (!backlink.getModifiers().contains(Modifier.FINAL)) { Utils.error(String.format( + Locale.US, "A @LinkingObjects field \"%s.%s\" must be final.", targetClass, targetField)); @@ -188,7 +196,7 @@ public boolean validateTarget(ClassMetaData clazz) { VariableElement field = clazz.getDeclaredField(sourceField); if (field == null) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "Field \"%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", does not exist in class \"%s\".", sourceField, targetClass, @@ -199,7 +207,7 @@ public boolean validateTarget(ClassMetaData clazz) { String fieldType = field.asType().toString(); if (!(targetClass.equals(fieldType) || targetClass.equals(Utils.getRealmListType(field)))) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "Field \"%s.%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", has type \"%s\" instead of \"%3$s\".", sourceClass, sourceField, diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index b0ad1ef915..18f4a8fda0 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; @@ -266,7 +267,7 @@ private boolean categorizeClassElements() { } if (fields.size() == 0) { - Utils.error(String.format("Class \"%s\" must contain at least 1 persistable field.", className)); + Utils.error(String.format(Locale.US, "Class \"%s\" must contain at least 1 persistable field.", className)); } return true; @@ -320,7 +321,7 @@ private boolean checkReferenceTypes() { // Report if the default constructor is missing private boolean checkDefaultConstructor() { if (!hasDefaultConstructor) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "Class \"%s\" must declare a public constructor with no arguments if it contains custom constructors.", className)); return false; @@ -332,7 +333,7 @@ private boolean checkDefaultConstructor() { private boolean checkForFinalFields() { for (VariableElement field : fields) { if (field.getModifiers().contains(Modifier.FINAL)) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "Class \"%s\" contains illegal final field \"%s\".", className, field.getSimpleName().toString())); return false; } @@ -343,7 +344,7 @@ private boolean checkForFinalFields() { private boolean checkForVolatileFields() { for (VariableElement field : fields) { if (field.getModifiers().contains(Modifier.VOLATILE)) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "Class \"%s\" contains illegal volatile field \"%s\".", className, field.getSimpleName().toString())); @@ -410,22 +411,22 @@ private boolean categorizeIndexField(Element element, VariableElement variableEl } } - Utils.error(String.format("Field \"%s\" of type \"%s\" cannot be an @Index.", element, element.asType())); + Utils.error(String.format(Locale.US, "Field \"%s\" of type \"%s\" cannot be an @Index.", element, element.asType())); return false; } // The field has the @Required annotation private void categorizeRequiredField(Element element, VariableElement variableElement) { if (Utils.isPrimitiveType(variableElement)) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "@Required annotation is unnecessary for primitive field \"%s\".", element)); } else if (Utils.isRealmList(variableElement) || Utils.isRealmModel(variableElement)) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "Field \"%s\" with type \"%s\" cannot be @Required.", element, element.asType())); } else { // Should never get here - user should remove @Required if (nullableFields.contains(variableElement)) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "Field \"%s\" with type \"%s\" appears to be nullable. Consider removing @Required.", element, element.asType())); @@ -437,7 +438,7 @@ private void categorizeRequiredField(Element element, VariableElement variableEl // String, short, int, long and must only be present one time private boolean categorizePrimaryKeyField(VariableElement variableElement) { if (primaryKey != null) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "A class cannot have more than one @PrimaryKey. Both \"%s\" and \"%s\" are annotated as @PrimaryKey.", primaryKey.getSimpleName().toString(), variableElement.getSimpleName().toString())); @@ -446,7 +447,7 @@ private boolean categorizePrimaryKeyField(VariableElement variableElement) { TypeMirror fieldType = variableElement.asType(); if (!isValidPrimaryKeyType(fieldType)) { - Utils.error(String.format( + Utils.error(String.format(Locale.US, "Field \"%s\" with type \"%s\" cannot be used as primary key. See @PrimaryKey for legal types.", variableElement.getSimpleName().toString(), fieldType)); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java index 1b177c5183..cdae9807cc 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import javax.annotation.processing.ProcessingEnvironment; @@ -44,7 +45,7 @@ public DefaultModuleGenerator(ProcessingEnvironment env) { } public void generate() throws IOException { - String qualifiedGeneratedClassName = String.format("%s.%s", Constants.REALM_PACKAGE_NAME, Constants.DEFAULT_MODULE_CLASS_NAME); + String qualifiedGeneratedClassName = String.format(Locale.US, "%s.%s", Constants.REALM_PACKAGE_NAME, Constants.DEFAULT_MODULE_CLASS_NAME); JavaFileObject sourceFile = env.getFiler().createSourceFile(qualifiedGeneratedClassName); JavaWriter writer = new JavaWriter(new BufferedWriter(sourceFile.openWriter())); writer.setIndent(" "); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java index 1b93e5b355..da89d6a46a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.HashMap; +import java.util.Locale; import java.util.Map; @@ -187,8 +188,8 @@ public void emitTypeConversion( // Only throw exception for primitive types. // For boxed types and String, exception will be thrown in the setter. String statementSetNullOrThrow = Utils.isPrimitiveType(fieldType) ? - String.format(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) : - String.format("((%s) obj).%s(null)", interfaceName, setter); + String.format(Locale.US, Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) : + String.format(Locale.US, "((%s) obj).%s(null)", interfaceName, setter); // @formatter:off writer .beginControlFlow("if (json.has(\"%s\"))", fieldName) @@ -211,8 +212,8 @@ public void emitStreamTypeConversion( // Only throw exception for primitive types. For boxed types and String, exception will be thrown in // the setter. String statementSetNullOrThrow = (Utils.isPrimitiveType(fieldType)) ? - String.format(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) : - String.format("((%s) obj).%s(null)", interfaceName, setter); + String.format(Locale.US, Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) : + String.format(Locale.US, "((%s) obj).%s(null)", interfaceName, setter); // @formatter:off writer .beginControlFlow("if (reader.peek() == JsonToken.NULL)") diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index bc16c86a53..0f86050dd5 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Locale; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; @@ -85,7 +86,7 @@ public RealmProxyClassGenerator(ProcessingEnvironment processingEnvironment, Cla this.simpleClassName = metadata.getSimpleClassName(); this.qualifiedClassName = metadata.getFullyQualifiedClassName(); this.interfaceName = Utils.getProxyInterfaceName(simpleClassName); - this.qualifiedGeneratedClassName = String.format("%s.%s", + this.qualifiedGeneratedClassName = String.format(Locale.US, "%s.%s", Constants.REALM_PACKAGE_NAME, Utils.getProxyClassName(simpleClassName)); // See the configuration for the debug build type, @@ -278,7 +279,7 @@ private void emitPersistedFieldAccessors(final JavaWriter writer) throws IOExcep } else if (Utils.isRealmList(field)) { emitRealmList(writer, field, fieldName, fieldTypeCanonicalName); } else { - throw new UnsupportedOperationException(String.format( + throw new UnsupportedOperationException(String.format(Locale.US, "Field \"%s\" of type \"%s\" is not supported.", fieldName, fieldTypeCanonicalName)); } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java index ed1785e904..ab60954f91 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java @@ -20,6 +20,7 @@ import java.io.BufferedWriter; import java.io.IOException; import java.util.EnumSet; +import java.util.Locale; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Modifier; @@ -42,7 +43,7 @@ public RealmProxyInterfaceGenerator(ProcessingEnvironment processingEnvironment, public void generate() throws IOException { String qualifiedGeneratedInterfaceName = - String.format("%s.%s", Constants.REALM_PACKAGE_NAME, Utils.getProxyInterfaceName(className)); + String.format(Locale.US, "%s.%s", Constants.REALM_PACKAGE_NAME, Utils.getProxyInterfaceName(className)); JavaFileObject sourceFile = processingEnvironment.getFiler().createSourceFile(qualifiedGeneratedInterfaceName); JavaWriter writer = new JavaWriter(new BufferedWriter(sourceFile.openWriter())); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index 65f4e7a843..5194896e56 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Locale; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; @@ -55,7 +56,7 @@ public RealmProxyMediatorGenerator(ProcessingEnvironment processingEnvironment, } public void generate() throws IOException { - String qualifiedGeneratedClassName = String.format("%s.%sMediator", REALM_PACKAGE_NAME, className); + String qualifiedGeneratedClassName = String.format(Locale.US, "%s.%sMediator", REALM_PACKAGE_NAME, className); JavaFileObject sourceFile = processingEnvironment.getFiler().createSourceFile(qualifiedGeneratedClassName); JavaWriter writer = new JavaWriter(new BufferedWriter(sourceFile.openWriter())); writer.setIndent(" "); diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java index 2f84e1f916..243336069f 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java @@ -69,6 +69,7 @@ public class RealmProcessorTest { private JavaFileObject backlinksNotFound = JavaFileObjects.forResource("some/test/Backlinks_NotFound.java"); private JavaFileObject backlinksNonFinalField = JavaFileObjects.forResource("some/test/Backlinks_NotFinal.java"); private JavaFileObject backlinksWrongType = JavaFileObjects.forResource("some/test/Backlinks_WrongType.java"); + private JavaFileObject nonLatinName = JavaFileObjects.forResource("some/test/ÁrvíztűrőTükörfúrógép.java"); @Test public void compileSimpleFile() { @@ -564,4 +565,12 @@ public void failsOnLinkingObjectsWithFieldWrongType() { .failsToCompile() .withErrorContaining("instead of"); } + + @Test + public void compareNonLatinName() throws Exception { + ASSERT.about(javaSource()) + .that(nonLatinName) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } } diff --git "a/realm/realm-annotations-processor/src/test/resources/some/test/\303\201rv\303\255zt\305\261r\305\221T\303\274k\303\266rf\303\272r\303\263g\303\251p.java" "b/realm/realm-annotations-processor/src/test/resources/some/test/\303\201rv\303\255zt\305\261r\305\221T\303\274k\303\266rf\303\272r\303\263g\303\251p.java" new file mode 100644 index 0000000000..0cf6fe08d5 --- /dev/null +++ "b/realm/realm-annotations-processor/src/test/resources/some/test/\303\201rv\303\255zt\305\261r\305\221T\303\274k\303\266rf\303\272r\303\263g\303\251p.java" @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package some.test; + +import io.realm.RealmObject; + + +/** + * A model class to test non latin class name. + */ +public class ÁrvíztűrőTükörfúrógép extends RealmObject { + public String name; + public long 델타; + public long Δέλτα; + public float 貸借対照表; +} From f531c9aedb57db295cbe7f8759ed301806d2f0fa Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 30 Jun 2017 09:54:21 +0200 Subject: [PATCH 0799/2110] Fixing isNull() for link queries (#4870) --- CHANGELOG.md | 2 ++ .../java/io/realm/RealmLinkTests.java | 24 +++++++++++++++++++ .../main/cpp/io_realm_internal_TableQuery.cpp | 10 ++++---- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fdb9c6338..44b2d7b0e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bug Fixes +* Fixed a bug in `isNull()`, `isNotNull()`, `isEmpty()`, and `isNotEmpty()` when queries involve nullable fields in link queries (#4856). + ### Internal * Removed `Table#Table()`, `Table#addEmptyRow()`, `Table#addEmptyRows()`, `Table#add(Object...)`, `Table#pivot(long,long,PivotType)` and `Table#createnative()`. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java index 2e802eb7f2..e9c94fb610 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java @@ -534,6 +534,9 @@ public void linkIsNull() { RealmResults owners2 = testRealm.where(Owner.class).isNull("cat").findAll(); assertEquals(1, owners2.size()); + + RealmResults owners3 = testRealm.where(Owner.class).isNull("dogs.birthday").findAll(); + assertEquals(0, owners3.size()); } @Test @@ -547,6 +550,27 @@ public void linkIsNotNull() { RealmResults owners2 = testRealm.where(Owner.class).isNotNull("cat").findAll(); assertEquals(0, owners2.size()); + + RealmResults owners3 = testRealm.where(Owner.class).isNotNull("dogs.birthday").findAll(); + assertEquals(1, owners3.size()); + } + + @Test + public void isEmpty() { + RealmResults owners1 = testRealm.where(Owner.class).isEmpty("cat.name").findAll(); + assertEquals(0, owners1.size()); + + RealmResults owners2 = testRealm.where(Owner.class).isEmpty("dogs.name").findAll(); + assertEquals(0, owners2.size()); + } + + @Test + public void isNotEmpty() { + RealmResults owners1 = testRealm.where(Owner.class).isNotEmpty("cat.name").findAll(); + assertEquals(1, owners1.size()); + + RealmResults owners2 = testRealm.where(Owner.class).isNotEmpty("dogs.name").findAll(); + assertEquals(1, owners2.size()); } @Test diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index ed674b6c3a..f8cc250cd5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -1510,7 +1510,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en } TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - int col_type = src_table_ref->get_column_type(S(column_idx)); + DataType col_type = table_ref->get_column_type(S(column_idx)); if (arr_len == 1) { switch (col_type) { case type_Link: @@ -1591,7 +1591,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - int col_type = src_table_ref->get_column_type(S(column_idx)); + DataType col_type = table_ref->get_column_type(S(column_idx)); if (arr_len == 1) { switch (col_type) { case type_Link: @@ -1674,7 +1674,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e return; } - int col_type = src_table_ref->get_column_type(column_idx); + TableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, index_arr); + DataType col_type = table_ref->get_column_type(column_idx); if (arr_len == 1) { // Field queries switch (col_type) { @@ -1744,7 +1745,8 @@ Java_io_realm_internal_TableQuery_nativeIsNotEmpty(JNIEnv *env, jobject, jlong n return; } - int col_type = src_table_ref->get_column_type(column_idx); + TableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, index_arr); + DataType col_type = table_ref->get_column_type(column_idx); if (arr_len == 1) { // Field queries switch (col_type) { From d675ca8588e46537eb243fff2cb0ecb609d45af3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 30 Jun 2017 15:26:04 +0800 Subject: [PATCH 0800/2110] Fix wrong changelog entry --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83cb810190..718c9a4bf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Breaking Changes +* An `IllegalStateException` will be thrown if the given `RealmModule` doesn't include all required model classes (#3398). + ### Deprecated ### Enhancements @@ -10,6 +12,8 @@ ### Internal +* Use Object Store to do table initialization. + ### Credits @@ -36,7 +40,6 @@ ### Breaking Changes * [ObjectServer] Updated protocol version to 18 which is only compatible with ROS > 1.6.0. -* An `IllegalStateException` will be thrown if the given `RealmModule` doesn't include all required model classes (#3398). ### Deprecated @@ -78,7 +81,6 @@ ### Internal * Factor out internal interface ManagedObject. -* Use Object Store to do table initialization. ## 3.3.1 (2017-05-26) From fc4de784bcc6ef156227f3ac62a758a68d913897 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 16 Jun 2017 18:41:32 +0800 Subject: [PATCH 0801/2110] Use Object Store to handle schema migration - Use Object Store to handle migration. This is for non-synced Realm. Object Store will compare the expected OsSchemaInfo with the current on on the disc. if they don't match, the migration callback supplied by java will called to do manual migration. - Update Object Store to 0d0aef97b8 --- CHANGELOG.md | 4 + .../io/realm/LinkingObjectsManagedTests.java | 8 +- .../io/realm/RealmConfigurationTests.java | 19 ++--- .../java/io/realm/RealmMigrationTests.java | 84 ++++++++++++------- .../cpp/io_realm_internal_SharedRealm.cpp | 53 +++++++----- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/BaseRealm.java | 40 +++++---- .../src/main/java/io/realm/DynamicRealm.java | 9 ++ .../src/main/java/io/realm/Realm.java | 70 +++++----------- .../java/io/realm/internal/SharedRealm.java | 35 ++++---- 10 files changed, 177 insertions(+), 147 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 718c9a4bf1..b61ad488e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,16 +3,20 @@ ### Breaking Changes * An `IllegalStateException` will be thrown if the given `RealmModule` doesn't include all required model classes (#3398). +* Bumping schema version only without any actual schema changes will just succeed even when the migration block is not supplied. It threw an `RealmMigrationNeededException` before in the same case. ### Deprecated ### Enhancements +* Added more detailed excpetion message for `RealmMigrationNeeded`. + ### Bug Fixes ### Internal * Use Object Store to do table initialization. +* Use Object Store to handle migration. ### Credits diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 2d61e6b637..b4d77345b3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -20,6 +20,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -47,6 +48,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -530,7 +532,11 @@ public void migration_backlinkedFieldInUse() { } catch (IOException e) { fail("Failed copying realm"); } catch (RealmMigrationNeededException expected) { - assertTrue(expected.getMessage().contains("Field count is")); + assertThat(expected.getMessage(), + CoreMatchers.allOf( + CoreMatchers.containsString("Property 'BacklinksSource.name' has been added"), + CoreMatchers.containsString("Property 'BacklinksTarget.parents' has been removed"))); + //assertTrue(expected.getMessage().contains("Field count is")); } finally { Realm.deleteRealm(realmConfig); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 5166f24a4a..5f1c0733bd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -18,6 +18,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -394,17 +395,13 @@ public void upgradeVersionWithNoMigration() { assertEquals(0, realm.getVersion()); realm.close(); - // Version upgrades should always require a migration. - try { - realm = Realm.getInstance(new RealmConfiguration.Builder(context) - .directory(configFactory.getRoot()) - .schemaVersion(42) - .build()); - fail(); - } catch (RealmMigrationNeededException expected) { - // And it should come with a cause. - assertEquals("Realm on disk need to migrate from v0 to v42", expected.getMessage()); - } + // Version upgrades only without any actual schema changes will just succeed, and the schema version will be + // set to the new one. + realm = Realm.getInstance(new RealmConfiguration.Builder(context) + .directory(configFactory.getRoot()) + .schemaVersion(42) + .build()); + assertEquals(42, realm.getSchema().getSchemaVersion()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 45feac73fe..e6080cf037 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -19,6 +19,7 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import android.util.Log; import org.hamcrest.CoreMatchers; import org.junit.After; @@ -32,14 +33,18 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.Date; +import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import io.realm.entities.AllTypes; import io.realm.entities.AnnotationTypes; +import io.realm.entities.Cat; import io.realm.entities.CatOwner; import io.realm.entities.Dog; +import io.realm.entities.DogPrimaryKey; import io.realm.entities.FieldOrder; import io.realm.entities.NullTypes; +import io.realm.entities.Owner; import io.realm.entities.PrimaryKeyAsBoxedByte; import io.realm.entities.PrimaryKeyAsBoxedInteger; import io.realm.entities.PrimaryKeyAsBoxedLong; @@ -234,9 +239,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm = Realm.getInstance(realmConfig); fail(); } catch (RealmMigrationNeededException e) { - if (!e.getMessage().equals("Primary key not defined for field 'id' in existing Realm file. @PrimaryKey was added.")) { - fail(e.toString()); - } + assertThat(e.getMessage(), CoreMatchers.containsString( + "Primary Key for class 'AnnotationTypes' has been added")); } finally { if (realm != null) { realm.close(); @@ -272,9 +276,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm = Realm.getInstance(realmConfig); fail(); } catch (RealmMigrationNeededException e) { - if (!e.getMessage().equals("Primary Key defined for field chars was removed.")) { - fail(e.toString()); - } + assertThat(e.getMessage(), + CoreMatchers.containsString("Primary Key for class 'StringOnly' has been removed.")); } finally { if (realm != null) { realm.close(); @@ -311,9 +314,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm = Realm.getInstance(realmConfig); fail(); } catch (RealmMigrationNeededException e) { - if (!e.getMessage().equals("Primary Key annotation definition was changed, from field id to field name")) { - fail(e.toString()); - } + assertThat(e.getMessage(), CoreMatchers.containsString( + "Primary Key for class 'PrimaryKeyAsString' has changed from 'id' to 'name'.")); } finally { if (realm != null) { realm.close(); @@ -823,7 +825,7 @@ public void migrationException_realmListChanged() throws IOException { fail(); } catch (RealmMigrationNeededException ignored) { assertThat(ignored.getMessage(), - CoreMatchers.containsString("Invalid RealmList type for field 'cats': 'class_Dog' expected ")); + CoreMatchers.containsString("Property 'CatOwner.cats' has been changed from 'array' to 'array'")); } } @@ -851,8 +853,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm.close(); fail(); } catch (RealmMigrationNeededException e) { - assertEquals("Field 'chars' is required. Either set @Required to field 'chars' or migrate using RealmObjectSchema.setNullable().", - e.getMessage()); + assertThat(e.getMessage(), CoreMatchers.containsString( + "Property 'StringOnly.chars' has been made optional")); } } @@ -983,10 +985,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm = Realm.getInstance(realmConfig); fail("Failed on " + field); } catch (RealmMigrationNeededException e) { - assertEquals("Field '" + field + "' does support null values in the existing Realm file." + - " Remove @Required or @PrimaryKey from field '" + field + "' " + - "or migrate using RealmObjectSchema.setNullable().", - e.getMessage()); + assertThat(e.getMessage(), CoreMatchers.containsString( + String.format("Property 'NullTypes.%s' has been made required", field))); } } } @@ -1051,16 +1051,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm = Realm.getInstance(realmConfig); fail("Failed on " + field); } catch (RealmMigrationNeededException e) { - if (field.equals(NullTypes.FIELD_STRING_NULL) || field.equals(NullTypes.FIELD_BYTES_NULL) || - field.equals(NullTypes.FIELD_DATE_NULL)) { - assertEquals("Field '" + field + "' is required. Either set @Required to field '" + - field + "' " + - "or migrate using RealmObjectSchema.setNullable().", e.getMessage()); - } else { - assertEquals("Field '" + field + "' does not support null values in the existing Realm file." - + " Either set @Required, use the primitive type for field '" - + field + "' or migrate using RealmObjectSchema.setNullable().", e.getMessage()); - } + assertThat(e.getMessage(), CoreMatchers.containsString( + String.format(Locale.US, "Property 'NullTypes.%s' has been made optional", field))); } } } @@ -1126,13 +1118,12 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm.close(); fail(); } catch (RealmMigrationNeededException expected) { + String pkFieldName = "id"; if (clazz == PrimaryKeyAsString.class) { - assertEquals("@PrimaryKey field 'name' does not support null values in the existing Realm file. Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required.", - expected.getMessage()); - } else { - assertEquals("@PrimaryKey field 'id' does not support null values in the existing Realm file. Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required.", - expected.getMessage()); + pkFieldName = "name"; } + assertThat(expected.getMessage(), CoreMatchers.containsString(String.format(Locale.US, + "Property '%s.%s' has been made optional", clazz.getSimpleName(), pkFieldName))); } } } @@ -1152,7 +1143,7 @@ public void migrating_nullableField_toward_notNullable_PrimaryKeyThrows() throws fail(); } catch (RealmMigrationNeededException expected) { assertThat(expected.getMessage(), CoreMatchers.containsString( - "Field 'id' does support null values in the existing Realm file.")); + String.format("Property '%s.%s' has been made required", clazz.getSimpleName(), "id"))); } } } @@ -1291,6 +1282,35 @@ public void migrationRequired_throwsOriginalException() { } } + @Test + public void migrationRequired_throwsExceptionInTheMigrationBlock() { + final RuntimeException exception = new RuntimeException("TEST"); + + RealmMigration migration = new RealmMigration() { + @Override + public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { + throw exception; + } + }; + RealmConfiguration config = configFactory.createConfigurationBuilder() + .migration(migration) + .schemaVersion(1) + .assetFile("default0.realm") // This Realm does not have the correct schema + .build(); + + Realm realm = null; + try { + realm = Realm.getInstance(config); + fail(); + } catch (RuntimeException expected) { + assertEquals(exception, expected); + } finally { + if (realm != null) { + realm.close(); + } + } + } + // TODO Add unit tests for default nullability // TODO Add unit tests for default Indexing for Primary keys } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 5eb72588d5..b444027253 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -21,8 +21,9 @@ #include "object-store/src/sync/sync_session.hpp" #endif -#include +#include +#include #include "object_store.hpp" #include "java_binding_context.hpp" #include "util.hpp" @@ -595,33 +596,47 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeCompact(JNIE return JNI_FALSE; } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv* env, jclass, +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNIEnv* env, jobject j_shared_realm, jlong shared_realm_ptr, jlong schema_ptr, - jlong version) + jlong version, + jobject j_migration_callback) { TR_ENTER_PTR(shared_realm_ptr) + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); auto* schema = reinterpret_cast(schema_ptr); - shared_realm->update_schema(*schema, static_cast(version), nullptr, true); + Realm::MigrationFunction migration_function = nullptr; + if (j_migration_callback) { + static JavaMethod run_migration_callback_method(env, j_shared_realm, "runMigrationCallback", + "(Lio/realm/internal/SharedRealm$MigrationCallback;JJ)V"); + migration_function = [&env, &j_shared_realm, &j_migration_callback, &shared_realm, + &version](SharedRealm old_realm, SharedRealm realm, Schema&) { + REALM_ASSERT_RELEASE(shared_realm == realm); + env->CallVoidMethod(j_shared_realm, run_migration_callback_method, j_migration_callback, + old_realm->schema_version(), version); + }; + } + shared_realm->update_schema(*schema, static_cast(version), migration_function, true); } - CATCH_STD() -} - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeRequiresMigration(JNIEnv* env, jclass, - jlong nativePtr, - jlong nativeSchemaPtr) -{ + catch (SchemaMismatchException& e) { + // An exception has been thrown in the migration block. + if (env->ExceptionCheck()) { + return; + } + static JavaClass migration_needed_class(env, "io/realm/exceptions/RealmMigrationNeededException"); + static JavaMethod constructor(env, migration_needed_class, "", + "(Ljava/lang/String;Ljava/lang/String;)V"); - TR_ENTER() - try { - auto& shared_realm = *(reinterpret_cast(nativePtr)); - auto* schema = reinterpret_cast(nativeSchemaPtr); - const std::vector& change_list = shared_realm->schema().compare(*schema); - return static_cast(!change_list.empty()); + jstring message = to_jstring(env, e.what()); + jstring path = to_jstring(env, shared_realm->config().path); + jobject migration_needed_exception = env->NewObject(migration_needed_class, constructor, path, message); + env->Throw(reinterpret_cast(migration_needed_exception)); + } + catch (InvalidSchemaVersionException& e) { + // To match the old behaviour. Otherwise it will be converted to ISE in the CATCH_STD. + ThrowException(env, IllegalArgument, e.what()); } CATCH_STD() - return JNI_FALSE; } static void finalize_shared_realm(jlong ptr) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 1e3cbb1789..0d0aef97b8 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 1e3cbb178952e26112a474f596aad6bf4f938adf +Subproject commit 0d0aef97b84f91ba0a13ac20c276ec207db43228 diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 8679a02d05..c8ed259e7c 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -32,6 +32,8 @@ import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; import io.realm.internal.InvalidRow; +import io.realm.internal.OsSchemaInfo; +import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; import io.realm.internal.SharedRealm; import io.realm.internal.Table; @@ -101,6 +103,15 @@ public void onSchemaVersionChanged(long currentVersion) { this.schema = new RealmSchema(this); } + BaseRealm(SharedRealm sharedRealm) { + this.threadId = Thread.currentThread().getId(); + this.configuration = sharedRealm.getConfiguration(); + this.realmCache = null; + + this.sharedRealm = sharedRealm; + this.schema = new RealmSchema(this); + } + /** * Sets the auto-refresh status of the Realm instance. *

            @@ -469,9 +480,6 @@ void doClose() { sharedRealm.close(); sharedRealm = null; } - if (schema != null) { - schema.close(); - } } /** @@ -610,13 +618,12 @@ static boolean compactRealm(final RealmConfiguration configuration) { * @param configuration configuration for the Realm that should be migrated. If this is a SyncConfiguration this * method does nothing. * @param migration if set, this migration block will override what is set in {@link RealmConfiguration}. - * @param callback callback for specific Realm type behaviors. * @param cause which triggers this migration. * @throws FileNotFoundException if the Realm file doesn't exist. * @throws IllegalArgumentException if the provided configuration is a {@link SyncConfiguration}. */ protected static void migrateRealm(final RealmConfiguration configuration, final RealmMigration migration, - final MigrationCallback callback, final RealmMigrationNeededException cause) + final RealmMigrationNeededException cause) throws FileNotFoundException { if (configuration == null) { @@ -645,16 +652,25 @@ public void onResult(int count) { return; } - RealmMigration realmMigration = (migration == null) ? configuration.getMigration() : migration; DynamicRealm realm = null; + RealmProxyMediator mediator = configuration.getSchemaMediator(); + OsSchemaInfo schemaInfo = new OsSchemaInfo(mediator.getExpectedObjectSchemaInfoMap().values()); try { // Create a DynamicRealm WITHOUT putting it into a RealmCache to avoid recursive locks and call init // steps multiple times (copy asset file / initialData transaction). realm = DynamicRealm.createInstance(configuration); realm.beginTransaction(); - long currentVersion = realm.getVersion(); - realmMigration.migrate(realm, currentVersion, configuration.getSchemaVersion()); - realm.setVersion(configuration.getSchemaVersion()); + SharedRealm.MigrationCallback migrationCallback = null; + if (configuration.getMigration() != null) { + migrationCallback = new SharedRealm.MigrationCallback() { + @Override + public void onMigrationNeeded(SharedRealm sharedRealm, long oldVersion, long newVersion) { + configuration.getMigration().migrate(DynamicRealm.createInstance(sharedRealm), + oldVersion, newVersion); + } + }; + } + realm.sharedRealm.updateSchema(schemaInfo, configuration.getSchemaVersion(), migrationCallback); realm.commitTransaction(); } catch (RuntimeException e) { if (realm != null) { @@ -664,7 +680,6 @@ public void onResult(int count) { } finally { if (realm != null) { realm.close(); - callback.migrationComplete(); } } } @@ -694,11 +709,6 @@ SharedRealm getSharedRealm() { return sharedRealm; } - // Internal delegate for migrations. - protected interface MigrationCallback { - void migrationComplete(); - } - public static final class RealmObjectContext { private BaseRealm realm; private Row row; diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index f69c1ad399..9b7f9213d2 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -22,6 +22,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.internal.CheckedRow; import io.realm.internal.OsObject; +import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.log.RealmLog; import rx.Observable; @@ -57,6 +58,10 @@ private DynamicRealm(RealmConfiguration configuration) { super(configuration); } + private DynamicRealm(SharedRealm sharedRealm) { + super(sharedRealm); + } + /** * Realm static constructor that returns a dynamic variant of the Realm instance defined by provided * {@link io.realm.RealmConfiguration}. Dynamic Realms do not care about schemaVersion and schemas, so opening a @@ -247,6 +252,10 @@ static DynamicRealm createInstance(RealmConfiguration configuration) { return new DynamicRealm(configuration); } + static DynamicRealm createInstance(SharedRealm sharedRealm) { + return new DynamicRealm(sharedRealm); + } + /** * {@inheritDoc} */ diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index f8357eb3c7..b00f03b023 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -373,15 +373,6 @@ static Realm createInstance(RealmCache cache) { } catch (RealmMigrationNeededException e) { if (configuration.shouldDeleteRealmIfMigrationNeeded()) { deleteRealm(configuration); - } else { - try { - if (configuration.getMigration() != null) { - migrateRealm(configuration, e); - } - } catch (FileNotFoundException fileNotFoundException) { - // Should never happen. - throw new RealmFileException(RealmFileException.Kind.NOT_FOUND, fileNotFoundException); - } } return createAndValidateFromCache(cache); @@ -392,7 +383,6 @@ private static Realm createAndValidateFromCache(RealmCache cache) { Realm realm = new Realm(cache); RealmConfiguration configuration = realm.configuration; - final long currentVersion = realm.getVersion(); final long requiredVersion = configuration.getSchemaVersion(); final ColumnIndices columnIndices = RealmCache.findColumnIndices(cache.getTypedColumnIndicesArray(), @@ -402,22 +392,6 @@ private static Realm createAndValidateFromCache(RealmCache cache) { // Copies global cache as a Realm local indices cache. realm.schema.setInitialColumnIndices(columnIndices); } else { - final boolean syncingConfig = configuration.isSyncConfiguration(); - - if (!syncingConfig && (currentVersion != UNVERSIONED)) { - if (currentVersion < requiredVersion) { - realm.doClose(); - throw new RealmMigrationNeededException( - configuration.getPath(), - String.format(Locale.US, "Realm on disk need to migrate from v%s to v%s", currentVersion, requiredVersion)); - } - if (requiredVersion < currentVersion) { - realm.doClose(); - throw new IllegalArgumentException( - String.format(Locale.US, "Realm on disk is newer than the one specified: v%s vs. v%s", currentVersion, requiredVersion)); - } - } - // Initializes Realm schema if needed. try { initializeRealm(realm); @@ -440,7 +414,7 @@ private static void initializeRealm(Realm realm) { // interprocess lock. This lock can obviously not be created by a Realm instance so we probably need // to implement it in Object Store. When this happens, the `beginTransaction(true)` can be removed again. realm.beginTransaction(true); - RealmConfiguration configuration = realm.getConfiguration(); + final RealmConfiguration configuration = realm.getConfiguration(); long currentVersion = realm.getVersion(); boolean unversioned = currentVersion == UNVERSIONED; long newVersion = configuration.getSchemaVersion(); @@ -454,21 +428,29 @@ private static void initializeRealm(Realm realm) { OsSchemaInfo schema = new OsSchemaInfo(mediator.getExpectedObjectSchemaInfoMap().values()); // Object Store handles all update logic - realm.sharedRealm.updateSchema(schema, newVersion); + realm.sharedRealm.updateSchema(schema, newVersion, null); commitChanges = true; } } else { // Only allow creating the schema if not in read-only mode - if (unversioned) { - if (configuration.isReadOnly()) { - throw new IllegalArgumentException("Cannot create the Realm schema in a read-only file."); - } + if (unversioned && configuration.isReadOnly()) { + throw new IllegalArgumentException("Cannot create the Realm schema in a read-only file."); + } - // Let Object Store initialize all tables - OsSchemaInfo schemaInfo = new OsSchemaInfo(mediator.getExpectedObjectSchemaInfoMap().values()); - realm.sharedRealm.updateSchema(schemaInfo, newVersion); - commitChanges = true; + // Let Object Store initialize all tables + OsSchemaInfo schemaInfo = new OsSchemaInfo(mediator.getExpectedObjectSchemaInfoMap().values()); + SharedRealm.MigrationCallback migrationCallback = null; + if (configuration.getMigration() != null) { + migrationCallback = new SharedRealm.MigrationCallback() { + @Override + public void onMigrationNeeded(SharedRealm sharedRealm, long oldVersion, long newVersion) { + configuration.getMigration().migrate(DynamicRealm.createInstance(sharedRealm), + oldVersion, newVersion); + } + }; } + realm.sharedRealm.updateSchema(schemaInfo, newVersion, migrationCallback); + commitChanges = true; } // Now that they have all been created, validate them. @@ -481,9 +463,7 @@ private static void initializeRealm(Realm realm) { configuration.isSyncConfiguration())); } - realm.getSchema().setInitialColumnIndices( - (unversioned) ? newVersion : currentVersion, - columnInfoMap); + realm.getSchema().setInitialColumnIndices(realm.getVersion(), columnInfoMap); // Finally add any initial data final Transaction transaction = configuration.getInitialDataTransaction(); @@ -1654,11 +1634,7 @@ public static void migrateRealm(RealmConfiguration configuration) throws FileNot */ private static void migrateRealm(final RealmConfiguration configuration, final RealmMigrationNeededException cause) throws FileNotFoundException { - BaseRealm.migrateRealm(configuration, null, new MigrationCallback() { - @Override - public void migrationComplete() { - } - }, cause); + BaseRealm.migrateRealm(configuration, null, cause); } /** @@ -1671,11 +1647,7 @@ public void migrationComplete() { */ public static void migrateRealm(RealmConfiguration configuration, RealmMigration migration) throws FileNotFoundException { - BaseRealm.migrateRealm(configuration, migration, new MigrationCallback() { - @Override - public void migrationComplete() { - } - }, null); + BaseRealm.migrateRealm(configuration, migration, null); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 2408560e85..d9c831e754 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -27,7 +27,7 @@ import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; - +@KeepMember public final class SharedRealm implements Closeable, NativeObject { // Const value for RealmFileException conversion @@ -168,6 +168,10 @@ public interface SchemaVersionListener { void onSchemaVersionChanged(long currentVersion); } + public interface MigrationCallback { + void onMigrationNeeded(SharedRealm sharedRealm, long oldVersion, long newVersion); + } + private final SchemaVersionListener schemaChangeListener; private final RealmConfiguration configuration; private final long nativePtr; @@ -372,8 +376,8 @@ public boolean compact() { * @param schemaInfo the expected schema. * @param version the target version. */ - public void updateSchema(OsSchemaInfo schemaInfo, long version) { - nativeUpdateSchema(nativePtr, schemaInfo.getNativePtr(), version); + public void updateSchema(OsSchemaInfo schemaInfo, long version, MigrationCallback migrationCallback) { + nativeUpdateSchema(nativePtr, schemaInfo.getNativePtr(), version, migrationCallback); } public void setAutoRefresh(boolean enabled) { @@ -385,19 +389,8 @@ public boolean isAutoRefresh() { return nativeIsAutoRefresh(nativePtr); } - /** - * Determine whether the passed schema needs to be updated. - *

            - * TODO: This method should not require the caller to get the native pointer. - * Instead, the signature should be something like: - * public void updateSchema(T schema, long version) - * ... that is, something that is a schema and that wraps a native object. - * - * @param schemaNativePtr the pointer to a native schema object. - * @return true if it will be necessary to call {@code updateSchema} - */ - public boolean requiresMigration(long schemaNativePtr) { - return nativeRequiresMigration(nativePtr, schemaNativePtr); + public RealmConfiguration getConfiguration() { + return configuration; } @Override @@ -497,6 +490,11 @@ private void executePendingRowQueries() { pendingRows.clear(); } + @KeepMember + private void runMigrationCallback(MigrationCallback callback, long oldVersion, long newVersion) { + callback.onMigrationNeeded(this, oldVersion, newVersion); + } + private static native void nativeInit(String temporaryDirectoryPath); // Keep last session as an 'object' to avoid any reference to sync code @@ -564,13 +562,12 @@ private static native long nativeCreateConfig(String realmPath, byte[] key, byte private static native boolean nativeCompact(long nativeSharedRealmPtr); - private static native void nativeUpdateSchema(long nativePtr, long nativeSchemaPtr, long version); + private native void nativeUpdateSchema(long nativePtr, long nativeSchemaPtr, long version, + MigrationCallback callback); private static native void nativeSetAutoRefresh(long nativePtr, boolean enabled); private static native boolean nativeIsAutoRefresh(long nativePtr); - private static native boolean nativeRequiresMigration(long nativePtr, long nativeSchemaPtr); - private static native long nativeGetFinalizerPtr(); } From 5d388ea9d7d238babc5d6f9a57682802df58a198 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 30 Jun 2017 12:54:39 +0100 Subject: [PATCH 0802/2110] fixes #4822 (#4862) * fixes #4822 --- CHANGELOG.md | 2 + .../java/io/realm/SessionTests.java | 2 +- .../java/io/realm/SyncConfigurationTests.java | 1 - .../java/io/realm/SyncManagerTests.java | 5 + .../java/io/realm/SyncUserTests.java | 6 +- .../io/realm/SyncedRealmMigrationTests.java | 7 - .../java/io/realm/util/SyncTestUtils.java | 38 ++++- .../main/cpp/io_realm_RealmFileUserStore.cpp | 14 ++ .../java/io/realm/RealmFileUserStore.java | 10 ++ .../objectServer/java/io/realm/SyncUser.java | 2 +- .../objectServer/java/io/realm/UserStore.java | 10 ++ .../internal/SyncObjectServerFacade.java | 22 ++- .../java/io/realm/objectserver/AuthTests.java | 131 ++++++++++++++++++ 13 files changed, 227 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44b2d7b0e9..72b3901d25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bug Fixes +* [ObjectServer] Fixed a bug related to the behaviour of `SyncUser#logout` and the use of invalid `SyncUser` with `SyncConfiguration` (#4822). + * Fixed a bug in `isNull()`, `isNotNull()`, `isEmpty()`, and `isNotEmpty()` when queries involve nullable fields in link queries (#4856). ### Internal diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 95bb08e745..acf65b49ac 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -56,7 +56,7 @@ public void setUp() { @Test public void get_syncValues() { SyncSession session = new SyncSession(configuration); - assertEquals("realm://objectserver.realm.io/JohnDoe/default", session.getServerUrl().toString()); + assertEquals("realm://objectserver.realm.io/" + user.getIdentity() + "/default", session.getServerUrl().toString()); assertEquals(user, session.getUser()); assertEquals(configuration, session.getConfiguration()); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 0ab6661ff9..c91a7e2def 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -34,7 +34,6 @@ import java.util.HashMap; import java.util.Map; -import io.realm.entities.AllJavaTypes; import io.realm.entities.StringOnly; import io.realm.rule.RunInLooperThread; import io.realm.rule.TestSyncConfigurationFactory; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 25caf4fb6a..e49a13e89b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -68,6 +68,10 @@ public Collection allUsers() { return null; } + @Override + public boolean isActive(String identity) { + return true; + } }; } @@ -139,6 +143,7 @@ public void loggedOut(SyncUser user) { assertEquals(0, counter[0]); assertEquals(0, counter[1]); } + @Test public void session() throws IOException { SyncUser user = createTestUser(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 237618dd48..32f8b61834 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -20,7 +20,7 @@ import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; -import org.junit.After; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Rule; @@ -74,8 +74,8 @@ public static void initUserStore() { SyncManager.setUserStore(userStore); } - @After - public void tearDown() { + @Before + public void setUp() { SyncManager.reset(); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index 9d3a57694d..927a5f5021 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -16,15 +16,10 @@ package io.realm; -import android.content.Context; -import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import java.io.FileNotFoundException; @@ -33,8 +28,6 @@ import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; import io.realm.exceptions.RealmMigrationNeededException; -import io.realm.log.RealmLog; -import io.realm.rule.TestRealmConfigurationFactory; import io.realm.rule.TestSyncConfigurationFactory; import io.realm.util.SyncTestUtils; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index 8d0a12d01f..87d856956e 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -28,6 +28,7 @@ import io.realm.ObjectServerError; import io.realm.SyncManager; import io.realm.SyncUser; +import io.realm.UserStore; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; @@ -37,7 +38,6 @@ public class SyncTestUtils { public static final String USER_TOKEN = UUID.randomUUID().toString(); public static final String REALM_TOKEN = UUID.randomUUID().toString(); public static final String DEFAULT_AUTH_URL = "http://objectserver.realm.io/auth"; - public static final String DEFAULT_USER_IDENTIFIER = "JohnDoe"; private final static Method SYNC_MANAGER_RESET_METHOD; static { @@ -49,6 +49,16 @@ public class SyncTestUtils { } } + private final static Method SYNC_MANAGER_GET_USER_STORE_METHOD; + static { + try { + SYNC_MANAGER_GET_USER_STORE_METHOD = SyncManager.class.getDeclaredMethod("getUserStore"); + SYNC_MANAGER_GET_USER_STORE_METHOD.setAccessible(true); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + public static SyncUser createRandomTestUser() { return createTestUser(UUID.randomUUID().toString(), UUID.randomUUID().toString(), @@ -59,19 +69,19 @@ public static SyncUser createRandomTestUser() { } public static SyncUser createTestAdminUser() { - return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, Long.MAX_VALUE, true); + return createTestUser(USER_TOKEN, REALM_TOKEN, UUID.randomUUID().toString(), DEFAULT_AUTH_URL, Long.MAX_VALUE, true); } public static SyncUser createTestUser() { - return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, Long.MAX_VALUE, false); + return createTestUser(USER_TOKEN, REALM_TOKEN, UUID.randomUUID().toString(), DEFAULT_AUTH_URL, Long.MAX_VALUE, false); } public static SyncUser createTestUser(long expires) { - return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, DEFAULT_AUTH_URL, expires, false); + return createTestUser(USER_TOKEN, REALM_TOKEN, UUID.randomUUID().toString(), DEFAULT_AUTH_URL, expires, false); } public static SyncUser createTestUser(String authUrl) { - return createTestUser(USER_TOKEN, REALM_TOKEN, DEFAULT_USER_IDENTIFIER, authUrl, Long.MAX_VALUE, false); + return createTestUser(USER_TOKEN, REALM_TOKEN, UUID.randomUUID().toString(), authUrl, Long.MAX_VALUE, false); } public static SyncUser createNamedTestUser(String userIdentifier) { @@ -94,7 +104,12 @@ public static SyncUser createTestUser(String userTokenValue, String realmTokenVa obj.put("authUrl", authUrl); obj.put("userToken", userToken.toJson()); obj.put("realms", realmList); - return SyncUser.fromJson(obj.toString()); + SyncUser syncUser = SyncUser.fromJson(obj.toString()); + // persist the user to the ObjectStore sync metadata, to simulate real login, otherwise SyncUser.isValid will + // "throw IllegalArgumentException: User not authenticated or authentication expired." since + // the call to SyncManager.getUserStore().isActive(syncUser.getIdentity()) will return false + addToUserStore(syncUser); + return syncUser; } catch (JSONException e) { throw new RuntimeException(e); } @@ -139,4 +154,15 @@ public static void resetSyncMetadata() { throw new AssertionError(e); } } + + private static void addToUserStore(SyncUser user) { + try { + UserStore userStore = (UserStore) SYNC_MANAGER_GET_USER_STORE_METHOD.invoke(null); + userStore.put(user); + } catch (InvocationTargetException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index e2eaa319cd..31d77f1e36 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -85,6 +85,20 @@ JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeLogoutUser(JNIEnv* CATCH_STD() } +JNIEXPORT jboolean JNICALL Java_io_realm_RealmFileUserStore_nativeIsActive(JNIEnv* env, jclass, jstring j_identity) +{ + TR_ENTER() + try { + JStringAccessor identity(env, j_identity); // throws + const std::shared_ptr& user = SyncManager::shared().get_existing_logged_in_user(identity); + if (user) { + return to_jbool(user->state() == SyncUser::State::Active); + } + } + CATCH_STD() + return JNI_FALSE; +} + JNIEXPORT jobjectArray JNICALL Java_io_realm_RealmFileUserStore_nativeGetAllUsers(JNIEnv* env, jclass) { TR_ENTER() diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java index e208131397..8464445c5b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java @@ -77,6 +77,14 @@ public Collection allUsers() { return Collections.emptyList(); } + /** + * {@inheritDoc} + */ + @Override + public boolean isActive(String identity) { + return nativeIsActive(identity); + } + private static SyncUser toSyncUserOrNull(String userJson) { if (userJson == null) { return null; @@ -95,4 +103,6 @@ private static SyncUser toSyncUserOrNull(String userJson) { protected static native void nativeUpdateOrCreateUser(String identity, String jsonToken, String url); protected static native void nativeLogoutUser(String identity); + + protected static native boolean nativeIsActive(String identity); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 94ea8c6a68..e709dd5da8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -458,7 +458,7 @@ public String toJson() { */ public boolean isValid() { Token userToken = getSyncUser().getUserToken(); - return syncUser.isLoggedIn() && userToken != null && userToken.expiresMs() > System.currentTimeMillis(); + return syncUser.isLoggedIn() && userToken != null && userToken.expiresMs() > System.currentTimeMillis() && SyncManager.getUserStore().isActive(syncUser.getIdentity()); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java index 7a7b488337..9d744157dc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java +++ b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java @@ -68,4 +68,14 @@ public interface UserStore { * @return Collection of all users. If no users exist, an empty collection is returned. */ Collection allUsers(); + + /** + * Returns the state of the specified user: {@code true} if active (not logged out), {@code false} otherwise. + * This method checks if the user was marked as logged out. If the user has expired but not actively logged out + * this method will return {@code true}. + * + * @param identity identity of the user. + * @return {@code true} if the user is not logged out, {@code false} otherwise. + */ + boolean isActive(String identity); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 60151daf6d..d70160e7fb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -28,9 +28,11 @@ import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.SyncSession; +import io.realm.SyncUser; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; import io.realm.internal.network.NetworkStateReceiver; +import io.realm.log.RealmLog; @SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep @@ -86,11 +88,23 @@ public void realmClosed(RealmConfiguration configuration) { public Object[] getUserAndServerUrl(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; + // make sure the user is still valid + SyncUser user = syncConfig.getUser(); + if (!user.isValid()) { + if (user.getAccessToken() == null) { + throw new IllegalStateException("The SyncUser is already logged out and can not use the provided configuration to open a Realm."); + } else { + // user was not logged out but the `refresh_token` is not longer valid + // the user will still get a stall version of Realm, that will work offline + // but not sync. + RealmLog.warn("Can not use the provided configuration to open a Realm, the SyncUser is no longer valid."); + } + } String rosServerUrl = syncConfig.getServerUrl().toString(); - String rosUserIdentity = syncConfig.getUser().getIdentity(); - String syncRealmAuthUrl = syncConfig.getUser().getAuthenticationUrl().toString(); - String rosRefreshToken = syncConfig.getUser().getAccessToken().value(); - return new Object[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosRefreshToken, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath()}; + String rosUserIdentity = user.getIdentity(); + String syncRealmAuthUrl = user.getAuthenticationUrl().toString(); + String rosSerializedUser = user.toJson(); + return new Object[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosSerializedUser, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath()}; } else { return new Object[6]; } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index a2ed00777c..83bf65cbb2 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -11,6 +11,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.UUID; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -18,6 +19,7 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.SyncConfiguration; import io.realm.SyncCredentials; import io.realm.SyncManager; @@ -28,9 +30,11 @@ import io.realm.rule.RunTestInLooperThread; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; @RunWith(AndroidJUnit4.class) @@ -266,4 +270,131 @@ public void changePassword_throwWhenUserIsLoggedOut() { user.changePassword("new-password"); } + // Cached instances of RealmConfiguration should not be allowed to be used if the user is no longer valid + @Test + public void cachedInstanceShouldThrowIfUserBecomeInvalid() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + + SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final RealmConfiguration configuration = new SyncConfiguration.Builder(user, Constants.USER_REALM).build(); + Realm realm = Realm.getInstance(configuration); + + user.logout(); + assertFalse(user.isValid()); + + final CountDownLatch backgroundThread = new CountDownLatch(1); + // Should throw when using the invalid configuration form a different thread + new Thread() { + @Override + public void run() { + try { + Realm.getInstance(configuration); + fail("Invalid SyncConfiguration should throw"); + } catch (IllegalStateException expected) { + } finally { + backgroundThread.countDown(); + } + } + }.start(); + + backgroundThread.await(); + + // it is ok to return the cached instance, since this use case is legit + // user refresh token can timeout, or the token can be revoked from ROS + // while running the Realm instance. So it doesn't make sense to break this behaviour + Realm cachedInstance = Realm.getInstance(configuration); + assertNotNull(cachedInstance); + + realm.close(); + cachedInstance.close(); + } + + @Test + public void buildingSyncConfigurationShouldThrowIfInvalidUser() { + String username = UUID.randomUUID().toString(); + String password = "password"; + + SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser currentUser = SyncUser.currentUser(); + user.logout(); + + assertFalse(user.isValid()); + + try { + // We should not be able to build a configuration with an invalid/logged out user + new SyncConfiguration.Builder(user, Constants.USER_REALM).build(); + fail("Invalid user, it should not be possible to create a SyncConfiguration"); + } catch (IllegalArgumentException expected) { + // User not authenticated or authentication expired. + } + + try { + // We should not be able to build a configuration with an invalid/logged out user + new SyncConfiguration.Builder(currentUser, Constants.USER_REALM).build(); + fail("Invalid currentUser, it should not be possible to create a SyncConfiguration"); + } catch (IllegalArgumentException expected) { + // User not authenticated or authentication expired. + } + } + + // using a logout user should throw + @Test + public void usingConfigurationWithInvalidUserShouldThrow() { + String username = UUID.randomUUID().toString(); + String password = "password"; + + SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + RealmConfiguration configuration = new SyncConfiguration.Builder(user, Constants.USER_REALM).build(); + user.logout(); + assertFalse(user.isValid()); + + try { + Realm.getInstance(configuration); + fail("SyncUser is not longer valid, it should not be possible to get a Realm instance"); + } catch (IllegalStateException expected) { + } + } + + // logging out 'user' should have the same impact on other instance(s) of the same user + @Test + public void loggingOutUserShouldImpactOtherInstances() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + + SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser currentUser = SyncUser.currentUser(); + + assertTrue(user.isValid()); + assertEquals(user, currentUser); + + user.logout(); + + assertFalse(user.isValid()); + assertFalse(currentUser.isValid()); + } + + // logging out 'currentUser' should have the same impact on other instance(s) of the user + @Test + public void loggingOutCurrentUserShouldImpactOtherInstances() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + + SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser currentUser = SyncUser.currentUser(); + + assertTrue(user.isValid()); + assertEquals(user, currentUser); + + SyncUser.currentUser().logout(); + + assertFalse(user.isValid()); + assertFalse(currentUser.isValid()); + assertNull(SyncUser.currentUser()); + } } From 691c0f4720156187bb5cde1dfa4cc1272a0aaa11 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 30 Jun 2017 14:18:18 +0100 Subject: [PATCH 0803/2110] [Sync] Adding user account lookup (#4882) --- CHANGELOG.md | 2 + dependencies.list | 2 +- .../objectServer/java/io/realm/ErrorCode.java | 3 + .../objectServer/java/io/realm/SyncUser.java | 77 +++++++++++ .../network/AuthenticationServer.java | 7 + .../network/LookupUserIdResponse.java | 129 ++++++++++++++++++ .../network/OkHttpAuthenticationServer.java | 30 ++++ .../java/io/realm/objectserver/AuthTests.java | 122 +++++++++++++++++ 8 files changed, 371 insertions(+), 1 deletion(-) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b11f311eff..5f4b783c47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ ### Enhancements +* [ObjectServer] Adding user lookup API for administrators (#4828). + ### Bug Fixes * [ObjectServer] Fixed a bug related to the behaviour of `SyncUser#logout` and the use of invalid `SyncUser` with `SyncConfiguration` (#4822). diff --git a/dependencies.list b/dependencies.list index 82afed2cb5..597493e8ca 100644 --- a/dependencies.list +++ b/dependencies.list @@ -10,4 +10,4 @@ REALM_SYNC_SHA256=b48fd48461b563e2a6b1605ec346aca48b64b64a12d42a0f5a61135906d490 # /tools/sync_test_server/Dockerfile specify which repo (apt) we should # install/use between 'realm' and 'realm-testing', the version below should # correspond to an existing version on the *specified* repo. -REALM_OBJECT_SERVER_DE_VERSION=1.7.6-62 +REALM_OBJECT_SERVER_DE_VERSION=1.8.1-149 diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 427e782030..827ce43f28 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -66,6 +66,9 @@ public enum ErrorCode { // 300 - 599 Reserved for Standard HTTP error codes + // user lookup endpoint returns 404 in case it couldn't honor the query + NOT_FOUND(404), + // Realm Authentication Server response errors (600 - 699) INVALID_PARAMETERS(601), MISSING_PARAMETERS(602), diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index e709dd5da8..1ef1f5cf10 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -43,6 +43,7 @@ import io.realm.internal.network.ChangePasswordResponse; import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.LogoutResponse; +import io.realm.internal.network.LookupUserIdResponse; import io.realm.internal.objectserver.ObjectServerUser; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; @@ -426,6 +427,82 @@ public SyncUser run() { }.start(); } + /** + * Helper method for Admin users in order to lookup a {@code SyncUser} using the identity provider and the used username. + * + * @param provider identity providers {@link io.realm.SyncCredentials.IdentityProvider} used when the account was created. + * @param providerId username or email used to create the account for the first time, + * what is needed will depend on what type of {@link SyncCredentials} was used. + * + * @return {@code SyncUser} associated with the given identity provider and providerId, or {@code null} in case + * of an {@code invalid} provider or {@code providerId}. + * @throws ObjectServerError in case of an error. + */ + public SyncUser retrieveUser(final String provider, final String providerId) throws ObjectServerError { + if (Util.isEmptyString(provider)) { + throw new IllegalArgumentException("Not-null 'provider' required."); + } + + if (Util.isEmptyString(providerId)) { + throw new IllegalArgumentException("None empty 'providerId' required."); + } + + if (!isAdmin()) { + throw new IllegalArgumentException("SyncUser needs to be admin in order to lookup other users ID."); + } + + AuthenticationServer authServer = SyncManager.getAuthServer(); + LookupUserIdResponse response = authServer.retrieveUser(getSyncUser().getUserToken(), provider, providerId, getAuthenticationUrl()); + if (!response.isValid()) { + // the endpoint returns a 404 if it can't honor the query, either because + // - provider is not valid + // - provider_id is not valid + // - token used is not an admin one + // in this case we should return null instead of throwing + if (response.getError().getErrorCode() == ErrorCode.NOT_FOUND) { + return null; + } else { + throw response.getError(); + } + } else { + SyncUser syncUser = SyncManager.getUserStore().get(response.getUserId()); + if (syncUser != null) { + return syncUser; + } else { + // build an SynUser without a token + Token refreshToken = new Token(null, response.getUserId(), null, 0, null, response.isAdmin()); + ObjectServerUser objectServerUser = new ObjectServerUser(refreshToken, getAuthenticationUrl()); + objectServerUser.localLogout(); + return new SyncUser(objectServerUser); + } + } + } + + /** + * Asynchronously lookup a {@code SyncUser} using the identity provider and the used username. + * This is for Admin users only. + * + * @param provider identity providers {@link io.realm.SyncCredentials.IdentityProvider} used when the account was created. + * @param providerId username or email used to create the account for the first time, + * what is needed will depend on what type of {@link SyncCredentials} was used. + * @param callback callback when the lookup has completed or failed. The callback will always happen on the same thread + * as this method is called on. + * @return representation of the async task that can be used to cancel it if needed. + */ + public RealmAsyncTask retrieveUserAsync(final String provider, final String providerId, final Callback callback) { + checkLooperThread("Asynchronously retrieving user id is only possible from looper threads."); + if (callback == null) { + throw new IllegalArgumentException("Non-null 'callback' required."); + } + + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public SyncUser run() { + return retrieveUser(provider, providerId); + } + }.start(); + } + private static void checkLooperThread(String errorMessage) { AndroidCapabilities capabilities = new AndroidCapabilities(); capabilities.checkCanDeliverNotification(errorMessage); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java index 869e39d8b6..8906515e83 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java @@ -66,4 +66,11 @@ public interface AuthenticationServer { * Changes a user's password using admin account. */ ChangePasswordResponse changePassword(Token adminToken, String userID, String newPassword, URL authenticationUrl); + + /** + * Looks up a {@code SyncUser} using the identity provider {@link io.realm.SyncCredentials.IdentityProvider} + * used when the account was created and the username or email used to create the account for the first time + * what is needed will depend on what type of {@link SyncCredentials} was used. + */ + LookupUserIdResponse retrieveUser(Token adminToken, String provider, String providerId, URL authenticationUrl); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java new file mode 100644 index 0000000000..03f162131e --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java @@ -0,0 +1,129 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Locale; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.log.RealmLog; +import okhttp3.Response; + +/** + * Class wrapping the response from `GET /api/providers/:provider/accounts/:provider_id` + */ +public class LookupUserIdResponse extends AuthServerResponse { + + private static final String JSON_FIELD_USER = "user"; + private static final String JSON_FIELD_USER_ID = "id"; + private static final String JSON_FIELD_USER_IS_ADMIN = "isAdmin"; + + private final String userId; + private final Boolean isAdmin; + + /** + * Helper method for creating the proper lookup user response. This method will set the appropriate error + * depending on any HTTP response codes or I/O errors. + * + * @param response the server response. + * @return the user lookup response. + */ + static LookupUserIdResponse from(Response response) { + String serverResponse; + try { + serverResponse = response.body().string(); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new LookupUserIdResponse(error); + } + if (!response.isSuccessful()) { + return new LookupUserIdResponse(AuthServerResponse.createError(serverResponse, response.code())); + } else { + return new LookupUserIdResponse(serverResponse); + } + } + + /** + * Helper method for creating a failed response. + */ + public static LookupUserIdResponse from(ObjectServerError objectServerError) { + return new LookupUserIdResponse(objectServerError); + } + + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static LookupUserIdResponse from(Exception exception) { + return LookupUserIdResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + + private LookupUserIdResponse(ObjectServerError error) { + RealmLog.debug("LookupUserIdResponse - Error: " + error); + setError(error); + this.error = error; + this.userId = null; + this.isAdmin = null; + } + + private LookupUserIdResponse(String serverResponse) { + ObjectServerError error; + String userId; + Boolean isAdmin; + String message; + try { + JSONObject obj = new JSONObject(serverResponse); + JSONObject jsonUser = obj.getJSONObject(JSON_FIELD_USER); + if (jsonUser != null) { + userId = jsonUser.optString(JSON_FIELD_USER_ID, null); + // can not use optBoolean since `null` is not permitted as default value + // (we need it for the Boolean boxed type) + isAdmin = jsonUser.has(JSON_FIELD_USER_IS_ADMIN) ? jsonUser.getBoolean(JSON_FIELD_USER_IS_ADMIN) : null; + error = null; + + message = String.format(Locale.US, "Identity %s; Path %b", userId, isAdmin); + + } else { + userId = null; + isAdmin = null; + error = null; + message = "user = null"; + } + + } catch (JSONException e) { + userId = null; + isAdmin = null; + error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); + message = String.format(Locale.US, "Error %s", error.getErrorMessage()); + } + + RealmLog.debug("LookupUserIdResponse. " + message); + setError(error); + this.userId = userId; + this.isAdmin = isAdmin; + } + + public String getUserId() { + return userId; + } + + public boolean isAdmin() { + return isAdmin; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 1c7bc4afb0..35f49c92b4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -36,6 +36,7 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final String ACTION_LOGOUT = "revoke"; // Auth end point for logging out users private static final String ACTION_CHANGE_PASSWORD = "password"; // Auth end point for changing passwords + private static final String ACTION_LOOKUP_USER_ID = "api/providers"; // Auth end point for looking up user id private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) @@ -106,6 +107,15 @@ public ChangePasswordResponse changePassword(Token adminToken, String userId, St } } + @Override + public LookupUserIdResponse retrieveUser(Token adminToken, String provider, String providerId, URL authenticationUrl) { + try { + return lookupUserId(buildLookupUserIdUrl(authenticationUrl, ACTION_LOOKUP_USER_ID, provider, providerId), adminToken.value()); + } catch (Exception e) { + return LookupUserIdResponse.from(e); + } + } + // Builds the URL for a specific auth endpoint private static URL buildActionUrl(URL authenticationUrl, String action) { final String baseUrlString = authenticationUrl.toExternalForm(); @@ -117,6 +127,18 @@ private static URL buildActionUrl(URL authenticationUrl, String action) { } } + private static URL buildLookupUserIdUrl(URL authenticationUrl, String action, String provider, String providerId) { + String authURL = authenticationUrl.toExternalForm(); + // we need the base URL without the '/auth' part + String baseUrlString = authURL.substring(0, authURL.indexOf(authenticationUrl.getPath())); + try { + String separator = baseUrlString.endsWith("/") ? "" : "/"; + return new URL(baseUrlString + separator + action + "/" + provider + "/accounts/" + providerId); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + private AuthenticateResponse authenticate(URL authenticationUrl, String requestBody) throws Exception { RealmLog.debug("Network request (authenticate): " + authenticationUrl); Request request = newAuthRequest(authenticationUrl).post(RequestBody.create(JSON, requestBody)).build(); @@ -141,6 +163,14 @@ private ChangePasswordResponse changePassword(URL changePasswordUrl, String requ return ChangePasswordResponse.from(response); } + private LookupUserIdResponse lookupUserId(URL lookupUserIdUrl, String token) throws Exception { + RealmLog.debug("Network request (lookupUserId): " + lookupUserIdUrl); + Request request = newAuthRequest(lookupUserIdUrl).get().header("Authorization", token).build(); + Call call = client.newCall(request); + Response response = call.execute(); + return LookupUserIdResponse.from(response); + } + private Request.Builder newAuthRequest(URL url) { return new Request.Builder() .url(url) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 83bf65cbb2..be0a22499f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -397,4 +397,126 @@ public void loggingOutCurrentUserShouldImpactOtherInstances() throws Interrupted assertFalse(currentUser.isValid()); assertNull(SyncUser.currentUser()); } + + @Test + public void retrieve() { + final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + + final String username = UUID.randomUUID().toString(); + final String password = "password"; + final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + final SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + assertTrue(user.isValid()); + + String identity = user.getIdentity(); + SyncUser syncUser = adminUser.retrieveUser(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, username); + assertNotNull(syncUser); + assertEquals(identity, syncUser.getIdentity()); + assertFalse(syncUser.isAdmin()); + assertTrue(syncUser.isValid()); + } + + @Test + public void retrieve_logout() { + final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + + final String username = UUID.randomUUID().toString(); + final String password = "password"; + final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + final SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final String identity = user.getIdentity(); + user.logout(); + assertFalse(user.isValid()); + + SyncUser syncUser = adminUser.retrieveUser(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, username); + assertNotNull(syncUser); + assertEquals(identity, syncUser.getIdentity()); + assertFalse(syncUser.isAdmin()); + assertFalse(syncUser.isValid()); + } + + @Test + public void retrieve_AdminUser() { + final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + SyncUser syncUser = adminUser.retrieveUser(SyncCredentials.IdentityProvider.DEBUG, "admin");// TODO use enum for auth provider + assertNotNull(syncUser); + assertEquals(adminUser.getIdentity(), syncUser.getIdentity()); + assertTrue(syncUser.isAdmin()); + assertTrue(syncUser.isValid()); + } + + @Test + public void retrieve_unknownProviderId() { + final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + SyncUser syncUser = adminUser.retrieveUser(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, "doesNotExist"); + assertNull(syncUser); + } + + @Test + public void retrieve_invalidProvider() { + final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + final String username = UUID.randomUUID().toString(); + final String password = "password"; + final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + final SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + assertTrue(user.isValid()); + + SyncUser syncUser = adminUser.retrieveUser("invalid", "username"); + assertNull(syncUser); + } + + @Test + public void retrieve_notAdmin() { + final String username1 = UUID.randomUUID().toString(); + final String password1 = "password"; + final SyncCredentials credentials1 = SyncCredentials.usernamePassword(username1, password1, true); + final SyncUser user1 = SyncUser.login(credentials1, Constants.AUTH_URL); + assertTrue(user1.isValid()); + + final String username2 = UUID.randomUUID().toString(); + final String password2 = "password"; + final SyncCredentials credentials2 = SyncCredentials.usernamePassword(username2, password2, true); + final SyncUser user2 = SyncUser.login(credentials2, Constants.AUTH_URL); + assertTrue(user2.isValid()); + + // trying to lookup user2 using user1 should not work (requires admin token) + try { + user1.retrieveUser(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, username2); + fail("It should not be possible to lookup a user using non admin token"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + @RunTestInLooperThread + public void retrieve_async() { + final String username = UUID.randomUUID().toString(); + final String password = "password"; + final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); + final SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + assertTrue(user.isValid()); + + // Login an admin user + final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + assertTrue(adminUser.isValid()); + assertTrue(adminUser.isAdmin()); + + final String identity = user.getIdentity(); + adminUser.retrieveUserAsync("password", username, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser syncUser) { + + assertNotNull(syncUser); + assertEquals(identity, syncUser.getIdentity()); + assertFalse(syncUser.isAdmin()); + assertTrue(syncUser.isValid()); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.getErrorMessage()); + } + }); + } } From d80488f066a43aeaf38d9c32f651829c00f8b0d9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 30 Jun 2017 19:03:03 +0200 Subject: [PATCH 0804/2110] Fix local_ref_table overflow doing when massive logging from the sync thread. (#4888) --- CHANGELOG.md | 4 ++-- realm/realm-library/src/main/cpp/jni_util/log.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b3901d25..6915f6fbb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,9 @@ ### Bug Fixes -* [ObjectServer] Fixed a bug related to the behaviour of `SyncUser#logout` and the use of invalid `SyncUser` with `SyncConfiguration` (#4822). - * Fixed a bug in `isNull()`, `isNotNull()`, `isEmpty()`, and `isNotEmpty()` when queries involve nullable fields in link queries (#4856). +* Rare crash in `RealmLog` when log level was set to `LogLevel.DEBUG`. +* [ObjectServer] Fixed a bug related to the behaviour of `SyncUser#logout` and the use of invalid `SyncUser` with `SyncConfiguration` (#4822). ### Internal diff --git a/realm/realm-library/src/main/cpp/jni_util/log.cpp b/realm/realm-library/src/main/cpp/jni_util/log.cpp index c90a4d5e25..14026e3ccc 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.cpp @@ -19,6 +19,7 @@ #include #include "jni_util/log.hpp" +#include "jni_util/java_local_ref.hpp" using namespace realm; using namespace realm::jni_util; @@ -91,8 +92,9 @@ void JavaLogger::log(Log::Level level, const char* tag, jthrowable throwable, co // "JNI called with pending exception". This is something that should be avoided when printing log in JNI -- // Always // print log before calling env->ThrowNew. Doing env->ExceptionCheck() here creates overhead for normal cases. - env->CallVoidMethod(m_java_logger, m_log_method, level, env->NewStringUTF(tag), throwable, - env->NewStringUTF(message)); + JavaLocalRef java_tag(env, env->NewStringUTF(tag)); + JavaLocalRef java_error_message(env, env->NewStringUTF(message)); + env->CallVoidMethod(m_java_logger, m_log_method, level, java_tag.get(), throwable, java_error_message.get()); } bool JavaLogger::is_same_object(JNIEnv* env, jobject java_logger) From 7bf4da25643fb0db5551e6a513471c309337ca00 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 2 Jul 2017 17:08:47 +0200 Subject: [PATCH 0805/2110] Fix ErrorProne warnings --- .../io/realm/DynamicRealmObjectTests.java | 5 ++-- .../java/io/realm/DynamicRealmTests.java | 2 +- .../java/io/realm/RealmCacheTests.java | 4 +-- .../io/realm/RealmConfigurationTests.java | 2 +- .../java/io/realm/RealmInMemoryTest.java | 28 +++++++++---------- .../java/io/realm/RealmInterprocessTest.java | 2 +- .../androidTest/java/io/realm/RealmTests.java | 6 ++-- .../io/realm/TypeBasedNotificationsTests.java | 2 +- .../java/io/realm/entities/Thread.java | 1 + .../io/realm/entities/conflict/String.java | 1 + .../io/realm/internal/CollectionTests.java | 14 +++++----- .../java/io/realm/internal/JNIRowTest.java | 2 +- .../realm/internal/ObserverPairListTests.java | 4 +-- .../realm/internal/android/JsonUtilsTest.java | 2 +- .../io/realm/internal/OutOfMemoryError.java | 2 +- .../objectserver/ProcessCommitTests.java | 2 -- 16 files changed, 39 insertions(+), 40 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 3a04a45e27..568e3b29e2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -750,7 +750,7 @@ public void run() { .setObject(AllJavaTypes.FIELD_OBJECT, dObjDynamic); fail(); } catch (IllegalArgumentException expected) { - assertEquals(expected.getMessage(), "Cannot add an object from another Realm instance."); + assertEquals("Cannot add an object from another Realm instance.", expected.getMessage()); } dynamicRealm.cancelTransaction(); @@ -861,8 +861,7 @@ public void run() { dynamicRealm.where(AllJavaTypes.CLASS_NAME).findFirst().setList(AllJavaTypes.FIELD_LIST, list); fail(); } catch (IllegalArgumentException expected) { - assertEquals(expected.getMessage(), - "Each element in 'list' must belong to the same Realm instance."); + assertEquals("Each element in 'list' must belong to the same Realm instance.", expected.getMessage()); } dynamicRealm.cancelTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index b98b5edbfa..7e8d36dd75 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -332,7 +332,7 @@ public void execute(DynamicRealm realm) { }); } catch (RuntimeException ignored) { // Ensures that we pass a valuable error message to the logger for developers. - assertEquals(testLogger.message, "Could not cancel transaction, not currently in a transaction."); + assertEquals("Could not cancel transaction, not currently in a transaction.", testLogger.message); } finally { RealmLog.remove(testLogger); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index 410ce9656f..252dc083a6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -115,7 +115,7 @@ public void getInstanceClearsCacheWhenFailed() { Realm.getInstance(configB); // Tries to open with key 2. fail(); } catch (RealmFileException expected) { - assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR); + assertEquals(RealmFileException.Kind.ACCESS_ERROR, expected.getKind()); // Deletes Realm so key 2 works. This should work as a Realm shouldn't be cached // if initialization failed. assertTrue(Realm.deleteRealm(configA)); @@ -164,7 +164,7 @@ public void dontCacheWrongConfigurations() throws IOException { Realm.getInstance(wrongConfig); fail(); } catch (RealmFileException expected) { - assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR); + assertEquals(RealmFileException.Kind.ACCESS_ERROR, expected.getKind()); } // Tries again with proper key. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index f5ecd5cd4d..8f99d806fa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -905,7 +905,7 @@ public void assetFileFakeFile() { Realm.getInstance(configuration); fail(); } catch (RealmFileException expected) { - assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR); + assertEquals(RealmFileException.Kind.ACCESS_ERROR, expected.getKind()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index 1b41af1114..9f7f26123d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -81,14 +81,14 @@ public void inMemoryRealm() { dog.setName("DinoDog"); testRealm.commitTransaction(); - assertEquals(testRealm.where(Dog.class).count(), 1); - assertEquals(testRealm.where(Dog.class).findFirst().getName(), "DinoDog"); + assertEquals(1, testRealm.where(Dog.class).count()); + assertEquals("DinoDog", testRealm.where(Dog.class).findFirst().getName()); testRealm.close(); // After all references to the in-mem-realm closed, // in-mem-realm with same identifier should create a fresh new instance. testRealm = Realm.getInstance(inMemConf); - assertEquals(testRealm.where(Dog.class).count(), 0); + assertEquals(0, testRealm.where(Dog.class).count()); } // Two in-memory Realms with different names should not affect each other. @@ -110,12 +110,12 @@ public void inMemoryRealmWithDifferentNames() { dog2.setName("UFODog"); testRealm2.commitTransaction(); - assertEquals(testRealm.where(Dog.class).count(), 1); + assertEquals(1, testRealm.where(Dog.class).count()); //noinspection ConstantConditions - assertEquals(testRealm.where(Dog.class).findFirst().getName(), "DinoDog"); - assertEquals(testRealm2.where(Dog.class).count(), 1); + assertEquals("DinoDog", testRealm.where(Dog.class).findFirst().getName()); + assertEquals(1, testRealm2.where(Dog.class).count()); //noinspection ConstantConditions - assertEquals(testRealm2.where(Dog.class).findFirst().getName(), "UFODog"); + assertEquals("UFODog", testRealm2.where(Dog.class).findFirst().getName()); testRealm2.close(); } @@ -161,13 +161,13 @@ public void writeCopyTo() { // Tests a normal Realm file. testRealm.writeCopyTo(new File(configFactory.getRoot(), fileName)); Realm onDiskRealm = Realm.getInstance(conf); - assertEquals(onDiskRealm.where(Dog.class).count(), 1); + assertEquals(1, onDiskRealm.where(Dog.class).count()); onDiskRealm.close(); // Tests a encrypted Realm file. testRealm.writeEncryptedCopyTo(new File(configFactory.getRoot(), encFileName), key); onDiskRealm = Realm.getInstance(encConf); - assertEquals(onDiskRealm.where(Dog.class).count(), 1); + assertEquals(1, onDiskRealm.where(Dog.class).count()); onDiskRealm.close(); // Tests with a wrong key to see if it fails as expected. try { @@ -178,7 +178,7 @@ public void writeCopyTo() { Realm.getInstance(wrongKeyConf); fail("Realm.getInstance should fail with RealmFileException"); } catch (RealmFileException expected) { - assertEquals(expected.getKind(), RealmFileException.Kind.ACCESS_ERROR); + assertEquals(RealmFileException.Kind.ACCESS_ERROR, expected.getKind()); } } @@ -207,7 +207,7 @@ public void run() { realm.commitTransaction(); try { - assertEquals(realm.where(Dog.class).count(), 1); + assertEquals(1, realm.where(Dog.class).count()); } catch (AssertionFailedError afe) { threadError[0] = afe; realm.close(); @@ -237,7 +237,7 @@ public void run() { // Refreshes will be ran in the next loop, manually refreshes it here. testRealm.waitForChange(); - assertEquals(testRealm.where(Dog.class).count(), 1); + assertEquals(1, testRealm.where(Dog.class).count()); // Step 3. // Releases the main thread Realm reference, and the worker thread holds the reference still. @@ -246,7 +246,7 @@ public void run() { // Step 4. // Creates a new Realm reference in main thread and checks the data. testRealm = Realm.getInstance(inMemConf); - assertEquals(testRealm.where(Dog.class).count(), 1); + assertEquals(1, testRealm.where(Dog.class).count()); testRealm.close(); // Let the worker thread continue. @@ -258,6 +258,6 @@ public void run() { // Since all previous Realm instances has been closed before, below will create a fresh new in-mem-realm instance. testRealm = Realm.getInstance(inMemConf); - assertEquals(testRealm.where(Dog.class).count(), 0); + assertEquals(0, testRealm.where(Dog.class).count()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java index 76c5c4c377..56649a1cf1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java @@ -282,7 +282,7 @@ public void testCreateInitialRealm() throws InterruptedException { public void run() { // Step 1 testRealm = Realm.getInstance(new RealmConfiguration.Builder(getContext()).build()); - assertEquals(testRealm.where(AllTypes.class).count(), 0); + assertEquals(0, testRealm.where(AllTypes.class).count()); testRealm.beginTransaction(); testRealm.createObject(AllTypes.class); testRealm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index d6ca5add85..ae80d7b7d4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -213,7 +213,7 @@ public void getInstance_writeProtectedFile() throws IOException { .build()); fail(); } catch (RealmFileException expected) { - assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); + assertEquals(RealmFileException.Kind.PERMISSION_DENIED, expected.getKind()); } } @@ -230,7 +230,7 @@ public void getInstance_writeProtectedFileWithContext() throws IOException { Realm.getInstance(new RealmConfiguration.Builder(context).directory(folder).name(REALM_FILE).build()); fail(); } catch (RealmFileException expected) { - assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); + assertEquals(RealmFileException.Kind.PERMISSION_DENIED, expected.getKind()); } } @@ -708,7 +708,7 @@ public void execute(Realm realm) { }); } catch (RuntimeException ignored) { // Ensures that we pass a valuable error message to the logger for developers. - assertEquals(testLogger.message, "Could not cancel transaction, not currently in a transaction."); + assertEquals("Could not cancel transaction, not currently in a transaction.", testLogger.message); } finally { RealmLog.remove(testLogger); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index aa4c01fc54..86067e4bb5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -877,7 +877,7 @@ public void onChange(Realm object) { looperThread.postRunnable(new Runnable() { @Override public void run() { - assertEquals(typebasedCommitInvocations.get(), 1); + assertEquals(1, typebasedCommitInvocations.get()); looperThread.testComplete(); } }); diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/Thread.java b/realm/realm-library/src/androidTest/java/io/realm/entities/Thread.java index 8bad4e5472..5509f2f8bb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/Thread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/Thread.java @@ -18,6 +18,7 @@ import io.realm.RealmObject; +@SuppressWarnings("JavaLangClash") public class Thread extends RealmObject { private String name; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/conflict/String.java b/realm/realm-library/src/androidTest/java/io/realm/entities/conflict/String.java index 7b57935d58..e6d10a2709 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/conflict/String.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/conflict/String.java @@ -19,6 +19,7 @@ import io.realm.RealmList; import io.realm.RealmObject; +@SuppressWarnings("JavaLangClash") public class String extends RealmObject { public String str; public RealmList strList; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 673bff18d0..0457739f03 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -135,10 +135,10 @@ public void constructor_withDistinct() { SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(null, table, "firstName"); Collection collection = new Collection(sharedRealm, table.where(), null, distinctDescriptor); - assertEquals(collection.size(), 3); - assertEquals(collection.getUncheckedRow(0).getString(0), "John"); - assertEquals(collection.getUncheckedRow(1).getString(0), "Erik"); - assertEquals(collection.getUncheckedRow(2).getString(0), "Henry"); + assertEquals(3, collection.size()); + assertEquals("John", collection.getUncheckedRow(0).getString(0)); + assertEquals("Erik", collection.getUncheckedRow(1).getString(0)); + assertEquals("Henry", collection.getUncheckedRow(2).getString(0)); } @@ -192,8 +192,8 @@ public void sort() { assertEquals(2, collection.size()); assertEquals(2, collection2.size()); - assertEquals(collection2.getUncheckedRow(0).getLong(2), 3); - assertEquals(collection2.getUncheckedRow(1).getLong(2), 4); + assertEquals(3, collection2.getUncheckedRow(0).getLong(2)); + assertEquals(4, collection2.getUncheckedRow(1).getLong(2)); } @Test @@ -366,7 +366,7 @@ public void addListener_queryReturned() { final Collection collection = new Collection(sharedRealm, table.where()); looperThread.keepStrongReference(collection); - assertEquals(collection.size(), 4); // Trigger the query to run. + assertEquals(4, collection.size()); // Trigger the query to run. collection.addListener(collection, new RealmChangeListener() { @Override public void onChange(Collection collection1) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java index 93bf8332ce..371f8f2176 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java @@ -134,7 +134,7 @@ public void nullValues() { UncheckedRow row = table.getUncheckedRow(rowIndex); row.setString(colStringIndex, "test"); - assertEquals(row.getString(colStringIndex), "test"); + assertEquals("test", row.getString(colStringIndex)); row.setNull(colStringIndex); assertNull(row.getString(colStringIndex)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java index a36ec5b8df..fb77df3b1e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java @@ -39,8 +39,8 @@ @RunWith(AndroidJUnit4.class) public class ObserverPairListTests { - private static class TestListener { - void onChange(Integer integer) { + private static class TestListener { + void onChange(T integer) { } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java index 42958d93fc..9d8a431a99 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java @@ -48,7 +48,7 @@ public void testParseJsonDateToDate() { String jsonDate = "/Date(1198908717056)/"; // 2007-12-27T23:11:57.056 Date output = JsonUtils.stringToDate(jsonDate); - assertEquals(output.getTime(), 1198908717056L); + assertEquals(1198908717056L, output.getTime()); } public void testNegativeLongDate() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OutOfMemoryError.java b/realm/realm-library/src/main/java/io/realm/internal/OutOfMemoryError.java index 88b664f797..f74c99e86f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OutOfMemoryError.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OutOfMemoryError.java @@ -21,7 +21,7 @@ * Can be thrown when Realm runs out of memory. * A JVM that catches this will be able to cleanup, e.g. release other resources to avoid also running out of memory. */ -@SuppressWarnings("serial") +@SuppressWarnings({"serial", "JavaLangClash"}) @Keep public class OutOfMemoryError extends Error { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 1a086c3079..4620330152 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -51,8 +51,6 @@ @RunWith(AndroidJUnit4.class) public class ProcessCommitTests extends BaseIntegrationTest { - @Rule - public RunInLooperThread looperThread = new RunInLooperThread(); @Rule public RunWithRemoteService remoteService = new RunWithRemoteService(); From 4e46954996a8027350f906ad48bd5edcdd1ee760 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 3 Jul 2017 12:53:30 +0800 Subject: [PATCH 0806/2110] typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b61ad488e8..6cba81f7ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ### Enhancements -* Added more detailed excpetion message for `RealmMigrationNeeded`. +* Added more detailed exception message for `RealmMigrationNeeded`. ### Bug Fixes From dfa90542ac21894de10e7cd3d461b8a6e38df3ba Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 3 Jul 2017 13:44:28 +0800 Subject: [PATCH 0807/2110] Exception def --- .../src/main/cpp/io_realm_internal_SharedRealm.cpp | 9 ++++++++- realm/realm-library/src/main/cpp/java_exception_def.cpp | 1 + realm/realm-library/src/main/cpp/java_exception_def.hpp | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index b444027253..8217e2af18 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -611,6 +611,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNI "(Lio/realm/internal/SharedRealm$MigrationCallback;JJ)V"); migration_function = [&env, &j_shared_realm, &j_migration_callback, &shared_realm, &version](SharedRealm old_realm, SharedRealm realm, Schema&) { + // We rely on the behaviour that Object Store will share_from_this from the original Realm and + // begin_transaction on it. So the realm passed from OS here will be the same realm instance which + // j_shared_realm is holding. REALM_ASSERT_RELEASE(shared_realm == realm); env->CallVoidMethod(j_shared_realm, run_migration_callback_method, j_migration_callback, old_realm->schema_version(), version); @@ -623,7 +626,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNI if (env->ExceptionCheck()) { return; } - static JavaClass migration_needed_class(env, "io/realm/exceptions/RealmMigrationNeededException"); + static JavaClass migration_needed_class(env, JavaExceptionDef::RealmMigrationNeeded); static JavaMethod constructor(env, migration_needed_class, "", "(Ljava/lang/String;Ljava/lang/String;)V"); @@ -633,6 +636,10 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeUpdateSchema(JNI env->Throw(reinterpret_cast(migration_needed_exception)); } catch (InvalidSchemaVersionException& e) { + // An exception has been thrown in the migration block. + if (env->ExceptionCheck()) { + return; + } // To match the old behaviour. Otherwise it will be converted to ISE in the CATCH_STD. ThrowException(env, IllegalArgument, e.what()); } diff --git a/realm/realm-library/src/main/cpp/java_exception_def.cpp b/realm/realm-library/src/main/cpp/java_exception_def.cpp index 81246e4483..aea9dbf904 100644 --- a/realm/realm-library/src/main/cpp/java_exception_def.cpp +++ b/realm/realm-library/src/main/cpp/java_exception_def.cpp @@ -20,3 +20,4 @@ using namespace realm::_impl; const char* JavaExceptionDef::IllegalState = "java/lang/IllegalStateException"; const char* JavaExceptionDef::IllegalArgument = "java/lang/IllegalArgumentException"; +const char* JavaExceptionDef::RealmMigrationNeeded = "io/realm/exceptions/RealmMigrationNeededException"; diff --git a/realm/realm-library/src/main/cpp/java_exception_def.hpp b/realm/realm-library/src/main/cpp/java_exception_def.hpp index 9842d185f4..0db02f93ef 100644 --- a/realm/realm-library/src/main/cpp/java_exception_def.hpp +++ b/realm/realm-library/src/main/cpp/java_exception_def.hpp @@ -26,6 +26,7 @@ class JavaExceptionDef { // Class names static const char* IllegalState; static const char* IllegalArgument; + static const char* RealmMigrationNeeded; }; } // namespace realm From 8ba9b5fc853aff0e9eb8aeab5d7e826f404d5a81 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 3 Jul 2017 14:02:04 +0800 Subject: [PATCH 0808/2110] dynamic realm in migration --- .../realm-library/src/main/java/io/realm/BaseRealm.java | 9 +++++++-- .../src/main/java/io/realm/DynamicRealm.java | 9 ++++++++- realm/realm-library/src/main/java/io/realm/Realm.java | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index c8ed259e7c..b49f5b689d 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -75,6 +75,7 @@ abstract class BaseRealm implements Closeable { // cache. It is also null if the Realm is closed. private RealmCache realmCache; protected SharedRealm sharedRealm; + private boolean shouldCloseSharedRealm; protected final RealmSchema schema; @@ -100,15 +101,19 @@ public void onSchemaVersionChanged(long currentVersion) { } } }, true); + this.shouldCloseSharedRealm = true; this.schema = new RealmSchema(this); } + // Create a realm instance directly from a SharedRealm instance. This instance doesn't have the ownership of the + // given SharedRealm instance. The SharedRealm instance should not be closed when close() called. BaseRealm(SharedRealm sharedRealm) { this.threadId = Thread.currentThread().getId(); this.configuration = sharedRealm.getConfiguration(); this.realmCache = null; this.sharedRealm = sharedRealm; + this.shouldCloseSharedRealm = false; this.schema = new RealmSchema(this); } @@ -476,7 +481,7 @@ public void close() { */ void doClose() { realmCache = null; - if (sharedRealm != null) { + if (sharedRealm != null && shouldCloseSharedRealm) { sharedRealm.close(); sharedRealm = null; } @@ -693,7 +698,7 @@ public void onMigrationNeeded(SharedRealm sharedRealm, long oldVersion, long new @Override protected void finalize() throws Throwable { - if (sharedRealm != null && !sharedRealm.isClosed()) { + if (sharedRealm != null && !sharedRealm.isClosed() && shouldCloseSharedRealm) { RealmLog.warn("Remember to call close() on all Realm instances. " + "Realm %s is being finalized without being closed, " + "this can lead to running out of native memory.", configuration.getPath() diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 9b7f9213d2..4139cd0b26 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -244,7 +244,7 @@ static DynamicRealm createInstance(RealmCache cache) { } /** - * Create a {@link DynamicRealm} instance without associating it to any RealmCache. + * Creates a {@link DynamicRealm} instance without associating it to any RealmCache. * * @return a {@link DynamicRealm} instance. */ @@ -252,6 +252,13 @@ static DynamicRealm createInstance(RealmConfiguration configuration) { return new DynamicRealm(configuration); } + /** + * Creates a {@link DynamicRealm} instance with a given {@link SharedRealm} instance without owning it. + * This is designed to be used in the migration block when opening a typed Realm instance. + * + * @param sharedRealm the existing {@link SharedRealm} instance. + * @return a {@link DynamicRealm} instance. + */ static DynamicRealm createInstance(SharedRealm sharedRealm) { return new DynamicRealm(sharedRealm); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index b00f03b023..027a37a7d8 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -444,6 +444,7 @@ private static void initializeRealm(Realm realm) { migrationCallback = new SharedRealm.MigrationCallback() { @Override public void onMigrationNeeded(SharedRealm sharedRealm, long oldVersion, long newVersion) { + // The sharedRealm here is the same instance of the typed Realm owned. No need to close it. configuration.getMigration().migrate(DynamicRealm.createInstance(sharedRealm), oldVersion, newVersion); } From 8da715595841e74d49910911cb4a78a557d073f1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 3 Jul 2017 14:13:54 +0800 Subject: [PATCH 0809/2110] add javadoc --- .../java/io/realm/internal/SharedRealm.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index d9c831e754..427aebc32a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -168,7 +168,20 @@ public interface SchemaVersionListener { void onSchemaVersionChanged(long currentVersion); } + /** + * The migration callback which will be called when the expected schema doesn't match the existing one in + * {@link #updateSchema(OsSchemaInfo, long, MigrationCallback)}. + */ public interface MigrationCallback { + + /** + * Call back function. + * + * @param sharedRealm the same {@link SharedRealm} instance of which + * {@link #updateSchema(OsSchemaInfo, long, MigrationCallback)} was called on. + * @param oldVersion the schema version of the existing Realm file. + * @param newVersion the expected schema version after migration. + */ void onMigrationNeeded(SharedRealm sharedRealm, long oldVersion, long newVersion); } @@ -375,6 +388,7 @@ public boolean compact() { * * @param schemaInfo the expected schema. * @param version the target version. + * @param migrationCallback the callback will be called when the schema doesn't match. */ public void updateSchema(OsSchemaInfo schemaInfo, long version, MigrationCallback migrationCallback) { nativeUpdateSchema(nativePtr, schemaInfo.getNativePtr(), version, migrationCallback); @@ -490,6 +504,13 @@ private void executePendingRowQueries() { pendingRows.clear(); } + /** + * Called from JNI when the expected schema doesn't match the existing one. + * + * @param callback the {@link MigrationCallback} in the {@link RealmConfiguration}. + * @param oldVersion the schema version of the existing Realm file. + * @param newVersion the expected schema version after migration. + */ @KeepMember private void runMigrationCallback(MigrationCallback callback, long oldVersion, long newVersion) { callback.onMigrationNeeded(this, oldVersion, newVersion); From 2fd558db3db2eaa517fdcb75c905af343a42cd89 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 3 Jul 2017 09:00:12 +0200 Subject: [PATCH 0810/2110] Better control of ROS instance used by the integration tests (#4874) --- .../java/io/realm/BaseIntegrationTest.java | 19 +++-- tools/sync_test_server/ros-testing-server.js | 83 ++++++++++--------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 9f86b3200e..8faa03f76b 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -55,7 +55,7 @@ public class BaseIntegrationTest { public final ExpectedException thrown = ExpectedException.none(); @BeforeClass - public static void setUp () throws Exception { + public static void setupTestClass() throws Exception { SyncManager.Debug.skipOnlineChecking = true; try { HttpUtils.startSyncServer(); @@ -67,7 +67,7 @@ public static void setUp () throws Exception { } @AfterClass - public static void tearDown () throws Exception { + public static void tearDownTestClass() throws Exception { try { HttpUtils.stopSyncServer(); } catch (Exception e) { @@ -77,8 +77,6 @@ public static void tearDown () throws Exception { @Before public void setupTest() throws IOException { - // TODO We should implement a more consistent reset method for all of Sync that reset - // everything completely including deleting all files. deleteRosFiles(); if (BaseRealm.applicationContext != null) { // Realm was already initialized. Reset all internal state @@ -98,21 +96,28 @@ public void setupTest() throws IOException { } @After - public void tearDownTest() throws IOException { + public void teardownTest() { if (looperThread.isTestComplete()) { // Non-looper tests can reset here - RealmLog.setLevel(originalLogLevel); + resetTestEnvironment(); } else { // Otherwise we need to wait for the test to complete looperThread.runAfterTest(new Runnable() { @Override public void run() { - RealmLog.setLevel(originalLogLevel); + resetTestEnvironment(); } }); } } + private void resetTestEnvironment() { + for (SyncUser syncUser : SyncUser.all().values()) { + syncUser.logout(); + } + RealmLog.setLevel(originalLogLevel); + } + // Cleanup filesystem to make sure nothing lives for the next test. // Failing to do so might lead to DIVERGENT_HISTORY errors being thrown if Realms from // previous tests are being accessed. diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index 9cfdda3a85..18d2139081 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -40,50 +40,52 @@ function startRealmObjectServer(done) { // https://github.com/realm/realm-object-server/issues/1297 var logFindingCounter = 2 - stopRealmObjectServer(); - temp.mkdir('ros', function(err, path) { - if (!err) { - winston.info("Starting sync server in ", path); - var env = Object.create( process.env ); - winston.info(env.NODE_ENV); - env.NODE_ENV = 'development'; - syncServerChildProcess = spawn('realm-object-server', - ['--root', path, - '--configuration', '/configuration.yml'], - { env: env}); - // local config: - syncServerChildProcess.stdout.on('data', (data) => { - if (logFindingCounter != 0 && /client: Closing Realm file: .*__auth.realm/.test(data)) { - if (logFindingCounter == 1) { - done() + stopRealmObjectServer(function(err) { + if(err) { + return; + } + temp.mkdir('ros', function(err, path) { + if (!err) { + winston.info("Starting sync server in ", path); + var env = Object.create( process.env ); + winston.info(env.NODE_ENV); + env.NODE_ENV = 'development'; + syncServerChildProcess = spawn('realm-object-server', + ['--root', path, + '--configuration', '/configuration.yml'], + { env: env, cwd: path}); + // local config: + syncServerChildProcess.stdout.on('data', (data) => { + if (logFindingCounter != 0 && /client: Closing Realm file: .*__auth.realm/.test(data)) { + if (logFindingCounter == 1) { + done() + } + logFindingCounter-- } - logFindingCounter-- - } - winston.info(`stdout: ${data}`); - }); + winston.info(`stdout: ${data}`); + }); - syncServerChildProcess.stderr.on('data', (data) => { - winston.info(`stderr: ${data}`); - }); + syncServerChildProcess.stderr.on('data', (data) => { + winston.info(`stderr: ${data}`); + }); - syncServerChildProcess.on('close', (code) => { - winston.info(`child process exited with code ${code}`); - }); - } + syncServerChildProcess.on('close', (code) => { + winston.info(`child process exited with code ${code}`); + }); + } + }); }); } -function stopRealmObjectServer() { +function stopRealmObjectServer(callback) { if (syncServerChildProcess) { - syncServerChildProcess.kill(); - syncServerChildProcess = null; - exec('rm -r ' + 'realm-object-server', function (err, stdout, stderr) { - if (err) { - winston.error(err); - } else { - winston.info("realm-object-server directory deleted"); - } + syncServerChildProcess.on('exit', function() { + syncServerChildProcess = null; + callback(); }); + syncServerChildProcess.kill(); + } else { + callback(); } } @@ -98,10 +100,11 @@ dispatcher.onGet("/start", function(req, res) { // stop a previously started sync server dispatcher.onGet("/stop", function(req, res) { - stopRealmObjectServer(); - winston.info("Sync server stopped"); - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('Stopping the server'); + stopRealmObjectServer(function() { + winston.info("Sync server stopped"); + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Stopping the server'); + }); }); //Create and start the Http server From 2a0c58cc2ffbebf3932a0196db62fba1f90a4cd0 Mon Sep 17 00:00:00 2001 From: LYK Date: Mon, 3 Jul 2017 20:25:33 +0900 Subject: [PATCH 0811/2110] Update Kotlin to 1.1.3 (#4886) --- examples/kotlinExample/build.gradle | 2 +- realm/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index d4a434d370..f7ddd7c06a 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.2-5' + ext.kotlin_version = '1.1.3' repositories { jcenter() mavenCentral() diff --git a/realm/build.gradle b/realm/build.gradle index 37a4993710..9c3bf25cc6 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.2-5' + ext.kotlin_version = '1.1.3' repositories { mavenLocal() google() From d51a61f3e821c77245aad2ff4ec05d795069a50e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 3 Jul 2017 18:12:09 +0800 Subject: [PATCH 0812/2110] Fix multi-processes tests When running remote service test, the remote service is killed in after function. It is too early when running it together with looper thread test. --- .../realm/rule/RunTestWithRemoteService.java | 3 ++- .../io/realm/rule/RunWithRemoteService.java | 21 +++++++++++++++---- .../objectserver/ProcessCommitTests.java | 6 ++++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java index 8abdea9c40..1286630c0e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java @@ -30,5 +30,6 @@ @Target(METHOD) @Retention(RUNTIME) public @interface RunTestWithRemoteService { - Class value(); + Class remoteService(); + boolean onLooperThread(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java index d2b84bba3e..8aff527678 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java @@ -51,7 +51,12 @@ * 2. Add a base message id in {@link RemoteTestService}. * 3. Add the service into the AndroidManifest.xml. And the android:process property must be ":remote". * 4. Annotate your test case by {@link RunTestWithRemoteService} with your remote service class. - * 5. You also need a looper in your test thread. Normally you can just use {@link RunTestInLooperThread}. + * 5. To run the tests on the looper thread: + * a) Add {@link RunTestInLooperThread} to the tests. + * b) Add {@code @RunTestWithRemoteService(remoteService = SimpleCommitRemoteService.class, onLooperThread = true)} + * Please notice that {@code onLooperThread} needs to be set to true to avoid the remote service getting killed + * before looper thread finished + * c) Call {@code looperThread.runAfterTest(remoteService.afterRunnable)} to kill the remote service after test. * 6. When your looper thread starts, register the service messenger by calling * {@link RunWithRemoteService#createHandler(Looper)}. * 7. Trigger your first step in the remote service process by calling @@ -86,6 +91,12 @@ public void handleMessage(Message msg) { private Messenger remoteMessenger; private Messenger localMessenger; private CountDownLatch serviceStartLatch; + public Runnable afterRunnable = new Runnable() { + @Override + public void run() { + after(); + } + }; private final ServiceConnection serviceConnection = new ServiceConnection() { @Override @@ -112,7 +123,7 @@ private void before(Class serviceClass) throws Throwable { TestHelper.awaitOrFail(serviceStartLatch); } - public void after() { + private void after() { getContext().unbindService(serviceConnection); // Kill the remote process. @@ -143,11 +154,13 @@ public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { - before(annotation.value()); + before(annotation.remoteService()); try { base.evaluate(); } finally { - after(); + if (!annotation.onLooperThread()) { + after(); + } } } }; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 4620330152..cca7e44aec 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -103,9 +103,10 @@ protected void run() { // A. Open the same sync Realm and add one object. // 2. Get the notification, check if the change in A is received. @Test - @RunTestWithRemoteService(SimpleCommitRemoteService.class) @RunTestInLooperThread + @RunTestWithRemoteService(remoteService = SimpleCommitRemoteService.class, onLooperThread = true) public void expectSimpleCommit() { + looperThread.runAfterTest(remoteService.afterRunnable); remoteService.createHandler(Looper.myLooper()); final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); @@ -178,9 +179,10 @@ protected void run() { // 2. Check if the 100 objects are received. // #. Repeat B/2 10 times. @Test - @RunTestWithRemoteService(ALotCommitsRemoteService.class) + @RunTestWithRemoteService(remoteService = ALotCommitsRemoteService.class, onLooperThread = true) @RunTestInLooperThread public void expectALot() throws Throwable { + looperThread.runAfterTest(remoteService.afterRunnable); remoteService.createHandler(Looper.myLooper()); final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); From d2e20b2557a0233918061ec3da99b7173f08c06f Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 3 Jul 2017 21:15:44 +0200 Subject: [PATCH 0813/2110] Last field might be a backlink field. (#4889) --- CHANGELOG.md | 3 +- .../io/realm/LinkingObjectsQueryTests.java | 15 +++++++- .../androidTest/java/io/realm/QueryTests.java | 36 +++++++++++++++++++ .../main/cpp/io_realm_internal_TableQuery.cpp | 4 +-- .../fields/CachedFieldDescriptor.java | 3 +- 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6915f6fbb7..729791cb29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ ### Bug Fixes * Fixed a bug in `isNull()`, `isNotNull()`, `isEmpty()`, and `isNotEmpty()` when queries involve nullable fields in link queries (#4856). -* Rare crash in `RealmLog` when log level was set to `LogLevel.DEBUG`. +* Fixed a bug in how to resolve field names when querying `@LinkingObjects` as the last field (#4864). +* Rare crash in `RealmLog` when log level was set to `LogLevel.DEBUG`. * [ObjectServer] Fixed a bug related to the behaviour of `SyncUser#logout` and the use of invalid `SyncUser` with `SyncConfiguration` (#4822). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java index 91155ee79a..4b2d657c55 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java @@ -23,6 +23,7 @@ import java.util.Date; import io.realm.entities.AllJavaTypes; +import io.realm.entities.BacklinksTarget; import io.realm.entities.NullTypes; import static org.junit.Assert.assertEquals; @@ -345,7 +346,7 @@ public void isNotNull_unsupportedLinkedTypes() { } @Test - public void isEmpty() { + public void isEmpty_linkingObjects() { createIsEmptyDataSet(realm); for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { switch (type) { @@ -363,6 +364,18 @@ public void isEmpty() { } } + @Test + public void isEmpty_multipleModelClasses() { + createLinkedDataSet(realm); + assertEquals(1, realm.where(BacklinksTarget.class).isEmpty(BacklinksTarget.FIELD_PARENTS).count()); + } + + @Test(expected = IllegalArgumentException.class) + public void equalTo_linkingObjectLast() { + createLinkedDataSet(realm); + realm.where(BacklinksTarget.class).equalTo(BacklinksTarget.FIELD_PARENTS, "parents"); + } + @Test public void isEmpty_acrossLink() { createIsEmptyDataSet(realm); diff --git a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java index 976e31c3ea..9811beed32 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java @@ -27,6 +27,8 @@ import java.util.concurrent.TimeUnit; import io.realm.entities.AllJavaTypes; +import io.realm.entities.BacklinksSource; +import io.realm.entities.BacklinksTarget; import io.realm.rule.RunInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -112,6 +114,40 @@ protected final void createIsEmptyDataSet(Realm realm) { realm.commitTransaction(); } + protected final void createLinkedDataSet(Realm realm) { + realm.beginTransaction(); + + realm.delete(BacklinksSource.class); + realm.delete(BacklinksTarget.class); + + BacklinksTarget target1 = realm.createObject(BacklinksTarget.class); + target1.setId(1); + + BacklinksTarget target2 = realm.createObject(BacklinksTarget.class); + target2.setId(2); + + BacklinksTarget target3 = realm.createObject(BacklinksTarget.class); + target3.setId(3); + + + BacklinksSource source1 = realm.createObject(BacklinksSource.class); + source1.setName("1"); + source1.setChild(target1); + + BacklinksSource source2 = realm.createObject(BacklinksSource.class); + source2.setName("2"); + source2.setChild(target2); + + BacklinksSource source3 = realm.createObject(BacklinksSource.class); + source3.setName("3"); + + BacklinksSource source4 = realm.createObject(BacklinksSource.class); + source4.setName("4"); + source4.setChild(target1); + + realm.commitTransaction(); + } + protected final void createIsNotEmptyDataSet(Realm realm) { realm.beginTransaction(); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index f8cc250cd5..996ca3ad82 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -1668,7 +1668,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e auto column_idx = static_cast(index_arr[arr_len - 1]); // Support a backlink as the last column in a field descriptor - Table* last = TBL(table_arr[arr_len-1]); + auto last = reinterpret_cast(table_arr[arr_len-1]); if (last != nullptr) { pQuery->and_query(src_table_ref->column(*last, column_idx).count() == 0); return; @@ -1739,7 +1739,7 @@ Java_io_realm_internal_TableQuery_nativeIsNotEmpty(JNIEnv *env, jobject, jlong n auto column_idx = static_cast(index_arr[arr_len - 1]); // Support a backlink as the last column in a field descriptor - auto last = reinterpret_cast

            (table_arr[arr_len-1]); + auto last = reinterpret_cast(table_arr[arr_len-1]); if (last != nullptr) { pQuery->and_query(src_table_ref->column(*last, column_idx).count() != 0); return; diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java index d919d1e8d0..9fde831fd2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java @@ -80,10 +80,11 @@ protected void compileFieldDescription(List fields) { } columnType = tableInfo.getColumnType(columnName); + // we don't check the type of the last field in the chain since it is done in the C++ code if (i < nFields - 1) { verifyInternalColumnType(currentTable, columnName, columnType); - currentTable = tableInfo.getLinkedTable(columnName); } + currentTable = tableInfo.getLinkedTable(columnName); columnIndices[i] = columnIndex; tableNativePointers[i] = (columnType != RealmFieldType.LINKING_OBJECTS) ? NativeObject.NULLPTR From 08eae7b1abf2adfc373399e75cb2b781057e88eb Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 4 Jul 2017 13:23:30 +0800 Subject: [PATCH 0814/2110] Flaky test caused by the temp dir --- .../java/io/realm/objectserver/ProcessCommitTests.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index cca7e44aec..42bd324388 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -38,11 +38,9 @@ import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.RemoteIntegrationTestService; import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.RunTestWithRemoteService; import io.realm.rule.RunWithRemoteService; -import io.realm.rule.TestSyncConfigurationFactory; import io.realm.services.RemoteTestService; import static org.junit.Assert.assertEquals; @@ -111,7 +109,9 @@ public void expectSimpleCommit() { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, realmUrl).build(); + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user,realmUrl) + .directory(looperThread.getRoot()) + .build(); final Realm realm = Realm.getInstance(syncConfig); final RealmResults all = realm.where(ProcessInfo.class).findAll(); looperThread.keepStrongReference(all); @@ -187,7 +187,9 @@ public void expectALot() throws Throwable { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, realmUrl).build(); + final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user,realmUrl) + .directory(looperThread.getRoot()) + .build(); final Realm realm = Realm.getInstance(syncConfig); final RealmResults all = realm.where(TestObject.class).findAllSorted("intProp"); looperThread.keepStrongReference(all); From 15fafa44b2aea37487447a1b5e339283b83f24ae Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Tue, 4 Jul 2017 15:47:05 +0900 Subject: [PATCH 0815/2110] Specify errorprone version to prevent sudden build failure (#4903) --- realm/realm-library/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index f1e2dfedde..9ee2ba5d0e 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -183,6 +183,9 @@ dependencies { androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + + // specify error prone version to prevent sudden failure + errorprone 'com.google.errorprone:error_prone_core:2.0.21' } task sourcesJar(type: Jar) { From 919b2d819f8ec722907f7c628e2d27382918d204 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 3 Jul 2017 15:01:36 +0800 Subject: [PATCH 0816/2110] Remove io.realm.internal.OutOfMemoryError --- CHANGELOG.md | 1 + .../main/cpp/io_realm_internal_TestUtil.cpp | 2 +- .../src/main/cpp/java_exception_def.cpp | 1 + .../src/main/cpp/java_exception_def.hpp | 1 + realm/realm-library/src/main/cpp/util.cpp | 4 +- .../io/realm/internal/OutOfMemoryError.java | 43 ------------------- 6 files changed, 7 insertions(+), 45 deletions(-) delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/OutOfMemoryError.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e3f8e1102..8f216d07c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Breaking Changes * An `IllegalStateException` will be thrown if the given `RealmModule` doesn't include all required model classes (#3398). +* Removed `io.realm.internal.OutOfMemoryError`. `java.lang.OutOfMemoryError` will be thrown instead. ### Deprecated diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp index 6eab915b78..7b7fcb5198 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp @@ -45,7 +45,7 @@ static jstring throwOrGetExpectedMessage(JNIEnv* env, jlong testcase, bool shoul ThrowException(env, UnsupportedOperation, "parm1", "parm2"); break; case OutOfMemory: - expect = "io.realm.internal.OutOfMemoryError: parm1 parm2"; + expect = "java.lang.OutOfMemoryError: parm1 parm2"; if (should_throw) ThrowException(env, OutOfMemory, "parm1", "parm2"); break; diff --git a/realm/realm-library/src/main/cpp/java_exception_def.cpp b/realm/realm-library/src/main/cpp/java_exception_def.cpp index 81246e4483..75819d603b 100644 --- a/realm/realm-library/src/main/cpp/java_exception_def.cpp +++ b/realm/realm-library/src/main/cpp/java_exception_def.cpp @@ -20,3 +20,4 @@ using namespace realm::_impl; const char* JavaExceptionDef::IllegalState = "java/lang/IllegalStateException"; const char* JavaExceptionDef::IllegalArgument = "java/lang/IllegalArgumentException"; +const char* JavaExceptionDef::OutOfMemory = "java/lang/OutOfMemoryError"; diff --git a/realm/realm-library/src/main/cpp/java_exception_def.hpp b/realm/realm-library/src/main/cpp/java_exception_def.hpp index 9842d185f4..6ff17ebe36 100644 --- a/realm/realm-library/src/main/cpp/java_exception_def.hpp +++ b/realm/realm-library/src/main/cpp/java_exception_def.hpp @@ -26,6 +26,7 @@ class JavaExceptionDef { // Class names static const char* IllegalState; static const char* IllegalArgument; + static const char* OutOfMemory; }; } // namespace realm diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 41ab64d1c8..0ddeaaf2b1 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -27,6 +27,7 @@ #include "io_realm_internal_SharedRealm.h" #include "shared_realm.hpp" #include "results.hpp" +#include "java_exception_def.hpp" #include "jni_util/java_exception_thrower.hpp" @@ -34,6 +35,7 @@ using namespace std; using namespace realm; using namespace realm::util; using namespace realm::jni_util; +using namespace realm::_impl; // Caching classes and constructors for boxed types. jclass java_lang_long; @@ -158,7 +160,7 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla break; case OutOfMemory: - jExceptionClass = env->FindClass("io/realm/internal/OutOfMemoryError"); + jExceptionClass = env->FindClass(JavaExceptionDef::OutOfMemory); message = classStr + " " + itemStr; break; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OutOfMemoryError.java b/realm/realm-library/src/main/java/io/realm/internal/OutOfMemoryError.java deleted file mode 100644 index f74c99e86f..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/OutOfMemoryError.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - - -/** - * Can be thrown when Realm runs out of memory. - * A JVM that catches this will be able to cleanup, e.g. release other resources to avoid also running out of memory. - */ -@SuppressWarnings({"serial", "JavaLangClash"}) -@Keep -public class OutOfMemoryError extends Error { - - public OutOfMemoryError() { - super(); - } - - public OutOfMemoryError(String message) { - super(message); - } - - public OutOfMemoryError(String message, Throwable cause) { - super(message, cause); - } - - public OutOfMemoryError(Throwable cause) { - super(cause); - } -} From daefc22473fcf682a06cccedeb5bdb62497b07a3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 4 Jul 2017 18:16:01 +0800 Subject: [PATCH 0817/2110] Fix findbugs --- .../src/main/java/io/realm/Realm.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 027a37a7d8..a51611ca8d 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1623,19 +1623,7 @@ private void checkValidObjectForDetach(E realmObject) { * @throws FileNotFoundException if the Realm file doesn't exist. */ public static void migrateRealm(RealmConfiguration configuration) throws FileNotFoundException { - migrateRealm(configuration, (RealmMigration) null); - } - - /** - * Called when migration needed in the Realm initialization. - * - * @param configuration {@link RealmConfiguration} - * @param cause which triggers this migration. - * @throws FileNotFoundException if the Realm file doesn't exist. - */ - private static void migrateRealm(final RealmConfiguration configuration, final RealmMigrationNeededException cause) - throws FileNotFoundException { - BaseRealm.migrateRealm(configuration, null, cause); + migrateRealm(configuration, null); } /** From 3006e631647fa7a923702a4526d9294c7f6d9587 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 5 Jul 2017 17:17:09 +0800 Subject: [PATCH 0818/2110] Support stable IDs for sync (#4693) * Support stable IDs for sync A special column to store stable IDs are added by sync. See https://github.com/realm/realm-sync/pull/1170 for details. - Calling sync::create_object instead of add_empty_row to create new object. - Calling sync::create_table/sync::create_table_with_primary_key instead of add_table to create a new object schema. - addPrimaryKey() is not allowed for synced Realm anymore. - New API RealmSchema.createWithPrimaryKeyField is added. --- CHANGELOG.md | 4 + dependencies.list | 14 +- .../io/realm/DynamicRealmObjectTests.java | 8 +- .../java/io/realm/RealmSchemaTests.java | 128 ++++++++++++++++++ .../androidTest/java/io/realm/TestHelper.java | 3 +- .../io/realm/internal/PrimaryKeyTests.java | 10 +- .../assets/stable_id_migration.realm | Bin 0 -> 8192 bytes .../java/io/realm/SchemaTests.java | 58 +++++++- .../io/realm/SyncedRealmMigrationTests.java | 31 ++++- .../main/cpp/io_realm_internal_OsObject.cpp | 24 +++- .../cpp/io_realm_internal_SharedRealm.cpp | 76 ++++++++++- realm/realm-library/src/main/cpp/object-store | 2 +- .../main/java/io/realm/RealmObjectSchema.java | 30 +++- .../src/main/java/io/realm/RealmSchema.java | 45 +++++- .../java/io/realm/internal/SharedRealm.java | 42 +++++- .../main/java/io/realm/internal/Table.java | 5 +- tools/sync_test_server/Dockerfile | 3 +- 17 files changed, 444 insertions(+), 39 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/assets/stable_id_migration.realm diff --git a/CHANGELOG.md b/CHANGELOG.md index ed3f44593b..e4341d2f3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Breaking Changes +* [ObjectServer] Updated protocol version to 19 which is only compatible with ROS > 2.0.0. + ### Deprecated ### Enhancements @@ -10,6 +12,8 @@ ### Internal +* Upgraded to Realm Sync 2.0.0-rc9 + ### Credits diff --git a/dependencies.list b/dependencies.list index 597493e8ca..6de8b3a330 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,13 +1,13 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=1.10.1 -REALM_SYNC_SHA256=b48fd48461b563e2a6b1605ec346aca48b64b64a12d42a0f5a61135906d49074 +REALM_SYNC_VERSION=2.0.0-rc9 +REALM_SYNC_SHA256=c019adf4c2908b24fd63561579bf7f15691c4ecf0b9deaf74d3fe1f888cdb2eb # Object Server Release used by Integration tests -# `realm` is stable releases, `realm-testing` is developer builds. -# https://packagecloud.io/realm/realm?filter=debs -# https://packagecloud.io/realm/realm-testing?filter=debs +# Stable releases: https://packagecloud.io/realm/realm?filter=debs +# Beta releases: https://packagecloud.io/realm/realm-beta?filter=debs +# Developer builds: https://packagecloud.io/realm/realm-testing?filter=debs # /tools/sync_test_server/Dockerfile specify which repo (apt) we should -# install/use between 'realm' and 'realm-testing', the version below should +# install/use between 'realm', 'realm-beta' and 'realm-testing', the version below should # correspond to an existing version on the *specified* repo. -REALM_OBJECT_SERVER_DE_VERSION=1.8.1-149 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha9-176 diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 568e3b29e2..4977151e49 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -18,6 +18,7 @@ import android.support.test.runner.AndroidJUnit4; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -56,6 +57,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1158,7 +1160,11 @@ public void getFieldNames() { AllJavaTypes.FIELD_DOUBLE, AllJavaTypes.FIELD_BOOLEAN, AllJavaTypes.FIELD_DATE, AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_OBJECT, AllJavaTypes.FIELD_LIST}; String[] keys = dObjTyped.getFieldNames(); - assertArrayEquals(expectedKeys, keys); + // After the stable ID support, primary key field will be inserted first before others. So even FIELD_STRING is + // the first defined field in the class, it will be inserted after FIELD_ID. + // See ObjectStore::add_initial_columns #if REALM_HAVE_SYNC_STABLE_IDS branch. + assertEquals(expectedKeys.length, keys.length); + assertThat(Arrays.asList(expectedKeys), Matchers.hasItems(keys)); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index 2c3bd625c8..b0fd566df5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -101,6 +101,7 @@ public void create_invalidNameThrows() { for (String name : names) { try { realmSchema.create(name); + fail(); } catch (IllegalArgumentException ignored) { } assertFalse(String.format("'%s' failed", name), realmSchema.contains(name)); @@ -114,6 +115,133 @@ public void create_duplicatedNameThrows() { realmSchema.create("Foo"); } + @Test + public void createWithPrimaryKeyField_string() { + // Not nullable + realmSchema.createWithPrimaryKeyField("FooNonNull", "pkField", String.class, FieldAttribute.REQUIRED); + RealmObjectSchema objectSchema = realmSchema.getSchemaForClass("FooNonNull"); + assertEquals("pkField", objectSchema.getPrimaryKey()); + assertEquals(RealmFieldType.STRING, objectSchema.getFieldType("pkField")); + assertFalse(objectSchema.isNullable("pkField")); + assertTrue(objectSchema.hasIndex("pkField")); + + // Nullable + realmSchema.createWithPrimaryKeyField("FooNull", "pkField", String.class); + objectSchema = realmSchema.getSchemaForClass("FooNull"); + assertEquals("pkField", objectSchema.getPrimaryKey()); + assertEquals(RealmFieldType.STRING, objectSchema.getFieldType("pkField")); + assertTrue(objectSchema.isNullable("pkField")); + assertTrue(objectSchema.hasIndex("pkField")); + } + + @Test + public void createWithPrimaryKeyField_boxedInteger() { + // Not nullable + realmSchema.createWithPrimaryKeyField("FooNonNull", "pkField", Integer.class, + FieldAttribute.REQUIRED); + RealmObjectSchema objectSchema = realmSchema.getSchemaForClass("FooNonNull"); + assertEquals("pkField", objectSchema.getPrimaryKey()); + assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); + assertFalse(objectSchema.isNullable("pkField")); + assertTrue(objectSchema.hasIndex("pkField")); + + // Nullable + realmSchema.createWithPrimaryKeyField("FooNull", "pkField", Integer.class); + objectSchema = realmSchema.getSchemaForClass("FooNull"); + assertEquals("pkField", objectSchema.getPrimaryKey()); + assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); + assertTrue(objectSchema.isNullable("pkField")); + assertTrue(objectSchema.hasIndex("pkField")); + } + + @Test + public void createWithPrimaryKeyField_int() { + // Without Required + realmSchema.createWithPrimaryKeyField("Foo", "pkField", int.class); + RealmObjectSchema objectSchema = realmSchema.getSchemaForClass("Foo"); + assertEquals("pkField", objectSchema.getPrimaryKey()); + assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); + assertFalse(objectSchema.isNullable("pkField")); + assertTrue(objectSchema.hasIndex("pkField")); + + // With Required + realmSchema.createWithPrimaryKeyField("FooRequired", "pkField", int.class, + FieldAttribute.REQUIRED); + objectSchema = realmSchema.getSchemaForClass("FooRequired"); + assertEquals("pkField", objectSchema.getPrimaryKey()); + assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); + assertFalse(objectSchema.isNullable("pkField")); + assertTrue(objectSchema.hasIndex("pkField")); + } + + @Test + public void createWithPrimaryKeyField_explicitIndexed() { + realmSchema.createWithPrimaryKeyField("Foo", "pkField", int.class, + FieldAttribute.INDEXED); + RealmObjectSchema objectSchema = realmSchema.getSchemaForClass("Foo"); + assertEquals("pkField", objectSchema.getPrimaryKey()); + assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); + assertFalse(objectSchema.isNullable("pkField")); + assertTrue(objectSchema.hasIndex("pkField")); + } + + @Test + public void createWithPrimaryKeyField_explicitPrimaryKey() { + realmSchema.createWithPrimaryKeyField("Foo", "pkField", int.class, + FieldAttribute.PRIMARY_KEY); + RealmObjectSchema objectSchema = realmSchema.getSchemaForClass("Foo"); + assertEquals("pkField", objectSchema.getPrimaryKey()); + assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); + assertFalse(objectSchema.isNullable("pkField")); + assertTrue(objectSchema.hasIndex("pkField")); + } + + @Test + public void createWithPrimaryKeyField_invalidClassNameThrows() { + String[] invalidNames = { null, "", TestHelper.getRandomString(57) }; + + for (String name : invalidNames) { + try { + realmSchema.createWithPrimaryKeyField(name, "pkField", int.class); + fail(); + } catch (IllegalArgumentException ignored) { + } + assertFalse(String.format("'%s' failed", name), realmSchema.contains(name)); + } + } + + @Test + public void createWithPrimaryKeyField_invalidFieldNameThrows() { + String[] invalidFieldNames = new String[] { null, "", "foo.bar", TestHelper.getRandomString(65) }; + for (String fieldName : invalidFieldNames) { + try { + realmSchema.createWithPrimaryKeyField("Foo", fieldName, int.class); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + } + + @Test + public void createWithPrimaryKeyField_invalidFieldTypeThrows() { + Class[] fieldTypes = new Class[] {float.class, Float.class, Double.class, double.class, RealmObject.class, + RealmList.class, Object.class}; + for (Class fieldType : fieldTypes) { + try { + realmSchema.createWithPrimaryKeyField("Foo", "pkField", fieldType); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + } + + @Test + public void createWithPrimaryKeyField_duplicatedNameThrows() { + realmSchema.createWithPrimaryKeyField("Foo", "pkField", int.class); + thrown.expect(IllegalArgumentException.class); + realmSchema.createWithPrimaryKeyField("Foo", "pkField", int.class); + } + @Test public void get() { RealmObjectSchema objectSchema = realmSchema.get(AllJavaTypes.CLASS_NAME); diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 469de59c00..b9f3568a2c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -346,11 +346,12 @@ public void log(int level, String tag, Throwable throwable, String message) { }; } + // Generate a random string with only capital letters which is always a valid class/field name. public static String getRandomString(int length) { Random r = new Random(); StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { - sb.append((char) r.nextInt(128)); // Restrict to standard ASCII chars. + sb.append((char) r.nextInt(26) + 'A'); // Restrict to capital letters } return sb.toString(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index f6f9970675..1b9152b3d6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -66,7 +66,7 @@ public void tearDown() { private Table getTableWithStringPrimaryKey() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); - Table t = sharedRealm.createTable("TestTable"); + Table t = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); long column = t.addColumn(RealmFieldType.STRING, "colName", true); t.addSearchIndex(column); t.setPrimaryKey("colName"); @@ -76,7 +76,7 @@ private Table getTableWithStringPrimaryKey() { private Table getTableWithIntegerPrimaryKey() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); - Table t = sharedRealm.createTable("TestTable"); + Table t = sharedRealm.createTable(Table.getTableNameForClass("class_TestTable")); long column = t.addColumn(RealmFieldType.INTEGER, "colName"); t.addSearchIndex(column); t.setPrimaryKey("colName"); @@ -91,7 +91,7 @@ public void removingPrimaryKeyRemovesConstraint_typeSetters() { SharedRealm sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); - Table tbl = sharedRealm.createTable("EmployeeTable"); + Table tbl = sharedRealm.createTable(Table.getTableNameForClass("EmployeeTable")); tbl.addColumn(RealmFieldType.STRING, "name"); tbl.setPrimaryKey("name"); @@ -221,7 +221,7 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableMigratedWithRightName( public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); - Table table = sharedRealm.createTable("TestTable"); + Table table = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); long column = table.addColumn(RealmFieldType.INTEGER, "PKColumn"); table.addSearchIndex(column); table.setPrimaryKey(column); @@ -236,7 +236,7 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { pkTable.removeSearchIndex(classColumn); // Tries to add a pk for another table. - Table table2 = sharedRealm.createTable("TestTable2"); + Table table2 = sharedRealm.createTable(Table.getTableNameForClass("TestTable2")); long column2 = table2.addColumn(RealmFieldType.INTEGER, "PKColumn"); table2.addSearchIndex(column2); try { diff --git a/realm/realm-library/src/androidTestObjectServer/assets/stable_id_migration.realm b/realm/realm-library/src/androidTestObjectServer/assets/stable_id_migration.realm new file mode 100644 index 0000000000000000000000000000000000000000..cda2d884566a875452a76af6fc06fb7e736eeba9 GIT binary patch literal 8192 zcmeHJJ&Y4a6n<~Uv+FdE3drrN|$uW_hxqN#lG~Bm?=Ddf8YD&+nLd9C1gh}a`U6> zKi!UFqHT;Xh)g#drccsg>Y^*Z-}Ila_{xs&9`B}uLF+-@>8APUem2@3-0cmsr&;c@ zqSg4rVczLIEkI%ycfMbGyQ4BV{$CWHm|5n}G5?n3gU*Ydi1Z;%=@<^ls~``5 z?GPJro|WRT)d_qzZ~gwa#1-swrTh!9`_Eg$^w-_2aL2Iw`HOy*4=u%t)S-#;jl>47 zuJa9U!YMX*sI{vOLF`LJSI<(a?DOhKzVO`RSGI-N_=Mc;HqL^0m1Q{Z;3QWRvNoUFo9*_YUR* z-c$`_7Pe~hjwu-VkuQAwdi?qXNBH2I+2L^$UDulsP;B3?%x6b0_(-9zYwEV%E9$^cHS}1Q z^x-8w{J5Rw1LR3Pcz9XncoY3zA1?6#E}xYA(&tE@&ilYN3HKH0ef>e_y>z$rNboNa zHZ4)r-vx%~GOZEC#bLW3$4M5s#mqPvhFIdN>JP~ptMjD;a)nr_Z&y!#aWfcF79M-) z3L1UQsv_zG)i#PD1z#?oiJ~sTVM7gTU0cO==f;o*AUQw*U$Z3w4eA7FR$rE(T^W$s za^OlOS-z1BRV=2lp9I>7681Y}AX=|1^o5(9o-Pj6(9{j_Lx0^ieY0oWz2o!!jBA-+ z{`vZ??71TUtEPEn-k6DbhsI}*wAebW*1dZ_Ka)D7@@6!?PCq&vv8SK8YKh|?%M3o} zYE-RoeP$?1B11797{^%RvhRKOl3Q zZp+!{k+-q#?r4cy+~)d*v=UozCna3CS^R>#AH9zbOSla^o#U4J^XXFFk)F--fMd_W zI6987WRfdK@S*$n_}X?CVka$jquN-F@zkfWA_v1J51W9L)zx>r<*Ku}E@CF5f0zrYGKu{nk@F^)E z?+=5g{8TmdiEiTUIg5Aj;PL(jHE4@!bVx~ca?$zmposq|9x#%J_Y2;MNz7Z6)RG%X rEbqkFz56~TEyG%a0zrYGKu{nk5EKXs1O
            (tablesArray[i]); @@ -87,10 +89,11 @@ static TableRef getTableForLinkQuery(jlong nativeQueryPtr, JniLongArray& tablesA } // Return TableRef point to original table or the link table -static TableRef getTableByArray(jlong nativeQueryPtr, JniLongArray& tablesArray, JniLongArray& indicesArray) +static TableRef getTableByArray(jlong nativeQueryPtr, const JLongArrayAccessor& tablesArray, + const JLongArrayAccessor& indicesArray) { auto table_ref = reinterpret_cast(nativeQueryPtr)->get_table(); - jsize link_element_count = indicesArray.len() - 1; + jsize link_element_count = indicesArray.size() - 1; for (int i = 0; i < link_element_count; ++i) { auto table_ptr = reinterpret_cast
            (tablesArray[i]); if (table_ptr == nullptr) { @@ -161,9 +164,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JJ(J jlongArray columnIndexes, jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { @@ -185,9 +188,9 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual_ jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { @@ -209,9 +212,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JJ jlongArray columnIndexes, jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { @@ -234,9 +237,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3 jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { @@ -257,9 +260,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JJ(JN jlongArray columnIndexes, jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { @@ -280,9 +283,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3 jlongArray columnIndexes, jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { @@ -304,8 +307,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JJJ(J jlongArray columnIndexes, jlong value1, jlong value2) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JLongArrayAccessor arr(env, columnIndexes); + jsize arr_len = arr.size(); if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Int)) { return; @@ -327,9 +330,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JF(J jlongArray columnIndexes, jlongArray tablePointers, jfloat value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { @@ -352,9 +355,9 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual_ jlongArray tablePointers, jfloat value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { @@ -376,9 +379,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JF jlongArray columnIndexes, jlongArray tablePointers, jfloat value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { @@ -401,9 +404,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3 jlongArray tablePointers, jfloat value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { @@ -424,9 +427,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JF(JN jlongArray columnIndexes, jlongArray tablePointers, jfloat value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { @@ -448,9 +451,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3 jlongArray tablePointers, jfloat value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { @@ -472,8 +475,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JFF(J jlongArray columnIndexes, jfloat value1, jfloat value2) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JLongArrayAccessor arr(env, columnIndexes); + jsize arr_len = arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Float)) { @@ -496,9 +499,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JD(J jlongArray columnIndexes, jlongArray tablePointers, jdouble value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { @@ -521,9 +524,9 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual_ jlongArray tablePointers, jdouble value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { @@ -545,9 +548,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JD jlongArray columnIndexes, jlongArray tablePointers, jdouble value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { @@ -570,9 +573,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3 jlongArray tablePointers, jdouble value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { @@ -593,9 +596,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JD(JN jlongArray columnIndexes, jlongArray tablePointers, jdouble value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { @@ -618,9 +621,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3 jlongArray tablePointers, jdouble value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { @@ -642,8 +645,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JDD(J jlongArray columnIndexes, jdouble value1, jdouble value2) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JLongArrayAccessor arr(env, columnIndexes); + jsize arr_len = arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Double)) { @@ -666,9 +669,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualTimestamp(JN jlongArray columnIndexes, jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { @@ -692,9 +695,9 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualT jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { @@ -717,9 +720,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterTimestamp( jlongArray columnIndexes, jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { @@ -743,9 +746,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualTimes jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { @@ -768,9 +771,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessTimestamp(JNI jlongArray columnIndexes, jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { @@ -794,9 +797,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualTimestam jlongArray tablePointers, jlong value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { @@ -819,8 +822,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenTimestamp( jlongArray columnIndexes, jlong value1, jlong value2) { - JniLongArray arr(env, columnIndexes); - jsize arr_len = arr.len(); + JLongArrayAccessor arr(env, columnIndexes); + jsize arr_len = arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Timestamp)) { @@ -844,9 +847,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JZ(J jlongArray columnIndexes, jlongArray tablePointers, jboolean value) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Bool)) { @@ -872,9 +875,9 @@ static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA jlongArray tablePointers, jstring value, jboolean caseSensitive, StringPredicate predicate) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); try { TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); if (value == NULL) { @@ -998,50 +1001,43 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeContains(JNIEnv* enum BinaryPredicate { BinaryEqual, BinaryNotEqual }; static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnIndexes, - jlongArray tablePointers, jbyteArray value, - BinaryPredicate predicate) + jlongArray tablePointers, jbyteArray value, BinaryPredicate predicate) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); try { + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - JniByteArray bytes(env, value); - BinaryData value2; - if (value == NULL) { - if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), index_arr[arr_len - 1])) { - return; - } - value2 = BinaryData(); - } - else { - if (!bytes.ptr()) { - ThrowException(env, IllegalArgument, "binaryPredicate"); - return; - } - value2 = BinaryData(reinterpret_cast(bytes.ptr()), S(bytes.len())); + + if (value == NULL && !TBL_AND_COL_NULLABLE(env, table_ref.get(), index_arr[arr_len - 1])) { + return; } + JByteArrayAccessor jarray_accessor(env, value); if (arr_len == 1) { if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Binary)) { return; } switch (predicate) { case BinaryEqual: - Q(nativeQueryPtr)->equal(S(index_arr[0]), value2); + Q(nativeQueryPtr)->equal(S(index_arr[0]), jarray_accessor.transform()); break; case BinaryNotEqual: - Q(nativeQueryPtr)->not_equal(S(index_arr[0]), value2); + Q(nativeQueryPtr)->not_equal(S(index_arr[0]), jarray_accessor.transform()); break; } } else { switch (predicate) { case BinaryEqual: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) == value2); + Q(nativeQueryPtr) + ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) == + jarray_accessor.transform()); break; case BinaryNotEqual: - Q(nativeQueryPtr)->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) != value2); + Q(nativeQueryPtr) + ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) != + jarray_accessor.transform()); break; } } @@ -1499,9 +1495,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en jlongArray tablePointers) { try { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); auto pQuery = reinterpret_cast(nativeQueryPtr); jlong column_idx = index_arr[arr_len - 1]; @@ -1579,9 +1575,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* jlongArray columnIndexes, jlongArray tablePointers) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); Query* pQuery = Q(nativeQueryPtr); try { jlong column_idx = index_arr[arr_len - 1]; @@ -1661,9 +1657,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e jlongArray columnIndexes, jlongArray tablePointers) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); Query* pQuery = reinterpret_cast(nativeQueryPtr); try { TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); @@ -1732,9 +1728,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotEmpty(JNIEnv *env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, jlongArray tablePointers) { - JniLongArray table_arr(env, tablePointers); - JniLongArray index_arr(env, columnIndexes); - jsize arr_len = index_arr.len(); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor index_arr(env, columnIndexes); + jsize arr_len = index_arr.size(); Query* pQuery = reinterpret_cast(nativeQueryPtr); try { TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 7be68bc5fe..bf3a2a2543 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -15,9 +15,12 @@ */ #include "io_realm_internal_UncheckedRow.h" + +#include "java_accessor.hpp" #include "util.hpp" using namespace realm; +using namespace realm::_impl; static void finalize_unchecked_row(jlong ptr); @@ -305,30 +308,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray(JN return; } - jbyte* bytePtr = nullptr; try { - if (value == nullptr) { - if (!(ROW(nativeRowPtr)->get_table()->is_nullable(S(columnIndex)))) { - ThrowNullValueException(env, ROW(nativeRowPtr)->get_table(), S(columnIndex)); - return; - } - ROW(nativeRowPtr)->set_binary(S(columnIndex), BinaryData()); - } - else { - bytePtr = env->GetByteArrayElements(value, NULL); - if (!bytePtr) { - ThrowException(env, IllegalArgument, "doByteArray"); - return; - } - size_t dataLen = S(env->GetArrayLength(value)); - ROW(nativeRowPtr)->set_binary(S(columnIndex), BinaryData(reinterpret_cast(bytePtr), dataLen)); + auto& row = *reinterpret_cast(nativeRowPtr); + if (value == nullptr && !(row.get_table()->is_nullable(S(columnIndex)))) { + ThrowNullValueException(env, ROW(nativeRowPtr)->get_table(), S(columnIndex)); + return; } - } - CATCH_STD() - if (bytePtr) { - env->ReleaseByteArrayElements(value, bytePtr, JNI_ABORT); + JByteArrayAccessor jarray_accessor(env, value); + row.set_binary(static_cast(columnIndex), jarray_accessor.transform()); } + CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLink(JNIEnv* env, jobject, jlong nativeRowPtr, diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp new file mode 100644 index 0000000000..7658acd7d1 --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -0,0 +1,225 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_IMPL_JAVA_ACCESSOR_HPP +#define REALM_JNI_IMPL_JAVA_ACCESSOR_HPP + +#include + +#include +#include + +#include + +#include + +#include "java_exception_def.hpp" +#include "jni_util/java_exception_thrower.hpp" + +// Utility classes for accessing Java objects from JNI +namespace realm { +namespace _impl { + +template +class JPrimitiveArrayAccessor; +typedef JPrimitiveArrayAccessor JByteArrayAccessor; +typedef JPrimitiveArrayAccessor JBooleanArrayAccessor; +typedef JPrimitiveArrayAccessor JLongArrayAccessor; + +// JPrimitiveArrayAccessor and JObjectArrayAccessor are not supposed to be used across JNI borders. They won't acquire +// references of the original Java object. Thus, you have to ensure the original java object is available during the +// life cycle of those accessors. Moreover, some returned object like BinaryData and StringData, they don't own the +// memory they use. So the accessor has to be available during the life cycle of those returned objects. + +// Accessor for Java primitive arrays +template +class JPrimitiveArrayAccessor { +public: + JPrimitiveArrayAccessor(JNIEnv* env, ArrayType jarray) + : m_size(jarray ? env->GetArrayLength(jarray) : 0) + , m_elements_holder(std::make_shared(env, jarray)) + { + check_init(env); + } + ~JPrimitiveArrayAccessor() = default; + + JPrimitiveArrayAccessor(JPrimitiveArrayAccessor&&) = default; + JPrimitiveArrayAccessor& operator=(JPrimitiveArrayAccessor&&) = default; + JPrimitiveArrayAccessor(const JPrimitiveArrayAccessor&) = default; + JPrimitiveArrayAccessor& operator=(const JPrimitiveArrayAccessor&) = default; + + inline bool is_null() { + return !m_elements_holder->m_jarray; + } + + inline jsize size() const noexcept + { + return m_size; + } + + inline ElementType* data() const noexcept + { + return m_elements_holder->m_data_ptr; + } + + inline const ElementType& operator[](const int index) const noexcept + { + return m_elements_holder->m_data_ptr[index]; + } + + // Converts the Java array into an instance of T. The returned value's life cycle may still rely on this accessor. + // (e.g.: BinaryData/StringData) + template + T transform(); + +private: + // Holding the data returned by GetXxxArrayElements call. + struct ElementsHolder { + ElementsHolder(JNIEnv*, ArrayType); + ~ElementsHolder(); + + JNIEnv* m_env; + const ArrayType m_jarray; + ElementType* m_data_ptr; + const jint m_release_mode = JNI_ABORT; + }; + + jsize m_size; + // For enabling copy/move constructors. ReleaseXxxArrayElements should only be called once. + std::shared_ptr m_elements_holder; + + inline void check_init(JNIEnv* env) + { + if (m_elements_holder->m_jarray != nullptr && m_elements_holder->m_data_ptr == nullptr) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, + util::format("GetXxxArrayElements failed on %1.", + reinterpret_cast(m_elements_holder->m_jarray))); + } + } +}; + +// Accessor for Java object arrays +template +class JObjectArrayAccessor { +public: + JObjectArrayAccessor(JNIEnv* env, jobjectArray jobject_array) + : m_env(env) + , m_jobject_array(jobject_array) + , m_size(jobject_array ? env->GetArrayLength(jobject_array) : 0) + { + } + ~JObjectArrayAccessor() + { + } + + // Not implemented + JObjectArrayAccessor(JObjectArrayAccessor&&) = delete; + JObjectArrayAccessor& operator=(JObjectArrayAccessor&&) = delete; + JObjectArrayAccessor(const JObjectArrayAccessor&) = delete; + JObjectArrayAccessor& operator=(const JObjectArrayAccessor&) = delete; + + inline jsize size() const noexcept + { + return m_size; + } + + inline AccessorType operator[](const int index) const noexcept + { + return AccessorType(m_env, static_cast(m_env->GetObjectArrayElement(m_jobject_array, index))); + } + +private: + JNIEnv* m_env; + jobjectArray m_jobject_array; + jsize m_size; +}; + +// Accessor for jbyteArray +template <> +inline JPrimitiveArrayAccessor::ElementsHolder::ElementsHolder(JNIEnv* env, jbyteArray jarray) + : m_env(env) + , m_jarray(jarray) + , m_data_ptr(jarray ? env->GetByteArrayElements(jarray, nullptr) : nullptr) +{ +} + +template <> +inline JPrimitiveArrayAccessor::ElementsHolder::~ElementsHolder() +{ + if (m_jarray) { + m_env->ReleaseByteArrayElements(m_jarray, m_data_ptr, m_release_mode); + } +} + +template <> +template <> +inline BinaryData JPrimitiveArrayAccessor::transform() +{ + return is_null() ? realm::BinaryData() + : realm::BinaryData(reinterpret_cast(m_elements_holder->m_data_ptr), m_size); +} + +template <> +template <> +inline std::vector JPrimitiveArrayAccessor::transform>() +{ + if (is_null()) { + return {}; + } + + std::vector v(m_size); + std::copy_n(m_elements_holder->m_data_ptr, v.size(), v.begin()); + return v; +} + +// Accessor for jbooleanArray +template <> +inline JPrimitiveArrayAccessor::ElementsHolder::ElementsHolder(JNIEnv* env, jbooleanArray jarray) + : m_env(env) + , m_jarray(jarray) + , m_data_ptr(jarray ? env->GetBooleanArrayElements(jarray, nullptr) : nullptr) +{ +} + +template <> +inline JPrimitiveArrayAccessor::ElementsHolder::~ElementsHolder() +{ + if (m_jarray) { + m_env->ReleaseBooleanArrayElements(m_jarray, m_data_ptr, m_release_mode); + } +} + +// Accessor for jlongArray +template <> +inline JPrimitiveArrayAccessor::ElementsHolder::ElementsHolder(JNIEnv* env, jlongArray jarray) + : m_env(env) + , m_jarray(jarray) + , m_data_ptr(jarray ? env->GetLongArrayElements(jarray, nullptr) : nullptr) +{ +} + +template <> +inline JPrimitiveArrayAccessor::ElementsHolder::~ElementsHolder() +{ + if (m_jarray) { + m_env->ReleaseLongArrayElements(m_jarray, m_data_ptr, m_release_mode); + } +} + +} // namespace realm +} // namespace _impl + +#endif // REALM_JNI_IMPL_JAVA_ACCESSOR_HPP diff --git a/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp b/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp index ebed50f6fd..90adec02ef 100644 --- a/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp +++ b/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp @@ -15,6 +15,7 @@ */ +#include "java_accessor.hpp" #include "java_sort_descriptor.hpp" #include "util.hpp" #include "jni_util/java_class.hpp" @@ -53,14 +54,14 @@ std::vector> JavaSortDescriptor::get_column_indices() const static JavaMethod get_column_indices_method(m_env, get_sort_desc_class(), "getColumnIndices", "()[[J"); jobjectArray column_indices = static_cast(m_env->CallObjectMethod(m_sort_desc_obj, get_column_indices_method)); - JniArrayOfArrays arrays(m_env, column_indices); - jsize arr_len = arrays.len(); + JObjectArrayAccessor arrays(m_env, column_indices); + jsize arr_len = arrays.size(); std::vector> indices; for (int i = 0; i < arr_len; ++i) { - JniLongArray& jni_long_array = arrays[i]; + auto jni_long_array = arrays[i]; std::vector col_indices; - for (int j = 0; j < jni_long_array.len(); ++j) { + for (int j = 0; j < jni_long_array.size(); ++j) { col_indices.push_back(static_cast(jni_long_array[j])); } indices.push_back(std::move(col_indices)); @@ -79,9 +80,9 @@ std::vector JavaSortDescriptor::get_ascendings() const noexcept return {}; } - JniBooleanArray ascending_array(m_env, ascendings); + JBooleanArrayAccessor ascending_array(m_env, ascendings); std::vector ascending_list; - jsize arr_len = ascending_array.len(); + jsize arr_len = ascending_array.size(); for (int i = 0; i < arr_len; i++) { ascending_list.push_back(static_cast(ascending_array[i])); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 5f08eec906..30346e469b 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -478,216 +478,6 @@ class JStringAccessor { std::size_t m_size; }; -class JniLongArray { -public: - JniLongArray(JNIEnv* env, jlongArray javaArray) - : m_env(env) - , m_javaArray(javaArray) - , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) - , m_array(javaArray == NULL ? NULL : env->GetLongArrayElements(javaArray, NULL)) - , m_releaseMode(JNI_ABORT) - { - } - - JniLongArray(JniLongArray& other) = delete; - - JniLongArray(JniLongArray&& other) - : m_env(other.m_env) - , m_javaArray(other.m_javaArray) - , m_arrayLength(other.m_arrayLength) - , m_array(other.m_array) - , m_releaseMode(other.m_releaseMode) - { - other.m_env = nullptr; - other.m_javaArray = nullptr; - other.m_arrayLength = 0; - other.m_array = nullptr; - } - - ~JniLongArray() - { - if (m_array) { - m_env->ReleaseLongArrayElements(m_javaArray, m_array, m_releaseMode); - } - } - - inline jsize len() const noexcept - { - return m_arrayLength; - } - - inline jlong* ptr() const noexcept - { - return m_array; - } - - inline jlong& operator[](const int index) noexcept - { - return m_array[index]; - } - - inline void updateOnRelease() noexcept - { - m_releaseMode = 0; - } - -private: - JNIEnv* m_env; - jlongArray m_javaArray; - jsize m_arrayLength; - jlong* m_array; - jint m_releaseMode; -}; - -template -class JniArrayOfArrays { -public: - JniArrayOfArrays(JNIEnv* env, jobjectArray javaArray) - : m_env(env) - , m_javaArray(javaArray) - , m_arrayLength(javaArray == nullptr ? 0 : env->GetArrayLength(javaArray)) - { - for (int i = 0; i < m_arrayLength; ++i) { - // No type checking. Internal use only. - J j_array = static_cast(env->GetObjectArrayElement(m_javaArray, i)); - m_array.push_back(T(env, j_array)); - } - } - - ~JniArrayOfArrays() - { - } - - inline jsize len() const noexcept - { - return m_arrayLength; - } - - inline T& operator[](const int index) noexcept - { - return m_array[index]; - } - -private: - JNIEnv* const m_env; - jobjectArray const m_javaArray; - jsize const m_arrayLength; - std::vector m_array; -}; - -class JniByteArray { -public: - JniByteArray(JNIEnv* env, jbyteArray javaArray) - : m_env(env) - , m_javaArray(javaArray) - , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) - , m_array(javaArray == NULL ? NULL : env->GetByteArrayElements(javaArray, NULL)) - , m_releaseMode(JNI_ABORT) - { - if (m_javaArray != nullptr && m_array == nullptr) { - // javaArray is not null but GetByteArrayElements returns null, something is really wrong. - throw std::runtime_error( - realm::util::format("GetByteArrayElements failed on byte array %x", m_javaArray)); - } - } - - ~JniByteArray() - { - if (m_array) { - m_env->ReleaseByteArrayElements(m_javaArray, m_array, m_releaseMode); - } - } - - inline jsize len() const noexcept - { - return m_arrayLength; - } - - inline jbyte* ptr() const noexcept - { - return m_array; - } - - inline jbyte& operator[](const int index) noexcept - { - return m_array[index]; - } - - inline operator realm::BinaryData() const noexcept - { - return realm::BinaryData(reinterpret_cast(m_array), m_arrayLength); - } - - inline operator std::vector() const noexcept - { - if (m_array == nullptr) { - return {}; - } - - std::vector v(m_arrayLength); - std::copy_n(m_array, v.size(), v.begin()); - return v; - } - - inline void updateOnRelease() noexcept - { - m_releaseMode = 0; - } - -private: - JNIEnv* const m_env; - jbyteArray const m_javaArray; - jsize const m_arrayLength; - jbyte* const m_array; - jint m_releaseMode; -}; - -class JniBooleanArray { -public: - JniBooleanArray(JNIEnv* env, jbooleanArray javaArray) - : m_env(env) - , m_javaArray(javaArray) - , m_arrayLength(javaArray == NULL ? 0 : env->GetArrayLength(javaArray)) - , m_array(javaArray == NULL ? NULL : env->GetBooleanArrayElements(javaArray, NULL)) - , m_releaseMode(JNI_ABORT) - { - } - - ~JniBooleanArray() - { - if (m_array) { - m_env->ReleaseBooleanArrayElements(m_javaArray, m_array, m_releaseMode); - } - } - - inline jsize len() const noexcept - { - return m_arrayLength; - } - - inline jboolean* ptr() const noexcept - { - return m_array; - } - - inline jboolean& operator[](const int index) noexcept - { - return m_array[index]; - } - - inline void updateOnRelease() noexcept - { - m_releaseMode = 0; - } - -private: - JNIEnv* const m_env; - jbooleanArray const m_javaArray; - jsize const m_arrayLength; - jboolean* const m_array; - jint m_releaseMode; -}; - inline jlong to_milliseconds(const realm::Timestamp& ts) { // From core's reference implementation aka unit test From 196b195606489686a62281dd6fa5a34224f59b13 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Mon, 11 Sep 2017 09:06:30 +0300 Subject: [PATCH 0950/2110] Fix a bug that RealmList.delete*() does not delete target objects (#5234) * added test cases exposing #5233 * make RealmList.delete*() delete target objects. This commit requires to applky following patch to Object Store. diff --git a/src/list.cpp b/src/list.cpp index 1734cfa..d8aa1e2 100644 --- a/src/list.cpp +++ b/src/list.cpp @@ -349,6 +349,16 @@ void List::swap(size_t ndx1, size_t ndx2) m_table->swap_rows(ndx1, ndx2); } +void List::delete_(size_t row_ndx) +{ + verify_in_transaction(); + verify_valid_row(row_ndx); + if (m_link_view) + m_link_view->remove_target_row(row_ndx); + else + m_table->remove(row_ndx); +} + void List::delete_all() { verify_in_transaction(); diff --git a/src/list.hpp b/src/list.hpp index a3b52af..db4e8d4 100644 --- a/src/list.hpp +++ b/src/list.hpp @@ -77,6 +77,7 @@ public: void remove(size_t list_ndx); void remove_all(); void swap(size_t ndx1, size_t ndx2); + void delete_(size_t list_ndx); void delete_all(); template * Fixed a bug that RealmList.deleteFromRealm(int), RealmList.deleteFirstFromRealm() and RealmList.deleteLastFromRealm() did not remove target objects from Realm. This bug was introduced in 3.7.1 (#5233). * Fix tests --- CHANGELOG.md | 1 + .../java/io/realm/CollectionTests.java | 4 + .../ManagedOrderedRealmCollectionTests.java | 35 +++++-- .../io/realm/ManagedRealmCollectionTests.java | 16 +++- .../java/io/realm/RealmListTests.java | 93 ++++++------------- .../src/main/cpp/io_realm_internal_OsList.cpp | 11 +++ realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/RealmList.java | 2 +- .../main/java/io/realm/internal/OsList.java | 6 ++ 9 files changed, 90 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a23a83b8..2a2ce692ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug Fixes * Fixed a JNI memory issue when doing queries which might potentially cause various native crashes. +* Fixed a bug that `RealmList.deleteFromRealm(int)`, `RealmList.deleteFirstFromRealm()` and `RealmList.deleteLastFromRealm()` did not remove target objects from Realm. This bug was introduced in `3.7.1` (#5233). ## 3.7.1 (2017-09-07) diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index 9a38ec50b1..7b38283cfa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -239,4 +239,8 @@ boolean isSnapshot(CollectionClass collectionClass) { return collectionClass == CollectionClass.REALMRESULTS_SNAPSHOT_LIST_BASE || collectionClass == CollectionClass.REALMRESULTS_SNAPSHOT_RESULTS_BASE; } + + boolean isRealmList(ManagedCollection collectionClass) { + return collectionClass == ManagedCollection.MANAGED_REALMLIST; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index 8e81f62878..c50e59cc54 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -34,6 +34,7 @@ import java.util.concurrent.Future; import io.realm.entities.AllJavaTypes; +import io.realm.entities.AllTypes; import io.realm.entities.Dog; import io.realm.entities.NullTypes; import io.realm.entities.Owner; @@ -613,15 +614,25 @@ public void sort_long() { public void deleteFromRealm() { OrderedRealmCollection collection = createNonCyclicCollection(realm, collectionClass); assertEquals(1, collection.get(1).getAge()); - realm.beginTransaction(); - collection.deleteFromRealm(0); - realm.commitTransaction(); - if (isSnapshot(collectionClass)) { - assertEquals(TEST_SIZE, collection.size()); - assertFalse(collection.get(0).isValid()); - } else { - assertEquals(TEST_SIZE - 1, collection.size()); - assertEquals(2, collection.get(1).getAge()); + + int[] indexToDelete = {TEST_SIZE/2, TEST_SIZE - 2, 0}; + int currentSize = TEST_SIZE; + + for (int i = 0; i < indexToDelete.length; i++) { + int index = indexToDelete[i]; + realm.beginTransaction(); + Dog dog = collection.get(index); + collection.deleteFromRealm(index); + realm.commitTransaction(); + if (isSnapshot(collectionClass)) { + assertEquals(TEST_SIZE, collection.size()); + assertFalse(collection.get(index).isValid()); + } else { + assertEquals(currentSize- 1, collection.size()); + } + assertFalse(dog.isValid()); + assertEquals(currentSize- 1, realm.where(Dog.class).count()); + currentSize -= 1; } } @@ -646,6 +657,7 @@ public void deleteFirstFromRealm() { assertEquals(0, collection.get(0).getAge()); realm.beginTransaction(); + Dog dog = collection.first(); assertTrue(collection.deleteFirstFromRealm()); realm.commitTransaction(); if (isSnapshot(collectionClass)) { @@ -655,6 +667,8 @@ public void deleteFirstFromRealm() { assertEquals(TEST_SIZE - 1, collection.size()); assertEquals(1, collection.get(0).getAge()); } + assertFalse(dog.isValid()); + assertEquals(TEST_SIZE - 1, realm.where(Dog.class).count()); } private OrderedRealmCollection createNonCyclicCollection(Realm realm, ManagedCollection collectionClass) { @@ -709,6 +723,7 @@ public void deleteFirstFromRealm_emptyCollection() { public void deleteLastFromRealm() { assertEquals(TEST_SIZE - 1, collection.last().getFieldLong()); realm.beginTransaction(); + AllJavaTypes allJavaTypes = collection.last(); assertTrue(collection.deleteLastFromRealm()); realm.commitTransaction(); if (isSnapshot(collectionClass)) { @@ -718,6 +733,8 @@ public void deleteLastFromRealm() { assertEquals(TEST_SIZE - 1, collection.size()); assertEquals(TEST_SIZE - 2, collection.last().getFieldLong()); } + assertFalse(allJavaTypes.isValid()); + assertEquals(TEST_SIZE - 1, realm.where(AllJavaTypes.class).count()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 387920dc58..341b181ad4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -16,6 +16,7 @@ package io.realm; +import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -671,6 +672,12 @@ public void deleteAllFromRealm() { } else { assertEquals(0, collection.size()); } + if (isRealmList(collectionClass)) { + // The parent object was not deleted + assertEquals(1, realm.where(AllJavaTypes.class).count()); + } else { + assertEquals(0, realm.where(AllJavaTypes.class).count()); + } } @Test(expected = IllegalStateException.class) @@ -691,11 +698,10 @@ public void deleteAllFromRealm_emptyList() { @Test public void deleteAllFromRealm_invalidList() { realm.close(); - try { - collection.deleteAllFromRealm(); - fail(); - } catch (IllegalStateException ignored) { - } + thrown.expect(IllegalStateException.class); + thrown.expectMessage(CoreMatchers.containsString( + "This Realm instance has already been closed, making it unusable.")); + collection.deleteAllFromRealm(); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 057bc0d958..7cfc9f674d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -29,8 +29,8 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllTypes; import io.realm.entities.Cat; @@ -39,7 +39,6 @@ import io.realm.entities.Dog; import io.realm.entities.Owner; import io.realm.internal.RealmObjectProxy; -import io.realm.internal.Table; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -104,6 +103,7 @@ private RealmList createUnmanagedDogList() { private RealmList createDeletedRealmList() { Owner owner = realm.where(Owner.class).findFirst(); + //noinspection ConstantConditions RealmList dogs = owner.getDogs(); realm.beginTransaction(); @@ -473,6 +473,20 @@ public void remove_byIndex() { assertEquals(dog5, removedDog); assertEquals(TEST_SIZE - 1, dogs.size()); + assertEquals(TEST_SIZE, realm.where(Dog.class).count()); + } + + @Test + public void remove_first() { + Owner owner = realm.where(Owner.class).findFirst(); + RealmList dogs = owner.getDogs(); + + realm.beginTransaction(); + dogs.remove(0); + realm.commitTransaction(); + + assertEquals(TEST_SIZE - 1, dogs.size()); + assertEquals(TEST_SIZE, realm.where(Dog.class).count()); } @Test @@ -485,6 +499,7 @@ public void remove_last() { realm.commitTransaction(); assertEquals(TEST_SIZE - 1, dogs.size()); + assertEquals(TEST_SIZE, realm.where(Dog.class).count()); } @Test @@ -510,6 +525,7 @@ public void remove_byObject() { assertTrue(result); assertEquals(TEST_SIZE - 1, dogs.size()); + assertEquals(TEST_SIZE, realm.where(Dog.class).count()); } @Test @@ -610,7 +626,7 @@ public void removeAll_unmanaged_wrongClass() { } @Test - public void remove_allAfterContainerObjectRemoved() { + public void removeAll_afterContainerObjectRemoved() { RealmList dogs = createDeletedRealmList(); realm.beginTransaction(); @@ -618,6 +634,14 @@ public void remove_allAfterContainerObjectRemoved() { dogs.removeAll(Collections.emptyList()); } + @Test + public void removeAll_outsideTransaction() { + List objectsToRemove = Collections.singletonList(collection.get(0)); + thrown.expect(IllegalStateException.class); + thrown.expectMessage(CoreMatchers.containsString("Objects can only be removed from inside a write transaction")); + collection.removeAll(objectsToRemove); + } + @Test public void get_afterContainerObjectRemoved() { RealmList dogs = createDeletedRealmList(); @@ -762,66 +786,6 @@ public void realmMethods_onDeletedLinkView() { } } - @Test - public void removeAllFromRealm() { - Owner owner = realm.where(Owner.class).findFirst(); - RealmList dogs = owner.getDogs(); - assertEquals(TEST_SIZE, dogs.size()); - - realm.beginTransaction(); - dogs.deleteAllFromRealm(); - realm.commitTransaction(); - assertEquals(0, dogs.size()); - assertEquals(0, realm.where(Dog.class).count()); - } - - @Test - public void removeAllFromRealm_outsideTransaction() { - Owner owner = realm.where(Owner.class).findFirst(); - RealmList dogs = owner.getDogs(); - try { - dogs.deleteAllFromRealm(); - fail("removeAllFromRealm should be called in a transaction."); - } catch (IllegalStateException e) { - assertThat(e.getMessage(), CoreMatchers.containsString("Must be in a write transaction ")); - } - } - - @Test - public void removeAllFromRealm_emptyList() { - RealmList dogs = realm.where(Owner.class).findFirst().getDogs(); - assertEquals(TEST_SIZE, dogs.size()); - - realm.beginTransaction(); - dogs.deleteAllFromRealm(); - realm.commitTransaction(); - assertEquals(0, dogs.size()); - assertEquals(0, realm.where(Dog.class).count()); - - // The dogs is empty now. - realm.beginTransaction(); - dogs.deleteAllFromRealm(); - realm.commitTransaction(); - assertEquals(0, dogs.size()); - assertEquals(0, realm.where(Dog.class).count()); - - } - - @Test - public void removeAllFromRealm_invalidListShouldThrow() { - RealmList dogs = realm.where(Owner.class).findFirst().getDogs(); - assertEquals(TEST_SIZE, dogs.size()); - realm.close(); - realm = null; - - try { - dogs.deleteAllFromRealm(); - fail("dogs is invalid and it should throw an exception"); - } catch (IllegalStateException e) { - assertEquals("This Realm instance has already been closed, making it unusable.", e.getMessage()); - } - } - @Test public void add_set_objectFromOtherThread() { final CountDownLatch finishedLatch = new CountDownLatch(1); @@ -871,7 +835,7 @@ public void add_set_dynamicObjectFromOtherThread() throws Throwable { final DynamicRealmObject dynDog = dynamicRealm.where(Dog.CLASS_NAME).findFirst(); final String expectedMsg = "Cannot copy an object to a Realm instance created in another thread."; - final AtomicReference thrownErrorRef = new AtomicReference(); + final AtomicReference thrownErrorRef = new AtomicReference<>(); new Thread(new Runnable() { @Override @@ -963,6 +927,7 @@ public void add_set_withWrongDynamicObjectType() { @Test public void add_set_dynamicObjectCreatedFromTypedRealm() { final String expectedMsg = "Cannot copy DynamicRealmObject between Realm instances."; + //noinspection ConstantConditions DynamicRealmObject dynDog = new DynamicRealmObject(realm.where(Dog.class).findFirst()); DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp index fe15002409..19ac174bc1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp @@ -192,6 +192,17 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsList_nativeIsValid(JNIEnv* e return JNI_FALSE; } +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeDelete(JNIEnv* env, jclass, jlong list_ptr, jlong index) +{ + TR_ENTER_PTR(list_ptr) + + try { + auto& list = *reinterpret_cast(list_ptr); + list.delete_at(S(index)); + } + CATCH_STD() +} + JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeDeleteAll(JNIEnv* env, jclass, jlong list_ptr) { TR_ENTER_PTR(list_ptr) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index d1a101fda6..4e3e0fbc90 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit d1a101fda6999e070c1e73cc5aff002c3de7c129 +Subproject commit 4e3e0fbc90b0c5cea53bfe07c2b93da2a033fd5e diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 86575af28e..48fdc9292f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -584,7 +584,7 @@ public RealmResults sort(String[] fieldNames, Sort[] sortOrders) { public void deleteFromRealm(int location) { if (isManaged()) { checkValidRealm(); - osList.remove(location); + osList.delete(location); modCount++; } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsList.java b/realm/realm-library/src/main/java/io/realm/internal/OsList.java index 6346d1c141..7d9a794f63 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsList.java @@ -78,6 +78,10 @@ public boolean isValid() { return nativeIsValid(nativePtr); } + public void delete(long index) { + nativeDelete(nativePtr, index); + } + public void deleteAll() { nativeDeleteAll(nativePtr); } @@ -113,5 +117,7 @@ public Table getTargetTable() { private static native boolean nativeIsValid(long nativePtr); + private static native void nativeDelete(long nativePtr, long index); + private static native void nativeDeleteAll(long nativePtr); } From 0e779b51694c6dfb755257933ddada013d894c42 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 11 Sep 2017 10:42:27 +0200 Subject: [PATCH 0951/2110] RxJava2 Support (#4991) * Moved all API's to RxJava2 * Added support change changeset observables --- CHANGELOG.md | 9 + Jenkinsfile | 2 +- examples/settings.gradle | 6 +- realm/realm-library/build.gradle | 4 +- .../io/realm/RealmConfigurationTests.java | 66 ++- .../java/io/realm/RxJavaTests.java | 506 +++++++++++++----- .../java/io/realm/internal/JNITableTest.java | 6 +- .../src/main/java/io/realm/BaseRealm.java | 11 +- .../src/main/java/io/realm/DynamicRealm.java | 5 +- .../src/main/java/io/realm/Realm.java | 5 +- .../java/io/realm/RealmConfiguration.java | 2 +- .../src/main/java/io/realm/RealmList.java | 47 +- .../src/main/java/io/realm/RealmObject.java | 97 +++- .../src/main/java/io/realm/RealmResults.java | 46 +- .../java/io/realm/internal/util/Pair.java | 15 +- .../java/io/realm/rx/CollectionChange.java | 100 ++++ .../main/java/io/realm/rx/ObjectChange.java | 103 ++++ .../io/realm/rx/RealmObservableFactory.java | 467 ++++++++++++---- .../java/io/realm/rx/RxObservableFactory.java | 143 ++++- 19 files changed, 1281 insertions(+), 359 deletions(-) create mode 100644 realm/realm-library/src/main/java/io/realm/rx/CollectionChange.java create mode 100644 realm/realm-library/src/main/java/io/realm/rx/ObjectChange.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c07017c4f..c837445e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,21 @@ ### Breaking Changes * [ObjectServer] Updated protocol version to 19 which is only compatible with ROS > 2.0.0. +* Realm has upgraded its RxJava1 support to RxJava2 (#3497) + * `Realm.asObservable()` has been renamed to `Realm.asFlowable()`. + * `RealmList.asObservable()` has been renamed to `RealmList.asFlowable()`. + * `RealmResults.asObservable()` has been renamed to `RealmResults.asFlowable()`. + * `RealmObject.asObservable()` has been renamed to `RealmObject.asFlowable()`. + * `RxObservableFactory` now return RxJava2 types instead of RxJava1 types. ### Deprecated ### Enhancements * Added `static RealmObject.getRealm(RealmModel)`, `RealmObject.getRealm()` and `DynamicRealmObject.getDynamicRealm()` (#4720). +* Added `RealmResults.asChangesetObservable()` that emits the pair `(results, changeset)` (#4277). +* Added `RealmList.asChangesetObservable()` that emits the pair `(list, changeset)` (#4277). +* Added `RealmObject.asChangesetObservable()` that emits the pair `(object, changeset)` (#4277). ### Bug Fixes diff --git a/Jenkinsfile b/Jenkinsfile index dc74785a4d..b1613502e4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -51,7 +51,7 @@ try { } } finally { storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + // storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' FIXME when updating examples step([$class: 'LintPublisher']) } } diff --git a/examples/settings.gradle b/examples/settings.gradle index 0f9f5242bd..361ed3b8f1 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -9,9 +9,9 @@ include 'moduleExample:app' include 'moduleExample:library' include 'realmModuleExample' include 'threadExample' -include 'unitTestExample' -include 'newsreaderExample' -include 'rxJavaExample' +//include 'unitTestExample' FIXME: Upgrade to RxJava2 +//include 'newsreaderExample' FIXME: Upgrade to RxJava2 +//include 'rxJavaExample' FIXME: Upgrade to RxJava2 include 'objectServerExample' rootProject.name = 'realm-examples' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 63c26342a1..7ffe3588bb 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -179,7 +179,7 @@ repositories { dependencies { - compileOnly 'io.reactivex:rxjava:1.1.0' + compileOnly 'io.reactivex.rxjava2:rxjava:2.1.0' compileOnly 'com.google.code.findbugs:findbugs-annotations:3.0.1' api "io.realm:realm-annotations:${version}" @@ -191,7 +191,7 @@ dependencies { kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation fileTree(dir: 'testLibs', include: ['*.jar']) - androidTestImplementation 'io.reactivex:rxjava:1.1.0' + androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.0' androidTestImplementation 'com.android.support.test:runner:1.0.0' androidTestImplementation 'com.android.support.test:rules:1.0.0' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index b6a6955cfe..3197a6b663 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -16,6 +16,11 @@ package io.realm; +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.test.MoreAsserts; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -24,15 +29,13 @@ import org.junit.runner.RunWith; import org.mockito.Mockito; -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; -import android.test.MoreAsserts; - import java.io.File; import java.io.IOException; import java.util.Set; +import io.reactivex.Flowable; +import io.reactivex.Observable; +import io.reactivex.Single; import io.realm.entities.AllTypes; import io.realm.entities.AnimalModule; import io.realm.entities.AssetFileModule; @@ -49,9 +52,10 @@ import io.realm.internal.modules.CompositeMediator; import io.realm.internal.modules.FilterableMediator; import io.realm.rule.TestRealmConfigurationFactory; +import io.realm.rx.CollectionChange; +import io.realm.rx.ObjectChange; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; -import rx.Observable; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -701,52 +705,82 @@ public void modelClasses_forFilterableMediator() throws Exception { public void rxFactory() { final RxObservableFactory dummyFactory = new RxObservableFactory() { @Override - public Observable from(Realm realm) { + public Flowable from(Realm realm) { + return null; + } + + @Override + public Flowable from(DynamicRealm realm) { + return null; + } + + @Override + public Flowable> from(Realm realm, RealmResults results) { + return null; + } + + @Override + public Observable>> changesetsFrom(Realm realm, RealmResults results) { + return null; + } + + @Override + public Flowable> from(DynamicRealm realm, RealmResults results) { + return null; + } + + @Override + public Observable>> changesetsFrom(DynamicRealm realm, RealmResults results) { + return null; + } + + @Override + public Flowable> from(Realm realm, RealmList list) { return null; } @Override - public Observable from(DynamicRealm realm) { + public Observable>> changesetsFrom(Realm realm, RealmList list) { return null; } @Override - public Observable> from(Realm realm, RealmResults results) { + public Flowable> from(DynamicRealm realm, RealmList list) { return null; } @Override - public Observable> from(DynamicRealm realm, RealmResults results) { + public Observable>> changesetsFrom(DynamicRealm realm, RealmList list) { return null; } @Override - public Observable> from(Realm realm, RealmList list) { + public Flowable from(Realm realm, E object) { return null; } @Override - public Observable> from(DynamicRealm realm, RealmList list) { + public Observable> changesetsFrom(Realm realm, E object) { return null; } @Override - public Observable from(Realm realm, E object) { + public Flowable from(DynamicRealm realm, DynamicRealmObject object) { return null; } @Override - public Observable from(DynamicRealm realm, DynamicRealmObject object) { + public Observable> changesetsFrom(DynamicRealm realm, DynamicRealmObject object) { return null; } @Override - public Observable> from(Realm realm, RealmQuery query) { + public Single> from(Realm realm, RealmQuery query) { return null; } @Override - public Observable> from(DynamicRealm realm, RealmQuery query) { + public Single> from(DynamicRealm realm, RealmQuery query) { return null; } }; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index eafe61d82d..0e3d29d2df 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -31,20 +31,24 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import io.reactivex.Flowable; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Predicate; import io.realm.entities.AllTypes; import io.realm.entities.CyclicType; import io.realm.entities.Dog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; -import rx.Observable; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; -import rx.functions.Func1; +import io.realm.rx.CollectionChange; +import io.realm.rx.ObjectChange; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -57,8 +61,8 @@ public class RxJavaTests { public final RunInLooperThread looperThread = new RunInLooperThread() { @Override public void looperTearDown() { - if (subscription != null && !subscription.isUnsubscribed()) { - subscription.unsubscribe(); + if (subscription != null && !subscription.isDisposed()) { + subscription.dispose(); } } }; @@ -66,7 +70,7 @@ public void looperTearDown() { public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); private Realm realm; - private Subscription subscription; + private Disposable subscription; @Before public void setUp() throws Exception { @@ -90,15 +94,57 @@ public void realmObject_emittedOnSubscribe() { realm.commitTransaction(); final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = obj.asObservable().subscribe(new Action1() { + subscription = obj.asFlowable().subscribe(new Consumer () { @Override - public void call(AllTypes rxObject) { + public void accept(AllTypes rxObject) throws Exception { assertTrue(rxObject == obj); subscribedNotified.set(true); } }); assertTrue(subscribedNotified.get()); - subscription.unsubscribe(); + subscription.dispose(); + } + + @Test + @UiThreadTest + public void realmObject_emitChangesetOnSubscribe() { + realm.beginTransaction(); + final AllTypes obj = realm.createObject(AllTypes.class); + realm.commitTransaction(); + + final AtomicBoolean subscribedNotified = new AtomicBoolean(false); + subscription = obj.asChangesetObservable().subscribe(new Consumer>() { + @Override + public void accept(ObjectChange change) throws Exception { + assertTrue(change.getObject() == obj); + assertNull(change.getChangeset()); + subscribedNotified.set(true); + } + }); + assertTrue(subscribedNotified.get()); + subscription.dispose(); + } + + @Test + @UiThreadTest + public void dynamicRealmObject_emitChangesetOnSubscribe() { + DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); + dynamicRealm.beginTransaction(); + final DynamicRealmObject obj = dynamicRealm.createObject(AllTypes.CLASS_NAME); + dynamicRealm.commitTransaction(); + + final AtomicBoolean subscribedNotified = new AtomicBoolean(false); + subscription = obj.asChangesetObservable().subscribe(new Consumer>() { + @Override + public void accept(ObjectChange change) throws Exception { + assertTrue(change.getObject() == obj); + assertNull(change.getChangeset()); + subscribedNotified.set(true); + } + }); + assertTrue(subscribedNotified.get()); + subscription.dispose(); + dynamicRealm.close(); } @Test @@ -110,10 +156,35 @@ public void realmObject_emittedOnUpdate() { final AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); - subscription = obj.asObservable().subscribe(new Action1() { + subscription = obj.asFlowable().subscribe(new Consumer() { + @Override + public void accept(AllTypes allTypes) throws Exception { + if (subscriberCalled.incrementAndGet() == 2) { + looperThread.testComplete(); + } + } + }); + + realm.beginTransaction(); + obj.setColumnLong(1); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread + public void realmObject_emittedChangesetOnUpdate() { + final AtomicInteger subscriberCalled = new AtomicInteger(0); + Realm realm = looperThread.getRealm(); + realm.beginTransaction(); + final AllTypes obj = realm.createObject(AllTypes.class); + realm.commitTransaction(); + + subscription = obj.asChangesetObservable().subscribe(new Consumer>() { @Override - public void call(AllTypes rxObject) { + public void accept(ObjectChange change) throws Exception { if (subscriberCalled.incrementAndGet() == 2) { + assertNotNull(change.getChangeset()); + assertTrue(change.getChangeset().isFieldChanged(AllTypes.FIELD_LONG)); looperThread.testComplete(); } } @@ -124,6 +195,31 @@ public void call(AllTypes rxObject) { realm.commitTransaction(); } + @Test + @RunTestInLooperThread + public void dynamicRealmObject_emittedChangesetOnUpdate() { + final AtomicInteger subscriberCalled = new AtomicInteger(0); + DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); + looperThread.closeAfterTest(realm); + realm.beginTransaction(); + final DynamicRealmObject obj = realm.createObject(AllTypes.CLASS_NAME); + realm.commitTransaction(); + + subscription = obj.asChangesetObservable().subscribe(new Consumer>() { + @Override + public void accept(ObjectChange change) throws Exception { + if (subscriberCalled.incrementAndGet() == 2) { + assertNotNull(change.getChangeset()); + assertTrue(change.getChangeset().isFieldChanged(AllTypes.FIELD_LONG)); + looperThread.testComplete(); + } + } + }); + realm.beginTransaction(); + obj.setLong(AllTypes.FIELD_LONG, 1); + realm.commitTransaction(); + } + @Test @UiThreadTest public void findFirst_emittedOnSubscribe() { @@ -132,15 +228,15 @@ public void findFirst_emittedOnSubscribe() { realm.commitTransaction(); final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 42).findFirst().asObservable() - .subscribe(new Action1() { + subscription = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 42).findFirst().asFlowable() + .subscribe(new Consumer () { @Override - public void call(AllTypes rxObject) { + public void accept(AllTypes allTypes) throws Exception { subscribedNotified.set(true); } }); assertTrue(subscribedNotified.get()); - subscription.unsubscribe(); + subscription.dispose(); } @Test @@ -152,15 +248,15 @@ public void findFirstAsync_emittedOnSubscribe() { final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final AllTypes asyncObj = realm.where(AllTypes.class).findFirstAsync(); - subscription = asyncObj.asObservable().subscribe(new Action1() { + subscription = asyncObj.asFlowable().subscribe(new Consumer() { @Override - public void call(AllTypes rxObject) { + public void accept(AllTypes rxObject) throws Exception { assertTrue(rxObject == asyncObj); subscribedNotified.set(true); } }); assertTrue(subscribedNotified.get()); - subscription.unsubscribe(); + subscription.dispose(); } @Test @@ -171,9 +267,9 @@ public void findFirstAsync_emittedOnUpdate() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); - subscription = realm.where(AllTypes.class).findFirstAsync().asObservable().subscribe(new Action1() { + subscription = realm.where(AllTypes.class).findFirstAsync().asFlowable().subscribe(new Consumer() { @Override - public void call(AllTypes rxObject) { + public void accept(AllTypes rxObject) throws Exception { if (subscriberCalled.incrementAndGet() == 2) { looperThread.testComplete(); } @@ -191,12 +287,12 @@ public void findFirstAsync_emittedOnDelete() { final AtomicInteger subscriberCalled = new AtomicInteger(0); final Realm realm = looperThread.getRealm(); realm.beginTransaction(); - final AllTypes obj = realm.createObject(AllTypes.class); + realm.createObject(AllTypes.class); realm.commitTransaction(); - subscription = realm.where(AllTypes.class).findFirstAsync().asObservable().subscribe(new Action1() { + subscription = realm.where(AllTypes.class).findFirstAsync().asFlowable().subscribe(new Consumer() { @Override - public void call(final AllTypes rxObject) { + public void accept(AllTypes rxObject) throws Exception { switch (subscriberCalled.incrementAndGet()) { case 1: assertFalse(rxObject.isLoaded()); @@ -228,16 +324,32 @@ public void execute(Realm realm) { public void realmResults_emittedOnSubscribe() { final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final RealmResults results = realm.where(AllTypes.class).findAll(); - subscription = results.asObservable().subscribe(new Action1>() { + subscription = results.asFlowable().subscribe(new Consumer>() { @Override @SuppressWarnings("ReferenceEquality") - public void call(RealmResults rxResults) { + public void accept(RealmResults rxResults) throws Exception { assertTrue(rxResults == results); subscribedNotified.set(true); } }); assertTrue(subscribedNotified.get()); - subscription.unsubscribe(); + subscription.dispose(); + } + + @Test + @UiThreadTest + public void realmResults_emittedChangesetOnSubscribe() { + final AtomicBoolean subscribedNotified = new AtomicBoolean(false); + final RealmResults results = realm.where(AllTypes.class).findAll(); + subscription = results.asChangesetObservable().subscribe(new Consumer>>() { + @Override + public void accept(CollectionChange> change) throws Exception { + assertEquals(results, change.getCollection()); + subscribedNotified.set(true); + } + }); + assertTrue(subscribedNotified.get()); + subscription.dispose(); } @Test @@ -247,16 +359,35 @@ public void realmList_emittedOnSubscribe() { realm.beginTransaction(); final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); realm.commitTransaction(); - subscription = list.asObservable().subscribe(new Action1>() { + subscription = list.asFlowable().subscribe(new Consumer>() { @Override @SuppressWarnings("ReferenceEquality") - public void call(RealmList rxList) { + public void accept(RealmList rxList) throws Exception { assertTrue(rxList == list); subscribedNotified.set(true); } }); assertTrue(subscribedNotified.get()); - subscription.unsubscribe(); + subscription.dispose(); + } + + @Test + @UiThreadTest + public void realmList_emittedChangesetOnSubscribe() { + final AtomicBoolean subscribedNotified = new AtomicBoolean(false); + realm.beginTransaction(); + final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + realm.commitTransaction(); + subscription = list.asChangesetObservable().subscribe(new Consumer>>() { + @Override + public void accept(CollectionChange> change) throws Exception { + assertEquals(list, change.getCollection()); + assertNull(change.getChangeset()); + subscribedNotified.set(true); + } + }); + assertTrue(subscribedNotified.get()); + subscription.dispose(); } @Test @@ -265,17 +396,36 @@ public void dynamicRealmResults_emittedOnSubscribe() { final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); - subscription = results.asObservable().subscribe(new Action1>() { + subscription = results.asFlowable().subscribe(new Consumer>() { @Override @SuppressWarnings("ReferenceEquality") - public void call(RealmResults rxResults) { + public void accept(RealmResults rxResults) throws Exception { assertTrue(rxResults == results); subscribedNotified.set(true); } }); assertTrue(subscribedNotified.get()); dynamicRealm.close(); - subscription.unsubscribe(); + subscription.dispose(); + } + + @Test + @UiThreadTest + public void dynamicRealmResults_emittedChangesetOnSubscribe() { + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); + final AtomicBoolean subscribedNotified = new AtomicBoolean(false); + final RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); + subscription = results.asChangesetObservable().subscribe(new Consumer>>() { + @Override + public void accept(CollectionChange> change) throws Exception { + assertEquals(results, change.getCollection()); + assertNull(change.getChangeset()); + subscribedNotified.set(true); + } + }); + assertTrue(subscribedNotified.get()); + dynamicRealm.close(); + subscription.dispose(); } @Test @@ -287,9 +437,9 @@ public void realmResults_emittedOnUpdate() { RealmResults results = realm.where(AllTypes.class).findAll(); realm.commitTransaction(); - subscription = results.asObservable().subscribe(new Action1>() { + subscription = results.asFlowable().subscribe(new Consumer>() { @Override - public void call(RealmResults allTypes) { + public void accept(RealmResults allTypes) throws Exception { if (subscriberCalled.incrementAndGet() == 2) { looperThread.testComplete(); } @@ -301,6 +451,29 @@ public void call(RealmResults allTypes) { realm.commitTransaction(); } + @Test + @RunTestInLooperThread + public void realmResults_emittedChangesetOnUpdate() { + final AtomicInteger subscriberCalled = new AtomicInteger(0); + Realm realm = looperThread.getRealm(); + realm.beginTransaction(); + RealmResults results = realm.where(AllTypes.class).findAll(); + realm.commitTransaction(); + + subscription = results.asChangesetObservable().subscribe(new Consumer>>() { + @Override + public void accept(CollectionChange> change) throws Exception { + if (subscriberCalled.incrementAndGet() == 2) { + assertEquals(1, change.getChangeset().getInsertions().length); + looperThread.testComplete(); + } + } + }); + realm.beginTransaction(); + realm.createObject(AllTypes.class); + realm.commitTransaction(); + } + @Test @RunTestInLooperThread public void realmList_emittedOnUpdate() { @@ -310,11 +483,36 @@ public void realmList_emittedOnUpdate() { final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); realm.commitTransaction(); - subscription = list.asObservable().subscribe(new Action1>() { + subscription = list.asFlowable().subscribe(new Consumer>() { + @Override + public void accept(RealmList dogs) throws Exception { + if (subscriberCalled.incrementAndGet() == 2) { + assertEquals(1, list.size()); + looperThread.testComplete(); + } + } + }); + + realm.beginTransaction(); + list.add(new Dog()); + realm.commitTransaction(); + } + + @Test + @RunTestInLooperThread + public void realmList_emittedChangesetOnUpdate() { + final AtomicInteger subscriberCalled = new AtomicInteger(0); + Realm realm = looperThread.getRealm(); + realm.beginTransaction(); + final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + realm.commitTransaction(); + + subscription = list.asChangesetObservable().subscribe(new Consumer>>() { @Override - public void call(RealmList dogs) { + public void accept(CollectionChange> change) throws Exception { if (subscriberCalled.incrementAndGet() == 2) { assertEquals(1, list.size()); + assertEquals(1, change.getChangeset().getInsertions().length); looperThread.testComplete(); } } @@ -334,9 +532,9 @@ public void dynamicRealmResults_emittedOnUpdate() { RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); dynamicRealm.commitTransaction(); - subscription = results.asObservable().subscribe(new Action1>() { + subscription = results.asFlowable().subscribe(new Consumer>() { @Override - public void call(RealmResults allTypes) { + public void accept(RealmResults dynamicRealmObjects) throws Exception { if (subscriberCalled.incrementAndGet() == 2) { dynamicRealm.close(); looperThread.testComplete(); @@ -349,21 +547,46 @@ public void call(RealmResults allTypes) { dynamicRealm.commitTransaction(); } + @Test + @RunTestInLooperThread + public void dynamicRealmResults_emittedChangesetOnUpdate() { + final AtomicInteger subscriberCalled = new AtomicInteger(0); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + looperThread.closeAfterTest(dynamicRealm); + dynamicRealm.beginTransaction(); + RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); + dynamicRealm.commitTransaction(); + + subscription = results.asChangesetObservable().subscribe(new Consumer>>() { + @Override + public void accept(CollectionChange> change) throws Exception { + if (subscriberCalled.incrementAndGet() == 2) { + assertEquals(1, change.getChangeset().getInsertions().length); + looperThread.testComplete(); + } + } + }); + + dynamicRealm.beginTransaction(); + dynamicRealm.createObject(AllTypes.CLASS_NAME); + dynamicRealm.commitTransaction(); + } + @Test @UiThreadTest public void findAllAsync_emittedOnSubscribe() { final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final RealmResults results = realm.where(AllTypes.class).findAllAsync(); - subscription = results.asObservable().subscribe(new Action1>() { + subscription = results.asFlowable().subscribe(new Consumer>() { @Override @SuppressWarnings("ReferenceEquality") - public void call(RealmResults rxResults) { + public void accept(RealmResults rxResults) throws Exception { assertTrue(rxResults == results); subscribedNotified.set(true); } }); assertTrue(subscribedNotified.get()); - subscription.unsubscribe(); + subscription.dispose(); } @Test @@ -371,9 +594,9 @@ public void call(RealmResults rxResults) { public void findAllAsync_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); Realm realm = looperThread.getRealm(); - subscription = realm.where(AllTypes.class).findAllAsync().asObservable().subscribe(new Action1>() { + subscription = realm.where(AllTypes.class).findAllAsync().asFlowable().subscribe(new Consumer>() { @Override - public void call(RealmResults rxResults) { + public void accept(RealmResults allTypes) throws Exception { if (subscriberCalled.incrementAndGet() == 2) { looperThread.testComplete(); } @@ -389,15 +612,15 @@ public void call(RealmResults rxResults) { @UiThreadTest public void realm_emittedOnSubscribe() { final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = realm.asObservable().subscribe(new Action1() { + subscription = realm.asFlowable().subscribe(new Consumer() { @Override - public void call(Realm rxRealm) { + public void accept(Realm rxRealm) throws Exception { assertTrue(rxRealm == realm); subscribedNotified.set(true); } }); assertTrue(subscribedNotified.get()); - subscription.unsubscribe(); + subscription.dispose(); } @Test @@ -405,9 +628,9 @@ public void call(Realm rxRealm) { public void realm_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); Realm realm = looperThread.getRealm(); - subscription = realm.asObservable().subscribe(new Action1() { + subscription = realm.asFlowable().subscribe(new Consumer() { @Override - public void call(Realm rxRealm) { + public void accept(Realm realm) throws Exception { if (subscriberCalled.incrementAndGet() == 2) { looperThread.testComplete(); } @@ -424,22 +647,23 @@ public void call(Realm rxRealm) { public void dynamicRealm_emittedOnSubscribe() { final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = dynamicRealm.asObservable().subscribe(new Action1() { + subscription = dynamicRealm.asFlowable().subscribe(new Consumer() { @Override - public void call(DynamicRealm rxRealm) { + public void accept(DynamicRealm rxRealm) throws Exception { assertTrue(rxRealm == dynamicRealm); subscribedNotified.set(true); } - }, new Action1() { + }, new Consumer() { @Override - public void call(Throwable throwable) { + public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); + fail(); } }); assertTrue(subscribedNotified.get()); dynamicRealm.close(); - subscription.unsubscribe(); + subscription.dispose(); } @Test @@ -447,9 +671,9 @@ public void call(Throwable throwable) { public void dynamicRealm_emittedOnUpdate() { final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); final AtomicInteger subscriberCalled = new AtomicInteger(0); - subscription = dynamicRealm.asObservable().subscribe(new Action1() { + subscription = dynamicRealm.asFlowable().subscribe(new Consumer() { @Override - public void call(DynamicRealm rxRealm) { + public void accept(DynamicRealm dynamicRealm) throws Exception { if (subscriberCalled.incrementAndGet() == 2) { dynamicRealm.close(); looperThread.testComplete(); @@ -466,15 +690,15 @@ public void call(DynamicRealm rxRealm) { @UiThreadTest public void unsubscribe_sameThread() { final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = realm.asObservable().subscribe(new Action1() { + subscription = realm.asFlowable().subscribe(new Consumer() { @Override - public void call(Realm rxRealm) { + public void accept(Realm rxRealm) throws Exception { assertTrue(rxRealm == realm); subscribedNotified.set(true); } }); assertEquals(1, realm.sharedRealm.realmNotifier.getListenersListSize()); - subscription.unsubscribe(); + subscription.dispose(); assertEquals(0, realm.sharedRealm.realmNotifier.getListenersListSize()); } @@ -483,9 +707,9 @@ public void call(Realm rxRealm) { public void unsubscribe_fromOtherThread() { final CountDownLatch unsubscribeCompleted = new CountDownLatch(1); final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - final Subscription subscription = realm.asObservable().subscribe(new Action1() { + final Disposable subscription = realm.asFlowable().subscribe(new Consumer() { @Override - public void call(Realm rxRealm) { + public void accept(Realm rxRealm) throws Exception { assertTrue(rxRealm == realm); subscribedNotified.set(true); } @@ -496,7 +720,7 @@ public void call(Realm rxRealm) { @Override public void run() { try { - subscription.unsubscribe(); + subscription.dispose(); fail(); } catch (IllegalStateException ignored) { } finally { @@ -506,7 +730,7 @@ public void run() { }).start(); TestHelper.awaitOrFail(unsubscribeCompleted); assertEquals(1, realm.sharedRealm.realmNotifier.getListenersListSize()); - // We cannot call subscription.unsubscribe() again, so manually close the extra Realm instance opened by + // We cannot call subscription.dispose() again, so manually close the extra Realm instance opened by // the Observable. realm.close(); } @@ -518,15 +742,16 @@ public void wrongGenericClassThrows() { final AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); - Observable obs = obj.asObservable(); - obs.subscribe(new Action1() { + Flowable obs = obj.asFlowable(); + @SuppressWarnings("unused") + Disposable subscription = obs.subscribe(new Consumer() { @Override - public void call(CyclicType cyclicType) { + public void accept(CyclicType cyclicType) throws Exception { fail(); } - }, new Action1() { + }, new Consumer() { @Override - public void call(Throwable ignored) { + public void accept(Throwable ignored) throws Exception { } }); } @@ -534,21 +759,22 @@ public void call(Throwable ignored) { @Test @UiThreadTest public void realm_closeInDoOnUnsubscribe() { - Observable observable = realm.asObservable() - .doOnUnsubscribe(new Action0() { + Flowable observable = realm.asFlowable() + .doOnCancel(new Action() { @Override - public void call() { + public void run() throws Exception { realm.close(); } }); - subscription = observable.subscribe(new Action1() { + subscription = observable.subscribe(new Consumer() { @Override - public void call(Realm rxRealm) { + public void accept(Realm realm) throws Exception { + assertEquals(2, Realm.getLocalInstanceCount(realm.getConfiguration())); } }); - subscription.unsubscribe(); + subscription.dispose(); assertTrue(realm.isClosed()); } @@ -557,42 +783,42 @@ public void call(Realm rxRealm) { public void dynamicRealm_closeInDoOnUnsubscribe() { final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); - Observable observable = dynamicRealm.asObservable() - .doOnUnsubscribe(new Action0() { + Flowable observable = dynamicRealm.asFlowable() + .doOnCancel(new Action() { @Override - public void call() { + public void run() throws Exception { dynamicRealm.close(); } }); - subscription = observable.subscribe(new Action1() { + subscription = observable.subscribe(new Consumer() { @Override - public void call(DynamicRealm rxRealm) { + public void accept(DynamicRealm ignored) throws Exception { } }); - subscription.unsubscribe(); + subscription.dispose(); assertTrue(dynamicRealm.isClosed()); } @Test @UiThreadTest public void realmResults_closeInDoOnUnsubscribe() { - Observable> observable = realm.where(AllTypes.class).findAll().asObservable() - .doOnUnsubscribe(new Action0() { + Flowable> observable = realm.where(AllTypes.class).findAll().asFlowable() + .doOnCancel(new Action() { @Override - public void call() { + public void run() throws Exception { realm.close(); } }); - subscription = observable.subscribe(new Action1>() { + subscription = observable.subscribe(new Consumer>() { @Override - public void call(RealmResults allTypes) { + public void accept(RealmResults ignored) throws Exception { } }); - subscription.unsubscribe(); + subscription.dispose(); assertTrue(realm.isClosed()); } @@ -603,19 +829,19 @@ public void realmList_closeInDoOnUnsubscribe() { RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); realm.commitTransaction(); - Observable> observable = list.asObservable().doOnUnsubscribe(new Action0() { + Flowable> observable = list.asFlowable().doOnCancel(new Action() { @Override - public void call() { + public void run() throws Exception { realm.close(); } }); - subscription = observable.subscribe(new Action1>() { + subscription = observable.subscribe(new Consumer>() { @Override - public void call(RealmList dogs) { + public void accept(RealmList ignored) throws Exception { } }); - subscription.unsubscribe(); + subscription.dispose(); assertTrue(realm.isClosed()); } @@ -624,21 +850,21 @@ public void call(RealmList dogs) { public void dynamicRealmResults_closeInDoOnUnsubscribe() { final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); - Observable> observable = dynamicRealm.where(AllTypes.CLASS_NAME).findAll().asObservable() - .doOnUnsubscribe(new Action0() { + Flowable> flowable = dynamicRealm.where(AllTypes.CLASS_NAME).findAll().asFlowable() + .doOnCancel(new Action() { @Override - public void call() { + public void run() throws Exception { dynamicRealm.close(); } }); - subscription = observable.subscribe(new Action1>() { + subscription = flowable.subscribe(new Consumer>() { @Override - public void call(RealmResults allTypes) { + public void accept(RealmResults ignored) throws Exception { } }); - subscription.unsubscribe(); + subscription.dispose(); assertTrue(dynamicRealm.isClosed()); } @@ -649,21 +875,21 @@ public void realmObject_closeInDoOnUnsubscribe() { realm.createObject(AllTypes.class); realm.commitTransaction(); - Observable observable = realm.where(AllTypes.class).findFirst().asObservable() - .doOnUnsubscribe(new Action0() { + Flowable flowable = realm.where(AllTypes.class).findFirst().asFlowable() + .doOnCancel(new Action() { @Override - public void call() { + public void run() throws Exception { realm.close(); } }); - subscription = observable.subscribe(new Action1() { + subscription = flowable.subscribe(new Consumer() { @Override - public void call(AllTypes allTypes) { + public void accept(AllTypes ignored) throws Exception { } }); - subscription.unsubscribe(); + subscription.dispose(); assertTrue(realm.isClosed()); } @@ -675,21 +901,21 @@ public void dynamicRealmObject_closeInDoOnUnsubscribe() { realm.commitTransaction(); final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); - Observable observable = dynamicRealm.where(AllTypes.CLASS_NAME).findFirst().asObservable() - .doOnUnsubscribe(new Action0() { + Flowable flowable = dynamicRealm.where(AllTypes.CLASS_NAME).findFirst().asFlowable() + .doOnCancel(new Action() { @Override - public void call() { + public void run() throws Exception { dynamicRealm.close(); } }); - subscription = observable.subscribe(new Action1() { + subscription = flowable.subscribe(new Consumer() { @Override - public void call(DynamicRealmObject obj) { + public void accept(DynamicRealmObject ignored) throws Exception { } }); - subscription.unsubscribe(); + subscription.dispose(); assertTrue(dynamicRealm.isClosed()); } @@ -697,6 +923,7 @@ public void call(DynamicRealmObject obj) { // waiting for results from the async API's. @Test @RunTestInLooperThread + @SuppressWarnings("CheckReturnValue") public void realmResults_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); @@ -710,26 +937,26 @@ public void realmResults_gcStressTest() { for (int i = 0; i < TEST_SIZE; i++) { // Doesn't keep a reference to the Observable. - realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, i).findAllAsync().asObservable() - .filter(new Func1, Boolean>() { + realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, i).findAllAsync().asFlowable() + .filter(new Predicate>() { @Override - public Boolean call(RealmResults results) { + public boolean test(RealmResults results) throws Exception { return results.isLoaded(); } }) .take(1) // Unsubscribes from Realm. - .subscribe(new Action1>() { + .subscribe(new Consumer>() { @Override - public void call(RealmResults result) { + public void accept(RealmResults allTypes) throws Exception { // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. Runtime.getRuntime().gc(); if (innerCounter.incrementAndGet() == TEST_SIZE) { looperThread.testComplete(); } } - }, new Action1() { + }, new Consumer() { @Override - public void call(Throwable throwable) { + public void accept(Throwable throwable) throws Exception { fail(throwable.toString()); } }); @@ -740,6 +967,7 @@ public void call(Throwable throwable) { // waiting for results from the async API's. @Test @RunTestInLooperThread + @SuppressWarnings("CheckReturnValue") public void dynamicRealmResults_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); @@ -753,17 +981,17 @@ public void dynamicRealmResults_gcStressTest() { for (int i = 0; i < TEST_SIZE; i++) { // Doesn't keep a reference to the Observable. - realm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findAllAsync().asObservable() - .filter(new Func1, Boolean>() { + realm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findAllAsync().asFlowable() + .filter(new Predicate>() { @Override - public Boolean call(RealmResults results) { + public boolean test(RealmResults results) throws Exception { return results.isLoaded(); } }) .take(1) // Unsubscribes from Realm. - .subscribe(new Action1>() { + .subscribe(new Consumer>() { @Override - public void call(RealmResults result) { + public void accept(RealmResults dynamicRealmObjects) throws Exception { // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. Runtime.getRuntime().gc(); if (innerCounter.incrementAndGet() == TEST_SIZE) { @@ -771,9 +999,9 @@ public void call(RealmResults result) { looperThread.testComplete(); } } - }, new Action1() { + }, new Consumer() { @Override - public void call(Throwable throwable) { + public void accept(Throwable throwable) throws Exception { fail(throwable.toString()); } }); @@ -784,6 +1012,7 @@ public void call(Throwable throwable) { // waiting for results from the async API's. @Test @RunTestInLooperThread + @SuppressWarnings("CheckReturnValue") public void realmObject_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); @@ -797,26 +1026,26 @@ public void realmObject_gcStressTest() { for (int i = 0; i < TEST_SIZE; i++) { // Doesn't keep a reference to the Observable. - realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, i).findFirstAsync().asObservable() - .filter(new Func1() { + realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, i).findFirstAsync().asFlowable() + .filter(new Predicate() { @Override - public Boolean call(AllTypes obj) { + public boolean test(AllTypes obj) throws Exception { return obj.isLoaded(); } }) .take(1) // Unsubscribes from Realm. - .subscribe(new Action1() { + .subscribe(new Consumer() { @Override - public void call(AllTypes result) { + public void accept(AllTypes allTypes) throws Exception { // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. Runtime.getRuntime().gc(); if (innerCounter.incrementAndGet() == TEST_SIZE) { looperThread.testComplete(); } } - }, new Action1() { + }, new Consumer() { @Override - public void call(Throwable throwable) { + public void accept(Throwable throwable) throws Exception { fail(throwable.toString()); } }); @@ -827,6 +1056,7 @@ public void call(Throwable throwable) { // waiting for results from the async API's. @Test @RunTestInLooperThread + @SuppressWarnings("CheckReturnValue") public void dynamicRealmObject_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); @@ -840,17 +1070,17 @@ public void dynamicRealmObject_gcStressTest() { for (int i = 0; i < TEST_SIZE; i++) { // Doesn't keep a reference to the Observable. - realm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findFirstAsync().asObservable() - .filter(new Func1() { + realm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findFirstAsync().asFlowable() + .filter(new Predicate() { @Override - public Boolean call(DynamicRealmObject obj) { + public boolean test(DynamicRealmObject obj) throws Exception { return obj.isLoaded(); } }) .take(1) // Unsubscribes from Realm. - .subscribe(new Action1() { + .subscribe(new Consumer() { @Override - public void call(DynamicRealmObject result) { + public void accept(DynamicRealmObject dynamicRealmObject) throws Exception { // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. Runtime.getRuntime().gc(); if (innerCounter.incrementAndGet() == TEST_SIZE) { @@ -858,9 +1088,9 @@ public void call(DynamicRealmObject result) { looperThread.testComplete(); } } - }, new Action1() { + }, new Consumer() { @Override - public void call(Throwable throwable) { + public void accept(Throwable throwable) throws Exception { fail(throwable.toString()); } }); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index fd893254ba..3ddea3c362 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -662,7 +662,7 @@ public void defaultValue_setAndGet() { new Pair(RealmFieldType.DOUBLE, Math.PI), new Pair(RealmFieldType.OBJECT, 0L) // FIXME: Currently, LIST does not support default value. - // new Pair(RealmFieldType.LIST, ) + // new CollectionChange(RealmFieldType.LIST, ) ); for (Pair columnInfo : columnInfoList) { @@ -783,7 +783,7 @@ public void defaultValue_setMultipleTimes() { new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}), new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L}) // FIXME: Currently, LIST does not support default value. - // new Pair(RealmFieldType.LIST, ) + // new CollectionChange(RealmFieldType.LIST, ) ); for (Pair columnInfo : columnInfoList) { @@ -913,7 +913,7 @@ public void defaultValue_overwrittenByNonDefault() { new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}), new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L}) // FIXME: Currently, LIST does not support default value. - // new Pair(RealmFieldType.LIST, ) + // new CollectionChange(RealmFieldType.LIST, ) ); for (Pair columnInfo : columnInfoList) { diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index ad0911e2a4..cd819a8608 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import io.reactivex.Flowable; import javax.annotation.Nullable; import io.realm.exceptions.RealmException; @@ -44,8 +45,6 @@ import io.realm.internal.Util; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.RealmLog; -import rx.Observable; - /** * Base class for all Realm instances. @@ -222,16 +221,16 @@ protected void removeListener(RealmChangeListener liste } /** - * Returns an RxJava Observable that monitors changes to this Realm. It will emit the current state + * Returns an RxJava Flowable that monitors changes to this Realm. It will emit the current state * when subscribed to. Items will continually be emitted as the Realm is updated - * {@code onComplete} will never be called. *

            - * If you would like the {@code asObservable()} to stop emitting items, you can instruct RxJava to + * If you would like the {@code asFlowable()} to stop emitting items, you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: *

            *

                  * {@code
            -     * realm.asObservable().first().subscribe( ... ) // You only get the results once
            +     * realm.asFlowable().first().subscribe( ... ) // You only get the results once
                  * }
                  * 
            * @@ -239,7 +238,7 @@ protected void removeListener(RealmChangeListener liste * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath. * @see RxJava and Realm */ - public abstract Observable asObservable(); + public abstract Flowable asFlowable(); /** * Removes all user-defined change listeners. diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 10bb52dab8..1dec0499b0 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -16,6 +16,7 @@ package io.realm; +import io.reactivex.Flowable; import java.util.Locale; import io.realm.exceptions.RealmException; @@ -25,8 +26,6 @@ import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.log.RealmLog; -import rx.Observable; - /** * DynamicRealm is a dynamic variant of {@link io.realm.Realm}. This means that all access to data and/or queries are @@ -261,7 +260,7 @@ static DynamicRealm createInstance(SharedRealm sharedRealm) { * {@inheritDoc} */ @Override - public Observable asObservable() { + public Flowable asFlowable() { return configuration.getRxFactory().from(this); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 63b75e95b2..bd85dd7bb3 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -46,6 +46,7 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; +import io.reactivex.Flowable; import javax.annotation.Nullable; import io.realm.exceptions.RealmException; @@ -63,8 +64,6 @@ import io.realm.internal.Table; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; -import rx.Observable; - /** * The Realm class is the storage and transactional manager of your object persistent store. It is in charge of creating @@ -183,7 +182,7 @@ private static OsSchemaInfo createExpectedSchemaInfo(RealmProxyMediator mediator * {@inheritDoc} */ @Override - public Observable asObservable() { + public Flowable asFlowable() { return configuration.getRxFactory().from(this); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 4504abbc05..ad52ddfa33 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -399,7 +399,7 @@ public String toString() { static synchronized boolean isRxJavaAvailable() { if (rxJavaAvailable == null) { try { - Class.forName("rx.Observable"); + Class.forName("io.reactivex.Flowable"); rxJavaAvailable = true; } catch (ClassNotFoundException ignore) { rxJavaAvailable = false; diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 48fdc9292f..9eaf1645e6 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -28,14 +28,15 @@ import java.util.Locale; import java.util.NoSuchElementException; +import io.reactivex.Flowable; +import io.reactivex.Observable; import javax.annotation.Nonnull; import javax.annotation.Nullable; import io.realm.internal.InvalidRow; import io.realm.internal.OsList; import io.realm.internal.RealmObjectProxy; -import rx.Observable; - +import io.realm.rx.CollectionChange; /** * RealmList is used to model one-to-many relationships in a {@link io.realm.RealmObject}. @@ -877,16 +878,16 @@ public String toString() { } /** - * Returns an Rx Observable that monitors changes to this RealmList. It will emit the current RealmList when + * Returns an Rx Flowable that monitors changes to this RealmList. It will emit the current RealmList when * subscribed to. RealmList will continually be emitted as the RealmList is updated - * {@code onComplete} will never be called. *

            - * If you would like the {@code asObservable()} to stop emitting items you can instruct RxJava to + * If you would like the {@code asFlowable()} to stop emitting items you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: *

            *

                  * {@code
            -     * list.asObservable()
            +     * list.asFlowable()
                  *      .first()
                  *      .subscribe( ... ) // You only get the results once
                  * }
            @@ -902,17 +903,47 @@ public String toString() {
                  * @see RxJava and Realm
                  */
                 @SuppressWarnings("unchecked")
            -    public Observable> asObservable() {
            +    public Flowable> asFlowable() {
                     if (realm instanceof Realm) {
                         return realm.configuration.getRxFactory().from((Realm) realm, this);
                     } else if (realm instanceof DynamicRealm) {
                         DynamicRealm dynamicRealm = (DynamicRealm) realm;
                         RealmList dynamicList = (RealmList) this;
                         @SuppressWarnings("UnnecessaryLocalVariable")
            -            Observable results = realm.configuration.getRxFactory().from(dynamicRealm, dynamicList);
            +            Flowable results = realm.configuration.getRxFactory().from(dynamicRealm, dynamicList);
                         return results;
                     } else {
            -            throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava.");
            +            throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2.");
            +        }
            +    }
            +
            +    /**
            +     * Returns an Rx Observable that monitors changes to this RealmList. It will emit the current RealmList when
            +     * subscribed. For each update to the RealmList a pair consisting of the RealmList and the
            +     * {@link OrderedCollectionChangeSet} will be sent. The changeset will be {@code null} the first
            +     * time an RealmList is emitted.
            +     * 

            + * RealmList will continually be emitted as the RealmList is updated - {@code onComplete} will never be called. + *

            + * * Note that when the {@link Realm} is accessed from threads other than where it was created, + * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers + * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} + * instead. + * + * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. + * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the + * corresponding Realm instance doesn't support RxJava. + * @see RxJava and Realm + */ + public Observable>> asChangesetObservable() { + if (realm instanceof Realm) { + return realm.configuration.getRxFactory().changesetsFrom((Realm) realm, this); + } else if (realm instanceof DynamicRealm) { + DynamicRealm dynamicRealm = (DynamicRealm) realm; + RealmList dynamicResults = (RealmList) this; + return (Observable) realm.configuration.getRxFactory().changesetsFrom(dynamicRealm, dynamicResults); + } else { + throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2."); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 020c7f5b07..6de1183104 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -18,13 +18,14 @@ import android.app.IntentService; +import io.reactivex.Flowable; +import io.reactivex.Observable; import io.realm.annotations.RealmClass; import io.realm.internal.InvalidRow; import io.realm.internal.ManagableObject; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import rx.Observable; - +import io.realm.rx.ObjectChange; /** * In Realm you define your RealmObject classes by sub-classing RealmObject and adding fields to be persisted. You then @@ -123,7 +124,7 @@ public static void deleteFromRealm(E object) { * when observed. *

                  * {@code
            -     * realm.where(BannerRealm.class).equalTo("type", type).findFirstAsync().asObservable()
            +     * realm.where(BannerRealm.class).equalTo("type", type).findFirstAsync().asFlowable()
                  *      .filter(result.isLoaded() && result.isValid())
                  *      .first()
                  * }
            @@ -536,7 +537,7 @@ public static  void addChangeListener(E object, RealmObjec
                  * @throws IllegalStateException if you try to add a listener inside a transaction.
                  */
                 public static  void addChangeListener(E object, RealmChangeListener listener) {
            -        addChangeListener(object, new ProxyState.RealmChangeListenerWrapper(listener));
            +        addChangeListener(object, new ProxyState.RealmChangeListenerWrapper<>(listener));
                 }
             
                 /**
            @@ -601,7 +602,7 @@ public static  void removeChangeListener(E object, RealmOb
                  * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread.
                  */
                 public static  void removeChangeListener(E object, RealmChangeListener listener) {
            -        removeChangeListener(object, new ProxyState.RealmChangeListenerWrapper(listener));
            +        removeChangeListener(object, new ProxyState.RealmChangeListenerWrapper<>(listener));
                 }
             
                 /**
            @@ -652,19 +653,19 @@ public static  void removeAllChangeListeners(E object) {
                 }
             
                 /**
            -     * Returns an RxJava Observable that monitors changes to this RealmObject. It will emit the current object when
            +     * Returns an RxJava Flowable that monitors changes to this RealmObject. It will emit the current object when
                  * subscribed to. Object updates will continually be emitted as the RealmObject is updated -
                  * {@code onComplete} will never be called.
                  * 

            - * When chaining a RealmObject observable use {@code obj.asObservable()} to pass on + * When chaining a RealmObject flowable use {@code obj.asFlowable()} to pass on * type information, otherwise the type of the following observables will be {@code RealmObject}. *

            - * If you would like the {@code asObservable()} to stop emitting items you can instruct RxJava to + * If you would like the {@code asFlowable()} to stop emitting items you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: *

            *

                  * {@code
            -     * obj.asObservable()
            +     * obj.asFlowable()
                  *      .filter(obj -> obj.isLoaded())
                  *      .first()
                  *      .subscribe( ... ) // You only get the object once
            @@ -683,25 +684,47 @@ public static  void removeAllChangeListeners(E object) {
                  * corresponding Realm instance doesn't support RxJava.
                  * @see RxJava and Realm
                  */
            -    public final  Observable asObservable() {
            +    public final  Flowable asFlowable() {
                     //noinspection unchecked
            -        return (Observable) RealmObject.asObservable(this);
            +        return (Flowable) RealmObject.asFlowable(this);
            +    }
            +
            +    /**
            +     * Returns an Rx Observable that monitors changes to this RealmObject. It will emit the current RealmObject when
            +     * subscribed to. For each update to the RealmObject a pair consisting of the RealmObject and the
            +     * {@link ObjectChangeSet} will be sent. The changeset will be {@code null} the first
            +     * time the RealmObject is emitted.
            +     * 

            + * The RealmObject will continually be emitted as it is updated - {@code onComplete} will never be called. + *

            + * Note that when the {@link Realm} is accessed from threads other than where it was created, + * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers + * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} + * instead. + * + * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. + * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the + * corresponding Realm instance doesn't support RxJava. + * @see RxJava and Realm + */ + public final Observable> asChangesetObservable() { + return (Observable) RealmObject.asChangesetObservable(this); } /** - * Returns an RxJava Observable that monitors changes to this RealmObject. It will emit the current object when + * Returns an RxJava Flowable that monitors changes to this RealmObject. It will emit the current object when * subscribed to. Object updates will continuously be emitted as the RealmObject is updated - * {@code onComplete} will never be called. *

            - * When chaining a RealmObject observable use {@code obj.asObservable()} to pass on + * When chaining a RealmObject observable use {@code obj.asFlowable()} to pass on * type information, otherwise the type of the following observables will be {@code RealmObject}. *

            - * If you would like the {@code asObservable()} to stop emitting items you can instruct RxJava to + * If you would like the {@code asFlowable()} to stop emitting items you can instruct RxJava to * emit only the first item by using the {@code first()} operator: *

            *

                  * {@code
            -     * obj.asObservable()
            +     * obj.asFlowable()
                  *      .filter(obj -> obj.isLoaded())
                  *      .first()
                  *      .subscribe( ... ) // You only get the object once
            @@ -713,7 +736,7 @@ public final  Observable asObservable() {
                  * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath.
                  * @see RxJava and Realm
                  */
            -    public static  Observable asObservable(E object) {
            +    public static  Flowable asFlowable(E object) {
                     if (object instanceof RealmObjectProxy) {
                         RealmObjectProxy proxy = (RealmObjectProxy) object;
                         BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm();
            @@ -723,7 +746,7 @@ public static  Observable asObservable(E object) {
                             DynamicRealm dynamicRealm = (DynamicRealm) realm;
                             DynamicRealmObject dynamicObject = (DynamicRealmObject) object;
                             @SuppressWarnings("unchecked")
            -                Observable observable = (Observable) realm.configuration.getRxFactory().from(dynamicRealm, dynamicObject);
            +                Flowable observable = (Flowable) realm.configuration.getRxFactory().from(dynamicRealm, dynamicObject);
                             return observable;
                         } else {
                             throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava." +
            @@ -734,4 +757,44 @@ public static  Observable asObservable(E object) {
                         throw new IllegalArgumentException("Cannot create Observables from unmanaged RealmObjects");
                     }
                 }
            +
            +
            +    /**
            +     * Returns an Rx Observable that monitors changes to this RealmObject. It will emit the current RealmObject when
            +     * subscribed to. For each update to the RealmObject a pair consisting of the RealmObject and the
            +     * {@link ObjectChangeSet} will be sent. The changeset will be {@code null} the first
            +     * time the RealmObject is emitted.
            +     * 

            + * The RealmObject will continually be emitted as it is updated - {@code onComplete} will never be called. + *

            + * Note that when the {@link Realm} is accessed from threads other than where it was created, + * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers + * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} + * instead. + * + * @param object RealmObject class that is being observed. Must be this class or its super types. + * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. + * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the + * corresponding Realm instance doesn't support RxJava. + * @see RxJava and Realm + */ + public static Observable> asChangesetObservable(E object) { + if (object instanceof RealmObjectProxy) { + RealmObjectProxy proxy = (RealmObjectProxy) object; + BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); + if (realm instanceof Realm) { + return realm.configuration.getRxFactory().changesetsFrom((Realm) realm, object); + } else if (realm instanceof DynamicRealm) { + DynamicRealm dynamicRealm = (DynamicRealm) realm; + DynamicRealmObject dynamicObject = (DynamicRealmObject) object; + return (Observable) realm.configuration.getRxFactory().changesetsFrom(dynamicRealm, dynamicObject); + } else { + throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava." + + " See https://realm.io/docs/java/latest/#rxjava for more details."); + } + } else { + // TODO Is this true? Should we just return Observable.just(object) ? + throw new IllegalArgumentException("Cannot create Observables from unmanaged RealmObjects"); + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index b3596cbc43..b1d272dcc7 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -20,6 +20,8 @@ import android.annotation.SuppressLint; import android.os.Looper; +import io.reactivex.Flowable; +import io.reactivex.Observable; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -29,8 +31,7 @@ import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; -import rx.Observable; - +import io.realm.rx.CollectionChange; /** * This class holds all the matches of a {@link RealmQuery} for a given Realm. The objects are not copied from @@ -268,16 +269,16 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener - * If you would like the {@code asObservable()} to stop emitting items you can instruct RxJava to + * If you would like the {@code asFlowable()} to stop emitting items you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: *

            *

                  * {@code
            -     * realm.where(Foo.class).findAllAsync().asObservable()
            +     * realm.where(Foo.class).findAllAsync().asFlowable()
                  *      .filter(results -> results.isLoaded())
                  *      .first()
                  *      .subscribe( ... ) // You only get the results once
            @@ -295,17 +296,46 @@ public void removeChangeListener(OrderedRealmCollectionChangeListenerRxJava and Realm
                  */
                 @SuppressWarnings("unchecked")
            -    public Observable> asObservable() {
            +    public Flowable> asFlowable() {
                     if (realm instanceof Realm) {
                         return realm.configuration.getRxFactory().from((Realm) realm, this);
                     } else if (realm instanceof DynamicRealm) {
                         DynamicRealm dynamicRealm = (DynamicRealm) realm;
                         RealmResults dynamicResults = (RealmResults) this;
                         @SuppressWarnings("UnnecessaryLocalVariable")
            -            Observable results = realm.configuration.getRxFactory().from(dynamicRealm, dynamicResults);
            +            Flowable results = realm.configuration.getRxFactory().from(dynamicRealm, dynamicResults);
                         return results;
                     } else {
            -            throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava.");
            +            throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2.");
            +        }
            +    }
            +
            +    /**
            +     * Returns an Rx Observable that monitors changes to this RealmResults. It will emit the current RealmResults when
            +     * subscribed. For each update to the RealmResult a pair consisting of the RealmResults and the
            +     * {@link OrderedCollectionChangeSet} will be sent. The changeset will be {@code null} the first
            +     * time an RealmResults is emitted.
            +     * 

            + * RealmResults will continually be emitted as the RealmResults are updated - {@code onComplete} will never be called. + *

            Note that when the {@link Realm} is accessed from threads other than where it was created, + * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers + * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} + * instead. + * + * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. + * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the + * corresponding Realm instance doesn't support RxJava. + * @see RxJava and Realm + */ + public Observable>> asChangesetObservable() { + if (realm instanceof Realm) { + return realm.configuration.getRxFactory().changesetsFrom((Realm) realm, this); + } else if (realm instanceof DynamicRealm) { + DynamicRealm dynamicRealm = (DynamicRealm) realm; + RealmResults dynamicResults = (RealmResults) this; + return (Observable) realm.configuration.getRxFactory().changesetsFrom(dynamicRealm, dynamicResults); + } else { + throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2."); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java index 86fcd889d5..b57b6c0ae3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java +++ b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java @@ -17,16 +17,19 @@ package io.realm.internal.util; /** - * Copy from the Android framework to avoid the dependency on Android classes + slight adjustment - * to support older versions of Android. - * - * Original source: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util/Pair.java - * * Container to ease passing around a tuple of two objects. This object provides a sensible * implementation of equals(), returning true if equals() is true on each of the contained * objects. */ public class Pair { + /** + * Implementation notes: + * + * Copy from the Android framework to avoid the dependency on Android classes + slight adjustment + * to support older versions of Android. + * + * Original source: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util/Pair.java + */ public F first; public S second; @@ -85,6 +88,6 @@ public String toString() { * @return a Pair that is templatized with the types of a and b. */ public static Pair create(A a, B b) { - return new Pair(a, b); + return new Pair<>(a, b); } } \ No newline at end of file diff --git a/realm/realm-library/src/main/java/io/realm/rx/CollectionChange.java b/realm/realm-library/src/main/java/io/realm/rx/CollectionChange.java new file mode 100644 index 0000000000..13e25f59af --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/rx/CollectionChange.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.rx; + +import javax.annotation.Nullable; + +import io.realm.OrderedCollectionChangeSet; +import io.realm.OrderedRealmCollection; +import io.realm.RealmList; +import io.realm.RealmResults; + +/** + * Container wrapping the result of a {@link io.realm.OrderedRealmCollectionChangeListener} being triggered. + *

            + * This is used by {@link RealmResults#asChangesetObservable()}} and {@link RealmList#asChangesetObservable()} as + * RxJava is only capable of emitting one item, not multiple. + */ +public class CollectionChange { + + private final E collection; + private final OrderedCollectionChangeSet changeset; + + /** + * Constructor for a CollectionChange. + * + * @param collection the collection that changed. + * @param changeset the changeset describing the change. + */ + public CollectionChange(E collection, @Nullable OrderedCollectionChangeSet changeset) { + this.collection = collection; + this.changeset = changeset; + } + + /** + * Returns the collection that was updated. + * + * @return collection that was updated. + */ + public E getCollection() { + return collection; + } + + /** + * Returns the changeset describing the update. + *

            + * This will be {@code null} the first time the stream emits the collection as well as when a asynchronous query + * is loaded for the first time. + *

            + *

            +     * {@code
            +     * // Example
            +     * realm.where(Person.class).findAllAsync().asChangesetObservable()
            +     *   .subscribe(new Consumer() {
            +     *    \@Override
            +     *     public void accept(CollectionChange item) throws Exception {
            +     *       item.getChangeset(); // Will return null the first two times
            +     *   }
            +     * });
            +     * }
            +     * 
            + * + * @return the changeset describing how the collection was updated. + */ + @Nullable + public OrderedCollectionChangeSet getChangeset() { + return changeset; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CollectionChange that = (CollectionChange) o; + + if (!collection.equals(that.collection)) return false; + return changeset != null ? changeset.equals(that.changeset) : that.changeset == null; + } + + @Override + public int hashCode() { + int result = collection.hashCode(); + result = 31 * result + (changeset != null ? changeset.hashCode() : 0); + return result; + } +} \ No newline at end of file diff --git a/realm/realm-library/src/main/java/io/realm/rx/ObjectChange.java b/realm/realm-library/src/main/java/io/realm/rx/ObjectChange.java new file mode 100644 index 0000000000..b65ad15599 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/rx/ObjectChange.java @@ -0,0 +1,103 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + + +package io.realm.rx; + +import javax.annotation.Nullable; + +import io.realm.ObjectChangeSet; +import io.realm.RealmModel; +import io.realm.RealmObject; + +/** + * Container wrapping the result of a {@link io.realm.RealmObjectChangeListener} being triggered. + *

            + * This is used by {@link RealmObject#asChangesetObservable()} and {@link RealmObject#asChangesetObservable(RealmModel)} + * as RxJava is only capable of emitting one item, not multiple. + */ +public class ObjectChange { + + private final E object; + private final ObjectChangeSet changeset; + + /** + * Constructor for a ObjectChange. + * + * @param object the object that was updated. + * @param changeset the changeset describing the update. + */ + public ObjectChange(E object, @Nullable ObjectChangeSet changeset) { + this.object = object; + this.changeset = changeset; + } + + public E getObject() { + return object; + } + + /** + * Returns the changeset describing the update. + *

            + * This will be {@code null} the first time the stream emits the object as well as when a asynchronous query + * is loaded for the first time. + *

            + *

            +     * {@code
            +     * // Example
            +     * realm.where(Person.class).findFirstAsync().asChangesetObservable()
            +     *   .subscribe(new Consumer() {
            +     *    \@Override
            +     *     public void accept(ObjectChange item) throws Exception {
            +     *       item.getChangeset(); // Will return null the first two times
            +     *   }
            +     * });
            +     * }
            +     * 
            + * + * @return the changeset describing how the object was updated. + */ + @Nullable + public ObjectChangeSet getChangeset() { + return changeset; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ObjectChange that = (ObjectChange) o; + + if (!object.equals(that.object)) return false; + return changeset != null ? changeset.equals(that.changeset) : that.changeset == null; + } + + @Override + public int hashCode() { + int result = object.hashCode(); + result = 31 * result + (changeset != null ? changeset.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ObjectChange{" + + "object=" + object + + ", changeset=" + changeset + + '}'; + } +} \ No newline at end of file diff --git a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java index 72fd5ac68e..36fa70e5b8 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java @@ -19,341 +19,576 @@ import java.util.IdentityHashMap; import java.util.Map; +import io.reactivex.BackpressureStrategy; +import io.reactivex.Flowable; +import io.reactivex.FlowableEmitter; +import io.reactivex.FlowableOnSubscribe; +import io.reactivex.Observable; +import io.reactivex.ObservableEmitter; +import io.reactivex.ObservableOnSubscribe; +import io.reactivex.Single; +import io.reactivex.disposables.Disposables; import io.realm.DynamicRealm; import io.realm.DynamicRealmObject; +import io.realm.ObjectChangeSet; +import io.realm.OrderedCollectionChangeSet; +import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmConfiguration; import io.realm.RealmList; import io.realm.RealmModel; import io.realm.RealmObject; +import io.realm.RealmObjectChangeListener; import io.realm.RealmQuery; import io.realm.RealmResults; -import rx.Observable; -import rx.Subscriber; -import rx.functions.Action0; -import rx.subscriptions.Subscriptions; - /** - * Factory class for creating Observables for RxJava (<=1.1.*). + * Factory class for creating Observables for RxJava (<=2.0.*). * - * @see Realm#asObservable() - * @see RealmObject#asObservable() - * @see RealmResults#asObservable() - * @see DynamicRealm#asObservable() - * @see DynamicRealmObject#asObservable() + * @see Realm#asFlowable() () + * @see RealmObject#asFlowable() + * @see RealmResults#asFlowable() + * @see DynamicRealm#asFlowable() + * @see DynamicRealmObject#asFlowable() */ public class RealmObservableFactory implements RxObservableFactory { // Maps for storing strong references to Realm classes while they are subscribed to. // This is needed if users create Observables without manually maintaining a reference to them. // In that case RealmObjects/RealmResults/RealmLists might be GC'ed too early. - ThreadLocal> resultsRefs = new ThreadLocal>() { + private ThreadLocal> resultsRefs = new ThreadLocal>() { @Override protected StrongReferenceCounter initialValue() { - return new StrongReferenceCounter(); + return new StrongReferenceCounter<>(); } }; - ThreadLocal> listRefs = new ThreadLocal>() { + private ThreadLocal> listRefs = new ThreadLocal>() { @Override protected StrongReferenceCounter initialValue() { - return new StrongReferenceCounter(); + return new StrongReferenceCounter<>(); } }; - ThreadLocal> objectRefs = new ThreadLocal>() { + private ThreadLocal> objectRefs = new ThreadLocal>() { @Override protected StrongReferenceCounter initialValue() { - return new StrongReferenceCounter(); + return new StrongReferenceCounter<>(); } }; + private static final BackpressureStrategy BACK_PRESSURE_STRATEGY = BackpressureStrategy.LATEST; + @Override - public Observable from(Realm realm) { + public Flowable from(Realm realm) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new Observable.OnSubscribe() { + return Flowable.create(new FlowableOnSubscribe () { @Override - public void call(final Subscriber subscriber) { - // Gets instance to make sure that the Realm is open for as long as the - // Observable is subscribed to it. + public void subscribe(final FlowableEmitter emitter) throws Exception { + // Instance is cached by Realm, so no need to keep strong reference final Realm observableRealm = Realm.getInstance(realmConfig); final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm realm) { - if (!subscriber.isUnsubscribed()) { - subscriber.onNext(observableRealm); + if (!emitter.isCancelled()) { + emitter.onNext(realm); } } }; observableRealm.addChangeListener(listener); - subscriber.add(Subscriptions.create(new Action0() { + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override - public void call() { + public void run() { observableRealm.removeChangeListener(listener); observableRealm.close(); } })); - subscriber.onNext(observableRealm); + + // Emit current value immediately + emitter.onNext(observableRealm); } - }); + }, BACK_PRESSURE_STRATEGY); } @Override - public Observable from(DynamicRealm realm) { + public Flowable from(DynamicRealm realm) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new Observable.OnSubscribe() { + return Flowable.create(new FlowableOnSubscribe() { @Override - public void call(final Subscriber subscriber) { - // Gets instance to make sure that the Realm is open for as long as the - // Observable is subscribed to it. + public void subscribe(final FlowableEmitter emitter) throws Exception { + // Instance is cached by Realm, so no need to keep strong reference final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(DynamicRealm realm) { - if (!subscriber.isUnsubscribed()) { - subscriber.onNext(observableRealm); + if (!emitter.isCancelled()) { + emitter.onNext(realm); } } }; observableRealm.addChangeListener(listener); - subscriber.add(Subscriptions.create(new Action0() { + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override - public void call() { + public void run() { observableRealm.removeChangeListener(listener); observableRealm.close(); } })); - // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest - // value. - subscriber.onNext(observableRealm); + // Emit current value immediately + emitter.onNext(observableRealm); } - }); + }, BACK_PRESSURE_STRATEGY); } @Override - public Observable> from(final Realm realm, final RealmResults results) { + public Flowable> from(final Realm realm, final RealmResults results) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new Observable.OnSubscribe>() { + return Flowable.create(new FlowableOnSubscribe>() { @Override - public void call(final Subscriber> subscriber) { + public void subscribe(final FlowableEmitter> emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); resultsRefs.get().acquireReference(results); - final RealmChangeListener> listener = new RealmChangeListener>() { @Override - public void onChange(RealmResults result) { - if (!subscriber.isUnsubscribed()) { - subscriber.onNext(results); + public void onChange(RealmResults results) { + if (!emitter.isCancelled()) { + emitter.onNext(results); + } + } + }; + results.addChangeListener(listener); + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + results.removeChangeListener(listener); + observableRealm.close(); + resultsRefs.get().releaseReference(results); + } + })); + + // Emit current value immediately + emitter.onNext(results); + + } + }, BACK_PRESSURE_STRATEGY); + } + + @Override + public Observable>> changesetsFrom(Realm realm, final RealmResults results) { + final RealmConfiguration realmConfig = realm.getConfiguration(); + return Observable.create(new ObservableOnSubscribe>>() { + @Override + public void subscribe(final ObservableEmitter>> emitter) throws Exception { + // Gets instance to make sure that the Realm is open for as long as the + // Observable is subscribed to it. + final Realm observableRealm = Realm.getInstance(realmConfig); + resultsRefs.get().acquireReference(results); + final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults e, OrderedCollectionChangeSet changeSet) { + if (!emitter.isDisposed()) { + emitter.onNext(new CollectionChange>(results, changeSet)); } } }; results.addChangeListener(listener); - subscriber.add(Subscriptions.create(new Action0() { + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override - public void call() { + public void run() { results.removeChangeListener(listener); observableRealm.close(); resultsRefs.get().releaseReference(results); } })); - // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest - // value. - subscriber.onNext(results); + // Emit current value immediately + emitter.onNext(new CollectionChange<>(results, null)); } }); } @Override - public Observable> from(DynamicRealm realm, final RealmResults results) { + public Flowable> from(DynamicRealm realm, final RealmResults results) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new Observable.OnSubscribe>() { + return Flowable.create(new FlowableOnSubscribe>() { @Override - public void call(final Subscriber> subscriber) { + public void subscribe(final FlowableEmitter> emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); resultsRefs.get().acquireReference(results); - final RealmChangeListener> listener = new RealmChangeListener>() { @Override - public void onChange(RealmResults result) { - if (!subscriber.isUnsubscribed()) { - subscriber.onNext(results); + public void onChange(RealmResults results) { + if (!emitter.isCancelled()) { + emitter.onNext(results); } } }; results.addChangeListener(listener); - subscriber.add(Subscriptions.create(new Action0() { + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override - public void call() { + public void run() { results.removeChangeListener(listener); observableRealm.close(); resultsRefs.get().releaseReference(results); } })); - // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest - // value. - subscriber.onNext(results); + // Emit current value immediately + emitter.onNext(results); + + } + }, BACK_PRESSURE_STRATEGY); + } + + @Override + public Observable>> changesetsFrom(DynamicRealm realm, final RealmResults results) { + final RealmConfiguration realmConfig = realm.getConfiguration(); + return Observable.create(new ObservableOnSubscribe>>() { + @Override + public void subscribe(final ObservableEmitter>> emitter) throws Exception { + // Gets instance to make sure that the Realm is open for as long as the + // Observable is subscribed to it. + final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); + resultsRefs.get().acquireReference(results); + final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults results, OrderedCollectionChangeSet changeSet) { + if (!emitter.isDisposed()) { + emitter.onNext(new CollectionChange<>(results, changeSet)); + } + } + }; + results.addChangeListener(listener); + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + results.removeChangeListener(listener); + observableRealm.close(); + resultsRefs.get().releaseReference(results); + } + })); + + // Emit current value immediately + emitter.onNext(new CollectionChange<>(results, null)); } }); } @Override - public Observable> from(Realm realm, final RealmList list) { + public Flowable> from(Realm realm, final RealmList list) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new Observable.OnSubscribe>() { + return Flowable.create(new FlowableOnSubscribe>() { @Override - public void call(final Subscriber> subscriber) { + public void subscribe(final FlowableEmitter> emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); listRefs.get().acquireReference(list); - final RealmChangeListener> listener = new RealmChangeListener>() { @Override - public void onChange(RealmList result) { - if (!subscriber.isUnsubscribed()) { - subscriber.onNext(list); + public void onChange(RealmList results) { + if (!emitter.isCancelled()) { + emitter.onNext(list); } } }; list.addChangeListener(listener); - subscriber.add(Subscriptions.create(new Action0() { + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override - public void call() { + public void run() { list.removeChangeListener(listener); observableRealm.close(); listRefs.get().releaseReference(list); } })); - // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest - // value. - subscriber.onNext(list); + // Emit current value immediately + emitter.onNext(list); + + } + }, BACK_PRESSURE_STRATEGY); + } + + @Override + public Observable>> changesetsFrom(Realm realm, final RealmList list) { + final RealmConfiguration realmConfig = realm.getConfiguration(); + return Observable.create(new ObservableOnSubscribe>>() { + @Override + public void subscribe(final ObservableEmitter>> emitter) throws Exception { + // Gets instance to make sure that the Realm is open for as long as the + // Observable is subscribed to it. + final Realm observableRealm = Realm.getInstance(realmConfig); + listRefs.get().acquireReference(list); + final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList results, OrderedCollectionChangeSet changeSet) { + if (!emitter.isDisposed()) { + emitter.onNext(new CollectionChange<>(results, changeSet)); + } + } + }; + list.addChangeListener(listener); + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + list.removeChangeListener(listener); + observableRealm.close(); + listRefs.get().releaseReference(list); + } + })); + + // Emit current value immediately + emitter.onNext(new CollectionChange<>(list, null)); } }); } @Override - public Observable> from(DynamicRealm realm, final RealmList list) { + public Flowable> from(DynamicRealm realm, final RealmList list) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new Observable.OnSubscribe>() { + return Flowable.create(new FlowableOnSubscribe>() { @Override - public void call(final Subscriber> subscriber) { + public void subscribe(final FlowableEmitter> emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); listRefs.get().acquireReference(list); - final RealmChangeListener> listener = new RealmChangeListener>() { @Override - public void onChange(RealmList result) { - if (!subscriber.isUnsubscribed()) { - subscriber.onNext(list); + public void onChange(RealmList results) { + if (!emitter.isCancelled()) { + emitter.onNext(list); } } }; list.addChangeListener(listener); - subscriber.add(Subscriptions.create(new Action0() { + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override - public void call() { + public void run() { list.removeChangeListener(listener); observableRealm.close(); listRefs.get().releaseReference(list); } })); - // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest - // value. - subscriber.onNext(list); + // Emit current value immediately + emitter.onNext(list); + + } + }, BACK_PRESSURE_STRATEGY); + } + + @Override + public Observable>> changesetsFrom(DynamicRealm realm, final RealmList list) { + final RealmConfiguration realmConfig = realm.getConfiguration(); + return Observable.create(new ObservableOnSubscribe>>() { + @Override + public void subscribe(final ObservableEmitter>> emitter) throws Exception { + // Gets instance to make sure that the Realm is open for as long as the + // Observable is subscribed to it. + final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); + listRefs.get().acquireReference(list); + final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList results, OrderedCollectionChangeSet changeSet) { + if (!emitter.isDisposed()) { + emitter.onNext(new CollectionChange<>(results, changeSet)); + } + } + }; + list.addChangeListener(listener); + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + list.removeChangeListener(listener); + observableRealm.close(); + listRefs.get().releaseReference(list); + } + })); + + // Emit current value immediately + emitter.onNext(new CollectionChange<>(list, null)); } }); } @Override - public Observable from(final Realm realm, final E object) { + public Flowable from(final Realm realm, final E object) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new Observable.OnSubscribe() { + return Flowable.create(new FlowableOnSubscribe() { @Override - public void call(final Subscriber subscriber) { + public void subscribe(final FlowableEmitter emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); objectRefs.get().acquireReference(object); - final RealmChangeListener listener = new RealmChangeListener() { @Override - public void onChange(E object) { - if (!subscriber.isUnsubscribed()) { - subscriber.onNext(object); + public void onChange(E obj) { + if (!emitter.isCancelled()) { + emitter.onNext(obj); + } + } + }; + RealmObject.addChangeListener(object, listener); + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + RealmObject.removeChangeListener(object, listener); + observableRealm.close(); + objectRefs.get().releaseReference(object); + } + })); + + // Emit current value immediately + emitter.onNext(object); + + } + }, BACK_PRESSURE_STRATEGY); + } + + @Override + public Observable> changesetsFrom(Realm realm, final E object) { + final RealmConfiguration realmConfig = realm.getConfiguration(); + return Observable.create(new ObservableOnSubscribe>() { + @Override + public void subscribe(final ObservableEmitter> emitter) throws Exception { + // Gets instance to make sure that the Realm is open for as long as the + // Observable is subscribed to it. + final Realm observableRealm = Realm.getInstance(realmConfig); + objectRefs.get().acquireReference(object); + final RealmObjectChangeListener listener = new RealmObjectChangeListener() { + @Override + public void onChange(E obj, ObjectChangeSet changeSet) { + if (!emitter.isDisposed()) { + emitter.onNext(new ObjectChange<>(obj, changeSet)); } } }; RealmObject.addChangeListener(object, listener); - subscriber.add(Subscriptions.create(new Action0() { + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override - public void call() { + public void run() { RealmObject.removeChangeListener(object, listener); observableRealm.close(); objectRefs.get().releaseReference(object); } })); - // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest - // value. - subscriber.onNext(object); + // Emit current value immediately + emitter.onNext(new ObjectChange<>(object, null)); } }); } @Override - public Observable from(DynamicRealm realm, final DynamicRealmObject object) { + public Flowable from(DynamicRealm realm, final DynamicRealmObject object) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new Observable.OnSubscribe() { + return Flowable.create(new FlowableOnSubscribe() { @Override - public void call(final Subscriber subscriber) { + public void subscribe(final FlowableEmitter emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); objectRefs.get().acquireReference(object); - final RealmChangeListener listener = new RealmChangeListener() { @Override - public void onChange(DynamicRealmObject object) { - if (!subscriber.isUnsubscribed()) { - subscriber.onNext(object); + public void onChange(DynamicRealmObject obj) { + if (!emitter.isCancelled()) { + emitter.onNext(obj); } } }; RealmObject.addChangeListener(object, listener); - subscriber.add(Subscriptions.create(new Action0() { + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override - public void call() { + public void run() { RealmObject.removeChangeListener(object, listener); observableRealm.close(); objectRefs.get().releaseReference(object); } })); - // Immediately calls onNext with the current value, as due to Realm's auto-update, it will be the latest - // value. - subscriber.onNext(object); + // Emit current value immediately + emitter.onNext(object); + + } + }, BACK_PRESSURE_STRATEGY); + } + + @Override + public Observable> changesetsFrom(DynamicRealm realm, final DynamicRealmObject object) { + final RealmConfiguration realmConfig = realm.getConfiguration(); + return Observable.create(new ObservableOnSubscribe>() { + @Override + public void subscribe(final ObservableEmitter> emitter) throws Exception { + // Gets instance to make sure that the Realm is open for as long as the + // Observable is subscribed to it. + final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); + objectRefs.get().acquireReference(object); + final RealmObjectChangeListener listener = new RealmObjectChangeListener() { + @Override + public void onChange(DynamicRealmObject obj, ObjectChangeSet changeSet) { + if (!emitter.isDisposed()) { + emitter.onNext(new ObjectChange<>(obj, changeSet)); + } + } + }; + object.addChangeListener(listener); + + // Cleanup when stream is disposed + emitter.setDisposable(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + object.removeChangeListener(listener); + observableRealm.close(); + objectRefs.get().releaseReference(object); + } + })); + + // Emit current value immediately + emitter.onNext(new ObjectChange<>(object, null)); } }); } @Override - public Observable> from(Realm realm, RealmQuery query) { + public Single> from(Realm realm, RealmQuery query) { throw new RuntimeException("RealmQuery not supported yet."); } @Override - public Observable> from(DynamicRealm realm, RealmQuery query) { + public Single> from(DynamicRealm realm, RealmQuery query) { throw new RuntimeException("RealmQuery not supported yet."); } @@ -364,7 +599,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return 37; + return 37; // Random number } diff --git a/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java index efe20fbe92..04af747f38 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java @@ -16,16 +16,18 @@ package io.realm.rx; +import io.reactivex.Flowable; +import io.reactivex.Observable; +import io.reactivex.Single; import io.realm.DynamicRealm; import io.realm.DynamicRealmObject; +import io.realm.OrderedCollectionChangeSet; import io.realm.Realm; import io.realm.RealmList; import io.realm.RealmModel; import io.realm.RealmObject; import io.realm.RealmQuery; import io.realm.RealmResults; -import rx.Observable; - /** * Factory interface for creating Rx Observables for Realm classes. @@ -33,57 +35,86 @@ public interface RxObservableFactory { /** - * Creates an Observable for a {@link Realm}. It should emit the initial state of the Realm when subscribed to and + * Creates a Flowable for a {@link Realm}. It should emit the initial state of the Realm when subscribed to and * on each subsequent update of the Realm. *

            - * Realm observables are hot observables as Realms are automatically kept up to date. + * Realm flowables are hot as Realms are automatically kept up to date. * * @param realm {@link Realm} to listen to changes for. * @return Rx observable that emit all updates to the Realm. */ - Observable from(Realm realm); + Flowable from(Realm realm); /** - * Creates an Observable for a {@link DynamicRealm}. It should emit the initial state of the Realm when subscribed + * Creates a Flowable for a {@link DynamicRealm}. It should emit the initial state of the Realm when subscribed * to and on each subsequent update of the Realm. *

            - * DynamicRealm observables are hot observables as DynamicRealms are automatically kept up to date. + * DynamicRealm observables are hot as DynamicRealms are automatically kept up to date. * * @param realm {@link DynamicRealm} to listen to changes for. * @return Rx observable that emit all updates to the DynamicRealm. */ - Observable from(DynamicRealm realm); + Flowable from(DynamicRealm realm); /** - * Creates an Observable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and + * Creates a Flowable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and * on each subsequent update of the RealmResults. *

            - * RealmResults observables are hot observables as RealmResults are automatically kept up to date. + * RealmResults observables are hot as RealmResults are automatically kept up to date. * * @param results {@link RealmResults} to listen to changes for. * @param realm {@link Realm} instance results are coming from. * @param type of RealmObject * @return Rx observable that emit all updates to the RealmObject. */ - Observable> from(Realm realm, RealmResults results); + Flowable> from(Realm realm, RealmResults results); /** * Creates an Observable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and + * on each subsequent update of the RealmResults it should emit the RealmResults + the {@link OrderedCollectionChangeSet} + * that describes the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefor be left to users. + * + * @param results {@link RealmResults} to listen to changes for. + * @param realm {@link Realm} instance results are coming from. + * @param type of RealmObject + * @return Rx observable that emit all updates + their changeset. + */ + Observable>> changesetsFrom(Realm realm, RealmResults results); + + /** + * Creates a Flowable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and * on each subsequent update of the RealmResults. *

            - * Realm observables are hot observables as RealmResults are automatically kept up to date. + * Realm observables are hot as RealmResults are automatically kept up to date. * * @param results {@link RealmResults} to listen to changes for. * @param realm {@link DynamicRealm} instance results are coming from. * @return Rx observable that emit all updates to the RealmResults. */ - Observable> from(DynamicRealm realm, RealmResults results); + Flowable> from(DynamicRealm realm, RealmResults results); + + /** + * Creates an Observable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and + * on each subsequent update of the RealmResults it should emit the RealmResults + the {@link OrderedCollectionChangeSet} + * that describes the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefor be left to users. + * + * @param results {@link RealmResults} to listen to changes for. + * @param realm {@link Realm} instance results are coming from. + * @return Rx observable that emit all updates + their changeset. + */ + Observable>> changesetsFrom(DynamicRealm realm, RealmResults results); /** * Creates an Observable for a {@link RealmList}. It should emit the initial list when subscribed to and on each * subsequent update of the RealmList. *

            - * RealmList observables are hot observables as RealmLists are automatically kept up to date. + * RealmList observables are hot as RealmLists are automatically kept up to date. *

            * Note: {@link io.realm.RealmChangeListener} is currently not supported on RealmLists. * @@ -91,46 +122,102 @@ public interface RxObservableFactory { * @param realm {@link Realm} instance list is coming from. * @param type of RealmObject */ - Observable> from(Realm realm, RealmList list); + Flowable> from(Realm realm, RealmList list); /** - * Creates an Observable for a {@link RealmList}. It should emit the initial list when subscribed to and on each + * Creates an Observable for a {@link RealmList}. It should emit the initial RealmList when subscribed to and + * on each subsequent update of the RealmIst it should emit the RealmList + the {@link OrderedCollectionChangeSet} + * that describes the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefor be left to users. + * + * @param list {@link RealmList} to listen to changes for. + * @param realm {@link Realm} instance list is coming from. + * @param type of RealmObject + * @return Rx observable that emit all updates + their changeset. + */ + Observable>> changesetsFrom(Realm realm, RealmList list); + + /** + * Creates a Flowable for a {@link RealmList}. It should emit the initial list when subscribed to and on each * subsequent update of the RealmList. *

            - * RealmList observables are hot observables as RealmLists are automatically kept up to date. + * RealmList observables are hot as RealmLists are automatically kept up to date. *

            * Note: {@link io.realm.RealmChangeListener} is currently not supported on RealmLists. * * @param list RealmList to listen to changes for. * @param realm {@link DynamicRealm} instance list is coming from. */ - Observable> from(DynamicRealm realm, RealmList list); + Flowable> from(DynamicRealm realm, RealmList list); /** - * Creates an Observable for a {@link RealmObject}. It should emit the initial object when subscribed to and on each + * Creates an Observable for a {@link RealmList}. It should emit the initial RealmList when subscribed to and + * on each subsequent update of the RealmList it should emit the RealmList + the {@link OrderedCollectionChangeSet} + * that describes the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefor be left to users. + * + * @param list {@link RealmList} to listen to changes for. + * @param realm {@link Realm} instance list is coming from. + * @return Rx observable that emit all updates + their changeset. + */ + Observable>> changesetsFrom(DynamicRealm realm, RealmList list); + + /** + * Creates a Flowable for a {@link RealmObject}. It should emit the initial object when subscribed to and on each * subsequent update of the object. *

            - * RealmObject observables are hot observables as RealmObjects are automatically kept up to date. + * RealmObject observables are hot as RealmObjects are automatically kept up to date. * * @param object RealmObject to listen to changes for. * @param realm {@link Realm} instance object is coming from. * @param type of RealmObject */ - Observable from(Realm realm, E object); + Flowable from(Realm realm, E object); /** - * Creates an Observable for a {@link DynamicRealmObject}. It should emit the initial object when subscribed to and + * Creates an Observable for a {@link RealmObject}. It should emit the initial object when subscribed to and on each + * subsequent update of the object it should emit the object + the {@link io.realm.ObjectChangeSet} that describes + * the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefore be left to the user. + * + * @param object RealmObject to listen to changes for. + * @param realm {@link Realm} instance object is coming from. + * @param type of RealmObject + */ + Observable> changesetsFrom(Realm realm, E object); + + /** + * Creates a Flowable for a {@link DynamicRealmObject}. It should emit the initial object when subscribed to and * on each subsequent update of the object. *

            - * DynamicRealmObject observables are hot observables as DynamicRealmObjects automatically are kept up to date. + * DynamicRealmObject observables are hot as DynamicRealmObjects automatically are kept up to date. * * @param object DynamicRealmObject to listen to changes for. * @param realm {@link DynamicRealm} instance object is coming from. */ - Observable from(DynamicRealm realm, DynamicRealmObject object); + Flowable from(DynamicRealm realm, DynamicRealmObject object); + + /** + * Creates an Observable for a {@link RealmObject}. It should emit the initial object when subscribed to and on each + * subsequent update of the object it should emit the object + the {@link io.realm.ObjectChangeSet} that describes + * the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefore be left to the user. + * + * @param object RealmObject to listen to changes for. + * @param realm {@link Realm} instance object is coming from. + */ + Observable> changesetsFrom(DynamicRealm realm, DynamicRealmObject object); /** - * Creates an Observable from a {@link RealmQuery}. It should emit the query and then complete. + * Creates a Single from a {@link RealmQuery}. It should emit the query and then complete. *

            * A RealmQuery observable is cold. * @@ -138,15 +225,15 @@ public interface RxObservableFactory { * @param realm {@link Realm} instance query is coming from. * @param type of RealmObject */ - Observable> from(Realm realm, RealmQuery query); + Single> from(Realm realm, RealmQuery query); /** - * Creates an Observable from a {@link RealmQuery}. It should emit the query and then complete. + * Creates a Single from a {@link RealmQuery}. It should emit the query and then complete. *

            * A RealmQuery observable is cold. * * @param query RealmObject to listen to changes for. * @param realm {@link DynamicRealm} instance query is coming from. */ - Observable> from(DynamicRealm realm, RealmQuery query); + Single> from(DynamicRealm realm, RealmQuery query); } From a66abc7a0dbe3f592f4fc4bfeb0c6e56c12b661c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 11 Sep 2017 16:01:46 +0800 Subject: [PATCH 0952/2110] getColumnInfo was checking obfuscated class name It should check the table name instead. Close #5211 --- CHANGELOG.md | 1 + .../src/main/java/io/realm/internal/ColumnIndices.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a2ce692ce..d8334f307d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fixed a JNI memory issue when doing queries which might potentially cause various native crashes. * Fixed a bug that `RealmList.deleteFromRealm(int)`, `RealmList.deleteFirstFromRealm()` and `RealmList.deleteLastFromRealm()` did not remove target objects from Realm. This bug was introduced in `3.7.1` (#5233). +* Crash with "'xxx' doesn't exist in current schema." when ProGuard is enabled (#5211). ## 3.7.1 (2017-09-07) diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java index d391154a9a..b0f2bd15f4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java @@ -100,7 +100,7 @@ public ColumnInfo getColumnInfo(String simpleClassName) { if (columnInfo == null) { Set> modelClasses = mediator.getModelClasses(); for (Class modelClass : modelClasses) { - if (modelClass.getSimpleName().equals(simpleClassName)) { + if (Table.getClassNameForTable(mediator.getTableName(modelClass)).equals(simpleClassName)) { columnInfo = getColumnInfo(modelClass); simpleClassNameToColumnInfoMap.put(simpleClassName, columnInfo); break; From 5b5d1ab8f4c4469bb1c86c1b982a3051af6aa186 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 11 Sep 2017 16:51:37 +0200 Subject: [PATCH 0953/2110] Upgrade to 2.0.0-rc18 (#5229) --- dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies.list b/dependencies.list index 2187414c7a..404a4e0da0 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.0.0-rc16 -REALM_SYNC_SHA256=c20c4e7333f01a3a4ea350cb20b9b7feba95ad4e52d612368b6187b72d518aa1 +REALM_SYNC_VERSION=2.0.0-rc18 +REALM_SYNC_SHA256=73cb89c1a04cafa871444aa91d93eb9df16af088bcb7d3ede73bb815d53a06e5 # Object Server Release used by Integration tests # Stable releases: https://packagecloud.io/realm/realm?filter=debs diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 4e3e0fbc90..6f7804a233 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 4e3e0fbc90b0c5cea53bfe07c2b93da2a033fd5e +Subproject commit 6f7804a2332732e0c2d2db6bf920a38c75e72fb2 From 1545c6d7ba5ad3262358c3bcaccfb5f279c00f08 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 11 Sep 2017 18:51:03 +0800 Subject: [PATCH 0954/2110] Sign release apk for examples - Close #5184 . without siging, monkeyRelease will fail with INSTALL_PARSE_FAILED_NO_CERTIFICATES - Enable proguard for all most all examples, both debug & release. This will give us a chance to see proguard issues as early as possible. - Fix a "realm was closed" crash for thread example. --- examples/encryptionExample/build.gradle | 6 +++++- examples/gridViewExample/build.gradle | 6 +++++- examples/introExample/build.gradle | 6 +++++- examples/jsonExample/build.gradle | 6 +++++- examples/kotlinExample/build.gradle | 6 +++++- examples/migrationExample/build.gradle | 6 +++++- examples/newsreaderExample/build.gradle | 2 ++ examples/objectServerExample/build.gradle | 4 +++- examples/rxJavaExample/build.gradle | 2 ++ examples/secureTokenAndroidKeyStore/build.gradle | 7 ++++++- examples/threadExample/build.gradle | 6 +++++- .../java/io/realm/examples/threads/ThreadFragment.java | 1 + .../java/io/realm/examples/threads/widget/DotsView.java | 3 +++ examples/unitTestExample/build.gradle | 6 +++++- 14 files changed, 57 insertions(+), 10 deletions(-) diff --git a/examples/encryptionExample/build.gradle b/examples/encryptionExample/build.gradle index d8729bbf7a..4655dfef93 100644 --- a/examples/encryptionExample/build.gradle +++ b/examples/encryptionExample/build.gradle @@ -16,7 +16,11 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true } } productFlavors { diff --git a/examples/gridViewExample/build.gradle b/examples/gridViewExample/build.gradle index d5462a06d4..6aa6ec9c97 100644 --- a/examples/gridViewExample/build.gradle +++ b/examples/gridViewExample/build.gradle @@ -15,7 +15,11 @@ android { } buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true } } productFlavors { diff --git a/examples/introExample/build.gradle b/examples/introExample/build.gradle index 3b6de30d5d..5d4bb7093f 100644 --- a/examples/introExample/build.gradle +++ b/examples/introExample/build.gradle @@ -16,7 +16,11 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true } } diff --git a/examples/jsonExample/build.gradle b/examples/jsonExample/build.gradle index 5dc86a70ee..b4327f86b3 100644 --- a/examples/jsonExample/build.gradle +++ b/examples/jsonExample/build.gradle @@ -16,7 +16,11 @@ android { } buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true } } productFlavors { diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index a513e5bab8..ee0b573240 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -32,7 +32,11 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true } } diff --git a/examples/migrationExample/build.gradle b/examples/migrationExample/build.gradle index 0339504aa8..53e5aaafa7 100644 --- a/examples/migrationExample/build.gradle +++ b/examples/migrationExample/build.gradle @@ -15,7 +15,11 @@ android { } buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true } } command { diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index b2f4f664cc..a1b3fdb34c 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -15,7 +15,9 @@ android { } buildTypes { release { + // FIXME: Fix the proguard with 3rd party libs minifyEnabled false + signingConfig signingConfigs.debug } } command { diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 7dc1ce05f3..db3f530a2f 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -43,10 +43,12 @@ android { def host = getIP() debug { buildConfigField "String", "OBJECT_SERVER_IP", "\"${host}\"" + minifyEnabled true } release { - minifyEnabled false buildConfigField "String", "OBJECT_SERVER_IP", "\"${host}\"" + minifyEnabled true + signingConfig signingConfigs.debug } } diff --git a/examples/rxJavaExample/build.gradle b/examples/rxJavaExample/build.gradle index d628950cb0..070174b3b4 100644 --- a/examples/rxJavaExample/build.gradle +++ b/examples/rxJavaExample/build.gradle @@ -19,7 +19,9 @@ android { buildTypes { release { + // FIXME: Fix the proguard with 3rd party libs minifyEnabled false + signingConfig signingConfigs.debug } } diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index 7bcadc62e7..a25f094d9b 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -18,7 +18,12 @@ android { } buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + debug { + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } diff --git a/examples/threadExample/build.gradle b/examples/threadExample/build.gradle index d04750cd69..25980aa6a9 100644 --- a/examples/threadExample/build.gradle +++ b/examples/threadExample/build.gradle @@ -15,7 +15,11 @@ android { } buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true } } command { diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/ThreadFragment.java b/examples/threadExample/src/main/java/io/realm/examples/threads/ThreadFragment.java index 43b9da847a..fc61e0bef6 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/ThreadFragment.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/ThreadFragment.java @@ -169,6 +169,7 @@ public void onPause() { public void onStop() { super.onStop(); // Remember to close the Realm instance when done with it. + dotsView.setRealmResults(null); realm.close(); } } diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/widget/DotsView.java b/examples/threadExample/src/main/java/io/realm/examples/threads/widget/DotsView.java index e3fcbea651..3756f3ccd0 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/widget/DotsView.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/widget/DotsView.java @@ -77,6 +77,9 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); + if (results == null) { + return; + } canvas.drawColor(Color.TRANSPARENT); for (Dot dot : results) { circlePaint.setColor(dot.getColor()); diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index 6891e4b414..553d5fd771 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -19,7 +19,11 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true } } From ec8f0e652ea68a704eee4deaa3d087d955d88578 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 12 Sep 2017 15:24:54 +0800 Subject: [PATCH 0955/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8334f307d..a14c94f61a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.7.2 (YYYY-MM-DD) +## 3.7.2 (2017-09-12) ### Bug Fixes From d6d0b82a5e44cd3ee64f1025be9507544e1b2bc1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 12 Sep 2017 15:24:57 +0800 Subject: [PATCH 0956/2110] Release v3.7.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5ad2245c02..47b6be3faf 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.7.2-SNAPSHOT \ No newline at end of file +3.7.2 \ No newline at end of file From 25efb26106b6b4d06260984f5044314824b2e2ee Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 12 Sep 2017 15:24:57 +0800 Subject: [PATCH 0957/2110] Prepare next release v3.7.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 47b6be3faf..9a294da7b4 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.7.2 \ No newline at end of file +3.7.3-SNAPSHOT \ No newline at end of file From 9ac6893f1e955c994cd507df7d50a80f3cc176ee Mon Sep 17 00:00:00 2001 From: LYK Date: Tue, 12 Sep 2017 21:55:38 +0900 Subject: [PATCH 0958/2110] Converting examples to RxJava2 (#5141) * Add changelog * Move Pair into public API (#5081) * Convert RxJava1 to RxJava2 (#4992) * Apply rxjava2 to unitTestExample * Update Jenkinsfile to store junit result of unitTestExample. * Convert newsreaderExample to RxJava2. * RxJava2: Add support for changeset observables (#5089) * Update rxJavaExample and improve some formatting. * Fix unit testing. * Fix build.gradle. * Update build.gradle to fix lint errors. * Improve some formatting. * Improve formatting. * Update style of RxJavaExample. * Add missing colors.xml file. * Update styles and colors. * Rename some disposables. * PR feedback. * flatMap -> switchMap and improve formatting. * Improve formatting. * Converting newsreaserExample to lambda. * Improve formatting. * Improve formatting. * Apply RxJava2 to unit testing. * Improve formatting. * Apply Lambda to rxJavaExample. * Improve formatting. * PR feedback * Re-enable RxJava2 examples * CompositeDisposable.dispose() -> CompositeDisposable.clear() * PR feedback. --- Jenkinsfile | 2 +- examples/newsreaderExample/build.gradle | 22 ++- .../newsreader/NewsReaderApplication.java | 11 +- .../examples/newsreader/model/Model.java | 16 +- .../examples/newsreader/model/Repository.java | 28 ++-- .../model/network/NYTimesDataLoader.java | 80 ++++----- .../model/network/NYTimesService.java | 8 +- ...almListNYTimesMultimediumDeserializer.java | 6 +- .../ui/details/DetailsActivity.java | 2 +- .../ui/details/DetailsPresenter.java | 40 ++--- .../newsreader/ui/main/MainActivity.java | 16 +- .../newsreader/ui/main/MainPresenter.java | 48 ++---- examples/rxJavaExample/build.gradle | 20 ++- .../realm/examples/rxjava/MainActivity.java | 13 +- .../realm/examples/rxjava/MyApplication.java | 24 +-- .../rxjava/animation/AnimationActivity.java | 47 ++---- .../rxjava/gotchas/GotchasActivity.java | 152 ++++++------------ .../{GithubApi.java => GitHubApi.java} | 10 +- .../examples/rxjava/retrofit/GitHubUser.java | 3 + .../rxjava/retrofit/RetrofitExample.java | 126 +++++++-------- .../throttle/ThrottleSearchActivity.java | 76 ++++----- .../src/main/res/values/colors.xml | 6 + .../src/main/res/values/styles.xml | 7 +- examples/settings.gradle | 6 +- examples/unitTestExample/build.gradle | 9 +- .../unittesting/jUnit4ExampleTest.java | 2 +- .../examples/unittesting/ExampleActivity.java | 74 ++++----- .../repository/DogRepositoryImpl.java | 10 +- .../src/main/res/values/colors.xml | 6 + .../src/main/res/values/styles.xml | 7 +- 30 files changed, 356 insertions(+), 521 deletions(-) rename examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/{GithubApi.java => GitHubApi.java} (82%) create mode 100644 examples/rxJavaExample/src/main/res/values/colors.xml create mode 100644 examples/unitTestExample/src/main/res/values/colors.xml diff --git a/Jenkinsfile b/Jenkinsfile index b1613502e4..dc74785a4d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -51,7 +51,7 @@ try { } } finally { storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - // storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' FIXME when updating examples + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' step([$class: 'LintPublisher']) } } diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index a1b3fdb34c..c62c2d757f 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -13,6 +13,7 @@ android { versionCode 1 versionName "1.0" } + buildTypes { release { // FIXME: Fix the proguard with 3rd party libs @@ -20,32 +21,39 @@ android { signingConfig signingConfigs.debug } } + command { monkey.events 2000 } + lintOptions { disable 'InvalidPackage' } + packagingOptions { exclude 'META-INF/services/javax.annotation.processing.Processor' exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) //noinspection GradleDependency implementation 'com.android.support:appcompat-v7:26.0.1' //noinspection GradleDependency implementation 'com.android.support:design:26.0.1' - implementation 'io.reactivex:rxjava:1.1.0' - implementation 'io.reactivex:rxandroid:1.1.0' - implementation 'com.squareup.retrofit:retrofit:2.0.0-beta2' - implementation 'com.squareup.retrofit:converter-jackson:2.0.0-beta2' - implementation 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' implementation 'com.jakewharton.timber:timber:4.1.0' implementation 'com.jakewharton:butterknife:8.5.1' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' + implementation 'com.squareup.retrofit2:converter-jackson:2.3.0' + implementation 'com.squareup.retrofit2:retrofit:2.3.0' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' + implementation 'io.reactivex.rxjava2:rxjava:2.1.0' implementation 'me.zhanghai.android.materialprogressbar:library:1.1.4' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' } diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/NewsReaderApplication.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/NewsReaderApplication.java index 77674d9c2b..7bba7cdf16 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/NewsReaderApplication.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/NewsReaderApplication.java @@ -19,10 +19,9 @@ import android.app.Application; import android.content.Context; +import io.reactivex.plugins.RxJavaPlugins; import io.realm.Realm; import io.realm.RealmConfiguration; -import rx.plugins.RxJavaErrorHandler; -import rx.plugins.RxJavaPlugins; import timber.log.Timber; public abstract class NewsReaderApplication extends Application { @@ -35,13 +34,7 @@ public void onCreate() { context = this; initializeTimber(); - RxJavaPlugins.getInstance().registerErrorHandler(new RxJavaErrorHandler() { - @Override - public void handleError(Throwable e) { - super.handleError(e); - Timber.e(e.toString()); - } - }); + RxJavaPlugins.setErrorHandler(throwable -> Timber.e(throwable.toString())); // Configure default configuration for Realm Realm.init(this); diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Model.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Model.java index bfd914f6ad..401ba2ac0a 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Model.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Model.java @@ -22,10 +22,10 @@ import java.util.HashMap; import java.util.Map; +import io.reactivex.Flowable; +import io.reactivex.Observable; import io.realm.RealmResults; import io.realm.examples.newsreader.model.entity.NYTimesStory; -import rx.Observable; -import rx.functions.Func1; /** * Model class for handling the business rules of the app. @@ -78,7 +78,7 @@ private Model(Repository repository) { /** * Returns the news feed for the currently selected category. */ - public Observable> getSelectedNewsFeed() { + public Flowable> getSelectedNewsFeed() { return repository.loadNewsFeed(selectedSection, false); } @@ -106,20 +106,14 @@ public void markAsRead(@NonNull String storyId, boolean read) { /** * Returns the story with the given Id */ - public Observable getStory(@NonNull final String storyId) { + public Flowable getStory(@NonNull final String storyId) { // Repository is only responsible for loading the data // Any validation is done by the model // See http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/ if (TextUtils.isEmpty(storyId)) { throw new IllegalArgumentException("Invalid storyId: " + storyId); } - return repository.loadStory(storyId) - .filter(new Func1() { - @Override - public Boolean call(NYTimesStory story) { - return story.isValid(); - } - }); + return repository.loadStory(storyId).filter(story -> story.isValid()); } /** diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Repository.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Repository.java index ab86adcbac..dc75037989 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Repository.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Repository.java @@ -24,6 +24,9 @@ import java.util.Map; import java.util.concurrent.TimeUnit; +import io.reactivex.Flowable; +import io.reactivex.Observable; +import io.reactivex.subjects.BehaviorSubject; import io.realm.Realm; import io.realm.RealmResults; import io.realm.Sort; @@ -31,9 +34,6 @@ import io.realm.examples.newsreader.R; import io.realm.examples.newsreader.model.entity.NYTimesStory; import io.realm.examples.newsreader.model.network.NYTimesDataLoader; -import rx.Observable; -import rx.functions.Func1; -import rx.subjects.BehaviorSubject; import timber.log.Timber; /** @@ -51,7 +51,7 @@ public class Repository implements Closeable { private final NYTimesDataLoader dataLoader; private final String apiKey; private Map lastNetworkRequest = new HashMap<>(); - private BehaviorSubject networkLoading = BehaviorSubject.create(false); + private BehaviorSubject networkLoading = BehaviorSubject.createDefault(false); @UiThread public Repository() { @@ -67,14 +67,14 @@ public Repository() { */ @UiThread public Observable networkInUse() { - return networkLoading.asObservable(); + return networkLoading.hide(); } /** * Loads the news feed as well as all future updates. */ @UiThread - public Observable> loadNewsFeed(@NonNull String sectionKey, boolean forceReload) { + public Flowable> loadNewsFeed(@NonNull String sectionKey, boolean forceReload) { // Start loading data from the network if needed // It will put all data into Realm if (forceReload || timeSinceLastNetworkRequest(sectionKey) > MINIMUM_NETWORK_WAIT_SEC) { @@ -84,9 +84,10 @@ public Observable> loadNewsFeed(@NonNull String secti // Return the data in Realm. The query result will be automatically updated when the network requests // save data in Realm - return realm.where(NYTimesStory.class).equalTo(NYTimesStory.API_SECTION, sectionKey) + return realm.where(NYTimesStory.class) + .equalTo(NYTimesStory.API_SECTION, sectionKey) .findAllSortedAsync(NYTimesStory.PUBLISHED_DATE, Sort.DESCENDING) - .asObservable(); + .asFlowable(); } private long timeSinceLastNetworkRequest(@NonNull String sectionKey) { @@ -128,15 +129,10 @@ public void onError(Throwable throwable) { * Returns story details */ @UiThread - public Observable loadStory(final String storyId) { + public Flowable loadStory(final String storyId) { return realm.where(NYTimesStory.class).equalTo(NYTimesStory.URL, storyId).findFirstAsync() - .asObservable() - .filter(new Func1() { - @Override - public Boolean call(NYTimesStory story) { - return story.isLoaded(); - } - }); + .asFlowable() + .filter(story -> story.isLoaded()); } /** diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesDataLoader.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesDataLoader.java index c9be445097..7e3d6a4624 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesDataLoader.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesDataLoader.java @@ -24,15 +24,15 @@ import java.util.List; import java.util.Locale; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.functions.Consumer; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.BehaviorSubject; import io.realm.Realm; import io.realm.examples.newsreader.model.entity.NYTimesStory; -import retrofit.JacksonConverterFactory; -import retrofit.Retrofit; -import retrofit.RxJavaCallAdapterFactory; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; -import rx.schedulers.Schedulers; -import rx.subjects.BehaviorSubject; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; +import retrofit2.Retrofit; import timber.log.Timber; /** @@ -49,7 +49,7 @@ public class NYTimesDataLoader { public NYTimesDataLoader() { Retrofit retrofit = new Retrofit.Builder() - .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(JacksonConverterFactory.create()) .baseUrl("http://api.nytimes.com/") .build(); @@ -69,19 +69,13 @@ private void loadNextSection(@NonNull final String sectionKey) { nyTimesService.topStories(sectionKey, apiKey) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Action1>>() { - @Override - public void call(NYTimesResponse> response) { - Timber.d("Success - Data received: %s", sectionKey); - processAndAddData(realm, response.section, response.results); - networkInUse.onNext(false); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - networkInUse.onNext(false); - Timber.d("Failure: Data not loaded: %s - %s", sectionKey, throwable.toString()); - } + .subscribe(response -> { + Timber.d("Success - Data received: %s", sectionKey); + processAndAddData(realm, response.section, response.results); + networkInUse.onNext(false); + }, throwable -> { + networkInUse.onNext(false); + Timber.d("Failure: Data not loaded: %s - %s", sectionKey, throwable.toString()); }); } @@ -89,35 +83,27 @@ public void call(Throwable throwable) { private void processAndAddData(final Realm realm, final String sectionKey, final List stories) { if (stories.isEmpty()) return; - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (NYTimesStory story : stories) { - Date parsedPublishedDate = inputDateFormat.parse(story.getPublishedDate(), new ParsePosition(0)); - story.setSortTimeStamp(parsedPublishedDate.getTime()); - story.setPublishedDate(outputDateFormat.format(parsedPublishedDate)); + realm.executeTransactionAsync(r -> { + for (NYTimesStory story : stories) { + Date parsedPublishedDate = inputDateFormat.parse(story.getPublishedDate(), new ParsePosition(0)); + story.setSortTimeStamp(parsedPublishedDate.getTime()); + story.setPublishedDate(outputDateFormat.format(parsedPublishedDate)); - // Find existing story in Realm (if any) - // If it exists, we need to merge the local state with the remote, because the local state - // contains more info than is available on the server. - NYTimesStory persistedStory = realm.where(NYTimesStory.class).equalTo(NYTimesStory.URL, story.getUrl()).findFirst(); - if (persistedStory != null) { - // Only local state is the `read` boolean. - story.setRead(persistedStory.isRead()); - } + // Find existing story in Realm (if any) + // If it exists, we need to merge the local state with the remote, because the local state + // contains more info than is available on the server. + NYTimesStory persistedStory = r.where(NYTimesStory.class).equalTo(NYTimesStory.URL, story.getUrl()).findFirst(); + if (persistedStory != null) { + // Only local state is the `read` boolean. + story.setRead(persistedStory.isRead()); + } - // Only create or update the local story if needed - if (persistedStory == null || !persistedStory.getUpdatedDate().equals(story.getUpdatedDate())) { - story.setApiSection(sectionKey); - realm.copyToRealmOrUpdate(story); - } + // Only create or update the local story if needed + if (persistedStory == null || !persistedStory.getUpdatedDate().equals(story.getUpdatedDate())) { + story.setApiSection(sectionKey); + r.copyToRealmOrUpdate(story); } } - }, new Realm.Transaction.OnError() { - @Override - public void onError(Throwable throwable) { - Timber.e(throwable, "Could not save data"); - } - }); + }, throwable -> Timber.e(throwable, "Could not save data")); } } \ No newline at end of file diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesService.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesService.java index a234cfede0..41c799c578 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesService.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesService.java @@ -19,11 +19,11 @@ import java.util.List; +import io.reactivex.Observable; import io.realm.examples.newsreader.model.entity.NYTimesStory; -import retrofit.http.GET; -import retrofit.http.Path; -import retrofit.http.Query; -import rx.Observable; +import retrofit2.http.GET; +import retrofit2.http.Path; +import retrofit2.http.Query; /** * Retrofit interface for the New York Times WebService diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/RealmListNYTimesMultimediumDeserializer.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/RealmListNYTimesMultimediumDeserializer.java index 9db49e3b1f..e0626af31f 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/RealmListNYTimesMultimediumDeserializer.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/RealmListNYTimesMultimediumDeserializer.java @@ -32,17 +32,17 @@ public class RealmListNYTimesMultimediumDeserializer extends JsonDeserializer> { - ObjectMapper objectMapper; + private ObjectMapper objectMapper; public RealmListNYTimesMultimediumDeserializer() { objectMapper = new ObjectMapper(); } @Override - public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + public List deserialize(JsonParser parser, DeserializationContext context) throws IOException { RealmList list = new RealmList<>(); - TreeNode treeNode = jp.getCodec().readTree(jp); + TreeNode treeNode = parser.getCodec().readTree(parser); if (!(treeNode instanceof ArrayNode)) { return list; } diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsActivity.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsActivity.java index 5222153ecf..99e6d0b499 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsActivity.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsActivity.java @@ -58,7 +58,7 @@ protected void onCreate(Bundle savedInstanceState) { // Setup initial views setContentView(R.layout.activity_details); ButterKnife.bind(this); - toolbar = (Toolbar) findViewById(R.id.toolbar); + toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); loaderView.setVisibility(View.VISIBLE); diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsPresenter.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsPresenter.java index 0efde8359c..d5c36c1e7c 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsPresenter.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsPresenter.java @@ -18,14 +18,12 @@ import java.util.concurrent.TimeUnit; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; import io.realm.examples.newsreader.model.Model; -import io.realm.examples.newsreader.model.entity.NYTimesStory; import io.realm.examples.newsreader.ui.Presenter; -import rx.Observable; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; -import rx.subscriptions.CompositeSubscription; /** * Presenter class for controlling the Main Activity @@ -35,7 +33,7 @@ public class DetailsPresenter implements Presenter { private final DetailsActivity view; private final Model model; private final String storyId; - private CompositeSubscription subscriptions; + private CompositeDisposable compositeDisposable = new CompositeDisposable(); public DetailsPresenter(DetailsActivity detailsActivity, Model model, String storyId) { this.storyId = storyId; @@ -51,32 +49,24 @@ public void onCreate() { @Override public void onResume() { // Show story details - Subscription detailsSubscription = model.getStory(storyId) - .subscribe(new Action1() { - @Override - public void call(NYTimesStory story) { - view.hideLoader(); - view.showStory(story); - view.setRead(story.isRead()); - } + Disposable detailsDisposable = model.getStory(storyId) + .subscribe(story -> { + view.hideLoader(); + view.showStory(story); + view.setRead(story.isRead()); }); + compositeDisposable.add(detailsDisposable); // Mark story as read if screen is visible for 2 seconds - Subscription timerSubscription = Observable.timer(2, TimeUnit.SECONDS) + Disposable timberDisposable = Observable.timer(2, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Action1() { - @Override - public void call(Long aLong) { - model.markAsRead(storyId, true); - } - }); - - subscriptions = new CompositeSubscription(detailsSubscription, timerSubscription); + .subscribe(aLong -> model.markAsRead(storyId, true)); + compositeDisposable.add(timberDisposable); } @Override public void onPause() { - subscriptions.unsubscribe(); + compositeDisposable.clear(); } @Override diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainActivity.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainActivity.java index fd6030a7c0..41b2d41c10 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainActivity.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainActivity.java @@ -58,26 +58,16 @@ protected void onCreate(Bundle savedInstanceState) { // Setup initial views setContentView(R.layout.activity_main); ButterKnife.bind(this); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); //noinspection ConstantConditions getSupportActionBar().setDisplayShowTitleEnabled(false); adapter = null; - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - presenter.listItemSelected(position); - } - }); + listView.setOnItemClickListener((parent, view, position, id) -> presenter.listItemSelected(position)); listView.setEmptyView(getLayoutInflater().inflate(R.layout.common_emptylist, listView, false)); - refreshView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - presenter.refreshList(); - } - }); + refreshView.setOnRefreshListener(() -> presenter.refreshList()); progressBar.setVisibility(View.INVISIBLE); // After setup, notify presenter diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainPresenter.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainPresenter.java index e9d3a840f4..96cd119a4f 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainPresenter.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainPresenter.java @@ -21,17 +21,14 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; -import io.realm.RealmResults; +import io.reactivex.disposables.Disposable; import io.realm.examples.newsreader.model.Model; import io.realm.examples.newsreader.model.entity.NYTimesStory; import io.realm.examples.newsreader.ui.Presenter; import io.realm.examples.newsreader.ui.details.DetailsActivity; -import rx.Subscription; -import rx.functions.Action1; /** * Presenter class for controlling the Main Activity @@ -42,8 +39,8 @@ public class MainPresenter implements Presenter { private final Model model; private List storiesData; private Map sections; - private Subscription loaderSubscription; - private Subscription listDataSubscription; + private Disposable loaderDisposable; + private Disposable listDataDisposable; public MainPresenter(MainActivity mainActivity, Model model) { this.view = mainActivity; @@ -55,34 +52,24 @@ public void onCreate() { sections = model.getSections(); // Sort sections alphabetically, but always have Home at the top ArrayList sectionList = new ArrayList<>(sections.values()); - Collections.sort(sectionList, new Comparator() { - @Override - public int compare(String lhs, String rhs) { - if (lhs.equals("Home")) return -1; - if (rhs.equals("Home")) return 1; - return lhs.compareToIgnoreCase(rhs); - } + Collections.sort(sectionList, (lhs, rhs) -> { + if (lhs.equals("Home")) return -1; + if (rhs.equals("Home")) return 1; + return lhs.compareToIgnoreCase(rhs); }); view.configureToolbar(sectionList); } @Override public void onResume() { - loaderSubscription = model.isNetworkUsed() - .subscribe(new Action1() { - @Override - public void call(Boolean networkInUse) { - view.showNetworkLoading(networkInUse); - } - }); - + loaderDisposable = model.isNetworkUsed().subscribe(networkInUse -> view.showNetworkLoading(networkInUse)); sectionSelected(model.getCurrentSectionKey()); } @Override public void onPause() { - loaderSubscription.unsubscribe(); - listDataSubscription.unsubscribe(); + loaderDisposable.dispose(); + listDataDisposable.dispose(); } @Override @@ -111,16 +98,13 @@ public void titleSpinnerSectionSelected(@NonNull String sectionLabel) { private void sectionSelected(@NonNull String sectionKey) { model.selectSection(sectionKey); - if (listDataSubscription != null) { - listDataSubscription.unsubscribe(); + if (listDataDisposable != null) { + listDataDisposable.dispose(); } - listDataSubscription = model.getSelectedNewsFeed() - .subscribe(new Action1>() { - @Override - public void call(RealmResults stories) { - storiesData = stories; - view.showList(stories); - } + listDataDisposable = model.getSelectedNewsFeed() + .subscribe(stories -> { + storiesData = stories; + view.showList(stories); }); } } diff --git a/examples/rxJavaExample/build.gradle b/examples/rxJavaExample/build.gradle index 070174b3b4..2a0393b8ab 100644 --- a/examples/rxJavaExample/build.gradle +++ b/examples/rxJavaExample/build.gradle @@ -28,11 +28,23 @@ android { command { monkey.events 2000 } + + packagingOptions { + exclude 'META-INF/LICENSE' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { - implementation 'io.reactivex:rxandroid:1.1.0' - implementation 'io.reactivex:rxjava:1.1.0' - implementation 'com.jakewharton.rxbinding:rxbinding:0.3.0' - implementation 'com.squareup.retrofit:retrofit:1.9.0' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' + implementation 'io.reactivex.rxjava2:rxjava:2.1.0' + implementation 'com.android.support:appcompat-v7:26.0.1' + implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' + implementation 'com.squareup.retrofit2:converter-jackson:2.3.0' + implementation 'com.squareup.retrofit2:retrofit:2.3.0' } diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MainActivity.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MainActivity.java index 91bd8f5d2c..dfd5532375 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MainActivity.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MainActivity.java @@ -19,7 +19,7 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.view.View; +import android.support.v7.app.AppCompatActivity; import android.view.ViewGroup; import android.widget.Button; @@ -31,7 +31,7 @@ import io.realm.examples.rxjava.retrofit.RetrofitExample; import io.realm.examples.rxjava.throttle.ThrottleSearchActivity; -public class MainActivity extends Activity { +public class MainActivity extends AppCompatActivity { private ViewGroup container; private final TreeMap> buttons = new TreeMap>() {{ @@ -45,7 +45,7 @@ public class MainActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - container = (ViewGroup) findViewById(R.id.list); + container = findViewById(R.id.list); setupButtons(); } @@ -53,12 +53,7 @@ private void setupButtons() { for (final Map.Entry> entry : buttons.entrySet()) { Button button = new Button(this); button.setText(entry.getKey()); - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(entry.getValue()); - } - }); + button.setOnClickListener(view -> startActivity(entry.getValue())); container.addView(button); } } diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java index 9ac0f17b60..52ed89fdb1 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java @@ -28,7 +28,6 @@ public class MyApplication extends Application { - private static MyApplication context; private static final TreeMap testPersons = new TreeMap<>(); static { testPersons.put("Chris", null); @@ -39,12 +38,12 @@ public class MyApplication extends Application { testPersons.put("Donn", "donnfelker"); testPersons.put("Nabil", "nhachicha"); testPersons.put("Ron", null); + testPersons.put("Leonardo", "dalinaum"); } @Override public void onCreate() { super.onCreate(); - context = this; Realm.init(this); RealmConfiguration config = new RealmConfiguration.Builder().build(); Realm.deleteRealm(config); @@ -54,23 +53,16 @@ public void onCreate() { // Create test data private void createTestData() { - final Random r = new Random(42); + final Random random = new Random(42); Realm realm = Realm.getDefaultInstance(); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (Map.Entry entry : testPersons.entrySet()) { - Person p = realm.createObject(Person.class); - p.setName(entry.getKey()); - p.setGithubUserName(entry.getValue()); - p.setAge(r.nextInt(100)); - } + realm.executeTransaction(r -> { + for (Map.Entry entry : testPersons.entrySet()) { + Person p = r.createObject(Person.class); + p.setName(entry.getKey()); + p.setGithubUserName(entry.getValue()); + p.setAge(random.nextInt(100)); } }); realm.close(); } - - public static MyApplication getContext() { - return context; - } } diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/animation/AnimationActivity.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/animation/AnimationActivity.java index ebcb5e1a05..291c5b5fba 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/animation/AnimationActivity.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/animation/AnimationActivity.java @@ -16,35 +16,31 @@ package io.realm.examples.rxjava.animation; -import android.app.Activity; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import android.view.ViewGroup; import android.widget.TextView; import java.util.concurrent.TimeUnit; +import io.reactivex.Flowable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import io.realm.Realm; -import io.realm.RealmResults; import io.realm.examples.rxjava.R; import io.realm.examples.rxjava.model.Person; -import rx.Observable; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; -import rx.functions.Func1; -import rx.functions.Func2; -public class AnimationActivity extends Activity { +public class AnimationActivity extends AppCompatActivity { private Realm realm; - private Subscription subscription; + private Disposable disposable; private ViewGroup container; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_animations); - container = (ViewGroup) findViewById(R.id.list); + container = findViewById(R.id.list); realm = Realm.getDefaultInstance(); } @@ -55,34 +51,21 @@ protected void onResume() { // Load all persons and start inserting them with 1 sec. intervals. // All RealmObject access has to be done on the same thread `findAllAsync` was called on. // Warning: This example doesn't handle back pressure well. - subscription = realm.where(Person.class).findAllAsync().asObservable() - .flatMap(new Func1, Observable>() { - @Override - public Observable call(RealmResults persons) { - return Observable.from(persons); - } - }) - .zipWith(Observable.interval(1, TimeUnit.SECONDS), new Func2() { - @Override - public Person call(Person person, Long tick) { - return person; - } - }) + disposable = realm.where(Person.class).findAllAsync().asFlowable() + .flatMap(persons -> Flowable.fromIterable(persons)) + .zipWith(Flowable.interval(1, TimeUnit.SECONDS), (person, tick) -> person) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Action1() { - @Override - public void call(Person person) { - TextView personView = new TextView(AnimationActivity.this); - personView.setText(person.getName()); - container.addView(personView); - } + .subscribe(person -> { + TextView personView = new TextView(AnimationActivity.this); + personView.setText(person.getName()); + container.addView(personView); }); } @Override protected void onPause() { super.onPause(); - subscription.unsubscribe(); + disposable.dispose(); } @Override diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas/GotchasActivity.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas/GotchasActivity.java index fcfd43d85e..4a269e0dd2 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas/GotchasActivity.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas/GotchasActivity.java @@ -16,24 +16,21 @@ package io.realm.examples.rxjava.gotchas; -import android.app.Activity; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import android.view.ViewGroup; import android.widget.TextView; -import java.util.List; import java.util.Random; +import io.reactivex.Flowable; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; import io.realm.Realm; import io.realm.Sort; import io.realm.examples.rxjava.R; import io.realm.examples.rxjava.model.Person; -import rx.Observable; -import rx.Subscription; -import rx.functions.Action1; -import rx.functions.Func1; -import rx.schedulers.Schedulers; -import rx.subscriptions.CompositeSubscription; /** * This class shows some of the current obstacles when combining RxJava and Realm. 2 things are @@ -52,151 +49,95 @@ * - https://github.com/realm/realm-java/issues/1208 * - https://github.com/realm/realm-java/issues/931 */ -public class GotchasActivity extends Activity { +public class GotchasActivity extends AppCompatActivity { private Realm realm; - private Subscription subscription; + private CompositeDisposable compositeDisposable = new CompositeDisposable(); private ViewGroup container; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gotchas); - container = (ViewGroup) findViewById(R.id.list); + container = findViewById(R.id.list); realm = Realm.getDefaultInstance(); } @Override protected void onResume() { super.onResume(); - - Subscription distinctSubscription = testDistinct(); - Subscription bufferSubscription = testBuffer(); - Subscription subscribeOnSubscription = testSubscribeOn(); + testDistinct(); + testBuffer(); + testSubscribeOn(); // Trigger updates - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.where(Person.class).findAllSorted( "name", Sort.ASCENDING).get(0).setAge(new Random().nextInt(100)); - } - }); - - subscription = new CompositeSubscription( - distinctSubscription, - bufferSubscription, - subscribeOnSubscription - ); + realm.executeTransaction(r -> + r.where(Person.class).findAllSorted( "name", Sort.ASCENDING).get(0).setAge(new Random().nextInt(100))); } /** * Shows how to be careful with `subscribeOn()` */ - private Subscription testSubscribeOn() { - Subscription subscribeOn = realm.asObservable() - .map(new Func1() { - @Override - public Person call(Realm realm) { - return realm.where(Person.class).findAllSorted("name").get(0); - } - }) + private void testSubscribeOn() { + Disposable subscribeOnDisposable = realm.asFlowable() + .map(realm -> realm.where(Person.class).findAllSorted("name").get(0)) // The Realm was created on the UI thread. Accessing it on `Schedulers.io()` will crash. // Avoid using subscribeOn() and use Realms `findAllAsync*()` methods instead. .subscribeOn(Schedulers.io()) // - .subscribe(new Action1() { - @Override - public void call(Person person) { - // Do nothing - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - showStatus("subscribeOn: " + throwable.toString()); - } - }); + .subscribe( + person -> {}, // Do nothing + throwable -> showStatus("subscribeOn: " + throwable.toString()) + ); + compositeDisposable.add(subscribeOnDisposable); // Use Realms Async API instead - Subscription asyncSubscribeOn = realm.where(Person.class).findAllSortedAsync("name").get(0).asObservable() - .subscribe(new Action1() { - @Override - public void call(Person person) { - showStatus("subscribeOn/async: " + person.getName() + ":" + person.getAge()); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - showStatus("subscribeOn/async: " + throwable.toString()); - } - }); - - return new CompositeSubscription(subscribeOn, asyncSubscribeOn); + Disposable asyncSubscribeOnDisposable = realm.where(Person.class).findAllSortedAsync("name").get(0).asFlowable() + .subscribe( + person -> showStatus("subscribeOn/async: " + person.getName() + ":" + person.getAge()), + throwable -> showStatus("subscribeOn/async: " +throwable.toString()) + ); + compositeDisposable.add(asyncSubscribeOnDisposable); } /** * Shows how to be careful with `buffer()` */ - private Subscription testBuffer() { - Observable personObserver = realm.asObservable().map(new Func1() { - @Override - public Person call(Realm realm) { - return realm.where(Person.class).findAllSorted("name").get(0); - } - }); + private void testBuffer() { + Flowable personFlowable = + realm.asFlowable().map(realm -> realm.where(Person.class).findAllSorted("name").get(0)); // buffer() caches objects until the buffer is full. Due to Realms auto-update of all objects it means // that all objects in the cache will contain the same data. // Either avoid using buffer or copy data into an unmanaged object. - return personObserver + Disposable disposable = personFlowable .buffer(2) - .subscribe(new Action1>() { - @Override - public void call(List persons) { - showStatus("Buffer[0] : " + persons.get(0).getName() + ":" + persons.get(0).getAge()); - showStatus("Buffer[1] : " + persons.get(1).getName() + ":" + persons.get(1).getAge()); - } + .subscribe(people -> { + showStatus("Buffer[0] : " + people.get(0).getName() + ":" + people.get(0).getAge()); + showStatus("Buffer[1] : " + people.get(1).getName() + ":" + people.get(1).getAge()); }); + compositeDisposable.add(disposable); } /** * Shows how to to be careful when using `distinct()` */ - private Subscription testDistinct() { - Observable personObserver = realm.asObservable().map(new Func1() { - @Override - public Person call(Realm realm) { - return realm.where(Person.class).findAllSorted("name").get(0); - } - }); + private void testDistinct() { + Flowable personFlowable = + realm.asFlowable().map(realm -> realm.where(Person.class).findAllSorted("name").get(0)); // distinct() and distinctUntilChanged() uses standard equals with older objects stored in a HashMap. // Realm objects auto-update which means the objects stored will also auto-update. // This makes comparing against older objects impossible (even if the new object has changed) because the // cached object will also have changed. // Use a keySelector function to work around this. - Subscription distinctItemTest = personObserver + Disposable distinctDisposable = personFlowable .distinct() // Because old == new. This will only allow the first version of the "Chris" object to pass. - .subscribe(new Action1() { - @Override - public void call(Person p) { - showStatus("distinct(): " + p.getName() + ":" + p.getAge()); - } - }); + .subscribe(person -> showStatus("distinct(): " + person.getName() + ":" + person.getAge())); + compositeDisposable.add(distinctDisposable); - Subscription distinctKeySelectorItemTest = personObserver - .distinct(new Func1() { // Use a keySelector function instead - @Override - public Integer call(Person p) { - return p.getAge(); - } - }) - .subscribe(new Action1() { - @Override - public void call(Person p) { - showStatus("distinct(keySelector): " + p.getName() + ":" + p.getAge()); - } - }); - - - return new CompositeSubscription(distinctItemTest, distinctKeySelectorItemTest); + Disposable distinctKeySelectorDisposable = personFlowable + .distinct(person -> person.getAge()) + .subscribe(person -> showStatus("distinct(keySelector): " + person.getName() + ":" + person.getAge())); + compositeDisposable.add(distinctKeySelectorDisposable); } private void showStatus(String message) { @@ -208,7 +149,7 @@ private void showStatus(String message) { @Override protected void onPause() { super.onPause(); - subscription.unsubscribe(); + compositeDisposable.clear(); } @Override @@ -216,5 +157,4 @@ protected void onDestroy() { super.onDestroy(); realm.close(); } - } diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GithubApi.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GitHubApi.java similarity index 82% rename from examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GithubApi.java rename to examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GitHubApi.java index f28d8ab6f8..4302178386 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GithubApi.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GitHubApi.java @@ -16,17 +16,17 @@ package io.realm.examples.rxjava.retrofit; -import retrofit.http.GET; -import retrofit.http.Path; -import rx.Observable; +import io.reactivex.Flowable; +import retrofit2.http.GET; +import retrofit2.http.Path; /** * GitHub API definition */ -interface GithubApi { +interface GitHubApi { /** * See https://developer.github.com/v3/users/ */ @GET("/users/{user}") - Observable user(@Path("user") String user); + Flowable user(@Path("user") String user); } diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GitHubUser.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GitHubUser.java index a659b752c8..860ee27525 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GitHubUser.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/GitHubUser.java @@ -16,10 +16,13 @@ package io.realm.examples.rxjava.retrofit; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * Model class for GitHub users: https://developer.github.com/v3/users/#get-a-single-user */ @SuppressWarnings("unused") +@JsonIgnoreProperties(ignoreUnknown = true) class GitHubUser { public String name; public int public_repos; diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/RetrofitExample.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/RetrofitExample.java index 2412dfb88b..4f01dbfa11 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/RetrofitExample.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/RetrofitExample.java @@ -16,40 +16,41 @@ package io.realm.examples.rxjava.retrofit; -import android.app.Activity; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import android.view.ViewGroup; import android.widget.TextView; import java.util.Locale; +import io.reactivex.Flowable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; import io.realm.Realm; -import io.realm.RealmResults; import io.realm.examples.rxjava.R; import io.realm.examples.rxjava.model.Person; -import retrofit.RequestInterceptor; -import retrofit.RestAdapter; -import rx.Observable; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; -import rx.functions.Func1; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; import static android.text.TextUtils.isEmpty; import static java.lang.String.format; -public class RetrofitExample extends Activity { +public class RetrofitExample extends AppCompatActivity { private Realm realm; - private Subscription subscription; + private Disposable disposable; private ViewGroup container; - private GithubApi api; + private GitHubApi api; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_network); - container = (ViewGroup) findViewById(R.id.list); + container = findViewById(R.id.list); realm = Realm.getDefaultInstance(); api = createGitHubApi(); } @@ -59,57 +60,33 @@ protected void onResume() { super.onResume(); // Load all persons and merge them with their latest stats from GitHub (if they have any) - subscription = realm.where(Person.class).isNotNull("githubUserName").findAllSortedAsync("name").asObservable() - .filter(new Func1, Boolean>() { - @Override - public Boolean call(RealmResults persons) { - // We only want the list once it is loaded. - return persons.isLoaded(); - } - }) - .flatMap(new Func1, Observable>() { - @Override - public Observable call(RealmResults persons) { - // Emit each person individually - return Observable.from(persons); - } - }) - .flatMap(new Func1>() { - @Override - public Observable call(Person person) { - // get GitHub statistics. Retrofit automatically does this on a separate thread. - return api.user(person.getGithubUserName()); - } - }) - .map(new Func1() { - @Override - public UserViewModel call(GitHubUser gitHubUser) { - // Map Network model to our View model - return new UserViewModel(gitHubUser.name, gitHubUser.public_repos, gitHubUser.public_gists); - } - }) - .observeOn(AndroidSchedulers.mainThread()) // Retrofit put us on a worker thread. Move back to UI - .subscribe(new Action1() { - @Override - public void call(UserViewModel user) { - // Print user info. - TextView userView = new TextView(RetrofitExample.this); - userView.setText(String.format(Locale.US, "%s : %d/%d", - user.getUsername(), user.getPublicRepos(), user.getPublicGists())); - container.addView(userView); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - throwable.printStackTrace(); - } - }); + disposable = realm.where(Person.class).isNotNull("githubUserName").findAllSortedAsync("name").asFlowable() + // We only want the list once it is loaded. + .filter(people -> people.isLoaded()) + .switchMap(people -> Flowable.fromIterable(people)) + + // get GitHub statistics. + .flatMap(person -> api.user(person.getGithubUserName())) + + // Map Network model to our View model + .map(gitHubUser -> new UserViewModel(gitHubUser.name, gitHubUser.public_repos, gitHubUser.public_gists)) + + // Retrofit put us on a worker thread. Move back to UI + .observeOn(AndroidSchedulers.mainThread()) + + .subscribe(user -> { + // Print user info. + TextView userView = new TextView(RetrofitExample.this); + userView.setText( + String.format(Locale.US, "%s : %d/%d", user.getUsername(), user.getPublicRepos(), user.getPublicGists())); + container.addView(userView); + }, throwable -> throwable.printStackTrace()); } @Override protected void onPause() { super.onPause(); - subscription.unsubscribe(); + disposable.dispose(); } @Override @@ -118,20 +95,31 @@ protected void onDestroy() { realm.close(); } - private GithubApi createGitHubApi() { + private GitHubApi createGitHubApi() { - RestAdapter.Builder builder = new RestAdapter.Builder().setEndpoint("https://api.github.com/"); + Retrofit.Builder builder = new Retrofit.Builder() + .baseUrl("https://api.github.com/") + .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) + .addConverterFactory(JacksonConverterFactory.create()); - final String githubToken = ""; // Set GitHub OAuth token to avoid throttling if example is used a lot - if (!isEmpty(githubToken)) { - builder.setRequestInterceptor(new RequestInterceptor() { - @Override - public void intercept(RequestFacade request) { - request.addHeader("Authorization", format("token %s", githubToken)); - } + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); + + final String gitHubToken = ""; // Set GitHub OAuth token to avoid throttling if example is used a lot + + if (!isEmpty(gitHubToken)) { + httpClientBuilder.addInterceptor(chain -> { + Request originalRequest = chain.request(); + Request modifiedRequest = originalRequest + .newBuilder() + .header("Authorization", format("token %s", gitHubToken)) + .method(originalRequest.method(), originalRequest.body()) + .build(); + return chain.proceed(modifiedRequest); }); } - return builder.build().create(GithubApi.class); + OkHttpClient httpClient = httpClientBuilder.build(); + builder.client(httpClient); + return builder.build().create(GitHubApi.class); } } diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java index 6a5c27991b..5b4bdd1df8 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java @@ -16,31 +16,27 @@ package io.realm.examples.rxjava.throttle; -import android.app.Activity; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import android.view.ViewGroup; import android.widget.EditText; import android.widget.TextView; -import com.jakewharton.rxbinding.widget.RxTextView; -import com.jakewharton.rxbinding.widget.TextViewTextChangeEvent; +import com.jakewharton.rxbinding2.widget.RxTextView; import java.util.concurrent.TimeUnit; +import io.reactivex.BackpressureStrategy; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import io.realm.Realm; -import io.realm.RealmResults; import io.realm.examples.rxjava.R; import io.realm.examples.rxjava.model.Person; -import rx.Observable; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; -import rx.functions.Func1; -public class ThrottleSearchActivity extends Activity { +public class ThrottleSearchActivity extends AppCompatActivity { private Realm realm; - private Subscription subscription; + private Disposable disposable; private EditText searchInputView; private ViewGroup searchResultsView; @@ -48,8 +44,8 @@ public class ThrottleSearchActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_throttlesearch); - searchInputView = (EditText) findViewById(R.id.search); - searchResultsView = (ViewGroup) findViewById(R.id.search_results); + searchInputView = findViewById(R.id.search); + searchResultsView = findViewById(R.id.search_results); realm = Realm.getDefaultInstance(); } @@ -58,49 +54,35 @@ protected void onResume() { super.onResume(); // Listen to key presses and only start search after user paused to avoid excessive redrawing on the screen. - subscription = RxTextView.textChangeEvents(searchInputView) + disposable = RxTextView.textChangeEvents(searchInputView) .debounce(200, TimeUnit.MILLISECONDS) // default Scheduler is Schedulers.computation() .observeOn(AndroidSchedulers.mainThread()) // Needed to access Realm data - .flatMap(new Func1>>() { - @Override - public Observable> call(TextViewTextChangeEvent event) { - // Use Async API to move Realm queries off the main thread. - // Realm currently doesn't support the standard Schedulers. - return realm.where(Person.class) - .beginsWith("name", event.text().toString()) - .findAllSortedAsync("name").asObservable(); - } - }) - .filter(new Func1, Boolean>() { - @Override - public Boolean call(RealmResults persons) { - // Only continue once data is actually loaded - // RealmObservables will emit the unloaded (empty) list as its first item - return persons.isLoaded(); - } + .toFlowable(BackpressureStrategy.BUFFER) + .switchMap(textChangeEvent -> { + // Use Async API to move Realm queries off the main thread. + // Realm currently doesn't support the standard Schedulers. + return realm.where(Person.class) + .beginsWith("name", textChangeEvent.text().toString()) + .findAllSortedAsync("name") + .asFlowable(); }) - .subscribe(new Action1>() { - @Override - public void call(RealmResults persons) { - searchResultsView.removeAllViews(); - for (Person person : persons) { - TextView view = new TextView(ThrottleSearchActivity.this); - view.setText(person.getName()); - searchResultsView.addView(view); - } - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - throwable.printStackTrace(); + // Only continue once data is actually loaded + // RealmObservables will emit the unloaded (empty) list as its first item + .filter(people -> people.isLoaded()) + .subscribe(people -> { + searchResultsView.removeAllViews(); + for (Person person : people) { + TextView view = new TextView(ThrottleSearchActivity.this); + view.setText(person.getName()); + searchResultsView.addView(view); } - }); + }, throwable -> throwable.printStackTrace()); } @Override protected void onPause() { super.onPause(); - subscription.unsubscribe(); + disposable.dispose(); } @Override diff --git a/examples/rxJavaExample/src/main/res/values/colors.xml b/examples/rxJavaExample/src/main/res/values/colors.xml new file mode 100644 index 0000000000..cb09b5ec1d --- /dev/null +++ b/examples/rxJavaExample/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + \ No newline at end of file diff --git a/examples/rxJavaExample/src/main/res/values/styles.xml b/examples/rxJavaExample/src/main/res/values/styles.xml index ff6c9d2c0f..b4390a2166 100644 --- a/examples/rxJavaExample/src/main/res/values/styles.xml +++ b/examples/rxJavaExample/src/main/res/values/styles.xml @@ -1,8 +1,9 @@ - - diff --git a/examples/settings.gradle b/examples/settings.gradle index 361ed3b8f1..0f9f5242bd 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -9,9 +9,9 @@ include 'moduleExample:app' include 'moduleExample:library' include 'realmModuleExample' include 'threadExample' -//include 'unitTestExample' FIXME: Upgrade to RxJava2 -//include 'newsreaderExample' FIXME: Upgrade to RxJava2 -//include 'rxJavaExample' FIXME: Upgrade to RxJava2 +include 'unitTestExample' +include 'newsreaderExample' +include 'rxJavaExample' include 'objectServerExample' rootProject.name = 'realm-examples' diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index 553d5fd771..965507acd9 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -30,11 +30,18 @@ android { command { monkey.events 2000 } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { - testImplementation 'io.reactivex:rxjava:1.1.0' + implementation 'com.android.support:appcompat-v7:26.0.1' + + testImplementation 'io.reactivex.rxjava2:rxjava:2.1.0' // Testing testImplementation 'junit:junit:4.12' diff --git a/examples/unitTestExample/src/androidTest/java/io/realm/examples/unittesting/jUnit4ExampleTest.java b/examples/unitTestExample/src/androidTest/java/io/realm/examples/unittesting/jUnit4ExampleTest.java index f605f1e1ea..12f8d2e44e 100644 --- a/examples/unitTestExample/src/androidTest/java/io/realm/examples/unittesting/jUnit4ExampleTest.java +++ b/examples/unitTestExample/src/androidTest/java/io/realm/examples/unittesting/jUnit4ExampleTest.java @@ -33,7 +33,7 @@ public class jUnit4ExampleTest { @Rule - public ActivityTestRule mActivityRule = new ActivityTestRule(ExampleActivity.class); + public ActivityTestRule mActivityRule = new ActivityTestRule<>(ExampleActivity.class); @Test public void testShouldBeAbleToLaunchActivityAndSeeRealmResults() { diff --git a/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java b/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java index 479037fabb..4b0f94e752 100644 --- a/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java +++ b/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java @@ -16,21 +16,18 @@ package io.realm.examples.unittesting; -import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import android.util.Log; -import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import io.realm.Realm; -import io.realm.RealmConfiguration; import io.realm.RealmResults; import io.realm.examples.unittesting.model.Person; - -public class ExampleActivity extends Activity { +public class ExampleActivity extends AppCompatActivity { public static final String TAG = ExampleActivity.class.getName(); private LinearLayout rootLayout = null; @@ -42,7 +39,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Realm.init(getApplicationContext()); setContentView(R.layout.activity_example); - rootLayout = ((LinearLayout) findViewById(R.id.container)); + rootLayout = findViewById(R.id.container); rootLayout.removeAllViews(); // Open the default Realm for the UI thread. @@ -71,24 +68,17 @@ protected void onPostExecute(String result) { foo.execute(); - findViewById(R.id.clean_up).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - v.setEnabled(false); - cleanUp(); - v.setEnabled(true); - } + findViewById(R.id.clean_up).setOnClickListener(view -> { + view.setEnabled(false); + Log.d("TAG", "clean up"); + cleanUp(); + view.setEnabled(true); }); } private void cleanUp() { // Delete all persons - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.delete(Person.class); - } - }); + realm.executeTransaction(r -> r.delete(Person.class)); } @Override @@ -108,15 +98,12 @@ private void basicCRUD(Realm realm) { showStatus("Perform basic Create/Read/Update/Delete (CRUD) operations..."); // All writes must be wrapped in a transaction to facilitate safe multi threading - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - // Add a person - Person person = realm.createObject(Person.class); - person.setId(1); - person.setName("John Young"); - person.setAge(14); - } + realm.executeTransaction(r -> { + // Add a person + Person person = r.createObject(Person.class); + person.setId(1); + person.setName("John Young"); + person.setAge(14); }); // Find the first person (no query conditions) and read a field @@ -124,28 +111,22 @@ public void execute(Realm realm) { showStatus(person.getName() + ":" + person.getAge()); // Update person in a transaction - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - person.setName("John Senior"); - person.setAge(89); - } + realm.executeTransaction(r -> { + person.setName("John Senior"); + person.setAge(89); }); showStatus(person.getName() + " got older: " + person.getAge()); // Add two more people - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - Person jane = realm.createObject(Person.class); - jane.setName("Jane"); - jane.setAge(27); - - Person doug = realm.createObject(Person.class); - doug.setName("Robert"); - doug.setAge(42); - } + realm.executeTransaction(r -> { + Person jane = r.createObject(Person.class); + jane.setName("Jane"); + jane.setAge(27); + + Person doug = r.createObject(Person.class); + doug.setName("Robert"); + doug.setAge(42); }); RealmResults people = realm.where(Person.class).findAll(); @@ -164,7 +145,8 @@ private String complexQuery() { // Find all persons where age between 1 and 99 and name begins with "J". RealmResults results = realm.where(Person.class) .between("age", 1, 99) // Notice implicit "and" operation - .beginsWith("name", "J").findAll(); + .beginsWith("name", "J") + .findAll(); status += "\nNumber of people aged between 1 and 99 who's name start with 'J': " + results.size(); realm.close(); diff --git a/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/repository/DogRepositoryImpl.java b/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/repository/DogRepositoryImpl.java index 60ff3b2eeb..b421c16413 100644 --- a/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/repository/DogRepositoryImpl.java +++ b/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/repository/DogRepositoryImpl.java @@ -19,17 +19,13 @@ import io.realm.Realm; import io.realm.examples.unittesting.model.Dog; - public class DogRepositoryImpl implements DogRepository { @Override public void createDog(final String name) { Realm realm = Realm.getDefaultInstance(); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - Dog dog = realm.createObject(Dog.class); - dog.setName(name); - } + realm.executeTransaction(r -> { + Dog dog = r.createObject(Dog.class); + dog.setName(name); }); realm.close(); } diff --git a/examples/unitTestExample/src/main/res/values/colors.xml b/examples/unitTestExample/src/main/res/values/colors.xml new file mode 100644 index 0000000000..cb09b5ec1d --- /dev/null +++ b/examples/unitTestExample/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + \ No newline at end of file diff --git a/examples/unitTestExample/src/main/res/values/styles.xml b/examples/unitTestExample/src/main/res/values/styles.xml index ff6c9d2c0f..b4390a2166 100644 --- a/examples/unitTestExample/src/main/res/values/styles.xml +++ b/examples/unitTestExample/src/main/res/values/styles.xml @@ -1,8 +1,9 @@ - - From 86547441cdc8e6fe77a364692122140ecaaf9a4d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 15 Sep 2017 16:32:15 +0800 Subject: [PATCH 0959/2110] Enable debug core by default --- realm/realm-library/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 7ffe3588bb..5115e05f78 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -37,7 +37,7 @@ ext.coreDir = file(project.coreSourcePath ? ext.ccachePath = project.findProperty('ccachePath') ?: System.getenv('NDK_CCACHE') ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE') // Set to true to enable linking with debug core. -ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProperty('enableDebugCore') : false +ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProperty('enableDebugCore') : true android { compileSdkVersion 26 From 9dbc3a0a1c9d65a5d3f2cb8f41ae7cb19c8de6b0 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 15 Sep 2017 18:44:33 +0800 Subject: [PATCH 0960/2110] Remove deprecated RealmResults.distinct APIs And populate less data for RealmQuery distinct tests to make the CI much faster. --- CHANGELOG.md | 4 + .../java/io/realm/RealmQueryTests.java | 70 +-- .../java/io/realm/RealmResultsTests.java | 587 ------------------ .../src/main/java/io/realm/RealmResults.java | 29 - 4 files changed, 39 insertions(+), 651 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c44194c2a1..244b15dbad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 4.0.0-BETA3 (YYYY-MM-DD) +### Breaking Changes + +* `RealmResults.distinct()`/`RealmResults.distinctAsync()` have been removed. Use `RealmQuery.distinct()`/`RealmQuery.distinctAsync()` instead. + ### Internal * Upgraded to Realm Sync 2.0.0-rc16. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 353d58886e..0c6f6523ae 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -3034,8 +3034,8 @@ private void populateForDistinctInvalidTypesLinked(Realm realm) { @Test public void distinct() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 + final long numberOfBlocks = 3; + final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL); @@ -3048,8 +3048,8 @@ public void distinct() { @Test public void distinct_withNullValues() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); for (String field : new String[]{AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) { @@ -3060,8 +3060,8 @@ public void distinct_withNullValues() { @Test public void distinct_notIndexedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) @@ -3076,8 +3076,8 @@ public void distinct_notIndexedFields() { @Test public void distinct_doesNotExist() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 + final long numberOfBlocks = 3; + final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { @@ -3101,8 +3101,8 @@ public void distinct_invalidTypes() { @Test public void distinct_indexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); for (String field : AnnotationIndexTypes.INDEX_FIELDS) { @@ -3116,8 +3116,8 @@ public void distinct_indexedLinkedFields() { @Test public void distinct_notIndexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { @@ -3144,8 +3144,8 @@ public void distinct_invalidTypesLinkedFields() { public void distinctAsync() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); final Realm realm = looperThread.getRealm(); - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 + final long numberOfBlocks = 3; + final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL); @@ -3220,8 +3220,8 @@ public void onChange(RealmResults object) { public void distinctAsync_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); final Realm realm = looperThread.getRealm(); - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // must be greater than 1 + final long numberOfBlocks = 3; + final long numberOfObjects = 3; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) @@ -3261,8 +3261,8 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread public void distinctAsync_doesNotExist() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { @@ -3289,8 +3289,8 @@ public void distinctAsync_invalidTypes() { @Test @RunTestInLooperThread public void distinctAsync_indexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); for (String field : AnnotationIndexTypes.INDEX_FIELDS) { @@ -3317,8 +3317,8 @@ public void distinctAsync_notIndexedLinkedFields() { @Test public void distinctMultiArgs() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 + final long numberOfBlocks = 3; + final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmQuery query = realm.where(AnnotationIndexTypes.class); @@ -3328,7 +3328,7 @@ public void distinctMultiArgs() { @Test public void distinctMultiArgs_switchedFieldsOrder() { - final long numberOfBlocks = 25; + final long numberOfBlocks = 3; TestHelper.populateForDistinctFieldsOrder(realm, numberOfBlocks); // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples. @@ -3342,8 +3342,8 @@ public void distinctMultiArgs_switchedFieldsOrder() { @Test public void distinctMultiArgs_emptyField() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmQuery query = realm.where(AnnotationIndexTypes.class); @@ -3396,8 +3396,8 @@ public void distinctMultiArgs_emptyField() { @Test public void distinctMultiArgs_withNullValues() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); RealmQuery query = realm.where(AnnotationIndexTypes.class); @@ -3407,8 +3407,8 @@ public void distinctMultiArgs_withNullValues() { @Test public void distinctMultiArgs_notIndexedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmQuery query = realm.where(AnnotationIndexTypes.class); @@ -3420,8 +3420,8 @@ public void distinctMultiArgs_notIndexedFields() { @Test public void distinctMultiArgs_doesNotExistField() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmQuery query = realm.where(AnnotationIndexTypes.class); @@ -3444,8 +3444,8 @@ public void distinctMultiArgs_invalidTypesFields() { @Test public void distinctMultiArgs_indexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); RealmQuery query = realm.where(AnnotationIndexTypes.class); @@ -3457,8 +3457,8 @@ public void distinctMultiArgs_indexedLinkedFields() { @Test public void distinctMultiArgs_notIndexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; + final long numberOfBlocks = 3; + final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); RealmQuery query = realm.where(AnnotationIndexTypes.class); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index b4ead0c1bf..d087c59f40 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -34,9 +34,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; -import io.realm.entities.AnnotationIndexTypes; import io.realm.entities.DefaultValueOfField; import io.realm.entities.Dog; import io.realm.entities.NonLatinFieldNames; @@ -49,7 +47,6 @@ import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -185,166 +182,6 @@ public void verifyArmComparisons() { assertEquals(10, realm.where(AllTypes.class).lessThan(AllTypes.FIELD_LONG, 0).findAll().size()); } - // RealmResults.distinct(): requires indexing, and type = boolean, integer, date, string. - private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOfObjects, boolean withNull) { - realm.beginTransaction(); - for (int i = 0; i < numberOfObjects * numberOfBlocks; i++) { - for (int j = 0; j < numberOfBlocks; j++) { - AnnotationIndexTypes obj = realm.createObject(AnnotationIndexTypes.class); - obj.setIndexBoolean(j % 2 == 0); - obj.setIndexLong(j); - obj.setIndexDate(withNull ? null : new Date(1000 * (long) j)); - obj.setIndexString(withNull ? null : "Test " + j); - obj.setNotIndexBoolean(j % 2 == 0); - obj.setNotIndexLong(j); - obj.setNotIndexDate(withNull ? null : new Date(1000 * (long) j)); - obj.setNotIndexString(withNull ? null : "Test " + j); - } - } - realm.commitTransaction(); - } - - private void populateForDistinctInvalidTypesLinked(Realm realm) { - realm.beginTransaction(); - AllJavaTypes notEmpty = new AllJavaTypes(); - notEmpty.setFieldBinary(new byte[]{1, 2, 3}); - notEmpty.setFieldObject(notEmpty); - notEmpty.setFieldList(new RealmList(notEmpty)); - realm.copyToRealm(notEmpty); - realm.commitTransaction(); - } - - @Test - public void distinct() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll().distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL); - assertEquals(2, distinctBool.size()); - for (String field : new String[]{AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).findAll().distinct(field); - assertEquals(field, numberOfBlocks, distinct.size()); - } - } - - @Test - @SuppressWarnings("ReferenceEquality") - public void distinct_restrictedByPreviousDistinct() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - // All objects - RealmResults allResults = realm.where(AnnotationIndexTypes.class).findAll(); - assertEquals("All Objects Count", numberOfBlocks * numberOfBlocks * numberOfObjects, allResults.size()); - // Distinctive dates - RealmResults distinctDates = allResults.distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); - assertEquals("Distinctive Dates", numberOfBlocks, distinctDates.size()); - // Distinctive Booleans - RealmResults distinctBooleans = distinctDates.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL); - assertEquals("Distinctive Booleans", 2, distinctBooleans.size()); - // distinct results are not the same object - assertTrue(allResults != distinctDates); - assertTrue(allResults != distinctBooleans); - } - - @Test - public void distinct_withNullValues() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - - for (String field : new String[]{AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).findAll().distinct(field); - assertEquals(field, 1, distinct.size()); - } - } - - @Test - public void distinct_notIndexedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) - .findAll().distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL); - assertEquals(2, distinctBool.size()); - for (String field : new String[]{AnnotationIndexTypes.FIELD_NOT_INDEX_LONG, - AnnotationIndexTypes.FIELD_NOT_INDEX_DATE, AnnotationIndexTypes.FIELD_NOT_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).findAll() - .distinct(field); - assertEquals(field, numberOfBlocks, distinct.size()); - } - } - - @Test - public void distinct_noneExistingField() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - try { - realm.where(AnnotationIndexTypes.class).findAll().distinct("doesNotExist"); - fail(); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void distinct_invalidTypes() { - populateTestRealm(); - - for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { - try { - realm.where(AllTypes.class).findAll().distinct(field); - fail(field); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void distinct_indexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - - for (String field : AnnotationIndexTypes.INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).findAll().distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field); - fail("Unsupported Index" + field + " linked field"); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void distinct_notIndexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - - for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).findAll().distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field); - fail("Unsupported notIndex" + field + " linked field"); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void distinct_invalidTypesLinkedFields() { - populateForDistinctInvalidTypesLinked(realm); - - try { - realm.where(AllJavaTypes.class).findAll().distinct(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); - fail("Unsupported columnBinary linked field"); - } catch (IllegalArgumentException ignored) { - } - } - @Test @RunTestInLooperThread public void changeListener_syncIfNeeded_updatedFromOtherThread() { @@ -417,430 +254,6 @@ private void populateTestRealm(Realm testRealm, int objects) { testRealm.commitTransaction(); } - @Test - @RunTestInLooperThread - public void distinctAsync() throws Throwable { - final AtomicInteger changeListenerCalled = new AtomicInteger(4); - final Realm realm = looperThread.getRealm(); - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_LONG); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); - - assertFalse(distinctBool.isLoaded()); - assertTrue(distinctBool.isValid()); - assertTrue(distinctBool.isEmpty()); - - assertFalse(distinctLong.isLoaded()); - assertTrue(distinctLong.isValid()); - assertTrue(distinctLong.isEmpty()); - - assertFalse(distinctDate.isLoaded()); - assertTrue(distinctDate.isValid()); - assertTrue(distinctDate.isEmpty()); - - assertFalse(distinctString.isLoaded()); - assertTrue(distinctString.isValid()); - assertTrue(distinctString.isEmpty()); - - final Runnable endTest = new Runnable() { - @Override - public void run() { - if (changeListenerCalled.decrementAndGet() == 0) { - looperThread.testComplete(); - } - } - }; - - looperThread.keepStrongReference(distinctBool); - looperThread.keepStrongReference(distinctLong); - looperThread.keepStrongReference(distinctDate); - looperThread.keepStrongReference(distinctString); - distinctBool.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(2, distinctBool.size()); - endTest.run(); - } - }); - - distinctLong.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(numberOfBlocks, distinctLong.size()); - endTest.run(); - } - }); - - distinctDate.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(numberOfBlocks, distinctDate.size()); - endTest.run(); - } - }); - - distinctString.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(numberOfBlocks, distinctString.size()); - endTest.run(); - } - }); - } - - @Test - @RunTestInLooperThread - public void distinctAsync_withNullValues() throws Throwable { - final AtomicInteger changeListenerCalled = new AtomicInteger(2); - final Realm realm = looperThread.getRealm(); - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); - - assertFalse(distinctDate.isLoaded()); - assertTrue(distinctDate.isValid()); - assertTrue(distinctDate.isEmpty()); - - assertFalse(distinctString.isLoaded()); - assertTrue(distinctString.isValid()); - assertTrue(distinctString.isEmpty()); - - final Runnable endTest = new Runnable() { - @Override - public void run() { - if (changeListenerCalled.decrementAndGet() == 0) { - looperThread.testComplete(); - } - } - }; - - looperThread.keepStrongReference(distinctDate); - looperThread.keepStrongReference(distinctString); - distinctDate.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals("distinctDate", 1, distinctDate.size()); - endTest.run(); - } - }); - - distinctString.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals("distinctString", 1, distinctString.size()); - endTest.run(); - } - }); - } - - @Test - @RunTestInLooperThread - public void distinctAsync_notIndexedFields() { - final AtomicInteger changeListenerCalled = new AtomicInteger(4); - Realm realm = looperThread.getRealm(); - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).findAll() - .distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).findAll() - .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_LONG); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).findAll() - .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).findAll() - .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_STRING); - - assertFalse(distinctBool.isLoaded()); - assertTrue(distinctBool.isValid()); - assertTrue(distinctBool.isEmpty()); - - assertFalse(distinctLong.isLoaded()); - assertTrue(distinctLong.isValid()); - assertTrue(distinctLong.isEmpty()); - - assertFalse(distinctDate.isLoaded()); - assertTrue(distinctDate.isValid()); - assertTrue(distinctDate.isEmpty()); - - assertFalse(distinctString.isLoaded()); - assertTrue(distinctString.isValid()); - assertTrue(distinctString.isEmpty()); - - final Runnable endTest = new Runnable() { - @Override - public void run() { - if (changeListenerCalled.decrementAndGet() == 0) { - looperThread.testComplete(); - } - } - }; - - looperThread.keepStrongReference(distinctBool); - looperThread.keepStrongReference(distinctLong); - looperThread.keepStrongReference(distinctDate); - looperThread.keepStrongReference(distinctString); - distinctBool.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(2, distinctBool.size()); - endTest.run(); - } - }); - - distinctLong.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(numberOfBlocks, distinctLong.size()); - endTest.run(); - } - }); - - distinctDate.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(numberOfBlocks, distinctDate.size()); - endTest.run(); - } - }); - - distinctString.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults object) { - assertEquals(numberOfBlocks, distinctString.size()); - endTest.run(); - } - }); - } - - @Test - @RunTestInLooperThread - public void distinctAsync_doesNotExist() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - try { - realm.where(AnnotationIndexTypes.class).findAll().distinctAsync("doesNotExist"); - } catch (IllegalArgumentException ignored) { - } - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - public void distinctAsync_invalidTypes() { - populateTestRealm(realm, TEST_DATA_SIZE); - - for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { - try { - realm.where(AllTypes.class).findAll().distinctAsync(field); - } catch (IllegalArgumentException ignored) { - } - } - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - public void distinctAsync_indexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - for (String field : AnnotationIndexTypes.INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).findAll().distinctAsync(AnnotationIndexTypes.FIELD_OBJECT + "." + field); - fail("Unsupported " + field + " linked field"); - } catch (IllegalArgumentException ignored) { - } - } - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - public void distinctAsync_notIndexedLinkedFields() { - populateForDistinctInvalidTypesLinked(realm); - - try { - realm.where(AllJavaTypes.class).findAll().distinctAsync(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); - } catch (IllegalArgumentException ignored) { - } - looperThread.testComplete(); - } - - @Test - public void distinctMultiArgs() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - RealmResults distinctMulti = results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.INDEX_FIELDS); - assertEquals(numberOfBlocks, distinctMulti.size()); - } - - @Test - public void distinctMultiArgs_switchedFieldsOrder() { - final long numberOfBlocks = 25; - TestHelper.populateForDistinctFieldsOrder(realm, numberOfBlocks); - - // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples. - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - RealmResults distinctStringLong = results.distinct(AnnotationIndexTypes.FIELD_INDEX_STRING, AnnotationIndexTypes.FIELD_INDEX_LONG); - RealmResults distinctLongString = results.distinct(AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_STRING); - assertEquals(4, distinctStringLong.size()); - assertEquals(4, distinctLongString.size()); - assertEquals(distinctStringLong.size(), distinctLongString.size()); - } - - @Test - public void distinctMultiArgs_emptyField() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - // An empty string field in the middle. - try { - results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, "", AnnotationIndexTypes.FIELD_INDEX_INT); - } catch (IllegalArgumentException ignored) { - } - // An empty string field at the end. - try { - results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, ""); - } catch (IllegalArgumentException ignored) { - } - // A null string field in the middle. - try { - results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, null, AnnotationIndexTypes.FIELD_INDEX_INT); - } catch (IllegalArgumentException ignored) { - } - // A null string field at the end. - try { - results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, null); - } catch (IllegalArgumentException ignored) { - } - // (String) Null makes varargs a null array. - try { - results.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String)null); - } catch (IllegalArgumentException ignored) { - } - // Two (String) null for first and varargs fields. - try { - results.distinct(null, (String) null); - } catch (IllegalArgumentException ignored) { - } - // "" & (String)null combination. - try { - results.distinct("", (String) null); - } catch (IllegalArgumentException ignored) { - } - // "" & (String)null combination. - try { - results.distinct(null, ""); - } catch (IllegalArgumentException ignored) { - } - // Two empty fields tests. - try { - results.distinct("", ""); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void distinctMultiArgs_withNullValues() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - RealmResults distinctMulti = results.distinct(AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING); - assertEquals(1, distinctMulti.size()); - } - - @Test - public void distinctMultiArgs_notIndexedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - try { - results.distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_STRING, AnnotationIndexTypes.NOT_INDEX_FIELDS); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void distinctMultiArgs_doesNotExistField() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - try { - results.distinct(AnnotationIndexTypes.FIELD_INDEX_INT, AnnotationIndexTypes.NONEXISTANT_MIX_FIELDS); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void distinctMultiArgs_invalidTypesFields() { - populateTestRealm(); - - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - try { - results.distinct(AllTypes.FIELD_REALMOBJECT, AllTypes.INVALID_TYPES_FIELDS_FOR_DISTINCT); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void distinctMultiArgs_indexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - try { - results.distinct(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING, AnnotationIndexTypes.INDEX_LINKED_FIELDS); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void distinctMultiArgs_notIndexedLinkedFields() { - final long numberOfBlocks = 25; - final long numberOfObjects = 10; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - try { - results.distinct(AnnotationIndexTypes.NOT_INDEX_LINKED_FILED_STRING, AnnotationIndexTypes.NOT_INDEX_LINKED_FIELDS); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void distinctMultiArgs_invalidTypesLinkedFields() { - populateForDistinctInvalidTypesLinked(realm); - - RealmResults results = realm.where(AnnotationIndexTypes.class).findAll(); - try { - results.distinct(AllJavaTypes.INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT, AllJavaTypes.INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT); - } catch (IllegalArgumentException ignored) { - } - } private RealmResults populateRealmResultsOnLinkView(Realm realm) { realm.beginTransaction(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index b1d272dcc7..4b61f799bb 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -338,33 +338,4 @@ public Observable>> asChangesetObservable() { throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2."); } } - - /** - * @deprecated use {@link RealmQuery#distinct(String)} on the return value of {@link #where()} instead. This will - * be removed in coming 3.x.x minor releases. - */ - @Deprecated - public RealmResults distinct(String fieldName) { - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(new SchemaConnector(realm.getSchema()), collection.getTable(), fieldName); - Collection distinctCollection = collection.distinct(distinctDescriptor); - return createLoadedResults(distinctCollection); - } - - /** - * @deprecated use {@link RealmQuery#distinctAsync(String)} on the return value of {@link #where()} instead. This - * will be removed in coming 3.x.x minor releases. - */ - @Deprecated - public RealmResults distinctAsync(String fieldName) { - return where().distinctAsync(fieldName); - } - - /** - * @deprecated use {@link RealmQuery#distinct(String, String...)} on the return value of {@link #where()} instead. - * This will be removed in coming 3.x.x minor releases. - */ - @Deprecated - public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { - return where().distinct(firstFieldName, remainingFieldNames); - } } From 8a3c4ce1f0d47c64dc4d97cfafbea1d65f5e5827 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Sat, 16 Sep 2017 20:18:37 +0800 Subject: [PATCH 0961/2110] Check and throw when converting String/byte[] (#5263) Throw IAE if the data length exceeds the limit. --- CHANGELOG.md | 4 ++ .../src/androidTest/AndroidManifest.xml | 3 +- .../java/io/realm/RealmObjectTests.java | 46 +++++++++++++++++++ .../src/main/cpp/io_realm_internal_Table.cpp | 3 ++ .../src/main/cpp/java_accessor.hpp | 9 ++++ realm/realm-library/src/main/cpp/util.cpp | 1 + realm/realm-library/src/main/cpp/util.hpp | 16 ++++++- .../main/java/io/realm/internal/Table.java | 3 ++ 8 files changed, 83 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244b15dbad..2fe5b41b83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 4.0.0-BETA3 (YYYY-MM-DD) +### Bug Fixes + +* Throw `IllegalArgumentException` instead of `IllegalStateException` when calling string/binary data setters if the data length exceeds the limit. + ### Breaking Changes * `RealmResults.distinct()`/`RealmResults.distinctAsync()` have been removed. Use `RealmQuery.distinct()`/`RealmQuery.distinctAsync()` instead. diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index 5590954aa1..f706bfdeae 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -14,7 +14,8 @@ android:targetSdkVersion="22"/> + android:debuggable="true" + android:largeHeap="true"> #include +#include #include @@ -168,6 +169,14 @@ template <> template <> inline BinaryData JPrimitiveArrayAccessor::transform() { + // To solve the link issue by directly using Table::max_binary_size + static constexpr size_t max_binary_size = Table::max_binary_size; + + if (static_cast(m_size) > max_binary_size) { + THROW_JAVA_EXCEPTION(m_elements_holder->m_env, JavaExceptionDef::IllegalArgument, + util::format("The length of 'byte[]' value is %1 which exceeds the max binary size %2.", + m_size, max_binary_size)); + } return is_null() ? realm::BinaryData() : realm::BinaryData(reinterpret_cast(m_elements_holder->m_data_ptr), m_size); } diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index e4f9b51dfc..b7e157cdac 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -427,6 +427,7 @@ transcode_complete : { JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) + : m_env(env) { // For efficiency, if the incoming UTF-16 string is sufficiently // small, we will choose an UTF-8 output buffer whose size (in diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 30346e469b..c5c536f5b3 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -29,13 +29,16 @@ #include #include #include +#include #include #include #include "io_realm_internal_Util.h" +#include "java_exception_def.hpp" #include "jni_util/log.hpp" +#include "jni_util/java_exception_thrower.hpp" #define CHECK_PARAMETERS 1 // Check all parameters in API and throw exceptions in java if invalid @@ -454,11 +457,21 @@ class JStringAccessor { public: JStringAccessor(JNIEnv*, jstring); // throws - operator realm::StringData() const noexcept + operator realm::StringData() const { + // To solve the link issue by directly using Table::max_string_size + static constexpr size_t max_string_size = realm::Table::max_string_size; + if (m_is_null) { return realm::StringData(NULL); } + else if (m_size > max_string_size) { + THROW_JAVA_EXCEPTION( + m_env, realm::_impl::JavaExceptionDef::IllegalArgument, + realm::util::format( + "The length of 'String' value in UTF8 encoding is %1 which exceeds the max string length %2.", + m_size, max_string_size)); + } else { return realm::StringData(m_data.get(), m_size); } @@ -473,6 +486,7 @@ class JStringAccessor { } private: + JNIEnv* m_env; bool m_is_null; std::unique_ptr m_data; std::size_t m_size; diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 704023bbfe..6162d8d820 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -47,6 +47,9 @@ public class Table implements TableSchema, NativeObject { private static final long PRIMARY_KEY_FIELD_COLUMN_INDEX = 1; public static final long NO_PRIMARY_KEY = -2; + public static final int MAX_BINARY_SIZE = 0xFFFFF8 - 8/*array header size*/; + public static final int MAX_STRING_SIZE = 0xFFFFF8 - 8/*array header size*/ - 1; + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final long nativePtr; From 0c9c0282d3cfd4b6f8e48cc418fcc9a492446720 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 18 Sep 2017 11:00:13 +0200 Subject: [PATCH 0962/2110] Use NPM to install integration test server (#5249) --- CHANGELOG.md | 12 +- dependencies.list | 11 +- .../io/realm/internal/PrimaryKeyTests.java | 53 +++++--- .../objectServer/java/io/realm/SyncUser.java | 26 +--- .../java/io/realm/SyncUserInfo.java | 51 ++++--- .../network/LookupUserIdResponse.java | 79 ++++++----- .../network/OkHttpAuthenticationServer.java | 17 ++- .../java/io/realm/BaseIntegrationTest.java | 2 +- .../java/io/realm/PermissionManagerTests.java | 2 +- .../java/io/realm/SSLConfigurationTests.java | 2 + .../java/io/realm/SyncedRealmTests.java | 3 + .../java/io/realm/objectserver/AuthTests.java | 29 ++-- .../objectserver/ManagementRealmTests.java | 1 + .../objectserver/ProgressListenerTests.java | 7 +- .../realm/objectserver/utils/HttpUtils.java | 58 +------- .../realm/objectserver/utils/UserFactory.java | 24 +++- tools/sync_test_server/Dockerfile | 18 +-- tools/sync_test_server/ros-testing-server.js | 124 ++++++++++-------- 18 files changed, 234 insertions(+), 285 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fe5b41b83..a0029bc00e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,17 @@ ## 4.0.0-BETA3 (YYYY-MM-DD) -### Bug Fixes - -* Throw `IllegalArgumentException` instead of `IllegalStateException` when calling string/binary data setters if the data length exceeds the limit. - ### Breaking Changes * `RealmResults.distinct()`/`RealmResults.distinctAsync()` have been removed. Use `RealmQuery.distinct()`/`RealmQuery.distinctAsync()` instead. +### Enhancements + +* [ObjectServer] `SyncUserInfo` now also exposes a users metadata using `SyncUserInfo.getMetadata()` + +### Bug Fixes + +* Throw `IllegalArgumentException` instead of `IllegalStateException` when calling string/binary data setters if the data length exceeds the limit. + ### Internal * Upgraded to Realm Sync 2.0.0-rc16. diff --git a/dependencies.list b/dependencies.list index 404a4e0da0..1398418c5d 100644 --- a/dependencies.list +++ b/dependencies.list @@ -3,11 +3,6 @@ REALM_SYNC_VERSION=2.0.0-rc18 REALM_SYNC_SHA256=73cb89c1a04cafa871444aa91d93eb9df16af088bcb7d3ede73bb815d53a06e5 -# Object Server Release used by Integration tests -# Stable releases: https://packagecloud.io/realm/realm?filter=debs -# Beta releases: https://packagecloud.io/realm/realm-beta?filter=debs -# Developer builds: https://packagecloud.io/realm/realm-testing?filter=debs -# /tools/sync_test_server/Dockerfile specify which repo (apt) we should -# install/use between 'realm', 'realm-beta' and 'realm-testing', the version below should -# correspond to an existing version on the *specified* repo. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-rc2-285 +# Object Server Release used by Integration tests. Installed using NPM. +# Use `npm view realm-object-server versions` to get a list of available versions. +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.30 diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index 1b9152b3d6..b63e45dd41 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -21,6 +21,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,8 +30,14 @@ import java.util.Arrays; import java.util.List; +import io.realm.DynamicRealm; +import io.realm.DynamicRealmObject; +import io.realm.FieldAttribute; +import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmFieldType; +import io.realm.RealmObjectSchema; +import io.realm.RealmSchema; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.rule.TestRealmConfigurationFactory; @@ -83,36 +90,40 @@ private Table getTableWithIntegerPrimaryKey() { return t; } - // Tests that primary key constraints are actually removed. + /** + * This test surfaces a bunch of problems, most of them seem to be around caching of the schema + * during a transaction + * + * 1) Removing the primary key do not invalidate the cache in RealmSchema and those cached + * are ImmutableRealmObjectSchema so do not change when the primary key is removed. + * + * 2) Addding `schema.refresh()` to RealmObjectSchema.removePrimaryKey()` causes + * RealmPrimaryKeyConstraintException anyway. Unclear why. + */ @Test + @Ignore("See https://github.com/realm/realm-java/issues/5231") public void removingPrimaryKeyRemovesConstraint_typeSetters() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name("removeConstraints").build(); - SharedRealm sharedRealm = SharedRealm.getInstance(config); - sharedRealm.beginTransaction(); - Table tbl = sharedRealm.createTable(Table.getTableNameForClass("EmployeeTable")); - tbl.addColumn(RealmFieldType.STRING, "name"); - tbl.setPrimaryKey("name"); + DynamicRealm realm = DynamicRealm.getInstance(config); + RealmSchema realmSchema = realm.getSchema(); + realm.beginTransaction(); + RealmObjectSchema tableSchema = realmSchema.create("Employee") + .addField("name", String.class, FieldAttribute.PRIMARY_KEY); - // Creates first entry with name "Foo". - tbl.setString(0, OsObject.createRow(tbl), "Foo", false); + realm.createObject("Employee", "Foo"); + DynamicRealmObject obj = realm.createObject("Employee", "Foo2"); - long rowIndex = OsObject.createRow(tbl); try { - tbl.setString(0, rowIndex, "Foo", false); // Tries to create 2nd entry with name Foo. - } catch (RealmPrimaryKeyConstraintException e1) { - tbl.setPrimaryKey(""); // Primary key check worked, now removes it and tries again. - try { - tbl.setString(0, rowIndex, "Foo", false); - return; - } catch (RealmException e2) { - fail("Primary key not removed"); - } + // Tries to create 2nd entry with name Foo. + obj.setString("name", "Foo"); + } catch (IllegalArgumentException e) { + tableSchema.removePrimaryKey(); + obj.setString("name", "Foo"); + } finally { + realm.close(); } - - fail("Primary key not enforced."); - sharedRealm.close(); } @Test diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 5839f59cfa..429e2e61c8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -274,31 +274,7 @@ public void logout() { // the similar SyncConfiguration using the same identity, but with different (new) // refresh-token. realms.clear(); - - // Finally revoke server token. The local user is logged out in any case. - final AuthenticationServer server = SyncManager.getAuthServer(); - // don't reference directly the refreshToken inside the revoke request - // as it may revoke the newly acquired and refresh_token - final Token refreshTokenToBeRevoked = refreshToken; - - ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; - networkPoolExecutor.submit(new ExponentialBackoffTask() { - - @Override - protected LogoutResponse execute() { - return server.logout(refreshTokenToBeRevoked, getAuthenticationUrl()); - } - - @Override - protected void onSuccess(LogoutResponse response) { - SyncManager.notifyUserLoggedOut(SyncUser.this); - } - - @Override - protected void onError(LogoutResponse response) { - RealmLog.error("Failed to log user out.\n" + response.getError().toString()); - } - }); + SyncManager.notifyUserLoggedOut(SyncUser.this); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java index f9acdc942e..f14fe208be 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java @@ -16,6 +16,9 @@ package io.realm; +import java.util.Collections; +import java.util.Map; + import io.realm.internal.network.LookupUserIdResponse; /** @@ -24,50 +27,44 @@ */ public class SyncUserInfo { - private final String provider; - private final String providerUserIdentity; private final String identity; private final boolean isAdmin; + private final Map metadata; - private SyncUserInfo(String provider, String providerUserIdentity, String identity, boolean isAdmin) { - this.provider = provider; - this.providerUserIdentity = providerUserIdentity; + private SyncUserInfo(String identity, boolean isAdmin, Map metadata) { this.identity = identity; this.isAdmin = isAdmin; + this.metadata = Collections.unmodifiableMap(metadata); } static SyncUserInfo fromLookupUserIdResponse(LookupUserIdResponse response) { - return new SyncUserInfo(response.getProvider(), response.getProviderId(), response.getUserId(), response.isAdmin()); + return new SyncUserInfo(response.getUserId(), response.isAdmin(), response.getMetadata()); } /** - * @return identity providers {@link io.realm.SyncCredentials.IdentityProvider} which manages the user represented by this user info instance. - */ - public String getProvider() { - return provider; - } - - /** - * @return The username or identity issued to this user by the authentication provider. - */ - public String getProviderUserIdentity() { - return providerUserIdentity; - } - - /** - * @return The identity issued to this user by the Realm Object Server. + * @return the identity issued to this user by the Realm Object Server. */ public String getIdentity() { return identity; } /** - * @return Whether the user is flagged on the Realm Object Server as an administrator. + * @return whether the user is flagged on the Realm Object Server as an administrator. */ public boolean isAdmin() { return isAdmin; } + /** + * Returns the metadata associated with the user. The metadata is a generic key/value map with + * the only restriction that a key must be non-empty. + * + * @return the metadata associated with this user. + */ + public Map getMetadata() { + return metadata; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -76,17 +73,15 @@ public boolean equals(Object o) { SyncUserInfo that = (SyncUserInfo) o; if (isAdmin != that.isAdmin) return false; - if (!provider.equals(that.provider)) return false; - if (!providerUserIdentity.equals(that.providerUserIdentity)) return false; - return identity.equals(that.identity); + if (!identity.equals(that.identity)) return false; + return metadata.equals(that.metadata); } @Override public int hashCode() { - int result = provider.hashCode(); - result = 31 * result + providerUserIdentity.hashCode(); - result = 31 * result + identity.hashCode(); + int result = identity.hashCode(); result = 31 * result + (isAdmin ? 1 : 0); + result = 31 * result + metadata.hashCode(); return result; } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java index 86c20952f4..eb60bd4890 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java @@ -19,7 +19,10 @@ import org.json.JSONObject; import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; import java.util.Locale; +import java.util.Map; import io.realm.ErrorCode; import io.realm.ObjectServerError; @@ -27,20 +30,17 @@ import okhttp3.Response; /** - * Class wrapping the response from `GET /api/providers/:provider/accounts/:provider_id` + * Class wrapping the response from `GET /auth/users/:userId` */ public class LookupUserIdResponse extends AuthServerResponse { - private static final String JSON_FIELD_PROVIDER = "provider"; - private static final String JSON_FIELD_PROVIDER_ID = "provider_id"; - private static final String JSON_FIELD_USER = "user"; - private static final String JSON_FIELD_USER_ID = "id"; + private static final String JSON_FIELD_USER_ID = "userId"; private static final String JSON_FIELD_USER_IS_ADMIN = "isAdmin"; + private static final String JSON_FIELD_METADATA = "metadata"; - private final String providerId; - private final String provider; private final String userId; private final Boolean isAdmin; + private final Map metadata; /** * Helper method for creating the proper lookup user response. This method will set the appropriate error @@ -82,63 +82,39 @@ private LookupUserIdResponse(ObjectServerError error) { RealmLog.debug("LookupUserIdResponse - Error: " + error); setError(error); this.error = error; - this.providerId = null; - this.provider = null; this.userId = null; this.isAdmin = null; + this.metadata = new HashMap<>(); } private LookupUserIdResponse(String serverResponse) { ObjectServerError error; - String provider; - String providerId; String userId; Boolean isAdmin; String message; + Map metadata; try { JSONObject obj = new JSONObject(serverResponse); - provider = obj.getString(JSON_FIELD_PROVIDER); - providerId = obj.getString(JSON_FIELD_PROVIDER_ID); - JSONObject jsonUser = obj.getJSONObject(JSON_FIELD_USER); - if (jsonUser != null) { - userId = jsonUser.optString(JSON_FIELD_USER_ID, null); - // can not use optBoolean since `null` is not permitted as default value - // (we need it for the Boolean boxed type) - isAdmin = jsonUser.has(JSON_FIELD_USER_IS_ADMIN) ? jsonUser.getBoolean(JSON_FIELD_USER_IS_ADMIN) : null; - error = null; - - message = String.format(Locale.US, "Identity %s; Path %b", userId, isAdmin); - - } else { - userId = null; - isAdmin = null; - error = null; - message = "user = null"; - } + userId = obj.getString(JSON_FIELD_USER_ID); + isAdmin = obj.getBoolean(JSON_FIELD_USER_IS_ADMIN); + metadata = jsonToMap(obj.getJSONObject(JSON_FIELD_METADATA)); + error = null; + + message = String.format(Locale.US, "Identity %s; Path %b", userId, isAdmin); } catch (JSONException e) { - provider = null; - providerId = null; userId = null; isAdmin = null; + metadata = new HashMap<>(); error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); message = String.format(Locale.US, "Error %s", error.getErrorMessage()); } RealmLog.debug("LookupUserIdResponse. " + message); setError(error); - this.providerId = providerId; - this.provider = provider; this.userId = userId; this.isAdmin = isAdmin; - } - - public String getProviderId() { - return providerId; - } - - public String getProvider() { - return provider; + this.metadata = metadata; } public String getUserId() { @@ -148,4 +124,25 @@ public String getUserId() { public boolean isAdmin() { return isAdmin; } + + public Map getMetadata() { return metadata; } + + private static Map jsonToMap(JSONObject json) throws JSONException { + Map map = new HashMap<>(); + if(json != JSONObject.NULL) { + map = toMap(json); + } + return map; + } + + private static Map toMap(JSONObject object) throws JSONException { + Map map = new HashMap<>(); + Iterator keysItr = object.keys(); + while(keysItr.hasNext()) { + String key = keysItr.next(); + String value = object.getString(key); + map.put(key, value); + } + return map; + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 35f49c92b4..997f59cea7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -25,6 +25,7 @@ import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; import okhttp3.Call; +import okhttp3.ConnectionPool; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -35,13 +36,17 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final String ACTION_LOGOUT = "revoke"; // Auth end point for logging out users - private static final String ACTION_CHANGE_PASSWORD = "password"; // Auth end point for changing passwords - private static final String ACTION_LOOKUP_USER_ID = "api/providers"; // Auth end point for looking up user id + private static final String ACTION_CHANGE_PASSWORD = "users/:userId:/password"; // Auth end point for changing passwords + private static final String ACTION_LOOKUP_USER_ID = "users"; // Auth end point for looking up user id private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) + // using custom Connection Pool to evict idle connection after 5 seconds rather than 5 minutes (which is the default) + // keeping idle connection on the pool will prevent the ROS to be stopped, since the HttpUtils#stopSyncServer query + // will not return before the tests timeout (ex 10 seconds for AuthTests) + .connectionPool(new ConnectionPool(5, 5, TimeUnit.SECONDS)) .build(); /** @@ -101,7 +106,7 @@ public ChangePasswordResponse changePassword(Token userToken, String newPassword public ChangePasswordResponse changePassword(Token adminToken, String userId, String newPassword, URL authenticationUrl) { try { String requestBody = ChangePasswordRequest.create(adminToken, userId, newPassword).toJson(); - return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD), requestBody); + return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD.replace(":userId:", userId)), requestBody); } catch (Exception e) { return ChangePasswordResponse.from(e); } @@ -129,11 +134,9 @@ private static URL buildActionUrl(URL authenticationUrl, String action) { private static URL buildLookupUserIdUrl(URL authenticationUrl, String action, String provider, String providerId) { String authURL = authenticationUrl.toExternalForm(); - // we need the base URL without the '/auth' part - String baseUrlString = authURL.substring(0, authURL.indexOf(authenticationUrl.getPath())); + String separator = authURL.endsWith("/") ? "" : "/"; try { - String separator = baseUrlString.endsWith("/") ? "" : "/"; - return new URL(baseUrlString + separator + action + "/" + provider + "/accounts/" + providerId); + return new URL(authURL + separator + action + "/" + providerId); } catch (MalformedURLException e) { throw new RuntimeException(e); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index b538fc953c..65542f0925 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -95,7 +95,7 @@ protected static void stopSyncServer() { try { HttpUtils.stopSyncServer(); } catch (Exception e) { - Log.e(HttpUtils.TAG, "Failed to stop Sync Server" + Util.getStackTrace(e)); + Log.e(HttpUtils.TAG, "Failed to stop Sync Server: " + Util.getStackTrace(e)); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 95c8c4a7c7..75b178ca6c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -52,8 +52,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -@Ignore("Wait for https://github.com/realm/realm-object-server/issues/1671 to be fixed") @RunWith(AndroidJUnit4.class) +@Ignore("Wait for https://github.com/realm/realm-object-server/issues/1671 to be fixed") public class PermissionManagerTests extends StandardIntegrationTest { private SyncUser user; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index bed08c965f..08888b9941 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -20,6 +20,7 @@ import android.support.test.runner.AndroidJUnit4; import android.text.style.TabStopSpan; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; @@ -40,6 +41,7 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) +@Ignore("See https://github.com/realm/ros/issues/240") public class SSLConfigurationTests extends StandardIntegrationTest { @Rule diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index fddce46123..660d9e6d56 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -20,6 +20,7 @@ import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +44,7 @@ * Catch all class for tests that not naturally fit anywhere else. */ @RunWith(AndroidJUnit4.class) +@Ignore("See https://github.com/realm/realm-java/issues/5177. All waitForInitialRemoteData tests seem to fail. Must be fixed") public class SyncedRealmTests extends StandardIntegrationTest { @Test @@ -117,6 +119,7 @@ public void execute(Realm realm) { // We cannot do much better since we cannot control the order of events internally in Realm which would be // needed to correctly test all error paths. @Test + @Ignore("See https://github.com/realm/realm-java/issues/5177") public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 7c249c1480..9bd027cd60 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -194,6 +194,7 @@ public void onError(ObjectServerError error) { } @Test + @Ignore("Resolve https://github.com/realm/ros/issues/273") public void changePassword() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; @@ -213,6 +214,7 @@ public void changePassword() { } @Test + @Ignore("Resolve https://github.com/realm/ros/issues/273") public void changePassword_using_admin() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; @@ -240,6 +242,7 @@ public void changePassword_using_admin() { @Test @RunTestInLooperThread + @Ignore("Resolve https://github.com/realm/ros/issues/273") public void changePassword_using_admin_async() { final String username = UUID.randomUUID().toString(); final String originalPassword = "password"; @@ -497,6 +500,7 @@ public void singleUserCanBeLoggedInAndOutRepeatedly() { } @Test + @Ignore("Resolve https://github.com/realm/ros/issues/261") public void revokedRefreshTokenIsNotSameAfterLogin() throws InterruptedException { final String uniqueName = UUID.randomUUID().toString(); @@ -519,6 +523,7 @@ public void revokedRefreshTokenIsNotSameAfterLogin() throws InterruptedException // WARNING: this test can fail if there's a difference between the server's and device's clock, causing the // refresh access token to be too far in time. @Test(timeout = 30000) + @Ignore("Resolve https://github.com/realm/ros/issues/277") public void preemptiveTokenRefresh() throws NoSuchFieldException, IllegalAccessException, InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); @@ -534,7 +539,7 @@ public void preemptiveTokenRefresh() throws NoSuchFieldException, IllegalAccessE .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { - Assert.fail(error.getErrorMessage()); + fail(error.getErrorMessage()); } }) .build(); @@ -582,8 +587,8 @@ public void execute(Realm realm) { assertNotEquals(accessToken, newAccessToken); // refresh_token identity is the same - Assert.assertEquals(user.getAccessToken().identity(), newAccessToken.identity()); - Assert.assertEquals(accessToken.identity(), newAccessToken.identity()); + assertEquals(user.getAccessToken().identity(), newAccessToken.identity()); + assertEquals(accessToken.identity(), newAccessToken.identity()); realm.close(); } @@ -603,10 +608,9 @@ public void retrieve() { SyncUserInfo userInfo = adminUser.retrieveInfoForUser(username, SyncCredentials.IdentityProvider.USERNAME_PASSWORD); assertNotNull(userInfo); - assertEquals(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, userInfo.getProvider()); - assertEquals(username, userInfo.getProviderUserIdentity()); assertEquals(identity, userInfo.getIdentity()); assertFalse(userInfo.isAdmin()); + assertTrue(userInfo.getMetadata().isEmpty()); } @@ -646,10 +650,9 @@ public void run() { SyncUserInfo userInfo = adminUser.retrieveInfoForUser(username, SyncCredentials.IdentityProvider.USERNAME_PASSWORD); assertNotNull(userInfo); - assertEquals(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, userInfo.getProvider()); - assertEquals(username, userInfo.getProviderUserIdentity()); assertEquals(identity, userInfo.getIdentity()); assertFalse(userInfo.isAdmin()); + assertTrue(userInfo.getMetadata().isEmpty()); looperThread.testComplete(); } @@ -660,15 +663,6 @@ public void run() { user.logout(); } - @Test - public void retrieve_AdminUser() { - final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncUserInfo userInfo = adminUser.retrieveInfoForUser("admin", SyncCredentials.IdentityProvider.DEBUG);// TODO use enum for auth provider - assertNotNull(userInfo); - assertEquals(adminUser.getIdentity(), userInfo.getIdentity()); - assertTrue(userInfo.isAdmin()); - } - @Test public void retrieve_unknownProviderId() { final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); @@ -730,10 +724,9 @@ public void retrieve_async() { @Override public void onSuccess(SyncUserInfo userInfo) { assertNotNull(userInfo); - assertEquals(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, userInfo.getProvider()); - assertEquals(username, userInfo.getProviderUserIdentity()); assertEquals(identity, userInfo.getIdentity()); assertFalse(userInfo.isAdmin()); + assertTrue(userInfo.getMetadata().isEmpty()); looperThread.testComplete(); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java index 135d607826..a2f96973e8 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java @@ -46,6 +46,7 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) +@Ignore("Resolve https://github.com/realm/ros/issues/18") public class ManagementRealmTests extends StandardIntegrationTest { // This is primarily a test making sure that an admin user actually connects correctly to ROS. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index a42a274f97..af4d52fdcb 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -18,6 +18,7 @@ import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,7 +30,6 @@ import javax.annotation.Nonnull; -import io.realm.BaseIntegrationTest; import io.realm.Progress; import io.realm.ProgressListener; import io.realm.ProgressMode; @@ -126,8 +126,6 @@ public void onChange(Progress progress) { }); TestHelper.awaitOrFail(allChangesDownloaded); realm.close(); - userWithData.logout(); - adminUser.logout(); } @Test @@ -189,8 +187,6 @@ public void onChange(Progress progress) { TestHelper.awaitOrFail(allChangesDownloaded); adminRealm.close(); userRealm.close(); - userWithData.logout(); - adminUser.logout(); worker.join(); } @@ -229,7 +225,6 @@ public void onChange(Progress progress) { realm.close(); } - @Test public void uploadProgressListener_changesOnly() { final CountDownLatch allChangeUploaded = new CountDownLatch(1); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index 1257444155..aba3c0ec97 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -19,10 +19,15 @@ import android.util.Log; import java.io.IOException; +import java.net.SocketTimeoutException; +import java.util.concurrent.TimeUnit; +import io.realm.log.RealmLog; import okhttp3.Headers; +import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -51,52 +56,6 @@ public static void startSyncServer() throws Exception { Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - - Headers responseHeaders = response.headers(); - for (int i = 0; i < responseHeaders.size(); i++) { - Log.d(TAG, responseHeaders.name(i) + ": " + responseHeaders.value(i)); - } - - Log.d(TAG, response.body().string()); - - // FIXME: Server ready checking should be done in the control server side! - if (!waitAuthServerReady()) { - stopSyncServer(); - throw new RuntimeException("Auth server cannot be started."); - } - } - - // Checking the server - private static boolean waitAuthServerReady() throws InterruptedException { - int retryTimes = 20; - - // Dummy invalid request, which will trigger a 400 (BAD REQUEST), but indicate the auth - // server is responsive - Request request = new Request.Builder() - .url(Constants.AUTH_SERVER_URL) - .build(); - - while (retryTimes != 0) { - Response response = null; - try { - response = client.newCall(request).execute(); - if (response.isSuccessful()) { - return true; - } - } catch (IOException e) { - // TODO As long as the auth server hasn't started yet, OKHttp cannot parse the response - // correctly. At this point it is unknown weather is a bug in OKHttp or an - // unknown host is reported. This can cause a lot of "false" errors in the log. - Thread.sleep(500); - } finally { - if (response != null) { - response.close(); - } - } - retryTimes--; - } - - return false; } public static void stopSyncServer() throws Exception { @@ -106,12 +65,5 @@ public static void stopSyncServer() throws Exception { Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - - Headers responseHeaders = response.headers(); - for (int i = 0; i < responseHeaders.size(); i++) { - Log.d(TAG, responseHeaders.name(i) + ": " + responseHeaders.value(i)); - } - - Log.d(TAG, response.body().string()); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index f4b13ed174..a1d325d687 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -18,6 +18,7 @@ import android.os.Handler; import android.os.HandlerThread; +import android.os.SystemClock; import java.util.Map; import java.util.UUID; @@ -25,6 +26,8 @@ import java.util.concurrent.atomic.AtomicInteger; import io.realm.AuthenticationListener; +import io.realm.ErrorCode; +import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.SyncCredentials; @@ -87,8 +90,25 @@ public SyncUser createDefaultUser(String authUrl) { public static SyncUser createAdminUser(String authUrl) { // `admin` required as user identifier to be granted admin rights. - SyncCredentials credentials = SyncCredentials.custom("admin", "debug", null); - return SyncUser.login(credentials, authUrl); + // ROS 2.0 comes with a default admin user named "realm-admin" with password "". + SyncCredentials credentials = SyncCredentials.usernamePassword("realm-admin", "", false); + int attempts = 3; + while (attempts > 0) { + attempts--; + try { + return SyncUser.login(credentials, authUrl); + } catch (ObjectServerError e) { + // ROS default admin user might not be created yet, we need to retry. + // Remove this work-around when https://github.com/realm/ros/issues/282 + // is fixed. + if (e.getErrorCode() != ErrorCode.INVALID_CREDENTIALS) { + throw e; + } + SystemClock.sleep(1000); + } + } + + throw new IllegalStateException("Could not login 'realm-admin'"); } // Since we don't have a reliable way to reset the sync server and client, just use a new user factory for every diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 3228ee8622..e53b38b584 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -1,23 +1,13 @@ -FROM ubuntu:16.04 +FROM node:6.11.2 ARG ROS_DE_VERSION -# Add realm repo -RUN apt-get update -qq \ - && apt-get install -y curl npm \ - && curl -s https://packagecloud.io/install/repositories/realm/realm-beta/script.deb.sh | bash - #&& curl -s https://packagecloud.io/install/repositories/realm/realm/script.deb.sh | bash - #&& curl -s https://packagecloud.io/install/repositories/realm/realm-testing/script.deb.sh | bash +# Install realm object server +RUN npm install -g realm-object-server@$ROS_DE_VERSION -S -# ROS npm dependencies -RUN npm init -y +# Install test server dependencies RUN npm install winston temp httpdispatcher@1.0.0 -# Install realm object server -RUN apt-get update -qq \ - && apt-get install -y realm-object-server-developer=$ROS_DE_VERSION \ - && apt-get clean - COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index 18d2139081..384c54984e 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -33,78 +33,90 @@ function handleRequest(request, response) { var syncServerChildProcess = null; -function startRealmObjectServer(done) { - // Hack for checking the ROS is fully initialized. - // Consider the ROS is initialized fully only if log below shows twice - // "client: Closing Realm file: /tmp/ros117521-7-1eiqt7a/internal_data/permission/__auth.realm" - // https://github.com/realm/realm-object-server/issues/1297 - var logFindingCounter = 2 - - stopRealmObjectServer(function(err) { - if(err) { - return; +// Waits for ROS to be fully initialized. +function waitForRosToInitialize(attempts, onSuccess, onError) { + if (attempts == 0) { + onError("Could not get ROS to start. See Docker log."); + return; + } + http.get("http://0.0.0.0:9080/health", function(res) { + if (res.statusCode != 200) { + winston.info("ROS /health/ returned: " + res.statusCode) + waitForRosToInitialize(attempts - 1,onSuccess) + } else { + onSuccess(); } - temp.mkdir('ros', function(err, path) { - if (!err) { - winston.info("Starting sync server in ", path); - var env = Object.create( process.env ); - winston.info(env.NODE_ENV); - env.NODE_ENV = 'development'; - syncServerChildProcess = spawn('realm-object-server', - ['--root', path, - '--configuration', '/configuration.yml'], - { env: env, cwd: path}); - // local config: - syncServerChildProcess.stdout.on('data', (data) => { - if (logFindingCounter != 0 && /client: Closing Realm file: .*__auth.realm/.test(data)) { - if (logFindingCounter == 1) { - done() - } - logFindingCounter-- - } - winston.info(`stdout: ${data}`); - }); - - syncServerChildProcess.stderr.on('data', (data) => { - winston.info(`stderr: ${data}`); - }); - - syncServerChildProcess.on('close', (code) => { - winston.info(`child process exited with code ${code}`); - }); - } - }); + }).on('error', function(err) { + // ROS not accepting any connections yet. + // Errors like ECONNREFUSED 0.0.0.0:9080 will be reported here. + // Wait a little before trying again (common startup is ~1 second). + setTimeout(function() { + waitForRosToInitialize(attempts - 1, onSuccess); + }, 200); }); } -function stopRealmObjectServer(callback) { - if (syncServerChildProcess) { - syncServerChildProcess.on('exit', function() { - syncServerChildProcess = null; - callback(); - }); - syncServerChildProcess.kill(); - } else { - callback(); - } +function startRealmObjectServer(onSuccess, onError) { + temp.mkdir('ros', function(err, path) { + if (!err) { + winston.info("Starting sync server in ", path); + var env = Object.create( process.env ); + winston.info(env.NODE_ENV); + env.NODE_ENV = 'development'; + syncServerChildProcess = spawn('ros', + ['start', '--data', path], + { env: env, cwd: path}); + + // local config: + syncServerChildProcess.stdout.on('data', (data) => { + winston.info(`stdout: ${data}`); + }); + + syncServerChildProcess.stderr.on('data', (data) => { + winston.info(`stderr: ${data}`); + }); + + waitForRosToInitialize(20, onSuccess, onError); + } + }); } +function stopRealmObjectServer(onSuccess, onError) { + if(syncServerChildProcess == null) { + onError("No ROS process found to stop"); + } + + syncServerChildProcess.on('exit', function(code) { + winston.info("ROS server stopped due to process being killed. Exit code: " + code); + syncServerChildProcess.removeAllListeners('exit'); + syncServerChildProcess = null; + onSuccess(); + }); + + // Move back to `SIGTERM` once https://github.com/realm/ros/issues/234 + // is resolved + syncServerChildProcess.kill('SIGKILL'); +} // start sync server dispatcher.onGet("/start", function(req, res) { + winston.info("Attempting to start ROS"); startRealmObjectServer(() => { res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('Starting a server'); - }) + res.end('ROS server started'); + }, function (err) { + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end('Starting a ROS server failed: ' + err); + }); }); // stop a previously started sync server dispatcher.onGet("/stop", function(req, res) { - stopRealmObjectServer(function() { - winston.info("Sync server stopped"); + winston.info("Attempting to stop ROS") + stopRealmObjectServer(function() { res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('Stopping the server'); - }); + res.end('ROS server stopped'); + }); }); //Create and start the Http server From c484750e983b0d5aee211ef6924a51119b59a652 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 18 Sep 2017 15:01:40 +0200 Subject: [PATCH 0963/2110] Unit test combining sort() and distinct() (#3522) --- CHANGELOG.md | 17 ++++++++ .../androidTest/java/io/realm/SortTest.java | 43 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0029bc00e..54dd039905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 4.0.0 (YYYY-MM-DD) + +## Breaking Changes + +* Calling `distinct()` on a sorted `RealmResults` no longer clears the sorting (#3503). + +## Deprecated + +## Enhancements + +## Bug Fixes + +## Internal + +## Credits + + ## 4.0.0-BETA3 (YYYY-MM-DD) ### Breaking Changes diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 6960e847ce..b21e48984a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger; import io.realm.entities.AllTypes; +import io.realm.entities.AnnotationIndexTypes; import io.realm.entities.StringOnly; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; @@ -66,6 +67,7 @@ public class SortTest { private void populateRealm(Realm realm) { realm.beginTransaction(); + realm.delete(AllTypes.class); AllTypes object1 = realm.createObject(AllTypes.class); object1.setColumnLong(5); @@ -82,6 +84,23 @@ private void populateRealm(Realm realm) { AllTypes object4 = realm.createObject(AllTypes.class); object4.setColumnLong(5); object4.setColumnString("Adam"); + + realm.delete(AnnotationIndexTypes.class); + AnnotationIndexTypes obj1 = realm.createObject(AnnotationIndexTypes.class); + obj1.setIndexLong(1); + obj1.setIndexInt(1); + obj1.setIndexString("A"); + + AnnotationIndexTypes obj2 = realm.createObject(AnnotationIndexTypes.class); + obj2.setIndexLong(2); + obj2.setIndexInt(1); + obj2.setIndexString("B"); + + AnnotationIndexTypes obj3 = realm.createObject(AnnotationIndexTypes.class); + obj3.setIndexLong(3); + obj3.setIndexInt(1); + obj3.setIndexString("C"); + realm.commitTransaction(); } @@ -528,6 +547,30 @@ public void onChange(RealmResults element) { realm.commitTransaction(); } + @Test + public void sortByLongDistinctByInt() { + // Before sorting: + // (FIELD_INDEX_LONG, FIELD_INDEX_INT, FIELD_INDEX_STRING) + // (1, 1, "A") + // (2, 1, "B") + // (3, 1, "C") + // After sorting + // (3, 1, "C") + // (2, 1, "B") + // (1, 1, "A) + RealmResults results1 = realm.where(AnnotationIndexTypes.class) + .findAllSorted(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.DESCENDING); + assertEquals(3, results1.size()); + assertEquals(3, results1.get(0).getIndexLong()); + + // After distinct: + // (3, 1, "C") + RealmResults results2 = results1.where().distinct(AnnotationIndexTypes.FIELD_INDEX_INT); + assertEquals(1, results2.size()); + assertEquals("C", results2.get(0).getIndexString()); + assertEquals(3, results2.get(0).getIndexLong()); + } + private void createAndTest(String str) { realm.beginTransaction(); realm.delete(StringOnly.class); From d4802f27702c8326fd96bf3c3c14f47ff2414205 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 18 Sep 2017 15:02:33 +0200 Subject: [PATCH 0964/2110] Ignore callbacks from Object Store if waiting was canceled. (#4989) --- .../src/main/cpp/io_realm_SyncSession.cpp | 20 +++--- .../java/io/realm/SyncSession.java | 34 ++++++++--- .../realm/objectserver/SyncSessionTests.java | 61 +++++++++++++++++++ 3 files changed, 99 insertions(+), 16 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index e2d9fdf138..a113c1479d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -139,6 +139,7 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveProgressListener(JN JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadCompletion(JNIEnv* env, jobject session_object, + jint callback_id, jstring j_local_realm_path) { TR_ENTER() @@ -149,11 +150,11 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple if (session) { static JavaClass java_sync_session_class(env, "io/realm/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", - "(Ljava/lang/Long;Ljava/lang/String;)V"); + "(ILjava/lang/Long;Ljava/lang/String;)V"); JavaGlobalRef java_session_object_ref(env, session_object); bool listener_registered = - session->wait_for_download_completion([java_session_object_ref](std::error_code error) { + session->wait_for_download_completion([java_session_object_ref, callback_id](std::error_code error) { JNIEnv* env = JniUtils::get_env(true); JavaLocalRef java_error_code; JavaLocalRef java_error_message; @@ -162,8 +163,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); } - env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, java_error_code.get(), - java_error_message.get()); + env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, + callback_id, java_error_code.get(), java_error_message.get()); }); return to_jbool(listener_registered); @@ -174,8 +175,9 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple } JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompletion(JNIEnv* env, - jobject session_object, - jstring j_local_realm_path) + jobject session_object, + jint callback_id, + jstring j_local_realm_path) { TR_ENTER() try { @@ -185,11 +187,11 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompleti if (session) { static JavaClass java_sync_session_class(env, "io/realm/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", - "(Ljava/lang/Long;Ljava/lang/String;)V"); + "(ILjava/lang/Long;Ljava/lang/String;)V"); JavaGlobalRef java_session_object_ref(env, session_object); bool listener_registered = - session->wait_for_upload_completion([java_session_object_ref](std::error_code error) { + session->wait_for_upload_completion([java_session_object_ref, callback_id](std::error_code error) { JNIEnv* env = JniUtils::get_env(true); JavaLocalRef java_error_code; JavaLocalRef java_error_message; @@ -198,7 +200,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompleti java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); } env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, - java_error_code.get(), java_error_message.get()); + callback_id, java_error_code.get(), java_error_message.get()); }); return to_jbool(listener_registered); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index cd17ab390b..9a3eb54852 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -31,6 +31,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -74,6 +75,10 @@ public class SyncSession { private AtomicBoolean onGoingAccessTokenQuery = new AtomicBoolean(false); private volatile boolean isClosed = false; private final AtomicReference waitingForServerChanges = new AtomicReference<>(null); + + // Keeps track of how many times `uploadAllLocalChanges()` or `downloadAllServerChanges()` have + // been called. This is needed so we can correctly ignore canceled requests. + private final AtomicInteger waitCounter = new AtomicInteger(0); private final Object waitForChangesMutex = new Object(); // We need JavaId -> Listener so C++ can trigger callbacks without keeping a reference to the @@ -305,10 +310,18 @@ void close() { // If the native listener was successfully registered, Object Store guarantees that this method will be called at // least once, even if the session is closed. @SuppressWarnings("unused") - private void notifyAllChangesSent(Long errorcode, String errorMessage) { + private void notifyAllChangesSent(int callbackId, Long errorcode, String errorMessage) { WaitForSessionWrapper wrapper = waitingForServerChanges.get(); if (wrapper != null) { - wrapper.handleResult(errorcode, errorMessage); + // Only react to callback if the callback is "active" + // A callback can only become inactive if the thread was interrupted: + // 1. Call `downloadAllServerChanges()` (callback = 1) + // 2. Interrupt it + // 3. Call `uploadAllLocalChanges()` ( callback = 2) + // 4. Sync notifies session that callback:1 is done. It should be ignored. + if (waitCounter.get() == callbackId) { + wrapper.handleResult(errorcode, errorMessage); + } } } @@ -373,11 +386,13 @@ private void waitForChanges(int direction) throws InterruptedException { throw new IllegalArgumentException("Unknown direction: " + direction); } if (!isClosed) { + String realmPath = configuration.getPath(); WaitForSessionWrapper wrapper = new WaitForSessionWrapper(); waitingForServerChanges.set(wrapper); + int callbackId = waitCounter.incrementAndGet(); boolean listenerRegistered = (direction == DIRECTION_DOWNLOAD) - ? nativeWaitForDownloadCompletion(configuration.getPath()) - : nativeWaitForUploadCompletion(configuration.getPath()); + ? nativeWaitForDownloadCompletion(callbackId, realmPath) + : nativeWaitForUploadCompletion(callbackId, realmPath); if (!listenerRegistered) { waitingForServerChanges.set(null); String errorMsg = ""; @@ -390,7 +405,12 @@ private void waitForChanges(int direction) throws InterruptedException { throw new ObjectServerError(ErrorCode.UNKNOWN, errorMsg + " Has the SyncClient been started?"); } - wrapper.waitForServerChanges(); + try { + wrapper.waitForServerChanges(); + } catch(InterruptedException e) { + waitingForServerChanges.set(null); // Ignore any results being sent if the wait was interrupted. + throw e; + } // This might return after the session was closed. In that case, just ignore any result try { @@ -700,7 +720,7 @@ public void throwExceptionIfNeeded() { private static native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); private static native void nativeRemoveProgressListener(String localRealmPath, long listenerToken); private static native boolean nativeRefreshAccessToken(String localRealmPath, String accessToken, String realmUrl); - private native boolean nativeWaitForDownloadCompletion(String localRealmPath); - private native boolean nativeWaitForUploadCompletion(String localRealmPath); + private native boolean nativeWaitForDownloadCompletion(int callbackId, String localRealmPath); + private native boolean nativeWaitForUploadCompletion(int callbackId, String localRealmPath); private static native byte nativeGetState(String localRealmPath); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java index 2bebc5a193..889d09897d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java @@ -11,6 +11,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; import java.util.Arrays; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -34,6 +35,8 @@ import io.realm.rule.TestSyncConfigurationFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; @@ -120,6 +123,64 @@ public void uploadDownloadAllChanges() throws InterruptedException { adminRealm.close(); } + @Test + public void interruptWaits() throws InterruptedException { + final SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + final SyncConfiguration userConfig = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build(); + final SyncConfiguration adminConfig = configFactory + .createSyncConfigurationBuilder(adminUser, userConfig.getServerUrl().toString()) + .build(); + + Thread t = new Thread(new Runnable() { + @Override + public void run() { + Realm userRealm = Realm.getInstance(userConfig); + userRealm.beginTransaction(); + userRealm.createObject(AllTypes.class); + userRealm.commitTransaction(); + SyncSession userSession = SyncManager.getSession(userConfig); + try { + // 1. Start download (which will be interrupted) + Thread.currentThread().interrupt(); + userSession.downloadAllServerChanges(); + } catch (InterruptedException ignored) { + assertFalse(Thread.currentThread().isInterrupted()); + } + try { + // 2. Upload all changes + userSession.uploadAllLocalChanges(); + } catch (InterruptedException e) { + fail("Upload interrupted"); + } + userRealm.close(); + + Realm adminRealm = Realm.getInstance(adminConfig); + SyncSession adminSession = SyncManager.getSession(adminConfig); + try { + // 3. Start upload (which will be interrupted) + Thread.currentThread().interrupt(); + adminSession.uploadAllLocalChanges(); + } catch (InterruptedException ignored) { + assertFalse(Thread.currentThread().isInterrupted()); // clear interrupted flag + } + try { + // 4. Download all changes + adminSession.downloadAllServerChanges(); + } catch (InterruptedException e) { + fail("Download interrupted"); + } + adminRealm.refresh(); + assertEquals(1, adminRealm.where(AllTypes.class).count()); + adminRealm.close(); + } + }); + t.start(); + t.join(); + } + // check that logging out a SyncUser used by different Realm will // affect all associated sessions. @Test(timeout=5000) From ae77222ae0a7d413c62ee24b3f8ae06833d6340e Mon Sep 17 00:00:00 2001 From: LYK Date: Tue, 19 Sep 2017 01:35:12 +0900 Subject: [PATCH 0965/2110] Add Gridview example's proguard configuration. (#5274) * Fix gridView. * Add missing proguard configuration file. * PR feedback. --- examples/gridViewExample/build.gradle | 2 ++ examples/gridViewExample/proguard-rules.pro | 1 + .../src/main/java/io/realm/examples/realmgridview/City.java | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 examples/gridViewExample/proguard-rules.pro diff --git a/examples/gridViewExample/build.gradle b/examples/gridViewExample/build.gradle index 6aa6ec9c97..178240ccf4 100644 --- a/examples/gridViewExample/build.gradle +++ b/examples/gridViewExample/build.gradle @@ -17,9 +17,11 @@ android { release { minifyEnabled true signingConfig signingConfigs.debug + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } productFlavors { diff --git a/examples/gridViewExample/proguard-rules.pro b/examples/gridViewExample/proguard-rules.pro new file mode 100644 index 0000000000..ca55feb449 --- /dev/null +++ b/examples/gridViewExample/proguard-rules.pro @@ -0,0 +1 @@ +-keep class io.realm.examples.realmgridview.City { ; } \ No newline at end of file diff --git a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/City.java b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/City.java index f0aec3c61c..020f0c422b 100644 --- a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/City.java +++ b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/City.java @@ -19,7 +19,8 @@ import io.realm.RealmObject; public class City extends RealmObject { - + // If you are using GSON, field names should not be obfuscated. + // Add either the proguard rule in proguard-rules.pro or the @SerializedName annotation. private String name; private long votes; From e3ef68891bb1dd45cf4e09674e1c03fad44b27ee Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 18 Sep 2017 19:02:00 +0800 Subject: [PATCH 0966/2110] No need to call Table.getPrimaryKey in proxy The columnInfo can be used instead which is much faster. --- CHANGELOG.md | 1 + .../processor/RealmProxyClassGenerator.java | 16 ++++++++++------ .../resources/io/realm/AllTypesRealmProxy.java | 14 ++++++++------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54dd039905..cd6f6deb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ ### Enhancements * [ObjectServer] `SyncUserInfo` now also exposes a users metadata using `SyncUserInfo.getMetadata()` +* Minor performance improvement when copy/insert objects into Realm. ### Bug Fixes diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 0cce92e5e9..9bfd5a7845 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -788,7 +788,9 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { .emitStatement("boolean canUpdate = update") .beginControlFlow("if (canUpdate)") .emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName) - .emitStatement("long pkColumnIndex = table.getPrimaryKey()"); + .emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", + columnInfoClassName(), columnInfoClassName(), qualifiedClassName) + .emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); String primaryKeyGetter = metadata.getPrimaryKeyGetter(); VariableElement primaryKeyElement = metadata.getPrimaryKey(); @@ -979,7 +981,7 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { columnInfoClassName(), columnInfoClassName(), qualifiedClassName); if (metadata.hasPrimaryKey()) { - writer.emitStatement("long pkColumnIndex = table.getPrimaryKey()"); + writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); } addPrimaryKeyCheckIfNeeded(metadata, true, writer); @@ -1046,7 +1048,7 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedClassName); if (metadata.hasPrimaryKey()) { - writer.emitStatement("long pkColumnIndex = table.getPrimaryKey()"); + writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); } writer.emitStatement("%s object = null", qualifiedClassName); @@ -1133,7 +1135,7 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { columnInfoClassName(), columnInfoClassName(), qualifiedClassName); if (metadata.hasPrimaryKey()) { - writer.emitStatement("long pkColumnIndex = table.getPrimaryKey()"); + writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); } addPrimaryKeyCheckIfNeeded(metadata, false, writer); @@ -1205,7 +1207,7 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedClassName); if (metadata.hasPrimaryKey()) { - writer.emitStatement("long pkColumnIndex = table.getPrimaryKey()"); + writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); } writer.emitStatement("%s object = null", qualifiedClassName); @@ -1711,7 +1713,9 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep .emitStatement("%s obj = null", qualifiedClassName) .beginControlFlow("if (update)") .emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName) - .emitStatement("long pkColumnIndex = table.getPrimaryKey()") + .emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", + columnInfoClassName(), columnInfoClassName(), qualifiedClassName) + .emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())) .emitStatement("long rowIndex = Table.NO_MATCH"); if (metadata.isNullable(metadata.getPrimaryKey())) { writer diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 8893573be5..994bef3d5d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -457,7 +457,8 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON some.test.AllTypes obj = null; if (update) { Table table = realm.getTable(some.test.AllTypes.class); - long pkColumnIndex = table.getPrimaryKey(); + AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); + long pkColumnIndex = columnInfo.columnStringIndex; long rowIndex = Table.NO_MATCH; if (json.isNull("columnString")) { rowIndex = table.findFirstNull(pkColumnIndex); @@ -692,7 +693,8 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob boolean canUpdate = update; if (canUpdate) { Table table = realm.getTable(some.test.AllTypes.class); - long pkColumnIndex = table.getPrimaryKey(); + AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); + long pkColumnIndex = columnInfo.columnStringIndex; String value = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); long rowIndex = Table.NO_MATCH; if (value == null) { @@ -775,7 +777,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M Table table = realm.getTable(some.test.AllTypes.class); long tableNativePtr = table.getNativePtr(); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); - long pkColumnIndex = table.getPrimaryKey(); + long pkColumnIndex = columnInfo.columnStringIndex; some.test.AllTypes object = null; while (objects.hasNext()) { object = (some.test.AllTypes) objects.next(); @@ -904,7 +906,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob Table table = realm.getTable(some.test.AllTypes.class); long tableNativePtr = table.getNativePtr(); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); - long pkColumnIndex = table.getPrimaryKey(); + long pkColumnIndex = columnInfo.columnStringIndex; some.test.AllTypes object = null; while (objects.hasNext()) { object = (some.test.AllTypes) objects.next(); From c503efae303a61a09d6f104dd0d301232c3b77be Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 20 Sep 2017 12:17:20 +0200 Subject: [PATCH 0967/2110] Upgrade to Sync-RC21 and ROS 2.0.0-alpha.34 (#5277) --- Jenkinsfile | 2 +- dependencies.list | 6 +- realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/util.cpp | 3 + .../realm/exceptions/RealmFileException.java | 9 ++- .../java/io/realm/internal/SharedRealm.java | 1 + .../objectServer/java/io/realm/SyncUser.java | 40 ++++++++---- .../internal/network/AuthServerResponse.java | 9 ++- .../network/ChangePasswordRequest.java | 5 +- .../network/OkHttpAuthenticationServer.java | 61 +++++++++++-------- .../java/io/realm/objectserver/AuthTests.java | 44 ++++++++----- .../EncryptedSynchronizedRealmTests.java | 27 ++------ .../realm/objectserver/utils/UserFactory.java | 36 ++--------- tools/sync_test_server/ros-testing-server.js | 22 ++++--- 14 files changed, 143 insertions(+), 124 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index dc74785a4d..19aa5a9e43 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,7 +6,7 @@ def buildSuccess = false def rosContainer try { node('android') { - timeout(time: 1, unit: 'HOURS') { + timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { stage('SCM') { diff --git a/dependencies.list b/dependencies.list index 1398418c5d..91cfac3223 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.0.0-rc18 -REALM_SYNC_SHA256=73cb89c1a04cafa871444aa91d93eb9df16af088bcb7d3ede73bb815d53a06e5 +REALM_SYNC_VERSION=2.0.0-rc21 +REALM_SYNC_SHA256=5e09e54e68e78683e006898f5a703f80e0ee49492fb0f9dc2384fcbbb9f02f70 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.30 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.34 diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 6f7804a233..cdd0d8c82b 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 6f7804a2332732e0c2d2db6bf920a38c75e72fb2 +Subproject commit cdd0d8c82ba6dfb60ebc2c339b26b7b3ca4d4047 diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index b7e157cdac..25c02994e0 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -224,6 +224,9 @@ void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::Rea case realm::RealmFileException::Kind::FormatUpgradeRequired: kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED; break; + case realm::RealmFileException::Kind::IncompatibleSyncedRealm: + kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_INCOMPATIBLE_SYNC_FILE; + break; } jstring jstr = env->NewStringUTF(message.c_str()); jobject exception = env->NewObject(cls, constructor, kind_code, jstr); diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java index 07e6b86453..716c0266c6 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java @@ -60,7 +60,12 @@ public enum Kind { /** * Thrown if the file needs to be upgraded to a new format, but upgrades have been explicitly disabled. */ - FORMAT_UPGRADE_REQUIRED; + FORMAT_UPGRADE_REQUIRED, + /** + * Thrown if an attempt was made to open an Realm file created with Realm Object Server 1.*, which is + * not compatible with Realm Object Server 2.*. This exception should automatically be handled by Realm. + */ + INCOMPATIBLE_SYNC_FILE; // Created from byte values by JNI. static Kind getKind(byte value) { @@ -79,6 +84,8 @@ static Kind getKind(byte value) { return FORMAT_UPGRADE_REQUIRED; case SharedRealm.FILE_EXCEPTION_KIND_BAD_HISTORY: return BAD_HISTORY; + case SharedRealm.FILE_EXCEPTION_INCOMPATIBLE_SYNC_FILE: + return INCOMPATIBLE_SYNC_FILE; default: throw new RuntimeException("Unknown value for RealmFileException kind."); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index c2a81e4f34..e2308c96ad 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -41,6 +41,7 @@ public final class SharedRealm implements Closeable, NativeObject { public static final byte FILE_EXCEPTION_KIND_NOT_FOUND = 4; public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 5; public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 6; + public static final byte FILE_EXCEPTION_INCOMPATIBLE_SYNC_FILE = 7; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); public static void initialize(File tempDirectory) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 429e2e61c8..23a60d532a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -274,7 +274,31 @@ public void logout() { // the similar SyncConfiguration using the same identity, but with different (new) // refresh-token. realms.clear(); - SyncManager.notifyUserLoggedOut(SyncUser.this); + + // Finally revoke server token. The local user is logged out in any case. + final AuthenticationServer server = SyncManager.getAuthServer(); + // don't reference directly the refreshToken inside the revoke request + // as it may revoke the newly acquired refresh_token + final Token refreshTokenToBeRevoked = refreshToken; + + ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; + networkPoolExecutor.submit(new ExponentialBackoffTask() { + + @Override + protected LogoutResponse execute() { + return server.logout(refreshTokenToBeRevoked, getAuthenticationUrl()); + } + + @Override + protected void onSuccess(LogoutResponse response) { + SyncManager.notifyUserLoggedOut(SyncUser.this); + } + + @Override + protected void onError(LogoutResponse response) { + RealmLog.error("Failed to log user out.\n" + response.getError().toString()); + } + }); } } @@ -503,16 +527,10 @@ public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final AuthenticationServer authServer = SyncManager.getAuthServer(); LookupUserIdResponse response = authServer.retrieveUser(refreshToken, provider, providerUserIdentity, getAuthenticationUrl()); if (!response.isValid()) { - // the endpoint returns a 404 if it can't honor the query, either because - // - provider is not valid - // - provider_id is not valid - // - token used is not an admin one - // in this case we should return null instead of throwing - if (response.getError().getErrorCode() == ErrorCode.NOT_FOUND) { - return null; - } else { - throw response.getError(); - } + // Right now errors are very inconsistent. See https://github.com/realm/ros/issues/310 + // Treat them all as "User not existing". This is too broad, and should be revisited + // once #310 is fixed. + return null; } else { return SyncUserInfo.fromLookupUserIdResponse(response); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java index c6d2c47045..4285b565a6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java @@ -63,7 +63,14 @@ public static ObjectServerError createError(String response, int httpErrorCode) JSONObject obj = new JSONObject(response); String title = obj.optString("title", null); String hint = obj.optString("hint", null); - ErrorCode errorCode = ErrorCode.fromInt(obj.optInt("code", -1)); + ErrorCode errorCode; + if (obj.has("code")) { + errorCode = ErrorCode.fromInt(obj.getInt("code")); + } else if (obj.has("status")) { + errorCode = ErrorCode.fromInt(obj.getInt("status")); + } else { + errorCode = ErrorCode.UNKNOWN; + } return new ObjectServerError(errorCode, title, hint); } catch (JSONException e) { return new ObjectServerError(ErrorCode.JSON_EXCEPTION, "Server failed with " + diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java index 8e22c09704..6ca1f3aef1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java @@ -56,10 +56,9 @@ private ChangePasswordRequest(String token, String newPassword, String userID) { public String toJson() { try { JSONObject request = new JSONObject(); - request.put("token", token); - request.put("password", newPassword); + request.put("newPassword", newPassword); if (userID != null) { - request.put("user_id", userID); + request.put("userId", userID); } return request.toString(); } catch (JSONException e) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 997f59cea7..88a6fcfd20 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -22,6 +22,7 @@ import java.util.concurrent.TimeUnit; import io.realm.SyncCredentials; +import io.realm.internal.Util; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; import okhttp3.Call; @@ -36,8 +37,8 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final String ACTION_LOGOUT = "revoke"; // Auth end point for logging out users - private static final String ACTION_CHANGE_PASSWORD = "users/:userId:/password"; // Auth end point for changing passwords - private static final String ACTION_LOOKUP_USER_ID = "users"; // Auth end point for looking up user id + private static final String ACTION_CHANGE_PASSWORD = "password"; // Auth end point for changing passwords + private static final String ACTION_LOOKUP_USER_ID = "/users/:provider:/:providerId:"; // Auth end point for looking up user id private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) @@ -86,7 +87,7 @@ public AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL auth public LogoutResponse logout(Token userToken, URL authenticationUrl) { try { String requestBody = LogoutRequest.create(userToken).toJson(); - return logout(buildActionUrl(authenticationUrl, ACTION_LOGOUT), requestBody); + return logout(buildActionUrl(authenticationUrl, ACTION_LOGOUT), userToken.value(), requestBody); } catch (Exception e) { return LogoutResponse.from(e); } @@ -96,7 +97,7 @@ public LogoutResponse logout(Token userToken, URL authenticationUrl) { public ChangePasswordResponse changePassword(Token userToken, String newPassword, URL authenticationUrl) { try { String requestBody = ChangePasswordRequest.create(userToken, newPassword).toJson(); - return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD), requestBody); + return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD), userToken.value(), requestBody); } catch (Exception e) { return ChangePasswordResponse.from(e); } @@ -106,7 +107,7 @@ public ChangePasswordResponse changePassword(Token userToken, String newPassword public ChangePasswordResponse changePassword(Token adminToken, String userId, String newPassword, URL authenticationUrl) { try { String requestBody = ChangePasswordRequest.create(adminToken, userId, newPassword).toJson(); - return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD.replace(":userId:", userId)), requestBody); + return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD), adminToken.value(), requestBody); } catch (Exception e) { return ChangePasswordResponse.from(e); } @@ -115,7 +116,10 @@ public ChangePasswordResponse changePassword(Token adminToken, String userId, St @Override public LookupUserIdResponse retrieveUser(Token adminToken, String provider, String providerId, URL authenticationUrl) { try { - return lookupUserId(buildLookupUserIdUrl(authenticationUrl, ACTION_LOOKUP_USER_ID, provider, providerId), adminToken.value()); + String action = ACTION_LOOKUP_USER_ID + .replace(":provider:", provider) + .replace(":providerId:", providerId); + return lookupUserId(buildActionUrl(authenticationUrl, action), adminToken.value()); } catch (Exception e) { return LookupUserIdResponse.from(e); } @@ -132,53 +136,62 @@ private static URL buildActionUrl(URL authenticationUrl, String action) { } } - private static URL buildLookupUserIdUrl(URL authenticationUrl, String action, String provider, String providerId) { - String authURL = authenticationUrl.toExternalForm(); - String separator = authURL.endsWith("/") ? "" : "/"; - try { - return new URL(authURL + separator + action + "/" + providerId); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - private AuthenticateResponse authenticate(URL authenticationUrl, String requestBody) throws Exception { RealmLog.debug("Network request (authenticate): " + authenticationUrl); - Request request = newAuthRequest(authenticationUrl).post(RequestBody.create(JSON, requestBody)).build(); + Request request = newAuthRequest(authenticationUrl) + .post(RequestBody.create(JSON, requestBody)) + .build(); Call call = client.newCall(request); Response response = call.execute(); return AuthenticateResponse.from(response); } - private LogoutResponse logout(URL logoutUrl, String requestBody) throws Exception { + private LogoutResponse logout(URL logoutUrl, String authToken, String requestBody) throws Exception { RealmLog.debug("Network request (logout): " + logoutUrl); - Request request = newAuthRequest(logoutUrl).post(RequestBody.create(JSON, requestBody)).build(); + Request request = newAuthRequest(logoutUrl, authToken) + .post(RequestBody.create(JSON, requestBody)) + .build(); Call call = client.newCall(request); Response response = call.execute(); return LogoutResponse.from(response); } - private ChangePasswordResponse changePassword(URL changePasswordUrl, String requestBody) throws Exception { + private ChangePasswordResponse changePassword(URL changePasswordUrl, String authToken, String requestBody) throws Exception { RealmLog.debug("Network request (changePassword): " + changePasswordUrl); - Request request = newAuthRequest(changePasswordUrl).put(RequestBody.create(JSON, requestBody)).build(); + Request request = newAuthRequest(changePasswordUrl, authToken) + .put(RequestBody.create(JSON, requestBody)) + .build(); Call call = client.newCall(request); Response response = call.execute(); return ChangePasswordResponse.from(response); } - private LookupUserIdResponse lookupUserId(URL lookupUserIdUrl, String token) throws Exception { + private LookupUserIdResponse lookupUserId(URL lookupUserIdUrl, String authToken) throws Exception { RealmLog.debug("Network request (lookupUserId): " + lookupUserIdUrl); - Request request = newAuthRequest(lookupUserIdUrl).get().header("Authorization", token).build(); + Request request = newAuthRequest(lookupUserIdUrl, authToken) + .get() + .build(); Call call = client.newCall(request); Response response = call.execute(); return LookupUserIdResponse.from(response); } private Request.Builder newAuthRequest(URL url) { - return new Request.Builder() + return newAuthRequest(url, null); + } + + private Request.Builder newAuthRequest(URL url, String authToken) { + Request.Builder builder = new Request.Builder() .url(url) .addHeader("Content-Type", "application/json") .addHeader("Accept", "application/json"); + + // Only add Authorization header for those API's that require it. + if (!Util.isEmptyString(authToken)) { + builder.addHeader("Authorization", authToken); + } + + return builder; } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 9bd027cd60..9b006ac45f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -32,6 +32,7 @@ import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.SyncUserInfo; +import io.realm.TestHelper; import io.realm.entities.StringOnly; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.objectserver.Token; @@ -90,7 +91,8 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread public void login_newUser() { - SyncCredentials credentials = SyncCredentials.usernamePassword("myUser", "password", true); + String userId = UUID.randomUUID().toString(); + SyncCredentials credentials = SyncCredentials.usernamePassword(userId, "password", true); SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { @@ -194,7 +196,6 @@ public void onError(ObjectServerError error) { } @Test - @Ignore("Resolve https://github.com/realm/ros/issues/273") public void changePassword() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; @@ -214,7 +215,6 @@ public void changePassword() { } @Test - @Ignore("Resolve https://github.com/realm/ros/issues/273") public void changePassword_using_admin() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; @@ -242,7 +242,6 @@ public void changePassword_using_admin() { @Test @RunTestInLooperThread - @Ignore("Resolve https://github.com/realm/ros/issues/273") public void changePassword_using_admin_async() { final String username = UUID.randomUUID().toString(); final String originalPassword = "password"; @@ -282,6 +281,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread + @Ignore("Wait until https://github.com/realm/ros/issues/309 is resolved") public void changePassword_throwWhenUserIsLoggedOut() { String username = UUID.randomUUID().toString(); String password = "password"; @@ -398,7 +398,6 @@ public void usingConfigurationWithInvalidUserShouldThrow() { RealmConfiguration configuration = new SyncConfiguration.Builder(user, Constants.USER_REALM).build(); user.logout(); assertFalse(user.isValid()); - Realm instance = Realm.getInstance(configuration); instance.close(); } @@ -500,23 +499,38 @@ public void singleUserCanBeLoggedInAndOutRepeatedly() { } @Test - @Ignore("Resolve https://github.com/realm/ros/issues/261") public void revokedRefreshTokenIsNotSameAfterLogin() throws InterruptedException { + final CountDownLatch userLoggedInAgain = new CountDownLatch(1); final String uniqueName = UUID.randomUUID().toString(); - SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); + final SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); - Token revokedRefreshToken = user.getAccessToken(); + final Token revokedRefreshToken = user.getAccessToken(); - user.logout(); + SyncManager.addAuthenticationListener(new AuthenticationListener() { + @Override + public void loggedIn(SyncUser user) { - credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); - SyncUser loggedInUser = SyncUser.login(credentials, Constants.AUTH_URL); + } - // still comparing the same user - Assert.assertEquals(revokedRefreshToken.identity(), loggedInUser.getAccessToken().identity()); - // different tokens - assertNotEquals(revokedRefreshToken.value(), loggedInUser.getAccessToken().value()); + @Override + public void loggedOut(SyncUser user) { + SystemClock.sleep(1000); // Remove once https://github.com/realm/ros/issues/304 is fixed + SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); + SyncUser loggedInUser = SyncUser.login(credentials, Constants.AUTH_URL); + + // still comparing the same user + assertEquals(revokedRefreshToken.identity(), loggedInUser.getAccessToken().identity()); + + // different tokens + assertNotEquals(revokedRefreshToken.value(), loggedInUser.getAccessToken().value()); + SyncManager.removeAuthenticationListener(this); + userLoggedInAgain.countDown(); + } + }); + + user.logout(); + TestHelper.awaitOrFail(userLoggedInAgain); } // The pre-emptive token refresh subsystem should function, and properly refresh the access token. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 50d7ed8b40..7806b4158b 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -1,18 +1,14 @@ package io.realm.objectserver; import android.os.SystemClock; -import android.text.style.TabStopSpan; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; import java.util.UUID; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import io.realm.BaseIntegrationTest; import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmResults; @@ -24,7 +20,6 @@ import io.realm.SyncUser; import io.realm.TestHelper; import io.realm.entities.StringOnly; -import io.realm.exceptions.RealmError; import io.realm.exceptions.RealmFileException; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; @@ -130,19 +125,6 @@ public void onError(SyncSession session, ObjectServerError error) { // STEP 2: make sure the changes gets to the server SyncManager.getSession(configWithEncryption).uploadAllLocalChanges(); - final CountDownLatch backgroundException = new CountDownLatch(1); - final AtomicBoolean exceptionThrown = new AtomicBoolean(false); - - Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - if (e instanceof RealmError && e.getMessage().contains("An exception has been thrown on the sync client thread")) { - exceptionThrown.set(true); - } - backgroundException.countDown(); - } - }); realm.close(); user.logout(); @@ -163,12 +145,11 @@ public void onError(SyncSession session, ObjectServerError error) { realm = Realm.getInstance(configWithoutEncryption); fail("It should not be possible to open the Realm without the encryption key set previously."); } catch (RealmFileException ignored) { + } finally { + if (realm != null) { + realm.close(); + } } - - TestHelper.awaitOrFail(backgroundException); - // restore default handler - Thread.setDefaultUncaughtExceptionHandler(defaultUncaughtExceptionHandler); - assertTrue("Sync Client Thread should throw an exception", exceptionThrown.get()); } // If client B encrypts its synced Realm, client A should be able to access that Realm with a different encryption key. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index a1d325d687..beb5c0cb06 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -161,40 +161,12 @@ public static void logoutAllUsers() { handler.post(new Runnable() { @Override public void run() { - final AtomicInteger usersLoggedOut = new AtomicInteger(0); - final int activeUsers = SyncUser.all().size(); - final AuthenticationListener listener = new AuthenticationListener() { - @Override - public void loggedIn(SyncUser user) { - SyncManager.removeAuthenticationListener(this); - fail("User logged in while exiting test: " + user); - } - - @Override - public void loggedOut(SyncUser user) { - if (usersLoggedOut.incrementAndGet() == activeUsers) { - SyncManager.removeAuthenticationListener(this); - allUsersLoggedOut.countDown(); - } - } - }; - SyncManager.addAuthenticationListener(listener); - Map users = SyncUser.all(); - if (users.isEmpty()) { - SyncManager.removeAuthenticationListener(listener); - allUsersLoggedOut.countDown(); - } else { - for (SyncUser user : users.values()) { - user.logout(); - if (!user.getAuthenticationUrl().toString().contains("127.0.0.1")) { - // For dummy users, calling `logout()` will never result in the - // authentication listener to trigger since the URL doesn't exist. - // For these cases, we manually trigger the listener. - listener.loggedOut(user); - } - } + for (SyncUser user : users.values()) { + user.logout(); } + SystemClock.sleep(2000); // Remove when https://github.com/realm/ros/issues/304 is fixed + allUsersLoggedOut.countDown(); } }); TestHelper.awaitOrFail(allUsersLoggedOut); diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index 384c54984e..0e46a056d8 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -42,7 +42,7 @@ function waitForRosToInitialize(attempts, onSuccess, onError) { http.get("http://0.0.0.0:9080/health", function(res) { if (res.statusCode != 200) { winston.info("ROS /health/ returned: " + res.statusCode) - waitForRosToInitialize(attempts - 1,onSuccess) + waitForRosToInitialize(attempts - 1, onSuccess, onError) } else { onSuccess(); } @@ -51,7 +51,7 @@ function waitForRosToInitialize(attempts, onSuccess, onError) { // Errors like ECONNREFUSED 0.0.0.0:9080 will be reported here. // Wait a little before trying again (common startup is ~1 second). setTimeout(function() { - waitForRosToInitialize(attempts - 1, onSuccess); + waitForRosToInitialize(attempts - 1, onSuccess, onError); }, 200); }); } @@ -64,7 +64,10 @@ function startRealmObjectServer(onSuccess, onError) { winston.info(env.NODE_ENV); env.NODE_ENV = 'development'; syncServerChildProcess = spawn('ros', - ['start', '--data', path], + ['start', + '--data', path, + '--access-token-ttl', '20' //WARNING : Changing this value may impact the timeout of the refresh token test (AuthTests#preemptiveTokenRefresh) + ], { env: env, cwd: path}); // local config: @@ -93,8 +96,6 @@ function stopRealmObjectServer(onSuccess, onError) { onSuccess(); }); - // Move back to `SIGTERM` once https://github.com/realm/ros/issues/234 - // is resolved syncServerChildProcess.kill('SIGKILL'); } @@ -103,10 +104,10 @@ dispatcher.onGet("/start", function(req, res) { winston.info("Attempting to start ROS"); startRealmObjectServer(() => { res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('ROS server started'); + res.end('ROS started'); }, function (err) { res.writeHead(500, {'Content-Type': 'text/plain'}); - res.end('Starting a ROS server failed: ' + err); + res.end('Starting ROS failed: ' + err); }); }); @@ -114,8 +115,11 @@ dispatcher.onGet("/start", function(req, res) { dispatcher.onGet("/stop", function(req, res) { winston.info("Attempting to stop ROS") stopRealmObjectServer(function() { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('ROS server stopped'); + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('ROS stopped'); + }, function(err) { + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end('Stopping ROS failed: ' + err); }); }); From d5210b389f36c272ff7e845b9eebf251804fe2ab Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 20 Sep 2017 20:34:11 +0100 Subject: [PATCH 0968/2110] Disable flaky test until 5294 is fixed (#5295) --- .../java/io/realm/objectserver/SyncSessionTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java index 889d09897d..a51e9b97d1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java @@ -7,6 +7,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -381,6 +382,7 @@ public void onChange(RealmResults stringOnlies) { // A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. @Test + @Ignore("until https://github.com/realm/realm-java/issues/5294 is fixed") public void downloadChangesWhenRealmOutOfScope() throws InterruptedException { final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); From 1ea2d473a2f4f5e89a3b9e16b68a853d09b110b4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 20 Sep 2017 21:35:11 +0200 Subject: [PATCH 0969/2110] Minimize overhead on CI for PR builds. (#5287) For PR's * Only build for armeabi-v7a * Only run tests on the ObjectServer variant --- Jenkinsfile | 192 ++++++++++++++++++++++++++++------------------------ 1 file changed, 102 insertions(+), 90 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 19aa5a9e43..84261d4ceb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,103 +9,115 @@ try { timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { - stage('SCM') { - checkout([ - $class: 'GitSCM', - branches: scm.branches, - gitTool: 'native git', - extensions: scm.extensions + [ - [$class: 'CleanCheckout'], - [$class: 'SubmoduleOption', recursiveSubmodules: true] - ], - userRemoteConfigs: scm.userRemoteConfigs - ]) - } - - def buildEnv - def rosEnv - stage('Docker build') { - // Docker image for build - buildEnv = docker.build 'realm-java:snapshot' - // Docker image for testing Realm Object Server - def dependProperties = readProperties file: 'dependencies.list' - def rosDeVersion = dependProperties["REALM_OBJECT_SERVER_DE_VERSION"] - rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_DE_VERSION=${rosDeVersion} tools/sync_test_server" - } - - rosContainer = rosEnv.run('-v /tmp=/tmp/.ros') - - try { - buildEnv.inside("-e HOME=/tmp " + - "-e _JAVA_OPTIONS=-Duser.home=/tmp " + - "--privileged " + - "-v /dev/bus/usb:/dev/bus/usb " + - "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + - "-v ${env.HOME}/.android:/tmp/.android " + - "-v ${env.HOME}/ccache:/tmp/.ccache " + - "--network container:${rosContainer.id}") { - stage('JVM tests') { - try { - withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG}" + stage('SCM') { + checkout([ + $class: 'GitSCM', + branches: scm.branches, + gitTool: 'native git', + extensions: scm.extensions + [ + [$class: 'CleanCheckout'], + [$class: 'SubmoduleOption', recursiveSubmodules: true] + ], + userRemoteConfigs: scm.userRemoteConfigs + ]) + } + + // Toggles for PR vs. Master builds. + // For PR's, we just build for arm-v7a and run unit tests for the ObjectServer variant + // A full build is done on `master`. + // TODO Once Android emulators are available on all nodes, we can switch to x86 builds + // on PR's for even more throughput. + def ABIs = "" + def instrumentationTestTarget = "connectedAndroidTest" + if (!['master'].contains(env.BRANCH_NAME)) { + ABIs = "armeabi-v7a" + instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting + } + + def buildEnv + def rosEnv + stage('Docker build') { + // Docker image for build + buildEnv = docker.build 'realm-java:snapshot' + // Docker image for testing Realm Object Server + def dependProperties = readProperties file: 'dependencies.list' + def rosDeVersion = dependProperties["REALM_OBJECT_SERVER_DE_VERSION"] + rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_DE_VERSION=${rosDeVersion} tools/sync_test_server" + } + + rosContainer = rosEnv.run('-v /tmp=/tmp/.ros') + + try { + buildEnv.inside("-e HOME=/tmp " + + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + + "--privileged " + + "-v /dev/bus/usb:/dev/bus/usb " + + "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + + "-v ${env.HOME}/.android:/tmp/.android " + + "-v ${env.HOME}/ccache:/tmp/.ccache " + + "--network container:${rosContainer.id}") { + stage('JVM tests') { + try { + withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { + sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} -PbuildTargetABIs=${ABIs}" + } + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) + } } - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) - } - } - - stage('Static code analysis') { - try { - gradle('realm', 'findbugs pmd checkstyle') - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) - step([$class: 'CheckStylePublisher', - canComputeNew: false, - defaultEncoding: '', - healthy: '', - pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - unHealthy: '' - ]) - } - } - - stage('Run instrumented tests') { - lock("${env.NODE_NAME}-android") { - boolean archiveLog = true - String backgroundPid - try { - backgroundPid = startLogCatCollector() - forwardAdbPorts() - gradle('realm', 'connectedAndroidTest') - archiveLog = false; - } finally { - stopLogCatCollector(backgroundPid, archiveLog) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + + stage('Static code analysis') { + try { + gradle('realm', 'findbugs pmd checkstyle') + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) + step([$class: 'CheckStylePublisher', + canComputeNew: false, + defaultEncoding: '', + healthy: '', + pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', + unHealthy: '' + ]) + } } - } - } - // TODO: add support for running monkey on the example apps + stage('Run instrumented tests') { + lock("${env.NODE_NAME}-android") { + boolean archiveLog = true + String backgroundPid + try { + backgroundPid = startLogCatCollector() + forwardAdbPorts() + gradle('realm', "${instrumentationTestTarget}") + archiveLog = false; + } finally { + stopLogCatCollector(backgroundPid, archiveLog) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } + } + } - if (env.BRANCH_NAME == 'master') { - stage('Collect metrics') { - collectAarMetrics() - } + // TODO: add support for running monkey on the example apps + + if (env.BRANCH_NAME == 'master') { + stage('Collect metrics') { + collectAarMetrics() + } - stage('Publish to OJO') { - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + stage('Publish to OJO') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + } + } } } - } - } - } finally { - sh "docker logs ${rosContainer.id}" - rosContainer.stop() - } + } finally { + sh "docker logs ${rosContainer.id}" + rosContainer.stop() + } } } currentBuild.rawBuild.setResult(Result.SUCCESS) From 6536877ccfe167fb196ef3faed5105bc2ad57a2b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 20 Sep 2017 23:19:33 +0100 Subject: [PATCH 0970/2110] Client reset fixes #4759 (#5159) * Exposing a `SyncConfiguration` that allows a user to open the backup Realm after the client reset (#4759). --- CHANGELOG.md | 1 + .../java/io/realm/SessionTests.java | 222 +++++++++++++++++- .../java/io/realm/RealmConfiguration.java | 44 ++-- .../java/io/realm/internal/OsRealmConfig.java | 3 +- .../java/io/realm/internal/SharedRealm.java | 1 - .../io/realm/ClientResetRequiredError.java | 22 +- .../java/io/realm/SyncConfiguration.java | 49 +++- .../java/io/realm/SyncSession.java | 3 +- .../java/io/realm/SyncedRealmTests.java | 1 - .../java/io/realm/objectserver/AuthTests.java | 4 +- .../objectserver/ProgressListenerTests.java | 6 +- 11 files changed, 316 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd6f6deb72..4eb2c28380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ ### Bug Fixes * Throw `IllegalArgumentException` instead of `IllegalStateException` when calling string/binary data setters if the data length exceeds the limit. +* Exposing a `RealmConfiguration` that allows a user to open the backup Realm after the client reset (#4759). ### Internal diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 5dc2af0691..c011fa563a 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -28,6 +28,10 @@ import java.util.concurrent.atomic.AtomicReference; +import io.realm.entities.StringOnly; +import io.realm.exceptions.RealmFileException; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestSyncConfigurationFactory; @@ -35,6 +39,7 @@ import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -123,7 +128,7 @@ public void onChange(Progress progress) { public void errorHandler_clientResetReported() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user , url) + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { @@ -138,6 +143,7 @@ public void onError(SyncSession session, ObjectServerError error) { assertEquals(filePathFromError, filePathFromConfig); assertFalse(handler.getBackupFile().exists()); assertTrue(handler.getOriginalFile().exists()); + looperThread.testComplete(); } }) @@ -156,7 +162,7 @@ public void onError(SyncSession session, ObjectServerError error) { public void errorHandler_manualExecuteClientReset() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user , url) + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { @@ -241,13 +247,221 @@ public void run() { SyncManager.simulateClientReset(SyncManager.getSession(config)); } + // Check that we can use the backup SyncConfiguration to open the Realm. + @Test + @RunTestInLooperThread + public void errorHandler_useBackupSyncConfigurationForClientReset() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; + } + + final ClientResetRequiredError handler = (ClientResetRequiredError) error; + // Execute Client Reset + looperThread.closeTestRealms(); + handler.executeClientReset(); + + // Validate that files have been moved + assertFalse(handler.getOriginalFile().exists()); + assertTrue(handler.getBackupFile().exists()); + + RealmConfiguration backupRealmConfiguration = handler.getBackupRealmConfiguration(); + assertNotNull(backupRealmConfiguration); + assertFalse(backupRealmConfiguration.isSyncConfiguration()); + + Realm backupRealm = Realm.getInstance(backupRealmConfiguration); + assertFalse(backupRealm.isEmpty()); + assertEquals(1, backupRealm.where(StringOnly.class).count()); + assertEquals("Foo", backupRealm.where(StringOnly.class).findAll().first().getChars()); + backupRealm.close(); + + // opening a Dynamic Realm should also work + DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); + dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); + RealmResults all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); + assertEquals(1, all.size()); + assertEquals("Foo", all.first().getString(StringOnly.FIELD_CHARS)); + dynamicRealm.close(); + looperThread.testComplete(); + } + }) + .modules(new StringOnlyModule()) + .build(); + + Realm realm = Realm.getInstance(config); + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Foo"); + realm.commitTransaction(); + + looperThread.addTestRealm(realm); + + // Trigger error + SyncManager.simulateClientReset(SyncManager.getSession(config)); + } + + // Check that we can open the backup file without using the provided SyncConfiguration, + // this might be the case if the user decide to act upon the client reset later (providing s/he + // persisted the location of the file) + @Test + @RunTestInLooperThread + public void errorHandler_useBackupSyncConfigurationAfterClientReset() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; + } + + final ClientResetRequiredError handler = (ClientResetRequiredError) error; + // Execute Client Reset + looperThread.closeTestRealms(); + handler.executeClientReset(); + + // Validate that files have been moved + assertFalse(handler.getOriginalFile().exists()); + assertTrue(handler.getBackupFile().exists()); + + String backupFile = handler.getBackupFile().getAbsolutePath(); + + // this SyncConf doesn't specify any module, it will throw a migration required + // exception since the backup Realm contain only StringOnly table + RealmConfiguration backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile); + + try { + Realm.getInstance(backupRealmConfiguration); + fail("Expected to throw a Migration required"); + } catch (RealmMigrationNeededException expected) { + } + + // opening a DynamicRealm will work though + DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); + + dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); + RealmResults all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); + assertEquals(1, all.size()); + assertEquals("Foo", all.first().getString(StringOnly.FIELD_CHARS)); + + // make sure we can't write to it (read-only Realm) + try { + dynamicRealm.beginTransaction(); + fail("Can't perform transactions on read-only Realms"); + } catch (IllegalStateException expected) { + } + dynamicRealm.close(); + + try { + SyncConfiguration.forRecovery(backupFile, null, StringOnly.class); + fail("Expected to throw java.lang.Class is not a RealmModule"); + } catch (IllegalArgumentException expected) { + } + + // specifying the module will allow to open the typed Realm + backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, null, new StringOnlyModule()); + Realm backupRealm = Realm.getInstance(backupRealmConfiguration); + assertFalse(backupRealm.isEmpty()); + assertEquals(1, backupRealm.where(StringOnly.class).count()); + RealmResults allSorted = backupRealm.where(StringOnly.class).findAll(); + assertEquals("Foo", allSorted.get(0).getChars()); + backupRealm.close(); + + looperThread.testComplete(); + } + }) + .modules(new StringOnlyModule()) + .build(); + + Realm realm = Realm.getInstance(config); + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Foo"); + realm.commitTransaction(); + + looperThread.addTestRealm(realm); + + // Trigger error + SyncManager.simulateClientReset(SyncManager.getSession(config)); + } + + // make sure the backup file Realm is encrypted with the same key as the original synced Realm. + @Test + @RunTestInLooperThread + public void errorHandler_useClientResetEncrypted() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + final byte[] randomKey = TestHelper.getRandomKey(); + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + .encryptionKey(randomKey) + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; + } + + final ClientResetRequiredError handler = (ClientResetRequiredError) error; + // Execute Client Reset + looperThread.closeTestRealms(); + handler.executeClientReset(); + + RealmConfiguration backupRealmConfiguration = handler.getBackupRealmConfiguration(); + + // can open encrypted backup Realm + Realm backupEncryptedRealm = Realm.getInstance(backupRealmConfiguration); + assertEquals(1, backupEncryptedRealm.where(StringOnly.class).count()); + RealmResults allSorted = backupEncryptedRealm.where(StringOnly.class).findAll(); + assertEquals("Foo", allSorted.get(0).getChars()); + backupEncryptedRealm.close(); + + String backupFile = handler.getBackupFile().getAbsolutePath(); + // build a conf to open a DynamicRealm + backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, randomKey, new StringOnlyModule()); + backupEncryptedRealm = Realm.getInstance(backupRealmConfiguration); + assertEquals(1, backupEncryptedRealm.where(StringOnly.class).count()); + allSorted = backupEncryptedRealm.where(StringOnly.class).findAll(); + assertEquals("Foo", allSorted.get(0).getChars()); + backupEncryptedRealm.close(); + + // using wrong key throw + try { + Realm.getInstance(SyncConfiguration.forRecovery(backupFile, TestHelper.getRandomKey(), new StringOnlyModule())); + fail("Expected to throw when using wrong encryption key"); + } catch (RealmFileException expected) { + } + + looperThread.testComplete(); + } + }) + .modules(new StringOnlyModule()) + .build(); + + Realm realm = Realm.getInstance(config); + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Foo"); + realm.commitTransaction(); + + looperThread.addTestRealm(realm); + + // Trigger error + SyncManager.simulateClientReset(SyncManager.getSession(config)); + } + @Test @UiThreadTest public void uploadAllLocalChanges_throwsOnUiThread() throws InterruptedException { - SyncUser user = createTestUser(); Realm realm = Realm.getInstance(configuration); try { SyncManager.getSession(configuration).uploadAllLocalChanges(); + fail("Should throw an IllegalStateException on Ui Thread"); } catch (IllegalStateException ignored) { } finally { realm.close(); @@ -257,10 +471,10 @@ public void uploadAllLocalChanges_throwsOnUiThread() throws InterruptedException @Test @UiThreadTest public void downloadAllServerChanges_throwsOnUiThread() throws InterruptedException { - SyncUser user = createTestUser(); Realm realm = Realm.getInstance(configuration); try { SyncManager.getSession(configuration).downloadAllServerChanges(); + fail("Should throw an IllegalStateException on Ui Thread"); } catch (IllegalStateException ignored) { } finally { realm.close(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index ad52ddfa33..b6546f405a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -104,8 +104,8 @@ public class RealmConfiguration { // We need to enumerate all parameters since SyncConfiguration and RealmConfiguration supports different // subsets of them. - protected RealmConfiguration(File realmDirectory, - String realmFileName, + protected RealmConfiguration(@Nullable File realmDirectory, + @Nullable String realmFileName, String canonicalPath, @Nullable String assetFilePath, @Nullable byte[] key, @@ -275,42 +275,48 @@ public boolean equals(Object obj) { if (schemaVersion != that.schemaVersion) { return false; } if (deleteRealmIfMigrationNeeded != that.deleteRealmIfMigrationNeeded) { return false; } - if (!realmDirectory.equals(that.realmDirectory)) { return false; } - if (!realmFileName.equals(that.realmFileName)) { return false; } + if (readOnly != that.readOnly) { return false; } + if (realmDirectory != null ? !realmDirectory.equals(that.realmDirectory) : that.realmDirectory != null) { + return false; + } + if (realmFileName != null ? !realmFileName.equals(that.realmFileName) : that.realmFileName != null) { + return false; + } if (!canonicalPath.equals(that.canonicalPath)) { return false; } + if (assetFilePath != null ? !assetFilePath.equals(that.assetFilePath) : that.assetFilePath != null) { + return false; + } if (!Arrays.equals(key, that.key)) { return false; } - if (!durability.equals(that.durability)) { return false; } - if (migration != null ? !migration.equals(that.migration) : that.migration != null) { return false; } - //noinspection SimplifiableIfStatement + if (migration != null ? !migration.equals(that.migration) : that.migration != null) { + return false; + } + if (durability != that.durability) { return false; } + if (!schemaMediator.equals(that.schemaMediator)) { return false; } if (rxObservableFactory != null ? !rxObservableFactory.equals(that.rxObservableFactory) : that.rxObservableFactory != null) { return false; } if (initialDataTransaction != null ? !initialDataTransaction.equals(that.initialDataTransaction) : that.initialDataTransaction != null) { return false; } - if (readOnly != that.readOnly) { return false; } - if (compactOnLaunch != null ? !compactOnLaunch.equals(that.compactOnLaunch) : that.compactOnLaunch != null) { return false; } - - return schemaMediator.equals(that.schemaMediator); + return compactOnLaunch != null ? compactOnLaunch.equals(that.compactOnLaunch) : that.compactOnLaunch == null; } - @Override public int hashCode() { - int result = realmDirectory.hashCode(); - result = 31 * result + realmFileName.hashCode(); + int result = realmDirectory != null ? realmDirectory.hashCode() : 0; + result = 31 * result + (realmFileName != null ? realmFileName.hashCode() : 0); result = 31 * result + canonicalPath.hashCode(); - result = 31 * result + (key != null ? Arrays.hashCode(key) : 0); - result = 31 * result + (int) schemaVersion; + result = 31 * result + (assetFilePath != null ? assetFilePath.hashCode() : 0); + result = 31 * result + Arrays.hashCode(key); + result = 31 * result + (int) (schemaVersion ^ (schemaVersion >>> 32)); result = 31 * result + (migration != null ? migration.hashCode() : 0); result = 31 * result + (deleteRealmIfMigrationNeeded ? 1 : 0); - result = 31 * result + schemaMediator.hashCode(); result = 31 * result + durability.hashCode(); + result = 31 * result + schemaMediator.hashCode(); result = 31 * result + (rxObservableFactory != null ? rxObservableFactory.hashCode() : 0); result = 31 * result + (initialDataTransaction != null ? initialDataTransaction.hashCode() : 0); result = 31 * result + (readOnly ? 1 : 0); result = 31 * result + (compactOnLaunch != null ? compactOnLaunch.hashCode() : 0); - return result; } @@ -365,7 +371,7 @@ private static RealmProxyMediator getModuleMediator(String fullyQualifiedModuleC public String toString() { //noinspection StringBufferReplaceableByString StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("realmDirectory: ").append(realmDirectory.toString()); + stringBuilder.append("realmDirectory: ").append(realmDirectory != null ? realmDirectory.toString() : ""); stringBuilder.append("\n"); stringBuilder.append("realmFileName : ").append(realmFileName); stringBuilder.append("\n"); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 096b62620d..24eb5faa8d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -188,7 +188,7 @@ private OsRealmConfig(final RealmConfiguration config, // Set schema related params. SchemaMode schemaMode = SchemaMode.SCHEMA_MODE_MANUAL; if (config.isReadOnly()) { - schemaMode = SchemaMode.SCHEMA_MODE_READONLY; + schemaMode = SchemaMode.SCHEMA_MODE_IMMUTABLE; } else if (syncRealmUrl != null) { schemaMode = SchemaMode.SCHEMA_MODE_ADDITIVE; } else if (config.shouldDeleteRealmIfMigrationNeeded()) { @@ -210,7 +210,6 @@ private OsRealmConfig(final RealmConfiguration config, if (initializationCallback != null) { nativeSetInitializationCallback(nativePtr, initializationCallback); } - // Set sync config if (syncRealmUrl != null) { nativeCreateAndSetSyncConfig(nativePtr, syncRealmUrl, syncRealmAuthUrl, syncUserIdentifier, diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index e2308c96ad..6f4e971551 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -26,7 +26,6 @@ import javax.annotation.Nullable; import io.realm.RealmConfiguration; -import io.realm.RealmFieldType; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; diff --git a/realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java b/realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java index f9a2fbe1e1..1ffc75acc7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java @@ -26,15 +26,17 @@ */ public class ClientResetRequiredError extends ObjectServerError { - private final RealmConfiguration configuration; + private final SyncConfiguration originalConfiguration; + private final RealmConfiguration backupConfiguration; private final File backupFile; private final File originalFile; - public ClientResetRequiredError(ErrorCode errorCode, String errorMessage, String backupFilePath, RealmConfiguration configuration) { + ClientResetRequiredError(ErrorCode errorCode, String errorMessage, SyncConfiguration originalConfiguration, RealmConfiguration backupConfiguration) { super(errorCode, errorMessage); - this.configuration = configuration; - this.backupFile = new File(backupFilePath); - this.originalFile = new File(configuration.getPath()); + this.originalConfiguration = originalConfiguration; + this.backupConfiguration = backupConfiguration; + this.backupFile = new File(backupConfiguration.getPath()); + this.originalFile = new File(originalConfiguration.getPath()); } /** @@ -50,11 +52,11 @@ public ClientResetRequiredError(ErrorCode errorCode, String errorMessage, String */ public void executeClientReset() { synchronized (Realm.class) { - if (Realm.getGlobalInstanceCount(configuration) > 0) { + if (Realm.getGlobalInstanceCount(originalConfiguration) > 0) { throw new IllegalStateException("Realm has not been fully closed. Client Reset cannot run before all " + "instances have been closed."); } - nativeExecuteClientReset(configuration.getPath()); + nativeExecuteClientReset(originalConfiguration.getPath()); } } @@ -70,6 +72,12 @@ public File getBackupFile() { return backupFile; } + /** + * @return the configuration that can be used to open the backup Realm offline. + */ + public RealmConfiguration getBackupRealmConfiguration() { + return backupConfiguration; + } /** * Returns the location of the original Realm file. After the Client Reset has completed, the file at this location * will be deleted. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 1c160cbdff..3659767dc9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -78,7 +78,7 @@ public class SyncConfiguration extends RealmConfiguration { static final int MAX_FULL_PATH_LENGTH = 256; static final int MAX_FILE_NAME_LENGTH = 255; private static final char[] INVALID_CHARS = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'}; -private final URI serverUrl; + private final URI serverUrl; private final SyncUser user; private final SyncSession.ErrorHandler errorHandler; private final boolean deleteRealmOnLogout; @@ -144,6 +144,53 @@ private SyncConfiguration(File directory, this.waitForInitialData = waitForInitialData; } + /** + * Returns a {@link RealmConfiguration} appropriate to open a read-only, non-synced Realm to recover any pending changes. + * This is useful when trying to open a backup/recovery Realm (after a client reset). + * + * @param canonicalPath the absolute path to the Realm file defined by this configuration. + * @param encryptionKey the key used to encrypt/decrypt the Realm file. + * @param modules if specified it will restricts Realm schema to the provided module. + * @return RealmConfiguration that can be used offline + */ + public static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, @Nullable Object... modules) { + HashSet validatedModules = new HashSet<>(); + if (modules != null && modules.length > 0) { + for (Object module : modules) { + if (!module.getClass().isAnnotationPresent(RealmModule.class)) { + throw new IllegalArgumentException(module.getClass().getCanonicalName() + " is not a RealmModule. " + + "Add @RealmModule to the class definition."); + } + validatedModules.add(module); + } + } else { + if (Realm.getDefaultModule() != null) { + validatedModules.add(Realm.getDefaultModule()); + } + } + + RealmProxyMediator schemaMediator = createSchemaMediator(validatedModules, Collections.>emptySet()); + return forRecovery(canonicalPath, encryptionKey, schemaMediator); + } + + /** + * Returns a {@link RealmConfiguration} appropriate to open a read-only, non-synced Realm to recover any pending changes. + * This is useful when trying to open a backup/recovery Realm (after a client reset). + * + * Note: This will use the default Realm module (composed of all {@link RealmModel}), and + * assume no encryption should be used as well. + * + * @param canonicalPath the absolute path to the Realm file defined by this configuration. + * @return RealmConfiguration that can be used offline + */ + public static RealmConfiguration forRecovery(String canonicalPath) { + return forRecovery(canonicalPath, null); + } + + static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) { + return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null); + } + static URI resolveServerUrl(URI serverUrl, String userIdentifier) { try { return new URI(serverUrl.toString().replace("/~/", "/" + userIdentifier + "/")); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 9a3eb54852..4387d8e26a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -167,9 +167,10 @@ void notifySessionError(int errorCode, String errorMessage) { ErrorCode errCode = ErrorCode.fromInt(errorCode); if (errCode == ErrorCode.CLIENT_RESET) { // errorMessage contains the path to the backed up file + RealmConfiguration backupRealmConfiguration = SyncConfiguration.forRecovery(errorMessage, configuration.getEncryptionKey(), configuration.getSchemaMediator()); errorHandler.onError(this, new ClientResetRequiredError(errCode, "A Client Reset is required. " + "Read more here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup.", - errorMessage, getConfiguration())); + configuration, backupRealmConfiguration)); } else { errorHandler.onError(this, new ObjectServerError(errCode, errorMessage)); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index 660d9e6d56..bd53f9209c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -267,5 +267,4 @@ public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { user.logout(); } } - } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 9b006ac45f..537bf257bd 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -577,7 +577,6 @@ public void execute(Realm realm) { final Token accessToken = entry.getValue(); Assert.assertNotNull(accessToken); - // getting refresh token delay Field refreshTokenTaskField = SyncSession.class.getDeclaredField("refreshTokenTask"); refreshTokenTaskField.setAccessible(true); @@ -596,8 +595,7 @@ public void execute(Realm realm) { SystemClock.sleep(TimeUnit.SECONDS.toMillis(3)); Token newAccessToken = accessTokens.get(syncConfiguration); - - assertThat("new Token is not expired", newAccessToken.expiresMs(), greaterThan(System.currentTimeMillis())); + assertThat("new Token expires after the old one", newAccessToken.expiresMs(), greaterThan(accessToken.expiresMs())); assertNotEquals(accessToken, newAccessToken); // refresh_token identity is the same diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index af4d52fdcb..e1943ae61c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -18,7 +18,6 @@ import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -187,6 +186,11 @@ public void onChange(Progress progress) { TestHelper.awaitOrFail(allChangesDownloaded); adminRealm.close(); userRealm.close(); + userWithData.logout(); + adminUser.logout(); + // FIXME sometimes the worker thread doesn't terminate + // causing the test thread to wait indefinitely until it times out + // https://github.com/realm/realm-java/issues/5245 worker.join(); } From 6e543f0528f5902bf03c2591ddab538c0bc3d98e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 19 Sep 2017 20:39:12 +0800 Subject: [PATCH 0971/2110] Cache the dowloaded core on CI --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index dc74785a4d..27c4ae7061 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -43,6 +43,7 @@ try { "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + "-v ${env.HOME}/.android:/tmp/.android " + "-v ${env.HOME}/ccache:/tmp/.ccache " + + "-e REALM_CORE_DOWNLOAD_DIR=/tmp/.gradle " + "--network container:${rosContainer.id}") { stage('JVM tests') { try { From 4b655703464d483f68229af477604482068e9644 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 20 Sep 2017 16:28:43 +0800 Subject: [PATCH 0972/2110] Enable test login_withAccessToken Close #4711 --- .../java/io/realm/objectserver/AuthTests.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 537bf257bd..e14a262566 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -112,11 +112,8 @@ public void onError(ObjectServerError error) { }); } - // FIXME: https://github.com/realm/realm-java/issues/4711 - // fail may be related to this issue https://github.com/realm/realm-java/issues/5068 @Test @RunTestInLooperThread - @Ignore("This fails expectSimpleCommit for some reasons, needs to be FIXED ASAP.") public void login_withAccessToken() { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); SyncCredentials credentials = SyncCredentials.accessToken(adminUser.getAccessToken().value(), "custom-admin-user", adminUser.isAdmin()); @@ -135,16 +132,8 @@ public void onError(SyncSession session, ObjectServerError error) { final Realm realm = Realm.getInstance(config); looperThread.addTestRealm(realm); - - // FIXME: Right now we have no Java API for detecting when a session is established - // So we optimistically assume it has been connected after 1 second. - looperThread.postRunnableDelayed(new Runnable() { - @Override - public void run() { - assertTrue(SyncManager.getSession(config).getUser().isValid()); - looperThread.testComplete(); - } - }, 1000); + assertTrue(config.getUser().isValid()); + looperThread.testComplete(); } @Override From 9dbf26fa6ca55a0b5f3ce957fe7157fd65ef869a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 21 Sep 2017 15:38:35 +0800 Subject: [PATCH 0973/2110] Always use object store to create PK table (#5284) - Add OsObjectStore class to wrap methods in ObjectStore.hpp. - Use ObjectStore to create meta tables. - Use ObjectStore to get/set primary key. - Always create meta tables when open a non-exist, non-readonly Dynamic Realm. - Clean code. --- CHANGELOG.md | 1 + .../processor/RealmProxyClassGenerator.java | 4 +- .../io/realm/AllTypesRealmProxy.java | 8 +- .../java/io/realm/IOSRealmTests.java | 3 +- .../java/io/realm/RealmAnnotationTests.java | 76 +---- .../java/io/realm/RealmMigrationTests.java | 146 ++++++-- .../java/io/realm/RealmObjectSchemaTests.java | 2 +- .../java/io/realm/RealmSchemaTests.java | 45 ++- .../androidTest/java/io/realm/TestHelper.java | 26 +- .../java/io/realm/entities/IOSAllTypes.java | 2 + .../realm/entities/PrimaryKeyAsInteger.java | 1 + .../io/realm/internal/CollectionTests.java | 28 +- .../io/realm/internal/JNISortedLongTest.java | 110 ------ .../io/realm/internal/PrimaryKeyTests.java | 20 +- .../io/realm/internal/SharedRealmTests.java | 28 -- .../io/realm/SyncedRealmMigrationTests.java | 1 + .../realm-library/src/main/cpp/CMakeLists.txt | 1 + .../io_realm_internal_OsObjectSchemaInfo.cpp | 16 + .../cpp/io_realm_internal_OsObjectStore.cpp | 142 ++++++++ .../cpp/io_realm_internal_SharedRealm.cpp | 78 +---- .../src/main/cpp/io_realm_internal_Table.cpp | 323 +----------------- .../src/main/java/io/realm/BaseRealm.java | 10 +- .../src/main/java/io/realm/DynamicRealm.java | 43 ++- .../io/realm/MutableRealmObjectSchema.java | 42 ++- .../java/io/realm/MutableRealmSchema.java | 18 +- .../src/main/java/io/realm/Realm.java | 15 +- .../src/main/java/io/realm/RealmList.java | 4 +- .../main/java/io/realm/RealmObjectSchema.java | 16 +- .../main/java/io/realm/internal/OsObject.java | 12 +- .../io/realm/internal/OsObjectSchemaInfo.java | 16 + .../java/io/realm/internal/OsObjectStore.java | 84 +++++ .../io/realm/internal/RealmProxyMediator.java | 18 +- .../java/io/realm/internal/SharedRealm.java | 39 +-- .../main/java/io/realm/internal/Table.java | 244 +------------ .../java/io/realm/internal/TableSchema.java | 43 --- .../java/io/realm/internal/UncheckedRow.java | 4 - 36 files changed, 615 insertions(+), 1054 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp create mode 100644 realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/TableSchema.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eb2c28380..607040c430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ * Upgraded to Realm Sync 2.0.0-rc16. * Upgraded to Realm Core 3.0.0-rc5. +* Always use Object Store to create primary key table. ## 4.0.0-BETA2 (2017-07-27) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 9bfd5a7845..8b2ea52b8d 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -1319,10 +1319,10 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP writer.beginControlFlow("if (rowIndex == Table.NO_MATCH)"); if (Utils.isString(metadata.getPrimaryKey())) { writer.emitStatement( - "rowIndex = OsObject.createRowWithPrimaryKey(table, primaryKeyValue)"); + "rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue)"); } else { writer.emitStatement( - "rowIndex = OsObject.createRowWithPrimaryKey(table, ((%s) object).%s())", + "rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 994bef3d5d..3a914bf064 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -786,7 +786,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, M rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(table, primaryKeyValue); + rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue); } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } @@ -915,7 +915,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map ob rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); } if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(table, primaryKeyValue); + rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue); } cache.put(object, rowIndex); Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index 555e1c1466..5b8cf56418 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -31,6 +31,7 @@ import io.realm.entities.IOSAllTypes; import io.realm.entities.IOSChild; +import io.realm.internal.OsObjectStore; import io.realm.internal.Table; import io.realm.rule.TestRealmConfigurationFactory; @@ -81,7 +82,7 @@ public void iOSDataTypes() throws IOException { RealmResults result = realm.where(IOSAllTypes.class).findAllSorted("id", Sort.ASCENDING); // Verifies metadata. Table table = realm.getTable(IOSAllTypes.class); - assertTrue(table.hasPrimaryKey()); + assertEquals("id", OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), IOSAllTypes.CLASS_NAME)); assertTrue(table.hasSearchIndex(table.getColumnIndex("id"))); // Iterative check. for (int i = 0; i < 10; i++) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java index 3e18d70e5a..e2d0a4b959 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java @@ -30,11 +30,13 @@ import io.realm.entities.PrimaryKeyAsLong; import io.realm.entities.PrimaryKeyAsString; import io.realm.exceptions.RealmPrimaryKeyConstraintException; +import io.realm.internal.OsObjectStore; import io.realm.internal.Table; import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -99,76 +101,6 @@ public void index() { assertFalse(table.hasSearchIndex(table.getColumnIndex("notIndexDate"))); } - // Tests migrating primary key from string to long with existing data. - @Test - public void primaryKey_migration_long() { - realm.beginTransaction(); - for (int i = 1; i <= 2; i++) { - PrimaryKeyAsString obj = realm.createObject(PrimaryKeyAsString.class, "String" + i); - obj.setId(i); - } - - Table table = realm.getTable(PrimaryKeyAsString.class); - table.setPrimaryKey("id"); - assertEquals(1, table.getPrimaryKey()); - realm.cancelTransaction(); - } - - // Tests migrating primary key from string to long with existing data. - @Test - public void primaryKey_migration_longDuplicateValues() { - realm.beginTransaction(); - for (int i = 1; i <= 2; i++) { - PrimaryKeyAsString obj = realm.createObject(PrimaryKeyAsString.class, "String" + i); - obj.setId(1); // Creates duplicate values. - } - - Table table = realm.getTable(PrimaryKeyAsString.class); - try { - table.setPrimaryKey("id"); - fail("It should not be possible to set a primary key column which already contains duplicate values."); - } catch (IllegalArgumentException ignored) { - assertEquals(0, table.getPrimaryKey()); - } finally { - realm.cancelTransaction(); - } - } - - // Tests migrating primary key from long to str with existing data. - @Test - public void primaryKey_migration_string() { - realm.beginTransaction(); - for (int i = 1; i <= 2; i++) { - PrimaryKeyAsLong obj = realm.createObject(PrimaryKeyAsLong.class, i); - obj.setName("String" + i); - } - - Table table = realm.getTable(PrimaryKeyAsLong.class); - table.setPrimaryKey("name"); - assertEquals(1, table.getPrimaryKey()); - realm.cancelTransaction(); - } - - // Tests migrating primary key from long to str with existing data. - @Test - public void primaryKey_migration_stringDuplicateValues() { - realm.beginTransaction(); - for (int i = 1; i <= 2; i++) { - PrimaryKeyAsLong obj = realm.createObject(PrimaryKeyAsLong.class, i); - obj.setName("String"); // Creates duplicate values. - } - - Table table = realm.getTable(PrimaryKeyAsLong.class); - try { - table.setPrimaryKey("name"); - fail("It should not be possible to set a primary key column which already contains duplicate values."); - } catch (IllegalArgumentException ignored) { - assertEquals(0, table.getPrimaryKey()); - } finally { - realm.cancelTransaction(); - } - } - @Test public void primaryKey_checkPrimaryKeyOnCreate() { realm.beginTransaction(); @@ -197,11 +129,11 @@ public void primaryKey_errorOnInsertingSameObject() { @Test public void primaryKey_isIndexed() { Table table = realm.getTable(PrimaryKeyAsString.class); - assertTrue(table.hasPrimaryKey()); + assertNotNull(OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), PrimaryKeyAsString.CLASS_NAME)); assertTrue(table.hasSearchIndex(table.getColumnIndex("name"))); table = realm.getTable(PrimaryKeyAsLong.class); - assertTrue(table.hasPrimaryKey()); + assertNotNull(OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), PrimaryKeyAsLong.CLASS_NAME)); assertTrue(table.hasSearchIndex(table.getColumnIndex("id"))); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 923581bc45..91e0d6945b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -62,6 +62,7 @@ import io.realm.entities.migration.MigrationPosteriorIndexOnly; import io.realm.entities.migration.MigrationPriorIndexOnly; import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.OsObjectStore; import io.realm.internal.Table; import io.realm.migration.MigrationPrimaryKey; import io.realm.rule.TestRealmConfigurationFactory; @@ -98,6 +99,18 @@ public void tearDown() { } } + private void assertPKField(Realm realm, String className, String expectedName, long expectedIndex) { + String pkField = OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, className); + assertNotNull(pkField); + RealmObjectSchema objectSchema = realm.getSchema().get(className); + assertNotNull(objectSchema); + assertTrue(objectSchema.hasField(expectedName)); + assertEquals(expectedName, pkField); + //noinspection ConstantConditions + assertEquals(expectedIndex, + realm.sharedRealm.getTable(Table.getTableNameForClass(className)).getColumnIndex(pkField)); + } + @Test public void getInstance_realmClosedAfterMigrationException() throws IOException { String REALM_NAME = "default0.realm"; @@ -371,10 +384,9 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Realm realm = Realm.getInstance(realmConfig); Table table = realm.getSchema().getTable(MigrationClassRenamed.class); - assertTrue(table.hasPrimaryKey()); assertEquals(MigrationClassRenamed.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertEquals(MigrationClassRenamed.DEFAULT_PRIMARY_INDEX, table.getPrimaryKey()); - assertEquals(MigrationClassRenamed.FIELD_PRIMARY, table.getColumnName(table.getPrimaryKey())); + assertPKField(realm, MigrationClassRenamed.CLASS_NAME, MigrationClassRenamed.FIELD_PRIMARY, + MigrationClassRenamed.DEFAULT_PRIMARY_INDEX); // Old schema does not exist. assertNull(realm.getSchema().get(MigrationPrimaryKey.CLASS_NAME)); } @@ -440,10 +452,9 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Realm realm = Realm.getInstance(realmConfig); Table table = realm.getSchema().getTable(MigrationClassRenamed.class); - assertTrue(table.hasPrimaryKey()); assertEquals(MigrationClassRenamed.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertEquals(MigrationClassRenamed.DEFAULT_PRIMARY_INDEX, table.getPrimaryKey()); - assertEquals(MigrationClassRenamed.FIELD_PRIMARY, table.getColumnName(table.getPrimaryKey())); + assertPKField(realm, MigrationClassRenamed.CLASS_NAME, MigrationClassRenamed.FIELD_PRIMARY, + MigrationClassRenamed.DEFAULT_PRIMARY_INDEX); // Old schema does not exist. assertNull(realm.getSchema().get(MigrationPrimaryKey.CLASS_NAME)); } @@ -553,10 +564,9 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Realm realm = Realm.getInstance(realmConfig); Table table = realm.getSchema().getTable(MigrationPosteriorIndexOnly.class); - assertTrue(table.hasPrimaryKey()); assertEquals(MigrationPosteriorIndexOnly.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertEquals(MigrationPosteriorIndexOnly.DEFAULT_PRIMARY_INDEX, table.getPrimaryKey()); - assertEquals(MigrationPosteriorIndexOnly.FIELD_PRIMARY, table.getColumnName(table.getPrimaryKey())); + assertPKField(realm, MigrationPosteriorIndexOnly.CLASS_NAME, MigrationPosteriorIndexOnly.FIELD_PRIMARY + , MigrationPosteriorIndexOnly.DEFAULT_PRIMARY_INDEX); } // Removing fields after a pk field does not affect the pk. @@ -580,10 +590,9 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Realm realm = Realm.getInstance(realmConfig); Table table = realm.getSchema().getTable(MigrationPriorIndexOnly.class); - assertTrue(table.hasPrimaryKey()); assertEquals(MigrationPriorIndexOnly.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertEquals(MigrationPriorIndexOnly.DEFAULT_PRIMARY_INDEX, table.getPrimaryKey()); - assertEquals(MigrationPriorIndexOnly.FIELD_PRIMARY, table.getColumnName(table.getPrimaryKey())); + assertPKField(realm, MigrationPriorIndexOnly.CLASS_NAME, MigrationPriorIndexOnly.FIELD_PRIMARY + , MigrationPriorIndexOnly.DEFAULT_PRIMARY_INDEX); } // Renaming the class should also rename the the class entry in the pk metadata table that tracks primary keys. @@ -606,13 +615,9 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Realm realm = Realm.getInstance(realmConfig); Table table = realm.getSchema().getTable(MigrationFieldRenamed.class); - assertTrue(table.hasPrimaryKey()); assertEquals(MigrationFieldRenamed.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertEquals(MigrationFieldRenamed.DEFAULT_PRIMARY_INDEX, table.getPrimaryKey()); - - RealmObjectSchema objectSchema = realm.getSchema().get(MigrationFieldRenamed.CLASS_NAME); - assertFalse(objectSchema.hasField(MigrationPrimaryKey.FIELD_PRIMARY)); - assertEquals(MigrationFieldRenamed.FIELD_PRIMARY, objectSchema.getPrimaryKey()); + assertPKField(realm, MigrationFieldRenamed.CLASS_NAME, MigrationFieldRenamed.FIELD_PRIMARY, + MigrationFieldRenamed.DEFAULT_PRIMARY_INDEX); } private void createObjectsWithOldPrimaryKey(final String className, final boolean insertNullValue) { @@ -672,13 +677,10 @@ public void apply(DynamicRealmObject obj) { Realm realm = Realm.getInstance(realmConfig); Table table = realm.getSchema().getTable(MigrationFieldTypeToInt.class); - assertTrue(table.hasPrimaryKey()); assertEquals(MigrationFieldTypeToInt.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertEquals(MigrationFieldTypeToInt.DEFAULT_PRIMARY_INDEX, table.getPrimaryKey()); + assertPKField(realm, MigrationFieldTypeToInt.CLASS_NAME, MigrationFieldTypeToInt.FIELD_PRIMARY, + MigrationFieldTypeToInt.DEFAULT_PRIMARY_INDEX); - RealmObjectSchema objectSchema = realm.getSchema().get(MigrationFieldTypeToInt.CLASS_NAME); - assertFalse(objectSchema.hasField(MigrationPrimaryKey.FIELD_PRIMARY)); - assertEquals(MigrationFieldTypeToInt.FIELD_PRIMARY, objectSchema.getPrimaryKey()); assertEquals(1, realm.where(MigrationFieldTypeToInt.class).count()); assertEquals(12, realm.where(MigrationFieldTypeToInt.class).findFirst().fieldIntPrimary); } @@ -720,13 +722,10 @@ public void apply(DynamicRealmObject obj) { Realm realm = Realm.getInstance(realmConfig); Table table = realm.getSchema().getTable(MigrationFieldTypeToInteger.class); - assertTrue(table.hasPrimaryKey()); assertEquals(MigrationFieldTypeToInteger.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertEquals(MigrationFieldTypeToInteger.DEFAULT_PRIMARY_INDEX, table.getPrimaryKey()); + assertPKField(realm, MigrationFieldTypeToInteger.CLASS_NAME, MigrationFieldTypeToInteger.FIELD_PRIMARY, + MigrationFieldTypeToInteger.DEFAULT_PRIMARY_INDEX); - RealmObjectSchema objectSchema = realm.getSchema().get(MigrationFieldTypeToInteger.CLASS_NAME); - assertFalse(objectSchema.hasField(MigrationPrimaryKey.FIELD_PRIMARY)); - assertEquals(MigrationFieldTypeToInteger.FIELD_PRIMARY, objectSchema.getPrimaryKey()); assertEquals(2, realm.where(MigrationFieldTypeToInteger.class).count()); // not-null value @@ -740,6 +739,95 @@ public void apply(DynamicRealmObject obj) { .count()); } + @Test + public void modifyPrimaryKeyFieldTypeFromIntToStringInMigration() { + RealmMigration migration = new RealmMigration() { + @Override + public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { + RealmObjectSchema objectSchema = realm.getSchema().get(PrimaryKeyAsString.CLASS_NAME); + assertNotNull(objectSchema); + assertEquals(PrimaryKeyAsString.FIELD_ID, objectSchema.getPrimaryKey()); + objectSchema.removePrimaryKey().addPrimaryKey(PrimaryKeyAsString.FIELD_PRIMARY_KEY); + } + }; + + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .schema(PrimaryKeyAsString.class) + .schemaVersion(1) + .migration(migration) + .build(); + + // Create the schema and set the int field as primary key + DynamicRealm dynamicRealm = DynamicRealm.getInstance(configuration); + dynamicRealm.beginTransaction(); + RealmSchema schema = dynamicRealm.getSchema(); + schema.create(PrimaryKeyAsString.CLASS_NAME) + .addField(PrimaryKeyAsString.FIELD_ID, long.class, FieldAttribute.PRIMARY_KEY) + .addField(PrimaryKeyAsString.FIELD_PRIMARY_KEY, String.class); + dynamicRealm.createObject(PrimaryKeyAsString.CLASS_NAME, 0) + .setString(PrimaryKeyAsString.FIELD_PRIMARY_KEY, "string0"); + dynamicRealm.createObject(PrimaryKeyAsString.CLASS_NAME, 1) + .setString(PrimaryKeyAsString.FIELD_PRIMARY_KEY, "string1"); + dynamicRealm.setVersion(0); + dynamicRealm.commitTransaction(); + + // Run migration + realm = Realm.getInstance(configuration); + RealmObjectSchema objectSchema = realm.getSchema().get(PrimaryKeyAsString.CLASS_NAME); + assertNotNull(objectSchema); + assertEquals(PrimaryKeyAsString.FIELD_PRIMARY_KEY, objectSchema.getPrimaryKey()); + RealmResults results = realm.where(PrimaryKeyAsString.class) + .findAllSorted(PrimaryKeyAsString.FIELD_ID); + assertEquals(2, results.size()); + assertEquals("string0", results.get(0).getName()); + assertEquals("string1", results.get(1).getName()); + } + + @Test + public void modifyPrimaryKeyFieldTypeFromStringToInt() { + RealmMigration migration = new RealmMigration() { + @Override + public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { + RealmObjectSchema objectSchema = realm.getSchema().get(PrimaryKeyAsInteger.CLASS_NAME); + assertNotNull(objectSchema); + assertEquals(PrimaryKeyAsInteger.FIELD_NAME, objectSchema.getPrimaryKey()); + objectSchema.removePrimaryKey().addPrimaryKey(PrimaryKeyAsInteger.FIELD_ID); + } + }; + + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .schema(PrimaryKeyAsInteger.class) + .schemaVersion(1) + .migration(migration) + .build(); + + // Create the schema and set the String field as primary key + DynamicRealm dynamicRealm = DynamicRealm.getInstance(configuration); + dynamicRealm.beginTransaction(); + RealmSchema schema = dynamicRealm.getSchema(); + schema.create(PrimaryKeyAsInteger.CLASS_NAME) + .addField(PrimaryKeyAsInteger.FIELD_ID, int.class) + .addField(PrimaryKeyAsInteger.FIELD_NAME, String.class, FieldAttribute.PRIMARY_KEY); + dynamicRealm.createObject(PrimaryKeyAsInteger.CLASS_NAME, "string0") + .setInt(PrimaryKeyAsInteger.FIELD_ID, 0); + dynamicRealm.createObject(PrimaryKeyAsInteger.CLASS_NAME, "string1") + .setInt(PrimaryKeyAsInteger.FIELD_ID, 1); + dynamicRealm.setVersion(0); + dynamicRealm.commitTransaction(); + + // Run migration + realm = Realm.getInstance(configuration); + + RealmObjectSchema objectSchema = realm.getSchema().get(PrimaryKeyAsInteger.CLASS_NAME); + assertNotNull(objectSchema); + assertEquals(PrimaryKeyAsInteger.FIELD_ID, objectSchema.getPrimaryKey()); + RealmResults results = realm.where(PrimaryKeyAsInteger.class) + .findAllSorted(PrimaryKeyAsInteger.FIELD_ID); + assertEquals(2, results.size()); + assertEquals(0, results.get(0).getId()); + assertEquals(1, results.get(1).getId()); + } + @Test public void settingPrimaryKeyWithObjectSchema() { // Creates v0 of the Realm. @@ -804,7 +892,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm = Realm.getInstance(realmConfig); Table table = realm.getTable(AnnotationTypes.class); assertEquals(3, table.getColumnCount()); - assertTrue(table.hasPrimaryKey()); + assertEquals("id", OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), "AnnotationTypes")); assertTrue(table.hasSearchIndex(table.getColumnIndex("id"))); assertTrue(table.hasSearchIndex(table.getColumnIndex("indexString"))); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index d72618711b..dedb61e2ba 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -496,7 +496,7 @@ public void addPrimaryKeyFieldModifier_duplicateValues() { fail(); } catch (IllegalArgumentException e) { // Checks if message reports correct field name. - assertTrue(e.getMessage().contains("\"" + fieldName + "\"")); + assertThat(e.getMessage(), CoreMatchers.containsString(fieldName)); } schema.removeField(fieldName); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index 6db847312d..52442d9351 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -99,7 +99,6 @@ public void setUp() { @After public void tearDown() { - realm.cancelTransaction(); realm.close(); } @@ -561,4 +560,48 @@ public void rename_shouldUpdateDynamicCache() { assertSame(foo, bar); assertEquals("bar", bar.getClassName()); } + + @Test + public void rename_newNameExists() { + if (type == SchemaType.IMMUTABLE) { + return; + } + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage( + CoreMatchers.containsString("Cat cannot be renamed because the new class already exists")); + realmSchema.rename("Cat", "Dog"); + } + + @Test + public void mutableMethodsCalled_notInTransaction() { + if (type == SchemaType.IMMUTABLE) { + return; + } + + realm.cancelTransaction(); + + try { + realmSchema.create("Foo"); + } catch (IllegalStateException expected) { + assertThat(expected.getMessage(), CoreMatchers.containsString("transaction")); + } + + try { + realmSchema.createWithPrimaryKeyField("Foo", "PK", String.class); + } catch (IllegalStateException expected) { + assertThat(expected.getMessage(), CoreMatchers.containsString("transaction")); + } + + try { + realmSchema.remove("Cat"); + } catch (IllegalStateException expected) { + assertThat(expected.getMessage(), CoreMatchers.containsString("transaction")); + } + + try { + realmSchema.rename("Cat", "Foo1"); + } catch (IllegalStateException expected) { + assertThat(expected.getMessage(), CoreMatchers.containsString("transaction")); + } + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 75c4a917c7..d253569dcb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -58,6 +58,7 @@ import io.realm.entities.PrimaryKeyAsString; import io.realm.internal.Collection; import io.realm.internal.OsObject; +import io.realm.internal.OsObjectStore; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.async.RealmThreadPoolExecutor; @@ -72,7 +73,6 @@ public class TestHelper { public static final int VERY_SHORT_WAIT_SECS = 1; public static final int SHORT_WAIT_SECS = 10; public static final int STANDARD_WAIT_SECS = 100; - public static final int LONG_WAIT_SECS = 1000; private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final Random RANDOM = new Random(); @@ -879,30 +879,6 @@ public static void initNullTypesTableExcludes(DynamicRealm realm, String excludi realm.commitTransaction(); } - public static void populateForMultiSort(Realm typedRealm) { - DynamicRealm dynamicRealm = DynamicRealm.getInstance(typedRealm.getConfiguration()); - populateForMultiSort(dynamicRealm); - dynamicRealm.close(); - typedRealm.waitForChange(); - } - - public static void populateForMultiSort(DynamicRealm realm) { - realm.beginTransaction(); - realm.delete(AllTypes.CLASS_NAME); - DynamicRealmObject object1 = realm.createObject(AllTypes.CLASS_NAME); - object1.setLong(AllTypes.FIELD_LONG, 5); - object1.setString(AllTypes.FIELD_STRING, "Adam"); - - DynamicRealmObject object2 = realm.createObject(AllTypes.CLASS_NAME); - object2.setLong(AllTypes.FIELD_LONG, 4); - object2.setString(AllTypes.FIELD_STRING, "Brian"); - - DynamicRealmObject object3 = realm.createObject(AllTypes.CLASS_NAME); - object3.setLong(AllTypes.FIELD_LONG, 4); - object3.setString(AllTypes.FIELD_STRING, "Adam"); - realm.commitTransaction(); - } - public static void populateSimpleAllTypesPrimaryKey(Realm realm) { realm.beginTransaction(); AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/IOSAllTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/IOSAllTypes.java index db36d1a8e9..771035d7c7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/IOSAllTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/IOSAllTypes.java @@ -24,6 +24,8 @@ public class IOSAllTypes extends RealmObject { + public final static String CLASS_NAME = "IOSAllTypes"; + @PrimaryKey private long id; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java index c54aa7b6fa..acc955ed01 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java @@ -23,6 +23,7 @@ public class PrimaryKeyAsInteger extends RealmObject { public static final String CLASS_NAME = "PrimaryKeyAsInteger"; public static final String FIELD_ID = "id"; + public static final String FIELD_NAME= "name"; @PrimaryKey private int id; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 2c6b451d71..52e5711b8b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -76,12 +76,20 @@ public void tearDown() { private SharedRealm getSharedRealm() { OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config) .autoUpdateNotification(true); - return SharedRealm.getInstance(configBuilder); + SharedRealm sharedRealm = SharedRealm.getInstance(configBuilder); + sharedRealm.beginTransaction(); + OsObjectStore.setSchemaVersion(sharedRealm, OsObjectStore.SCHEMA_NOT_VERSIONED); + sharedRealm.commitTransaction(); + return sharedRealm; + } + + private Table getTable(SharedRealm sharedRealm) { + return sharedRealm.getTable(Table.getTableNameForClass("test_table")); } private void populateData() { sharedRealm.beginTransaction(); - table = sharedRealm.createTable("test_table"); + table = sharedRealm.createTable(Table.getTableNameForClass("test_table")); // Specify the column types and names long columnIdx = table.addColumn(RealmFieldType.STRING, "firstName"); table.addSearchIndex(columnIdx); @@ -127,7 +135,7 @@ public void run() { private void addRow(SharedRealm sharedRealm) { sharedRealm.beginTransaction(); - table = sharedRealm.getTable("test_table"); + Table table = getTable(sharedRealm); OsObject.createRow(table); sharedRealm.commitTransaction(); } @@ -154,7 +162,7 @@ public void constructor_queryIsValidated() { public void constructor_queryOnDeletedTable() { TableQuery query = table.where(); sharedRealm.beginTransaction(); - sharedRealm.removeTable(table.getName()); + assertTrue(OsObjectStore.deleteTableForObject(sharedRealm, table.getClassName())); sharedRealm.commitTransaction(); // Query should be checked before creating OS Results. thrown.expect(IllegalStateException.class); @@ -246,7 +254,7 @@ public void distinct() { @RunTestInLooperThread public void addListener_shouldBeCalledToReturnTheQueryResults() { final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); + Table table = getTable(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); looperThread.keepStrongReference(collection); @@ -267,7 +275,7 @@ public void onChange(Collection collection1) { public void addListener_shouldBeCalledWhenRefreshToReturnTheQueryResults() { final AtomicBoolean onChangeCalled = new AtomicBoolean(false); final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); + Table table = getTable(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); collection.addListener(collection, new RealmChangeListener() { @@ -335,7 +343,7 @@ public void onChange(Collection element) { @RunTestInLooperThread public void addListener_queryNotReturned() { final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); + Table table = getTable(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); looperThread.keepStrongReference(collection); @@ -356,7 +364,7 @@ public void onChange(Collection collection1) { @RunTestInLooperThread public void addListener_queryReturned() { final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); + Table table = getTable(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); looperThread.keepStrongReference(collection); @@ -380,7 +388,7 @@ public void onChange(Collection collection1) { @RunTestInLooperThread public void addListener_triggeredByLocalCommit() { final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); + Table table = getTable(sharedRealm); final AtomicInteger listenerCounter = new AtomicInteger(0); final Collection collection = new Collection(sharedRealm, table.where()); @@ -461,7 +469,7 @@ public void collectionIterator_invalid_nonLooperThread_byRefresh() { @RunTestInLooperThread public void collectionIterator_invalid_looperThread_byRemoteTransaction() { final SharedRealm sharedRealm = getSharedRealm(); - Table table = sharedRealm.getTable("test_table"); + Table table = getTable(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); final TestIterator iterator = new TestIterator(collection); looperThread.keepStrongReference(collection); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java deleted file mode 100644 index fdfaa4cdf9..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNISortedLongTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.RealmFieldType; -import io.realm.TestHelper; -import io.realm.rule.TestRealmConfigurationFactory; - -import static org.junit.Assert.assertEquals; - - -@RunWith(AndroidJUnit4.class) -public class JNISortedLongTest { - - @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - - @SuppressWarnings("FieldCanBeLocal") - private RealmConfiguration config; - private SharedRealm sharedRealm; - private Table table; - - @Before - public void setUp() throws Exception { - Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); - config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); - } - - @After - public void tearDown() { - if (sharedRealm != null && !sharedRealm.isClosed()) { - sharedRealm.close(); - } - } - - private void init() { - Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); - table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { - @Override - public void execute(Table table) { - table.addColumn(RealmFieldType.INTEGER, "number"); - table.addColumn(RealmFieldType.STRING, "name"); - - TestHelper.addRowWithValues(table, 1, "A"); - TestHelper.addRowWithValues(table, 10, "B"); - TestHelper.addRowWithValues(table, 20, "C"); - TestHelper.addRowWithValues(table, 30, "B"); - TestHelper.addRowWithValues(table, 40, "D"); - TestHelper.addRowWithValues(table, 50, "D"); - TestHelper.addRowWithValues(table, 60, "D"); - TestHelper.addRowWithValues(table, 60, "D"); - } - }); - - assertEquals(8, table.size()); - } - - @Test - public void shouldTestSortedIntTable() { - init(); - - // Before first entry. - assertEquals(0, table.lowerBoundLong(0, 0)); - assertEquals(0, table.upperBoundLong(0, 0)); - - // Finds middle match. - assertEquals(4, table.lowerBoundLong(0, 40)); - assertEquals(5, table.upperBoundLong(0, 40)); - - // Finds middle (nonexisting). - assertEquals(5, table.lowerBoundLong(0, 41)); - assertEquals(5, table.upperBoundLong(0, 41)); - - // Beyond last entry. - assertEquals(8, table.lowerBoundLong(0, 100)); - assertEquals(8, table.upperBoundLong(0, 100)); - - // Finds last match (duplicated). - assertEquals(6, table.lowerBoundLong(0, 60)); - assertEquals(8, table.upperBoundLong(0, 60)); - - } - -} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index b63e45dd41..7614175792 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -73,20 +73,22 @@ public void tearDown() { private Table getTableWithStringPrimaryKey() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); + OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table Table t = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); long column = t.addColumn(RealmFieldType.STRING, "colName", true); t.addSearchIndex(column); - t.setPrimaryKey("colName"); + OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable", "colName"); return t; } private Table getTableWithIntegerPrimaryKey() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); + OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table Table t = sharedRealm.createTable(Table.getTableNameForClass("class_TestTable")); long column = t.addColumn(RealmFieldType.INTEGER, "colName"); t.addSearchIndex(column); - t.setPrimaryKey("colName"); + OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable", "colName"); return t; } @@ -101,7 +103,6 @@ private Table getTableWithIntegerPrimaryKey() { * RealmPrimaryKeyConstraintException anyway. Unclear why. */ @Test - @Ignore("See https://github.com/realm/realm-java/issues/5231") public void removingPrimaryKeyRemovesConstraint_typeSetters() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name("removeConstraints").build(); @@ -182,8 +183,7 @@ public void migratePrimaryKeyTableIfNeeded_first() throws IOException { assertTrue(Table.migratePrimaryKeyTableIfNeeded(sharedRealm)); sharedRealm.commitTransaction(); Table t = sharedRealm.getTable("class_AnnotationTypes"); - assertTrue(t.hasPrimaryKey()); - assertEquals(t.getColumnIndex("id"), t.getPrimaryKey()); + assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); assertEquals(RealmFieldType.STRING, sharedRealm.getTable("pk").getColumnType(0)); } @@ -195,8 +195,7 @@ public void migratePrimaryKeyTableIfNeeded_second() throws IOException { assertTrue(Table.migratePrimaryKeyTableIfNeeded(sharedRealm)); sharedRealm.commitTransaction(); Table t = sharedRealm.getTable("class_AnnotationTypes"); - assertTrue(t.hasPrimaryKey()); - assertEquals(t.getColumnIndex("id"), t.getPrimaryKey()); + assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); assertEquals("AnnotationTypes", sharedRealm.getTable("pk").getString(0, 0)); } @@ -232,13 +231,14 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableMigratedWithRightName( public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); + OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table Table table = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); long column = table.addColumn(RealmFieldType.INTEGER, "PKColumn"); table.addSearchIndex(column); - table.setPrimaryKey(column); + OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable", "PKColumn"); sharedRealm.commitTransaction(); - assertEquals(table.getPrimaryKey(), table.getColumnIndex("PKColumn")); + assertEquals("PKColumn", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "TestTable")); // Now we have a pk table with search index. sharedRealm.beginTransaction(); @@ -251,7 +251,7 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { long column2 = table2.addColumn(RealmFieldType.INTEGER, "PKColumn"); table2.addSearchIndex(column2); try { - table2.setPrimaryKey(column2); + OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable2", "PKColumn"); } catch (IllegalStateException ignored) { // Column has no search index. } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java index 42ba2a3110..98ff45c0df 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java @@ -107,34 +107,6 @@ public void isInTransaction_returnFalseWhenRealmClosed() { sharedRealm = null; } - @Test - public void removeTable() { - sharedRealm.beginTransaction(); - sharedRealm.createTable("TableToRemove"); - assertTrue(sharedRealm.hasTable("TableToRemove")); - sharedRealm.removeTable("TableToRemove"); - assertFalse(sharedRealm.hasTable("TableToRemove")); - sharedRealm.commitTransaction(); - } - - @Test - public void removeTable_notInTransactionThrows() { - sharedRealm.beginTransaction(); - sharedRealm.createTable("TableToRemove"); - sharedRealm.commitTransaction(); - thrown.expect(IllegalStateException.class); - sharedRealm.removeTable("TableToRemove"); - } - - @Test - public void removeTable_tableNotExist() { - sharedRealm.beginTransaction(); - assertFalse(sharedRealm.hasTable("TableToRemove")); - thrown.expect(RealmError.class); - sharedRealm.removeTable("TableToRemove"); - sharedRealm.cancelTransaction(); - } - @Test public void renameTable() { sharedRealm.beginTransaction(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index a178189e22..f866cd1c9b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -32,6 +32,7 @@ import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.OsObjectStore; import io.realm.rule.TestSyncConfigurationFactory; import io.realm.util.SyncTestUtils; diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 3cd5a1280d..5948d3b29d 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -44,6 +44,7 @@ set(classes_LIST io.realm.internal.OsObjectSchemaInfo io.realm.internal.Collection io.realm.internal.NativeObjectReference io.realm.internal.OsCollectionChangeSet io.realm.internal.OsObject io.realm.internal.OsRealmConfig io.realm.internal.OsList + io.realm.internal.OsObjectStore ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp index a07c5415f4..4b864a0b6f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp @@ -109,3 +109,19 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrope CATCH_STD() return reinterpret_cast(nullptr); } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrimaryKeyProperty(JNIEnv* env, jclass, + jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + + try { + auto& object_schema = *reinterpret_cast(native_ptr); + auto* property = object_schema.primary_key_property(); + if (property) { + return reinterpret_cast(new Property(*property)); + } + } + CATCH_STD() + return reinterpret_cast(nullptr); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp new file mode 100644 index 0000000000..f63b7f937d --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "io_realm_internal_OsObjectStore.h" + +#include +#include + +#include "util.hpp" +#include "jni_util/java_exception_thrower.hpp" +#include "jni_util/java_exception_thrower.hpp" + +using namespace realm; +using namespace realm::jni_util; +using namespace realm::util; +using namespace realm::_impl; + +// FIXME: Enable after https://github.com/realm/realm-object-store/pull/550 merged +//static_assert(io_realm_internal_OsObjectStore_SCHEMA_NOT_VERSIONED == static_cast(ObjectStore::NotVersioned), +// ""); + +JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetPrimaryKeyForObject(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring j_class_name, + jstring j_pk_field_name) +{ + TR_ENTER_PTR(shared_realm_ptr) + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + JStringAccessor class_name_accessor(env, j_class_name); + JStringAccessor pk_field_name_accessor(env, j_pk_field_name); + + auto table = ObjectStore::table_for_object_type(shared_realm->read_group(), class_name_accessor); + if (!table) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, + format("Class '%1' doesn't exist.", StringData(class_name_accessor))); + } + + if (j_pk_field_name) { + // Not removal, check the column. + auto pk_column_ndx = table->get_column_index(pk_field_name_accessor); + if (pk_column_ndx == realm::npos) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, + format("Field '%1' doesn't exist in Class '%2'.", + StringData(pk_field_name_accessor), StringData(class_name_accessor))); + } + + // Check valid column type + auto field_type = table->get_column_type(pk_column_ndx); + if (field_type != type_Int && field_type != type_String) { + THROW_JAVA_EXCEPTION( + env, JavaExceptionDef::IllegalArgument, + format("Field '%1' is not a valid primary key type.", StringData(pk_field_name_accessor))); + } + + // Check duplicated values. The pk field must have been indexed before set as a PK. + if (table->get_distinct_view(pk_column_ndx).size() != table->size()) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, + format("Field '%1' cannot be set as primary key since there are duplicated " + "values for field '%1' in Class '%2'.", + StringData(pk_field_name_accessor), StringData(class_name_accessor))); + } + } + shared_realm->verify_in_write(); + ObjectStore::set_primary_key_for_object(shared_realm->read_group(), class_name_accessor, + pk_field_name_accessor); + } + CATCH_STD() +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_OsObjectStore_nativeGetPrimaryKeyForObject(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring j_class_name) +{ + TR_ENTER_PTR(shared_realm_ptr) + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + JStringAccessor class_name_accessor(env, j_class_name); + StringData pk_field_name = + ObjectStore::get_primary_key_for_object(shared_realm->read_group(), class_name_accessor); + return pk_field_name.size() == 0 ? nullptr : to_jstring(env, pk_field_name); + } + CATCH_STD() + return nullptr; +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetSchemaVersion(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jlong schema_version) +{ + TR_ENTER_PTR(shared_realm_ptr) + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + shared_realm->verify_in_write(); + ObjectStore::set_schema_version(shared_realm->read_group(), schema_version); + } + CATCH_STD() +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectStore_nativeGetSchemaVersion(JNIEnv* env, jclass, + jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return ObjectStore::get_schema_version(shared_realm->read_group()); + } + CATCH_STD() + return ObjectStore::NotVersioned; +} + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsObjectStore_nativeDeleteTableForObject(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jstring j_class_name) +{ + TR_ENTER_PTR(shared_realm_ptr) + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + JStringAccessor class_name_accessor(env, j_class_name); + shared_realm->verify_in_write(); + if (!ObjectStore::table_for_object_type(shared_realm->read_group(), class_name_accessor)) { + return JNI_FALSE; + } + ObjectStore::delete_data_for_object(shared_realm->read_group(), class_name_accessor); + return JNI_TRUE; + } + CATCH_STD() + return JNI_FALSE; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 37babfa46e..7cf19d9600 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -172,38 +172,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEn return static_cast(NULL); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersion(JNIEnv* env, jclass, - jlong shared_realm_ptr) -{ - TR_ENTER_PTR(shared_realm_ptr) - - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - try { - return static_cast(ObjectStore::get_schema_version(shared_realm->read_group())); - } - CATCH_STD() - return static_cast(ObjectStore::NotVersioned); -} - -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetVersion(JNIEnv* env, jclass, - jlong shared_realm_ptr, jlong version) -{ - TR_ENTER_PTR(shared_realm_ptr) - - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - try { - if (!shared_realm->is_in_transaction()) { - std::ostringstream ss; - ss << "Cannot set schema version when the realm is not in transaction."; - ThrowException(env, IllegalState, ss.str()); - return; - } - - ObjectStore::set_schema_version(shared_realm->read_group(), static_cast(version)); - } - CATCH_STD() -} - JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv* env, jclass, jlong shared_realm_ptr) { @@ -291,8 +259,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateTable(JNIEnv* env, jclass, jlong shared_realm_ptr, - jstring j_table_name, - jboolean is_pk_table) + jstring j_table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -303,24 +270,17 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateTable(JNI shared_realm->verify_in_write(); // throws Table* table; auto& group = shared_realm->read_group(); - if (is_pk_table) { - // sync::create_table() will add an extra column for stable ID which is not allowed for pk table. - table = LangBindHelper::add_table(group, table_name); // throws - } - else { #if REALM_ENABLE_SYNC - // Sync doesn't throw when table exists. - if (group.has_table(table_name)) { - THROW_JAVA_EXCEPTION( - env, JavaExceptionDef::IllegalArgument, - format(c_table_name_exists_exception_msg, table_name.substr(TABLE_PREFIX.length()))); - } - auto table_ref = sync::create_table(group, table_name); // throws - table = LangBindHelper::get_table(group, table_ref->get_index_in_group()); + // Sync doesn't throw when table exists. + if (group.has_table(table_name)) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, + format(c_table_name_exists_exception_msg, table_name.substr(TABLE_PREFIX.length()))); + } + auto table_ref = sync::create_table(group, table_name); // throws + table = LangBindHelper::get_table(group, table_ref->get_index_in_group()); #else - table = LangBindHelper::add_table(group, table_name); // throws + table = LangBindHelper::add_table(group, table_name); // throws #endif - } return reinterpret_cast(table); } catch (TableNameInUse& e) { @@ -426,26 +386,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIE CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRemoveTable(JNIEnv* env, jclass, - jlong shared_realm_ptr, - jstring table_name) -{ - TR_ENTER_PTR(shared_realm_ptr) - - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - try { - JStringAccessor name(env, table_name); - if (!shared_realm->is_in_transaction()) { - std::ostringstream ss; - ss << "Class " << name << " cannot be removed when the realm is not in transaction."; - ThrowException(env, IllegalState, ss.str()); - return; - } - shared_realm->read_group().remove_table(name); - } - CATCH_STD() -} - JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 51fb04384d..a0f0e96fe4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -37,6 +37,9 @@ static_assert(io_realm_internal_Table_MAX_BINARY_SIZE == Table::max_binary_size, static const char* c_null_values_cannot_set_required_msg = "The primary key field '%1' has 'null' values stored. It " "cannot be converted to a '@Required' primary key field."; +static const size_t CLASS_COLUMN_INDEX = 0; // ObjectStore::c_primaryKeyObjectClassColumnIndex +static const size_t FIELD_COLUMN_INDEX = 1; // ObjectStore::c_primaryKeyPropertyNameColumnIndex + static void finalize_table(jlong ptr); @@ -691,19 +694,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeIncrementLong(JNIEnv* CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLongUnique(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, - jlong value) -{ - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) { - return; - } - try { - TBL(nativeTablePtr)->set_int_unique(S(columnIndex), S(rowIndex), value); - } - CATCH_STD() -} - JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean(JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jboolean value, jboolean isDefault) @@ -762,28 +752,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString(JNIEnv* env, CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetStringUnique(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, - jstring value) -{ - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) { - return; - } - try { - if (value == nullptr) { - if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { - return; - } - TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), null{}); - } - else { - JStringAccessor value2(env, value); // throws - TBL(nativeTablePtr)->set_string_unique(S(columnIndex), S(rowIndex), value2); - } - } - CATCH_STD() -} - JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp(JNIEnv* env, jclass, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jlong timestampValue, jboolean isDefault) @@ -850,27 +818,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull(JNIEnv* env, j CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNullUnique(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) -{ - Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { - return; - } - if (!TBL_AND_ROW_INDEX_VALID(env, pTable, rowIndex)) { - return; - } - if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) { - return; - } - - try { - pTable->set_null_unique(S(columnIndex), S(rowIndex)); - } - CATCH_STD() -} - - JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetRowPtr(JNIEnv* env, jobject, jlong nativeTablePtr, jlong index) { @@ -1128,117 +1075,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull(JNIEnv* // FindAll - -// FIXME: reenable when find_first_timestamp() is implemented -/* -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindAllTimestamp( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong dateTimeValue) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) - return 0; - try { - TableView* pTableView = new TableView(TBL(nativeTablePtr)->find_all_timestamp(S(columnIndex), -from_milliseconds(dateTimeValue))); - return reinterpret_cast(pTableView); - } CATCH_STD() - return 0; -} -*/ - - -// experimental -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeLowerBoundInt(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong value) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { - return 0; - } - - Table* pTable = TBL(nativeTablePtr); - try { - return static_cast(pTable->lower_bound_int(S(columnIndex), S(value))); - } - CATCH_STD() - return 0; -} - - -// experimental -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeUpperBoundInt(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong value) -{ - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { - return 0; - } - - Table* pTable = TBL(nativeTablePtr); - try { - return static_cast(pTable->upper_bound_int(S(columnIndex), S(value))); - } - CATCH_STD() - return 0; -} - // -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetSortedViewMulti(JNIEnv* env, jobject, - jlong nativeTablePtr, - jlongArray columnIndices, - jbooleanArray ascending) -{ - Table* pTable = TBL(nativeTablePtr); - - JLongArrayAccessor long_arr(env, columnIndices); - JBooleanArrayAccessor bool_arr(env, ascending); - jsize arr_len = long_arr.size(); - jsize asc_len = bool_arr.size(); - - if (arr_len == 0) { - ThrowException(env, IllegalArgument, "You must provide at least one field name."); - return 0; - } - if (asc_len == 0) { - ThrowException(env, IllegalArgument, "You must provide at least one sort order."); - return 0; - } - if (arr_len != asc_len) { - ThrowException(env, IllegalArgument, "Number of column indices and sort orders do not match."); - return 0; - } - - std::vector> indices(S(arr_len)); - std::vector ascendings(S(arr_len)); - - for (int i = 0; i < arr_len; ++i) { - if (!TBL_AND_COL_INDEX_VALID(env, pTable, S(long_arr[i]))) { - return 0; - } - int colType = pTable->get_column_type(S(long_arr[i])); - switch (colType) { - case type_Int: - case type_Bool: - case type_String: - case type_Double: - case type_Float: - case type_Timestamp: - indices[i] = std::vector{S(long_arr[i])}; - ascendings[i] = S(bool_arr[i]); - break; - default: - ThrowException(env, IllegalArgument, "Sort is only support on String, Date, boolean, byte, short, " - "int, long and their boxed variants."); - return 0; - } - } - - try { - TableView* pTableView = new TableView(pTable->get_sorted_view(SortDescriptor(*pTable, indices, ascendings))); - return reinterpret_cast(pTableView); - } - CATCH_STD() - return 0; -} - JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetName(JNIEnv* env, jobject, jlong nativeTablePtr) { try { @@ -1252,143 +1090,12 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetName(JNIEnv* env return nullptr; } - -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeToJson(JNIEnv* env, jobject, jlong nativeTablePtr) -{ - Table* table = TBL(nativeTablePtr); - if (!TABLE_VALID(env, table)) { - return nullptr; - } - - // Write table to string in JSON format - try { - ostringstream ss; - ss.sync_with_stdio(false); // for performance - table->to_json(ss); - const string str = ss.str(); - return to_jstring(env, str); - } - CATCH_STD() - return nullptr; -} - JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid(JNIEnv*, jobject, jlong nativeTablePtr) { TR_ENTER_PTR(nativeTablePtr) return to_jbool(TBL(nativeTablePtr)->is_attached()); // noexcept } -// Checks if the primary key column contains any duplicate values, making it ineligible as a -// primary key. -static bool check_valid_primary_key_column(JNIEnv* env, Table* table, StringData column_name) // throws -{ - size_t column_index = table->get_column_index(column_name); - if (column_index == realm::not_found) { - std::ostringstream error_msg; - error_msg << table->get_name() << " does not contain the field \"" << column_name << "\""; - ThrowException(env, IllegalArgument, error_msg.str()); - } - DataType column_type = table->get_column_type(column_index); - TableView results = table->get_sorted_view(column_index); - - switch (column_type) { - case type_Int: - if (results.size() > 1) { - int64_t val = results.get_int(column_index, 0); - for (size_t i = 1; i < results.size(); i++) { - int64_t next_val = results.get_int(column_index, i); - if (val == next_val) { - std::ostringstream error_msg; - error_msg << "Field \"" << column_name << "\" cannot be a primary key, "; - error_msg << "it already contains duplicate values: " << val; - ThrowException(env, IllegalArgument, error_msg.str()); - return false; - } - else { - val = next_val; - } - } - } - return true; - - case type_String: - if (results.size() > 1) { - string str = results.get_string(column_index, 0); - for (size_t i = 1; i < results.size(); i++) { - string next_str = results.get_string(column_index, i); - if (str.compare(next_str) == 0) { - std::ostringstream error_msg; - error_msg << "Field \"" << column_name << "\" cannot be a primary key, "; - error_msg << "it already contains duplicate values: " << str; - ThrowException(env, IllegalArgument, error_msg.str()); - return false; - } - else { - str = next_str; - } - } - } - return true; - - default: - std::ostringstream error_msg; - error_msg << "Invalid primary key type for column: " << column_name; - ThrowException(env, IllegalArgument, error_msg.str()); - return false; - } -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey(JNIEnv* env, jobject, - jlong nativePrivateKeyTablePtr, - jlong nativeTablePtr, jstring columnName) -{ - try { - Table* table = TBL(nativeTablePtr); - Table* pk_table = TBL(nativePrivateKeyTablePtr); - const std::string table_name(table->get_name().substr(TABLE_PREFIX.length())); // Remove "class_" prefix - size_t row_index = - pk_table->find_first_string(io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX, table_name); - - if (columnName == NULL || env->GetStringLength(columnName) == 0) { - // No primary key provided => remove previous set keys - if (row_index != realm::not_found) { - pk_table->remove(row_index); - } - return io_realm_internal_Table_NO_PRIMARY_KEY; - } - else { - JStringAccessor new_primary_key_column_name(env, columnName); - size_t primary_key_column_index = table->get_column_index(new_primary_key_column_name); - if (row_index == realm::not_found) { - // No primary key is currently set - if (check_valid_primary_key_column(env, table, new_primary_key_column_name)) { - row_index = pk_table->add_empty_row(); - pk_table->set_string_unique(io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX, row_index, - table_name); - pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, - new_primary_key_column_name); - } - } - else { - // Primary key already exists - // We only wish to check for duplicate values if a column isn't already a primary key - StringData current_primary_key = - pk_table->get_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index); - if (new_primary_key_column_name != current_primary_key) { - if (check_valid_primary_key_column(env, table, new_primary_key_column_name)) { - pk_table->set_string(io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX, row_index, - new_primary_key_column_name); - } - } - } - - return static_cast(primary_key_column_index); - } - } - CATCH_STD() - return 0; -} - // 1) Fixes interop issue with Cocoa Realm where the Primary Key table had different types. // This affects: // - All Realms created by Cocoa and used by Realm-android up to 0.80.1 @@ -1411,9 +1118,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSetPrimaryKey(JNIEnv* JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded( JNIEnv*, jclass, jlong groupNativePtr, jlong privateKeyTableNativePtr) { - const size_t CLASS_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX; - const size_t FIELD_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX; - auto group = reinterpret_cast(groupNativePtr); Table* pk_table = TBL(privateKeyTableNativePtr); jboolean changed = JNI_FALSE; @@ -1464,10 +1168,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyT JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativePrimaryKeyTableNeedsMigration(JNIEnv*, jclass, jlong primaryKeyTableNativePtr) { - - const size_t CLASS_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX; - const size_t FIELD_COLUMN_INDEX = io_realm_internal_Table_PRIMARY_KEY_FIELD_COLUMN_INDEX; - Table* pk_table = TBL(primaryKeyTableNativePtr); // Fix wrong types (string, int) -> (string, string) @@ -1496,23 +1196,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema(JNIE return to_jbool(*TBL(thisTablePtr)->get_descriptor() == *TBL(otherTablePtr)->get_descriptor()); } - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeVersion(JNIEnv* env, jobject, jlong nativeTablePtr) -{ - bool valid = (TBL(nativeTablePtr) != nullptr); - if (valid) { - if (!TBL(nativeTablePtr)->is_attached()) { - ThrowException(env, IllegalState, "The Realm has been closed and is no longer accessible."); - return 0; - } - } - try { - return static_cast(TBL(nativeTablePtr)->get_version_counter()); - } - CATCH_STD() - return 0; -} - static void finalize_table(jlong ptr) { TR_ENTER_PTR(ptr) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index cd819a8608..6ba0f39a8b 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -35,6 +35,7 @@ import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; import io.realm.internal.InvalidRow; +import io.realm.internal.OsObjectStore; import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSchemaInfo; import io.realm.internal.RealmProxyMediator; @@ -75,7 +76,7 @@ abstract class BaseRealm implements Closeable { // Which RealmCache is this Realm associated to. It is null if the Realm instance is opened without being put into a // cache. It is also null if the Realm is closed. private RealmCache realmCache; - protected SharedRealm sharedRealm; + public SharedRealm sharedRealm; private boolean shouldCloseSharedRealm; private SharedRealm.SchemaChangedCallback schemaChangedCallback = new SharedRealm.SchemaChangedCallback() { @Override @@ -476,7 +477,7 @@ public RealmConfiguration getConfiguration() { * @return the schema version for the Realm file backing this Realm. */ public long getVersion() { - return sharedRealm.getSchemaVersion(); + return OsObjectStore.getSchemaVersion(sharedRealm); } /** @@ -535,11 +536,6 @@ public boolean isEmpty() { return sharedRealm.isEmpty(); } - // package protected so unit tests can access it - void setVersion(long version) { - sharedRealm.setSchemaVersion(version); - } - /** * Returns the schema for this Realm. * diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 1dec0499b0..ec14a7169e 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -23,6 +23,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.internal.CheckedRow; import io.realm.internal.OsObject; +import io.realm.internal.OsObjectStore; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.log.RealmLog; @@ -51,8 +52,28 @@ public class DynamicRealm extends BaseRealm { private final RealmSchema schema; - private DynamicRealm(RealmCache cache) { + private DynamicRealm(final RealmCache cache) { super(cache, null); + RealmCache.invokeWithGlobalRefCount(cache.getConfiguration(), new RealmCache.Callback() { + @Override + public void onResult(int count) { + if (count > 0) { + return; + } + if (cache.getConfiguration().isReadOnly()) { + return; + } + if (OsObjectStore.getSchemaVersion(sharedRealm) != OsObjectStore.SCHEMA_NOT_VERSIONED) { + return; + } + sharedRealm.beginTransaction(); + if (OsObjectStore.getSchemaVersion(sharedRealm) == OsObjectStore.SCHEMA_NOT_VERSIONED) { + // To initialize the meta table. + OsObjectStore.setSchemaVersion(sharedRealm, OsObjectStore.SCHEMA_NOT_VERSIONED); + } + sharedRealm.commitTransaction(); + } + }); this.schema = new MutableRealmSchema(this); } @@ -111,11 +132,12 @@ public static RealmAsyncTask getInstanceAsync(RealmConfiguration configuration, public DynamicRealmObject createObject(String className) { checkIfValid(); Table table = schema.getTable(className); + String pkField = OsObjectStore.getPrimaryKeyForObject(sharedRealm, className); // Check and throw the exception earlier for a better exception message. - if (table.hasPrimaryKey()) { + if (pkField != null) { throw new RealmException(String.format(Locale.US, - "'%s' has a primary key, use" + - " 'createObject(String, Object)' instead.", className)); + "'%s' has a primary key field '%s', use 'createObject(String, Object)' instead.", + className, pkField)); } return new DynamicRealmObject(this, CheckedRow.getFromRow(OsObject.create(table))); @@ -274,6 +296,19 @@ public RealmSchema getSchema() { return schema; } + /** + * Set the schema version of this dynamic realm to the given version number. If the meta table doesn't exist, this + * will create the meta table first. + *

            + * NOTE: This API is for internal testing only. Except testing, the schema version should always be set by the + * Object Store during schema initialization or migration. + * + * @param version the schema version to be set. + */ + void setVersion(long version) { + OsObjectStore.setSchemaVersion(sharedRealm, version); + } + /** * Encapsulates a Realm transaction. *

            diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index 8514eef8cd..2281c0ff8a 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -18,8 +18,10 @@ import java.util.Locale; +import javax.annotation.Nonnull; import javax.annotation.Nullable; +import io.realm.internal.OsObjectStore; import io.realm.internal.Table; /** @@ -52,18 +54,17 @@ public RealmObjectSchema setClassName(String className) { throw new IllegalArgumentException("Class already exists: " + className); } // in case this table has a primary key, we need to transfer it after renaming the table. - String oldTableName = null; - String pkField = null; - if (table.hasPrimaryKey()) { - oldTableName = table.getName(); - pkField = getPrimaryKey(); - table.setPrimaryKey(null); - } //noinspection ConstantConditions - realm.sharedRealm.renameTable(table.getName(), internalTableName); - if (pkField != null && !pkField.isEmpty()) { + @Nonnull String oldTableName = table.getName(); + @Nonnull String oldClassName = table.getClassName(); + String pkField = OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, oldClassName); + if (pkField != null) { + OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, oldClassName, null); + } + realm.sharedRealm.renameTable(oldTableName, internalTableName); + if (pkField != null) { try { - table.setPrimaryKey(pkField); + OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, className, pkField); } catch (Exception e) { // revert the table name back when something goes wrong //noinspection ConstantConditions @@ -139,8 +140,9 @@ public RealmObjectSchema removeField(String fieldName) { throw new IllegalStateException(fieldName + " does not exist."); } long columnIndex = getColumnIndex(fieldName); - if (table.getPrimaryKey() == columnIndex) { - table.setPrimaryKey(null); + String className = getClassName(); + if (fieldName.equals(OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, className))) { + OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, className, fieldName); } table.removeColumn(columnIndex); return this; @@ -191,29 +193,33 @@ public RealmObjectSchema addPrimaryKey(String fieldName) { checkAddPrimaryKeyForSync(); checkLegalName(fieldName); checkFieldExists(fieldName); - if (table.hasPrimaryKey()) { - throw new IllegalStateException("A primary key is already defined"); + String currentPKField = OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, getClassName()); + if (currentPKField != null) { + throw new IllegalStateException( + String.format(Locale.ENGLISH, "Field '%s' has been already defined as primary key.", + currentPKField)); } - table.setPrimaryKey(fieldName); long columnIndex = getColumnIndex(fieldName); if (!table.hasSearchIndex(columnIndex)) { // No exception will be thrown since adding PrimaryKey implies the column has an index. table.addSearchIndex(columnIndex); } + OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, getClassName(), fieldName); return this; } @Override public RealmObjectSchema removePrimaryKey() { realm.checkNotInSync(); // Destructive modifications are not permitted. - if (!table.hasPrimaryKey()) { + String pkField = OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, getClassName()); + if (pkField == null) { throw new IllegalStateException(getClassName() + " doesn't have a primary key."); } - long columnIndex = table.getPrimaryKey(); + long columnIndex = table.getColumnIndex(pkField); if (table.hasSearchIndex(columnIndex)) { table.removeSearchIndex(columnIndex); } - table.setPrimaryKey(""); + OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, getClassName(), null); return this; } diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java index a661b255bf..8d36769f95 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java @@ -18,6 +18,7 @@ import java.util.Locale; +import io.realm.internal.OsObjectStore; import io.realm.internal.Table; /** @@ -85,12 +86,9 @@ public void remove(String className) { realm.checkNotInSync(); // Destructive modifications are not permitted. checkNotEmpty(className, EMPTY_STRING_MSG); String internalTableName = Table.getTableNameForClass(className); - checkHasTable(className, "Cannot remove class because it is not in this Realm: " + className); - Table table = getTable(className); - if (table.hasPrimaryKey()) { - table.setPrimaryKey(null); + if (!OsObjectStore.deleteTableForObject(realm.getSharedRealm(), className)) { + throw new IllegalArgumentException("Cannot remove class because it is not in this Realm: " + className); } - realm.getSharedRealm().removeTable(internalTableName); removeFromClassNameToSchemaMap(internalTableName); } @@ -107,11 +105,9 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { } // Checks if there is a primary key defined for the old class. - Table oldTable = getTable(oldClassName); - String pkField = null; - if (oldTable.hasPrimaryKey()) { - pkField = oldTable.getColumnName(oldTable.getPrimaryKey()); - oldTable.setPrimaryKey(null); + String pkField = OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, oldClassName); + if (pkField != null) { + OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, oldClassName, null); } realm.getSharedRealm().renameTable(oldInternalName, newInternalName); @@ -119,7 +115,7 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { // Sets the primary key for the new class if necessary. if (pkField != null) { - table.setPrimaryKey(pkField); + OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, newClassName, pkField); } RealmObjectSchema objectSchema = removeFromClassNameToSchemaMap(oldInternalName); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index bd85dd7bb3..f95bb86647 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -55,6 +55,8 @@ import io.realm.internal.ColumnIndices; import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.OsObjectStore; import io.realm.internal.OsSchemaInfo; import io.realm.internal.RealmCore; import io.realm.internal.RealmNotifier; @@ -765,8 +767,9 @@ public E createObjectFromJson(Class clazz, InputStream } checkIfValid(); E realmObject; - Table table = schema.getTable(clazz); - if (table.hasPrimaryKey()) { + + if (OsObjectStore.getPrimaryKeyForObject( + sharedRealm, configuration.getSchemaMediator().getSimpleClassName(clazz)) != null) { // As we need the primary key value we have to first parse the entire input stream as in the general // case that value might be the last property. :( Scanner scanner = null; @@ -873,7 +876,8 @@ E createObjectInternal( List excludeFields) { Table table = schema.getTable(clazz); // Checks and throws the exception earlier for a better exception message. - if (table.hasPrimaryKey()) { + if (OsObjectStore.getPrimaryKeyForObject( + sharedRealm, configuration.getSchemaMediator().getSimpleClassName(clazz)) != null) { throw new RealmException(String.format(Locale.US, "'%s' has a primary key, use" + " 'createObject(Class, Object)' instead.", table.getClassName())); } @@ -1560,7 +1564,10 @@ private void checkNotNullObject(E object) { } private void checkHasPrimaryKey(Class clazz) { - if (!schema.getTable(clazz).hasPrimaryKey()) { + String className = configuration.getSchemaMediator().getSimpleClassName(clazz); + OsObjectSchemaInfo objectSchemaInfo = sharedRealm.getSchemaInfo().getObjectSchemaInfo(className); + + if (objectSchemaInfo.getPrimaryKeyProperty() == null) { throw new IllegalArgumentException("A RealmObject with no @PrimaryKey cannot be updated: " + clazz.toString()); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 9eaf1645e6..78499e64b5 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -35,6 +35,7 @@ import io.realm.internal.InvalidRow; import io.realm.internal.OsList; +import io.realm.internal.OsObjectStore; import io.realm.internal.RealmObjectProxy; import io.realm.rx.CollectionChange; @@ -291,7 +292,8 @@ private E copyToRealmIfNeeded(E object) { // At this point the object can only be a typed object, so the backing Realm cannot be a DynamicRealm. Realm realm = (Realm) this.realm; - if (realm.getTable(object.getClass()).hasPrimaryKey()) { + if (OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), + realm.getConfiguration().getSchemaMediator().getSimpleClassName(object.getClass())) != null) { return realm.copyToRealmOrUpdate(object); } else { return realm.copyToRealm(object); diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index ab91b6c740..e490cd33ca 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -27,6 +27,7 @@ import io.realm.annotations.Required; import io.realm.internal.ColumnInfo; import io.realm.internal.OsObject; +import io.realm.internal.OsObjectStore; import io.realm.internal.Table; import io.realm.internal.fields.FieldDescriptor; @@ -319,8 +320,8 @@ public boolean isNullable(String fieldName) { * @see #addPrimaryKey(String) */ public boolean isPrimaryKey(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return columnIndex == table.getPrimaryKey(); + checkFieldExists(fieldName); + return fieldName.equals(OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, getClassName())); } /** @@ -330,7 +331,7 @@ public boolean isPrimaryKey(String fieldName) { * @see io.realm.annotations.PrimaryKey */ public boolean hasPrimaryKey() { - return table.hasPrimaryKey(); + return OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, getClassName()) != null; } /** @@ -340,10 +341,11 @@ public boolean hasPrimaryKey() { * @throws IllegalStateException if the class doesn't have a primary key defined. */ public String getPrimaryKey() { - if (!table.hasPrimaryKey()) { + String pkField = OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, getClassName()); + if (pkField == null) { throw new IllegalStateException(getClassName() + " doesn't have a primary key."); } - return table.getColumnName(table.getPrimaryKey()); + return pkField; } /** @@ -400,7 +402,9 @@ RealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean if (indexed) { table.addSearchIndex(columnIndex); } - if (primary) { table.setPrimaryKey(name); } + if (primary) { + OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, getClassName(), name); + } return this; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index 651dc416be..8422bc5bfb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -174,11 +174,11 @@ public static long createRow(Table table) { } private static long getAndVerifyPrimaryKeyColumnIndex(Table table) { - long primaryKeyColumnIndex = table.getPrimaryKey(); - if (primaryKeyColumnIndex == Table.NO_PRIMARY_KEY) { + String pkField = OsObjectStore.getPrimaryKeyForObject(table.getSharedRealm(), table.getClassName()); + if (pkField == null) { throw new IllegalStateException(table.getName() + " has no primary key defined."); } - return primaryKeyColumnIndex; + return table.getColumnIndex(pkField); } // TODO: consider to return a OsObject instead when integrating with Object Store's object accessor. @@ -218,10 +218,12 @@ public static UncheckedRow createWithPrimaryKey(Table table, @Nullable Object pr * This is used for the fast bulk insertion. * * @param table the table where the object is created. + * @param primaryKeyColumnIndex the column index of primary key field. + * @param primaryKeyValue the primary key value. * @return a newly created {@code UncheckedRow}. */ - public static long createRowWithPrimaryKey(Table table, Object primaryKeyValue) { - long primaryKeyColumnIndex = getAndVerifyPrimaryKeyColumnIndex(table); + // FIXME: Proxy could just pass the pk index here which is much faster. + public static long createRowWithPrimaryKey(Table table, long primaryKeyColumnIndex, Object primaryKeyValue) { RealmFieldType type = table.getColumnType(primaryKeyColumnIndex); final SharedRealm sharedRealm = table.getSharedRealm(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java index 58892d9630..36c7e9aef5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; + import io.realm.RealmFieldType; /** @@ -145,6 +147,17 @@ public Property getProperty(String propertyName) { return new Property(nativeGetProperty(nativePtr, propertyName)); } + /** + * Returns the primary key property for this {@code ObjectSchema}. + * + * @return a {@link Property} object of the primary key property, {@code null} if this {@code ObjectSchema} doesn't + * contains a primary key. + */ + public @Nullable Property getPrimaryKeyProperty() { + long propertyPtr = nativeGetPrimaryKeyProperty(nativePtr); + return propertyPtr == 0 ? null : new Property(nativeGetPrimaryKeyProperty(nativePtr)); + } + @Override public long getNativePtr() { return nativePtr; @@ -165,4 +178,7 @@ public long getNativeFinalizerPtr() { // Throw ISE if the property doesn't exist. private static native long nativeGetProperty(long nativePtr, String propertyName); + + // Return nullptr if it doesn't have a primary key. + private static native long nativeGetPrimaryKeyProperty(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java new file mode 100644 index 0000000000..5e05a43310 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import javax.annotation.Nullable; + +/** + * Java wrapper for methods in object_store.hpp. + */ +public class OsObjectStore { + + public final static long SCHEMA_NOT_VERSIONED = -1; + + /** + * Sets the primary key field for the given class. + *

            + * NOTE: The search index has to be added to the field before calling this method. + * + * @throws IllegalStateException if it is not in a transaction. + * @throws IllegalStateException if the given class doesn't exist. + * @throws IllegalStateException if the given field doesn't exist. + * @throws IllegalStateException if the given field is not a valid type for primary key. + * @throws IllegalStateException if there are duplicated values for the given field. + */ + public static void setPrimaryKeyForObject(SharedRealm sharedRealm, String className, + @Nullable String primaryKeyFieldName) { + nativeSetPrimaryKeyForObject(sharedRealm.getNativePtr(), className, primaryKeyFieldName); + } + + public static @Nullable String getPrimaryKeyForObject(SharedRealm sharedRealm, String className) { + return nativeGetPrimaryKeyForObject(sharedRealm.getNativePtr(), className); + } + + /** + * Sets the schema version to the given {@link SharedRealm}. This method will create meta tables if they don't exist. + * @throws IllegalStateException if it is not in a transaction. + */ + public static void setSchemaVersion(SharedRealm sharedRealm, long schemaVersion) { + nativeSetSchemaVersion(sharedRealm.getNativePtr(), schemaVersion); + } + + /** + * Returns the schema version of the given {@link SharedRealm}. If meta tables don't exist, this will return + * {@link #SCHEMA_NOT_VERSIONED}. + */ + public static long getSchemaVersion(SharedRealm sharedRealm) { + return nativeGetSchemaVersion(sharedRealm.getNativePtr()); + } + + /** + * Deletes the table with the given class name. + * + * @return {@code true} if the table has been deleted. {@code false} if the table doesn't exist. + * @throws IllegalStateException if it is not in a transaction. + */ + public static boolean deleteTableForObject(SharedRealm sharedRealm, String className) { + return nativeDeleteTableForObject(sharedRealm.getNativePtr(), className); + } + + private native static void nativeSetPrimaryKeyForObject(long sharedRealmPtr, String className, + @Nullable String primaryKeyFieldName); + + private native static @Nullable String nativeGetPrimaryKeyForObject(long sharedRealmPtr, String className); + + private native static void nativeSetSchemaVersion(long sharedRealmPtr, long schemaVersion); + + private native static long nativeGetSchemaVersion(long sharedRealmPtr); + + private native static boolean nativeDeleteTableForObject(long sharedRealmPtr, String className); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index 54777969ac..7bc1a37072 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -70,14 +70,28 @@ public abstract class RealmProxyMediator { /** * Returns the name that Realm should use for all its internal tables. This is the un-obfuscated name of the - * class. + * class with the Realm table prefix. * * @param clazz the {@link RealmObject} class reference. - * @return the simple name of an RealmObject class (before it has been obfuscated). + * @return the simple name of an RealmObject class (before it has been obfuscated) with Realm table prefix. * @throws java.lang.NullPointerException if null is given as argument. + * @deprecated use {{@link #getSimpleClassName(Class)}} instead. */ + @Deprecated public abstract String getTableName(Class clazz); + /** + * Returns the name that Realm should use for all its internal tables. This is the un-obfuscated simple name of the + * class. + * + * @param clazz the {@link RealmObject} class reference. + * @return the simple name of an RealmObject class (before it has been obfuscated). + */ + public String getSimpleClassName(Class clazz) { + Class originalClass = Util.getOriginalModelClass(clazz); + return Table.getClassNameForTable(getTableName(originalClass)); + } + /** * Creates a new instance of an {@link RealmObjectProxy} for the given RealmObject class. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 6f4e971551..6165ce00d0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -48,9 +48,6 @@ public static void initialize(File tempDirectory) { // already initialized return; } - if (tempDirectory == null) { - throw new IllegalArgumentException("'tempDirectory' must not be null."); - } String temporaryDirectoryPath = tempDirectory.getAbsolutePath(); if (!tempDirectory.isDirectory() && !tempDirectory.mkdirs() && !tempDirectory.isDirectory()) { @@ -246,14 +243,6 @@ public boolean isInTransaction() { return nativeIsInTransaction(nativePtr); } - public void setSchemaVersion(long schemaVersion) { - nativeSetVersion(nativePtr, schemaVersion); - } - - public long getSchemaVersion() { - return nativeGetVersion(nativePtr); - } - // FIXME: This should be removed, migratePrimaryKeyTableIfNeeded is using it which should be in Object Store instead? long getGroupNative() { return nativeReadGroup(nativePtr); @@ -283,19 +272,7 @@ public Table getTable(String name) { * @return a created {@link Table} object. */ public Table createTable(String name) { - return new Table(this, nativeCreateTable(nativePtr, name, false)); - } - - /** - * Creates a primary key table with then given name. Native assertion will happen if the table with the same name - * exists. This function is different from {@link #createTable(String)} which will call {@code create_table()} from - * sync to do the creation. This will always call the core's {@code add_table()} to avoid creating the stable id - * column for pk table. - * - * @return a created {@link Table} object. - */ - public Table createPkTable() { - return new Table(this, nativeCreateTable(nativePtr, Table.PRIMARY_KEY_TABLE_NAME, true)); + return new Table(this, nativeCreateTable(nativePtr, name)); } /** @@ -319,10 +296,6 @@ public void renameTable(String oldName, String newName) { nativeRenameTable(nativePtr, oldName, newName); } - public void removeTable(String name) { - nativeRemoveTable(nativePtr, name); - } - public String getTableName(int index) { return nativeGetTableName(nativePtr, index); } @@ -524,10 +497,6 @@ private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealm private static native boolean nativeIsInTransaction(long nativeSharedRealmPtr); - private static native long nativeGetVersion(long nativeSharedRealmPtr); - - private static native void nativeSetVersion(long nativeSharedRealmPtr, long version); - private static native long nativeReadGroup(long nativeSharedRealmPtr); private static native boolean nativeIsEmpty(long nativeSharedRealmPtr); @@ -540,9 +509,7 @@ private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealm private static native long nativeGetTable(long nativeSharedRealmPtr, String tableName); // Throw IAE if the table exists already. - // FIXME: isPkTable should be removed after integration with OS schema. All the meta tables should be handled in - // the Object Store. - private static native long nativeCreateTable(long nativeSharedRealmPtr, String tableName, boolean isPkTable); + private static native long nativeCreateTable(long nativeSharedRealmPtr, String tableName); // Throw IAE if the table exists already. // If isStringType is false, the PK field will be created as an integer PK field. @@ -556,8 +523,6 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare private static native void nativeRenameTable(long nativeSharedRealmPtr, String oldTableName, String newTableName); - private static native void nativeRemoveTable(long nativeSharedRealmPtr, String tableName); - private static native long nativeSize(long nativeSharedRealmPtr); private static native void nativeWriteCopy(long nativeSharedRealmPtr, String path, @Nullable byte[] key); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 6162d8d820..cb2f9b551f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -21,7 +21,6 @@ import javax.annotation.Nullable; import io.realm.RealmFieldType; -import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; @@ -30,7 +29,7 @@ * (define/insert/delete/update) a table has. All the native communications to the Realm C++ library are also handled by * this class. */ -public class Table implements TableSchema, NativeObject { +public class Table implements NativeObject { private static final String TABLE_PREFIX = Util.getTablePrefix(); private static final int TABLE_NAME_MAX_LENGTH = 63; // Max length of table names @@ -41,11 +40,6 @@ public class Table implements TableSchema, NativeObject { public static final int NO_MATCH = -1; static final String PRIMARY_KEY_TABLE_NAME = "pk"; - private static final String PRIMARY_KEY_CLASS_COLUMN_NAME = "pk_table"; - private static final long PRIMARY_KEY_CLASS_COLUMN_INDEX = 0; - private static final String PRIMARY_KEY_FIELD_COLUMN_NAME = "pk_property"; - private static final long PRIMARY_KEY_FIELD_COLUMN_INDEX = 1; - public static final long NO_PRIMARY_KEY = -2; public static final int MAX_BINARY_SIZE = 0xFFFFF8 - 8/*array header size*/; public static final int MAX_STRING_SIZE = 0xFFFFF8 - 8/*array header size*/ - 1; @@ -56,7 +50,6 @@ public class Table implements TableSchema, NativeObject { private final NativeContext context; private final SharedRealm sharedRealm; - private long cachedPrimaryKeyColumnIndex = NO_MATCH; Table(Table parent, long nativePointer) { this(parent.sharedRealm, nativePointer); @@ -117,7 +110,6 @@ public long addColumn(RealmFieldType type, String name, boolean isNullable) { * * @return the index of the new column. */ - @Override public long addColumn(RealmFieldType type, String name) { return addColumn(type, name, false); } @@ -133,40 +125,30 @@ public long addColumnLink(RealmFieldType type, String name, Table table) { } /** - * Removes a column in the table dynamically. If {@code columnIndex} is smaller than the primary - * key column index, {@link #invalidateCachedPrimaryKeyIndex()} will be called to recalculate the - * primary key column index. + * Removes a column in the table dynamically. *

            - *

            It should be noted if {@code columnIndex} is the same as the primary key column index, + * It should be noted if {@code columnIndex} is the same as the primary key column index, * the primary key column is removed from the meta table. * * @param columnIndex the column index to be removed. */ - @Override public void removeColumn(long columnIndex) { + final String className = getClassName(); // Checks the PK column index before removing a column. We don't know if we're hitting a PK col, // but it should be noted that once a column is removed, there is no way we can find whether // a PK exists or not. - final long oldPkColumnIndex = getPrimaryKey(); + final String columnName = getColumnName(columnIndex); + final String pkName = OsObjectStore.getPrimaryKeyForObject(sharedRealm, getClassName()); // First removes a column. If there is no error, we can proceed. Otherwise, it will stop here. nativeRemoveColumn(nativePtr, columnIndex); - // Checks if a PK exists and takes actions if there is. This is same as hasPrimaryKey(), but - // this relies on the local cache. - if (oldPkColumnIndex >= 0) { - + // Checks if a PK exists and takes actions if there is. + if (columnName.equals(pkName)) { // In case we're hitting PK column, we should remove the PK as it is either 1) a user has // forgotten to remove PK or 2) removeColumn gets called before setPrimaryKey(null) is called. // Since there is no danger in removing PK twice, we'll do it here to be on safe side. - if (oldPkColumnIndex == columnIndex) { - setPrimaryKey(null); - - // But if you remove a column with a smaller index than that of PK column, you need to - // recalculate the PK column index as core could have changed its column index. - } else if (oldPkColumnIndex > columnIndex) { - invalidateCachedPrimaryKeyIndex(); - } + OsObjectStore.setPrimaryKeyForObject(sharedRealm, className, null); } } @@ -177,34 +159,20 @@ public void removeColumn(long columnIndex) { * @param columnIndex the column index to be renamed. * @param newName a new name replacing the old column name. * @throws IllegalArgumentException if {@code newFieldName} is an empty string, or exceeds field name length limit. - * @throws IllegalStateException if a PrimaryKey column name could not be found in the meta table, but {@link #getPrimaryKey()} returns an index. */ - @Override public void renameColumn(long columnIndex, String newName) { verifyColumnName(newName); // Gets the old column name. We'll assume that the old column name is *NOT* an empty string. final String oldName = nativeGetColumnName(nativePtr, columnIndex); - // Also old pk index. Once a column name changes, there is no way you can find the column name - // by old name. - final long oldPkColumnIndex = getPrimaryKey(); + final String pkName = OsObjectStore.getPrimaryKeyForObject(sharedRealm, getClassName()); // Then let's try to rename a column. If an error occurs for some reasons, we'll throw. nativeRenameColumn(nativePtr, columnIndex, newName); // Renames a primary key. At this point, renaming the column name should have been fine. - if (oldPkColumnIndex == columnIndex) { + if (oldName.equals(pkName)) { try { - Table pkTable = getPrimaryKeyTable(); - if (pkTable == null) { - throw new IllegalStateException( - "Table is not created from a SharedRealm, primary key is not available"); - } - long pkRowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, getClassName()); - if (pkRowIndex != NO_MATCH) { - nativeSetString(pkTable.nativePtr, PRIMARY_KEY_FIELD_COLUMN_INDEX, pkRowIndex, newName, false); - } else { - throw new IllegalStateException("Non-existent PrimaryKey column cannot be renamed"); - } + OsObjectStore.setPrimaryKeyForObject(sharedRealm, getClassName(), newName); } catch (Exception e) { // We failed to rename the pk meta table. roll back the column name, not pk meta table // then rethrow. @@ -335,36 +303,6 @@ public void moveLastOver(long rowIndex) { nativeMoveLastOver(nativePtr, rowIndex); } - private boolean isPrimaryKeyColumn(long columnIndex) { - return columnIndex == getPrimaryKey(); - } - - /** - * Returns the column index for the primary key. - * - * @return the column index or {@code #NO_MATCH} if no primary key is set. - */ - public long getPrimaryKey() { - if (cachedPrimaryKeyColumnIndex >= 0 || cachedPrimaryKeyColumnIndex == NO_PRIMARY_KEY) { - return cachedPrimaryKeyColumnIndex; - } else { - Table pkTable = getPrimaryKeyTable(); - if (pkTable == null) { - return NO_PRIMARY_KEY; // Free table = No primary key. - } - - long rowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, getClassName()); - if (rowIndex != NO_MATCH) { - String pkColumnName = pkTable.getUncheckedRow(rowIndex).getString(PRIMARY_KEY_FIELD_COLUMN_INDEX); - cachedPrimaryKeyColumnIndex = getColumnIndex(pkColumnName); - } else { - cachedPrimaryKeyColumnIndex = NO_PRIMARY_KEY; - } - - return cachedPrimaryKeyColumnIndex; - } - } - /** * Checks if a given column is a primary key column. * @@ -372,53 +310,7 @@ public long getPrimaryKey() { * @return {@code true} if column is a primary key, {@code false} otherwise. */ private boolean isPrimaryKey(long columnIndex) { - return columnIndex >= 0 && columnIndex == getPrimaryKey(); - } - - /** - * Checks if a table has a primary key. - * - * @return {@code true} if primary key is defined, {@code false} otherwise. - */ - public boolean hasPrimaryKey() { - return getPrimaryKey() >= 0; - } - - void checkStringValueIsLegal(long columnIndex, long rowToUpdate, String value) { - if (isPrimaryKey(columnIndex)) { - long rowIndex = findFirstString(columnIndex, value); - if (rowIndex != rowToUpdate && rowIndex != NO_MATCH) { - throwDuplicatePrimaryKeyException(value); - } - } - } - - void checkIntValueIsLegal(long columnIndex, long rowToUpdate, long value) { - if (isPrimaryKeyColumn(columnIndex)) { - long rowIndex = findFirstLong(columnIndex, value); - if (rowIndex != rowToUpdate && rowIndex != NO_MATCH) { - throwDuplicatePrimaryKeyException(value); - } - } - } - - // Checks if it is ok to use null value for given row and column. - void checkDuplicatedNullForPrimaryKeyValue(long columnIndex, long rowToUpdate) { - if (isPrimaryKeyColumn(columnIndex)) { - RealmFieldType type = getColumnType(columnIndex); - switch (type) { - case STRING: - case INTEGER: - long rowIndex = findFirstNull(columnIndex); - if (rowIndex != rowToUpdate && rowIndex != NO_MATCH) { - throwDuplicatePrimaryKeyException("null"); - } - break; - default: - // Since it is sufficient to check the existence of duplicated null values - // on PrimaryKey in supported types only, this part is left empty. - } - } + return getColumnName(columnIndex).equals(OsObjectStore.getPrimaryKeyForObject(sharedRealm, getClassName())); } /** @@ -529,7 +421,6 @@ public CheckedRow getCheckedRow(long index) { public void setLong(long columnIndex, long rowIndex, long value, boolean isDefault) { checkImmutable(); - checkIntValueIsLegal(columnIndex, rowIndex, value); nativeSetLong(nativePtr, columnIndex, rowIndex, value, isDefault); } @@ -567,13 +458,11 @@ public void setDate(long columnIndex, long rowIndex, Date date, boolean isDefaul * @param rowIndex 0 based index value of the cell row. * @param value a String value to set in the cell. */ - public void setString(long columnIndex, long rowIndex, String value, boolean isDefault) { + public void setString(long columnIndex, long rowIndex, @Nullable String value, boolean isDefault) { checkImmutable(); if (value == null) { - checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); nativeSetNull(nativePtr, columnIndex, rowIndex, isDefault); } else { - checkStringValueIsLegal(columnIndex, rowIndex, value); nativeSetString(nativePtr, columnIndex, rowIndex, value, isDefault); } } @@ -590,7 +479,6 @@ public void setLink(long columnIndex, long rowIndex, long value, boolean isDefau public void setNull(long columnIndex, long rowIndex, boolean isDefault) { checkImmutable(); - checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); nativeSetNull(nativePtr, columnIndex, rowIndex, isDefault); } @@ -604,54 +492,6 @@ public void removeSearchIndex(long columnIndex) { nativeRemoveSearchIndex(nativePtr, columnIndex); } - /** - * Defines a primary key for this table. This needs to be called manually before inserting data into the table. - * - * @param columnName the name of the field that will function primary key. "" or {@code null} will remove any - * previous set magic key. - * @throws io.realm.exceptions.RealmException if it is not possible to set the primary key due to the column - * not having distinct values (i.e. violating the primary key constraint). - */ - public void setPrimaryKey(@Nullable String columnName) { - Table pkTable = getPrimaryKeyTable(); - if (pkTable == null) { - throw new RealmException("Primary keys are only supported if Table is part of a Group"); - } - cachedPrimaryKeyColumnIndex = nativeSetPrimaryKey(pkTable.nativePtr, nativePtr, columnName); - } - - public void setPrimaryKey(long columnIndex) { - setPrimaryKey(nativeGetColumnName(nativePtr, columnIndex)); - } - - private Table getPrimaryKeyTable() { - if (sharedRealm == null) { - return null; - } - - // FIXME: The PK table creation should be handle by Object Store after integration of OS Schema. - if (!sharedRealm.hasTable(PRIMARY_KEY_TABLE_NAME)) { - sharedRealm.createPkTable(); - } - - Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); - if (pkTable.getColumnCount() == 0) { - checkImmutable(); - long columnIndex = pkTable.addColumn(RealmFieldType.STRING, PRIMARY_KEY_CLASS_COLUMN_NAME); - pkTable.addSearchIndex(columnIndex); - pkTable.addColumn(RealmFieldType.STRING, PRIMARY_KEY_FIELD_COLUMN_NAME); - } - - return pkTable; - } - - /** - * Invalidates a cached primary key column index for the table. - */ - private void invalidateCachedPrimaryKeyIndex() { - cachedPrimaryKeyColumnIndex = NO_MATCH; - } - /* * 1) Migration required to fix https://github.com/realm/realm-java/issues/1059 * This will convert INTEGER column to the corresponding STRING column if needed. @@ -773,15 +613,6 @@ public long findFirstNull(long columnIndex) { return nativeFindFirstNull(nativePtr, columnIndex); } - // Experimental feature - public long lowerBoundLong(long columnIndex, long value) { - return nativeLowerBoundInt(nativePtr, columnIndex, value); - } - - public long upperBoundLong(long columnIndex, long value) { - return nativeUpperBoundInt(nativePtr, columnIndex, value); - } - // /** @@ -804,10 +635,6 @@ public String getClassName() { return getClassNameForTable(getName()); } - public String toJson() { - return nativeToJson(nativePtr); - } - @Override public String toString() { long columnCount = getColumnCount(); @@ -817,10 +644,6 @@ public String toString() { stringBuilder.append(getName()); stringBuilder.append(" "); } - if (hasPrimaryKey()) { - String pkFieldName = getColumnName(getPrimaryKey()); - stringBuilder.append("has \'").append(pkFieldName).append("\' field as a PrimaryKey, and "); - } stringBuilder.append("contains "); stringBuilder.append(columnCount); stringBuilder.append(" columns: "); @@ -857,24 +680,6 @@ public boolean hasSameSchema(Table table) { return nativeHasSameSchema(this.nativePtr, table.nativePtr); } - /** - * Checks if a given table name is a name for a model table. - */ - public static boolean isModelTable(String tableName) { - return tableName.startsWith(TABLE_PREFIX); - } - - /** - * Reports the current versioning counter for the table. The versioning counter is guaranteed to - * change when the contents of the table changes after advance_read() or promote_to_write(), or - * immediately after calls to methods which change the table. - * - * @return version_counter for the table. - */ - public long getVersion() { - return nativeVersion(nativePtr); - } - @Nullable public static String getClassNameForTable(@Nullable String name) { if (name == null) { return null; } @@ -925,8 +730,6 @@ public static String getTableNameForClass(String name) { private native void nativeMoveLastOver(long nativeTablePtr, long rowIndex); - private native long nativeGetSortedViewMulti(long nativeTableViewPtr, long[] columnIndices, boolean[] ascending); - private native long nativeGetLong(long nativeTablePtr, long columnIndex, long rowIndex); private native boolean nativeGetBoolean(long nativeTablePtr, long columnIndex, long rowIndex); @@ -951,8 +754,6 @@ public static String getTableNameForClass(String name) { public static native void nativeSetLong(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); - public static native void nativeSetLongUnique(long nativeTablePtr, long columnIndex, long rowIndex, long value); - public static native void nativeIncrementLong(long nativeTablePtr, long columnIndex, long rowIndex, long value); public static native void nativeSetBoolean(long nativeTablePtr, long columnIndex, long rowIndex, boolean value, boolean isDefault); @@ -965,19 +766,12 @@ public static String getTableNameForClass(String name) { public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value, boolean isDefault); - public static native void nativeSetStringUnique(long nativeTablePtr, long columnIndex, long rowIndex, String value); - public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex, boolean isDefault); - // Use nativeSetStringUnique(null) for String column! - public static native void nativeSetNullUnique(long nativeTablePtr, long columnIndex, long rowIndex); - public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data, boolean isDefault); public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); - private native long nativeSetPrimaryKey(long privateKeyTableNativePtr, long nativePtr, @Nullable String columnName); - private static native boolean nativeMigratePrimaryKeyTableIfNeeded(long groupNativePtr, long primaryKeyTableNativePtr); private static native boolean nativePrimaryKeyTableNeedsMigration(long primaryKeyTableNativePtr); @@ -1016,19 +810,9 @@ public static String getTableNameForClass(String name) { public static native long nativeFindFirstNull(long nativeTablePtr, long columnIndex); - // FIXME: Disabled in cpp code, see comments there - // private native long nativeFindAllTimestamp(long nativePtr, long columnIndex, long dateTimeValue); - private native long nativeLowerBoundInt(long nativePtr, long columnIndex, long value); - - private native long nativeUpperBoundInt(long nativePtr, long columnIndex, long value); - private native String nativeGetName(long nativeTablePtr); - private native String nativeToJson(long nativeTablePtr); - private native boolean nativeHasSameSchema(long thisTable, long otherTable); - private native long nativeVersion(long nativeTablePtr); - private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableSchema.java b/realm/realm-library/src/main/java/io/realm/internal/TableSchema.java deleted file mode 100644 index 47200f336d..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/TableSchema.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - - -import io.realm.RealmFieldType; - - -public interface TableSchema { - - long addColumn(RealmFieldType type, String name); - - void removeColumn(long columnIndex); - - void renameColumn(long columnIndex, String newName); - - /* - // FIXME the column information classes should be here as well. - // There is currently no path based implementation in core, so we should consider adding them with Spec, or wait for a core implementation. - - long getColumnCount(); - - String getColumnName(long columnIndex); - - long getColumnIndex(String name); - - ColumnType getColumnType(long columnIndex); - */ -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 4cdf1e9712..d17c236ee2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -182,7 +182,6 @@ public OsList getLinkList(long columnIndex) { @Override public void setLong(long columnIndex, long value) { parent.checkImmutable(); - getTable().checkIntValueIsLegal(columnIndex, getIndex(), value); nativeSetLong(nativePtr, columnIndex, value); } @@ -225,10 +224,8 @@ public void setDate(long columnIndex, Date date) { public void setString(long columnIndex, @Nullable String value) { parent.checkImmutable(); if (value == null) { - getTable().checkDuplicatedNullForPrimaryKeyValue(columnIndex, getIndex()); nativeSetNull(nativePtr, columnIndex); } else { - getTable().checkStringValueIsLegal(columnIndex, getIndex(), value); nativeSetString(nativePtr, columnIndex, value); } } @@ -264,7 +261,6 @@ public boolean isNull(long columnIndex) { @Override public void setNull(long columnIndex) { parent.checkImmutable(); - getTable().checkDuplicatedNullForPrimaryKeyValue(columnIndex, getIndex()); nativeSetNull(nativePtr, columnIndex); } From 94a00733be84230b7a71c66be4015003660f3704 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 22 Sep 2017 16:34:50 +0800 Subject: [PATCH 0974/2110] Remove some deprecated APIs --- CHANGELOG.md | 4 + .../src/main/java/io/realm/RealmObject.java | 22 ------ .../main/java/io/realm/RealmObjectSchema.java | 7 -- .../src/main/java/io/realm/RealmResults.java | 9 --- .../src/main/java/io/realm/RealmSchema.java | 7 -- .../objectServer/java/io/realm/SyncUser.java | 79 ------------------- 6 files changed, 4 insertions(+), 124 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 607040c430..e3fee3baeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## Breaking Changes * Calling `distinct()` on a sorted `RealmResults` no longer clears the sorting (#3503). +* [ObjectServer] Removed deprecated APIs `SyncUser.retrieveUser()` and `SyncUser.retrieveUserAsync()`. Use `SyncUser.retrieveInfoForUser()` and `retrieveInfoForUserAsync()` instead. +* Removed deprecated APIs `RealmSchema.close()` and `RealmObjectSchema.close()`. Those don't have to be called anymore. +* Removed deprecated API `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. +* Removed deprecated API `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. ## Deprecated diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 6de1183104..2b6162e6aa 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -605,16 +605,6 @@ public static void removeChangeListener(E object, RealmCh removeChangeListener(object, new ProxyState.RealmChangeListenerWrapper<>(listener)); } - /** - * Removes all registered listeners. - * - * @deprecated Use {@link #removeAllChangeListeners()} instead. - */ - @Deprecated - public final void removeChangeListeners() { - RealmObject.removeChangeListeners(this); - } - /** * Removes all registered listeners. */ @@ -622,18 +612,6 @@ public final void removeAllChangeListeners() { RealmObject.removeAllChangeListeners(this); } - /** - * Removes all registered listeners from the given RealmObject. - * - * @param object RealmObject to remove all listeners from. - * @throws IllegalArgumentException if object is {@code null} or isn't managed by Realm. - * @deprecated Use {@link RealmObject#removeAllChangeListeners(RealmModel)} instead. - */ - @Deprecated - public static void removeChangeListeners(E object) { - removeAllChangeListeners(object); - } - /** * Removes all registered listeners from the given RealmObject. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index e490cd33ca..30a584b996 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -95,13 +95,6 @@ public abstract class RealmObjectSchema { this.columnInfo = columnInfo; } - /** - * @deprecated {@link RealmObjectSchema} doesn't have to be released manually. - */ - @Deprecated - public void close() { - } - /** * Returns the name of the RealmObject class being represented by this schema. *

            diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 4b61f799bb..0619784ffa 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -233,15 +233,6 @@ public void removeAllChangeListeners() { collection.removeAllListeners(); } - /** - * Use {@link #removeAllChangeListeners()} instead. - */ - @SuppressWarnings("unused") - @Deprecated - public void removeChangeListeners() { - removeAllChangeListeners(); - } - /** * Removes the specified change listener. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 74ca6fafe8..9830514072 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -64,13 +64,6 @@ public abstract class RealmSchema { this.columnIndices = columnIndices; } - /** - * @deprecated {@link RealmSchema} doesn't have to be released manually. - */ - @Deprecated - public void close() { - } - /** * Returns the {@link RealmObjectSchema} for a given class. If this {@link RealmSchema} is immutable, an immutable * {@link RealmObjectSchema} will be returned. Otherwise, it returns an mutable {@link RealmObjectSchema}. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 23a60d532a..2b24ec28d4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -421,85 +421,6 @@ public SyncUser run() { }.start(); } - /** - * Helper method for Admin users in order to lookup a {@code SyncUser} using the identity provider and the used username. - * - * @param provider identity providers {@link io.realm.SyncCredentials.IdentityProvider} used when the account was created. - * @param providerId username or email used to create the account for the first time, - * what is needed will depend on what type of {@link SyncCredentials} was used. - * - * @return {@code SyncUser} associated with the given identity provider and providerId, or {@code null} in case - * of an {@code invalid} provider or {@code providerId}. - * @throws ObjectServerError in case of an error. - * @deprecated as of release 3.6.0, replaced by {@link #retrieveInfoForUser(String, String)}} - */ - @Deprecated - public SyncUser retrieveUser(final String provider, final String providerId) throws ObjectServerError { - if (Util.isEmptyString(provider)) { - throw new IllegalArgumentException("Not-null 'provider' required."); - } - - if (Util.isEmptyString(providerId)) { - throw new IllegalArgumentException("None empty 'providerId' required."); - } - - if (!isAdmin()) { - throw new IllegalArgumentException("SyncUser needs to be admin in order to lookup other users ID."); - } - - AuthenticationServer authServer = SyncManager.getAuthServer(); - LookupUserIdResponse response = authServer.retrieveUser(refreshToken, provider, providerId, getAuthenticationUrl()); - if (!response.isValid()) { - // the endpoint returns a 404 if it can't honor the query, either because - // - provider is not valid - // - provider_id is not valid - // - token used is not an admin one - // in this case we should return null instead of throwing - if (response.getError().getErrorCode() == ErrorCode.NOT_FOUND) { - return null; - } else { - throw response.getError(); - } - } else { - SyncUser syncUser = SyncManager.getUserStore().get(response.getUserId(), getAuthenticationUrl().toString()); - if (syncUser != null) { - return syncUser; - } else { - // build a SynUser without a token - Token refreshToken = new Token(null, response.getUserId(), null, 0, null, response.isAdmin()); - return new SyncUser(refreshToken, getAuthenticationUrl()); - } - } - } - - /** - * Asynchronously lookup a {@code SyncUser} using the identity provider and the used username. - * This is for Admin users only. - * - * @param provider identity providers {@link io.realm.SyncCredentials.IdentityProvider} used when the account was created. - * @param providerId username or email used to create the account for the first time, - * what is needed will depend on what type of {@link SyncCredentials} was used. - * @param callback callback when the lookup has completed or failed. The callback will always happen on the same thread - * as this method is called on. - * @return representation of the async task that can be used to cancel it if needed. - * @deprecated as of release 3.6.0, replaced by {@link #retrieveInfoForUserAsync(String, String, RequestCallback)}} - */ - @Deprecated - public RealmAsyncTask retrieveUserAsync(final String provider, final String providerId, final Callback callback) { - checkLooperThread("Asynchronously retrieving user id is only possible from looper threads."); - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public SyncUser run() { - return retrieveUser(provider, providerId); - } - }.start(); - } - /** * Given a Realm Object Server authentication provider and a provider identifier for a user (for example, a username), look up and return user information for that user. * From bb71e9aaf180dd539638237ffd75c509c17e5c1b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 22 Sep 2017 11:58:39 +0200 Subject: [PATCH 0975/2110] Upgrade to ROS-2.0.0-alpha.35 (#5296) --- dependencies.list | 2 +- .../network/ChangePasswordRequest.java | 4 ++-- .../network/LookupUserIdResponse.java | 4 ++-- .../java/io/realm/objectserver/AuthTests.java | 7 ++++++ .../realm/objectserver/utils/UserFactory.java | 22 ++++--------------- 5 files changed, 16 insertions(+), 23 deletions(-) diff --git a/dependencies.list b/dependencies.list index 91cfac3223..0025e7c835 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,4 +5,4 @@ REALM_SYNC_SHA256=5e09e54e68e78683e006898f5a703f80e0ee49492fb0f9dc2384fcbbb9f02f # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.34 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.35 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java index 6ca1f3aef1..827ec0746a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java @@ -56,9 +56,9 @@ private ChangePasswordRequest(String token, String newPassword, String userID) { public String toJson() { try { JSONObject request = new JSONObject(); - request.put("newPassword", newPassword); + request.put("new_password", newPassword); if (userID != null) { - request.put("userId", userID); + request.put("user_id", userID); } return request.toString(); } catch (JSONException e) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java index eb60bd4890..a9d93d7c93 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java @@ -34,8 +34,8 @@ */ public class LookupUserIdResponse extends AuthServerResponse { - private static final String JSON_FIELD_USER_ID = "userId"; - private static final String JSON_FIELD_USER_IS_ADMIN = "isAdmin"; + private static final String JSON_FIELD_USER_ID = "user_id"; + private static final String JSON_FIELD_USER_IS_ADMIN = "is_admin"; private static final String JSON_FIELD_METADATA = "metadata"; private final String userId; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index e14a262566..256df2f2aa 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -185,6 +185,7 @@ public void onError(ObjectServerError error) { } @Test + @Ignore("Wait for https://github.com/realm/ros/issues/335") public void changePassword() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; @@ -204,6 +205,7 @@ public void changePassword() { } @Test + @Ignore("See https://github.com/realm/ros/issues/335") public void changePassword_using_admin() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; @@ -231,6 +233,7 @@ public void changePassword_using_admin() { @Test @RunTestInLooperThread + @Ignore("Wait for https://github.com/realm/ros/issues/335") public void changePassword_using_admin_async() { final String username = UUID.randomUUID().toString(); final String originalPassword = "password"; @@ -595,6 +598,7 @@ public void execute(Realm realm) { } @Test + @Ignore("Wait for https://github.com/realm/ros/issues/333") public void retrieve() { final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); @@ -618,6 +622,7 @@ public void retrieve() { // retrieving a logged out user @Test @RunTestInLooperThread + @Ignore("Wait for https://github.com/realm/ros/issues/333") public void retrieve_logout() { final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); @@ -685,6 +690,7 @@ public void retrieve_invalidProvider() { } @Test + @Ignore("Wait for https://github.com/realm/ros/issues/333") public void retrieve_notAdmin() { final String username1 = UUID.randomUUID().toString(); final String password1 = "password"; @@ -708,6 +714,7 @@ public void retrieve_notAdmin() { @Test @RunTestInLooperThread + @Ignore("Wait for https://github.com/realm/ros/issues/333") public void retrieve_async() { final String username = UUID.randomUUID().toString(); final String password = "password"; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index beb5c0cb06..a669b7dda4 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -92,23 +92,7 @@ public static SyncUser createAdminUser(String authUrl) { // `admin` required as user identifier to be granted admin rights. // ROS 2.0 comes with a default admin user named "realm-admin" with password "". SyncCredentials credentials = SyncCredentials.usernamePassword("realm-admin", "", false); - int attempts = 3; - while (attempts > 0) { - attempts--; - try { - return SyncUser.login(credentials, authUrl); - } catch (ObjectServerError e) { - // ROS default admin user might not be created yet, we need to retry. - // Remove this work-around when https://github.com/realm/ros/issues/282 - // is fixed. - if (e.getErrorCode() != ErrorCode.INVALID_CREDENTIALS) { - throw e; - } - SystemClock.sleep(1000); - } - } - - throw new IllegalStateException("Could not login 'realm-admin'"); + return SyncUser.login(credentials, authUrl); } // Since we don't have a reliable way to reset the sync server and client, just use a new user factory for every @@ -165,8 +149,10 @@ public void run() { for (SyncUser user : users.values()) { user.logout(); } - SystemClock.sleep(2000); // Remove when https://github.com/realm/ros/issues/304 is fixed + // FIXME https://github.com/realm/ros/issues/338 + SystemClock.sleep(2000); allUsersLoggedOut.countDown(); + } }); TestHelper.awaitOrFail(allUsersLoggedOut); From 3e1a57e4dadde692b5999143a89436b3f4c17ca8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 22 Sep 2017 17:16:03 +0800 Subject: [PATCH 0976/2110] Remove largeRealmMultipleThreads It was added in #1379 to expose the timeout problem in finalizer. Since we are not using finalizer anymore, just remove it. --- .../java/io/realm/RealmQueryTests.java | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 0c6f6523ae..35456d248c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2554,47 +2554,6 @@ public void isNotNull_listFieldThrows() { } } - @Ignore("Disabled because it is time consuming") - @Test - public void largeRealmMultipleThreads() throws InterruptedException { - final int nObjects = 500000; - final int nThreads = 3; - final CountDownLatch latch = new CountDownLatch(nThreads); - - realm.beginTransaction(); - realm.delete(StringOnly.class); - for (int i = 0; i < nObjects; i++) { - StringOnly stringOnly = realm.createObject(StringOnly.class); - stringOnly.setChars(String.format("string %d", i)); - } - realm.commitTransaction(); - - - for (int i = 0; i < nThreads; i++) { - Thread thread = new Thread( - new Runnable() { - @Override - @SuppressWarnings("ElementsCountedInLoop") - public void run() { - RealmConfiguration realmConfig = configFactory.createConfiguration(); - Realm realm = Realm.getInstance(realmConfig); - RealmResults realmResults = realm.where(StringOnly.class).findAll(); - int n = 0; - for (StringOnly ignored : realmResults) { - n = n + 1; - } - assertEquals(nObjects, n); - realm.close(); - latch.countDown(); - } - } - ); - thread.start(); - } - - TestHelper.awaitOrFail(latch); - } - @Test public void isValid_tableQuery() { final RealmQuery query = realm.where(AllTypes.class); From c974042e082b9767f2fb3e91d11f1cfbd6cdc58a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 23 Sep 2017 20:36:20 +0200 Subject: [PATCH 0977/2110] Fix change listener getting GC'ed (#5311) --- .../java/io/realm/examples/objectserver/CounterActivity.java | 4 +++- .../java/io/realm/examples/objectserver/MyApplication.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 8fa975cab6..1baf3a9e2f 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -79,6 +79,7 @@ public void run() { @BindView(R.id.text_counter) TextView counterView; @BindView(R.id.progressbar) MaterialProgressBar progressBar; + private CRDTCounter counter; // Keep strong reference to counter to keep change listeners alive. @Override protected void onCreate(Bundle savedInstanceState) { @@ -107,7 +108,7 @@ public void execute(@Nonnull Realm realm) { realm = Realm.getInstance(config); counterView.setText("-"); - CRDTCounter counter = realm.where(CRDTCounter.class).equalTo("name", user.getIdentity()).findFirstAsync(); + counter = realm.where(CRDTCounter.class).equalTo("name", user.getIdentity()).findFirstAsync(); counter.addChangeListener(new RealmChangeListener() { @Override public void onChange(@Nonnull CRDTCounter counter) { @@ -131,6 +132,7 @@ protected void onStop() { } closeRealm(); user = null; + counter = null; } @Override diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java index 8fb13a829b..e4511eeb78 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java @@ -31,7 +31,7 @@ public void onCreate() { // Enable full log output when debugging if (BuildConfig.DEBUG) { - RealmLog.setLevel(Log.VERBOSE); + RealmLog.setLevel(Log.DEBUG); } } } From 6a03b11f764baaf79b0a7696e014dcade59e5cd6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 23 Sep 2017 20:36:36 +0200 Subject: [PATCH 0978/2110] Add support for new error codes (#5308) --- .../realm-library/src/objectServer/java/io/realm/ErrorCode.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index abdd2fcfea..446a32b57b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -51,6 +51,8 @@ public enum ErrorCode { BAD_ERROR_CODE(114), // Bad error code (ERROR) BAD_COMPRESSION(115), // Bad compression (DOWNLOAD) BAD_CLIENT_VERSION_DOWNLOAD(116),// Bad last integrated client version in changeset header (DOWNLOAD) + SSL_SERVER_CERT_REJECTED(117), // SSL server certificate rejected + PONG_TIMEOUT(118), // Timeout on reception of PONG response messsage // Session level errors (200 - 299) SESSION_CLOSED(200, Category.RECOVERABLE), // Session closed (no error) From ff932d23c8f0e24f0fb4f7b5c436932c016fc539 Mon Sep 17 00:00:00 2001 From: Jussi Pekonen Date: Fri, 22 Sep 2017 13:07:30 +0300 Subject: [PATCH 0979/2110] Add support for ISO8601 2-digit time zone designator in Date parsing (#5309) --- CHANGELOG.md | 6 ++++++ .../io/realm/internal/android/ISO8601UtilsTest.java | 11 +++++++++++ .../java/io/realm/internal/android/ISO8601Utils.java | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a14c94f61a..ed7b08037f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.7.3 (xxxx-xx-xx) + +### Bug Fixes + +* Added support for ISO8601 2-digit time zone designators (#5309). + ## 3.7.2 (2017-09-12) ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java index 0fffbd9f57..492b34b359 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java @@ -123,6 +123,17 @@ public void testParseOptional() throws java.text.ParseException { assertEquals(dateZeroSecondAndMillis, d); } + public void testTimeZoneDesignator() throws java.text.ParseException { + Date d = ISO8601Utils.parse("2007-08-13T21:51+02:00", new ParsePosition(0)); + assertEquals(dateZeroSecondAndMillis, d); + + d = ISO8601Utils.parse("2007-08-13T21:51+0200", new ParsePosition(0)); + assertEquals(dateZeroSecondAndMillis, d); + + d = ISO8601Utils.parse("2007-08-13T21:51+02", new ParsePosition(0)); + assertEquals(dateZeroSecondAndMillis, d); + } + public void testParseRfc3339Examples() throws java.text.ParseException { // Two digit milliseconds. Date d = ISO8601Utils.parse("1985-04-12T23:20:50.52Z", new ParsePosition(0)); diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java b/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java index 1a2c0fba4c..d430757548 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/ISO8601Utils.java @@ -158,6 +158,10 @@ public static Date parse(String date, ParsePosition pos) throws ParseException { } else if (timezoneIndicator == '+' || timezoneIndicator == '-') { String timezoneOffset = date.substring(offset); offset += timezoneOffset.length(); + // Convert 2-digit time zone designator to 4-digit designator + if (timezoneOffset.length() == 3) { + timezoneOffset += "00"; + } // 18-Jun-2015, tatu: Minor simplification, skip offset of "+0000"/"+00:00" if ("+0000".equals(timezoneOffset) || "+00:00".equals(timezoneOffset)) { timezone = TIMEZONE_Z; From 3f6844a3cc82859d746437b4c4106c13601222f4 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 25 Sep 2017 11:08:10 +0100 Subject: [PATCH 0980/2110] 4.0 API breaking changes (#5314) * Refactored deprecated `Callback` * SyncUser: renamed `getAccessToken` + method is no longer public --- CHANGELOG.md | 2 + .../examples/objectserver/LoginActivity.java | 2 +- .../io/realm/AuthenticateRequestTests.java | 4 +- .../java/io/realm/SyncUserTests.java | 12 +-- .../java/io/realm/util/SyncTestUtils.java | 15 +++- .../java/io/realm/SyncSession.java | 6 +- .../objectServer/java/io/realm/SyncUser.java | 86 ++++--------------- .../java/io/realm/objectserver/AuthTests.java | 24 +++--- .../EncryptedSynchronizedRealmTests.java | 3 +- .../realm/objectserver/SyncSessionTests.java | 12 ++- 10 files changed, 64 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3fee3baeb..0a65e65deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ * Removed deprecated APIs `RealmSchema.close()` and `RealmObjectSchema.close()`. Those don't have to be called anymore. * Removed deprecated API `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. * Removed deprecated API `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. +* `SyncUser.Callback` to becomes generic. +* Removed `SyncUser.getAccessToken` method from public API, and rename it to `getRefreshToken`. ## Deprecated diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java index 73793b1edf..9bf5479f15 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -78,7 +78,7 @@ public void login(boolean createUser) { String password = this.password.getText().toString(); SyncCredentials creds = SyncCredentials.usernamePassword(username, password, createUser); - SyncUser.Callback callback = new SyncUser.Callback() { + SyncUser.Callback callback = new SyncUser.Callback() { @Override public void onSuccess(@Nonnull SyncUser user) { progressDialog.dismiss(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index bdadf6211c..a7fff7a2ef 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -39,7 +39,7 @@ public void setUp() { @Test public void realmLogin() throws URISyntaxException, JSONException { - Token t = SyncTestUtils.createTestUser().getAccessToken(); + Token t = SyncTestUtils.createTestUser().getRefreshToken(); AuthenticateRequest request = AuthenticateRequest.realmLogin(t, new URI("realm://objectserver/" + t.identity() + "/default")); JSONObject obj = new JSONObject(request.toJson()); @@ -60,7 +60,7 @@ public void userLogin() throws URISyntaxException, JSONException { @Test public void userRefresh() throws URISyntaxException, JSONException { - Token t = SyncTestUtils.createTestUser().getAccessToken(); + Token t = SyncTestUtils.createTestUser().getRefreshToken(); AuthenticateRequest request = AuthenticateRequest.userRefresh(t, new URI("realm://objectserver/" + t.identity() + "/default")); JSONObject obj = new JSONObject(request.toJson()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 1c90667d09..5d67f68af5 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -50,8 +50,8 @@ import io.realm.internal.network.AuthenticationServer; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; -import io.realm.objectserver.utils.UserFactory; import io.realm.objectserver.utils.StringOnlyModule; +import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.util.SyncTestUtils; @@ -382,7 +382,7 @@ public void changePasswordAsync_nonLooperThreadThrows() { SyncUser user = createTestUser(); thrown.expect(IllegalStateException.class); - user.changePasswordAsync("password", new SyncUser.Callback() { + user.changePasswordAsync("password", new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { fail(); @@ -400,7 +400,7 @@ public void changePassword_admin_Async_nonLooperThreadThrows() { SyncUser user = createTestUser(); thrown.expect(IllegalStateException.class); - user.changePasswordAsync("user-id", "new", new SyncUser.Callback() { + user.changePasswordAsync("user-id", "new", new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { fail(); @@ -543,11 +543,11 @@ public void fromJson_WorkWithRemovedObjectServerUser() { // since the user is not persisted in the UserStore // isValid() requires SyncManager.getUserStore().isActive(identity) // to return true as well. - Token accessToken = syncUser.getAccessToken(); - assertNotNull(accessToken); + Token refreshToken = syncUser.getRefreshToken(); + assertNotNull(refreshToken); // refresh token should expire in 10 years (July 23, 2027) Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(accessToken.expiresMs()); + calendar.setTimeInMillis(refreshToken.expiresMs()); int day = calendar.get(Calendar.DAY_OF_MONTH); int month = calendar.get(Calendar.MONTH); int year = calendar.get(Calendar.YEAR); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index 167bb3b3a1..97caaa18bd 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -37,10 +37,13 @@ public class SyncTestUtils { public static final String DEFAULT_AUTH_URL = "http://objectserver.realm.io/auth"; private final static Method SYNC_MANAGER_GET_USER_STORE_METHOD; + private final static Method SYNC_USER_GET_ACCESS_TOKEN_METHOD; static { try { SYNC_MANAGER_GET_USER_STORE_METHOD = SyncManager.class.getDeclaredMethod("getUserStore"); + SYNC_USER_GET_ACCESS_TOKEN_METHOD = SyncUser.class.getDeclaredMethod("getRefreshToken"); SYNC_MANAGER_GET_USER_STORE_METHOD.setAccessible(true); + SYNC_USER_GET_ACCESS_TOKEN_METHOD.setAccessible(true); } catch (NoSuchMethodException e) { throw new AssertionError(e); } @@ -106,13 +109,19 @@ public static AuthenticateResponse createErrorResponse(ErrorCode code) { return AuthenticateResponse.from(new ObjectServerError(code, "dummy")); } + public static Token getRefreshToken(SyncUser user) { + try { + return (Token) SYNC_USER_GET_ACCESS_TOKEN_METHOD.invoke(user); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } + } + private static void addToUserStore(SyncUser user) { try { UserStore userStore = (UserStore) SYNC_MANAGER_GET_USER_STORE_METHOD.invoke(null); userStore.put(user); - } catch (InvocationTargetException e) { - throw new AssertionError(e); - } catch (IllegalAccessException e) { + } catch (InvocationTargetException | IllegalAccessException e) { throw new AssertionError(e); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 4387d8e26a..102eaba041 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -504,7 +504,7 @@ String getAccessToken(final AuthenticationServer authServer, String refreshToken try { JSONObject refreshTokenJSON = new JSONObject(refreshToken); Token newRefreshToken = Token.from(refreshTokenJSON.getJSONObject("userToken")); - if (newRefreshToken.hashCode() != getUser().getAccessToken().hashCode()) { + if (newRefreshToken.hashCode() != getUser().getRefreshToken().hashCode()) { RealmLog.debug("Session[%s]: Access token updated", configuration.getPath()); getUser().setRefreshToken(newRefreshToken); } @@ -551,7 +551,7 @@ private void authenticateRealm(final AuthenticationServer authServer) { protected AuthenticateResponse execute() { if (!isClosed && !Thread.currentThread().isInterrupted()) { return authServer.loginToRealm( - getUser().getAccessToken(), //refresh token in fact + getUser().getRefreshToken(), //refresh token in fact configuration.getServerUrl(), getUser().getAuthenticationUrl() ); @@ -629,7 +629,7 @@ private void refreshAccessToken(final AuthenticationServer authServer) { @Override protected AuthenticateResponse execute() { if (!isClosed && !Thread.currentThread().isInterrupted()) { - return authServer.refreshUser(getUser().getAccessToken(), configuration.getServerUrl(), getUser().getAuthenticationUrl()); + return authServer.refreshUser(getUser().getRefreshToken(), configuration.getServerUrl(), getUser().getAuthenticationUrl()); } return null; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 2b24ec28d4..0a69915366 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -47,7 +47,6 @@ import io.realm.internal.network.LookupUserIdResponse; import io.realm.internal.objectserver.Token; import io.realm.internal.permissions.ManagementModule; -import io.realm.internal.permissions.PermissionModule; import io.realm.log.RealmLog; /** @@ -221,9 +220,9 @@ public static SyncUser login(final SyncCredentials credentials, final String aut * @return representation of the async task that can be used to cancel it if needed. * @throws IllegalArgumentException if not on a Looper thread. */ - public static RealmAsyncTask loginAsync(final SyncCredentials credentials, final String authenticationUrl, final Callback callback) { + public static RealmAsyncTask loginAsync(final SyncCredentials credentials, final String authenticationUrl, final Callback callback) { checkLooperThread("Asynchronous login is only possible from looper threads."); - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { @Override public SyncUser run() throws ObjectServerError { return login(credentials, authenticationUrl); @@ -375,13 +374,13 @@ public void changePassword(final String userId, final String newPassword) throws * @return representation of the async task that can be used to cancel it if needed. * @throws IllegalArgumentException if not on a Looper thread. */ - public RealmAsyncTask changePasswordAsync(final String newPassword, final Callback callback) { + public RealmAsyncTask changePasswordAsync(final String newPassword, final Callback callback) { checkLooperThread("Asynchronous changing password is only possible from looper threads."); //noinspection ConstantConditions if (callback == null) { throw new IllegalArgumentException("Non-null 'callback' required."); } - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { @Override public SyncUser run() { changePassword(newPassword); @@ -405,14 +404,14 @@ public SyncUser run() { * @return representation of the async task that can be used to cancel it if needed. * @throws IllegalArgumentException if not on a Looper thread. */ - public RealmAsyncTask changePasswordAsync(final String userId, final String newPassword, final Callback callback) { + public RealmAsyncTask changePasswordAsync(final String userId, final String newPassword, final Callback callback) { checkLooperThread("Asynchronous changing password is only possible from looper threads."); //noinspection ConstantConditions if (callback == null) { throw new IllegalArgumentException("Non-null 'callback' required."); } - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { @Override public SyncUser run() { changePassword(userId, newPassword); @@ -470,7 +469,7 @@ public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final * as this method is called on. * @return representation of the async task that can be used to cancel it if needed. */ - public RealmAsyncTask retrieveInfoForUserAsync(final String providerUserIdentity, final String provider, final RequestCallback callback) { + public RealmAsyncTask retrieveInfoForUserAsync(final String providerUserIdentity, final String provider, final Callback callback) { checkLooperThread("Asynchronously retrieving user is only possible from looper threads."); //noinspection ConstantConditions if (callback == null) { @@ -478,12 +477,8 @@ public RealmAsyncTask retrieveInfoForUserAsync(final String providerUserIdentity } return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - // TODO remove this override on next major release when we remove the deprecated Callback @Override - public SyncUser run() {return null;} - - @Override - public SyncUserInfo execute() throws ObjectServerError { + public SyncUserInfo run() throws ObjectServerError { return retrieveInfoForUser(providerUserIdentity, provider); } }.start(); @@ -553,12 +548,12 @@ public String getIdentity() { } /** - * Returns this user's access token. This is the users credential for accessing the Realm Object Server and should + * Returns this user's refresh token. This is the users credential for accessing the Realm Object Server and should * be treated as sensitive data. * - * @return the user's access token. If this user has logged out or the login has expired {@code null} is returned. + * @return the user's refresh token. If this user has logged out or the login has expired {@code null} is returned. */ - public Token getAccessToken() { + Token getRefreshToken() { return refreshToken; } @@ -685,32 +680,19 @@ public String toString() { // Class wrapping requests made against the auth server. Is also responsible for calling with success/error on the // correct thread. private static abstract class Request { - @Nullable - private final Callback callback; - @Nullable - private final RequestCallback genericCallback; + private final Callback callback; private final RealmNotifier handler; private final ThreadPoolExecutor networkPoolExecutor; - Request(ThreadPoolExecutor networkPoolExecutor, @Nullable Callback callback) { + Request(ThreadPoolExecutor networkPoolExecutor, @Nullable Callback callback) { this.callback = callback; - this.genericCallback = null; - this.handler = new AndroidRealmNotifier(null, new AndroidCapabilities()); - this.networkPoolExecutor = networkPoolExecutor; - } - - Request(ThreadPoolExecutor networkPoolExecutor, @Nullable RequestCallback callback) { - this.callback = null; - this.genericCallback = callback; this.handler = new AndroidRealmNotifier(null, new AndroidCapabilities()); this.networkPoolExecutor = networkPoolExecutor; } // Implements the request. Return the current sync user if the request succeeded. Otherwise throw an error. - public abstract SyncUser run() throws ObjectServerError; - //TODO next major release, remove run, rename execute to run and make it abstract - public T execute() throws ObjectServerError {return null;} + public abstract T run() throws ObjectServerError; // Start the request public RealmAsyncTask start() { @@ -718,13 +700,7 @@ public RealmAsyncTask start() { @Override public void run() { try { - // co-exist the old and new callback - if (genericCallback != null) { - postSuccess(Request.this.execute()); - } else { - postSuccess(Request.this.run()); - } - + postSuccess(Request.this.run()); } catch (ObjectServerError e) { postError(e); } catch (Throwable e) { @@ -752,45 +728,19 @@ public void run() { } } - private void postSuccess(final SyncUser user) { - if (callback != null) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onSuccess(user); - } - }); - } - } - private void postSuccess(final T result) { - if (genericCallback != null) { + if (callback != null) { handler.post(new Runnable() { @Override public void run() { - genericCallback.onSuccess(result); + callback.onSuccess(result); } }); } } } - // TODO remove and replace uses by RequestCallback on next major release - public interface Callback { - /** - * @deprecated as per 3.6.0 release, replaced by {@link RequestCallback#onSuccess(Object)} - */ - @Deprecated - void onSuccess(SyncUser user); - - /** - * @deprecated as per 3.6.0 release, replaced by {@link RequestCallback#onError(ObjectServerError)} - */ - @Deprecated - void onError(ObjectServerError error); - } - - public interface RequestCallback { + public interface Callback { void onSuccess(T result); void onError(ObjectServerError error); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 256df2f2aa..fddc2c5ea5 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -40,6 +40,7 @@ import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; +import io.realm.util.SyncTestUtils; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; @@ -74,7 +75,7 @@ public void login_userNotExist() { @RunTestInLooperThread public void loginAsync_userNotExist() { SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { fail(); @@ -93,7 +94,7 @@ public void onError(ObjectServerError error) { public void login_newUser() { String userId = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(userId, "password", true); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { assertFalse(user.isAdmin()); @@ -116,8 +117,8 @@ public void onError(ObjectServerError error) { @RunTestInLooperThread public void login_withAccessToken() { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentials = SyncCredentials.accessToken(adminUser.getAccessToken().value(), "custom-admin-user", adminUser.isAdmin()); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncCredentials credentials = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(adminUser).value(), "custom-admin-user", adminUser.isAdmin()); + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { assertTrue(user.isAdmin()); @@ -159,7 +160,7 @@ public void run() { @Override public void run() { SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { fail(); @@ -248,7 +249,7 @@ public void changePassword_using_admin_async() { // Change password using admin user final String newPassword = "new-password"; - adminUser.changePasswordAsync(userOld.getIdentity(), newPassword, new SyncUser.Callback() { + adminUser.changePasswordAsync(userOld.getIdentity(), newPassword, new SyncUser.Callback() { @Override public void onSuccess(SyncUser administratorUser) { assertEquals(adminUser, administratorUser); @@ -497,7 +498,7 @@ public void revokedRefreshTokenIsNotSameAfterLogin() throws InterruptedException final SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); - final Token revokedRefreshToken = user.getAccessToken(); + final Token revokedRefreshToken = SyncTestUtils.getRefreshToken(user); SyncManager.addAuthenticationListener(new AuthenticationListener() { @Override @@ -511,11 +512,12 @@ public void loggedOut(SyncUser user) { SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); SyncUser loggedInUser = SyncUser.login(credentials, Constants.AUTH_URL); + Token token = SyncTestUtils.getRefreshToken(loggedInUser); // still comparing the same user - assertEquals(revokedRefreshToken.identity(), loggedInUser.getAccessToken().identity()); + assertEquals(revokedRefreshToken.identity(), token.identity()); // different tokens - assertNotEquals(revokedRefreshToken.value(), loggedInUser.getAccessToken().value()); + assertNotEquals(revokedRefreshToken.value(), token.value()); SyncManager.removeAuthenticationListener(this); userLoggedInAgain.countDown(); } @@ -591,7 +593,7 @@ public void execute(Realm realm) { assertNotEquals(accessToken, newAccessToken); // refresh_token identity is the same - assertEquals(user.getAccessToken().identity(), newAccessToken.identity()); + assertEquals(SyncTestUtils.getRefreshToken(user).identity(), newAccessToken.identity()); assertEquals(accessToken.identity(), newAccessToken.identity()); realm.close(); @@ -728,7 +730,7 @@ public void retrieve_async() { assertTrue(adminUser.isAdmin()); final String identity = user.getIdentity(); - adminUser.retrieveInfoForUserAsync(username, SyncCredentials.IdentityProvider.USERNAME_PASSWORD, new SyncUser.RequestCallback() { + adminUser.retrieveInfoForUserAsync(username, SyncCredentials.IdentityProvider.USERNAME_PASSWORD, new SyncUser.Callback() { @Override public void onSuccess(SyncUserInfo userInfo) { assertNotNull(userInfo); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 7806b4158b..d51f3520f0 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -24,6 +24,7 @@ import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; +import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -187,7 +188,7 @@ public void onError(SyncSession session, ObjectServerError error) { // STEP 3: prepare a synced Realm for client B (admin user) SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentials = SyncCredentials.accessToken(admin.getAccessToken().value(), "custom-admin-user"); + SyncCredentials credentials = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); SyncUser adminUser = SyncUser.login(credentials, Constants.AUTH_URL); final byte[] adminRandomKey = TestHelper.getRandomKey(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java index a51e9b97d1..79dc4e682c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java @@ -12,16 +12,14 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.CountDownLatch; import java.util.Arrays; import java.util.UUID; import java.util.concurrent.CountDownLatch; -import io.realm.BaseIntegrationTest; import io.realm.Realm; -import io.realm.StandardIntegrationTest; import io.realm.RealmChangeListener; import io.realm.RealmResults; +import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; import io.realm.SyncCredentials; import io.realm.SyncManager; @@ -34,10 +32,10 @@ import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.TestSyncConfigurationFactory; +import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; @@ -275,7 +273,7 @@ public void run() { // access the Realm from an different path on the device (using admin user), then monitor // when the offline commits get synchronized SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentialsAdmin = SyncCredentials.accessToken(admin.getAccessToken().value(), "custom-admin-user"); + SyncCredentials credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); SyncUser adminUser = SyncUser.login(credentialsAdmin, Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) @@ -349,7 +347,7 @@ public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { public void run() { // using an admin user to open the Realm on different path on the device to monitor when all the uploads are done SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentialsAdmin = SyncCredentials.accessToken(admin.getAccessToken().value(), "custom-admin-user"); + SyncCredentials credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); SyncUser adminUser = SyncUser.login(credentialsAdmin, Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) @@ -420,7 +418,7 @@ public void downloadChangesWhenRealmOutOfScope() throws InterruptedException { public void run() { // using an admin user to open the Realm on different path on the device then some commits SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentialsAdmin = SyncCredentials.accessToken(admin.getAccessToken().value(), "custom-admin-user"); + SyncCredentials credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); SyncUser adminUser = SyncUser.login(credentialsAdmin, Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) From 67d2b080b66e12568130ba4db4a8d60d030a2c3e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 25 Sep 2017 19:38:20 +0800 Subject: [PATCH 0981/2110] Fix flaky logout_sameSyncUserMultipleSessions --- .../java/io/realm/objectserver/SyncSessionTests.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java index 79dc4e682c..71ce342a7d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java @@ -14,7 +14,6 @@ import java.util.Arrays; import java.util.UUID; -import java.util.concurrent.CountDownLatch; import io.realm.Realm; import io.realm.RealmChangeListener; @@ -220,9 +219,11 @@ public void logout_sameSyncUserMultipleSessions() { credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); SyncUser.login(credentials, Constants.AUTH_URL); - // reviving the sessions - assertEquals(SyncSession.State.WAITING_FOR_ACCESS_TOKEN, session1.getState()); - assertEquals(SyncSession.State.WAITING_FOR_ACCESS_TOKEN, session2.getState()); + // reviving the sessions. The state could be changed concurrently. + assertTrue(session1.getState() == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || + session1.getState() == SyncSession.State.ACTIVE); + assertTrue(session2.getState() == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || + session2.getState() == SyncSession.State.ACTIVE); realm1.close(); realm2.close(); From 8353e65251e1ef382366d59e8a7739f5e356d468 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 25 Sep 2017 14:00:16 +0100 Subject: [PATCH 0982/2110] Support offline client reset (#5297) * Exposing a `SyncConfiguration` that allows a user to open the backup Realm after the client reset (#4759). --- CHANGELOG.md | 2 +- .../rule/TestRealmConfigurationFactory.java | 1 - .../java/io/realm/SessionTests.java | 1 + .../java/io/realm/SyncConfigurationTests.java | 11 +- .../java/io/realm/SyncManagerTests.java | 1 - .../io/realm/SyncedRealmMigrationTests.java | 65 ++++++++++-- realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/util.cpp | 28 +++-- .../java/io/realm/RealmConfiguration.java | 22 +++- .../java/io/realm/internal/OsRealmConfig.java | 4 +- .../java/io/realm/SyncConfiguration.java | 5 +- .../IncompatibleSyncedFileException.java | 96 ++++++++++++++++++ .../assets/sync-1.x.realm} | Bin 8192 -> 8192 bytes 13 files changed, 198 insertions(+), 40 deletions(-) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/exceptions/IncompatibleSyncedFileException.java rename realm/realm-library/src/{androidTestObjectServer/assets/stable_id_migration.realm => syncIntegrationTest/assets/sync-1.x.realm} (64%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a65e65deb..5cffb2de46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ ### Bug Fixes * Throw `IllegalArgumentException` instead of `IllegalStateException` when calling string/binary data setters if the data length exceeds the limit. -* Exposing a `RealmConfiguration` that allows a user to open the backup Realm after the client reset (#4759). +* Exposing a `RealmConfiguration` that allows a user to open the backup Realm after the client reset (#4759/#5223). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index d262d92e07..66827fa920 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -32,7 +32,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import io.realm.CompactOnLaunchCallback; import io.realm.Realm; import io.realm.RealmConfiguration; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index c011fa563a..54172550a3 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -274,6 +274,7 @@ public void onError(SyncSession session, ObjectServerError error) { RealmConfiguration backupRealmConfiguration = handler.getBackupRealmConfiguration(); assertNotNull(backupRealmConfiguration); assertFalse(backupRealmConfiguration.isSyncConfiguration()); + assertTrue(backupRealmConfiguration.isRecoveryConfiguration()); Realm backupRealm = Realm.getInstance(backupRealmConfiguration); assertFalse(backupRealm.isEmpty()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index c91a7e2def..43339cab79 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -16,12 +16,10 @@ package io.realm; -import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -61,13 +59,6 @@ public class SyncConfigurationTests { @Rule public final ExpectedException thrown = ExpectedException.none(); - private Context context; - - @Before - public void setUp() { - context = InstrumentationRegistry.getContext(); - } - @After public void tearDown() throws Exception { SyncManager.reset(); @@ -104,7 +95,7 @@ public void serverUrl_setsFolderAndFileName() { SyncConfiguration config = new SyncConfiguration.Builder(user, serverUrl).build(); - assertEquals(new File(context.getFilesDir(), expectedFolder), config.getRealmDirectory()); + assertEquals(new File(InstrumentationRegistry.getContext().getFilesDir(), expectedFolder), config.getRealmDirectory()); assertEquals(expectedFileName, config.getRealmFileName()); } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 2d306e413e..839c935b31 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -166,6 +166,5 @@ public void session() throws IOException { assertEquals(user, session.getUser()); // see also SessionTests realm.close(); - SyncManager.reset(); } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index f866cd1c9b..60b25da8fe 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -19,20 +19,21 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import io.realm.entities.IndexedFields; -import io.realm.entities.PrimaryKeyAsInteger; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; -import io.realm.exceptions.RealmMigrationNeededException; -import io.realm.internal.OsObjectStore; +import io.realm.exceptions.IncompatibleSyncedFileException; +import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.TestSyncConfigurationFactory; import io.realm.util.SyncTestUtils; @@ -51,6 +52,14 @@ public class SyncedRealmMigrationTests { @Rule public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + @BeforeClass + public static void beforeClass () { + // another Test class may have the BaseRealm.applicationContext set but + // the SyncManager reset. This will make assertion to fail, we need to re-initialise + // the sync_manager.cpp#m_file_manager (configFactory rule do this) + BaseRealm.applicationContext = null; + } + @Test public void migrateRealm_syncConfigurationThrows() { SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth").build(); @@ -296,18 +305,54 @@ public void moreFieldsThanExpectedIsAllowed() { realm.close(); } - // The stable_id_migration.realm is created with sync v1.8.5 with one object created for each object schema. @Test - @Ignore("Not supported by sync right now.") - public void stableIDMigrationCauseClientReset() throws IOException { + public void offlineClientReset() throws IOException { SyncConfiguration config = configFactory .createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") - .schema(StringOnly.class, PrimaryKeyAsString.class, PrimaryKeyAsInteger.class) - .name("stable_id_migration.realm") + .modules(new StringOnlyModule()) .build(); - configFactory.copyRealmFromAssets(InstrumentationRegistry.getContext(), "stable_id_migration.realm", config); + + String path = config.getPath(); + File realmFile = new File (path); + assertFalse(realmFile.exists()); + // copy the 1.x Realm + configFactory.copyRealmFromAssets(InstrumentationRegistry.getContext(), "sync-1.x.realm", config); + assertTrue(realmFile.exists()); + + // open the file using the new ROS 2.x server + try { + Realm.getInstance(config); + fail("should throw IncompatibleSyncedFileException"); + } catch (IncompatibleSyncedFileException expected) { + String recoveryPath = expected.getRecoveryPath(); + assertTrue(new File(recoveryPath).exists()); + // can open the backup Realm + RealmConfiguration backupRealmConfiguration = expected.getBackupRealmConfiguration(null, new StringOnlyModule()); + Realm backupRealm = Realm.getInstance(backupRealmConfiguration); + assertFalse(backupRealm.isEmpty()); + RealmResults all = backupRealm.where(StringOnly.class).findAll(); + assertEquals(1, all.size()); + assertEquals("Hello from ROS 1.X", all.get(0).getChars()); + + // make sure it's read only + try { + backupRealm.beginTransaction(); + fail("Backup Realm should be read-only, we should throw"); + } catch (IllegalStateException ignored) { + } + backupRealm.close(); + + // we can open in dynamic mode + DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); + dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); + RealmResults allDynamic = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); + assertEquals(1, allDynamic.size()); + assertEquals("Hello from ROS 1.X", allDynamic.first().getString(StringOnly.FIELD_CHARS)); + dynamicRealm.close(); + } + Realm realm = Realm.getInstance(config); - // TODO: Should the local realm be cleaned? It contains one object for each object schema in the realm. + assertTrue(realm.isEmpty()); realm.close(); } } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index cdd0d8c82b..317dc9b3d9 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit cdd0d8c82ba6dfb60ebc2c339b26b7b3ca4d4047 +Subproject commit 317dc9b3d9ef1b5de5ace72e05ac6d58e443bef5 diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 25c02994e0..357ef845f8 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "utf8.hpp" #include "util.hpp" @@ -38,7 +39,7 @@ using namespace realm::util; using namespace realm::jni_util; using namespace realm::_impl; -void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind); +void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind, const std::string& path = ""); void ConvertException(JNIEnv* env, const char* file, int line) { @@ -67,7 +68,7 @@ void ConvertException(JNIEnv* env, const char* file, int line) } catch (RealmFileException& e) { ss << e.what() << " (" << e.underlying() << ") (" << e.path() << ") in " << file << " line " << line; - ThrowRealmFileException(env, ss.str(), e.kind()); + ThrowRealmFileException(env, ss.str(), e.kind(), e.path()); } catch (File::AccessError& e) { ss << e.what() << " (" << e.get_path() << ") in " << file << " line " << line; @@ -195,11 +196,13 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla env->DeleteLocalRef(jExceptionClass); } -void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind) +void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind, const std::string& path) { - jclass cls = env->FindClass("io/realm/exceptions/RealmFileException"); + static JavaClass jrealm_file_exception_cls(env, "io/realm/exceptions/RealmFileException"); + static JavaClass jincompatible_synced_file_cls(env, "io/realm/exceptions/IncompatibleSyncedFileException"); + static JavaMethod jicompatible_synced_ctor(env, jincompatible_synced_file_cls, "", "(Ljava/lang/String;Ljava/lang/String;)V"); + static JavaMethod constructor(env, jrealm_file_exception_cls, "", "(BLjava/lang/String;)V"); - jmethodID constructor = env->GetMethodID(cls, "", "(BLjava/lang/String;)V"); // Initial value to suppress gcc warning. jbyte kind_code = -1; // To suppress compile warning. switch (kind) { @@ -225,13 +228,16 @@ void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::Rea kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED; break; case realm::RealmFileException::Kind::IncompatibleSyncedRealm: - kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_INCOMPATIBLE_SYNC_FILE; - break; - } - jstring jstr = env->NewStringUTF(message.c_str()); - jobject exception = env->NewObject(cls, constructor, kind_code, jstr); + jobject jexception = env->NewObject(jincompatible_synced_file_cls, jicompatible_synced_ctor, + to_jstring(env, message), to_jstring(env, path)); + env->Throw(reinterpret_cast(jexception)); + env->DeleteLocalRef(jexception); + return; + } + jstring jmessage = to_jstring(env, message); + jstring jpath = to_jstring(env, path); + jobject exception = env->NewObject(jrealm_file_exception_cls, constructor, kind_code, jmessage, jpath); env->Throw(reinterpret_cast(exception)); - env->DeleteLocalRef(cls); env->DeleteLocalRef(exception); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index b6546f405a..6678fe935d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -101,6 +101,11 @@ public class RealmConfiguration { private final Realm.Transaction initialDataTransaction; private final boolean readOnly; private final CompactOnLaunchCallback compactOnLaunch; + /** + * Whether this RealmConfiguration is intended to open a + * recovery Realm produced after an offline/online client reset. + */ + private final boolean isRecoveryConfiguration; // We need to enumerate all parameters since SyncConfiguration and RealmConfiguration supports different // subsets of them. @@ -117,7 +122,8 @@ protected RealmConfiguration(@Nullable File realmDirectory, @Nullable RxObservableFactory rxObservableFactory, @Nullable Realm.Transaction initialDataTransaction, boolean readOnly, - @Nullable CompactOnLaunchCallback compactOnLaunch) { + @Nullable CompactOnLaunchCallback compactOnLaunch, + boolean isRecoveryConfiguration) { this.realmDirectory = realmDirectory; this.realmFileName = realmFileName; this.canonicalPath = canonicalPath; @@ -132,6 +138,7 @@ protected RealmConfiguration(@Nullable File realmDirectory, this.initialDataTransaction = initialDataTransaction; this.readOnly = readOnly; this.compactOnLaunch = compactOnLaunch; + this.isRecoveryConfiguration = isRecoveryConfiguration; } public File getRealmDirectory() { @@ -266,6 +273,14 @@ public boolean isReadOnly() { return readOnly; } + /** + * @return {@code true} if this configuration is intended to open a backup Realm (as a result of a client reset). + * @see ClientResetRequiredError + */ + public boolean isRecoveryConfiguration() { + return isRecoveryConfiguration; + } + @Override public boolean equals(Object obj) { if (this == obj) { return true; } @@ -276,6 +291,7 @@ public boolean equals(Object obj) { if (schemaVersion != that.schemaVersion) { return false; } if (deleteRealmIfMigrationNeeded != that.deleteRealmIfMigrationNeeded) { return false; } if (readOnly != that.readOnly) { return false; } + if (isRecoveryConfiguration != that.isRecoveryConfiguration) { return false; } if (realmDirectory != null ? !realmDirectory.equals(that.realmDirectory) : that.realmDirectory != null) { return false; } @@ -317,6 +333,7 @@ public int hashCode() { result = 31 * result + (initialDataTransaction != null ? initialDataTransaction.hashCode() : 0); result = 31 * result + (readOnly ? 1 : 0); result = 31 * result + (compactOnLaunch != null ? compactOnLaunch.hashCode() : 0); + result = 31 * result + (isRecoveryConfiguration ? 1 : 0); return result; } @@ -802,7 +819,8 @@ public RealmConfiguration build() { rxFactory, initialDataTransaction, readOnly, - compactOnLaunch + compactOnLaunch, + false ); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 24eb5faa8d..f83f4f765c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -187,8 +187,10 @@ private OsRealmConfig(final RealmConfiguration config, // Set schema related params. SchemaMode schemaMode = SchemaMode.SCHEMA_MODE_MANUAL; - if (config.isReadOnly()) { + if (config.isRecoveryConfiguration()) { schemaMode = SchemaMode.SCHEMA_MODE_IMMUTABLE; + } else if (config.isReadOnly()) { + schemaMode = SchemaMode.SCHEMA_MODE_READONLY; } else if (syncRealmUrl != null) { schemaMode = SchemaMode.SCHEMA_MODE_ADDITIVE; } else if (config.shouldDeleteRealmIfMigrationNeeded()) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 3659767dc9..2b69a760a0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -131,7 +131,8 @@ private SyncConfiguration(File directory, rxFactory, initialDataTransaction, readOnly, - null + null, + false ); this.user = user; @@ -188,7 +189,7 @@ public static RealmConfiguration forRecovery(String canonicalPath) { } static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) { - return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null); + return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true); } static URI resolveServerUrl(URI serverUrl, String userIdentifier) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/exceptions/IncompatibleSyncedFileException.java b/realm/realm-library/src/objectServer/java/io/realm/exceptions/IncompatibleSyncedFileException.java new file mode 100644 index 0000000000..2742b11ac7 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/exceptions/IncompatibleSyncedFileException.java @@ -0,0 +1,96 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.exceptions; + +import javax.annotation.Nullable; + +import io.realm.RealmConfiguration; +import io.realm.RealmModel; +import io.realm.SyncConfiguration; +import io.realm.internal.Keep; + +/** + * An exception thrown when attempting to open an incompatible Synchronized Realm file. This usually happens + * when the Realm file was created with an older version of the SDK and automatic migration to the current version + * is not possible. When such an exception occurs, the original file is moved to a backup location and a new file is + * created instead. If you wish to migrate any data from the backup location, you can use {@link #getBackupRealmConfiguration()} + * to obtain a {@link RealmConfiguration} that can then be used to open the backup Realm. After that, retry + * opening the original Realm file (which now should be recreated as an empty file) and copy all data from the backup file to the new one. + *

            + * {@code
            + *  SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, serverUri).build();
            + *  try {
            + *      Realm realm = Realm.getInstance(syncConfig);
            + *  } catch (IncompatibleSyncedFileException exception) {
            + *      RealmConfiguration backupConfig = exception.getBackupRealmConfiguration();
            + *      Realm backupRealm = Realm.getInstance(backupConfig);
            + *      realm = Realm.GetInstance(syncConfig);
            + *  }
            + * }
            + * 
            + */ +@Keep +public class IncompatibleSyncedFileException extends RealmFileException { + private final String path; + + public IncompatibleSyncedFileException(String message, String recoveryPath) { + super(Kind.INCOMPATIBLE_SYNC_FILE, message); + this.path = recoveryPath; + } + + /** + * Gets a {@link RealmConfiguration} instance that can be used to open the backup Realm file. + * + * Note: This will use the default Realm module (composed of all {@link RealmModel}), and + * assume no encryption should be used as well. + * + * @return A configuration object for the backup Realm. + */ + public RealmConfiguration getBackupRealmConfiguration() { + return SyncConfiguration.forRecovery(path, null); + } + + /** + * Gets a {@link RealmConfiguration} instance that can be used to open the backup Realm file. + * + * Note: This will use the default Realm module (composed of all {@link RealmModel}). + * + * @param encryptionKey Optional encryption key that was used to encrypt the original Realm file. + * @return A configuration object for the backup Realm. + */ + public RealmConfiguration getBackupRealmConfiguration(@Nullable byte[] encryptionKey) { + return SyncConfiguration.forRecovery(path, encryptionKey); + } + + /** + * Gets a {@link RealmConfiguration} instance that can be used to open the backup Realm file. + * + * @param encryptionKey Optional encryption key that was used to encrypt the original Realm file. + * @param modules restricts Realm schema to the provided module. + * @return A configuration object for the backup Realm. + */ + public RealmConfiguration getBackupRealmConfiguration(@Nullable byte[] encryptionKey, Object... modules) { + return SyncConfiguration.forRecovery(path, encryptionKey, modules); + } + + /** + * @return Absolute path to the backup Realm file. + */ + public String getRecoveryPath() { + return path; + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/assets/stable_id_migration.realm b/realm/realm-library/src/syncIntegrationTest/assets/sync-1.x.realm similarity index 64% rename from realm/realm-library/src/androidTestObjectServer/assets/stable_id_migration.realm rename to realm/realm-library/src/syncIntegrationTest/assets/sync-1.x.realm index cda2d884566a875452a76af6fc06fb7e736eeba9..3f7404b42ec9d13290540635119addab571d23fd 100644 GIT binary patch literal 8192 zcmeHLJ!~UI6n-;1`;!Ed4LQIPLTm$a1Mw+BG8Kj+y6Z0Du1I$_IUhd7mspN<3KB)i zlv_|3i6W&Xgp>|TP`IE-8HvUcjYZ`K$q@x9`QH2_wy|}09mPH=`}F4he{aS+o8lU2 zI*Sc|_~93yhau5tpx24K=AKz!@O<}KtzCUwZC4lQ-`;Ih4-X$bXt$p3e|>+y@y(3g zD(8_za@i_a&OvVOJ!)6KYSip<2)WrhXx3V79YV34fF1rcvAl(Pk13@dB_YcZG|U6b z5ljrJ6Lf>(%A}tC1~j58>jzn$!*+uyw*B=daysW&i;ugh>|dQ73_3T zHZSyIdqU6g%c61}AN>pbX>xgX>($mF>{#Y_c04*S=Q|duIJNm)pPZ*ZvCetU#QAj| z_KQVR48-js$HnN67{lhC5F&Du^hvDKEuzr2n&V~|!m>}q8X39a+!AtSVleNZ4H?f) z+$XtCZ;|wMONh>~bRRX;do@uXmW8cL>ycMDAbFkZKar{I$mzK_KWtm^$1flJwQl3X z{>>xVl|9*)=MG@#rs;A@E>oO>ea zsIKa%5c8-g6PEh~9@-B5dfe{_weUKYJ?60V(1RwHw;+?B$_R;Bc6N@#)|>73+h1?? z-(Bp#vF$gH7)%Ari&osbLsY}!zrD?s{>=EzU3E|4=o>AK8u5s-~ai?zkm9! z{sZ=oCb#b7Mf=v_b!q)x6)%HUKbq`&9$teemBYx`@$X2w0Ix+j{rUU!IX~x@FlcN! z*>e(Gp10ifZ{Z^+JG|WbtK#Kwa@RBGaWs`L=kg6a zld%(ymng2p+iRu@j)V9)m$!iLf6iPGaZsTqo#CG!Ol*hzx!ujXweb0D{)_l3PS#eD qzr5sbF2*l=W(Z^mWC&ykWC&ykWC&ykWC&ykWC&ykWC;A95cn7NHdE3drrN|$uW_hxqN#lG~Bm?=Ddf8YD&+nLd9C1gh}a`U6> zKi!UFqHT;Xh)g#drccsg>Y^*Z-}Ila_{xs&9`B}uLF+-@>8APUem2@3-0cmsr&;c@ zqSg4rVczLIEkI%ycfMbGyQ4BV{$CWHm|5n}G5?n3gU*Ydi1Z;%=@<^ls~``5 z?GPJro|WRT)d_qzZ~gwa#1-swrTh!9`_Eg$^w-_2aL2Iw`HOy*4=u%t)S-#;jl>47 zuJa9U!YMX*sI{vOLF`LJSI<(a?DOhKzVO`RSGI-N_=Mc;HqL^0m1Q{Z;3QWRvNoUFo9*_YUR* z-c$`_7Pe~hjwu-VkuQAwdi?qXNBH2I+2L^$UDulsP;B3?%x6b0_(-9zYwEV%E9$^cHS}1Q z^x-8w{J5Rw1LR3Pcz9XncoY3zA1?6#E}xYA(&tE@&ilYN3HKH0ef>e_y>z$rNboNa zHZ4)r-vx%~GOZEC#bLW3$4M5s#mqPvhFIdN>JP~ptMjD;a)nr_Z&y!#aWfcF79M-) z3L1UQsv_zG)i#PD1z#?oiJ~sTVM7gTU0cO==f;o*AUQw*U$Z3w4eA7FR$rE(T^W$s za^OlOS-z1BRV=2lp9I>7681Y}AX=|1^o5(9o-Pj6(9{j_Lx0^ieY0oWz2o!!jBA-+ z{`vZ??71TUtEPEn-k6DbhsI}*wAebW*1dZ_Ka)D7@@6!?PCq&vv8SK8YKh|?%M3o} zYE-RoeP$?1B11797{^%RvhRKOl3Q zZp+!{k+-q#?r4cy+~)d*v=UozCna3CS^R>#AH9zbOSla^o#U4J^XXFFk)F--fMd_W zI6987WRfdK@S*$n_}X?CVka$jquN-F@zkfWA_v1J51W9L)zx>r<*Ku}E@CF5f0zrYGKu{nk@F^)E z?+=5g{8TmdiEiTUIg5Aj;PL(jHE4@!bVx~ca?$zmposq|9x#%J_Y2;MNz7Z6)RG%X rEbqkFz56~TEyG%a0zrYGKu{nk5EKXs1O Date: Mon, 25 Sep 2017 18:52:00 +0800 Subject: [PATCH 0983/2110] Move PK table migration to JNI totally - Hide all the information about PK table from Java. - Remove SharedRealm.readGroup(). --- .../io/realm/internal/PrimaryKeyTests.java | 18 ++--- .../cpp/io_realm_internal_SharedRealm.cpp | 14 ---- .../src/main/cpp/io_realm_internal_Table.cpp | 76 ++++++++++++------- .../src/main/java/io/realm/RealmCache.java | 10 +-- .../java/io/realm/internal/SharedRealm.java | 7 -- .../main/java/io/realm/internal/Table.java | 28 ++----- 6 files changed, 64 insertions(+), 89 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index 7614175792..08fda9f32b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -179,9 +179,7 @@ public void addEmptyRowWithPrimaryKeyLong() { public void migratePrimaryKeyTableIfNeeded_first() throws IOException { configFactory.copyRealmFromAssets(context, "080_annotationtypes.realm", "default.realm"); sharedRealm = SharedRealm.getInstance(config); - sharedRealm.beginTransaction(); - assertTrue(Table.migratePrimaryKeyTableIfNeeded(sharedRealm)); - sharedRealm.commitTransaction(); + Table.migratePrimaryKeyTableIfNeeded(sharedRealm); Table t = sharedRealm.getTable("class_AnnotationTypes"); assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); assertEquals(RealmFieldType.STRING, sharedRealm.getTable("pk").getColumnType(0)); @@ -191,9 +189,7 @@ public void migratePrimaryKeyTableIfNeeded_first() throws IOException { public void migratePrimaryKeyTableIfNeeded_second() throws IOException { configFactory.copyRealmFromAssets(context, "0841_annotationtypes.realm", "default.realm"); sharedRealm = SharedRealm.getInstance(config); - sharedRealm.beginTransaction(); - assertTrue(Table.migratePrimaryKeyTableIfNeeded(sharedRealm)); - sharedRealm.commitTransaction(); + Table.migratePrimaryKeyTableIfNeeded(sharedRealm); Table t = sharedRealm.getTable("class_AnnotationTypes"); assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); assertEquals("AnnotationTypes", sharedRealm.getTable("pk").getString(0, 0)); @@ -213,9 +209,7 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableMigratedWithRightName( configFactory.copyRealmFromAssets(context, "0841_pk_migration.realm", "default.realm"); sharedRealm = SharedRealm.getInstance(config); - sharedRealm.beginTransaction(); - assertTrue(Table.migratePrimaryKeyTableIfNeeded(sharedRealm)); - sharedRealm.commitTransaction(); + Table.migratePrimaryKeyTableIfNeeded(sharedRealm); Table table = sharedRealm.getTable("pk"); for (int i = 0; i < table.size(); i++) { @@ -255,13 +249,17 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { } catch (IllegalStateException ignored) { // Column has no search index. } + sharedRealm.commitTransaction(); assertFalse(pkTable.hasSearchIndex(classColumn)); Table.migratePrimaryKeyTableIfNeeded(sharedRealm); assertTrue(pkTable.hasSearchIndex(classColumn)); + + sharedRealm.beginTransaction(); // Now it works. table2.addSearchIndex(column2); - sharedRealm.cancelTransaction(); + OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable2", "PKColumn"); + sharedRealm.commitTransaction(); } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 7cf19d9600..474a4c54cd 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -158,20 +158,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsInTransact return static_cast(shared_realm->is_in_transaction()); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeReadGroup(JNIEnv* env, jclass, - jlong shared_realm_ptr) -{ - TR_ENTER_PTR(shared_realm_ptr) - - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - try { - return reinterpret_cast(&shared_realm->read_group()); - } - CATCH_STD() - - return static_cast(NULL); -} - JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv* env, jclass, jlong shared_realm_ptr) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index a0f0e96fe4..c078d9d33d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -20,6 +20,7 @@ #include "io_realm_internal_Table.h" #include "tablebase_tpl.hpp" +#include "shared_realm.hpp" #include "util/format.hpp" #include "java_accessor.hpp" @@ -37,6 +38,7 @@ static_assert(io_realm_internal_Table_MAX_BINARY_SIZE == Table::max_binary_size, static const char* c_null_values_cannot_set_required_msg = "The primary key field '%1' has 'null' values stored. It " "cannot be converted to a '@Required' primary key field."; +static const char* const PK_TABLE_NAME = "pk"; // ObjectStore::c_primaryKeyTableName static const size_t CLASS_COLUMN_INDEX = 0; // ObjectStore::c_primaryKeyObjectClassColumnIndex static const size_t FIELD_COLUMN_INDEX = 1; // ObjectStore::c_primaryKeyPropertyNameColumnIndex @@ -1096,6 +1098,28 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid(JNIEnv*, j return to_jbool(TBL(nativeTablePtr)->is_attached()); // noexcept } +static bool pk_table_needs_migration(ConstTableRef pk_table) +{ + // Fix wrong types (string, int) -> (string, string) + if (pk_table->get_column_type(FIELD_COLUMN_INDEX) == type_Int) { + return true; + } + + // If needed remove "class_" prefix from class names + size_t number_of_rows = pk_table->size(); + for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { + StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); + if (table_name.begins_with(TABLE_PREFIX)) { + return true; + } + } + // From realm-java 2.0.0, pk table's class column requires a search index. + if (!pk_table->has_search_index(CLASS_COLUMN_INDEX)) { + return true; + } + return false; +} + // 1) Fixes interop issue with Cocoa Realm where the Primary Key table had different types. // This affects: // - All Realms created by Cocoa and used by Realm-android up to 0.80.1 @@ -1115,12 +1139,9 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid(JNIEnv*, j // This methods converts the old (wrong) table format (string, integer) to the right (string,string) format and strips // any class names in the col[0] of their "class_" prefix -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded( - JNIEnv*, jclass, jlong groupNativePtr, jlong privateKeyTableNativePtr) +static bool migrate_pk_table(const Group& group, TableRef pk_table) { - auto group = reinterpret_cast(groupNativePtr); - Table* pk_table = TBL(privateKeyTableNativePtr); - jboolean changed = JNI_FALSE; + bool changed = false; // Fix wrong types (string, int) -> (string, string) if (pk_table->get_column_type(FIELD_COLUMN_INDEX) == type_Int) { @@ -1132,7 +1153,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyT for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); size_t col_ndx = static_cast(pk_table->get_int(FIELD_COLUMN_INDEX, row_ndx)); - StringData col_name = group->get_table(table_name)->get_column_name(col_ndx); + StringData col_name = group.get_table(table_name)->get_column_name(col_ndx); // Make a copy of the string pk_table->set_string(tmp_col_ndx, row_ndx, col_name); } @@ -1141,7 +1162,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyT // The column index for the renamed column will then be the same as the deleted old column pk_table->remove_column(FIELD_COLUMN_INDEX); pk_table->rename_column(pk_table->get_column_index(tmp_col_name), StringData("pk_property")); - changed = JNI_TRUE; + changed = true; } // If needed remove "class_" prefix from class names @@ -1153,41 +1174,42 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyT std::string str(table_name.substr(TABLE_PREFIX.length())); StringData sd(str); pk_table->set_string(CLASS_COLUMN_INDEX, row_ndx, sd); - changed = JNI_TRUE; + changed = true; } } // From realm-java 2.0.0, pk table's class column requires a search index. if (!pk_table->has_search_index(CLASS_COLUMN_INDEX)) { pk_table->add_search_index(CLASS_COLUMN_INDEX); - changed = JNI_TRUE; + changed = true; } return changed; } -JNIEXPORT jboolean JNICALL -Java_io_realm_internal_Table_nativePrimaryKeyTableNeedsMigration(JNIEnv*, jclass, jlong primaryKeyTableNativePtr) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded(JNIEnv* env, jclass, + jlong shared_realm_ptr) { - Table* pk_table = TBL(primaryKeyTableNativePtr); + TR_ENTER_PTR(shared_realm_ptr) + auto& shared_realm = *reinterpret_cast(shared_realm_ptr); + try { + if (!shared_realm->read_group().has_table(PK_TABLE_NAME)) { + return; + } - // Fix wrong types (string, int) -> (string, string) - if (pk_table->get_column_type(FIELD_COLUMN_INDEX) == type_Int) { - return JNI_TRUE; - } + auto pk_table = shared_realm->read_group().get_table(PK_TABLE_NAME); + if (!pk_table_needs_migration(pk_table)) { + return; + } - // If needed remove "class_" prefix from class names - size_t number_of_rows = pk_table->size(); - for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { - StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); - if (table_name.begins_with(TABLE_PREFIX)) { - return JNI_TRUE; + shared_realm->begin_transaction(); + if (migrate_pk_table(shared_realm->read_group(), pk_table)) { + shared_realm->commit_transaction(); + } + else { + shared_realm->cancel_transaction(); } } - // From realm-java 2.0.0, pk table's class column requires a search index. - if (!pk_table->has_search_index(CLASS_COLUMN_INDEX)) { - return JNI_TRUE; - } - return JNI_FALSE; + CATCH_STD() } JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema(JNIEnv*, jobject, jlong thisTablePtr, diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 1044874e21..7adc914661 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -315,15 +315,7 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo if (fileExists) { // Primary key problem only exists before we release sync. sharedRealm = SharedRealm.getInstance(configuration); - - if (Table.primaryKeyTableNeedsMigration(sharedRealm)) { - sharedRealm.beginTransaction(); - if (Table.migratePrimaryKeyTableIfNeeded(sharedRealm)) { - sharedRealm.commitTransaction(); - } else { - sharedRealm.cancelTransaction(); - } - } + Table.migratePrimaryKeyTableIfNeeded(sharedRealm); } } } finally { diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 6165ce00d0..13ac86c7d8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -243,11 +243,6 @@ public boolean isInTransaction() { return nativeIsInTransaction(nativePtr); } - // FIXME: This should be removed, migratePrimaryKeyTableIfNeeded is using it which should be in Object Store instead? - long getGroupNative() { - return nativeReadGroup(nativePtr); - } - public boolean hasTable(String name) { return nativeHasTable(nativePtr, name); } @@ -497,8 +492,6 @@ private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealm private static native boolean nativeIsInTransaction(long nativeSharedRealmPtr); - private static native long nativeReadGroup(long nativeSharedRealmPtr); - private static native boolean nativeIsEmpty(long nativeSharedRealmPtr); private static native void nativeRefresh(long nativeSharedRealmPtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index cb2f9b551f..31c004a77d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -39,8 +39,6 @@ public class Table implements NativeObject { public static final boolean NOT_NULLABLE = false; public static final int NO_MATCH = -1; - static final String PRIMARY_KEY_TABLE_NAME = "pk"; - public static final int MAX_BINARY_SIZE = 0xFFFFF8 - 8/*array header size*/; public static final int MAX_STRING_SIZE = 0xFFFFF8 - 8/*array header size*/ - 1; @@ -500,24 +498,12 @@ public void removeSearchIndex(long columnIndex) { * 2) Migration required to fix: https://github.com/realm/realm-java/issues/1703 * This will remove the prefix "class_" from all table names in the pk_column * Any database created on Realm-Java 0.84.1 and below will have this error. + * + * The native method will begin a transaction and make the migration if needed. + * This function should not be called in a transaction. */ - public static boolean migratePrimaryKeyTableIfNeeded(SharedRealm sharedRealm) { - if (sharedRealm == null || !sharedRealm.isInTransaction()) { - throwImmutable(); - } - if (!sharedRealm.hasTable(PRIMARY_KEY_TABLE_NAME)) { - return false; - } - Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); - return nativeMigratePrimaryKeyTableIfNeeded(sharedRealm.getGroupNative(), pkTable.nativePtr); - } - - public static boolean primaryKeyTableNeedsMigration(SharedRealm sharedRealm) { - if (!sharedRealm.hasTable(PRIMARY_KEY_TABLE_NAME)) { - return false; - } - Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); - return nativePrimaryKeyTableNeedsMigration(pkTable.nativePtr); + public static void migratePrimaryKeyTableIfNeeded(SharedRealm sharedRealm) { + nativeMigratePrimaryKeyTableIfNeeded(sharedRealm.getNativePtr()); } public boolean hasSearchIndex(long columnIndex) { @@ -772,9 +758,7 @@ public static String getTableNameForClass(String name) { public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); - private static native boolean nativeMigratePrimaryKeyTableIfNeeded(long groupNativePtr, long primaryKeyTableNativePtr); - - private static native boolean nativePrimaryKeyTableNeedsMigration(long primaryKeyTableNativePtr); + private static native void nativeMigratePrimaryKeyTableIfNeeded(long sharedRealmPtr); private native void nativeAddSearchIndex(long nativePtr, long columnIndex); From 1bbcda7ec99d410be796261825fe719dfa7a7f40 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 26 Sep 2017 10:36:55 +0800 Subject: [PATCH 0984/2110] IncompatibleSyncedFileException only for sync --- realm/realm-library/src/main/cpp/util.cpp | 10 ++++++++-- .../java/io/realm/objectserver/SyncSessionTests.java | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 357ef845f8..72fd8b3ea5 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -199,8 +199,6 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::RealmFileException::Kind kind, const std::string& path) { static JavaClass jrealm_file_exception_cls(env, "io/realm/exceptions/RealmFileException"); - static JavaClass jincompatible_synced_file_cls(env, "io/realm/exceptions/IncompatibleSyncedFileException"); - static JavaMethod jicompatible_synced_ctor(env, jincompatible_synced_file_cls, "", "(Ljava/lang/String;Ljava/lang/String;)V"); static JavaMethod constructor(env, jrealm_file_exception_cls, "", "(BLjava/lang/String;)V"); // Initial value to suppress gcc warning. @@ -228,11 +226,19 @@ void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::Rea kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED; break; case realm::RealmFileException::Kind::IncompatibleSyncedRealm: +#if REALM_ENABLE_SYNC + static JavaClass jincompatible_synced_file_cls(env, + "io/realm/exceptions/IncompatibleSyncedFileException"); + static JavaMethod jicompatible_synced_ctor(env, jincompatible_synced_file_cls, "", + "(Ljava/lang/String;Ljava/lang/String;)V"); jobject jexception = env->NewObject(jincompatible_synced_file_cls, jicompatible_synced_ctor, to_jstring(env, message), to_jstring(env, path)); env->Throw(reinterpret_cast(jexception)); env->DeleteLocalRef(jexception); return; +#else + REALM_ASSERT_RELEASE_EX(false, "'IncompatibleSyncedRealm' should not be thrown for non-sync realm."); +#endif } jstring jmessage = to_jstring(env, message); jstring jpath = to_jstring(env, path); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java index 71ce342a7d..b260e71ccb 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.UUID; +import java.util.concurrent.CountDownLatch; import io.realm.Realm; import io.realm.RealmChangeListener; @@ -36,6 +37,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) From 3ff13e53acc9d4d60d86591968e205e5f30b378d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 26 Sep 2017 20:27:49 +0800 Subject: [PATCH 0985/2110] Re-enable breakingSchemaChange_throws (#5317) --- .../io/realm/SyncedRealmMigrationTests.java | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index 60b25da8fe..16ceef31c0 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -19,19 +19,27 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.hamcrest.CoreMatchers; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import io.realm.entities.IndexedFields; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.OsRealmConfig; +import io.realm.internal.OsSchemaInfo; +import io.realm.internal.SharedRealm; import io.realm.exceptions.IncompatibleSyncedFileException; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.TestSyncConfigurationFactory; @@ -51,6 +59,8 @@ public class SyncedRealmMigrationTests { @Rule public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); @BeforeClass public static void beforeClass () { @@ -135,30 +145,26 @@ public void missingFields_hiddenSilently() { // Check that a Realm cannot be opened if it contain breaking schema changes, like changing a primary key @Test - @Ignore("This test will throw earlier when trying to add a PK field. That case is already covered by" + - " SchemaTest.addField_withPrimaryKeyModifier_notAllowed(). Although this test will still be valuable for" + - "Object Store schema integration.") - // FIXME: Enabled this after OS schema integration. public void breakingSchemaChange_throws() { SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") .schema(PrimaryKeyAsString.class) .build(); // Setup initial Realm schema (with a different primary key) - DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); - RealmSchema schema = dynamicRealm.getSchema(); - dynamicRealm.beginTransaction(); - schema.create(PrimaryKeyAsString.class.getSimpleName()) - .addField(PrimaryKeyAsString.FIELD_PRIMARY_KEY, String.class) - .addField(PrimaryKeyAsString.FIELD_ID, long.class, FieldAttribute.PRIMARY_KEY); - dynamicRealm.commitTransaction(); - dynamicRealm.close(); - - try { - Realm.getInstance(config); - fail(); - } catch (IllegalStateException ignored) { - } + OsObjectSchemaInfo expectedObjectSchema = new OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME) + .addPersistedProperty(PrimaryKeyAsString.FIELD_PRIMARY_KEY, RealmFieldType.STRING, false, true, false) + .addPersistedProperty(PrimaryKeyAsString.FIELD_ID, RealmFieldType.INTEGER, true, true, true) + .build(); + List list = new ArrayList(); + list.add(expectedObjectSchema); + OsSchemaInfo schemaInfo = new OsSchemaInfo(list); + OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config).schemaInfo(schemaInfo); + SharedRealm.getInstance(configBuilder).close(); + + thrown.expectMessage( + CoreMatchers.containsString("The following changes cannot be made in additive-only schema mode:")); + thrown.expect(IllegalStateException.class); + Realm.getInstance(config); } // Check that indexes are not being added if the schema version is the same From 666aab306179c311d7c2025f94423c5049aa4849 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 26 Sep 2017 14:40:56 +0200 Subject: [PATCH 0986/2110] Update credits --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed7b08037f..02c2761ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ -## 3.7.3 (xxxx-xx-xx) +## 3.7.3 (YYYY-MM-DD) ### Bug Fixes * Added support for ISO8601 2-digit time zone designators (#5309). +### Credits + +Thanks to @JussiPekonen for adding support for 2-digit time zone designators when importing JSON (#5309). + ## 3.7.2 (2017-09-12) ### Bug Fixes From 06aaffcd55a0ae5348a0d2d721e5248dd416efaa Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 27 Sep 2017 15:03:08 +0200 Subject: [PATCH 0987/2110] Add support for alpha.38 changes (#5313) --- dependencies.list | 2 +- .../objectServer/java/io/realm/SyncUser.java | 9 +++-- .../java/io/realm/SyncUserInfo.java | 19 ++++++++- .../network/ChangePasswordRequest.java | 4 +- .../network/LookupUserIdResponse.java | 40 +++++++++++-------- .../network/OkHttpAuthenticationServer.java | 2 +- .../java/io/realm/SyncedRealmTests.java | 1 - .../java/io/realm/objectserver/AuthTests.java | 24 ++++++----- .../realm/objectserver/utils/HttpUtils.java | 5 +++ .../realm/objectserver/utils/UserFactory.java | 2 - 10 files changed, 69 insertions(+), 39 deletions(-) diff --git a/dependencies.list b/dependencies.list index 0025e7c835..f0cdeba2d2 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,4 +5,4 @@ REALM_SYNC_SHA256=5e09e54e68e78683e006898f5a703f80e0ee49492fb0f9dc2384fcbbb9f02f # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.35 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.38 diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 0a69915366..7d0a5a2c69 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -447,10 +447,11 @@ public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final AuthenticationServer authServer = SyncManager.getAuthServer(); LookupUserIdResponse response = authServer.retrieveUser(refreshToken, provider, providerUserIdentity, getAuthenticationUrl()); if (!response.isValid()) { - // Right now errors are very inconsistent. See https://github.com/realm/ros/issues/310 - // Treat them all as "User not existing". This is too broad, and should be revisited - // once #310 is fixed. - return null; + if (response.getError().getErrorCode() == ErrorCode.UNKNOWN_ACCOUNT) { + return null; + } else { + throw response.getError(); + } } else { return SyncUserInfo.fromLookupUserIdResponse(response); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java index f14fe208be..a9a1369966 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java @@ -30,15 +30,17 @@ public class SyncUserInfo { private final String identity; private final boolean isAdmin; private final Map metadata; + private final Map accounts; - private SyncUserInfo(String identity, boolean isAdmin, Map metadata) { + private SyncUserInfo(String identity, boolean isAdmin, Map metadata, Map accounts) { this.identity = identity; this.isAdmin = isAdmin; this.metadata = Collections.unmodifiableMap(metadata); + this.accounts = Collections.unmodifiableMap(accounts); } static SyncUserInfo fromLookupUserIdResponse(LookupUserIdResponse response) { - return new SyncUserInfo(response.getUserId(), response.isAdmin(), response.getMetadata()); + return new SyncUserInfo(response.getUserId(), response.isAdmin(), response.getMetadata(), response.getAccounts()); } /** @@ -65,6 +67,19 @@ public Map getMetadata() { return metadata; } + /** + * Returns the accounts associated with this user. The map returned is a map of {@link SyncCredentials.IdentityProvider} + * and the providerId used in that provider. + *

            + * Example being {@code ("password", "my@email.com") }, if the user created an account using the standard account creation + * supported by the Realm Object Server. + *

            + * A user can have multiple accounts associated with it. + * + * @return the accounts associated with the user. + */ + public Map getAccounts() { return accounts; } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java index 827ec0746a..b3ee747053 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java @@ -56,10 +56,12 @@ private ChangePasswordRequest(String token, String newPassword, String userID) { public String toJson() { try { JSONObject request = new JSONObject(); - request.put("new_password", newPassword); if (userID != null) { request.put("user_id", userID); } + JSONObject data = new JSONObject(); + data.put("new_password", newPassword); + request.put("data", data); return request.toString(); } catch (JSONException e) { throw new RuntimeException(e); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java index a9d93d7c93..810b4ee87f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java @@ -15,12 +15,12 @@ */ package io.realm.internal.network; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.HashMap; -import java.util.Iterator; import java.util.Locale; import java.util.Map; @@ -36,12 +36,13 @@ public class LookupUserIdResponse extends AuthServerResponse { private static final String JSON_FIELD_USER_ID = "user_id"; private static final String JSON_FIELD_USER_IS_ADMIN = "is_admin"; - private static final String JSON_FIELD_METADATA = "metadata"; + private static final String JSON_FIELD_METADATA = "metadata"; + private static final String JSON_FIELD_ACCOUNTS = "accounts"; private final String userId; private final Boolean isAdmin; private final Map metadata; - + private final Map accounts; /** * Helper method for creating the proper lookup user response. This method will set the appropriate error * depending on any HTTP response codes or I/O errors. @@ -85,6 +86,7 @@ private LookupUserIdResponse(ObjectServerError error) { this.userId = null; this.isAdmin = null; this.metadata = new HashMap<>(); + this.accounts = new HashMap<>(); } private LookupUserIdResponse(String serverResponse) { @@ -93,11 +95,13 @@ private LookupUserIdResponse(String serverResponse) { Boolean isAdmin; String message; Map metadata; + Map accounts; try { JSONObject obj = new JSONObject(serverResponse); userId = obj.getString(JSON_FIELD_USER_ID); isAdmin = obj.getBoolean(JSON_FIELD_USER_IS_ADMIN); - metadata = jsonToMap(obj.getJSONObject(JSON_FIELD_METADATA)); + metadata = jsonToMap(obj.getJSONArray(JSON_FIELD_METADATA), "key", "value"); + accounts = jsonToMap(obj.getJSONArray(JSON_FIELD_ACCOUNTS), "provider", "provider_id"); error = null; message = String.format(Locale.US, "Identity %s; Path %b", userId, isAdmin); @@ -106,6 +110,7 @@ private LookupUserIdResponse(String serverResponse) { userId = null; isAdmin = null; metadata = new HashMap<>(); + accounts = new HashMap<>(); error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); message = String.format(Locale.US, "Error %s", error.getErrorMessage()); } @@ -115,6 +120,7 @@ private LookupUserIdResponse(String serverResponse) { this.userId = userId; this.isAdmin = isAdmin; this.metadata = metadata; + this.accounts = accounts; } public String getUserId() { @@ -127,21 +133,21 @@ public boolean isAdmin() { public Map getMetadata() { return metadata; } - private static Map jsonToMap(JSONObject json) throws JSONException { - Map map = new HashMap<>(); - if(json != JSONObject.NULL) { - map = toMap(json); - } - return map; - } + public Map getAccounts() { return accounts; } - private static Map toMap(JSONObject object) throws JSONException { + // Assume arrays of key/value irrespectively of what they are named. + // Throws if this is not the case + private static Map jsonToMap(JSONArray array, String keyName, String valueName) throws JSONException { Map map = new HashMap<>(); - Iterator keysItr = object.keys(); - while(keysItr.hasNext()) { - String key = keysItr.next(); - String value = object.getString(key); - map.put(key, value); + if (array == null) { + return map; + } + for (int i = 0; i < array.length(); i++) { + JSONObject obj = array.getJSONObject(i); + if (obj.length() != 2) { + throw new IllegalStateException("Array object not a key/value object. Has " + obj.length() + " fields"); + } + map.put(obj.getString(keyName), obj.getString(valueName)); } return map; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 88a6fcfd20..e3b5db8a09 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -38,7 +38,7 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final String ACTION_LOGOUT = "revoke"; // Auth end point for logging out users private static final String ACTION_CHANGE_PASSWORD = "password"; // Auth end point for changing passwords - private static final String ACTION_LOOKUP_USER_ID = "/users/:provider:/:providerId:"; // Auth end point for looking up user id + private static final String ACTION_LOOKUP_USER_ID = "users/:provider:/:providerId:"; // Auth end point for looking up user id private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index bd53f9209c..c93851536e 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -44,7 +44,6 @@ * Catch all class for tests that not naturally fit anywhere else. */ @RunWith(AndroidJUnit4.class) -@Ignore("See https://github.com/realm/realm-java/issues/5177. All waitForInitialRemoteData tests seem to fail. Must be fixed") public class SyncedRealmTests extends StandardIntegrationTest { @Test diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index fddc2c5ea5..8f313270ed 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -186,7 +186,6 @@ public void onError(ObjectServerError error) { } @Test - @Ignore("Wait for https://github.com/realm/ros/issues/335") public void changePassword() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; @@ -198,15 +197,23 @@ public void changePassword() { String newPassword = "new-password"; userOld.changePassword(newPassword); userOld.logout(); + + // Make sure old password doesn't work + try { + SyncUser.login(SyncCredentials.usernamePassword(username, originalPassword, false), Constants.AUTH_URL); + fail(); + } catch (ObjectServerError e) { + assertEquals(ErrorCode.INVALID_CREDENTIALS, e.getErrorCode()); + } + + // Then login with new password credentials = SyncCredentials.usernamePassword(username, newPassword, false); SyncUser userNew = SyncUser.login(credentials, Constants.AUTH_URL); - assertTrue(userNew.isValid()); assertEquals(userOld.getIdentity(), userNew.getIdentity()); } @Test - @Ignore("See https://github.com/realm/ros/issues/335") public void changePassword_using_admin() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; @@ -234,7 +241,6 @@ public void changePassword_using_admin() { @Test @RunTestInLooperThread - @Ignore("Wait for https://github.com/realm/ros/issues/335") public void changePassword_using_admin_async() { final String username = UUID.randomUUID().toString(); final String originalPassword = "password"; @@ -274,7 +280,6 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread - @Ignore("Wait until https://github.com/realm/ros/issues/309 is resolved") public void changePassword_throwWhenUserIsLoggedOut() { String username = UUID.randomUUID().toString(); String password = "password"; @@ -492,6 +497,7 @@ public void singleUserCanBeLoggedInAndOutRepeatedly() { } @Test + @Ignore("See https://github.com/realm/ros/issues/360") public void revokedRefreshTokenIsNotSameAfterLogin() throws InterruptedException { final CountDownLatch userLoggedInAgain = new CountDownLatch(1); final String uniqueName = UUID.randomUUID().toString(); @@ -508,7 +514,6 @@ public void loggedIn(SyncUser user) { @Override public void loggedOut(SyncUser user) { - SystemClock.sleep(1000); // Remove once https://github.com/realm/ros/issues/304 is fixed SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); SyncUser loggedInUser = SyncUser.login(credentials, Constants.AUTH_URL); @@ -600,7 +605,6 @@ public void execute(Realm realm) { } @Test - @Ignore("Wait for https://github.com/realm/ros/issues/333") public void retrieve() { final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); @@ -618,13 +622,13 @@ public void retrieve() { assertEquals(identity, userInfo.getIdentity()); assertFalse(userInfo.isAdmin()); assertTrue(userInfo.getMetadata().isEmpty()); + assertEquals(username, userInfo.getAccounts().get(SyncCredentials.IdentityProvider.USERNAME_PASSWORD)); } // retrieving a logged out user @Test @RunTestInLooperThread - @Ignore("Wait for https://github.com/realm/ros/issues/333") public void retrieve_logout() { final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); @@ -661,6 +665,7 @@ public void run() { assertEquals(identity, userInfo.getIdentity()); assertFalse(userInfo.isAdmin()); assertTrue(userInfo.getMetadata().isEmpty()); + assertEquals(username, userInfo.getAccounts().get(SyncCredentials.IdentityProvider.USERNAME_PASSWORD)); looperThread.testComplete(); } @@ -692,7 +697,6 @@ public void retrieve_invalidProvider() { } @Test - @Ignore("Wait for https://github.com/realm/ros/issues/333") public void retrieve_notAdmin() { final String username1 = UUID.randomUUID().toString(); final String password1 = "password"; @@ -716,7 +720,6 @@ public void retrieve_notAdmin() { @Test @RunTestInLooperThread - @Ignore("Wait for https://github.com/realm/ros/issues/333") public void retrieve_async() { final String username = UUID.randomUUID().toString(); final String password = "password"; @@ -737,6 +740,7 @@ public void onSuccess(SyncUserInfo userInfo) { assertEquals(identity, userInfo.getIdentity()); assertFalse(userInfo.isAdmin()); assertTrue(userInfo.getMetadata().isEmpty()); + assertEquals(username, userInfo.getAccounts().get(SyncCredentials.IdentityProvider.USERNAME_PASSWORD)); looperThread.testComplete(); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index aba3c0ec97..9c4d3881dc 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -16,6 +16,7 @@ package io.realm.objectserver.utils; +import android.os.SystemClock; import android.util.Log; import java.io.IOException; @@ -56,6 +57,10 @@ public static void startSyncServer() throws Exception { Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + // Work around race condition between starting ROS and logging in first user + // See https://github.com/realm/ros/issues/389 + SystemClock.sleep(2000); } public static void stopSyncServer() throws Exception { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index a669b7dda4..24345677b3 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -149,8 +149,6 @@ public void run() { for (SyncUser user : users.values()) { user.logout(); } - // FIXME https://github.com/realm/ros/issues/338 - SystemClock.sleep(2000); allUsersLoggedOut.countDown(); } From ecd9b24c1559e44072ada909efffee98814f38c7 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 28 Sep 2017 16:05:30 +0900 Subject: [PATCH 0988/2110] update buildTools to 26.0.2 (#5335) --- Dockerfile | 2 +- README.md | 2 +- examples/build.gradle | 2 +- library-benchmarks/build.gradle | 2 +- realm/realm-library/build.gradle | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index f316c71ce3..f1a65fe040 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,7 +50,7 @@ RUN mkdir "${ANDROID_HOME}/licenses" && \ echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" RUN sdkmanager --update RUN sdkmanager 'platform-tools' -RUN sdkmanager 'build-tools;26.0.1' +RUN sdkmanager 'build-tools;26.0.2' RUN sdkmanager 'extras;android;m2repository' RUN sdkmanager 'platforms;android-26' diff --git a/README.md b/README.md index fd6eac9bce..13cea460b7 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install the Android SDK **Build-Tools 26.0.1**, **Android O (API 26)** (for example through Android Studio’s **Android SDK Manager**). + * Download & install the Android SDK **Build-Tools 26.0.2**, **Android O (API 26)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * If you use Android Studio, Android Studio 3.0 or later is required. diff --git a/examples/build.gradle b/examples/build.gradle index 2098d0d2e5..c0d569277d 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,5 +1,5 @@ project.ext.sdkVersion = 26 -project.ext.buildTools = '26.0.1' +project.ext.buildTools = '26.0.2' // Don't cache SNAPSHOT (changing) dependencies. configurations.all { diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index ce15de82a8..13ec8c5955 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -27,7 +27,7 @@ apply plugin: 'realm-android' android { compileSdkVersion 26 - buildToolsVersion "26.0.1" + buildToolsVersion "26.0.2" defaultConfig { minSdkVersion 15 diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 63c26342a1..af6a3bbc83 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -41,7 +41,7 @@ ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProper android { compileSdkVersion 26 - buildToolsVersion '26.0.1' + buildToolsVersion '26.0.2' defaultConfig { minSdkVersion 9 From fbeec3008f2932254e5937445a88fef3de26e1fb Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 29 Sep 2017 12:47:27 +0900 Subject: [PATCH 0989/2110] Update Kotlin to 1.1.51 (#5337) * update Kotlin to 1.1.50 * use -Xjsr305 instead of deprecated -Xjsr305-annotations * update Kotlin to 1.1.51 --- examples/kotlinExample/build.gradle | 2 +- realm/build.gradle | 2 +- realm/realm-library/build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index ee0b573240..ad5ff37929 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.4-3' + ext.kotlin_version = '1.1.51' repositories { jcenter() mavenCentral() diff --git a/realm/build.gradle b/realm/build.gradle index 9e16444ee8..cb16b05480 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.4-3' + ext.kotlin_version = '1.1.51' repositories { mavenLocal() google() diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index af6a3bbc83..28ca6ce9e3 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -162,7 +162,7 @@ project.afterEvaluate { // enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out/ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { - freeCompilerArgs = ["-Xjsr305-annotations=enable"] + freeCompilerArgs = ["-Xjsr305=strict"] } } From b17765508253dbf4bbcc38a6fcc7e7db15c682c4 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 29 Sep 2017 12:47:51 +0900 Subject: [PATCH 0990/2110] update RxJava2 to 2.1.1 (#5343) --- realm/realm-library/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ec8b316534..69f5c4028f 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -179,7 +179,7 @@ repositories { dependencies { - compileOnly 'io.reactivex.rxjava2:rxjava:2.1.0' + compileOnly 'io.reactivex.rxjava2:rxjava:2.1.1' compileOnly 'com.google.code.findbugs:findbugs-annotations:3.0.1' api "io.realm:realm-annotations:${version}" @@ -191,7 +191,7 @@ dependencies { kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation fileTree(dir: 'testLibs', include: ['*.jar']) - androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.0' + androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.1' androidTestImplementation 'com.android.support.test:runner:1.0.0' androidTestImplementation 'com.android.support.test:rules:1.0.0' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' From 5f0ec7afc97d7d2a2c52a066b857cbe4b7e98658 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 29 Sep 2017 15:58:15 +0900 Subject: [PATCH 0991/2110] update test support library to 1.0.1 (#5342) --- examples/unitTestExample/build.gradle | 4 ++-- library-benchmarks/build.gradle | 4 ++-- realm/realm-library/build.gradle | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index 553d5fd771..da2d83f263 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -48,9 +48,9 @@ dependencies { testImplementation "org.powermock:powermock-classloading-xstream:1.6.5" - androidTestImplementation 'com.android.support.test:runner:0.5' + androidTestImplementation 'com.android.support.test:runner:1.0.1' // Set this dependency to use JUnit 4 rules - androidTestImplementation 'com.android.support.test:rules:0.5' + androidTestImplementation 'com.android.support.test:rules:1.0.1' // Set this dependency to build and run Espresso tests androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2' } diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 13ec8c5955..556f8ae9f4 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -53,8 +53,8 @@ repositories { } dependencies { - androidTestImplementation 'com.android.support.test:runner:0.5' - androidTestImplementation 'com.android.support.test:rules:0.5' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test:rules:1.0.1' androidTestImplementation 'junit:junit:4.12' androidTestImplementation 'dk.ilios:spanner:0.6.0' androidTestImplementation 'com.opencsv:opencsv:3.4' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 28ca6ce9e3..ab6372f925 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -192,8 +192,8 @@ dependencies { kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation fileTree(dir: 'testLibs', include: ['*.jar']) androidTestImplementation 'io.reactivex:rxjava:1.1.0' - androidTestImplementation 'com.android.support.test:runner:1.0.0' - androidTestImplementation 'com.android.support.test:rules:1.0.0' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test:rules:1.0.1' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' From 4e9ac64c38419e6a955343e039801aa9699a5852 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 28 Sep 2017 17:47:17 +0800 Subject: [PATCH 0992/2110] Build core from source - Separate core library cmake to RealmCore.cmake - Build core from source code with the new core's cmake build. This is much faster than before, it will only build the needed ABI and release/debug variant. --- realm/realm-library/build.gradle | 31 +---- .../src/main/cpp/CMake/RealmCore.cmake | 125 ++++++++++++++++++ .../realm-library/src/main/cpp/CMakeLists.txt | 72 +++------- 3 files changed, 150 insertions(+), 78 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/CMake/RealmCore.cmake diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 03b329631a..4bd455df9e 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -61,6 +61,7 @@ android { "-DENABLE_DEBUG_CORE=$project.enableDebugCore" if (project.ccachePath) arguments "-DNDK_CCACHE=$project.ccachePath" if (project.lcachePath) arguments "-DNDK_LCACHE=$project.lcachePath" + if (project.coreSourcePath) arguments "-DCORE_SOURCE_PATH=$project.coreSourcePath" if (project.hasProperty('buildTargetABIs') && !project.getProperty('buildTargetABIs').trim().isEmpty()) { abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*')) } else { @@ -120,7 +121,7 @@ android { abortOnError false } - flavorDimensions 'api' + flavorDimensions 'api' productFlavors { base { @@ -496,35 +497,9 @@ task downloadCore() { } } -task compileCore(group: 'build setup', description: 'Compile the core library from source code') { - // Build the library from core source code - doFirst { - if (!coreSourcePath) { - throw new GradleException('The coreSourcePath is not set.') - } - exec { - workingDir = coreSourcePath - commandLine = [ - "bash", - "build.sh", - "build-android" - ] - } - } - - // Copy the core tar ball - doLast { - copy { - from "${coreSourcePath}/realm-core-android-${coreVersion}.tar.gz" - into project.coreArchiveFile.parent - rename "realm-core-android-${coreVersion}.tar.gz", "realm-sync-android-${coreVersion}.tar.gz" - } - } -} - task deployCore(group: 'build setup', description: 'Deploy the latest version of Realm Core') { dependsOn { - coreSourcePath ? compileCore : downloadCore + downloadCore } // Build with the output from core source dir. No need to deploy anything. diff --git a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake new file mode 100644 index 0000000000..bedbba6b6b --- /dev/null +++ b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake @@ -0,0 +1,125 @@ +########################################################################### +# +# Copyright 2017 Realm Inc. +# +# 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(ExternalProject) + +function(build_existing_realm_core core_source_path) + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(debug_lib_suffix "-dbg") + add_compile_options(-DREALM_DEBUG) + else() + add_compile_options(-DNDEBUG) + endif() + + ExternalProject_Add(realm-core + SOURCE_DIR ${core_source_path} + PREFIX ${core_source_path}/build-android-${ANDROID_ABI}-${CMAKE_BUILD_TYPE} + CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DANDROID_ABI=${ANDROID_ABI} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DREALM_BUILD_LIB_ONLY=YES + -DREALM_ENABLE_ENCRYPTION=1 + INSTALL_COMMAND "" + LOG_CONFIGURE 1 + LOG_BUILD 1 + ) + + ExternalProject_Get_Property(realm-core SOURCE_DIR) + ExternalProject_Get_Property(realm-core BINARY_DIR) + + # Create directories that are included in INTERFACE_INCLUDE_DIRECTORIES, as CMake requires they exist at + # configure time, when they'd otherwise not be created until we download and extract core. + file(MAKE_DIRECTORY "${BINARY_DIR}/src") + + set(core_lib_file "${BINARY_DIR}/src/realm/librealm${debug_lib_suffix}.a") + add_library(lib_realm_core STATIC IMPORTED) + set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_file} + IMPORTED_LINK_INTERFACE_LIBRARIES atomic + INTERFACE_INCLUDE_DIRECTORIES "${SOURCE_DIR}/src;${BINARY_DIR}/src") + + ExternalProject_Add_Step(realm-core ensure-libraries + DEPENDEES build + BYPRODUCTS ${core_lib_file} + ) + + add_dependencies(lib_realm_core realm-core) +endfunction() + +# Add the sync released as the library. +function(use_sync_release enable_sync sync_dist_path) + # Link to core/sync debug lib for debug build if it is debug build and linking with debug core is enabled. + if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND ${ENABLE_DEBUG_CORE}) + set(debug_lib_suffix "-dbg") + add_compile_options(-DREALM_DEBUG) + else() + add_compile_options(-DNDEBUG) + endif() + + # Configure import realm core lib + set(core_lib_path ${sync_dist_path}/librealm-android-${ANDROID_ABI}${debug_lib_suffix}.a) + if (NOT EXISTS ${core_lib_path}) + if (ARMEABI) + set(core_lib_path ${sync_dist_path}/librealm-android-arm${debug_lib_suffix}.a) + elseif (ARMEABI_V7A) + set(core_lib_path ${sync_dist_path}/librealm-android-arm-v7a${debug_lib_suffix}.a) + elseif (ARM64_V8A) + set(core_lib_path ${sync_dist_path}/librealm-android-arm64${debug_lib_suffix}.a) + else() + message(FATAL_ERROR "Cannot find core lib file: ${core_lib_path}") + endif() + endif() + + add_library(lib_realm_core STATIC IMPORTED) + + # -latomic is not set by default for mips and armv5. + # See https://code.google.com/p/android/issues/detail?id=182094 + set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_path} + IMPORTED_LINK_INTERFACE_LIBRARIES atomic + INTERFACE_INCLUDE_DIRECTORIES "${sync_dist_path}/include") + + if (enable_sync) + # Sync static library + set(sync_lib_path ${sync_dist_path}/librealm-sync-android-${ANDROID_ABI}${debug_lib_suffix}.a) + # Workaround for old core's funny ABI nicknames + if (NOT EXISTS ${sync_lib_path}) + if (ARMEABI) + set(sync_lib_path ${sync_dist_path}/librealm-sync-android-arm${debug_lib_suffix}.a) + elseif (ARMEABI_V7A) + set(sync_lib_path ${sync_dist_path}/librealm-sync-android-arm-v7a${debug_lib_suffix}.a) + elseif (ARM64_V8A) + set(sync_lib_path ${sync_dist_path}/librealm-sync-android-arm64${debug_lib_suffix}.a) + else() + message(FATAL_ERROR "Cannot find sync lib file: ${sync_lib_path}") + endif() + endif() + add_library(lib_realm_sync STATIC IMPORTED) + set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_path} + IMPORTED_LINK_INTERFACE_LIBRARIES lib_realm_core) + endif() + + set(REALM_CORE_INCLUDE_DIR "${sync_dist_path}/include") +endfunction() + +# Add core/sync libraries. Set the core_source_path to build core from source. +# FIXME: Build from sync source is not supported yet. +function(use_realm_core enable_sync sync_dist_path core_source_path) + if (core_source_path) + build_existing_realm_core(${core_source_path}) + else() + use_sync_release(${enable_sync} ${sync_dist_path}) + endif() +endfunction() diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 5948d3b29d..a33fb1158e 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -1,5 +1,24 @@ +########################################################################### +# +# Copyright 2017 Realm Inc. +# +# 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. +# +########################################################################### cmake_minimum_required(VERSION 3.6.0) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake") + # find javah find_package(Java COMPONENTS Development) if (NOT Java_Development_FOUND) @@ -63,56 +82,9 @@ create_javah(TARGET jni_headers DEPENDS ${classes_PATH} ) -# Link to core/sync debug lib for debug build if it is debug build and linking with debug core is enabled. -# FIXME: Ideally we should linking to debug core on CI for testing, but the assertions slow down the CI a lot. -if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND ${ENABLE_DEBUG_CORE}) - set(debug_lib_SUFFIX "-dbg") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DREALM_DEBUG") -else() - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DNDEBUG") -endif() - -# Configure import realm core lib -set(core_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-android-${ANDROID_ABI}${debug_lib_SUFFIX}.a) -# Workaround for old core's funny ABI nicknames -if (NOT EXISTS ${core_lib_PATH}) - if (ARMEABI) - set(core_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-android-arm${debug_lib_SUFFIX}.a) - elseif (ARMEABI_V7A) - set(core_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-android-arm-v7a${debug_lib_SUFFIX}.a) - elseif (ARM64_V8A) - set(core_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-android-arm64${debug_lib_SUFFIX}.a) - else() - message(FATAL_ERROR "Cannot find core lib file: ${core_lib_PATH}") - endif() -endif() - -add_library(lib_realm_core STATIC IMPORTED) - -# -latomic is not set by default for mips and armv5. -# See https://code.google.com/p/android/issues/detail?id=182094 -set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_PATH} - IMPORTED_LINK_INTERFACE_LIBRARIES atomic) +include(RealmCore) -if (build_SYNC) - # Sync static library - set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-${ANDROID_ABI}${debug_lib_SUFFIX}.a) - # Workaround for old core's funny ABI nicknames - if (NOT EXISTS ${sync_lib_PATH}) - if (ARMEABI) - set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm${debug_lib_SUFFIX}.a) - elseif (ARMEABI_V7A) - set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm-v7a${debug_lib_SUFFIX}.a) - elseif (ARM64_V8A) - set(sync_lib_PATH ${REALM_CORE_DIST_DIR}/librealm-sync-android-arm64${debug_lib_SUFFIX}.a) - else() - message(FATAL_ERROR "Cannot find sync lib file: ${sync_lib_PATH}") - endif() - endif() - add_library(lib_realm_sync STATIC IMPORTED) - set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_PATH} - IMPORTED_LINK_INTERFACE_LIBRARIES lib_realm_core) -endif() +use_realm_core(${build_SYNC} "${REALM_CORE_DIST_DIR}" "${CORE_SOURCE_PATH}") # Download openssl lib #string(TOLOWER "${CMAKE_BUILD_TYPE}" openssl_build_TYPE) @@ -136,7 +108,7 @@ get_target_property(crypto_LIB crypto IMPORTED_LOCATION) get_target_property(ssl_LIB ssl IMPORTED_LOCATION) # build application's shared lib -include_directories(${REALM_CORE_DIST_DIR}/include +include_directories( ${CMAKE_SOURCE_DIR} ${jni_headers_PATH} ${CMAKE_SOURCE_DIR}/object-store/src) From ee14f58ce13e59602a86a6a6d50063087f428da1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 29 Sep 2017 12:09:49 +0200 Subject: [PATCH 0993/2110] Upgrade ROS to alpha.39 (#5333) --- dependencies.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.list b/dependencies.list index f0cdeba2d2..1bc28f304a 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,4 +5,4 @@ REALM_SYNC_SHA256=5e09e54e68e78683e006898f5a703f80e0ee49492fb0f9dc2384fcbbb9f02f # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.38 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.39 From 68d1bf8a989d1f06102e1f585f286dcade0ce3d3 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Sun, 1 Oct 2017 00:12:42 +0800 Subject: [PATCH 0994/2110] Update Sync to 2.0.0-rc26 and ROS 2.0.0-alpha.42 (#5346) --- CHANGELOG.md | 3 +++ dependencies.list | 7 ++++--- .../androidTest/java/io/realm/CollectionTests.java | 4 +++- .../java/io/realm/ManagedRealmCollectionTests.java | 2 ++ .../java/io/realm/RealmAsyncQueryTests.java | 2 ++ .../java/io/realm/RealmObjectSchemaTests.java | 9 +++++++++ .../androidTest/java/io/realm/RealmObjectTests.java | 12 ++++++++++++ .../src/main/cpp/io_realm_internal_OsRealmConfig.cpp | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- 9 files changed, 37 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 218a261eb0..9dcb1a99c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ ## Internal +* Upgraded to Realm Sync 2.0.0-rc25. +* Upgraded to Realm Core 4.0.0. + ## Credits diff --git a/dependencies.list b/dependencies.list index 1bc28f304a..50a75546e2 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,9 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.0.0-rc21 -REALM_SYNC_SHA256=5e09e54e68e78683e006898f5a703f80e0ee49492fb0f9dc2384fcbbb9f02f70 +REALM_SYNC_VERSION=2.0.0-rc26 +REALM_SYNC_SHA256=98f44f67051df80bae5d51d8b17912e1fcd076f390fa4c50dfc7a6eddb2d2205 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.39 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.42 + diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index 7b38283cfa..8d61753fdb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -84,14 +84,16 @@ protected enum OrderedCollectionMutatorMethod { protected void populateRealm(Realm realm, int objects) { realm.beginTransaction(); realm.delete(AllJavaTypes.class); - realm.delete(NonLatinFieldNames.class); + // realm.delete(NonLatinFieldNames.class); FIXME Disabled until https://github.com/realm/realm-java/issues/5354 is fixed if (objects > 0) { for (int i = 0; i < objects; i++) { AllJavaTypes obj = realm.createObject(AllJavaTypes.class, i); fillObject(i, objects, obj); + /** FIXME Disabled until https://github.com/realm/realm-java/issues/5354 is fixed NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); + */ // Sets the linked object to itself. obj.setFieldObject(obj); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 341b181ad4..6a9443ec9f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -19,6 +19,7 @@ import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -480,6 +481,7 @@ public void sum_partialNullRows() { } @Test + @Ignore("See https://github.com/realm/realm-java/issues/5354") public void sum_nonLatinColumnNames() { OrderedRealmCollection resultList = createNonLatinCollection(realm, collectionClass); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 12223436d2..e0868a7555 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -20,6 +20,7 @@ import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -498,6 +499,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread + @Ignore("See https://github.com/realm/realm-java/issues/5354") public void accessingRealmListOnUnloadedRealmObjectShouldThrow() { Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index dedb61e2ba..ce9a07c8b1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -19,6 +19,7 @@ import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -32,6 +33,7 @@ import io.realm.entities.AllJavaTypes; import io.realm.entities.Dog; +import io.realm.entities.NonLatinFieldNames; import io.realm.internal.Table; import io.realm.rule.TestRealmConfigurationFactory; @@ -1107,6 +1109,13 @@ public void getFieldIndex() { dynamicRealm.close(); } + @Test + @Ignore("See https://github.com/realm/realm-java/issues/5354") + public void getFieldType_nonLatinName() { + RealmObjectSchema objSchema = realm.getSchema().get(NonLatinFieldNames.class.getSimpleName()); + assertEquals(RealmFieldType.INTEGER, objSchema.getFieldType(NonLatinFieldNames.FIELD_LONG_GREEK_CHAR)); + } + private interface FieldRunnable { void run(String fieldName); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index e8b8221e46..8844cfcc23 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -22,6 +22,7 @@ import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -48,6 +49,7 @@ import io.realm.entities.CustomMethods; import io.realm.entities.CyclicType; import io.realm.entities.Dog; +import io.realm.entities.NonLatinFieldNames; import io.realm.entities.NullTypes; import io.realm.entities.StringAndInt; import io.realm.entities.pojo.AllTypesRealmModel; @@ -2120,4 +2122,14 @@ public void setter_string_long_values() { assertThat(expected.getMessage(), CoreMatchers.containsString("which exceeds the max string length")); } } + + @Test + @Ignore("See https://github.com/realm/realm-java/issues/5354") + public void setter_nonLatinFieldName() { + // Reproduces https://github.com/realm/realm-java/pull/5346 + realm.beginTransaction(); + NonLatinFieldNames obj = realm.createObject(NonLatinFieldNames.class); + obj.setΔέλτα(42); + realm.commitTransaction(); + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 099e984014..567aae92fa 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -299,7 +299,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSy if (access_token_string) { // reusing cached valid token JStringAccessor access_token(env, access_token_string); - session->refresh_access_token(access_token, realm::util::Optional(syncConfig.realm_url)); + session->refresh_access_token(access_token, realm::util::Optional(syncConfig.realm_url())); } }; diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 317dc9b3d9..0d0615caaf 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 317dc9b3d9ef1b5de5ace72e05ac6d58e443bef5 +Subproject commit 0d0615caaf0df6dbccca3f86a05303892c3a7a2e From e5287770615abac7d93895028893c80d6653484e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 1 Oct 2017 00:20:25 +0200 Subject: [PATCH 0995/2110] Use release version of Sync when testing (#5358) --- realm/realm-library/build.gradle | 2 +- .../src/androidTest/java/io/realm/CollectionTests.java | 4 +--- .../java/io/realm/ManagedRealmCollectionTests.java | 1 - .../src/androidTest/java/io/realm/RealmAsyncQueryTests.java | 1 - .../src/androidTest/java/io/realm/RealmObjectSchemaTests.java | 1 - .../src/androidTest/java/io/realm/RealmObjectTests.java | 1 - 6 files changed, 2 insertions(+), 8 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 4bd455df9e..bf4ad83f42 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -37,7 +37,7 @@ ext.coreDir = file(project.coreSourcePath ? ext.ccachePath = project.findProperty('ccachePath') ?: System.getenv('NDK_CCACHE') ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE') // Set to true to enable linking with debug core. -ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProperty('enableDebugCore') : true +ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProperty('enableDebugCore') : false //FIXME Use 'false' as default until https://github.com/realm/realm-java/issues/5354 is fixed android { compileSdkVersion 26 diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index 8d61753fdb..c45a8cd2b9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -84,16 +84,14 @@ protected enum OrderedCollectionMutatorMethod { protected void populateRealm(Realm realm, int objects) { realm.beginTransaction(); realm.delete(AllJavaTypes.class); - // realm.delete(NonLatinFieldNames.class); FIXME Disabled until https://github.com/realm/realm-java/issues/5354 is fixed + realm.delete(NonLatinFieldNames.class); if (objects > 0) { for (int i = 0; i < objects; i++) { AllJavaTypes obj = realm.createObject(AllJavaTypes.class, i); fillObject(i, objects, obj); - /** FIXME Disabled until https://github.com/realm/realm-java/issues/5354 is fixed NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); - */ // Sets the linked object to itself. obj.setFieldObject(obj); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 6a9443ec9f..96fc619417 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -481,7 +481,6 @@ public void sum_partialNullRows() { } @Test - @Ignore("See https://github.com/realm/realm-java/issues/5354") public void sum_nonLatinColumnNames() { OrderedRealmCollection resultList = createNonLatinCollection(realm, collectionClass); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index e0868a7555..770337505e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -499,7 +499,6 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread - @Ignore("See https://github.com/realm/realm-java/issues/5354") public void accessingRealmListOnUnloadedRealmObjectShouldThrow() { Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index ce9a07c8b1..e77c0543db 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -1110,7 +1110,6 @@ public void getFieldIndex() { } @Test - @Ignore("See https://github.com/realm/realm-java/issues/5354") public void getFieldType_nonLatinName() { RealmObjectSchema objSchema = realm.getSchema().get(NonLatinFieldNames.class.getSimpleName()); assertEquals(RealmFieldType.INTEGER, objSchema.getFieldType(NonLatinFieldNames.FIELD_LONG_GREEK_CHAR)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 8844cfcc23..258b29483a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -2124,7 +2124,6 @@ public void setter_string_long_values() { } @Test - @Ignore("See https://github.com/realm/realm-java/issues/5354") public void setter_nonLatinFieldName() { // Reproduces https://github.com/realm/realm-java/pull/5346 realm.beginTransaction(); From d9e0ae4ac083286c5440cc4bdb0f18d9c70fdb45 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Sun, 1 Oct 2017 19:14:49 +0900 Subject: [PATCH 0996/2110] Add Supports for Primitive Lists (#5031) * Extended Annotation Processor to support it in model classes * Relaxed Generic Constraints * Support in insert/insertOrUpdate * Support in copyToRealm/copyToRealmOrUpdate * Support in copyFromRealm --- CHANGELOG.md | 4 + .../examples/kotlin/KotlinExampleActivity.kt | 2 +- .../io/realm/processor/ClassMetaData.java | 168 +- .../java/io/realm/processor/Constants.java | 29 +- .../io/realm/processor/RealmProcessor.java | 6 +- .../processor/RealmProxyClassGenerator.java | 266 +- .../java/io/realm/processor/TypeMirrors.java | 80 + .../main/java/io/realm/processor/Utils.java | 44 +- .../processor/RealmBacklinkProcessorTest.java | 10 + .../processor/ValueListProcessorTest.java | 49 + .../io/realm/AllTypesRealmProxy.java | 1150 +++++++- .../io/realm/NullTypesRealmProxy.java | 2373 ++++++++++++++++- .../test/resources/some/test/AllTypes.java | 12 + .../some/test/InvalidListElementType.java | 28 + .../some/test/InvalidResultsElementType.java | 31 + .../test/resources/some/test/NullTypes.java | 39 + .../test/resources/some/test/ValueList.java | 35 + .../io/realm/DynamicRealmObjectTests.java | 6 +- .../io/realm/LinkingObjectsDynamicTests.java | 35 + .../realm/ManagedRealmListForValueTests.java | 1563 +++++++++++ ...ManagedRealmListForValue_toArrayTests.java | 467 ++++ .../OrderedRealmCollectionIteratorTests.java | 11 +- .../androidTest/java/io/realm/QueryTests.java | 10 + .../io/realm/RealmConfigurationTests.java | 20 +- .../java/io/realm/RealmListTests.java | 42 +- .../java/io/realm/RealmObjectTests.java | 2 +- .../java/io/realm/RealmSchemaTests.java | 58 +- .../androidTest/java/io/realm/RealmTests.java | 165 +- .../java/io/realm/entities/AllJavaTypes.java | 103 + .../java/io/realm/entities/AllTypes.java | 76 +- .../io/realm/entities/AllTypesPrimaryKey.java | 64 + .../realm/entities/DefaultValueOfField.java | 175 +- .../java/io/realm/entities/NullTypes.java | 221 ++ .../java/io/realm/internal/OsListTests.java | 484 ++++ .../realm/internal/SortDescriptorTests.java | 7 + .../main/cpp/io_realm_internal_Collection.cpp | 109 +- .../src/main/cpp/io_realm_internal_OsList.cpp | 408 ++- .../src/main/cpp/io_realm_internal_Table.cpp | 30 +- .../cpp/io_realm_internal_UncheckedRow.cpp | 29 +- .../src/main/cpp/java_accessor.hpp | 206 +- .../src/main/cpp/java_class_global_def.cpp | 43 + .../src/main/cpp/java_class_global_def.hpp | 24 + .../cpp/observable_collection_wrapper.hpp | 105 + .../src/main/cpp/tablebase_tpl.hpp | 47 - realm/realm-library/src/main/cpp/util.cpp | 29 +- realm/realm-library/src/main/cpp/util.hpp | 5 +- .../java/io/realm/DynamicRealmObject.java | 61 +- .../java/io/realm/OrderedRealmCollection.java | 4 +- .../OrderedRealmCollectionChangeListener.java | 4 +- .../io/realm/OrderedRealmCollectionImpl.java | 47 +- .../realm/OrderedRealmCollectionSnapshot.java | 2 +- .../src/main/java/io/realm/Realm.java | 36 + .../main/java/io/realm/RealmCollection.java | 2 +- .../main/java/io/realm/RealmFieldType.java | 138 +- .../src/main/java/io/realm/RealmList.java | 1042 ++++++-- .../main/java/io/realm/RealmObjectSchema.java | 6 + .../src/main/java/io/realm/RealmQuery.java | 96 +- .../src/main/java/io/realm/RealmResults.java | 2 +- .../java/io/realm/internal/CheckedRow.java | 35 + .../java/io/realm/internal/Collection.java | 71 +- .../java/io/realm/internal/InvalidRow.java | 12 +- .../realm/internal/ObservableCollection.java | 72 + .../main/java/io/realm/internal/OsList.java | 222 +- .../io/realm/internal/OsObjectSchemaInfo.java | 20 +- .../java/io/realm/internal/PendingRow.java | 12 +- .../main/java/io/realm/internal/Property.java | 46 +- .../src/main/java/io/realm/internal/Row.java | 7 +- .../java/io/realm/internal/UncheckedRow.java | 12 +- .../io/realm/rx/RealmObservableFactory.java | 52 +- .../java/io/realm/rx/RxObservableFactory.java | 26 +- 70 files changed, 10076 insertions(+), 821 deletions(-) create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java create mode 100644 realm/realm-annotations-processor/src/test/java/io/realm/processor/ValueListProcessorTest.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/InvalidListElementType.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/InvalidResultsElementType.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/ValueList.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValueTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValue_toArrayTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java create mode 100644 realm/realm-library/src/main/cpp/java_class_global_def.cpp create mode 100644 realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp delete mode 100644 realm/realm-library/src/main/cpp/tablebase_tpl.hpp create mode 100644 realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dcb1a99c4..c09db3b3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,14 @@ * Removed deprecated API `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. * `SyncUser.Callback` to becomes generic. * Removed `SyncUser.getAccessToken` method from public API, and rename it to `getRefreshToken`. +* Relaxed upper bound of type parameter of `RealmList`, `RealmQuery`, `RealmResults`, `RealmCollection`, `OrderedRealmCollection` and `OrderedRealmCollectionSnapshot`. ## Deprecated ## Enhancements +* Now users can use `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` as a type parameter of `RealmList`. + ## Bug Fixes ## Internal @@ -29,6 +32,7 @@ ### Breaking Changes * `RealmResults.distinct()`/`RealmResults.distinctAsync()` have been removed. Use `RealmQuery.distinct()`/`RealmQuery.distinctAsync()` instead. +* `RealmQuery.createQuery(Realm, Class)`, `RealmQuery.createDynamicQuery(DynamicRealm, String)`, `RealmQuery.createQueryFromResult(RealmResults)` and `RealmQuery.createQueryFromList(RealmList)` have been removed. Use `Realm.where(Class)`, `DynamicRealm.where(String)`, RealmResults.where()` and `RealmList.where()` instead. ### Enhancements diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index bc4d648959..643704a169 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -174,7 +174,7 @@ class KotlinExampleActivity : Activity() { // Sorting val sortedPersons = realm.where(Person::class.java).findAllSorted("age", Sort.DESCENDING) - status += "\nSorting ${sortedPersons.last().name} == ${realm.where(Person::class.java).findAll().first().name}" + status += "\nSorting ${sortedPersons.last()?.name} == ${realm.where(Person::class.java).findAll().first()?.name}" } finally { realm.close() diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index d58b0bc051..3f9cae0b10 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -57,6 +57,7 @@ public class ClassMetaData { private final List indexedFields = new ArrayList(); // list of all fields marked @Index. private final Set backlinks = new HashSet(); private final Set nullableFields = new HashSet(); // Set of fields which can be nullable + private final Set nullableValueListFields = new HashSet(); // Set of fields whose elements can be nullable private String packageName; // package name for model class. private boolean hasDefaultConstructor; // True if model has a public no-arg constructor. @@ -66,21 +67,36 @@ public class ClassMetaData { private boolean containsHashCode; private final List validPrimaryKeyTypes; + private final List validListValueTypes; private final Types typeUtils; private final Elements elements; - public ClassMetaData(ProcessingEnvironment env, TypeElement clazz) { + public ClassMetaData(ProcessingEnvironment env, TypeMirrors typeMirrors, TypeElement clazz) { this.classType = clazz; this.className = clazz.getSimpleName().toString(); typeUtils = env.getTypeUtils(); elements = env.getElementUtils(); - TypeMirror stringType = env.getElementUtils().getTypeElement("java.lang.String").asType(); + + validPrimaryKeyTypes = Arrays.asList( - stringType, - typeUtils.getPrimitiveType(TypeKind.SHORT), - typeUtils.getPrimitiveType(TypeKind.INT), - typeUtils.getPrimitiveType(TypeKind.LONG), - typeUtils.getPrimitiveType(TypeKind.BYTE) + typeMirrors.STRING_MIRROR, + typeMirrors.PRIMITIVE_LONG_MIRROR, + typeMirrors.PRIMITIVE_INT_MIRROR, + typeMirrors.PRIMITIVE_SHORT_MIRROR, + typeMirrors.PRIMITIVE_BYTE_MIRROR + ); + + validListValueTypes = Arrays.asList( + typeMirrors.STRING_MIRROR, + typeMirrors.BINARY_MIRROR, + typeMirrors.BOOLEAN_MIRROR, + typeMirrors.LONG_MIRROR, + typeMirrors.INTEGER_MIRROR, + typeMirrors.SHORT_MIRROR, + typeMirrors.BYTE_MIRROR, + typeMirrors.DOUBLE_MIRROR, + typeMirrors.FLOAT_MIRROR, + typeMirrors.DATE_MIRROR ); for (Element element : classType.getEnclosedElements()) { @@ -167,6 +183,15 @@ public boolean isNullable(VariableElement variableElement) { return nullableFields.contains(variableElement); } + /** + * Checks if the element of {@code RealmList} designated by {@code realmListVariableElement} is nullable. + * + * @return {@code true} if the element is nullable type, {@code false} otherwise. + */ + public boolean isElementNullable(VariableElement realmListVariableElement) { + return nullableValueListFields.contains(realmListVariableElement); + } + /** * Checks if a VariableElement is indexed. * @@ -240,7 +265,7 @@ public boolean generate() { packageName = packageElement.getQualifiedName().toString(); if (!categorizeClassElements()) { return false; } - if (!checkListTypes()) { return false; } + if (!checkCollectionTypes()) { return false; } if (!checkReferenceTypes()) { return false; } if (!checkDefaultConstructor()) { return false; } if (!checkForFinalFields()) { return false; } @@ -274,25 +299,14 @@ private boolean categorizeClassElements() { return true; } - private boolean checkListTypes() { + private boolean checkCollectionTypes() { for (VariableElement field : fields) { - if (Utils.isRealmList(field) || Utils.isRealmResults(field)) { - // Check for missing generic (default back to Object) - if (Utils.getGenericTypeQualifiedName(field) == null) { - Utils.error("No generic type supplied for field", field); + if (Utils.isRealmList(field)) { + if (!checkRealmListType(field)) { return false; } - - // Check that the referenced type is a concrete class and not an interface - TypeMirror fieldType = field.asType(); - List typeArguments = ((DeclaredType) fieldType).getTypeArguments(); - String genericCanonicalType = typeArguments.get(0).toString(); - TypeElement typeElement = elements.getTypeElement(genericCanonicalType); - if (typeElement.getSuperclass().getKind() == TypeKind.NONE) { - Utils.error( - "Only concrete Realm classes are allowed in RealmLists. " - + "Neither interfaces nor abstract classes are allowed.", - field); + } else if (Utils.isRealmResults(field)) { + if (!checkRealmResultsType(field)) { return false; } } @@ -301,6 +315,75 @@ private boolean checkListTypes() { return true; } + private boolean checkRealmListType(VariableElement field) { + // Check for missing generic (default back to Object) + if (Utils.getGenericTypeQualifiedName(field) == null) { + Utils.error("No generic type supplied for field", field); + return false; + } + + // Check that the referenced type is a concrete class and not an interface + TypeMirror fieldType = field.asType(); + final TypeMirror elementTypeMirror = ((DeclaredType) fieldType).getTypeArguments().get(0); + if (elementTypeMirror.getKind() == TypeKind.DECLARED /* class of interface*/) { + TypeElement elementTypeElement = (TypeElement) ((DeclaredType) elementTypeMirror).asElement(); + if (elementTypeElement.getSuperclass().getKind() == TypeKind.NONE) { + Utils.error( + "Only concrete Realm classes are allowed in RealmLists. " + + "Neither interfaces nor abstract classes are allowed.", + field); + return false; + } + } + + // Check if the actual value class is acceptable + if (!validListValueTypes.contains(elementTypeMirror) && !Utils.isRealmModel(elementTypeMirror)) { + final StringBuilder messageBuilder = new StringBuilder( + "Element type of RealmList must be a class implementing 'RealmModel' or one of the "); + final String separator = ", "; + for (TypeMirror type : validListValueTypes) { + messageBuilder.append('\'').append(type.toString()).append('\'').append(separator); + } + messageBuilder.setLength(messageBuilder.length() - separator.length()); + messageBuilder.append('.'); + Utils.error(messageBuilder.toString(), field); + return false; + } + + return true; + } + + private boolean checkRealmResultsType(VariableElement field) { + // Only classes implementing RealmModel are allowed since RealmResults field is used only for backlinks. + + // Check for missing generic (default back to Object) + if (Utils.getGenericTypeQualifiedName(field) == null) { + Utils.error("No generic type supplied for field", field); + return false; + } + + TypeMirror fieldType = field.asType(); + final TypeMirror elementTypeMirror = ((DeclaredType) fieldType).getTypeArguments().get(0); + if (elementTypeMirror.getKind() == TypeKind.DECLARED /* class or interface*/) { + TypeElement elementTypeElement = (TypeElement) ((DeclaredType) elementTypeMirror).asElement(); + if (elementTypeElement.getSuperclass().getKind() == TypeKind.NONE) { + Utils.error( + "Only concrete Realm classes are allowed in RealmResults. " + + "Neither interfaces nor abstract classes are allowed.", + field); + return false; + } + } + + // Check if the actual value class is acceptable + if (!Utils.isRealmModel(elementTypeMirror)) { + Utils.error("Element type of RealmResults must be a class implementing 'RealmModel'.", field); + return false; + } + + return true; + } + private boolean checkReferenceTypes() { for (VariableElement field : fields) { if (Utils.isRealmModel(field)) { @@ -376,14 +459,23 @@ private boolean categorizeField(Element element) { if (!categorizeIndexField(element, field)) { return false; } } - if (isRequiredField(field)) { + // @Required annotation of RealmList field only affects its value type, not field itself. + if (Utils.isRealmList(field)) { + // We only check @Required annotation. @org.jetbrains.annotations.NotNull annotation should not affect nullability of the list values. + if (!hasRequiredAnnotation(field)) { + final List fieldTypeArguments = ((DeclaredType) field.asType()).getTypeArguments(); + if (fieldTypeArguments.isEmpty() || !Utils.isRealmModel(fieldTypeArguments.get(0))) { + nullableValueListFields.add(field); + } + } + } else if (isRequiredField(field)) { categorizeRequiredField(element, field); } else { - // The field doesn't have the @Required annotation. + // The field doesn't have the @Required and @org.jetbrains.annotations.NotNull annotation. // Without @Required annotation, boxed types/RealmObject/Date/String/bytes should be added to // nullableFields. - // RealmList and Primitive types are NOT nullable always. @Required annotation is not supported. - if (!Utils.isPrimitiveType(field) && !Utils.isRealmList(field)) { + // RealmList of models, RealmResults(backlinks) and primitive types are NOT nullable. @Required annotation is not supported. + if (!Utils.isPrimitiveType(field) && !Utils.isRealmResults(field)) { nullableFields.add(field); } } @@ -409,8 +501,26 @@ private boolean categorizeField(Element element) { return true; } + /** + * This method only checks if the field has {@code @Required} annotation. + * In most cases, you should use {@link #isRequiredField(VariableElement)} to take into account + * Kotlin's annotation as well. + * + * @param field target field. + * @return {@code true} if the field has {@code @Required} annotation, {@code false} otherwise. + * @see #isRequiredField(VariableElement) + */ + private boolean hasRequiredAnnotation(VariableElement field) { + return field.getAnnotation(Required.class) != null; + } + + /** + * Checks if the field is annotated as required. + * @param field target field. + * @return {@code true} if the field is annotated as required, {@code false} otherwise. + */ private boolean isRequiredField(VariableElement field) { - if (field.getAnnotation(Required.class) != null) { + if (hasRequiredAnnotation(field)) { return true; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java index 9dd2b68b8c..fbce99063d 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java @@ -54,7 +54,16 @@ public enum RealmFieldType { REALM_INTEGER("INTEGER", "Long"), OBJECT("OBJECT", "Object"), LIST("LIST", "List"), - BACKLINK("BACKLINK", null); + + BACKLINK("LINKING_OBJECTS", null), + + INTEGER_LIST("INTEGER_LIST", "List"), + BOOLEAN_LIST("BOOLEAN_LIST", "List"), + STRING_LIST("STRING_LIST", "List"), + BINARY_LIST("BINARY_LIST", "List"), + DATE_LIST("DATE_LIST", "List"), + FLOAT_LIST("FLOAT_LIST", "List"), + DOUBLE_LIST("DOUBLE_LIST", "List"); private final String realmType; private final String javaType; @@ -110,4 +119,22 @@ public String getJavaType() { // TODO: add support for char and Char JAVA_TO_REALM_TYPES = Collections.unmodifiableMap(m); } + + + static final Map LIST_ELEMENT_TYPE_TO_REALM_TYPES; + + static { + Map m = new HashMap(); + m.put("java.lang.Byte", RealmFieldType.INTEGER_LIST); + m.put("java.lang.Short", RealmFieldType.INTEGER_LIST); + m.put("java.lang.Integer", RealmFieldType.INTEGER_LIST); + m.put("java.lang.Long", RealmFieldType.INTEGER_LIST); + m.put("java.lang.Float", RealmFieldType.FLOAT_LIST); + m.put("java.lang.Double", RealmFieldType.DOUBLE_LIST); + m.put("java.lang.Boolean", RealmFieldType.BOOLEAN_LIST); + m.put("java.lang.String", RealmFieldType.STRING_LIST); + m.put("java.util.Date", RealmFieldType.DATE_LIST); + m.put("byte[]", RealmFieldType.BINARY_LIST); + LIST_ELEMENT_TYPE_TO_REALM_TYPES = Collections.unmodifiableMap(m); + } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index ef75e16923..758a5ce031 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -172,6 +172,8 @@ public boolean process(Set annotations, RoundEnvironment // Create all proxy classes private boolean processAnnotations(RoundEnvironment roundEnv) { + final TypeMirrors typeMirrors = new TypeMirrors(processingEnv); + for (Element classElement : roundEnv.getElementsAnnotatedWith(RealmClass.class)) { // The class must either extend RealmObject or implement RealmModel @@ -186,7 +188,7 @@ private boolean processAnnotations(RoundEnvironment roundEnv) { return false; } - ClassMetaData metadata = new ClassMetaData(processingEnv, (TypeElement) classElement); + ClassMetaData metadata = new ClassMetaData(processingEnv, typeMirrors, (TypeElement) classElement); if (!metadata.isModelClass()) { continue; } Utils.note("Processing class " + metadata.getSimpleClassName()); @@ -202,7 +204,7 @@ private boolean processAnnotations(RoundEnvironment roundEnv) { Utils.error(e.getMessage(), classElement); } - RealmProxyClassGenerator sourceCodeGenerator = new RealmProxyClassGenerator(processingEnv, metadata); + RealmProxyClassGenerator sourceCodeGenerator = new RealmProxyClassGenerator(processingEnv, typeMirrors, metadata); try { sourceCodeGenerator.generate(); } catch (IOException e) { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 8b2ea52b8d..e1ea070565 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -32,6 +32,7 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import javax.tools.JavaFileObject; @@ -76,6 +77,7 @@ public class RealmProxyClassGenerator { } private final ProcessingEnvironment processingEnvironment; + private final TypeMirrors typeMirrors; private final ClassMetaData metadata; private final String simpleClassName; private final String qualifiedClassName; @@ -83,8 +85,9 @@ public class RealmProxyClassGenerator { private final String qualifiedGeneratedClassName; private final boolean suppressWarnings; - public RealmProxyClassGenerator(ProcessingEnvironment processingEnvironment, ClassMetaData metadata) { + public RealmProxyClassGenerator(ProcessingEnvironment processingEnvironment, TypeMirrors typeMirrors, ClassMetaData metadata) { this.processingEnvironment = processingEnvironment; + this.typeMirrors = typeMirrors; this.metadata = metadata; this.simpleClassName = metadata.getSimpleClassName(); this.qualifiedClassName = metadata.getFullyQualifiedClassName(); @@ -312,7 +315,8 @@ private void emitPersistedFieldAccessors(final JavaWriter writer) throws IOExcep } else if (Utils.isRealmModel(field)) { emitRealmModel(writer, field, fieldName, fieldTypeCanonicalName); } else if (Utils.isRealmList(field)) { - emitRealmList(writer, field, fieldName, fieldTypeCanonicalName); + final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); + emitRealmList(writer, field, fieldName, fieldTypeCanonicalName, elementTypeMirror); } else { throw new UnsupportedOperationException(String.format(Locale.US, "Field \"%s\" of type \"%s\" is not supported.", fieldName, fieldTypeCanonicalName)); @@ -501,15 +505,17 @@ public void emit(JavaWriter writer) throws IOException { //@formatter:on /** - * LinkLists + * ModelList, ValueList */ //@formatter:off private void emitRealmList( JavaWriter writer, final VariableElement field, String fieldName, - String fieldTypeCanonicalName) throws IOException { - String genericType = Utils.getGenericTypeQualifiedName(field); + String fieldTypeCanonicalName, + final TypeMirror elementTypeMirror) throws IOException { + final String genericType = Utils.getGenericTypeQualifiedName(field); + final boolean forRealmModel = Utils.isRealmModel(elementTypeMirror); // Getter writer.emitAnnotation("Override"); @@ -518,9 +524,15 @@ private void emitRealmList( .emitSingleLineComment("use the cached value if available") .beginControlFlow("if (" + fieldName + "RealmList != null)") .emitStatement("return " + fieldName + "RealmList") - .nextControlFlow("else") - .emitStatement("OsList osList = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)) - .emitStatement(fieldName + "RealmList = new RealmList<%s>(%s.class, osList, proxyState.getRealm$realm())", + .nextControlFlow("else"); + if (Utils.isRealmModelList(field)) { + writer.emitStatement("OsList osList = proxyState.getRow$realm().getModelList(%s)", + fieldIndexVariableReference(field)); + } else { + writer.emitStatement("OsList osList = proxyState.getRow$realm().getValueList(%1$s, RealmFieldType.%2$s)", + fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name()); + } + writer.emitStatement(fieldName + "RealmList = new RealmList<%s>(%s.class, osList, proxyState.getRealm$realm())", genericType, genericType) .emitStatement("return " + fieldName + "RealmList") .endControlFlow() @@ -538,12 +550,17 @@ public void emit(JavaWriter writer) throws IOException { field.getSimpleName().toString()) .emitStatement("return") .endControlFlow(); - final String modelFqcn = Utils.getGenericTypeQualifiedName(field); - writer.beginControlFlow("if (value != null && !value.isManaged())") + + if (!forRealmModel) { + return; + } + + writer.emitSingleLineComment("if the list contains unmanaged RealmObjects, convert them to managed.") + .beginControlFlow("if (value != null && !value.isManaged())") .emitStatement("final Realm realm = (Realm) proxyState.getRealm$realm()") - .emitStatement("final RealmList<%1$s> original = value", modelFqcn) - .emitStatement("value = new RealmList<%1$s>()", modelFqcn) - .beginControlFlow("for (%1$s item : original)", modelFqcn) + .emitStatement("final RealmList<%1$s> original = value", genericType) + .emitStatement("value = new RealmList<%1$s>()", genericType) + .beginControlFlow("for (%1$s item : original)", genericType) .beginControlFlow("if (item == null || RealmObject.isManaged(item))") .emitStatement("value.add(item)") .nextControlFlow("else") @@ -555,25 +572,72 @@ public void emit(JavaWriter writer) throws IOException { // LinkView currently does not support default value feature. Just fallback to normal code. } }); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()") - .emitStatement("OsList osList = proxyState.getRow$realm().getLinkList(%s)", fieldIndexVariableReference(field)) - .emitStatement("osList.removeAll()") + writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); + if (Utils.isRealmModelList(field)) { + writer.emitStatement("OsList osList = proxyState.getRow$realm().getModelList(%s)", + fieldIndexVariableReference(field)); + } else { + writer.emitStatement("OsList osList = proxyState.getRow$realm().getValueList(%1$s, RealmFieldType.%2$s)", + fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name()); + } + writer.emitStatement("osList.removeAll()") .beginControlFlow("if (value == null)") .emitStatement("return") - .endControlFlow() - .beginControlFlow("for (RealmModel linkedObject : (RealmList) value)") - .beginControlFlow("if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject)))") - .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must be a valid managed object.\")") - .endControlFlow() - .beginControlFlow("if (((RealmObjectProxy) linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") - .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must belong to the same Realm.\")") - .endControlFlow() - .emitStatement("osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex())") - .endControlFlow() - .endMethod(); + .endControlFlow(); + + if (forRealmModel) { + writer.beginControlFlow("for (RealmModel linkedObject : value)") + .beginControlFlow("if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject)))") + .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must be a valid managed object.\")") + .endControlFlow() + .beginControlFlow("if (((RealmObjectProxy) linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") + .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must belong to the same Realm.\")") + .endControlFlow() + .emitStatement("osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex())") + .endControlFlow(); + } else { + writer.beginControlFlow("for (%1$s item : value)", genericType) + .beginControlFlow("if (item == null)") + .emitStatement(metadata.isElementNullable(field) ? "osList.addNull()" : "throw new IllegalArgumentException(\"Storing 'null' into " + fieldName + "' is not allowed by the schema.\")") + .nextControlFlow("else") + .emitStatement(getStatementForAppendingValueToOsList("osList", "item", elementTypeMirror)) + .endControlFlow() + .endControlFlow(); + } + writer.endMethod(); + } //@formatter:on + private String getStatementForAppendingValueToOsList( + @SuppressWarnings("SameParameterValue") String osListVariableName, + @SuppressWarnings("SameParameterValue") String valueVariableName, + TypeMirror elementTypeMirror) { + if (elementTypeMirror == typeMirrors.STRING_MIRROR) { + return osListVariableName + ".addString(" + valueVariableName + ")"; + } + if (elementTypeMirror == typeMirrors.LONG_MIRROR || elementTypeMirror == typeMirrors.INTEGER_MIRROR + || elementTypeMirror == typeMirrors.SHORT_MIRROR || elementTypeMirror == typeMirrors.BYTE_MIRROR) { + return osListVariableName + ".addLong(" + valueVariableName + ".longValue())"; + } + if (elementTypeMirror.equals(typeMirrors.BINARY_MIRROR)) { + return osListVariableName + ".addBinary(" + valueVariableName + ")"; + } + if (elementTypeMirror == typeMirrors.DATE_MIRROR) { + return osListVariableName + ".addDate(" + valueVariableName + ")"; + } + if (elementTypeMirror == typeMirrors.BOOLEAN_MIRROR) { + return osListVariableName + ".addBoolean(" + valueVariableName + ")"; + } + if (elementTypeMirror == typeMirrors.DOUBLE_MIRROR) { + return osListVariableName + ".addDouble(" + valueVariableName + ".doubleValue())"; + } + if (elementTypeMirror == typeMirrors.FLOAT_MIRROR) { + return osListVariableName + ".addFloat(" + valueVariableName + ".floatValue())"; + } + throw new RuntimeException("unexpected element type: " + elementTypeMirror.toString()); + } + private interface CodeEmitter { void emit(JavaWriter writer) throws IOException; } @@ -666,7 +730,7 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce for (VariableElement field : metadata.getFields()) { String fieldName = field.getSimpleName().toString(); - Constants.RealmFieldType fieldType = getRealmType(field); + Constants.RealmFieldType fieldType = getRealmTypeChecked(field); switch (fieldType) { case NOTYPE: // Perhaps this should fail quickly? @@ -679,12 +743,34 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce break; case LIST: + // only for model list. String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.LIST, \"%s\")", fieldName, genericTypeSimpleName); break; - default: + case INTEGER_LIST: + case BOOLEAN_LIST: + case STRING_LIST: + case BINARY_LIST: + case DATE_LIST: + case FLOAT_LIST: + case DOUBLE_LIST: + writer.emitStatement("builder.addPersistedValueListProperty(\"%s\", %s, %s)", + fieldName, fieldType.getRealmType(), metadata.isElementNullable(field) ? "!Property.REQUIRED" : "Property.REQUIRED"); + break; + + case BACKLINK: + throw new IllegalArgumentException("LinkingObject field should not be added to metadata"); + + case INTEGER: + case FLOAT: + case DOUBLE: + case BOOLEAN: + case STRING: + case DATE: + case BINARY: + case REALM_INTEGER: String nullableFlag = (metadata.isNullable(field) ? "!" : "") + "Property.REQUIRED"; String indexedFlag = (metadata.isIndexed(field) ? "" : "!") + "Property.INDEXED"; String primaryKeyFlag = (metadata.isPrimaryKey(field) ? "" : "!") + "Property.PRIMARY_KEY"; @@ -694,6 +780,10 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce primaryKeyFlag, indexedFlag, nullableFlag); + break; + + default: + throw new IllegalArgumentException("'fieldType' " + fieldName + " is not handled"); } } for (Backlink backlink: metadata.getBacklinkFields()) { @@ -1005,7 +1095,7 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { .endControlFlow() .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .endControlFlow(); - } else if (Utils.isRealmList(field)) { + } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer .emitEmptyLine() @@ -1021,7 +1111,23 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { .emitStatement("%1$sOsList.addRow(cacheItemIndex%1$s)", fieldName) .endControlFlow() .endControlFlow(); - + } else if (Utils.isRealmValueList(field)) { + final String genericType = Utils.getGenericTypeQualifiedName(field); + final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); + writer + .emitEmptyLine() + .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", + genericType, fieldName, interfaceName, getter) + .beginControlFlow("if (%sList != null)", fieldName) + .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .beginControlFlow("if (%1$sItem == null)", fieldName) + .emitStatement(fieldName + "OsList.addNull()") + .nextControlFlow("else") + .emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) + .endControlFlow() + .endControlFlow() + .endControlFlow(); } else { if (metadata.getPrimaryKey() != field) { setTableValues(writer, fieldType, fieldName, interfaceName, getter, false); @@ -1085,7 +1191,7 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { .endControlFlow() .emitStatement("table.setLink(columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) .endControlFlow(); - } else if (Utils.isRealmList(field)) { + } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer .emitEmptyLine() @@ -1102,6 +1208,23 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { .endControlFlow() .endControlFlow(); + } else if (Utils.isRealmValueList(field)) { + final String genericType = Utils.getGenericTypeQualifiedName(field); + final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); + writer + .emitEmptyLine() + .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", + genericType, fieldName, interfaceName, getter) + .beginControlFlow("if (%sList != null)", fieldName) + .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .beginControlFlow("if (%1$sItem == null)", fieldName) + .emitStatement("%1$sOsList.addNull()", fieldName) + .nextControlFlow("else") + .emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) + .endControlFlow() + .endControlFlow() + .endControlFlow(); } else { if (metadata.getPrimaryKey() != field) { setTableValues(writer, fieldType, fieldName, interfaceName, getter, false); @@ -1161,7 +1284,7 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) .endControlFlow(); - } else if (Utils.isRealmList(field)) { + } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer .emitEmptyLine() @@ -1180,6 +1303,25 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { .endControlFlow() .emitEmptyLine(); + } else if (Utils.isRealmValueList(field)) { + final String genericType = Utils.getGenericTypeQualifiedName(field); + final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); + writer + .emitEmptyLine() + .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) + .emitStatement("%1$sOsList.removeAll()", fieldName) + .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", + genericType, fieldName, interfaceName, getter) + .beginControlFlow("if (%sList != null)", fieldName) + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .beginControlFlow("if (%1$sItem == null)", fieldName) + .emitStatement("%1$sOsList.addNull()", fieldName) + .nextControlFlow("else") + .emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) + .endControlFlow() + .endControlFlow() + .endControlFlow() + .emitEmptyLine(); } else { if (metadata.getPrimaryKey() != field) { setTableValues(writer, fieldType, fieldName, interfaceName, getter, true); @@ -1245,7 +1387,7 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) .endControlFlow(); - } else if (Utils.isRealmList(field)) { + } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer .emitEmptyLine() @@ -1264,6 +1406,26 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException .endControlFlow() .emitEmptyLine(); + } else if (Utils.isRealmValueList(field)) { + final String genericType = Utils.getGenericTypeQualifiedName(field); + final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); + writer + .emitEmptyLine() + .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) + .emitStatement("%1$sOsList.removeAll()", fieldName) + .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", + genericType, fieldName, interfaceName, getter) + .beginControlFlow("if (%sList != null)", fieldName) + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .beginControlFlow("if (%1$sItem == null)", fieldName) + .emitStatement("%1$sOsList.addNull()", fieldName) + .nextControlFlow("else") + .emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", + fieldName + "Item", elementTypeMirror)) + .endControlFlow() + .endControlFlow() + .endControlFlow() + .emitEmptyLine(); } else { if (metadata.getPrimaryKey() != field) { setTableValues(writer, fieldType, fieldName, interfaceName, getter, true); @@ -1395,7 +1557,7 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { .endControlFlow() // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. .endControlFlow(); - } else if (Utils.isRealmList(field)) { + } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer.emitEmptyLine() .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) @@ -1417,6 +1579,8 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { .endControlFlow() .emitEmptyLine(); + } else if (Utils.isRealmValueList(field)) { + writer.emitStatement("realmObjectCopy.%s(realmObjectSource.%s())", setter, getter); } else if (Utils.isMutableRealmInteger(field)) { writer.emitEmptyLine() .emitStatement("realmObjectCopy.%1$s().set(realmObjectSource.%1$s().get())", getter); @@ -1471,7 +1635,7 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException .emitSingleLineComment("Deep copy of %s", fieldName) .emitStatement("unmanagedCopy.%s(%s.createDetachedCopy(realmSource.%s(), currentDepth + 1, maxDepth, cache))", setter, Utils.getProxyClassSimpleName(field), getter); - } else if (Utils.isRealmList(field)) { + } else if (Utils.isRealmModelList(field)) { writer .emitEmptyLine() .emitSingleLineComment("Deep copy of %s", fieldName) @@ -1490,6 +1654,11 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException .emitStatement("unmanaged%sList.add(item)", fieldName) .endControlFlow() .endControlFlow(); + } else if (Utils.isRealmValueList(field)) { + writer + .emitEmptyLine() + .emitStatement("unmanagedCopy.%1$s(new RealmList<%2$s>())", setter, Utils.getGenericTypeQualifiedName(field)) + .emitStatement("unmanagedCopy.%1$s().addAll(realmSource.%1$s())", getter); } else if (Utils.isMutableRealmInteger(field)) { // If the user initializes the unmanaged MutableRealmInteger to null, this will fail mysteriously. writer.emitStatement("unmanagedCopy.%s().set(realmSource.%s().get())", getter, getter); @@ -1498,6 +1667,7 @@ private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException } } + writer.emitEmptyLine(); writer.emitStatement("return unmanagedObject"); writer.endMethod(); writer.emitEmptyLine(); @@ -1541,7 +1711,7 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { .endControlFlow() // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. .endControlFlow(); - } else if (Utils.isRealmList(field)) { + } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) @@ -1561,6 +1731,8 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { .endControlFlow() .endControlFlow(); + } else if (Utils.isRealmValueList(field)) { + writer.emitStatement("realmObjectTarget.%s(realmObjectSource.%s())", setter, getter); } else if (Utils.isMutableRealmInteger(field)) { writer.emitStatement("realmObjectTarget.%s().set(realmObjectSource.%s().get())", getter, getter); } else { @@ -1778,7 +1950,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep writer ); - } else if (Utils.isRealmList(field)) { + } else if (Utils.isRealmModelList(field)) { RealmJsonTypeHelper.emitFillRealmListWithJsonValue( "objProxy", metadata.getInternalGetter(fieldName), @@ -1788,6 +1960,10 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep Utils.getProxyClassSimpleName(field), writer); + } else if (Utils.isRealmValueList(field)) { + // FIXME need to implement logic for value list fields. + writer.emitSingleLineComment(String.format(Locale.ENGLISH, + "TODO implement logic for value list %1$s.", field.getSimpleName())); } else if (Utils.isMutableRealmInteger(field)) { RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( "objProxy", @@ -1860,7 +2036,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer ); - } else if (Utils.isRealmList(field)) { + } else if (Utils.isRealmModelList(field)) { RealmJsonTypeHelper.emitFillRealmListFromStream( "objProxy", metadata.getInternalGetter(fieldName), @@ -1869,6 +2045,9 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { Utils.getProxyClassSimpleName(field), writer); + } else if (Utils.isRealmValueList(field)) { + // FIXME need to implement logic for value list fields. + writer.emitSingleLineComment("TODO implement logic for value list."); } else if (Utils.isMutableRealmInteger(field)) { RealmJsonTypeHelper.emitFillJavaTypeFromStream( "objProxy", @@ -1946,9 +2125,16 @@ private Constants.RealmFieldType getRealmType(VariableElement field) { if (Utils.isRealmModel(field)) { return Constants.RealmFieldType.OBJECT; } - if (Utils.isRealmList(field)) { + if (Utils.isRealmModelList(field)) { return Constants.RealmFieldType.LIST; } + if (Utils.isRealmValueList(field)) { + final Constants.RealmFieldType fieldType = Utils.getValueListFieldType(field); + if (fieldType == null) { + return Constants.RealmFieldType.NOTYPE; + } + return fieldType; + } return Constants.RealmFieldType.NOTYPE; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java new file mode 100644 index 0000000000..8757727a14 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java @@ -0,0 +1,80 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.processor; + +import java.util.Date; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + + +/** + * This class provides {@link TypeMirror} instances used in annotation processor. + */ +class TypeMirrors { + final TypeMirror STRING_MIRROR; + final TypeMirror BINARY_MIRROR; + final TypeMirror BOOLEAN_MIRROR; + final TypeMirror LONG_MIRROR; + final TypeMirror INTEGER_MIRROR; + final TypeMirror SHORT_MIRROR; + final TypeMirror BYTE_MIRROR; + final TypeMirror DOUBLE_MIRROR; + final TypeMirror FLOAT_MIRROR; + final TypeMirror DATE_MIRROR; + + final TypeMirror PRIMITIVE_LONG_MIRROR; + final TypeMirror PRIMITIVE_INT_MIRROR; + final TypeMirror PRIMITIVE_SHORT_MIRROR; + final TypeMirror PRIMITIVE_BYTE_MIRROR; + + TypeMirrors(ProcessingEnvironment env) { + final Types typeUtils = env.getTypeUtils(); + final Elements elementUtils = env.getElementUtils(); + + STRING_MIRROR = elementUtils.getTypeElement("java.lang.String").asType(); + BINARY_MIRROR = typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.BYTE)); + BOOLEAN_MIRROR = elementUtils.getTypeElement(Boolean.class.getName()).asType(); + LONG_MIRROR = elementUtils.getTypeElement(Long.class.getName()).asType(); + INTEGER_MIRROR = elementUtils.getTypeElement(Integer.class.getName()).asType(); + SHORT_MIRROR = elementUtils.getTypeElement(Short.class.getName()).asType(); + BYTE_MIRROR = elementUtils.getTypeElement(Byte.class.getName()).asType(); + DOUBLE_MIRROR = elementUtils.getTypeElement(Double.class.getName()).asType(); + FLOAT_MIRROR = elementUtils.getTypeElement(Float.class.getName()).asType(); + DATE_MIRROR = elementUtils.getTypeElement(Date.class.getName()).asType(); + + PRIMITIVE_LONG_MIRROR = typeUtils.getPrimitiveType(TypeKind.LONG); + PRIMITIVE_INT_MIRROR = typeUtils.getPrimitiveType(TypeKind.INT); + PRIMITIVE_SHORT_MIRROR = typeUtils.getPrimitiveType(TypeKind.SHORT); + PRIMITIVE_BYTE_MIRROR = typeUtils.getPrimitiveType(TypeKind.BYTE); + } + + /** + * @return the {@link TypeMirror} of the elements in {@code RealmList}. + */ + public static TypeMirror getRealmListElementTypeMirror(VariableElement field) { + if (!Utils.isRealmList(field)) { + return null; + } + return ((DeclaredType) field.asType()).getTypeArguments().get(0); + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java index 5d08a68c25..24d20531cd 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java @@ -164,11 +164,51 @@ public static boolean isRealmList(VariableElement field) { return typeUtils.isAssignable(field.asType(), realmList); } + /** + * @param field {@link VariableElement} of a value list field. + * @return element type of the list field. + */ + public static Constants.RealmFieldType getValueListFieldType(VariableElement field) { + final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); + return Constants.LIST_ELEMENT_TYPE_TO_REALM_TYPES.get(elementTypeMirror.toString()); + } + + /** + * @return {@code true} if a given field type is {@code RealmList} and its element type is {@Code RealmObject}, + * {@code false} otherwise. + */ + public static boolean isRealmModelList(VariableElement field) { + final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); + if (elementTypeMirror == null) { + return false; + } + return isRealmModel(elementTypeMirror); + } + + /** + * @return {@code true} if a given field type is {@code RealmList} and its element type is value type, + * {@code false} otherwise. + */ + public static boolean isRealmValueList(VariableElement field) { + final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); + if (elementTypeMirror == null) { + return false; + } + return !isRealmModel(elementTypeMirror); + } + /** * @return {@code true} if a given field type is {@code RealmModel}, {@code false} otherwise. */ - public static boolean isRealmModel(VariableElement field) { - return typeUtils.isAssignable(field.asType(), realmModel); + public static boolean isRealmModel(Element field) { + return isRealmModel(field.asType()); + } + + /** + * @return {@code true} if a given type is {@code RealmModel}, {@code false} otherwise. + */ + public static boolean isRealmModel(TypeMirror type) { + return typeUtils.isAssignable(type, realmModel); } public static boolean isRealmResults(VariableElement field) { diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java index 193a0804f9..76a8f61ffd 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java @@ -25,6 +25,7 @@ import javax.lang.model.element.Modifier; import javax.tools.JavaFileObject; +import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; import static org.truth0.Truth.ASSERT; @@ -32,6 +33,7 @@ public class RealmBacklinkProcessorTest { private final JavaFileObject backlinks = JavaFileObjects.forResource("some/test/Backlinks.java"); private final JavaFileObject backlinksTarget = JavaFileObjects.forResource("some/test/BacklinkTarget.java"); + private final JavaFileObject invalidResultsValueType = JavaFileObjects.forResource("some/test/InvalidResultsElementType.java"); @Test public void compileBacklinks() { @@ -202,4 +204,12 @@ private RealmSyntheticTestClass.Field createBacklinkTestClass() { .hasGetter(false) .hasSetter(false); } + + @Test + public void failToCompileInvalidResultsElementType() { + ASSERT.about(javaSource()) + .that(invalidResultsValueType) + .processedWith(new RealmProcessor()) + .failsToCompile(); + } } diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/ValueListProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/ValueListProcessorTest.java new file mode 100644 index 0000000000..1cb0bf9bb9 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/ValueListProcessorTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.processor; + +import com.google.testing.compile.JavaFileObjects; + +import org.junit.Ignore; +import org.junit.Test; + +import javax.tools.JavaFileObject; + +import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; +import static org.truth0.Truth.ASSERT; + + +public class ValueListProcessorTest { + private final JavaFileObject valueList = JavaFileObjects.forResource("some/test/ValueList.java"); + private final JavaFileObject invalidListValueType = JavaFileObjects.forResource("some/test/InvalidListElementType.java"); + + @Test + @Ignore("need to implement primitive list support in realm-library") + public void compileValueList() { + ASSERT.about(javaSource()) + .that(valueList) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + @Test + public void failToCompileInvalidListElementType() { + ASSERT.about(javaSource()) + .that(invalidListValueType) + .processedWith(new RealmProcessor()) + .failsToCompile(); + } +} diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 3a914bf064..72a21aa73c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -34,7 +34,7 @@ @SuppressWarnings("all") public class AllTypesRealmProxy extends some.test.AllTypes - implements RealmObjectProxy, AllTypesRealmProxyInterface { + implements RealmObjectProxy, AllTypesRealmProxyInterface { static final class AllTypesColumnInfo extends ColumnInfo { long columnStringIndex; @@ -47,9 +47,19 @@ static final class AllTypesColumnInfo extends ColumnInfo { long columnMutableRealmIntegerIndex; long columnObjectIndex; long columnRealmListIndex; + long columnStringListIndex; + long columnBinaryListIndex; + long columnBooleanListIndex; + long columnLongListIndex; + long columnIntegerListIndex; + long columnShortListIndex; + long columnByteListIndex; + long columnDoubleListIndex; + long columnFloatListIndex; + long columnDateListIndex; AllTypesColumnInfo(OsSchemaInfo schemaInfo) { - super(10); + super(20); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("AllTypes"); this.columnStringIndex = addColumnDetails("columnString", objectSchemaInfo); this.columnLongIndex = addColumnDetails("columnLong", objectSchemaInfo); @@ -61,6 +71,16 @@ static final class AllTypesColumnInfo extends ColumnInfo { this.columnMutableRealmIntegerIndex = addColumnDetails("columnMutableRealmInteger", objectSchemaInfo); this.columnObjectIndex = addColumnDetails("columnObject", objectSchemaInfo); this.columnRealmListIndex = addColumnDetails("columnRealmList", objectSchemaInfo); + this.columnStringListIndex = addColumnDetails("columnStringList", objectSchemaInfo); + this.columnBinaryListIndex = addColumnDetails("columnBinaryList", objectSchemaInfo); + this.columnBooleanListIndex = addColumnDetails("columnBooleanList", objectSchemaInfo); + this.columnLongListIndex = addColumnDetails("columnLongList", objectSchemaInfo); + this.columnIntegerListIndex = addColumnDetails("columnIntegerList", objectSchemaInfo); + this.columnShortListIndex = addColumnDetails("columnShortList", objectSchemaInfo); + this.columnByteListIndex = addColumnDetails("columnByteList", objectSchemaInfo); + this.columnDoubleListIndex = addColumnDetails("columnDoubleList", objectSchemaInfo); + this.columnFloatListIndex = addColumnDetails("columnFloatList", objectSchemaInfo); + this.columnDateListIndex = addColumnDetails("columnDateList", objectSchemaInfo); addBacklinkDetails(schemaInfo, "parentObjects", "AllTypes", "columnObject"); } @@ -88,6 +108,16 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.columnMutableRealmIntegerIndex = src.columnMutableRealmIntegerIndex; dst.columnObjectIndex = src.columnObjectIndex; dst.columnRealmListIndex = src.columnRealmListIndex; + dst.columnStringListIndex = src.columnStringListIndex; + dst.columnBinaryListIndex = src.columnBinaryListIndex; + dst.columnBooleanListIndex = src.columnBooleanListIndex; + dst.columnLongListIndex = src.columnLongListIndex; + dst.columnIntegerListIndex = src.columnIntegerListIndex; + dst.columnShortListIndex = src.columnShortListIndex; + dst.columnByteListIndex = src.columnByteListIndex; + dst.columnDoubleListIndex = src.columnDoubleListIndex; + dst.columnFloatListIndex = src.columnFloatListIndex; + dst.columnDateListIndex = src.columnDateListIndex; } } @@ -105,16 +135,36 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { fieldNames.add("columnMutableRealmInteger"); fieldNames.add("columnObject"); fieldNames.add("columnRealmList"); + fieldNames.add("columnStringList"); + fieldNames.add("columnBinaryList"); + fieldNames.add("columnBooleanList"); + fieldNames.add("columnLongList"); + fieldNames.add("columnIntegerList"); + fieldNames.add("columnShortList"); + fieldNames.add("columnByteList"); + fieldNames.add("columnDoubleList"); + fieldNames.add("columnFloatList"); + fieldNames.add("columnDateList"); FIELD_NAMES = Collections.unmodifiableList(fieldNames); } private AllTypesColumnInfo columnInfo; private ProxyState proxyState; private final MutableRealmInteger.Managed columnMutableRealmIntegerMutableRealmInteger = new MutableRealmInteger.Managed() { - @Override protected ProxyState getProxyState() { return proxyState; } - @Override protected long getColumnIndex() { return columnInfo.columnMutableRealmIntegerIndex; } - }; + @Override protected ProxyState getProxyState() { return proxyState; } + @Override protected long getColumnIndex() { return columnInfo.columnMutableRealmIntegerIndex; } + }; private RealmList columnRealmListRealmList; + private RealmList columnStringListRealmList; + private RealmList columnBinaryListRealmList; + private RealmList columnBooleanListRealmList; + private RealmList columnLongListRealmList; + private RealmList columnIntegerListRealmList; + private RealmList columnShortListRealmList; + private RealmList columnByteListRealmList; + private RealmList columnDoubleListRealmList; + private RealmList columnFloatListRealmList; + private RealmList columnDateListRealmList; private RealmResults parentObjectsBacklinks; AllTypesRealmProxy() { @@ -361,7 +411,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnRealmListRealmList != null) { return columnRealmListRealmList; } else { - OsList osList = proxyState.getRow$realm().getLinkList(columnInfo.columnRealmListIndex); + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.columnRealmListIndex); columnRealmListRealmList = new RealmList(some.test.AllTypes.class, osList, proxyState.getRealm$realm()); return columnRealmListRealmList; } @@ -376,6 +426,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (proxyState.getExcludeFields$realm().contains("columnRealmList")) { return; } + // if the list contains unmanaged RealmObjects, convert them to managed. if (value != null && !value.isManaged()) { final Realm realm = (Realm) proxyState.getRealm$realm(); final RealmList original = value; @@ -391,12 +442,12 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getLinkList(columnInfo.columnRealmListIndex); + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.columnRealmListIndex); osList.removeAll(); if (value == null) { return; } - for (RealmModel linkedObject : (RealmList) value) { + for (RealmModel linkedObject : value) { if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject))) { throw new IllegalArgumentException("Each element of 'value' must be a valid managed object."); } @@ -407,6 +458,396 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } } + @Override + public RealmList realmGet$columnStringList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnStringListRealmList != null) { + return columnStringListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnStringListIndex, RealmFieldType.STRING_LIST); + columnStringListRealmList = new RealmList(java.lang.String.class, osList, proxyState.getRealm$realm()); + return columnStringListRealmList; + } + } + + @Override + public void realmSet$columnStringList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnStringList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnStringListIndex, RealmFieldType.STRING_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.String item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addString(item); + } + } + } + + @Override + public RealmList realmGet$columnBinaryList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnBinaryListRealmList != null) { + return columnBinaryListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBinaryListIndex, RealmFieldType.BINARY_LIST); + columnBinaryListRealmList = new RealmList(byte[].class, osList, proxyState.getRealm$realm()); + return columnBinaryListRealmList; + } + } + + @Override + public void realmSet$columnBinaryList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnBinaryList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBinaryListIndex, RealmFieldType.BINARY_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (byte[] item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addBinary(item); + } + } + } + + @Override + public RealmList realmGet$columnBooleanList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnBooleanListRealmList != null) { + return columnBooleanListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBooleanListIndex, RealmFieldType.BOOLEAN_LIST); + columnBooleanListRealmList = new RealmList(java.lang.Boolean.class, osList, proxyState.getRealm$realm()); + return columnBooleanListRealmList; + } + } + + @Override + public void realmSet$columnBooleanList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnBooleanList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBooleanListIndex, RealmFieldType.BOOLEAN_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Boolean item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addBoolean(item); + } + } + } + + @Override + public RealmList realmGet$columnLongList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnLongListRealmList != null) { + return columnLongListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnLongListIndex, RealmFieldType.INTEGER_LIST); + columnLongListRealmList = new RealmList(java.lang.Long.class, osList, proxyState.getRealm$realm()); + return columnLongListRealmList; + } + } + + @Override + public void realmSet$columnLongList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnLongList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnLongListIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Long item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$columnIntegerList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnIntegerListRealmList != null) { + return columnIntegerListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnIntegerListIndex, RealmFieldType.INTEGER_LIST); + columnIntegerListRealmList = new RealmList(java.lang.Integer.class, osList, proxyState.getRealm$realm()); + return columnIntegerListRealmList; + } + } + + @Override + public void realmSet$columnIntegerList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnIntegerList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnIntegerListIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Integer item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$columnShortList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnShortListRealmList != null) { + return columnShortListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnShortListIndex, RealmFieldType.INTEGER_LIST); + columnShortListRealmList = new RealmList(java.lang.Short.class, osList, proxyState.getRealm$realm()); + return columnShortListRealmList; + } + } + + @Override + public void realmSet$columnShortList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnShortList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnShortListIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Short item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$columnByteList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnByteListRealmList != null) { + return columnByteListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnByteListIndex, RealmFieldType.INTEGER_LIST); + columnByteListRealmList = new RealmList(java.lang.Byte.class, osList, proxyState.getRealm$realm()); + return columnByteListRealmList; + } + } + + @Override + public void realmSet$columnByteList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnByteList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnByteListIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Byte item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$columnDoubleList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnDoubleListRealmList != null) { + return columnDoubleListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDoubleListIndex, RealmFieldType.DOUBLE_LIST); + columnDoubleListRealmList = new RealmList(java.lang.Double.class, osList, proxyState.getRealm$realm()); + return columnDoubleListRealmList; + } + } + + @Override + public void realmSet$columnDoubleList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnDoubleList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDoubleListIndex, RealmFieldType.DOUBLE_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Double item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addDouble(item.doubleValue()); + } + } + } + + @Override + public RealmList realmGet$columnFloatList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnFloatListRealmList != null) { + return columnFloatListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnFloatListIndex, RealmFieldType.FLOAT_LIST); + columnFloatListRealmList = new RealmList(java.lang.Float.class, osList, proxyState.getRealm$realm()); + return columnFloatListRealmList; + } + } + + @Override + public void realmSet$columnFloatList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnFloatList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnFloatListIndex, RealmFieldType.FLOAT_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Float item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addFloat(item.floatValue()); + } + } + } + + @Override + public RealmList realmGet$columnDateList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnDateListRealmList != null) { + return columnDateListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDateListIndex, RealmFieldType.DATE_LIST); + columnDateListRealmList = new RealmList(java.util.Date.class, osList, proxyState.getRealm$realm()); + return columnDateListRealmList; + } + } + + @Override + public void realmSet$columnDateList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnDateList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDateListIndex, RealmFieldType.DATE_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.util.Date item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addDate(item); + } + } + } + @Override public RealmResults realmGet$parentObjects() { BaseRealm realm = proxyState.getRealm$realm(); @@ -430,6 +871,16 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { builder.addPersistedProperty("columnMutableRealmInteger", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedLinkProperty("columnObject", RealmFieldType.OBJECT, "AllTypes"); builder.addPersistedLinkProperty("columnRealmList", RealmFieldType.LIST, "AllTypes"); + builder.addPersistedValueListProperty("columnStringList", RealmFieldType.STRING_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnBinaryList", RealmFieldType.BINARY_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnBooleanList", RealmFieldType.BOOLEAN_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnLongList", RealmFieldType.INTEGER_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnIntegerList", RealmFieldType.INTEGER_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnShortList", RealmFieldType.INTEGER_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnByteList", RealmFieldType.INTEGER_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnDoubleList", RealmFieldType.DOUBLE_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnFloatList", RealmFieldType.FLOAT_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnDateList", RealmFieldType.DATE_LIST, !Property.REQUIRED); builder.addComputedLinkProperty("parentObjects", "AllTypes", "columnObject"); return builder.build(); } @@ -452,8 +903,8 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) - throws JSONException { - final List excludeFields = new ArrayList(2); + throws JSONException { + final List excludeFields = new ArrayList(12); some.test.AllTypes obj = null; if (update) { Table table = realm.getTable(some.test.AllTypes.class); @@ -479,8 +930,38 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON if (json.has("columnObject")) { excludeFields.add("columnObject"); } - if (json.has("columnRealmList")) { - excludeFields.add("columnRealmList"); + if (json.has("columnRealmList")) { + excludeFields.add("columnRealmList"); + } + if (json.has("columnStringList")) { + excludeFields.add("columnStringList"); + } + if (json.has("columnBinaryList")) { + excludeFields.add("columnBinaryList"); + } + if (json.has("columnBooleanList")) { + excludeFields.add("columnBooleanList"); + } + if (json.has("columnLongList")) { + excludeFields.add("columnLongList"); + } + if (json.has("columnIntegerList")) { + excludeFields.add("columnIntegerList"); + } + if (json.has("columnShortList")) { + excludeFields.add("columnShortList"); + } + if (json.has("columnByteList")) { + excludeFields.add("columnByteList"); + } + if (json.has("columnDoubleList")) { + excludeFields.add("columnDoubleList"); + } + if (json.has("columnFloatList")) { + excludeFields.add("columnFloatList"); + } + if (json.has("columnDateList")) { + excludeFields.add("columnDateList"); } if (json.has("columnString")) { if (json.isNull("columnString")) { @@ -564,13 +1045,23 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON } } } + // TODO implement logic for value listcolumnStringList. + // TODO implement logic for value listcolumnBinaryList. + // TODO implement logic for value listcolumnBooleanList. + // TODO implement logic for value listcolumnLongList. + // TODO implement logic for value listcolumnIntegerList. + // TODO implement logic for value listcolumnShortList. + // TODO implement logic for value listcolumnByteList. + // TODO implement logic for value listcolumnDoubleList. + // TODO implement logic for value listcolumnFloatList. + // TODO implement logic for value listcolumnDateList. return obj; } @SuppressWarnings("cast") @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader reader) - throws IOException { + throws IOException { boolean jsonHasPrimaryKey = false; final some.test.AllTypes obj = new some.test.AllTypes(); final AllTypesRealmProxyInterface objProxy = (AllTypesRealmProxyInterface) obj; @@ -636,7 +1127,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } else if (name.equals("columnMutableRealmInteger")) { Long val = null; if (reader.peek() != JsonToken.NULL) { - val = reader.nextLong() + val = reader.nextLong(); } else { reader.skipValue(); } @@ -662,6 +1153,26 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } reader.endArray(); } + } else if (name.equals("columnStringList")) { + // TODO implement logic for value list. + } else if (name.equals("columnBinaryList")) { + // TODO implement logic for value list. + } else if (name.equals("columnBooleanList")) { + // TODO implement logic for value list. + } else if (name.equals("columnLongList")) { + // TODO implement logic for value list. + } else if (name.equals("columnIntegerList")) { + // TODO implement logic for value list. + } else if (name.equals("columnShortList")) { + // TODO implement logic for value list. + } else if (name.equals("columnByteList")) { + // TODO implement logic for value list. + } else if (name.equals("columnDoubleList")) { + // TODO implement logic for value list. + } else if (name.equals("columnFloatList")) { + // TODO implement logic for value list. + } else if (name.equals("columnDateList")) { + // TODO implement logic for value list. } else { reader.skipValue(); } @@ -767,6 +1278,16 @@ public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, } } + realmObjectCopy.realmSet$columnStringList(realmObjectSource.realmGet$columnStringList()); + realmObjectCopy.realmSet$columnBinaryList(realmObjectSource.realmGet$columnBinaryList()); + realmObjectCopy.realmSet$columnBooleanList(realmObjectSource.realmGet$columnBooleanList()); + realmObjectCopy.realmSet$columnLongList(realmObjectSource.realmGet$columnLongList()); + realmObjectCopy.realmSet$columnIntegerList(realmObjectSource.realmGet$columnIntegerList()); + realmObjectCopy.realmSet$columnShortList(realmObjectSource.realmGet$columnShortList()); + realmObjectCopy.realmSet$columnByteList(realmObjectSource.realmGet$columnByteList()); + realmObjectCopy.realmSet$columnDoubleList(realmObjectSource.realmGet$columnDoubleList()); + realmObjectCopy.realmSet$columnFloatList(realmObjectSource.realmGet$columnFloatList()); + realmObjectCopy.realmSet$columnDateList(realmObjectSource.realmGet$columnDateList()); return realmObject; } @@ -828,6 +1349,126 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnStringListList = ((AllTypesRealmProxyInterface) object).realmGet$columnStringList(); + if (columnStringListList != null) { + OsList columnStringListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnStringListIndex); + for (java.lang.String columnStringListItem : columnStringListList) { + if (columnStringListItem == null) { + columnStringListOsList.addNull(); + } else { + columnStringListOsList.addString(columnStringListItem); + } + } + } + + RealmList columnBinaryListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); + if (columnBinaryListList != null) { + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); + for (byte[] columnBinaryListItem : columnBinaryListList) { + if (columnBinaryListItem == null) { + columnBinaryListOsList.addNull(); + } else { + columnBinaryListOsList.addBinary(columnBinaryListItem); + } + } + } + + RealmList columnBooleanListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); + if (columnBooleanListList != null) { + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); + for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { + if (columnBooleanListItem == null) { + columnBooleanListOsList.addNull(); + } else { + columnBooleanListOsList.addBoolean(columnBooleanListItem); + } + } + } + + RealmList columnLongListList = ((AllTypesRealmProxyInterface) object).realmGet$columnLongList(); + if (columnLongListList != null) { + OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); + for (java.lang.Long columnLongListItem : columnLongListList) { + if (columnLongListItem == null) { + columnLongListOsList.addNull(); + } else { + columnLongListOsList.addLong(columnLongListItem.longValue()); + } + } + } + + RealmList columnIntegerListList = ((AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); + if (columnIntegerListList != null) { + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); + for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { + if (columnIntegerListItem == null) { + columnIntegerListOsList.addNull(); + } else { + columnIntegerListOsList.addLong(columnIntegerListItem.longValue()); + } + } + } + + RealmList columnShortListList = ((AllTypesRealmProxyInterface) object).realmGet$columnShortList(); + if (columnShortListList != null) { + OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); + for (java.lang.Short columnShortListItem : columnShortListList) { + if (columnShortListItem == null) { + columnShortListOsList.addNull(); + } else { + columnShortListOsList.addLong(columnShortListItem.longValue()); + } + } + } + + RealmList columnByteListList = ((AllTypesRealmProxyInterface) object).realmGet$columnByteList(); + if (columnByteListList != null) { + OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); + for (java.lang.Byte columnByteListItem : columnByteListList) { + if (columnByteListItem == null) { + columnByteListOsList.addNull(); + } else { + columnByteListOsList.addLong(columnByteListItem.longValue()); + } + } + } + + RealmList columnDoubleListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); + if (columnDoubleListList != null) { + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); + for (java.lang.Double columnDoubleListItem : columnDoubleListList) { + if (columnDoubleListItem == null) { + columnDoubleListOsList.addNull(); + } else { + columnDoubleListOsList.addDouble(columnDoubleListItem.doubleValue()); + } + } + } + + RealmList columnFloatListList = ((AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); + if (columnFloatListList != null) { + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); + for (java.lang.Float columnFloatListItem : columnFloatListList) { + if (columnFloatListItem == null) { + columnFloatListOsList.addNull(); + } else { + columnFloatListOsList.addFloat(columnFloatListItem.floatValue()); + } + } + } + + RealmList columnDateListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDateList(); + if (columnDateListList != null) { + OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); + for (java.util.Date columnDateListItem : columnDateListList) { + if (columnDateListItem == null) { + columnDateListOsList.addNull(); + } else { + columnDateListOsList.addDate(columnDateListItem); + } + } + } return rowIndex; } @@ -896,6 +1537,126 @@ public static void insert(Realm realm, Iterator objects, M columnRealmListOsList.addRow(cacheItemIndexcolumnRealmList); } } + + RealmList columnStringListList = ((AllTypesRealmProxyInterface) object).realmGet$columnStringList(); + if (columnStringListList != null) { + OsList columnStringListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnStringListIndex); + for (java.lang.String columnStringListItem : columnStringListList) { + if (columnStringListItem == null) { + columnStringListOsList.addNull(); + } else { + columnStringListOsList.addString(columnStringListItem); + } + } + } + + RealmList columnBinaryListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); + if (columnBinaryListList != null) { + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); + for (byte[] columnBinaryListItem : columnBinaryListList) { + if (columnBinaryListItem == null) { + columnBinaryListOsList.addNull(); + } else { + columnBinaryListOsList.addBinary(columnBinaryListItem); + } + } + } + + RealmList columnBooleanListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); + if (columnBooleanListList != null) { + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); + for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { + if (columnBooleanListItem == null) { + columnBooleanListOsList.addNull(); + } else { + columnBooleanListOsList.addBoolean(columnBooleanListItem); + } + } + } + + RealmList columnLongListList = ((AllTypesRealmProxyInterface) object).realmGet$columnLongList(); + if (columnLongListList != null) { + OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); + for (java.lang.Long columnLongListItem : columnLongListList) { + if (columnLongListItem == null) { + columnLongListOsList.addNull(); + } else { + columnLongListOsList.addLong(columnLongListItem.longValue()); + } + } + } + + RealmList columnIntegerListList = ((AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); + if (columnIntegerListList != null) { + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); + for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { + if (columnIntegerListItem == null) { + columnIntegerListOsList.addNull(); + } else { + columnIntegerListOsList.addLong(columnIntegerListItem.longValue()); + } + } + } + + RealmList columnShortListList = ((AllTypesRealmProxyInterface) object).realmGet$columnShortList(); + if (columnShortListList != null) { + OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); + for (java.lang.Short columnShortListItem : columnShortListList) { + if (columnShortListItem == null) { + columnShortListOsList.addNull(); + } else { + columnShortListOsList.addLong(columnShortListItem.longValue()); + } + } + } + + RealmList columnByteListList = ((AllTypesRealmProxyInterface) object).realmGet$columnByteList(); + if (columnByteListList != null) { + OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); + for (java.lang.Byte columnByteListItem : columnByteListList) { + if (columnByteListItem == null) { + columnByteListOsList.addNull(); + } else { + columnByteListOsList.addLong(columnByteListItem.longValue()); + } + } + } + + RealmList columnDoubleListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); + if (columnDoubleListList != null) { + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); + for (java.lang.Double columnDoubleListItem : columnDoubleListList) { + if (columnDoubleListItem == null) { + columnDoubleListOsList.addNull(); + } else { + columnDoubleListOsList.addDouble(columnDoubleListItem.doubleValue()); + } + } + } + + RealmList columnFloatListList = ((AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); + if (columnFloatListList != null) { + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); + for (java.lang.Float columnFloatListItem : columnFloatListList) { + if (columnFloatListItem == null) { + columnFloatListOsList.addNull(); + } else { + columnFloatListOsList.addFloat(columnFloatListItem.floatValue()); + } + } + } + + RealmList columnDateListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDateList(); + if (columnDateListList != null) { + OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); + for (java.util.Date columnDateListItem : columnDateListList) { + if (columnDateListItem == null) { + columnDateListOsList.addNull(); + } else { + columnDateListOsList.addDate(columnDateListItem); + } + } + } } } @@ -965,6 +1726,146 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnStringListList = ((AllTypesRealmProxyInterface) object).realmGet$columnStringList(); + if (columnStringListList != null) { + for (java.lang.String columnStringListItem : columnStringListList) { + if (columnStringListItem == null) { + columnStringListOsList.addNull(); + } else { + columnStringListOsList.addString(columnStringListItem); + } + } + } + + + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); + columnBinaryListOsList.removeAll(); + RealmList columnBinaryListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); + if (columnBinaryListList != null) { + for (byte[] columnBinaryListItem : columnBinaryListList) { + if (columnBinaryListItem == null) { + columnBinaryListOsList.addNull(); + } else { + columnBinaryListOsList.addBinary(columnBinaryListItem); + } + } + } + + + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); + columnBooleanListOsList.removeAll(); + RealmList columnBooleanListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); + if (columnBooleanListList != null) { + for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { + if (columnBooleanListItem == null) { + columnBooleanListOsList.addNull(); + } else { + columnBooleanListOsList.addBoolean(columnBooleanListItem); + } + } + } + + + OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); + columnLongListOsList.removeAll(); + RealmList columnLongListList = ((AllTypesRealmProxyInterface) object).realmGet$columnLongList(); + if (columnLongListList != null) { + for (java.lang.Long columnLongListItem : columnLongListList) { + if (columnLongListItem == null) { + columnLongListOsList.addNull(); + } else { + columnLongListOsList.addLong(columnLongListItem.longValue()); + } + } + } + + + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); + columnIntegerListOsList.removeAll(); + RealmList columnIntegerListList = ((AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); + if (columnIntegerListList != null) { + for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { + if (columnIntegerListItem == null) { + columnIntegerListOsList.addNull(); + } else { + columnIntegerListOsList.addLong(columnIntegerListItem.longValue()); + } + } + } + + + OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); + columnShortListOsList.removeAll(); + RealmList columnShortListList = ((AllTypesRealmProxyInterface) object).realmGet$columnShortList(); + if (columnShortListList != null) { + for (java.lang.Short columnShortListItem : columnShortListList) { + if (columnShortListItem == null) { + columnShortListOsList.addNull(); + } else { + columnShortListOsList.addLong(columnShortListItem.longValue()); + } + } + } + + + OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); + columnByteListOsList.removeAll(); + RealmList columnByteListList = ((AllTypesRealmProxyInterface) object).realmGet$columnByteList(); + if (columnByteListList != null) { + for (java.lang.Byte columnByteListItem : columnByteListList) { + if (columnByteListItem == null) { + columnByteListOsList.addNull(); + } else { + columnByteListOsList.addLong(columnByteListItem.longValue()); + } + } + } + + + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); + columnDoubleListOsList.removeAll(); + RealmList columnDoubleListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); + if (columnDoubleListList != null) { + for (java.lang.Double columnDoubleListItem : columnDoubleListList) { + if (columnDoubleListItem == null) { + columnDoubleListOsList.addNull(); + } else { + columnDoubleListOsList.addDouble(columnDoubleListItem.doubleValue()); + } + } + } + + + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); + columnFloatListOsList.removeAll(); + RealmList columnFloatListList = ((AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); + if (columnFloatListList != null) { + for (java.lang.Float columnFloatListItem : columnFloatListList) { + if (columnFloatListItem == null) { + columnFloatListOsList.addNull(); + } else { + columnFloatListOsList.addFloat(columnFloatListItem.floatValue()); + } + } + } + + + OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); + columnDateListOsList.removeAll(); + RealmList columnDateListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDateList(); + if (columnDateListList != null) { + for (java.util.Date columnDateListItem : columnDateListList) { + if (columnDateListItem == null) { + columnDateListOsList.addNull(); + } else { + columnDateListOsList.addDate(columnDateListItem); + } + } + } + return rowIndex; } @@ -1041,6 +1942,146 @@ public static void insertOrUpdate(Realm realm, Iterator ob } } + + OsList columnStringListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnStringListIndex); + columnStringListOsList.removeAll(); + RealmList columnStringListList = ((AllTypesRealmProxyInterface) object).realmGet$columnStringList(); + if (columnStringListList != null) { + for (java.lang.String columnStringListItem : columnStringListList) { + if (columnStringListItem == null) { + columnStringListOsList.addNull(); + } else { + columnStringListOsList.addString(columnStringListItem); + } + } + } + + + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); + columnBinaryListOsList.removeAll(); + RealmList columnBinaryListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); + if (columnBinaryListList != null) { + for (byte[] columnBinaryListItem : columnBinaryListList) { + if (columnBinaryListItem == null) { + columnBinaryListOsList.addNull(); + } else { + columnBinaryListOsList.addBinary(columnBinaryListItem); + } + } + } + + + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); + columnBooleanListOsList.removeAll(); + RealmList columnBooleanListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); + if (columnBooleanListList != null) { + for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { + if (columnBooleanListItem == null) { + columnBooleanListOsList.addNull(); + } else { + columnBooleanListOsList.addBoolean(columnBooleanListItem); + } + } + } + + + OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); + columnLongListOsList.removeAll(); + RealmList columnLongListList = ((AllTypesRealmProxyInterface) object).realmGet$columnLongList(); + if (columnLongListList != null) { + for (java.lang.Long columnLongListItem : columnLongListList) { + if (columnLongListItem == null) { + columnLongListOsList.addNull(); + } else { + columnLongListOsList.addLong(columnLongListItem.longValue()); + } + } + } + + + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); + columnIntegerListOsList.removeAll(); + RealmList columnIntegerListList = ((AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); + if (columnIntegerListList != null) { + for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { + if (columnIntegerListItem == null) { + columnIntegerListOsList.addNull(); + } else { + columnIntegerListOsList.addLong(columnIntegerListItem.longValue()); + } + } + } + + + OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); + columnShortListOsList.removeAll(); + RealmList columnShortListList = ((AllTypesRealmProxyInterface) object).realmGet$columnShortList(); + if (columnShortListList != null) { + for (java.lang.Short columnShortListItem : columnShortListList) { + if (columnShortListItem == null) { + columnShortListOsList.addNull(); + } else { + columnShortListOsList.addLong(columnShortListItem.longValue()); + } + } + } + + + OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); + columnByteListOsList.removeAll(); + RealmList columnByteListList = ((AllTypesRealmProxyInterface) object).realmGet$columnByteList(); + if (columnByteListList != null) { + for (java.lang.Byte columnByteListItem : columnByteListList) { + if (columnByteListItem == null) { + columnByteListOsList.addNull(); + } else { + columnByteListOsList.addLong(columnByteListItem.longValue()); + } + } + } + + + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); + columnDoubleListOsList.removeAll(); + RealmList columnDoubleListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); + if (columnDoubleListList != null) { + for (java.lang.Double columnDoubleListItem : columnDoubleListList) { + if (columnDoubleListItem == null) { + columnDoubleListOsList.addNull(); + } else { + columnDoubleListOsList.addDouble(columnDoubleListItem.doubleValue()); + } + } + } + + + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); + columnFloatListOsList.removeAll(); + RealmList columnFloatListList = ((AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); + if (columnFloatListList != null) { + for (java.lang.Float columnFloatListItem : columnFloatListList) { + if (columnFloatListItem == null) { + columnFloatListOsList.addNull(); + } else { + columnFloatListOsList.addFloat(columnFloatListItem.floatValue()); + } + } + } + + + OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); + columnDateListOsList.removeAll(); + RealmList columnDateListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDateList(); + if (columnDateListList != null) { + for (java.util.Date columnDateListItem : columnDateListList) { + if (columnDateListItem == null) { + columnDateListOsList.addNull(); + } else { + columnDateListOsList.addDate(columnDateListItem); + } + } + } + } } @@ -1089,6 +2130,37 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje unmanagedcolumnRealmListList.add(item); } } + + unmanagedCopy.realmSet$columnStringList(new RealmList()); + unmanagedCopy.realmGet$columnStringList().addAll(realmSource.realmGet$columnStringList()); + + unmanagedCopy.realmSet$columnBinaryList(new RealmList()); + unmanagedCopy.realmGet$columnBinaryList().addAll(realmSource.realmGet$columnBinaryList()); + + unmanagedCopy.realmSet$columnBooleanList(new RealmList()); + unmanagedCopy.realmGet$columnBooleanList().addAll(realmSource.realmGet$columnBooleanList()); + + unmanagedCopy.realmSet$columnLongList(new RealmList()); + unmanagedCopy.realmGet$columnLongList().addAll(realmSource.realmGet$columnLongList()); + + unmanagedCopy.realmSet$columnIntegerList(new RealmList()); + unmanagedCopy.realmGet$columnIntegerList().addAll(realmSource.realmGet$columnIntegerList()); + + unmanagedCopy.realmSet$columnShortList(new RealmList()); + unmanagedCopy.realmGet$columnShortList().addAll(realmSource.realmGet$columnShortList()); + + unmanagedCopy.realmSet$columnByteList(new RealmList()); + unmanagedCopy.realmGet$columnByteList().addAll(realmSource.realmGet$columnByteList()); + + unmanagedCopy.realmSet$columnDoubleList(new RealmList()); + unmanagedCopy.realmGet$columnDoubleList().addAll(realmSource.realmGet$columnDoubleList()); + + unmanagedCopy.realmSet$columnFloatList(new RealmList()); + unmanagedCopy.realmGet$columnFloatList().addAll(realmSource.realmGet$columnFloatList()); + + unmanagedCopy.realmSet$columnDateList(new RealmList()); + unmanagedCopy.realmGet$columnDateList().addAll(realmSource.realmGet$columnDateList()); + return unmanagedObject; } @@ -1127,6 +2199,16 @@ static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, so } } } + realmObjectTarget.realmSet$columnStringList(realmObjectSource.realmGet$columnStringList()); + realmObjectTarget.realmSet$columnBinaryList(realmObjectSource.realmGet$columnBinaryList()); + realmObjectTarget.realmSet$columnBooleanList(realmObjectSource.realmGet$columnBooleanList()); + realmObjectTarget.realmSet$columnLongList(realmObjectSource.realmGet$columnLongList()); + realmObjectTarget.realmSet$columnIntegerList(realmObjectSource.realmGet$columnIntegerList()); + realmObjectTarget.realmSet$columnShortList(realmObjectSource.realmGet$columnShortList()); + realmObjectTarget.realmSet$columnByteList(realmObjectSource.realmGet$columnByteList()); + realmObjectTarget.realmSet$columnDoubleList(realmObjectSource.realmGet$columnDoubleList()); + realmObjectTarget.realmSet$columnFloatList(realmObjectSource.realmGet$columnFloatList()); + realmObjectTarget.realmSet$columnDateList(realmObjectSource.realmGet$columnDateList()); return realmObject; } @@ -1176,6 +2258,46 @@ public String toString() { stringBuilder.append("{columnRealmList:"); stringBuilder.append("RealmList[").append(realmGet$columnRealmList().size()).append("]"); stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnStringList:"); + stringBuilder.append("RealmList[").append(realmGet$columnStringList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnBinaryList:"); + stringBuilder.append("RealmList[").append(realmGet$columnBinaryList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnBooleanList:"); + stringBuilder.append("RealmList[").append(realmGet$columnBooleanList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnLongList:"); + stringBuilder.append("RealmList[").append(realmGet$columnLongList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnIntegerList:"); + stringBuilder.append("RealmList[").append(realmGet$columnIntegerList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnShortList:"); + stringBuilder.append("RealmList[").append(realmGet$columnShortList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnByteList:"); + stringBuilder.append("RealmList[").append(realmGet$columnByteList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnDoubleList:"); + stringBuilder.append("RealmList[").append(realmGet$columnDoubleList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnFloatList:"); + stringBuilder.append("RealmList[").append(realmGet$columnFloatList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnDateList:"); + stringBuilder.append("RealmList[").append(realmGet$columnDateList().size()).append("]"); + stringBuilder.append("}"); stringBuilder.append("]"); return stringBuilder.toString(); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index ccacda2a95..43544b3e1e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -57,9 +57,29 @@ static final class NullTypesColumnInfo extends ColumnInfo { long fieldDateNotNullIndex; long fieldDateNullIndex; long fieldObjectNullIndex; + long fieldStringListNotNullIndex; + long fieldStringListNullIndex; + long fieldBinaryListNotNullIndex; + long fieldBinaryListNullIndex; + long fieldBooleanListNotNullIndex; + long fieldBooleanListNullIndex; + long fieldLongListNotNullIndex; + long fieldLongListNullIndex; + long fieldIntegerListNotNullIndex; + long fieldIntegerListNullIndex; + long fieldShortListNotNullIndex; + long fieldShortListNullIndex; + long fieldByteListNotNullIndex; + long fieldByteListNullIndex; + long fieldDoubleListNotNullIndex; + long fieldDoubleListNullIndex; + long fieldFloatListNotNullIndex; + long fieldFloatListNullIndex; + long fieldDateListNotNullIndex; + long fieldDateListNullIndex; NullTypesColumnInfo(OsSchemaInfo schemaInfo) { - super(21); + super(41); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("NullTypes"); this.fieldStringNotNullIndex = addColumnDetails("fieldStringNotNull", objectSchemaInfo); this.fieldStringNullIndex = addColumnDetails("fieldStringNull", objectSchemaInfo); @@ -82,6 +102,26 @@ static final class NullTypesColumnInfo extends ColumnInfo { this.fieldDateNotNullIndex = addColumnDetails("fieldDateNotNull", objectSchemaInfo); this.fieldDateNullIndex = addColumnDetails("fieldDateNull", objectSchemaInfo); this.fieldObjectNullIndex = addColumnDetails("fieldObjectNull", objectSchemaInfo); + this.fieldStringListNotNullIndex = addColumnDetails("fieldStringListNotNull", objectSchemaInfo); + this.fieldStringListNullIndex = addColumnDetails("fieldStringListNull", objectSchemaInfo); + this.fieldBinaryListNotNullIndex = addColumnDetails("fieldBinaryListNotNull", objectSchemaInfo); + this.fieldBinaryListNullIndex = addColumnDetails("fieldBinaryListNull", objectSchemaInfo); + this.fieldBooleanListNotNullIndex = addColumnDetails("fieldBooleanListNotNull", objectSchemaInfo); + this.fieldBooleanListNullIndex = addColumnDetails("fieldBooleanListNull", objectSchemaInfo); + this.fieldLongListNotNullIndex = addColumnDetails("fieldLongListNotNull", objectSchemaInfo); + this.fieldLongListNullIndex = addColumnDetails("fieldLongListNull", objectSchemaInfo); + this.fieldIntegerListNotNullIndex = addColumnDetails("fieldIntegerListNotNull", objectSchemaInfo); + this.fieldIntegerListNullIndex = addColumnDetails("fieldIntegerListNull", objectSchemaInfo); + this.fieldShortListNotNullIndex = addColumnDetails("fieldShortListNotNull", objectSchemaInfo); + this.fieldShortListNullIndex = addColumnDetails("fieldShortListNull", objectSchemaInfo); + this.fieldByteListNotNullIndex = addColumnDetails("fieldByteListNotNull", objectSchemaInfo); + this.fieldByteListNullIndex = addColumnDetails("fieldByteListNull", objectSchemaInfo); + this.fieldDoubleListNotNullIndex = addColumnDetails("fieldDoubleListNotNull", objectSchemaInfo); + this.fieldDoubleListNullIndex = addColumnDetails("fieldDoubleListNull", objectSchemaInfo); + this.fieldFloatListNotNullIndex = addColumnDetails("fieldFloatListNotNull", objectSchemaInfo); + this.fieldFloatListNullIndex = addColumnDetails("fieldFloatListNull", objectSchemaInfo); + this.fieldDateListNotNullIndex = addColumnDetails("fieldDateListNotNull", objectSchemaInfo); + this.fieldDateListNullIndex = addColumnDetails("fieldDateListNull", objectSchemaInfo); } NullTypesColumnInfo(ColumnInfo src, boolean mutable) { @@ -119,6 +159,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.fieldDateNotNullIndex = src.fieldDateNotNullIndex; dst.fieldDateNullIndex = src.fieldDateNullIndex; dst.fieldObjectNullIndex = src.fieldObjectNullIndex; + dst.fieldStringListNotNullIndex = src.fieldStringListNotNullIndex; + dst.fieldStringListNullIndex = src.fieldStringListNullIndex; + dst.fieldBinaryListNotNullIndex = src.fieldBinaryListNotNullIndex; + dst.fieldBinaryListNullIndex = src.fieldBinaryListNullIndex; + dst.fieldBooleanListNotNullIndex = src.fieldBooleanListNotNullIndex; + dst.fieldBooleanListNullIndex = src.fieldBooleanListNullIndex; + dst.fieldLongListNotNullIndex = src.fieldLongListNotNullIndex; + dst.fieldLongListNullIndex = src.fieldLongListNullIndex; + dst.fieldIntegerListNotNullIndex = src.fieldIntegerListNotNullIndex; + dst.fieldIntegerListNullIndex = src.fieldIntegerListNullIndex; + dst.fieldShortListNotNullIndex = src.fieldShortListNotNullIndex; + dst.fieldShortListNullIndex = src.fieldShortListNullIndex; + dst.fieldByteListNotNullIndex = src.fieldByteListNotNullIndex; + dst.fieldByteListNullIndex = src.fieldByteListNullIndex; + dst.fieldDoubleListNotNullIndex = src.fieldDoubleListNotNullIndex; + dst.fieldDoubleListNullIndex = src.fieldDoubleListNullIndex; + dst.fieldFloatListNotNullIndex = src.fieldFloatListNotNullIndex; + dst.fieldFloatListNullIndex = src.fieldFloatListNullIndex; + dst.fieldDateListNotNullIndex = src.fieldDateListNotNullIndex; + dst.fieldDateListNullIndex = src.fieldDateListNullIndex; } } @@ -147,11 +207,51 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { fieldNames.add("fieldDateNotNull"); fieldNames.add("fieldDateNull"); fieldNames.add("fieldObjectNull"); + fieldNames.add("fieldStringListNotNull"); + fieldNames.add("fieldStringListNull"); + fieldNames.add("fieldBinaryListNotNull"); + fieldNames.add("fieldBinaryListNull"); + fieldNames.add("fieldBooleanListNotNull"); + fieldNames.add("fieldBooleanListNull"); + fieldNames.add("fieldLongListNotNull"); + fieldNames.add("fieldLongListNull"); + fieldNames.add("fieldIntegerListNotNull"); + fieldNames.add("fieldIntegerListNull"); + fieldNames.add("fieldShortListNotNull"); + fieldNames.add("fieldShortListNull"); + fieldNames.add("fieldByteListNotNull"); + fieldNames.add("fieldByteListNull"); + fieldNames.add("fieldDoubleListNotNull"); + fieldNames.add("fieldDoubleListNull"); + fieldNames.add("fieldFloatListNotNull"); + fieldNames.add("fieldFloatListNull"); + fieldNames.add("fieldDateListNotNull"); + fieldNames.add("fieldDateListNull"); FIELD_NAMES = Collections.unmodifiableList(fieldNames); } private NullTypesColumnInfo columnInfo; private ProxyState proxyState; + private RealmList fieldStringListNotNullRealmList; + private RealmList fieldStringListNullRealmList; + private RealmList fieldBinaryListNotNullRealmList; + private RealmList fieldBinaryListNullRealmList; + private RealmList fieldBooleanListNotNullRealmList; + private RealmList fieldBooleanListNullRealmList; + private RealmList fieldLongListNotNullRealmList; + private RealmList fieldLongListNullRealmList; + private RealmList fieldIntegerListNotNullRealmList; + private RealmList fieldIntegerListNullRealmList; + private RealmList fieldShortListNotNullRealmList; + private RealmList fieldShortListNullRealmList; + private RealmList fieldByteListNotNullRealmList; + private RealmList fieldByteListNullRealmList; + private RealmList fieldDoubleListNotNullRealmList; + private RealmList fieldDoubleListNullRealmList; + private RealmList fieldFloatListNotNullRealmList; + private RealmList fieldFloatListNullRealmList; + private RealmList fieldDateListNotNullRealmList; + private RealmList fieldDateListNullRealmList; NullTypesRealmProxy() { proxyState.setConstructionFinished(); @@ -823,7 +923,787 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { throw new IllegalArgumentException("'value' belongs to a different Realm."); } - proxyState.getRow$realm().setLink(columnInfo.fieldObjectNullIndex, ((RealmObjectProxy)value).realmGet$proxyState().getRow$realm().getIndex()); + proxyState.getRow$realm().setLink(columnInfo.fieldObjectNullIndex, ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex()); + } + + @Override + public RealmList realmGet$fieldStringListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldStringListNotNullRealmList != null) { + return fieldStringListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNotNullIndex, RealmFieldType.STRING_LIST); + fieldStringListNotNullRealmList = new RealmList(java.lang.String.class, osList, proxyState.getRealm$realm()); + return fieldStringListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldStringListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldStringListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNotNullIndex, RealmFieldType.STRING_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.String item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldStringListNotNull' is not allowed by the schema."); + } else { + osList.addString(item); + } + } + } + + @Override + public RealmList realmGet$fieldStringListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldStringListNullRealmList != null) { + return fieldStringListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNullIndex, RealmFieldType.STRING_LIST); + fieldStringListNullRealmList = new RealmList(java.lang.String.class, osList, proxyState.getRealm$realm()); + return fieldStringListNullRealmList; + } + } + + @Override + public void realmSet$fieldStringListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldStringListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNullIndex, RealmFieldType.STRING_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.String item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addString(item); + } + } + } + + @Override + public RealmList realmGet$fieldBinaryListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldBinaryListNotNullRealmList != null) { + return fieldBinaryListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNotNullIndex, RealmFieldType.BINARY_LIST); + fieldBinaryListNotNullRealmList = new RealmList(byte[].class, osList, proxyState.getRealm$realm()); + return fieldBinaryListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldBinaryListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldBinaryListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNotNullIndex, RealmFieldType.BINARY_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (byte[] item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldBinaryListNotNull' is not allowed by the schema."); + } else { + osList.addBinary(item); + } + } + } + + @Override + public RealmList realmGet$fieldBinaryListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldBinaryListNullRealmList != null) { + return fieldBinaryListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNullIndex, RealmFieldType.BINARY_LIST); + fieldBinaryListNullRealmList = new RealmList(byte[].class, osList, proxyState.getRealm$realm()); + return fieldBinaryListNullRealmList; + } + } + + @Override + public void realmSet$fieldBinaryListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldBinaryListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNullIndex, RealmFieldType.BINARY_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (byte[] item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addBinary(item); + } + } + } + + @Override + public RealmList realmGet$fieldBooleanListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldBooleanListNotNullRealmList != null) { + return fieldBooleanListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNotNullIndex, RealmFieldType.BOOLEAN_LIST); + fieldBooleanListNotNullRealmList = new RealmList(java.lang.Boolean.class, osList, proxyState.getRealm$realm()); + return fieldBooleanListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldBooleanListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldBooleanListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNotNullIndex, RealmFieldType.BOOLEAN_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Boolean item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldBooleanListNotNull' is not allowed by the schema."); + } else { + osList.addBoolean(item); + } + } + } + + @Override + public RealmList realmGet$fieldBooleanListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldBooleanListNullRealmList != null) { + return fieldBooleanListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNullIndex, RealmFieldType.BOOLEAN_LIST); + fieldBooleanListNullRealmList = new RealmList(java.lang.Boolean.class, osList, proxyState.getRealm$realm()); + return fieldBooleanListNullRealmList; + } + } + + @Override + public void realmSet$fieldBooleanListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldBooleanListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNullIndex, RealmFieldType.BOOLEAN_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Boolean item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addBoolean(item); + } + } + } + + @Override + public RealmList realmGet$fieldLongListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldLongListNotNullRealmList != null) { + return fieldLongListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNotNullIndex, RealmFieldType.INTEGER_LIST); + fieldLongListNotNullRealmList = new RealmList(java.lang.Long.class, osList, proxyState.getRealm$realm()); + return fieldLongListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldLongListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldLongListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNotNullIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Long item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldLongListNotNull' is not allowed by the schema."); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$fieldLongListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldLongListNullRealmList != null) { + return fieldLongListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNullIndex, RealmFieldType.INTEGER_LIST); + fieldLongListNullRealmList = new RealmList(java.lang.Long.class, osList, proxyState.getRealm$realm()); + return fieldLongListNullRealmList; + } + } + + @Override + public void realmSet$fieldLongListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldLongListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNullIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Long item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$fieldIntegerListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldIntegerListNotNullRealmList != null) { + return fieldIntegerListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNotNullIndex, RealmFieldType.INTEGER_LIST); + fieldIntegerListNotNullRealmList = new RealmList(java.lang.Integer.class, osList, proxyState.getRealm$realm()); + return fieldIntegerListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldIntegerListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldIntegerListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNotNullIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Integer item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldIntegerListNotNull' is not allowed by the schema."); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$fieldIntegerListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldIntegerListNullRealmList != null) { + return fieldIntegerListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNullIndex, RealmFieldType.INTEGER_LIST); + fieldIntegerListNullRealmList = new RealmList(java.lang.Integer.class, osList, proxyState.getRealm$realm()); + return fieldIntegerListNullRealmList; + } + } + + @Override + public void realmSet$fieldIntegerListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldIntegerListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNullIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Integer item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$fieldShortListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldShortListNotNullRealmList != null) { + return fieldShortListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNotNullIndex, RealmFieldType.INTEGER_LIST); + fieldShortListNotNullRealmList = new RealmList(java.lang.Short.class, osList, proxyState.getRealm$realm()); + return fieldShortListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldShortListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldShortListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNotNullIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Short item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldShortListNotNull' is not allowed by the schema."); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$fieldShortListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldShortListNullRealmList != null) { + return fieldShortListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNullIndex, RealmFieldType.INTEGER_LIST); + fieldShortListNullRealmList = new RealmList(java.lang.Short.class, osList, proxyState.getRealm$realm()); + return fieldShortListNullRealmList; + } + } + + @Override + public void realmSet$fieldShortListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldShortListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNullIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Short item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$fieldByteListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldByteListNotNullRealmList != null) { + return fieldByteListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNotNullIndex, RealmFieldType.INTEGER_LIST); + fieldByteListNotNullRealmList = new RealmList(java.lang.Byte.class, osList, proxyState.getRealm$realm()); + return fieldByteListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldByteListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldByteListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNotNullIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Byte item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldByteListNotNull' is not allowed by the schema."); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$fieldByteListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldByteListNullRealmList != null) { + return fieldByteListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNullIndex, RealmFieldType.INTEGER_LIST); + fieldByteListNullRealmList = new RealmList(java.lang.Byte.class, osList, proxyState.getRealm$realm()); + return fieldByteListNullRealmList; + } + } + + @Override + public void realmSet$fieldByteListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldByteListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNullIndex, RealmFieldType.INTEGER_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Byte item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addLong(item.longValue()); + } + } + } + + @Override + public RealmList realmGet$fieldDoubleListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldDoubleListNotNullRealmList != null) { + return fieldDoubleListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNotNullIndex, RealmFieldType.DOUBLE_LIST); + fieldDoubleListNotNullRealmList = new RealmList(java.lang.Double.class, osList, proxyState.getRealm$realm()); + return fieldDoubleListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldDoubleListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldDoubleListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNotNullIndex, RealmFieldType.DOUBLE_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Double item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldDoubleListNotNull' is not allowed by the schema."); + } else { + osList.addDouble(item.doubleValue()); + } + } + } + + @Override + public RealmList realmGet$fieldDoubleListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldDoubleListNullRealmList != null) { + return fieldDoubleListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNullIndex, RealmFieldType.DOUBLE_LIST); + fieldDoubleListNullRealmList = new RealmList(java.lang.Double.class, osList, proxyState.getRealm$realm()); + return fieldDoubleListNullRealmList; + } + } + + @Override + public void realmSet$fieldDoubleListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldDoubleListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNullIndex, RealmFieldType.DOUBLE_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Double item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addDouble(item.doubleValue()); + } + } + } + + @Override + public RealmList realmGet$fieldFloatListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldFloatListNotNullRealmList != null) { + return fieldFloatListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNotNullIndex, RealmFieldType.FLOAT_LIST); + fieldFloatListNotNullRealmList = new RealmList(java.lang.Float.class, osList, proxyState.getRealm$realm()); + return fieldFloatListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldFloatListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldFloatListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNotNullIndex, RealmFieldType.FLOAT_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Float item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldFloatListNotNull' is not allowed by the schema."); + } else { + osList.addFloat(item.floatValue()); + } + } + } + + @Override + public RealmList realmGet$fieldFloatListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldFloatListNullRealmList != null) { + return fieldFloatListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNullIndex, RealmFieldType.FLOAT_LIST); + fieldFloatListNullRealmList = new RealmList(java.lang.Float.class, osList, proxyState.getRealm$realm()); + return fieldFloatListNullRealmList; + } + } + + @Override + public void realmSet$fieldFloatListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldFloatListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNullIndex, RealmFieldType.FLOAT_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.lang.Float item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addFloat(item.floatValue()); + } + } + } + + @Override + public RealmList realmGet$fieldDateListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldDateListNotNullRealmList != null) { + return fieldDateListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNotNullIndex, RealmFieldType.DATE_LIST); + fieldDateListNotNullRealmList = new RealmList(java.util.Date.class, osList, proxyState.getRealm$realm()); + return fieldDateListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldDateListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldDateListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNotNullIndex, RealmFieldType.DATE_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.util.Date item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldDateListNotNull' is not allowed by the schema."); + } else { + osList.addDate(item); + } + } + } + + @Override + public RealmList realmGet$fieldDateListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldDateListNullRealmList != null) { + return fieldDateListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNullIndex, RealmFieldType.DATE_LIST); + fieldDateListNullRealmList = new RealmList(java.util.Date.class, osList, proxyState.getRealm$realm()); + return fieldDateListNullRealmList; + } + } + + @Override + public void realmSet$fieldDateListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldDateListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNullIndex, RealmFieldType.DATE_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.util.Date item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addDate(item); + } + } } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { @@ -849,6 +1729,26 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { builder.addPersistedProperty("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedLinkProperty("fieldObjectNull", RealmFieldType.OBJECT, "NullTypes"); + builder.addPersistedValueListProperty("fieldStringListNotNull", RealmFieldType.STRING_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldStringListNull", RealmFieldType.STRING_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldBinaryListNotNull", RealmFieldType.BINARY_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldBinaryListNull", RealmFieldType.BINARY_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldBooleanListNotNull", RealmFieldType.BOOLEAN_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldBooleanListNull", RealmFieldType.BOOLEAN_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldLongListNotNull", RealmFieldType.INTEGER_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldLongListNull", RealmFieldType.INTEGER_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldIntegerListNotNull", RealmFieldType.INTEGER_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldIntegerListNull", RealmFieldType.INTEGER_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldShortListNotNull", RealmFieldType.INTEGER_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldShortListNull", RealmFieldType.INTEGER_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldByteListNotNull", RealmFieldType.INTEGER_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldByteListNull", RealmFieldType.INTEGER_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldDoubleListNotNull", RealmFieldType.DOUBLE_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldDoubleListNull", RealmFieldType.DOUBLE_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldFloatListNotNull", RealmFieldType.FLOAT_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldFloatListNull", RealmFieldType.FLOAT_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldDateListNotNull", RealmFieldType.DATE_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldDateListNull", RealmFieldType.DATE_LIST, !Property.REQUIRED); return builder.build(); } @@ -871,11 +1771,72 @@ public static List getFieldNames() { @SuppressWarnings("cast") public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { - final List excludeFields = new ArrayList(1); + final List excludeFields = new ArrayList(21); if (json.has("fieldObjectNull")) { excludeFields.add("fieldObjectNull"); } + if (json.has("fieldStringListNotNull")) { + excludeFields.add("fieldStringListNotNull"); + } + if (json.has("fieldStringListNull")) { + excludeFields.add("fieldStringListNull"); + } + if (json.has("fieldBinaryListNotNull")) { + excludeFields.add("fieldBinaryListNotNull"); + } + if (json.has("fieldBinaryListNull")) { + excludeFields.add("fieldBinaryListNull"); + } + if (json.has("fieldBooleanListNotNull")) { + excludeFields.add("fieldBooleanListNotNull"); + } + if (json.has("fieldBooleanListNull")) { + excludeFields.add("fieldBooleanListNull"); + } + if (json.has("fieldLongListNotNull")) { + excludeFields.add("fieldLongListNotNull"); + } + if (json.has("fieldLongListNull")) { + excludeFields.add("fieldLongListNull"); + } + if (json.has("fieldIntegerListNotNull")) { + excludeFields.add("fieldIntegerListNotNull"); + } + if (json.has("fieldIntegerListNull")) { + excludeFields.add("fieldIntegerListNull"); + } + if (json.has("fieldShortListNotNull")) { + excludeFields.add("fieldShortListNotNull"); + } + if (json.has("fieldShortListNull")) { + excludeFields.add("fieldShortListNull"); + } + if (json.has("fieldByteListNotNull")) { + excludeFields.add("fieldByteListNotNull"); + } + if (json.has("fieldByteListNull")) { + excludeFields.add("fieldByteListNull"); + } + if (json.has("fieldDoubleListNotNull")) { + excludeFields.add("fieldDoubleListNotNull"); + } + if (json.has("fieldDoubleListNull")) { + excludeFields.add("fieldDoubleListNull"); + } + if (json.has("fieldFloatListNotNull")) { + excludeFields.add("fieldFloatListNotNull"); + } + if (json.has("fieldFloatListNull")) { + excludeFields.add("fieldFloatListNull"); + } + if (json.has("fieldDateListNotNull")) { + excludeFields.add("fieldDateListNotNull"); + } + if (json.has("fieldDateListNull")) { + excludeFields.add("fieldDateListNull"); + } some.test.NullTypes obj = realm.createObjectInternal(some.test.NullTypes.class, true, excludeFields); + final NullTypesRealmProxyInterface objProxy = (NullTypesRealmProxyInterface) obj; if (json.has("fieldStringNotNull")) { if (json.isNull("fieldStringNotNull")) { @@ -1035,6 +1996,26 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO objProxy.realmSet$fieldObjectNull(fieldObjectNullObj); } } + // TODO implement logic for value listfieldStringListNotNull. + // TODO implement logic for value listfieldStringListNull. + // TODO implement logic for value listfieldBinaryListNotNull. + // TODO implement logic for value listfieldBinaryListNull. + // TODO implement logic for value listfieldBooleanListNotNull. + // TODO implement logic for value listfieldBooleanListNull. + // TODO implement logic for value listfieldLongListNotNull. + // TODO implement logic for value listfieldLongListNull. + // TODO implement logic for value listfieldIntegerListNotNull. + // TODO implement logic for value listfieldIntegerListNull. + // TODO implement logic for value listfieldShortListNotNull. + // TODO implement logic for value listfieldShortListNull. + // TODO implement logic for value listfieldByteListNotNull. + // TODO implement logic for value listfieldByteListNull. + // TODO implement logic for value listfieldDoubleListNotNull. + // TODO implement logic for value listfieldDoubleListNull. + // TODO implement logic for value listfieldFloatListNotNull. + // TODO implement logic for value listfieldFloatListNull. + // TODO implement logic for value listfieldDateListNotNull. + // TODO implement logic for value listfieldDateListNull. return obj; } @@ -1206,6 +2187,46 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader some.test.NullTypes fieldObjectNullObj = NullTypesRealmProxy.createUsingJsonStream(realm, reader); objProxy.realmSet$fieldObjectNull(fieldObjectNullObj); } + } else if (name.equals("fieldStringListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldStringListNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldBinaryListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldBinaryListNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldBooleanListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldBooleanListNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldLongListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldLongListNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldIntegerListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldIntegerListNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldShortListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldShortListNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldByteListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldByteListNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldDoubleListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldDoubleListNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldFloatListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldFloatListNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldDateListNotNull")) { + // TODO implement logic for value list. + } else if (name.equals("fieldDateListNull")) { + // TODO implement logic for value list. } else { reader.skipValue(); } @@ -1278,6 +2299,26 @@ public static some.test.NullTypes copy(Realm realm, some.test.NullTypes newObjec realmObjectCopy.realmSet$fieldObjectNull(NullTypesRealmProxy.copyOrUpdate(realm, fieldObjectNullObj, update, cache)); } } + realmObjectCopy.realmSet$fieldStringListNotNull(realmObjectSource.realmGet$fieldStringListNotNull()); + realmObjectCopy.realmSet$fieldStringListNull(realmObjectSource.realmGet$fieldStringListNull()); + realmObjectCopy.realmSet$fieldBinaryListNotNull(realmObjectSource.realmGet$fieldBinaryListNotNull()); + realmObjectCopy.realmSet$fieldBinaryListNull(realmObjectSource.realmGet$fieldBinaryListNull()); + realmObjectCopy.realmSet$fieldBooleanListNotNull(realmObjectSource.realmGet$fieldBooleanListNotNull()); + realmObjectCopy.realmSet$fieldBooleanListNull(realmObjectSource.realmGet$fieldBooleanListNull()); + realmObjectCopy.realmSet$fieldLongListNotNull(realmObjectSource.realmGet$fieldLongListNotNull()); + realmObjectCopy.realmSet$fieldLongListNull(realmObjectSource.realmGet$fieldLongListNull()); + realmObjectCopy.realmSet$fieldIntegerListNotNull(realmObjectSource.realmGet$fieldIntegerListNotNull()); + realmObjectCopy.realmSet$fieldIntegerListNull(realmObjectSource.realmGet$fieldIntegerListNull()); + realmObjectCopy.realmSet$fieldShortListNotNull(realmObjectSource.realmGet$fieldShortListNotNull()); + realmObjectCopy.realmSet$fieldShortListNull(realmObjectSource.realmGet$fieldShortListNull()); + realmObjectCopy.realmSet$fieldByteListNotNull(realmObjectSource.realmGet$fieldByteListNotNull()); + realmObjectCopy.realmSet$fieldByteListNull(realmObjectSource.realmGet$fieldByteListNull()); + realmObjectCopy.realmSet$fieldDoubleListNotNull(realmObjectSource.realmGet$fieldDoubleListNotNull()); + realmObjectCopy.realmSet$fieldDoubleListNull(realmObjectSource.realmGet$fieldDoubleListNull()); + realmObjectCopy.realmSet$fieldFloatListNotNull(realmObjectSource.realmGet$fieldFloatListNotNull()); + realmObjectCopy.realmSet$fieldFloatListNull(realmObjectSource.realmGet$fieldFloatListNull()); + realmObjectCopy.realmSet$fieldDateListNotNull(realmObjectSource.realmGet$fieldDateListNotNull()); + realmObjectCopy.realmSet$fieldDateListNull(realmObjectSource.realmGet$fieldDateListNull()); return realmObject; } @@ -1346,38 +2387,278 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldStringListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); + if (fieldStringListNotNullList != null) { + OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNotNullIndex); + for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { + if (fieldStringListNotNullItem == null) { + fieldStringListNotNullOsList.addNull(); + } else { + fieldStringListNotNullOsList.addString(fieldStringListNotNullItem); + } + } + } + + RealmList fieldStringListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); + if (fieldStringListNullList != null) { + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); + for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { + if (fieldStringListNullItem == null) { + fieldStringListNullOsList.addNull(); + } else { + fieldStringListNullOsList.addString(fieldStringListNullItem); + } + } + } + + RealmList fieldBinaryListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); + if (fieldBinaryListNotNullList != null) { + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); + for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { + if (fieldBinaryListNotNullItem == null) { + fieldBinaryListNotNullOsList.addNull(); + } else { + fieldBinaryListNotNullOsList.addBinary(fieldBinaryListNotNullItem); + } + } + } + + RealmList fieldBinaryListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); + if (fieldBinaryListNullList != null) { + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); + for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { + if (fieldBinaryListNullItem == null) { + fieldBinaryListNullOsList.addNull(); + } else { + fieldBinaryListNullOsList.addBinary(fieldBinaryListNullItem); + } + } + } + + RealmList fieldBooleanListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); + if (fieldBooleanListNotNullList != null) { + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); + for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { + if (fieldBooleanListNotNullItem == null) { + fieldBooleanListNotNullOsList.addNull(); + } else { + fieldBooleanListNotNullOsList.addBoolean(fieldBooleanListNotNullItem); + } + } + } + + RealmList fieldBooleanListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); + if (fieldBooleanListNullList != null) { + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); + for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { + if (fieldBooleanListNullItem == null) { + fieldBooleanListNullOsList.addNull(); + } else { + fieldBooleanListNullOsList.addBoolean(fieldBooleanListNullItem); + } + } + } + + RealmList fieldLongListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); + if (fieldLongListNotNullList != null) { + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); + for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { + if (fieldLongListNotNullItem == null) { + fieldLongListNotNullOsList.addNull(); + } else { + fieldLongListNotNullOsList.addLong(fieldLongListNotNullItem.longValue()); + } + } + } + + RealmList fieldLongListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); + if (fieldLongListNullList != null) { + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); + for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { + if (fieldLongListNullItem == null) { + fieldLongListNullOsList.addNull(); + } else { + fieldLongListNullOsList.addLong(fieldLongListNullItem.longValue()); + } + } + } + + RealmList fieldIntegerListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); + if (fieldIntegerListNotNullList != null) { + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); + for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { + if (fieldIntegerListNotNullItem == null) { + fieldIntegerListNotNullOsList.addNull(); + } else { + fieldIntegerListNotNullOsList.addLong(fieldIntegerListNotNullItem.longValue()); + } + } + } + + RealmList fieldIntegerListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); + if (fieldIntegerListNullList != null) { + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); + for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { + if (fieldIntegerListNullItem == null) { + fieldIntegerListNullOsList.addNull(); + } else { + fieldIntegerListNullOsList.addLong(fieldIntegerListNullItem.longValue()); + } + } + } + + RealmList fieldShortListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); + if (fieldShortListNotNullList != null) { + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); + for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { + if (fieldShortListNotNullItem == null) { + fieldShortListNotNullOsList.addNull(); + } else { + fieldShortListNotNullOsList.addLong(fieldShortListNotNullItem.longValue()); + } + } + } + + RealmList fieldShortListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); + if (fieldShortListNullList != null) { + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); + for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { + if (fieldShortListNullItem == null) { + fieldShortListNullOsList.addNull(); + } else { + fieldShortListNullOsList.addLong(fieldShortListNullItem.longValue()); + } + } + } + + RealmList fieldByteListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); + if (fieldByteListNotNullList != null) { + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); + for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { + if (fieldByteListNotNullItem == null) { + fieldByteListNotNullOsList.addNull(); + } else { + fieldByteListNotNullOsList.addLong(fieldByteListNotNullItem.longValue()); + } + } + } + + RealmList fieldByteListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); + if (fieldByteListNullList != null) { + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); + for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { + if (fieldByteListNullItem == null) { + fieldByteListNullOsList.addNull(); + } else { + fieldByteListNullOsList.addLong(fieldByteListNullItem.longValue()); + } + } } - Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); - if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); + + RealmList fieldDoubleListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); + if (fieldDoubleListNotNullList != null) { + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); + for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { + if (fieldDoubleListNotNullItem == null) { + fieldDoubleListNotNullOsList.addNull(); + } else { + fieldDoubleListNotNullOsList.addDouble(fieldDoubleListNotNullItem.doubleValue()); + } + } } - Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); - if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); + + RealmList fieldDoubleListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); + if (fieldDoubleListNullList != null) { + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); + for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { + if (fieldDoubleListNullItem == null) { + fieldDoubleListNullOsList.addNull(); + } else { + fieldDoubleListNullOsList.addDouble(fieldDoubleListNullItem.doubleValue()); + } + } } - Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); - if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); + + RealmList fieldFloatListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); + if (fieldFloatListNotNullList != null) { + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); + for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { + if (fieldFloatListNotNullItem == null) { + fieldFloatListNotNullOsList.addNull(); + } else { + fieldFloatListNotNullOsList.addFloat(fieldFloatListNotNullItem.floatValue()); + } + } } - java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); - if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); + + RealmList fieldFloatListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); + if (fieldFloatListNullList != null) { + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); + for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { + if (fieldFloatListNullItem == null) { + fieldFloatListNullOsList.addNull(); + } else { + fieldFloatListNullOsList.addFloat(fieldFloatListNullItem.floatValue()); + } + } } - java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); - if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); + + RealmList fieldDateListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); + if (fieldDateListNotNullList != null) { + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); + for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { + if (fieldDateListNotNullItem == null) { + fieldDateListNotNullOsList.addNull(); + } else { + fieldDateListNotNullOsList.addDate(fieldDateListNotNullItem); + } + } } - some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); - if (fieldObjectNullObj != null) { - Long cachefieldObjectNull = cache.get(fieldObjectNullObj); - if (cachefieldObjectNull == null) { - cachefieldObjectNull = NullTypesRealmProxy.insert(realm, fieldObjectNullObj, cache); + RealmList fieldDateListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); + if (fieldDateListNullList != null) { + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); + for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { + if (fieldDateListNullItem == null) { + fieldDateListNullOsList.addNull(); + } else { + fieldDateListNullOsList.addDate(fieldDateListNullItem); + } } - Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); } return rowIndex; } @@ -1487,6 +2768,246 @@ public static void insert(Realm realm, Iterator objects, M } table.setLink(columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); } + + RealmList fieldStringListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); + if (fieldStringListNotNullList != null) { + OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNotNullIndex); + for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { + if (fieldStringListNotNullItem == null) { + fieldStringListNotNullOsList.addNull(); + } else { + fieldStringListNotNullOsList.addString(fieldStringListNotNullItem); + } + } + } + + RealmList fieldStringListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); + if (fieldStringListNullList != null) { + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); + for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { + if (fieldStringListNullItem == null) { + fieldStringListNullOsList.addNull(); + } else { + fieldStringListNullOsList.addString(fieldStringListNullItem); + } + } + } + + RealmList fieldBinaryListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); + if (fieldBinaryListNotNullList != null) { + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); + for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { + if (fieldBinaryListNotNullItem == null) { + fieldBinaryListNotNullOsList.addNull(); + } else { + fieldBinaryListNotNullOsList.addBinary(fieldBinaryListNotNullItem); + } + } + } + + RealmList fieldBinaryListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); + if (fieldBinaryListNullList != null) { + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); + for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { + if (fieldBinaryListNullItem == null) { + fieldBinaryListNullOsList.addNull(); + } else { + fieldBinaryListNullOsList.addBinary(fieldBinaryListNullItem); + } + } + } + + RealmList fieldBooleanListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); + if (fieldBooleanListNotNullList != null) { + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); + for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { + if (fieldBooleanListNotNullItem == null) { + fieldBooleanListNotNullOsList.addNull(); + } else { + fieldBooleanListNotNullOsList.addBoolean(fieldBooleanListNotNullItem); + } + } + } + + RealmList fieldBooleanListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); + if (fieldBooleanListNullList != null) { + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); + for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { + if (fieldBooleanListNullItem == null) { + fieldBooleanListNullOsList.addNull(); + } else { + fieldBooleanListNullOsList.addBoolean(fieldBooleanListNullItem); + } + } + } + + RealmList fieldLongListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); + if (fieldLongListNotNullList != null) { + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); + for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { + if (fieldLongListNotNullItem == null) { + fieldLongListNotNullOsList.addNull(); + } else { + fieldLongListNotNullOsList.addLong(fieldLongListNotNullItem.longValue()); + } + } + } + + RealmList fieldLongListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); + if (fieldLongListNullList != null) { + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); + for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { + if (fieldLongListNullItem == null) { + fieldLongListNullOsList.addNull(); + } else { + fieldLongListNullOsList.addLong(fieldLongListNullItem.longValue()); + } + } + } + + RealmList fieldIntegerListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); + if (fieldIntegerListNotNullList != null) { + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); + for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { + if (fieldIntegerListNotNullItem == null) { + fieldIntegerListNotNullOsList.addNull(); + } else { + fieldIntegerListNotNullOsList.addLong(fieldIntegerListNotNullItem.longValue()); + } + } + } + + RealmList fieldIntegerListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); + if (fieldIntegerListNullList != null) { + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); + for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { + if (fieldIntegerListNullItem == null) { + fieldIntegerListNullOsList.addNull(); + } else { + fieldIntegerListNullOsList.addLong(fieldIntegerListNullItem.longValue()); + } + } + } + + RealmList fieldShortListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); + if (fieldShortListNotNullList != null) { + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); + for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { + if (fieldShortListNotNullItem == null) { + fieldShortListNotNullOsList.addNull(); + } else { + fieldShortListNotNullOsList.addLong(fieldShortListNotNullItem.longValue()); + } + } + } + + RealmList fieldShortListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); + if (fieldShortListNullList != null) { + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); + for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { + if (fieldShortListNullItem == null) { + fieldShortListNullOsList.addNull(); + } else { + fieldShortListNullOsList.addLong(fieldShortListNullItem.longValue()); + } + } + } + + RealmList fieldByteListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); + if (fieldByteListNotNullList != null) { + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); + for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { + if (fieldByteListNotNullItem == null) { + fieldByteListNotNullOsList.addNull(); + } else { + fieldByteListNotNullOsList.addLong(fieldByteListNotNullItem.longValue()); + } + } + } + + RealmList fieldByteListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); + if (fieldByteListNullList != null) { + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); + for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { + if (fieldByteListNullItem == null) { + fieldByteListNullOsList.addNull(); + } else { + fieldByteListNullOsList.addLong(fieldByteListNullItem.longValue()); + } + } + } + + RealmList fieldDoubleListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); + if (fieldDoubleListNotNullList != null) { + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); + for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { + if (fieldDoubleListNotNullItem == null) { + fieldDoubleListNotNullOsList.addNull(); + } else { + fieldDoubleListNotNullOsList.addDouble(fieldDoubleListNotNullItem.doubleValue()); + } + } + } + + RealmList fieldDoubleListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); + if (fieldDoubleListNullList != null) { + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); + for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { + if (fieldDoubleListNullItem == null) { + fieldDoubleListNullOsList.addNull(); + } else { + fieldDoubleListNullOsList.addDouble(fieldDoubleListNullItem.doubleValue()); + } + } + } + + RealmList fieldFloatListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); + if (fieldFloatListNotNullList != null) { + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); + for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { + if (fieldFloatListNotNullItem == null) { + fieldFloatListNotNullOsList.addNull(); + } else { + fieldFloatListNotNullOsList.addFloat(fieldFloatListNotNullItem.floatValue()); + } + } + } + + RealmList fieldFloatListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); + if (fieldFloatListNullList != null) { + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); + for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { + if (fieldFloatListNullItem == null) { + fieldFloatListNullOsList.addNull(); + } else { + fieldFloatListNullOsList.addFloat(fieldFloatListNullItem.floatValue()); + } + } + } + + RealmList fieldDateListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); + if (fieldDateListNotNullList != null) { + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); + for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { + if (fieldDateListNotNullItem == null) { + fieldDateListNotNullOsList.addNull(); + } else { + fieldDateListNotNullOsList.addDate(fieldDateListNotNullItem); + } + } + } + + RealmList fieldDateListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); + if (fieldDateListNullList != null) { + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); + for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { + if (fieldDateListNullItem == null) { + fieldDateListNullOsList.addNull(); + } else { + fieldDateListNullOsList.addDate(fieldDateListNullItem); + } + } + } } } @@ -1571,65 +3092,345 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldStringListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); + if (fieldStringListNotNullList != null) { + for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { + if (fieldStringListNotNullItem == null) { + fieldStringListNotNullOsList.addNull(); + } else { + fieldStringListNotNullOsList.addString(fieldStringListNotNullItem); + } + } + } + + + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); + fieldStringListNullOsList.removeAll(); + RealmList fieldStringListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); + if (fieldStringListNullList != null) { + for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { + if (fieldStringListNullItem == null) { + fieldStringListNullOsList.addNull(); + } else { + fieldStringListNullOsList.addString(fieldStringListNullItem); + } + } + } + + + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); + fieldBinaryListNotNullOsList.removeAll(); + RealmList fieldBinaryListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); + if (fieldBinaryListNotNullList != null) { + for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { + if (fieldBinaryListNotNullItem == null) { + fieldBinaryListNotNullOsList.addNull(); + } else { + fieldBinaryListNotNullOsList.addBinary(fieldBinaryListNotNullItem); + } + } + } + + + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); + fieldBinaryListNullOsList.removeAll(); + RealmList fieldBinaryListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); + if (fieldBinaryListNullList != null) { + for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { + if (fieldBinaryListNullItem == null) { + fieldBinaryListNullOsList.addNull(); + } else { + fieldBinaryListNullOsList.addBinary(fieldBinaryListNullItem); + } + } + } + + + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); + fieldBooleanListNotNullOsList.removeAll(); + RealmList fieldBooleanListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); + if (fieldBooleanListNotNullList != null) { + for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { + if (fieldBooleanListNotNullItem == null) { + fieldBooleanListNotNullOsList.addNull(); + } else { + fieldBooleanListNotNullOsList.addBoolean(fieldBooleanListNotNullItem); + } + } + } + + + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); + fieldBooleanListNullOsList.removeAll(); + RealmList fieldBooleanListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); + if (fieldBooleanListNullList != null) { + for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { + if (fieldBooleanListNullItem == null) { + fieldBooleanListNullOsList.addNull(); + } else { + fieldBooleanListNullOsList.addBoolean(fieldBooleanListNullItem); + } + } + } + + + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); + fieldLongListNotNullOsList.removeAll(); + RealmList fieldLongListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); + if (fieldLongListNotNullList != null) { + for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { + if (fieldLongListNotNullItem == null) { + fieldLongListNotNullOsList.addNull(); + } else { + fieldLongListNotNullOsList.addLong(fieldLongListNotNullItem.longValue()); + } + } + } + + + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); + fieldLongListNullOsList.removeAll(); + RealmList fieldLongListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); + if (fieldLongListNullList != null) { + for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { + if (fieldLongListNullItem == null) { + fieldLongListNullOsList.addNull(); + } else { + fieldLongListNullOsList.addLong(fieldLongListNullItem.longValue()); + } + } + } + + + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); + fieldIntegerListNotNullOsList.removeAll(); + RealmList fieldIntegerListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); + if (fieldIntegerListNotNullList != null) { + for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { + if (fieldIntegerListNotNullItem == null) { + fieldIntegerListNotNullOsList.addNull(); + } else { + fieldIntegerListNotNullOsList.addLong(fieldIntegerListNotNullItem.longValue()); + } + } + } + + + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); + fieldIntegerListNullOsList.removeAll(); + RealmList fieldIntegerListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); + if (fieldIntegerListNullList != null) { + for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { + if (fieldIntegerListNullItem == null) { + fieldIntegerListNullOsList.addNull(); + } else { + fieldIntegerListNullOsList.addLong(fieldIntegerListNullItem.longValue()); + } + } + } + + + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); + fieldShortListNotNullOsList.removeAll(); + RealmList fieldShortListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); + if (fieldShortListNotNullList != null) { + for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { + if (fieldShortListNotNullItem == null) { + fieldShortListNotNullOsList.addNull(); + } else { + fieldShortListNotNullOsList.addLong(fieldShortListNotNullItem.longValue()); + } + } + } + + + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); + fieldShortListNullOsList.removeAll(); + RealmList fieldShortListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); + if (fieldShortListNullList != null) { + for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { + if (fieldShortListNullItem == null) { + fieldShortListNullOsList.addNull(); + } else { + fieldShortListNullOsList.addLong(fieldShortListNullItem.longValue()); + } + } } - Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); - if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, false); + + + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); + fieldByteListNotNullOsList.removeAll(); + RealmList fieldByteListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); + if (fieldByteListNotNullList != null) { + for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { + if (fieldByteListNotNullItem == null) { + fieldByteListNotNullOsList.addNull(); + } else { + fieldByteListNotNullOsList.addLong(fieldByteListNotNullItem.longValue()); + } + } } - Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); - if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, false); + + + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); + fieldByteListNullOsList.removeAll(); + RealmList fieldByteListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); + if (fieldByteListNullList != null) { + for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { + if (fieldByteListNullItem == null) { + fieldByteListNullOsList.addNull(); + } else { + fieldByteListNullOsList.addLong(fieldByteListNullItem.longValue()); + } + } } - Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); - if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, false); + + + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); + fieldDoubleListNotNullOsList.removeAll(); + RealmList fieldDoubleListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); + if (fieldDoubleListNotNullList != null) { + for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { + if (fieldDoubleListNotNullItem == null) { + fieldDoubleListNotNullOsList.addNull(); + } else { + fieldDoubleListNotNullOsList.addDouble(fieldDoubleListNotNullItem.doubleValue()); + } + } } - Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); - if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, false); + + + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); + fieldDoubleListNullOsList.removeAll(); + RealmList fieldDoubleListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); + if (fieldDoubleListNullList != null) { + for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { + if (fieldDoubleListNullItem == null) { + fieldDoubleListNullOsList.addNull(); + } else { + fieldDoubleListNullOsList.addDouble(fieldDoubleListNullItem.doubleValue()); + } + } } - Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); - if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, false); + + + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); + fieldFloatListNotNullOsList.removeAll(); + RealmList fieldFloatListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); + if (fieldFloatListNotNullList != null) { + for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { + if (fieldFloatListNotNullItem == null) { + fieldFloatListNotNullOsList.addNull(); + } else { + fieldFloatListNotNullOsList.addFloat(fieldFloatListNotNullItem.floatValue()); + } + } } - java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); - if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, false); + + + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); + fieldFloatListNullOsList.removeAll(); + RealmList fieldFloatListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); + if (fieldFloatListNullList != null) { + for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { + if (fieldFloatListNullItem == null) { + fieldFloatListNullOsList.addNull(); + } else { + fieldFloatListNullOsList.addFloat(fieldFloatListNullItem.floatValue()); + } + } } - java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); - if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); - } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, false); + + + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); + fieldDateListNotNullOsList.removeAll(); + RealmList fieldDateListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); + if (fieldDateListNotNullList != null) { + for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { + if (fieldDateListNotNullItem == null) { + fieldDateListNotNullOsList.addNull(); + } else { + fieldDateListNotNullOsList.addDate(fieldDateListNotNullItem); + } + } } - some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); - if (fieldObjectNullObj != null) { - Long cachefieldObjectNull = cache.get(fieldObjectNullObj); - if (cachefieldObjectNull == null) { - cachefieldObjectNull = NullTypesRealmProxy.insertOrUpdate(realm, fieldObjectNullObj, cache); + + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); + fieldDateListNullOsList.removeAll(); + RealmList fieldDateListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); + if (fieldDateListNullList != null) { + for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { + if (fieldDateListNullItem == null) { + fieldDateListNullOsList.addNull(); + } else { + fieldDateListNullOsList.addDate(fieldDateListNullItem); + } } - Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); - } else { - Table.nativeNullifyLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex); } + return rowIndex; } @@ -1780,6 +3581,286 @@ public static void insertOrUpdate(Realm realm, Iterator ob } else { Table.nativeNullifyLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex); } + + OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNotNullIndex); + fieldStringListNotNullOsList.removeAll(); + RealmList fieldStringListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); + if (fieldStringListNotNullList != null) { + for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { + if (fieldStringListNotNullItem == null) { + fieldStringListNotNullOsList.addNull(); + } else { + fieldStringListNotNullOsList.addString(fieldStringListNotNullItem); + } + } + } + + + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); + fieldStringListNullOsList.removeAll(); + RealmList fieldStringListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); + if (fieldStringListNullList != null) { + for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { + if (fieldStringListNullItem == null) { + fieldStringListNullOsList.addNull(); + } else { + fieldStringListNullOsList.addString(fieldStringListNullItem); + } + } + } + + + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); + fieldBinaryListNotNullOsList.removeAll(); + RealmList fieldBinaryListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); + if (fieldBinaryListNotNullList != null) { + for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { + if (fieldBinaryListNotNullItem == null) { + fieldBinaryListNotNullOsList.addNull(); + } else { + fieldBinaryListNotNullOsList.addBinary(fieldBinaryListNotNullItem); + } + } + } + + + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); + fieldBinaryListNullOsList.removeAll(); + RealmList fieldBinaryListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); + if (fieldBinaryListNullList != null) { + for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { + if (fieldBinaryListNullItem == null) { + fieldBinaryListNullOsList.addNull(); + } else { + fieldBinaryListNullOsList.addBinary(fieldBinaryListNullItem); + } + } + } + + + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); + fieldBooleanListNotNullOsList.removeAll(); + RealmList fieldBooleanListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); + if (fieldBooleanListNotNullList != null) { + for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { + if (fieldBooleanListNotNullItem == null) { + fieldBooleanListNotNullOsList.addNull(); + } else { + fieldBooleanListNotNullOsList.addBoolean(fieldBooleanListNotNullItem); + } + } + } + + + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); + fieldBooleanListNullOsList.removeAll(); + RealmList fieldBooleanListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); + if (fieldBooleanListNullList != null) { + for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { + if (fieldBooleanListNullItem == null) { + fieldBooleanListNullOsList.addNull(); + } else { + fieldBooleanListNullOsList.addBoolean(fieldBooleanListNullItem); + } + } + } + + + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); + fieldLongListNotNullOsList.removeAll(); + RealmList fieldLongListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); + if (fieldLongListNotNullList != null) { + for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { + if (fieldLongListNotNullItem == null) { + fieldLongListNotNullOsList.addNull(); + } else { + fieldLongListNotNullOsList.addLong(fieldLongListNotNullItem.longValue()); + } + } + } + + + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); + fieldLongListNullOsList.removeAll(); + RealmList fieldLongListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); + if (fieldLongListNullList != null) { + for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { + if (fieldLongListNullItem == null) { + fieldLongListNullOsList.addNull(); + } else { + fieldLongListNullOsList.addLong(fieldLongListNullItem.longValue()); + } + } + } + + + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); + fieldIntegerListNotNullOsList.removeAll(); + RealmList fieldIntegerListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); + if (fieldIntegerListNotNullList != null) { + for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { + if (fieldIntegerListNotNullItem == null) { + fieldIntegerListNotNullOsList.addNull(); + } else { + fieldIntegerListNotNullOsList.addLong(fieldIntegerListNotNullItem.longValue()); + } + } + } + + + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); + fieldIntegerListNullOsList.removeAll(); + RealmList fieldIntegerListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); + if (fieldIntegerListNullList != null) { + for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { + if (fieldIntegerListNullItem == null) { + fieldIntegerListNullOsList.addNull(); + } else { + fieldIntegerListNullOsList.addLong(fieldIntegerListNullItem.longValue()); + } + } + } + + + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); + fieldShortListNotNullOsList.removeAll(); + RealmList fieldShortListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); + if (fieldShortListNotNullList != null) { + for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { + if (fieldShortListNotNullItem == null) { + fieldShortListNotNullOsList.addNull(); + } else { + fieldShortListNotNullOsList.addLong(fieldShortListNotNullItem.longValue()); + } + } + } + + + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); + fieldShortListNullOsList.removeAll(); + RealmList fieldShortListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); + if (fieldShortListNullList != null) { + for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { + if (fieldShortListNullItem == null) { + fieldShortListNullOsList.addNull(); + } else { + fieldShortListNullOsList.addLong(fieldShortListNullItem.longValue()); + } + } + } + + + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); + fieldByteListNotNullOsList.removeAll(); + RealmList fieldByteListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); + if (fieldByteListNotNullList != null) { + for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { + if (fieldByteListNotNullItem == null) { + fieldByteListNotNullOsList.addNull(); + } else { + fieldByteListNotNullOsList.addLong(fieldByteListNotNullItem.longValue()); + } + } + } + + + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); + fieldByteListNullOsList.removeAll(); + RealmList fieldByteListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); + if (fieldByteListNullList != null) { + for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { + if (fieldByteListNullItem == null) { + fieldByteListNullOsList.addNull(); + } else { + fieldByteListNullOsList.addLong(fieldByteListNullItem.longValue()); + } + } + } + + + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); + fieldDoubleListNotNullOsList.removeAll(); + RealmList fieldDoubleListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); + if (fieldDoubleListNotNullList != null) { + for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { + if (fieldDoubleListNotNullItem == null) { + fieldDoubleListNotNullOsList.addNull(); + } else { + fieldDoubleListNotNullOsList.addDouble(fieldDoubleListNotNullItem.doubleValue()); + } + } + } + + + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); + fieldDoubleListNullOsList.removeAll(); + RealmList fieldDoubleListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); + if (fieldDoubleListNullList != null) { + for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { + if (fieldDoubleListNullItem == null) { + fieldDoubleListNullOsList.addNull(); + } else { + fieldDoubleListNullOsList.addDouble(fieldDoubleListNullItem.doubleValue()); + } + } + } + + + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); + fieldFloatListNotNullOsList.removeAll(); + RealmList fieldFloatListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); + if (fieldFloatListNotNullList != null) { + for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { + if (fieldFloatListNotNullItem == null) { + fieldFloatListNotNullOsList.addNull(); + } else { + fieldFloatListNotNullOsList.addFloat(fieldFloatListNotNullItem.floatValue()); + } + } + } + + + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); + fieldFloatListNullOsList.removeAll(); + RealmList fieldFloatListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); + if (fieldFloatListNullList != null) { + for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { + if (fieldFloatListNullItem == null) { + fieldFloatListNullOsList.addNull(); + } else { + fieldFloatListNullOsList.addFloat(fieldFloatListNullItem.floatValue()); + } + } + } + + + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); + fieldDateListNotNullOsList.removeAll(); + RealmList fieldDateListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); + if (fieldDateListNotNullList != null) { + for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { + if (fieldDateListNotNullItem == null) { + fieldDateListNotNullOsList.addNull(); + } else { + fieldDateListNotNullOsList.addDate(fieldDateListNotNullItem); + } + } + } + + + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); + fieldDateListNullOsList.removeAll(); + RealmList fieldDateListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); + if (fieldDateListNullList != null) { + for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { + if (fieldDateListNullItem == null) { + fieldDateListNullOsList.addNull(); + } else { + fieldDateListNullOsList.addDate(fieldDateListNullItem); + } + } + } + } } @@ -1825,6 +3906,67 @@ public static some.test.NullTypes createDetachedCopy(some.test.NullTypes realmOb // Deep copy of fieldObjectNull unmanagedCopy.realmSet$fieldObjectNull(NullTypesRealmProxy.createDetachedCopy(realmSource.realmGet$fieldObjectNull(), currentDepth + 1, maxDepth, cache)); + + unmanagedCopy.realmSet$fieldStringListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldStringListNotNull().addAll(realmSource.realmGet$fieldStringListNotNull()); + + unmanagedCopy.realmSet$fieldStringListNull(new RealmList()); + unmanagedCopy.realmGet$fieldStringListNull().addAll(realmSource.realmGet$fieldStringListNull()); + + unmanagedCopy.realmSet$fieldBinaryListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldBinaryListNotNull().addAll(realmSource.realmGet$fieldBinaryListNotNull()); + + unmanagedCopy.realmSet$fieldBinaryListNull(new RealmList()); + unmanagedCopy.realmGet$fieldBinaryListNull().addAll(realmSource.realmGet$fieldBinaryListNull()); + + unmanagedCopy.realmSet$fieldBooleanListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldBooleanListNotNull().addAll(realmSource.realmGet$fieldBooleanListNotNull()); + + unmanagedCopy.realmSet$fieldBooleanListNull(new RealmList()); + unmanagedCopy.realmGet$fieldBooleanListNull().addAll(realmSource.realmGet$fieldBooleanListNull()); + + unmanagedCopy.realmSet$fieldLongListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldLongListNotNull().addAll(realmSource.realmGet$fieldLongListNotNull()); + + unmanagedCopy.realmSet$fieldLongListNull(new RealmList()); + unmanagedCopy.realmGet$fieldLongListNull().addAll(realmSource.realmGet$fieldLongListNull()); + + unmanagedCopy.realmSet$fieldIntegerListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldIntegerListNotNull().addAll(realmSource.realmGet$fieldIntegerListNotNull()); + + unmanagedCopy.realmSet$fieldIntegerListNull(new RealmList()); + unmanagedCopy.realmGet$fieldIntegerListNull().addAll(realmSource.realmGet$fieldIntegerListNull()); + + unmanagedCopy.realmSet$fieldShortListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldShortListNotNull().addAll(realmSource.realmGet$fieldShortListNotNull()); + + unmanagedCopy.realmSet$fieldShortListNull(new RealmList()); + unmanagedCopy.realmGet$fieldShortListNull().addAll(realmSource.realmGet$fieldShortListNull()); + + unmanagedCopy.realmSet$fieldByteListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldByteListNotNull().addAll(realmSource.realmGet$fieldByteListNotNull()); + + unmanagedCopy.realmSet$fieldByteListNull(new RealmList()); + unmanagedCopy.realmGet$fieldByteListNull().addAll(realmSource.realmGet$fieldByteListNull()); + + unmanagedCopy.realmSet$fieldDoubleListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldDoubleListNotNull().addAll(realmSource.realmGet$fieldDoubleListNotNull()); + + unmanagedCopy.realmSet$fieldDoubleListNull(new RealmList()); + unmanagedCopy.realmGet$fieldDoubleListNull().addAll(realmSource.realmGet$fieldDoubleListNull()); + + unmanagedCopy.realmSet$fieldFloatListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldFloatListNotNull().addAll(realmSource.realmGet$fieldFloatListNotNull()); + + unmanagedCopy.realmSet$fieldFloatListNull(new RealmList()); + unmanagedCopy.realmGet$fieldFloatListNull().addAll(realmSource.realmGet$fieldFloatListNull()); + + unmanagedCopy.realmSet$fieldDateListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldDateListNotNull().addAll(realmSource.realmGet$fieldDateListNotNull()); + + unmanagedCopy.realmSet$fieldDateListNull(new RealmList()); + unmanagedCopy.realmGet$fieldDateListNull().addAll(realmSource.realmGet$fieldDateListNull()); + return unmanagedObject; } @@ -1918,6 +4060,86 @@ public String toString() { stringBuilder.append("{fieldObjectNull:"); stringBuilder.append(realmGet$fieldObjectNull() != null ? "NullTypes" : "null"); stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldStringListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldStringListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldStringListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldStringListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldBinaryListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldBinaryListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldBinaryListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldBinaryListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldBooleanListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldBooleanListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldBooleanListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldBooleanListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldLongListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldLongListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldLongListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldLongListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldIntegerListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldIntegerListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldIntegerListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldIntegerListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldShortListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldShortListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldShortListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldShortListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldByteListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldByteListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldByteListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldByteListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldDoubleListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldDoubleListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldDoubleListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldDoubleListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldFloatListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldFloatListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldFloatListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldFloatListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldDateListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldDateListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldDateListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldDateListNull().size()).append("]"); + stringBuilder.append("}"); stringBuilder.append("]"); return stringBuilder.toString(); } @@ -1958,5 +4180,4 @@ public boolean equals(Object o) { return true; } - } diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java index f3d88fc2cb..4539db1355 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java @@ -51,6 +51,18 @@ public class AllTypes extends RealmObject { private RealmList columnRealmList; + private RealmList columnStringList; + private RealmList columnBinaryList; + private RealmList columnBooleanList; + private RealmList columnLongList; + private RealmList columnIntegerList; + private RealmList columnShortList; + private RealmList columnByteList; + private RealmList columnDoubleList; + private RealmList columnFloatList; + private RealmList columnDateList; + + @LinkingObjects(FIELD_PARENTS) private final RealmResults parentObjects = null; diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/InvalidListElementType.java b/realm/realm-annotations-processor/src/test/resources/some/test/InvalidListElementType.java new file mode 100644 index 0000000000..217aee5523 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/InvalidListElementType.java @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package some.test; + +import java.math.BigInteger; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class InvalidListElementType extends RealmObject { + public RealmList bigIntegerList; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/InvalidResultsElementType.java b/realm/realm-annotations-processor/src/test/resources/some/test/InvalidResultsElementType.java new file mode 100644 index 0000000000..dd832054d8 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/InvalidResultsElementType.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package some.test; + +import java.math.BigInteger; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class InvalidResultsElementType extends RealmObject { + public InvalidResultsElementType child; + + @LinkingObjects("child") + public RealmResults bigIntegerBacklinks; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NullTypes.java b/realm/realm-annotations-processor/src/test/resources/some/test/NullTypes.java index 1904076340..45bc47aae6 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/NullTypes.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NullTypes.java @@ -66,6 +66,45 @@ public class NullTypes extends RealmObject { private NullTypes fieldObjectNull; + @Required + private RealmList fieldStringListNotNull; + private RealmList fieldStringListNull; + + @Required + private RealmList fieldBinaryListNotNull; + private RealmList fieldBinaryListNull; + + @Required + private RealmList fieldBooleanListNotNull; + private RealmList fieldBooleanListNull; + + @Required + private RealmList fieldLongListNotNull; + private RealmList fieldLongListNull; + + @Required + private RealmList fieldIntegerListNotNull; + private RealmList fieldIntegerListNull; + + @Required + private RealmList fieldShortListNotNull; + private RealmList fieldShortListNull; + + @Required + private RealmList fieldByteListNotNull; + private RealmList fieldByteListNull; + + @Required + private RealmList fieldDoubleListNotNull; + private RealmList fieldDoubleListNull; + + @Required + private RealmList fieldFloatListNotNull; + private RealmList fieldFloatListNull; + + @Required + private RealmList fieldDateListNotNull; + private RealmList fieldDateListNull; public String getFieldStringNotNull() { return realmGet$fieldStringNotNull(); diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/ValueList.java b/realm/realm-annotations-processor/src/test/resources/some/test/ValueList.java new file mode 100644 index 0000000000..33d615fc8e --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/ValueList.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package some.test; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class ValueList extends RealmObject { + public RealmList stringList; + public RealmList binaryList; + public RealmList booleanList; + public RealmList longList; + public RealmList integerList; + public RealmList shortList; + public RealmList byteList; + public RealmList doubleList; + public RealmList floatList; + public RealmList integerList; +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 9877018d9b..c87b63b0ce 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -1159,7 +1159,11 @@ public void getFieldNames() { String[] expectedKeys = {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID, AllJavaTypes.FIELD_LONG, AllJavaTypes.FIELD_SHORT, AllJavaTypes.FIELD_INT, AllJavaTypes.FIELD_BYTE, AllJavaTypes.FIELD_FLOAT, AllJavaTypes.FIELD_DOUBLE, AllJavaTypes.FIELD_BOOLEAN, AllJavaTypes.FIELD_DATE, - AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_OBJECT, AllJavaTypes.FIELD_LIST}; + AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_OBJECT, AllJavaTypes.FIELD_LIST, + AllJavaTypes.FIELD_STRING_LIST, AllJavaTypes.FIELD_BINARY_LIST, AllJavaTypes.FIELD_BOOLEAN_LIST, + AllJavaTypes.FIELD_LONG_LIST, AllJavaTypes.FIELD_INTEGER_LIST, AllJavaTypes.FIELD_SHORT_LIST, + AllJavaTypes.FIELD_BYTE_LIST, AllJavaTypes.FIELD_DOUBLE_LIST, AllJavaTypes.FIELD_FLOAT_LIST, + AllJavaTypes.FIELD_DATE_LIST}; String[] keys = dObjTyped.getFieldNames(); // After the stable ID support, primary key field will be inserted first before others. So even FIELD_STRING is // the first defined field in the class, it will be inserted after FIELD_ID. diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index 39c5d346b2..b94b6b4593 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -207,6 +207,41 @@ public void linkingObjects_invalidFieldType() { case DOUBLE: object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE); break; + case INTEGER_LIST: + // FIXME zaki50 enable this once Primitive List is implemented + //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_INT_LIST); + //break; + throw new IllegalArgumentException("Unexpected field type"); + case BOOLEAN_LIST: + // FIXME zaki50 enable this once Primitive List is implemented + //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_BOOLEAN_LIST); + //break; + throw new IllegalArgumentException("Unexpected field type"); + case STRING_LIST: + // FIXME zaki50 enable this once Primitive List is implemented + //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_STRING_LIST); + //break; + throw new IllegalArgumentException("Unexpected field type"); + case BINARY_LIST: + // FIXME zaki50 enable this once Primitive List is implemented + //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_BINARY_LIST); + //break; + throw new IllegalArgumentException("Unexpected field type"); + case DATE_LIST: + // FIXME zaki50 enable this once Primitive List is implemented + //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DATE_LIST); + //break; + throw new IllegalArgumentException("Unexpected field type"); + case FLOAT_LIST: + // FIXME zaki50 enable this once Primitive List is implemented + //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_FLOAT_LIST); + //break; + throw new IllegalArgumentException("Unexpected field type"); + case DOUBLE_LIST: + // FIXME zaki50 enable this once Primitive List is implemented + //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE_LIST); + //break; + throw new IllegalArgumentException("Unexpected field type"); default: fail("unknown type: " + fieldType); break; diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValueTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValueTests.java new file mode 100644 index 0000000000..0e8cddf5d4 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValueTests.java @@ -0,0 +1,1563 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; + +import io.realm.entities.NullTypes; +import io.realm.internal.Table; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.TestRealmConfigurationFactory; + +import static io.realm.ManagedRealmListForValueTests.ListType.BINARY_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.BOOLEAN_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.BYTE_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.DATE_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.DOUBLE_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.FLOAT_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.INTEGER_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.LONG_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.SHORT_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.STRING_LIST; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + + +/** + * Unit tests specific for RealmList with value elements. + */ +@RunWith(Parameterized.class) +public class ManagedRealmListForValueTests extends CollectionTests { + + static final int NON_NULL_TEST_SIZE = 10; + static final int NULLABLE_TEST_SIZE = NON_NULL_TEST_SIZE * 2; + + enum ListType { + STRING_LIST(String.class.getName()), + BOOLEAN_LIST(Boolean.class.getName()), + BINARY_LIST(byte[].class.getSimpleName()/* using simple name since array class is a bit special */), + LONG_LIST(Long.class.getName()), + INTEGER_LIST(Integer.class.getName()), + SHORT_LIST(Short.class.getName()), + BYTE_LIST(Byte.class.getName()), + DOUBLE_LIST(Double.class.getName()), + FLOAT_LIST(Float.class.getName()), + DATE_LIST(Date.class.getName()); + + private final String valueTypeName; + + ListType(String valueTypeName) { + this.valueTypeName = valueTypeName; + } + + public String getValueTypeName() { + return valueTypeName; + } + } + + @Parameterized.Parameters(name = "{index}: Type: {0}, Nullable?: {1}") + public static Collection parameters() { + final List paramsList = new ArrayList<>(); + for (ListType listType : ListType.values()) { + paramsList.add(new Object[] {listType, Boolean.TRUE}); + paramsList.add(new Object[] {listType, Boolean.FALSE}); + } + return paramsList; + } + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + @Parameterized.Parameter + public ListType listType; + + @Parameterized.Parameter(1) + public Boolean isTypeNullable; + + private Realm realm; + private NullTypes object; + private RealmList list; + + @SuppressWarnings("unchecked") + @Before + public void setUp() throws Exception { + final RealmConfiguration.Builder configurationBuilder = configFactory.createConfigurationBuilder(); + configurationBuilder.schema(NullTypes.class); + RealmConfiguration realmConfig = configurationBuilder.build(); + + realm = Realm.getInstance(realmConfig); + + realm.beginTransaction(); + object = realm.createObject(NullTypes.class, 0); + for (ListType type : ListType.values()) { + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + switch (type) { + case STRING_LIST: { + final RealmList nonnull = object.getFieldStringListNotNull(); + nonnull.add(generateValue(STRING_LIST, i)); + final RealmList nullable = object.getFieldStringListNull(); + nullable.add("" + i); + nullable.add(null); + } + break; + case BOOLEAN_LIST: { + final RealmList nonnull = object.getFieldBooleanListNotNull(); + nonnull.add(generateValue(BOOLEAN_LIST, i)); + final RealmList nullable = object.getFieldBooleanListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case BINARY_LIST: { + final RealmList nonnull = object.getFieldBinaryListNotNull(); + nonnull.add(generateValue(BINARY_LIST, i)); + final RealmList nullable = object.getFieldBinaryListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case LONG_LIST: { + final RealmList nonnull = object.getFieldLongListNotNull(); + nonnull.add(generateValue(LONG_LIST, i)); + final RealmList nullable = object.getFieldLongListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case INTEGER_LIST: { + final RealmList nonnull = object.getFieldIntegerListNotNull(); + nonnull.add(generateValue(INTEGER_LIST, i)); + final RealmList nullable = object.getFieldIntegerListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case SHORT_LIST: { + final RealmList nonnull = object.getFieldShortListNotNull(); + nonnull.add(generateValue(SHORT_LIST, i)); + final RealmList nullable = object.getFieldShortListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case BYTE_LIST: { + final RealmList nonnull = object.getFieldByteListNotNull(); + nonnull.add(generateValue(BYTE_LIST, i)); + final RealmList nullable = object.getFieldByteListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case DOUBLE_LIST: { + final RealmList nonnull = object.getFieldDoubleListNotNull(); + nonnull.add(generateValue(DOUBLE_LIST, i)); + final RealmList nullable = object.getFieldDoubleListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case FLOAT_LIST: { + final RealmList nonnull = object.getFieldFloatListNotNull(); + nonnull.add(generateValue(FLOAT_LIST, i)); + final RealmList nullable = object.getFieldFloatListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case DATE_LIST: { + final RealmList nonnull = object.getFieldDateListNotNull(); + nonnull.add(generateValue(DATE_LIST, i)); + final RealmList nullable = object.getFieldDateListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + default: + throw new AssertionError("unexpected value type: " + listType.name()); + } + } + } + realm.commitTransaction(); + + list = getListFor(object, listType, isTypeNullable); + } + + static RealmList getListFor(NullTypes object, ListType listType, boolean nullable) { + switch (listType) { + case STRING_LIST: + return nullable ? object.getFieldStringListNull() : object.getFieldStringListNotNull(); + case BOOLEAN_LIST: + return nullable ? object.getFieldBooleanListNull() : object.getFieldBooleanListNotNull(); + case BINARY_LIST: + return nullable ? object.getFieldBinaryListNull() : object.getFieldBinaryListNotNull(); + case LONG_LIST: + return nullable ? object.getFieldLongListNull() : object.getFieldLongListNotNull(); + case INTEGER_LIST: + return nullable ? object.getFieldIntegerListNull() : object.getFieldIntegerListNotNull(); + case SHORT_LIST: + return nullable ? object.getFieldShortListNull() : object.getFieldShortListNotNull(); + case BYTE_LIST: + return nullable ? object.getFieldByteListNull() : object.getFieldByteListNotNull(); + case DOUBLE_LIST: + return nullable ? object.getFieldDoubleListNull() : object.getFieldDoubleListNotNull(); + case FLOAT_LIST: + return nullable ? object.getFieldFloatListNull() : object.getFieldFloatListNotNull(); + case DATE_LIST: + return nullable ? object.getFieldDateListNull() : object.getFieldDateListNotNull(); + default: + throw new AssertionError("unexpected value type: " + listType.name()); + } + } + + @After + public void tearDown() throws Exception { + if (realm != null) { + realm.close(); + } + } + + static Object generateValue(ListType listType, int i) { + switch (listType) { + case STRING_LIST: + return "" + i; + case BOOLEAN_LIST: + return i % 2 == 0 ? Boolean.FALSE : Boolean.TRUE; + case BINARY_LIST: + return new byte[] {(byte) i}; + case LONG_LIST: + return (long) i; + case INTEGER_LIST: + return i; + case SHORT_LIST: + return (short) i; + case BYTE_LIST: + return (byte) i; + case DOUBLE_LIST: + return (double) i; + case FLOAT_LIST: + return (float) i; + case DATE_LIST: + return new Date(i); + default: + throw new AssertionError("unexpected value type: " + listType.name()); + } + } + + private static Object generateHugeValue(ListType listType, int size) { + final byte[] bytes = new byte[size]; + switch (listType) { + case STRING_LIST: + Arrays.fill(bytes, (byte) 'a'); + return new String(bytes, Charset.forName("US-ASCII")); + case BINARY_LIST: + return bytes; + default: + throw new AssertionError("'generateHugeValue' does not support this type: " + listType.name()); + } + } + + private void assertValueEquals(@Nullable Object expected, @Nullable Object actual) { + assertValueEquals(null, expected, actual); + } + + private void assertValueEquals(@SuppressWarnings("SameParameterValue") @Nullable String message, @Nullable Object expected, @Nullable Object actual) { + if (listType == BINARY_LIST) { + assertArrayEquals(message, (byte[]) expected, (byte[]) actual); + } else { + assertEquals(message, expected, actual); + } + } + + @Test + public void readValues() { + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + switch (listType) { + case STRING_LIST: { + assertEquals(generateValue(STRING_LIST, i), list.get(isTypeNullable ? (i * 2) : i)); + } + break; + case BOOLEAN_LIST: { + assertEquals(generateValue(BOOLEAN_LIST, i), list.get(isTypeNullable ? (i * 2) : i)); + } + break; + case BINARY_LIST: { + assertArrayEquals((byte[]) generateValue(BINARY_LIST, i), (byte[]) list.get(isTypeNullable ? (i * 2) : i)); + } + break; + case LONG_LIST: { + //noinspection UnnecessaryBoxing + assertEquals(generateValue(LONG_LIST, i), list.get(isTypeNullable ? (i * 2) : i)); + } + break; + case INTEGER_LIST: { + //noinspection UnnecessaryBoxing + assertEquals(generateValue(INTEGER_LIST, i), list.get(isTypeNullable ? (i * 2) : i)); + } + break; + case SHORT_LIST: { + //noinspection UnnecessaryBoxing + assertEquals(generateValue(SHORT_LIST, i), list.get(isTypeNullable ? (i * 2) : i)); + } + break; + case BYTE_LIST: { + //noinspection UnnecessaryBoxing + assertEquals(generateValue(BYTE_LIST, i), list.get(isTypeNullable ? (i * 2) : i)); + } + break; + case DOUBLE_LIST: { + //noinspection UnnecessaryBoxing + assertEquals(generateValue(DOUBLE_LIST, i), list.get(isTypeNullable ? (i * 2) : i)); + } + break; + case FLOAT_LIST: { + //noinspection UnnecessaryBoxing + assertEquals(generateValue(FLOAT_LIST, i), list.get(isTypeNullable ? (i * 2) : i)); + } + break; + case DATE_LIST: { + assertEquals(generateValue(DATE_LIST, i), list.get(isTypeNullable ? (i * 2) : i)); + } + break; + default: + throw new AssertionError("unexpected value type: " + listType.name()); + } + if (isTypeNullable) { + assertNull(list.get(i * 2 + 1)); + } + } + } + + @Test + public void isValid() { + assertTrue(list.isValid()); + + realm.close(); + + assertFalse(list.isValid()); + } + + @Test + public void isValid_whenParentRemoved() { + realm.beginTransaction(); + object.deleteFromRealm(); + realm.commitTransaction(); + + // RealmList contained in removed object is invalid. + assertFalse(list.isValid()); + } + + @Test + public void add_exceedingSizeLimitValueThrows() { + if (listType != STRING_LIST && listType != BINARY_LIST) { + return; + } + + final int sizeLimit; + switch (listType) { + case STRING_LIST: + sizeLimit = Table.MAX_STRING_SIZE; + break; + case BINARY_LIST: + sizeLimit = Table.MAX_BINARY_SIZE; + break; + default: + throw new AssertionError("Unexpected list type: " + listType.name()); + } + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + //noinspection unchecked + list.add(generateHugeValue(listType, sizeLimit)); + } + }); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final long sizeBeforeException = list.size(); + thrown.expect(IllegalArgumentException.class); + try { + //noinspection unchecked + list.add(generateHugeValue(listType, sizeLimit + 1)); + } finally { + // FIXME This assertion fails now. Code will be fixed in master branch first. + assertEquals(sizeBeforeException, list.size()); + } + } + }); + } + + @Test + public void move_outOfBoundsLowerThrows() { + realm.beginTransaction(); + try { + list.move(0, -1); + fail("Indexes < 0 should throw an exception"); + } catch (IndexOutOfBoundsException ignored) { + } finally { + realm.cancelTransaction(); + } + } + + @Test + public void move_outOfBoundsHigherThrows() { + realm.beginTransaction(); + try { + list.move(list.size() - 1, list.size()); + fail("Indexes >= size() should throw an exception"); + } catch (IndexOutOfBoundsException ignored) { + ignored.printStackTrace(); + } finally { + realm.cancelTransaction(); + } + } + + @Test + public void clear_then_add() { + realm.beginTransaction(); + list.clear(); + + assertTrue(list.isEmpty()); + + //noinspection unchecked + list.add(generateValue(listType, -100)); + + realm.commitTransaction(); + + assertEquals(1, list.size()); + } + + @Test + public void size() { + assertEquals(isTypeNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE, list.size()); + } + + @Test + public void remove_nonNullByIndex() { + final int targetIndex = 6; + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final Object removed = list.remove(targetIndex); + final int dataIndex = isTypeNullable ? targetIndex / 2 : targetIndex; + assertValueEquals(generateValue(listType, dataIndex), removed); + } + }); + + assertEquals(isTypeNullable ? (NULLABLE_TEST_SIZE - 1) : (NON_NULL_TEST_SIZE - 1), list.size()); + for (int i = 0; i < list.size(); i++) { + final int originalIndex = i < targetIndex ? i : i + 1; + if (isTypeNullable) { + if (originalIndex % 2 == 1) { + assertNull(list.get(i)); + } else { + assertValueEquals(generateValue(listType, originalIndex / 2), list.get(i)); + } + } else { + assertValueEquals(generateValue(listType, originalIndex), list.get(i)); + } + } + } + + @Test + public void remove_nullByIndex() { + if (!isTypeNullable) { + return; + } + + final int targetIndex = 7; + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + assertNull(list.remove(targetIndex)); + assertEquals(NULLABLE_TEST_SIZE - 1, list.size()); + } + }); + + for (int i = 0; i < list.size(); i++) { + final int originalIndex = i < targetIndex ? i : i + 1; + if (originalIndex % 2 == 1) { + assertNull(list.get(i)); + } else { + assertValueEquals(generateValue(listType, originalIndex / 2), list.get(i)); + } + } + } + + @Test + public void remove_first() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final Object removed = list.remove(0); + assertValueEquals(generateValue(listType, 0), removed); + } + }); + + assertEquals((isTypeNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE) - 1, list.size()); + } + + @Test + public void remove_last() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + final Object removed = list.remove((isTypeNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE) - 1); + if (isTypeNullable) { + assertNull(removed); + } else { + assertValueEquals(generateValue(listType, NON_NULL_TEST_SIZE - 1), removed); + } + } + }); + + assertEquals((isTypeNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE) - 1, list.size()); + } + + @Test + public void remove_fromEmptyListThrows() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + list.clear(); + thrown.expect(IndexOutOfBoundsException.class); + list.remove(0); + } + }); + } + + @Test + public void remove_byObject() { + final Object value = list.get(0); + final int initialSize = list.size(); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + if (listType == BINARY_LIST) { + assertFalse(list.remove(value)); // since 'equals()' never return true against binary array. + } else { + assertTrue(list.remove(value)); + } + } + }); + + assertEquals((listType == BINARY_LIST) ? initialSize : (initialSize - 1), list.size()); + } + + @Test + public void remove_byNull() { + final int initialSize = list.size(); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + if (isTypeNullable) { + assertTrue(list.remove(null)); + } else { + assertFalse(list.remove(null)); + } + } + }); + + assertEquals(isTypeNullable ? (initialSize - 1) : initialSize, list.size()); + } + + @Test + public void deleteFirst() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + assertTrue(list.deleteFirstFromRealm()); + } + }); + + assertEquals(isTypeNullable ? (NULLABLE_TEST_SIZE - 1) : (NON_NULL_TEST_SIZE - 1), list.size()); + for (int i = 0; i < list.size(); i++) { + final int originalIndex = i + 1; + if (isTypeNullable) { + if (originalIndex % 2 == 1) { + assertNull(list.get(i)); + } else { + assertValueEquals(generateValue(listType, originalIndex / 2), list.get(i)); + } + } else { + assertValueEquals(generateValue(listType, originalIndex), list.get(i)); + } + } + } + + @Test + public void deleteLast() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + assertTrue(list.deleteLastFromRealm()); + } + }); + + assertEquals(isTypeNullable ? (NULLABLE_TEST_SIZE - 1) : (NON_NULL_TEST_SIZE - 1), list.size()); + for (int i = 0; i < list.size(); i++) { + if (isTypeNullable) { + if (i % 2 == 1) { + assertNull(list.get(i)); + } else { + assertValueEquals(generateValue(listType, i / 2), list.get(i)); + } + } else { + assertValueEquals(generateValue(listType, i), list.get(i)); + } + } + } + + @Test + public void addAt_afterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + //noinspection unchecked + list.add(generateValue(listType, 100)); + } + + @Test + public void addAt_invalidIndex() { + final int initialSize = list.size(); + try { + realm.beginTransaction(); + //noinspection unchecked + list.add(initialSize + 1, generateValue(listType, 1000)); + fail(); + } catch (IndexOutOfBoundsException e) { + // make sure that the size is not changed + assertEquals(initialSize, list.size()); + } + } + + @Test + public void set_afterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + //noinspection unchecked + list.set(0, generateValue(listType, 100)); + } + + @Test + public void set_invalidIndex() { + final int initialSize = list.size(); + try { + realm.beginTransaction(); + //noinspection unchecked + list.set(initialSize, generateValue(listType, 1000)); + fail(); + } catch (IndexOutOfBoundsException e) { + // make sure that the size is not changed + assertEquals(initialSize, list.size()); + } + } + + @Test + public void move_afterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + list.move(0, 1); + } + + @Test + public void clear_afterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + list.clear(); + } + + @Test + public void remove_atAfterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + list.remove(0); + } + + @Test + public void remove_objectAfterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + list.remove(generateValue(listType, 4)); + } + + @Test + public void remove_unsupportedTypeIgnored() { + final int initialSize = list.size(); + + final List unsupportedValues = Arrays.asList( + new int[] {0}, + new StringBuilder("0") + ); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + for (Object unsupportedValue : unsupportedValues) { + //noinspection UseBulkOperation + assertFalse(list.remove(unsupportedValue)); + } + } + }); + + assertEquals(initialSize, list.size()); + } + + @Test + public void removeAll() { + final List toBeRemoved = Arrays.asList( + null, + generateValue(listType, 2), + generateValue(listType, 4)); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + if (!isTypeNullable && listType == BINARY_LIST) { + //noinspection unchecked + assertFalse(list.removeAll(toBeRemoved)); // since 'equals()' never return true against binary array. + } else { + //noinspection unchecked + assertTrue(list.removeAll(toBeRemoved)); + } + } + }); + + switch (listType) { + case BINARY_LIST: + assertEquals(NON_NULL_TEST_SIZE, list.size()); + break; + case BOOLEAN_LIST: + assertEquals(NON_NULL_TEST_SIZE / 2, list.size()); + break; + default: + assertEquals(NON_NULL_TEST_SIZE - 2, list.size()); + break; + } + } + + @Test + public void removeAll_unsupportedTypeIgnored() { + final int initialSize = list.size(); + + final List unsupportedValues = Arrays.asList( + new int[] {0}, + new StringBuilder("0") + ); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + //noinspection unchecked + assertFalse(list.removeAll(unsupportedValues)); + } + }); + + assertEquals(initialSize, list.size()); + } + + @Test + public void removeAll_afterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + //noinspection unchecked + list.removeAll(Collections.emptyList()); + } + + @Test + public void get_afterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + list.get(0); + } + + @Test + public void first_afterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + list.first(); + } + + @Test + public void last_afterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + list.last(); + } + + @Test + public void size_afterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + + thrown.expect(IllegalStateException.class); + list.size(); + } + + @Test(expected = IllegalStateException.class) + public void where() { + list.where(); + } + + @Test + public void toString_() { + final StringBuilder sb = new StringBuilder("RealmList<").append(listType.getValueTypeName()).append(">@["); + final String separator = ","; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + final Object value = generateValue(listType, i); + + if (value instanceof byte[]) { + sb.append("byte[").append(((byte[]) value).length).append("]"); + } else { + sb.append(value); + } + sb.append(separator); + if (isTypeNullable) { + sb.append("null").append(separator); + } + } + sb.setLength(sb.length() - separator.length()); + sb.append("]"); + + assertEquals(sb.toString(), list.toString()); + } + + @Test + public void toString_AfterContainerObjectRemoved() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object.deleteFromRealm(); + } + }); + assertEquals("RealmList<" + listType.getValueTypeName() + ">@[invalid]", list.toString()); + } + + @Test + public void deleteAllFromRealm() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + list.deleteAllFromRealm(); + } + }); + + assertEquals(0, list.size()); + } + + @Test + public void deleteAllFromRealm_outsideTransaction() { + try { + list.deleteAllFromRealm(); + fail(); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().contains("Cannot modify managed objects outside of a write transaction")); + } + } + + @Test + public void deleteAllFromRealm_emptyList() { + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + list.deleteAllFromRealm(); + } + }); + assertEquals(0, list.size()); + + // The dogs is empty now. + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + list.deleteAllFromRealm(); + } + }); + assertEquals(0, list.size()); + } + + @Test + public void deleteAllFromRealm_invalidListShouldThrow() { + realm.close(); + realm = null; + + thrown.expect(IllegalStateException.class); + thrown.expectMessage(is("This Realm instance has already been closed, making it unusable.")); + list.deleteAllFromRealm(); + } + + @Test + public void add_null_nonNullableListThrows() { + if (isTypeNullable) { + return; + } + + realm.beginTransaction(); + final int initialSize = list.size(); + try { + thrown.expect(IllegalArgumentException.class); + //noinspection unchecked + list.add(null); + } finally { + assertEquals(initialSize, list.size()); + } + } + + @Test + public void addAt_null_nonNullableListThrows() { + if (isTypeNullable) { + return; + } + + realm.beginTransaction(); + final int initialSize = list.size(); + try { + thrown.expect(IllegalArgumentException.class); + //noinspection unchecked + list.add(1, null); + } finally { + assertEquals(initialSize, list.size()); + } + } + + @Test + public void set_null_nonNullableListThrows() { + if (isTypeNullable) { + return; + } + + realm.beginTransaction(); + final int initialSize = list.size(); + try { + thrown.expect(IllegalArgumentException.class); + //noinspection unchecked + list.set(0, null); + } finally { + assertEquals(initialSize, list.size()); + } + } + + @Test + @RunTestInLooperThread + public void changeListener_forAddObject() { + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + + // add 3 elements as an initial data + //noinspection unchecked + list.add(generateValue(listType, 0)); + //noinspection unchecked + list.add(generateValue(listType, 100)); + //noinspection unchecked + list.add(generateValue(listType, 200)); + } + }); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }); + //noinspection unchecked + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + assertNotNull(changes); + assertEquals(1, changes.getInsertions().length); + assertEquals(0, changes.getDeletions().length); + assertEquals(0, changes.getChanges().length); + assertEquals(3, changes.getInsertions()[0]); + assertEquals(1, listenerCalledCount.getAndIncrement()); + } + }); + + realm.beginTransaction(); + //noinspection unchecked + list.add(generateValue(listType, 100)); + realm.commitTransaction(); + + assertEquals(2, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void changeListener_forAddAt() { + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + + // add 3 elements as an initial data + //noinspection unchecked + list.add(generateValue(listType, 0)); + //noinspection unchecked + list.add(generateValue(listType, 100)); + //noinspection unchecked + list.add(generateValue(listType, 200)); + } + }); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }); + //noinspection unchecked + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + assertNotNull(changes); + assertEquals(1, changes.getInsertions().length); + assertEquals(0, changes.getDeletions().length); + assertEquals(0, changes.getChanges().length); + assertEquals(1, changes.getInsertions()[0]); + assertEquals(1, listenerCalledCount.getAndIncrement()); + } + }); + + realm.beginTransaction(); + //noinspection unchecked + list.add(1, generateValue(listType, 500)); + realm.commitTransaction(); + + assertEquals(2, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void changeListener_forSet() { + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + + // add 3 elements as an initial data + //noinspection unchecked + list.add(generateValue(listType, 0)); + //noinspection unchecked + list.add(generateValue(listType, 100)); + //noinspection unchecked + list.add(generateValue(listType, 200)); + } + }); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }); + //noinspection unchecked + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + assertNotNull(changes); + assertEquals(0, changes.getInsertions().length); + assertEquals(0, changes.getDeletions().length); + assertEquals(1, changes.getChanges().length); + assertEquals(1, changes.getChanges()[0]); + assertEquals(1, listenerCalledCount.getAndIncrement()); + } + }); + + realm.beginTransaction(); + //noinspection unchecked + list.set(1, generateValue(listType, 500)); + realm.commitTransaction(); + + assertEquals(2, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void changeListener_forRemoveAt() { + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + + // add 3 elements as an initial data + //noinspection unchecked + list.add(generateValue(listType, 0)); + //noinspection unchecked + list.add(generateValue(listType, 100)); + //noinspection unchecked + list.add(generateValue(listType, 200)); + } + }); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }); + //noinspection unchecked + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + assertNotNull(changes); + assertEquals(0, changes.getInsertions().length); + assertEquals(1, changes.getDeletions().length); + assertEquals(0, changes.getChanges().length); + assertEquals(1, changes.getDeletions()[0]); + assertEquals(1, listenerCalledCount.getAndIncrement()); + } + }); + + realm.beginTransaction(); + //noinspection unchecked + if (listType == BINARY_LIST) { + assertArrayEquals((byte[]) generateValue(listType, 100), (byte[]) list.remove(1)); + } else { + assertEquals(generateValue(listType, 100), list.remove(1)); + } + realm.commitTransaction(); + + assertEquals(2, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void changeListener_forRemoveObject() { + if (listType == BINARY_LIST) { + // 'removeAll()' never remove byte array element since 'equals()' never return true against byte array. + looperThread.testComplete(); + return; + } + + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + + // add 3 elements as an initial data + //noinspection unchecked + list.add(generateValue(listType, 0)); + //noinspection unchecked + list.add(generateValue(listType, 101)); + //noinspection unchecked + list.add(generateValue(listType, 200)); + } + }); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }); + //noinspection unchecked + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + assertNotNull(changes); + assertEquals(0, changes.getInsertions().length); + assertEquals(1, changes.getDeletions().length); + assertEquals(0, changes.getChanges().length); + assertEquals(1, changes.getDeletions()[0]); + assertEquals(1, listenerCalledCount.getAndIncrement()); + } + }); + + realm.beginTransaction(); + //noinspection unchecked + assertTrue(list.remove(generateValue(listType, 101))); + realm.commitTransaction(); + + assertEquals(2, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void changeListener_forRemoveAll() { + if (listType == BINARY_LIST) { + // 'removeAll()' never remove byte array element since 'equals()' never return true against byte array. + looperThread.testComplete(); + return; + } + + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + + // add 3 elements as an initial data + //noinspection unchecked + list.add(generateValue(listType, 0)); + //noinspection unchecked + list.add(generateValue(listType, 100)); + //noinspection unchecked + list.add(generateValue(listType, 200)); + } + }); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }); + //noinspection unchecked + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + assertNotNull(changes); + assertEquals(0, changes.getInsertions().length); + assertEquals(listType == BOOLEAN_LIST ? 3 : 2, changes.getDeletions().length); + assertEquals(0, changes.getChanges().length); + assertEquals(1, changes.getDeletionRanges().length); + assertEquals(listType == BOOLEAN_LIST ? 0 : 1, changes.getDeletionRanges()[0].startIndex); + assertEquals(listType == BOOLEAN_LIST ? 3 : 2, changes.getDeletionRanges()[0].length); + assertEquals(1, listenerCalledCount.getAndIncrement()); + } + }); + + realm.beginTransaction(); + //noinspection unchecked + + final boolean removed = list.removeAll(Arrays.asList(generateValue(listType, 100), generateValue(listType, 200), generateValue(listType, 300))); + assertTrue(removed); + realm.commitTransaction(); + + assertEquals(2, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void changeListener_forDeleteAt() { + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + + // add 3 elements as an initial data + //noinspection unchecked + list.add(generateValue(listType, 0)); + //noinspection unchecked + list.add(generateValue(listType, 100)); + //noinspection unchecked + list.add(generateValue(listType, 200)); + } + }); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }); + //noinspection unchecked + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + assertNotNull(changes); + assertEquals(0, changes.getInsertions().length); + assertEquals(1, changes.getDeletions().length); + assertEquals(0, changes.getChanges().length); + assertEquals(1, changes.getDeletions()[0]); + assertEquals(1, listenerCalledCount.getAndIncrement()); + } + }); + + realm.beginTransaction(); + //noinspection unchecked + list.deleteFromRealm(1); + realm.commitTransaction(); + + assertEquals(2, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void changeListener_forDeleteAll() { + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + + // add 3 elements as an initial data + //noinspection unchecked + list.add(generateValue(listType, 0)); + //noinspection unchecked + list.add(generateValue(listType, 100)); + //noinspection unchecked + list.add(generateValue(listType, 200)); + } + }); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + } + }); + //noinspection unchecked + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + assertNotNull(changes); + assertEquals(0, changes.getInsertions().length); + assertEquals(3, changes.getDeletions().length); + assertEquals(0, changes.getChanges().length); + assertEquals(1, changes.getDeletionRanges().length); + assertEquals(0, changes.getDeletionRanges()[0].startIndex); + assertEquals(3, changes.getDeletionRanges()[0].length); + assertEquals(1, listenerCalledCount.getAndIncrement()); + } + }); + + realm.beginTransaction(); + //noinspection unchecked + list.deleteAllFromRealm(); + realm.commitTransaction(); + + assertEquals(2, listenerCalledCount.get()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void removeAllChangeListeners() { + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + } + }); + + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + fail(); + } + }); + //noinspection unchecked + list.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + fail(); + } + }); + + list.removeAllChangeListeners(); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + // This one is added after removal, so it should be triggered. + //noinspection unchecked + list.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + listenerCalledCount.incrementAndGet(); + looperThread.testComplete(); + } + }); + + // This should trigger the listener if there is any. + realm.beginTransaction(); + //noinspection unchecked + list.add(generateValue(listType, 500)); + realm.commitTransaction(); + + assertEquals(1, listenerCalledCount.get()); + } + + @SuppressWarnings("unchecked") + @Test + @RunTestInLooperThread + public void removeChangeListener() { + Realm realm = looperThread.getRealm(); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + object = realm.createObject(NullTypes.class, 1000); + list = getListFor(object, listType, isTypeNullable); + } + }); + + final AtomicInteger listenerCalledCount = new AtomicInteger(0); + RealmChangeListener> listener1 = new RealmChangeListener>() { + @Override + public void onChange(RealmList element) { + fail(); + } + }; + OrderedRealmCollectionChangeListener> listener2 = + new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changes) { + assertEquals(0, listenerCalledCount.getAndIncrement()); + looperThread.testComplete(); + } + }; + + list.addChangeListener(listener1); + list.addChangeListener(listener2); + + list.removeChangeListener(listener1); + + // This should trigger the listener if there is any. + realm.beginTransaction(); + list.add(generateValue(listType, 500)); + realm.commitTransaction(); + assertEquals(1, listenerCalledCount.get()); + } + + @Test + public void createSnapshot() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage(is(RealmList.ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE)); + list.createSnapshot(); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValue_toArrayTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValue_toArrayTests.java new file mode 100644 index 0000000000..1d7dc8b403 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValue_toArrayTests.java @@ -0,0 +1,467 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import io.realm.ManagedRealmListForValueTests.ListType; +import io.realm.entities.NullTypes; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.TestRealmConfigurationFactory; + +import static io.realm.ManagedRealmListForValueTests.ListType.BINARY_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.BOOLEAN_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.BYTE_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.DATE_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.DOUBLE_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.FLOAT_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.INTEGER_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.LONG_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.SHORT_LIST; +import static io.realm.ManagedRealmListForValueTests.ListType.STRING_LIST; +import static io.realm.ManagedRealmListForValueTests.NON_NULL_TEST_SIZE; +import static io.realm.ManagedRealmListForValueTests.NULLABLE_TEST_SIZE; +import static io.realm.ManagedRealmListForValueTests.generateValue; +import static io.realm.ManagedRealmListForValueTests.getListFor; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + + +/** + * Unit tests specific for RealmList with value elements. + */ +@RunWith(Parameterized.class) +public class ManagedRealmListForValue_toArrayTests extends CollectionTests { + + @Parameterized.Parameters(name = "{index}: Type: {0}, Nullable?: {1}") + public static Collection parameters() { + final List paramsList = new ArrayList<>(); + for (ListType listType : ListType.values()) { + paramsList.add(new Object[] {listType, Boolean.TRUE}); + paramsList.add(new Object[] {listType, Boolean.FALSE}); + } + return paramsList; + } + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + @Parameterized.Parameter + public ListType listType; + + @Parameterized.Parameter(1) + public Boolean typeIsNullable; + + private Realm realm; + private RealmList list; + + @SuppressWarnings("unchecked") + @Before + public void setUp() throws Exception { + final RealmConfiguration.Builder configurationBuilder = configFactory.createConfigurationBuilder(); + configurationBuilder.schema(NullTypes.class); + RealmConfiguration realmConfig = configurationBuilder.build(); + + realm = Realm.getInstance(realmConfig); + + realm.beginTransaction(); + final NullTypes object = realm.createObject(NullTypes.class, 0); + for (ListType type : ListType.values()) { + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + switch (type) { + case STRING_LIST: { + final RealmList nonnull = object.getFieldStringListNotNull(); + nonnull.add(generateValue(STRING_LIST, i)); + final RealmList nullable = object.getFieldStringListNull(); + nullable.add("" + i); + nullable.add(null); + } + break; + case BOOLEAN_LIST: { + final RealmList nonnull = object.getFieldBooleanListNotNull(); + nonnull.add(generateValue(BOOLEAN_LIST, i)); + final RealmList nullable = object.getFieldBooleanListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case BINARY_LIST: { + final RealmList nonnull = object.getFieldBinaryListNotNull(); + nonnull.add(generateValue(BINARY_LIST, i)); + final RealmList nullable = object.getFieldBinaryListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case LONG_LIST: { + final RealmList nonnull = object.getFieldLongListNotNull(); + nonnull.add(generateValue(LONG_LIST, i)); + final RealmList nullable = object.getFieldLongListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case INTEGER_LIST: { + final RealmList nonnull = object.getFieldIntegerListNotNull(); + nonnull.add(generateValue(INTEGER_LIST, i)); + final RealmList nullable = object.getFieldIntegerListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case SHORT_LIST: { + final RealmList nonnull = object.getFieldShortListNotNull(); + nonnull.add(generateValue(SHORT_LIST, i)); + final RealmList nullable = object.getFieldShortListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case BYTE_LIST: { + final RealmList nonnull = object.getFieldByteListNotNull(); + nonnull.add(generateValue(BYTE_LIST, i)); + final RealmList nullable = object.getFieldByteListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case DOUBLE_LIST: { + final RealmList nonnull = object.getFieldDoubleListNotNull(); + nonnull.add(generateValue(DOUBLE_LIST, i)); + final RealmList nullable = object.getFieldDoubleListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case FLOAT_LIST: { + final RealmList nonnull = object.getFieldFloatListNotNull(); + nonnull.add(generateValue(FLOAT_LIST, i)); + final RealmList nullable = object.getFieldFloatListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + case DATE_LIST: { + final RealmList nonnull = object.getFieldDateListNotNull(); + nonnull.add(generateValue(DATE_LIST, i)); + final RealmList nullable = object.getFieldDateListNull(); + nullable.add(nonnull.last()); + nullable.add(null); + } + break; + default: + throw new AssertionError("unexpected value type: " + listType.name()); + } + } + } + realm.commitTransaction(); + + list = getListFor(object, listType, typeIsNullable); + } + + @After + public void tearDown() throws Exception { + if (realm != null) { + realm.close(); + } + } + + @Test + public void toArray() { + final Object[] expected = new Object[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = generateValue(listType, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = generateValue(listType, i); + } + } + + if (listType != BINARY_LIST) { + assertArrayEquals(expected, list.toArray()); + } else { + final Object[] array = list.toArray(); + assertEquals(expected.length, array.length); + for (int i = 0; i < expected.length; i++) { + if (expected[i] == null) { + assertNull(array[i]); + } else { + assertTrue(array[i] instanceof byte[]); + assertArrayEquals((byte[]) expected[i], (byte[]) array[i]); + } + } + } + } + + @Test + public void toArray_withStringArray() { + if (listType != STRING_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new String[0]); + // should not reach here + return; + } + + final String[] expected = new String[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (String) generateValue(STRING_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (String) generateValue(STRING_LIST, i); + } + } + final Object[] returnedArray = list.toArray(new String[0]); + assertEquals(String.class, returnedArray.getClass().getComponentType()); + assertArrayEquals(expected, returnedArray); + } + + @Test + public void toArray_withBooleanArray() { + if (listType != BOOLEAN_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new Boolean[0]); + // should not reach here + return; + } + + final Boolean[] expected = new Boolean[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (Boolean) generateValue(BOOLEAN_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (Boolean) generateValue(BOOLEAN_LIST, i); + } + } + + final Object[] returnedArray = list.toArray(new Boolean[0]); + assertEquals(Boolean.class, returnedArray.getClass().getComponentType()); + assertArrayEquals(expected, returnedArray); + } + + @Test + public void toArray_withBinaryArray() { + if (listType != BINARY_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new byte[0][]); + // should not reach here + return; + } + + final byte[][] expected = new byte[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE][]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (byte[]) generateValue(BINARY_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (byte[]) generateValue(BINARY_LIST, i); + } + } + final Object[] returnedArray = list.toArray(new byte[0][]); + assertEquals(byte[].class, returnedArray.getClass().getComponentType()); + + assertEquals(expected.length, returnedArray.length); + for (int i = 0; i < expected.length; i++) { + if (expected[i] == null) { + assertNull(returnedArray[i]); + } else { + assertTrue(returnedArray[i] instanceof byte[]); + assertArrayEquals(expected[i], (byte[]) returnedArray[i]); + } + } + } + + @Test + public void toArray_withLongArray() { + if (listType != LONG_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new Long[0]); + // should not reach here + return; + } + + final Long[] expected = new Long[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (Long) generateValue(LONG_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (Long) generateValue(LONG_LIST, i); + } + } + final Object[] returnedArray = list.toArray(new Long[0]); + assertEquals(Long.class, returnedArray.getClass().getComponentType()); + assertArrayEquals(expected, returnedArray); + } + + @Test + public void toArray_withIntegerArray() { + if (listType != INTEGER_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new Integer[0]); + // should not reach here + return; + } + + final Integer[] expected = new Integer[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (Integer) generateValue(INTEGER_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (Integer) generateValue(INTEGER_LIST, i); + } + } + final Object[] returnedArray = list.toArray(new Integer[0]); + assertEquals(Integer.class, returnedArray.getClass().getComponentType()); + assertArrayEquals(expected, returnedArray); + } + + @Test + public void toArray_withShortArray() { + if (listType != SHORT_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new Short[0]); + // should not reach here + return; + } + + final Short[] expected = new Short[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (Short) generateValue(SHORT_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (Short) generateValue(SHORT_LIST, i); + } + } + final Object[] returnedArray = list.toArray(new Short[0]); + assertEquals(Short.class, returnedArray.getClass().getComponentType()); + assertArrayEquals(expected, returnedArray); + } + + @Test + public void toArray_withByteArray() { + if (listType != BYTE_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new Byte[0]); + // should not reach here + return; + } + + final Byte[] expected = new Byte[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (Byte) generateValue(BYTE_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (Byte) generateValue(BYTE_LIST, i); + } + } + final Object[] returnedArray = list.toArray(new Byte[0]); + assertEquals(Byte.class, returnedArray.getClass().getComponentType()); + assertArrayEquals(expected, returnedArray); + } + + @Test + public void toArray_withDoubleArray() { + if (listType != DOUBLE_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new Double[0]); + // should not reach here + return; + } + + final Double[] expected = new Double[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (Double) generateValue(DOUBLE_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (Double) generateValue(DOUBLE_LIST, i); + } + } + final Object[] returnedArray = list.toArray(new Double[0]); + assertEquals(Double.class, returnedArray.getClass().getComponentType()); + assertArrayEquals(expected, returnedArray); + } + + @Test + public void toArray_withFloatArray() { + if (listType != FLOAT_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new Float[0]); + // should not reach here + return; + } + + final Float[] expected = new Float[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (Float) generateValue(FLOAT_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (Float) generateValue(FLOAT_LIST, i); + } + } + final Object[] returnedArray = list.toArray(new Float[0]); + assertEquals(Float.class, returnedArray.getClass().getComponentType()); + assertArrayEquals(expected, returnedArray); + } + + @Test + public void toArray_withDateArray() { + if (listType != DATE_LIST) { + thrown.expect(ArrayStoreException.class); + list.toArray(new Date[0]); + // should not reach here + return; + } + + final Date[] expected = new Date[typeIsNullable ? NULLABLE_TEST_SIZE : NON_NULL_TEST_SIZE]; + for (int i = 0; i < NON_NULL_TEST_SIZE; i++) { + if (typeIsNullable) { + expected[i * 2] = (Date) generateValue(DATE_LIST, i); + expected[i * 2 + 1] = null; + } else { + expected[i] = (Date) generateValue(DATE_LIST, i); + } + } + final Object[] returnedArray = list.toArray(new Date[0]); + assertEquals(Date.class, returnedArray.getClass().getComponentType()); + assertArrayEquals(expected, returnedArray); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index 534730bb58..d7e705a0fd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -642,16 +642,21 @@ public void listIterator_unsupportedMethods() { fail(); } catch (UnsupportedOperationException e) { assertResultsOrSnapshot(); - } catch (IllegalStateException e) { + } catch (IllegalStateException e) { // since next() was never called. assertRealmList(); } try { it.add(null); - fail(); + if (collectionClass != CollectionClass.UNMANAGED_REALMLIST) { + fail(); + } } catch (UnsupportedOperationException e) { assertResultsOrSnapshot(); } catch (IllegalArgumentException e) { + if (collectionClass == CollectionClass.UNMANAGED_REALMLIST) { + fail(); + } assertRealmList(); } @@ -660,7 +665,7 @@ public void listIterator_unsupportedMethods() { fail(); } catch (UnsupportedOperationException e) { assertResultsOrSnapshot(); - } catch (IllegalStateException e) { + } catch (IllegalStateException e) { // since the collection is empty assertRealmList(); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java index 9811beed32..a2d025dfb7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java @@ -65,6 +65,16 @@ public abstract class QueryTests { list.remove(RealmFieldType.UNSUPPORTED_MIXED); list.remove(RealmFieldType.UNSUPPORTED_TABLE); list.remove(RealmFieldType.UNSUPPORTED_DATE); + + // FIXME zaki50 revisit once we implement query for Primitive List + list.remove(RealmFieldType.STRING_LIST); + list.remove(RealmFieldType.BINARY_LIST); + list.remove(RealmFieldType.BOOLEAN_LIST); + list.remove(RealmFieldType.INTEGER_LIST); + list.remove(RealmFieldType.DOUBLE_LIST); + list.remove(RealmFieldType.FLOAT_LIST); + list.remove(RealmFieldType.DATE_LIST); + NOT_SUPPORTED_IS_EMPTY_TYPES = Collections.unmodifiableList(list); NOT_SUPPORTED_IS_NOT_EMPTY_TYPES = Collections.unmodifiableList(list); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 3197a6b663..633cbe6e64 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -715,42 +715,42 @@ public Flowable from(DynamicRealm realm) { } @Override - public Flowable> from(Realm realm, RealmResults results) { + public Flowable> from(Realm realm, RealmResults results) { return null; } @Override - public Observable>> changesetsFrom(Realm realm, RealmResults results) { + public Observable>> changesetsFrom(Realm realm, RealmResults results) { return null; } @Override - public Flowable> from(DynamicRealm realm, RealmResults results) { + public Flowable> from(DynamicRealm realm, RealmResults results) { return null; } @Override - public Observable>> changesetsFrom(DynamicRealm realm, RealmResults results) { + public Observable>> changesetsFrom(DynamicRealm realm, RealmResults results) { return null; } @Override - public Flowable> from(Realm realm, RealmList list) { + public Flowable> from(Realm realm, RealmList list) { return null; } @Override - public Observable>> changesetsFrom(Realm realm, RealmList list) { + public Observable>> changesetsFrom(Realm realm, RealmList list) { return null; } @Override - public Flowable> from(DynamicRealm realm, RealmList list) { + public Flowable> from(DynamicRealm realm, RealmList list) { return null; } @Override - public Observable>> changesetsFrom(DynamicRealm realm, RealmList list) { + public Observable>> changesetsFrom(DynamicRealm realm, RealmList list) { return null; } @@ -775,12 +775,12 @@ public Observable> changesetsFrom(DynamicRealm } @Override - public Single> from(Realm realm, RealmQuery query) { + public Single> from(Realm realm, RealmQuery query) { return null; } @Override - public Single> from(DynamicRealm realm, RealmQuery query) { + public Single> from(DynamicRealm realm, RealmQuery query) { return null; } }; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 7cfc9f674d..6547300fed 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -141,9 +141,11 @@ public void add_unmanagedMode() { assertEquals(object, list.get(0)); } - @Test (expected = IllegalArgumentException.class) + @Test public void add_nullInUnmanagedMode() { - new RealmList().add(null); + final RealmList list = new RealmList<>(); + assertTrue(list.add(null)); + assertEquals(1, list.size()); } @Test @@ -189,9 +191,25 @@ public void add_objectAtIndexInManagedMode() { assertEquals("Dog 42", collection.get(0).getName()); } - @Test (expected = IllegalArgumentException.class) + @Test + public void add_objectAtInvalidIndexInManagedModeThrows() { + final int initialDogCount = realm.where(Dog.class).findAll().size(); + + realm.beginTransaction(); + try { + final int invalidIndex = collection.size() + 1; + collection.add(invalidIndex, new Dog("Dog 42")); + fail(); + } catch (IndexOutOfBoundsException e) { + assertEquals(initialDogCount, realm.where(Dog.class).findAll().size()); + } + } + + @Test public void add_nullAtIndexInUnmanagedMode() { - new RealmList().add(0, null); + final RealmList list = new RealmList<>(); + list.add(0, null); + assertEquals(1, list.size()); } @Test @@ -223,11 +241,9 @@ public void set_managedMode() { @Test public void set_nullInUnmanagedMode() { - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - RealmList list = new RealmList(); + RealmList list = new RealmList<>(); list.add(new AllTypes()); - thrown.expect(IllegalArgumentException.class); - list.set(0, null); + assertNotNull(list.set(0, null)); } @Test @@ -684,12 +700,12 @@ public void where_afterContainerObjectRemoved() { @Test public void toString_AfterContainerObjectRemoved() { RealmList dogs = createDeletedRealmList(); - assertEquals("Dog@[invalid]", dogs.toString()); + assertEquals("RealmList@[invalid]", dogs.toString()); } @Test public void toString_managedMode() { - StringBuilder sb = new StringBuilder("Dog@["); + StringBuilder sb = new StringBuilder("RealmList@["); for (int i = 0; i < collection.size() - 1; i++) { sb.append(((RealmObjectProxy) (collection.get(i))).realmGet$proxyState().getRow$realm().getIndex()); sb.append(","); @@ -894,6 +910,7 @@ public void add_set_withWrongDynamicObjectType() { dynamicRealm.beginTransaction(); RealmList list = dynamicRealm.createObject(Owner.CLASS_NAME) .getList(Owner.FIELD_DOGS); + list.add(dynamicRealm.createObject(Dog.CLASS_NAME)); DynamicRealmObject dynCat = dynamicRealm.createObject(Cat.CLASS_NAME); try { @@ -934,6 +951,7 @@ public void add_set_dynamicObjectCreatedFromTypedRealm() { dynamicRealm.beginTransaction(); RealmList list = dynamicRealm.createObject(Owner.CLASS_NAME) .getList(Owner.FIELD_DOGS); + list.add(dynamicRealm.createObject(Dog.CLASS_NAME)); try { list.add(dynDog); @@ -1084,7 +1102,7 @@ public void createSnapshot_shouldUseTargetTable() { realm.commitTransaction(); assertEquals(sizeBefore - 1, collection.size()); - assertNotNull(collection.osList); - assertEquals(collection.osList.getTargetTable().getName(), snapshot.getTable().getName()); + assertNotNull(collection.getOsList()); + assertEquals(collection.getOsList().getTargetTable().getName(), snapshot.getTable().getName()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 258b29483a..be4bc94e1f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -121,7 +121,7 @@ public void row_isValid() { realm.commitTransaction(); assertNotNull("RealmObject.realmGetRow returns zero ", row); - assertEquals(10, row.getColumnCount()); + assertEquals(17, row.getColumnCount()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index 52442d9351..7c12ddba71 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -26,14 +26,18 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import io.realm.entities.AllJavaTypes; import io.realm.entities.Cat; import io.realm.entities.Dog; import io.realm.entities.DogPrimaryKey; +import io.realm.entities.NullTypes; import io.realm.entities.Owner; import io.realm.entities.PrimaryKeyAsString; import io.realm.internal.Table; @@ -85,7 +89,7 @@ public RealmSchemaTests(SchemaType type) { public void setUp() { RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .schema(AllJavaTypes.class, Owner.class, PrimaryKeyAsString.class, Cat.class, Dog.class, - DogPrimaryKey.class) + DogPrimaryKey.class, NullTypes.class) .build(); Realm.getInstance(realmConfig).close(); // create Schema if (type == SchemaType.MUTABLE) { @@ -105,16 +109,18 @@ public void tearDown() { @Test public void getAll() { Set objectSchemas = realmSchema.getAll(); - assertEquals(6, objectSchemas.size()); + assertEquals(7, objectSchemas.size()); - List expectedTables = Arrays.asList( - AllJavaTypes.CLASS_NAME, "Owner", "Cat", "Dog", "DogPrimaryKey", "PrimaryKeyAsString"); + List expectedTables = new ArrayList<>(Arrays.asList( + AllJavaTypes.CLASS_NAME, "Owner", "Cat", "Dog", "DogPrimaryKey", "PrimaryKeyAsString", NullTypes.CLASS_NAME)); for (RealmObjectSchema objectSchema : objectSchemas) { assertThat(objectSchema, CoreMatchers.instanceOf(type.objectSchemaClass)); - if (!expectedTables.contains(objectSchema.getClassName())) { - fail(objectSchema.getClassName() + " was not found"); + if (!expectedTables.remove(objectSchema.getClassName())) { + fail(objectSchema.getClassName() + " is not expected"); } } + assertTrue("expected class is not contained in schema: " + (expectedTables.isEmpty() ? "" : expectedTables.get(0)), + expectedTables.isEmpty()); } @Test @@ -561,6 +567,46 @@ public void rename_shouldUpdateDynamicCache() { assertEquals("bar", bar.getClassName()); } + @Test + public void schemaInformationOfPrimitiveLists() { + Map fieldNameToType = new HashMap<>(); + fieldNameToType.put(NullTypes.FIELD_STRING_LIST_NULL, RealmFieldType.STRING_LIST); + fieldNameToType.put(NullTypes.FIELD_STRING_LIST_NOT_NULL, RealmFieldType.STRING_LIST); + fieldNameToType.put(NullTypes.FIELD_BINARY_LIST_NULL, RealmFieldType.BINARY_LIST); + fieldNameToType.put(NullTypes.FIELD_BINARY_LIST_NOT_NULL, RealmFieldType.BINARY_LIST); + fieldNameToType.put(NullTypes.FIELD_BOOLEAN_LIST_NULL, RealmFieldType.BOOLEAN_LIST); + fieldNameToType.put(NullTypes.FIELD_BOOLEAN_LIST_NOT_NULL, RealmFieldType.BOOLEAN_LIST); + fieldNameToType.put(NullTypes.FIELD_DATE_LIST_NULL, RealmFieldType.DATE_LIST); + fieldNameToType.put(NullTypes.FIELD_DATE_LIST_NOT_NULL, RealmFieldType.DATE_LIST); + fieldNameToType.put(NullTypes.FIELD_DOUBLE_LIST_NULL, RealmFieldType.DOUBLE_LIST); + fieldNameToType.put(NullTypes.FIELD_DOUBLE_LIST_NOT_NULL, RealmFieldType.DOUBLE_LIST); + fieldNameToType.put(NullTypes.FIELD_FLOAT_LIST_NULL, RealmFieldType.FLOAT_LIST); + fieldNameToType.put(NullTypes.FIELD_FLOAT_LIST_NOT_NULL, RealmFieldType.FLOAT_LIST); + fieldNameToType.put(NullTypes.FIELD_LONG_LIST_NULL, RealmFieldType.INTEGER_LIST); + fieldNameToType.put(NullTypes.FIELD_LONG_LIST_NOT_NULL, RealmFieldType.INTEGER_LIST); + fieldNameToType.put(NullTypes.FIELD_INTEGER_LIST_NULL, RealmFieldType.INTEGER_LIST); + fieldNameToType.put(NullTypes.FIELD_INTEGER_LIST_NOT_NULL, RealmFieldType.INTEGER_LIST); + fieldNameToType.put(NullTypes.FIELD_SHORT_LIST_NULL, RealmFieldType.INTEGER_LIST); + fieldNameToType.put(NullTypes.FIELD_SHORT_LIST_NOT_NULL, RealmFieldType.INTEGER_LIST); + fieldNameToType.put(NullTypes.FIELD_BYTE_LIST_NULL, RealmFieldType.INTEGER_LIST); + fieldNameToType.put(NullTypes.FIELD_BYTE_LIST_NOT_NULL, RealmFieldType.INTEGER_LIST); + + final RealmObjectSchema objectSchema = realmSchema.get(NullTypes.CLASS_NAME); + assertNotNull(objectSchema); + + for (Map.Entry entry : fieldNameToType.entrySet()) { + final String fieldName = entry.getKey(); + final RealmFieldType expectedType = entry.getValue(); + + assertEquals(expectedType, objectSchema.getFieldType(fieldName)); + assertEquals("isNullable('" + fieldName + "')", + !fieldName.endsWith("NotNull"), objectSchema.isNullable(fieldName)); + assertEquals("isRequired('" + fieldName + "')", + fieldName.endsWith("NotNull"), objectSchema.isRequired(fieldName)); + assertFalse(objectSchema.isPrimaryKey(fieldName)); + } + } + @Test public void rename_newNameExists() { if (type == SchemaType.IMMUTABLE) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 6485e6ed5c..37d51b8bcd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -64,8 +64,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.Nullable; - import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; @@ -120,7 +118,6 @@ import static io.realm.internal.test.ExtraTests.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; @@ -132,7 +129,6 @@ import static org.mockito.Mockito.when; - @RunWith(AndroidJUnit4.class) public class RealmTests { private final static int TEST_DATA_SIZE = 10; @@ -1304,6 +1300,14 @@ public void copyToRealm() { allTypes.setColumnRealmObject(dog); allTypes.setColumnRealmList(list); + allTypes.setColumnStringList(new RealmList("1")); + allTypes.setColumnBinaryList(new RealmList(new byte[] {1})); + allTypes.setColumnBooleanList(new RealmList(true)); + allTypes.setColumnLongList(new RealmList(1L)); + allTypes.setColumnDoubleList(new RealmList(1D)); + allTypes.setColumnFloatList(new RealmList(1F)); + allTypes.setColumnDateList(new RealmList(new Date(1L))); + realm.beginTransaction(); AllTypes realmTypes = realm.copyToRealm(allTypes); realm.commitTransaction(); @@ -1318,7 +1322,22 @@ public void copyToRealm() { assertArrayEquals(allTypes.getColumnBinary(), realmTypes.getColumnBinary()); assertEquals(allTypes.getColumnRealmObject().getName(), dog.getName()); assertEquals(list.size(), realmTypes.getColumnRealmList().size()); + //noinspection ConstantConditions assertEquals(list.get(0).getName(), realmTypes.getColumnRealmList().get(0).getName()); + assertEquals(1, realmTypes.getColumnStringList().size()); + assertEquals("1", realmTypes.getColumnStringList().get(0)); + assertEquals(1, realmTypes.getColumnBooleanList().size()); + assertEquals(true, realmTypes.getColumnBooleanList().get(0)); + assertEquals(1, realmTypes.getColumnBinaryList().size()); + assertArrayEquals(new byte[] {1}, realmTypes.getColumnBinaryList().get(0)); + assertEquals(1, realmTypes.getColumnLongList().size()); + assertEquals((Long) 1L, realmTypes.getColumnLongList().get(0)); + assertEquals(1, realmTypes.getColumnDoubleList().size()); + assertEquals((Double) 1D, realmTypes.getColumnDoubleList().get(0)); + assertEquals(1, realmTypes.getColumnFloatList().size()); + assertEquals((Float) 1F, realmTypes.getColumnFloatList().get(0)); + assertEquals(1, realmTypes.getColumnDateList().size()); + assertEquals(new Date(1), realmTypes.getColumnDateList().get(0)); } @Test @@ -1393,8 +1412,8 @@ public void copyToRealm_cyclicListReferences() { oneCyclicType.setName("One"); CyclicType anotherCyclicType = new CyclicType(); anotherCyclicType.setName("Two"); - oneCyclicType.setObjects(new RealmList(anotherCyclicType)); - anotherCyclicType.setObjects(new RealmList(oneCyclicType)); + oneCyclicType.setObjects(new RealmList<>(anotherCyclicType)); + anotherCyclicType.setObjects(new RealmList<>(oneCyclicType)); realm.beginTransaction(); CyclicType realmObject = realm.copyToRealm(oneCyclicType); @@ -1414,6 +1433,15 @@ public void copyToRealm_convertsNullToDefaultValue() { assertEquals("", realmTypes.getColumnString()); assertEquals(new Date(0), realmTypes.getColumnDate()); assertArrayEquals(new byte[0], realmTypes.getColumnBinary()); + + assertNotNull(realmTypes.getColumnRealmList()); + assertNotNull(realmTypes.getColumnStringList()); + assertNotNull(realmTypes.getColumnBinaryList()); + assertNotNull(realmTypes.getColumnBooleanList()); + assertNotNull(realmTypes.getColumnLongList()); + assertNotNull(realmTypes.getColumnDoubleList()); + assertNotNull(realmTypes.getColumnFloatList()); + assertNotNull(realmTypes.getColumnDateList()); } // Check that using copyToRealm will set the primary key directly instead of first setting @@ -1713,6 +1741,13 @@ public void execute(Realm realm) { obj.setColumnRealmObject(new DogPrimaryKey(1, "Dog1")); obj.setColumnRealmList(new RealmList(new DogPrimaryKey(2, "Dog2"))); obj.setColumnBoxedBoolean(true); + obj.setColumnStringList(new RealmList<>("1")); + obj.setColumnBooleanList(new RealmList<>(false)); + obj.setColumnBinaryList(new RealmList<>(new byte[] {1})); + obj.setColumnLongList(new RealmList<>(1L)); + obj.setColumnDoubleList(new RealmList<>(1D)); + obj.setColumnFloatList(new RealmList<>(1F)); + obj.setColumnDateList(new RealmList<>(new Date(1L))); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); @@ -1726,6 +1761,13 @@ public void execute(Realm realm) { obj2.setColumnRealmObject(new DogPrimaryKey(3, "Dog3")); obj2.setColumnRealmList(new RealmList(new DogPrimaryKey(4, "Dog4"))); obj2.setColumnBoxedBoolean(false); + obj2.setColumnStringList(new RealmList<>("2", "3")); + obj2.setColumnBooleanList(new RealmList<>(true, false)); + obj2.setColumnBinaryList(new RealmList<>(new byte[] {2}, new byte[] {3})); + obj2.setColumnLongList(new RealmList<>(2L, 3L)); + obj2.setColumnDoubleList(new RealmList<>(2D, 3D)); + obj2.setColumnFloatList(new RealmList<>(2F, 3F)); + obj2.setColumnDateList(new RealmList<>(new Date(2L), new Date(3L))); realm.copyToRealmOrUpdate(obj2); } }); @@ -1745,6 +1787,27 @@ public void execute(Realm realm) { assertEquals(1, obj.getColumnRealmList().size()); assertEquals("Dog4", obj.getColumnRealmList().get(0).getName()); assertFalse(obj.getColumnBoxedBoolean()); + assertEquals(2, obj.getColumnStringList().size()); + assertEquals("2", obj.getColumnStringList().get(0)); + assertEquals("3", obj.getColumnStringList().get(1)); + assertEquals(2, obj.getColumnBooleanList().size()); + assertEquals(true, obj.getColumnBooleanList().get(0)); + assertEquals(false, obj.getColumnBooleanList().get(1)); + assertEquals(2, obj.getColumnBinaryList().size()); + assertArrayEquals(new byte[] {2}, obj.getColumnBinaryList().get(0)); + assertArrayEquals(new byte[] {3}, obj.getColumnBinaryList().get(1)); + assertEquals(2, obj.getColumnLongList().size()); + assertEquals((Long) 2L, obj.getColumnLongList().get(0)); + assertEquals((Long) 3L, obj.getColumnLongList().get(1)); + assertEquals(2, obj.getColumnDoubleList().size()); + assertEquals((Double) 2D, obj.getColumnDoubleList().get(0)); + assertEquals((Double) 3D, obj.getColumnDoubleList().get(1)); + assertEquals(2, obj.getColumnFloatList().size()); + assertEquals((Float) 2F, obj.getColumnFloatList().get(0)); + assertEquals((Float) 3F, obj.getColumnFloatList().get(1)); + assertEquals(2, obj.getColumnDateList().size()); + assertEquals(new Date(2L), obj.getColumnDateList().get(0)); + assertEquals(new Date(3L), obj.getColumnDateList().get(1)); } @Test @@ -2764,6 +2827,17 @@ public void copyToRealm_defaultValuesAreIgnored() { list.add(listItem); obj.setFieldList(list); + obj.setFieldStringList(new RealmList<>("2", "3")); + obj.setFieldBooleanList(new RealmList<>(true, false)); + obj.setFieldBinaryList(new RealmList<>(new byte[] {2}, new byte[] {3})); + obj.setFieldLongList(new RealmList<>(2L, 3L)); + obj.setFieldIntegerList(new RealmList<>(2, 3)); + obj.setFieldShortList(new RealmList<>((short) 2, (short) 3)); + obj.setFieldByteList(new RealmList<>((byte) 2, (byte) 3)); + obj.setFieldDoubleList(new RealmList<>(2D, 3D)); + obj.setFieldFloatList(new RealmList<>(2F, 3F)); + obj.setFieldDateList(new RealmList<>(new Date(2L), new Date(3L))); + managedObj = realm.copyToRealm(obj); } realm.commitTransaction(); @@ -2781,11 +2855,42 @@ public void copyToRealm_defaultValuesAreIgnored() { assertEquals(fieldDoubleValue, managedObj.getFieldDouble(), 0D); assertEquals(fieldBooleanValue, managedObj.isFieldBoolean()); assertEquals(fieldDateValue, managedObj.getFieldDate()); - assertTrue(Arrays.equals(fieldBinaryValue, managedObj.getFieldBinary())); + assertArrayEquals(fieldBinaryValue, managedObj.getFieldBinary()); assertEquals(fieldObjectIntValue, managedObj.getFieldObject().getFieldInt()); assertEquals(1, managedObj.getFieldList().size()); assertEquals(fieldListIntValue, managedObj.getFieldList().first().getFieldInt()); + assertEquals(2, managedObj.getFieldStringList().size()); + assertEquals("2", managedObj.getFieldStringList().get(0)); + assertEquals("3", managedObj.getFieldStringList().get(1)); + assertEquals(2, managedObj.getFieldBooleanList().size()); + assertEquals(true, managedObj.getFieldBooleanList().get(0)); + assertEquals(false, managedObj.getFieldBooleanList().get(1)); + assertEquals(2, managedObj.getFieldBinaryList().size()); + assertArrayEquals(new byte[] {2}, managedObj.getFieldBinaryList().get(0)); + assertArrayEquals(new byte[] {3}, managedObj.getFieldBinaryList().get(1)); + assertEquals(2, managedObj.getFieldLongList().size()); + assertEquals((Long) 2L, managedObj.getFieldLongList().get(0)); + assertEquals((Long) 3L, managedObj.getFieldLongList().get(1)); + assertEquals(2, managedObj.getFieldIntegerList().size()); + assertEquals((Integer) 2, managedObj.getFieldIntegerList().get(0)); + assertEquals((Integer) 3, managedObj.getFieldIntegerList().get(1)); + assertEquals(2, managedObj.getFieldShortList().size()); + assertEquals((Short) (short) 2, managedObj.getFieldShortList().get(0)); + assertEquals((Short) (short) 3, managedObj.getFieldShortList().get(1)); + assertEquals(2, managedObj.getFieldByteList().size()); + assertEquals((Byte) (byte) 2, managedObj.getFieldByteList().get(0)); + assertEquals((Byte) (byte) 3, managedObj.getFieldByteList().get(1)); + assertEquals(2, managedObj.getFieldDoubleList().size()); + assertEquals((Double) 2D, managedObj.getFieldDoubleList().get(0)); + assertEquals((Double) 3D, managedObj.getFieldDoubleList().get(1)); + assertEquals(2, managedObj.getFieldFloatList().size()); + assertEquals((Float) 2F, managedObj.getFieldFloatList().get(0)); + assertEquals((Float) 3F, managedObj.getFieldFloatList().get(1)); + assertEquals(2, managedObj.getFieldDateList().size()); + assertEquals(new Date(2L), managedObj.getFieldDateList().get(0)); + assertEquals(new Date(3L), managedObj.getFieldDateList().get(1)); + // Makes sure that excess object by default value is not created. assertEquals(2, realm.where(RandomPrimaryKey.class).count()); } @@ -2820,6 +2925,17 @@ public void copyFromRealm_defaultValuesAreIgnored() { list.add(listItem); obj.setFieldList(list); + obj.setFieldStringList(new RealmList<>("2", "3")); + obj.setFieldBooleanList(new RealmList<>(true, false)); + obj.setFieldBinaryList(new RealmList<>(new byte[] {2}, new byte[] {3})); + obj.setFieldLongList(new RealmList<>(2L, 3L)); + obj.setFieldIntegerList(new RealmList<>(2, 3)); + obj.setFieldShortList(new RealmList<>((short) 2, (short) 3)); + obj.setFieldByteList(new RealmList<>((byte) 2, (byte) 3)); + obj.setFieldDoubleList(new RealmList<>(2D, 3D)); + obj.setFieldFloatList(new RealmList<>(2F, 3F)); + obj.setFieldDateList(new RealmList<>(new Date(2L), new Date(3L))); + managedObj = realm.copyToRealm(obj); } realm.commitTransaction(); @@ -2838,10 +2954,42 @@ public void copyFromRealm_defaultValuesAreIgnored() { assertEquals(managedObj.getFieldDouble(), copy.getFieldDouble(), 0D); assertEquals(managedObj.isFieldBoolean(), copy.isFieldBoolean()); assertEquals(managedObj.getFieldDate(), copy.getFieldDate()); - assertTrue(Arrays.equals(managedObj.getFieldBinary(), copy.getFieldBinary())); + assertArrayEquals(managedObj.getFieldBinary(), copy.getFieldBinary()); assertEquals(managedObj.getFieldObject().getFieldInt(), copy.getFieldObject().getFieldInt()); assertEquals(1, copy.getFieldList().size()); + //noinspection ConstantConditions assertEquals(managedObj.getFieldList().first().getFieldInt(), copy.getFieldList().first().getFieldInt()); + + assertEquals(2, managedObj.getFieldStringList().size()); + assertEquals("2", managedObj.getFieldStringList().get(0)); + assertEquals("3", managedObj.getFieldStringList().get(1)); + assertEquals(2, managedObj.getFieldBooleanList().size()); + assertEquals(true, managedObj.getFieldBooleanList().get(0)); + assertEquals(false, managedObj.getFieldBooleanList().get(1)); + assertEquals(2, managedObj.getFieldBinaryList().size()); + assertArrayEquals(new byte[] {2}, managedObj.getFieldBinaryList().get(0)); + assertArrayEquals(new byte[] {3}, managedObj.getFieldBinaryList().get(1)); + assertEquals(2, managedObj.getFieldLongList().size()); + assertEquals((Long) 2L, managedObj.getFieldLongList().get(0)); + assertEquals((Long) 3L, managedObj.getFieldLongList().get(1)); + assertEquals(2, managedObj.getFieldIntegerList().size()); + assertEquals((Integer) 2, managedObj.getFieldIntegerList().get(0)); + assertEquals((Integer) 3, managedObj.getFieldIntegerList().get(1)); + assertEquals(2, managedObj.getFieldShortList().size()); + assertEquals((Short) (short) 2, managedObj.getFieldShortList().get(0)); + assertEquals((Short) (short) 3, managedObj.getFieldShortList().get(1)); + assertEquals(2, managedObj.getFieldByteList().size()); + assertEquals((Byte) (byte) 2, managedObj.getFieldByteList().get(0)); + assertEquals((Byte) (byte) 3, managedObj.getFieldByteList().get(1)); + assertEquals(2, managedObj.getFieldDoubleList().size()); + assertEquals((Double) 2D, managedObj.getFieldDoubleList().get(0)); + assertEquals((Double) 3D, managedObj.getFieldDoubleList().get(1)); + assertEquals(2, managedObj.getFieldFloatList().size()); + assertEquals((Float) 2F, managedObj.getFieldFloatList().get(0)); + assertEquals((Float) 3F, managedObj.getFieldFloatList().get(1)); + assertEquals(2, managedObj.getFieldDateList().size()); + assertEquals(new Date(2L), managedObj.getFieldDateList().get(0)); + assertEquals(new Date(3L), managedObj.getFieldDateList().get(1)); } // Tests close Realm in another thread different from where it is created. @@ -4157,6 +4305,7 @@ public void init_waitForFilesDir() throws NoSuchMethodException, InvocationTarge when(mockContext.getFilesDir()).then(new Answer() { int calls = 0; File userFolder = tmpFolder.newFolder(); + @Override public File answer(InvocationOnMock invocationOnMock) throws Throwable { calls++; diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java index 22e509c956..8a85897b55 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java @@ -45,6 +45,18 @@ public class AllJavaTypes extends RealmObject { public static final String FIELD_BINARY = "fieldBinary"; public static final String FIELD_OBJECT = "fieldObject"; public static final String FIELD_LIST = "fieldList"; + + public static final String FIELD_STRING_LIST = "fieldStringList"; + public static final String FIELD_BINARY_LIST = "fieldBinaryList"; + public static final String FIELD_BOOLEAN_LIST = "fieldBooleanList"; + public static final String FIELD_LONG_LIST = "fieldLongList"; + public static final String FIELD_INTEGER_LIST = "fieldIntegerList"; + public static final String FIELD_SHORT_LIST = "fieldShortList"; + public static final String FIELD_BYTE_LIST = "fieldByteList"; + public static final String FIELD_DOUBLE_LIST = "fieldDoubleList"; + public static final String FIELD_FLOAT_LIST = "fieldFloatList"; + public static final String FIELD_DATE_LIST = "fieldDateList"; + public static final String FIELD_LO_OBJECT = "objectParents"; public static final String FIELD_LO_LIST = "listParents"; @@ -77,6 +89,17 @@ public class AllJavaTypes extends RealmObject { private AllJavaTypes fieldObject; private RealmList fieldList; + private RealmList fieldStringList; + private RealmList fieldBinaryList; + private RealmList fieldBooleanList; + private RealmList fieldLongList; + private RealmList fieldIntegerList; + private RealmList fieldShortList; + private RealmList fieldByteList; + private RealmList fieldDoubleList; + private RealmList fieldFloatList; + private RealmList fieldDateList; + @LinkingObjects(FIELD_OBJECT) private final RealmResults objectParents = null; @@ -203,6 +226,86 @@ public void setFieldList(RealmList columnRealmList) { this.fieldList = columnRealmList; } + public RealmList getFieldStringList() { + return fieldStringList; + } + + public void setFieldStringList(RealmList fieldStringList) { + this.fieldStringList = fieldStringList; + } + + public RealmList getFieldBinaryList() { + return fieldBinaryList; + } + + public void setFieldBinaryList(RealmList fieldBinaryList) { + this.fieldBinaryList = fieldBinaryList; + } + + public RealmList getFieldBooleanList() { + return fieldBooleanList; + } + + public void setFieldBooleanList(RealmList fieldBooleanList) { + this.fieldBooleanList = fieldBooleanList; + } + + public RealmList getFieldLongList() { + return fieldLongList; + } + + public void setFieldLongList(RealmList fieldLongList) { + this.fieldLongList = fieldLongList; + } + + public RealmList getFieldIntegerList() { + return fieldIntegerList; + } + + public void setFieldIntegerList(RealmList fieldIntegerList) { + this.fieldIntegerList = fieldIntegerList; + } + + public RealmList getFieldShortList() { + return fieldShortList; + } + + public void setFieldShortList(RealmList fieldShortList) { + this.fieldShortList = fieldShortList; + } + + public RealmList getFieldByteList() { + return fieldByteList; + } + + public void setFieldByteList(RealmList fieldByteList) { + this.fieldByteList = fieldByteList; + } + + public RealmList getFieldDoubleList() { + return fieldDoubleList; + } + + public void setFieldDoubleList(RealmList fieldDoubleList) { + this.fieldDoubleList = fieldDoubleList; + } + + public RealmList getFieldFloatList() { + return fieldFloatList; + } + + public void setFieldFloatList(RealmList fieldFloatList) { + this.fieldFloatList = fieldFloatList; + } + + public RealmList getFieldDateList() { + return fieldDateList; + } + + public void setFieldDateList(RealmList fieldDateList) { + this.fieldDateList = fieldDateList; + } + public RealmResults getObjectParents() { return objectParents; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypes.java index 5e7d80bacc..f4322445aa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypes.java @@ -37,8 +37,18 @@ public class AllTypes extends RealmObject { public static final String FIELD_REALMOBJECT = "columnRealmObject"; public static final String FIELD_REALMLIST = "columnRealmList"; + public static final String FIELD_STRING_LIST = "columnStringList"; + public static final String FIELD_BINARY_LIST = "columnBinaryList"; + public static final String FIELD_BOOLEAN_LIST = "columnBooleanList"; + public static final String FIELD_LONG_LIST = "columnLongList"; + public static final String FIELD_DOUBLE_LIST = "columnDoubleList"; + public static final String FIELD_FLOAT_LIST = "columnFloatList"; + public static final String FIELD_DATE_LIST = "columnDateList"; + public static final String[] INVALID_TYPES_FIELDS_FOR_DISTINCT - = new String[] {FIELD_REALMOBJECT, FIELD_REALMLIST, FIELD_DOUBLE, FIELD_FLOAT}; + = new String[] {FIELD_REALMOBJECT, FIELD_REALMLIST, FIELD_DOUBLE, FIELD_FLOAT, + FIELD_STRING_LIST, FIELD_BINARY_LIST, FIELD_BOOLEAN_LIST, FIELD_LONG_LIST, + FIELD_DOUBLE_LIST, FIELD_FLOAT_LIST, FIELD_DATE_LIST}; @Required private String columnString = ""; @@ -55,6 +65,14 @@ public class AllTypes extends RealmObject { private Dog columnRealmObject; private RealmList columnRealmList; + private RealmList columnStringList; + private RealmList columnBinaryList; + private RealmList columnBooleanList; + private RealmList columnLongList; + private RealmList columnDoubleList; + private RealmList columnFloatList; + private RealmList columnDateList; + public String getColumnString() { return columnString; } @@ -130,4 +148,60 @@ public RealmList getColumnRealmList() { public void setColumnRealmList(RealmList columnRealmList) { this.columnRealmList = columnRealmList; } + + public RealmList getColumnStringList() { + return columnStringList; + } + + public void setColumnStringList(RealmList columnStringList) { + this.columnStringList = columnStringList; + } + + public RealmList getColumnBinaryList() { + return columnBinaryList; + } + + public void setColumnBinaryList(RealmList columnBinaryList) { + this.columnBinaryList = columnBinaryList; + } + + public RealmList getColumnBooleanList() { + return columnBooleanList; + } + + public void setColumnBooleanList(RealmList columnBooleanList) { + this.columnBooleanList = columnBooleanList; + } + + public RealmList getColumnLongList() { + return columnLongList; + } + + public void setColumnLongList(RealmList columnLongList) { + this.columnLongList = columnLongList; + } + + public RealmList getColumnDoubleList() { + return columnDoubleList; + } + + public void setColumnDoubleList(RealmList columnDoubleList) { + this.columnDoubleList = columnDoubleList; + } + + public RealmList getColumnFloatList() { + return columnFloatList; + } + + public void setColumnFloatList(RealmList columnFloatList) { + this.columnFloatList = columnFloatList; + } + + public RealmList getColumnDateList() { + return columnDateList; + } + + public void setColumnDateList(RealmList columnDateList) { + this.columnDateList = columnDateList; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypesPrimaryKey.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypesPrimaryKey.java index 5965b6d031..c6bff3fda0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypesPrimaryKey.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypesPrimaryKey.java @@ -35,6 +35,14 @@ public class AllTypesPrimaryKey extends RealmObject { private RealmList columnRealmList; private Boolean columnBoxedBoolean; + private RealmList columnStringList; + private RealmList columnBinaryList; + private RealmList columnBooleanList; + private RealmList columnLongList; + private RealmList columnDoubleList; + private RealmList columnFloatList; + private RealmList columnDateList; + public String getColumnString() { return columnString; } @@ -114,4 +122,60 @@ public Boolean getColumnBoxedBoolean() { public void setColumnBoxedBoolean(Boolean columnBoxedBoolean) { this.columnBoxedBoolean = columnBoxedBoolean; } + + public RealmList getColumnStringList() { + return columnStringList; + } + + public void setColumnStringList(RealmList columnStringList) { + this.columnStringList = columnStringList; + } + + public RealmList getColumnBinaryList() { + return columnBinaryList; + } + + public void setColumnBinaryList(RealmList columnBinaryList) { + this.columnBinaryList = columnBinaryList; + } + + public RealmList getColumnBooleanList() { + return columnBooleanList; + } + + public void setColumnBooleanList(RealmList columnBooleanList) { + this.columnBooleanList = columnBooleanList; + } + + public RealmList getColumnLongList() { + return columnLongList; + } + + public void setColumnLongList(RealmList columnLongList) { + this.columnLongList = columnLongList; + } + + public RealmList getColumnDoubleList() { + return columnDoubleList; + } + + public void setColumnDoubleList(RealmList columnDoubleList) { + this.columnDoubleList = columnDoubleList; + } + + public RealmList getColumnFloatList() { + return columnFloatList; + } + + public void setColumnFloatList(RealmList columnFloatList) { + this.columnFloatList = columnFloatList; + } + + public RealmList getColumnDateList() { + return columnDateList; + } + + public void setColumnDateList(RealmList columnDateList) { + this.columnDateList = columnDateList; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOfField.java b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOfField.java index 0378e64817..c9e13697d3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOfField.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/DefaultValueOfField.java @@ -27,42 +27,63 @@ public class DefaultValueOfField extends RealmObject { public static final String CLASS_NAME = "DefaultValueOfField"; - public static String FIELD_IGNORED = "fieldIgnored"; - public static String FIELD_RANDOM_STRING = "fieldRandomString"; - public static String FIELD_STRING = "fieldString"; - public static String FIELD_SHORT = "fieldShort"; - public static String FIELD_INT = "fieldInt"; - public static String FIELD_LONG_PRIMARY_KEY = "fieldLongPrimaryKey"; - public static String FIELD_LONG = "fieldLong"; - public static String FIELD_BYTE = "fieldByte"; - public static String FIELD_FLOAT = "fieldFloat"; - public static String FIELD_DOUBLE = "fieldDouble"; - public static String FIELD_BOOLEAN = "fieldBoolean"; - public static String FIELD_DATE = "fieldDate"; - public static String FIELD_BINARY = "fieldBinary"; - public static String FIELD_OBJECT = "fieldObject"; - public static String FIELD_LIST = "fieldList"; - - - public static String FIELD_IGNORED_DEFAULT_VALUE = "ignored"; - public static String FIELD_STRING_DEFAULT_VALUE = "defaultString"; - public static short FIELD_SHORT_DEFAULT_VALUE = 1234; - public static int FIELD_INT_DEFAULT_VALUE = 123456; - public static long FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE = 2L * Integer.MAX_VALUE; - public static long FIELD_LONG_DEFAULT_VALUE = 3L * Integer.MAX_VALUE; - public static byte FIELD_BYTE_DEFAULT_VALUE = 100; - public static float FIELD_FLOAT_DEFAULT_VALUE = 0.5f; - public static double FIELD_DOUBLE_DEFAULT_VALUE = 0.25; - public static boolean FIELD_BOOLEAN_DEFAULT_VALUE = true; - public static Date FIELD_DATE_DEFAULT_VALUE = new Date(1473691826000L /*2016/9/12 23:56:26 JST*/); - public static byte[] FIELD_BINARY_DEFAULT_VALUE = new byte[] {123, -100, 0, 2}; - public static RandomPrimaryKey FIELD_OBJECT_DEFAULT_VALUE; - public static RealmList FIELD_LIST_DEFAULT_VALUE; + public static final String FIELD_IGNORED = "fieldIgnored"; + public static final String FIELD_RANDOM_STRING = "fieldRandomString"; + public static final String FIELD_STRING = "fieldString"; + public static final String FIELD_SHORT = "fieldShort"; + public static final String FIELD_INT = "fieldInt"; + public static final String FIELD_LONG_PRIMARY_KEY = "fieldLongPrimaryKey"; + public static final String FIELD_LONG = "fieldLong"; + public static final String FIELD_BYTE = "fieldByte"; + public static final String FIELD_FLOAT = "fieldFloat"; + public static final String FIELD_DOUBLE = "fieldDouble"; + public static final String FIELD_BOOLEAN = "fieldBoolean"; + public static final String FIELD_DATE = "fieldDate"; + public static final String FIELD_BINARY = "fieldBinary"; + public static final String FIELD_OBJECT = "fieldObject"; + public static final String FIELD_LIST = "fieldList"; + + + public static final String FIELD_IGNORED_DEFAULT_VALUE = "ignored"; + public static final String FIELD_STRING_DEFAULT_VALUE = "defaultString"; + public static final short FIELD_SHORT_DEFAULT_VALUE = 1234; + public static final int FIELD_INT_DEFAULT_VALUE = 123456; + public static final long FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE = 2L * Integer.MAX_VALUE; + public static final long FIELD_LONG_DEFAULT_VALUE = 3L * Integer.MAX_VALUE; + public static final byte FIELD_BYTE_DEFAULT_VALUE = 100; + public static final float FIELD_FLOAT_DEFAULT_VALUE = 0.5f; + public static final double FIELD_DOUBLE_DEFAULT_VALUE = 0.25; + public static final boolean FIELD_BOOLEAN_DEFAULT_VALUE = true; + public static final Date FIELD_DATE_DEFAULT_VALUE = new Date(1473691826000L /*2016/9/12 23:56:26 JST*/); + public static final byte[] FIELD_BINARY_DEFAULT_VALUE = new byte[] {123, -100, 0, 2}; + public static final RandomPrimaryKey FIELD_OBJECT_DEFAULT_VALUE; + public static final RealmList FIELD_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_STRING_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_BOOLEAN_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_BINARY_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_LONG_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_INTEGER_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_SHORT_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_BYTE_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_DOUBLE_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_FLOAT_LIST_DEFAULT_VALUE; + public static final RealmList FIELD_DATE_LIST_DEFAULT_VALUE; static { FIELD_OBJECT_DEFAULT_VALUE = new RandomPrimaryKey(); FIELD_LIST_DEFAULT_VALUE = new RealmList(); FIELD_LIST_DEFAULT_VALUE.add(new RandomPrimaryKey()); + + FIELD_STRING_LIST_DEFAULT_VALUE = new RealmList<>("1"); + FIELD_BOOLEAN_LIST_DEFAULT_VALUE = new RealmList<>(true); + FIELD_BINARY_LIST_DEFAULT_VALUE = new RealmList<>(new byte[] {1}); + FIELD_LONG_LIST_DEFAULT_VALUE = new RealmList<>(1L); + FIELD_INTEGER_LIST_DEFAULT_VALUE = new RealmList<>(1); + FIELD_SHORT_LIST_DEFAULT_VALUE = new RealmList<>((short) 1); + FIELD_BYTE_LIST_DEFAULT_VALUE = new RealmList<>((byte) 1); + FIELD_DOUBLE_LIST_DEFAULT_VALUE = new RealmList<>(1D); + FIELD_FLOAT_LIST_DEFAULT_VALUE = new RealmList<>(1F); + FIELD_DATE_LIST_DEFAULT_VALUE = new RealmList<>(new Date(1)); } public static String lastRandomStringValue; @@ -83,8 +104,18 @@ public class DefaultValueOfField extends RealmObject { private RandomPrimaryKey fieldObject = FIELD_OBJECT_DEFAULT_VALUE; private RealmList fieldList = FIELD_LIST_DEFAULT_VALUE; - public DefaultValueOfField() { + private RealmList fieldStringList = FIELD_STRING_LIST_DEFAULT_VALUE; + private RealmList fieldBinaryList = FIELD_BINARY_LIST_DEFAULT_VALUE; + private RealmList fieldBooleanList = FIELD_BOOLEAN_LIST_DEFAULT_VALUE; + private RealmList fieldLongList = FIELD_LONG_LIST_DEFAULT_VALUE; + private RealmList fieldIntegerList = FIELD_INTEGER_LIST_DEFAULT_VALUE; + private RealmList fieldShortList = FIELD_SHORT_LIST_DEFAULT_VALUE; + private RealmList fieldByteList = FIELD_BYTE_LIST_DEFAULT_VALUE; + private RealmList fieldDoubleList = FIELD_DOUBLE_LIST_DEFAULT_VALUE; + private RealmList fieldFloatList = FIELD_FLOAT_LIST_DEFAULT_VALUE; + private RealmList fieldDateList = FIELD_DATE_LIST_DEFAULT_VALUE; + public DefaultValueOfField() { } public DefaultValueOfField(long fieldLong) { @@ -210,4 +241,84 @@ public RealmList getFieldList() { public void setFieldList(RealmList fieldList) { this.fieldList = fieldList; } + + public RealmList getFieldStringList() { + return fieldStringList; + } + + public void setFieldStringList(RealmList fieldStringList) { + this.fieldStringList = fieldStringList; + } + + public RealmList getFieldBinaryList() { + return fieldBinaryList; + } + + public void setFieldBinaryList(RealmList fieldBinaryList) { + this.fieldBinaryList = fieldBinaryList; + } + + public RealmList getFieldBooleanList() { + return fieldBooleanList; + } + + public void setFieldBooleanList(RealmList fieldBooleanList) { + this.fieldBooleanList = fieldBooleanList; + } + + public RealmList getFieldLongList() { + return fieldLongList; + } + + public void setFieldLongList(RealmList fieldLongList) { + this.fieldLongList = fieldLongList; + } + + public RealmList getFieldIntegerList() { + return fieldIntegerList; + } + + public void setFieldIntegerList(RealmList fieldIntegerList) { + this.fieldIntegerList = fieldIntegerList; + } + + public RealmList getFieldShortList() { + return fieldShortList; + } + + public void setFieldShortList(RealmList fieldShortList) { + this.fieldShortList = fieldShortList; + } + + public RealmList getFieldByteList() { + return fieldByteList; + } + + public void setFieldByteList(RealmList fieldByteList) { + this.fieldByteList = fieldByteList; + } + + public RealmList getFieldDoubleList() { + return fieldDoubleList; + } + + public void setFieldDoubleList(RealmList fieldDoubleList) { + this.fieldDoubleList = fieldDoubleList; + } + + public RealmList getFieldFloatList() { + return fieldFloatList; + } + + public void setFieldFloatList(RealmList fieldFloatList) { + this.fieldFloatList = fieldFloatList; + } + + public RealmList getFieldDateList() { + return fieldDateList; + } + + public void setFieldDateList(RealmList fieldDateList) { + this.fieldDateList = fieldDateList; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java index db8d4a2dbb..47842e8796 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java @@ -66,6 +66,27 @@ public class NullTypes extends RealmObject { public static final String FIELD_LO_OBJECT = "objectParents"; public static final String FIELD_LO_LIST = "listParents"; + public static final String FIELD_STRING_LIST_NOT_NULL = "fieldStringListNotNull"; + public static final String FIELD_STRING_LIST_NULL = "fieldStringListNull"; + public static final String FIELD_BINARY_LIST_NOT_NULL = "fieldBinaryListNotNull"; + public static final String FIELD_BINARY_LIST_NULL = "fieldBinaryListNull"; + public static final String FIELD_BOOLEAN_LIST_NOT_NULL = "fieldBooleanListNotNull"; + public static final String FIELD_BOOLEAN_LIST_NULL = "fieldBooleanListNull"; + public static final String FIELD_LONG_LIST_NOT_NULL = "fieldLongListNotNull"; + public static final String FIELD_LONG_LIST_NULL = "fieldLongListNull"; + public static final String FIELD_INTEGER_LIST_NOT_NULL = "fieldIntegerListNotNull"; + public static final String FIELD_INTEGER_LIST_NULL = "fieldIntegerListNull"; + public static final String FIELD_SHORT_LIST_NOT_NULL = "fieldShortListNotNull"; + public static final String FIELD_SHORT_LIST_NULL = "fieldShortListNull"; + public static final String FIELD_BYTE_LIST_NOT_NULL = "fieldByteListNotNull"; + public static final String FIELD_BYTE_LIST_NULL = "fieldByteListNull"; + public static final String FIELD_DOUBLE_LIST_NOT_NULL = "fieldDoubleListNotNull"; + public static final String FIELD_DOUBLE_LIST_NULL = "fieldDoubleListNull"; + public static final String FIELD_FLOAT_LIST_NOT_NULL = "fieldFloatListNotNull"; + public static final String FIELD_FLOAT_LIST_NULL = "fieldFloatListNull"; + public static final String FIELD_DATE_LIST_NOT_NULL = "fieldDateListNotNull"; + public static final String FIELD_DATE_LIST_NULL = "fieldDateListNull"; + @PrimaryKey private int id; @@ -114,6 +135,46 @@ public class NullTypes extends RealmObject { // never nullable private RealmList fieldListNull; + @Required + private RealmList fieldStringListNotNull; + private RealmList fieldStringListNull; + + @Required + private RealmList fieldBinaryListNotNull; + private RealmList fieldBinaryListNull; + + @Required + private RealmList fieldBooleanListNotNull; + private RealmList fieldBooleanListNull; + + @Required + private RealmList fieldLongListNotNull; + private RealmList fieldLongListNull; + + @Required + private RealmList fieldIntegerListNotNull; + private RealmList fieldIntegerListNull; + + @Required + private RealmList fieldShortListNotNull; + private RealmList fieldShortListNull; + + @Required + private RealmList fieldByteListNotNull; + private RealmList fieldByteListNull; + + @Required + private RealmList fieldDoubleListNotNull; + private RealmList fieldDoubleListNull; + + @Required + private RealmList fieldFloatListNotNull; + private RealmList fieldFloatListNull; + + @Required + private RealmList fieldDateListNotNull; + private RealmList fieldDateListNull; + // never nullable @LinkingObjects(FIELD_OBJECT_NULL) private final RealmResults objectParents = null; @@ -313,4 +374,164 @@ public RealmResults getObjectParents() { public RealmResults getListParents() { return listParents; } + + public RealmList getFieldStringListNotNull() { + return fieldStringListNotNull; + } + + public void setFieldStringListNotNull(RealmList fieldStringListNotNull) { + this.fieldStringListNotNull = fieldStringListNotNull; + } + + public RealmList getFieldStringListNull() { + return fieldStringListNull; + } + + public void setFieldStringListNull(RealmList fieldStringListNull) { + this.fieldStringListNull = fieldStringListNull; + } + + public RealmList getFieldBinaryListNotNull() { + return fieldBinaryListNotNull; + } + + public void setFieldBinaryListNotNull(RealmList fieldBinaryListNotNull) { + this.fieldBinaryListNotNull = fieldBinaryListNotNull; + } + + public RealmList getFieldBinaryListNull() { + return fieldBinaryListNull; + } + + public void setFieldBinaryListNull(RealmList fieldBinaryListNull) { + this.fieldBinaryListNull = fieldBinaryListNull; + } + + public RealmList getFieldBooleanListNotNull() { + return fieldBooleanListNotNull; + } + + public void setFieldBooleanListNotNull(RealmList fieldBooleanListNotNull) { + this.fieldBooleanListNotNull = fieldBooleanListNotNull; + } + + public RealmList getFieldBooleanListNull() { + return fieldBooleanListNull; + } + + public void setFieldBooleanListNull(RealmList fieldBooleanListNull) { + this.fieldBooleanListNull = fieldBooleanListNull; + } + + public RealmList getFieldLongListNotNull() { + return fieldLongListNotNull; + } + + public void setFieldLongListNotNull(RealmList fieldLongListNotNull) { + this.fieldLongListNotNull = fieldLongListNotNull; + } + + public RealmList getFieldLongListNull() { + return fieldLongListNull; + } + + public void setFieldLongListNull(RealmList fieldLongListNull) { + this.fieldLongListNull = fieldLongListNull; + } + + public RealmList getFieldIntegerListNotNull() { + return fieldIntegerListNotNull; + } + + public void setFieldIntegerListNotNull(RealmList fieldIntegerListNotNull) { + this.fieldIntegerListNotNull = fieldIntegerListNotNull; + } + + public RealmList getFieldIntegerListNull() { + return fieldIntegerListNull; + } + + public void setFieldIntegerListNull(RealmList fieldIntegerListNull) { + this.fieldIntegerListNull = fieldIntegerListNull; + } + + public RealmList getFieldShortListNotNull() { + return fieldShortListNotNull; + } + + public void setFieldShortListNotNull(RealmList fieldShortListNotNull) { + this.fieldShortListNotNull = fieldShortListNotNull; + } + + public RealmList getFieldShortListNull() { + return fieldShortListNull; + } + + public void setFieldShortListNull(RealmList fieldShortListNull) { + this.fieldShortListNull = fieldShortListNull; + } + + public RealmList getFieldByteListNotNull() { + return fieldByteListNotNull; + } + + public void setFieldByteListNotNull(RealmList fieldByteListNotNull) { + this.fieldByteListNotNull = fieldByteListNotNull; + } + + public RealmList getFieldByteListNull() { + return fieldByteListNull; + } + + public void setFieldByteListNull(RealmList fieldByteListNull) { + this.fieldByteListNull = fieldByteListNull; + } + + public RealmList getFieldDoubleListNotNull() { + return fieldDoubleListNotNull; + } + + public void setFieldDoubleListNotNull(RealmList fieldDoubleListNotNull) { + this.fieldDoubleListNotNull = fieldDoubleListNotNull; + } + + public RealmList getFieldDoubleListNull() { + return fieldDoubleListNull; + } + + public void setFieldDoubleListNull(RealmList fieldDoubleListNull) { + this.fieldDoubleListNull = fieldDoubleListNull; + } + + public RealmList getFieldFloatListNotNull() { + return fieldFloatListNotNull; + } + + public void setFieldFloatListNotNull(RealmList fieldFloatListNotNull) { + this.fieldFloatListNotNull = fieldFloatListNotNull; + } + + public RealmList getFieldFloatListNull() { + return fieldFloatListNull; + } + + public void setFieldFloatListNull(RealmList fieldFloatListNull) { + this.fieldFloatListNull = fieldFloatListNull; + } + + public RealmList getFieldDateListNotNull() { + return fieldDateListNotNull; + } + + public void setFieldDateListNotNull(RealmList fieldDateListNotNull) { + this.fieldDateListNotNull = fieldDateListNotNull; + } + + public RealmList getFieldDateListNull() { + return fieldDateListNull; + } + + public void setFieldDateListNull(RealmList fieldDateListNull) { + this.fieldDateListNull = fieldDateListNull; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java new file mode 100644 index 0000000000..2b266233ce --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java @@ -0,0 +1,484 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import io.realm.RealmConfiguration; +import io.realm.RealmFieldType; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.fail; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class OsListTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + private SharedRealm sharedRealm; + private UncheckedRow row; + private OsObjectSchemaInfo testObjectSchemaInfo; + + @Before + public void setUp() { + OsObjectSchemaInfo objectSchemaInfo = new OsObjectSchemaInfo.Builder("TestModel") + .addPersistedValueListProperty("longList", RealmFieldType.INTEGER_LIST, !Property.REQUIRED) + .addPersistedValueListProperty("doubleList", RealmFieldType.DOUBLE_LIST, !Property.REQUIRED) + .addPersistedValueListProperty("floatList", RealmFieldType.FLOAT_LIST, !Property.REQUIRED) + .addPersistedValueListProperty("booleanList", RealmFieldType.BOOLEAN_LIST, !Property.REQUIRED) + .addPersistedValueListProperty("binaryList", RealmFieldType.BINARY_LIST, !Property.REQUIRED) + .addPersistedValueListProperty("dateList", RealmFieldType.DATE_LIST, !Property.REQUIRED) + .addPersistedValueListProperty("stringList", RealmFieldType.STRING_LIST, !Property.REQUIRED) + + .addPersistedValueListProperty("requiredLongList", RealmFieldType.INTEGER_LIST, Property.REQUIRED) + .addPersistedValueListProperty("requiredDoubleList", RealmFieldType.DOUBLE_LIST, Property.REQUIRED) + .addPersistedValueListProperty("requiredFloatList", RealmFieldType.FLOAT_LIST, Property.REQUIRED) + .addPersistedValueListProperty("requiredBooleanList", RealmFieldType.BOOLEAN_LIST, Property.REQUIRED) + .addPersistedValueListProperty("requiredBinaryList", RealmFieldType.BINARY_LIST, Property.REQUIRED) + .addPersistedValueListProperty("requiredDateList", RealmFieldType.DATE_LIST, Property.REQUIRED) + .addPersistedValueListProperty("requiredStringList", RealmFieldType.STRING_LIST, Property.REQUIRED) + + .build(); + List objectSchemaInfoList = new ArrayList(); + objectSchemaInfoList.add(objectSchemaInfo); + + OsSchemaInfo schemaInfo = new OsSchemaInfo(objectSchemaInfoList); + + RealmConfiguration config = configFactory.createConfiguration(); + OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config) + .autoUpdateNotification(true) + .schemaInfo(schemaInfo); + sharedRealm = SharedRealm.getInstance(configBuilder); + sharedRealm.beginTransaction(); + Table table = sharedRealm.getTable(Table.getTableNameForClass("TestModel")); + row = table.getUncheckedRow(OsObject.createRow(table)); + sharedRealm.commitTransaction(); + + schemaInfo = sharedRealm.getSchemaInfo(); + testObjectSchemaInfo = schemaInfo.getObjectSchemaInfo("TestModel"); + + sharedRealm.beginTransaction(); + } + + @After + public void tearDown() { + sharedRealm.cancelTransaction(); + sharedRealm.close(); + } + + private void addNull_insertNull_setNull_nullableList(OsList osList) { + assertNotNull(osList.getValue(1)); + osList.insertNull(1); + assertNull(osList.getValue(1)); + + osList.addNull(); + assertNull(osList.getValue(osList.size() - 1)); + + assertNotNull(osList.getValue(2)); + osList.setNull(2); + assertNull(osList.getValue(2)); + } + + private void addNull_insertNull_setNull_requiredList(OsList osList) { + long initialSize = osList.size(); + try { + osList.insertNull(0); + fail(); + } catch (IllegalArgumentException ignored) { + assertEquals(initialSize, osList.size()); + } + + initialSize = osList.size(); + try { + osList.addNull(); + fail(); + } catch (IllegalArgumentException ignored) { + assertEquals(initialSize, osList.size()); + } + + initialSize = osList.size(); + try { + osList.setNull(0); + fail(); + } catch (IllegalArgumentException ignored) { + assertEquals(initialSize, osList.size()); + } + } + + private void add_insert_set_values_long(OsList osList) { + osList.addLong(42); + Long value = (Long) osList.getValue(0); + assertNotNull(value); + assertEquals(42, value.longValue()); + + osList.insertLong(0, 24); + value = (Long) osList.getValue(0); + assertNotNull(value); + assertEquals(24, value.longValue()); + + osList.setLong(0, 42); + value = (Long) osList.getValue(0); + assertNotNull(value); + assertEquals(42, value.longValue()); + } + + @Test + public void add_insert_set_get_Long() { + long index = testObjectSchemaInfo.getProperty("longList").getColumnIndex(); + OsList osList = new OsList(row, index); + + add_insert_set_values_long(osList); + addNull_insertNull_setNull_nullableList(osList); + } + + @Test + public void add_insert_get_set_required_Long() { + long index = testObjectSchemaInfo.getProperty("requiredLongList").getColumnIndex(); + OsList osList = new OsList(row, index); + + add_insert_set_values_long(osList); + addNull_insertNull_setNull_requiredList(osList); + } + + private void add_insert_set_values_double(OsList osList) { + osList.addDouble(42d); + Double value = (Double) osList.getValue(0); + assertNotNull(value); + assertEquals(42d, value.doubleValue(), 0d); + + osList.insertDouble(0, 24); + value = (Double) osList.getValue(0); + assertNotNull(value); + assertEquals(24d, value.longValue(), 0d); + + osList.setDouble(0, 42); + value = (Double) osList.getValue(0); + assertNotNull(value); + assertEquals(42d, value.longValue(), 0d); + } + + @Test + public void add_insert_set_get_Double() { + long index = testObjectSchemaInfo.getProperty("doubleList").getColumnIndex(); + OsList osList = new OsList(row, index); + + add_insert_set_values_double(osList); + addNull_insertNull_setNull_nullableList(osList); + } + + @Test + public void add_insert_set_get_required_Double() { + long index = testObjectSchemaInfo.getProperty("requiredDoubleList").getColumnIndex(); + OsList osList = new OsList(row, index); + + add_insert_set_values_double(osList); + addNull_insertNull_setNull_requiredList(osList); + } + + private void add_insert_set_values_float(OsList osList) { + osList.addFloat(42f); + Float value = (Float) osList.getValue(0); + assertNotNull(value); + assertEquals(42f, value.doubleValue(), 0f); + + osList.insertFloat(0, 24f); + value = (Float) osList.getValue(0); + assertNotNull(value); + assertEquals(24f, value.longValue(), 0f); + + osList.setFloat(0, 42f); + value = (Float) osList.getValue(0); + assertNotNull(value); + assertEquals(42f, value.longValue(), 0f); + } + + @Test + public void add_insert_get_Float() { + long index = testObjectSchemaInfo.getProperty("floatList").getColumnIndex(); + OsList osList = new OsList(row, index); + + add_insert_set_values_float(osList); + addNull_insertNull_setNull_nullableList(osList); + } + + @Test + public void add_insert_get_required_Float() { + long index = testObjectSchemaInfo.getProperty("requiredFloatList").getColumnIndex(); + OsList osList = new OsList(row, index); + + add_insert_set_values_float(osList); + addNull_insertNull_setNull_requiredList(osList); + } + + private void add_insert_set_values_boolean(OsList osList) { + osList.addBoolean(true); + Boolean value = (Boolean) osList.getValue(0); + assertNotNull(value); + assertTrue(value); + + osList.insertBoolean(0, false); + value = (Boolean) osList.getValue(0); + assertNotNull(value); + assertFalse(value); + + osList.setBoolean(0, true); + value = (Boolean) osList.getValue(0); + assertNotNull(value); + assertTrue(value); + } + + @Test + public void add_insert_set_get_Boolean() { + long index = testObjectSchemaInfo.getProperty("booleanList").getColumnIndex(); + OsList osList = new OsList(row, index); + + add_insert_set_values_boolean(osList); + addNull_insertNull_setNull_nullableList(osList); + } + + @Test + public void add_insert_set_get_required_Boolean() { + long index = testObjectSchemaInfo.getProperty("requiredBooleanList").getColumnIndex(); + OsList osList = new OsList(row, index); + + add_insert_set_values_boolean(osList); + addNull_insertNull_setNull_requiredList(osList); + } + + @Test + public void add_insert_set_get_Date() { + long index = testObjectSchemaInfo.getProperty("dateList").getColumnIndex(); + OsList osList = new OsList(row, index); + + Date date42 = new Date(42); + Date date24 = new Date(24); + + osList.addDate(null); + Date value = (Date) osList.getValue(0); + assertNull(value); + + osList.addDate(date42); + value = (Date) osList.getValue(1); + assertNotNull(value); + assertEquals(date42, value); + + osList.insertDate(0, null); + value = (Date) osList.getValue(0); + assertNull(value); + + osList.insertDate(0, date24); + value = (Date) osList.getValue(0); + assertNotNull(value); + assertEquals(date24, value); + + osList.insertNull(0); + value = (Date) osList.getValue(0); + assertNull(value); + + osList.addNull(); + assertNull(osList.getValue(5)); + + osList.setDate(5, date42); + value = (Date) osList.getValue(5); + assertNotNull(value); + assertEquals(date42, value); + + osList.setDate(5, null); + value = (Date) osList.getValue(5); + assertNull(value); + } + + @Test + public void add_insert_set_null_required_Date() { + long index = testObjectSchemaInfo.getProperty("requiredDateList").getColumnIndex(); + OsList osList = new OsList(row, index); + + addNull_insertNull_setNull_requiredList(osList); + + try { + osList.insertDate(0, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + + try { + osList.addDate(null); + fail(); + } catch (IllegalArgumentException ignored) { + } + + try { + osList.setDate(0, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void add_insert_get_String() { + long index = testObjectSchemaInfo.getProperty("stringList").getColumnIndex(); + OsList osList = new OsList(row, index); + + osList.addString(null); + String value = (String) osList.getValue(0); + assertNull(value); + + osList.addString("42"); + value = (String) osList.getValue(1); + assertNotNull(value); + assertEquals("42", value); + + osList.insertString(0, null); + value = (String) osList.getValue(0); + assertNull(value); + + osList.insertString(0, "24"); + value = (String) osList.getValue(0); + assertNotNull(value); + assertEquals("24", value); + + osList.insertNull(0); + value = (String) osList.getValue(0); + assertNull(value); + + osList.addNull(); + assertNull(osList.getValue(5)); + + osList.setString(5, "24"); + value = (String) osList.getValue(5); + assertNotNull(value); + assertEquals("24", value); + + osList.setString(5, null); + value = (String) osList.getValue(5); + assertNull(value); + } + + @Test + public void add_insert_set_null_required_String() { + long index = testObjectSchemaInfo.getProperty("requiredStringList").getColumnIndex(); + OsList osList = new OsList(row, index); + + addNull_insertNull_setNull_requiredList(osList); + + try { + osList.insertString(0, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + + try { + osList.addString(null); + fail(); + } catch (IllegalArgumentException ignored) { + } + + try { + osList.setString(0, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void add_insert_get_Binary() { + long index = testObjectSchemaInfo.getProperty("binaryList").getColumnIndex(); + OsList osList = new OsList(row, index); + + byte[] bytes42 = new byte[1]; + bytes42[0] = 42; + byte[] bytes24 = new byte[2]; + bytes24[0] = 24; + bytes24[1] = 24; + + osList.addBinary(null); + byte[] value = (byte[]) osList.getValue(0); + assertNull(value); + + osList.addBinary(bytes42); + value = (byte[]) osList.getValue(1); + assertNotNull(value); + assertArrayEquals(bytes42, value); + + osList.insertBinary(0, null); + value = (byte[]) osList.getValue(0); + assertNull(value); + + osList.insertBinary(0, bytes24); + value = (byte[]) osList.getValue(0); + assertNotNull(value); + assertArrayEquals(bytes24, value); + + osList.insertNull(0); + value = (byte[]) osList.getValue(0); + assertNull(value); + + osList.addNull(); + assertNull(osList.getValue(5)); + + osList.setBinary(5, bytes24); + value = (byte[]) osList.getValue(5); + assertNotNull(value); + assertArrayEquals(bytes24, value); + + osList.setBinary(5, null); + value = (byte[]) osList.getValue(5); + assertNull(value); + } + + @Test + public void add_insert_set_null_required_Binary() { + long index = testObjectSchemaInfo.getProperty("requiredBinaryList").getColumnIndex(); + OsList osList = new OsList(row, index); + + addNull_insertNull_setNull_requiredList(osList); + + try { + osList.insertBinary(0, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + + try { + osList.addBinary(null); + fail(); + } catch (IllegalArgumentException ignored) { + } + + try { + osList.setBinary(0, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java index 02ee939e37..381b408de3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java @@ -244,6 +244,13 @@ private Set getValidFieldTypes(Set filter) { case UNSUPPORTED_TABLE: case UNSUPPORTED_MIXED: case LINKING_OBJECTS: // TODO: should be supported?s + case INTEGER_LIST: // FIXME zaki50 revisit this once Primitive List query is implemented + case BOOLEAN_LIST: + case STRING_LIST: + case BINARY_LIST: + case DATE_LIST: + case FLOAT_LIST: + case DOUBLE_LIST: break; case LIST: case OBJECT: diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index 34731cedcf..e9b6330a90 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -14,49 +14,22 @@ * limitations under the License. */ -#include #include "io_realm_internal_Collection.h" #include #include #include +#include "java_class_global_def.hpp" #include "java_sort_descriptor.hpp" +#include "observable_collection_wrapper.hpp" #include "util.hpp" -#include "java_class_global_def.hpp" - -#include "jni_util/java_class.hpp" -#include "jni_util/java_global_weak_ref.hpp" -#include "jni_util/java_method.hpp" using namespace realm; using namespace realm::jni_util; using namespace realm::_impl; -// We need to control the life cycle of Results, weak ref of Java Collection object and the NotificationToken. -// Wrap all three together, so when the Java Collection object gets GCed, all three of them will be invalidated. -struct ResultsWrapper { - JavaGlobalWeakRef m_collection_weak_ref; - NotificationToken m_notification_token; - Results m_results; - - ResultsWrapper(Results& results) - : m_collection_weak_ref() - , m_notification_token() - , m_results(std::move(results)) - { - } - - ResultsWrapper(ResultsWrapper&&) = delete; - ResultsWrapper& operator=(ResultsWrapper&&) = delete; - - ResultsWrapper(ResultsWrapper const&) = delete; - ResultsWrapper& operator=(ResultsWrapper const&) = delete; - - ~ResultsWrapper() - { - } -}; +typedef ObservableCollectionWrapper ResultsWrapper; static void finalize_results(jlong ptr); @@ -104,7 +77,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResultsFro { TR_ENTER() try { - auto& list = *reinterpret_cast(list_ptr); + auto& list_wrapper = *reinterpret_cast*>(list_ptr); + auto& list = list_wrapper.collection(); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); Results results = j_sort_desc ? list.sort(JavaSortDescriptor(env, j_sort_desc).sort_descriptor()) : @@ -122,7 +96,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateSnapshot(J TR_ENTER_PTR(native_ptr); try { auto wrapper = reinterpret_cast(native_ptr); - auto snapshot_results = wrapper->m_results.snapshot(); + auto snapshot_results = wrapper->collection().snapshot(); auto snapshot_wrapper = new ResultsWrapper(snapshot_results); return reinterpret_cast(snapshot_wrapper); } @@ -137,7 +111,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeContains(JNIE try { auto wrapper = reinterpret_cast(native_ptr); auto row = reinterpret_cast(native_row_ptr); - size_t index = wrapper->m_results.index_of(RowExpr(*row)); + size_t index = wrapper->collection().index_of(RowExpr(*row)); return to_jbool(index != not_found); } CATCH_STD(); @@ -150,7 +124,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeGetRow(JNIEnv* e TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->m_results.get(static_cast(index)); + auto row = wrapper->collection().get(static_cast(index)); return reinterpret_cast(new Row(std::move(row))); } CATCH_STD() @@ -162,7 +136,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv* TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto optional_row = wrapper->m_results.first(); + auto optional_row = wrapper->collection().first(); if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } @@ -176,7 +150,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeLastRow(JNIEnv* TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto optional_row = wrapper->m_results.last(); + auto optional_row = wrapper->collection().last(); if (optional_row) { return reinterpret_cast(new Row(std::move(optional_row.value()))); } @@ -190,7 +164,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeClear(JNIEnv* env TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - wrapper->m_results.clear(); + wrapper->collection().clear(); } CATCH_STD() } @@ -200,7 +174,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeSize(JNIEnv* env TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - return static_cast(wrapper->m_results.size()); + return static_cast(wrapper->collection().size()); } CATCH_STD() return 0; @@ -217,13 +191,13 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_Collection_nativeAggregate(JNIE Optional value; switch (agg_func) { case io_realm_internal_Collection_AGGREGATE_FUNCTION_MINIMUM: - value = wrapper->m_results.min(index); + value = wrapper->collection().min(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_MAXIMUM: - value = wrapper->m_results.max(index); + value = wrapper->collection().max(index); break; case io_realm_internal_Collection_AGGREGATE_FUNCTION_AVERAGE: { - Optional value_count(wrapper->m_results.average(index)); + Optional value_count(wrapper->collection().average(index)); if (value_count) { value = Optional(Mixed(value_count.value())); } @@ -233,7 +207,7 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_Collection_nativeAggregate(JNIE break; } case io_realm_internal_Collection_AGGREGATE_FUNCTION_SUM: - value = wrapper->m_results.sum(index); + value = wrapper->collection().sum(index); break; default: REALM_UNREACHABLE(); @@ -267,7 +241,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeSort(JNIEnv* env TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto sorted_result = wrapper->m_results.sort(JavaSortDescriptor(env, j_sort_desc).sort_descriptor()); + auto sorted_result = wrapper->collection().sort(JavaSortDescriptor(env, j_sort_desc).sort_descriptor()); return reinterpret_cast(new ResultsWrapper(sorted_result)); } CATCH_STD() @@ -281,7 +255,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeDistinct(JNIEnv* try { auto wrapper = reinterpret_cast(native_ptr); auto distinct_result = - wrapper->m_results.distinct(JavaSortDescriptor(env, j_distinct_desc).distinct_descriptor()); + wrapper->collection().distinct(JavaSortDescriptor(env, j_distinct_desc).distinct_descriptor()); return reinterpret_cast(new ResultsWrapper(distinct_result)); } CATCH_STD() @@ -293,38 +267,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeStartListening(JN { TR_ENTER_PTR(native_ptr) - static JavaClass os_results_class(env, "io/realm/internal/Collection"); - static JavaMethod notify_change_listeners(env, os_results_class, "notifyChangeListeners", "(J)V"); - try { auto wrapper = reinterpret_cast(native_ptr); - if (!wrapper->m_collection_weak_ref) { - wrapper->m_collection_weak_ref = JavaGlobalWeakRef(env, instance); - } - - auto cb = [=](CollectionChangeSet const& changes, std::exception_ptr err) { - // OS will call all notifiers' callback in one run, so check the Java exception first!! - if (env->ExceptionCheck()) - return; - - if (err) { - try { - std::rethrow_exception(err); - } - catch (const std::exception& e) { - realm::jni_util::Log::e("Caught exception in collection change callback %1", e.what()); - return; - } - } - - wrapper->m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv* local_env, jobject collection_obj) { - local_env->CallVoidMethod( - collection_obj, notify_change_listeners, - reinterpret_cast(changes.empty() ? 0 : new CollectionChangeSet(changes))); - }); - }; - - wrapper->m_notification_token = wrapper->m_results.add_notification_callback(cb); + wrapper->start_listening(env, instance); } CATCH_STD() } @@ -335,7 +280,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeStopListening(JNI try { auto wrapper = reinterpret_cast(native_ptr); - wrapper->m_notification_token = {}; + wrapper->stop_listening(); } CATCH_STD() } @@ -352,7 +297,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeWhere(JNIEnv* en try { auto wrapper = reinterpret_cast(native_ptr); - auto table_view = wrapper->m_results.get_tableview(); + auto table_view = wrapper->collection().get_tableview(); Query* query = new Query(table_view.get_parent(), std::unique_ptr(new TableView(std::move(table_view)))); return reinterpret_cast(query); @@ -369,7 +314,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv* auto wrapper = reinterpret_cast(native_ptr); auto row = reinterpret_cast(row_native_ptr); - return static_cast(wrapper->m_results.index_of(RowExpr(*row))); + return static_cast(wrapper->collection().index_of(RowExpr(*row))); } CATCH_STD() return npos; @@ -380,7 +325,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteLast(JN TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->m_results.last(); + auto row = wrapper->collection().last(); if (row && row->is_attached()) { row->move_last_over(); return JNI_TRUE; @@ -396,7 +341,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteFirst(J try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->m_results.first(); + auto row = wrapper->collection().first(); if (row && row->is_attached()) { row->move_last_over(); return JNI_TRUE; @@ -413,7 +358,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeDelete(JNIEnv* en try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->m_results.get(index); + auto row = wrapper->collection().get(index); if (row.is_attached()) { row.move_last_over(); } @@ -426,7 +371,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeIsValid(JNIEn TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - return wrapper->m_results.is_valid(); + return wrapper->collection().is_valid(); } CATCH_STD() return JNI_FALSE; @@ -437,7 +382,7 @@ JNIEXPORT jbyte JNICALL Java_io_realm_internal_Collection_nativeGetMode(JNIEnv* TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - switch (wrapper->m_results.get_mode()) { + switch (wrapper->collection().get_mode()) { case Results::Mode::Empty: return io_realm_internal_Collection_MODE_EMPTY; case Results::Mode::Table: diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp index 19ac174bc1..ccee1c9720 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp @@ -20,44 +20,95 @@ #include #include +#include "observable_collection_wrapper.hpp" +#include "java_accessor.hpp" +#include "java_exception_def.hpp" +#include "jni_util/java_exception_thrower.hpp" #include "util.hpp" using namespace realm; +using namespace realm::util; +using namespace realm::_impl; -static void finalize_list(jlong ptr) +typedef ObservableCollectionWrapper ListWrapper; + +namespace { +void finalize_list(jlong ptr) { TR_ENTER_PTR(ptr) - delete reinterpret_cast(ptr); + delete reinterpret_cast(ptr); +} + +inline void add_value(JNIEnv* env, jlong list_ptr, Any&& value) +{ + auto& wrapper = *reinterpret_cast(list_ptr); + + JavaAccessorContext context(env); + wrapper.collection().add(context, value); +} + +inline void insert_value(JNIEnv* env, jlong list_ptr, jlong pos, Any&& value) +{ + auto& wrapper = *reinterpret_cast(list_ptr); + + JavaAccessorContext context(env); + wrapper.collection().insert(context, pos, value); } +inline void set_value(JNIEnv* env, jlong list_ptr, jlong pos, Any&& value) +{ + auto& wrapper = *reinterpret_cast(list_ptr); + + JavaAccessorContext context(env); + wrapper.collection().set(context, pos, value); +} + +// Check nullable earlier https://github.com/realm/realm-object-store/issues/544 +inline void check_nullable(JNIEnv* env, jlong list_ptr, jobject jobject_ptr = nullptr) +{ + auto& wrapper = *reinterpret_cast(list_ptr); + if (!jobject_ptr && !is_nullable(wrapper.collection().get_type())) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, + "This 'RealmList' is not nullable. A non-null value is expected."); + } +} +} // anonymous namespace + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_list); } -JNIEXPORT jlongArray JNICALL Java_io_realm_internal_OsList_nativeCreate(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong row_ptr, - jlong column_index) +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_OsList_nativeCreate(JNIEnv* env, jclass, jlong shared_realm_ptr, + jlong row_ptr, jlong column_index) { TR_ENTER_PTR(row_ptr) try { auto& row = *reinterpret_cast(row_ptr); - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, &row, column_index, type_LinkList)) { + if (!ROW_AND_COL_INDEX_VALID(env, &row, column_index)) { return 0; } auto& shared_realm = *reinterpret_cast(shared_realm_ptr); - LinkViewRef link_view_ref(row.get_linklist(column_index)); - auto list_ptr = new List(shared_realm, link_view_ref); + jlong ret[2]; - Table* target_table_ptr = &(link_view_ref)->get_target_table(); - LangBindHelper::bind_table_ptr(target_table_ptr); + List list(shared_realm, *row.get_table(), column_index, row.get_index()); + ListWrapper* wrapper_ptr = new ListWrapper(list); + ret[0] = reinterpret_cast(wrapper_ptr); - jlong ret[2]; - ret[0] = reinterpret_cast(list_ptr); - ret[1] = reinterpret_cast(target_table_ptr); + if (wrapper_ptr->collection().get_type() == PropertyType::Object) { + LinkViewRef link_view_ref(row.get_linklist(column_index)); + + Table* target_table_ptr = &(link_view_ref)->get_target_table(); + LangBindHelper::bind_table_ptr(target_table_ptr); + ret[1] = reinterpret_cast(target_table_ptr); + } + else { + ret[1] = reinterpret_cast(nullptr); + } jlongArray ret_array = env->NewLongArray(2); if (!ret_array) { @@ -77,8 +128,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeGetRow(JNIEnv* env, TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - auto row = list.get(column_index); + auto& wrapper = *reinterpret_cast(list_ptr); + auto row = wrapper.collection().get(column_index); return reinterpret_cast(new Row(std::move(row))); } CATCH_STD() @@ -91,8 +142,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddRow(JNIEnv* env, j TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - list.add(static_cast(target_row_index)); + auto& wrapper = *reinterpret_cast(list_ptr); + wrapper.collection().add(static_cast(target_row_index)); } CATCH_STD() } @@ -103,8 +154,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertRow(JNIEnv* env TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - list.insert(static_cast(pos), static_cast(target_row_index)); + auto& wrapper = *reinterpret_cast(list_ptr); + wrapper.collection().insert(static_cast(pos), static_cast(target_row_index)); } CATCH_STD() } @@ -115,8 +166,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetRow(JNIEnv* env, j TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - list.set(static_cast(pos), static_cast(target_row_index)); + auto& wrapper = *reinterpret_cast(list_ptr); + wrapper.collection().set(static_cast(pos), static_cast(target_row_index)); } CATCH_STD() } @@ -127,8 +178,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeMove(JNIEnv* env, jcl TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - list.move(source_index, target_index); + auto& wrapper = *reinterpret_cast(list_ptr); + wrapper.collection().move(source_index, target_index); } CATCH_STD() } @@ -138,8 +189,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeRemove(JNIEnv* env, j TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - list.remove(index); + auto& wrapper = *reinterpret_cast(list_ptr); + wrapper.collection().remove(index); } CATCH_STD() } @@ -149,8 +200,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeRemoveAll(JNIEnv* env TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - list.remove_all(); + auto& wrapper = *reinterpret_cast(list_ptr); + wrapper.collection().remove_all(); } CATCH_STD() } @@ -160,8 +211,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeSize(JNIEnv* env, jc TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - return list.size(); + auto& wrapper = *reinterpret_cast(list_ptr); + return wrapper.collection().size(); } CATCH_STD() return 0; @@ -172,8 +223,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeGetQuery(JNIEnv* env TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - auto query = list.get_query(); + auto& wrapper = *reinterpret_cast(list_ptr); + auto query = wrapper.collection().get_query(); return reinterpret_cast(new Query(std::move(query))); } CATCH_STD() @@ -185,8 +236,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsList_nativeIsValid(JNIEnv* e TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - return list.is_valid(); + auto& wrapper = *reinterpret_cast(list_ptr); + return wrapper.collection().is_valid(); } CATCH_STD() return JNI_FALSE; @@ -197,8 +248,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeDelete(JNIEnv* env, j TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - list.delete_at(S(index)); + auto& wrapper = *reinterpret_cast(list_ptr); + wrapper.collection().delete_at(S(index)); } CATCH_STD() } @@ -208,8 +259,293 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeDeleteAll(JNIEnv* env TR_ENTER_PTR(list_ptr) try { - auto& list = *reinterpret_cast(list_ptr); - list.delete_all(); + auto& wrapper = *reinterpret_cast(list_ptr); + wrapper.collection().delete_all(); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeStartListening(JNIEnv* env, jobject instance, + jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + + try { + auto wrapper = reinterpret_cast(native_ptr); + wrapper->start_listening(env, instance); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + + try { + auto wrapper = reinterpret_cast(native_ptr); + wrapper->stop_listening(); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddNull(JNIEnv* env, jclass, jlong list_ptr) +{ + TR_ENTER_PTR(list_ptr) + try { + check_nullable(env, list_ptr); + add_value(env, list_ptr, Any()); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertNull(JNIEnv* env, jclass, jlong list_ptr, jlong pos) +{ + TR_ENTER_PTR(list_ptr) + try { + check_nullable(env, list_ptr); + insert_value(env, list_ptr, pos, Any()); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetNull(JNIEnv* env, jclass, jlong list_ptr, jlong pos) +{ + TR_ENTER_PTR(list_ptr) + try { + check_nullable(env, list_ptr); + set_value(env, list_ptr, pos, Any()); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddLong(JNIEnv* env, jclass, jlong list_ptr, jlong value) +{ + TR_ENTER_PTR(list_ptr) + try { + add_value(env, list_ptr, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertLong(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jlong value) +{ + TR_ENTER_PTR(list_ptr) + try { + insert_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetLong(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jlong value) +{ + TR_ENTER_PTR(list_ptr) + try { + set_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddDouble(JNIEnv* env, jclass, jlong list_ptr, + jdouble value) +{ + TR_ENTER_PTR(list_ptr) + try { + add_value(env, list_ptr, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertDouble(JNIEnv* env, jclass, jlong list_ptr, + jlong pos, jdouble value) +{ + TR_ENTER_PTR(list_ptr) + try { + insert_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetDouble(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jdouble value) +{ + TR_ENTER_PTR(list_ptr) + try { + set_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddFloat(JNIEnv* env, jclass, jlong list_ptr, jfloat value) +{ + TR_ENTER_PTR(list_ptr) + try { + add_value(env, list_ptr, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertFloat(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jfloat value) +{ + TR_ENTER_PTR(list_ptr) + try { + insert_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetFloat(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jfloat value) +{ + TR_ENTER_PTR(list_ptr) + try { + set_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddBoolean(JNIEnv* env, jclass, jlong list_ptr, + jboolean value) +{ + TR_ENTER_PTR(list_ptr) + try { + add_value(env, list_ptr, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertBoolean(JNIEnv* env, jclass, jlong list_ptr, + jlong pos, jboolean value) +{ + TR_ENTER_PTR(list_ptr) + try { + insert_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetBoolean(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jboolean value) +{ + TR_ENTER_PTR(list_ptr) + try { + set_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddBinary(JNIEnv* env, jclass, jlong list_ptr, + jbyteArray value) +{ + TR_ENTER_PTR(list_ptr) + try { + check_nullable(env, list_ptr, value); + JByteArrayAccessor accessor(env, value); + add_value(env, list_ptr, Any(accessor)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertBinary(JNIEnv* env, jclass, jlong list_ptr, + jlong pos, jbyteArray value) +{ + TR_ENTER_PTR(list_ptr) + try { + check_nullable(env, list_ptr, value); + JByteArrayAccessor accessor(env, value); + insert_value(env, list_ptr, pos, Any(accessor)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetBinary(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jbyteArray value) +{ + TR_ENTER_PTR(list_ptr) + try { + check_nullable(env, list_ptr, value); + JByteArrayAccessor accessor(env, value); + set_value(env, list_ptr, pos, Any(accessor)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddDate(JNIEnv* env, jclass, jlong list_ptr, jlong value) +{ + TR_ENTER_PTR(list_ptr) + try { + add_value(env, list_ptr, Any(value)); } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertDate(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jlong value) +{ + TR_ENTER_PTR(list_ptr) + try { + insert_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetDate(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jlong value) +{ + TR_ENTER_PTR(list_ptr) + try { + set_value(env, list_ptr, pos, Any(value)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddString(JNIEnv* env, jclass, jlong list_ptr, + jstring value) +{ + TR_ENTER_PTR(list_ptr) + try { + check_nullable(env, list_ptr, value); + JStringAccessor accessor(env, value); + add_value(env, list_ptr, Any(accessor)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertString(JNIEnv* env, jclass, jlong list_ptr, + jlong pos, jstring value) +{ + TR_ENTER_PTR(list_ptr) + try { + check_nullable(env, list_ptr, value); + JStringAccessor accessor(env, value); + insert_value(env, list_ptr, pos, Any(accessor)); + } + CATCH_STD(); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetString(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jstring value) +{ + TR_ENTER_PTR(list_ptr) + try { + check_nullable(env, list_ptr, value); + JStringAccessor accessor(env, value); + set_value(env, list_ptr, pos, Any(accessor)); + } + CATCH_STD() +} + +JNIEXPORT jobject JNICALL Java_io_realm_internal_OsList_nativeGetValue(JNIEnv* env, jclass, jlong list_ptr, jlong pos) +{ + TR_ENTER_PTR(list_ptr) + try { + auto& wrapper = *reinterpret_cast(list_ptr); + JavaAccessorContext context(env); + return any_cast(wrapper.collection().get(context, pos)); + } + CATCH_STD() + + return nullptr; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index c078d9d33d..ced1e5d440 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -17,8 +17,8 @@ #include #include "util.hpp" +#include "io_realm_internal_Property.h" #include "io_realm_internal_Table.h" -#include "tablebase_tpl.hpp" #include "shared_realm.hpp" #include "util/format.hpp" @@ -173,8 +173,14 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable(J ThrowException(env, UnsupportedOperation, "Not allowed to convert field in subtable."); return JNI_FALSE; } - size_t column_index = S(columnIndex); - return to_jbool(table->is_nullable(column_index)); + + if (table->get_column_type(S(columnIndex)) != type_Table) { + // for other than primitive list (including object, object list). + return to_jbool(table->is_nullable(S(columnIndex))); // noexcept + } + // For primitive list + // FIXME: Add test in https://github.com/realm/realm-java/pull/5221 before merging to master + return to_jbool(table->get_descriptor()->get_subdescriptor(S(columnIndex))->is_nullable(S(0))); // noexcept } @@ -513,7 +519,16 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_Table_nativeGetColumnType(JNIEnv* return 0; } - return static_cast(TBL(nativeTablePtr)->get_column_type(S(columnIndex))); // noexcept + auto column_type = TBL(nativeTablePtr)->get_column_type(S(columnIndex)); // noexcept + if (column_type != type_Table) { + // For other than primitive list (including object, object list). + return static_cast(column_type); + } + // For primitive list + // FIXME: Add test in https://github.com/realm/realm-java/pull/5221 before merging to master + // FIXME: Add method in Object Store to return a PropertyType. + return static_cast(TBL(nativeTablePtr)->get_descriptor()->get_subdescriptor(S(columnIndex))->get_column_type(S(0)) + + io_realm_internal_Property_TYPE_ARRAY); // noexcept } @@ -618,8 +633,13 @@ JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_Table_nativeGetByteArray(JNI if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) { return nullptr; } + try { + realm::BinaryData bin = TBL(nativeTablePtr)->get_binary(S(columnIndex), S(rowIndex)); + return JavaClassGlobalDef::new_byte_array(env, bin); + } + CATCH_STD() - return tbl_GetByteArray

            (env, nativeTablePtr, columnIndex, rowIndex); // noexcept + return nullptr; } JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLink(JNIEnv* env, jobject, jlong nativeTablePtr, diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index bf3a2a2543..319597fb17 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -15,6 +15,7 @@ */ #include "io_realm_internal_UncheckedRow.h" +#include "io_realm_internal_Property.h" #include "java_accessor.hpp" #include "util.hpp" @@ -71,7 +72,13 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType(J jlong columnIndex) { TR_ENTER_PTR(nativeRowPtr) - return static_cast(ROW(nativeRowPtr)->get_column_type(S(columnIndex))); // noexcept + auto column_type = ROW(nativeRowPtr)->get_column_type(S(columnIndex)); // noexcept + if (column_type != type_Table) { + return static_cast(column_type); + } + // FIXME: Add test in https://github.com/realm/realm-java/pull/5221 before merging to master + return static_cast(ROW(nativeRowPtr)->get_table()->get_descriptor()->get_subdescriptor(S(columnIndex))->get_column_type(S(0)) + + io_realm_internal_Property_TYPE_ARRAY); // noexcept } JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetIndex(JNIEnv* env, jobject, jlong nativeRowPtr) @@ -164,22 +171,12 @@ JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteAr return nullptr; } - BinaryData bin = ROW(nativeRowPtr)->get_binary(S(columnIndex)); - if (bin.is_null()) { - return nullptr; - } - else if (bin.size() <= MAX_JSIZE) { - jbyteArray jresult = env->NewByteArray(static_cast(bin.size())); - if (jresult) { - env->SetByteArrayRegion(jresult, 0, static_cast(bin.size()), - reinterpret_cast(bin.data())); // throws - } - return jresult; - } - else { - ThrowException(env, IllegalArgument, "Length of ByteArray is larger than an Int."); - return nullptr; + try { + BinaryData bin = ROW(nativeRowPtr)->get_binary(S(columnIndex)); + return JavaClassGlobalDef::new_byte_array(env, bin); } + CATCH_STD() + return nullptr; } JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLink(JNIEnv* env, jobject, jlong nativeRowPtr, diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index 4ec2e0cbd4..333f77a3f4 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -25,8 +25,11 @@ #include #include +#include +#include #include +#include "java_class_global_def.hpp" #include "java_exception_def.hpp" #include "jni_util/java_exception_thrower.hpp" @@ -62,7 +65,8 @@ class JPrimitiveArrayAccessor { JPrimitiveArrayAccessor(const JPrimitiveArrayAccessor&) = default; JPrimitiveArrayAccessor& operator=(const JPrimitiveArrayAccessor&) = default; - inline bool is_null() { + inline bool is_null() + { return !m_elements_holder->m_jarray; } @@ -148,6 +152,114 @@ class JObjectArrayAccessor { jsize m_size; }; +// An object accessor context which can be used to create and access objects +// using util::Any as the type-erased value type. In addition, this serves as +// the reference implementation of an accessor context that must be implemented +// by each binding. +class JavaAccessorContext { +public: + JavaAccessorContext(JNIEnv* env) + : m_env(env){} + + // Convert from core types to the boxed type + util::Any box(BinaryData v) const + { + return reinterpret_cast(JavaClassGlobalDef::new_byte_array(m_env, v)); + } + util::Any box(List /*v*/) const + { + REALM_TERMINATE("not supported"); + } + util::Any box(Object /*v*/) const + { + REALM_TERMINATE("not supported"); + } + util::Any box(Results /*v*/) const + { + REALM_TERMINATE("not supported"); + } + util::Any box(StringData v) const + { + return reinterpret_cast(to_jstring(m_env, v)); + } + util::Any box(Timestamp v) const + { + return JavaClassGlobalDef::new_date(m_env, v); + } + util::Any box(bool v) const + { + return _impl::JavaClassGlobalDef::new_boolean(m_env, v); + } + util::Any box(double v) const + { + return _impl::JavaClassGlobalDef::new_double(m_env, v); + } + util::Any box(float v) const + { + return _impl::JavaClassGlobalDef::new_float(m_env, v); + } + util::Any box(int64_t v) const + { + return _impl::JavaClassGlobalDef::new_long(m_env, v); + } + util::Any box(util::Optional v) const + { + return v ? _impl::JavaClassGlobalDef::new_boolean(m_env, v.value()) : nullptr; + } + util::Any box(util::Optional v) const + { + return v ? _impl::JavaClassGlobalDef::new_double(m_env, v.value()) : nullptr; + } + util::Any box(util::Optional v) const + { + return v ? _impl::JavaClassGlobalDef::new_float(m_env, v.value()) : nullptr; + } + util::Any box(util::Optional v) const + { + return v ? _impl::JavaClassGlobalDef::new_long(m_env, v.value()) : nullptr; + } + util::Any box(RowExpr) const + { + REALM_TERMINATE("not supported"); + } + + // Any properties are only supported by the Cocoa binding to enable reading + // old Realm files that may have used them. Other bindings can safely not + // implement this. + util::Any box(Mixed) const + { + REALM_TERMINATE("not supported"); + } + + // Convert from the boxed type to core types. This needs to be implemented + // for all of the types which `box()` can take, plus `RowExpr` and optional + // versions of the numeric types, minus `List` and `Results`. + // + // `create` and `update` are only applicable to `unbox`. If + // `create` is false then when given something which is not a managed Realm + // object `unbox()` should simply return a detached row expr, while if it's + // true then `unbox()` should create a new object in the context's Realm + // using the provided value. If `update` is true then upsert semantics + // should be used for this. + template + T unbox(util::Any& v, bool /*create*/ = false, bool /*update*/ = false) const + { + return any_cast(v); + } + +private: + JNIEnv* m_env; + + inline void check_value_not_null(util::Any& v, const char* expected_type) const + { + if (!v.has_value()) { + THROW_JAVA_EXCEPTION( + m_env, JavaExceptionDef::IllegalArgument, + util::format("This field is required. A non-null '%1' type value is expected.", expected_type)); + } + } +}; + // Accessor for jbyteArray template <> inline JPrimitiveArrayAccessor::ElementsHolder::ElementsHolder(JNIEnv* env, jbyteArray jarray) @@ -196,7 +308,8 @@ inline std::vector JPrimitiveArrayAccessor::transform -inline JPrimitiveArrayAccessor::ElementsHolder::ElementsHolder(JNIEnv* env, jbooleanArray jarray) +inline JPrimitiveArrayAccessor::ElementsHolder::ElementsHolder(JNIEnv* env, + jbooleanArray jarray) : m_env(env) , m_jarray(jarray) , m_data_ptr(jarray ? env->GetBooleanArrayElements(jarray, nullptr) : nullptr) @@ -228,6 +341,95 @@ inline JPrimitiveArrayAccessor::ElementsHolder::~ElementsHold } } +template <> +inline bool JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + check_value_not_null(v, "Boolean"); + return any_cast(v) == JNI_TRUE; +} + +template <> +inline int64_t JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + check_value_not_null(v, "Long"); + return static_cast(any_cast(v)); +} + +template <> +inline double JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + check_value_not_null(v, "Double"); + return static_cast(any_cast(v)); +} + +template <> +inline float JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + check_value_not_null(v, "Float"); + return static_cast(any_cast(v)); +} + +template <> +inline StringData JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + if (!v.has_value()) { + return StringData(); + } + auto& value = any_cast(v); + return value; +} + +template <> +inline BinaryData JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + if (!v.has_value()) + return BinaryData(); + auto& value = any_cast(v); + return value.transform(); +} + +template <> +inline Timestamp JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + return v.has_value() ? from_milliseconds(any_cast(v)) : Timestamp(); +} + +template <> +inline RowExpr JavaAccessorContext::unbox(util::Any&, bool, bool) const +{ + REALM_TERMINATE("not supported"); +} + +template <> +inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + return v.has_value() ? util::make_optional(any_cast(v) == JNI_TRUE) : util::none; +} + +template <> +inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + return v.has_value() ? util::make_optional(static_cast(any_cast(v))) : util::none; +} + +template <> +inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + return v.has_value() ? util::make_optional(any_cast(v)) : util::none; +} + +template <> +inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +{ + return v.has_value() ? util::make_optional(any_cast(v)) : util::none; +} + +template <> +inline Mixed JavaAccessorContext::unbox(util::Any&, bool, bool) const +{ + REALM_TERMINATE("not supported"); +} + } // namespace realm } // namespace _impl diff --git a/realm/realm-library/src/main/cpp/java_class_global_def.cpp b/realm/realm-library/src/main/cpp/java_class_global_def.cpp new file mode 100644 index 0000000000..d1ecd443ca --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_class_global_def.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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 "realm/array_blob.hpp" + +#include "java_class_global_def.hpp" +#include "java_exception_def.hpp" +#include "jni_util/java_exception_thrower.hpp" + +using namespace realm; +using namespace realm::_impl; + +jbyteArray JavaClassGlobalDef::new_byte_array(JNIEnv* env, const BinaryData& binary_data) +{ + static_assert(MAX_JSIZE >= ArrayBlob::max_binary_size, "ArrayBlob's max size is too big."); + + if (binary_data.is_null()) { + return nullptr; + } + + auto size = static_cast(binary_data.size()); + jbyteArray ret = env->NewByteArray(size); + if (!ret) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::OutOfMemory, + util::format("'NewByteArray' failed with size %1.", size)); + } + + env->SetByteArrayRegion(ret, 0, size, reinterpret_cast(binary_data.data())); + return ret; +} diff --git a/realm/realm-library/src/main/cpp/java_class_global_def.hpp b/realm/realm-library/src/main/cpp/java_class_global_def.hpp index d47e5dc5d4..268082de8e 100644 --- a/realm/realm-library/src/main/cpp/java_class_global_def.hpp +++ b/realm/realm-library/src/main/cpp/java_class_global_def.hpp @@ -26,6 +26,9 @@ #include namespace realm { + +class BinaryData; + namespace _impl { // Manage a global static jclass pool which will be initialized when JNI_OnLoad() called. @@ -46,6 +49,7 @@ class JavaClassGlobalDef { , m_java_lang_double(env, "java/lang/Double", false) , m_java_util_date(env, "java/util/Date", false) , m_java_lang_string(env, "java/lang/String", false) + , m_java_lang_boolean(env, "java/lang/Boolean", false) , m_shared_realm_schema_change_callback(env, "io/realm/internal/SharedRealm$SchemaChangedCallback", false) , m_realm_notifier(env, "io/realm/internal/RealmNotifier", false) { @@ -56,6 +60,7 @@ class JavaClassGlobalDef { jni_util::JavaClass m_java_lang_double; jni_util::JavaClass m_java_util_date; jni_util::JavaClass m_java_lang_string; + jni_util::JavaClass m_java_lang_boolean; jni_util::JavaClass m_shared_realm_schema_change_callback; jni_util::JavaClass m_realm_notifier; @@ -113,9 +118,24 @@ class JavaClassGlobalDef { return instance()->m_java_lang_double; } + // java.lang.Boolean + inline static jobject new_boolean(JNIEnv* env, bool value) + { + static jni_util::JavaMethod init(env, instance()->m_java_lang_boolean, "", "(Z)V"); + return env->NewObject(instance()->m_java_lang_boolean, init, value ? JNI_TRUE : JNI_FALSE); + } + inline static const jni_util::JavaClass& java_lang_boolean() + { + return instance()->m_java_lang_boolean; + } + // java.util.Date + // return nullptr if ts is null inline static jobject new_date(JNIEnv* env, const realm::Timestamp& ts) { + if (ts.is_null()) { + return nullptr; + } static jni_util::JavaMethod init(env, instance()->m_java_util_date, "", "(J)V"); return env->NewObject(instance()->m_java_util_date, init, to_milliseconds(ts)); } @@ -130,6 +150,10 @@ class JavaClassGlobalDef { return instance()->m_java_lang_string; } + // byte[] + // return nullptr if binary_data is null + static jbyteArray new_byte_array(JNIEnv* env, const BinaryData& binary_data); + // io.realm.internal.SharedRealm.SchemaChangedCallback inline static const jni_util::JavaClass& shared_realm_schema_change_callback() { diff --git a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp new file mode 100644 index 0000000000..ed53c62c9f --- /dev/null +++ b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp @@ -0,0 +1,105 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_IMPL_OBSERVABLE_COLLECTION_WRAPPER_HPP +#define REALM_JNI_IMPL_OBSERVABLE_COLLECTION_WRAPPER_HPP + +#include "jni_util/java_class.hpp" +#include "jni_util/java_global_weak_ref.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/log.hpp" + +namespace realm { +namespace _impl { + +// Wrapper of Object Store List & Results. +// We need to control the life cycle of Results/List, weak ref of Java Collection object and the NotificationToken. +// Wrap all three together, so when the Java Collection object gets GCed, all three of them will be invalidated. +template +class ObservableCollectionWrapper { +public: + ObservableCollectionWrapper(T& collection) + : m_collection_weak_ref() + , m_notification_token() + , m_collection(std::move(collection)) + { + } + + ~ObservableCollectionWrapper() = default; + + ObservableCollectionWrapper(ObservableCollectionWrapper&&) = delete; + ObservableCollectionWrapper& operator=(ObservableCollectionWrapper&&) = delete; + ObservableCollectionWrapper(ObservableCollectionWrapper const&) = delete; + ObservableCollectionWrapper& operator=(ObservableCollectionWrapper const&) = delete; + + T& collection() + { + return m_collection; + }; + void start_listening(JNIEnv* env, jobject j_collection_object); + void stop_listening(); + +private: + jni_util::JavaGlobalWeakRef m_collection_weak_ref; + NotificationToken m_notification_token; + T m_collection; +}; + +template +void ObservableCollectionWrapper::start_listening(JNIEnv* env, jobject j_collection_object) +{ + static jni_util::JavaClass os_results_class(env, "io/realm/internal/ObservableCollection"); + static jni_util::JavaMethod notify_change_listeners(env, os_results_class, "notifyChangeListeners", "(J)V"); + + if (!m_collection_weak_ref) { + m_collection_weak_ref = jni_util::JavaGlobalWeakRef(env, j_collection_object); + } + + auto cb = [=](CollectionChangeSet const& changes, std::exception_ptr err) { + // OS will call all notifiers' callback in one run, so check the Java exception first!! + if (env->ExceptionCheck()) + return; + + if (err) { + try { + std::rethrow_exception(err); + } + catch (const std::exception& e) { + realm::jni_util::Log::e("Caught exception in collection change callback %1", e.what()); + return; + } + } + + m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv* local_env, jobject collection_obj) { + local_env->CallVoidMethod( + collection_obj, notify_change_listeners, + reinterpret_cast(changes.empty() ? 0 : new CollectionChangeSet(changes))); + }); + }; + + m_notification_token = m_collection.add_notification_callback(cb); +} + +template +void ObservableCollectionWrapper::stop_listening() +{ + m_notification_token = {}; +} + +} // namespace realm +} // namespace _impl + +#endif // REALM_JNI_IMPL_OBSERVABLE_COLLECTION_WRAPPER_HPP diff --git a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp b/realm/realm-library/src/main/cpp/tablebase_tpl.hpp deleted file mode 100644 index 54db725c51..0000000000 --- a/realm/realm-library/src/main/cpp/tablebase_tpl.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -#ifndef REALM_JNI_TABLEBASE_TPL_HPP -#define REALM_JNI_TABLEBASE_TPL_HPP - -#include - -template -jbyteArray tbl_GetByteArray(JNIEnv* env, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) -{ - if (!TBL_AND_INDEX_VALID(env, reinterpret_cast(nativeTablePtr), columnIndex, rowIndex)) { - return nullptr; - } - - realm::BinaryData bin = reinterpret_cast(nativeTablePtr)->get_binary(S(columnIndex), S(rowIndex)); - if (bin.is_null()) { - return nullptr; - } - if (bin.size() <= MAX_JSIZE) { - jbyteArray jresult = env->NewByteArray(static_cast(bin.size())); - if (jresult) { - env->SetByteArrayRegion(jresult, 0, static_cast(bin.size()), - reinterpret_cast(bin.data())); // throws - } - return jresult; - } - else { - ThrowException(env, IllegalArgument, "Length of ByteArray is larger than an Int."); - return nullptr; - } -} - -#endif // REALM_JNI_TABLEBASE_TPL_HPP diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 72fd8b3ea5..f21d1bfc09 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -110,7 +110,15 @@ void ConvertException(JNIEnv* env, const char* file, int line) ThrowException(env, IllegalState, ss.str()); } catch (realm::LogicError e) { - ThrowException(env, IllegalState, e.what()); + ExceptionKind kind; + if (e.kind() == LogicError::string_too_big || e.kind() == LogicError::binary_too_big || + e.kind() == LogicError::column_not_nullable) { + kind = IllegalArgument; + } + else { + kind = IllegalState; + } + ThrowException(env, kind, e.what()); } catch (std::logic_error e) { ThrowException(env, IllegalState, e.what()); @@ -255,23 +263,6 @@ void ThrowNullValueException(JNIEnv* env, Table* table, size_t col_ndx) ThrowException(env, IllegalArgument, ss.str()); } -bool GetBinaryData(JNIEnv* env, jobject jByteBuffer, realm::BinaryData& bin) -{ - const char* data = static_cast(env->GetDirectBufferAddress(jByteBuffer)); - if (!data) { - ThrowException(env, IllegalArgument, "ByteBuffer is invalid"); - return false; - } - jlong size = env->GetDirectBufferCapacity(jByteBuffer); - if (size < 0) { - ThrowException(env, IllegalArgument, "Can't get BufferCapacity."); - return false; - } - bin = BinaryData(data, S(size)); - return true; -} - - //********************************************************************* // String handling //********************************************************************* @@ -473,7 +464,7 @@ JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) buf_size = Xcode::find_utf8_buf_size(begin, end, error_code); } char* tmp_char_array = new char[buf_size]; // throws - m_data.reset(tmp_char_array); + m_data.reset(tmp_char_array, std::default_delete()); { const jchar* in_begin = chars.data(); const jchar* in_end = in_begin + chars.size(); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index c5c536f5b3..21bb35c354 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -433,9 +433,6 @@ inline bool TblIndexAndTypeInsertValid(JNIEnv* env, T* pTable, jlong columnIndex TypeValid(env, pTable, columnIndex, expectColType); } -bool GetBinaryData(JNIEnv* env, jobject jByteBuffer, realm::BinaryData& data); - - // Utility function for appending StringData, which is returned // by a lot of core functions, and might potentially be NULL. std::string concat_stringdata(const char* message, realm::StringData data); @@ -488,7 +485,7 @@ class JStringAccessor { private: JNIEnv* m_env; bool m_is_null; - std::unique_ptr m_data; + std::shared_ptr m_data; std::size_t m_size; }; diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index f924430967..20b9a54a79 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -337,18 +337,18 @@ public DynamicRealmObject getObject(String fieldName) { } /** - * Returns the {@link RealmList} of objects being linked to from this field. + * Returns the {@link RealmList} of {@link DynamicRealmObject}s being linked from the given field. * * @param fieldName the name of the field. * @return the {@link RealmList} data for this field. - * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain a list of links. + * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain a list of objects. */ public RealmList getList(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { - OsList osList = proxyState.getRow$realm().getLinkList(columnIndex); + OsList osList = proxyState.getRow$realm().getList(columnIndex); //noinspection ConstantConditions @Nonnull String className = osList.getTargetTable().getClassName(); @@ -359,6 +359,18 @@ public RealmList getList(String fieldName) { } } + /** + * Returns the {@link RealmList} of values being linked from the given field. + * + * @param fieldName the name of the field. + * @return the {@link RealmList} data for this field. + * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain a list of values. + */ + public RealmList getValueList(String fieldName, Class valueClass) { + // TODO implement this + return null; + } + /** * Checks if the value of a given field is {@code null}. * @@ -501,9 +513,29 @@ private void setValue(String fieldName, Object value) { } else if (valueClass == DynamicRealmObject.class) { setObject(fieldName, (DynamicRealmObject) value); } else if (valueClass == RealmList.class) { - @SuppressWarnings("unchecked") - RealmList list = (RealmList) value; - setList(fieldName, list); + RealmList list = (RealmList) value; + if (list.className == null && list.clazz == null) { + // unmanaged RealmList + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + final RealmFieldType columnType = proxyState.getRow$realm().getColumnType(columnIndex); + if (columnType == RealmFieldType.LIST) { + //noinspection unchecked + for (Object element : list) { + if (!(element instanceof RealmModel)) { + throw new IllegalArgumentException("All elements in the list must be an instance of RealmModel."); + } + } + //noinspection unchecked + setList(fieldName, (RealmList) list); + } else { + setValueList(fieldName, list); + } + } else if (list.className != null || RealmModel.class.isAssignableFrom(list.clazz)) { + //noinspection unchecked + setList(fieldName, (RealmList) list); + } else { + setValueList(fieldName, list); + } } else { throw new IllegalArgumentException("Value is of an type not supported: " + value.getClass()); } @@ -713,7 +745,7 @@ public void setList(String fieldName, RealmList list) { } long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - OsList osList = proxyState.getRow$realm().getLinkList(columnIndex); + OsList osList = proxyState.getRow$realm().getList(columnIndex); Table linkTargetTable = osList.getTargetTable(); //noinspection ConstantConditions @Nonnull @@ -760,6 +792,19 @@ public void setList(String fieldName, RealmList list) { } } + /** + * Sets the reference to a {@link RealmList} on the given field. + * + * @param fieldName field name. + * @param list list of references. + * @throws IllegalArgumentException if field name doesn't exist, it is not a list field, the type + * of the object represented by the DynamicRealmObject doesn't match or any element in the list belongs to a + * different Realm. + */ + public void setValueList(String fieldName, RealmList list) { + // TODO implement this + } + /** * Sets the value to {@code null} for the given field. * @@ -923,7 +968,7 @@ public String toString() { break; case LIST: String targetClassName = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName(); - sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getLinkList(columnIndex).size())); + sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getList(columnIndex).size())); break; case UNSUPPORTED_TABLE: case UNSUPPORTED_MIXED: diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java index 4034f5c062..0024658bd9 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollection.java @@ -100,7 +100,7 @@ * As you can see, after deletion, the size and elements order of snapshot stay the same as before. But the element at * the position becomes invalid. */ -public interface OrderedRealmCollection extends List, RealmCollection { +public interface OrderedRealmCollection extends List, RealmCollection { /** * Gets the first object from the collection. @@ -108,6 +108,7 @@ public interface OrderedRealmCollection extends List, R * @return the first object. * @throws IndexOutOfBoundsException if the collection is empty. */ + @Nullable E first(); /** @@ -124,6 +125,7 @@ public interface OrderedRealmCollection extends List, R * @return the last object. * @throws IndexOutOfBoundsException if the collection is empty. */ + @Nullable E last(); /** diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java index b9a5261c85..24216e776d 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java @@ -16,6 +16,8 @@ package io.realm; +import javax.annotation.Nullable; + /** * {@link OrderedRealmCollectionChangeListener} can be registered with a {@link RealmResults} to receive a notification * with a {@link OrderedCollectionChangeSet} to describe the details of what have been changed in the collection from @@ -36,5 +38,5 @@ public interface OrderedRealmCollectionChangeListener { * @param changeSet object with information about which rows in the collection were added, removed or modified. * {@code null} is returned the first time an async query is completed. */ - void onChange(T t, OrderedCollectionChangeSet changeSet); + void onChange(T t, @Nullable OrderedCollectionChangeSet changeSet); } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index 2f6d096990..39163349d9 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -9,6 +9,7 @@ import javax.annotation.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.Collection; import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; @@ -20,7 +21,7 @@ /** * General implementation for {@link OrderedRealmCollection} which is based on the {@code Collection}. */ -abstract class OrderedRealmCollectionImpl +abstract class OrderedRealmCollectionImpl extends AbstractList implements OrderedRealmCollection { private final static String NOT_SUPPORTED_MESSAGE = "This method is not supported by 'RealmResults' or" + " 'OrderedRealmCollectionSnapshot'."; @@ -28,6 +29,9 @@ abstract class OrderedRealmCollectionImpl final BaseRealm realm; @Nullable final Class classSpec; // Return type @Nullable final String className; // Class name used by DynamicRealmObjects + // FIXME implement this + @SuppressFBWarnings("SS_SHOULD_BE_STATIC") + final boolean forValues = false; final Collection collection; @@ -108,15 +112,23 @@ public boolean contains(@Nullable Object object) { * @throws IndexOutOfBoundsException if {@code location < 0 || location >= size()}. */ @Override + @Nullable public E get(int location) { realm.checkIfValid(); - return realm.get(classSpec, className, collection.getUncheckedRow(location)); + if (forValues) { + // TODO implement this + return null; + } + + //noinspection unchecked + return (E) realm.get((Class) classSpec, className, collection.getUncheckedRow(location)); } /** * {@inheritDoc} */ @Override + @Nullable public E first() { return firstImpl(true, null); } @@ -134,8 +146,14 @@ public E first(@Nullable E defaultValue) { private E firstImpl(boolean shouldThrow, @Nullable E defaultValue) { UncheckedRow row = collection.firstUncheckedRow(); + if (forValues) { + // TODO implement this + return null; + } + if (row != null) { - return realm.get(classSpec, className, row); + //noinspection unchecked + return (E) realm.get((Class) classSpec, className, row); } else { if (shouldThrow) { throw new IndexOutOfBoundsException("No results were found."); @@ -149,6 +167,7 @@ private E firstImpl(boolean shouldThrow, @Nullable E defaultValue) { * {@inheritDoc} */ @Override + @Nullable public E last() { return lastImpl(true, null); } @@ -167,8 +186,14 @@ public E last(@Nullable E defaultValue) { private E lastImpl(boolean shouldThrow, @Nullable E defaultValue) { UncheckedRow row = collection.lastUncheckedRow(); + if (forValues) { + // TODO implement this + return null; + } + if (row != null) { - return realm.get(classSpec, className, row); + //noinspection unchecked + return (E) realm.get((Class) classSpec, className, row); } else { if (shouldThrow) { throw new IndexOutOfBoundsException("No results were found."); @@ -535,7 +560,12 @@ private class RealmCollectionIterator extends Collection.Iterator { @Override protected E convertRowToObject(UncheckedRow row) { - return realm.get(classSpec, className, row); + if (forValues) { + // TODO implement this + return null; + } + //noinspection unchecked + return (E) realm.get((Class) classSpec, className, row); } } @@ -558,7 +588,12 @@ private class RealmCollectionListIterator extends Collection.ListIterator { @Override protected E convertRowToObject(UncheckedRow row) { - return realm.get(classSpec, className, row); + if (forValues) { + // TODO implement this + return null; + } + //noinspection unchecked + return (E) realm.get((Class) classSpec, className, row); } } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java index 6a2989647d..5e4ce01545 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java @@ -48,7 +48,7 @@ * } * */ -public class OrderedRealmCollectionSnapshot extends OrderedRealmCollectionImpl { +public class OrderedRealmCollectionSnapshot extends OrderedRealmCollectionImpl { private int size = -1; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index f95bb86647..6644275d38 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -432,6 +432,9 @@ static Realm createInstance(SharedRealm sharedRealm) { * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the * JSON object the {@link RealmObject} field will be set to the default value for that type. * + *

            + * This method currently does not support value list field. + * * @param clazz type of Realm objects to create. * @param json an array where each JSONObject must map to the specified class. * @throws RealmException if mapping from JSON fails. @@ -461,6 +464,9 @@ public void createAllFromJson(Class clazz, JSONArray j * a new {@link RealmObject} is created and a field is not found in the JSON object, that field will be assigned the * default value for the field type. * + *

            + * This method currently does not support value list field. + * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json array with object data. * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. @@ -490,6 +496,9 @@ public void createOrUpdateAllFromJson(Class clazz, JSO * JSON properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the * JSON object the {@link RealmObject} field will be set to the default value for that type. * + *

            + * This method currently does not support value list field. + * * @param clazz type of Realm objects to create. * @param json the JSON array as a String where each object can map to the specified class. * @throws RealmException if mapping from JSON fails. @@ -519,6 +528,9 @@ public void createAllFromJson(Class clazz, String json * If a new {@link RealmObject} is created and a field is not found in the JSON object, that field will be assigned * the default value for the field type. * + *

            + * This method currently does not support value list field. + * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json string with an array of JSON objects. * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. @@ -552,6 +564,9 @@ public void createOrUpdateAllFromJson(Class clazz, Str *

            * This API is only available in API level 11 or later. * + *

            + * This method currently does not support value list field. + * * @param clazz type of Realm objects created. * @param inputStream the JSON array as a InputStream. All objects in the array must be of the specified class. * @throws RealmException if mapping from JSON fails. @@ -588,6 +603,9 @@ public void createAllFromJson(Class clazz, InputStream *

            * This API is only available in API level 11 or later. * + *

            + * This method currently does not support value list field. + * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the InputStream with a list of object data in JSON format. * @throws IllegalArgumentException if trying to update a class without a {@link io.realm.annotations.PrimaryKey}. @@ -628,6 +646,9 @@ public void createOrUpdateAllFromJson(Class clazz, Inp * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON * object the {@link RealmObject} field will be set to the default value for that type. * + *

            + * This method currently does not support value list field. + * * @param clazz type of Realm object to create. * @param json the JSONObject with object data. * @return created object or {@code null} if no JSON data was provided. @@ -657,6 +678,9 @@ public E createObjectFromJson(Class clazz, JSONObject * and a field is not found in the JSON object, that field will not be updated. If a new {@link RealmObject} is * created and a field is not found in the JSON object, that field will be assigned the default value for the field type. * + *

            + * This method currently does not support value list field. + * * @param clazz Type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json {@link org.json.JSONObject} with object data. * @return created or updated {@link io.realm.RealmObject}. @@ -685,6 +709,9 @@ public E createOrUpdateObjectFromJson(Class clazz, JSO * properties with unknown properties will be ignored. If a {@link RealmObject} field is not present in the JSON * object the {@link RealmObject} field will be set to the default value for that type. * + *

            + * This method currently does not support value list field. + * * @param clazz type of Realm object to create. * @param json the JSON string with object data. * @return created object or {@code null} if JSON string was empty or null. @@ -716,6 +743,9 @@ public E createObjectFromJson(Class clazz, String json * {@link RealmObject} is created and a field is not found in the JSON object, that field will be assigned the * default value for the field type. * + *

            + * This method currently does not support value list field. + * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param json string with object data in JSON format. * @return created or updated {@link io.realm.RealmObject}. @@ -750,6 +780,9 @@ public E createOrUpdateObjectFromJson(Class clazz, Str *

            * This API is only available in API level 11 or later. * + *

            + * This method currently does not support value list field. + * * @param clazz type of Realm object to create. * @param inputStream the JSON object data as a InputStream. * @return created object or {@code null} if JSON string was empty or null. @@ -805,6 +838,9 @@ public E createObjectFromJson(Class clazz, InputStream *

            * This API is only available in API level 11 or later. * + *

            + * This method currently does not support value list field. + * * @param clazz type of {@link io.realm.RealmObject} to create or update. It must have a primary key defined. * @param in the {@link InputStream} with object data in JSON format. * @return created or updated {@link io.realm.RealmObject}. diff --git a/realm/realm-library/src/main/java/io/realm/RealmCollection.java b/realm/realm-library/src/main/java/io/realm/RealmCollection.java index 55e6bc687e..7516d2b3bc 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCollection.java @@ -35,7 +35,7 @@ * * @param type of {@link RealmObject} stored in the collection. */ -public interface RealmCollection extends Collection, ManagableObject { +public interface RealmCollection extends Collection, ManagableObject { /** * Returns a {@link RealmQuery}, which can be used to query for specific objects from this collection. diff --git a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java index 38b923d97e..f28c4b5c52 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java +++ b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java @@ -18,10 +18,45 @@ import java.nio.ByteBuffer; -import javax.annotation.Nullable; - import io.realm.internal.Keep; +import io.realm.internal.Property; + +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_BINARY; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_BOOLEAN; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_DATE; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_DOUBLE; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_FLOAT; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_INTEGER; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_LINKING_OBJECTS; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_LIST; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_OBJECT; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_STRING; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_UNSUPPORTED_DATE; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_UNSUPPORTED_MIXED; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_UNSUPPORTED_TABLE; +import static io.realm.RealmFieldTypeConstants.LIST_OFFSET; +import static io.realm.RealmFieldTypeConstants.MAX_CORE_TYPE_VALUE; + +interface RealmFieldTypeConstants { + int LIST_OFFSET = Property.TYPE_ARRAY; + + int CORE_TYPE_VALUE_INTEGER = 0; + int CORE_TYPE_VALUE_BOOLEAN = 1; + int CORE_TYPE_VALUE_STRING = 2; + int CORE_TYPE_VALUE_BINARY = 4; + int CORE_TYPE_VALUE_UNSUPPORTED_TABLE = 5; + int CORE_TYPE_VALUE_UNSUPPORTED_MIXED = 6; + int CORE_TYPE_VALUE_UNSUPPORTED_DATE = 7; + int CORE_TYPE_VALUE_DATE = 8; + int CORE_TYPE_VALUE_FLOAT = 9; + int CORE_TYPE_VALUE_DOUBLE = 10; + int CORE_TYPE_VALUE_OBJECT = 12; + int CORE_TYPE_VALUE_LIST = 13; + int CORE_TYPE_VALUE_LINKING_OBJECTS = 14; + + int MAX_CORE_TYPE_VALUE = CORE_TYPE_VALUE_LINKING_OBJECTS; +} /** * List of the types used by Realm's underlying storage engine. @@ -33,27 +68,41 @@ @Keep public enum RealmFieldType { // Makes sure numbers match with . - INTEGER(0), - BOOLEAN(1), - STRING(2), - BINARY(4), - UNSUPPORTED_TABLE(5), - UNSUPPORTED_MIXED(6), - UNSUPPORTED_DATE(7), - DATE(8), - FLOAT(9), - DOUBLE(10), - OBJECT(12), - LIST(13), - LINKING_OBJECTS(14); + INTEGER(CORE_TYPE_VALUE_INTEGER), + BOOLEAN(CORE_TYPE_VALUE_BOOLEAN), + STRING(CORE_TYPE_VALUE_STRING), + BINARY(CORE_TYPE_VALUE_BINARY), + UNSUPPORTED_TABLE(CORE_TYPE_VALUE_UNSUPPORTED_TABLE), + UNSUPPORTED_MIXED(CORE_TYPE_VALUE_UNSUPPORTED_MIXED), + UNSUPPORTED_DATE(CORE_TYPE_VALUE_UNSUPPORTED_DATE), + DATE(CORE_TYPE_VALUE_DATE), + FLOAT(CORE_TYPE_VALUE_FLOAT), + DOUBLE(CORE_TYPE_VALUE_DOUBLE), + OBJECT(CORE_TYPE_VALUE_OBJECT), + + LIST(CORE_TYPE_VALUE_LIST), + LINKING_OBJECTS(CORE_TYPE_VALUE_LINKING_OBJECTS), + + INTEGER_LIST(CORE_TYPE_VALUE_INTEGER + LIST_OFFSET), + BOOLEAN_LIST(CORE_TYPE_VALUE_BOOLEAN + LIST_OFFSET), + STRING_LIST(CORE_TYPE_VALUE_STRING + LIST_OFFSET), + BINARY_LIST(CORE_TYPE_VALUE_BINARY + LIST_OFFSET), + DATE_LIST(CORE_TYPE_VALUE_DATE + LIST_OFFSET), + FLOAT_LIST(CORE_TYPE_VALUE_FLOAT + LIST_OFFSET), + DOUBLE_LIST(CORE_TYPE_VALUE_DOUBLE + LIST_OFFSET); // Primitive array for fast mapping between between native values and their Realm type. - private static final RealmFieldType[] typeList = new RealmFieldType[15]; + private static final RealmFieldType[] basicTypes = new RealmFieldType[MAX_CORE_TYPE_VALUE + 1]; + private static final RealmFieldType[] listTypes = new RealmFieldType[MAX_CORE_TYPE_VALUE + 1]; static { - RealmFieldType[] columnTypes = values(); - for (int i = 0; i < columnTypes.length; i++) { - typeList[columnTypes[i].nativeValue] = columnTypes[i]; + for (RealmFieldType columnType : values()) { + final int nativeValue = columnType.nativeValue; + if (nativeValue < LIST_OFFSET) { + basicTypes[nativeValue] = columnType; + } else { + listTypes[nativeValue - LIST_OFFSET] = columnType; + } } } @@ -80,30 +129,44 @@ public int getNativeValue() { */ public boolean isValid(Object obj) { switch (nativeValue) { - case 0: + case CORE_TYPE_VALUE_INTEGER: return (obj instanceof Long || obj instanceof Integer || obj instanceof Short || obj instanceof Byte); - case 1: + case CORE_TYPE_VALUE_BOOLEAN: return (obj instanceof Boolean); - case 2: + case CORE_TYPE_VALUE_STRING: return (obj instanceof String); - case 4: + case CORE_TYPE_VALUE_BINARY: return (obj instanceof byte[] || obj instanceof ByteBuffer); - case 5: + case CORE_TYPE_VALUE_UNSUPPORTED_TABLE: //noinspection ConstantConditions return (obj == null || obj instanceof Object[][]); - case 7: + case CORE_TYPE_VALUE_UNSUPPORTED_DATE: return (obj instanceof java.util.Date); // The unused DateTime. - case 8: + case CORE_TYPE_VALUE_DATE: return (obj instanceof java.util.Date); - case 9: + case CORE_TYPE_VALUE_FLOAT: return (obj instanceof Float); - case 10: + case CORE_TYPE_VALUE_DOUBLE: return (obj instanceof Double); - case 12: + case CORE_TYPE_VALUE_OBJECT: return false; - case 13: + case CORE_TYPE_VALUE_LIST: return false; - case 14: + case CORE_TYPE_VALUE_LINKING_OBJECTS: + return false; + case CORE_TYPE_VALUE_INTEGER + LIST_OFFSET: + return false; + case CORE_TYPE_VALUE_BOOLEAN + LIST_OFFSET: + return false; + case CORE_TYPE_VALUE_STRING + LIST_OFFSET: + return false; + case CORE_TYPE_VALUE_BINARY + LIST_OFFSET: + return false; + case CORE_TYPE_VALUE_DATE + LIST_OFFSET: + return false; + case CORE_TYPE_VALUE_FLOAT + LIST_OFFSET: + return false; + case CORE_TYPE_VALUE_DOUBLE + LIST_OFFSET: return false; default: throw new RuntimeException("Unsupported Realm type: " + this); @@ -118,12 +181,21 @@ public boolean isValid(Object obj) { * @throws IllegalArgumentException if value isn't valid. */ public static RealmFieldType fromNativeValue(int value) { - if (0 <= value && value < typeList.length) { - RealmFieldType e = typeList[value]; + if (0 <= value && value < basicTypes.length) { + RealmFieldType e = basicTypes[value]; if (e != null) { return e; } } + if (LIST_OFFSET <= value) { + final int elementValue = value - LIST_OFFSET; + if (elementValue < listTypes.length) { + RealmFieldType e = listTypes[elementValue]; + if (e != null) { + return e; + } + } + } throw new IllegalArgumentException("Invalid native Realm type: " + value); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 78499e64b5..7bcbe78c35 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -28,17 +28,18 @@ import java.util.Locale; import java.util.NoSuchElementException; -import io.reactivex.Flowable; -import io.reactivex.Observable; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import io.reactivex.Flowable; +import io.reactivex.Observable; import io.realm.internal.InvalidRow; import io.realm.internal.OsList; import io.realm.internal.OsObjectStore; import io.realm.internal.RealmObjectProxy; import io.realm.rx.CollectionChange; + /** * RealmList is used to model one-to-many relationships in a {@link io.realm.RealmObject}. * RealmList has two modes: A managed and unmanaged mode. In managed mode all objects are persisted inside a Realm, in @@ -57,20 +58,23 @@ * @param the class of objects in list. */ -public class RealmList extends AbstractList implements OrderedRealmCollection { +public class RealmList extends AbstractList implements OrderedRealmCollection { - private static final String ONLY_IN_MANAGED_MODE_MESSAGE = "This method is only available in managed mode"; - private static final String NULL_OBJECTS_NOT_ALLOWED_MESSAGE = "RealmList does not accept null values"; - public static final String REMOVE_OUTSIDE_TRANSACTION_ERROR = "Objects can only be removed from inside a write transaction"; + private static final String ONLY_IN_MANAGED_MODE_MESSAGE = "This method is only available in managed mode."; + static final String ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE = "This feature is available only when the element type is implementing RealmModel."; + public static final String REMOVE_OUTSIDE_TRANSACTION_ERROR = "Objects can only be removed from inside a write transaction."; - private final io.realm.internal.Collection collection; @Nullable protected Class clazz; @Nullable protected String className; - final OsList osList; + + // Always null if RealmList is unmanaged, always non-null if managed. + private final ManagedListOperator osListOperator; final protected BaseRealm realm; private List unmanagedList; + // Used for listeners on RealmList + private io.realm.internal.Collection osResults; /** * Creates a RealmList in unmanaged mode, where the elements are not controlled by a Realm. @@ -80,9 +84,8 @@ public class RealmList extends AbstractList implements * Use {@link io.realm.Realm#copyToRealm(Iterable)} to properly persist its elements in Realm. */ public RealmList() { - collection = null; - osList = null; realm = null; + osListOperator = null; unmanagedList = new ArrayList<>(); } @@ -95,14 +98,14 @@ public RealmList() { * * @param objects initial objects in the list. */ + @SafeVarargs public RealmList(E... objects) { //noinspection ConstantConditions if (objects == null) { throw new IllegalArgumentException("The objects argument cannot be null"); } - collection = null; - osList = null; realm = null; + osListOperator = null; unmanagedList = new ArrayList<>(objects.length); Collections.addAll(unmanagedList, objects); } @@ -115,17 +118,19 @@ public RealmList(E... objects) { * @param realm reference to Realm containing the data. */ RealmList(Class clazz, OsList osList, BaseRealm realm) { - this.collection = new io.realm.internal.Collection(realm.sharedRealm, osList, null); this.clazz = clazz; - this.osList = osList; + osListOperator = getOperator(realm, osList, clazz, null); this.realm = realm; } RealmList(String className, OsList osList, BaseRealm realm) { - this.collection = new io.realm.internal.Collection(realm.sharedRealm, osList, null); - this.osList = osList; this.realm = realm; this.className = className; + osListOperator = getOperator(realm, osList, null, className); + } + + OsList getOsList() { + return osListOperator.getOsList(); } /** @@ -152,7 +157,7 @@ public boolean isManaged() { } private boolean isAttached() { - return osList != null && osList.isValid(); + return osListOperator != null && osListOperator.isValid(); } /** @@ -169,22 +174,18 @@ private boolean isAttached() { * * * @param location the index at which to insert. - * @param object the object to add. + * @param element the element to add. * @throws IllegalStateException if Realm instance has been closed or container object has been removed. * @throws IndexOutOfBoundsException if {@code location < 0 || location > size()}. */ @Override - public void add(int location, E object) { - checkValidObject(object); + public void add(int location, @Nullable E element) { + //noinspection ConstantConditions if (isManaged()) { checkValidRealm(); - if (location < 0 || location > size()) { - throw new IndexOutOfBoundsException("Invalid index " + location + ", size is " + size()); - } - RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded(object); - osList.insertRow(location, proxy.realmGet$proxyState().getRow$realm().getIndex()); + osListOperator.insert(location, element); } else { - unmanagedList.add(location, object); + unmanagedList.add(location, element); } modCount++; } @@ -205,12 +206,10 @@ public void add(int location, E object) { * @throws IllegalStateException if Realm instance has been closed or parent object has been removed. */ @Override - public boolean add(E object) { - checkValidObject(object); + public boolean add(@Nullable E object) { if (isManaged()) { checkValidRealm(); - RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded(object); - osList.addRow(proxy.realmGet$proxyState().getRow$realm().getIndex()); + osListOperator.append(object); } else { unmanagedList.add(object); } @@ -236,70 +235,17 @@ public boolean add(E object) { * @throws IndexOutOfBoundsException if {@code location < 0 || location >= size()}. */ @Override - public E set(int location, E object) { - checkValidObject(object); + public E set(int location, @Nullable E object) { E oldObject; if (isManaged()) { checkValidRealm(); - RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded(object); - oldObject = get(location); - osList.setRow(location, proxy.realmGet$proxyState().getRow$realm().getIndex()); - return oldObject; + oldObject = osListOperator.set(location, object); } else { oldObject = unmanagedList.set(location, object); } return oldObject; } - // Transparently copies an unmanaged object or managed object from another Realm to the Realm backing this RealmList. - private E copyToRealmIfNeeded(E object) { - if (object instanceof RealmObjectProxy) { - RealmObjectProxy proxy = (RealmObjectProxy) object; - - if (proxy instanceof DynamicRealmObject) { - //noinspection ConstantConditions - @Nonnull - String listClassName = className; - if (proxy.realmGet$proxyState().getRealm$realm() == realm) { - String objectClassName = ((DynamicRealmObject) object).getType(); - if (listClassName.equals(objectClassName)) { - // Same Realm instance and same target table - return object; - } else { - // Different target table - throw new IllegalArgumentException(String.format(Locale.US, - "The object has a different type from list's." + - " Type of the list is '%s', type of object is '%s'.", listClassName, objectClassName)); - } - } else if (realm.threadId == proxy.realmGet$proxyState().getRealm$realm().threadId) { - // We don't support moving DynamicRealmObjects across Realms automatically. The overhead is too big as - // you have to run a full schema validation for each object. - // And copying from another Realm instance pointed to the same Realm file is not supported as well. - throw new IllegalArgumentException("Cannot copy DynamicRealmObject between Realm instances."); - } else { - throw new IllegalStateException("Cannot copy an object to a Realm instance created in another thread."); - } - } else { - // Object is already in this realm - if (proxy.realmGet$proxyState().getRow$realm() != null && proxy.realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - if (realm != proxy.realmGet$proxyState().getRealm$realm()) { - throw new IllegalArgumentException("Cannot copy an object from another Realm instance."); - } - return object; - } - } - } - - // At this point the object can only be a typed object, so the backing Realm cannot be a DynamicRealm. - Realm realm = (Realm) this.realm; - if (OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), - realm.getConfiguration().getSchemaMediator().getSimpleClassName(object.getClass())) != null) { - return realm.copyToRealmOrUpdate(object); - } else { - return realm.copyToRealm(object); - } - } - /** * Moves an object from one position to another, while maintaining a fixed sized list. * RealmObjects will be shifted so no {@code null} values are introduced. @@ -313,10 +259,15 @@ private E copyToRealmIfNeeded(E object) { public void move(int oldPos, int newPos) { if (isManaged()) { checkValidRealm(); - osList.move(oldPos, newPos); + osListOperator.move(oldPos, newPos); } else { - checkIndex(oldPos); - checkIndex(newPos); + final int listSize = unmanagedList.size(); + if (oldPos < 0 || listSize <= oldPos) { + throw new IndexOutOfBoundsException("Invalid index " + oldPos + ", size is " + listSize); + } + if (newPos < 0 || listSize <= newPos) { + throw new IndexOutOfBoundsException("Invalid index " + newPos + ", size is " + listSize); + } E object = unmanagedList.remove(oldPos); if (newPos > oldPos) { unmanagedList.add(newPos - 1, object); @@ -338,7 +289,7 @@ public void move(int oldPos, int newPos) { public void clear() { if (isManaged()) { checkValidRealm(); - osList.removeAll(); + osListOperator.removeAll(); } else { unmanagedList.clear(); } @@ -359,7 +310,7 @@ public E remove(int location) { if (isManaged()) { checkValidRealm(); removedItem = get(location); - osList.remove(location); + osListOperator.remove(location); } else { removedItem = unmanagedList.remove(location); } @@ -385,7 +336,7 @@ public E remove(int location) { * @throws NullPointerException if {@code object} is {@code null}. */ @Override - public boolean remove(Object object) { + public boolean remove(@Nullable Object object) { if (isManaged() && !realm.isInTransaction()) { throw new IllegalStateException(REMOVE_OUTSIDE_TRANSACTION_ERROR); } @@ -422,7 +373,7 @@ public boolean removeAll(Collection collection) { @Override public boolean deleteFirstFromRealm() { if (isManaged()) { - if (size() > 0) { + if (!osListOperator.isEmpty()) { deleteFromRealm(0); modCount++; return true; @@ -440,8 +391,8 @@ public boolean deleteFirstFromRealm() { @Override public boolean deleteLastFromRealm() { if (isManaged()) { - if (size() > 0) { - deleteFromRealm(size() - 1); + if (!osListOperator.isEmpty()) { + osListOperator.deleteLast(); modCount++; return true; } else { @@ -461,10 +412,11 @@ public boolean deleteLastFromRealm() { * @throws IndexOutOfBoundsException if {@code location < 0 || location >= size()}. */ @Override + @Nullable public E get(int location) { if (isManaged()) { checkValidRealm(); - return realm.get(clazz, className, osList.getUncheckedRow(location)); + return osListOperator.get(location); } else { return unmanagedList.get(location); } @@ -474,6 +426,7 @@ public E get(int location) { * {@inheritDoc} */ @Override + @Nullable public E first() { return firstImpl(true, null); } @@ -491,7 +444,7 @@ public E first(@Nullable E defaultValue) { private E firstImpl(boolean shouldThrow, @Nullable E defaultValue) { if (isManaged()) { checkValidRealm(); - if (!osList.isEmpty()) { + if (!osListOperator.isEmpty()) { return get(0); } } else if (unmanagedList != null && !unmanagedList.isEmpty()) { @@ -509,6 +462,7 @@ private E firstImpl(boolean shouldThrow, @Nullable E defaultValue) { * {@inheritDoc} */ @Override + @Nullable public E last() { return lastImpl(true, null); } @@ -526,8 +480,8 @@ public E last(@Nullable E defaultValue) { private E lastImpl(boolean shouldThrow, @Nullable E defaultValue) { if (isManaged()) { checkValidRealm(); - if (!osList.isEmpty()) { - return get((int) osList.size() - 1); + if (!osListOperator.isEmpty()) { + return get(osListOperator.size() - 1); } } else if (unmanagedList != null && !unmanagedList.isEmpty()) { return unmanagedList.get(unmanagedList.size() - 1); @@ -587,7 +541,7 @@ public RealmResults sort(String[] fieldNames, Sort[] sortOrders) { public void deleteFromRealm(int location) { if (isManaged()) { checkValidRealm(); - osList.delete(location); + osListOperator.delete(location); modCount++; } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -604,8 +558,7 @@ public void deleteFromRealm(int location) { public int size() { if (isManaged()) { checkValidRealm(); - long size = osList.size(); - return size < Integer.MAX_VALUE ? (int) size : Integer.MAX_VALUE; + return osListOperator.size(); } else { return unmanagedList.size(); } @@ -622,6 +575,9 @@ public int size() { public RealmQuery where() { if (isManaged()) { checkValidRealm(); + if (!osListOperator.forRealmModel()) { + throw new IllegalStateException(ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE); + } return RealmQuery.createQueryFromList(this); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); @@ -634,11 +590,8 @@ public RealmQuery where() { @Override @Nullable public Number min(String fieldName) { - if (isManaged()) { - return this.where().min(fieldName); - } else { - throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); - } + // where() throws if not managed + return where().min(fieldName); } /** @@ -647,11 +600,8 @@ public Number min(String fieldName) { @Override @Nullable public Number max(String fieldName) { - if (isManaged()) { - return this.where().max(fieldName); - } else { - throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); - } + // where() throws if not managed + return this.where().max(fieldName); } /** @@ -659,11 +609,8 @@ public Number max(String fieldName) { */ @Override public Number sum(String fieldName) { - if (isManaged()) { - return this.where().sum(fieldName); - } else { - throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); - } + // where() throws if not managed + return this.where().sum(fieldName); } /** @@ -671,11 +618,8 @@ public Number sum(String fieldName) { */ @Override public double average(String fieldName) { - if (isManaged()) { - return this.where().average(fieldName); - } else { - throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); - } + // where() throws if not managed + return this.where().average(fieldName); } /** @@ -684,11 +628,8 @@ public double average(String fieldName) { @Override @Nullable public Date maxDate(String fieldName) { - if (isManaged()) { - return this.where().maximumDate(fieldName); - } else { - throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); - } + // where() throws if not managed + return this.where().maximumDate(fieldName); } /** @@ -697,11 +638,8 @@ public Date maxDate(String fieldName) { @Override @Nullable public Date minDate(String fieldName) { - if (isManaged()) { - return this.where().minimumDate(fieldName); - } else { - throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); - } + // where() throws if not managed + return this.where().minimumDate(fieldName); } /** @@ -711,8 +649,8 @@ public Date minDate(String fieldName) { public boolean deleteAllFromRealm() { if (isManaged()) { checkValidRealm(); - if (size() > 0) { - osList.deleteAll(); + if (!osListOperator.isEmpty()) { + osListOperator.deleteAll(); modCount++; return true; } else { @@ -762,12 +700,7 @@ public boolean contains(@Nullable Object object) { } } - for (E e : this) { - if (e.equals(object)) { - return true; - } - } - return false; + return super.contains(object); } else { return unmanagedList.contains(object); } @@ -808,20 +741,6 @@ public ListIterator listIterator(int location) { } } - private void checkValidObject(E object) { - //noinspection ConstantConditions - if (object == null) { - throw new IllegalArgumentException(NULL_OBJECTS_NOT_ALLOWED_MESSAGE); - } - } - - private void checkIndex(int location) { - int size = size(); - if (location < 0 || location >= size) { - throw new IndexOutOfBoundsException("Invalid index " + location + ", size is " + size); - } - } - private void checkValidRealm() { realm.checkIfValid(); } @@ -835,47 +754,104 @@ public OrderedRealmCollectionSnapshot createSnapshot() { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); } checkValidRealm(); + if (!osListOperator.forRealmModel()) { + throw new IllegalStateException(ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE); + } if (className != null) { return new OrderedRealmCollectionSnapshot<>( realm, - new io.realm.internal.Collection(realm.sharedRealm, osList, null), + new io.realm.internal.Collection(realm.sharedRealm, osListOperator.getOsList(), null), className); } else { // 'clazz' is non-null when 'dynamicClassName' is null. //noinspection ConstantConditions return new OrderedRealmCollectionSnapshot<>( realm, - new io.realm.internal.Collection(realm.sharedRealm, osList, null), + new io.realm.internal.Collection(realm.sharedRealm, osListOperator.getOsList(), null), clazz); } } @Override public String toString() { - StringBuilder sb = new StringBuilder(); - if (isManaged()) { - // 'clazz' is non-null when 'dynamicClassName' is null. - //noinspection ConstantConditions - sb.append(className != null ? className : realm.getSchema().getSchemaForClass(clazz).getClassName()); - } else { - sb.append(getClass().getSimpleName()); - } - sb.append("@["); - if (isManaged() && !isAttached()) { - sb.append("invalid"); + final String separator = ","; + final StringBuilder sb = new StringBuilder(); + + if (!isManaged()) { + // Build String for unmanaged RealmList + + // Unmanaged RealmList does not know actual element type. + sb.append("RealmList@["); + // Print list values + final int size = size(); + for (int i = 0; i < size; i++) { + final E value = get(i); + if (value instanceof RealmModel) { + sb.append(System.identityHashCode(value)); + } else { + if (value instanceof byte[]) { + sb.append("byte[").append(((byte[]) value).length).append("]"); + } else { + sb.append(value); + } + } + sb.append(separator); + } + if (0 < size()) { + sb.setLength(sb.length() - separator.length()); + } + sb.append("]"); } else { - for (int i = 0; i < size(); i++) { - if (isManaged()) { - sb.append(((RealmObjectProxy) get(i)).realmGet$proxyState().getRow$realm().getIndex()); + // Build String for managed RealmList + + // Determines type of List + sb.append("RealmList<"); + if (className != null) { + sb.append(className); + } else { + // 'clazz' is non-null when 'dynamicClassName' is null. + //noinspection ConstantConditions,unchecked + if (isClassForRealmModel(clazz)) { + //noinspection ConstantConditions,unchecked + sb.append(realm.getSchema().getSchemaForClass((Class) clazz).getClassName()); } else { - sb.append(System.identityHashCode(get(i))); + if (clazz == byte[].class) { + sb.append(clazz.getSimpleName()); + } else { + sb.append(clazz.getName()); + } + } + } + sb.append(">@["); + + //Print list values + if (!isAttached()) { + sb.append("invalid"); + } else if (isClassForRealmModel(clazz)) { + for (int i = 0; i < size(); i++) { + //noinspection ConstantConditions + sb.append(((RealmObjectProxy) get(i)).realmGet$proxyState().getRow$realm().getIndex()); + sb.append(separator); } - if (i < size() - 1) { - sb.append(','); + if (0 < size()) { + sb.setLength(sb.length() - separator.length()); + } + } else { + for (int i = 0; i < size(); i++) { + final E value = get(i); + if (value instanceof byte[]) { + sb.append("byte[").append(((byte[]) value).length).append("]"); + } else { + sb.append(value); + } + sb.append(separator); + } + if (0 < size()) { + sb.setLength(sb.length() - separator.length()); } } + sb.append("]"); } - sb.append("]"); return sb.toString(); } @@ -909,10 +885,8 @@ public Flowable> asFlowable() { if (realm instanceof Realm) { return realm.configuration.getRxFactory().from((Realm) realm, this); } else if (realm instanceof DynamicRealm) { - DynamicRealm dynamicRealm = (DynamicRealm) realm; - RealmList dynamicList = (RealmList) this; @SuppressWarnings("UnnecessaryLocalVariable") - Flowable results = realm.configuration.getRxFactory().from(dynamicRealm, dynamicList); + Flowable> results = realm.configuration.getRxFactory().from((DynamicRealm) realm, this); return results; } else { throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2."); @@ -927,7 +901,7 @@ public Flowable> asFlowable() { *

            * RealmList will continually be emitted as the RealmList is updated - {@code onComplete} will never be called. *

            - * * Note that when the {@link Realm} is accessed from threads other than where it was created, + * * Note that when the {@link Realm} is accessed from threads other than where it was created, * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} * instead. @@ -992,7 +966,11 @@ private void checkForAddRemoveListener(@Nullable Object listener, boolean checkL */ public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - collection.addListener(this, listener); + if (osListOperator.forRealmModel()) { + getOrCreateOsResultsForListener().addListener(this, listener); + } else { + osListOperator.getOsList().addListener(this, listener); + } } /** @@ -1005,7 +983,11 @@ public void addChangeListener(OrderedRealmCollectionChangeListener> */ public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - collection.removeListener(this, listener); + if (osListOperator.forRealmModel()) { + getOrCreateOsResultsForListener().removeListener(this, listener); + } else { + osListOperator.getOsList().removeListener(this, listener); + } } /** @@ -1043,7 +1025,11 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - collection.addListener(this, listener); + if (osListOperator.forRealmModel()) { + getOrCreateOsResultsForListener().addListener(this, listener); + } else { + osListOperator.getOsList().addListener(this, listener); + } } /** @@ -1056,7 +1042,11 @@ public void addChangeListener(RealmChangeListener> listener) { */ public void removeChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); - collection.removeListener(this, listener); + if (osListOperator.forRealmModel()) { + getOrCreateOsResultsForListener().removeListener(this, listener); + } else { + osListOperator.getOsList().removeListener(this, listener); + } } /** @@ -1067,7 +1057,11 @@ public void removeChangeListener(RealmChangeListener> listener) { */ public void removeAllChangeListeners() { checkForAddRemoveListener(null, false); - collection.removeAllListeners(); + if (osListOperator.forRealmModel()) { + getOrCreateOsResultsForListener().removeAllListeners(); + } else { + osListOperator.getOsList().removeAllListeners(); + } } // Custom RealmList iterator. @@ -1105,6 +1099,7 @@ public boolean hasNext() { * {@inheritDoc} */ @Override + @Nullable public E next() { checkValidRealm(); checkConcurrentModification(); @@ -1178,6 +1173,7 @@ public boolean hasPrevious() { * {@inheritDoc} */ @Override + @Nullable public E previous() { checkConcurrentModification(); int i = cursor - 1; @@ -1211,7 +1207,7 @@ public int previousIndex() { * {@inheritDoc} */ @Override - public void set(E e) { + public void set(@Nullable E e) { realm.checkIfValid(); if (lastRet < 0) { throw new IllegalStateException(); @@ -1230,10 +1226,10 @@ public void set(E e) { * Adding a new object to the RealmList. If the object is not already manage by Realm it will be transparently * copied using {@link Realm#copyToRealmOrUpdate(RealmModel)} * - * @see #add(RealmModel) + * @see #add(Object) */ @Override - public void add(E e) { + public void add(@Nullable E e) { realm.checkIfValid(); checkConcurrentModification(); try { @@ -1247,4 +1243,680 @@ public void add(E e) { } } } + + private static boolean isClassForRealmModel(Class clazz) { + return RealmModel.class.isAssignableFrom(clazz); + } + + private ManagedListOperator getOperator(BaseRealm realm, OsList osList, @Nullable Class clazz, @Nullable String className) { + if (clazz == null || isClassForRealmModel(clazz)) { + return new RealmModelListOperator<>(realm, osList, clazz, className); + } + if (clazz == String.class) { + //noinspection unchecked + return (ManagedListOperator) new StringListOperator(realm, osList, (Class) clazz); + } + if (clazz == Long.class || clazz == Integer.class || clazz == Short.class || clazz == Byte.class) { + return new LongListOperator<>(realm, osList, clazz); + } + if (clazz == Boolean.class) { + //noinspection unchecked + return (ManagedListOperator) new BooleanListOperator(realm, osList, (Class) clazz); + } + if (clazz == byte[].class) { + //noinspection unchecked + return (ManagedListOperator) new BinaryListOperator(realm, osList, (Class) clazz); + } + if (clazz == Double.class) { + //noinspection unchecked + return (ManagedListOperator) new DoubleListOperator(realm, osList, (Class) clazz); + } + if (clazz == Float.class) { + //noinspection unchecked + return (ManagedListOperator) new FloatListOperator(realm, osList, (Class) clazz); + } + if (clazz == Date.class) { + //noinspection unchecked + return (ManagedListOperator) new DateListOperator(realm, osList, (Class) clazz); + } + throw new IllegalArgumentException("Unexpected value class: " + clazz.getName()); + } + + // TODO: Object Store is not able to merge change set for links list. Luckily since we were still using LinkView + // when ship the fine grain notifications, the listener on RealmList is actually added to a OS Results which is + // created from the link view. OS Results is computing the change set by comparing the old/new collection. So it + // will give the right results if you remove all elements from a RealmList then add all them back and add one more + // new element. By right results it means the change set only include one insertion. But if the listener is on the + // OS List, the change set will include all ranges of th list. So we keep the old behaviour for + // RealmList for now. See https://github.com/realm/realm-object-store/issues/541 + private io.realm.internal.Collection getOrCreateOsResultsForListener() { + if (osResults == null) { + this.osResults = new io.realm.internal.Collection(realm.sharedRealm, osListOperator.getOsList(), null); + } + return osResults; + } +} + +/** + * This class provides facade for against {@link OsList}. {@link OsList} is used for both {@link RealmModel}s + * and values, but there are some subtle differences in actual operation. + *

            + * This class provides common interface for them. + *

            + * You need to use appropriate sub-class for underlying field type. + * + * @param class of element which is returned on read operation. + */ +abstract class ManagedListOperator { + static final String NULL_OBJECTS_NOT_ALLOWED_MESSAGE = "RealmList does not accept null values."; + static final String INVALID_OBJECT_TYPE_MESSAGE = "Unacceptable value type. Acceptable: %1$s, actual: %2$s ."; + + final BaseRealm realm; + final OsList osList; + @Nullable + final Class clazz; + + ManagedListOperator(BaseRealm realm, OsList osList, @Nullable Class clazz) { + this.realm = realm; + this.clazz = clazz; + this.osList = osList; + } + + public abstract boolean forRealmModel(); + + public final OsList getOsList() { + return osList; + } + + public final boolean isValid() { + return osList.isValid(); + } + + public final int size() { + final long actualSize = osList.size(); + return actualSize < Integer.MAX_VALUE ? (int) actualSize : Integer.MAX_VALUE; + } + + public final boolean isEmpty() { + return osList.isEmpty(); + } + + protected abstract void checkValidValue(@Nullable Object value); + + @Nullable + public abstract T get(int index); + + public final void append(@Nullable Object value) { + checkValidValue(value); + + if (value == null) { + appendNull(); + } else { + appendValue(value); + } + } + + private void appendNull() { + osList.addNull(); + } + + abstract protected void appendValue(Object value); + + public final void insert(int index, @Nullable Object value) { + checkValidValue(value); + + if (value == null) { + insertNull(index); + } else { + insertValue(index, value); + } + + } + + protected void insertNull(int index) { + osList.insertNull(index); + } + + protected abstract void insertValue(int index, Object value); + + @Nullable + public final T set(int index, @Nullable Object value) { + checkValidValue(value); + + //noinspection unchecked + final T oldObject = get(index); + if (value == null) { + setNull(index); + } else { + setValue(index, value); + } + return oldObject; + } + + protected void setNull(int index) { + osList.setNull(index); + } + + abstract protected void setValue(int index, Object value); + + final void move(int oldPos, int newPos) { + osList.move(oldPos, newPos); + } + + final void remove(int index) { + osList.remove(index); + } + + final void removeAll() { + osList.removeAll(); + } + + final void delete(int index) { + osList.delete(index); + } + + final void deleteLast() { + osList.delete(osList.size() - 1); + } + + final void deleteAll() { + osList.deleteAll(); + } + +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link RealmModel} list field. + */ +final class RealmModelListOperator extends ManagedListOperator { + + @Nullable + private final String className; + + RealmModelListOperator(BaseRealm realm, OsList osList, @Nullable Class clazz, @Nullable String className) { + super(realm, osList, clazz); + this.className = className; + } + + @Override + public boolean forRealmModel() { + return true; + } + + @Override + public T get(int index) { + //noinspection unchecked + return (T) realm.get((Class) clazz, className, osList.getUncheckedRow(index)); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + throw new IllegalArgumentException(NULL_OBJECTS_NOT_ALLOWED_MESSAGE); + } + if (!(value instanceof RealmModel)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.String", + value.getClass().getName())); + } + } + + private void checkInsertIndex(int index) { + final int size = size(); + if (index < 0 || size < index) { + throw new IndexOutOfBoundsException("Invalid index " + index + ", size is " + osList.size()); + } + } + + @Override + public void appendValue(Object value) { + final RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded((RealmModel) value); + osList.addRow(proxy.realmGet$proxyState().getRow$realm().getIndex()); + } + + @Override + protected void insertNull(int index) { + throw new RuntimeException("Should not reach here."); + } + + @Override + public void insertValue(int index, Object value) { + // need to check in advance to avoid unnecessary copy of unmanaged object into Realm. + checkInsertIndex(index); + + RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded((RealmModel) value); + osList.insertRow(index, proxy.realmGet$proxyState().getRow$realm().getIndex()); + } + + @Override + protected void setNull(int index) { + throw new RuntimeException("Should not reach here."); + } + + @Override + protected void setValue(int index, Object value) { + RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded((RealmModel) value); + osList.setRow(index, proxy.realmGet$proxyState().getRow$realm().getIndex()); + } + + // Transparently copies an unmanaged object or managed object from another Realm to the Realm backing this RealmList. + private E copyToRealmIfNeeded(E object) { + if (object instanceof RealmObjectProxy) { + RealmObjectProxy proxy = (RealmObjectProxy) object; + + if (proxy instanceof DynamicRealmObject) { + //noinspection ConstantConditions + @Nonnull + String listClassName = className; + if (proxy.realmGet$proxyState().getRealm$realm() == realm) { + String objectClassName = ((DynamicRealmObject) object).getType(); + if (listClassName.equals(objectClassName)) { + // Same Realm instance and same target table + return object; + } else { + // Different target table + throw new IllegalArgumentException(String.format(Locale.US, + "The object has a different type from list's." + + " Type of the list is '%s', type of object is '%s'.", listClassName, objectClassName)); + } + } else if (realm.threadId == proxy.realmGet$proxyState().getRealm$realm().threadId) { + // We don't support moving DynamicRealmObjects across Realms automatically. The overhead is too big as + // you have to run a full schema validation for each object. + // And copying from another Realm instance pointed to the same Realm file is not supported as well. + throw new IllegalArgumentException("Cannot copy DynamicRealmObject between Realm instances."); + } else { + throw new IllegalStateException("Cannot copy an object to a Realm instance created in another thread."); + } + } else { + // Object is already in this realm + if (proxy.realmGet$proxyState().getRow$realm() != null && proxy.realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + if (realm != proxy.realmGet$proxyState().getRealm$realm()) { + throw new IllegalArgumentException("Cannot copy an object from another Realm instance."); + } + return object; + } + } + } + + // At this point the object can only be a typed object, so the backing Realm cannot be a DynamicRealm. + Realm realm = (Realm) this.realm; + if (OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), + realm.getConfiguration().getSchemaMediator().getSimpleClassName(object.getClass())) != null) { + return realm.copyToRealmOrUpdate(object); + } else { + return realm.copyToRealm(object); + } + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link String} list field. + */ +final class StringListOperator extends ManagedListOperator { + + StringListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public String get(int index) { + return (String) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof String)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.String", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addString((String) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertString(index, (String) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setString(index, (String) value); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code long} list field. + */ +final class LongListOperator extends ManagedListOperator { + + LongListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public T get(int index) { + final Long value = (Long) osList.getValue(index); + if (value == null) { + return null; + } + if (clazz == Long.class) { + //noinspection unchecked + return (T) value; + } + if (clazz == Integer.class) { + //noinspection unchecked,UnnecessaryBoxing,ConstantConditions + return clazz.cast(Integer.valueOf(value.intValue())); + } + if (clazz == Short.class) { + //noinspection unchecked,UnnecessaryBoxing,ConstantConditions + return clazz.cast(Short.valueOf(value.shortValue())); + } + if (clazz == Byte.class) { + //noinspection unchecked,UnnecessaryBoxing,ConstantConditions + return clazz.cast(Byte.valueOf(value.byteValue())); + } + //noinspection ConstantConditions + throw new IllegalStateException("Unexpected element type: " + clazz.getName()); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Number)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.Long, java.lang.Integer, java.lang.Short, java.lang.Byte", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addLong(((Number) value).longValue()); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertLong(index, ((Number) value).longValue()); + } + + @Override + protected void setValue(int index, Object value) { + osList.setLong(index, ((Number) value).longValue()); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code boolean} list field. + */ +final class BooleanListOperator extends ManagedListOperator { + + BooleanListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Boolean get(int index) { + return (Boolean) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Boolean)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.Boolean", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addBoolean((Boolean) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertBoolean(index, (Boolean) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setBoolean(index, (Boolean) value); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code byte[]} list field. + */ +final class BinaryListOperator extends ManagedListOperator { + + BinaryListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public byte[] get(int index) { + return (byte[]) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof byte[])) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "byte[]", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addBinary((byte[]) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertBinary(index, (byte[]) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setBinary(index, (byte[]) value); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code double} list field. + */ +final class DoubleListOperator extends ManagedListOperator { + + DoubleListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Double get(int index) { + return (Double) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Number)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.Number", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addDouble(((Number) value).doubleValue()); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertDouble(index, ((Number) value).doubleValue()); + } + + @Override + protected void setValue(int index, Object value) { + osList.setDouble(index, ((Number) value).doubleValue()); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code float} list field. + */ +final class FloatListOperator extends ManagedListOperator { + + FloatListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Float get(int index) { + return (Float) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Number)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.Number", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addFloat(((Number) value).floatValue()); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertFloat(index, ((Number) value).floatValue()); + } + + @Override + protected void setValue(int index, Object value) { + osList.setFloat(index, ((Number) value).floatValue()); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link Date} list field. + */ +final class DateListOperator extends ManagedListOperator { + + DateListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Date get(int index) { + return (Date) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Date)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.util.Date", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addDate((Date) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertDate(index, (Date) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setDate(index, (Date) value); + } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 30a584b996..27f0129173 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -255,6 +255,9 @@ public boolean hasIndex(String fieldName) { /** * Sets a field to be required i.e., it is not allowed to hold {@code null} values. This is equivalent to switching * between boxed types and their primitive variant e.g., {@code Integer} to {@code int}. + *

            + * If the type of designated field is a list of values (not {@link RealmObject}s , specified nullability + * only affects its elements, not the field itself. Value list itself is always non-nullable. * * @param fieldName name of field in the class. * @param required {@code true} if field should be required, {@code false} otherwise. @@ -269,6 +272,9 @@ public boolean hasIndex(String fieldName) { /** * Sets a field to be nullable i.e., it should be able to hold {@code null} values. This is equivalent to switching * between primitive types and their boxed variant e.g., {@code int} to {@code Integer}. + *

            + * If the type of designated field is a list of values (not {@link RealmObject}s , specified nullability + * only affects its elements, not the field itself. Value list itself is always non-nullable. * * @param fieldName name of field in the class. * @param nullable {@code true} if field should be nullable, {@code false} otherwise. diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index a87ff88b7d..5081ecfbc4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -53,7 +53,7 @@ * @see Realm#where(Class) * @see RealmResults#where() */ -public class RealmQuery { +public class RealmQuery { private final Table table; private final BaseRealm realm; @@ -61,7 +61,9 @@ public class RealmQuery { private final RealmObjectSchema schema; private Class clazz; private String className; + private final boolean forValues; private final OsList osList; + private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; private static final String ASYNC_QUERY_WRONG_THREAD_MESSAGE = "Async query cannot be created on current thread."; @@ -74,7 +76,7 @@ public class RealmQuery { * @return {@link RealmQuery} object. After building the query call one of the {@code find*} methods * to run it. */ - public static RealmQuery createQuery(Realm realm, Class clazz) { + static RealmQuery createQuery(Realm realm, Class clazz) { return new RealmQuery<>(realm, clazz); } @@ -86,7 +88,7 @@ public static RealmQuery createQuery(Realm realm, Clas * @return {@link RealmQuery} object. After building the query call one of the {@code find*} methods * to run it. */ - public static RealmQuery createDynamicQuery(DynamicRealm realm, String className) { + static RealmQuery createDynamicQuery(DynamicRealm realm, String className) { return new RealmQuery<>(realm, className); } @@ -98,7 +100,7 @@ public static RealmQuery createDynamicQuery(DynamicRea * to run it. */ @SuppressWarnings("unchecked") - public static RealmQuery createQueryFromResult(RealmResults queryResults) { + static RealmQuery createQueryFromResult(RealmResults queryResults) { //noinspection ConstantConditions return (queryResults.classSpec == null) ? new RealmQuery(queryResults, queryResults.className) @@ -113,43 +115,78 @@ public static RealmQuery createQueryFromResult(RealmRe * to run it. */ @SuppressWarnings("unchecked") - public static RealmQuery createQueryFromList(RealmList list) { + static RealmQuery createQueryFromList(RealmList list) { //noinspection ConstantConditions return (list.clazz == null) - ? new RealmQuery(list.realm, list.osList, list.className) - : new RealmQuery(list.realm, list.osList, list.clazz); + ? new RealmQuery(list.realm, list.getOsList(), list.className) + : new RealmQuery(list.realm, list.getOsList(), list.clazz); + } + + private static boolean isClassForRealmModel(Class clazz) { + return RealmModel.class.isAssignableFrom(clazz); } private RealmQuery(Realm realm, Class clazz) { this.realm = realm; this.clazz = clazz; - this.schema = realm.getSchema().getSchemaForClass(clazz); - this.table = schema.getTable(); - this.osList = null; - this.query = table.where(); + this.forValues = !isClassForRealmModel(clazz); + if (forValues) { + // TODO implement this + this.schema = null; + this.table = null; + this.osList = null; + this.query = null; + } else { + //noinspection unchecked + this.schema = realm.getSchema().getSchemaForClass((Class) clazz); + this.table = schema.getTable(); + this.osList = null; + this.query = table.where(); + } } private RealmQuery(RealmResults queryResults, Class clazz) { this.realm = queryResults.realm; this.clazz = clazz; - this.schema = realm.getSchema().getSchemaForClass(clazz); - this.table = queryResults.getTable(); - this.osList = null; - this.query = queryResults.getCollection().where(); + this.forValues = !isClassForRealmModel(clazz); + if (forValues) { + // TODO implement this + this.schema = null; + this.table = null; + this.osList = null; + this.query = null; + } else { + //noinspection unchecked + this.schema = realm.getSchema().getSchemaForClass((Class) clazz); + this.table = queryResults.getTable(); + this.osList = null; + this.query = queryResults.getCollection().where(); + } } private RealmQuery(BaseRealm realm, OsList osList, Class clazz) { this.realm = realm; this.clazz = clazz; - this.schema = realm.getSchema().getSchemaForClass(clazz); - this.table = schema.getTable(); - this.osList = osList; - this.query = osList.getQuery(); + this.forValues = !isClassForRealmModel(clazz); + if (forValues) { + // TODO implement this + this.schema = null; + this.table = null; + this.osList = null; + this.query = null; + } else { + //noinspection unchecked + this.schema = realm.getSchema().getSchemaForClass((Class) clazz); + this.table = schema.getTable(); + this.osList = osList; + this.query = osList.getQuery(); + } } private RealmQuery(BaseRealm realm, String className) { this.realm = realm; this.className = className; + this.forValues = false; this.schema = realm.getSchema().getSchemaForClass(className); this.table = schema.getTable(); this.query = table.where(); @@ -159,6 +196,7 @@ private RealmQuery(BaseRealm realm, String className) { private RealmQuery(RealmResults queryResults, String className) { this.realm = queryResults.realm; this.className = className; + this.forValues = false; this.schema = realm.getSchema().getSchemaForClass(className); this.table = schema.getTable(); this.query = queryResults.getCollection().where(); @@ -168,6 +206,7 @@ private RealmQuery(RealmResults queryResults, String classNa private RealmQuery(BaseRealm realm, OsList osList, String className) { this.realm = realm; this.className = className; + this.forValues = false; this.schema = realm.getSchema().getSchemaForClass(className); this.table = schema.getTable(); this.query = osList.getQuery(); @@ -1932,8 +1971,14 @@ public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, public E findFirst() { realm.checkIfValid(); + if (forValues) { + // TODO implement this; + return null; + } + long tableRowIndex = getSourceRowIndexForFirstObject(); - return (tableRowIndex < 0) ? null : realm.get(clazz, className, tableRowIndex); + //noinspection unchecked + return (tableRowIndex < 0) ? null : (E) realm.get((Class) clazz, className, tableRowIndex); } /** @@ -1949,6 +1994,10 @@ public E findFirst() { public E findFirstAsync() { realm.checkIfValid(); + if (forValues) { + throw new UnsupportedOperationException("findFirstAsync() available only when type parameter 'E' is implementing RealmModel."); + } + realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); Row row; if (realm.isInTransaction()) { @@ -1969,8 +2018,11 @@ public E findFirstAsync() { //noinspection unchecked result = (E) new DynamicRealmObject(realm, row); } else { - result = realm.getConfiguration().getSchemaMediator().newInstance( - clazz, realm, row, realm.getSchema().getColumnInfo(clazz), + //noinspection unchecked + final Class modelClass = (Class) clazz; + //noinspection unchecked + result = (E) realm.getConfiguration().getSchemaMediator().newInstance( + modelClass, realm, row, realm.getSchema().getColumnInfo(modelClass), false, Collections.emptyList()); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 0619784ffa..e7892701dd 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -60,7 +60,7 @@ * @see RealmQuery#findAll() * @see Realm#executeTransaction(Realm.Transaction) */ -public class RealmResults extends OrderedRealmCollectionImpl { +public class RealmResults extends OrderedRealmCollectionImpl { // Called from Realm Proxy classes @SuppressLint("unused") diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index f3a5426cdd..f3a3e1f97b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -16,6 +16,8 @@ package io.realm.internal; +import java.util.Locale; + import io.realm.RealmFieldType; @@ -95,6 +97,39 @@ public void setNull(long columnIndex) { } } + @Override + public OsList getList(long columnIndex) { + RealmFieldType fieldType = getTable().getColumnType(columnIndex); + if (fieldType != RealmFieldType.LIST) { + throw new IllegalArgumentException( + String.format(Locale.US, "Field '%s' is not a 'RealmList'.", + getTable().getColumnName(columnIndex))); + } + return super.getList(columnIndex); + } + + @Override + public OsList getModelList(long columnIndex) { + RealmFieldType fieldType = getTable().getColumnType(columnIndex); + if (fieldType != RealmFieldType.LIST) { + throw new IllegalArgumentException( + String.format(Locale.US, "Field '%s' is not a 'RealmList'.", + getTable().getColumnName(columnIndex))); + } + return super.getModelList(columnIndex); + } + + @Override + public OsList getValueList(long columnIndex, RealmFieldType fieldType) { + final RealmFieldType actualFieldType = getTable().getColumnType(columnIndex); + if (fieldType != actualFieldType) { + throw new IllegalArgumentException( + String.format(Locale.US, "The type of field '%1$s' is not 'RealmFieldType.%2$s'.", + getTable().getColumnName(columnIndex), fieldType.name())); + } + return super.getValueList(columnIndex, fieldType); + } + @Override protected native long nativeGetColumnCount(long nativeTablePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/Collection.java index ec81feb7e9..df0b2c6cf0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Collection.java @@ -22,7 +22,6 @@ import javax.annotation.Nullable; -import io.realm.OrderedCollectionChangeSet; import io.realm.OrderedRealmCollectionChangeListener; import io.realm.RealmChangeListener; @@ -31,68 +30,11 @@ * Java wrapper of Object Store Results class. * It is the backend of binding's query results and back links. */ -@Keep -public class Collection implements NativeObject { +public class Collection implements NativeObject, ObservableCollection { private static final String CLOSED_REALM_MESSAGE = "This Realm instance has already been closed, making it unusable."; - private static class CollectionObserverPair extends ObserverPairList.ObserverPair { - public CollectionObserverPair(T observer, Object listener) { - super(observer, listener); - } - - public void onChange(T observer, OrderedCollectionChangeSet changes) { - if (listener instanceof OrderedRealmCollectionChangeListener) { - //noinspection unchecked - ((OrderedRealmCollectionChangeListener) listener).onChange(observer, changes); - } else if (listener instanceof RealmChangeListener) { - //noinspection unchecked - ((RealmChangeListener) listener).onChange(observer); - } else { - throw new RuntimeException("Unsupported listener type: " + listener); - } - } - } - - private static class RealmChangeListenerWrapper implements OrderedRealmCollectionChangeListener { - private final RealmChangeListener listener; - - RealmChangeListenerWrapper(RealmChangeListener listener) { - this.listener = listener; - } - - @Override - public void onChange(T collection, OrderedCollectionChangeSet changes) { - listener.onChange(collection); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof RealmChangeListenerWrapper && - listener == ((RealmChangeListenerWrapper) obj).listener; - } - - @Override - public int hashCode() { - return listener.hashCode(); - } - } - - private static class Callback implements ObserverPairList.Callback { - private final OrderedCollectionChangeSet changeSet; - - Callback(OrderedCollectionChangeSet changeSet) { - this.changeSet = changeSet; - } - - @Override - public void onCalled(CollectionObserverPair pair, Object observer) { - //noinspection unchecked - pair.onChange(observer, changeSet); - } - } - // Custom Collection iterator. It ensures that we only iterate on a Realm collection that hasn't changed. public static abstract class Iterator implements java.util.Iterator { Collection iteratorCollection; @@ -130,6 +72,7 @@ public boolean hasNext() { * {@inheritDoc} */ @Override + @Nullable public T next() { checkValid(); pos++; @@ -170,6 +113,7 @@ void checkValid() { } } + @Nullable T get(int pos) { return convertRowToObject(iteratorCollection.getUncheckedRow(pos)); } @@ -199,7 +143,7 @@ public ListIterator(Collection collection, int start) { */ @Override @Deprecated - public void add(T object) { + public void add(@Nullable T object) { throw new UnsupportedOperationException("Adding an element is not supported. Use Realm.createObject() instead."); } @@ -225,6 +169,7 @@ public int nextIndex() { * {@inheritDoc} */ @Override + @Nullable public T previous() { checkValid(); try { @@ -253,7 +198,7 @@ public int previousIndex() { */ @Override @Deprecated - public void set(T object) { + public void set(@Nullable T object) { throw new UnsupportedOperationException("Replacing and element is not supported."); } } @@ -514,8 +459,8 @@ public boolean isValid() { } // Called by JNI - @SuppressWarnings("unused") - private void notifyChangeListeners(long nativeChangeSetPtr) { + @Override + public void notifyChangeListeners(long nativeChangeSetPtr) { if (nativeChangeSetPtr == 0 && isLoaded()) { return; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index fba9493fb2..79510c7e08 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -105,7 +105,17 @@ public boolean isNullLink(long columnIndex) { } @Override - public OsList getLinkList(long columnIndex) { + public OsList getList(long columnIndex) { + throw getStubException(); + } + + @Override + public OsList getModelList(long columnIndex) { + throw getStubException(); + } + + @Override + public OsList getValueList(long columnIndex, RealmFieldType fieldType) { throw getStubException(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java b/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java new file mode 100644 index 0000000000..7004df192c --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java @@ -0,0 +1,72 @@ +package io.realm.internal; + +import javax.annotation.Nullable; + +import io.realm.OrderedCollectionChangeSet; +import io.realm.OrderedRealmCollectionChangeListener; +import io.realm.RealmChangeListener; + +// Helper class for supporting add change listeners on OsResults & OsList. +@Keep +interface ObservableCollection { + + class CollectionObserverPair extends ObserverPairList.ObserverPair { + public CollectionObserverPair(T observer, Object listener) { + super(observer, listener); + } + + public void onChange(T observer, @Nullable OrderedCollectionChangeSet changes) { + if (listener instanceof OrderedRealmCollectionChangeListener) { + //noinspection unchecked + ((OrderedRealmCollectionChangeListener) listener).onChange(observer, changes); + } else if (listener instanceof RealmChangeListener) { + //noinspection unchecked + ((RealmChangeListener) listener).onChange(observer); + } else { + throw new RuntimeException("Unsupported listener type: " + listener); + } + } + } + + class RealmChangeListenerWrapper implements OrderedRealmCollectionChangeListener { + private final RealmChangeListener listener; + + RealmChangeListenerWrapper(RealmChangeListener listener) { + this.listener = listener; + } + + @Override + public void onChange(T collection, @Nullable OrderedCollectionChangeSet changes) { + listener.onChange(collection); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof RealmChangeListenerWrapper && + listener == ((RealmChangeListenerWrapper) obj).listener; + } + + @Override + public int hashCode() { + return listener.hashCode(); + } + } + + class Callback implements ObserverPairList.Callback { + private final OrderedCollectionChangeSet changeSet; + + Callback(@Nullable OrderedCollectionChangeSet changeSet) { + this.changeSet = changeSet; + } + + @Override + public void onCalled(CollectionObserverPair pair, Object observer) { + //noinspection unchecked + pair.onChange(observer, changeSet); + } + } + + // Called by JNI + @SuppressWarnings("SameParameterValue") + void notifyChangeListeners(long nativeChangeSetPtr); +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsList.java b/realm/realm-library/src/main/java/io/realm/internal/OsList.java index 7d9a794f63..bedf3e928a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsList.java @@ -1,14 +1,23 @@ package io.realm.internal; +import java.util.Date; + +import javax.annotation.Nullable; + +import io.realm.OrderedRealmCollectionChangeListener; +import io.realm.RealmChangeListener; + /** * Java wrapper of Object Store List class. This backs managed versions of RealmList. */ -public class OsList implements NativeObject { +public class OsList implements NativeObject, ObservableCollection { private final long nativePtr; private final NativeContext context; private final Table targetTable; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); + private final ObserverPairList observerPairs = + new ObserverPairList(); public OsList(UncheckedRow row, long columnIndex) { SharedRealm sharedRealm = row.getTable().getSharedRealm(); @@ -18,7 +27,11 @@ public OsList(UncheckedRow row, long columnIndex) { this.context = sharedRealm.context; context.addReference(this); - targetTable = new Table(sharedRealm, ptrs[1]); + if (ptrs[1] != 0) { + targetTable = new Table(sharedRealm, ptrs[1]); + } else { + targetTable = null; + } } @Override @@ -47,6 +60,119 @@ public void setRow(long pos, long targetRowIndex) { nativeSetRow(nativePtr, pos, targetRowIndex); } + public void addNull() { + nativeAddNull(nativePtr); + } + + public void insertNull(long pos) { + nativeInsertNull(nativePtr, pos); + } + + public void setNull(long pos) { + nativeSetNull(nativePtr, pos); + } + + public void addLong(long value) { + nativeAddLong(nativePtr, value); + } + + public void insertLong(long pos, long value) { + nativeInsertLong(nativePtr, pos, value); + } + + public void setLong(long pos, long value) { + nativeSetLong(nativePtr, pos, value); + } + + public void addDouble(double value) { + nativeAddDouble(nativePtr, value); + } + + public void insertDouble(long pos, double value) { + nativeInsertDouble(nativePtr, pos, value); + } + + public void setDouble(long pos, double value) { + nativeSetDouble(nativePtr, pos, value); + } + + public void addFloat(float value) { + nativeAddFloat(nativePtr, value); + } + + public void insertFloat(long pos, float value) { + nativeInsertFloat(nativePtr, pos, value); + } + + public void setFloat(long pos, float value) { + nativeSetFloat(nativePtr, pos, value); + } + + public void addBoolean(boolean value) { + nativeAddBoolean(nativePtr, value); + } + + public void insertBoolean(long pos, boolean value) { + nativeInsertBoolean(nativePtr, pos, value); + } + + public void setBoolean(long pos, boolean value) { + nativeSetBoolean(nativePtr, pos, value); + } + + public void addBinary(@Nullable byte[] value) { + nativeAddBinary(nativePtr, value); + } + + public void insertBinary(long pos, @Nullable byte[] value) { + nativeInsertBinary(nativePtr, pos, value); + } + + public void setBinary(long pos, @Nullable byte[] value) { + nativeSetBinary(nativePtr, pos, value); + } + + public void addString(@Nullable String value) { + nativeAddString(nativePtr, value); + } + + public void insertString(long pos, @Nullable String value) { + nativeInsertString(nativePtr, pos, value); + } + + public void setString(long pos, @Nullable String value) { + nativeSetString(nativePtr, pos, value); + } + + public void addDate(@Nullable Date value) { + if (value == null) { + nativeAddNull(nativePtr); + } else { + nativeAddDate(nativePtr, value.getTime()); + } + } + + public void insertDate(long pos, @Nullable Date value) { + if (value == null) { + nativeInsertNull(nativePtr, pos); + } else { + nativeInsertDate(nativePtr, pos, value.getTime()); + } + } + + public void setDate(long pos, @Nullable Date value) { + if (value == null) { + nativeSetNull(nativePtr, pos); + } else { + nativeSetDate(nativePtr, pos, value.getTime()); + } + } + + @Nullable + public Object getValue(long pos) { + return nativeGetValue(nativePtr, pos); + } + public void move(long sourceIndex, long targetIndex) { nativeMove(nativePtr, sourceIndex, targetIndex); } @@ -90,6 +216,44 @@ public Table getTargetTable() { return targetTable; } + public void addListener(T observer, OrderedRealmCollectionChangeListener listener) { + if (observerPairs.isEmpty()) { + nativeStartListening(nativePtr); + } + CollectionObserverPair collectionObserverPair = new CollectionObserverPair(observer, listener); + observerPairs.add(collectionObserverPair); + } + + public void addListener(T observer, RealmChangeListener listener) { + addListener(observer, new RealmChangeListenerWrapper(listener)); + } + + public void removeListener(T observer, OrderedRealmCollectionChangeListener listener) { + observerPairs.remove(observer, listener); + if (observerPairs.isEmpty()) { + nativeStopListening(nativePtr); + } + } + + public void removeListener(T observer, RealmChangeListener listener) { + removeListener(observer, new RealmChangeListenerWrapper(listener)); + } + + public void removeAllListeners() { + observerPairs.clear(); + nativeStopListening(nativePtr); + } + + // Called by JNI + @Override + public void notifyChangeListeners(long nativeChangeSetPtr) { + if (nativeChangeSetPtr == 0) { + // First time "query" returns. Do nothing. + return; + } + observerPairs.foreach(new Callback(new OsCollectionChangeSet(nativeChangeSetPtr))); + } + private static native long nativeGetFinalizerPtr(); // TODO: nativeTablePtr is not necessary. It is used to create FieldDescriptor which should be generated from @@ -120,4 +284,58 @@ public Table getTargetTable() { private static native void nativeDelete(long nativePtr, long index); private static native void nativeDeleteAll(long nativePtr); + + private static native void nativeAddNull(long nativePtr); + + private static native void nativeInsertNull(long nativePtr, long pos); + + private static native void nativeSetNull(long nativePtr, long pos); + + private static native void nativeAddLong(long nativePtr, long value); + + private static native void nativeInsertLong(long nativePtr, long pos, long value); + + private static native void nativeSetLong(long nativePtr, long pos, long value); + + private static native void nativeAddDouble(long nativePtr, double value); + + private static native void nativeInsertDouble(long nativePtr, long pos, double value); + + private static native void nativeSetDouble(long nativePtr, long pos, double value); + + private static native void nativeAddFloat(long nativePtr, float value); + + private static native void nativeInsertFloat(long nativePtr, long pos, float value); + + private static native void nativeSetFloat(long nativePtr, long pos, float value); + + private static native void nativeAddBoolean(long nativePtr, boolean value); + + private static native void nativeInsertBoolean(long nativePtr, long pos, boolean value); + + private static native void nativeSetBoolean(long nativePtr, long pos, boolean value); + + private static native void nativeAddBinary(long nativePtr, @Nullable byte[] value); + + private static native void nativeInsertBinary(long nativePtr, long pos, @Nullable byte[] value); + + private static native void nativeSetBinary(long nativePtr, long pos, @Nullable byte[] value); + + private static native void nativeAddDate(long nativePtr, long value); + + private static native void nativeInsertDate(long nativePtr, long pos, long value); + + private static native void nativeSetDate(long nativePtr, long pos, long value); + + private static native void nativeAddString(long nativePtr, @Nullable String value); + + private static native void nativeInsertString(long nativePtr, long pos, @Nullable String value); + + private static native void nativeSetString(long nativePtr, long pos, @Nullable String value); + + private static native Object nativeGetValue(long nativePtr, long pos); + + private native void nativeStartListening(long nativePtr); + + private native void nativeStopListening(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java index 36c7e9aef5..5b25bc1d43 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java @@ -46,22 +46,36 @@ public Builder(String className) { } /** - * Adds a persisted non-link property to this builder. + * Adds a persisted non-link, non value list property to this builder. * * @param name the name of the property. * @param type the type of the property. * @param isPrimaryKey set to true if this property is the primary key. * @param isIndexed set to true if this property needs an index. - * @param isRequired set to false if this property is not nullable. + * @param isRequired set to true if this property is not nullable. * @return this {@code OsObjectSchemaInfo}. */ public Builder addPersistedProperty(String name, RealmFieldType type, boolean isPrimaryKey, boolean isIndexed, - boolean isRequired) { + boolean isRequired) { final Property property = new Property(name, type, isPrimaryKey, isIndexed, isRequired); persistedPropertyList.add(property); return this; } + /** + * Adds a persisted value list property to this builder. + * + * @param name the name of the property. + * @param type the type of the property. It must be one of value list type. + * @param isRequired set to true if this property is not nullable. + * @return this {@code OsObjectSchemaInfo}. + */ + public Builder addPersistedValueListProperty(String name, RealmFieldType type, boolean isRequired) { + final Property property = new Property(name, type, !Property.PRIMARY_KEY, !Property.INDEXED, isRequired); + persistedPropertyList.add(property); + return this; + } + /** * Adds a persisted link property to this {@code OsObjectSchemaInfo}. A persisted link property will be stored * in the Realm file's schema. diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 67c16c24b9..88ace3a27b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -134,7 +134,17 @@ public boolean isNullLink(long columnIndex) { } @Override - public OsList getLinkList(long columnIndex) { + public OsList getList(long columnIndex) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public OsList getModelList(long columnIndex) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public OsList getValueList(long columnIndex, RealmFieldType fieldType) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Property.java b/realm/realm-library/src/main/java/io/realm/internal/Property.java index 4cb755f768..03a1026223 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Property.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Property.java @@ -21,6 +21,14 @@ import io.realm.RealmFieldType; +import static io.realm.RealmFieldType.BINARY_LIST; +import static io.realm.RealmFieldType.BOOLEAN_LIST; +import static io.realm.RealmFieldType.DATE_LIST; +import static io.realm.RealmFieldType.DOUBLE_LIST; +import static io.realm.RealmFieldType.FLOAT_LIST; +import static io.realm.RealmFieldType.INTEGER_LIST; +import static io.realm.RealmFieldType.STRING_LIST; + /** * Class for handling properties/fields. @@ -64,7 +72,6 @@ public class Property implements NativeObject { } Property(String name, RealmFieldType type, String linkedClassName) { - // Ignore the isRequired when creating the linking property. this(nativeCreatePersistedLinkProperty(name, convertFromRealmFieldType(type, false), linkedClassName)); } @@ -110,6 +117,28 @@ private static int convertFromRealmFieldType(RealmFieldType fieldType, boolean i case DOUBLE: type = TYPE_DOUBLE; break; + case INTEGER_LIST: + //noinspection PointlessBitwiseExpression + type = TYPE_INT | TYPE_ARRAY; + break; + case BOOLEAN_LIST: + type = TYPE_BOOL | TYPE_ARRAY; + break; + case STRING_LIST: + type = TYPE_STRING | TYPE_ARRAY; + break; + case BINARY_LIST: + type = TYPE_DATA | TYPE_ARRAY; + break; + case DATE_LIST: + type = TYPE_DATE | TYPE_ARRAY; + break; + case FLOAT_LIST: + type = TYPE_FLOAT | TYPE_ARRAY; + break; + case DOUBLE_LIST: + type = TYPE_DOUBLE | TYPE_ARRAY; + break; default: throw new IllegalArgumentException( String.format(Locale.US, "Unsupported filed type: '%s'.", fieldType.name())); @@ -142,6 +171,21 @@ private static RealmFieldType convertToRealmFieldType(int propertyType) { return RealmFieldType.FLOAT; case TYPE_DOUBLE: return RealmFieldType.DOUBLE; + //noinspection PointlessBitwiseExpression + case TYPE_INT | TYPE_ARRAY: + return INTEGER_LIST; + case TYPE_BOOL | TYPE_ARRAY: + return BOOLEAN_LIST; + case TYPE_STRING | TYPE_ARRAY: + return STRING_LIST; + case TYPE_DATA | TYPE_ARRAY: + return BINARY_LIST; + case TYPE_DATE | TYPE_ARRAY: + return DATE_LIST; + case TYPE_FLOAT | TYPE_ARRAY: + return FLOAT_LIST; + case TYPE_DOUBLE | TYPE_ARRAY: + return DOUBLE_LIST; default: throw new IllegalArgumentException( String.format(Locale.US, "Unsupported property type: '%d'", propertyType)); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index 655ef6d224..f1a556f057 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -81,7 +81,12 @@ public interface Row { boolean isNullLink(long columnIndex); - OsList getLinkList(long columnIndex); + // FIXME remove this in DynamicRealm PR + OsList getList(long columnIndex); + + OsList getModelList(long columnIndex); + + OsList getValueList(long columnIndex, RealmFieldType fieldType); void setLong(long columnIndex, long value); diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index d17c236ee2..5c98952c7d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -173,7 +173,17 @@ public boolean isNullLink(long columnIndex) { } @Override - public OsList getLinkList(long columnIndex) { + public OsList getList(long columnIndex) { + return new OsList(this, columnIndex); + } + + @Override + public OsList getModelList(long columnIndex) { + return new OsList(this, columnIndex); + } + + @Override + public OsList getValueList(long columnIndex, RealmFieldType fieldType) { return new OsList(this, columnIndex); } diff --git a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java index 36fa70e5b8..8b72dd0c12 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java @@ -145,7 +145,7 @@ public void run() { } @Override - public Flowable> from(final Realm realm, final RealmResults results) { + public Flowable> from(final Realm realm, final RealmResults results) { final RealmConfiguration realmConfig = realm.getConfiguration(); return Flowable.create(new FlowableOnSubscribe>() { @Override @@ -182,7 +182,7 @@ public void run() { } @Override - public Observable>> changesetsFrom(Realm realm, final RealmResults results) { + public Observable>> changesetsFrom(Realm realm, final RealmResults results) { final RealmConfiguration realmConfig = realm.getConfiguration(); return Observable.create(new ObservableOnSubscribe>>() { @Override @@ -218,18 +218,18 @@ public void run() { } @Override - public Flowable> from(DynamicRealm realm, final RealmResults results) { + public Flowable> from(DynamicRealm realm, final RealmResults results) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Flowable.create(new FlowableOnSubscribe>() { + return Flowable.create(new FlowableOnSubscribe>() { @Override - public void subscribe(final FlowableEmitter> emitter) throws Exception { + public void subscribe(final FlowableEmitter> emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); resultsRefs.get().acquireReference(results); - final RealmChangeListener> listener = new RealmChangeListener>() { + final RealmChangeListener> listener = new RealmChangeListener>() { @Override - public void onChange(RealmResults results) { + public void onChange(RealmResults results) { if (!emitter.isCancelled()) { emitter.onNext(results); } @@ -255,18 +255,18 @@ public void run() { } @Override - public Observable>> changesetsFrom(DynamicRealm realm, final RealmResults results) { + public Observable>> changesetsFrom(DynamicRealm realm, final RealmResults results) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new ObservableOnSubscribe>>() { + return Observable.create(new ObservableOnSubscribe>>() { @Override - public void subscribe(final ObservableEmitter>> emitter) throws Exception { + public void subscribe(final ObservableEmitter>> emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); resultsRefs.get().acquireReference(results); - final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { + final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { @Override - public void onChange(RealmResults results, OrderedCollectionChangeSet changeSet) { + public void onChange(RealmResults results, OrderedCollectionChangeSet changeSet) { if (!emitter.isDisposed()) { emitter.onNext(new CollectionChange<>(results, changeSet)); } @@ -291,7 +291,7 @@ public void run() { } @Override - public Flowable> from(Realm realm, final RealmList list) { + public Flowable> from(Realm realm, final RealmList list) { final RealmConfiguration realmConfig = realm.getConfiguration(); return Flowable.create(new FlowableOnSubscribe>() { @Override @@ -328,7 +328,7 @@ public void run() { } @Override - public Observable>> changesetsFrom(Realm realm, final RealmList list) { + public Observable>> changesetsFrom(Realm realm, final RealmList list) { final RealmConfiguration realmConfig = realm.getConfiguration(); return Observable.create(new ObservableOnSubscribe>>() { @Override @@ -364,18 +364,18 @@ public void run() { } @Override - public Flowable> from(DynamicRealm realm, final RealmList list) { + public Flowable> from(DynamicRealm realm, final RealmList list) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Flowable.create(new FlowableOnSubscribe>() { + return Flowable.create(new FlowableOnSubscribe>() { @Override - public void subscribe(final FlowableEmitter> emitter) throws Exception { + public void subscribe(final FlowableEmitter> emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); listRefs.get().acquireReference(list); - final RealmChangeListener> listener = new RealmChangeListener>() { + final RealmChangeListener> listener = new RealmChangeListener>() { @Override - public void onChange(RealmList results) { + public void onChange(RealmList results) { if (!emitter.isCancelled()) { emitter.onNext(list); } @@ -401,18 +401,18 @@ public void run() { } @Override - public Observable>> changesetsFrom(DynamicRealm realm, final RealmList list) { + public Observable>> changesetsFrom(DynamicRealm realm, final RealmList list) { final RealmConfiguration realmConfig = realm.getConfiguration(); - return Observable.create(new ObservableOnSubscribe>>() { + return Observable.create(new ObservableOnSubscribe>>() { @Override - public void subscribe(final ObservableEmitter>> emitter) throws Exception { + public void subscribe(final ObservableEmitter>> emitter) throws Exception { // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); listRefs.get().acquireReference(list); - final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { + final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { @Override - public void onChange(RealmList results, OrderedCollectionChangeSet changeSet) { + public void onChange(RealmList results, OrderedCollectionChangeSet changeSet) { if (!emitter.isDisposed()) { emitter.onNext(new CollectionChange<>(results, changeSet)); } @@ -583,12 +583,12 @@ public void run() { } @Override - public Single> from(Realm realm, RealmQuery query) { + public Single> from(Realm realm, RealmQuery query) { throw new RuntimeException("RealmQuery not supported yet."); } @Override - public Single> from(DynamicRealm realm, RealmQuery query) { + public Single> from(DynamicRealm realm, RealmQuery query) { throw new RuntimeException("RealmQuery not supported yet."); } diff --git a/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java index 04af747f38..e5784f42ac 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RxObservableFactory.java @@ -67,7 +67,7 @@ public interface RxObservableFactory { * @param type of RealmObject * @return Rx observable that emit all updates to the RealmObject. */ - Flowable> from(Realm realm, RealmResults results); + Flowable> from(Realm realm, RealmResults results); /** * Creates an Observable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and @@ -82,7 +82,7 @@ public interface RxObservableFactory { * @param type of RealmObject * @return Rx observable that emit all updates + their changeset. */ - Observable>> changesetsFrom(Realm realm, RealmResults results); + Observable>> changesetsFrom(Realm realm, RealmResults results); /** * Creates a Flowable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and @@ -94,7 +94,7 @@ public interface RxObservableFactory { * @param realm {@link DynamicRealm} instance results are coming from. * @return Rx observable that emit all updates to the RealmResults. */ - Flowable> from(DynamicRealm realm, RealmResults results); + Flowable> from(DynamicRealm realm, RealmResults results); /** * Creates an Observable for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and @@ -108,7 +108,7 @@ public interface RxObservableFactory { * @param realm {@link Realm} instance results are coming from. * @return Rx observable that emit all updates + their changeset. */ - Observable>> changesetsFrom(DynamicRealm realm, RealmResults results); + Observable>> changesetsFrom(DynamicRealm realm, RealmResults results); /** * Creates an Observable for a {@link RealmList}. It should emit the initial list when subscribed to and on each @@ -120,9 +120,9 @@ public interface RxObservableFactory { * * @param list RealmObject to listen to changes for. * @param realm {@link Realm} instance list is coming from. - * @param type of RealmObject + * @param type of query target */ - Flowable> from(Realm realm, RealmList list); + Flowable> from(Realm realm, RealmList list); /** * Creates an Observable for a {@link RealmList}. It should emit the initial RealmList when subscribed to and @@ -137,7 +137,7 @@ public interface RxObservableFactory { * @param type of RealmObject * @return Rx observable that emit all updates + their changeset. */ - Observable>> changesetsFrom(Realm realm, RealmList list); + Observable>> changesetsFrom(Realm realm, RealmList list); /** * Creates a Flowable for a {@link RealmList}. It should emit the initial list when subscribed to and on each @@ -150,7 +150,7 @@ public interface RxObservableFactory { * @param list RealmList to listen to changes for. * @param realm {@link DynamicRealm} instance list is coming from. */ - Flowable> from(DynamicRealm realm, RealmList list); + Flowable> from(DynamicRealm realm, RealmList list); /** * Creates an Observable for a {@link RealmList}. It should emit the initial RealmList when subscribed to and @@ -164,7 +164,7 @@ public interface RxObservableFactory { * @param realm {@link Realm} instance list is coming from. * @return Rx observable that emit all updates + their changeset. */ - Observable>> changesetsFrom(DynamicRealm realm, RealmList list); + Observable>> changesetsFrom(DynamicRealm realm, RealmList list); /** * Creates a Flowable for a {@link RealmObject}. It should emit the initial object when subscribed to and on each @@ -174,7 +174,7 @@ public interface RxObservableFactory { * * @param object RealmObject to listen to changes for. * @param realm {@link Realm} instance object is coming from. - * @param type of RealmObject + * @param type of query target */ Flowable from(Realm realm, E object); @@ -223,9 +223,9 @@ public interface RxObservableFactory { * * @param query RealmQuery to emit. * @param realm {@link Realm} instance query is coming from. - * @param type of RealmObject + * @param type of query target */ - Single> from(Realm realm, RealmQuery query); + Single> from(Realm realm, RealmQuery query); /** * Creates a Single from a {@link RealmQuery}. It should emit the query and then complete. @@ -235,5 +235,5 @@ public interface RxObservableFactory { * @param query RealmObject to listen to changes for. * @param realm {@link DynamicRealm} instance query is coming from. */ - Single> from(DynamicRealm realm, RealmQuery query); + Single> from(DynamicRealm realm, RealmQuery query); } From da1572d24b90b7350850d1def550900ea5e82747 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 1 Oct 2017 17:55:04 +0200 Subject: [PATCH 0997/2110] Remove unsupported types from RealmFieldType (#5338) --- CHANGELOG.md | 1 + .../io/realm/LinkingObjectsDynamicTests.java | 5 ---- .../androidTest/java/io/realm/QueryTests.java | 3 --- .../androidTest/java/io/realm/TestHelper.java | 26 ++++++++++++------- .../realm/internal/SortDescriptorTests.java | 3 --- .../java/io/realm/DynamicRealmObject.java | 6 ----- .../main/java/io/realm/RealmFieldType.java | 8 ------ 7 files changed, 17 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c09db3b3d7..771001e515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Removed deprecated API `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. * `SyncUser.Callback` to becomes generic. * Removed `SyncUser.getAccessToken` method from public API, and rename it to `getRefreshToken`. +* Removed `UNSUPPORTED_TABLE`, `UNSUPPORTED_MIXED` and `UNSUPPORTED_DATE` from `RealmFieldType`. * Relaxed upper bound of type parameter of `RealmList`, `RealmQuery`, `RealmResults`, `RealmCollection`, `OrderedRealmCollection` and `OrderedRealmCollectionSnapshot`. ## Deprecated diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index b94b6b4593..9d881dd773 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -174,11 +174,6 @@ public void linkingObjects_invalidFieldType() { for (RealmFieldType fieldType : RealmFieldType.values()) { try { switch (fieldType) { - // skip unsupported types - case UNSUPPORTED_TABLE: // fall-through - case UNSUPPORTED_MIXED: // fall-through - case UNSUPPORTED_DATE: - continue; // skip valid types case OBJECT: // fall-through case LIST: diff --git a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java index a2d025dfb7..5ef1233760 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java @@ -62,9 +62,6 @@ public abstract class QueryTests { list = new ArrayList<>(Arrays.asList(RealmFieldType.values())); list.removeAll(SUPPORTED_IS_EMPTY_TYPES); - list.remove(RealmFieldType.UNSUPPORTED_MIXED); - list.remove(RealmFieldType.UNSUPPORTED_TABLE); - list.remove(RealmFieldType.UNSUPPORTED_DATE); // FIXME zaki50 revisit once we implement query for Primitive List list.remove(RealmFieldType.STRING_LIST); diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index d253569dcb..530ca471b2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -92,21 +92,29 @@ public void onResult(int count) { } public static RealmFieldType getColumnType(Object o) { - if (o instanceof Boolean) + if (o instanceof Boolean) { return RealmFieldType.BOOLEAN; - if (o instanceof String) + } + if (o instanceof String) { return RealmFieldType.STRING; - if (o instanceof Long) + } + if (o instanceof Long) { return RealmFieldType.INTEGER; - if (o instanceof Float) + } + if (o instanceof Float) { return RealmFieldType.FLOAT; - if (o instanceof Double) + } + if (o instanceof Double) { return RealmFieldType.DOUBLE; - if (o instanceof Date) + } + if (o instanceof Date) { return RealmFieldType.DATE; - if (o instanceof byte[]) + } + if (o instanceof byte[]) { return RealmFieldType.BINARY; - return RealmFieldType.UNSUPPORTED_MIXED; + } + + throw new IllegalArgumentException("Unsupported type"); } /** @@ -203,8 +211,6 @@ public static long addRowWithValues(Table table, Object... values) { table.setBinaryByteArray(columnIndex, rowIndex, (byte[]) value, false); } break; - case UNSUPPORTED_MIXED: - case UNSUPPORTED_TABLE: default: throw new RuntimeException("Unexpected columnType: " + String.valueOf(colTypes[(int) columnIndex])); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java index 381b408de3..a8c4bd22f5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java @@ -240,9 +240,6 @@ private Set getValidFieldTypes(Set filter) { for (RealmFieldType type : RealmFieldType.values()) { if (!filter.contains(type)) { switch (type) { - case UNSUPPORTED_DATE: - case UNSUPPORTED_TABLE: - case UNSUPPORTED_MIXED: case LINKING_OBJECTS: // TODO: should be supported?s case INTEGER_LIST: // FIXME zaki50 revisit this once Primitive List query is implemented case BOOLEAN_LIST: diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 20b9a54a79..012085f1f8 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -113,8 +113,6 @@ public E get(String fieldName) { return (E) getObject(fieldName); case LIST: return (E) getList(fieldName); - case UNSUPPORTED_TABLE: - case UNSUPPORTED_MIXED: default: throw new IllegalStateException("Field type not supported: " + type); } @@ -395,8 +393,6 @@ public boolean isNull(String fieldName) { case DATE: return proxyState.getRow$realm().isNull(columnIndex); case LIST: - case UNSUPPORTED_TABLE: - case UNSUPPORTED_MIXED: default: return false; } @@ -970,8 +966,6 @@ public String toString() { String targetClassName = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName(); sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getList(columnIndex).size())); break; - case UNSUPPORTED_TABLE: - case UNSUPPORTED_MIXED: default: sb.append("?"); break; diff --git a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java index f28c4b5c52..2514255c69 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java +++ b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java @@ -72,9 +72,6 @@ public enum RealmFieldType { BOOLEAN(CORE_TYPE_VALUE_BOOLEAN), STRING(CORE_TYPE_VALUE_STRING), BINARY(CORE_TYPE_VALUE_BINARY), - UNSUPPORTED_TABLE(CORE_TYPE_VALUE_UNSUPPORTED_TABLE), - UNSUPPORTED_MIXED(CORE_TYPE_VALUE_UNSUPPORTED_MIXED), - UNSUPPORTED_DATE(CORE_TYPE_VALUE_UNSUPPORTED_DATE), DATE(CORE_TYPE_VALUE_DATE), FLOAT(CORE_TYPE_VALUE_FLOAT), DOUBLE(CORE_TYPE_VALUE_DOUBLE), @@ -137,11 +134,6 @@ public boolean isValid(Object obj) { return (obj instanceof String); case CORE_TYPE_VALUE_BINARY: return (obj instanceof byte[] || obj instanceof ByteBuffer); - case CORE_TYPE_VALUE_UNSUPPORTED_TABLE: - //noinspection ConstantConditions - return (obj == null || obj instanceof Object[][]); - case CORE_TYPE_VALUE_UNSUPPORTED_DATE: - return (obj instanceof java.util.Date); // The unused DateTime. case CORE_TYPE_VALUE_DATE: return (obj instanceof java.util.Date); case CORE_TYPE_VALUE_FLOAT: From 8b0ba19b4f48795794dbb4182e633df40653d641 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 1 Oct 2017 18:47:12 +0200 Subject: [PATCH 0998/2110] Expose SyncSessionStopPolicy in SyncConfiguration (#5356) Expose SyncSessionStopPolicy as an option on SyncConfiguration. This makes it possible to make integration tests more deterministic. --- .../java/io/realm/SchemaTests.java | 1 - .../java/io/realm/SessionTests.java | 1 - .../java/io/realm/SyncConfigurationTests.java | 1 - .../io/realm/SyncedRealmMigrationTests.java | 2 - .../TestSyncConfigurationFactory.java | 10 +++-- .../cpp/io_realm_internal_OsRealmConfig.cpp | 6 ++- .../io/realm/internal/ObjectServerFacade.java | 2 +- .../java/io/realm/internal/OsRealmConfig.java | 38 ++++++++++++++----- .../java/io/realm/SyncConfiguration.java | 33 ++++++++++++++-- .../internal/SyncObjectServerFacade.java | 5 ++- .../java/io/realm/BaseIntegrationTest.java | 1 - .../java/io/realm/SSLConfigurationTests.java | 2 - .../{objectserver => }/SyncSessionTests.java | 15 ++------ .../objectserver/ProgressListenerTests.java | 2 +- .../suite/IntegrationTestSuite.java | 2 +- 15 files changed, 78 insertions(+), 43 deletions(-) rename realm/realm-library/src/androidTestObjectServer/java/io/realm/{rule => }/TestSyncConfigurationFactory.java (75%) rename realm/realm-library/src/syncIntegrationTest/java/io/realm/{objectserver => }/SyncSessionTests.java (98%) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java index b7c6e43b90..e1c613eb86 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java @@ -28,7 +28,6 @@ import java.util.Set; import io.realm.entities.StringOnly; -import io.realm.rule.TestSyncConfigurationFactory; import io.realm.util.SyncTestUtils; import static junit.framework.Assert.assertEquals; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 54172550a3..b2759133f5 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -34,7 +34,6 @@ import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import io.realm.rule.TestSyncConfigurationFactory; import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 43339cab79..922044f17d 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -34,7 +34,6 @@ import io.realm.entities.StringOnly; import io.realm.rule.RunInLooperThread; -import io.realm.rule.TestSyncConfigurationFactory; import static io.realm.util.SyncTestUtils.createNamedTestUser; import static io.realm.util.SyncTestUtils.createTestUser; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index 16ceef31c0..c87639c9c6 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -21,7 +21,6 @@ import org.hamcrest.CoreMatchers; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -42,7 +41,6 @@ import io.realm.internal.SharedRealm; import io.realm.exceptions.IncompatibleSyncedFileException; import io.realm.objectserver.utils.StringOnlyModule; -import io.realm.rule.TestSyncConfigurationFactory; import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/rule/TestSyncConfigurationFactory.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java similarity index 75% rename from realm/realm-library/src/androidTestObjectServer/java/io/realm/rule/TestSyncConfigurationFactory.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java index 23b8f908a5..9e7573c865 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/rule/TestSyncConfigurationFactory.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.realm.rule; +package io.realm; -import io.realm.SyncConfiguration; -import io.realm.SyncUser; +import io.realm.internal.OsRealmConfig; +import io.realm.rule.TestRealmConfigurationFactory; /** * Test rule used for creating SyncConfigurations. Will ensure that any Realm files are deleted when the @@ -26,6 +26,8 @@ public class TestSyncConfigurationFactory extends TestRealmConfigurationFactory { public SyncConfiguration.Builder createSyncConfigurationBuilder(SyncUser user, String url) { - return new SyncConfiguration.Builder(user, url).directory(getRoot()); + return new SyncConfiguration.Builder(user, url) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .directory(getRoot()); } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 567aae92fa..68fe07ac89 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -249,7 +249,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNo #if REALM_ENABLE_SYNC JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( JNIEnv* env, jclass, jlong native_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, - jstring j_reresh_token) + jstring j_reresh_token, jbyte j_session_stop_policy) { TR_ENTER_PTR(native_ptr) auto& config = *reinterpret_cast(native_ptr); @@ -320,9 +320,11 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSy std::copy_n(config.encryption_key.begin(), 64, sync_encryption_key->begin()); } + SyncSessionStopPolicy session_stop_policy = static_cast(j_session_stop_policy); + JStringAccessor realm_url(env, j_sync_realm_url); config.sync_config = std::make_shared(SyncConfig{ - user, realm_url, SyncSessionStopPolicy::AfterChangesUploaded, std::move(bind_handler), std::move(error_handler), + user, realm_url, session_stop_policy, std::move(bind_handler), std::move(error_handler), nullptr, sync_encryption_key}); } CATCH_STD() diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index f834da2b3c..b4cf5a0e4d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -67,7 +67,7 @@ public void realmClosed(RealmConfiguration configuration) { } public Object[] getUserAndServerUrl(RealmConfiguration config) { - return new Object[6]; + return new Object[7]; } public static ObjectServerFacade getFacade(boolean needSyncFacade) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index f83f4f765c..3ff9eeb4de 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -56,6 +56,22 @@ public byte getNativeValue() { } } + public enum SyncSessionStopPolicy { + IMMEDIATELY(SYNCSESSION_STOP_POLICY_VALUE_IMMEDIATELY), // Immediately stop the session as soon as all Realms/Sessions go out of scope. + LIVE_INDEFINITELY(SYNCSESSION_STOP_POLICY_VALUE_LIVE_INDEFINETELY), // Never stop the session. + AFTER_CHANGES_UPLOADED(SYNCSESSION_STOP_POLICY_VALUE_AFTER_CHANGES_UPLOADED); // Once all Realms/Sessions go out of scope, wait for uploads to complete and stop. + + final byte value; + + SyncSessionStopPolicy(byte value) { + this.value = value; + } + + public byte getNativeValue() { + return value; + } + } + /** * Builder class for creating {@code OsRealmConfig}. The {@code OsRealmConfig} instance should only be created by * {@link SharedRealm}. @@ -134,6 +150,9 @@ OsRealmConfig build() { private static final byte SCHEMA_MODE_VALUE_RESET_FILE = 3; private static final byte SCHEMA_MODE_VALUE_ADDITIVE = 4; private static final byte SCHEMA_MODE_VALUE_MANUAL = 5; + private static final byte SYNCSESSION_STOP_POLICY_VALUE_IMMEDIATELY = 0; + private static final byte SYNCSESSION_STOP_POLICY_VALUE_LIVE_INDEFINETELY = 1; + private static final byte SYNCSESSION_STOP_POLICY_VALUE_AFTER_CHANGES_UPLOADED = 2; private final static long nativeFinalizerPtr = nativeGetFinalizerPtr(); @@ -165,13 +184,14 @@ private OsRealmConfig(final RealmConfiguration config, NativeContext.dummyContext.addReference(this); // Retrieve Sync settings first. We need syncRealmUrl to identify if this is a SyncConfig - Object[] syncUserConf = ObjectServerFacade.getSyncFacadeIfPossible().getUserAndServerUrl(realmConfiguration); - String syncUserIdentifier = (String) syncUserConf[0]; - String syncRealmUrl = (String) syncUserConf[1]; - String syncRealmAuthUrl = (String) syncUserConf[2]; - String syncRefreshToken = (String) syncUserConf[3]; - boolean syncClientValidateSsl = (Boolean.TRUE.equals(syncUserConf[4])); - String syncSslTrustCertificatePath = (String) syncUserConf[5]; + Object[] syncConfigurationOptions = ObjectServerFacade.getSyncFacadeIfPossible().getUserAndServerUrl(realmConfiguration); + String syncUserIdentifier = (String) syncConfigurationOptions[0]; + String syncRealmUrl = (String) syncConfigurationOptions[1]; + String syncRealmAuthUrl = (String) syncConfigurationOptions[2]; + String syncRefreshToken = (String) syncConfigurationOptions[3]; + boolean syncClientValidateSsl = (Boolean.TRUE.equals(syncConfigurationOptions[4])); + String syncSslTrustCertificatePath = (String) syncConfigurationOptions[5]; + Byte sessionStopPolicy = (Byte) syncConfigurationOptions[6]; // Set encryption key byte[] key = config.getEncryptionKey(); @@ -215,7 +235,7 @@ private OsRealmConfig(final RealmConfiguration config, // Set sync config if (syncRealmUrl != null) { nativeCreateAndSetSyncConfig(nativePtr, syncRealmUrl, syncRealmAuthUrl, syncUserIdentifier, - syncRefreshToken); + syncRefreshToken, sessionStopPolicy); nativeSetSyncConfigSslSettings(nativePtr, syncClientValidateSsl, syncSslTrustCertificatePath); } } @@ -255,7 +275,7 @@ private native void nativeSetSchemaConfig(long nativePtr, byte schemaMode, long private static native void nativeEnableChangeNotification(long nativePtr, boolean enableNotification); private static native void nativeCreateAndSetSyncConfig(long nativePtr, String syncRealmUrl, - String authUrl, String userId, String refreshToken); + String authUrl, String userId, String refreshToken, byte sessionStopPolicy); private static native void nativeSetSyncConfigSslSettings(long nativePtr, boolean validateSsl, String trustCertificatePath); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 2b69a760a0..88b0d9dc25 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -88,6 +88,7 @@ public class SyncConfiguration extends RealmConfiguration { @Nullable private final String serverCertificateFilePath; private final boolean waitForInitialData; + private final OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy; private SyncConfiguration(File directory, String filename, @@ -116,7 +117,8 @@ private SyncConfiguration(File directory, String serverCertificateAssetName, @Nullable String serverCertificateFilePath, - boolean waitForInitialData + boolean waitForInitialData, + OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy ) { super(directory, filename, @@ -143,6 +145,7 @@ private SyncConfiguration(File directory, this.serverCertificateAssetName = serverCertificateAssetName; this.serverCertificateFilePath = serverCertificateFilePath; this.waitForInitialData = waitForInitialData; + this.sessionStopPolicy = sessionStopPolicy; } /** @@ -345,6 +348,17 @@ boolean isSyncConfiguration() { return true; } + /** + * NOTE: Only for internal usage. May change without warning. + * + * Returns the stop policy for the session for this Realm once the Realm has been closed. + * + * @return the stop policy used by the session once the Realm is closed. + */ + public OsRealmConfig.SyncSessionStopPolicy getSessionStopPolicy() { + return sessionStopPolicy; + } + /** * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ @@ -379,7 +393,7 @@ public static final class Builder { private String serverCertificateAssetName; @Nullable private String serverCertificateFilePath; - + private OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy = OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED; /** * Creates an instance of the Builder for the SyncConfiguration. *

            @@ -613,6 +627,18 @@ SyncConfiguration.Builder schema(Class firstClass, Class @@ -937,7 +963,8 @@ public SyncConfiguration build() { syncClientValidateSsl, serverCertificateAssetName, serverCertificateFilePath, - waitForServerChanges + waitForServerChanges, + sessionStopPolicy ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 4de59a8788..f4c3d4d9df 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -92,9 +92,10 @@ public Object[] getUserAndServerUrl(RealmConfiguration config) { String rosUserIdentity = user.getIdentity(); String syncRealmAuthUrl = user.getAuthenticationUrl().toString(); String rosSerializedUser = user.toJson(); - return new Object[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosSerializedUser, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath()}; + byte sessionStopPolicy = syncConfig.getSessionStopPolicy().getNativeValue(); + return new Object[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosSerializedUser, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath(), sessionStopPolicy}; } else { - return new Object[6]; + return new Object[7]; } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 65542f0925..fa0406c914 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -32,7 +32,6 @@ import io.realm.objectserver.utils.HttpUtils; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunInLooperThread; -import io.realm.rule.TestSyncConfigurationFactory; /** diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index 08888b9941..8d16609bee 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -18,7 +18,6 @@ import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; -import android.text.style.TabStopSpan; import org.junit.Ignore; import org.junit.Rule; @@ -34,7 +33,6 @@ import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; -import io.realm.rule.TestSyncConfigurationFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java similarity index 98% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java rename to realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index b260e71ccb..0c6f778b30 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -1,4 +1,4 @@ -package io.realm.objectserver; +package io.realm; import android.os.Handler; import android.os.HandlerThread; @@ -16,22 +16,12 @@ import java.util.UUID; import java.util.concurrent.CountDownLatch; -import io.realm.Realm; -import io.realm.RealmChangeListener; -import io.realm.RealmResults; -import io.realm.StandardIntegrationTest; -import io.realm.SyncConfiguration; -import io.realm.SyncCredentials; -import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncUser; -import io.realm.TestHelper; import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; +import io.realm.internal.OsRealmConfig; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.TestSyncConfigurationFactory; import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; @@ -327,6 +317,7 @@ public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { final SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED) .modules(new StringOnlyModule()) .build(); Realm realm = Realm.getInstance(syncConfiguration); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index e1943ae61c..307c46660a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -43,7 +43,7 @@ import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.TestSyncConfigurationFactory; +import io.realm.TestSyncConfigurationFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java index 85c69b9236..fef9b158bd 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java @@ -27,7 +27,7 @@ import io.realm.objectserver.ManagementRealmTests; import io.realm.objectserver.ProcessCommitTests; import io.realm.objectserver.ProgressListenerTests; -import io.realm.objectserver.SyncSessionTests; +import io.realm.SyncSessionTests; // Test suite includes all integration tests. Makes it easy to run all integration tests in the Android Studio. @RunWith(Suite.class) From 34650574da7203d2859bc24bccc7b0046714570c Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 2 Oct 2017 15:09:28 +0800 Subject: [PATCH 0999/2110] UOE instead of IAE for methods in value list --- .../java/io/realm/ManagedRealmListForValueTests.java | 4 ++-- realm/realm-library/src/main/java/io/realm/RealmList.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValueTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValueTests.java index 0e8cddf5d4..3518becf0a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValueTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValueTests.java @@ -900,7 +900,7 @@ public void execute(Realm realm) { list.size(); } - @Test(expected = IllegalStateException.class) + @Test(expected = UnsupportedOperationException.class) public void where() { list.where(); } @@ -1556,7 +1556,7 @@ public void onChange(RealmList collection, @Nullable OrderedCollectionCh @Test public void createSnapshot() { - thrown.expect(IllegalStateException.class); + thrown.expect(UnsupportedOperationException.class); thrown.expectMessage(is(RealmList.ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE)); list.createSnapshot(); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 7bcbe78c35..0b0ef1e75e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -576,7 +576,7 @@ public RealmQuery where() { if (isManaged()) { checkValidRealm(); if (!osListOperator.forRealmModel()) { - throw new IllegalStateException(ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE); + throw new UnsupportedOperationException(ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE); } return RealmQuery.createQueryFromList(this); } else { @@ -755,7 +755,7 @@ public OrderedRealmCollectionSnapshot createSnapshot() { } checkValidRealm(); if (!osListOperator.forRealmModel()) { - throw new IllegalStateException(ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE); + throw new UnsupportedOperationException(ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE); } if (className != null) { return new OrderedRealmCollectionSnapshot<>( From d361e1133d583de5ffce8b6e1092b080ea839baf Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 2 Oct 2017 19:47:18 +0100 Subject: [PATCH 1000/2110] Support Partial Sync (#5359) * Add preview support for partial sync --- CHANGELOG.md | 1 + .../io/realm/AuthenticateRequestTests.java | 4 +- .../src/main/cpp/io_realm_SyncSession.cpp | 3 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 11 +- .../cpp/io_realm_internal_SharedRealm.cpp | 58 +++++++ .../src/main/java/io/realm/Realm.java | 46 +++++- .../io/realm/internal/ObjectServerFacade.java | 5 +- .../java/io/realm/internal/OsRealmConfig.java | 26 ++- .../java/io/realm/internal/SharedRealm.java | 45 +++++- .../java/io/realm/SyncConfiguration.java | 33 +++- .../java/io/realm/SyncSession.java | 10 +- .../internal/SyncObjectServerFacade.java | 11 +- .../internal/network/AuthenticateRequest.java | 8 +- .../network/OkHttpAuthenticationServer.java | 4 +- .../java/io/realm/SyncSessionTests.java | 1 + .../realm/objectserver/PartialSyncTests.java | 149 ++++++++++++++++++ .../objectserver/model/PartialSyncModule.java | 23 +++ .../model/PartialSyncObjectA.java | 40 +++++ .../model/PartialSyncObjectB.java | 49 ++++++ 19 files changed, 498 insertions(+), 29 deletions(-) create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncModule.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncObjectA.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncObjectB.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 771001e515..1f0570ff09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ * [ObjectServer] `SyncUserInfo` now also exposes a users metadata using `SyncUserInfo.getMetadata()` * Minor performance improvement when copy/insert objects into Realm. +* [ObjectServer] Added preview support for partial synchronization (#5276). This feature is in `@Beta` and will probably change. ### Bug Fixes diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index a7fff7a2ef..b2aaa7cc5a 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -40,7 +40,7 @@ public void setUp() { @Test public void realmLogin() throws URISyntaxException, JSONException { Token t = SyncTestUtils.createTestUser().getRefreshToken(); - AuthenticateRequest request = AuthenticateRequest.realmLogin(t, new URI("realm://objectserver/" + t.identity() + "/default")); + AuthenticateRequest request = AuthenticateRequest.realmLogin(t, new URI("realm://objectserver/" + t.identity() + "/default").getPath()); JSONObject obj = new JSONObject(request.toJson()); assertEquals("/" + t.identity() + "/default", obj.get("path")); @@ -61,7 +61,7 @@ public void userLogin() throws URISyntaxException, JSONException { @Test public void userRefresh() throws URISyntaxException, JSONException { Token t = SyncTestUtils.createTestUser().getRefreshToken(); - AuthenticateRequest request = AuthenticateRequest.userRefresh(t, new URI("realm://objectserver/" + t.identity() + "/default")); + AuthenticateRequest request = AuthenticateRequest.userRefresh(t, new URI("realm://objectserver/" + t.identity() + "/default").getPath()); JSONObject obj = new JSONObject(request.toJson()); assertTrue(obj.has("path")); diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index a113c1479d..20c8f764cd 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -63,7 +63,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JN if (session) { JStringAccessor access_token(env, j_access_token); JStringAccessor realm_url(env, j_sync_realm_url); - session->refresh_access_token(access_token, std::string(realm_url)); + + session->refresh_access_token(access_token, std::string(session->config().realm_url())); return JNI_TRUE; } else { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 68fe07ac89..9a891e070e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -247,9 +247,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNo } #if REALM_ENABLE_SYNC -JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( +JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( JNIEnv* env, jclass, jlong native_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, - jstring j_reresh_token, jbyte j_session_stop_policy) + jstring j_refresh_token, jboolean j_is_partial, jbyte j_session_stop_policy) { TR_ENTER_PTR(native_ptr) auto& config = *reinterpret_cast(native_ptr); @@ -310,7 +310,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSy std::shared_ptr user = SyncManager::shared().get_existing_logged_in_user(sync_user_identifier); if (!user) { JStringAccessor realm_auth_url(env, j_auth_url); - JStringAccessor refresh_token(env, j_reresh_token); + JStringAccessor refresh_token(env, j_refresh_token); user = SyncManager::shared().get_user(sync_user_identifier, refresh_token); } @@ -326,8 +326,13 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSy config.sync_config = std::make_shared(SyncConfig{ user, realm_url, session_stop_policy, std::move(bind_handler), std::move(error_handler), nullptr, sync_encryption_key}); + config.sync_config->is_partial = (j_is_partial == JNI_TRUE); + + return to_jstring(env, config.sync_config->realm_url().c_str()); + } CATCH_STD() + return nullptr; } JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigSslSettings( diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp index 474a4c54cd..9649f18563 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp @@ -19,6 +19,10 @@ #include "object-store/src/sync/sync_manager.hpp" #include "object-store/src/sync/sync_config.hpp" #include "object-store/src/sync/sync_session.hpp" +#include "object-store/src/results.hpp" +#include "object-store/src/sync/partial_sync.hpp" + +#include "observable_collection_wrapper.hpp" #endif #include @@ -41,6 +45,10 @@ using namespace realm::jni_util; static const char* c_table_name_exists_exception_msg = "Class already exists: '%1'."; +#if REALM_ENABLE_SYNC // used only for partial sync now +typedef ObservableCollectionWrapper ResultsWrapper; +#endif + JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv* env, jclass, jstring temporary_directory_path) { @@ -501,3 +509,53 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRegisterSchemaCh java_binding_context.set_schema_changed_callback(env, j_schema_changed_callback); } } + +JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRegisterPartialSyncQuery( + REALM_UNUSED JNIEnv* env, REALM_UNUSED jobject j_shared_realm_instance, REALM_UNUSED jlong shared_realm_ptr, REALM_UNUSED jstring j_class_name, + REALM_UNUSED jstring j_query, REALM_UNUSED jobject j_callback) +{ + TR_ENTER_PTR(shared_realm_ptr) + +#if REALM_ENABLE_SYNC + + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + try { + JStringAccessor class_name(env, j_class_name); // throws + JStringAccessor query(env, j_query); // throws + + // The lambda will capture the copied reference and it will be unreferenced when the lambda's life cycle is over. + // That happens when the Realm is closed or the callback has been triggered once. + JavaGlobalRef j_callback_ref(env, j_callback); + JavaGlobalWeakRef j_shared_realm_instance_ref(env, j_shared_realm_instance); + + static JavaClass shared_realm_class(env, "io/realm/internal/SharedRealm"); + static JavaMethod partial_sync_cb(env, shared_realm_class, "runPartialSyncRegistrationCallback", + "(Ljava/lang/String;JLio/realm/internal/SharedRealm$PartialSyncCallback;)V"); + + auto cb = [j_callback_ref, j_shared_realm_instance_ref](Results results, std::exception_ptr err) { + JNIEnv* env = JniUtils::get_env(true); + j_shared_realm_instance_ref.call_with_local_ref(env, [&](JNIEnv*, jobject row_obj) { + if (err) { + try { + std::rethrow_exception(err); + } + catch (const std::exception& e) { + env->CallVoidMethod(row_obj, partial_sync_cb, to_jstring(env, e.what()), + reinterpret_cast(nullptr), j_callback_ref.get()); + } + return; + } + + auto wrapper = new ResultsWrapper(results); + env->CallVoidMethod(row_obj, partial_sync_cb, nullptr, reinterpret_cast(wrapper), + j_callback_ref.get()); + }); + }; + + partial_sync::register_query(shared_realm, class_name, query, std::move(cb)); + } + CATCH_STD() +#else + REALM_TERMINATE("Unsupported operation. Only available when used with the Realm Object Server"); +#endif +} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 6644275d38..b5416f96e4 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -46,9 +46,10 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; -import io.reactivex.Flowable; import javax.annotation.Nullable; +import io.reactivex.Flowable; +import io.realm.annotations.Beta; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; @@ -1684,6 +1685,44 @@ public static boolean compactRealm(RealmConfiguration configuration) { return BaseRealm.compactRealm(configuration); } + /** + * If the Realm is a partially synchronized Realm, fetch and synchronize the objects of a given + * object type that match the given query (in string format). + * + * The results will be returned asynchronously in the callback. + * + * @param clazz the class to query. + * @param query string query. + * @param callback A callback used to vend the results of a partial sync fetch. + * @throws IllegalStateException if it is called from a non-Looper or {@link IntentService} thread. + * @throws IllegalStateException if called from a non-synchronized (Realm Object Server) Realm. + */ + @Beta + public void subscribeToObjects(final Class clazz, String query, final PartialSyncCallback callback) { + checkIfValid(); + if (!configuration.isSyncConfiguration()) { + throw new IllegalStateException("Partial sync is only available for synchronized Realm (Realm Object Server)"); + } + + sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); + + String className = configuration.getSchemaMediator().getSimpleClassName(clazz); + SharedRealm.PartialSyncCallback internalCallback = new SharedRealm.PartialSyncCallback(className) { + @Override + public void onSuccess(io.realm.internal.Collection osResults) { + RealmResults results = new RealmResults<>(Realm.this, osResults, clazz); + callback.onSuccess(results); + } + + @Override + public void onError(RealmException error) { + callback.onError(error); + } + }; + + sharedRealm.registerPartialSyncQuery(query, internalCallback); + } + Table getTable(Class clazz) { return schema.getTable(clazz); } @@ -1800,4 +1839,9 @@ public void onError(Throwable exception) { super.onError(exception); } } + + public static abstract class PartialSyncCallback { + public abstract void onSuccess(RealmResults results); + public abstract void onError(RealmException error); + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index b4cf5a0e4d..2e4cce938a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -19,6 +19,7 @@ import android.content.Context; import java.lang.reflect.InvocationTargetException; +import java.net.URI; import io.realm.RealmConfiguration; import io.realm.exceptions.RealmException; @@ -67,7 +68,7 @@ public void realmClosed(RealmConfiguration configuration) { } public Object[] getUserAndServerUrl(RealmConfiguration config) { - return new Object[7]; + return new Object[8]; } public static ObjectServerFacade getFacade(boolean needSyncFacade) { @@ -86,7 +87,7 @@ public static ObjectServerFacade getSyncFacadeIfPossible() { } // If no session yet exists for this path. Wrap a new Java Session around an existing OS one. - public void wrapObjectStoreSessionIfRequired(RealmConfiguration config) { + public void wrapObjectStoreSessionIfRequired(OsRealmConfig config) { } public String getSyncServerCertificateAssetName(RealmConfiguration config) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 3ff9eeb4de..afc199fe72 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -16,10 +16,14 @@ package io.realm.internal; +import java.net.URI; +import java.net.URISyntaxException; + import javax.annotation.Nullable; import io.realm.CompactOnLaunchCallback; import io.realm.RealmConfiguration; +import io.realm.log.RealmLog; /** * Java wrapper of Object Store's Realm::Config. @@ -157,6 +161,7 @@ OsRealmConfig build() { private final static long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final RealmConfiguration realmConfiguration; + private final URI resolvedRealmURI; private final long nativePtr; // Every SharedRealm instance has to be created from an OsRealmConfig instance. And the SharedRealm's NativeContext // object will be the same as the context here. This is because of we may create different SharedRealm instances @@ -192,6 +197,7 @@ private OsRealmConfig(final RealmConfiguration config, boolean syncClientValidateSsl = (Boolean.TRUE.equals(syncConfigurationOptions[4])); String syncSslTrustCertificatePath = (String) syncConfigurationOptions[5]; Byte sessionStopPolicy = (Byte) syncConfigurationOptions[6]; + boolean isPartial = (Boolean.TRUE.equals(syncConfigurationOptions[7])); // Set encryption key byte[] key = config.getEncryptionKey(); @@ -232,12 +238,20 @@ private OsRealmConfig(final RealmConfiguration config, if (initializationCallback != null) { nativeSetInitializationCallback(nativePtr, initializationCallback); } + + URI resolvedRealmURI = null; // Set sync config if (syncRealmUrl != null) { - nativeCreateAndSetSyncConfig(nativePtr, syncRealmUrl, syncRealmAuthUrl, syncUserIdentifier, - syncRefreshToken, sessionStopPolicy); + String resolvedSyncRealmUrl = nativeCreateAndSetSyncConfig(nativePtr, syncRealmUrl, syncRealmAuthUrl, syncUserIdentifier, + syncRefreshToken, isPartial, sessionStopPolicy); + try { + resolvedRealmURI = new URI(resolvedSyncRealmUrl); + } catch (URISyntaxException e) { + RealmLog.error(e, "Cannot create a URI from the Realm URL address"); + } nativeSetSyncConfigSslSettings(nativePtr, syncClientValidateSsl, syncSslTrustCertificatePath); } + this.resolvedRealmURI = resolvedRealmURI; } @Override @@ -254,6 +268,10 @@ public RealmConfiguration getRealmConfiguration() { return realmConfiguration; } + public URI getResolvedRealmURI() { + return resolvedRealmURI; + } + NativeContext getContext() { return context; } @@ -274,8 +292,8 @@ private native void nativeSetSchemaConfig(long nativePtr, byte schemaMode, long private static native void nativeEnableChangeNotification(long nativePtr, boolean enableNotification); - private static native void nativeCreateAndSetSyncConfig(long nativePtr, String syncRealmUrl, - String authUrl, String userId, String refreshToken, byte sessionStopPolicy); + private static native String nativeCreateAndSetSyncConfig(long nativePtr, String syncRealmUrl, + String authUrl, String userId, String refreshToken, boolean isPartial, byte sessionStopPolicy); private static native void nativeSetSyncConfigSslSettings(long nativePtr, boolean validateSsl, String trustCertificatePath); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 13ac86c7d8..32a52cc644 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -26,6 +26,7 @@ import javax.annotation.Nullable; import io.realm.RealmConfiguration; +import io.realm.exceptions.RealmException; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; @@ -166,6 +167,21 @@ public interface SchemaChangedCallback { void onSchemaChanged(); } + /** + * Callback function to be called from JNI by Object Store when the partial sync results returned. + */ + @Keep + public abstract static class PartialSyncCallback { + private final String className; + + protected PartialSyncCallback(String className) { + this.className = className; + } + + public abstract void onSuccess(Collection results); + public abstract void onError(RealmException error); + } + private final OsRealmConfig osRealmConfig; private final long nativePtr; final NativeContext context; @@ -220,7 +236,7 @@ public static SharedRealm getInstance(RealmConfiguration config) { */ public static SharedRealm getInstance(OsRealmConfig.Builder configBuilder) { OsRealmConfig osRealmConfig = configBuilder.build(); - ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(osRealmConfig.getRealmConfiguration()); + ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(osRealmConfig); return new SharedRealm(osRealmConfig); } @@ -348,6 +364,10 @@ public boolean isAutoRefresh() { return nativeIsAutoRefresh(nativePtr); } + public void registerPartialSyncQuery(String query, PartialSyncCallback callback) { + nativeRegisterPartialSyncQuery(nativePtr, callback.className, query, callback); + } + public RealmConfiguration getConfiguration() { return osRealmConfig.getRealmConfiguration(); } @@ -476,6 +496,26 @@ private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealm callback.onInit(new SharedRealm(nativeSharedRealmPtr, osRealmConfig)); } + /** + * Called from JNI when the partial sync callback is invoked from the ObjectStore. + * @param error if the partial sync query failed to register. + * @param nativeResultsPtr pointer to the {@code Results} of the partial sync query. + * @param callback the callback registered from the user to notify the success/error of the partial sync query. + */ + @SuppressWarnings("unused") + private void runPartialSyncRegistrationCallback(@Nullable String error, long nativeResultsPtr, + PartialSyncCallback callback) { + if (error != null) { + callback.onError(new RealmException(error)); + } else { + @SuppressWarnings("ConstantConditions") + Table table = getTable(Table.getTableNameForClass(callback.className)); + Collection results = new Collection(this, table, nativeResultsPtr, true); + callback.onSuccess(results); + } + } + + private static native void nativeInit(String temporaryDirectoryPath); private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier); @@ -536,4 +576,7 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare private static native long nativeGetSchemaInfo(long nativePtr); private static native void nativeRegisterSchemaChangedCallback(long nativePtr, SchemaChangedCallback callback); + + private native void nativeRegisterPartialSyncQuery( + long nativeSharedRealmPtr, String className, String query, PartialSyncCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 88b0d9dc25..61de76ff92 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -89,6 +89,7 @@ public class SyncConfiguration extends RealmConfiguration { private final String serverCertificateFilePath; private final boolean waitForInitialData; private final OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy; + private final boolean isPartial; private SyncConfiguration(File directory, String filename, @@ -118,7 +119,8 @@ private SyncConfiguration(File directory, @Nullable String serverCertificateFilePath, boolean waitForInitialData, - OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy + OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy, + boolean isPartial ) { super(directory, filename, @@ -146,6 +148,7 @@ private SyncConfiguration(File directory, this.serverCertificateFilePath = serverCertificateFilePath; this.waitForInitialData = waitForInitialData; this.sessionStopPolicy = sessionStopPolicy; + this.isPartial = isPartial; } /** @@ -359,6 +362,21 @@ public OsRealmConfig.SyncSessionStopPolicy getSessionStopPolicy() { return sessionStopPolicy; } + /** + * Whether this configuration is for a partial synchronization Realm. + * Partial synchronization allows a synchronized Realm to be opened in such a way that + * only objects requested by the user are synchronized to the device. You can use it by setting + * the {@link Builder#partialRealm()}, opening the Realm, and then calling + * {@link Realm#subscribeToObjects(Class, String, Realm.PartialSyncCallback)} with the type of + * object you're interested in, a string containing a query determining which objects you want + * to subscribe to, and a callback which will report the results. + * + * @return {@code true} to open a partial synchronization Realm {@code false} otherwise. + */ + public boolean isPartialRealm() { + return isPartial; + } + /** * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ @@ -394,6 +412,7 @@ public static final class Builder { @Nullable private String serverCertificateFilePath; private OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy = OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED; + private boolean isPartial = false; /** * Creates an instance of the Builder for the SyncConfiguration. *

            @@ -819,6 +838,15 @@ public SyncConfiguration.Builder readOnly() { return this; } + /** + * Setting this will open a partially synchronized Realm. + * @see #isPartialRealm() + */ + public SyncConfiguration.Builder partialRealm() { + this.isPartial = true; + return this; + } + private String MD5(String in) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); @@ -964,7 +992,8 @@ public SyncConfiguration build() { serverCertificateAssetName, serverCertificateFilePath, waitForServerChanges, - sessionStopPolicy + sessionStopPolicy, + isPartial ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 102eaba041..3b31657ca3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -101,6 +101,8 @@ public class SyncSession { private static final byte STATE_VALUE_INACTIVE = 3; private static final byte STATE_VALUE_ERROR = 4; + private URI resolvedRealmURI; + public enum State { WAITING_FOR_ACCESS_TOKEN(STATE_VALUE_WAITING_FOR_ACCESS_TOKEN), ACTIVE(STATE_VALUE_ACTIVE), @@ -376,6 +378,10 @@ public void uploadAllLocalChanges() throws InterruptedException { } } + public void setResolvedRealmURI(URI resolvedRealmURI) { + this.resolvedRealmURI = resolvedRealmURI; + } + /** * This method should only be called when guarded by the {@link #waitForChangesMutex}. * It will block into all changes have been either uploaded or downloaded depending on the chosen direction. @@ -552,7 +558,7 @@ protected AuthenticateResponse execute() { if (!isClosed && !Thread.currentThread().isInterrupted()) { return authServer.loginToRealm( getUser().getRefreshToken(), //refresh token in fact - configuration.getServerUrl(), + resolvedRealmURI, getUser().getAuthenticationUrl() ); } @@ -629,7 +635,7 @@ private void refreshAccessToken(final AuthenticationServer authServer) { @Override protected AuthenticateResponse execute() { if (!isClosed && !Thread.currentThread().isInterrupted()) { - return authServer.refreshUser(getUser().getRefreshToken(), configuration.getServerUrl(), getUser().getAuthenticationUrl()); + return authServer.refreshUser(getUser().getRefreshToken(), resolvedRealmURI, getUser().getAuthenticationUrl()); } return null; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index f4c3d4d9df..2b8616e8d7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -93,9 +93,9 @@ public Object[] getUserAndServerUrl(RealmConfiguration config) { String syncRealmAuthUrl = user.getAuthenticationUrl().toString(); String rosSerializedUser = user.toJson(); byte sessionStopPolicy = syncConfig.getSessionStopPolicy().getNativeValue(); - return new Object[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosSerializedUser, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath(), sessionStopPolicy}; + return new Object[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosSerializedUser, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath(), sessionStopPolicy, syncConfig.isPartialRealm()}; } else { - return new Object[7]; + return new Object[8]; } } @@ -104,9 +104,10 @@ public static Context getApplicationContext() { } @Override - public void wrapObjectStoreSessionIfRequired(RealmConfiguration config) { - if (config instanceof SyncConfiguration) { - SyncManager.getSession((SyncConfiguration) config); + public void wrapObjectStoreSessionIfRequired(OsRealmConfig config) { + if (config.getRealmConfiguration() instanceof SyncConfiguration) { + SyncSession session = SyncManager.getSession((SyncConfiguration) config.getRealmConfiguration()); + session.setResolvedRealmURI(config.getResolvedRealmURI()); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java index e921daec6c..bc333b9ab0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java @@ -56,11 +56,11 @@ public static AuthenticateRequest userLogin(SyncCredentials credentials) { /** * Generates a request for refreshing a user token. */ - public static AuthenticateRequest userRefresh(Token userToken, URI serverUrl) { + public static AuthenticateRequest userRefresh(Token userToken, String serverUrl) { return new AuthenticateRequest("realm", userToken.value(), SyncManager.APP_ID, - serverUrl.getPath(), + serverUrl, Collections.emptyMap() ); } @@ -68,12 +68,12 @@ public static AuthenticateRequest userRefresh(Token userToken, URI serverUrl) { /** * Generates a request for accessing a Realm */ - public static AuthenticateRequest realmLogin(Token userToken, URI serverUrl) { + public static AuthenticateRequest realmLogin(Token userToken, String serverUrl) { // Authenticate a given Realm path using an already logged in user. return new AuthenticateRequest("realm", userToken.value(), SyncManager.APP_ID, - serverUrl.getPath(), + serverUrl, Collections.emptyMap() ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index e3b5db8a09..c6fdeb9f36 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -66,7 +66,7 @@ public AuthenticateResponse loginUser(SyncCredentials credentials, URL authentic @Override public AuthenticateResponse loginToRealm(Token refreshToken, URI serverUrl, URL authenticationUrl) { try { - String requestBody = AuthenticateRequest.realmLogin(refreshToken, serverUrl).toJson(); + String requestBody = AuthenticateRequest.realmLogin(refreshToken, serverUrl.getPath()).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { return AuthenticateResponse.from(e); @@ -76,7 +76,7 @@ public AuthenticateResponse loginToRealm(Token refreshToken, URI serverUrl, URL @Override public AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL authenticationUrl) { try { - String requestBody = AuthenticateRequest.userRefresh(userToken, serverUrl).toJson(); + String requestBody = AuthenticateRequest.userRefresh(userToken, serverUrl.getPath()).toJson(); return authenticate(authenticationUrl, requestBody); } catch (Exception e) { return AuthenticateResponse.from(e); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 0c6f778b30..eb5c1a5701 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -446,4 +446,5 @@ public void run() { assertEquals(3, realm.where(StringOnly.class).count()); realm.close(); } + } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java new file mode 100644 index 0000000000..1414e392ed --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -0,0 +1,149 @@ +package io.realm.objectserver; + +import android.os.Handler; +import android.os.HandlerThread; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; + +import io.realm.Realm; +import io.realm.RealmResults; +import io.realm.StandardIntegrationTest; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.SyncUser; +import io.realm.TestHelper; +import io.realm.exceptions.RealmException; +import io.realm.objectserver.model.PartialSyncModule; +import io.realm.objectserver.model.PartialSyncObjectA; +import io.realm.objectserver.model.PartialSyncObjectB; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.UserFactory; +import io.realm.TestSyncConfigurationFactory; + +import static org.hamcrest.number.OrderingComparison.greaterThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class PartialSyncTests extends StandardIntegrationTest { + @Rule + public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @Test + public void partialSync() throws InterruptedException { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); + + final SyncConfiguration partialSyncConfig = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .modules(new PartialSyncModule()) + .partialRealm() + .build(); + SyncConfiguration adminConfig = configFactory + .createSyncConfigurationBuilder(adminUser, partialSyncConfig.getServerUrl().toString()) + .modules(new PartialSyncModule()) + .build(); + + // Using Admin user, populate the Realm. + Realm realm = Realm.getInstance(adminConfig); + realm.beginTransaction(); + PartialSyncObjectA objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(0); + objectA.setString("realm"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(1); + objectA.setString(""); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(2); + objectA.setString(""); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(3); + objectA.setString(""); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(4); + objectA.setString("realm"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(5); + objectA.setString("sync"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(6); + objectA.setString("partial"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(7); + objectA.setString("partial"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(8); + objectA.setString("partial"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(9); + objectA.setString("partial"); + + for (int i = 0; i < 10; i++) { + realm.createObject(PartialSyncObjectB.class).setNumber(i); + } + realm.commitTransaction(); + + SyncManager.getSession(adminConfig).uploadAllLocalChanges(); + realm.close(); + + final CountDownLatch latch = new CountDownLatch(2); + + HandlerThread handlerThread = new HandlerThread("background"); + handlerThread.start(); + Handler handler = new Handler(handlerThread.getLooper()); + handler.post(new Runnable() { + @Override + public void run() { + final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); + assertTrue(partialSyncRealm.isEmpty()); + + partialSyncRealm.subscribeToObjects(PartialSyncObjectA.class, "number > 5", new Realm.PartialSyncCallback() { + + @Override + public void onSuccess(RealmResults results) { + assertEquals(4, results.size()); + for (PartialSyncObjectA object : results) { + assertThat(object.getNumber(), greaterThan(5)); + assertEquals("partial", object.getString()); + } + // make sure the Realm contains only PartialSyncObjectA + assertEquals(0, partialSyncRealm.where(PartialSyncObjectB.class).count()); + latch.countDown(); + } + + @Override + public void onError(RealmException error) { + fail(error.getMessage()); + } + }); + + // Invalid query + partialSyncRealm.subscribeToObjects(PartialSyncObjectA.class, "invalid_property > 5", new Realm.PartialSyncCallback() { + + @Override + public void onSuccess(RealmResults results) { + fail("Invalid query should not succeed"); + } + + @Override + public void onError(RealmException error) { + assertNotNull(error); + partialSyncRealm.close(); + latch.countDown(); + } + }); + + } + }); + + TestHelper.awaitOrFail(latch); + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncModule.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncModule.java new file mode 100644 index 0000000000..6552293cf7 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncModule.java @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.model; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = {PartialSyncObjectA.class, PartialSyncObjectB.class}) +public class PartialSyncModule { +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncObjectA.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncObjectA.java new file mode 100644 index 0000000000..4af96f4768 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncObjectA.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.model; + +import io.realm.RealmObject; + +public class PartialSyncObjectA extends RealmObject { + private int number; + private String string; + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncObjectB.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncObjectB.java new file mode 100644 index 0000000000..7a6c453229 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncObjectB.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.objectserver.model; + +import io.realm.RealmObject; + +public class PartialSyncObjectB extends RealmObject { + private int number; + private String firstString; + private String secondString; + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public String getFirstString() { + return firstString; + } + + public void setFirstString(String firstString) { + this.firstString = firstString; + } + + public String getSecondString() { + return secondString; + } + + public void setSecondString(String secondString) { + this.secondString = secondString; + } +} From b922b099a8fc865e04d20d19261792abe55fcec8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 3 Oct 2017 12:43:20 +0200 Subject: [PATCH 1001/2110] RealmObjectSchema and DynamicRealm support for Lists of Primitives (#5329) --- CHANGELOG.md | 1 + .../io/realm/DynamicRealmObjectTests.java | 334 ++++++++++-- .../java/io/realm/RealmObjectSchemaTests.java | 261 +++++++++- .../src/main/cpp/io_realm_internal_Table.cpp | 475 ++++++++++-------- .../java/io/realm/DynamicRealmObject.java | 233 +++++++-- .../io/realm/ImmutableRealmObjectSchema.java | 5 + .../io/realm/MutableRealmObjectSchema.java | 22 +- .../java/io/realm/MutableRealmSchema.java | 6 +- .../src/main/java/io/realm/RealmList.java | 2 +- .../main/java/io/realm/RealmObjectSchema.java | 80 ++- .../java/io/realm/internal/CheckedRow.java | 11 - .../java/io/realm/internal/InvalidRow.java | 5 - .../java/io/realm/internal/PendingRow.java | 5 - .../src/main/java/io/realm/internal/Row.java | 3 - .../main/java/io/realm/internal/Table.java | 25 +- .../java/io/realm/internal/UncheckedRow.java | 5 - 16 files changed, 1112 insertions(+), 361 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f0570ff09..ae0b36785a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ## Enhancements +* Added support for primitive lists in migrations using `RealmObjectSchema.addRealmListField(String name, Class type)` (#5329). * Now users can use `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` as a type parameter of `RealmList`. ## Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index c87b63b0ce..3ac4e327db 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -95,6 +95,13 @@ public void setUp() { typedObj.setFieldDate(new Date(1000)); typedObj.setFieldObject(typedObj); typedObj.getFieldList().add(typedObj); + typedObj.getFieldIntegerList().add(1); + typedObj.getFieldStringList().add("str"); + typedObj.getFieldBooleanList().add(true); + typedObj.getFieldFloatList().add(1.23F); + typedObj.getFieldDoubleList().add(1.234D); + typedObj.getFieldBinaryList().add(new byte[] {1, 2, 3}); + typedObj.getFieldDateList().add(new Date(1000)); dObjTyped = new DynamicRealmObject(typedObj); realm.commitTransaction(); @@ -114,15 +121,16 @@ public void tearDown() { // Types supported by the DynamicRealmObject. private enum SupportedType { - BOOLEAN, SHORT, INT, LONG, BYTE, FLOAT, DOUBLE, STRING, BINARY, DATE, OBJECT, LIST + BOOLEAN, SHORT, INT, LONG, BYTE, FLOAT, DOUBLE, STRING, BINARY, DATE, OBJECT, LIST, + LIST_INTEGER, LIST_STRING, LIST_BOOLEAN, LIST_FLOAT, LIST_DOUBLE, LIST_BINARY, LIST_DATE } private enum ThreadConfinedMethods { GET_BOOLEAN, GET_BYTE, GET_SHORT, GET_INT, GET_LONG, GET_FLOAT, GET_DOUBLE, - GET_BLOB, GET_STRING, GET_DATE, GET_OBJECT, GET_LIST, GET, + GET_BLOB, GET_STRING, GET_DATE, GET_OBJECT, GET_LIST, GET_PRIMITIVE_LIST, GET, SET_BOOLEAN, SET_BYTE, SET_SHORT, SET_INT, SET_LONG, SET_FLOAT, SET_DOUBLE, - SET_BLOB, SET_STRING, SET_DATE, SET_OBJECT, SET_LIST, SET, + SET_BLOB, SET_STRING, SET_DATE, SET_OBJECT, SET_LIST, SET_PRIMITIVE_LIST, SET, IS_NULL, SET_NULL, @@ -134,33 +142,35 @@ private enum ThreadConfinedMethods { @SuppressWarnings({"ResultOfMethodCallIgnored", "EqualsWithItself", "SelfEquals"}) private static void callThreadConfinedMethod(DynamicRealmObject obj, ThreadConfinedMethods method) { switch (method) { - case GET_BOOLEAN: obj.getBoolean(AllJavaTypes.FIELD_BOOLEAN); break; - case GET_BYTE: obj.getByte(AllJavaTypes.FIELD_BYTE); break; - case GET_SHORT: obj.getShort(AllJavaTypes.FIELD_SHORT); break; - case GET_INT: obj.getInt(AllJavaTypes.FIELD_INT); break; - case GET_LONG: obj.getLong(AllJavaTypes.FIELD_LONG); break; - case GET_FLOAT: obj.getFloat(AllJavaTypes.FIELD_FLOAT); break; - case GET_DOUBLE: obj.getDouble(AllJavaTypes.FIELD_DOUBLE); break; - case GET_BLOB: obj.getBlob(AllJavaTypes.FIELD_BINARY); break; - case GET_STRING: obj.getString(AllJavaTypes.FIELD_STRING); break; - case GET_DATE: obj.getDate(AllJavaTypes.FIELD_DATE); break; - case GET_OBJECT: obj.getObject(AllJavaTypes.FIELD_OBJECT); break; - case GET_LIST: obj.getList(AllJavaTypes.FIELD_LIST); break; - case GET: obj.get(AllJavaTypes.FIELD_LONG); break; - - case SET_BOOLEAN: obj.setBoolean(AllJavaTypes.FIELD_BOOLEAN, true); break; - case SET_BYTE: obj.setByte(AllJavaTypes.FIELD_BYTE, (byte) 1); break; - case SET_SHORT: obj.setShort(AllJavaTypes.FIELD_SHORT, (short) 1); break; - case SET_INT: obj.setInt(AllJavaTypes.FIELD_INT, 1); break; - case SET_LONG: obj.setLong(AllJavaTypes.FIELD_LONG, 1L); break; - case SET_FLOAT: obj.setFloat(AllJavaTypes.FIELD_FLOAT, 1F); break; - case SET_DOUBLE: obj.setDouble(AllJavaTypes.FIELD_DOUBLE, 1D); break; - case SET_BLOB: obj.setBlob(AllJavaTypes.FIELD_BINARY, new byte[] {1, 2, 3}); break; - case SET_STRING: obj.setString(AllJavaTypes.FIELD_STRING, "12345"); break; - case SET_DATE: obj.setDate(AllJavaTypes.FIELD_DATE, new Date(1L)); break; - case SET_OBJECT: obj.setObject(AllJavaTypes.FIELD_OBJECT, obj); break; - case SET_LIST: obj.setList(AllJavaTypes.FIELD_LIST, new RealmList<>(obj)); break; - case SET: obj.set(AllJavaTypes.FIELD_LONG, 1L); break; + case GET_BOOLEAN: obj.getBoolean(AllJavaTypes.FIELD_BOOLEAN); break; + case GET_BYTE: obj.getByte(AllJavaTypes.FIELD_BYTE); break; + case GET_SHORT: obj.getShort(AllJavaTypes.FIELD_SHORT); break; + case GET_INT: obj.getInt(AllJavaTypes.FIELD_INT); break; + case GET_LONG: obj.getLong(AllJavaTypes.FIELD_LONG); break; + case GET_FLOAT: obj.getFloat(AllJavaTypes.FIELD_FLOAT); break; + case GET_DOUBLE: obj.getDouble(AllJavaTypes.FIELD_DOUBLE); break; + case GET_BLOB: obj.getBlob(AllJavaTypes.FIELD_BINARY); break; + case GET_STRING: obj.getString(AllJavaTypes.FIELD_STRING); break; + case GET_DATE: obj.getDate(AllJavaTypes.FIELD_DATE); break; + case GET_OBJECT: obj.getObject(AllJavaTypes.FIELD_OBJECT); break; + case GET_LIST: obj.getList(AllJavaTypes.FIELD_LIST); break; + case GET_PRIMITIVE_LIST: obj.getList(AllJavaTypes.FIELD_STRING_LIST, String.class); break; + case GET: obj.get(AllJavaTypes.FIELD_LONG); break; + + case SET_BOOLEAN: obj.setBoolean(AllJavaTypes.FIELD_BOOLEAN, true); break; + case SET_BYTE: obj.setByte(AllJavaTypes.FIELD_BYTE, (byte) 1); break; + case SET_SHORT: obj.setShort(AllJavaTypes.FIELD_SHORT, (short) 1); break; + case SET_INT: obj.setInt(AllJavaTypes.FIELD_INT, 1); break; + case SET_LONG: obj.setLong(AllJavaTypes.FIELD_LONG, 1L); break; + case SET_FLOAT: obj.setFloat(AllJavaTypes.FIELD_FLOAT, 1F); break; + case SET_DOUBLE: obj.setDouble(AllJavaTypes.FIELD_DOUBLE, 1D); break; + case SET_BLOB: obj.setBlob(AllJavaTypes.FIELD_BINARY, new byte[] {1, 2, 3}); break; + case SET_STRING: obj.setString(AllJavaTypes.FIELD_STRING, "12345"); break; + case SET_DATE: obj.setDate(AllJavaTypes.FIELD_DATE, new Date(1L)); break; + case SET_OBJECT: obj.setObject(AllJavaTypes.FIELD_OBJECT, obj); break; + case SET_LIST: obj.setList(AllJavaTypes.FIELD_LIST, new RealmList<>(obj)); break; + case SET_PRIMITIVE_LIST: obj.setList(AllJavaTypes.FIELD_STRING_LIST,new RealmList("foo")); break; + case SET: obj.set(AllJavaTypes.FIELD_LONG, 1L); break; case IS_NULL: obj.isNull(AllJavaTypes.FIELD_OBJECT); break; case SET_NULL: obj.setNull(AllJavaTypes.FIELD_OBJECT); break; @@ -326,7 +336,16 @@ private static void callGetter(DynamicRealmObject target, SupportedType type, Li case BINARY: target.getBlob(fieldName); break; case DATE: target.getDate(fieldName); break; case OBJECT: target.getObject(fieldName); break; - case LIST: target.getList(fieldName); break; + case LIST: + case LIST_INTEGER: + case LIST_STRING: + case LIST_BOOLEAN: + case LIST_FLOAT: + case LIST_DOUBLE: + case LIST_BINARY: + case LIST_DATE: + target.getList(fieldName); + break; default: fail(); } @@ -450,6 +469,13 @@ private static void callSetter(DynamicRealmObject target, SupportedType type, Li case DATE: target.getDate(fieldName); break; case OBJECT: target.setObject(fieldName, null); target.setObject(fieldName, target); break; case LIST: target.setList(fieldName, new RealmList()); break; + case LIST_INTEGER: target.setList(fieldName, new RealmList(1)); break; + case LIST_STRING: target.setList(fieldName, new RealmList("foo")); break; + case LIST_BOOLEAN: target.setList(fieldName, new RealmList(true)); break; + case LIST_FLOAT: target.setList(fieldName, new RealmList(1.23F)); break; + case LIST_DOUBLE: target.setList(fieldName, new RealmList(1.234D)); break; + case LIST_BINARY: target.setList(fieldName, new RealmList(new byte[]{})); break; + case LIST_DATE: target.setList(fieldName, new RealmList(new Date())); break; default: fail(); } @@ -509,6 +535,27 @@ public void typedGettersAndSetters() { dObj.setObject(AllJavaTypes.FIELD_OBJECT, dObj); assertEquals(dObj, dObj.getObject(AllJavaTypes.FIELD_OBJECT)); break; + case LIST_INTEGER: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_INTEGER_LIST, Integer.class, new RealmList<>(null, 1)); + break; + case LIST_STRING: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_STRING_LIST, String.class, new RealmList<>(null, "foo")); + break; + case LIST_BOOLEAN: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_BOOLEAN_LIST, Boolean.class, new RealmList<>(null, true)); + break; + case LIST_FLOAT: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_FLOAT_LIST, Float.class, new RealmList<>(null, 1.23F)); + break; + case LIST_DOUBLE: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_DOUBLE_LIST, Double.class, new RealmList<>(null, 1.234D)); + break; + case LIST_BINARY: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_BINARY_LIST, byte[].class, new RealmList<>(null, new byte[] {1, 2, 3})); + break; + case LIST_DATE: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_DATE_LIST, Date.class, new RealmList<>(null, new Date(1000))); + break; case LIST: // Ignores. See testGetList/testSetList. break; @@ -521,6 +568,11 @@ public void typedGettersAndSetters() { } } + private void checkSetGetValueList(DynamicRealmObject obj, String fieldName, Class primitiveType, RealmList list) { + obj.set(fieldName, list); + assertArrayEquals(list.toArray(), obj.getList(fieldName, primitiveType).toArray()); + } + @Test public void setter_null() { realm.beginTransaction(); @@ -545,6 +597,55 @@ public void setter_null() { } catch (IllegalArgumentException ignored) { } break; + case LIST_INTEGER: + try { + dObj.setNull(NullTypes.FIELD_INTEGER_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_STRING: + try { + dObj.setNull(NullTypes.FIELD_STRING_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_BOOLEAN: + try { + dObj.setNull(NullTypes.FIELD_BOOLEAN_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_FLOAT: + try { + dObj.setNull(NullTypes.FIELD_FLOAT_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_DOUBLE: + try { + dObj.setNull(NullTypes.FIELD_DOUBLE_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_BINARY: + try { + dObj.setNull(NullTypes.FIELD_BINARY_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_DATE: + try { + dObj.setNull(NullTypes.FIELD_DATE_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; case BOOLEAN: dObj.setNull(NullTypes.FIELD_BOOLEAN_NULL); assertTrue(dObj.isNull(NullTypes.FIELD_BOOLEAN_NULL)); @@ -606,6 +707,13 @@ public void setter_nullOnRequiredFieldsThrows() { switch (type) { case OBJECT: continue; // Ignore case LIST: fieldName = NullTypes.FIELD_LIST_NULL; break; + case LIST_INTEGER: fieldName = NullTypes.FIELD_INTEGER_LIST_NULL; break; + case LIST_STRING: fieldName = NullTypes.FIELD_STRING_LIST_NULL; break; + case LIST_BOOLEAN: fieldName = NullTypes.FIELD_BOOLEAN_LIST_NULL; break; + case LIST_FLOAT: fieldName = NullTypes.FIELD_FLOAT_LIST_NULL; break; + case LIST_DOUBLE: fieldName = NullTypes.FIELD_DATE_LIST_NULL; break; + case LIST_BINARY: fieldName = NullTypes.FIELD_BINARY_LIST_NULL; break; + case LIST_DATE: fieldName = NullTypes.FIELD_DATE_LIST_NULL; break; case BOOLEAN: fieldName = NullTypes.FIELD_BOOLEAN_NOT_NULL; break; case BYTE: fieldName = NullTypes.FIELD_BYTE_NOT_NULL; break; case SHORT: fieldName = NullTypes.FIELD_SHORT_NOT_NULL; break; @@ -651,6 +759,55 @@ public void typedSetter_null() { } catch (IllegalArgumentException ignored) { } break; + case LIST_INTEGER: + try { + dObj.setList(NullTypes.FIELD_INTEGER_LIST_NULL, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_STRING: + try { + dObj.setList(NullTypes.FIELD_STRING_LIST_NULL, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_BOOLEAN: + try { + dObj.setList(NullTypes.FIELD_BOOLEAN_LIST_NULL, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_FLOAT: + try { + dObj.setList(NullTypes.FIELD_FLOAT_LIST_NULL, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_DOUBLE: + try { + dObj.setList(NullTypes.FIELD_DOUBLE_LIST_NULL, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_BINARY: + try { + dObj.setList(NullTypes.FIELD_BINARY_LIST_NULL, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_DATE: + try { + dObj.setList(NullTypes.FIELD_DATE_LIST_NULL, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; case DATE: dObj.setDate(NullTypes.FIELD_DATE_NULL, null); assertNull(dObj.getDate(NullTypes.FIELD_DATE_NULL)); @@ -885,6 +1042,32 @@ public void setList_wrongTypeThrows() { dObjTyped.setList(AllJavaTypes.FIELD_LIST, wrongDynamicList); } + @Test + public void setList_javaModelClassesThrowProperErrorMessage() { + dynamicRealm.beginTransaction(); + try { + dObjDynamic.setList(AllJavaTypes.FIELD_LIST, new RealmList<>(typedObj)); + fail(); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("RealmList must contain `DynamicRealmObject's, not Java model classes.")); + } + } + + @Test + public void setList_objectsOwnList() { + dynamicRealm.beginTransaction(); + + // Test model classes + int originalSize = dObjDynamic.getList(AllJavaTypes.FIELD_LIST).size(); + dObjDynamic.setList(AllJavaTypes.FIELD_LIST, dObjDynamic.getList(AllJavaTypes.FIELD_LIST)); + assertEquals(originalSize, dObjDynamic.getList(AllJavaTypes.FIELD_LIST).size()); + + // Smoke test value lists + originalSize = dObjDynamic.getList(AllJavaTypes.FIELD_STRING_LIST, String.class).size(); + dObjDynamic.setList(AllJavaTypes.FIELD_STRING_LIST, dObjDynamic.getList(AllJavaTypes.FIELD_STRING_LIST, String.class)); + assertEquals(originalSize, dObjDynamic.getList(AllJavaTypes.FIELD_STRING_LIST, String.class).size()); + } + @Test public void untypedSetter_listWrongTypeThrows() { realm.beginTransaction(); @@ -925,6 +1108,7 @@ public void getList() { assertEquals("fido", listObject.getString(Dog.FIELD_NAME)); } + @Test public void untypedGetterSetter() { realm.beginTransaction(); @@ -977,7 +1161,7 @@ public void untypedGetterSetter() { dObj.set(AllJavaTypes.FIELD_OBJECT, dObj); assertEquals(dObj, dObj.get(AllJavaTypes.FIELD_OBJECT)); break; - case LIST: + case LIST: { RealmList newList = new RealmList(); newList.add(dObj); dObj.set(AllJavaTypes.FIELD_LIST, newList); @@ -985,6 +1169,63 @@ public void untypedGetterSetter() { assertEquals(1, list.size()); assertEquals(dObj, list.get(0)); break; + } + case LIST_INTEGER: { + RealmList newList = new RealmList<>(null, 1); + dObj.set(AllJavaTypes.FIELD_INTEGER_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_INTEGER_LIST, Integer.class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } + case LIST_STRING: { + RealmList newList = new RealmList<>(null, "Foo"); + dObj.set(AllJavaTypes.FIELD_STRING_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_STRING_LIST, String.class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } + case LIST_BOOLEAN: { + RealmList newList = new RealmList<>(null, true); + dObj.set(AllJavaTypes.FIELD_BOOLEAN_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_BOOLEAN_LIST, Boolean.class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } + case LIST_FLOAT: { + RealmList newList = new RealmList<>(null, 1.23F); + dObj.set(AllJavaTypes.FIELD_FLOAT_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_FLOAT_LIST, Float.class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } + case LIST_DOUBLE: { + RealmList newList = new RealmList<>(null, 1.24D); + dObj.set(AllJavaTypes.FIELD_DOUBLE_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_DOUBLE_LIST, Double.class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } + case LIST_BINARY: { + RealmList newList = new RealmList<>(null, new byte[] {1, 2, 3}); + dObj.set(AllJavaTypes.FIELD_BINARY_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_BINARY_LIST, byte[].class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } + case LIST_DATE: { + RealmList newList = new RealmList<>(null, new Date(1000)); + dObj.set(AllJavaTypes.FIELD_DATE_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_DATE_LIST, Date.class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } default: fail(); } @@ -1033,11 +1274,17 @@ public void untypedSetter_usingStringConversion() { // These types don't have a string representation that can be parsed. case OBJECT: case LIST: + case LIST_INTEGER: + case LIST_STRING: + case LIST_BOOLEAN: + case LIST_FLOAT: + case LIST_DOUBLE: + case LIST_BINARY: + case LIST_DATE: case STRING: case BINARY: case BYTE: break; - default: fail("Unknown type: " + type); break; @@ -1081,6 +1328,13 @@ public void untypedSetter_illegalImplicitConversionThrows() { case BYTE: case OBJECT: case LIST: + case LIST_INTEGER: + case LIST_STRING: + case LIST_BOOLEAN: + case LIST_FLOAT: + case LIST_DOUBLE: + case LIST_BINARY: + case LIST_DATE: case STRING: case BINARY: continue; @@ -1200,6 +1454,13 @@ public void getFieldType() { assertEquals(RealmFieldType.INTEGER, dObjTyped.getFieldType(AllJavaTypes.FIELD_SHORT)); assertEquals(RealmFieldType.INTEGER, dObjTyped.getFieldType(AllJavaTypes.FIELD_INT)); assertEquals(RealmFieldType.INTEGER, dObjTyped.getFieldType(AllJavaTypes.FIELD_LONG)); + assertEquals(RealmFieldType.INTEGER_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_INTEGER_LIST)); + assertEquals(RealmFieldType.STRING_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_STRING_LIST)); + assertEquals(RealmFieldType.BOOLEAN_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_BOOLEAN_LIST)); + assertEquals(RealmFieldType.FLOAT_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_FLOAT_LIST)); + assertEquals(RealmFieldType.DOUBLE_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_DOUBLE_LIST)); + assertEquals(RealmFieldType.BINARY_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_BINARY_LIST)); + assertEquals(RealmFieldType.DATE_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_DATE_LIST)); } @Test @@ -1253,6 +1514,13 @@ public void toString_nullValues() { assertTrue(str.contains(NullTypes.FIELD_DATE_NULL + ":null")); assertTrue(str.contains(NullTypes.FIELD_OBJECT_NULL + ":null")); assertTrue(str.contains(NullTypes.FIELD_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_INTEGER_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_STRING_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_BOOLEAN_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_FLOAT_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_DOUBLE_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_BINARY_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_DATE_LIST_NULL + ":RealmList[0]")); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index e77c0543db..6615bffa9b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -19,7 +19,6 @@ import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -37,8 +36,10 @@ import io.realm.internal.Table; import io.realm.rule.TestRealmConfigurationFactory; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -107,6 +108,7 @@ public enum SchemaFieldType { SIMPLE, OBJECT, LIST } + // Enumerate all standard field types public enum FieldType { STRING(String.class, true), SHORT(Short.class, true), PRIMITIVE_SHORT(short.class, false), @@ -118,8 +120,7 @@ public enum FieldType { DOUBLE(Double.class, true), PRIMITIVE_DOUBLE(double.class, false), BLOB(byte[].class, true), DATE(Date.class, true), - OBJECT(RealmObject.class, false), - LIST(RealmList.class, false); + OBJECT(RealmObject.class, false); final Class clazz; final boolean defaultNullable; @@ -138,6 +139,37 @@ public boolean isNullable() { } } + // Enumerate all list types + public enum FieldListType { + STRING_LIST(String.class, true), + SHORT_LIST(Short.class, true), PRIMITIVE_SHORT_LIST(short.class, false), + INT_LIST(Integer.class, true), PRIMITIVE_INT_LIST(int.class, false), + LONG_LIST(Long.class, true), PRIMITIVE_LONG_LIST(long.class, false), + BYTE_LIST(Byte.class, true), PRIMITIVE_BYTE_LIST(byte.class, false), + BOOLEAN_LIST(Boolean.class, true), PRIMITIVE_BOOLEAN_LIST(boolean.class, false), + FLOAT_LIST(Float.class, true), PRIMITIVE_FLOAT_LIST(float.class, false), + DOUBLE_LIST(Double.class, true), PRIMITIVE_DOUBLE_LIST(double.class, false), + BLOB_LIST(byte[].class, true), + DATE_LIST(Date.class, true), + LIST(RealmList.class, false); // List of Realm Objects + + final Class clazz; + final boolean defaultNullable; + + FieldListType(Class clazz, boolean defaultNullable) { + this.clazz = clazz; + this.defaultNullable = defaultNullable; + } + + public Class getType() { + return clazz; + } + + public boolean isNullable() { + return defaultNullable; + } + } + public enum IndexFieldType { STRING(String.class, true), SHORT(Short.class, true), PRIMITIVE_SHORT(short.class, false), @@ -253,20 +285,28 @@ public void addRemoveField() { } return; } + String fieldName = "foo"; for (FieldType fieldType : FieldType.values()) { - String fieldName = "foo"; switch (fieldType) { case OBJECT: schema.addRealmObjectField(fieldName, DOG_SCHEMA); checkAddedAndRemovable(fieldName); break; + default: + // All simple fields + schema.addField(fieldName, fieldType.getType()); + checkAddedAndRemovable(fieldName); + } + } + for (FieldListType fieldType : FieldListType.values()) { + switch (fieldType) { case LIST: schema.addRealmListField(fieldName, DOG_SCHEMA); checkAddedAndRemovable(fieldName); break; default: - // All simple fields - schema.addField(fieldName, fieldType.getType()); + // All primitive lists + schema.addRealmListField(fieldName, fieldType.getType()); checkAddedAndRemovable(fieldName); } } @@ -354,11 +394,10 @@ public void requiredFieldAttribute() { if (type == ObjectSchemaType.IMMUTABLE) { return; } + String fieldName = "foo"; for (FieldType fieldType : FieldType.values()) { - String fieldName = "foo"; switch (fieldType) { case OBJECT: continue; // Not possible. - case LIST: continue; // Not possible. default: // All simple types schema.addField(fieldName, fieldType.getType(), FieldAttribute.REQUIRED); @@ -366,6 +405,20 @@ public void requiredFieldAttribute() { schema.removeField(fieldName); } } + for (FieldListType fieldType : FieldListType.values()) { + switch(fieldType) { + case LIST: + continue; // Not possible. + default: + // All simple list types + schema.addRealmListField(fieldName, fieldType.getType()); + if (fieldType.isNullable()) { + schema.setRequired(fieldName, true); + } + assertTrue(fieldName + " should be required", schema.isRequired(fieldName)); + schema.removeField(fieldName); + } + } } @Test @@ -397,6 +450,12 @@ public void invalidIndexedFieldAttributeThrows() { } catch (IllegalArgumentException ignored) { } } + + // Probe for all variants of primitive lists + try { + schema.addRealmListField("foo", String.class); + } catch (IllegalArgumentException ignored) { + } } @Test @@ -438,6 +497,17 @@ public void invalidPrimaryKeyFieldAttributeThrows() { } catch (IllegalArgumentException ignored) { } } + + try { + schema.addRealmListField("foo", schema); + } catch (IllegalArgumentException ignored) { + } + + // Probe for all variants of primitive lists + try { + schema.addRealmListField("foo", String.class); + } catch (IllegalArgumentException ignored) { + } } @Test @@ -544,14 +614,14 @@ public void addIndexFieldModifier_alreadyIndexedThrows() { } @Test - public void setRemoveNullable() { + public void setNullable_trueAndFalse() { if (type == ObjectSchemaType.IMMUTABLE) { thrown.expect(UnsupportedOperationException.class); schema.setNullable("test", true); return; } + String fieldName = "foo"; for (FieldType fieldType : FieldType.values()) { - String fieldName = "foo"; switch (fieldType) { case OBJECT: // Objects are always nullable and cannot be changed. @@ -563,6 +633,17 @@ public void setRemoveNullable() { } catch (IllegalArgumentException ignored) { } break; + default: + // All simple types. + schema.addField(fieldName, fieldType.getType()); + assertEquals(fieldType.isNullable(), schema.isNullable(fieldName)); + schema.setNullable(fieldName, !fieldType.isNullable()); + assertEquals(!fieldType.isNullable(), schema.isNullable(fieldName)); + } + schema.removeField(fieldName); + } + for (FieldListType fieldType : FieldListType.values()) { + switch (fieldType) { case LIST: // Lists are not nullable and cannot be configured to be so. schema.addRealmListField(fieldName, schema); @@ -574,25 +655,25 @@ public void setRemoveNullable() { } break; default: - // All simple types. - schema.addField(fieldName, fieldType.getType()); - assertEquals(fieldType.isNullable(), schema.isNullable(fieldName)); + // All simple list types. + schema.addRealmListField(fieldName, fieldType.getType()); + assertEquals("Type: " + fieldType, fieldType.isNullable(), schema.isNullable(fieldName)); schema.setNullable(fieldName, !fieldType.isNullable()); - assertEquals(!fieldType.isNullable(), schema.isNullable(fieldName)); + assertEquals("Type: " + fieldType, !fieldType.isNullable(), schema.isNullable(fieldName)); } schema.removeField(fieldName); } } @Test - public void setRemoveRequired() { + public void setRequired_trueAndFalse() { if (type == ObjectSchemaType.IMMUTABLE) { thrown.expect(UnsupportedOperationException.class); schema.setRequired("test", true); return; } + String fieldName = "foo"; for (FieldType fieldType : FieldType.values()) { - String fieldName = "foo"; switch (fieldType) { case OBJECT: // Objects are always nullable and cannot be configured otherwise. @@ -604,6 +685,17 @@ public void setRemoveRequired() { } catch (IllegalArgumentException ignored) { } break; + default: + // All simple types. + schema.addField(fieldName, fieldType.getType()); + assertEquals(!fieldType.isNullable(), schema.isRequired(fieldName)); + schema.setRequired(fieldName, fieldType.isNullable()); + assertEquals(fieldType.isNullable(), schema.isRequired(fieldName)); + } + schema.removeField(fieldName); + } + for (FieldListType fieldType : FieldListType.values()) { + switch (fieldType) { case LIST: // Lists are always non-nullable and cannot be configured otherwise. schema.addRealmListField(fieldName, schema); @@ -615,8 +707,8 @@ public void setRemoveRequired() { } break; default: - // All simple types. - schema.addField(fieldName, fieldType.getType()); + // All simple list types. + schema.addRealmListField(fieldName, fieldType.getType()); assertEquals(!fieldType.isNullable(), schema.isRequired(fieldName)); schema.setRequired(fieldName, fieldType.isNullable()); assertEquals(fieldType.isNullable(), schema.isRequired(fieldName)); @@ -636,8 +728,7 @@ public void setRequired_nullValueBecomesDefaultValue() { String fieldName = fieldType.name(); switch (fieldType) { case OBJECT: - case LIST: - // Skip always nullable fields. + // Skip always nullable fields break; default: // Skip not-nullable fields . @@ -667,8 +758,119 @@ public void setRequired_nullValueBecomesDefaultValue() { break; } } + for (FieldListType fieldType : FieldListType.values()) { + switch(fieldType) { + case LIST: + // Skip always non-nullable fields. + break; + case STRING_LIST: + checkListValueConversionToDefaultValue(String.class, ""); + break; + case SHORT_LIST: + checkListValueConversionToDefaultValue(Short.class, (short) 0); + break; + case INT_LIST: + checkListValueConversionToDefaultValue(Integer.class, 0); + break; + case LONG_LIST: + checkListValueConversionToDefaultValue(Long.class, 0L); + break; + case BYTE_LIST: + checkListValueConversionToDefaultValue(Byte.class, (byte) 0); + break; + case BOOLEAN_LIST: + checkListValueConversionToDefaultValue(Boolean.class, false); + break; + case FLOAT_LIST: + checkListValueConversionToDefaultValue(Float.class, 0.0F); + break; + case DOUBLE_LIST: + checkListValueConversionToDefaultValue(Double.class, 0.0D); + break; + case BLOB_LIST: + checkListValueConversionToDefaultValue(byte[].class, new byte[0]); + break; + case DATE_LIST: + checkListValueConversionToDefaultValue(Date.class, new Date(0)); + break; + case PRIMITIVE_INT_LIST: + case PRIMITIVE_LONG_LIST: + case PRIMITIVE_BYTE_LIST: + case PRIMITIVE_BOOLEAN_LIST: + case PRIMITIVE_FLOAT_LIST: + case PRIMITIVE_DOUBLE_LIST: + case PRIMITIVE_SHORT_LIST: + // Skip not-nullable fields + break; + default: + throw new IllegalArgumentException("Unknown type: " + fieldType); + } + } } + // Checks that null values in a value list are correctly converted to default values + // when field is set to required. + private void checkListValueConversionToDefaultValue(Class type, Object defaultValue) { + schema.addRealmListField("foo", type); + DynamicRealmObject obj = ((DynamicRealm) realm).createObject(schema.getClassName()); + RealmList list = new RealmList<>(); + list.add(null); + obj.setList("foo", list); + assertNull(obj.getList("foo", type).first()); + + // Convert from nullable to required + schema.setRequired("foo", true); + if (defaultValue instanceof byte[]) { + assertArrayEquals((byte[]) defaultValue, (byte[]) obj.getList("foo", type).first()); + } else { + assertEquals(defaultValue, obj.getList("foo", type).first()); + } + + // Convert back again + schema.setRequired("foo", false); + if (defaultValue instanceof byte[]) { + //noinspection ConstantConditions + assertArrayEquals((byte[]) defaultValue, (byte[]) obj.getList("foo", type).first()); + } else { + assertEquals(defaultValue, obj.getList("foo", type).first()); + } + + // Cleanup + schema.removeField("foo"); + } + + // Special test for making sure that binary data in all forms are transformed correctly + // when moving between nullable and required states. + @Test + public void binaryData_nullabilityConversions() { + if (type == ObjectSchemaType.IMMUTABLE) { + return; + } + schema.addRealmListField("foo", byte[].class); + + DynamicRealmObject obj = ((DynamicRealm) realm).createObject(schema.getClassName()); + RealmList list = obj.getList("foo", byte[].class); + assertTrue(list.size() == 0); + + // Initial content (nullable) + list.add(null); + list.add(new byte[] {1, 2, 3}); + assertNull(list.get(0)); + assertArrayEquals(new byte[] {1, 2, 3}, list.get(1)); + + // Transform to required + schema.setRequired("foo", true); + list = obj.getList("foo", byte[].class); + assertEquals(0, list.get(0).length); + assertArrayEquals(new byte[] {1, 2, 3}, list.get(1)); + + // Transform back to nullable + schema.setRequired("foo", false); + list = obj.getList("foo", byte[].class); + assertEquals(0, list.get(0).length); + assertArrayEquals(new byte[] {1, 2, 3}, list.get(1)); + } + @Test public void setRequired_true_onPrimaryKeyField_containsNullValues_shouldThrow() { if (type == ObjectSchemaType.IMMUTABLE) { @@ -781,7 +983,7 @@ public void setRequired_false_onIndexedField() { } @Test - public void setRemovePrimaryKey() { + public void setPrimaryKey_trueAndFalse() { if (type == ObjectSchemaType.IMMUTABLE) { try { schema.addPrimaryKey("test"); @@ -824,7 +1026,7 @@ public void removeNonExistingPrimaryKeyThrows() { } @Test - public void setRemoveIndex() { + public void setIndex_trueAndFalse() { if (type == ObjectSchemaType.IMMUTABLE) { try { schema.addIndex("test"); @@ -1115,6 +1317,21 @@ public void getFieldType_nonLatinName() { assertEquals(RealmFieldType.INTEGER, objSchema.getFieldType(NonLatinFieldNames.FIELD_LONG_GREEK_CHAR)); } + @Test + public void addList_modelClassThrowsWithProperError() { + if (type == ObjectSchemaType.IMMUTABLE) { + return; + } + + try { + schema.addRealmListField("field", AllJavaTypes.class); + fail(); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("Use 'addRealmListField(String name, RealmObjectSchema schema)' instead")); + } + } + + private interface FieldRunnable { void run(String fieldName); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index ced1e5d440..1618c48ad9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -74,7 +74,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumn(JNIEnv* env try { JStringAccessor name2(env, name); // throws bool is_column_nullable = to_bool(isNullable); - DataType dataType = DataType(colType); if (is_column_nullable && dataType == type_LinkList) { ThrowException(env, IllegalArgument, "List fields cannot be nullable."); @@ -85,6 +84,25 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumn(JNIEnv* env return 0; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddPrimitiveListColumn(JNIEnv* env, jobject, + jlong native_table_ptr, jint j_col_type, + jstring j_name, jboolean j_is_nullable) +{ + if (!TABLE_VALID(env, TBL(native_table_ptr))) { + return 0; + } + try { + JStringAccessor name(env, j_name); // throws + bool is_column_nullable = to_bool(j_is_nullable); + DataType data_type = DataType(j_col_type); + Table* table = TBL(native_table_ptr); + size_t col = table->add_column(type_Table, name); + return table->get_subdescriptor(col)->add_column(data_type, ObjectStore::ArrayColumnName, nullptr, is_column_nullable); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumnLink(JNIEnv* env, jobject, jlong nativeTablePtr, jint colType, jstring name, jlong targetTablePtr) @@ -179,7 +197,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable(J return to_jbool(table->is_nullable(S(columnIndex))); // noexcept } // For primitive list - // FIXME: Add test in https://github.com/realm/realm-java/pull/5221 before merging to master return to_jbool(table->get_descriptor()->get_subdescriptor(S(columnIndex))->is_nullable(S(0))); // noexcept } @@ -199,7 +216,101 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable(J // 5. search indexing must be preserved // 6. removing the original column and renaming the temporary column will make it look like original is being modified -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullable(JNIEnv* env, jobject, +// Converts a table to allow for nullable values +// Works on both normal table columns and sub tables +static void convert_column_to_nullable(JNIEnv* env, Table* old_table, size_t old_col_ndx, Table* new_table, size_t new_col_ndx, bool is_primary_key) +{ + DataType column_type = old_table->get_column_type(old_col_ndx); + if (old_table != new_table) { + new_table->add_empty_row(old_table->size()); + } + for (size_t i = 0; i < old_table->size(); ++i) { + switch (column_type) { + case type_String: { + // Payload copy is needed + StringData sd(old_table->get_string(old_col_ndx, i)); + if (is_primary_key) { + new_table->set_string_unique(new_col_ndx, i, sd); + } + else { + new_table->set_string(new_col_ndx, i, sd); + } + break; + } + case type_Binary: { + BinaryData bd = old_table->get_binary(old_col_ndx, i); + new_table->set_binary(new_col_ndx, i, BinaryData(bd.data(), bd.size())); + break; + } + case type_Int: + if (is_primary_key) { + new_table->set_int_unique(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); + } + else { + new_table->set_int(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); + } + break; + case type_Bool: + new_table->set_bool(new_col_ndx, i, old_table->get_bool(old_col_ndx, i)); + break; + case type_Timestamp: + new_table->set_timestamp(new_col_ndx, i, old_table->get_timestamp(old_col_ndx, i)); + break; + case type_Float: + new_table->set_float(new_col_ndx, i, old_table->get_float(old_col_ndx, i)); + break; + case type_Double: + new_table->set_double(new_col_ndx, i, old_table->get_double(old_col_ndx, i)); + break; + case type_Link: + case type_LinkList: + case type_Mixed: + case type_Table: + // checked previously + break; + case type_OldDateTime: + ThrowException(env, UnsupportedOperation, "The old DateTime type is not supported."); + return; + } + } +} + +// Creates the new column into which all old data is copied when switching between nullable and non-nullable. +static void create_new_column(Table* table, size_t column_index, bool nullable) +{ + std::string column_name = table->get_column_name(column_index); + DataType column_type = table->get_column_type(column_index); + bool is_subtable = table->get_column_type(column_index) == DataType::type_Table; + size_t j = 0; + while (true) { + std::ostringstream ss; + ss << std::string("__TMP__") << j; + std::string str = ss.str(); + StringData tmp_column_name(str); + if (table->get_column_index(tmp_column_name) == realm::not_found) { + if (is_subtable) { + DataType original_type = table->get_subdescriptor(column_index)->get_column_type(0); + table->insert_column(column_index, type_Table, tmp_column_name, true); + table->get_subdescriptor(column_index)->add_column(original_type, ObjectStore::ArrayColumnName, nullptr, nullable); + } + else { + table->insert_column(column_index, column_type, tmp_column_name, nullable); + } + break; + } + j++; + } + + // Search index has too be added first since if it is a PK field, add_xxx_unique will check it. + if (!is_subtable) { + // TODO indexes on sub tables not supported yet? + if (table->has_search_index(column_index + 1)) { + table->add_search_index(column_index); + } + } +} + +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullable(JNIEnv* env, jobject obj, jlong native_table_ptr, jlong j_column_index, jboolean is_primary_key) @@ -208,248 +319,212 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { return; } - if (table->has_shared_type()) { - ThrowException(env, UnsupportedOperation, "Not allowed to convert field in subtable."); - return; - } try { - size_t column_index = S(j_column_index); - if (table->is_nullable(column_index)) { - return; // column is already nullable + Table* table = TBL(native_table_ptr); + if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { + return; + } + if (table->has_shared_type()) { + ThrowException(env, UnsupportedOperation, "Not allowed to convert field in subtable."); + return; } - std::string column_name = table->get_column_name(column_index); + size_t column_index = S(j_column_index); DataType column_type = table->get_column_type(column_index); - if (column_type == type_Link || column_type == type_LinkList || column_type == type_Mixed || - column_type == type_Table) { + std::string column_name = table->get_column_name(column_index); + bool is_subtable = (column_type == DataType::type_Table); + + // Cannot convert Object links or lists of objects + if (column_type == type_Link || column_type == type_LinkList || column_type == type_Mixed) { ThrowException(env, IllegalArgument, "Wrong type - cannot be converted to nullable."); } - std::string tmp_column_name; + // Exit quickly if column is already nullable + if (Java_io_realm_internal_Table_nativeIsColumnNullable(env, obj, native_table_ptr, j_column_index)) { + return; + } - size_t j = 0; - while (true) { - std::ostringstream ss; - ss << std::string("__TMP__") << j; - std::string str = ss.str(); - StringData sd(str); - if (table->get_column_index(sd) == realm::not_found) { - table->insert_column(column_index, column_type, sd, true); - tmp_column_name = ss.str(); - break; + // 1. Create temporary table + create_new_column(table, column_index, true); + + // Move all values + if (is_subtable) { + for (size_t i = 0; i < table->size(); ++i) { + TableRef new_subtable = table->get_subtable(column_index, i); + TableRef old_subtable = table->get_subtable(column_index + 1, i); + convert_column_to_nullable(env, old_subtable.get(), 0, new_subtable.get(), 0, is_primary_key); } - j++; } - - // Search index has too be added first since if it is a PK field, add_xxx_unique will check it. - if (table->has_search_index(column_index + 1)) { - table->add_search_index(column_index); + else { + convert_column_to_nullable(env, table, column_index + 1, table, column_index, is_primary_key); } - for (size_t i = 0; i < table->size(); ++i) { - switch (column_type) { - case type_String: { + // Cleanup + table->remove_column(column_index + 1); + table->rename_column(column_index, column_name); + + } + CATCH_STD() +} + +// Convert a tables values to not nullable, but converting all null values to the defaul value for the type +// Works on both normal table columns and sub tables +static void convert_column_to_not_nullable(JNIEnv* env, Table* old_table, size_t old_col_ndx, Table* new_table, size_t new_col_ndx, bool is_primary_key) +{ + DataType column_type = old_table->get_column_type(old_col_ndx); + std::string column_name = old_table->get_column_name(old_col_ndx); + if (old_table != new_table) { + new_table->add_empty_row(old_table->size()); + } + for (size_t i = 0; i < old_table->size(); ++i) { + switch (column_type) { // FIXME: respect user-specified default values + case type_String: { + StringData sd = old_table->get_string(old_col_ndx, i); + if (sd == realm::null()) { + if (is_primary_key) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, + format(c_null_values_cannot_set_required_msg, column_name)); + } + else { + new_table->set_string(new_col_ndx, i, ""); + } + } + else { // Payload copy is needed - StringData sd(table->get_string(column_index + 1, i)); if (is_primary_key) { - table->set_string_unique(column_index, i, sd); + new_table->set_string_unique(new_col_ndx, i, sd); } else { - table->set_string(column_index, i, sd); + new_table->set_string(new_col_ndx, i, sd); } - break; } - case type_Binary: { + break; + } + case type_Binary: { + BinaryData bd = old_table->get_binary(old_col_ndx, i); + if (bd.is_null()) { + new_table->set_binary(new_col_ndx, i, BinaryData("", 0)); + } + else { // Payload copy is needed - BinaryData bd = table->get_binary(column_index + 1, i); - std::vector binary_copy(bd.data(), bd.data() + bd.size()); - table->set_binary(column_index, i, BinaryData(binary_copy.data(), binary_copy.size())); - break; + std::vector bd_copy(bd.data(), bd.data() + bd.size()); + new_table->set_binary(new_col_ndx, i, BinaryData(bd_copy.data(), bd_copy.size())); } - case type_Int: + break; + } + case type_Int: + if (old_table->is_null(old_col_ndx, i)) { if (is_primary_key) { - table->set_int_unique(column_index, i, table->get_int(column_index + 1, i)); + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, + format(c_null_values_cannot_set_required_msg, column_name)); } else { - table->set_int(column_index, i, table->get_int(column_index + 1, i)); + new_table->set_int(new_col_ndx, i, 0); } - break; - case type_Bool: - table->set_bool(column_index, i, table->get_bool(column_index + 1, i)); - break; - case type_Timestamp: - table->set_timestamp(column_index, i, table->get_timestamp(column_index + 1, i)); - break; - case type_Float: - table->set_float(column_index, i, table->get_float(column_index + 1, i)); - break; - case type_Double: - table->set_double(column_index, i, table->get_double(column_index + 1, i)); - break; - case type_Link: - case type_LinkList: - case type_Mixed: - case type_Table: - // checked previously - break; - case type_OldDateTime: - ThrowException(env, UnsupportedOperation, "The old DateTime type is not supported."); - return; - } + } + else { + if (is_primary_key) { + new_table->set_int_unique(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); + } + else { + new_table->set_int(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); + } + } + break; + case type_Bool: + if (old_table->is_null(old_col_ndx, i)) { + new_table->set_bool(new_col_ndx, i, false); + } + else { + new_table->set_bool(new_col_ndx, i, old_table->get_bool(old_col_ndx, i)); + } + break; + case type_Timestamp: + if (old_table->is_null(old_col_ndx, i)) { + new_table->set_timestamp(new_col_ndx, i, Timestamp(0, 0)); + } + else { + new_table->set_timestamp(new_col_ndx, i, old_table->get_timestamp(old_col_ndx, i)); + } + break; + case type_Float: + if (old_table->is_null(old_col_ndx, i)) { + new_table->set_float(new_col_ndx, i, 0.0); + } + else { + new_table->set_float(new_col_ndx, i, old_table->get_float(old_col_ndx, i)); + } + break; + case type_Double: + if (old_table->is_null(old_col_ndx, i)) { + new_table->set_double(new_col_ndx, i, 0.0); + } + else { + new_table->set_double(new_col_ndx, i, old_table->get_double(old_col_ndx, i)); + } + break; + case type_Link: + case type_LinkList: + case type_Mixed: + case type_Table: + // checked previously + break; + case type_OldDateTime: + // not used + ThrowException(env, UnsupportedOperation, "The old DateTime type is not supported."); + return; } - table->remove_column(column_index + 1); - table->rename_column(table->get_column_index(tmp_column_name), column_name); } - CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNullable(JNIEnv* env, jobject, + +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNullable(JNIEnv* env, jobject obj, jlong native_table_ptr, jlong j_column_index, jboolean is_primary_key) { - Table* table = TBL(native_table_ptr); - if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { - return; - } - if (table->has_shared_type()) { - ThrowException(env, UnsupportedOperation, "Not allowed to convert field in subtable."); - return; - } try { - size_t column_index = S(j_column_index); - if (!table->is_nullable(column_index)) { - return; // column is already not nullable + Table* table = TBL(native_table_ptr); + if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { + return; + } + if (table->has_shared_type()) { + ThrowException(env, UnsupportedOperation, "Not allowed to convert field in subtable."); + return; } + // Exit quickly if column is already non-nullable + if (!Java_io_realm_internal_Table_nativeIsColumnNullable(env, obj, native_table_ptr, j_column_index)) { + return; + } + + size_t column_index = S(j_column_index); std::string column_name = table->get_column_name(column_index); DataType column_type = table->get_column_type(column_index); - if (column_type == type_Link || column_type == type_LinkList || column_type == type_Mixed || - column_type == type_Table) { + bool is_subtable = (column_type == DataType::type_Table); + + if (column_type == type_Link || column_type == type_LinkList || column_type == type_Mixed) { ThrowException(env, IllegalArgument, "Wrong type - cannot be converted to nullable."); } - std::string tmp_column_name; - size_t j = 0; - while (true) { - std::ostringstream ss; - ss << std::string("__TMP__") << j; - std::string str = ss.str(); - StringData sd(str); - if (table->get_column_index(sd) == realm::not_found) { - table->insert_column(column_index, column_type, sd, false); - tmp_column_name = ss.str(); - break; + // 1. Create temporary table + create_new_column(table, column_index, false); + + // 2. Move all values + if (is_subtable) { + for (size_t i = 0; i < table->size(); ++i) { + TableRef new_subtable = table->get_subtable(column_index, i); + TableRef old_subtable = table->get_subtable(column_index + 1, i); + convert_column_to_not_nullable(env, old_subtable.get(), 0, new_subtable.get(), 0, is_primary_key); } - j++; } - - // Search index has too be added first since if it is a PK field, add_xxx_unique will check it. - if (table->has_search_index(column_index + 1)) { - table->add_search_index(column_index); + else { + convert_column_to_not_nullable(env, table, column_index + 1, table, column_index, is_primary_key); } - for (size_t i = 0; i < table->size(); ++i) { - switch (column_type) { // FIXME: respect user-specified default values - case type_String: { - StringData sd = table->get_string(column_index + 1, i); - if (sd == realm::null()) { - if (is_primary_key) { - THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, - format(c_null_values_cannot_set_required_msg, column_name)); - } - else { - table->set_string(column_index, i, ""); - } - } - else { - // Payload copy is needed - if (is_primary_key) { - table->set_string_unique(column_index, i, sd); - } - else { - table->set_string(column_index, i, sd); - } - } - break; - } - case type_Binary: { - BinaryData bd = table->get_binary(column_index + 1, i); - if (bd.is_null()) { - table->set_binary(column_index, i, BinaryData("", 0)); - } - else { - // Payload copy is needed - std::vector bd_copy(bd.data(), bd.data() + bd.size()); - table->set_binary(column_index, i, BinaryData(bd_copy.data(), bd_copy.size())); - } - break; - } - case type_Int: - if (table->is_null(column_index + 1, i)) { - if (is_primary_key) { - THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, - format(c_null_values_cannot_set_required_msg, column_name)); - } - else { - table->set_int(column_index, i, 0); - } - } - else { - if (is_primary_key) { - table->set_int_unique(column_index, i, table->get_int(column_index + 1, i)); - } - else { - table->set_int(column_index, i, table->get_int(column_index + 1, i)); - } - } - break; - case type_Bool: - if (table->is_null(column_index + 1, i)) { - table->set_bool(column_index, i, false); - } - else { - table->set_bool(column_index, i, table->get_bool(column_index + 1, i)); - } - break; - case type_Timestamp: - if (table->is_null(column_index + 1, i)) { - table->set_timestamp(column_index, i, Timestamp(0, 0)); - } - else { - table->set_timestamp(column_index, i, table->get_timestamp(column_index + 1, i)); - } - break; - case type_Float: - if (table->is_null(column_index + 1, i)) { - table->set_float(column_index, i, 0.0); - } - else { - table->set_float(column_index, i, table->get_float(column_index + 1, i)); - } - break; - case type_Double: - if (table->is_null(column_index + 1, i)) { - table->set_double(column_index, i, 0.0); - } - else { - table->set_double(column_index, i, table->get_double(column_index + 1, i)); - } - break; - case type_Link: - case type_LinkList: - case type_Mixed: - case type_Table: - // checked previously - break; - case type_OldDateTime: - // not used - ThrowException(env, UnsupportedOperation, "The old DateTime type is not supported."); - return; - } - } + // 3. Delete old values table->remove_column(column_index + 1); - table->rename_column(table->get_column_index(tmp_column_name), column_name); + table->rename_column(column_index, column_name); } CATCH_STD() } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 012085f1f8..85fbbaecdf 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.Date; +import java.util.Iterator; import java.util.Locale; import javax.annotation.Nonnull; @@ -336,6 +337,8 @@ public DynamicRealmObject getObject(String fieldName) { /** * Returns the {@link RealmList} of {@link DynamicRealmObject}s being linked from the given field. + *

            + * If the list contains primitive types, use {@link #getList(String, Class)} instead. * * @param fieldName the name of the field. * @return the {@link RealmList} data for this field. @@ -346,7 +349,7 @@ public RealmList getList(String fieldName) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); try { - OsList osList = proxyState.getRow$realm().getList(columnIndex); + OsList osList = proxyState.getRow$realm().getModelList(columnIndex); //noinspection ConstantConditions @Nonnull String className = osList.getTargetTable().getClassName(); @@ -358,15 +361,54 @@ public RealmList getList(String fieldName) { } /** - * Returns the {@link RealmList} of values being linked from the given field. + * Returns the {@link RealmList} containing only primitive values. + * + *

            + * If the list contains references to other Realm objects, use {@link #getList(String)} instead. * * @param fieldName the name of the field. + * @param primitiveType the type of elements in the list. Only primitive types are supported. * @return the {@link RealmList} data for this field. - * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain a list of values. + * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain a list of primitive objects. */ - public RealmList getValueList(String fieldName, Class valueClass) { - // TODO implement this - return null; + public RealmList getList(String fieldName, Class primitiveType) { + proxyState.getRealm$realm().checkIfValid(); + + if (primitiveType == null) { + throw new IllegalArgumentException("Non-null 'primitiveType' required."); + } + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + RealmFieldType realmType = classToRealmType(primitiveType); + try { + OsList osList = proxyState.getRow$realm().getValueList(columnIndex, realmType); + return new RealmList<>(primitiveType, osList, proxyState.getRealm$realm()); + } catch (IllegalArgumentException e) { + checkFieldType(fieldName, columnIndex, realmType); + throw e; + } + } + + private RealmFieldType classToRealmType(Class primitiveType) { + if (primitiveType.equals(Integer.class) + || primitiveType.equals(Long.class) + || primitiveType.equals(Short.class) + || primitiveType.equals(Byte.class)) { + return RealmFieldType.INTEGER_LIST; + } else if (primitiveType.equals(Boolean.class)) { + return RealmFieldType.BOOLEAN_LIST; + } else if (primitiveType.equals(String.class)) { + return RealmFieldType.STRING_LIST; + } else if (primitiveType.equals(byte[].class)) { + return RealmFieldType.BINARY_LIST; + } else if (primitiveType.equals(Date.class)) { + return RealmFieldType.DATE_LIST; + } else if (primitiveType.equals(Float.class)) { + return RealmFieldType.FLOAT_LIST; + } else if (primitiveType.equals(Double.class)) { + return RealmFieldType.DOUBLE_LIST; + } else { + throw new IllegalArgumentException("Unsupported element type. Only primitive types supported. Yours was: " + primitiveType); + } } /** @@ -393,6 +435,15 @@ public boolean isNull(String fieldName) { case DATE: return proxyState.getRow$realm().isNull(columnIndex); case LIST: + case LINKING_OBJECTS: + case INTEGER_LIST: + case BOOLEAN_LIST: + case STRING_LIST: + case BINARY_LIST: + case DATE_LIST: + case FLOAT_LIST: + case DOUBLE_LIST: + // fall through default: return false; } @@ -510,28 +561,7 @@ private void setValue(String fieldName, Object value) { setObject(fieldName, (DynamicRealmObject) value); } else if (valueClass == RealmList.class) { RealmList list = (RealmList) value; - if (list.className == null && list.clazz == null) { - // unmanaged RealmList - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - final RealmFieldType columnType = proxyState.getRow$realm().getColumnType(columnIndex); - if (columnType == RealmFieldType.LIST) { - //noinspection unchecked - for (Object element : list) { - if (!(element instanceof RealmModel)) { - throw new IllegalArgumentException("All elements in the list must be an instance of RealmModel."); - } - } - //noinspection unchecked - setList(fieldName, (RealmList) list); - } else { - setValueList(fieldName, list); - } - } else if (list.className != null || RealmModel.class.isAssignableFrom(list.clazz)) { - //noinspection unchecked - setList(fieldName, (RealmList) list); - } else { - setValueList(fieldName, list); - } + setList(fieldName, list); } else { throw new IllegalArgumentException("Value is of an type not supported: " + value.getClass()); } @@ -727,21 +757,54 @@ public void setObject(String fieldName, @Nullable DynamicRealmObject value) { * Sets the reference to a {@link RealmList} on the given field. * * @param fieldName field name. - * @param list list of references. - * @throws IllegalArgumentException if field name doesn't exist, it is not a list field, the type - * of the object represented by the DynamicRealmObject doesn't match or any element in the list belongs to a - * different Realm. + * @param list list of objects. Must either be primitive types or {@link DynamicRealmObject}s. + * @throws IllegalArgumentException if field name doesn't exist, it is not a list field, the objects in the + * list doesn't match the expected type or any Realm object in the list belongs to a different Realm. */ - public void setList(String fieldName, RealmList list) { + public void setList(String fieldName, RealmList list) { proxyState.getRealm$realm().checkIfValid(); //noinspection ConstantConditions if (list == null) { - throw new IllegalArgumentException("Null values not allowed for lists"); + throw new IllegalArgumentException("Non-null 'list' required"); + } + + // Find type of list in Realm + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + final RealmFieldType columnType = proxyState.getRow$realm().getColumnType(columnIndex); + + switch (columnType) { + case LIST: + // Due to type erasure it is not possible to check the generic parameter, + // instead we try to see if the first element is of the wrong type in order + // to throw a better error message. + // Primitive types are checked inside `setModelList` + if (!list.isEmpty()) { + E element = list.first(); + if (!(element instanceof DynamicRealmObject) && RealmModel.class.isAssignableFrom(element.getClass())) { + throw new IllegalArgumentException("RealmList must contain `DynamicRealmObject's, not Java model classes."); + } + } + //noinspection unchecked + setModelList(fieldName, (RealmList) list); + break; + case INTEGER_LIST: + case BOOLEAN_LIST: + case STRING_LIST: + case BINARY_LIST: + case DATE_LIST: + case FLOAT_LIST: + case DOUBLE_LIST: + setValueList(fieldName, list, columnType); + break; + default: + throw new IllegalArgumentException(String.format("Field '%s' is not a list but a %s", fieldName, columnType)); } + } + private void setModelList(String fieldName, RealmList list) { long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - OsList osList = proxyState.getRow$realm().getList(columnIndex); + OsList osList = proxyState.getRow$realm().getModelList(columnIndex); Table linkTargetTable = osList.getTargetTable(); //noinspection ConstantConditions @Nonnull @@ -788,17 +851,72 @@ public void setList(String fieldName, RealmList list) { } } - /** - * Sets the reference to a {@link RealmList} on the given field. - * - * @param fieldName field name. - * @param list list of references. - * @throws IllegalArgumentException if field name doesn't exist, it is not a list field, the type - * of the object represented by the DynamicRealmObject doesn't match or any element in the list belongs to a - * different Realm. - */ - public void setValueList(String fieldName, RealmList list) { - // TODO implement this + @SuppressWarnings("unchecked") + private void setValueList(String fieldName, RealmList list, RealmFieldType primitiveType) { + long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + OsList osList = proxyState.getRow$realm().getValueList(columnIndex, primitiveType); + + Class elementClass; + switch(primitiveType) { + case INTEGER_LIST: elementClass = (Class) Long.class; break; + case BOOLEAN_LIST: elementClass = (Class) Boolean.class; break; + case STRING_LIST: elementClass = (Class) String.class; break; + case BINARY_LIST: elementClass = (Class) byte[].class; break; + case DATE_LIST: elementClass = (Class) Date.class; break; + case FLOAT_LIST: elementClass = (Class) Float.class; break; + case DOUBLE_LIST: elementClass = (Class) Double.class; break; + default: + throw new IllegalArgumentException("Unsupported type: " + primitiveType); + } + final ManagedListOperator operator = getOperator(proxyState.getRealm$realm(), osList, primitiveType, elementClass); + + if (list.isManaged() && osList.size() == list.size()) { + // There is a chance that the source list and the target list are the same list in the same object. + // In this case, we can't use removeAll(). + final int size = list.size(); + final Iterator iterator = list.iterator(); + for (int i = 0; i < size; i++) { + @Nullable + final Object value = iterator.next(); + operator.set(i, value); + } + } else { + osList.removeAll(); + for (Object value : list) { + operator.append(value); + } + } + } + + private ManagedListOperator getOperator(BaseRealm realm, OsList osList, RealmFieldType valueListType, Class valueClass) { + if (valueListType == RealmFieldType.STRING_LIST) { + //noinspection unchecked + return (ManagedListOperator) new StringListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.INTEGER_LIST) { + return new LongListOperator<>(realm, osList, valueClass); + } + if (valueListType == RealmFieldType.BOOLEAN_LIST) { + //noinspection unchecked + return (ManagedListOperator) new BooleanListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.BINARY_LIST) { + //noinspection unchecked + return (ManagedListOperator) new BinaryListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.DOUBLE_LIST) { + //noinspection unchecked + return (ManagedListOperator) new DoubleListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.FLOAT_LIST) { + //noinspection unchecked + return (ManagedListOperator) new FloatListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.DATE_LIST) { + //noinspection unchecked + return (ManagedListOperator) new DateListOperator(realm, osList, (Class) valueClass); + } + throw new IllegalArgumentException("Unexpected list type: " + valueListType.name()); } /** @@ -964,7 +1082,28 @@ public String toString() { break; case LIST: String targetClassName = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName(); - sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getList(columnIndex).size())); + sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getModelList(columnIndex).size())); + break; + case INTEGER_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + break; + case BOOLEAN_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + break; + case STRING_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + break; + case BINARY_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + break; + case DATE_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + break; + case FLOAT_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + break; + case DOUBLE_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); break; default: sb.append("?"); diff --git a/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java index 477f08758a..48c3e67d4e 100644 --- a/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java @@ -55,6 +55,11 @@ public RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema o throw new UnsupportedOperationException(SCHEMA_IMMUTABLE_EXCEPTION_MSG); } + @Override + public RealmObjectSchema addRealmListField(String fieldName, Class primitiveType) { + throw new UnsupportedOperationException(SCHEMA_IMMUTABLE_EXCEPTION_MSG); + } + @Override public RealmObjectSchema removeField(String fieldName) { throw new UnsupportedOperationException(SCHEMA_IMMUTABLE_EXCEPTION_MSG); diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index 2281c0ff8a..51e269bb93 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -19,7 +19,6 @@ import java.util.Locale; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import io.realm.internal.OsObjectStore; import io.realm.internal.Table; @@ -105,7 +104,7 @@ public RealmObjectSchema addField(String fieldName, Class fieldType, FieldAtt nullable = false; } - long columnIndex = table.addColumn(metadata.realmType, fieldName, nullable); + long columnIndex = table.addColumn(metadata.fieldType, fieldName, nullable); try { addModifiers(fieldName, attributes); } catch (Exception e) { @@ -132,6 +131,25 @@ public RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema o return this; } + @Override + public RealmObjectSchema addRealmListField(String fieldName, Class primitiveType) { + checkLegalName(fieldName); + checkFieldNameIsAvailable(fieldName); + + FieldMetaData metadata = SUPPORTED_SIMPLE_FIELDS.get(primitiveType); + if (metadata == null) { + if (primitiveType.equals(RealmObjectSchema.class) || RealmModel.class.isAssignableFrom(primitiveType)) { + throw new IllegalArgumentException("Use 'addRealmListField(String name, RealmObjectSchema schema)' instead to add lists that link to other RealmObjects: " + fieldName); + } else { + throw new IllegalArgumentException(String.format(Locale.US, + "RealmList does not support lists with this type: %s(%s)", + fieldName, primitiveType)); + } + } + table.addColumn(metadata.listType, fieldName, metadata.defaultNullable); + return this; + } + @Override public RealmObjectSchema removeField(String fieldName) { realm.checkNotInSync(); // destructive modification of a schema is not permitted diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java index 8d36769f95..10f060b24e 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java @@ -64,12 +64,12 @@ public RealmObjectSchema createWithPrimaryKeyField(String className, String prim String internalTableName = checkAndGetTableNameFromClassName(className); RealmObjectSchema.FieldMetaData metadata = RealmObjectSchema.getSupportedSimpleFields().get(fieldType); - if (metadata == null || (metadata.realmType != RealmFieldType.STRING && - metadata.realmType != RealmFieldType.INTEGER)) { + if (metadata == null || (metadata.fieldType != RealmFieldType.STRING && + metadata.fieldType != RealmFieldType.INTEGER)) { throw new IllegalArgumentException(String.format("Realm doesn't support primary key field type '%s'.", fieldType)); } - boolean isStringField = (metadata.realmType == RealmFieldType.STRING); + boolean isStringField = (metadata.fieldType == RealmFieldType.STRING); boolean nullable = metadata.defaultNullable; if (MutableRealmObjectSchema.containsAttribute(attributes, FieldAttribute.REQUIRED)) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 0b0ef1e75e..45c5521fe2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -70,7 +70,7 @@ public class RealmList extends AbstractList implements OrderedRealmCollect protected String className; // Always null if RealmList is unmanaged, always non-null if managed. - private final ManagedListOperator osListOperator; + final ManagedListOperator osListOperator; final protected BaseRealm realm; private List unmanagedList; // Used for listeners on RealmList diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 27f0129173..3442f1074e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; + import io.realm.annotations.Required; import io.realm.internal.ColumnInfo; import io.realm.internal.OsObject; @@ -47,23 +49,23 @@ public abstract class RealmObjectSchema { static { Map, FieldMetaData> m = new HashMap<>(); - m.put(String.class, new FieldMetaData(RealmFieldType.STRING, true)); - m.put(short.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Short.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(int.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Integer.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(long.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Long.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(float.class, new FieldMetaData(RealmFieldType.FLOAT, false)); - m.put(Float.class, new FieldMetaData(RealmFieldType.FLOAT, true)); - m.put(double.class, new FieldMetaData(RealmFieldType.DOUBLE, false)); - m.put(Double.class, new FieldMetaData(RealmFieldType.DOUBLE, true)); - m.put(boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, false)); - m.put(Boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, true)); - m.put(byte.class, new FieldMetaData(RealmFieldType.INTEGER, false)); - m.put(Byte.class, new FieldMetaData(RealmFieldType.INTEGER, true)); - m.put(byte[].class, new FieldMetaData(RealmFieldType.BINARY, true)); - m.put(Date.class, new FieldMetaData(RealmFieldType.DATE, true)); + m.put(String.class, new FieldMetaData(RealmFieldType.STRING, RealmFieldType.STRING_LIST, true)); + m.put(short.class, new FieldMetaData(RealmFieldType.INTEGER, RealmFieldType.INTEGER_LIST, false)); + m.put(Short.class, new FieldMetaData(RealmFieldType.INTEGER, RealmFieldType.INTEGER_LIST, true)); + m.put(int.class, new FieldMetaData(RealmFieldType.INTEGER, RealmFieldType.INTEGER_LIST, false)); + m.put(Integer.class, new FieldMetaData(RealmFieldType.INTEGER, RealmFieldType.INTEGER_LIST, true)); + m.put(long.class, new FieldMetaData(RealmFieldType.INTEGER, RealmFieldType.INTEGER_LIST, false)); + m.put(Long.class, new FieldMetaData(RealmFieldType.INTEGER, RealmFieldType.INTEGER_LIST, true)); + m.put(float.class, new FieldMetaData(RealmFieldType.FLOAT, RealmFieldType.FLOAT_LIST, false)); + m.put(Float.class, new FieldMetaData(RealmFieldType.FLOAT, RealmFieldType.FLOAT_LIST, true)); + m.put(double.class, new FieldMetaData(RealmFieldType.DOUBLE, RealmFieldType.DOUBLE_LIST, false)); + m.put(Double.class, new FieldMetaData(RealmFieldType.DOUBLE, RealmFieldType.DOUBLE_LIST, true)); + m.put(boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, RealmFieldType.BOOLEAN_LIST, false)); + m.put(Boolean.class, new FieldMetaData(RealmFieldType.BOOLEAN, RealmFieldType.BOOLEAN_LIST, true)); + m.put(byte.class, new FieldMetaData(RealmFieldType.INTEGER, RealmFieldType.INTEGER_LIST, false)); + m.put(Byte.class, new FieldMetaData(RealmFieldType.INTEGER, RealmFieldType.INTEGER_LIST, true)); + m.put(byte[].class, new FieldMetaData(RealmFieldType.BINARY, RealmFieldType.BINARY_LIST, true)); + m.put(Date.class, new FieldMetaData(RealmFieldType.DATE, RealmFieldType.DATE_LIST, true)); SUPPORTED_SIMPLE_FIELDS = Collections.unmodifiableMap(m); } @@ -71,8 +73,8 @@ public abstract class RealmObjectSchema { static { Map, FieldMetaData> m = new HashMap<>(); - m.put(RealmObject.class, new FieldMetaData(RealmFieldType.OBJECT, false)); - m.put(RealmList.class, new FieldMetaData(RealmFieldType.LIST, false)); + m.put(RealmObject.class, new FieldMetaData(RealmFieldType.OBJECT, null, false)); + m.put(RealmList.class, new FieldMetaData(RealmFieldType.LIST, null, false)); SUPPORTED_LINKED_FIELDS = Collections.unmodifiableMap(m); } @@ -151,7 +153,9 @@ public String getClassName() { public abstract RealmObjectSchema addRealmObjectField(String fieldName, RealmObjectSchema objectSchema); /** - * Adds a new field that references a {@link RealmList}. + * Adds a new field that contains a {@link RealmList} with references to other Realm model classes. + *

            + * If the list contains primitive types, use {@link #addRealmListField(String, Class)} instead. * * @param fieldName name of the field to add. * @param objectSchema schema for the Realm type being referenced. @@ -161,6 +165,34 @@ public String getClassName() { */ public abstract RealmObjectSchema addRealmListField(String fieldName, RealmObjectSchema objectSchema); + /** + * Adds a new field that references a {@link RealmList} with primitive values. See {@link RealmObject} for the + * list of supported types. + *

            + * Nullability of elements are defined by using the correct class e.g., {@code Integer.class} instead of + * {@code int.class}. Alternatively {@link #setRequired(String, boolean)} can be used. + *

            + * Example: + *

            +     * {@code
            +     * // Defines the list of Strings as being non null.
            +     * RealmObjectSchema schema = schema.create("Person")
            +     *     .addRealmListField("children", String.class)
            +     *     .setRequired("children", true)
            +     * }
            +     * 
            + * If the list contains references to other Realm classes, use + * {@link #addRealmListField(String, RealmObjectSchema)} instead. + * + * @param fieldName name of the field to add. + * @param primitiveType simple type of elements in the array. + * @return the updated schema. + * @throws IllegalArgumentException if the field name is illegal, a field with that name already exists or + * the element type isn't supported. + * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable. + */ + public abstract RealmObjectSchema addRealmListField(String fieldName, Class primitiveType); + /** * Removes a field from the class. * @@ -525,11 +557,13 @@ protected void copy(ColumnInfo src, ColumnInfo dst) { // Tuple containing data about each supported Java type. static final class FieldMetaData { - final RealmFieldType realmType; + final RealmFieldType fieldType; // Underlying Realm type for fields with this type + final RealmFieldType listType; // Underlying Realm type for RealmLists containing this type final boolean defaultNullable; - FieldMetaData(RealmFieldType realmType, boolean defaultNullable) { - this.realmType = realmType; + FieldMetaData(RealmFieldType fieldType, @Nullable RealmFieldType listType, boolean defaultNullable) { + this.fieldType = fieldType; + this.listType = listType; this.defaultNullable = defaultNullable; } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index f3a3e1f97b..115130670b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -97,17 +97,6 @@ public void setNull(long columnIndex) { } } - @Override - public OsList getList(long columnIndex) { - RealmFieldType fieldType = getTable().getColumnType(columnIndex); - if (fieldType != RealmFieldType.LIST) { - throw new IllegalArgumentException( - String.format(Locale.US, "Field '%s' is not a 'RealmList'.", - getTable().getColumnName(columnIndex))); - } - return super.getList(columnIndex); - } - @Override public OsList getModelList(long columnIndex) { RealmFieldType fieldType = getTable().getColumnType(columnIndex); diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index 79510c7e08..42f160d1d9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -104,11 +104,6 @@ public boolean isNullLink(long columnIndex) { throw getStubException(); } - @Override - public OsList getList(long columnIndex) { - throw getStubException(); - } - @Override public OsList getModelList(long columnIndex) { throw getStubException(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 88ace3a27b..638fc3d5f8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -133,11 +133,6 @@ public boolean isNullLink(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } - @Override - public OsList getList(long columnIndex) { - throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); - } - @Override public OsList getModelList(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index f1a556f057..681d818999 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -81,9 +81,6 @@ public interface Row { boolean isNullLink(long columnIndex); - // FIXME remove this in DynamicRealm PR - OsList getList(long columnIndex); - OsList getModelList(long columnIndex); OsList getValueList(long columnIndex, RealmFieldType fieldType); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 31c004a77d..b68bab8ab0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -100,7 +100,28 @@ private void verifyColumnName(String name) { */ public long addColumn(RealmFieldType type, String name, boolean isNullable) { verifyColumnName(name); - return nativeAddColumn(nativePtr, type.getNativeValue(), name, isNullable); + switch (type) { + case INTEGER: + case BOOLEAN: + case STRING: + case BINARY: + case DATE: + case FLOAT: + case DOUBLE: + return nativeAddColumn(nativePtr, type.getNativeValue(), name, isNullable); + + case INTEGER_LIST: + case BOOLEAN_LIST: + case STRING_LIST: + case BINARY_LIST: + case DATE_LIST: + case FLOAT_LIST: + case DOUBLE_LIST: + return nativeAddPrimitiveListColumn(nativePtr, type.getNativeValue() - 128, name, isNullable); + + default: + throw new IllegalArgumentException("Unsupported type: " + type); + } } /** @@ -688,6 +709,8 @@ public static String getTableNameForClass(String name) { private native long nativeAddColumn(long nativeTablePtr, int type, String name, boolean isNullable); + private native long nativeAddPrimitiveListColumn(long nativeTablePtr, int type, String name, boolean isNullable); + private native long nativeAddColumnLink(long nativeTablePtr, int type, String name, long targetTablePtr); private native void nativeRenameColumn(long nativeTablePtr, long columnIndex, String name); diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 5c98952c7d..c7e035b89c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -172,11 +172,6 @@ public boolean isNullLink(long columnIndex) { return nativeIsNullLink(nativePtr, columnIndex); } - @Override - public OsList getList(long columnIndex) { - return new OsList(this, columnIndex); - } - @Override public OsList getModelList(long columnIndex) { return new OsList(this, columnIndex); From 88b45bd59236d77e0c9fc23c19df6708c361e4e7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 3 Oct 2017 13:48:31 +0200 Subject: [PATCH 1002/2110] Upgrade to Sync-RC27 and Remove deprecated Management Realm (#5357) * Upgrade to latest Sync-RC27 * Remove deprecated Management Realm from public API * Enable as many PermissionManager tests as possible. --- CHANGELOG.md | 5 +- dependencies.list | 7 +- .../java/io/realm/SyncUserTests.java | 27 -- .../java/io/realm/PermissionManager.java | 255 ++++++++++-------- .../java/io/realm/SyncSession.java | 8 +- .../objectServer/java/io/realm/SyncUser.java | 46 ---- .../permissions/ManagementModule.java | 4 - .../java/io/realm/PermissionManagerTests.java | 48 ++-- .../objectserver/ManagementRealmTests.java | 190 ------------- .../suite/IntegrationTestSuite.java | 2 - 10 files changed, 176 insertions(+), 416 deletions(-) delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ae0b36785a..d4e390b70b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Calling `distinct()` on a sorted `RealmResults` no longer clears the sorting (#3503). * [ObjectServer] Removed deprecated APIs `SyncUser.retrieveUser()` and `SyncUser.retrieveUserAsync()`. Use `SyncUser.retrieveInfoForUser()` and `retrieveInfoForUserAsync()` instead. +* [ObjectServer] Removed deprecated API `SyncUser.getManagementRealm()`. * Removed deprecated APIs `RealmSchema.close()` and `RealmObjectSchema.close()`. Those don't have to be called anymore. * Removed deprecated API `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. * Removed deprecated API `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. @@ -23,8 +24,8 @@ ## Internal -* Upgraded to Realm Sync 2.0.0-rc25. -* Upgraded to Realm Core 4.0.0. +* Upgraded to Realm Sync 2.0.0-rc27. +* Upgraded to Realm Core 4.0.1. ## Credits diff --git a/dependencies.list b/dependencies.list index 50a75546e2..c7acfe203e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,9 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.0.0-rc26 -REALM_SYNC_SHA256=98f44f67051df80bae5d51d8b17912e1fcd076f390fa4c50dfc7a6eddb2d2205 +REALM_SYNC_VERSION=2.0.0-rc27 +REALM_SYNC_SHA256=3a558b10ecab3e8dbf6cbceae7fe40af38ad8d8467ddd1fe036ce92c1e7810f4 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.42 - +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.44 diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 5d67f68af5..6fb7dee8b5 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -36,8 +36,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.util.Calendar; import java.util.Iterator; @@ -280,31 +278,6 @@ public void currentUser_returnsUserAfterLogin() { assertEquals(user, SyncUser.currentUser()); } - @Test - public void getManagementRealm() { - SyncUser user = SyncTestUtils.createTestUser(); - Realm managementRealm = user.getManagementRealm(); - assertNotNull(managementRealm); - managementRealm.close(); - } - - @Test - public void getManagementRealm_enforceTLS() throws URISyntaxException { - // Non TLS - SyncUser user = SyncTestUtils.createTestUser("http://objectserver.realm.io/auth"); - Realm managementRealm = user.getManagementRealm(); - SyncConfiguration config = (SyncConfiguration) managementRealm.getConfiguration(); - assertEquals(new URI("realm://objectserver.realm.io/" + user.getIdentity() + "/__management"), config.getServerUrl()); - managementRealm.close(); - - // TLS - user = SyncTestUtils.createTestUser("https://objectserver.realm.io/auth"); - managementRealm = user.getManagementRealm(); - config = (SyncConfiguration) managementRealm.getConfiguration(); - assertEquals(new URI("realms://objectserver.realm.io/" + user.getIdentity() + "/__management"), config.getServerUrl()); - managementRealm.close(); - } - @Test public void toString_returnDescription() { SyncUser user = SyncTestUtils.createTestUser("http://objectserver.realm.io/auth"); diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java index 170b11db05..e05ff917a8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; +import io.realm.internal.OsRealmConfig; import io.realm.internal.Util; import io.realm.internal.permissions.BasePermissionApi; import io.realm.internal.permissions.ManagementModule; @@ -100,7 +101,7 @@ protected Cache initialValue() { } private enum RealmType { - DEFAULT_PERMISSION_REALM("__permission", true), +// DEFAULT_PERMISSION_REALM("__starpermissions", true), PERMISSION_REALM("__permission", false), MANAGEMENT_REALM("__management", false); @@ -126,7 +127,7 @@ public boolean isGlobalRealm() { // Used to track the lifecycle of the PermissionManager private RealmAsyncTask managementRealmOpenTask; private RealmAsyncTask permissionRealmOpenTask; - private RealmAsyncTask defaultPermissionRealmOpenTask; +// private RealmAsyncTask defaultPermissionRealmOpenTask; private boolean openInProgress = false; private boolean closed; @@ -134,10 +135,10 @@ public boolean isGlobalRealm() { private Handler handler = new Handler(); final SyncConfiguration managementRealmConfig; final SyncConfiguration permissionRealmConfig; - final SyncConfiguration defaultPermissionRealmConfig; +// final SyncConfiguration defaultPermissionRealmConfig; private Realm permissionRealm; private Realm managementRealm; - private Realm defaultPermissionRealm; +// private Realm defaultPermissionRealm; // Task list used to queue tasks until the underlying Realms are done opening (or failed doing so). private Deque delayedTasks = new LinkedList<>(); @@ -153,7 +154,7 @@ public boolean isGlobalRealm() { private final Object errorLock = new Object(); private volatile ObjectServerError permissionRealmError = null; private volatile ObjectServerError managementRealmError = null; - private volatile ObjectServerError defaultPermissionRealmError = null; +// private volatile ObjectServerError defaultPermissionRealmError = null; // A client reset was encountered in one of the Realms. // This has invalidated the PermissionManager and it must be closed as soon as possible. @@ -164,7 +165,7 @@ public boolean isGlobalRealm() { // Cached result of the permission query. This will be filled, once the first PermissionAsyncTask has loaded // the result. private RealmResults userPermissions; - private RealmResults defaultPermissions; +// private RealmResults defaultPermissions; private RealmResults offers; /** @@ -189,6 +190,7 @@ public void onError(SyncSession session, ObjectServerError error) { } }) .modules(new ManagementModule()) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); permissionRealmConfig = new SyncConfiguration.Builder( @@ -203,25 +205,25 @@ public void onError(SyncSession session, ObjectServerError error) { }) .modules(new PermissionModule()) .waitForInitialRemoteData() - // FIXME: Something is seriously wrong with the Permission Realm. It doesn't seem to - // exist on the server. Making it impossible to mark it read only - // .readOnly() - .build(); - - defaultPermissionRealmConfig = new SyncConfiguration.Builder( - user, getRealmUrl(RealmType.DEFAULT_PERMISSION_REALM, user.getAuthenticationUrl())) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - synchronized (errorLock) { - defaultPermissionRealmError = error; - } - } - }) - .modules(new PermissionModule()) - .waitForInitialRemoteData() .readOnly() + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); + +// defaultPermissionRealmConfig = new SyncConfiguration.Builder( +// user, getRealmUrl(RealmType.DEFAULT_PERMISSION_REALM, user.getAuthenticationUrl())) +// .errorHandler(new SyncSession.ErrorHandler() { +// @Override +// public void onError(SyncSession session, ObjectServerError error) { +// synchronized (errorLock) { +// defaultPermissionRealmError = error; +// } +// } +// }) +// .modules(new PermissionModule()) +// .waitForInitialRemoteData() +// .readOnly() +// .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) +// .build(); } /** @@ -232,13 +234,13 @@ public void onError(SyncSession session, ObjectServerError error) { * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. */ public RealmAsyncTask getPermissions(PermissionsCallback callback) { - checkIfValidThread(); + checkIfValid(); checkCallbackNotNull(callback); return addTask(new GetPermissionsAsyncTask(this, callback)); } /** - * TODO: Removed from the public API until we know for 100% that we are going to use this going forward. + * NOTE: Moved out of the public API until we know for sure how this is going to work. * * Returns default permissions for all Realms. The default permissions are the ones that will be used if no * user specific permissions is in effect. @@ -248,9 +250,10 @@ public RealmAsyncTask getPermissions(PermissionsCallback callback) { * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. */ RealmAsyncTask getDefaultPermissions(PermissionsCallback callback) { - checkIfValidThread(); + checkIfValid(); checkCallbackNotNull(callback); - return addTask(new GetDefaultPermissionsAsyncTask(this, callback)); + return null; + // return addTask(new GetDefaultPermissionsAsyncTask(this, callback)); } /** @@ -267,7 +270,7 @@ RealmAsyncTask getDefaultPermissions(PermissionsCallback callback) { * @return async task representing the request. This can be used to cancel it if needed. */ public RealmAsyncTask applyPermissions(PermissionRequest request, ApplyPermissionsCallback callback) { - checkIfValidThread(); + checkIfValid(); checkCallbackNotNull(callback); return addTask(new ApplyPermissionTask(this, request, callback)); } @@ -291,7 +294,7 @@ public RealmAsyncTask applyPermissions(PermissionRequest request, ApplyPermissio * high level description. */ public RealmAsyncTask makeOffer(PermissionOffer offer, MakeOfferCallback callback) { - checkIfValidThread(); + checkIfValid(); checkCallbackNotNull(callback); if (offer.isOfferCreated()) { throw new IllegalStateException("Offer has already been created: " + offer); @@ -308,7 +311,7 @@ public RealmAsyncTask makeOffer(PermissionOffer offer, MakeOfferCallback callbac * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. */ public RealmAsyncTask acceptOffer(String offerToken, AcceptOfferCallback callback) { - checkIfValidThread(); + checkIfValid(); checkCallbackNotNull(callback); if (Util.isEmptyString(offerToken)) { throw new IllegalArgumentException("Non-empty 'offerToken' required."); @@ -325,7 +328,7 @@ public RealmAsyncTask acceptOffer(String offerToken, AcceptOfferCallback callbac * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. */ public RealmAsyncTask revokeOffer(String offerToken, RevokeOfferCallback callback) { - checkIfValidThread(); + checkIfValid(); checkCallbackNotNull(callback); return addTask(new RevokeOfferAsyncTask(this, offerToken, callback)); } @@ -338,7 +341,7 @@ public RealmAsyncTask revokeOffer(String offerToken, RevokeOfferCallback callbac * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. */ public RealmAsyncTask getCreatedOffers(OffersCallback callback) { - checkIfValidThread(); + checkIfValid(); checkCallbackNotNull(callback); return addTask(new GetOffersAsyncTask(this, callback)); } @@ -389,6 +392,7 @@ private void openRealms() { @Override public void onSuccess(Realm realm) { managementRealm = realm; + managementRealmOpenTask = null; checkIfRealmsAreOpenedAndRunDelayedTasks(); } @@ -396,6 +400,7 @@ public void onSuccess(Realm realm) { public void onError(Throwable exception) { synchronized (errorLock) { managementRealmError = new ObjectServerError(ErrorCode.UNKNOWN, exception); + managementRealmOpenTask = null; checkIfRealmsAreOpenedAndRunDelayedTasks(); } } @@ -404,6 +409,7 @@ public void onError(Throwable exception) { @Override public void onSuccess(Realm realm) { permissionRealm = realm; + permissionRealmOpenTask = null; checkIfRealmsAreOpenedAndRunDelayedTasks(); } @@ -411,32 +417,35 @@ public void onSuccess(Realm realm) { public void onError(Throwable exception) { synchronized (errorLock) { permissionRealmError = new ObjectServerError(ErrorCode.UNKNOWN, exception); + permissionRealmOpenTask = null; checkIfRealmsAreOpenedAndRunDelayedTasks(); } } }); - defaultPermissionRealmOpenTask = Realm.getInstanceAsync(defaultPermissionRealmConfig, new Realm.Callback() { - @Override - public void onSuccess(Realm realm) { - defaultPermissionRealm = realm; - checkIfRealmsAreOpenedAndRunDelayedTasks(); - } - - @Override - public void onError(Throwable exception) { - synchronized (errorLock) { - defaultPermissionRealmError = new ObjectServerError(ErrorCode.UNKNOWN, exception); - checkIfRealmsAreOpenedAndRunDelayedTasks(); - } - } - }); +// defaultPermissionRealmOpenTask = Realm.getInstanceAsync(defaultPermissionRealmConfig, new Realm.Callback() { +// @Override +// public void onSuccess(Realm realm) { +// defaultPermissionRealm = realm; +// defaultPermissionRealmOpenTask = null; +// checkIfRealmsAreOpenedAndRunDelayedTasks(); +// } +// +// @Override +// public void onError(Throwable exception) { +// synchronized (errorLock) { +// defaultPermissionRealmError = new ObjectServerError(ErrorCode.UNKNOWN, exception); +// defaultPermissionRealmOpenTask = null; +// checkIfRealmsAreOpenedAndRunDelayedTasks(); +// } +// } +// }); } } private void checkIfRealmsAreOpenedAndRunDelayedTasks() { synchronized (errorLock) { if ((permissionRealm != null || permissionRealmError != null) - && (defaultPermissionRealm != null || defaultPermissionRealmError != null) +// && (defaultPermissionRealm != null || defaultPermissionRealmError != null) && (managementRealm != null || managementRealmError != null)) { openInProgress = false; runDelayedTasks(); @@ -451,15 +460,19 @@ private void checkCallbackNotNull(PermissionManagerBaseCallback callback) { } private boolean isReady() { - return managementRealm != null && permissionRealm != null && defaultPermissionRealm != null; + return managementRealm != null && permissionRealm != null; // && defaultPermissionRealm != null; } - private void checkIfValidThread() { + private void checkIfValid() { // Checks if we are in thread that created the PermissionManager. if (threadId != Thread.currentThread().getId()) { throw new IllegalStateException("PermissionManager was accessed from the wrong thread. It can only be " + "accessed on the thread it was created on."); } + + if (closed) { + throw new IllegalStateException("PermissionManager has been closed. No further actions are possible."); + } } /** @@ -468,7 +481,7 @@ private void checkIfValidThread() { */ @Override public void close() { - checkIfValidThread(); + checkIfValid(); // Multiple instances open, just decrement the reference count synchronized (cacheLock) { @@ -482,6 +495,7 @@ public void close() { cache.instanceCounter = 0; cache.pm = null; } + closed = true; delayedTasks.clear(); // If Realms are still being opened, abort that task @@ -493,22 +507,22 @@ public void close() { permissionRealmOpenTask.cancel(); permissionRealmOpenTask = null; } - if (defaultPermissionRealmOpenTask != null) { - defaultPermissionRealmOpenTask.cancel(); - defaultPermissionRealmOpenTask = null; - } +// if (defaultPermissionRealmOpenTask != null) { +// defaultPermissionRealmOpenTask.cancel(); +// defaultPermissionRealmOpenTask = null; +// } // If Realms are opened. Close them. if (managementRealm != null) { managementRealm.close(); } + if (permissionRealm != null) { permissionRealm.close(); } - if (defaultPermissionRealm != null) { - defaultPermissionRealm.close(); - } - closed = true; +// if (defaultPermissionRealm != null) { +// defaultPermissionRealm.close(); +// } } /** @@ -517,7 +531,11 @@ public void close() { * @return {@code true} if the PermissionManager is closed, {@code false} if it is still open. */ public boolean isClosed() { - checkIfValidThread(); + // Don't use `checkIfValid()` as it throws because closed might be false. + if (threadId != Thread.currentThread().getId()) { + throw new IllegalStateException("PermissionManager was accessed from the wrong thread. It can only be " + + "accessed on the thread it was created on."); + } return closed; } @@ -593,54 +611,54 @@ void notifyCallbackWithSuccess(RealmResults permissions) { } } - // Task responsible for loading the Default Permissions result and returning it to the user. - // The Permission result is not considered available until the query has completed. - private class GetDefaultPermissionsAsyncTask extends PermissionManagerTask> { - - private final PermissionsCallback callback; - // Prevent permissions from being GC'ed until fully loaded. - private RealmResults loadingPermissions; - - GetDefaultPermissionsAsyncTask(PermissionManager permissionManager, PermissionsCallback callback) { - super(permissionManager, callback); - this.callback = callback; - } - - @Override - public void run() { - if (checkAndReportInvalidState()) { return; } - if (defaultPermissions != null) { - notifyCallbackWithSuccess(defaultPermissions); - } else { - // Start loading permissions. - // TODO Right now multiple getPermission() calls will result in multiple - // queries being executed. The first one to return will be the one returned - // by all callbacks. - loadingPermissions = permissionRealm.where(Permission.class).findAllAsync(); - loadingPermissions.addChangeListener(new RealmChangeListener >() { - @Override - public void onChange(RealmResults loadedPermissions) { - if (loadedPermissions.size() > 0) { - loadingPermissions.removeChangeListener(this); - if (checkAndReportInvalidState()) { return; } - if (defaultPermissions == null) { - defaultPermissions = loadedPermissions; - } - notifyCallbackWithSuccess(defaultPermissions); - } - } - }); - } - } - - void notifyCallbackWithSuccess(RealmResults permissions) { - try { - callback.onSuccess(permissions); - } finally { - activeTasks.remove(this); - } - } - } +// // Task responsible for loading the Default Permissions result and returning it to the user. +// // The Permission result is not considered available until the query has completed. +// private class GetDefaultPermissionsAsyncTask extends PermissionManagerTask> { +// +// private final PermissionsCallback callback; +// // Prevent permissions from being GC'ed until fully loaded. +// private RealmResults loadingPermissions; +// +// GetDefaultPermissionsAsyncTask(PermissionManager permissionManager, PermissionsCallback callback) { +// super(permissionManager, callback); +// this.callback = callback; +// } +// +// @Override +// public void run() { +// if (checkAndReportInvalidState()) { return; } +// if (defaultPermissions != null) { +// notifyCallbackWithSuccess(defaultPermissions); +// } else { +// // Start loading permissions. +// // TODO Right now multiple getPermission() calls will result in multiple +// // queries being executed. The first one to return will be the one returned +// // by all callbacks. +// loadingPermissions = permissionRealm.where(Permission.class).findAllAsync(); +// loadingPermissions.addChangeListener(new RealmChangeListener >() { +// @Override +// public void onChange(RealmResults loadedPermissions) { +// if (loadedPermissions.size() > 0) { +// loadingPermissions.removeChangeListener(this); +// if (checkAndReportInvalidState()) { return; } +// if (defaultPermissions == null) { +// defaultPermissions = loadedPermissions; +// } +// notifyCallbackWithSuccess(defaultPermissions); +// } +// } +// }); +// } +// } +// +// void notifyCallbackWithSuccess(RealmResults permissions) { +// try { +// callback.onSuccess(permissions); +// } finally { +// activeTasks.remove(this); +// } +// } +// } // Class encapsulating setting a Permission by writing a PermissionChange and waiting for it to // be processed. @@ -887,6 +905,7 @@ public void run() { public void onChange(RealmResults permissions) { if (!permissions.isEmpty()) { grantedPermissionResults.removeChangeListener(this); + //noinspection ConstantConditions notifyCallbackWithSuccess(managedResponse.getRealmUrl(), permissions.first()); } } @@ -1003,14 +1022,14 @@ protected final boolean checkAndReportInvalidState() { // Only hold lock while making a safe copy of current error state managementErrorHappened = (permissionManager.managementRealmError != null); permissionErrorHappened = (permissionManager.permissionRealmError != null); - defaultPermissionErrorHappened = (permissionManager.defaultPermissionRealmError != null); +// defaultPermissionErrorHappened = (permissionManager.defaultPermissionRealmError != null); managementError = permissionManager.managementRealmError; permissionError = permissionManager.permissionRealmError; - defaultPermissionError = permissionManager.defaultPermissionRealmError; +// defaultPermissionError = permissionManager.defaultPermissionRealmError; } // Everything seems valid - if (!permissionErrorHappened && !managementErrorHappened && !defaultPermissionErrorHappened) { + if (!permissionErrorHappened && !managementErrorHappened) {// && !defaultPermissionErrorHappened) { return false; } @@ -1038,12 +1057,12 @@ protected final boolean checkAndReportInvalidState() { permissionManager.clientReset = true; } - if (defaultPermissionErrorHappened && defaultPermissionError instanceof ClientResetRequiredError) { - ClientResetRequiredError cr = (ClientResetRequiredError) defaultPermissionError; - permissionManager.defaultPermissionRealm.close(); - cr.executeClientReset(); - permissionManager.clientReset = true; - } +// if (defaultPermissionErrorHappened && defaultPermissionError instanceof ClientResetRequiredError) { +// ClientResetRequiredError cr = (ClientResetRequiredError) defaultPermissionError; +// permissionManager.defaultPermissionRealm.close(); +// cr.executeClientReset(); +// permissionManager.clientReset = true; +// } // Handle errors Map errors = new LinkedHashMap<>(); @@ -1052,7 +1071,7 @@ protected final boolean checkAndReportInvalidState() { } else { if (managementErrorHappened) { errors.put("Management Realm", managementError); } if (permissionErrorHappened) { errors.put("Permission Realm", permissionError); } - if (defaultPermissionErrorHappened) { errors.put("Default Permission Realm", defaultPermissionError); } +// if (defaultPermissionErrorHappened) { errors.put("Default Permission Realm", defaultPermissionError); } } notifyCallbackWithError(combineRealmErrors(errors)); // This will remove the task from the task list diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 3b31657ca3..5ed82d663f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -19,6 +19,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.io.InterruptedIOException; import java.net.URI; import java.util.HashMap; import java.util.IdentityHashMap; @@ -586,7 +587,12 @@ protected void onError(AuthenticateResponse response) { onGoingAccessTokenQuery.set(false); RealmLog.debug("Session[%s]: Failed to get access token (%s)", configuration.getPath(), response.getError().getErrorCode()); - if (!isClosed && !Thread.currentThread().isInterrupted()) { + if (!isClosed + && !Thread.currentThread().isInterrupted() + // We might be interrupted while negotiating an access token with the Realm Object Server + // This will result in a InterruptedIOException from OkHttp. We should ignore this as + // well. + && !(response.getError().getException() instanceof InterruptedIOException)) { errorHandler.onError(SyncSession.this, response.getError()); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 7d0a5a2c69..0d3e9b1c74 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -27,7 +27,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; @@ -46,7 +45,6 @@ import io.realm.internal.network.LogoutResponse; import io.realm.internal.network.LookupUserIdResponse; import io.realm.internal.objectserver.Token; -import io.realm.internal.permissions.ManagementModule; import io.realm.log.RealmLog; /** @@ -67,36 +65,6 @@ public class SyncUser { // maps all RealmConfiguration and accessToken, using this SyncUser. private final Map realms = new HashMap(); - private static class ManagementConfig { - private SyncConfiguration managementRealmConfig; - - synchronized SyncConfiguration initAndGetManagementRealmConfig(final SyncUser user) { - if (managementRealmConfig == null) { - managementRealmConfig = new SyncConfiguration.Builder( - user, getManagementRealmUrl(user.getAuthenticationUrl())) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { - RealmLog.error("Client Reset required for user's management Realm: " + user.toString()); - } else { - RealmLog.error(String.format(Locale.US, - "Unexpected error with %s's management Realm: %s", - user.getIdentity(), - error.toString())); - } - } - }) - .modules(new ManagementModule()) - .build(); - } - - return managementRealmConfig; - } - } - - private final ManagementConfig managementConfig = new ManagementConfig(); - SyncUser(Token refreshToken, URL authenticationUrl) { this.identity = refreshToken.identity(); this.authenticationUrl = authenticationUrl; @@ -562,20 +530,6 @@ void setRefreshToken(Token refreshToken) { this.refreshToken = refreshToken; } - /** - * Returns an instance of the Management Realm owned by the user. - *

            - * This Realm can be used to control access and permissions for Realms owned by the user. This includes - * giving other users access to Realms. - * - * @see How to control permissions - * @deprecated use {@link #getPermissionManager()} instead. - */ - @Deprecated - public Realm getManagementRealm() { - return Realm.getInstance(managementConfig.initAndGetManagementRealmConfig(this)); - } - /** * Returns all the valid sessions belonging to the user. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/ManagementModule.java b/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/ManagementModule.java index 8ba6882d11..358330d92b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/ManagementModule.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/ManagementModule.java @@ -19,10 +19,6 @@ import io.realm.annotations.RealmModule; import io.realm.permissions.PermissionOffer; - -/** - * FIXME Javadoc - */ @RealmModule(library = true, classes = { PermissionChange.class, PermissionOffer.class, PermissionOfferResponse.class }) public class ManagementModule { } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 75b178ca6c..5d5b06f149 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -36,7 +36,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import io.realm.internal.Util; +import io.realm.internal.OsRealmConfig; +import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; import io.realm.permissions.AccessLevel; @@ -53,7 +54,6 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) -@Ignore("Wait for https://github.com/realm/realm-object-server/issues/1671 to be fixed") public class PermissionManagerTests extends StandardIntegrationTest { private SyncUser user; @@ -87,7 +87,6 @@ public void onError(ObjectServerError error) { @RunTestInLooperThread(emulateMainThread = true) public void getPermissions_noLongerValidWhenPermissionManagerIsClosed() { final PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); pm.getPermissions(new PermissionManager.PermissionsCallback() { @Override public void onSuccess(RealmResults permissions) { @@ -99,6 +98,7 @@ public void onSuccess(RealmResults permissions) { @Override public void onError(ObjectServerError error) { + pm.close(); fail(error.toString()); } }); @@ -106,6 +106,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore("See https://github.com/realm/ros/issues/437") public void getPermissions_updatedWithNewRealms() { PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -149,9 +150,9 @@ public void onError(ObjectServerError error) { }); } - @Ignore("Until https://github.com/realm/realm-object-server/issues/1671 has been solved") @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore("See https://github.com/realm/ros/issues/437") public void getPermissions_updatedWithNewRealms_stressTest() { final PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -172,6 +173,7 @@ public void onSuccess(RealmResults permissions) { permissions.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults permissions) { + RealmLog.error(Arrays.toString(permissions.toArray())); // FIXME Debug output for CI. Remove before release. Permission p = permissions.where().endsWith("path", "test9").findFirst(); if (p != null) { assertTrue(p.mayRead()); @@ -196,18 +198,14 @@ public void getPermissions_closed() throws IOException { PermissionManager pm = user.getPermissionManager(); pm.close(); + thrown.expect(IllegalStateException.class); pm.getPermissions(new PermissionManager.PermissionsCallback() { @Override public void onSuccess(RealmResults permissions) { fail(); } - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals(IllegalStateException.class, error.getException().getClass()); - looperThread.testComplete(); - } + public void onError(ObjectServerError error) { fail(); } }); } @@ -245,6 +243,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore("See https://github.com/realm/ros/issues/432") public void getPermissions_addTaskAfterClientReset() { final PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -429,6 +428,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore("See https://github.com/realm/ros/issues/432") public void getDefaultPermissions_returnLoadedResults() { PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -449,20 +449,24 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore("See https://github.com/realm/ros/issues/432") public void getDefaultPermissions_noLongerValidWhenPermissionManagerIsClosed() { final PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); pm.getDefaultPermissions(new PermissionManager.PermissionsCallback() { @Override public void onSuccess(RealmResults permissions) { - assertTrue(permissions.isValid()); - pm.close(); + try { + assertTrue(permissions.isValid()); + } finally { + pm.close(); + } assertFalse(permissions.isValid()); looperThread.testComplete(); } @Override public void onError(ObjectServerError error) { + pm.close(); fail(error.toString()); } }); @@ -479,21 +483,16 @@ public void getDefaultPermissions_updatedWithNewRealms() { @RunTestInLooperThread(emulateMainThread = true) public void getDefaultPermissions_closed() throws IOException { PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); pm.close(); + thrown.expect(IllegalStateException.class); pm.getDefaultPermissions(new PermissionManager.PermissionsCallback() { @Override public void onSuccess(RealmResults permissions) { fail(); } - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals(IllegalStateException.class, error.getException().getClass()); - looperThread.testComplete(); - } + public void onError(ObjectServerError error) { fail(); } }); } @@ -650,6 +649,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore("See https://github.com/realm/ros/issues/429") public void applyPermissions_wrongUrlFails() { String wrongUrl = createRemoteRealm(user, "test") + "-notexisting"; @@ -716,6 +716,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore("See https://github.com/realm/ros/issues/426") public void applyPermissions_withUsername() { String user1Username = TestHelper.getRandomEmail(); String user2Username = TestHelper.getRandomEmail(); @@ -830,6 +831,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore("See https://github.com/realm/ros/issues/430") public void makeOffer_noManageAccessThrows() { // User 2 creates a Realm SyncUser user2 = UserFactory.createUniqueUser(); @@ -902,7 +904,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("Figure out how the time differs between emulator and server") + @Ignore public void acceptOffer_expiredThrows() { // Trying to guess how long CI is to process this. The offer cannot be created if it // already expired. @@ -1181,7 +1183,9 @@ public void run() { */ private String createRemoteRealm(SyncUser user, String realmName) { String url = Constants.AUTH_SERVER_URL + "~/" + realmName; - SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + SyncConfiguration config = new SyncConfiguration.Builder(user, url) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build(); Realm realm = Realm.getInstance(config); SyncSession session = SyncManager.getSession(config); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java deleted file mode 100644 index a2f96973e8..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ManagementRealmTests.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.objectserver; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.atomic.AtomicReference; - -import io.realm.ObjectServerError; -import io.realm.Realm; -import io.realm.RealmChangeListener; -import io.realm.RealmResults; -import io.realm.StandardIntegrationTest; -import io.realm.SyncConfiguration; -import io.realm.SyncSession; -import io.realm.SyncUser; -import io.realm.entities.Dog; -import io.realm.internal.permissions.PermissionOfferResponse; -import io.realm.log.RealmLog; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; -import io.realm.permissions.AccessLevel; -import io.realm.permissions.PermissionOffer; -import io.realm.rule.RunTestInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -@Ignore("Resolve https://github.com/realm/ros/issues/18") -public class ManagementRealmTests extends StandardIntegrationTest { - - // This is primarily a test making sure that an admin user actually connects correctly to ROS. - // See https://github.com/realm/realm-java/issues/4750 - @Test - @RunTestInLooperThread - public void adminUser_writeInvalidPermissionOffer() { - final SyncUser user = UserFactory.createAdminUser(Constants.AUTH_URL); - assertTrue(user.isValid()); - Realm realm = user.getManagementRealm(); - looperThread.closeAfterTest(realm); - looperThread.runAfterTest(new Runnable() { - @Override - public void run() { - user.logout(); - } - }); - realm.beginTransaction(); - // Invalid Permission offer - realm.copyToRealm(new PermissionOffer("*", AccessLevel.WRITE, null)); - realm.commitTransaction(); - RealmResults results = realm.where(PermissionOffer.class).findAllAsync(); - looperThread.keepStrongReference(results); - results.addChangeListener(new RealmChangeListener >() { - @Override - public void onChange(RealmResults offers) { - if (offers.size() > 0) { - PermissionOffer offer = offers.first(); - Integer statusCode = offer.getStatusCode(); - if (statusCode != null && statusCode > 0) { - assertTrue(offer.getStatusMessage().contains("The path is invalid or current user has no access.")); - looperThread.testComplete(); - } - } - } - }); - } - - @Ignore("Failing due to terminate called after throwing an instance of 'realm::MultipleSyncAgents'. Will be fixed when upgrading to Sync 1.10") - @Test - @RunTestInLooperThread - public void create_acceptOffer() { - SyncUser user1 = UserFactory.createUniqueUser(Constants.AUTH_URL); - final SyncUser user2 = UserFactory.createUniqueUser(Constants.AUTH_URL); - - // 1. User1 creates Realm that user2 does not have access - final String user1RealmUrl = "realm://127.0.0.1:9080/" + user1.getIdentity() + "/permission-offer-test"; - SyncConfiguration config1 = new SyncConfiguration.Builder(user1, user1RealmUrl). - errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail("Realm 1 unexpected error: " + error); - } - }) - .build(); - final Realm realm1 = Realm.getInstance(config1); - looperThread.addTestRealm(realm1); - realm1.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObject(Dog.class); - } - }); - - // 2. Create configuration for User2's Realm. - final SyncConfiguration config2 = new SyncConfiguration.Builder(user2, user1RealmUrl).build(); - - // 3. Create PermissionOffer - final AtomicReference offerId = new AtomicReference(null); - final Realm user1ManagementRealm = user1.getManagementRealm(); - looperThread.addTestRealm(user1ManagementRealm); - user1ManagementRealm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - PermissionOffer offer = new PermissionOffer(user1RealmUrl, AccessLevel.WRITE, null); - offerId.set(offer.getId()); - realm.copyToRealm(offer); - } - }, new Realm.Transaction.OnSuccess() { - @Override - public void onSuccess() { - // 4. Wait for offer to get an token - RealmLog.error("OfferID: " + offerId.get()); - RealmResults offers = user1ManagementRealm.where(PermissionOffer.class) - .equalTo("id", offerId.get()) - .findAllAsync(); - looperThread.keepStrongReference(offers); - offers.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults offers) { - final PermissionOffer offer = offers.first(null); - if (offer != null && offer.isOfferCreated() && offer.getToken() != null) { - // 5. User2 uses the token to accept the offer - final String offerToken = offer.getToken(); - final AtomicReference offerResponseId = new AtomicReference(); - final Realm user2ManagementRealm = user2.getManagementRealm(); - looperThread.addTestRealm(user2ManagementRealm); - user2ManagementRealm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - PermissionOfferResponse offerResponse = new PermissionOfferResponse(offerToken); - offerResponseId.set(offerResponse.getId()); - realm.copyToRealm(offerResponse); - } - }, new Realm.Transaction.OnSuccess() { - @Override - public void onSuccess() { - // 6. Wait for the offer response to be accepted - RealmResults responses = user2ManagementRealm.where(PermissionOfferResponse.class) - .equalTo("id", offerResponseId.get()) - .findAllAsync(); - looperThread.keepStrongReference(responses); - responses.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults responses) { - PermissionOfferResponse response = responses.first(null); - if (response != null && response.isSuccessful() && response.getToken().equals(offerToken)) { - // 7. Response accepted. It should now be possible for user2 to access user1's Realm - Realm realm = Realm.getInstance(config2); - looperThread.addTestRealm(realm); - RealmResults dogs = realm.where(Dog.class).findAll(); - looperThread.keepStrongReference(dogs); - dogs.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults element) { - assertEquals(1, element.size()); - looperThread.testComplete(); - } - }); - } - } - }); - } - }); - } - } - }); - } - }); - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java index fef9b158bd..d13f3546d1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java @@ -24,7 +24,6 @@ import io.realm.SyncedRealmTests; import io.realm.objectserver.AuthTests; import io.realm.objectserver.EncryptedSynchronizedRealmTests; -import io.realm.objectserver.ManagementRealmTests; import io.realm.objectserver.ProcessCommitTests; import io.realm.objectserver.ProgressListenerTests; import io.realm.SyncSessionTests; @@ -36,7 +35,6 @@ SyncedRealmTests.class, AuthTests.class, EncryptedSynchronizedRealmTests.class, - ManagementRealmTests.class, ProcessCommitTests.class, ProgressListenerTests.class, SyncSessionTests.class}) From 8357d669faf581225251c72d88cf0ca4d46a7c49 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 3 Oct 2017 14:51:03 +0200 Subject: [PATCH 1003/2110] Prepare CHANGELOG for public release (#5368) --- CHANGELOG.md | 106 +++++++++++++++------------------------------------ version.txt | 2 +- 2 files changed, 31 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e390b70b..b85ce0abc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,101 +1,55 @@ -## 4.0.0 (YYYY-MM-DD) +## 4.0.0-RC1 (YYYY-MM-DD) ## Breaking Changes -* Calling `distinct()` on a sorted `RealmResults` no longer clears the sorting (#3503). +The internal file format has been upgraded. Opening an older Realm will upgrade the file automatically, but older versions of Realm will no longer be able to read the file. + +* [ObjectServer] Updated protocol version to 22 which is only compatible with Realm Object Server >= 2.0.0. * [ObjectServer] Removed deprecated APIs `SyncUser.retrieveUser()` and `SyncUser.retrieveUserAsync()`. Use `SyncUser.retrieveInfoForUser()` and `retrieveInfoForUserAsync()` instead. +* [ObjectServer] `SyncUser.Callback` now accepts a generic parameter indicating type of object returned when `onSuccess` is called. +* [ObjectServer] Renamed `SyncUser.getAccessToken` to `SyncUser.getRefreshToken`. * [ObjectServer] Removed deprecated API `SyncUser.getManagementRealm()`. -* Removed deprecated APIs `RealmSchema.close()` and `RealmObjectSchema.close()`. Those don't have to be called anymore. -* Removed deprecated API `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. -* Removed deprecated API `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. -* `SyncUser.Callback` to becomes generic. -* Removed `SyncUser.getAccessToken` method from public API, and rename it to `getRefreshToken`. -* Removed `UNSUPPORTED_TABLE`, `UNSUPPORTED_MIXED` and `UNSUPPORTED_DATE` from `RealmFieldType`. +* Calling `distinct()` on a sorted `RealmResults` no longer clears any sorting defined (#3503). * Relaxed upper bound of type parameter of `RealmList`, `RealmQuery`, `RealmResults`, `RealmCollection`, `OrderedRealmCollection` and `OrderedRealmCollectionSnapshot`. - -## Deprecated - -## Enhancements - -* Added support for primitive lists in migrations using `RealmObjectSchema.addRealmListField(String name, Class type)` (#5329). -* Now users can use `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` as a type parameter of `RealmList`. - -## Bug Fixes - -## Internal - -* Upgraded to Realm Sync 2.0.0-rc27. -* Upgraded to Realm Core 4.0.1. - -## Credits - - -## 4.0.0-BETA3 (YYYY-MM-DD) - -### Breaking Changes - -* `RealmResults.distinct()`/`RealmResults.distinctAsync()` have been removed. Use `RealmQuery.distinct()`/`RealmQuery.distinctAsync()` instead. -* `RealmQuery.createQuery(Realm, Class)`, `RealmQuery.createDynamicQuery(DynamicRealm, String)`, `RealmQuery.createQueryFromResult(RealmResults)` and `RealmQuery.createQueryFromList(RealmList)` have been removed. Use `Realm.where(Class)`, `DynamicRealm.where(String)`, RealmResults.where()` and `RealmList.where()` instead. - -### Enhancements - -* [ObjectServer] `SyncUserInfo` now also exposes a users metadata using `SyncUserInfo.getMetadata()` -* Minor performance improvement when copy/insert objects into Realm. -* [ObjectServer] Added preview support for partial synchronization (#5276). This feature is in `@Beta` and will probably change. - -### Bug Fixes - -* Throw `IllegalArgumentException` instead of `IllegalStateException` when calling string/binary data setters if the data length exceeds the limit. -* Exposing a `RealmConfiguration` that allows a user to open the backup Realm after the client reset (#4759/#5223). - -### Internal - -* Upgraded to Realm Sync 2.0.0-rc16. -* Upgraded to Realm Core 3.0.0-rc5. -* Always use Object Store to create primary key table. - - -## 4.0.0-BETA2 (2017-07-27) - -### Bug Fixes - -* [ObjectServer] Realm no longer throws a native “unsupported instruction” exception in some cases when opening a synced Realm asynchronously (https://github.com/realm/realm-object-store/issues/502). - - -## 4.0.0-BETA1 (2017-07-13) - -### Breaking Changes - -* [ObjectServer] Updated protocol version to 19 which is only compatible with ROS > 2.0.0. * Realm has upgraded its RxJava1 support to RxJava2 (#3497) * `Realm.asObservable()` has been renamed to `Realm.asFlowable()`. * `RealmList.asObservable()` has been renamed to `RealmList.asFlowable()`. * `RealmResults.asObservable()` has been renamed to `RealmResults.asFlowable()`. * `RealmObject.asObservable()` has been renamed to `RealmObject.asFlowable()`. * `RxObservableFactory` now return RxJava2 types instead of RxJava1 types. +* Removed deprecated APIs `RealmSchema.close()` and `RealmObjectSchema.close()`. Those don't have to be called anymore. +* Removed deprecated API `RealmResults.removeChangeListeners()`. Use `RealmResults.removeAllChangeListeners()` instead. +* Removed deprecated API `RealmObject.removeChangeListeners()`. Use `RealmObject.removeAllChangeListeners()` instead. +* Removed `UNSUPPORTED_TABLE`, `UNSUPPORTED_MIXED` and `UNSUPPORTED_DATE` from `RealmFieldType`. +* Removed deprecated API `RealmResults.distinct()`/`RealmResults.distinctAsync()`. Use `RealmQuery.distinct()`/`RealmQuery.distinctAsync()` instead. +* `RealmQuery.createQuery(Realm, Class)`, `RealmQuery.createDynamicQuery(DynamicRealm, String)`, `RealmQuery.createQueryFromResult(RealmResults)` and `RealmQuery.createQueryFromList(RealmList)` have been removed. Use `Realm.where(Class)`, `DynamicRealm.where(String)`, `RealmResults.where()` and `RealmList.where()` instead. -### Deprecated - -### Enhancements +## Enhancements +* [ObjectServer] `SyncUserInfo` now also exposes a users metadata using `SyncUserInfo.getMetadata()` +* `RealmList` can now contain `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` values. [Queries](https://github.com/realm/realm-java/issues/5361) and [Importing primitive lists from JSON](https://github.com/realm/realm-java/issues/5361) are not supported yet. +* Added support for lists of primitives in `RealmObjectSchema` with `setRealmListField(String fieldName, Class primitiveType)` +* Added support for lists of primitives in `DynamicRealmObject` with `setList(String fieldName, RealmList list)` and `getList(String fieldName, Class primitiveType)`. +* Minor performance improvement when copy/insert objects into Realm. * Added `static RealmObject.getRealm(RealmModel)`, `RealmObject.getRealm()` and `DynamicRealmObject.getDynamicRealm()` (#4720). * Added `RealmResults.asChangesetObservable()` that emits the pair `(results, changeset)` (#4277). * Added `RealmList.asChangesetObservable()` that emits the pair `(list, changeset)` (#4277). * Added `RealmObject.asChangesetObservable()` that emits the pair `(object, changeset)` (#4277). -### Bug Fixes - -### Internal - -* Upgraded to Realm Sync 2.0.0-rc12. -* Upgraded to Realm Core 3.0.0-rc3. - +## Bug Fixes -## 3.7.3 (YYYY-MM-DD) +* [ObjectServer] Exposing a `RealmConfiguration` that allows a user to open the backup Realm after the client reset (#4759/#5223). +* [ObjectServer] Realm no longer throws a native “unsupported instruction” exception in some cases when opening a synced Realm asynchronously (https://github.com/realm/realm-object-store/issues/502). +* Throw `IllegalArgumentException` instead of `IllegalStateException` when calling string/binary data setters if the data length exceeds the limit. +* Added support for ISO8601 2-digit time zone designators (#5309). +* "Bad File Header" caused by the device running out of space while compacting the Realm (#5011). +* `RealmQuery.equalTo()` failed to find null values on an indexed field if using Case.INSENSITIVE (#5299). -### Bug Fixes +## Internal -* Added support for ISO8601 2-digit time zone designators (#5309). +* Upgraded to Realm Sync 2.0.0-rc27. +* Upgraded to Realm Core 4.0.1. +* Use Object Store to create the primary key table. ### Credits diff --git a/version.txt b/version.txt index e0c4a70607..339735c9d8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.0.0-BETA3-SNAPSHOT +4.0.0-RC1-SNAPSHOT From 0ab3d135335127c3d56b1f5f379fc05686160c9d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 3 Oct 2017 15:53:40 +0200 Subject: [PATCH 1004/2110] Don't push non-final release to Github --- tools/release.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/release.sh b/tools/release.sh index 73f94f6ee6..a8494e50fb 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -212,8 +212,14 @@ push_release() { # Push branch & tag git checkout releases - git push origin releases - git push origin "v${VERSION}" + + # Don't push to releases branch if we are doing a beta release. + if [[ ! "$VERSION" =~ [a-zA-Z] ]] ; then + git push origin releases + git push origin "v${VERSION}" + else + echo "Non-final release. Release was not pushed to Github. Remember to remove commits on `releases` branch manually." + fi } publish_javadoc() { From 8b9461040b9290944ec2058c7b91824dd529edfb Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 3 Oct 2017 17:40:07 +0200 Subject: [PATCH 1005/2110] Prepare next dev iteration. Fix mistake in Changelog. --- CHANGELOG.md | 17 +++++++++++++++-- version.txt | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b85ce0abc1..fab4ba118d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,17 @@ -## 4.0.0-RC1 (YYYY-MM-DD) +## 4.0.0 (YYYY-MM-DD) + +## Breaking Changes + +## Enhancements + +## Bug Fixes + +## Internal + +## Credits + + +## 4.0.0-RC1 (2017-10-03) ## Breaking Changes @@ -28,7 +41,7 @@ The internal file format has been upgraded. Opening an older Realm will upgrade * [ObjectServer] `SyncUserInfo` now also exposes a users metadata using `SyncUserInfo.getMetadata()` * `RealmList` can now contain `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` values. [Queries](https://github.com/realm/realm-java/issues/5361) and [Importing primitive lists from JSON](https://github.com/realm/realm-java/issues/5361) are not supported yet. -* Added support for lists of primitives in `RealmObjectSchema` with `setRealmListField(String fieldName, Class primitiveType)` +* Added support for lists of primitives in `RealmObjectSchema` with `addRealmListField(String fieldName, Class primitiveType)` * Added support for lists of primitives in `DynamicRealmObject` with `setList(String fieldName, RealmList list)` and `getList(String fieldName, Class primitiveType)`. * Minor performance improvement when copy/insert objects into Realm. * Added `static RealmObject.getRealm(RealmModel)`, `RealmObject.getRealm()` and `DynamicRealmObject.getDynamicRealm()` (#4720). diff --git a/version.txt b/version.txt index 339735c9d8..94ae9ee1fa 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.0.0-RC1-SNAPSHOT +4.0.0-SNAPSHOT From db72d94e719b413da597b54282c9c7323145cff7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 4 Oct 2017 10:20:42 +0200 Subject: [PATCH 1006/2110] Make CI more reliable (#5377) --- .../syncIntegrationTest/java/io/realm/SyncSessionTests.java | 2 -- .../syncIntegrationTest/java/io/realm/SyncedRealmTests.java | 1 + .../java/io/realm/objectserver/PartialSyncTests.java | 2 ++ .../java/io/realm/objectserver/ProcessCommitTests.java | 3 ++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index eb5c1a5701..f2fb6af2c6 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -7,7 +7,6 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -374,7 +373,6 @@ public void onChange(RealmResults stringOnlies) { // A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. @Test - @Ignore("until https://github.com/realm/realm-java/issues/5294 is fixed") public void downloadChangesWhenRealmOutOfScope() throws InterruptedException { final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index c93851536e..ba321beb9f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -155,6 +155,7 @@ public void run() { // needed to correctly test all error paths. @Test @RunTestInLooperThread + @Ignore("See https://github.com/realm/realm-java/issues/5373") public void waitForInitialData_resilientInCaseOfRetriesAsync() { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index 1414e392ed..ffb89ac860 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -4,6 +4,7 @@ import android.os.HandlerThread; import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,6 +39,7 @@ public class PartialSyncTests extends StandardIntegrationTest { public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); @Test + @Ignore("See https://github.com/realm/realm-java/issues/5375") public void partialSync() throws InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index fe4eebfe8c..e4847f19aa 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -20,6 +20,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,7 +28,6 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; -import io.realm.BaseIntegrationTest; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmResults; @@ -104,6 +104,7 @@ protected void run() { @Test @RunTestInLooperThread @RunTestWithRemoteService(remoteService = SimpleCommitRemoteService.class, onLooperThread = true) + @Ignore("See https://github.com/realm/realm-java/issues/5376") public void expectSimpleCommit() { looperThread.runAfterTest(remoteService.afterRunnable); remoteService.createHandler(Looper.myLooper()); From af32d3cd3c08e41ccf6976b06db293c8d9375b17 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 4 Oct 2017 23:28:22 +0100 Subject: [PATCH 1007/2110] fixes #5375 (#5382) --- .../realm/objectserver/PartialSyncTests.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index ffb89ac860..4bc73892f1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -4,7 +4,6 @@ import android.os.HandlerThread; import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -18,13 +17,13 @@ import io.realm.SyncManager; import io.realm.SyncUser; import io.realm.TestHelper; +import io.realm.TestSyncConfigurationFactory; import io.realm.exceptions.RealmException; import io.realm.objectserver.model.PartialSyncModule; import io.realm.objectserver.model.PartialSyncObjectA; import io.realm.objectserver.model.PartialSyncObjectB; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; -import io.realm.TestSyncConfigurationFactory; import static org.hamcrest.number.OrderingComparison.greaterThan; import static org.junit.Assert.assertEquals; @@ -39,23 +38,22 @@ public class PartialSyncTests extends StandardIntegrationTest { public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); @Test - @Ignore("See https://github.com/realm/realm-java/issues/5375") public void partialSync() throws InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - final SyncConfiguration partialSyncConfig = configFactory + final SyncConfiguration syncConfig = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .waitForInitialRemoteData() .modules(new PartialSyncModule()) - .partialRealm() .build(); - SyncConfiguration adminConfig = configFactory - .createSyncConfigurationBuilder(adminUser, partialSyncConfig.getServerUrl().toString()) + + final SyncConfiguration partialSyncConfig = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .modules(new PartialSyncModule()) + .partialRealm() .build(); - // Using Admin user, populate the Realm. - Realm realm = Realm.getInstance(adminConfig); + Realm realm = Realm.getInstance(syncConfig); realm.beginTransaction(); PartialSyncObjectA objectA = realm.createObject(PartialSyncObjectA.class); objectA.setNumber(0); @@ -93,8 +91,9 @@ public void partialSync() throws InterruptedException { } realm.commitTransaction(); - SyncManager.getSession(adminConfig).uploadAllLocalChanges(); + SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); + Realm.deleteRealm(syncConfig); final CountDownLatch latch = new CountDownLatch(2); From 6eec0341424f9757fcac1bdab065a5d7fa502230 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 5 Oct 2017 07:55:46 +0200 Subject: [PATCH 1008/2110] Update ROS integration test server to alpha.46 (#5381) --- dependencies.list | 2 +- .../java/io/realm/PermissionManagerTests.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies.list b/dependencies.list index c7acfe203e..3a595eb7d2 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,4 +5,4 @@ REALM_SYNC_SHA256=3a558b10ecab3e8dbf6cbceae7fe40af38ad8d8467ddd1fe036ce92c1e7810 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.44 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.46 diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 5d5b06f149..070f9fa348 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -649,7 +649,6 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/429") public void applyPermissions_wrongUrlFails() { String wrongUrl = createRemoteRealm(user, "test") + "-notexisting"; @@ -670,7 +669,8 @@ public void onSuccess() { @Override public void onError(ObjectServerError error) { - assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); + // FIXME: Should be 614, see https://github.com/realm/ros/issues/429 + assertEquals(ErrorCode.INVALID_PARAMETERS, error.getErrorCode()); looperThread.testComplete(); } }); From fd09f67604fd98b75abbe88ed6c576680cde6c60 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 6 Oct 2017 10:52:04 +0200 Subject: [PATCH 1009/2110] Keep annotations at runtime (#5384) --- CHANGELOG.md | 2 ++ .../src/main/java/io/realm/annotations/Ignore.java | 2 +- realm-annotations/src/main/java/io/realm/annotations/Index.java | 2 +- .../src/main/java/io/realm/annotations/LinkingObjects.java | 2 +- .../src/main/java/io/realm/annotations/PrimaryKey.java | 2 +- .../src/main/java/io/realm/annotations/RealmClass.java | 2 +- .../src/main/java/io/realm/annotations/Required.java | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fab4ba118d..4b0c80c1ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ## Enhancements +* All Realm annotations are now kept at runtime, allowing runtime tools access to them (#5344). + ## Bug Fixes ## Internal diff --git a/realm-annotations/src/main/java/io/realm/annotations/Ignore.java b/realm-annotations/src/main/java/io/realm/annotations/Ignore.java index 784f6c7676..bc86b24c26 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/Ignore.java +++ b/realm-annotations/src/main/java/io/realm/annotations/Ignore.java @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Retention(RetentionPolicy.CLASS) +@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Ignore { diff --git a/realm-annotations/src/main/java/io/realm/annotations/Index.java b/realm-annotations/src/main/java/io/realm/annotations/Index.java index bb4f9e1b17..ea6000c336 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/Index.java +++ b/realm-annotations/src/main/java/io/realm/annotations/Index.java @@ -27,7 +27,7 @@ *

            * NOTICE: Only String, int, byte, short, long, boolean and Date fields can be indexed. */ -@Retention(RetentionPolicy.CLASS) +@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Index { diff --git a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java index eef7205372..93be19f438 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java +++ b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java @@ -97,7 +97,7 @@ * equivalent to link queries. Please read for more * information. */ -@Retention(RetentionPolicy.CLASS) +@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface LinkingObjects { /** diff --git a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java index b9e263cf4e..daac8c110f 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java +++ b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java @@ -34,7 +34,7 @@ * String, Byte, Short, Integer, and Long are also allowed, and further permitted to have {@code null} * as a primary key value. */ -@Retention(RetentionPolicy.CLASS) +@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface PrimaryKey { diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java index 22111078c4..d0ab776fbb 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java @@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Retention(RetentionPolicy.CLASS) +@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface RealmClass { diff --git a/realm-annotations/src/main/java/io/realm/annotations/Required.java b/realm-annotations/src/main/java/io/realm/annotations/Required.java index a6e500c637..2950b0e3de 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/Required.java +++ b/realm-annotations/src/main/java/io/realm/annotations/Required.java @@ -29,7 +29,7 @@ * Fields with primitive types and the {@link io.realm.RealmList} type are required implicitly. * Fields with {@link io.realm.RealmObject} type are always nullable. */ -@Retention(RetentionPolicy.CLASS) +@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Required { From 9fceccd1be7ed388bc2ed9e179ad1c0ab85061be Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 11 Oct 2017 19:37:19 +0800 Subject: [PATCH 1010/2110] Fix cmake license (#5407) --- Dockerfile | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index f1a65fe040..e2c50e9865 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,10 +49,13 @@ RUN cd /opt && \ RUN mkdir "${ANDROID_HOME}/licenses" && \ echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" RUN sdkmanager --update +# Accept all licenses +RUN yes y | sdkmanager --licenses RUN sdkmanager 'platform-tools' RUN sdkmanager 'build-tools;26.0.2' RUN sdkmanager 'extras;android;m2repository' RUN sdkmanager 'platforms;android-26' +RUN sdkmanager 'cmake;3.6.4111459' # Install the NDK RUN mkdir /opt/android-ndk-tmp && \ @@ -64,13 +67,5 @@ RUN mkdir /opt/android-ndk-tmp && \ rm -rf /opt/android-ndk-tmp && \ chmod -R a+rX /opt/android-ndk -# Install cmake -RUN mkdir /opt/cmake-tmp && \ - cd /opt/cmake-tmp && \ - wget -q https://dl.google.com/android/repository/cmake-3.6.3155560-linux-x86_64.zip -O cmake-linux.zip && \ - mkdir -p ${ANDROID_HOME}/cmake/3.6.3155560 && \ - unzip cmake-linux.zip -d ${ANDROID_HOME}/cmake/3.6.3155560 && \ - rm -rf /opt/cmake-tmp - # Make the SDK universally writable RUN chmod -R a+rwX ${ANDROID_HOME} From 329cfcfed9a7f2ac4d6def263ca1044fc28f874e Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 11 Oct 2017 19:33:01 +0100 Subject: [PATCH 1011/2110] Enabling client reset test (#5388) * fixes #5143 --- .../java/io/realm/SessionTests.java | 53 ------------------- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/SyncSessionTests.java | 52 ++++++++++++++++++ 3 files changed, 53 insertions(+), 54 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index b2759133f5..f4dc71ac09 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -21,13 +21,10 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.atomic.AtomicReference; - import io.realm.entities.StringOnly; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; @@ -196,56 +193,6 @@ public void onError(SyncSession session, ObjectServerError error) { SyncManager.simulateClientReset(SyncManager.getSession(config)); } - // Check that if we manually trigger a Client Reset, then it should be possible to start - // downloading the Realm immediately after. - @Test - @RunTestInLooperThread - @Ignore("https://github.com/realm/realm-java/issues/5143") - public void clientReset_manualTriggerAllowSessionToRestart() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/~/myrealm"; - final AtomicReference configRef = new AtomicReference<>(null); - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user , url) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); - - // Try to re-open Realm and download it again - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - // Validate that files have been moved - assertFalse(handler.getOriginalFile().exists()); - assertTrue(handler.getBackupFile().exists()); - - SyncConfiguration config = configRef.get(); - Realm instance = Realm.getInstance(config); - looperThread.addTestRealm(instance); - try { - SyncManager.getSession(config).downloadAllServerChanges(); - looperThread.testComplete(); - } catch (InterruptedException e) { - fail(e.toString()); - } - } - }); - } - }) - .build(); - configRef.set(config); - - Realm realm = Realm.getInstance(config); - looperThread.addTestRealm(realm); - - // Trigger error - SyncManager.simulateClientReset(SyncManager.getSession(config)); - } - // Check that we can use the backup SyncConfiguration to open the Realm. @Test @RunTestInLooperThread diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 0d0615caaf..8a387856db 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 0d0615caaf0df6dbccca3f86a05303892c3a7a2e +Subproject commit 8a387856db0beb6a95e385546d1752188aff14a6 diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index f2fb6af2c6..21399cc133 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.UUID; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; @@ -21,6 +22,7 @@ import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; +import io.realm.rule.RunTestInLooperThread; import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; @@ -445,4 +447,54 @@ public void run() { realm.close(); } + // Check that if we manually trigger a Client Reset, then it should be possible to start + // downloading the Realm immediately after. + @Test + @RunTestInLooperThread + public void clientReset_manualTriggerAllowSessionToRestart() { + final String uniqueName = UUID.randomUUID().toString(); + SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); + SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + + final AtomicReference configRef = new AtomicReference<>(null); + final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM).directory(looperThread.getRoot()) + + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + final ClientResetRequiredError handler = (ClientResetRequiredError) error; + // Execute Client Reset + looperThread.closeTestRealms(); + handler.executeClientReset(); + + // Try to re-open Realm and download it again + looperThread.postRunnable(new Runnable() { + @Override + public void run() { + // Validate that files have been moved + assertFalse(handler.getOriginalFile().exists()); + assertTrue(handler.getBackupFile().exists()); + + SyncConfiguration config = configRef.get(); + Realm instance = Realm.getInstance(config); + looperThread.addTestRealm(instance); + try { + SyncManager.getSession(config).downloadAllServerChanges(); + looperThread.testComplete(); + } catch (InterruptedException e) { + fail(e.toString()); + } + } + }); + } + }) + .build(); + configRef.set(config); + + Realm realm = Realm.getInstance(config); + looperThread.addTestRealm(realm); + // Trigger error + SyncManager.simulateClientReset(SyncManager.getSession(config)); + } + } From 2ae81e832d53b6533105b934d71f866e74cdec73 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 11 Oct 2017 20:43:10 +0100 Subject: [PATCH 1012/2110] Update to ROS 2.0.0-rc4 for testing (#5408) --- dependencies.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.list b/dependencies.list index 3a595eb7d2..dd56b99fec 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,4 +5,4 @@ REALM_SYNC_SHA256=3a558b10ecab3e8dbf6cbceae7fe40af38ad8d8467ddd1fe036ce92c1e7810 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-alpha.46 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-rc.4 From 19a434d795b3d8ebdf576fc85dd49096679512d6 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 12 Oct 2017 12:13:34 +0900 Subject: [PATCH 1013/2110] Update android gradle plugin to 3.0.0-beta7 (#4899) --- examples/build.gradle | 2 +- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54713 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +-- examples/newsreaderExample/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54713 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +-- gradle.properties | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54713 bytes gradle/wrapper/gradle-wrapper.properties | 3 +-- gradlew | 3 --- library-benchmarks/build.gradle | 2 +- library-benchmarks/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54713 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54713 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54713 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +-- realm.properties | 2 +- realm/build.gradle | 2 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54713 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +-- realm/realm-library/build.gradle | 19 ++++++++++++++++++ tools/update_gradle_wrapper.sh | 10 --------- 24 files changed, 33 insertions(+), 31 deletions(-) create mode 100644 examples/newsreaderExample/gradle.properties create mode 100644 library-benchmarks/gradle.properties diff --git a/examples/build.gradle b/examples/build.gradle index c0d569277d..f2c813c27f 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -23,7 +23,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-alpha4' + classpath 'com.android.tools.build:gradle:3.0.0-beta7' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-gradle-plugin:${currentVersion}" diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index b938b9891be5bc22fb8fe6dd34a78d203d4a6e93..d457a1a990f3f8cee976589d405f62b13f1eede8 100644 GIT binary patch delta 678 zcmYL{T}YEr9LC@CH#Vcm-enH=PEo8(Uzs{%!-I!QiFu`>6#sjw&u?ck~}VbW_&W^PcFZ0hBMf_ z*#&}a>@j`>?d$_AGu4!okkWGCpHV4~33-euD2>Rtppy-JK>D1}@h~3-t%_>(LUwA0 z*b}_aT38alwKKGmE9wSIp{PGBkhlFSOVR*d&7Pu)B8<&i;V&|wdQPQ$n`n4!MhtM& zmkVrh=n2_zqFff0Zira;Z z%r`QEDSVuaKG=M_aW|~804Boq^vs91zP&Im%czMcRO07o!CC(LQ9Ir05mM_>aQc%<)?R@fz1#7Qqp?q* zr_ah$8T%F#)`r%F>y)}Fg5*LI%SL7F66FlS{-6{cgJv8^$oLskDD%FsD&nEjf>PSx=Ts%#jw%jE?di}inz_-Ae`)aLn)+skFT#^8qY7d&?Yz5oCK delta 716 zcmYL{T}YEr7{}l9DBEi(zBByjRVOKtzGAs#_>tm9LCdvesL@QyE|v+BCFDgRAqjn4 z&s+bcUxs zOTJWn)GA5RxyP1Hd1HTiC_Tg^k2>?VIgr`iCfo5V&}c~1k<@P2%eOtQNIq}g2Wzlx zcPpL-JJ=*Pf+jW#HPpa1aYI*t{*XpS25UGU4yHnjDOe4ubY90TBRo1iK1UST^vYfy zxwD#K3122}7H=uVY`B(3rNbB5I@;%Z*+b0Ccay1HMD~^!qA{XTS&fj+np`~KGV8FA z(MThengyOV=itzSMt5vNzP4rKR9wN>f=YRIVc8uyaN3LTg?E)WgjF1EAzP=w!RSe< zbP0?{Pf%vh4(f4av4IS3RJ#x2(W1t#!@t-}J~#aCIw)%jnqn%`&=tE%%L8~hT!QtO zmE6|^7RSoqimMbF6!OntHoS3-Ufkre0^j2%x;Mel3QDn$zJ;XToig%EVMEyVspLQJ!YSh{qMmfsmdpcSA0eupi$oxMyD#ph+m~t zmWPk24*K>1pHk)6zNLb*t1A0}fz>_=&f)9aF+MGgo&_)yIKU?!-I!QiFu`>6#sjw&u?ck~}VbW_&W^PcFZ0hBMf_ z*#&}a>@j`>?d$_AGu4!okkWGCpHV4~33-euD2>Rtppy-JK>D1}@h~3-t%_>(LUwA0 z*b}_aT38alwKKGmE9wSIp{PGBkhlFSOVR*d&7Pu)B8<&i;V&|wdQPQ$n`n4!MhtM& zmkVrh=n2_zqFff0Zira;Z z%r`QEDSVuaKG=M_aW|~804Boq^vs91zP&Im%czMcRO07o!CC(LQ9Ir05mM_>aQc%<)?R@fz1#7Qqp?q* zr_ah$8T%F#)`r%F>y)}Fg5*LI%SL7F66FlS{-6{cgJv8^$oLskDD%FsD&nEjf>PSx=Ts%#jw%jE?di}inz_-Ae`)aLn)+skFT#^8qY7d&?Yz5oCK delta 716 zcmYL{T}YEr7{}l9DBEjId}sL4JDsFN`ikWe@gv2Jf|g4&)W{rV7s~|667r&ukc2)i za`jIlQbu+uL_$P^C}khcx#{NSMG0Mx>>{a)pqtK)mgnmC|3A<9AI@`*cQ)hwY=*lo zOTJWj)Fw&NxyRNnIkkUlcx#wRZguW$lP|NkRd(Q)ufZ6rA*sWmmv6g!LV3J-A8h{C z-K}`)?_^U*`7LZ7YM`EN;)X6C0|AZ947Ok%+L#J0q97Si>Aa3xX1H~Fd=4qF>y^Dc za%VNc8oW%?S-hnf^TBE!wH3U`Hqf!y#~xyC@d8;&MPy%T0UAOYl~f7YUX_CfTxJ~> zG8}54;#z^HYqN1^NuxVyY>;?c6kuEV?BL>?Est{N!o3K}CS)6gBcN~?o-IZ}j; zh>cv=1ezix=!vQn7!vZ&kO9+Cjb7a3vJBs&7P>dWu^G-4jf!t^EXSLbIx>!mhW1et zuPCTnRjGSSSR<=v=;vL5)#F8INhrvVsVonc*Z>twz%tPy%*~ied!BGvf$Df86;2B5 zoivK-yLfs_Jl;pYpNoQX%}Q%iA~t7wyQkKp82&w&Bvp7s@ruXH12pQN!Prc>81buw z%5w2B(MjJv;8UU$+qYD3HmR~77)!-I!QiFu`>6#sjw&u?ck~}VbW_&W^PcFZ0hBMf_ z*#&}a>@j`>?d$_AGu4!okkWGCpHV4~33-euD2>Rtppy-JK>D1}@h~3-t%_>(LUwA0 z*b}_aT38alwKKGmE9wSIp{PGBkhlFSOVR*d&7Pu)B8<&i;V&|wdQPQ$n`n4!MhtM& zmkVrh=n2_zqFff0Zira;Z z%r`QEDSVuaKG=M_aW|~804Boq^vs91zP&Im%czMcRO07o!CC(LQ9Ir05mM_>aQc%<)?R@fz1#7Qqp?q* zr_ah$8T%F#)`r%F>y)}Fg5*LI%SL7F66FlS{-6{cgJv8^$oLskDD%FsD&nEjf>PSx=Ts%#jw%jE?di}inz_-Ae`)aLn)+skFT#^8qY7d&?Yz5oCK delta 716 zcmYL{Ur3Wt7{R&z z$(JjSIwVOt_oS&^Uf;jjzuC_uzdAWpAI$VL%P#y1)*6!4B(=Kq@*RI?G@m!`gCo?u zyA{tuZEO_lAuF4K8m?g*xTz~ZPgtW;21g_xElhAa3xrSR+Y_#9Q>)GK>= z;z?cc2JKa3pHf$qSAX1j~6s{1Jeuj6!2o&TMcDZL0w#B8am?FXsH*kd`7Ir z9pt?(usB|Z&V)+gJ|X|~Wn(O%(aT$0mg9TEO80#n8{k>gsN^Z0E-M8UJ%M9ZThHg{~h#}-hs|2>!_RRl!wYM_({Xw*H9!Ev(~@$0n8 z^6)X;M&CZ*Q`&^>TPiraqOu?8UFoLKEWXYjb_4FUiF diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 55e3e0344d..c583957d2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Aug 08 09:18:56 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip diff --git a/gradlew b/gradlew index 4f0d4910d0..cccdd3d517 100755 --- a/gradlew +++ b/gradlew @@ -160,9 +160,6 @@ save () { echo " " } APP_ARGS=$(save "$@") -# Realm's work-around for a bug in Gradle 4.1 https://github.com/gradle/gradle/issues/2673 -APP_ARGS="${APP_ARGS} '--console=plain' \\ - " # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 556f8ae9f4..8b58216363 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-alpha4' + classpath 'com.android.tools.build:gradle:3.0.0-beta7' classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" } } diff --git a/library-benchmarks/gradle.properties b/library-benchmarks/gradle.properties new file mode 100644 index 0000000000..160890028a --- /dev/null +++ b/library-benchmarks/gradle.properties @@ -0,0 +1 @@ +org.gradle.caching=true diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.jar b/library-benchmarks/gradle/wrapper/gradle-wrapper.jar index 55420b622f26f9c74f567e7962fe5e50d3175f33..d457a1a990f3f8cee976589d405f62b13f1eede8 100644 GIT binary patch delta 678 zcmYL{T}YEr9LC@CH#Vcm-enH=PEo8(Uzs{%!-I!QiFu`>6#sjw&u?ck~}VbW_&W^PcFZ0hBMf_ z*#&}a>@j`>?d$_AGu4!okkWGCpHV4~33-euD2>Rtppy-JK>D1}@h~3-t%_>(LUwA0 z*b}_aT38alwKKGmE9wSIp{PGBkhlFSOVR*d&7Pu)B8<&i;V&|wdQPQ$n`n4!MhtM& zmkVrh=n2_zqFff0Zira;Z z%r`QEDSVuaKG=M_aW|~804Boq^vs91zP&Im%czMcRO07o!CC(LQ9Ir05mM_>aQc%<)?R@fz1#7Qqp?q* zr_ah$8T%F#)`r%F>y)}Fg5*LI%SL7F66FlS{-6{cgJv8^$oLskDD%FsD&nEjf>PSx=Ts%#jw%jE?di}inz_-Ae`)aLn)+skFT#^8qY7d&?Yz5oCK delta 716 zcmYL{T}YEr7{}l9DBHVId}mDU^^25fzGAsVrl#m9Xt`#F8d+9$u}qLGAukFEN$BGu zSN|j;Wn`B^G>B*rrR>AG58a$zl+g9aE|R(ky6JedJXgQ}|9Q^;aGrB~Ga2t^GTc^! ze5vxVO_HQDk6SwB^xlo3jUgtvRqxwIf98c&*@0jFdSjx7q;`j1zU}S~n|Sjc*aEFP zTk$l|!6uOoSlBGoU>#e>4P74kgBq1E*g_^anF=kYU?r&1Ssk}Z;MVE!Ijq30S9bHr z?bV2u&}Eu3@RnlChN^kgM(842L;HL$dkF9RIkJ?A$lkJi)Q2@HtrD`WDhCg^G#nH% z60WD>T7f5PvvFWSqdRsXU)!^ABCcR;L8V-WupG{8v^fg!g?AM>g;f}7B73L6!N_qk zcMFV1j!|aMHtKOGT1Q3~DqZ{WD5|mR@I@QRif?f=KYZ+@X{&zc~@ZdSP`023i1*vGr^MRr~GkP#+!w?o=|Dm6D})Ioot|j34!M) zjH3E3xw$2o?4{q&MWJW8g_b8pY|hkHPpwDE`uAXxRN)cDE1nV_pi$p6MyJhU#II5+ z%f-i32Yvg1PpL9&-BQ8n6_x$Kz)Bwl=J0jy2%nZl&;0QE_wh-}<+3bGo1gFhf8(bW R?3~Ccjr*&Wx}$+j^Djpr4FmuH diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index db87399dd3..c583957d2b 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Aug 08 09:18:59 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index fcb4ed43375e3df2bdccb1364a1605322b4c9419..d457a1a990f3f8cee976589d405f62b13f1eede8 100644 GIT binary patch delta 678 zcmYL{T}YEr9LC@CH#Vcm-enH=PEo8(Uzs{%!-I!QiFu`>6#sjw&u?ck~}VbW_&W^PcFZ0hBMf_ z*#&}a>@j`>?d$_AGu4!okkWGCpHV4~33-euD2>Rtppy-JK>D1}@h~3-t%_>(LUwA0 z*b}_aT38alwKKGmE9wSIp{PGBkhlFSOVR*d&7Pu)B8<&i;V&|wdQPQ$n`n4!MhtM& zmkVrh=n2_zqFff0Zira;Z z%r`QEDSVuaKG=M_aW|~804Boq^vs91zP&Im%czMcRO07o!CC(LQ9Ir05mM_>aQc%<)?R@fz1#7Qqp?q* zr_ah$8T%F#)`r%F>y)}Fg5*LI%SL7F66FlS{-6{cgJv8^$oLskDD%FsD&nEjf>PSx=Ts%#jw%jE?di}inz_-Ae`)aLn)+skFT#^8qY7d&?Yz5oCK delta 716 zcmYL{T}YEr7{}l9DBEi(zBByj^^25fzGAsV{7BJJ&~j;p8m*+}#WF!!3GJegkc2)i za`jIlQbu+uL_$P^C}khceYnldixRq)>>{a)pqq|I%X9Vn|DWgl59c|@JDc%-HpAVJ zC10*SYL+DF+~f9Md42!p$mR%>-0Ixh7GLH-hit_!Uy~tGM^d*{FW+|CLixOTAI$!a z-K}`)?_pC|_nX)})IcNKzztmih5{OuF_?q-=wvFisDgArrSm#&mBFpk<8w%XMX&7T zkvppe?ZE+>&f+blm=D(SsLkLdwubISJ9~(^#S3Jr5RvwZLNtXmDz6c;t0or@xXd~% zWH{7BrS$?&*XQ8Sl16tdLcX?S<5Wz+_>xL_R$*B?bI@fi!WZ6E(kZOsa4T7Q1vyG=k%)e4(4GaJP diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 79d8ff7b73..c583957d2b 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Aug 08 09:19:02 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 47c7f9ed1875b55e9f1d0cf619bf6d733bd7ff31..d457a1a990f3f8cee976589d405f62b13f1eede8 100644 GIT binary patch delta 678 zcmYL{T}YEr9LC@CH#Vcm-enH=PEo8(Uzs{%!-I!QiFu`>6#sjw&u?ck~}VbW_&W^PcFZ0hBMf_ z*#&}a>@j`>?d$_AGu4!okkWGCpHV4~33-euD2>Rtppy-JK>D1}@h~3-t%_>(LUwA0 z*b}_aT38alwKKGmE9wSIp{PGBkhlFSOVR*d&7Pu)B8<&i;V&|wdQPQ$n`n4!MhtM& zmkVrh=n2_zqFff0Zira;Z z%r`QEDSVuaKG=M_aW|~804Boq^vs91zP&Im%czMcRO07o!CC(LQ9Ir05mM_>aQc%<)?R@fz1#7Qqp?q* zr_ah$8T%F#)`r%F>y)}Fg5*LI%SL7F66FlS{-6{cgJv8^$oLskDD%FsD&nEjf>PSx=Ts%#jw%jE?di}inz_-Ae`)aLn)+skFT#^8qY7d&?Yz5oCK delta 716 zcmYL{T}YEr7{}l9DBHVId}mDU^^259U$M4Crl#m9Xt`#F8d*{+SSCo8kQar7B=m8S ztA7%aGO|k{5+V|!lzlk&p_|i-61pDQMN$_*H=P|V&(-h$f1dL{oada`>5TW&86K-a zzF2kGCP~tn$1R<5YVYRY<{*df0lU*_dj*@0iadSkqXq;`j1zU}D_nRxRa*!-eVHJj($lfV%AbgxE zx&_9<$0)OB8}&F8sUxEsRqp+G6w%mq%tji?>&C3R2FjX(hN#LkbVaYyaz9=U6=6MU zBlk6dh0#)U$5aXo2>E9q3zIR8Ufkre9N%LWx;MnJ2|Y_172o1mfj3K5G7gJ|w&6lv zQD9wGscS@7L(8Y==Usv3(IPY_739TLW`ZT&NBLv0j5P~$Bd*e}CtOy-oM@neae?Q@ zjiUN4k=~L>xajwDQRrD|p_K^{n=`r9Q|nc-{ymr^ReDA7inoLZXw*A}k*NwX;#WzP z<>F(qgT8&hr(_wnZmHn(s>*(#f3=tV^Y}V{gilMOXFkmM_VG!|<+3bG>CgB7zwy%w Rc24A!!u_=>-BEwq{0mhz4GsVR diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 4381dc5c6a..c583957d2b 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Aug 08 09:19:04 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip diff --git a/realm.properties b/realm.properties index 3d842610f2..b36f4827a1 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=4.1 +gradleVersion=4.2.1 ndkVersion=r10e diff --git a/realm/build.gradle b/realm/build.gradle index cb16b05480..3c49834f42 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-alpha4' + classpath 'com.android.tools.build:gradle:3.0.0-beta7' classpath 'de.undercouch:gradle-download-task:3.2.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' diff --git a/realm/gradle/wrapper/gradle-wrapper.jar b/realm/gradle/wrapper/gradle-wrapper.jar index 15c7b608fb9696e37a05af9a1456826ae80d140b..d457a1a990f3f8cee976589d405f62b13f1eede8 100644 GIT binary patch delta 678 zcmYL{T}YEr9LC@CH#Vcm-enH=PEo8(Uzs{%!-I!QiFu`>6#sjw&u?ck~}VbW_&W^PcFZ0hBMf_ z*#&}a>@j`>?d$_AGu4!okkWGCpHV4~33-euD2>Rtppy-JK>D1}@h~3-t%_>(LUwA0 z*b}_aT38alwKKGmE9wSIp{PGBkhlFSOVR*d&7Pu)B8<&i;V&|wdQPQ$n`n4!MhtM& zmkVrh=n2_zqFff0Zira;Z z%r`QEDSVuaKG=M_aW|~804Boq^vs91zP&Im%czMcRO07o!CC(LQ9Ir05mM_>aQc%<)?R@fz1#7Qqp?q* zr_ah$8T%F#)`r%F>y)}Fg5*LI%SL7F66FlS{-6{cgJv8^$oLskDD%FsD&nEjf>PSx=Ts%#jw%jE?di}inz_-Ae`)aLn)+skFT#^8qY7d&?Yz5oCK delta 716 zcmYL{T}YEr7{}l9DBEi(zBByj-KL~O`iiwB!;dCz6tr9}Lyat{b+Js4EFmun2}$VV zB3J(;B4uQkLL^8eL@E1l&P_L`7bSEp*+o(pK{p+bmgnmC|3A<9AI@{m^hCz{i41>D zmVCMLs8f=pb59&?^7{VGzRf-+`PIoOdoa`6B)jn|SZ7F9lhoqY%Xj=8(R|*#56)22 z?p8btwX#vHhpcP{YPgnd;HIts-C>Q28Jv-PG&2=iT)|3MrSm#&6~nL7<8xGjORwzZ zkvq!{N8}2PW$_j>W+E0IwHdj@*3dHNWsfj9cY&;>BGOx0h`OjoB~?Ots&es=%dEpf z#-ep(wh26K%fX>}jqbXHeB;W-sf2=|d6n|q!g4p~z~e5$7v5!R7M3yAK(01{y|I&2 z-XSm%J3*P9JE+Hzg<3NBQ0Y5}#|s*}f$0T11$>zHRYO@-P#;&BhW7Y1TI#{8eiPQ> zPV!wBXpEPjBcW2bSI9rT*%(V`^zs&$W%!=3(*1sp4d`6d$b6e)Io>YTkYPYHcm|BT zqM&9;rS?H#^)H>FpZ5e>hD>NoD=0{+EFacnHx&-UI@~DCjigF@o^n|MORAoVMg(3Q zF^KBB)Yg_%%1ggrh(hPGgO*1{Z0^{0k1e2N|9db=stAbU)j%;1(5P!1gX865#IMsT z%frWXD}DQbPw7%@-%`QZ6_x!!&q^1CX7P3Q7@w9#&x4o@9^jLd%Vb%Wwm#qg|He-% R+C7oeIv%W6>W+uD%)d{Q4GI7N diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index c3448f25a5..c583957d2b 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Aug 08 09:19:01 JST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ab6372f925..ca3040a388 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -157,6 +157,14 @@ project.afterEvaluate { tasks.withType(JavaCompile) { options.compilerArgs << '-Werror' } + + tasks.all { task -> + android.productFlavors.all { flavor -> + if (task.name == "publish${flavor.name.capitalize()}PublicationPublicationToMavenLocal") { + task.dependsOn "assemble${flavor.name.capitalize()}" + } + } + } } // enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out/ @@ -583,6 +591,17 @@ project.afterEvaluate { if (project.hasProperty('buildTargetABIs') && project.getProperty('buildTargetABIs').trim().isEmpty()) { variant.externalNativeBuildTasks[0].enabled = false } + + // all Java files must be compiled before native build + android.libraryVariants.all { anotherVariant -> + if (variant.flavorName == anotherVariant.flavorName) { + variant.externalNativeBuildTasks[0].dependsOn("compile${anotherVariant.name.capitalize()}JavaWithJavac") + } + } + // as of android gradle plugin 3.0.0-alpha5, generateJsonModel* triggers native build. Java files must be compiled before them. + android.buildTypes.all { buildType -> + tasks["generateJsonModel${variant.name.capitalize()}"].dependsOn "compile${variant.flavorName.capitalize()}${buildType.name.capitalize()}JavaWithJavac" + } } } diff --git a/tools/update_gradle_wrapper.sh b/tools/update_gradle_wrapper.sh index b20f7006b1..224817cf23 100755 --- a/tools/update_gradle_wrapper.sh +++ b/tools/update_gradle_wrapper.sh @@ -10,8 +10,6 @@ HERE=`pwd` cd "$(dirname $0)/.." -pushd . - for i in $(find $(pwd) -type f -name gradlew); do cd $(dirname $i) pwd @@ -19,13 +17,5 @@ for i in $(find $(pwd) -type f -name gradlew); do sed -E -i '' s/-bin\\.zip\$/-all.zip/ gradle/wrapper/gradle-wrapper.properties done -popd - -sed -i '' '/^APP_ARGS=/a\ -# Realm'"'"'s work-around for a bug in Gradle 4.1 https://github.com/gradle/gradle/issues/2673\ -APP_ARGS="\${APP_ARGS} '"'"'--console=plain'"'"' \\\\\ -\ "\ -' gradlew - cd $HERE From 4ec283c20cc0ebfc213f02aec862a15b1fb53712 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 12 Oct 2017 12:20:38 +0200 Subject: [PATCH 1014/2110] Use Context if ApplicationContext if not available (#5406) --- CHANGELOG.md | 2 ++ realm/realm-library/src/main/java/io/realm/Realm.java | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b0c80c1ff..12cc9dc980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ## Bug Fixes +* Don't try to acquire `ApplicationContext` in `Realm.init(Context)` (#5389). + ## Internal ## Credits diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index b5416f96e4..65681c1767 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -246,7 +246,11 @@ public static synchronized void init(Context context) { RealmCore.loadLibrary(context); setDefaultConfiguration(new RealmConfiguration.Builder(context).build()); ObjectServerFacade.getSyncFacadeIfPossible().init(context); - BaseRealm.applicationContext = context.getApplicationContext(); + if (context.getApplicationContext() != null) { + BaseRealm.applicationContext = context.getApplicationContext(); + } else { + BaseRealm.applicationContext = context; + } SharedRealm.initialize(new File(context.getFilesDir(), ".realm.temp")); } } From 4c56aaaddef6e5ac7278d257ee0a0569eb9f72b6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 12 Oct 2017 14:38:40 +0200 Subject: [PATCH 1015/2110] Setting own list back on an object accidentially cleared it (#5396) * Add unit test showing setting own list does not work. * Do not clear own list if given as input. * Clear in the correct place * Add test for copyToRealmOrUpdate * Unit test for insertOrUpdate * Add test for insertOrUpdate bulk and non-bulk * Fix wrong comparison * Wrong spelling * Update changelog * Fix mistakes. Move check out of proxy classes. * Dont always check for managed objects * Always check for managed state. * Better changelog * Correct check for unmanaged objects. Optimized loops. * Optimize `insert` loops * More loop optimizations --- CHANGELOG.md | 3 +- .../processor/RealmProxyClassGenerator.java | 186 +++++++++++------- .../io/realm/AllTypesRealmProxy.java | 127 +++++++----- .../io/realm/NullTypesRealmProxy.java | 54 +++-- .../java/io/realm/BulkInsertTests.java | 21 ++ .../java/io/realm/RealmObjectTests.java | 31 +++ .../androidTest/java/io/realm/RealmTests.java | 13 ++ .../src/main/java/io/realm/ProxyState.java | 16 ++ 8 files changed, 300 insertions(+), 151 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12cc9dc980..6342d17cd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ ## Bug Fixes -* Don't try to acquire `ApplicationContext` in `Realm.init(Context)` (#5389). +* Assigning a managed object's own list to itself would accidentally clear it (#5395). +* Don't try to acquire `ApplicationContext` if not available in `Realm.init(Context)` (#5389). ## Internal diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index e1ea070565..21152a4ab4 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -477,12 +477,7 @@ public void emit(JavaWriter writer) throws IOException { .emitStatement("row.nullifyLink(%s)", fieldIndexVariableReference(field)) .emitStatement("return") .endControlFlow(); - writer.beginControlFlow("if (!RealmObject.isValid(value))") - .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") - .endControlFlow(); - writer.beginControlFlow("if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") - .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") - .endControlFlow(); + writer.emitStatement("proxyState.checkValidObject(value)"); writer.emitStatement("row.getTable().setLink(%s, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true)", fieldIndexVariableReference(field)); writer.emitStatement("return"); @@ -493,12 +488,7 @@ public void emit(JavaWriter writer) throws IOException { .emitStatement("proxyState.getRow$realm().nullifyLink(%s)", fieldIndexVariableReference(field)) .emitStatement("return") .endControlFlow() - .beginControlFlow("if (!(RealmObject.isManaged(value) && RealmObject.isValid(value)))") - .emitStatement("throw new IllegalArgumentException(\"'value' is not a valid managed object.\")") - .endControlFlow() - .beginControlFlow("if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") - .emitStatement("throw new IllegalArgumentException(\"'value' belongs to a different Realm.\")") - .endControlFlow() + .emitStatement("proxyState.checkValidObject(value)") .emitStatement("proxyState.getRow$realm().setLink(%s, ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex())", fieldIndexVariableReference(field)) .endMethod(); } @@ -572,37 +562,52 @@ public void emit(JavaWriter writer) throws IOException { // LinkView currently does not support default value feature. Just fallback to normal code. } }); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - if (Utils.isRealmModelList(field)) { - writer.emitStatement("OsList osList = proxyState.getRow$realm().getModelList(%s)", - fieldIndexVariableReference(field)); - } else { - writer.emitStatement("OsList osList = proxyState.getRow$realm().getValueList(%1$s, RealmFieldType.%2$s)", - fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name()); - } - writer.emitStatement("osList.removeAll()") - .beginControlFlow("if (value == null)") - .emitStatement("return") - .endControlFlow(); + writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); + if (Utils.isRealmModelList(field)) { + writer.emitStatement("OsList osList = proxyState.getRow$realm().getModelList(%s)", + fieldIndexVariableReference(field)); + } else { + writer.emitStatement("OsList osList = proxyState.getRow$realm().getValueList(%1$s, RealmFieldType.%2$s)", + fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name()); + } if (forRealmModel) { - writer.beginControlFlow("for (RealmModel linkedObject : value)") - .beginControlFlow("if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject)))") - .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must be a valid managed object.\")") + // Model lists. + writer + .emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") + .beginControlFlow("if (value != null && value.size() == osList.size())") + .emitStatement("int objects = value.size()") + .beginControlFlow("for (int i = 0; i < objects; i++)") + .emitStatement("%s linkedObject = value.get(i)", genericType) + .emitStatement("proxyState.checkValidObject(linkedObject)") + .emitStatement("osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex())") .endControlFlow() - .beginControlFlow("if (((RealmObjectProxy) linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm())") - .emitStatement("throw new IllegalArgumentException(\"Each element of 'value' must belong to the same Realm.\")") + .nextControlFlow("else") + .emitStatement("osList.removeAll()") + .beginControlFlow("if (value == null)") + .emitStatement("return") .endControlFlow() - .emitStatement("osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex())") - .endControlFlow(); + .emitStatement("int objects = value.size()") + .beginControlFlow("for (int i = 0; i < objects; i++)") + .emitStatement("%s linkedObject = value.get(i)", genericType) + .emitStatement("proxyState.checkValidObject(linkedObject)") + .emitStatement("osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex())") + .endControlFlow() + .endControlFlow(); } else { - writer.beginControlFlow("for (%1$s item : value)", genericType) + // Value lists + writer + .emitStatement("osList.removeAll()") + .beginControlFlow("if (value == null)") + .emitStatement("return") + .endControlFlow() + .beginControlFlow("for (%1$s item : value)", genericType) .beginControlFlow("if (item == null)") - .emitStatement(metadata.isElementNullable(field) ? "osList.addNull()" : "throw new IllegalArgumentException(\"Storing 'null' into " + fieldName + "' is not allowed by the schema.\")") + .emitStatement(metadata.isElementNullable(field) ? "osList.addNull()" : "throw new IllegalArgumentException(\"Storing 'null' into " + fieldName + "' is not allowed by the schema.\")") .nextControlFlow("else") - .emitStatement(getStatementForAppendingValueToOsList("osList", "item", elementTypeMirror)) + .emitStatement(getStatementForAppendingValueToOsList("osList", "item", elementTypeMirror)) .endControlFlow() - .endControlFlow(); + .endControlFlow(); } writer.endMethod(); @@ -1287,21 +1292,33 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer - .emitEmptyLine() - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) + .emitEmptyLine() + .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) + .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + .beginControlFlow("if (%1$sList != null && %1$sList.size() == %1$sOsList.size())", fieldName) + .emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") + .emitStatement("int objects = %1$sList.size()", fieldName) + .beginControlFlow("for (int i = 0; i < objects; i++)") + .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) + .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) + .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .emitStatement("%1$sOsList.setRow(i, cacheItemIndex%1$s)", fieldName) + .endControlFlow() + .nextControlFlow("else") .emitStatement("%1$sOsList.removeAll()", fieldName) - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", - genericType, fieldName, interfaceName, getter) .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("%1$sOsList.addRow(cacheItemIndex%1$s)", fieldName) - .endControlFlow() + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) + .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .emitStatement("%1$sOsList.addRow(cacheItemIndex%1$s)", fieldName) + .endControlFlow() .endControlFlow() - .emitEmptyLine(); + .endControlFlow() + .emitEmptyLine(); } else if (Utils.isRealmValueList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); @@ -1390,21 +1407,33 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer - .emitEmptyLine() - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) + .emitEmptyLine() + .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) + .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + .beginControlFlow("if (%1$sList != null && %1$sList.size() == %1$sOsList.size())", fieldName) + .emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") + .emitStatement("int objectCount = %1$sList.size()", fieldName) + .beginControlFlow("for (int i = 0; i < objectCount; i++)") + .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) + .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) + .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .emitStatement("%1$sOsList.setRow(i, cacheItemIndex%1$s)", fieldName) + .endControlFlow() + .nextControlFlow("else") .emitStatement("%1$sOsList.removeAll()", fieldName) - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", - genericType, fieldName, interfaceName, getter) .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("%1$sOsList.addRow(cacheItemIndex%1$s)", fieldName) - .endControlFlow() + .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) + .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) + .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .emitStatement("%1$sOsList.addRow(cacheItemIndex%1$s)", fieldName) + .endControlFlow() .endControlFlow() - .emitEmptyLine(); + .endControlFlow() + .emitEmptyLine(); } else if (Utils.isRealmValueList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); @@ -1714,23 +1743,34 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer - .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) - .emitStatement("RealmList<%s> %sRealmList = realmObjectTarget.%s()", - genericType, fieldName, getter) + .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) + .emitStatement("RealmList<%s> %sRealmList = realmObjectTarget.%s()", genericType, fieldName, getter) + .beginControlFlow("if (%1$sList != null && %1$sList.size() == %1$sRealmList.size())", fieldName) + .emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") + .emitStatement("int objects = %sList.size()", fieldName) + .beginControlFlow("for (int i = 0; i < objects; i++)") + .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) + .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) + .beginControlFlow("if (cache%s != null)", fieldName) + .emitStatement("%1$sRealmList.set(i, cache%1$s)", fieldName) + .nextControlFlow("else") + .emitStatement("%1$sRealmList.set(i, %2$s.copyOrUpdate(realm, %1$sItem, true, cache))", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .endControlFlow() + .nextControlFlow("else") .emitStatement("%sRealmList.clear()", fieldName) .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) - .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%1$sRealmList.add(cache%1$s)", fieldName) - .nextControlFlow("else") - .emitStatement("%1$sRealmList.add(%2$s.copyOrUpdate(realm, %1$sItem, true, cache))", - fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() + .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) + .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) + .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) + .beginControlFlow("if (cache%s != null)", fieldName) + .emitStatement("%1$sRealmList.add(cache%1$s)", fieldName) + .nextControlFlow("else") + .emitStatement("%1$sRealmList.add(%2$s.copyOrUpdate(realm, %1$sItem, true, cache))", fieldName, Utils.getProxyClassSimpleName(field)) + .endControlFlow() + .endControlFlow() .endControlFlow() - .endControlFlow(); - + .endControlFlow(); } else if (Utils.isRealmValueList(field)) { writer.emitStatement("realmObjectTarget.%s(realmObjectSource.%s())", setter, getter); } else if (Utils.isMutableRealmInteger(field)) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 72a21aa73c..5449d4a78f 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -380,12 +380,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { row.nullifyLink(columnInfo.columnObjectIndex); return; } - if (!RealmObject.isValid(value)) { - throw new IllegalArgumentException("'value' is not a valid managed object."); - } - if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { - throw new IllegalArgumentException("'value' belongs to a different Realm."); - } + proxyState.checkValidObject(value); row.getTable().setLink(columnInfo.columnObjectIndex, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true); return; } @@ -395,12 +390,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { proxyState.getRow$realm().nullifyLink(columnInfo.columnObjectIndex); return; } - if (!(RealmObject.isManaged(value) && RealmObject.isValid(value))) { - throw new IllegalArgumentException("'value' is not a valid managed object."); - } - if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { - throw new IllegalArgumentException("'value' belongs to a different Realm."); - } + proxyState.checkValidObject(value); proxyState.getRow$realm().setLink(columnInfo.columnObjectIndex, ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex()); } @@ -443,18 +433,25 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { proxyState.getRealm$realm().checkIfValid(); OsList osList = proxyState.getRow$realm().getModelList(columnInfo.columnRealmListIndex); - osList.removeAll(); - if (value == null) { - return; - } - for (RealmModel linkedObject : value) { - if (!(RealmObject.isManaged(linkedObject) && RealmObject.isValid(linkedObject))) { - throw new IllegalArgumentException("Each element of 'value' must be a valid managed object."); + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + if (value != null && value.size() == osList.size()) { + int objects = value.size(); + for (int i = 0; i < objects; i++) { + some.test.AllTypes linkedObject = value.get(i); + proxyState.checkValidObject(linkedObject); + osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex()); + } + } else { + osList.removeAll(); + if (value == null) { + return; } - if (((RealmObjectProxy) linkedObject).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { - throw new IllegalArgumentException("Each element of 'value' must belong to the same Realm."); + int objects = value.size(); + for (int i = 0; i < objects; i++) { + some.test.AllTypes linkedObject = value.get(i); + proxyState.checkValidObject(linkedObject); + osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex()); } - osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex()); } } @@ -1045,16 +1042,16 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON } } } - // TODO implement logic for value listcolumnStringList. - // TODO implement logic for value listcolumnBinaryList. - // TODO implement logic for value listcolumnBooleanList. - // TODO implement logic for value listcolumnLongList. - // TODO implement logic for value listcolumnIntegerList. - // TODO implement logic for value listcolumnShortList. - // TODO implement logic for value listcolumnByteList. - // TODO implement logic for value listcolumnDoubleList. - // TODO implement logic for value listcolumnFloatList. - // TODO implement logic for value listcolumnDateList. + // TODO implement logic for value list columnStringList. + // TODO implement logic for value list columnBinaryList. + // TODO implement logic for value list columnBooleanList. + // TODO implement logic for value list columnLongList. + // TODO implement logic for value list columnIntegerList. + // TODO implement logic for value list columnShortList. + // TODO implement logic for value list columnByteList. + // TODO implement logic for value list columnDoubleList. + // TODO implement logic for value list columnFloatList. + // TODO implement logic for value list columnDateList. return obj; } @@ -1714,15 +1711,28 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); - if (columnRealmListList != null) { - for (some.test.AllTypes columnRealmListItem : columnRealmListList) { + if (columnRealmListList != null && columnRealmListList.size() == columnRealmListOsList.size()) { + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + int objects = columnRealmListList.size(); + for (int i = 0; i < objects; i++) { + some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); } - columnRealmListOsList.addRow(cacheItemIndexcolumnRealmList); + columnRealmListOsList.setRow(i, cacheItemIndexcolumnRealmList); + } + } else { + columnRealmListOsList.removeAll(); + if (columnRealmListList != null) { + for (some.test.AllTypes columnRealmListItem : columnRealmListList) { + Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); + if (cacheItemIndexcolumnRealmList == null) { + cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); + } + columnRealmListOsList.addRow(cacheItemIndexcolumnRealmList); + } } } @@ -1930,15 +1940,28 @@ public static void insertOrUpdate(Realm realm, Iterator ob } OsList columnRealmListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnRealmListIndex); - columnRealmListOsList.removeAll(); RealmList columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); - if (columnRealmListList != null) { - for (some.test.AllTypes columnRealmListItem : columnRealmListList) { + if (columnRealmListList != null && columnRealmListList.size() == columnRealmListOsList.size()) { + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + int objectCount = columnRealmListList.size(); + for (int i = 0; i < objectCount; i++) { + some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); } - columnRealmListOsList.addRow(cacheItemIndexcolumnRealmList); + columnRealmListOsList.setRow(i, cacheItemIndexcolumnRealmList); + } + } else { + columnRealmListOsList.removeAll(); + if (columnRealmListList != null) { + for (some.test.AllTypes columnRealmListItem : columnRealmListList) { + Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); + if (cacheItemIndexcolumnRealmList == null) { + cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); + } + columnRealmListOsList.addRow(cacheItemIndexcolumnRealmList); + } } } @@ -2187,15 +2210,29 @@ static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, so } RealmList columnRealmListList = realmObjectSource.realmGet$columnRealmList(); RealmList columnRealmListRealmList = realmObjectTarget.realmGet$columnRealmList(); - columnRealmListRealmList.clear(); - if (columnRealmListList != null) { - for (int i = 0; i < columnRealmListList.size(); i++) { + if (columnRealmListList != null && columnRealmListList.size() == columnRealmListRealmList.size()) { + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + int objects = columnRealmListList.size(); + for (int i = 0; i < objects; i++) { some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); if (cachecolumnRealmList != null) { - columnRealmListRealmList.add(cachecolumnRealmList); + columnRealmListRealmList.set(i, cachecolumnRealmList); } else { - columnRealmListRealmList.add(AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); + columnRealmListRealmList.set(i, AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); + } + } + } else { + columnRealmListRealmList.clear(); + if (columnRealmListList != null) { + for (int i = 0; i < columnRealmListList.size(); i++) { + some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); + some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); + if (cachecolumnRealmList != null) { + columnRealmListRealmList.add(cachecolumnRealmList); + } else { + columnRealmListRealmList.add(AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); + } } } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 43544b3e1e..d95445f615 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -902,12 +902,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { row.nullifyLink(columnInfo.fieldObjectNullIndex); return; } - if (!RealmObject.isValid(value)) { - throw new IllegalArgumentException("'value' is not a valid managed object."); - } - if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { - throw new IllegalArgumentException("'value' belongs to a different Realm."); - } + proxyState.checkValidObject(value); row.getTable().setLink(columnInfo.fieldObjectNullIndex, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true); return; } @@ -917,12 +912,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { proxyState.getRow$realm().nullifyLink(columnInfo.fieldObjectNullIndex); return; } - if (!(RealmObject.isManaged(value) && RealmObject.isValid(value))) { - throw new IllegalArgumentException("'value' is not a valid managed object."); - } - if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != proxyState.getRealm$realm()) { - throw new IllegalArgumentException("'value' belongs to a different Realm."); - } + proxyState.checkValidObject(value); proxyState.getRow$realm().setLink(columnInfo.fieldObjectNullIndex, ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex()); } @@ -1996,26 +1986,26 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO objProxy.realmSet$fieldObjectNull(fieldObjectNullObj); } } - // TODO implement logic for value listfieldStringListNotNull. - // TODO implement logic for value listfieldStringListNull. - // TODO implement logic for value listfieldBinaryListNotNull. - // TODO implement logic for value listfieldBinaryListNull. - // TODO implement logic for value listfieldBooleanListNotNull. - // TODO implement logic for value listfieldBooleanListNull. - // TODO implement logic for value listfieldLongListNotNull. - // TODO implement logic for value listfieldLongListNull. - // TODO implement logic for value listfieldIntegerListNotNull. - // TODO implement logic for value listfieldIntegerListNull. - // TODO implement logic for value listfieldShortListNotNull. - // TODO implement logic for value listfieldShortListNull. - // TODO implement logic for value listfieldByteListNotNull. - // TODO implement logic for value listfieldByteListNull. - // TODO implement logic for value listfieldDoubleListNotNull. - // TODO implement logic for value listfieldDoubleListNull. - // TODO implement logic for value listfieldFloatListNotNull. - // TODO implement logic for value listfieldFloatListNull. - // TODO implement logic for value listfieldDateListNotNull. - // TODO implement logic for value listfieldDateListNull. + // TODO implement logic for value list fieldStringListNotNull. + // TODO implement logic for value list fieldStringListNull. + // TODO implement logic for value list fieldBinaryListNotNull. + // TODO implement logic for value list fieldBinaryListNull. + // TODO implement logic for value list fieldBooleanListNotNull. + // TODO implement logic for value list fieldBooleanListNull. + // TODO implement logic for value list fieldLongListNotNull. + // TODO implement logic for value list fieldLongListNull. + // TODO implement logic for value list fieldIntegerListNotNull. + // TODO implement logic for value list fieldIntegerListNull. + // TODO implement logic for value list fieldShortListNotNull. + // TODO implement logic for value list fieldShortListNull. + // TODO implement logic for value list fieldByteListNotNull. + // TODO implement logic for value list fieldByteListNull. + // TODO implement logic for value list fieldDoubleListNotNull. + // TODO implement logic for value list fieldDoubleListNull. + // TODO implement logic for value list fieldFloatListNotNull. + // TODO implement logic for value list fieldFloatListNull. + // TODO implement logic for value list fieldDateListNotNull. + // TODO implement logic for value list fieldDateListNull. return obj; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java index e8c659fdaf..42ece9c78d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java @@ -912,6 +912,27 @@ public void insertOrUpdate_shouldNotClearRealmList() { assertEquals(1, allTypes.getColumnRealmList().size()); } + @Test + public void insertOrUpdate_ownList() { + realm.beginTransaction(); + AllJavaTypes managedObj = realm.createObject(AllJavaTypes.class, 1); + managedObj.getFieldList().add(managedObj); + AllJavaTypes unmanagedObj = realm.copyFromRealm(managedObj); + unmanagedObj.setFieldList(managedObj.getFieldList()); + + // Check single object insert + realm.insertOrUpdate(unmanagedObj); + managedObj = realm.where(AllJavaTypes.class).findFirst(); + assertEquals(1, managedObj.getFieldList().size()); + assertEquals(1, managedObj.getFieldList().first().getFieldId()); + + // Check collection insert + realm.insertOrUpdate(Arrays.asList(unmanagedObj)); + managedObj = realm.where(AllJavaTypes.class).findFirst(); + assertEquals(1, managedObj.getFieldList().size()); + assertEquals(1, managedObj.getFieldList().first().getFieldId()); + } + @Test public void insert_collectionOfManagedObjects() { realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index be4bc94e1f..667336ce69 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -908,6 +908,37 @@ public void run() { thread.join(); } + @Test + public void setter_list_ownList() { + // Create initial list + realm.beginTransaction(); + RealmList allTypesRealmModels = new RealmList<>(); + for (int i = 0; i < 2; i++) { + allTypesRealmModels.add(new AllJavaTypes(i)); + } + AllJavaTypes model = new AllJavaTypes(2); + model.setFieldList(allTypesRealmModels); + model = realm.copyToRealm(model); + realm.commitTransaction(); + assertEquals(2, model.getFieldList().size()); + + // Check that setting own list does not clear it by accident. + realm.beginTransaction(); + model.setFieldList(model.getFieldList()); + realm.commitTransaction(); + assertEquals(2, model.getFieldList().size()); + + // Check that a unmanaged list throws the correct exception + realm.beginTransaction(); + RealmList unmanagedList = new RealmList<>(); + unmanagedList.addAll(realm.copyFromRealm(model.getFieldList())); + try { + model.setFieldList(unmanagedList); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + @Test public void classNameConflictsWithFrameworkClass() { // The model class' name (Thread) clashed with a common Java class. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 37d51b8bcd..463d43a410 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -1810,6 +1810,19 @@ public void execute(Realm realm) { assertEquals(new Date(3L), obj.getColumnDateList().get(1)); } + @Test + public void copyToRealmOrUpdate_overrideOwnList() { + realm.beginTransaction(); + AllJavaTypes managedObj = realm.createObject(AllJavaTypes.class, 1); + managedObj.getFieldList().add(managedObj); + AllJavaTypes unmanagedObj = realm.copyFromRealm(managedObj); + unmanagedObj.setFieldList(managedObj.getFieldList()); + + managedObj = realm.copyToRealmOrUpdate(unmanagedObj); + assertEquals(1, managedObj.getFieldList().size()); + assertEquals(1, managedObj.getFieldList().first().getFieldId()); + } + @Test public void copyToRealmOrUpdate_cyclicObject() { CyclicTypePrimaryKey oneCyclicType = new CyclicTypePrimaryKey(1); diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index a47679b12b..7ebdce93e2 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -22,6 +22,7 @@ import io.realm.internal.ObserverPairList; import io.realm.internal.PendingRow; +import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.OsObject; import io.realm.internal.UncheckedRow; @@ -200,4 +201,19 @@ public void onQueryFinished(Row row) { registerToObjectNotifier(); } } + + /** + * Check that object is a valid and managed object by this Realm. + * Used by proxy classes to verify input. + * + * @param value model object + */ + public void checkValidObject(RealmModel value) { + if (!RealmObject.isValid(value) || !RealmObject.isManaged(value)) { + throw new IllegalArgumentException("'value' is not a valid managed object."); + } + if (((RealmObjectProxy) value).realmGet$proxyState().getRealm$realm() != getRealm$realm()) { + throw new IllegalArgumentException("'value' belongs to a different Realm."); + } + } } From 2cfc1eccfad134a9d162f7cd83ef5ecc9c0a0da7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 12 Oct 2017 20:34:38 +0200 Subject: [PATCH 1016/2110] Fix some performance issues when initializing the Schema (#5404) --- CHANGELOG.md | 1 + .../processor/RealmProxyClassGenerator.java | 10 +++- .../RealmProxyMediatorGenerator.java | 4 +- .../io/realm/AllTypesRealmProxy.java | 4 +- .../io/realm/BooleansRealmProxy.java | 7 ++- .../io/realm/NullTypesRealmProxy.java | 4 +- .../io/realm/RealmDefaultModuleMediator.java | 4 +- .../resources/io/realm/SimpleRealmProxy.java | 6 +- .../io/realm/LinkingObjectsManagedTests.java | 8 +-- .../java/io/realm/internal/OsListTests.java | 2 +- .../io/realm/SyncedRealmMigrationTests.java | 2 +- .../io_realm_internal_OsObjectSchemaInfo.cpp | 42 ++++++++----- .../io/realm/internal/OsObjectSchemaInfo.java | 60 ++++++++++++------- .../main/java/io/realm/internal/Property.java | 22 ++----- 14 files changed, 101 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6342d17cd3..4c3e44376c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Enhancements * All Realm annotations are now kept at runtime, allowing runtime tools access to them (#5344). +* Speedup schema initialization when a Realm file is first accessed (#5391). ## Bug Fixes diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 21152a4ab4..71bb69b86e 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -244,7 +244,7 @@ private void emitClassFields(JavaWriter writer) throws IOException { writer.emitField("List", "FIELD_NAMES", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)); writer.beginInitializer(true) - .emitStatement("List fieldNames = new ArrayList()"); + .emitStatement("List fieldNames = new ArrayList(%s)", metadata.getFields().size()); for (VariableElement field : metadata.getFields()) { writer.emitStatement("fieldNames.add(\"%s\")", field.getSimpleName().toString()); } @@ -728,8 +728,14 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce "createExpectedObjectSchemaInfo", // Method name EnumSet.of(Modifier.PRIVATE, Modifier.STATIC)); // Modifiers + // Guess capacity for Arrays used by OsObjectSchemaInfo. + // Used to prevent array resizing at runtime + int persistedFields = metadata.getFields().size(); + int computedFields = metadata.getBacklinkFields().size(); + writer.emitStatement( - "OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\")", this.simpleClassName); + "OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\", %s, %s)", + this.simpleClassName, persistedFields, computedFields); // For each field generate corresponding table index constant for (VariableElement field : metadata.getFields()) { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index af3fc09c2c..bd4476fc9f 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -119,7 +119,7 @@ public void generate() throws IOException { private void emitFields(JavaWriter writer) throws IOException { writer.emitField("Set>", "MODEL_CLASSES", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)); writer.beginInitializer(true); - writer.emitStatement("Set> modelClasses = new HashSet>()"); + writer.emitStatement("Set> modelClasses = new HashSet>(%s)", qualifiedModelClasses.size()); for (String clazz : qualifiedModelClasses) { writer.emitStatement("modelClasses.add(%s.class)", clazz); } @@ -137,7 +137,7 @@ private void emitGetExpectedObjectSchemaInfoMap(JavaWriter writer) throws IOExce writer.emitStatement( "Map, OsObjectSchemaInfo> infoMap = " + - "new HashMap, OsObjectSchemaInfo>()"); + "new HashMap, OsObjectSchemaInfo>(%s)", qualifiedProxyClasses.size()); for (int i = 0; i < qualifiedProxyClasses.size(); i++) { writer.emitStatement("infoMap.put(%s.class, %s.getExpectedObjectSchemaInfo())", qualifiedModelClasses.get(i), qualifiedProxyClasses.get(i)); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 5449d4a78f..0a20dd36a9 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -124,7 +124,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); private static final List FIELD_NAMES; static { - List fieldNames = new ArrayList(); + List fieldNames = new ArrayList(20); fieldNames.add("columnString"); fieldNames.add("columnLong"); fieldNames.add("columnFloat"); @@ -857,7 +857,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes"); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", 20, 1); builder.addPersistedProperty("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index ccf71948d8..fa4b8282c7 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -74,7 +74,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); private static final List FIELD_NAMES; static { - List fieldNames = new ArrayList(); + List fieldNames = new ArrayList(4); fieldNames.add("done"); fieldNames.add("isReady"); fieldNames.add("mCompleted"); @@ -192,7 +192,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Booleans"); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Booleans", 4, 0); builder.addPersistedProperty("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -221,6 +221,7 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON throws JSONException { final List excludeFields = Collections. emptyList(); some.test.Booleans obj = realm.createObjectInternal(some.test.Booleans.class, true, excludeFields); + final BooleansRealmProxyInterface objProxy = (BooleansRealmProxyInterface) obj; if (json.has("done")) { if (json.isNull("done")) { @@ -439,6 +440,7 @@ public static some.test.Booleans createDetachedCopy(some.test.Booleans realmObje unmanagedCopy.realmSet$isReady(realmSource.realmGet$isReady()); unmanagedCopy.realmSet$mCompleted(realmSource.realmGet$mCompleted()); unmanagedCopy.realmSet$anotherBoolean(realmSource.realmGet$anotherBoolean()); + return unmanagedObject; } @@ -504,5 +506,4 @@ public boolean equals(Object o) { return true; } - } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index d95445f615..020e091216 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -185,7 +185,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); private static final List FIELD_NAMES; static { - List fieldNames = new ArrayList(); + List fieldNames = new ArrayList(41); fieldNames.add("fieldStringNotNull"); fieldNames.add("fieldStringNull"); fieldNames.add("fieldBooleanNotNull"); @@ -1697,7 +1697,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NullTypes"); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NullTypes", 41, 0); builder.addPersistedProperty("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index a38f5851e4..72344ed633 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -27,7 +27,7 @@ class DefaultRealmModuleMediator extends RealmProxyMediator { private static final Set> MODEL_CLASSES; static { - Set> modelClasses = new HashSet>(); + Set> modelClasses = new HashSet>(1); modelClasses.add(some.test.AllTypes.class); MODEL_CLASSES = Collections.unmodifiableSet(modelClasses); } @@ -35,7 +35,7 @@ class DefaultRealmModuleMediator extends RealmProxyMediator { @Override public Map, OsObjectSchemaInfo> getExpectedObjectSchemaInfoMap() { Map, OsObjectSchemaInfo> infoMap = - new HashMap, OsObjectSchemaInfo>(); + new HashMap, OsObjectSchemaInfo>(1); infoMap.put(some.test.AllTypes.class, io.realm.AllTypesRealmProxy.getExpectedObjectSchemaInfo()); return infoMap; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 46f59410df..9266f2c253 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -68,7 +68,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); private static final List FIELD_NAMES; static { - List fieldNames = new ArrayList(); + List fieldNames = new ArrayList(2); fieldNames.add("name"); fieldNames.add("age"); FIELD_NAMES = Collections.unmodifiableList(fieldNames); @@ -148,7 +148,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Simple"); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Simple", 2, 0); builder.addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); return builder.build(); @@ -175,6 +175,7 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb throws JSONException { final List excludeFields = Collections. emptyList(); some.test.Simple obj = realm.createObjectInternal(some.test.Simple.class, true, excludeFields); + final SimpleRealmProxyInterface objProxy = (SimpleRealmProxyInterface) obj; if (json.has("name")) { if (json.isNull("name")) { @@ -369,6 +370,7 @@ public static some.test.Simple createDetachedCopy(some.test.Simple realmObject, SimpleRealmProxyInterface realmSource = (SimpleRealmProxyInterface) realmObject; unmanagedCopy.realmSet$name(realmSource.realmGet$name()); unmanagedCopy.realmSet$age(realmSource.realmGet$age()); + return unmanagedObject; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 0561dd58ed..d690a3fb44 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -593,11 +593,11 @@ public void migration_backlinkedSourceFieldDoesntExist() throws ClassNotFoundExc // Mock the schema info so the only difference compared with the original schema is that the LinkingObject field // points to BacklinksSource.childNotExist. - OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget") + OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", 1, 1) .addPersistedProperty("id", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED) .addComputedLinkProperty("parents", "BacklinksSource", "childNotExist" /*"child" is the original value*/) .build(); - OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource") + OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", 2, 0) .addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED) .addPersistedLinkProperty("child", RealmFieldType.OBJECT, "BacklinksTarget") .build(); @@ -636,11 +636,11 @@ public void migration_backlinkedSourceFieldWrongType() { // Mock the schema info so the only difference compared with the original schema is that BacklinksSource.child // type is changed to BacklinksSource from BacklinksTarget. - OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget") + OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", 1, 1) .addPersistedProperty("id", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED) .addComputedLinkProperty("parents", "BacklinksSource", "child") .build(); - OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource") + OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", 2, 0) .addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED) .addPersistedLinkProperty("child", RealmFieldType.OBJECT, "BacklinksSource"/*"BacklinksTarget" is the original value*/) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java index 2b266233ce..b5ff28356a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java @@ -51,7 +51,7 @@ public class OsListTests { @Before public void setUp() { - OsObjectSchemaInfo objectSchemaInfo = new OsObjectSchemaInfo.Builder("TestModel") + OsObjectSchemaInfo objectSchemaInfo = new OsObjectSchemaInfo.Builder("TestModel",14, 0) .addPersistedValueListProperty("longList", RealmFieldType.INTEGER_LIST, !Property.REQUIRED) .addPersistedValueListProperty("doubleList", RealmFieldType.DOUBLE_LIST, !Property.REQUIRED) .addPersistedValueListProperty("floatList", RealmFieldType.FLOAT_LIST, !Property.REQUIRED) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index c87639c9c6..b12d014bea 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -149,7 +149,7 @@ public void breakingSchemaChange_throws() { .build(); // Setup initial Realm schema (with a different primary key) - OsObjectSchemaInfo expectedObjectSchema = new OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME) + OsObjectSchemaInfo expectedObjectSchema = new OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME, 2, 0) .addPersistedProperty(PrimaryKeyAsString.FIELD_PRIMARY_KEY, RealmFieldType.STRING, false, true, false) .addPersistedProperty(PrimaryKeyAsString.FIELD_ID, RealmFieldType.INTEGER, true, true, true) .build(); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp index 4b864a0b6f..b291ea3c28 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp @@ -16,11 +16,14 @@ #include "io_realm_internal_OsObjectSchemaInfo.h" +#include + #include #include -#include "jni_util/java_exception_thrower.hpp" +#include "java_accessor.hpp" #include "java_exception_def.hpp" +#include "jni_util/java_exception_thrower.hpp" #include "util.hpp" using namespace realm; @@ -53,24 +56,33 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetFinal return reinterpret_cast(&finalize_object_schema); } - -JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeAddProperty(JNIEnv* env, jclass, - jlong native_ptr, - jlong property_ptr, - jboolean is_computed) +JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeAddProperties(JNIEnv* env, jclass, + jlong native_ptr, + jlongArray j_persisted_properties, + jlongArray j_computed_properties) { TR_ENTER_PTR(native_ptr) try { - ObjectSchema* object_schema = reinterpret_cast(native_ptr); - Property* property = reinterpret_cast(property_ptr); - if (is_computed) { - object_schema->computed_properties.push_back(*property); - } - else { - object_schema->persisted_properties.push_back(*property); - if (property->is_primary) { - object_schema->primary_key = property->name; + ObjectSchema& object_schema = *reinterpret_cast(native_ptr); + JLongArrayAccessor persisted_properties(env, j_persisted_properties); + for (jsize i = 0; i < persisted_properties.size(); ++i) + { + Property* prop = reinterpret_cast(persisted_properties[i]); + REALM_ASSERT_DEBUG(prop != nullptr); + if (prop->is_primary) { + object_schema.primary_key = prop->name; } + object_schema.persisted_properties.emplace_back(std::move(*prop)); + delete prop; + } + + JLongArrayAccessor computed_properties(env, j_computed_properties); + for (jsize i = 0; i < computed_properties.size(); ++i) + { + Property* prop = reinterpret_cast(computed_properties[i]); + REALM_ASSERT_DEBUG(prop != nullptr); + object_schema.computed_properties.emplace_back(std::move(*prop)); + delete prop; } } CATCH_STD() diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java index 5b25bc1d43..dc23ee4f0f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java @@ -16,9 +16,6 @@ package io.realm.internal; -import java.util.ArrayList; -import java.util.List; - import javax.annotation.Nullable; import io.realm.RealmFieldType; @@ -31,9 +28,11 @@ public class OsObjectSchemaInfo implements NativeObject { public static class Builder { - private String className; - private List persistedPropertyList = new ArrayList(); - private List computedPropertyList = new ArrayList(); + private final String className; + private final long[] persistedPropertyPtrArray; + private int persistedPropertyPtrCurPos = 0; + private final long[] computedPropertyPtrArray; + private int computedPropertyPtrCurPos = 0; /** * Creates an empty builder for {@code OsObjectSchemaInfo}. This constructor is intended to be used by @@ -41,8 +40,10 @@ public static class Builder { * * @param className name of the class */ - public Builder(String className) { + public Builder(String className, int persistedPropertyCapacity, int computedPropertyCapacity) { this.className = className; + this.persistedPropertyPtrArray = new long[persistedPropertyCapacity]; + this.computedPropertyPtrArray = new long[computedPropertyCapacity]; } /** @@ -57,8 +58,10 @@ public Builder(String className) { */ public Builder addPersistedProperty(String name, RealmFieldType type, boolean isPrimaryKey, boolean isIndexed, boolean isRequired) { - final Property property = new Property(name, type, isPrimaryKey, isIndexed, isRequired); - persistedPropertyList.add(property); + long propertyPtr = Property.nativeCreatePersistedProperty(name, + Property.convertFromRealmFieldType(type, isRequired), isPrimaryKey, isIndexed); + persistedPropertyPtrArray[persistedPropertyPtrCurPos] = propertyPtr; + persistedPropertyPtrCurPos++; return this; } @@ -71,8 +74,10 @@ public Builder addPersistedProperty(String name, RealmFieldType type, boolean is * @return this {@code OsObjectSchemaInfo}. */ public Builder addPersistedValueListProperty(String name, RealmFieldType type, boolean isRequired) { - final Property property = new Property(name, type, !Property.PRIMARY_KEY, !Property.INDEXED, isRequired); - persistedPropertyList.add(property); + long propertyPtr = Property.nativeCreatePersistedProperty(name, + Property.convertFromRealmFieldType(type, isRequired), !Property.PRIMARY_KEY, !Property.INDEXED); + persistedPropertyPtrArray[persistedPropertyPtrCurPos] = propertyPtr; + persistedPropertyPtrCurPos++; return this; } @@ -86,8 +91,10 @@ public Builder addPersistedValueListProperty(String name, RealmFieldType type, b * @return this {@code OsObjectSchemaInfo.Builder}. */ public Builder addPersistedLinkProperty(String name, RealmFieldType type, String linkedClassName) { - final Property property = new Property(name, type, linkedClassName); - persistedPropertyList.add(property); + long propertyPtr = Property.nativeCreatePersistedLinkProperty(name, + Property.convertFromRealmFieldType(type, false), linkedClassName); + persistedPropertyPtrArray[persistedPropertyPtrCurPos] = propertyPtr; + persistedPropertyPtrCurPos++; return this; } @@ -102,20 +109,26 @@ public Builder addPersistedLinkProperty(String name, RealmFieldType type, String * @return this {@code OsObjectSchemaInfo.Builder}. */ public Builder addComputedLinkProperty(String name, String targetClassname, String targetFieldName) { - final Property property = new Property(name, targetClassname, targetFieldName); - computedPropertyList.add(property); + long propertyPtr = Property.nativeCreateComputedLinkProperty(name, targetClassname, targetFieldName); + computedPropertyPtrArray[computedPropertyPtrCurPos] = propertyPtr; + computedPropertyPtrCurPos++; return this; } + /** + * Creates {@link OsObjectSchemaInfo} object from this builder. After calling, this {@code Builder} becomes + * invalid. All the property pointers will be freed. + * + * @return a newly created {@link OsObjectSchemaInfo}. + */ public OsObjectSchemaInfo build() { - OsObjectSchemaInfo info = new OsObjectSchemaInfo(className); - for (Property property : persistedPropertyList) { - nativeAddProperty(info.nativePtr, property.getNativePtr(), false); - } - for (Property property : computedPropertyList) { - nativeAddProperty(info.nativePtr, property.getNativePtr(), true); + if (persistedPropertyPtrCurPos == -1 || computedPropertyPtrCurPos == -1) { + throw new IllegalStateException("'OsObjectSchemaInfo.build()' has been called before on this object."); } - + OsObjectSchemaInfo info = new OsObjectSchemaInfo(className); + nativeAddProperties(info.nativePtr, persistedPropertyPtrArray, computedPropertyPtrArray); + persistedPropertyPtrCurPos = -1; + computedPropertyPtrCurPos = -1; return info; } } @@ -186,7 +199,8 @@ public long getNativeFinalizerPtr() { private static native long nativeGetFinalizerPtr(); - private static native void nativeAddProperty(long nativePtr, long nativePropertyPtr, boolean isComputed); + // Add properties to the ObjectSchema and delete property pointers. + private static native void nativeAddProperties(long nativePtr, long[] persistedPropPtrs, long[] computedPropPtrs); private static native String nativeGetClassName(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Property.java b/realm/realm-library/src/main/java/io/realm/internal/Property.java index 03a1026223..0bcff85f24 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Property.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Property.java @@ -67,24 +67,12 @@ public class Property implements NativeObject { private long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - Property(String name, RealmFieldType type, boolean isPrimary, boolean isIndexed, boolean isRequired) { - this(nativeCreatePersistedProperty(name, convertFromRealmFieldType(type, isRequired), isPrimary, isIndexed)); - } - - Property(String name, RealmFieldType type, String linkedClassName) { - this(nativeCreatePersistedLinkProperty(name, convertFromRealmFieldType(type, false), linkedClassName)); - } - - Property(String name, String sourceClassName, String sourceFieldName) { - this(nativeCreateComputedLinkProperty(name, sourceClassName, sourceFieldName)); - } - Property(long nativePtr) { this.nativePtr = nativePtr; NativeContext.dummyContext.addReference(this); } - private static int convertFromRealmFieldType(RealmFieldType fieldType, boolean isRequired) { + static int convertFromRealmFieldType(RealmFieldType fieldType, boolean isRequired) { int type; switch (fieldType) { case OBJECT: @@ -217,12 +205,14 @@ public long getNativeFinalizerPtr() { private static native long nativeGetFinalizerPtr(); - private static native long nativeCreatePersistedProperty( + // nativeCreateXxxProperty will be called by OsObjectSchemaInfo directly to avoid creating temporary Property + // objects. + static native long nativeCreatePersistedProperty( String name, int type, boolean isPrimary, boolean isIndexed); - private static native long nativeCreatePersistedLinkProperty(String name, int type, String linkedToName); + static native long nativeCreatePersistedLinkProperty(String name, int type, String linkedToName); - private static native long nativeCreateComputedLinkProperty( + static native long nativeCreateComputedLinkProperty( String name, String sourceClassName, String sourceFieldName); private static native int nativeGetType(long nativePtr); From af7e0cccc915f7ee7c88f9002255103855fd59cd Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 13 Oct 2017 15:36:55 +0800 Subject: [PATCH 1017/2110] Update Sync to 2.0.0 (#5415) --- CHANGELOG.md | 3 +++ dependencies.list | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c3e44376c..b2d8cfe396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ ## Internal +* Upgraded to Realm Sync 2.0.0. +* Upgraded to Realm Core 4.0.2. + ## Credits diff --git a/dependencies.list b/dependencies.list index dd56b99fec..9f4ab3db86 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.0.0-rc27 -REALM_SYNC_SHA256=3a558b10ecab3e8dbf6cbceae7fe40af38ad8d8467ddd1fe036ce92c1e7810f4 +REALM_SYNC_VERSION=2.0.0 +REALM_SYNC_SHA256=2d3661cdb94d6509b4a43d6daab17c9223fbb1e6608e317205bd61b4ef1b9516 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. From 0d53f67e519be979a4eb17fafc3a5cbdc07dc49d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 13 Oct 2017 18:13:28 +0800 Subject: [PATCH 1018/2110] Remove readonly for defaultPermissionRealmConfig close #5414 --- CHANGELOG.md | 1 + .../src/objectServer/java/io/realm/PermissionManager.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02c2761ef1..10f7bc858d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug Fixes * Added support for ISO8601 2-digit time zone designators (#5309). +* [ObjectServer] Fixed "Cannot open the read only Realm" issue when get`PermissionManager` (#5414). ### Credits diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java index 170b11db05..afbc49a355 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java @@ -220,7 +220,9 @@ public void onError(SyncSession session, ObjectServerError error) { }) .modules(new PermissionModule()) .waitForInitialRemoteData() - .readOnly() + // FIXME: Something is seriously wrong with the Permission Realm. It doesn't seem to + // exist on the server. Making it impossible to mark it read only + //.readOnly() .build(); } From 8a874c4492afa96a554c9eb1ce026f6fb1fc5148 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 13 Oct 2017 17:29:04 +0200 Subject: [PATCH 1019/2110] Upgrade ROS Integration Test Server to 2.0.0-rc.5 (#5385) --- CHANGELOG.md | 1 + dependencies.list | 3 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/PermissionManager.java | 8 +++- .../java/io/realm/PermissionManagerTests.java | 39 +++++++------------ .../java/io/realm/objectserver/AuthTests.java | 2 - .../realm/objectserver/utils/Constants.java | 15 ++++--- tools/sync_test_server/Dockerfile | 2 +- tools/sync_test_server/ros-testing-server.js | 15 +++++++ 9 files changed, 46 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d8cfe396..7cc16f18cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Assigning a managed object's own list to itself would accidentally clear it (#5395). * Don't try to acquire `ApplicationContext` if not available in `Realm.init(Context)` (#5389). +* Removing and re-adding a changelistener from inside a changelistener sometimes caused notifications to be missed (#5411). ## Internal diff --git a/dependencies.list b/dependencies.list index 9f4ab3db86..d997db1ea9 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,4 +5,5 @@ REALM_SYNC_SHA256=2d3661cdb94d6509b4a43d6daab17c9223fbb1e6608e317205bd61b4ef1b95 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-rc.4 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-rc.5 + diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 8a387856db..b416d9ac98 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 8a387856db0beb6a95e385546d1752188aff14a6 +Subproject commit b416d9ac9893aa1b36f0a4c0c2d6533e78fe5060 diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java index e05ff917a8..dff2d1f602 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java @@ -23,6 +23,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.LinkedHashMap; @@ -198,6 +199,7 @@ user, getRealmUrl(RealmType.PERMISSION_REALM, user.getAuthenticationUrl())) .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { + RealmLog.error("Error in __permission:\n" + error.toString()); synchronized (errorLock) { permissionRealmError = error; } @@ -588,9 +590,11 @@ public void run() { loadingPermissions.addChangeListener(new RealmChangeListener >() { @Override public void onChange(RealmResults loadedPermissions) { - // FIXME Wait until both the __permission and __management Realm are available - if (loadedPermissions.size() > 0) { + RealmLog.error(String.format("1stCallback: Size: %s, Permissions: %s", loadedPermissions.size(), Arrays.toString(loadedPermissions.toArray()))); + // Don't report ready until both __permission and __management Realm are there + if (loadedPermissions.size() > 1) { loadingPermissions.removeChangeListener(this); + loadingPermissions = null; if (checkAndReportInvalidState()) { return; } if (userPermissions == null) { userPermissions = loadedPermissions; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 070f9fa348..770ae872c4 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -106,9 +106,8 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/437") public void getPermissions_updatedWithNewRealms() { - PermissionManager pm = user.getPermissionManager(); + final PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); pm.getPermissions(new PermissionManager.PermissionsCallback() { @Override @@ -132,6 +131,7 @@ public void onError(SyncSession session, ObjectServerError error) { permissions.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults permissions) { + RealmLog.error(String.format("2ndCallback: Size: %s, Permissions: %s", permissions.size(), Arrays.toString(permissions.toArray()))); Permission p = permissions.where().endsWith("path", "tests2").findFirst(); if (p != null) { assertTrue(p.mayRead()); @@ -152,8 +152,8 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/437") public void getPermissions_updatedWithNewRealms_stressTest() { + final int TEST_SIZE = 10; final PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); pm.getPermissions(new PermissionManager.PermissionsCallback() { @@ -162,8 +162,8 @@ public void onSuccess(RealmResults permissions) { assertTrue(permissions.isLoaded()); assertInitialPermissions(permissions); - for (int i = 0; i < 10; i++) { - SyncConfiguration configNew = new SyncConfiguration.Builder(user, "realm://127.0.0.1:9080/~/test" + i).build(); + for (int i = 0; i < TEST_SIZE; i++) { + SyncConfiguration configNew = new SyncConfiguration.Builder(user, "realm://" + Constants.HOST + "/~/test" + i).build(); Realm newRealm = Realm.getInstance(configNew); looperThread.closeAfterTest(newRealm); } @@ -173,8 +173,8 @@ public void onSuccess(RealmResults permissions) { permissions.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults permissions) { - RealmLog.error(Arrays.toString(permissions.toArray())); // FIXME Debug output for CI. Remove before release. - Permission p = permissions.where().endsWith("path", "test9").findFirst(); + RealmLog.error(String.format("Size: %s, Permissions: %s", permissions.size(), Arrays.toString(permissions.toArray()))); + Permission p = permissions.where().endsWith("path", "test" + (TEST_SIZE - 1)).findFirst(); if (p != null) { assertTrue(p.mayRead()); assertTrue(p.mayWrite()); @@ -243,7 +243,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/432") + @Ignore("Wait for default permission Realm support") public void getPermissions_addTaskAfterClientReset() { final PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -428,7 +428,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/432") + @Ignore("See https://github.com/realm/ros/issues/520") public void getDefaultPermissions_returnLoadedResults() { PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -449,7 +449,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/432") + @Ignore("See https://github.com/realm/ros/issues/520") public void getDefaultPermissions_noLongerValidWhenPermissionManagerIsClosed() { final PermissionManager pm = user.getPermissionManager(); pm.getDefaultPermissions(new PermissionManager.PermissionsCallback() { @@ -481,6 +481,7 @@ public void getDefaultPermissions_updatedWithNewRealms() { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore("See https://github.com/realm/ros/issues/520") public void getDefaultPermissions_closed() throws IOException { PermissionManager pm = user.getPermissionManager(); pm.close(); @@ -716,7 +717,6 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/426") public void applyPermissions_withUsername() { String user1Username = TestHelper.getRandomEmail(); String user2Username = TestHelper.getRandomEmail(); @@ -831,7 +831,6 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/430") public void makeOffer_noManageAccessThrows() { // User 2 creates a Realm SyncUser user2 = UserFactory.createUniqueUser(); @@ -1208,19 +1207,7 @@ public void onChange(Progress progress) { * states and fail if neither of these can be verified. */ private void assertInitialPermissions(RealmResults permissions) { - // For a new user, the PermissionManager should contain 1 entry for the __permission Realm, but we are - // creating the __management Realm at the same time, so this might be here as well. - permissions = permissions.sort("path"); - if (permissions.size() == 1) { - // FIXME It is very unpredictable which Permission is returned. This needs to be fixed. - Permission permission = permissions.first(); - assertTrue(permission.getPath().endsWith("__permission") || permission.getPath().endsWith("__management")); - } else if (permissions.size() == 2) { - assertTrue("Failed: " + permissions.get(0).toString(), permissions.get(0).getPath().endsWith("__management")); - assertTrue("Failed: " + permissions.get(1).toString(), permissions.get(1).getPath().endsWith("__permission")); - } else { - fail("Permission Realm contains unknown permissions: " + Arrays.toString(permissions.toArray())); - } + assertEquals("Could not find __permissions Realm", 1, permissions.where().endsWith("path", "__permission").count()); + assertEquals("Could not find __management Realm", 1, permissions.where().endsWith("path", "__management").count()); } - } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 8f313270ed..bc3f4e6f39 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -497,7 +497,6 @@ public void singleUserCanBeLoggedInAndOutRepeatedly() { } @Test - @Ignore("See https://github.com/realm/ros/issues/360") public void revokedRefreshTokenIsNotSameAfterLogin() throws InterruptedException { final CountDownLatch userLoggedInAgain = new CountDownLatch(1); final String uniqueName = UUID.randomUUID().toString(); @@ -536,7 +535,6 @@ public void loggedOut(SyncUser user) { // WARNING: this test can fail if there's a difference between the server's and device's clock, causing the // refresh access token to be too far in time. @Test(timeout = 30000) - @Ignore("Resolve https://github.com/realm/ros/issues/277") public void preemptiveTokenRefresh() throws NoSuchFieldException, IllegalAccessException, InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java index 0bf26e0eca..3f2e74c5ad 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -18,14 +18,13 @@ public class Constants { - public static final String USER_REALM = "realm://127.0.0.1:9080/~/tests"; - public static final String USER_REALM_2 = "realm://127.0.0.1:9080/~/tests2"; - public static final String USER_REALM_SECURE = "realms://127.0.0.1:9443/~/tests"; - public static final String SYNC_SERVER_URL = "realm://127.0.0.1:9080/~/tests"; - public static final String SYNC_SERVER_URL_2 = "realm://127.0.0.1/~/tests2"; + public static String HOST = "127.0.0.1"; + public static final String USER_REALM = "realm://" + HOST + ":9080/~/tests"; + public static final String USER_REALM_2 = "realm://" + HOST + ":9080/~/tests2"; + public static final String USER_REALM_SECURE = "realms://" + HOST + ":9443/~/tests"; + public static final String SYNC_SERVER_URL = "realm://" + HOST + ":9080/~/tests"; + public static final String SYNC_SERVER_URL_2 = "realm://" + HOST + "/~/tests2"; - public static final String AUTH_SERVER_URL = "http://127.0.0.1:9080/"; + public static final String AUTH_SERVER_URL = "http://" + HOST + ":9080/"; public static final String AUTH_URL = AUTH_SERVER_URL + "auth"; - - public static final long TEST_TIMEOUT_SECS = 300; } diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index e53b38b584..97cd798d1b 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -6,7 +6,7 @@ ARG ROS_DE_VERSION RUN npm install -g realm-object-server@$ROS_DE_VERSION -S # Install test server dependencies -RUN npm install winston temp httpdispatcher@1.0.0 +RUN npm install winston temp httpdispatcher@1.0.0 fs-extra COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index 0e46a056d8..d199e33677 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -6,6 +6,7 @@ const spawn = require('child_process').spawn; const exec = require('child_process').exec; var http = require('http'); var dispatcher = require('httpdispatcher'); +var fs = require('fs-extra'); // Automatically track and cleanup files at exit temp.track(); @@ -63,9 +64,23 @@ function startRealmObjectServer(onSuccess, onError) { var env = Object.create( process.env ); winston.info(env.NODE_ENV); env.NODE_ENV = 'development'; + + // Manually cleanup Global Notifier State + // See https://github.com/realm/ros/issues/437#issuecomment-335380095 + var globalNotifierDir = path + '/realm-object-server'; + winston.info('Cleaning state in: ' + globalNotifierDir); + fs.removeSync(globalNotifierDir) + if (fs.existsSync(globalNotifierDir)) { + onError("Could not delete the global notifier directory: " + globalNotifierDir); + return; + } + fs.mkdirsSync(path + '/realm-object-server/io.realm.object-server-utility/metadata/') + + // Start ROS syncServerChildProcess = spawn('ros', ['start', '--data', path, + // '--loglevel', 'detail', // Enable when debugging '--access-token-ttl', '20' //WARNING : Changing this value may impact the timeout of the refresh token test (AuthTests#preemptiveTokenRefresh) ], { env: env, cwd: path}); From f34bf99ca624bb221259d37549ae068c00e7632c Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Sat, 14 Oct 2017 15:34:31 +0530 Subject: [PATCH 1020/2110] Upgrade to RxJava 2.1.4 and OkHttp 3.9.0 (#5349) --- CHANGELOG.md | 2 ++ realm/realm-library/build.gradle | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cc16f18cf..2572de7365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ * Upgraded to Realm Sync 2.0.0. * Upgraded to Realm Core 4.0.2. +* Upgraded to OkHttp 3.9.0 . +* Upgraded to RxJava 2.1.4 . ## Credits diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 7b9265a049..34c1c257c8 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -188,7 +188,7 @@ repositories { dependencies { - compileOnly 'io.reactivex.rxjava2:rxjava:2.1.1' + compileOnly 'io.reactivex.rxjava2:rxjava:2.1.4' compileOnly 'com.google.code.findbugs:findbugs-annotations:3.0.1' api "io.realm:realm-annotations:${version}" @@ -196,11 +196,11 @@ dependencies { implementation 'com.getkeepsafe.relinker:relinker:1.2.2' kaptObjectServer project(':realm-annotations-processor') - objectServerImplementation 'com.squareup.okhttp3:okhttp:3.7.0' + objectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation fileTree(dir: 'testLibs', include: ['*.jar']) - androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.1' + androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.4' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test:rules:1.0.1' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' From d3618c13da1cd85cea2b6bd2ca26574bca5a1a91 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Oct 2017 15:16:51 +0200 Subject: [PATCH 1021/2110] Update all dependencies (#5423) * Update to ROS-2.0.0-rc11 * Update to Sync 2.0.2 * Store ROS logs as an artifact for each test run * Re-enable default permissions + tests. --- CHANGELOG.md | 2 +- Jenkinsfile | 13 +- dependencies.list | 6 +- .../java/io/realm/PermissionManager.java | 213 +++++++++--------- .../java/io/realm/PermissionManagerTests.java | 12 +- .../java/io/realm/SyncSessionTests.java | 3 + tools/sync_test_server/Dockerfile | 2 +- tools/sync_test_server/ros-testing-server.js | 17 +- 8 files changed, 147 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2572de7365..3a70a28d6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ ## Internal -* Upgraded to Realm Sync 2.0.0. +* Upgraded to Realm Sync 2.0.2. * Upgraded to Realm Core 4.0.2. * Upgraded to OkHttp 3.9.0 . * Upgraded to RxJava 2.1.4 . diff --git a/Jenkinsfile b/Jenkinsfile index e93e987237..a5b2845569 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -45,7 +45,7 @@ try { rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_DE_VERSION=${rosDeVersion} tools/sync_test_server" } - rosContainer = rosEnv.run('-v /tmp=/tmp/.ros') + rosContainer = rosEnv.run() try { buildEnv.inside("-e HOME=/tmp " + @@ -116,6 +116,7 @@ try { } } } finally { + archiveRosLog(rosContainer.id) sh "docker logs ${rosContainer.id}" rosContainer.stop() } @@ -173,6 +174,16 @@ def stopLogCatCollector(String backgroundPid, boolean archiveLog) { sh 'rm logcat.txt' } +def archiveRosLog(String id) { + sh "docker cp ${id}:/tmp/ros-testing-server.log ./ros.log" + zip([ + 'zipFile': 'roslog.zip', + 'archive': true, + 'glob' : 'ros.log' + ]) + sh 'rm ros.log' +} + def sendMetrics(String metricName, String metricValue, Map tags) { def tagsString = getTagsString(tags) withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: '5b8ad2d9-61a4-43b5-b4df-b8ff6b1f16fa', passwordVariable: 'influx_pass', usernameVariable: 'influx_user']]) { diff --git a/dependencies.list b/dependencies.list index d997db1ea9..838c222f42 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,9 +1,9 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.0.0 -REALM_SYNC_SHA256=2d3661cdb94d6509b4a43d6daab17c9223fbb1e6608e317205bd61b4ef1b9516 +REALM_SYNC_VERSION=2.0.2 +REALM_SYNC_SHA256=33c9dace6dc280712101110895d38509bbca74fdb31ba31b61dc0ad383472b03 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-rc.5 +REALM_OBJECT_SERVER_DE_VERSION=2.0.0-rc.11 diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java index dff2d1f602..9d1acc3527 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java @@ -102,7 +102,7 @@ protected Cache initialValue() { } private enum RealmType { -// DEFAULT_PERMISSION_REALM("__starpermissions", true), + DEFAULT_PERMISSION_REALM("__wildcardpermissions", true), PERMISSION_REALM("__permission", false), MANAGEMENT_REALM("__management", false); @@ -128,7 +128,7 @@ public boolean isGlobalRealm() { // Used to track the lifecycle of the PermissionManager private RealmAsyncTask managementRealmOpenTask; private RealmAsyncTask permissionRealmOpenTask; -// private RealmAsyncTask defaultPermissionRealmOpenTask; + private RealmAsyncTask defaultPermissionRealmOpenTask; private boolean openInProgress = false; private boolean closed; @@ -136,10 +136,10 @@ public boolean isGlobalRealm() { private Handler handler = new Handler(); final SyncConfiguration managementRealmConfig; final SyncConfiguration permissionRealmConfig; -// final SyncConfiguration defaultPermissionRealmConfig; + final SyncConfiguration defaultPermissionRealmConfig; private Realm permissionRealm; private Realm managementRealm; -// private Realm defaultPermissionRealm; + private Realm defaultPermissionRealm; // Task list used to queue tasks until the underlying Realms are done opening (or failed doing so). private Deque delayedTasks = new LinkedList<>(); @@ -155,7 +155,7 @@ public boolean isGlobalRealm() { private final Object errorLock = new Object(); private volatile ObjectServerError permissionRealmError = null; private volatile ObjectServerError managementRealmError = null; -// private volatile ObjectServerError defaultPermissionRealmError = null; + private volatile ObjectServerError defaultPermissionRealmError = null; // A client reset was encountered in one of the Realms. // This has invalidated the PermissionManager and it must be closed as soon as possible. @@ -166,7 +166,7 @@ public boolean isGlobalRealm() { // Cached result of the permission query. This will be filled, once the first PermissionAsyncTask has loaded // the result. private RealmResults userPermissions; -// private RealmResults defaultPermissions; + private RealmResults defaultPermissions; private RealmResults offers; /** @@ -211,21 +211,22 @@ public void onError(SyncSession session, ObjectServerError error) { .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); -// defaultPermissionRealmConfig = new SyncConfiguration.Builder( -// user, getRealmUrl(RealmType.DEFAULT_PERMISSION_REALM, user.getAuthenticationUrl())) -// .errorHandler(new SyncSession.ErrorHandler() { -// @Override -// public void onError(SyncSession session, ObjectServerError error) { -// synchronized (errorLock) { -// defaultPermissionRealmError = error; -// } -// } -// }) -// .modules(new PermissionModule()) -// .waitForInitialRemoteData() -// .readOnly() -// .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) -// .build(); + defaultPermissionRealmConfig = new SyncConfiguration.Builder( + user, getRealmUrl(RealmType.DEFAULT_PERMISSION_REALM, user.getAuthenticationUrl())) + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + RealmLog.error("Error in __wildcardpermissions:\n" + error.toString()); + synchronized (errorLock) { + defaultPermissionRealmError = error; + } + } + }) + .modules(new PermissionModule()) + .waitForInitialRemoteData() + .readOnly() + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build(); } /** @@ -251,11 +252,10 @@ public RealmAsyncTask getPermissions(PermissionsCallback callback) { * live query result, that will be auto-updated like any other {@link RealmResults}. * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. */ - RealmAsyncTask getDefaultPermissions(PermissionsCallback callback) { + public RealmAsyncTask getDefaultPermissions(PermissionsCallback callback) { checkIfValid(); checkCallbackNotNull(callback); - return null; - // return addTask(new GetDefaultPermissionsAsyncTask(this, callback)); + return addTask(new GetDefaultPermissionsAsyncTask(this, callback)); } /** @@ -424,30 +424,30 @@ public void onError(Throwable exception) { } } }); -// defaultPermissionRealmOpenTask = Realm.getInstanceAsync(defaultPermissionRealmConfig, new Realm.Callback() { -// @Override -// public void onSuccess(Realm realm) { -// defaultPermissionRealm = realm; -// defaultPermissionRealmOpenTask = null; -// checkIfRealmsAreOpenedAndRunDelayedTasks(); -// } -// -// @Override -// public void onError(Throwable exception) { -// synchronized (errorLock) { -// defaultPermissionRealmError = new ObjectServerError(ErrorCode.UNKNOWN, exception); -// defaultPermissionRealmOpenTask = null; -// checkIfRealmsAreOpenedAndRunDelayedTasks(); -// } -// } -// }); + defaultPermissionRealmOpenTask = Realm.getInstanceAsync(defaultPermissionRealmConfig, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + defaultPermissionRealm = realm; + defaultPermissionRealmOpenTask = null; + checkIfRealmsAreOpenedAndRunDelayedTasks(); + } + + @Override + public void onError(Throwable exception) { + synchronized (errorLock) { + defaultPermissionRealmError = new ObjectServerError(ErrorCode.UNKNOWN, exception); + defaultPermissionRealmOpenTask = null; + checkIfRealmsAreOpenedAndRunDelayedTasks(); + } + } + }); } } private void checkIfRealmsAreOpenedAndRunDelayedTasks() { synchronized (errorLock) { if ((permissionRealm != null || permissionRealmError != null) -// && (defaultPermissionRealm != null || defaultPermissionRealmError != null) + && (defaultPermissionRealm != null || defaultPermissionRealmError != null) && (managementRealm != null || managementRealmError != null)) { openInProgress = false; runDelayedTasks(); @@ -509,10 +509,10 @@ public void close() { permissionRealmOpenTask.cancel(); permissionRealmOpenTask = null; } -// if (defaultPermissionRealmOpenTask != null) { -// defaultPermissionRealmOpenTask.cancel(); -// defaultPermissionRealmOpenTask = null; -// } + if (defaultPermissionRealmOpenTask != null) { + defaultPermissionRealmOpenTask.cancel(); + defaultPermissionRealmOpenTask = null; + } // If Realms are opened. Close them. if (managementRealm != null) { @@ -522,9 +522,9 @@ public void close() { if (permissionRealm != null) { permissionRealm.close(); } -// if (defaultPermissionRealm != null) { -// defaultPermissionRealm.close(); -// } + if (defaultPermissionRealm != null) { + defaultPermissionRealm.close(); + } } /** @@ -615,54 +615,55 @@ void notifyCallbackWithSuccess(RealmResults permissions) { } } -// // Task responsible for loading the Default Permissions result and returning it to the user. -// // The Permission result is not considered available until the query has completed. -// private class GetDefaultPermissionsAsyncTask extends PermissionManagerTask> { -// -// private final PermissionsCallback callback; -// // Prevent permissions from being GC'ed until fully loaded. -// private RealmResults loadingPermissions; -// -// GetDefaultPermissionsAsyncTask(PermissionManager permissionManager, PermissionsCallback callback) { -// super(permissionManager, callback); -// this.callback = callback; -// } -// -// @Override -// public void run() { -// if (checkAndReportInvalidState()) { return; } -// if (defaultPermissions != null) { -// notifyCallbackWithSuccess(defaultPermissions); -// } else { -// // Start loading permissions. -// // TODO Right now multiple getPermission() calls will result in multiple -// // queries being executed. The first one to return will be the one returned -// // by all callbacks. -// loadingPermissions = permissionRealm.where(Permission.class).findAllAsync(); -// loadingPermissions.addChangeListener(new RealmChangeListener >() { -// @Override -// public void onChange(RealmResults loadedPermissions) { -// if (loadedPermissions.size() > 0) { -// loadingPermissions.removeChangeListener(this); -// if (checkAndReportInvalidState()) { return; } -// if (defaultPermissions == null) { -// defaultPermissions = loadedPermissions; -// } -// notifyCallbackWithSuccess(defaultPermissions); -// } -// } -// }); -// } -// } -// -// void notifyCallbackWithSuccess(RealmResults permissions) { -// try { -// callback.onSuccess(permissions); -// } finally { -// activeTasks.remove(this); -// } -// } -// } + // Task responsible for loading the Default Permissions result and returning it to the user. + // The Permission result is not considered available until the query has completed. + private class GetDefaultPermissionsAsyncTask extends PermissionManagerTask> { + + private final PermissionsCallback callback; + // Prevent permissions from being GC'ed until fully loaded. + private RealmResults loadingPermissions; + + GetDefaultPermissionsAsyncTask(PermissionManager permissionManager, PermissionsCallback callback) { + super(permissionManager, callback); + this.callback = callback; + } + + @Override + public void run() { + if (checkAndReportInvalidState()) { return; } + if (defaultPermissions != null) { + notifyCallbackWithSuccess(defaultPermissions); + } else { + // Start loading permissions. + // TODO Right now multiple getPermission() calls will result in multiple + // queries being executed. The first one to return will be the one returned + // by all callbacks. + loadingPermissions = defaultPermissionRealm.where(Permission.class).findAllAsync(); + loadingPermissions.addChangeListener(new RealmChangeListener >() { + @Override + public void onChange(RealmResults loadedPermissions) { + // Wildcard permissions should contain 1 Realm as the default, namely __wildcardpermissions + if (loadedPermissions.size() > 0) { + loadingPermissions.removeChangeListener(this); + if (checkAndReportInvalidState()) { return; } + if (defaultPermissions == null) { + defaultPermissions = loadedPermissions; + } + notifyCallbackWithSuccess(defaultPermissions); + } + } + }); + } + } + + void notifyCallbackWithSuccess(RealmResults permissions) { + try { + callback.onSuccess(permissions); + } finally { + activeTasks.remove(this); + } + } + } // Class encapsulating setting a Permission by writing a PermissionChange and waiting for it to // be processed. @@ -1026,10 +1027,10 @@ protected final boolean checkAndReportInvalidState() { // Only hold lock while making a safe copy of current error state managementErrorHappened = (permissionManager.managementRealmError != null); permissionErrorHappened = (permissionManager.permissionRealmError != null); -// defaultPermissionErrorHappened = (permissionManager.defaultPermissionRealmError != null); + defaultPermissionErrorHappened = (permissionManager.defaultPermissionRealmError != null); managementError = permissionManager.managementRealmError; permissionError = permissionManager.permissionRealmError; -// defaultPermissionError = permissionManager.defaultPermissionRealmError; + defaultPermissionError = permissionManager.defaultPermissionRealmError; } // Everything seems valid @@ -1061,12 +1062,12 @@ protected final boolean checkAndReportInvalidState() { permissionManager.clientReset = true; } -// if (defaultPermissionErrorHappened && defaultPermissionError instanceof ClientResetRequiredError) { -// ClientResetRequiredError cr = (ClientResetRequiredError) defaultPermissionError; -// permissionManager.defaultPermissionRealm.close(); -// cr.executeClientReset(); -// permissionManager.clientReset = true; -// } + if (defaultPermissionErrorHappened && defaultPermissionError instanceof ClientResetRequiredError) { + ClientResetRequiredError cr = (ClientResetRequiredError) defaultPermissionError; + permissionManager.defaultPermissionRealm.close(); + cr.executeClientReset(); + permissionManager.clientReset = true; + } // Handle errors Map errors = new LinkedHashMap<>(); @@ -1075,7 +1076,7 @@ protected final boolean checkAndReportInvalidState() { } else { if (managementErrorHappened) { errors.put("Management Realm", managementError); } if (permissionErrorHappened) { errors.put("Permission Realm", permissionError); } -// if (defaultPermissionErrorHappened) { errors.put("Default Permission Realm", defaultPermissionError); } + if (defaultPermissionErrorHappened) { errors.put("Default Permission Realm", defaultPermissionError); } } notifyCallbackWithError(combineRealmErrors(errors)); // This will remove the task from the task list diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 770ae872c4..70e76c7b65 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -106,6 +106,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore public void getPermissions_updatedWithNewRealms() { final PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -152,6 +153,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) + @Ignore public void getPermissions_updatedWithNewRealms_stressTest() { final int TEST_SIZE = 10; final PermissionManager pm = user.getPermissionManager(); @@ -243,7 +245,6 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("Wait for default permission Realm support") public void getPermissions_addTaskAfterClientReset() { final PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -428,7 +429,6 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/520") public void getDefaultPermissions_returnLoadedResults() { PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -436,7 +436,7 @@ public void getDefaultPermissions_returnLoadedResults() { @Override public void onSuccess(RealmResults permissions) { assertTrue(permissions.isLoaded()); - assertInitialPermissions(permissions); + assertInitialDefaultPermissions(permissions); looperThread.testComplete(); } @@ -449,7 +449,6 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/520") public void getDefaultPermissions_noLongerValidWhenPermissionManagerIsClosed() { final PermissionManager pm = user.getPermissionManager(); pm.getDefaultPermissions(new PermissionManager.PermissionsCallback() { @@ -481,7 +480,6 @@ public void getDefaultPermissions_updatedWithNewRealms() { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore("See https://github.com/realm/ros/issues/520") public void getDefaultPermissions_closed() throws IOException { PermissionManager pm = user.getPermissionManager(); pm.close(); @@ -1210,4 +1208,8 @@ private void assertInitialPermissions(RealmResults permissions) { assertEquals("Could not find __permissions Realm", 1, permissions.where().endsWith("path", "__permission").count()); assertEquals("Could not find __management Realm", 1, permissions.where().endsWith("path", "__management").count()); } + + private void assertInitialDefaultPermissions(RealmResults permissions) { + assertEquals("Could not find __wildcardpermissions Realm", 1, permissions.where().endsWith("path", "__wildcardpermissions").count()); + } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 21399cc133..7fce3b7a02 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -7,6 +7,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -115,6 +116,7 @@ public void uploadDownloadAllChanges() throws InterruptedException { } @Test + @Ignore() public void interruptWaits() throws InterruptedException { final SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); @@ -307,6 +309,7 @@ public void onChange(RealmResults stringOnlies) { // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. // this test validate the behaviour of SyncSessionStopPolicy::AfterChangesUploaded @Test + @Ignore() public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 97cd798d1b..8a936242db 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -6,7 +6,7 @@ ARG ROS_DE_VERSION RUN npm install -g realm-object-server@$ROS_DE_VERSION -S # Install test server dependencies -RUN npm install winston temp httpdispatcher@1.0.0 fs-extra +RUN npm install winston temp httpdispatcher@1.0.0 fs-extra moment COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index d199e33677..75e5868eb3 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -7,6 +7,7 @@ const exec = require('child_process').exec; var http = require('http'); var dispatcher = require('httpdispatcher'); var fs = require('fs-extra'); +var moment = require('moment') // Automatically track and cleanup files at exit temp.track(); @@ -15,9 +16,16 @@ if (process. argv. length <= 2) { console.log("Usage: " + __filename + " somefile.log"); process.exit(-1); } + const logFile = process.argv[2]; winston.level = 'debug'; -winston.add(winston.transports.File, { filename: logFile }); +winston.add(winston.transports.File, { + filename: logFile, + json: false, + formatter: function(options) { + return moment().format('YYYY-MM-DD HH:mm:ss.SSSS') + ' ' + (undefined !== options.message ? options.message : ''); + } +}); const PORT = 8888; @@ -64,6 +72,7 @@ function startRealmObjectServer(onSuccess, onError) { var env = Object.create( process.env ); winston.info(env.NODE_ENV); env.NODE_ENV = 'development'; + env.JENKINS = 1; // Skip email check in ROS // Manually cleanup Global Notifier State // See https://github.com/realm/ros/issues/437#issuecomment-335380095 @@ -80,18 +89,18 @@ function startRealmObjectServer(onSuccess, onError) { syncServerChildProcess = spawn('ros', ['start', '--data', path, - // '--loglevel', 'detail', // Enable when debugging + '--loglevel', 'detail', '--access-token-ttl', '20' //WARNING : Changing this value may impact the timeout of the refresh token test (AuthTests#preemptiveTokenRefresh) ], { env: env, cwd: path}); // local config: syncServerChildProcess.stdout.on('data', (data) => { - winston.info(`stdout: ${data}`); + winston.info(`${data}`); }); syncServerChildProcess.stderr.on('data', (data) => { - winston.info(`stderr: ${data}`); + winston.info(`${data}`); }); waitForRosToInitialize(20, onSuccess, onError); From e40a42b6fa70ed082f1f69f9060a681a4901a122 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Oct 2017 15:17:51 +0200 Subject: [PATCH 1022/2110] Fix wrong Javadoc (#5427) --- realm/realm-library/src/main/java/io/realm/RealmQuery.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index a87ff88b7d..97b5aeb465 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1763,8 +1763,7 @@ public RealmResults findAll() { } /** - * Finds all objects that fulfill the query conditions and sorted by specific field name. - * This method is only available from a Looper thread. + * Finds all objects that fulfill the query conditions. This method is only available from a Looper thread. * * @return immediately an empty {@link RealmResults}. Users need to register a listener * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. From 73b9850f88c5d80a1d36e671144231bf38836329 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 16 Oct 2017 23:11:56 +0800 Subject: [PATCH 1023/2110] Enable SSL integration tests (#5430) - Some features were missing which caused ssl tests failures. See https://github.com/realm/openssl-android/pull/2 Linking with pre-build openssl 1.0.2k-1. - Use a new Realm instead of using deleteRealm() --- realm/realm-library/src/main/cpp/CMakeLists.txt | 3 ++- .../java/io/realm/SSLConfigurationTests.java | 10 ++++++---- tools/sync_test_server/ros-testing-server.js | 4 ++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index a33fb1158e..eb1d5f79e2 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -91,7 +91,8 @@ use_realm_core(${build_SYNC} "${REALM_CORE_DIST_DIR}" "${CORE_SOURCE_PATH}") set(openssl_build_TYPE "release") # FIXME Read the openssl version from core when the core/sync release has that information. set(openssl_VERSION "1.0.2k") -set(openssl_FILENAME "openssl-${openssl_build_TYPE}-${openssl_VERSION}-Android-${ANDROID_ABI}") +set(openssl_BUILD_NUMBER "1") +set(openssl_FILENAME "openssl-${openssl_build_TYPE}-${openssl_VERSION}-${openssl_BUILD_NUMBER}-Android-${ANDROID_ABI}") set(openssl_URL "http://static.realm.io/downloads/openssl/${openssl_VERSION}/Android/${ANDROID_ABI}/${openssl_FILENAME}.tar.gz") message(STATUS "Downloading OpenSSL...") diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index 8d16609bee..ab943c1e3c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -39,7 +39,6 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) -@Ignore("See https://github.com/realm/ros/issues/240") public class SSLConfigurationTests extends StandardIntegrationTest { @Rule @@ -65,12 +64,12 @@ public void trustedRootCA() throws InterruptedException { SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. realm.close(); user.logout(); - Realm.deleteRealm(configOld); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() .trustedRootCA("trusted_ca.pem") @@ -106,12 +105,12 @@ public void withoutSSLVerification() throws InterruptedException { SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. realm.close(); user.logout(); - Realm.deleteRealm(configOld); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() .disableSSLVerification() @@ -147,12 +146,12 @@ public void trustedRootCA_syncShouldFailWithoutTrustedCA() throws InterruptedExc SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. realm.close(); user.logout(); - Realm.deleteRealm(configOld); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .name("useSsl") .schema(StringOnly.class) .build(); realm = Realm.getInstance(config); @@ -170,6 +169,7 @@ public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); + int originalLevel = RealmLog.getLevel(); RealmLog.add(testLogger); RealmLog.setLevel(LogLevel.WARN); @@ -181,6 +181,8 @@ public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { assertEquals("SSL Verification is disabled, the provided server certificate will not be used.", testLogger.message); + RealmLog.remove(testLogger); + RealmLog.setLevel(originalLevel); } @Test diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index 75e5868eb3..6225970ad2 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -90,6 +90,10 @@ function startRealmObjectServer(onSuccess, onError) { ['start', '--data', path, '--loglevel', 'detail', + '--https', + '--https-key', '/127_0_0_1-server.key.pem', + '--https-cert', '/127_0_0_1-chain.crt.pem', + '--https-port', '9443', '--access-token-ttl', '20' //WARNING : Changing this value may impact the timeout of the refresh token test (AuthTests#preemptiveTokenRefresh) ], { env: env, cwd: path}); From 1ed0277fce4aa0cd7e842752e2338f5f2a55bd2a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Oct 2017 21:43:19 +0200 Subject: [PATCH 1024/2110] Update changelog --- CHANGELOG.md | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7b22bb196..c7faab46c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,4 @@ -## 4.0.0 (YYYY-MM-DD) - -## Breaking Changes - -## Enhancements - -* All Realm annotations are now kept at runtime, allowing runtime tools access to them (#5344). -* Speedup schema initialization when a Realm file is first accessed (#5391). - -## Bug Fixes - -* Assigning a managed object's own list to itself would accidentally clear it (#5395). -* Don't try to acquire `ApplicationContext` if not available in `Realm.init(Context)` (#5389). -* Removing and re-adding a changelistener from inside a changelistener sometimes caused notifications to be missed (#5411). - -## Internal - -* Upgraded to Realm Sync 2.0.2. -* Upgraded to Realm Core 4.0.2. -* Upgraded to OkHttp 3.9.0 . -* Upgraded to RxJava 2.1.4 . - -## Credits - - -## 4.0.0-RC1 (2017-10-03) +## 4.0.0 (2016-10-16) ## Breaking Changes @@ -60,6 +35,8 @@ The internal file format has been upgraded. Opening an older Realm will upgrade * Added `RealmResults.asChangesetObservable()` that emits the pair `(results, changeset)` (#4277). * Added `RealmList.asChangesetObservable()` that emits the pair `(list, changeset)` (#4277). * Added `RealmObject.asChangesetObservable()` that emits the pair `(object, changeset)` (#4277). +* All Realm annotations are now kept at runtime, allowing runtime tools access to them (#5344). +* Speedup schema initialization when a Realm file is first accessed (#5391). ## Bug Fixes @@ -70,11 +47,16 @@ The internal file format has been upgraded. Opening an older Realm will upgrade * Added support for ISO8601 2-digit time zone designators (#5309). * "Bad File Header" caused by the device running out of space while compacting the Realm (#5011). * `RealmQuery.equalTo()` failed to find null values on an indexed field if using Case.INSENSITIVE (#5299). +* Assigning a managed object's own list to itself would accidentally clear it (#5395). +* Don't try to acquire `ApplicationContext` if not available in `Realm.init(Context)` (#5389). +* Removing and re-adding a changelistener from inside a changelistener sometimes caused notifications to be missed (#5411). ## Internal -* Upgraded to Realm Sync 2.0.0-rc27. -* Upgraded to Realm Core 4.0.1. +* Upgraded to Realm Sync 2.0.2. +* Upgraded to Realm Core 4.0.2. +* Upgraded to OkHttp 3.9.0. +* Upgraded to RxJava 2.1.4. * Use Object Store to create the primary key table. ### Credits From e26255b9c5248620861565eb3caf7c908c4f8277 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Oct 2017 21:45:32 +0200 Subject: [PATCH 1025/2110] Release v4.0.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 94ae9ee1fa..0c89fc927e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.0.0-SNAPSHOT +4.0.0 \ No newline at end of file From 40a93bbbd3a0213c53f3eccc10c040d94084be90 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Oct 2017 21:45:32 +0200 Subject: [PATCH 1026/2110] Prepare next release v4.0.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 0c89fc927e..06b5019af3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.0.0 \ No newline at end of file +4.0.1-SNAPSHOT \ No newline at end of file From c1b41935a18f830e73489c63a6861a15dffd088f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Oct 2017 23:07:23 +0200 Subject: [PATCH 1027/2110] Prepare next dev iteration --- CHANGELOG.md | 13 +++++++++++++ version.txt | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7faab46c3..18d5167ed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 4.1.0 (YYYY-MM-DD) + +## Breaking Changes + +## Enhancements + +## Bug Fixes + +## Internal + +### Credits + + ## 4.0.0 (2016-10-16) ## Breaking Changes diff --git a/version.txt b/version.txt index 06b5019af3..83a328a922 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.0.1-SNAPSHOT \ No newline at end of file +4.1.0-SNAPSHOT \ No newline at end of file From f80f06df2bbf6d4052590be153dcdee2397aa663 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 17 Oct 2017 21:54:35 +0800 Subject: [PATCH 1028/2110] Process safe deleteRealm and assetFile (#5417) - Use SharedRealm::call_with_lock() to make sure deleteRealm and assetFile calls are process safe. - Also those calls will throw if the Realm instance on the sync client thread is still opened. --- CHANGELOG.md | 6 +- .../androidTest/java/io/realm/RealmTests.java | 7 +- .../io/realm/internal/CollectionTests.java | 47 +++++++--- .../io/realm/internal/JNIColumnInfoTest.java | 13 ++- .../java/io/realm/internal/JNITableTest.java | 12 ++- .../io/realm/internal/OsObjectStoreTests.java | 94 +++++++++++++++++++ .../io/realm/internal/RealmNotifierTests.java | 14 +-- .../java/io/realm/rule/RunInLooperThread.java | 1 + .../rule/TestRealmConfigurationFactory.java | 34 ++++++- .../cpp/io_realm_internal_OsObjectStore.cpp | 22 +++++ .../cpp/io_realm_internal_OsRealmConfig.cpp | 12 ++- .../cpp/jni_util/java_exception_thrower.cpp | 6 +- .../cpp/jni_util/java_exception_thrower.hpp | 11 ++- .../src/main/java/io/realm/BaseRealm.java | 14 +-- .../src/main/java/io/realm/Realm.java | 11 ++- .../src/main/java/io/realm/RealmCache.java | 44 ++++++--- .../java/io/realm/internal/OsObjectStore.java | 17 ++++ .../src/main/java/io/realm/internal/Util.java | 55 +++++------ .../java/io/realm/SyncedRealmTests.java | 16 +++- .../EncryptedSynchronizedRealmTests.java | 5 +- .../realm/objectserver/PartialSyncTests.java | 2 +- 21 files changed, 338 insertions(+), 105 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d5167ed2..e44a76c9a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ ## 4.1.0 (YYYY-MM-DD) -## Breaking Changes - ## Enhancements +* `Realm.deleteRealm()` and `RealmConfiguration.assetFile()` are multi-processes safe now. + ## Bug Fixes +* Fix some potential database corruption caused by deleting the Realm file while a Realm instance are still opened in another process or the sync client thread. + ## Internal ### Credits diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 463d43a410..48d43d74d0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -2301,8 +2301,11 @@ public void run() { assertTrue(Realm.deleteRealm(configuration)); - // Directory should be empty now. - assertEquals(0, tempDir.listFiles().length); + assertEquals(1, tempDir.listFiles().length); + + // Lock file should never be deleted + File lockFile = new File(configuration.getPath() + ".lock"); + assertTrue(lockFile.exists()); } // Tests that all methods that require a transaction. (ie. any function that mutates Realm data) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java index 52e5711b8b..9175df0012 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java @@ -57,15 +57,13 @@ public class CollectionTests { private final long[] oneNullTable = new long[] {NativeObject.NULLPTR}; - private RealmConfiguration config; private SharedRealm sharedRealm; private Table table; @Before public void setUp() { - config = configFactory.createConfiguration(); sharedRealm = getSharedRealm(); - populateData(); + populateData(sharedRealm); } @After @@ -74,6 +72,16 @@ public void tearDown() { } private SharedRealm getSharedRealm() { + RealmConfiguration config = configFactory.createConfiguration(); + return getSharedRealm(config); + } + + private SharedRealm getSharedRealmForLooper() { + RealmConfiguration config = looperThread.createConfiguration(); + return getSharedRealm(config); + } + + private SharedRealm getSharedRealm(RealmConfiguration config) { OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config) .autoUpdateNotification(true); SharedRealm sharedRealm = SharedRealm.getInstance(configBuilder); @@ -87,7 +95,7 @@ private Table getTable(SharedRealm sharedRealm) { return sharedRealm.getTable(Table.getTableNameForClass("test_table")); } - private void populateData() { + private void populateData(SharedRealm sharedRealm) { sharedRealm.beginTransaction(); table = sharedRealm.createTable(Table.getTableNameForClass("test_table")); // Specify the column types and names @@ -119,12 +127,13 @@ private void populateData() { sharedRealm.commitTransaction(); } - private void addRowAsync() { + private void addRowAsync(final SharedRealm sharedRealm) { final CountDownLatch latch = new CountDownLatch(1); + final RealmConfiguration configuration = sharedRealm.getConfiguration(); new Thread(new Runnable() { @Override public void run() { - SharedRealm sharedRealm = getSharedRealm(); + SharedRealm sharedRealm = getSharedRealm(configuration); addRow(sharedRealm); sharedRealm.close(); latch.countDown(); @@ -253,7 +262,8 @@ public void distinct() { @Test @RunTestInLooperThread public void addListener_shouldBeCalledToReturnTheQueryResults() { - final SharedRealm sharedRealm = getSharedRealm(); + final SharedRealm sharedRealm = getSharedRealmForLooper(); + populateData(sharedRealm); Table table = getTable(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); @@ -332,7 +342,7 @@ public void onChange(Collection element) { } }); - addRowAsync(); + addRowAsync(sharedRealm); sharedRealm.waitForChange(); sharedRealm.refresh(); @@ -342,7 +352,8 @@ public void onChange(Collection element) { @Test @RunTestInLooperThread public void addListener_queryNotReturned() { - final SharedRealm sharedRealm = getSharedRealm(); + final SharedRealm sharedRealm = getSharedRealmForLooper(); + populateData(sharedRealm); Table table = getTable(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); @@ -357,13 +368,14 @@ public void onChange(Collection collection1) { } }); - addRowAsync(); + addRowAsync(sharedRealm); } @Test @RunTestInLooperThread public void addListener_queryReturned() { - final SharedRealm sharedRealm = getSharedRealm(); + final SharedRealm sharedRealm = getSharedRealmForLooper(); + populateData(sharedRealm); Table table = getTable(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); @@ -379,7 +391,7 @@ public void onChange(Collection collection1) { } }); - addRowAsync(); + addRowAsync(sharedRealm); } // Local commit will trigger the listener first when beginTransaction gets called then again when transaction @@ -387,7 +399,8 @@ public void onChange(Collection collection1) { @Test @RunTestInLooperThread public void addListener_triggeredByLocalCommit() { - final SharedRealm sharedRealm = getSharedRealm(); + final SharedRealm sharedRealm = getSharedRealmForLooper(); + populateData(sharedRealm); Table table = getTable(sharedRealm); final AtomicInteger listenerCounter = new AtomicInteger(0); @@ -468,7 +481,8 @@ public void collectionIterator_invalid_nonLooperThread_byRefresh() { @Test @RunTestInLooperThread public void collectionIterator_invalid_looperThread_byRemoteTransaction() { - final SharedRealm sharedRealm = getSharedRealm(); + final SharedRealm sharedRealm = getSharedRealmForLooper(); + populateData(sharedRealm); Table table = getTable(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); final TestIterator iterator = new TestIterator(collection); @@ -487,7 +501,7 @@ public void onChange(Collection element) { } }); - addRowAsync(); + addRowAsync(sharedRealm); } @Test @@ -522,6 +536,9 @@ public void onChange(Collection element) { @Test @RunTestInLooperThread public void load() { + final SharedRealm sharedRealm = getSharedRealmForLooper(); + looperThread.closeAfterTest(sharedRealm); + populateData(sharedRealm); final Collection collection = new Collection(sharedRealm, table.where()); collection.addListener(collection, new RealmChangeListener() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java index 74e66ff861..a0c0d8cac8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java @@ -19,6 +19,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -39,16 +40,13 @@ public class JNIColumnInfoTest { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - @SuppressWarnings("FieldCanBeLocal") - private RealmConfiguration config; - @SuppressWarnings("FieldCanBeLocal") private SharedRealm sharedRealm; private Table table; @Before public void setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); - config = configFactory.createConfiguration(); + RealmConfiguration config = configFactory.createConfiguration(); sharedRealm = SharedRealm.getInstance(config); table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @@ -60,6 +58,13 @@ public void execute(Table table) { }); } + @After + public void tearDown() { + if (sharedRealm != null) { + sharedRealm.close(); + } + } + @Test public void shouldGetColumnInformation() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index 3ddea3c362..dc61fbf252 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -19,6 +19,7 @@ import android.support.test.runner.AndroidJUnit4; import android.util.Pair; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -60,6 +61,13 @@ public void setUp() { sharedRealm = SharedRealm.getInstance(config); } + @After + public void tearDown() { + if (sharedRealm != null) { + sharedRealm.close(); + } + } + @Test public void tableToString() { Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @@ -307,10 +315,6 @@ public void execute(Table t) { @Test public void getName() { String TABLE_NAME = "tableName"; - RealmConfiguration configuration = configFactory.createConfiguration(); - Realm.deleteRealm(configuration); - - SharedRealm sharedRealm = SharedRealm.getInstance(configuration); //noinspection TryFinallyCanBeTryWithResources try { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java new file mode 100644 index 0000000000..cb9e620b1b --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.internal; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.realm.RealmConfiguration; +import io.realm.rule.TestRealmConfigurationFactory; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +// Tests for OsObjectStore +@RunWith(AndroidJUnit4.class) +public class OsObjectStoreTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Test + public void callWithLock() { + RealmConfiguration config = configFactory.createConfiguration(); + + // Return false if there are opened SharedRealm instance + SharedRealm sharedRealm = SharedRealm.getInstance(config); + assertFalse(OsObjectStore.callWithLock(config, new Runnable() { + @Override + public void run() { + fail(); + } + })); + sharedRealm.close(); + + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + assertTrue(OsObjectStore.callWithLock(config, new Runnable() { + @Override + public void run() { + callbackCalled.set(true); + } + })); + assertTrue(callbackCalled.get()); + } + + // Test if a java exception can be thrown from the callback. + @Test + public void callWithLock_throwInCallback() { + RealmConfiguration config = configFactory.createConfiguration(); + final RuntimeException exception = new RuntimeException(); + + try { + OsObjectStore.callWithLock(config, new Runnable() { + @Override + public void run() { + throw exception; + } + }); + fail(); + } catch (RuntimeException e) { + assertEquals(exception, e); + } + + // The lock should be released after exception thrown + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + assertTrue(OsObjectStore.callWithLock(config, new Runnable() { + @Override + public void run() { + callbackCalled.set(true); + } + })); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 44ac4bcdb6..87ba794501 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -110,15 +110,11 @@ public void onChange(SharedRealm sharedRealm) { } private void makeRemoteChanges(final RealmConfiguration config) { - new Thread(new Runnable() { - @Override - public void run() { - SharedRealm sharedRealm = getSharedRealm(config); - sharedRealm.beginTransaction(); - sharedRealm.commitTransaction(); - sharedRealm.close(); - } - }).start(); + // We don't use cache from RealmCoordinator + SharedRealm sharedRealm = getSharedRealm(config); + sharedRealm.beginTransaction(); + sharedRealm.commitTransaction(); + sharedRealm.close(); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index 8f998e57c7..ebc149f8c5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -297,6 +297,7 @@ protected void after() { @Override public Statement apply(Statement base, Description description) { + setTestName(description); final RunTestInLooperThread annotation = description.getAnnotation(RunTestInLooperThread.class); if (annotation == null) { return base; diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index 66827fa920..a82d87c530 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Collections; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -48,9 +49,11 @@ public class TestRealmConfigurationFactory extends TemporaryFolder { private final Set configurations = Collections.newSetFromMap(map); private boolean unitTestFailed = false; + private String testName = ""; + private File tempFolder = null; @Override - public Statement apply(final Statement base, Description description) { + public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { @@ -90,6 +93,30 @@ protected void after() { } } + @Override + public void create() throws IOException { + super.create(); + tempFolder = new File(super.getRoot(), testName); + tempFolder.delete(); + tempFolder.mkdir(); + } + + @Override + public File getRoot() { + if (tempFolder == null) { + throw new IllegalStateException( + "the temporary folder has not yet been created"); + } + return tempFolder; + } + + /** + * To be called in the {@link #apply(Statement, Description)}. + */ + protected void setTestName(Description description) { + testName = description.getDisplayName(); + } + public synchronized void setUnitTestFailed() { this.unitTestFailed = true; } @@ -163,8 +190,9 @@ public void copyRealmFromAssets(Context context, String realmPath, String newNam } public void copyRealmFromAssets(Context context, String realmPath, RealmConfiguration config) throws IOException { - // Deletes the existing file before copy - Realm.deleteRealm(config); + if (new File(config.getPath()).exists()) { + throw new IllegalStateException(String.format(Locale.ENGLISH, "%s exists!", config.getPath())); + } File outFile = new File(config.getRealmDirectory(), config.getRealmFileName()); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp index f63b7f937d..a3018f02d8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp @@ -20,6 +20,7 @@ #include #include "util.hpp" +#include "jni_util/java_method.hpp" #include "jni_util/java_exception_thrower.hpp" #include "jni_util/java_exception_thrower.hpp" @@ -140,3 +141,24 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsObjectStore_nativeDeleteTabl CATCH_STD() return JNI_FALSE; } + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsObjectStore_nativeCallWithLock(JNIEnv* env, jclass, + jstring j_realm_path, + jobject j_runnable) +{ + TR_ENTER(); + try { + JStringAccessor path_accessor(env, j_realm_path); + std::string realm_path(path_accessor); + static JavaClass runnable_class(env, "java/lang/Runnable"); + static JavaMethod run_method(env, runnable_class, "run", "()V"); + bool result = SharedGroup::call_with_lock(realm_path, [&](std::string path) { + REALM_ASSERT_RELEASE_EX(realm_path.compare(path) == 0, realm_path.c_str(), path.c_str()); + env->CallVoidMethod(j_runnable, run_method); + TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env, nullptr); + }); + return result; + } + CATCH_STD() + return false; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 9a891e070e..b07e992dc8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -154,7 +154,10 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSchemaConfi reinterpret_cast(new_shared_realm_ptr), config_global.get(), obj, old_realm->schema_version()); }); - TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env); + // Close the SharedRealm. Otherwise it will only be closed when the Java OsSharedRealm gets GCed. And + // that will be too late. + TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED( + env, [&new_shared_realm_ptr]() { (*new_shared_realm_ptr)->close(); }); }; } else { @@ -185,7 +188,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetCompactOnLa result = env->CallBooleanMethod(obj, should_compact, static_cast(totalBytes), static_cast(usedBytes)); }); - TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env); + TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env, nullptr); return result; }; } @@ -226,7 +229,10 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetInitializat reinterpret_cast(new_shared_realm_ptr), config_global_ref.get(), obj); }); - TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env); + // Close the SharedRealm. Otherwise it will only be closed when the Java OsSharedRealm gets GCed. And + // that will be too late. + TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED( + env, [&new_shared_realm_ptr]() { (*new_shared_realm_ptr)->close(); }); }; } else { diff --git a/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp index a867145b08..6278fd7fb8 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp @@ -52,10 +52,14 @@ void JavaExceptionThrower::throw_java_exception(JNIEnv* env) env->ThrowNew(m_exception_class, message.c_str()); } -void JavaExceptionThrower::terminate_jni_if_java_exception_occurred(JNIEnv* env, const char* file_path, int line_num) +void JavaExceptionThrower::terminate_jni_if_java_exception_occurred(JNIEnv* env, CleanUpFunction clean_up_func, + const char* file_path, int line_num) { if (!env->ExceptionCheck()) { return; } + if (clean_up_func) { + clean_up_func(); + } throw JavaExceptionThrower(file_path, line_num); } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.hpp b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.hpp index e35bcc8a6d..488c5399b0 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.hpp @@ -19,6 +19,7 @@ #include +#include #include #include "java_class.hpp" @@ -29,13 +30,15 @@ namespace jni_util { #define THROW_JAVA_EXCEPTION(env, class_name, message) \ throw realm::jni_util::JavaExceptionThrower(env, class_name, message, __FILE__, __LINE__) -#define TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env) \ - JavaExceptionThrower::terminate_jni_if_java_exception_occurred(env, __FILE__, __LINE__); +#define TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env, clean_up_func) \ + JavaExceptionThrower::terminate_jni_if_java_exception_occurred(env, clean_up_func, __FILE__, __LINE__); // Class to help throw a Java exception from JNI code. // This exception will be called from CATCH_STD and throw a Java exception there. class JavaExceptionThrower : public std::runtime_error { public: + using CleanUpFunction = std::function; + JavaExceptionThrower(const char* file_path, int line_num); JavaExceptionThrower(JNIEnv* env, const char* class_name, std::string message, const char* file_path, int line_num); @@ -44,7 +47,9 @@ class JavaExceptionThrower : public std::runtime_error { // This method will throw a JavaExceptionThrower to terminate JNI then return to java if there is an Java // exception has been thrown before. - static void terminate_jni_if_java_exception_occurred(JNIEnv* env, const char* file_path, int line_num); + // clean_up_fucn will be called before throwing the c++ exception if there is a pending java exception. + static void terminate_jni_if_java_exception_occurred(JNIEnv* env, CleanUpFunction clean_up_func, + const char* file_path, int line_num); private: JavaClass m_exception_class; diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 6ba0f39a8b..aad610199f 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -609,20 +609,20 @@ public void deleteAll() { */ static boolean deleteRealm(final RealmConfiguration configuration) { final AtomicBoolean realmDeleted = new AtomicBoolean(true); - RealmCache.invokeWithGlobalRefCount(configuration, new RealmCache.Callback() { + boolean callbackExecuted = OsObjectStore.callWithLock(configuration, new Runnable() { @Override - public void onResult(int count) { - if (count != 0) { - throw new IllegalStateException("It's not allowed to delete the file associated with an open Realm. " + - "Remember to close() all the instances of the Realm before deleting its file: " + configuration.getPath()); - } - + public void run() { String canonicalPath = configuration.getPath(); File realmFolder = configuration.getRealmDirectory(); String realmFileName = configuration.getRealmFileName(); realmDeleted.set(Util.deleteRealm(canonicalPath, realmFolder, realmFileName)); } }); + if (!callbackExecuted) { + throw new IllegalStateException("It's not allowed to delete the file associated with an open Realm. " + + "Remember to close() all the instances of the Realm before deleting its file: " + + configuration.getPath()); + } return realmDeleted.get(); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 65681c1767..6e5428adfd 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1657,11 +1657,18 @@ public static void migrateRealm(RealmConfiguration configuration, @Nullable Real } /** - * Deletes the Realm file specified by the given {@link RealmConfiguration} from the filesystem. + * Deletes the Realm file along with the related temporary files specified by the given {@link RealmConfiguration} + * from the filesystem. Temporary file with ".lock" extension won't be deleted. + *

            * All Realm instances must be closed before calling this method. + *

            + * WARNING: For synchronized Realm, there is a chance that an internal Realm instance on the background thread is + * not closed even all the user controlled Realm instances are closed. This will result an + * {@code IllegalStateException}. See issue https://github.com/realm/realm-java/issues/5416 . * * @param configuration a {@link RealmConfiguration}. - * @return {@code false} if a file could not be deleted. The failing file will be logged. + * @return {@code false} if the Realm file could not be deleted. Temporary files deletion failure won't impact + * the return value. All of the failing file deletions will be logged. * @throws IllegalStateException if not all realm instances are closed. */ public static boolean deleteRealm(RealmConfiguration configuration) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 7adc914661..a7544f5da6 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -35,6 +35,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.internal.Capabilities; import io.realm.internal.ObjectServerFacade; +import io.realm.internal.OsObjectStore; import io.realm.internal.RealmNotifier; import io.realm.internal.SharedRealm; import io.realm.internal.Table; @@ -303,11 +304,12 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo } catch (Throwable t) { // If an error happened while downloading initial data, we need to reset the file so we can // download it again on the next attempt. - // Realm.deleteRealm() is under the same lock as this method and globalCount is still 0, so - // this should be safe. sharedRealm.close(); sharedRealm = null; - Realm.deleteRealm(configuration); + // FIXME: We don't have a way to ensure that the Realm instance on client thread has been + // closed for now. + // https://github.com/realm/realm-java/issues/5416 + BaseRealm.deleteRealm(configuration); throw t; } } @@ -492,20 +494,32 @@ synchronized void invokeWithLock(Callback0 callback) { * @param configuration configuration object for Realm instance. * @throws RealmFileException if copying the file fails. */ - private static void copyAssetFileIfNeeded(RealmConfiguration configuration) { - if (configuration.hasAssetFile()) { - File realmFile = new File(configuration.getRealmDirectory(), configuration.getRealmFileName()); - - copyFileIfNeeded(configuration.getAssetFilePath(), realmFile); - } + private static void copyAssetFileIfNeeded(final RealmConfiguration configuration) { + final File realmFileFromAsset = configuration.hasAssetFile() ? + new File(configuration.getRealmDirectory(), configuration.getRealmFileName()) + : null; + final String syncServerCertificateAssetName = ObjectServerFacade.getFacade( + configuration.isSyncConfiguration()).getSyncServerCertificateAssetName(configuration); + final boolean certFileExists = !Util.isEmptyString(syncServerCertificateAssetName); + + if (realmFileFromAsset!= null || certFileExists) { + OsObjectStore.callWithLock(configuration, new Runnable() { + @Override + public void run() { + if (realmFileFromAsset != null) { + copyFileIfNeeded(configuration.getAssetFilePath(), realmFileFromAsset); + } - // Copy Sync Server certificate path if available - String syncServerCertificateAssetName = ObjectServerFacade.getFacade(configuration.isSyncConfiguration()).getSyncServerCertificateAssetName(configuration); - if (!Util.isEmptyString(syncServerCertificateAssetName)) { - String syncServerCertificateFilePath = ObjectServerFacade.getFacade(configuration.isSyncConfiguration()).getSyncServerCertificateFilePath(configuration); + // Copy Sync Server certificate path if available + if (certFileExists) { + String syncServerCertificateFilePath = ObjectServerFacade.getFacade( + configuration.isSyncConfiguration()).getSyncServerCertificateFilePath(configuration); - File certificateFile = new File(syncServerCertificateFilePath); - copyFileIfNeeded(syncServerCertificateAssetName, certificateFile); + File certificateFile = new File(syncServerCertificateFilePath); + copyFileIfNeeded(syncServerCertificateAssetName, certificateFile); + } + } + }); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java index 5e05a43310..aa2b9f8192 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java @@ -18,6 +18,8 @@ import javax.annotation.Nullable; +import io.realm.RealmConfiguration; + /** * Java wrapper for methods in object_store.hpp. */ @@ -71,6 +73,19 @@ public static boolean deleteTableForObject(SharedRealm sharedRealm, String class return nativeDeleteTableForObject(sharedRealm.getNativePtr(), className); } + /** + * Try to grab an exclusive lock on the given Realm file. If the lock can be acquired, the {@code runnable} will be + * executed while the lock is held. The lock will ensure no one else can read from or write to the Realm file at the + * same time. + * + * @param configuration to specify the realm path. + * @param runnable to run with lock. + * @return {@code true} if the lock can be acquired and the {@code runnable} has been executed. + */ + public static boolean callWithLock(RealmConfiguration configuration, Runnable runnable) { + return nativeCallWithLock(configuration.getPath(), runnable); + } + private native static void nativeSetPrimaryKeyForObject(long sharedRealmPtr, String className, @Nullable String primaryKeyFieldName); @@ -81,4 +96,6 @@ private native static void nativeSetPrimaryKeyForObject(long sharedRealmPtr, Str private native static long nativeGetSchemaVersion(long sharedRealmPtr); private native static boolean nativeDeleteTableForObject(long sharedRealmPtr, String className); + + private native static boolean nativeCallWithLock(String realmPath, Runnable runnable); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index ce505b603f..6dc7389628 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -23,10 +23,12 @@ import java.io.StringWriter; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; +import io.realm.RealmConfiguration; import io.realm.RealmModel; import io.realm.RealmObject; import io.realm.log.RealmLog; @@ -96,46 +98,45 @@ public static boolean isEmptyString(@Nullable String str) { return str == null || str.length() == 0; } + /** + * To delete Realm and related temporary files. This must be called in + * {@link OsObjectStore#callWithLock(RealmConfiguration, Runnable)}'s callback. + * + * @return {@code true} if the realm file is deleted. Temporary file deletion failure will not impact the return + * value, instead, a warning will be logged. + */ public static boolean deleteRealm(String canonicalPath, File realmFolder, String realmFileName) { - boolean realmDeleted = true; final String management = ".management"; File managementFolder = new File(realmFolder, realmFileName + management); + File realmFile = new File(canonicalPath); // Deletes files in management directory and the directory. // There is no subfolders in the management directory. File[] files = managementFolder.listFiles(); if (files != null) { for (File file : files) { - realmDeleted = realmDeleted && file.delete(); + boolean deleteResult = file.delete(); + if (!deleteResult) { + RealmLog.warn( String.format(Locale.ENGLISH,"Realm temporary file at %s cannot be deleted", + file.getAbsolutePath())); + } } } - realmDeleted = realmDeleted && managementFolder.delete(); - - // Deletes specific files in root directory. - return realmDeleted && deletes(canonicalPath, realmFolder, realmFileName); - } + if (managementFolder.exists() && !managementFolder.delete()) { + RealmLog.warn( String.format(Locale.ENGLISH,"Realm temporary folder at %s cannot be deleted", + managementFolder.getAbsolutePath())); + } - private static boolean deletes(String canonicalPath, File rootFolder, String realmFileName) { - final AtomicBoolean realmDeleted = new AtomicBoolean(true); - - List filesToDelete = Arrays.asList( - new File(rootFolder, realmFileName), - new File(rootFolder, realmFileName + ".lock"), - // Old core log file naming styles - new File(rootFolder, realmFileName + ".log_a"), - new File(rootFolder, realmFileName + ".log_b"), - new File(rootFolder, realmFileName + ".log"), - new File(canonicalPath)); - for (File fileToDelete : filesToDelete) { - if (fileToDelete.exists()) { - boolean deleteResult = fileToDelete.delete(); - if (!deleteResult) { - realmDeleted.set(false); - RealmLog.warn("Could not delete the file %s", fileToDelete); - } + boolean realmDeleted; + if (realmFile.exists()) { + realmDeleted = realmFile.delete(); + if (!realmDeleted) { + RealmLog.warn(String.format(Locale.ENGLISH,"Realm file at %s cannot be deleted", + realmFile.getAbsolutePath())); } + } else { + realmDeleted = true; } - return realmDeleted.get(); + return realmDeleted; } - } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index ba321beb9f..7e581f6a6f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -20,6 +20,7 @@ import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; +import org.hamcrest.CoreMatchers; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,12 +32,14 @@ import io.realm.entities.StringOnly; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.OsRealmConfig; import io.realm.objectserver.utils.Constants; import io.realm.rule.RunTestInLooperThread; import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -58,7 +61,9 @@ public void waitForInitialRemoteData_mainThreadThrows() { try { realm = Realm.getInstance(config); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalStateException expected) { + assertThat(expected.getMessage(), CoreMatchers.containsString( + "downloadAllServerChanges() cannot be called from the main thread.")); } finally { if (realm != null) { realm.close(); @@ -75,6 +80,7 @@ public void waitForInitialRemoteData() throws InterruptedException { // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .schema(StringOnly.class) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); Realm realm = Realm.getInstance(configOld); realm.executeTransaction(new Realm.Transaction() { @@ -88,12 +94,12 @@ public void execute(Realm realm) { SyncManager.getSession(configOld).uploadAllLocalChanges(); realm.close(); user.logout(); - Realm.deleteRealm(configOld); - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes (pray it managed to do so within the time frame). + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .name("newRealm") .schema(StringOnly.class) .waitForInitialRemoteData() .build(); @@ -204,12 +210,12 @@ public void execute(Realm realm) { SyncManager.getSession(configOld).uploadAllLocalChanges(); realm.close(); user.logout(); - Realm.deleteRealm(configOld); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes (pray it managed to do so within the time frame). user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .name("newRealm") .waitForInitialRemoteData() .readOnly() .schema(StringOnly.class) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index d51f3520f0..ded87c8ca1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -70,11 +70,12 @@ public void onError(SyncSession session, ObjectServerError error) { SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. realm.close(); user.logout(); - Realm.deleteRealm(configWithEncryption); - // STEP 3: try to open again the Realm without the encryption key should not fail + // STEP 3: try to open again the same sync Realm but different local name without the encryption key should not + // fail user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); SyncConfiguration configWithoutEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .name("newName") .modules(new StringOnlyModule()) .waitForInitialRemoteData() .errorHandler(new SyncSession.ErrorHandler() { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index 4bc73892f1..15d3f2ae49 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -49,6 +49,7 @@ public void partialSync() throws InterruptedException { final SyncConfiguration partialSyncConfig = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .name("partialSync") .modules(new PartialSyncModule()) .partialRealm() .build(); @@ -93,7 +94,6 @@ public void partialSync() throws InterruptedException { SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); - Realm.deleteRealm(syncConfig); final CountDownLatch latch = new CountDownLatch(2); From daac897d06b7c9bc5d25adc7a3fe783a10543f1f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 17 Oct 2017 15:07:09 +0800 Subject: [PATCH 1029/2110] Add realm.ignoreKotlinNullability To disable treating kotlin non-null types as @Required. From v3.6.0, all the kotlin RealmModel's non-null field will be set as required in the schema. However it is a breaking change for the kotlin project which has a Realm file created before 3.6.0. To disable this behaviour introduced in 3.6.0, add below things to the project's build.gradle: kapt { arguments { arg("realm.ignoreKotlinNullability", true) } } Close #5412 --- CHANGELOG.md | 7 +++++++ .../main/java/io/realm/processor/ClassMetaData.java | 11 +++++++++++ .../main/java/io/realm/processor/RealmProcessor.java | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7faab46c3..e5f42c2eed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.0.1 (YYYY-MM-DD) + +## Bug Fixes + +* Added `realm.ignoreKotlinNullability` as a kapt argument to disable treating kotlin non-null types as `@Required` (#5412) (introduced in `v3.6.0`). + + ## 4.0.0 (2016-10-16) ## Breaking Changes diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index 3f9cae0b10..cb8b7a54c7 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -51,6 +51,8 @@ * Utility class for holding metadata for RealmProxy classes. */ public class ClassMetaData { + private static final String OPTION_IGNORE_KOTLIN_NULLABILITY = "realm.ignoreKotlinNullability"; + private final TypeElement classType; // Reference to model class. private final String className; // Model class simple name. private final List fields = new ArrayList(); // List of all fields in the class except those @Ignored. @@ -71,6 +73,8 @@ public class ClassMetaData { private final Types typeUtils; private final Elements elements; + private final boolean ignoreKotlinNullability; + public ClassMetaData(ProcessingEnvironment env, TypeMirrors typeMirrors, TypeElement clazz) { this.classType = clazz; this.className = clazz.getSimpleName().toString(); @@ -111,6 +115,9 @@ public ClassMetaData(ProcessingEnvironment env, TypeMirrors typeMirrors, TypeEle } } } + + ignoreKotlinNullability = Boolean.valueOf( + env.getOptions().getOrDefault(OPTION_IGNORE_KOTLIN_NULLABILITY, "false")); } @Override @@ -524,6 +531,10 @@ private boolean isRequiredField(VariableElement field) { return true; } + if (ignoreKotlinNullability) { + return false; + } + // Kotlin uses the `org.jetbrains.annotations.NotNull` annotation to mark non-null fields. // In order to fully support the Kotlin type system we interpret `@NotNull` as an alias // for `@Required` diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index 758a5ce031..1d68cae873 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -124,7 +124,7 @@ "io.realm.annotations.RealmModule", "io.realm.annotations.Required" }) -@SupportedOptions(value = {"realm.suppressWarnings"}) +@SupportedOptions(value = {"realm.suppressWarnings", "realm.ignoreKotlinNullability"}) public class RealmProcessor extends AbstractProcessor { // Don't consume annotations. This allows 3rd party annotation processors to run. From b4987486c1edd3cf0076fa25c49fd839220e5ae8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 18 Oct 2017 14:36:12 +0800 Subject: [PATCH 1030/2110] Miss setTestName --- .../java/io/realm/rule/TestRealmConfigurationFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java index a82d87c530..74a0ec5a15 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -57,6 +57,7 @@ public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { + setTestName(description); before(); try { base.evaluate(); From 380edac7fa34f979e64103b05cbce374c709e5d4 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 18 Oct 2017 18:07:41 +0800 Subject: [PATCH 1031/2110] Fix progress listener flaky tests close #5245 --- .../objectserver/ProgressListenerTests.java | 70 ++++++++++++------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index 307c46660a..b2e0fb21c7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -23,6 +23,7 @@ import org.junit.runner.RunWith; import java.net.URI; +import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -82,17 +83,32 @@ private void assertTransferComplete(Progress progress, boolean nonZeroChange) { // Create remote data for a given user. private URI createRemoteData(SyncUser user) { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .name("remote") + .build(); final Realm realm = Realm.getInstance(config); - writeSampleData(realm); final CountDownLatch changesUploaded = new CountDownLatch(1); final SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + final long beforeAdd = realm.where(AllTypes.class).count(); + writeSampleData(realm); + + session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { @Override public void onChange(Progress progress) { if (progress.isTransferComplete()) { - session.removeProgressListener(this); - changesUploaded.countDown(); + Realm realm = Realm.getInstance(config); + final long afterAdd = realm.where(AllTypes.class).count(); + realm.close(); + + RealmLog.warn(String.format(Locale.ENGLISH,"createRemoteData upload %d/%d objects count:%d", + progress.getTransferredBytes(), progress.getTransferableBytes(), afterAdd)); + // FIXME: Remove this after https://github.com/realm/realm-object-store/issues/581 + if (afterAdd == TEST_SIZE + beforeAdd) { + session.removeProgressListener(this); + changesUploaded.countDown(); + } else if (afterAdd < TEST_SIZE + beforeAdd) { + fail("The added objects are more than expected."); + } } } }); @@ -148,50 +164,51 @@ public void run() { worker.start(); SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - final SyncConfiguration adminConfig = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()).build(); + final SyncConfiguration adminConfig = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()) + .name("local") + .build(); Realm adminRealm = Realm.getInstance(adminConfig); Realm userRealm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM).build()); // Keep session alive SyncSession session = SyncManager.getSession(adminConfig); session.addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { @Override public void onChange(Progress progress) { - if (progress.isTransferComplete()) { + Realm adminRealm = Realm.getInstance(adminConfig); + long objectCounts = adminRealm.where(AllTypes.class).count(); + adminRealm.close(); + // The downloading progress listener could be triggered at the db version where only contains the meta + // data. So we start checking from when the first 10 objects downloaded. + if (objectCounts != 0 && progress.isTransferComplete()) { + switch (transferCompleted.incrementAndGet()) { - case 1: - // Initial trigger when registering - assertTransferComplete(progress, false); - break; - case 2: { + case 1: { + assertEquals(TEST_SIZE, objectCounts); assertTransferComplete(progress, true); - Realm adminRealm = Realm.getInstance(adminConfig); - assertEquals(TEST_SIZE, adminRealm.where(AllTypes.class).count()); - adminRealm.close(); startWorker.countDown(); break; } - case 3: { + case 2: { assertTransferComplete(progress, true); - Realm adminRealm = Realm.getInstance(adminConfig); - assertEquals(TEST_SIZE * 2, adminRealm.where(AllTypes.class).count()); - adminRealm.close(); + assertEquals(TEST_SIZE * 2, objectCounts); allChangesDownloaded.countDown(); break; } default: fail("Transfer complete called too many times:" + transferCompleted.get()); } + RealmLog.warn(String.format( + Locale.ENGLISH,"downloadProgressListener_indefinitely download %d/%d objects count:%d", + progress.getTransferredBytes(), progress.getTransferableBytes(), objectCounts)); } } }); TestHelper.awaitOrFail(allChangesDownloaded); adminRealm.close(); userRealm.close(); + // worker thread will hang if logout happens before listener triggered. + worker.join(); userWithData.logout(); adminUser.logout(); - // FIXME sometimes the worker thread doesn't terminate - // causing the test thread to wait indefinitely until it times out - // https://github.com/realm/realm-java/issues/5245 - worker.join(); } // Make sure that a ProgressListener continues to report the correct thing, even if it crashed @@ -263,7 +280,12 @@ public void uploadProgressListener_indefinitely() { session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { @Override public void onChange(Progress progress) { - if (progress.isTransferComplete()) { + Realm tempRealm = Realm.getInstance(config); + long objectsCount = tempRealm.where(AllTypes.class).count(); + tempRealm.close(); + // FIXME: Remove the objectsCount checking when + // https://github.com/realm/realm-object-store/issues/581 gets fixed + if (objectsCount != 0 && progress.isTransferComplete()) { switch(transferCompleted.incrementAndGet()) { case 1: Realm realm = Realm.getInstance(config); From 05fdb9258340c50d1bed0fb05abf149ca17908d7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 19 Oct 2017 13:31:50 +0800 Subject: [PATCH 1032/2110] Ignore downloadProgressListener_indefinitely Need fix https://github.com/realm/realm-sync/issues/1770 --- .../java/io/realm/objectserver/ProgressListenerTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index b2e0fb21c7..d757107729 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -18,6 +18,7 @@ import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -144,6 +145,7 @@ public void onChange(Progress progress) { } @Test + @Ignore("https://github.com/realm/realm-sync/issues/1770") public void downloadProgressListener_indefinitely() throws InterruptedException { final AtomicInteger transferCompleted = new AtomicInteger(0); final CountDownLatch allChangesDownloaded = new CountDownLatch(1); From fe56c690493fdbcae1d4059687b4a88bc41b04e5 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 20 Oct 2017 11:16:19 +0800 Subject: [PATCH 1033/2110] Increase http timeout Otherwise integration tests would fail when setting the network type to GSM on emulator. --- CHANGELOG.md | 1 + .../io/realm/internal/network/OkHttpAuthenticationServer.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f42c2eed..756e30cd02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Bug Fixes * Added `realm.ignoreKotlinNullability` as a kapt argument to disable treating kotlin non-null types as `@Required` (#5412) (introduced in `v3.6.0`). +* Increased http connect/write timeout for low bandwidth network. ## 4.0.0 (2016-10-16) diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index c6fdeb9f36..4783b3d42e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -41,8 +41,8 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { private static final String ACTION_LOOKUP_USER_ID = "users/:provider:/:providerId:"; // Auth end point for looking up user id private final OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(10, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) // using custom Connection Pool to evict idle connection after 5 seconds rather than 5 minutes (which is the default) // keeping idle connection on the pool will prevent the ROS to be stopped, since the HttpUtils#stopSyncServer query From 657a8ee4cfea5ba3879da53ffd3516a05346971a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 20 Oct 2017 17:14:04 +0800 Subject: [PATCH 1034/2110] Merge entries in changelog --- CHANGELOG.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4222c347d..efd1477afb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,21 +7,11 @@ ## Bug Fixes * Fix some potential database corruption caused by deleting the Realm file while a Realm instance are still opened in another process or the sync client thread. - -## Internal - -### Credits - - -## 4.0.1 (YYYY-MM-DD) - -## Bug Fixes - * Added `realm.ignoreKotlinNullability` as a kapt argument to disable treating kotlin non-null types as `@Required` (#5412) (introduced in `v3.6.0`). * Increased http connect/write timeout for low bandwidth network. -## 4.0.0 (2016-10-16) +## 4.0.0 (2017-10-16) ## Breaking Changes From f2d6bf6d323f21a8168a91f1b7d91235f49e2b9b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 20 Oct 2017 17:14:11 +0800 Subject: [PATCH 1035/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd1477afb..75c09e5384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.1.0 (YYYY-MM-DD) +## 4.1.0 (2017-10-20) ## Enhancements From bce3ef5d6bad3aa50dc86b80d965ba233b7610a8 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 20 Oct 2017 17:14:12 +0800 Subject: [PATCH 1036/2110] Release v4.1.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 83a328a922..99eba4de93 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.1.0-SNAPSHOT \ No newline at end of file +4.1.0 \ No newline at end of file From 79f12c76450859d391830a529248d3708250c2cb Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 20 Oct 2017 17:14:12 +0800 Subject: [PATCH 1037/2110] Prepare next release v4.1.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 99eba4de93..2f81801b79 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.1.0 \ No newline at end of file +4.1.1-SNAPSHOT \ No newline at end of file From 8ec4517d089a96a8c9065025cdb40d8aa08d47fe Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 23 Oct 2017 14:21:00 +0800 Subject: [PATCH 1038/2110] Update ROS to 2.0.4 (#5440) --- dependencies.list | 2 +- tools/sync_test_server/Dockerfile | 4 ++++ tools/sync_test_server/ros-testing-server.js | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dependencies.list b/dependencies.list index 838c222f42..198c90ffc8 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,5 +5,5 @@ REALM_SYNC_SHA256=33c9dace6dc280712101110895d38509bbca74fdb31ba31b61dc0ad383472b # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.0-rc.11 +REALM_OBJECT_SERVER_DE_VERSION=2.0.4 diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 8a936242db..4a79c0f023 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -11,4 +11,8 @@ RUN npm install winston temp httpdispatcher@1.0.0 fs-extra moment COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ +#Bypass the ROS license check +ENV DOCKER_DATA_PATH / +ENV ROS_TOS_EMAIL_ADDRESS 'ci@realm.io' + CMD /usr/bin/ros-testing-server.js /tmp/ros-testing-server.log diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index 6225970ad2..d86165a6e8 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -72,7 +72,6 @@ function startRealmObjectServer(onSuccess, onError) { var env = Object.create( process.env ); winston.info(env.NODE_ENV); env.NODE_ENV = 'development'; - env.JENKINS = 1; // Skip email check in ROS // Manually cleanup Global Notifier State // See https://github.com/realm/ros/issues/437#issuecomment-335380095 From d8eee1ae95914a6a8837603157448f9ebd4091fd Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 20 Oct 2017 15:04:59 +0800 Subject: [PATCH 1039/2110] Clean up SharedRealm.java - Re-structure codes. Move inner classes to the top, followed by the fields defines. - Format code. - Remove unused var collections. --- .../java/io/realm/internal/SharedRealm.java | 117 +++++++++--------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 32a52cc644..3c72498e09 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -33,49 +33,6 @@ @Keep public final class SharedRealm implements Closeable, NativeObject { - // Const value for RealmFileException conversion - public static final byte FILE_EXCEPTION_KIND_ACCESS_ERROR = 0; - public static final byte FILE_EXCEPTION_KIND_BAD_HISTORY = 1; - public static final byte FILE_EXCEPTION_KIND_PERMISSION_DENIED = 2; - public static final byte FILE_EXCEPTION_KIND_EXISTS = 3; - public static final byte FILE_EXCEPTION_KIND_NOT_FOUND = 4; - public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 5; - public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 6; - public static final byte FILE_EXCEPTION_INCOMPATIBLE_SYNC_FILE = 7; - private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - - public static void initialize(File tempDirectory) { - if (SharedRealm.temporaryDirectory != null) { - // already initialized - return; - } - - String temporaryDirectoryPath = tempDirectory.getAbsolutePath(); - if (!tempDirectory.isDirectory() && !tempDirectory.mkdirs() && !tempDirectory.isDirectory()) { - throw new IOException("failed to create temporary directory: " + temporaryDirectoryPath); - } - - if (!temporaryDirectoryPath.endsWith("/")) { - temporaryDirectoryPath += "/"; - } - nativeInit(temporaryDirectoryPath); - SharedRealm.temporaryDirectory = tempDirectory; - } - - public static File getTemporaryDirectory() { - return temporaryDirectory; - } - - private static volatile File temporaryDirectory; - - private final List> pendingRows = new CopyOnWriteArrayList<>(); - public final List> collections = new CopyOnWriteArrayList<>(); - public final List> iterators = new ArrayList<>(); - - // JNI will only hold a weak global ref to this. - public final RealmNotifier realmNotifier; - public final Capabilities capabilities; - public static class VersionID implements Comparable { public final long version; public final long index; @@ -140,9 +97,9 @@ public interface MigrationCallback { * Callback function. * * @param sharedRealm the same {@link SharedRealm} instance which has been created from the same - * {@link OsRealmConfig} instance. - * @param oldVersion the schema version of the existing Realm file. - * @param newVersion the expected schema version after migration. + * {@link OsRealmConfig} instance. + * @param oldVersion the schema version of the existing Realm file. + * @param newVersion the expected schema version after migration. */ void onMigrationNeeded(SharedRealm sharedRealm, long oldVersion, long newVersion); } @@ -161,9 +118,10 @@ public interface InitializationCallback { /** * Callback function to be called from JNI by Object Store when the schema is changed. */ - @SuppressWarnings("unused") @Keep public interface SchemaChangedCallback { + // Called from JNI + @SuppressWarnings("unused") void onSchemaChanged(); } @@ -179,13 +137,33 @@ protected PartialSyncCallback(String className) { } public abstract void onSuccess(Collection results); + public abstract void onError(RealmException error); } - private final OsRealmConfig osRealmConfig; + // Const value for RealmFileException conversion + public static final byte FILE_EXCEPTION_KIND_ACCESS_ERROR = 0; + public static final byte FILE_EXCEPTION_KIND_BAD_HISTORY = 1; + public static final byte FILE_EXCEPTION_KIND_PERMISSION_DENIED = 2; + public static final byte FILE_EXCEPTION_KIND_EXISTS = 3; + public static final byte FILE_EXCEPTION_KIND_NOT_FOUND = 4; + public static final byte FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE = 5; + public static final byte FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED = 6; + public static final byte FILE_EXCEPTION_INCOMPATIBLE_SYNC_FILE = 7; + + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final long nativePtr; + private final OsRealmConfig osRealmConfig; final NativeContext context; private final OsSchemaInfo schemaInfo; + private static volatile File temporaryDirectory; + // JNI will only hold a weak global ref to this. + public final RealmNotifier realmNotifier; + public final Capabilities capabilities; + + private final List> pendingRows = new CopyOnWriteArrayList<>(); + // Package protected for testing + final List> iterators = new ArrayList<>(); private SharedRealm(OsRealmConfig osRealmConfig) { Capabilities capabilities = new AndroidCapabilities(); @@ -241,6 +219,28 @@ public static SharedRealm getInstance(OsRealmConfig.Builder configBuilder) { return new SharedRealm(osRealmConfig); } + public static void initialize(File tempDirectory) { + if (SharedRealm.temporaryDirectory != null) { + // already initialized + return; + } + + String temporaryDirectoryPath = tempDirectory.getAbsolutePath(); + if (!tempDirectory.isDirectory() && !tempDirectory.mkdirs() && !tempDirectory.isDirectory()) { + throw new IOException("failed to create temporary directory: " + temporaryDirectoryPath); + } + + if (!temporaryDirectoryPath.endsWith("/")) { + temporaryDirectoryPath += "/"; + } + nativeInit(temporaryDirectoryPath); + SharedRealm.temporaryDirectory = tempDirectory; + } + + public static File getTemporaryDirectory() { + return temporaryDirectory; + } + public void beginTransaction() { detachIterators(); executePendingRowQueries(); @@ -290,12 +290,12 @@ public Table createTable(String name) { * Creates a {@link Table} and adds a primary key field to it. Native assertion will happen if the table with the * same name exists. * - * @param tableName the name of table. + * @param tableName the name of table. * @param primaryKeyFieldName the name of primary key field. - * @param isStringType if this is true, the primary key field will be create as a string field. Otherwise it will - * be created as an integer field. - * @param isNullable if the primary key field is nullable or not. - * @return a creatd {@link Table} object. + * @param isStringType if this is true, the primary key field will be create as a string field. Otherwise it will + * be created as an integer field. + * @param isNullable if the primary key field is nullable or not. + * @return a newly created {@link Table} object. */ public Table createTableWithPrimaryKey(String tableName, String primaryKeyFieldName, boolean isStringType, boolean isNullable) { @@ -420,7 +420,7 @@ void addIterator(Collection.Iterator iterator) { } // The detaching should happen before transaction begins. - void detachIterators() { + private void detachIterators() { for (WeakReference iteratorRef : iterators) { Collection.Iterator iterator = iteratorRef.get(); if (iterator != null) { @@ -476,7 +476,7 @@ private void executePendingRowQueries() { /** * Called from JNI when the expected schema doesn't match the existing one. * - * @param callback the {@link MigrationCallback} in the {@link RealmConfiguration}. + * @param callback the {@link MigrationCallback} in the {@link RealmConfiguration}. * @param oldVersion the schema version of the existing Realm file. */ @SuppressWarnings("unused") @@ -498,13 +498,14 @@ private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealm /** * Called from JNI when the partial sync callback is invoked from the ObjectStore. - * @param error if the partial sync query failed to register. + * + * @param error if the partial sync query failed to register. * @param nativeResultsPtr pointer to the {@code Results} of the partial sync query. - * @param callback the callback registered from the user to notify the success/error of the partial sync query. + * @param callback the callback registered from the user to notify the success/error of the partial sync query. */ @SuppressWarnings("unused") private void runPartialSyncRegistrationCallback(@Nullable String error, long nativeResultsPtr, - PartialSyncCallback callback) { + PartialSyncCallback callback) { if (error != null) { callback.onError(new RealmException(error)); } else { From c30bc22134403d8a98ecb492d9a6deaf2db6e73f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 23 Oct 2017 16:25:14 +0800 Subject: [PATCH 1040/2110] Rename Collection to OsResults --- .../java/io/realm/RealmResultsTests.java | 14 +- .../androidTest/java/io/realm/SortTest.java | 32 +-- .../androidTest/java/io/realm/TestHelper.java | 12 +- ...llectionTests.java => OsResultsTests.java} | 224 +++++++++--------- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../main/cpp/io_realm_internal_Collection.cpp | 66 +++--- .../cpp/observable_collection_wrapper.hpp | 4 +- .../src/main/java/io/realm/BaseRealm.java | 2 +- .../io/realm/OrderedRealmCollectionImpl.java | 90 +++---- .../realm/OrderedRealmCollectionSnapshot.java | 22 +- .../src/main/java/io/realm/Realm.java | 3 +- .../src/main/java/io/realm/RealmList.java | 11 +- .../src/main/java/io/realm/RealmQuery.java | 14 +- .../src/main/java/io/realm/RealmResults.java | 35 ++- .../{Collection.java => OsResults.java} | 78 +++--- .../java/io/realm/internal/PendingRow.java | 16 +- .../java/io/realm/internal/SharedRealm.java | 16 +- 17 files changed, 320 insertions(+), 321 deletions(-) rename realm/realm-library/src/androidTest/java/io/realm/internal/{CollectionTests.java => OsResultsTests.java} (64%) rename realm/realm-library/src/main/java/io/realm/internal/{Collection.java => OsResults.java} (85%) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index d087c59f40..8ab8c830a9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -41,7 +41,7 @@ import io.realm.entities.Owner; import io.realm.entities.RandomPrimaryKey; import io.realm.entities.StringOnly; -import io.realm.internal.Collection; +import io.realm.internal.OsResults; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -99,15 +99,15 @@ public void findFirst() { @Test public void size_returns_Integer_MAX_VALUE_for_huge_results() { - final Collection collection = Mockito.mock(Collection.class); - final RealmResults targetResult = TestHelper.newRealmResults(realm, collection, AllTypes.class); + final OsResults osResults = Mockito.mock(OsResults.class); + final RealmResults targetResult = TestHelper.newRealmResults(realm, osResults, AllTypes.class); - Mockito.when(collection.isLoaded()).thenReturn(true); - Mockito.when(collection.size()).thenReturn(((long) Integer.MAX_VALUE) - 1); + Mockito.when(osResults.isLoaded()).thenReturn(true); + Mockito.when(osResults.size()).thenReturn(((long) Integer.MAX_VALUE) - 1); assertEquals(Integer.MAX_VALUE - 1, targetResult.size()); - Mockito.when(collection.size()).thenReturn(((long) Integer.MAX_VALUE)); + Mockito.when(osResults.size()).thenReturn(((long) Integer.MAX_VALUE)); assertEquals(Integer.MAX_VALUE, targetResult.size()); - Mockito.when(collection.size()).thenReturn(((long) Integer.MAX_VALUE) + 1); + Mockito.when(osResults.size()).thenReturn(((long) Integer.MAX_VALUE) + 1); assertEquals(Integer.MAX_VALUE, targetResult.size()); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index b21e48984a..287d17bf44 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -182,19 +182,19 @@ private void checkSortTwoFieldsStringAscendingIntAscending(RealmResults results) { @@ -208,19 +208,19 @@ private void checkSortTwoFieldsIntString(RealmResults results) { assertEquals("Adam", results.get(0).getColumnString()); assertEquals(4, results.get(0).getColumnLong()); - assertEquals(0, results.getCollection().indexOf(getRowBySourceIndexFromAllTypesTable(2))); + assertEquals(0, results.getOsResults().indexOf(getRowBySourceIndexFromAllTypesTable(2))); assertEquals("Brian", results.get(1).getColumnString()); assertEquals(4, results.get(1).getColumnLong()); - assertEquals(1, results.getCollection().indexOf(getRowBySourceIndexFromAllTypesTable(1))); + assertEquals(1, results.getOsResults().indexOf(getRowBySourceIndexFromAllTypesTable(1))); assertEquals("Adam", results.get(2).getColumnString()); assertEquals(5, results.get(2).getColumnLong()); - assertEquals(2, results.getCollection().indexOf(getRowBySourceIndexFromAllTypesTable(0))); + assertEquals(2, results.getOsResults().indexOf(getRowBySourceIndexFromAllTypesTable(0))); assertEquals("Adam", results.get(3).getColumnString()); assertEquals(5, results.get(3).getColumnLong()); - assertEquals(3, results.getCollection().indexOf(getRowBySourceIndexFromAllTypesTable(3))); + assertEquals(3, results.getOsResults().indexOf(getRowBySourceIndexFromAllTypesTable(3))); } private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -234,19 +234,19 @@ private void checkSortTwoFieldsIntAscendingStringDescending(RealmResults results) { @@ -260,19 +260,19 @@ private void checkSortTwoFieldsStringAscendingIntDescending(RealmResults RealmResults newRealmResults( - BaseRealm realm, Collection collection, Class tableClass) { + BaseRealm realm, OsResults osResults, Class tableClass) { //noinspection TryWithIdenticalCatches try { final Constructor c = RealmResults.class.getDeclaredConstructor( - BaseRealm.class, Collection.class, Class.class); + BaseRealm.class, OsResults.class, Class.class); c.setAccessible(true); //noinspection unchecked - return c.newInstance(realm, collection, tableClass); + return c.newInstance(realm, osResults, tableClass); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InstantiationException e) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java similarity index 64% rename from realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java rename to realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java index 9175df0012..c81e4c1aed 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java @@ -47,7 +47,7 @@ @RunWith(AndroidJUnit4.class) -public class CollectionTests { +public class OsResultsTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule @@ -152,19 +152,19 @@ private void addRow(SharedRealm sharedRealm) { @Test public void constructor_withDistinct() { SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(null, table, "firstName"); - Collection collection = new Collection(sharedRealm, table.where(), null, distinctDescriptor); + OsResults osResults = new OsResults(sharedRealm, table.where(), null, distinctDescriptor); - assertEquals(3, collection.size()); - assertEquals("John", collection.getUncheckedRow(0).getString(0)); - assertEquals("Erik", collection.getUncheckedRow(1).getString(0)); - assertEquals("Henry", collection.getUncheckedRow(2).getString(0)); + assertEquals(3, osResults.size()); + assertEquals("John", osResults.getUncheckedRow(0).getString(0)); + assertEquals("Erik", osResults.getUncheckedRow(1).getString(0)); + assertEquals("Henry", osResults.getUncheckedRow(2).getString(0)); } @Test(expected = UnsupportedOperationException.class) public void constructor_queryIsValidated() { - // Collection's constructor should call TableQuery.validateQuery() - new Collection(sharedRealm, table.where().or()); + // OsResults's constructor should call TableQuery.validateQuery() + new OsResults(sharedRealm, table.where().or()); } @Test @@ -175,86 +175,86 @@ public void constructor_queryOnDeletedTable() { sharedRealm.commitTransaction(); // Query should be checked before creating OS Results. thrown.expect(IllegalStateException.class); - new Collection(sharedRealm, query); + new OsResults(sharedRealm, query); } @Test public void size() { - Collection collection = new Collection(sharedRealm, table.where()); - assertEquals(4, collection.size()); + OsResults osResults = new OsResults(sharedRealm, table.where()); + assertEquals(4, osResults.size()); } @Test public void where() { - Collection collection = new Collection(sharedRealm, table.where()); - Collection collection2 = new Collection(sharedRealm, collection.where().equalTo(new long[] {0}, oneNullTable, "John")); - Collection collection3 = new Collection(sharedRealm, collection2.where().equalTo(new long[] {1}, oneNullTable, "Anderson")); + OsResults osResults = new OsResults(sharedRealm, table.where()); + OsResults osResults2 = new OsResults(sharedRealm, osResults.where().equalTo(new long[] {0}, oneNullTable, "John")); + OsResults osResults3 = new OsResults(sharedRealm, osResults2.where().equalTo(new long[] {1}, oneNullTable, "Anderson")); // A new native Results should be created. - assertTrue(collection.getNativePtr() != collection2.getNativePtr()); - assertTrue(collection2.getNativePtr() != collection3.getNativePtr()); + assertTrue(osResults.getNativePtr() != osResults2.getNativePtr()); + assertTrue(osResults2.getNativePtr() != osResults3.getNativePtr()); - assertEquals(4, collection.size()); - assertEquals(2, collection2.size()); - assertEquals(1, collection3.size()); + assertEquals(4, osResults.size()); + assertEquals(2, osResults2.size()); + assertEquals(1, osResults3.size()); } @Test public void sort() { - Collection collection = new Collection(sharedRealm, table.where().greaterThan(new long[] {2}, oneNullTable, 1)); + OsResults osResults = new OsResults(sharedRealm, table.where().greaterThan(new long[] {2}, oneNullTable, 1)); SortDescriptor sortDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); - Collection collection2 = collection.sort(sortDescriptor); + OsResults osResults2 = osResults.sort(sortDescriptor); // A new native Results should be created. - assertTrue(collection.getNativePtr() != collection2.getNativePtr()); - assertEquals(2, collection.size()); - assertEquals(2, collection2.size()); + assertTrue(osResults.getNativePtr() != osResults2.getNativePtr()); + assertEquals(2, osResults.size()); + assertEquals(2, osResults2.size()); - assertEquals(3, collection2.getUncheckedRow(0).getLong(2)); - assertEquals(4, collection2.getUncheckedRow(1).getLong(2)); + assertEquals(3, osResults2.getUncheckedRow(0).getLong(2)); + assertEquals(4, osResults2.getUncheckedRow(1).getLong(2)); } @Test public void clear() { assertEquals(4, table.size()); - Collection collection = new Collection(sharedRealm, table.where()); + OsResults osResults = new OsResults(sharedRealm, table.where()); sharedRealm.beginTransaction(); - collection.clear(); + osResults.clear(); sharedRealm.commitTransaction(); assertEquals(0, table.size()); } @Test public void contains() { - Collection collection = new Collection(sharedRealm, table.where()); + OsResults osResults = new OsResults(sharedRealm, table.where()); UncheckedRow row = table.getUncheckedRow(0); - assertTrue(collection.contains(row)); + assertTrue(osResults.contains(row)); } @Test public void indexOf() { SortDescriptor sortDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); - Collection collection = new Collection(sharedRealm, table.where(), sortDescriptor); + OsResults osResults = new OsResults(sharedRealm, table.where(), sortDescriptor); UncheckedRow row = table.getUncheckedRow(0); - assertEquals(3, collection.indexOf(row)); + assertEquals(3, osResults.indexOf(row)); } @Test public void distinct() { - Collection collection = new Collection(sharedRealm, table.where().lessThan(new long[] {2}, oneNullTable, 4)); + OsResults osResults = new OsResults(sharedRealm, table.where().lessThan(new long[] {2}, oneNullTable, 4)); SortDescriptor distinctDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); - Collection collection2 = collection.distinct(distinctDescriptor); + OsResults osResults2 = osResults.distinct(distinctDescriptor); // A new native Results should be created. - assertTrue(collection.getNativePtr() != collection2.getNativePtr()); - assertEquals(3, collection.size()); - assertEquals(2, collection2.size()); + assertTrue(osResults.getNativePtr() != osResults2.getNativePtr()); + assertEquals(3, osResults.size()); + assertEquals(2, osResults2.size()); - assertEquals(3, collection2.getUncheckedRow(0).getLong(2)); - assertEquals(1, collection2.getUncheckedRow(1).getLong(2)); + assertEquals(3, osResults2.getUncheckedRow(0).getLong(2)); + assertEquals(1, osResults2.getUncheckedRow(1).getLong(2)); } // 1. Create a results and add listener. @@ -266,13 +266,13 @@ public void addListener_shouldBeCalledToReturnTheQueryResults() { populateData(sharedRealm); Table table = getTable(sharedRealm); - final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference(collection); - collection.addListener(collection, new RealmChangeListener() { + final OsResults osResults = new OsResults(sharedRealm, table.where()); + looperThread.keepStrongReference(osResults); + osResults.addListener(osResults, new RealmChangeListener() { @Override - public void onChange(Collection collection1) { - assertEquals(collection, collection1); - assertEquals(4, collection1.size()); + public void onChange(OsResults osResults1) { + assertEquals(osResults, osResults1); + assertEquals(4, osResults1.size()); sharedRealm.close(); looperThread.testComplete(); } @@ -287,12 +287,12 @@ public void addListener_shouldBeCalledWhenRefreshToReturnTheQueryResults() { final SharedRealm sharedRealm = getSharedRealm(); Table table = getTable(sharedRealm); - final Collection collection = new Collection(sharedRealm, table.where()); - collection.addListener(collection, new RealmChangeListener() { + final OsResults osResults = new OsResults(sharedRealm, table.where()); + osResults.addListener(osResults, new RealmChangeListener() { @Override - public void onChange(Collection collection1) { - assertEquals(collection, collection1); - assertEquals(4, collection1.size()); + public void onChange(OsResults osResults1) { + assertEquals(osResults, osResults1); + assertEquals(4, osResults1.size()); sharedRealm.close(); onChangeCalled.set(true); } @@ -304,17 +304,17 @@ public void onChange(Collection collection1) { @Test public void addListener_shouldBeCalledWhenRefreshAfterLocalCommit() { final CountDownLatch latch = new CountDownLatch(2); - final Collection collection = new Collection(sharedRealm, table.where()); - assertEquals(4, collection.size()); // See `populateData()` - collection.addListener(collection, new RealmChangeListener() { + final OsResults osResults = new OsResults(sharedRealm, table.where()); + assertEquals(4, osResults.size()); // See `populateData()` + osResults.addListener(osResults, new RealmChangeListener() { @Override - public void onChange(Collection element) { + public void onChange(OsResults element) { if (latch.getCount() == 2) { // triggered by beginTransaction - assertEquals(4, collection.size()); + assertEquals(4, osResults.size()); } else if (latch.getCount() == 1) { // triggered by refresh - assertEquals(5, collection.size()); + assertEquals(5, osResults.size()); } else { fail(); } @@ -332,11 +332,11 @@ public void onChange(Collection element) { @Test public void addListener_triggeredByRefresh() { final CountDownLatch latch = new CountDownLatch(1); - Collection collection = new Collection(sharedRealm, table.where()); - collection.size(); - collection.addListener(collection, new RealmChangeListener() { + OsResults osResults = new OsResults(sharedRealm, table.where()); + osResults.size(); + osResults.addListener(osResults, new RealmChangeListener() { @Override - public void onChange(Collection element) { + public void onChange(OsResults element) { assertEquals(1, latch.getCount()); latch.countDown(); } @@ -356,13 +356,13 @@ public void addListener_queryNotReturned() { populateData(sharedRealm); Table table = getTable(sharedRealm); - final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference(collection); - collection.addListener(collection, new RealmChangeListener() { + final OsResults osResults = new OsResults(sharedRealm, table.where()); + looperThread.keepStrongReference(osResults); + osResults.addListener(osResults, new RealmChangeListener() { @Override - public void onChange(Collection collection1) { - assertEquals(collection, collection1); - assertEquals(5, collection1.size()); + public void onChange(OsResults osResults1) { + assertEquals(osResults, osResults1); + assertEquals(5, osResults1.size()); sharedRealm.close(); looperThread.testComplete(); } @@ -378,14 +378,14 @@ public void addListener_queryReturned() { populateData(sharedRealm); Table table = getTable(sharedRealm); - final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference(collection); - assertEquals(4, collection.size()); // Trigger the query to run. - collection.addListener(collection, new RealmChangeListener() { + final OsResults osResults = new OsResults(sharedRealm, table.where()); + looperThread.keepStrongReference(osResults); + assertEquals(4, osResults.size()); // Trigger the query to run. + osResults.addListener(osResults, new RealmChangeListener() { @Override - public void onChange(Collection collection1) { - assertEquals(collection, collection1); - assertEquals(5, collection1.size()); + public void onChange(OsResults osResults1) { + assertEquals(osResults, osResults1); + assertEquals(5, osResults1.size()); sharedRealm.close(); looperThread.testComplete(); } @@ -404,17 +404,17 @@ public void addListener_triggeredByLocalCommit() { Table table = getTable(sharedRealm); final AtomicInteger listenerCounter = new AtomicInteger(0); - final Collection collection = new Collection(sharedRealm, table.where()); - looperThread.keepStrongReference(collection); - collection.addListener(collection, new RealmChangeListener() { + final OsResults osResults = new OsResults(sharedRealm, table.where()); + looperThread.keepStrongReference(osResults); + osResults.addListener(osResults, new RealmChangeListener() { @Override - public void onChange(Collection collection1) { + public void onChange(OsResults osResults1) { switch (listenerCounter.getAndIncrement()) { case 0: - assertEquals(4, collection1.size()); + assertEquals(4, osResults1.size()); break; case 1: - assertEquals(5, collection1.size()); + assertEquals(5, osResults1.size()); sharedRealm.close(); break; default: @@ -428,9 +428,9 @@ public void onChange(Collection collection1) { looperThread.testComplete(); } - private static class TestIterator extends Collection.Iterator { - TestIterator(Collection collection) { - super(collection); + private static class TestIterator extends OsResults.Iterator { + TestIterator(OsResults osResults) { + super(osResults); } @Override @@ -439,8 +439,8 @@ protected Integer convertRowToObject(UncheckedRow row) { } boolean isDetached(SharedRealm sharedRealm) { - for (WeakReference iteratorRef : sharedRealm.iterators) { - Collection.Iterator iterator = iteratorRef.get(); + for (WeakReference iteratorRef : sharedRealm.iterators) { + OsResults.Iterator iterator = iteratorRef.get(); if (iterator == this) { return false; } @@ -451,8 +451,8 @@ boolean isDetached(SharedRealm sharedRealm) { @Test public void collectionIterator_detach_byBeginTransaction() { - final Collection collection = new Collection(sharedRealm, table.where()); - TestIterator iterator = new TestIterator(collection); + final OsResults osResults = new OsResults(sharedRealm, table.where()); + TestIterator iterator = new TestIterator(osResults); assertFalse(iterator.isDetached(sharedRealm)); sharedRealm.beginTransaction(); assertTrue(iterator.isDetached(sharedRealm)); @@ -463,15 +463,15 @@ public void collectionIterator_detach_byBeginTransaction() { @Test public void collectionIterator_detach_createdInTransaction() { sharedRealm.beginTransaction(); - final Collection collection = new Collection(sharedRealm, table.where()); - TestIterator iterator = new TestIterator(collection); + final OsResults osResults = new OsResults(sharedRealm, table.where()); + TestIterator iterator = new TestIterator(osResults); assertTrue(iterator.isDetached(sharedRealm)); } @Test public void collectionIterator_invalid_nonLooperThread_byRefresh() { - final Collection collection = new Collection(sharedRealm, table.where()); - TestIterator iterator = new TestIterator(collection); + final OsResults osResults = new OsResults(sharedRealm, table.where()); + TestIterator iterator = new TestIterator(osResults); assertFalse(iterator.isDetached(sharedRealm)); sharedRealm.refresh(); thrown.expect(ConcurrentModificationException.class); @@ -484,13 +484,13 @@ public void collectionIterator_invalid_looperThread_byRemoteTransaction() { final SharedRealm sharedRealm = getSharedRealmForLooper(); populateData(sharedRealm); Table table = getTable(sharedRealm); - final Collection collection = new Collection(sharedRealm, table.where()); - final TestIterator iterator = new TestIterator(collection); - looperThread.keepStrongReference(collection); + final OsResults osResults = new OsResults(sharedRealm, table.where()); + final TestIterator iterator = new TestIterator(osResults); + looperThread.keepStrongReference(osResults); assertFalse(iterator.isDetached(sharedRealm)); - collection.addListener(collection, new RealmChangeListener() { + osResults.addListener(osResults, new RealmChangeListener() { @Override - public void onChange(Collection element) { + public void onChange(OsResults element) { try { iterator.checkValid(); fail(); @@ -506,29 +506,29 @@ public void onChange(Collection element) { @Test public void collectionIterator_newInstance_throwsWhenSharedRealmIsClosed() { - final Collection collection = new Collection(sharedRealm, table.where()); + final OsResults osResults = new OsResults(sharedRealm, table.where()); sharedRealm.close(); thrown.expect(IllegalStateException.class); - new TestIterator(collection); + new TestIterator(osResults); } @Test public void getMode() { - Collection collection = new Collection(sharedRealm, table.where()); - assertTrue(Collection.Mode.QUERY == collection.getMode()); - collection.firstUncheckedRow(); // Run the query - assertTrue(Collection.Mode.TABLEVIEW == collection.getMode()); + OsResults osResults = new OsResults(sharedRealm, table.where()); + assertTrue(OsResults.Mode.QUERY == osResults.getMode()); + osResults.firstUncheckedRow(); // Run the query + assertTrue(OsResults.Mode.TABLEVIEW == osResults.getMode()); } @Test public void createSnapshot() { - Collection collection = new Collection(sharedRealm, table.where()); - Collection snapshot = collection.createSnapshot(); - assertTrue(Collection.Mode.TABLEVIEW == snapshot.getMode()); + OsResults osResults = new OsResults(sharedRealm, table.where()); + OsResults snapshot = osResults.createSnapshot(); + assertTrue(OsResults.Mode.TABLEVIEW == snapshot.getMode()); thrown.expect(IllegalStateException.class); - snapshot.addListener(snapshot, new RealmChangeListener() { + snapshot.addListener(snapshot, new RealmChangeListener() { @Override - public void onChange(Collection element) { + public void onChange(OsResults element) { } }); } @@ -539,15 +539,15 @@ public void load() { final SharedRealm sharedRealm = getSharedRealmForLooper(); looperThread.closeAfterTest(sharedRealm); populateData(sharedRealm); - final Collection collection = new Collection(sharedRealm, table.where()); - collection.addListener(collection, new RealmChangeListener() { + final OsResults osResults = new OsResults(sharedRealm, table.where()); + osResults.addListener(osResults, new RealmChangeListener() { @Override - public void onChange(Collection element) { - assertTrue(collection.isLoaded()); + public void onChange(OsResults element) { + assertTrue(osResults.isLoaded()); looperThread.testComplete(); } }); - assertFalse(collection.isLoaded()); - collection.load(); + assertFalse(osResults.isLoaded()); + osResults.load(); } } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index eb1d5f79e2..c36935cb8d 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -60,7 +60,7 @@ set(classes_LIST io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.internal.Property io.realm.internal.OsSchemaInfo - io.realm.internal.OsObjectSchemaInfo io.realm.internal.Collection + io.realm.internal.OsObjectSchemaInfo io.realm.internal.OsResults io.realm.internal.NativeObjectReference io.realm.internal.OsCollectionChangeSet io.realm.internal.OsObject io.realm.internal.OsRealmConfig io.realm.internal.OsList io.realm.internal.OsObjectStore diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp index e9b6330a90..26f53313cc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_internal_Collection.h" +#include "io_realm_internal_OsResults.h" #include #include @@ -39,7 +39,7 @@ static void finalize_results(jlong ptr) delete reinterpret_cast(ptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResults(JNIEnv* env, jclass, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResults(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong query_ptr, jobject j_sort_desc, jobject j_distinct_desc) @@ -70,7 +70,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResults(JN return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResultsFromList(JNIEnv* env, jclass, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResultsFromList(JNIEnv* env, jclass, jlong shared_realm_ptr, jlong list_ptr, jobject j_sort_desc) @@ -91,7 +91,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResultsFro return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr); try { @@ -104,7 +104,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateSnapshot(J return reinterpret_cast(nullptr); } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeContains(JNIEnv* env, jclass, jlong native_ptr, +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeContains(JNIEnv* env, jclass, jlong native_ptr, jlong native_row_ptr) { TR_ENTER_PTR(native_ptr); @@ -118,7 +118,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeContains(JNIE return JNI_FALSE; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeGetRow(JNIEnv* env, jclass, jlong native_ptr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeGetRow(JNIEnv* env, jclass, jlong native_ptr, jint index) { TR_ENTER_PTR(native_ptr) @@ -131,7 +131,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeGetRow(JNIEnv* e return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeFirstRow(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -145,7 +145,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeFirstRow(JNIEnv* return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeLastRow(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeLastRow(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -159,7 +159,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeLastRow(JNIEnv* return reinterpret_cast(nullptr); } -JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeClear(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeClear(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -169,7 +169,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeClear(JNIEnv* env CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeSize(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeSize(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -180,7 +180,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeSize(JNIEnv* env return 0; } -JNIEXPORT jobject JNICALL Java_io_realm_internal_Collection_nativeAggregate(JNIEnv* env, jclass, jlong native_ptr, +JNIEXPORT jobject JNICALL Java_io_realm_internal_OsResults_nativeAggregate(JNIEnv* env, jclass, jlong native_ptr, jlong column_index, jbyte agg_func) { TR_ENTER_PTR(native_ptr) @@ -190,13 +190,13 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_Collection_nativeAggregate(JNIE size_t index = S(column_index); Optional value; switch (agg_func) { - case io_realm_internal_Collection_AGGREGATE_FUNCTION_MINIMUM: + case io_realm_internal_OsResults_AGGREGATE_FUNCTION_MINIMUM: value = wrapper->collection().min(index); break; - case io_realm_internal_Collection_AGGREGATE_FUNCTION_MAXIMUM: + case io_realm_internal_OsResults_AGGREGATE_FUNCTION_MAXIMUM: value = wrapper->collection().max(index); break; - case io_realm_internal_Collection_AGGREGATE_FUNCTION_AVERAGE: { + case io_realm_internal_OsResults_AGGREGATE_FUNCTION_AVERAGE: { Optional value_count(wrapper->collection().average(index)); if (value_count) { value = Optional(Mixed(value_count.value())); @@ -206,7 +206,7 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_Collection_nativeAggregate(JNIE } break; } - case io_realm_internal_Collection_AGGREGATE_FUNCTION_SUM: + case io_realm_internal_OsResults_AGGREGATE_FUNCTION_SUM: value = wrapper->collection().sum(index); break; default: @@ -235,7 +235,7 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_Collection_nativeAggregate(JNIE return static_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeSort(JNIEnv* env, jclass, jlong native_ptr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeSort(JNIEnv* env, jclass, jlong native_ptr, jobject j_sort_desc) { TR_ENTER_PTR(native_ptr) @@ -248,7 +248,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeSort(JNIEnv* env return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeDistinct(JNIEnv* env, jclass, jlong native_ptr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeDistinct(JNIEnv* env, jclass, jlong native_ptr, jobject j_distinct_desc) { TR_ENTER_PTR(native_ptr) @@ -262,7 +262,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeDistinct(JNIEnv* return reinterpret_cast(nullptr); } -JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeStartListening(JNIEnv* env, jobject instance, +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeStartListening(JNIEnv* env, jobject instance, jlong native_ptr) { TR_ENTER_PTR(native_ptr) @@ -274,7 +274,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeStartListening(JN CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) { TR_ENTER_PTR(native_ptr) @@ -285,13 +285,13 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeStopListening(JNI CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeGetFinalizerPtr(JNIEnv*, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_results); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeWhere(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeWhere(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -306,7 +306,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeWhere(JNIEnv* en return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv* env, jclass, jlong native_ptr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeIndexOf(JNIEnv* env, jclass, jlong native_ptr, jlong row_native_ptr) { TR_ENTER_PTR(native_ptr) @@ -320,7 +320,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeIndexOf(JNIEnv* return npos; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteLast(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeDeleteLast(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -335,7 +335,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteLast(JN return JNI_FALSE; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteFirst(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeDeleteFirst(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) @@ -351,7 +351,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeDeleteFirst(J return JNI_FALSE; } -JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeDelete(JNIEnv* env, jclass, jlong native_ptr, +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeDelete(JNIEnv* env, jclass, jlong native_ptr, jlong index) { TR_ENTER_PTR(native_ptr) @@ -366,7 +366,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Collection_nativeDelete(JNIEnv* en CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeIsValid(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeIsValid(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { @@ -377,29 +377,29 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Collection_nativeIsValid(JNIEn return JNI_FALSE; } -JNIEXPORT jbyte JNICALL Java_io_realm_internal_Collection_nativeGetMode(JNIEnv* env, jclass, jlong native_ptr) +JNIEXPORT jbyte JNICALL Java_io_realm_internal_OsResults_nativeGetMode(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); switch (wrapper->collection().get_mode()) { case Results::Mode::Empty: - return io_realm_internal_Collection_MODE_EMPTY; + return io_realm_internal_OsResults_MODE_EMPTY; case Results::Mode::Table: - return io_realm_internal_Collection_MODE_TABLE; + return io_realm_internal_OsResults_MODE_TABLE; case Results::Mode::Query: - return io_realm_internal_Collection_MODE_QUERY; + return io_realm_internal_OsResults_MODE_QUERY; case Results::Mode::LinkView: - return io_realm_internal_Collection_MODE_LINKVIEW; + return io_realm_internal_OsResults_MODE_LINKVIEW; case Results::Mode::TableView: - return io_realm_internal_Collection_MODE_TABLEVIEW; + return io_realm_internal_OsResults_MODE_TABLEVIEW; } } CATCH_STD() return -1; // Invalid mode value } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Collection_nativeCreateResultsFromBacklinks(JNIEnv *env, jclass, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResultsFromBacklinks(JNIEnv *env, jclass, jlong shared_realm_ptr, jlong row_ptr, jlong src_table_ptr, diff --git a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp index ed53c62c9f..6b86d91138 100644 --- a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp +++ b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp @@ -26,8 +26,8 @@ namespace realm { namespace _impl { // Wrapper of Object Store List & Results. -// We need to control the life cycle of Results/List, weak ref of Java Collection object and the NotificationToken. -// Wrap all three together, so when the Java Collection object gets GCed, all three of them will be invalidated. +// We need to control the life cycle of Results/List, weak ref of Java OsResults/OsList object and the NotificationToken. +// Wrap all three together, so when the Java OsResults/OsList object gets GCed, all three of them will be invalidated. template class ObservableCollectionWrapper { public: diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index aad610199f..aa3f85e9e4 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -543,7 +543,7 @@ public boolean isEmpty() { */ public abstract RealmSchema getSchema(); - // Used by RealmList/RealmResults, to create RealmObject from a Collection. + // Used by RealmList/RealmResults, to create RealmObject from a OsResults. // Invariant: if dynamicClassName != null -> clazz == DynamicRealmObject E get(@Nullable Class clazz, @Nullable String dynamicClassName, UncheckedRow row) { final boolean isDynamicRealmObject = dynamicClassName != null; diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index 39163349d9..da24bf45d9 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -10,7 +10,7 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.internal.Collection; +import io.realm.internal.OsResults; import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; import io.realm.internal.SortDescriptor; @@ -33,29 +33,29 @@ abstract class OrderedRealmCollectionImpl @SuppressFBWarnings("SS_SHOULD_BE_STATIC") final boolean forValues = false; - final Collection collection; + final OsResults osResults; - OrderedRealmCollectionImpl(BaseRealm realm, Collection collection, Class clazz) { - this(realm, collection, clazz, null); + OrderedRealmCollectionImpl(BaseRealm realm, OsResults osResults, Class clazz) { + this(realm, osResults, clazz, null); } - OrderedRealmCollectionImpl(BaseRealm realm, Collection collection, String className) { - this(realm, collection, null, className); + OrderedRealmCollectionImpl(BaseRealm realm, OsResults osResults, String className) { + this(realm, osResults, null, className); } - private OrderedRealmCollectionImpl(BaseRealm realm, Collection collection, @Nullable Class clazz, @Nullable String className) { + private OrderedRealmCollectionImpl(BaseRealm realm, OsResults osResults, @Nullable Class clazz, @Nullable String className) { this.realm = realm; - this.collection = collection; + this.osResults = osResults; this.classSpec = clazz; this.className = className; } Table getTable() { - return collection.getTable(); + return osResults.getTable(); } - Collection getCollection() { - return collection; + OsResults getOsResults() { + return osResults; } /** @@ -63,7 +63,7 @@ Collection getCollection() { */ @Override public boolean isValid() { - return collection.isValid(); + return osResults.isValid(); } /** @@ -121,7 +121,7 @@ public E get(int location) { } //noinspection unchecked - return (E) realm.get((Class) classSpec, className, collection.getUncheckedRow(location)); + return (E) realm.get((Class) classSpec, className, osResults.getUncheckedRow(location)); } /** @@ -144,7 +144,7 @@ public E first(@Nullable E defaultValue) { @Nullable private E firstImpl(boolean shouldThrow, @Nullable E defaultValue) { - UncheckedRow row = collection.firstUncheckedRow(); + UncheckedRow row = osResults.firstUncheckedRow(); if (forValues) { // TODO implement this @@ -184,7 +184,7 @@ public E last(@Nullable E defaultValue) { @Nullable private E lastImpl(boolean shouldThrow, @Nullable E defaultValue) { - UncheckedRow row = collection.lastUncheckedRow(); + UncheckedRow row = osResults.lastUncheckedRow(); if (forValues) { // TODO implement this @@ -210,7 +210,7 @@ private E lastImpl(boolean shouldThrow, @Nullable E defaultValue) { public void deleteFromRealm(int location) { // TODO: Implement the delete in OS level and do check there! realm.checkIfValidAndInTransaction(); - collection.delete(location); + osResults.delete(location); } /** @@ -220,7 +220,7 @@ public void deleteFromRealm(int location) { public boolean deleteAllFromRealm() { realm.checkIfValid(); if (size() > 0) { - collection.clear(); + osResults.clear(); return true; } return false; @@ -277,7 +277,7 @@ private long getColumnIndexForSort(String fieldName) { if (fieldName.contains(".")) { throw new IllegalArgumentException("Aggregates on child object fields are not supported: " + fieldName); } - long columnIndex = collection.getTable().getColumnIndex(fieldName); + long columnIndex = osResults.getTable().getColumnIndex(fieldName); if (columnIndex < 0) { throw new IllegalArgumentException(String.format(Locale.US, "Field '%s' does not exist.", fieldName)); } @@ -290,10 +290,10 @@ private long getColumnIndexForSort(String fieldName) { @Override public RealmResults sort(String fieldName) { SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(getSchemaConnector(), collection.getTable(), fieldName, Sort.ASCENDING); + SortDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldName, Sort.ASCENDING); - Collection sortedCollection = collection.sort(sortDescriptor); - return createLoadedResults(sortedCollection); + OsResults sortedOsResults = osResults.sort(sortDescriptor); + return createLoadedResults(sortedOsResults); } /** @@ -302,10 +302,10 @@ public RealmResults sort(String fieldName) { @Override public RealmResults sort(String fieldName, Sort sortOrder) { SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(getSchemaConnector(), collection.getTable(), fieldName, sortOrder); + SortDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldName, sortOrder); - Collection sortedCollection = collection.sort(sortDescriptor); - return createLoadedResults(sortedCollection); + OsResults sortedOsResults = osResults.sort(sortDescriptor); + return createLoadedResults(sortedOsResults); } /** @@ -314,10 +314,10 @@ public RealmResults sort(String fieldName, Sort sortOrder) { @Override public RealmResults sort(String fieldNames[], Sort sortOrders[]) { SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(getSchemaConnector(), collection.getTable(), fieldNames, sortOrders); + SortDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldNames, sortOrders); - Collection sortedCollection = collection.sort(sortDescriptor); - return createLoadedResults(sortedCollection); + OsResults sortedOsResults = osResults.sort(sortDescriptor); + return createLoadedResults(sortedOsResults); } /** @@ -338,7 +338,7 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName @Override public int size() { if (isLoaded()) { - long size = collection.size(); + long size = osResults.size(); return (size > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) size; } return 0; @@ -351,7 +351,7 @@ public int size() { public Number min(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateNumber(io.realm.internal.Collection.Aggregate.MINIMUM, columnIndex); + return osResults.aggregateNumber(OsResults.Aggregate.MINIMUM, columnIndex); } /** @@ -361,7 +361,7 @@ public Number min(String fieldName) { public Date minDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateDate(Collection.Aggregate.MINIMUM, columnIndex); + return osResults.aggregateDate(OsResults.Aggregate.MINIMUM, columnIndex); } /** @@ -371,7 +371,7 @@ public Date minDate(String fieldName) { public Number max(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateNumber(Collection.Aggregate.MAXIMUM, columnIndex); + return osResults.aggregateNumber(OsResults.Aggregate.MAXIMUM, columnIndex); } /** @@ -389,7 +389,7 @@ public Number max(String fieldName) { public Date maxDate(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateDate(Collection.Aggregate.MAXIMUM, columnIndex); + return osResults.aggregateDate(OsResults.Aggregate.MAXIMUM, columnIndex); } @@ -400,7 +400,7 @@ public Date maxDate(String fieldName) { public Number sum(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - return collection.aggregateNumber(Collection.Aggregate.SUM, columnIndex); + return osResults.aggregateNumber(OsResults.Aggregate.SUM, columnIndex); } /** @@ -411,7 +411,7 @@ public double average(String fieldName) { realm.checkIfValid(); long columnIndex = getColumnIndexForSort(fieldName); - Number avg = collection.aggregateNumber(Collection.Aggregate.AVERAGE, columnIndex); + Number avg = osResults.aggregateNumber(OsResults.Aggregate.AVERAGE, columnIndex); return avg.doubleValue(); } @@ -481,7 +481,7 @@ public boolean retainAll(@SuppressWarnings("NullableProblems") java.util.Collect public boolean deleteLastFromRealm() { // TODO: Implement the deleteLast in OS level and do check there! realm.checkIfValidAndInTransaction(); - return collection.deleteLast(); + return osResults.deleteLast(); } /** @@ -493,7 +493,7 @@ public boolean deleteLastFromRealm() { public boolean deleteFirstFromRealm() { // TODO: Implement the deleteLast in OS level and do check there! realm.checkIfValidAndInTransaction(); - return collection.deleteFirst(); + return osResults.deleteFirst(); } /** @@ -553,9 +553,9 @@ public boolean addAll(@SuppressWarnings("NullableProblems") java.util.Collection } // Custom RealmResults iterator. It ensures that we only iterate on a Realm that hasn't changed. - private class RealmCollectionIterator extends Collection.Iterator { + private class RealmCollectionIterator extends OsResults.Iterator { RealmCollectionIterator() { - super(OrderedRealmCollectionImpl.this.collection); + super(OrderedRealmCollectionImpl.this.osResults); } @Override @@ -572,18 +572,18 @@ protected E convertRowToObject(UncheckedRow row) { @Override public OrderedRealmCollectionSnapshot createSnapshot() { if (className != null) { - return new OrderedRealmCollectionSnapshot(realm, collection, className); + return new OrderedRealmCollectionSnapshot(realm, osResults, className); } else { // 'classSpec' is non-null when 'className' is null. //noinspection ConstantConditions - return new OrderedRealmCollectionSnapshot(realm, collection, classSpec); + return new OrderedRealmCollectionSnapshot(realm, osResults, classSpec); } } // Custom RealmResults list iterator. - private class RealmCollectionListIterator extends Collection.ListIterator { + private class RealmCollectionListIterator extends OsResults.ListIterator { RealmCollectionListIterator(int start) { - super(OrderedRealmCollectionImpl.this.collection, start); + super(OrderedRealmCollectionImpl.this.osResults, start); } @Override @@ -597,14 +597,14 @@ protected E convertRowToObject(UncheckedRow row) { } } - RealmResults createLoadedResults(Collection newCollection) { + RealmResults createLoadedResults(OsResults newOsResults) { RealmResults results; if (className != null) { - results = new RealmResults(realm, newCollection, className); + results = new RealmResults(realm, newOsResults, className); } else { // 'classSpec' is non-null when 'className' is null. //noinspection ConstantConditions - results = new RealmResults(realm, newCollection, classSpec); + results = new RealmResults(realm, newOsResults, classSpec); } results.load(); return results; diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java index 5e4ce01545..6b2d9fd30a 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java @@ -18,7 +18,7 @@ import java.util.Locale; -import io.realm.internal.Collection; +import io.realm.internal.OsResults; import io.realm.internal.UncheckedRow; @@ -52,12 +52,12 @@ public class OrderedRealmCollectionSnapshot extends OrderedRealmCollectionImp private int size = -1; - OrderedRealmCollectionSnapshot(BaseRealm realm, Collection collection, Class clazz) { - super(realm, collection.createSnapshot(), clazz); + OrderedRealmCollectionSnapshot(BaseRealm realm, OsResults osResults, Class clazz) { + super(realm, osResults.createSnapshot(), clazz); } - OrderedRealmCollectionSnapshot(BaseRealm realm, Collection collection, String className) { - super(realm, collection.createSnapshot(), className); + OrderedRealmCollectionSnapshot(BaseRealm realm, OsResults osResults, String className) { + super(realm, osResults.createSnapshot(), className); } /** @@ -171,9 +171,9 @@ public OrderedRealmCollectionSnapshot createSnapshot() { @Override public void deleteFromRealm(int location) { realm.checkIfValidAndInTransaction(); - UncheckedRow row = collection.getUncheckedRow(location); + UncheckedRow row = osResults.getUncheckedRow(location); if (row.isAttached()) { - collection.delete(location); + osResults.delete(location); } } @@ -186,8 +186,8 @@ public void deleteFromRealm(int location) { @Override public boolean deleteFirstFromRealm() { realm.checkIfValidAndInTransaction(); - UncheckedRow row = collection.firstUncheckedRow(); - return row != null && row.isAttached() && collection.deleteFirst(); + UncheckedRow row = osResults.firstUncheckedRow(); + return row != null && row.isAttached() && osResults.deleteFirst(); } /** @@ -199,8 +199,8 @@ public boolean deleteFirstFromRealm() { @Override public boolean deleteLastFromRealm() { realm.checkIfValidAndInTransaction(); - UncheckedRow row = collection.lastUncheckedRow(); - return row != null && row.isAttached() && collection.deleteLast(); + UncheckedRow row = osResults.lastUncheckedRow(); + return row != null && row.isAttached() && osResults.deleteLast(); } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 6e5428adfd..c4da704041 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -58,6 +58,7 @@ import io.realm.internal.OsObject; import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsObjectStore; +import io.realm.internal.OsResults; import io.realm.internal.OsSchemaInfo; import io.realm.internal.RealmCore; import io.realm.internal.RealmNotifier; @@ -1720,7 +1721,7 @@ public void subscribeToObjects(final Class clazz, Stri String className = configuration.getSchemaMediator().getSimpleClassName(clazz); SharedRealm.PartialSyncCallback internalCallback = new SharedRealm.PartialSyncCallback(className) { @Override - public void onSuccess(io.realm.internal.Collection osResults) { + public void onSuccess(OsResults osResults) { RealmResults results = new RealmResults<>(Realm.this, osResults, clazz); callback.onSuccess(results); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 45c5521fe2..e1ba427ce4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -36,6 +36,7 @@ import io.realm.internal.InvalidRow; import io.realm.internal.OsList; import io.realm.internal.OsObjectStore; +import io.realm.internal.OsResults; import io.realm.internal.RealmObjectProxy; import io.realm.rx.CollectionChange; @@ -74,7 +75,7 @@ public class RealmList extends AbstractList implements OrderedRealmCollect final protected BaseRealm realm; private List unmanagedList; // Used for listeners on RealmList - private io.realm.internal.Collection osResults; + private OsResults osResults; /** * Creates a RealmList in unmanaged mode, where the elements are not controlled by a Realm. @@ -760,14 +761,14 @@ public OrderedRealmCollectionSnapshot createSnapshot() { if (className != null) { return new OrderedRealmCollectionSnapshot<>( realm, - new io.realm.internal.Collection(realm.sharedRealm, osListOperator.getOsList(), null), + new OsResults(realm.sharedRealm, osListOperator.getOsList(), null), className); } else { // 'clazz' is non-null when 'dynamicClassName' is null. //noinspection ConstantConditions return new OrderedRealmCollectionSnapshot<>( realm, - new io.realm.internal.Collection(realm.sharedRealm, osListOperator.getOsList(), null), + new OsResults(realm.sharedRealm, osListOperator.getOsList(), null), clazz); } } @@ -1289,9 +1290,9 @@ private ManagedListOperator getOperator(BaseRealm realm, OsList osList, @Null // new element. By right results it means the change set only include one insertion. But if the listener is on the // OS List, the change set will include all ranges of th list. So we keep the old behaviour for // RealmList for now. See https://github.com/realm/realm-object-store/issues/541 - private io.realm.internal.Collection getOrCreateOsResultsForListener() { + private OsResults getOrCreateOsResultsForListener() { if (osResults == null) { - this.osResults = new io.realm.internal.Collection(realm.sharedRealm, osListOperator.getOsList(), null); + this.osResults = new OsResults(realm.sharedRealm, osListOperator.getOsList(), null); } return osResults; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 7825f3c72e..89fafb6b95 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -24,7 +24,7 @@ import javax.annotation.Nullable; import io.realm.annotations.Required; -import io.realm.internal.Collection; +import io.realm.internal.OsResults; import io.realm.internal.OsList; import io.realm.internal.PendingRow; import io.realm.internal.RealmObjectProxy; @@ -160,7 +160,7 @@ private RealmQuery(RealmResults queryResults, Class clazz) { this.schema = realm.getSchema().getSchemaForClass((Class) clazz); this.table = queryResults.getTable(); this.osList = null; - this.query = queryResults.getCollection().where(); + this.query = queryResults.getOsResults().where(); } } @@ -199,7 +199,7 @@ private RealmQuery(RealmResults queryResults, String classNa this.forValues = false; this.schema = realm.getSchema().getSchemaForClass(className); this.table = schema.getTable(); - this.query = queryResults.getCollection().where(); + this.query = queryResults.getOsResults().where(); this.osList = null; } @@ -2002,7 +2002,7 @@ public E findFirstAsync() { if (realm.isInTransaction()) { // It is not possible to create async query inside a transaction. So immediately query the first object. // See OS Results::prepare_async() - row = new Collection(realm.sharedRealm, query).firstUncheckedRow(); + row = new OsResults(realm.sharedRealm, query).firstUncheckedRow(); } else { // prepares an empty reference of the RealmObject which is backed by a pending query, // then update it once the query complete in the background. @@ -2038,11 +2038,11 @@ private RealmResults createRealmResults(TableQuery query, @Nullable SortDescriptor distinctDescriptor, boolean loadResults) { RealmResults results; - Collection collection = new Collection(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); + OsResults osResults = new OsResults(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); if (isDynamicQuery()) { - results = new RealmResults<>(realm, collection, className); + results = new RealmResults<>(realm, osResults, className); } else { - results = new RealmResults<>(realm, collection, clazz); + results = new RealmResults<>(realm, osResults, clazz); } if (loadResults) { results.load(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index e7892701dd..9a553a092f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -22,13 +22,12 @@ import io.reactivex.Flowable; import io.reactivex.Observable; -import javax.annotation.Nonnull; + import javax.annotation.Nullable; import io.realm.internal.CheckedRow; -import io.realm.internal.Collection; +import io.realm.internal.OsResults; import io.realm.internal.Row; -import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.rx.CollectionChange; @@ -69,7 +68,7 @@ static RealmResults createBacklinkResults(BaseRealm re Table srcTable = realm.getSchema().getTable(srcTableType); return new RealmResults<>( realm, - Collection.createBacklinksCollection(realm.sharedRealm, uncheckedRow, srcTable, srcFieldName), + OsResults.createBacklinksCollection(realm.sharedRealm, uncheckedRow, srcTable, srcFieldName), srcTableType); } @@ -79,16 +78,16 @@ static RealmResults createDynamicBacklinkResults(DynamicReal //noinspection ConstantConditions return new RealmResults<>( realm, - Collection.createBacklinksCollection(realm.sharedRealm, row, srcTable, srcFieldName), + OsResults.createBacklinksCollection(realm.sharedRealm, row, srcTable, srcFieldName), srcClassName); } - RealmResults(BaseRealm realm, Collection collection, Class clazz) { - super(realm, collection, clazz); + RealmResults(BaseRealm realm, OsResults osResults, Class clazz) { + super(realm, osResults, clazz); } - RealmResults(BaseRealm realm, Collection collection, String className) { - super(realm, collection, className); + RealmResults(BaseRealm realm, OsResults osResults, String className) { + super(realm, osResults, className); } /** @@ -118,7 +117,7 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName @Override public boolean isLoaded() { realm.checkIfValid(); - return collection.isLoaded(); + return osResults.isLoaded(); } /** @@ -129,12 +128,12 @@ public boolean isLoaded() { */ @Override public boolean load() { - // The Collection doesn't have to be loaded before accessing it if the query has not returned. - // Instead, accessing the Collection will just trigger the execution of query if needed. We add this flag is + // The OsResults doesn't have to be loaded before accessing it if the query has not returned. + // Instead, accessing the OsResults will just trigger the execution of query if needed. We add this flag is // only to keep the original behavior of those APIs. eg.: For a async RealmResults, before query returns, the // size() call should return 0 instead of running the query get the real size. realm.checkIfValid(); - collection.load(); + osResults.load(); return true; } @@ -173,7 +172,7 @@ public boolean load() { */ public void addChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); - collection.addListener(this, listener); + osResults.addListener(this, listener); } /** @@ -211,7 +210,7 @@ public void addChangeListener(RealmChangeListener> listener) { */ public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - collection.addListener(this, listener); + osResults.addListener(this, listener); } private void checkForAddRemoveListener(@Nullable Object listener, boolean checkListener) { @@ -230,7 +229,7 @@ private void checkForAddRemoveListener(@Nullable Object listener, boolean checkL */ public void removeAllChangeListeners() { checkForAddRemoveListener(null, false); - collection.removeAllListeners(); + osResults.removeAllListeners(); } /** @@ -243,7 +242,7 @@ public void removeAllChangeListeners() { */ public void removeChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); - collection.removeListener(this, listener); + osResults.removeListener(this, listener); } /** @@ -256,7 +255,7 @@ public void removeChangeListener(RealmChangeListener> listener) */ public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - collection.removeListener(this, listener); + osResults.removeListener(this, listener); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/Collection.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java similarity index 85% rename from realm/realm-library/src/main/java/io/realm/internal/Collection.java rename to realm/realm-library/src/main/java/io/realm/internal/OsResults.java index df0b2c6cf0..ce294974ea 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Collection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -30,32 +30,32 @@ * Java wrapper of Object Store Results class. * It is the backend of binding's query results and back links. */ -public class Collection implements NativeObject, ObservableCollection { +public class OsResults implements NativeObject, ObservableCollection { private static final String CLOSED_REALM_MESSAGE = "This Realm instance has already been closed, making it unusable."; - // Custom Collection iterator. It ensures that we only iterate on a Realm collection that hasn't changed. + // Custom OsResults iterator. It ensures that we only iterate on a Realm OsResults that hasn't changed. public static abstract class Iterator implements java.util.Iterator { - Collection iteratorCollection; + OsResults iteratorOsResults; protected int pos = -1; - public Iterator(Collection collection) { - if (collection.sharedRealm.isClosed()) { + public Iterator(OsResults osResults) { + if (osResults.sharedRealm.isClosed()) { throw new IllegalStateException(CLOSED_REALM_MESSAGE); } - this.iteratorCollection = collection; + this.iteratorOsResults = osResults; - if (collection.isSnapshot) { + if (osResults.isSnapshot) { // No need to detach a snapshot. return; } - if (collection.sharedRealm.isInTransaction()) { + if (osResults.sharedRealm.isInTransaction()) { detach(); } else { - iteratorCollection.sharedRealm.addIterator(this); + iteratorOsResults.sharedRealm.addIterator(this); } } @@ -65,7 +65,7 @@ public Iterator(Collection collection) { @Override public boolean hasNext() { checkValid(); - return pos + 1 < iteratorCollection.size(); + return pos + 1 < iteratorOsResults.size(); } /** @@ -76,8 +76,8 @@ public boolean hasNext() { public T next() { checkValid(); pos++; - if (pos >= iteratorCollection.size()) { - throw new NoSuchElementException("Cannot access index " + pos + " when size is " + iteratorCollection.size() + + if (pos >= iteratorOsResults.size()) { + throw new NoSuchElementException("Cannot access index " + pos + " when size is " + iteratorOsResults.size() + ". Remember to check hasNext() before using next()."); } return get(pos); @@ -95,7 +95,7 @@ public void remove() { } void detach() { - iteratorCollection = iteratorCollection.createSnapshot(); + iteratorOsResults = iteratorOsResults.createSnapshot(); } // The iterator becomes invalid after receiving a remote change notification. In Java, the destruction of @@ -103,11 +103,11 @@ void detach() { // like what realm-cocoa does, we will have a massive overhead since all the iterators created in the previous // event loop need to be detached. void invalidate() { - iteratorCollection = null; + iteratorOsResults = null; } void checkValid() { - if (iteratorCollection == null) { + if (iteratorOsResults == null) { throw new ConcurrentModificationException( "No outside changes to a Realm is allowed while iterating a living Realm collection."); } @@ -115,7 +115,7 @@ void checkValid() { @Nullable T get(int pos) { - return convertRowToObject(iteratorCollection.getUncheckedRow(pos)); + return convertRowToObject(iteratorOsResults.getUncheckedRow(pos)); } // Returns the RealmModel by given row in this list. This has to be implemented in the upper layer since @@ -126,13 +126,13 @@ T get(int pos) { // Custom Realm collection list iterator. public static abstract class ListIterator extends Iterator implements java.util.ListIterator { - public ListIterator(Collection collection, int start) { - super(collection); - if (start >= 0 && start <= iteratorCollection.size()) { + public ListIterator(OsResults osResults, int start) { + super(osResults); + if (start >= 0 && start <= iteratorOsResults.size()) { pos = start - 1; } else { throw new IndexOutOfBoundsException("Starting location must be a valid index: [0, " - + (iteratorCollection.size() - 1) + "]. Yours was " + start); + + (iteratorOsResults.size() - 1) + "]. Yours was " + start); } } @@ -276,17 +276,17 @@ static Mode getByValue(byte value) { } } - public static Collection createBacklinksCollection(SharedRealm realm, UncheckedRow row, Table srcTable, String srcFieldName) { + public static OsResults createBacklinksCollection(SharedRealm realm, UncheckedRow row, Table srcTable, String srcFieldName) { long backlinksPtr = nativeCreateResultsFromBacklinks( realm.getNativePtr(), row.getNativePtr(), srcTable.getNativePtr(), srcTable.getColumnIndex(srcFieldName)); - return new Collection(realm, srcTable, backlinksPtr, true); + return new OsResults(realm, srcTable, backlinksPtr, true); } - public Collection(SharedRealm sharedRealm, TableQuery query, - @Nullable SortDescriptor sortDescriptor, @Nullable SortDescriptor distinctDescriptor) { + public OsResults(SharedRealm sharedRealm, TableQuery query, + @Nullable SortDescriptor sortDescriptor, @Nullable SortDescriptor distinctDescriptor) { query.validateQuery(); this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), @@ -300,31 +300,31 @@ public Collection(SharedRealm sharedRealm, TableQuery query, this.loaded = false; } - public Collection(SharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor) { + public OsResults(SharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor) { this(sharedRealm, query, sortDescriptor, null); } - public Collection(SharedRealm sharedRealm, TableQuery query) { + public OsResults(SharedRealm sharedRealm, TableQuery query) { this(sharedRealm, query, null, null); } - public Collection(SharedRealm sharedRealm, OsList osList, @Nullable SortDescriptor sortDescriptor) { + public OsResults(SharedRealm sharedRealm, OsList osList, @Nullable SortDescriptor sortDescriptor) { this.nativePtr = nativeCreateResultsFromList(sharedRealm.getNativePtr(), osList.getNativePtr(), sortDescriptor); this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.table = osList.getTargetTable(); this.context.addReference(this); - // Collection created from OsList is loaded by default. So that the listener won't be triggered with empty + // OsResults created from OsList is loaded by default. So that the listener won't be triggered with empty // change set. this.loaded = true; } - private Collection(SharedRealm sharedRealm, Table table, long nativePtr) { + private OsResults(SharedRealm sharedRealm, Table table, long nativePtr) { this(sharedRealm, table, nativePtr, false); } - Collection(SharedRealm sharedRealm, Table table, long nativePtr, boolean loaded) { + OsResults(SharedRealm sharedRealm, Table table, long nativePtr, boolean loaded) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.table = table; @@ -333,13 +333,13 @@ private Collection(SharedRealm sharedRealm, Table table, long nativePtr) { this.loaded = loaded; } - public Collection createSnapshot() { + public OsResults createSnapshot() { if (isSnapshot) { return this; } - Collection collection = new Collection(sharedRealm, table, nativeCreateSnapshot(nativePtr)); - collection.isSnapshot = true; - return collection; + OsResults osResults = new OsResults(sharedRealm, table, nativeCreateSnapshot(nativePtr)); + osResults.isSnapshot = true; + return osResults; } @Override @@ -397,12 +397,12 @@ public void clear() { nativeClear(nativePtr); } - public Collection sort(SortDescriptor sortDescriptor) { - return new Collection(sharedRealm, table, nativeSort(nativePtr, sortDescriptor)); + public OsResults sort(SortDescriptor sortDescriptor) { + return new OsResults(sharedRealm, table, nativeSort(nativePtr, sortDescriptor)); } - public Collection distinct(SortDescriptor distinctDescriptor) { - return new Collection(sharedRealm, table, nativeDistinct(nativePtr, distinctDescriptor)); + public OsResults distinct(SortDescriptor distinctDescriptor) { + return new OsResults(sharedRealm, table, nativeDistinct(nativePtr, distinctDescriptor)); } public boolean contains(UncheckedRow row) { @@ -532,7 +532,7 @@ private static native long nativeCreateResultsFromList(long sharedRealmPtr, long private static native void nativeDelete(long nativePtr, long index); - // Non-static, we need this Collection object in JNI. + // Non-static, we need this OsResults object in JNI. private native void nativeStartListening(long nativePtr); private native void nativeStopListening(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 638fc3d5f8..ccdad47857 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -32,7 +32,7 @@ public interface FrontEnd { "The query has been executed. This 'PendingRow' is not valid anymore."; private SharedRealm sharedRealm; - private Collection pendingCollection; + private OsResults pendingOsResults; private RealmChangeListener listener; private WeakReference frontEndRef; private boolean returnCheckedRow; @@ -40,7 +40,7 @@ public interface FrontEnd { public PendingRow(SharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor, final boolean returnCheckedRow) { this.sharedRealm = sharedRealm; - pendingCollection = new Collection(sharedRealm, query, sortDescriptor, null); + pendingOsResults = new OsResults(sharedRealm, query, sortDescriptor, null); listener = new RealmChangeListener() { @Override @@ -48,7 +48,7 @@ public void onChange(PendingRow pendingRow) { notifyFrontEnd(); } }; - pendingCollection.addListener(this, listener); + pendingOsResults.addListener(this, listener); this.returnCheckedRow = returnCheckedRow; sharedRealm.addPendingRow(this); } @@ -214,8 +214,8 @@ public boolean hasColumn(String fieldName) { } private void clearPendingCollection() { - pendingCollection.removeListener(this, listener); - pendingCollection = null; + pendingOsResults.removeListener(this, listener); + pendingOsResults = null; listener = null; sharedRealm.removePendingRow(this); } @@ -231,9 +231,9 @@ private void notifyFrontEnd() { return; } - if (pendingCollection.isValid()) { + if (pendingOsResults.isValid()) { // PendingRow will always get the first Row of the query since we only support findFirst. - UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); + UncheckedRow uncheckedRow = pendingOsResults.firstUncheckedRow(); // Clear the pending collection immediately in case beginTransaction is called in the listener which will // execute the query again. clearPendingCollection(); @@ -254,7 +254,7 @@ private void notifyFrontEnd() { // Execute the query immediately and call frontend's onQueryFinished(). public void executeQuery() { - if (pendingCollection == null) { + if (pendingOsResults == null) { throw new IllegalStateException(QUERY_EXECUTED_MESSAGE); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java index 3c72498e09..bdb0cb3505 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java @@ -136,7 +136,7 @@ protected PartialSyncCallback(String className) { this.className = className; } - public abstract void onSuccess(Collection results); + public abstract void onSuccess(OsResults results); public abstract void onError(RealmException error); } @@ -163,7 +163,7 @@ protected PartialSyncCallback(String className) { private final List> pendingRows = new CopyOnWriteArrayList<>(); // Package protected for testing - final List> iterators = new ArrayList<>(); + final List> iterators = new ArrayList<>(); private SharedRealm(OsRealmConfig osRealmConfig) { Capabilities capabilities = new AndroidCapabilities(); @@ -415,14 +415,14 @@ public void registerSchemaChangedCallback(SchemaChangedCallback callback) { // The iterator will iterate on a snapshot Results if it is accessed inside a transaction. // See https://github.com/realm/realm-java/issues/3883 for more information. // Should only be called by Iterator's constructor. - void addIterator(Collection.Iterator iterator) { + void addIterator(OsResults.Iterator iterator) { iterators.add(new WeakReference<>(iterator)); } // The detaching should happen before transaction begins. private void detachIterators() { - for (WeakReference iteratorRef : iterators) { - Collection.Iterator iterator = iteratorRef.get(); + for (WeakReference iteratorRef : iterators) { + OsResults.Iterator iterator = iteratorRef.get(); if (iterator != null) { iterator.detach(); } @@ -432,8 +432,8 @@ private void detachIterators() { // Invalidates all iterators when a remote change notification is received. void invalidateIterators() { - for (WeakReference iteratorRef : iterators) { - Collection.Iterator iterator = iteratorRef.get(); + for (WeakReference iteratorRef : iterators) { + OsResults.Iterator iterator = iteratorRef.get(); if (iterator != null) { iterator.invalidate(); } @@ -511,7 +511,7 @@ private void runPartialSyncRegistrationCallback(@Nullable String error, long nat } else { @SuppressWarnings("ConstantConditions") Table table = getTable(Table.getTableNameForClass(callback.className)); - Collection results = new Collection(this, table, nativeResultsPtr, true); + OsResults results = new OsResults(this, table, nativeResultsPtr, true); callback.onSuccess(results); } } From 7fa71513d0cda78304298993139a95d2875f7a86 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 24 Oct 2017 12:43:14 +0200 Subject: [PATCH 1041/2110] Prepare next dev iteration --- CHANGELOG.md | 25 +++++++++++++++++++------ version.txt | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c09e5384..8a1d5f036f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,23 @@ +## 4.2.0 (YYYY-MM-DD) + +### Breaking Changes + +### Enhancements + +### Bug Fixes + +### Interal + +### Credits + + ## 4.1.0 (2017-10-20) -## Enhancements +### Enhancements * `Realm.deleteRealm()` and `RealmConfiguration.assetFile()` are multi-processes safe now. -## Bug Fixes +### Bug Fixes * Fix some potential database corruption caused by deleting the Realm file while a Realm instance are still opened in another process or the sync client thread. * Added `realm.ignoreKotlinNullability` as a kapt argument to disable treating kotlin non-null types as `@Required` (#5412) (introduced in `v3.6.0`). @@ -13,7 +26,7 @@ ## 4.0.0 (2017-10-16) -## Breaking Changes +### Breaking Changes The internal file format has been upgraded. Opening an older Realm will upgrade the file automatically, but older versions of Realm will no longer be able to read the file. @@ -37,7 +50,7 @@ The internal file format has been upgraded. Opening an older Realm will upgrade * Removed deprecated API `RealmResults.distinct()`/`RealmResults.distinctAsync()`. Use `RealmQuery.distinct()`/`RealmQuery.distinctAsync()` instead. * `RealmQuery.createQuery(Realm, Class)`, `RealmQuery.createDynamicQuery(DynamicRealm, String)`, `RealmQuery.createQueryFromResult(RealmResults)` and `RealmQuery.createQueryFromList(RealmList)` have been removed. Use `Realm.where(Class)`, `DynamicRealm.where(String)`, `RealmResults.where()` and `RealmList.where()` instead. -## Enhancements +### Enhancements * [ObjectServer] `SyncUserInfo` now also exposes a users metadata using `SyncUserInfo.getMetadata()` * `RealmList` can now contain `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` values. [Queries](https://github.com/realm/realm-java/issues/5361) and [Importing primitive lists from JSON](https://github.com/realm/realm-java/issues/5361) are not supported yet. @@ -51,7 +64,7 @@ The internal file format has been upgraded. Opening an older Realm will upgrade * All Realm annotations are now kept at runtime, allowing runtime tools access to them (#5344). * Speedup schema initialization when a Realm file is first accessed (#5391). -## Bug Fixes +### Bug Fixes * [ObjectServer] Exposing a `RealmConfiguration` that allows a user to open the backup Realm after the client reset (#4759/#5223). * [ObjectServer] Realm no longer throws a native “unsupported instruction” exception in some cases when opening a synced Realm asynchronously (https://github.com/realm/realm-object-store/issues/502). @@ -64,7 +77,7 @@ The internal file format has been upgraded. Opening an older Realm will upgrade * Don't try to acquire `ApplicationContext` if not available in `Realm.init(Context)` (#5389). * Removing and re-adding a changelistener from inside a changelistener sometimes caused notifications to be missed (#5411). -## Internal +### Internal * Upgraded to Realm Sync 2.0.2. * Upgraded to Realm Core 4.0.2. diff --git a/version.txt b/version.txt index 2f81801b79..c3a2c7076f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.1.1-SNAPSHOT \ No newline at end of file +4.2.0-SNAPSHOT \ No newline at end of file From cd6680963b76715228f1cda7ef806dd53a00ce4b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 24 Oct 2017 12:45:35 +0200 Subject: [PATCH 1042/2110] Enable work-around for failing permission tests (#5461) --- .../java/io/realm/PermissionManagerTests.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 70e76c7b65..dce05265a9 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -1205,11 +1205,24 @@ public void onChange(Progress progress) { * states and fail if neither of these can be verified. */ private void assertInitialPermissions(RealmResults permissions) { - assertEquals("Could not find __permissions Realm", 1, permissions.where().endsWith("path", "__permission").count()); - assertEquals("Could not find __management Realm", 1, permissions.where().endsWith("path", "__management").count()); + assertGreaterThan("Unexpected count() for __permission Realm: " + Arrays.toString(permissions.toArray()), 0, permissions.where().endsWith("path", "__permission").count()); + assertGreaterThan("Unexpected count() for __management Realm: " + Arrays.toString(permissions.toArray()), 0, permissions.where().endsWith("path", "__management").count()); + // FIXME: Enable these again when https://github.com/realm/ros/issues/549 is fixed + // assertEquals("Unexpected count() for __permission Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__permission").count()); + // assertEquals("Unexpected count() for __management Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__management").count()); } private void assertInitialDefaultPermissions(RealmResults permissions) { - assertEquals("Could not find __wildcardpermissions Realm", 1, permissions.where().endsWith("path", "__wildcardpermissions").count()); + assertGreaterThan("Unexpected count() for __wildcardpermissions Realm: " + Arrays.toString(permissions.toArray()), 0, permissions.where().endsWith("path", "__wildcardpermissions").count()); + + // FIXME: Enable these again when https://github.com/realm/ros/issues/549 is fixed + // assertEquals("Unexpected count() for __wildcardpermissions Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__wildcardpermissions").count()); + } + + private void assertGreaterThan(String error, int base, long count) { + if (count <= base) { + throw new AssertionError(error); + } } + } From 6eb1b4f5a3c7a8295848c7e1611a746c0c90381d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 24 Oct 2017 17:36:52 +0200 Subject: [PATCH 1043/2110] Updated to Realm Sync 2.1 --- CHANGELOG.md | 15 +++++++++++++++ dependencies.list | 6 +++--- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/objectServer/java/io/realm/ErrorCode.java | 2 ++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c09e5384..8535dcbf4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 4.1.1 (YYYY-MM-DD) + +### Breaking Changes + +### Enhancements + +### Bug Fixes + +### Interal + +* Updated Realm Sync to 2.1.0 + +### Credits + + ## 4.1.0 (2017-10-20) ## Enhancements diff --git a/dependencies.list b/dependencies.list index 198c90ffc8..49ad30bacb 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,9 +1,9 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.0.2 -REALM_SYNC_SHA256=33c9dace6dc280712101110895d38509bbca74fdb31ba31b61dc0ad383472b03 +REALM_SYNC_VERSION=2.1.0 +REALM_SYNC_SHA256=cd52b2ee53ef80b4b9ec80eede7ca5fa28a96353ad7e4d26cf516dbb12586966 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.4 +REALM_OBJECT_SERVER_DE_VERSION=2.0.6 diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index b416d9ac98..eee2b44b1d 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit b416d9ac9893aa1b36f0a4c0c2d6533e78fe5060 +Subproject commit eee2b44b1dd7351e1243e3ff03b31f065f5b4d9a diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 446a32b57b..9aab07578b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -25,6 +25,7 @@ public enum ErrorCode { // See Client::Error in https://github.com/realm/realm-sync/blob/master/src/realm/sync/client.hpp // See https://github.com/realm/realm-object-server/blob/master/object-server/doc/problems.md + // See https://github.com/realm/realm-sync/blob/develop/src/realm/sync/protocol.hpp // Realm Java errors (0-49) UNKNOWN(-1), // Catch-all @@ -73,6 +74,7 @@ public enum ErrorCode { DIVERGING_HISTORIES(211), // Diverging histories (IDENT) BAD_CHANGESET(212), // Bad changeset (UPLOAD) DISABLED_SESSION(213), // Disabled session + PARTIAL_SYNC_DISABLED(214), // Partial sync disabled (BIND) // 300 - 599 Reserved for Standard HTTP error codes MULTIPLE_CHOICES(300), From df26533ed3debb1fe8258e51e8013aefcf0b83a4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 24 Oct 2017 20:53:59 +0200 Subject: [PATCH 1044/2110] New version of node required --- tools/sync_test_server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 4a79c0f023..c4f792b11c 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6.11.2 +FROM node:6.11.4 ARG ROS_DE_VERSION From 9f4f550a9024c39aab86648e31bdd9b51a8fed2d Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 25 Oct 2017 10:01:02 +0800 Subject: [PATCH 1045/2110] Remove depreacted RealmProxyMediator.getTableName (#5456) Prefix "class_" should be hide from java layer and handled in Object Store. Try to that direction step by step. Close #5455 --- CHANGELOG.md | 7 ++++++ .../java/io/realm/processor/Constants.java | 1 - .../processor/RealmProxyClassGenerator.java | 8 +++---- .../RealmProxyMediatorGenerator.java | 9 ++++---- .../io/realm/AllTypesRealmProxy.java | 4 ++-- .../io/realm/BooleansRealmProxy.java | 4 ++-- .../io/realm/NullTypesRealmProxy.java | 4 ++-- .../io/realm/RealmDefaultModuleMediator.java | 5 ++--- .../resources/io/realm/SimpleRealmProxy.java | 4 ++-- .../io/realm/internal/PrimaryKeyTests.java | 2 +- .../src/main/java/io/realm/Realm.java | 2 +- .../src/main/java/io/realm/RealmSchema.java | 4 +++- .../java/io/realm/internal/ColumnIndices.java | 2 +- .../io/realm/internal/RealmProxyMediator.java | 22 ++++++++----------- .../main/java/io/realm/internal/Table.java | 3 --- .../internal/modules/CompositeMediator.java | 4 ++-- .../internal/modules/FilterableMediator.java | 4 ++-- 17 files changed, 44 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c09e5384..4363981b0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.1.1 (YYYY-MM-DD) + +## Bug Fixes + +# Fixed the compile warnings of using deprecated method `RealmProxyMediator.getTableName()` in generated mediator classes (#5455). + + ## 4.1.0 (2017-10-20) ## Enhancements diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java index fbce99063d..0c6d09306c 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java @@ -26,7 +26,6 @@ public class Constants { public static final String PROXY_SUFFIX = "RealmProxy"; public static final String INTERFACE_SUFFIX = "RealmProxyInterface"; public static final String INDENT = " "; - public static final String TABLE_PREFIX = "class_"; public static final String DEFAULT_MODULE_CLASS_NAME = "DefaultRealmModule"; static final String STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE = "throw new IllegalArgumentException(\"Trying to set non-nullable field '%s' to null.\")"; diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 71bb69b86e..fcb035547e 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -144,7 +144,7 @@ public void generate() throws IOException, UnsupportedOperationException { emitCreateExpectedObjectSchemaInfo(writer); emitGetExpectedObjectSchemaInfo(writer); emitCreateColumnInfoMethod(writer); - emitGetTableNameMethod(writer); + emitGetSimpleClassNameMethod(writer); emitGetFieldNamesMethod(writer); emitCreateOrUpdateUsingJsonObject(writer); emitCreateUsingJsonStream(writer); @@ -833,9 +833,9 @@ private void emitCreateColumnInfoMethod(JavaWriter writer) throws IOException { } //@formatter:off - private void emitGetTableNameMethod(JavaWriter writer) throws IOException { - writer.beginMethod("String", "getTableName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) - .emitStatement("return \"%s%s\"", Constants.TABLE_PREFIX, simpleClassName) + private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException { + writer.beginMethod("String", "getSimpleClassName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) + .emitStatement("return \"%s\"", simpleClassName) .endMethod() .emitEmptyLine(); } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index bd4476fc9f..4e1a89aba5 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -80,7 +80,6 @@ public void generate() throws IOException { "io.realm.internal.RealmObjectProxy", "io.realm.internal.RealmProxyMediator", "io.realm.internal.Row", - "io.realm.internal.Table", "io.realm.internal.OsSchemaInfo", "io.realm.internal.OsObjectSchemaInfo", "org.json.JSONException", @@ -101,7 +100,7 @@ public void generate() throws IOException { emitGetExpectedObjectSchemaInfoMap(writer); emitCreateColumnInfoMethod(writer); emitGetFieldNamesMethod(writer); - emitGetTableNameMethod(writer); + emitGetSimpleClassNameMethod(writer); emitNewInstanceMethod(writer); emitGetClassModelList(writer); emitCopyToRealmMethod(writer); @@ -187,18 +186,18 @@ public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitEmptyLine(); } - private void emitGetTableNameMethod(JavaWriter writer) throws IOException { + private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); writer.beginMethod( "String", - "getTableName", + "getSimpleClassNameImpl", EnumSet.of(Modifier.PUBLIC), "Class", "clazz" ); emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return %s.getTableName()", qualifiedProxyClasses.get(i)); + writer.emitStatement("return %s.getSimpleClassName()", qualifiedProxyClasses.get(i)); } }, writer); writer.endMethod(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 0a20dd36a9..0c429a8dec 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -890,8 +890,8 @@ public static AllTypesColumnInfo createColumnInfo(OsSchemaInfo schemaInfo) { return new AllTypesColumnInfo(schemaInfo); } - public static String getTableName() { - return "class_AllTypes"; + public static String getSimpleClassName() { + return "AllTypes"; } public static List getFieldNames() { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index fa4b8282c7..28beeb3ebb 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -208,8 +208,8 @@ public static BooleansColumnInfo createColumnInfo(OsSchemaInfo schemaInfo) { return new BooleansColumnInfo(schemaInfo); } - public static String getTableName() { - return "class_Booleans"; + public static String getSimpleClassName() { + return "Booleans"; } public static List getFieldNames() { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 020e091216..65562e6d8e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -1750,8 +1750,8 @@ public static NullTypesColumnInfo createColumnInfo(OsSchemaInfo schemaInfo) { return new NullTypesColumnInfo(schemaInfo); } - public static String getTableName() { - return "class_NullTypes"; + public static String getSimpleClassName() { + return "NullTypes"; } public static List getFieldNames() { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 72344ed633..c8f0dc1849 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -9,7 +9,6 @@ import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; import io.realm.internal.SharedRealm; -import io.realm.internal.Table; import java.io.IOException; import java.util.Collection; import java.util.Collections; @@ -61,11 +60,11 @@ public List getFieldNames(Class clazz) { } @Override - public String getTableName(Class clazz) { + public String getSimpleClassNameImpl(Class clazz) { checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { - return io.realm.AllTypesRealmProxy.getTableName(); + return io.realm.AllTypesRealmProxy.getSimpleClassName(); } throw getMissingProxyClassException(clazz); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 9266f2c253..150ca08c9c 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -162,8 +162,8 @@ public static SimpleColumnInfo createColumnInfo(OsSchemaInfo schemaInfo) { return new SimpleColumnInfo(schemaInfo); } - public static String getTableName() { - return "class_Simple"; + public static String getSimpleClassName() { + return "Simple"; } public static List getFieldNames() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index 08fda9f32b..84b0cfc9b5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -85,7 +85,7 @@ private Table getTableWithIntegerPrimaryKey() { sharedRealm = SharedRealm.getInstance(config); sharedRealm.beginTransaction(); OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table - Table t = sharedRealm.createTable(Table.getTableNameForClass("class_TestTable")); + Table t = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); long column = t.addColumn(RealmFieldType.INTEGER, "colName"); t.addSearchIndex(column); OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable", "colName"); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index c4da704041..90415531b0 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -161,7 +161,7 @@ private Realm(RealmCache cache) { RealmProxyMediator mediator = configuration.getSchemaMediator(); Set> classes = mediator.getModelClasses(); for (Class clazz : classes) { - String tableName = mediator.getTableName(clazz); + String tableName = Table.getTableNameForClass(mediator.getSimpleClassName(clazz)); if (!sharedRealm.hasTable(tableName)) { sharedRealm.close(); throw new RealmMigrationNeededException(configuration.getPath(), diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 9830514072..bd789c569f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -182,7 +182,9 @@ Table getTable(Class clazz) { table = classToTable.get(originalClass); } if (table == null) { - table = realm.getSharedRealm().getTable(realm.getConfiguration().getSchemaMediator().getTableName(originalClass)); + String tableName = Table.getTableNameForClass( + realm.getConfiguration().getSchemaMediator().getSimpleClassName(originalClass)); + table = realm.getSharedRealm().getTable(tableName); classToTable.put(originalClass, table); } if (isProxyClass(originalClass, clazz)) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java index b0f2bd15f4..98f47c2485 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java @@ -100,7 +100,7 @@ public ColumnInfo getColumnInfo(String simpleClassName) { if (columnInfo == null) { Set> modelClasses = mediator.getModelClasses(); for (Class modelClass : modelClasses) { - if (Table.getClassNameForTable(mediator.getTableName(modelClass)).equals(simpleClassName)) { + if (mediator.getSimpleClassName(modelClass).equals(simpleClassName)) { columnInfo = getColumnInfo(modelClass); simpleClassNameToColumnInfoMap.put(simpleClassName, columnInfo); break; diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index 7bc1a37072..188a6f96b2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -69,28 +69,24 @@ public abstract class RealmProxyMediator { public abstract List getFieldNames(Class clazz); /** - * Returns the name that Realm should use for all its internal tables. This is the un-obfuscated name of the - * class with the Realm table prefix. + * Returns the name that Realm should use for all its internal tables. This is the un-obfuscated simple name of the + * class. * - * @param clazz the {@link RealmObject} class reference. - * @return the simple name of an RealmObject class (before it has been obfuscated) with Realm table prefix. - * @throws java.lang.NullPointerException if null is given as argument. - * @deprecated use {{@link #getSimpleClassName(Class)}} instead. + * @param clazz the {@link RealmModel} or the Realm object proxy class reference. + * @return the simple name of an RealmObject class (before it has been obfuscated). */ - @Deprecated - public abstract String getTableName(Class clazz); + public final String getSimpleClassName(Class clazz) { + return getSimpleClassNameImpl(Util.getOriginalModelClass(clazz)); + } /** * Returns the name that Realm should use for all its internal tables. This is the un-obfuscated simple name of the * class. * - * @param clazz the {@link RealmObject} class reference. + * @param clazz the {@link RealmModel} class reference. * @return the simple name of an RealmObject class (before it has been obfuscated). */ - public String getSimpleClassName(Class clazz) { - Class originalClass = Util.getOriginalModelClass(clazz); - return Table.getClassNameForTable(getTableName(originalClass)); - } + protected abstract String getSimpleClassNameImpl(Class clazz); /** * Creates a new instance of an {@link RealmObjectProxy} for the given RealmObject class. diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index b68bab8ab0..0135e69f60 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -699,9 +699,6 @@ public static String getClassNameForTable(@Nullable String name) { public static String getTableNameForClass(String name) { //noinspection ConstantConditions if (name == null) { return null; } - if (name.startsWith(TABLE_PREFIX)) { - return name; - } return TABLE_PREFIX + name; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index 390ad0104a..08df441f79 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -83,9 +83,9 @@ public List getFieldNames(Class clazz) { } @Override - public String getTableName(Class clazz) { + protected String getSimpleClassNameImpl(Class clazz) { RealmProxyMediator mediator = getMediator(clazz); - return mediator.getTableName(clazz); + return mediator.getSimpleClassName(clazz); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index e2d3c99e22..ef9fb5654c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -98,9 +98,9 @@ public List getFieldNames(Class clazz) { } @Override - public String getTableName(Class clazz) { + protected String getSimpleClassNameImpl(Class clazz) { checkSchemaHasClass(clazz); - return originalMediator.getTableName(clazz); + return originalMediator.getSimpleClassName(clazz); } @Override From 5a3082ad4f705f66f9ddd6e49eb28fbceb73432f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 25 Oct 2017 10:31:22 +0800 Subject: [PATCH 1046/2110] Update Object Store to 136b3a32a21 --- realm/realm-library/src/main/cpp/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index eee2b44b1d..136b3a32a2 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit eee2b44b1dd7351e1243e3ff03b31f065f5b4d9a +Subproject commit 136b3a32a218f50275f1183ed078b31945a9e29f From fb7925bf6f748922ef425970f5b93a3fcb48e6ca Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 24 Oct 2017 12:27:20 +0200 Subject: [PATCH 1047/2110] Upgrade to Build Tools 3.0.0-rc2 --- examples/build.gradle | 2 +- examples/gradle.properties | 8 ++++++++ library-benchmarks/build.gradle | 2 +- realm/build.gradle | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index f2c813c27f..17d9a57116 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -23,7 +23,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-beta7' + classpath 'com.android.tools.build:gradle:3.0.0-rc2' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-gradle-plugin:${currentVersion}" diff --git a/examples/gradle.properties b/examples/gradle.properties index 3cae06226c..69f84662c3 100644 --- a/examples/gradle.properties +++ b/examples/gradle.properties @@ -3,3 +3,11 @@ org.gradle.caching=true # disable AAPT2 to work around an issue of Robolectric in unitTestExample https://github.com/robolectric/robolectric/issues/3169 android.enableAapt2=false + +# Gradle sync failed: Due to a limitation of Gradle’s new variant-aware dependency management, loading the Android Gradle plugin in different class loaders leads to a build error. +# This can occur when the buildscript classpaths that contain the Android Gradle plugin in sub-projects, or included projects in the case of composite builds, are set differently. +# To resolve this issue, add the Android Gradle plugin to only the buildscript classpath of the top-level build.gradle file. +# In the case of composite builds, also make sure the build script classpaths that contain the Android Gradle plugin are identical across the main and included projects. +# If you are using a version of Gradle that has fixed the issue, you can disable this check by setting android.enableBuildScriptClasspathCheck=false in the gradle.properties file. +# To learn more about this issue, go to https://d.android.com/r/tools/buildscript-classpath-check.html. +android.enableBuildScriptClasspathCheck=false \ No newline at end of file diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 8b58216363..6d48a2410e 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-beta7' + classpath 'com.android.tools.build:gradle:3.0.0-rc2' classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" } } diff --git a/realm/build.gradle b/realm/build.gradle index 3c49834f42..87890bb6a3 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-beta7' + classpath 'com.android.tools.build:gradle:3.0.0-rc2' classpath 'de.undercouch:gradle-download-task:3.2.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' From dc0f5ecd5c286b366d52ddceb2cd4d9d315a9b06 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 25 Oct 2017 16:43:08 +0100 Subject: [PATCH 1048/2110] fix SocketTimeout (#5453) * Adding a max retries to the ExponentialBackoff to avoid retrying indefinitely the logout (revoke) query, which causes some tests to block * Binding IOException to ErrorCode.IO_EXCEPTION --- CHANGELOG.md | 2 +- .../src/objectServer/java/io/realm/ErrorCode.java | 11 ++++++++--- .../src/objectServer/java/io/realm/SyncUser.java | 2 +- .../internal/network/ExponentialBackoffTask.java | 11 ++++++++++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 191b86427a..1c6c793b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ * Fix some potential database corruption caused by deleting the Realm file while a Realm instance are still opened in another process or the sync client thread. * Added `realm.ignoreKotlinNullability` as a kapt argument to disable treating kotlin non-null types as `@Required` (#5412) (introduced in `v3.6.0`). * Increased http connect/write timeout for low bandwidth network. - +* [ObjectServer] now retrying network query when encountering any `IOException` (#5453). ## 4.0.0 (2017-10-16) diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 9aab07578b..100c89b613 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -16,7 +16,8 @@ package io.realm; -import java.net.ConnectException; + +import java.io.IOException; /** * This class enumerate all potential errors related to using the Object Server or synchronizing data. @@ -200,8 +201,12 @@ public static ErrorCode fromInt(int errorCode) { * @return mapped {@link ErrorCode}. */ public static ErrorCode fromException(Exception exception) { - // ConnectException is recoverable (with exponential backoff) - return (exception instanceof ConnectException) ? ErrorCode.IO_EXCEPTION : ErrorCode.UNKNOWN; + // IOException are recoverable (with exponential backoff) + if (exception instanceof IOException) { + return ErrorCode.IO_EXCEPTION; + } else { + return ErrorCode.UNKNOWN; + } } public enum Category { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 0d3e9b1c74..ce82386309 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -249,7 +249,7 @@ public void logout() { final Token refreshTokenToBeRevoked = refreshToken; ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; - networkPoolExecutor.submit(new ExponentialBackoffTask() { + networkPoolExecutor.submit(new ExponentialBackoffTask(3) { @Override protected LogoutResponse execute() { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java index 1e428dd190..a5686ae139 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java @@ -24,6 +24,15 @@ * Abstracts the concept of running an network task with incremental backoff. It will run forever until interrupted. */ public abstract class ExponentialBackoffTask implements Runnable { + private final int maxRetries; + + public ExponentialBackoffTask(int maxRetries) { + this.maxRetries = maxRetries; + } + + public ExponentialBackoffTask() { + this(Integer.MAX_VALUE - 1); + } // Task to perform protected abstract T execute(); @@ -69,7 +78,7 @@ public void run() { onSuccess(response); break; } else { - if (shouldAbortTask(response)) { + if (shouldAbortTask(response) || attempt == maxRetries + 1) { onError(response); break; } From ae575841ee43c833a64199bdc7c09e45e1d55a26 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 15:15:52 +0530 Subject: [PATCH 1049/2110] Android Gradle Plugin 3.0, Gradle 4.3-rc3, New D8 Compiler Enabled --- examples/build.gradle | 6 +----- examples/gradle.properties | 1 + examples/gradle/wrapper/gradle-wrapper.properties | 2 +- gradle-plugin/gradle.properties | 1 + gradle-plugin/gradle/wrapper/gradle-wrapper.properties | 2 +- gradle.properties | 1 + gradle/wrapper/gradle-wrapper.properties | 2 +- library-benchmarks/build.gradle | 6 +----- library-benchmarks/gradle.properties | 1 + library-benchmarks/gradle/wrapper/gradle-wrapper.properties | 2 +- realm-annotations/gradle.properties | 1 + realm-annotations/gradle/wrapper/gradle-wrapper.properties | 2 +- realm-transformer/gradle.properties | 1 + realm-transformer/gradle/wrapper/gradle-wrapper.properties | 2 +- realm.properties | 2 +- realm/build.gradle | 6 +----- realm/gradle.properties | 1 + realm/gradle/wrapper/gradle-wrapper.properties | 2 +- 18 files changed, 18 insertions(+), 23 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index a1ce954f41..1c72f768d7 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -23,11 +23,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { -<<<<<<< HEAD - classpath 'com.android.tools.build:gradle:3.0.0-rc2' -======= - classpath 'com.android.tools.build:gradle:3.0.0-beta4' ->>>>>>> d9108f12f655d47a4cf06998ac0514e05c4c57a2 + classpath 'com.android.tools.build:gradle:3.0.0' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-gradle-plugin:${currentVersion}" diff --git a/examples/gradle.properties b/examples/gradle.properties index 69f84662c3..9c2c6c4094 100644 --- a/examples/gradle.properties +++ b/examples/gradle.properties @@ -1,5 +1,6 @@ org.gradle.jvmargs=-Xmx2048M org.gradle.caching=true +android.enableD8=true # disable AAPT2 to work around an issue of Robolectric in unitTestExample https://github.com/robolectric/robolectric/issues/3169 android.enableAapt2=false diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index c583957d2b..5161f013c5 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip diff --git a/gradle-plugin/gradle.properties b/gradle-plugin/gradle.properties index 160890028a..71b7cfd594 100644 --- a/gradle-plugin/gradle.properties +++ b/gradle-plugin/gradle.properties @@ -1 +1,2 @@ org.gradle.caching=true +android.enableD8=true diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index c583957d2b..5161f013c5 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip diff --git a/gradle.properties b/gradle.properties index a409f36833..1e7f6c617c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ org.gradle.jvmargs=-XX:MaxPermSize=512m org.gradle.caching=true +android.enableD8=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c583957d2b..5161f013c5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index c89636b1b9..b5937508b3 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -5,11 +5,7 @@ buildscript { jcenter() } dependencies { -<<<<<<< HEAD - classpath 'com.android.tools.build:gradle:3.0.0-rc2' -======= - classpath 'com.android.tools.build:gradle:3.0.0-beta4' ->>>>>>> d9108f12f655d47a4cf06998ac0514e05c4c57a2 + classpath 'com.android.tools.build:gradle:3.0.0' classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" } } diff --git a/library-benchmarks/gradle.properties b/library-benchmarks/gradle.properties index 160890028a..2fdd4b5a9a 100644 --- a/library-benchmarks/gradle.properties +++ b/library-benchmarks/gradle.properties @@ -1 +1,2 @@ org.gradle.caching=true +android.enableD8=true \ No newline at end of file diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index c583957d2b..5161f013c5 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip diff --git a/realm-annotations/gradle.properties b/realm-annotations/gradle.properties index 160890028a..71b7cfd594 100644 --- a/realm-annotations/gradle.properties +++ b/realm-annotations/gradle.properties @@ -1 +1,2 @@ org.gradle.caching=true +android.enableD8=true diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index c583957d2b..5161f013c5 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip diff --git a/realm-transformer/gradle.properties b/realm-transformer/gradle.properties index 160890028a..71b7cfd594 100644 --- a/realm-transformer/gradle.properties +++ b/realm-transformer/gradle.properties @@ -1 +1,2 @@ org.gradle.caching=true +android.enableD8=true diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index c583957d2b..5161f013c5 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip diff --git a/realm.properties b/realm.properties index b36f4827a1..30bbeef440 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=4.2.1 +gradleVersion=4.3-rc-3 ndkVersion=r10e diff --git a/realm/build.gradle b/realm/build.gradle index d922bf60c9..751a26cafd 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -9,11 +9,7 @@ buildscript { } dependencies { -<<<<<<< HEAD - classpath 'com.android.tools.build:gradle:3.0.0-rc2' -======= - classpath 'com.android.tools.build:gradle:3.0.0-beta4' ->>>>>>> d9108f12f655d47a4cf06998ac0514e05c4c57a2 + classpath 'com.android.tools.build:gradle:3.0.0' classpath 'de.undercouch:gradle-download-task:3.2.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' diff --git a/realm/gradle.properties b/realm/gradle.properties index 0be17a49db..e93c5a6121 100644 --- a/realm/gradle.properties +++ b/realm/gradle.properties @@ -1,2 +1,3 @@ org.gradle.jvmargs=-Xms512m -Xmx2048m org.gradle.caching=true +android.enableD8=true diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index c583957d2b..5161f013c5 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip From 33f747c9b42b0e07d3e78d0ede30656ffba94475 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 15:24:16 +0530 Subject: [PATCH 1050/2110] Source and Target Compatibility 1.8 --- gradle-plugin/build.gradle | 4 ++-- realm-annotations/build.gradle | 4 ++-- realm-transformer/build.gradle | 4 ++-- realm/realm-annotations-processor/build.gradle | 4 ++-- realm/realm-library/build.gradle | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 24cedb3dc1..456d6c2afc 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -25,8 +25,8 @@ repositories { jcenter() } -sourceCompatibility = 1.6 -targetCompatibility = 1.6 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 group = 'io.realm' version = file("${projectDir}/../version.txt").text.trim(); diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index f97d38992e..d23622ac71 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -22,8 +22,8 @@ apply plugin: 'maven-publish' apply plugin: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.bintray' -sourceCompatibility = '1.6' -targetCompatibility = '1.6' +sourceCompatibility = '1.8' +targetCompatibility = '1.8' group = 'io.realm' version = file("${projectDir}/../version.txt").text.trim(); diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index a10ba1ecb0..7f721d4600 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -31,8 +31,8 @@ properties.load(new FileInputStream("${projectDir}/../dependencies.list")) def syncVersion = properties.getProperty('REALM_SYNC_VERSION') -sourceCompatibility = '1.6' -targetCompatibility = '1.6' +sourceCompatibility = '1.8' +targetCompatibility = '1.8' repositories { mavenLocal() diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 43c75faafd..c0145cddb9 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -4,8 +4,8 @@ apply plugin: 'maven-publish' apply plugin: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.bintray' -sourceCompatibility = '1.6' -targetCompatibility = '1.6' +sourceCompatibility = '1.8' +targetCompatibility = '1.8' dependencies { compile "com.squareup:javawriter:2.5.0" diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 34c1c257c8..768be80195 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -105,8 +105,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } packagingOptions { From 68f4e79404fe6ed991b1ec40c7dee5ced6eb58c1 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 26 Oct 2017 10:54:44 +0100 Subject: [PATCH 1051/2110] Removed @SafeVarargs annotation since it's not available until API 19 (#5469) * removed @SafeVarargs annotation since it's not available until API 19, fixes #5463 --- CHANGELOG.md | 6 ++++-- .../src/main/java/io/realm/RealmConfiguration.java | 1 - realm/realm-library/src/main/java/io/realm/RealmList.java | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c6c793b2b..0b86fcc6fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ ### Bug Fixes * Fixed the compile warnings of using deprecated method `RealmProxyMediator.getTableName()` in generated mediator classes (#5455). +* [ObjectServer] now retrying network query when encountering any `IOException` (#5453). +* Fixed a `NoClassDefFoundError` due to using `@SafeVarargs` below API 19 (#5463). -### Interal +### Internal * Updated Realm Sync to 2.1.0 @@ -26,7 +28,7 @@ * Fix some potential database corruption caused by deleting the Realm file while a Realm instance are still opened in another process or the sync client thread. * Added `realm.ignoreKotlinNullability` as a kapt argument to disable treating kotlin non-null types as `@Required` (#5412) (introduced in `v3.6.0`). * Increased http connect/write timeout for low bandwidth network. -* [ObjectServer] now retrying network query when encountering any `IOException` (#5453). + ## 4.0.0 (2017-10-16) diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 6678fe935d..766a47e23e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -762,7 +762,6 @@ private void addModule(Object module) { * create a module. These classes must be available in the default module. Calling this will remove any * previously configured modules. */ - @SafeVarargs final Builder schema(Class firstClass, Class... additionalClasses) { //noinspection ConstantConditions if (firstClass == null) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index e1ba427ce4..267d9aff0b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -99,7 +99,6 @@ public RealmList() { * * @param objects initial objects in the list. */ - @SafeVarargs public RealmList(E... objects) { //noinspection ConstantConditions if (objects == null) { From a20a3f496cd44bc5b6383a0eb16f618ca4e336ec Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 15:38:47 +0530 Subject: [PATCH 1052/2110] SDK 27, Build Tools 27.0.0, ReadMe Update --- Dockerfile | 4 ++-- README.md | 4 ++-- examples/build.gradle | 5 +++-- examples/encryptionExample/build.gradle | 2 +- examples/gridViewExample/build.gradle | 2 +- examples/introExample/build.gradle | 2 +- examples/jsonExample/build.gradle | 2 +- examples/kotlinExample/build.gradle | 2 +- examples/migrationExample/build.gradle | 2 +- examples/moduleExample/app/build.gradle | 2 +- examples/moduleExample/library/build.gradle | 2 +- examples/newsreaderExample/build.gradle | 6 +++--- examples/objectServerExample/build.gradle | 7 +++---- examples/rxJavaExample/build.gradle | 4 ++-- examples/secureTokenAndroidKeyStore/build.gradle | 6 +++--- examples/threadExample/build.gradle | 2 +- examples/unitTestExample/build.gradle | 4 ++-- library-benchmarks/build.gradle | 6 +++--- realm/realm-library/build.gradle | 8 ++++---- 19 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Dockerfile b/Dockerfile index e2c50e9865..a9f428430d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,9 +52,9 @@ RUN sdkmanager --update # Accept all licenses RUN yes y | sdkmanager --licenses RUN sdkmanager 'platform-tools' -RUN sdkmanager 'build-tools;26.0.2' +RUN sdkmanager 'build-tools;27.0.0' RUN sdkmanager 'extras;android;m2repository' -RUN sdkmanager 'platforms;android-26' +RUN sdkmanager 'platforms;android-2' RUN sdkmanager 'cmake;3.6.4111459' # Install the NDK diff --git a/README.md b/README.md index e6b22c7e0b..569ed9ae4d 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,8 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites - * Download the [**JDK 7**](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html) or [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install the Android SDK **Build-Tools 26.0.2**, **Android O (API 26)** (for example through Android Studio’s **Android SDK Manager**). + * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. + * Download & install the Android SDK **Build-Tools 27.0.0**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * If you use Android Studio, Android Studio 3.0 or later is required. diff --git a/examples/build.gradle b/examples/build.gradle index 1c72f768d7..75ee1a1a83 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,5 +1,6 @@ -project.ext.sdkVersion = 26 -project.ext.buildTools = '26.0.2' +project.ext.sdkVersion = 27 +project.ext.minSdkVersion = 15 +project.ext.buildTools = '27.0.0' // Don't cache SNAPSHOT (changing) dependencies. configurations.all { diff --git a/examples/encryptionExample/build.gradle b/examples/encryptionExample/build.gradle index 4655dfef93..b737f2d151 100644 --- a/examples/encryptionExample/build.gradle +++ b/examples/encryptionExample/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId 'examples.realm.io.encryptionExample' targetSdkVersion rootProject.sdkVersion - minSdkVersion 14 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName '1.0' } diff --git a/examples/gridViewExample/build.gradle b/examples/gridViewExample/build.gradle index 178240ccf4..a64c883bb7 100644 --- a/examples/gridViewExample/build.gradle +++ b/examples/gridViewExample/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId 'io.realm.examples.realmgridview' targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } diff --git a/examples/introExample/build.gradle b/examples/introExample/build.gradle index 5d4bb7093f..922117c31c 100644 --- a/examples/introExample/build.gradle +++ b/examples/introExample/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId 'io.realm.examples.intro' targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } diff --git a/examples/jsonExample/build.gradle b/examples/jsonExample/build.gradle index b4327f86b3..64118e56f2 100644 --- a/examples/jsonExample/build.gradle +++ b/examples/jsonExample/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId 'io.realm.examples.json' targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index ad5ff37929..6e4df60829 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -25,7 +25,7 @@ android { applicationId 'io.realm.examples.kotlin' //noinspection GroovyAssignabilityCheck targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } diff --git a/examples/migrationExample/build.gradle b/examples/migrationExample/build.gradle index 53e5aaafa7..4ce6632b94 100644 --- a/examples/migrationExample/build.gradle +++ b/examples/migrationExample/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId "examples.realm.io.migration" targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } diff --git a/examples/moduleExample/app/build.gradle b/examples/moduleExample/app/build.gradle index 328822593e..c0398d5f63 100644 --- a/examples/moduleExample/app/build.gradle +++ b/examples/moduleExample/app/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId 'io.realm.examples.appmodules' targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } diff --git a/examples/moduleExample/library/build.gradle b/examples/moduleExample/library/build.gradle index 3697638a79..78c14a606d 100644 --- a/examples/moduleExample/library/build.gradle +++ b/examples/moduleExample/library/build.gradle @@ -7,7 +7,7 @@ android { defaultConfig { targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index c62c2d757f..6faf3cc6b2 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId "io.realm.examples.newsreader" targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } @@ -44,9 +44,9 @@ android { dependencies { //noinspection GradleDependency - implementation 'com.android.support:appcompat-v7:26.0.1' + implementation 'com.android.support:appcompat-v7:27.0.0' //noinspection GradleDependency - implementation 'com.android.support:design:26.0.1' + implementation 'com.android.support:design:27.0.0' implementation 'com.jakewharton.timber:timber:4.1.0' implementation 'com.jakewharton:butterknife:8.5.1' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index db3f530a2f..81b1f5c2a8 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -29,7 +29,7 @@ android { defaultConfig { applicationId 'io.realm.examples.objectserver' targetSdkVersion rootProject.sdkVersion - minSdkVersion 16 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } @@ -62,9 +62,8 @@ realm { } dependencies { - implementation 'com.android.support:support-v4:26.0.1' - implementation 'com.android.support:appcompat-v7:26.0.1' - implementation 'com.android.support:design:26.0.1' + implementation 'com.android.support:appcompat-v7:27.0.0' + implementation 'com.android.support:design:27.0.0' implementation 'me.zhanghai.android.materialprogressbar:library:1.3.0' implementation 'com.jakewharton:butterknife:8.5.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' diff --git a/examples/rxJavaExample/build.gradle b/examples/rxJavaExample/build.gradle index 2a0393b8ab..17c262a500 100644 --- a/examples/rxJavaExample/build.gradle +++ b/examples/rxJavaExample/build.gradle @@ -12,7 +12,7 @@ android { applicationId 'io.realm.examples.rxjava' //noinspection GroovyAssignabilityCheck targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } @@ -42,7 +42,7 @@ android { dependencies { implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' implementation 'io.reactivex.rxjava2:rxjava:2.1.0' - implementation 'com.android.support:appcompat-v7:26.0.1' + implementation 'com.android.support:appcompat-v7:27.0.0' implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' implementation 'com.squareup.retrofit2:converter-jackson:2.3.0' diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index a25f094d9b..af5c9c4fcd 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -8,8 +8,8 @@ android { defaultConfig { applicationId "io.realm.examples.securetokenandroidkeystore" - minSdkVersion 14 - targetSdkVersion 25 + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.sdkVersion versionCode 1 versionName "1.0" @@ -37,7 +37,7 @@ dependencies { androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - implementation 'com.android.support:appcompat-v7:26.0.1' + implementation 'com.android.support:appcompat-v7:27.0.0' testImplementation 'junit:junit:4.12' implementation 'io.realm:secure-userstore:1.0.1' } diff --git a/examples/threadExample/build.gradle b/examples/threadExample/build.gradle index 25980aa6a9..97957f98f0 100644 --- a/examples/threadExample/build.gradle +++ b/examples/threadExample/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId "io.realm.examples.threads" targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index cdb4e5535f..64feeb13b9 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId 'io.realm.examples.unittesting' targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" @@ -39,7 +39,7 @@ android { dependencies { - implementation 'com.android.support:appcompat-v7:26.0.1' + implementation 'com.android.support:appcompat-v7:27.0.0' testImplementation 'io.reactivex.rxjava2:rxjava:2.1.0' diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index b5937508b3..ffc3e81c30 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -26,12 +26,12 @@ apply plugin: 'com.android.library' apply plugin: 'realm-android' android { - compileSdkVersion 26 - buildToolsVersion "26.0.2" + compileSdkVersion 27 + buildToolsVersion "27.0.0" defaultConfig { minSdkVersion 15 - targetSdkVersion 22 // Below 23 to avoid new permission system introduced in M + targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 768be80195..80bec2440b 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -40,12 +40,12 @@ ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProperty('enableDebugCore') : false //FIXME Use 'false' as default until https://github.com/realm/realm-java/issues/5354 is fixed android { - compileSdkVersion 26 - buildToolsVersion '26.0.2' + compileSdkVersion 27 + buildToolsVersion '27.0.0' defaultConfig { minSdkVersion 9 - targetSdkVersion 25 + targetSdkVersion 27 versionName version project.archivesBaseName = "realm-android-library" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -106,7 +106,7 @@ android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_ } packagingOptions { From 9f33fb30e5bae8872018483d3581d9b0e741ce19 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 15:39:51 +0530 Subject: [PATCH 1053/2110] fix --- realm/realm-library/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 80bec2440b..f51f463f7d 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -106,7 +106,7 @@ android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_ + targetCompatibility JavaVersion.VERSION_1_8 } packagingOptions { From bf4dac4ec878330e189c96e3fa2a2130605b65cf Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 15:41:35 +0530 Subject: [PATCH 1054/2110] DockerFix --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a9f428430d..fb553f5d3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ RUN yes y | sdkmanager --licenses RUN sdkmanager 'platform-tools' RUN sdkmanager 'build-tools;27.0.0' RUN sdkmanager 'extras;android;m2repository' -RUN sdkmanager 'platforms;android-2' +RUN sdkmanager 'platforms;android-26' RUN sdkmanager 'cmake;3.6.4111459' # Install the NDK From 437f5641809aeda40f345822804f700d517eafd1 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 15:43:13 +0530 Subject: [PATCH 1055/2110] Docker fix --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fb553f5d3e..2160e4e180 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ RUN yes y | sdkmanager --licenses RUN sdkmanager 'platform-tools' RUN sdkmanager 'build-tools;27.0.0' RUN sdkmanager 'extras;android;m2repository' -RUN sdkmanager 'platforms;android-26' +RUN sdkmanager 'platforms;android-27' RUN sdkmanager 'cmake;3.6.4111459' # Install the NDK From 3d33ea3d9f9ae41def3a0745216e143508b109d9 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 15:57:19 +0530 Subject: [PATCH 1056/2110] Removed D8 From Library Modules --- gradle-plugin/gradle.properties | 3 +-- gradle.properties | 2 +- realm-annotations/gradle.properties | 3 +-- realm-transformer/gradle.properties | 3 +-- realm/gradle.properties | 3 +-- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/gradle-plugin/gradle.properties b/gradle-plugin/gradle.properties index 71b7cfd594..5f1ed7bbe0 100644 --- a/gradle-plugin/gradle.properties +++ b/gradle-plugin/gradle.properties @@ -1,2 +1 @@ -org.gradle.caching=true -android.enableD8=true +org.gradle.caching=true \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 1e7f6c617c..09e1425217 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.jvmargs=-XX:MaxPermSize=512m org.gradle.caching=true -android.enableD8=true +android.enableD8=true \ No newline at end of file diff --git a/realm-annotations/gradle.properties b/realm-annotations/gradle.properties index 71b7cfd594..5f1ed7bbe0 100644 --- a/realm-annotations/gradle.properties +++ b/realm-annotations/gradle.properties @@ -1,2 +1 @@ -org.gradle.caching=true -android.enableD8=true +org.gradle.caching=true \ No newline at end of file diff --git a/realm-transformer/gradle.properties b/realm-transformer/gradle.properties index 71b7cfd594..5f1ed7bbe0 100644 --- a/realm-transformer/gradle.properties +++ b/realm-transformer/gradle.properties @@ -1,2 +1 @@ -org.gradle.caching=true -android.enableD8=true +org.gradle.caching=true \ No newline at end of file diff --git a/realm/gradle.properties b/realm/gradle.properties index e93c5a6121..20cd0c88de 100644 --- a/realm/gradle.properties +++ b/realm/gradle.properties @@ -1,3 +1,2 @@ org.gradle.jvmargs=-Xms512m -Xmx2048m -org.gradle.caching=true -android.enableD8=true +org.gradle.caching=true \ No newline at end of file From 1597699499acea2ffdb7ecf25faa777563e8f221 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 16:01:25 +0530 Subject: [PATCH 1057/2110] Removed unused gradle prproperties from NewsReader --- examples/newsreaderExample/gradle.properties | 1 - 1 file changed, 1 deletion(-) delete mode 100644 examples/newsreaderExample/gradle.properties diff --git a/examples/newsreaderExample/gradle.properties b/examples/newsreaderExample/gradle.properties deleted file mode 100644 index 31590309be..0000000000 --- a/examples/newsreaderExample/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -android.enableD8=true From 9290d1c2b182473c87932d86906867aefc831335 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 16:07:28 +0530 Subject: [PATCH 1058/2110] Typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 569ed9ae4d..35a7d2f2f5 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Generating the Javadoc using the command above may generate warnings. The Javado ### Upgrading Gradle Wrappers - All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer `gradleVersion` propertiy defined in `/realm.properties` in order to determine Geadle Version of generating wrappers. After generating Gradle Wrappers, we need to modify `gradle/wrapper/gradle-wrapper.properties` to use `*-all.zip` distribution instead of `*-bin.zip` distribution. + All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer `gradleVersion` property defined in `/realm.properties` in order to determine Gradle Version of generating wrappers. After generating Gradle Wrappers, we need to modify `gradle/wrapper/gradle-wrapper.properties` to use `*-all.zip` distribution instead of `*-bin.zip` distribution. We have a script `./tools/update_gradle_wrapper.sh` to automate these steps. When you update Gradle Wrappers, please obey the following steps. From 1817190018ddd417242d2995445fd8eac9fc3bc3 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 16:33:03 +0530 Subject: [PATCH 1059/2110] Library Updates --- examples/gridViewExample/build.gradle | 2 +- examples/newsreaderExample/build.gradle | 6 +++--- examples/objectServerExample/build.gradle | 4 ++-- examples/threadExample/build.gradle | 2 +- examples/unitTestExample/build.gradle | 2 +- gradle-plugin/build.gradle | 8 ++++++-- realm-transformer/build.gradle | 4 ++-- realm/build.gradle | 10 +++++----- realm/realm-annotations-processor/build.gradle | 2 +- realm/realm-library/build.gradle | 6 +++--- 10 files changed, 25 insertions(+), 21 deletions(-) diff --git a/examples/gridViewExample/build.gradle b/examples/gridViewExample/build.gradle index a64c883bb7..681edc2472 100644 --- a/examples/gridViewExample/build.gradle +++ b/examples/gridViewExample/build.gradle @@ -43,5 +43,5 @@ android { } dependencies { - implementation 'com.google.code.gson:gson:2.5' + implementation 'com.google.code.gson:gson:2.8.2' } diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index 6faf3cc6b2..d50616b897 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -47,13 +47,13 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.0' //noinspection GradleDependency implementation 'com.android.support:design:27.0.0' - implementation 'com.jakewharton.timber:timber:4.1.0' + implementation 'com.jakewharton.timber:timber:4.5.1' implementation 'com.jakewharton:butterknife:8.5.1' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' implementation 'com.squareup.retrofit2:converter-jackson:2.3.0' implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' - implementation 'io.reactivex.rxjava2:rxjava:2.1.0' + implementation 'io.reactivex.rxjava2:rxjava:2.1.5' implementation 'me.zhanghai.android.materialprogressbar:library:1.1.4' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' //TODO:Can be refactored with Native Android Data Binding } diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 81b1f5c2a8..fdf1e51fb1 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -65,6 +65,6 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.0' implementation 'com.android.support:design:27.0.0' implementation 'me.zhanghai.android.materialprogressbar:library:1.3.0' - implementation 'com.jakewharton:butterknife:8.5.1' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' + implementation 'com.jakewharton:butterknife:8.8.1'//TODO:Can be refactored with Native Android Data Binding + annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'//TODO:Can be refactored with Native Android Data Binding } diff --git a/examples/threadExample/build.gradle b/examples/threadExample/build.gradle index 97957f98f0..15cba318aa 100644 --- a/examples/threadExample/build.gradle +++ b/examples/threadExample/build.gradle @@ -29,5 +29,5 @@ android { dependencies { //noinspection GradleDependency - implementation 'com.android.support:appcompat-v7:24.0.0' + implementation 'com.android.support:appcompat-v7:27.0.0' } diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index 64feeb13b9..e366e57b39 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -41,7 +41,7 @@ android { dependencies { implementation 'com.android.support:appcompat-v7:27.0.0' - testImplementation 'io.reactivex.rxjava2:rxjava:2.1.0' + testImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' // Testing testImplementation 'junit:junit:4.12' diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 456d6c2afc..7962a6cec8 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -46,8 +46,12 @@ dependencies { compile gradleApi() compile localGroovy() compile "io.realm:realm-transformer:${version}" - compile 'com.neenbedankt.gradle.plugins:android-apt:1.8' - provided 'com.android.tools.build:gradle:2.1.0' + /*Note: the latest Android Gradle plugin has now built in support for annotation processors and warns and/or blocks android-apt, + see this https://bitbucket.org/hvisser/android-apt/wiki/Migration page on how to migrate + and this https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ for more info. + */ + compile 'com.neenbedankt.gradle.plugins:android-apt:1.8' //TODO: https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ + provided 'com.android.tools.build:gradle:3.0.0' testCompile gradleTestKit() testCompile 'junit:junit:4.12' diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 7f721d4600..d861caf85d 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -58,8 +58,8 @@ dependencies { compile localGroovy() compile gradleApi() compile "io.realm:realm-annotations:${version}" - provided 'com.android.tools.build:gradle:2.1.0' - compile 'org.javassist:javassist:3.20.0-GA' + provided 'com.android.tools.build:gradle:3.0.0' + compile 'org.javassist:javassist:3.22.0-GA' testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { exclude module: 'groovy-all' diff --git a/realm/build.gradle b/realm/build.gradle index 751a26cafd..d1b36ccbd0 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -10,15 +10,15 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.0.0' - classpath 'de.undercouch:gradle-download-task:3.2.0' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + classpath 'de.undercouch:gradle-download-task:3.3.0' + classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.4' - classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.1' - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.2' + classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2' + classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.4' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" - classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' + classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.13' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index c0145cddb9..c97c1e3635 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -8,7 +8,7 @@ sourceCompatibility = '1.8' targetCompatibility = '1.8' dependencies { - compile "com.squareup:javawriter:2.5.0" + compile "com.squareup:javawriter:2.5.1" compile "io.realm:realm-annotations:${version}" testCompile files('../realm-library/build/intermediates/bundles/baseRelease/classes.jar') // Java projects cannot depend on AAR files diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index f51f463f7d..81abfe6ee7 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -188,7 +188,7 @@ repositories { dependencies { - compileOnly 'io.reactivex.rxjava2:rxjava:2.1.4' + compileOnly 'io.reactivex.rxjava2:rxjava:2.1.5' compileOnly 'com.google.code.findbugs:findbugs-annotations:3.0.1' api "io.realm:realm-annotations:${version}" @@ -200,7 +200,7 @@ dependencies { kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation fileTree(dir: 'testLibs', include: ['*.jar']) - androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.4' + androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test:rules:1.0.1' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' @@ -210,7 +210,7 @@ dependencies { androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" // specify error prone version to prevent sudden failure - errorprone 'com.google.errorprone:error_prone_core:2.0.21' + errorprone 'com.google.errorprone:error_prone_core:2.1.2' } task sourcesJar(type: Jar) { From fe6c432a4324d43fead49df4d3466ec755c477e3 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 16:39:29 +0530 Subject: [PATCH 1060/2110] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35a7d2f2f5..8ff7afe698 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ In case you don't want to use the precompiled version, you can build Realm yours * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. * Download & install the Android SDK **Build-Tools 27.0.0**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). - * If you use Android Studio, Android Studio 3.0 or later is required. + * If you use Android Studio, Android Studio 3.0 or higher is required. * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library/Android`. The download will unzip as the directory `android-ndk-r10e`. From f3b54cbfe387be899800438f67fb246a86aa500d Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 16:43:33 +0530 Subject: [PATCH 1061/2110] Documentation Improvements --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ff7afe698..4018a79797 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,13 @@ The API reference is located at [realm.io/docs/java/api](https://realm.io/docs/j ## Using Snapshots -If you want to test recent bugfixes or features that have not been packaged in an official release yet, you can use a **-SNAPSHOT** release of the current development version of Realm via Gradle, available on [Jfrog OSS](http://oss.jfrog.org/oss-snapshot-local/io/realm/realm-gradle-plugin/) +If you want to test recent bugfixes or features that have not been packaged in an official release yet, you can use a **-SNAPSHOT** release of the current development version of Realm via Gradle, available on [JFrog OSS](http://oss.jfrog.org/oss-snapshot-local/io/realm/realm-gradle-plugin/) -```gradle +```In build.gradle buildscript { repositories { + jcenter() + google() maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' } @@ -44,6 +46,8 @@ buildscript { allprojects { repositories { + jcenter() + google() maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' } From 755213f1321150e6ce4284f9d41953103849ba74 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 19:13:03 +0530 Subject: [PATCH 1062/2110] Update Gradle Wrappers --- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54713 -> 54732 bytes .../gradle/wrapper/gradle-wrapper.jar | Bin 54713 -> 54732 bytes gradle/wrapper/gradle-wrapper.jar | Bin 54713 -> 54732 bytes gradlew | 6 +++--- gradlew.bat | 6 +++--- .../gradle/wrapper/gradle-wrapper.jar | Bin 54713 -> 54732 bytes .../gradle/wrapper/gradle-wrapper.jar | Bin 54713 -> 54732 bytes .../gradle/wrapper/gradle-wrapper.jar | Bin 54713 -> 54732 bytes realm/gradle/wrapper/gradle-wrapper.jar | Bin 54713 -> 54732 bytes 9 files changed, 6 insertions(+), 6 deletions(-) mode change 100644 => 100755 gradlew.bat diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index d457a1a990f3f8cee976589d405f62b13f1eede8..0bdf3fe94139883078c58008a6b84cd063bfaf64 100644 GIT binary patch delta 1824 zcmX|?X*kQO#7)G|?JEG2mVD6zHFpg{>$ z`%*$9mc+i5QQE03?bwZ~F?p|Ro=^94e&^ihy3U7ln#o*)WUh=G+>UQAt7hst@ob0u z6~f7p0k3P2ywA@E@57aWIXFJ@b8uV+klB*J(f!K=8$>)t=Ayyq3t2|VbUCr&j@Znn z(WB!np@>xs-9p-@Jl-sCbtgG-c*lAtvEi6l^^W58)KWpHP@k;dy2$!9Y6bQE`|b^n zYs+(pMo)99xNU>yOD+uZ{Ufn9#WJVECaE3?TyK!jeOmvV5{~-puG>$yeo{#pUQ?7U z3xAPCcsd#vEqZRA_=jtyy1Zl7%Q!uMnu#q{Qn(Q`p_Ic!QLV<`dR3E#d#ix{E0?oQ_uw#FaJ2~=-8rR7Fe{s{1= zwJa+>RLc=6s|wWeIN$r2RXLbkqD*N~&tnocL9cmBjAb!-ai$r#(~>@v>Rj4YM8T=33HaB11N8&PbNVfwd6YEDA+NdBD&4`)N4 z-T|nST9L%7tHXRz<{dy+brsHPy-{|*MlGP(_qEuu8>fT~ag5@1-LIm}qn5ALEtOE$ z@tc$iSbw*B26VxoUe7sHwcQWnl<4D@rEm+ ziZ{%E`jWI_Wixq6ibaliwxplx7a=W;_>2q4OcBtCj!&Oqkk*lwR7F~9_B&dYrN+1D=$7;+N2zcnEsNug zUu6IKI;+DAD=h*xEO`Pi5juM!>kz!^q5(R3)#cX$QjFZxay3L9YBjXC+$S?B4i2=W zW(kFq`c3ckBrj6$Ma9)F3bh1(+e4wVZ|-1KKwia(M>C~Wb8YS-vn>X7LW7zt{8_W1 zGu5v#x0lbl`=1VP8j2lji)p^TiCohKxAw<5Dgk&%_y>?Nz&s!WEI zE{|Awl3jB@)EQ5p^zqx^Lke1^g5KgovdP@fe;=g?rnk415F=v~?TO@~mwAZ}cwPG* zC5>aLNx&+1>M+ZbXd@q}W4C?|10Daw7aH4t{pO$~P6k{~(s7y_q#2%ZzphY@{Gf9< z_OqiNS|NWyqi+q5O3cYw7n~dlN<*2bwa4!+%92d7;sz(F$oFh_ElgfkFbz zIiI=p+^e4U{C^<7Gq^Egot51fwFXf$$%fd7q=y~Gx!woiQ19Thl{_kVUJmT{ z@dD21-1jwo^cAz%2U=f#4;x*ivkdTkGxTJU_AL8Lg|0I0x{)j{30-t+b@5zLlrN#R?oZXcE!#1TcS`}wkk;?=e@t?zTe#yYR&@`=R>G#a3SPP z0dzI~%>s78-_BC`E+2LFc#$PUD_WJbR0SNTJeGc3i@&Z-if#+fJz{U^~lpA&` z^#PY*C6G$wsYC#^!#6;3G3*4Z81YjA18$7KLD)y^P}qnyh&!GwRpTW9)EEp9VZb?0 zZv?{^gh&UF=_qz^odE}l|L^px2&gi3K#JLHiOD|4rj5*hOPEX#5R}9QE~5sZ? zK;i#$G>TzoIsSV(#cYTzmO3?10!5Snk4g}L*$@mU9fNbq107@bprbaRb4>2^Qk?|~ zPl4??9K_iL1dd}t6Wzci_@% delta 1777 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 delta 1777 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 delta 1777 zcmYL~c{tR27r^B0;IzrvFH@IeeW#3XQ!^S`(h^R#}O%?3oarOjPo^Z1S(P{>7K^ zM9tH!c)60IDCHi?`(RVWnLp_8SMnjl$3-no%zI)G-W9X+*-qIktvJ)x3Z7D0&}^CJ z?OW*gPu(=_Utj|e>%;A+mqCZQ%DgfuXKg~4LNwauJJ!csBY)wby!1G{2C2otk<&TPvDHLZ&!pr#=KB29Hj0@gB1SW@~~23(>6 zma8aXZDp4&t{H=AIo9#I#lNqdc43{Y?jP;fu&@&+=yIdV_I<5{)@S9#Z=K{5UX}Rj z!gNct^@CtqhY(9ST^!X{HRixM?Z$b|um|p%i9S)RueKSH4aC~06s$MOc`Bl0`#Ug; zID;@NV!(CN?V$j5TR?||Q&_tsd4^xcja_;v+0cM{=i%#&zBgbOR!k8-5XO$ouPa`t zsn#mIh8Xtis^LSZv=;|;q@8yh@s3b4RS8VTnKnI7%;ipB?KjKa=Q!@=%+VW-T>-hZ z-c65`4HcDr&cyU$f%=Ni_UC^6O_7KzKRKAs9>*NcBD!nk=Khv-u+O`IM*ZiTHU zo0{BFg7H6-Q@3H#9@psGn?~qJFKh4E@{e+{eMg2}(wBT_`rM)k*t%ysKHhryWxNo2 zOQeAC{lpJ7_W<;ahm2~2GmL%GC}J!Cqcg942-8NGS1j zakeKMq?8=`b2z)Q85yy**8F&8OEELv$D_D0pt$WioH?_xR`5LdtI%|BRp@otg{=su zxE^^#G;#H`f1Ihltc$7-8Qp7OJTYlof9dj>HH~4z%k^a$b?5QP=8d>u2nr#Ml}u>t zUQ0>oQ%x4BGV<&CR*e6-l`=h^@g-Cg8_O@*J>JA3Nb_p*ZY8FCH7HfXv|J&Gs3c~5 z{5NUGfW*sl41Ttm3^Tp>TO->_N`BSrsaTagGvrdW>QQeNEX8yUF(kjZf!QRaF$1!WQsFT%bAwj*O0454AO^VCFuU3Ari7fZab>y zfi&cW>X^<047eavrHOKVyHYca3sabk@n(k>MT?mWE3`DyR{arJU&w^wa8N z_m_BGQFkv?_@B)UhBJKtQaDIu%YnO0HNgZ>t^W=nd4FHD^&0@95B5=J;3{DIXdhDs ztbvus`?xb;0vt=-$2+X6fLQuI-cFYS`79*>nz`Q^4jKXCS^s^Mbz*M<2N{D%;8@PS z@?(pD$JrNwb<+P@6G0YO!*&-`0O3P{fKSn$mIWz87X{(q;E*ja-vVB0klnjAB47;@ z4tjHt0KXl4$nga_>EH@oA2jTPgS0OAA%TA)3$)^@OaA(WlY35lFMrkl2M^5So/dev/null +cd "`dirname \"$PRG\"`/.." >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null @@ -64,7 +64,7 @@ case "`uname`" in ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH=$APP_HOME/lib/gradle-launcher-4.3.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then @@ -162,7 +162,7 @@ save () { APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.launcher.GradleMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then diff --git a/gradlew.bat b/gradlew.bat old mode 100644 new mode 100755 index e95643d6a2..74c5a12175 --- a/gradlew.bat +++ b/gradlew.bat @@ -11,7 +11,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% +set APP_HOME=%DIRNAME%.. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @@ -63,10 +63,10 @@ set CMD_LINE_ARGS=%* :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH=%APP_HOME%\lib\gradle-launcher-4.3.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.launcher.GradleMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.jar b/library-benchmarks/gradle/wrapper/gradle-wrapper.jar index d457a1a990f3f8cee976589d405f62b13f1eede8..0bdf3fe94139883078c58008a6b84cd063bfaf64 100644 GIT binary patch delta 1824 zcmX|?X*kQO#7)G|?JEG2mVD6zHFpg{>$ z`%*$9mc+i5QQE03?bwZ~F?p|Ro=^94e&^ihy3U7ln#o*)WUh=G+>UQAt7hst@ob0u z6~f7p0k3P2ywA@E@57aWIXFJ@b8uV+klB*J(f!K=8$>)t=Ayyq3t2|VbUCr&j@Znn z(WB!np@>xs-9p-@Jl-sCbtgG-c*lAtvEi6l^^W58)KWpHP@k;dy2$!9Y6bQE`|b^n zYs+(pMo)99xNU>yOD+uZ{Ufn9#WJVECaE3?TyK!jeOmvV5{~-puG>$yeo{#pUQ?7U z3xAPCcsd#vEqZRA_=jtyy1Zl7%Q!uMnu#q{Qn(Q`p_Ic!QLV<`dR3E#d#ix{E0?oQ_uw#FaJ2~=-8rR7Fe{s{1= zwJa+>RLc=6s|wWeIN$r2RXLbkqD*N~&tnocL9cmBjAb!-ai$r#(~>@v>Rj4YM8T=33HaB11N8&PbNVfwd6YEDA+NdBD&4`)N4 z-T|nST9L%7tHXRz<{dy+brsHPy-{|*MlGP(_qEuu8>fT~ag5@1-LIm}qn5ALEtOE$ z@tc$iSbw*B26VxoUe7sHwcQWnl<4D@rEm+ ziZ{%E`jWI_Wixq6ibaliwxplx7a=W;_>2q4OcBtCj!&Oqkk*lwR7F~9_B&dYrN+1D=$7;+N2zcnEsNug zUu6IKI;+DAD=h*xEO`Pi5juM!>kz!^q5(R3)#cX$QjFZxay3L9YBjXC+$S?B4i2=W zW(kFq`c3ckBrj6$Ma9)F3bh1(+e4wVZ|-1KKwia(M>C~Wb8YS-vn>X7LW7zt{8_W1 zGu5v#x0lbl`=1VP8j2lji)p^TiCohKxAw<5Dgk&%_y>?Nz&s!WEI zE{|Awl3jB@)EQ5p^zqx^Lke1^g5KgovdP@fe;=g?rnk415F=v~?TO@~mwAZ}cwPG* zC5>aLNx&+1>M+ZbXd@q}W4C?|10Daw7aH4t{pO$~P6k{~(s7y_q#2%ZzphY@{Gf9< z_OqiNS|NWyqi+q5O3cYw7n~dlN<*2bwa4!+%92d7;sz(F$oFh_ElgfkFbz zIiI=p+^e4U{C^<7Gq^Egot51fwFXf$$%fd7q=y~Gx!woiQ19Thl{_kVUJmT{ z@dD21-1jwo^cAz%2U=f#4;x*ivkdTkGxTJU_AL8Lg|0I0x{)j{30-t+b@5zLlrN#R?oZXcE!#1TcS`}wkk;?=e@t?zTe#yYR&@`=R>G#a3SPP z0dzI~%>s78-_BC`E+2LFc#$PUD_WJbR0SNTJeGc3i@&Z-if#+fJz{U^~lpA&` z^#PY*C6G$wsYC#^!#6;3G3*4Z81YjA18$7KLD)y^P}qnyh&!GwRpTW9)EEp9VZb?0 zZv?{^gh&UF=_qz^odE}l|L^px2&gi3K#JLHiOD|4rj5*hOPEX#5R}9QE~5sZ? zK;i#$G>TzoIsSV(#cYTzmO3?10!5Snk4g}L*$@mU9fNbq107@bprbaRb4>2^Qk?|~ zPl4??9K_iL1dd}t6Wzci_@% delta 1777 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 delta 1777 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 delta 1777 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 delta 1777 zcmYL~c{tR27r^B0;IzrvFH@IeeW#3XQ!^S`(h^R#}O%?3oarOjPo^Z1S(P{>7K^ zM9tH!c)60IDCHi?`(RVWnLp_8SMnjl$3-no%zI)G-W9X+*-qIktvJ)x3Z7D0&}^CJ z?OW*gPu(=_Utj|e>%;A+mqCZQ%DgfuXKg~4LNwauJJ!csBY)wby!1G{2C2otk<&TPvDHLZ&!pr#=KB29Hj0@gB1SW@~~23(>6 zma8aXZDp4&t{H=AIo9#I#lNqdc43{Y?jP;fu&@&+=yIdV_I<5{)@S9#Z=K{5UX}Rj z!gNct^@CtqhY(9ST^!X{HRixM?Z$b|um|p%i9S)RueKSH4aC~06s$MOc`Bl0`#Ug; zID;@NV!(CN?V$j5TR?||Q&_tsd4^xcja_;v+0cM{=i%#&zBgbOR!k8-5XO$ouPa`t zsn#mIh8Xtis^LSZv=;|;q@8yh@s3b4RS8VTnKnI7%;ipB?KjKa=Q!@=%+VW-T>-hZ z-c65`4HcDr&cyU$f%=Ni_UC^6O_7KzKRKAs9>*NcBD!nk=Khv-u+O`IM*ZiTHU zo0{BFg7H6-Q@3H#9@psGn?~qJFKh4E@{e+{eMg2}(wBT_`rM)k*t%ysKHhryWxNo2 zOQeAC{lpJ7_W<;ahm2~2GmL%GC}J!Cqcg942-8NGS1j zakeKMq?8=`b2z)Q85yy**8F&8OEELv$D_D0pt$WioH?_xR`5LdtI%|BRp@otg{=su zxE^^#G;#H`f1Ihltc$7-8Qp7OJTYlof9dj>HH~4z%k^a$b?5QP=8d>u2nr#Ml}u>t zUQ0>oQ%x4BGV<&CR*e6-l`=h^@g-Cg8_O@*J>JA3Nb_p*ZY8FCH7HfXv|J&Gs3c~5 z{5NUGfW*sl41Ttm3^Tp>TO->_N`BSrsaTagGvrdW>QQeNEX8yUF(kjZf!QRaF$1!WQsFT%bAwj*O0454AO^VCFuU3Ari7fZab>y zfi&cW>X^<047eavrHOKVyHYca3sabk@n(k>MT?mWE3`DyR{arJU&w^wa8N z_m_BGQFkv?_@B)UhBJKtQaDIu%YnO0HNgZ>t^W=nd4FHD^&0@95B5=J;3{DIXdhDs ztbvus`?xb;0vt=-$2+X6fLQuI-cFYS`79*>nz`Q^4jKXCS^s^Mbz*M<2N{D%;8@PS z@?(pD$JrNwb<+P@6G0YO!*&-`0O3P{fKSn$mIWz87X{(q;E*ja-vVB0klnjAB47;@ z4tjHt0KXl4$nga_>EH@oA2jTPgS0OAA%TA)3$)^@OaA(WlY35lFMrkl2M^5So Date: Thu, 26 Oct 2017 19:17:30 +0530 Subject: [PATCH 1063/2110] fix --- gradlew | 4 ++-- gradlew.bat | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gradlew b/gradlew index e31b7be05e..3a348dc4df 100755 --- a/gradlew +++ b/gradlew @@ -64,7 +64,7 @@ case "`uname`" in ;; esac -CLASSPATH=$APP_HOME/lib/gradle-launcher-4.3.jar +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then @@ -162,7 +162,7 @@ save () { APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.launcher.GradleMain "$APP_ARGS" +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then diff --git a/gradlew.bat b/gradlew.bat index 74c5a12175..e95643d6a2 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -11,7 +11,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME%.. +set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @@ -63,10 +63,10 @@ set CMD_LINE_ARGS=%* :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\lib\gradle-launcher-4.3.jar +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.launcher.GradleMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell From a3a34c0087fdb6e23165d5d7cc8858c17cb0e3f5 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 19:25:15 +0530 Subject: [PATCH 1064/2110] Wrapper Updates --- examples/gradlew.bat | 0 gradle-plugin/gradlew.bat | 0 gradlew | 2 +- library-benchmarks/gradlew.bat | 0 realm-annotations/gradlew.bat | 0 realm-transformer/gradlew.bat | 0 realm/gradlew.bat | 0 7 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 examples/gradlew.bat mode change 100644 => 100755 gradle-plugin/gradlew.bat mode change 100644 => 100755 library-benchmarks/gradlew.bat mode change 100644 => 100755 realm-annotations/gradlew.bat mode change 100644 => 100755 realm-transformer/gradlew.bat mode change 100644 => 100755 realm/gradlew.bat diff --git a/examples/gradlew.bat b/examples/gradlew.bat old mode 100644 new mode 100755 diff --git a/gradle-plugin/gradlew.bat b/gradle-plugin/gradlew.bat old mode 100644 new mode 100755 diff --git a/gradlew b/gradlew index 3a348dc4df..cccdd3d517 100755 --- a/gradlew +++ b/gradlew @@ -20,7 +20,7 @@ while [ -h "$PRG" ] ; do fi done SAVED="`pwd`" -cd "`dirname \"$PRG\"`/.." >/dev/null +cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null diff --git a/library-benchmarks/gradlew.bat b/library-benchmarks/gradlew.bat old mode 100644 new mode 100755 diff --git a/realm-annotations/gradlew.bat b/realm-annotations/gradlew.bat old mode 100644 new mode 100755 diff --git a/realm-transformer/gradlew.bat b/realm-transformer/gradlew.bat old mode 100644 new mode 100755 diff --git a/realm/gradlew.bat b/realm/gradlew.bat old mode 100644 new mode 100755 From d3036c9399b95742fd508bdbfe6f88ee9ef65e73 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 19:42:40 +0530 Subject: [PATCH 1065/2110] Update --- examples/secureTokenAndroidKeyStore/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index af5c9c4fcd..d049a183af 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -34,7 +34,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { + androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', { exclude group: 'com.android.support', module: 'support-annotations' }) implementation 'com.android.support:appcompat-v7:27.0.0' From edb12fd9f4405f97f8844bc66675b34f173ddfe2 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 19:47:06 +0530 Subject: [PATCH 1066/2110] Android Gradle Plugin 3.1.0-alpha01 --- examples/build.gradle | 2 +- gradle-plugin/build.gradle | 2 +- .../src/test/groovy/io/realm/gradle/PluginTest.groovy | 4 ++-- library-benchmarks/build.gradle | 2 +- realm-transformer/build.gradle | 2 +- realm/build.gradle | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 75ee1a1a83..90bcb1a6c4 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -24,7 +24,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.1.0-alpha01' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-gradle-plugin:${currentVersion}" diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 7962a6cec8..693e66f371 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -51,7 +51,7 @@ dependencies { and this https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ for more info. */ compile 'com.neenbedankt.gradle.plugins:android-apt:1.8' //TODO: https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ - provided 'com.android.tools.build:gradle:3.0.0' + provided 'com.android.tools.build:gradle:3.1.0-alpha01' testCompile gradleTestKit() testCompile 'junit:junit:4.12' diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index c99d4f53b4..bca3c3a3dd 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -53,7 +53,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:3.1.0-alpha01' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } @@ -78,7 +78,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:3.1.0-alpha01' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index ffc3e81c30..6626a7fe11 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.1.0-alpha01' classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" } } diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index d861caf85d..a0548ecfd5 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -58,7 +58,7 @@ dependencies { compile localGroovy() compile gradleApi() compile "io.realm:realm-annotations:${version}" - provided 'com.android.tools.build:gradle:3.0.0' + provided 'com.android.tools.build:gradle:3.1.0-alpha01' compile 'org.javassist:javassist:3.22.0-GA' testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { diff --git a/realm/build.gradle b/realm/build.gradle index d1b36ccbd0..7facca4531 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.1.0-alpha01' classpath 'de.undercouch:gradle-download-task:3.3.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' From 3146dc6de3d3c95dab1479b3d41f2f054fe70acc Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Thu, 26 Oct 2017 20:23:09 +0530 Subject: [PATCH 1067/2110] Documentation Updates and Added Badge --- CHANGELOG.md | 4 ++-- README.md | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5339065f72..da4acf5de8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Bug Fixes -### Interal +### Internal ### Credits @@ -21,7 +21,7 @@ * Fixed the compile warnings of using deprecated method `RealmProxyMediator.getTableName()` in generated mediator classes (#5455). -### Interal +### Internal * Updated Realm Sync to 2.1.0 diff --git a/README.md b/README.md index 4018a79797..f93fac19a3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ ![Realm](logo.png) +[![bintray](https://api.bintray.com/packages/realm/maven/realm-gradle-plugin/images/download.svg) ](https://bintray.com/realm/maven/realm-gradle-plugin/_latestVersion) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/realm/realm-java/blob/master/LICENSE) + Realm is a mobile database that runs directly inside phones, tablets or wearables. This repository holds the source code for the Java version of Realm, which currently runs only on Android. @@ -21,7 +24,7 @@ The API reference is located at [realm.io/docs/java/api](https://realm.io/docs/j ## Getting Help -- **Need help with your code?**: Look for previous questions on the [#realm tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) — or [ask a new question](http://stackoverflow.com/questions/ask?tags=realm). We activtely monitor & answer questions on SO! +- **Need help with your code?**: Look for previous questions on the [#realm tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) — or [ask a new question](http://stackoverflow.com/questions/ask?tags=realm). We actively monitor & answer questions on StackOverflow! - **Have a bug to report?** [Open an issue](https://github.com/realm/realm-java/issues/new). If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue. - **Have a feature request?** [Open an issue](https://github.com/realm/realm-java/issues/new). Tell us what the feature should do, and why you want the feature. - Sign up for our [**Community Newsletter**](https://go.pardot.com/l/210132/2017-04-26/3j74l) to get regular tips, learn about other use-cases and get alerted of blogposts and tutorials about Realm. @@ -158,8 +161,7 @@ Generating the Javadoc using the command above may generate warnings. The Javado ### Upgrading Gradle Wrappers - All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer `gradleVersion` property defined in `/realm.properties` in order to determine Gradle Version of generating wrappers. After generating Gradle Wrappers, we need to modify `gradle/wrapper/gradle-wrapper.properties` to use `*-all.zip` distribution instead of `*-bin.zip` distribution. - + All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer `gradleVersion` property defined in `/realm.properties` in order to determine Gradle Version of generating wrappers. We have a script `./tools/update_gradle_wrapper.sh` to automate these steps. When you update Gradle Wrappers, please obey the following steps. 1. Edit `gradleVersion` property in defined in `/realm.properties` to new Gradle Wrapper version. From 900d8e72a6844fb44c3464ac1a1eac07e7c91b78 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 27 Oct 2017 09:46:59 +0100 Subject: [PATCH 1068/2110] fix secureTokenAndroidKeyStore example to pass monkey test (#5477) * fix secureTokenAndroidKeyStore example to pass monkey test --- .../MainActivity.java | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java index 167ab982fd..6fff42f95e 100644 --- a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java +++ b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java @@ -23,18 +23,15 @@ import com.example.securetokenandroidkeystore.R; -import org.json.JSONException; -import org.json.JSONObject; - import java.security.KeyStoreException; -import java.util.UUID; +import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.SyncConfiguration; +import io.realm.SyncCredentials; import io.realm.SyncManager; import io.realm.SyncUser; import io.realm.android.SecureUserStore; -import io.realm.internal.objectserver.Token; /** * Activity responsible of unlocking the KeyStore @@ -86,29 +83,21 @@ protected void onResume() { // build SyncConfiguration with a user store to store encrypted Token. private void buildSyncConf() { // the rest of Sync logic ... - SyncUser user = createTestUser(Long.MAX_VALUE); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); - Realm realm = Realm.getInstance(secureConfig); - // ... - } - - // Helpers - private final static String USER_TOKEN = UUID.randomUUID().toString(); + SyncCredentials credentials = SyncCredentials.usernamePassword("username", "password"); + final String urlAuth = "http://objectserver.realm.io:9080/auth"; + final String url = "realm://objectserver.realm.io/default"; + + SyncUser.loginAsync(credentials, urlAuth, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser user) { + SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); + Realm realm = Realm.getInstance(secureConfig); + // ... + } - private static SyncUser createTestUser(long expires) { - Token userToken = new Token(USER_TOKEN, "JohnDoe", null, expires, null); - JSONObject obj = new JSONObject(); - try { - JSONObject realmDesc = new JSONObject(); - realmDesc.put("uri", "realm://objectserver.realm.io/default"); - - obj.put("authUrl", "http://objectserver.realm.io/auth"); - obj.put("userToken", userToken.toJson()); - return SyncUser.fromJson(obj.toString()); - } catch (JSONException e) { - throw new RuntimeException(e); - } + @Override + public void onError(ObjectServerError error) {} + }); } private void keystoreLockedMessage() { From b2de99d6c7be1f2d2e0a88a09b53a4c151b9146b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 27 Oct 2017 10:05:17 +0100 Subject: [PATCH 1069/2110] Fix monkey, release no longer need a real device (#5478) * Remove dependency to android-command plugin because of https://github.com/novoda/gradle-android-command-plugin/issues/122 now running monkey without the plugin --- examples/build.gradle | 35 ++++++++++++++++++- examples/encryptionExample/build.gradle | 5 --- examples/gridViewExample/build.gradle | 5 --- examples/introExample/build.gradle | 4 --- examples/jsonExample/build.gradle | 5 --- examples/kotlinExample/build.gradle | 5 --- examples/migrationExample/build.gradle | 4 --- examples/moduleExample/app/build.gradle | 5 --- examples/newsreaderExample/build.gradle | 5 --- examples/objectServerExample/build.gradle | 5 --- examples/rxJavaExample/build.gradle | 5 --- .../secureTokenAndroidKeyStore/build.gradle | 4 --- examples/settings.gradle | 2 -- examples/threadExample/build.gradle | 4 --- examples/unitTestExample/build.gradle | 5 --- tools/release.sh | 4 +-- version.txt | 2 +- 17 files changed, 37 insertions(+), 67 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 17d9a57116..944e287edb 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -6,6 +6,15 @@ configurations.all { resolutionStrategy.cacheChangingModulesFor 0, 'seconds' } +static String getAppId (path) { + String build = new File(path).text + def matcher = build =~ 'applicationId.*' + def appId = matcher.size() > 0 ? matcher[0].trim() - 'applicationId' - ~/\s/ : ''; + String myappId = appId.replaceAll('"', '') + myappId = myappId.replaceAll('\'', '') + return myappId +} + allprojects { def currentVersion = file("${rootDir}/../version.txt").text.trim() @@ -24,7 +33,6 @@ allprojects { } dependencies { classpath 'com.android.tools.build:gradle:3.0.0-rc2' - classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } @@ -38,6 +46,31 @@ allprojects { jcenter() google() } + + if (!project.name.startsWith("realm-examples") + && !project.name.startsWith("library") + && !project.name.startsWith("moduleExample")) { // exclude root and library project + ["Debug", "Release"].each { + task "monkey${it}"(dependsOn: "install${it}") { + doLast { + def numberOfEvents = 2000 + def appId = getAppId("${project.projectDir}/build.gradle") + def process = "adb shell monkey -p ${appId} ${numberOfEvents}".execute([], project.rootDir) + + def sout = new StringBuilder(), serr = new StringBuilder() + process.consumeProcessOutput(sout, serr) + process.waitFor() + + if (process.exitValue() != 0 + || serr?.toString()?.trim()?.size() > 0 + || !sout?.toString()?.trim()?.contains("Events injected: ${numberOfEvents}")) { + // fail Gradle build + throw new GradleException("monkey failed for AppID: ${appId} \nStd out: ${sout}\nStd err: ${serr}") + } + } + } + } + } } task wrapper(type: Wrapper) { diff --git a/examples/encryptionExample/build.gradle b/examples/encryptionExample/build.gradle index 4655dfef93..1d7baebd33 100644 --- a/examples/encryptionExample/build.gradle +++ b/examples/encryptionExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -25,8 +24,4 @@ android { } productFlavors { } - - command { - monkey.events 2000 - } } diff --git a/examples/gridViewExample/build.gradle b/examples/gridViewExample/build.gradle index 178240ccf4..10615bfbe1 100644 --- a/examples/gridViewExample/build.gradle +++ b/examples/gridViewExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -27,10 +26,6 @@ android { productFlavors { } - command { - monkey.events 2000 - } - splits { // Split apks on build target ABI, view all options for the splits here: // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits diff --git a/examples/introExample/build.gradle b/examples/introExample/build.gradle index 5d4bb7093f..fdf0d2dfdc 100644 --- a/examples/introExample/build.gradle +++ b/examples/introExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -27,7 +26,4 @@ android { productFlavors { } - command { - monkey.events 2000 - } } diff --git a/examples/jsonExample/build.gradle b/examples/jsonExample/build.gradle index b4327f86b3..e3bd188356 100644 --- a/examples/jsonExample/build.gradle +++ b/examples/jsonExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -25,10 +24,6 @@ android { } productFlavors { } - - command { - monkey.events 2000 - } } dependencies { diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index ad5ff37929..91d3f3166a 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -12,7 +12,6 @@ buildscript { apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -40,10 +39,6 @@ android { } } - command { - monkey.events 2000 - } - sourceSets { main.java.srcDirs += 'src/main/kotlin' } diff --git a/examples/migrationExample/build.gradle b/examples/migrationExample/build.gradle index 53e5aaafa7..b7991c006c 100644 --- a/examples/migrationExample/build.gradle +++ b/examples/migrationExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -22,7 +21,4 @@ android { minifyEnabled true } } - command { - monkey.events 2000 - } } diff --git a/examples/moduleExample/app/build.gradle b/examples/moduleExample/app/build.gradle index 328822593e..424f257901 100644 --- a/examples/moduleExample/app/build.gradle +++ b/examples/moduleExample/app/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -30,10 +29,6 @@ android { signingConfig signingConfigs.release } } - - command { - monkey.events 2000 - } } dependencies { diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index c62c2d757f..e5cfcf0acb 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -22,10 +21,6 @@ android { } } - command { - monkey.events 2000 - } - lintOptions { disable 'InvalidPackage' } diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index db3f530a2f..722e362451 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' // Credit: http://jeremie-martinez.com/2015/05/05/inject-host-gradle/ @@ -51,10 +50,6 @@ android { signingConfig signingConfigs.debug } } - - command { - monkey.events 2000 - } } realm { diff --git a/examples/rxJavaExample/build.gradle b/examples/rxJavaExample/build.gradle index 2a0393b8ab..d6269c5c59 100644 --- a/examples/rxJavaExample/build.gradle +++ b/examples/rxJavaExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -25,10 +24,6 @@ android { } } - command { - monkey.events 2000 - } - packagingOptions { exclude 'META-INF/LICENSE' } diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index a25f094d9b..9e90be2ad2 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -27,9 +26,6 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - command { - monkey.events 2000 - } } dependencies { diff --git a/examples/settings.gradle b/examples/settings.gradle index 0f9f5242bd..5dbeeef366 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -7,7 +7,6 @@ include 'kotlinExample' include 'migrationExample' include 'moduleExample:app' include 'moduleExample:library' -include 'realmModuleExample' include 'threadExample' include 'unitTestExample' include 'newsreaderExample' @@ -15,4 +14,3 @@ include 'rxJavaExample' include 'objectServerExample' rootProject.name = 'realm-examples' - diff --git a/examples/threadExample/build.gradle b/examples/threadExample/build.gradle index 25980aa6a9..91f88acc74 100644 --- a/examples/threadExample/build.gradle +++ b/examples/threadExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -22,9 +21,6 @@ android { minifyEnabled true } } - command { - monkey.events 2000 - } } dependencies { diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index cdb4e5535f..ef7e152157 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { @@ -27,10 +26,6 @@ android { } } - command { - monkey.events 2000 - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 diff --git a/tools/release.sh b/tools/release.sh index a8494e50fb..5d38989bb5 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -165,7 +165,7 @@ build() { check_adb_device # Verify examples - (cd examples && ./gradlew uninstallAll && ./gradlew monkeyDebug) + (cd examples && ./gradlew clean uninstallAll && ./gradlew monkeyDebug) } upload_to_bintray() { @@ -200,7 +200,7 @@ publish_distribution() { # Test check_adb_device pushd examples/ - ./gradlew uninstallAll + ./gradlew clean uninstallAll ./gradlew monkeyRelease popd popd diff --git a/version.txt b/version.txt index 2f81801b79..5ebf768a4b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.1.1-SNAPSHOT \ No newline at end of file +4.1.1-SNAPSHOT From f923b6a03b22ecf926d92a9f6caf5c1c5d5ac9d7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 27 Oct 2017 10:14:16 +0100 Subject: [PATCH 1070/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b86fcc6fd..9c40fefa54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.1.1 (YYYY-MM-DD) +## 4.1.1 (2017-10-27) ### Breaking Changes From c8e4d7c8e206d53ac3cca2bc3ad2b6ff9ca8beaf Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 27 Oct 2017 10:14:20 +0100 Subject: [PATCH 1071/2110] Release v4.1.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5ebf768a4b..2582dddfd5 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.1.1-SNAPSHOT +4.1.1 \ No newline at end of file From cdd6ee589fce4bf0f4ad1525b489913a5a03d479 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 27 Oct 2017 10:14:20 +0100 Subject: [PATCH 1072/2110] Prepare next release v4.1.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 2582dddfd5..d1a8f58b38 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.1.1 \ No newline at end of file +4.1.2-SNAPSHOT \ No newline at end of file From 325bcf69129a73ba944025b68460e0219d4bd269 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 26 Oct 2017 19:25:29 +0800 Subject: [PATCH 1073/2110] Remove a FIXME --- .../src/main/cpp/io_realm_internal_OsObjectStore.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp index a3018f02d8..4ffff87662 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp @@ -29,9 +29,8 @@ using namespace realm::jni_util; using namespace realm::util; using namespace realm::_impl; -// FIXME: Enable after https://github.com/realm/realm-object-store/pull/550 merged -//static_assert(io_realm_internal_OsObjectStore_SCHEMA_NOT_VERSIONED == static_cast(ObjectStore::NotVersioned), -// ""); +static_assert(io_realm_internal_OsObjectStore_SCHEMA_NOT_VERSIONED == static_cast(ObjectStore::NotVersioned), + ""); JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetPrimaryKeyForObject(JNIEnv* env, jclass, jlong shared_realm_ptr, From 77d99b06408b2d3705f6404e0c6c484f9c404223 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 31 Oct 2017 17:59:54 +0800 Subject: [PATCH 1074/2110] Rename SharedRealm to OsSharedRealm (#5482) --- .../processor/RealmProxyClassGenerator.java | 1 - .../RealmProxyMediatorGenerator.java | 1 - .../io/realm/AllTypesRealmProxy.java | 1 - .../io/realm/BooleansRealmProxy.java | 1 - .../io/realm/NullTypesRealmProxy.java | 1 - .../io/realm/RealmDefaultModuleMediator.java | 1 - .../resources/io/realm/SimpleRealmProxy.java | 1 - .../androidTest/java/io/realm/RealmTests.java | 8 +-- .../androidTest/java/io/realm/TestHelper.java | 14 ++--- .../io/realm/internal/JNIColumnInfoTest.java | 4 +- .../java/io/realm/internal/JNIQueryTest.java | 4 +- .../java/io/realm/internal/JNIRowTest.java | 4 +- .../io/realm/internal/JNITableInsertTest.java | 4 +- .../java/io/realm/internal/JNITableTest.java | 11 ++-- .../java/io/realm/internal/OsListTests.java | 4 +- .../io/realm/internal/OsObjectStoreTests.java | 4 +- .../io/realm/internal/OsResultsTests.java | 36 +++++------ ...ealmTests.java => OsSharedRealmTests.java} | 16 ++--- .../io/realm/internal/PrimaryKeyTests.java | 18 +++--- .../io/realm/internal/RealmNotifierTests.java | 24 +++---- .../realm/internal/SortDescriptorTests.java | 4 +- .../internal/TableIndexAndDistinctTest.java | 4 +- .../io/realm/SyncedRealmMigrationTests.java | 4 +- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 14 ++--- ...on.cpp => io_realm_internal_OsResults.cpp} | 0 ...pp => io_realm_internal_OsSharedRealm.cpp} | 62 +++++++++---------- .../src/main/cpp/java_class_global_def.hpp | 4 +- realm/realm-library/src/main/cpp/util.cpp | 16 ++--- .../src/main/java/io/realm/BaseRealm.java | 38 ++++++------ .../src/main/java/io/realm/DynamicRealm.java | 10 +-- .../src/main/java/io/realm/Realm.java | 18 +++--- .../src/main/java/io/realm/RealmCache.java | 8 +-- .../realm/exceptions/RealmFileException.java | 18 +++--- .../java/io/realm/internal/ColumnIndices.java | 2 +- .../java/io/realm/internal/ColumnInfo.java | 2 +- .../main/java/io/realm/internal/OsList.java | 2 +- .../main/java/io/realm/internal/OsObject.java | 14 ++--- .../java/io/realm/internal/OsObjectStore.java | 14 ++--- .../java/io/realm/internal/OsRealmConfig.java | 30 ++++----- .../java/io/realm/internal/OsResults.java | 16 ++--- .../java/io/realm/internal/OsSchemaInfo.java | 14 ++--- .../{SharedRealm.java => OsSharedRealm.java} | 46 +++++++------- .../java/io/realm/internal/PendingRow.java | 6 +- .../java/io/realm/internal/ProxyUtils.java | 2 +- .../java/io/realm/internal/RealmNotifier.java | 8 +-- .../main/java/io/realm/internal/Table.java | 10 +-- .../android/AndroidRealmNotifier.java | 4 +- .../main/java/io/realm/log/RealmLogger.java | 2 +- .../objectServer/java/io/realm/ErrorCode.java | 2 +- 50 files changed, 261 insertions(+), 273 deletions(-) rename realm/realm-library/src/androidTest/java/io/realm/internal/{SharedRealmTests.java => OsSharedRealmTests.java} (91%) rename realm/realm-library/src/main/cpp/{io_realm_internal_Collection.cpp => io_realm_internal_OsResults.cpp} (100%) rename realm/realm-library/src/main/cpp/{io_realm_internal_SharedRealm.cpp => io_realm_internal_OsSharedRealm.cpp} (86%) rename realm/realm-library/src/main/java/io/realm/internal/{SharedRealm.java => OsSharedRealm.java} (92%) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index fcb035547e..c20a722be2 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -58,7 +58,6 @@ public class RealmProxyClassGenerator { "io.realm.internal.ProxyUtils", "io.realm.internal.RealmObjectProxy", "io.realm.internal.Row", - "io.realm.internal.SharedRealm", "io.realm.internal.Table", "io.realm.internal.android.JsonUtils", "io.realm.log.RealmLog", diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index 4e1a89aba5..1370de9e12 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -76,7 +76,6 @@ public void generate() throws IOException { "java.util.Iterator", "java.util.Collection", "io.realm.internal.ColumnInfo", - "io.realm.internal.SharedRealm", "io.realm.internal.RealmObjectProxy", "io.realm.internal.RealmProxyMediator", "io.realm.internal.Row", diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 0c429a8dec..ad9ce265ac 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -15,7 +15,6 @@ import io.realm.internal.ProxyUtils; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.android.JsonUtils; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 28beeb3ebb..77d04eae89 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -15,7 +15,6 @@ import io.realm.internal.ProxyUtils; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.android.JsonUtils; import io.realm.log.RealmLog; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 65562e6d8e..2a7f2cf9d6 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -15,7 +15,6 @@ import io.realm.internal.ProxyUtils; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.android.JsonUtils; import io.realm.log.RealmLog; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index c8f0dc1849..29e965598d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -8,7 +8,6 @@ import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; -import io.realm.internal.SharedRealm; import java.io.IOException; import java.util.Collection; import java.util.Collections; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 150ca08c9c..4d882b466a 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -15,7 +15,6 @@ import io.realm.internal.ProxyUtils; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.android.JsonUtils; import io.realm.log.RealmLog; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 48d43d74d0..7d872718ba 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -102,7 +102,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; -import io.realm.internal.SharedRealm; +import io.realm.internal.OsSharedRealm; import io.realm.internal.Table; import io.realm.internal.util.Pair; import io.realm.log.RealmLog; @@ -649,13 +649,13 @@ public void cancelTransaction() { @Test public void executeTransaction_null() { - SharedRealm.VersionID oldVersion = realm.sharedRealm.getVersionID(); + OsSharedRealm.VersionID oldVersion = realm.sharedRealm.getVersionID(); try { realm.executeTransaction(null); fail("null transaction should throw"); } catch (IllegalArgumentException ignored) { } - SharedRealm.VersionID newVersion = realm.sharedRealm.getVersionID(); + OsSharedRealm.VersionID newVersion = realm.sharedRealm.getVersionID(); assertEquals(oldVersion, newVersion); } @@ -4232,7 +4232,7 @@ public void namedPipeDirForExternalStorage() { realm.close(); realm = null; - final File namedPipeDir = SharedRealm.getTemporaryDirectory(); + final File namedPipeDir = OsSharedRealm.getTemporaryDirectory(); assertTrue(namedPipeDir.isDirectory()); TestHelper.deleteRecursively(namedPipeDir); //noinspection ResultOfMethodCallIgnored diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java index 4ef63520df..a765dc60c2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java @@ -57,7 +57,7 @@ import io.realm.entities.PrimaryKeyAsString; import io.realm.internal.OsResults; import io.realm.internal.OsObject; -import io.realm.internal.SharedRealm; +import io.realm.internal.OsSharedRealm; import io.realm.internal.Table; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.LogLevel; @@ -219,22 +219,22 @@ public static long addRowWithValues(Table table, Object... values) { /** * Creates an empty table whose name is "temp" with 1 column of all our supported column types, currently 7 columns. * - * @param sharedRealm A {@link SharedRealm} where the table is created. + * @param sharedRealm A {@link OsSharedRealm} where the table is created. * @return created table. */ - public static Table createTableWithAllColumnTypes(SharedRealm sharedRealm) { + public static Table createTableWithAllColumnTypes(OsSharedRealm sharedRealm) { return createTableWithAllColumnTypes(sharedRealm, "temp"); } /** * Creates an empty table with 1 column of all our supported column types, currently 7 columns. * - * @param sharedRealm A {@link SharedRealm} where the table is created. + * @param sharedRealm A {@link OsSharedRealm} where the table is created. * @param name name of the table. * @return created table. */ @SuppressWarnings("WeakerAccess") - public static Table createTableWithAllColumnTypes(SharedRealm sharedRealm, + public static Table createTableWithAllColumnTypes(OsSharedRealm sharedRealm, @SuppressWarnings("SameParameterValue") String name) { boolean wasInTransaction = sharedRealm.isInTransaction(); if (!wasInTransaction) { @@ -264,7 +264,7 @@ public static Table createTableWithAllColumnTypes(SharedRealm sharedRealm, } } - public static Table createTable(SharedRealm sharedRealm, String name) { + public static Table createTable(OsSharedRealm sharedRealm, String name) { return createTable(sharedRealm, name, null); } @@ -272,7 +272,7 @@ public interface AdditionalTableSetup { void execute(Table table); } - public static Table createTable(SharedRealm sharedRealm, String name, AdditionalTableSetup additionalSetup) { + public static Table createTable(OsSharedRealm sharedRealm, String name, AdditionalTableSetup additionalSetup) { boolean wasInTransaction = sharedRealm.isInTransaction(); if (!wasInTransaction) { sharedRealm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java index a0c0d8cac8..2363200822 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java @@ -40,14 +40,14 @@ public class JNIColumnInfoTest { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; private Table table; @Before public void setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); RealmConfiguration config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index 6fa6a32ea9..6e662d46a1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -47,7 +47,7 @@ public class JNIQueryTest { @SuppressWarnings("FieldCanBeLocal") private RealmConfiguration config; - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; private Table table; private final long[] oneNullTable = new long[]{NativeObject.NULLPTR}; @@ -56,7 +56,7 @@ public class JNIQueryTest { public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); } @After diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java index 371f8f2176..8b62d0765a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java @@ -48,13 +48,13 @@ public class JNIRowTest { @SuppressWarnings("FieldCanBeLocal") private RealmConfiguration config; - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; @Before public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); sharedRealm.beginTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java index c98458c169..cd1794b669 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java @@ -48,7 +48,7 @@ public class JNITableInsertTest { @SuppressWarnings("FieldCanBeLocal") private RealmConfiguration config; - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; private List value = new ArrayList<>(); @@ -56,7 +56,7 @@ public class JNITableInsertTest { public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); } @After diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index dc61fbf252..6e92ce4b72 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -32,7 +32,6 @@ import java.util.Locale; import java.util.concurrent.atomic.AtomicLong; -import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmFieldType; import io.realm.TestHelper; @@ -53,12 +52,12 @@ public class JNITableTest { @SuppressWarnings("FieldCanBeLocal") private RealmConfiguration config; - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; @Before public void setUp() { config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); } @After @@ -649,7 +648,7 @@ public void execute(Table table) { @Test public void defaultValue_setAndGet() { - final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); + final OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configFactory.createConfiguration()); //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); @@ -770,7 +769,7 @@ public void defaultValue_setAndGet() { @Test public void defaultValue_setMultipleTimes() { - final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); + final OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configFactory.createConfiguration()); //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); @@ -900,7 +899,7 @@ public void defaultValue_setMultipleTimes() { @Test public void defaultValue_overwrittenByNonDefault() { - final SharedRealm sharedRealm = SharedRealm.getInstance(configFactory.createConfiguration()); + final OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configFactory.createConfiguration()); //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java index b5ff28356a..030a8f9667 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java @@ -45,7 +45,7 @@ public class OsListTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; private UncheckedRow row; private OsObjectSchemaInfo testObjectSchemaInfo; @@ -78,7 +78,7 @@ public void setUp() { OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config) .autoUpdateNotification(true) .schemaInfo(schemaInfo); - sharedRealm = SharedRealm.getInstance(configBuilder); + sharedRealm = OsSharedRealm.getInstance(configBuilder); sharedRealm.beginTransaction(); Table table = sharedRealm.getTable(Table.getTableNameForClass("TestModel")); row = table.getUncheckedRow(OsObject.createRow(table)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java index cb9e620b1b..290a2133e4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java @@ -44,8 +44,8 @@ public class OsObjectStoreTests { public void callWithLock() { RealmConfiguration config = configFactory.createConfiguration(); - // Return false if there are opened SharedRealm instance - SharedRealm sharedRealm = SharedRealm.getInstance(config); + // Return false if there are opened OsSharedRealm instance + OsSharedRealm sharedRealm = OsSharedRealm.getInstance(config); assertFalse(OsObjectStore.callWithLock(config, new Runnable() { @Override public void run() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java index c81e4c1aed..48590baf9e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java @@ -57,7 +57,7 @@ public class OsResultsTests { private final long[] oneNullTable = new long[] {NativeObject.NULLPTR}; - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; private Table table; @Before @@ -71,31 +71,31 @@ public void tearDown() { sharedRealm.close(); } - private SharedRealm getSharedRealm() { + private OsSharedRealm getSharedRealm() { RealmConfiguration config = configFactory.createConfiguration(); return getSharedRealm(config); } - private SharedRealm getSharedRealmForLooper() { + private OsSharedRealm getSharedRealmForLooper() { RealmConfiguration config = looperThread.createConfiguration(); return getSharedRealm(config); } - private SharedRealm getSharedRealm(RealmConfiguration config) { + private OsSharedRealm getSharedRealm(RealmConfiguration config) { OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config) .autoUpdateNotification(true); - SharedRealm sharedRealm = SharedRealm.getInstance(configBuilder); + OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configBuilder); sharedRealm.beginTransaction(); OsObjectStore.setSchemaVersion(sharedRealm, OsObjectStore.SCHEMA_NOT_VERSIONED); sharedRealm.commitTransaction(); return sharedRealm; } - private Table getTable(SharedRealm sharedRealm) { + private Table getTable(OsSharedRealm sharedRealm) { return sharedRealm.getTable(Table.getTableNameForClass("test_table")); } - private void populateData(SharedRealm sharedRealm) { + private void populateData(OsSharedRealm sharedRealm) { sharedRealm.beginTransaction(); table = sharedRealm.createTable(Table.getTableNameForClass("test_table")); // Specify the column types and names @@ -127,13 +127,13 @@ private void populateData(SharedRealm sharedRealm) { sharedRealm.commitTransaction(); } - private void addRowAsync(final SharedRealm sharedRealm) { + private void addRowAsync(final OsSharedRealm sharedRealm) { final CountDownLatch latch = new CountDownLatch(1); final RealmConfiguration configuration = sharedRealm.getConfiguration(); new Thread(new Runnable() { @Override public void run() { - SharedRealm sharedRealm = getSharedRealm(configuration); + OsSharedRealm sharedRealm = getSharedRealm(configuration); addRow(sharedRealm); sharedRealm.close(); latch.countDown(); @@ -142,7 +142,7 @@ public void run() { TestHelper.awaitOrFail(latch); } - private void addRow(SharedRealm sharedRealm) { + private void addRow(OsSharedRealm sharedRealm) { sharedRealm.beginTransaction(); Table table = getTable(sharedRealm); OsObject.createRow(table); @@ -262,7 +262,7 @@ public void distinct() { @Test @RunTestInLooperThread public void addListener_shouldBeCalledToReturnTheQueryResults() { - final SharedRealm sharedRealm = getSharedRealmForLooper(); + final OsSharedRealm sharedRealm = getSharedRealmForLooper(); populateData(sharedRealm); Table table = getTable(sharedRealm); @@ -284,7 +284,7 @@ public void onChange(OsResults osResults1) { @Test public void addListener_shouldBeCalledWhenRefreshToReturnTheQueryResults() { final AtomicBoolean onChangeCalled = new AtomicBoolean(false); - final SharedRealm sharedRealm = getSharedRealm(); + final OsSharedRealm sharedRealm = getSharedRealm(); Table table = getTable(sharedRealm); final OsResults osResults = new OsResults(sharedRealm, table.where()); @@ -352,7 +352,7 @@ public void onChange(OsResults element) { @Test @RunTestInLooperThread public void addListener_queryNotReturned() { - final SharedRealm sharedRealm = getSharedRealmForLooper(); + final OsSharedRealm sharedRealm = getSharedRealmForLooper(); populateData(sharedRealm); Table table = getTable(sharedRealm); @@ -374,7 +374,7 @@ public void onChange(OsResults osResults1) { @Test @RunTestInLooperThread public void addListener_queryReturned() { - final SharedRealm sharedRealm = getSharedRealmForLooper(); + final OsSharedRealm sharedRealm = getSharedRealmForLooper(); populateData(sharedRealm); Table table = getTable(sharedRealm); @@ -399,7 +399,7 @@ public void onChange(OsResults osResults1) { @Test @RunTestInLooperThread public void addListener_triggeredByLocalCommit() { - final SharedRealm sharedRealm = getSharedRealmForLooper(); + final OsSharedRealm sharedRealm = getSharedRealmForLooper(); populateData(sharedRealm); Table table = getTable(sharedRealm); final AtomicInteger listenerCounter = new AtomicInteger(0); @@ -438,7 +438,7 @@ protected Integer convertRowToObject(UncheckedRow row) { return null; } - boolean isDetached(SharedRealm sharedRealm) { + boolean isDetached(OsSharedRealm sharedRealm) { for (WeakReference iteratorRef : sharedRealm.iterators) { OsResults.Iterator iterator = iteratorRef.get(); if (iterator == this) { @@ -481,7 +481,7 @@ public void collectionIterator_invalid_nonLooperThread_byRefresh() { @Test @RunTestInLooperThread public void collectionIterator_invalid_looperThread_byRemoteTransaction() { - final SharedRealm sharedRealm = getSharedRealmForLooper(); + final OsSharedRealm sharedRealm = getSharedRealmForLooper(); populateData(sharedRealm); Table table = getTable(sharedRealm); final OsResults osResults = new OsResults(sharedRealm, table.where()); @@ -536,7 +536,7 @@ public void onChange(OsResults element) { @Test @RunTestInLooperThread public void load() { - final SharedRealm sharedRealm = getSharedRealmForLooper(); + final OsSharedRealm sharedRealm = getSharedRealmForLooper(); looperThread.closeAfterTest(sharedRealm); populateData(sharedRealm); final OsResults osResults = new OsResults(sharedRealm, table.where()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java similarity index 91% rename from realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java rename to realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java index 98ff45c0df..8aee472032 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java @@ -34,19 +34,19 @@ import static junit.framework.Assert.assertTrue; @RunWith(AndroidJUnit4.class) -public class SharedRealmTests { +public class OsSharedRealmTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule public final ExpectedException thrown = ExpectedException.none(); private RealmConfiguration config; - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; @Before public void setUp() { config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); } @After @@ -58,10 +58,10 @@ public void tearDown() { @Test public void getVersionID() { - SharedRealm.VersionID versionID1 = sharedRealm.getVersionID(); + OsSharedRealm.VersionID versionID1 = sharedRealm.getVersionID(); sharedRealm.beginTransaction(); sharedRealm.commitTransaction(); - SharedRealm.VersionID versionID2 = sharedRealm.getVersionID(); + OsSharedRealm.VersionID versionID2 = sharedRealm.getVersionID(); assertFalse(versionID1.equals(versionID2)); } @@ -138,7 +138,7 @@ public void renameTable_tableNotExist() { private void changeSchemaByAnotherRealm() { - SharedRealm sharedRealm = SharedRealm.getInstance(config); + OsSharedRealm sharedRealm = OsSharedRealm.getInstance(config); sharedRealm.beginTransaction(); sharedRealm.createTable("NewTable"); sharedRealm.commitTransaction(); @@ -151,7 +151,7 @@ public void registerSchemaChangedCallback_beginTransaction() { assertFalse(sharedRealm.hasTable("NewTable")); - sharedRealm.registerSchemaChangedCallback(new SharedRealm.SchemaChangedCallback() { + sharedRealm.registerSchemaChangedCallback(new OsSharedRealm.SchemaChangedCallback() { @Override public void onSchemaChanged() { assertTrue(sharedRealm.hasTable("NewTable")); @@ -169,7 +169,7 @@ public void registerSchemaChangedCallback_refresh() { assertFalse(sharedRealm.hasTable("NewTable")); - sharedRealm.registerSchemaChangedCallback(new SharedRealm.SchemaChangedCallback() { + sharedRealm.registerSchemaChangedCallback(new OsSharedRealm.SchemaChangedCallback() { @Override public void onSchemaChanged() { assertTrue(sharedRealm.hasTable("NewTable")); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index 84b0cfc9b5..04c431318f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -21,7 +21,6 @@ import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,13 +32,10 @@ import io.realm.DynamicRealm; import io.realm.DynamicRealmObject; import io.realm.FieldAttribute; -import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmFieldType; import io.realm.RealmObjectSchema; import io.realm.RealmSchema; -import io.realm.exceptions.RealmException; -import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.rule.TestRealmConfigurationFactory; import static junit.framework.Assert.assertFalse; @@ -55,7 +51,7 @@ public class PrimaryKeyTests { private android.content.Context context; private RealmConfiguration config; - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; @Before public void setUp() throws Exception { @@ -71,7 +67,7 @@ public void tearDown() { } private Table getTableWithStringPrimaryKey() { - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); sharedRealm.beginTransaction(); OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table Table t = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); @@ -82,7 +78,7 @@ private Table getTableWithStringPrimaryKey() { } private Table getTableWithIntegerPrimaryKey() { - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); sharedRealm.beginTransaction(); OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table Table t = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); @@ -178,7 +174,7 @@ public void addEmptyRowWithPrimaryKeyLong() { @Test public void migratePrimaryKeyTableIfNeeded_first() throws IOException { configFactory.copyRealmFromAssets(context, "080_annotationtypes.realm", "default.realm"); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); Table.migratePrimaryKeyTableIfNeeded(sharedRealm); Table t = sharedRealm.getTable("class_AnnotationTypes"); assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); @@ -188,7 +184,7 @@ public void migratePrimaryKeyTableIfNeeded_first() throws IOException { @Test public void migratePrimaryKeyTableIfNeeded_second() throws IOException { configFactory.copyRealmFromAssets(context, "0841_annotationtypes.realm", "default.realm"); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); Table.migratePrimaryKeyTableIfNeeded(sharedRealm); Table t = sharedRealm.getTable("class_AnnotationTypes"); assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); @@ -208,7 +204,7 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableMigratedWithRightName( "Post", "Tags", "Threads", "User"); configFactory.copyRealmFromAssets(context, "0841_pk_migration.realm", "default.realm"); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); Table.migratePrimaryKeyTableIfNeeded(sharedRealm); Table table = sharedRealm.getTable("pk"); @@ -223,7 +219,7 @@ public void migratePrimaryKeyTableIfNeeded_primaryKeyTableMigratedWithRightName( // See https://github.com/realm/realm-java/pull/3488 @Test public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); sharedRealm.beginTransaction(); OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table Table table = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 87ba794501..86f42ff0fc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -71,10 +71,10 @@ public void setUp() throws Exception { public void tearDown() { } - private SharedRealm getSharedRealm(RealmConfiguration config) { + private OsSharedRealm getSharedRealm(RealmConfiguration config) { OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config) .autoUpdateNotification(true); - return SharedRealm.getInstance(configBuilder); + return OsSharedRealm.getInstance(configBuilder); } @Test @@ -94,10 +94,10 @@ public void run() { @RunTestInLooperThread public void addChangeListener_byLocalChanges() { final AtomicBoolean commitReturns = new AtomicBoolean(false); - SharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); - sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { + OsSharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); + sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override - public void onChange(SharedRealm sharedRealm) { + public void onChange(OsSharedRealm sharedRealm) { // Transaction has been committed in core, but commitTransaction hasn't returned in java. assertFalse(commitReturns.get()); looperThread.testComplete(); @@ -111,7 +111,7 @@ public void onChange(SharedRealm sharedRealm) { private void makeRemoteChanges(final RealmConfiguration config) { // We don't use cache from RealmCoordinator - SharedRealm sharedRealm = getSharedRealm(config); + OsSharedRealm sharedRealm = getSharedRealm(config); sharedRealm.beginTransaction(); sharedRealm.commitTransaction(); sharedRealm.close(); @@ -128,11 +128,11 @@ public void addChangeListener_byRemoteChanges() { looperThread.getRealm().close(); - SharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); + OsSharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); looperThread.keepStrongReference(sharedRealm); - sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { + sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override - public void onChange(SharedRealm sharedRealm) { + public void onChange(OsSharedRealm sharedRealm) { int commits = commitCounter.get(); int listenerCount = listenerCounter.addAndGet(1); assertEquals(commits, listenerCount); @@ -152,7 +152,7 @@ public void onChange(SharedRealm sharedRealm) { @Test @RunTestInLooperThread public void removeChangeListeners() { - SharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); + OsSharedRealm sharedRealm = getSharedRealm(looperThread.getConfiguration()); Integer dummyObserver = 1; looperThread.keepStrongReference(dummyObserver); looperThread.keepStrongReference(sharedRealm); @@ -162,9 +162,9 @@ public void onChange(Integer dummy) { fail(); } }); - sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { + sharedRealm.realmNotifier.addChangeListener(sharedRealm, new RealmChangeListener() { @Override - public void onChange(SharedRealm sharedRealm) { + public void onChange(OsSharedRealm sharedRealm) { sharedRealm.close(); looperThread.testComplete(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java index a8c4bd22f5..6a762423c7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java @@ -47,13 +47,13 @@ public class SortDescriptorTests { @Rule public final ExpectedException thrown = ExpectedException.none(); - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; private Table table; @Before public void setUp() { RealmConfiguration config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); sharedRealm.beginTransaction(); table = sharedRealm.createTable("test_table"); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java index 3efa42c683..2d8ea55467 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java @@ -43,14 +43,14 @@ public class TableIndexAndDistinctTest { @SuppressWarnings("FieldCanBeLocal") private RealmConfiguration config; - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; private Table table; @Before public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); config = configFactory.createConfiguration(); - sharedRealm = SharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config); sharedRealm.beginTransaction(); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index b12d014bea..af07ef5986 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -38,7 +38,7 @@ import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSchemaInfo; -import io.realm.internal.SharedRealm; +import io.realm.internal.OsSharedRealm; import io.realm.exceptions.IncompatibleSyncedFileException; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.util.SyncTestUtils; @@ -157,7 +157,7 @@ public void breakingSchemaChange_throws() { list.add(expectedObjectSchema); OsSchemaInfo schemaInfo = new OsSchemaInfo(list); OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config).schemaInfo(schemaInfo); - SharedRealm.getInstance(configBuilder).close(); + OsSharedRealm.getInstance(configBuilder).close(); thrown.expectMessage( CoreMatchers.containsString("The following changes cannot be made in additive-only schema mode:")); diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index c36935cb8d..e099fe82b7 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -58,7 +58,7 @@ set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/classes/${REAL set(classes_LIST io.realm.internal.Table io.realm.internal.CheckedRow io.realm.internal.Util io.realm.internal.UncheckedRow - io.realm.internal.TableQuery io.realm.internal.SharedRealm io.realm.internal.TestUtil + io.realm.internal.TableQuery io.realm.internal.OsSharedRealm io.realm.internal.TestUtil io.realm.log.LogLevel io.realm.log.RealmLog io.realm.internal.Property io.realm.internal.OsSchemaInfo io.realm.internal.OsObjectSchemaInfo io.realm.internal.OsResults io.realm.internal.NativeObjectReference io.realm.internal.OsCollectionChangeSet diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index b07e992dc8..b54172f289 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -61,7 +61,7 @@ static void finalize_realm_config(jlong ptr) static JavaClass& get_shared_realm_class(JNIEnv* env) { - static JavaClass shared_realm_class(env, "io/realm/internal/SharedRealm"); + static JavaClass shared_realm_class(env, "io/realm/internal/OsSharedRealm"); return shared_realm_class; } @@ -133,7 +133,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSchemaConfi if (j_migration_callback) { static JavaMethod run_migration_callback_method( env, get_shared_realm_class(env), "runMigrationCallback", - "(JLio/realm/internal/OsRealmConfig;Lio/realm/internal/SharedRealm$MigrationCallback;J)V", true); + "(JLio/realm/internal/OsRealmConfig;Lio/realm/internal/OsSharedRealm$MigrationCallback;J)V", true); // weak ref to avoid leaks caused by circular refs. JavaGlobalWeakRef j_config_weak(env, j_config); JavaGlobalWeakRef j_migration_cb_weak(env, j_migration_callback); @@ -142,7 +142,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSchemaConfi config.migration_function = [j_migration_cb_weak, j_config_weak](SharedRealm old_realm, SharedRealm realm, Schema&) { JNIEnv* env = JniUtils::get_env(false); - // Java needs a new pointer for the SharedRealm life control. + // Java needs a new pointer for the OsSharedRealm life control. SharedRealm* new_shared_realm_ptr = new SharedRealm(realm); JavaGlobalRef config_global = j_config_weak.global_ref(env); if (!config_global) { @@ -154,7 +154,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSchemaConfi reinterpret_cast(new_shared_realm_ptr), config_global.get(), obj, old_realm->schema_version()); }); - // Close the SharedRealm. Otherwise it will only be closed when the Java OsSharedRealm gets GCed. And + // Close the OsSharedRealm. Otherwise it will only be closed when the Java OsSharedRealm gets GCed. And // that will be too late. TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED( env, [&new_shared_realm_ptr]() { (*new_shared_realm_ptr)->close(); }); @@ -212,13 +212,13 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetInitializat if (j_init_callback) { static JavaMethod run_initialization_callback_method( env, get_shared_realm_class(env), "runInitializationCallback", - "(JLio/realm/internal/OsRealmConfig;Lio/realm/internal/SharedRealm$InitializationCallback;)V", true); + "(JLio/realm/internal/OsRealmConfig;Lio/realm/internal/OsSharedRealm$InitializationCallback;)V", true); // weak ref to avoid leaks caused by circular refs. JavaGlobalWeakRef j_init_cb_weak(env, j_init_callback); JavaGlobalWeakRef j_config_weak(env, j_config); config.initialization_function = [j_init_cb_weak, j_config_weak](SharedRealm realm) { JNIEnv* env = JniUtils::get_env(false); - // Java needs a new pointer for the SharedRealm life control. + // Java needs a new pointer for the OsSharedRealm life control. SharedRealm* new_shared_realm_ptr = new SharedRealm(realm); JavaGlobalRef config_global_ref = j_config_weak.global_ref(env); if (!config_global_ref) { @@ -229,7 +229,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetInitializat reinterpret_cast(new_shared_realm_ptr), config_global_ref.get(), obj); }); - // Close the SharedRealm. Otherwise it will only be closed when the Java OsSharedRealm gets GCed. And + // Close the OsSharedRealm. Otherwise it will only be closed when the Java OsSharedRealm gets GCed. And // that will be too late. TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED( env, [&new_shared_realm_ptr]() { (*new_shared_realm_ptr)->close(); }); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp similarity index 100% rename from realm/realm-library/src/main/cpp/io_realm_internal_Collection.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp similarity index 86% rename from realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 9649f18563..1baf6190b7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_internal_SharedRealm.h" +#include "io_realm_internal_OsSharedRealm.h" #if REALM_ENABLE_SYNC #include "object-store/src/sync/sync_manager.hpp" #include "object-store/src/sync/sync_config.hpp" @@ -49,7 +49,7 @@ static const char* c_table_name_exists_exception_msg = "Class already exists: '% typedef ObservableCollectionWrapper ResultsWrapper; #endif -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv* env, jclass, +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeInit(JNIEnv* env, jclass, jstring temporary_directory_path) { TR_ENTER() @@ -61,7 +61,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeInit(JNIEnv* env CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSharedRealm(JNIEnv* env, jclass, jlong config_ptr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSharedRealm(JNIEnv* env, jclass, jlong config_ptr, jobject realm_notifier) { TR_ENTER_PTR(config_ptr) @@ -102,7 +102,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSharedRealm( return reinterpret_cast(nullptr); } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -114,7 +114,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCloseSharedRealm } } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeBeginTransaction(JNIEnv* env, jclass, +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeBeginTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -126,7 +126,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeBeginTransaction CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCommitTransaction(JNIEnv* env, jclass, +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCommitTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -144,7 +144,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCommitTransactio CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCancelTransaction(JNIEnv* env, jclass, +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCancelTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -157,7 +157,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeCancelTransactio } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsInTransaction(JNIEnv*, jclass, +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsInTransaction(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -166,7 +166,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsInTransact return static_cast(shared_realm->is_in_transaction()); } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIEnv* env, jclass, +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsEmpty(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -179,7 +179,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsEmpty(JNIE return JNI_FALSE; } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRefresh(JNIEnv* env, jclass, jlong shared_realm_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRefresh(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -190,7 +190,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRefresh(JNIEnv* CATCH_STD() } -JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersionID(JNIEnv* env, jclass, +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetVersionID(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -218,7 +218,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_SharedRealm_nativeGetVersion return NULL; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -227,7 +227,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsClosed(JNI } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv* env, jclass, jlong shared_realm_ptr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTable(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring table_name) { TR_ENTER_PTR(shared_realm_ptr) @@ -251,7 +251,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetTable(JNIEnv return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateTable(JNIEnv* env, jclass, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTable(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_table_name) { @@ -287,7 +287,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateTable(JNI return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateTableWithPrimaryKeyField( +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWithPrimaryKeyField( JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_table_name, jstring j_field_name, jboolean is_string_type, jboolean is_nullable) { @@ -329,7 +329,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeCreateTableWith return reinterpret_cast(nullptr); } -JNIEXPORT jstring JNICALL Java_io_realm_internal_SharedRealm_nativeGetTableName(JNIEnv* env, jclass, +JNIEXPORT jstring JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTableName(JNIEnv* env, jclass, jlong shared_realm_ptr, jint index) { @@ -343,7 +343,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_SharedRealm_nativeGetTableName( return NULL; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeHasTable(JNIEnv* env, jclass, +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeHasTable(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring table_name) { @@ -358,7 +358,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeHasTable(JNI return JNI_FALSE; } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIEnv* env, jclass, +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRenameTable(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring old_table_name, jstring new_table_name) @@ -380,7 +380,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRenameTable(JNIE CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv* env, jclass, jlong shared_realm_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeSize(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -393,7 +393,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeSize(JNIEnv* en return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv* env, jclass, jlong shared_realm_ptr, +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeWriteCopy(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring path, jbyteArray key) { TR_ENTER_PTR(shared_realm_ptr); @@ -407,7 +407,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeWriteCopy(JNIEnv CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeWaitForChange(JNIEnv* env, jclass, +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeWaitForChange(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr); @@ -422,7 +422,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeWaitForChang return JNI_FALSE; } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeStopWaitForChange(JNIEnv* env, jclass, +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeStopWaitForChange(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr); @@ -435,7 +435,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeStopWaitForChang CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeCompact(JNIEnv* env, jclass, +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeCompact(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr); @@ -455,13 +455,13 @@ static void finalize_shared_realm(jlong ptr) delete reinterpret_cast(ptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetFinalizerPtr(JNIEnv*, jclass) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetFinalizerPtr(JNIEnv*, jclass) { TR_ENTER() return reinterpret_cast(&finalize_shared_realm); } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetAutoRefresh(JNIEnv* env, jclass, +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeSetAutoRefresh(JNIEnv* env, jclass, jlong shared_realm_ptr, jboolean enabled) { @@ -473,7 +473,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeSetAutoRefresh(J CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsAutoRefresh(JNIEnv* env, jclass, +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsAutoRefresh(JNIEnv* env, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -485,7 +485,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_SharedRealm_nativeIsAutoRefres return JNI_FALSE; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSchemaInfo(JNIEnv*, jclass, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSchemaInfo(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) @@ -495,7 +495,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_SharedRealm_nativeGetSchemaInfo(J return reinterpret_cast(&shared_realm->schema()); } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRegisterSchemaChangedCallback( +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterSchemaChangedCallback( JNIEnv* env, jclass, jlong shared_realm_ptr, jobject j_schema_changed_callback) { TR_ENTER_PTR(shared_realm_ptr) @@ -510,7 +510,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRegisterSchemaCh } } -JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRegisterPartialSyncQuery( +JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterPartialSyncQuery( REALM_UNUSED JNIEnv* env, REALM_UNUSED jobject j_shared_realm_instance, REALM_UNUSED jlong shared_realm_ptr, REALM_UNUSED jstring j_class_name, REALM_UNUSED jstring j_query, REALM_UNUSED jobject j_callback) { @@ -528,9 +528,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_SharedRealm_nativeRegisterPartialS JavaGlobalRef j_callback_ref(env, j_callback); JavaGlobalWeakRef j_shared_realm_instance_ref(env, j_shared_realm_instance); - static JavaClass shared_realm_class(env, "io/realm/internal/SharedRealm"); + static JavaClass shared_realm_class(env, "io/realm/internal/OsSharedRealm"); static JavaMethod partial_sync_cb(env, shared_realm_class, "runPartialSyncRegistrationCallback", - "(Ljava/lang/String;JLio/realm/internal/SharedRealm$PartialSyncCallback;)V"); + "(Ljava/lang/String;JLio/realm/internal/OsSharedRealm$PartialSyncCallback;)V"); auto cb = [j_callback_ref, j_shared_realm_instance_ref](Results results, std::exception_ptr err) { JNIEnv* env = JniUtils::get_env(true); diff --git a/realm/realm-library/src/main/cpp/java_class_global_def.hpp b/realm/realm-library/src/main/cpp/java_class_global_def.hpp index 268082de8e..f1baf81348 100644 --- a/realm/realm-library/src/main/cpp/java_class_global_def.hpp +++ b/realm/realm-library/src/main/cpp/java_class_global_def.hpp @@ -50,7 +50,7 @@ class JavaClassGlobalDef { , m_java_util_date(env, "java/util/Date", false) , m_java_lang_string(env, "java/lang/String", false) , m_java_lang_boolean(env, "java/lang/Boolean", false) - , m_shared_realm_schema_change_callback(env, "io/realm/internal/SharedRealm$SchemaChangedCallback", false) + , m_shared_realm_schema_change_callback(env, "io/realm/internal/OsSharedRealm$SchemaChangedCallback", false) , m_realm_notifier(env, "io/realm/internal/RealmNotifier", false) { } @@ -154,7 +154,7 @@ class JavaClassGlobalDef { // return nullptr if binary_data is null static jbyteArray new_byte_array(JNIEnv* env, const BinaryData& binary_data); - // io.realm.internal.SharedRealm.SchemaChangedCallback + // io.realm.internal.OsSharedRealm.SchemaChangedCallback inline static const jni_util::JavaClass& shared_realm_schema_change_callback() { return instance()->m_shared_realm_schema_change_callback; diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index f21d1bfc09..296cac34ff 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -25,7 +25,7 @@ #include "util.hpp" #include "io_realm_internal_Util.h" -#include "io_realm_internal_SharedRealm.h" +#include "io_realm_internal_OsSharedRealm.h" #include "shared_realm.hpp" #include "results.hpp" #include "list.hpp" @@ -213,25 +213,25 @@ void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::Rea jbyte kind_code = -1; // To suppress compile warning. switch (kind) { case realm::RealmFileException::Kind::AccessError: - kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_ACCESS_ERROR; + kind_code = io_realm_internal_OsSharedRealm_FILE_EXCEPTION_KIND_ACCESS_ERROR; break; case realm::RealmFileException::Kind::BadHistoryError: - kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_BAD_HISTORY; + kind_code = io_realm_internal_OsSharedRealm_FILE_EXCEPTION_KIND_BAD_HISTORY; break; case realm::RealmFileException::Kind::PermissionDenied: - kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_PERMISSION_DENIED; + kind_code = io_realm_internal_OsSharedRealm_FILE_EXCEPTION_KIND_PERMISSION_DENIED; break; case realm::RealmFileException::Kind::Exists: - kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_EXISTS; + kind_code = io_realm_internal_OsSharedRealm_FILE_EXCEPTION_KIND_EXISTS; break; case realm::RealmFileException::Kind::NotFound: - kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_NOT_FOUND; + kind_code = io_realm_internal_OsSharedRealm_FILE_EXCEPTION_KIND_NOT_FOUND; break; case realm::RealmFileException::Kind::IncompatibleLockFile: - kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE; + kind_code = io_realm_internal_OsSharedRealm_FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE; break; case realm::RealmFileException::Kind::FormatUpgradeRequired: - kind_code = io_realm_internal_SharedRealm_FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED; + kind_code = io_realm_internal_OsSharedRealm_FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED; break; case realm::RealmFileException::Kind::IncompatibleSyncedRealm: #if REALM_ENABLE_SYNC diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index aa3f85e9e4..37eddd6371 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -38,9 +38,9 @@ import io.realm.internal.OsObjectStore; import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSchemaInfo; +import io.realm.internal.OsSharedRealm; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; -import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.Util; @@ -76,9 +76,9 @@ abstract class BaseRealm implements Closeable { // Which RealmCache is this Realm associated to. It is null if the Realm instance is opened without being put into a // cache. It is also null if the Realm is closed. private RealmCache realmCache; - public SharedRealm sharedRealm; + public OsSharedRealm sharedRealm; private boolean shouldCloseSharedRealm; - private SharedRealm.SchemaChangedCallback schemaChangedCallback = new SharedRealm.SchemaChangedCallback() { + private OsSharedRealm.SchemaChangedCallback schemaChangedCallback = new OsSharedRealm.SchemaChangedCallback() { @Override public void onSchemaChanged() { RealmSchema schema = getSchema(); @@ -100,17 +100,17 @@ public void onSchemaChanged() { this.configuration = configuration; this.realmCache = null; - SharedRealm.MigrationCallback migrationCallback = null; + OsSharedRealm.MigrationCallback migrationCallback = null; if (schemaInfo != null && configuration.getMigration() != null) { migrationCallback = createMigrationCallback(configuration.getMigration()); } - SharedRealm.InitializationCallback initializationCallback = null; + OsSharedRealm.InitializationCallback initializationCallback = null; final Realm.Transaction initialDataTransaction = configuration.getInitialDataTransaction(); if (initialDataTransaction != null) { - initializationCallback = new SharedRealm.InitializationCallback() { + initializationCallback = new OsSharedRealm.InitializationCallback() { @Override - public void onInit(SharedRealm sharedRealm) { + public void onInit(OsSharedRealm sharedRealm) { initialDataTransaction.execute(Realm.createInstance(sharedRealm)); } }; @@ -121,15 +121,15 @@ public void onInit(SharedRealm sharedRealm) { .migrationCallback(migrationCallback) .schemaInfo(schemaInfo) .initializationCallback(initializationCallback); - this.sharedRealm = SharedRealm.getInstance(configBuilder); + this.sharedRealm = OsSharedRealm.getInstance(configBuilder); this.shouldCloseSharedRealm = true; sharedRealm.registerSchemaChangedCallback(schemaChangedCallback); } - // Create a realm instance directly from a SharedRealm instance. This instance doesn't have the ownership of the - // given SharedRealm instance. The SharedRealm instance should not be closed when close() called. - BaseRealm(SharedRealm sharedRealm) { + // Create a realm instance directly from a OsSharedRealm instance. This instance doesn't have the ownership of the + // given OsSharedRealm instance. The OsSharedRealm instance should not be closed when close() called. + BaseRealm(OsSharedRealm sharedRealm) { this.threadId = Thread.currentThread().getId(); this.configuration = sharedRealm.getConfiguration(); this.realmCache = null; @@ -633,7 +633,7 @@ public void run() { * @return {@code true} if compaction succeeded, {@code false} otherwise. */ static boolean compactRealm(final RealmConfiguration configuration) { - SharedRealm sharedRealm = SharedRealm.getInstance(configuration); + OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configuration); Boolean result = sharedRealm.compact(); sharedRealm.close(); return result; @@ -680,7 +680,7 @@ public void onResult(int count) { RealmProxyMediator mediator = configuration.getSchemaMediator(); OsSchemaInfo schemaInfo = new OsSchemaInfo(mediator.getExpectedObjectSchemaInfoMap().values()); - SharedRealm.MigrationCallback migrationCallback = null; + OsSharedRealm.MigrationCallback migrationCallback = null; final RealmMigration migrationToBeApplied = migration != null ? migration : configuration.getMigration(); if (migrationToBeApplied != null) { migrationCallback = createMigrationCallback(migrationToBeApplied); @@ -689,10 +689,10 @@ public void onResult(int count) { .autoUpdateNotification(false) .schemaInfo(schemaInfo) .migrationCallback(migrationCallback); - SharedRealm sharedRealm = null; + OsSharedRealm sharedRealm = null; try { sharedRealm = - SharedRealm.getInstance(configBuilder); + OsSharedRealm.getInstance(configBuilder); } finally { if (sharedRealm != null) { sharedRealm.close(); @@ -707,10 +707,10 @@ public void onResult(int count) { } } - private static SharedRealm.MigrationCallback createMigrationCallback(final RealmMigration migration) { - return new SharedRealm.MigrationCallback() { + private static OsSharedRealm.MigrationCallback createMigrationCallback(final RealmMigration migration) { + return new OsSharedRealm.MigrationCallback() { @Override - public void onMigrationNeeded(SharedRealm sharedRealm, long oldVersion, long newVersion) { + public void onMigrationNeeded(OsSharedRealm sharedRealm, long oldVersion, long newVersion) { migration.migrate(DynamicRealm.createInstance(sharedRealm), oldVersion, newVersion); } }; @@ -730,7 +730,7 @@ protected void finalize() throws Throwable { super.finalize(); } - SharedRealm getSharedRealm() { + OsSharedRealm getSharedRealm() { return sharedRealm; } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index ec14a7169e..c62b5268d6 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -24,7 +24,7 @@ import io.realm.internal.CheckedRow; import io.realm.internal.OsObject; import io.realm.internal.OsObjectStore; -import io.realm.internal.SharedRealm; +import io.realm.internal.OsSharedRealm; import io.realm.internal.Table; import io.realm.log.RealmLog; @@ -77,7 +77,7 @@ public void onResult(int count) { this.schema = new MutableRealmSchema(this); } - private DynamicRealm(SharedRealm sharedRealm) { + private DynamicRealm(OsSharedRealm sharedRealm) { super(sharedRealm); this.schema = new MutableRealmSchema(this); } @@ -268,13 +268,13 @@ static DynamicRealm createInstance(RealmCache cache) { } /** - * Creates a {@link DynamicRealm} instance with a given {@link SharedRealm} instance without owning it. + * Creates a {@link DynamicRealm} instance with a given {@link OsSharedRealm} instance without owning it. * This is designed to be used in the migration block when opening a typed Realm instance. * - * @param sharedRealm the existing {@link SharedRealm} instance. + * @param sharedRealm the existing {@link OsSharedRealm} instance. * @return a {@link DynamicRealm} instance. */ - static DynamicRealm createInstance(SharedRealm sharedRealm) { + static DynamicRealm createInstance(OsSharedRealm sharedRealm) { return new DynamicRealm(sharedRealm); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 90415531b0..769e460c32 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -60,11 +60,11 @@ import io.realm.internal.OsObjectStore; import io.realm.internal.OsResults; import io.realm.internal.OsSchemaInfo; +import io.realm.internal.OsSharedRealm; import io.realm.internal.RealmCore; import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; -import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; @@ -172,7 +172,7 @@ private Realm(RealmCache cache) { } } - private Realm(SharedRealm sharedRealm) { + private Realm(OsSharedRealm sharedRealm) { super(sharedRealm); schema = new ImmutableRealmSchema(this, new ColumnIndices(configuration.getSchemaMediator(), sharedRealm.getSchemaInfo())); @@ -252,7 +252,7 @@ public static synchronized void init(Context context) { } else { BaseRealm.applicationContext = context; } - SharedRealm.initialize(new File(context.getFilesDir(), ".realm.temp")); + OsSharedRealm.initialize(new File(context.getFilesDir(), ".realm.temp")); } } @@ -425,10 +425,10 @@ static Realm createInstance(RealmCache cache) { } /** - * Creates a {@code Realm} instance directly from a {@link SharedRealm}. This {@code Realm} doesn't need to be + * Creates a {@code Realm} instance directly from a {@link OsSharedRealm}. This {@code Realm} doesn't need to be * closed. */ - static Realm createInstance(SharedRealm sharedRealm) { + static Realm createInstance(OsSharedRealm sharedRealm) { return new Realm(sharedRealm); } @@ -1480,7 +1480,7 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, sharedRealm.capabilities.checkCanDeliverNotification("Callback cannot be delivered on current thread."); } - // We need to use the same configuration to open a background SharedRealm (i.e Realm) + // We need to use the same configuration to open a background OsSharedRealm (i.e Realm) // to perform the transaction final RealmConfiguration realmConfiguration = getConfiguration(); // We need to deliver the callback even if the Realm is closed. So acquire a reference to the notifier here. @@ -1493,7 +1493,7 @@ public void run() { return; } - SharedRealm.VersionID versionID = null; + OsSharedRealm.VersionID versionID = null; Throwable exception = null; final Realm bgRealm = Realm.getInstance(realmConfiguration); @@ -1522,7 +1522,7 @@ public void run() { } final Throwable backgroundException = exception; - final SharedRealm.VersionID backgroundVersionID = versionID; + final OsSharedRealm.VersionID backgroundVersionID = versionID; // Cannot be interrupted anymore. if (canDeliverNotification) { if (backgroundVersionID != null && onSuccess != null) { @@ -1719,7 +1719,7 @@ public void subscribeToObjects(final Class clazz, Stri sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); String className = configuration.getSchemaMediator().getSimpleClassName(clazz); - SharedRealm.PartialSyncCallback internalCallback = new SharedRealm.PartialSyncCallback(className) { + OsSharedRealm.PartialSyncCallback internalCallback = new OsSharedRealm.PartialSyncCallback(className) { @Override public void onSuccess(OsResults osResults) { RealmResults results = new RealmResults<>(Realm.this, osResults, clazz); diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index a7544f5da6..1399b3e22f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -36,8 +36,8 @@ import io.realm.internal.Capabilities; import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsObjectStore; +import io.realm.internal.OsSharedRealm; import io.realm.internal.RealmNotifier; -import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; @@ -291,14 +291,14 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo copyAssetFileIfNeeded(configuration); boolean fileExists = configuration.realmExists(); - SharedRealm sharedRealm = null; + OsSharedRealm sharedRealm = null; try { if (configuration.isSyncConfiguration()) { // If waitForInitialRemoteData() was enabled, we need to make sure that all data is downloaded // before proceeding. We need to open the Realm instance first to start any potential underlying // SyncSession so this will work. TODO: This needs to be decoupled. if (!fileExists) { - sharedRealm = SharedRealm.getInstance(configuration); + sharedRealm = OsSharedRealm.getInstance(configuration); try { ObjectServerFacade.getSyncFacadeIfPossible().downloadRemoteChanges(configuration); } catch (Throwable t) { @@ -316,7 +316,7 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo } else { if (fileExists) { // Primary key problem only exists before we release sync. - sharedRealm = SharedRealm.getInstance(configuration); + sharedRealm = OsSharedRealm.getInstance(configuration); Table.migratePrimaryKeyTableIfNeeded(sharedRealm); } } diff --git a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java index 716c0266c6..b6db6ed87a 100644 --- a/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java +++ b/realm/realm-library/src/main/java/io/realm/exceptions/RealmFileException.java @@ -18,7 +18,7 @@ import java.util.Locale; import io.realm.internal.Keep; -import io.realm.internal.SharedRealm; +import io.realm.internal.OsSharedRealm; /** @@ -70,21 +70,21 @@ public enum Kind { // Created from byte values by JNI. static Kind getKind(byte value) { switch (value) { - case SharedRealm.FILE_EXCEPTION_KIND_ACCESS_ERROR: + case OsSharedRealm.FILE_EXCEPTION_KIND_ACCESS_ERROR: return ACCESS_ERROR; - case SharedRealm.FILE_EXCEPTION_KIND_PERMISSION_DENIED: + case OsSharedRealm.FILE_EXCEPTION_KIND_PERMISSION_DENIED: return PERMISSION_DENIED; - case SharedRealm.FILE_EXCEPTION_KIND_EXISTS: + case OsSharedRealm.FILE_EXCEPTION_KIND_EXISTS: return EXISTS; - case SharedRealm.FILE_EXCEPTION_KIND_NOT_FOUND: + case OsSharedRealm.FILE_EXCEPTION_KIND_NOT_FOUND: return NOT_FOUND; - case SharedRealm.FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE: + case OsSharedRealm.FILE_EXCEPTION_KIND_INCOMPATIBLE_LOCK_FILE: return INCOMPATIBLE_LOCK_FILE; - case SharedRealm.FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED: + case OsSharedRealm.FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED: return FORMAT_UPGRADE_REQUIRED; - case SharedRealm.FILE_EXCEPTION_KIND_BAD_HISTORY: + case OsSharedRealm.FILE_EXCEPTION_KIND_BAD_HISTORY: return BAD_HISTORY; - case SharedRealm.FILE_EXCEPTION_INCOMPATIBLE_SYNC_FILE: + case OsSharedRealm.FILE_EXCEPTION_INCOMPATIBLE_SYNC_FILE: return INCOMPATIBLE_SYNC_FILE; default: throw new RuntimeException("Unknown value for RealmFileException kind."); diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java index 98f47c2485..07bceca628 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java @@ -55,7 +55,7 @@ public final class ColumnIndices { new HashMap(); private final RealmProxyMediator mediator; - // Due to the nature of Object Store's Realm::m_schema, SharedRealm's OsObjectSchemaInfo object is fixed after set. + // Due to the nature of Object Store's Realm::m_schema, OsSharedRealm's OsObjectSchemaInfo object is fixed after set. private final OsSchemaInfo osSchemaInfo; diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java index c56343a1fa..1825e855b9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java @@ -42,7 +42,7 @@ * and the column index field is the index of the backlink source field, in the source table * *

            - * The instance of this class is dedicated to a single {@link SharedRealm} instance. Thus this is not supposed to be + * The instance of this class is dedicated to a single {@link OsSharedRealm} instance. Thus this is not supposed to be * used across threads. * An instance can be mutated, after construction, in four ways: *

              diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsList.java b/realm/realm-library/src/main/java/io/realm/internal/OsList.java index bedf3e928a..f532d6df69 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsList.java @@ -20,7 +20,7 @@ public class OsList implements NativeObject, ObservableCollection { new ObserverPairList(); public OsList(UncheckedRow row, long columnIndex) { - SharedRealm sharedRealm = row.getTable().getSharedRealm(); + OsSharedRealm sharedRealm = row.getTable().getSharedRealm(); long[] ptrs = nativeCreate(sharedRealm.getNativePtr(), row.getNativePtr(), columnIndex); this.nativePtr = ptrs[0]; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index 8422bc5bfb..f332befe50 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -98,7 +98,7 @@ public void onCalled(ObjectObserverPair pair, Object observer) { private ObserverPairList observerPairs = new ObserverPairList(); - public OsObject(SharedRealm sharedRealm, UncheckedRow row) { + public OsObject(OsSharedRealm sharedRealm, UncheckedRow row) { nativePtr = nativeCreate(sharedRealm.getNativePtr(), row.getNativePtr()); sharedRealm.context.addReference(this); } @@ -152,11 +152,11 @@ public void setObserverPairs(ObserverPairList pairs) { /** * Create an object in the given table which doesn't have a primary key column defined. * - * @param table the table where the object is created. This table must be atached to {@link SharedRealm}. + * @param table the table where the object is created. This table must be atached to {@link OsSharedRealm}. * @return a newly created {@code UncheckedRow}. */ public static UncheckedRow create(Table table) { - final SharedRealm sharedRealm = table.getSharedRealm(); + final OsSharedRealm sharedRealm = table.getSharedRealm(); return new UncheckedRow(sharedRealm.context, table, nativeCreateNewObject(sharedRealm.getNativePtr(), table.getNativePtr())); } @@ -169,7 +169,7 @@ public static UncheckedRow create(Table table) { * @return a newly created row's index. */ public static long createRow(Table table) { - final SharedRealm sharedRealm = table.getSharedRealm(); + final OsSharedRealm sharedRealm = table.getSharedRealm(); return nativeCreateRow(sharedRealm.getNativePtr(), table.getNativePtr()); } @@ -186,13 +186,13 @@ private static long getAndVerifyPrimaryKeyColumnIndex(Table table) { * Create an object in the given table which has a primary key column defined, and set the primary key with given * value. * - * @param table the table where the object is created. This table must be atached to {@link SharedRealm}. + * @param table the table where the object is created. This table must be atached to {@link OsSharedRealm}. * @return a newly created {@code UncheckedRow}. */ public static UncheckedRow createWithPrimaryKey(Table table, @Nullable Object primaryKeyValue) { long primaryKeyColumnIndex = getAndVerifyPrimaryKeyColumnIndex(table); RealmFieldType type = table.getColumnType(primaryKeyColumnIndex); - final SharedRealm sharedRealm = table.getSharedRealm(); + final OsSharedRealm sharedRealm = table.getSharedRealm(); if (type == RealmFieldType.STRING) { if (primaryKeyValue != null && !(primaryKeyValue instanceof String)) { @@ -225,7 +225,7 @@ public static UncheckedRow createWithPrimaryKey(Table table, @Nullable Object pr // FIXME: Proxy could just pass the pk index here which is much faster. public static long createRowWithPrimaryKey(Table table, long primaryKeyColumnIndex, Object primaryKeyValue) { RealmFieldType type = table.getColumnType(primaryKeyColumnIndex); - final SharedRealm sharedRealm = table.getSharedRealm(); + final OsSharedRealm sharedRealm = table.getSharedRealm(); if (type == RealmFieldType.STRING) { if (primaryKeyValue != null && !(primaryKeyValue instanceof String)) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java index aa2b9f8192..5623f8d086 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java @@ -38,28 +38,28 @@ public class OsObjectStore { * @throws IllegalStateException if the given field is not a valid type for primary key. * @throws IllegalStateException if there are duplicated values for the given field. */ - public static void setPrimaryKeyForObject(SharedRealm sharedRealm, String className, + public static void setPrimaryKeyForObject(OsSharedRealm sharedRealm, String className, @Nullable String primaryKeyFieldName) { nativeSetPrimaryKeyForObject(sharedRealm.getNativePtr(), className, primaryKeyFieldName); } - public static @Nullable String getPrimaryKeyForObject(SharedRealm sharedRealm, String className) { + public static @Nullable String getPrimaryKeyForObject(OsSharedRealm sharedRealm, String className) { return nativeGetPrimaryKeyForObject(sharedRealm.getNativePtr(), className); } /** - * Sets the schema version to the given {@link SharedRealm}. This method will create meta tables if they don't exist. + * Sets the schema version to the given {@link OsSharedRealm}. This method will create meta tables if they don't exist. * @throws IllegalStateException if it is not in a transaction. */ - public static void setSchemaVersion(SharedRealm sharedRealm, long schemaVersion) { + public static void setSchemaVersion(OsSharedRealm sharedRealm, long schemaVersion) { nativeSetSchemaVersion(sharedRealm.getNativePtr(), schemaVersion); } /** - * Returns the schema version of the given {@link SharedRealm}. If meta tables don't exist, this will return + * Returns the schema version of the given {@link OsSharedRealm}. If meta tables don't exist, this will return * {@link #SCHEMA_NOT_VERSIONED}. */ - public static long getSchemaVersion(SharedRealm sharedRealm) { + public static long getSchemaVersion(OsSharedRealm sharedRealm) { return nativeGetSchemaVersion(sharedRealm.getNativePtr()); } @@ -69,7 +69,7 @@ public static long getSchemaVersion(SharedRealm sharedRealm) { * @return {@code true} if the table has been deleted. {@code false} if the table doesn't exist. * @throws IllegalStateException if it is not in a transaction. */ - public static boolean deleteTableForObject(SharedRealm sharedRealm, String className) { + public static boolean deleteTableForObject(OsSharedRealm sharedRealm, String className) { return nativeDeleteTableForObject(sharedRealm.getNativePtr(), className); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index afc199fe72..8b731071c6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -78,13 +78,13 @@ public byte getNativeValue() { /** * Builder class for creating {@code OsRealmConfig}. The {@code OsRealmConfig} instance should only be created by - * {@link SharedRealm}. + * {@link OsSharedRealm}. */ public static class Builder { private RealmConfiguration configuration; private OsSchemaInfo schemaInfo = null; - private SharedRealm.MigrationCallback migrationCallback = null; - private SharedRealm.InitializationCallback initializationCallback = null; + private OsSharedRealm.MigrationCallback migrationCallback = null; + private OsSharedRealm.InitializationCallback initializationCallback = null; private boolean autoUpdateNotification = false; /** @@ -112,7 +112,7 @@ public Builder schemaInfo(@Nullable OsSchemaInfo schemaInfo) { * @param migrationCallback callback to be set. * @return this {@link OsRealmConfig.Builder}. */ - public Builder migrationCallback(@Nullable SharedRealm.MigrationCallback migrationCallback) { + public Builder migrationCallback(@Nullable OsSharedRealm.MigrationCallback migrationCallback) { this.migrationCallback = migrationCallback; return this; } @@ -123,7 +123,7 @@ public Builder migrationCallback(@Nullable SharedRealm.MigrationCallback migrati * @param initializationCallback the callback to be set. * @return this {@link OsRealmConfig.Builder}. */ - public Builder initializationCallback(@Nullable SharedRealm.InitializationCallback initializationCallback) { + public Builder initializationCallback(@Nullable OsSharedRealm.InitializationCallback initializationCallback) { this.initializationCallback = initializationCallback; return this; } @@ -141,7 +141,7 @@ public Builder autoUpdateNotification(boolean autoUpdateNotification) { } // Package private because of the OsRealmConfig needs to carry the NativeContext. This should only be called - // by the SharedRealm. + // by the OsSharedRealm. OsRealmConfig build() { return new OsRealmConfig(configuration, autoUpdateNotification, schemaInfo, migrationCallback, initializationCallback); @@ -163,10 +163,10 @@ OsRealmConfig build() { private final RealmConfiguration realmConfiguration; private final URI resolvedRealmURI; private final long nativePtr; - // Every SharedRealm instance has to be created from an OsRealmConfig instance. And the SharedRealm's NativeContext - // object will be the same as the context here. This is because of we may create different SharedRealm instances + // Every OsSharedRealm instance has to be created from an OsRealmConfig instance. And the OsSharedRealm's NativeContext + // object will be the same as the context here. This is because of we may create different OsSharedRealm instances // with different shared_ptrs which are point to the same SharedGroup object. It could happen when we create - // SharedRealm for migration/initialization callback. The context has to be the same object for those cases for + // OsSharedRealm for migration/initialization callback. The context has to be the same object for those cases for // core destructor's thread safety. private final NativeContext context = new NativeContext(); @@ -175,15 +175,15 @@ OsRealmConfig build() { @SuppressWarnings({"FieldCanBeLocal", "unused"}) private final CompactOnLaunchCallback compactOnLaunchCallback; @SuppressWarnings({"FieldCanBeLocal", "unused"}) - private final SharedRealm.MigrationCallback migrationCallback; + private final OsSharedRealm.MigrationCallback migrationCallback; @SuppressWarnings({"FieldCanBeLocal", "unused"}) - private final SharedRealm.InitializationCallback initializationCallback; + private final OsSharedRealm.InitializationCallback initializationCallback; private OsRealmConfig(final RealmConfiguration config, boolean autoUpdateNotification, @Nullable OsSchemaInfo schemaInfo, - @Nullable SharedRealm.MigrationCallback migrationCallback, - @Nullable SharedRealm.InitializationCallback initializationCallback) { + @Nullable OsSharedRealm.MigrationCallback migrationCallback, + @Nullable OsSharedRealm.InitializationCallback initializationCallback) { this.realmConfiguration = config; this.nativePtr = nativeCreate(config.getPath(), false, true); NativeContext.dummyContext.addReference(this); @@ -284,11 +284,11 @@ NativeContext getContext() { private native void nativeSetSchemaConfig(long nativePtr, byte schemaMode, long schemaVersion, long schemaInfoPtr, - @Nullable SharedRealm.MigrationCallback migrationCallback); + @Nullable OsSharedRealm.MigrationCallback migrationCallback); private static native void nativeSetCompactOnLaunchCallback(long nativePtr, CompactOnLaunchCallback callback); - private native void nativeSetInitializationCallback(long nativePtr, SharedRealm.InitializationCallback callback); + private native void nativeSetInitializationCallback(long nativePtr, OsSharedRealm.InitializationCallback callback); private static native void nativeEnableChangeNotification(long nativePtr, boolean enableNotification); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index ce294974ea..73e7e7efad 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -205,7 +205,7 @@ public void set(@Nullable T object) { private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - private final SharedRealm sharedRealm; + private final OsSharedRealm sharedRealm; private final NativeContext context; private final Table table; private boolean loaded; @@ -276,7 +276,7 @@ static Mode getByValue(byte value) { } } - public static OsResults createBacklinksCollection(SharedRealm realm, UncheckedRow row, Table srcTable, String srcFieldName) { + public static OsResults createBacklinksCollection(OsSharedRealm realm, UncheckedRow row, Table srcTable, String srcFieldName) { long backlinksPtr = nativeCreateResultsFromBacklinks( realm.getNativePtr(), row.getNativePtr(), @@ -285,7 +285,7 @@ public static OsResults createBacklinksCollection(SharedRealm realm, UncheckedRo return new OsResults(realm, srcTable, backlinksPtr, true); } - public OsResults(SharedRealm sharedRealm, TableQuery query, + public OsResults(OsSharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor, @Nullable SortDescriptor distinctDescriptor) { query.validateQuery(); @@ -300,15 +300,15 @@ public OsResults(SharedRealm sharedRealm, TableQuery query, this.loaded = false; } - public OsResults(SharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor) { + public OsResults(OsSharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor) { this(sharedRealm, query, sortDescriptor, null); } - public OsResults(SharedRealm sharedRealm, TableQuery query) { + public OsResults(OsSharedRealm sharedRealm, TableQuery query) { this(sharedRealm, query, null, null); } - public OsResults(SharedRealm sharedRealm, OsList osList, @Nullable SortDescriptor sortDescriptor) { + public OsResults(OsSharedRealm sharedRealm, OsList osList, @Nullable SortDescriptor sortDescriptor) { this.nativePtr = nativeCreateResultsFromList(sharedRealm.getNativePtr(), osList.getNativePtr(), sortDescriptor); this.sharedRealm = sharedRealm; @@ -320,11 +320,11 @@ public OsResults(SharedRealm sharedRealm, OsList osList, @Nullable SortDescripto this.loaded = true; } - private OsResults(SharedRealm sharedRealm, Table table, long nativePtr) { + private OsResults(OsSharedRealm sharedRealm, Table table, long nativePtr) { this(sharedRealm, table, nativePtr, false); } - OsResults(SharedRealm sharedRealm, Table table, long nativePtr, boolean loaded) { + OsResults(OsSharedRealm sharedRealm, Table table, long nativePtr, boolean loaded) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.table = table; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsSchemaInfo.java index 4585c514b5..c2c2a6ff1e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSchemaInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSchemaInfo.java @@ -22,15 +22,15 @@ * When it is created from java binding, it is used for initializing/validating the schemas through Object Store. It * won't contain the column indices information. *

              - * When this is get from the Object Store {@code SharedRealm} instance, this represents the real schema of the Realm + * When this is get from the Object Store {@code OsSharedRealm} instance, this represents the real schema of the Realm * file. It will contain all the schema information as well as the information about the column indices. */ public class OsSchemaInfo implements NativeObject { private long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - // Hold the ref to the SharedRealm to ensure the SharedRealm won't be freed before this gets GCed. + // Hold the ref to the OsSharedRealm to ensure the OsSharedRealm won't be freed before this gets GCed. @SuppressWarnings("unused") - private final SharedRealm sharedRealm; + private final OsSharedRealm sharedRealm; /** * Constructs a {@code OsSchemaInfo} object from a given {@code OsObjectSchemaInfo} list. @@ -46,14 +46,14 @@ public OsSchemaInfo(java.util.Collection objectSchemaInfoLis /** * Constructs a {@code OsSchemaInfo} and bind its life cycle with the given {@code ShareRealm}. The native pointer * held by this instance points to the reference of ObjectStore's {@code Realm::m_schema}. It will be valid - * as long as the {@code SharedRealm} instance is not GCed. + * as long as the {@code OsSharedRealm} instance is not GCed. *

              - * This should only be called by {@link SharedRealm}. + * This should only be called by {@link OsSharedRealm}. * * @param nativePtr the pointer to the Object Store's {@code Realm::m_schema}. - * @param sharedRealm the {@code SharedRealm} instance which is owning the schema object. + * @param sharedRealm the {@code OsSharedRealm} instance which is owning the schema object. */ - OsSchemaInfo(long nativePtr, SharedRealm sharedRealm) { + OsSchemaInfo(long nativePtr, OsSharedRealm sharedRealm) { this.nativePtr = nativePtr; this.sharedRealm = sharedRealm; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java similarity index 92% rename from realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java rename to realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index bdb0cb3505..7bb3a075af 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -31,7 +31,7 @@ import io.realm.internal.android.AndroidRealmNotifier; @Keep -public final class SharedRealm implements Closeable, NativeObject { +public final class OsSharedRealm implements Closeable, NativeObject { public static class VersionID implements Comparable { public final long version; @@ -96,12 +96,12 @@ public interface MigrationCallback { /** * Callback function. * - * @param sharedRealm the same {@link SharedRealm} instance which has been created from the same + * @param sharedRealm the same {@link OsSharedRealm} instance which has been created from the same * {@link OsRealmConfig} instance. * @param oldVersion the schema version of the existing Realm file. * @param newVersion the expected schema version after migration. */ - void onMigrationNeeded(SharedRealm sharedRealm, long oldVersion, long newVersion); + void onMigrationNeeded(OsSharedRealm sharedRealm, long oldVersion, long newVersion); } /** @@ -110,9 +110,9 @@ public interface MigrationCallback { @Keep public interface InitializationCallback { /** - * @param sharedRealm a {@link SharedRealm} instance which is in transaction state. + * @param sharedRealm a {@link OsSharedRealm} instance which is in transaction state. */ - void onInit(SharedRealm sharedRealm); + void onInit(OsSharedRealm sharedRealm); } /** @@ -165,7 +165,7 @@ protected PartialSyncCallback(String className) { // Package protected for testing final List> iterators = new ArrayList<>(); - private SharedRealm(OsRealmConfig osRealmConfig) { + private OsSharedRealm(OsRealmConfig osRealmConfig) { Capabilities capabilities = new AndroidCapabilities(); RealmNotifier realmNotifier = new AndroidRealmNotifier(this, capabilities); @@ -181,13 +181,13 @@ private SharedRealm(OsRealmConfig osRealmConfig) { } /** - * Creates a {@code SharedRealm} instance from a given Object Store's {@code SharedRealm} pointer. This is used to - * create {@code SharedRealm} from the callback functions. When this is called, there is another - * {@code SharedRealm} instance with the same {@link OsRealmConfig} which has been created before. Although they + * Creates a {@code OsSharedRealm} instance from a given Object Store's {@code OsSharedRealm} pointer. This is used to + * create {@code OsSharedRealm} from the callback functions. When this is called, there is another + * {@code OsSharedRealm} instance with the same {@link OsRealmConfig} which has been created before. Although they * are different {@code shared_ptr}, they point to the same {@code SharedGroup} instance. The {@code context} has * to be the same one to ensure core's destructor thread safety. */ - private SharedRealm(long nativeSharedRealmPtr, OsRealmConfig osRealmConfig) { + private OsSharedRealm(long nativeSharedRealmPtr, OsRealmConfig osRealmConfig) { this.nativePtr = nativeSharedRealmPtr; this.osRealmConfig = osRealmConfig; this.schemaInfo = new OsSchemaInfo(nativeGetSchemaInfo(nativePtr), this); @@ -202,9 +202,9 @@ private SharedRealm(long nativeSharedRealmPtr, OsRealmConfig osRealmConfig) { /** - * Creates a {@code SharedRealm} instance in dynamic schema mode. + * Creates a {@code OsSharedRealm} instance in dynamic schema mode. */ - public static SharedRealm getInstance(RealmConfiguration config) { + public static OsSharedRealm getInstance(RealmConfiguration config) { OsRealmConfig.Builder builder = new OsRealmConfig.Builder(config); return getInstance(builder); } @@ -212,15 +212,15 @@ public static SharedRealm getInstance(RealmConfiguration config) { /** * Creates a {@code ShareRealm} instance from the given {@link OsRealmConfig.Builder}. */ - public static SharedRealm getInstance(OsRealmConfig.Builder configBuilder) { + public static OsSharedRealm getInstance(OsRealmConfig.Builder configBuilder) { OsRealmConfig osRealmConfig = configBuilder.build(); ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(osRealmConfig); - return new SharedRealm(osRealmConfig); + return new OsSharedRealm(osRealmConfig); } public static void initialize(File tempDirectory) { - if (SharedRealm.temporaryDirectory != null) { + if (OsSharedRealm.temporaryDirectory != null) { // already initialized return; } @@ -234,7 +234,7 @@ public static void initialize(File tempDirectory) { temporaryDirectoryPath += "/"; } nativeInit(temporaryDirectoryPath); - SharedRealm.temporaryDirectory = tempDirectory; + OsSharedRealm.temporaryDirectory = tempDirectory; } public static File getTemporaryDirectory() { @@ -327,9 +327,9 @@ public void refresh() { nativeRefresh(nativePtr); } - public SharedRealm.VersionID getVersionID() { + public OsSharedRealm.VersionID getVersionID() { long[] versionId = nativeGetVersionID(nativePtr); - return new SharedRealm.VersionID(versionId[0], versionId[1]); + return new OsSharedRealm.VersionID(versionId[0], versionId[1]); } public boolean isClosed() { @@ -379,7 +379,7 @@ public void close() { } synchronized (context) { nativeCloseSharedRealm(nativePtr); - // Don't reset the nativePtr since we still rely on Object Store to check if the given SharedRealm ptr + // Don't reset the nativePtr since we still rely on Object Store to check if the given OsSharedRealm ptr // is closed or not. } } @@ -395,7 +395,7 @@ public long getNativeFinalizerPtr() { } /** - * @return the {@link OsSchemaInfo} of this {@code SharedRealm}. + * @return the {@link OsSchemaInfo} of this {@code OsSharedRealm}. */ public OsSchemaInfo getSchemaInfo() { return schemaInfo; @@ -482,18 +482,18 @@ private void executePendingRowQueries() { @SuppressWarnings("unused") private static void runMigrationCallback(long nativeSharedRealmPtr, OsRealmConfig osRealmConfig, MigrationCallback callback, long oldVersion) { - callback.onMigrationNeeded(new SharedRealm(nativeSharedRealmPtr, osRealmConfig), oldVersion, + callback.onMigrationNeeded(new OsSharedRealm(nativeSharedRealmPtr, osRealmConfig), oldVersion, osRealmConfig.getRealmConfiguration().getSchemaVersion()); } /** * Called from JNI when the schema is created the first time. * - * @param callback to be executed with a given in-transact {@link SharedRealm}. + * @param callback to be executed with a given in-transact {@link OsSharedRealm}. */ @SuppressWarnings("unused") private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealmConfig osRealmConfig, InitializationCallback callback) { - callback.onInit(new SharedRealm(nativeSharedRealmPtr, osRealmConfig)); + callback.onInit(new OsSharedRealm(nativeSharedRealmPtr, osRealmConfig)); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index ccdad47857..3b4b9802be 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -31,14 +31,14 @@ public interface FrontEnd { private static final String QUERY_EXECUTED_MESSAGE = "The query has been executed. This 'PendingRow' is not valid anymore."; - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; private OsResults pendingOsResults; private RealmChangeListener listener; private WeakReference frontEndRef; private boolean returnCheckedRow; - public PendingRow(SharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor, - final boolean returnCheckedRow) { + public PendingRow(OsSharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor, + final boolean returnCheckedRow) { this.sharedRealm = sharedRealm; pendingOsResults = new OsResults(sharedRealm, query, sortDescriptor, null); diff --git a/realm/realm-library/src/main/java/io/realm/internal/ProxyUtils.java b/realm/realm-library/src/main/java/io/realm/internal/ProxyUtils.java index 7b9da29d3d..6ea3d1c706 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ProxyUtils.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ProxyUtils.java @@ -22,7 +22,7 @@ public class ProxyUtils { - public static void verifyField(SharedRealm sharedRealm, Map columnTypes, String fieldName, RealmFieldType fieldType, String fieldSimpleType) { + public static void verifyField(OsSharedRealm sharedRealm, Map columnTypes, String fieldName, RealmFieldType fieldType, String fieldSimpleType) { if (!columnTypes.containsKey(fieldName)) { throw new RealmMigrationNeededException( sharedRealm.getPath(), diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 06864b403a..cec9d4acdd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -74,11 +74,11 @@ public void onCalled(RealmObserverPair pair, Object observer) { } }; - protected RealmNotifier(@Nullable SharedRealm sharedRealm) { + protected RealmNotifier(@Nullable OsSharedRealm sharedRealm) { this.sharedRealm = sharedRealm; } - private SharedRealm sharedRealm; + private OsSharedRealm sharedRealm; // TODO: The only reason we have this is that async transactions is not supported by OS yet. And OS is using ALopper // which will be using a different message queue from which java is using to deliver remote Realm changes message. // We need a way to deliver the async transaction onSuccess callback to the caller thread after the caller Realm @@ -115,7 +115,7 @@ void didChange() { // Called from JavaBindingContext::before_notify. // This will be called in the caller thread when: // 1. Get changed notification by this/other Realm instances. - // 2. SharedRealm::refresh called. + // 2. OsSharedRealm::refresh called. // In both cases, this will be called before the any other callbacks (changed callbacks, async query callbacks.). // Package protected to avoid finding class by name in JNI. @SuppressWarnings("unused") @@ -125,7 +125,7 @@ void beforeNotify() { } /** - * Called when close SharedRealm to clean up any event left in to queue. + * Called when close OsSharedRealm to clean up any event left in to queue. */ @Override public void close() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 0135e69f60..8be0dc7078 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -47,13 +47,13 @@ public class Table implements NativeObject { private final long nativePtr; private final NativeContext context; - private final SharedRealm sharedRealm; + private final OsSharedRealm sharedRealm; Table(Table parent, long nativePointer) { this(parent.sharedRealm, nativePointer); } - Table(SharedRealm sharedRealm, long nativePointer) { + Table(OsSharedRealm sharedRealm, long nativePointer) { this.context = sharedRealm.context; this.sharedRealm = sharedRealm; this.nativePtr = nativePointer; @@ -346,7 +346,7 @@ public static void throwDuplicatePrimaryKeyException(Object value) { // Getters // - SharedRealm getSharedRealm() { + OsSharedRealm getSharedRealm() { return sharedRealm; } @@ -523,7 +523,7 @@ public void removeSearchIndex(long columnIndex) { * The native method will begin a transaction and make the migration if needed. * This function should not be called in a transaction. */ - public static void migratePrimaryKeyTableIfNeeded(SharedRealm sharedRealm) { + public static void migratePrimaryKeyTableIfNeeded(OsSharedRealm sharedRealm) { nativeMigratePrimaryKeyTableIfNeeded(sharedRealm.getNativePtr()); } @@ -543,7 +543,7 @@ boolean isImmutable() { return sharedRealm != null && !sharedRealm.isInTransaction(); } - // This checking should be moved to SharedRealm level. + // This checking should be moved to OsSharedRealm level. void checkImmutable() { if (isImmutable()) { throwImmutable(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java index 5e4f2e067a..29f93a8fe4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidRealmNotifier.java @@ -8,7 +8,7 @@ import io.realm.internal.Capabilities; import io.realm.internal.Keep; import io.realm.internal.RealmNotifier; -import io.realm.internal.SharedRealm; +import io.realm.internal.OsSharedRealm; /** @@ -18,7 +18,7 @@ public class AndroidRealmNotifier extends RealmNotifier { private Handler handler; - public AndroidRealmNotifier(@Nullable SharedRealm sharedRealm, Capabilities capabilities) { + public AndroidRealmNotifier(@Nullable OsSharedRealm sharedRealm, Capabilities capabilities) { super(sharedRealm); if (capabilities.canDeliverNotification()) { handler = new Handler(Looper.myLooper()); diff --git a/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java index 8cc0f60785..d0e99acdf7 100644 --- a/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java +++ b/realm/realm-library/src/main/java/io/realm/log/RealmLogger.java @@ -25,7 +25,7 @@ * Interface for custom loggers that can be registered at {@link RealmLog#add(RealmLogger)}. * The different log levels are described in {@link LogLevel}. */ -@Keep // This interface is used as a parameter type of a native method in SharedRealm.java +@Keep // This interface is used as a parameter type of a native method in OsSharedRealm.java public interface RealmLogger { /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 100c89b613..24f936108f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -32,7 +32,7 @@ public enum ErrorCode { UNKNOWN(-1), // Catch-all IO_EXCEPTION(0, Category.RECOVERABLE), // Some IO error while either contacting the server or reading the response JSON_EXCEPTION(1), // JSON input could not be parsed correctly - CLIENT_RESET(7), // Client Reset required. Don't change this value without modifying io_realm_internal_SharedRealm.cpp + CLIENT_RESET(7), // Client Reset required. Don't change this value without modifying io_realm_internal_OsSharedRealm.cpp // Realm Object Server errors (100 - 199) // Connection level and protocol errors. From d090c7f487279bf8d97571c7491cf602033905b9 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 26 Oct 2017 12:58:07 +0800 Subject: [PATCH 1075/2110] Enable downloadProgressListener_indefinitely In createRemoteData we try to identify the uploading is finished by two conditions: - Check if the newly created data exists in the Realm on the callback thread. - Check if uploadable == uploaded This works if the callback is called on the sync client thread. But actually there is a chance the callback gets called immediately after registered because of some other data created before just got uploaded. In this case, the data does exist in the Realm on the caller thread (we just add on the same thread), and the uploaded could equal to the uploadable. By only accept the callback on the sync client thread by if (threadId == Thread.currentThread().getId()) will make sure the data has been uploaded. --- .../objectserver/ProgressListenerTests.java | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index d757107729..9183adda5e 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -18,7 +18,6 @@ import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,6 +34,7 @@ import io.realm.ProgressListener; import io.realm.ProgressMode; import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; import io.realm.SyncManager; @@ -83,19 +83,23 @@ private void assertTransferComplete(Progress progress, boolean nonZeroChange) { } // Create remote data for a given user. - private URI createRemoteData(SyncUser user) { - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .name("remote") - .build(); + private URI createRemoteData(final SyncConfiguration config) { final Realm realm = Realm.getInstance(config); final CountDownLatch changesUploaded = new CountDownLatch(1); final SyncSession session = SyncManager.getSession(config); final long beforeAdd = realm.where(AllTypes.class).count(); writeSampleData(realm); + final long threadId = Thread.currentThread().getId(); + session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { @Override public void onChange(Progress progress) { + // FIXME: This check is to make sure before this method returns, all the uploads has been done. + // See https://github.com/realm/realm-object-store/issues/581#issuecomment-339353832 + if (threadId == Thread.currentThread().getId()) { + return; + } if (progress.isTransferComplete()) { Realm realm = Realm.getInstance(config); final long afterAdd = realm.where(AllTypes.class).count(); @@ -118,11 +122,21 @@ public void onChange(Progress progress) { return config.getServerUrl(); } + private long getStoreTestDataSize(RealmConfiguration config) { + Realm adminRealm = Realm.getInstance(config); + long objectCounts = adminRealm.where(AllTypes.class).count(); + adminRealm.close(); + + return objectCounts; + } + @Test public void downloadProgressListener_changesOnly() { final CountDownLatch allChangesDownloaded = new CountDownLatch(1); SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); - URI serverUrl = createRemoteData(userWithData); + SyncConfiguration userWithDataConfig = configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM) + .build(); + URI serverUrl = createRemoteData(userWithDataConfig); SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()).build(); @@ -133,9 +147,7 @@ public void downloadProgressListener_changesOnly() { public void onChange(Progress progress) { if (progress.isTransferComplete()) { assertTransferComplete(progress, true); - Realm realm = Realm.getInstance(config); - assertEquals(TEST_SIZE, realm.where(AllTypes.class).count()); - realm.close(); + assertEquals(TEST_SIZE, getStoreTestDataSize(config)); allChangesDownloaded.countDown(); } } @@ -145,14 +157,16 @@ public void onChange(Progress progress) { } @Test - @Ignore("https://github.com/realm/realm-sync/issues/1770") public void downloadProgressListener_indefinitely() throws InterruptedException { final AtomicInteger transferCompleted = new AtomicInteger(0); final CountDownLatch allChangesDownloaded = new CountDownLatch(1); final CountDownLatch startWorker = new CountDownLatch(1); final SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); + final SyncConfiguration userWithDataConfig = configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM) + .name("remote") + .build(); - URI serverUrl = createRemoteData(userWithData); + URI serverUrl = createRemoteData(userWithDataConfig); // Create worker thread that puts data into another Realm. // This is to avoid blocking one progress listener while waiting for another to complete. @@ -160,7 +174,7 @@ public void downloadProgressListener_indefinitely() throws InterruptedException @Override public void run() { TestHelper.awaitOrFail(startWorker); - createRemoteData(userWithData); + createRemoteData(userWithDataConfig); } }); worker.start(); @@ -170,16 +184,16 @@ public void run() { .name("local") .build(); Realm adminRealm = Realm.getInstance(adminConfig); - Realm userRealm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM).build()); // Keep session alive SyncSession session = SyncManager.getSession(adminConfig); session.addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { @Override public void onChange(Progress progress) { - Realm adminRealm = Realm.getInstance(adminConfig); - long objectCounts = adminRealm.where(AllTypes.class).count(); - adminRealm.close(); + long objectCounts = getStoreTestDataSize(adminConfig); // The downloading progress listener could be triggered at the db version where only contains the meta // data. So we start checking from when the first 10 objects downloaded. + RealmLog.warn(String.format( + Locale.ENGLISH,"downloadProgressListener_indefinitely download %d/%d objects count:%d", + progress.getTransferredBytes(), progress.getTransferableBytes(), objectCounts)); if (objectCounts != 0 && progress.isTransferComplete()) { switch (transferCompleted.incrementAndGet()) { @@ -198,15 +212,11 @@ public void onChange(Progress progress) { default: fail("Transfer complete called too many times:" + transferCompleted.get()); } - RealmLog.warn(String.format( - Locale.ENGLISH,"downloadProgressListener_indefinitely download %d/%d objects count:%d", - progress.getTransferredBytes(), progress.getTransferableBytes(), objectCounts)); } } }); TestHelper.awaitOrFail(allChangesDownloaded); adminRealm.close(); - userRealm.close(); // worker thread will hang if logout happens before listener triggered. worker.join(); userWithData.logout(); From ebbd7466a0836459b043dcbb47d93a814a58250f Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 1 Nov 2017 11:18:48 +0800 Subject: [PATCH 1076/2110] Fix a socket timeout issue with test server (#5466) * Fix a socket timeout issue with test server If startRealmObjectServer was called twice without stop in between, syncServerChildProcess will be overwritten by a terminated process since the 2nd call of creating sync server will fail and set the syncServerChildProcess. This would make the stopRealmObjectServer get stuck forever. In practice, we need to make sure the server has been stopped before start it. So just treat the "start" as a "restart" command which will stop the sync server if needed. Thus, no need to stop in tear down functions in java side anymore. * remove useless function --- .../java/io/realm/BaseIntegrationTest.java | 3 ++- .../java/io/realm/IsolatedIntegrationTests.java | 2 -- .../java/io/realm/StandardIntegrationTest.java | 5 ----- .../java/io/realm/objectserver/utils/HttpUtils.java | 7 +++++++ tools/sync_test_server/ros-testing-server.js | 10 ++++++++-- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index fa0406c914..4c31b68f10 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -88,7 +88,8 @@ protected static void startSyncServer() { } /** - * Stops the ROS instance used for the test. + * Stops the ROS instance used for the test. The {@link #startSyncServer()} will stop the sync server if needed, so + * normally there is no need to call this. */ protected static void stopSyncServer() { try { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java index e25225fbbe..09715009a1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java @@ -26,14 +26,12 @@ public void teardownTest() { if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { // Non-looper tests can reset here restoreEnvironmentAfterTest(); - stopSyncServer(); } else { // Otherwise we need to wait for the test to complete looperThread.runAfterTest(new Runnable() { @Override public void run() { restoreEnvironmentAfterTest(); - stopSyncServer(); } }); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java index e2b7bb9513..858824853d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java @@ -35,11 +35,6 @@ public static void setupTestClass() throws Exception { startSyncServer(); } - @AfterClass - public static void tearDownTestClass() throws Exception { - stopSyncServer(); - } - @Before public void setupTest() throws IOException { prepareEnvironmentForTest(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index 9c4d3881dc..cc3fcd95ee 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -50,6 +50,9 @@ public class HttpUtils { private static final String STOP_SERVER = "http://127.0.0.1:8888/stop"; public static final String TAG = "IntegrationTestServer"; + /** + * Start the sync server. If the server has been started before, stop it first. + */ public static void startSyncServer() throws Exception { Request request = new Request.Builder() .url(START_SERVER) @@ -63,6 +66,10 @@ public static void startSyncServer() throws Exception { SystemClock.sleep(2000); } + /** + * Stop the sync server if it is alive. {@link #startSyncServer()} will implicitly stop the server if needed, so + * normally there is no need to call this. + */ public static void stopSyncServer() throws Exception { Request request = new Request.Builder() .url(STOP_SERVER) diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index d86165a6e8..2f841d4f43 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -66,6 +66,12 @@ function waitForRosToInitialize(attempts, onSuccess, onError) { } function startRealmObjectServer(onSuccess, onError) { + stopRealmObjectServer(() => { + doStartRealmObjectServer(onSuccess, onError) + }, onError) +} + +function doStartRealmObjectServer(onSuccess, onError) { temp.mkdir('ros', function(err, path) { if (!err) { winston.info("Starting sync server in ", path); @@ -112,8 +118,8 @@ function startRealmObjectServer(onSuccess, onError) { } function stopRealmObjectServer(onSuccess, onError) { - if(syncServerChildProcess == null) { - onError("No ROS process found to stop"); + if(syncServerChildProcess == null || syncServerChildProcess.killed) { + onSuccess("No ROS process found or the process has been killed before"); } syncServerChildProcess.on('exit', function(code) { From 926e0e626ca6a80940725dc28a098b3207ad7746 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 1 Nov 2017 16:55:25 +0800 Subject: [PATCH 1077/2110] Add multi-process example (#5473) All Realm public APIs should be process-safe now. Encryption is not supported for multi processes due to core restriction https://github.com/realm/realm-core/issues/1845 Accessing same Realm file in different processes from different apks is safe, but the notifications won't work. --- CHANGELOG.md | 2 + examples/multiprocessExample/.gitignore | 1 + examples/multiprocessExample/build.gradle | 31 ++++++ .../multiprocessExample/proguard-rules.pro | 17 ++++ .../src/main/AndroidManifest.xml | 26 +++++ .../AnotherProcessService.java | 59 ++++++++++++ .../MainActivity.java | 90 ++++++++++++++++++ .../MyApplication.java | 32 +++++++ .../realmmultiprocessexample/Utils.java | 58 +++++++++++ .../models/ProcessInfo.java | 54 +++++++++++ .../src/main/res/layout/activity_main.xml | 26 +++++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4906 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2968 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7076 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11165 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 16078 bytes .../src/main/res/values-w820dp/dimens.xml | 6 ++ .../src/main/res/values/colors.xml | 6 ++ .../src/main/res/values/dimens.xml | 5 + .../src/main/res/values/strings.xml | 4 + .../src/main/res/values/styles.xml | 11 +++ examples/settings.gradle | 1 + .../src/main/java/io/realm/Realm.java | 6 +- 23 files changed, 432 insertions(+), 3 deletions(-) create mode 100644 examples/multiprocessExample/.gitignore create mode 100644 examples/multiprocessExample/build.gradle create mode 100644 examples/multiprocessExample/proguard-rules.pro create mode 100644 examples/multiprocessExample/src/main/AndroidManifest.xml create mode 100644 examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/AnotherProcessService.java create mode 100644 examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MainActivity.java create mode 100644 examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MyApplication.java create mode 100644 examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/Utils.java create mode 100644 examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/models/ProcessInfo.java create mode 100644 examples/multiprocessExample/src/main/res/layout/activity_main.xml create mode 100644 examples/multiprocessExample/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 examples/multiprocessExample/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 examples/multiprocessExample/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 examples/multiprocessExample/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 examples/multiprocessExample/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 examples/multiprocessExample/src/main/res/values-w820dp/dimens.xml create mode 100644 examples/multiprocessExample/src/main/res/values/colors.xml create mode 100644 examples/multiprocessExample/src/main/res/values/dimens.xml create mode 100644 examples/multiprocessExample/src/main/res/values/strings.xml create mode 100644 examples/multiprocessExample/src/main/res/values/styles.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index c4021da167..3a31b73df9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Enhancements +* Added support for using non-encrypted Realms in multiple processes. Some caveats apply. Read [doc](https://realm.io/docs/java/latest/#multiprocess) for more info (#1091). + ### Bug Fixes ### Interal diff --git a/examples/multiprocessExample/.gitignore b/examples/multiprocessExample/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/examples/multiprocessExample/.gitignore @@ -0,0 +1 @@ +/build diff --git a/examples/multiprocessExample/build.gradle b/examples/multiprocessExample/build.gradle new file mode 100644 index 0000000000..22768495f6 --- /dev/null +++ b/examples/multiprocessExample/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' +apply plugin: 'android-command' +apply plugin: 'realm-android' + +android { + compileSdkVersion rootProject.sdkVersion + buildToolsVersion rootProject.buildTools + + defaultConfig { + applicationId "io.realm.examples.realmmultiprocessexample" + targetSdkVersion rootProject.sdkVersion + minSdkVersion 15 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true + } + } +} + +dependencies { + implementation 'com.android.support:appcompat-v7:26.0.1' +} + diff --git a/examples/multiprocessExample/proguard-rules.pro b/examples/multiprocessExample/proguard-rules.pro new file mode 100644 index 0000000000..8456b3daec --- /dev/null +++ b/examples/multiprocessExample/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/cc/.android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/examples/multiprocessExample/src/main/AndroidManifest.xml b/examples/multiprocessExample/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..91862ee505 --- /dev/null +++ b/examples/multiprocessExample/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/AnotherProcessService.java b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/AnotherProcessService.java new file mode 100644 index 0000000000..72c56433d5 --- /dev/null +++ b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/AnotherProcessService.java @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.examples.realmmultiprocessexample; + +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; + +import io.realm.Realm; + +public class AnotherProcessService extends Service { + + Handler handler; + + @Override + public void onCreate() { + super.onCreate(); + + handler = new Handler(Looper.myLooper()); + final Runnable runnable = new Runnable() { + @Override + public void run() { + Realm realm = Realm.getDefaultInstance(); + realm.beginTransaction(); + realm.copyToRealmOrUpdate(Utils.createStandaloneProcessInfo(AnotherProcessService.this)); + realm.commitTransaction(); + realm.close(); + handler.postDelayed(this, 1000); + } + }; + handler.postDelayed(runnable, 1000); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + throw new UnsupportedOperationException("Not yet implemented"); + } +} diff --git a/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MainActivity.java b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MainActivity.java new file mode 100644 index 0000000000..6c279d4a99 --- /dev/null +++ b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MainActivity.java @@ -0,0 +1,90 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.examples.realmmultiprocessexample; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Locale; + +import io.realm.Realm; +import io.realm.RealmChangeListener; +import io.realm.RealmResults; +import io.realm.examples.realmmultiprocessexample.models.ProcessInfo; + +public class MainActivity extends AppCompatActivity { + + private TextView textView; + private Realm realm; + private RealmResults processInfoResults; + private DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss", Locale.ENGLISH); + + private RealmChangeListener> listener = + new RealmChangeListener>() { + @Override + public void onChange(RealmResults results) { + StringBuilder stringBuilder = new StringBuilder(); + + for (ProcessInfo processInfo : results) { + stringBuilder.append(processInfo.getName()); + stringBuilder.append("\npid: "); + stringBuilder.append(processInfo.getPid()); + stringBuilder.append("\nlast response time: "); + stringBuilder.append(dateFormat.format(processInfo.getLastResponseDate())); + stringBuilder.append("\n------\n"); + } + textView.setText(stringBuilder.toString()); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + textView = (TextView) findViewById(R.id.textView); + + if (realm == null) { + realm = Realm.getDefaultInstance(); + processInfoResults = realm.where(ProcessInfo.class).findAllAsync(); + processInfoResults.addChangeListener(listener); + } + + realm.beginTransaction(); + realm.copyToRealmOrUpdate(Utils.createStandaloneProcessInfo(this)); + realm.commitTransaction(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (realm != null) { + realm.close(); + realm = null; + processInfoResults = null; + } + } + + public void onStartButton(View button) { + Intent intent = new Intent(MainActivity.this, AnotherProcessService.class); + startService(intent); + button.setEnabled(false); + } +} diff --git a/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MyApplication.java b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MyApplication.java new file mode 100644 index 0000000000..d078915489 --- /dev/null +++ b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MyApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.examples.realmmultiprocessexample; + +import android.app.Application; + +import io.realm.Realm; +import io.realm.RealmConfiguration; + +public class MyApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + Realm.init(this); + RealmConfiguration configuration = new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build(); + Realm.setDefaultConfiguration(configuration); + } +} diff --git a/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/Utils.java b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/Utils.java new file mode 100644 index 0000000000..68baaee8f5 --- /dev/null +++ b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/Utils.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.examples.realmmultiprocessexample; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.content.Context; +import android.os.Process; + +import java.util.Date; +import java.util.List; + +import io.realm.examples.realmmultiprocessexample.models.ProcessInfo; + +public class Utils { + + public static String getMyProcessName(Context context) { + String processName = ""; + ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); + List infoList = am.getRunningAppProcesses(); + if (infoList == null) { + throw new RuntimeException("getRunningAppProcesses() returns 'null'."); + } + for (RunningAppProcessInfo info : infoList) { + try { + if (info.pid == Process.myPid()) { + processName = info.processName; + break; + } + } catch (Exception ignored) { + } + } + return processName; + } + + public static ProcessInfo createStandaloneProcessInfo(Context context) { + ProcessInfo processInfo = new ProcessInfo(); + processInfo.setName(getMyProcessName(context)); + processInfo.setPid(android.os.Process.myPid()); + processInfo.setLastResponseDate(new Date()); + + return processInfo; + } +} diff --git a/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/models/ProcessInfo.java b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/models/ProcessInfo.java new file mode 100644 index 0000000000..7df0bdff41 --- /dev/null +++ b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/models/ProcessInfo.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.examples.realmmultiprocessexample.models; + +import java.util.Date; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; + +public class ProcessInfo extends RealmObject { + @PrimaryKey + private String name; + private int pid; + @Required + private Date lastResponseDate; + + public int getPid() { + return pid; + } + + public void setPid(int pid) { + this.pid = pid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getLastResponseDate() { + return lastResponseDate; + } + + public void setLastResponseDate(Date lastResponseDate) { + this.lastResponseDate = lastResponseDate; + } +} diff --git a/examples/multiprocessExample/src/main/res/layout/activity_main.xml b/examples/multiprocessExample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..79f8daf590 --- /dev/null +++ b/examples/multiprocessExample/src/main/res/layout/activity_main.xml @@ -0,0 +1,26 @@ + + + +

            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 O0000 literal 0 HcmV?d00001 diff --git a/examples/multiprocessExample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/multiprocessExample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9ece04b26b69f1d98f9294716e8c982a4577b9 GIT binary patch literal 11165 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 + + 64dp + diff --git a/examples/multiprocessExample/src/main/res/values/colors.xml b/examples/multiprocessExample/src/main/res/values/colors.xml new file mode 100644 index 0000000000..3ab3e9cbce --- /dev/null +++ b/examples/multiprocessExample/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/examples/multiprocessExample/src/main/res/values/dimens.xml b/examples/multiprocessExample/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..47c8224673 --- /dev/null +++ b/examples/multiprocessExample/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/examples/multiprocessExample/src/main/res/values/strings.xml b/examples/multiprocessExample/src/main/res/values/strings.xml new file mode 100644 index 0000000000..2b2a3c2acf --- /dev/null +++ b/examples/multiprocessExample/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + Realm Multi Process example + Start remote service + diff --git a/examples/multiprocessExample/src/main/res/values/styles.xml b/examples/multiprocessExample/src/main/res/values/styles.xml new file mode 100644 index 0000000000..5885930df6 --- /dev/null +++ b/examples/multiprocessExample/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/examples/settings.gradle b/examples/settings.gradle index 5dbeeef366..9edd7eb54f 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -12,5 +12,6 @@ include 'unitTestExample' include 'newsreaderExample' include 'rxJavaExample' include 'objectServerExample' +include 'multiprocessExample' rootProject.name = 'realm-examples' diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 769e460c32..57940dd4bb 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1670,7 +1670,7 @@ public static void migrateRealm(RealmConfiguration configuration, @Nullable Real * @param configuration a {@link RealmConfiguration}. * @return {@code false} if the Realm file could not be deleted. Temporary files deletion failure won't impact * the return value. All of the failing file deletions will be logged. - * @throws IllegalStateException if not all realm instances are closed. + * @throws IllegalStateException if there are Realm instances opened on other threads or other processes. */ public static boolean deleteRealm(RealmConfiguration configuration) { return BaseRealm.deleteRealm(configuration); @@ -1769,8 +1769,8 @@ public static Object getDefaultModule() { } /** - * Returns the current number of open Realm instances across all threads that are using this configuration. - * This includes both dynamic and normal Realms. + * Returns the current number of open Realm instances across all threads in current process that are using this + * configuration. This includes both dynamic and normal Realms. * * @param configuration the {@link io.realm.RealmConfiguration} for the Realm. * @return number of open Realm instances across all threads. From a75e1148cddbd828e4bb2ddc23ff6eb325f3a962 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 1 Nov 2017 17:02:29 +0800 Subject: [PATCH 1078/2110] Remove android-command for multiprocess example --- examples/multiprocessExample/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/multiprocessExample/build.gradle b/examples/multiprocessExample/build.gradle index 22768495f6..1823f58ecf 100644 --- a/examples/multiprocessExample/build.gradle +++ b/examples/multiprocessExample/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'android-command' apply plugin: 'realm-android' android { From e720acd7faf932a0499197ee9d254b7abb047fe6 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 1 Nov 2017 02:01:47 +0000 Subject: [PATCH 1079/2110] update ObjectStore to reflect SyncConfig refactor https://github.com/realm/realm-object-store/pull/590\) --- .../cpp/io_realm_internal_OsRealmConfig.cpp | 17 +++++++++-------- realm/realm-library/src/main/cpp/object-store | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index b54172f289..fd7d3fdc9e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -320,19 +320,20 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe user = SyncManager::shared().get_user(sync_user_identifier, refresh_token); } - util::Optional> sync_encryption_key(util::none); - if (!config.encryption_key.empty()) { - sync_encryption_key = std::array(); - std::copy_n(config.encryption_key.begin(), 64, sync_encryption_key->begin()); - } + SyncSessionStopPolicy session_stop_policy = static_cast(j_session_stop_policy); JStringAccessor realm_url(env, j_sync_realm_url); - config.sync_config = std::make_shared(SyncConfig{ - user, realm_url, session_stop_policy, std::move(bind_handler), std::move(error_handler), - nullptr, sync_encryption_key}); + config.sync_config = std::make_shared(SyncConfig{user, realm_url}); + config.sync_config->stop_policy = session_stop_policy; + config.sync_config->bind_session_handler = std::move(bind_handler); + config.sync_config->error_handler = std::move(error_handler); config.sync_config->is_partial = (j_is_partial == JNI_TRUE); + if (!config.encryption_key.empty()) { + config.sync_config->realm_encryption_key = std::array(); + std::copy_n(config.encryption_key.begin(), 64, config.sync_config->realm_encryption_key->begin()); + } return to_jstring(env, config.sync_config->realm_url().c_str()); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 136b3a32a2..1cb3a165dc 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 136b3a32a218f50275f1183ed078b31945a9e29f +Subproject commit 1cb3a165dc703a706cd107318b38c2f49fa3f31f From d1f7a166cd1a04ed406e160e7145b7589a0015da Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Fri, 3 Nov 2017 09:53:41 +0530 Subject: [PATCH 1080/2110] Update CHANGELOG.md --- CHANGELOG.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1694411c2c..09af30a62a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,3 @@ -## 4.2.0 (YYYY-MM-DD) - -### Breaking Changes - -### Enhancements - -### Bug Fixes - -### Internal - -### Credits - - ## 4.1.1 (2017-10-27) ### Breaking Changes From 653988a4f57a415c195d24521efbb2e15c175538 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Fri, 3 Nov 2017 09:54:18 +0530 Subject: [PATCH 1081/2110] Update build.gradle --- examples/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index f6d838d044..ee1616e19b 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -33,8 +33,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha01' - classpath 'com.novoda:gradle-android-command-plugin:1.7.1' + classpath 'com.android.tools.build:gradle:3.0.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } From 967e3a5d11cd44c61da541f3cf555c228883804d Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Fri, 3 Nov 2017 09:55:22 +0530 Subject: [PATCH 1082/2110] Update version.txt --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index c3a2c7076f..8acd11d8b9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.2.0-SNAPSHOT \ No newline at end of file +4.1.2-SNAPSHOT From 5acc57ab0d7a15ba3087a177f804642cdeb54e77 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Fri, 3 Nov 2017 10:08:17 +0530 Subject: [PATCH 1083/2110] Update Wrappers --- .../gradle/wrapper/gradle-wrapper.jar | Bin 54732 -> 54727 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 54732 -> 54727 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54732 -> 54727 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54732 -> 54727 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm.properties | 2 +- 9 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 0bdf3fe94139883078c58008a6b84cd063bfaf64..27768f1bbac3ce2d055b20d521f12da78d331e8e 100644 GIT binary patch delta 58 zcmX@Jn)&!@<_!)jp9oIAPYsx4di5ugkM E03^Z?!Tjp9oIAPYsx4di5ugkM E03^Z?!Tjp9oIAPYsx4di5ugkM E03^Z?!Tjp9oIAPYsx4di5ugkM E03^Z?!T Date: Fri, 3 Nov 2017 16:16:10 +0800 Subject: [PATCH 1084/2110] Add Credits --- CHANGELOG.md | 2 ++ version.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09af30a62a..b30e5da09c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ ### Credits +* Thanks to @vivekkiran for updating gralde and plugins to support Android Studio `3.0.0` (#5472). + ## 4.1.0 (2017-10-20) diff --git a/version.txt b/version.txt index 8acd11d8b9..d1a8f58b38 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.1.2-SNAPSHOT +4.1.2-SNAPSHOT \ No newline at end of file From 3e19d16509c7237a39033d895076b848e395a32b Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 3 Nov 2017 01:50:30 -0700 Subject: [PATCH 1085/2110] Specify distributionType in wrapper task (#5502) --- build.gradle | 1 + examples/build.gradle | 1 + gradle-plugin/build.gradle | 1 + library-benchmarks/build.gradle | 1 + realm-annotations/build.gradle | 1 + realm-transformer/build.gradle | 1 + realm/build.gradle | 1 + tools/update_gradle_wrapper.sh | 1 - 8 files changed, 7 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a27e1486e0..3ffcf0360b 100644 --- a/build.gradle +++ b/build.gradle @@ -478,4 +478,5 @@ release { task wrapper(type: Wrapper) { gradleVersion = project.gradleVersion + distributionType = 'all' } diff --git a/examples/build.gradle b/examples/build.gradle index 944e287edb..621691d9f1 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -75,4 +75,5 @@ allprojects { task wrapper(type: Wrapper) { gradleVersion = project.gradleVersion + distributionType = 'all' } diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 24cedb3dc1..3d5bcc70d2 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -76,6 +76,7 @@ compileJava.dependsOn generateVersionClass task wrapper(type: Wrapper) { gradleVersion = project.gradleVersion + distributionType = 'all' } def commonPom = { diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 6d48a2410e..5701253237 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -20,6 +20,7 @@ allprojects { task wrapper(type: Wrapper) { gradleVersion = project.gradleVersion + distributionType = 'all' } apply plugin: 'com.android.library' diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index f97d38992e..dcaa5dc90d 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -114,4 +114,5 @@ artifactory { task wrapper(type: Wrapper) { gradleVersion = project.gradleVersion + distributionType = 'all' } diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index a10ba1ecb0..9553214093 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -164,5 +164,6 @@ artifactory { task wrapper(type: Wrapper) { gradleVersion = project.gradleVersion + distributionType = 'all' } diff --git a/realm/build.gradle b/realm/build.gradle index 87890bb6a3..1be855767b 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -41,4 +41,5 @@ allprojects { task wrapper(type: Wrapper) { gradleVersion = project.gradleVersion + distributionType = 'all' } diff --git a/tools/update_gradle_wrapper.sh b/tools/update_gradle_wrapper.sh index 224817cf23..6096d3535e 100755 --- a/tools/update_gradle_wrapper.sh +++ b/tools/update_gradle_wrapper.sh @@ -14,7 +14,6 @@ for i in $(find $(pwd) -type f -name gradlew); do cd $(dirname $i) pwd ./gradlew wrapper - sed -E -i '' s/-bin\\.zip\$/-all.zip/ gradle/wrapper/gradle-wrapper.properties done cd $HERE From 1cdf7aac8e1670d113079b7d957f1372d68f3e00 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 6 Nov 2017 13:14:55 +0100 Subject: [PATCH 1086/2110] Fix spelling mistake (#5513) --- .../src/main/java/io/realm/internal/OsResults.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 73e7e7efad..045472ca98 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -199,7 +199,7 @@ public int previousIndex() { @Override @Deprecated public void set(@Nullable T object) { - throw new UnsupportedOperationException("Replacing and element is not supported."); + throw new UnsupportedOperationException("Replacing an element is not supported."); } } From dd85ccf3d97e45b61721a5ad8e9f72f8054f0fa6 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Fri, 10 Nov 2017 00:38:46 +0530 Subject: [PATCH 1087/2110] Added the and() function in RealmQuery Purely added it for syntactic sugar. Merely makes query reading easier. --- .../src/main/java/io/realm/RealmQuery.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 89fafb6b95..b274bbbe1f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1519,6 +1519,18 @@ public RealmQuery or() { return orWithoutThreadValidation(); } + + /** + * Logical-and two conditions + * Technically, does nothing. Intended purely for syntactic sugar + * + * @return the query object + */ + public RealmQuery and() { + realm.checkIfValid(); + + return this; + } private RealmQuery orWithoutThreadValidation() { this.query.or(); From 5338680cd16f85f1aceb51c79b30954d5fadbb4f Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Fri, 10 Nov 2017 00:47:12 +0530 Subject: [PATCH 1088/2110] Repositioned the code to make it cleaner --- .../src/main/java/io/realm/RealmQuery.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index b274bbbe1f..aeaaccfba9 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1519,6 +1519,11 @@ public RealmQuery or() { return orWithoutThreadValidation(); } + + private RealmQuery orWithoutThreadValidation() { + this.query.or(); + return this; + } /** * Logical-and two conditions @@ -1532,11 +1537,6 @@ public RealmQuery and() { return this; } - private RealmQuery orWithoutThreadValidation() { - this.query.or(); - return this; - } - /** * Negate condition. * From 683e09f9c4749ccde05cdbecfb32788dbd55b1bf Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 10 Nov 2017 23:31:52 +0100 Subject: [PATCH 1089/2110] Fix leaking file handlers. (#5523) --- CHANGELOG.md | 15 ++-- realm-transformer/build.gradle | 2 +- .../realm/transformer/ManagedClassPool.groovy | 82 +++++++++++++++++++ .../realm/transformer/RealmTransformer.groovy | 42 +--------- 4 files changed, 96 insertions(+), 45 deletions(-) create mode 100644 realm-transformer/src/main/groovy/io/realm/transformer/ManagedClassPool.groovy diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c40fefa54..b2342ab345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,15 @@ -## 4.1.1 (2017-10-27) +## 4.1.2 (YYYY-MM-DD) -### Breaking Changes +### Bug Fixes -### Enhancements +* Leaked file handler in the Realm Transformer (#5521) + +### Internal + +* Updated JavaAssist to 3.22.0-GA + + +## 4.1.1 (2017-10-27) ### Bug Fixes @@ -14,8 +21,6 @@ * Updated Realm Sync to 2.1.0 -### Credits - ## 4.1.0 (2017-10-20) diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 9553214093..a5f4d48de7 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -59,7 +59,7 @@ dependencies { compile gradleApi() compile "io.realm:realm-annotations:${version}" provided 'com.android.tools.build:gradle:2.1.0' - compile 'org.javassist:javassist:3.20.0-GA' + compile 'org.javassist:javassist:3.22.0-GA' testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { exclude module: 'groovy-all' diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/ManagedClassPool.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/ManagedClassPool.groovy new file mode 100644 index 0000000000..c3a0389d26 --- /dev/null +++ b/realm-transformer/src/main/groovy/io/realm/transformer/ManagedClassPool.groovy @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.transformer + +import com.android.build.api.transform.TransformInput +import javassist.ClassPath +import javassist.ClassPool + +/** + * This class is a wrapper around JavaAssists {@code ClassPool} class that allows for correct cleanup + * of the resources used. + */ +@SuppressWarnings("GroovyUnusedDeclaration") +class ManagedClassPool extends ClassPool implements Closeable { + + def List pathElements = new ArrayList() + + /** + * Constructor for creating and populating the JavAssist class pool. + * Remember to call {@link #close()} when done with it to avoid leaking file resources + * + * @param inputs the inputs provided by the Transform API + * @param referencedInputs the referencedInputs provided by the Transform API + * @return the populated ClassPool instance + */ + ManagedClassPool(Collection inputs, Collection referencedInputs) { + // Don't use ClassPool.getDefault(). Doing consecutive builds in the same run (e.g. debug+release) + // will use a cached object and all the classes will be frozen. + super(null) + appendSystemPath() + + inputs.each { + it.directoryInputs.each { + pathElements.add(appendClassPath(it.file.absolutePath)) + } + + it.jarInputs.each { + pathElements.add(appendClassPath(it.file.absolutePath)) + } + } + + referencedInputs.each { + it.directoryInputs.each { + pathElements.add(appendClassPath(it.file.absolutePath)) + } + + it.jarInputs.each { + pathElements.add(appendClassPath(it.file.absolutePath)) + } + } + } + + /** + * Detach all ClassPath elements, effectively closing the class pool. + */ + @Override + void close() throws IOException { + // Cleanup class pool. Internally it keeps a list of JarFile references that are only + // cleaned up if the the ClassPath element wrapping it is manually removed. + // See https://github.com/jboss-javassist/javassist/issues/165 + def iter = pathElements.iterator() + while (iter.hasNext()) { + def cp = iter.next() + removeClassPath(cp) + iter.remove() + } + } +} diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index bd99fb271d..957581a6cf 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -22,7 +22,6 @@ import com.google.common.collect.ImmutableSet import com.google.common.collect.Sets import com.google.common.io.Files import groovy.io.FileType -import io.realm.annotations.Ignore import io.realm.annotations.RealmClass import javassist.ClassPool import javassist.CtClass @@ -30,7 +29,6 @@ import org.gradle.api.Project import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.lang.reflect.Modifier import java.util.jar.JarFile import java.util.regex.Pattern @@ -89,7 +87,7 @@ class RealmTransformer extends Transform { def allClassNames = merge(inputClassNames, referencedClassNames); // Create and populate the Javassist class pool - ClassPool classPool = createClassPool(inputs, referencedInputs) + ClassPool classPool = new ManagedClassPool(inputs, referencedInputs) // Append android.jar to class pool. We don't need the class names of them but only the class in the pool for // javassist. See https://github.com/realm/realm-java/issues/2703. addBootClassesToClassPool(classPool) @@ -148,6 +146,7 @@ class RealmTransformer extends Transform { logger.debug "Realm Transform time: ${toc-tic} milliseconds" this.sendAnalytics(inputs, inputModelClasses) + classPool.close() } /** @@ -187,42 +186,6 @@ class RealmTransformer extends Transform { } } - /** - * Creates and populates the Javassist class pool. - * - * @param inputs the inputs provided by the Transform API - * @param referencedInputs the referencedInputs provided by the Transform API - * @return the populated ClassPool instance - */ - private ClassPool createClassPool(Collection inputs, Collection referencedInputs) { - // Don't use ClassPool.getDefault(). Doing consecutive builds in the same run (e.g. debug+release) - // will use a cached object and all the classes will be frozen. - ClassPool classPool = new ClassPool(null) - classPool.appendSystemPath() - - inputs.each { - it.directoryInputs.each { - classPool.appendClassPath(it.file.absolutePath) - } - - it.jarInputs.each { - classPool.appendClassPath(it.file.absolutePath) - } - } - - referencedInputs.each { - it.directoryInputs.each { - classPool.appendClassPath(it.file.absolutePath) - } - - it.jarInputs.each { - classPool.appendClassPath(it.file.absolutePath) - } - } - - return classPool - } - private static Set getClassNames(Collection inputs) { Set classNames = new HashSet() @@ -255,6 +218,7 @@ class RealmTransformer extends Transform { .replace('\\' as char , '.' as char) classNames.add(className) } + jarFile.close() // Crash transformer if this fails } } return classNames From b5da772d296eba9a84a06f430c8608879b0b8834 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Sat, 11 Nov 2017 04:20:12 +0530 Subject: [PATCH 1090/2110] Updated CHANGELOG.md Updated Java Doc for and() function. --- CHANGELOG.md | 1 + realm/realm-library/src/main/java/io/realm/RealmQuery.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a31b73df9..c9acc71d39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements * Added support for using non-encrypted Realms in multiple processes. Some caveats apply. Read [doc](https://realm.io/docs/java/latest/#multiprocess) for more info (#1091). +* Added the and() function to `RealmQuery` in order to improve readability. ### Bug Fixes diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index aeaaccfba9..353da2a112 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1527,7 +1527,7 @@ private RealmQuery orWithoutThreadValidation() { /** * Logical-and two conditions - * Technically, does nothing. Intended purely for syntactic sugar + * Realm automatically applies logical-and between all query statements, so this is intended only as a mean to increase readability. * * @return the query object */ From 3cbdaaeb69a6ba29940233f4433d515d1aaeb88e Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Sat, 11 Nov 2017 04:32:16 +0530 Subject: [PATCH 1091/2110] Added tests for and() in RealmQuery --- .../java/io/realm/RealmQueryTests.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 35456d248c..ece9fabd6c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -199,6 +199,7 @@ private enum ThreadConfinedMethods { BEGIN_GROUP, END_GROUP, OR, + AND, NOT, IS_NULL, IS_NOT_NULL, @@ -315,6 +316,7 @@ private static void callThreadConfinedMethod(RealmQuery query, ThreadConfined case BEGIN_GROUP: query.beginGroup(); break; case END_GROUP: query.endGroup(); break; case OR: query.or(); break; + case AND: query.and(); break; case NOT: query.not(); break; case IS_NULL: query.isNull( AllJavaTypes.FIELD_DATE); break; case IS_NOT_NULL: query.isNotNull( AllJavaTypes.FIELD_DATE); break; @@ -583,6 +585,19 @@ public void and_implicit() { resultList = query.between(AllTypes.FIELD_LONG, 1, 100).findAll(); assertEquals(1, resultList.size()); } + + @Test + public void and_explicit() { + populateTestRealm(realm, 200); + + RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 31.2345f); + RealmResults resultList = query.and().between(AllTypes.FIELD_LONG, 1, 10).findAll(); + assertEquals(0, resultList.size()); + + query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 81.2345f); + resultList = query.and().between(AllTypes.FIELD_LONG, 1, 100).findAll(); + assertEquals(1, resultList.size()); + } @Test public void lessThan() { From 6893105fc7c9a4261374f9cc433b19f0fe09cb7c Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 13 Nov 2017 16:55:25 +0000 Subject: [PATCH 1092/2110] CI to always create the zip file even if the tests passes --- Jenkinsfile | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a5b2845569..18cd311cef 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -87,7 +87,6 @@ try { stage('Run instrumented tests') { lock("${env.NODE_NAME}-android") { - boolean archiveLog = true String backgroundPid try { backgroundPid = startLogCatCollector() @@ -95,7 +94,7 @@ try { gradle('realm', "${instrumentationTestTarget}") archiveLog = false; } finally { - stopLogCatCollector(backgroundPid, archiveLog) + stopLogCatCollector(backgroundPid) storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' } } @@ -162,14 +161,12 @@ def String startLogCatCollector() { return readFile("pid").trim() } -def stopLogCatCollector(String backgroundPid, boolean archiveLog) { +def stopLogCatCollector(String backgroundPid) { sh "kill ${backgroundPid}" if (archiveLog) { - zip([ - 'zipFile': 'logcat.zip', + 'zipFile': 'logcat.zip', 'archive': true, 'glob' : 'logcat.txt' - ]) } sh 'rm logcat.txt' } From b40917601575d6e92aa2cdcbc0aca0c148b00382 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 13 Nov 2017 17:12:52 +0000 Subject: [PATCH 1093/2110] fixing script --- Jenkinsfile | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 18cd311cef..f511eb1188 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -92,7 +92,6 @@ try { backgroundPid = startLogCatCollector() forwardAdbPorts() gradle('realm', "${instrumentationTestTarget}") - archiveLog = false; } finally { stopLogCatCollector(backgroundPid) storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' @@ -163,11 +162,11 @@ def String startLogCatCollector() { def stopLogCatCollector(String backgroundPid) { sh "kill ${backgroundPid}" - if (archiveLog) { - 'zipFile': 'logcat.zip', - 'archive': true, - 'glob' : 'logcat.txt' - } + zip([ + 'zipFile': 'logcat.zip', + 'archive': true, + 'glob' : 'logcat.txt' + ]) sh 'rm logcat.txt' } From 0bcb9559dddf071eeee0c9c3d3a2946f3a15eb51 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 16 Nov 2017 11:10:02 +0800 Subject: [PATCH 1094/2110] Support importing primitive list from JSON (#5505) Close #5362 --- CHANGELOG.md | 3 +- .../processor/RealmProxyClassGenerator.java | 12 +- .../main/java/io/realm/processor/Utils.java | 14 +- .../io/realm/AllTypesRealmProxy.java | 42 +-- .../io/realm/BooleansRealmProxy.java | 2 +- .../io/realm/NullTypesRealmProxy.java | 82 +++--- .../resources/io/realm/SimpleRealmProxy.java | 2 +- .../java/io/realm/RealmJsonTests.java | 131 ++++++++- .../io/realm/entities/PrimitiveListTypes.java | 145 ++++++++++ .../src/main/java/io/realm/ProxyUtils.java | 257 ++++++++++++++++++ .../java/io/realm/internal/ProxyUtils.java | 37 --- 11 files changed, 612 insertions(+), 115 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/PrimitiveListTypes.java create mode 100644 realm/realm-library/src/main/java/io/realm/ProxyUtils.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/ProxyUtils.java diff --git a/CHANGELOG.md b/CHANGELOG.md index de72b9094d..495fbd3032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements * Added support for using non-encrypted Realms in multiple processes. Some caveats apply. Read [doc](https://realm.io/docs/java/latest/#multiprocess) for more info (#1091). +* Added support for importing primitive lists from JSON (#5362). ### Bug Fixes @@ -79,7 +80,7 @@ The internal file format has been upgraded. Opening an older Realm will upgrade ### Enhancements * [ObjectServer] `SyncUserInfo` now also exposes a users metadata using `SyncUserInfo.getMetadata()` -* `RealmList` can now contain `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` values. [Queries](https://github.com/realm/realm-java/issues/5361) and [Importing primitive lists from JSON](https://github.com/realm/realm-java/issues/5361) are not supported yet. +* `RealmList` can now contain `String`, `byte[]`, `Boolean`, `Long`, `Integer`, `Short`, `Byte`, `Double`, `Float` and `Date` values. [Queries](https://github.com/realm/realm-java/issues/5361) and [Importing primitive lists from JSON](https://github.com/realm/realm-java/issues/5362) are not supported yet. * Added support for lists of primitives in `RealmObjectSchema` with `addRealmListField(String fieldName, Class primitiveType)` * Added support for lists of primitives in `DynamicRealmObject` with `setList(String fieldName, RealmList list)` and `getList(String fieldName, Class primitiveType)`. * Minor performance improvement when copy/insert objects into Realm. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index c20a722be2..6faacbfa29 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -55,7 +55,7 @@ public class RealmProxyClassGenerator { "io.realm.internal.OsSchemaInfo", "io.realm.internal.OsObjectSchemaInfo", "io.realm.internal.Property", - "io.realm.internal.ProxyUtils", + "io.realm.ProxyUtils", "io.realm.internal.RealmObjectProxy", "io.realm.internal.Row", "io.realm.internal.Table", @@ -2006,9 +2006,8 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep writer); } else if (Utils.isRealmValueList(field)) { - // FIXME need to implement logic for value list fields. - writer.emitSingleLineComment(String.format(Locale.ENGLISH, - "TODO implement logic for value list %1$s.", field.getSimpleName())); + writer.emitStatement("ProxyUtils.setRealmListWithJsonObject(objProxy.%1$s(), json, \"%2$s\")", + metadata.getInternalGetter(fieldName), fieldName); } else if (Utils.isMutableRealmInteger(field)) { RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( "objProxy", @@ -2091,8 +2090,9 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer); } else if (Utils.isRealmValueList(field)) { - // FIXME need to implement logic for value list fields. - writer.emitSingleLineComment("TODO implement logic for value list."); + writer.emitStatement("objProxy.%1$s(ProxyUtils.createRealmListWithJsonStream(%2$s.class, reader))", + metadata.getInternalSetter(fieldName), + Utils.getRealmListType(field)); } else if (Utils.isMutableRealmInteger(field)) { RealmJsonTypeHelper.emitFillJavaTypeFromStream( "objProxy", diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java index 24d20531cd..7e7a274af1 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java @@ -10,6 +10,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ReferenceType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; @@ -218,7 +219,7 @@ public static boolean isRealmResults(VariableElement field) { // get the fully-qualified type name for the generic type of a RealmResults public static String getRealmResultsType(VariableElement field) { if (!Utils.isRealmResults(field)) { return null; } - DeclaredType type = getGenericTypeForContainer(field); + ReferenceType type = getGenericTypeForContainer(field); if (null == type) { return null; } return type.toString(); } @@ -226,14 +227,14 @@ public static String getRealmResultsType(VariableElement field) { // get the fully-qualified type name for the generic type of a RealmList public static String getRealmListType(VariableElement field) { if (!Utils.isRealmList(field)) { return null; } - DeclaredType type = getGenericTypeForContainer(field); + ReferenceType type = getGenericTypeForContainer(field); if (null == type) { return null; } return type.toString(); } // Note that, because subclassing subclasses of RealmObject is forbidden, // there is no need to deal with constructs like: RealmResults<? extends Foos<. - public static DeclaredType getGenericTypeForContainer(VariableElement field) { + public static ReferenceType getGenericTypeForContainer(VariableElement field) { TypeMirror fieldType = field.asType(); TypeKind kind = fieldType.getKind(); if (kind != TypeKind.DECLARED) { return null; } @@ -243,9 +244,10 @@ public static DeclaredType getGenericTypeForContainer(VariableElement field) { fieldType = args.get(0); kind = fieldType.getKind(); - if (kind != TypeKind.DECLARED) { return null; } + // We also support RealmList + if (kind != TypeKind.DECLARED && kind != TypeKind.ARRAY) { return null; } - return (DeclaredType) fieldType; + return (ReferenceType) fieldType; } /** @@ -265,7 +267,7 @@ public static String getFieldTypeSimpleName(VariableElement field) { /** * @return the simple type name for a field. */ - public static String getFieldTypeSimpleName(DeclaredType type) { + public static String getFieldTypeSimpleName(ReferenceType type) { return (null == type) ? null : getFieldTypeSimpleName(type.toString()); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index ad9ce265ac..fb00a17894 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.OsList; @@ -12,7 +13,6 @@ import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsSchemaInfo; import io.realm.internal.Property; -import io.realm.internal.ProxyUtils; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.Table; @@ -1041,16 +1041,16 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON } } } - // TODO implement logic for value list columnStringList. - // TODO implement logic for value list columnBinaryList. - // TODO implement logic for value list columnBooleanList. - // TODO implement logic for value list columnLongList. - // TODO implement logic for value list columnIntegerList. - // TODO implement logic for value list columnShortList. - // TODO implement logic for value list columnByteList. - // TODO implement logic for value list columnDoubleList. - // TODO implement logic for value list columnFloatList. - // TODO implement logic for value list columnDateList. + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnStringList(), json, "columnStringList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnBinaryList(), json, "columnBinaryList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnBooleanList(), json, "columnBooleanList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnLongList(), json, "columnLongList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnIntegerList(), json, "columnIntegerList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnShortList(), json, "columnShortList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnByteList(), json, "columnByteList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnDoubleList(), json, "columnDoubleList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnFloatList(), json, "columnFloatList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnDateList(), json, "columnDateList"); return obj; } @@ -1150,25 +1150,25 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r reader.endArray(); } } else if (name.equals("columnStringList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnStringList(ProxyUtils.createRealmListWithJsonStream(java.lang.String.class, reader)); } else if (name.equals("columnBinaryList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnBinaryList(ProxyUtils.createRealmListWithJsonStream(byte[].class, reader)); } else if (name.equals("columnBooleanList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnBooleanList(ProxyUtils.createRealmListWithJsonStream(java.lang.Boolean.class, reader)); } else if (name.equals("columnLongList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnLongList(ProxyUtils.createRealmListWithJsonStream(java.lang.Long.class, reader)); } else if (name.equals("columnIntegerList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnIntegerList(ProxyUtils.createRealmListWithJsonStream(java.lang.Integer.class, reader)); } else if (name.equals("columnShortList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnShortList(ProxyUtils.createRealmListWithJsonStream(java.lang.Short.class, reader)); } else if (name.equals("columnByteList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnByteList(ProxyUtils.createRealmListWithJsonStream(java.lang.Byte.class, reader)); } else if (name.equals("columnDoubleList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnDoubleList(ProxyUtils.createRealmListWithJsonStream(java.lang.Double.class, reader)); } else if (name.equals("columnFloatList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnFloatList(ProxyUtils.createRealmListWithJsonStream(java.lang.Float.class, reader)); } else if (name.equals("columnDateList")) { - // TODO implement logic for value list. + objProxy.realmSet$columnDateList(ProxyUtils.createRealmListWithJsonStream(java.util.Date.class, reader)); } else { reader.skipValue(); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index 77d04eae89..b9573557f8 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.OsList; @@ -12,7 +13,6 @@ import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsSchemaInfo; import io.realm.internal.Property; -import io.realm.internal.ProxyUtils; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.Table; diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index 2a7f2cf9d6..bef81c5e86 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.OsList; @@ -12,7 +13,6 @@ import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsSchemaInfo; import io.realm.internal.Property; -import io.realm.internal.ProxyUtils; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.Table; @@ -1985,26 +1985,26 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO objProxy.realmSet$fieldObjectNull(fieldObjectNullObj); } } - // TODO implement logic for value list fieldStringListNotNull. - // TODO implement logic for value list fieldStringListNull. - // TODO implement logic for value list fieldBinaryListNotNull. - // TODO implement logic for value list fieldBinaryListNull. - // TODO implement logic for value list fieldBooleanListNotNull. - // TODO implement logic for value list fieldBooleanListNull. - // TODO implement logic for value list fieldLongListNotNull. - // TODO implement logic for value list fieldLongListNull. - // TODO implement logic for value list fieldIntegerListNotNull. - // TODO implement logic for value list fieldIntegerListNull. - // TODO implement logic for value list fieldShortListNotNull. - // TODO implement logic for value list fieldShortListNull. - // TODO implement logic for value list fieldByteListNotNull. - // TODO implement logic for value list fieldByteListNull. - // TODO implement logic for value list fieldDoubleListNotNull. - // TODO implement logic for value list fieldDoubleListNull. - // TODO implement logic for value list fieldFloatListNotNull. - // TODO implement logic for value list fieldFloatListNull. - // TODO implement logic for value list fieldDateListNotNull. - // TODO implement logic for value list fieldDateListNull. + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldStringListNotNull(), json, "fieldStringListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldStringListNull(), json, "fieldStringListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldBinaryListNotNull(), json, "fieldBinaryListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldBinaryListNull(), json, "fieldBinaryListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldBooleanListNotNull(), json, "fieldBooleanListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldBooleanListNull(), json, "fieldBooleanListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldLongListNotNull(), json, "fieldLongListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldLongListNull(), json, "fieldLongListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldIntegerListNotNull(), json, "fieldIntegerListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldIntegerListNull(), json, "fieldIntegerListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldShortListNotNull(), json, "fieldShortListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldShortListNull(), json, "fieldShortListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldByteListNotNull(), json, "fieldByteListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldByteListNull(), json, "fieldByteListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldDoubleListNotNull(), json, "fieldDoubleListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldDoubleListNull(), json, "fieldDoubleListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldFloatListNotNull(), json, "fieldFloatListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldFloatListNull(), json, "fieldFloatListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldDateListNotNull(), json, "fieldDateListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldDateListNull(), json, "fieldDateListNull"); return obj; } @@ -2177,45 +2177,45 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader objProxy.realmSet$fieldObjectNull(fieldObjectNullObj); } } else if (name.equals("fieldStringListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldStringListNotNull(ProxyUtils.createRealmListWithJsonStream(java.lang.String.class, reader)); } else if (name.equals("fieldStringListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldStringListNull(ProxyUtils.createRealmListWithJsonStream(java.lang.String.class, reader)); } else if (name.equals("fieldBinaryListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldBinaryListNotNull(ProxyUtils.createRealmListWithJsonStream(byte[].class, reader)); } else if (name.equals("fieldBinaryListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldBinaryListNull(ProxyUtils.createRealmListWithJsonStream(byte[].class, reader)); } else if (name.equals("fieldBooleanListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldBooleanListNotNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Boolean.class, reader)); } else if (name.equals("fieldBooleanListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldBooleanListNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Boolean.class, reader)); } else if (name.equals("fieldLongListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldLongListNotNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Long.class, reader)); } else if (name.equals("fieldLongListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldLongListNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Long.class, reader)); } else if (name.equals("fieldIntegerListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldIntegerListNotNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Integer.class, reader)); } else if (name.equals("fieldIntegerListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldIntegerListNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Integer.class, reader)); } else if (name.equals("fieldShortListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldShortListNotNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Short.class, reader)); } else if (name.equals("fieldShortListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldShortListNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Short.class, reader)); } else if (name.equals("fieldByteListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldByteListNotNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Byte.class, reader)); } else if (name.equals("fieldByteListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldByteListNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Byte.class, reader)); } else if (name.equals("fieldDoubleListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldDoubleListNotNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Double.class, reader)); } else if (name.equals("fieldDoubleListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldDoubleListNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Double.class, reader)); } else if (name.equals("fieldFloatListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldFloatListNotNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Float.class, reader)); } else if (name.equals("fieldFloatListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldFloatListNull(ProxyUtils.createRealmListWithJsonStream(java.lang.Float.class, reader)); } else if (name.equals("fieldDateListNotNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldDateListNotNull(ProxyUtils.createRealmListWithJsonStream(java.util.Date.class, reader)); } else if (name.equals("fieldDateListNull")) { - // TODO implement logic for value list. + objProxy.realmSet$fieldDateListNull(ProxyUtils.createRealmListWithJsonStream(java.util.Date.class, reader)); } else { reader.skipValue(); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 4d882b466a..4473a97537 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; import io.realm.internal.OsList; @@ -12,7 +13,6 @@ import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsSchemaInfo; import io.realm.internal.Property; -import io.realm.internal.ProxyUtils; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.Table; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index c5ea5c07a1..d1edfba2a0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -43,6 +43,8 @@ import java.util.GregorianCalendar; import java.util.TimeZone; +import javax.annotation.Nullable; + import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.AnnotationTypes; @@ -51,6 +53,7 @@ import io.realm.entities.NoPrimaryKeyNullTypes; import io.realm.entities.NullTypes; import io.realm.entities.OwnerPrimaryKey; +import io.realm.entities.PrimitiveListTypes; import io.realm.entities.RandomPrimaryKey; import io.realm.exceptions.RealmException; import io.realm.internal.Util; @@ -60,6 +63,7 @@ import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -401,7 +405,15 @@ public void createFromJson_respectDefaultValues() throws JSONException { assertEquals(DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE, managedObj.getFieldDouble(), 0d); assertEquals(DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE, managedObj.isFieldBoolean()); assertEquals(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE, managedObj.getFieldDate()); - assertTrue(Arrays.equals(DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE, managedObj.getFieldBinary())); + assertArrayEquals(DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE, managedObj.getFieldBinary()); + assertArrayEquals(DefaultValueOfField.FIELD_BYTE_LIST_DEFAULT_VALUE.toArray(), managedObj.getFieldByteList().toArray()); + assertArrayEquals(DefaultValueOfField.FIELD_SHORT_LIST_DEFAULT_VALUE.toArray(), managedObj.getFieldShortList().toArray()); + assertArrayEquals(DefaultValueOfField.FIELD_INTEGER_LIST_DEFAULT_VALUE.toArray(), managedObj.getFieldIntegerList().toArray()); + assertArrayEquals(DefaultValueOfField.FIELD_LONG_LIST_DEFAULT_VALUE.toArray(), managedObj.getFieldLongList().toArray()); + assertArrayEquals(DefaultValueOfField.FIELD_BOOLEAN_LIST_DEFAULT_VALUE.toArray(), managedObj.getFieldBooleanList().toArray()); + assertArrayEquals(DefaultValueOfField.FIELD_BINARY_LIST_DEFAULT_VALUE.toArray(), managedObj.getFieldBinaryList().toArray()); + assertArrayEquals(DefaultValueOfField.FIELD_STRING_LIST_DEFAULT_VALUE.toArray(), managedObj.getFieldStringList().toArray()); + assertArrayEquals(DefaultValueOfField.FIELD_DATE_LIST_DEFAULT_VALUE.toArray(), managedObj.getFieldDateList().toArray()); assertEquals(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE, managedObj.getFieldObject().getFieldInt()); assertEquals(1, managedObj.getFieldList().size()); assertEquals(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE, managedObj.getFieldList().first().getFieldInt()); @@ -1709,4 +1721,121 @@ public void createObjectFromJson_objectWithPrimaryKeySetValueDirectlyFromStream( assertEquals(1, owners.get(1).getId()); assertEquals("bar", owners.get(1).getName()); } + + private void testPrimitiveListWithValues(String fieldName, Object[] values) throws JSONException, IOException { + testPrimitiveListWithValues(fieldName, values, values); + } + + private void testPrimitiveListWithValues(String fieldName, @Nullable Object[] valuesToSave, Object[] valuesToLoad) + throws JSONException, IOException { + JSONObject jsonObject = new JSONObject(); + JSONArray jsonArray = valuesToSave != null ? new JSONArray(valuesToSave) : null; + jsonObject.put(fieldName, jsonArray); + + // Test from JSONObject + realm.beginTransaction(); + PrimitiveListTypes primitiveListTypes = realm.createObjectFromJson(PrimitiveListTypes.class, jsonObject); + realm.commitTransaction(); + assertNotNull(primitiveListTypes); + assertArrayEquals(valuesToLoad, primitiveListTypes.getList(fieldName).toArray()); + + // Test from JSONStream + realm.beginTransaction(); + primitiveListTypes = realm.createObjectFromJson(PrimitiveListTypes.class, convertJsonObjectToStream(jsonObject)); + realm.commitTransaction(); + assertNotNull(primitiveListTypes); + assertArrayEquals(valuesToLoad, primitiveListTypes.getList(fieldName).toArray()); + } + + @Test + public void createObjectFromJson_primitiveList_mixedValues() throws JSONException, IOException { + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_STRING_LIST, new String[] {"a", null, "bc"}); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_BOOLEAN_LIST, new Boolean[] {true, null, false}); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_DOUBLE_LIST, new Double[] {1.0d, null, 2.0d}); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_FLOAT_LIST, new Float[] {1.0f, null, 2.0f}); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_BYTE_LIST, new Byte[] {1, null, 2}); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_SHORT_LIST, new Short[] {1, null, 2}); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_INT_LIST, new Integer[] {1, null, 2}); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_LONG_LIST, new Long[] {1L, null, 2L}); + + // Date as integer + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_DATE_LIST, + new Integer[] {0, null, 1}, + new Date[] {new Date(0), null, new Date(1)}); + // Date as String + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_DATE_LIST, + new String [] {"/Date(1000)/", null, "/Date(2000)/"}, + new Date[] {new Date(1000), null, new Date(2000)}); + // Date as String timezone + // Oct 03 2015 14:45.33 + Calendar cal = GregorianCalendar.getInstance(); + cal.setTimeZone(TimeZone.getTimeZone("Australia/West")); + cal.set(2015, Calendar.OCTOBER, 3, 14, 45, 33); + cal.set(Calendar.MILLISECOND, 376); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_DATE_LIST, + new String [] {"/Date(1443854733376+0800)/", null}, + new Date[] {cal.getTime(), null}); + + + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_BINARY_LIST, + new String[] {new String(Base64.encode(new byte[] {1, 2, 3}, Base64.DEFAULT), UTF_8), + null, new String(Base64.encode(new byte[] {4, 5, 6}, Base64.DEFAULT), UTF_8)}, + new byte[][] {new byte[]{1, 2, 3}, null, new byte[]{4, 5, 6}}); + } + + // Null list will be saved as empty list since We don't support nullable RealmList + @Test + public void createObjectFromJson_primitiveList_nullList() throws IOException, JSONException { + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_STRING_LIST, null, new String[0]); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_BOOLEAN_LIST, null, new Boolean[0]); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_DOUBLE_LIST, null, new Double[0]); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_FLOAT_LIST, null, new Float[0]); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_BYTE_LIST, null, new Byte[0]); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_SHORT_LIST, null, new Short[0]); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_INT_LIST, null, new Integer[0]); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_LONG_LIST, null, new Long[0]); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_DATE_LIST, null, new Date[0]); + testPrimitiveListWithValues(PrimitiveListTypes.FIELD_BYTE_LIST, null, new byte[0][]); + } + + private void testRequiredPrimitiveListWithNullValue(String fieldName) throws JSONException, IOException { + JSONObject jsonObject = new JSONObject(); + JSONArray jsonArray =new JSONArray(); + jsonArray.put(null); + jsonObject.put(fieldName, jsonArray); + + // Test from JSONObject + realm.beginTransaction(); + try { + realm.createObjectFromJson(PrimitiveListTypes.class, jsonObject); + fail(); + } catch (IllegalArgumentException ignored) { + } finally { + realm.cancelTransaction(); + } + + // Test from JSONStream + realm.beginTransaction(); + try { + realm.createObjectFromJson(PrimitiveListTypes.class, convertJsonObjectToStream(jsonObject)); + fail(); + } catch (IllegalArgumentException ignored) { + } finally { + realm.cancelTransaction(); + } + } + + @Test + public void createObjectFromJson_primitiveList_nullValueForRequiredField() throws IOException, JSONException { + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_STRING_LIST); + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_BOOLEAN_LIST); + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_DOUBLE_LIST); + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_FLOAT_LIST); + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_BYTE_LIST); + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_SHORT_LIST); + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_INT_LIST); + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_LONG_LIST); + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_DATE_LIST); + testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_BYTE_LIST); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimitiveListTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimitiveListTypes.java new file mode 100644 index 0000000000..9e80a1b946 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimitiveListTypes.java @@ -0,0 +1,145 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import java.util.Date; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.Required; + +public class PrimitiveListTypes extends RealmObject { + public static final String FIELD_STRING_LIST = "stringList"; + public static final String FIELD_BINARY_LIST = "binaryList"; + public static final String FIELD_BOOLEAN_LIST = "booleanList"; + public static final String FIELD_DOUBLE_LIST = "doubleList"; + public static final String FIELD_FLOAT_LIST = "floatList"; + public static final String FIELD_DATE_LIST = "dateList"; + public static final String FIELD_BYTE_LIST = "byteList"; + public static final String FIELD_SHORT_LIST = "shortList"; + public static final String FIELD_INT_LIST = "intList"; + public static final String FIELD_LONG_LIST = "longList"; + public static final String FIELD_REQUIRED_STRING_LIST = "requiredStringList"; + public static final String FIELD_REQUIRED_BINARY_LIST = "requiredBinaryList"; + public static final String FIELD_REQUIRED_BOOLEAN_LIST = "requiredBooleanList"; + public static final String FIELD_REQUIRED_DOUBLE_LIST = "requiredDoubleList"; + public static final String FIELD_REQUIRED_FLOAT_LIST = "requiredFloatList"; + public static final String FIELD_REQUIRED_DATE_LIST = "requiredDateList"; + public static final String FIELD_REQUIRED_BYTE_LIST = "requiredByteList"; + public static final String FIELD_REQUIRED_SHORT_LIST = "requiredShortList"; + public static final String FIELD_REQUIRED_INT_LIST = "requiredIntList"; + public static final String FIELD_REQUIRED_LONG_LIST = "requiredLongList"; + + @SuppressWarnings("unused") + private RealmList stringList; + @SuppressWarnings("unused") + private RealmList binaryList; + @SuppressWarnings("unused") + private RealmList booleanList; + @SuppressWarnings("unused") + private RealmList doubleList; + @SuppressWarnings("unused") + private RealmList floatList; + @SuppressWarnings("unused") + private RealmList dateList; + @SuppressWarnings("unused") + private RealmList byteList; + @SuppressWarnings("unused") + private RealmList shortList; + @SuppressWarnings("unused") + private RealmList intList; + @SuppressWarnings("unused") + private RealmList longList; + + @SuppressWarnings("unused") + @Required + private RealmList requiredStringList; + @SuppressWarnings("unused") + @Required + private RealmList requiredBinaryList; + @SuppressWarnings("unused") + @Required + private RealmList requiredBooleanList; + @SuppressWarnings("unused") + @Required + private RealmList requiredDoubleList; + @SuppressWarnings("unused") + @Required + private RealmList requiredFloatList; + @SuppressWarnings("unused") + @Required + private RealmList requiredDateList; + @SuppressWarnings("unused") + @Required + private RealmList requiredByteList; + @SuppressWarnings("unused") + @Required + private RealmList requiredShortList; + @SuppressWarnings("unused") + @Required + private RealmList requiredIntList; + @SuppressWarnings("unused") + @Required + private RealmList requiredLongList; + + public RealmList getList(String fieldName) { + switch (fieldName) { + case FIELD_STRING_LIST: + return stringList; + case FIELD_BINARY_LIST: + return binaryList; + case FIELD_BOOLEAN_LIST: + return booleanList; + case FIELD_DOUBLE_LIST: + return doubleList; + case FIELD_FLOAT_LIST: + return floatList; + case FIELD_DATE_LIST: + return dateList; + case FIELD_BYTE_LIST: + return byteList; + case FIELD_SHORT_LIST: + return shortList; + case FIELD_INT_LIST: + return intList; + case FIELD_LONG_LIST: + return longList; + case FIELD_REQUIRED_STRING_LIST: + return requiredStringList; + case FIELD_REQUIRED_BINARY_LIST: + return requiredBinaryList; + case FIELD_REQUIRED_BOOLEAN_LIST: + return requiredBooleanList; + case FIELD_REQUIRED_DOUBLE_LIST: + return requiredDoubleList; + case FIELD_REQUIRED_FLOAT_LIST: + return requiredFloatList; + case FIELD_REQUIRED_DATE_LIST: + return requiredDateList; + case FIELD_REQUIRED_BYTE_LIST: + return requiredByteList; + case FIELD_REQUIRED_SHORT_LIST: + return requiredShortList; + case FIELD_REQUIRED_INT_LIST: + return requiredIntList; + case FIELD_REQUIRED_LONG_LIST: + return requiredLongList; + default: + throw new IllegalArgumentException("Unknown field name: '" + fieldName + "'."); + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/ProxyUtils.java b/realm/realm-library/src/main/java/io/realm/ProxyUtils.java new file mode 100644 index 0000000000..e4d4c3648b --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/ProxyUtils.java @@ -0,0 +1,257 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.JsonReader; +import android.util.JsonToken; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Date; +import java.util.Locale; + +import javax.annotation.Nullable; + +import io.realm.internal.OsList; +import io.realm.internal.android.JsonUtils; + +class ProxyUtils { + + /** + * Called by proxy to set the managed {@link RealmList} according to the given {@link JSONObject}. + * + * @param realmList the managed {@link RealmList}. + * @param jsonObject the {@link JSONObject} which may contain the data of the list to be set. + * @param fieldName the field name of the {@link RealmList}. + * @param type of the {@link RealmList}. + * @throws JSONException if it fails to parse JSON. + */ + static void setRealmListWithJsonObject( + RealmList realmList, JSONObject jsonObject, String fieldName) throws JSONException { + if (!jsonObject.has(fieldName)) { + return; + } + + OsList osList = realmList.getOsList(); + if (jsonObject.isNull(fieldName)) { + osList.removeAll(); + return; + } + + JSONArray jsonArray = jsonObject.getJSONArray(fieldName); + osList.removeAll(); + int arraySize = jsonArray.length(); + + if (realmList.clazz == Boolean.class) { + for (int i = 0; i < arraySize; i++) { + if (jsonArray.isNull(i)) { + osList.addNull(); + } else { + osList.addBoolean(jsonArray.getBoolean(i)); + } + } + } else if (realmList.clazz == Float.class) { + for (int i = 0; i < arraySize; i++) { + if (jsonArray.isNull(i)) { + osList.addNull(); + } else { + osList.addFloat((float) jsonArray.getDouble(i)); + } + } + } else if (realmList.clazz == Double.class) { + for (int i = 0; i < arraySize; i++) { + if (jsonArray.isNull(i)) { + osList.addNull(); + } else { + osList.addDouble(jsonArray.getDouble(i)); + } + } + } else if (realmList.clazz == String.class) { + for (int i = 0; i < arraySize; i++) { + if (jsonArray.isNull(i)) { + osList.addNull(); + } else { + osList.addString(jsonArray.getString(i)); + } + } + } else if (realmList.clazz == byte[].class) { + for (int i = 0; i < arraySize; i++) { + if (jsonArray.isNull(i)) { + osList.addNull(); + } else { + osList.addBinary(JsonUtils.stringToBytes(jsonArray.getString(i))); + } + } + } else if (realmList.clazz == Date.class ) { + for (int i = 0; i < arraySize; i++) { + if (jsonArray.isNull(i)) { + osList.addNull(); + continue; + } + + Object timestamp = jsonArray.get(i); + if (timestamp instanceof String) { + osList.addDate(JsonUtils.stringToDate((String) timestamp)); + } else { + osList.addDate(new Date(jsonArray.getLong(i))); + } + } + } else if (realmList.clazz == Long.class || realmList.clazz == Integer.class || + realmList.clazz == Short.class || realmList.clazz == Byte.class) { + for (int i = 0; i < arraySize; i++) { + if (jsonArray.isNull(i)) { + osList.addNull(); + } else { + osList.addLong(jsonArray.getLong(i)); + } + } + } else { + throwWrongElementType(realmList.clazz); + } + } + + /** + * Called by proxy to create a unmanaged {@link RealmList} according to the given {@link JsonReader}. + * + * @param elementClass the type of the {@link RealmList}. + * @param jsonReader the JSON stream to be parsed which may contain the data of the list to be set. + * @param type of the {@link RealmList}. + * @throws IOException if it fails to parse JSON stream. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + static RealmList createRealmListWithJsonStream(Class elementClass, JsonReader jsonReader) throws IOException { + + if (jsonReader.peek() == null) { + jsonReader.skipValue(); + return null; + } + + jsonReader.beginArray(); + RealmList realmList = new RealmList(); + + if (elementClass == Boolean.class) { + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else { + realmList.add(jsonReader.nextBoolean()); + } + } + } else if (elementClass == Float.class) { + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else { + realmList.add((float) jsonReader.nextDouble()); + } + } + } else if (elementClass == Double.class) { + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else { + realmList.add(jsonReader.nextDouble()); + } + } + } else if (elementClass == String.class) { + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else { + realmList.add(jsonReader.nextString()); + } + } + } else if (elementClass == byte[].class) { + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else { + realmList.add(JsonUtils.stringToBytes(jsonReader.nextString())); + } + } + } else if (elementClass == Date.class) { + while (jsonReader.hasNext()) { + JsonToken token = jsonReader.peek(); + if (token == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else if (token == JsonToken.NUMBER) { + realmList.add(new Date(jsonReader.nextLong())); + } else { + realmList.add(JsonUtils.stringToDate(jsonReader.nextString())); + } + } + } else if (elementClass == Long.class) { + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else { + realmList.add(jsonReader.nextLong()); + } + } + } else if (elementClass == Integer.class) { + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else { + realmList.add((int)jsonReader.nextLong()); + } + } + } else if (elementClass == Short.class) { + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else { + realmList.add((short)jsonReader.nextLong()); + } + } + } else if (elementClass == Byte.class) { + while (jsonReader.hasNext()) { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + realmList.add(null); + } else { + realmList.add((byte)jsonReader.nextLong()); + } + } + } else { + throwWrongElementType(elementClass); + } + + jsonReader.endArray(); + + return realmList; + } + + private static void throwWrongElementType(@Nullable Class clazz) { + throw new IllegalArgumentException(String.format(Locale.ENGLISH, "Element type '%s' is not handled.", + clazz)); + } + +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/ProxyUtils.java b/realm/realm-library/src/main/java/io/realm/internal/ProxyUtils.java deleted file mode 100644 index 6ea3d1c706..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/ProxyUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ -package io.realm.internal; - -import java.util.Map; - -import io.realm.RealmFieldType; -import io.realm.exceptions.RealmMigrationNeededException; - -public class ProxyUtils { - - public static void verifyField(OsSharedRealm sharedRealm, Map columnTypes, String fieldName, RealmFieldType fieldType, String fieldSimpleType) { - if (!columnTypes.containsKey(fieldName)) { - throw new RealmMigrationNeededException( - sharedRealm.getPath(), - String.format("Missing field '%s' in existing Realm file. Either remove field or migrate using io.realm.internal.Table.addColumn().", fieldName)); - } - if (columnTypes.get(fieldName) != fieldType) { - throw new RealmMigrationNeededException( - sharedRealm.getPath(), - String.format("Invalid type '%s' for field '%s' in existing Realm file.", fieldSimpleType, fieldName)); - } - } -} From c491e1ae25a5562870026cee7db85f99c2501e65 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 16 Nov 2017 15:39:53 +0800 Subject: [PATCH 1095/2110] Update sync to 2.1.4 Close #2459 --- CHANGELOG.md | 9 ++++++--- dependencies.list | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2342ab345..5b5ab3f063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,14 @@ ### Bug Fixes -* Leaked file handler in the Realm Transformer (#5521) +* Leaked file handler in the Realm Transformer (#5521). +* Potential fix for "RealmError: Incompatible lock file" crash (#2459). ### Internal -* Updated JavaAssist to 3.22.0-GA +* Updated JavaAssist to 3.22.0-GA. +* Upgraded to Realm Sync 2.1.4. +* Upgraded to Realm Core 4.0.3. ## 4.1.1 (2017-10-27) @@ -19,7 +22,7 @@ ### Internal -* Updated Realm Sync to 2.1.0 +* Updated Realm Sync to 2.1.0. ## 4.1.0 (2017-10-20) diff --git a/dependencies.list b/dependencies.list index 49ad30bacb..de1b76ac57 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.1.0 -REALM_SYNC_SHA256=cd52b2ee53ef80b4b9ec80eede7ca5fa28a96353ad7e4d26cf516dbb12586966 +REALM_SYNC_VERSION=2.1.4 +REALM_SYNC_SHA256=6d32ef44acbf4a63b654ceeaadce036feeefd04a4ca649a95a22a0e7d56df84d # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. From 13feaef0fe403a499be802d80c1cb40dd4566566 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 17 Nov 2017 09:26:29 +0000 Subject: [PATCH 1096/2110] [Sync] Verify certificate using TrustManager (#5515) --- CHANGELOG.md | 1 + ...rustManagerCertificateValidationTests.java | 255 ++++++++++++++++++ .../cpp/io_realm_internal_OsRealmConfig.cpp | 27 ++ realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/SyncManager.java | 142 +++++++++- .../assets/untrusted_ca.pem | 28 ++ .../java/io/realm/SSLConfigurationTests.java | 139 ++++++++-- .../keys/android_test_certificate.crt | Bin 0 -> 1484 bytes 8 files changed, 572 insertions(+), 22 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/assets/untrusted_ca.pem create mode 100644 tools/sync_test_server/keys/android_test_certificate.crt diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a1351d1a7..accd4eec71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Added support for using non-encrypted Realms in multiple processes. Some caveats apply. Read [doc](https://realm.io/docs/java/latest/#multiprocess) for more info (#1091). * Added support for importing primitive lists from JSON (#5362). +* [ObjectServer] Support SSL validation using Android TrustManager (no need to specify `trustedRootCA` in `SynConfiguration` if the certificate is installed on the device), fixes (#4759). ### Bug Fixes diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java new file mode 100644 index 0000000000..4ed36faccc --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java @@ -0,0 +1,255 @@ +package io.realm; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class TrustManagerCertificateValidationTests { + + @BeforeClass + public static void setUp() { + // mainly to setup logging otherwise + // java.lang.UnsatisfiedLinkError: No implementation found for void io.realm.log.RealmLog.nativeSetLogLevel(int) (tried Java_io_realm_log_RealmLog_nativeSetLogLevel and Java_io_realm_log_RealmLog_nativeSetLogLevel__I) + // will be thrown + Realm.init(InstrumentationRegistry.getTargetContext()); + } + + // IMPORTANT: Following test assume the root certificate is installed on the test device + // certificate is located in /tools/sync_test_server/keys/android_test_certificate.crt + // adb push /tools/sync_test_server/keys/android_test_certificate.crt /sdcard/ + // then import the certificate from the device (Settings/Security/Install from storage) + @Test + public void sslVerifyCallback_certificateChainWithRootCAInstalledShouldValidate() { + // simulating the following certificate chain + // --- + // Certificate chain + // 0 s:/DC=127.0.0.1/O=Realm/OU=Realm/CN=127.0.0.1 + // i:/DC=io/DC=realm/O=Realm/OU=Realm Test Signing CA/CN=Realm Test Signing CA + // 1 s:/DC=io/DC=realm/O=Realm/OU=Realm Test Signing CA/CN=Realm Test Signing CA + // i:/DC=io/DC=realm/O=Realm/OU=Realm Test Root CA/CN=Realm Test Root CA + // --- + + // s:/DC=127.0.0.1/O=Realm/OU=Realm/CN=127.0.0.1 + String pem_depth0 = "-----BEGIN CERTIFICATE-----\n" + + "MIIE1DCCArygAwIBAgIBBzANBgkqhkiG9w0BAQUFADB7MRIwEAYKCZImiZPyLGQB\n" + + "GRYCaW8xFTATBgoJkiaJk/IsZAEZFgVyZWFsbTEOMAwGA1UECgwFUmVhbG0xHjAc\n" + + "BgNVBAsMFVJlYWxtIFRlc3QgU2lnbmluZyBDQTEeMBwGA1UEAwwVUmVhbG0gVGVz\n" + + "dCBTaWduaW5nIENBMB4XDTE3MDUxNzIzMjg0OFoXDTE5MDUxNzIzMjg0OFowTzEZ\n" + + "MBcGCgmSJomT8ixkARkWCTEyNy4wLjAuMTEOMAwGA1UECgwFUmVhbG0xDjAMBgNV\n" + + "BAsMBVJlYWxtMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUA\n" + + "A4IBDwAwggEKAoIBAQC3jJl7a1spgJyZt/64HgZsTVi9OLbME2r//fYmoHHSipTq\n" + + "Br7huFsDXpaOYRkPgF+4UUOXADhnRw4JuKuA0ZyBuIHbC7TF3no89ZzLvysS/rGd\n" + + "TqBKq67EERlUxRftWMNy8OVG3CFBTGMdMYXzuvataT7Yhp3EVjtSR10k3UCv+foD\n" + + "TE4tW9I03PCkGRMU9mx8HEe9fXmiCWGtP41OWcWupys5AOk0aGxv2GCiqSQzHJ+A\n" + + "tMaOujeYcT3dgmbY4MKBzEvRXVgmz4UKrP0IpUBQ//lz6CcYe3B1cyojx9cVvsrO\n" + + "V8nuu2202P3HIkcomwBeS6+CY8PXanROYBeUavuDAgMBAAGjgY4wgYswDgYDVR0P\n" + + "AQH/BAQDAgWgMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF\n" + + "BwMCMB0GA1UdDgQWBBTGvfRJ9S52UkTx4s4ubPlZsVYUrTAfBgNVHSMEGDAWgBQn\n" + + "eeHa8RXQ6eWGMIfnH1/PJzpwtDAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEB\n" + + "BQUAA4ICAQCbP3T0aXJrW3WItxBf4HOygr7ccRuj1qRurqZfUXhcgGQgISATFgjQ\n" + + "rhX2UiTZI1wk7WI7DuZfAEu/oZQ0KvsqRl9U5jt/voFb3+h4ph7O4oe5i+TYBB8Y\n" + + "xCmAeiGpVsUp7k4oM/qNkkaiMTHF+TEZ7R32x3WCZbYarbw0SvMYBaCj1JpQ8u+7\n" + + "xC+JEJVoF2qFds6IjBnP16pww9BZm5rA0KjQ08318I5eGauhrlTcB6xtbtjw7mVH\n" + + "3ikedhsdDmL13R32bq0nLo2+xKhBC7FEIj0ps1d0PjtBKBmNSO1lBVuOF6erRSTZ\n" + + "lQDkBOds2GtrKoleH/u08hwgVer1QJlYot7Dg+UBcPhT6Y2Vugsg0JnmtDEFVQCc\n" + + "9/OWfHRbfcdqruyQ+A/y8FjsgAx5BLDzac3lQfL1/ES62U8/Mv5p824fMpRieBd2\n" + + "3NUMGaaLl3DpGTmo+rEAphhvSy04Lx2WC4eYhsEsdUQ8DuHr9MROAsef98wwinIj\n" + + "v0R8fD/3fLGx16pL5B7dyv1ajS6q/0mvpWNviDEmfbOk401NRdZEexKobga7gcCA\n" + + "pF+VO9SlSgEdAA57XSApl9DWiHPxicEBVIWbnO9Bbfm2g8xlrDTKv4j8NE9/YjDi\n" + + "2QLrx1iGkG/kfl8gRfLEoH6tklqFjwiQPehlvlR54mI8XY5XNioXuw==\n" + + "-----END CERTIFICATE-----"; + + // s:/DC=io/DC=realm/O=Realm/OU=Realm Test Signing CA/CN=Realm Test Signing CA + String pem_depth1 = "-----BEGIN CERTIFICATE-----\n" + + "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\n" + + "pf7lM/4=\n" + + "-----END CERTIFICATE-----\n"; + + String serverAddress = "127.0.0.1"; + + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth1, 1)); + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth0, 0)); + } + + @Test + public void sslVerifyCallback_shouldVerifyHostname() { + // simulating the following certificate chain + // --- + // Certificate chain + // 0 s:/CN=*.ie1.realmlab.net + // i:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon + // 1 s:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon + // i:/C=US/O=Amazon/CN=Amazon Root CA 1 + // 2 s:/C=US/O=Amazon/CN=Amazon Root CA 1 + // i:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 + // 3 s:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 + // i:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority + // --- + + // ie1.realmlab.net + String pem_depth0 = "-----BEGIN CERTIFICATE-----\n" + + "MIIEWDCCA0CgAwIBAgIQBE6+74j1z/Z88OEsSc3VIzANBgkqhkiG9w0BAQsFADBG\n" + + "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRUwEwYDVQQLEwxTZXJ2ZXIg\n" + + "Q0EgMUIxDzANBgNVBAMTBkFtYXpvbjAeFw0xNzA0MDMwMDAwMDBaFw0xODA1MDMx\n" + + "MjAwMDBaMB0xGzAZBgNVBAMMEiouaWUxLnJlYWxtbGFiLm5ldDCCASIwDQYJKoZI\n" + + "hvcNAQEBBQADggEPADCCAQoCggEBAKfV/38WJ47qvr4Onopu+XKYlTyTsvouX2VQ\n" + + "jRopM0gdXehp9BfwnFme8KUVZLSYh0vdmY7Wm5A7oxcL4ZuUpDSs9+xuERNg1YMD\n" + + "gI46ehj08+KUSfuqsVuw3gpNM6VPtpKY2I4//fJFmJKTWXA/fl35By0Xbuv4I180\n" + + "FFWu7CV0N4b/QQsjT0+CVvAjHRMMTpw0qtcZGQ4lWNNiqcqUql+Eklm/90S+lyBD\n" + + "q8YQUwcxhMgxKt6M5zwJpWuIbjov9kygDzlw/YU8P5wqvgocfnnXaKw+rr7EdiTS\n" + + "U2ZT99JO0F0CPzPZnphNrRtjkJ4Chtp0FVRqAdthpGH4i1VIKP0CAwEAAaOCAWkw\n" + + "ggFlMB8GA1UdIwQYMBaAFFmkZgZSoHuVkjyjlAcnlnRb+T3QMB0GA1UdDgQWBBRP\n" + + "5MQbQpMCFJgjiFgEtZUIiKdNeDAdBgNVHREEFjAUghIqLmllMS5yZWFsbWxhYi5u\n" + + "ZXQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD\n" + + "AjA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vY3JsLnNjYTFiLmFtYXpvbnRydXN0\n" + + "LmNvbS9zY2ExYi5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwdQYIKwYBBQUHAQEE\n" + + "aTBnMC0GCCsGAQUFBzABhiFodHRwOi8vb2NzcC5zY2ExYi5hbWF6b250cnVzdC5j\n" + + "b20wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuc2NhMWIuYW1hem9udHJ1c3QuY29t\n" + + "L3NjYTFiLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQAObbVL\n" + + "zDqqFO4iDjR4VRTYQbb3gSDxySqFqMm4iBJBmqgNRDsNDb75EmlbB0udbZ6+LHDK\n" + + "pmPh81ocdJECHZctidDh1zCkVf3uOYyPJqxNpt0ZCurGMTi4i5kaIbAwR50lZU2V\n" + + "eSkR5rYFoBIVcUNbXzzOMLTcJrRqbVYz7z9zCN71l12dKNMXdu9tLcec+WCGi0R+\n" + + "MNBOQ/XVlAzymsmQM6nWb0DEQ86ya9AAAMVQBVgyeEPZNPidxc82kU8pML9mO0Yl\n" + + "MtbgZWXH1kTppsi+/WbOwy+kalpiMJ7TXIvHmQat81FWiJNTnKwfVEsz79Op8EAW\n" + + "p9RkpzfSQpZQ30/u\n" + + "-----END CERTIFICATE-----\n"; + + // OU=Server CA 1B/CN=Amazon + String pem_depth1 = "-----BEGIN CERTIFICATE-----\n" + + "MIIESTCCAzGgAwIBAgITBn+UV4WH6Kx33rJTMlu8mYtWDTANBgkqhkiG9w0BAQsF\n" + + "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" + + "b24gUm9vdCBDQSAxMB4XDTE1MTAyMjAwMDAwMFoXDTI1MTAxOTAwMDAwMFowRjEL\n" + + "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEVMBMGA1UECxMMU2VydmVyIENB\n" + + "IDFCMQ8wDQYDVQQDEwZBbWF6b24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" + + "AoIBAQDCThZn3c68asg3Wuw6MLAd5tES6BIoSMzoKcG5blPVo+sDORrMd4f2AbnZ\n" + + "cMzPa43j4wNxhplty6aUKk4T1qe9BOwKFjwK6zmxxLVYo7bHViXsPlJ6qOMpFge5\n" + + "blDP+18x+B26A0piiQOuPkfyDyeR4xQghfj66Yo19V+emU3nazfvpFA+ROz6WoVm\n" + + "B5x+F2pV8xeKNR7u6azDdU5YVX1TawprmxRC1+WsAYmz6qP+z8ArDITC2FMVy2fw\n" + + "0IjKOtEXc/VfmtTFch5+AfGYMGMqqvJ6LcXiAhqG5TI+Dr0RtM88k+8XUBCeQ8IG\n" + + "KuANaL7TiItKZYxK1MMuTJtV9IblAgMBAAGjggE7MIIBNzASBgNVHRMBAf8ECDAG\n" + + "AQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUWaRmBlKge5WSPKOUByeW\n" + + "dFv5PdAwHwYDVR0jBBgwFoAUhBjMhTTsvAyUlC4IWZzHshBOCggwewYIKwYBBQUH\n" + + "AQEEbzBtMC8GCCsGAQUFBzABhiNodHRwOi8vb2NzcC5yb290Y2ExLmFtYXpvbnRy\n" + + "dXN0LmNvbTA6BggrBgEFBQcwAoYuaHR0cDovL2NydC5yb290Y2ExLmFtYXpvbnRy\n" + + "dXN0LmNvbS9yb290Y2ExLmNlcjA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3Js\n" + + "LnJvb3RjYTEuYW1hem9udHJ1c3QuY29tL3Jvb3RjYTEuY3JsMBMGA1UdIAQMMAow\n" + + "CAYGZ4EMAQIBMA0GCSqGSIb3DQEBCwUAA4IBAQCFkr41u3nPo4FCHOTjY3NTOVI1\n" + + "59Gt/a6ZiqyJEi+752+a1U5y6iAwYfmXss2lJwJFqMp2PphKg5625kXg8kP2CN5t\n" + + "6G7bMQcT8C8xDZNtYTd7WPD8UZiRKAJPBXa30/AbwuZe0GaFEQ8ugcYQgSn+IGBI\n" + + "8/LwhBNTZTUVEWuCUUBVV18YtbAiPq3yXqMB48Oz+ctBWuZSkbvkNodPLamkB2g1\n" + + "upRyzQ7qDn1X8nn8N8V7YJ6y68AtkHcNSRAnpTitxBKjtKPISLMVCx7i4hncxHZS\n" + + "yLyKQXhw2W2Xs0qLeC1etA+jTGDK4UfLeC0SF7FSi8o5LL21L8IzApar2pR/\n" + + "-----END CERTIFICATE-----\n"; + // Amazon Root CA 1 + String pem_depth2 = "-----BEGIN CERTIFICATE-----\n" + + "MIIEkjCCA3qgAwIBAgITBn+USionzfP6wq4rAfkI7rnExjANBgkqhkiG9w0BAQsF\n" + + "ADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNj\n" + + "b3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4x\n" + + "OzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1\n" + + "dGhvcml0eSAtIEcyMB4XDTE1MDUyNTEyMDAwMFoXDTM3MTIzMTAxMDAwMFowOTEL\n" + + "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" + + "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" + + "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" + + "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" + + "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" + + "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" + + "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" + + "jgSubJrIqg0CAwEAAaOCATEwggEtMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\n" + + "BAQDAgGGMB0GA1UdDgQWBBSEGMyFNOy8DJSULghZnMeyEE4KCDAfBgNVHSMEGDAW\n" + + "gBScXwDfqgHXMCs4iKK4bUqc8hGRgzB4BggrBgEFBQcBAQRsMGowLgYIKwYBBQUH\n" + + "MAGGImh0dHA6Ly9vY3NwLnJvb3RnMi5hbWF6b250cnVzdC5jb20wOAYIKwYBBQUH\n" + + "MAKGLGh0dHA6Ly9jcnQucm9vdGcyLmFtYXpvbnRydXN0LmNvbS9yb290ZzIuY2Vy\n" + + "MD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwucm9vdGcyLmFtYXpvbnRydXN0\n" + + "LmNvbS9yb290ZzIuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQsF\n" + + "AAOCAQEAYjdCXLwQtT6LLOkMm2xF4gcAevnFWAu5CIw+7bMlPLVvUOTNNWqnkzSW\n" + + "MiGpSESrnO09tKpzbeR/FoCJbM8oAxiDR3mjEH4wW6w7sGDgd9QIpuEdfF7Au/ma\n" + + "eyKdpwAJfqxGF4PcnCZXmTA5YpaP7dreqsXMGz7KQ2hsVxa81Q4gLv7/wmpdLqBK\n" + + "bRRYh5TmOTFffHPLkIhqhBGWJ6bt2YFGpn6jcgAKUj6DiAdjd4lpFw85hdKrCEVN\n" + + "0FE6/V1dN2RMfjCyVSRCnTawXZwXgWHxyvkQAiSr6w10kY17RSlQOYiypok1JR4U\n" + + "akcjMS9cmvqtmg5iUaQqqcT5NJ0hGA==\n" + + "-----END CERTIFICATE-----\n"; + + // O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 + String pem_depth3 = "-----BEGIN CERTIFICATE-----\n" + + "MIIEdTCCA12gAwIBAgIJAKcOSkw0grd/MA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNV\n" + + "BAYTAlVTMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTIw\n" + + "MAYDVQQLEylTdGFyZmllbGQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0\n" + + "eTAeFw0wOTA5MDIwMDAwMDBaFw0zNDA2MjgxNzM5MTZaMIGYMQswCQYDVQQGEwJV\n" + + "UzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UE\n" + + "ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZp\n" + + "ZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEi\n" + + "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVDDrEKvlO4vW+GZdfjohTsR8/\n" + + "y8+fIBNtKTrID30892t2OGPZNmCom15cAICyL1l/9of5JUOG52kbUpqQ4XHj2C0N\n" + + "Tm/2yEnZtvMaVq4rtnQU68/7JuMauh2WLmo7WJSJR1b/JaCTcFOD2oR0FMNnngRo\n" + + "Ot+OQFodSk7PQ5E751bWAHDLUu57fa4657wx+UX2wmDPE1kCK4DMNEffud6QZW0C\n" + + "zyyRpqbn3oUYSXxmTqM6bam17jQuug0DuDPfR+uxa40l2ZvOgdFFRjKWcIfeAg5J\n" + + "Q4W2bHO7ZOphQazJ1FTfhy/HIrImzJ9ZVGif/L4qL8RVHHVAYBeFAlU5i38FAgMB\n" + + "AAGjgfAwge0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0O\n" + + "BBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtV\n" + + "rNzXEMIOqYjnME8GCCsGAQUFBwEBBEMwQTAcBggrBgEFBQcwAYYQaHR0cDovL28u\n" + + "c3MyLnVzLzAhBggrBgEFBQcwAoYVaHR0cDovL3guc3MyLnVzL3guY2VyMCYGA1Ud\n" + + "HwQfMB0wG6AZoBeGFWh0dHA6Ly9zLnNzMi51cy9yLmNybDARBgNVHSAECjAIMAYG\n" + + "BFUdIAAwDQYJKoZIhvcNAQELBQADggEBACMd44pXyn3pF3lM8R5V/cxTbj5HD9/G\n" + + "VfKyBDbtgB9TxF00KGu+x1X8Z+rLP3+QsjPNG1gQggL4+C/1E2DUBc7xgQjB3ad1\n" + + "l08YuW3e95ORCLp+QCztweq7dp4zBncdDQh/U90bZKuCJ/Fp1U1ervShw3WnWEQt\n" + + "8jxwmKy6abaVd38PMV4s/KCHOkdp8Hlf9BRUpJVeEXgSYCfOn8J3/yNTd126/+pZ\n" + + "59vPr5KW7ySaNRB6nJHGDn2Z9j8Z3/VyVOEVqQdZe4O/Ui5GjLIAZHYcSNPYeehu\n" + + "VsyuLAOQ1xk4meTKCRlb/weWsKh/NEnfVqn3sF/tM+2MR7cwA130A4w=\n" + + "-----END CERTIFICATE-----\n"; + + String serverAddress = "nabil-test.ie1.realmlab.net"; + + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth3, 3)); + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth2, 2)); + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth1, 1)); + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth0, 0)); + + // reaching depth0 will validate (or not) the entire chain, then removing the PEMs from memory + // make sure the hostname verify works + + String wrongServerAddress = "hax0r-test.realmlab.net"; + assertTrue(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth3, 3)); + assertTrue(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth2, 2)); + assertTrue(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth1, 1)); + // Note hax0r-test.ie1.realmlab.net is valid since the certificate allow *.ie1.realmlab.net + // but the method fails because of the hostname verification + assertFalse(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth0, 0)); + } +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index fd7d3fdc9e..8a8eb0bcb3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -21,6 +21,7 @@ #include #include #include + #endif #include "java_accessor.hpp" @@ -360,6 +361,32 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigS JStringAccessor cert_path(env, j_sync_ssl_trust_certificate_path); config.sync_config->ssl_trust_certificate_path = realm::util::Optional(cert_path); } + else if (config.sync_config->client_validate_ssl) { + // set default callback to allow Android to check the certificate + static JavaClass sync_manager_class(env, "io/realm/SyncManager"); + static JavaMethod java_ssl_verify_callback(env, sync_manager_class, "sslVerifyCallback", + "(Ljava/lang/String;Ljava/lang/String;I)Z", true); + + std::function ssl_verify_callback = + [](const std::string server_address, REALM_UNUSED realm::sync::Client::port_type server_port, + const char* pem_data, size_t pem_size, REALM_UNUSED int preverify_ok, int depth) { + + Log::d("Callback to Java requesting certificate validation for host %1", + server_address.c_str()); + + JNIEnv* env = realm::jni_util::JniUtils::get_env(true); + + jstring jserver_address = to_jstring(env, server_address.c_str()); + // deep copy the pem_data into a string so DeleteLocalRef delete the local reference not the original const char + std::string pem(pem_data, pem_size); + jstring jpem = to_jstring(env, pem.c_str()); + bool isValid = env->CallStaticBooleanMethod(sync_manager_class, java_ssl_verify_callback, + jserver_address, + jpem, depth) == JNI_TRUE; + return isValid; + }; + config.sync_config->ssl_verify_callback = std::move(ssl_verify_callback); + } } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 1cb3a165dc..e446a4c73c 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 1cb3a165dc703a706cd107318b38c2f49fa3f31f +Subproject commit e446a4c73c52c70ac3d4eb801e0e0a286e21acbc diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 4ba4d0e5d9..43381058c4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -16,7 +16,17 @@ package io.realm; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -27,13 +37,18 @@ import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.Keep; +import io.realm.internal.Util; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.network.OkHttpAuthenticationServer; import io.realm.log.RealmLog; +import okhttp3.internal.tls.OkHostnameVerifier; /** * The SyncManager is the central controller for interacting with the Realm Object Server. @@ -293,13 +308,25 @@ static void notifyUserLoggedOut(SyncUser user) { */ @SuppressWarnings("unused") private static synchronized void notifyErrorHandler(int errorCode, String errorMessage, @Nullable String path) { - for (SyncSession syncSession : sessions.values()) { - if (path == null || path.equals(syncSession.getConfiguration().getPath())) { + if (Util.isEmptyString(path)) { + // notify all sessions + for (SyncSession syncSession : sessions.values()) { + try { + syncSession.notifySessionError(errorCode, errorMessage); + } catch (Exception exception) { + RealmLog.error(exception); + } + } + } else { + SyncSession syncSession = sessions.get(path); + if (syncSession != null) { try { syncSession.notifySessionError(errorCode, errorMessage); } catch (Exception exception) { RealmLog.error(exception); } + } else { + RealmLog.warn("Cannot find the SyncSession corresponding to the path: " + path); } } } @@ -356,6 +383,117 @@ private synchronized static String bindSessionWithConfig(String sessionPath, Str return null; } + // Holds the certificate chain (per hostname). We need to keep the order of each certificate + // according to it's depth in the chain. The depth of the last + // certificate is 0. The depth of the first certificate is chain + // length - 1. + private static HashMap> ROS_CERTIFICATES_CHAIN; + + // The default Android Trust Manager which uses the default KeyStore to + // validate the certificate chain. + private static X509TrustManager TRUST_MANAGER; + + // Help transform a String PEM representation of the certificate, into + // X509Certificate format. + private static CertificateFactory CERTIFICATE_FACTORY; + + // From Sync implementation: + // A recommended way of using the callback function is to return true + // if preverify_ok = 1 and depth > 0, + // always check the host name if depth = 0, + // and use an independent verification step if preverify_ok = 0. + // + // Another possible way of using the callback is to collect all the + // ROS_CERTIFICATES_CHAIN until depth = 0, and present the entire chain for + // independent verification. + // + // In this implementation we use the second method, since it's more suitable for + // the underlying Java API we need to call to validate the certificate chain. + @SuppressWarnings("unused") + synchronized static boolean sslVerifyCallback(String serverAddress, String pemData, int depth) { + try { + if (ROS_CERTIFICATES_CHAIN == null) { + ROS_CERTIFICATES_CHAIN = new HashMap<>(); + TRUST_MANAGER = systemDefaultTrustManager(); + CERTIFICATE_FACTORY = CertificateFactory.getInstance("X.509"); + } + + if (!ROS_CERTIFICATES_CHAIN.containsKey(serverAddress)) { + ROS_CERTIFICATES_CHAIN.put(serverAddress, new ArrayList()); + } + + ROS_CERTIFICATES_CHAIN.get(serverAddress).add(pemData); + + if (depth == 0) { + // transform all PEM ROS_CERTIFICATES_CHAIN into Java X509 + // with respecting the order/depth provided from Sync. + List pemChain = ROS_CERTIFICATES_CHAIN.get(serverAddress); + int n = pemChain.size(); + X509Certificate[] chain = new X509Certificate[n]; + for (String pem : pemChain) { + // The depth of the last certificate is 0. + // The depth of the first certificate is chain length - 1. + chain[--n] = buildCertificateFromPEM(pem); + } + + // verify the entire chain + try { + TRUST_MANAGER.checkServerTrusted(chain, "RSA"); + // verify the hostname + boolean isValid = OkHostnameVerifier.INSTANCE.verify(serverAddress, chain[0]); + if (isValid) { + return true; + } else { + RealmLog.error("Can not verify the hostname for the host: " + serverAddress); + return false; + } + } catch (CertificateException e) { + RealmLog.error(e, "Can not validate SSL chain certificate for the host: " + serverAddress); + return false; + } finally { + // don't keep the certificate chain in memory + ROS_CERTIFICATES_CHAIN.remove(serverAddress); + } + } else { + // return true, since the verification will happen for the entire chain + // when receiving the depth == 0 (host certificate) + return true; + } + } catch (Exception e) { + RealmLog.error(e, "Error during certificate validation for host: " + serverAddress); + return false; + } + } + + // Credit OkHttp https://github.com/square/okhttp/blob/e5c84e1aef9572adb493197c1b6c4e882aca085b/okhttp/src/main/java/okhttp3/OkHttpClient.java#L270 + private static X509TrustManager systemDefaultTrustManager() { + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init((KeyStore) null); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + + Arrays.toString(trustManagers)); + } + return (X509TrustManager) trustManagers[0]; + } catch (GeneralSecurityException e) { + throw new AssertionError(); // The system has no TLS. Just give up. + } + } + + private static X509Certificate buildCertificateFromPEM(String pem) throws IOException, CertificateException { + InputStream stream = null; + try { + stream = new ByteArrayInputStream(pem.getBytes("UTF-8")); + return (X509Certificate) CERTIFICATE_FACTORY.generateCertificate(stream); + } finally { + if (stream != null) { + stream.close(); + } + } + } + /** * Resets the SyncManger and clear all existing users. * This will also terminate all sessions. diff --git a/realm/realm-library/src/syncIntegrationTest/assets/untrusted_ca.pem b/realm/realm-library/src/syncIntegrationTest/assets/untrusted_ca.pem new file mode 100644 index 0000000000..8c4c741058 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/assets/untrusted_ca.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTEvMC0GA1UEAxMmRGlnaUNlcnQgU0hBMiBIaWdoIEFzc3Vy +YW5jZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2 +4C/CJAbIbQRf1+8KZAayfSImZRauQkCbztyfn3YHPsMwVYcZuU+UDlqUH1VWtMIC +Kq/QmO4LQNfE0DtyyBSe75CxEamu0si4QzrZCwvV1ZX1QK/IHe1NnF9Xt4ZQaJn1 +itrSxwUfqJfJ3KSxgoQtxq2lnMcZgqaFD15EWCo3j/018QsIJzJa9buLnqS9UdAn +4t07QjOjBSjEuyjMmqwrIw14xnvmXnG3Sj4I+4G3FhahnSMSTeXXkgisdaScus0X +sh5ENWV/UyU50RwKmmMbGZJ0aAo3wsJSSMs5WqK24V3B3aAguCGikyZvFEohQcft +bZvySC/zA/WiaJJTL17jAgMBAAGjggFJMIIBRTASBgNVHRMBAf8ECDAGAQH/AgEA +MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +NAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy +dC5jb20wSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29t +L0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDA9BgNVHSAENjA0MDIG +BFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQ +UzAdBgNVHQ4EFgQUUWj/kK8CB3U8zNllZGKiErhZcjswHwYDVR0jBBgwFoAUsT7D +aQP4v0cB1JgmGggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBABiKlYkD5m3fXPwd +aOpKj4PWUS+Na0QWnqxj9dJubISZi6qBcYRb7TROsLd5kinMLYBq8I4g4Xmk/gNH +E+r1hspZcX30BJZr01lYPf7TMSVcGDiEo+afgv2MW5gxTs14nhr9hctJqvIni5ly +/D6q1UEL2tU2ob8cbkdJf17ZSHwD2f2LSaCYJkJA69aSEaRkCldUxPUd1gJea6zu +xICaEnL6VpPX/78whQYwvwt/Tv9XBZ0k7YXDK/umdaisLRbvfXknsuvCnQsH6qqF +0wGjIChBWUMo0oHjqvbsezt3tkBigAVBRQHvFwY+3sAzm2fTYS5yh+Rp/BIAV0Ae +cPUeybQ= +-----END CERTIFICATE----- diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index ab943c1e3c..9260a5351f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -19,7 +19,6 @@ import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; @@ -51,30 +50,32 @@ public void trustedRootCA() throws InterruptedException { SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + //noinspection unchecked + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .schema(StringOnly.class) .build(); - Realm realm = Realm.getInstance(configOld); + Realm realm = Realm.getInstance(syncConfig); realm.beginTransaction(); realm.createObject(StringOnly.class).setChars("Foo"); realm.commitTransaction(); // make sure the changes gets to the server - SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. + SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); user.logout(); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + //noinspection unchecked + SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() .trustedRootCA("trusted_ca.pem") .build(); - realm = Realm.getInstance(config); + realm = Realm.getInstance(syncConfigSSL); RealmResults all = realm.where(StringOnly.class).findAll(); try { @@ -92,30 +93,32 @@ public void withoutSSLVerification() throws InterruptedException { SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + //noinspection unchecked + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .schema(StringOnly.class) .build(); - Realm realm = Realm.getInstance(configOld); + Realm realm = Realm.getInstance(syncConfig); realm.beginTransaction(); realm.createObject(StringOnly.class).setChars("Foo"); realm.commitTransaction(); // make sure the changes gets to the server - SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. + SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); user.logout(); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + //noinspection unchecked + SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() .disableSSLVerification() .build(); - realm = Realm.getInstance(config); + realm = Realm.getInstance(syncConfigSSL); RealmResults all = realm.where(StringOnly.class).findAll(); try { @@ -133,28 +136,33 @@ public void trustedRootCA_syncShouldFailWithoutTrustedCA() throws InterruptedExc SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + //noinspection unchecked + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .schema(StringOnly.class) .build(); - Realm realm = Realm.getInstance(configOld); + Realm realm = Realm.getInstance(syncConfig); realm.beginTransaction(); realm.createObject(StringOnly.class).setChars("Foo"); realm.commitTransaction(); // make sure the changes gets to the server - SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. + SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); user.logout(); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + //noinspection unchecked + SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") .schema(StringOnly.class) + .trustedRootCA("untrusted_ca.pem") .build(); - realm = Realm.getInstance(config); + // waitForInitialRemoteData will throw an Internal error (125): Operation Canceled + SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); + realm = Realm.getInstance(syncConfigSSL); try { assertTrue(realm.isEmpty()); } finally { @@ -173,7 +181,9 @@ public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { RealmLog.add(testLogger); RealmLog.setLevel(LogLevel.WARN); + //noinspection unchecked configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .name("useSsl") .schema(StringOnly.class) .trustedRootCA("trusted_ca.pem") .disableSSLVerification() @@ -190,15 +200,106 @@ public void trustedRootCA_notExisting_certificate_willThrow() { String username = UUID.randomUUID().toString(); String password = "password"; SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + //noinspection unchecked + SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .schema(StringOnly.class) - .trustedRootCA("not_existing_file.pem") + .trustedRootCA("none_existing_file.pem") .build(); try { - Realm.getInstance(config); + Realm.getInstance(syncConfig); fail(); } catch (RealmFileException ignored) { } } + + @Test + public void combiningTrustedRootCA_and_disableSSLVerification() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + // 1. Copy a valid Realm to the server using ssl_verify_path option + //noinspection unchecked + final SyncConfiguration syncConfigWithCertificate = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .schema(StringOnly.class) + .trustedRootCA("trusted_ca.pem") + .build(); + Realm realm = Realm.getInstance(syncConfigWithCertificate); + + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Foo"); + realm.commitTransaction(); + + // make sure the changes gets to the server + SyncManager.getSession(syncConfigWithCertificate).uploadAllLocalChanges(); + realm.close(); + user.logout(); + + // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should + // download the uploaded changes. + user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + //noinspection unchecked + SyncConfiguration syncConfigDisableSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .name("useSsl") + .schema(StringOnly.class) + .waitForInitialRemoteData() + .disableSSLVerification() + .build(); + realm = Realm.getInstance(syncConfigDisableSSL); + + RealmResults all = realm.where(StringOnly.class).findAll(); + try { + assertEquals(1, all.size()); + assertEquals("Foo", all.get(0).getChars()); + } finally { + realm.close(); + } + } + + // IMPORTANT: Following test assume the root certificate is installed on the test device + // certificate is located in /tools/sync_test_server/keys/android_test_certificate.crt + // adb push /tools/sync_test_server/keys/android_test_certificate.crt /sdcard/ + // then import the certificate from the device (Settings/Security/Install from storage) + @Test + public void sslVerifyCallback_isUsed() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + // 1. Copy a valid Realm to the server using ssl_verify_path option + //noinspection unchecked + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly.class) + .build(); + Realm realm = Realm.getInstance(syncConfig); + + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Foo"); + realm.commitTransaction(); + + // make sure the changes gets to the server + SyncManager.getSession(syncConfig).uploadAllLocalChanges(); + realm.close(); + user.logout(); + + // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should + // download the uploaded changes. + user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + //noinspection unchecked + SyncConfiguration syncConfigSecure = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .name("useSsl") + .schema(StringOnly.class) + .waitForInitialRemoteData() + .build(); + realm = Realm.getInstance(syncConfigSecure); + + RealmResults all = realm.where(StringOnly.class).findAll(); + try { + assertEquals(1, all.size()); + assertEquals("Foo", all.get(0).getChars()); + } finally { + realm.close(); + } + } } diff --git a/tools/sync_test_server/keys/android_test_certificate.crt b/tools/sync_test_server/keys/android_test_certificate.crt new file mode 100644 index 0000000000000000000000000000000000000000..53a5f087efe810f3e3e44318e494d2038f45aaa2 GIT binary patch literal 1484 zcmXqLVm)Ed#JphvGZP~d6CkJHZIOdYMqlm>7+19 ziZNy88;TkTqlmEL#wKYXC(dhVW?*SxZeVF*WMmvA&TE9s9VlltF)1Mj5F;xCa}yIkgFzD$7gG}x zBf}l%O_Mh6`+v*%^Ny?DPV-pn{q}R}GZ{;%xcnEYGo5pGb7BS8hUSK*H;ZD@KGa^h z|8vg1t$xq46C$6#~9&5iRpW^n%(j*bZ8ZvD97-#+_Yui^sqU&*%0A8OYs5Mi|~ z$a#Exd1JPnVEXa78?*OKGy8T#w)u6(-C64V_tWLh8ZazONwW<=TyozQuF<&Y@Ch+?*gLSs>p0EF!r#MI~obJ{W7roBw=;V^6 z^W`q32yeL7oZy%FU5u6crR$rWK@~#53D-NmMwBW%S?%@s^v2kxa36bjmx2jw%L_zo zvYUTuunRSQD_~O54Ju%qCR47No)gz_Bk=E2=h%d`I`LCimFRxzb2fOH{`z<-m$&r> z`*p3i4AiuCCE5)Jr(3160M({&};l7x2G5-R3OI zeH;Bl$21QVNN$dr_m97}d1~|0+l)7kOw50$ntirUZ&PN6{LAq7K1U?ZUUKp~D7m-y zq~G(+tiSS(8gU)|u=mYnmn#h%YPv$FrI*a*JjKoQI%-=;;F<}^FMSR*X)O0ox-a1`(>=NBvr#9P-NC(Gu9`pUC43f^KYX<1b%0@Z`PB-csVAAP zCo&7??fZAP*;L!-j%#DSSiRcwY%BQW%HoeWob4%U&_zCM>R~)TY#k?)#7U*D|r+9ScVXH@r?*06F z?A$D|eG-l;BJbSGKHc6p;}hRsiFM0Ad<+e%n)RtAp!^fZ@h|pYrJ1LDIzC>gefZti ZJ$vSATUZK|L`zK4`gOqD?ZAJxFaV;QXXpR` literal 0 HcmV?d00001 From e6f399e3405afcf781713b3b55373eab58cf5294 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 17 Nov 2017 09:27:19 +0000 Subject: [PATCH 1097/2110] Re-enable some deactivated tests (#5511) --- Jenkinsfile | 18 ++++------ dependencies.list | 2 +- .../java/io/realm/PermissionManagerTests.java | 19 ++++------- .../java/io/realm/SSLConfigurationTests.java | 2 +- .../java/io/realm/SyncSessionTests.java | 2 -- .../java/io/realm/SyncedRealmTests.java | 5 ++- .../objectserver/ProcessCommitTests.java | 2 -- .../realm/objectserver/utils/HttpUtils.java | 9 ++--- tools/sync_test_server/Dockerfile | 4 +++ tools/sync_test_server/ros-testing-server.js | 34 +++++++++++-------- 10 files changed, 44 insertions(+), 53 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a5b2845569..f511eb1188 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -87,15 +87,13 @@ try { stage('Run instrumented tests') { lock("${env.NODE_NAME}-android") { - boolean archiveLog = true String backgroundPid try { backgroundPid = startLogCatCollector() forwardAdbPorts() gradle('realm', "${instrumentationTestTarget}") - archiveLog = false; } finally { - stopLogCatCollector(backgroundPid, archiveLog) + stopLogCatCollector(backgroundPid) storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' } } @@ -162,15 +160,13 @@ def String startLogCatCollector() { return readFile("pid").trim() } -def stopLogCatCollector(String backgroundPid, boolean archiveLog) { +def stopLogCatCollector(String backgroundPid) { sh "kill ${backgroundPid}" - if (archiveLog) { - zip([ - 'zipFile': 'logcat.zip', - 'archive': true, - 'glob' : 'logcat.txt' - ]) - } + zip([ + 'zipFile': 'logcat.zip', + 'archive': true, + 'glob' : 'logcat.txt' + ]) sh 'rm logcat.txt' } diff --git a/dependencies.list b/dependencies.list index de1b76ac57..35394be8e1 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,5 +5,5 @@ REALM_SYNC_SHA256=6d32ef44acbf4a63b654ceeaadce036feeefd04a4ca649a95a22a0e7d56df8 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.6 +REALM_OBJECT_SERVER_DE_VERSION=2.0.18 diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index dce05265a9..84c2788de8 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -106,7 +106,6 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore public void getPermissions_updatedWithNewRealms() { final PermissionManager pm = user.getPermissionManager(); looperThread.closeAfterTest(pm); @@ -153,7 +152,6 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore public void getPermissions_updatedWithNewRealms_stressTest() { final int TEST_SIZE = 10; final PermissionManager pm = user.getPermissionManager(); @@ -289,7 +287,7 @@ public void onError(ObjectServerError error) { }); } - @Ignore("See https://github.com/realm/realm-java/issues/5143") + @Ignore("The PermissionManager can only be opened from the main thread") @Test public void clientResetOnMultipleThreads() { @@ -901,7 +899,7 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread(emulateMainThread = true) - @Ignore + @Ignore("The offer is randomly accepted mostly on docker-02 SHIELD K1") public void acceptOffer_expiredThrows() { // Trying to guess how long CI is to process this. The offer cannot be created if it // already expired. @@ -1181,6 +1179,7 @@ public void run() { private String createRemoteRealm(SyncUser user, String realmName) { String url = Constants.AUTH_SERVER_URL + "~/" + realmName; SyncConfiguration config = new SyncConfiguration.Builder(user, url) + .name(realmName) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); @@ -1205,18 +1204,12 @@ public void onChange(Progress progress) { * states and fail if neither of these can be verified. */ private void assertInitialPermissions(RealmResults permissions) { - assertGreaterThan("Unexpected count() for __permission Realm: " + Arrays.toString(permissions.toArray()), 0, permissions.where().endsWith("path", "__permission").count()); - assertGreaterThan("Unexpected count() for __management Realm: " + Arrays.toString(permissions.toArray()), 0, permissions.where().endsWith("path", "__management").count()); - // FIXME: Enable these again when https://github.com/realm/ros/issues/549 is fixed - // assertEquals("Unexpected count() for __permission Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__permission").count()); - // assertEquals("Unexpected count() for __management Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__management").count()); + assertEquals("Unexpected count() for __permission Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__permission").count()); + assertEquals("Unexpected count() for __management Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__management").count()); } private void assertInitialDefaultPermissions(RealmResults permissions) { - assertGreaterThan("Unexpected count() for __wildcardpermissions Realm: " + Arrays.toString(permissions.toArray()), 0, permissions.where().endsWith("path", "__wildcardpermissions").count()); - - // FIXME: Enable these again when https://github.com/realm/ros/issues/549 is fixed - // assertEquals("Unexpected count() for __wildcardpermissions Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__wildcardpermissions").count()); + assertEquals("Unexpected count() for __wildcardpermissions Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__wildcardpermissions").count()); } private void assertGreaterThan(String error, int base, long count) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index ab943c1e3c..d7ba354e8e 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -42,7 +42,7 @@ public class SSLConfigurationTests extends StandardIntegrationTest { @Rule - public Timeout globalTimeout = Timeout.seconds(10); + public Timeout globalTimeout = Timeout.seconds(120); @Test public void trustedRootCA() throws InterruptedException { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 7fce3b7a02..55aab7e5f5 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -116,7 +116,6 @@ public void uploadDownloadAllChanges() throws InterruptedException { } @Test - @Ignore() public void interruptWaits() throws InterruptedException { final SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); @@ -309,7 +308,6 @@ public void onChange(RealmResults stringOnlies) { // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. // this test validate the behaviour of SyncSessionStopPolicy::AfterChangesUploaded @Test - @Ignore() public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index 7e581f6a6f..7eaa41f60a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -124,7 +124,8 @@ public void execute(Realm realm) { // We cannot do much better since we cannot control the order of events internally in Realm which would be // needed to correctly test all error paths. @Test - @Ignore("See https://github.com/realm/realm-java/issues/5177") + @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + + " https://github.com/realm/realm-java/issues/5416") public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); @@ -166,6 +167,8 @@ public void waitForInitialData_resilientInCaseOfRetriesAsync() { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .directory(configurationFactory.getRoot()) .waitForInitialRemoteData() .build(); Random randomizer = new Random(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index e4847f19aa..b477f33609 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -20,7 +20,6 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -104,7 +103,6 @@ protected void run() { @Test @RunTestInLooperThread @RunTestWithRemoteService(remoteService = SimpleCommitRemoteService.class, onLooperThread = true) - @Ignore("See https://github.com/realm/realm-java/issues/5376") public void expectSimpleCommit() { looperThread.runAfterTest(remoteService.afterRunnable); remoteService.createHandler(Looper.myLooper()); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index cc3fcd95ee..dc0ae46830 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -17,18 +17,12 @@ package io.realm.objectserver.utils; import android.os.SystemClock; -import android.util.Log; import java.io.IOException; -import java.net.SocketTimeoutException; import java.util.concurrent.TimeUnit; -import io.realm.log.RealmLog; -import okhttp3.Headers; -import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.RequestBody; import okhttp3.Response; /** @@ -40,8 +34,9 @@ public class HttpUtils { // TODO If the timeouts are longer than the test timeout you risk getting // "Realm could not be deleted errors". + // FIXME re-adjust timeout after https://github.com/realm/realm-object-server-private/issues/697 is fixed private final static OkHttpClient client = new OkHttpClient.Builder() - .retryOnConnectionFailure(true) + .connectTimeout(2, TimeUnit.MINUTES) .build(); // adb reverse tcp:8888 tcp:8888 diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index c4f792b11c..8a9a766a41 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -1,5 +1,9 @@ FROM node:6.11.4 +# set timezone to Copenhagen (by default it's using UTC) to match Android's device time. +RUN cp /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime +RUN echo "Europe/Copenhagen" > /etc/timezone + ARG ROS_DE_VERSION # Install realm object server diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index 2f841d4f43..e183378084 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -43,7 +43,7 @@ function handleRequest(request, response) { var syncServerChildProcess = null; // Waits for ROS to be fully initialized. -function waitForRosToInitialize(attempts, onSuccess, onError) { +function waitForRosToInitialize(attempts, onSuccess, onError, startSequence) { if (attempts == 0) { onError("Could not get ROS to start. See Docker log."); return; @@ -51,16 +51,16 @@ function waitForRosToInitialize(attempts, onSuccess, onError) { http.get("http://0.0.0.0:9080/health", function(res) { if (res.statusCode != 200) { winston.info("ROS /health/ returned: " + res.statusCode) - waitForRosToInitialize(attempts - 1, onSuccess, onError) + waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence) } else { - onSuccess(); + onSuccess(startSequence); } }).on('error', function(err) { // ROS not accepting any connections yet. // Errors like ECONNREFUSED 0.0.0.0:9080 will be reported here. // Wait a little before trying again (common startup is ~1 second). setTimeout(function() { - waitForRosToInitialize(attempts - 1, onSuccess, onError); + waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); }, 200); }); } @@ -112,7 +112,7 @@ function doStartRealmObjectServer(onSuccess, onError) { winston.info(`${data}`); }); - waitForRosToInitialize(20, onSuccess, onError); + waitForRosToInitialize(100, onSuccess, onError, Date.now()); } }); } @@ -121,24 +121,28 @@ function stopRealmObjectServer(onSuccess, onError) { if(syncServerChildProcess == null || syncServerChildProcess.killed) { onSuccess("No ROS process found or the process has been killed before"); } + if (syncServerChildProcess) { + syncServerChildProcess.on('exit', function(code) { + winston.info("ROS server stopped due to process being killed. Exit code: " + code); + syncServerChildProcess.removeAllListeners('exit'); + syncServerChildProcess = null; + onSuccess(); + }); - syncServerChildProcess.on('exit', function(code) { - winston.info("ROS server stopped due to process being killed. Exit code: " + code); - syncServerChildProcess.removeAllListeners('exit'); - syncServerChildProcess = null; - onSuccess(); - }); - - syncServerChildProcess.kill('SIGKILL'); + syncServerChildProcess.kill('SIGKILL'); + } } // start sync server dispatcher.onGet("/start", function(req, res) { winston.info("Attempting to start ROS"); - startRealmObjectServer(() => { + startRealmObjectServer((startSequence) => { res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('ROS started'); + let response = `ROS started after ${Date.now() - startSequence} ms`; + winston.info(response); + res.end(response); }, function (err) { + winston.error('Starting ROS failed: ' + err); res.writeHead(500, {'Content-Type': 'text/plain'}); res.end('Starting ROS failed: ' + err); }); From d5dfd6cfbdaf15930fde32fb5042ec4d40dfc9f6 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 17 Nov 2017 18:47:26 +0800 Subject: [PATCH 1098/2110] Merge entries in changelog --- CHANGELOG.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f5836113a..3507757039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,27 +9,21 @@ * [ObjectServer] Support SSL validation using Android TrustManager (no need to specify `trustedRootCA` in `SynConfiguration` if the certificate is installed on the device), fixes (#4759). * Added the and() function to `RealmQuery` in order to improve readability. - -### Bug Fixes - -### Interal - -### Credits - - -## 4.1.2 (YYYY-MM-DD) - ### Bug Fixes * Leaked file handler in the Realm Transformer (#5521). * Potential fix for "RealmError: Incompatible lock file" crash (#2459). -### Internal +### Interal * Updated JavaAssist to 3.22.0-GA. * Upgraded to Realm Sync 2.1.4. * Upgraded to Realm Core 4.0.3. +### Credits + +Thanks to @rakshithravi1997 for adding `RealmQuery.and()` (#5520). + ## 4.1.1 (2017-10-27) From 85ec0fa94bd9f30b0e3f684eb16af946ca6bc587 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 17 Nov 2017 18:47:33 +0800 Subject: [PATCH 1099/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3507757039..47022631a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.2.0 (YYYY-MM-DD) +## 4.2.0 (2017-11-17) ### Breaking Changes From dfe7e49e3b7f4d86c5e82b92b12e54aa8254f475 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 17 Nov 2017 18:47:36 +0800 Subject: [PATCH 1100/2110] Release v4.2.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index c3a2c7076f..ef8d7569d6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.2.0-SNAPSHOT \ No newline at end of file +4.2.0 \ No newline at end of file From 4bb5242f6f2fad114316d152ca26386f5b4fe518 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 17 Nov 2017 18:47:36 +0800 Subject: [PATCH 1101/2110] Prepare next release v4.2.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index ef8d7569d6..d168f1d8bd 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.2.0 \ No newline at end of file +4.2.1-SNAPSHOT \ No newline at end of file From 8c16a7a3d70bbd03a53d55886049e3b6a6157e38 Mon Sep 17 00:00:00 2001 From: Ben Sandee Date: Sun, 19 Nov 2017 21:53:34 -0600 Subject: [PATCH 1102/2110] fix small typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47022631a8..afed4873ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ * Leaked file handler in the Realm Transformer (#5521). * Potential fix for "RealmError: Incompatible lock file" crash (#2459). -### Interal +### Internal * Updated JavaAssist to 3.22.0-GA. * Upgraded to Realm Sync 2.1.4. From 8f10ec329618d39d6d43b8c781ec3e371f66f53a Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Mon, 20 Nov 2017 20:01:56 +0800 Subject: [PATCH 1103/2110] Add credits --- CHANGELOG.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afed4873ff..a6167b9399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ -## 4.2.0 (2017-11-17) +## 4.2.1 (YYYY-MM-DD) -### Breaking Changes +### Enhancements + +### Bug Fixes + +### Internal + +### Credits + +* Thanks to @tbsandee for fixing a typo (#5548). + + +## 4.2.0 (2017-11-17) ### Enhancements @@ -22,7 +33,7 @@ ### Credits -Thanks to @rakshithravi1997 for adding `RealmQuery.and()` (#5520). +* Thanks to @rakshithravi1997 for adding `RealmQuery.and()` (#5520). ## 4.1.1 (2017-10-27) @@ -114,7 +125,7 @@ The internal file format has been upgraded. Opening an older Realm will upgrade ### Credits -Thanks to @JussiPekonen for adding support for 2-digit time zone designators when importing JSON (#5309). +* Thanks to @JussiPekonen for adding support for 2-digit time zone designators when importing JSON (#5309). ## 3.7.2 (2017-09-12) From aa437daa2168e6c52bdf1928f16757f1e07ff2e9 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 22 Nov 2017 16:14:03 +0800 Subject: [PATCH 1104/2110] Added notification to OsList instead of OsResults (#5552) - The notification token for RealmList will be added to the underlying OsList instead of the OsResults created from the list. This will have a different behavior for a corner case, see https://github.com/realm/realm-object-store/pull/602 . Thus the test cases needs to be modified accordingly. - This would be the precondition of using https://github.com/realm/realm-object-store/pull/601 - Add toString() for OrderedCollectionChangeSet. --- CHANGELOG.md | 4 + .../OrderedCollectionChangeSetTests.java | 79 ++++++++++++++----- .../io/realm/OrderedCollectionChangeSet.java | 7 ++ .../src/main/java/io/realm/RealmList.java | 50 ++---------- .../realm/internal/OsCollectionChangeSet.java | 20 +++++ 5 files changed, 98 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6167b9399..2e10c710f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,12 @@ ### Bug Fixes +* Added missing `toString()` for the implementation of `OrderedCollectionChangeSet`. + ### Internal +* Use `OsList` instead of `OsResults` to add notification token on for `RealmList`. + ### Credits * Thanks to @tbsandee for fixing a typo (#5548). diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java index ca5fe29fc6..1c3788f092 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -26,6 +26,8 @@ import java.util.Arrays; import java.util.List; +import javax.annotation.Nullable; + import io.realm.entities.Dog; import io.realm.entities.Owner; import io.realm.rule.RunInLooperThread; @@ -38,9 +40,11 @@ import static junit.framework.Assert.assertSame; import static junit.framework.Assert.fail; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotEquals; // Tests for the ordered collection fine grained notifications for both RealmResults and RealmList. +@SuppressWarnings("ConstantConditions") // Suppress the null return value warnings for RealmList.get() @RunWith(Parameterized.class) public class OrderedCollectionChangeSetTests { @@ -110,16 +114,6 @@ private void checkRanges(OrderedCollectionChangeSet.Range[] ranges, int... index } } - // Re-adds the dogs so they would be sorted by age in the list. - private void reorderRealmList(Realm realm) { - RealmResults dogs = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); - Owner owner = realm.where(Owner.class).findFirst(); - owner.getDogs().clear(); - for (Dog dog : dogs) { - owner.getDogs().add(dog); - } - } - // Deletes Dogs objects which's columnLong is in the indices array. private void deleteObjects(Realm realm, int... indices) { for (int index : indices) { @@ -130,10 +124,25 @@ private void deleteObjects(Realm realm, int... indices) { // Creates Dogs objects with columnLong set to the value elements in indices array. private void createObjects(Realm realm, int... indices) { for (int index : indices) { - realm.createObject(Dog.class).setAge(index); - } - if (type == ObservablesType.REALM_LIST) { - reorderRealmList(realm); + Dog dog = realm.createObject(Dog.class); + dog.setAge(index); + if (type == ObservablesType.REALM_LIST) { + Owner owner = realm.where(Owner.class).findFirst(); + assertNotNull(owner); + RealmList dogs = owner.getDogs(); + boolean added = false; + // Insert the newly created dog to the RealmList by the order of age. + for (int i = 0; i < dogs.size(); i++) { + if (dog.getAge() <= dogs.get(i).getAge()) { + dogs.add(i, dog); + added = true; + break; + } + } + if (!added) { + dogs.add(dog); + } + } } } @@ -147,9 +156,41 @@ private void modifyObjects(Realm realm, int... indices) { } private void moveObjects(Realm realm, int originAge, int newAge) { - realm.where(Dog.class).equalTo(Dog.FIELD_AGE, originAge).findFirst().setAge(newAge); if (type == ObservablesType.REALM_LIST) { - reorderRealmList(realm); + // For RealmList we need to: + // 1. Find the object by the original age and move it to the new place where it should be with the new age + // set -- the RealmList is sorted by age. + // 2. Set the object's age with new value. + RealmList dogs = realm.where(Owner.class).findFirst().getDogs(); + int originIdx = -1; + int newIdx = -1; + for (int i = 0; i < dogs.size(); i++) { + Dog dog = dogs.get(i); + assertNotNull(dog); + if (dog.getAge() == originAge) { + originIdx = i; + break; + } + } + assertNotEquals(-1, originIdx); + for (int i = 0; i < dogs.size(); i++) { + if (i == originIdx) { + // not precise code, but good enough for testing. + continue; + } + if (newAge <= dogs.get(i).getAge()) { + newIdx = i; + break; + } + } + if (newIdx == -1) { + newIdx = dogs.size() - 1; + } + dogs.get(originIdx).setAge(newAge); + dogs.move(originIdx, newIdx); + } else { + // Since the RealmResults is sorted by age, just simply set the object's age with new value. + realm.where(Dog.class).equalTo(Dog.FIELD_AGE, originAge).findFirst().setAge(newAge); } } @@ -160,7 +201,7 @@ private void registerCheckListener(Realm realm, final ChangesCheck changesCheck) looperThread.keepStrongReference(results); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void onChange(RealmResults collection, @Nullable OrderedCollectionChangeSet changeSet) { changesCheck.check(changeSet); } }); @@ -170,7 +211,7 @@ public void onChange(RealmResults collection, OrderedCollectionChangeSet ch looperThread.keepStrongReference(list); list.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override - public void onChange(RealmList collection, OrderedCollectionChangeSet changeSet) { + public void onChange(RealmList collection, @Nullable OrderedCollectionChangeSet changeSet) { changesCheck.check(changeSet); } }); @@ -416,7 +457,7 @@ public void emptyChangeSet_findAllAsync() { looperThread.keepStrongReference(results); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override - public void onChange(RealmResults collection, OrderedCollectionChangeSet changeSet) { + public void onChange(RealmResults collection, @Nullable OrderedCollectionChangeSet changeSet) { assertSame(collection, results); assertEquals(10, collection.size()); assertNull(changeSet); diff --git a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java index a162848569..52ca14ee0c 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java @@ -16,6 +16,8 @@ package io.realm; +import java.util.Locale; + /** * This interface describes the changes made to a collection during the last update. *

            @@ -95,5 +97,10 @@ public Range(int startIndex, int length) { this.startIndex = startIndex; this.length = length; } + + @Override + public String toString() { + return String.format(Locale.ENGLISH, "startIndex: %d, length: %d", startIndex, length); + } } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 267d9aff0b..c0b9ac8fec 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -63,7 +63,7 @@ public class RealmList extends AbstractList implements OrderedRealmCollect private static final String ONLY_IN_MANAGED_MODE_MESSAGE = "This method is only available in managed mode."; static final String ALLOWED_ONLY_FOR_REALM_MODEL_ELEMENT_MESSAGE = "This feature is available only when the element type is implementing RealmModel."; - public static final String REMOVE_OUTSIDE_TRANSACTION_ERROR = "Objects can only be removed from inside a write transaction."; + private static final String REMOVE_OUTSIDE_TRANSACTION_ERROR = "Objects can only be removed from inside a write transaction."; @Nullable protected Class clazz; @@ -71,11 +71,9 @@ public class RealmList extends AbstractList implements OrderedRealmCollect protected String className; // Always null if RealmList is unmanaged, always non-null if managed. - final ManagedListOperator osListOperator; + private final ManagedListOperator osListOperator; final protected BaseRealm realm; private List unmanagedList; - // Used for listeners on RealmList - private OsResults osResults; /** * Creates a RealmList in unmanaged mode, where the elements are not controlled by a Realm. @@ -966,11 +964,7 @@ private void checkForAddRemoveListener(@Nullable Object listener, boolean checkL */ public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - if (osListOperator.forRealmModel()) { - getOrCreateOsResultsForListener().addListener(this, listener); - } else { - osListOperator.getOsList().addListener(this, listener); - } + osListOperator.getOsList().addListener(this, listener); } /** @@ -983,11 +977,7 @@ public void addChangeListener(OrderedRealmCollectionChangeListener> */ public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - if (osListOperator.forRealmModel()) { - getOrCreateOsResultsForListener().removeListener(this, listener); - } else { - osListOperator.getOsList().removeListener(this, listener); - } + osListOperator.getOsList().removeListener(this, listener); } /** @@ -1025,11 +1015,7 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - if (osListOperator.forRealmModel()) { - getOrCreateOsResultsForListener().addListener(this, listener); - } else { - osListOperator.getOsList().addListener(this, listener); - } + osListOperator.getOsList().addListener(this, listener); } /** @@ -1042,11 +1028,7 @@ public void addChangeListener(RealmChangeListener> listener) { */ public void removeChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); - if (osListOperator.forRealmModel()) { - getOrCreateOsResultsForListener().removeListener(this, listener); - } else { - osListOperator.getOsList().removeListener(this, listener); - } + osListOperator.getOsList().removeListener(this, listener); } /** @@ -1057,11 +1039,7 @@ public void removeChangeListener(RealmChangeListener> listener) { */ public void removeAllChangeListeners() { checkForAddRemoveListener(null, false); - if (osListOperator.forRealmModel()) { - getOrCreateOsResultsForListener().removeAllListeners(); - } else { - osListOperator.getOsList().removeAllListeners(); - } + osListOperator.getOsList().removeAllListeners(); } // Custom RealmList iterator. @@ -1281,20 +1259,6 @@ private ManagedListOperator getOperator(BaseRealm realm, OsList osList, @Null } throw new IllegalArgumentException("Unexpected value class: " + clazz.getName()); } - - // TODO: Object Store is not able to merge change set for links list. Luckily since we were still using LinkView - // when ship the fine grain notifications, the listener on RealmList is actually added to a OS Results which is - // created from the link view. OS Results is computing the change set by comparing the old/new collection. So it - // will give the right results if you remove all elements from a RealmList then add all them back and add one more - // new element. By right results it means the change set only include one insertion. But if the listener is on the - // OS List, the change set will include all ranges of th list. So we keep the old behaviour for - // RealmList for now. See https://github.com/realm/realm-object-store/issues/541 - private OsResults getOrCreateOsResultsForListener() { - if (osResults == null) { - this.osResults = new OsResults(realm.sharedRealm, osListOperator.getOsList(), null); - } - return osResults; - } } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java index 986c18ba6d..93bbc3e7ac 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java @@ -16,6 +16,8 @@ package io.realm.internal; +import java.util.Arrays; + import javax.annotation.Nullable; import io.realm.OrderedCollectionChangeSet; @@ -132,4 +134,22 @@ private Range[] longArrayToRangeArray(int[] longArray) { // Returns the indices array. private native static int[] nativeGetIndices(long nativePtr, int type); + + @Override + public String toString() { + if (nativePtr == 0) { + return "Change set is empty."; + } + + String string = "Deletion Ranges: " + + Arrays.toString(getDeletionRanges()) + + "\n" + + "Insertion Ranges: " + + Arrays.toString(getInsertionRanges()) + + "\n" + + "Change Ranges: " + + Arrays.toString(getChangeRanges()); + return string; + + } } From cd90163433ddc128cbe96531f7fe62f24c17b32e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 21 Nov 2017 18:41:19 +0800 Subject: [PATCH 1105/2110] Evaluate queries immediately for sync queries - OsResults.load() actually does evaluate queries if needed now by using newly exposed OS method Results.evaluate_query_if_needed(). So the Results's mode will be changed to TABLEVIEW from QUERY. This matches the old async query behaviour and solved the performance issues when the query results are huge, the size() method took unnecessary long time even the Results accessor will be called at the next line. close #5328 close #5387 - Update Object Store to 3eb19c014fdf . - Refactor the APIs for creating OsResults. --- CHANGELOG.md | 1 + .../io/realm/OrderedRealmCollectionTests.java | 7 +- .../io/realm/internal/OsResultsTests.java | 54 ++++++------- .../main/cpp/io_realm_internal_OsResults.cpp | 33 +++----- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/RealmList.java | 4 +- .../src/main/java/io/realm/RealmQuery.java | 4 +- .../src/main/java/io/realm/RealmResults.java | 4 +- .../java/io/realm/internal/OsResults.java | 77 ++++++------------- .../java/io/realm/internal/OsSharedRealm.java | 2 +- .../java/io/realm/internal/PendingRow.java | 2 +- 11 files changed, 79 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e10c710f6..e0563dfb1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Bug Fixes * Added missing `toString()` for the implementation of `OrderedCollectionChangeSet`. +* Sync queries are evaluated immediately to solve the performance issue when the query results are huge, `RealmResults.size()` takes too long time (#5387). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java index 5a9ef34cb2..2b6191da8c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionTests.java @@ -428,7 +428,12 @@ public void createSnapshot() { break; case MANAGED_REALMLIST: case REALMRESULTS: - assertEquals(collection.size(), snapshot.size()); + int sizeBeforeChange = collection.size(); + realm.beginTransaction(); + collection.deleteLastFromRealm(); + realm.commitTransaction(); + assertEquals(sizeBeforeChange - 1, collection.size()); + assertEquals(sizeBeforeChange, snapshot.size()); break; default: break; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java index 48590baf9e..de67a7e5df 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java @@ -152,7 +152,7 @@ private void addRow(OsSharedRealm sharedRealm) { @Test public void constructor_withDistinct() { SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(null, table, "firstName"); - OsResults osResults = new OsResults(sharedRealm, table.where(), null, distinctDescriptor); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where(), null, distinctDescriptor); assertEquals(3, osResults.size()); assertEquals("John", osResults.getUncheckedRow(0).getString(0)); @@ -164,7 +164,7 @@ public void constructor_withDistinct() { @Test(expected = UnsupportedOperationException.class) public void constructor_queryIsValidated() { // OsResults's constructor should call TableQuery.validateQuery() - new OsResults(sharedRealm, table.where().or()); + OsResults.createFromQuery(sharedRealm, table.where().or()); } @Test @@ -175,20 +175,20 @@ public void constructor_queryOnDeletedTable() { sharedRealm.commitTransaction(); // Query should be checked before creating OS Results. thrown.expect(IllegalStateException.class); - new OsResults(sharedRealm, query); + OsResults.createFromQuery(sharedRealm, query); } @Test public void size() { - OsResults osResults = new OsResults(sharedRealm, table.where()); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); assertEquals(4, osResults.size()); } @Test public void where() { - OsResults osResults = new OsResults(sharedRealm, table.where()); - OsResults osResults2 = new OsResults(sharedRealm, osResults.where().equalTo(new long[] {0}, oneNullTable, "John")); - OsResults osResults3 = new OsResults(sharedRealm, osResults2.where().equalTo(new long[] {1}, oneNullTable, "Anderson")); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); + OsResults osResults2 = OsResults.createFromQuery(sharedRealm, osResults.where().equalTo(new long[] {0}, oneNullTable, "John")); + OsResults osResults3 = OsResults.createFromQuery(sharedRealm, osResults2.where().equalTo(new long[] {1}, oneNullTable, "Anderson")); // A new native Results should be created. assertTrue(osResults.getNativePtr() != osResults2.getNativePtr()); @@ -201,7 +201,7 @@ public void where() { @Test public void sort() { - OsResults osResults = new OsResults(sharedRealm, table.where().greaterThan(new long[] {2}, oneNullTable, 1)); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where().greaterThan(new long[] {2}, oneNullTable, 1)); SortDescriptor sortDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); OsResults osResults2 = osResults.sort(sortDescriptor); @@ -218,7 +218,7 @@ public void sort() { @Test public void clear() { assertEquals(4, table.size()); - OsResults osResults = new OsResults(sharedRealm, table.where()); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); sharedRealm.beginTransaction(); osResults.clear(); sharedRealm.commitTransaction(); @@ -227,7 +227,7 @@ public void clear() { @Test public void contains() { - OsResults osResults = new OsResults(sharedRealm, table.where()); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); UncheckedRow row = table.getUncheckedRow(0); assertTrue(osResults.contains(row)); } @@ -236,14 +236,14 @@ public void contains() { public void indexOf() { SortDescriptor sortDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); - OsResults osResults = new OsResults(sharedRealm, table.where(), sortDescriptor); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where(), sortDescriptor, null); UncheckedRow row = table.getUncheckedRow(0); assertEquals(3, osResults.indexOf(row)); } @Test public void distinct() { - OsResults osResults = new OsResults(sharedRealm, table.where().lessThan(new long[] {2}, oneNullTable, 4)); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where().lessThan(new long[] {2}, oneNullTable, 4)); SortDescriptor distinctDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); OsResults osResults2 = osResults.distinct(distinctDescriptor); @@ -266,7 +266,7 @@ public void addListener_shouldBeCalledToReturnTheQueryResults() { populateData(sharedRealm); Table table = getTable(sharedRealm); - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); looperThread.keepStrongReference(osResults); osResults.addListener(osResults, new RealmChangeListener() { @Override @@ -287,7 +287,7 @@ public void addListener_shouldBeCalledWhenRefreshToReturnTheQueryResults() { final OsSharedRealm sharedRealm = getSharedRealm(); Table table = getTable(sharedRealm); - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); osResults.addListener(osResults, new RealmChangeListener() { @Override public void onChange(OsResults osResults1) { @@ -304,7 +304,7 @@ public void onChange(OsResults osResults1) { @Test public void addListener_shouldBeCalledWhenRefreshAfterLocalCommit() { final CountDownLatch latch = new CountDownLatch(2); - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); assertEquals(4, osResults.size()); // See `populateData()` osResults.addListener(osResults, new RealmChangeListener() { @Override @@ -332,7 +332,7 @@ public void onChange(OsResults element) { @Test public void addListener_triggeredByRefresh() { final CountDownLatch latch = new CountDownLatch(1); - OsResults osResults = new OsResults(sharedRealm, table.where()); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); osResults.size(); osResults.addListener(osResults, new RealmChangeListener() { @Override @@ -356,7 +356,7 @@ public void addListener_queryNotReturned() { populateData(sharedRealm); Table table = getTable(sharedRealm); - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); looperThread.keepStrongReference(osResults); osResults.addListener(osResults, new RealmChangeListener() { @Override @@ -378,7 +378,7 @@ public void addListener_queryReturned() { populateData(sharedRealm); Table table = getTable(sharedRealm); - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); looperThread.keepStrongReference(osResults); assertEquals(4, osResults.size()); // Trigger the query to run. osResults.addListener(osResults, new RealmChangeListener() { @@ -404,7 +404,7 @@ public void addListener_triggeredByLocalCommit() { Table table = getTable(sharedRealm); final AtomicInteger listenerCounter = new AtomicInteger(0); - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); looperThread.keepStrongReference(osResults); osResults.addListener(osResults, new RealmChangeListener() { @Override @@ -451,7 +451,7 @@ boolean isDetached(OsSharedRealm sharedRealm) { @Test public void collectionIterator_detach_byBeginTransaction() { - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); TestIterator iterator = new TestIterator(osResults); assertFalse(iterator.isDetached(sharedRealm)); sharedRealm.beginTransaction(); @@ -463,14 +463,14 @@ public void collectionIterator_detach_byBeginTransaction() { @Test public void collectionIterator_detach_createdInTransaction() { sharedRealm.beginTransaction(); - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); TestIterator iterator = new TestIterator(osResults); assertTrue(iterator.isDetached(sharedRealm)); } @Test public void collectionIterator_invalid_nonLooperThread_byRefresh() { - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); TestIterator iterator = new TestIterator(osResults); assertFalse(iterator.isDetached(sharedRealm)); sharedRealm.refresh(); @@ -484,7 +484,7 @@ public void collectionIterator_invalid_looperThread_byRemoteTransaction() { final OsSharedRealm sharedRealm = getSharedRealmForLooper(); populateData(sharedRealm); Table table = getTable(sharedRealm); - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); final TestIterator iterator = new TestIterator(osResults); looperThread.keepStrongReference(osResults); assertFalse(iterator.isDetached(sharedRealm)); @@ -506,7 +506,7 @@ public void onChange(OsResults element) { @Test public void collectionIterator_newInstance_throwsWhenSharedRealmIsClosed() { - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); sharedRealm.close(); thrown.expect(IllegalStateException.class); new TestIterator(osResults); @@ -514,7 +514,7 @@ public void collectionIterator_newInstance_throwsWhenSharedRealmIsClosed() { @Test public void getMode() { - OsResults osResults = new OsResults(sharedRealm, table.where()); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); assertTrue(OsResults.Mode.QUERY == osResults.getMode()); osResults.firstUncheckedRow(); // Run the query assertTrue(OsResults.Mode.TABLEVIEW == osResults.getMode()); @@ -522,7 +522,7 @@ public void getMode() { @Test public void createSnapshot() { - OsResults osResults = new OsResults(sharedRealm, table.where()); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); OsResults snapshot = osResults.createSnapshot(); assertTrue(OsResults.Mode.TABLEVIEW == snapshot.getMode()); thrown.expect(IllegalStateException.class); @@ -539,7 +539,7 @@ public void load() { final OsSharedRealm sharedRealm = getSharedRealmForLooper(); looperThread.closeAfterTest(sharedRealm); populateData(sharedRealm); - final OsResults osResults = new OsResults(sharedRealm, table.where()); + final OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); osResults.addListener(osResults, new RealmChangeListener() { @Override public void onChange(OsResults element) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index 26f53313cc..500ed0a8a5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -70,27 +70,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResults(JNI return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResultsFromList(JNIEnv* env, jclass, - jlong shared_realm_ptr, - jlong list_ptr, - jobject j_sort_desc) -{ - TR_ENTER() - try { - auto& list_wrapper = *reinterpret_cast*>(list_ptr); - auto& list = list_wrapper.collection(); - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - Results results = j_sort_desc ? - list.sort(JavaSortDescriptor(env, j_sort_desc).sort_descriptor()) : - list.as_results(); - auto wrapper = new ResultsWrapper(results); - - return reinterpret_cast(wrapper); - } - CATCH_STD() - return reinterpret_cast(nullptr); -} - JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr); @@ -421,3 +400,15 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResultsFrom CATCH_STD() return reinterpret_cast(nullptr); } + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeEvaluateQueryIfNeeded(JNIEnv* env, jclass, + jlong native_ptr, + jboolean wants_notifications) +{ + TR_ENTER_PTR(native_ptr) + try { + auto wrapper = reinterpret_cast(native_ptr); + wrapper->collection().evaluate_query_if_needed(wants_notifications); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index e446a4c73c..3eb19c014f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit e446a4c73c52c70ac3d4eb801e0e0a286e21acbc +Subproject commit 3eb19c014fdfa0f02a03d4acf71d046d29a6dfa6 diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index c0b9ac8fec..a0838b7abe 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -758,14 +758,14 @@ public OrderedRealmCollectionSnapshot createSnapshot() { if (className != null) { return new OrderedRealmCollectionSnapshot<>( realm, - new OsResults(realm.sharedRealm, osListOperator.getOsList(), null), + OsResults.createFromQuery(realm.sharedRealm, osListOperator.getOsList().getQuery()), className); } else { // 'clazz' is non-null when 'dynamicClassName' is null. //noinspection ConstantConditions return new OrderedRealmCollectionSnapshot<>( realm, - new OsResults(realm.sharedRealm, osListOperator.getOsList(), null), + OsResults.createFromQuery(realm.sharedRealm, osListOperator.getOsList().getQuery()), clazz); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 353da2a112..656cd32737 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -2014,7 +2014,7 @@ public E findFirstAsync() { if (realm.isInTransaction()) { // It is not possible to create async query inside a transaction. So immediately query the first object. // See OS Results::prepare_async() - row = new OsResults(realm.sharedRealm, query).firstUncheckedRow(); + row = OsResults.createFromQuery(realm.sharedRealm, query).firstUncheckedRow(); } else { // prepares an empty reference of the RealmObject which is backed by a pending query, // then update it once the query complete in the background. @@ -2050,7 +2050,7 @@ private RealmResults createRealmResults(TableQuery query, @Nullable SortDescriptor distinctDescriptor, boolean loadResults) { RealmResults results; - OsResults osResults = new OsResults(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); + OsResults osResults = OsResults.createFromQuery(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); if (isDynamicQuery()) { results = new RealmResults<>(realm, osResults, className); } else { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 9a553a092f..de4a3c388f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -68,7 +68,7 @@ static RealmResults createBacklinkResults(BaseRealm re Table srcTable = realm.getSchema().getTable(srcTableType); return new RealmResults<>( realm, - OsResults.createBacklinksCollection(realm.sharedRealm, uncheckedRow, srcTable, srcFieldName), + OsResults.createForBacklinks(realm.sharedRealm, uncheckedRow, srcTable, srcFieldName), srcTableType); } @@ -78,7 +78,7 @@ static RealmResults createDynamicBacklinkResults(DynamicReal //noinspection ConstantConditions return new RealmResults<>( realm, - OsResults.createBacklinksCollection(realm.sharedRealm, row, srcTable, srcFieldName), + OsResults.createForBacklinks(realm.sharedRealm, row, srcTable, srcFieldName), srcClassName); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 045472ca98..c810eb9b9b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -85,8 +85,6 @@ public T next() { /** * Not supported by Realm collection iterators. - * - * @throws UnsupportedOperationException */ @Override @Deprecated @@ -138,8 +136,6 @@ public ListIterator(OsResults osResults, int start) { /** * Unsupported by Realm collection iterators. - * - * @throws UnsupportedOperationException */ @Override @Deprecated @@ -193,8 +189,6 @@ public int previousIndex() { /** * Unsupported by RealmResults iterators. - * - * @throws UnsupportedOperationException */ @Override @Deprecated @@ -276,61 +270,37 @@ static Mode getByValue(byte value) { } } - public static OsResults createBacklinksCollection(OsSharedRealm realm, UncheckedRow row, Table srcTable, String srcFieldName) { + public static OsResults createForBacklinks(OsSharedRealm realm, UncheckedRow row, Table srcTable, + String srcFieldName) { long backlinksPtr = nativeCreateResultsFromBacklinks( realm.getNativePtr(), row.getNativePtr(), srcTable.getNativePtr(), srcTable.getColumnIndex(srcFieldName)); - return new OsResults(realm, srcTable, backlinksPtr, true); + return new OsResults(realm, srcTable, backlinksPtr); } - public OsResults(OsSharedRealm sharedRealm, TableQuery query, - @Nullable SortDescriptor sortDescriptor, @Nullable SortDescriptor distinctDescriptor) { + public static OsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query, + @Nullable SortDescriptor sortDescriptor, + @Nullable SortDescriptor distinctDescriptor) { query.validateQuery(); - - this.nativePtr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), + long ptr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), sortDescriptor, distinctDescriptor); - - this.sharedRealm = sharedRealm; - this.context = sharedRealm.context; - this.table = query.getTable(); - this.context.addReference(this); - this.loaded = false; + return new OsResults(sharedRealm, query.getTable(), ptr); } - public OsResults(OsSharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor) { - this(sharedRealm, query, sortDescriptor, null); + public static OsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query) { + return createFromQuery(sharedRealm, query, null, null); } - public OsResults(OsSharedRealm sharedRealm, TableQuery query) { - this(sharedRealm, query, null, null); - } - - public OsResults(OsSharedRealm sharedRealm, OsList osList, @Nullable SortDescriptor sortDescriptor) { - this.nativePtr = nativeCreateResultsFromList(sharedRealm.getNativePtr(), osList.getNativePtr(), sortDescriptor); - - this.sharedRealm = sharedRealm; - this.context = sharedRealm.context; - this.table = osList.getTargetTable(); - this.context.addReference(this); - // OsResults created from OsList is loaded by default. So that the listener won't be triggered with empty - // change set. - this.loaded = true; - } - - private OsResults(OsSharedRealm sharedRealm, Table table, long nativePtr) { - this(sharedRealm, table, nativePtr, false); - } - - OsResults(OsSharedRealm sharedRealm, Table table, long nativePtr, boolean loaded) { + OsResults(OsSharedRealm sharedRealm, Table table, long nativePtr) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; this.table = table; this.nativePtr = nativePtr; this.context.addReference(this); - this.loaded = loaded; + this.loaded = getMode() != Mode.QUERY; } public OsResults createSnapshot() { @@ -477,16 +447,17 @@ public Mode getMode() { return Mode.getByValue(nativeGetMode(nativePtr)); } - // The Results of Object Store will be queried asynchronously in nature. But we do have to support "sync" query by - // Java like RealmQuery.findAll(). + // The Results with mode QUERY will be evaluated asynchronously in Object Store. But we do have to support "sync" + // query by Java like RealmQuery.findAll(). // The flag is used for following cases: - // 1. For sync query, loaded will be set to true when collection is created. So we will bypass the first trigger of - // listener if it comes with empty change set from Object Store since we assume user already got the query - // result. - // 2. For async query, when load() gets called with loaded not set, the listener should be triggered with empty + // 1. When Results is created, loaded will be set to false if the mode is QUERY. For other modes, loaded will be set + // to true. + // 2. For sync query (RealmQuery.findAll()), load() should be called after the Results creation. Then query will be + // evaluated immediately and then loaded will be set to true (And the mode will be changed to TABLEVIEW in OS). + // 3. For async query, when load() gets called with loaded not set, the listener should be triggered with empty // change set since it is considered as query first returned. - // 3. If the listener triggered with empty change set after load() called for async queries, it is treated as the - // same case as 1). + // 4. If the listener triggered with empty change set after load() called for async queries, it is treated as the + // same case as 2). public boolean isLoaded() { return loaded; } @@ -495,6 +466,7 @@ public void load() { if (loaded) { return; } + nativeEvaluateQueryIfNeeded(nativePtr, false); notifyChangeListeners(0); } @@ -503,9 +475,6 @@ public void load() { private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, @Nullable SortDescriptor sortDesc, @Nullable SortDescriptor distinctDesc); - private static native long nativeCreateResultsFromList(long sharedRealmPtr, long listPtr, - @Nullable SortDescriptor sortDesc); - private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); @@ -546,4 +515,6 @@ private static native long nativeCreateResultsFromList(long sharedRealmPtr, long private static native byte nativeGetMode(long nativePtr); private static native long nativeCreateResultsFromBacklinks(long sharedRealmNativePtr, long rowNativePtr, long srcTableNativePtr, long srColIndex); + + private static native void nativeEvaluateQueryIfNeeded(long nativePtr, boolean wantsNotifications); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 7bb3a075af..027e0e71b8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -511,7 +511,7 @@ private void runPartialSyncRegistrationCallback(@Nullable String error, long nat } else { @SuppressWarnings("ConstantConditions") Table table = getTable(Table.getTableNameForClass(callback.className)); - OsResults results = new OsResults(this, table, nativeResultsPtr, true); + OsResults results = new OsResults(this, table, nativeResultsPtr); callback.onSuccess(results); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 3b4b9802be..bfa733cd33 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -40,7 +40,7 @@ public interface FrontEnd { public PendingRow(OsSharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor, final boolean returnCheckedRow) { this.sharedRealm = sharedRealm; - pendingOsResults = new OsResults(sharedRealm, query, sortDescriptor, null); + pendingOsResults = OsResults.createFromQuery(sharedRealm, query, sortDescriptor, null); listener = new RealmChangeListener() { @Override From 8ed3448fc14e505174ece643a232f7c3b112a7b1 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Nov 2017 13:05:59 +0800 Subject: [PATCH 1106/2110] Fix flaky test --- .../syncIntegrationTest/java/io/realm/SyncSessionTests.java | 6 +++++- .../java/io/realm/objectserver/utils/Constants.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 55aab7e5f5..494938d9e1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -12,7 +12,9 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; @@ -309,6 +311,7 @@ public void onChange(RealmResults stringOnlies) { // this test validate the behaviour of SyncSessionStopPolicy::AfterChangesUploaded @Test public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { + final List strongRefs = new ArrayList<>(); final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); @@ -351,6 +354,7 @@ public void run() { .build(); final Realm adminRealm = Realm.getInstance(adminConfig); RealmResults all = adminRealm.where(StringOnly.class).findAll(); + strongRefs.add(all); RealmChangeListener> realmChangeListener = new RealmChangeListener>() { @Override public void onChange(RealmResults stringOnlies) { @@ -369,9 +373,9 @@ public void onChange(RealmResults stringOnlies) { }); TestHelper.awaitOrFail(testCompleted, 60); + handlerThread.join(); user.logout(); - realm.close(); } // A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java index 3f2e74c5ad..70b9cf492b 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -23,7 +23,7 @@ public class Constants { public static final String USER_REALM_2 = "realm://" + HOST + ":9080/~/tests2"; public static final String USER_REALM_SECURE = "realms://" + HOST + ":9443/~/tests"; public static final String SYNC_SERVER_URL = "realm://" + HOST + ":9080/~/tests"; - public static final String SYNC_SERVER_URL_2 = "realm://" + HOST + "/~/tests2"; + public static final String SYNC_SERVER_URL_2 = "realm://" + HOST + ":9080/~/tests2"; public static final String AUTH_SERVER_URL = "http://" + HOST + ":9080/"; public static final String AUTH_URL = AUTH_SERVER_URL + "auth"; From 2d2e7dd750f602d8569504c114254c28c9c9d11e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Nov 2017 17:35:33 +0800 Subject: [PATCH 1107/2110] Deprecate mips (#5561) --- CHANGELOG.md | 13 +++++++++++++ version.txt | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0563dfb1f..c315435bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 4.3.0 (YYYY-MM-DD) + +### Deprecated + +* Support for mips deivces are deprecated. + +### Enhancements + +### Bug Fixes + +### Internal + + ## 4.2.1 (YYYY-MM-DD) ### Enhancements diff --git a/version.txt b/version.txt index d168f1d8bd..9aadf8cf65 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.2.1-SNAPSHOT \ No newline at end of file +4.3.0-SNAPSHOT From cdc65caa369be86062b25a2e8de49ad177a1ce2e Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Nov 2017 17:39:25 +0800 Subject: [PATCH 1108/2110] Fix errorprone issues --- .../java/io/realm/DynamicRealmObjectTests.java | 15 ++++----------- .../ManagedOrderedRealmCollectionTests.java | 8 ++++---- .../io/realm/ManagedRealmCollectionTests.java | 8 ++++---- .../OrderedRealmCollectionIteratorTests.java | 2 ++ .../java/io/realm/RealmObjectTests.java | 17 ++++------------- .../java/io/realm/RealmQueryTests.java | 14 ++------------ .../java/io/realm/RealmSchemaTests.java | 4 ++++ .../androidTest/java/io/realm/RealmTests.java | 8 +------- .../src/androidTest/java/io/realm/SortTest.java | 2 +- .../java/io/realm/rule/RunInLooperThread.java | 8 ++++---- .../src/main/java/io/realm/RealmCache.java | 4 ++-- .../java/io/realm/PermissionManager.java | 4 +--- 12 files changed, 33 insertions(+), 61 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 3ac4e327db..0acf690f3c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -220,8 +220,8 @@ public void run() { try { callThreadConfinedMethod(obj, method); fail("IllegalStateException must be thrown."); - } catch (Throwable e) { - if (e instanceof IllegalStateException && expectedMessage.equals(e.getMessage())) { + } catch (IllegalStateException e) { + if (expectedMessage.equals(e.getMessage())) { // expected exception continue; } @@ -1613,15 +1613,14 @@ public void getRealm_illegalThreadThrows() throws Throwable { final DynamicRealmObject object = dynamicRealm.where(AllTypes.CLASS_NAME).findFirst(); final CountDownLatch threadFinished = new CountDownLatch(1); - final AtomicReference throwable = new AtomicReference<>(); final Thread thread = new Thread(new Runnable() { @Override public void run() { try { object.getDynamicRealm(); fail(); - } catch (Throwable t) { - throwable.set(t); + } catch (IllegalStateException e) { + assertEquals(BaseRealm.INCORRECT_THREAD_MESSAGE, e.getMessage()); } finally { threadFinished.countDown(); } @@ -1629,11 +1628,5 @@ public void run() { }); thread.start(); TestHelper.awaitOrFail(threadFinished); - - final Throwable thrownInTheThread = throwable.get(); - if (!(thrownInTheThread instanceof IllegalStateException)) { - throw thrownInTheThread; - } - assertEquals(BaseRealm.INCORRECT_THREAD_MESSAGE, thrownInTheThread.getMessage()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index c50e59cc54..366d4395f9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -779,10 +779,10 @@ public void mutableMethodsOutsideTransactions() { case REMOVE_INDEX: collection.remove(0); break; } fail("Unknown method or it failed to throw: " + method); - } catch (Throwable t) { - if (!t.getClass().equals(expected)) { - fail(method + " didn't throw the expected exception. Was: " + t + ", expected: " + expected); - } + } catch (IllegalStateException e) { + assertEquals(expected, e.getClass()); + } catch (UnsupportedOperationException e) { + assertEquals(expected, e.getClass()); } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 96fc619417..b27d7b3796 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -784,10 +784,10 @@ public void mutableMethodsOutsideTransactions() { case RETAIN_ALL: collection.retainAll(Collections.singletonList(new AllJavaTypes())); break; } fail("Unknown method or it failed to throw: " + method); - } catch (Throwable t) { - if (!t.getClass().equals(expected)) { - fail(method + " didn't throw the expected exception. Was: " + t + ", expected: " + expected); - } + } catch (IllegalStateException e) { + assertEquals(expected, e.getClass()); + } catch (UnsupportedOperationException e) { + assertEquals(expected, e.getClass()); } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index d7e705a0fd..706182e5cc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -429,6 +429,7 @@ public void listIterator_remove_beforeNext() { try { it.remove(); + fail(); } catch (IllegalStateException e) { assertRealmList(); } catch (UnsupportedOperationException e) { @@ -612,6 +613,7 @@ public void listIterator_set() { assertEquals(42, obj.getFieldLong()); } + @Test public void listIterator_add() { if (skipTest(CollectionClass.REALMRESULTS)) { return; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 667336ce69..8d99101714 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -2080,23 +2080,20 @@ public void getRealm_illegalThreadThrows() throws Throwable { realm.commitTransaction(); final CountDownLatch threadFinished = new CountDownLatch(1); - final AtomicReference throwable = new AtomicReference<>(); final Thread thread = new Thread(new Runnable() { @Override public void run() { try { object.getRealm(); fail(); - } catch (Throwable t) { - throwable.set(t); - threadFinished.countDown(); - return; + } catch (IllegalStateException e) { + assertEquals(BaseRealm.INCORRECT_THREAD_MESSAGE, e.getMessage()); } try { RealmObject.getRealm(object); fail(); - } catch (Throwable t) { - throwable.set(t); + } catch (IllegalStateException e) { + assertEquals(BaseRealm.INCORRECT_THREAD_MESSAGE, e.getMessage()); } finally { threadFinished.countDown(); } @@ -2104,12 +2101,6 @@ public void run() { }); thread.start(); TestHelper.awaitOrFail(threadFinished); - - final Throwable thrownInTheThread = throwable.get(); - if (!(thrownInTheThread instanceof IllegalStateException)) { - throw thrownInTheThread; - } - assertEquals(BaseRealm.INCORRECT_THREAD_MESSAGE, thrownInTheThread.getMessage()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 35456d248c..e1a1d52b6f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -357,7 +357,6 @@ private static void callThreadConfinedMethod(RealmQuery query, ThreadConfined public void callThreadConfinedMethodsFromWrongThread() throws Throwable { final RealmQuery query = realm.where(AllJavaTypes.class); - final AtomicReference throwableFromThread = new AtomicReference(); final CountDownLatch testFinished = new CountDownLatch(1); final String expectedMessage; @@ -380,13 +379,8 @@ public void run() { try { callThreadConfinedMethod(query, method); fail("IllegalStateException must be thrown."); - } catch (Throwable e) { - if (e instanceof IllegalStateException && expectedMessage.equals(e.getMessage())) { - // expected exception - continue; - } - throwableFromThread.set(e); - return; + } catch (IllegalStateException e) { + assertEquals(expectedMessage, e.getMessage()); } } } finally { @@ -397,10 +391,6 @@ public void run() { thread.start(); TestHelper.awaitOrFail(testFinished); - final Throwable throwable = throwableFromThread.get(); - if (throwable != null) { - throw throwable; - } } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index 7c12ddba71..73db5a1f65 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -628,24 +628,28 @@ public void mutableMethodsCalled_notInTransaction() { try { realmSchema.create("Foo"); + fail(); } catch (IllegalStateException expected) { assertThat(expected.getMessage(), CoreMatchers.containsString("transaction")); } try { realmSchema.createWithPrimaryKeyField("Foo", "PK", String.class); + fail(); } catch (IllegalStateException expected) { assertThat(expected.getMessage(), CoreMatchers.containsString("transaction")); } try { realmSchema.remove("Cat"); + fail(); } catch (IllegalStateException expected) { assertThat(expected.getMessage(), CoreMatchers.containsString("transaction")); } try { realmSchema.rename("Cat", "Foo1"); + fail(); } catch (IllegalStateException expected) { assertThat(expected.getMessage(), CoreMatchers.containsString("transaction")); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 7d872718ba..509d05f3eb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -4017,7 +4017,6 @@ public void run() { @Test public void waitForChange_onLooperThread() throws Throwable { final CountDownLatch bgRealmClosed = new CountDownLatch(1); - final ExceptionHolder bgError = new ExceptionHolder(); Thread thread = new Thread(new Runnable() { @Override @@ -4027,8 +4026,7 @@ public void run() { try { realm.waitForChange(); fail(); - } catch (Throwable expected) { - bgError.setException(expected); + } catch (IllegalStateException ignored) { } finally { realm.close(); bgRealmClosed.countDown(); @@ -4038,10 +4036,6 @@ public void run() { thread.start(); TestHelper.awaitOrFail(bgRealmClosed); - if (bgError.getException() instanceof AssertionError) { - throw bgError.getException(); - } - assertEquals(IllegalStateException.class, bgError.getException().getClass()); } // Cannot wait inside of a transaction. diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 287d17bf44..06e6792365 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -619,7 +619,7 @@ private int factorial(int n) { @Test public void sortCaseSensitive() { chars = "'- !\"#$%&()*,./:;?_+<=>123aAbBcCxXyYzZ"; - createAndTest(new StringBuffer(chars).reverse().toString()); + createAndTest(new StringBuilder(chars).reverse().toString()); // try all permutations - keep the list short chars = "12aAbB"; diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java index ebc149f8c5..31d73961f2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java @@ -80,7 +80,7 @@ public class RunInLooperThread extends TestRealmConfigurationFactory { // events (Callbacks happening in the future), so we add a strong reference // to them for the duration of the test. // Access guarded by 'lock' - private LinkedList keepStrongReference; + private List keepStrongReference; // Custom Realm used by the test. Saving the reference here will guarantee // that the instance is closed when exiting the test. @@ -266,9 +266,9 @@ protected void before() throws Throwable { super.before(); RealmConfiguration config = createConfiguration(UUID.randomUUID().toString()); - LinkedList refs = new LinkedList<>(); - List realms = new LinkedList<>(); - LinkedList closeables = new LinkedList<>(); + List refs = new ArrayList<>(); + List realms = new ArrayList<>(); + List closeables = new ArrayList<>(); synchronized (lock) { realmConfiguration = config; diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 1399b3e22f..3dac06e938 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -20,11 +20,11 @@ import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; @@ -200,7 +200,7 @@ public void run() { // are not allowed and an exception will be thrown when trying to add it to the cache list. // A weak ref is used to hold the RealmCache instance. The weak ref entry will be cleared if and only if there // is no Realm instance holding a strong ref to it and there is no Realm instance associated it is BEING created. - private static final List> cachesList = new LinkedList>(); + private static final List> cachesList = new ArrayList>(); // See leak() // isLeaked flag is used to avoid adding strong ref multiple times without iterating the list. diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java index 9d1acc3527..d41906a017 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java @@ -24,10 +24,8 @@ import java.net.URL; import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -142,7 +140,7 @@ public boolean isGlobalRealm() { private Realm defaultPermissionRealm; // Task list used to queue tasks until the underlying Realms are done opening (or failed doing so). - private Deque delayedTasks = new LinkedList<>(); + private List delayedTasks = new ArrayList<>(); // List of tasks that are being processed. Used to keep strong references for listeners to work. // The task must remove itself from this list once it either completes From 2554de24847e8c539a6b423364ecfd6245eb48cd Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Nov 2017 17:55:09 +0800 Subject: [PATCH 1109/2110] Update android gradle plugin to alpha03 --- gradle-plugin/build.gradle | 3 ++- .../src/test/groovy/io/realm/gradle/PluginTest.groovy | 4 ++-- library-benchmarks/build.gradle | 2 +- realm-transformer/build.gradle | 4 +++- realm/build.gradle | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 0f8fa1f70d..6524932ee5 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -22,6 +22,7 @@ props.each { key, val -> repositories { mavenLocal() + google() jcenter() } @@ -51,7 +52,7 @@ dependencies { and this https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ for more info. */ compile 'com.neenbedankt.gradle.plugins:android-apt:1.8' //TODO: https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ - provided 'com.android.tools.build:gradle:3.1.0-alpha01' + provided 'com.android.tools.build:gradle:3.1.0-alpha03' testCompile gradleTestKit() testCompile 'junit:junit:4.12' diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index bca3c3a3dd..2f9080ebd8 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -53,7 +53,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha01' + classpath 'com.android.tools.build:gradle:3.1.0-alpha03' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } @@ -78,7 +78,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha01' + classpath 'com.android.tools.build:gradle:3.1.0-alpha03' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index a613196fa2..b33a32eba1 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha01' + classpath 'com.android.tools.build:gradle:3.1.0-alpha03' classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" } } diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 1b1f53ac6c..c2a125116c 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -1,5 +1,6 @@ buildscript { repositories { + google() jcenter() } dependencies { @@ -36,6 +37,7 @@ targetCompatibility = '1.8' repositories { mavenLocal() + google() jcenter() } @@ -58,7 +60,7 @@ dependencies { compile localGroovy() compile gradleApi() compile "io.realm:realm-annotations:${version}" - provided 'com.android.tools.build:gradle:3.1.0-alpha01' + provided 'com.android.tools.build:gradle:3.1.0-alpha03' compile 'org.javassist:javassist:3.22.0-GA' testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { diff --git a/realm/build.gradle b/realm/build.gradle index 71a29354b6..ce69d4e851 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha01' + classpath 'com.android.tools.build:gradle:3.1.0-alpha03' classpath 'de.undercouch:gradle-download-task:3.3.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' From b68b0196b8dad82f0b5daf22420c3def1e604311 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Nov 2017 18:01:52 +0800 Subject: [PATCH 1110/2110] Fix format & typo in readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f93fac19a3..dea721885f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![Realm](logo.png) [![bintray](https://api.bintray.com/packages/realm/maven/realm-gradle-plugin/images/download.svg) ](https://bintray.com/realm/maven/realm-gradle-plugin/_latestVersion) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/realm/realm-java/blob/master/LICENSE) +[![License](https://img.shields.io/badge/License-Apache-blue.svg)](https://github.com/realm/realm-java/blob/master/LICENSE) Realm is a mobile database that runs directly inside phones, tablets or wearables. This repository holds the source code for the Java version of Realm, which currently runs only on Android. @@ -33,7 +33,7 @@ The API reference is located at [realm.io/docs/java/api](https://realm.io/docs/j If you want to test recent bugfixes or features that have not been packaged in an official release yet, you can use a **-SNAPSHOT** release of the current development version of Realm via Gradle, available on [JFrog OSS](http://oss.jfrog.org/oss-snapshot-local/io/realm/realm-gradle-plugin/) -```In build.gradle +``` buildscript { repositories { jcenter() @@ -161,7 +161,7 @@ Generating the Javadoc using the command above may generate warnings. The Javado ### Upgrading Gradle Wrappers - All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer `gradleVersion` property defined in `/realm.properties` in order to determine Gradle Version of generating wrappers. + All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer to `gradleVersion` property defined in `/realm.properties` in order to determine Gradle Version of generating wrappers. We have a script `./tools/update_gradle_wrapper.sh` to automate these steps. When you update Gradle Wrappers, please obey the following steps. 1. Edit `gradleVersion` property in defined in `/realm.properties` to new Gradle Wrapper version. From 632e0bf162c33dc7be413f6111422498c0342a8b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Nov 2017 19:08:34 +0800 Subject: [PATCH 1111/2110] Update gradle to 4.3.1 build tools to 27.0.1 --- CHANGELOG.md | 4 ++-- Dockerfile | 2 +- README.md | 2 +- examples/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/newsreaderExample/build.gradle | 4 ++-- examples/objectServerExample/build.gradle | 4 ++-- examples/rxJavaExample/build.gradle | 2 +- .../secureTokenAndroidKeyStore/build.gradle | 2 +- examples/threadExample/build.gradle | 2 +- examples/unitTestExample/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54727 -> 54731 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 54727 -> 54731 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54727 -> 54731 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54727 -> 54731 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/realm-library/build.gradle | 2 +- 23 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58299f888c..4094f4e5bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,12 @@ ### Internal * Use `OsList` instead of `OsResults` to add notification token on for `RealmList`. -* Updated gralde and plugins to support Android Studio `3.0.0` (#5472). +* Updated Gralde and plugins to support Android Studio `3.0.0` (#5472). ### Credits * Thanks to @tbsandee for fixing a typo (#5548). -* Thanks to @vivekkiran for updating gralde and plugins to support Android Studio `3.0.0` (#5472). +* Thanks to @vivekkiran for updating Gralde and plugins to support Android Studio `3.0.0` (#5472). ## 4.2.0 (2017-11-17) diff --git a/Dockerfile b/Dockerfile index 2160e4e180..94700dcac9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ RUN sdkmanager --update # Accept all licenses RUN yes y | sdkmanager --licenses RUN sdkmanager 'platform-tools' -RUN sdkmanager 'build-tools;27.0.0' +RUN sdkmanager 'build-tools;27.0.1' RUN sdkmanager 'extras;android;m2repository' RUN sdkmanager 'platforms;android-27' RUN sdkmanager 'cmake;3.6.4111459' diff --git a/README.md b/README.md index dea721885f..d7b70b59ba 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install the Android SDK **Build-Tools 27.0.0**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). + * Download & install the Android SDK **Build-Tools 27.0.1**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * If you use Android Studio, Android Studio 3.0 or higher is required. diff --git a/examples/build.gradle b/examples/build.gradle index 03a268e979..317a72b51c 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,6 +1,6 @@ project.ext.sdkVersion = 27 project.ext.minSdkVersion = 15 -project.ext.buildTools = '27.0.0' +project.ext.buildTools = '27.0.1' // Don't cache SNAPSHOT (changing) dependencies. configurations.all { diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 5161f013c5..702c4b68b8 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index e62469dbc8..60271b0fbd 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -39,9 +39,9 @@ android { dependencies { //noinspection GradleDependency - implementation 'com.android.support:appcompat-v7:27.0.0' + implementation 'com.android.support:appcompat-v7:27.0.1' //noinspection GradleDependency - implementation 'com.android.support:design:27.0.0' + implementation 'com.android.support:design:27.0.1' implementation 'com.jakewharton.timber:timber:4.5.1' implementation 'com.jakewharton:butterknife:8.5.1' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index e9fdc318c2..5044b5d4eb 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -57,8 +57,8 @@ realm { } dependencies { - implementation 'com.android.support:appcompat-v7:27.0.0' - implementation 'com.android.support:design:27.0.0' + implementation 'com.android.support:appcompat-v7:27.0.1' + implementation 'com.android.support:design:27.0.1' implementation 'me.zhanghai.android.materialprogressbar:library:1.3.0' implementation 'com.jakewharton:butterknife:8.8.1'//TODO:Can be refactored with Native Android Data Binding annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'//TODO:Can be refactored with Native Android Data Binding diff --git a/examples/rxJavaExample/build.gradle b/examples/rxJavaExample/build.gradle index 5fda79ed33..d08e9d7f73 100644 --- a/examples/rxJavaExample/build.gradle +++ b/examples/rxJavaExample/build.gradle @@ -37,7 +37,7 @@ android { dependencies { implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' implementation 'io.reactivex.rxjava2:rxjava:2.1.0' - implementation 'com.android.support:appcompat-v7:27.0.0' + implementation 'com.android.support:appcompat-v7:27.0.1' implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' implementation 'com.squareup.retrofit2:converter-jackson:2.3.0' diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index cd41f2ce4e..d9a629ca4f 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -33,7 +33,7 @@ dependencies { androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', { exclude group: 'com.android.support', module: 'support-annotations' }) - implementation 'com.android.support:appcompat-v7:27.0.0' + implementation 'com.android.support:appcompat-v7:27.0.1' testImplementation 'junit:junit:4.12' implementation 'io.realm:secure-userstore:1.0.1' } diff --git a/examples/threadExample/build.gradle b/examples/threadExample/build.gradle index 4dbe2aa322..1454c3f5e2 100644 --- a/examples/threadExample/build.gradle +++ b/examples/threadExample/build.gradle @@ -25,5 +25,5 @@ android { dependencies { //noinspection GradleDependency - implementation 'com.android.support:appcompat-v7:27.0.0' + implementation 'com.android.support:appcompat-v7:27.0.1' } diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index faaefe45bb..fb80e93400 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -34,7 +34,7 @@ android { dependencies { - implementation 'com.android.support:appcompat-v7:27.0.0' + implementation 'com.android.support:appcompat-v7:27.0.1' testImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 27768f1bbac3ce2d055b20d521f12da78d331e8e..6b6ea3ab4ff4f69d55c5fd9c0a6ac70f47d41008 100644 GIT binary patch delta 716 zcmYk4Pe_wt9LL}1H=UY^ubHN=o70uBUNL848V=e@bEPvulf1~pMiREdsDog3=v0H5 zU(`rL@R$$@dFWD;rkieaHfIH0x^xH+Vu$DtL2o}SeNUh7=l6%_;dw)|y2z|g=IL?_7-hvT0<$J-B(z`TgOd%2k)D>=r-%|+H??GA(g7ktgK-NnJO4tE3z>W zRw?5c#}DC?WUOIqsmbEgJaEdH{|+ii!*DOR(1B)dX?A5{ZTU1ATsturwiT6}9UO;N zn#j_H(ue`wUAdTz$w-YTA_XOpiZPr6WTe zm3X7*gFAkc^!HeNkDn!rkI~~R!ksmhR6iVvqqyeJ$ESphA8QI3M_IW?W!%+N_gjU^ z$5?sCa=0s~Da(YWDv_PAicHuOSLyQS=QfTP{r~Z_S1H(i8I68ne4||78T>2%00CVJ AkpKVy delta 743 zcmYk4T}YEr7{_;>#pYn;TO*yX)5)~8UYku*EL)_6B8E9~M3Ph>(^a4`2r4ma;>6bUC=Sbe1M`8Qh17 zu!MBJr01hD-%kF&-ZEnz#+N1R7_%Eb8%2Y@poB|>hRg!z%`y%zNl=GnI%W~2>Tm-U z*+q2ObNMKRv=su+*J!a7uA;)44DQ9r6@>8#K$mZp-09Uk?>N*Jya%aSV+{8 zWk9&%WFvJCia3;PqdgBr{FQ7X>#&GDsYbGmh32&1Uo~+5V^_-Wg#fFwZ_AS)~L~nnfSjeHNYPJPt#!%FDdkQu}MjUKj%?Tv2s8m*gEh1u`Y`1iSpe;WSHa?OR{ GANUR6YXqMF diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 590f0e81da..702c4b68b8 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 27768f1bbac3ce2d055b20d521f12da78d331e8e..6b6ea3ab4ff4f69d55c5fd9c0a6ac70f47d41008 100644 GIT binary patch delta 716 zcmYk4Pe_wt9LL}1H=UY^ubHN=o70uBUNL848V=e@bEPvulf1~pMiREdsDog3=v0H5 zU(`rL@R$$@dFWD;rkieaHfIH0x^xH+Vu$DtL2o}SeNUh7=l6%_;dw)|y2z|g=IL?_7-hvT0<$J-B(z`TgOd%2k)D>=r-%|+H??GA(g7ktgK-NnJO4tE3z>W zRw?5c#}DC?WUOIqsmbEgJaEdH{|+ii!*DOR(1B)dX?A5{ZTU1ATsturwiT6}9UO;N zn#j_H(ue`wUAdTz$w-YTA_XOpiZPr6WTe zm3X7*gFAkc^!HeNkDn!rkI~~R!ksmhR6iVvqqyeJ$ESphA8QI3M_IW?W!%+N_gjU^ z$5?sCa=0s~Da(YWDv_PAicHuOSLyQS=QfTP{r~Z_S1H(i8I68ne4||78T>2%00CVJ AkpKVy delta 743 zcmYk4T}YEr7{_;>#pYn;TO*yX)5)~8UYku*EL)_6B8E9~M3Ph>(^a4`2r4ma;>6bUC=Sbe1M`8Qh17 zu!MBJr01hD-%kF&-ZEnz#+N1R7_%Eb8%2Y@poB|>hRg!z%`y%zNl=GnI%W~2>Tm-U z*+q2ObNMKRv=su+*J!a7uA;)44DQ9r6@>8#K$mZp-09Uk?>N*Jya%aSV+{8 zWk9&%WFvJCia3;PqdgBr{FQ7X>#&GDsYbGmh32&1Uo~+5V^_-Wg#fFwZ_AS)~L~nnfSjeHNYPJPt#!%FDdkQu}MjUKj%?Tv2s8m*gEh1u`Y`1iSpe;WSHa?OR{ GANUR6YXqMF diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 590f0e81da..702c4b68b8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index 5161f013c5..702c4b68b8 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index 27768f1bbac3ce2d055b20d521f12da78d331e8e..6b6ea3ab4ff4f69d55c5fd9c0a6ac70f47d41008 100644 GIT binary patch delta 716 zcmYk4Pe_wt9LL}1H=UY^ubHN=o70uBUNL848V=e@bEPvulf1~pMiREdsDog3=v0H5 zU(`rL@R$$@dFWD;rkieaHfIH0x^xH+Vu$DtL2o}SeNUh7=l6%_;dw)|y2z|g=IL?_7-hvT0<$J-B(z`TgOd%2k)D>=r-%|+H??GA(g7ktgK-NnJO4tE3z>W zRw?5c#}DC?WUOIqsmbEgJaEdH{|+ii!*DOR(1B)dX?A5{ZTU1ATsturwiT6}9UO;N zn#j_H(ue`wUAdTz$w-YTA_XOpiZPr6WTe zm3X7*gFAkc^!HeNkDn!rkI~~R!ksmhR6iVvqqyeJ$ESphA8QI3M_IW?W!%+N_gjU^ z$5?sCa=0s~Da(YWDv_PAicHuOSLyQS=QfTP{r~Z_S1H(i8I68ne4||78T>2%00CVJ AkpKVy delta 743 zcmYk4T}YEr7{_;>#pYn;TO*yX)5)~8UYku*EL)_6B8E9~M3Ph>(^a4`2r4ma;>6bUC=Sbe1M`8Qh17 zu!MBJr01hD-%kF&-ZEnz#+N1R7_%Eb8%2Y@poB|>hRg!z%`y%zNl=GnI%W~2>Tm-U z*+q2ObNMKRv=su+*J!a7uA;)44DQ9r6@>8#K$mZp-09Uk?>N*Jya%aSV+{8 zWk9&%WFvJCia3;PqdgBr{FQ7X>#&GDsYbGmh32&1Uo~+5V^_-Wg#fFwZ_AS)~L~nnfSjeHNYPJPt#!%FDdkQu}MjUKj%?Tv2s8m*gEh1u`Y`1iSpe;WSHa?OR{ GANUR6YXqMF diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 590f0e81da..702c4b68b8 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 27768f1bbac3ce2d055b20d521f12da78d331e8e..6b6ea3ab4ff4f69d55c5fd9c0a6ac70f47d41008 100644 GIT binary patch delta 716 zcmYk4Pe_wt9LL}1H=UY^ubHN=o70uBUNL848V=e@bEPvulf1~pMiREdsDog3=v0H5 zU(`rL@R$$@dFWD;rkieaHfIH0x^xH+Vu$DtL2o}SeNUh7=l6%_;dw)|y2z|g=IL?_7-hvT0<$J-B(z`TgOd%2k)D>=r-%|+H??GA(g7ktgK-NnJO4tE3z>W zRw?5c#}DC?WUOIqsmbEgJaEdH{|+ii!*DOR(1B)dX?A5{ZTU1ATsturwiT6}9UO;N zn#j_H(ue`wUAdTz$w-YTA_XOpiZPr6WTe zm3X7*gFAkc^!HeNkDn!rkI~~R!ksmhR6iVvqqyeJ$ESphA8QI3M_IW?W!%+N_gjU^ z$5?sCa=0s~Da(YWDv_PAicHuOSLyQS=QfTP{r~Z_S1H(i8I68ne4||78T>2%00CVJ AkpKVy delta 743 zcmYk4T}YEr7{_;>#pYn;TO*yX)5)~8UYku*EL)_6B8E9~M3Ph>(^a4`2r4ma;>6bUC=Sbe1M`8Qh17 zu!MBJr01hD-%kF&-ZEnz#+N1R7_%Eb8%2Y@poB|>hRg!z%`y%zNl=GnI%W~2>Tm-U z*+q2ObNMKRv=su+*J!a7uA;)44DQ9r6@>8#K$mZp-09Uk?>N*Jya%aSV+{8 zWk9&%WFvJCia3;PqdgBr{FQ7X>#&GDsYbGmh32&1Uo~+5V^_-Wg#fFwZ_AS)~L~nnfSjeHNYPJPt#!%FDdkQu}MjUKj%?Tv2s8m*gEh1u`Y`1iSpe;WSHa?OR{ GANUR6YXqMF diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 590f0e81da..702c4b68b8 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/realm.properties b/realm.properties index 3eaf1f35b4..fd53887478 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=4.3 +gradleVersion=4.3.1 ndkVersion=r10e diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 5161f013c5..702c4b68b8 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-rc-3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 81abfe6ee7..016e5e4c19 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -41,7 +41,7 @@ ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProper android { compileSdkVersion 27 - buildToolsVersion '27.0.0' + buildToolsVersion '27.0.1' defaultConfig { minSdkVersion 9 From bcea3cddf2e963c011b299f62ef69094281a0e35 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Nov 2017 19:41:05 +0800 Subject: [PATCH 1112/2110] Fix example build issues --- examples/multiprocessExample/build.gradle | 2 +- examples/threadExample/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/multiprocessExample/build.gradle b/examples/multiprocessExample/build.gradle index 1823f58ecf..f4e84dceaf 100644 --- a/examples/multiprocessExample/build.gradle +++ b/examples/multiprocessExample/build.gradle @@ -25,6 +25,6 @@ android { } dependencies { - implementation 'com.android.support:appcompat-v7:26.0.1' + implementation 'com.android.support:appcompat-v7:27.0.1' } diff --git a/examples/threadExample/build.gradle b/examples/threadExample/build.gradle index 1454c3f5e2..c0188a8bb5 100644 --- a/examples/threadExample/build.gradle +++ b/examples/threadExample/build.gradle @@ -25,5 +25,5 @@ android { dependencies { //noinspection GradleDependency - implementation 'com.android.support:appcompat-v7:27.0.1' + implementation 'com.android.support:appcompat-v7:24.0.0' } From b71410d773ef16f9ddff043d0a56a23fb986eea7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Nov 2017 23:25:06 +0800 Subject: [PATCH 1113/2110] Fix realm-annotations-processor tests --- realm/realm-annotations-processor/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index c97c1e3635..5c203099a7 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -15,7 +15,7 @@ dependencies { testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it testCompile group:'junit', name:'junit', version:'4.12' testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' - testCompile files(file("${System.env.ANDROID_HOME}/platforms/android-26/android.jar")) + testCompile files(file("${System.env.ANDROID_HOME}/platforms/android-27/android.jar")) } // for Ant filter From 5dcab1b1345555aeacba83b1d3b527df07f49efc Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 24 Nov 2017 10:44:53 +0800 Subject: [PATCH 1114/2110] Fix listIterator_add test --- .../OrderedRealmCollectionIteratorTests.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index 706182e5cc..b173530c82 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -42,6 +42,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -615,21 +616,23 @@ public void listIterator_set() { @Test public void listIterator_add() { - if (skipTest(CollectionClass.REALMRESULTS)) { + if (skipTest(CollectionClass.REALMRESULTS, CollectionClass.REALMRESULTS_SNAPSHOT_RESULTS_BASE, + CollectionClass.REALMRESULTS_SNAPSHOT_LIST_BASE)) { return; } realm.beginTransaction(); ListIterator it = collection.listIterator(); - // Calling set() before next() should throw. - try { - it.add(new AllJavaTypes()); - fail(); - } catch (IllegalStateException ignored) { - } + // The element is inserted immediately before the element that would be returned by next(), if any, and after + // the element that would be returned by previous(), if any. (If the list contains no elements, the new element + // becomes the sole element on the list.) + it.add(new AllJavaTypes(4242)); + AllJavaTypes obj = collection.first(); + assertNotNull(obj); + assertEquals(4242, obj.getFieldLong()); - AllJavaTypes obj = it.next(); + obj = it.next(); assertEquals(0, obj.getFieldLong()); it.add(new AllJavaTypes(42)); obj = it.previous(); From af07ea7d9da4f6dff26ae7192b4a918735577b02 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Fri, 24 Nov 2017 10:46:35 +0800 Subject: [PATCH 1115/2110] Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c315435bcf..b9db98ed37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Deprecated -* Support for mips deivces are deprecated. +* Support for mips devices are deprecated. ### Enhancements From b27eccc14ccbcc0edfc4f77b3c2d12e680b3bd6a Mon Sep 17 00:00:00 2001 From: Madis Pink Date: Mon, 27 Nov 2017 10:06:39 +0200 Subject: [PATCH 1116/2110] Ensure stable classes order in generated mediators (#5567) Fixes #5566. --- .../src/main/java/io/realm/processor/ModuleMetaData.java | 3 ++- .../src/main/java/io/realm/processor/RealmProcessor.java | 3 ++- .../test/resources/io/realm/RealmDefaultModuleMediator.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java index a9df486007..af2bf3cc35 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -81,7 +82,7 @@ public boolean generate(Set clazzes) { if (module.allClasses()) { classes = availableClasses; } else { - classes = new HashSet(); + classes = new LinkedHashSet(); Set classNames = getClassMetaDataFromModule(classElement); for (String fullyQualifiedClassName : classNames) { ClassMetaData metadata = classMetaData.get(fullyQualifiedClassName); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index 1d68cae873..713ab6b2ef 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -132,7 +133,7 @@ public class RealmProcessor extends AbstractProcessor { // List of all fields maintained by Realm (RealmResults) - private final Set classesToValidate = new HashSet(); + private final Set classesToValidate = new LinkedHashSet(); // List of backlinks private final Set backlinksToValidate = new HashSet(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 29e965598d..1a43964296 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -33,7 +34,7 @@ class DefaultRealmModuleMediator extends RealmProxyMediator { @Override public Map, OsObjectSchemaInfo> getExpectedObjectSchemaInfoMap() { Map, OsObjectSchemaInfo> infoMap = - new HashMap, OsObjectSchemaInfo>(1); + new LinkedHashMap, OsObjectSchemaInfo>(1); infoMap.put(some.test.AllTypes.class, io.realm.AllTypesRealmProxy.getExpectedObjectSchemaInfo()); return infoMap; } From 84a1d7a99fc2d20351c5a5edd30a47d22089a0d8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 27 Nov 2017 09:12:03 +0100 Subject: [PATCH 1117/2110] Add credits for improved incremental compiler support --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76933384dd..f5ac70303e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,16 @@ ### Enhancements +* The Realm annotation processor now has a stable output when there are no changes to model classes, improving support for incremental compilers (#5567). + ### Bug Fixes ### Internal +### Credits + +* Thanks to @madisp for adding better support for incremental compilers (#5567). + ## 4.2.1 (YYYY-MM-DD) From 3a73c0012db79d4dd1f5ed85cb71d02ac6dd9402 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 27 Nov 2017 09:53:13 +0100 Subject: [PATCH 1118/2110] Revert broken unit test --- .../test/resources/io/realm/RealmDefaultModuleMediator.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 1a43964296..80a30b44ef 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -14,7 +14,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -33,8 +32,7 @@ class DefaultRealmModuleMediator extends RealmProxyMediator { @Override public Map, OsObjectSchemaInfo> getExpectedObjectSchemaInfoMap() { - Map, OsObjectSchemaInfo> infoMap = - new LinkedHashMap, OsObjectSchemaInfo>(1); + Map, OsObjectSchemaInfo> infoMap = new HashMap, OsObjectSchemaInfo>(1); infoMap.put(some.test.AllTypes.class, io.realm.AllTypesRealmProxy.getExpectedObjectSchemaInfo()); return infoMap; } From 24aa93bcab411c5fa2bbfeaea01039ccd6548459 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 28 Nov 2017 07:37:01 +0100 Subject: [PATCH 1119/2110] Improve encryption example (#5571) --- examples/encryptionExample/build.gradle | 2 +- .../src/main/AndroidManifest.xml | 2 +- .../EncryptionExampleActivity.java | 10 +++++- .../MyApplication.java | 2 +- .../Person.java | 2 +- .../io/realm/examples/encryption/Util.java | 33 +++++++++++++++++++ 6 files changed, 46 insertions(+), 5 deletions(-) rename examples/encryptionExample/src/main/java/io/realm/examples/{encryptionexample => encryption}/EncryptionExampleActivity.java (83%) rename examples/encryptionExample/src/main/java/io/realm/examples/{encryptionexample => encryption}/MyApplication.java (94%) rename examples/encryptionExample/src/main/java/io/realm/examples/{encryptionexample => encryption}/Person.java (95%) create mode 100644 examples/encryptionExample/src/main/java/io/realm/examples/encryption/Util.java diff --git a/examples/encryptionExample/build.gradle b/examples/encryptionExample/build.gradle index 52da22b404..10707b22ae 100644 --- a/examples/encryptionExample/build.gradle +++ b/examples/encryptionExample/build.gradle @@ -6,7 +6,7 @@ android { buildToolsVersion rootProject.buildTools defaultConfig { - applicationId 'examples.realm.io.encryptionExample' + applicationId 'io.realm.examples.encryption' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 diff --git a/examples/encryptionExample/src/main/AndroidManifest.xml b/examples/encryptionExample/src/main/AndroidManifest.xml index 00f9f6c7be..93190053ee 100644 --- a/examples/encryptionExample/src/main/AndroidManifest.xml +++ b/examples/encryptionExample/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - >> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + +} From 1ffc5d528584780eb338a416ca332cedd97e1929 Mon Sep 17 00:00:00 2001 From: dong Date: Tue, 28 Nov 2017 14:38:18 +0800 Subject: [PATCH 1120/2110] Fix typo (#5569) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5ac70303e..36bb3c07ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,12 +29,12 @@ ### Internal * Use `OsList` instead of `OsResults` to add notification token on for `RealmList`. -* Updated Gralde and plugins to support Android Studio `3.0.0` (#5472). +* Updated Gradle and plugins to support Android Studio `3.0.0` (#5472). ### Credits * Thanks to @tbsandee for fixing a typo (#5548). -* Thanks to @vivekkiran for updating Gralde and plugins to support Android Studio `3.0.0` (#5472). +* Thanks to @vivekkiran for updating Gradle and plugins to support Android Studio `3.0.0` (#5472). ## 4.2.0 (2017-11-17) From de8167df3fa47aaacb386851e538b39c42f39ae0 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 23 Nov 2017 18:11:14 +0800 Subject: [PATCH 1121/2110] Maybe a fix for flaky CI --- .../java/io/realm/objectserver/ProcessCommitTests.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index b477f33609..08ba70684e 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -19,11 +19,11 @@ import android.os.Looper; import android.support.test.runner.AndroidJUnit4; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.IOException; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -52,8 +52,9 @@ public class ProcessCommitTests extends StandardIntegrationTest { @Rule public RunWithRemoteService remoteService = new RunWithRemoteService(); - @Before - public void before() throws Exception { + @Override + public void setupTest() throws IOException { + super.setupTest(); UserFactory.resetInstance(); } From 94a2eba8059130bb0ea36de9620a5ce9bea49c58 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 29 Nov 2017 09:56:06 +0100 Subject: [PATCH 1122/2110] Add support for new sort and distinct predicates (#5568) --- CHANGELOG.md | 4 + .../java/io/realm/BulkInsertTests.java | 10 +- .../java/io/realm/CollectionTests.java | 4 +- .../java/io/realm/DynamicRealmTests.java | 17 +- .../java/io/realm/IOSRealmTests.java | 2 +- .../io/realm/LinkingObjectsManagedTests.java | 2 +- .../ManagedOrderedRealmCollectionTests.java | 2 +- .../io/realm/ManagedRealmCollectionTests.java | 12 +- .../OrderedCollectionChangeSetTests.java | 4 +- .../OrderedRealmCollectionIteratorTests.java | 7 +- .../java/io/realm/RealmAsyncQueryTests.java | 45 ++-- .../java/io/realm/RealmMigrationTests.java | 6 +- .../java/io/realm/RealmModelTests.java | 7 +- .../java/io/realm/RealmObjectSchemaTests.java | 2 +- .../java/io/realm/RealmQueryTests.java | 216 +++++++++--------- .../java/io/realm/RealmResultsTests.java | 4 +- .../androidTest/java/io/realm/RealmTests.java | 4 +- .../androidTest/java/io/realm/SortTest.java | 79 ++++++- .../main/cpp/io_realm_internal_OsResults.cpp | 2 - .../src/main/java/io/realm/RealmList.java | 4 +- .../src/main/java/io/realm/RealmQuery.java | 139 ++++++++++- .../src/main/java/io/realm/Sort.java | 2 +- .../java/io/realm/SyncSessionTests.java | 2 +- .../EncryptedSynchronizedRealmTests.java | 4 +- .../objectserver/ProcessCommitTests.java | 2 +- 25 files changed, 395 insertions(+), 187 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36bb3c07ee..f432530baf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,13 @@ ### Deprecated * Support for mips devices are deprecated. +* `RealmQuery.findAllSorted()` and `RealmQuery.findAllSortedAsync()` variants in favor of predicate `RealmQuery.sort().findAll()`. +* `RealmQuery.distinct()` and `RealmQuery.distinctAsync()` variants in favor of predicate `RealmQuery.distinctValues().findAll()` ### Enhancements +* New query predicate: `sort()`. +* New query predicate: `distinctValues()`. Will be renamed to `distinct` in next major version. * The Realm annotation processor now has a stable output when there are no changes to model classes, improving support for incremental compilers (#5567). ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java index 42ece9c78d..3b1142cfc0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java @@ -258,7 +258,7 @@ public void insert_cyclicType() { realm.insert(Arrays.asList(oneCyclicType, anotherCyclicType)); realm.commitTransaction(); - RealmResults realmObjects = realm.where(CyclicType.class).findAllSorted(CyclicType.FIELD_NAME); + RealmResults realmObjects = realm.where(CyclicType.class).sort(CyclicType.FIELD_NAME).findAll(); assertNotNull(realmObjects); assertEquals(2, realmObjects.size()); assertEquals("One", realmObjects.get(0).getName()); @@ -276,7 +276,7 @@ public void insertOrUpdate_cyclicType() { realm.insertOrUpdate(Arrays.asList(oneCyclicType, anotherCyclicType)); realm.commitTransaction(); - RealmResults realmObjects = realm.where(CyclicTypePrimaryKey.class).findAllSorted("name"); + RealmResults realmObjects = realm.where(CyclicTypePrimaryKey.class).sort("name").findAll(); assertNotNull(realmObjects); assertEquals(2, realmObjects.size()); assertEquals("One", realmObjects.get(0).getName()); @@ -694,7 +694,7 @@ public void insertOrUpdate_mixingNoPrimaryKeyAndPrimaryKeyModels() { realm.insertOrUpdate(objA_no_pk); realm.commitTransaction(); - all = realm.where(NoPrimaryKeyWithPrimaryKeyObjectRelation.class).findAllSorted("columnString"); + all = realm.where(NoPrimaryKeyWithPrimaryKeyObjectRelation.class).sort("columnString").findAll(); assertEquals(2, all.size()); assertEquals("A", all.get(0).getColumnString()); assertEquals(8, all.get(0).getColumnInt()); @@ -735,7 +735,9 @@ public void insertOrUpdate_mixingPrimaryAndNoPrimaryKeyList() { realm.insertOrUpdate(objects); realm.commitTransaction(); - RealmResults all = realm.where(NoPrimaryKeyWithPrimaryKeyObjectRelation.class).findAllSorted("columnString", Sort.DESCENDING); + RealmResults all = realm.where(NoPrimaryKeyWithPrimaryKeyObjectRelation.class) + .sort("columnString", Sort.DESCENDING) + .findAll(); assertEquals(2, all.size()); assertEquals("B", all.get(0).getColumnString()); assertEquals("A", all.get(1).getColumnString()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java index c45a8cd2b9..64cb64ede3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CollectionTests.java @@ -98,7 +98,7 @@ protected void populateRealm(Realm realm, int objects) { // Adds all items to the RealmList on the first object. AllJavaTypes firstObj = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_ID, 0).findFirst(); - RealmResults listData = realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_ID, Sort.ASCENDING); + RealmResults listData = realm.where(AllJavaTypes.class).sort(AllJavaTypes.FIELD_ID, Sort.ASCENDING).findAll(); RealmList list = firstObj.getFieldList(); for (int i = 0; i < listData.size(); i++) { list.add(listData.get(i)); @@ -202,7 +202,7 @@ protected OrderedRealmCollection createStringCollection(Realm real obj.setFieldString(arg); } realm.commitTransaction(); - orderedCollection = realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_STRING); + orderedCollection = realm.where(AllJavaTypes.class).sort(AllJavaTypes.FIELD_STRING).findAll(); break; case REALMRESULTS_SNAPSHOT_LIST_BASE: diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 7e8d36dd75..573c1e21d7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -394,11 +394,12 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread - public void findAllSortedAsync() { + public void sort_async() { final DynamicRealm dynamicRealm = initializeDynamicRealm(); final RealmResults allTypes = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 0, 4) - .findAllSortedAsync(AllTypes.FIELD_STRING, Sort.DESCENDING); + .sort(AllTypes.FIELD_STRING, Sort.DESCENDING) + .findAllAsync(); assertFalse(allTypes.isLoaded()); assertEquals(0, allTypes.size()); @@ -428,7 +429,7 @@ private DynamicRealm initializeDynamicRealm() { @Test @RunTestInLooperThread - public void findAllSortedAsync_usingMultipleFields() { + public void sort_async_usingMultipleFields() { final DynamicRealm dynamicRealm = initializeDynamicRealm(); dynamicRealm.setAutoRefresh(false); @@ -448,18 +449,20 @@ public void findAllSortedAsync_usingMultipleFields() { // Sorts first set by using: String[ASC], Long[DESC]. final RealmResults realmResults1 = dynamicRealm.where(AllTypes.CLASS_NAME) - .findAllSortedAsync( + .sort( new String[]{AllTypes.FIELD_STRING, AllTypes.FIELD_LONG}, new Sort[]{Sort.ASCENDING, Sort.DESCENDING} - ); + ) + .findAllAsync(); // Sorts second set by using: String[DESC], Long[ASC]. final RealmResults realmResults2 = dynamicRealm.where(AllTypes.CLASS_NAME) .between(AllTypes.FIELD_LONG, 0, 5) - .findAllSortedAsync( + .sort( new String[]{AllTypes.FIELD_STRING, AllTypes.FIELD_LONG}, new Sort[]{Sort.DESCENDING, Sort.ASCENDING} - ); + ) + .findAllAsync(); final Runnable signalCallbackDone = new Runnable() { final AtomicInteger callbacksDone = new AtomicInteger(2); diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index 5b8cf56418..bd1ed35c67 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -79,7 +79,7 @@ public void iOSDataTypes() throws IOException { configFactory.copyRealmFromAssets(context, "ios/" + iosVersion + "-alltypes.realm", REALM_NAME); realm = Realm.getDefaultInstance(); - RealmResults result = realm.where(IOSAllTypes.class).findAllSorted("id", Sort.ASCENDING); + RealmResults result = realm.where(IOSAllTypes.class).sort("id", Sort.ASCENDING).findAll(); // Verifies metadata. Table table = realm.getTable(IOSAllTypes.class); assertEquals("id", OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), IOSAllTypes.CLASS_NAME)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index d690a3fb44..d609d7fc8e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -678,7 +678,7 @@ public void query_multipleReferencesWithDistinct() { assertEquals(2, child.getListParents().size()); - RealmResults distinctParents = child.getListParents().where().distinct("fieldId"); + RealmResults distinctParents = child.getListParents().where().distinctValues("fieldId").findAll(); assertEquals(1, distinctParents.size()); assertTrue(child.getListParents().contains(parent)); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index 366d4395f9..5fa3c49f3e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -698,7 +698,7 @@ private OrderedRealmCollection createNonCyclicCollection(Realm realm, Manag dog.setName("Dog " + i); } realm.commitTransaction(); - orderedCollection = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); + orderedCollection = realm.where(Dog.class).sort(Dog.FIELD_AGE).findAll(); break; default: diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index b27d7b3796..7b02a12d96 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -138,7 +138,8 @@ private OrderedRealmCollection createCollection(ManagedCollection case REALMRESULTS_SNAPSHOT_RESULTS_BASE: case REALMRESULTS: orderedCollection = realm.where(AllJavaTypes.class) - .findAllSorted(AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + .sort(AllJavaTypes.FIELD_LONG, Sort.ASCENDING) + .findAll(); break; default: @@ -341,15 +342,15 @@ public void where_findAll_size() { } @Test - public void where_findAllSorted() { - RealmResults results = realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + public void where_sort() { + RealmResults results = realm.where(AllJavaTypes.class).sort(AllJavaTypes.FIELD_LONG, Sort.ASCENDING).findAll(); assertEquals(TEST_SIZE, results.size()); //noinspection ConstantConditions assertEquals(0, results.first().getFieldLong()); //noinspection ConstantConditions assertEquals(TEST_SIZE - 1, results.last().getFieldLong()); - RealmResults reverseList = realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_LONG, Sort.DESCENDING); + RealmResults reverseList = realm.where(AllJavaTypes.class).sort(AllJavaTypes.FIELD_LONG, Sort.DESCENDING).findAll(); assertEquals(TEST_SIZE, reverseList.size()); //noinspection ConstantConditions assertEquals(0, reverseList.last().getFieldLong()); @@ -357,8 +358,7 @@ public void where_findAllSorted() { assertEquals(TEST_SIZE - 1, reverseList.first().getFieldLong()); try { - realm.where(AllJavaTypes.class).findAllSorted("invalid", - Sort.DESCENDING); + realm.where(AllJavaTypes.class).sort("invalid", Sort.DESCENDING).findAll(); fail(); } catch (IllegalArgumentException ignored) { } diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java index 1c3788f092..4b5cf29e3d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -197,7 +197,7 @@ private void moveObjects(Realm realm, int originAge, int newAge) { private void registerCheckListener(Realm realm, final ChangesCheck changesCheck) { switch (type) { case REALM_RESULTS: - RealmResults results = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); + RealmResults results = realm.where(Dog.class).sort(Dog.FIELD_AGE).findAll(); looperThread.keepStrongReference(results); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override @@ -453,7 +453,7 @@ public void emptyChangeSet_findAllAsync() { Realm realm = looperThread.getRealm(); populateData(realm, 10); - final RealmResults results = realm.where(Dog.class).findAllSortedAsync(Dog.FIELD_AGE); + final RealmResults results = realm.where(Dog.class).sort(Dog.FIELD_AGE).findAllAsync(); looperThread.keepStrongReference(results); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index b173530c82..a5d15ad8e1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -99,7 +99,9 @@ private OrderedRealmCollection createCollection(Realm realm, Colle case UNMANAGED_REALMLIST: populateRealm(realm, sampleSize); - RealmResults objects = realm.where(AllJavaTypes.class).findAllSorted(AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + RealmResults objects = realm.where(AllJavaTypes.class) + .sort(AllJavaTypes.FIELD_LONG, Sort.ASCENDING) + .findAll(); RealmList inMemoryList = new RealmList(); inMemoryList.addAll(objects); return inMemoryList; @@ -108,7 +110,8 @@ private OrderedRealmCollection createCollection(Realm realm, Colle case REALMRESULTS: populateRealm(realm, sampleSize); orderedCollection = realm.where(AllJavaTypes.class) - .findAllSorted(AllJavaTypes.FIELD_LONG, Sort.ASCENDING); + .sort(AllJavaTypes.FIELD_LONG, Sort.ASCENDING) + .findAll(); break; default: diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 770337505e..6d7a320cd2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -20,7 +20,6 @@ import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -769,13 +768,14 @@ public void onChange(AllTypes element) { // similar UC as #testFindAllAsync using 'findAllSorted' @Test @RunTestInLooperThread - public void findAllSortedAsync() throws Throwable { + public void sort_async() throws Throwable { final Realm realm = looperThread.getRealm(); populateTestRealm(realm, 10); final RealmResults results = realm.where(AllTypes.class) .between("columnLong", 0, 4) - .findAllSortedAsync("columnString", Sort.DESCENDING); + .sort("columnString", Sort.DESCENDING) + .findAllAsync(); assertFalse(results.isLoaded()); assertEquals(0, results.size()); @@ -879,16 +879,16 @@ public void run() { @Test @RunTestInLooperThread - public void distinctAsync() throws Throwable { + public void distinct_async() throws Throwable { Realm realm = looperThread.getRealm(); final long numberOfBlocks = 25; final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctAsync("indexBoolean"); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinctAsync("indexLong"); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinctAsync("indexDate"); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinctAsync("indexString"); + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctValues("indexBoolean").findAllAsync(); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinctValues("indexLong").findAllAsync(); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinctValues("indexDate").findAllAsync(); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinctValues("indexString").findAllAsync(); assertFalse(distinctBool.isLoaded()); assertTrue(distinctBool.isValid()); @@ -955,7 +955,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread() - public void distinctAsync_rememberQueryParams() { + public void distinct_async_rememberQueryParams() { final Realm realm = looperThread.getRealm(); realm.beginTransaction(); final int TEST_SIZE = 10; @@ -966,7 +966,8 @@ public void distinctAsync_rememberQueryParams() { RealmResults results = realm.where(AllJavaTypes.class) .notEqualTo(AllJavaTypes.FIELD_ID, TEST_SIZE / 2) - .distinctAsync(AllJavaTypes.FIELD_ID); + .distinctValues(AllJavaTypes.FIELD_ID) + .findAllAsync(); results.addChangeListener(new RealmChangeListener>() { @Override @@ -987,13 +988,17 @@ public void distinctAsync_notIndexedFields() throws Throwable { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) - .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL); + .distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL) + .findAllAsync(); final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class) - .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_LONG); + .distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_LONG) + .findAllAsync(); final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) - .distinctAsync(AnnotationIndexTypes.FIELD_NOT_INDEX_DATE); + .distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_DATE) + .findAllAsync(); final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) - .distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); + .distinctValues(AnnotationIndexTypes.FIELD_INDEX_STRING) + .findAllAsync(); assertFalse(distinctBool.isLoaded()); assertTrue(distinctBool.isValid()); @@ -1067,7 +1072,7 @@ public void distinctAsync_noneExistingField() throws Throwable { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { - realm.where(AnnotationIndexTypes.class).distinctAsync("doesNotExist"); + realm.where(AnnotationIndexTypes.class).distinctValues("doesNotExist").findAllAsync(); fail(); } catch (IllegalArgumentException ignored) { looperThread.testComplete(); @@ -1094,10 +1099,10 @@ public void batchUpdateDifferentTypeOfQueries() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmResults findAllAsync = realm.where(AllTypes.class).findAllAsync(); - RealmResults findAllSorted = realm.where(AllTypes.class).findAllSortedAsync("columnString", Sort.ASCENDING); - RealmResults findAllSortedMulti = realm.where(AllTypes.class).findAllSortedAsync(new String[]{"columnString", "columnLong"}, - new Sort[]{Sort.ASCENDING, Sort.DESCENDING}); - RealmResults findDistinct = realm.where(AnnotationIndexTypes.class).distinctAsync("indexString"); + RealmResults findAllSorted = realm.where(AllTypes.class).sort("columnString", Sort.ASCENDING).findAllAsync(); + RealmResults findAllSortedMulti = realm.where(AllTypes.class).sort(new String[]{"columnString", "columnLong"}, + new Sort[]{Sort.ASCENDING, Sort.DESCENDING}).findAllAsync(); + RealmResults findDistinct = realm.where(AnnotationIndexTypes.class).distinctValues("indexString").findAllAsync(); looperThread.keepStrongReference(findAllAsync); looperThread.keepStrongReference(findAllSorted); @@ -1264,7 +1269,7 @@ public void badVersion_syncTransaction() throws NoSuchFieldException, IllegalAcc Realm realm = looperThread.getRealm(); // 1. Makes sure that async query is not started. - final RealmResults result = realm.where(AllTypes.class).findAllSortedAsync(AllTypes.FIELD_STRING); + final RealmResults result = realm.where(AllTypes.class).sort(AllTypes.FIELD_STRING).findAllAsync(); looperThread.keepStrongReference(result); result.addChangeListener(new RealmChangeListener>() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 91e0d6945b..2c56308eca 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -777,7 +777,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { assertNotNull(objectSchema); assertEquals(PrimaryKeyAsString.FIELD_PRIMARY_KEY, objectSchema.getPrimaryKey()); RealmResults results = realm.where(PrimaryKeyAsString.class) - .findAllSorted(PrimaryKeyAsString.FIELD_ID); + .sort(PrimaryKeyAsString.FIELD_ID) + .findAll(); assertEquals(2, results.size()); assertEquals("string0", results.get(0).getName()); assertEquals("string1", results.get(1).getName()); @@ -822,7 +823,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { assertNotNull(objectSchema); assertEquals(PrimaryKeyAsInteger.FIELD_ID, objectSchema.getPrimaryKey()); RealmResults results = realm.where(PrimaryKeyAsInteger.class) - .findAllSorted(PrimaryKeyAsInteger.FIELD_ID); + .sort(PrimaryKeyAsInteger.FIELD_ID) + .findAll(); assertEquals(2, results.size()); assertEquals(0, results.get(0).getId()); assertEquals(1, results.get(1).getId()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index 1bf59fdf2a..cbaae6111e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -125,7 +125,10 @@ public void copyToRealm() { public void copyFromRealm() { populateTestRealm(realm, TEST_DATA_SIZE); - AllTypesRealmModel realmObject = realm.where(AllTypesRealmModel.class).findAllSorted(AllTypesRealmModel.FIELD_LONG).first(); + AllTypesRealmModel realmObject = realm.where(AllTypesRealmModel.class) + .sort(AllTypesRealmModel.FIELD_LONG) + .findAll() + .first(); AllTypesRealmModel unmanagedObject = realm.copyFromRealm(realmObject); assertArrayEquals(realmObject.columnBinary, unmanagedObject.columnBinary); assertEquals(realmObject.columnString, unmanagedObject.columnString); @@ -198,7 +201,7 @@ public void async_query() { Realm realm = looperThread.getRealm(); populateTestRealm(realm, TEST_DATA_SIZE); - final RealmResults allTypesRealmModels = realm.where(AllTypesRealmModel.class).distinctAsync(AllTypesRealmModel.FIELD_STRING); + final RealmResults allTypesRealmModels = realm.where(AllTypesRealmModel.class).distinctValues(AllTypesRealmModel.FIELD_STRING).findAllAsync(); looperThread.keepStrongReference(allTypesRealmModels); allTypesRealmModels.addChangeListener(new RealmChangeListener>() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 6615bffa9b..039e411a32 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -919,7 +919,7 @@ private void setRequired_onPrimaryKeyField(boolean isRequired) { assertTrue(schema.hasPrimaryKey()); assertTrue(schema.hasIndex(fieldName)); - RealmResults results = ((DynamicRealm)realm).where(className).findAllSorted(fieldName); + RealmResults results = ((DynamicRealm)realm).where(className).sort(fieldName).findAll(); assertEquals(2, results.size()); if (fieldType == PrimaryKeyFieldType.STRING) { assertEquals("1", results.get(0).getString(fieldName)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index c97668d62e..50467274cb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -209,7 +209,6 @@ private enum ThreadConfinedMethods { IS_VALID, DISTINCT, DISTINCT_BY_MULTIPLE_FIELDS, - DISTINCT_ASYNC, SUM, AVERAGE, @@ -221,14 +220,9 @@ private enum ThreadConfinedMethods { FIND_ALL, FIND_ALL_ASYNC, - FIND_ALL_SORTED, - FIND_ALL_SORTED_ASYNC, - FIND_ALL_SORTED_WITH_ORDER, - FIND_ALL_SORTED_ASYNC_WITH_ORDER, - FIND_ALL_SORTED_WITH_TWO_ORDERS, - FIND_ALL_SORTED_ASYNC_WITH_TWO_ORDERS, - FIND_ALL_SORTED_WITH_MANY_ORDERS, - FIND_ALL_SORTED_ASYNC_WITH_MANY_ORDERS, + SORT, + SORT_WITH_ORDER, + SORT_WITH_MANY_ORDERS, FIND_FIRST, FIND_FIRST_ASYNC, @@ -324,9 +318,8 @@ private static void callThreadConfinedMethod(RealmQuery query, ThreadConfined case IS_NOT_EMPTY: query.isNotEmpty( AllJavaTypes.FIELD_STRING); break; case IS_VALID: query.isValid(); break; - case DISTINCT: query.distinct( AllJavaTypes.FIELD_STRING); break; - case DISTINCT_BY_MULTIPLE_FIELDS: query.distinct( AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID); break; - case DISTINCT_ASYNC: query.distinctAsync( AllJavaTypes.FIELD_STRING); break; + case DISTINCT: query.distinctValues( AllJavaTypes.FIELD_STRING); break; + case DISTINCT_BY_MULTIPLE_FIELDS: query.distinctValues( AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID); break; case SUM: query.sum( AllJavaTypes.FIELD_INT); break; case AVERAGE: query.average( AllJavaTypes.FIELD_INT); break; @@ -338,15 +331,9 @@ private static void callThreadConfinedMethod(RealmQuery query, ThreadConfined case FIND_ALL: query.findAll(); break; case FIND_ALL_ASYNC: query.findAllAsync(); break; - case FIND_ALL_SORTED: query.findAllSorted( AllJavaTypes.FIELD_STRING); break; - case FIND_ALL_SORTED_ASYNC: query.findAllSortedAsync( AllJavaTypes.FIELD_STRING); break; - case FIND_ALL_SORTED_WITH_ORDER: query.findAllSorted( AllJavaTypes.FIELD_STRING, Sort.DESCENDING); break; - case FIND_ALL_SORTED_ASYNC_WITH_ORDER: query.findAllSortedAsync( AllJavaTypes.FIELD_STRING, Sort.DESCENDING); break; - case FIND_ALL_SORTED_WITH_TWO_ORDERS: query.findAllSorted( AllJavaTypes.FIELD_STRING, Sort.DESCENDING, AllJavaTypes.FIELD_ID, Sort.DESCENDING); break; - case FIND_ALL_SORTED_ASYNC_WITH_TWO_ORDERS: query.findAllSortedAsync( AllJavaTypes.FIELD_STRING, Sort.DESCENDING, AllJavaTypes.FIELD_ID, Sort.DESCENDING); break; - case FIND_ALL_SORTED_WITH_MANY_ORDERS: query.findAllSorted( new String[] {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID}, new Sort[] {Sort.DESCENDING, Sort.DESCENDING}); break; - case FIND_ALL_SORTED_ASYNC_WITH_MANY_ORDERS: query.findAllSortedAsync( new String[] {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID}, new Sort[] {Sort.DESCENDING, Sort.DESCENDING}); break; - + case SORT: query.sort(AllJavaTypes.FIELD_STRING); break; + case SORT_WITH_ORDER: query.sort(AllJavaTypes.FIELD_STRING, Sort.ASCENDING); break; + case SORT_WITH_MANY_ORDERS: query.sort(new String[] {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID}, new Sort[] {Sort.DESCENDING, Sort.DESCENDING}); break; case FIND_FIRST: query.findFirst(); break; case FIND_FIRST_ASYNC: query.findFirstAsync(); break; @@ -1266,36 +1253,42 @@ public void queryLink() { // Dog.weight has index 4 which is more than the total number of columns in Owner // This tests exposes a subtle error where the Owner table spec is used instead of Dog table spec. RealmResults dogs = realm.where(Owner.class).findFirst().getDogs().where() - .findAllSorted("name", Sort.ASCENDING); + .sort("name", Sort.ASCENDING) + .findAll(); Dog dog = dogs.where().equalTo("weight", 1d).findFirst(); assertEquals(dog1, dog); } @Test - public void findAllSorted_multiFailures() { + public void sort_multiFailures() { // Zero fields specified. try { - realm.where(AllTypes.class).findAllSorted(new String[]{}, new Sort[]{}); + realm.where(AllTypes.class).sort(new String[]{}, new Sort[]{}).findAll(); fail(); } catch (IllegalArgumentException ignored) { } // Number of fields and sorting orders don't match. try { - realm.where(AllTypes.class).findAllSorted(new String[]{AllTypes.FIELD_STRING}, - new Sort[]{Sort.ASCENDING, Sort.ASCENDING}); + realm.where(AllTypes.class) + .sort(new String[]{AllTypes.FIELD_STRING},new Sort[]{Sort.ASCENDING, Sort.ASCENDING}) + .findAll(); fail(); } catch (IllegalArgumentException ignored) { } // Null is not allowed. try { - realm.where(AllTypes.class).findAllSorted((String[]) null, null); + realm.where(AllTypes.class) + .sort((String[]) null, null) + .findAll(); fail(); } catch (IllegalArgumentException ignored) { } try { - realm.where(AllTypes.class).findAllSorted(new String[]{AllTypes.FIELD_STRING}, null); + realm.where(AllTypes.class) + .sort(new String[]{AllTypes.FIELD_STRING}, null) + .findAll(); fail(); } catch (IllegalArgumentException ignored) { } @@ -1303,15 +1296,24 @@ public void findAllSorted_multiFailures() { // Non-existing field name. try { realm.where(AllTypes.class) - .findAllSorted(new String[]{AllTypes.FIELD_STRING, "do-not-exist"}, - new Sort[]{Sort.ASCENDING, Sort.ASCENDING}); + .sort(new String[]{AllTypes.FIELD_STRING, "do-not-exist"}, new Sort[]{Sort.ASCENDING, Sort.ASCENDING}) + .findAll(); fail(); } catch (IllegalArgumentException ignored) { } + + // Defining sort multiple times + try { + realm.where(AllTypes.class) + .sort(AllTypes.FIELD_STRING) + .sort(AllTypes.FIELD_STRING); + fail(); + } catch (IllegalStateException ignored) { + } } @Test - public void findAllSorted_singleField() { + public void sort_singleField() { realm.beginTransaction(); for (int i = 0; i < TEST_DATA_SIZE; i++) { AllTypes allTypes = realm.createObject(AllTypes.class); @@ -1320,7 +1322,8 @@ public void findAllSorted_singleField() { realm.commitTransaction(); RealmResults sortedList = realm.where(AllTypes.class) - .findAllSorted(new String[]{AllTypes.FIELD_LONG}, new Sort[]{Sort.DESCENDING}); + .sort(new String[]{AllTypes.FIELD_LONG}, new Sort[]{Sort.DESCENDING}) + .findAll(); assertEquals(TEST_DATA_SIZE, sortedList.size()); assertEquals(TEST_DATA_SIZE - 1, sortedList.first().getColumnLong()); assertEquals(0, sortedList.last().getColumnLong()); @@ -2883,21 +2886,23 @@ public void execute(Realm realm) { } @Test - public void findAllSorted_onSubObjectField() { + public void sort_onSubObjectField() { populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) - .findAllSorted(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE); + .sort(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE) + .findAll(); assertEquals(0, results.get(0).getColumnRealmObject().getAge()); assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); } @Test @RunTestInLooperThread - public void findAllSortedAsync_onSubObjectField() { + public void sort_async_onSubObjectField() { Realm realm = looperThread.getRealm(); populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) - .findAllSortedAsync(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE); + .sort(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE) + .findAllAsync(); looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override @@ -2924,7 +2929,7 @@ public void findAll_indexedCaseInsensitiveFields() { } @Test - public void findAllSorted_listOnSubObjectField() { + public void sort_listOnSubObjectField() { String[] fieldNames = new String[2]; fieldNames[0] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; fieldNames[1] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; @@ -2935,37 +2940,12 @@ public void findAllSorted_listOnSubObjectField() { populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) - .findAllSorted(fieldNames, sorts); + .sort(fieldNames, sorts) + .findAll(); assertEquals(0, results.get(0).getColumnRealmObject().getAge()); assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); } - @Test - @RunTestInLooperThread - public void findAllSortedAsync_listOnSubObjectField() { - Realm realm = looperThread.getRealm(); - String[] fieldNames = new String[2]; - fieldNames[0] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; - fieldNames[1] = AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_AGE; - - Sort[] sorts = new Sort[2]; - sorts[0] = Sort.ASCENDING; - sorts[1] = Sort.ASCENDING; - - populateTestRealm(realm, TEST_DATA_SIZE); - RealmResults results = realm.where(AllTypes.class) - .findAllSortedAsync(fieldNames, sorts); - looperThread.keepStrongReference(results); - results.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults results) { - assertEquals(0, results.get(0).getColumnRealmObject().getAge()); - assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); - looperThread.testComplete(); - } - }); - } - // RealmQuery.distinct(): requires indexing, and type = boolean, integer, date, string. private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOfObjects, boolean withNull) { realm.beginTransaction(); @@ -3002,10 +2982,10 @@ public void distinct() { final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL); + RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL).findAll(); assertEquals(2, distinctBool.size()); for (String field : new String[]{AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field); + RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinctValues(field).findAll(); assertEquals(field, numberOfBlocks, distinct.size()); } } @@ -3017,11 +2997,18 @@ public void distinct_withNullValues() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); for (String field : new String[]{AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field); + RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinctValues(field).findAll(); assertEquals(field, 1, distinct.size()); } } + @Test(expected = IllegalStateException.class) + public void distinct_failIfAppliedMultipleTimes() { + realm.where(AnnotationIndexTypes.class) + .distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE) + .distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE); + } + @Test public void distinct_notIndexedFields() { final long numberOfBlocks = 3; @@ -3029,11 +3016,12 @@ public void distinct_notIndexedFields() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL); + .distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL) + .findAll(); assertEquals(2, distinctBool.size()); for (String field : new String[]{AnnotationIndexTypes.FIELD_NOT_INDEX_LONG, AnnotationIndexTypes.FIELD_NOT_INDEX_DATE, AnnotationIndexTypes.FIELD_NOT_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field); + RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinctValues(field).findAll(); assertEquals(field, numberOfBlocks, distinct.size()); } } @@ -3045,7 +3033,8 @@ public void distinct_doesNotExist() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { - realm.where(AnnotationIndexTypes.class).distinct("doesNotExist"); + realm.where(AnnotationIndexTypes.class).distinctValues("doesNotExist").findAll(); + fail(); } catch (IllegalArgumentException ignored) { } } @@ -3056,7 +3045,7 @@ public void distinct_invalidTypes() { for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { try { - realm.where(AllTypes.class).distinct(field); + realm.where(AllTypes.class).distinctValues(field).findAll(); fail(field); } catch (IllegalArgumentException ignored) { } @@ -3071,7 +3060,9 @@ public void distinct_indexedLinkedFields() { for (String field : AnnotationIndexTypes.INDEX_FIELDS) { try { - realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field); + realm.where(AnnotationIndexTypes.class) + .distinctValues(AnnotationIndexTypes.FIELD_OBJECT + "." + field) + .findAll(); fail("Unsupported Index" + field + " linked field"); } catch (IllegalArgumentException ignored) { } @@ -3086,7 +3077,9 @@ public void distinct_notIndexedLinkedFields() { for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { try { - realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field); + realm.where(AnnotationIndexTypes.class) + .distinctValues(AnnotationIndexTypes.FIELD_OBJECT + "." + field) + .findAll(); fail("Unsupported notIndex" + field + " linked field"); } catch (IllegalArgumentException ignored) { } @@ -3098,24 +3091,26 @@ public void distinct_invalidTypesLinkedFields() { populateForDistinctInvalidTypesLinked(realm); try { - realm.where(AllJavaTypes.class).distinct(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); + realm.where(AllJavaTypes.class) + .distinctValues(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY) + .findAll(); } catch (IllegalArgumentException ignored) { } } @Test @RunTestInLooperThread - public void distinctAsync() throws Throwable { + public void distinct_async() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 3; final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_BOOL); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_LONG); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL).findAllAsync(); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_LONG).findAllAsync(); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE).findAllAsync(); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_STRING).findAllAsync(); assertFalse(distinctBool.isLoaded()); assertTrue(distinctBool.isValid()); @@ -3181,7 +3176,7 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread - public void distinctAsync_withNullValues() throws Throwable { + public void distinct_async_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 3; @@ -3189,9 +3184,11 @@ public void distinctAsync_withNullValues() throws Throwable { populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) - .distinctAsync(AnnotationIndexTypes.FIELD_INDEX_DATE); + .distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE) + .findAllAsync(); final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) - .distinctAsync(AnnotationIndexTypes.FIELD_INDEX_STRING); + .distinctValues(AnnotationIndexTypes.FIELD_INDEX_STRING) + .findAllAsync(); final Runnable endTest = new Runnable() { @Override @@ -3224,13 +3221,13 @@ public void onChange(RealmResults object) { @Test @RunTestInLooperThread - public void distinctAsync_doesNotExist() { + public void distinct_async_doesNotExist() { final long numberOfBlocks = 3; final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { - realm.where(AnnotationIndexTypes.class).distinctAsync("doesNotExist"); + realm.where(AnnotationIndexTypes.class).distinctValues("doesNotExist").findAllAsync(); } catch (IllegalArgumentException ignored) { } looperThread.testComplete(); @@ -3238,12 +3235,12 @@ public void distinctAsync_doesNotExist() { @Test @RunTestInLooperThread - public void distinctAsync_invalidTypes() { + public void distinct_async_invalidTypes() { populateTestRealm(realm, TEST_DATA_SIZE); for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { try { - realm.where(AllTypes.class).distinctAsync(field); + realm.where(AllTypes.class).distinctValues(field).findAllAsync(); } catch (IllegalArgumentException ignored) { } } @@ -3252,14 +3249,14 @@ public void distinctAsync_invalidTypes() { @Test @RunTestInLooperThread - public void distinctAsync_indexedLinkedFields() { + public void distinct_async_indexedLinkedFields() { final long numberOfBlocks = 3; final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); for (String field : AnnotationIndexTypes.INDEX_FIELDS) { try { - realm.where(AnnotationIndexTypes.class).distinctAsync(AnnotationIndexTypes.FIELD_OBJECT + "." + field); + realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_OBJECT + "." + field).findAllAsync(); fail("Unsupported " + field + " linked field"); } catch (IllegalArgumentException ignored) { } @@ -3269,11 +3266,11 @@ public void distinctAsync_indexedLinkedFields() { @Test @RunTestInLooperThread - public void distinctAsync_notIndexedLinkedFields() { + public void distinct_async_notIndexedLinkedFields() { populateForDistinctInvalidTypesLinked(realm); try { - realm.where(AllJavaTypes.class).distinctAsync(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY); + realm.where(AllJavaTypes.class).distinctValues(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY).findAllAsync(); } catch (IllegalArgumentException ignored) { } looperThread.testComplete(); @@ -3286,7 +3283,7 @@ public void distinctMultiArgs() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmQuery query = realm.where(AnnotationIndexTypes.class); - RealmResults distinctMulti = query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.INDEX_FIELDS); + RealmResults distinctMulti = query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.INDEX_FIELDS).findAll(); assertEquals(numberOfBlocks, distinctMulti.size()); } @@ -3296,9 +3293,8 @@ public void distinctMultiArgs_switchedFieldsOrder() { TestHelper.populateForDistinctFieldsOrder(realm, numberOfBlocks); // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples. - RealmQuery query = realm.where(AnnotationIndexTypes.class); - RealmResults distinctStringLong = query.distinct(AnnotationIndexTypes.FIELD_INDEX_STRING, AnnotationIndexTypes.FIELD_INDEX_LONG); - RealmResults distinctLongString = query.distinct(AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_STRING); + RealmResults distinctStringLong = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_STRING, AnnotationIndexTypes.FIELD_INDEX_LONG).findAll(); + RealmResults distinctLongString = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_STRING).findAll(); assertEquals(4, distinctStringLong.size()); assertEquals(4, distinctLongString.size()); assertEquals(distinctStringLong.size(), distinctLongString.size()); @@ -3313,47 +3309,47 @@ public void distinctMultiArgs_emptyField() { RealmQuery query = realm.where(AnnotationIndexTypes.class); // An empty string field in the middle. try { - query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, "", AnnotationIndexTypes.FIELD_INDEX_INT); + query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, "", AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); } catch (IllegalArgumentException ignored) { } // An empty string field at the end. try { - query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, ""); + query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, "").findAll(); } catch (IllegalArgumentException ignored) { } // A null string field in the middle. try { - query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null, AnnotationIndexTypes.FIELD_INDEX_INT); + query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null, AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); } catch (IllegalArgumentException ignored) { } // A null string field at the end. try { - query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, (String) null); + query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, (String) null).findAll(); } catch (IllegalArgumentException ignored) { } // (String) Null makes varargs a null array. try { - query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null); + query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null).findAll(); } catch (IllegalArgumentException ignored) { } // Two (String) null for first and varargs fields. try { - query.distinct((String) null, (String) null); + query.distinctValues((String) null, (String) null).findAll(); } catch (IllegalArgumentException ignored) { } // "" & (String) null combination. try { - query.distinct("", (String) null); + query.distinctValues("", (String) null).findAll(); } catch (IllegalArgumentException ignored) { } // "" & (String) null combination. try { - query.distinct((String) null, ""); + query.distinctValues((String) null, "").findAll(); } catch (IllegalArgumentException ignored) { } // Two empty fields tests. try { - query.distinct("", ""); + query.distinctValues("", "").findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3365,7 +3361,7 @@ public void distinctMultiArgs_withNullValues() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); RealmQuery query = realm.where(AnnotationIndexTypes.class); - RealmResults distinctMulti = query.distinct(AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING); + RealmResults distinctMulti = query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING).findAll(); assertEquals(1, distinctMulti.size()); } @@ -3377,7 +3373,7 @@ public void distinctMultiArgs_notIndexedFields() { RealmQuery query = realm.where(AnnotationIndexTypes.class); try { - query.distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_STRING, AnnotationIndexTypes.NOT_INDEX_FIELDS); + query.distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_STRING, AnnotationIndexTypes.NOT_INDEX_FIELDS).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3390,7 +3386,7 @@ public void distinctMultiArgs_doesNotExistField() { RealmQuery query = realm.where(AnnotationIndexTypes.class); try { - query.distinct(AnnotationIndexTypes.FIELD_INDEX_INT, AnnotationIndexTypes.NONEXISTANT_MIX_FIELDS); + query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT, AnnotationIndexTypes.NONEXISTANT_MIX_FIELDS).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3401,7 +3397,7 @@ public void distinctMultiArgs_invalidTypesFields() { RealmQuery query = realm.where(AllTypes.class); try { - query.distinct(AllTypes.FIELD_REALMOBJECT, AllTypes.INVALID_TYPES_FIELDS_FOR_DISTINCT); + query.distinctValues(AllTypes.FIELD_REALMOBJECT, AllTypes.INVALID_TYPES_FIELDS_FOR_DISTINCT).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3414,7 +3410,7 @@ public void distinctMultiArgs_indexedLinkedFields() { RealmQuery query = realm.where(AnnotationIndexTypes.class); try { - query.distinct(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING, AnnotationIndexTypes.INDEX_LINKED_FIELDS); + query.distinctValues(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING, AnnotationIndexTypes.INDEX_LINKED_FIELDS).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3427,7 +3423,7 @@ public void distinctMultiArgs_notIndexedLinkedFields() { RealmQuery query = realm.where(AnnotationIndexTypes.class); try { - query.distinct(AnnotationIndexTypes.NOT_INDEX_LINKED_FILED_STRING, AnnotationIndexTypes.NOT_INDEX_LINKED_FIELDS); + query.distinctValues(AnnotationIndexTypes.NOT_INDEX_LINKED_FILED_STRING, AnnotationIndexTypes.NOT_INDEX_LINKED_FIELDS).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3438,7 +3434,7 @@ public void distinctMultiArgs_invalidTypesLinkedFields() { RealmQuery query = realm.where(AllJavaTypes.class); try { - query.distinct(AllJavaTypes.INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT, AllJavaTypes.INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT); + query.distinctValues(AllJavaTypes.INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT, AllJavaTypes.INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT).findAll(); } catch (IllegalArgumentException ignored) { } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 8ab8c830a9..6a7e1cdbf3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -73,7 +73,9 @@ public void setUp() { RealmConfiguration realmConfig = configFactory.createConfiguration(); realm = Realm.getInstance(realmConfig); populateTestRealm(); - collection = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG, Sort.ASCENDING); + collection = realm.where(AllTypes.class) + .sort(AllTypes.FIELD_LONG, Sort.ASCENDING) + .findAll(); } @After diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 509d05f3eb..45d7689b6b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3299,7 +3299,7 @@ public void copyFromRealm_invalidDepthThrows() { @Test public void copyFromRealm() { populateTestRealm(); - AllTypes realmObject = realm.where(AllTypes.class).findAllSorted("columnLong").first(); + AllTypes realmObject = realm.where(AllTypes.class).sort("columnLong").findAll().first(); AllTypes unmanagedObject = realm.copyFromRealm(realmObject); assertArrayEquals(realmObject.getColumnBinary(), unmanagedObject.getColumnBinary()); assertEquals(realmObject.getColumnString(), unmanagedObject.getColumnString()); @@ -3313,7 +3313,7 @@ public void copyFromRealm() { @Test public void copyFromRealm_newCopyEachTime() { populateTestRealm(); - AllTypes realmObject = realm.where(AllTypes.class).findAllSorted("columnLong").first(); + AllTypes realmObject = realm.where(AllTypes.class).sort("columnLong").findAll().first(); AllTypes unmanagedObject1 = realm.copyFromRealm(realmObject); AllTypes unmanagedObject2 = realm.copyFromRealm(realmObject); assertFalse(unmanagedObject1 == unmanagedObject2); diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 06e6792365..91d961276d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -31,6 +31,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.AnnotationIndexTypes; +import io.realm.entities.Dog; import io.realm.entities.StringOnly; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; @@ -72,18 +73,22 @@ private void populateRealm(Realm realm) { AllTypes object1 = realm.createObject(AllTypes.class); object1.setColumnLong(5); object1.setColumnString("Adam"); + object1.setColumnRealmObject(realm.copyToRealm(new Dog("D"))); AllTypes object2 = realm.createObject(AllTypes.class); object2.setColumnLong(4); object2.setColumnString("Brian"); + object2.setColumnRealmObject(realm.copyToRealm(new Dog("C"))); AllTypes object3 = realm.createObject(AllTypes.class); object3.setColumnLong(4); object3.setColumnString("Adam"); + object3.setColumnRealmObject(realm.copyToRealm(new Dog("B"))); AllTypes object4 = realm.createObject(AllTypes.class); object4.setColumnLong(5); object4.setColumnString("Adam"); + object4.setColumnRealmObject(realm.copyToRealm(new Dog("A"))); realm.delete(AnnotationIndexTypes.class); AnnotationIndexTypes obj1 = realm.createObject(AnnotationIndexTypes.class); @@ -112,10 +117,10 @@ private UncheckedRow getRowBySourceIndexFromAllTypesTable(long sourceRowIndex) { @Before public void setUp() { // Creates a Realm with the following objects: - // 0: (5, "Adam") - // 1: (4, "Brian") - // 2: (4, "Adam") - // 3: (5, "Adam") + // 0: (5, "Adam", Dog("D")) + // 1: (4, "Brian", Dog("C")) + // 2: (4, "Adam", Dog("B")) + // 3: (5, "Adam", Dog("A")) // Injecting the Instrumentation instance is required // for your test to run with AndroidJUnitRunner. @@ -472,7 +477,7 @@ public void sortingDates() { populateDates(realm, TEST_SIZE); - RealmResults objectsAscending = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_DATE, Sort.ASCENDING); + RealmResults objectsAscending = realm.where(AllTypes.class).sort(AllTypes.FIELD_DATE, Sort.ASCENDING).findAll(); assertEquals(TEST_SIZE, objectsAscending.size()); int i = 0; for (AllTypes allTypes : objectsAscending) { @@ -480,7 +485,7 @@ public void sortingDates() { i++; } - RealmResults objectsDescending = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_DATE, Sort.DESCENDING); + RealmResults objectsDescending = realm.where(AllTypes.class).sort(AllTypes.FIELD_DATE, Sort.DESCENDING).findAll(); assertEquals(TEST_SIZE, objectsDescending.size()); i = TEST_SIZE - 1; for (AllTypes allTypes : objectsDescending) { @@ -509,7 +514,7 @@ public void run() { } }; - RealmResults objectsAscending = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_DATE, Sort.ASCENDING); + RealmResults objectsAscending = realm.where(AllTypes.class).sort(AllTypes.FIELD_DATE, Sort.ASCENDING).findAll(); assertEquals(TEST_SIZE, objectsAscending.size()); looperThread.keepStrongReference(objectsAscending); objectsAscending.addChangeListener(new RealmChangeListener>() { @@ -525,7 +530,7 @@ public void onChange(RealmResults element) { } }); - RealmResults objectsDescending = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_DATE, Sort.DESCENDING); + RealmResults objectsDescending = realm.where(AllTypes.class).sort(AllTypes.FIELD_DATE, Sort.DESCENDING).findAll(); assertEquals(TEST_SIZE, objectsDescending.size()); looperThread.keepStrongReference(objectsDescending); objectsDescending.addChangeListener(new RealmChangeListener>() { @@ -559,18 +564,70 @@ public void sortByLongDistinctByInt() { // (2, 1, "B") // (1, 1, "A) RealmResults results1 = realm.where(AnnotationIndexTypes.class) - .findAllSorted(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.DESCENDING); + .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.DESCENDING) + .findAll(); assertEquals(3, results1.size()); assertEquals(3, results1.get(0).getIndexLong()); // After distinct: // (3, 1, "C") - RealmResults results2 = results1.where().distinct(AnnotationIndexTypes.FIELD_INDEX_INT); + RealmResults results2 = results1.where().distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); assertEquals(1, results2.size()); assertEquals("C", results2.get(0).getIndexString()); assertEquals(3, results2.get(0).getIndexLong()); } + @Test + public void sortAndDistinctMixed() { + // Dataset: + // (FIELD_INDEX_LONG, FIELD_INDEX_INT, FIELD_INDEX_STRING) + // (1, 1, "A") + // (2, 1, "B") + // (3, 1, "C") + // Depending on the sorting, distinct should pick the first element encountered. + // The order of sort/distinct in the query should not matter + + // Case 1: Selecting highest numbers + RealmResults results1a = realm.where(AnnotationIndexTypes.class) + .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.DESCENDING) + .distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT) + .findAll(); + assertEquals(1, results1a.size()); + assertEquals(3, results1a.get(0).getIndexLong()); + + RealmResults results1b = realm.where(AnnotationIndexTypes.class) + .distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT) + .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.DESCENDING) + .findAll(); + assertEquals(1, results1b.size()); + assertEquals(3, results1b.get(0).getIndexLong()); + + // Case 1: Selecting lowest number numbers + RealmResults results2a = realm.where(AnnotationIndexTypes.class) + .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.ASCENDING) + .distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT) + .findAll(); + assertEquals(1, results2a.size()); + assertEquals(1, results2a.get(0).getIndexLong()); + + RealmResults results2b = realm.where(AnnotationIndexTypes.class) + .distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT) + .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.ASCENDING) + .findAll(); + assertEquals(1, results2b.size()); + assertEquals(1, results2b.get(0).getIndexLong()); + } + + @Test + public void sortByChildValue() { + RealmResults result = realm.where(AllTypes.class) + .sort(AllTypes.FIELD_REALMOBJECT + "." + Dog.FIELD_NAME, Sort.ASCENDING) + .findAll(); + + assertEquals("A", result.first().getColumnRealmObject().getName()); + assertEquals("D", result.last().getColumnRealmObject().getName()); + } + private void createAndTest(String str) { realm.beginTransaction(); realm.delete(StringOnly.class); @@ -579,7 +636,7 @@ private void createAndTest(String str) { stringOnly.setChars(str.substring(i, i + 1)); } realm.commitTransaction(); - RealmResults stringOnlies = realm.where(StringOnly.class).findAllSorted("chars"); + RealmResults stringOnlies = realm.where(StringOnly.class).sort("chars").findAll(); for (int i = 0; i < chars.length(); i++) { assertEquals(chars.substring(i, i + 1), stringOnlies.get(i).getChars()); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index 500ed0a8a5..560eb31f52 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -52,9 +52,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResults(JNI } auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - DescriptorOrdering descriptor_ordering; - REALM_ASSERT_RELEASE(!(j_sort_desc && j_distinct_desc)); if (j_sort_desc) { descriptor_ordering.append_sort(JavaSortDescriptor(env, j_sort_desc).sort_descriptor()); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index a0838b7abe..af87170e3c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -506,7 +506,7 @@ public RealmResults sort(String fieldName) { @Override public RealmResults sort(String fieldName, Sort sortOrder) { if (isManaged()) { - return this.where().findAllSorted(fieldName, sortOrder); + return this.where().sort(fieldName, sortOrder).findAll(); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); } @@ -526,7 +526,7 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName @Override public RealmResults sort(String[] fieldNames, Sort[] sortOrders) { if (isManaged()) { - return where().findAllSorted(fieldNames, sortOrders); + return where().sort(fieldNames, sortOrders).findAll(); } else { throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 656cd32737..1662b3de8f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -23,6 +23,7 @@ import javax.annotation.Nullable; +import io.realm.annotations.Beta; import io.realm.annotations.Required; import io.realm.internal.OsResults; import io.realm.internal.OsList; @@ -33,6 +34,7 @@ import io.realm.internal.Table; import io.realm.internal.TableQuery; import io.realm.internal.fields.FieldDescriptor; +import io.realm.log.RealmLog; /** @@ -63,6 +65,8 @@ public class RealmQuery { private String className; private final boolean forValues; private final OsList osList; + private SortDescriptor sortDescriptor; + private SortDescriptor distinctDescriptor; private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; @@ -1595,6 +1599,7 @@ public RealmQuery isNotEmpty(String fieldName) { * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, or points * to linked fields. */ + @Deprecated public RealmResults distinct(String fieldName) { realm.checkIfValid(); @@ -1615,6 +1620,7 @@ public RealmResults distinct(String fieldName) { * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, or points * to linked fields. */ + @Deprecated public RealmResults distinctAsync(String fieldName) { realm.checkIfValid(); @@ -1635,6 +1641,7 @@ public RealmResults distinctAsync(String fieldName) { * @throws IllegalArgumentException if field names is empty or {@code null}, does not exist, * is an unsupported type, or points to a linked field. */ + @Deprecated public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { realm.checkIfValid(); @@ -1810,7 +1817,7 @@ public long count() { public RealmResults findAll() { realm.checkIfValid(); - return createRealmResults(query, null, null, true); + return createRealmResults(query, sortDescriptor, distinctDescriptor, true); } /** @@ -1824,7 +1831,7 @@ public RealmResults findAllAsync() { realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - return createRealmResults(query, null, null, false); + return createRealmResults(query, sortDescriptor, distinctDescriptor, false); } /** @@ -1841,9 +1848,9 @@ public RealmResults findAllAsync() { * {@link RealmObject} or a child {@link RealmList}. */ @SuppressWarnings("unchecked") + @Deprecated public RealmResults findAllSorted(String fieldName, Sort sortOrder) { realm.checkIfValid(); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldName, sortOrder); return createRealmResults(query, sortDescriptor, null, true); } @@ -1857,6 +1864,7 @@ public RealmResults findAllSorted(String fieldName, Sort sortOrder) { * @throws java.lang.IllegalArgumentException if field name does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ + @Deprecated public RealmResults findAllSortedAsync(final String fieldName, final Sort sortOrder) { realm.checkIfValid(); @@ -1865,6 +1873,125 @@ public RealmResults findAllSortedAsync(final String fieldName, final Sort sor return createRealmResults(query, sortDescriptor, null, false); } + /** + * Sorts the query result by the specific field name in ascending order. + *

            + * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', + * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. + * + * @param fieldName the field name to sort by. + * @throws IllegalArgumentException if the field name does not exist. + * @throws IllegalStateException if a sorting order was already defined. + */ + public RealmQuery sort(String fieldName) { + realm.checkIfValid(); + return sort(fieldName, Sort.ASCENDING); + } + + /** + * Sorts the query result by the specified field name and order. + *

            + * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', + * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. + * + * @param fieldName the field name to sort by. + * @param sortOrder how to sort the results. + * @throws IllegalArgumentException if the field name does not exist. + * @throws IllegalStateException if a sorting order was already defined. + */ + public RealmQuery sort(String fieldName, Sort sortOrder) { + realm.checkIfValid(); + return sort(new String[] { fieldName}, new Sort[] { sortOrder}); + } + + /** + * Sorts the query result by the specific field names in the provided orders. {@code fieldName2} is only used + * in case of equal values in {@code fieldName1}. + *

            + * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', + * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. + * + * @param fieldName1 first field name + * @param sortOrder1 sort order for first field + * @param fieldName2 second field name + * @param sortOrder2 sort order for second field + * @throws IllegalArgumentException if the field name does not exist. + * @throws IllegalStateException if a sorting order was already defined. + */ + public RealmQuery sort(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { + realm.checkIfValid(); + return sort(new String[] { fieldName1, fieldName2 }, new Sort[] { sortOrder1, sortOrder2 }); + } + + /** + * Sorts the query result by the specific field names in the provided orders. Later fields will only be used + * if the previous field values are equal. + *

            + * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', + * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. + * + * @param fieldNames an array of field names to sort by. + * @param sortOrders how to sort the field names. + * @throws IllegalArgumentException if the field name does not exist. + * @throws IllegalStateException if a sorting order was already defined. + */ + public RealmQuery sort(String[] fieldNames, Sort[] sortOrders) { + realm.checkIfValid(); + if (sortDescriptor != null) { + throw new IllegalStateException("A sorting order was already defined."); + } + sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldNames, sortOrders); + return this; + } + + /** + * BETA API: Will be renamed to {@code distinct} in next major release. + * + * Selects a distinct set of objects of a specific class. If the result is sorted, the first object will be + * returned in case of multiple occurrences, otherwise it is undefined which object is returned. + *

            + * Adding {@link io.realm.annotations.Index} to the corresponding field will make this operation much faster. + * + * @param fieldName the field name. + * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, or points + * to linked fields. + * @throws IllegalStateException if distinct field names were already defined. + */ + @Beta + public RealmQuery distinctValues(String fieldName) { + return distinctValues(fieldName, new String[]{}); + } + + /** + * BETA API: Will be renamed to {@code distinct} in next major release. + * + * Selects a distinct set of objects of a specific class. When multiple distinct fields are + * given, all unique combinations of values in the fields will be returned. In case of multiple + * matches, it is undefined which object is returned. Unless the result is sorted, then the + * first object will be returned. + * + * @param firstFieldName first field name to use when finding distinct objects. + * @param remainingFieldNames remaining field names when determining all unique combinations of field values. + * @throws IllegalArgumentException if field names is empty or {@code null}, does not exist, + * is an unsupported type, or points to a linked field. + * @throws IllegalStateException if distinct field names were already defined. + */ + @Beta + public RealmQuery distinctValues(String firstFieldName, String... remainingFieldNames) { + realm.checkIfValid(); + if (distinctDescriptor != null) { + throw new IllegalStateException("Distinct fields have already been defined."); + } + if (remainingFieldNames.length == 0) { + distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), table, firstFieldName); + } else { + String[] fieldNames = new String[1 + remainingFieldNames.length]; + fieldNames[0] = firstFieldName; + System.arraycopy(remainingFieldNames, 0, fieldNames, 1, remainingFieldNames.length); + distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), table, fieldNames); + } + return this; + } /** * Finds all objects that fulfill the query conditions and sorted by specific field name in ascending order. @@ -1878,6 +2005,7 @@ public RealmResults findAllSortedAsync(final String fieldName, final Sort sor * @throws java.lang.IllegalArgumentException if the field name does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ + @Deprecated public RealmResults findAllSorted(String fieldName) { return findAllSorted(fieldName, Sort.ASCENDING); } @@ -1891,6 +2019,7 @@ public RealmResults findAllSorted(String fieldName) { * @throws java.lang.IllegalArgumentException if the field name does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ + @Deprecated public RealmResults findAllSortedAsync(String fieldName) { return findAllSortedAsync(fieldName, Sort.ASCENDING); } @@ -1908,6 +2037,7 @@ public RealmResults findAllSortedAsync(String fieldName) { * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ + @Deprecated public RealmResults findAllSorted(String[] fieldNames, Sort[] sortOrders) { realm.checkIfValid(); @@ -1930,6 +2060,7 @@ private boolean isDynamicQuery() { * {@link RealmObject} or a child {@link RealmList}. * @see io.realm.RealmResults */ + @Deprecated public RealmResults findAllSortedAsync(String[] fieldNames, final Sort[] sortOrders) { realm.checkIfValid(); @@ -1953,6 +2084,7 @@ public RealmResults findAllSortedAsync(String[] fieldNames, final Sort[] sort * @throws java.lang.IllegalArgumentException if a field name does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ + @Deprecated public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { return findAllSorted(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); @@ -1967,6 +2099,7 @@ public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, * @throws java.lang.IllegalArgumentException if a field name does not exist or it belongs to a child * {@link RealmObject} or a child {@link RealmList}. */ + @Deprecated public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, String fieldName2, Sort sortOrder2) { return findAllSortedAsync(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); diff --git a/realm/realm-library/src/main/java/io/realm/Sort.java b/realm/realm-library/src/main/java/io/realm/Sort.java index 5d7c3d6f5d..861cccfbd6 100644 --- a/realm/realm-library/src/main/java/io/realm/Sort.java +++ b/realm/realm-library/src/main/java/io/realm/Sort.java @@ -19,7 +19,7 @@ /** * This class describes the sorting order used in Realm queries. * - * @see io.realm.RealmQuery#findAllSorted(String, Sort) + * @see io.realm.RealmQuery#sort(String, Sort) */ public enum Sort { ASCENDING(true), diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 494938d9e1..cf3602bfe1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -279,7 +279,7 @@ public void run() { .build(); final Realm adminRealm = Realm.getInstance(adminConfig); - RealmResults all = adminRealm.where(StringOnly.class).findAllSorted(StringOnly.FIELD_CHARS); + RealmResults all = adminRealm.where(StringOnly.class).sort(StringOnly.FIELD_CHARS).findAll(); RealmChangeListener> realmChangeListener = new RealmChangeListener>() { @Override public void onChange(RealmResults stringOnlies) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index ded87c8ca1..9c0418233c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -226,8 +226,8 @@ public void onError(SyncSession session, ObjectServerError error) { adminRealm = Realm.getInstance(adminConfigWithEncryption); - RealmResults allSorted = realm.where(StringOnly.class).findAllSorted(StringOnly.FIELD_CHARS); - RealmResults allSortedAdmin = adminRealm.where(StringOnly.class).findAllSorted(StringOnly.FIELD_CHARS); + RealmResults allSorted = realm.where(StringOnly.class).sort(StringOnly.FIELD_CHARS).findAll(); + RealmResults allSortedAdmin = adminRealm.where(StringOnly.class).sort(StringOnly.FIELD_CHARS).findAll(); assertEquals("Hi Alice", allSorted.get(0).getChars()); assertEquals("Hi Bob", allSorted.get(1).getChars()); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 08ba70684e..2b7c9383cb 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -192,7 +192,7 @@ public void expectALot() throws Throwable { .directory(looperThread.getRoot()) .build(); final Realm realm = Realm.getInstance(syncConfig); - final RealmResults all = realm.where(TestObject.class).findAllSorted("intProp"); + final RealmResults all = realm.where(TestObject.class).sort("intProp").findAll(); looperThread.keepStrongReference(all); final AtomicInteger listenerCalledCounter = new AtomicInteger(0); all.addChangeListener(new RealmChangeListener>() { From a0d73b933c2f9fd6a6b463e5907747edcf801f92 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Wed, 29 Nov 2017 21:57:18 +0800 Subject: [PATCH 1123/2110] Life cycle of temp OsSharedRealm in callbacks (#5576) Every temp OsSharedRealm created during construction for the callbacks have to be closed before the exception throws to users. close #5570 --- CHANGELOG.md | 1 + .../androidTest/java/io/realm/RealmTests.java | 25 +++++++++++- .../cpp/io_realm_internal_OsRealmConfig.cpp | 10 +---- .../java/io/realm/internal/OsSharedRealm.java | 39 ++++++++++++++++++- 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4094f4e5bf..4181eaa0c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Added missing `toString()` for the implementation of `OrderedCollectionChangeSet`. * Sync queries are evaluated immediately to solve the performance issue when the query results are huge, `RealmResults.size()` takes too long time (#5387). +* Correctly close the Realm instance if an exception was thrown while opening it. This avoids `IllegalStateException` when deleting the Realm in the catch block (#5570). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 509d05f3eb..136b5676ee 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -110,7 +110,6 @@ import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; -import io.realm.util.ExceptionHolder; import io.realm.util.RealmThread; import static io.realm.TestHelper.testNoObjectFound; @@ -4467,4 +4466,28 @@ public void getInstance_wrongSchemaInReadonlyThrows() { } catch (RealmMigrationNeededException ignored) { } } + + // https://github.com/realm/realm-java/issues/5570 + @Test + public void getInstance_migrationExceptionThrows_migrationBlockDefiend_realmInstancesShouldBeClosed() { + RealmConfiguration config = configFactory.createConfigurationBuilder() + .name("readonly.realm") + .schema(StringOnlyReadOnly.class, AllJavaTypes.class) + .schemaVersion(2) + .assetFile("readonly.realm") + .migration(new RealmMigration() { + @Override + public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { + } + }) + .build(); + + try { + realm = Realm.getInstance(config); + fail(); + } catch (RealmMigrationNeededException ignored) { + // No Realm instance should be opened at this time. + Realm.deleteRealm(config); + } + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 8a8eb0bcb3..1da13db3ce 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -155,10 +155,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSchemaConfi reinterpret_cast(new_shared_realm_ptr), config_global.get(), obj, old_realm->schema_version()); }); - // Close the OsSharedRealm. Otherwise it will only be closed when the Java OsSharedRealm gets GCed. And - // that will be too late. - TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED( - env, [&new_shared_realm_ptr]() { (*new_shared_realm_ptr)->close(); }); + TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env, nullptr); }; } else { @@ -230,10 +227,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetInitializat reinterpret_cast(new_shared_realm_ptr), config_global_ref.get(), obj); }); - // Close the OsSharedRealm. Otherwise it will only be closed when the Java OsSharedRealm gets GCed. And - // that will be too late. - TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED( - env, [&new_shared_realm_ptr]() { (*new_shared_realm_ptr)->close(); }); + TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env, nullptr); }; } else { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 027e0e71b8..137a2ef199 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -160,6 +160,14 @@ protected PartialSyncCallback(String className) { // JNI will only hold a weak global ref to this. public final RealmNotifier realmNotifier; public final Capabilities capabilities; + // For the Java callbacks during constructing in Object Store, some temporary OsSharedRealm objects need to be + // created as the parameter of the callback. The native pointers of those temp OsSharedRealm objects have to be + // valid during the whole life cycle of the Java object. The living native pointers still hold a ref-count to the + // SharedRealm which means the SharedRealm won't be closed automatically if there is any exception throws during + // construction. GC will clear them later, but that would be too late. So we are tracking the temp OsSharedRealm + // during the construction stage and manually close them if exception throws. + private final static List sharedRealmsUnderConstruction = new CopyOnWriteArrayList(); + private final List tempSharedRealmsForCallback = new ArrayList(); private final List> pendingRows = new CopyOnWriteArrayList<>(); // Package protected for testing @@ -169,10 +177,25 @@ private OsSharedRealm(OsRealmConfig osRealmConfig) { Capabilities capabilities = new AndroidCapabilities(); RealmNotifier realmNotifier = new AndroidRealmNotifier(this, capabilities); - this.nativePtr = nativeGetSharedRealm(osRealmConfig.getNativePtr(), realmNotifier); + // SharedRealms under constructions are identified by the Context. + this.context = osRealmConfig.getContext(); + sharedRealmsUnderConstruction.add(this); + try { + this.nativePtr = nativeGetSharedRealm(osRealmConfig.getNativePtr(), realmNotifier); + } catch (Throwable t) { + // The SharedRealm instances have to be closed before throw. + for (OsSharedRealm sharedRealm: tempSharedRealmsForCallback) { + if (!sharedRealm.isClosed()) { + sharedRealm.close(); + } + } + throw t; + } finally { + tempSharedRealmsForCallback.clear(); + sharedRealmsUnderConstruction.remove(this); + } this.osRealmConfig = osRealmConfig; this.schemaInfo = new OsSchemaInfo(nativeGetSchemaInfo(nativePtr), this); - this.context = osRealmConfig.getContext(); this.context.addReference(this); this.capabilities = capabilities; @@ -198,6 +221,18 @@ private OsSharedRealm(long nativeSharedRealmPtr, OsRealmConfig osRealmConfig) { // This instance should never need notifications. this.realmNotifier = null; nativeSetAutoRefresh(nativePtr, false); + + boolean foundParentSharedRealm = false; + for (OsSharedRealm sharedRealm : sharedRealmsUnderConstruction) { + if (sharedRealm.context == osRealmConfig.getContext()) { + foundParentSharedRealm = true; + sharedRealm.tempSharedRealmsForCallback.add(this); + break; + } + } + if (!foundParentSharedRealm) { + throw new IllegalStateException("Cannot find the parent 'OsSharedRealm' which is under construction."); + } } From e9109771e0251e73d87eaf9b0498d1da73e1188f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 1 Dec 2017 19:33:15 +0100 Subject: [PATCH 1124/2110] Kotlin Extension Library (#4684) --- CHANGELOG.md | 1 + Jenkinsfile | 6 +- examples/kotlinExample/build.gradle | 11 +- .../examples/kotlin/KotlinExampleActivity.kt | 33 +- examples/settings.gradle | 3 +- .../main/groovy/io/realm/gradle/Realm.groovy | 6 +- .../realm/gradle/RealmPluginExtension.groovy | 29 +- realm/build.gradle | 6 + realm/gradle.properties | 3 +- realm/kotlin-extensions/.gitignore | 1 + realm/kotlin-extensions/build.gradle | 370 ++++++++++++++++++ realm/kotlin-extensions/proguard-rules.pro | 25 ++ .../kotlin/io/realm/KotlinRealmModelTests.kt | 229 +++++++++++ .../kotlin/io/realm/KotlinRealmQueryTests.kt | 159 ++++++++ .../kotlin/io/realm/KotlinRealmTests.kt | 61 +++ .../io/realm/entities/AllPropTypesClass.kt | 20 + .../io/realm/entities/PrimaryKeyClass.kt | 12 + .../kotlin/io/realm/entities/SimpleClass.kt | 10 + .../src/main/AndroidManifest.xml | 2 + .../kotlin/io/realm/kotlin/RealmExtensions.kt | 102 +++++ .../io/realm/kotlin/RealmModelExtensions.kt | 213 ++++++++++ .../io/realm/kotlin/RealmQueryExtensions.kt | 153 ++++++++ .../src/main/res/values/strings.xml | 3 + realm/realm-library/build.gradle | 25 +- .../java/io/realm/TestHelper.java | 0 .../java/io/realm/entities/AllTypes.java | 0 .../io/realm/entities/AllTypesPrimaryKey.java | 0 .../realm/entities/AnnotationIndexTypes.java | 0 .../io/realm/entities/BacklinksSource.java | 0 .../io/realm/entities/BacklinksTarget.java | 0 .../java/io/realm/entities/Cat.java | 0 .../java/io/realm/entities/Dog.java | 0 .../java/io/realm/entities/DogPrimaryKey.java | 0 .../java/io/realm/entities/NullTypes.java | 0 .../java/io/realm/entities/Owner.java | 0 .../realm/entities/PrimaryKeyAsBoxedByte.java | 0 .../entities/PrimaryKeyAsBoxedInteger.java | 0 .../realm/entities/PrimaryKeyAsBoxedLong.java | 0 .../entities/PrimaryKeyAsBoxedShort.java | 0 .../io/realm/entities/PrimaryKeyAsByte.java | 0 .../realm/entities/PrimaryKeyAsInteger.java | 0 .../io/realm/entities/PrimaryKeyAsLong.java | 0 .../io/realm/entities/PrimaryKeyAsShort.java | 0 .../io/realm/entities/PrimaryKeyAsString.java | 0 .../PrimaryKeyRequiredAsBoxedByte.java | 0 .../PrimaryKeyRequiredAsBoxedInteger.java | 0 .../PrimaryKeyRequiredAsBoxedLong.java | 0 .../PrimaryKeyRequiredAsBoxedShort.java | 0 .../io/realm/objectid/NullPrimaryKey.java | 0 .../java/io/realm/rule/RunInLooperThread.java | 0 .../io/realm/rule/RunTestInLooperThread.java | 0 .../realm/rule/RunTestWithRemoteService.java | 0 .../io/realm/rule/RunWithRemoteService.java | 0 .../rule/TestRealmConfigurationFactory.java | 0 .../realm/services/RemoteProcessService.java | 0 .../io/realm/services/RemoteTestService.java | 2 - realm/settings.gradle | 5 +- 57 files changed, 1444 insertions(+), 46 deletions(-) create mode 100644 realm/kotlin-extensions/.gitignore create mode 100644 realm/kotlin-extensions/build.gradle create mode 100644 realm/kotlin-extensions/proguard-rules.pro create mode 100644 realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt create mode 100644 realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt create mode 100644 realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt create mode 100644 realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/AllPropTypesClass.kt create mode 100644 realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/PrimaryKeyClass.kt create mode 100644 realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/SimpleClass.kt create mode 100644 realm/kotlin-extensions/src/main/AndroidManifest.xml create mode 100644 realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt create mode 100644 realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmModelExtensions.kt create mode 100644 realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt create mode 100644 realm/kotlin-extensions/src/main/res/values/strings.xml rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/TestHelper.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/AllTypes.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/AllTypesPrimaryKey.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/AnnotationIndexTypes.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/BacklinksSource.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/BacklinksTarget.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/Cat.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/Dog.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/DogPrimaryKey.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/NullTypes.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/Owner.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyAsBoxedByte.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyAsBoxedInteger.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyAsBoxedLong.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyAsBoxedShort.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyAsByte.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyAsInteger.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyAsLong.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyAsShort.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyAsString.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyRequiredAsBoxedByte.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyRequiredAsBoxedInteger.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyRequiredAsBoxedLong.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/entities/PrimaryKeyRequiredAsBoxedShort.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/objectid/NullPrimaryKey.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/rule/RunInLooperThread.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/rule/RunTestInLooperThread.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/rule/RunTestWithRemoteService.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/rule/RunWithRemoteService.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/rule/TestRealmConfigurationFactory.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/services/RemoteProcessService.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/java/io/realm/services/RemoteTestService.java (98%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42bed8de7e..5a54642629 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Enhancements +* Projects using Kotlin now include additional extension functions that make working with Kotlin easier. See [docs](https://realm.io/docs/java/latest/#kotlin) for more info (#4684). * New query predicate: `sort()`. * New query predicate: `distinctValues()`. Will be renamed to `distinct` in next major version. * The Realm annotation processor now has a stable output when there are no changes to model classes, improving support for incremental compilers (#5567). diff --git a/Jenkinsfile b/Jenkinsfile index f511eb1188..7c8e9a7d6d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -95,6 +95,7 @@ try { } finally { stopLogCatCollector(backgroundPid) storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' } } } @@ -195,8 +196,9 @@ def getTagsString(Map tags) { def storeJunitResults(String path) { step([ $class: 'JUnitResultArchiver', - testResults: path - ]) + allowEmptyResults: true, + testResults: path + ]) } def collectAarMetrics() { diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 88e03b0551..df885d428c 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -44,10 +44,17 @@ android { } } -// enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out/ +// This is added automatically if Kotlin is registered in the project, but Kotlin extension functions +// for Realm can be excluded if needed. +realm { + kotlinExtensionsEnabled = true +} + + +// enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/09/kotlin-1-1-50-is-out/ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { - freeCompilerArgs = ["-Xjsr305-annotations=enable"] + freeCompilerArgs = ["-Xjsr305=strict"] } } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index 643704a169..82a2cfb8f5 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -26,6 +26,8 @@ import io.realm.Sort import io.realm.examples.kotlin.model.Cat import io.realm.examples.kotlin.model.Dog import io.realm.examples.kotlin.model.Person +import io.realm.kotlin.createObject +import io.realm.kotlin.where import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread import kotlin.properties.Delegates @@ -91,13 +93,13 @@ class KotlinExampleActivity : Activity() { // All writes must be wrapped in a transaction to facilitate safe multi threading realm.executeTransaction { // Add a person - val person = realm.createObject(Person::class.java, 0) + val person = realm.createObject(0) person.name = "Young Person" person.age = 14 } // Find the first person (no query conditions) and read a field - val person = realm.where(Person::class.java).findFirst()!! + val person = realm.where().findFirst()!! showStatus(person.name + ": " + person.age) // Update person in a transaction @@ -110,18 +112,19 @@ class KotlinExampleActivity : Activity() { private fun basicQuery(realm: Realm) { showStatus("\nPerforming basic Query operation...") - showStatus("Number of persons: ${realm.where(Person::class.java).count()}") + showStatus("Number of persons: ${realm.where().count()}") - val results = realm.where(Person::class.java).equalTo("age", 99.toInt()).findAll() + val ageCriteria = 99 + val results = realm.where().equalTo("age", ageCriteria).findAll() showStatus("Size of result set: " + results.size) } private fun basicLinkQuery(realm: Realm) { showStatus("\nPerforming basic Link Query operation...") - showStatus("Number of persons: ${realm.where(Person::class.java).count()}") + showStatus("Number of persons: ${realm.where().count()}") - val results = realm.where(Person::class.java).equalTo("cats.name", "Tiger").findAll() + val results = realm.where().equalTo("cats.name", "Tiger").findAll() showStatus("Size of result set: ${results.size}") } @@ -135,10 +138,10 @@ class KotlinExampleActivity : Activity() { try { // Add ten persons in one transaction realm.executeTransaction { - val fido = realm.createObject(Dog::class.java) + val fido = realm.createObject() fido.name = "fido" for (i in 1..9) { - val person = realm.createObject(Person::class.java, i.toLong()) + val person = realm.createObject(i.toLong()) person.name = "Person no. $i" person.age = i person.dog = fido @@ -150,7 +153,7 @@ class KotlinExampleActivity : Activity() { person.tempReference = 42 for (j in 0..i - 1) { - val cat = realm.createObject(Cat::class.java) + val cat = realm.createObject() cat.name = "Cat_$j" person.cats.add(cat) } @@ -158,10 +161,10 @@ class KotlinExampleActivity : Activity() { } // Implicit read transactions allow you to access your objects - status += "\nNumber of persons: ${realm.where(Person::class.java).count()}" + status += "\nNumber of persons: ${realm.where().count()}" // Iterate over all objects - for (person in realm.where(Person::class.java).findAll()) { + for (person in realm.where().findAll()) { val dogName: String = person?.dog?.name ?: "None" status += "\n${person.name}: ${person.age} : $dogName : ${person.cats.size}" @@ -173,8 +176,8 @@ class KotlinExampleActivity : Activity() { } // Sorting - val sortedPersons = realm.where(Person::class.java).findAllSorted("age", Sort.DESCENDING) - status += "\nSorting ${sortedPersons.last()?.name} == ${realm.where(Person::class.java).findAll().first()?.name}" + val sortedPersons = realm.where().findAllSorted(Person::age.name, Sort.DESCENDING) + status += "\nSorting ${sortedPersons.last()?.name} == ${realm.where().findAll().first()?.name}" } finally { realm.close() @@ -189,11 +192,11 @@ class KotlinExampleActivity : Activity() { // extension method 'use' (pun intended). Realm.getDefaultInstance().use { // 'it' is the implicit lambda parameter of type Realm - status += "\nNumber of persons: ${it.where(Person::class.java).count()}" + status += "\nNumber of persons: ${it.where().count()}" // Find all persons where age between 7 and 9 and name begins with "Person". val results = it - .where(Person::class.java) + .where() .between("age", 7, 9) // Notice implicit "and" operation .beginsWith("name", "Person") .findAll() diff --git a/examples/settings.gradle b/examples/settings.gradle index 9edd7eb54f..42ff9775fe 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -1,3 +1,4 @@ +rootProject.name = 'realm-examples' include 'secureTokenAndroidKeyStore' include 'encryptionExample' include 'gridViewExample' @@ -13,5 +14,3 @@ include 'newsreaderExample' include 'rxJavaExample' include 'objectServerExample' include 'multiprocessExample' - -rootProject.name = 'realm-examples' diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 6a24fa434d..6064b95cf9 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -42,14 +42,16 @@ class Realm implements Plugin { def syncEnabledDefault = false def dependencyConfigurationName = getDependencyConfigurationName(project) - project.extensions.create('realm', RealmPluginExtension, project, syncEnabledDefault, dependencyConfigurationName) - def usesAptPlugin = project.plugins.findPlugin('com.neenbedankt.android-apt') != null def isKotlinProject = project.plugins.findPlugin('kotlin-android') != null + def useKotlinExtensionsDefault = isKotlinProject def hasAnnotationProcessorConfiguration = project.getConfigurations().findByName('annotationProcessor') != null // TODO add a parameter in 'realm' block if this should be specified by users def preferAptOnKotlinProject = false + + project.extensions.create('realm', RealmPluginExtension, project, syncEnabledDefault, useKotlinExtensionsDefault, dependencyConfigurationName) + if (shouldApplyAndroidAptPlugin(usesAptPlugin, isKotlinProject, hasAnnotationProcessorConfiguration, preferAptOnKotlinProject)) { project.plugins.apply(AndroidAptPlugin) diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy index 42bd8e5f39..bd1ea27e38 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy @@ -21,28 +21,45 @@ import org.gradle.api.Project class RealmPluginExtension { private Project project def boolean syncEnabled + def boolean kotlinExtensionsEnabled private String dependencyConfigurationName - RealmPluginExtension(Project project, boolean syncEnabledDefault, String dependencyConfigurationName) { + RealmPluginExtension(Project project, boolean syncEnabledDefault, boolean useKotlinExtensionsDefault, String dependencyConfigurationName) { this.project = project this.dependencyConfigurationName = dependencyConfigurationName setSyncEnabled(syncEnabledDefault) + setKotlinExtensionsEnabled(useKotlinExtensionsDefault) } void setSyncEnabled(value) { this.syncEnabled = value; + setDependencies(syncEnabled, kotlinExtensionsEnabled) + } + + void setKotlinExtensionsEnabled(value) { + this.kotlinExtensionsEnabled = value + setDependencies(syncEnabled, kotlinExtensionsEnabled) + } - // remove realm android library first + void setDependencies(boolean syncEnabled, boolean kotlinExtensionsEnabled) { + // remove libraries first def iterator = project.getConfigurations().getByName(dependencyConfigurationName).getDependencies().iterator(); while (iterator.hasNext()) { def item = iterator.next() - if (item.group == 'io.realm' && item.name.startsWith('realm-android-library')) { - iterator.remove() + if (item.group == 'io.realm') { + if (item.name.startsWith('realm-android-library')) { + iterator.remove() + } + if (item.name.startsWith('realm-android-kotlin-extensions')) { + iterator.remove() + } } } // then add again - def artifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" - project.dependencies.add(dependencyConfigurationName, "io.realm:${artifactName}:${Version.VERSION}") + def syncArtifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" + project.dependencies.add(dependencyConfigurationName, "io.realm:${syncArtifactName}:${Version.VERSION}") + def kotlinExtArtifactName = "realm-android-kotlin-extensions${kotlinExtensionsEnabled ? '-object-server' : ''}" + project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") } } diff --git a/realm/build.gradle b/realm/build.gradle index ce69d4e851..be9e85de9c 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,5 +1,10 @@ +project.ext.minSdkVersion = 9 +project.ext.compileSdkVersion = 26 +project.ext.buildToolsVersion = '26.0.2' + buildscript { ext.kotlin_version = '1.1.51' + ext.dokka_version = '0.9.15' repositories { mavenLocal() google() @@ -20,6 +25,7 @@ buildscript { classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.13' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" } } diff --git a/realm/gradle.properties b/realm/gradle.properties index 20cd0c88de..b9c41987bf 100644 --- a/realm/gradle.properties +++ b/realm/gradle.properties @@ -1,2 +1,3 @@ org.gradle.jvmargs=-Xms512m -Xmx2048m -org.gradle.caching=true \ No newline at end of file +org.gradle.caching=true +kotlin.incremental=false; diff --git a/realm/kotlin-extensions/.gitignore b/realm/kotlin-extensions/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/realm/kotlin-extensions/.gitignore @@ -0,0 +1 @@ +/build diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle new file mode 100644 index 0000000000..bd05e52d59 --- /dev/null +++ b/realm/kotlin-extensions/build.gradle @@ -0,0 +1,370 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'com.github.dcendents.android-maven' +apply plugin: 'maven-publish' +apply plugin: 'com.jfrog.artifactory' +apply plugin: 'de.undercouch.download' +apply plugin: 'org.jetbrains.dokka' + +// TODO How many of these work on Kotlin? +//apply plugin: 'findbugs' +//apply plugin: 'pmd' +//apply plugin: 'checkstyle' +//apply plugin: 'com.github.kt3k.coveralls' +//apply plugin: 'net.ltgt.errorprone' + +import io.realm.transformer.RealmTransformer +android.registerTransform(new RealmTransformer()) + +android { + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.compileSdkVersion + versionName version + project.archivesBaseName = "realm-kotlin-extensions" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + debug { + // https://youtrack.jetbrains.com/issue/KT-11333 + // Until this is resolved, enabling code coverage will break extension functions + // during instrumentation testing. + testCoverageEnabled = false + } + release { + minifyEnabled false + } + } + + flavorDimensions 'api' + + productFlavors { + base { + dimension 'api' + } + objectServer { + dimension 'api' + } + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + androidTest.java.srcDirs += ['src/androidTest/kotlin', '../realm-library/src/testUtils/java'] + objectServer.java.srcDirs += 'src/objectServer/kotlin' + androidTestObjectServer.java.srcDirs += 'src/androidTestObjectServer/kotlin' + } +} + +dependencies { + implementation project(':realm-library') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + androidTestImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test:rules:1.0.1' + kaptAndroidTest project(':realm-annotations-processor') + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" +} + +repositories { + mavenCentral() +} + +// enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/09/kotlin-1-1-50-is-out/ +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + freeCompilerArgs = ["-Xjsr305=strict"] + } +} + + +task sourcesJar(type: Jar) { + from android.sourceSets.objectServer.java.srcDirs + from android.sourceSets.main.java.srcDirs + classifier = 'sources' +} + +dokka { + // TODO Filtering is currently not possible https://youtrack.jetbrains.com/issue/KT-21022 + // This means we cannot filter R/BuildConfig files for the time being + outputFormat = 'html' + outputDirectory = "$buildDir/dokka" +} + +task javadocJar(type: Jar, dependsOn: dokka) { + classifier = 'javadoc' + from "$buildDir/dokka" +} + +// Deployment + +install { + repositories.mavenInstaller { + pom { + project { + packaging 'aar' + + // Add your description here + name 'realm-kotlin-extensions' + description 'Kotlin specific APIs and extension functions for Realm for Android' + url 'http://realm.io' + + // Set your license + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + issueManagement { + system 'github' + url 'https://github.com/realm/realm-java/issues' + } + scm { + url 'scm:https://github.com/realm/realm-java' + connection 'scm:git@github.com:realm/realm-java.git' + developerConnection 'scm:git@github.com:realm/realm-java.git' + } + } + } + } +} + +// The publications doesn't know about our AAR dependencies, so we have to manually add them to the pom +// Credit: http://stackoverflow.com/questions/24743562/gradle-not-including-dependencies-in-published-pom-xml +def createPomDependencies(configurationNames) { + return { + def dependenciesNode = asNode().appendNode('dependencies') + configurationNames.each { configurationName -> + configurations[configurationName].allDependencies.each { + if (it.group != null && it.name != null && it.name != 'realm-library') { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + + //If there are any exclusions in dependency + if (it.excludeRules.size() > 0) { + def exclusionsNode = dependencyNode.appendNode('exclusions') + it.excludeRules.each { rule -> + def exclusionNode = exclusionsNode.appendNode('exclusion') + exclusionNode.appendNode('groupId', rule.group) + exclusionNode.appendNode('artifactId', rule.module) + } + } + } + } + } + } +} + +publishing { + publications { + basePublication(MavenPublication) { + groupId 'io.realm' + artifactId 'realm-android-kotlin-extensions' + version project.version + artifact file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-base-release.aar") + artifact sourcesJar + artifact javadocJar + + pom.withXml(createPomDependencies(["baseImplementation", "implementation", "baseApi", "api"])) + } + + objectServerPublication(MavenPublication) { + groupId 'io.realm' + artifactId 'realm-android-kotlin-extensions-object-server' + version project.version + artifact file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-objectServer-release.aar") + artifact sourcesJar + artifact javadocJar + + pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) + } + } + repositories { + maven { + credentials(AwsCredentials) { + accessKey project.hasProperty('s3AccessKey') ? s3AccessKey : 'noAccessKey' + secretKey project.hasProperty('s3SecretKey') ? s3SecretKey : 'noSecretKey' + } + if (project.version.endsWith('-SNAPSHOT')) { + url "s3://realm-ci-artifacts/maven/snapshots/" + } else { + url "s3://realm-ci-artifacts/maven/releases/" + } + } + } +} + +artifactory { + contextUrl = 'https://oss.jfrog.org/artifactory' + publish { + repository { + repoKey = 'oss-snapshot-local' + username = project.hasProperty('bintrayUser') ? bintrayUser : 'noUser' + password = project.hasProperty('bintrayKey') ? bintrayKey : 'noKey' + } + defaults { + publications('basePublication', 'objectServerPublication') + publishPom = true + publishIvy = false + } + } +} + +artifacts { + archives javadocJar + archives sourcesJar +} + +publishToMavenLocal.dependsOn assemble + +android.productFlavors.all { flavor -> + def librarySuffix = flavor.name == 'base' ? '' : '-object-server' + def userName = project.findProperty('bintrayUser') ?: 'noUser' + def accessKey = project.findProperty('bintrayKey') ?: 'noKey' + def artifactId = "realm-android-kotlin-extensions${librarySuffix}" + + // BINTRAY + + task("bintrayAar${flavor.name.capitalize()}", type: Exec) { + dependsOn "assemble${flavor.name.capitalize()}" + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/outputs/aar/realm-kotlin-extensions-${flavor.name}-release.aar", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/${artifactId}/${project.version}/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}.aar?publish=0" + } + + task("bintraySources${flavor.name.capitalize()}", type: Exec) { + dependsOn sourcesJar + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-kotlin-extensions-${project.version}-sources.jar", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/${artifactId}/${project.version}/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}-sources.jar?publish=0" + } + + task("bintrayJavadoc${flavor.name.capitalize()}", type: Exec) { + dependsOn javadocJar + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-kotlin-extensions-${project.version}-javadoc.jar", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/${artifactId}/${project.version}/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}-javadoc.jar?publish=0" + } + + task("bintrayPom${flavor.name.capitalize()}", type: Exec) { + dependsOn "publish${flavor.name.capitalize()}PublicationPublicationToMavenLocal" + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/publications/${flavor.name}Publication/pom-default.xml", + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/${artifactId}/${project.version}/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}.pom?publish=0" + } + + // OJO + + task("ojoAar${flavor.name.capitalize()}", type: Exec) { + dependsOn "assemble${flavor.name.capitalize()}" + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/outputs/aar/realm-kotlin-extensions-${flavor.name}-release.aar", + '-u', + "${userName}:${accessKey}", + "https://oss.jfrog.org/artifactory/oss-snapshot-local/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}.aar?publish=0" + } + + task("ojoSources${flavor.name.capitalize()}", type: Exec) { + dependsOn sourcesJar + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-kotlin-extensions-${project.version}-sources.jar", + '-u', + "${userName}:${accessKey}", + "https://oss.jfrog.org/artifactory/oss-snapshot-local/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}-sources.jar?publish=0" + } + + task("ojoJavadoc${flavor.name.capitalize()}", type: Exec) { + dependsOn javadocJar + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/libs/realm-kotlin-extensions-${project.version}-javadoc.jar", + '-u', + "${userName}:${accessKey}", + "https://oss.jfrog.org/artifactory/oss-snapshot-local/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}-javadoc.jar?publish=0" + } + + task("ojoPom${flavor.name.capitalize()}", type: Exec) { + dependsOn "publish${flavor.name.capitalize()}PublicationPublicationToMavenLocal" + group = 'Publishing' + commandLine 'curl', + '-X', + 'PUT', + '-T', + "${buildDir}/publications/${flavor.name}Publication/pom-default.xml", + '-u', + "${userName}:${accessKey}", + "https://oss.jfrog.org/artifactory/oss-snapshot-local/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}.pom?publish=0" + } + + task("bintray${flavor.name.capitalize()}") { + dependsOn "bintrayAar${flavor.name.capitalize()}" + dependsOn "bintraySources${flavor.name.capitalize()}" + dependsOn "bintrayJavadoc${flavor.name.capitalize()}" + dependsOn "bintrayPom${flavor.name.capitalize()}" + group = 'Publishing' + } + + task("ojo${flavor.name.capitalize()}") { + dependsOn "ojoAar${flavor.name.capitalize()}" + dependsOn "ojoSources${flavor.name.capitalize()}" + dependsOn "ojoJavadoc${flavor.name.capitalize()}" + dependsOn "ojoPom${flavor.name.capitalize()}" + group = 'Publishing' + } +} + +task bintrayUpload() { + android.productFlavors.all { flavor -> + dependsOn "bintray${flavor.name.capitalize()}" + } + group = 'Publishing' +} + +task ojoUpload() { + android.productFlavors.all { flavor -> + dependsOn "ojo${flavor.name.capitalize()}" + } + group = 'Publishing' +} diff --git a/realm/kotlin-extensions/proguard-rules.pro b/realm/kotlin-extensions/proguard-rules.pro new file mode 100644 index 0000000000..64bf447535 --- /dev/null +++ b/realm/kotlin-extensions/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/opt/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt new file mode 100644 index 0000000000..4130e260d3 --- /dev/null +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt @@ -0,0 +1,229 @@ +package io.realm + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 +import io.realm.entities.PrimaryKeyClass +import io.realm.entities.SimpleClass +import io.realm.kotlin.* +import io.realm.rule.RunInLooperThread +import io.realm.rule.RunTestInLooperThread +import io.realm.rule.TestRealmConfigurationFactory +import org.junit.* +import org.junit.Assert.* +import org.junit.runner.RunWith + +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +class KotlinRealmModelTests { + + @Suppress("MemberVisibilityCanPrivate") + @get:Rule + val configFactory = TestRealmConfigurationFactory() + + @get:Rule + val looperThread = RunInLooperThread() + + private lateinit var realm: Realm + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getTargetContext()) + realm = Realm.getInstance(configFactory.createConfiguration()) + } + + @After + fun tearDown() { + realm.close() + } + + @Test + fun deleteFromRealm() { + // Make sure starting with 0 + Assert.assertEquals(0, realm.where().count()) + + // Add 1, check count + realm.executeTransaction { it.createObject() } + Assert.assertEquals(1, realm.where().count()) + + // Delete the first, check count again. !! is intentional to make + // sure we are sure calling deleteFromRealm + realm.executeTransaction { realm.where().findFirst()!!.deleteFromRealm() } + Assert.assertEquals(0, realm.where().count()) + + } + + @Test + fun isValid() { + realm.executeTransaction { + val obj = it.createObject() + assertTrue("Expected valid after insert", obj.isValid()) + + obj.deleteFromRealm() + assertFalse("Expected invalid after delete", obj.isValid()) + } + } + + @Test + fun isManaged() { + realm.executeTransaction { + var obj = SimpleClass() + assertFalse("Expected not managed until attached", obj.isManaged()) + + obj = it.copyToRealm(obj) + assertTrue("Expected managed after attaching", obj.isManaged()) + } + } + + @Test + @RunTestInLooperThread + fun addChangeListener_RealmObjectChangeListener_addObject() { + val realm = looperThread.realm + realm.beginTransaction() + val obj = realm.createObject() + realm.commitTransaction() + + looperThread.keepStrongReference(obj) + obj.addChangeListener( RealmObjectChangeListener { updatedObj, changes -> + assertTrue(changes?.isFieldChanged(SimpleClass::name.name) ?: false) + assertEquals("simple1", updatedObj.name) + looperThread.testComplete() + }) + + realm.beginTransaction() + obj.name = "simple1" + realm.commitTransaction() + } + + @Test + @RunTestInLooperThread + fun addChangeListener_RealmChangeListener_addObject() { + val realm = looperThread.realm + realm.beginTransaction() + val obj = realm.createObject() + realm.commitTransaction() + + looperThread.keepStrongReference(obj) + obj.addChangeListener( RealmChangeListener { simpleClass -> + assertEquals("simple1", simpleClass.name) + looperThread.testComplete() + }) + + realm.beginTransaction() + obj.name = "simple1" + realm.commitTransaction() + } + + @Test + @RunTestInLooperThread + fun removeChangeListener_RealmChangeListener_removeObject() { + val realm = looperThread.realm + realm.beginTransaction() + val obj = realm.createObject(101) + realm.commitTransaction() + + val listener = RealmChangeListener{ + fail() + } + + obj.addChangeListener(listener) + obj.removeChangeListener(listener) + + realm.beginTransaction() + obj.name = "Bobby Risigliano" + realm.commitTransaction() + + // Try to trigger the listeners. + realm.sharedRealm.refresh() + looperThread.testComplete() + } + + @Test + @RunTestInLooperThread + fun removeChangeListener_RealmObjectChangeListener_removeObject() { + val realm = looperThread.realm + realm.beginTransaction() + val obj = realm.createObject(101) + realm.commitTransaction() + + val listener = RealmObjectChangeListener{ _,_ -> + fail() + } + + obj.addChangeListener(listener) + obj.removeChangeListener(listener) + + realm.beginTransaction() + obj.name = "Bobby Risigliano" + realm.commitTransaction() + + // Try to trigger the listeners. + realm.sharedRealm.refresh() + looperThread.testComplete() + } + + @Test + @RunTestInLooperThread + fun removeAllChangeListeners() { + val realm = looperThread.realm + realm.beginTransaction() + val obj = realm.createObject(101) + realm.commitTransaction() + + val changeListener = RealmChangeListener { + fail() + } + val objectChangeListener = RealmObjectChangeListener { _,_ -> + fail() + } + + obj.addChangeListener(changeListener) + obj.addChangeListener(objectChangeListener) + + obj.removeAllChangeListeners() + + realm.beginTransaction() + obj.name = "Bobby Risigliano" + realm.commitTransaction() + + // Try to trigger the listeners. + realm.sharedRealm.refresh() + looperThread.testComplete() + } + + @Test + @RunTestInLooperThread + @Throws(Throwable::class) + fun isLoaded() { + val realm = looperThread.realm + + realm.executeTransaction { it.createObject() } + + val result = realm.where().findFirstAsync() + assertFalse("Expect isLoaded is false just after async call", result.isLoaded()) + + looperThread.keepStrongReference(result) + + result.addChangeListener(RealmChangeListener { r -> + assertTrue("Expected the loading to have completed", r.isLoaded()) + looperThread.testComplete() + }) + } + + @Test + @RunTestInLooperThread + @Throws(Throwable::class) + fun load() { + val realm = looperThread.realm + + realm.executeTransaction { it.createObject() } + + val result = realm.where().findFirstAsync() + assertFalse("Expect isLoaded is false just after async call", result.isLoaded()) + + result.load() + + assertTrue("Expected isLoaded is true after blocking on load()", result.isLoaded()) + looperThread.testComplete() + } + +} diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt new file mode 100644 index 0000000000..a7c9049eb6 --- /dev/null +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt @@ -0,0 +1,159 @@ +package io.realm + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 +import io.realm.entities.AllPropTypesClass +import io.realm.kotlin.createObject +import io.realm.kotlin.oneOf +import io.realm.kotlin.where +import io.realm.rule.TestRealmConfigurationFactory +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* + +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +class KotlinRealmQueryTests { + + @Suppress("MemberVisibilityCanPrivate") + @get:Rule + val configFactory = TestRealmConfigurationFactory() + + private lateinit var realm: Realm + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getTargetContext()) + realm = Realm.getInstance(configFactory.createConfiguration()) + } + + @After + fun tearDown() { + realm.close() + } + + @Test + fun oneOf_String() { + realm.beginTransaction() + val obj = realm.createObject() + obj.stringVar = "test" + realm.commitTransaction() + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::stringVar.name, arrayOf("test")) + .count()) + } + + @Test + fun oneOf_Byte() { + realm.beginTransaction() + val obj = realm.createObject() + obj.byteVar = 3 + realm.commitTransaction() + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::byteVar.name, arrayOf(3)) + .count()) + } + + @Test + fun oneOf_Short() { + realm.beginTransaction() + val obj = realm.createObject() + obj.shortVar = 3 + realm.commitTransaction() + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::shortVar.name, arrayOf(3)) + .count()) + } + + @Test + fun oneOf_Int() { + realm.beginTransaction() + val obj = realm.createObject() + obj.intVar = 3 + realm.commitTransaction() + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::intVar.name, arrayOf(3)) + .count()) + } + + @Test + fun oneOf_Long() { + realm.beginTransaction() + val obj = realm.createObject() + obj.longVar = 3 + realm.commitTransaction() + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::longVar.name, arrayOf(3)) + .count()) + } + + @Test + fun oneOf_Double() { + realm.beginTransaction() + val obj = realm.createObject() + obj.doubleVar = 3.5 + realm.commitTransaction() + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::doubleVar.name, arrayOf(3.5)) + .count()) + } + + @Test + fun oneOf_Float() { + realm.beginTransaction() + val obj = realm.createObject() + obj.floatVar = 3.5f + realm.commitTransaction() + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::floatVar.name, arrayOf(3.5f)) + .count()) + } + + @Test + fun oneOf_Boolean() { + realm.beginTransaction() + val obj = realm.createObject() + obj.booleanVar = true + realm.commitTransaction() + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::booleanVar.name, arrayOf(true)) + .count()) + } + + @Test + fun oneOf_Date() { + + val testDate = Date() + + realm.beginTransaction() + val obj = realm.createObject() + obj.dateVar = testDate + realm.commitTransaction() + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::dateVar.name, arrayOf(testDate)) + .count()) + } + +} diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt new file mode 100644 index 0000000000..eebd34cf20 --- /dev/null +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt @@ -0,0 +1,61 @@ +package io.realm + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 +import io.realm.entities.PrimaryKeyClass +import io.realm.entities.SimpleClass +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import io.realm.rule.TestRealmConfigurationFactory +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +class KotlinRealmTests { + + @Suppress("MemberVisibilityCanPrivate") + @get:Rule + val configFactory = TestRealmConfigurationFactory() + + private lateinit var realm: Realm + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getTargetContext()) + realm = Realm.getInstance(configFactory.createConfiguration()) + } + + @After + fun tearDown() { + realm.close() + } + + @Test + fun createObject() { + realm.executeTransaction { + it.createObject() + } + assertEquals(1, realm.where().count()) + } + + + @Test + fun createObject_primaryKey() { + realm.executeTransaction { + it.createObject(1) + } + assertEquals(1, realm.where().count()) + } + + @Test + fun where() { + assertEquals(0, realm.where().count()) + } + + +} \ No newline at end of file diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/AllPropTypesClass.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/AllPropTypesClass.kt new file mode 100644 index 0000000000..35202fc7da --- /dev/null +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/AllPropTypesClass.kt @@ -0,0 +1,20 @@ +package io.realm.entities + +import io.realm.RealmModel +import io.realm.annotations.RealmClass +import java.util.* + +@RealmClass +open class AllPropTypesClass : RealmModel { + + var stringVar: String = "" + var byteVar: Byte = 0 + var shortVar: Short = 0 + var intVar: Int = 0 + var longVar: Long = 0 + var doubleVar: Double = 0.0 + var floatVar: Float = 0.0f + var booleanVar : Boolean = false + var dateVar : Date = Date() + +} diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/PrimaryKeyClass.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/PrimaryKeyClass.kt new file mode 100644 index 0000000000..83b1729737 --- /dev/null +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/PrimaryKeyClass.kt @@ -0,0 +1,12 @@ +package io.realm.entities + +import io.realm.RealmModel +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmClass + +@RealmClass +open class PrimaryKeyClass: RealmModel { + @PrimaryKey + var id: Long = 0 + var name: String = "" +} diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/SimpleClass.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/SimpleClass.kt new file mode 100644 index 0000000000..2091fbe672 --- /dev/null +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/SimpleClass.kt @@ -0,0 +1,10 @@ +package io.realm.entities + +import io.realm.RealmModel +import io.realm.RealmObject +import io.realm.annotations.RealmClass + +@RealmClass +open class SimpleClass : RealmModel { + var name: String = "" +} diff --git a/realm/kotlin-extensions/src/main/AndroidManifest.xml b/realm/kotlin-extensions/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..3d03c8bb1c --- /dev/null +++ b/realm/kotlin-extensions/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt new file mode 100644 index 0000000000..1307e48a38 --- /dev/null +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.kotlin + +import io.realm.Realm +import io.realm.RealmModel +import io.realm.RealmQuery +import io.realm.exceptions.RealmException + +/** + * Returns a typed RealmQuery, which can be used to query for specific objects of this type + * + * @param T the class of the object which is to be queried for. + * @return a typed `RealmQuery`, which can be used to query for specific objects of this type. + */ +inline fun Realm.where(): RealmQuery { + return this.where(T::class.java) +} + +/** + * Deletes all objects of the specified class from the Realm. + * + * @param T the class of the object which is to be queried for. + * @throws IllegalStateException if the corresponding Realm is closed or called from an incorrect thread. + */ +inline fun Realm.delete() { + return this.delete(T::class.java) +} + +/** + * + * Instantiates and adds a new object to the Realm. + * + * This method is only available for model classes with no `@PrimaryKey` annotation. + * If you like to create an object that has a primary key, use [createObject] instead. + * + * @param T the Class of the object to create. + * @return the new object. + * @throws RealmException if the primary key is defined in the model class or an object cannot be created. + */ +inline fun Realm.createObject(): T { + return this.createObject(T::class.java) +} + +/** + * + * Instantiates and adds a new object to the Realm with the primary key value already set. + * + * If the value violates the primary key constraint, no object will be added and a RealmException will be + * thrown. The default value for primary key provided by the model class will be ignored. + * + * @param T the Class of the object to create. + * @param primaryKeyValue value for the primary key field. + * @return the new object. + * @throws RealmException if object could not be created due to the primary key being invalid. + * @throws IllegalStateException if the model class does not have an primary key defined. + * @throws IllegalArgumentException if the `primaryKeyValue` doesn't have a value that can be converted to the + * expected value. + */ +inline fun Realm.createObject(primaryKeyValue: Any?): T { + return this.createObject(T::class.java, primaryKeyValue) +} + +/** +TODO: Figure out if we should include this is or not. Using this makes it possible to do + +inline fun Realm.callTransaction(crossinline action: Realm.() -> T): T { + val ref = AtomicReference() + executeTransaction { + ref.set(action(it)) + } + return ref.get() +} + +Missing functions. Consider these for inclusion later: +- createAllFromJson(Class clazz, InputStream inputStream) +- createAllFromJson(Class clazz, org.json.JSONArray json) +- createAllFromJson(Class clazz, String json) +- createObjectFromJson(Class clazz, InputStream inputStream) +- createObjectFromJson(Class clazz, org.json.JSONObject json) +- createObjectFromJson(Class clazz, String json) +- createOrUpdateAllFromJson(Class clazz, InputStream in) +- createOrUpdateAllFromJson(Class clazz, org.json.JSONArray json) +- createOrUpdateAllFromJson(Class clazz, String json) +- createOrUpdateObjectFromJson(Class clazz, InputStream in) +- createOrUpdateObjectFromJson(Class clazz, org.json.JSONObject json) +- createOrUpdateObjectFromJson(Class clazz, String json) +- createOrUpdateObjectFromJson(Class clazz, String json) +*/ \ No newline at end of file diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmModelExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmModelExtensions.kt new file mode 100644 index 0000000000..d668c7ab4f --- /dev/null +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmModelExtensions.kt @@ -0,0 +1,213 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.kotlin + +import io.realm.RealmChangeListener +import io.realm.RealmModel +import io.realm.RealmObject +import io.realm.RealmObjectChangeListener + +/** + * Deletes the object from the Realm it is currently associated with. + * + * After this method is called the object will be invalid and any operation (read or write) performed on it will + * fail with an `IllegalStateException`. + * + * @throws IllegalStateException if the corresponding Realm is closed or in an incorrect thread. + * @see [isValid] + */ +fun RealmModel.deleteFromRealm() { + RealmObject.deleteFromRealm(this) +} + +/** + * Checks if the RealmObject is still valid to use i.e., the RealmObject hasn't been deleted nor has the + * Realm been closed. It will always return `true` for unmanaged objects. + * + * @return `true` if the object is still accessible or an unmanaged object, `false` otherwise. + */ +fun RealmModel.isValid(): Boolean { + return RealmObject.isValid(this) +} + +/** + * Checks if this object is managed by Realm. A managed object is just a wrapper around the data in the underlying + * Realm file. On Looper threads, a managed object will be live-updated so it always points to the latest data. It + * is possible to register a change listener using [addChangeListener] to be + * notified when changes happen. Managed objects are thread confined so that they cannot be accessed from other threads + * than the one that created them. + * + * If this method returns `false`, the object is unmanaged. An unmanaged object is just a normal Kotlin object, + * so it can be passed freely across threads, but the data in the object is not connected to the underlying Realm, + * so it will not be live updated. + * + * It is possible to create a managed object from an unmanaged object by using + * [io.realm.Realm.copyToRealm]. An unmanaged object can be created from a managed object by using + * [io.realm.Realm.copyFromRealm]. + * + * @return `true` if the object is managed, `false` if it is unmanaged. + */ +fun RealmModel.isManaged(): Boolean { + return RealmObject.isManaged(this) +} + +/** + * Checks if the query used to find this RealmObject has completed. + * + * Async methods like [io.realm.RealmQuery.findFirstAsync] return an RealmObject that represents the future result + * of the RealmQuery. It can be considered similar to a [java.util.concurrent.Future] in this regard. + * + * Once `isLoaded()` returns `true`, the object represents the query result even if the query + * didn't find any object matching the query parameters. In this case the RealmObject will + * become a `null` object. + * + * "Null" objects represents `null`. An exception is thrown if any accessor is called, so it is important to also + * check isValid before calling any methods. A common pattern is: + * + * + * ```kotlin + * val person = realm.where().findFirstAsync() + * person.isLoaded() // == false + * person.addChangeListener { p -> + * p.isLoaded() // always true here + * if(p.isValid()) { + * // It is safe to access this person. + * } + * } + * ``` + * Synchronous RealmObjects are by definition blocking hence this method will always return `true` for them. + * This method will return `true` if called on an unmanaged object (created outside of Realm). + * + * @return `true` if the query has completed, `false` if the query is in + * progress. + * @see [isValid] + */ +fun RealmModel.isLoaded(): Boolean { + return RealmObject.isLoaded(this) +} + +/** + * Makes an asynchronous query blocking. This will also trigger any registered listeners. + * + * Note: This will return `true` if called for an unmanaged object (created outside of Realm). + * + * @return `true` if it successfully completed the query, `false` otherwise. + */ +fun RealmModel.load(): Boolean { + return RealmObject.load(this) +} + + +/** + * Adds a change listener to a RealmObject that will be triggered if any value field or referenced RealmObject field + * is changed, or the RealmList field itself is changed. + + * Registering a change listener will not prevent the underlying RealmObject from being garbage collected. + * If the RealmObject is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + * + * ```kotlin + * class MyActivity : Activity { + * + * private var person: Person? + * + * override fun onCreate(savedInstanceState: Bundle?) { + * super.onCreate(savedInstanceState) + * person = realm.where().findFirst() + * person?.addChangeListener(RealmChangeListener { person -> + * // React to change + * }) + * } + * } + * ``` + * + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the `object` is `null` or an unmanaged object, or the change + * listener is `null`. + * @throws IllegalStateException if you try to add a listener from a non-Looper or IntentService thread. + * @throws IllegalStateException if you try to add a listener inside a transaction. + */ +fun E.addChangeListener(listener: RealmChangeListener) { + RealmObject.addChangeListener(this, listener) +} + +/** + * Adds a change listener to a RealmObject to get detailed information about the changes. The listener will be + * triggered if any value field or referenced RealmObject field is changed, or the RealmList field itself is + * changed. + + * Registering a change listener will not prevent the underlying RealmObject from being garbage collected. + * If the RealmObject is garbage collected, the change listener will stop being triggered. To avoid this, keep a + * strong reference for as long as appropriate e.g. in a class variable. + * + * ```kotlin + * class MyActivity : Activity { + * + * private var person: Person? + * + * override fun onCreate(savedInstanceState: Bundle?) { + * super.onCreate(savedInstanceState) + * person = realm.where().findFirst() + * person?.addChangeListener(RealmObjectChangeListener { person, changeSet -> + * // React to change + * }) + * } + * } + * ``` + * + * @param listener the change listener to be notified. + * @throws IllegalArgumentException if the `object` is `null` or an unmanaged object, or the change + * listener is `null`. + * @throws IllegalStateException if you try to add a listener from a non-Looper or IntentService thread. + * @throws IllegalStateException if you try to add a listener inside a transaction. + */ +fun E.addChangeListener(listener: RealmObjectChangeListener) { + RealmObject.addChangeListener(this, listener) +} + +/** + * Removes a previously registered listener on the given RealmObject. + * + * @param listener the instance to be removed. + * @throws IllegalArgumentException if the `object` or the change listener is `null`. + * @throws IllegalArgumentException if object is an unmanaged RealmObject. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + */ +fun E.removeChangeListener(listener: RealmChangeListener) { + RealmObject.removeChangeListener(this, listener) +} + +/** + * Removes a previously registered listener on the given RealmObject. + * + * @param listener the instance to be removed. + * @throws IllegalArgumentException if the `object` or the change listener is `null`. + * @throws IllegalArgumentException if object is an unmanaged RealmObject. + * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. + */ +fun E.removeChangeListener(listener: RealmObjectChangeListener) { + RealmObject.removeChangeListener(this, listener) +} + +/** + * Removes all registered listeners from the given RealmObject. + * + * @throws IllegalArgumentException if object is `null` or isn't managed by Realm. + */ +fun RealmModel.removeAllChangeListeners() { + return RealmObject.removeAllChangeListeners(this) +} + diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt new file mode 100644 index 0000000000..e9f2c1a4a8 --- /dev/null +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.kotlin + +import io.realm.Case +import io.realm.RealmModel +import io.realm.RealmQuery +import java.util.* + + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @param casing how casing is handled. [Case.INSENSITIVE] works only for the Latin-1 characters. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a String field or `values` is `null` or + * empty. + */ +fun RealmQuery.oneOf(propertyName: String, + value: Array, + casing: Case = Case.SENSITIVE): RealmQuery { + return this.`in`(propertyName, value, casing) +} + + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Byte field or `values` is `null` or + * empty. + */ +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Short field or `values` is `null` or + * empty. + */ +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Integer field or `values` is `null` + * or empty. + */ +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Long field or `values` is `null` or + * empty. + */ +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Double field or `values` is `null` or + * empty. + */ +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Float field or `values` is `null` or + * empty. + */ +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Boolean field or `values` is `null` + * or empty. + */ +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Date field or `values` is `null` or + * empty. + */ +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} diff --git a/realm/kotlin-extensions/src/main/res/values/strings.xml b/realm/kotlin-extensions/src/main/res/values/strings.xml new file mode 100644 index 0000000000..764b07b814 --- /dev/null +++ b/realm/kotlin-extensions/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + kotlin-extensions + diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 016e5e4c19..ce4fd40b06 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -40,12 +40,12 @@ ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProperty('enableDebugCore') : false //FIXME Use 'false' as default until https://github.com/realm/realm-java/issues/5354 is fixed android { - compileSdkVersion 27 - buildToolsVersion '27.0.1' + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion defaultConfig { - minSdkVersion 9 - targetSdkVersion 27 + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.compileSdkVersion versionName version project.archivesBaseName = "realm-android-library" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -90,13 +90,14 @@ android { buildTypes { debug { - testCoverageEnabled = true + // FIXME: If enabled, crashes with https://issuetracker.google.com/issues/37116868 + testCoverageEnabled = false } } sourceSets { androidTest { - java.srcDirs += 'src/androidTest/kotlin' + java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java'] } androidTestObjectServer { java.srcDirs += 'src/syncIntegrationTest/java' @@ -168,7 +169,7 @@ project.afterEvaluate { } } -// enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-1-4-is-out/ +// enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/09/kotlin-1-1-50-is-out/ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { freeCompilerArgs = ["-Xjsr305=strict"] @@ -612,7 +613,7 @@ android.productFlavors.all { flavor -> def librarySuffix = flavor.name == 'base' ? '' : '-object-server' def userName = project.findProperty('bintrayUser') ?: 'noUser' def accessKey = project.findProperty('bintrayKey') ?: 'noKey' - + def artifactId = "realm-android-library${librarySuffix}" // BINTRAY task("bintrayAar${flavor.name.capitalize()}", type: Exec) { @@ -625,7 +626,7 @@ android.productFlavors.all { flavor -> "${buildDir}/outputs/aar/realm-android-library-${flavor.name}-release.aar", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.aar?publish=0" + "https://api.bintray.com/content/realm/maven/${artifactId}/${project.version}/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}.aar?publish=0" } task("bintraySources${flavor.name.capitalize()}", type: Exec) { @@ -638,7 +639,7 @@ android.productFlavors.all { flavor -> "${buildDir}/libs/realm-android-library-${project.version}-sources.jar", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-sources.jar?publish=0" + "https://api.bintray.com/content/realm/maven/${artifactId}/${project.version}/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}-sources.jar?publish=0" } task("bintrayJavadoc${flavor.name.capitalize()}", type: Exec) { @@ -651,7 +652,7 @@ android.productFlavors.all { flavor -> "${buildDir}/libs/realm-android-library-${project.version}-javadoc.jar", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}-javadoc.jar?publish=0" + "https://api.bintray.com/content/realm/maven/${artifactId}/${project.version}/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}-javadoc.jar?publish=0" } task("bintrayPom${flavor.name.capitalize()}", type: Exec) { @@ -664,7 +665,7 @@ android.productFlavors.all { flavor -> "${buildDir}/publications/${flavor.name}Publication/pom-default.xml", '-u', "${userName}:${accessKey}", - "https://api.bintray.com/content/realm/maven/realm-android-library${librarySuffix}/${project.version}/io/realm/realm-android-library${librarySuffix}/${project.version}/realm-android-library${librarySuffix}-${project.version}.pom?publish=0" + "https://api.bintray.com/content/realm/maven/${artifactId}/${project.version}/io/realm/${artifactId}/${project.version}/${artifactId}-${project.version}.pom?publish=0" } // OJO diff --git a/realm/realm-library/src/androidTest/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/TestHelper.java rename to realm/realm-library/src/testUtils/java/io/realm/TestHelper.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/AllTypes.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllTypesPrimaryKey.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypesPrimaryKey.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/AllTypesPrimaryKey.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/AllTypesPrimaryKey.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AnnotationIndexTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AnnotationIndexTypes.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/AnnotationIndexTypes.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/AnnotationIndexTypes.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java b/realm/realm-library/src/testUtils/java/io/realm/entities/BacklinksSource.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksSource.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/BacklinksSource.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java b/realm/realm-library/src/testUtils/java/io/realm/entities/BacklinksTarget.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/BacklinksTarget.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/BacklinksTarget.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/Cat.java b/realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/Cat.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/Dog.java b/realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/Dog.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/DogPrimaryKey.java b/realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/DogPrimaryKey.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/NullTypes.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/Owner.java b/realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/Owner.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedByte.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsBoxedByte.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedByte.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsBoxedByte.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedInteger.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsBoxedInteger.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedInteger.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsBoxedInteger.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedLong.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsBoxedLong.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedLong.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsBoxedLong.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedShort.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsBoxedShort.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsBoxedShort.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsBoxedShort.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsByte.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsByte.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsByte.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsByte.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsInteger.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsInteger.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsInteger.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsLong.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsLong.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsLong.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsLong.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsShort.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsShort.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsShort.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsShort.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsString.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsString.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsString.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsString.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyRequiredAsBoxedByte.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyRequiredAsBoxedByte.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyRequiredAsBoxedByte.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyRequiredAsBoxedByte.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyRequiredAsBoxedInteger.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyRequiredAsBoxedInteger.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyRequiredAsBoxedInteger.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyRequiredAsBoxedInteger.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyRequiredAsBoxedLong.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyRequiredAsBoxedLong.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyRequiredAsBoxedLong.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyRequiredAsBoxedLong.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyRequiredAsBoxedShort.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyRequiredAsBoxedShort.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyRequiredAsBoxedShort.java rename to realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyRequiredAsBoxedShort.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/objectid/NullPrimaryKey.java b/realm/realm-library/src/testUtils/java/io/realm/objectid/NullPrimaryKey.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/objectid/NullPrimaryKey.java rename to realm/realm-library/src/testUtils/java/io/realm/objectid/NullPrimaryKey.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java rename to realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunTestInLooperThread.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunTestInLooperThread.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/rule/RunTestInLooperThread.java rename to realm/realm-library/src/testUtils/java/io/realm/rule/RunTestInLooperThread.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunTestWithRemoteService.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/rule/RunTestWithRemoteService.java rename to realm/realm-library/src/testUtils/java/io/realm/rule/RunTestWithRemoteService.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/rule/RunWithRemoteService.java rename to realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/rule/TestRealmConfigurationFactory.java rename to realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java similarity index 100% rename from realm/realm-library/src/androidTest/java/io/realm/services/RemoteProcessService.java rename to realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java diff --git a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteTestService.java b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java similarity index 98% rename from realm/realm-library/src/androidTest/java/io/realm/services/RemoteTestService.java rename to realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java index 46eee6e089..57b0deec15 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/services/RemoteTestService.java +++ b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java @@ -29,8 +29,6 @@ import java.io.File; import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; import java.util.HashMap; import java.util.Map; diff --git a/realm/settings.gradle b/realm/settings.gradle index 4540b8669d..e986dd6c2c 100644 --- a/realm/settings.gradle +++ b/realm/settings.gradle @@ -1,3 +1,4 @@ // Realm projects -include 'realm-library' -include 'realm-annotations-processor' +include ':realm-library' +include ':realm-annotations-processor' +include ':kotlin-extensions' From 3d63e2b3b181d1dcab79e79bbbc9ff3ff70980e7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Dec 2017 10:24:17 +0100 Subject: [PATCH 1125/2110] Add explicit support for JWT tokens (#5581) --- CHANGELOG.md | 1 + .../java/io/realm/CredentialsTests.java | 21 +++++++++++++++++++ .../java/io/realm/SyncCredentials.java | 20 ++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a54642629..3737111c0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Enhancements +* [ObjectServer] Added explicit support for JSON Web Tokens (JWT) using `SyncCredentials.jwt(String token)`. It requires Object Server 2.0.23+ (#5580). * Projects using Kotlin now include additional extension functions that make working with Kotlin easier. See [docs](https://realm.io/docs/java/latest/#kotlin) for more info (#4684). * New query predicate: `sort()`. * New query predicate: `distinctValues()`. Will be renamed to `distinct` in next major version. diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java index c0a5d4fffc..41e9115fb0 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java @@ -86,6 +86,27 @@ public void google_invalidInput() { } } + @Test + public void jwt() { + SyncCredentials creds = SyncCredentials.jwt("foo"); + + assertEquals(SyncCredentials.IdentityProvider.JWT, creds.getIdentityProvider()); + assertEquals("foo", creds.getUserIdentifier()); + assertTrue(creds.getUserInfo().isEmpty()); + } + + @Test + public void jwt_invalidInput() { + String[] invalidInput = {null, ""}; + for (String input : invalidInput) { + try { + SyncCredentials.jwt(input); + fail(input + " should have failed"); + } catch (IllegalArgumentException ignored) { + } + } + } + @Test public void usernamePassword_register() { SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar", true); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 5bd7176a6d..664b39e307 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -97,6 +97,20 @@ public static SyncCredentials google(String googleToken) { return new SyncCredentials(googleToken, IdentityProvider.GOOGLE, null); } + /** + * Creates credentials based on a JSON Web Token (JWT). + * + * @param jwtToken a JWT token that identifies the user. + * @return a set of credentials that can be used to log into the Object Server using + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} or + * {@link SyncUser#loginAsync(SyncCredentials, String)}. + * @throws IllegalArgumentException if the token is either {@code null} or empty. + */ + public static SyncCredentials jwt(String jwtToken) { + assertStringNotEmpty(jwtToken, "jwtToken"); + return new SyncCredentials(jwtToken, IdentityProvider.JWT, null); + } + /** * Creates credentials based on a login with username and password. These credentials will only be verified * by the Object Server. @@ -263,6 +277,12 @@ public static final class IdentityProvider { */ public static final String GOOGLE = "google"; + /** + * Credentials are given in the form of a standard JSON Web Token that will be verified + * by the Realm Object Server. + */ + public static final String JWT = "jwt"; + /** * Credentials will be verified by the Object Server. * From d0e626121cc5583aac8fc9c8b53df5f685b1e3f7 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 5 Dec 2017 17:24:46 +0800 Subject: [PATCH 1126/2110] Update sync to 2.1.8 (#5582) --- CHANGELOG.md | 2 ++ dependencies.list | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4181eaa0c0..452906071a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ * Use `OsList` instead of `OsResults` to add notification token on for `RealmList`. * Updated Gralde and plugins to support Android Studio `3.0.0` (#5472). +* Upgraded to Realm Sync 2.1.8. +* Upgraded to Realm Core 4.0.4. ### Credits diff --git a/dependencies.list b/dependencies.list index 35394be8e1..3703b72124 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,9 +1,9 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.1.4 -REALM_SYNC_SHA256=6d32ef44acbf4a63b654ceeaadce036feeefd04a4ca649a95a22a0e7d56df84d +REALM_SYNC_VERSION=2.1.8 +REALM_SYNC_SHA256=14e4aabe270638aa96f84396be27985b6809e532183035c5150dd2933d676248 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.0.18 +REALM_OBJECT_SERVER_DE_VERSION=2.1.0 From 3e64fa8b3abec7d49e1ae80ad573f6c764bf725b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 5 Dec 2017 18:43:40 +0800 Subject: [PATCH 1127/2110] Listener not called on RealmList (#5574) --- CHANGELOG.md | 1 + .../OrderedCollectionChangeSetTests.java | 94 ++++++++++++++++++- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 452906071a..16e4c82aa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Added missing `toString()` for the implementation of `OrderedCollectionChangeSet`. * Sync queries are evaluated immediately to solve the performance issue when the query results are huge, `RealmResults.size()` takes too long time (#5387). * Correctly close the Realm instance if an exception was thrown while opening it. This avoids `IllegalStateException` when deleting the Realm in the catch block (#5570). +* Fixed the listener on `RealmList` not being called when removing the listener then adding it again (#5507). Please notice that a similar issue still exists for `RealmResults`. ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java index 1c3788f092..8196b2f0d7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -194,11 +194,25 @@ private void moveObjects(Realm realm, int originAge, int newAge) { } } - private void registerCheckListener(Realm realm, final ChangesCheck changesCheck) { + private OrderedRealmCollection getTestingCollection(Realm realm) { switch (type) { case REALM_RESULTS: RealmResults results = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE); looperThread.keepStrongReference(results); + return results; + case REALM_LIST: + RealmList list = realm.where(Owner.class).findFirst().getDogs(); + looperThread.keepStrongReference(list); + return list; + } + fail(); + return null; + } + + private void registerCheckListener(Realm realm, final ChangesCheck changesCheck) { + switch (type) { + case REALM_RESULTS: + RealmResults results = (RealmResults) getTestingCollection(realm); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmResults collection, @Nullable OrderedCollectionChangeSet changeSet) { @@ -207,7 +221,7 @@ public void onChange(RealmResults collection, @Nullable OrderedCollectionCh }); break; case REALM_LIST: - RealmList list = realm.where(Owner.class).findFirst().getDogs(); + RealmList list = (RealmList) getTestingCollection(realm); looperThread.keepStrongReference(list); list.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override @@ -465,4 +479,80 @@ public void onChange(RealmResults collection, @Nullable OrderedCollectionCh } }); } + + // To reproduce https://github.com/realm/realm-java/issues/5507 + // 1. Add listener to a collection + // A. change the collection in a background thread + // 2. Remove the listener + // 3. Add another listener + // 4. the listener added in step 3 should be triggered with change set in step A + @Test + @RunTestInLooperThread + public void addChangeListener_bug5507() throws InterruptedException { + // FIXME: See https://github.com/realm/realm-object-store/issues/605 + if (type == ObservablesType.REALM_RESULTS) { + looperThread.testComplete(); + return; + } + + Realm realm = looperThread.getRealm(); + populateData(realm, 1); + + OrderedRealmCollectionChangeListener> listener1 = + new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(OrderedRealmCollection dogs, @Nullable OrderedCollectionChangeSet changeSet) { + fail(); + } + }; + + OrderedRealmCollection dogs = getTestingCollection(realm); + assertEquals(1, dogs.size()); + + if (type == ObservablesType.REALM_LIST) { + //noinspection unchecked + ((RealmList) dogs).addChangeListener(listener1); + } else { + //noinspection unchecked + ((RealmResults) dogs).addChangeListener(listener1); + } + + Thread bgThread = new Thread(new Runnable() { + @Override + public void run() { + Realm realm = Realm.getInstance(looperThread.getConfiguration()); + realm.beginTransaction(); + createObjects(realm, 2); + realm.commitTransaction(); + realm.close(); + } + }); + bgThread.start(); + bgThread.join(); + + if (type == ObservablesType.REALM_LIST) { + //noinspection unchecked + ((RealmList) dogs).removeChangeListener(listener1); + } else { + //noinspection unchecked + ((RealmResults) dogs).removeChangeListener(listener1); + } + + OrderedRealmCollectionChangeListener> listener2 = + new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(OrderedRealmCollection dogs, @Nullable OrderedCollectionChangeSet changeSet) { + assertEquals(2, dogs.size()); + looperThread.testComplete(); + } + }; + + if (type == ObservablesType.REALM_LIST) { + //noinspection unchecked + ((RealmList) dogs).addChangeListener(listener2); + } else { + //noinspection unchecked + ((RealmResults) dogs).addChangeListener(listener2); + } + } } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 3eb19c014f..2b7db38bd1 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 3eb19c014fdfa0f02a03d4acf71d046d29a6dfa6 +Subproject commit 2b7db38bd112c82c55a0fa4bbecd24f652d45ba1 From 57af0554f31a022762941b2538c1de3859135116 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Dec 2017 12:59:58 +0100 Subject: [PATCH 1128/2110] Fixed changelog for 4.3 --- CHANGELOG.md | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68770d38a8..978cdf50a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.3.0 (YYYY-MM-DD) +## 4.3.0 (2017-12-05) ### Deprecated @@ -16,19 +16,6 @@ ### Bug Fixes -### Internal - -### Credits - -* Thanks to @madisp for adding better support for incremental compilers (#5567). - - -## 4.2.1 (YYYY-MM-DD) - -### Enhancements - -### Bug Fixes - * Added missing `toString()` for the implementation of `OrderedCollectionChangeSet`. * Sync queries are evaluated immediately to solve the performance issue when the query results are huge, `RealmResults.size()` takes too long time (#5387). * Correctly close the Realm instance if an exception was thrown while opening it. This avoids `IllegalStateException` when deleting the Realm in the catch block (#5570). @@ -37,7 +24,7 @@ ### Internal * Use `OsList` instead of `OsResults` to add notification token on for `RealmList`. -* Updated Gralde and plugins to support Android Studio `3.0.0` (#5472). +* Updated Gradle and plugins to support Android Studio `3.0.0` (#5472). * Upgraded to Realm Sync 2.1.8. * Upgraded to Realm Core 4.0.4. @@ -45,6 +32,7 @@ * Thanks to @tbsandee for fixing a typo (#5548). * Thanks to @vivekkiran for updating Gradle and plugins to support Android Studio `3.0.0` (#5472). +* Thanks to @madisp for adding better support for incremental compilers (#5567). ## 4.2.0 (2017-11-17) From 2d48f8d7e3949ccc2c743827b9701b286a7842b4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Dec 2017 14:39:40 +0100 Subject: [PATCH 1129/2110] Fixed bad Javadoc --- .../src/objectServer/java/io/realm/SyncCredentials.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 664b39e307..0ff32a6bfe 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -102,8 +102,7 @@ public static SyncCredentials google(String googleToken) { * * @param jwtToken a JWT token that identifies the user. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} or - * {@link SyncUser#loginAsync(SyncCredentials, String)}. + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if the token is either {@code null} or empty. */ public static SyncCredentials jwt(String jwtToken) { From cb7cb2a24215fe2b4020f5400da38b6195337351 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Dec 2017 14:39:53 +0100 Subject: [PATCH 1130/2110] Release v4.3.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 9aadf8cf65..8191138914 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.0-SNAPSHOT +4.3.0 \ No newline at end of file From 464f2e5aaef3cce7431d92f272f004924a8cdf0f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Dec 2017 14:39:53 +0100 Subject: [PATCH 1131/2110] Prepare next release v4.3.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 8191138914..b5d898602c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.0 \ No newline at end of file +4.3.1-SNAPSHOT \ No newline at end of file From 892b4fef379487f668906edbc5c6638b38c52eb0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Dec 2017 14:45:12 +0100 Subject: [PATCH 1132/2110] Prepare next dev iteration --- CHANGELOG.md | 13 +++++++++++++ version.txt | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 978cdf50a8..f4a4924941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 4.4.0 (YYYY-MM-DD) + +### Deprecated + +### Enhancements + +### Bug Fixes + +### Internal + +### Credits + + ## 4.3.0 (2017-12-05) ### Deprecated diff --git a/version.txt b/version.txt index b5d898602c..84980dc3fe 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.1-SNAPSHOT \ No newline at end of file +4.4.0-SNAPSHOT \ No newline at end of file From daa7100ac9f32d1dab239921cbb6d7b9a353ce59 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Dec 2017 20:18:17 +0100 Subject: [PATCH 1133/2110] Fixes Kotlin standard library always being added. --- CHANGELOG.md | 7 +++++++ .../groovy/io/realm/gradle/RealmPluginExtension.groovy | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 978cdf50a8..484f6c233e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.3.1 (YYYY-MM-DD) + +### Bug Fixes + +* Fixed kotlin standard library being added to both Java and Kotlin projects (#5587). + + ## 4.3.0 (2017-12-05) ### Deprecated diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy index bd1ea27e38..12b51751ad 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy @@ -59,7 +59,10 @@ class RealmPluginExtension { // then add again def syncArtifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" project.dependencies.add(dependencyConfigurationName, "io.realm:${syncArtifactName}:${Version.VERSION}") - def kotlinExtArtifactName = "realm-android-kotlin-extensions${kotlinExtensionsEnabled ? '-object-server' : ''}" - project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") + + if (kotlinExtensionsEnabled) { + def kotlinExtArtifactName = "realm-android-kotlin-extensions${syncEnabled ? '-object-server' : ''}" + project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") + } } } From 802adeacd3c02aa5036b30c1009a67dbe615365d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Dec 2017 20:23:02 +0100 Subject: [PATCH 1134/2110] Revert "Fixes Kotlin standard library always being added." This reverts commit daa7100ac9f32d1dab239921cbb6d7b9a353ce59. --- CHANGELOG.md | 7 ------- .../groovy/io/realm/gradle/RealmPluginExtension.groovy | 7 ++----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 484f6c233e..978cdf50a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,3 @@ -## 4.3.1 (YYYY-MM-DD) - -### Bug Fixes - -* Fixed kotlin standard library being added to both Java and Kotlin projects (#5587). - - ## 4.3.0 (2017-12-05) ### Deprecated diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy index 12b51751ad..bd1ea27e38 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy @@ -59,10 +59,7 @@ class RealmPluginExtension { // then add again def syncArtifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" project.dependencies.add(dependencyConfigurationName, "io.realm:${syncArtifactName}:${Version.VERSION}") - - if (kotlinExtensionsEnabled) { - def kotlinExtArtifactName = "realm-android-kotlin-extensions${syncEnabled ? '-object-server' : ''}" - project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") - } + def kotlinExtArtifactName = "realm-android-kotlin-extensions${kotlinExtensionsEnabled ? '-object-server' : ''}" + project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") } } From 679795ea2c07ca06682eda7a821279464889174a Mon Sep 17 00:00:00 2001 From: Maelig Date: Wed, 6 Dec 2017 11:16:47 +0100 Subject: [PATCH 1135/2110] Add @deprecated javadoc in RealmQuery.java (#5589) --- CHANGELOG.md | 13 ++++++++++ .../src/main/java/io/realm/RealmQuery.java | 26 +++++++++++++++++-- version.txt | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 978cdf50a8..f4a4924941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 4.4.0 (YYYY-MM-DD) + +### Deprecated + +### Enhancements + +### Bug Fixes + +### Internal + +### Credits + + ## 4.3.0 (2017-12-05) ### Deprecated diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 1662b3de8f..7cb8bb2634 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1528,7 +1528,7 @@ private RealmQuery orWithoutThreadValidation() { this.query.or(); return this; } - + /** * Logical-and two conditions * Realm automatically applies logical-and between all query statements, so this is intended only as a mean to increase readability. @@ -1537,7 +1537,7 @@ private RealmQuery orWithoutThreadValidation() { */ public RealmQuery and() { realm.checkIfValid(); - + return this; } @@ -1588,6 +1588,8 @@ public RealmQuery isNotEmpty(String fieldName) { } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#distinctValues(String)} then {@link RealmQuery#findAll()} + * * Returns a distinct set of objects of a specific class. If the result is sorted, the first * object will be returned in case of multiple occurrences, otherwise it is undefined which * object is returned. @@ -1608,6 +1610,8 @@ public RealmResults distinct(String fieldName) { } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#distinctValues(String)} then {@link RealmQuery#findAllAsync()} + * * Asynchronously returns a distinct set of objects of a specific class. If the result is * sorted, the first object will be returned in case of multiple occurrences, otherwise it is * undefined which object is returned. @@ -1630,6 +1634,8 @@ public RealmResults distinctAsync(String fieldName) { } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#distinctValues(String, String[])} then {@link RealmQuery#findAll()} + * * Returns a distinct set of objects from a specific class. When multiple distinct fields are * given, all unique combinations of values in the fields will be returned. In case of multiple * matches, it is undefined which object is returned. Unless the result is sorted, then the @@ -1835,6 +1841,8 @@ public RealmResults findAllAsync() { } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String, Sort)} then {@link RealmQuery#findAll()} + * * Finds all objects that fulfill the query conditions and sorted by specific field name. *

            * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', @@ -1856,6 +1864,8 @@ public RealmResults findAllSorted(String fieldName, Sort sortOrder) { } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String, Sort)} then {@link RealmQuery#findAllAsync()} + * * Similar to {@link #findAllSorted(String, Sort)} but runs asynchronously on a worker thread * (need a Realm opened from a looper thread to work). * @@ -1994,6 +2004,8 @@ public RealmQuery distinctValues(String firstFieldName, String... remainingFi } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String)} then {@link RealmQuery#findAll()} + * * Finds all objects that fulfill the query conditions and sorted by specific field name in ascending order. *

            * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', @@ -2011,6 +2023,8 @@ public RealmResults findAllSorted(String fieldName) { } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String)} then {@link RealmQuery#findAllAsync()} + * * Similar to {@link #findAllSorted(String)} but runs asynchronously on a worker thread. * This method is only available from a Looper thread. * @@ -2025,6 +2039,8 @@ public RealmResults findAllSortedAsync(String fieldName) { } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String[], Sort[])} then {@link RealmQuery#findAll()} + * * Finds all objects that fulfill the query conditions and sorted by specific field names. *

            * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', @@ -2050,6 +2066,8 @@ private boolean isDynamicQuery() { } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String[], Sort[])} then {@link RealmQuery#findAllAsync()} + * * Similar to {@link #findAllSorted(String[], Sort[])} but runs asynchronously. * from a worker thread. * This method is only available from a Looper thread. @@ -2070,6 +2088,8 @@ public RealmResults findAllSortedAsync(String[] fieldNames, final Sort[] sort } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String, Sort, String, Sort)} then {@link RealmQuery#findAll()} + * * Finds all objects that fulfill the query conditions and sorted by specific field names in ascending order. *

            * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', @@ -2091,6 +2111,8 @@ public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, } /** + * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String, Sort, String, Sort)} then {@link RealmQuery#findAllAsync()} + * * Similar to {@link #findAllSorted(String, Sort, String, Sort)} but runs asynchronously on a worker thread * This method is only available from a Looper thread. * diff --git a/version.txt b/version.txt index b5d898602c..84980dc3fe 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.1-SNAPSHOT \ No newline at end of file +4.4.0-SNAPSHOT \ No newline at end of file From 39da417b63131e71e1d6ba545ef2fbc9dee5abd0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Dec 2017 11:17:55 +0100 Subject: [PATCH 1136/2110] Revert accidental changes to CHANGELOG and version.txt --- CHANGELOG.md | 13 ------------- version.txt | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4a4924941..978cdf50a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,3 @@ -## 4.4.0 (YYYY-MM-DD) - -### Deprecated - -### Enhancements - -### Bug Fixes - -### Internal - -### Credits - - ## 4.3.0 (2017-12-05) ### Deprecated diff --git a/version.txt b/version.txt index 84980dc3fe..b5d898602c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.4.0-SNAPSHOT \ No newline at end of file +4.3.1-SNAPSHOT \ No newline at end of file From 01ecad66f337027a2f70905199bddace59c7ef1a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Dec 2017 11:18:19 +0100 Subject: [PATCH 1137/2110] Fixes Kotlin extension functions always being added to both Java and Kotlin projects. (#5592) --- CHANGELOG.md | 7 +++++++ .../groovy/io/realm/gradle/RealmPluginExtension.groovy | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 978cdf50a8..484f6c233e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.3.1 (YYYY-MM-DD) + +### Bug Fixes + +* Fixed kotlin standard library being added to both Java and Kotlin projects (#5587). + + ## 4.3.0 (2017-12-05) ### Deprecated diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy index bd1ea27e38..12b51751ad 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy @@ -59,7 +59,10 @@ class RealmPluginExtension { // then add again def syncArtifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" project.dependencies.add(dependencyConfigurationName, "io.realm:${syncArtifactName}:${Version.VERSION}") - def kotlinExtArtifactName = "realm-android-kotlin-extensions${kotlinExtensionsEnabled ? '-object-server' : ''}" - project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") + + if (kotlinExtensionsEnabled) { + def kotlinExtArtifactName = "realm-android-kotlin-extensions${syncEnabled ? '-object-server' : ''}" + project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") + } } } From 9633a5c99f2aa3c52373e55562d79c3c4b9a11ca Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Dec 2017 11:30:46 +0100 Subject: [PATCH 1138/2110] Updated release data for 4.3.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 484f6c233e..726e42690c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.3.1 (YYYY-MM-DD) +## 4.3.1 (2017-12-06) ### Bug Fixes From efbe783561671fbeba3e7ac0dde167c25b2027e4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Dec 2017 11:31:16 +0100 Subject: [PATCH 1139/2110] Release v4.3.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b5d898602c..ecedc98d1d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.1-SNAPSHOT \ No newline at end of file +4.3.1 \ No newline at end of file From 5e3ecea65467f76b38bb7e9d65767746481da06c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Dec 2017 11:31:17 +0100 Subject: [PATCH 1140/2110] Prepare next release v4.3.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index ecedc98d1d..b2595557b0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.1 \ No newline at end of file +4.3.2-SNAPSHOT \ No newline at end of file From 45b8f40855544e4a4072d9f7bfefd0a5eb186e96 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Sun, 10 Dec 2017 21:08:16 +0800 Subject: [PATCH 1141/2110] Fix getLocalInstanceCount doc (#5599) --- realm/realm-library/src/main/java/io/realm/Realm.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 57940dd4bb..194540d5f6 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1791,7 +1791,7 @@ public void onResult(int count) { * dynamic and normal Realms. * * @param configuration the {@link io.realm.RealmConfiguration} for the Realm. - * @return number of open Realm instances across all threads. + * @return number of open Realm instances on the caller thread. */ public static int getLocalInstanceCount(RealmConfiguration configuration) { return RealmCache.getLocalThreadCount(configuration); From 3ec45df9faf6f1727773de68bcad6fabb3a237d6 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 12 Dec 2017 12:38:29 +0800 Subject: [PATCH 1142/2110] Better exception message for addField() (#5610) * Better exception message for addField() Close #3388 --- CHANGELOG.md | 7 +++++++ .../java/io/realm/RealmObjectSchemaTests.java | 14 ++++++++++++++ .../java/io/realm/MutableRealmObjectSchema.java | 4 ++++ .../src/main/java/io/realm/RealmObjectSchema.java | 2 +- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 726e42690c..8754e08d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.3.2 (YYYY-MM-DD) + +### Bug Fixes + +* Throws a better exception message when calling `RealmObjectSchema.addField()` with a `RealmModel` class (#3388). + + ## 4.3.1 (2017-12-06) ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 039e411a32..74c3b6e0cd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -356,6 +356,20 @@ public void run(String fieldName) { } } + @Test + public void addField_realmModelThrows() { + if (type == ObjectSchemaType.IMMUTABLE) { + return; + } + try { + schema.addField("test", Dog.class); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), CoreMatchers.containsString( + "Use 'addRealmObjectField()' instead to add fields that link to other RealmObjects:")); + } + } + private void checkAddFieldTwice(String fieldName, FieldRunnable runnable) { runnable.run(fieldName); try { diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index 51e269bb93..8015c22c75 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -87,6 +87,10 @@ public RealmObjectSchema addField(String fieldName, Class fieldType, FieldAtt if (metadata == null) { if (SUPPORTED_LINKED_FIELDS.containsKey(fieldType)) { throw new IllegalArgumentException("Use addRealmObjectField() instead to add fields that link to other RealmObjects: " + fieldName); + } else if (RealmModel.class.isAssignableFrom(fieldType)) { + throw new IllegalArgumentException(String.format(Locale.US, + "Use 'addRealmObjectField()' instead to add fields that link to other RealmObjects: %s(%s)", + fieldName, fieldType)); } else { throw new IllegalArgumentException(String.format(Locale.US, "Realm doesn't support this field type: %s(%s)", diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 3442f1074e..338e194e51 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -136,8 +136,8 @@ public String getClassName() { * @param attributes set of attributes for this field. * @return the updated schema. * @throws IllegalArgumentException if the type isn't supported, field name is illegal or a field with that name - * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable. * already exists. + * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable. */ public abstract RealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes); From 7601d713b5c5f3fee22cad2112819775d6d25a6b Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 12 Dec 2017 13:56:03 +0800 Subject: [PATCH 1143/2110] Fix CI failure caused by ROS docker Health checking without waiting is too frequent. Use 0.5 second as the interval between every attempt. NOTE: CI failure caused by this usually cannot be seen the exact test case in the gradle log. But it will give you something like: "Tests on Nexus 5X - 6.0 failed: Test run failed to complete. Expected 3762 tests, received 3" --- tools/sync_test_server/ros-testing-server.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index e183378084..bdf1938075 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -51,7 +51,9 @@ function waitForRosToInitialize(attempts, onSuccess, onError, startSequence) { http.get("http://0.0.0.0:9080/health", function(res) { if (res.statusCode != 200) { winston.info("ROS /health/ returned: " + res.statusCode) - waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence) + setTimeout(function() { + waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); + }, 500); } else { onSuccess(startSequence); } @@ -61,7 +63,7 @@ function waitForRosToInitialize(attempts, onSuccess, onError, startSequence) { // Wait a little before trying again (common startup is ~1 second). setTimeout(function() { waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); - }, 200); + }, 500); }); } @@ -112,7 +114,8 @@ function doStartRealmObjectServer(onSuccess, onError) { winston.info(`${data}`); }); - waitForRosToInitialize(100, onSuccess, onError, Date.now()); + // The interval between every health check is 0.5 second. Give the ROS 15 seconds to get fully initialized. + waitForRosToInitialize(30, onSuccess, onError, Date.now()); } }); } From 69e0b05cc1b35395232a2cb0b6b1cf0d6fea9339 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Tue, 12 Dec 2017 12:39:56 +0800 Subject: [PATCH 1144/2110] Use https for RealmVersionChecker Close #4043 --- CHANGELOG.md | 1 + .../src/main/java/io/realm/processor/RealmVersionChecker.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8754e08d95..df4a90869d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug Fixes * Throws a better exception message when calling `RealmObjectSchema.addField()` with a `RealmModel` class (#3388). +* Use https for Realm version checker (#4043). ## 4.3.1 (2017-12-06) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java index b0f37fa741..f59358c298 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java @@ -27,9 +27,9 @@ public class RealmVersionChecker { - public static final String REALM_ANDROID_DOWNLOAD_URL = "http://static.realm.io/downloads/java/latest"; + public static final String REALM_ANDROID_DOWNLOAD_URL = "https://static.realm.io/downloads/java/latest"; - private static final String VERSION_URL = "http://static.realm.io/update/java?"; + private static final String VERSION_URL = "https://static.realm.io/update/java?"; private static final String REALM_VERSION = Version.VERSION; private static final String REALM_VERSION_PATTERN = "\\d+\\.\\d+\\.\\d+"; private static final int READ_TIMEOUT = 2000; From 35b57da8c656ceac849b6be87586ca8dbab69758 Mon Sep 17 00:00:00 2001 From: Vivek Kiran Date: Mon, 8 Jan 2018 18:41:20 +0530 Subject: [PATCH 1145/2110] Update Gradle Wrapper to 4.4.1 and Update to Kotlin 1.2 (#5642) * Update Gradle Wrapper and Update to Kotlin 1.2 * Update to Gradle 4.4.1 * Typo fixes * kotlin-stdlib-jre7 -> kotlin-stdlib-jdk7 * Remove Unnecessary Semicolons ; --- README.md | 58 +++++++++--------- build.gradle | 2 +- examples/build.gradle | 4 +- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54732 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/kotlinExample/build.gradle | 4 +- examples/objectServerExample/build.gradle | 14 ++--- gradle-plugin/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54731 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../realm/gradle/RealmPluginExtension.groovy | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 54731 -> 54333 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- library-benchmarks/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54732 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-annotations/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54731 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-transformer/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54731 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../realm/transformer/RealmTransformer.groovy | 10 +-- .../transformer/BytecodeModifierTest.groovy | 8 +-- realm.properties | 2 +- realm/build.gradle | 6 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 54732 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/kotlin-extensions/build.gradle | 2 +- realm/realm-library/build.gradle | 6 +- 30 files changed, 71 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index d7b70b59ba..eb5523d646 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ This repository holds the source code for the Java version of Realm, which curre ## Features -* **Mobile-first:** Realm is the first database built from the ground up to run directly inside phones, tablets and wearables. +* **Mobile-first:** Realm is the first database built from the ground up to run directly inside phones, tablets, and wearables. * **Simple:** Data is directly exposed as objects and queryable by code, removing the need for ORM's riddled with performance & maintenance issues. Plus, we've worked hard to [keep our API down to very few classes](https://realm.io/docs/java/): most of our users pick it up intuitively, getting simple apps up & running in minutes. * **Modern:** Realm supports easy thread-safety, relationships & encryption. -* **Fast:** Realm is faster than even raw SQLite on common operations, while maintaining an extremely rich feature set. +* **Fast:** Realm is faster than even raw SQLite on common operations while maintaining an extremely rich feature set. ## Getting Started @@ -74,7 +74,7 @@ In case you don't want to use the precompiled version, you can build Realm yours * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library/Android`. The download will unzip as the directory `android-ndk-r10e`. - * If you will be building with Android Studio, you will need to tell it to use the correct NDK. To do this, define the variable `ndk.dir` in `realm/local.properties` and assign it the full path name of the directory that you unzipped above. Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited. + * If you will be building with Android Studio, you will need to tell it to use the correct NDK. To do this, define the variable `ndk.dir` in `realm/local.properties` and assign it the full pathname of the directory that you unzipped above. Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited. ``` ndk.dir=/Users/brian/Library/Android/android-ndk-r10e/r10e @@ -88,7 +88,7 @@ You may unzip the file wherever you choose. For macOS, a suggested location is export ANDROID_NDK_HOME=~/Library/Android/android-ndk-r10e ``` - * If you will be launching Android Studio from the macOS Finder, you should also run the following two commands: + * If you are launching Android Studio from the macOS Finder, you should also run the following two commands: ``` launchctl setenv ANDROID_HOME "$ANDROID_HOME" @@ -101,7 +101,7 @@ You may unzip the file wherever you choose. For macOS, a suggested location is export REALM_CORE_DOWNLOAD_DIR=~/.realmCore ``` - macOS users must also run the following command in order for Android Studio to see this environment variable.. + macOS users must also run the following command for Android Studio to see this environment variable. ``` launchctl setenv REALM_CORE_DOWNLOAD_DIR "$REALM_CORE_DOWNLOAD_DIR" @@ -109,10 +109,10 @@ You may unzip the file wherever you choose. For macOS, a suggested location is It would be a good idea to add all of the symbol definitions (and their accompanying `launchctl` commands, if you are using macOS) to your `~/.profile` (or `~/.zprofile` if the login shell is `zsh`) - * If you develop Realm Java with Android Studio, we recommend you to exclude some directories from indexing target by executing following steps on Android Studio. It really speeds up indexing phase after build. + * If you develop Realm Java with Android Studio, we recommend you to exclude some directories from indexing target by executing following steps on Android Studio. It really speeds up indexing phase after the build. - Under `/realm/realm-library/`, select `build`, `.externalNativeBuild` and `distribution` folders in `Project` view. - - Press `Command + Shift + A` to open `Find action` dialog. If you are not using defaut keymap nor using macOS, you can find your shortcut key in `Keymap` preference by searching `Find action`. + - Press `Command + Shift + A` to open `Find action` dialog. If you are not using default keymap nor using macOS, you can find your shortcut key in `Keymap` preference by searching `Find action`. - Search `Excluded` (not `Exclude`) action and select it. Selected folder icons should become orange (in default theme). - Restart Android Studio. @@ -132,7 +132,7 @@ git clone https://github.com/realm/realm-java.git --recursive ### Build -Once you have completed all the pre-requisites building Realm is done with a simple command +Once you have completed all the pre-requisites building Realm is done with a simple command. ``` ./gradlew assemble @@ -161,7 +161,7 @@ Generating the Javadoc using the command above may generate warnings. The Javado ### Upgrading Gradle Wrappers - All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer to `gradleVersion` property defined in `/realm.properties` in order to determine Gradle Version of generating wrappers. + All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer to `gradleVersion` property defined in `/realm.properties` to determine Gradle Version of generating wrappers. We have a script `./tools/update_gradle_wrapper.sh` to automate these steps. When you update Gradle Wrappers, please obey the following steps. 1. Edit `gradleVersion` property in defined in `/realm.properties` to new Gradle Wrapper version. @@ -169,7 +169,7 @@ We have a script `./tools/update_gradle_wrapper.sh` to automate these steps. Whe ### Gotchas -The repository is organized in six Gradle projects: +The repository is organized into six Gradle projects: * `realm`: it contains the actual library (including the JNI layer) and the annotations processor. * `realm-annotations`: it contains the annotations defined by Realm. @@ -188,28 +188,28 @@ that you can run `./gradlew :realm:realm-library:compileBaseDebugAndroidTestSour ## Examples -The `./examples` folder contain a number of example projects showing how Realm can be used. If this is the first time you checkout or pull a new version of this repository to try the examples, you must call `./gradlew installRealmJava` from the top-level directory first. Otherwise the examples will not compile as they depend on all Realm artifacts being installed in `mavenLocal()`. +The `./examples` folder contains many example projects showing how Realm can be used. If this is the first time you checkout or pull a new version of this repository to try the examples, you must call `./gradlew installRealmJava` from the top-level directory first. Otherwise, the examples will not compile as they depend on all Realm artifacts being installed in `mavenLocal()`. Standalone examples can be [downloaded from website](https://realm.io/docs/java/latest/#getting-started). ## Running Tests on a Device -To run these tests you must have a device connected to the build computer and the `adb` command must be in your `PATH` +To run these tests, you must have a device connected to the build computer, and the `adb` command must be in your `PATH` -1. Connect an Android device and verify that that the command `adb devices` shows a connected device: +1. Connect an Android device and verify that the command `adb devices` shows a connected device: + + ```sh + adb devices + List of devices attached + 004c03eb5615429f device + ``` - ```sh - adb devices - List of devices attached - 004c03eb5615429f device - ``` - 2. Run instrumentation tests: - ```sh - cd realm - ./gradlew connectedBaseDebugAndroidTest - ``` + ```sh + cd realm + ./gradlew connectedBaseDebugAndroidTest + ``` These tests may take as much as half an hour to complete. @@ -234,12 +234,12 @@ To run a testing server locally: 3. Run instrumentation tests - In a new terminal window, run: + In a new terminal window, run: - ```sh - cd realm - ./gradlew connectedObjectServerDebugAndroidTest - ``` + ```sh + cd realm + ./gradlew connectedObjectServerDebugAndroidTest + ``` Note that if using VirtualBox (Genymotion), the network needs to be bridged for the tests to work. This is done in `VirtualBox > Network`. Set "Adapter 2" to "Bridged Adapter". @@ -272,7 +272,7 @@ not eligible to receive the product under U.S. law.** ## Feedback -**_If you use Realm and are happy with it, all we ask is that you please consider sending out a tweet mentioning [@realm](http://twitter.com/realm) to share your thoughts!_** +**_If you use Realm and are happy with it, all we ask is that you, please consider sending out a tweet mentioning [@realm](http://twitter.com/realm) to share your thoughts!_** **_And if you don't like it, please let us know what you would like improved, so we can fix it!_** diff --git a/build.gradle b/build.gradle index 3ffcf0360b..12370c0abf 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { apply plugin: 'ch.netzwerg.release' -def currentVersion = file("${projectDir}/version.txt").text.trim(); +def currentVersion = file("${projectDir}/version.txt").text.trim() def props = new Properties() props.load(new FileInputStream("${rootDir}/realm.properties")) diff --git a/examples/build.gradle b/examples/build.gradle index 317a72b51c..a801b01386 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -10,7 +10,7 @@ configurations.all { static String getAppId (path) { String build = new File(path).text def matcher = build =~ 'applicationId.*' - def appId = matcher.size() > 0 ? matcher[0].trim() - 'applicationId' - ~/\s/ : ''; + def appId = matcher.size() > 0 ? matcher[0].trim() - 'applicationId' - ~/\s/ : '' String myappId = appId.replaceAll('"', '') myappId = myappId.replaceAll('\'', '') return myappId @@ -33,7 +33,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.1.0-alpha06' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index 0bdf3fe94139883078c58008a6b84cd063bfaf64..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 16129 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 diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 702c4b68b8..57c7d2d22b 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index df885d428c..9b887acf98 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.51' + ext.kotlin_version = '1.2.10' repositories { jcenter() mavenCentral() @@ -59,6 +59,6 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:${kotlin_version}" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlin_version}" implementation 'org.jetbrains.anko:anko-sdk15:0.9.1' } diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 5044b5d4eb..9f7afb9744 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -3,22 +3,22 @@ apply plugin: 'realm-android' // Credit: http://jeremie-martinez.com/2015/05/05/inject-host-gradle/ def getIP() { - InetAddress result = null; - Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + InetAddress result = null + Enumeration interfaces = NetworkInterface.getNetworkInterfaces() while (interfaces.hasMoreElements()) { - Enumeration addresses = interfaces.nextElement().getInetAddresses(); + Enumeration addresses = interfaces.nextElement().getInetAddresses() while (addresses.hasMoreElements()) { - InetAddress address = addresses.nextElement(); + InetAddress address = addresses.nextElement() if (!address.isLoopbackAddress()) { if (address.isSiteLocalAddress()) { - return address.getHostAddress(); + return address.getHostAddress() } else if (result == null) { - result = address; + result = address } } } } - return (result != null ? result : InetAddress.getLocalHost()).getHostAddress(); + return (result != null ? result : InetAddress.getLocalHost()).getHostAddress() } android { diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 6524932ee5..cac9adc22b 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -30,7 +30,7 @@ sourceCompatibility = 1.8 targetCompatibility = 1.8 group = 'io.realm' -version = file("${projectDir}/../version.txt").text.trim(); +version = file("${projectDir}/../version.txt").text.trim() configurations { provided diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 6b6ea3ab4ff4f69d55c5fd9c0a6ac70f47d41008..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 15797 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 U=U+A*2Z1>&bWr1i|GE4B04mXu<^TWy delta 16241 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 z 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 diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 702c4b68b8..57c7d2d22b 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy index 12b51751ad..fc343412dd 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy @@ -32,7 +32,7 @@ class RealmPluginExtension { } void setSyncEnabled(value) { - this.syncEnabled = value; + this.syncEnabled = value setDependencies(syncEnabled, kotlinExtensionsEnabled) } @@ -43,7 +43,7 @@ class RealmPluginExtension { void setDependencies(boolean syncEnabled, boolean kotlinExtensionsEnabled) { // remove libraries first - def iterator = project.getConfigurations().getByName(dependencyConfigurationName).getDependencies().iterator(); + def iterator = project.getConfigurations().getByName(dependencyConfigurationName).getDependencies().iterator() while (iterator.hasNext()) { def item = iterator.next() if (item.group == 'io.realm') { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 6b6ea3ab4ff4f69d55c5fd9c0a6ac70f47d41008..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 15797 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 U=U+A*2Z1>&bWr1i|GE4B04mXu<^TWy delta 16241 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 z zH}ksPe)CaA`K$@$=Czbez(^!Lqz=VYNw}8^96PGTOw59OrDc^PFog|l-uo5V{?Qv> z#k_vR${0CG7TW2pKT$xO9zhXuB=*L7({$2v@0q#1YNX0C?1IU8r!_1e()RO_WZ=@F zImhVsWlNZo%R>`@TimYjP2jReMR3@RmC&KsEtli7c&ZF?nW#9AW1zY?Y!08TddX7NM~`J90jo8Vt53 zv2jhk4}l_sHgU=w9uLzlR`8&rLd;uv4-yL7qA*&>|qjuUO*E z6dobZ_CiwQ$+G3A76Eeoxh;A?|0SvD9M_B}7Z(l7^!u(3h-~ZhZFuT; zr%ISrL|hgP?q#UjX$RF|D1jtqK$v_?W)w;@*BwC%?%UPPqo;F-(RXv5B()(Gw+fwd zy(Lxc1`XBdtxP+M7WIm1ZCnw?^AI~m=1N7+fgh4w$6FkFW>y)(V`$@PrcLctI_;^P(FwT|CeX`76XLaYOMCBWro)&mtwsxAP1e{Z_#-sCx_Y!sxuI^m z@|d$uac7y^3nh~8QaQ{S&-+{pDOC;S2NygucM{$rK_B(~JXN03@xLRr#bACC_zhfOAbc)DLqJ%uJPr;)L(_`tIAj24xyeNN#I%`ycmErmF`lyC29e zJ%jR@gLK~>a^V(uqaXwGI~PWgb4s5co;_pDI4!SB48n&1!HignmwjKXQ$FrF>kG!w z`l7_TB7XWK&?flB?EOCzy$5iWn<(%HBwJS>-Z-IVCA*rD0cALfwfqmZL%#kuxNqn) zeLgSX;PCTd4^;EjuZxN>SE3BVmUv;P1|IFPea5Ga54_VDA7lNj5YJ2xj>LKStMLT! z2BbS4>-kQQy<Zy9{T~TEGA~`dAI>)*>hHN=dn52D29WdCti9E z-g-l!i#2hs=4agA+257VM<{Of41_WbhG@=F3H;m{%t?s`;9JF8nI3bb1-#`yI1}E9 z|D6U7Q`m}lz(7EjAwWPR{;poX9D&9D5&->QXv!%3Zm(Ke*pZFmRb^F}^gmo>e?XK0%doiS&!UBUDn z*%KzA-g8;GA6Z^K5tY;em(&T$vUe*j?>_J)d7sA{rwkMbvs)ZCYV+!=X+5%cvzdyY zI+7YHCb(fq%-&G)Oa_S@p(?bYmIRPNne7+~Ui}CYUe$|HvmuCc!DwjOWCd)f*F$!cLHz^bgh~zd0 ze8}%=N{rZg%Tra>b=Rouh) zJ+pg#g8)H+qXNTOrRLERdU>?G?m}3vlZyWmOc2}RYXlkuBorMe7$gp`cE>ov^zAVL zYEy!T1cUf%l%&U*I3a^Z&Nh?5f8O(BMc-6tI zd-}_uiF7U+)aE}f=PQ0adA#xYyzS&v@td2uHe_toh70~V$+*wD+3a@uGkrbul??zD z;@wWFgLJFTr64Up|A_{W!zI;g;ti{9|f5)i*CghfXc=Lz<%>i`h zyHbDb%L^B=Ajts^QR45PxGuH*FR%bA4-e|7oV#Zu+}K@nxj**+xOcE;*%yim54HXp z@=O$^%0g?cV?S&Bqn-S99Q z>$Z97o9~pXa|dMxAXKQWMtJvpqV|nXOdmBv63g&bZLk3+ylB#aT{Fc#qSXf-3Ws!7 zWpnwvCx?rkY~c$aImsuq$)Sq<2rqnDV8G$bX2_4(*vf94vX31;#_jD-kXcM4)bq8m z!*%K2Vx#`&aW|w~*8&q_i|nq5kHej*L|*GjB(JB(v<(gh%%hgW-7==!d=4IO;RbZ0 zC~UvjTV-l8&Z14=Gbqd8Hky_j&{96NTb+Au(_quDzw!{EyT;OS6~CBj*ACWkem}5O zyQv0ijq4Hb?EqG>`ka3CbRnsyLO-j~V?$UW;p`TBJgIA-vtwW3hPy9E>HH*zKz%tqT46Wa2 zZYtO`e>wddZpxGF)3DLBCv>G=4UeTc6=?|&Mu25wuv6s$x)O(<3BR;Jj7-7uXntr^ zEidu7SZ&S|E;Z^cEnP3FPpK{7?udLi2pl{U@>BvwM(cNWr8#bBAQcaA{j;`;%VR0F z3UE1GM4T22ZKLrW_^Y>#)~sw$m*~+H=(qxRXj>sU>D;kI;iiqH`V)Jb^BH!mxsXhg za=4w3asg_XgAG#)$pSioSmw|$NA~81~cgnw6IzDFH#rwoNX={B+hpH3f!!9@;SHTO~ z%Xit|gHu)@ISQ9>WAIjh%|v85gG*-LJi%+E+*SK@zrWkQtM~6fEE*@3@1kv^wmyMB zS^39MG{+DWFG1Q*O`HpZ$xm34JQQOy`uGFd_q$ju*cR6<=}T;I7D^X>G5(eyywufl zd9cBB_bi9&`t_YJFN5s5w4@@%AY~IbWLAf-+mE*@#2^cXUt%jfh&|pT_Ovb;!JHHO z)f9c8r=+)!f{X0y)}S1mu2DcRg z-ut`hQ0ejSO;MJQ9l5c|RyyCW_n0p}!b-!xx zb5URapLv@-AIt09*Bj3O+1if!giy@bh9Glno>%fKA~6~xzha9644HgmsG>OlcTkAA z5=Zcz#R5j5#XUy<-1#hqhFAqGUCu4}w%A2doW&p?s5V~&?JW*R1h7>mQ>4nBAbKD6 z0ac!ATub1|n0Qy%#w{GW?x=`pusM`3tcsEB;^{yY7oQ@wTaYI@BDB^LY24n#^wgsg zk0Clcn9UwZb29gCXxOq+fmjl77YobqD|09v4Iw_asf4vohh8+ zj@7ka=@#hH{WOXAc58_cXA1^x?__v@qOEwqVI?i=j;9VRHS=lEqGAe9a?%oBI-0J~Q*cQ6`5lAaw znO=@cFPdtfdR4XOu4w%JB;a_{;e33xAHGumYHY=r8@%bJgA69*^_~(mUDI2-p1p_J!UW?42xUx&C4uIJbxxz$hFye3v zMLCq=uGJX~+_%+3OZlDc4*i9Cqw@Xpn!7V{6OifXnp&Ta@ob20Hzb_!K?J$SY7p`j zNINtVe;?TEJcsk5y$PiZTP{ftQzu_fj%3_eSJuX(rq2QHU?%)4mH4*t`u2=PyKEO_ zspWUBmXZ!4WL9A7GYrr~u1sU!Qk}=zVeGse#{VEOkjf{!?ML1XWyvm~ zcfyw)PNux#R-AdwT4@A)e!d~*24P|{AF2;3hXL6rmMUn`Qj^WaX{N-O@rT%yPvIrbZXxhuq!4xYd#A($-#*0zIFqyfFP`ii0QDY{~VP3GwRboB}cWg2{u z=MTylp=6rz2vUS+tO1c`Vj(*qX}y^`b&jxQYq#dc@>PlVMj>#ozG+}e-VR>{8Hwx~ zK5(AU!-Ok_v|Qk>Ss$@n=Q3MdQjwNI{oq%N%e>7BKvEe{f0Xi$$(XLE7KvXiZ#U6K zAHvj9!O}yOv0gzIq1)|)AD+X@tui_Wbqs{(|HmdAW^yFs1Y85MTEy$ zs13u%o8AL-hEPOF3Rk{Tr(m&8K5Ywy$UVC<$r1hJ@-6ro;?ycy&iPwkHO0uR zo}V%5yvewx7(Rt`YYNx$&2NZQ(^5+zkQ|bfo0JFl+m(PNsVC*#B}kt49##LJ!jMq{ z6C%nPHyp#8yn19VY4d_^ZiRRV#RM&h70W2d*&+}?`60ZESU0;n+@T7OTiOBC46zniXs$*2M zs37MN&M%>ZXo7`Sj9e@}ej(+Una>u*r0Yv~Z_Zz69xxG&X7O@aqSsaNp^5tF$*pV3)EM1IwOcXY>j^LdDaIp+9%|H>D=5~ z>y-MJ&gD>l_pemI>=+`zl>++T!%>TV`;}`QW}fdZv@vQs1|0in2^<Rl|x5=FvSN+>8L9%ORT|L@`X+3@i!&9Kbs|;-B(=2tBq|ZX+gS3 zS1cPj;h)y+V>=oE`MV%Mm=R7I?bWSjr=e4cGWDN+N4$!*5(H%iy!yRt^5{VqSG|)k z5|7HYKOd+4yaLbytUPu<)rRt8D85%{&DBM) zhXgAfMVoiiG=JZFJ?hFQ3`f@! zZ!2W{oJit7TeEpC&C0gsBPfZQ!&fPBGlv*(9P)S8Vc-|fSF(r=v8X>8r9=HD!(l6T z6Nr0B_f5hVKs=`?OkbLO_Z>NUie$))S)>l(s5InKJokf~{o9}iYys#!+$$O;YjKaz z>+D{naJ70}B-Qd>_#R8OQuZG0FjzJ}LZh z2pXUyW-rlT0t&=M6HK0)OsJ-?m3+tjMCpEkj3N)2`J~?WT85&^zyMaAbn)FBb*4P6 zEXC%4$hgyyMRVf@zBBuIz{0bLeIGB>O{o2DBrXwgPg5zQb?`d{ZJEMGY8Lm6gMI|A z83zpj>5?P-y(5%zKxxTwa!cdB_dx&;nnSu?y~S?zPl&mL09r(!`GooCB_VY3MdH?R z-5#$01v53~2y<7x?Mt_G*ircfdt-<>!FsVq3w?C0`~df%kpNEiFVEKu0cy^&?PKcM zKmGJbBC`BMc^ac78^y8+3%5`!zbV?S7xpy(Yv=I}#um+cY>hg*+6tn`E)Z!n&eYFH zF_WITZnBFJLGn=k=B!jUUgZ#6+W{-)M|h%Ve8k@SxA zE1SuhoIBc`uLt^j*sa!(xCnIa!St{-xXah8FSW^8WNh2IeIF+jHgv0f_~~64SB9&A z7({Ij?}q)C;safO7h~6v>?mRCRG~B@+G;C;l%Lce&ktWNb+t|i1zkFlfdqnBi|rw} zLBbYCfi!M79H&rR*;bo zSonpAc=>L*F#fRw4%+){JeF;7nf#%E-_bCjWDl1IBCf&8`AH~#%$v|ZM6Hxf-jgyZ z&h)Z)Mfbt>sa#_Y-5&Hs*0XNVY{Rgn9J+hBNVA0nx?(uOAgGwX&1OO55xP)35QrZw zg7ev!*GEW-IZTweM@iRb|yJL_|}|^7-$@_L|xH^Y%Xmz(cs6GZ5OE z_&?)yk=!x>%Vwt=c1~$Vz1E@4CkYM49j>k8OuCo+D;YGl%;Z)EqD&?$Hky?;Ev`Xd z$v0dgu6O5~Yx~{w2h?dWZdZQ7J4%SWrqtd??fo^}d@vAKI@Z}cjp+ivxcbX^o7L}i z=&w0UA~`pagMM&DQ-P)x!~9qk-q_1o@rZnj!jk;}1Qm-eE!9ZZNL7xj*5ObDz0`AQqTM zOQ|OeGw)uJ5YStps1=)y#(~kshYPl1wgRO%04?SovUph{qh0u7Uy%&~37L?Cuih1TqTHV3I zZ|w;W6u64i2BuxnUv2?Cy0`$LAd( zH^>DZc2c+Y!T8%R#E*#EO2~5UuXo8AG*B(J>|!)oSl; zfEAI{R}q)}D67u>dH))A_q^+hWsoGTq0~)w(74mH$d9(@h!sB6ZW#U?Qf(LgGfe_i zhy?o#_L*O+h(8E;=Bs(fDQ#3+L)%y3_n_xzwld2m4VTFxBc@NRNA}fLW9;!=p|!6c zG0HIx#MflfeUNvpyn@qF2t0hHxSrlZ0d>_B z4WfsX_;728kyO}`YzYOQt34t0eF(tlJ5Ywh7UHU`c(B^MpG>|dS1x=Hxy@)F0x)fS zQCct%oN@FQoEFX6Pd!BTvGB7;9`9rE%Yo~0CRZBO+a$2DgfZ#`07!y=1}%eL9nus^ zDY}BRMmwDDs8M0T0 z&_H{luG0oRFfWrFFd~7s!NIaRoo{u3>-v3>o((2VGy5TbB}lEyL4=97Zb=;wP$^1r8zn1q|hfUe(sp=tQLao6KAUjfa??ogg)1Z)#+lo^G zR-3UCP8ybd{=4};44r?bNJFhGHRT)Pc8OEOBuz(a7Ou^49Q>gXNzG_?*2>Jh6WpQN zC@**8EV-Xj1!D-0#f2??P7Zqc7rK0{S6?t*>kWU`OLSUlvK=)tymo*(0G;DWS03KW zf(a)@{{M>rNtW`vddCIh;AE+7z`JRT&F?;G;i&jb(= zzuzokYKag`Fd?DtQVI$FV{)l!Aj+lri?ol*75Rofnq^fQ;?iNtl~p&!wiP~G0yZ~S zkK4K%UcbCHJgs%ldY;nKCgqSQpAHT#`Mqkome((CZ<^e;-(T9$#n>VhkLQuz0I%-= zaRK77lN!7a`jk%v;)}yPRh}!u`Lb~B1A;7POMM>P%jLBJgS2I~C(F!?5uTGXwcZeg zS3Z%0cpl8#QoiYv06vC8)YhXwkAR<;G7AX^lsBu%i)huWE3Cw0pd& z0u5q{O?IwCIRj3Is=2p%Sy)a@!}rcQD%fy&bHD0tv_b#Kr*am;G@ zjmeYIxim7B5Bols-!N1qUG^vCMt+LP`jT)-NSx8mSx-}%<#EFF(uyu)# zQDC(iCR2`XVV19E{ka@rKM6Mi2)NxfZe3t&1?N>}8?CGC#a8SPWZ8(vEr=66gx}FD zQiH_02?jM$xh%Cfj-NK-M#f*S4~(Z}Zxp~AWYWrEmKUKZPtL}U zO4Aj^`hKmCfdG9Vo2o}qbdkF05|1Uz0pEnz&eNT!|{fz8mojdBD)HeJXbFltTo0^5gV1Cll2 z#;H5zH%N4xQm9heXNlo-5px5g!b6Ey zadQJYs+~C|+D@1W$-}8s<4^=8aFhtM3FKGRIObUVJ6n?m0P1IcnRUk!;_9kpNIS{m z&)@^)*RD;wNS1hybi8Es`07@O+=7r%p-fT2bFNVn2VX}VHZs=45XWqkO8@00qieJD zbX(z(-&x5`c?)s$&g&ry%m=NW*RvzQ1D@w*DHPP^QgT}#eR7QY^rVq2k#*2YJ}%0^ zu`{V=s|yY$0P3bHur86V-n18!nNEGwy~VYuIYMz55v@^fDy&t5hL~mM88jqv+~lWh zmK!;6U~{o#`c#ef+{~!~Gg3Z-*NJlBAt6sV7Xwmg`Z^7q?qp)zDWt(sX)eK}PDtyJ z!go@o{5y`ofbdZ8FVxd35s_a}&f*!$1F$i%xP$pB02~}=>@C|_;r4}G##vjaEnzp7 zXvI5&CCr=4h=JndHKWK`F1uOe)`{GKPIO{VC4pxU6lf0jJY4G$N{4;kQ)+lO%g&8m zyYBhi&@<-Z{k)h!;mi18ydS2&iMHtw1j%wL(#k{mo(L{tPa7YhNlJwE$pslOw(IpO00_Ln7Uk=8!+9qWH2FQ?J-W-dzz-$Z zA8X}3xLX6$Vgrpd&Jjp_P(um+4ZVlLw>xWZw@G;-ATZ4gvB3|&RUzD^hcagb(Knzc zd(#|jabTk=S{;Z7>$PBcX$K= zQcJF3+-P=R<0K^|NU{S6wb z;W@)^mETT4_4LB3j-ilEP!kU{hXqIRl=L;)!D zkfsR}EJjZPAw}!Cwq*P5_qSfN{4JkKdL$8{BNXo?nBCm%c}~ZSTVFFj?E5fY*iwr2 zc72WuBwx?Y2vrzAI?rN;s$ztZVV@j2?Q<(4zUu?k@r)igy;21w3tV&zB=XEGUukQ_BS`RR+hEC8ntPYwWkvt4U-i=oVP0R=joA zJi=M&TcneiL&}c`r8OIvs-tLZ_aT+`x}EyL zGzf9nu$*H3($L3rg61A(ne>zPr;i4qwf(#-y$0W-xnK1p^2^K?XYI4ASs|`hJfdTUIYHT)ct_sEp#3pg}j^w4( z*5^84x7pM47ts>`(7u(yMVVnupXaTK@O6Yv$GciZ&)i((afg=9-pJZvm(POYW^e44 zP1BEK0@Ve@?(68@6MUvh=UBDRVUs;0jy6L++Pn`JCnPOsoX4a!Ihp^4wUCQaP|n6p z@9|>1`2Y?1p~ukV*?P3uF)AQC(QGjsb83U`^!f_eb67gEnsMQP4NE!~RaSj@dS`TP zsX7fYZG3*&s|Pg5vyR4SU#RkDYB6FJ=&XFE*j$*{B86-z-qzH7JsF2=N&QVJUJtUX zecQ#UEN69rQ>JENA$K7+uxmLq>jQMz6RpSkmD>}FA35osw|0)XhnTlCLnLj7Kies?L?-SOO*`LRW`5SIcQX6Tp4G4_QZacmV zfebhUJ)|CVmi8fnu>+&l=1H$Hal!{h24qKGTS%RJ0Df0vZg*$izF5x4iaYq&X8Bm3EB5j1+XvC$4I`LOBOt6SuOA8>PdC0dVJLvfpGe2?aPf~RI5Rio zG9^hGa&sC)$E27pB4edL@$$e7{x0X78~Qsttb?JKxFk6My+W_|4($!gVpC0MVK0ZO zc7Q$CJT==`x`v{4xszd z*jUz?G7agy45pF74hnhUdDa;cfzkam=a`zGGaY)yQ0kybZM_IGfcC^i(-kD#$&mpc zJOx*)EEN{jLp^qA%%mMYxopGG_PGg>;xaEQ)W|@ha^>q+{Nkg4vmp6s=o&JD33Q=OiUP{8H07N?0q;{8J z;L$DdCDN}&(q)pjkgF6(9zG#KpPYi`{=lT{kOm2e%zDdFX_vHsgzxNmduE)>y1nYX z5v|NUw?%L7=|3>+$Os;4o{qfpd;}>5$f9i{cieq*9nTk^W)!gzsdR>dERB z{YA#NQ(_6yLB;%sH|Xhk5fHI7`$*ltCChFinh0Hw2jB6%&*|dwJDfd3qo1l7&ROU4v{3*XVkr!gk)_@XyA?11y3*F~X zLw><0ZTzy}_Q~z6gII@9viqR>z<)ZH6a;Lt%w0I+ekp{6D)RO1)mRLQO7TjJN@$aB>~@m{XclB5+>%8@UB%9_e* z>El7NhYuFFWrfwa5)L;K?l&4vVpXKRFU*PV4Zx;LaZLGgg8Uiw)!8I3GG}yIhQ@d) zJa{gQP^0ZgGPo8(1Koy4AYfc#1KSatvv^}A`4CN41o+zu0P-hJe^Gz?RPbVQqmOcg zXXT&fVqO)WIVhDD`D2lYBe}$!5r}|dF(VA932?=}^ezf;^S|K{EPTw4q(KLEXs2ESh84EsxyTWEg0zfpoF1yEg)jI!m6f$!>e#>PW{kWP6`zf zWOX>o_wZRs?h{st6-6Mf-&zxHjGey~&8y3~PB+IO>V>(^Z}A*3Vm@n7_1+rz)aKHT zb>)JS6XYtmK9k=?bT4ip82SX&$l3063bH;E_(|Ewi4ss(XDik2$JRWc* z4;h%*f!eUtu!DrK%nI?>0u7T2z)}VF_ACCUpt%8kU>xTEU&4GcVFFg|0`#{d0RDD` z2HHI4lSV?_Xi`|6b{?{|874+?_-r_)oRZ-?%BwUy&>8|Dyi3m^}Xv z5CO`5#R-uy+a*%)>fKUGiuschM|4+2~pYD!;q;ui~{}aC4`#UOje@8{|A3I;pf1R~w6aD8S|2L)y z0z&x@*fanCfT4f|Ls&qnc|0(RCSYX8H=yY}6WB-R-zEdldJF})^w$F33%s2-16%0> oYA=|Al@9=W7nlkDhj^HOeWL%pasKiBX8?$}NDHMg_&-noA6OE!xBvhE diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 702c4b68b8..57c7d2d22b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index b33a32eba1..6c6b50658b 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha03' + classpath 'com.android.tools.build:gradle:3.1.0-alpha06' classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" } } diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.jar b/library-benchmarks/gradle/wrapper/gradle-wrapper.jar index 0bdf3fe94139883078c58008a6b84cd063bfaf64..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 16129 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 diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index 702c4b68b8..57c7d2d22b 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index 7e4ab2eb5c..43628a8f8a 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -26,7 +26,7 @@ sourceCompatibility = '1.8' targetCompatibility = '1.8' group = 'io.realm' -version = file("${projectDir}/../version.txt").text.trim(); +version = file("${projectDir}/../version.txt").text.trim() def commonPom = { licenses { diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index 6b6ea3ab4ff4f69d55c5fd9c0a6ac70f47d41008..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 15797 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 U=U+A*2Z1>&bWr1i|GE4B04mXu<^TWy delta 16241 zcmZ9z18^o$*EJg3HYQFcwr$&)Bok|5Cr@lYv2EM7ZQGd`6WsaU```Dw^LJHOo$9@6 zpYA@Z4)$KVs}y{q0z8frLR|2tSUj&5EbayFo#U+|C`cS$T{Gb?IC#8ayCO&skRwFk zTpkJV;}1_ULNld9R?%MJE zpN~6q_cPrISz?QlAnCNThw#wp2gQfb#*diF(R$MqIV$ul{tEt%ooipOL&49_M@|sN zgAM9tBbgz589i8e~CI&=| zZa<&_c(5d+7ggGF8Db0uUYAXV=hN4>v!%^lfi=P9OOWcOl_=OHUO;5ERLM9DQ!`R? zt-`F=-=}p}oyf(HrAwF%>gX-Pw=oxxP-c!QpzR$<^ik(1#Yw#xMkNPV&14w68|nyK z435KUvt$$0e4mE*lIaHaR#n<5Tr`tDSF_jv+AN!JKMX(#lwua@Oio5~(y`8X34za%Zz8^doEQAiJE$k75GyO#HaO zE6OJ3dby>m)d{Z@tH=iRaZ z4FhAjHR^d$=*u(XP0DX^o|>HX$t`e{t@ss1P3nzFWvJ)-Lh_uM`HC@XgWF){V2olSfMGP`$B?AmPl1#`jRHI)wAj{%~~ zrQfguOSOtJIeSiR9{~c#R{Ur9mj{Gys-kG%-{1AF&I_((qby$FeptUq3}1UG7oEo` zPVQi>)epp37Z=Yc~h$Oc}@LD^FD40zU2!?EGm@bFcYFxo_j7_g+Wu z^FtyuAaqrPwEVZX4c{ZYNZ`cmI@A4WPLFSXi>t}YW-v_7#+hJWY1gR{18=x#s@AkJ zUeSPn0TSn|=5k;362BoP2?{JY^p(D9O?l^teNsetoUmACl(mUEoa>y&r>e z<6w)9Jhx%7agqAnNELCH`EwRR7uN{AqxHg+*tJ&3Yb{*Z%stGbTO!r`#SPZnA^I?F z2y*iqq}36&oe1jZIlm~Kw4W2#O|k3z&cwBl%kbJnI96ApDn*t)faFKTbWGdOwONeO z^w_hKXe^4=SDn8Fu-ntk>#=)v6Zy}RD7+x6{D+=ZwjN*HCIPTx?pvFWouhQDv?v2L zJEmuVSnnD=&w`J;8uCo)2-(-e5PpdwTbh6Yoq_UuD0*}fQ#wKNmNmz-)z8DBWG6vT z4_JMNESQ!|*t4LP@7reE)+%!JKjDYgKbFTZTL&^G%q(TQ(Wv-rV4s((w;)x3PuBqz ziPhZmhrkJ!Kadj5FwgwJ30G-=%7PNgr-Cj_A*c-GAg{4$E`1{;YOOnsI)e(emiXL{ zyg@u_SRm9brZ>nAxVf`Zn#21^RP4Se*R?bphHOSZ(?L#ym+P~)xm!aAch9HyA0(0x zanz|Wws|znxqvXKdSS*8d)eMvzYRTBT9MvVKVGn5ad!vBps+t+L@-hSrp1h3iy|cB zvsv@edgSJ#6&@4`Qx)-r-F2ka6*TIqSSAIzI1cqhn(RxRBTP#dHlH=Y6bAHlkd$_qzCW5w~gvW5B_j`36T{X%4k+MmYXW{gCECYgJ}S^#Vh)W{rqAg!!mq z8VMFgY&baWXs$OLE~HSv{(~7ZeXO*+2RVX#dor`>fZVV2@@iNqmd5o?A50qge$TZG z9pKJ*d}rm+H{1oKCcRF*`IAczT#nb>m*@D!MaPQVKbPwB!xI2n%#5-!A12f zxr2Qsg>B$>*?M_$>Lot&wfc99ZHlth_KsFg*O zKnH7!96NCukn+w_byq`)~9Q)%)_mscaTR>no%xPfg1b^y+V_kk>TV4$= zL|_0DA=qv2Q_2f_-O~1$r#X!03Bn**7X?QhYJ#sOwy71GGch##LVj0US{3d`FYxilBA>#P5<@ zXAQow!4zVZZXzq!XU?A&R!@?%t*;G#u#NhZ8mtwzCAQ!JOJ7da;=RytXcFmX^-1WO z2yCl82t%%F>~881WK}6Qw#e$ZRZlRpAG{b9HTcxS{yF)Wdj1OEH_x#C6(K5P98iGO z5DHaS%BlWuNgV`qB5bqpV!r(!O0wV(7$6`pFd!fx!XQ781?CHYhMw90Wp9`a-G3z`dKU{B2&J}9>E#wZhuzQ@px z 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 diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 702c4b68b8..57c7d2d22b 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index c2a125116c..8bc83e5e24 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -25,7 +25,7 @@ apply plugin: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.bintray' group = 'io.realm' -version = file("${projectDir}/../version.txt").text.trim(); +version = file("${projectDir}/../version.txt").text.trim() def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../dependencies.list")) diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 6b6ea3ab4ff4f69d55c5fd9c0a6ac70f47d41008..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 15797 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 U=U+A*2Z1>&bWr1i|GE4B04mXu<^TWy delta 16241 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 z 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 diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 702c4b68b8..57c7d2d22b 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy index 957581a6cf..53d9889db0 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy @@ -84,7 +84,7 @@ class RealmTransformer extends Transform { // Find all the class names def inputClassNames = getClassNames(inputs) def referencedClassNames = getClassNames(referencedInputs) - def allClassNames = merge(inputClassNames, referencedClassNames); + def allClassNames = merge(inputClassNames, referencedClassNames) // Create and populate the Javassist class pool ClassPool classPool = new ManagedClassPool(inputs, referencedInputs) @@ -103,7 +103,7 @@ class RealmTransformer extends Transform { .findAll { it.superclass?.equals(baseProxyMediator) } logger.debug "Proxy Mediator Classes: ${proxyMediatorClasses*.name}" proxyMediatorClasses.each { - BytecodeModifier.overrideTransformedMarker(it); + BytecodeModifier.overrideTransformedMarker(it) } // Find the model classes @@ -174,8 +174,8 @@ class RealmTransformer extends Transform { it.getPackageName() } - def targetSdk = project?.android?.defaultConfig?.targetSdkVersion?.mApiLevel as String; - def minSdk = project?.android?.defaultConfig?.minSdkVersion?.mApiLevel as String; + def targetSdk = project?.android?.defaultConfig?.targetSdkVersion?.mApiLevel as String + def minSdk = project?.android?.defaultConfig?.minSdkVersion?.mApiLevel as String def env = System.getenv() def disableAnalytics = env["REALM_DISABLE_ANALYTICS"] @@ -252,7 +252,7 @@ class RealmTransformer extends Transform { Set merged = new HashSet() merged.addAll(set1) merged.addAll(set2) - return merged; + return merged } // There is no official way to get the path to android.jar for transform. diff --git a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy index 495289af6d..2e2e6cd279 100644 --- a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy +++ b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy @@ -198,8 +198,8 @@ class BytecodeModifierTest extends Specification { def codeAttribute = methodInfo.getCodeAttribute() for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { - int index = ci.next(); - int op = ci.byteAt(index); + int index = ci.next() + int op = ci.byteAt(index) if (op == Opcode.GETFIELD) { return true } @@ -212,8 +212,8 @@ class BytecodeModifierTest extends Specification { def codeAttribute = methodInfo.getCodeAttribute() for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { - int index = ci.next(); - int op = ci.byteAt(index); + int index = ci.next() + int op = ci.byteAt(index) if (op == Opcode.INVOKEVIRTUAL) { return true } diff --git a/realm.properties b/realm.properties index fd53887478..4567c707cc 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=4.3.1 +gradleVersion=4.4.1 ndkVersion=r10e diff --git a/realm/build.gradle b/realm/build.gradle index be9e85de9c..290fe8258e 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -3,7 +3,7 @@ project.ext.compileSdkVersion = 26 project.ext.buildToolsVersion = '26.0.2' buildscript { - ext.kotlin_version = '1.1.51' + ext.kotlin_version = '1.2.10' ext.dokka_version = '0.9.15' repositories { mavenLocal() @@ -14,7 +14,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha03' + classpath 'com.android.tools.build:gradle:3.1.0-alpha06' classpath 'de.undercouch:gradle-download-task:3.3.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' @@ -37,7 +37,7 @@ allprojects { } group = 'io.realm' - version = file("${rootDir}/../version.txt").text.trim(); + version = file("${rootDir}/../version.txt").text.trim() repositories { mavenLocal() google() diff --git a/realm/gradle/wrapper/gradle-wrapper.jar b/realm/gradle/wrapper/gradle-wrapper.jar index 0bdf3fe94139883078c58008a6b84cd063bfaf64..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 16129 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 diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 702c4b68b8..57c7d2d22b 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index bd05e52d59..3ba0e38c80 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -61,7 +61,7 @@ android { dependencies { implementation project(':realm-library') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" androidTestImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test:rules:1.0.1' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ce4fd40b06..86f675551b 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -207,7 +207,7 @@ dependencies { androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' - androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" // specify error prone version to prevent sudden failure @@ -465,7 +465,7 @@ task downloadCore() { return true } if (project.forceDownloadCore) { - return true; + return true } if (!isHashCheckingEnabled()) { println "Skipping hash check(empty \'coreSha256Hash\')." @@ -497,7 +497,7 @@ task downloadCore() { throw new GradleException("Invalid checksum for file '" + "${project.coreArchiveFile.getName()}'. Expected " + "${project.coreSha256Hash.toLowerCase()} but got " + - "${calculatedHash.toLowerCase()}."); + "${calculatedHash.toLowerCase()}.") } } else { println 'Skipping hash check (empty \'coreSha256Hash\').' From 588796e980cfedcba2af22264aa281ee6360b5e7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 8 Jan 2018 15:03:18 +0000 Subject: [PATCH 1146/2110] Update Gradle plugin (#5617) * Update Readme and Kotlin deps * update Android Gradle plugin and Gradle wrapper --- README.md | 2 +- examples/build.gradle | 2 +- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54329 bytes .../layout/activity_realm_basic_example.xml | 3 +-- examples/multiprocessExample/build.gradle | 2 +- examples/newsreaderExample/build.gradle | 4 ++-- .../src/main/res/layout/content_details.xml | 3 +-- .../src/main/res/layout/content_main.xml | 3 +-- examples/objectServerExample/build.gradle | 4 ++-- examples/rxJavaExample/build.gradle | 2 +- .../secureTokenAndroidKeyStore/build.gradle | 2 +- examples/unitTestExample/build.gradle | 2 +- gradle-plugin/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54329 bytes gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54329 bytes library-benchmarks/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54329 bytes realm-transformer/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54329 bytes realm/build.gradle | 2 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54329 bytes .../realm-annotations-processor/build.gradle | 2 +- 23 files changed, 18 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index eb5523d646..6b2a3f20be 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * Download & install the Android SDK **Build-Tools 27.0.1**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). + * Download & install the Android SDK **Build-Tools 27.0.2**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * If you use Android Studio, Android Studio 3.0 or higher is required. diff --git a/examples/build.gradle b/examples/build.gradle index a801b01386..d7299615a6 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,6 +1,6 @@ project.ext.sdkVersion = 27 project.ext.minSdkVersion = 15 -project.ext.buildTools = '27.0.1' +project.ext.buildTools = '27.0.2' // Don't cache SNAPSHOT (changing) dependencies. configurations.all { diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index 99340b4ad18d3c7e764794d300ffd35017036793..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 100644 GIT binary patch delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL diff --git a/examples/kotlinExample/src/main/res/layout/activity_realm_basic_example.xml b/examples/kotlinExample/src/main/res/layout/activity_realm_basic_example.xml index 8c5a06512e..38d19031c7 100644 --- a/examples/kotlinExample/src/main/res/layout/activity_realm_basic_example.xml +++ b/examples/kotlinExample/src/main/res/layout/activity_realm_basic_example.xml @@ -6,8 +6,7 @@ android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" android:layout_width="match_parent" - android:layout_height="match_parent" - tools:keep="@layout/activity_realm_basic_example"> + android:layout_height="match_parent"> + tools:context=".ui.details.DetailsActivity"> + tools:context=".ui.main.MainActivity"> W@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 99340b4ad18d3c7e764794d300ffd35017036793..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 100644 GIT binary patch delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 6c6b50658b..0382c346d3 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -28,7 +28,7 @@ apply plugin: 'realm-android' android { compileSdkVersion 27 - buildToolsVersion "27.0.0" + buildToolsVersion "27.0.2" defaultConfig { minSdkVersion 15 diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.jar b/library-benchmarks/gradle/wrapper/gradle-wrapper.jar index 99340b4ad18d3c7e764794d300ffd35017036793..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 100644 GIT binary patch delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index 99340b4ad18d3c7e764794d300ffd35017036793..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 100644 GIT binary patch delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 8bc83e5e24..ac6b0e2e07 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -60,7 +60,7 @@ dependencies { compile localGroovy() compile gradleApi() compile "io.realm:realm-annotations:${version}" - provided 'com.android.tools.build:gradle:3.1.0-alpha03' + provided 'com.android.tools.build:gradle:3.1.0-alpha06' compile 'org.javassist:javassist:3.22.0-GA' testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 99340b4ad18d3c7e764794d300ffd35017036793..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 100644 GIT binary patch delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL diff --git a/realm/build.gradle b/realm/build.gradle index 290fe8258e..82f7c806cf 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,6 +1,6 @@ project.ext.minSdkVersion = 9 project.ext.compileSdkVersion = 26 -project.ext.buildToolsVersion = '26.0.2' +project.ext.buildToolsVersion = '27.0.2' buildscript { ext.kotlin_version = '1.2.10' diff --git a/realm/gradle/wrapper/gradle-wrapper.jar b/realm/gradle/wrapper/gradle-wrapper.jar index 99340b4ad18d3c7e764794d300ffd35017036793..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 100644 GIT binary patch delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 5c203099a7..a272ee6680 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -11,7 +11,7 @@ dependencies { compile "com.squareup:javawriter:2.5.1" compile "io.realm:realm-annotations:${version}" - testCompile files('../realm-library/build/intermediates/bundles/baseRelease/classes.jar') // Java projects cannot depend on AAR files + testCompile files('../realm-library/build/intermediates/intermediate-jars/objectServer/release/classes.jar') // Java projects cannot depend on AAR files testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it testCompile group:'junit', name:'junit', version:'4.12' testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' From 82b44ac922a89d7626bfe0523f9bb1ded12e0eaf Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 9 Jan 2018 12:30:31 +0100 Subject: [PATCH 1147/2110] Updated version number --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b2595557b0..d99e7162d0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.2-SNAPSHOT \ No newline at end of file +5.0.0-SNAPSHOT \ No newline at end of file From 4df4cf4fffecf003009a91232d4d96b312f77031 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 11 Jan 2018 09:33:14 +0000 Subject: [PATCH 1148/2110] Fixing Plugin Test (#5672) * Fixing Plugin Test (not passing) * Added a Jenkins step to run Gradle plugin tests * Added a Jenkins step to run Transformer tests --- Jenkinsfile | 16 ++++++++++++++++ .../groovy/io/realm/gradle/PluginTest.groovy | 13 ++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7c8e9a7d6d..e2f45c0704 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,6 +69,22 @@ try { } } + stage('Gradle plugin tests') { + try { + gradle('gradle-plugin', 'check') + } finally { + storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + } + } + + stage('Realm Transformer tests') { + try { + gradle('realm-transformer', 'check') + } finally { + storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + } + } + stage('Static code analysis') { try { gradle('realm', 'findbugs pmd checkstyle') diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index 2f9080ebd8..e07e0e66f1 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -16,6 +16,8 @@ package io.realm.gradle +import io.realm.transformer.RealmTransformer + import com.android.build.api.transform.Transform import org.gradle.api.GradleException import org.gradle.api.Project @@ -30,7 +32,6 @@ import org.junit.Before import org.junit.Test import static org.junit.Assert.assertEquals -import static org.junit.Assert.assertFalse import static org.junit.Assert.assertTrue import static org.junit.Assert.fail @@ -40,13 +41,13 @@ class PluginTest { private String currentVersion @Before - public void setUp() { + void setUp() { project = ProjectBuilder.builder().build() currentVersion = new File("../version.txt").text.trim() } @Test - public void pluginAddsRightDependencies() { + void pluginAddsRightDependencies() { project.buildscript { repositories { mavenLocal() @@ -62,8 +63,6 @@ class PluginTest { project.apply plugin: 'com.android.application' project.apply plugin: 'realm-android' - assertTrue(containsUrl(project.repositories, 'https://jitpack.io')) - assertTrue(containsDependency(project.dependencies, 'io.realm', 'realm-android-library', currentVersion)) assertTrue(containsDependency(project.dependencies, 'io.realm', 'realm-annotations', currentVersion)) @@ -71,7 +70,7 @@ class PluginTest { } @Test - public void pluginFailsWithoutAndroidPlugin() { + void pluginFailsWithoutAndroidPlugin() { project.buildscript { repositories { mavenLocal() @@ -107,7 +106,7 @@ class PluginTest { def configurationContainerField = DefaultDependencyHandler.class.getDeclaredField("configurationContainer") configurationContainerField.setAccessible(true) def configurationContainer = configurationContainerField.get(dependencies) - def compileConfiguration = configurationContainer.findByName("compile") + def compileConfiguration = configurationContainer.findByName("api") def DependencySet dependencySet = compileConfiguration.getDependencies() for (Dependency dependency in dependencySet) { From 7dfa63ea2bf6ab26bbf57dc34f6b06ba78c19215 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 12 Jan 2018 14:23:38 +0100 Subject: [PATCH 1149/2110] Upgrade to Sync 2.2.9 (#5676) --- CHANGELOG.md | 4 ++++ dependencies.list | 6 +++--- .../src/main/cpp/io_realm_internal_OsObject.cpp | 1 - .../main/cpp/io_realm_internal_OsRealmConfig.cpp | 2 +- .../src/main/cpp/io_realm_internal_OsSchemaInfo.cpp | 2 -- .../src/main/cpp/io_realm_internal_Table.cpp | 1 - realm/realm-library/src/main/cpp/java_accessor.hpp | 3 +-- .../src/main/cpp/java_sort_descriptor.hpp | 2 -- .../src/main/cpp/jni_impl/android_logger.cpp | 2 -- .../main/cpp/jni_util/java_exception_thrower.cpp | 2 -- realm/realm-library/src/main/cpp/jni_util/log.hpp | 13 ++++++------- realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/util.hpp | 2 -- 13 files changed, 16 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df4a90869d..3133d7f865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ * Throws a better exception message when calling `RealmObjectSchema.addField()` with a `RealmModel` class (#3388). * Use https for Realm version checker (#4043). +### Internal + +* Upgraded to Realm Sync 2.2.9 +* Upgraded to Realm Core 5.1.2 ## 4.3.1 (2017-12-06) diff --git a/dependencies.list b/dependencies.list index 3703b72124..e137ca9248 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,9 +1,9 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.1.8 -REALM_SYNC_SHA256=14e4aabe270638aa96f84396be27985b6809e532183035c5150dd2933d676248 +REALM_SYNC_VERSION=2.2.9 +REALM_SYNC_SHA256=d770d639d2b187c15e6d0bc798b909f5b424d61444f1f83f9e56f5be43e96afc # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.1.0 +REALM_OBJECT_SERVER_DE_VERSION=2.5.1 diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 3099c70d56..8d07d997ce 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include "util.hpp" #include "java_class_global_def.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 1da13db3ce..928e244ca5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -362,7 +362,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigS "(Ljava/lang/String;Ljava/lang/String;I)Z", true); std::function ssl_verify_callback = - [](const std::string server_address, REALM_UNUSED realm::sync::Client::port_type server_port, + [](const std::string server_address, REALM_UNUSED realm::sync::Session::port_type server_port, const char* pem_data, size_t pem_size, REALM_UNUSED int preverify_ok, int depth) { Log::d("Callback to Java requesting certificate validation for host %1", diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp index 0f428436a7..744a6ea7af 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp @@ -19,8 +19,6 @@ #include #include #include -#include - #include "java_accessor.hpp" #include "java_exception_def.hpp" #include "util.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 1618c48ad9..346d614e57 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -21,7 +21,6 @@ #include "io_realm_internal_Table.h" #include "shared_realm.hpp" -#include "util/format.hpp" #include "java_accessor.hpp" #include "java_exception_def.hpp" diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index 333f77a3f4..3db10074c3 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -26,8 +26,7 @@ #include #include -#include -#include +#include #include "java_class_global_def.hpp" #include "java_exception_def.hpp" diff --git a/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp b/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp index 81384f9a15..92893bdca0 100644 --- a/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp +++ b/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp @@ -19,8 +19,6 @@ #include -#include "descriptor_ordering.hpp" - namespace realm { namespace jni_util { diff --git a/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp b/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp index 8e3972aea0..80bb4d78f5 100644 --- a/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp +++ b/realm/realm-library/src/main/cpp/jni_impl/android_logger.cpp @@ -16,8 +16,6 @@ #include -#include "util/format.hpp" - #include "android_logger.hpp" using namespace realm; diff --git a/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp index 6278fd7fb8..9ec171aad7 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_exception_thrower.cpp @@ -17,8 +17,6 @@ #include "java_exception_thrower.hpp" #include "log.hpp" -#include - using namespace realm::util; using namespace realm::jni_util; diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp index 98cc58d1c9..db61e5fc7a 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -27,7 +27,6 @@ #include "io_realm_log_LogLevel.h" #include "realm/util/logger.hpp" -#include "util/format.hpp" #define TR_ENTER() \ if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ @@ -114,32 +113,32 @@ class Log { template inline static void t(const char* fmt, Args&&... args) { - shared().log(trace, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + shared().log(trace, REALM_JNI_TAG, nullptr, util::format(fmt, {util::Printable(args)...}).c_str()); } template inline static void d(const char* fmt, Args&&... args) { - shared().log(debug, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + shared().log(debug, REALM_JNI_TAG, nullptr, util::format(fmt, {util::Printable(args)...}).c_str()); } template inline static void i(const char* fmt, Args&&... args) { - shared().log(info, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + shared().log(info, REALM_JNI_TAG, nullptr, util::format(fmt, {util::Printable(args)...}).c_str()); } template inline static void w(const char* fmt, Args&&... args) { - shared().log(warn, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + shared().log(warn, REALM_JNI_TAG, nullptr, util::format(fmt, {util::Printable(args)...}).c_str()); } template inline static void e(const char* fmt, Args&&... args) { - shared().log(error, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + shared().log(error, REALM_JNI_TAG, nullptr, util::format(fmt, {util::Printable(args)...}).c_str()); } template inline static void f(const char* fmt, Args&&... args) { - shared().log(fatal, REALM_JNI_TAG, nullptr, _impl::format(fmt, {_impl::Printable(args)...}).c_str()); + shared().log(fatal, REALM_JNI_TAG, nullptr, util::format(fmt, {util::Printable(args)...}).c_str()); } static realm::util::RootLogger::Level convert_to_core_log_level(Level level); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 2b7db38bd1..8517ee7f43 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 2b7db38bd112c82c55a0fa4bbecd24f652d45ba1 +Subproject commit 8517ee7f4378fe0f54945b3e4973766ff65e455d diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 21bb35c354..6161f2ad36 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -32,8 +32,6 @@ #include #include -#include - #include "io_realm_internal_Util.h" #include "java_exception_def.hpp" From 1ae964239fd7deb8329c1a07e01f80c273af2c97 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 12 Jan 2018 15:20:38 +0000 Subject: [PATCH 1150/2110] fixing deps scope (#5675) * fixing scope of maven-publish POM files --- CHANGELOG.md | 1 + gradle-plugin/build.gradle | 3 ++- realm-transformer/build.gradle | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3133d7f865..e267c9cde2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Throws a better exception message when calling `RealmObjectSchema.addField()` with a `RealmModel` class (#3388). * Use https for Realm version checker (#4043). +* Prevent Realms Gradle plugin from transitively forcing specific versions of Google Build Tools onto downstream projects (#5640). ### Internal diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index d9a9d8695e..f4eb2c691c 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -52,10 +52,11 @@ dependencies { and this https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ for more info. */ compile 'com.neenbedankt.gradle.plugins:android-apt:1.8' //TODO: https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ - provided 'com.android.tools.build:gradle:3.1.0-alpha06' + compileOnly 'com.android.tools.build:gradle:3.1.0-alpha06' testCompile gradleTestKit() testCompile 'junit:junit:4.12' + testCompile 'com.android.tools.build:gradle:3.1.0-alpha06' } //for Ant filter diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index ac6b0e2e07..5fdb8f61c8 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -60,7 +60,7 @@ dependencies { compile localGroovy() compile gradleApi() compile "io.realm:realm-annotations:${version}" - provided 'com.android.tools.build:gradle:3.1.0-alpha06' + compileOnly 'com.android.tools.build:gradle:3.1.0-alpha06' compile 'org.javassist:javassist:3.22.0-GA' testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { From 0977f006d90e4daec807e92f129c40fa6346c985 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 12 Jan 2018 23:25:04 +0000 Subject: [PATCH 1151/2110] Nh/reverting ros to fix tests (#5682) * Reverting to ROS 2.1.0 for tests, since we have permission tests flakiness with 2.5.1 --- dependencies.list | 2 +- .../java/io/realm/objectserver/AuthTests.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dependencies.list b/dependencies.list index e137ca9248..da2d4d0168 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,5 +5,5 @@ REALM_SYNC_SHA256=d770d639d2b187c15e6d0bc798b909f5b424d61444f1f83f9e56f5be43e96a # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.5.1 +REALM_OBJECT_SERVER_DE_VERSION=2.1.0 diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index bc3f4e6f39..30555e9628 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -534,6 +534,7 @@ public void loggedOut(SyncUser user) { // The pre-emptive token refresh subsystem should function, and properly refresh the access token. // WARNING: this test can fail if there's a difference between the server's and device's clock, causing the // refresh access token to be too far in time. + @Ignore("Test still times out https://github.com/realm/realm-java/issues/5681") @Test(timeout = 30000) public void preemptiveTokenRefresh() throws NoSuchFieldException, IllegalAccessException, InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); From 70351a9e202d3e873f25983bfde49ae85449e281 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 15 Jan 2018 14:56:27 +0000 Subject: [PATCH 1152/2110] fixes unknown error code * Logging a warning instead of throwing an exception when sync report an unknown error code, --- CHANGELOG.md | 1 + .../java/io/realm/SessionTests.java | 374 +++++++++--------- .../objectServer/java/io/realm/ErrorCode.java | 5 +- 3 files changed, 197 insertions(+), 183 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e267c9cde2..520fe2ec18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Throws a better exception message when calling `RealmObjectSchema.addField()` with a `RealmModel` class (#3388). * Use https for Realm version checker (#4043). * Prevent Realms Gradle plugin from transitively forcing specific versions of Google Build Tools onto downstream projects (#5640). +* [ObjectServer] logging a warning message instead of throwing an exception, when sync report an unknown error code (#5403). ### Internal diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index f4dc71ac09..152a993251 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -25,9 +25,12 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.atomic.AtomicBoolean; + import io.realm.entities.StringOnly; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.log.RealmLog; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; @@ -96,17 +99,11 @@ public void removeProgressListener() { SyncSession session = SyncManager.getSession(configuration); ProgressListener[] listeners = new ProgressListener[] { null, - new ProgressListener() { - @Override - public void onChange(Progress progress) { - // Listener 1, not present - } + progress -> { + // Listener 1, not present }, - new ProgressListener() { - @Override - public void onChange(Progress progress) { - // Listener 2, present - } + progress -> { + // Listener 2, present } }; session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, listeners[2]); @@ -125,23 +122,20 @@ public void errorHandler_clientResetReported() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - String filePathFromError = handler.getOriginalFile().getAbsolutePath(); - String filePathFromConfig = session.getConfiguration().getPath(); - assertEquals(filePathFromError, filePathFromConfig); - assertFalse(handler.getBackupFile().exists()); - assertTrue(handler.getOriginalFile().exists()); - - looperThread.testComplete(); + .errorHandler((session, error) -> { + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; } + + final ClientResetRequiredError handler = (ClientResetRequiredError) error; + String filePathFromError = handler.getOriginalFile().getAbsolutePath(); + String filePathFromConfig = session.getConfiguration().getPath(); + assertEquals(filePathFromError, filePathFromConfig); + assertFalse(handler.getBackupFile().exists()); + assertTrue(handler.getOriginalFile().exists()); + + looperThread.testComplete(); }) .build(); @@ -159,30 +153,27 @@ public void errorHandler_manualExecuteClientReset() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - try { - handler.executeClientReset(); - fail("All Realms should be closed before executing Client Reset can be allowed"); - } catch(IllegalStateException ignored) { - } - - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); + .errorHandler((session, error) -> { + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; + } - // Validate that files have been moved - assertFalse(handler.getOriginalFile().exists()); - assertTrue(handler.getBackupFile().exists()); - looperThread.testComplete(); + final ClientResetRequiredError handler = (ClientResetRequiredError) error; + try { + handler.executeClientReset(); + fail("All Realms should be closed before executing Client Reset can be allowed"); + } catch(IllegalStateException ignored) { } + + // Execute Client Reset + looperThread.closeTestRealms(); + handler.executeClientReset(); + + // Validate that files have been moved + assertFalse(handler.getOriginalFile().exists()); + assertTrue(handler.getBackupFile().exists()); + looperThread.testComplete(); }) .build(); @@ -200,43 +191,40 @@ public void errorHandler_useBackupSyncConfigurationForClientReset() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); - - // Validate that files have been moved - assertFalse(handler.getOriginalFile().exists()); - assertTrue(handler.getBackupFile().exists()); - - RealmConfiguration backupRealmConfiguration = handler.getBackupRealmConfiguration(); - assertNotNull(backupRealmConfiguration); - assertFalse(backupRealmConfiguration.isSyncConfiguration()); - assertTrue(backupRealmConfiguration.isRecoveryConfiguration()); - - Realm backupRealm = Realm.getInstance(backupRealmConfiguration); - assertFalse(backupRealm.isEmpty()); - assertEquals(1, backupRealm.where(StringOnly.class).count()); - assertEquals("Foo", backupRealm.where(StringOnly.class).findAll().first().getChars()); - backupRealm.close(); - - // opening a Dynamic Realm should also work - DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); - dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); - RealmResults all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); - assertEquals(1, all.size()); - assertEquals("Foo", all.first().getString(StringOnly.FIELD_CHARS)); - dynamicRealm.close(); - looperThread.testComplete(); + .errorHandler((session, error) -> { + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; } + + final ClientResetRequiredError handler = (ClientResetRequiredError) error; + // Execute Client Reset + looperThread.closeTestRealms(); + handler.executeClientReset(); + + // Validate that files have been moved + assertFalse(handler.getOriginalFile().exists()); + assertTrue(handler.getBackupFile().exists()); + + RealmConfiguration backupRealmConfiguration = handler.getBackupRealmConfiguration(); + assertNotNull(backupRealmConfiguration); + assertFalse(backupRealmConfiguration.isSyncConfiguration()); + assertTrue(backupRealmConfiguration.isRecoveryConfiguration()); + + Realm backupRealm = Realm.getInstance(backupRealmConfiguration); + assertFalse(backupRealm.isEmpty()); + assertEquals(1, backupRealm.where(StringOnly.class).count()); + assertEquals("Foo", backupRealm.where(StringOnly.class).findAll().first().getChars()); + backupRealm.close(); + + // opening a Dynamic Realm should also work + DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); + dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); + RealmResults all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); + assertEquals(1, all.size()); + assertEquals("Foo", all.first().getString(StringOnly.FIELD_CHARS)); + dynamicRealm.close(); + looperThread.testComplete(); }) .modules(new StringOnlyModule()) .build(); @@ -261,68 +249,65 @@ public void errorHandler_useBackupSyncConfigurationAfterClientReset() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); + .errorHandler((session, error) -> { + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; + } + + final ClientResetRequiredError handler = (ClientResetRequiredError) error; + // Execute Client Reset + looperThread.closeTestRealms(); + handler.executeClientReset(); + + // Validate that files have been moved + assertFalse(handler.getOriginalFile().exists()); + assertTrue(handler.getBackupFile().exists()); + + String backupFile = handler.getBackupFile().getAbsolutePath(); + + // this SyncConf doesn't specify any module, it will throw a migration required + // exception since the backup Realm contain only StringOnly table + RealmConfiguration backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile); + + try { + Realm.getInstance(backupRealmConfiguration); + fail("Expected to throw a Migration required"); + } catch (RealmMigrationNeededException expected) { + } + + // opening a DynamicRealm will work though + DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); - // Validate that files have been moved - assertFalse(handler.getOriginalFile().exists()); - assertTrue(handler.getBackupFile().exists()); - - String backupFile = handler.getBackupFile().getAbsolutePath(); - - // this SyncConf doesn't specify any module, it will throw a migration required - // exception since the backup Realm contain only StringOnly table - RealmConfiguration backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile); - - try { - Realm.getInstance(backupRealmConfiguration); - fail("Expected to throw a Migration required"); - } catch (RealmMigrationNeededException expected) { - } - - // opening a DynamicRealm will work though - DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); - - dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); - RealmResults all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); - assertEquals(1, all.size()); - assertEquals("Foo", all.first().getString(StringOnly.FIELD_CHARS)); - - // make sure we can't write to it (read-only Realm) - try { - dynamicRealm.beginTransaction(); - fail("Can't perform transactions on read-only Realms"); - } catch (IllegalStateException expected) { - } - dynamicRealm.close(); - - try { - SyncConfiguration.forRecovery(backupFile, null, StringOnly.class); - fail("Expected to throw java.lang.Class is not a RealmModule"); - } catch (IllegalArgumentException expected) { - } - - // specifying the module will allow to open the typed Realm - backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, null, new StringOnlyModule()); - Realm backupRealm = Realm.getInstance(backupRealmConfiguration); - assertFalse(backupRealm.isEmpty()); - assertEquals(1, backupRealm.where(StringOnly.class).count()); - RealmResults allSorted = backupRealm.where(StringOnly.class).findAll(); - assertEquals("Foo", allSorted.get(0).getChars()); - backupRealm.close(); - - looperThread.testComplete(); + dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); + RealmResults all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); + assertEquals(1, all.size()); + assertEquals("Foo", all.first().getString(StringOnly.FIELD_CHARS)); + + // make sure we can't write to it (read-only Realm) + try { + dynamicRealm.beginTransaction(); + fail("Can't perform transactions on read-only Realms"); + } catch (IllegalStateException expected) { + } + dynamicRealm.close(); + + try { + SyncConfiguration.forRecovery(backupFile, null, StringOnly.class); + fail("Expected to throw java.lang.Class is not a RealmModule"); + } catch (IllegalArgumentException expected) { } + + // specifying the module will allow to open the typed Realm + backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, null, new StringOnlyModule()); + Realm backupRealm = Realm.getInstance(backupRealmConfiguration); + assertFalse(backupRealm.isEmpty()); + assertEquals(1, backupRealm.where(StringOnly.class).count()); + RealmResults allSorted = backupRealm.where(StringOnly.class).findAll(); + assertEquals("Foo", allSorted.get(0).getChars()); + backupRealm.close(); + + looperThread.testComplete(); }) .modules(new StringOnlyModule()) .build(); @@ -347,46 +332,43 @@ public void errorHandler_useClientResetEncrypted() { final byte[] randomKey = TestHelper.getRandomKey(); final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) .encryptionKey(randomKey) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); + .errorHandler((session, error) -> { + if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { + fail("Wrong error " + error.toString()); + return; + } - RealmConfiguration backupRealmConfiguration = handler.getBackupRealmConfiguration(); - - // can open encrypted backup Realm - Realm backupEncryptedRealm = Realm.getInstance(backupRealmConfiguration); - assertEquals(1, backupEncryptedRealm.where(StringOnly.class).count()); - RealmResults allSorted = backupEncryptedRealm.where(StringOnly.class).findAll(); - assertEquals("Foo", allSorted.get(0).getChars()); - backupEncryptedRealm.close(); - - String backupFile = handler.getBackupFile().getAbsolutePath(); - // build a conf to open a DynamicRealm - backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, randomKey, new StringOnlyModule()); - backupEncryptedRealm = Realm.getInstance(backupRealmConfiguration); - assertEquals(1, backupEncryptedRealm.where(StringOnly.class).count()); - allSorted = backupEncryptedRealm.where(StringOnly.class).findAll(); - assertEquals("Foo", allSorted.get(0).getChars()); - backupEncryptedRealm.close(); - - // using wrong key throw - try { - Realm.getInstance(SyncConfiguration.forRecovery(backupFile, TestHelper.getRandomKey(), new StringOnlyModule())); - fail("Expected to throw when using wrong encryption key"); - } catch (RealmFileException expected) { - } - - looperThread.testComplete(); + final ClientResetRequiredError handler = (ClientResetRequiredError) error; + // Execute Client Reset + looperThread.closeTestRealms(); + handler.executeClientReset(); + + RealmConfiguration backupRealmConfiguration = handler.getBackupRealmConfiguration(); + + // can open encrypted backup Realm + Realm backupEncryptedRealm = Realm.getInstance(backupRealmConfiguration); + assertEquals(1, backupEncryptedRealm.where(StringOnly.class).count()); + RealmResults allSorted = backupEncryptedRealm.where(StringOnly.class).findAll(); + assertEquals("Foo", allSorted.get(0).getChars()); + backupEncryptedRealm.close(); + + String backupFile = handler.getBackupFile().getAbsolutePath(); + // build a conf to open a DynamicRealm + backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, randomKey, new StringOnlyModule()); + backupEncryptedRealm = Realm.getInstance(backupRealmConfiguration); + assertEquals(1, backupEncryptedRealm.where(StringOnly.class).count()); + allSorted = backupEncryptedRealm.where(StringOnly.class).findAll(); + assertEquals("Foo", allSorted.get(0).getChars()); + backupEncryptedRealm.close(); + + // using wrong key throw + try { + Realm.getInstance(SyncConfiguration.forRecovery(backupFile, TestHelper.getRandomKey(), new StringOnlyModule())); + fail("Expected to throw when using wrong encryption key"); + } catch (RealmFileException expected) { } + + looperThread.testComplete(); }) .modules(new StringOnlyModule()) .build(); @@ -427,4 +409,32 @@ public void downloadAllServerChanges_throwsOnUiThread() throws InterruptedExcept realm.close(); } } + + @Test + @UiThreadTest + public void unrecognizedErrorCode_errorHandler() { + AtomicBoolean errorHandlerCalled = new AtomicBoolean(false); + configuration = new SyncConfiguration + .Builder(user, REALM_URI) + .errorHandler((session, error) -> { + errorHandlerCalled.set(true); + assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); + assertEquals(ErrorCode.Category.FATAL, error.getCategory()); + + }) + .build(); + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getSession(configuration); + + TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); + RealmLog.add(testLogger); + + session.notifySessionError(3, "Unknown Error"); + RealmLog.remove(testLogger); + + assertTrue(errorHandlerCalled.get()); + assertEquals("Unknown error code: 3", testLogger.message); + + realm.close(); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 24f936108f..8b47653bec 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -19,6 +19,8 @@ import java.io.IOException; +import io.realm.log.RealmLog; + /** * This class enumerate all potential errors related to using the Object Server or synchronizing data. */ @@ -192,7 +194,8 @@ public static ErrorCode fromInt(int errorCode) { return error; } } - throw new IllegalArgumentException("Unknown error code: " + errorCode); + RealmLog.warn("Unknown error code: " + errorCode); + return UNKNOWN; } /** From dc6c9a77c4f67066011014e06702d3e3568865df Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 16 Jan 2018 08:31:11 +0100 Subject: [PATCH 1153/2110] Add implicit support for Partial Sync and add States to OrderedChangeSet (#5611) --- CHANGELOG.md | 16 ++ .../OrderedCollectionChangeSetTests.java | 48 +++- .../main/cpp/collection_changeset_wrapper.hpp | 104 ++++++++ ...o_realm_internal_OsCollectionChangeSet.cpp | 51 +++- .../main/cpp/io_realm_internal_OsObject.cpp | 2 + .../src/main/cpp/io_realm_internal_Table.cpp | 5 +- .../src/main/cpp/java_accessor.hpp | 3 + realm/realm-library/src/main/cpp/object-store | 2 +- .../cpp/observable_collection_wrapper.hpp | 8 +- realm/realm-library/src/main/cpp/util.hpp | 1 - .../io/realm/OrderedCollectionChangeSet.java | 88 +++++++ .../OrderedRealmCollectionChangeListener.java | 5 +- .../src/main/java/io/realm/RealmQuery.java | 15 +- .../io/realm/internal/EmptyLoadChangeSet.java | 117 ++++++++ .../realm/internal/ObservableCollection.java | 9 +- .../realm/internal/OsCollectionChangeSet.java | 95 +++++-- .../main/java/io/realm/internal/OsList.java | 5 +- .../java/io/realm/internal/OsResults.java | 18 +- .../java/io/realm/internal/OsSharedRealm.java | 1 + .../internal/StatefulCollectionChangeSet.java | 87 ++++++ .../java/io/realm/BaseIntegrationTest.java | 41 ++- .../io/realm/IsolatedIntegrationTests.java | 2 + .../java/io/realm/SSLConfigurationTests.java | 19 ++ .../io/realm/StandardIntegrationTest.java | 7 + .../realm/objectserver/PartialSyncTests.java | 249 ++++++++++++++---- .../realm/objectserver/utils/HttpUtils.java | 5 +- 26 files changed, 873 insertions(+), 130 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/collection_changeset_wrapper.hpp create mode 100644 realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/StatefulCollectionChangeSet.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 520fe2ec18..952697e6c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 5.0.0 (YYYY-MM-DD) + +### Breaking Changes + +* The `OrderedCollectionChangeSet` parameter in `OrderedRealmCollectionChangeListener.onChange()` is no longer nullable. Use `changeSet.getState()` instead (#5619). + +### Enhancements + +* Added support for partial Realms. Read [here](https://realm.io/docs/java/latest/#partial-realms) for more information. +* Added two new methods to `OrderedCollectionChangeSet`: `getState()` and `getError()` (#5619). + +### Internal + +* Upgraded to Realm Sync 2.2.2. + + ## 4.3.2 (YYYY-MM-DD) ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java index a8e0b8701c..267860f80a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -36,11 +36,11 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertSame; import static junit.framework.Assert.fail; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; // Tests for the ordered collection fine grained notifications for both RealmResults and RealmList. @@ -459,7 +459,7 @@ public void run() { // The change set should be empty when the async query returns at the first time. @Test @RunTestInLooperThread - public void emptyChangeSet_findAllAsync() { + public void initialChangeSet_findAllAsync() { if (type == ObservablesType.REALM_LIST) { looperThread.testComplete(); return; @@ -469,14 +469,42 @@ public void emptyChangeSet_findAllAsync() { populateData(realm, 10); final RealmResults results = realm.where(Dog.class).sort(Dog.FIELD_AGE).findAllAsync(); looperThread.keepStrongReference(results); - results.addChangeListener(new OrderedRealmCollectionChangeListener>() { - @Override - public void onChange(RealmResults collection, @Nullable OrderedCollectionChangeSet changeSet) { - assertSame(collection, results); - assertEquals(10, collection.size()); - assertNull(changeSet); - looperThread.testComplete(); - } + results.addChangeListener((collection, changeSet) -> { + assertSame(collection, results); + assertEquals(10, collection.size()); + assertTrue(changeSet.isCompleteResult()); + assertEquals(OrderedCollectionChangeSet.State.INITIAL, changeSet.getState()); + assertEquals(0, changeSet.getInsertions().length); + assertEquals(0, changeSet.getChanges().length); + assertEquals(0, changeSet.getDeletions().length); + looperThread.testComplete(); + }); + } + + // The change set should be empty when the async query returns at the first time. + @Test + @RunTestInLooperThread + public void initialChangeSet_findAll() { + if (type == ObservablesType.REALM_LIST) { + looperThread.testComplete(); + return; + } + + Realm realm = looperThread.getRealm(); + populateData(realm, 10); + final RealmResults results = realm.where(Dog.class).sort(Dog.FIELD_AGE).findAll(); + looperThread.keepStrongReference(results); + results.addChangeListener((collection, changeSet) -> { + assertSame(collection, results); + assertEquals(11, collection.size()); + assertTrue(changeSet.isCompleteResult()); + assertEquals(OrderedCollectionChangeSet.State.UPDATE, changeSet.getState()); + assertEquals(1, changeSet.getInsertions().length); + looperThread.testComplete(); + }); + + realm.executeTransaction(r -> { + r.createObject(Dog.class); }); } diff --git a/realm/realm-library/src/main/cpp/collection_changeset_wrapper.hpp b/realm/realm-library/src/main/cpp/collection_changeset_wrapper.hpp new file mode 100644 index 0000000000..41a3602327 --- /dev/null +++ b/realm/realm-library/src/main/cpp/collection_changeset_wrapper.hpp @@ -0,0 +1,104 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_IMPL_COLLECTION_CHANGESET_WRAPPER_HPP +#define REALM_JNI_IMPL_COLLECTION_CHANGESET_WRAPPER_HPP + +#include "collection_notifications.hpp" +#include "util.hpp" +#include "jni_util/java_class.hpp" +#include "jni_util/java_global_weak_ref.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/log.hpp" +#include "jni_util/jni_utils.hpp" +#include "jni_util/java_class.hpp" +#include "jni_util/java_method.hpp" +#include "sync/partial_sync.hpp" +#include "object-store/src/subscription_state.hpp" + +#include + +using namespace realm::jni_util; + +namespace realm { +namespace _impl { + +// Wrapper of Object Store CollectionChangeSet +// It is used to better control the mapping between Object Store concepts and Java API's, especially +// when it comes to states and defining errors. +class CollectionChangeSetWrapper { +public: + CollectionChangeSetWrapper(CollectionChangeSet const& changeset, std::string error_message, bool partial_sync_realm) + : m_changeset(changeset) + , m_error_message(error_message) + , m_partial_sync_realm(partial_sync_realm) + { + } + + ~CollectionChangeSetWrapper() = default; + + CollectionChangeSetWrapper(CollectionChangeSetWrapper&&) = delete; + CollectionChangeSetWrapper& operator=(CollectionChangeSetWrapper&&) = delete; + CollectionChangeSetWrapper(CollectionChangeSetWrapper const&) = delete; + CollectionChangeSetWrapper& operator=(CollectionChangeSetWrapper const&) = delete; + + CollectionChangeSet& get() + { + return m_changeset; + }; + + + + jthrowable get_error() { + JNIEnv* env = JniUtils::get_env(false); + if (m_error_message != "") { + static JavaClass realm_exception_class(env, "io/realm/exceptions/RealmException"); + static JavaMethod realm_exception_constructor(env, realm_exception_class, "", "(Ljava/lang/String;)V"); + return (jthrowable) env->NewObject(realm_exception_class, realm_exception_constructor, to_jstring(env, m_error_message)); + } else if (m_changeset.partial_sync_error_message != "") { + // Indicates a soft error, i.e. illegal name of query. + static JavaClass illegal_argument_class(env, "java/lang/IllegalArgumentException"); + static JavaMethod illegal_argument_constructor(env, illegal_argument_class, "", "(Ljava/lang/String;)V"); + return (jthrowable) env->NewObject(illegal_argument_class, illegal_argument_constructor, to_jstring(env, m_changeset.partial_sync_error_message)); + } else { + return nullptr; + } + } + + bool is_remote_data_loaded() { + if (!m_partial_sync_realm) { + return true; + } + + return m_changeset.partial_sync_new_state == partial_sync::SubscriptionState::Initialized + || m_changeset.partial_sync_new_state == partial_sync::SubscriptionState::NotSupported; + } + + bool is_empty() { + return m_changeset.empty() && m_error_message.empty(); + } + +private: + CollectionChangeSet m_changeset; + std::string m_error_message; // From any exception being thrown that are not reported using Partial Sync + bool m_partial_sync_realm; // if true, this Realm supports partial Sync +}; + + +} // namespace realm +} // namespace _impl + +#endif // REALM_JNI_IMPL_COLLECTION_CHANGESET_WRAPPER_HPP diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp index 807ce9afa2..47a1ab20c6 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "subscription_state.hpp" +#include "collection_changeset_wrapper.hpp" #include "io_realm_internal_OsCollectionChangeSet.h" #include @@ -21,6 +23,7 @@ #include "util.hpp" using namespace realm; +using namespace _impl; static void finalize_changeset(jlong ptr); static jintArray index_set_to_jint_array(JNIEnv* env, const IndexSet& index_set); @@ -29,7 +32,7 @@ static jintArray index_set_to_indices_array(JNIEnv* env, const IndexSet& index_s static void finalize_changeset(jlong ptr) { TR_ENTER_PTR(ptr); - delete reinterpret_cast(ptr); + delete reinterpret_cast(ptr); } static jintArray index_set_to_jint_array(JNIEnv* env, const IndexSet& index_set) @@ -91,14 +94,14 @@ JNIEXPORT jintArray JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeG { TR_ENTER_PTR(native_ptr) // no throws - auto& change_set = *reinterpret_cast(native_ptr); + auto& change_set = *reinterpret_cast(native_ptr); switch (type) { case io_realm_internal_OsCollectionChangeSet_TYPE_DELETION: - return index_set_to_jint_array(env, change_set.deletions); + return index_set_to_jint_array(env, change_set.get().deletions); case io_realm_internal_OsCollectionChangeSet_TYPE_INSERTION: - return index_set_to_jint_array(env, change_set.insertions); + return index_set_to_jint_array(env, change_set.get().insertions); case io_realm_internal_OsCollectionChangeSet_TYPE_MODIFICATION: - return index_set_to_jint_array(env, change_set.modifications_new); + return index_set_to_jint_array(env, change_set.get().modifications_new); default: REALM_UNREACHABLE(); } @@ -109,15 +112,45 @@ JNIEXPORT jintArray JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeG { TR_ENTER_PTR(native_ptr) // no throws - auto& change_set = *reinterpret_cast(native_ptr); + auto& change_set = *reinterpret_cast(native_ptr); switch (type) { case io_realm_internal_OsCollectionChangeSet_TYPE_DELETION: - return index_set_to_indices_array(env, change_set.deletions); + return index_set_to_indices_array(env, change_set.get().deletions); case io_realm_internal_OsCollectionChangeSet_TYPE_INSERTION: - return index_set_to_indices_array(env, change_set.insertions); + return index_set_to_indices_array(env, change_set.get().insertions); case io_realm_internal_OsCollectionChangeSet_TYPE_MODIFICATION: - return index_set_to_indices_array(env, change_set.modifications_new); + return index_set_to_indices_array(env, change_set.get().modifications_new); default: REALM_UNREACHABLE(); } } + +JNIEXPORT jobject JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetError(JNIEnv*, jobject, jlong native_ptr) { + TR_ENTER_PTR(native_ptr) + auto& change_set = *reinterpret_cast(native_ptr); + return change_set.get_error(); +} + +JNIEXPORT jboolean Java_io_realm_internal_OsCollectionChangeSet_nativeIsRemoteDataLoaded(JNIEnv*, jobject, jlong native_ptr) { + TR_ENTER_PTR(native_ptr) + auto& change_set = *reinterpret_cast(native_ptr); + return change_set.is_remote_data_loaded(); +} + +JNIEXPORT jint JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetOldStatusCode(JNIEnv*, jobject, jlong native_ptr) { + TR_ENTER_PTR(native_ptr) + auto& change_set = *reinterpret_cast(native_ptr); + return static_cast(change_set.get().partial_sync_old_state); +} + +JNIEXPORT jint JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetNewStatusCode(JNIEnv*, jobject, jlong native_ptr) { + TR_ENTER_PTR(native_ptr) + auto& change_set = *reinterpret_cast(native_ptr); + return static_cast(change_set.get().partial_sync_new_state); +} + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeIsEmpty(JNIEnv*, jobject, jlong native_ptr) { + TR_ENTER_PTR(native_ptr) + auto& change_set = *reinterpret_cast(native_ptr); + return to_jbool(change_set.is_empty()); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 8d07d997ce..66278fbcfd 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -20,6 +20,8 @@ #if REALM_ENABLE_SYNC #include #endif +#include + #include #include #include diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 346d614e57..4f79cc4b7d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -20,12 +20,13 @@ #include "io_realm_internal_Property.h" #include "io_realm_internal_Table.h" -#include "shared_realm.hpp" - #include "java_accessor.hpp" #include "java_exception_def.hpp" +#include "shared_realm.hpp" #include "jni_util/java_exception_thrower.hpp" +#include + using namespace std; using namespace realm; using namespace realm::_impl; diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index 3db10074c3..87eb4ee693 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -1,3 +1,4 @@ + /* * Copyright 2017 Realm Inc. * @@ -24,6 +25,8 @@ #include #include +#include +#include #include #include diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 8517ee7f43..c7599df766 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 8517ee7f4378fe0f54945b3e4973766ff65e455d +Subproject commit c7599df7661f6b716c66027fc058fff682b45514 diff --git a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp index 6b86d91138..5c30c65740 100644 --- a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp +++ b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp @@ -17,6 +17,7 @@ #ifndef REALM_JNI_IMPL_OBSERVABLE_COLLECTION_WRAPPER_HPP #define REALM_JNI_IMPL_OBSERVABLE_COLLECTION_WRAPPER_HPP +#include "collection_changeset_wrapper.hpp" #include "jni_util/java_class.hpp" #include "jni_util/java_global_weak_ref.hpp" #include "jni_util/java_method.hpp" @@ -68,25 +69,26 @@ void ObservableCollectionWrapper::start_listening(JNIEnv* env, jobject j_coll m_collection_weak_ref = jni_util::JavaGlobalWeakRef(env, j_collection_object); } + bool partial_sync_realm = m_collection.get_realm()->is_partial(); auto cb = [=](CollectionChangeSet const& changes, std::exception_ptr err) { // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; + std::string error_message = ""; if (err) { try { std::rethrow_exception(err); } catch (const std::exception& e) { - realm::jni_util::Log::e("Caught exception in collection change callback %1", e.what()); - return; + error_message = e.what(); } } m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv* local_env, jobject collection_obj) { local_env->CallVoidMethod( collection_obj, notify_change_listeners, - reinterpret_cast(changes.empty() ? 0 : new CollectionChangeSet(changes))); + reinterpret_cast(new CollectionChangeSetWrapper(changes, error_message, partial_sync_realm))); }); }; diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 6161f2ad36..0b8106cd10 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -31,7 +31,6 @@ #include #include #include - #include "io_realm_internal_Util.h" #include "java_exception_def.hpp" diff --git a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java index 52ca14ee0c..97c1fcf9cd 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java @@ -18,6 +18,8 @@ import java.util.Locale; +import javax.annotation.Nullable; + /** * This interface describes the changes made to a collection during the last update. *

            @@ -28,6 +30,44 @@ * change, or an array of {@link Range}s. */ public interface OrderedCollectionChangeSet { + + /** + * State describing the nature of the changeset. + */ + public enum State { + /** + * This state is used first time the callback is invoked. The query will have completed and + * data is ready for the UI. + */ + INITIAL, + /** + * This state is used for every subsequent update after the first. + */ + UPDATE, + /** + * This state is used if some error occurred on the background evaluating the query. + *

            + * For local and fully synchronized Realms, this state should only be encountered if the + * Realm could not be succesfully opened in the background,. + *

            + * For partially synchronized Realms, it is only possible to get into this state if an error + * happened while evaluating the query on the server or some other error prevented data from + * being downloaded. + *

            + * In this state, the content of the {@link RealmResults} is undefined. + */ + ERROR + } + + /** + * Returns the state represented by this change. See {@link State} for a description of the + * different states a changeset can be in. + * + * @return what kind of state is represented by this changeset. + * @see State + */ + State getState(); + /** * The deleted indices in the previous version of the collection. * @@ -73,6 +113,54 @@ public interface OrderedCollectionChangeSet { */ Range[] getChangeRanges(); + /** + * Returns any error that happened. If an error has happened, the state of the collection and other + * changeset information is undefined. It is possible for a collection to go into an error state + * after being created and starting to send updates. + * + * @return the error that happened. + */ + @Nullable + Throwable getError(); + + /** + * Returns {@code true} if the query result is considered "complete". For all local Realms, or + * fully synchronized Realms, this method will always return {@code true}. + *

            + * This method thus only makes sense for partially synchronized Realms (as defined by setting + * {@link SyncConfiguration.Builder#partialRealm()}. + *

            + * For those Realms, data is only downloaded when queried which means that until the data is + * downloaded, a local query might return a query result that would not have been possible on a + * fully synchronized Realm. + *

            + * Consider the following case: + *

              + *
            1. An app is online and makes a query for all messages containing the word "Realm".
            2. + *
            3. Partial synchronization downloads all those messages.
            4. + *
            5. The app goes offline.
            6. + *
            7. The app makes an offline query against all messages containing the word "Database".
            8. + *
            + * + * Here there are two situations where the query result might be considered "incomplete". + *

            + * The first is when the "Realm" query runs for the first time. The local query will finish + * faster than the network can download data so the query will initially report an empty + * incomplete query result. + *

            + * The second is when the "Database" query is run. The initial query result will not be + * empty, but contain all messages that contain both "Realm" and "Database", as they are already + * available offline. + *

            + * In both cases, a new notification will be triggered as soon as the device is able to download + * the data required to produce a "complete" query result. + * + * @return {@code true} if the query result is fully consistent with the server at some point in + * time. {@code false} if the query was executed while the device was offline or all data + * has not been downloaded yet. + */ + boolean isCompleteResult(); + /** * */ diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java index 24216e776d..6c57480848 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionChangeListener.java @@ -35,8 +35,7 @@ public interface OrderedRealmCollectionChangeListener { * This will be called when the async query is finished the first time or the collection of objects has changed. * * @param t the collection this listener is registered to. - * @param changeSet object with information about which rows in the collection were added, removed or modified. - * {@code null} is returned the first time an async query is completed. + * @param changeSet object with information about the change. */ - void onChange(T t, @Nullable OrderedCollectionChangeSet changeSet); + void onChange(T t, OrderedCollectionChangeSet changeSet); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 7cb8bb2634..21a2e7bc8a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1536,9 +1536,8 @@ private RealmQuery orWithoutThreadValidation() { * @return the query object */ public RealmQuery and() { - realm.checkIfValid(); - - return this; + realm.checkIfValid(); + return this; } /** @@ -2106,7 +2105,7 @@ public RealmResults findAllSortedAsync(String[] fieldNames, final Sort[] sort */ @Deprecated public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, - String fieldName2, Sort sortOrder2) { + String fieldName2, Sort sortOrder2) { return findAllSorted(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); } @@ -2123,7 +2122,7 @@ public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, */ @Deprecated public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, - String fieldName2, Sort sortOrder2) { + String fieldName2, Sort sortOrder2) { return findAllSortedAsync(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); } @@ -2201,9 +2200,9 @@ public E findFirstAsync() { } private RealmResults createRealmResults(TableQuery query, - @Nullable SortDescriptor sortDescriptor, - @Nullable SortDescriptor distinctDescriptor, - boolean loadResults) { + @Nullable SortDescriptor sortDescriptor, + @Nullable SortDescriptor distinctDescriptor, + boolean loadResults) { RealmResults results; OsResults osResults = OsResults.createFromQuery(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); if (isDynamicQuery()) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java new file mode 100644 index 0000000000..7253e188e1 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java @@ -0,0 +1,117 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.internal; + +import io.realm.RealmResults; + +/** + * Fake changeset used if {@link RealmResults#load()} is called manually. + */ +public class EmptyLoadChangeSet extends OsCollectionChangeSet { + + private static final int[] NO_INDEX_CHANGES = new int[0]; + private static final Range[] NO_RANGE_CHANGES = new Range[0]; + + public EmptyLoadChangeSet() { + super(0, true); + // FIXME Read partial sync status from Realm when creating this + } + + @Override + public State getState() { + return State.INITIAL; + } + + @Override + public int[] getDeletions() { + return NO_INDEX_CHANGES; + } + + @Override + public int[] getInsertions() { + return NO_INDEX_CHANGES; + } + + @Override + public int[] getChanges() { + return NO_INDEX_CHANGES; + } + + @Override + public Range[] getDeletionRanges() { + return NO_RANGE_CHANGES; + } + + @Override + public Range[] getInsertionRanges() { + return NO_RANGE_CHANGES; + } + + @Override + public Range[] getChangeRanges() { + return NO_RANGE_CHANGES; + } + + @Override + public Throwable getError() { + return null; + } + + @Override + public boolean isRemoteDataLoaded() { + return false; + } + + @Override + public boolean isCompleteResult() { + return isRemoteDataLoaded(); + } + + @Override + public int getOldStatusCode() { + return -3; // Undefined + } + + @Override + public int getNewStatusCode() { + return -3; // Undefined + } + + @Override + public boolean isFirstAsyncCallback() { + return super.isFirstAsyncCallback(); + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public String toString() { + return super.toString(); + } + + @Override + public long getNativePtr() { + return super.getNativePtr(); + } + + @Override + public long getNativeFinalizerPtr() { + return super.getNativeFinalizerPtr(); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java b/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java index 7004df192c..be7ba607dd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java @@ -9,16 +9,15 @@ // Helper class for supporting add change listeners on OsResults & OsList. @Keep interface ObservableCollection { - class CollectionObserverPair extends ObserverPairList.ObserverPair { public CollectionObserverPair(T observer, Object listener) { super(observer, listener); } - public void onChange(T observer, @Nullable OrderedCollectionChangeSet changes) { + public void onChange(T observer, OsCollectionChangeSet changes) { if (listener instanceof OrderedRealmCollectionChangeListener) { //noinspection unchecked - ((OrderedRealmCollectionChangeListener) listener).onChange(observer, changes); + ((OrderedRealmCollectionChangeListener) listener).onChange(observer, new StatefulCollectionChangeSet(changes)); } else if (listener instanceof RealmChangeListener) { //noinspection unchecked ((RealmChangeListener) listener).onChange(observer); @@ -53,9 +52,9 @@ public int hashCode() { } class Callback implements ObserverPairList.Callback { - private final OrderedCollectionChangeSet changeSet; + private final OsCollectionChangeSet changeSet; - Callback(@Nullable OrderedCollectionChangeSet changeSet) { + Callback(OsCollectionChangeSet changeSet) { this.changeSet = changeSet; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java index 93bbc3e7ac..7f98101f37 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java @@ -45,12 +45,19 @@ public class OsCollectionChangeSet implements OrderedCollectionChangeSet, Native private static long finalizerPtr = nativeGetFinalizerPtr(); private final long nativePtr; + private final boolean firstAsyncCallback; - public OsCollectionChangeSet(long nativePtr) { + public OsCollectionChangeSet(long nativePtr, boolean firstAsyncCallback) { this.nativePtr = nativePtr; + this.firstAsyncCallback = firstAsyncCallback; NativeContext.dummyContext.addReference(this); } + @Override + public State getState() { + throw new UnsupportedOperationException("This method should be overridden in a subclass"); + } + /** * {@inheritDoc} */ @@ -99,17 +106,41 @@ public Range[] getChangeRanges() { return longArrayToRangeArray(nativeGetRanges(nativePtr, TYPE_MODIFICATION)); } - /** - * {@inheritDoc} - */ @Override - public long getNativePtr() { - return nativePtr; + public Throwable getError() { + return (Throwable) nativeGetError(nativePtr); } @Override - public long getNativeFinalizerPtr() { - return finalizerPtr; + public boolean isCompleteResult() { + throw new UnsupportedOperationException("This method should be overridden in a subclass"); + } + + public boolean isRemoteDataLoaded() { + return nativeIsRemoteDataLoaded(nativePtr); + } + + public int getOldStatusCode() { + return nativeGetOldStatusCode(nativePtr); + } + + public int getNewStatusCode() { + return nativeGetNewStatusCode(nativePtr); + } + + /** + * Returns {@code true} if this is the first time an asynchronous query returns a result, i.e. + * the query completed. + */ + public boolean isFirstAsyncCallback() { + return firstAsyncCallback; + } + + /** + * Returns {@code true} if this changeset is empty, and doesn't contain any relevant changes. + */ + public boolean isEmpty() { + return nativeIsEmpty(nativePtr); } // Convert long array returned by the nativeGetXxxRanges() to Range array. @@ -127,14 +158,6 @@ private Range[] longArrayToRangeArray(int[] longArray) { return ranges; } - private native static long nativeGetFinalizerPtr(); - - // Returns the ranges as an long array. eg.: [startIndex1, length1, startIndex2, length2, ...] - private native static int[] nativeGetRanges(long nativePtr, int type); - - // Returns the indices array. - private native static int[] nativeGetIndices(long nativePtr, int type); - @Override public String toString() { if (nativePtr == 0) { @@ -152,4 +175,44 @@ public String toString() { return string; } + + /** + * {@inheritDoc} + */ + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return finalizerPtr; + } + + // Returns the underlying error if an error was detected. + // The underlying layer will wrap it in an appropropriate exception class. + // `null` is returned if no error is present + @Nullable + private native Object nativeGetError(long nativePtr); + + private native int nativeGetOldStatusCode(long nativePtr); + + private native int nativeGetNewStatusCode(long nativePtr); + + // Returns true if the data described by the subscription has been downloaded to the device, + // false if not. In either case, the query is run against the local dataset. + private native boolean nativeIsRemoteDataLoaded(long nativePtr); + + /** + * Returns {@code true} if this changeset is empty, and doesn't contain any relevant changes. + */ + private native boolean nativeIsEmpty(long nativePtr); + + private native static long nativeGetFinalizerPtr(); + + // Returns the ranges as a long array. eg.: [startIndex1, length1, startIndex2, length2, ...] + private native static int[] nativeGetRanges(long nativePtr, int type); + + // Returns the indices array. + private native static int[] nativeGetIndices(long nativePtr, int type); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsList.java b/realm/realm-library/src/main/java/io/realm/internal/OsList.java index f532d6df69..6f30d9d364 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsList.java @@ -247,11 +247,12 @@ public void removeAllListeners() { // Called by JNI @Override public void notifyChangeListeners(long nativeChangeSetPtr) { - if (nativeChangeSetPtr == 0) { + OsCollectionChangeSet changeset = new OsCollectionChangeSet(nativeChangeSetPtr, false); + if (changeset.isEmpty()) { // First time "query" returns. Do nothing. return; } - observerPairs.foreach(new Callback(new OsCollectionChangeSet(nativeChangeSetPtr))); + observerPairs.foreach(new Callback(changeset)); } private static native long nativeGetFinalizerPtr(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index c810eb9b9b..ca6e8c636e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -431,16 +431,20 @@ public boolean isValid() { // Called by JNI @Override public void notifyChangeListeners(long nativeChangeSetPtr) { - if (nativeChangeSetPtr == 0 && isLoaded()) { + // Object Store compute the change set between the SharedGroup versions when the query created and the latest. + // So it is possible it deliver a non-empty change set for the first async query returns. + OsCollectionChangeSet changeset = (nativeChangeSetPtr == 0) + ? new EmptyLoadChangeSet() + : new OsCollectionChangeSet(nativeChangeSetPtr, !isLoaded()); + + // Happens e.g. if a synchronous query is created, a change listener is added and then + // a transaction is started on the same thread. This will trigger all notifications + // and deliver an empty changeset. + if (changeset.isEmpty() && isLoaded()) { return; } - boolean wasLoaded = loaded; loaded = true; - // Object Store compute the change set between the SharedGroup versions when the query created and the latest. - // So it is possible it deliver a non-empty change set for the first async query returns. In this case, we - // return an empty change set to user since it is considered as the first time async query returns. - observerPairs.foreach(new Callback(nativeChangeSetPtr == 0 || !wasLoaded ? - null : new OsCollectionChangeSet(nativeChangeSetPtr))); + observerPairs.foreach(new Callback(changeset)); } public Mode getMode() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 137a2ef199..8bbb34df8a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -129,6 +129,7 @@ public interface SchemaChangedCallback { * Callback function to be called from JNI by Object Store when the partial sync results returned. */ @Keep + @Deprecated public abstract static class PartialSyncCallback { private final String className; diff --git a/realm/realm-library/src/main/java/io/realm/internal/StatefulCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/StatefulCollectionChangeSet.java new file mode 100644 index 0000000000..dae64a411e --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/StatefulCollectionChangeSet.java @@ -0,0 +1,87 @@ +package io.realm.internal; + +import javax.annotation.Nullable; + +import io.realm.OrderedCollectionChangeSet; +import io.realm.log.RealmLog; + +/** + * A wrapper around {@link OsCollectionChangeSet} that makes it stateful with regard to how many + * times it has been invoked. + * + * Note that Object Store will calculate the changes between the query was registered and when it + * completes. This information is not useful and might even be misleading when reporting first + * result ({@link io.realm.OrderedCollectionChangeSet.State#INITIAL}. + */ +public class StatefulCollectionChangeSet implements OrderedCollectionChangeSet { + + private final OrderedCollectionChangeSet changeset; + private final Throwable error; + private final State state; + private final boolean remoteDataSynchronized; + + /** + * @param backingChangeset Underlying changeset backing this. + */ + public StatefulCollectionChangeSet(OsCollectionChangeSet backingChangeset) { + this.changeset = backingChangeset; + + // Calculate the state here since object is immutable + boolean isInitial = backingChangeset.isFirstAsyncCallback(); + remoteDataSynchronized = backingChangeset.isRemoteDataLoaded(); + + error = backingChangeset.getError(); + if (error != null) { + state = State.ERROR; + } else { + state = (isInitial) ? State.INITIAL : State.UPDATE; + } + } + + @Override + public State getState() { + return state; + } + + @Override + public int[] getDeletions() { + return changeset.getDeletions(); + } + + @Override + public int[] getInsertions() { + return changeset.getInsertions(); + } + + @Override + public int[] getChanges() { + return changeset.getChanges(); + } + + @Override + public Range[] getDeletionRanges() { + return changeset.getDeletionRanges(); + } + + @Override + public Range[] getInsertionRanges() { + return changeset.getInsertionRanges(); + } + + @Override + public Range[] getChangeRanges() { + return changeset.getChangeRanges(); + } + + @Nullable + @Override + public Throwable getError() { + return error; + } + + @Override + public boolean isCompleteResult() { + return remoteDataSynchronized; + } +} + diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 4c31b68f10..9fe1c29f7d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -26,6 +26,7 @@ import java.io.File; import java.io.IOException; +import io.realm.internal.OsRealmConfig; import io.realm.internal.Util; import io.realm.log.LogLevel; import io.realm.log.RealmLog; @@ -43,9 +44,6 @@ public abstract class BaseIntegrationTest { private static int originalLogLevel; - @Rule - public final TestSyncConfigurationFactory configurationFactory = new TestSyncConfigurationFactory(); - @Rule public RunInLooperThread looperThread = new RunInLooperThread(); @@ -55,8 +53,13 @@ public abstract class BaseIntegrationTest { @Rule public final ExpectedException thrown = ExpectedException.none(); + protected ConfigurationWrapper configurationFactory = new ConfigurationWrapper(looperThread); + + static { + // Attempt to combat issues with the sync meta data Realm not being correctly cleaned + } + protected void prepareEnvironmentForTest() throws IOException { - // FIXME Trying to reset the device environment is crashing tests somehow deleteRosFiles(); if (BaseRealm.applicationContext != null) { // Realm was already initialized. Reset all internal state @@ -128,4 +131,32 @@ private static void deleteFile(File file) throws IOException { throw new IllegalStateException("Failed to delete file or directory: " + file.getAbsolutePath()); } } -} + + // Returns a valid SyncConfiguration usable by tests + // FIXME: WARNING: Do not use `SyncTestRealmConfigurationFactory`, but use this. Refactor later. + protected static class ConfigurationWrapper { + + private final RunInLooperThread looperThread; + + ConfigurationWrapper(RunInLooperThread looperThread) { + this.looperThread = looperThread; + try { + // The RunInLooperThread rule might not be fully created yet. Do it here. + // The `create()` call is idempotent, so should be safe. + looperThread.create(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public SyncConfiguration.Builder createSyncConfigurationBuilder(SyncUser user, String url) { + return new SyncConfiguration.Builder(user, url) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .directory(looperThread.getRoot()); + } + + public File getRoot() { + return looperThread.getRoot(); + } + } +} \ No newline at end of file diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java index 09715009a1..e25225fbbe 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java @@ -26,12 +26,14 @@ public void teardownTest() { if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { // Non-looper tests can reset here restoreEnvironmentAfterTest(); + stopSyncServer(); } else { // Otherwise we need to wait for the test to complete looperThread.runAfterTest(new Runnable() { @Override public void run() { restoreEnvironmentAfterTest(); + stopSyncServer(); } }); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index 68de33b306..7eb39fc43d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -32,6 +32,7 @@ import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; +import io.realm.rule.RunTestInLooperThread; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -40,10 +41,15 @@ @RunWith(AndroidJUnit4.class) public class SSLConfigurationTests extends StandardIntegrationTest { + // TODO: All tests in this class are currently marked @RunTestInLooperThread, + // this is strictly not necessary, but currently needed to avoid other issues with setting + // up tests. + @Rule public Timeout globalTimeout = Timeout.seconds(120); @Test + @RunTestInLooperThread public void trustedRootCA() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; @@ -84,9 +90,11 @@ public void trustedRootCA() throws InterruptedException { } finally { realm.close(); } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void withoutSSLVerification() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; @@ -127,9 +135,11 @@ public void withoutSSLVerification() throws InterruptedException { } finally { realm.close(); } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void trustedRootCA_syncShouldFailWithoutTrustedCA() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; @@ -168,9 +178,11 @@ public void trustedRootCA_syncShouldFailWithoutTrustedCA() throws InterruptedExc } finally { realm.close(); } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { String username = UUID.randomUUID().toString(); String password = "password"; @@ -193,9 +205,11 @@ public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { testLogger.message); RealmLog.remove(testLogger); RealmLog.setLevel(originalLevel); + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void trustedRootCA_notExisting_certificate_willThrow() { String username = UUID.randomUUID().toString(); String password = "password"; @@ -211,9 +225,11 @@ public void trustedRootCA_notExisting_certificate_willThrow() { fail(); } catch (RealmFileException ignored) { } + looperThread.testComplete(); } @Test + @RunTestInLooperThread public void combiningTrustedRootCA_and_disableSSLVerification() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; @@ -255,6 +271,7 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt } finally { realm.close(); } + looperThread.testComplete(); } // IMPORTANT: Following test assume the root certificate is installed on the test device @@ -262,6 +279,7 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt // adb push /tools/sync_test_server/keys/android_test_certificate.crt /sdcard/ // then import the certificate from the device (Settings/Security/Install from storage) @Test + @RunTestInLooperThread public void sslVerifyCallback_isUsed() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; @@ -301,5 +319,6 @@ public void sslVerifyCallback_isUsed() throws InterruptedException { } finally { realm.close(); } + looperThread.testComplete(); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java index 858824853d..aa720b15de 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java @@ -27,6 +27,8 @@ /** * The standard base class for integration tests. * This class will keep a ROS instance running for all tests to minimize the overhead between each test. + * + * NOTE: All tests extending this class should use `@RunTestInLooperThread` tests. */ public abstract class StandardIntegrationTest extends BaseIntegrationTest { @@ -35,6 +37,11 @@ public static void setupTestClass() throws Exception { startSyncServer(); } + @AfterClass + public static void tearDownTestClass() throws Exception { + stopSyncServer(); + } + @Before public void setupTest() throws IOException { prepareEnvironmentForTest(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index 15d3f2ae49..1696c0f91a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -1,29 +1,31 @@ package io.realm.objectserver; -import android.os.Handler; -import android.os.HandlerThread; import android.support.test.runner.AndroidJUnit4; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import io.realm.DynamicRealm; +import io.realm.OrderedCollectionChangeSet; import io.realm.Realm; +import io.realm.RealmList; import io.realm.RealmResults; import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.SyncUser; -import io.realm.TestHelper; -import io.realm.TestSyncConfigurationFactory; +import io.realm.entities.AllJavaTypes; +import io.realm.entities.AllTypes; +import io.realm.entities.Dog; import io.realm.exceptions.RealmException; import io.realm.objectserver.model.PartialSyncModule; import io.realm.objectserver.model.PartialSyncObjectA; import io.realm.objectserver.model.PartialSyncObjectB; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; +import io.realm.rule.RunTestInLooperThread; import static org.hamcrest.number.OrderingComparison.greaterThan; import static org.junit.Assert.assertEquals; @@ -34,26 +36,90 @@ @RunWith(AndroidJUnit4.class) public class PartialSyncTests extends StandardIntegrationTest { - @Rule - public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); @Test + @RunTestInLooperThread + public void invalidQuery() { + AtomicInteger callbacks = new AtomicInteger(0); + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .partialRealm() + .build(); + + final Realm realm = Realm.getInstance(partialSyncConfig); + looperThread.closeAfterTest(realm); + + // Backlinks not yet supported: https://github.com/realm/realm-core/pull/2947 + RealmResults query = realm.where(AllJavaTypes.class).equalTo("objectParents.fieldString", "Foo").findAllAsync(); + query.addChangeListener((results, changeSet) -> { + switch (callbacks.incrementAndGet()) { + case 1: + assertEquals(OrderedCollectionChangeSet.State.INITIAL, changeSet.getState()); + break; + + case 2: + assertEquals(OrderedCollectionChangeSet.State.ERROR, OrderedCollectionChangeSet.State.ERROR); + assertTrue(changeSet.getError() instanceof IllegalArgumentException); + Throwable iae = changeSet.getError(); + assertTrue(iae.getMessage().contains("ERROR: realm::QueryParser: Key path resolution failed")); + looperThread.testComplete(); + break; + + default: + fail("Unexpected state: " + changeSet.getState()); + } + }); + looperThread.keepStrongReference(query); + } + + // List queries are operating on data that are always up to date as data in a list will + // always be fetched as part of another top-level subscription. Thus `remoteDataLoaded` is + // always true and no queries on them can fail. + @Test + @RunTestInLooperThread + public void listQueries_doNotCreateSubscriptions() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .partialRealm() + .build(); + + final DynamicRealm dRealm = DynamicRealm.getInstance(partialSyncConfig); + final Realm realm = Realm.getInstance(partialSyncConfig); + looperThread.closeAfterTest(dRealm); + looperThread.closeAfterTest(realm); + + realm.beginTransaction(); + RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + list.add(new Dog("Fido")); + list.add(new Dog("Eido")); + realm.commitTransaction(); + + RealmResults query = list.where().sort("name").findAllAsync(); + query.addChangeListener((dogs, changeSet) -> { + assertEquals(OrderedCollectionChangeSet.State.INITIAL, changeSet.getState()); + assertEquals(0, dRealm.where("__ResultSets").count()); + looperThread.testComplete(); + }); + looperThread.keepStrongReference(query); + } + + @Test + @RunTestInLooperThread public void partialSync() throws InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final SyncConfiguration syncConfig = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .waitForInitialRemoteData() .modules(new PartialSyncModule()) .build(); - final SyncConfiguration partialSyncConfig = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .name("partialSync") .modules(new PartialSyncModule()) .partialRealm() .build(); + // Create server data Realm realm = Realm.getInstance(syncConfig); realm.beginTransaction(); PartialSyncObjectA objectA = realm.createObject(PartialSyncObjectA.class); @@ -91,60 +157,135 @@ public void partialSync() throws InterruptedException { realm.createObject(PartialSyncObjectB.class).setNumber(i); } realm.commitTransaction(); - SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); - final CountDownLatch latch = new CountDownLatch(2); + // Download data in partial Realm + final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); + looperThread.closeAfterTest(partialSyncRealm); + assertTrue(partialSyncRealm.isEmpty()); - HandlerThread handlerThread = new HandlerThread("background"); - handlerThread.start(); - Handler handler = new Handler(handlerThread.getLooper()); - handler.post(new Runnable() { - @Override - public void run() { - final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); - assertTrue(partialSyncRealm.isEmpty()); - - partialSyncRealm.subscribeToObjects(PartialSyncObjectA.class, "number > 5", new Realm.PartialSyncCallback() { - - @Override - public void onSuccess(RealmResults results) { - assertEquals(4, results.size()); - for (PartialSyncObjectA object : results) { - assertThat(object.getNumber(), greaterThan(5)); - assertEquals("partial", object.getString()); - } - // make sure the Realm contains only PartialSyncObjectA - assertEquals(0, partialSyncRealm.where(PartialSyncObjectB.class).count()); - latch.countDown(); - } + RealmResults results = partialSyncRealm.where(PartialSyncObjectA.class) + .greaterThan("number", 5) + .findAllAsync(); + looperThread.keepStrongReference(results); - @Override - public void onError(RealmException error) { - fail(error.getMessage()); + results.addChangeListener((partialSyncObjectAS, changeSet) -> { + if (changeSet.isCompleteResult()) { + if (results.size() == 4) { + for (PartialSyncObjectA object : results) { + assertThat(object.getNumber(), greaterThan(5)); + assertEquals("partial", object.getString()); } - }); + // make sure the Realm contains only PartialSyncObjectA + assertEquals(0, partialSyncRealm.where(PartialSyncObjectB.class).count()); + looperThread.testComplete(); + } + } + }); + } - // Invalid query - partialSyncRealm.subscribeToObjects(PartialSyncObjectA.class, "invalid_property > 5", new Realm.PartialSyncCallback() { - @Override - public void onSuccess(RealmResults results) { - fail("Invalid query should not succeed"); - } + @Test + @Deprecated + @RunTestInLooperThread + public void partialSync_oldApi() throws InterruptedException { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - @Override - public void onError(RealmException error) { - assertNotNull(error); - partialSyncRealm.close(); - latch.countDown(); - } - }); + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .waitForInitialRemoteData() + .modules(new PartialSyncModule()) + .build(); + + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .name("partialSync") + .modules(new PartialSyncModule()) + .partialRealm() + .build(); + + Realm realm = Realm.getInstance(syncConfig); + realm.beginTransaction(); + PartialSyncObjectA objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(0); + objectA.setString("realm"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(1); + objectA.setString(""); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(2); + objectA.setString(""); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(3); + objectA.setString(""); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(4); + objectA.setString("realm"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(5); + objectA.setString("sync"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(6); + objectA.setString("partial"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(7); + objectA.setString("partial"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(8); + objectA.setString("partial"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(9); + objectA.setString("partial"); + + for (int i = 0; i < 10; i++) { + realm.createObject(PartialSyncObjectB.class).setNumber(i); + } + realm.commitTransaction(); + + SyncManager.getSession(syncConfig).uploadAllLocalChanges(); + realm.close(); + + AtomicInteger countdown = new AtomicInteger(2); + final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); + looperThread.closeAfterTest(partialSyncRealm); + assertTrue(partialSyncRealm.isEmpty()); + partialSyncRealm.subscribeToObjects(PartialSyncObjectA.class, "number > 5", new Realm.PartialSyncCallback() { + + @Override + public void onSuccess(RealmResults results) { + assertEquals(4, results.size()); + for (PartialSyncObjectA object : results) { + assertThat(object.getNumber(), greaterThan(5)); + assertEquals("partial", object.getString()); + } + // make sure the Realm contains only PartialSyncObjectA + assertEquals(0, partialSyncRealm.where(PartialSyncObjectB.class).count()); + if (countdown.decrementAndGet() == 0) { + looperThread.testComplete(); + } + } + + @Override + public void onError(RealmException error) { + fail(error.getMessage()); } }); - TestHelper.awaitOrFail(latch); + // Invalid query + partialSyncRealm.subscribeToObjects(PartialSyncObjectA.class, "invalid_property > 5", new Realm.PartialSyncCallback() { + + @Override + public void onSuccess(RealmResults results) { + fail("Invalid query should not succeed"); + } + + @Override + public void onError(RealmException error) { + assertNotNull(error); + if (countdown.decrementAndGet() == 0) { + looperThread.testComplete(); + } + } + }); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index dc0ae46830..5a4d22679b 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -37,6 +37,7 @@ public class HttpUtils { // FIXME re-adjust timeout after https://github.com/realm/realm-object-server-private/issues/697 is fixed private final static OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(2, TimeUnit.MINUTES) + .readTimeout(30, TimeUnit.SECONDS) .build(); // adb reverse tcp:8888 tcp:8888 @@ -55,10 +56,6 @@ public static void startSyncServer() throws Exception { Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - - // Work around race condition between starting ROS and logging in first user - // See https://github.com/realm/ros/issues/389 - SystemClock.sleep(2000); } /** From d8f01ff2952fa8ce5fa2511065bc54a634b4c602 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 17 Jan 2018 00:31:22 +0100 Subject: [PATCH 1154/2110] Partial Sync: Add support for named subscriptions (#5669) --- .../main/cpp/io_realm_internal_OsResults.cpp | 7 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../cpp/observable_collection_wrapper.cpp | 54 ++++ .../cpp/observable_collection_wrapper.hpp | 29 +- realm/realm-library/src/main/cpp/util.hpp | 4 + .../src/main/java/io/realm/BaseRealm.java | 12 + .../src/main/java/io/realm/RealmQuery.java | 56 +++- .../src/main/java/io/realm/RealmResults.java | 18 +- .../io/realm/internal/ObjectServerFacade.java | 4 + .../java/io/realm/internal/OsResults.java | 14 +- .../internal/SyncObjectServerFacade.java | 10 + .../realm/objectserver/PartialSyncTests.java | 250 +++++++++++------- .../testUtils/java/io/realm/TestHelper.java | 4 +- 13 files changed, 334 insertions(+), 130 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/observable_collection_wrapper.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index 560eb31f52..c4c69823f0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "java_class_global_def.hpp" #include "java_sort_descriptor.hpp" @@ -240,13 +241,15 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeDistinct(JNIEnv* } JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeStartListening(JNIEnv* env, jobject instance, - jlong native_ptr) + jlong native_ptr, jstring j_subscription_name) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - wrapper->start_listening(env, instance); + JStringAccessor subscription_name(env, j_subscription_name); + auto key = subscription_name.is_null_or_empty() ? util::none : util::Optional(subscription_name); + wrapper->start_listening(env, instance, key); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index c7599df766..af5cfb171d 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit c7599df7661f6b716c66027fc058fff682b45514 +Subproject commit af5cfb171d9ece46d9ae1c40717162fea628dcea diff --git a/realm/realm-library/src/main/cpp/observable_collection_wrapper.cpp b/realm/realm-library/src/main/cpp/observable_collection_wrapper.cpp new file mode 100644 index 0000000000..16876740bb --- /dev/null +++ b/realm/realm-library/src/main/cpp/observable_collection_wrapper.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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 "collection_changeset_wrapper.hpp" +#include "observable_collection_wrapper.hpp" +#include "jni_util/java_class.hpp" +#include "jni_util/java_global_weak_ref.hpp" +#include "jni_util/java_method.hpp" + +#include +#include + +using namespace realm; +using namespace realm::_impl; + +namespace realm { +namespace _impl { + +// Specific override for List that do not support named callbacks for partial sync +template<> +void ObservableCollectionWrapper::start_listening(JNIEnv *env, jobject j_collection_object, util::Optional) { + auto cb = create_callback(env, j_collection_object); + m_notification_token = m_collection.add_notification_callback(cb); +} + +// Specific override for Results that do support named callbacks +template<> +void ObservableCollectionWrapper::start_listening(JNIEnv *env, jobject j_collection_object, + util::Optional subscription_name) { + auto cb = create_callback(env, j_collection_object); + m_notification_token = m_collection.add_notification_callback(cb, subscription_name); +} + +template +void ObservableCollectionWrapper::start_listening(JNIEnv*, jobject, util::Optional) +{ + // Ignore +} + +} // end _impl namespace +} // end realm namespace diff --git a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp index 5c30c65740..3d108d67cd 100644 --- a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp +++ b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp @@ -23,6 +23,9 @@ #include "jni_util/java_method.hpp" #include "jni_util/log.hpp" +#include +#include + namespace realm { namespace _impl { @@ -50,27 +53,32 @@ class ObservableCollectionWrapper { { return m_collection; }; - void start_listening(JNIEnv* env, jobject j_collection_object); + + void start_listening(JNIEnv* env, jobject j_collection_object, util::Optional subscription_name = util::none); void stop_listening(); private: + // Shared logic for creating collection callbacks + CollectionChangeCallback create_callback(JNIEnv *env, jobject j_collection_object); + jni_util::JavaGlobalWeakRef m_collection_weak_ref; NotificationToken m_notification_token; T m_collection; }; template -void ObservableCollectionWrapper::start_listening(JNIEnv* env, jobject j_collection_object) +CollectionChangeCallback ObservableCollectionWrapper::create_callback(JNIEnv *env, jobject j_collection_object) { static jni_util::JavaClass os_results_class(env, "io/realm/internal/ObservableCollection"); - static jni_util::JavaMethod notify_change_listeners(env, os_results_class, "notifyChangeListeners", "(J)V"); + static jni_util::JavaMethod notify_change_listeners(env, os_results_class, + "notifyChangeListeners", "(J)V"); if (!m_collection_weak_ref) { m_collection_weak_ref = jni_util::JavaGlobalWeakRef(env, j_collection_object); } bool partial_sync_realm = m_collection.get_realm()->is_partial(); - auto cb = [=](CollectionChangeSet const& changes, std::exception_ptr err) { + auto cb = [=](CollectionChangeSet const &changes, std::exception_ptr err) { // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; @@ -80,19 +88,22 @@ void ObservableCollectionWrapper::start_listening(JNIEnv* env, jobject j_coll try { std::rethrow_exception(err); } - catch (const std::exception& e) { + catch (const std::exception &e) { error_message = e.what(); } } - m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv* local_env, jobject collection_obj) { + m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv *local_env, + jobject collection_obj) { local_env->CallVoidMethod( - collection_obj, notify_change_listeners, - reinterpret_cast(new CollectionChangeSetWrapper(changes, error_message, partial_sync_realm))); + collection_obj, notify_change_listeners, + reinterpret_cast(new CollectionChangeSetWrapper(changes, + error_message, + partial_sync_realm))); }); }; - m_notification_token = m_collection.add_notification_callback(cb); + return cb; } template diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 0b8106cd10..42071658f0 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -451,6 +451,10 @@ class JStringAccessor { public: JStringAccessor(JNIEnv*, jstring); // throws + bool is_null_or_empty() { + return m_is_null || m_size == 0; + } + operator realm::StringData() const { // To solve the link issue by directly using Table::max_string_size diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 37eddd6371..96c5e8e86b 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -35,6 +35,7 @@ import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; import io.realm.internal.InvalidRow; +import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsObjectStore; import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSchemaInfo; @@ -433,6 +434,17 @@ protected void checkIfInTransaction() { } } + protected void checkIfPartialRealm() { + boolean isPartialRealm = false; + if (configuration.isSyncConfiguration()) { + isPartialRealm = ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(configuration); + } + + if (!isPartialRealm) { + throw new IllegalStateException("This method is only available on partially synchronized Realms."); + } + } + /** * Checks if the Realm is valid and in a transaction. */ diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 21a2e7bc8a..07ed823fb1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -33,6 +33,7 @@ import io.realm.internal.SortDescriptor; import io.realm.internal.Table; import io.realm.internal.TableQuery; +import io.realm.internal.Util; import io.realm.internal.fields.FieldDescriptor; import io.realm.log.RealmLog; @@ -1605,7 +1606,7 @@ public RealmResults distinct(String fieldName) { realm.checkIfValid(); SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), query.getTable(), fieldName); - return createRealmResults(query, null, distinctDescriptor, true); + return createRealmResults(query, null, distinctDescriptor, true, ""); } /** @@ -1629,7 +1630,7 @@ public RealmResults distinctAsync(String fieldName) { realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), query.getTable(), fieldName); - return createRealmResults(query, null, distinctDescriptor, false); + return createRealmResults(query, null, distinctDescriptor, false, ""); } /** @@ -1655,7 +1656,7 @@ public RealmResults distinct(String firstFieldName, String... remainingFieldN fieldNames[0] = firstFieldName; System.arraycopy(remainingFieldNames, 0, fieldNames, 1, remainingFieldNames.length); SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), table, fieldNames); - return createRealmResults(query, null, distinctDescriptor, true); + return createRealmResults(query, null, distinctDescriptor, true, ""); } /** @@ -1822,11 +1823,17 @@ public long count() { public RealmResults findAll() { realm.checkIfValid(); - return createRealmResults(query, sortDescriptor, distinctDescriptor, true); + return createRealmResults(query, sortDescriptor, distinctDescriptor, true, ""); } /** * Finds all objects that fulfill the query conditions. This method is only available from a Looper thread. + *

            + * On partially synchronized Realms, defined by setting {@link SyncConfiguration.Builder#partialRealm()}, + * this method will also create an anonymous subscription that will download all server data matching + * the query. + *

            + * * * @return immediately an empty {@link RealmResults}. Users need to register a listener * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. @@ -1836,9 +1843,33 @@ public RealmResults findAllAsync() { realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - return createRealmResults(query, sortDescriptor, distinctDescriptor, false); + return createRealmResults(query, sortDescriptor, distinctDescriptor, false, ""); + } + + /** + * Finds all objects that fulfill the query condition(s). This method is only available from a Looper thread. + *

            + * This method is only available on partially synchronized Realms and will also create a named subscription + * that will synchronize all server data matching the query. Named subscriptions can be removed again by + * calling {@code Realm.unsubscribe(subscriptionName}. + * + * @return immediately an empty {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * @see io.realm.RealmResults + * @throws IllegalStateException If the Realm is a not a partially synchronized Realm. + */ + public RealmResults findAllAsync(String subscriptionName) { + realm.checkIfValid(); + realm.checkIfPartialRealm(); + if (Util.isEmptyString(subscriptionName)) { + throw new IllegalArgumentException("Non-empty 'subscriptionName' required."); + } + + realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); + return createRealmResults(query, sortDescriptor, distinctDescriptor, false, subscriptionName); } + /** * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String, Sort)} then {@link RealmQuery#findAll()} * @@ -1859,7 +1890,7 @@ public RealmResults findAllAsync() { public RealmResults findAllSorted(String fieldName, Sort sortOrder) { realm.checkIfValid(); SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldName, sortOrder); - return createRealmResults(query, sortDescriptor, null, true); + return createRealmResults(query, sortDescriptor, null, true, ""); } /** @@ -1879,7 +1910,7 @@ public RealmResults findAllSortedAsync(final String fieldName, final Sort sor realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldName, sortOrder); - return createRealmResults(query, sortDescriptor, null, false); + return createRealmResults(query, sortDescriptor, null, false, ""); } /** @@ -2057,7 +2088,7 @@ public RealmResults findAllSorted(String[] fieldNames, Sort[] sortOrders) { realm.checkIfValid(); SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldNames, sortOrders); - return createRealmResults(query, sortDescriptor, null, true); + return createRealmResults(query, sortDescriptor, null, true, ""); } private boolean isDynamicQuery() { @@ -2083,7 +2114,7 @@ public RealmResults findAllSortedAsync(String[] fieldNames, final Sort[] sort realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldNames, sortOrders); - return createRealmResults(query, sortDescriptor, null, false); + return createRealmResults(query, sortDescriptor, null, false, ""); } /** @@ -2202,13 +2233,14 @@ public E findFirstAsync() { private RealmResults createRealmResults(TableQuery query, @Nullable SortDescriptor sortDescriptor, @Nullable SortDescriptor distinctDescriptor, - boolean loadResults) { + boolean loadResults, + String subscriptionName) { RealmResults results; OsResults osResults = OsResults.createFromQuery(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); if (isDynamicQuery()) { - results = new RealmResults<>(realm, osResults, className); + results = new RealmResults<>(realm, osResults, className, subscriptionName); } else { - results = new RealmResults<>(realm, osResults, clazz); + results = new RealmResults<>(realm, osResults, clazz, subscriptionName); } if (loadResults) { results.load(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index de4a3c388f..1bafb8a5c2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -61,6 +61,8 @@ */ public class RealmResults extends OrderedRealmCollectionImpl { + private final String subscriptionName; + // Called from Realm Proxy classes @SuppressLint("unused") static RealmResults createBacklinkResults(BaseRealm realm, Row row, Class srcTableType, String srcFieldName) { @@ -83,11 +85,21 @@ static RealmResults createDynamicBacklinkResults(DynamicReal } RealmResults(BaseRealm realm, OsResults osResults, Class clazz) { - super(realm, osResults, clazz); + this(realm, osResults, clazz, ""); } RealmResults(BaseRealm realm, OsResults osResults, String className) { + this(realm, osResults, className, ""); + } + + RealmResults(BaseRealm realm, OsResults osResults, Class clazz, String subscriptionName) { + super(realm, osResults, clazz); + this.subscriptionName = subscriptionName; + } + + RealmResults(BaseRealm realm, OsResults osResults, String className, String subscriptionName) { super(realm, osResults, className); + this.subscriptionName = subscriptionName; } /** @@ -172,7 +184,7 @@ public boolean load() { */ public void addChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); - osResults.addListener(this, listener); + osResults.addListener(this, listener, subscriptionName); } /** @@ -210,7 +222,7 @@ public void addChangeListener(RealmChangeListener> listener) { */ public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - osResults.addListener(this, listener); + osResults.addListener(this, listener, subscriptionName); } private void checkForAddRemoveListener(@Nullable Object listener, boolean checkListener) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 2e4cce938a..7a88f6beb4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -115,4 +115,8 @@ public void downloadRemoteChanges(RealmConfiguration config) { public boolean wasDownloadInterrupted(Throwable throwable) { return false; } + + public boolean isPartialRealm(RealmConfiguration configuration) { + return false; + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index ca6e8c636e..451b40d176 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -397,15 +397,23 @@ public boolean deleteLast() { } public void addListener(T observer, OrderedRealmCollectionChangeListener listener) { + addListener(observer, listener, ""); + } + + public void addListener(T observer, OrderedRealmCollectionChangeListener listener, String subscriptionName) { if (observerPairs.isEmpty()) { - nativeStartListening(nativePtr); + nativeStartListening(nativePtr, subscriptionName); } CollectionObserverPair collectionObserverPair = new CollectionObserverPair(observer, listener); observerPairs.add(collectionObserverPair); } public void addListener(T observer, RealmChangeListener listener) { - addListener(observer, new RealmChangeListenerWrapper(listener)); + addListener(observer, new RealmChangeListenerWrapper(listener), ""); + } + + public void addListener(T observer, RealmChangeListener listener, String subscriptionName) { + addListener(observer, new RealmChangeListenerWrapper(listener), subscriptionName); } public void removeListener(T observer, OrderedRealmCollectionChangeListener listener) { @@ -506,7 +514,7 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native void nativeDelete(long nativePtr, long index); // Non-static, we need this OsResults object in JNI. - private native void nativeStartListening(long nativePtr); + private native void nativeStartListening(long nativePtr, String subscriptionName); private native void nativeStopListening(long nativePtr); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 2b8616e8d7..b12da634d0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -175,4 +175,14 @@ public void downloadRemoteChanges(RealmConfiguration config) { public boolean wasDownloadInterrupted(Throwable throwable) { return (throwable instanceof DownloadingRealmInterruptedException); } + + @Override + public boolean isPartialRealm(RealmConfiguration configuration) { + if (configuration instanceof SyncConfiguration) { + SyncConfiguration syncConfig = (SyncConfiguration) configuration; + return syncConfig.isPartialRealm(); + } + + return false; + } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index 1696c0f91a..52865de89a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -20,6 +20,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.Dog; import io.realm.exceptions.RealmException; +import io.realm.log.RealmLog; import io.realm.objectserver.model.PartialSyncModule; import io.realm.objectserver.model.PartialSyncObjectA; import io.realm.objectserver.model.PartialSyncObjectB; @@ -52,23 +53,13 @@ public void invalidQuery() { // Backlinks not yet supported: https://github.com/realm/realm-core/pull/2947 RealmResults query = realm.where(AllJavaTypes.class).equalTo("objectParents.fieldString", "Foo").findAllAsync(); query.addChangeListener((results, changeSet) -> { - switch (callbacks.incrementAndGet()) { - case 1: - assertEquals(OrderedCollectionChangeSet.State.INITIAL, changeSet.getState()); - break; - - case 2: - assertEquals(OrderedCollectionChangeSet.State.ERROR, OrderedCollectionChangeSet.State.ERROR); - assertTrue(changeSet.getError() instanceof IllegalArgumentException); - Throwable iae = changeSet.getError(); - assertTrue(iae.getMessage().contains("ERROR: realm::QueryParser: Key path resolution failed")); - looperThread.testComplete(); - break; - - default: - fail("Unexpected state: " + changeSet.getState()); - } - }); + if (changeSet.getState() == OrderedCollectionChangeSet.State.ERROR) { + assertTrue(changeSet.getError() instanceof IllegalArgumentException); + Throwable iae = changeSet.getError(); + assertTrue(iae.getMessage().contains("ERROR: realm::QueryParser: Key path resolution failed")); + looperThread.testComplete(); + } + }); looperThread.keepStrongReference(query); } @@ -105,7 +96,7 @@ public void listQueries_doNotCreateSubscriptions() { @Test @RunTestInLooperThread - public void partialSync() throws InterruptedException { + public void anonymousSubscription() throws InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) @@ -119,46 +110,7 @@ public void partialSync() throws InterruptedException { .partialRealm() .build(); - // Create server data - Realm realm = Realm.getInstance(syncConfig); - realm.beginTransaction(); - PartialSyncObjectA objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(0); - objectA.setString("realm"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(1); - objectA.setString(""); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(2); - objectA.setString(""); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(3); - objectA.setString(""); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(4); - objectA.setString("realm"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(5); - objectA.setString("sync"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(6); - objectA.setString("partial"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(7); - objectA.setString("partial"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(8); - objectA.setString("partial"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(9); - objectA.setString("partial"); - - for (int i = 0; i < 10; i++) { - realm.createObject(PartialSyncObjectB.class).setNumber(i); - } - realm.commitTransaction(); - SyncManager.getSession(syncConfig).uploadAllLocalChanges(); - realm.close(); + createServerData(syncConfig); // Download data in partial Realm final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); @@ -185,11 +137,9 @@ public void partialSync() throws InterruptedException { }); } - @Test - @Deprecated @RunTestInLooperThread - public void partialSync_oldApi() throws InterruptedException { + public void namedSubscription() throws InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) @@ -203,46 +153,106 @@ public void partialSync_oldApi() throws InterruptedException { .partialRealm() .build(); - Realm realm = Realm.getInstance(syncConfig); - realm.beginTransaction(); - PartialSyncObjectA objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(0); - objectA.setString("realm"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(1); - objectA.setString(""); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(2); - objectA.setString(""); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(3); - objectA.setString(""); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(4); - objectA.setString("realm"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(5); - objectA.setString("sync"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(6); - objectA.setString("partial"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(7); - objectA.setString("partial"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(8); - objectA.setString("partial"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(9); - objectA.setString("partial"); + createServerData(syncConfig); - for (int i = 0; i < 10; i++) { - realm.createObject(PartialSyncObjectB.class).setNumber(i); + // Download data in partial Realm + final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); + looperThread.closeAfterTest(partialSyncRealm); + assertTrue(partialSyncRealm.isEmpty()); + + RealmResults results = partialSyncRealm.where(PartialSyncObjectA.class) + .greaterThan("number", 5) + .findAllAsync("my-subscription-id"); + looperThread.keepStrongReference(results); + + results.addChangeListener((partialSyncObjectAS, changeSet) -> { + if (changeSet.isCompleteResult()) { + if (results.size() == 4) { + for (PartialSyncObjectA object : results) { + assertThat(object.getNumber(), greaterThan(5)); + assertEquals("partial", object.getString()); + } + // make sure the Realm contains only PartialSyncObjectA + assertEquals(0, partialSyncRealm.where(PartialSyncObjectB.class).count()); + looperThread.testComplete(); + } + } + }); + + } + + @Test + @RunTestInLooperThread + public void partialSync_namedSubscriptionThrowsOnNonPartialRealms() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + final SyncConfiguration fullSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .name("fullySynchronizedRealm") + .build(); + + Realm realm = Realm.getInstance(fullSyncConfig); + looperThread.closeAfterTest(realm); + + try { + realm.where(PartialSyncObjectA.class).findAllAsync("my-id"); + fail(); + } catch (IllegalStateException ignore) { + looperThread.testComplete(); } - realm.commitTransaction(); + } - SyncManager.getSession(syncConfig).uploadAllLocalChanges(); - realm.close(); + @Test + @RunTestInLooperThread + public void partialSync_namedSubscription_namedConflictThrows() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .name("partialSync") + .modules(new PartialSyncModule()) + .partialRealm() + .build(); + + Realm realm = Realm.getInstance(partialSyncConfig); + looperThread.closeAfterTest(realm); + + RealmResults results1 = realm.where(PartialSyncObjectA.class) + .greaterThan("number", 0) // FIXME: Work-around Query serializer not accepting empty query for now + .findAllAsync("my-id"); + results1.addChangeListener((results, changeSet) -> { + // Ignore. Just used to trigger partial sync path + }); + + RealmResults results2 = realm.where(PartialSyncObjectB.class) + .greaterThan("number", 0) // FIXME: Work-around Query serializer not accepting empty query for now + .findAllAsync("my-id"); + results2.addChangeListener((results, changeSet) -> { + if (changeSet.getState() == OrderedCollectionChangeSet.State.ERROR) { + assertEquals(OrderedCollectionChangeSet.State.ERROR, changeSet.getState()); + assertTrue(changeSet.getError() instanceof IllegalArgumentException); + looperThread.testComplete(); + } + }); + + looperThread.keepStrongReference(results1); + looperThread.keepStrongReference(results2); + } + + @Test + @Deprecated + @RunTestInLooperThread + public void partialSync_oldApi() throws InterruptedException { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .waitForInitialRemoteData() + .modules(new PartialSyncModule()) + .build(); + + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .name("partialSync") + .modules(new PartialSyncModule()) + .partialRealm() + .build(); + + createServerData(syncConfig); AtomicInteger countdown = new AtomicInteger(2); final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); @@ -288,4 +298,48 @@ public void onError(RealmException error) { } }); } + + private void createServerData(SyncConfiguration syncConfig) throws InterruptedException { + // Create server data + // Create server data + Realm realm = Realm.getInstance(syncConfig); + realm.beginTransaction(); + PartialSyncObjectA objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(0); + objectA.setString("realm"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(1); + objectA.setString(""); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(2); + objectA.setString(""); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(3); + objectA.setString(""); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(4); + objectA.setString("realm"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(5); + objectA.setString("sync"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(6); + objectA.setString("partial"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(7); + objectA.setString("partial"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(8); + objectA.setString("partial"); + objectA = realm.createObject(PartialSyncObjectA.class); + objectA.setNumber(9); + objectA.setString("partial"); + + for (int i = 0; i < 10; i++) { + realm.createObject(PartialSyncObjectB.class).setNumber(i); + } + realm.commitTransaction(); + SyncManager.getSession(syncConfig).uploadAllLocalChanges(); + realm.close(); + } } diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index a765dc60c2..b1d2d2f15a 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -1006,10 +1006,10 @@ public static RealmResults newRealmResults( //noinspection TryWithIdenticalCatches try { final Constructor c = RealmResults.class.getDeclaredConstructor( - BaseRealm.class, OsResults.class, Class.class); + BaseRealm.class, OsResults.class, Class.class, String.class); c.setAccessible(true); //noinspection unchecked - return c.newInstance(realm, osResults, tableClass); + return c.newInstance(realm, osResults, tableClass, ""); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InstantiationException e) { From 48cf4adaa462246085f1650021d86d0f3f2e53b5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 17 Jan 2018 14:55:56 +0100 Subject: [PATCH 1155/2110] Partial Sync: Support unsubscribe (#5686) --- CHANGELOG.md | 2 +- .../java/io/realm/SyncedRealmTests.java | 168 ++++++++++++++++++ .../src/main/java/io/realm/Realm.java | 89 ++++++++++ ....java => SyncedRealmIntegrationTests.java} | 2 +- .../realm/objectserver/PartialSyncTests.java | 147 +++++++++------ .../suite/IntegrationTestSuite.java | 4 +- 6 files changed, 356 insertions(+), 56 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java rename realm/realm-library/src/syncIntegrationTest/java/io/realm/{SyncedRealmTests.java => SyncedRealmIntegrationTests.java} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 952697e6c2..555817dcaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Enhancements -* Added support for partial Realms. Read [here](https://realm.io/docs/java/latest/#partial-realms) for more information. +* [ObjectServer] Added support for partial Realms. Read [here](https://realm.io/docs/java/latest/#partial-realms) for more information. * Added two new methods to `OrderedCollectionChangeSet`: `getState()` and `getError()` (#5619). ### Internal diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java new file mode 100644 index 0000000000..8d9cfea755 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -0,0 +1,168 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.util.SyncTestUtils; + +import static org.junit.Assert.fail; + +/** + * Testing sync specific methods on {@link Realm}. + */ +@RunWith(AndroidJUnit4.class) +public class SyncedRealmTests { + + @Rule + public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Realm realm; + + @After + public void tearDown() { + if (realm != null && !realm.isClosed()) { + realm.close(); + } + } + + private Realm getNormalRealm() { + RealmConfiguration config = configFactory.createConfiguration(); + realm = Realm.getInstance(config); + return realm; + } + + private Realm getPartialRealm() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/fullsync") + .partialRealm() + .build(); + realm = Realm.getInstance(config); + return realm; + } + + private Realm getFullySyncRealm() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/fullsync") + .build(); + realm = Realm.getInstance(config); + return realm; + } + + @Test + public void unsubscribeAsync_nullOrEmptyArgumentsThrows() { + Realm realm = getPartialRealm(); + Realm.UnsubscribeCallback callback = new Realm.UnsubscribeCallback() { + @Override + public void onSuccess(String subscriptionName) { + } + + @Override + public void onError(String subscriptionName, Throwable error) { + } + }; + + try { + //noinspection ConstantConditions + realm.unsubscribeAsync(null, callback); + fail(); + } catch (IllegalArgumentException ignore) { + } + + try { + realm.unsubscribeAsync("", callback); + fail(); + } catch (IllegalArgumentException ignore) { + } + + try { + //noinspection ConstantConditions + realm.unsubscribeAsync("my-id", null); + fail(); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void unsubscribeAsync_nonLooperThreadThrows() { + Realm realm = getPartialRealm(); + Realm.UnsubscribeCallback callback = new Realm.UnsubscribeCallback() { + @Override + public void onSuccess(String subscriptionName) { + } + + @Override + public void onError(String subscriptionName, Throwable error) { + } + }; + + try { + //noinspection ConstantConditions + realm.unsubscribeAsync("my-id", callback); + fail(); + } catch (IllegalStateException ignore) { + } + } + + @Test + @RunTestInLooperThread + public void unsubscribeAsync_nonPartialRealmThrows() { + Realm.UnsubscribeCallback callback = new Realm.UnsubscribeCallback() { + @Override + public void onSuccess(String subscriptionName) { + } + + @Override + public void onError(String subscriptionName, Throwable error) { + } + }; + + Realm realm = getNormalRealm(); + try { + //noinspection ConstantConditions + realm.unsubscribeAsync("my-id", callback); + fail(); + } catch (UnsupportedOperationException ignore) { + } finally { + realm.close(); + } + + realm = getFullySyncRealm(); + try { + //noinspection ConstantConditions + realm.unsubscribeAsync("my-id", callback); + fail(); + } catch (UnsupportedOperationException ignore) { + } finally { + realm.close(); + } + + looperThread.testComplete(); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 194540d5f6..b6a701c383 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -54,6 +54,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnIndices; +import io.realm.internal.NativeObject; import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsObject; import io.realm.internal.OsObjectSchemaInfo; @@ -66,6 +67,8 @@ import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; +import io.realm.internal.TableQuery; +import io.realm.internal.Util; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; @@ -1697,6 +1700,70 @@ public static boolean compactRealm(RealmConfiguration configuration) { return BaseRealm.compactRealm(configuration); } + /** + * Cancel a named subscription that was created by calling {@link RealmQuery#findAllAsync(String)}. + * If after this, some objects are no longer part of any active subscription they will be removed + * locally from the device (but not on the server). + * + * The effect of unsubscribing is not immediate. The local Realm must coordinate with the Object + * Server before this can happen. A successful callback just indicate that the request was + * succesfully enqueued and any data will be removed as soon as possible. When the data is + * actually removed locally, a standard change notification will be triggered and from the + * perspective of the device it will look like the data was deleted. + * + * @param subscriptionName name of the subscription to remove + * @param callback callback reporting back if the intent to unsubscribe was enqueued successfully or failed. + * @return a {@link RealmAsyncTask} representing a cancellable task. + * @throws IllegalArgumentException if no {@code subscriptionName} or {@code callback} was provided. + * @throws IllegalStateException if called on a non-looper thread. + * @throws UnsupportedOperationException if the Realm is not a partially synchronized Realm. + */ + @Beta + public RealmAsyncTask unsubscribeAsync(String subscriptionName, Realm.UnsubscribeCallback callback) { + if (Util.isEmptyString(subscriptionName)) { + throw new IllegalArgumentException("Non-empty 'subscriptionName' required."); + } + //noinspection ConstantConditions + if (callback == null) { + throw new IllegalArgumentException("'callback' required."); + } + sharedRealm.capabilities.checkCanDeliverNotification("This method is only available from a Looper thread."); + if (!ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(configuration)) { + throw new UnsupportedOperationException("Realm is not a partially synchronized Realm: " + configuration.getPath()); + } + + return executeTransactionAsync(new Transaction() { + @Override + public void execute(Realm realm) { + + // Need to manually run a dynamic query here. + // TODO Add support for DynamicRealm.executeTransactionAsync() + Table table = realm.sharedRealm.getTable("class___ResultSets"); + TableQuery query = table.where() + .equalTo(new long[]{table.getColumnIndex("name")}, new long[]{NativeObject.NULLPTR}, subscriptionName); + + OsResults result = OsResults.createFromQuery(realm.sharedRealm, query); + long count = result.size(); + if (count == 0) { + throw new IllegalArgumentException("No active subscription named '"+ subscriptionName +"' exists."); + } + if (count > 1) { + RealmLog.warn("Multiple subscriptions named '" + subscriptionName + "' exists. This should not be possible. They will all be deleted"); + } + result.clear(); + } + }, new Transaction.OnSuccess() { + @Override + public void onSuccess() { + callback.onSuccess(subscriptionName); + } + }, new Transaction.OnError() { + @Override + public void onError(Throwable error) { + callback.onError(subscriptionName, error); + } + }); + } /** * If the Realm is a partially synchronized Realm, fetch and synchronize the objects of a given * object type that match the given query (in string format). @@ -1833,6 +1900,28 @@ interface OnError { } } + /** + * Interface used when canceling partial sync subscriptions. + * + * @see #unsubscribeAsync(String, UnsubscribeCallback) + */ + public interface UnsubscribeCallback { + /** + * Callback invoked when the request to unsubscribe was succesfully enqueued. + * + * @param subscriptionName subscription that was canceled. + */ + void onSuccess(String subscriptionName); + + /** + * Callback invoked if an error happened while trying to unsubscribe. + * + * @param subscriptionName subscription on which the error occurred. + * @param error cause of error. + */ + void onError(String subscriptionName, Throwable error); + } + /** * {@inheritDoc} */ diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java similarity index 99% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java rename to realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 7eaa41f60a..572312ecba 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -47,7 +47,7 @@ * Catch all class for tests that not naturally fit anywhere else. */ @RunWith(AndroidJUnit4.class) -public class SyncedRealmTests extends StandardIntegrationTest { +public class SyncedRealmIntegrationTests extends StandardIntegrationTest { @Test @UiThreadTest diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index 52865de89a..b95d62ca89 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -9,7 +9,9 @@ import io.realm.DynamicRealm; import io.realm.OrderedCollectionChangeSet; +import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Realm; +import io.realm.RealmChangeListener; import io.realm.RealmList; import io.realm.RealmResults; import io.realm.StandardIntegrationTest; @@ -38,15 +40,15 @@ @RunWith(AndroidJUnit4.class) public class PartialSyncTests extends StandardIntegrationTest { + private static final int TEST_SIZE = 10; + @Test @RunTestInLooperThread public void invalidQuery() { - AtomicInteger callbacks = new AtomicInteger(0); SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .partialRealm() .build(); - final Realm realm = Realm.getInstance(partialSyncConfig); looperThread.closeAfterTest(realm); @@ -98,22 +100,10 @@ public void listQueries_doNotCreateSubscriptions() { @RunTestInLooperThread public void anonymousSubscription() throws InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .waitForInitialRemoteData() - .modules(new PartialSyncModule()) - .build(); - - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .name("partialSync") - .modules(new PartialSyncModule()) - .partialRealm() - .build(); - - createServerData(syncConfig); + createServerData(user, Constants.SYNC_SERVER_URL); // Download data in partial Realm - final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); + final Realm partialSyncRealm = getPartialRealm(user); looperThread.closeAfterTest(partialSyncRealm); assertTrue(partialSyncRealm.isEmpty()); @@ -141,22 +131,10 @@ public void anonymousSubscription() throws InterruptedException { @RunTestInLooperThread public void namedSubscription() throws InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .waitForInitialRemoteData() - .modules(new PartialSyncModule()) - .build(); - - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .name("partialSync") - .modules(new PartialSyncModule()) - .partialRealm() - .build(); - - createServerData(syncConfig); + createServerData(user, Constants.SYNC_SERVER_URL); // Download data in partial Realm - final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); + final Realm partialSyncRealm = getPartialRealm(user); looperThread.closeAfterTest(partialSyncRealm); assertTrue(partialSyncRealm.isEmpty()); @@ -204,13 +182,7 @@ public void partialSync_namedSubscriptionThrowsOnNonPartialRealms() { @RunTestInLooperThread public void partialSync_namedSubscription_namedConflictThrows() { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .name("partialSync") - .modules(new PartialSyncModule()) - .partialRealm() - .build(); - - Realm realm = Realm.getInstance(partialSyncConfig); + Realm realm = getPartialRealm(user); looperThread.closeAfterTest(realm); RealmResults results1 = realm.where(PartialSyncObjectA.class) @@ -236,26 +208,83 @@ public void partialSync_namedSubscription_namedConflictThrows() { } @Test - @Deprecated @RunTestInLooperThread - public void partialSync_oldApi() throws InterruptedException { + public void unsubscribeAsync() throws InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + createServerData(user, Constants.SYNC_SERVER_URL); + Realm realm = getPartialRealm(user); + looperThread.closeAfterTest(realm); - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .waitForInitialRemoteData() - .modules(new PartialSyncModule()) - .build(); + final String subscriptionName = "my-objects"; + RealmResults r = realm.where(PartialSyncObjectB.class) + .greaterThan("number", 0) + .findAllAsync(subscriptionName); - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .name("partialSync") - .modules(new PartialSyncModule()) - .partialRealm() - .build(); + r.addChangeListener((results, changeSet) -> { + if (changeSet.isCompleteResult()) { + // 1. Partial sync downloaded all expected objects + assertEquals(TEST_SIZE - 1, results.size()); + r.removeAllChangeListeners(); + + // 2. Attempt to remove them again + realm.unsubscribeAsync(subscriptionName, new Realm.UnsubscribeCallback() { + @Override + public void onSuccess(String subscriptionName) { + assertEquals(subscriptionName, subscriptionName); + + // Use global Realm change listener to avoid re-subscribing + realm.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(Realm realm) { + // Eventually they should be removed + if (realm.where(PartialSyncObjectB.class).count() == 0) { + looperThread.testComplete(); + } + } + }); + } + + @Override + public void onError(String subscriptionName, Throwable error) { + fail(error.toString()); + } + }); + } + }); + } + + @Test + @RunTestInLooperThread + public void unsubscribeAsync_nonExistingIdThrows() throws InterruptedException { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + Realm realm = getPartialRealm(user); + looperThread.closeAfterTest(realm); - createServerData(syncConfig); + realm.unsubscribeAsync("i-dont-exist", new Realm.UnsubscribeCallback() { + @Override + public void onSuccess(String subscriptionName) { + fail(); + } + + @Override + public void onError(String subscriptionName, Throwable error) { + assertEquals("i-dont-exist", subscriptionName); + assertTrue(error instanceof IllegalArgumentException); + assertTrue(error.getMessage().contains("No active subscription named")); + looperThread.testComplete(); + } + }); + } + + @Test + @Deprecated + @RunTestInLooperThread + public void partialSync_oldApi() throws InterruptedException { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + createServerData(user, Constants.SYNC_SERVER_URL); AtomicInteger countdown = new AtomicInteger(2); - final Realm partialSyncRealm = Realm.getInstance(partialSyncConfig); + final Realm partialSyncRealm = getPartialRealm(user); looperThread.closeAfterTest(partialSyncRealm); assertTrue(partialSyncRealm.isEmpty()); @@ -299,7 +328,21 @@ public void onError(RealmException error) { }); } - private void createServerData(SyncConfiguration syncConfig) throws InterruptedException { + private Realm getPartialRealm(SyncUser user) { + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .name("partialSync") + .modules(new PartialSyncModule()) + .partialRealm() + .build(); + return Realm.getInstance(partialSyncConfig); + } + + private void createServerData(SyncUser user, String url) throws InterruptedException { + final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, url) + .waitForInitialRemoteData() + .modules(new PartialSyncModule()) + .build(); + // Create server data // Create server data Realm realm = Realm.getInstance(syncConfig); @@ -335,7 +378,7 @@ private void createServerData(SyncConfiguration syncConfig) throws InterruptedEx objectA.setNumber(9); objectA.setString("partial"); - for (int i = 0; i < 10; i++) { + for (int i = 0; i < TEST_SIZE; i++) { realm.createObject(PartialSyncObjectB.class).setNumber(i); } realm.commitTransaction(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java index d13f3546d1..6f21b0135c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java @@ -21,7 +21,7 @@ import org.junit.runners.Suite; import io.realm.SSLConfigurationTests; -import io.realm.SyncedRealmTests; +import io.realm.SyncedRealmIntegrationTests; import io.realm.objectserver.AuthTests; import io.realm.objectserver.EncryptedSynchronizedRealmTests; import io.realm.objectserver.ProcessCommitTests; @@ -32,7 +32,7 @@ @RunWith(Suite.class) @Suite.SuiteClasses({ SSLConfigurationTests.class, - SyncedRealmTests.class, + SyncedRealmIntegrationTests.class, AuthTests.class, EncryptedSynchronizedRealmTests.class, ProcessCommitTests.class, From b260f91d1203f6862152dce767f126b9d7575628 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 17 Jan 2018 14:10:31 +0000 Subject: [PATCH 1156/2110] Nh/ros test tiemout (#5689) * Some Integration tests are running twice, checking if the TestSuite is causing this * Increase the start ROS connection read timeout * add logging to find out when ROS fails to start * adjusting ROS startup timeout * Fixed a pipline that causes all ABIs to build, despite selecting just one ( branch for example) --- Jenkinsfile | 2 +- .../suite/IntegrationTestSuite.java | 42 ------------------- .../realm/objectserver/utils/HttpUtils.java | 7 +--- tools/sync_test_server/ros-testing-server.js | 7 ++-- 4 files changed, 7 insertions(+), 51 deletions(-) delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java diff --git a/Jenkinsfile b/Jenkinsfile index e2f45c0704..fec751058f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -87,7 +87,7 @@ try { stage('Static code analysis') { try { - gradle('realm', 'findbugs pmd checkstyle') + gradle('realm', 'findbugs pmd checkstyle -PbuildTargetABIs=${ABIs}') } finally { publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java deleted file mode 100644 index d13f3546d1..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/suite/IntegrationTestSuite.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.objectserver.suite; - - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -import io.realm.SSLConfigurationTests; -import io.realm.SyncedRealmTests; -import io.realm.objectserver.AuthTests; -import io.realm.objectserver.EncryptedSynchronizedRealmTests; -import io.realm.objectserver.ProcessCommitTests; -import io.realm.objectserver.ProgressListenerTests; -import io.realm.SyncSessionTests; - -// Test suite includes all integration tests. Makes it easy to run all integration tests in the Android Studio. -@RunWith(Suite.class) -@Suite.SuiteClasses({ - SSLConfigurationTests.class, - SyncedRealmTests.class, - AuthTests.class, - EncryptedSynchronizedRealmTests.class, - ProcessCommitTests.class, - ProgressListenerTests.class, - SyncSessionTests.class}) -public class IntegrationTestSuite { -} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index dc0ae46830..e456364cf0 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -36,7 +36,8 @@ public class HttpUtils { // "Realm could not be deleted errors". // FIXME re-adjust timeout after https://github.com/realm/realm-object-server-private/issues/697 is fixed private final static OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(2, TimeUnit.MINUTES) + .connectTimeout(40, TimeUnit.SECONDS) + .readTimeout(40, TimeUnit.SECONDS)// since ROS startup timeout is 30s .build(); // adb reverse tcp:8888 tcp:8888 @@ -55,10 +56,6 @@ public static void startSyncServer() throws Exception { Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - - // Work around race condition between starting ROS and logging in first user - // See https://github.com/realm/ros/issues/389 - SystemClock.sleep(2000); } /** diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js index bdf1938075..25561bbb7e 100755 --- a/tools/sync_test_server/ros-testing-server.js +++ b/tools/sync_test_server/ros-testing-server.js @@ -50,7 +50,7 @@ function waitForRosToInitialize(attempts, onSuccess, onError, startSequence) { } http.get("http://0.0.0.0:9080/health", function(res) { if (res.statusCode != 200) { - winston.info("ROS /health/ returned: " + res.statusCode) + winston.warn("ROS /health/ returned: " + res.statusCode) setTimeout(function() { waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); }, 500); @@ -58,6 +58,7 @@ function waitForRosToInitialize(attempts, onSuccess, onError, startSequence) { onSuccess(startSequence); } }).on('error', function(err) { + winston.warn("ROS /health/ returned an error: " + err) // ROS not accepting any connections yet. // Errors like ECONNREFUSED 0.0.0.0:9080 will be reported here. // Wait a little before trying again (common startup is ~1 second). @@ -114,8 +115,8 @@ function doStartRealmObjectServer(onSuccess, onError) { winston.info(`${data}`); }); - // The interval between every health check is 0.5 second. Give the ROS 15 seconds to get fully initialized. - waitForRosToInitialize(30, onSuccess, onError, Date.now()); + // The interval between every health check is 0.5 second. Give the ROS 30 seconds to get fully initialized. + waitForRosToInitialize(60, onSuccess, onError, Date.now()); } }); } From f77678da639d9f6d2b7aacfad8d3aec4001fee99 Mon Sep 17 00:00:00 2001 From: Maximilian Alexander Date: Wed, 17 Jan 2018 10:55:39 -0800 Subject: [PATCH 1157/2110] adding nickname and anoymous auth (#5673) * adding nickname and anonymous * editing changelog and adding tests * Add integration test for anonymous and nickname credentials * update to latest ROS for tests * Using latest OS --- CHANGELOG.md | 5 ++ dependencies.list | 2 +- .../java/io/realm/CredentialsTests.java | 28 ++++++ realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/SyncCredentials.java | 46 +++++++++- .../java/io/realm/objectserver/AuthTests.java | 86 +++++++++++++++++-- 6 files changed, 160 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 520fe2ec18..9c52c171d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ * Prevent Realms Gradle plugin from transitively forcing specific versions of Google Build Tools onto downstream projects (#5640). * [ObjectServer] logging a warning message instead of throwing an exception, when sync report an unknown error code (#5403). +### Enhancements + +* [ObjectServer] added support for both Anonymous and Nickname authentication. + + ### Internal * Upgraded to Realm Sync 2.2.9 diff --git a/dependencies.list b/dependencies.list index da2d4d0168..bd4aa3ff41 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,5 +5,5 @@ REALM_SYNC_SHA256=d770d639d2b187c15e6d0bc798b909f5b424d61444f1f83f9e56f5be43e96a # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.1.0 +REALM_OBJECT_SERVER_DE_VERSION=2.6.0 diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java index 41e9115fb0..d16d0f5bc7 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java @@ -24,6 +24,7 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -107,6 +108,33 @@ public void jwt_invalidInput() { } } + @Test + public void anonymous() { + SyncCredentials creds = SyncCredentials.anonymous(); + assertEquals(SyncCredentials.IdentityProvider.ANONYMOUS, creds.getIdentityProvider()); + assertTrue(creds.getUserInfo().isEmpty()); + } + + @Test + public void nickname() { + SyncCredentials creds = SyncCredentials.nickname("foo", false); + assertEquals(SyncCredentials.IdentityProvider.NICKNAME, creds.getIdentityProvider()); + assertFalse(creds.getUserInfo().isEmpty()); + assertFalse((Boolean) creds.getUserInfo().get("is_admin")); + } + + @Test + public void nickname_invalidInput() { + String[] invalidInput = {null, ""}; + for (String input : invalidInput) { + try { + SyncCredentials.nickname(input, false); + fail(input + " should have failed"); + } catch (IllegalArgumentException ignored) { + } + } + } + @Test public void usernamePassword_register() { SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar", true); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 8517ee7f43..9aab0ffd5b 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 8517ee7f4378fe0f54945b3e4973766ff65e455d +Subproject commit 9aab0ffd5bc7bfc438ec28375ba581cf732f57ee diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 0ff32a6bfe..560bafff7a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -22,6 +22,8 @@ import javax.annotation.Nullable; +import io.realm.internal.Util; + /** * Credentials represent a login with a 3rd party login provider in an OAuth2 login flow, and are used by the Realm @@ -110,6 +112,38 @@ public static SyncCredentials jwt(String jwtToken) { return new SyncCredentials(jwtToken, IdentityProvider.JWT, null); } + /** + * Creates credentials anonymously. + * + * Note: logging the user out again means that data is lost with no means of recovery + * and it isn't possible to share the user details across devices. + * + * @return a set of credentials that can be used to log into the Object Server using + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + */ + public static SyncCredentials anonymous() { + return new SyncCredentials("", IdentityProvider.ANONYMOUS, null); + } + + /** + * Creates credentials using a nickname. + * + * Note: This is mainly intended for demo/test, since it's ie. possible to log user + * in by just knowing their "nickname" (no password required). + * This provider should not be used in production. + * + * @param nickname that identifies a user + * @return a set of credentials that can be used to log into the Object Server using + * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * @throws IllegalArgumentException if the nickname is either {@code null} or empty. + */ + public static SyncCredentials nickname(String nickname, boolean isAdmin) { + assertStringNotEmpty(nickname, "nickname"); + Map userInfo = new HashMap(); + userInfo.put("is_admin", isAdmin); + return new SyncCredentials(nickname, IdentityProvider.NICKNAME, userInfo); + } + /** * Creates credentials based on a login with username and password. These credentials will only be verified * by the Object Server. @@ -207,7 +241,7 @@ public static SyncCredentials accessToken(String accessToken, String identifier, private static void assertStringNotEmpty(String string, String message) { //noinspection ConstantConditions - if (string == null || "".equals(string)) { + if (Util.isEmptyString(string)) { throw new IllegalArgumentException("Non-null '" + message + "' required."); } } @@ -282,6 +316,16 @@ public static final class IdentityProvider { */ public static final String JWT = "jwt"; + /** + * Credentials do not require user/password (anonymous user). + */ + public static final String ANONYMOUS = "anonymous"; + + /** + * Credentials will be verified with a nickname. + */ + public static final String NICKNAME = "nickname"; + /** * Credentials will be verified by the Object Server. * diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 30555e9628..09f4c8a275 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -34,6 +34,7 @@ import io.realm.SyncUserInfo; import io.realm.TestHelper; import io.realm.entities.StringOnly; +import io.realm.internal.Util; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.objectserver.Token; import io.realm.objectserver.utils.Constants; @@ -123,12 +124,7 @@ public void login_withAccessToken() { public void onSuccess(SyncUser user) { assertTrue(user.isAdmin()); final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail("Session failed: " + error); - } - }) + .errorHandler((session, error) -> fail("Session failed: " + error)) .build(); final Realm realm = Realm.getInstance(config); @@ -144,6 +140,84 @@ public void onError(ObjectServerError error) { }); } + @Test + @RunTestInLooperThread + public void login_withAnonymous() { + SyncCredentials credentials = SyncCredentials.anonymous(); + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser user) { + assertFalse(user.isAdmin()); + final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL) + .errorHandler((session, error) -> fail("Session failed: " + error)) + .build(); + + final Realm realm = Realm.getInstance(config); + looperThread.addTestRealm(realm); + assertFalse(Util.isEmptyString(config.getUser().getIdentity())); + assertTrue(config.getUser().isValid()); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail("Login failed: " + error); + } + }); + } + + @Test + @RunTestInLooperThread + public void login_withNickname() { + SyncCredentials credentials = SyncCredentials.nickname("foo", false); + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser user) { + assertFalse(user.isAdmin()); + final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL) + .errorHandler((session, error) -> fail("Session failed: " + error)) + .build(); + + final Realm realm = Realm.getInstance(config); + looperThread.addTestRealm(realm); + assertFalse(Util.isEmptyString(config.getUser().getIdentity())); + assertTrue(config.getUser().isValid()); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail("Login failed: " + error); + } + }); + } + + @Test + @RunTestInLooperThread + public void login_withNicknameAsAdmin() { + SyncCredentials credentials = SyncCredentials.nickname("foo", true); + SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(SyncUser user) { + assertTrue(user.isAdmin()); + final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL) + .errorHandler((session, error) -> fail("Session failed: " + error)) + .build(); + + final Realm realm = Realm.getInstance(config); + looperThread.addTestRealm(realm); + assertFalse(Util.isEmptyString(config.getUser().getIdentity())); + assertTrue(config.getUser().isValid()); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail("Login failed: " + error); + } + }); + } + @Test public void loginAsync_errorHandlerThrows() throws InterruptedException { final AtomicBoolean errorThrown = new AtomicBoolean(false); From 47484e9e0de08bca996ae8b70218ad9a53d2ff08 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 17 Jan 2018 19:10:55 +0000 Subject: [PATCH 1158/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c52c171d3..8712e4528a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.3.2 (YYYY-MM-DD) +## 4.3.2 (2018-01-17) ### Bug Fixes From e64b847e34f1b03441e555cc4cc994d74b1f7d7d Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 17 Jan 2018 19:10:57 +0000 Subject: [PATCH 1159/2110] Release v4.3.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b2595557b0..7e961f9e14 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.2-SNAPSHOT \ No newline at end of file +4.3.2 \ No newline at end of file From 7fff5047deeb883a4008f9d88f351a3790acece5 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 17 Jan 2018 19:10:57 +0000 Subject: [PATCH 1160/2110] Prepare next release v4.3.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7e961f9e14..b7a5918b61 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.2 \ No newline at end of file +4.3.3-SNAPSHOT \ No newline at end of file From c37dab9b9b2e7d8c1be8b2c9d6a4abc677e1d6be Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Thu, 18 Jan 2018 06:03:18 +0800 Subject: [PATCH 1161/2110] Javadoc for Required annotation (#5646) It caused confusion like: https://stackoverflow.com/questions/47940219/realmmigrationneededexception-when-adding-realmlistint-kotlin --- .../java/io/realm/annotations/Required.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/realm-annotations/src/main/java/io/realm/annotations/Required.java b/realm-annotations/src/main/java/io/realm/annotations/Required.java index 2950b0e3de..16750abcf8 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/Required.java +++ b/realm-annotations/src/main/java/io/realm/annotations/Required.java @@ -21,13 +21,20 @@ import java.lang.annotation.Target; /** - * This annotation will mark the field as not nullable. When the field is {@link Required}, - * it cannot be set to {@code null}. + * This annotation will mark the field or the element of a primitive {@link io.realm.RealmList} as not nullable. *

            - * Only {@code Boolean, Byte, Short, Integer, Long, Float, Double, String, byte[], Date} can be annotated - * with {@link Required}. Compiling will fail when fields with other types have {@link Required} annotation. - * Fields with primitive types and the {@link io.realm.RealmList} type are required implicitly. - * Fields with {@link io.realm.RealmObject} type are always nullable. + * When a field of type {@code Boolean, Byte, Short, Integer, Long, Float, Double, String, byte[], Date} is annotated + * with {@link Required}, it cannot be set to {@code null}. + *

            + * Fields with primitive types are implicitly required. + *

            + * When a primitive {@link io.realm.RealmList} ({@code RealmList, RealmList, RealmList, + * RealmList, RealmList, RealmList, RealmList, RealmList, RealmList, + * RealmList}) is annotated with {@link Required}, it cannot contain {@code null} values. + *

            + * The {@link io.realm.RealmList} field itself is required always. + *

            + * Compiling will fail when fields with other types have {@link Required} annotation. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) From 355beb8f2463e02beb02c49fd4647f8bb79d1f51 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 18 Jan 2018 17:48:10 +0000 Subject: [PATCH 1162/2110] downgrade javassist (#5698) * Downgrade version of Javassist causing issue with classloader at runtime #5641 --- CHANGELOG.md | 7 +++++++ realm-transformer/build.gradle | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8712e4528a..ebe7a37575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.3.3 (YYYY-MM-DD) + +### Internal + +* Downgrade JavaAssist to 3.21.0-GA to fix an issue with a `ClassNotFoundException` at runtime (#5641). + + ## 4.3.2 (2018-01-17) ### Bug Fixes diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 5fdb8f61c8..eb4789361d 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -61,7 +61,7 @@ dependencies { compile gradleApi() compile "io.realm:realm-annotations:${version}" compileOnly 'com.android.tools.build:gradle:3.1.0-alpha06' - compile 'org.javassist:javassist:3.22.0-GA' + compile 'org.javassist:javassist:3.21.0-GA' testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { exclude module: 'groovy-all' From 3378c1fce8e90b685101f1e6462be25c0f9804e6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 19 Jan 2018 00:29:48 +0100 Subject: [PATCH 1163/2110] Remove deprecated methods (#5685) --- CHANGELOG.md | 3 + .../examples/intro/IntroExampleActivity.java | 2 +- .../examples/kotlin/KotlinExampleActivity.kt | 2 +- .../examples/newsreader/model/Repository.java | 3 +- .../rxjava/gotchas/GotchasActivity.java | 10 +- .../rxjava/retrofit/RetrofitExample.java | 2 +- .../throttle/ThrottleSearchActivity.java | 3 +- .../examples/threads/AsyncQueryFragment.java | 5 +- .../io/realm/LinkingObjectsManagedTests.java | 2 +- .../java/io/realm/RealmAsyncQueryTests.java | 22 +- .../java/io/realm/RealmModelTests.java | 2 +- .../java/io/realm/RealmQueryTests.java | 88 +++--- .../androidTest/java/io/realm/SortTest.java | 10 +- .../cpp/io_realm_internal_OsSharedRealm.cpp | 50 ---- .../src/main/java/io/realm/Realm.java | 42 --- .../src/main/java/io/realm/RealmQuery.java | 251 +----------------- .../java/io/realm/internal/OsSharedRealm.java | 44 --- .../java/io/realm/SyncConfiguration.java | 8 +- .../realm/objectserver/PartialSyncTests.java | 52 ---- 19 files changed, 87 insertions(+), 514 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33216d9095..edb74314f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ### Breaking Changes * The `OrderedCollectionChangeSet` parameter in `OrderedRealmCollectionChangeListener.onChange()` is no longer nullable. Use `changeSet.getState()` instead (#5619). +* `realm.subscribeForObjects()` have been removed. Use `RealmQuery.findAllAsync(String subscriptionName)` and `RealmQuery.findAllAsync()` instead. +* Removed previously deprecated `RealmQuery.findAllSorted()`, `RealmQuery.findAllSortedAsync()` `RealmQuery.distinct() and `RealmQuery.distinctAsync()`. +* Renamed `RealmQuery.distinctValues()` to `RealmQuery.distinct()` ### Enhancements diff --git a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java index faa6b1eb78..669cd75af7 100644 --- a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java +++ b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java @@ -191,7 +191,7 @@ public void execute(Realm realm) { } // Sorting - RealmResults sortedPersons = realm.where(Person.class).findAllSorted("age", Sort.DESCENDING); + RealmResults sortedPersons = realm.where(Person.class).sort("age", Sort.DESCENDING).findAll(); status += "\nSorting " + sortedPersons.last().getName() + " == " + realm.where(Person.class).findFirst() .getName(); diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index 82a2cfb8f5..25a6622827 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -176,7 +176,7 @@ class KotlinExampleActivity : Activity() { } // Sorting - val sortedPersons = realm.where().findAllSorted(Person::age.name, Sort.DESCENDING) + val sortedPersons = realm.where().sort(Person::age.name, Sort.DESCENDING).findAll() status += "\nSorting ${sortedPersons.last()?.name} == ${realm.where().findAll().first()?.name}" } finally { diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Repository.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Repository.java index dc75037989..0f23bf17d5 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Repository.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Repository.java @@ -86,7 +86,8 @@ public Flowable> loadNewsFeed(@NonNull String section // save data in Realm return realm.where(NYTimesStory.class) .equalTo(NYTimesStory.API_SECTION, sectionKey) - .findAllSortedAsync(NYTimesStory.PUBLISHED_DATE, Sort.DESCENDING) + .sort(NYTimesStory.PUBLISHED_DATE, Sort.DESCENDING) + .findAllAsync() .asFlowable(); } diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas/GotchasActivity.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas/GotchasActivity.java index 4a269e0dd2..6013d6ebee 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas/GotchasActivity.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas/GotchasActivity.java @@ -71,7 +71,7 @@ protected void onResume() { // Trigger updates realm.executeTransaction(r -> - r.where(Person.class).findAllSorted( "name", Sort.ASCENDING).get(0).setAge(new Random().nextInt(100))); + r.where(Person.class).sort( "name", Sort.ASCENDING).findAll().get(0).setAge(new Random().nextInt(100))); } /** @@ -79,7 +79,7 @@ protected void onResume() { */ private void testSubscribeOn() { Disposable subscribeOnDisposable = realm.asFlowable() - .map(realm -> realm.where(Person.class).findAllSorted("name").get(0)) + .map(realm -> realm.where(Person.class).sort("name").findAll().get(0)) // The Realm was created on the UI thread. Accessing it on `Schedulers.io()` will crash. // Avoid using subscribeOn() and use Realms `findAllAsync*()` methods instead. .subscribeOn(Schedulers.io()) // @@ -90,7 +90,7 @@ private void testSubscribeOn() { compositeDisposable.add(subscribeOnDisposable); // Use Realms Async API instead - Disposable asyncSubscribeOnDisposable = realm.where(Person.class).findAllSortedAsync("name").get(0).asFlowable() + Disposable asyncSubscribeOnDisposable = realm.where(Person.class).sort("name").findAllAsync().get(0).asFlowable() .subscribe( person -> showStatus("subscribeOn/async: " + person.getName() + ":" + person.getAge()), throwable -> showStatus("subscribeOn/async: " +throwable.toString()) @@ -103,7 +103,7 @@ private void testSubscribeOn() { */ private void testBuffer() { Flowable personFlowable = - realm.asFlowable().map(realm -> realm.where(Person.class).findAllSorted("name").get(0)); + realm.asFlowable().map(realm -> realm.where(Person.class).sort("name").findAll().get(0)); // buffer() caches objects until the buffer is full. Due to Realms auto-update of all objects it means // that all objects in the cache will contain the same data. @@ -122,7 +122,7 @@ private void testBuffer() { */ private void testDistinct() { Flowable personFlowable = - realm.asFlowable().map(realm -> realm.where(Person.class).findAllSorted("name").get(0)); + realm.asFlowable().map(realm -> realm.where(Person.class).sort("name").findAll().get(0)); // distinct() and distinctUntilChanged() uses standard equals with older objects stored in a HashMap. // Realm objects auto-update which means the objects stored will also auto-update. diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/RetrofitExample.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/RetrofitExample.java index 4f01dbfa11..e76e15af25 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/RetrofitExample.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/retrofit/RetrofitExample.java @@ -60,7 +60,7 @@ protected void onResume() { super.onResume(); // Load all persons and merge them with their latest stats from GitHub (if they have any) - disposable = realm.where(Person.class).isNotNull("githubUserName").findAllSortedAsync("name").asFlowable() + disposable = realm.where(Person.class).isNotNull("githubUserName").sort("name").findAllAsync().asFlowable() // We only want the list once it is loaded. .filter(people -> people.isLoaded()) .switchMap(people -> Flowable.fromIterable(people)) diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java index 5b4bdd1df8..ffab76936e 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/throttle/ThrottleSearchActivity.java @@ -63,7 +63,8 @@ protected void onResume() { // Realm currently doesn't support the standard Schedulers. return realm.where(Person.class) .beginsWith("name", textChangeEvent.text().toString()) - .findAllSortedAsync("name") + .sort("name") + .findAllAsync() .asFlowable(); }) // Only continue once data is actually loaded diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java index 3fdde6744e..42aa4bb904 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/AsyncQueryFragment.java @@ -65,10 +65,11 @@ public void onStart() { allSortedDots = realm.where(Dot.class) .between("x", 25, 75) .between("y", 0, 50) - .findAllSortedAsync( + .sort( "x", Sort.ASCENDING, "y", Sort.DESCENDING - ); + ) + .findAllAsync(); dotAdapter.updateList(allSortedDots); allSortedDots.addChangeListener(this); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index d609d7fc8e..ef0e68ffbc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -678,7 +678,7 @@ public void query_multipleReferencesWithDistinct() { assertEquals(2, child.getListParents().size()); - RealmResults distinctParents = child.getListParents().where().distinctValues("fieldId").findAll(); + RealmResults distinctParents = child.getListParents().where().distinct("fieldId").findAll(); assertEquals(1, distinctParents.size()); assertTrue(child.getListParents().contains(parent)); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 6d7a320cd2..34b356ae5c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -885,10 +885,10 @@ public void distinct_async() throws Throwable { final long numberOfObjects = 10; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctValues("indexBoolean").findAllAsync(); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinctValues("indexLong").findAllAsync(); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinctValues("indexDate").findAllAsync(); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinctValues("indexString").findAllAsync(); + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinct("indexBoolean").findAllAsync(); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinct("indexLong").findAllAsync(); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinct("indexDate").findAllAsync(); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinct("indexString").findAllAsync(); assertFalse(distinctBool.isLoaded()); assertTrue(distinctBool.isValid()); @@ -966,7 +966,7 @@ public void distinct_async_rememberQueryParams() { RealmResults results = realm.where(AllJavaTypes.class) .notEqualTo(AllJavaTypes.FIELD_ID, TEST_SIZE / 2) - .distinctValues(AllJavaTypes.FIELD_ID) + .distinct(AllJavaTypes.FIELD_ID) .findAllAsync(); results.addChangeListener(new RealmChangeListener>() { @@ -988,16 +988,16 @@ public void distinctAsync_notIndexedFields() throws Throwable { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL) + .distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL) .findAllAsync(); final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_LONG) + .distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_LONG) .findAllAsync(); final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_DATE) + .distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_DATE) .findAllAsync(); final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_INDEX_STRING) + .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING) .findAllAsync(); assertFalse(distinctBool.isLoaded()); @@ -1072,7 +1072,7 @@ public void distinctAsync_noneExistingField() throws Throwable { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { - realm.where(AnnotationIndexTypes.class).distinctValues("doesNotExist").findAllAsync(); + realm.where(AnnotationIndexTypes.class).distinct("doesNotExist").findAllAsync(); fail(); } catch (IllegalArgumentException ignored) { looperThread.testComplete(); @@ -1102,7 +1102,7 @@ public void batchUpdateDifferentTypeOfQueries() { RealmResults findAllSorted = realm.where(AllTypes.class).sort("columnString", Sort.ASCENDING).findAllAsync(); RealmResults findAllSortedMulti = realm.where(AllTypes.class).sort(new String[]{"columnString", "columnLong"}, new Sort[]{Sort.ASCENDING, Sort.DESCENDING}).findAllAsync(); - RealmResults findDistinct = realm.where(AnnotationIndexTypes.class).distinctValues("indexString").findAllAsync(); + RealmResults findDistinct = realm.where(AnnotationIndexTypes.class).distinct("indexString").findAllAsync(); looperThread.keepStrongReference(findAllAsync); looperThread.keepStrongReference(findAllSorted); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index cbaae6111e..2f3c334857 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -201,7 +201,7 @@ public void async_query() { Realm realm = looperThread.getRealm(); populateTestRealm(realm, TEST_DATA_SIZE); - final RealmResults allTypesRealmModels = realm.where(AllTypesRealmModel.class).distinctValues(AllTypesRealmModel.FIELD_STRING).findAllAsync(); + final RealmResults allTypesRealmModels = realm.where(AllTypesRealmModel.class).distinct(AllTypesRealmModel.FIELD_STRING).findAllAsync(); looperThread.keepStrongReference(allTypesRealmModels); allTypesRealmModels.addChangeListener(new RealmChangeListener>() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 50467274cb..68a4bc5f95 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -18,7 +18,6 @@ import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,7 +26,6 @@ import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; @@ -318,8 +316,8 @@ private static void callThreadConfinedMethod(RealmQuery query, ThreadConfined case IS_NOT_EMPTY: query.isNotEmpty( AllJavaTypes.FIELD_STRING); break; case IS_VALID: query.isValid(); break; - case DISTINCT: query.distinctValues( AllJavaTypes.FIELD_STRING); break; - case DISTINCT_BY_MULTIPLE_FIELDS: query.distinctValues( AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID); break; + case DISTINCT: query.distinct( AllJavaTypes.FIELD_STRING); break; + case DISTINCT_BY_MULTIPLE_FIELDS: query.distinct( AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID); break; case SUM: query.sum( AllJavaTypes.FIELD_INT); break; case AVERAGE: query.average( AllJavaTypes.FIELD_INT); break; @@ -2982,10 +2980,10 @@ public void distinct() { final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL).findAll(); + RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL).findAll(); assertEquals(2, distinctBool.size()); for (String field : new String[]{AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinctValues(field).findAll(); + RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field).findAll(); assertEquals(field, numberOfBlocks, distinct.size()); } } @@ -2997,7 +2995,7 @@ public void distinct_withNullValues() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); for (String field : new String[]{AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinctValues(field).findAll(); + RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field).findAll(); assertEquals(field, 1, distinct.size()); } } @@ -3005,8 +3003,8 @@ public void distinct_withNullValues() { @Test(expected = IllegalStateException.class) public void distinct_failIfAppliedMultipleTimes() { realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE) - .distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE); + .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE) + .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); } @Test @@ -3016,12 +3014,12 @@ public void distinct_notIndexedFields() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL) + .distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL) .findAll(); assertEquals(2, distinctBool.size()); for (String field : new String[]{AnnotationIndexTypes.FIELD_NOT_INDEX_LONG, AnnotationIndexTypes.FIELD_NOT_INDEX_DATE, AnnotationIndexTypes.FIELD_NOT_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinctValues(field).findAll(); + RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field).findAll(); assertEquals(field, numberOfBlocks, distinct.size()); } } @@ -3033,7 +3031,7 @@ public void distinct_doesNotExist() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { - realm.where(AnnotationIndexTypes.class).distinctValues("doesNotExist").findAll(); + realm.where(AnnotationIndexTypes.class).distinct("doesNotExist").findAll(); fail(); } catch (IllegalArgumentException ignored) { } @@ -3045,7 +3043,7 @@ public void distinct_invalidTypes() { for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { try { - realm.where(AllTypes.class).distinctValues(field).findAll(); + realm.where(AllTypes.class).distinct(field).findAll(); fail(field); } catch (IllegalArgumentException ignored) { } @@ -3061,7 +3059,7 @@ public void distinct_indexedLinkedFields() { for (String field : AnnotationIndexTypes.INDEX_FIELDS) { try { realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_OBJECT + "." + field) + .distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field) .findAll(); fail("Unsupported Index" + field + " linked field"); } catch (IllegalArgumentException ignored) { @@ -3078,7 +3076,7 @@ public void distinct_notIndexedLinkedFields() { for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { try { realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_OBJECT + "." + field) + .distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field) .findAll(); fail("Unsupported notIndex" + field + " linked field"); } catch (IllegalArgumentException ignored) { @@ -3092,7 +3090,7 @@ public void distinct_invalidTypesLinkedFields() { try { realm.where(AllJavaTypes.class) - .distinctValues(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY) + .distinct(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY) .findAll(); } catch (IllegalArgumentException ignored) { } @@ -3107,10 +3105,10 @@ public void distinct_async() throws Throwable { final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL).findAllAsync(); - final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_LONG).findAllAsync(); - final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE).findAllAsync(); - final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_STRING).findAllAsync(); + final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL).findAllAsync(); + final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_LONG).findAllAsync(); + final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_DATE).findAllAsync(); + final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_STRING).findAllAsync(); assertFalse(distinctBool.isLoaded()); assertTrue(distinctBool.isValid()); @@ -3184,10 +3182,10 @@ public void distinct_async_withNullValues() throws Throwable { populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE) + .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE) .findAllAsync(); final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_INDEX_STRING) + .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING) .findAllAsync(); final Runnable endTest = new Runnable() { @@ -3227,7 +3225,7 @@ public void distinct_async_doesNotExist() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { - realm.where(AnnotationIndexTypes.class).distinctValues("doesNotExist").findAllAsync(); + realm.where(AnnotationIndexTypes.class).distinct("doesNotExist").findAllAsync(); } catch (IllegalArgumentException ignored) { } looperThread.testComplete(); @@ -3240,7 +3238,7 @@ public void distinct_async_invalidTypes() { for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { try { - realm.where(AllTypes.class).distinctValues(field).findAllAsync(); + realm.where(AllTypes.class).distinct(field).findAllAsync(); } catch (IllegalArgumentException ignored) { } } @@ -3256,7 +3254,7 @@ public void distinct_async_indexedLinkedFields() { for (String field : AnnotationIndexTypes.INDEX_FIELDS) { try { - realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_OBJECT + "." + field).findAllAsync(); + realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field).findAllAsync(); fail("Unsupported " + field + " linked field"); } catch (IllegalArgumentException ignored) { } @@ -3270,7 +3268,7 @@ public void distinct_async_notIndexedLinkedFields() { populateForDistinctInvalidTypesLinked(realm); try { - realm.where(AllJavaTypes.class).distinctValues(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY).findAllAsync(); + realm.where(AllJavaTypes.class).distinct(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY).findAllAsync(); } catch (IllegalArgumentException ignored) { } looperThread.testComplete(); @@ -3283,7 +3281,7 @@ public void distinctMultiArgs() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmQuery query = realm.where(AnnotationIndexTypes.class); - RealmResults distinctMulti = query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.INDEX_FIELDS).findAll(); + RealmResults distinctMulti = query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.INDEX_FIELDS).findAll(); assertEquals(numberOfBlocks, distinctMulti.size()); } @@ -3293,8 +3291,8 @@ public void distinctMultiArgs_switchedFieldsOrder() { TestHelper.populateForDistinctFieldsOrder(realm, numberOfBlocks); // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples. - RealmResults distinctStringLong = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_STRING, AnnotationIndexTypes.FIELD_INDEX_LONG).findAll(); - RealmResults distinctLongString = realm.where(AnnotationIndexTypes.class).distinctValues(AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_STRING).findAll(); + RealmResults distinctStringLong = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_STRING, AnnotationIndexTypes.FIELD_INDEX_LONG).findAll(); + RealmResults distinctLongString = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_STRING).findAll(); assertEquals(4, distinctStringLong.size()); assertEquals(4, distinctLongString.size()); assertEquals(distinctStringLong.size(), distinctLongString.size()); @@ -3309,47 +3307,47 @@ public void distinctMultiArgs_emptyField() { RealmQuery query = realm.where(AnnotationIndexTypes.class); // An empty string field in the middle. try { - query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, "", AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); + query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, "", AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); } catch (IllegalArgumentException ignored) { } // An empty string field at the end. try { - query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, "").findAll(); + query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, "").findAll(); } catch (IllegalArgumentException ignored) { } // A null string field in the middle. try { - query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null, AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); + query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null, AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); } catch (IllegalArgumentException ignored) { } // A null string field at the end. try { - query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, (String) null).findAll(); + query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, (String) null).findAll(); } catch (IllegalArgumentException ignored) { } // (String) Null makes varargs a null array. try { - query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null).findAll(); + query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null).findAll(); } catch (IllegalArgumentException ignored) { } // Two (String) null for first and varargs fields. try { - query.distinctValues((String) null, (String) null).findAll(); + query.distinct((String) null, (String) null).findAll(); } catch (IllegalArgumentException ignored) { } // "" & (String) null combination. try { - query.distinctValues("", (String) null).findAll(); + query.distinct("", (String) null).findAll(); } catch (IllegalArgumentException ignored) { } // "" & (String) null combination. try { - query.distinctValues((String) null, "").findAll(); + query.distinct((String) null, "").findAll(); } catch (IllegalArgumentException ignored) { } // Two empty fields tests. try { - query.distinctValues("", "").findAll(); + query.distinct("", "").findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3361,7 +3359,7 @@ public void distinctMultiArgs_withNullValues() { populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); RealmQuery query = realm.where(AnnotationIndexTypes.class); - RealmResults distinctMulti = query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING).findAll(); + RealmResults distinctMulti = query.distinct(AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING).findAll(); assertEquals(1, distinctMulti.size()); } @@ -3373,7 +3371,7 @@ public void distinctMultiArgs_notIndexedFields() { RealmQuery query = realm.where(AnnotationIndexTypes.class); try { - query.distinctValues(AnnotationIndexTypes.FIELD_NOT_INDEX_STRING, AnnotationIndexTypes.NOT_INDEX_FIELDS).findAll(); + query.distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_STRING, AnnotationIndexTypes.NOT_INDEX_FIELDS).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3386,7 +3384,7 @@ public void distinctMultiArgs_doesNotExistField() { RealmQuery query = realm.where(AnnotationIndexTypes.class); try { - query.distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT, AnnotationIndexTypes.NONEXISTANT_MIX_FIELDS).findAll(); + query.distinct(AnnotationIndexTypes.FIELD_INDEX_INT, AnnotationIndexTypes.NONEXISTANT_MIX_FIELDS).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3397,7 +3395,7 @@ public void distinctMultiArgs_invalidTypesFields() { RealmQuery query = realm.where(AllTypes.class); try { - query.distinctValues(AllTypes.FIELD_REALMOBJECT, AllTypes.INVALID_TYPES_FIELDS_FOR_DISTINCT).findAll(); + query.distinct(AllTypes.FIELD_REALMOBJECT, AllTypes.INVALID_TYPES_FIELDS_FOR_DISTINCT).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3410,7 +3408,7 @@ public void distinctMultiArgs_indexedLinkedFields() { RealmQuery query = realm.where(AnnotationIndexTypes.class); try { - query.distinctValues(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING, AnnotationIndexTypes.INDEX_LINKED_FIELDS).findAll(); + query.distinct(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING, AnnotationIndexTypes.INDEX_LINKED_FIELDS).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3423,7 +3421,7 @@ public void distinctMultiArgs_notIndexedLinkedFields() { RealmQuery query = realm.where(AnnotationIndexTypes.class); try { - query.distinctValues(AnnotationIndexTypes.NOT_INDEX_LINKED_FILED_STRING, AnnotationIndexTypes.NOT_INDEX_LINKED_FIELDS).findAll(); + query.distinct(AnnotationIndexTypes.NOT_INDEX_LINKED_FILED_STRING, AnnotationIndexTypes.NOT_INDEX_LINKED_FIELDS).findAll(); } catch (IllegalArgumentException ignored) { } } @@ -3434,7 +3432,7 @@ public void distinctMultiArgs_invalidTypesLinkedFields() { RealmQuery query = realm.where(AllJavaTypes.class); try { - query.distinctValues(AllJavaTypes.INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT, AllJavaTypes.INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT).findAll(); + query.distinct(AllJavaTypes.INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT, AllJavaTypes.INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT).findAll(); } catch (IllegalArgumentException ignored) { } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 91d961276d..6f41a48b9b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -571,7 +571,7 @@ public void sortByLongDistinctByInt() { // After distinct: // (3, 1, "C") - RealmResults results2 = results1.where().distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); + RealmResults results2 = results1.where().distinct(AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); assertEquals(1, results2.size()); assertEquals("C", results2.get(0).getIndexString()); assertEquals(3, results2.get(0).getIndexLong()); @@ -590,13 +590,13 @@ public void sortAndDistinctMixed() { // Case 1: Selecting highest numbers RealmResults results1a = realm.where(AnnotationIndexTypes.class) .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.DESCENDING) - .distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT) + .distinct(AnnotationIndexTypes.FIELD_INDEX_INT) .findAll(); assertEquals(1, results1a.size()); assertEquals(3, results1a.get(0).getIndexLong()); RealmResults results1b = realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT) + .distinct(AnnotationIndexTypes.FIELD_INDEX_INT) .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.DESCENDING) .findAll(); assertEquals(1, results1b.size()); @@ -605,13 +605,13 @@ public void sortAndDistinctMixed() { // Case 1: Selecting lowest number numbers RealmResults results2a = realm.where(AnnotationIndexTypes.class) .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.ASCENDING) - .distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT) + .distinct(AnnotationIndexTypes.FIELD_INDEX_INT) .findAll(); assertEquals(1, results2a.size()); assertEquals(1, results2a.get(0).getIndexLong()); RealmResults results2b = realm.where(AnnotationIndexTypes.class) - .distinctValues(AnnotationIndexTypes.FIELD_INDEX_INT) + .distinct(AnnotationIndexTypes.FIELD_INDEX_INT) .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.ASCENDING) .findAll(); assertEquals(1, results2b.size()); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 1baf6190b7..b58f07022d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -509,53 +509,3 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterSchema java_binding_context.set_schema_changed_callback(env, j_schema_changed_callback); } } - -JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterPartialSyncQuery( - REALM_UNUSED JNIEnv* env, REALM_UNUSED jobject j_shared_realm_instance, REALM_UNUSED jlong shared_realm_ptr, REALM_UNUSED jstring j_class_name, - REALM_UNUSED jstring j_query, REALM_UNUSED jobject j_callback) -{ - TR_ENTER_PTR(shared_realm_ptr) - -#if REALM_ENABLE_SYNC - - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - try { - JStringAccessor class_name(env, j_class_name); // throws - JStringAccessor query(env, j_query); // throws - - // The lambda will capture the copied reference and it will be unreferenced when the lambda's life cycle is over. - // That happens when the Realm is closed or the callback has been triggered once. - JavaGlobalRef j_callback_ref(env, j_callback); - JavaGlobalWeakRef j_shared_realm_instance_ref(env, j_shared_realm_instance); - - static JavaClass shared_realm_class(env, "io/realm/internal/OsSharedRealm"); - static JavaMethod partial_sync_cb(env, shared_realm_class, "runPartialSyncRegistrationCallback", - "(Ljava/lang/String;JLio/realm/internal/OsSharedRealm$PartialSyncCallback;)V"); - - auto cb = [j_callback_ref, j_shared_realm_instance_ref](Results results, std::exception_ptr err) { - JNIEnv* env = JniUtils::get_env(true); - j_shared_realm_instance_ref.call_with_local_ref(env, [&](JNIEnv*, jobject row_obj) { - if (err) { - try { - std::rethrow_exception(err); - } - catch (const std::exception& e) { - env->CallVoidMethod(row_obj, partial_sync_cb, to_jstring(env, e.what()), - reinterpret_cast(nullptr), j_callback_ref.get()); - } - return; - } - - auto wrapper = new ResultsWrapper(results); - env->CallVoidMethod(row_obj, partial_sync_cb, nullptr, reinterpret_cast(wrapper), - j_callback_ref.get()); - }); - }; - - partial_sync::register_query(shared_realm, class_name, query, std::move(cb)); - } - CATCH_STD() -#else - REALM_TERMINATE("Unsupported operation. Only available when used with the Realm Object Server"); -#endif -} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index b6a701c383..a2d1974d2f 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1764,43 +1764,6 @@ public void onError(Throwable error) { } }); } - /** - * If the Realm is a partially synchronized Realm, fetch and synchronize the objects of a given - * object type that match the given query (in string format). - * - * The results will be returned asynchronously in the callback. - * - * @param clazz the class to query. - * @param query string query. - * @param callback A callback used to vend the results of a partial sync fetch. - * @throws IllegalStateException if it is called from a non-Looper or {@link IntentService} thread. - * @throws IllegalStateException if called from a non-synchronized (Realm Object Server) Realm. - */ - @Beta - public void subscribeToObjects(final Class clazz, String query, final PartialSyncCallback callback) { - checkIfValid(); - if (!configuration.isSyncConfiguration()) { - throw new IllegalStateException("Partial sync is only available for synchronized Realm (Realm Object Server)"); - } - - sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); - - String className = configuration.getSchemaMediator().getSimpleClassName(clazz); - OsSharedRealm.PartialSyncCallback internalCallback = new OsSharedRealm.PartialSyncCallback(className) { - @Override - public void onSuccess(OsResults osResults) { - RealmResults results = new RealmResults<>(Realm.this, osResults, clazz); - callback.onSuccess(results); - } - - @Override - public void onError(RealmException error) { - callback.onError(error); - } - }; - - sharedRealm.registerPartialSyncQuery(query, internalCallback); - } Table getTable(Class clazz) { return schema.getTable(clazz); @@ -1940,9 +1903,4 @@ public void onError(Throwable exception) { super.onError(exception); } } - - public static abstract class PartialSyncCallback { - public abstract void onSuccess(RealmResults results); - public abstract void onError(RealmException error); - } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 07ed823fb1..58efc0f991 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -25,8 +25,8 @@ import io.realm.annotations.Beta; import io.realm.annotations.Required; -import io.realm.internal.OsResults; import io.realm.internal.OsList; +import io.realm.internal.OsResults; import io.realm.internal.PendingRow; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; @@ -35,7 +35,6 @@ import io.realm.internal.TableQuery; import io.realm.internal.Util; import io.realm.internal.fields.FieldDescriptor; -import io.realm.log.RealmLog; /** @@ -46,7 +45,7 @@ * RealmObject class is refactored care has to be taken to not break any queries. *

            * A {@link io.realm.Realm} is unordered, which means that there is no guarantee that querying a Realm will return the - * objects in the order they where inserted. Use {@link #findAllSorted(String)} and similar methods if a specific order + * objects in the order they where inserted. Use {@link #sort(String)} (String)} and similar methods if a specific order * is required. *

            * A RealmQuery cannot be passed between different threads. @@ -1587,78 +1586,6 @@ public RealmQuery isNotEmpty(String fieldName) { return this; } - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#distinctValues(String)} then {@link RealmQuery#findAll()} - * - * Returns a distinct set of objects of a specific class. If the result is sorted, the first - * object will be returned in case of multiple occurrences, otherwise it is undefined which - * object is returned. - *

            - * Adding {@link io.realm.annotations.Index} to the corresponding field will make this operation much faster. - * - * @param fieldName the field name. - * @return a non-null {@link RealmResults} containing the distinct objects. - * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, or points - * to linked fields. - */ - @Deprecated - public RealmResults distinct(String fieldName) { - realm.checkIfValid(); - - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), query.getTable(), fieldName); - return createRealmResults(query, null, distinctDescriptor, true, ""); - } - - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#distinctValues(String)} then {@link RealmQuery#findAllAsync()} - * - * Asynchronously returns a distinct set of objects of a specific class. If the result is - * sorted, the first object will be returned in case of multiple occurrences, otherwise it is - * undefined which object is returned. - * Adding {@link io.realm.annotations.Index} to the corresponding field will make this operation much faster. - * - * @param fieldName the field name. - * @return immediately a {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the - * query completes. - * @throws IllegalArgumentException if a field is {@code null}, does not exist, is an unsupported type, or points - * to linked fields. - */ - @Deprecated - public RealmResults distinctAsync(String fieldName) { - realm.checkIfValid(); - - realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), query.getTable(), fieldName); - return createRealmResults(query, null, distinctDescriptor, false, ""); - } - - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#distinctValues(String, String[])} then {@link RealmQuery#findAll()} - * - * Returns a distinct set of objects from a specific class. When multiple distinct fields are - * given, all unique combinations of values in the fields will be returned. In case of multiple - * matches, it is undefined which object is returned. Unless the result is sorted, then the - * first object will be returned. - * - * @param firstFieldName first field name to use when finding distinct objects. - * @param remainingFieldNames remaining field names when determining all unique combinations of field values. - * @return a non-null {@link RealmResults} containing the distinct objects. - * @throws IllegalArgumentException if field names is empty or {@code null}, does not exist, - * is an unsupported type, or points to a linked field. - */ - @Deprecated - public RealmResults distinct(String firstFieldName, String... remainingFieldNames) { - realm.checkIfValid(); - - String[] fieldNames = new String[1 + remainingFieldNames.length]; - - fieldNames[0] = firstFieldName; - System.arraycopy(remainingFieldNames, 0, fieldNames, 1, remainingFieldNames.length); - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), table, fieldNames); - return createRealmResults(query, null, distinctDescriptor, true, ""); - } - /** * Calculates the sum of a given field. * @@ -1869,50 +1796,6 @@ public RealmResults findAllAsync(String subscriptionName) { return createRealmResults(query, sortDescriptor, distinctDescriptor, false, subscriptionName); } - - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String, Sort)} then {@link RealmQuery#findAll()} - * - * Finds all objects that fulfill the query conditions and sorted by specific field name. - *

            - * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', - * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. - * - * @param fieldName the field name to sort by. - * @param sortOrder how to sort the results. - * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero - * objects is returned. - * @throws java.lang.IllegalArgumentException if field name does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. - */ - @SuppressWarnings("unchecked") - @Deprecated - public RealmResults findAllSorted(String fieldName, Sort sortOrder) { - realm.checkIfValid(); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldName, sortOrder); - return createRealmResults(query, sortDescriptor, null, true, ""); - } - - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String, Sort)} then {@link RealmQuery#findAllAsync()} - * - * Similar to {@link #findAllSorted(String, Sort)} but runs asynchronously on a worker thread - * (need a Realm opened from a looper thread to work). - * - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @throws java.lang.IllegalArgumentException if field name does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. - */ - @Deprecated - public RealmResults findAllSortedAsync(final String fieldName, final Sort sortOrder) { - realm.checkIfValid(); - - realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldName, sortOrder); - return createRealmResults(query, sortDescriptor, null, false, ""); - } - /** * Sorts the query result by the specific field name in ascending order. *

            @@ -1985,8 +1868,6 @@ public RealmQuery sort(String[] fieldNames, Sort[] sortOrders) { } /** - * BETA API: Will be renamed to {@code distinct} in next major release. - * * Selects a distinct set of objects of a specific class. If the result is sorted, the first object will be * returned in case of multiple occurrences, otherwise it is undefined which object is returned. *

            @@ -1998,13 +1879,11 @@ public RealmQuery sort(String[] fieldNames, Sort[] sortOrders) { * @throws IllegalStateException if distinct field names were already defined. */ @Beta - public RealmQuery distinctValues(String fieldName) { - return distinctValues(fieldName, new String[]{}); + public RealmQuery distinct(String fieldName) { + return distinct(fieldName, new String[]{}); } /** - * BETA API: Will be renamed to {@code distinct} in next major release. - * * Selects a distinct set of objects of a specific class. When multiple distinct fields are * given, all unique combinations of values in the fields will be returned. In case of multiple * matches, it is undefined which object is returned. Unless the result is sorted, then the @@ -2017,7 +1896,7 @@ public RealmQuery distinctValues(String fieldName) { * @throws IllegalStateException if distinct field names were already defined. */ @Beta - public RealmQuery distinctValues(String firstFieldName, String... remainingFieldNames) { + public RealmQuery distinct(String firstFieldName, String... remainingFieldNames) { realm.checkIfValid(); if (distinctDescriptor != null) { throw new IllegalStateException("Distinct fields have already been defined."); @@ -2033,130 +1912,10 @@ public RealmQuery distinctValues(String firstFieldName, String... remainingFi return this; } - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String)} then {@link RealmQuery#findAll()} - * - * Finds all objects that fulfill the query conditions and sorted by specific field name in ascending order. - *

            - * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', - * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. - * - * @param fieldName the field name to sort by. - * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero - * objects is returned. - * @throws java.lang.IllegalArgumentException if the field name does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. - */ - @Deprecated - public RealmResults findAllSorted(String fieldName) { - return findAllSorted(fieldName, Sort.ASCENDING); - } - - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String)} then {@link RealmQuery#findAllAsync()} - * - * Similar to {@link #findAllSorted(String)} but runs asynchronously on a worker thread. - * This method is only available from a Looper thread. - * - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @throws java.lang.IllegalArgumentException if the field name does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. - */ - @Deprecated - public RealmResults findAllSortedAsync(String fieldName) { - return findAllSortedAsync(fieldName, Sort.ASCENDING); - } - - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String[], Sort[])} then {@link RealmQuery#findAll()} - * - * Finds all objects that fulfill the query conditions and sorted by specific field names. - *

            - * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', - * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. - * - * @param fieldNames an array of field names to sort by. - * @param sortOrders how to sort the field names. - * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero - * objects is returned. - * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. - */ - @Deprecated - public RealmResults findAllSorted(String[] fieldNames, Sort[] sortOrders) { - realm.checkIfValid(); - - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldNames, sortOrders); - return createRealmResults(query, sortDescriptor, null, true, ""); - } - private boolean isDynamicQuery() { return className != null; } - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String[], Sort[])} then {@link RealmQuery#findAllAsync()} - * - * Similar to {@link #findAllSorted(String[], Sort[])} but runs asynchronously. - * from a worker thread. - * This method is only available from a Looper thread. - * - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @throws java.lang.IllegalArgumentException if one of the field names does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. - * @see io.realm.RealmResults - */ - @Deprecated - public RealmResults findAllSortedAsync(String[] fieldNames, final Sort[] sortOrders) { - realm.checkIfValid(); - - realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldNames, sortOrders); - return createRealmResults(query, sortDescriptor, null, false, ""); - } - - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String, Sort, String, Sort)} then {@link RealmQuery#findAll()} - * - * Finds all objects that fulfill the query conditions and sorted by specific field names in ascending order. - *

            - * Sorting is currently limited to character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', - * 'Latin Extended B' (UTF-8 range 0-591). For other character sets, sorting will have no effect. - * - * @param fieldName1 first field name - * @param sortOrder1 sort order for first field - * @param fieldName2 second field name - * @param sortOrder2 sort order for second field - * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero - * objects is returned. - * @throws java.lang.IllegalArgumentException if a field name does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. - */ - @Deprecated - public RealmResults findAllSorted(String fieldName1, Sort sortOrder1, - String fieldName2, Sort sortOrder2) { - return findAllSorted(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); - } - - /** - * @deprecated Since 4.3.0, now use {@link RealmQuery#sort(String, Sort, String, Sort)} then {@link RealmQuery#findAllAsync()} - * - * Similar to {@link #findAllSorted(String, Sort, String, Sort)} but runs asynchronously on a worker thread - * This method is only available from a Looper thread. - * - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @throws java.lang.IllegalArgumentException if a field name does not exist or it belongs to a child - * {@link RealmObject} or a child {@link RealmList}. - */ - @Deprecated - public RealmResults findAllSortedAsync(String fieldName1, Sort sortOrder1, - String fieldName2, Sort sortOrder2) { - return findAllSortedAsync(new String[] {fieldName1, fieldName2}, new Sort[] {sortOrder1, sortOrder2}); - } - /** * Finds the first object that fulfills the query conditions. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 8bbb34df8a..ae94ae0969 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -125,23 +125,6 @@ public interface SchemaChangedCallback { void onSchemaChanged(); } - /** - * Callback function to be called from JNI by Object Store when the partial sync results returned. - */ - @Keep - @Deprecated - public abstract static class PartialSyncCallback { - private final String className; - - protected PartialSyncCallback(String className) { - this.className = className; - } - - public abstract void onSuccess(OsResults results); - - public abstract void onError(RealmException error); - } - // Const value for RealmFileException conversion public static final byte FILE_EXCEPTION_KIND_ACCESS_ERROR = 0; public static final byte FILE_EXCEPTION_KIND_BAD_HISTORY = 1; @@ -400,10 +383,6 @@ public boolean isAutoRefresh() { return nativeIsAutoRefresh(nativePtr); } - public void registerPartialSyncQuery(String query, PartialSyncCallback callback) { - nativeRegisterPartialSyncQuery(nativePtr, callback.className, query, callback); - } - public RealmConfiguration getConfiguration() { return osRealmConfig.getRealmConfiguration(); } @@ -532,27 +511,6 @@ private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealm callback.onInit(new OsSharedRealm(nativeSharedRealmPtr, osRealmConfig)); } - /** - * Called from JNI when the partial sync callback is invoked from the ObjectStore. - * - * @param error if the partial sync query failed to register. - * @param nativeResultsPtr pointer to the {@code Results} of the partial sync query. - * @param callback the callback registered from the user to notify the success/error of the partial sync query. - */ - @SuppressWarnings("unused") - private void runPartialSyncRegistrationCallback(@Nullable String error, long nativeResultsPtr, - PartialSyncCallback callback) { - if (error != null) { - callback.onError(new RealmException(error)); - } else { - @SuppressWarnings("ConstantConditions") - Table table = getTable(Table.getTableNameForClass(callback.className)); - OsResults results = new OsResults(this, table, nativeResultsPtr); - callback.onSuccess(results); - } - } - - private static native void nativeInit(String temporaryDirectoryPath); private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier); @@ -614,6 +572,4 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare private static native void nativeRegisterSchemaChangedCallback(long nativePtr, SchemaChangedCallback callback); - private native void nativeRegisterPartialSyncQuery( - long nativeSharedRealmPtr, String className, String query, PartialSyncCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 61de76ff92..e37b913928 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -364,14 +364,12 @@ public OsRealmConfig.SyncSessionStopPolicy getSessionStopPolicy() { /** * Whether this configuration is for a partial synchronization Realm. + *

            * Partial synchronization allows a synchronized Realm to be opened in such a way that - * only objects requested by the user are synchronized to the device. You can use it by setting - * the {@link Builder#partialRealm()}, opening the Realm, and then calling - * {@link Realm#subscribeToObjects(Class, String, Realm.PartialSyncCallback)} with the type of - * object you're interested in, a string containing a query determining which objects you want - * to subscribe to, and a callback which will report the results. + * only objects queried by the user are synchronized to the device. * * @return {@code true} to open a partial synchronization Realm {@code false} otherwise. + * @see Builder#partialRealm() for more details. */ public boolean isPartialRealm() { return isPartial; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index b95d62ca89..88061cbfdf 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -276,58 +276,6 @@ public void onError(String subscriptionName, Throwable error) { }); } - @Test - @Deprecated - @RunTestInLooperThread - public void partialSync_oldApi() throws InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - createServerData(user, Constants.SYNC_SERVER_URL); - - AtomicInteger countdown = new AtomicInteger(2); - final Realm partialSyncRealm = getPartialRealm(user); - looperThread.closeAfterTest(partialSyncRealm); - assertTrue(partialSyncRealm.isEmpty()); - - partialSyncRealm.subscribeToObjects(PartialSyncObjectA.class, "number > 5", new Realm.PartialSyncCallback() { - - @Override - public void onSuccess(RealmResults results) { - assertEquals(4, results.size()); - for (PartialSyncObjectA object : results) { - assertThat(object.getNumber(), greaterThan(5)); - assertEquals("partial", object.getString()); - } - // make sure the Realm contains only PartialSyncObjectA - assertEquals(0, partialSyncRealm.where(PartialSyncObjectB.class).count()); - if (countdown.decrementAndGet() == 0) { - looperThread.testComplete(); - } - } - - @Override - public void onError(RealmException error) { - fail(error.getMessage()); - } - }); - - // Invalid query - partialSyncRealm.subscribeToObjects(PartialSyncObjectA.class, "invalid_property > 5", new Realm.PartialSyncCallback() { - - @Override - public void onSuccess(RealmResults results) { - fail("Invalid query should not succeed"); - } - - @Override - public void onError(RealmException error) { - assertNotNull(error); - if (countdown.decrementAndGet() == 0) { - looperThread.testComplete(); - } - } - }); - } - private Realm getPartialRealm(SyncUser user) { final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .name("partialSync") From c3cdde450d7e01f773fc15e61a35c273426e2473 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 19 Jan 2018 11:32:49 +0000 Subject: [PATCH 1164/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebe7a37575..3e2532e149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.3.3 (YYYY-MM-DD) +## 4.3.3 (2018-01-19) ### Internal From 3fd42ecfae3672f798559111ade633f0d2fc7428 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 19 Jan 2018 11:32:50 +0000 Subject: [PATCH 1165/2110] Release v4.3.3 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b7a5918b61..2533cac5ba 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.3-SNAPSHOT \ No newline at end of file +4.3.3 \ No newline at end of file From f13d94d2acc70c864d7425785db93ab7abfc512a Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 19 Jan 2018 11:32:50 +0000 Subject: [PATCH 1166/2110] Prepare next release v4.3.4-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 2533cac5ba..932e22fb7f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.3 \ No newline at end of file +4.3.4-SNAPSHOT \ No newline at end of file From efa153a8ed0b9aa7f190e45c70f4a81853b8cd2a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 19 Jan 2018 15:14:41 +0100 Subject: [PATCH 1167/2110] Remove getFieldNames from proxy classes (#5700) --- .../processor/RealmProxyClassGenerator.java | 19 ------- .../RealmProxyMediatorGenerator.java | 19 ------- .../io/realm/AllTypesRealmProxy.java | 29 ----------- .../io/realm/BooleansRealmProxy.java | 13 ----- .../io/realm/NullTypesRealmProxy.java | 50 ------------------- .../io/realm/RealmDefaultModuleMediator.java | 10 ---- .../resources/io/realm/SimpleRealmProxy.java | 11 ---- .../io/realm/internal/RealmProxyMediator.java | 8 --- .../internal/modules/CompositeMediator.java | 6 --- .../internal/modules/FilterableMediator.java | 6 --- 10 files changed, 171 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 6faacbfa29..54e8560b88 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -144,7 +144,6 @@ public void generate() throws IOException, UnsupportedOperationException { emitGetExpectedObjectSchemaInfo(writer); emitCreateColumnInfoMethod(writer); emitGetSimpleClassNameMethod(writer); - emitGetFieldNamesMethod(writer); emitCreateOrUpdateUsingJsonObject(writer); emitCreateUsingJsonStream(writer); emitCopyOrUpdateMethod(writer); @@ -240,15 +239,6 @@ private void emitClassFields(JavaWriter writer) throws IOException { writer.emitEmptyLine() .emitField("OsObjectSchemaInfo", "expectedObjectSchemaInfo", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL), "createExpectedObjectSchemaInfo()"); - - writer.emitField("List", "FIELD_NAMES", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)); - writer.beginInitializer(true) - .emitStatement("List fieldNames = new ArrayList(%s)", metadata.getFields().size()); - for (VariableElement field : metadata.getFields()) { - writer.emitStatement("fieldNames.add(\"%s\")", field.getSimpleName().toString()); - } - writer.emitStatement("FIELD_NAMES = Collections.unmodifiableList(fieldNames)") - .endInitializer(); } //@formatter:on @@ -840,15 +830,6 @@ private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException } //@formatter:on - //@formatter:off - private void emitGetFieldNamesMethod(JavaWriter writer) throws IOException { - writer.beginMethod("List", "getFieldNames", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) - .emitStatement("return FIELD_NAMES") - .endMethod() - .emitEmptyLine(); - } - //@formatter:on - //@formatter:off private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer.beginMethod( diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index 1370de9e12..a667e57590 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -98,7 +98,6 @@ public void generate() throws IOException { emitFields(writer); emitGetExpectedObjectSchemaInfoMap(writer); emitCreateColumnInfoMethod(writer); - emitGetFieldNamesMethod(writer); emitGetSimpleClassNameMethod(writer); emitNewInstanceMethod(writer); emitGetClassModelList(writer); @@ -167,24 +166,6 @@ public void emitStatement(int i, JavaWriter writer) throws IOException { writer.emitEmptyLine(); } - private void emitGetFieldNamesMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "List", - "getFieldNames", - EnumSet.of(Modifier.PUBLIC), - "Class", "clazz" - ); - emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return %s.getFieldNames()", qualifiedProxyClasses.get(i)); - } - }, writer); - writer.endMethod(); - writer.emitEmptyLine(); - } - private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); writer.beginMethod( diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index fb00a17894..03e4de06d4 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -121,31 +121,6 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); - private static final List FIELD_NAMES; - static { - List fieldNames = new ArrayList(20); - fieldNames.add("columnString"); - fieldNames.add("columnLong"); - fieldNames.add("columnFloat"); - fieldNames.add("columnDouble"); - fieldNames.add("columnBoolean"); - fieldNames.add("columnDate"); - fieldNames.add("columnBinary"); - fieldNames.add("columnMutableRealmInteger"); - fieldNames.add("columnObject"); - fieldNames.add("columnRealmList"); - fieldNames.add("columnStringList"); - fieldNames.add("columnBinaryList"); - fieldNames.add("columnBooleanList"); - fieldNames.add("columnLongList"); - fieldNames.add("columnIntegerList"); - fieldNames.add("columnShortList"); - fieldNames.add("columnByteList"); - fieldNames.add("columnDoubleList"); - fieldNames.add("columnFloatList"); - fieldNames.add("columnDateList"); - FIELD_NAMES = Collections.unmodifiableList(fieldNames); - } private AllTypesColumnInfo columnInfo; private ProxyState proxyState; @@ -893,10 +868,6 @@ public static String getSimpleClassName() { return "AllTypes"; } - public static List getFieldNames() { - return FIELD_NAMES; - } - @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index b9573557f8..cb8c2ffb70 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -71,15 +71,6 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); - private static final List FIELD_NAMES; - static { - List fieldNames = new ArrayList(4); - fieldNames.add("done"); - fieldNames.add("isReady"); - fieldNames.add("mCompleted"); - fieldNames.add("anotherBoolean"); - FIELD_NAMES = Collections.unmodifiableList(fieldNames); - } private BooleansColumnInfo columnInfo; private ProxyState proxyState; @@ -211,10 +202,6 @@ public static String getSimpleClassName() { return "Booleans"; } - public static List getFieldNames() { - return FIELD_NAMES; - } - @SuppressWarnings("cast") public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index bef81c5e86..fecf92e93e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -182,52 +182,6 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); - private static final List FIELD_NAMES; - static { - List fieldNames = new ArrayList(41); - fieldNames.add("fieldStringNotNull"); - fieldNames.add("fieldStringNull"); - fieldNames.add("fieldBooleanNotNull"); - fieldNames.add("fieldBooleanNull"); - fieldNames.add("fieldBytesNotNull"); - fieldNames.add("fieldBytesNull"); - fieldNames.add("fieldByteNotNull"); - fieldNames.add("fieldByteNull"); - fieldNames.add("fieldShortNotNull"); - fieldNames.add("fieldShortNull"); - fieldNames.add("fieldIntegerNotNull"); - fieldNames.add("fieldIntegerNull"); - fieldNames.add("fieldLongNotNull"); - fieldNames.add("fieldLongNull"); - fieldNames.add("fieldFloatNotNull"); - fieldNames.add("fieldFloatNull"); - fieldNames.add("fieldDoubleNotNull"); - fieldNames.add("fieldDoubleNull"); - fieldNames.add("fieldDateNotNull"); - fieldNames.add("fieldDateNull"); - fieldNames.add("fieldObjectNull"); - fieldNames.add("fieldStringListNotNull"); - fieldNames.add("fieldStringListNull"); - fieldNames.add("fieldBinaryListNotNull"); - fieldNames.add("fieldBinaryListNull"); - fieldNames.add("fieldBooleanListNotNull"); - fieldNames.add("fieldBooleanListNull"); - fieldNames.add("fieldLongListNotNull"); - fieldNames.add("fieldLongListNull"); - fieldNames.add("fieldIntegerListNotNull"); - fieldNames.add("fieldIntegerListNull"); - fieldNames.add("fieldShortListNotNull"); - fieldNames.add("fieldShortListNull"); - fieldNames.add("fieldByteListNotNull"); - fieldNames.add("fieldByteListNull"); - fieldNames.add("fieldDoubleListNotNull"); - fieldNames.add("fieldDoubleListNull"); - fieldNames.add("fieldFloatListNotNull"); - fieldNames.add("fieldFloatListNull"); - fieldNames.add("fieldDateListNotNull"); - fieldNames.add("fieldDateListNull"); - FIELD_NAMES = Collections.unmodifiableList(fieldNames); - } private NullTypesColumnInfo columnInfo; private ProxyState proxyState; @@ -1753,10 +1707,6 @@ public static String getSimpleClassName() { return "NullTypes"; } - public static List getFieldNames() { - return FIELD_NAMES; - } - @SuppressWarnings("cast") public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 80a30b44ef..506273e123 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -47,16 +47,6 @@ public ColumnInfo createColumnInfo(Class clazz, OsSchemaIn throw getMissingProxyClassException(clazz); } - @Override - public List getFieldNames(Class clazz) { - checkClass(clazz); - - if (clazz.equals(some.test.AllTypes.class)) { - return io.realm.AllTypesRealmProxy.getFieldNames(); - } - throw getMissingProxyClassException(clazz); - } - @Override public String getSimpleClassNameImpl(Class clazz) { checkClass(clazz); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 4473a97537..1ea6822a31 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -65,13 +65,6 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); - private static final List FIELD_NAMES; - static { - List fieldNames = new ArrayList(2); - fieldNames.add("name"); - fieldNames.add("age"); - FIELD_NAMES = Collections.unmodifiableList(fieldNames); - } private SimpleColumnInfo columnInfo; private ProxyState proxyState; @@ -165,10 +158,6 @@ public static String getSimpleClassName() { return "Simple"; } - public static List getFieldNames() { - return FIELD_NAMES; - } - @SuppressWarnings("cast") public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index 188a6f96b2..58db9cfe46 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -60,14 +60,6 @@ public abstract class RealmProxyMediator { */ public abstract ColumnInfo createColumnInfo(Class clazz, OsSchemaInfo osSchemaInfo); - /** - * Returns a map of non-obfuscated object field names to their internal Realm name. - * - * @param clazz the {@link RealmObject} class reference. - * @return The simple name of an RealmObject class (before it has been obfuscated). - */ - public abstract List getFieldNames(Class clazz); - /** * Returns the name that Realm should use for all its internal tables. This is the un-obfuscated simple name of the * class. diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index 08df441f79..626041c3cb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -76,12 +76,6 @@ public ColumnInfo createColumnInfo(Class clazz, OsSchemaIn return mediator.createColumnInfo(clazz, osSchemaInfo); } - @Override - public List getFieldNames(Class clazz) { - RealmProxyMediator mediator = getMediator(clazz); - return mediator.getFieldNames(clazz); - } - @Override protected String getSimpleClassNameImpl(Class clazz) { RealmProxyMediator mediator = getMediator(clazz); diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index ef9fb5654c..b3ec7decb9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -91,12 +91,6 @@ public ColumnInfo createColumnInfo(Class clazz, OsSchemaIn return originalMediator.createColumnInfo(clazz, osSchemaInfo); } - @Override - public List getFieldNames(Class clazz) { - checkSchemaHasClass(clazz); - return originalMediator.getFieldNames(clazz); - } - @Override protected String getSimpleClassNameImpl(Class clazz) { checkSchemaHasClass(clazz); From ac1372e2196a7f177014279720f885c82eaf4c67 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 23 Jan 2018 16:07:52 +0100 Subject: [PATCH 1168/2110] Release SNAPSHOTs from next-major (#5706) --- Jenkinsfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index fec751058f..a6eeb60267 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -29,7 +29,7 @@ try { // on PR's for even more throughput. def ABIs = "" def instrumentationTestTarget = "connectedAndroidTest" - if (!['master'].contains(env.BRANCH_NAME)) { + if (!['master', 'next-major'].contains(env.BRANCH_NAME)) { ABIs = "armeabi-v7a" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting } @@ -118,11 +118,13 @@ try { // TODO: add support for running monkey on the example apps - if (env.BRANCH_NAME == 'master') { + if (['master'].contains(env.BRANCH_NAME)) { stage('Collect metrics') { collectAarMetrics() } + } + if (['master', 'next-major'].contains(env.BRANCH_NAME)) { stage('Publish to OJO') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" @@ -145,7 +147,7 @@ try { buildSuccess = false throw e } finally { - if (['master', 'releases'].contains(env.BRANCH_NAME) && !buildSuccess) { + if (['master', 'releases', 'next-major'].contains(env.BRANCH_NAME) && !buildSuccess) { node { withCredentials([[$class: 'StringBinding', credentialsId: 'slack-java-url', variable: 'SLACK_URL']]) { def payload = JsonOutput.toJson([ From f089a8789312e0cacad5621a4de6677936635d57 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 29 Jan 2018 08:47:01 +0100 Subject: [PATCH 1169/2110] Support for overriding internal names using annotations (#5280) * Add support for @RealmName which makes it possible to override the name of classes and fields. * Remove RealmName annotation. Add methods for specifying names to RealmModule, RealmClass and a new RealmField annotation. * Fix compile issues * Fix Javadoc and most unit tests. * Add test for conflicting modules * Begun work on checking conflicting policies * Fix merge * Use correct version of Object Store * Better docs * Backup * Processor tests are green * Fleshing out unit tests for using name policies in annotations * Cleanup * Fix required value lists + cleanup * Fix module defaults not being correctly applied * Add support and unit tests for field name overrides * Add changelog * Javadoc updates * Use custom built parser for converting variable names * Better Javadoc * RealmField is now retained at Runtime * Fix unit tests * Unify build tools * Cleanup * Remove FIXMEs * Fix tests after merge from master * PR feedback * More PR feedback * PR feedback * More PR feedback * Output correctly internal name * Fix library module mediators not being correctly created * Fix missing spaces in error message * Better example * Improve wording --- CHANGELOG.md | 7 + .../java/io/realm/annotations/RealmClass.java | 20 + .../java/io/realm/annotations/RealmField.java | 40 ++ .../io/realm/annotations/RealmModule.java | 25 + .../realm/annotations/RealmNamingPolicy.java | 170 +++++++ .../io/realm/processor/ClassCollection.java | 70 +++ .../io/realm/processor/ClassMetaData.java | 157 +++++-- .../io/realm/processor/ModuleMetaData.java | 346 ++++++++++++-- .../io/realm/processor/RealmFieldElement.java | 124 +++++ .../io/realm/processor/RealmProcessor.java | 96 ++-- .../processor/RealmProxyClassGenerator.java | 170 +++---- .../RealmProxyInterfaceGenerator.java | 2 +- .../RealmProxyMediatorGenerator.java | 11 +- .../main/java/io/realm/processor/Utils.java | 63 ++- .../nameconverter/CamelCaseConverter.java | 44 ++ .../nameconverter/IdentityConverter.java | 30 ++ .../LowerCaseWithSeparatorConverter.java | 44 ++ .../nameconverter/NameConverter.java | 31 ++ .../nameconverter/PascalCaseConverter.java | 38 ++ .../nameconverter/WordTokenizer.java | 145 ++++++ .../realm/processor/NameConverterTests.java | 149 ++++++ .../processor/RealmBacklinkProcessorTest.java | 2 +- .../io/realm/processor/RealmNameTest.java | 113 +++++ .../realm/processor/RealmProcessorTest.java | 16 +- .../io/realm/AllTypesRealmProxy.java | 40 +- .../io/realm/BooleansRealmProxy.java | 8 +- ...amePolicyMixedClassSettingsRealmProxy.java | 443 ++++++++++++++++++ .../NamePolicyModuleDefaultsRealmProxy.java | 443 ++++++++++++++++++ .../io/realm/NullTypesRealmProxy.java | 82 ++-- .../io/realm/RealmDefaultModuleMediator.java | 2 +- .../resources/io/realm/SimpleRealmProxy.java | 4 +- .../some/test/NamePolicyClassOnly.java | 31 ++ ...lictingModuleDefinitionsForAllClasses.java | 37 ++ ...gModuleDefinitionsForMixedDefinitions.java | 37 ++ ...ctingModuleDefinitionsForNamedClasses.java | 37 ++ .../some/test/NamePolicyFieldNameOnly.java | 31 ++ .../test/NamePolicyMixedClassSettings.java | 32 ++ .../resources/some/test/NamePolicyModule.java | 24 + .../some/test/NamePolicyModuleDefaults.java | 28 ++ .../java/io/realm/CustomRealmNameTests.java | 237 ++++++++++ .../ClassNameOverrideModulePolicy.java | 63 +++ .../entities/realmname/ClassWithPolicy.java | 68 +++ .../realmname/CustomRealmNamesModule.java | 31 ++ .../realmname/DefaultPolicyFromModule.java | 22 + .../FieldNameOverrideClassPolicy.java | 32 ++ .../io/realm/ImmutableRealmObjectSchema.java | 14 + .../java/io/realm/ImmutableRealmSchema.java | 4 +- .../io/realm/MutableRealmObjectSchema.java | 14 + .../java/io/realm/MutableRealmSchema.java | 4 +- .../main/java/io/realm/RealmObjectSchema.java | 6 +- .../java/io/realm/internal/ColumnIndices.java | 2 +- .../java/io/realm/internal/ColumnInfo.java | 67 ++- .../fields/CachedFieldDescriptor.java | 11 +- .../fields/DynamicFieldDescriptor.java | 5 +- .../internal/fields/FieldDescriptor.java | 9 +- .../internal/modules/CompositeMediator.java | 20 +- version.txt | 2 +- 57 files changed, 3476 insertions(+), 327 deletions(-) create mode 100644 realm-annotations/src/main/java/io/realm/annotations/RealmField.java create mode 100644 realm-annotations/src/main/java/io/realm/annotations/RealmNamingPolicy.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.java create mode 100644 realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java create mode 100644 realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmNameTest.java create mode 100644 realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyMixedClassSettingsRealmProxy.java create mode 100644 realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyModuleDefaultsRealmProxy.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyClassOnly.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForAllClasses.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForMixedDefinitions.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForNamedClasses.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyFieldNameOnly.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyMixedClassSettings.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyModule.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyModuleDefaults.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassNameOverrideModulePolicy.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassWithPolicy.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/realmname/CustomRealmNamesModule.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/realmname/DefaultPolicyFromModule.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/realmname/FieldNameOverrideClassPolicy.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e2532e149..aab89cd4b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.4.0 (YYYY-MM-DD) + +### Enhancements + +* Added support for mapping between a Java name and the underlying name in the Realm file using `@RealmModule`, `@RealmClass` and `@RealmField` annotations (#5280). + + ## 4.3.3 (2018-01-19) ### Internal diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java index d0ab776fbb..b4a3abb695 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java @@ -23,9 +23,29 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Interface used to mark a class that can be persisted by Realm. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface RealmClass { + /** + * Manually set the internal name used by Realm for this class. If this class is part of + * any modules, this will also override any name policy set using + * {@link RealmModule#classNamingPolicy()}. + * + * @see io.realm.annotations.RealmNamingPolicy for more information about what setting the name means. + */ + String name() default ""; + + /** + * The naming policy applied to all fields in this class. The default policy is {@link RealmNamingPolicy#NO_POLICY}. + *

            + * It is possible to override the naming policy for each field by using the {@link RealmField} annotation. + * + * @see io.realm.annotations.RealmNamingPolicy for more information about what setting this policy means. + */ + RealmNamingPolicy fieldNamingPolicy() default RealmNamingPolicy.NO_POLICY; } diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmField.java b/realm-annotations/src/main/java/io/realm/annotations/RealmField.java new file mode 100644 index 0000000000..0cdc67f6fd --- /dev/null +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmField.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.annotations; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used on fields in Realm model classes. It describes metadata about the field. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Inherited +public @interface RealmField { + + /** + * Manually set the internal name used by Realm for this field. This will override any + * {@link RealmNamingPolicy} set on the class or the module. + * + * @see io.realm.annotations.RealmNamingPolicy for more information about what setting the name means. + */ + String name() default ""; +} diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmModule.java b/realm-annotations/src/main/java/io/realm/annotations/RealmModule.java index c8690de362..2928b1cd22 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/RealmModule.java +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmModule.java @@ -93,4 +93,29 @@ * an exception. */ Class[] classes() default {}; + + /** + * The naming policy applied to all classes part of this module. The default policy is {@link RealmNamingPolicy#NO_POLICY}. + * To define a naming policy for all fields in the classes, use {@link #fieldNamingPolicy()}. + *

            + * It is possible to override the naming policy specified in the module in each class using the {@link RealmClass} + * annotation. + *

            + * If a class is part of multiple modules, the same naming policy must be applied to both modules, otherwise + * an error will be thrown. + * + * @see io.realm.annotations.RealmNamingPolicy for more information about what setting this policy means. + */ + RealmNamingPolicy classNamingPolicy() default RealmNamingPolicy.NO_POLICY; + + /** + * The naming policy applied to all field names in all classes part of this module. The default policy is + * {@link RealmNamingPolicy#NO_POLICY}. To define a naming policy for class names, use {@link #classNamingPolicy()}. + *

            + * It is possible to override this naming policy using either {@link RealmClass#fieldNamingPolicy()} or + * {@link RealmField#name()}. + * + * @see io.realm.annotations.RealmNamingPolicy for more information about what setting this policy means. + */ + RealmNamingPolicy fieldNamingPolicy() default RealmNamingPolicy.NO_POLICY; } diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmNamingPolicy.java b/realm-annotations/src/main/java/io/realm/annotations/RealmNamingPolicy.java new file mode 100644 index 0000000000..2e2aac3608 --- /dev/null +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmNamingPolicy.java @@ -0,0 +1,170 @@ +package io.realm.annotations; + +/** + * This enum defines the possible ways class and field names can be mapped from what is used in Java + * to the name used internally in the Realm file. + *

            + * Examples where this is useful: + *

              + *
            • + * To support two model classes with the same simple name but in different packages. + *
            • + *
            • + * To make it easier to work with cross platform schemas as naming conventions are different. + *
            • + *
            • + * To use a Java class name that is longer than the 57 character limit enforced by Realm. + *
            • + *
            • + * To change a field name in Java without forcing app users through a migration process. + *
            • + *
            + * + * Depending on where the policy is applied, it will have slightly different semantics: + *
              + *
            • + * If applied to {@link RealmModule#classNamingPolicy} all classes part of that module + * will be affected. If a class is part of multiple modules, the same naming policy must be + * applied to both modules, otherwise an error will be thrown. + *
            • + *
            • + * If applied to {@link RealmModule#fieldNamingPolicy} all persistable fields in all classes + * part of this module will be affected. + *
            • + * + *
            • + * If applied to {@link RealmClass#fieldNamingPolicy} all fields in that class will be + * affected. This will override any field naming policy specified on a module. + *
            • + *
            + *

            + * An example of this: + *

            + * {@code
            + * \@RealmClass(name = "__person", fieldNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
            + * public class Person implements RealmModel { // is converted to "__person" internally
            + *     public string firstName; // Is converted to "first_name" internally
            + * }
            + * }
            + * 
            + *

            + * Choosing an internal name that differs from the name used in the Java model classes has the + * following implications: + *

              + *
            • + * Queries on {@code DynamicRealm} must use the internal name. Queries on normal {@code Realm} + * instances must continue to use the name as it is defined in the Java class. + *
            • + *
            • + * Migrations must use the internal name when creating classes and fields. + *
            • + *
            • + * Schema errors reported will use the internal names. + *
            • + *
            + *

            + * When automatically converting Java variable names, each variable name is normalized by splitting + * it into a list of words that are then joined using the rules of the target format. The following + * heuristics are used for determining what constitutes a "word". + *

              + *
            1. + * Anytime a {@code _} or {@code $} is encountered. + * Examples are "_FirstName", "_First_Name" and "$First$Name" which all becomes "First" and "Name". + *
            2. + *
            3. + * Anytime you switch from a lower case character to an upper case character as + * identified by {@link Character#isUpperCase(int)} and {@link Character#isLowerCase(int)}. + * Example is "FirstName" which becomes "First" and "Name". + *
            4. + *
            5. + * Anytime you switch from more than one uppercase character to a lower case one. The last + * upper case letter is assumed to be part of the next word. This is identified by using + * {@link Character#isUpperCase(int)} and {@link Character#isLowerCase(int)}. + * Example is "FIRSTName" which becomes "FIRST" and "Name. + *
            6. + *
            7. + * Some characters like emojiis are neither uppercase nor lowercase characters, so they will + * be part of the current word. + * Examples are "my😁" and "MY😁" which are both treated as one word. + *
            8. + *
            9. + * Hungarian notation, i.e. variable names starting with lowercase "m" followed by uppercase + * letter is stripped and not considered part of any word. + * Example is "mFirstName" and "mFIRSTName" which becomes "First" and "Name. + *
            10. + *
            + *

            + * Note that changing the internal name does NOT affect importing data from JSON. The JSON + * data must still follow the names as defined in the Realm Java class. + *

            + * When it comes to parsing JSON using standard libraries like Moshi, GSON or Jackson it is + * important to keep in mind that these libraries define the transformation from JSON to Java + * while setting internal Realm names define the transformation from Java to the Realm file. + *

            + * This means that if you want to import data into Realm from JSON using these libraries you still + * need to provide the annotations from both the JSON parser library and Realm. + *

            + * Using Moshi, it would look something like this: + *

            + * {@code
            + * public class Person extends RealmObject {
            + *     \@Json(name = "first_name") // Name used in JSON input.
            + *     \@RealmField(name = "first_name") // Name used internally in the Realm file.
            + *     public string firstName; // name used in Java
            + * }
            + * }
            + * 
            + * + * @see RealmModule + * @see RealmClass + * @see RealmField + */ +public enum RealmNamingPolicy { + + /** + * No policy is applied. This policy will not override any policy set on a parent element, e.g. + * if set in {@link RealmClass#fieldNamingPolicy}, the module policy will still apply to field + * names. + *

            + * If two modules disagree on the policy and one of them is {@code NO_POLICY}, the other will + * be chosen without an error being thrown. + *

            + * This policy is the default. + */ + NO_POLICY, + + /** + * The name in the Java model class is used as is internally. + */ + IDENTITY, + + /** + * The name in the Java model class is converted to camelCase, i.e. all words are joined + * together with the first letter in the first word lower cased, and the first letter of + * all subsequent words upper cased. This is the standard naming schema in Java, Kotlin, Swift + * and JavaScript. + *

            + * Examples: "firstName", "FirstName", "mFirstName", "FIRST_NAME", "First$Name" all becomes + * "firstName". + */ + CAMEL_CASE, + + /** + * The name in the Java model class is converted to PascalCase, i.e. all words are joined + * together with the first letter of all words upper cased. This is the default naming scheme + * in .NET. + *

            + * Examples: "firstName", "FirstName", "mFirstName", "FIRST_NAME", "First$Name" all becomes + * "FirstName". + */ + PASCAL_CASE, + + /** + * The name in the Java model class is converted lowercase with each word separated by {@code _}. + * This is the default naming scheme in C++. + *

            + * Examples: "firstName", "FirstName", "mFirstName", "FIRST_NAME", "First$Name" all becomes + * "first_name". + */ + LOWER_CASE_WITH_UNDERSCORES +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java new file mode 100644 index 0000000000..c500785d67 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java @@ -0,0 +1,70 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.processor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * Wrapper around all Realm model classes metadata found during processing. It also + * allows easy lookup for specific class data. + */ +public class ClassCollection { + + // These three collections should always stay in sync + private Map simpleNameClassMap = new LinkedHashMap<>(); + private Map qualifiedNameClassMap = new LinkedHashMap<>(); + private Set classSet = new LinkedHashSet<>(); + + public void addClass(ClassMetaData metadata) { + classSet.add(metadata); + simpleNameClassMap.put(metadata.getSimpleJavaClassName(), metadata); + qualifiedNameClassMap.put(metadata.getFullyQualifiedClassName(), metadata); + } + + public Set getClasses() { + return Collections.unmodifiableSet(classSet); + } + + public ClassMetaData getClassFromSimpleName(String simpleJavaClassName) { + ClassMetaData data = simpleNameClassMap.get(simpleJavaClassName); + if (data == null) { + throw new NullPointerException("Class " + simpleJavaClassName + " was not found"); + } + return data; + } + + public ClassMetaData getClassFromQualifiedName(String qualifiedJavaClassName) { + ClassMetaData data = qualifiedNameClassMap.get(qualifiedJavaClassName); + if (data == null) { + throw new NullPointerException("Class " + qualifiedJavaClassName + " was not found"); + } + return data; + } + + public int size() { + return classSet.size(); + } + + public boolean containsQualifiedClass(String qualifiedClassName) { + return qualifiedNameClassMap.containsKey(qualifiedClassName); + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index cb8b7a54c7..8fc5378619 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -44,7 +44,11 @@ import io.realm.annotations.Index; import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; +import io.realm.annotations.RealmField; +import io.realm.annotations.RealmNamingPolicy; import io.realm.annotations.Required; +import io.realm.processor.nameconverter.NameConverter; /** @@ -52,14 +56,15 @@ */ public class ClassMetaData { private static final String OPTION_IGNORE_KOTLIN_NULLABILITY = "realm.ignoreKotlinNullability"; + private static final int MAX_CLASSNAME_LENGTH = 57; private final TypeElement classType; // Reference to model class. - private final String className; // Model class simple name. - private final List fields = new ArrayList(); // List of all fields in the class except those @Ignored. - private final List indexedFields = new ArrayList(); // list of all fields marked @Index. + private final String javaClassName; // Model class simple name as defined in Java. + private final List fields = new ArrayList(); // List of all fields in the class except those @Ignored. + private final List indexedFields = new ArrayList(); // list of all fields marked @Index. private final Set backlinks = new HashSet(); - private final Set nullableFields = new HashSet(); // Set of fields which can be nullable - private final Set nullableValueListFields = new HashSet(); // Set of fields whose elements can be nullable + private final Set nullableFields = new HashSet(); // Set of fields which can be nullable + private final Set nullableValueListFields = new HashSet(); // Set of fields whose elements can be nullable private String packageName; // package name for model class. private boolean hasDefaultConstructor; // True if model has a public no-arg constructor. @@ -67,17 +72,19 @@ public class ClassMetaData { private boolean containsToString; private boolean containsEquals; private boolean containsHashCode; + private String internalClassName; private final List validPrimaryKeyTypes; private final List validListValueTypes; private final Types typeUtils; private final Elements elements; + private NameConverter defaultFieldNameFormatter; private final boolean ignoreKotlinNullability; public ClassMetaData(ProcessingEnvironment env, TypeMirrors typeMirrors, TypeElement clazz) { this.classType = clazz; - this.className = clazz.getSimpleName().toString(); + this.javaClassName = clazz.getSimpleName().toString(); typeUtils = env.getTypeUtils(); elements = env.getElementUtils(); @@ -125,8 +132,15 @@ public String toString() { return "class " + getFullyQualifiedClassName(); } - public String getSimpleClassName() { - return className; + public String getSimpleJavaClassName() { + return javaClassName; + } + + /** + * Returns the name that Realm Core uses when saving data from this Java class. + */ + public String getInternalClassName() { + return internalClassName; } public String getPackageName() { @@ -134,10 +148,10 @@ public String getPackageName() { } public String getFullyQualifiedClassName() { - return packageName + "." + className; + return packageName + "." + javaClassName; } - public List getFields() { + public List getFields() { return Collections.unmodifiableList(fields); } @@ -153,7 +167,7 @@ public String getInternalSetter(String fieldName) { return "realmSet$" + fieldName; } - public List getIndexedFields() { + public List getIndexedFields() { return Collections.unmodifiableList(indexedFields); } @@ -252,9 +266,10 @@ public VariableElement getDeclaredField(String fieldName) { * Builds the meta data structures for this class. Any errors or messages will be * posted on the provided Messager. * + * @param moduleMetaData pre-processed module meta data. * @return True if meta data was correctly created and processing can continue, false otherwise. */ - public boolean generate() { + public boolean generate(ModuleMetaData moduleMetaData) { // Get the package of the class Element enclosingElement = classType.getEnclosingElement(); if (!enclosingElement.getKind().equals(ElementKind.PACKAGE)) { @@ -262,6 +277,7 @@ public boolean generate() { return false; } + // Check if the @RealmClass is considered valid with respect to the type hierarchy TypeElement parentElement = (TypeElement) Utils.getSuperClass(classType); if (!parentElement.toString().equals("java.lang.Object") && !parentElement.toString().equals("io.realm.RealmObject")) { Utils.error("Valid model classes must either extend RealmObject or implement RealmModel.", classType); @@ -271,6 +287,31 @@ public boolean generate() { PackageElement packageElement = (PackageElement) enclosingElement; packageName = packageElement.getQualifiedName().toString(); + // Determine naming rules for this class + String qualifiedClassName = packageName + "." + javaClassName; + NameConverter moduleClassNameFormatter = moduleMetaData.getClassNameFormatter(qualifiedClassName); + defaultFieldNameFormatter = moduleMetaData.getFieldNameFormatter(qualifiedClassName); + + RealmClass realmClassAnnotation = classType.getAnnotation(RealmClass.class); + // If name has been specifically set, it should override any module policy. + if (!realmClassAnnotation.name().equals("")) { + internalClassName = realmClassAnnotation.name(); + } else { + internalClassName = moduleClassNameFormatter.convert(javaClassName); + } + if (internalClassName.length() > MAX_CLASSNAME_LENGTH) { + Utils.error(String.format(Locale.US, "Internal class name is too long. Class '%s' " + + "is converted to '%s', which is longer than the maximum allowed of %d characters", + javaClassName, internalClassName, 57)); + return false; + } + + // If field name policy has been explicitly set, override the module field name policy + if (realmClassAnnotation.fieldNamingPolicy() != RealmNamingPolicy.NO_POLICY) { + defaultFieldNameFormatter = Utils.getNameFormatter(realmClassAnnotation.fieldNamingPolicy()); + } + + // Categorize and check the rest of the file if (!categorizeClassElements()) { return false; } if (!checkCollectionTypes()) { return false; } if (!checkReferenceTypes()) { return false; } @@ -300,7 +341,7 @@ private boolean categorizeClassElements() { } if (fields.size() == 0) { - Utils.error(String.format(Locale.US, "Class \"%s\" must contain at least 1 persistable field.", className)); + Utils.error(String.format(Locale.US, "Class \"%s\" must contain at least 1 persistable field.", javaClassName)); } return true; @@ -414,7 +455,7 @@ private boolean checkDefaultConstructor() { if (!hasDefaultConstructor) { Utils.error(String.format(Locale.US, "Class \"%s\" must declare a public constructor with no arguments if it contains custom constructors.", - className)); + javaClassName)); return false; } else { return true; @@ -430,7 +471,7 @@ private boolean checkForFinalFields() { continue; } - Utils.error(String.format(Locale.US, "Class \"%s\" contains illegal final field \"%s\".", className, + Utils.error(String.format(Locale.US, "Class \"%s\" contains illegal final field \"%s\".", javaClassName, field.getSimpleName().toString())); return false; @@ -443,7 +484,7 @@ private boolean checkForVolatileFields() { if (field.getModifiers().contains(Modifier.VOLATILE)) { Utils.error(String.format(Locale.US, "Class \"%s\" contains illegal volatile field \"%s\".", - className, + javaClassName, field.getSimpleName().toString())); return false; } @@ -452,31 +493,47 @@ private boolean checkForVolatileFields() { } private boolean categorizeField(Element element) { - VariableElement field = (VariableElement) element; + VariableElement fieldRef = (VariableElement) element; // completely ignore any static fields - if (field.getModifiers().contains(Modifier.STATIC)) { return true; } + if (fieldRef.getModifiers().contains(Modifier.STATIC)) { return true; } // Ignore fields marked with @Ignore or if they are transient - if (field.getAnnotation(Ignore.class) != null || field.getModifiers().contains(Modifier.TRANSIENT)) { + if (fieldRef.getAnnotation(Ignore.class) != null || fieldRef.getModifiers().contains(Modifier.TRANSIENT)) { return true; } + // Determine name for field + String internalFieldName = getInternalFieldName(fieldRef, defaultFieldNameFormatter); + RealmFieldElement field = new RealmFieldElement(fieldRef, internalFieldName); + if (field.getAnnotation(Index.class) != null) { if (!categorizeIndexField(element, field)) { return false; } } // @Required annotation of RealmList field only affects its value type, not field itself. if (Utils.isRealmList(field)) { + boolean hasRequiredAnnotation = hasRequiredAnnotation(field); + final List listGenericType = ((DeclaredType) field.asType()).getTypeArguments(); + boolean containsRealmModelClasses = (!listGenericType.isEmpty() && Utils.isRealmModel(listGenericType.get(0))); + + // @Required not allowed if the list contains Realm model classes + if (hasRequiredAnnotation && containsRealmModelClasses) { + Utils.error("@Required not allowed on RealmList's that contain other Realm model classes."); + return false; + } + + // @Required thus only makes sense for RealmLists with primitive types // We only check @Required annotation. @org.jetbrains.annotations.NotNull annotation should not affect nullability of the list values. - if (!hasRequiredAnnotation(field)) { - final List fieldTypeArguments = ((DeclaredType) field.asType()).getTypeArguments(); - if (fieldTypeArguments.isEmpty() || !Utils.isRealmModel(fieldTypeArguments.get(0))) { + if (!hasRequiredAnnotation) { + if (!containsRealmModelClasses) { nullableValueListFields.add(field); } } } else if (isRequiredField(field)) { - categorizeRequiredField(element, field); + if (!checkBasicRequiredAnnotationUsage(element, field)) { + return false; + } } else { // The field doesn't have the @Required and @org.jetbrains.annotations.NotNull annotation. // Without @Required annotation, boxed types/RealmObject/Date/String/bytes should be added to @@ -508,6 +565,22 @@ private boolean categorizeField(Element element) { return true; } + private String getInternalFieldName(VariableElement field, NameConverter defaultConverter) { + RealmField nameAnnotation = field.getAnnotation(RealmField.class); + if (nameAnnotation != null) { + String declaredName = nameAnnotation.name(); + if (!declaredName.equals("")) { + return declaredName; + } else { + Utils.note(String.format("Empty internal name defined on @RealmField. " + + "Falling back to named used by Java model class: %s", field.getSimpleName()), field); + return field.getSimpleName().toString(); + } + } else { + return defaultConverter.convert(field.getSimpleName().toString()); + } + } + /** * This method only checks if the field has {@code @Required} annotation. * In most cases, you should use {@link #isRequiredField(VariableElement)} to take into account @@ -549,13 +622,13 @@ private boolean isRequiredField(VariableElement field) { // The field has the @Index annotation. It's only valid for column types: // STRING, DATE, INTEGER, BOOLEAN, and RealmMutableInteger - private boolean categorizeIndexField(Element element, VariableElement variableElement) { + private boolean categorizeIndexField(Element element, RealmFieldElement fieldElement) { boolean indexable = false; - if (Utils.isMutableRealmInteger(variableElement)) { + if (Utils.isMutableRealmInteger(fieldElement)) { indexable = true; } else { - Constants.RealmFieldType realmType = Constants.JAVA_TO_REALM_TYPES.get(variableElement.asType().toString()); + Constants.RealmFieldType realmType = Constants.JAVA_TO_REALM_TYPES.get(fieldElement.asType().toString()); if (realmType != null) { switch (realmType) { case STRING: @@ -568,7 +641,7 @@ private boolean categorizeIndexField(Element element, VariableElement variableEl } if (indexable) { - indexedFields.add(variableElement); + indexedFields.add(fieldElement); return true; } @@ -577,17 +650,18 @@ private boolean categorizeIndexField(Element element, VariableElement variableEl } // The field has the @Required annotation - private void categorizeRequiredField(Element element, VariableElement variableElement) { + // Returns `true` if the field could be correctly validated, `false` if an error was reported. + private boolean checkBasicRequiredAnnotationUsage(Element element, VariableElement variableElement) { if (Utils.isPrimitiveType(variableElement)) { Utils.error(String.format(Locale.US, "@Required or @NotNull annotation is unnecessary for primitive field \"%s\".", element)); - return; + return false; } if (Utils.isRealmModel(variableElement)) { Utils.error(String.format(Locale.US, "Field \"%s\" with type \"%s\" cannot be @Required or @NotNull.", element, element.asType())); - return; + return false; } // Should never get here - user should remove @Required @@ -596,34 +670,38 @@ private void categorizeRequiredField(Element element, VariableElement variableEl "Field \"%s\" with type \"%s\" appears to be nullable. Consider removing @Required.", element, element.asType())); + + return false; } + + return true; } // The field has the @PrimaryKey annotation. It is only valid for // String, short, int, long and must only be present one time - private boolean categorizePrimaryKeyField(VariableElement variableElement) { + private boolean categorizePrimaryKeyField(RealmFieldElement fieldElement) { if (primaryKey != null) { Utils.error(String.format(Locale.US, "A class cannot have more than one @PrimaryKey. Both \"%s\" and \"%s\" are annotated as @PrimaryKey.", primaryKey.getSimpleName().toString(), - variableElement.getSimpleName().toString())); + fieldElement.getSimpleName().toString())); return false; } - TypeMirror fieldType = variableElement.asType(); + TypeMirror fieldType = fieldElement.asType(); if (!isValidPrimaryKeyType(fieldType)) { Utils.error(String.format(Locale.US, "Field \"%s\" with type \"%s\" cannot be used as primary key. See @PrimaryKey for legal types.", - variableElement.getSimpleName().toString(), + fieldElement.getSimpleName().toString(), fieldType)); return false; } - primaryKey = variableElement; + primaryKey = fieldElement; // Also add as index. All types of primary key can be indexed. - if (!indexedFields.contains(variableElement)) { - indexedFields.add(variableElement); + if (!indexedFields.contains(fieldElement)) { + indexedFields.add(fieldElement); } return true; @@ -657,5 +735,10 @@ private boolean isValidPrimaryKeyType(TypeMirror type) { } return false; } + + public Element getClassElement() { + return classType; + } + } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java index af2bf3cc35..aa4ef4ad8c 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java @@ -16,6 +16,7 @@ package io.realm.processor; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; @@ -31,35 +32,67 @@ import javax.lang.model.element.TypeElement; import io.realm.annotations.RealmModule; +import io.realm.annotations.RealmNamingPolicy; +import io.realm.processor.nameconverter.NameConverter; /** * Utility class for holding metadata for the Realm modules. + *

            + * Modules are inherently difficult to process because a model class can be part of multiple modules + * that contain information required by the model class (e.g. class/field naming policies). At the + * same time, the module will need the data from processed model classes to fully complete its + * analysis (e.g. to ensure that only valid Realm model classes are added to the module). + *

            + * For this reason, processing modules are separated into 3 steps: + *

              + *
            1. + * Pre-processing. Done by calling {@link #preProcess(Set)}, which will do an initial parse + * of the modules and build up all information it can before processing any model classes. + *
            2. + *
            3. + * Process model classes. See {@link ClassMetaData#generate(ModuleMetaData)}. + *
            4. + *
            5. + * Post-processing. Done by calling {@link #postProcess(ClassCollection)}. All modules can now + * be fully verified, and all metadata required to output module files can be generated. + *
            6. + *
            */ public class ModuleMetaData { - private final Set availableClasses; + // Pre-processing + // + private Set globalModules = new HashSet<>(); // All modules with `allClasses = true` set + private Map> specificClassesModules = new HashMap<>(); // Modules with classes specifically named + private Map classNamingPolicy = new HashMap(); + private Map fieldNamingPolicy = new HashMap(); + private Map moduleAnnotations = new HashMap<>(); + + // Post-processing + // private Map> modules = new HashMap>(); private Map> libraryModules = new HashMap>(); - private Map classMetaData = new HashMap(); // - private boolean shouldCreateDefaultModule; - public ModuleMetaData(Set availableClasses) { - this.availableClasses = availableClasses; - for (ClassMetaData classMetaData : availableClasses) { - this.classMetaData.put(classMetaData.getFullyQualifiedClassName(), classMetaData); - } - } + private boolean shouldCreateDefaultModule; /** - * Builds the meta data structures for this class. Any errors or messages will be posted on the provided Messager. + * Builds all meta data structures that can be calculated before processing any model classes. + * Any errors or messages will be posted on the provided Messager. * - * @return True if meta data was correctly created and processing can continue, false otherwise. + * @return True if meta data was correctly created and processing of model classes can continue, false otherwise. */ - public boolean generate(Set clazzes) { + public boolean preProcess(Set moduleClasses) { + + // Tracks all module settings with `allClasses` enabled + Set globalModuleInfo = new HashSet<>(); + + // Tracks which modules a class was mentioned in by name using `classes = { ... }` + // > classSpecificModuleInfo = new HashMap<>(); // Check that modules are setup correctly - for (Element classElement : clazzes) { + for (Element classElement : moduleClasses) { String classSimpleName = classElement.getSimpleName().toString(); // Check that the annotation is only applied to a class @@ -69,38 +102,136 @@ public boolean generate(Set clazzes) { } // Check that allClasses and classes are not set at the same time - RealmModule module = classElement.getAnnotation(RealmModule.class); + RealmModule moduleAnnoation = classElement.getAnnotation(RealmModule.class); Utils.note("Processing module " + classSimpleName); - if (module.allClasses() && hasCustomClassList(classElement)) { + if (moduleAnnoation.allClasses() && hasCustomClassList(classElement)) { Utils.error("Setting @RealmModule(allClasses=true) will override @RealmModule(classes={...}) in " + classSimpleName); return false; } - // Check that classes added are proper Realm model classes - String qualifiedName = ((TypeElement) classElement).getQualifiedName().toString(); - Set classes; - if (module.allClasses()) { - classes = availableClasses; - } else { - classes = new LinkedHashSet(); - Set classNames = getClassMetaDataFromModule(classElement); - for (String fullyQualifiedClassName : classNames) { - ClassMetaData metadata = classMetaData.get(fullyQualifiedClassName); - if (metadata == null) { - Utils.error(Utils.stripPackage(fullyQualifiedClassName) + " could not be added to the module. " + - "Only classes extending RealmObject, which are part of this project, can be added."); + // Validate that naming policies are correctly configured. + if (!validateNamingPolicies(globalModuleInfo, classSpecificModuleInfo, (TypeElement) classElement, moduleAnnoation)) { + return false; + } + + moduleAnnotations.put(((TypeElement) classElement).getQualifiedName().toString(), moduleAnnoation); + } + + return true; + } + + /** + * Validates that the class/field naming policy for this module is correct. + * + * @param globalModuleInfo list of all modules with `allClasses` set + * @param classSpecificModuleInfo map of explicit classes and which modules they are explicitly mentioned in. + * @param classElement class element currently being validated + * @param moduleAnnotation annotation on this class. + * @return {@code true} if everything checks out, {@code false} if an error was found and reported. + */ + private boolean validateNamingPolicies(Set globalModuleInfo, Map> classSpecificModuleInfo, TypeElement classElement, RealmModule moduleAnnotation) { + RealmNamingPolicy classNamePolicy = moduleAnnotation.classNamingPolicy(); + RealmNamingPolicy fieldNamePolicy = moduleAnnotation.fieldNamingPolicy(); + String qualifiedModuleClassName = classElement.getQualifiedName().toString(); + ModulePolicyInfo moduleInfo = new ModulePolicyInfo(qualifiedModuleClassName, classNamePolicy, fieldNamePolicy); + + // The difference between `allClasses` and a list of classes is a bit tricky at this stage + // as we haven't processed the full list of classes yet. We therefore need to treat + // each case specifically :( + // We do not compare against the default module as it is always configured correctly + // with NO_POLICY, meaning it will not trigger any errors. + if (moduleAnnotation.allClasses()) { + // Check for conflicts with all other modules with `allClasses` set. + for (ModulePolicyInfo otherModuleInfo : globalModuleInfo) { + if (checkAndReportPolicyConflict(moduleInfo, otherModuleInfo)) { + return false; + } + } + + // Check for conflicts with specifically named classes. This can happen if another + // module is listing specific classes with another policy. + for (Map.Entry> classPolicyInfo : classSpecificModuleInfo.entrySet()) { + for (ModulePolicyInfo otherModuleInfo : classPolicyInfo.getValue()) { + if (checkAndReportPolicyConflict(moduleInfo, otherModuleInfo)) { return false; } - classes.add(metadata); } } - // Create either a Library or App module - if (module.library()) { - libraryModules.put(qualifiedName, classes); - } else { - modules.put(qualifiedName, classes); + // Everything checks out. Add moduleInfo so we can track it for the next module. + globalModuleInfo.add(moduleInfo); + globalModules.add(qualifiedModuleClassName); + + } else { + // We need to verify each class in the modules class list + Set classNames = getClassListFromModule(classElement); + for (String qualifiedClassName : classNames) { + + // Check that no other module with `allClasses` conflict with this specific + // class configuration + for (ModulePolicyInfo otherModuleInfo : globalModuleInfo) { + if (checkAndReportPolicyConflict(moduleInfo, otherModuleInfo)) { + return false; + } + } + + // Check that this specific class isn't conflicting with another module + // specifically mentioning it using `classes = { ... }` + List otherModules = classSpecificModuleInfo.get(qualifiedClassName); + if (otherModules != null) { + for (ModulePolicyInfo otherModuleInfo : otherModules) { + if (checkAndReportPolicyConflict(qualifiedClassName, moduleInfo, otherModuleInfo)) { + return false; + } + } + } + + // Keep track of the specific class for other module checks. We only + // need to track the latest module seen as previous errors would have been + // caught in a previous iteration of the loop. + if (!classSpecificModuleInfo.containsKey(qualifiedClassName)) { + classSpecificModuleInfo.put(qualifiedClassName, new ArrayList<>()); + } + classSpecificModuleInfo.get(qualifiedClassName).add(moduleInfo); + } + specificClassesModules.put(qualifiedModuleClassName, classNames); + } + + classNamingPolicy.put(qualifiedModuleClassName, classNamePolicy); + fieldNamingPolicy.put(qualifiedModuleClassName, fieldNamePolicy); + return true; + } + + /** + * All model classes have now been processed and the final validation of modules can occur. + * Any errors or messages will be posted on the provided Messager. + * + * @param modelClasses all Realm model classes found by the annotation processor. + * @return {@code true} if the module is valid, {@code false} otherwise. + */ + public boolean postProcess(ClassCollection modelClasses) { + + // Process all global modules + for (String qualifiedModuleClassName : globalModules) { + Set classData = new LinkedHashSet<>(); + classData.addAll(modelClasses.getClasses()); + defineModule(qualifiedModuleClassName, classData); + } + + // Process all modules with specific classes + for (Map.Entry> module : specificClassesModules.entrySet()) { + String qualifiedModuleClassName = module.getKey(); + Set classData = new LinkedHashSet<>(); + for (String qualifiedModelClassName : module.getValue()) { + if (!modelClasses.containsQualifiedClass(qualifiedModelClassName)) { + Utils.error(Utils.stripPackage(qualifiedModelClassName) + " could not be added to the module. " + + "Only classes extending RealmObject or implementing RealmModel, which are part of this project, can be added."); + return false; + + } + classData.add(modelClasses.getClassFromQualifiedName(qualifiedModelClassName)); } + defineModule(qualifiedModuleClassName, classData); } // Check that app and library modules are not mixed @@ -112,19 +243,80 @@ public boolean generate(Set clazzes) { // Create default Realm module if needed. // Note: Kotlin will trigger the annotation processor even if no Realm annotations are used. // The DefaultRealmModule should not be created in this case either. - if (libraryModules.size() == 0 && availableClasses.size() > 0) { + if (libraryModules.size() == 0 && modelClasses.size() > 0) { shouldCreateDefaultModule = true; String defaultModuleName = Constants.REALM_PACKAGE_NAME + "." + Constants.DEFAULT_MODULE_CLASS_NAME; - modules.put(defaultModuleName, availableClasses); + modules.put(defaultModuleName, modelClasses.getClasses()); } return true; } + private void defineModule(String qualifiedModuleClassName, Set classData) { + if (!classData.isEmpty()) { + if (moduleAnnotations.get(qualifiedModuleClassName).library()) { + libraryModules.put(qualifiedModuleClassName, classData); + } else { + modules.put(qualifiedModuleClassName, classData); + } + } + } + + // Checks if two modules have policy conflicts. Returns true if a conflict was found and reported. + private boolean checkAndReportPolicyConflict(ModulePolicyInfo moduleInfo, ModulePolicyInfo otherModuleInfo) { + return checkAndReportPolicyConflict(null, moduleInfo, otherModuleInfo); + } + + /** + * Check for name policy conflicts and report the error if found. + * + * @param className optional class name if a specific class is being checked. + * @param moduleInfo current module. + * @param otherModuleInfo already processed module. + * @return {@code true} if any errors was reported, {@code false} otherwise. + */ + private boolean checkAndReportPolicyConflict(String className, ModulePolicyInfo moduleInfo, ModulePolicyInfo otherModuleInfo) { + boolean foundErrors = false; + + // Check class naming policy + RealmNamingPolicy classPolicy = moduleInfo.classNamePolicy; + RealmNamingPolicy otherClassPolicy = otherModuleInfo.classNamePolicy; + if (classPolicy != RealmNamingPolicy.NO_POLICY + && otherClassPolicy != RealmNamingPolicy.NO_POLICY + && classPolicy != otherClassPolicy) { + Utils.error(String.format("The modules %s and %s disagree on the class naming policy%s: %s vs. %s. " + + "They same policy must be used.", + moduleInfo.qualifiedModuleClassName, + otherModuleInfo.qualifiedModuleClassName, + (className != null) ? " for " + className : "", + classPolicy, + otherClassPolicy)); + foundErrors = true; + } + + // Check field naming policy + RealmNamingPolicy fieldPolicy = moduleInfo.fieldNamePolicy; + RealmNamingPolicy otherFieldPolicy = otherModuleInfo.fieldNamePolicy; + if (fieldPolicy != RealmNamingPolicy.NO_POLICY + && otherFieldPolicy != RealmNamingPolicy.NO_POLICY + && fieldPolicy != otherFieldPolicy) { + Utils.error(String.format("The modules %s and %s disagree on the field naming policy%s: %s vs. %s. " + + "They same policy should be used.", + moduleInfo.qualifiedModuleClassName, + otherModuleInfo.qualifiedModuleClassName, + (className != null) ? " for " + className : "", + fieldPolicy, + otherFieldPolicy)); + foundErrors = true; + } + + return foundErrors; + } + // Detour needed to access the class elements in the array // See http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ @SuppressWarnings("unchecked") - private Set getClassMetaDataFromModule(Element classElement) { + private Set getClassListFromModule(Element classElement) { AnnotationMirror annotationMirror = getAnnotationMirror(classElement); AnnotationValue annotationValue = getAnnotationValue(annotationMirror); Set classes = new HashSet(); @@ -191,4 +383,84 @@ public Map> getAllModules() { public boolean shouldCreateDefaultModule() { return shouldCreateDefaultModule; } + + /** + * Only available after {@link #preProcess(Set)} has run. + * Returns the module name policy the given name. + */ + public NameConverter getClassNameFormatter(String qualifiedClassName) { + // We already validated that module definitions all agree on the same name policy + // so just find first match + if (!globalModules.isEmpty()) { + return Utils.getNameFormatter(classNamingPolicy.get(globalModules.iterator().next())); + } + + // No global modules found, so find match in modules specifically listing the class. + // We already validated that all modules agree on the converter, so just find first match. + for (Map.Entry> moduleInfo : specificClassesModules.entrySet()) { + if (moduleInfo.getValue().contains(qualifiedClassName)) { + return Utils.getNameFormatter(classNamingPolicy.get(moduleInfo.getKey())); + } + } + + // No policy was provided anywhere for this class + return Utils.getNameFormatter(RealmNamingPolicy.NO_POLICY); + } + + + /** + * Only available after {@link #preProcess(Set)} has run. + * + * Returns the module name policy the field names. + * + * @param qualifiedClassName + */ + public NameConverter getFieldNameFormatter(String qualifiedClassName) { + // We already validated that module definitions all agree on the same name policy + // so just find first match + if (!globalModules.isEmpty()) { + return Utils.getNameFormatter(fieldNamingPolicy.get(globalModules.iterator().next())); + } + + for (Map.Entry> moduleInfo : specificClassesModules.entrySet()) { + if (moduleInfo.getValue().contains(qualifiedClassName)) { + return Utils.getNameFormatter(fieldNamingPolicy.get(moduleInfo.getKey())); + } + } + + return Utils.getNameFormatter(RealmNamingPolicy.NO_POLICY); + } + + // Tuple helper class + private class ModulePolicyInfo { + public final String qualifiedModuleClassName; + public final RealmNamingPolicy classNamePolicy; + public final RealmNamingPolicy fieldNamePolicy; + + public ModulePolicyInfo(String qualifiedModuleClassName, RealmNamingPolicy classNamePolicy, RealmNamingPolicy fieldNamePolicy) { + this.qualifiedModuleClassName = qualifiedModuleClassName; + this.classNamePolicy = classNamePolicy; + this.fieldNamePolicy = fieldNamePolicy; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ModulePolicyInfo that = (ModulePolicyInfo) o; + + if (!qualifiedModuleClassName.equals(that.qualifiedModuleClassName)) return false; + if (classNamePolicy != that.classNamePolicy) return false; + return fieldNamePolicy == that.fieldNamePolicy; + } + + @Override + public int hashCode() { + int result = qualifiedModuleClassName.hashCode(); + result = 31 * result + classNamePolicy.hashCode(); + result = 31 * result + fieldNamePolicy.hashCode(); + return result; + } + } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.java new file mode 100644 index 0000000000..578766bc81 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.java @@ -0,0 +1,124 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.processor; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * Wrapper for {@link javax.lang.model.element.VariableElement} that makes it possible to add + * additional metadata. + */ +public class RealmFieldElement implements VariableElement { + + private final VariableElement fieldReference; + private final String internalFieldName; // Name used for this field internally in Realm. + + public RealmFieldElement(VariableElement fieldReference, String internalFieldName) { + this.fieldReference = fieldReference; + this.internalFieldName = internalFieldName; + } + + public VariableElement getFieldReference() { + return fieldReference; + } + + /** + * Returns the name that Realm Core uses internally when saving data to this field. + * {@link #getSimpleName()} returns the name in the Java class. + */ + public String getInternalFieldName() { + return internalFieldName; + } + + public Set getModifiers() { + return fieldReference.getModifiers(); + } + + public TypeMirror asType() { + return fieldReference.asType(); + } + + @Override + public ElementKind getKind() { + return null; + } + + @Override + public Object getConstantValue() { + return fieldReference.getConstantValue(); + } + + /** + * Returns the name for this field in the Java class. + * {@link #getInternalFieldName()} returns the name used by Realm Core for the same field. + */ + @Override + public Name getSimpleName() { + return fieldReference.getSimpleName(); + } + + @Override + public Element getEnclosingElement() { + return fieldReference.getEnclosingElement(); + } + + @Override + public List getEnclosedElements() { + return fieldReference.getEnclosedElements(); + } + + @Override + public List getAnnotationMirrors() { + return fieldReference.getAnnotationMirrors(); + } + + @Override + public A getAnnotation(Class aClass) { + return fieldReference.getAnnotation(aClass); + } + + @Override + public A[] getAnnotationsByType(Class aClass) { + return fieldReference.getAnnotationsByType(aClass); + } + + @Override + public R accept(ElementVisitor elementVisitor, P p) { + return fieldReference.accept(elementVisitor, p); + } + + @Override + public String toString() { + // Mimics the behaviour of the standard implementation of VariableElement `toString()` + // Some methods in RealmProxyClassGenerator depended on this. + return getSimpleName().toString(); + } + + public String getJavaName() { + return getSimpleName().toString(); + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index 713ab6b2ef..c557e866a3 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -66,14 +65,12 @@ *
          • A RealmObjectProxy object is created for each class annotated with {@link io.realm.annotations.RealmClass}. This * proxy extends the original RealmObject class and rewires all field access to point to the native Realm memory instead of * Java memory. It also adds some static helper methods to the class.
          • - *

            *

          • The annotation processor is either in "library" mode or in "app" mode. This is defined by having a class * annotated with @RealmModule(library = true). It is not allowed to have both a class with library = true and * library = false in the same IntelliJ module and it will cause the annotation processor to throw an exception. If no * library modules are defined, we will create a DefaultRealmModule containing all known RealmObjects and with the * {@code @RealmModule} annotation. Realm automatically knows about this module, but it is still possible for users to create * their own modules with a subset of model classes.
          • - *

            *

          • For each class annotated with @RealmModule a matching Mediator class is created (including the default one). This * class has an interface that matches the static helper methods for the proxy classes. All access to these static * helper methods should be done through this Mediator.
          • @@ -119,6 +116,7 @@ */ @SupportedAnnotationTypes({ "io.realm.annotations.RealmClass", + "io.realm.annotations.RealmField", "io.realm.annotations.Ignore", "io.realm.annotations.Index", "io.realm.annotations.PrimaryKey", @@ -130,10 +128,11 @@ public class RealmProcessor extends AbstractProcessor { // Don't consume annotations. This allows 3rd party annotation processors to run. private static final boolean CONSUME_ANNOTATIONS = false; + private static final boolean ABORT = true; // Abort the annotation processor by consuming all annotations + private final ClassCollection classCollection = new ClassCollection(); // Metadata for all classes found + private ModuleMetaData moduleMetaData; // Metadata for all modules found - // List of all fields maintained by Realm (RealmResults) - private final Set classesToValidate = new LinkedHashSet(); // List of backlinks private final Set backlinksToValidate = new HashSet(); @@ -153,27 +152,29 @@ public boolean process(Set annotations, RoundEnvironment RealmVersionChecker.getInstance(processingEnv).executeRealmVersionUpdate(); } - if (roundEnv.errorRaised()) { return true; } + if (roundEnv.errorRaised()) { return ABORT; } if (!hasProcessedModules) { Utils.initialize(processingEnv); + TypeMirrors typeMirrors = new TypeMirrors(processingEnv); - if (!processAnnotations(roundEnv)) { return true; } - + // Build up internal metadata while validating as much as possible + if (!preProcessModules(roundEnv)) { return ABORT; } + if (!processClassAnnotations(roundEnv, typeMirrors)) { return ABORT; } + if (!postProcessModules()) { return ABORT; } + if (!validateBacklinks()) { return ABORT; } hasProcessedModules = true; - if (!processModules(roundEnv)) { return true; } - } - if (roundEnv.processingOver()) { - if (!validateBacklinks()) { return true; } + // Create all files + if (!createProxyClassFiles(typeMirrors)) { return ABORT; } + if (!createModuleFiles(roundEnv)) { return ABORT; } } return CONSUME_ANNOTATIONS; } // Create all proxy classes - private boolean processAnnotations(RoundEnvironment roundEnv) { - final TypeMirrors typeMirrors = new TypeMirrors(processingEnv); + private boolean processClassAnnotations(RoundEnvironment roundEnv, TypeMirrors typeMirrors) { for (Element classElement : roundEnv.getElementsAnnotatedWith(RealmClass.class)) { @@ -192,39 +193,28 @@ private boolean processAnnotations(RoundEnvironment roundEnv) { ClassMetaData metadata = new ClassMetaData(processingEnv, typeMirrors, (TypeElement) classElement); if (!metadata.isModelClass()) { continue; } - Utils.note("Processing class " + metadata.getSimpleClassName()); - if (!metadata.generate()) { return false; } + Utils.note("Processing class " + metadata.getSimpleJavaClassName()); + if (!metadata.generate(moduleMetaData)) { return false; } - classesToValidate.add(metadata); + classCollection.addClass(metadata); backlinksToValidate.addAll(metadata.getBacklinkFields()); - - RealmProxyInterfaceGenerator interfaceGenerator = new RealmProxyInterfaceGenerator(processingEnv, metadata); - try { - interfaceGenerator.generate(); - } catch (IOException e) { - Utils.error(e.getMessage(), classElement); - } - - RealmProxyClassGenerator sourceCodeGenerator = new RealmProxyClassGenerator(processingEnv, typeMirrors, metadata); - try { - sourceCodeGenerator.generate(); - } catch (IOException e) { - Utils.error(e.getMessage(), classElement); - } catch (UnsupportedOperationException e) { - Utils.error(e.getMessage(), classElement); - } } return true; } - // Returns true if modules was processed successfully, false otherwise - private boolean processModules(RoundEnvironment roundEnv) { - ModuleMetaData moduleMetaData = new ModuleMetaData(classesToValidate); - if (!moduleMetaData.generate(roundEnv.getElementsAnnotatedWith(RealmModule.class))) { - return false; - } + // Returns true if modules were processed successfully, false otherwise + private boolean preProcessModules(RoundEnvironment roundEnv) { + moduleMetaData = new ModuleMetaData(); + return moduleMetaData.preProcess(roundEnv.getElementsAnnotatedWith(RealmModule.class)); + } + // Returns true of modules where succesfully validated, false otherwise + private boolean postProcessModules() { + return moduleMetaData.postProcess(classCollection); + } + + private boolean createModuleFiles(RoundEnvironment roundEnv) { // Create default module if needed if (moduleMetaData.shouldCreateDefaultModule()) { if (!createDefaultModule()) { @@ -242,6 +232,27 @@ private boolean processModules(RoundEnvironment roundEnv) { return true; } + private boolean createProxyClassFiles(TypeMirrors typeMirrors) { + for (ClassMetaData metadata : classCollection.getClasses()) { + RealmProxyInterfaceGenerator interfaceGenerator = new RealmProxyInterfaceGenerator(processingEnv, metadata); + try { + interfaceGenerator.generate(); + } catch (IOException e) { + Utils.error(e.getMessage(), metadata.getClassElement()); + return false; + } + + RealmProxyClassGenerator sourceCodeGenerator = new RealmProxyClassGenerator(processingEnv, typeMirrors, metadata, classCollection); + try { + sourceCodeGenerator.generate(); + } catch (IOException | UnsupportedOperationException e) { + Utils.error(e.getMessage(), metadata.getClassElement()); + return false; + } + } + return true; + } + private boolean createDefaultModule() { Utils.note("Creating DefaultRealmModule"); DefaultModuleGenerator defaultModuleGenerator = new DefaultModuleGenerator(processingEnv); @@ -278,13 +289,8 @@ private boolean createMediator(String simpleModuleName, Set modul private boolean validateBacklinks() { boolean allValid = true; - Map realmClasses = new HashMap(classesToValidate.size()); - for (ClassMetaData classData : classesToValidate) { - realmClasses.put(classData.getFullyQualifiedClassName(), classData); - } - for (Backlink backlink : backlinksToValidate) { - ClassMetaData clazz = realmClasses.get(backlink.getSourceClass()); + ClassMetaData clazz = classCollection.getClassFromQualifiedName(backlink.getSourceClass()); // If the class is not here it might be part of some other compilation unit. if (clazz == null) { continue; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 54e8560b88..1a002e258d 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -78,21 +78,25 @@ public class RealmProxyClassGenerator { private final ProcessingEnvironment processingEnvironment; private final TypeMirrors typeMirrors; private final ClassMetaData metadata; - private final String simpleClassName; - private final String qualifiedClassName; + private final ClassCollection classCollection; + private final String simpleJavaClassName; + private final String qualifiedJavaClassName; + private final String internalClassName; private final String interfaceName; private final String qualifiedGeneratedClassName; private final boolean suppressWarnings; - public RealmProxyClassGenerator(ProcessingEnvironment processingEnvironment, TypeMirrors typeMirrors, ClassMetaData metadata) { + public RealmProxyClassGenerator(ProcessingEnvironment processingEnvironment, TypeMirrors typeMirrors, ClassMetaData metadata, ClassCollection classes) { this.processingEnvironment = processingEnvironment; this.typeMirrors = typeMirrors; this.metadata = metadata; - this.simpleClassName = metadata.getSimpleClassName(); - this.qualifiedClassName = metadata.getFullyQualifiedClassName(); - this.interfaceName = Utils.getProxyInterfaceName(simpleClassName); + this.classCollection = classes; + this.simpleJavaClassName = metadata.getSimpleJavaClassName(); + this.qualifiedJavaClassName = metadata.getFullyQualifiedClassName(); + this.internalClassName = metadata.getInternalClassName(); + this.interfaceName = Utils.getProxyInterfaceName(simpleJavaClassName); this.qualifiedGeneratedClassName = String.format(Locale.US, "%s.%s", - Constants.REALM_PACKAGE_NAME, Utils.getProxyClassName(simpleClassName)); + Constants.REALM_PACKAGE_NAME, Utils.getProxyClassName(simpleJavaClassName)); // See the configuration for the debug build type, // in the realm-library project, for an example of how to set this flag. @@ -125,7 +129,7 @@ public void generate() throws IOException, UnsupportedOperationException { qualifiedGeneratedClassName, // full qualified name of the item to generate "class", // the type of the item EnumSet.of(Modifier.PUBLIC), // modifiers to apply - qualifiedClassName, // class to extend + qualifiedJavaClassName, // class to extend "RealmObjectProxy", // interfaces to implement interfaceName) .emitEmptyLine(); @@ -183,16 +187,19 @@ private void emitColumnInfoClass(JavaWriter writer) throws IOException { "OsSchemaInfo", "schemaInfo"); writer.emitStatement("super(%s)", metadata.getFields().size()); writer.emitStatement("OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo(\"%1$s\")", - simpleClassName); - for (VariableElement field : metadata.getFields()) { + internalClassName); + for (RealmFieldElement field : metadata.getFields()) { writer.emitStatement( - "this.%1$sIndex = addColumnDetails(\"%1$s\", objectSchemaInfo)", - field.getSimpleName().toString()); + "this.%1$sIndex = addColumnDetails(\"%1$s\", \"%2$s\", objectSchemaInfo)", + field.getJavaName(), + field.getInternalFieldName()); } for (Backlink backlink : metadata.getBacklinkFields()) { writer.emitStatement( "addBacklinkDetails(schemaInfo, \"%s\", \"%s\", \"%s\")", - backlink.getTargetField(), Utils.stripPackage(backlink.getSourceClass()), backlink.getSourceField()); + backlink.getTargetField(), + classCollection.getClassFromQualifiedName(backlink.getSourceClass()).getInternalClassName(), + backlink.getSourceField()); } writer.endConstructor() .emitEmptyLine(); @@ -246,7 +253,7 @@ private void emitClassFields(JavaWriter writer) throws IOException { private void emitInstanceFields(JavaWriter writer) throws IOException { writer.emitEmptyLine() .emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE)) - .emitField("ProxyState<" + qualifiedClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)); + .emitField("ProxyState<" + qualifiedJavaClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)); for (VariableElement variableElement : metadata.getFields()) { if (Utils.isMutableRealmInteger(variableElement)) { @@ -277,7 +284,7 @@ private void emitMutableRealmIntegerField(JavaWriter writer, VariableElement var + " @Override protected ProxyState<%1$s> getProxyState() { return proxyState; }\n" + " @Override protected long getColumnIndex() { return columnInfo.%2$s; }\n" + "}", - qualifiedClassName, columnIndexVarName(variableElement))); + qualifiedJavaClassName, columnIndexVarName(variableElement))); } //@formatter:on @@ -668,7 +675,7 @@ private void emitInjectContextMethod(JavaWriter writer) throws IOException { .endControlFlow() .emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()") .emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()) - .emitStatement("this.proxyState = new ProxyState<%1$s>(this)", qualifiedClassName) + .emitStatement("this.proxyState = new ProxyState<%1$s>(this)", qualifiedJavaClassName) .emitStatement("proxyState.setRealm$realm(context.getRealm())") .emitStatement("proxyState.setRow$realm(context.getRow())") .emitStatement("proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue())") @@ -724,31 +731,32 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce writer.emitStatement( "OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\", %s, %s)", - this.simpleClassName, persistedFields, computedFields); + internalClassName, persistedFields, computedFields); // For each field generate corresponding table index constant - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); + for (RealmFieldElement field : metadata.getFields()) { + String fieldName = field.getInternalFieldName(); Constants.RealmFieldType fieldType = getRealmTypeChecked(field); switch (fieldType) { - case NOTYPE: + case NOTYPE: { // Perhaps this should fail quickly? break; - - case OBJECT: + } + case OBJECT: { String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); + String internalClassName = classCollection.getClassFromSimpleName(fieldTypeSimpleName).getInternalClassName(); writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.OBJECT, \"%s\")", - fieldName, fieldTypeSimpleName); + fieldName, internalClassName); break; - - case LIST: - // only for model list. + } + case LIST: { String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); + String internalClassName = classCollection.getClassFromSimpleName(genericTypeSimpleName).getInternalClassName(); // FIXME support for raw data writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.LIST, \"%s\")", - fieldName, genericTypeSimpleName); + fieldName, internalClassName); break; - + } case INTEGER_LIST: case BOOLEAN_LIST: case STRING_LIST: @@ -788,7 +796,7 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce } for (Backlink backlink: metadata.getBacklinkFields()) { writer.emitStatement("builder.addComputedLinkProperty(\"%s\", \"%s\", \"%s\")", - backlink.getTargetField(), backlink.getSimpleSourceClass(), backlink.getSourceField()); + backlink.getTargetField(), classCollection.getClassFromSimpleName(backlink.getSimpleSourceClass()).getInternalClassName(), backlink.getSourceField()); } writer.emitStatement("return builder.build()"); writer.endMethod() @@ -824,7 +832,7 @@ private void emitCreateColumnInfoMethod(JavaWriter writer) throws IOException { //@formatter:off private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException { writer.beginMethod("String", "getSimpleClassName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) - .emitStatement("return \"%s\"", simpleClassName) + .emitStatement("return \"%s\"", internalClassName) .endMethod() .emitEmptyLine(); } @@ -833,10 +841,10 @@ private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException //@formatter:off private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer.beginMethod( - qualifiedClassName, // Return type + qualifiedJavaClassName, // Return type "copyOrUpdate", // Method name EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedClassName, "object", "boolean", "update", "Map", "cache" // Argument type & argument name + "Realm", "realm", qualifiedJavaClassName, "object", "boolean", "update", "Map", "cache" // Argument type & argument name ); writer @@ -857,7 +865,7 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer.emitStatement("RealmObjectProxy cachedRealmObject = cache.get(object)") .beginControlFlow("if (cachedRealmObject != null)") - .emitStatement("return (%s) cachedRealmObject", qualifiedClassName) + .emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName) .endControlFlow() .emitEmptyLine(); @@ -865,12 +873,12 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer.emitStatement("return copy(realm, object, update, cache)"); } else { writer - .emitStatement("%s realmObject = null", qualifiedClassName) + .emitStatement("%s realmObject = null", qualifiedJavaClassName) .emitStatement("boolean canUpdate = update") .beginControlFlow("if (canUpdate)") - .emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName) + .emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) .emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedClassName) + columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) .emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); String primaryKeyGetter = metadata.getPrimaryKeyGetter(); @@ -908,7 +916,7 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { .beginControlFlow("try") .emitStatement( "objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", - qualifiedClassName) + qualifiedJavaClassName) .emitStatement("realmObject = new %s()", qualifiedGeneratedClassName) .emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") .nextControlFlow("finally") @@ -1047,7 +1055,7 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { "long", // Return type "insert", // Method name EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedClassName, "object", "Map", "cache" // Argument type & argument name + "Realm", "realm", qualifiedJavaClassName, "object", "Map", "cache" // Argument type & argument name ); // If object is already in the Realm there is nothing to update @@ -1056,10 +1064,10 @@ private void emitInsertMethod(JavaWriter writer) throws IOException { .emitStatement("return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()") .endControlFlow(); - writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); + writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName); writer.emitStatement("long tableNativePtr = table.getNativePtr()"); writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedClassName); + columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName); if (metadata.hasPrimaryKey()) { writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); @@ -1140,17 +1148,17 @@ private void emitInsertListMethod(JavaWriter writer) throws IOException { "Realm", "realm", "Iterator", "objects", "Map", "cache" // Argument type & argument name ); - writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); + writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName); writer.emitStatement("long tableNativePtr = table.getNativePtr()"); writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedClassName); + columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName); if (metadata.hasPrimaryKey()) { writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); } - writer.emitStatement("%s object = null", qualifiedClassName); + writer.emitStatement("%s object = null", qualifiedJavaClassName); writer.beginControlFlow("while (objects.hasNext())") - .emitStatement("object = (%s) objects.next()", qualifiedClassName); + .emitStatement("object = (%s) objects.next()", qualifiedJavaClassName); writer.beginControlFlow("if (cache.containsKey(object))") .emitStatement("continue") .endControlFlow(); @@ -1234,7 +1242,7 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { "long", // Return type "insertOrUpdate", // Method name EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedClassName, "object", "Map", "cache" // Argument type & argument name + "Realm", "realm", qualifiedJavaClassName, "object", "Map", "cache" // Argument type & argument name ); // If object is already in the Realm there is nothing to update @@ -1243,10 +1251,10 @@ private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { .emitStatement("return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()") .endControlFlow(); - writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); + writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName); writer.emitStatement("long tableNativePtr = table.getNativePtr()"); writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedClassName); + columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName); if (metadata.hasPrimaryKey()) { writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); @@ -1347,17 +1355,17 @@ private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException "Realm", "realm", "Iterator", "objects", "Map", "cache" // Argument type & argument name ); - writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName); + writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName); writer.emitStatement("long tableNativePtr = table.getNativePtr()"); writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedClassName); + columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName); if (metadata.hasPrimaryKey()) { writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); } - writer.emitStatement("%s object = null", qualifiedClassName); + writer.emitStatement("%s object = null", qualifiedJavaClassName); writer.beginControlFlow("while (objects.hasNext())"); - writer.emitStatement("object = (%s) objects.next()", qualifiedClassName); + writer.emitStatement("object = (%s) objects.next()", qualifiedJavaClassName); writer.beginControlFlow("if (cache.containsKey(object))") .emitStatement("continue") .endControlFlow(); @@ -1518,14 +1526,14 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP private void emitCopyMethod(JavaWriter writer) throws IOException { writer.beginMethod( - qualifiedClassName, // Return type + qualifiedJavaClassName, // Return type "copy", // Method name EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedClassName, "newObject", "boolean", "update", "Map", "cache"); // Argument type & argument name + "Realm", "realm", qualifiedJavaClassName, "newObject", "boolean", "update", "Map", "cache"); // Argument type & argument name writer.emitStatement("RealmObjectProxy cachedRealmObject = cache.get(newObject)"); writer.beginControlFlow("if (cachedRealmObject != null)") - .emitStatement("return (%s) cachedRealmObject", qualifiedClassName) + .emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName) .endControlFlow(); @@ -1533,10 +1541,10 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { .emitSingleLineComment("rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields."); if (metadata.hasPrimaryKey()) { writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, ((%s) newObject).%s(), false, Collections.emptyList())", - qualifiedClassName, qualifiedClassName, interfaceName, metadata.getPrimaryKeyGetter()); + qualifiedJavaClassName, qualifiedJavaClassName, interfaceName, metadata.getPrimaryKeyGetter()); } else { writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, false, Collections.emptyList())", - qualifiedClassName, qualifiedClassName); + qualifiedJavaClassName, qualifiedJavaClassName); } writer.emitStatement("cache.put(newObject, (RealmObjectProxy) realmObject)"); @@ -1613,25 +1621,25 @@ private void emitCopyMethod(JavaWriter writer) throws IOException { //@formatter:off private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException { writer.beginMethod( - qualifiedClassName, // Return type + qualifiedJavaClassName, // Return type "createDetachedCopy", // Method name EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - qualifiedClassName, "realmObject", "int", "currentDepth", "int", "maxDepth", "Map>", "cache"); + qualifiedJavaClassName, "realmObject", "int", "currentDepth", "int", "maxDepth", "Map>", "cache"); writer .beginControlFlow("if (currentDepth > maxDepth || realmObject == null)") .emitStatement("return null") .endControlFlow() .emitStatement("CacheData cachedObject = cache.get(realmObject)") - .emitStatement("%s unmanagedObject", qualifiedClassName) + .emitStatement("%s unmanagedObject", qualifiedJavaClassName) .beginControlFlow("if (cachedObject == null)") - .emitStatement("unmanagedObject = new %s()", qualifiedClassName) + .emitStatement("unmanagedObject = new %s()", qualifiedJavaClassName) .emitStatement("cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject))") .nextControlFlow("else") .emitSingleLineComment("Reuse cached object or recreate it because it was encountered at a lower depth.") .beginControlFlow("if (currentDepth >= cachedObject.minDepth)") - .emitStatement("return (%s) cachedObject.object", qualifiedClassName) + .emitStatement("return (%s) cachedObject.object", qualifiedJavaClassName) .endControlFlow() - .emitStatement("unmanagedObject = (%s) cachedObject.object", qualifiedClassName) + .emitStatement("unmanagedObject = (%s) cachedObject.object", qualifiedJavaClassName) .emitStatement("cachedObject.minDepth = currentDepth") .endControlFlow(); @@ -1695,10 +1703,10 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { } writer.beginMethod( - qualifiedClassName, // Return type + qualifiedJavaClassName, // Return type "update", // Method name EnumSet.of(Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedClassName, "realmObject", qualifiedClassName, "newObject", "Map", "cache"); // Argument type & argument name + "Realm", "realm", qualifiedJavaClassName, "realmObject", qualifiedJavaClassName, "newObject", "Map", "cache"); // Argument type & argument name writer .emitStatement("%1$s realmObjectTarget = (%1$s) realmObject", interfaceName) @@ -1784,9 +1792,9 @@ private void emitToStringMethod(JavaWriter writer) throws IOException { .beginControlFlow("if (!RealmObject.isValid(this))") .emitStatement("return \"Invalid object\"") .endControlFlow(); - writer.emitStatement("StringBuilder stringBuilder = new StringBuilder(\"%s = proxy[\")", simpleClassName); + writer.emitStatement("StringBuilder stringBuilder = new StringBuilder(\"%s = proxy[\")", simpleJavaClassName); - Collection fields = metadata.getFields(); + Collection fields = metadata.getFields(); int i = fields.size() - 1; for (VariableElement field : fields) { String fieldName = field.getSimpleName().toString(); @@ -1860,8 +1868,8 @@ private void emitEqualsMethod(JavaWriter writer) throws IOException { if (metadata.containsEquals()) { return; } - String proxyClassName = Utils.getProxyClassName(simpleClassName); - String otherObjectVarName = "a" + simpleClassName; + String proxyClassName = Utils.getProxyClassName(simpleJavaClassName); + String otherObjectVarName = "a" + simpleJavaClassName; writer.emitAnnotation("Override") .beginMethod("boolean", "equals", EnumSet.of(Modifier.PUBLIC), "Object", "o") .emitStatement("if (this == o) return true") @@ -1886,7 +1894,7 @@ private void emitEqualsMethod(JavaWriter writer) throws IOException { private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOException { writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.beginMethod( - qualifiedClassName, + qualifiedJavaClassName, "createOrUpdateUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "JSONObject", "json", "boolean", "update"), @@ -1904,15 +1912,15 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep if (!metadata.hasPrimaryKey()) { buildExcludeFieldsList(writer, metadata.getFields()); writer.emitStatement("%s obj = realm.createObjectInternal(%s.class, true, excludeFields)", - qualifiedClassName, qualifiedClassName); + qualifiedJavaClassName, qualifiedJavaClassName); } else { String pkType = Utils.isString(metadata.getPrimaryKey()) ? "String" : "Long"; writer - .emitStatement("%s obj = null", qualifiedClassName) + .emitStatement("%s obj = null", qualifiedJavaClassName) .beginControlFlow("if (update)") - .emitStatement("Table table = realm.getTable(%s.class)", qualifiedClassName) + .emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) .emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedClassName) + columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) .emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())) .emitStatement("long rowIndex = Table.NO_MATCH"); if (metadata.isNullable(metadata.getPrimaryKey())) { @@ -1938,7 +1946,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep .beginControlFlow("try") .emitStatement( "objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", - qualifiedClassName) + qualifiedJavaClassName) .emitStatement("obj = new %s()", qualifiedGeneratedClassName) .nextControlFlow("finally") .emitStatement("objectContext.clear()") @@ -1951,7 +1959,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep String primaryKeyFieldType = metadata.getPrimaryKey().asType().toString(); String primaryKeyFieldName = metadata.getPrimaryKey().getSimpleName().toString(); RealmJsonTypeHelper.emitCreateObjectWithPrimaryKeyValue( - qualifiedClassName, qualifiedGeneratedClassName, primaryKeyFieldType, primaryKeyFieldName, writer); + qualifiedJavaClassName, qualifiedGeneratedClassName, primaryKeyFieldType, primaryKeyFieldName, writer); writer.endControlFlow(); } //@formatter:on @@ -2013,7 +2021,7 @@ private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOExcep writer.emitEmptyLine(); } - private void buildExcludeFieldsList(JavaWriter writer, Collection fields) throws IOException { + private void buildExcludeFieldsList(JavaWriter writer, Collection fields) throws IOException { for (VariableElement field : fields) { if (Utils.isRealmModel(field) || Utils.isRealmList(field)) { final String fieldName = field.getSimpleName().toString(); @@ -2030,7 +2038,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { writer.emitAnnotation("SuppressWarnings", "\"cast\""); writer.emitAnnotation("TargetApi", "Build.VERSION_CODES.HONEYCOMB"); writer.beginMethod( - qualifiedClassName, + qualifiedJavaClassName, "createUsingJsonStream", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "JsonReader", "reader"), @@ -2039,13 +2047,13 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { if (metadata.hasPrimaryKey()) { writer.emitStatement("boolean jsonHasPrimaryKey = false"); } - writer.emitStatement("final %s obj = new %s()", qualifiedClassName, qualifiedClassName); + writer.emitStatement("final %s obj = new %s()", qualifiedJavaClassName, qualifiedJavaClassName); writer.emitStatement("final %1$s objProxy = (%1$s) obj", interfaceName); writer.emitStatement("reader.beginObject()"); writer.beginControlFlow("while (reader.hasNext())"); writer.emitStatement("String name = reader.nextName()"); writer.beginControlFlow("if (false)"); - Collection fields = metadata.getFields(); + Collection fields = metadata.getFields(); for (VariableElement field : fields) { String fieldName = field.getSimpleName().toString(); String qualifiedFieldType = field.asType().toString(); @@ -2114,7 +2122,7 @@ private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { } private String columnInfoClassName() { - return simpleClassName + "ColumnInfo"; + return simpleJavaClassName + "ColumnInfo"; } private String columnIndexVarName(VariableElement variableElement) { @@ -2129,7 +2137,7 @@ private String fieldIndexVariableReference(VariableElement variableElement) { return "columnInfo." + columnIndexVarName(variableElement); } - private static int countModelOrListFields(Collection fields) { + private static int countModelOrListFields(Collection fields) { int count = 0; for (VariableElement f : fields) { if (Utils.isRealmModel(f) || Utils.isRealmList(f)) { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java index ebe68a616c..25cbe13a41 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java @@ -38,7 +38,7 @@ public class RealmProxyInterfaceGenerator { public RealmProxyInterfaceGenerator(ProcessingEnvironment processingEnvironment, ClassMetaData metaData) { this.processingEnvironment = processingEnvironment; this.metaData = metaData; - this.className = metaData.getSimpleClassName(); + this.className = metaData.getSimpleJavaClassName(); } public void generate() throws IOException { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index a667e57590..5081c513bf 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -40,8 +40,10 @@ public class RealmProxyMediatorGenerator { private final String className; private final ProcessingEnvironment processingEnvironment; - private final List qualifiedModelClasses = new ArrayList(); - private final List qualifiedProxyClasses = new ArrayList(); + private final List qualifiedModelClasses = new ArrayList<>(); + private final List qualifiedProxyClasses = new ArrayList<>(); + private final List internalClassNames = new ArrayList<>(); + public RealmProxyMediatorGenerator(ProcessingEnvironment processingEnvironment, String className, Set classesToValidate) { @@ -49,9 +51,10 @@ public RealmProxyMediatorGenerator(ProcessingEnvironment processingEnvironment, this.className = className; for (ClassMetaData metadata : classesToValidate) { - String simpleName = metadata.getSimpleClassName(); + String simpleName = metadata.getSimpleJavaClassName(); qualifiedModelClasses.add(metadata.getFullyQualifiedClassName()); qualifiedProxyClasses.add(REALM_PACKAGE_NAME + "." + getProxyClassName(simpleName)); + internalClassNames.add(metadata.getInternalClassName()); } } @@ -177,7 +180,7 @@ private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return %s.getSimpleClassName()", qualifiedProxyClasses.get(i)); + writer.emitStatement("return \"%s\"", internalClassNames.get(i)); } }, writer); writer.endMethod(); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java index 7e7a274af1..5884c70818 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java @@ -17,13 +17,20 @@ import javax.lang.model.util.Types; import javax.tools.Diagnostic; +import io.realm.annotations.RealmNamingPolicy; +import io.realm.processor.nameconverter.CamelCaseConverter; +import io.realm.processor.nameconverter.LowerCaseWithSeparatorConverter; +import io.realm.processor.nameconverter.NameConverter; +import io.realm.processor.nameconverter.IdentityConverter; +import io.realm.processor.nameconverter.PascalCaseConverter; + /** * Utility methods working with the Realm processor. */ public class Utils { - public static Types typeUtils; + private static Types typeUtils; private static Messager messager; private static TypeMirror realmInteger; private static DeclaredType realmList; @@ -54,10 +61,6 @@ public static boolean isDefaultConstructor(Element constructor) { return false; } - public static String lowerFirstChar(String input) { - return input.substring(0, 1).toLowerCase() + input.substring(1); - } - public static String getProxyClassSimpleName(VariableElement field) { if (typeUtils.isAssignable(field.asType(), realmList)) { return getProxyClassName(getGenericTypeSimpleName(field)); @@ -175,7 +178,7 @@ public static Constants.RealmFieldType getValueListFieldType(VariableElement fie } /** - * @return {@code true} if a given field type is {@code RealmList} and its element type is {@Code RealmObject}, + * @return {@code true} if a given field type is {@code RealmList} and its element type is {@code RealmObject}, * {@code false} otherwise. */ public static boolean isRealmModelList(VariableElement field) { @@ -209,7 +212,26 @@ public static boolean isRealmModel(Element field) { * @return {@code true} if a given type is {@code RealmModel}, {@code false} otherwise. */ public static boolean isRealmModel(TypeMirror type) { + // This will return the wrong result if a model class doesn't exist at all, but + // the compiler will catch that eventually. return typeUtils.isAssignable(type, realmModel); +// // Not sure what is happening here, but typeUtils.isAssignable("Foo", realmModel) +// // returns true even if Foo doesn't exist. No idea why this is happening. +// // For now punt on the problem and check the direct supertype which should be either +// // RealmObject or RealmModel. +// // Original implementation: `` +// // +// // Theory: It looks like if `type` has the internal TypeTag.ERROR (internal API) it +// // automatically translate to being assignable to everything. Possible some Java Specification +// // rule taking effect. In our case, however we can do better since all Realm classes +// // must be in the same compilation unit, so we should be able to look the type up. +// for (TypeMirror typeMirror : typeUtils.directSupertypes(type)) { +// String supertype = typeMirror.toString(); +// if (supertype.equals("io.realm.RealmObject") || supertype.equals("io.realm.RealmModel")) { +// return true; +// } +// } +// return false; } public static boolean isRealmResults(VariableElement field) { @@ -320,6 +342,11 @@ public static String stripPackage(String fullyQualifiedClassName) { } public static void error(String message, Element element) { + if (element instanceof RealmFieldElement) { + // Element is being cast to Symbol internally which breaks any implementors of the + // Element interface. This is a hack to work around that. Bad bad Oracle + element = ((RealmFieldElement) element).getFieldReference(); + } messager.printMessage(Diagnostic.Kind.ERROR, message, element); } @@ -327,6 +354,15 @@ public static void error(String message) { messager.printMessage(Diagnostic.Kind.ERROR, message); } + public static void note(String message, Element element) { + if (element instanceof RealmFieldElement) { + // Element is being cast to Symbol internally which breaks any implementors of the + // Element interface. This is a hack to work around that. Bad bad Oracle + element = ((RealmFieldElement) element).getFieldReference(); + } + messager.printMessage(Diagnostic.Kind.NOTE, message, element); + } + public static void note(String message) { messager.printMessage(Diagnostic.Kind.NOTE, message); } @@ -339,4 +375,19 @@ public static String getProxyInterfaceName(String className) { return className + Constants.INTERFACE_SUFFIX; } + public static NameConverter getNameFormatter(RealmNamingPolicy policy) { + if (policy == null) { + return new IdentityConverter(); + } + switch (policy) { + case NO_POLICY: return new IdentityConverter(); + case IDENTITY: return new IdentityConverter(); + case LOWER_CASE_WITH_UNDERSCORES: return new LowerCaseWithSeparatorConverter('_'); + case CAMEL_CASE: return new CamelCaseConverter(); + case PASCAL_CASE: return new PascalCaseConverter(); + default: + throw new IllegalArgumentException("Unknown policy: " + policy); + } + } + } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.java new file mode 100644 index 0000000000..f060cf2a41 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.processor.nameconverter; + +/** + * Converter that converts input to "camelCase". + */ +public class CamelCaseConverter implements NameConverter { + + private final WordTokenizer tokenizer = new WordTokenizer(); + + @Override + public String convert(String name) { + String[] words = tokenizer.split(name); + StringBuilder output = new StringBuilder(); + boolean firstWordEmitted = false; + for (int i = 0; i < words.length; i++) { + String word = words[i].toLowerCase(); + if (firstWordEmitted) { + int codepoint = word.codePointAt(0); + output.appendCodePoint(Character.toUpperCase(codepoint)); + output.append(word.substring(Character.charCount(codepoint))); + } else { + output.append(word); + firstWordEmitted = true; + } + } + + return output.toString(); + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.java new file mode 100644 index 0000000000..4408407c3a --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.processor.nameconverter; + +/** + * Converter that doesn't do any conversion when translating from Java to Realm. + * + * @see io.realm.annotations.RealmNamingPolicy#IDENTITY + */ +public class IdentityConverter implements NameConverter { + + @Override + public String convert(String name) { + return name; + } + +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.java new file mode 100644 index 0000000000..3be7b191a3 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.processor.nameconverter; + +/** + * Converter that converts input to lower case with a defined separator character. + */ +public class LowerCaseWithSeparatorConverter implements NameConverter { + + private final WordTokenizer tokenizer = new WordTokenizer(); + private final char separator; + + public LowerCaseWithSeparatorConverter(char separator) { + this.separator = separator; + } + + @Override + public String convert(String name) { + String[] words = tokenizer.split(name); + StringBuilder output = new StringBuilder(); + for (int i = 0; i < words.length; i++) { + String word = words[i].toLowerCase(); + output.append(word); + if (i < words.length - 1) { + output.append(separator); + } + } + + return output.toString(); + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.java new file mode 100644 index 0000000000..593adf48ce --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.processor.nameconverter; + +/** + * Interface for converters that can implement a given naming policy. + * + * @see io.realm.annotations.RealmNamingPolicy + */ +public interface NameConverter { + /** + * Converts the {@code name} so it matches the {@link io.realm.annotations.RealmNamingPolicy}. + * + * @param name string to convert. + * @return the converted string. + */ + String convert(String name); +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.java new file mode 100644 index 0000000000..3849a72f64 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.java @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.processor.nameconverter; + +/** + * Converter that converts input to "PascalCase". + */ +public class PascalCaseConverter implements NameConverter { + + private final WordTokenizer tokenizer = new WordTokenizer(); + + @Override + public String convert(String name) { + String[] words = tokenizer.split(name); + StringBuilder output = new StringBuilder(); + for (int i = 0; i < words.length; i++) { + String word = words[i].toLowerCase(); + int codepoint = word.codePointAt(0); + output.appendCodePoint(Character.toUpperCase(codepoint)); + output.append(word.substring(Character.charCount(codepoint))); + } + + return output.toString(); + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.java new file mode 100644 index 0000000000..9a1e3caa27 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.java @@ -0,0 +1,145 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.processor.nameconverter; + +import java.util.ArrayList; +import java.util.List; + +/** + * Segments a Java variable name into component words. + * + * Java variable names must follow the rules described in: + * https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.8 + * + * In this implementation we treat word separators as any of the following: + *
              + *
            1. + * Anytime a {@code _} or {@code $} is encountered. + * Example is "_FooBar" or "_Foo$Bar" which both becomes "Foo" and "Bar". + *
            2. + *
            3. + * Anytime you switch from a lower case character to an upper case character as + * identified by a `Character.isUpperCase(codepoint)` and `Character.isLowerCase(codepoint)`. + * Example is "FooBar" which becomes "Foo" and "Bar". + *
            4. + *
            5. + * Anytime you switch from more than one uppercase character to a lower case one. As + * identified by `Character.isUpperCase(codepoint)` and `Character.isLowerCase(codepoint)`. + * Example is "FOOBar" which becomes "FOO" and "Bar. + *
            6. + *
            7. + * Some characters like emojiis are neither uppercase or lowercase characters, so they will + * not trigger any of the above rules. + * Examples are "my😁" and "MY😁" which are both treated as one word. + *
            8. + *
            9. + * Hungarian notation, i.e. strings starting with lowercase "m" followed by uppercase letter + * is stripped and not considered part of any word. + *
            10. + *
            + */ +public class WordTokenizer { + + /** + * Segments a string into words as described above + */ + String[] split(String str) { + if (str == null || str.isEmpty()) { + return new String[0]; + } + + Integer previousCodepoint; + Integer currentCodepoint = null; + int length = str.length(); + int offset = 0; + StringBuilder currentWord = new StringBuilder(); + List words = new ArrayList<>(); + Boolean wordAllUpperCase = null; + int lastCodePointCharLength = 0; + while (offset < length) { + previousCodepoint = currentCodepoint; + currentCodepoint = str.codePointAt(offset); + int currentCharCount = Character.charCount(currentCodepoint); + boolean previousCodePointUpperCase = previousCodepoint != null && Character.isUpperCase(previousCodepoint); + boolean previousCodePointLowerCase = previousCodepoint != null && Character.isLowerCase(previousCodepoint); + boolean currentCodePointUpperCase = Character.isUpperCase(currentCodepoint); + boolean currentCodePointLowerCase = Character.isLowerCase(currentCodepoint); + + // Separator char encountered not part of any word, but indicate a boundary + if (currentCodepoint == '_' || currentCodepoint == '$') { + if (currentWord.length() > 0) { + words.add(currentWord.toString()); + currentWord.setLength(0); + } + + wordAllUpperCase = null; + offset += currentCharCount; + lastCodePointCharLength = 0; + continue; + } + + // Change between lower case and upper case indicate a word boundary + if (previousCodePointLowerCase && currentCodePointUpperCase) { + if (currentWord.length() > 0) { + words.add(currentWord.toString()); + currentWord.setLength(0); + currentWord.appendCodePoint(currentCodepoint); + } + + wordAllUpperCase = true; + offset += currentCharCount; + lastCodePointCharLength = currentCharCount; + continue; + } + + // Change between upper case and lower case indicated a word boundary on the previous + // char if multiple upper case characters where encountered. + if (currentWord.length() > 1 + && (wordAllUpperCase != null && wordAllUpperCase) + && previousCodePointUpperCase && currentCodePointLowerCase) { + words.add(currentWord.substring(0, currentWord.length() - lastCodePointCharLength)); + currentWord.substring(0, currentWord.length() - lastCodePointCharLength); + currentWord.delete(0, currentWord.length() - lastCodePointCharLength); + currentWord.appendCodePoint(currentCodepoint); + + wordAllUpperCase = false; + offset += currentCharCount; + lastCodePointCharLength = currentCharCount; + continue; + } + + // Add codepoint to current word + currentWord.appendCodePoint(currentCodepoint); + wordAllUpperCase = currentCodePointUpperCase && (wordAllUpperCase == null || wordAllUpperCase); + offset += currentCharCount; + lastCodePointCharLength = currentCharCount; + } + + // Add final word when exiting loop + if (currentWord.length() > 0) { + words.add(currentWord.toString()); + } + + // Remove hungarian notation if found + if (words.get(0).equals("m")) { + words.remove(0); + } + + String[] result = new String[words.size()]; + words.toArray(result); + return result; + } +} diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java new file mode 100644 index 0000000000..8465d0d28c --- /dev/null +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java @@ -0,0 +1,149 @@ +package io.realm.processor; + +import org.junit.Test; + +import java.util.LinkedHashMap; +import java.util.Map; + +import io.realm.processor.nameconverter.CamelCaseConverter; +import io.realm.processor.nameconverter.LowerCaseWithSeparatorConverter; +import io.realm.processor.nameconverter.NameConverter; +import io.realm.processor.nameconverter.PascalCaseConverter; + +import static org.junit.Assert.assertEquals; + +public class NameConverterTests { + + @Test + public void camelCase() { + NameConverter converter = new CamelCaseConverter(); + Map values = new LinkedHashMap() {{ + // + put("camelCase", "camelCase"); + put("PascalCase", "pascalCase"); + put("mHungarianNotation", "hungarianNotation"); + put("_PascalCaseWithStartingSeparator", "pascalCaseWithStartingSeparator"); + put("_camelCaseWithStartingSeparator", "camelCaseWithStartingSeparator"); + put("ALL_CAPS_WITH_SEPARATOR", "allCapsWithSeparator"); + put("ALLCAPS", "allcaps"); + put("_ALL_CAPS_WITH_STARTING_SEPARATOR", "allCapsWithStartingSeparator"); + put("alllower", "alllower"); + put("all_lower_with_separator", "allLowerWithSeparator"); + + // $ Separator + put("$generatedNames", "generatedNames"); + put("generatedNames$", "generatedNames"); + put("generated$Names", "generatedNames"); + + // Non-ascii chars + put("πPi", "πPi"); + put("NonAsciiÆøÅ", "nonAsciiÆøÅ"); + + // Multiple upper case letters + put("HTMLFile", "htmlFile"); + put("aHTMLFile", "aHtmlFile"); + put("_HTMLFile", "htmlFile"); + + // Emojiis are neither upper case nor lower case (Smiley) + put("\uD83D\uDE01", "\uD83D\uDE01"); + put("m\uD83D\uDE01", "m\uD83D\uDE01"); + put("M\uD83D\uDE01", "m\uD83D\uDE01"); + put("\uD83D\uDE01Smiley", "\uD83D\uDE01smiley"); + put("_\uD83D\uDE01smiley", "\uD83D\uDE01smiley"); + }}; + + for (Map.Entry entry : values.entrySet()) { + assertEquals(entry.getValue(), converter.convert(entry.getKey())); + } + } + + @Test + public void pascalCase() { + NameConverter converter = new PascalCaseConverter(); + Map values = new LinkedHashMap() {{ + // + put("camelCase", "CamelCase"); + put("PascalCase", "PascalCase"); + put("mHungarianNotation", "HungarianNotation"); + put("_PascalCaseWithStartingSeparator", "PascalCaseWithStartingSeparator"); + put("_camelCaseWithStartingSeparator", "CamelCaseWithStartingSeparator"); + put("ALL_CAPS_WITH_SEPARATOR", "AllCapsWithSeparator"); + put("ALLCAPS", "Allcaps"); + put("_ALL_CAPS_WITH_STARTING_SEPARATOR", "AllCapsWithStartingSeparator"); + put("alllower", "Alllower"); + put("all_lower_with_separator", "AllLowerWithSeparator"); + + // $ Separator + put("$generatedNames", "GeneratedNames"); + put("generatedNames$", "GeneratedNames"); + put("generated$Names", "GeneratedNames"); + + // Non-ascii chars + put("πPi", "ΠPi"); + put("NonAsciiÆøÅ", "NonAsciiÆøÅ"); + + // Multiple upper case letters + put("HTMLFile", "HtmlFile"); + put("aHTMLFile", "AHtmlFile"); + put("_HTMLFile", "HtmlFile"); + + // Emojiis are neither upper case nor lower case (Smiley) + put("\uD83D\uDE01", "\uD83D\uDE01"); + put("m\uD83D\uDE01", "M\uD83D\uDE01"); + put("M\uD83D\uDE01", "M\uD83D\uDE01"); + put("\uD83D\uDE01Smiley", "\uD83D\uDE01smiley"); + put("_\uD83D\uDE01smiley", "\uD83D\uDE01smiley"); + }}; + + for (Map.Entry entry : values.entrySet()) { + assertEquals(entry.getValue(), converter.convert(entry.getKey())); + } + } + + @Test + public void lowerCaseWithUnderscore() { + NameConverter converter = new LowerCaseWithSeparatorConverter('_'); + Map values = new LinkedHashMap() {{ + // + // Common naming schemes using ASCII chars + put("camelCase", "camel_case"); + put("PascalCase", "pascal_case"); + put("mHungarianNotation", "hungarian_notation"); + put("_mHungarianNotation", "hungarian_notation"); + put("mHungarian_mNotation", "hungarian_m_notation"); + put("_PascalCaseWithStartingSeparator", "pascal_case_with_starting_separator"); + put("_camelCaseWithStartingSeparator", "camel_case_with_starting_separator"); + put("ALL_CAPS_WITH_SEPARATOR", "all_caps_with_separator"); + put("ALLCAPS", "allcaps"); + put("_ALL_CAPS_WITH_STARTING_SEPARATOR", "all_caps_with_starting_separator"); + put("alllower", "alllower"); + put("all_lower_with_separator", "all_lower_with_separator"); + + // $ Separator + put("$generatedNames", "generated_names"); + put("generatedNames$", "generated_names"); + put("generated$Names", "generated_names"); + + // Non-ascii chars + put("πPi", "π_pi"); + put("NonAsciiÆøÅ", "non_ascii_æø_å"); + + // Multiple upper case letters + put("HTMLFile", "html_file"); + put("aHTMLFile", "a_html_file"); + put("_HTMLFile", "html_file"); + + // Emojiis are neither upper case nor lower case (Smiley) + put("\uD83D\uDE01", "\uD83D\uDE01"); + put("m\uD83D\uDE01", "m\uD83D\uDE01"); + put("M\uD83D\uDE01", "m\uD83D\uDE01"); + put("\uD83D\uDE01Smiley", "\uD83D\uDE01smiley"); + put("_\uD83D\uDE01smiley", "\uD83D\uDE01smiley"); + }}; + + for (Map.Entry entry : values.entrySet()) { + assertEquals(entry.getValue(), converter.convert(entry.getKey())); + } + } + +} diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java index 76a8f61ffd..46448a3a0c 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java @@ -131,7 +131,7 @@ public void failsOnLinkingObjectsWithRequiredFields() throws IOException { .that(Arrays.asList(backlinksTarget, javaFileObject)) .processedWith(new RealmProcessor()) .failsToCompile() - .withErrorContaining("cannot be @Required"); + .withErrorContaining("The @LinkingObjects field "); } @Test diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmNameTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmNameTest.java new file mode 100644 index 0000000000..033cae1101 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmNameTest.java @@ -0,0 +1,113 @@ +package io.realm.processor; + +import com.google.testing.compile.JavaFileObjects; + +import org.junit.Test; + +import java.util.Arrays; + +import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; +import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; +import static org.truth0.Truth.ASSERT; + +public class RealmNameTest { + + // Check that a class only with class name policy compiles + @Test + public void compileOnlyClassNamePolicyFile() { + ASSERT.about(javaSource()) + .that(JavaFileObjects.forResource("some/test/NamePolicyClassOnly.java")) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + // Check that a class only with a field name policy compiles + @Test + public void compileOnlyFieldNamePolicyFile() { + ASSERT.about(javaSource()) + .that(JavaFileObjects.forResource("some/test/NamePolicyFieldNameOnly.java")) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + // Check that things compile if there is only a module with name policies defined + @Test + public void compileModuleWithNamePolicyFile() { + ASSERT.about(javaSource()) + .that(JavaFileObjects.forResource("some/test/NamePolicyModule.java")) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + // Check the effect of setting both module class/field name policies, class name, field + // name policy and explicit names on fields (i.e = Specific class name + field name should win. + @Test + public void compareProcessedNamingPolicyClassFile() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/NamePolicyModule.java"), + JavaFileObjects.forResource("some/test/NamePolicyMixedClassSettings.java"), + JavaFileObjects.forResource("some/test/NamePolicyFieldNameOnly.java"), + JavaFileObjects.forResource("some/test/NamePolicyClassOnly.java") + )) + .processedWith(new RealmProcessor()) + .compilesWithoutError() + .and() + .generatesSources(JavaFileObjects.forResource("io/realm/NamePolicyMixedClassSettingsRealmProxy.java")); + } + + // Check the effect of module default on a class with no settings itself + @Test + public void compareProcessedDefaultClassFile() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/NamePolicyModule.java"), + JavaFileObjects.forResource("some/test/NamePolicyModuleDefaults.java") + )) + .processedWith(new RealmProcessor()) + .compilesWithoutError() + .and() + .generatesSources(JavaFileObjects.forResource("io/realm/NamePolicyModuleDefaultsRealmProxy.java")); + } + + // Check that trying to compile two modules with different policies using `allClasses = true` will fail. + @Test + public void compileModulesWithConflictingPoliciesForAllClassesFails() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/NamePolicyConflictingModuleDefinitionsForAllClasses.java"), + JavaFileObjects.forResource("some/test/Simple.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("disagree on the class naming policy"); + } + + // Check that trying to compile two modules with different policies using `classes = { ... }` will fail. + @Test + public void compileModulesWithConflictingPoliciesForNamedClassesFails() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/NamePolicyConflictingModuleDefinitionsForNamedClasses.java"), + JavaFileObjects.forResource("some/test/Simple.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("disagree on the class naming policy"); + } + + // Check that trying to compile two modules with different policies using a mix of `allClasses` + // and `classes = { ... }` will fail. + @Test + public void compileModulesWithConflictingPoliciesAndMixedClassDefinitionsFails() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/NamePolicyConflictingModuleDefinitionsForMixedDefinitions.java"), + JavaFileObjects.forResource("some/test/Simple.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("disagree on the class naming policy"); + } + +} diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java index 9907db2b3c..ec043729e6 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java @@ -343,7 +343,7 @@ public void compileRequiredTypes() throws IOException { for (String fieldType : validPrimaryKeyFieldTypes) { RealmSyntheticTestClass javaFileObject = - new RealmSyntheticTestClass.Builder().name("ValidPrimaryKeyType").field("testField", fieldType, "Required").build(); + new RealmSyntheticTestClass.Builder().name("ValidRequiredType").field("testField", fieldType, "Required").build(); ASSERT.about(javaSource()) .that(javaFileObject) .processedWith(new RealmProcessor()) @@ -354,14 +354,16 @@ public void compileRequiredTypes() throws IOException { // Not supported "Required" annotation types @Test public void compileInvalidRequiredTypes() throws IOException { - final String[] validPrimaryKeyFieldTypes = {"byte", "short", "int", "long", "float", "double", + final String[] invalidRequiredAnnotationFieldTypes = {"byte", "short", "int", "long", "float", "double", "boolean", "RealmList", "Simple"}; - for (String fieldType : validPrimaryKeyFieldTypes) { - RealmSyntheticTestClass javaFileObject = - new RealmSyntheticTestClass.Builder().name("ValidPrimaryKeyType").field("testField", fieldType, "Required").build(); - ASSERT.about(javaSource()) - .that(javaFileObject) + for (String fieldType : invalidRequiredAnnotationFieldTypes) { + RealmSyntheticTestClass javaFileObject = new RealmSyntheticTestClass.Builder() + .name("InvalidRequiredType") + .field("testField", fieldType, "Required") + .build(); + ASSERT.about(javaSources()) + .that(Arrays.asList(simpleModel, javaFileObject)) .processedWith(new RealmProcessor()) .failsToCompile(); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java index 03e4de06d4..4351c458c9 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java @@ -60,26 +60,26 @@ static final class AllTypesColumnInfo extends ColumnInfo { AllTypesColumnInfo(OsSchemaInfo schemaInfo) { super(20); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("AllTypes"); - this.columnStringIndex = addColumnDetails("columnString", objectSchemaInfo); - this.columnLongIndex = addColumnDetails("columnLong", objectSchemaInfo); - this.columnFloatIndex = addColumnDetails("columnFloat", objectSchemaInfo); - this.columnDoubleIndex = addColumnDetails("columnDouble", objectSchemaInfo); - this.columnBooleanIndex = addColumnDetails("columnBoolean", objectSchemaInfo); - this.columnDateIndex = addColumnDetails("columnDate", objectSchemaInfo); - this.columnBinaryIndex = addColumnDetails("columnBinary", objectSchemaInfo); - this.columnMutableRealmIntegerIndex = addColumnDetails("columnMutableRealmInteger", objectSchemaInfo); - this.columnObjectIndex = addColumnDetails("columnObject", objectSchemaInfo); - this.columnRealmListIndex = addColumnDetails("columnRealmList", objectSchemaInfo); - this.columnStringListIndex = addColumnDetails("columnStringList", objectSchemaInfo); - this.columnBinaryListIndex = addColumnDetails("columnBinaryList", objectSchemaInfo); - this.columnBooleanListIndex = addColumnDetails("columnBooleanList", objectSchemaInfo); - this.columnLongListIndex = addColumnDetails("columnLongList", objectSchemaInfo); - this.columnIntegerListIndex = addColumnDetails("columnIntegerList", objectSchemaInfo); - this.columnShortListIndex = addColumnDetails("columnShortList", objectSchemaInfo); - this.columnByteListIndex = addColumnDetails("columnByteList", objectSchemaInfo); - this.columnDoubleListIndex = addColumnDetails("columnDoubleList", objectSchemaInfo); - this.columnFloatListIndex = addColumnDetails("columnFloatList", objectSchemaInfo); - this.columnDateListIndex = addColumnDetails("columnDateList", objectSchemaInfo); + this.columnStringIndex = addColumnDetails("columnString", "columnString", objectSchemaInfo); + this.columnLongIndex = addColumnDetails("columnLong", "columnLong", objectSchemaInfo); + this.columnFloatIndex = addColumnDetails("columnFloat", "columnFloat", objectSchemaInfo); + this.columnDoubleIndex = addColumnDetails("columnDouble", "columnDouble", objectSchemaInfo); + this.columnBooleanIndex = addColumnDetails("columnBoolean", "columnBoolean", objectSchemaInfo); + this.columnDateIndex = addColumnDetails("columnDate", "columnDate", objectSchemaInfo); + this.columnBinaryIndex = addColumnDetails("columnBinary", "columnBinary", objectSchemaInfo); + this.columnMutableRealmIntegerIndex = addColumnDetails("columnMutableRealmInteger", "columnMutableRealmInteger", objectSchemaInfo); + this.columnObjectIndex = addColumnDetails("columnObject", "columnObject", objectSchemaInfo); + this.columnRealmListIndex = addColumnDetails("columnRealmList", "columnRealmList", objectSchemaInfo); + this.columnStringListIndex = addColumnDetails("columnStringList", "columnStringList", objectSchemaInfo); + this.columnBinaryListIndex = addColumnDetails("columnBinaryList", "columnBinaryList", objectSchemaInfo); + this.columnBooleanListIndex = addColumnDetails("columnBooleanList", "columnBooleanList", objectSchemaInfo); + this.columnLongListIndex = addColumnDetails("columnLongList", "columnLongList", objectSchemaInfo); + this.columnIntegerListIndex = addColumnDetails("columnIntegerList", "columnIntegerList", objectSchemaInfo); + this.columnShortListIndex = addColumnDetails("columnShortList", "columnShortList", objectSchemaInfo); + this.columnByteListIndex = addColumnDetails("columnByteList", "columnByteList", objectSchemaInfo); + this.columnDoubleListIndex = addColumnDetails("columnDoubleList", "columnDoubleList", objectSchemaInfo); + this.columnFloatListIndex = addColumnDetails("columnFloatList", "columnFloatList", objectSchemaInfo); + this.columnDateListIndex = addColumnDetails("columnDateList", "columnDateList", objectSchemaInfo); addBacklinkDetails(schemaInfo, "parentObjects", "AllTypes", "columnObject"); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java index cb8c2ffb70..f0a55dba46 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java @@ -43,10 +43,10 @@ static final class BooleansColumnInfo extends ColumnInfo { BooleansColumnInfo(OsSchemaInfo schemaInfo) { super(4); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("Booleans"); - this.doneIndex = addColumnDetails("done", objectSchemaInfo); - this.isReadyIndex = addColumnDetails("isReady", objectSchemaInfo); - this.mCompletedIndex = addColumnDetails("mCompleted", objectSchemaInfo); - this.anotherBooleanIndex = addColumnDetails("anotherBoolean", objectSchemaInfo); + this.doneIndex = addColumnDetails("done", "done", objectSchemaInfo); + this.isReadyIndex = addColumnDetails("isReady", "isReady", objectSchemaInfo); + this.mCompletedIndex = addColumnDetails("mCompleted", "mCompleted", objectSchemaInfo); + this.anotherBooleanIndex = addColumnDetails("anotherBoolean", "anotherBoolean", objectSchemaInfo); } BooleansColumnInfo(ColumnInfo src, boolean mutable) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyMixedClassSettingsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyMixedClassSettingsRealmProxy.java new file mode 100644 index 0000000000..5b37b1c93c --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyMixedClassSettingsRealmProxy.java @@ -0,0 +1,443 @@ +package io.realm; + + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.JsonReader; +import android.util.JsonToken; +import io.realm.ProxyUtils; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.ColumnInfo; +import io.realm.internal.OsList; +import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.OsSchemaInfo; +import io.realm.internal.Property; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; +import io.realm.internal.Table; +import io.realm.internal.android.JsonUtils; +import io.realm.log.RealmLog; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +@SuppressWarnings("all") +public class NamePolicyMixedClassSettingsRealmProxy extends some.test.NamePolicyMixedClassSettings + implements RealmObjectProxy, NamePolicyMixedClassSettingsRealmProxyInterface { + + static final class NamePolicyMixedClassSettingsColumnInfo extends ColumnInfo { + long firstNameIndex; + long lastNameIndex; + + NamePolicyMixedClassSettingsColumnInfo(OsSchemaInfo schemaInfo) { + super(2); + OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("customName"); + this.firstNameIndex = addColumnDetails("firstName", "first_name", objectSchemaInfo); + this.lastNameIndex = addColumnDetails("lastName", "LastName", objectSchemaInfo); + } + + NamePolicyMixedClassSettingsColumnInfo(ColumnInfo src, boolean mutable) { + super(src, mutable); + copy(src, this); + } + + @Override + protected final ColumnInfo copy(boolean mutable) { + return new NamePolicyMixedClassSettingsColumnInfo(this, mutable); + } + + @Override + protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { + final NamePolicyMixedClassSettingsColumnInfo src = (NamePolicyMixedClassSettingsColumnInfo) rawSrc; + final NamePolicyMixedClassSettingsColumnInfo dst = (NamePolicyMixedClassSettingsColumnInfo) rawDst; + dst.firstNameIndex = src.firstNameIndex; + dst.lastNameIndex = src.lastNameIndex; + } + } + + private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); + + private NamePolicyMixedClassSettingsColumnInfo columnInfo; + private ProxyState proxyState; + + NamePolicyMixedClassSettingsRealmProxy() { + proxyState.setConstructionFinished(); + } + + @Override + public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } + final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); + this.columnInfo = (NamePolicyMixedClassSettingsColumnInfo) context.getColumnInfo(); + this.proxyState = new ProxyState(this); + proxyState.setRealm$realm(context.getRealm()); + proxyState.setRow$realm(context.getRow()); + proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); + proxyState.setExcludeFields$realm(context.getExcludeFields()); + } + + @Override + @SuppressWarnings("cast") + public String realmGet$firstName() { + proxyState.getRealm$realm().checkIfValid(); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.firstNameIndex); + } + + @Override + public void realmSet$firstName(String value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.firstNameIndex, row.getIndex(), true); + return; + } + row.getTable().setString(columnInfo.firstNameIndex, row.getIndex(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + proxyState.getRow$realm().setNull(columnInfo.firstNameIndex); + return; + } + proxyState.getRow$realm().setString(columnInfo.firstNameIndex, value); + } + + @Override + @SuppressWarnings("cast") + public String realmGet$lastName() { + proxyState.getRealm$realm().checkIfValid(); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.lastNameIndex); + } + + @Override + public void realmSet$lastName(String value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.lastNameIndex, row.getIndex(), true); + return; + } + row.getTable().setString(columnInfo.lastNameIndex, row.getIndex(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + proxyState.getRow$realm().setNull(columnInfo.lastNameIndex); + return; + } + proxyState.getRow$realm().setString(columnInfo.lastNameIndex, value); + } + + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("customName", 2, 0); + builder.addPersistedProperty("first_name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addPersistedProperty("LastName", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + return builder.build(); + } + + public static OsObjectSchemaInfo getExpectedObjectSchemaInfo() { + return expectedObjectSchemaInfo; + } + + public static NamePolicyMixedClassSettingsColumnInfo createColumnInfo(OsSchemaInfo schemaInfo) { + return new NamePolicyMixedClassSettingsColumnInfo(schemaInfo); + } + + public static String getSimpleClassName() { + return "customName"; + } + + @SuppressWarnings("cast") + public static some.test.NamePolicyMixedClassSettings createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) + throws JSONException { + final List excludeFields = Collections. emptyList(); + some.test.NamePolicyMixedClassSettings obj = realm.createObjectInternal(some.test.NamePolicyMixedClassSettings.class, true, excludeFields); + + final NamePolicyMixedClassSettingsRealmProxyInterface objProxy = (NamePolicyMixedClassSettingsRealmProxyInterface) obj; + if (json.has("firstName")) { + if (json.isNull("firstName")) { + objProxy.realmSet$firstName(null); + } else { + objProxy.realmSet$firstName((String) json.getString("firstName")); + } + } + if (json.has("lastName")) { + if (json.isNull("lastName")) { + objProxy.realmSet$lastName(null); + } else { + objProxy.realmSet$lastName((String) json.getString("lastName")); + } + } + return obj; + } + + @SuppressWarnings("cast") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static some.test.NamePolicyMixedClassSettings createUsingJsonStream(Realm realm, JsonReader reader) + throws IOException { + final some.test.NamePolicyMixedClassSettings obj = new some.test.NamePolicyMixedClassSettings(); + final NamePolicyMixedClassSettingsRealmProxyInterface objProxy = (NamePolicyMixedClassSettingsRealmProxyInterface) obj; + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (false) { + } else if (name.equals("firstName")) { + if (reader.peek() != JsonToken.NULL) { + objProxy.realmSet$firstName((String) reader.nextString()); + } else { + reader.skipValue(); + objProxy.realmSet$firstName(null); + } + } else if (name.equals("lastName")) { + if (reader.peek() != JsonToken.NULL) { + objProxy.realmSet$lastName((String) reader.nextString()); + } else { + reader.skipValue(); + objProxy.realmSet$lastName(null); + } + } else { + reader.skipValue(); + } + } + reader.endObject(); + return realm.copyToRealm(obj); + } + + public static some.test.NamePolicyMixedClassSettings copyOrUpdate(Realm realm, some.test.NamePolicyMixedClassSettings object, boolean update, Map cache) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); + if (otherRealm.threadId != realm.threadId) { + throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); + } + if (otherRealm.getPath().equals(realm.getPath())) { + return object; + } + } + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + RealmObjectProxy cachedRealmObject = cache.get(object); + if (cachedRealmObject != null) { + return (some.test.NamePolicyMixedClassSettings) cachedRealmObject; + } + + return copy(realm, object, update, cache); + } + + public static some.test.NamePolicyMixedClassSettings copy(Realm realm, some.test.NamePolicyMixedClassSettings newObject, boolean update, Map cache) { + RealmObjectProxy cachedRealmObject = cache.get(newObject); + if (cachedRealmObject != null) { + return (some.test.NamePolicyMixedClassSettings) cachedRealmObject; + } + + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.NamePolicyMixedClassSettings realmObject = realm.createObjectInternal(some.test.NamePolicyMixedClassSettings.class, false, Collections.emptyList()); + cache.put(newObject, (RealmObjectProxy) realmObject); + + NamePolicyMixedClassSettingsRealmProxyInterface realmObjectSource = (NamePolicyMixedClassSettingsRealmProxyInterface) newObject; + NamePolicyMixedClassSettingsRealmProxyInterface realmObjectCopy = (NamePolicyMixedClassSettingsRealmProxyInterface) realmObject; + + realmObjectCopy.realmSet$firstName(realmObjectSource.realmGet$firstName()); + realmObjectCopy.realmSet$lastName(realmObjectSource.realmGet$lastName()); + return realmObject; + } + + public static long insert(Realm realm, some.test.NamePolicyMixedClassSettings object, Map cache) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + } + Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); + long tableNativePtr = table.getNativePtr(); + NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); + long rowIndex = OsObject.createRow(table); + cache.put(object, rowIndex); + String realmGet$firstName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); + if (realmGet$firstName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + } + String realmGet$lastName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); + if (realmGet$lastName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + } + return rowIndex; + } + + public static void insert(Realm realm, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); + long tableNativePtr = table.getNativePtr(); + NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); + some.test.NamePolicyMixedClassSettings object = null; + while (objects.hasNext()) { + object = (some.test.NamePolicyMixedClassSettings) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + long rowIndex = OsObject.createRow(table); + cache.put(object, rowIndex); + String realmGet$firstName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); + if (realmGet$firstName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + } + String realmGet$lastName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); + if (realmGet$lastName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + } + } + } + + public static long insertOrUpdate(Realm realm, some.test.NamePolicyMixedClassSettings object, Map cache) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + } + Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); + long tableNativePtr = table.getNativePtr(); + NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); + long rowIndex = OsObject.createRow(table); + cache.put(object, rowIndex); + String realmGet$firstName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); + if (realmGet$firstName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); + } + String realmGet$lastName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); + if (realmGet$lastName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameIndex, rowIndex, false); + } + return rowIndex; + } + + public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); + long tableNativePtr = table.getNativePtr(); + NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); + some.test.NamePolicyMixedClassSettings object = null; + while (objects.hasNext()) { + object = (some.test.NamePolicyMixedClassSettings) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + long rowIndex = OsObject.createRow(table); + cache.put(object, rowIndex); + String realmGet$firstName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); + if (realmGet$firstName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); + } + String realmGet$lastName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); + if (realmGet$lastName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameIndex, rowIndex, false); + } + } + } + + public static some.test.NamePolicyMixedClassSettings createDetachedCopy(some.test.NamePolicyMixedClassSettings realmObject, int currentDepth, int maxDepth, Map> cache) { + if (currentDepth > maxDepth || realmObject == null) { + return null; + } + CacheData cachedObject = cache.get(realmObject); + some.test.NamePolicyMixedClassSettings unmanagedObject; + if (cachedObject == null) { + unmanagedObject = new some.test.NamePolicyMixedClassSettings(); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + } else { + // Reuse cached object or recreate it because it was encountered at a lower depth. + if (currentDepth >= cachedObject.minDepth) { + return (some.test.NamePolicyMixedClassSettings) cachedObject.object; + } + unmanagedObject = (some.test.NamePolicyMixedClassSettings) cachedObject.object; + cachedObject.minDepth = currentDepth; + } + NamePolicyMixedClassSettingsRealmProxyInterface unmanagedCopy = (NamePolicyMixedClassSettingsRealmProxyInterface) unmanagedObject; + NamePolicyMixedClassSettingsRealmProxyInterface realmSource = (NamePolicyMixedClassSettingsRealmProxyInterface) realmObject; + unmanagedCopy.realmSet$firstName(realmSource.realmGet$firstName()); + unmanagedCopy.realmSet$lastName(realmSource.realmGet$lastName()); + + return unmanagedObject; + } + + @Override + @SuppressWarnings("ArrayToString") + public String toString() { + if (!RealmObject.isValid(this)) { + return "Invalid object"; + } + StringBuilder stringBuilder = new StringBuilder("NamePolicyMixedClassSettings = proxy["); + stringBuilder.append("{firstName:"); + stringBuilder.append(realmGet$firstName() != null ? realmGet$firstName() : "null"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{lastName:"); + stringBuilder.append(realmGet$lastName() != null ? realmGet$lastName() : "null"); + stringBuilder.append("}"); + stringBuilder.append("]"); + return stringBuilder.toString(); + } + + @Override + public ProxyState realmGet$proxyState() { + return proxyState; + } + + @Override + public int hashCode() { + String realmName = proxyState.getRealm$realm().getPath(); + String tableName = proxyState.getRow$realm().getTable().getName(); + long rowIndex = proxyState.getRow$realm().getIndex(); + + int result = 17; + result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); + result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); + result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32)); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NamePolicyMixedClassSettingsRealmProxy aNamePolicyMixedClassSettings = (NamePolicyMixedClassSettingsRealmProxy)o; + + String path = proxyState.getRealm$realm().getPath(); + String otherPath = aNamePolicyMixedClassSettings.proxyState.getRealm$realm().getPath(); + if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + + String tableName = proxyState.getRow$realm().getTable().getName(); + String otherTableName = aNamePolicyMixedClassSettings.proxyState.getRow$realm().getTable().getName(); + if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; + + if (proxyState.getRow$realm().getIndex() != aNamePolicyMixedClassSettings.proxyState.getRow$realm().getIndex()) return false; + + return true; + } +} diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyModuleDefaultsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyModuleDefaultsRealmProxy.java new file mode 100644 index 0000000000..b1aaceae87 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyModuleDefaultsRealmProxy.java @@ -0,0 +1,443 @@ +package io.realm; + + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.JsonReader; +import android.util.JsonToken; +import io.realm.ProxyUtils; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.ColumnInfo; +import io.realm.internal.OsList; +import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.OsSchemaInfo; +import io.realm.internal.Property; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; +import io.realm.internal.Table; +import io.realm.internal.android.JsonUtils; +import io.realm.log.RealmLog; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +@SuppressWarnings("all") +public class NamePolicyModuleDefaultsRealmProxy extends some.test.NamePolicyModuleDefaults + implements RealmObjectProxy, NamePolicyModuleDefaultsRealmProxyInterface { + + static final class NamePolicyModuleDefaultsColumnInfo extends ColumnInfo { + long firstNameIndex; + long lastNameIndex; + + NamePolicyModuleDefaultsColumnInfo(OsSchemaInfo schemaInfo) { + super(2); + OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("NamePolicyModuleDefaults"); + this.firstNameIndex = addColumnDetails("firstName", "FirstName", objectSchemaInfo); + this.lastNameIndex = addColumnDetails("lastName", "LastName", objectSchemaInfo); + } + + NamePolicyModuleDefaultsColumnInfo(ColumnInfo src, boolean mutable) { + super(src, mutable); + copy(src, this); + } + + @Override + protected final ColumnInfo copy(boolean mutable) { + return new NamePolicyModuleDefaultsColumnInfo(this, mutable); + } + + @Override + protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { + final NamePolicyModuleDefaultsColumnInfo src = (NamePolicyModuleDefaultsColumnInfo) rawSrc; + final NamePolicyModuleDefaultsColumnInfo dst = (NamePolicyModuleDefaultsColumnInfo) rawDst; + dst.firstNameIndex = src.firstNameIndex; + dst.lastNameIndex = src.lastNameIndex; + } + } + + private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); + + private NamePolicyModuleDefaultsColumnInfo columnInfo; + private ProxyState proxyState; + + NamePolicyModuleDefaultsRealmProxy() { + proxyState.setConstructionFinished(); + } + + @Override + public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } + final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); + this.columnInfo = (NamePolicyModuleDefaultsColumnInfo) context.getColumnInfo(); + this.proxyState = new ProxyState(this); + proxyState.setRealm$realm(context.getRealm()); + proxyState.setRow$realm(context.getRow()); + proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); + proxyState.setExcludeFields$realm(context.getExcludeFields()); + } + + @Override + @SuppressWarnings("cast") + public String realmGet$firstName() { + proxyState.getRealm$realm().checkIfValid(); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.firstNameIndex); + } + + @Override + public void realmSet$firstName(String value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.firstNameIndex, row.getIndex(), true); + return; + } + row.getTable().setString(columnInfo.firstNameIndex, row.getIndex(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + proxyState.getRow$realm().setNull(columnInfo.firstNameIndex); + return; + } + proxyState.getRow$realm().setString(columnInfo.firstNameIndex, value); + } + + @Override + @SuppressWarnings("cast") + public String realmGet$lastName() { + proxyState.getRealm$realm().checkIfValid(); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.lastNameIndex); + } + + @Override + public void realmSet$lastName(String value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.lastNameIndex, row.getIndex(), true); + return; + } + row.getTable().setString(columnInfo.lastNameIndex, row.getIndex(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + proxyState.getRow$realm().setNull(columnInfo.lastNameIndex); + return; + } + proxyState.getRow$realm().setString(columnInfo.lastNameIndex, value); + } + + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NamePolicyModuleDefaults", 2, 0); + builder.addPersistedProperty("FirstName", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addPersistedProperty("LastName", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + return builder.build(); + } + + public static OsObjectSchemaInfo getExpectedObjectSchemaInfo() { + return expectedObjectSchemaInfo; + } + + public static NamePolicyModuleDefaultsColumnInfo createColumnInfo(OsSchemaInfo schemaInfo) { + return new NamePolicyModuleDefaultsColumnInfo(schemaInfo); + } + + public static String getSimpleClassName() { + return "NamePolicyModuleDefaults"; + } + + @SuppressWarnings("cast") + public static some.test.NamePolicyModuleDefaults createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) + throws JSONException { + final List excludeFields = Collections. emptyList(); + some.test.NamePolicyModuleDefaults obj = realm.createObjectInternal(some.test.NamePolicyModuleDefaults.class, true, excludeFields); + + final NamePolicyModuleDefaultsRealmProxyInterface objProxy = (NamePolicyModuleDefaultsRealmProxyInterface) obj; + if (json.has("firstName")) { + if (json.isNull("firstName")) { + objProxy.realmSet$firstName(null); + } else { + objProxy.realmSet$firstName((String) json.getString("firstName")); + } + } + if (json.has("lastName")) { + if (json.isNull("lastName")) { + objProxy.realmSet$lastName(null); + } else { + objProxy.realmSet$lastName((String) json.getString("lastName")); + } + } + return obj; + } + + @SuppressWarnings("cast") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static some.test.NamePolicyModuleDefaults createUsingJsonStream(Realm realm, JsonReader reader) + throws IOException { + final some.test.NamePolicyModuleDefaults obj = new some.test.NamePolicyModuleDefaults(); + final NamePolicyModuleDefaultsRealmProxyInterface objProxy = (NamePolicyModuleDefaultsRealmProxyInterface) obj; + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (false) { + } else if (name.equals("firstName")) { + if (reader.peek() != JsonToken.NULL) { + objProxy.realmSet$firstName((String) reader.nextString()); + } else { + reader.skipValue(); + objProxy.realmSet$firstName(null); + } + } else if (name.equals("lastName")) { + if (reader.peek() != JsonToken.NULL) { + objProxy.realmSet$lastName((String) reader.nextString()); + } else { + reader.skipValue(); + objProxy.realmSet$lastName(null); + } + } else { + reader.skipValue(); + } + } + reader.endObject(); + return realm.copyToRealm(obj); + } + + public static some.test.NamePolicyModuleDefaults copyOrUpdate(Realm realm, some.test.NamePolicyModuleDefaults object, boolean update, Map cache) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); + if (otherRealm.threadId != realm.threadId) { + throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); + } + if (otherRealm.getPath().equals(realm.getPath())) { + return object; + } + } + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + RealmObjectProxy cachedRealmObject = cache.get(object); + if (cachedRealmObject != null) { + return (some.test.NamePolicyModuleDefaults) cachedRealmObject; + } + + return copy(realm, object, update, cache); + } + + public static some.test.NamePolicyModuleDefaults copy(Realm realm, some.test.NamePolicyModuleDefaults newObject, boolean update, Map cache) { + RealmObjectProxy cachedRealmObject = cache.get(newObject); + if (cachedRealmObject != null) { + return (some.test.NamePolicyModuleDefaults) cachedRealmObject; + } + + // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. + some.test.NamePolicyModuleDefaults realmObject = realm.createObjectInternal(some.test.NamePolicyModuleDefaults.class, false, Collections.emptyList()); + cache.put(newObject, (RealmObjectProxy) realmObject); + + NamePolicyModuleDefaultsRealmProxyInterface realmObjectSource = (NamePolicyModuleDefaultsRealmProxyInterface) newObject; + NamePolicyModuleDefaultsRealmProxyInterface realmObjectCopy = (NamePolicyModuleDefaultsRealmProxyInterface) realmObject; + + realmObjectCopy.realmSet$firstName(realmObjectSource.realmGet$firstName()); + realmObjectCopy.realmSet$lastName(realmObjectSource.realmGet$lastName()); + return realmObject; + } + + public static long insert(Realm realm, some.test.NamePolicyModuleDefaults object, Map cache) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + } + Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); + long tableNativePtr = table.getNativePtr(); + NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); + long rowIndex = OsObject.createRow(table); + cache.put(object, rowIndex); + String realmGet$firstName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); + if (realmGet$firstName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + } + String realmGet$lastName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); + if (realmGet$lastName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + } + return rowIndex; + } + + public static void insert(Realm realm, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); + long tableNativePtr = table.getNativePtr(); + NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); + some.test.NamePolicyModuleDefaults object = null; + while (objects.hasNext()) { + object = (some.test.NamePolicyModuleDefaults) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + long rowIndex = OsObject.createRow(table); + cache.put(object, rowIndex); + String realmGet$firstName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); + if (realmGet$firstName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + } + String realmGet$lastName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); + if (realmGet$lastName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + } + } + } + + public static long insertOrUpdate(Realm realm, some.test.NamePolicyModuleDefaults object, Map cache) { + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + } + Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); + long tableNativePtr = table.getNativePtr(); + NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); + long rowIndex = OsObject.createRow(table); + cache.put(object, rowIndex); + String realmGet$firstName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); + if (realmGet$firstName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); + } + String realmGet$lastName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); + if (realmGet$lastName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameIndex, rowIndex, false); + } + return rowIndex; + } + + public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); + long tableNativePtr = table.getNativePtr(); + NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); + some.test.NamePolicyModuleDefaults object = null; + while (objects.hasNext()) { + object = (some.test.NamePolicyModuleDefaults) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + continue; + } + long rowIndex = OsObject.createRow(table); + cache.put(object, rowIndex); + String realmGet$firstName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); + if (realmGet$firstName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); + } + String realmGet$lastName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); + if (realmGet$lastName != null) { + Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameIndex, rowIndex, false); + } + } + } + + public static some.test.NamePolicyModuleDefaults createDetachedCopy(some.test.NamePolicyModuleDefaults realmObject, int currentDepth, int maxDepth, Map> cache) { + if (currentDepth > maxDepth || realmObject == null) { + return null; + } + CacheData cachedObject = cache.get(realmObject); + some.test.NamePolicyModuleDefaults unmanagedObject; + if (cachedObject == null) { + unmanagedObject = new some.test.NamePolicyModuleDefaults(); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + } else { + // Reuse cached object or recreate it because it was encountered at a lower depth. + if (currentDepth >= cachedObject.minDepth) { + return (some.test.NamePolicyModuleDefaults) cachedObject.object; + } + unmanagedObject = (some.test.NamePolicyModuleDefaults) cachedObject.object; + cachedObject.minDepth = currentDepth; + } + NamePolicyModuleDefaultsRealmProxyInterface unmanagedCopy = (NamePolicyModuleDefaultsRealmProxyInterface) unmanagedObject; + NamePolicyModuleDefaultsRealmProxyInterface realmSource = (NamePolicyModuleDefaultsRealmProxyInterface) realmObject; + unmanagedCopy.realmSet$firstName(realmSource.realmGet$firstName()); + unmanagedCopy.realmSet$lastName(realmSource.realmGet$lastName()); + + return unmanagedObject; + } + + @Override + @SuppressWarnings("ArrayToString") + public String toString() { + if (!RealmObject.isValid(this)) { + return "Invalid object"; + } + StringBuilder stringBuilder = new StringBuilder("NamePolicyModuleDefaults = proxy["); + stringBuilder.append("{firstName:"); + stringBuilder.append(realmGet$firstName() != null ? realmGet$firstName() : "null"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{lastName:"); + stringBuilder.append(realmGet$lastName() != null ? realmGet$lastName() : "null"); + stringBuilder.append("}"); + stringBuilder.append("]"); + return stringBuilder.toString(); + } + + @Override + public ProxyState realmGet$proxyState() { + return proxyState; + } + + @Override + public int hashCode() { + String realmName = proxyState.getRealm$realm().getPath(); + String tableName = proxyState.getRow$realm().getTable().getName(); + long rowIndex = proxyState.getRow$realm().getIndex(); + + int result = 17; + result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); + result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); + result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32)); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NamePolicyModuleDefaultsRealmProxy aNamePolicyModuleDefaults = (NamePolicyModuleDefaultsRealmProxy)o; + + String path = proxyState.getRealm$realm().getPath(); + String otherPath = aNamePolicyModuleDefaults.proxyState.getRealm$realm().getPath(); + if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + + String tableName = proxyState.getRow$realm().getTable().getName(); + String otherTableName = aNamePolicyModuleDefaults.proxyState.getRow$realm().getTable().getName(); + if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; + + if (proxyState.getRow$realm().getIndex() != aNamePolicyModuleDefaults.proxyState.getRow$realm().getIndex()) return false; + + return true; + } +} diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java index fecf92e93e..e7af4b8897 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java @@ -80,47 +80,47 @@ static final class NullTypesColumnInfo extends ColumnInfo { NullTypesColumnInfo(OsSchemaInfo schemaInfo) { super(41); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("NullTypes"); - this.fieldStringNotNullIndex = addColumnDetails("fieldStringNotNull", objectSchemaInfo); - this.fieldStringNullIndex = addColumnDetails("fieldStringNull", objectSchemaInfo); - this.fieldBooleanNotNullIndex = addColumnDetails("fieldBooleanNotNull", objectSchemaInfo); - this.fieldBooleanNullIndex = addColumnDetails("fieldBooleanNull", objectSchemaInfo); - this.fieldBytesNotNullIndex = addColumnDetails("fieldBytesNotNull", objectSchemaInfo); - this.fieldBytesNullIndex = addColumnDetails("fieldBytesNull", objectSchemaInfo); - this.fieldByteNotNullIndex = addColumnDetails("fieldByteNotNull", objectSchemaInfo); - this.fieldByteNullIndex = addColumnDetails("fieldByteNull", objectSchemaInfo); - this.fieldShortNotNullIndex = addColumnDetails("fieldShortNotNull", objectSchemaInfo); - this.fieldShortNullIndex = addColumnDetails("fieldShortNull", objectSchemaInfo); - this.fieldIntegerNotNullIndex = addColumnDetails("fieldIntegerNotNull", objectSchemaInfo); - this.fieldIntegerNullIndex = addColumnDetails("fieldIntegerNull", objectSchemaInfo); - this.fieldLongNotNullIndex = addColumnDetails("fieldLongNotNull", objectSchemaInfo); - this.fieldLongNullIndex = addColumnDetails("fieldLongNull", objectSchemaInfo); - this.fieldFloatNotNullIndex = addColumnDetails("fieldFloatNotNull", objectSchemaInfo); - this.fieldFloatNullIndex = addColumnDetails("fieldFloatNull", objectSchemaInfo); - this.fieldDoubleNotNullIndex = addColumnDetails("fieldDoubleNotNull", objectSchemaInfo); - this.fieldDoubleNullIndex = addColumnDetails("fieldDoubleNull", objectSchemaInfo); - this.fieldDateNotNullIndex = addColumnDetails("fieldDateNotNull", objectSchemaInfo); - this.fieldDateNullIndex = addColumnDetails("fieldDateNull", objectSchemaInfo); - this.fieldObjectNullIndex = addColumnDetails("fieldObjectNull", objectSchemaInfo); - this.fieldStringListNotNullIndex = addColumnDetails("fieldStringListNotNull", objectSchemaInfo); - this.fieldStringListNullIndex = addColumnDetails("fieldStringListNull", objectSchemaInfo); - this.fieldBinaryListNotNullIndex = addColumnDetails("fieldBinaryListNotNull", objectSchemaInfo); - this.fieldBinaryListNullIndex = addColumnDetails("fieldBinaryListNull", objectSchemaInfo); - this.fieldBooleanListNotNullIndex = addColumnDetails("fieldBooleanListNotNull", objectSchemaInfo); - this.fieldBooleanListNullIndex = addColumnDetails("fieldBooleanListNull", objectSchemaInfo); - this.fieldLongListNotNullIndex = addColumnDetails("fieldLongListNotNull", objectSchemaInfo); - this.fieldLongListNullIndex = addColumnDetails("fieldLongListNull", objectSchemaInfo); - this.fieldIntegerListNotNullIndex = addColumnDetails("fieldIntegerListNotNull", objectSchemaInfo); - this.fieldIntegerListNullIndex = addColumnDetails("fieldIntegerListNull", objectSchemaInfo); - this.fieldShortListNotNullIndex = addColumnDetails("fieldShortListNotNull", objectSchemaInfo); - this.fieldShortListNullIndex = addColumnDetails("fieldShortListNull", objectSchemaInfo); - this.fieldByteListNotNullIndex = addColumnDetails("fieldByteListNotNull", objectSchemaInfo); - this.fieldByteListNullIndex = addColumnDetails("fieldByteListNull", objectSchemaInfo); - this.fieldDoubleListNotNullIndex = addColumnDetails("fieldDoubleListNotNull", objectSchemaInfo); - this.fieldDoubleListNullIndex = addColumnDetails("fieldDoubleListNull", objectSchemaInfo); - this.fieldFloatListNotNullIndex = addColumnDetails("fieldFloatListNotNull", objectSchemaInfo); - this.fieldFloatListNullIndex = addColumnDetails("fieldFloatListNull", objectSchemaInfo); - this.fieldDateListNotNullIndex = addColumnDetails("fieldDateListNotNull", objectSchemaInfo); - this.fieldDateListNullIndex = addColumnDetails("fieldDateListNull", objectSchemaInfo); + this.fieldStringNotNullIndex = addColumnDetails("fieldStringNotNull", "fieldStringNotNull", objectSchemaInfo); + this.fieldStringNullIndex = addColumnDetails("fieldStringNull", "fieldStringNull", objectSchemaInfo); + this.fieldBooleanNotNullIndex = addColumnDetails("fieldBooleanNotNull", "fieldBooleanNotNull", objectSchemaInfo); + this.fieldBooleanNullIndex = addColumnDetails("fieldBooleanNull", "fieldBooleanNull", objectSchemaInfo); + this.fieldBytesNotNullIndex = addColumnDetails("fieldBytesNotNull", "fieldBytesNotNull", objectSchemaInfo); + this.fieldBytesNullIndex = addColumnDetails("fieldBytesNull", "fieldBytesNull", objectSchemaInfo); + this.fieldByteNotNullIndex = addColumnDetails("fieldByteNotNull", "fieldByteNotNull", objectSchemaInfo); + this.fieldByteNullIndex = addColumnDetails("fieldByteNull", "fieldByteNull", objectSchemaInfo); + this.fieldShortNotNullIndex = addColumnDetails("fieldShortNotNull", "fieldShortNotNull", objectSchemaInfo); + this.fieldShortNullIndex = addColumnDetails("fieldShortNull", "fieldShortNull", objectSchemaInfo); + this.fieldIntegerNotNullIndex = addColumnDetails("fieldIntegerNotNull", "fieldIntegerNotNull", objectSchemaInfo); + this.fieldIntegerNullIndex = addColumnDetails("fieldIntegerNull", "fieldIntegerNull", objectSchemaInfo); + this.fieldLongNotNullIndex = addColumnDetails("fieldLongNotNull", "fieldLongNotNull", objectSchemaInfo); + this.fieldLongNullIndex = addColumnDetails("fieldLongNull", "fieldLongNull", objectSchemaInfo); + this.fieldFloatNotNullIndex = addColumnDetails("fieldFloatNotNull", "fieldFloatNotNull", objectSchemaInfo); + this.fieldFloatNullIndex = addColumnDetails("fieldFloatNull", "fieldFloatNull", objectSchemaInfo); + this.fieldDoubleNotNullIndex = addColumnDetails("fieldDoubleNotNull", "fieldDoubleNotNull", objectSchemaInfo); + this.fieldDoubleNullIndex = addColumnDetails("fieldDoubleNull", "fieldDoubleNull", objectSchemaInfo); + this.fieldDateNotNullIndex = addColumnDetails("fieldDateNotNull", "fieldDateNotNull", objectSchemaInfo); + this.fieldDateNullIndex = addColumnDetails("fieldDateNull", "fieldDateNull", objectSchemaInfo); + this.fieldObjectNullIndex = addColumnDetails("fieldObjectNull", "fieldObjectNull", objectSchemaInfo); + this.fieldStringListNotNullIndex = addColumnDetails("fieldStringListNotNull", "fieldStringListNotNull", objectSchemaInfo); + this.fieldStringListNullIndex = addColumnDetails("fieldStringListNull", "fieldStringListNull", objectSchemaInfo); + this.fieldBinaryListNotNullIndex = addColumnDetails("fieldBinaryListNotNull", "fieldBinaryListNotNull", objectSchemaInfo); + this.fieldBinaryListNullIndex = addColumnDetails("fieldBinaryListNull", "fieldBinaryListNull", objectSchemaInfo); + this.fieldBooleanListNotNullIndex = addColumnDetails("fieldBooleanListNotNull", "fieldBooleanListNotNull", objectSchemaInfo); + this.fieldBooleanListNullIndex = addColumnDetails("fieldBooleanListNull", "fieldBooleanListNull", objectSchemaInfo); + this.fieldLongListNotNullIndex = addColumnDetails("fieldLongListNotNull", "fieldLongListNotNull", objectSchemaInfo); + this.fieldLongListNullIndex = addColumnDetails("fieldLongListNull", "fieldLongListNull", objectSchemaInfo); + this.fieldIntegerListNotNullIndex = addColumnDetails("fieldIntegerListNotNull", "fieldIntegerListNotNull", objectSchemaInfo); + this.fieldIntegerListNullIndex = addColumnDetails("fieldIntegerListNull", "fieldIntegerListNull", objectSchemaInfo); + this.fieldShortListNotNullIndex = addColumnDetails("fieldShortListNotNull", "fieldShortListNotNull", objectSchemaInfo); + this.fieldShortListNullIndex = addColumnDetails("fieldShortListNull", "fieldShortListNull", objectSchemaInfo); + this.fieldByteListNotNullIndex = addColumnDetails("fieldByteListNotNull", "fieldByteListNotNull", objectSchemaInfo); + this.fieldByteListNullIndex = addColumnDetails("fieldByteListNull", "fieldByteListNull", objectSchemaInfo); + this.fieldDoubleListNotNullIndex = addColumnDetails("fieldDoubleListNotNull", "fieldDoubleListNotNull", objectSchemaInfo); + this.fieldDoubleListNullIndex = addColumnDetails("fieldDoubleListNull", "fieldDoubleListNull", objectSchemaInfo); + this.fieldFloatListNotNullIndex = addColumnDetails("fieldFloatListNotNull", "fieldFloatListNotNull", objectSchemaInfo); + this.fieldFloatListNullIndex = addColumnDetails("fieldFloatListNull", "fieldFloatListNull", objectSchemaInfo); + this.fieldDateListNotNullIndex = addColumnDetails("fieldDateListNotNull", "fieldDateListNotNull", objectSchemaInfo); + this.fieldDateListNullIndex = addColumnDetails("fieldDateListNull", "fieldDateListNull", objectSchemaInfo); } NullTypesColumnInfo(ColumnInfo src, boolean mutable) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 506273e123..efb8bd8dab 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -52,7 +52,7 @@ public String getSimpleClassNameImpl(Class clazz) { checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { - return io.realm.AllTypesRealmProxy.getSimpleClassName(); + return "AllTypes"; } throw getMissingProxyClassException(clazz); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java index 1ea6822a31..1039391788 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java @@ -41,8 +41,8 @@ static final class SimpleColumnInfo extends ColumnInfo { SimpleColumnInfo(OsSchemaInfo schemaInfo) { super(2); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("Simple"); - this.nameIndex = addColumnDetails("name", objectSchemaInfo); - this.ageIndex = addColumnDetails("age", objectSchemaInfo); + this.nameIndex = addColumnDetails("name", "name", objectSchemaInfo); + this.ageIndex = addColumnDetails("age", "age", objectSchemaInfo); } SimpleColumnInfo(ColumnInfo src, boolean mutable) { diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyClassOnly.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyClassOnly.java new file mode 100644 index 0000000000..0b0972fa0f --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyClassOnly.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.annotations.RealmClass; +import io.realm.RealmObject; +import io.realm.annotations.RealmField; +import io.realm.annotations.RealmNamingPolicy; + +/** + * Class with only a custom name + */ +@RealmClass(name = "customName") +public class NamePolicyClassOnly extends RealmObject { + + public String firstName; + public String lastName; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForAllClasses.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForAllClasses.java new file mode 100644 index 0000000000..0b2d6f94d1 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForAllClasses.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +import io.realm.annotations.RealmModule; +import io.realm.annotations.RealmNamingPolicy; +import some.test.AllTypes; + +public class NamePolicyConflictingModuleDefinitionsForAllClasses { + + @RealmModule(allClasses = true, + classNamingPolicy = RealmNamingPolicy.IDENTITY, + fieldNamingPolicy = RealmNamingPolicy.IDENTITY) + public class MyModule1 { + + } + + @RealmModule(allClasses = true, + classNamingPolicy = RealmNamingPolicy.CAMEL_CASE, + fieldNamingPolicy = RealmNamingPolicy.CAMEL_CASE) + public class MyModule2 { + + } + +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForMixedDefinitions.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForMixedDefinitions.java new file mode 100644 index 0000000000..95cce279b5 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForMixedDefinitions.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +import io.realm.annotations.RealmModule; +import io.realm.annotations.RealmNamingPolicy; +import some.test.Simple; + +public class NamePolicyConflictingModuleDefinitionsForMixedDefinitions { + + @RealmModule(classes = { Simple.class }, + classNamingPolicy = RealmNamingPolicy.IDENTITY, + fieldNamingPolicy = RealmNamingPolicy.IDENTITY) + public class MyModule1 { + + } + + @RealmModule(allClasses = true, + classNamingPolicy = RealmNamingPolicy.CAMEL_CASE, + fieldNamingPolicy = RealmNamingPolicy.CAMEL_CASE) + public class MyModule2 { + + } + +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForNamedClasses.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForNamedClasses.java new file mode 100644 index 0000000000..cf072d1dd0 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyConflictingModuleDefinitionsForNamedClasses.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +import io.realm.annotations.RealmModule; +import io.realm.annotations.RealmNamingPolicy; +import some.test.Simple; + +public class NamePolicyConflictingModuleDefinitionsForNamedClasses { + + @RealmModule(classes = { Simple.class }, + classNamingPolicy = RealmNamingPolicy.IDENTITY, + fieldNamingPolicy = RealmNamingPolicy.IDENTITY) + public class MyModule1 { + + } + + @RealmModule(classes = { Simple.class }, + classNamingPolicy = RealmNamingPolicy.CAMEL_CASE, + fieldNamingPolicy = RealmNamingPolicy.CAMEL_CASE) + public class MyModule2 { + + } + +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyFieldNameOnly.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyFieldNameOnly.java new file mode 100644 index 0000000000..1109fb5e21 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyFieldNameOnly.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.annotations.RealmClass; +import io.realm.RealmObject; +import io.realm.annotations.RealmField; +import io.realm.annotations.RealmNamingPolicy; + +/** + * Class with only a field name annotation + */ +public class NamePolicyFieldNameOnly extends RealmObject { + + @RealmField(name = "first_name") + public String firstName; + public String lastName; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyMixedClassSettings.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyMixedClassSettings.java new file mode 100644 index 0000000000..3281a82a23 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyMixedClassSettings.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.annotations.RealmClass; +import io.realm.RealmObject; +import io.realm.annotations.RealmField; +import io.realm.annotations.RealmNamingPolicy; + +/** + * Class with mixed settings class/field name settings + */ +@RealmClass(name = "customName", fieldNamingPolicy = RealmNamingPolicy.PASCAL_CASE) +public class NamePolicyMixedClassSettings extends RealmObject { + + @RealmField(name = "first_name") + public String firstName; + public String lastName; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyModule.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyModule.java new file mode 100644 index 0000000000..ce903bd6bb --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyModule.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.annotations.RealmModule; +import io.realm.annotations.RealmNamingPolicy; + +@RealmModule(allClasses = true, classNamingPolicy = RealmNamingPolicy.PASCAL_CASE, fieldNamingPolicy = RealmNamingPolicy.PASCAL_CASE) +public class NamePolicyModule { + +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyModuleDefaults.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyModuleDefaults.java new file mode 100644 index 0000000000..cf6e7cc46f --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyModuleDefaults.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.annotations.RealmField; +import io.realm.annotations.RealmNamingPolicy; + +/** + * Class which inherit all naming policies from the module. + */ +public class NamePolicyModuleDefaults extends RealmObject { + public String firstName; + public String lastName; +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java b/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java new file mode 100644 index 0000000000..a49f7393a3 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java @@ -0,0 +1,237 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.realm.entities.realmname.ClassNameOverrideModulePolicy; +import io.realm.entities.realmname.ClassWithPolicy; +import io.realm.entities.realmname.CustomRealmNamesModule; +import io.realm.entities.realmname.FieldNameOverrideClassPolicy; +import io.realm.rule.TestRealmConfigurationFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * This class contains tests for checking that changing the internal Realm name + * works correctly. + */ +@RunWith(AndroidJUnit4.class) +public class CustomRealmNameTests { + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + private Realm realm; + private DynamicRealm dynamicRealm; + + @Before + public void setUp() { + RealmConfiguration config = configFactory.createConfigurationBuilder() + .modules(new CustomRealmNamesModule()) + .build(); + realm = Realm.getInstance(config); + dynamicRealm = DynamicRealm.getInstance(config); + } + + + @After + public void tearDown() { + if (realm != null && !realm.isClosed()) { + realm.close(); + } + if (dynamicRealm != null && !dynamicRealm.isClosed()) { + dynamicRealm.close(); + } + } + + // + // Build checks + // + + // Check that the module policy is used as the default for class and field names + @Test + public void modulePolicy_defaultPolicy() { + assertTrue(realm.getSchema().contains("default_policy_from_module")); + RealmObjectSchema classSchema = realm.getSchema().get("default_policy_from_module"); + assertTrue(classSchema.hasField("camel_case")); + } + + // Check that field name policies on classes override those from modules + @Test + public void classFieldPolicy_overrideModuleFieldPolicy() { + assertTrue(realm.getSchema().contains(ClassWithPolicy.CLASS_NAME)); + RealmObjectSchema classSchema = realm.getSchema().get(ClassWithPolicy.CLASS_NAME); + for (String field : ClassWithPolicy.ALL_FIELDS) { + assertTrue(field + " was not found.", classSchema.hasField(field)); + } + } + + // Check that explicit class name override both module and class policies + @Test + public void className_overrideModuleClassPolicy() { + assertTrue(realm.getSchema().contains(ClassNameOverrideModulePolicy.CLASS_NAME)); + } + + // Check that a explicitly setting a field name overrides a class field name policy + @Test + public void fieldName_overrideClassPolicy() { + RealmObjectSchema classSchema = realm.getSchema().get(FieldNameOverrideClassPolicy.CLASS_NAME); + assertTrue(classSchema.hasField(FieldNameOverrideClassPolicy.FIELD_CAMEL_CASE)); + } + + // Check that a explicitly setting a field name overrides a module field name policy + @Test + public void fieldName_overrideModulePolicy() { + RealmObjectSchema classSchema = realm.getSchema().get(FieldNameOverrideClassPolicy.CLASS_NAME); + assertTrue(classSchema.hasField(FieldNameOverrideClassPolicy.FIELD_CAMEL_CASE)); + } + + // + // Query tests + // + // Mostly smoke test, as we only want to test that the query system correctly maps between + // Java field names and cores. + // + @Test + public void typedQueryWithJavaNames() { + RealmResults results = realm.where(ClassWithPolicy.class) + .equalTo("camelCase", "foo") // Java name in model class + .equalTo("parents.PascalCase", 1) // Backlinks also uses java names + .sort("mHungarian") // Sorting uses Java names + .distinctValues("customName") // Distinct uses Java names + .findAll(); + assertTrue(results.isEmpty()); + } + + @Test + public void typedQueryWithInternalNamesThrows() { + + // Normal predicates + try { + realm.where(ClassWithPolicy.class).equalTo(ClassWithPolicy.FIELD_CAMEL_CASE, ""); + } catch (IllegalArgumentException ignore) { + } + + // Sorting + try { + realm.where(ClassWithPolicy.class).sort(ClassWithPolicy.FIELD_CAMEL_CASE); + } catch (IllegalArgumentException ignore) { + } + + // Distinct + try { + realm.where(ClassWithPolicy.class).distinctValues(ClassWithPolicy.FIELD_CAMEL_CASE); + } catch (IllegalArgumentException ignore) { + } + + // Backlinks do not exist as internal fields that can be queried + } + + + @Test + public void dynamicQueryWithInternalNames() { + // Backlink queries not supported on dynamic queries + RealmResults results = dynamicRealm.where(ClassWithPolicy.CLASS_NAME) + .equalTo(ClassWithPolicy.FIELD_CAMEL_CASE, "foo") // Normal queries use internal names + .sort(ClassWithPolicy.FIELD_M_HUNGARIAN) // Sorting uses internal names + .distinctValues(ClassWithPolicy.FIELD_CUSTOM_NAME) // Distinct uses internal names + .findAll(); + assertTrue(results.isEmpty()); + } + + @Test + public void dynamicQueryWithJavaNamesThrows() { + try { + dynamicRealm.where(ClassWithPolicy.CLASS_NAME).equalTo("camelCase", ""); + } catch (IllegalArgumentException ignore) { + } + + // Sorting + try { + dynamicRealm.where(ClassWithPolicy.CLASS_NAME).sort("camelCase"); + } catch (IllegalArgumentException ignore) { + } + + // Distinct + try { + dynamicRealm.where(ClassWithPolicy.CLASS_NAME).distinctValues("camelCase"); + } catch (IllegalArgumentException ignore) { + } + } + + // + // Schema tests + // + @Test + public void typedSchemaReturnsInternalNames() { + RealmSchema schema = realm.getSchema(); + assertTrue(schema.contains(ClassWithPolicy.CLASS_NAME)); + RealmObjectSchema classSchema = schema.get(ClassWithPolicy.CLASS_NAME); + assertEquals(ClassWithPolicy.ALL_FIELDS.size(), classSchema.getFieldNames().size()); + for (String fieldName : ClassWithPolicy.ALL_FIELDS) { + assertTrue("Could not find: " + fieldName, classSchema.hasField(fieldName)); + } + } + + @Test + public void dynamicSchemaReturnsInternalNames() { + RealmSchema schema = realm.getSchema(); + assertTrue(schema.contains(ClassWithPolicy.CLASS_NAME)); + RealmObjectSchema classSchema = schema.get(ClassWithPolicy.CLASS_NAME); + assertEquals(ClassWithPolicy.ALL_FIELDS.size(), classSchema.getFieldNames().size()); + for (String fieldName : ClassWithPolicy.ALL_FIELDS) { + assertTrue("Could not find: " + fieldName, classSchema.hasField(fieldName)); + } + } + + // + // Dynamic Realm tests + // + @Test + public void createObjects() { + dynamicRealm.executeTransaction(r -> { + // Use internal name + DynamicRealmObject obj = r.createObject(ClassWithPolicy.CLASS_NAME); + assertNotNull(obj); + }); + } + + + // + // Realm tests + // + @Test + public void copyOrUpdate() { + realm.executeTransaction(r -> { + ClassWithPolicy obj = new ClassWithPolicy(); + try { + r.copyToRealmOrUpdate(obj); // Verify that we correctly check that a primary key is missing + fail(); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().startsWith("A RealmObject with no @PrimaryKey cannot be updated")); + } + }); + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassNameOverrideModulePolicy.java b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassNameOverrideModulePolicy.java new file mode 100644 index 0000000000..2ed523910c --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassNameOverrideModulePolicy.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities.realmname; + +import java.util.Arrays; +import java.util.List; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.RealmClass; +import io.realm.annotations.RealmField; +import io.realm.annotations.RealmNamingPolicy; + +// Class will inherit RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES from the module `CustomRealmNamesModule` +@RealmClass(name = "class-name-override", fieldNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) +public class ClassNameOverrideModulePolicy extends RealmObject { + + // Expected internal names + public static final String CLASS_NAME = "class-name-override"; + public static final String FIELD_CAMEL_CASE = "camel_case"; + public static final String FIELD_PASCAL_CASE = "pascal_case"; + public static final String FIELD_M_HUNGARIAN = "hungarian"; + public static final String FIELD_ALLCAPS = "allcaps"; + public static final String FIELD_ALLLOWER = "alllower"; + public static final String FIELD_WITH_UNDERSCORES = "with_underscores"; + public static final String FIELD_WITH_SPECIAL_CHARS = "internal_var"; + public static final String FIELD_CUSTOM_NAME = "a different name"; + public static final List ALL_FIELDS = Arrays.asList( + FIELD_CAMEL_CASE, + FIELD_PASCAL_CASE, + FIELD_M_HUNGARIAN, + FIELD_ALLCAPS, + FIELD_ALLLOWER, + FIELD_WITH_UNDERSCORES, + FIELD_WITH_SPECIAL_CHARS, + FIELD_CUSTOM_NAME + ); + + public String camelCase; + public int PascalCase; + public boolean mHungarian; + public boolean ALLCAPS; + public boolean alllower; + public boolean with_underscores; + public RealmList $_internalVar; + @RealmField(name = "a different name") // This will override the class policy + public String customName; +} + + diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassWithPolicy.java b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassWithPolicy.java new file mode 100644 index 0000000000..8af0c469b3 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassWithPolicy.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities.realmname; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.RealmField; +import io.realm.annotations.RealmNamingPolicy; + +@RealmClass(fieldNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) +public class ClassWithPolicy extends RealmObject { + + public static final String CLASS_NAME = "class_with_policy"; + public static final String FIELD_CAMEL_CASE = "camel_case"; + public static final String FIELD_PASCAL_CASE = "pascal_case"; + public static final String FIELD_M_HUNGARIAN = "hungarian"; + public static final String FIELD_ALLCAPS = "allcaps"; + public static final String FIELD_ALLLOWER = "alllower"; + public static final String FIELD_FIRST_CAPS = "first_caps"; + public static final String FIELD_WITH_UNDERSCORES = "with_underscores"; + public static final String FIELD_WITH_SPECIAL_CHARS = "internal_var"; + public static final String FIELD_CUSTOM_NAME = "a different name"; + public static final List ALL_FIELDS = Arrays.asList( + FIELD_CAMEL_CASE, + FIELD_PASCAL_CASE, + FIELD_M_HUNGARIAN, + FIELD_ALLCAPS, + FIELD_ALLLOWER, + FIELD_FIRST_CAPS, + FIELD_WITH_UNDERSCORES, + FIELD_WITH_SPECIAL_CHARS, + FIELD_CUSTOM_NAME + ); + + public String camelCase; + public int PascalCase; + public boolean mHungarian; + public byte[] ALLCAPS; + public Date alllower; + public long FIRSTCaps; + public ClassWithPolicy with_underscores; + public RealmList $_internalVar; + @RealmField(name = "a different name") + public String customName; + + @LinkingObjects("with_underscores") + public final RealmResults parents = null; +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/CustomRealmNamesModule.java b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/CustomRealmNamesModule.java new file mode 100644 index 0000000000..ec11780ecf --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/CustomRealmNamesModule.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.entities.realmname; + +import io.realm.annotations.RealmModule; +import io.realm.annotations.RealmNamingPolicy; + +@RealmModule(classes = { + ClassNameOverrideModulePolicy.class, + ClassWithPolicy.class, + DefaultPolicyFromModule.class, + FieldNameOverrideClassPolicy.class }, + classNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES, + fieldNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES +) +public class CustomRealmNamesModule { + +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/DefaultPolicyFromModule.java b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/DefaultPolicyFromModule.java new file mode 100644 index 0000000000..925b865852 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/DefaultPolicyFromModule.java @@ -0,0 +1,22 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.entities.realmname; + +import io.realm.RealmObject; + +public class DefaultPolicyFromModule extends RealmObject { + public String camelCase; // case formatter should be inherited from CustomRealmNamesModule +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/FieldNameOverrideClassPolicy.java b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/FieldNameOverrideClassPolicy.java new file mode 100644 index 0000000000..5be787f68b --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/FieldNameOverrideClassPolicy.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ +package io.realm.entities.realmname; + +import io.realm.RealmObject; +import io.realm.annotations.RealmClass; +import io.realm.annotations.RealmField; +import io.realm.annotations.RealmNamingPolicy; + +@RealmClass(fieldNamingPolicy = RealmNamingPolicy.PASCAL_CASE) +public class FieldNameOverrideClassPolicy extends RealmObject { + + public static final String CLASS_NAME = "field_name_override_class_policy"; + public static final String FIELD_CAMEL_CASE = "camel_case"; + + @RealmField(name = FIELD_CAMEL_CASE) + public String camelCase; + +} diff --git a/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java index 48c3e67d4e..46b8507beb 100644 --- a/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java @@ -18,6 +18,7 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.Table; +import io.realm.internal.fields.FieldDescriptor; /** * Immutable {@link RealmObjectSchema}. @@ -104,4 +105,17 @@ public RealmObjectSchema setNullable(String fieldName, boolean nullable) { public RealmObjectSchema transform(Function function) { throw new UnsupportedOperationException(SCHEMA_IMMUTABLE_EXCEPTION_MSG); } + + /** + * Returns a field descriptor based on Java field names found in model classes. + * + * @param publicJavaNameDescription field name or linked field description + * @param validColumnTypes valid field type for the last field in a linked field + * @return the corresponding FieldDescriptor. + * @throws IllegalArgumentException if a proper FieldDescriptor could not be created. + */ + @Override + FieldDescriptor getColumnIndices(String publicJavaNameDescription, RealmFieldType... validColumnTypes) { + return FieldDescriptor.createStandardFieldDescriptor(getSchemaConnector(), getTable(), publicJavaNameDescription, validColumnTypes); + } } diff --git a/realm/realm-library/src/main/java/io/realm/ImmutableRealmSchema.java b/realm/realm-library/src/main/java/io/realm/ImmutableRealmSchema.java index b3d79f7da0..7740f4899e 100644 --- a/realm/realm-library/src/main/java/io/realm/ImmutableRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/ImmutableRealmSchema.java @@ -20,7 +20,9 @@ import io.realm.internal.Table; /** - * Immutable {@link RealmSchema}. + * Immutable {@link RealmSchema} used by {@link Realm}. + * + * @see MutableRealmSchema for schema support for {@link DynamicRealm}. */ class ImmutableRealmSchema extends RealmSchema { diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index 8015c22c75..277a665446 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -22,6 +22,7 @@ import io.realm.internal.OsObjectStore; import io.realm.internal.Table; +import io.realm.internal.fields.FieldDescriptor; /** * Mutable {@link RealmObjectSchema}. @@ -291,6 +292,19 @@ public RealmObjectSchema transform(Function function) { return this; } + /** + * Returns a field descriptor based on the internal field names found in the Realm file. + * + * @param internalColumnNameDescription internal column name or internal linked column name description. + * @param validColumnTypes valid field type for the last field in a linked field + * @return the corresponding FieldDescriptor. + * @throws IllegalArgumentException if a proper FieldDescriptor could not be created. + */ + @Override + FieldDescriptor getColumnIndices(String internalColumnNameDescription, RealmFieldType... validColumnTypes) { + return FieldDescriptor.createStandardFieldDescriptor(getSchemaConnector(), getTable(), internalColumnNameDescription, validColumnTypes); + } + // Invariant: Field was just added. This method is responsible for cleaning up attributes if it fails. private void addModifiers(String fieldName, FieldAttribute[] attributes) { boolean indexAdded = false; diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java index 10f060b24e..83c0c72ec0 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java @@ -22,7 +22,9 @@ import io.realm.internal.Table; /** - * Mutable {@link RealmSchema}. + * Mutable {@link RealmSchema} used by {@link DynamicRealm}. + * + * @see ImmutableRealmSchema for schema support for {@link Realm}. */ class MutableRealmSchema extends RealmSchema { diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 338e194e51..8ce435b4b6 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -424,9 +424,7 @@ public RealmFieldType getFieldType(String fieldName) { * @param validColumnTypes valid field type for the last field in a linked field * @return a FieldDescriptor */ - protected final FieldDescriptor getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes) { - return FieldDescriptor.createStandardFieldDescriptor(getSchemaConnector(), getTable(), fieldDescription, validColumnTypes); - } + abstract FieldDescriptor getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes); RealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { long columnIndex = table.addColumn(type, name, (required) ? Table.NOT_NULLABLE : Table.NULLABLE); @@ -464,7 +462,7 @@ static final Map, FieldMetaData> getSupportedSimpleFields() { return SUPPORTED_SIMPLE_FIELDS; } - private SchemaConnector getSchemaConnector() { + protected final SchemaConnector getSchemaConnector() { return new SchemaConnector(schema); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java index 07bceca628..d90b47c31d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java @@ -88,7 +88,7 @@ public ColumnInfo getColumnInfo(Class clazz) { } /** - * Returns the {@link ColumnInfo} for the passed class name. + * Returns the {@link ColumnInfo} for the provided internal class name. * * @param simpleClassName the simple name of the class for which to get the ColumnInfo. * @return the corresponding {@link ColumnInfo} object. diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java index 1825e855b9..3c88cd5f3b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java @@ -90,7 +90,8 @@ public String toString() { } - private final Map indicesMap; + private final Map indicesFromJavaFieldNames; + private final Map indicesFromColumnNames; private final boolean mutable; /** @@ -109,15 +110,16 @@ protected ColumnInfo(int mapSize) { * @param mutable false to make this instance effectively final */ protected ColumnInfo(@Nullable ColumnInfo src, boolean mutable) { - this((src == null) ? 0 : src.indicesMap.size(), mutable); + this((src == null) ? 0 : src.indicesFromJavaFieldNames.size(), mutable); // ColumnDetails are immutable and may be re-used. if (src != null) { - indicesMap.putAll(src.indicesMap); + indicesFromJavaFieldNames.putAll(src.indicesFromJavaFieldNames); } } private ColumnInfo(int mapSize, boolean mutable) { - this.indicesMap = new HashMap<>(mapSize); + this.indicesFromJavaFieldNames = new HashMap<>(mapSize); + this.indicesFromColumnNames = new HashMap<>(mapSize); this.mutable = mutable; } @@ -135,8 +137,8 @@ public final boolean isMutable() { * * @return column index. */ - public long getColumnIndex(String columnName) { - ColumnDetails details = indicesMap.get(columnName); + public long getColumnIndex(String javaFieldName) { + ColumnDetails details = indicesFromJavaFieldNames.get(javaFieldName); return (details == null) ? -1 : details.columnIndex; } @@ -146,8 +148,8 @@ public long getColumnIndex(String columnName) { * @return {@link ColumnDetails} or {@code null} if not found. */ @Nullable - public ColumnDetails getColumnDetails(String columnName) { - return indicesMap.get(columnName); + public ColumnDetails getColumnDetails(String javaFieldName) { + return indicesFromJavaFieldNames.get(javaFieldName); } /** @@ -165,22 +167,36 @@ public void copyFrom(ColumnInfo src) { throw new NullPointerException("Attempt to copy null ColumnInfo"); } - indicesMap.clear(); - indicesMap.putAll(src.indicesMap); + indicesFromJavaFieldNames.clear(); + indicesFromJavaFieldNames.putAll(src.indicesFromJavaFieldNames); + indicesFromColumnNames.clear(); + indicesFromColumnNames.putAll(src.indicesFromColumnNames); copy(src, this); } @Override public String toString() { StringBuilder buf = new StringBuilder("ColumnInfo["); - buf.append(mutable).append(","); - if (indicesMap != null) { + buf.append("mutable="+mutable).append(","); + if (indicesFromJavaFieldNames != null) { + buf.append("JavaFieldNames=["); boolean commaNeeded = false; - for (Map.Entry entry : indicesMap.entrySet()) { + for (Map.Entry entry : indicesFromJavaFieldNames.entrySet()) { if (commaNeeded) { buf.append(","); } buf.append(entry.getKey()).append("->").append(entry.getValue()); commaNeeded = true; } + buf.append("]"); + } + if (indicesFromColumnNames != null) { + buf.append(", InternalFieldNames=["); + boolean commaNeeded = false; + for (Map.Entry entry : indicesFromColumnNames.entrySet()) { + if (commaNeeded) { buf.append(","); } + buf.append(entry.getKey()).append("->").append(entry.getValue()); + commaNeeded = true; + } + buf.append("]"); } return buf.append("]").toString(); } @@ -212,13 +228,16 @@ public String toString() { *

            * No validation done here. Presuming that all necessary validation takes place in {@code Proxy.validateTable}. * - * @param columnName The name of the column whose index is sought. + * @param javaFieldName The name of the java field name. + * @param internalColumnName The underlying column name in the Realm file for the Java field name. * @param objectSchemaInfo the {@link OsObjectSchemaInfo} for the corresponding {@code RealmObject}. - * @return the index of the column in the table + * @return the index of the column in the table. */ - protected final long addColumnDetails(String columnName, OsObjectSchemaInfo objectSchemaInfo) { - Property property = objectSchemaInfo.getProperty(columnName); - indicesMap.put(columnName, new ColumnDetails(property)); + protected final long addColumnDetails(String javaFieldName, String internalColumnName, OsObjectSchemaInfo objectSchemaInfo) { + Property property = objectSchemaInfo.getProperty(internalColumnName); + ColumnDetails cd = new ColumnDetails(property); + indicesFromJavaFieldNames.put(javaFieldName, cd); + indicesFromColumnNames.put(internalColumnName, cd); return property.getColumnIndex(); } @@ -228,13 +247,13 @@ protected final long addColumnDetails(String columnName, OsObjectSchemaInfo obje * Must be called from within the subclass constructor, to maintain the effectively-final contract. * * @param schemaInfo the {@link OsSchemaInfo} of the corresponding {@code Realm} instance. - * @param columnName The name of the backlink column. + * @param javaFieldName The name of the backlink column. * @param sourceTableName The name of the backlink source class. - * @param sourceColumnName The name of the backlink source field. + * @param sourceJavaFieldName The name of the backlink source field. */ - protected final void addBacklinkDetails(OsSchemaInfo schemaInfo, String columnName, String sourceTableName, String sourceColumnName) { - long columnIndex = schemaInfo.getObjectSchemaInfo(sourceTableName).getProperty(sourceColumnName).getColumnIndex(); - indicesMap.put(columnName, new ColumnDetails(columnIndex, RealmFieldType.LINKING_OBJECTS, sourceTableName)); + protected final void addBacklinkDetails(OsSchemaInfo schemaInfo, String javaFieldName, String sourceTableName, String sourceJavaFieldName) { + long columnIndex = schemaInfo.getObjectSchemaInfo(sourceTableName).getProperty(sourceJavaFieldName).getColumnIndex(); + indicesFromJavaFieldNames.put(javaFieldName, new ColumnDetails(columnIndex, RealmFieldType.LINKING_OBJECTS, sourceTableName)); } /** @@ -245,6 +264,6 @@ protected final void addBacklinkDetails(OsSchemaInfo schemaInfo, String columnNa */ @SuppressWarnings("ReturnOfCollectionOrArrayField") public Map getIndicesMap() { - return indicesMap; + return indicesFromJavaFieldNames; } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java index 0849a2e30b..3f6a47336b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/CachedFieldDescriptor.java @@ -1,4 +1,3 @@ -package io.realm.internal.fields; /* * Copyright 2017 Realm Inc. * @@ -14,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package io.realm.internal.fields; import java.util.List; import java.util.Locale; @@ -23,16 +23,19 @@ import io.realm.internal.ColumnInfo; import io.realm.internal.NativeObject; - /** * Parses the passed field description (@see parseFieldDescription(String) and returns the information * necessary for RealmQuery predicates to select the specified records. * Because the values returned by this method will, immediately, be handed to native code, they are - * in coordinated arrays, not a List<ColumnDeatils> + * in coordinated arrays, not a List<ColumnDetails> * There are two kinds of records. If return[1][i] is NativeObject.NULLPTR, return[0][i] contains * the column index for the i-th element in the dotted field description path. * If return[1][i] is *not* NativeObject.NULLPTR, it is a pointer to the source table for a backlink * and return[0][i] is the column index of the source column in that table. + * + * This class only understands how to parse field descriptions consisting of Java field names as + * given in the model classes. If a field is specified using internal column names, like e.g. + * queries done on a {@link io.realm.DynamicRealm} use {@link DynamicFieldDescriptor} instead. */ class CachedFieldDescriptor extends FieldDescriptor { private final SchemaProxy schema; @@ -41,7 +44,7 @@ class CachedFieldDescriptor extends FieldDescriptor { /** * @param schema the associated Realm Schema * @param className the starting Table: where(Table.class) - * @param fieldDescription fieldName or link path to a field name. + * @param fieldDescription fieldName or link path to a field name using field names from Java model classes */ CachedFieldDescriptor(SchemaProxy schema, String className, String fieldDescription, Set validInternalColumnTypes, Set validFinalColumnTypes) { super(fieldDescription, validInternalColumnTypes, validFinalColumnTypes); diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java index 7328073b28..70997e36a5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java @@ -26,6 +26,9 @@ /** * A field descriptor that uses dynamic table lookup. * Use when cache cannot be trusted... + * + * This class only understands how to parse field descriptions consisting of internal column names, + * if a field is specified using Java model class names, use {@link CachedFieldDescriptor} instead. */ class DynamicFieldDescriptor extends FieldDescriptor { private final Table table; @@ -34,7 +37,7 @@ class DynamicFieldDescriptor extends FieldDescriptor { * Build a dynamic field descriptor for the passed field description string. * * @param table the start table. - * @param fieldDescription the field description. + * @param fieldDescription the field description using internal columns. * @param validInternalColumnTypes valid types for the last field in the field description. * @param validFinalColumnTypes valid types for the last field in the field description. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java index 41f0e51aa1..6d3857bb88 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java @@ -97,7 +97,14 @@ public static FieldDescriptor createStandardFieldDescriptor( Table table, String fieldDescription, RealmFieldType... validFinalColumnTypes) { - return createFieldDescriptor(schema, table, fieldDescription, null, new HashSet<>(Arrays.asList(validFinalColumnTypes))); + + return createFieldDescriptor( + schema, + table, + fieldDescription, + null, + new HashSet<>(Arrays.asList(validFinalColumnTypes)) + ); } /** diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index 626041c3cb..137ba6605d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -46,6 +46,7 @@ public class CompositeMediator extends RealmProxyMediator { private final Map, RealmProxyMediator> mediators; + private final Map> internalClassNames = new HashMap<>(); public CompositeMediator(RealmProxyMediator... mediators) { final HashMap, RealmProxyMediator> tempMediators = new HashMap<>(); @@ -53,7 +54,23 @@ public CompositeMediator(RealmProxyMediator... mediators) { if (mediators != null) { for (RealmProxyMediator mediator : mediators) { for (Class realmClass : mediator.getModelClasses()) { + // Verify that the module doesn't contain conflicting definitions for the same + // underlying internal name. Can only happen if we add a module from a library + // and a module from the app at the same time. + String newInternalName = mediator.getSimpleClassName(realmClass); + Class existingClass = internalClassNames.get(newInternalName); + if (existingClass != null && !existingClass.equals(realmClass)) { + throw new IllegalStateException(String.format("It is not allowed for two different " + + "model classes to share the same internal name in Realm. The " + + "classes %s and %s are being included from the modules '%s' and '%s' " + + "and they share the same internal name '%s'.", existingClass, realmClass, + tempMediators.get(existingClass), mediator, + newInternalName)); + } + + // Store mapping between tempMediators.put(realmClass, mediator); + internalClassNames.put(newInternalName, realmClass); } } } @@ -62,8 +79,7 @@ public CompositeMediator(RealmProxyMediator... mediators) { @Override public Map, OsObjectSchemaInfo> getExpectedObjectSchemaInfoMap() { - Map, OsObjectSchemaInfo> infoMap = - new HashMap, OsObjectSchemaInfo>(); + Map, OsObjectSchemaInfo> infoMap = new HashMap<>(); for (RealmProxyMediator mediator : mediators.values()) { infoMap.putAll(mediator.getExpectedObjectSchemaInfoMap()); } diff --git a/version.txt b/version.txt index 932e22fb7f..84980dc3fe 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.4-SNAPSHOT \ No newline at end of file +4.4.0-SNAPSHOT \ No newline at end of file From 116cd8804fae332420fe515ddd906746c804cdaf Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 29 Jan 2018 11:16:11 +0100 Subject: [PATCH 1170/2110] Use correct methods for doing distinct queries. --- .../androidTest/java/io/realm/CustomRealmNameTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java b/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java index a49f7393a3..e4c23cbce0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java @@ -120,7 +120,7 @@ public void typedQueryWithJavaNames() { .equalTo("camelCase", "foo") // Java name in model class .equalTo("parents.PascalCase", 1) // Backlinks also uses java names .sort("mHungarian") // Sorting uses Java names - .distinctValues("customName") // Distinct uses Java names + .distinct("customName") // Distinct uses Java names .findAll(); assertTrue(results.isEmpty()); } @@ -142,7 +142,7 @@ public void typedQueryWithInternalNamesThrows() { // Distinct try { - realm.where(ClassWithPolicy.class).distinctValues(ClassWithPolicy.FIELD_CAMEL_CASE); + realm.where(ClassWithPolicy.class).distinct(ClassWithPolicy.FIELD_CAMEL_CASE); } catch (IllegalArgumentException ignore) { } @@ -156,7 +156,7 @@ public void dynamicQueryWithInternalNames() { RealmResults results = dynamicRealm.where(ClassWithPolicy.CLASS_NAME) .equalTo(ClassWithPolicy.FIELD_CAMEL_CASE, "foo") // Normal queries use internal names .sort(ClassWithPolicy.FIELD_M_HUNGARIAN) // Sorting uses internal names - .distinctValues(ClassWithPolicy.FIELD_CUSTOM_NAME) // Distinct uses internal names + .distinct(ClassWithPolicy.FIELD_CUSTOM_NAME) // Distinct uses internal names .findAll(); assertTrue(results.isEmpty()); } @@ -176,7 +176,7 @@ public void dynamicQueryWithJavaNamesThrows() { // Distinct try { - dynamicRealm.where(ClassWithPolicy.CLASS_NAME).distinctValues("camelCase"); + dynamicRealm.where(ClassWithPolicy.CLASS_NAME).distinct("camelCase"); } catch (IllegalArgumentException ignore) { } } From 84612c3201a23debb94e3618e3d63af8e821f92a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 29 Jan 2018 13:26:16 +0100 Subject: [PATCH 1171/2110] Add support for lists with non-null values in RealmQuery.oneOf (#5723) --- CHANGELOG.md | 7 + .../kotlin/io/realm/KotlinRealmQueryTests.kt | 61 +++++++- .../io/realm/entities/AllPropTypesClass.kt | 9 ++ .../io/realm/kotlin/RealmQueryExtensions.kt | 141 ++++++++++++++++++ 4 files changed, 210 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e2532e149..a43735164d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.3.4 (YYYY-MM-DD) + +## Bug Fixes + +* Added missing `RealmQuery.oneOf()` for Kotlin that accepts non-nullable types (#5717). + + ## 4.3.3 (2018-01-19) ### Internal diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt index a7c9049eb6..19da173b66 100644 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt @@ -45,8 +45,13 @@ class KotlinRealmQueryTests { assertEquals(1, realm.where() - .oneOf(AllPropTypesClass::stringVar.name, arrayOf("test")) + .oneOf(AllPropTypesClass::nullableStringVar.name, arrayOf(null, "test")) .count()) + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::stringVar.name, arrayOf("test")) + .count()) } @Test @@ -58,7 +63,12 @@ class KotlinRealmQueryTests { assertEquals(1, realm.where() - .oneOf(AllPropTypesClass::byteVar.name, arrayOf(3)) + .oneOf(AllPropTypesClass::nullableByteVar.name, arrayOf(null, 3)) + .count()) + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::byteVar.name, arrayOf(3)) .count()) } @@ -71,7 +81,12 @@ class KotlinRealmQueryTests { assertEquals(1, realm.where() - .oneOf(AllPropTypesClass::shortVar.name, arrayOf(3)) + .oneOf(AllPropTypesClass::nullableShortVar.name, arrayOf(null, 3)) + .count()) + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::shortVar.name, arrayOf(3)) .count()) } @@ -84,7 +99,12 @@ class KotlinRealmQueryTests { assertEquals(1, realm.where() - .oneOf(AllPropTypesClass::intVar.name, arrayOf(3)) + .oneOf(AllPropTypesClass::nullableIntVar.name, arrayOf(null, 3)) + .count()) + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::intVar.name, arrayOf(3)) .count()) } @@ -97,7 +117,12 @@ class KotlinRealmQueryTests { assertEquals(1, realm.where() - .oneOf(AllPropTypesClass::longVar.name, arrayOf(3)) + .oneOf(AllPropTypesClass::nullableLongVar.name, arrayOf(null, 3)) + .count()) + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::longVar.name, arrayOf(3)) .count()) } @@ -110,7 +135,12 @@ class KotlinRealmQueryTests { assertEquals(1, realm.where() - .oneOf(AllPropTypesClass::doubleVar.name, arrayOf(3.5)) + .oneOf(AllPropTypesClass::nullableDoubleVar.name, arrayOf(null, 3.5)) + .count()) + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::doubleVar.name, arrayOf(3.5)) .count()) } @@ -123,7 +153,12 @@ class KotlinRealmQueryTests { assertEquals(1, realm.where() - .oneOf(AllPropTypesClass::floatVar.name, arrayOf(3.5f)) + .oneOf(AllPropTypesClass::nullableFloatVar.name, arrayOf(null, 3.5f)) + .count()) + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::floatVar.name, arrayOf(3.5f)) .count()) } @@ -134,6 +169,11 @@ class KotlinRealmQueryTests { obj.booleanVar = true realm.commitTransaction() + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::nullableBooleanVar.name, arrayOf(null, true)) + .count()) + assertEquals(1, realm.where() .oneOf(AllPropTypesClass::booleanVar.name, arrayOf(true)) @@ -152,7 +192,12 @@ class KotlinRealmQueryTests { assertEquals(1, realm.where() - .oneOf(AllPropTypesClass::dateVar.name, arrayOf(testDate)) + .oneOf(AllPropTypesClass::nullableDateVar.name, arrayOf(null, testDate)) + .count()) + + assertEquals(1, + realm.where() + .oneOf(AllPropTypesClass::dateVar.name, arrayOf(testDate)) .count()) } diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/AllPropTypesClass.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/AllPropTypesClass.kt index 35202fc7da..4870b0a3c3 100644 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/AllPropTypesClass.kt +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/entities/AllPropTypesClass.kt @@ -17,4 +17,13 @@ open class AllPropTypesClass : RealmModel { var booleanVar : Boolean = false var dateVar : Date = Date() + var nullableStringVar: String? = null + var nullableByteVar: Byte? = null + var nullableShortVar: Short? = null + var nullableIntVar: Int? = null + var nullableLongVar: Long? = null + var nullableDoubleVar: Double? = null + var nullableFloatVar: Float? = null + var nullableBooleanVar : Boolean? = null + var nullableDateVar : Date? = null } diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt index e9f2c1a4a8..b223597f85 100644 --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt @@ -151,3 +151,144 @@ fun RealmQuery.oneOf(propertyName: String, value: Array): RealmQuery { return this.`in`(propertyName, value) } + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @param casing how casing is handled. [Case.INSENSITIVE] works only for the Latin-1 characters. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a String field or `values` is `null` or + * empty. + */ +@JvmName("nonNullOneOf") +fun RealmQuery.oneOf(propertyName: String, + value: Array, + casing: Case = Case.SENSITIVE): RealmQuery { + return this.`in`(propertyName, value, casing) +} + + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Byte field or `values` is `null` or + * empty. + */ +@JvmName("nonNullOneOf") +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Short field or `values` is `null` or + * empty. + */ +@JvmName("nonNullOneOf") +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Integer field or `values` is `null` + * or empty. + */ +@JvmName("nonNullOneOf") +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Long field or `values` is `null` or + * empty. + */ +@JvmName("nonNullOneOf") +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Double field or `values` is `null` or + * empty. + */ +@JvmName("nonNullOneOf") +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Float field or `values` is `null` or + * empty. + */ +@JvmName("nonNullOneOf") +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Boolean field or `values` is `null` + * or empty. + */ +@JvmName("nonNullOneOf") +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + +/** + * In comparison. This allows you to test if objects match any value in an array of values. + * + * @param fieldName the field to compare. + * @param values array of values to compare with and it cannot be null or empty. + * @return the query object. + * @throws java.lang.IllegalArgumentException if the field isn't a Date field or `values` is `null` or + * empty. + */ +@JvmName("nonNullOneOf") +fun RealmQuery.oneOf(propertyName: String, + value: Array): RealmQuery { + return this.`in`(propertyName, value) +} + From 58e5ae6faf0df2f8cb62c279688dd0fdf666d6ef Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 4 Feb 2018 11:02:45 +0100 Subject: [PATCH 1172/2110] Correctly support multiple classes with the same name (#5737) --- .../realm/transformer/BytecodeModifier.groovy | 2 +- realm/config/findbugs/findbugs-filter.xml | 8 +- .../java/io/realm/processor/Backlink.java | 38 +- .../io/realm/processor/ClassCollection.java | 12 +- .../io/realm/processor/ClassMetaData.java | 12 + .../processor/RealmProxyClassGenerator.java | 23 +- .../RealmProxyInterfaceGenerator.java | 2 +- .../RealmProxyMediatorGenerator.java | 7 +- .../main/java/io/realm/processor/Utils.java | 57 +-- .../processor/RealmBacklinkProcessorTest.java | 73 ++-- .../io/realm/processor/RealmNameTest.java | 4 +- .../realm/processor/RealmProcessorTest.java | 8 +- .../io/realm/RealmDefaultModuleMediator.java | 26 +- ...java => some_test_AllTypesRealmProxy.java} | 238 ++++++------ ...java => some_test_BooleansRealmProxy.java} | 52 +-- ...mePolicyMixedClassSettingsRealmProxy.java} | 36 +- ...t_NamePolicyModuleDefaultsRealmProxy.java} | 36 +- ...ava => some_test_NullTypesRealmProxy.java} | 364 +++++++++--------- ...y.java => some_test_SimpleRealmProxy.java} | 34 +- .../some/test/BacklinkSelfReference.java | 15 + .../resources/some/test/BacklinkSource.java | 10 + .../resources/some/test/BacklinkTarget.java | 11 +- .../test/resources/some/test/Backlinks.java | 16 - .../test/conflict/BacklinkSelfReference.java | 12 + .../java/io/realm/ColumnInfoTests.java | 20 +- .../io/realm/RealmProxyMediatorTests.java | 6 +- .../androidTest/java/io/realm/RealmTests.java | 4 +- .../realm/entities/conflict/AllJavaTypes.java | 26 ++ .../io/realm/internal/OsObjectSchemaInfo.java | 10 +- 29 files changed, 591 insertions(+), 571 deletions(-) rename realm/realm-annotations-processor/src/test/resources/io/realm/{AllTypesRealmProxy.java => some_test_AllTypesRealmProxy.java} (88%) rename realm/realm-annotations-processor/src/test/resources/io/realm/{BooleansRealmProxy.java => some_test_BooleansRealmProxy.java} (89%) rename realm/realm-annotations-processor/src/test/resources/io/realm/{NamePolicyMixedClassSettingsRealmProxy.java => some_test_NamePolicyMixedClassSettingsRealmProxy.java} (89%) rename realm/realm-annotations-processor/src/test/resources/io/realm/{NamePolicyModuleDefaultsRealmProxy.java => some_test_NamePolicyModuleDefaultsRealmProxy.java} (89%) rename realm/realm-annotations-processor/src/test/resources/io/realm/{NullTypesRealmProxy.java => some_test_NullTypesRealmProxy.java} (90%) rename realm/realm-annotations-processor/src/test/resources/io/realm/{SimpleRealmProxy.java => some_test_SimpleRealmProxy.java} (90%) create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/BacklinkSelfReference.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/BacklinkSource.java delete mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/Backlinks.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/conflict/BacklinkSelfReference.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/conflict/AllJavaTypes.java diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy index 2ad7183dd2..c00055369d 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy @@ -85,7 +85,7 @@ class BytecodeModifier { * @param classPool the Javassist class pool */ public static void addRealmProxyInterface(CtClass clazz, ClassPool classPool) { - def proxyInterface = classPool.get("io.realm.${clazz.getSimpleName()}RealmProxyInterface") + def proxyInterface = classPool.get("io.realm.${clazz.getName().replace(".", "_")}RealmProxyInterface") clazz.addInterface(proxyInterface) } diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index 25485943d5..279dd332d8 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -13,16 +13,16 @@ - + - + - + - + diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java index e658f157a8..e158871457 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java @@ -32,7 +32,7 @@ * Backlinks are automatically created and destroyed when the forward references to which they correspond are * created and destroyed. This can dramatically reduce the complexity of client code. *

            - * To expose backinks for use, create a declaration as follows: + * To expose backlinks for use, create a declaration as follows: * * class TargetClass { * // ... @@ -58,11 +58,11 @@ * the field is initialized (typically null). */ final class Backlink { - private final VariableElement backlink; + private final VariableElement backlinkField; /** * The fully-qualified name of the class containing the targetField, - * the field annotated with the {@literal @}LinkingObjects annotation. + * which is the field annotated with the {@literal @}LinkingObjects annotation. */ private final String targetClass; @@ -74,12 +74,12 @@ final class Backlink { /** * The fully-qualified name of the class to which the backlinks, from targetField, - * point: The generic argument to the type of the targetField. + * point. */ private final String sourceClass; /** - * The name of the field, in SourceClass that creates the backlink. + * The name of the field, in SourceClass that has a normal link to targetClass. * Making this field, in an instance I of SourceClass, * a reference to an instance J of TargetClass * will cause the targetField of J to contain a backlink to I. @@ -87,16 +87,16 @@ final class Backlink { private final String sourceField; - public Backlink(ClassMetaData clazz, VariableElement backlink) { - if ((null == clazz) || (null == backlink)) { - throw new NullPointerException(String.format(Locale.US, "null parameter: %s, %s", clazz, backlink)); + public Backlink(ClassMetaData clazz, VariableElement backlinkField) { + if ((null == clazz) || (null == backlinkField)) { + throw new NullPointerException(String.format(Locale.US, "null parameter: %s, %s", clazz, backlinkField)); } - this.backlink = backlink; + this.backlinkField = backlinkField; this.targetClass = clazz.getFullyQualifiedClassName(); - this.targetField = backlink.getSimpleName().toString(); - this.sourceClass = Utils.getRealmResultsType(backlink); - this.sourceField = backlink.getAnnotation(LinkingObjects.class).value(); + this.targetField = backlinkField.getSimpleName().toString(); + this.sourceClass = Utils.getRealmResultsType(backlinkField); + this.sourceField = backlinkField.getAnnotation(LinkingObjects.class).value(); } public String getTargetClass() { @@ -116,11 +116,7 @@ public String getSourceField() { } public String getTargetFieldType() { - return backlink.asType().toString(); - } - - public String getSimpleSourceClass() { - return Utils.getFieldTypeSimpleName(Utils.getGenericTypeForContainer(backlink)); + return backlinkField.asType().toString(); } /** @@ -130,7 +126,7 @@ public String getSimpleSourceClass() { */ public boolean validateSource() { // A @LinkingObjects cannot be @Required - if (backlink.getAnnotation(Required.class) != null) { + if (backlinkField.getAnnotation(Required.class) != null) { Utils.error(String.format( Locale.US, "The @LinkingObjects field \"%s.%s\" cannot be @Required.", @@ -160,13 +156,13 @@ public boolean validateSource() { } // The annotated element must be a RealmResult - if (!Utils.isRealmResults(backlink)) { + if (!Utils.isRealmResults(backlinkField)) { Utils.error(String.format( Locale.US, "The field \"%s.%s\" is a \"%s\". Fields annotated with @LinkingObjects must be RealmResults.", targetClass, targetField, - backlink.asType())); + backlinkField.asType())); return false; } @@ -180,7 +176,7 @@ public boolean validateSource() { } // A @LinkingObjects field must be final - if (!backlink.getModifiers().contains(Modifier.FINAL)) { + if (!backlinkField.getModifiers().contains(Modifier.FINAL)) { Utils.error(String.format( Locale.US, "A @LinkingObjects field \"%s.%s\" must be final.", diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java index c500785d67..095314f1b9 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java @@ -30,13 +30,11 @@ public class ClassCollection { // These three collections should always stay in sync - private Map simpleNameClassMap = new LinkedHashMap<>(); private Map qualifiedNameClassMap = new LinkedHashMap<>(); private Set classSet = new LinkedHashSet<>(); public void addClass(ClassMetaData metadata) { classSet.add(metadata); - simpleNameClassMap.put(metadata.getSimpleJavaClassName(), metadata); qualifiedNameClassMap.put(metadata.getFullyQualifiedClassName(), metadata); } @@ -44,18 +42,10 @@ public Set getClasses() { return Collections.unmodifiableSet(classSet); } - public ClassMetaData getClassFromSimpleName(String simpleJavaClassName) { - ClassMetaData data = simpleNameClassMap.get(simpleJavaClassName); - if (data == null) { - throw new NullPointerException("Class " + simpleJavaClassName + " was not found"); - } - return data; - } - public ClassMetaData getClassFromQualifiedName(String qualifiedJavaClassName) { ClassMetaData data = qualifiedNameClassMap.get(qualifiedJavaClassName); if (data == null) { - throw new NullPointerException("Class " + qualifiedJavaClassName + " was not found"); + throw new IllegalArgumentException("Class " + qualifiedJavaClassName + " was not found"); } return data; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index 8fc5378619..8292873163 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -143,6 +143,18 @@ public String getInternalClassName() { return internalClassName; } + /** + * Returns the internal field name that matches the one in the Java model class. + */ + public String getInternalFieldName(String javaFieldName) { + for (RealmFieldElement field : fields) { + if (field.getJavaName().equals(javaFieldName)) { + return field.getInternalFieldName(); + } + } + throw new IllegalArgumentException("Could not find fieldname: " + javaFieldName); + } + public String getPackageName() { return packageName; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 1a002e258d..7f154623c4 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -94,9 +94,9 @@ public RealmProxyClassGenerator(ProcessingEnvironment processingEnvironment, Typ this.simpleJavaClassName = metadata.getSimpleJavaClassName(); this.qualifiedJavaClassName = metadata.getFullyQualifiedClassName(); this.internalClassName = metadata.getInternalClassName(); - this.interfaceName = Utils.getProxyInterfaceName(simpleJavaClassName); + this.interfaceName = Utils.getProxyInterfaceName(qualifiedJavaClassName); this.qualifiedGeneratedClassName = String.format(Locale.US, "%s.%s", - Constants.REALM_PACKAGE_NAME, Utils.getProxyClassName(simpleJavaClassName)); + Constants.REALM_PACKAGE_NAME, Utils.getProxyClassName(qualifiedJavaClassName)); // See the configuration for the debug build type, // in the realm-library project, for an example of how to set this flag. @@ -744,15 +744,15 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce break; } case OBJECT: { - String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); - String internalClassName = classCollection.getClassFromSimpleName(fieldTypeSimpleName).getInternalClassName(); + String fieldTypeQualifiedName = Utils.getFieldTypeQualifiedName(field); + String internalClassName = classCollection.getClassFromQualifiedName(fieldTypeQualifiedName).getInternalClassName(); writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.OBJECT, \"%s\")", fieldName, internalClassName); break; } case LIST: { - String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); - String internalClassName = classCollection.getClassFromSimpleName(genericTypeSimpleName).getInternalClassName(); // FIXME support for raw data + String genericTypeQualifiedName = Utils.getGenericTypeQualifiedName(field); + String internalClassName = classCollection.getClassFromQualifiedName(genericTypeQualifiedName).getInternalClassName(); // FIXME support for raw data writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.LIST, \"%s\")", fieldName, internalClassName); break; @@ -795,8 +795,11 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce } } for (Backlink backlink: metadata.getBacklinkFields()) { + ClassMetaData sourceClass = classCollection.getClassFromQualifiedName(backlink.getSourceClass()); + String targetField = backlink.getTargetField(); // Only in the model, so no internal name exists + String internalSourceField = sourceClass.getInternalFieldName(backlink.getSourceField()); writer.emitStatement("builder.addComputedLinkProperty(\"%s\", \"%s\", \"%s\")", - backlink.getTargetField(), classCollection.getClassFromSimpleName(backlink.getSimpleSourceClass()).getInternalClassName(), backlink.getSourceField()); + targetField, sourceClass.getInternalClassName(), internalSourceField); } writer.emitStatement("return builder.build()"); writer.endMethod() @@ -1801,14 +1804,14 @@ private void emitToStringMethod(JavaWriter writer) throws IOException { writer.emitStatement("stringBuilder.append(\"{%s:\")", fieldName); if (Utils.isRealmModel(field)) { - String fieldTypeSimpleName = Utils.getFieldTypeSimpleName(field); + String fieldTypeSimpleName = Utils.stripPackage(Utils.getFieldTypeQualifiedName(field)); writer.emitStatement( "stringBuilder.append(%s() != null ? \"%s\" : \"null\")", metadata.getInternalGetter(fieldName), fieldTypeSimpleName ); } else if (Utils.isRealmList(field)) { - String genericTypeSimpleName = Utils.getGenericTypeSimpleName(field); + String genericTypeSimpleName = Utils.stripPackage(Utils.getGenericTypeQualifiedName(field)); writer.emitStatement("stringBuilder.append(\"RealmList<%s>[\").append(%s().size()).append(\"]\")", genericTypeSimpleName, metadata.getInternalGetter(fieldName)); @@ -1868,7 +1871,7 @@ private void emitEqualsMethod(JavaWriter writer) throws IOException { if (metadata.containsEquals()) { return; } - String proxyClassName = Utils.getProxyClassName(simpleJavaClassName); + String proxyClassName = Utils.getProxyClassName(qualifiedJavaClassName); String otherObjectVarName = "a" + simpleJavaClassName; writer.emitAnnotation("Override") .beginMethod("boolean", "equals", EnumSet.of(Modifier.PUBLIC), "Object", "o") diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java index 25cbe13a41..94e3727902 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java @@ -38,7 +38,7 @@ public class RealmProxyInterfaceGenerator { public RealmProxyInterfaceGenerator(ProcessingEnvironment processingEnvironment, ClassMetaData metaData) { this.processingEnvironment = processingEnvironment; this.metaData = metaData; - this.className = metaData.getSimpleJavaClassName(); + this.className = metaData.getFullyQualifiedClassName(); } public void generate() throws IOException { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index 5081c513bf..d058617c1e 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -51,9 +51,8 @@ public RealmProxyMediatorGenerator(ProcessingEnvironment processingEnvironment, this.className = className; for (ClassMetaData metadata : classesToValidate) { - String simpleName = metadata.getSimpleJavaClassName(); qualifiedModelClasses.add(metadata.getFullyQualifiedClassName()); - qualifiedProxyClasses.add(REALM_PACKAGE_NAME + "." + getProxyClassName(simpleName)); + qualifiedProxyClasses.add(REALM_PACKAGE_NAME + "." + Utils.getProxyClassName(metadata.getFullyQualifiedClassName())); internalClassNames.add(metadata.getInternalClassName()); } } @@ -475,10 +474,6 @@ private void emitMediatorShortCircuitSwitch(ProxySwitchStatement statement, Java } - private String getProxyClassName(String clazz) { - return clazz + Constants.PROXY_SUFFIX; - } - private interface ProxySwitchStatement { void emitStatement(int i, JavaWriter writer) throws IOException; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java index 5884c70818..9b0e4309e0 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java @@ -63,17 +63,17 @@ public static boolean isDefaultConstructor(Element constructor) { public static String getProxyClassSimpleName(VariableElement field) { if (typeUtils.isAssignable(field.asType(), realmList)) { - return getProxyClassName(getGenericTypeSimpleName(field)); + return getProxyClassName(getGenericTypeQualifiedName(field)); } else { - return getProxyClassName(getFieldTypeSimpleName(field)); + return getProxyClassName(getFieldTypeQualifiedName(field)); } } /** * @return the proxy class name for a given clazz */ - public static String getProxyClassName(String clazz) { - return clazz + Constants.PROXY_SUFFIX; + public static String getProxyClassName(String qualifiedClassName) { + return qualifiedClassName.replace(".", "_") + Constants.PROXY_SUFFIX; } /** @@ -84,7 +84,7 @@ public static boolean isString(VariableElement field) { if (field == null) { throw new IllegalArgumentException("Argument 'field' cannot be null."); } - return getFieldTypeSimpleName(field).equals("String"); + return getFieldTypeQualifiedName(field).equals("java.lang.String"); } /** @@ -133,7 +133,7 @@ public static boolean isByteArray(VariableElement field) { if (field == null) { throw new IllegalArgumentException("Argument 'field' cannot be null."); } - return getFieldTypeSimpleName(field).equals("byte[]"); + return getFieldTypeQualifiedName(field).equals("byte[]"); } /** @@ -279,30 +279,6 @@ public static String getFieldTypeQualifiedName(VariableElement field) { return field.asType().toString(); } - /** - * @return the simple type name for a field. - */ - public static String getFieldTypeSimpleName(VariableElement field) { - return (null == field) ? null : getFieldTypeSimpleName(getFieldTypeQualifiedName(field)); - } - - /** - * @return the simple type name for a field. - */ - public static String getFieldTypeSimpleName(ReferenceType type) { - return (null == type) ? null : getFieldTypeSimpleName(type.toString()); - } - - /** - * @return the simple type name for a field. - */ - public static String getFieldTypeSimpleName(String fieldTypeQualifiedName) { - if ((null != fieldTypeQualifiedName) && (fieldTypeQualifiedName.contains("."))) { - fieldTypeQualifiedName = fieldTypeQualifiedName.substring(fieldTypeQualifiedName.lastIndexOf('.') + 1); - } - return fieldTypeQualifiedName; - } - /** * @return the generic type for Lists of the form {@code List} */ @@ -315,20 +291,6 @@ public static String getGenericTypeQualifiedName(VariableElement field) { return typeArguments.get(0).toString(); } - /** - * @return the generic type for Lists of the form {@code List} - */ - public static String getGenericTypeSimpleName(VariableElement field) { - final String genericTypeName = getGenericTypeQualifiedName(field); - if (genericTypeName == null) { - return null; - } - if (!genericTypeName.contains(".")) { - return genericTypeName; - } - return genericTypeName.substring(genericTypeName.lastIndexOf('.') + 1); - } - /** * Strips the package name from a fully qualified class name. */ @@ -371,8 +333,11 @@ public static Element getSuperClass(TypeElement classType) { return typeUtils.asElement(classType.getSuperclass()); } - public static String getProxyInterfaceName(String className) { - return className + Constants.INTERFACE_SUFFIX; + /** + * Returns the interface name for proxy class interfaces + */ + public static String getProxyInterfaceName(String qualifiedClassName) { + return qualifiedClassName.replace(".", "_") + Constants.INTERFACE_SUFFIX; } public static NameConverter getNameFormatter(RealmNamingPolicy policy) { diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java index 46448a3a0c..38929fba51 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmBacklinkProcessorTest.java @@ -31,36 +31,34 @@ public class RealmBacklinkProcessorTest { - private final JavaFileObject backlinks = JavaFileObjects.forResource("some/test/Backlinks.java"); - private final JavaFileObject backlinksTarget = JavaFileObjects.forResource("some/test/BacklinkTarget.java"); + private final JavaFileObject sourceClass = JavaFileObjects.forResource("some/test/BacklinkSource.java"); + private final JavaFileObject targetClass = JavaFileObjects.forResource("some/test/BacklinkTarget.java"); private final JavaFileObject invalidResultsValueType = JavaFileObjects.forResource("some/test/InvalidResultsElementType.java"); @Test public void compileBacklinks() { ASSERT.about(javaSources()) - .that(Arrays.asList(backlinks, backlinksTarget)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .compilesWithoutError(); } @Test public void compileSyntheticBacklinks() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() - .builder().build(); + RealmSyntheticTestClass targetClass = createBacklinkTestClass().builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .compilesWithoutError(); } @Test public void failOnLinkingObjectsWithInvalidFieldType() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() - // Backlinks must be RealmResults + RealmSyntheticTestClass targetClass = createBacklinkTestClass() .type("BacklinkTarget") .builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .failsToCompile() .withErrorContaining("Fields annotated with @LinkingObjects must be RealmResults"); @@ -68,12 +66,12 @@ public void failOnLinkingObjectsWithInvalidFieldType() throws IOException { @Test public void failOnLinkingObjectsWithNonFinalField() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() + RealmSyntheticTestClass targetClass = createBacklinkTestClass() // A field with a @LinkingObjects annotation must be final .modifiers(Modifier.PUBLIC) .builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .failsToCompile() .withErrorContaining("must be final"); @@ -81,14 +79,14 @@ public void failOnLinkingObjectsWithNonFinalField() throws IOException { @Test public void failsOnLinkingObjectsWithLinkedFields() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() + RealmSyntheticTestClass targetClass = createBacklinkTestClass() // Defining a backlink more than one levels back is not supported. // It can be queried though: `equalTo("selectedFieldParents.selectedFieldParents") .clearAnnotations() .annotation("LinkingObjects(\"child.id\")") .builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .failsToCompile() .withErrorContaining("The use of '.' to specify fields in referenced classes is not supported"); @@ -96,13 +94,13 @@ public void failsOnLinkingObjectsWithLinkedFields() throws IOException { @Test public void failsOnLinkingObjectsMissingFieldName() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() + RealmSyntheticTestClass targetClass = createBacklinkTestClass() // No backlinked field specified .clearAnnotations() .annotation("LinkingObjects") .builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .failsToCompile() .withErrorContaining("must have a parameter identifying the link target"); @@ -110,12 +108,12 @@ public void failsOnLinkingObjectsMissingFieldName() throws IOException { @Test public void failsOnLinkingObjectsMissingGeneric() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() + RealmSyntheticTestClass targetClass = createBacklinkTestClass() // No backlink generic param specified .type("RealmResults") .builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .failsToCompile() .withErrorContaining("must specify a generic type"); @@ -123,12 +121,12 @@ public void failsOnLinkingObjectsMissingGeneric() throws IOException { @Test public void failsOnLinkingObjectsWithRequiredFields() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() + RealmSyntheticTestClass targetClass = createBacklinkTestClass() // A backlinked field may not be @Required .annotation("Required") .builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .failsToCompile() .withErrorContaining("The @LinkingObjects field "); @@ -136,12 +134,12 @@ public void failsOnLinkingObjectsWithRequiredFields() throws IOException { @Test public void failsOnLinkingObjectsWithIgnoreFields() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() + RealmSyntheticTestClass targetClass = createBacklinkTestClass() // An @Ignored, backlinked field is completely ignored .annotation("Ignore") .builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .compilesWithoutError(); } @@ -149,27 +147,27 @@ public void failsOnLinkingObjectsWithIgnoreFields() throws IOException { // TODO: This seems like a "gottcha". We should warn. @Test public void ignoreStaticLinkingObjects() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() + RealmSyntheticTestClass targetClass = createBacklinkTestClass() .modifiers(Modifier.PUBLIC, Modifier.STATIC) .type("RealmResults") .clearAnnotations() .annotation("LinkingObjects(\"xxx\")") .builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .compilesWithoutError(); } @Test public void failsOnLinkingObjectsFieldNotFound() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() - // The argument to the @LinkingObjects annotation must name a field in the target class + RealmSyntheticTestClass targetClass = createBacklinkTestClass() + // The argument to the @LinkingObjects annotation must name a field in the source class .clearAnnotations() .annotation("LinkingObjects(\"xxx\")") .builder().build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .failsToCompile() .withErrorContaining("does not exist in class"); @@ -177,13 +175,13 @@ public void failsOnLinkingObjectsFieldNotFound() throws IOException { @Test public void failsOnLinkingObjectsWithFieldWrongType() throws IOException { - RealmSyntheticTestClass javaFileObject = createBacklinkTestClass() + RealmSyntheticTestClass targetClass = createBacklinkTestClass() // The type of the field named in the @LinkingObjects annotation must match - // the generic type of the annotated field. BacklinkTarget.child is a Backlink, + // the generic type of the annotated field. BacklinkSource.child is a Backlink, // not a Backlinks_WrongType. - .builder().name("Backlinks_WrongType").build(); + .builder().name("BacklinkTarget_WrongType").build(); ASSERT.about(javaSources()) - .that(Arrays.asList(backlinksTarget, javaFileObject)) + .that(Arrays.asList(sourceClass, targetClass)) .processedWith(new RealmProcessor()) .failsToCompile() .withErrorContaining("instead of"); @@ -193,11 +191,11 @@ public void failsOnLinkingObjectsWithFieldWrongType() throws IOException { // It returns the ref to the backlinked Field. Tests can modify the // field in perverse ways, to verify failure modes. private RealmSyntheticTestClass.Field createBacklinkTestClass() { - return new RealmSyntheticTestClass.Builder().name("Backlinks") + return new RealmSyntheticTestClass.Builder().name("BacklinkTarget") .field().name("id").type("int").builder() .field() .name("parents") - .type("RealmResults") + .type("RealmResults") .modifiers(Modifier.PUBLIC, Modifier.FINAL) .annotation("LinkingObjects(\"child\")") .initializer("null") @@ -212,4 +210,15 @@ public void failToCompileInvalidResultsElementType() { .processedWith(new RealmProcessor()) .failsToCompile(); } + + @Test + public void compileBacklinkClassesWithSimpleNameConflicts() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/BacklinkSelfReference.java"), + JavaFileObjects.forResource("some/test/conflict/BacklinkSelfReference.java") + )) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } } diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmNameTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmNameTest.java index 033cae1101..b8f7aa3677 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmNameTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmNameTest.java @@ -53,7 +53,7 @@ public void compareProcessedNamingPolicyClassFile() { .processedWith(new RealmProcessor()) .compilesWithoutError() .and() - .generatesSources(JavaFileObjects.forResource("io/realm/NamePolicyMixedClassSettingsRealmProxy.java")); + .generatesSources(JavaFileObjects.forResource("io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java")); } // Check the effect of module default on a class with no settings itself @@ -67,7 +67,7 @@ public void compareProcessedDefaultClassFile() { .processedWith(new RealmProcessor()) .compilesWithoutError() .and() - .generatesSources(JavaFileObjects.forResource("io/realm/NamePolicyModuleDefaultsRealmProxy.java")); + .generatesSources(JavaFileObjects.forResource("io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java")); } // Check that trying to compile two modules with different policies using `allClasses = true` will fail. diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java index ec043729e6..511adf03e1 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java @@ -33,13 +33,13 @@ public class RealmProcessorTest { private final JavaFileObject simpleModel = JavaFileObjects.forResource("some/test/Simple.java"); - private final JavaFileObject simpleProxy = JavaFileObjects.forResource("io/realm/SimpleRealmProxy.java"); + private final JavaFileObject simpleProxy = JavaFileObjects.forResource("io/realm/some_test_SimpleRealmProxy.java"); private final JavaFileObject allTypesModel = JavaFileObjects.forResource("some/test/AllTypes.java"); - private final JavaFileObject allTypesProxy = JavaFileObjects.forResource("io/realm/AllTypesRealmProxy.java"); + private final JavaFileObject allTypesProxy = JavaFileObjects.forResource("io/realm/some_test_AllTypesRealmProxy.java"); private final JavaFileObject allTypesDefaultModule = JavaFileObjects.forResource("io/realm/RealmDefaultModule.java"); private final JavaFileObject allTypesDefaultMediator = JavaFileObjects.forResource("io/realm/RealmDefaultModuleMediator.java"); private final JavaFileObject booleansModel = JavaFileObjects.forResource("some/test/Booleans.java"); - private final JavaFileObject booleansProxy = JavaFileObjects.forResource("io/realm/BooleansRealmProxy.java"); + private final JavaFileObject booleansProxy = JavaFileObjects.forResource("io/realm/some_test_BooleansRealmProxy.java"); private final JavaFileObject emptyModel = JavaFileObjects.forResource("some/test/Empty.java"); private final JavaFileObject finalModel = JavaFileObjects.forResource("some/test/Final.java"); private final JavaFileObject transientModel = JavaFileObjects.forResource("some/test/Transient.java"); @@ -47,7 +47,7 @@ public class RealmProcessorTest { private final JavaFileObject fieldNamesModel = JavaFileObjects.forResource("some/test/FieldNames.java"); private final JavaFileObject customAccessorModel = JavaFileObjects.forResource("some/test/CustomAccessor.java"); private final JavaFileObject nullTypesModel = JavaFileObjects.forResource("some/test/NullTypes.java"); - private final JavaFileObject nullTypesProxy = JavaFileObjects.forResource("io/realm/NullTypesRealmProxy.java"); + private final JavaFileObject nullTypesProxy = JavaFileObjects.forResource("io/realm/some_test_NullTypesRealmProxy.java"); private final JavaFileObject missingGenericTypeModel = JavaFileObjects.forResource("some/test/MissingGenericType.java"); private final JavaFileObject conflictingFieldNameModel = JavaFileObjects.forResource("some/test/ConflictingFieldName.java"); private final JavaFileObject invalidRealmModelModel_1 = JavaFileObjects.forResource("some/test/InvalidModelRealmModel_1.java"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index efb8bd8dab..0983dd6b14 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -33,7 +33,7 @@ class DefaultRealmModuleMediator extends RealmProxyMediator { @Override public Map, OsObjectSchemaInfo> getExpectedObjectSchemaInfoMap() { Map, OsObjectSchemaInfo> infoMap = new HashMap, OsObjectSchemaInfo>(1); - infoMap.put(some.test.AllTypes.class, io.realm.AllTypesRealmProxy.getExpectedObjectSchemaInfo()); + infoMap.put(some.test.AllTypes.class, io.realm.some_test_AllTypesRealmProxy.getExpectedObjectSchemaInfo()); return infoMap; } @@ -42,7 +42,7 @@ public ColumnInfo createColumnInfo(Class clazz, OsSchemaIn checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { - return io.realm.AllTypesRealmProxy.createColumnInfo(schemaInfo); + return io.realm.some_test_AllTypesRealmProxy.createColumnInfo(schemaInfo); } throw getMissingProxyClassException(clazz); } @@ -65,7 +65,7 @@ public E newInstance(Class clazz, Object baseRealm, Ro checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { - return clazz.cast(new io.realm.AllTypesRealmProxy()); + return clazz.cast(new io.realm.some_test_AllTypesRealmProxy()); } throw getMissingProxyClassException(clazz); } finally { @@ -85,7 +85,7 @@ public E copyOrUpdate(Realm realm, E obj, boolean update, @SuppressWarnings("unchecked") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass()); if (clazz.equals(some.test.AllTypes.class)) { - return clazz.cast(io.realm.AllTypesRealmProxy.copyOrUpdate(realm, (some.test.AllTypes) obj, update, cache)); + return clazz.cast(io.realm.some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some.test.AllTypes) obj, update, cache)); } throw getMissingProxyClassException(clazz); } @@ -97,7 +97,7 @@ public void insert(Realm realm, RealmModel object, Map cache) @SuppressWarnings("unchecked") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass()); if (clazz.equals(some.test.AllTypes.class)) { - io.realm.AllTypesRealmProxy.insert(realm, (some.test.AllTypes) object, cache); + io.realm.some_test_AllTypesRealmProxy.insert(realm, (some.test.AllTypes) object, cache); } else { throw getMissingProxyClassException(clazz); } @@ -116,13 +116,13 @@ public void insert(Realm realm, Collection objects) { @SuppressWarnings("unchecked") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass()); if (clazz.equals(some.test.AllTypes.class)) { - io.realm.AllTypesRealmProxy.insert(realm, (some.test.AllTypes) object, cache); + io.realm.some_test_AllTypesRealmProxy.insert(realm, (some.test.AllTypes) object, cache); } else { throw getMissingProxyClassException(clazz); } if (iterator.hasNext()) { if (clazz.equals(some.test.AllTypes.class)) { - io.realm.AllTypesRealmProxy.insert(realm, iterator, cache); + io.realm.some_test_AllTypesRealmProxy.insert(realm, iterator, cache); } else { throw getMissingProxyClassException(clazz); } @@ -137,7 +137,7 @@ public void insertOrUpdate(Realm realm, RealmModel obj, Map ca @SuppressWarnings("unchecked") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass()); if (clazz.equals(some.test.AllTypes.class)) { - io.realm.AllTypesRealmProxy.insertOrUpdate(realm, (some.test.AllTypes) obj, cache); + io.realm.some_test_AllTypesRealmProxy.insertOrUpdate(realm, (some.test.AllTypes) obj, cache); } else { throw getMissingProxyClassException(clazz); } @@ -156,13 +156,13 @@ public void insertOrUpdate(Realm realm, Collection objects @SuppressWarnings("unchecked") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass()); if (clazz.equals(some.test.AllTypes.class)) { - io.realm.AllTypesRealmProxy.insertOrUpdate(realm, (some.test.AllTypes) object, cache); + io.realm.some_test_AllTypesRealmProxy.insertOrUpdate(realm, (some.test.AllTypes) object, cache); } else { throw getMissingProxyClassException(clazz); } if (iterator.hasNext()) { if (clazz.equals(some.test.AllTypes.class)) { - io.realm.AllTypesRealmProxy.insertOrUpdate(realm, iterator, cache); + io.realm.some_test_AllTypesRealmProxy.insertOrUpdate(realm, iterator, cache); } else { throw getMissingProxyClassException(clazz); } @@ -176,7 +176,7 @@ public E createOrUpdateUsingJsonObject(Class clazz, Re checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { - return clazz.cast(io.realm.AllTypesRealmProxy.createOrUpdateUsingJsonObject(realm, json, update)); + return clazz.cast(io.realm.some_test_AllTypesRealmProxy.createOrUpdateUsingJsonObject(realm, json, update)); } throw getMissingProxyClassException(clazz); } @@ -187,7 +187,7 @@ public E createUsingJsonStream(Class clazz, Realm real checkClass(clazz); if (clazz.equals(some.test.AllTypes.class)) { - return clazz.cast(io.realm.AllTypesRealmProxy.createUsingJsonStream(realm, reader)); + return clazz.cast(io.realm.some_test_AllTypesRealmProxy.createUsingJsonStream(realm, reader)); } throw getMissingProxyClassException(clazz); } @@ -199,7 +199,7 @@ public E createDetachedCopy(E realmObject, int maxDepth, @SuppressWarnings("unchecked") Class clazz = (Class) realmObject.getClass().getSuperclass(); if (clazz.equals(some.test.AllTypes.class)) { - return clazz.cast(io.realm.AllTypesRealmProxy.createDetachedCopy((some.test.AllTypes) realmObject, 0, maxDepth, cache)); + return clazz.cast(io.realm.some_test_AllTypesRealmProxy.createDetachedCopy((some.test.AllTypes) realmObject, 0, maxDepth, cache)); } throw getMissingProxyClassException(clazz); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java similarity index 88% rename from realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java rename to realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index 4351c458c9..5dcdcd1f45 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -32,8 +32,8 @@ import org.json.JSONObject; @SuppressWarnings("all") -public class AllTypesRealmProxy extends some.test.AllTypes - implements RealmObjectProxy, AllTypesRealmProxyInterface { +public class some_test_AllTypesRealmProxy extends some.test.AllTypes + implements RealmObjectProxy, some_test_AllTypesRealmProxyInterface { static final class AllTypesColumnInfo extends ColumnInfo { long columnStringIndex; @@ -141,7 +141,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private RealmList columnDateListRealmList; private RealmResults parentObjectsBacklinks; - AllTypesRealmProxy() { + some_test_AllTypesRealmProxy() { proxyState.setConstructionFinished(); } @@ -887,7 +887,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); try { objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); - obj = new io.realm.AllTypesRealmProxy(); + obj = new io.realm.some_test_AllTypesRealmProxy(); } finally { objectContext.clear(); } @@ -932,16 +932,16 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON } if (json.has("columnString")) { if (json.isNull("columnString")) { - obj = (io.realm.AllTypesRealmProxy) realm.createObjectInternal(some.test.AllTypes.class, null, true, excludeFields); + obj = (io.realm.some_test_AllTypesRealmProxy) realm.createObjectInternal(some.test.AllTypes.class, null, true, excludeFields); } else { - obj = (io.realm.AllTypesRealmProxy) realm.createObjectInternal(some.test.AllTypes.class, json.getString("columnString"), true, excludeFields); + obj = (io.realm.some_test_AllTypesRealmProxy) realm.createObjectInternal(some.test.AllTypes.class, json.getString("columnString"), true, excludeFields); } } else { throw new IllegalArgumentException("JSON object doesn't have the primary key field 'columnString'."); } } - final AllTypesRealmProxyInterface objProxy = (AllTypesRealmProxyInterface) obj; + final some_test_AllTypesRealmProxyInterface objProxy = (some_test_AllTypesRealmProxyInterface) obj; if (json.has("columnLong")) { if (json.isNull("columnLong")) { throw new IllegalArgumentException("Trying to set non-nullable field 'columnLong' to null."); @@ -996,7 +996,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON if (json.isNull("columnObject")) { objProxy.realmSet$columnObject(null); } else { - some.test.AllTypes columnObjectObj = AllTypesRealmProxy.createOrUpdateUsingJsonObject(realm, json.getJSONObject("columnObject"), update); + some.test.AllTypes columnObjectObj = some_test_AllTypesRealmProxy.createOrUpdateUsingJsonObject(realm, json.getJSONObject("columnObject"), update); objProxy.realmSet$columnObject(columnObjectObj); } } @@ -1007,7 +1007,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON objProxy.realmGet$columnRealmList().clear(); JSONArray array = json.getJSONArray("columnRealmList"); for (int i = 0; i < array.length(); i++) { - some.test.AllTypes item = AllTypesRealmProxy.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update); + some.test.AllTypes item = some_test_AllTypesRealmProxy.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update); objProxy.realmGet$columnRealmList().add(item); } } @@ -1031,7 +1031,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r throws IOException { boolean jsonHasPrimaryKey = false; final some.test.AllTypes obj = new some.test.AllTypes(); - final AllTypesRealmProxyInterface objProxy = (AllTypesRealmProxyInterface) obj; + final some_test_AllTypesRealmProxyInterface objProxy = (some_test_AllTypesRealmProxyInterface) obj; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -1104,7 +1104,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r reader.skipValue(); objProxy.realmSet$columnObject(null); } else { - some.test.AllTypes columnObjectObj = AllTypesRealmProxy.createUsingJsonStream(realm, reader); + some.test.AllTypes columnObjectObj = some_test_AllTypesRealmProxy.createUsingJsonStream(realm, reader); objProxy.realmSet$columnObject(columnObjectObj); } } else if (name.equals("columnRealmList")) { @@ -1115,7 +1115,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r objProxy.realmSet$columnRealmList(new RealmList()); reader.beginArray(); while (reader.hasNext()) { - some.test.AllTypes item = AllTypesRealmProxy.createUsingJsonStream(realm, reader); + some.test.AllTypes item = some_test_AllTypesRealmProxy.createUsingJsonStream(realm, reader); objProxy.realmGet$columnRealmList().add(item); } reader.endArray(); @@ -1173,7 +1173,7 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob Table table = realm.getTable(some.test.AllTypes.class); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); long pkColumnIndex = columnInfo.columnStringIndex; - String value = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); + String value = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); long rowIndex = Table.NO_MATCH; if (value == null) { rowIndex = table.findFirstNull(pkColumnIndex); @@ -1185,7 +1185,7 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob } else { try { objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); - realmObject = new io.realm.AllTypesRealmProxy(); + realmObject = new io.realm.some_test_AllTypesRealmProxy(); cache.put(object, (RealmObjectProxy) realmObject); } finally { objectContext.clear(); @@ -1203,11 +1203,11 @@ public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, } // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.AllTypes realmObject = realm.createObjectInternal(some.test.AllTypes.class, ((AllTypesRealmProxyInterface) newObject).realmGet$columnString(), false, Collections.emptyList()); + some.test.AllTypes realmObject = realm.createObjectInternal(some.test.AllTypes.class, ((some_test_AllTypesRealmProxyInterface) newObject).realmGet$columnString(), false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); - AllTypesRealmProxyInterface realmObjectSource = (AllTypesRealmProxyInterface) newObject; - AllTypesRealmProxyInterface realmObjectCopy = (AllTypesRealmProxyInterface) realmObject; + some_test_AllTypesRealmProxyInterface realmObjectSource = (some_test_AllTypesRealmProxyInterface) newObject; + some_test_AllTypesRealmProxyInterface realmObjectCopy = (some_test_AllTypesRealmProxyInterface) realmObject; realmObjectCopy.realmSet$columnLong(realmObjectSource.realmGet$columnLong()); realmObjectCopy.realmSet$columnFloat(realmObjectSource.realmGet$columnFloat()); @@ -1226,7 +1226,7 @@ public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, if (cachecolumnObject != null) { realmObjectCopy.realmSet$columnObject(cachecolumnObject); } else { - realmObjectCopy.realmSet$columnObject(AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, update, cache)); + realmObjectCopy.realmSet$columnObject(some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, update, cache)); } } @@ -1240,7 +1240,7 @@ public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, if (cachecolumnRealmList != null) { columnRealmListRealmList.add(cachecolumnRealmList); } else { - columnRealmListRealmList.add(AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, update, cache)); + columnRealmListRealmList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, update, cache)); } } } @@ -1266,7 +1266,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); + RealmList columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null) { OsList columnRealmListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnRealmListIndex); for (some.test.AllTypes columnRealmListItem : columnRealmListList) { Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { - cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insert(realm, columnRealmListItem, cache); + cacheItemIndexcolumnRealmList = some_test_AllTypesRealmProxy.insert(realm, columnRealmListItem, cache); } columnRealmListOsList.addRow(cacheItemIndexcolumnRealmList); } } - RealmList columnStringListList = ((AllTypesRealmProxyInterface) object).realmGet$columnStringList(); + RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { OsList columnStringListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnStringListIndex); for (java.lang.String columnStringListItem : columnStringListList) { @@ -1329,7 +1329,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnBinaryListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); + RealmList columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); for (byte[] columnBinaryListItem : columnBinaryListList) { @@ -1341,7 +1341,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnBooleanListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); + RealmList columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { @@ -1353,7 +1353,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnLongListList = ((AllTypesRealmProxyInterface) object).realmGet$columnLongList(); + RealmList columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); for (java.lang.Long columnLongListItem : columnLongListList) { @@ -1365,7 +1365,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnIntegerListList = ((AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); + RealmList columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { @@ -1377,7 +1377,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnShortListList = ((AllTypesRealmProxyInterface) object).realmGet$columnShortList(); + RealmList columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); for (java.lang.Short columnShortListItem : columnShortListList) { @@ -1389,7 +1389,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnByteListList = ((AllTypesRealmProxyInterface) object).realmGet$columnByteList(); + RealmList columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); for (java.lang.Byte columnByteListItem : columnByteListList) { @@ -1401,7 +1401,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnDoubleListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); + RealmList columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); for (java.lang.Double columnDoubleListItem : columnDoubleListList) { @@ -1413,7 +1413,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnFloatListList = ((AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); + RealmList columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); for (java.lang.Float columnFloatListItem : columnFloatListList) { @@ -1425,7 +1425,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnDateListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDateList(); + RealmList columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); for (java.util.Date columnDateListItem : columnDateListList) { @@ -1454,7 +1454,7 @@ public static void insert(Realm realm, Iterator objects, M cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - String primaryKeyValue = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); + String primaryKeyValue = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); long rowIndex = Table.NO_MATCH; if (primaryKeyValue == null) { rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); @@ -1467,45 +1467,45 @@ public static void insert(Realm realm, Iterator objects, M Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); - java.util.Date realmGet$columnDate = ((AllTypesRealmProxyInterface) object).realmGet$columnDate(); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); } - byte[] realmGet$columnBinary = ((AllTypesRealmProxyInterface) object).realmGet$columnBinary(); + byte[] realmGet$columnBinary = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); } - Long realmGet$columnMutableRealmInteger = ((AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); + Long realmGet$columnMutableRealmInteger = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); if (realmGet$columnMutableRealmInteger != null) { Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerIndex, rowIndex, realmGet$columnMutableRealmInteger.longValue(), false); } - some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) object).realmGet$columnObject(); + some.test.AllTypes columnObjectObj = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObject(); if (columnObjectObj != null) { Long cachecolumnObject = cache.get(columnObjectObj); if (cachecolumnObject == null) { - cachecolumnObject = AllTypesRealmProxy.insert(realm, columnObjectObj, cache); + cachecolumnObject = some_test_AllTypesRealmProxy.insert(realm, columnObjectObj, cache); } table.setLink(columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); } - RealmList columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); + RealmList columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null) { OsList columnRealmListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnRealmListIndex); for (some.test.AllTypes columnRealmListItem : columnRealmListList) { Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { - cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insert(realm, columnRealmListItem, cache); + cacheItemIndexcolumnRealmList = some_test_AllTypesRealmProxy.insert(realm, columnRealmListItem, cache); } columnRealmListOsList.addRow(cacheItemIndexcolumnRealmList); } } - RealmList columnStringListList = ((AllTypesRealmProxyInterface) object).realmGet$columnStringList(); + RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { OsList columnStringListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnStringListIndex); for (java.lang.String columnStringListItem : columnStringListList) { @@ -1517,7 +1517,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList columnBinaryListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); + RealmList columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); for (byte[] columnBinaryListItem : columnBinaryListList) { @@ -1529,7 +1529,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList columnBooleanListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); + RealmList columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { @@ -1541,7 +1541,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList columnLongListList = ((AllTypesRealmProxyInterface) object).realmGet$columnLongList(); + RealmList columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); for (java.lang.Long columnLongListItem : columnLongListList) { @@ -1553,7 +1553,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList columnIntegerListList = ((AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); + RealmList columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { @@ -1565,7 +1565,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList columnShortListList = ((AllTypesRealmProxyInterface) object).realmGet$columnShortList(); + RealmList columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); for (java.lang.Short columnShortListItem : columnShortListList) { @@ -1577,7 +1577,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList columnByteListList = ((AllTypesRealmProxyInterface) object).realmGet$columnByteList(); + RealmList columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); for (java.lang.Byte columnByteListItem : columnByteListList) { @@ -1589,7 +1589,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList columnDoubleListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); + RealmList columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); for (java.lang.Double columnDoubleListItem : columnDoubleListList) { @@ -1601,7 +1601,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList columnFloatListList = ((AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); + RealmList columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); for (java.lang.Float columnFloatListItem : columnFloatListList) { @@ -1613,7 +1613,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList columnDateListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDateList(); + RealmList columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); for (java.util.Date columnDateListItem : columnDateListList) { @@ -1635,7 +1635,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); + RealmList columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null && columnRealmListList.size() == columnRealmListOsList.size()) { // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. int objects = columnRealmListList.size(); @@ -1689,7 +1689,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnStringListList = ((AllTypesRealmProxyInterface) object).realmGet$columnStringList(); + RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { for (java.lang.String columnStringListItem : columnStringListList) { if (columnStringListItem == null) { @@ -1723,7 +1723,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnBinaryListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); + RealmList columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { for (byte[] columnBinaryListItem : columnBinaryListList) { if (columnBinaryListItem == null) { @@ -1737,7 +1737,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnBooleanListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); + RealmList columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { if (columnBooleanListItem == null) { @@ -1751,7 +1751,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnLongListList = ((AllTypesRealmProxyInterface) object).realmGet$columnLongList(); + RealmList columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { for (java.lang.Long columnLongListItem : columnLongListList) { if (columnLongListItem == null) { @@ -1765,7 +1765,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnIntegerListList = ((AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); + RealmList columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { if (columnIntegerListItem == null) { @@ -1779,7 +1779,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnShortListList = ((AllTypesRealmProxyInterface) object).realmGet$columnShortList(); + RealmList columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { for (java.lang.Short columnShortListItem : columnShortListList) { if (columnShortListItem == null) { @@ -1793,7 +1793,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnByteListList = ((AllTypesRealmProxyInterface) object).realmGet$columnByteList(); + RealmList columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { for (java.lang.Byte columnByteListItem : columnByteListList) { if (columnByteListItem == null) { @@ -1807,7 +1807,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnDoubleListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); + RealmList columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { for (java.lang.Double columnDoubleListItem : columnDoubleListList) { if (columnDoubleListItem == null) { @@ -1821,7 +1821,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnFloatListList = ((AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); + RealmList columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { for (java.lang.Float columnFloatListItem : columnFloatListList) { if (columnFloatListItem == null) { @@ -1835,7 +1835,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnDateListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDateList(); + RealmList columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { for (java.util.Date columnDateListItem : columnDateListList) { if (columnDateListItem == null) { @@ -1864,7 +1864,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); continue; } - String primaryKeyValue = ((AllTypesRealmProxyInterface) object).realmGet$columnString(); + String primaryKeyValue = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); long rowIndex = Table.NO_MATCH; if (primaryKeyValue == null) { rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); @@ -1875,34 +1875,34 @@ public static void insertOrUpdate(Realm realm, Iterator ob rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue); } cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); - java.util.Date realmGet$columnDate = ((AllTypesRealmProxyInterface) object).realmGet$columnDate(); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.columnDateIndex, rowIndex, false); } - byte[] realmGet$columnBinary = ((AllTypesRealmProxyInterface) object).realmGet$columnBinary(); + byte[] realmGet$columnBinary = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, false); } - Long realmGet$columnMutableRealmInteger = ((AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); + Long realmGet$columnMutableRealmInteger = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); if (realmGet$columnMutableRealmInteger != null) { Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerIndex, rowIndex, realmGet$columnMutableRealmInteger.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.columnMutableRealmIntegerIndex, rowIndex, false); } - some.test.AllTypes columnObjectObj = ((AllTypesRealmProxyInterface) object).realmGet$columnObject(); + some.test.AllTypes columnObjectObj = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObject(); if (columnObjectObj != null) { Long cachecolumnObject = cache.get(columnObjectObj); if (cachecolumnObject == null) { - cachecolumnObject = AllTypesRealmProxy.insertOrUpdate(realm, columnObjectObj, cache); + cachecolumnObject = some_test_AllTypesRealmProxy.insertOrUpdate(realm, columnObjectObj, cache); } Table.nativeSetLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); } else { @@ -1910,7 +1910,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } OsList columnRealmListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnRealmListIndex); - RealmList columnRealmListList = ((AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); + RealmList columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null && columnRealmListList.size() == columnRealmListOsList.size()) { // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. int objectCount = columnRealmListList.size(); @@ -1918,7 +1918,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { - cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); + cacheItemIndexcolumnRealmList = some_test_AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); } columnRealmListOsList.setRow(i, cacheItemIndexcolumnRealmList); } @@ -1928,7 +1928,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob for (some.test.AllTypes columnRealmListItem : columnRealmListList) { Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { - cacheItemIndexcolumnRealmList = AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); + cacheItemIndexcolumnRealmList = some_test_AllTypesRealmProxy.insertOrUpdate(realm, columnRealmListItem, cache); } columnRealmListOsList.addRow(cacheItemIndexcolumnRealmList); } @@ -1938,7 +1938,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnStringListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnStringListIndex); columnStringListOsList.removeAll(); - RealmList columnStringListList = ((AllTypesRealmProxyInterface) object).realmGet$columnStringList(); + RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { for (java.lang.String columnStringListItem : columnStringListList) { if (columnStringListItem == null) { @@ -1952,7 +1952,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); columnBinaryListOsList.removeAll(); - RealmList columnBinaryListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); + RealmList columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { for (byte[] columnBinaryListItem : columnBinaryListList) { if (columnBinaryListItem == null) { @@ -1966,7 +1966,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); columnBooleanListOsList.removeAll(); - RealmList columnBooleanListList = ((AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); + RealmList columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { if (columnBooleanListItem == null) { @@ -1980,7 +1980,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); columnLongListOsList.removeAll(); - RealmList columnLongListList = ((AllTypesRealmProxyInterface) object).realmGet$columnLongList(); + RealmList columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { for (java.lang.Long columnLongListItem : columnLongListList) { if (columnLongListItem == null) { @@ -1994,7 +1994,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); columnIntegerListOsList.removeAll(); - RealmList columnIntegerListList = ((AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); + RealmList columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { if (columnIntegerListItem == null) { @@ -2008,7 +2008,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); columnShortListOsList.removeAll(); - RealmList columnShortListList = ((AllTypesRealmProxyInterface) object).realmGet$columnShortList(); + RealmList columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { for (java.lang.Short columnShortListItem : columnShortListList) { if (columnShortListItem == null) { @@ -2022,7 +2022,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); columnByteListOsList.removeAll(); - RealmList columnByteListList = ((AllTypesRealmProxyInterface) object).realmGet$columnByteList(); + RealmList columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { for (java.lang.Byte columnByteListItem : columnByteListList) { if (columnByteListItem == null) { @@ -2036,7 +2036,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); columnDoubleListOsList.removeAll(); - RealmList columnDoubleListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); + RealmList columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { for (java.lang.Double columnDoubleListItem : columnDoubleListList) { if (columnDoubleListItem == null) { @@ -2050,7 +2050,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); columnFloatListOsList.removeAll(); - RealmList columnFloatListList = ((AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); + RealmList columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { for (java.lang.Float columnFloatListItem : columnFloatListList) { if (columnFloatListItem == null) { @@ -2064,7 +2064,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); columnDateListOsList.removeAll(); - RealmList columnDateListList = ((AllTypesRealmProxyInterface) object).realmGet$columnDateList(); + RealmList columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { for (java.util.Date columnDateListItem : columnDateListList) { if (columnDateListItem == null) { @@ -2095,8 +2095,8 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje unmanagedObject = (some.test.AllTypes) cachedObject.object; cachedObject.minDepth = currentDepth; } - AllTypesRealmProxyInterface unmanagedCopy = (AllTypesRealmProxyInterface) unmanagedObject; - AllTypesRealmProxyInterface realmSource = (AllTypesRealmProxyInterface) realmObject; + some_test_AllTypesRealmProxyInterface unmanagedCopy = (some_test_AllTypesRealmProxyInterface) unmanagedObject; + some_test_AllTypesRealmProxyInterface realmSource = (some_test_AllTypesRealmProxyInterface) realmObject; unmanagedCopy.realmSet$columnString(realmSource.realmGet$columnString()); unmanagedCopy.realmSet$columnLong(realmSource.realmGet$columnLong()); unmanagedCopy.realmSet$columnFloat(realmSource.realmGet$columnFloat()); @@ -2107,7 +2107,7 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje unmanagedCopy.realmGet$columnMutableRealmInteger().set(realmSource.realmGet$columnMutableRealmInteger().get()); // Deep copy of columnObject - unmanagedCopy.realmSet$columnObject(AllTypesRealmProxy.createDetachedCopy(realmSource.realmGet$columnObject(), currentDepth + 1, maxDepth, cache)); + unmanagedCopy.realmSet$columnObject(some_test_AllTypesRealmProxy.createDetachedCopy(realmSource.realmGet$columnObject(), currentDepth + 1, maxDepth, cache)); // Deep copy of columnRealmList if (currentDepth == maxDepth) { @@ -2119,7 +2119,7 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje int nextDepth = currentDepth + 1; int size = managedcolumnRealmListList.size(); for (int i = 0; i < size; i++) { - some.test.AllTypes item = AllTypesRealmProxy.createDetachedCopy(managedcolumnRealmListList.get(i), nextDepth, maxDepth, cache); + some.test.AllTypes item = some_test_AllTypesRealmProxy.createDetachedCopy(managedcolumnRealmListList.get(i), nextDepth, maxDepth, cache); unmanagedcolumnRealmListList.add(item); } } @@ -2158,8 +2158,8 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje } static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, some.test.AllTypes newObject, Map cache) { - AllTypesRealmProxyInterface realmObjectTarget = (AllTypesRealmProxyInterface) realmObject; - AllTypesRealmProxyInterface realmObjectSource = (AllTypesRealmProxyInterface) newObject; + some_test_AllTypesRealmProxyInterface realmObjectTarget = (some_test_AllTypesRealmProxyInterface) realmObject; + some_test_AllTypesRealmProxyInterface realmObjectSource = (some_test_AllTypesRealmProxyInterface) newObject; realmObjectTarget.realmSet$columnLong(realmObjectSource.realmGet$columnLong()); realmObjectTarget.realmSet$columnFloat(realmObjectSource.realmGet$columnFloat()); realmObjectTarget.realmSet$columnDouble(realmObjectSource.realmGet$columnDouble()); @@ -2175,7 +2175,7 @@ static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, so if (cachecolumnObject != null) { realmObjectTarget.realmSet$columnObject(cachecolumnObject); } else { - realmObjectTarget.realmSet$columnObject(AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, true, cache)); + realmObjectTarget.realmSet$columnObject(some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, true, cache)); } } RealmList columnRealmListList = realmObjectSource.realmGet$columnRealmList(); @@ -2189,7 +2189,7 @@ static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, so if (cachecolumnRealmList != null) { columnRealmListRealmList.set(i, cachecolumnRealmList); } else { - columnRealmListRealmList.set(i, AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); + columnRealmListRealmList.set(i, some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); } } } else { @@ -2201,7 +2201,7 @@ static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, so if (cachecolumnRealmList != null) { columnRealmListRealmList.add(cachecolumnRealmList); } else { - columnRealmListRealmList.add(AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); + columnRealmListRealmList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); } } } @@ -2331,7 +2331,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AllTypesRealmProxy aAllTypes = (AllTypesRealmProxy)o; + some_test_AllTypesRealmProxy aAllTypes = (some_test_AllTypesRealmProxy)o; String path = proxyState.getRealm$realm().getPath(); String otherPath = aAllTypes.proxyState.getRealm$realm().getPath(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java similarity index 89% rename from realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java rename to realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java index f0a55dba46..d2c9f22cff 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java @@ -31,8 +31,8 @@ import org.json.JSONObject; @SuppressWarnings("all") -public class BooleansRealmProxy extends some.test.Booleans - implements RealmObjectProxy, BooleansRealmProxyInterface { +public class some_test_BooleansRealmProxy extends some.test.Booleans + implements RealmObjectProxy, some_test_BooleansRealmProxyInterface { static final class BooleansColumnInfo extends ColumnInfo { long doneIndex; @@ -75,7 +75,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private BooleansColumnInfo columnInfo; private ProxyState proxyState; - BooleansRealmProxy() { + some_test_BooleansRealmProxy() { proxyState.setConstructionFinished(); } @@ -208,7 +208,7 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON final List excludeFields = Collections. emptyList(); some.test.Booleans obj = realm.createObjectInternal(some.test.Booleans.class, true, excludeFields); - final BooleansRealmProxyInterface objProxy = (BooleansRealmProxyInterface) obj; + final some_test_BooleansRealmProxyInterface objProxy = (some_test_BooleansRealmProxyInterface) obj; if (json.has("done")) { if (json.isNull("done")) { throw new IllegalArgumentException("Trying to set non-nullable field 'done' to null."); @@ -245,7 +245,7 @@ public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSON public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { final some.test.Booleans obj = new some.test.Booleans(); - final BooleansRealmProxyInterface objProxy = (BooleansRealmProxyInterface) obj; + final some_test_BooleansRealmProxyInterface objProxy = (some_test_BooleansRealmProxyInterface) obj; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -315,8 +315,8 @@ public static some.test.Booleans copy(Realm realm, some.test.Booleans newObject, some.test.Booleans realmObject = realm.createObjectInternal(some.test.Booleans.class, false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); - BooleansRealmProxyInterface realmObjectSource = (BooleansRealmProxyInterface) newObject; - BooleansRealmProxyInterface realmObjectCopy = (BooleansRealmProxyInterface) realmObject; + some_test_BooleansRealmProxyInterface realmObjectSource = (some_test_BooleansRealmProxyInterface) newObject; + some_test_BooleansRealmProxyInterface realmObjectCopy = (some_test_BooleansRealmProxyInterface) realmObject; realmObjectCopy.realmSet$done(realmObjectSource.realmGet$done()); realmObjectCopy.realmSet$isReady(realmObjectSource.realmGet$isReady()); @@ -334,10 +334,10 @@ public static long insert(Realm realm, some.test.Booleans object, Map objects, M } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); } } @@ -373,10 +373,10 @@ public static long insertOrUpdate(Realm realm, some.test.Booleans object, Map ob } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); } } @@ -420,8 +420,8 @@ public static some.test.Booleans createDetachedCopy(some.test.Booleans realmObje unmanagedObject = (some.test.Booleans) cachedObject.object; cachedObject.minDepth = currentDepth; } - BooleansRealmProxyInterface unmanagedCopy = (BooleansRealmProxyInterface) unmanagedObject; - BooleansRealmProxyInterface realmSource = (BooleansRealmProxyInterface) realmObject; + some_test_BooleansRealmProxyInterface unmanagedCopy = (some_test_BooleansRealmProxyInterface) unmanagedObject; + some_test_BooleansRealmProxyInterface realmSource = (some_test_BooleansRealmProxyInterface) realmObject; unmanagedCopy.realmSet$done(realmSource.realmGet$done()); unmanagedCopy.realmSet$isReady(realmSource.realmGet$isReady()); unmanagedCopy.realmSet$mCompleted(realmSource.realmGet$mCompleted()); @@ -478,7 +478,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - BooleansRealmProxy aBooleans = (BooleansRealmProxy)o; + some_test_BooleansRealmProxy aBooleans = (some_test_BooleansRealmProxy)o; String path = proxyState.getRealm$realm().getPath(); String otherPath = aBooleans.proxyState.getRealm$realm().getPath(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyMixedClassSettingsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java similarity index 89% rename from realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyMixedClassSettingsRealmProxy.java rename to realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java index 5b37b1c93c..eb71397828 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyMixedClassSettingsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java @@ -31,8 +31,8 @@ import org.json.JSONObject; @SuppressWarnings("all") -public class NamePolicyMixedClassSettingsRealmProxy extends some.test.NamePolicyMixedClassSettings - implements RealmObjectProxy, NamePolicyMixedClassSettingsRealmProxyInterface { +public class some_test_NamePolicyMixedClassSettingsRealmProxy extends some.test.NamePolicyMixedClassSettings + implements RealmObjectProxy, some_test_NamePolicyMixedClassSettingsRealmProxyInterface { static final class NamePolicyMixedClassSettingsColumnInfo extends ColumnInfo { long firstNameIndex; @@ -69,7 +69,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private NamePolicyMixedClassSettingsColumnInfo columnInfo; private ProxyState proxyState; - NamePolicyMixedClassSettingsRealmProxy() { + some_test_NamePolicyMixedClassSettingsRealmProxy() { proxyState.setConstructionFinished(); } @@ -172,7 +172,7 @@ public static some.test.NamePolicyMixedClassSettings createOrUpdateUsingJsonObje final List excludeFields = Collections. emptyList(); some.test.NamePolicyMixedClassSettings obj = realm.createObjectInternal(some.test.NamePolicyMixedClassSettings.class, true, excludeFields); - final NamePolicyMixedClassSettingsRealmProxyInterface objProxy = (NamePolicyMixedClassSettingsRealmProxyInterface) obj; + final some_test_NamePolicyMixedClassSettingsRealmProxyInterface objProxy = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) obj; if (json.has("firstName")) { if (json.isNull("firstName")) { objProxy.realmSet$firstName(null); @@ -195,7 +195,7 @@ public static some.test.NamePolicyMixedClassSettings createOrUpdateUsingJsonObje public static some.test.NamePolicyMixedClassSettings createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { final some.test.NamePolicyMixedClassSettings obj = new some.test.NamePolicyMixedClassSettings(); - final NamePolicyMixedClassSettingsRealmProxyInterface objProxy = (NamePolicyMixedClassSettingsRealmProxyInterface) obj; + final some_test_NamePolicyMixedClassSettingsRealmProxyInterface objProxy = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) obj; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -251,8 +251,8 @@ public static some.test.NamePolicyMixedClassSettings copy(Realm realm, some.test some.test.NamePolicyMixedClassSettings realmObject = realm.createObjectInternal(some.test.NamePolicyMixedClassSettings.class, false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); - NamePolicyMixedClassSettingsRealmProxyInterface realmObjectSource = (NamePolicyMixedClassSettingsRealmProxyInterface) newObject; - NamePolicyMixedClassSettingsRealmProxyInterface realmObjectCopy = (NamePolicyMixedClassSettingsRealmProxyInterface) realmObject; + some_test_NamePolicyMixedClassSettingsRealmProxyInterface realmObjectSource = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) newObject; + some_test_NamePolicyMixedClassSettingsRealmProxyInterface realmObjectCopy = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) realmObject; realmObjectCopy.realmSet$firstName(realmObjectSource.realmGet$firstName()); realmObjectCopy.realmSet$lastName(realmObjectSource.realmGet$lastName()); @@ -268,11 +268,11 @@ public static long insert(Realm realm, some.test.NamePolicyMixedClassSettings ob NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$firstName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); + String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); } - String realmGet$lastName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); + String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); } @@ -295,11 +295,11 @@ public static void insert(Realm realm, Iterator objects, M } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$firstName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); + String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); } - String realmGet$lastName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); + String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); } @@ -315,13 +315,13 @@ public static long insertOrUpdate(Realm realm, some.test.NamePolicyMixedClassSet NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$firstName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); + String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); } - String realmGet$lastName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); + String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); } else { @@ -346,13 +346,13 @@ public static void insertOrUpdate(Realm realm, Iterator ob } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$firstName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); + String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); } - String realmGet$lastName = ((NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); + String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); } else { @@ -378,8 +378,8 @@ public static some.test.NamePolicyMixedClassSettings createDetachedCopy(some.tes unmanagedObject = (some.test.NamePolicyMixedClassSettings) cachedObject.object; cachedObject.minDepth = currentDepth; } - NamePolicyMixedClassSettingsRealmProxyInterface unmanagedCopy = (NamePolicyMixedClassSettingsRealmProxyInterface) unmanagedObject; - NamePolicyMixedClassSettingsRealmProxyInterface realmSource = (NamePolicyMixedClassSettingsRealmProxyInterface) realmObject; + some_test_NamePolicyMixedClassSettingsRealmProxyInterface unmanagedCopy = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) unmanagedObject; + some_test_NamePolicyMixedClassSettingsRealmProxyInterface realmSource = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) realmObject; unmanagedCopy.realmSet$firstName(realmSource.realmGet$firstName()); unmanagedCopy.realmSet$lastName(realmSource.realmGet$lastName()); @@ -426,7 +426,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - NamePolicyMixedClassSettingsRealmProxy aNamePolicyMixedClassSettings = (NamePolicyMixedClassSettingsRealmProxy)o; + some_test_NamePolicyMixedClassSettingsRealmProxy aNamePolicyMixedClassSettings = (some_test_NamePolicyMixedClassSettingsRealmProxy)o; String path = proxyState.getRealm$realm().getPath(); String otherPath = aNamePolicyMixedClassSettings.proxyState.getRealm$realm().getPath(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyModuleDefaultsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java similarity index 89% rename from realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyModuleDefaultsRealmProxy.java rename to realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java index b1aaceae87..221ca41710 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NamePolicyModuleDefaultsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java @@ -31,8 +31,8 @@ import org.json.JSONObject; @SuppressWarnings("all") -public class NamePolicyModuleDefaultsRealmProxy extends some.test.NamePolicyModuleDefaults - implements RealmObjectProxy, NamePolicyModuleDefaultsRealmProxyInterface { +public class some_test_NamePolicyModuleDefaultsRealmProxy extends some.test.NamePolicyModuleDefaults + implements RealmObjectProxy, some_test_NamePolicyModuleDefaultsRealmProxyInterface { static final class NamePolicyModuleDefaultsColumnInfo extends ColumnInfo { long firstNameIndex; @@ -69,7 +69,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private NamePolicyModuleDefaultsColumnInfo columnInfo; private ProxyState proxyState; - NamePolicyModuleDefaultsRealmProxy() { + some_test_NamePolicyModuleDefaultsRealmProxy() { proxyState.setConstructionFinished(); } @@ -172,7 +172,7 @@ public static some.test.NamePolicyModuleDefaults createOrUpdateUsingJsonObject(R final List excludeFields = Collections. emptyList(); some.test.NamePolicyModuleDefaults obj = realm.createObjectInternal(some.test.NamePolicyModuleDefaults.class, true, excludeFields); - final NamePolicyModuleDefaultsRealmProxyInterface objProxy = (NamePolicyModuleDefaultsRealmProxyInterface) obj; + final some_test_NamePolicyModuleDefaultsRealmProxyInterface objProxy = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) obj; if (json.has("firstName")) { if (json.isNull("firstName")) { objProxy.realmSet$firstName(null); @@ -195,7 +195,7 @@ public static some.test.NamePolicyModuleDefaults createOrUpdateUsingJsonObject(R public static some.test.NamePolicyModuleDefaults createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { final some.test.NamePolicyModuleDefaults obj = new some.test.NamePolicyModuleDefaults(); - final NamePolicyModuleDefaultsRealmProxyInterface objProxy = (NamePolicyModuleDefaultsRealmProxyInterface) obj; + final some_test_NamePolicyModuleDefaultsRealmProxyInterface objProxy = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) obj; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -251,8 +251,8 @@ public static some.test.NamePolicyModuleDefaults copy(Realm realm, some.test.Nam some.test.NamePolicyModuleDefaults realmObject = realm.createObjectInternal(some.test.NamePolicyModuleDefaults.class, false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); - NamePolicyModuleDefaultsRealmProxyInterface realmObjectSource = (NamePolicyModuleDefaultsRealmProxyInterface) newObject; - NamePolicyModuleDefaultsRealmProxyInterface realmObjectCopy = (NamePolicyModuleDefaultsRealmProxyInterface) realmObject; + some_test_NamePolicyModuleDefaultsRealmProxyInterface realmObjectSource = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) newObject; + some_test_NamePolicyModuleDefaultsRealmProxyInterface realmObjectCopy = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) realmObject; realmObjectCopy.realmSet$firstName(realmObjectSource.realmGet$firstName()); realmObjectCopy.realmSet$lastName(realmObjectSource.realmGet$lastName()); @@ -268,11 +268,11 @@ public static long insert(Realm realm, some.test.NamePolicyModuleDefaults object NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$firstName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); + String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); } - String realmGet$lastName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); + String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); } @@ -295,11 +295,11 @@ public static void insert(Realm realm, Iterator objects, M } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$firstName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); + String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); } - String realmGet$lastName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); + String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); } @@ -315,13 +315,13 @@ public static long insertOrUpdate(Realm realm, some.test.NamePolicyModuleDefault NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$firstName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); + String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); } - String realmGet$lastName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); + String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); } else { @@ -346,13 +346,13 @@ public static void insertOrUpdate(Realm realm, Iterator ob } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$firstName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); + String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); } - String realmGet$lastName = ((NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); + String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); } else { @@ -378,8 +378,8 @@ public static some.test.NamePolicyModuleDefaults createDetachedCopy(some.test.Na unmanagedObject = (some.test.NamePolicyModuleDefaults) cachedObject.object; cachedObject.minDepth = currentDepth; } - NamePolicyModuleDefaultsRealmProxyInterface unmanagedCopy = (NamePolicyModuleDefaultsRealmProxyInterface) unmanagedObject; - NamePolicyModuleDefaultsRealmProxyInterface realmSource = (NamePolicyModuleDefaultsRealmProxyInterface) realmObject; + some_test_NamePolicyModuleDefaultsRealmProxyInterface unmanagedCopy = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) unmanagedObject; + some_test_NamePolicyModuleDefaultsRealmProxyInterface realmSource = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) realmObject; unmanagedCopy.realmSet$firstName(realmSource.realmGet$firstName()); unmanagedCopy.realmSet$lastName(realmSource.realmGet$lastName()); @@ -426,7 +426,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - NamePolicyModuleDefaultsRealmProxy aNamePolicyModuleDefaults = (NamePolicyModuleDefaultsRealmProxy)o; + some_test_NamePolicyModuleDefaultsRealmProxy aNamePolicyModuleDefaults = (some_test_NamePolicyModuleDefaultsRealmProxy)o; String path = proxyState.getRealm$realm().getPath(); String otherPath = aNamePolicyModuleDefaults.proxyState.getRealm$realm().getPath(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java similarity index 90% rename from realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java rename to realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index e7af4b8897..c4c3669172 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -31,8 +31,8 @@ import org.json.JSONObject; @SuppressWarnings("all") -public class NullTypesRealmProxy extends some.test.NullTypes - implements RealmObjectProxy, NullTypesRealmProxyInterface { +public class some_test_NullTypesRealmProxy extends some.test.NullTypes + implements RealmObjectProxy, some_test_NullTypesRealmProxyInterface { static final class NullTypesColumnInfo extends ColumnInfo { long fieldStringNotNullIndex; @@ -206,7 +206,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private RealmList fieldDateListNotNullRealmList; private RealmList fieldDateListNullRealmList; - NullTypesRealmProxy() { + some_test_NullTypesRealmProxy() { proxyState.setConstructionFinished(); } @@ -1776,7 +1776,7 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO } some.test.NullTypes obj = realm.createObjectInternal(some.test.NullTypes.class, true, excludeFields); - final NullTypesRealmProxyInterface objProxy = (NullTypesRealmProxyInterface) obj; + final some_test_NullTypesRealmProxyInterface objProxy = (some_test_NullTypesRealmProxyInterface) obj; if (json.has("fieldStringNotNull")) { if (json.isNull("fieldStringNotNull")) { objProxy.realmSet$fieldStringNotNull(null); @@ -1931,7 +1931,7 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO if (json.isNull("fieldObjectNull")) { objProxy.realmSet$fieldObjectNull(null); } else { - some.test.NullTypes fieldObjectNullObj = NullTypesRealmProxy.createOrUpdateUsingJsonObject(realm, json.getJSONObject("fieldObjectNull"), update); + some.test.NullTypes fieldObjectNullObj = some_test_NullTypesRealmProxy.createOrUpdateUsingJsonObject(realm, json.getJSONObject("fieldObjectNull"), update); objProxy.realmSet$fieldObjectNull(fieldObjectNullObj); } } @@ -1963,7 +1963,7 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { final some.test.NullTypes obj = new some.test.NullTypes(); - final NullTypesRealmProxyInterface objProxy = (NullTypesRealmProxyInterface) obj; + final some_test_NullTypesRealmProxyInterface objProxy = (some_test_NullTypesRealmProxyInterface) obj; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -2123,7 +2123,7 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader reader.skipValue(); objProxy.realmSet$fieldObjectNull(null); } else { - some.test.NullTypes fieldObjectNullObj = NullTypesRealmProxy.createUsingJsonStream(realm, reader); + some.test.NullTypes fieldObjectNullObj = some_test_NullTypesRealmProxy.createUsingJsonStream(realm, reader); objProxy.realmSet$fieldObjectNull(fieldObjectNullObj); } } else if (name.equals("fieldStringListNotNull")) { @@ -2203,8 +2203,8 @@ public static some.test.NullTypes copy(Realm realm, some.test.NullTypes newObjec some.test.NullTypes realmObject = realm.createObjectInternal(some.test.NullTypes.class, false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); - NullTypesRealmProxyInterface realmObjectSource = (NullTypesRealmProxyInterface) newObject; - NullTypesRealmProxyInterface realmObjectCopy = (NullTypesRealmProxyInterface) realmObject; + some_test_NullTypesRealmProxyInterface realmObjectSource = (some_test_NullTypesRealmProxyInterface) newObject; + some_test_NullTypesRealmProxyInterface realmObjectCopy = (some_test_NullTypesRealmProxyInterface) realmObject; realmObjectCopy.realmSet$fieldStringNotNull(realmObjectSource.realmGet$fieldStringNotNull()); realmObjectCopy.realmSet$fieldStringNull(realmObjectSource.realmGet$fieldStringNull()); @@ -2235,7 +2235,7 @@ public static some.test.NullTypes copy(Realm realm, some.test.NullTypes newObjec if (cachefieldObjectNull != null) { realmObjectCopy.realmSet$fieldObjectNull(cachefieldObjectNull); } else { - realmObjectCopy.realmSet$fieldObjectNull(NullTypesRealmProxy.copyOrUpdate(realm, fieldObjectNullObj, update, cache)); + realmObjectCopy.realmSet$fieldObjectNull(some_test_NullTypesRealmProxy.copyOrUpdate(realm, fieldObjectNullObj, update, cache)); } } realmObjectCopy.realmSet$fieldStringListNotNull(realmObjectSource.realmGet$fieldStringListNotNull()); @@ -2270,97 +2270,97 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldStringListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); + RealmList fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNotNullIndex); for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { @@ -2372,7 +2372,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldStringListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); + RealmList fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { @@ -2384,7 +2384,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBinaryListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); + RealmList fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { @@ -2396,7 +2396,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBinaryListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); + RealmList fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { @@ -2408,7 +2408,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBooleanListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); + RealmList fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { @@ -2420,7 +2420,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBooleanListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); + RealmList fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { @@ -2432,7 +2432,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldLongListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); + RealmList fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { @@ -2444,7 +2444,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldLongListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); + RealmList fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { @@ -2456,7 +2456,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldIntegerListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); + RealmList fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { @@ -2468,7 +2468,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldIntegerListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); + RealmList fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { @@ -2480,7 +2480,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldShortListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); + RealmList fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { @@ -2492,7 +2492,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldShortListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); + RealmList fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { @@ -2504,7 +2504,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldByteListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); + RealmList fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { @@ -2516,7 +2516,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldByteListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); + RealmList fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { @@ -2528,7 +2528,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDoubleListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); + RealmList fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { @@ -2540,7 +2540,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDoubleListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); + RealmList fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { @@ -2552,7 +2552,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldFloatListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); + RealmList fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { @@ -2564,7 +2564,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldFloatListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); + RealmList fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { @@ -2576,7 +2576,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDateListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); + RealmList fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { @@ -2588,7 +2588,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDateListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); + RealmList fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { @@ -2618,97 +2618,97 @@ public static void insert(Realm realm, Iterator objects, M } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); + String realmGet$fieldStringNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); } - String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); + String realmGet$fieldStringNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); } - Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); + Boolean realmGet$fieldBooleanNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); } - Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); + Boolean realmGet$fieldBooleanNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); } - byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); + byte[] realmGet$fieldBytesNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); } - byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); + byte[] realmGet$fieldBytesNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); } - Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); + Number realmGet$fieldByteNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); } - Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); + Number realmGet$fieldByteNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); } - Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); + Number realmGet$fieldShortNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); } - Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); + Number realmGet$fieldShortNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); } - Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); + Number realmGet$fieldIntegerNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); } - Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); + Number realmGet$fieldIntegerNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); } - Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); + Number realmGet$fieldLongNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); } - Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); + Number realmGet$fieldLongNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); } - Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); + Float realmGet$fieldFloatNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); } - Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); + Float realmGet$fieldFloatNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); } - Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); + Double realmGet$fieldDoubleNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); } - Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); + Double realmGet$fieldDoubleNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); } - java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); + java.util.Date realmGet$fieldDateNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); } - java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); + java.util.Date realmGet$fieldDateNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); } - some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); + some.test.NullTypes fieldObjectNullObj = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); if (fieldObjectNullObj != null) { Long cachefieldObjectNull = cache.get(fieldObjectNullObj); if (cachefieldObjectNull == null) { - cachefieldObjectNull = NullTypesRealmProxy.insert(realm, fieldObjectNullObj, cache); + cachefieldObjectNull = some_test_NullTypesRealmProxy.insert(realm, fieldObjectNullObj, cache); } table.setLink(columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); } - RealmList fieldStringListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); + RealmList fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNotNullIndex); for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { @@ -2720,7 +2720,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldStringListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); + RealmList fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { @@ -2732,7 +2732,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldBinaryListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); + RealmList fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { @@ -2744,7 +2744,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldBinaryListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); + RealmList fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { @@ -2756,7 +2756,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldBooleanListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); + RealmList fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { @@ -2768,7 +2768,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldBooleanListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); + RealmList fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { @@ -2780,7 +2780,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldLongListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); + RealmList fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { @@ -2792,7 +2792,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldLongListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); + RealmList fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { @@ -2804,7 +2804,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldIntegerListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); + RealmList fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { @@ -2816,7 +2816,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldIntegerListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); + RealmList fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { @@ -2828,7 +2828,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldShortListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); + RealmList fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { @@ -2840,7 +2840,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldShortListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); + RealmList fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { @@ -2852,7 +2852,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldByteListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); + RealmList fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { @@ -2864,7 +2864,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldByteListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); + RealmList fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { @@ -2876,7 +2876,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldDoubleListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); + RealmList fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { @@ -2888,7 +2888,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldDoubleListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); + RealmList fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { @@ -2900,7 +2900,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldFloatListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); + RealmList fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { @@ -2912,7 +2912,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldFloatListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); + RealmList fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { @@ -2924,7 +2924,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldDateListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); + RealmList fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { @@ -2936,7 +2936,7 @@ public static void insert(Realm realm, Iterator objects, M } } - RealmList fieldDateListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); + RealmList fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { @@ -2959,132 +2959,132 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldStringListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); + RealmList fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { if (fieldStringListNotNullItem == null) { @@ -3107,7 +3107,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldStringListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); + RealmList fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { if (fieldStringListNullItem == null) { @@ -3121,7 +3121,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBinaryListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); + RealmList fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { if (fieldBinaryListNotNullItem == null) { @@ -3135,7 +3135,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBinaryListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); + RealmList fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { if (fieldBinaryListNullItem == null) { @@ -3149,7 +3149,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBooleanListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); + RealmList fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { if (fieldBooleanListNotNullItem == null) { @@ -3163,7 +3163,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBooleanListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); + RealmList fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { if (fieldBooleanListNullItem == null) { @@ -3177,7 +3177,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldLongListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); + RealmList fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { if (fieldLongListNotNullItem == null) { @@ -3191,7 +3191,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldLongListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); + RealmList fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { if (fieldLongListNullItem == null) { @@ -3205,7 +3205,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldIntegerListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); + RealmList fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { if (fieldIntegerListNotNullItem == null) { @@ -3219,7 +3219,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldIntegerListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); + RealmList fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { if (fieldIntegerListNullItem == null) { @@ -3233,7 +3233,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldShortListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); + RealmList fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { if (fieldShortListNotNullItem == null) { @@ -3247,7 +3247,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldShortListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); + RealmList fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { if (fieldShortListNullItem == null) { @@ -3261,7 +3261,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldByteListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); + RealmList fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { if (fieldByteListNotNullItem == null) { @@ -3275,7 +3275,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldByteListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); + RealmList fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { if (fieldByteListNullItem == null) { @@ -3289,7 +3289,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDoubleListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); + RealmList fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { if (fieldDoubleListNotNullItem == null) { @@ -3303,7 +3303,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDoubleListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); + RealmList fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { if (fieldDoubleListNullItem == null) { @@ -3317,7 +3317,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldFloatListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); + RealmList fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { if (fieldFloatListNotNullItem == null) { @@ -3331,7 +3331,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldFloatListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); + RealmList fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { if (fieldFloatListNullItem == null) { @@ -3345,7 +3345,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDateListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); + RealmList fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { if (fieldDateListNotNullItem == null) { @@ -3359,7 +3359,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDateListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); + RealmList fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { if (fieldDateListNullItem == null) { @@ -3389,132 +3389,132 @@ public static void insertOrUpdate(Realm realm, Iterator ob } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$fieldStringNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); + String realmGet$fieldStringNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, false); } - String realmGet$fieldStringNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); + String realmGet$fieldStringNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, false); } - Boolean realmGet$fieldBooleanNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); + Boolean realmGet$fieldBooleanNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, false); } - Boolean realmGet$fieldBooleanNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); + Boolean realmGet$fieldBooleanNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, false); } - byte[] realmGet$fieldBytesNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); + byte[] realmGet$fieldBytesNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, false); } - byte[] realmGet$fieldBytesNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); + byte[] realmGet$fieldBytesNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, false); } - Number realmGet$fieldByteNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); + Number realmGet$fieldByteNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, false); } - Number realmGet$fieldByteNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); + Number realmGet$fieldByteNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, false); } - Number realmGet$fieldShortNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); + Number realmGet$fieldShortNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, false); } - Number realmGet$fieldShortNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); + Number realmGet$fieldShortNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, false); } - Number realmGet$fieldIntegerNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); + Number realmGet$fieldIntegerNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, false); } - Number realmGet$fieldIntegerNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); + Number realmGet$fieldIntegerNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, false); } - Number realmGet$fieldLongNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); + Number realmGet$fieldLongNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, false); } - Number realmGet$fieldLongNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); + Number realmGet$fieldLongNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, false); } - Float realmGet$fieldFloatNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); + Float realmGet$fieldFloatNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, false); } - Float realmGet$fieldFloatNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); + Float realmGet$fieldFloatNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, false); } - Double realmGet$fieldDoubleNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); + Double realmGet$fieldDoubleNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, false); } - Double realmGet$fieldDoubleNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); + Double realmGet$fieldDoubleNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, false); } - java.util.Date realmGet$fieldDateNotNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); + java.util.Date realmGet$fieldDateNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, false); } - java.util.Date realmGet$fieldDateNull = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); + java.util.Date realmGet$fieldDateNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, false); } - some.test.NullTypes fieldObjectNullObj = ((NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); + some.test.NullTypes fieldObjectNullObj = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); if (fieldObjectNullObj != null) { Long cachefieldObjectNull = cache.get(fieldObjectNullObj); if (cachefieldObjectNull == null) { - cachefieldObjectNull = NullTypesRealmProxy.insertOrUpdate(realm, fieldObjectNullObj, cache); + cachefieldObjectNull = some_test_NullTypesRealmProxy.insertOrUpdate(realm, fieldObjectNullObj, cache); } Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); } else { @@ -3523,7 +3523,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNotNullIndex); fieldStringListNotNullOsList.removeAll(); - RealmList fieldStringListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); + RealmList fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { if (fieldStringListNotNullItem == null) { @@ -3537,7 +3537,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); fieldStringListNullOsList.removeAll(); - RealmList fieldStringListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); + RealmList fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { if (fieldStringListNullItem == null) { @@ -3551,7 +3551,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); fieldBinaryListNotNullOsList.removeAll(); - RealmList fieldBinaryListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); + RealmList fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { if (fieldBinaryListNotNullItem == null) { @@ -3565,7 +3565,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); fieldBinaryListNullOsList.removeAll(); - RealmList fieldBinaryListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); + RealmList fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { if (fieldBinaryListNullItem == null) { @@ -3579,7 +3579,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); fieldBooleanListNotNullOsList.removeAll(); - RealmList fieldBooleanListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); + RealmList fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { if (fieldBooleanListNotNullItem == null) { @@ -3593,7 +3593,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); fieldBooleanListNullOsList.removeAll(); - RealmList fieldBooleanListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); + RealmList fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { if (fieldBooleanListNullItem == null) { @@ -3607,7 +3607,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); fieldLongListNotNullOsList.removeAll(); - RealmList fieldLongListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); + RealmList fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { if (fieldLongListNotNullItem == null) { @@ -3621,7 +3621,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); fieldLongListNullOsList.removeAll(); - RealmList fieldLongListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); + RealmList fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { if (fieldLongListNullItem == null) { @@ -3635,7 +3635,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); fieldIntegerListNotNullOsList.removeAll(); - RealmList fieldIntegerListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); + RealmList fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { if (fieldIntegerListNotNullItem == null) { @@ -3649,7 +3649,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); fieldIntegerListNullOsList.removeAll(); - RealmList fieldIntegerListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); + RealmList fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { if (fieldIntegerListNullItem == null) { @@ -3663,7 +3663,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); fieldShortListNotNullOsList.removeAll(); - RealmList fieldShortListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); + RealmList fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { if (fieldShortListNotNullItem == null) { @@ -3677,7 +3677,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); fieldShortListNullOsList.removeAll(); - RealmList fieldShortListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); + RealmList fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { if (fieldShortListNullItem == null) { @@ -3691,7 +3691,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); fieldByteListNotNullOsList.removeAll(); - RealmList fieldByteListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); + RealmList fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { if (fieldByteListNotNullItem == null) { @@ -3705,7 +3705,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); fieldByteListNullOsList.removeAll(); - RealmList fieldByteListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); + RealmList fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { if (fieldByteListNullItem == null) { @@ -3719,7 +3719,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); fieldDoubleListNotNullOsList.removeAll(); - RealmList fieldDoubleListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); + RealmList fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { if (fieldDoubleListNotNullItem == null) { @@ -3733,7 +3733,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); fieldDoubleListNullOsList.removeAll(); - RealmList fieldDoubleListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); + RealmList fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { if (fieldDoubleListNullItem == null) { @@ -3747,7 +3747,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); fieldFloatListNotNullOsList.removeAll(); - RealmList fieldFloatListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); + RealmList fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { if (fieldFloatListNotNullItem == null) { @@ -3761,7 +3761,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); fieldFloatListNullOsList.removeAll(); - RealmList fieldFloatListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); + RealmList fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { if (fieldFloatListNullItem == null) { @@ -3775,7 +3775,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); fieldDateListNotNullOsList.removeAll(); - RealmList fieldDateListNotNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); + RealmList fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { if (fieldDateListNotNullItem == null) { @@ -3789,7 +3789,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); fieldDateListNullOsList.removeAll(); - RealmList fieldDateListNullList = ((NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); + RealmList fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { if (fieldDateListNullItem == null) { @@ -3820,8 +3820,8 @@ public static some.test.NullTypes createDetachedCopy(some.test.NullTypes realmOb unmanagedObject = (some.test.NullTypes) cachedObject.object; cachedObject.minDepth = currentDepth; } - NullTypesRealmProxyInterface unmanagedCopy = (NullTypesRealmProxyInterface) unmanagedObject; - NullTypesRealmProxyInterface realmSource = (NullTypesRealmProxyInterface) realmObject; + some_test_NullTypesRealmProxyInterface unmanagedCopy = (some_test_NullTypesRealmProxyInterface) unmanagedObject; + some_test_NullTypesRealmProxyInterface realmSource = (some_test_NullTypesRealmProxyInterface) realmObject; unmanagedCopy.realmSet$fieldStringNotNull(realmSource.realmGet$fieldStringNotNull()); unmanagedCopy.realmSet$fieldStringNull(realmSource.realmGet$fieldStringNull()); unmanagedCopy.realmSet$fieldBooleanNotNull(realmSource.realmGet$fieldBooleanNotNull()); @@ -3844,7 +3844,7 @@ public static some.test.NullTypes createDetachedCopy(some.test.NullTypes realmOb unmanagedCopy.realmSet$fieldDateNull(realmSource.realmGet$fieldDateNull()); // Deep copy of fieldObjectNull - unmanagedCopy.realmSet$fieldObjectNull(NullTypesRealmProxy.createDetachedCopy(realmSource.realmGet$fieldObjectNull(), currentDepth + 1, maxDepth, cache)); + unmanagedCopy.realmSet$fieldObjectNull(some_test_NullTypesRealmProxy.createDetachedCopy(realmSource.realmGet$fieldObjectNull(), currentDepth + 1, maxDepth, cache)); unmanagedCopy.realmSet$fieldStringListNotNull(new RealmList()); unmanagedCopy.realmGet$fieldStringListNotNull().addAll(realmSource.realmGet$fieldStringListNotNull()); @@ -4105,7 +4105,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - NullTypesRealmProxy aNullTypes = (NullTypesRealmProxy)o; + some_test_NullTypesRealmProxy aNullTypes = (some_test_NullTypesRealmProxy)o; String path = proxyState.getRealm$realm().getPath(); String otherPath = aNullTypes.proxyState.getRealm$realm().getPath(); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java similarity index 90% rename from realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java rename to realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java index 1039391788..fd591f4bc5 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java @@ -31,8 +31,8 @@ import org.json.JSONObject; @SuppressWarnings("all") -public class SimpleRealmProxy extends some.test.Simple - implements RealmObjectProxy, SimpleRealmProxyInterface { +public class some_test_SimpleRealmProxy extends some.test.Simple + implements RealmObjectProxy, some_test_SimpleRealmProxyInterface { static final class SimpleColumnInfo extends ColumnInfo { long nameIndex; @@ -69,7 +69,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private SimpleColumnInfo columnInfo; private ProxyState proxyState; - SimpleRealmProxy() { + some_test_SimpleRealmProxy() { proxyState.setConstructionFinished(); } @@ -164,7 +164,7 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb final List excludeFields = Collections. emptyList(); some.test.Simple obj = realm.createObjectInternal(some.test.Simple.class, true, excludeFields); - final SimpleRealmProxyInterface objProxy = (SimpleRealmProxyInterface) obj; + final some_test_SimpleRealmProxyInterface objProxy = (some_test_SimpleRealmProxyInterface) obj; if (json.has("name")) { if (json.isNull("name")) { objProxy.realmSet$name(null); @@ -187,7 +187,7 @@ public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONOb public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader reader) throws IOException { final some.test.Simple obj = new some.test.Simple(); - final SimpleRealmProxyInterface objProxy = (SimpleRealmProxyInterface) obj; + final some_test_SimpleRealmProxyInterface objProxy = (some_test_SimpleRealmProxyInterface) obj; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); @@ -243,8 +243,8 @@ public static some.test.Simple copy(Realm realm, some.test.Simple newObject, boo some.test.Simple realmObject = realm.createObjectInternal(some.test.Simple.class, false, Collections.emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); - SimpleRealmProxyInterface realmObjectSource = (SimpleRealmProxyInterface) newObject; - SimpleRealmProxyInterface realmObjectCopy = (SimpleRealmProxyInterface) realmObject; + some_test_SimpleRealmProxyInterface realmObjectSource = (some_test_SimpleRealmProxyInterface) newObject; + some_test_SimpleRealmProxyInterface realmObjectCopy = (some_test_SimpleRealmProxyInterface) realmObject; realmObjectCopy.realmSet$name(realmObjectSource.realmGet$name()); realmObjectCopy.realmSet$age(realmObjectSource.realmGet$age()); @@ -260,11 +260,11 @@ public static long insert(Realm realm, some.test.Simple object, Map objects, M } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$name = ((SimpleRealmProxyInterface) object).realmGet$name(); + String realmGet$name = ((some_test_SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface) object).realmGet$age(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); } } @@ -301,13 +301,13 @@ public static long insertOrUpdate(Realm realm, some.test.Simple object, Map ob } long rowIndex = OsObject.createRow(table); cache.put(object, rowIndex); - String realmGet$name = ((SimpleRealmProxyInterface) object).realmGet$name(); + String realmGet$name = ((some_test_SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); } else { Table.nativeSetNull(tableNativePtr, columnInfo.nameIndex, rowIndex, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((SimpleRealmProxyInterface) object).realmGet$age(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); } } @@ -354,8 +354,8 @@ public static some.test.Simple createDetachedCopy(some.test.Simple realmObject, unmanagedObject = (some.test.Simple) cachedObject.object; cachedObject.minDepth = currentDepth; } - SimpleRealmProxyInterface unmanagedCopy = (SimpleRealmProxyInterface) unmanagedObject; - SimpleRealmProxyInterface realmSource = (SimpleRealmProxyInterface) realmObject; + some_test_SimpleRealmProxyInterface unmanagedCopy = (some_test_SimpleRealmProxyInterface) unmanagedObject; + some_test_SimpleRealmProxyInterface realmSource = (some_test_SimpleRealmProxyInterface) realmObject; unmanagedCopy.realmSet$name(realmSource.realmGet$name()); unmanagedCopy.realmSet$age(realmSource.realmGet$age()); diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkSelfReference.java b/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkSelfReference.java new file mode 100644 index 0000000000..600db68589 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkSelfReference.java @@ -0,0 +1,15 @@ +package some.test; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; + +public class BacklinkSelfReference extends RealmObject { + + public String id; + public BacklinkSelfReference self; + + @LinkingObjects("self") + final RealmResults parents = null; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkSource.java b/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkSource.java new file mode 100644 index 0000000000..c86d3daaff --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkSource.java @@ -0,0 +1,10 @@ +package some.test; + +import io.realm.RealmList; +import io.realm.RealmObject; + +public class BacklinkSource extends RealmObject { + private String id; + private BacklinkTarget child; + private RealmList children; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkTarget.java b/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkTarget.java index 40632c1a7d..715f57d2cd 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkTarget.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/BacklinkTarget.java @@ -1,12 +1,15 @@ package some.test; -import io.realm.RealmList; import io.realm.RealmObject; import io.realm.RealmResults; import io.realm.annotations.LinkingObjects; public class BacklinkTarget extends RealmObject { - private String id; - private Backlinks child; - private RealmList children; + private int id; + + @LinkingObjects("child") + private final RealmResults simpleParents = null; + + @LinkingObjects("children") + private final RealmResults listParents = null; } diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks.java b/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks.java deleted file mode 100644 index 8ad6f6e435..0000000000 --- a/realm/realm-annotations-processor/src/test/resources/some/test/Backlinks.java +++ /dev/null @@ -1,16 +0,0 @@ -package some.test; - -import io.realm.RealmList; -import io.realm.RealmObject; -import io.realm.RealmResults; -import io.realm.annotations.LinkingObjects; - -public class Backlinks extends RealmObject { - private int id; - - @LinkingObjects("child") - private final RealmResults simpleParents = null; - - @LinkingObjects("children") - private final RealmResults listParents = null; -} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/conflict/BacklinkSelfReference.java b/realm/realm-annotations-processor/src/test/resources/some/test/conflict/BacklinkSelfReference.java new file mode 100644 index 0000000000..99a9fa2113 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/conflict/BacklinkSelfReference.java @@ -0,0 +1,12 @@ +package some.test.conflict; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; + +// Test Backlink resolution when there is simple class name conflicts, but not internal name conflicts. +@RealmClass(name = "!BacklinkSelfReference") +public class BacklinkSelfReference extends RealmObject { + public String name; +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java index 01b6920726..02ef67f017 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java @@ -59,10 +59,10 @@ public void tearDown() { @Test public void copyColumnInfoFrom_checkIndex() { - CatRealmProxy.CatColumnInfo sourceColumnInfo - = (CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); - CatRealmProxy.CatColumnInfo targetColumnInfo - = (CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); + io_realm_entities_CatRealmProxy.CatColumnInfo sourceColumnInfo + = (io_realm_entities_CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); + io_realm_entities_CatRealmProxy.CatColumnInfo targetColumnInfo + = (io_realm_entities_CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); // Checks precondition. assertNotSame(sourceColumnInfo, targetColumnInfo); @@ -100,8 +100,8 @@ public void copyColumnInfoFrom_checkIndex() { @Test public void copy_differentInstanceSameValues() { - final CatRealmProxy.CatColumnInfo columnInfo - = (CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); + final io_realm_entities_CatRealmProxy.CatColumnInfo columnInfo + = (io_realm_entities_CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); columnInfo.nameIndex = 1; columnInfo.ageIndex = 2; @@ -112,7 +112,7 @@ public void copy_differentInstanceSameValues() { columnInfo.ownerIndex = 7; columnInfo.scaredOfDogIndex = 8; - CatRealmProxy.CatColumnInfo copy = (CatRealmProxy.CatColumnInfo) columnInfo.copy(true); + io_realm_entities_CatRealmProxy.CatColumnInfo copy = (io_realm_entities_CatRealmProxy.CatColumnInfo) columnInfo.copy(true); // verify that the copy is identical assertNotSame(columnInfo, copy); @@ -149,10 +149,10 @@ public void copy_differentInstanceSameValues() { @Test public void copy_immutableThrows() { - final CatRealmProxy.CatColumnInfo original - = (CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); + final io_realm_entities_CatRealmProxy.CatColumnInfo original + = (io_realm_entities_CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); - CatRealmProxy.CatColumnInfo copy = (CatRealmProxy.CatColumnInfo) original.copy(false); + io_realm_entities_CatRealmProxy.CatColumnInfo copy = (io_realm_entities_CatRealmProxy.CatColumnInfo) original.copy(false); try { copy.copyFrom(original); fail("Attempt to copy to an immutable ColumnInfo should throwS"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java index 7646e91645..8158986c35 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java @@ -60,7 +60,7 @@ public void tearDown() { @Test public void createColumnInfo_noDuplicateIndexInIndexFields() { RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); - CatRealmProxy.CatColumnInfo columnInfo = (CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); + io_realm_entities_CatRealmProxy.CatColumnInfo columnInfo = (io_realm_entities_CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); final Set indexSet = new HashSet(); int indexCount = 0; @@ -88,8 +88,8 @@ public void createColumnInfo_noDuplicateIndexInIndexFields() { @Test public void createColumnInfo_noDuplicateIndexInIndicesMap() { RealmProxyMediator mediator = realm.getConfiguration().getSchemaMediator(); - CatRealmProxy.CatColumnInfo columnInfo; - columnInfo = (CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); + io_realm_entities_CatRealmProxy.CatColumnInfo columnInfo; + columnInfo = (io_realm_entities_CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); final Set indexSet = new HashSet(); int indexCount = 0; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index f1490c163c..4f2442b72e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -4128,8 +4128,8 @@ public void nonAdditiveSchemaChangesWhenTypedRealmExists() throws InterruptedExc .name("schemaChangeTest") .build(); Realm realm = Realm.getInstance(realmConfig); - StringOnlyRealmProxy.StringOnlyColumnInfo columnInfo - = (StringOnlyRealmProxy.StringOnlyColumnInfo) realm.getSchema().getColumnInfo(StringOnly.class); + io_realm_entities_StringOnlyRealmProxy.StringOnlyColumnInfo columnInfo + = (io_realm_entities_StringOnlyRealmProxy.StringOnlyColumnInfo) realm.getSchema().getColumnInfo(StringOnly.class); assertEquals(0, columnInfo.charsIndex); realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/conflict/AllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/conflict/AllJavaTypes.java new file mode 100644 index 0000000000..ef31fc30c5 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/conflict/AllJavaTypes.java @@ -0,0 +1,26 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.entities.conflict; + +import io.realm.RealmObject; +import io.realm.annotations.RealmClass; + +// Potential conflict with `io.realm.entities.AllJavaTypes` but proxy classes should be generated +// using the internal name (Which do not conflict) +@RealmClass(name = "!AllJavaTypes") +public class AllJavaTypes extends RealmObject { + public String name; +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java index dc23ee4f0f..cac92e21df 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java @@ -103,13 +103,13 @@ public Builder addPersistedLinkProperty(String name, RealmFieldType type, String * information in the Realm file's schema. This property type will always be * {@link RealmFieldType#LINKING_OBJECTS}. * - * @param name the name of the link property. - * @param targetClassname The class name of the property links to. - * @param targetFieldName The field name of the property links to. + * @param name the name of the property . + * @param sourceClass The class name of the the class linking to this class, ie. the source class. + * @param sourceClassName The field name in the source class that links to this class. * @return this {@code OsObjectSchemaInfo.Builder}. */ - public Builder addComputedLinkProperty(String name, String targetClassname, String targetFieldName) { - long propertyPtr = Property.nativeCreateComputedLinkProperty(name, targetClassname, targetFieldName); + public Builder addComputedLinkProperty(String name, String sourceClass, String sourceClassName) { + long propertyPtr = Property.nativeCreateComputedLinkProperty(name, sourceClass, sourceClassName); computedPropertyPtrArray[computedPropertyPtrCurPos] = propertyPtr; computedPropertyPtrCurPos++; return this; From 060a73b362f082fab1fa53ee90fc6d7f0424a7b7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 5 Feb 2018 17:37:12 +0000 Subject: [PATCH 1173/2110] Fixes #5677 (#5741) * Fixes #5677 - removed session network listener since it's redundant with the SyncManager one --- CHANGELOG.md | 2 +- .../java/io/realm/SyncSession.java | 28 ++++--------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a43735164d..e013ff5e23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Bug Fixes * Added missing `RealmQuery.oneOf()` for Kotlin that accepts non-nullable types (#5717). - +* [ObjectServer] Fixed an issue preventing sync to resume when the network is back (#5677). ## 4.3.3 (2018-01-19) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 5ed82d663f..30ff8c8ad2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -70,7 +70,6 @@ public class SyncSession { private final SyncConfiguration configuration; private final ErrorHandler errorHandler; private RealmAsyncTask networkRequest; - private NetworkStateReceiver.ConnectionListener networkListener; private RealmAsyncTask refreshTokenTask; private RealmAsyncTask refreshTokenNetworkRequest; private AtomicBoolean onGoingAccessTokenQuery = new AtomicBoolean(false); @@ -516,30 +515,11 @@ String getAccessToken(final AuthenticationServer authServer, String refreshToken getUser().setRefreshToken(newRefreshToken); } } catch (JSONException e) { - RealmLog.error(e,"Session[%s]: Can not parse the refresh_token into a valid JSONObject: ", configuration.getPath()); + RealmLog.error(e, "Session[%s]: Can not parse the refresh_token into a valid JSONObject: ", configuration.getPath()); } } - if (!onGoingAccessTokenQuery.getAndSet(true)) { - if (NetworkStateReceiver.isOnline(SyncObjectServerFacade.getApplicationContext())) { - authenticateRealm(authServer); - - } else { - // Wait for connection to become available, before trying again. - // The Session might potentially stay in this state for the lifetime of the application. - // This is acceptable. - networkListener = new NetworkStateReceiver.ConnectionListener() { - @Override - public void onChange(boolean connectionAvailable) { - if (connectionAvailable) { - if (!onGoingAccessTokenQuery.getAndSet(true)) { - authenticateRealm(authServer); - } - NetworkStateReceiver.removeListener(this); - } - } - }; - NetworkStateReceiver.addListener(networkListener); - } + if (!onGoingAccessTokenQuery.get() && NetworkStateReceiver.isOnline(SyncObjectServerFacade.getApplicationContext())) { + authenticateRealm(authServer); } } return null; @@ -552,6 +532,7 @@ private void authenticateRealm(final AuthenticationServer authServer) { } clearScheduledAccessTokenRefresh(); + onGoingAccessTokenQuery.set(true); // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { @Override @@ -601,6 +582,7 @@ protected void onError(AuthenticateResponse response) { } private void scheduleRefreshAccessToken(final AuthenticationServer authServer, long expireDateInMs) { + onGoingAccessTokenQuery.set(true); // calculate the delay time before which we should refresh the access_token, // we adjust to 10 second to proactively refresh the access_token before the session // hit the expire date on the token From a9134bf4f79f947aae82fd6f51ce260645e476ce Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 6 Feb 2018 14:46:10 +0000 Subject: [PATCH 1174/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e013ff5e23..461c004f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.3.4 (YYYY-MM-DD) +## 4.3.4 (2018-02-06) ## Bug Fixes From e67d1f42db58914ca45e94b2f9f0b41b9dc92d19 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 6 Feb 2018 14:46:14 +0000 Subject: [PATCH 1175/2110] Release v4.3.4 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 932e22fb7f..a6695ff98b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.4-SNAPSHOT \ No newline at end of file +4.3.4 \ No newline at end of file From ee5969f16afc0aa5a209fce02f27d5c2c3d735e6 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 6 Feb 2018 14:46:14 +0000 Subject: [PATCH 1176/2110] Prepare next release v4.3.5-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a6695ff98b..212811e11f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.3.4 \ No newline at end of file +4.3.5-SNAPSHOT \ No newline at end of file From f7594b65fb354bfee4ca14e6b6ca909028a9f133 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 21 Feb 2018 12:35:04 +0100 Subject: [PATCH 1177/2110] Use proper link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b2a3f20be..7fb11c6441 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This repository holds the source code for the Java version of Realm, which curre ## Getting Started -Please see the [detailed instructions in our docs](https://realm.io/docs/java/#installation) to add Realm to your project. +Please see the [detailed instructions in our docs](https://realm.io/docs/java/latest/#installation) to add Realm to your project. ## Documentation From 018baa05fc0cbe59b6c60d3cc8ed40619e0a9988 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 26 Feb 2018 14:26:11 +0100 Subject: [PATCH 1178/2110] Support internal names for types in other compilation units (#5764) --- .../realm/examples/appmodules/model/Pig.java | 6 +++ .../io/realm/processor/ModuleMetaData.java | 25 +++++++-- .../io/realm/processor/RealmProcessor.java | 2 +- .../processor/RealmProxyClassGenerator.java | 21 ++++++-- .../main/java/io/realm/processor/Utils.java | 51 ++++++++++++++++++- .../realm/some_test_AllTypesRealmProxy.java | 4 ++ .../realm/some_test_BooleansRealmProxy.java | 4 ++ ...amePolicyMixedClassSettingsRealmProxy.java | 4 ++ ...st_NamePolicyModuleDefaultsRealmProxy.java | 4 ++ .../realm/some_test_NullTypesRealmProxy.java | 4 ++ .../io/realm/some_test_SimpleRealmProxy.java | 4 ++ 11 files changed, 117 insertions(+), 12 deletions(-) diff --git a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Pig.java b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Pig.java index ef9e5ba860..e2cb83eb1b 100644 --- a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Pig.java +++ b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Pig.java @@ -16,12 +16,18 @@ package io.realm.examples.appmodules.model; +import io.realm.RealmList; import io.realm.RealmObject; +import io.realm.examples.librarymodules.model.Dog; public class Pig extends RealmObject { private String name; + // It is possible for model classes to to reference library model classes as long + // as they all are included in the schema when opening the Realm. + private RealmList afraidOf = new RealmList<>(); + public String getName() { return name; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java index aa4ef4ad8c..d9488d92e6 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java @@ -102,19 +102,19 @@ public boolean preProcess(Set moduleClasses) { } // Check that allClasses and classes are not set at the same time - RealmModule moduleAnnoation = classElement.getAnnotation(RealmModule.class); + RealmModule moduleAnnotation = classElement.getAnnotation(RealmModule.class); Utils.note("Processing module " + classSimpleName); - if (moduleAnnoation.allClasses() && hasCustomClassList(classElement)) { + if (moduleAnnotation.allClasses() && hasCustomClassList(classElement)) { Utils.error("Setting @RealmModule(allClasses=true) will override @RealmModule(classes={...}) in " + classSimpleName); return false; } // Validate that naming policies are correctly configured. - if (!validateNamingPolicies(globalModuleInfo, classSpecificModuleInfo, (TypeElement) classElement, moduleAnnoation)) { + if (!validateNamingPolicies(globalModuleInfo, classSpecificModuleInfo, (TypeElement) classElement, moduleAnnotation)) { return false; } - moduleAnnotations.put(((TypeElement) classElement).getQualifiedName().toString(), moduleAnnoation); + moduleAnnotations.put(((TypeElement) classElement).getQualifiedName().toString(), moduleAnnotation); } return true; @@ -236,7 +236,22 @@ public boolean postProcess(ClassCollection modelClasses) { // Check that app and library modules are not mixed if (modules.size() > 0 && libraryModules.size() > 0) { - Utils.error("Normal modules and library modules cannot be mixed in the same project"); + StringBuilder sb = new StringBuilder(); + sb.append("Normal modules and library modules cannot be mixed in the same project."); + sb.append('\n'); + sb.append("Normal module(s):\n"); + for (String module : modules.keySet()) { + sb.append(" "); + sb.append(module); + sb.append('\n'); + } + sb.append("Library module(s):\n"); + for (String module : libraryModules.keySet()) { + sb.append(" "); + sb.append(module); + sb.append('\n'); + } + Utils.error(sb.toString()); return false; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java index c557e866a3..9edeea9902 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java @@ -209,7 +209,7 @@ private boolean preProcessModules(RoundEnvironment roundEnv) { return moduleMetaData.preProcess(roundEnv.getElementsAnnotatedWith(RealmModule.class)); } - // Returns true of modules where succesfully validated, false otherwise + // Returns true of modules where successfully validated, false otherwise private boolean postProcessModules() { return moduleMetaData.postProcess(classCollection); } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 7f154623c4..ef1e771df3 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -745,15 +745,15 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce } case OBJECT: { String fieldTypeQualifiedName = Utils.getFieldTypeQualifiedName(field); - String internalClassName = classCollection.getClassFromQualifiedName(fieldTypeQualifiedName).getInternalClassName(); - writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.OBJECT, \"%s\")", + String internalClassName = Utils.getReferencedTypeInternalClassNameStatement(fieldTypeQualifiedName, classCollection); + writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.OBJECT, %s)", fieldName, internalClassName); break; } case LIST: { String genericTypeQualifiedName = Utils.getGenericTypeQualifiedName(field); - String internalClassName = classCollection.getClassFromQualifiedName(genericTypeQualifiedName).getInternalClassName(); // FIXME support for raw data - writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.LIST, \"%s\")", + String internalClassName = Utils.getReferencedTypeInternalClassNameStatement(genericTypeQualifiedName, classCollection); + writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.LIST, %s)", fieldName, internalClassName); break; } @@ -795,6 +795,8 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce } } for (Backlink backlink: metadata.getBacklinkFields()) { + // Backlinks can only be created between classes in the current round of annotation processing + // as the forward link cannot be created unless you know the type already. ClassMetaData sourceClass = classCollection.getClassFromQualifiedName(backlink.getSourceClass()); String targetField = backlink.getTargetField(); // Only in the model, so no internal name exists String internalSourceField = sourceClass.getInternalFieldName(backlink.getSourceField()); @@ -838,6 +840,17 @@ private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException .emitStatement("return \"%s\"", internalClassName) .endMethod() .emitEmptyLine(); + + // Helper class for the annotation processor so it can access the internal class name + // without needing to load the parent class (which we cannot do as it transitively loads + // native code, which cannot be loaded on the JVM). + writer.beginType( + "ClassNameHelper", // full qualified name of the item to generate + "class", // the type of the item + EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)); // modifiers to apply + writer.emitField("String", "INTERNAL_CLASS_NAME", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL), "\""+ internalClassName+"\""); + writer.endType(); + writer.emitEmptyLine(); } //@formatter:on diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java index 9b0e4309e0..85b92a64ac 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java @@ -1,5 +1,8 @@ package io.realm.processor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.List; import javax.annotation.processing.Messager; @@ -19,12 +22,11 @@ import io.realm.annotations.RealmNamingPolicy; import io.realm.processor.nameconverter.CamelCaseConverter; +import io.realm.processor.nameconverter.IdentityConverter; import io.realm.processor.nameconverter.LowerCaseWithSeparatorConverter; import io.realm.processor.nameconverter.NameConverter; -import io.realm.processor.nameconverter.IdentityConverter; import io.realm.processor.nameconverter.PascalCaseConverter; - /** * Utility methods working with the Realm processor. */ @@ -355,4 +357,49 @@ public static NameConverter getNameFormatter(RealmNamingPolicy policy) { } } + /** + * Tries to find the internal class name for a referenced type. In model classes this can + * happen with either direct object references or using `RealmList` or `RealmResults`. + *

            + * This name is required by schema builders that operate on internal names and not the public ones. + *

            + * Finding the internal name is easy if the referenced type is included in the current round + * of annotation processing. In that case the internal name was also calculated in the same round + *

            + * If the referenced type was already compiled, e.g being included from library, then we need + * to get the name from the proxy class. Fortunately ProGuard should not have obfuscated any + * class files at this point, meaning we can look it up dynamically. + *

            + * If a name is looked up using the class loader, it also means that developers need to + * combine a library and app module of model classes at runtime in the RealmConfiguration, but + * this should be a valid use case. + * + * @param qualifiedClassName type to lookup the internal name for. + * @param classCollection collection of classes found in the current round of annotation processing. + * @throws IllegalArgumentException If the internal name could not be looked up + * @return the statement that evalutes to the internal class name. This will either be a string + * constant or a reference to a static field in another class. In both cases, the return result + * should not be put in quotes. + */ + public static String getReferencedTypeInternalClassNameStatement(String qualifiedClassName, ClassCollection classCollection) { + + // Attempt to lookup internal name in current round + if (classCollection.containsQualifiedClass(qualifiedClassName)) { + ClassMetaData metadata = classCollection.getClassFromQualifiedName(qualifiedClassName); + return "\"" + metadata.getInternalClassName() + "\""; + } + + // If we cannot find the name in the current processor round, we have to defer resolving the + // name to runtime. The reason being that proxy classes in libraries on the classpath + // might already have been obfuscated, which means we have no easy way of finding them. + // + // Doing it this way unfortunately means that if the class is not on the apps classpath + // a rather obscure class-not-found exception will be thrown, but since this is probably + // a very niche use case that is acceptable for now. + // + // TODO: We could probably create an internal annotation like `@InternalName("__Permission")` + // which should make it possible for the annotation processor to read the value from the + // proxy class, even for files in other jar files. + return "io.realm." + Utils.getProxyClassName(qualifiedClassName) + ".ClassNameHelper.INTERNAL_CLASS_NAME"; + } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index 5dcdcd1f45..ee1fccac5a 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -868,6 +868,10 @@ public static String getSimpleClassName() { return "AllTypes"; } + public static final class ClassNameHelper { + public static final String INTERNAL_CLASS_NAME = "AllTypes"; + } + @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java index d2c9f22cff..ac32ff2b20 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java @@ -202,6 +202,10 @@ public static String getSimpleClassName() { return "Booleans"; } + public static final class ClassNameHelper { + public static final String INTERNAL_CLASS_NAME = "Booleans"; + } + @SuppressWarnings("cast") public static some.test.Booleans createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java index eb71397828..b33b615633 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java @@ -166,6 +166,10 @@ public static String getSimpleClassName() { return "customName"; } + public static final class ClassNameHelper { + public static final String INTERNAL_CLASS_NAME = "customName"; + } + @SuppressWarnings("cast") public static some.test.NamePolicyMixedClassSettings createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java index 221ca41710..29a6d97b08 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java @@ -166,6 +166,10 @@ public static String getSimpleClassName() { return "NamePolicyModuleDefaults"; } + public static final class ClassNameHelper { + public static final String INTERNAL_CLASS_NAME = "NamePolicyModuleDefaults"; + } + @SuppressWarnings("cast") public static some.test.NamePolicyModuleDefaults createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index c4c3669172..1e5c65b4a4 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -1707,6 +1707,10 @@ public static String getSimpleClassName() { return "NullTypes"; } + public static final class ClassNameHelper { + public static final String INTERNAL_CLASS_NAME = "NullTypes"; + } + @SuppressWarnings("cast") public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java index fd591f4bc5..c072b532cb 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java @@ -158,6 +158,10 @@ public static String getSimpleClassName() { return "Simple"; } + public static final class ClassNameHelper { + public static final String INTERNAL_CLASS_NAME = "Simple"; + } + @SuppressWarnings("cast") public static some.test.Simple createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { From 6171847cf149733e66ef93dad72b63613e756ce8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 26 Feb 2018 16:57:49 +0100 Subject: [PATCH 1179/2110] Refactor Partial Sync to use Object Store master (#5759) --- CHANGELOG.md | 5 + dependencies.list | 9 +- .../main/java/io/realm/processor/Utils.java | 11 +- .../realm-library/src/main/cpp/CMakeLists.txt | 3 +- .../main/cpp/collection_changeset_wrapper.hpp | 104 -------------- ...o_realm_internal_OsCollectionChangeSet.cpp | 55 ++------ .../main/cpp/io_realm_internal_OsResults.cpp | 6 +- .../cpp/io_realm_internal_OsSharedRealm.cpp | 8 ++ .../io_realm_internal_sync_OsSubscription.cpp | 118 ++++++++++++++++ .../src/main/cpp/java_binding_context.cpp | 18 +++ .../src/main/cpp/java_binding_context.hpp | 3 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../cpp/observable_collection_wrapper.cpp | 54 -------- .../cpp/observable_collection_wrapper.hpp | 32 ++--- .../src/main/cpp/subscription_wrapper.hpp | 93 +++++++++++++ .../src/main/java/io/realm/RealmQuery.java | 31 ++++- .../src/main/java/io/realm/RealmResults.java | 24 +--- .../io/realm/internal/EmptyLoadChangeSet.java | 34 ++--- .../io/realm/internal/ObjectServerFacade.java | 4 + .../io/realm/internal/ObserverPairList.java | 2 +- .../realm/internal/OsCollectionChangeSet.java | 56 ++++---- .../java/io/realm/internal/OsResults.java | 31 ++--- .../java/io/realm/internal/OsSharedRealm.java | 9 ++ .../java/io/realm/internal/RealmNotifier.java | 35 ++++- .../internal/SubscriptionAwareOsResults.java | 109 +++++++++++++++ .../realm/internal/sync/OsSubscription.java | 131 ++++++++++++++++++ .../internal/sync/SubscriptionAction.java | 43 ++++++ .../java/io/realm/PermissionManagerTests.java | 1 + .../realm/objectserver/PartialSyncTests.java | 4 +- .../testUtils/java/io/realm/TestHelper.java | 4 +- 30 files changed, 702 insertions(+), 337 deletions(-) delete mode 100644 realm/realm-library/src/main/cpp/collection_changeset_wrapper.hpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp delete mode 100644 realm/realm-library/src/main/cpp/observable_collection_wrapper.cpp create mode 100644 realm/realm-library/src/main/cpp/subscription_wrapper.hpp create mode 100644 realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e435da1391..15a1ded5ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ * [ObjectServer] Added support for partial Realms. Read [here](https://realm.io/docs/java/latest/#partial-realms) for more information. * Added two new methods to `OrderedCollectionChangeSet`: `getState()` and `getError()` (#5619). +### Internal + +* Upgraded to Realm Sync 3.0.0-beta.6 +* Upgraded to Realm Core 5.3.0 + ## 4.4.0 (YYYY-MM-DD) diff --git a/dependencies.list b/dependencies.list index bd4aa3ff41..99cc81bdf5 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,9 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=2.2.9 -REALM_SYNC_SHA256=d770d639d2b187c15e6d0bc798b909f5b424d61444f1f83f9e56f5be43e96afc - +REALM_SYNC_VERSION=3.0.0-beta.6 +REALM_SYNC_SHA256=6704f50fbe64fe7f208376ba570467a4a5526ee158da84714f03f182d3ed0e4d +c # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=2.6.0 - +REALM_OBJECT_SERVER_DE_VERSION=3.0.0-alpha.6 diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java index 85b92a64ac..88d3a47abd 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java @@ -390,12 +390,13 @@ public static String getReferencedTypeInternalClassNameStatement(String qualifie } // If we cannot find the name in the current processor round, we have to defer resolving the - // name to runtime. The reason being that proxy classes in libraries on the classpath - // might already have been obfuscated, which means we have no easy way of finding them. - // + // name to runtime. The reason being that the annotation processor can only access the + // compile type class path using Elements and Types which do not allow us to read + // field values. + // // Doing it this way unfortunately means that if the class is not on the apps classpath - // a rather obscure class-not-found exception will be thrown, but since this is probably - // a very niche use case that is acceptable for now. + // a rather obscure class-not-found exception will be thrown when starting the app, but since + // this is probably a very niche use case that is acceptable for now. // // TODO: We could probably create an internal annotation like `@InternalName("__Permission")` // which should make it possible for the annotation processor to read the value from the diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index e099fe82b7..aa67d94199 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -63,7 +63,7 @@ set(classes_LIST io.realm.internal.OsObjectSchemaInfo io.realm.internal.OsResults io.realm.internal.NativeObjectReference io.realm.internal.OsCollectionChangeSet io.realm.internal.OsObject io.realm.internal.OsRealmConfig io.realm.internal.OsList - io.realm.internal.OsObjectStore + io.realm.internal.OsObjectStore io.realm.internal.sync.OsSubscription ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 @@ -170,6 +170,7 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFileUserStore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_sync_OsSubscription.cpp ) endif() diff --git a/realm/realm-library/src/main/cpp/collection_changeset_wrapper.hpp b/realm/realm-library/src/main/cpp/collection_changeset_wrapper.hpp deleted file mode 100644 index 41a3602327..0000000000 --- a/realm/realm-library/src/main/cpp/collection_changeset_wrapper.hpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -#ifndef REALM_JNI_IMPL_COLLECTION_CHANGESET_WRAPPER_HPP -#define REALM_JNI_IMPL_COLLECTION_CHANGESET_WRAPPER_HPP - -#include "collection_notifications.hpp" -#include "util.hpp" -#include "jni_util/java_class.hpp" -#include "jni_util/java_global_weak_ref.hpp" -#include "jni_util/java_method.hpp" -#include "jni_util/log.hpp" -#include "jni_util/jni_utils.hpp" -#include "jni_util/java_class.hpp" -#include "jni_util/java_method.hpp" -#include "sync/partial_sync.hpp" -#include "object-store/src/subscription_state.hpp" - -#include - -using namespace realm::jni_util; - -namespace realm { -namespace _impl { - -// Wrapper of Object Store CollectionChangeSet -// It is used to better control the mapping between Object Store concepts and Java API's, especially -// when it comes to states and defining errors. -class CollectionChangeSetWrapper { -public: - CollectionChangeSetWrapper(CollectionChangeSet const& changeset, std::string error_message, bool partial_sync_realm) - : m_changeset(changeset) - , m_error_message(error_message) - , m_partial_sync_realm(partial_sync_realm) - { - } - - ~CollectionChangeSetWrapper() = default; - - CollectionChangeSetWrapper(CollectionChangeSetWrapper&&) = delete; - CollectionChangeSetWrapper& operator=(CollectionChangeSetWrapper&&) = delete; - CollectionChangeSetWrapper(CollectionChangeSetWrapper const&) = delete; - CollectionChangeSetWrapper& operator=(CollectionChangeSetWrapper const&) = delete; - - CollectionChangeSet& get() - { - return m_changeset; - }; - - - - jthrowable get_error() { - JNIEnv* env = JniUtils::get_env(false); - if (m_error_message != "") { - static JavaClass realm_exception_class(env, "io/realm/exceptions/RealmException"); - static JavaMethod realm_exception_constructor(env, realm_exception_class, "", "(Ljava/lang/String;)V"); - return (jthrowable) env->NewObject(realm_exception_class, realm_exception_constructor, to_jstring(env, m_error_message)); - } else if (m_changeset.partial_sync_error_message != "") { - // Indicates a soft error, i.e. illegal name of query. - static JavaClass illegal_argument_class(env, "java/lang/IllegalArgumentException"); - static JavaMethod illegal_argument_constructor(env, illegal_argument_class, "", "(Ljava/lang/String;)V"); - return (jthrowable) env->NewObject(illegal_argument_class, illegal_argument_constructor, to_jstring(env, m_changeset.partial_sync_error_message)); - } else { - return nullptr; - } - } - - bool is_remote_data_loaded() { - if (!m_partial_sync_realm) { - return true; - } - - return m_changeset.partial_sync_new_state == partial_sync::SubscriptionState::Initialized - || m_changeset.partial_sync_new_state == partial_sync::SubscriptionState::NotSupported; - } - - bool is_empty() { - return m_changeset.empty() && m_error_message.empty(); - } - -private: - CollectionChangeSet m_changeset; - std::string m_error_message; // From any exception being thrown that are not reported using Partial Sync - bool m_partial_sync_realm; // if true, this Realm supports partial Sync -}; - - -} // namespace realm -} // namespace _impl - -#endif // REALM_JNI_IMPL_COLLECTION_CHANGESET_WRAPPER_HPP diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp index 47a1ab20c6..e79ae3f0d8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "subscription_state.hpp" -#include "collection_changeset_wrapper.hpp" #include "io_realm_internal_OsCollectionChangeSet.h" #include @@ -23,7 +21,6 @@ #include "util.hpp" using namespace realm; -using namespace _impl; static void finalize_changeset(jlong ptr); static jintArray index_set_to_jint_array(JNIEnv* env, const IndexSet& index_set); @@ -32,7 +29,7 @@ static jintArray index_set_to_indices_array(JNIEnv* env, const IndexSet& index_s static void finalize_changeset(jlong ptr) { TR_ENTER_PTR(ptr); - delete reinterpret_cast(ptr); + delete reinterpret_cast(ptr); } static jintArray index_set_to_jint_array(JNIEnv* env, const IndexSet& index_set) @@ -90,67 +87,37 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetFi } JNIEXPORT jintArray JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetRanges(JNIEnv* env, jclass, - jlong native_ptr, jint type) + jlong native_ptr, jint type) { TR_ENTER_PTR(native_ptr) // no throws - auto& change_set = *reinterpret_cast(native_ptr); + auto& change_set = *reinterpret_cast(native_ptr); switch (type) { case io_realm_internal_OsCollectionChangeSet_TYPE_DELETION: - return index_set_to_jint_array(env, change_set.get().deletions); + return index_set_to_jint_array(env, change_set.deletions); case io_realm_internal_OsCollectionChangeSet_TYPE_INSERTION: - return index_set_to_jint_array(env, change_set.get().insertions); + return index_set_to_jint_array(env, change_set.insertions); case io_realm_internal_OsCollectionChangeSet_TYPE_MODIFICATION: - return index_set_to_jint_array(env, change_set.get().modifications_new); + return index_set_to_jint_array(env, change_set.modifications_new); default: REALM_UNREACHABLE(); } } JNIEXPORT jintArray JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetIndices(JNIEnv* env, jclass, - jlong native_ptr, jint type) + jlong native_ptr, jint type) { TR_ENTER_PTR(native_ptr) // no throws - auto& change_set = *reinterpret_cast(native_ptr); + auto& change_set = *reinterpret_cast(native_ptr); switch (type) { case io_realm_internal_OsCollectionChangeSet_TYPE_DELETION: - return index_set_to_indices_array(env, change_set.get().deletions); + return index_set_to_indices_array(env, change_set.deletions); case io_realm_internal_OsCollectionChangeSet_TYPE_INSERTION: - return index_set_to_indices_array(env, change_set.get().insertions); + return index_set_to_indices_array(env, change_set.insertions); case io_realm_internal_OsCollectionChangeSet_TYPE_MODIFICATION: - return index_set_to_indices_array(env, change_set.get().modifications_new); + return index_set_to_indices_array(env, change_set.modifications_new); default: REALM_UNREACHABLE(); } } - -JNIEXPORT jobject JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetError(JNIEnv*, jobject, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - auto& change_set = *reinterpret_cast(native_ptr); - return change_set.get_error(); -} - -JNIEXPORT jboolean Java_io_realm_internal_OsCollectionChangeSet_nativeIsRemoteDataLoaded(JNIEnv*, jobject, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - auto& change_set = *reinterpret_cast(native_ptr); - return change_set.is_remote_data_loaded(); -} - -JNIEXPORT jint JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetOldStatusCode(JNIEnv*, jobject, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - auto& change_set = *reinterpret_cast(native_ptr); - return static_cast(change_set.get().partial_sync_old_state); -} - -JNIEXPORT jint JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetNewStatusCode(JNIEnv*, jobject, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - auto& change_set = *reinterpret_cast(native_ptr); - return static_cast(change_set.get().partial_sync_new_state); -} - -JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeIsEmpty(JNIEnv*, jobject, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - auto& change_set = *reinterpret_cast(native_ptr); - return to_jbool(change_set.is_empty()); -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index c4c69823f0..9c3670b388 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -241,15 +241,13 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeDistinct(JNIEnv* } JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeStartListening(JNIEnv* env, jobject instance, - jlong native_ptr, jstring j_subscription_name) + jlong native_ptr) { TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - JStringAccessor subscription_name(env, j_subscription_name); - auto key = subscription_name.is_null_or_empty() ? util::none : util::Optional(subscription_name); - wrapper->start_listening(env, instance, key); + wrapper->start_listening(env, instance); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index b58f07022d..bb63ca1e2d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -509,3 +509,11 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterSchema java_binding_context.set_schema_changed_callback(env, j_schema_changed_callback); } } + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsPartial(JNIEnv*, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + // No throws + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return to_jbool(shared_realm->is_partial()); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp new file mode 100644 index 0000000000..5f9641eea1 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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 "io_realm_internal_sync_OsSubscription.h" + +#include "java_class_global_def.hpp" +#include "observable_collection_wrapper.hpp" +#include "util.hpp" +#include "subscription_wrapper.hpp" +#include "jni_util/java_class.hpp" +#include "jni_util/java_method.hpp" + +#include +#include + +using namespace realm; +using namespace realm::jni_util; +using namespace realm::_impl; + +typedef ObservableCollectionWrapper ResultsWrapper; + +static void finalize_subscription(jlong ptr); + +static void finalize_subscription(jlong ptr) +{ + TR_ENTER_PTR(ptr); + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeCreate(JNIEnv* env, jclass, jlong results_ptr, jstring j_subscription_name) +{ + TR_ENTER() + try { + const auto results = reinterpret_cast(results_ptr); + JStringAccessor subscription_name(env, j_subscription_name); + auto key = subscription_name.is_null_or_empty() ? util::none : util::Optional(subscription_name); + auto subscription = partial_sync::subscribe(results->collection(), key); + auto wrapper = new SubscriptionWrapper(std::move(subscription)); + return reinterpret_cast(wrapper); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + + +JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetFinalizerPtr(JNIEnv*, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_subscription); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_sync_OsSubscription_nativeStartListening(JNIEnv* env, jobject object, jlong native_ptr) +{ + TR_ENTER() + try { + auto wrapper = reinterpret_cast(native_ptr); + wrapper->start_listening(env, object); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_sync_OsSubscription_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) +{ + TR_ENTER() + try { + auto wrapper = reinterpret_cast(native_ptr); + wrapper->stop_listening(); + } + CATCH_STD() +} + +JNIEXPORT jint JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetState(JNIEnv* env, jclass, jlong native_ptr) +{ + TR_ENTER() + try { + auto wrapper = reinterpret_cast(native_ptr); + return static_cast(wrapper->subscription().state()); + } + CATCH_STD() + return 0; +} + +JNIEXPORT jobject JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetError(JNIEnv* env, jclass, jlong native_ptr) +{ + TR_ENTER() + try { + auto wrapper = reinterpret_cast(native_ptr); + auto err = wrapper->subscription().error(); + if (err) { + std::string error_message = ""; + try { + std::rethrow_exception(err); + } + catch (const std::exception &e) { + error_message = e.what(); + } + + static JavaClass illegal_argument_class(env, "java/lang/IllegalArgumentException"); + static JavaMethod illegal_argument_constructor(env, illegal_argument_class, "", "(Ljava/lang/String;)V"); + return static_cast(env->NewObject(illegal_argument_class, illegal_argument_constructor, to_jstring(env, error_message))); + } + return nullptr; + } + CATCH_STD() + return nullptr; +} diff --git a/realm/realm-library/src/main/cpp/java_binding_context.cpp b/realm/realm-library/src/main/cpp/java_binding_context.cpp index 2406d18498..c924307a6f 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.cpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.cpp @@ -72,3 +72,21 @@ void JavaBindingContext::set_schema_changed_callback(JNIEnv* env, jobject schema { m_schema_changed_callback = JavaGlobalWeakRef(env, schema_changed_callback); } + +void JavaBindingContext::will_send_notifications() { + auto env = JniUtils::get_env(); + m_java_notifier.call_with_local_ref(env, [&](JNIEnv*, jobject notifier_obj) { + static JavaMethod realm_notifier_will_send_notifications(env, JavaClassGlobalDef::realm_notifier(), + "willSendNotifications", "()V"); + env->CallVoidMethod(notifier_obj, realm_notifier_will_send_notifications); + }); +} + +void JavaBindingContext::did_send_notifications() { + auto env = JniUtils::get_env(); + m_java_notifier.call_with_local_ref(env, [&](JNIEnv*, jobject notifier_obj) { + static JavaMethod realm_notifier_did_send_notifications(env, JavaClassGlobalDef::realm_notifier(), + "didSendNotifications", "()V"); + env->CallVoidMethod(notifier_obj, realm_notifier_did_send_notifications); + }); +} diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index 0ff2caea2b..eeff8523b4 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -46,7 +46,8 @@ class JavaBindingContext final : public BindingContext { void did_change(std::vector const& observers, std::vector const& invalidated, bool version_changed = true) override; void schema_did_change(Schema const&) override; - + void will_send_notifications() override; + void did_send_notifications() override; explicit JavaBindingContext(const ConcreteJavaBindContext& concrete_context) : m_java_notifier(concrete_context.jni_env, concrete_context.java_notifier) , m_schema_changed_callback() diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 536262e7ca..61f1031285 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 536262e7ca6a8a871b3904f895d3427440c00c78 +Subproject commit 61f1031285b93bde3a57a4157982d035cad49f42 diff --git a/realm/realm-library/src/main/cpp/observable_collection_wrapper.cpp b/realm/realm-library/src/main/cpp/observable_collection_wrapper.cpp deleted file mode 100644 index 16876740bb..0000000000 --- a/realm/realm-library/src/main/cpp/observable_collection_wrapper.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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 "collection_changeset_wrapper.hpp" -#include "observable_collection_wrapper.hpp" -#include "jni_util/java_class.hpp" -#include "jni_util/java_global_weak_ref.hpp" -#include "jni_util/java_method.hpp" - -#include -#include - -using namespace realm; -using namespace realm::_impl; - -namespace realm { -namespace _impl { - -// Specific override for List that do not support named callbacks for partial sync -template<> -void ObservableCollectionWrapper::start_listening(JNIEnv *env, jobject j_collection_object, util::Optional) { - auto cb = create_callback(env, j_collection_object); - m_notification_token = m_collection.add_notification_callback(cb); -} - -// Specific override for Results that do support named callbacks -template<> -void ObservableCollectionWrapper::start_listening(JNIEnv *env, jobject j_collection_object, - util::Optional subscription_name) { - auto cb = create_callback(env, j_collection_object); - m_notification_token = m_collection.add_notification_callback(cb, subscription_name); -} - -template -void ObservableCollectionWrapper::start_listening(JNIEnv*, jobject, util::Optional) -{ - // Ignore -} - -} // end _impl namespace -} // end realm namespace diff --git a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp index 3d108d67cd..0f6ff53b76 100644 --- a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp +++ b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp @@ -17,7 +17,6 @@ #ifndef REALM_JNI_IMPL_OBSERVABLE_COLLECTION_WRAPPER_HPP #define REALM_JNI_IMPL_OBSERVABLE_COLLECTION_WRAPPER_HPP -#include "collection_changeset_wrapper.hpp" #include "jni_util/java_class.hpp" #include "jni_util/java_global_weak_ref.hpp" #include "jni_util/java_method.hpp" @@ -53,57 +52,48 @@ class ObservableCollectionWrapper { { return m_collection; }; - - void start_listening(JNIEnv* env, jobject j_collection_object, util::Optional subscription_name = util::none); + void start_listening(JNIEnv* env, jobject j_collection_object); void stop_listening(); private: - // Shared logic for creating collection callbacks - CollectionChangeCallback create_callback(JNIEnv *env, jobject j_collection_object); - jni_util::JavaGlobalWeakRef m_collection_weak_ref; NotificationToken m_notification_token; T m_collection; }; template -CollectionChangeCallback ObservableCollectionWrapper::create_callback(JNIEnv *env, jobject j_collection_object) +void ObservableCollectionWrapper::start_listening(JNIEnv* env, jobject j_collection_object) { static jni_util::JavaClass os_results_class(env, "io/realm/internal/ObservableCollection"); - static jni_util::JavaMethod notify_change_listeners(env, os_results_class, - "notifyChangeListeners", "(J)V"); + static jni_util::JavaMethod notify_change_listeners(env, os_results_class, "notifyChangeListeners", "(J)V"); if (!m_collection_weak_ref) { m_collection_weak_ref = jni_util::JavaGlobalWeakRef(env, j_collection_object); } - bool partial_sync_realm = m_collection.get_realm()->is_partial(); - auto cb = [=](CollectionChangeSet const &changes, std::exception_ptr err) { + auto cb = [=](CollectionChangeSet const& changes, std::exception_ptr err) { // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; - std::string error_message = ""; if (err) { try { std::rethrow_exception(err); } - catch (const std::exception &e) { - error_message = e.what(); + catch (const std::exception& e) { + realm::jni_util::Log::e("Caught exception in collection change callback %1", e.what()); + return; } } - m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv *local_env, - jobject collection_obj) { + m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv* local_env, jobject collection_obj) { local_env->CallVoidMethod( - collection_obj, notify_change_listeners, - reinterpret_cast(new CollectionChangeSetWrapper(changes, - error_message, - partial_sync_realm))); + collection_obj, notify_change_listeners, + reinterpret_cast(changes.empty() ? 0 : new CollectionChangeSet(changes))); }); }; - return cb; + m_notification_token = m_collection.add_notification_callback(cb); } template diff --git a/realm/realm-library/src/main/cpp/subscription_wrapper.hpp b/realm/realm-library/src/main/cpp/subscription_wrapper.hpp new file mode 100644 index 0000000000..7c58651637 --- /dev/null +++ b/realm/realm-library/src/main/cpp/subscription_wrapper.hpp @@ -0,0 +1,93 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_IMPL_SUBSCRIPTION_WRAPPER_HPP +#define REALM_JNI_IMPL_SUBSCRIPTION_WRAPPER_HPP + + +#include "jni_util/java_class.hpp" +#include "jni_util/java_global_weak_ref.hpp" +#include "jni_util/java_method.hpp" + +#include +#include + +namespace realm { +namespace _impl { + +// Wrapper of Object Store Subscription +// We need to control the life cycle of Results/List, weak ref of Java OsResults/OsList object and the NotificationToken. +// Wrap all three together, so when the Java OsResults/OsList object gets GCed, all three of them will be invalidated. +class SubscriptionWrapper { +public: + SubscriptionWrapper(partial_sync::Subscription subscription) + : m_subscription_weak_ref(), + m_notification_token(), + m_subscription(std::move(subscription)) + { + } + + ~SubscriptionWrapper() = default; + + SubscriptionWrapper(SubscriptionWrapper &&) = delete; + SubscriptionWrapper &operator=(SubscriptionWrapper &&) = delete; + SubscriptionWrapper(SubscriptionWrapper const &) = delete; + SubscriptionWrapper &operator=(SubscriptionWrapper const &) = delete; + + partial_sync::Subscription& subscription() { + return m_subscription; + }; + + void start_listening(JNIEnv* env, jobject j_subscription_object); + void stop_listening(); + +private: + jni_util::JavaGlobalWeakRef m_subscription_weak_ref; + partial_sync::SubscriptionNotificationToken m_notification_token; + partial_sync::Subscription m_subscription; +}; + +void SubscriptionWrapper::start_listening(JNIEnv *env, jobject j_subscription_object) +{ + static jni_util::JavaClass os_results_class(env, "io/realm/internal/sync/OsSubscription"); + static jni_util::JavaMethod notify_change_listeners(env, os_results_class, "notifyChangeListeners", "()V"); + + if (!m_subscription_weak_ref) { + m_subscription_weak_ref = jni_util::JavaGlobalWeakRef(env, j_subscription_object); + } + + auto cb = [=]() { + // OS will call all notifiers' callback in one run, so check the Java exception first!! + if (env->ExceptionCheck()) + return; + + m_subscription_weak_ref.call_with_local_ref(env, [&](JNIEnv *local_env, jobject subscription_obj) { + local_env->CallVoidMethod(subscription_obj, notify_change_listeners); + }); + }; + + m_notification_token = m_subscription.add_notification_callback(cb); +} + +void SubscriptionWrapper::stop_listening() +{ + m_notification_token = {}; +} + +} // namespace _impl +} // namespace realm + +#endif // REALM_JNI_IMPL_SUBSCRIPTION_WRAPPER_HPP diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 58efc0f991..3c59c9d7dd 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -25,16 +25,19 @@ import io.realm.annotations.Beta; import io.realm.annotations.Required; +import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsList; import io.realm.internal.OsResults; import io.realm.internal.PendingRow; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.SortDescriptor; +import io.realm.internal.SubscriptionAwareOsResults; import io.realm.internal.Table; import io.realm.internal.TableQuery; import io.realm.internal.Util; import io.realm.internal.fields.FieldDescriptor; +import io.realm.internal.sync.SubscriptionAction; /** @@ -1750,7 +1753,7 @@ public long count() { public RealmResults findAll() { realm.checkIfValid(); - return createRealmResults(query, sortDescriptor, distinctDescriptor, true, ""); + return createRealmResults(query, sortDescriptor, distinctDescriptor, true, SubscriptionAction.NO_SUBSCRIPTION); } /** @@ -1770,7 +1773,13 @@ public RealmResults findAllAsync() { realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - return createRealmResults(query, sortDescriptor, distinctDescriptor, false, ""); + SubscriptionAction subscriptionAction; + if (ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(realm.getConfiguration())) { + subscriptionAction = SubscriptionAction.ANONYMOUS_SUBSCRIPTION; + } else { + subscriptionAction = SubscriptionAction.NO_SUBSCRIPTION; + } + return createRealmResults(query, sortDescriptor, distinctDescriptor, false, subscriptionAction); } /** @@ -1793,7 +1802,7 @@ public RealmResults findAllAsync(String subscriptionName) { } realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - return createRealmResults(query, sortDescriptor, distinctDescriptor, false, subscriptionName); + return createRealmResults(query, sortDescriptor, distinctDescriptor, false, SubscriptionAction.create(subscriptionName)); } /** @@ -1989,21 +1998,29 @@ public E findFirstAsync() { return result; } + private RealmResults createRealmResults(TableQuery query, @Nullable SortDescriptor sortDescriptor, @Nullable SortDescriptor distinctDescriptor, boolean loadResults, - String subscriptionName) { + SubscriptionAction subscriptionAction) { RealmResults results; - OsResults osResults = OsResults.createFromQuery(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); + OsResults osResults; + if (subscriptionAction.shouldCreateSubscriptions()) { + osResults = SubscriptionAwareOsResults.createFromQuery(realm.sharedRealm, query, sortDescriptor, distinctDescriptor, subscriptionAction.getName()); + } else { + osResults = OsResults.createFromQuery(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); + } + if (isDynamicQuery()) { - results = new RealmResults<>(realm, osResults, className, subscriptionName); + results = new RealmResults<>(realm, osResults, className); } else { - results = new RealmResults<>(realm, osResults, clazz, subscriptionName); + results = new RealmResults<>(realm, osResults, clazz); } if (loadResults) { results.load(); } + return results; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 1bafb8a5c2..a2596932d2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -20,11 +20,10 @@ import android.annotation.SuppressLint; import android.os.Looper; -import io.reactivex.Flowable; -import io.reactivex.Observable; - import javax.annotation.Nullable; +import io.reactivex.Flowable; +import io.reactivex.Observable; import io.realm.internal.CheckedRow; import io.realm.internal.OsResults; import io.realm.internal.Row; @@ -61,8 +60,6 @@ */ public class RealmResults extends OrderedRealmCollectionImpl { - private final String subscriptionName; - // Called from Realm Proxy classes @SuppressLint("unused") static RealmResults createBacklinkResults(BaseRealm realm, Row row, Class srcTableType, String srcFieldName) { @@ -85,21 +82,11 @@ static RealmResults createDynamicBacklinkResults(DynamicReal } RealmResults(BaseRealm realm, OsResults osResults, Class clazz) { - this(realm, osResults, clazz, ""); - } - - RealmResults(BaseRealm realm, OsResults osResults, String className) { - this(realm, osResults, className, ""); - } - - RealmResults(BaseRealm realm, OsResults osResults, Class clazz, String subscriptionName) { super(realm, osResults, clazz); - this.subscriptionName = subscriptionName; } - RealmResults(BaseRealm realm, OsResults osResults, String className, String subscriptionName) { + RealmResults(BaseRealm realm, OsResults osResults, String className) { super(realm, osResults, className); - this.subscriptionName = subscriptionName; } /** @@ -111,7 +98,6 @@ public RealmQuery where() { return RealmQuery.createQueryFromResult(this); } - /** * {@inheritDoc} */ @@ -184,7 +170,7 @@ public boolean load() { */ public void addChangeListener(RealmChangeListener> listener) { checkForAddRemoveListener(listener, true); - osResults.addListener(this, listener, subscriptionName); + osResults.addListener(this, listener); } /** @@ -222,7 +208,7 @@ public void addChangeListener(RealmChangeListener> listener) { */ public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { checkForAddRemoveListener(listener, true); - osResults.addListener(this, listener, subscriptionName); + osResults.addListener(this, listener); } private void checkForAddRemoveListener(@Nullable Object listener, boolean checkListener) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java index 7253e188e1..ebaa0fc3c5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java @@ -15,19 +15,22 @@ */ package io.realm.internal; +import javax.annotation.Nullable; + import io.realm.RealmResults; +import io.realm.internal.sync.OsSubscription; /** - * Fake changeset used if {@link RealmResults#load()} is called manually. + * Empty changeset used if {@link RealmResults#load()} is called manually or if no collection + * changeset was available but the subscription was updated. */ public class EmptyLoadChangeSet extends OsCollectionChangeSet { private static final int[] NO_INDEX_CHANGES = new int[0]; private static final Range[] NO_RANGE_CHANGES = new Range[0]; - public EmptyLoadChangeSet() { - super(0, true); - // FIXME Read partial sync status from Realm when creating this + public EmptyLoadChangeSet(@Nullable OsSubscription subscription, boolean isPartialRealm) { + super(0, true, subscription, isPartialRealm); } @Override @@ -67,12 +70,15 @@ public Range[] getChangeRanges() { @Override public Throwable getError() { + if (subscription != null && subscription.getState() == OsSubscription.SubscriptionState.ERROR) { + return subscription.getError(); + } return null; } @Override public boolean isRemoteDataLoaded() { - return false; + return super.isRemoteDataLoaded(); } @Override @@ -80,16 +86,6 @@ public boolean isCompleteResult() { return isRemoteDataLoaded(); } - @Override - public int getOldStatusCode() { - return -3; // Undefined - } - - @Override - public int getNewStatusCode() { - return -3; // Undefined - } - @Override public boolean isFirstAsyncCallback() { return super.isFirstAsyncCallback(); @@ -97,7 +93,13 @@ public boolean isFirstAsyncCallback() { @Override public boolean isEmpty() { - return true; + // Since this class represents "No collection" changes, it is only considered empty + // if no partial sync updates are found + if (subscription == null) { + return true; + } else { + return false; + } } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 7a88f6beb4..d3ee69eb6f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -119,4 +119,8 @@ public boolean wasDownloadInterrupted(Throwable throwable) { public boolean isPartialRealm(RealmConfiguration configuration) { return false; } + + public OsResults createSubscriptionAwareResults(OsSharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor, String name) { + throw new IllegalStateException("Should only be called by builds supporting Sync"); + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java index 1a0c65a773..2132485f63 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObserverPairList.java @@ -45,7 +45,7 @@ public abstract static class ObserverPair { // Should only be set by the outer class. To marked it as removed in case it is removed in foreach callback. boolean removed = false; - ObserverPair(T observer, S listener) { + public ObserverPair(T observer, S listener) { this.listener = listener; this.observerRef = new WeakReference(observer); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java index 7f98101f37..44902a5e02 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java @@ -21,7 +21,7 @@ import javax.annotation.Nullable; import io.realm.OrderedCollectionChangeSet; - +import io.realm.internal.sync.OsSubscription; /** * Implementation of {@link OrderedCollectionChangeSet}. This class holds a pointer to the Object Store's @@ -46,10 +46,18 @@ public class OsCollectionChangeSet implements OrderedCollectionChangeSet, Native private static long finalizerPtr = nativeGetFinalizerPtr(); private final long nativePtr; private final boolean firstAsyncCallback; + protected final OsSubscription subscription; + protected final boolean isPartialRealm; public OsCollectionChangeSet(long nativePtr, boolean firstAsyncCallback) { + this(nativePtr, firstAsyncCallback, null, false); + } + + public OsCollectionChangeSet(long nativePtr, boolean firstAsyncCallback, @Nullable OsSubscription subscription, boolean isPartialRealm) { this.nativePtr = nativePtr; this.firstAsyncCallback = firstAsyncCallback; + this.subscription = subscription; + this.isPartialRealm = isPartialRealm; NativeContext.dummyContext.addReference(this); } @@ -108,7 +116,10 @@ public Range[] getChangeRanges() { @Override public Throwable getError() { - return (Throwable) nativeGetError(nativePtr); + if (subscription != null && subscription.getState() == OsSubscription.SubscriptionState.ERROR) { + return subscription.getError(); + } + return null; } @Override @@ -117,15 +128,15 @@ public boolean isCompleteResult() { } public boolean isRemoteDataLoaded() { - return nativeIsRemoteDataLoaded(nativePtr); - } - - public int getOldStatusCode() { - return nativeGetOldStatusCode(nativePtr); - } - - public int getNewStatusCode() { - return nativeGetNewStatusCode(nativePtr); + if (!isPartialRealm) { + return true; + } else if (subscription == null) { + // This will in some cases return false positives, like adding change listeners + // to synchronous queries. For now this is acceptable. + return false; + } else { + return subscription.getState() == OsSubscription.SubscriptionState.COMPLETE; + } } /** @@ -140,7 +151,9 @@ public boolean isFirstAsyncCallback() { * Returns {@code true} if this changeset is empty, and doesn't contain any relevant changes. */ public boolean isEmpty() { - return nativeIsEmpty(nativePtr); + // Since this wrap a Object Store changeset, it will always contains changes if an + // Object Store changeset exists. + return nativePtr == 0; } // Convert long array returned by the nativeGetXxxRanges() to Range array. @@ -189,25 +202,6 @@ public long getNativeFinalizerPtr() { return finalizerPtr; } - // Returns the underlying error if an error was detected. - // The underlying layer will wrap it in an appropropriate exception class. - // `null` is returned if no error is present - @Nullable - private native Object nativeGetError(long nativePtr); - - private native int nativeGetOldStatusCode(long nativePtr); - - private native int nativeGetNewStatusCode(long nativePtr); - - // Returns true if the data described by the subscription has been downloaded to the device, - // false if not. In either case, the query is run against the local dataset. - private native boolean nativeIsRemoteDataLoaded(long nativePtr); - - /** - * Returns {@code true} if this changeset is empty, and doesn't contain any relevant changes. - */ - private native boolean nativeIsEmpty(long nativePtr); - private native static long nativeGetFinalizerPtr(); // Returns the ranges as a long array. eg.: [startIndex1, length1, startIndex2, length2, ...] diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 451b40d176..55690afbd1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -16,14 +16,17 @@ package io.realm.internal; +import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Date; +import java.util.List; import java.util.NoSuchElementException; import javax.annotation.Nullable; import io.realm.OrderedRealmCollectionChangeListener; import io.realm.RealmChangeListener; +import io.realm.internal.sync.OsSubscription; /** @@ -202,9 +205,10 @@ public void set(@Nullable T object) { private final OsSharedRealm sharedRealm; private final NativeContext context; private final Table table; - private boolean loaded; + protected boolean loaded; private boolean isSnapshot = false; - private final ObserverPairList observerPairs = + + protected final ObserverPairList observerPairs = new ObserverPairList(); // Public for static checking in JNI @@ -397,23 +401,15 @@ public boolean deleteLast() { } public void addListener(T observer, OrderedRealmCollectionChangeListener listener) { - addListener(observer, listener, ""); - } - - public void addListener(T observer, OrderedRealmCollectionChangeListener listener, String subscriptionName) { if (observerPairs.isEmpty()) { - nativeStartListening(nativePtr, subscriptionName); + nativeStartListening(nativePtr); } CollectionObserverPair collectionObserverPair = new CollectionObserverPair(observer, listener); observerPairs.add(collectionObserverPair); } public void addListener(T observer, RealmChangeListener listener) { - addListener(observer, new RealmChangeListenerWrapper(listener), ""); - } - - public void addListener(T observer, RealmChangeListener listener, String subscriptionName) { - addListener(observer, new RealmChangeListenerWrapper(listener), subscriptionName); + addListener(observer, new RealmChangeListenerWrapper(listener)); } public void removeListener(T observer, OrderedRealmCollectionChangeListener listener) { @@ -442,8 +438,8 @@ public void notifyChangeListeners(long nativeChangeSetPtr) { // Object Store compute the change set between the SharedGroup versions when the query created and the latest. // So it is possible it deliver a non-empty change set for the first async query returns. OsCollectionChangeSet changeset = (nativeChangeSetPtr == 0) - ? new EmptyLoadChangeSet() - : new OsCollectionChangeSet(nativeChangeSetPtr, !isLoaded()); + ? new EmptyLoadChangeSet(null, sharedRealm.isPartial()) + : new OsCollectionChangeSet(nativeChangeSetPtr, !isLoaded(), null, sharedRealm.isPartial()); // Happens e.g. if a synchronous query is created, a change listener is added and then // a transaction is started on the same thread. This will trigger all notifications @@ -484,8 +480,8 @@ public void load() { private static native long nativeGetFinalizerPtr(); - private static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, - @Nullable SortDescriptor sortDesc, @Nullable SortDescriptor distinctDesc); + protected static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, + @Nullable SortDescriptor sortDesc, @Nullable SortDescriptor distinctDesc); private static native long nativeCreateSnapshot(long nativePtr); @@ -514,7 +510,7 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native void nativeDelete(long nativePtr, long index); // Non-static, we need this OsResults object in JNI. - private native void nativeStartListening(long nativePtr, String subscriptionName); + private native void nativeStartListening(long nativePtr); private native void nativeStopListening(long nativePtr); @@ -529,4 +525,5 @@ private static native long nativeCreateResults(long sharedRealmNativePtr, long q private static native long nativeCreateResultsFromBacklinks(long sharedRealmNativePtr, long rowNativePtr, long srcTableNativePtr, long srColIndex); private static native void nativeEvaluateQueryIfNeeded(long nativePtr, boolean wantsNotifications); + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index ae94ae0969..4a5fcc9e4f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -426,6 +426,13 @@ public void registerSchemaChangedCallback(SchemaChangedCallback callback) { nativeRegisterSchemaChangedCallback(nativePtr, callback); } + /** + * Returns {@code true} if this Realm is a partially synchronized Realm. + */ + public boolean isPartial() { + return nativeIsPartial(nativePtr); + } + // addIterator(), detachIterators() and invalidateIterators() are used to make RealmResults stable iterators work. // The iterator will iterate on a snapshot Results if it is accessed inside a transaction. // See https://github.com/realm/realm-java/issues/3883 for more information. @@ -572,4 +579,6 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare private static native void nativeRegisterSchemaChangedCallback(long nativePtr, SchemaChangedCallback callback); + private static native boolean nativeIsPartial(long nativePtr); + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index cec9d4acdd..96e4f78481 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -88,6 +88,13 @@ protected RealmNotifier(@Nullable OsSharedRealm sharedRealm) { // This list is NOT supposed to be thread safe! private List transactionCallbacks = new ArrayList(); + // List of runnables called when Object Store is about to start sending out notifications about + // a version update for the current thread. + private List startSendingNotificationsCallbacks = new ArrayList<>(); + + // List of runnables called when Object Store has finished sending out notifications for the + // version of the Realm on this thread. + private List finishedSendingNotificationsCallbacks = new ArrayList<>(); // Called from JavaBindingContext::did_change. // This will be called in the caller thread when: @@ -124,16 +131,34 @@ void beforeNotify() { sharedRealm.invalidateIterators(); } + // Called from JavaBindingContext::will_send_notifications + // This will be called before any change notifications are delivered when updating a + // Realm version. This will be triggered even if no change listeners are registered. + void willSendNotifications() { + for (int i = 0; i < startSendingNotificationsCallbacks.size(); i++) { + startSendingNotificationsCallbacks.get(i).run(); + } + } + + // Called from JavaBindingContext::will_send_notifications + void didSendNotifications() { + for (int i = 0; i < startSendingNotificationsCallbacks.size(); i++) { + finishedSendingNotificationsCallbacks.get(i).run(); + } + } + /** * Called when close OsSharedRealm to clean up any event left in to queue. */ @Override public void close() { removeAllChangeListeners(); + startSendingNotificationsCallbacks.clear(); + finishedSendingNotificationsCallbacks.clear(); } public void addChangeListener(T observer, RealmChangeListener realmChangeListener) { - RealmObserverPair observerPair = new RealmObserverPair(observer, realmChangeListener); + RealmObserverPair observerPair = new RealmObserverPair<>(observer, realmChangeListener); realmObserverPairs.add(observerPair); } @@ -165,4 +190,12 @@ public void addTransactionCallback(Runnable runnable) { public int getListenersListSize() { return realmObserverPairs.size(); } + + public void addBeginSendingNotificationsCallback(Runnable runnable) { + startSendingNotificationsCallbacks.add(runnable); + } + + public void addFinishedSendingNotificationsCallback(Runnable runnable) { + finishedSendingNotificationsCallbacks.add(runnable); + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java new file mode 100644 index 0000000000..f9451e99dc --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java @@ -0,0 +1,109 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.internal; + +import javax.annotation.Nullable; + +import io.realm.RealmChangeListener; +import io.realm.internal.sync.OsSubscription; + +/** + * Wrapper around Object Stores Results class that is capable of combining partial sync Subscription + * state updates and collection change updates. + */ +public class SubscriptionAwareOsResults extends OsResults { + + private final String subscriptionName; + // The native ptr to a delayed notification. Since Java group all notifications for each + // RealmResults, only one change from OS will ever be sent. + private long delayedNotificationPtr = 0; + // If true, the subscription somehow changed during this round of notifications being sent + private boolean subscriptionChanged; + // Reference to a (potential) underlying subscription + private OsSubscription subscription = null; + private boolean collectionChanged = false; + + public static SubscriptionAwareOsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query, + @Nullable SortDescriptor sortDescriptor, + @Nullable SortDescriptor distinctDescriptor, + String subscriptionName) { + query.validateQuery(); + long ptr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), sortDescriptor, distinctDescriptor); + return new SubscriptionAwareOsResults(sharedRealm, query.getTable(), ptr, subscriptionName); + } + + SubscriptionAwareOsResults(OsSharedRealm sharedRealm, Table table, long nativePtr, String subscriptionName) { + super(sharedRealm, table, nativePtr); + + this.subscriptionName = subscriptionName; + this.subscription = new OsSubscription(this, subscriptionName); + this.subscription.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(Object o) { + subscriptionChanged = true; + } + }); + RealmNotifier notifier = sharedRealm.realmNotifier; + notifier.addBeginSendingNotificationsCallback(new Runnable() { + @Override + public void run() { + subscriptionChanged = false; + collectionChanged = false; + delayedNotificationPtr = 0; + } + }); + notifier.addFinishedSendingNotificationsCallback(new Runnable() { + @Override + public void run() { + if (collectionChanged || subscriptionChanged) { + triggerDelayedChangeListener(); + } + } + }); + } + + private void triggerDelayedChangeListener() { + // Object Store compute the change set between the SharedGroup versions when the query created and the latest. + // So it is possible it deliver a non-empty change set for the first async query returns. + OsCollectionChangeSet changeset; + // Only parse on Subscription if it changed + OsSubscription subscription = (subscriptionChanged) ? this.subscription : null; + if (delayedNotificationPtr == 0) { + changeset = new EmptyLoadChangeSet(subscription, true); + } else { + changeset = new OsCollectionChangeSet(delayedNotificationPtr, !isLoaded(), subscription, true); + } + + // Happens e.g. if a synchronous query is created, a change listener is added and then + // a transaction is started on the same thread. This will trigger all notifications + // and deliver an empty changeset. + if (changeset.isEmpty() && isLoaded()) { + return; + } + loaded = true; + observerPairs.foreach(new Callback(changeset)); + } + + @Override + public void notifyChangeListeners(long nativeChangeSetPtr) { + collectionChanged = true; + delayedNotificationPtr = nativeChangeSetPtr; + } + +} + + diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java b/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java new file mode 100644 index 0000000000..608f91f601 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java @@ -0,0 +1,131 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.internal.sync; + +import javax.annotation.Nullable; + +import io.realm.RealmChangeListener; +import io.realm.internal.KeepMember; +import io.realm.internal.NativeObject; +import io.realm.internal.ObserverPairList; +import io.realm.internal.OsResults; + +public class OsSubscription implements NativeObject { + + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); + + // Mirrors the values in https://github.com/realm/realm-object-store/blob/master/src/sync/subscription_state.hpp + public enum SubscriptionState { + ERROR(-1), // An error occurred while creating or processing the partial sync subscription. + CREATING(2), // The subscription is being created. + PENDING(0), // The subscription was created, but has not yet been processed by the sync server. + COMPLETE(1), // The subscription has been processed by the sync server and data is being synced to the device. + INVALIDATED(3); // The subscription has been removed. + + private final int val; + + SubscriptionState(int val) { + this.val = val; + } + + public static SubscriptionState fromInternalValue(int val) { + for (SubscriptionState subscriptionState : values()) { + if (subscriptionState.val == val) { + return subscriptionState; + } + } + throw new IllegalArgumentException("Unknown value: " + val); + } + } + + private static class SubscriptionObserverPair + extends ObserverPairList.ObserverPair> { + public SubscriptionObserverPair(OsSubscription observer, RealmChangeListener listener) { + super(observer, listener); + } + + public void onChange(OsSubscription observer) { + listener.onChange(observer); + } + } + + private static class Callback implements ObserverPairList.Callback { + @Override + public void onCalled(SubscriptionObserverPair pair, Object observer) { + pair.onChange((OsSubscription) observer); + } + } + + private final long nativePtr; + protected final ObserverPairList observerPairs = new ObserverPairList<>(); + + public OsSubscription(OsResults results, String subscriptionName) { + this.nativePtr = nativeCreate(results.getNativePtr(), subscriptionName); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + public SubscriptionState getState() { + return SubscriptionState.fromInternalValue(nativeGetState(nativePtr)); + } + + @Nullable + public Throwable getError() { + return (Throwable) nativeGetError(nativePtr); + } + + public void addChangeListener(RealmChangeListener listener) { + if (observerPairs.isEmpty()) { + nativeStartListening(nativePtr); + } + observerPairs.add(new SubscriptionObserverPair(this, listener)); + } + + public void removeChangeListener(RealmChangeListener listener) { + observerPairs.remove(this, listener); + if (observerPairs.isEmpty()) { + nativeStopListening(nativePtr); + } + } + + // Called from JNI + @KeepMember + private void notifyChangeListeners() { + observerPairs.foreach(new Callback()); + } + + private static native long nativeCreate(long resultsNativePtr, String subscriptionName); + + private static native long nativeGetFinalizerPtr(); + + private static native int nativeGetState(long nativePtr); + + private static native Object nativeGetError(long nativePtr); + + private native void nativeStartListening(long nativePtr); + + private native void nativeStopListening(long nativePtr); + +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java b/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java new file mode 100644 index 0000000000..72c5ea0d72 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.internal.sync; + +/** + * Wrapper class describing if and how a subscription should be created when creating a query result. + */ +public class SubscriptionAction { + public static final SubscriptionAction NO_SUBSCRIPTION = new SubscriptionAction(null); + public static final SubscriptionAction ANONYMOUS_SUBSCRIPTION = new SubscriptionAction(""); + + public static SubscriptionAction create(String subscriptionName) { + return new SubscriptionAction(subscriptionName); + } + + private final String subscriptionName; + + private SubscriptionAction(String name) { + this.subscriptionName = name; + } + + public boolean shouldCreateSubscriptions() { + return subscriptionName != null; + } + + public String getName() { + return subscriptionName; + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 84c2788de8..95dad7ad87 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -54,6 +54,7 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) +@Ignore // FIXME: Re-enable once Permissions are stable on ROS public class PermissionManagerTests extends StandardIntegrationTest { private SyncUser user; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index 88061cbfdf..c33f7a1c65 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -58,7 +58,7 @@ public void invalidQuery() { if (changeSet.getState() == OrderedCollectionChangeSet.State.ERROR) { assertTrue(changeSet.getError() instanceof IllegalArgumentException); Throwable iae = changeSet.getError(); - assertTrue(iae.getMessage().contains("ERROR: realm::QueryParser: Key path resolution failed")); + assertTrue(iae.getMessage().contains("Querying over backlinks is disabled but backlinks were found")); looperThread.testComplete(); } }); @@ -186,14 +186,12 @@ public void partialSync_namedSubscription_namedConflictThrows() { looperThread.closeAfterTest(realm); RealmResults results1 = realm.where(PartialSyncObjectA.class) - .greaterThan("number", 0) // FIXME: Work-around Query serializer not accepting empty query for now .findAllAsync("my-id"); results1.addChangeListener((results, changeSet) -> { // Ignore. Just used to trigger partial sync path }); RealmResults results2 = realm.where(PartialSyncObjectB.class) - .greaterThan("number", 0) // FIXME: Work-around Query serializer not accepting empty query for now .findAllAsync("my-id"); results2.addChangeListener((results, changeSet) -> { if (changeSet.getState() == OrderedCollectionChangeSet.State.ERROR) { diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index b1d2d2f15a..a765dc60c2 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -1006,10 +1006,10 @@ public static RealmResults newRealmResults( //noinspection TryWithIdenticalCatches try { final Constructor c = RealmResults.class.getDeclaredConstructor( - BaseRealm.class, OsResults.class, Class.class, String.class); + BaseRealm.class, OsResults.class, Class.class); c.setAccessible(true); //noinspection unchecked - return c.newInstance(realm, osResults, tableClass, ""); + return c.newInstance(realm, osResults, tableClass); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InstantiationException e) { From 29721b7b0660d6211c246c45d96930113b40de71 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 26 Feb 2018 20:25:54 +0100 Subject: [PATCH 1180/2110] Non-nullable types are a subclass of nullable types. (#5746) --- .../io/realm/kotlin/RealmQueryExtensions.kt | 159 +----------------- 1 file changed, 9 insertions(+), 150 deletions(-) diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt index b223597f85..6b7f3c73a1 100644 --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt @@ -32,7 +32,7 @@ import java.util.* * empty. */ fun RealmQuery.oneOf(propertyName: String, - value: Array, + value: Array, casing: Case = Case.SENSITIVE): RealmQuery { return this.`in`(propertyName, value, casing) } @@ -48,7 +48,7 @@ fun RealmQuery.oneOf(propertyName: String, * empty. */ fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { + value: Array): RealmQuery { return this.`in`(propertyName, value) } @@ -62,7 +62,7 @@ fun RealmQuery.oneOf(propertyName: String, * empty. */ fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { + value: Array): RealmQuery { return this.`in`(propertyName, value) } @@ -76,7 +76,7 @@ fun RealmQuery.oneOf(propertyName: String, * or empty. */ fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { + value: Array): RealmQuery { return this.`in`(propertyName, value) } @@ -90,7 +90,7 @@ fun RealmQuery.oneOf(propertyName: String, * empty. */ fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { + value: Array): RealmQuery { return this.`in`(propertyName, value) } @@ -104,7 +104,7 @@ fun RealmQuery.oneOf(propertyName: String, * empty. */ fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { + value: Array): RealmQuery { return this.`in`(propertyName, value) } @@ -119,7 +119,7 @@ fun RealmQuery.oneOf(propertyName: String, * empty. */ fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { + value: Array): RealmQuery { return this.`in`(propertyName, value) } @@ -134,7 +134,7 @@ fun RealmQuery.oneOf(propertyName: String, * or empty. */ fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { + value: Array): RealmQuery { return this.`in`(propertyName, value) } @@ -148,147 +148,6 @@ fun RealmQuery.oneOf(propertyName: String, * empty. */ fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { + value: Array): RealmQuery { return this.`in`(propertyName, value) } - -/** - * In comparison. This allows you to test if objects match any value in an array of values. - * - * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. - * @param casing how casing is handled. [Case.INSENSITIVE] works only for the Latin-1 characters. - * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a String field or `values` is `null` or - * empty. - */ -@JvmName("nonNullOneOf") -fun RealmQuery.oneOf(propertyName: String, - value: Array, - casing: Case = Case.SENSITIVE): RealmQuery { - return this.`in`(propertyName, value, casing) -} - - -/** - * In comparison. This allows you to test if objects match any value in an array of values. - * - * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. - * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Byte field or `values` is `null` or - * empty. - */ -@JvmName("nonNullOneOf") -fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { - return this.`in`(propertyName, value) -} - -/** - * In comparison. This allows you to test if objects match any value in an array of values. - * - * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. - * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Short field or `values` is `null` or - * empty. - */ -@JvmName("nonNullOneOf") -fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { - return this.`in`(propertyName, value) -} - -/** - * In comparison. This allows you to test if objects match any value in an array of values. - * - * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. - * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Integer field or `values` is `null` - * or empty. - */ -@JvmName("nonNullOneOf") -fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { - return this.`in`(propertyName, value) -} - -/** - * In comparison. This allows you to test if objects match any value in an array of values. - * - * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. - * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Long field or `values` is `null` or - * empty. - */ -@JvmName("nonNullOneOf") -fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { - return this.`in`(propertyName, value) -} - -/** - * In comparison. This allows you to test if objects match any value in an array of values. - * - * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. - * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Double field or `values` is `null` or - * empty. - */ -@JvmName("nonNullOneOf") -fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { - return this.`in`(propertyName, value) -} - - -/** - * In comparison. This allows you to test if objects match any value in an array of values. - * - * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. - * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Float field or `values` is `null` or - * empty. - */ -@JvmName("nonNullOneOf") -fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { - return this.`in`(propertyName, value) -} - - -/** - * In comparison. This allows you to test if objects match any value in an array of values. - * - * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. - * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Boolean field or `values` is `null` - * or empty. - */ -@JvmName("nonNullOneOf") -fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { - return this.`in`(propertyName, value) -} - -/** - * In comparison. This allows you to test if objects match any value in an array of values. - * - * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. - * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Date field or `values` is `null` or - * empty. - */ -@JvmName("nonNullOneOf") -fun RealmQuery.oneOf(propertyName: String, - value: Array): RealmQuery { - return this.`in`(propertyName, value) -} - From 4ef4fb4b6de5b23a771d22a34be67431e5efeca0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 27 Feb 2018 10:50:23 +0100 Subject: [PATCH 1181/2110] Use OS/master --- realm/realm-library/src/main/cpp/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 61f1031285..703c391c64 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 61f1031285b93bde3a57a4157982d035cad49f42 +Subproject commit 703c391c64f73c812358940993942338f1bc706d From dbf1d39666c650a15354952feb51edb4b2f00dda Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 1 Mar 2018 14:19:51 +0100 Subject: [PATCH 1182/2110] Support clearing data using Partial Sync (#5781) --- .../java/io/realm/util/SyncTestUtils.java | 17 ++++++++++ .../src/main/cpp/io_realm_internal_Table.cpp | 8 +++-- .../src/main/java/io/realm/BaseRealm.java | 3 +- .../src/main/java/io/realm/DynamicRealm.java | 2 +- .../src/main/java/io/realm/Realm.java | 2 +- .../main/java/io/realm/internal/Table.java | 8 +++-- .../realm/objectserver/PartialSyncTests.java | 31 +++++++++++++++---- 7 files changed, 57 insertions(+), 14 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java index 97caaa18bd..61a1dbd3a0 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java @@ -25,7 +25,10 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; +import io.realm.Realm; +import io.realm.SyncConfiguration; import io.realm.SyncManager; +import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.UserStore; import io.realm.internal.network.AuthenticateResponse; @@ -125,4 +128,18 @@ private static void addToUserStore(SyncUser user) { throw new AssertionError(e); } } + + // Fully synchronize a Realm with the server by making sure that all changes are uploaded + // and downloaded again. + public static void syncRealm(Realm realm) { + SyncConfiguration config = (SyncConfiguration) realm.getConfiguration(); + SyncSession session = SyncManager.getSession(config); + try { + session.uploadAllLocalChanges(); + session.downloadAllServerChanges(); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + realm.refresh(); + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 4f79cc4b7d..d85ad5e1d9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -537,13 +537,17 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSize(JNIEnv* env, job return static_cast(TBL(nativeTablePtr)->size()); // noexcept } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear(JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear(JNIEnv* env, jobject, jlong nativeTablePtr, jboolean is_partial_realm) { if (!TABLE_VALID(env, TBL(nativeTablePtr))) { return; } try { - TBL(nativeTablePtr)->clear(); + if (is_partial_realm) { + TBL(nativeTablePtr)->where().find_all().clear(RemoveMode::unordered); + } else { + TBL(nativeTablePtr)->clear(); + } } CATCH_STD() } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 96c5e8e86b..176758f880 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -611,8 +611,9 @@ E get(@Nullable Class clazz, @Nullable String dynamicC */ public void deleteAll() { checkIfValid(); + boolean isPartialRealm = sharedRealm.isPartial(); for (RealmObjectSchema objectSchema : getSchema().getAll()) { - getSchema().getTable(objectSchema.getClassName()).clear(); + getSchema().getTable(objectSchema.getClassName()).clear(isPartialRealm); } } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index c62b5268d6..ea52f977fc 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -227,7 +227,7 @@ public void removeAllChangeListeners() { public void delete(String className) { checkIfValid(); checkIfInTransaction(); - schema.getTable(className).clear(); + schema.getTable(className).clear(sharedRealm.isPartial()); } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index a2d1974d2f..7f5eb30a6b 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1586,7 +1586,7 @@ public void run() { */ public void delete(Class clazz) { checkIfValid(); - schema.getTable(clazz).clear(); + schema.getTable(clazz).clear(sharedRealm.isPartial()); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 8be0dc7078..533603d665 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -261,10 +261,12 @@ public boolean isEmpty() { /** * Clears the table i.e., deleting all rows in the table. + * + * If using partial sync, this method will behave similarly to 'findAll().deleteFromRealm()'. */ - public void clear() { + public void clear(boolean partialRealm) { checkImmutable(); - nativeClear(nativePtr); + nativeClear(nativePtr, partialRealm); } // Column Information. @@ -724,7 +726,7 @@ public static String getTableNameForClass(String name) { private native long nativeSize(long nativeTablePtr); - private native void nativeClear(long nativeTablePtr); + private native void nativeClear(long nativeTablePtr, boolean partialRealm); private native long nativeGetColumnCount(long nativeTablePtr); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index c33f7a1c65..439cf14774 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -5,11 +5,8 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.atomic.AtomicInteger; - import io.realm.DynamicRealm; import io.realm.OrderedCollectionChangeSet; -import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmList; @@ -21,18 +18,16 @@ import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.Dog; -import io.realm.exceptions.RealmException; -import io.realm.log.RealmLog; import io.realm.objectserver.model.PartialSyncModule; import io.realm.objectserver.model.PartialSyncObjectA; import io.realm.objectserver.model.PartialSyncObjectB; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; +import io.realm.util.SyncTestUtils; import static org.hamcrest.number.OrderingComparison.greaterThan; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -274,6 +269,30 @@ public void onError(String subscriptionName, Throwable error) { }); } + @Test + @RunTestInLooperThread + public void clearTable() throws InterruptedException { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + Realm realm = getPartialRealm(user); + looperThread.closeAfterTest(realm); + + // Create test data and make sure it is uploaded to the server + RealmResults result = realm.where(PartialSyncObjectA.class).findAllAsync(); + realm.executeTransaction(r -> { + r.createObject(PartialSyncObjectA.class).setString("ObjectA"); + }); + SyncTestUtils.syncRealm(realm); + assertEquals(1, result.size()); + + // Delete data and make sure it is accepted by the server + realm.executeTransaction(r -> { + r.delete(PartialSyncObjectA.class); + }); + SyncTestUtils.syncRealm(realm); + assertTrue(result.isEmpty()); + looperThread.testComplete(); + } + private Realm getPartialRealm(SyncUser user) { final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .name("partialSync") From 14c95f3865f3fbbe7728da55c982e73bed27c83d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 1 Mar 2018 15:18:32 +0100 Subject: [PATCH 1183/2110] Disable Realm.delete* when using partial sync (#5782) --- .../java/io/realm/SyncedRealmTests.java | 33 ++++++++++++++++++- .../src/main/java/io/realm/BaseRealm.java | 16 +++++++-- .../src/main/java/io/realm/DynamicRealm.java | 5 +++ .../src/main/java/io/realm/Realm.java | 6 +++- .../realm/objectserver/PartialSyncTests.java | 6 +++- 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index 8d9cfea755..6c6cab9b26 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -18,12 +18,12 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import io.realm.objectserver.model.PartialSyncObjectA; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.util.SyncTestUtils; @@ -165,4 +165,35 @@ public void onError(String subscriptionName, Throwable error) { looperThread.testComplete(); } + + @Test + public void delete_throws() { + realm = getPartialRealm(); + realm.beginTransaction(); + try { + realm.deleteAll(); + fail(); + } catch (IllegalStateException e) { + } + + try { + realm.delete(PartialSyncObjectA.class); + fail(); + } catch (IllegalStateException e) { + } + realm.cancelTransaction(); + + DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); + try { + dynamicRealm.beginTransaction(); + try { + dynamicRealm.delete(PartialSyncObjectA.class.getSimpleName()); + fail(); + } catch (IllegalStateException e) { + } + } finally { + dynamicRealm.close(); + } + } + } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 176758f880..fe3645bc1e 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -64,8 +64,12 @@ abstract class BaseRealm implements Closeable { "This Realm instance has already been closed, making it unusable."; private static final String NOT_IN_TRANSACTION_MESSAGE = "Changing Realm data can only be done from inside a transaction."; - static final String LISTENER_NOT_ALLOWED_MESSAGE = "Listeners cannot be used on current thread."; - + static final String LISTENER_NOT_ALLOWED_MESSAGE = + "Listeners cannot be used on current thread."; + static final String DELETE_NOT_SUPPORTED_UNDER_PARTIAL_SYNC = + "This API is not supported by partially " + + "synchronized Realms. Either unsubscribe using 'Realm.unsubscribeAsync()' or " + + "delete the objects using a query and 'RealmResults.deleteAllFromRealm()'"; static volatile Context applicationContext; @@ -606,11 +610,17 @@ E get(@Nullable Class clazz, @Nullable String dynamicC /** * Deletes all objects from this Realm. + *

            + * If the Realm is a partially synchronized Realm, all subscriptions will be cleared as well. * - * @throws IllegalStateException if the corresponding Realm is closed or called from an incorrect thread. + * @throws IllegalStateException if the corresponding Realm is a partially synchronized Realm, is + * closed or called from an incorrect thread. */ public void deleteAll() { checkIfValid(); + if (sharedRealm.isPartial()) { + throw new IllegalStateException(DELETE_NOT_SUPPORTED_UNDER_PARTIAL_SYNC); + } boolean isPartialRealm = sharedRealm.isPartial(); for (RealmObjectSchema objectSchema : getSchema().getAll()) { getSchema().getTable(objectSchema.getClassName()).clear(isPartialRealm); diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index ea52f977fc..b60043a377 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -223,10 +223,15 @@ public void removeAllChangeListeners() { * Deletes all objects of the specified class from the Realm. * * @param className the class for which all objects should be removed. + * @throws IllegalStateException if the corresponding Realm is a partially synchronized Realm, is + * closed or called from an incorrect thread. */ public void delete(String className) { checkIfValid(); checkIfInTransaction(); + if (sharedRealm.isPartial()) { + throw new IllegalStateException(DELETE_NOT_SUPPORTED_UNDER_PARTIAL_SYNC); + } schema.getTable(className).clear(sharedRealm.isPartial()); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 7f5eb30a6b..282ee8665f 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1582,10 +1582,14 @@ public void run() { * Deletes all objects of the specified class from the Realm. * * @param clazz the class which objects should be removed. - * @throws IllegalStateException if the corresponding Realm is closed or called from an incorrect thread. + * @throws IllegalStateException if the corresponding Realm is a partially synchronized Realm, is + * closed or called from an incorrect thread. */ public void delete(Class clazz) { checkIfValid(); + if (sharedRealm.isPartial()) { + throw new IllegalStateException(DELETE_NOT_SUPPORTED_UNDER_PARTIAL_SYNC); + } schema.getTable(clazz).clear(sharedRealm.isPartial()); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index 439cf14774..4224dc8e6a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -286,7 +286,11 @@ public void clearTable() throws InterruptedException { // Delete data and make sure it is accepted by the server realm.executeTransaction(r -> { - r.delete(PartialSyncObjectA.class); + // TODO the API's that actual use the clearTable instruction have all been disabled for now + // and are throwing IllegalStateException (realm.delete(Class) and realm.deleteAll). + // Keep the test for time being, but use the recommend workaround for deleting objects + // instead. + r.where(PartialSyncObjectA.class).findAll().deleteAllFromRealm(); }); SyncTestUtils.syncRealm(realm); assertTrue(result.isEmpty()); From 7d08fa406d81f50d2ceb42cfbd0b47a7d3999743 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 1 Mar 2018 22:31:31 +0100 Subject: [PATCH 1184/2110] Add support for Object Level Permissions (#5729) --- CHANGELOG.md | 5 + dependencies.list | 6 +- realm/config/findbugs/findbugs-filter.xml | 15 + realm/realm-library/build.gradle | 12 +- .../proguard-rules-build-common.pro | 2 + .../proguard-rules-build-objectServer.pro | 2 + ...e.pro => proguard-rules-consumer-base.pro} | 1 + ...pro => proguard-rules-consumer-common.pro} | 0 ... proguard-rules-consumer-objectServer.pro} | 0 .../java/io/realm/RealmCacheTests.java | 3 + .../io/realm/RealmConfigurationTests.java | 125 ++-- .../java/io/realm/RealmInterprocessTest.java | 11 +- .../androidTest/java/io/realm/RealmTests.java | 10 +- .../io/realm/ObjectLevelPermissionsTest.java | 528 ++++++++++++++++ .../java/io/realm/SessionTests.java | 11 +- .../java/io/realm/SyncConfigurationTests.java | 5 +- .../java/io/realm/SyncManagerTests.java | 2 + .../java/io/realm/SyncUserTests.java | 5 +- .../realm/TestSyncConfigurationFactory.java | 2 + .../cpp/io_realm_internal_OsSharedRealm.cpp | 38 ++ realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/BaseRealm.java | 44 +- .../src/main/java/io/realm/DynamicRealm.java | 86 ++- .../src/main/java/io/realm/Realm.java | 76 +++ .../java/io/realm/RealmConfiguration.java | 25 +- .../src/main/java/io/realm/RealmQuery.java | 7 +- .../io/realm/internal/EmptyLoadChangeSet.java | 4 + .../io/realm/internal/ObjectServerFacade.java | 5 +- .../java/io/realm/internal/OsSharedRealm.java | 23 + .../internal/SubscriptionAwareOsResults.java | 30 +- .../internal/annotations/ObjectServer.java | 32 + .../internal/modules/CompositeMediator.java | 3 +- .../sync/permissions/ClassPermissions.java | 92 +++ .../sync/permissions/ClassPrivileges.java | 158 +++++ .../sync/permissions/ObjectPrivileges.java | 136 +++++ .../io/realm/sync/permissions/Permission.java | 573 ++++++++++++++++++ .../sync/permissions/PermissionUser.java | 75 +++ .../sync/permissions/RealmPermissions.java | 51 ++ .../sync/permissions/RealmPrivileges.java | 159 +++++ .../java/io/realm/sync/permissions/Role.java | 113 ++++ .../realm/sync/permissions/package-info.java | 18 + .../java/io/realm/PermissionManager.java | 2 +- .../java/io/realm/SyncConfiguration.java | 53 +- .../internal/SyncObjectServerFacade.java | 6 + .../permissions/ObjectPermissionsModule.java | 18 + .../realm/permissions/PermissionRequest.java | 4 +- .../java/io/realm/BaseIntegrationTest.java | 2 + .../java/io/realm/PermissionManagerTests.java | 10 +- .../java/io/realm/SyncSessionTests.java | 4 +- .../java/io/realm/objectserver/AuthTests.java | 16 +- ...ObjectLevelPermissionIntegrationTests.java | 291 +++++++++ .../objectserver/ProcessCommitTests.java | 32 +- .../objectserver/model/PermissionObject.java | 26 + .../realm/objectserver/utils/Constants.java | 1 + .../realm/objectserver/utils/UserFactory.java | 15 +- .../realm/entities/AllTypesModelModule.java | 24 + .../rule/TestRealmConfigurationFactory.java | 7 +- .../realm/services/RemoteProcessService.java | 10 +- 58 files changed, 2871 insertions(+), 145 deletions(-) create mode 100644 realm/realm-library/proguard-rules-build-common.pro create mode 100644 realm/realm-library/proguard-rules-build-objectServer.pro rename realm/realm-library/{proguard-rules-base.pro => proguard-rules-consumer-base.pro} (99%) rename realm/realm-library/{proguard-rules-common.pro => proguard-rules-consumer-common.pro} (100%) rename realm/realm-library/{proguard-rules-objectServer.pro => proguard-rules-consumer-objectServer.pro} (100%) create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/annotations/ObjectServer.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPrivileges.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/ObjectPrivileges.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPrivileges.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java create mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/package-info.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PermissionObject.java create mode 100644 realm/realm-library/src/testUtils/java/io/realm/entities/AllTypesModelModule.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 15a1ded5ec..e014431ca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 5.0.0 (YYYY-MM-DD) +### Known Bugs + +* API's marked @ObjectServer are shipped as part of the base binary, they should only be available when enabling synchronized Realms. + ### Breaking Changes * The `OrderedCollectionChangeSet` parameter in `OrderedRealmCollectionChangeListener.onChange()` is no longer nullable. Use `changeSet.getState()` instead (#5619). @@ -10,6 +14,7 @@ ### Enhancements * [ObjectServer] Added support for partial Realms. Read [here](https://realm.io/docs/java/latest/#partial-realms) for more information. +* [ObjectServer] Added support for Object Level Permissions (requires partial synchronized Realms). Read [here](https://realm.io/docs/java/latest/#partial-realms) for more information. * Added two new methods to `OrderedCollectionChangeSet`: `getState()` and `getError()` (#5619). ### Internal diff --git a/dependencies.list b/dependencies.list index 99cc81bdf5..993ad77a5c 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.0.0-beta.6 -REALM_SYNC_SHA256=6704f50fbe64fe7f208376ba570467a4a5526ee158da84714f03f182d3ed0e4d +REALM_SYNC_VERSION=3.0.0-beta.10 +REALM_SYNC_SHA256=7c36a38c0e5c0a46b22d5eee2b494bd9cc0219a526087a040ada86332f13401d c # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=3.0.0-alpha.6 +REALM_OBJECT_SERVER_DE_VERSION=3.0.0-alpha.8 diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index 279dd332d8..eb4b1c96ff 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -24,5 +24,20 @@ + + + + + + + + + + + + + + + diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 86f675551b..20fa873053 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -92,6 +92,12 @@ android { debug { // FIXME: If enabled, crashes with https://issuetracker.google.com/issues/37116868 testCoverageEnabled = false + // minifyEnabled = true; + // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + + release { + // minifyEnabled = true; } } @@ -132,7 +138,8 @@ android { arguments "-DREALM_FLAVOR=base" } } - consumerProguardFiles 'proguard-rules-common.pro', 'proguard-rules-base.pro' + consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-base.pro' + proguardFiles 'proguard-rules-build-common.pro' } objectServer { dimension 'api' @@ -141,7 +148,8 @@ android { arguments "-DREALM_FLAVOR=objectServer" } } - consumerProguardFiles 'proguard-rules-common.pro', 'proguard-rules-objectServer.pro' + consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-objectServer.pro' + proguardFiles 'proguard-rules-build-common.pro', 'proguard-rules-build-objectServer.pro' } } diff --git a/realm/realm-library/proguard-rules-build-common.pro b/realm/realm-library/proguard-rules-build-common.pro new file mode 100644 index 0000000000..b5ca18e12a --- /dev/null +++ b/realm/realm-library/proguard-rules-build-common.pro @@ -0,0 +1,2 @@ +# Common proguard configuration for building the Base and ObjectServer variants +# Note: This is for _building the Realm library, not for consuming it. diff --git a/realm/realm-library/proguard-rules-build-objectServer.pro b/realm/realm-library/proguard-rules-build-objectServer.pro new file mode 100644 index 0000000000..09a9d548f6 --- /dev/null +++ b/realm/realm-library/proguard-rules-build-objectServer.pro @@ -0,0 +1,2 @@ +# Proguard configuration specific for building the ObjectServer variant. +# Note: This is for _building the Realm library, not for consuming it. diff --git a/realm/realm-library/proguard-rules-base.pro b/realm/realm-library/proguard-rules-consumer-base.pro similarity index 99% rename from realm/realm-library/proguard-rules-base.pro rename to realm/realm-library/proguard-rules-consumer-base.pro index 19a3b8d3ba..5054eff929 100644 --- a/realm/realm-library/proguard-rules-base.pro +++ b/realm/realm-library/proguard-rules-consumer-base.pro @@ -1,2 +1,3 @@ # It's OK not to exist SyncObjectServerFacade in base library. -dontnote io.realm.internal.SyncObjectServerFacade + diff --git a/realm/realm-library/proguard-rules-common.pro b/realm/realm-library/proguard-rules-consumer-common.pro similarity index 100% rename from realm/realm-library/proguard-rules-common.pro rename to realm/realm-library/proguard-rules-consumer-common.pro diff --git a/realm/realm-library/proguard-rules-objectServer.pro b/realm/realm-library/proguard-rules-consumer-objectServer.pro similarity index 100% rename from realm/realm-library/proguard-rules-objectServer.pro rename to realm/realm-library/proguard-rules-consumer-objectServer.pro diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index 252dc083a6..9fd28e2e0d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; import io.realm.exceptions.RealmFileException; @@ -271,6 +272,7 @@ public void getInstance_differentConfigurationsShouldNotBlockEachOther() throws final RealmConfiguration config1 = configFactory.createConfigurationBuilder() .name("config1.realm") + .schema(AllJavaTypes.class) .initialData(new Realm.Transaction() { @Override public void execute(Realm realm) { @@ -282,6 +284,7 @@ public void execute(Realm realm) { RealmConfiguration config2 = configFactory.createConfigurationBuilder() .name("config2.realm") + .schema(AllJavaTypes.class) .build(); Thread thread = new Thread(new Runnable() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 633cbe6e64..3e9a2e32d4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -37,6 +37,7 @@ import io.reactivex.Observable; import io.reactivex.Single; import io.realm.entities.AllTypes; +import io.realm.entities.AllTypesModelModule; import io.realm.entities.AnimalModule; import io.realm.entities.AssetFileModule; import io.realm.entities.Cat; @@ -137,7 +138,7 @@ public void getInstance_nullConfigThrows() { @Test public void constructBuilder_nullNameThrows() { try { - new RealmConfiguration.Builder(context).name(null); + configFactory.createConfigurationBuilder().name(null); fail(); } catch (IllegalArgumentException ignored) { } @@ -146,7 +147,7 @@ public void constructBuilder_nullNameThrows() { @Test public void constructBuilder_emptyNameThrows() { try { - new RealmConfiguration.Builder(context).name(""); + configFactory.createConfigurationBuilder().name(""); fail(); } catch (IllegalArgumentException ignored) { } @@ -154,14 +155,14 @@ public void constructBuilder_emptyNameThrows() { @Test(expected = IllegalArgumentException.class) public void directory_null() { - new RealmConfiguration.Builder(context).directory(null); + configFactory.createConfigurationBuilder().directory(null); } @Test public void directory_writeProtectedDir() { File dir = new File("/"); thrown.expect(IllegalArgumentException.class); - new RealmConfiguration.Builder(context).directory(dir); + configFactory.createConfigurationBuilder().directory(dir); } @Test @@ -170,7 +171,7 @@ public void directory_dirIsAFile() throws IOException { File file = new File(dir, "dummyfile"); assertTrue(file.createNewFile()); thrown.expect(IllegalArgumentException.class); - new RealmConfiguration.Builder(context).directory(file); + configFactory.createConfigurationBuilder().directory(file); } @Test @@ -189,7 +190,7 @@ public void getInstance_idForHashCollision() { @Test public void constructBuilder_nullKeyThrows() { try { - new RealmConfiguration.Builder(context).encryptionKey(null); + configFactory.createConfigurationBuilder().encryptionKey(null); fail(); } catch (IllegalArgumentException ignored) { } @@ -204,7 +205,7 @@ public void constructBuilder_wrongKeyLengthThrows() { }; for (byte[] key : wrongKeys) { try { - new RealmConfiguration.Builder(context).encryptionKey(key); + configFactory.createConfigurationBuilder().encryptionKey(key); fail("Key with length " + key.length + " should throw an exception"); } catch (IllegalArgumentException ignored) { } @@ -214,7 +215,7 @@ public void constructBuilder_wrongKeyLengthThrows() { @Test public void constructBuilder_negativeVersionThrows() { try { - new RealmConfiguration.Builder(context).schemaVersion(-1); + configFactory.createConfigurationBuilder().schemaVersion(-1); fail(); } catch (IllegalArgumentException ignored) { } @@ -222,7 +223,7 @@ public void constructBuilder_negativeVersionThrows() { @Test public void constructBuilder_versionLessThanDiscVersionThrows() { - realm = Realm.getInstance(new RealmConfiguration.Builder(context) + realm = Realm.getInstance(configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schemaVersion(42) .build()); @@ -231,7 +232,7 @@ public void constructBuilder_versionLessThanDiscVersionThrows() { int[] wrongVersions = new int[] { 0, 1, 41 }; for (int version : wrongVersions) { try { - realm = Realm.getInstance(new RealmConfiguration.Builder(context) + realm = Realm.getInstance(configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schemaVersion(version) .build()); @@ -244,7 +245,7 @@ public void constructBuilder_versionLessThanDiscVersionThrows() { @Test public void constructBuilder_versionEqualWhenSchemaChangesThrows() { // Creates initial Realm. - RealmConfiguration config = new RealmConfiguration.Builder(context) + RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schemaVersion(42) .schema(StringOnly.class) @@ -253,7 +254,7 @@ public void constructBuilder_versionEqualWhenSchemaChangesThrows() { // Creates new instance with a configuration containing another schema. try { - config = new RealmConfiguration.Builder(context) + config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schemaVersion(42) .schema(StringAndInt.class) @@ -267,7 +268,7 @@ public void constructBuilder_versionEqualWhenSchemaChangesThrows() { // Only Dog is included in the schema definition, but in order to create Dog, the Owner has to be defined as well. @Test public void schemaDoesNotContainAllDefinedObjectShouldThrow() { - RealmConfiguration config = new RealmConfiguration.Builder(context) + RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schema(Dog.class) .build(); @@ -278,7 +279,7 @@ public void schemaDoesNotContainAllDefinedObjectShouldThrow() { @Test public void migration_nullThrows() { try { - new RealmConfiguration.Builder(context).migration(null).build(); + configFactory.createConfigurationBuilder().migration(null).build(); fail(); } catch (IllegalArgumentException ignored) { } @@ -288,14 +289,14 @@ public void migration_nullThrows() { public void modules_nonRealmModulesThrows() { // Tests first argument. try { - new RealmConfiguration.Builder(context).modules(new Object()); + configFactory.createConfigurationBuilder().modules(new Object()); fail(); } catch (IllegalArgumentException ignored) { } // Tests second argument. try { - new RealmConfiguration.Builder(context).modules(Realm.getDefaultModule(), new Object()); + configFactory.createConfigurationBuilder().modules(Realm.getDefaultModule(), new Object()); fail(); } catch (IllegalArgumentException ignored) { } @@ -303,9 +304,9 @@ public void modules_nonRealmModulesThrows() { @Test public void modules() { - RealmConfiguration realmConfig = new RealmConfiguration.Builder(context) + RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) - .modules(Realm.getDefaultModule(), (Object) null) + .modules(new AllTypesModelModule(), (Object) null) .build(); realm = Realm.getInstance(realmConfig); assertNotNull(realm.getTable(AllTypes.class)); @@ -326,7 +327,7 @@ public void getInstance() { @Test public void standardSetup() { - RealmConfiguration config = new RealmConfiguration.Builder(context) + RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .name("foo.realm") .encryptionKey(TestHelper.getRandomKey()) @@ -349,7 +350,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { @Test public void deleteRealmIfMigrationNeeded() { // Populates v0 of a Realm with an object. - RealmConfiguration config = new RealmConfiguration.Builder(context) + RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schema(StringOnly.class) .schemaVersion(0) @@ -363,7 +364,7 @@ public void deleteRealmIfMigrationNeeded() { realm.close(); // Changes schema and verifies that Realm has been cleared. - config = new RealmConfiguration.Builder(context) + config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schema(StringOnly.class, StringAndInt.class) .schemaVersion(1) @@ -378,7 +379,7 @@ public void deleteRealmIfMigrationNeeded_failsWhenAssetFileProvided() { Context context = InstrumentationRegistry.getInstrumentation().getContext(); // Has a builder instance to isolate codepath. - RealmConfiguration.Builder builder = new RealmConfiguration.Builder(context); + RealmConfiguration.Builder builder = configFactory.createConfigurationBuilder(); try { builder .assetFile("asset_file.realm") @@ -398,7 +399,7 @@ public void upgradeVersionWithNoMigration() { // Version upgrades only without any actual schema changes will just succeed, and the schema version will be // set to the new one. - realm = Realm.getInstance(new RealmConfiguration.Builder(context) + realm = Realm.getInstance(configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schemaVersion(42) .build()); @@ -407,43 +408,43 @@ public void upgradeVersionWithNoMigration() { @Test public void equals() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context).build(); + RealmConfiguration config1 = configFactory.createConfiguration(); + RealmConfiguration config2 = configFactory.createConfiguration(); assertTrue(config1.equals(config2)); } @Test public void equals_respectReadOnly() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context).assetFile("foo").build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context).assetFile("foo").readOnly().build(); + RealmConfiguration config1 = configFactory.createConfigurationBuilder().assetFile("foo").build(); + RealmConfiguration config2 = configFactory.createConfigurationBuilder().assetFile("foo").readOnly().build(); assertFalse(config1.equals(config2)); } @Test public void equalsWhenRxJavaUnavailable() { // Test for https://github.com/realm/realm-java/issues/2416 - RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); + RealmConfiguration config1 = configFactory.createConfigurationBuilder().directory(configFactory.getRoot()).build(); TestHelper.emulateRxJavaUnavailable(config1); - RealmConfiguration config2 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); + RealmConfiguration config2 = configFactory.createConfigurationBuilder().directory(configFactory.getRoot()).build(); TestHelper.emulateRxJavaUnavailable(config2); assertTrue(config1.equals(config2)); } @Test public void hashCode_Test() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); + RealmConfiguration config1 = configFactory.createConfigurationBuilder().directory(configFactory.getRoot()).build(); + RealmConfiguration config2 = configFactory.createConfigurationBuilder().directory(configFactory.getRoot()).build(); assertEquals(config1.hashCode(), config2.hashCode()); } @Test public void equals_withCustomModules() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context) + RealmConfiguration config1 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .modules(new HumanModule(), new AnimalModule()) .build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context) + RealmConfiguration config2 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .modules(new AnimalModule(), new HumanModule()) .build(); @@ -453,11 +454,11 @@ public void equals_withCustomModules() { @Test public void hashCode_withCustomModules() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context) + RealmConfiguration config1 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .modules(new HumanModule(), new AnimalModule()) .build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context) + RealmConfiguration config2 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .modules(new AnimalModule(), new HumanModule()) .build(); @@ -467,11 +468,11 @@ public void hashCode_withCustomModules() { @Test public void hashCode_withDifferentRxObservableFactory() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context) + RealmConfiguration config1 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .rxFactory(new RealmObservableFactory()) .build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context) + RealmConfiguration config2 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .rxFactory(new RealmObservableFactory() { @Override @@ -486,8 +487,8 @@ public int hashCode() { @Test public void equals_configurationsReturnCachedRealm() { - Realm realm1 = Realm.getInstance(new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build()); - Realm realm2 = Realm.getInstance(new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build()); + Realm realm1 = Realm.getInstance(configFactory.createConfigurationBuilder().directory(configFactory.getRoot()).build()); + Realm realm2 = Realm.getInstance(configFactory.createConfigurationBuilder().directory(configFactory.getRoot()).build()); try { assertEquals(realm1, realm2); } finally { @@ -498,8 +499,8 @@ public void equals_configurationsReturnCachedRealm() { @Test public void schemaVersion_differentVersionsThrows() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).schemaVersion(1).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).schemaVersion(2).build(); + RealmConfiguration config1 = configFactory.createConfigurationBuilder().directory(configFactory.getRoot()).schemaVersion(1).build(); + RealmConfiguration config2 = configFactory.createConfigurationBuilder().directory(configFactory.getRoot()).schemaVersion(2).build(); Realm realm1 = Realm.getInstance(config1); try { @@ -513,11 +514,11 @@ public void schemaVersion_differentVersionsThrows() { @Test public void encryptionKey_differentEncryptionKeysThrows() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context) + RealmConfiguration config1 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .encryptionKey(TestHelper.getRandomKey()) .build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context) + RealmConfiguration config2 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .encryptionKey(TestHelper.getRandomKey()) .build(); @@ -534,11 +535,11 @@ public void encryptionKey_differentEncryptionKeysThrows() { @Test public void schema_differentSchemasThrows() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context) + RealmConfiguration config1 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schema(StringOnly.class) .build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context) + RealmConfiguration config2 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schema(StringAndInt.class).build(); @@ -555,11 +556,11 @@ public void schema_differentSchemasThrows() { // Creates Realm instances with same name but different durabilities is not allowed. @Test public void inMemory_differentDurabilityThrows() { - RealmConfiguration config1 = new RealmConfiguration.Builder(context) + RealmConfiguration config1 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .inMemory() .build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context) + RealmConfiguration config2 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .build(); @@ -589,8 +590,8 @@ public void inMemory_differentDurabilityThrows() { // It is allowed to create multiple Realm with same name but in different directory. @Test public void constructBuilder_differentDirSameName() throws IOException { - RealmConfiguration config1 = new RealmConfiguration.Builder(context).directory(configFactory.getRoot()).build(); - RealmConfiguration config2 = new RealmConfiguration.Builder(context).directory(configFactory.newFolder()).build(); + RealmConfiguration config1 = configFactory.createConfigurationBuilder().directory(configFactory.getRoot()).build(); + RealmConfiguration config2 = configFactory.createConfigurationBuilder().directory(configFactory.newFolder()).build(); Realm realm1 = Realm.getInstance(config1); Realm realm2 = Realm.getInstance(config2); @@ -603,7 +604,7 @@ public void encryptionKey_keyStorage() throws Exception { // Generates a key and uses it in a RealmConfiguration. byte[] oldKey = TestHelper.getRandomKey(12345); byte[] key = oldKey; - RealmConfiguration config = new RealmConfiguration.Builder(context) + RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .encryptionKey(key) .build(); @@ -620,8 +621,6 @@ public void encryptionKey_keyStorage() throws Exception { @Test public void modelClassesForDefaultMediator() throws Exception { - assertTrue(defaultConfig.getSchemaMediator() instanceof DefaultRealmModuleMediator); - final Set> realmClasses = defaultConfig.getRealmObjectClasses(); assertTrue(realmClasses.contains(AllTypes.class)); @@ -636,7 +635,7 @@ public void modelClassesForDefaultMediator() throws Exception { @Test public void modelClasses_forGeneratedMediator() throws Exception { - final RealmConfiguration config = new RealmConfiguration.Builder(context) + final RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .modules(new HumanModule()) .build(); @@ -658,7 +657,7 @@ public void modelClasses_forGeneratedMediator() throws Exception { @Test public void modelClasses_forCompositeMediator() throws Exception { - final RealmConfiguration config = new RealmConfiguration.Builder(context) + final RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .modules(new HumanModule(), new AnimalModule()) .build(); @@ -681,7 +680,7 @@ public void modelClasses_forCompositeMediator() throws Exception { @Test public void modelClasses_forFilterableMediator() throws Exception { //noinspection unchecked - final RealmConfiguration config = new RealmConfiguration.Builder(context) + final RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) .schema(AllTypes.class, CatOwner.class) .build(); @@ -931,13 +930,13 @@ public void execute(final Realm realm) { @Test public void assetFileNullAndEmptyFileName() { try { - new RealmConfiguration.Builder(context).assetFile(null).build(); + configFactory.createConfigurationBuilder().assetFile(null).build(); fail(); } catch (IllegalArgumentException ignored) { } try { - new RealmConfiguration.Builder(context).assetFile("").build(); + configFactory.createConfigurationBuilder().assetFile("").build(); fail(); } catch (IllegalArgumentException ignored) { } @@ -946,10 +945,10 @@ public void assetFileNullAndEmptyFileName() { @Test public void assetFileWithInMemoryConfig() { // Ensures that there is no data. - Realm.deleteRealm(new RealmConfiguration.Builder(context).build()); + Realm.deleteRealm(configFactory.createConfigurationBuilder().build()); try { - new RealmConfiguration.Builder(context).assetFile("asset_file.realm").inMemory().build(); + configFactory.createConfigurationBuilder().assetFile("asset_file.realm").inMemory().build(); fail(); } catch (RealmException ignored) { } @@ -958,9 +957,9 @@ public void assetFileWithInMemoryConfig() { @Test public void assetFileFakeFile() { // Ensures that there is no data. - Realm.deleteRealm(new RealmConfiguration.Builder(context).build()); + Realm.deleteRealm(configFactory.createConfigurationBuilder().build()); - RealmConfiguration configuration = new RealmConfiguration.Builder(context).assetFile("no_file").build(); + RealmConfiguration configuration = configFactory.createConfigurationBuilder().assetFile("no_file").build(); try { Realm.getInstance(configuration); fail(); @@ -972,7 +971,7 @@ public void assetFileFakeFile() { @Test public void assetFileValidFile() throws IOException { // Ensures that there is no data. - Realm.deleteRealm(new RealmConfiguration.Builder(context).build()); + Realm.deleteRealm(configFactory.createConfigurationBuilder().build()); RealmConfiguration configuration = new RealmConfiguration .Builder(context) @@ -1007,7 +1006,7 @@ public void assetFile_failsWhenDeleteRealmIfMigrationNeededConfigured() { Context context = InstrumentationRegistry.getInstrumentation().getContext(); // Has a builder instance to isolate codepath. - RealmConfiguration.Builder builder = new RealmConfiguration.Builder(context); + RealmConfiguration.Builder builder = configFactory.createConfigurationBuilder(); try { builder .deleteRealmIfMigrationNeeded() diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java index 56649a1cf1..9cb082391d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java @@ -36,6 +36,7 @@ import java.util.concurrent.TimeUnit; import io.realm.entities.AllTypes; +import io.realm.entities.AllTypesModelModule; import io.realm.services.RemoteProcessService; @@ -52,6 +53,7 @@ // B. Open three Realms // 2. assertTrue("OK, remote process win. You can open more Realms than I do in the main local process", false); public class RealmInterprocessTest extends AndroidTestCase { + private Realm testRealm; private Messenger remoteMessenger; private Messenger localMessenger; @@ -155,8 +157,7 @@ public void handleMessage(Message msg) { @Override protected void setUp() throws Exception { super.setUp(); - - Realm.deleteRealm(new RealmConfiguration.Builder(getContext()).build()); + Realm.deleteRealm(getConfiguration()); // Starts the testing service. serviceStartLatch = new CountDownLatch(1); @@ -165,6 +166,10 @@ protected void setUp() throws Exception { assertTrue(serviceStartLatch.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS)); } + private RealmConfiguration getConfiguration() { + return new RealmConfiguration.Builder(getContext()).modules(new AllTypesModelModule()).build(); + } + @Override protected void tearDown() throws Exception { int counter = 10; @@ -281,7 +286,7 @@ public void testCreateInitialRealm() throws InterruptedException { @Override public void run() { // Step 1 - testRealm = Realm.getInstance(new RealmConfiguration.Builder(getContext()).build()); + testRealm = Realm.getInstance(getConfiguration()); assertEquals(0, testRealm.where(AllTypes.class).count()); testRealm.beginTransaction(); testRealm.createObject(AllTypes.class); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 4f2442b72e..9124ace416 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -209,7 +209,7 @@ public void getInstance_writeProtectedFile() throws IOException { assertTrue(realmFile.setWritable(false)); try { - Realm.getInstance(new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + Realm.getInstance(configFactory.createConfigurationBuilder() .directory(folder) .name(REALM_FILE) .build()); @@ -229,7 +229,7 @@ public void getInstance_writeProtectedFileWithContext() throws IOException { assertTrue(realmFile.setWritable(false)); try { - Realm.getInstance(new RealmConfiguration.Builder(context).directory(folder).name(REALM_FILE).build()); + Realm.getInstance(configFactory.createConfigurationBuilder().directory(folder).name(REALM_FILE).build()); fail(); } catch (RealmFileException expected) { assertEquals(RealmFileException.Kind.PERMISSION_DENIED, expected.getKind()); @@ -1044,7 +1044,7 @@ public void compactRealm_populatedRealm() throws IOException { @Test public void compactRealm_onExternalStorage() { final File externalFilesDir = context.getExternalFilesDir(null); - final RealmConfiguration config = new RealmConfiguration.Builder() + final RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(externalFilesDir) .name("external.realm") .build(); @@ -2261,7 +2261,7 @@ public void deleteRealm() throws InterruptedException { File tempDirRenamed = new File(configFactory.getRoot(), "delete_test_dir_2"); assertTrue(tempDir.mkdir()); - final RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + final RealmConfiguration configuration = configFactory.createConfigurationBuilder() .directory(tempDir) .build(); @@ -4232,7 +4232,7 @@ public void namedPipeDirForExternalStorage() { namedPipeDir.mkdirs(); final File externalFilesDir = context.getExternalFilesDir(null); - final RealmConfiguration config = new RealmConfiguration.Builder() + final RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(externalFilesDir) .name("external.realm") .build(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java new file mode 100644 index 0000000000..f2eb2f2c3d --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java @@ -0,0 +1,528 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.realm.annotations.RealmModule; +import io.realm.entities.AllJavaTypes; +import io.realm.entities.Dog; +import io.realm.exceptions.RealmException; +import io.realm.rule.RunInLooperThread; +import io.realm.sync.permissions.ClassPermissions; +import io.realm.sync.permissions.ClassPrivileges; +import io.realm.sync.permissions.ObjectPrivileges; +import io.realm.sync.permissions.Permission; +import io.realm.sync.permissions.RealmPermissions; +import io.realm.sync.permissions.RealmPrivileges; +import io.realm.sync.permissions.Role; + +import static io.realm.util.SyncTestUtils.createTestUser; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class ObjectLevelPermissionsTest { + + private static String REALM_URI = "realm://objectserver.realm.io/~/default"; + + private SyncConfiguration configuration; + private SyncUser user; + + @Rule + public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + private Realm realm; + private DynamicRealm dynamicRealm; + + @RealmModule(classes = { AllJavaTypes.class }) + public static class TestModule { + } + + @Before + public void setUp() { + user = createTestUser(); + configuration = new SyncConfiguration.Builder(user, REALM_URI) + .partialRealm() + .modules(new TestModule()) + .build(); + realm = Realm.getInstance(configuration); + dynamicRealm = DynamicRealm.getInstance(configuration); + } + + @After + public void tearDown() { + if (realm != null && !realm.isClosed()) { + realm.close(); + } + if (dynamicRealm != null && !dynamicRealm.isClosed()) { + dynamicRealm.close(); + } + } + + @Test + public void getPrivileges_realm_localDefaults() { + RealmPrivileges privileges = realm.getPrivileges(); + assertFullAccess(privileges); + + privileges = dynamicRealm.getPrivileges(); + assertFullAccess(privileges); + } + + @Test + public void getPrivileges_realm_revokeLocally() { + realm.executeTransaction(r -> { + Role role = realm.getRoles().where().equalTo("name", "everyone").findFirst(); + role.removeMember(user.getIdentity()); + }); + + RealmPrivileges privileges = realm.getPrivileges(); + assertNoAccess(privileges); + + privileges = dynamicRealm.getPrivileges(); + assertNoAccess(privileges); + } + + @Test + public void getPrivileges_class_localDefaults() { + ClassPrivileges privileges = realm.getPrivileges(AllJavaTypes.class); + assertFullAccess(privileges); + + privileges = dynamicRealm.getPrivileges(AllJavaTypes.CLASS_NAME); + assertFullAccess(privileges); + } + + @Test + public void getPrivileges_class_revokeLocally() { + realm.executeTransaction(r -> { + Role role = realm.getRoles().where().equalTo("name", "everyone").findFirst(); + role.removeMember(user.getIdentity()); + }); + + ClassPrivileges privileges = realm.getPrivileges(AllJavaTypes.class); + assertNoAccess(privileges); + + privileges = dynamicRealm.getPrivileges(AllJavaTypes.CLASS_NAME); + assertNoAccess(privileges); + } + + @Test + public void getPrivileges_object_localDefaults() { + realm.beginTransaction(); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); + realm.commitTransaction(); + assertFullAccess(realm.getPrivileges(obj)); + + dynamicRealm.beginTransaction(); + DynamicRealmObject dynamicObject = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1); + dynamicRealm.commitTransaction(); + assertFullAccess(dynamicRealm.getPrivileges(dynamicObject)); + } + + @Test + public void getPrivileges_object_revokeLocally() { + realm.executeTransaction(r -> { + Role role = realm.getRoles().where().equalTo("name", "everyone").findFirst(); + role.removeMember(user.getIdentity()); + }); + + realm.beginTransaction(); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); + realm.commitTransaction(); + assertNoAccess(realm.getPrivileges(obj)); + + dynamicRealm.beginTransaction(); + DynamicRealmObject dynamicObject = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1); + dynamicRealm.commitTransaction(); + assertNoAccess(dynamicRealm.getPrivileges(dynamicObject)); + } + + @Test + public void getPrivileges_closedRealmThrows() { + realm.close(); + try { + realm.getPrivileges(); + fail(); + } catch(IllegalStateException ignored) { + } + + try { + realm.getPrivileges(AllJavaTypes.class); + fail(); + } catch(IllegalStateException ignored) { + } + + try { + //noinspection ConstantConditions + realm.getPrivileges((RealmModel) null); + fail(); + } catch(IllegalStateException ignored) { + } + + dynamicRealm.close(); + try { + dynamicRealm.getPrivileges(); + fail(); + } catch(IllegalStateException ignored) { + } + + try { + dynamicRealm.getPrivileges(AllJavaTypes.CLASS_NAME); + fail(); + } catch(IllegalStateException ignored) { + } + + try { + //noinspection ConstantConditions + dynamicRealm.getPrivileges((RealmModel) null); + fail(); + } catch(IllegalStateException ignored) { + } + } + + @Test + public void getPrivileges_wrongThreadThrows() throws InterruptedException { + Thread thread = new Thread(() -> { + try { + realm.getPrivileges(); + fail(); + } catch(IllegalStateException ignored) { + } + + try { + realm.getPrivileges(AllJavaTypes.class); + fail(); + } catch(IllegalStateException ignored) { + } + + try { + //noinspection ConstantConditions + realm.getPrivileges((RealmModel) null); + fail(); + } catch(IllegalStateException ignored) { + } + + try { + dynamicRealm.getPrivileges(); + fail(); + } catch(IllegalStateException ignored) { + } + + try { + dynamicRealm.getPrivileges(AllJavaTypes.CLASS_NAME); + fail(); + } catch(IllegalStateException ignored) { + } + + try { + //noinspection ConstantConditions + dynamicRealm.getPrivileges((RealmModel) null); + fail(); + } catch(IllegalStateException ignored) { + } + }); + thread.start(); + thread.join(TestHelper.STANDARD_WAIT_SECS * 1000); + } + + @Test + public void getPrivileges_class_notPartofSchemaThrows() { + try { + realm.getPrivileges(Dog.class); + fail(); + } catch (RealmException ignore) { + } + + try { + dynamicRealm.getPrivileges("Dog"); + fail(); + } catch (RealmException ignore) { + } + } + + @Test + public void getPrivileges_class_nullThrows() { + try { + //noinspection ConstantConditions + realm.getPrivileges((Class) null); + fail(); + } catch (IllegalArgumentException ignore) { + } + + try { + //noinspection ConstantConditions + dynamicRealm.getPrivileges((String) null); + fail(); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void getPrivileges_object_nullThrows() { + try { + //noinspection ConstantConditions + realm.getPrivileges((RealmModel) null); + fail(); + } catch (IllegalArgumentException ignore) { + } + + try { + //noinspection ConstantConditions + dynamicRealm.getPrivileges((DynamicRealmObject) null); + fail(); + } catch (IllegalArgumentException ignore) { + } + } + + @Test(expected = IllegalArgumentException.class) + public void getPrivileges_object_unmanagedThrows() { + // DynamicRealm do not support unmanaged DynamicRealmObjects + realm.getPrivileges(new AllJavaTypes(0)); + } + + @Test + public void getPrivileges_object_wrongRealmThrows() { + Realm otherRealm = Realm.getInstance(configFactory.createConfiguration("other")); + otherRealm.beginTransaction(); + AllJavaTypes obj = otherRealm.createObject(AllJavaTypes.class, 0); + try { + realm.getPrivileges(obj); + fail(); + } catch (IllegalArgumentException ignored) { + } finally { + otherRealm.close(); + } + } + + + @Test + public void getPermissions() { + // Typed RealmPermissions + RealmPermissions realmPermissions = realm.getPermissions(); + RealmList list = realmPermissions.getPermissions(); + assertEquals(1, list.size()); + assertEquals("everyone", list.first().getRole().getName()); + assertFullAccess(list.first()); + +// // FIXME: Dynamic RealmPermissions - Until support is enabled +// realmPermissions = dynamicRealm.getPermissions(); +// list = realmPermissions.getPermissions(); +// assertEquals(1, list.size()); +// assertEquals("everyone", list.first().getRole().getName()); +// assertFullAccess(list.first()); + } + + @Test + public void getPermissions_wrongThreadThrows() throws InterruptedException { + Thread t = new Thread(() -> { + try { + realm.getPermissions(); + fail(); + } catch (IllegalStateException ignore) { + } + +// FIXME: Disabled until support is enabled +// try { +// dynamicRealm.getPermissions(); +// fail(); +// } catch (IllegalStateException ignore) { +// } + }); + t.start(); + t.join(TestHelper.STANDARD_WAIT_SECS * 1000); + } + + @Test + public void getPermissions_closedRealmThrows() { + realm.close(); + try { + realm.getPermissions(); + fail(); + } catch (IllegalStateException ignore) { + } + +// FIXME Disabled until support is enabled +// dynamicRealm.close(); +// try { +// dynamicRealm.getPermissions(); +// fail(); +// } catch (IllegalStateException ignore) { +// } + } + + @Test + public void getClassPermissions() { + // Typed RealmPermissions + ClassPermissions classPermissions = realm.getPermissions(AllJavaTypes.class); + assertEquals("AllJavaTypes", classPermissions.getName()); + RealmList list = classPermissions.getPermissions(); + assertEquals(1, list.size()); + assertEquals("everyone", list.first().getRole().getName()); + assertFullAccess(list.first()); + + // FIXME: Dynamic RealmPermissions - Disabled until support is enabled +// classPermissions = dynamicRealm.getPermissions(AllJavaTypes.CLASS_NAME); +// assertEquals("AllJavaTypes", classPermissions.getName()); +// list = classPermissions.getPermissions(); +// assertEquals(1, list.size()); +// assertEquals("everyone", list.first().getRole().getName()); +// assertDefaultAccess(list.first()); + } + + @Test + public void getClassPermissions_wrongThreadThrows() throws InterruptedException { + Thread t = new Thread(() -> { + try { + realm.getPermissions(AllJavaTypes.class); + fail(); + } catch (IllegalStateException ignore) { + } + +// FIXME: Disabled until support is enabled +// try { +// dynamicRealm.getPermissions(AllJavaTypes.CLASS_NAME); +// fail(); +// } catch (IllegalStateException ignore) { +// } + }); + t.start(); + t.join(TestHelper.STANDARD_WAIT_SECS * 1000); + } + + @Test + public void getClassPermissions_closedRealmThrows() { + realm.close(); + try { + realm.getPermissions(AllJavaTypes.class); + fail(); + } catch (IllegalStateException ignore) { + } + +// FIXME: Disabled until support is enabled +// dynamicRealm.close(); +// try { +// dynamicRealm.getPermissions(AllJavaTypes.CLASS_NAME); +// fail(); +// } catch (IllegalStateException ignore) { +// } + } + + @Test + public void getRoles() { + RealmResults roles = realm.getRoles(); + assertEquals(1, roles.size()); + Role role = roles.first(); + assertEquals("everyone", role.getName()); + assertTrue(role.hasMember(user.getIdentity())); + } + + @Test + public void getRoles_wrongThreadThrows() throws InterruptedException { + Thread t = new Thread(() -> { + try { + realm.getRoles(); + fail(); + } catch (IllegalStateException ignore) { + } + }); + t.start(); + t.join(TestHelper.STANDARD_WAIT_SECS * 1000); + + } + + @Test + public void getRoles_closedRealmThrows() { + realm.close(); + try { + realm.getRoles(); + fail(); + } catch (IllegalStateException ignore) { + } + +// FIXME: Until support is enabled +// dynamicRealm.close(); +// try { +// dynamicRealm.getRoles(); +// fail(); +// } catch (IllegalStateException ignore) { +// } + } + + private void assertFullAccess(RealmPrivileges privileges) { + assertTrue(privileges.canRead()); + assertTrue(privileges.canUpdate()); + assertTrue(privileges.canSetPermissions()); + assertTrue(privileges.canModifySchema()); + } + + private void assertFullAccess(ClassPrivileges privileges) { + assertTrue(privileges.canCreate()); + assertTrue(privileges.canRead()); + assertTrue(privileges.canUpdate()); + assertTrue(privileges.canQuery()); + assertTrue(privileges.canSetPermissions()); + } + + private void assertFullAccess(ObjectPrivileges privileges) { + assertTrue(privileges.canRead()); + assertTrue(privileges.canUpdate()); + assertTrue(privileges.canDelete()); + assertTrue(privileges.canSetPermissions()); + } + + private void assertFullAccess(Permission permission) { + assertTrue(permission.canCreate()); + assertTrue(permission.canRead()); + assertTrue(permission.canUpdate()); + assertTrue(permission.canDelete()); + assertTrue(permission.canQuery()); + assertTrue(permission.canSetPermissions()); + assertTrue(permission.canModifySchema()); + } + + private void assertNoAccess(RealmPrivileges privileges) { + assertFalse(privileges.canRead()); + assertFalse(privileges.canUpdate()); + assertFalse(privileges.canSetPermissions()); + assertFalse(privileges.canModifySchema()); + } + + private void assertNoAccess(ClassPrivileges privileges) { + assertFalse(privileges.canCreate()); + assertFalse(privileges.canRead()); + assertFalse(privileges.canUpdate()); + assertFalse(privileges.canQuery()); + assertFalse(privileges.canSetPermissions()); + } + + private void assertNoAccess(ObjectPrivileges privileges) { + assertFalse(privileges.canRead()); + assertFalse(privileges.canUpdate()); + assertFalse(privileges.canDelete()); + assertFalse(privileges.canSetPermissions()); + } + +} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 152a993251..0f426b3954 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -30,6 +30,7 @@ import io.realm.entities.StringOnly; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.log.RealmLog; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; @@ -62,7 +63,7 @@ public class SessionTests { @Before public void setUp() { user = createTestUser(); - configuration = new SyncConfiguration.Builder(user, REALM_URI).build(); + configuration = new SyncConfiguration.Builder(user, REALM_URI).addModule(new ObjectPermissionsModule()).build(); } @Test @@ -191,6 +192,7 @@ public void errorHandler_useBackupSyncConfigurationForClientReset() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + .schema(StringOnly.class) .errorHandler((session, error) -> { if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { fail("Wrong error " + error.toString()); @@ -273,7 +275,7 @@ public void errorHandler_useBackupSyncConfigurationAfterClientReset() { try { Realm.getInstance(backupRealmConfiguration); fail("Expected to throw a Migration required"); - } catch (RealmMigrationNeededException expected) { + } catch (IllegalStateException expected) { } // opening a DynamicRealm will work though @@ -332,6 +334,7 @@ public void errorHandler_useClientResetEncrypted() { final byte[] randomKey = TestHelper.getRandomKey(); final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) .encryptionKey(randomKey) + .modules(new StringOnlyModule()) .errorHandler((session, error) -> { if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { fail("Wrong error " + error.toString()); @@ -370,7 +373,6 @@ public void errorHandler_useClientResetEncrypted() { looperThread.testComplete(); }) - .modules(new StringOnlyModule()) .build(); Realm realm = Realm.getInstance(config); @@ -414,8 +416,7 @@ public void downloadAllServerChanges_throwsOnUiThread() throws InterruptedExcept @UiThreadTest public void unrecognizedErrorCode_errorHandler() { AtomicBoolean errorHandlerCalled = new AtomicBoolean(false); - configuration = new SyncConfiguration - .Builder(user, REALM_URI) + configuration = configFactory.createSyncConfigurationBuilder(user, REALM_URI) .errorHandler((session, error) -> { errorHandlerCalled.set(true); assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 922044f17d..9fb9deda0b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -33,6 +33,7 @@ import java.util.Map; import io.realm.entities.StringOnly; +import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; import static io.realm.util.SyncTestUtils.createNamedTestUser; @@ -446,9 +447,9 @@ public void multipleUsersReferenceSameRealm() { SyncUser user1 = createNamedTestUser("user1"); SyncUser user2 = createNamedTestUser("user2"); String sharedUrl = "realm://ros.realm.io/42/default"; - SyncConfiguration config1 = new SyncConfiguration.Builder(user1, sharedUrl).build(); + SyncConfiguration config1 = new SyncConfiguration.Builder(user1, sharedUrl).modules(new StringOnlyModule()).build(); Realm realm1 = Realm.getInstance(config1); - SyncConfiguration config2 = new SyncConfiguration.Builder(user2, sharedUrl).build(); + SyncConfiguration config2 = new SyncConfiguration.Builder(user2, sharedUrl).modules(new StringOnlyModule()).build(); Realm realm2 = null; // Verify that two different configurations can be used for the same URL diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 839c935b31..89c13dd672 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.Collections; +import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.TestRealmConfigurationFactory; @@ -159,6 +160,7 @@ public void session() throws IOException { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = new SyncConfiguration.Builder(user, url) + .modules(new StringOnlyModule()) .build(); // This will trigger the creation of the session Realm realm = Realm.getInstance(config); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 6fb7dee8b5..7cb5dfda6d 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -43,6 +43,7 @@ import java.util.Map; import java.util.UUID; +import io.realm.entities.AllTypesModelModule; import io.realm.entities.StringOnly; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.network.AuthenticationServer; @@ -468,7 +469,7 @@ public void allSessions() { SyncUser user = createTestUser(); assertEquals(0, user.allSessions().size()); - SyncConfiguration configuration1 = new SyncConfiguration.Builder(user, url1).build(); + SyncConfiguration configuration1 = new SyncConfiguration.Builder(user, url1).modules(new AllTypesModelModule()).build(); Realm realm1 = Realm.getInstance(configuration1); List allSessions = user.allSessions(); assertEquals(1, allSessions.size()); @@ -477,7 +478,7 @@ public void allSessions() { assertEquals(user, session.getUser()); assertEquals(url1, session.getServerUrl().toString()); - SyncConfiguration configuration2 = new SyncConfiguration.Builder(user, url2).build(); + SyncConfiguration configuration2 = new SyncConfiguration.Builder(user, url2).modules(new AllTypesModelModule()).build(); Realm realm2 = Realm.getInstance(configuration2); allSessions = user.allSessions(); assertEquals(2, allSessions.size()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java index 9e7573c865..36d0625db0 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java @@ -17,6 +17,7 @@ package io.realm; import io.realm.internal.OsRealmConfig; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.rule.TestRealmConfigurationFactory; /** @@ -28,6 +29,7 @@ public class TestSyncConfigurationFactory extends TestRealmConfigurationFactory public SyncConfiguration.Builder createSyncConfigurationBuilder(SyncUser user, String url) { return new SyncConfiguration.Builder(user, url) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .addModule(new ObjectPermissionsModule()) .directory(getRoot()); } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index bb63ca1e2d..034578b0b5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -510,6 +510,44 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterSchema } } +#if REALM_ENABLE_SYNC +JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetRealmPrivileges( + JNIEnv*, jclass, jlong shared_realm_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return static_cast(shared_realm->get_privileges()); +} + +JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetClassPrivileges( + JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_class_name) +{ + TR_ENTER_PTR(shared_realm_ptr) + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + JStringAccessor class_name(env, j_class_name); + return static_cast(shared_realm->get_privileges(StringData(class_name))); + } + CATCH_STD() + return 0; +} + +JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetObjectPrivileges( + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong row_ptr) +{ + TR_ENTER_PTR(shared_realm_ptr) + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto r = reinterpret_cast(row_ptr); + RowExpr row = r->get_table()->get(r->get_index()); + + return static_cast(shared_realm->get_privileges(row)); + } + CATCH_STD() + return 0; +} +#endif + JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsPartial(JNIEnv*, jclass, jlong shared_realm_ptr) { TR_ENTER_PTR(shared_realm_ptr) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 703c391c64..bb559df923 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 703c391c64f73c812358940993942338f1bc706d +Subproject commit bb559df9237ece49f9c889993f7c1aff619b48f9 diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index fe3645bc1e..885ba03f55 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -26,9 +26,10 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import io.reactivex.Flowable; import javax.annotation.Nullable; +import io.reactivex.Flowable; +import io.realm.annotations.Beta; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; @@ -40,13 +41,19 @@ import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSchemaInfo; import io.realm.internal.OsSharedRealm; +import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.Util; +import io.realm.internal.annotations.ObjectServer; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.RealmLog; +import io.realm.sync.permissions.ObjectPrivileges; +import io.realm.sync.permissions.RealmPermissions; +import io.realm.sync.permissions.RealmPrivileges; +import io.realm.sync.permissions.Role; /** * Base class for all Realm instances. @@ -496,6 +503,41 @@ public long getVersion() { return OsObjectStore.getSchemaVersion(sharedRealm); } + /** + * Returns the privileges granted to the current user for this Realm. + * + * @return the privileges granted the current user for this Realm. + */ + @Beta + @ObjectServer + public RealmPrivileges getPrivileges() { + checkIfValid(); + return new RealmPrivileges(sharedRealm.getPrivileges()); + } + + /** + * Returns the privileges granted to the current user for the given object. + * + * @param object Realm object to get privileges for. + * @return the privileges granted the current user for the object. + * @throws IllegalArgumentException if the object is either null, unmanaged or not part of this Realm. + */ + public ObjectPrivileges getPrivileges(RealmModel object) { + checkIfValid(); + //noinspection ConstantConditions + if (object == null) { + throw new IllegalArgumentException("Non-null 'object' required."); + } + if (!RealmObject.isManaged(object)) { + throw new IllegalArgumentException("Only managed objects have privileges. This is a an unmanaged object: " + object.toString()); + } + if (!((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(getPath())) { + throw new IllegalArgumentException("Object belongs to a different Realm."); + } + UncheckedRow row = (UncheckedRow) ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm(); + return new ObjectPrivileges(sharedRealm.getObjectPrivileges(row)); + } + /** * Closes the Realm instance and all its resources. *

            diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index b60043a377..c8e164bc64 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -16,9 +16,10 @@ package io.realm; -import io.reactivex.Flowable; import java.util.Locale; +import io.reactivex.Flowable; +import io.realm.annotations.Beta; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.internal.CheckedRow; @@ -26,7 +27,10 @@ import io.realm.internal.OsObjectStore; import io.realm.internal.OsSharedRealm; import io.realm.internal.Table; +import io.realm.internal.Util; +import io.realm.internal.annotations.ObjectServer; import io.realm.log.RealmLog; +import io.realm.sync.permissions.ClassPrivileges; /** * DynamicRealm is a dynamic variant of {@link io.realm.Realm}. This means that all access to data and/or queries are @@ -291,6 +295,86 @@ public Flowable asFlowable() { return configuration.getRxFactory().from(this); } +// FIXME: Depends on a typed schema. Find a work-around +// /** +// * {@inheritDoc} +// */ +// @Beta +// @ObjectServer +// @Override +// public RealmPermissions getPermissions() { +// checkIfValid(); +// Table table = sharedRealm.getTable("class___Realm"); +// TableQuery query = table.where(); +// OsResults result = OsResults.createFromQuery(sharedRealm, query); +// return new RealmResults<>(this, result, RealmPermissions.class).first(); +// } + + +// FIXME: Depends on a typed schema. Find a work-around +// /** +// * Returns all permissions associated with the given class. Attach a change listener +// * using {@link ClassPermissions#addChangeListener(RealmChangeListener)} to be notified about +// * any future changes. +// * +// * @param className class to receive permissions for. +// * @return the permissions for the given class or {@code null} if no permissions where found. +// * @throws RealmException if the class is not part of this Realms schema. +// */ +// @Beta +// @ObjectServer +// public ClassPermissions getPermissions(String className) { +// checkIfValid(); +// //noinspection ConstantConditions +// if (Util.isEmptyString(className)) { +// throw new IllegalArgumentException("Non-empty 'className' required."); +// } +// if (!schema.contains(className)) { +// throw new RealmException("Class '" + className + "' is not part of the schema for this Realm."); +// } +// Table table = sharedRealm.getTable("class___Class"); +// TableQuery query = table.where() +// .equalTo(new long[]{table.getColumnIndex("name")}, new long[]{NativeObject.NULLPTR}, className); +// OsResults result = OsResults.createFromQuery(sharedRealm, query); +// return new RealmResults<>(this, result, ClassPermissions.class).first(null); +// } + +// FIXME: Depends on a typed schema. Find a work-around +// /** +// * {@inheritDoc} +// */ +// @Beta +// @ObjectServer +// @Override +// public RealmResults getRoles() { +// checkIfValid(); +// //noinspection ConstantConditions +// Table table = sharedRealm.getTable("class___Role"); +// TableQuery query = table.where(); +// OsResults result = OsResults.createFromQuery(sharedRealm, query); +// return new RealmResults<>(this, result, Role.class); +// } + + /** + * Returns the privileges granted the current user for the given class. + * + * @param className class to get privileges for. + * @return the privileges granted the current user for the given class. + */ + @Beta + @ObjectServer + public ClassPrivileges getPrivileges(String className) { + checkIfValid(); + //noinspection ConstantConditions + if (Util.isEmptyString(className)) { + throw new IllegalArgumentException("Non-empty 'className' required."); + } + if (!schema.contains(className)) { + throw new RealmException("Class '" + className + "' is not part of the schema for this Realm"); + } + return new ClassPrivileges(sharedRealm.getClassPrivileges(className)); + } + /** * Returns the mutable schema for this Realm. * diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 282ee8665f..3bf2ba64fd 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -66,11 +66,19 @@ import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.TableQuery; +import io.realm.internal.UncheckedRow; import io.realm.internal.Util; +import io.realm.internal.annotations.ObjectServer; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; +import io.realm.sync.permissions.ClassPermissions; +import io.realm.sync.permissions.ClassPrivileges; +import io.realm.sync.permissions.RealmPermissions; +import io.realm.sync.permissions.RealmPrivileges; +import io.realm.sync.permissions.Role; /** * The Realm class is the storage and transactional manager of your object persistent store. It is in charge of creating @@ -1769,6 +1777,74 @@ public void onError(Throwable error) { }); } + /** + * Returns all permissions associated with the current Realm. Attach a change listener + * using {@link RealmPermissions#addChangeListener(RealmChangeListener)} to be notified about + * any future changes. + * + * @return all permissions for the current Realm. + */ + @Beta + @ObjectServer + public RealmPermissions getPermissions() { + checkIfValid(); + return where(RealmPermissions.class).findFirst(); + } + + /** + * Returns all {@link Role} objects available in this Realm. Attach a change listener + * using {@link Role#addChangeListener(RealmChangeListener)} to be notified about + * any future changes. + * + * @return all roles available in the current Realm. + */ + @Beta + @ObjectServer + public RealmResults getRoles() { + checkIfValid(); + return where(Role.class).sort("name").findAll(); + } + + /** + * Returns the privileges granted the current user for the given class. + * + * @param clazz class to get privileges for. + * @return the privileges granted the current user for the given class. + */ + @Beta + @ObjectServer + public ClassPrivileges getPrivileges(Class clazz) { + checkIfValid(); + //noinspection ConstantConditions + if (clazz == null) { + throw new IllegalArgumentException("Non-null 'clazz' required."); + } + String className = configuration.getSchemaMediator().getSimpleClassName(clazz); + return new ClassPrivileges(sharedRealm.getClassPrivileges(className)); + } + + /** + * Returns all permissions associated with the given class. Attach a change listener + * using {@link ClassPermissions#addChangeListener(RealmChangeListener)} to be notified about + * any future changes. + * + * @param clazz class to receive permissions for. + * @return the permissions for the given class or {@code null} if no permissions where found. + * @throws RealmException if the class is not part of this Realms schema. + */ + @Beta + @ObjectServer + public ClassPermissions getPermissions(Class clazz) { + checkIfValid(); + //noinspection ConstantConditions + if (clazz == null) { + throw new IllegalArgumentException("Non-null 'clazz' required."); + } + return where(ClassPermissions.class) + .equalTo("name", configuration.getSchemaMediator().getSimpleClassName(clazz)) + .findFirst(); + } + Table getTable(Class clazz) { return schema.getTable(clazz); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 766a47e23e..66be10db7a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -658,6 +658,22 @@ public Builder modules(Object baseModule, Object... additionalModules) { return this; } + /** + * FIXME: Temporary visible + * DEBUG method. Will add a module unconditionally. + * + * Adds a module to already defined modules. + */ + public final Builder addModule(Object module) { + //noinspection ConstantConditions + if (module != null) { + checkModule(module); + modules.add(module); + } + + return this; + } + /** * Sets the {@link RxObservableFactory} used to create Rx Observables from Realm objects. * The default factory is {@link RealmObservableFactory}. @@ -749,14 +765,6 @@ public Builder compactOnLaunch(CompactOnLaunchCallback compactOnLaunch) { return this; } - private void addModule(Object module) { - //noinspection ConstantConditions - if (module != null) { - checkModule(module); - modules.add(module); - } - } - /** * DEBUG method. This restricts the Realm schema to only consist of the provided classes without having to * create a module. These classes must be available in the default module. Calling this will remove any @@ -805,6 +813,7 @@ public RealmConfiguration build() { rxFactory = new RealmObservableFactory(); } + return new RealmConfiguration(directory, fileName, getCanonicalPath(new File(directory, fileName)), diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 3c59c9d7dd..d2597933f0 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1774,7 +1774,9 @@ public RealmResults findAllAsync() { realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); SubscriptionAction subscriptionAction; - if (ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(realm.getConfiguration())) { + + // Don't create subscriptions for list queries as they are always part of an object covered by another query. + if (realm.sharedRealm.isPartial() && osList == null) { subscriptionAction = SubscriptionAction.ANONYMOUS_SUBSCRIPTION; } else { subscriptionAction = SubscriptionAction.NO_SUBSCRIPTION; @@ -1797,6 +1799,9 @@ public RealmResults findAllAsync() { public RealmResults findAllAsync(String subscriptionName) { realm.checkIfValid(); realm.checkIfPartialRealm(); + if (osList != null) { + throw new IllegalStateException("Cannot create subscriptions for queries based on a 'RealmList'"); + } if (Util.isEmptyString(subscriptionName)) { throw new IllegalArgumentException("Non-empty 'subscriptionName' required."); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java index ebaa0fc3c5..3ac1a8ebad 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java @@ -29,6 +29,10 @@ public class EmptyLoadChangeSet extends OsCollectionChangeSet { private static final int[] NO_INDEX_CHANGES = new int[0]; private static final Range[] NO_RANGE_CHANGES = new Range[0]; + public EmptyLoadChangeSet(@Nullable OsSubscription subscription, boolean firstCallback, boolean isPartialRealm) { + super(0, firstCallback, subscription, isPartialRealm); + } + public EmptyLoadChangeSet(@Nullable OsSubscription subscription, boolean isPartialRealm) { super(0, true, subscription, isPartialRealm); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index d3ee69eb6f..509ffa8c99 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -19,7 +19,6 @@ import android.content.Context; import java.lang.reflect.InvocationTargetException; -import java.net.URI; import io.realm.RealmConfiguration; import io.realm.exceptions.RealmException; @@ -120,6 +119,10 @@ public boolean isPartialRealm(RealmConfiguration configuration) { return false; } + public void addSupportForObjectLevelPermissions(RealmConfiguration.Builder builder) { + // Do nothing + } + public OsResults createSubscriptionAwareResults(OsSharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor, String name) { throw new IllegalStateException("Should only be called by builds supporting Sync"); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 4a5fcc9e4f..b897c13f5c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -26,9 +26,12 @@ import javax.annotation.Nullable; import io.realm.RealmConfiguration; +import io.realm.RealmModel; import io.realm.exceptions.RealmException; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; +import io.realm.internal.annotations.ObjectServer; +import io.realm.sync.permissions.RealmPrivileges; @Keep public final class OsSharedRealm implements Closeable, NativeObject { @@ -351,6 +354,21 @@ public OsSharedRealm.VersionID getVersionID() { return new OsSharedRealm.VersionID(versionId[0], versionId[1]); } + @ObjectServer + public int getPrivileges() { + return nativeGetRealmPrivileges(nativePtr); + } + + @ObjectServer + public int getClassPrivileges(String className) { + return nativeGetClassPrivileges(nativePtr, className); + } + + @ObjectServer + public int getObjectPrivileges(UncheckedRow row) { + return nativeGetObjectPrivileges(nativePtr, ((UncheckedRow) row).getNativePtr()); + } + public boolean isClosed() { return nativeIsClosed(nativePtr); } @@ -579,6 +597,11 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare private static native void nativeRegisterSchemaChangedCallback(long nativePtr, SchemaChangedCallback callback); + private static native int nativeGetRealmPrivileges(long nativePtr); + + private static native int nativeGetClassPrivileges(long nativePtr, String className); + + private static native int nativeGetObjectPrivileges(long nativePtr, long rowNativePtr); private static native boolean nativeIsPartial(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java index f9451e99dc..18d91b4626 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java @@ -27,7 +27,6 @@ */ public class SubscriptionAwareOsResults extends OsResults { - private final String subscriptionName; // The native ptr to a delayed notification. Since Java group all notifications for each // RealmResults, only one change from OS will ever be sent. private long delayedNotificationPtr = 0; @@ -36,6 +35,7 @@ public class SubscriptionAwareOsResults extends OsResults { // Reference to a (potential) underlying subscription private OsSubscription subscription = null; private boolean collectionChanged = false; + private boolean firstCallback; public static SubscriptionAwareOsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor, @@ -49,11 +49,11 @@ public static SubscriptionAwareOsResults createFromQuery(OsSharedRealm sharedRea SubscriptionAwareOsResults(OsSharedRealm sharedRealm, Table table, long nativePtr, String subscriptionName) { super(sharedRealm, table, nativePtr); - this.subscriptionName = subscriptionName; + this.firstCallback = true; this.subscription = new OsSubscription(this, subscriptionName); - this.subscription.addChangeListener(new RealmChangeListener() { + this.subscription.addChangeListener(new RealmChangeListener() { @Override - public void onChange(Object o) { + public void onChange(OsSubscription o) { subscriptionChanged = true; } }); @@ -77,15 +77,24 @@ public void run() { } private void triggerDelayedChangeListener() { - // Object Store compute the change set between the SharedGroup versions when the query created and the latest. - // So it is possible it deliver a non-empty change set for the first async query returns. - OsCollectionChangeSet changeset; - // Only parse on Subscription if it changed + // Only parse on the subscription if it actually changed OsSubscription subscription = (subscriptionChanged) ? this.subscription : null; + + // In case no collection listener was triggered, only trigger the listener if non-relevant + // changes happened to the subscription. In our case this means we only care about the + // errors and a completed subscription + if (delayedNotificationPtr == 0 + && subscription != null + && subscription.getState() != OsSubscription.SubscriptionState.ERROR + && subscription.getState() != OsSubscription.SubscriptionState.COMPLETE) { + return; + } + + OsCollectionChangeSet changeset; if (delayedNotificationPtr == 0) { - changeset = new EmptyLoadChangeSet(subscription, true); + changeset = new EmptyLoadChangeSet(subscription, firstCallback, true); } else { - changeset = new OsCollectionChangeSet(delayedNotificationPtr, !isLoaded(), subscription, true); + changeset = new OsCollectionChangeSet(delayedNotificationPtr, firstCallback, subscription, true); } // Happens e.g. if a synchronous query is created, a change listener is added and then @@ -95,6 +104,7 @@ private void triggerDelayedChangeListener() { return; } loaded = true; + firstCallback = false; observerPairs.foreach(new Callback(changeset)); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/annotations/ObjectServer.java b/realm/realm-library/src/main/java/io/realm/internal/annotations/ObjectServer.java new file mode 100644 index 0000000000..a70ebfc80a --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/annotations/ObjectServer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.internal.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to mark the classes as being specific to the Realm Object Server. + * They will be stripped in the Base variant. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.METHOD}) + +public @interface ObjectServer { +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index 137ba6605d..88fa860c3e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -31,6 +31,7 @@ import io.realm.Realm; import io.realm.RealmModel; +import io.realm.exceptions.RealmException; import io.realm.internal.ColumnInfo; import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsSchemaInfo; @@ -176,7 +177,7 @@ public boolean transformerApplied() { private RealmProxyMediator getMediator(Class clazz) { RealmProxyMediator mediator = mediators.get(clazz); if (mediator == null) { - throw new IllegalArgumentException(clazz.getSimpleName() + " is not part of the schema for this Realm"); + throw new RealmException(clazz.getSimpleName() + " is not part of the schema for this Realm"); } return mediator; } diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java new file mode 100644 index 0000000000..aadd98706f --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java @@ -0,0 +1,92 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.sync.permissions; + +import io.realm.RealmList; +import io.realm.RealmModel; +import io.realm.RealmObject; +import io.realm.annotations.Ignore; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; +import io.realm.internal.annotations.ObjectServer; + +/** + * Class describing all permissions related to a given Realm model class. These permissions will + * be inherited by any concrete objects of the given type. + *

            + * If a class level permission grants a privilege, it is still possible for individual objects + * to revoke them again, i.e. it is possible for the class level permission to grant general read + * access, while the individual objects are still able to revoke them. + *

            + * The opposite is not true, so if a privilege is not granted at the class level, it can never + * be granted at the object level, no matter what kind of permissions are set there. + * + * @see Object Level Permissions for an detailed description of the Realm Object + * Server permission system. + */ +@ObjectServer +@RealmClass(name = "__Class") +public class ClassPermissions extends RealmObject { + + @PrimaryKey + @Required + private String name; // Name of the class in the schema + private RealmList permissions = new RealmList<>(); + + @Ignore + Class modelClassRef; + + public ClassPermissions() { + // Required by Realm + } + + /** + * Creates permissions for the given Realm model class. Only one {@code ClassPermissions} object + * can exist pr Realm model class. + * + * @param clazz class to create permissions. + */ + public ClassPermissions(Class clazz) { + if (clazz == null) { + throw new IllegalArgumentException("Non-null 'clazz' required."); + } + modelClassRef = clazz; + name = clazz.getSimpleName(); + } + + /** + * Returns the name of the class these permissions apply to. If this object is unmanaged + * this name returned will be the simple name of the Java class. If the object is managed + * it will be the internal name Realm uses to represent the class. + * + * @return the name of the class these permissions apply to. + */ + public String getName() { + return name; + } + + /** + * Returns all Class level permissions for the class defined by {@link #getName()}. This is the + * default set of permissions for the class unless otherwise re-defined by object level + * permissions. + * + * @return all Class level permissions + */ + public RealmList getPermissions() { + return permissions; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPrivileges.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPrivileges.java new file mode 100644 index 0000000000..dbaea92950 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPrivileges.java @@ -0,0 +1,158 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.sync.permissions; + +import io.realm.internal.annotations.ObjectServer; + +/** + * This object combines all privileges granted on the Class by all Roles which the + * current User is a member of into the final privileges which will be enforced by + * the server. + * + * The privilege calculation is done locally using cached data, and inherently may + * be stale. It is possible that this method may indicate that an operation is + * permitted but the server will still reject it if permission is revoked before + * the changes have been integrated on the server. If this happens, the server will + * automatically revoke any illegal operations. + * + * Non-synchronized Realms always have permission to perform all operations. + */ +@ObjectServer +public final class ClassPrivileges { + + private boolean canRead; + private boolean canUpdate; + private boolean canDelete; + private boolean canSetPermissions; + private boolean canQuery; + private boolean canCreate; + private boolean canModifySchema; + + public ClassPrivileges(long privileges) { + this.canRead = (privileges & (1 << 0)) != 0; + this.canUpdate = (privileges & (1 << 1)) != 0; + this.canDelete = (privileges & (1 << 2)) != 0; + this.canSetPermissions = (privileges & (1 << 3)) != 0; + this.canQuery = (privileges & (1 << 4)) != 0; + this.canCreate = (privileges & (1 << 5)) != 0; + this.canModifySchema = (privileges & (1 << 6)) != 0; + } + + /** + * Returns whether or not the user can read objects of this type. + *

            + * If {@code false}, the current User is not permitted to see objects of this type, and + + attempting to query this class will always return empty results. + +

            + + Note that Read permissions are transitive, and so it may be possible to read an + + object which the user does not directly have Read permissions for by following a + + link to it from an object they do have Read permissions for. This does not apply + + to any of the other permission types. + * + * @return {@code true} if the user can read objects of the given type, {@code false} if not. + */ + public boolean canRead() { + return canRead; + } + + /** + * Returns whether or not the user can update objects of the given type. + *

            + * If {@code true}, the user is allowed to update properties on all objects of this type in + * the Realm. This does not include updating permissions nor creating or deleting objects. + * + * @return {@code true} if the user can update objects of the given type, {@code false} if not. + */ + public boolean canUpdate() { + return canUpdate; + }; + + /** + * Returns whether or not the user can change the {@link ClassPermissions} object representing + * the given class. See this clas for further details. + * + * @return {@code true} if the user can modify the {@link ClassPermissions} object for the given + * class, {@code false} if not. + * @see ClassPermissions + */ + public boolean canSetPermissions() { + return canSetPermissions; + }; + + /** + * Returns whether or not the user can query the given class. + *

            + * If this returns {@code false}, queries can still be run, but they will always return the + * empty result. This can be useful to prevent people from querying leaf objects in a tree + * structure and force them to only access objects through some parent objects that reference + * them. + * + * @return {@code true} if the user can query the given class, {@code false} if not. + */ + public boolean canQuery() { + return canQuery; + } + + /** + * Returns whether or not this user is allowed to create objects of this type. + * + * @return {@code true} if the user can create objects of this type, {@code false} if not. + */ + public boolean canCreate() { + return canCreate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ClassPrivileges that = (ClassPrivileges) o; + + if (canRead != that.canRead) return false; + if (canUpdate != that.canUpdate) return false; + if (canDelete != that.canDelete) return false; + if (canSetPermissions != that.canSetPermissions) return false; + if (canQuery != that.canQuery) return false; + if (canCreate != that.canCreate) return false; + return canModifySchema == that.canModifySchema; + } + + @Override + public int hashCode() { + int result = (canRead ? 1 : 0); + result = 31 * result + (canUpdate ? 1 : 0); + result = 31 * result + (canDelete ? 1 : 0); + result = 31 * result + (canSetPermissions ? 1 : 0); + result = 31 * result + (canQuery ? 1 : 0); + result = 31 * result + (canCreate ? 1 : 0); + result = 31 * result + (canModifySchema ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "RealmPrivileges{" + + "canRead=" + canRead + + ", canUpdate=" + canUpdate + + ", canDelete=" + canDelete + + ", canSetPermissions=" + canSetPermissions + + ", canQuery=" + canQuery + + ", canCreate=" + canCreate + + ", canModifySchema=" + canModifySchema + + '}'; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/ObjectPrivileges.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/ObjectPrivileges.java new file mode 100644 index 0000000000..2ffff816da --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/ObjectPrivileges.java @@ -0,0 +1,136 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.sync.permissions; + +import io.realm.Realm; +import io.realm.RealmModel; +import io.realm.internal.annotations.ObjectServer; + +/** + * This object combines all privileges granted on a Realm object by all Roles which the + * current User is a member of into the final privileges which will be enforced by + * the server. + * + * The privilege calculation is done locally using cached data, and inherently may + * be stale. It is possible that this method may indicate that an operation is + * permitted but the server will still reject it if permission is revoked before + * the changes have been integrated on the server. If this happens, the server will + * automatically revoke any illegal operations. + * + * Non-synchronized Realms always have permission to perform all operations. + */ +@ObjectServer +public final class ObjectPrivileges { + + private boolean canRead; + private boolean canUpdate; + private boolean canDelete; + private boolean canSetPermissions; + private boolean canQuery; + private boolean canCreate; + private boolean canModifySchema; + + public ObjectPrivileges(long privileges) { + this.canRead = (privileges & (1 << 0)) != 0; + this.canUpdate = (privileges & (1 << 1)) != 0; + this.canDelete = (privileges & (1 << 2)) != 0; + this.canSetPermissions = (privileges & (1 << 3)) != 0; + this.canQuery = (privileges & (1 << 4)) != 0; + this.canCreate = (privileges & (1 << 5)) != 0; + this.canModifySchema = (privileges & (1 << 6)) != 0; + } + + /** + * Returns whether or not the user can see/read the object. + * + * @return {@code true} if the user can read the object, {@code false} if not. + */ + public boolean canRead() { + return canRead; + } + + /** + * Returns whether or not the user can update fields on the object. This does not + * include deleting (see {@link #canDelete()} nor if permissions can be updated (see + * {@link #canSetPermissions()}). + * + * @return {@code true} if the user can update fields on the object, {@code false} if not. + */ + public boolean canUpdate() { + return canUpdate; + }; + + + /** + * Returns whether or not the user can delete the object. + * + * @return {@code true} if the user can delete the object, {@code false} if not. + */ + public boolean canDelete() { + return canDelete; + } + + /** + * Returns whether or not the user can change permissions on the object through its custom + * permission field (A field of the type {@code RealmList}). + * + * @return {@code true} if the user can modify the permissions on the object, {@code false} if not. + */ + public boolean canSetPermissions() { + return canSetPermissions; + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ObjectPrivileges that = (ObjectPrivileges) o; + + if (canRead != that.canRead) return false; + if (canUpdate != that.canUpdate) return false; + if (canDelete != that.canDelete) return false; + if (canSetPermissions != that.canSetPermissions) return false; + if (canQuery != that.canQuery) return false; + if (canCreate != that.canCreate) return false; + return canModifySchema == that.canModifySchema; + } + + @Override + public int hashCode() { + int result = (canRead ? 1 : 0); + result = 31 * result + (canUpdate ? 1 : 0); + result = 31 * result + (canDelete ? 1 : 0); + result = 31 * result + (canSetPermissions ? 1 : 0); + result = 31 * result + (canQuery ? 1 : 0); + result = 31 * result + (canCreate ? 1 : 0); + result = 31 * result + (canModifySchema ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "RealmPrivileges{" + + "canRead=" + canRead + + ", canUpdate=" + canUpdate + + ", canDelete=" + canDelete + + ", canSetPermissions=" + canSetPermissions + + ", canQuery=" + canQuery + + ", canCreate=" + canCreate + + ", canModifySchema=" + canModifySchema + + '}'; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java new file mode 100644 index 0000000000..d0838b4287 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java @@ -0,0 +1,573 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.sync.permissions; + +import io.realm.RealmObject; +import io.realm.annotations.RealmClass; +import io.realm.internal.annotations.ObjectServer; + +/** + * This class encapsulates the privileges granted a given {@link Role}. These privileges can be + * applied to either the entire Realm, Classes or individual objects. + *

            + * If no privileges are defined for an individual object, the values {@link ClassPermissions} + * will be inherited, if no values are defined there, the ones from {@link RealmPermissions} will + * be used. If no values can be found there, no privileges are granted. + *

            + * Not all privileges are meaningful all levels, e.g. `canCreate` is only meaningful when applied to + * classes, but it can still be defined at the Realm level. In that case all class permission objects + * will inherit the value unless they specifically override it. See the individual privileges for the + * details. + *

            + * When added to either {@link RealmPermissions}, {@link ClassPermissions} or a {@link RealmObject}, + * only one Permission object can exist for that role. If multiple objects are added the behavior + * is undefined and the Object Server might modify or delete both objects. + * + * @see Object Level Permissions for an detailed description of the Realm Object + * Server permission system. + */ +@ObjectServer +@RealmClass(name = "__Permission") +public class Permission extends RealmObject { + + /** + * Creates a {@link Permission} object in a fluid manner. + */ + public static class Builder { + private Role role; + private boolean canRead = false; + private boolean canUpdate = false; + private boolean canDelete = false; + private boolean canSetPermissions = false; + private boolean canQuery = false; + private boolean canCreate = false; + private boolean canModifySchema = false; + + /** + * Creates the builder. The default state is that no privileges are enabled. + * + * @param role {@link Role} for which these privileges apply. + */ + public Builder(Role role) { + this.role = role; + } + + /** + * Enables all privileges. + */ + public Builder allPrivileges() { + canRead = false; + canUpdate = false; + canDelete = false; + canSetPermissions = false; + canQuery = false; + canCreate = false; + canModifySchema = false; + return this; + } + + /** + * Disables all privileges. + */ + public Builder noPrivileges() { + canRead = true; + canUpdate = true; + canDelete = true; + canSetPermissions = true; + canQuery = true; + canCreate = true; + canModifySchema = true; + return this; + } + + /** + * Defines if this role can read from given resource or not. + * + *

              + *
            1. + * Realm: + * The role is allowed to read all objects from the Realm. If {@code false}, the + * Realm will appear completely empty to the role, effectively making it inaccessible. + *
            2. + *
            3. + * Class: + * The role is allowed to read the objects of this type and all referenced objects, + * even if those objects themselves have set this to {@code false}. + * If {@code false}, the role cannot see any object of this type and all queries + * against the type will return no results. + *
            4. + *
            5. + * Object: + * Determines if a role is allowed to see the individual object or not. + *
            6. + *
            + * + * @param canRead {@code true} if the role is allowed to read this resource, {@code false} if not. + */ + public Builder canRead(boolean canRead) { + this.canRead = canRead; + return this; + } + + /** + * Defines if this role can update the given resource or not. + * + *
              + *
            1. + * Realm: + * If {@code true}, the role is allowed update properties on all objects in the Realm. + * This does not include updating permissions nor creating or deleting objects. + *
            2. + *
            3. + * Class: + * If {@code true}, the role is allowed update properties on all objects of this type in + * the Realm. This does not include updating permissions nor creating or deleting objects. + *
            4. + *
            5. + * Object: + * If {@code true}, the role is allowed to update properties on the object. This + * does not cover updating permissions or deleting the object. + *
            6. + *
            + * + * @param canUpdate {@code true} if the role is allowed to update this resource, {@code false} if not. + */ + public Builder canUpdate(boolean canUpdate) { + this.canUpdate = canUpdate; + return this; + } + + /** + * Defines if this role can delete the given resource or not. + * + *
              + *
            1. + * Realm: + * Not applicable. + *
            2. + *
            3. + * Class: + * Not applicable. + *
            4. + *
            5. + * Object: + * If {@code true}, the role is allowed to delete the object. + *
            6. + *
            + * + * @param canDelete {@code true} if the role is allowed to delete this resource, {@code false} if not. + */ + public Builder canDelete(boolean canDelete) { + this.canDelete = canDelete; + return this; + } + + /** + * Defines if this role is allowed to change permissions on the given resource. + * Permissions can only be granted at the same permission level or below. E.g. if set on + * a Class, it is not possible to change Realm level permissions, but does allow the role to + * change object level permissions for objects of that type. + * + *
              + *
            1. + * Realm: + * The role is allowed to modify the {@link RealmPermissions} object. + *
            2. + *
            3. + * Class: + * The role is allowed the change the {@link ClassPermissions} object. + *
            4. + *
            5. + * Object: + * The role is allowed to change the permissions on this object. + *
            6. + *
            + * + * @param canSetPermissions {@code true} if the role is allowed to change the permissions for this resource. + */ + public Builder canSetPermissions(boolean canSetPermissions) { + this.canSetPermissions = canSetPermissions; + return this; + } + + /** + * Defines if this role is allowed to query the resource or not. + *

            + * Note, that local queries are always possible, but the query result will just be empty. + * + *

              + *
            1. + * Realm: + * Not applicable. + *
            2. + *
            3. + * Class: + * The role is allowed to query objects of this type. + *
            4. + *
            5. + * Object: + * Not applicable. + *
            6. + *
            + * + * @param canQuery {@code true} if the role is allowed to query objects of this type. + */ + public Builder canQuery(boolean canQuery) { + this.canQuery = canQuery; + return this; + } + + + /** + * Defines if this role is allowed to create objects of this type. + * + *
              + *
            1. + * Realm: + * Not applicable. + *
            2. + *
            3. + * Class: + * If {@code true}, the role is allowed to create objects of this type. + *
            4. + *
            5. + * Object: + * Not applicable. + *
            6. + *
            + * + * @param canCreate {@code true} if the role is allowed to create objects of this type. + */ + public Builder canCreate(boolean canCreate) { + this.canCreate = canCreate; + return this; + } + + /** + * Defines if this role is allowed to modify the schema of this resource. + * + *
              + *
            1. + * Realm: + * If {@code true} the role is allowed to create classes in the Realm. + *
            2. + *
            3. + * Class: + * If {@code true}, the role is allowed to add properties to the specified class. + *
            4. + *
            5. + * Object: + * Not applicable. + *
            6. + *
            + * + * @param canModifySchema {@code true} if the role is allowed to modify the schema of this resource. + */ + public Builder canModifySchema(boolean canModifySchema) { + this.canModifySchema = canModifySchema; + return this; + } + + /** + * Creates the unmanaged {@link Permission} object. + */ + public Permission build() { + return new Permission( + role, + canRead, + canUpdate, + canDelete, + canSetPermissions, + canQuery, + canCreate, + canModifySchema + ); + } + } + + private Role role; + private boolean canRead; + private boolean canUpdate; + private boolean canDelete; + private boolean canSetPermissions; + private boolean canQuery; + private boolean canCreate; + private boolean canModifySchema; + + public Permission() { + // Required by Realm + } + + /** + * Creates a set of privileges for the given role. + */ + public Permission(Role role) { + this.role = role; + } + + /** + * Creates a set of privileges for the given role. + */ + private Permission(Role role, boolean canRead, boolean canUpdate, boolean canDelete, boolean canSetPermissions, boolean canQuery, boolean canCreate, boolean canModifySchema) { + this.role = role; + this.canRead = canRead; + this.canUpdate = canUpdate; + this.canDelete = canDelete; + this.canSetPermissions = canSetPermissions; + this.canQuery = canQuery; + this.canCreate = canCreate; + this.canModifySchema = canModifySchema; + } + + /** + * Returns the role these privileges apply to. + * + * @return the role these privileges apply to. + */ + public Role getRole() { + return role; + } + + /** + * Returns {@code true} if the role is allowed to read the resource, {@code false} if not. + */ + public boolean canRead() { + return canRead; + } + + /** + * Defines if this role can read from given resource or not. + * + *
              + *
            1. + * Realm: + * The role is allowed to read all objects from the Realm. If {@code false}, the + * Realm will appear completely empty to the role, effectively making it inaccessible. + *
            2. + *
            3. + * Class: + * The role is allowed to read the objects of this type and all referenced objects, + * even if those objects themselves have set this to {@code false}. + * If {@code false}, the role cannot see any object of this type and all queries + * against the type will return no results. + *
            4. + *
            5. + * Object: + * Determines if a role is allowed to see the individual object or not. + *
            6. + *
            + * + * @param canRead {@code true} if the role is allowed to read this resource, {@code false} if not. + */ + public void setCanRead(boolean canRead) { + this.canRead = canRead; + } + + /** + * Returns {@code true} if the role is allowed to update the resource, {@code false} if not. + */ + public boolean canUpdate() { + return canUpdate; + } + + /** + * Defines if this role can update the given resource or not. + * + *
              + *
            1. + * Realm: + * If {@code true}, the role is allowed update properties on all objects in the Realm. + * This does not include updating permissions nor creating or deleting objects. + *
            2. + *
            3. + * Class: + * If {@code true}, the role is allowed update properties on all objects of this type in + * the Realm. This does not include updating permissions nor creating or deleting objects. + *
            4. + *
            5. + * Object: + * If {@code true}, the role is allowed to update properties on the object. This + * does not cover updating permissions or deleting the object. + *
            6. + *
            + * + * @param canUpdate {@code true} if the role is allowed to update this resource, {@code false} if not. + */ + public void setCanUpdate(boolean canUpdate) { + this.canUpdate = canUpdate; + } + + /** + * Returns {@code true} if the role is allowed to delete the object , {@code false} if not. + */ + public boolean canDelete() { + return canDelete; + } + + /** + * Defines if this role can delete the given resource or not. + * + *
              + *
            1. + * Realm: + * Not applicable. + *
            2. + *
            3. + * Class: + * Not applicable. + *
            4. + *
            5. + * Object: + * If {@code true}, the role is allowed to delete the object. + *
            6. + *
            + * + * @param canDelete {@code true} if the role is allowed to delete this resource, {@code false} if not. + */ + public void setCanDelete(boolean canDelete) { + this.canDelete = canDelete; + } + + /** + * Returns {@code true} if this this role is allowed to change permissions on the given resource. + */ + public boolean canSetPermissions() { + return canSetPermissions; + } + + /** + * Defines if this role is allowed to change permissions on the given resource. + * Permissions can only be granted at the same permission level or below. E.g. if set on + * a Class, it is not possible to change Realm level permissions, but does allow the role to + * change object level permissions for objects of that type. + * + *
              + *
            1. + * Realm: + * The role is allowed to modify the {@link RealmPermissions} object. + *
            2. + *
            3. + * Class: + * The role is allowed the change the {@link ClassPermissions} object. + *
            4. + *
            5. + * Object: + * The role is allowed to change the permissions on this object. + *
            6. + *
            + * + * @param canSetPermissions {@code true} if the role is allowed to change the permissions for this resource. + */ + public void setCanSetPermissions(boolean canSetPermissions) { + this.canSetPermissions = canSetPermissions; + } + + /** + * Returns {@code true} if the role is allowed to query the resource, {@code false} if not. + */ + public boolean canQuery() { + return canQuery; + } + + /** + * Defines if this role is allowed to query the resource or not. + *

            + * Note, that local queries are always possible, but the query result will just be empty. + * + *

              + *
            1. + * Realm: + * Not applicable. + *
            2. + *
            3. + * Class: + * The role is allowed to query objects of this type. + *
            4. + *
            5. + * Object: + * Not applicable. + *
            6. + *
            + * + * @param canQuery {@code true} if the role is allowed to query objects of this type. + */ + public void setCanQuery(boolean canQuery) { + this.canQuery = canQuery; + } + + /** + * Returns {@code true} if the role is allowed to create objects, {@code false} if not. + */ + public boolean canCreate() { + return canCreate; + } + + /** + * Defines if this role is allowed to create objects of this type. + * + *
              + *
            1. + * Realm: + * Not applicable. + *
            2. + *
            3. + * Class: + * If {@code true}, the role is allowed to create objects of this type. + *
            4. + *
            5. + * Object: + * Not applicable. + *
            6. + *
            + * + * @param canCreate {@code true} if the role is allowed to create objects of this type. + */ + public void setCanCreate(boolean canCreate) { + this.canCreate = canCreate; + } + + /** + * Returns {@code true} if the role is allowed to modify the schema of the resource, + * {@code false} if not. + */ + public boolean canModifySchema() { + return canModifySchema; + } + + /** + * Defines if this role is allowed to modify the schema of this resource. + * + *
              + *
            1. + * Realm: + * If {@code true} the role is allowed to create classes in the Realm. + *
            2. + *
            3. + * Class: + * If {@code true}, the role is allowed to add properties to the specified class. + *
            4. + *
            5. + * Object: + * Not applicable. + *
            6. + *
            + * + * @param canModifySchema {@code true} if the role is allowed to modify the schema of this resource. + */ + public void setCanModifySchema(boolean canModifySchema) { + this.canModifySchema = canModifySchema; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java new file mode 100644 index 0000000000..7be7f168ba --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java @@ -0,0 +1,75 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.sync.permissions; + +import javax.annotation.Nullable; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; +import io.realm.internal.annotations.ObjectServer; + +/** + * Class describes a user in the Realm Object Servers Permission system. + * The Id should be identical to the value from {@code SyncUser.getIdentity()} + * + * @see Object Level Permissions for an detailed description of the Realm Object + * Server permission system. + */ +@ObjectServer +@RealmClass(name = "__User") +public class PermissionUser extends RealmObject { + @PrimaryKey + @Required + private String id; + + @LinkingObjects("members") + final RealmResults roles = null; + + public PermissionUser() { + // Required by Realm + } + + /** + * Creates a new user. + * + * @param id identify of the user. Should be identitical to {@code SyncUser.getIdentity()}. + */ + public PermissionUser(String id) { + this.id = id; + } + + /** + * Returns the identify of this user. + * + */ + public String getId() { + return id; + } + + + /** + * Returns all {@link Role}s this user has. + * + * @return all roles this user has. + */ + public @Nullable RealmResults getRoles() { + return roles; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java new file mode 100644 index 0000000000..41b0f05009 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.sync.permissions; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; +import io.realm.internal.annotations.ObjectServer; + +/** + * Class describing all permissions related to a given Realm. Permissions attached to this class + * are treated as the default permissions if not otherwise overridden by {@link ClassPermissions} + * or object level permissions. + * + * @see Object Level Permissions for an detailed description of the Realm Object + * Server permission system. + */ +@ObjectServer +@RealmClass(name = "__Realm") +public class RealmPermissions extends RealmObject { + @PrimaryKey + private int id = 0; // Singleton object for the Realm file + private RealmList permissions = new RealmList<>(); + + public RealmPermissions() { + // Required by Realm + } + + /** + * Returns all Realm level permissions, i.e. permissions that apply to the Realm as a whole. + * + * @return all Realm level permissions + */ + public RealmList getPermissions() { + return permissions; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPrivileges.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPrivileges.java new file mode 100644 index 0000000000..6213302d28 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPrivileges.java @@ -0,0 +1,159 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.sync.permissions; + +import io.realm.Realm; +import io.realm.RealmModel; +import io.realm.internal.annotations.ObjectServer; + +/** + * This object combines all privileges granted on the Realm by all Roles which the + * current User is a member of into the final privileges which will be enforced by + * the server. + * + * The privilege calculation is done locally using cached data, and inherently may + * be stale. It is possible that this method may indicate that an operation is + * permitted but the server will still reject it if permission is revoked before + * the changes have been integrated on the server. If this happens, the server will automatically + * revoke any illegal operations. + * + * Non-synchronized Realms always have permission to perform all operations. + */ +@ObjectServer +public final class RealmPrivileges { + + private boolean canRead; + private boolean canUpdate; + private boolean canDelete; + private boolean canSetPermissions; + private boolean canQuery; + private boolean canCreate; + private boolean canModifySchema; + + public RealmPrivileges(long privileges) { + this.canRead = (privileges & (1 << 0)) != 0; + this.canUpdate = (privileges & (1 << 1)) != 0; + this.canDelete = (privileges & (1 << 2)) != 0; + this.canSetPermissions = (privileges & (1 << 3)) != 0; + this.canQuery = (privileges & (1 << 4)) != 0; + this.canCreate = (privileges & (1 << 5)) != 0; + this.canModifySchema = (privileges & (1 << 6)) != 0; + } + + /** + * Returns whether or not can see this Realm. If {@code true}, the user is allowed to read all + * objects and classes from the Realm. If {@code false}, the Realm will appear completely empty + * (including having no schema), effectively making it inaccessible. + * + * @return {@code true} if the user can see the Realm, {@code false} if not. + */ + public boolean canRead() { + return canRead; + } + + /** + * Returns whether or not the user can update Realm objects. If {@code true}, the user is + * allowed to update properties on all objects in the Realm. This does not include updating + * permissions nor creating or deleting objects. If {@code false}, the Realm is effectively + * read-only. + *

            + * This property also in part control if schema updates are possible. If this returns + * {@code false}, the user is not allowed to update the schema, if {@code true}, schema updates + * are allowed if {@link #canModifySchema()} also returns {@code true}. + * + * @return {@code true} if the user can update this Realm, {@code false} if not. + */ + public boolean canUpdate() { + return canUpdate; + }; + + /** + * Returns whether or not the user can change {@link RealmPermissions}. See this class for + * further information. + * + * @return {@code true} if the user can modify the {@link RealmPermissions} object, + * {@code false} if not. + * @see RealmPermissions + */ + public boolean canSetPermissions() { + return canSetPermissions; + }; + + /** + * Returns whether or not the user can modify the schema of the given resource. + * + *

              + *
            1. + * Realm: + * If {@code true} the user is allowed to create classes in the Realm. + *
            2. + *
            3. + * Class: + * If {@code true}, the user is allowed to add properties to the given class. + *
            4. + *
            5. + * Object: + * Not applicable. + *
            6. + *
            + * + * @return {@code true} if the user can modify the schema of the given resource, {@code false} if not. + */ + public boolean canModifySchema() { + return canModifySchema; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RealmPrivileges that = (RealmPrivileges) o; + + if (canRead != that.canRead) return false; + if (canUpdate != that.canUpdate) return false; + if (canDelete != that.canDelete) return false; + if (canSetPermissions != that.canSetPermissions) return false; + if (canQuery != that.canQuery) return false; + if (canCreate != that.canCreate) return false; + return canModifySchema == that.canModifySchema; + } + + @Override + public int hashCode() { + int result = (canRead ? 1 : 0); + result = 31 * result + (canUpdate ? 1 : 0); + result = 31 * result + (canDelete ? 1 : 0); + result = 31 * result + (canSetPermissions ? 1 : 0); + result = 31 * result + (canQuery ? 1 : 0); + result = 31 * result + (canCreate ? 1 : 0); + result = 31 * result + (canModifySchema ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "RealmPrivileges{" + + "canRead=" + canRead + + ", canUpdate=" + canUpdate + + ", canDelete=" + canDelete + + ", canSetPermissions=" + canSetPermissions + + ", canQuery=" + canQuery + + ", canCreate=" + canCreate + + ", canModifySchema=" + canModifySchema + + '}'; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java new file mode 100644 index 0000000000..629ab67ff3 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java @@ -0,0 +1,113 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.sync.permissions; + +import io.realm.Realm; +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; +import io.realm.internal.Util; +import io.realm.internal.annotations.ObjectServer; + +/** + * A role describes a function or area of authority in the Realm Object Server permission system. + * Multiple users can have the same role and a role can be assigned different permissions. + * + * @see Object Level Permissions for an detailed description of the Realm Object + * Server permission system. + */ +@ObjectServer +@RealmClass(name = "__Role") +public class Role extends RealmObject { + @PrimaryKey + @Required + private String name; + private RealmList members = new RealmList<>(); + + public Role() { + // Required by Realm; + } + + /** + * Creates a new named role. The name must be unique. + * + * @param name a unique name for the role. + */ + public Role(String name) { + this.name = name; + } + + /** + * Returns the name of this role. + * + * @return name of this role. + */ + public String getName() { + return name; + } + + /** + * Adds a member to this Role. Must be done from within a write transaction. + * + * @param userId userid of the SyncUser. + * @throws IllegalStateException if not in a write transaction. + * @throws IllegalArgumentException if {@code null} or empty {@code userId} is provided. + */ + public void addMember(String userId) { + if (isManaged()) { + if (Util.isEmptyString(userId)) { + throw new IllegalArgumentException("Non-empty 'userId' required"); + } + Realm realm = getRealm(); + PermissionUser user = realm.where(PermissionUser.class).equalTo("id", userId).findFirst(); + if (user == null) { + user = realm.createObject(PermissionUser.class, userId); + } + members.add(user); + + } else { + throw new IllegalStateException("Can not add a member to a non managed Role"); + } + } + + /** + * Removes a member from this Role. Must be done from within a write transaction. + * + * @param userId userid of the SyncUser to remove. + * @return {@code true} if the user could be removed, {@code false} if not. + * @throws IllegalStateException if not in a write transaction. + */ + public boolean removeMember(String userId) { + PermissionUser user = getRealm().where(PermissionUser.class).equalTo("id", userId).findFirst(); + if (user != null) { + return members.remove(user); + } else { + return false; + } + } + + /** + * Checks if the provided user has this role. + * + * @param userId user to check + * @return {@code true} if the user has this role, {@code false} if not. + */ + public boolean hasMember(String userId) { + return members.where().equalTo("id", userId).count() > 0; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/package-info.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/package-info.java new file mode 100644 index 0000000000..9c4e4ae3be --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +@javax.annotation.ParametersAreNonnullByDefault +package io.realm.sync.permissions; diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java index d41906a017..c9471a7df1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java @@ -205,7 +205,7 @@ public void onError(SyncSession session, ObjectServerError error) { }) .modules(new PermissionModule()) .waitForInitialRemoteData() - .readOnly() + // .readOnly() Temporarily disabled due to issues with ROS 3.0.0-alpha.X .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index e37b913928..23516c66fb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -38,9 +38,12 @@ import io.realm.internal.OsRealmConfig; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Util; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.log.RealmLog; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; +import io.realm.sync.permissions.PermissionUser; +import io.realm.sync.permissions.Role; /** * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm @@ -713,6 +716,43 @@ public Builder modules(Object baseModule, Object... additionalModules) { return this; } + /** + * Replaces the existing module(s) with one or more {@link RealmModule}s. Using this method will replace the + * current schema for this Realm with the schema defined by the provided modules. + *

            + * A reference to the default Realm module containing all Realm classes in the project (but not dependencies), + * can be found using {@link Realm#getDefaultModule()}. Combining the schema from the app project and a library + * dependency is thus done using the following code: + *

            + * {@code builder.modules(Realm.getDefaultMode(), new MyLibraryModule()); } + *

            + * @param modules list of modules tthe first Realm module (required). + * @throws IllegalArgumentException if any of the modules don't have the {@link RealmModule} annotation. + * @see Realm#getDefaultModule() + */ + public Builder modules(Iterable modules) { + this.modules.clear(); + if (modules != null) { + for (Object module : modules) { + addModule(module); + } + } + return this; + } + + /** + * Adds a module to the already defined modules. + */ + public Builder addModule(Object module) { + //noinspection ConstantConditions + if (module != null) { + checkModule(module); + modules.add(module); + } + + return this; + } + /** * Sets the {@link RxObservableFactory} used to create Rx Observables from Realm objects. * The default factory is {@link RealmObservableFactory}. @@ -965,6 +1005,11 @@ public SyncConfiguration build() { } } + // If partial sync is enabled, also add support for Object Level Permissions + if (isPartial) { + addModule(new ObjectPermissionsModule()); + } + return new SyncConfiguration( // Realm Configuration options realmFileDirectory, @@ -995,14 +1040,6 @@ public SyncConfiguration build() { ); } - private void addModule(Object module) { - //noinspection ConstantConditions - if (module != null) { - checkModule(module); - modules.add(module); - } - } - private void checkModule(Object module) { if (!module.getClass().isAnnotationPresent(RealmModule.class)) { throw new IllegalArgumentException(module.getClass().getCanonicalName() + " is not a RealmModule. " + diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index b12da634d0..58928d0b3d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -32,6 +32,7 @@ import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; import io.realm.internal.network.NetworkStateReceiver; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; @SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep @@ -185,4 +186,9 @@ public boolean isPartialRealm(RealmConfiguration configuration) { return false; } + + @Override + public void addSupportForObjectLevelPermissions(RealmConfiguration.Builder builder) { + builder.addModule(new ObjectPermissionsModule()); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java b/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java new file mode 100644 index 0000000000..e1f6f27af3 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java @@ -0,0 +1,18 @@ +package io.realm.internal.sync.permissions; + +import io.realm.annotations.RealmModule; +import io.realm.sync.permissions.ClassPermissions; +import io.realm.sync.permissions.Permission; +import io.realm.sync.permissions.RealmPermissions; +import io.realm.sync.permissions.PermissionUser; +import io.realm.sync.permissions.Role; + +@RealmModule(library = true, classes = { + ClassPermissions.class, + Permission.class, + RealmPermissions.class, + Role.class, + PermissionUser.class +}) +public class ObjectPermissionsModule { +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java index 59dca34d0a..c10a9af401 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java @@ -26,7 +26,7 @@ /** * This class represents the intent of giving a set of permissions to some users for some Realm(s). *

            - * If the request is successful, a {@link Permission} entry will be added to each affected users + * If the request is successful, a {@link io.realm.permissions.Permission} entry will be added to each affected users * {@link PermissionManager}, where it can be fetched using * {@link PermissionManager#getPermissions(PermissionManager.PermissionsCallback)} * @@ -114,7 +114,7 @@ public UserCondition getCondition() { * all Realms, for which the user sending the request, has administrative rights. * * @return the Realm URL for which the permissions should be granted. - * @see Permission#mayManage() + * @see io.realm.permissions.Permission#mayManage() */ public String getUrl() { return url; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 9fe1c29f7d..160ac6c8e3 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -28,6 +28,7 @@ import io.realm.internal.OsRealmConfig; import io.realm.internal.Util; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.objectserver.utils.HttpUtils; @@ -152,6 +153,7 @@ protected static class ConfigurationWrapper { public SyncConfiguration.Builder createSyncConfigurationBuilder(SyncUser user, String url) { return new SyncConfiguration.Builder(user, url) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .modules(Realm.getDefaultModule(), new ObjectPermissionsModule()) .directory(looperThread.getRoot()); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 95dad7ad87..37dc8a475f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -36,6 +36,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import io.realm.entities.AllJavaTypes; import io.realm.internal.OsRealmConfig; import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; @@ -54,7 +55,7 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) -@Ignore // FIXME: Re-enable once Permissions are stable on ROS +@Ignore // FIXME: Temporary disable unit tests due to lates (3.0.0-alpha.2) ROS having issues. Re-enable once ROS is stable again. public class PermissionManagerTests extends StandardIntegrationTest { private SyncUser user; @@ -118,6 +119,7 @@ public void onSuccess(RealmResults permissions) { // Create new Realm, which should create a new Permission entry SyncConfiguration config2 = new SyncConfiguration.Builder(user, Constants.USER_REALM_2) + .schema(AllJavaTypes.class) .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { @@ -164,7 +166,9 @@ public void onSuccess(RealmResults permissions) { assertInitialPermissions(permissions); for (int i = 0; i < TEST_SIZE; i++) { - SyncConfiguration configNew = new SyncConfiguration.Builder(user, "realm://" + Constants.HOST + "/~/test" + i).build(); + SyncConfiguration configNew = new SyncConfiguration.Builder(user, "realm://" + Constants.HOST + "/~/test" + i) + .schema(AllJavaTypes.class) + .build(); Realm newRealm = Realm.getInstance(configNew); looperThread.closeAfterTest(newRealm); } @@ -773,6 +777,7 @@ public void onSuccess() { // Default permissions are not recorded in the __permission Realm for user2 // Only way to check is by opening the Realm. SyncConfiguration config = new SyncConfiguration.Builder(user2, url) + .schema(AllJavaTypes.class) .waitForInitialRemoteData() .errorHandler(new SyncSession.ErrorHandler() { @Override @@ -1181,6 +1186,7 @@ private String createRemoteRealm(SyncUser user, String realmName) { String url = Constants.AUTH_SERVER_URL + "~/" + realmName; SyncConfiguration config = new SyncConfiguration.Builder(user, url) .name(realmName) + .schema(AllJavaTypes.class) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index cf3602bfe1..dcdce4ffb6 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -462,8 +462,8 @@ public void clientReset_manualTriggerAllowSessionToRestart() { SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); final AtomicReference configRef = new AtomicReference<>(null); - final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM).directory(looperThread.getRoot()) - + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .directory(looperThread.getRoot()) .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 09f4c8a275..14c971cc03 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -123,7 +123,7 @@ public void login_withAccessToken() { @Override public void onSuccess(SyncUser user) { assertTrue(user.isAdmin()); - final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL) + final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .errorHandler((session, error) -> fail("Session failed: " + error)) .build(); @@ -148,7 +148,7 @@ public void login_withAnonymous() { @Override public void onSuccess(SyncUser user) { assertFalse(user.isAdmin()); - final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL) + final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .errorHandler((session, error) -> fail("Session failed: " + error)) .build(); @@ -174,7 +174,7 @@ public void login_withNickname() { @Override public void onSuccess(SyncUser user) { assertFalse(user.isAdmin()); - final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL) + final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .errorHandler((session, error) -> fail("Session failed: " + error)) .build(); @@ -200,7 +200,7 @@ public void login_withNicknameAsAdmin() { @Override public void onSuccess(SyncUser user) { assertTrue(user.isAdmin()); - final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.SYNC_SERVER_URL) + final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .errorHandler((session, error) -> fail("Session failed: " + error)) .build(); @@ -401,7 +401,7 @@ public void cachedInstanceShouldNotThrowIfRefreshTokenExpires() throws Interrupt when(user.isValid()).thenReturn(true, false); - final RealmConfiguration configuration = new SyncConfiguration.Builder(user, Constants.USER_REALM).build(); + final RealmConfiguration configuration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); Realm realm = Realm.getInstance(configuration); assertFalse(user.isValid()); @@ -444,7 +444,7 @@ public void buildingSyncConfigurationShouldThrowIfInvalidUser() { try { // We should not be able to build a configuration with an invalid/logged out user - new SyncConfiguration.Builder(user, Constants.USER_REALM).build(); + configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); fail("Invalid user, it should not be possible to create a SyncConfiguration"); } catch (IllegalArgumentException expected) { // User not authenticated or authentication expired. @@ -452,7 +452,7 @@ public void buildingSyncConfigurationShouldThrowIfInvalidUser() { try { // We should not be able to build a configuration with an invalid/logged out user - new SyncConfiguration.Builder(currentUser, Constants.USER_REALM).build(); + configurationFactory.createSyncConfigurationBuilder(currentUser, Constants.USER_REALM).build(); fail("Invalid currentUser, it should not be possible to create a SyncConfiguration"); } catch (IllegalArgumentException expected) { // User not authenticated or authentication expired. @@ -467,7 +467,7 @@ public void usingConfigurationWithInvalidUserShouldThrow() { SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); - RealmConfiguration configuration = new SyncConfiguration.Builder(user, Constants.USER_REALM).build(); + RealmConfiguration configuration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); user.logout(); assertFalse(user.isValid()); Realm instance = Realm.getInstance(configuration); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java new file mode 100644 index 0000000000..5110ce45f0 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java @@ -0,0 +1,291 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.objectserver; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import io.realm.ObjectServerError; +import io.realm.PermissionManager; +import io.realm.Realm; +import io.realm.RealmResults; +import io.realm.StandardIntegrationTest; +import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.SyncUser; +import io.realm.TestHelper; +import io.realm.annotations.RealmModule; +import io.realm.entities.AllJavaTypes; +import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.permissions.PermissionModule; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; +import io.realm.objectserver.model.PermissionObject; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.StringOnlyModule; +import io.realm.objectserver.utils.UserFactory; +import io.realm.permissions.AccessLevel; +import io.realm.permissions.PermissionRequest; +import io.realm.permissions.UserCondition; +import io.realm.rule.RunTestInLooperThread; +import io.realm.sync.permissions.ClassPrivileges; +import io.realm.sync.permissions.ObjectPrivileges; +import io.realm.sync.permissions.Permission; +import io.realm.sync.permissions.RealmPrivileges; +import io.realm.sync.permissions.Role; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class ObjectLevelPermissionIntegrationTests extends StandardIntegrationTest { + + @RealmModule(classes = {AllJavaTypes.class}) + public static class ObjectLevelTestModule { + } + + @RealmModule(classes = {PermissionObject.class}) + public static class OLPermissionModule { + } + + // Check default privileges after being online for the first time + @Test + @RunTestInLooperThread() + public void getPrivileges_serverDefaults() throws InterruptedException { + String realmUrl = Constants.GLOBAL_REALM + "_getPrivileges_serverDefaults"; + List schemaModule = Arrays.asList(new ObjectLevelTestModule()); + createWorldReadableRealm(realmUrl, schemaModule); + + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, realmUrl) + .modules(schemaModule) + .partialRealm() + .build(); + + Realm realm = Realm.getInstance(syncConfig); + + // Make sure that all objects are part of the Partial Sync transitive closure + realm.where(AllJavaTypes.class).findAllAsync("keep-AllJavaTypes"); + + // Create offline object + realm.beginTransaction(); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); + realm.commitTransaction(); + assertEquals(1, realm.where(AllJavaTypes.class).count()); + + // Make sure that server permissions have been applied to local object + SyncManager.getSession(syncConfig).uploadAllLocalChanges(); + SyncManager.getSession(syncConfig).downloadAllServerChanges(); + realm.refresh(); + + // Check Realm privileges + RealmPrivileges realmPrivileges = realm.getPrivileges(); + assertFullAccess(realmPrivileges); + + // Check Class privileges + ClassPrivileges classPrivileges = realm.getPrivileges(AllJavaTypes.class); + assertFullAccess(classPrivileges); + + // Check Object privileges + assertEquals(1, realm.where(AllJavaTypes.class).count()); + ObjectPrivileges objectPrivileges = realm.getPrivileges(obj); + assertFullAccess(objectPrivileges); + + realm.close(); + looperThread.testComplete(); + } + +// @Test +// @RunTestInLooperThread +// public void getRoles() { +// fail("FIXME"); +// looperThread.testComplete(); +// } + + // Restrict read/write permission, only the owner of the object can see/modify it + @Test + @RunTestInLooperThread() + public void restrictAccessToOwner() throws InterruptedException { + String realmUrl = Constants.GLOBAL_REALM + "_restrictAccessToOwner"; + List schemaModules = Arrays.asList(new StringOnlyModule(), new OLPermissionModule(), new ObjectPermissionsModule()); + createWorldReadableRealm(realmUrl, schemaModules); + + // connect with user1 + SyncUser user1 = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration user1SyncConfig = configurationFactory + .createSyncConfigurationBuilder(user1, realmUrl) + .modules(schemaModules) + .partialRealm() + .build(); + Realm user1Realm = Realm.getInstance(user1SyncConfig); + user1Realm.beginTransaction(); + + // added a new Role to restrict access to our objects + Role role = user1Realm.createObject(Role.class, "role_" + user1.getIdentity()); + role.addMember(user1.getIdentity()); + + // add permission so this will be only visible and modifiable from user1 + Permission userPermission = new Permission(role); + userPermission.setCanRead(true); + userPermission.setCanQuery(true); + userPermission.setCanCreate(true); + userPermission.setCanUpdate(true); + userPermission.setCanUpdate(true); + userPermission.setCanDelete(true); + userPermission.setCanSetPermissions(true); + userPermission.setCanModifySchema(true); + + PermissionObject permissionObject1 = user1Realm.createObject(PermissionObject.class, "Foo"); + permissionObject1.getPermissions().add(userPermission); + user1Realm.commitTransaction(); + + SyncManager.getSession(user1SyncConfig).uploadAllLocalChanges(); + user1Realm.close(); + + // Connect with admin user and verify that user1 object is visible (non-partial Realm) + SyncUser adminUser = UserFactory.createNicknameUser(Constants.AUTH_URL, "admin2", true); + SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, realmUrl) + .modules(schemaModules) + .waitForInitialRemoteData() + .build(); + Realm adminRealm = Realm.getInstance(adminConfig); + RealmResults allPermissionObjects = adminRealm.where(PermissionObject.class).findAll(); + assertEquals(1, allPermissionObjects.size()); + PermissionObject permissionObject = allPermissionObjects.first(); + assertEquals("Foo", permissionObject.getName()); + assertEquals(1, permissionObject.getPermissions().size()); + Permission permission = permissionObject.getPermissions().get(0); + assertFullAccess(permission); + adminRealm.close(); + + // Connect with user 2 and verify that user1 object is not visible + SyncUser user2 = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfig2 = configurationFactory.createSyncConfigurationBuilder(user2, realmUrl) + .modules(schemaModules) + .partialRealm() + .build(); + Realm user2Realm = Realm.getInstance(syncConfig2); + looperThread.closeAfterTest(user2Realm); + RealmResults allAsync = user2Realm.where(PermissionObject.class).findAllAsync(); + looperThread.keepStrongReference(allAsync); + // new object should not be visible for user2 partial sync + allAsync.addChangeListener((permissionObjects2, changeSet) -> { + switch (changeSet.getState()) { + case INITIAL: + assertEquals(0, permissionObjects2.size()); + break; + case UPDATE: + assertEquals(0, permissionObjects2.size()); + looperThread.testComplete(); + break; + case ERROR: + fail("Unexpected error callback"); + break; + } + }); + } + + private void assertFullAccess(Permission permission) { + assertTrue(permission.canCreate()); + assertTrue(permission.canRead()); + assertTrue(permission.canUpdate()); + assertTrue(permission.canDelete()); + assertTrue(permission.canQuery()); + assertTrue(permission.canSetPermissions()); + assertTrue(permission.canModifySchema()); + } + + private void assertFullAccess(ClassPrivileges privileges) { + assertTrue(privileges.canCreate()); + assertTrue(privileges.canRead()); + assertTrue(privileges.canUpdate()); + assertTrue(privileges.canQuery()); + assertTrue(privileges.canSetPermissions()); + } + + private void assertFullAccess(RealmPrivileges privileges) { + assertTrue(privileges.canRead()); + assertTrue(privileges.canUpdate()); + assertTrue(privileges.canSetPermissions()); + assertTrue(privileges.canModifySchema()); + } + + private void assertFullAccess(ObjectPrivileges privileges) { + assertTrue(privileges.canRead()); + assertTrue(privileges.canUpdate()); + assertTrue(privileges.canDelete()); + assertTrue(privileges.canSetPermissions()); + } + + private void createWorldReadableRealm(String realmUrl, List modules) { + HandlerThread t = new HandlerThread("create-realm-thread"); + t.start(); + Handler handler = new Handler(t.getLooper()); + CountDownLatch setupRealm = new CountDownLatch(1); + handler.post(() -> { + final boolean oldValue = AndroidCapabilities.EMULATE_MAIN_THREAD; + SyncUser adminUser = UserFactory.createNicknameUser(Constants.AUTH_URL, "admin", true); + SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, realmUrl) + .modules(modules) + .addModule(new PermissionModule()) + .waitForInitialRemoteData() + .build(); + Realm.getInstanceAsync(syncConfig, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + AndroidCapabilities.EMULATE_MAIN_THREAD = true; + PermissionManager pm = adminUser.getPermissionManager(); + pm.applyPermissions(new PermissionRequest(UserCondition.noExistingPermissions(), realmUrl, AccessLevel.WRITE), new PermissionManager.ApplyPermissionsCallback() { + @Override + public void onSuccess() { + handler.post(() -> { + AndroidCapabilities.EMULATE_MAIN_THREAD = oldValue; + pm.close(); + realm.close(); + adminUser.logout(); + setupRealm.countDown(); + }); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + @Override + public void onError(Throwable exception) { + fail(exception.toString()); + } + }); + }); + TestHelper.awaitOrFail(setupRealm); + } + +} + diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 2b7c9383cb..c658818171 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -32,7 +32,11 @@ import io.realm.RealmResults; import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; +import io.realm.SyncManager; +import io.realm.SyncSession; import io.realm.SyncUser; +import io.realm.TestHelper; +import io.realm.annotations.RealmModule; import io.realm.objectserver.model.ProcessInfo; import io.realm.objectserver.model.TestObject; import io.realm.objectserver.utils.Constants; @@ -49,6 +53,9 @@ @RunWith(AndroidJUnit4.class) public class ProcessCommitTests extends StandardIntegrationTest { + @RealmModule(classes = { ProcessInfo.class, TestObject.class }) + public static class ProcessCommitTestsModule { } + @Rule public RunWithRemoteService remoteService = new RunWithRemoteService(); @@ -68,6 +75,7 @@ protected void run() { String realmUrl = Constants.SYNC_SERVER_URL; final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + .modules(new ProcessCommitTestsModule()) .directory(getService().getRoot()) .build(); getService().setRealm(Realm.getInstance(syncConfig)); @@ -79,13 +87,20 @@ protected void run() { processInfo.setPid(android.os.Process.myPid()); processInfo.setThreadId(Thread.currentThread().getId()); realm.commitTransaction(); - // FIXME: If we close the Realm here, the data won't be able to synced to the main process. Is it a bug - // in sync client which stops too early? - // Realm is currently configured with stop_immediately. This means the sync session is closed as soon as - // the last realm instance is closed. Not doing this would make the Realm lifecycle really - // unpredictable. We should have an easy way to wait for all changes to be uploaded though. - // Perhaps SyncSession.uploadAllLocalChanges() or something similar to - // SyncSesson.downloadAllServerChanges() + Thread t = new Thread(() -> { + try { + SyncManager.getSession(syncConfig).uploadAllLocalChanges(); + } catch (InterruptedException e) { + throw new IllegalStateException("Upload interrupted", e); + } + }); + t.start(); + try { + t.join(TestHelper.SHORT_WAIT_SECS * 1000); + } catch (InterruptedException e) { + throw new IllegalStateException("Waiting for background thread interrupted", e); + } + realm.close(); } }; @@ -111,6 +126,7 @@ public void expectSimpleCommit() { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user,realmUrl) + .modules(new ProcessCommitTestsModule()) .directory(looperThread.getRoot()) .build(); final Realm realm = Realm.getInstance(syncConfig); @@ -143,6 +159,7 @@ protected void run() { String realmUrl = Constants.SYNC_SERVER_URL; final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + .modules(new ProcessCommitTestsModule()) .directory(getService().getRoot()) .name(UUID.randomUUID().toString() + ".realm") .build(); @@ -189,6 +206,7 @@ public void expectALot() throws Throwable { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user,realmUrl) + .modules(new ProcessCommitTestsModule()) .directory(looperThread.getRoot()) .build(); final Realm realm = Realm.getInstance(syncConfig); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PermissionObject.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PermissionObject.java new file mode 100644 index 0000000000..53903a2f34 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PermissionObject.java @@ -0,0 +1,26 @@ +package io.realm.objectserver.model; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; +import io.realm.sync.permissions.Permission; + +public class PermissionObject extends RealmObject { + @PrimaryKey + @Required + private String name; + private RealmList permissions = new RealmList<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public RealmList getPermissions() { + return permissions; + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java index 70b9cf492b..9414bb4fcd 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -21,6 +21,7 @@ public class Constants { public static String HOST = "127.0.0.1"; public static final String USER_REALM = "realm://" + HOST + ":9080/~/tests"; public static final String USER_REALM_2 = "realm://" + HOST + ":9080/~/tests2"; + public static final String GLOBAL_REALM = "realm://" + HOST + ":9080/tests"; public static final String USER_REALM_SECURE = "realms://" + HOST + ":9443/~/tests"; public static final String SYNC_SERVER_URL = "realm://" + HOST + ":9080/~/tests"; public static final String SYNC_SERVER_URL_2 = "realm://" + HOST + ":9080/~/tests2"; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index 24345677b3..987177ebfd 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -34,6 +34,7 @@ import io.realm.SyncManager; import io.realm.SyncUser; import io.realm.TestHelper; +import io.realm.internal.ObjectServerFacade; import io.realm.log.RealmLog; import static org.junit.Assert.fail; @@ -48,9 +49,12 @@ public class UserFactory { // test starts and store it in a Realm. Then it can be retrieved for every process. private String userName; private static UserFactory instance; - private static RealmConfiguration configuration = new RealmConfiguration.Builder() - .name("user-factory.realm") - .build(); + private static RealmConfiguration configuration; + static { + RealmConfiguration.Builder builder = new RealmConfiguration.Builder().name("user-factory.realm"); + ObjectServerFacade.getSyncFacadeIfPossible().addSupportForObjectLevelPermissions(builder); + configuration = builder.build(); + } private UserFactory(String userName) { this.userName = userName; @@ -95,6 +99,11 @@ public static SyncUser createAdminUser(String authUrl) { return SyncUser.login(credentials, authUrl); } + public static SyncUser createNicknameUser(String authUrl, String nickname, boolean isAdmin) { + SyncCredentials credentials = SyncCredentials.nickname(nickname, isAdmin); + return SyncUser.login(credentials, authUrl); + } + // Since we don't have a reliable way to reset the sync server and client, just use a new user factory for every // test case. public static void resetInstance() { diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypesModelModule.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypesModelModule.java new file mode 100644 index 0000000000..7f31e0b774 --- /dev/null +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypesModelModule.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.annotations.RealmModule; + +@RealmModule(classes = { AllTypes.class, Dog.class, Owner.class, Cat.class, DogPrimaryKey.class }) +public class AllTypesModelModule { + +} + diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java index 74a0ec5a15..b6c6bb1e30 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -35,6 +35,7 @@ import io.realm.Realm; import io.realm.RealmConfiguration; +import io.realm.internal.ObjectServerFacade; import static org.junit.Assert.assertTrue; @@ -129,7 +130,9 @@ private synchronized boolean isUnitTestFailed() { // This builder creates a configuration that is *NOT* managed. // You have to delete it yourself. public RealmConfiguration.Builder createConfigurationBuilder() { - return new RealmConfiguration.Builder().directory(getRoot()); + RealmConfiguration.Builder builder = new RealmConfiguration.Builder().directory(getRoot()); + ObjectServerFacade.getSyncFacadeIfPossible().addSupportForObjectLevelPermissions(builder); + return builder; } public RealmConfiguration createConfiguration() { @@ -168,6 +171,8 @@ public RealmConfiguration createConfiguration(String subDir, String name, Object if (module != null) { builder.modules(module); + } else { + ObjectServerFacade.getSyncFacadeIfPossible().addSupportForObjectLevelPermissions(builder); } if (key != null) { diff --git a/realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java index 4062ee8e38..ec54957291 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java +++ b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java @@ -31,6 +31,7 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.entities.AllTypes; +import io.realm.entities.AllTypesModelModule; /** * Helper service for multi-processes support testing. @@ -128,7 +129,7 @@ private static String currentLine() { @Override void run() { - thiz.testRealm = Realm.getInstance(new RealmConfiguration.Builder().build()); + thiz.testRealm = Realm.getInstance(getConfiguration()); int expected = 1; long got = thiz.testRealm.where(AllTypes.class).count(); if (expected == got) { @@ -144,10 +145,15 @@ void run() { @Override void run() { - thiz.testRealm = Realm.getInstance(new RealmConfiguration.Builder().build()); + thiz.testRealm = Realm.getInstance(getConfiguration()); thiz.testRealm.close(); response(null); Runtime.getRuntime().exit(0); } }; + + private static RealmConfiguration getConfiguration() { + return new RealmConfiguration.Builder().modules(new AllTypesModelModule()).build(); + } + } From 41881627044378edcc1d28f5eff2d47c3d006fe5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 1 Mar 2018 22:33:27 +0100 Subject: [PATCH 1185/2110] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e014431ca3..e78eeebdcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.0 (YYYY-MM-DD) +## 5.0.0-BETA1 (2018-03-01) ### Known Bugs @@ -19,7 +19,7 @@ ### Internal -* Upgraded to Realm Sync 3.0.0-beta.6 +* Upgraded to Realm Sync 3.0.0-beta.10 * Upgraded to Realm Core 5.3.0 From 5c30abb2937cf0a24b0ce7c0e22d170cbe50626a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 1 Mar 2018 22:34:59 +0100 Subject: [PATCH 1186/2110] Prepare beta release --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 71d2eb1c7f..b7a1be4425 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.0.0-SNAPSHOT +5.0.0-BETA1 From 6da38592c73b4b7152df27a40db18eaf311cf8a3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 1 Mar 2018 23:30:50 +0100 Subject: [PATCH 1187/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b7a1be4425..71d2eb1c7f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.0.0-BETA1 +5.0.0-SNAPSHOT From 3aa2608bd712bcb844ffaa0ed296dbb5cb2b3e1d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 4 Mar 2018 21:43:03 +0100 Subject: [PATCH 1188/2110] Better exception message if non model class is provided as function argument --- CHANGELOG.md | 7 +++++++ .../src/androidTest/java/io/realm/RealmTests.java | 15 +++++++++++++++ .../src/main/java/io/realm/internal/Util.java | 8 ++++++++ 3 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 461c004f2b..7e2e428f57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.3.5 (YYY-MM-DD) + +## Bug Fixes + +* Better exception message if a non model class is provided to methods only accepting those (#5779). + + ## 4.3.4 (2018-02-06) ## Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index f1490c163c..9f2b87bd12 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -283,6 +283,21 @@ public void where() { assertEquals(TEST_DATA_SIZE, resultList.size()); } + @Test + public void where_throwsIfClassArgIsNotASubtype() { + try { + realm.where(RealmObject.class); + fail(); + } catch (IllegalArgumentException ignore) { + } + + try { + realm.where(RealmModel.class); + fail(); + } catch (IllegalArgumentException ignore) { + } + } + // Note that this test is relying on the values set while initializing the test dataset // TODO Move to RealmQueryTests? @Test diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 6dc7389628..594956506f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -47,6 +47,14 @@ public static String getTablePrefix() { * was a RealmProxy class. */ public static Class getOriginalModelClass(Class clazz) { + + // The compiler would allow these classes to be parsed as arguments, but they are never + // valid as an Realm model class + if (clazz.equals(RealmModel.class) || clazz.equals(RealmObject.class)) { + throw new IllegalArgumentException("RealmModel or RealmObject was parsed as an argument. " + + "Only subclasses of these can used as arguments to methods that accept a Realm model class."); + } + // This cast is correct because 'clazz' is either the type // generated by RealmProxy or the original type extending directly from RealmObject. @SuppressWarnings("unchecked") From 4df3cb64d44ea0d6d594e284da685cc3b21640ae Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 4 Mar 2018 21:44:12 +0100 Subject: [PATCH 1189/2110] Revert "Better exception message if non model class is provided as function argument" This reverts commit 3aa2608bd712bcb844ffaa0ed296dbb5cb2b3e1d. --- CHANGELOG.md | 7 ------- .../src/androidTest/java/io/realm/RealmTests.java | 15 --------------- .../src/main/java/io/realm/internal/Util.java | 8 -------- 3 files changed, 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e2e428f57..461c004f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,3 @@ -## 4.3.5 (YYY-MM-DD) - -## Bug Fixes - -* Better exception message if a non model class is provided to methods only accepting those (#5779). - - ## 4.3.4 (2018-02-06) ## Bug Fixes diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 9f2b87bd12..f1490c163c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -283,21 +283,6 @@ public void where() { assertEquals(TEST_DATA_SIZE, resultList.size()); } - @Test - public void where_throwsIfClassArgIsNotASubtype() { - try { - realm.where(RealmObject.class); - fail(); - } catch (IllegalArgumentException ignore) { - } - - try { - realm.where(RealmModel.class); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - // Note that this test is relying on the values set while initializing the test dataset // TODO Move to RealmQueryTests? @Test diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 594956506f..6dc7389628 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -47,14 +47,6 @@ public static String getTablePrefix() { * was a RealmProxy class. */ public static Class getOriginalModelClass(Class clazz) { - - // The compiler would allow these classes to be parsed as arguments, but they are never - // valid as an Realm model class - if (clazz.equals(RealmModel.class) || clazz.equals(RealmObject.class)) { - throw new IllegalArgumentException("RealmModel or RealmObject was parsed as an argument. " + - "Only subclasses of these can used as arguments to methods that accept a Realm model class."); - } - // This cast is correct because 'clazz' is either the type // generated by RealmProxy or the original type extending directly from RealmObject. @SuppressWarnings("unchecked") From db9c57112c13867e48e3a93179378bf617740eb8 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 6 Mar 2018 18:57:57 +0000 Subject: [PATCH 1190/2110] use correct hash for dependency --- dependencies.list | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.list b/dependencies.list index 993ad77a5c..1eb9c3c52b 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases REALM_SYNC_VERSION=3.0.0-beta.10 -REALM_SYNC_SHA256=7c36a38c0e5c0a46b22d5eee2b494bd9cc0219a526087a040ada86332f13401d -c +REALM_SYNC_SHA256=d5f2b1639efb5d64369d628c38e6d0698a1fbe6c2112b0f3321a85e170d6824e + # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. REALM_OBJECT_SERVER_DE_VERSION=3.0.0-alpha.8 From 3771b9a95bb539f05a7b032feedffce848a22eec Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Mar 2018 20:59:08 +0100 Subject: [PATCH 1191/2110] Prepare release 5.0.0-BETA1 --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e78eeebdcf..d09e364f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.0-BETA1 (2018-03-01) +## 5.0.0-BETA1 (2018-03-06) ### Known Bugs diff --git a/version.txt b/version.txt index 71d2eb1c7f..b7a1be4425 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.0.0-SNAPSHOT +5.0.0-BETA1 From c87d84299fc36c643398db1ca30c499cf01940e3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Mar 2018 22:20:15 +0100 Subject: [PATCH 1192/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b7a1be4425..71d2eb1c7f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.0.0-BETA1 +5.0.0-SNAPSHOT From a83b980e30665ef2a4aa9078bd505e9b09425dc8 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 12 Mar 2018 14:34:10 +0000 Subject: [PATCH 1193/2110] logout logging not resuming sync (#5820) * Fixes https://github.com/realm/my-first-realm-app/issues/22 logout/login resume syncing --- CHANGELOG.md | 1 + .../java/io/realm/SessionTests.java | 41 ++++++++++++++----- .../java/io/realm/SyncManager.java | 40 +++++++++++++++++- .../java/io/realm/SyncSession.java | 2 +- .../objectServer/java/io/realm/SyncUser.java | 9 +++- .../internal/SyncObjectServerFacade.java | 3 +- .../java/io/realm/SyncSessionTests.java | 1 - .../java/io/realm/SyncedRealmTests.java | 33 +++++++++++++++ 8 files changed, 112 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 461c004f2b..402cfc71a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Added missing `RealmQuery.oneOf()` for Kotlin that accepts non-nullable types (#5717). * [ObjectServer] Fixed an issue preventing sync to resume when the network is back (#5677). +* [ObjectServer] Fixed an issue where login after a logout will not resume Syncing (https://github.com/realm/my-first-realm-app/issues/22). ## 4.3.3 (2018-01-19) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 152a993251..e4144720b5 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -20,6 +20,7 @@ import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; +import org.hamcrest.CoreMatchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -39,6 +40,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -75,7 +77,7 @@ public void get_syncValues() { @Test public void addDownloadProgressListener_nullThrows() { - SyncSession session = SyncManager.getSession(configuration); + SyncSession session = SyncManager.getOrCreateSession(configuration, null); try { session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, null); fail(); @@ -85,7 +87,7 @@ public void addDownloadProgressListener_nullThrows() { @Test public void addUploadProgressListener_nullThrows() { - SyncSession session = SyncManager.getSession(configuration); + SyncSession session = SyncManager.getOrCreateSession(configuration, null); try { session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, null); fail(); @@ -96,7 +98,7 @@ public void addUploadProgressListener_nullThrows() { @Test public void removeProgressListener() { Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getSession(configuration); + SyncSession session = SyncManager.getOrCreateSession(configuration, null); ProgressListener[] listeners = new ProgressListener[] { null, progress -> { @@ -143,7 +145,7 @@ public void errorHandler_clientResetReported() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getSession(config)); + SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); } // Check that we can manually execute the Client Reset. @@ -181,7 +183,7 @@ public void errorHandler_manualExecuteClientReset() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getSession(config)); + SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); } // Check that we can use the backup SyncConfiguration to open the Realm. @@ -237,7 +239,7 @@ public void errorHandler_useBackupSyncConfigurationForClientReset() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getSession(config)); + SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); } // Check that we can open the backup file without using the provided SyncConfiguration, @@ -320,7 +322,7 @@ public void errorHandler_useBackupSyncConfigurationAfterClientReset() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getSession(config)); + SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); } // make sure the backup file Realm is encrypted with the same key as the original synced Realm. @@ -381,7 +383,7 @@ public void errorHandler_useClientResetEncrypted() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getSession(config)); + SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); } @Test @@ -389,7 +391,7 @@ public void errorHandler_useClientResetEncrypted() { public void uploadAllLocalChanges_throwsOnUiThread() throws InterruptedException { Realm realm = Realm.getInstance(configuration); try { - SyncManager.getSession(configuration).uploadAllLocalChanges(); + SyncManager.getOrCreateSession(configuration, null).uploadAllLocalChanges(); fail("Should throw an IllegalStateException on Ui Thread"); } catch (IllegalStateException ignored) { } finally { @@ -402,7 +404,7 @@ public void uploadAllLocalChanges_throwsOnUiThread() throws InterruptedException public void downloadAllServerChanges_throwsOnUiThread() throws InterruptedException { Realm realm = Realm.getInstance(configuration); try { - SyncManager.getSession(configuration).downloadAllServerChanges(); + SyncManager.getOrCreateSession(configuration, null).downloadAllServerChanges(); fail("Should throw an IllegalStateException on Ui Thread"); } catch (IllegalStateException ignored) { } finally { @@ -424,7 +426,7 @@ public void unrecognizedErrorCode_errorHandler() { }) .build(); Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getSession(configuration); + SyncSession session = SyncManager.getOrCreateSession(configuration, null); TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); RealmLog.add(testLogger); @@ -437,4 +439,21 @@ public void unrecognizedErrorCode_errorHandler() { realm.close(); } + + @Test + public void getSessionThrowsOnNonExistingSession() { + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getSession(configuration); + assertEquals(configuration, session.getConfiguration()); + + // Closing the Realm should remove the session + realm.close(); + try { + SyncManager.getSession(configuration); + fail("getSession should throw an ISE"); + } catch (IllegalStateException expected) { + assertThat(expected.getMessage(), CoreMatchers.containsString( + "No SyncSession found using the path : ")); + } + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 43381058c4..7e022ed57f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -19,6 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.cert.CertificateException; @@ -201,15 +202,43 @@ public static void setDefaultSessionErrorHandler(@Nullable SyncSession.ErrorHand } } + /** + * Gets a cached {@link SyncSession} for the given {@link SyncConfiguration} or throw if no one exists yet. + * + * A session should exist after you open a Realm with a {@link SyncConfiguration}. + * + * @param syncConfiguration configuration object for the synchronized Realm. + * @return the {@link SyncSession} for the specified Realm. + * @throws IllegalArgumentException if syncConfiguration is {@code null}. + * @throws IllegalStateException if the session could not be found using the provided {@code SyncConfiguration}. + */ + public static synchronized SyncSession getSession(SyncConfiguration syncConfiguration) throws IllegalStateException { + //noinspection ConstantConditions + if (syncConfiguration == null) { + throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); + } + + SyncSession session = sessions.get(syncConfiguration.getPath()); + if (session == null) { + throw new IllegalStateException("No SyncSession found using the path : " + syncConfiguration.getPath() + + "\nplease ensure to call this method after you've open the Realm"); + } + + return session; + } + /** * Gets any cached {@link SyncSession} for the given {@link SyncConfiguration} or create a new one if * no one exists. * + * Note: This is mainly for internal usage, consider using {@link #getSession(SyncConfiguration)} instead. + * * @param syncConfiguration configuration object for the synchronized Realm. + * @param resolvedRealmURL resolved Realm URL with the user specific part if not a global Realm. * @return the {@link SyncSession} for the specified Realm. * @throws IllegalArgumentException if syncConfiguration is {@code null}. */ - public static synchronized SyncSession getSession(SyncConfiguration syncConfiguration) { + public static synchronized SyncSession getOrCreateSession(SyncConfiguration syncConfiguration, @Nullable URI resolvedRealmURL) { // This will not create a new native (Object Store) session, this will only associate a Realm's path // with a SyncSession. Object Store's SyncManager is responsible of the life cycle (including creation) // of the native session, the provided Java wrap, helps interact with the native session, when reporting error @@ -228,6 +257,15 @@ public static synchronized SyncSession getSession(SyncConfiguration syncConfigur RealmLog.debug("first session created add network listener"); NetworkStateReceiver.addListener(networkListener); } + if (resolvedRealmURL != null) { + session.setResolvedRealmURI(resolvedRealmURL); + // Currently when the user login, the Object Store will try to revive it's inactive sessions + // (stored previously after a logout). this will cause the OS to call bindSession to obtain an + // access token, however since the Realm might not be open yet, the wrapObjectStoreSessionIfRequired + // will not be invoked to wrap the OS store session with the Java session, the Sync client to not resume + // syncing. + session.getAccessToken(authServer, ""); + } } return session; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 30ff8c8ad2..f05656a86f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -378,7 +378,7 @@ public void uploadAllLocalChanges() throws InterruptedException { } } - public void setResolvedRealmURI(URI resolvedRealmURI) { + void setResolvedRealmURI(URI resolvedRealmURI) { this.resolvedRealmURI = resolvedRealmURI; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index ce82386309..1fe9941442 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -33,6 +33,7 @@ import javax.annotation.Nullable; +import io.realm.exceptions.RealmException; import io.realm.internal.RealmNotifier; import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; @@ -229,9 +230,13 @@ public void logout() { // invalidate all pending refresh_token queries for (SyncConfiguration syncConfiguration : realms.keySet()) { - SyncSession session = SyncManager.getSession(syncConfiguration); - if (session != null) { + try { + SyncSession session = SyncManager.getSession(syncConfiguration); session.clearScheduledAccessTokenRefresh(); + } catch (IllegalStateException e) { + if (!e.getMessage().contains("No SyncSession found")) { + throw e; + }// else no session, either the Realm was not opened or session was removed. } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 2b8616e8d7..e75638e138 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -106,8 +106,7 @@ public static Context getApplicationContext() { @Override public void wrapObjectStoreSessionIfRequired(OsRealmConfig config) { if (config.getRealmConfiguration() instanceof SyncConfiguration) { - SyncSession session = SyncManager.getSession((SyncConfiguration) config.getRealmConfiguration()); - session.setResolvedRealmURI(config.getResolvedRealmURI()); + SyncManager.getOrCreateSession((SyncConfiguration) config.getRealmConfiguration(), config.getResolvedRealmURI()); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index cf3602bfe1..ccaf33d00a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -7,7 +7,6 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index 7eaa41f60a..70db449819 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -40,6 +40,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -49,6 +50,38 @@ @RunWith(AndroidJUnit4.class) public class SyncedRealmTests extends StandardIntegrationTest { + + @Test + public void loginLogoutResumeSyncing() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .schema(StringOnly.class) + .build(); + + Realm realm = Realm.getInstance(config); + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Foo"); + realm.commitTransaction(); + SyncManager.getSession(config).uploadAllLocalChanges(); + user.logout(); + realm.close(); + assertTrue(Realm.deleteRealm(config)); + + user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + SyncConfiguration config2 = new SyncConfiguration.Builder(user, Constants.USER_REALM) + .schema(StringOnly.class) + .build(); + + Realm realm2 = Realm.getInstance(config2); + SyncManager.getSession(config2).downloadAllServerChanges(); + realm2.refresh(); + assertEquals(1, realm2.where(StringOnly.class).count()); + realm2.close(); + } + @Test @UiThreadTest public void waitForInitialRemoteData_mainThreadThrows() { From d4939456a4691ce5647e9d11ced2089b18bc21a1 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 12 Mar 2018 19:42:05 +0000 Subject: [PATCH 1194/2110] Using a specific version of winston library (#5827) --- tools/sync_test_server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 8a9a766a41..29f8e46fad 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -10,7 +10,7 @@ ARG ROS_DE_VERSION RUN npm install -g realm-object-server@$ROS_DE_VERSION -S # Install test server dependencies -RUN npm install winston temp httpdispatcher@1.0.0 fs-extra moment +RUN npm install winston@2.4.0 temp httpdispatcher@1.0.0 fs-extra moment COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem configuration.yml / COPY ros-testing-server.js /usr/bin/ From 7aceeae04186a209bc8b71961de97482102e8667 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Mar 2018 11:40:52 +0100 Subject: [PATCH 1195/2110] Fix module example (#5830) --- .../realm/examples/appmodules/ModulesExampleActivity.java | 2 +- .../main/java/io/realm/examples/appmodules/model/Pig.java | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java index 780029cf8b..d08f35687a 100644 --- a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java +++ b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java @@ -75,7 +75,7 @@ protected void onCreate(Bundle savedInstanceState) { .modules(new ZooAnimalsModule(), new CreepyAnimalsModule()) .build(); - // Multiple Realms can be open at the same time + // Multiple Realms can be opened at the same time showStatus("Opening multiple Realms"); Realm defaultRealm = Realm.getInstance(defaultConfig); final Realm farmRealm = Realm.getInstance(farmAnimalsConfig); diff --git a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Pig.java b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Pig.java index e2cb83eb1b..ef9e5ba860 100644 --- a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Pig.java +++ b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/model/Pig.java @@ -16,18 +16,12 @@ package io.realm.examples.appmodules.model; -import io.realm.RealmList; import io.realm.RealmObject; -import io.realm.examples.librarymodules.model.Dog; public class Pig extends RealmObject { private String name; - // It is possible for model classes to to reference library model classes as long - // as they all are included in the schema when opening the Realm. - private RealmList afraidOf = new RealmList<>(); - public String getName() { return name; } From 6c1a8b263c323317aeb2940018d3fc7ce785cf2a Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 13 Mar 2018 11:49:15 +0000 Subject: [PATCH 1196/2110] update release date in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc898d950f..d4b6abdbdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.4.0 (YYYY-MM-DD) +## 4.4.0 (2018-03-13) ### Enhancements From 2930e2616329799348f605b5f9bbde52e62fd079 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 13 Mar 2018 11:51:01 +0000 Subject: [PATCH 1197/2110] Release v4.4.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 99fc609bd4..64b5ae3938 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.4.0-SNAPSHOT +4.4.0 \ No newline at end of file From bd2259362dfbaa90c90b2590d784252836cdb60b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 13 Mar 2018 11:51:01 +0000 Subject: [PATCH 1198/2110] Prepare next release v4.4.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 64b5ae3938..45963d3ece 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.4.0 \ No newline at end of file +4.4.1-SNAPSHOT \ No newline at end of file From 427283da2dc312f0ba8bd153db6baa6a397e3f3e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 14 Mar 2018 00:01:40 +0100 Subject: [PATCH 1199/2110] Align Sync API's with Cocoa (#5835) --- CHANGELOG.md | 3 + .../objectserver/CounterActivity.java | 4 +- .../examples/objectserver/LoginActivity.java | 2 +- .../MainActivity.java | 2 +- .../io/realm/AuthenticateRequestTests.java | 2 +- .../java/io/realm/SyncUserTests.java | 38 +++---- .../java/io/realm/SyncConfiguration.java | 6 +- .../java/io/realm/SyncCredentials.java | 24 ++-- .../java/io/realm/SyncManager.java | 2 +- .../objectServer/java/io/realm/SyncUser.java | 11 +- .../java/io/realm/SSLConfigurationTests.java | 34 +++--- .../java/io/realm/SyncSessionTests.java | 34 +++--- .../io/realm/SyncedRealmIntegrationTests.java | 32 +++--- .../java/io/realm/objectserver/AuthTests.java | 106 +++++++++--------- .../EncryptedSynchronizedRealmTests.java | 22 ++-- ...ObjectLevelPermissionIntegrationTests.java | 4 +- .../objectserver/ProcessCommitTests.java | 9 +- .../objectserver/ProgressListenerTests.java | 4 +- .../realm/objectserver/utils/UserFactory.java | 18 +-- 19 files changed, 174 insertions(+), 183 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a485a0472b..b09a45f904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ ### Breaking Changes +* [ObjectServer] Renamed `SyncUser.currentUser()` to `SyncUser.current()`. +* [ObjectServer] Renamed `SyncUser.login(...)` and `SyncUser.loginAsync(...)` to `SyncUser.logIn(...)` and `SyncUser.logInAsync(...)`. +* [ObjectServer] Renamed `SyncUser.logout()` to `SyncUser.logOut()`. * The `OrderedCollectionChangeSet` parameter in `OrderedRealmCollectionChangeListener.onChange()` is no longer nullable. Use `changeSet.getState()` instead (#5619). * `realm.subscribeForObjects()` have been removed. Use `RealmQuery.findAllAsync(String subscriptionName)` and `RealmQuery.findAllAsync()` instead. * Removed previously deprecated `RealmQuery.findAllSorted()`, `RealmQuery.findAllSortedAsync()` `RealmQuery.distinct() and `RealmQuery.distinctAsync()`. diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 1baf3a9e2f..6984f15bf9 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -146,7 +146,7 @@ public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case R.id.action_logout: closeRealm(); - user.logout(); + user.logOut(); user = getLoggedInUser(); return true; @@ -198,7 +198,7 @@ public void execute(@Nonnull Realm realm) { private SyncUser getLoggedInUser() { SyncUser user = null; - try { user = SyncUser.currentUser(); } + try { user = SyncUser.current(); } catch (IllegalStateException ignore) { } if (user == null) { diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java index 9bf5479f15..2f87b3a0bd 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -103,7 +103,7 @@ public void onError(@Nonnull ObjectServerError error) { } }; - SyncUser.loginAsync(creds, REALM_AUTH_URL, callback); + SyncUser.logInAsync(creds, REALM_AUTH_URL, callback); } @Override diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java index 6fff42f95e..ecce92c122 100644 --- a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java +++ b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java @@ -87,7 +87,7 @@ private void buildSyncConf() { final String urlAuth = "http://objectserver.realm.io:9080/auth"; final String url = "realm://objectserver.realm.io/default"; - SyncUser.loginAsync(credentials, urlAuth, new SyncUser.Callback() { + SyncUser.logInAsync(credentials, urlAuth, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index b2aaa7cc5a..957a1f196a 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -78,7 +78,7 @@ public void errorsNotWrapped() { SyncManager.setAuthServerImpl(authServer); try { - SyncUser.login(SyncCredentials.facebook("foo"), "http://foo.bar/auth"); + SyncUser.logIn(SyncCredentials.facebook("foo"), "http://foo.bar/auth"); fail(); } catch (ObjectServerError e) { assertEquals(ErrorCode.ACCESS_DENIED, e.getErrorCode()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 7cb5dfda6d..603eae46ae 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -141,8 +141,8 @@ public void equals_validUser() { public void equals_loggedOutUser() { final SyncUser user1 = createFakeUser("id_value"); final SyncUser user2 = createFakeUser("id_value"); - user1.logout(); - user2.logout(); + user1.logOut(); + user2.logOut(); assertTrue(user1.equals(user2)); } @@ -155,7 +155,7 @@ public void hashCode_validUser() { @Test public void hashCode_loggedOutUser() { final SyncUser user = createFakeUser("id_value"); - user.logout(); + user.logOut(); assertNotEquals(0, user.hashCode()); } @@ -174,7 +174,7 @@ public void currentUser_returnsNullIfUserExpired() { userStore.put(SyncTestUtils.createTestUser(Long.MIN_VALUE)); // Invalid users should not be returned when asking the for the current user - assertNull(SyncUser.currentUser()); + assertNull(SyncUser.current()); } @Test @@ -191,12 +191,12 @@ public AuthenticateResponse answer(InvocationOnMock invocationOnMock) throws Thr return getNewRandomUser(); } }); - SyncUser.login(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); - SyncUser.login(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); + SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); + SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); - // 2. Verify currentUser() now throws + // 2. Verify current() now throws try { - SyncUser.currentUser(); + SyncUser.current(); fail(); } catch (IllegalStateException ignore) { } @@ -220,11 +220,11 @@ public void currentUser_clearedOnLogout() { UserStore userStore = SyncManager.getUserStore(); userStore.put(user); - SyncUser savedUser = SyncUser.currentUser(); + SyncUser savedUser = SyncUser.current(); assertEquals(user, savedUser); assertNotNull(savedUser); - savedUser.logout(); - assertNull(SyncUser.currentUser()); + savedUser.logOut(); + assertNull(SyncUser.current()); } // `all()` returns an empty list if no users are logged in @@ -275,8 +275,8 @@ public void currentUser_returnsUserAfterLogin() { AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(Long.MAX_VALUE)); - SyncUser user = SyncUser.login(SyncCredentials.facebook("foo"), "http://bar.com/auth"); - assertEquals(user, SyncUser.currentUser()); + SyncUser user = SyncUser.logIn(SyncCredentials.facebook("foo"), "http://bar.com/auth"); + assertEquals(user, SyncUser.current()); } @Test @@ -295,7 +295,7 @@ public void login_withAccessToken() { SyncManager.setAuthServerImpl(authServer); try { SyncCredentials credentials = SyncCredentials.accessToken("foo", "bar"); - SyncUser user = SyncUser.login(credentials, "http://ros.realm.io/auth"); + SyncUser user = SyncUser.logIn(credentials, "http://ros.realm.io/auth"); assertTrue(user.isValid()); } finally { SyncManager.setAuthServerImpl(originalServer); @@ -324,9 +324,9 @@ public void login_appendAuthSegment() { String input = url[0]; String normalizedInput = url[1]; SyncCredentials credentials = SyncCredentials.accessToken("token", UUID.randomUUID().toString()); - SyncUser user = SyncUser.login(credentials, input); + SyncUser user = SyncUser.logIn(credentials, input); assertEquals(normalizedInput, user.getAuthenticationUrl().toString()); - user.logout(); + user.logOut(); } } finally { SyncManager.setAuthServerImpl(originalServer); @@ -446,8 +446,8 @@ public void getPermissionManger_instanceUniqueToUser() { } finally { pm1.close(); pm2.close(); - user1.logout(); - user2.logout(); + user1.logOut(); + user2.logOut(); } } @@ -553,7 +553,7 @@ public void execute(Realm realm) { realm.createObject(StringOnly.class).setChars("1"); } }); - user.logout(); + user.logOut(); realm.close(); final File realmPath = new File (syncConfiguration.getPath()); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 23516c66fb..88be3f6f9d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -42,15 +42,13 @@ import io.realm.log.RealmLog; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; -import io.realm.sync.permissions.PermissionUser; -import io.realm.sync.permissions.Role; /** * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm * Object Server. *

            * A valid {@link SyncUser} is required to create a {@link SyncConfiguration}. See {@link SyncCredentials} and - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} for more information on + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)} for more information on * how to get a user object. *

            * A minimal {@link SyncConfiguration} can be found below. @@ -903,7 +901,7 @@ private String MD5(String in) { /** * Setting this will cause the local Realm file used to synchronize changes to be deleted if the {@link SyncUser} - * owning this Realm logs out from the device using {@link SyncUser#logout()}. + * owning this Realm logs out from the device using {@link SyncUser#logOut()}. *

            * The default behavior is that the Realm file is allowed to stay behind, making it possible for users to log * in again and have access to their data faster. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 560bafff7a..5b8945feab 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -78,7 +78,7 @@ public class SyncCredentials { * * @param facebookToken a facebook userIdentifier acquired by logging into Facebook. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static SyncCredentials facebook(String facebookToken) { @@ -91,7 +91,7 @@ public static SyncCredentials facebook(String facebookToken) { * * @param googleToken a google userIdentifier acquired by logging into Google. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static SyncCredentials google(String googleToken) { @@ -104,7 +104,7 @@ public static SyncCredentials google(String googleToken) { * * @param jwtToken a JWT token that identifies the user. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if the token is either {@code null} or empty. */ public static SyncCredentials jwt(String jwtToken) { @@ -119,7 +119,7 @@ public static SyncCredentials jwt(String jwtToken) { * and it isn't possible to share the user details across devices. * * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. */ public static SyncCredentials anonymous() { return new SyncCredentials("", IdentityProvider.ANONYMOUS, null); @@ -134,7 +134,7 @@ public static SyncCredentials anonymous() { * * @param nickname that identifies a user * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if the nickname is either {@code null} or empty. */ public static SyncCredentials nickname(String nickname, boolean isAdmin) { @@ -154,7 +154,7 @@ public static SyncCredentials nickname(String nickname, boolean isAdmin) { * create a user twice when logging in, so this flag should only be set to {@code true} the first * time a users log in. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static SyncCredentials usernamePassword(String username, String password, boolean createUser) { @@ -172,7 +172,7 @@ public static SyncCredentials usernamePassword(String username, String password, * @param username username of the user. * @param password the users password. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static SyncCredentials usernamePassword(String username, String password) { @@ -189,7 +189,7 @@ public static SyncCredentials usernamePassword(String username, String password) * data will be serialized to JSON, so all values must be mappable to a valid JSON data type. Custom * classes will be converted using {@code toString()}. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)}. + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if any parameter is either {@code null} or empty. */ public static SyncCredentials custom(String userIdentifier, String identityProvider, @Nullable Map userInfo) { @@ -204,7 +204,7 @@ public static SyncCredentials custom(String userIdentifier, String identityProvi /** * Creates credentials from an existing access token. Since an access token is the proof that a user already * has logged in. Credentials created this way are automatically assumed to have successfully logged in. - * This means that providing these credentials to {@link SyncUser#login(SyncCredentials, String)} will always + * This means that providing these credentials to {@link SyncUser#logIn(SyncCredentials, String)} will always * succeed, but accessing any Realm after might fail if the token is no longer valid. *

            * It is assumed that this user is not an administrator. Otherwise use {@link #accessToken(String, String, boolean)}. @@ -212,7 +212,7 @@ public static SyncCredentials custom(String userIdentifier, String identityProvi * @param accessToken user's access token. * @param identifier user identifier. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)} */ public static SyncCredentials accessToken(String accessToken, String identifier) { return accessToken(accessToken, identifier, false); @@ -221,7 +221,7 @@ public static SyncCredentials accessToken(String accessToken, String identifier) /** * Creates credentials from an existing access token. Since an access token is the proof that a user already * has logged in. Credentials created this way are automatically assumed to have successfully logged in. - * This means that providing these credentials to {@link SyncUser#login(SyncCredentials, String)} will always + * This means that providing these credentials to {@link SyncUser#logIn(SyncCredentials, String)} will always * succeed, but accessing any Realm after might fail if the token is no longer valid. * * @param accessToken user's access token. @@ -230,7 +230,7 @@ public static SyncCredentials accessToken(String accessToken, String identifier) * non-privileged users. It is to not possible to upgrade a non-admin token to an admin token by setting this * value. It is purely informational. * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#loginAsync(SyncCredentials, String, SyncUser.Callback)} + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)} */ public static SyncCredentials accessToken(String accessToken, String identifier, boolean isAdmin) { HashMap userInfo = new HashMap(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 7e022ed57f..cc45224295 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -148,7 +148,7 @@ static void init(String appId, UserStore userStore) { /** * Set the {@link UserStore} used by the Realm Object Server to save user information. - * If no Userstore is specified {@link SyncUser#currentUser()} will always return {@code null}. + * If no Userstore is specified {@link SyncUser#current()} will always return {@code null}. * * @param userStore {@link UserStore} to use. * @throws IllegalArgumentException if {@code userStore} is {@code null}. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 1fe9941442..c7f98d65b9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -33,7 +33,6 @@ import javax.annotation.Nullable; -import io.realm.exceptions.RealmException; import io.realm.internal.RealmNotifier; import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; @@ -80,7 +79,7 @@ public class SyncUser { * expired. * @throws IllegalStateException if multiple users are logged in. */ - public static SyncUser currentUser() { + public static SyncUser current() { SyncUser user = SyncManager.getUserStore().getCurrent(); if (user != null && user.isValid()) { return user; @@ -135,7 +134,7 @@ public static SyncUser fromJson(String user) { * @throws ObjectServerError if the login failed. * @throws IllegalArgumentException if the URL is malformed. */ - public static SyncUser login(final SyncCredentials credentials, final String authenticationUrl) throws ObjectServerError { + public static SyncUser logIn(final SyncCredentials credentials, final String authenticationUrl) throws ObjectServerError { URL authUrl; try { authUrl = new URL(authenticationUrl); @@ -189,12 +188,12 @@ public static SyncUser login(final SyncCredentials credentials, final String aut * @return representation of the async task that can be used to cancel it if needed. * @throws IllegalArgumentException if not on a Looper thread. */ - public static RealmAsyncTask loginAsync(final SyncCredentials credentials, final String authenticationUrl, final Callback callback) { + public static RealmAsyncTask logInAsync(final SyncCredentials credentials, final String authenticationUrl, final Callback callback) { checkLooperThread("Asynchronous login is only possible from looper threads."); return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { @Override public SyncUser run() throws ObjectServerError { - return login(credentials, authenticationUrl); + return logIn(credentials, authenticationUrl); } }.start(); } @@ -218,7 +217,7 @@ public SyncUser run() throws ObjectServerError { // */ // this is a fire and forget, end user should not worry about the state of the async query @SuppressWarnings("FutureReturnValueIgnored") - public void logout() { + public void logOut() { // Acquire lock to prevent users creating new instances synchronized (Realm.class) { if (!SyncManager.getUserStore().isActive(identity, authenticationUrl.toString())) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index 7eb39fc43d..a899730c7e 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -53,7 +53,7 @@ public class SSLConfigurationTests extends StandardIntegrationTest { public void trustedRootCA() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server //noinspection unchecked @@ -69,11 +69,11 @@ public void trustedRootCA() throws InterruptedException { // make sure the changes gets to the server SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); - user.logout(); + user.logOut(); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. - user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") @@ -98,7 +98,7 @@ public void trustedRootCA() throws InterruptedException { public void withoutSSLVerification() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server //noinspection unchecked @@ -114,11 +114,11 @@ public void withoutSSLVerification() throws InterruptedException { // make sure the changes gets to the server SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); - user.logout(); + user.logOut(); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. - user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") @@ -143,7 +143,7 @@ public void withoutSSLVerification() throws InterruptedException { public void trustedRootCA_syncShouldFailWithoutTrustedCA() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server //noinspection unchecked @@ -159,11 +159,11 @@ public void trustedRootCA_syncShouldFailWithoutTrustedCA() throws InterruptedExc // make sure the changes gets to the server SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); - user.logout(); + user.logOut(); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. - user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") @@ -186,7 +186,7 @@ public void trustedRootCA_syncShouldFailWithoutTrustedCA() throws InterruptedExc public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); int originalLevel = RealmLog.getLevel(); @@ -213,7 +213,7 @@ public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { public void trustedRootCA_notExisting_certificate_willThrow() { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .schema(StringOnly.class) @@ -233,7 +233,7 @@ public void trustedRootCA_notExisting_certificate_willThrow() { public void combiningTrustedRootCA_and_disableSSLVerification() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server using ssl_verify_path option //noinspection unchecked @@ -250,11 +250,11 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt // make sure the changes gets to the server SyncManager.getSession(syncConfigWithCertificate).uploadAllLocalChanges(); realm.close(); - user.logout(); + user.logOut(); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. - user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigDisableSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") @@ -283,7 +283,7 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt public void sslVerifyCallback_isUsed() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server using ssl_verify_path option //noinspection unchecked @@ -299,11 +299,11 @@ public void sslVerifyCallback_isUsed() throws InterruptedException { // make sure the changes gets to the server SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); - user.logout(); + user.logOut(); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes. - user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigSecure = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 346177d527..8b33e38896 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -67,7 +67,7 @@ public void getState_throwOnClosedSession() { SyncSession session = SyncManager.getSession(syncConfiguration); realm.close(); - user.logout(); + user.logOut(); thrown.expect(IllegalStateException.class); thrown.expectMessage("Could not find session, Realm was probably closed"); session.getState(); @@ -83,7 +83,7 @@ public void getState_loggedOut() { SyncSession session = SyncManager.getSession(syncConfiguration); - user.logout(); + user.logOut(); SyncSession.State state = session.getState(); assertEquals(SyncSession.State.INACTIVE, state); @@ -180,7 +180,7 @@ public void run() { public void logout_sameSyncUserMultipleSessions() { String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); SyncConfiguration syncConfiguration1 = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) @@ -206,13 +206,13 @@ public void logout_sameSyncUserMultipleSessions() { assertEquals(session1.getUser(), session2.getUser()); - user.logout(); + user.logOut(); assertEquals(SyncSession.State.INACTIVE, session1.getState()); assertEquals(SyncSession.State.INACTIVE, session2.getState()); credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); - SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser.logIn(credentials, Constants.AUTH_URL); // reviving the sessions. The state could be changed concurrently. assertTrue(session1.getState() == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || @@ -229,7 +229,7 @@ public void logout_sameSyncUserMultipleSessions() { public void logBackResumeUpload() throws InterruptedException, NoSuchFieldException, IllegalAccessException { final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) @@ -247,7 +247,7 @@ public void execute(Realm realm) { final SyncSession session = SyncManager.getSession(syncConfiguration); session.uploadAllLocalChanges(); - user.logout(); + user.logOut(); // add a commit while we're still offline realm.executeTransaction(new Realm.Transaction() { @@ -270,7 +270,7 @@ public void run() { // when the offline commits get synchronized SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); SyncCredentials credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); - SyncUser adminUser = SyncUser.login(credentialsAdmin, Constants.AUTH_URL); + SyncUser adminUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) .modules(new StringOnlyModule()) @@ -298,7 +298,7 @@ public void onChange(RealmResults stringOnlies) { // this login will re-activate the logged out user, and resume all it's pending sessions // the OS will trigger bindSessionWithConfig with the new refresh_token, in order to obtain // a new access_token. - SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser.logIn(credentials, Constants.AUTH_URL); } }); @@ -313,7 +313,7 @@ public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { final List strongRefs = new ArrayList<>(); final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final char[] chars = new char[1_000_000];// 2MB Arrays.fill(chars, '.'); @@ -346,7 +346,7 @@ public void run() { // using an admin user to open the Realm on different path on the device to monitor when all the uploads are done SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); SyncCredentials credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); - SyncUser adminUser = SyncUser.login(credentialsAdmin, Constants.AUTH_URL); + SyncUser adminUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) .modules(new StringOnlyModule()) @@ -374,7 +374,7 @@ public void onChange(RealmResults stringOnlies) { TestHelper.awaitOrFail(testCompleted, 60); handlerThread.join(); - user.logout(); + user.logOut(); } // A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. @@ -382,7 +382,7 @@ public void onChange(RealmResults stringOnlies) { public void downloadChangesWhenRealmOutOfScope() throws InterruptedException { final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) @@ -398,11 +398,11 @@ public void downloadChangesWhenRealmOutOfScope() throws InterruptedException { session.uploadAllLocalChanges(); // Log out the user. - user.logout(); + user.logOut(); // Log the user back in. credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); - SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser.logIn(credentials, Constants.AUTH_URL); // now let the admin upload some commits final CountDownLatch backgroundUpload = new CountDownLatch(1); @@ -417,7 +417,7 @@ public void run() { // using an admin user to open the Realm on different path on the device then some commits SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); SyncCredentials credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); - SyncUser adminUser = SyncUser.login(credentialsAdmin, Constants.AUTH_URL); + SyncUser adminUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) .modules(new StringOnlyModule()) @@ -458,7 +458,7 @@ public void run() { public void clientReset_manualTriggerAllowSessionToRestart() { final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final AtomicReference configRef = new AtomicReference<>(null); final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 5f2df02232..05c6ff67da 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -55,7 +55,7 @@ public class SyncedRealmIntegrationTests extends StandardIntegrationTest { public void loginLogoutResumeSyncing() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) .schema(StringOnly.class) @@ -66,11 +66,11 @@ public void loginLogoutResumeSyncing() throws InterruptedException { realm.createObject(StringOnly.class).setChars("Foo"); realm.commitTransaction(); SyncManager.getSession(config).uploadAllLocalChanges(); - user.logout(); + user.logOut(); realm.close(); assertTrue(Realm.deleteRealm(config)); - user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); SyncConfiguration config2 = new SyncConfiguration.Builder(user, Constants.USER_REALM) .schema(StringOnly.class) .build(); @@ -108,7 +108,7 @@ public void waitForInitialRemoteData_mainThreadThrows() { public void waitForInitialRemoteData() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) @@ -126,11 +126,11 @@ public void execute(Realm realm) { }); SyncManager.getSession(configOld).uploadAllLocalChanges(); realm.close(); - user.logout(); + user.logOut(); // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.login(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) .name("newRealm") .schema(StringOnly.class) @@ -161,7 +161,7 @@ public void execute(Realm realm) { " https://github.com/realm/realm-java/issues/5416") public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) .waitForInitialRemoteData() .build(); @@ -198,7 +198,7 @@ public void run() { @Ignore("See https://github.com/realm/realm-java/issues/5373") public void waitForInitialData_resilientInCaseOfRetriesAsync() { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .directory(configurationFactory.getRoot()) @@ -228,7 +228,7 @@ public void onError(Throwable exception) { public void waitForInitialRemoteData_readOnlyTrue() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) @@ -245,11 +245,11 @@ public void execute(Realm realm) { }); SyncManager.getSession(configOld).uploadAllLocalChanges(); realm.close(); - user.logout(); + user.logOut(); // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .name("newRealm") .waitForInitialRemoteData() @@ -261,13 +261,13 @@ public void execute(Realm realm) { realm = Realm.getInstance(configNew); assertEquals(10, realm.where(StringOnly.class).count()); realm.close(); - user.logout(); + user.logOut(); } @Test public void waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .waitForInitialRemoteData() .readOnly() @@ -286,14 +286,14 @@ public void waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { if (realm != null) { realm.close(); } - user.logout(); + user.logOut(); } } @Test public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema .schema(StringOnly.class) // This schema should be written when opening the empty Realm. @@ -306,7 +306,7 @@ public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { assertEquals(0, realm.where(StringOnly.class).count()); } finally { realm.close(); - user.logout(); + user.logOut(); } } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 14c971cc03..009a988e86 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -65,7 +65,7 @@ public class AuthTests extends StandardIntegrationTest { public void login_userNotExist() { SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); try { - SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser.logIn(credentials, Constants.AUTH_URL); fail(); } catch (ObjectServerError expected) { assertEquals(ErrorCode.INVALID_CREDENTIALS, expected.getErrorCode()); @@ -76,7 +76,7 @@ public void login_userNotExist() { @RunTestInLooperThread public void loginAsync_userNotExist() { SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { fail(); @@ -95,7 +95,7 @@ public void onError(ObjectServerError error) { public void login_newUser() { String userId = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(userId, "password", true); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { assertFalse(user.isAdmin()); @@ -119,7 +119,7 @@ public void onError(ObjectServerError error) { public void login_withAccessToken() { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); SyncCredentials credentials = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(adminUser).value(), "custom-admin-user", adminUser.isAdmin()); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { assertTrue(user.isAdmin()); @@ -144,7 +144,7 @@ public void onError(ObjectServerError error) { @RunTestInLooperThread public void login_withAnonymous() { SyncCredentials credentials = SyncCredentials.anonymous(); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { assertFalse(user.isAdmin()); @@ -170,7 +170,7 @@ public void onError(ObjectServerError error) { @RunTestInLooperThread public void login_withNickname() { SyncCredentials credentials = SyncCredentials.nickname("foo", false); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { assertFalse(user.isAdmin()); @@ -196,7 +196,7 @@ public void onError(ObjectServerError error) { @RunTestInLooperThread public void login_withNicknameAsAdmin() { SyncCredentials credentials = SyncCredentials.nickname("foo", true); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { assertTrue(user.isAdmin()); @@ -234,7 +234,7 @@ public void run() { @Override public void run() { SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); - SyncUser.loginAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { + SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { fail(); @@ -264,17 +264,17 @@ public void changePassword() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; SyncCredentials credentials = SyncCredentials.usernamePassword(username, originalPassword, true); - SyncUser userOld = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser userOld = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(userOld.isValid()); // Change password and try to log in with new password String newPassword = "new-password"; userOld.changePassword(newPassword); - userOld.logout(); + userOld.logOut(); // Make sure old password doesn't work try { - SyncUser.login(SyncCredentials.usernamePassword(username, originalPassword, false), Constants.AUTH_URL); + SyncUser.logIn(SyncCredentials.usernamePassword(username, originalPassword, false), Constants.AUTH_URL); fail(); } catch (ObjectServerError e) { assertEquals(ErrorCode.INVALID_CREDENTIALS, e.getErrorCode()); @@ -282,7 +282,7 @@ public void changePassword() { // Then login with new password credentials = SyncCredentials.usernamePassword(username, newPassword, false); - SyncUser userNew = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser userNew = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(userNew.isValid()); assertEquals(userOld.getIdentity(), userNew.getIdentity()); } @@ -292,7 +292,7 @@ public void changePassword_using_admin() { String username = UUID.randomUUID().toString(); String originalPassword = "password"; SyncCredentials credentials = SyncCredentials.usernamePassword(username, originalPassword, true); - SyncUser userOld = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser userOld = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(userOld.isValid()); // Login an admin user @@ -305,9 +305,9 @@ public void changePassword_using_admin() { adminUser.changePassword(userOld.getIdentity(), newPassword); // Try to log in with new password - userOld.logout(); + userOld.logOut(); credentials = SyncCredentials.usernamePassword(username, newPassword, false); - SyncUser userNew = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser userNew = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(userNew.isValid()); assertEquals(userOld.getIdentity(), userNew.getIdentity()); @@ -319,7 +319,7 @@ public void changePassword_using_admin_async() { final String username = UUID.randomUUID().toString(); final String originalPassword = "password"; final SyncCredentials credentials = SyncCredentials.usernamePassword(username, originalPassword, true); - final SyncUser userOld = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncUser userOld = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(userOld.isValid()); // Login an admin user @@ -335,9 +335,9 @@ public void onSuccess(SyncUser administratorUser) { assertEquals(adminUser, administratorUser); // Try to log in with new password - userOld.logout(); + userOld.logOut(); SyncCredentials credentials = SyncCredentials.usernamePassword(username, newPassword, false); - SyncUser userNew = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser userNew = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(userNew.isValid()); assertEquals(userOld.getIdentity(), userNew.getIdentity()); @@ -358,7 +358,7 @@ public void changePassword_throwWhenUserIsLoggedOut() { String username = UUID.randomUUID().toString(); String password = "password"; SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); SyncManager.addAuthenticationListener(new AuthenticationListener() { @Override public void loggedIn(SyncUser user) { @@ -388,7 +388,7 @@ public void run() { looperThread.testComplete(); } }); - user.logout(); + user.logOut(); } @Test @@ -397,7 +397,7 @@ public void cachedInstanceShouldNotThrowIfRefreshTokenExpires() throws Interrupt String password = "password"; SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = spy(SyncUser.login(credentials, Constants.AUTH_URL)); + final SyncUser user = spy(SyncUser.logIn(credentials, Constants.AUTH_URL)); when(user.isValid()).thenReturn(true, false); @@ -427,7 +427,7 @@ public void run() { realm.close(); cachedInstance.close(); - user.logout(); + user.logOut(); } @Test @@ -436,9 +436,9 @@ public void buildingSyncConfigurationShouldThrowIfInvalidUser() { String password = "password"; SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); - SyncUser currentUser = SyncUser.currentUser(); - user.logout(); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + SyncUser currentUser = SyncUser.current(); + user.logOut(); assertFalse(user.isValid()); @@ -466,9 +466,9 @@ public void usingConfigurationWithInvalidUserShouldThrow() { String password = "password"; SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); RealmConfiguration configuration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); - user.logout(); + user.logOut(); assertFalse(user.isValid()); Realm instance = Realm.getInstance(configuration); instance.close(); @@ -477,9 +477,9 @@ public void usingConfigurationWithInvalidUserShouldThrow() { @Test public void logout_currentUserMoreThanOne() { UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncUser.currentUser().logout(); + SyncUser.current().logOut(); SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - assertEquals(user, SyncUser.currentUser()); + assertEquals(user, SyncUser.current()); } // logging out 'user' should have the same impact on other instance(s) of the same user @@ -489,36 +489,36 @@ public void loggingOutUserShouldImpactOtherInstances() throws InterruptedExcepti String password = "password"; SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); - SyncUser currentUser = SyncUser.currentUser(); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + SyncUser currentUser = SyncUser.current(); assertTrue(user.isValid()); assertEquals(user, currentUser); - user.logout(); + user.logOut(); assertFalse(user.isValid()); assertFalse(currentUser.isValid()); } - // logging out 'currentUser' should have the same impact on other instance(s) of the user + // logging out 'current' should have the same impact on other instance(s) of the user @Test public void loggingOutCurrentUserShouldImpactOtherInstances() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); - SyncUser currentUser = SyncUser.currentUser(); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + SyncUser currentUser = SyncUser.current(); assertTrue(user.isValid()); assertEquals(user, currentUser); - SyncUser.currentUser().logout(); + SyncUser.current().logOut(); assertFalse(user.isValid()); assertFalse(currentUser.isValid()); - assertNull(SyncUser.currentUser()); + assertNull(SyncUser.current()); } // verify that multiple users can be logged in at the same time @@ -530,7 +530,7 @@ public void multipleUsersCanBeLoggedInSimultaneously() { for (int i = 0; i < users.length; i++) { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), password, true); - users[i] = SyncUser.login(credentials, Constants.AUTH_URL); + users[i] = SyncUser.logIn(credentials, Constants.AUTH_URL); } for (int i = 0; i < users.length; i++) { @@ -538,7 +538,7 @@ public void multipleUsersCanBeLoggedInSimultaneously() { } for (int i = 0; i < users.length; i++) { - users[i].logout(); + users[i].logOut(); } for (int i = 0; i < users.length; i++) { @@ -555,17 +555,17 @@ public void singleUserCanBeLoggedInAndOutRepeatedly() { // register the user the first time SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(user.isValid()); - user.logout(); + user.logOut(); assertFalse(user.isValid()); // on subsequent logins, the user is already registered. credentials = credentials = SyncCredentials.usernamePassword(username, password, false); for (int i = 0; i < 3; i++) { - user = SyncUser.login(credentials, Constants.AUTH_URL); + user = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(user.isValid()); - user.logout(); + user.logOut(); assertFalse(user.isValid()); } } @@ -576,7 +576,7 @@ public void revokedRefreshTokenIsNotSameAfterLogin() throws InterruptedException final String uniqueName = UUID.randomUUID().toString(); final SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final Token revokedRefreshToken = SyncTestUtils.getRefreshToken(user); SyncManager.addAuthenticationListener(new AuthenticationListener() { @@ -588,7 +588,7 @@ public void loggedIn(SyncUser user) { @Override public void loggedOut(SyncUser user) { SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); - SyncUser loggedInUser = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser loggedInUser = SyncUser.logIn(credentials, Constants.AUTH_URL); Token token = SyncTestUtils.getRefreshToken(loggedInUser); // still comparing the same user @@ -601,7 +601,7 @@ public void loggedOut(SyncUser user) { } }); - user.logout(); + user.logOut(); TestHelper.awaitOrFail(userLoggedInAgain); } @@ -684,7 +684,7 @@ public void retrieve() { final String username = UUID.randomUUID().toString(); final String password = "password"; final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(user.isValid()); String identity = user.getIdentity(); @@ -708,7 +708,7 @@ public void retrieve_logout() { final String username = UUID.randomUUID().toString(); final String password = "password"; final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final String identity = user.getIdentity(); // unless the refresh_token is revoked (via logout) the admin user can still retrieve the user @@ -746,7 +746,7 @@ public void run() { } }); - user.logout(); + user.logOut(); } @Test @@ -762,7 +762,7 @@ public void retrieve_invalidProvider() { final String username = UUID.randomUUID().toString(); final String password = "password"; final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(user.isValid()); SyncUserInfo userInfo = adminUser.retrieveInfoForUser("username", "invalid"); @@ -774,13 +774,13 @@ public void retrieve_notAdmin() { final String username1 = UUID.randomUUID().toString(); final String password1 = "password"; final SyncCredentials credentials1 = SyncCredentials.usernamePassword(username1, password1, true); - final SyncUser user1 = SyncUser.login(credentials1, Constants.AUTH_URL); + final SyncUser user1 = SyncUser.logIn(credentials1, Constants.AUTH_URL); assertTrue(user1.isValid()); final String username2 = UUID.randomUUID().toString(); final String password2 = "password"; final SyncCredentials credentials2 = SyncCredentials.usernamePassword(username2, password2, true); - final SyncUser user2 = SyncUser.login(credentials2, Constants.AUTH_URL); + final SyncUser user2 = SyncUser.logIn(credentials2, Constants.AUTH_URL); assertTrue(user2.isValid()); // trying to lookup user2 using user1 should not work (requires admin token) @@ -797,7 +797,7 @@ public void retrieve_async() { final String username = UUID.randomUUID().toString(); final String password = "password"; final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL); + final SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); assertTrue(user.isValid()); // Login an admin user diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 9c0418233c..7c8b64a2f7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -43,7 +43,7 @@ public void setEncryptionKey_canReOpenRealmWithoutKey() { // STEP 1: open a synced Realm using a local encryption key String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); final byte[] randomKey = TestHelper.getRandomKey(); @@ -69,11 +69,11 @@ public void onError(SyncSession session, ObjectServerError error) { // STEP 2: make sure the changes gets to the server SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. realm.close(); - user.logout(); + user.logOut(); // STEP 3: try to open again the same sync Realm but different local name without the encryption key should not // fail - user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); SyncConfiguration configWithoutEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .name("newName") .modules(new StringOnlyModule()) @@ -92,7 +92,7 @@ public void onError(SyncSession session, ObjectServerError error) { assertEquals("Hi Alice", all.get(0).getChars()); realm.close(); - user.logout(); + user.logOut(); } // If an encrypted synced Realm is re-opened with the wrong key, throw an exception. @@ -101,7 +101,7 @@ public void setEncryptionKey_shouldCrashIfKeyNotProvided() throws InterruptedExc // STEP 1: open a synced Realm using a local encryption key String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); final byte[] randomKey = TestHelper.getRandomKey(); @@ -128,10 +128,10 @@ public void onError(SyncSession session, ObjectServerError error) { SyncManager.getSession(configWithEncryption).uploadAllLocalChanges(); realm.close(); - user.logout(); + user.logOut(); // STEP 3: try to open again the Realm without the encryption key should fail - user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); SyncConfiguration configWithoutEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .modules(new StringOnlyModule()) .waitForInitialRemoteData() @@ -160,7 +160,7 @@ public void setEncryptionKey_differentClientsWithDifferentKeys() throws Interrup // STEP 1: prepare a synced Realm for client A String username = UUID.randomUUID().toString(); String password = "password"; - SyncUser user = SyncUser.login(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); final byte[] randomKey = TestHelper.getRandomKey(); @@ -190,7 +190,7 @@ public void onError(SyncSession session, ObjectServerError error) { // STEP 3: prepare a synced Realm for client B (admin user) SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); SyncCredentials credentials = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); - SyncUser adminUser = SyncUser.login(credentials, Constants.AUTH_URL); + SyncUser adminUser = SyncUser.logIn(credentials, Constants.AUTH_URL); final byte[] adminRandomKey = TestHelper.getRandomKey(); @@ -235,9 +235,9 @@ public void onError(SyncSession session, ObjectServerError error) { assertEquals("Hi Bob", allSortedAdmin.get(1).getChars()); adminRealm.close(); - adminUser.logout(); + adminUser.logOut(); realm.close(); - user.logout(); + user.logOut(); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java index 5110ce45f0..c21a4c8c5d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java @@ -17,10 +17,8 @@ import android.os.Handler; import android.os.HandlerThread; -import android.os.Looper; import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -266,7 +264,7 @@ public void onSuccess() { AndroidCapabilities.EMULATE_MAIN_THREAD = oldValue; pm.close(); realm.close(); - adminUser.logout(); + adminUser.logOut(); setupRealm.countDown(); }); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index c658818171..214af8c97d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -33,7 +33,6 @@ import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; import io.realm.SyncManager; -import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.TestHelper; import io.realm.annotations.RealmModule; @@ -108,7 +107,7 @@ protected void run() { @Override protected void run() { getService().getRealm().close(); - user.logout(); + user.logOut(); } }; } @@ -138,7 +137,7 @@ public void onChange(RealmResults element) { assertEquals(1, all.size()); assertEquals("Background_Process1", all.get(0).getName()); realm.close(); - user.logout(); + user.logOut(); remoteService.triggerServiceStep(SimpleCommitRemoteService.stepB_closeRealmAndLogOut); @@ -186,7 +185,7 @@ protected void run() { @Override protected void run() { getService().getRealm().close(); - user.logout(); + user.logOut(); } }; } @@ -228,7 +227,7 @@ public void onChange(RealmResults element) { if (counter == 10) { remoteService.triggerServiceStep(ALotCommitsRemoteService.stepC_closeRealm); realm.close(); - user.logout(); + user.logOut(); looperThread.testComplete(); } else { remoteService.triggerServiceStep(ALotCommitsRemoteService.stepB_createObjects); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index 9183adda5e..5efddd5eb7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -219,8 +219,8 @@ public void onChange(Progress progress) { adminRealm.close(); // worker thread will hang if logout happens before listener triggered. worker.join(); - userWithData.logout(); - adminUser.logout(); + userWithData.logOut(); + adminUser.logOut(); } // Make sure that a ProgressListener continues to report the correct thing, even if it crashed diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index 987177ebfd..ccfd1c2f85 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -18,20 +18,14 @@ import android.os.Handler; import android.os.HandlerThread; -import android.os.SystemClock; import java.util.Map; import java.util.UUID; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicInteger; -import io.realm.AuthenticationListener; -import io.realm.ErrorCode; -import io.realm.ObjectServerError; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.SyncCredentials; -import io.realm.SyncManager; import io.realm.SyncUser; import io.realm.TestHelper; import io.realm.internal.ObjectServerFacade; @@ -62,7 +56,7 @@ private UserFactory(String userName) { public SyncUser loginWithDefaultUser(String authUrl) { SyncCredentials credentials = SyncCredentials.usernamePassword(userName, PASSWORD, false); - return SyncUser.login(credentials, authUrl); + return SyncUser.logIn(credentials, authUrl); } /** @@ -83,25 +77,25 @@ public static SyncUser createUniqueUser(String authUrl) { private static SyncUser createUser(String username, String authUrl) { SyncCredentials credentials = SyncCredentials.usernamePassword(username, PASSWORD, true); - return SyncUser.login(credentials, authUrl); + return SyncUser.logIn(credentials, authUrl); } public SyncUser createDefaultUser(String authUrl) { SyncCredentials credentials = SyncCredentials.usernamePassword(userName, PASSWORD, true); - return SyncUser.login(credentials, authUrl); + return SyncUser.logIn(credentials, authUrl); } public static SyncUser createAdminUser(String authUrl) { // `admin` required as user identifier to be granted admin rights. // ROS 2.0 comes with a default admin user named "realm-admin" with password "". SyncCredentials credentials = SyncCredentials.usernamePassword("realm-admin", "", false); - return SyncUser.login(credentials, authUrl); + return SyncUser.logIn(credentials, authUrl); } public static SyncUser createNicknameUser(String authUrl, String nickname, boolean isAdmin) { SyncCredentials credentials = SyncCredentials.nickname(nickname, isAdmin); - return SyncUser.login(credentials, authUrl); + return SyncUser.logIn(credentials, authUrl); } // Since we don't have a reliable way to reset the sync server and client, just use a new user factory for every @@ -156,7 +150,7 @@ public static void logoutAllUsers() { public void run() { Map users = SyncUser.all(); for (SyncUser user : users.values()) { - user.logout(); + user.logOut(); } allUsersLoggedOut.countDown(); From 44daf5871276c9ccb13cc1d1a10ff87557bf6828 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 14 Mar 2018 11:35:15 +0100 Subject: [PATCH 1200/2110] Better exception message if non model class is provided (#5796) --- CHANGELOG.md | 7 +++++++ .../src/androidTest/java/io/realm/RealmTests.java | 15 +++++++++++++++ .../src/main/java/io/realm/internal/Util.java | 8 ++++++++ 3 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4b6abdbdc..43d39d9be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.4.1 (YYY-MM-DD) + +## Bug Fixes + +* Better exception message if a non model class is provided to methods only accepting those (#5779). + + ## 4.4.0 (2018-03-13) ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 4f2442b72e..65be8cd651 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -283,6 +283,21 @@ public void where() { assertEquals(TEST_DATA_SIZE, resultList.size()); } + @Test + public void where_throwsIfClassArgIsNotASubtype() { + try { + realm.where(RealmObject.class); + fail(); + } catch (IllegalArgumentException ignore) { + } + + try { + realm.where(RealmModel.class); + fail(); + } catch (IllegalArgumentException ignore) { + } + } + // Note that this test is relying on the values set while initializing the test dataset // TODO Move to RealmQueryTests? @Test diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 6dc7389628..2e99d61680 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -47,6 +47,14 @@ public static String getTablePrefix() { * was a RealmProxy class. */ public static Class getOriginalModelClass(Class clazz) { + + // The compiler would allow these classes to be passed as arguments, but they are never + // valid as a Realm model class + if (clazz.equals(RealmModel.class) || clazz.equals(RealmObject.class)) { + throw new IllegalArgumentException("RealmModel or RealmObject was passed as an argument. " + + "Only subclasses of these can be used as arguments to methods that accept a Realm model class."); + } + // This cast is correct because 'clazz' is either the type // generated by RealmProxy or the original type extending directly from RealmObject. @SuppressWarnings("unchecked") From bba4adb99af8d38c84c627c528402c5331e74f37 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 14 Mar 2018 10:47:59 +0000 Subject: [PATCH 1201/2110] deleteRealm throwing in loginLogoutResumeSyncing (#5836) - Workaround deleteRealm throwing in test - Fixed Changelog entries --- CHANGELOG.md | 5 ++++- .../java/io/realm/SyncedRealmTests.java | 14 +++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43d39d9be6..9594e11658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ * Added support for mapping between a Java name and the underlying name in the Realm file using `@RealmModule`, `@RealmClass` and `@RealmField` annotations (#5280). +## Bug Fixes + +* [ObjectServer] Fixed an issue where login after a logout will not resume Syncing (https://github.com/realm/my-first-realm-app/issues/22). + ## 4.3.4 (2018-02-06) @@ -18,7 +22,6 @@ * Added missing `RealmQuery.oneOf()` for Kotlin that accepts non-nullable types (#5717). * [ObjectServer] Fixed an issue preventing sync to resume when the network is back (#5677). -* [ObjectServer] Fixed an issue where login after a logout will not resume Syncing (https://github.com/realm/my-first-realm-app/issues/22). ## 4.3.3 (2018-01-19) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java index 70db449819..a3449de67e 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmTests.java @@ -59,6 +59,7 @@ public void loginLogoutResumeSyncing() throws InterruptedException { SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) .schema(StringOnly.class) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); Realm realm = Realm.getInstance(config); @@ -68,7 +69,18 @@ public void loginLogoutResumeSyncing() throws InterruptedException { SyncManager.getSession(config).uploadAllLocalChanges(); user.logout(); realm.close(); - assertTrue(Realm.deleteRealm(config)); + try { + assertTrue(Realm.deleteRealm(config)); + } catch (IllegalStateException e) { + // FIXME: We don't have a way to ensure that the Realm instance on client thread has been + // closed for now. + // https://github.com/realm/realm-java/issues/5416 + if (e.getMessage().contains("It's not allowed to delete the file")) { + // retry after 1 second + SystemClock.sleep(1000); + assertTrue(Realm.deleteRealm(config)); + } + } user = SyncUser.login(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); SyncConfiguration config2 = new SyncConfiguration.Builder(user, Constants.USER_REALM) From a9e4566d2e0f42e226fe3b83bd0f934e8c063c08 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 14 Mar 2018 22:29:40 +0000 Subject: [PATCH 1202/2110] Update dependencies (#5834) * Partial Sync is needed to create the Reference Realm for permission tests * Add partial Sync to create the reference Realm * Using latest OS --- dependencies.list | 6 +++--- .../java/io/realm/ObjectLevelPermissionsTest.java | 11 +++++++++-- realm/realm-library/src/main/cpp/object-store | 2 +- .../ObjectLevelPermissionIntegrationTests.java | 1 + .../java/io/realm/objectserver/PartialSyncTests.java | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/dependencies.list b/dependencies.list index 1eb9c3c52b..f58dbb65cb 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.0.0-beta.10 -REALM_SYNC_SHA256=d5f2b1639efb5d64369d628c38e6d0698a1fbe6c2112b0f3321a85e170d6824e +REALM_SYNC_VERSION=3.0.0 +REALM_SYNC_SHA256=9141177ccc92d8f9282625dace61eee5c3d971d2daca7593266e175b610a24cf # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=3.0.0-alpha.8 +REALM_OBJECT_SERVER_DE_VERSION=3.0.0-rc.1 diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java index f2eb2f2c3d..111733af79 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java @@ -433,10 +433,17 @@ public void getClassPermissions_closedRealmThrows() { @Test public void getRoles() { RealmResults roles = realm.getRoles(); - assertEquals(1, roles.size()); - Role role = roles.first(); + assertEquals(2, roles.size()); + + roles = roles.where().sort("name").findAll(); + Role role = roles.get(0); + assertEquals("__User:" + user.getIdentity(), role.getName()); + assertTrue(role.hasMember(user.getIdentity())); + + role = roles.get(1); assertEquals("everyone", role.getName()); assertTrue(role.hasMember(user.getIdentity())); + } @Test diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index bb559df923..f2a536d29d 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit bb559df9237ece49f9c889993f7c1aff619b48f9 +Subproject commit f2a536d29de48e34e60799a5bf3f36e13806387e diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java index c21a4c8c5d..c3f7979701 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java @@ -250,6 +250,7 @@ private void createWorldReadableRealm(String realmUrl, List modules) { SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, realmUrl) .modules(modules) .addModule(new PermissionModule()) + .partialRealm() .waitForInitialRemoteData() .build(); Realm.getInstanceAsync(syncConfig, new Realm.Callback() { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java index 4224dc8e6a..accd479d25 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java @@ -309,10 +309,10 @@ private Realm getPartialRealm(SyncUser user) { private void createServerData(SyncUser user, String url) throws InterruptedException { final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, url) .waitForInitialRemoteData() + .partialRealm() .modules(new PartialSyncModule()) .build(); - // Create server data // Create server data Realm realm = Realm.getInstance(syncConfig); realm.beginTransaction(); From 09a7dc2f99ac0465633f8ef13f541fdffbf0d4e9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 15 Mar 2018 00:38:36 +0100 Subject: [PATCH 1203/2110] Add support for automatic configurations (#5822) --- CHANGELOG.md | 6 ++ .../java/io/realm/SyncConfigurationTests.java | 85 ++++++++++++++++++- .../java/io/realm/SyncConfiguration.java | 62 ++++++++++++++ .../io/realm/SyncedRealmIntegrationTests.java | 17 ++++ 4 files changed, 169 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b09a45f904..b243f799a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.0 (YYYY-MM-DD) + +## Enhancements + +* [ObjectServer] Added `SyncConfiguration.automatic()` and `SyncConfiguration.automatic(SyncUser user)` (#5806). + ## 5.0.0-BETA1 (2018-03-06) ### Known Bugs diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 9fb9deda0b..fe5195774f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -29,12 +29,14 @@ import java.io.File; import java.io.IOException; +import java.net.URI; import java.util.HashMap; import java.util.Map; import io.realm.entities.StringOnly; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; +import io.realm.util.SyncTestUtils; import static io.realm.util.SyncTestUtils.createNamedTestUser; import static io.realm.util.SyncTestUtils.createTestUser; @@ -60,7 +62,10 @@ public class SyncConfigurationTests { public final ExpectedException thrown = ExpectedException.none(); @After - public void tearDown() throws Exception { + public void tearDown() { + for (SyncUser syncUser : SyncUser.all().values()) { + syncUser.logOut(); + } SyncManager.reset(); } @@ -466,4 +471,82 @@ public void multipleUsersReferenceSameRealm() { assertNotEquals(config1.getPath(), config2.getPath()); } + @Test + public void automatic_throwsIfNoUserIsLoggedIn() { + try { + SyncConfiguration.automatic(); + fail(); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().startsWith("No user was logged in")); + } + } + + @Test + public void automatic_throwsIfMultipleUsersIsLoggedIn() { + SyncTestUtils.createTestUser(); + SyncTestUtils.createTestUser(); + try { + SyncConfiguration.automatic(); + fail(); + } catch (IllegalStateException e) { + assertEquals("Current user is not valid if more that one valid, logged-in user exists.", e.getMessage()); + } + } + + @Test + public void automaticWithUser_throwsIfNullOrInvalid() { + try { + //noinspection ConstantConditions + SyncConfiguration.automatic(null); + fail(); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().startsWith("Non-null 'user' required.")); + } + SyncUser user = SyncTestUtils.createTestUser(); + user.logOut(); + try { + SyncConfiguration.automatic(user); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("User is no logger valid. Log the user in again.", e.getMessage()); + } + } + + @Test + public void automatic_isPartial() { + SyncUser user = SyncTestUtils.createTestUser(); + + SyncConfiguration config = SyncConfiguration.automatic(); + assertTrue(config.isPartialRealm()); + + config = SyncConfiguration.automatic(user); + assertTrue(config.isPartialRealm()); + } + + @Test + public void automatic_convertsAuthUrl() { + Object[][] input = { + // AuthUrl -> Expected Realm URL + { "http://ros.realm.io/auth", "realm://ros.realm.io/default" }, + { "http://ros.realm.io:7777", "realm://ros.realm.io/default" }, + { "http://127.0.0.1/auth", "realm://127.0.0.1/default" }, + { "HTTP://ros.realm.io" , "realm://ros.realm.io/default" }, + + { "https://ros.realm.io/auth", "realms://ros.realm.io/default" }, + { "https://ros.realm.io:7777", "realms://ros.realm.io/default" }, + { "https://127.0.0.1/auth", "realms://127.0.0.1/default" }, + { "HTTPS://ros.realm.io" , "realms://ros.realm.io/default" }, + }; + + for (Object[] test : input) { + String authUrl = (String) test[0]; + String realmUrl = (String) test[1]; + + SyncUser user = SyncTestUtils.createTestUser(authUrl); + SyncConfiguration config = SyncConfiguration.automatic(); + URI url = config.getServerUrl(); + assertEquals(realmUrl, url.toString()); + user.logOut(); + } + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 88be3f6f9d..362c9022b1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -22,6 +22,7 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -33,6 +34,7 @@ import javax.annotation.Nullable; +import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.OsRealmConfig; @@ -181,6 +183,66 @@ public static RealmConfiguration forRecovery(String canonicalPath, @Nullable byt return forRecovery(canonicalPath, encryptionKey, schemaMediator); } + /** + * Creates an automatic default configuration based on the the currently logged in user. + *

            + * This configuration will point to the default Realm on the server where the user was + * authenticated. + * + * @throws IllegalStateException if no user are logged in, or multiple users have. Only one should + * be logged in when calling this method. + * @return The constructed {@link SyncConfiguration}. + */ + @Beta + public static SyncConfiguration automatic() { + SyncUser user = SyncUser.current(); + if (user == null) { + throw new IllegalStateException("No user was logged in."); + } + return getDefaultConfig(user); + } + + /** + * Creates an automatic default configuration for the provided user. + *

            + * This configuration will point to the default Realm on the server where the user was + * authenticated. + * + * @throws IllegalArgumentException if no user was provided or the user isn't valid. + * @return The constructed {@link SyncConfiguration}. + */ + @Beta + public static SyncConfiguration automatic(SyncUser user) { + if (user == null) { + throw new IllegalArgumentException("Non-null 'user' required."); + } + if (!user.isValid()) { + throw new IllegalArgumentException("User is no logger valid. Log the user in again."); + } + return getDefaultConfig(user); + } + + private static SyncConfiguration getDefaultConfig(SyncUser user) { + return new SyncConfiguration.Builder(user, createUrl(user)) + .partialRealm() + .build(); + } + + // Infer the URL to the default Realm based on the server used to login the user + private static String createUrl(SyncUser user) { + URL url = user.getAuthenticationUrl(); + String protocol = url.getProtocol(); + String host = url.getHost(); + + if (protocol.equalsIgnoreCase("https")) { + protocol = "realms"; + } else { + protocol = "realm"; + } + + return protocol + "://" + host + "/default"; + } + /** * Returns a {@link RealmConfiguration} appropriate to open a read-only, non-synced Realm to recover any pending changes. * This is useful when trying to open a backup/recovery Realm (after a client reset). diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 05c6ff67da..11ec379ed7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -309,4 +309,21 @@ public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { user.logOut(); } } + + @Test + public void defaultRealm() throws InterruptedException { + SyncCredentials credentials = SyncCredentials.nickname("test", true); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + SyncConfiguration config = SyncConfiguration.automatic(); + Realm realm = Realm.getInstance(config); + SyncManager.getSession(config).downloadAllServerChanges(); + realm.refresh(); + + try { + assertFalse(realm.isEmpty()); + } finally { + realm.close(); + user.logOut(); + } + } } From 11cf548ef2dcbb7cdc0ff246566784a2e0b40d6b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 15 Mar 2018 00:46:30 +0100 Subject: [PATCH 1204/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb29ccecfa..54a845bf37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.0 (YYYY-MM-DD) +## 5.0.0 (2018-03-15) This release is compatible with the Realm Object Server 3.0.0-beta.3 or later. From 8740eb6ce5bfc8536be2b480988a6212f2ce8466 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 15 Mar 2018 00:47:29 +0100 Subject: [PATCH 1205/2110] Release v5.0.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 71d2eb1c7f..28cbf7c0aa 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.0.0-SNAPSHOT +5.0.0 \ No newline at end of file From 87ec4a3f6b2a114f1302930438d48a26e27a40a8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 15 Mar 2018 00:47:29 +0100 Subject: [PATCH 1206/2110] Prepare next release v5.0.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 28cbf7c0aa..3fa3b389a5 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.0.0 \ No newline at end of file +5.0.1-SNAPSHOT \ No newline at end of file From 5497ac901bf8f7479b887230662243bb0249902e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 15 Mar 2018 02:01:30 +0100 Subject: [PATCH 1207/2110] Prepare for next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 3fa3b389a5..c30f0ec2be 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.0.1-SNAPSHOT \ No newline at end of file +5.1.0-SNAPSHOT \ No newline at end of file From 087417ecb8f29e718cb8bff7959d91bb4ead27b0 Mon Sep 17 00:00:00 2001 From: Gabor Varadi Date: Fri, 16 Mar 2018 10:10:46 +0100 Subject: [PATCH 1208/2110] Update Intro example (#5841) --- examples/introExample/build.gradle | 4 + .../examples/intro/IntroExampleActivity.java | 241 +++++++++++------- .../realm/examples/intro/MyApplication.java | 5 +- .../io/realm/examples/intro/model/Cat.java | 11 +- .../io/realm/examples/intro/model/Dog.java | 16 +- .../io/realm/examples/intro/model/Person.java | 40 ++- 6 files changed, 215 insertions(+), 102 deletions(-) diff --git a/examples/introExample/build.gradle b/examples/introExample/build.gradle index dc13980d3b..84188bec37 100644 --- a/examples/introExample/build.gradle +++ b/examples/introExample/build.gradle @@ -5,6 +5,10 @@ android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } defaultConfig { applicationId 'io.realm.examples.intro' targetSdkVersion rootProject.sdkVersion diff --git a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java index 669cd75af7..4660587de3 100644 --- a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java +++ b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,10 @@ import android.widget.LinearLayout; import android.widget.TextView; +import java.lang.ref.WeakReference; +import java.util.Arrays; + +import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Realm; import io.realm.RealmResults; import io.realm.Sort; @@ -31,73 +35,86 @@ import io.realm.examples.intro.model.Person; public class IntroExampleActivity extends Activity { + public static final String TAG = "IntroExampleActivity"; - public static final String TAG = IntroExampleActivity.class.getName(); - private LinearLayout rootLayout = null; + private LinearLayout rootLayout; private Realm realm; + // Results obtained from a Realm are live, and can be observed on looper threads (like the UI thread). + // Note that if you want to observe the RealmResults for a long time, then it should be a field reference. + // Otherwise, the RealmResults can no longer be notified if the GC has cleared the reference to it. + private RealmResults persons; + + // OrderedRealmCollectionChangeListener receives fine-grained changes - insertions, deletions, and changes. + // If the change set isn't needed, then RealmChangeListener can also be used. + private final OrderedRealmCollectionChangeListener> realmChangeListener = (people, changeSet) -> { + String insertions = changeSet.getInsertions().length == 0 ? "" : "\n - Insertions: " + Arrays.toString(changeSet.getInsertions()); + String deletions = changeSet.getDeletions().length == 0 ? "" : "\n - Deletions: " + Arrays.toString(changeSet.getDeletions()); + String changes = changeSet.getChanges().length == 0 ? "" : "\n - Changes: " + Arrays.toString(changeSet.getChanges()); + showStatus("Person was loaded, or written to. " + insertions + deletions + changes); + }; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_realm_basic_example); - rootLayout = ((LinearLayout) findViewById(R.id.container)); + rootLayout = findViewById(R.id.container); rootLayout.removeAllViews(); - // These operations are small enough that - // we can generally safely run them on the UI thread. + // Clear the Realm if the example was previously run. + Realm.deleteRealm(Realm.getDefaultConfiguration()); // Create the Realm instance realm = Realm.getDefaultInstance(); + // Asynchronous queries are evaluated on a background thread, + // and passed to the registered change listener when it's done. + // The change listener is also called on any future writes that change the result set. + persons = realm.where(Person.class).findAllAsync(); + + // The change listener will be notified when the data is loaded, + // or the Realm is written to from any threads (and the result set is modified). + persons.addChangeListener(realmChangeListener); + + // These operations are small enough that + // we can generally safely run them on the UI thread. basicCRUD(realm); basicQuery(realm); basicLinkQuery(realm); // More complex operations can be executed on another thread. - new AsyncTask() { - @Override - protected String doInBackground(Void... voids) { - String info; - info = complexReadWrite(); - info += complexQuery(); - return info; - } - - @Override - protected void onPostExecute(String result) { - showStatus(result); - } - }.execute(); + new ComplexBackgroundOperations(this).execute(); } @Override protected void onDestroy() { super.onDestroy(); + persons.removeAllChangeListeners(); // Remove the change listener when no longer needed. realm.close(); // Remember to close Realm when done. } - private void showStatus(String txt) { - Log.i(TAG, txt); - TextView tv = new TextView(this); - tv.setText(txt); - rootLayout.addView(tv); + private void showStatus(String text) { + Log.i(TAG, text); + TextView textView = new TextView(this); + textView.setText(text); + rootLayout.addView(textView); } private void basicCRUD(Realm realm) { showStatus("Perform basic Create/Read/Update/Delete (CRUD) operations..."); // All writes must be wrapped in a transaction to facilitate safe multi threading - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - // Add a person - Person person = realm.createObject(Person.class); - person.setId(1); - person.setName("Young Person"); - person.setAge(14); - - } + realm.executeTransaction(r -> { + // Add a person. + // RealmObjects with primary keys created with `createObject()` must specify the primary key value as an argument. + Person person = r.createObject(Person.class, 1); + person.setName("Young Person"); + person.setAge(14); + + // Even young people have at least one phone in this day and age. + // Please note that this is a RealmList that contains primitive values. + person.getPhoneNumbers().add("+1 123 4567"); }); // Find the first person (no query conditions) and read a field @@ -105,26 +122,30 @@ public void execute(Realm realm) { showStatus(person.getName() + ":" + person.getAge()); // Update person in a transaction - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - person.setName("Senior Person"); - person.setAge(99); - showStatus(person.getName() + " got older: " + person.getAge()); - } + realm.executeTransaction(r -> { + // Managed objects can be modified inside transactions. + person.setName("Senior Person"); + person.setAge(99); + showStatus(person.getName() + " got older: " + person.getAge()); }); // Delete all persons - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.delete(Person.class); - } - }); + showStatus("Deleting all persons"); + realm.executeTransaction(r -> r.delete(Person.class)); } private void basicQuery(Realm realm) { showStatus("\nPerforming basic Query operation..."); + + // Let's add a person so that the query returns something. + realm.executeTransaction(r -> { + Person oldPerson = new Person(); + oldPerson.setId(99); + oldPerson.setAge(99); + oldPerson.setName("George"); + realm.insertOrUpdate(oldPerson); + }); + showStatus("Number of persons: " + realm.where(Person.class).count()); RealmResults results = realm.where(Person.class).equalTo("age", 99).findAll(); @@ -134,6 +155,18 @@ private void basicQuery(Realm realm) { private void basicLinkQuery(Realm realm) { showStatus("\nPerforming basic Link Query operation..."); + + // Let's add a person with a cat so that the query returns something. + realm.executeTransaction(r -> { + Person catLady = realm.createObject(Person.class, 24); + catLady.setAge(52); + catLady.setName("Mary"); + + Cat tiger = realm.createObject(Cat.class); + tiger.name = "Tiger"; + catLady.getCats().add(tiger); + }); + showStatus("Number of persons: " + realm.where(Person.class).count()); RealmResults results = realm.where(Person.class).equalTo("cats.name", "Tiger").findAll(); @@ -141,37 +174,76 @@ private void basicLinkQuery(Realm realm) { showStatus("Size of result set: " + results.size()); } - private String complexReadWrite() { - String status = "\nPerforming complex Read/Write operation..."; + // This AsyncTask shows how to use Realm in background thread operations. + // + // AsyncTasks should be static inner classes to avoid memory leaks. + // In this example, WeakReference is used for the sake of simplicity. + private static class ComplexBackgroundOperations extends AsyncTask { + private WeakReference weakReference; + + public ComplexBackgroundOperations(IntroExampleActivity introExampleActivity) { + this.weakReference = new WeakReference<>(introExampleActivity); + } + + @Override + protected void onPreExecute() { + IntroExampleActivity activity = weakReference.get(); + if (activity == null) { + return; + } + activity.showStatus("\n\nBeginning complex operations on background thread."); + } + + @Override + protected String doInBackground(Void... voids) { + IntroExampleActivity activity = weakReference.get(); + if (activity == null) { + return ""; + } + // Open the default realm. Uses `try-with-resources` to automatically close Realm when done. + // All threads must use their own reference to the realm. + // Realm instances, RealmResults, and managed RealmObjects can not be transferred across threads. + try (Realm realm = Realm.getDefaultInstance()) { + String info; + info = activity.complexReadWrite(realm); + info += activity.complexQuery(realm); + return info; + } + } - // Open the default realm. All threads must use its own reference to the realm. - // Those can not be transferred across threads. - Realm realm = Realm.getDefaultInstance(); + @Override + protected void onPostExecute(String result) { + IntroExampleActivity activity = weakReference.get(); + if (activity == null) { + return; + } + activity.showStatus(result); + } + } + + private String complexReadWrite(Realm realm) { + String status = "\nPerforming complex Read/Write operation..."; // Add ten persons in one transaction - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - Dog fido = realm.createObject(Dog.class); - fido.name = "fido"; - for (int i = 0; i < 10; i++) { - Person person = realm.createObject(Person.class); - person.setId(i); - person.setName("Person no. " + i); - person.setAge(i); - person.setDog(fido); - - // The field tempReference is annotated with @Ignore. - // This means setTempReference sets the Person tempReference - // field directly. The tempReference is NOT saved as part of - // the RealmObject: - person.setTempReference(42); - - for (int j = 0; j < i; j++) { - Cat cat = realm.createObject(Cat.class); - cat.name = "Cat_" + j; - person.getCats().add(cat); - } + realm.executeTransaction(r -> { + Dog fido = r.createObject(Dog.class); + fido.name = "fido"; + for (int i = 0; i < 10; i++) { + Person person = r.createObject(Person.class, i); + person.setName("Person no. " + i); + person.setAge(i); + person.setDog(fido); + + // The field tempReference is annotated with @Ignore. + // This means setTempReference sets the Person tempReference + // field directly. The tempReference is NOT saved as part of + // the RealmObject: + person.setTempReference(42); + + for (int j = 0; j < i; j++) { + Cat cat = r.createObject(Cat.class); + cat.name = "Cat_" + j; + person.getCats().add(cat); } } }); @@ -179,15 +251,15 @@ public void execute(Realm realm) { // Implicit read transactions allow you to access your objects status += "\nNumber of persons: " + realm.where(Person.class).count(); - // Iterate over all objects - for (Person pers : realm.where(Person.class).findAll()) { + // Iterate over all objects, with an iterator + for (Person person : realm.where(Person.class).findAll()) { String dogName; - if (pers.getDog() == null) { + if (person.getDog() == null) { dogName = "None"; } else { - dogName = pers.getDog().name; + dogName = person.getDog().name; } - status += "\n" + pers.getName() + ":" + pers.getAge() + " : " + dogName + " : " + pers.getCats().size(); + status += "\n" + person.getName() + ":" + person.getAge() + " : " + dogName + " : " + person.getCats().size(); } // Sorting @@ -195,23 +267,20 @@ public void execute(Realm realm) { status += "\nSorting " + sortedPersons.last().getName() + " == " + realm.where(Person.class).findFirst() .getName(); - realm.close(); return status; } - private String complexQuery() { + private String complexQuery(Realm realm) { String status = "\n\nPerforming complex Query operation..."; - - Realm realm = Realm.getDefaultInstance(); status += "\nNumber of persons: " + realm.where(Person.class).count(); // Find all persons where age between 7 and 9 and name begins with "Person". RealmResults results = realm.where(Person.class) .between("age", 7, 9) // Notice implicit "and" operation .beginsWith("name", "Person").findAll(); + status += "\nSize of result set: " + results.size(); - realm.close(); return status; } } diff --git a/examples/introExample/src/main/java/io/realm/examples/intro/MyApplication.java b/examples/introExample/src/main/java/io/realm/examples/intro/MyApplication.java index 45e40f3748..79d00c24ec 100644 --- a/examples/introExample/src/main/java/io/realm/examples/intro/MyApplication.java +++ b/examples/introExample/src/main/java/io/realm/examples/intro/MyApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,5 +27,8 @@ public void onCreate() { super.onCreate(); // Initialize Realm. Should only be done once when the application starts. Realm.init(this); + + // In this example, no default configuration is set, + // so by default, `RealmConfiguration.Builder().build()` is used. } } diff --git a/examples/introExample/src/main/java/io/realm/examples/intro/model/Cat.java b/examples/introExample/src/main/java/io/realm/examples/intro/model/Cat.java index c06ae48237..586787b6d3 100644 --- a/examples/introExample/src/main/java/io/realm/examples/intro/model/Cat.java +++ b/examples/introExample/src/main/java/io/realm/examples/intro/model/Cat.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,14 @@ package io.realm.examples.intro.model; import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; public class Cat extends RealmObject { - public String name; + // It is possible to also use public fields, instead of getters/setters. + public String name; + + // You can define inverse relationships. + @LinkingObjects("cats") + public final RealmResults owners = null; } diff --git a/examples/introExample/src/main/java/io/realm/examples/intro/model/Dog.java b/examples/introExample/src/main/java/io/realm/examples/intro/model/Dog.java index d6339bf300..a76284440d 100644 --- a/examples/introExample/src/main/java/io/realm/examples/intro/model/Dog.java +++ b/examples/introExample/src/main/java/io/realm/examples/intro/model/Dog.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,18 @@ package io.realm.examples.intro.model; -import io.realm.RealmObject; +import io.realm.RealmModel; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; -public class Dog extends RealmObject { +// It is possible to use @RealmClass and implement RealmModel, instead of extending RealmObject. +@RealmClass +public class Dog implements RealmModel { + // It is possible to also use public fields, instead of getters/setters. public String name; + + // You can define inverse relationships. + @LinkingObjects("dog") + public final RealmResults owners = null; } diff --git a/examples/introExample/src/main/java/io/realm/examples/intro/model/Person.java b/examples/introExample/src/main/java/io/realm/examples/intro/model/Person.java index cf9975d269..815eff324b 100644 --- a/examples/introExample/src/main/java/io/realm/examples/intro/model/Person.java +++ b/examples/introExample/src/main/java/io/realm/examples/intro/model/Person.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,27 +19,39 @@ import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.Ignore; +import io.realm.annotations.Index; +import io.realm.annotations.PrimaryKey; // Your model just have to extend RealmObject. // This will inherit an annotation which produces proxy getters and setters for all fields. +// It is also possible to use @RealmClass annotation, and implement RealmModel interface. public class Person extends RealmObject { // All fields are by default persisted. - private String name; private int age; - // Other objects in a one-to-one relation must also subclass RealmObject + // Adding an index makes queries execute faster on that field. + @Index + private String name; + + // Primary keys are optional, but it allows identifying a specific object + // when Realm writes are instructed to update if the object already exists in the Realm + @PrimaryKey + private long id; + + // Other objects in a one-to-one relation must also implement RealmModel, or extend RealmObject private Dog dog; - // One-to-many relations is simply a RealmList of the objects which also subclass RealmObject + // One-to-many relations is simply a RealmList of the objects which also implements RealmModel private RealmList cats; + // It is also possible to have list of primitive types (long, String, Date, byte[], etc.) + private RealmList phoneNumbers; + // You can instruct Realm to ignore a field and not persist it. @Ignore private int tempReference; - private long id; - // Let your IDE generate getters and setters for you! // Or if you like you can even have public fields and no accessors! See Dog.java and Cat.java public String getName() { @@ -58,6 +70,14 @@ public void setAge(int age) { this.age = age; } + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + public Dog getDog() { return dog; } @@ -82,11 +102,11 @@ public void setTempReference(int tempReference) { this.tempReference = tempReference; } - public long getId() { - return id; + public RealmList getPhoneNumbers() { + return phoneNumbers; } - public void setId(long id) { - this.id = id; + public void setPhoneNumbers(RealmList phoneNumbers) { + this.phoneNumbers = phoneNumbers; } } From ad302da32269e12c18c0795e24fd0917d70d54fd Mon Sep 17 00:00:00 2001 From: Gabor Varadi Date: Fri, 16 Mar 2018 10:12:52 +0100 Subject: [PATCH 1209/2110] Update Kotlin sample (#5843) --- examples/kotlinExample/build.gradle | 2 +- .../examples/kotlin/KotlinExampleActivity.kt | 148 +++++++++--------- .../io/realm/examples/kotlin/model/Cat.kt | 5 + .../io/realm/examples/kotlin/model/Dog.kt | 1 + 4 files changed, 77 insertions(+), 79 deletions(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 9b887acf98..aa27cdec51 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -60,5 +60,5 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlin_version}" - implementation 'org.jetbrains.anko:anko-sdk15:0.9.1' + implementation "org.jetbrains.anko:anko-commons:0.10.4" } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index 25a6622827..3d7af8c2dc 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -30,36 +30,34 @@ import io.realm.kotlin.createObject import io.realm.kotlin.where import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread -import kotlin.properties.Delegates class KotlinExampleActivity : Activity() { - companion object { - val TAG: String = KotlinExampleActivity::class.java.simpleName + const val TAG: String = "KotlinExampleActivity" } - private var rootLayout: LinearLayout by Delegates.notNull() - private var realm: Realm by Delegates.notNull() + private lateinit var rootLayout: LinearLayout + private lateinit var realm: Realm override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_realm_basic_example) + rootLayout = findViewById(R.id.container) rootLayout.removeAllViews() - // These operations are small enough that - // we can generally safely run them on the UI thread. - // Open the realm for the UI thread. realm = Realm.getDefaultInstance() // Delete all persons // Using executeTransaction with a lambda reduces code size and makes it impossible // to forget to commit the transaction. - realm.executeTransaction { + realm.executeTransaction { realm -> realm.deleteAll() } + // These operations are small enough that + // we can generally safely run them on the UI thread. basicCRUD(realm) basicQuery(realm) basicLinkQuery(realm) @@ -67,8 +65,17 @@ class KotlinExampleActivity : Activity() { // More complex operations can be executed on another thread, for example using // Anko's doAsync extension method. doAsync { - var info = complexReadWrite() - info += complexQuery() + var info = "" + + // Open the default realm. All threads must use its own reference to the realm. + // Those can not be transferred across threads. + + // Realm implements the Closable interface, therefore + // we can make use of Kotlin's built-in extension method 'use' (pun intended). + Realm.getDefaultInstance().use { realm -> + info += complexReadWrite(realm) + info += complexQuery(realm) + } uiThread { showStatus(info) } @@ -80,18 +87,19 @@ class KotlinExampleActivity : Activity() { realm.close() // Remember to close Realm when done. } - private fun showStatus(txt: String) { - Log.i(TAG, txt) - val tv = TextView(this) - tv.text = txt - rootLayout.addView(tv) + private fun showStatus(text: String) { + Log.i(TAG, text) + val textView = TextView(this) + textView.text = text + rootLayout.addView(textView) } + @Suppress("NAME_SHADOWING") private fun basicCRUD(realm: Realm) { showStatus("Perform basic Create/Read/Update/Delete (CRUD) operations...") // All writes must be wrapped in a transaction to facilitate safe multi threading - realm.executeTransaction { + realm.executeTransaction { realm -> // Add a person val person = realm.createObject(0) person.name = "Young Person" @@ -103,7 +111,7 @@ class KotlinExampleActivity : Activity() { showStatus(person.name + ": " + person.age) // Update person in a transaction - realm.executeTransaction { + realm.executeTransaction { _ -> person.name = "Senior Person" person.age = 99 showStatus(person.name + " got older: " + person.age) @@ -129,84 +137,68 @@ class KotlinExampleActivity : Activity() { showStatus("Size of result set: ${results.size}") } - private fun complexReadWrite(): String { + private fun complexReadWrite(realm: Realm): String { var status = "\nPerforming complex Read/Write operation..." - // Open the default realm. All threads must use its own reference to the realm. - // Those can not be transferred across threads. - val realm = Realm.getDefaultInstance() - try { - // Add ten persons in one transaction - realm.executeTransaction { - val fido = realm.createObject() - fido.name = "fido" - for (i in 1..9) { - val person = realm.createObject(i.toLong()) - person.name = "Person no. $i" - person.age = i - person.dog = fido - - // The field tempReference is annotated with @Ignore. - // This means setTempReference sets the Person tempReference - // field directly. The tempReference is NOT saved as part of - // the RealmObject: - person.tempReference = 42 - - for (j in 0..i - 1) { - val cat = realm.createObject() - cat.name = "Cat_$j" - person.cats.add(cat) - } + // Add ten persons in one transaction + realm.executeTransaction { + val fido = realm.createObject() + fido.name = "fido" + for (i in 1..9) { + val person = realm.createObject(i.toLong()) + person.name = "Person no. $i" + person.age = i + person.dog = fido + + // The field tempReference is annotated with @Ignore. + // This means setTempReference sets the Person tempReference + // field directly. The tempReference is NOT saved as part of + // the RealmObject: + person.tempReference = 42 + + for (j in 0..i - 1) { + val cat = realm.createObject() + cat.name = "Cat_$j" + person.cats.add(cat) } } + } - // Implicit read transactions allow you to access your objects - status += "\nNumber of persons: ${realm.where().count()}" + // Implicit read transactions allow you to access your objects + status += "\nNumber of persons: ${realm.where().count()}" - // Iterate over all objects - for (person in realm.where().findAll()) { - val dogName: String = person?.dog?.name ?: "None" + // Iterate over all objects + for (person in realm.where().findAll()) { + val dogName: String = person?.dog?.name ?: "None" - status += "\n${person.name}: ${person.age} : $dogName : ${person.cats.size}" + status += "\n${person.name}: ${person.age} : $dogName : ${person.cats.size}" - // The field tempReference is annotated with @Ignore - // Though we initially set its value to 42, it has - // not been saved as part of the Person RealmObject: - check(person.tempReference == 0) - } + // The field tempReference is annotated with @Ignore + // Though we initially set its value to 42, it has + // not been saved as part of the Person RealmObject: + check(person.tempReference == 0) + } - // Sorting - val sortedPersons = realm.where().sort(Person::age.name, Sort.DESCENDING).findAll() - status += "\nSorting ${sortedPersons.last()?.name} == ${realm.where().findAll().first()?.name}" + // Sorting + val sortedPersons = realm.where().sort(Person::age.name, Sort.DESCENDING).findAll() + status += "\nSorting ${sortedPersons.last()?.name} == ${realm.where().findAll().first()?.name}" - } finally { - realm.close() - } return status } - private fun complexQuery(): String { + private fun complexQuery(realm: Realm): String { var status = "\n\nPerforming complex Query operation..." - // Realm implements the Closable interface, therefore we can make use of Kotlin's built-in - // extension method 'use' (pun intended). - Realm.getDefaultInstance().use { - // 'it' is the implicit lambda parameter of type Realm - status += "\nNumber of persons: ${it.where().count()}" - - // Find all persons where age between 7 and 9 and name begins with "Person". - val results = it - .where() - .between("age", 7, 9) // Notice implicit "and" operation - .beginsWith("name", "Person") - .findAll() + status += "\nNumber of persons: ${realm.where().count()}" - status += "\nSize of result set: ${results.size}" + // Find all persons where age between 7 and 9 and name begins with "Person". + val results = realm.where() + .between("age", 7, 9) // Notice implicit "and" operation + .beginsWith("name", "Person") + .findAll() - } + status += "\nSize of result set: ${results.size}" return status } - - } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Cat.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Cat.kt index 60e21b81b5..b1d4a37e1a 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Cat.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Cat.kt @@ -17,7 +17,12 @@ package io.realm.examples.kotlin.model import io.realm.RealmObject +import io.realm.RealmResults +import io.realm.annotations.LinkingObjects open class Cat : RealmObject() { var name: String? = null + + @LinkingObjects("cats") + val owners: RealmResults? = null } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Dog.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Dog.kt index 17d8b5fd75..e1237ed648 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Dog.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/model/Dog.kt @@ -22,6 +22,7 @@ import io.realm.annotations.LinkingObjects open class Dog : RealmObject() { var name: String? = null + @LinkingObjects("dog") val owners: RealmResults? = null } From 5a9dd479a1a30ba7ca1ed6e7b2a79115e6a24e97 Mon Sep 17 00:00:00 2001 From: Gabor Varadi Date: Fri, 16 Mar 2018 10:13:34 +0100 Subject: [PATCH 1210/2110] Update GridView example (#5840) --- examples/gridViewExample/build.gradle | 4 + .../io/realm/examples/realmgridview/City.java | 4 +- .../examples/realmgridview/CityAdapter.java | 54 +++++--- .../GridViewExampleActivity.java | 126 ++++++------------ .../examples/realmgridview/MyApplication.java | 46 ++++++- .../res/layout/activity_realm_example.xml | 15 +-- .../src/main/res/layout/city_listitem.xml | 18 ++- .../src/main/res/values-w820dp/dimens.xml | 6 - .../src/main/res/values/dimens.xml | 5 - .../src/main/res/values/strings.xml | 6 +- 10 files changed, 143 insertions(+), 141 deletions(-) delete mode 100644 examples/gridViewExample/src/main/res/values-w820dp/dimens.xml delete mode 100644 examples/gridViewExample/src/main/res/values/dimens.xml diff --git a/examples/gridViewExample/build.gradle b/examples/gridViewExample/build.gradle index f10dad85fa..59945d719b 100644 --- a/examples/gridViewExample/build.gradle +++ b/examples/gridViewExample/build.gradle @@ -5,6 +5,10 @@ android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } defaultConfig { applicationId 'io.realm.examples.realmgridview' targetSdkVersion rootProject.sdkVersion diff --git a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/City.java b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/City.java index 020f0c422b..151d8de0d5 100644 --- a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/City.java +++ b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/City.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,12 @@ package io.realm.examples.realmgridview; import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; public class City extends RealmObject { // If you are using GSON, field names should not be obfuscated. // Add either the proguard rule in proguard-rules.pro or the @SerializedName annotation. + @PrimaryKey private String name; private long votes; diff --git a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/CityAdapter.java b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/CityAdapter.java index 147b8ac328..d2a21396f9 100644 --- a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/CityAdapter.java +++ b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/CityAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,13 @@ package io.realm.examples.realmgridview; -import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -33,32 +33,26 @@ // a developer could update the getView() to pull items from the Realm. public class CityAdapter extends BaseAdapter { + private List cities = Collections.emptyList(); - private LayoutInflater inflater; - - private List cities = null; - - public CityAdapter(Context context) { - inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + public CityAdapter() { } public void setData(List details) { + if (details == null) { + details = Collections.emptyList(); + } this.cities = details; + notifyDataSetChanged(); } @Override public int getCount() { - if (cities == null) { - return 0; - } return cities.size(); } @Override - public Object getItem(int position) { - if (cities == null || cities.get(position) == null) { - return null; - } + public City getItem(int position) { return cities.get(position); } @@ -67,18 +61,36 @@ public long getItemId(int i) { return i; } + // ViewHolder caches view resources so that `findViewById` is not called for each row + private static class ViewHolder { + private TextView name; + private TextView vote; + + public ViewHolder(View view) { + name = view.findViewById(R.id.name); + vote = view.findViewById(R.id.votes); + } + + public void bind(City city) { + name.setText(city.getName()); + vote.setText(String.format(Locale.US, "%d", city.getVotes())); + } + } + @Override public View getView(int position, View currentView, ViewGroup parent) { + // GridView requires ViewHolder pattern to ensure optimal performance + ViewHolder viewHolder; if (currentView == null) { - currentView = inflater.inflate(R.layout.city_listitem, parent, false); + currentView = LayoutInflater.from(parent.getContext()).inflate(R.layout.city_listitem, parent, false); + viewHolder = new ViewHolder(currentView); + currentView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder)currentView.getTag(); } City city = cities.get(position); - - if (city != null) { - ((TextView) currentView.findViewById(R.id.name)).setText(city.getName()); - ((TextView) currentView.findViewById(R.id.votes)).setText(String.format(Locale.US, "%d",city.getVotes())); - } + viewHolder.bind(city); return currentView; } diff --git a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/GridViewExampleActivity.java b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/GridViewExampleActivity.java index c37961ae44..beb0c25772 100644 --- a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/GridViewExampleActivity.java +++ b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/GridViewExampleActivity.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,123 +22,73 @@ import android.widget.AdapterView; import android.widget.GridView; -import com.google.gson.ExclusionStrategy; -import com.google.gson.FieldAttributes; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.gson.reflect.TypeToken; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.RealmObject; +import io.realm.RealmChangeListener; import io.realm.RealmResults; public class GridViewExampleActivity extends Activity implements AdapterView.OnItemClickListener { - private GridView mGridView; - private CityAdapter mAdapter; + private GridView gridView; + private CityAdapter adapter; private Realm realm; + private RealmResults cities; + private RealmChangeListener> realmChangeListener = cities -> { + // Set the cities to the adapter only when async query is loaded. + // It will also be called for any future writes made to the Realm. + adapter.setData(cities); + }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_realm_example); - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build(); + // This is the GridView adapter + adapter = new CityAdapter(); + + //This is the GridView which will display the list of cities + gridView = findViewById(R.id.cities_list); + gridView.setAdapter(adapter); + gridView.setOnItemClickListener(GridViewExampleActivity.this); // Clear the realm from last time - Realm.deleteRealm(realmConfiguration); + //noinspection ConstantConditions + Realm.deleteRealm(Realm.getDefaultConfiguration()); // Create a new empty instance of Realm - realm = Realm.getInstance(realmConfiguration); - } + realm = Realm.getDefaultInstance(); - @Override - public void onResume() { - super.onResume(); - - // Load from file "cities.json" first time - if(mAdapter == null) { - List cities = loadCities(); - - //This is the GridView adapter - mAdapter = new CityAdapter(this); - mAdapter.setData(cities); - - //This is the GridView which will display the list of cities - mGridView = (GridView) findViewById(R.id.cities_list); - mGridView.setAdapter(mAdapter); - mGridView.setOnItemClickListener(GridViewExampleActivity.this); - mAdapter.notifyDataSetChanged(); - mGridView.invalidate(); - } + // Obtain the cities in the Realm with asynchronous query. + cities = realm.where(City.class).findAllAsync(); + + // The RealmChangeListener will be called when the results are asynchronously loaded, and available for use. + cities.addChangeListener(realmChangeListener); } @Override protected void onDestroy() { super.onDestroy(); + cities.removeAllChangeListeners(); // Remove change listeners to prevent updating views not yet GCed. realm.close(); // Remember to close Realm when done. } - private List loadCities() { - // In this case we're loading from local assets. - // NOTE: could alternatively easily load from network - InputStream stream; - try { - stream = getAssets().open("cities.json"); - } catch (IOException e) { - return null; - } - - Gson gson = new GsonBuilder().create(); - - JsonElement json = new JsonParser().parse(new InputStreamReader(stream)); - List cities = gson.fromJson(json, new TypeToken>() {}.getType()); - - // Open a transaction to store items into the realm - // Use copyToRealm() to convert the objects into proper RealmObjects managed by Realm. - realm.beginTransaction(); - Collection realmCities = realm.copyToRealm(cities); - realm.commitTransaction(); - - return new ArrayList(realmCities); - } - - public void updateCities() { - // Pull all the cities from the realm - RealmResults cities = realm.where(City.class).findAll(); - - // Put these items in the Adapter - mAdapter.setData(cities); - mAdapter.notifyDataSetChanged(); - mGridView.invalidate(); - } - @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - City modifiedCity = (City)mAdapter.getItem(position); - - // Acquire the RealmObject matching the name of the clicked City. - final City city = realm.where(City.class).equalTo("name", modifiedCity.getName()).findFirst(); - - // Create a transaction to increment the vote count for the selected City in the realm - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { + City modifiedCity = adapter.getItem(position); + + // Acquire the name of the clicked City, in order to be able to query for it. + final String name = modifiedCity.getName(); + + // Create an asynchronous transaction to increment the vote count for the selected City in the Realm. + // The write will happen on a background thread, and the RealmChangeListener will update the GridView automatically. + realm.executeTransactionAsync(bgRealm -> { + // We need to find the City we want to modify from the background thread's Realm + City city = bgRealm.where(City.class).equalTo("name", name).findFirst(); + if (city != null) { + // Let's increase the votes of the selected city! city.setVotes(city.getVotes() + 1); } }); - - updateCities(); } } diff --git a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/MyApplication.java b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/MyApplication.java index 2cef075c10..ab1c127bd4 100644 --- a/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/MyApplication.java +++ b/examples/gridViewExample/src/main/java/io/realm/examples/realmgridview/MyApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,55 @@ import android.app.Application; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; + import io.realm.Realm; +import io.realm.RealmConfiguration; public class MyApplication extends Application { - @Override public void onCreate() { super.onCreate(); Realm.init(this); + Realm.setDefaultConfiguration(new RealmConfiguration.Builder() + .initialData(realm -> { + // Load from file "cities.json" first time + List cities = loadCities(); + if (cities != null) { + // Use insertOrUpdate() to convert the objects into proper RealmObjects managed by Realm. + realm.insertOrUpdate(cities); + } + }) + .deleteRealmIfMigrationNeeded() + .build() + ); + } + + private List loadCities() { + // In this case we're loading from local assets. + // NOTE: could alternatively easily load from network. + // However, that would need to happen on a background thread. + InputStream stream; + try { + stream = getAssets().open("cities.json"); + } catch (IOException e) { + return null; + } + + Gson gson = new GsonBuilder().create(); + + JsonElement json = new JsonParser().parse(new InputStreamReader(stream)); + + return gson.fromJson(json, new TypeToken>() { + }.getType()); } } diff --git a/examples/gridViewExample/src/main/res/layout/activity_realm_example.xml b/examples/gridViewExample/src/main/res/layout/activity_realm_example.xml index a76c48167f..d23e384d99 100644 --- a/examples/gridViewExample/src/main/res/layout/activity_realm_example.xml +++ b/examples/gridViewExample/src/main/res/layout/activity_realm_example.xml @@ -3,25 +3,24 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:paddingBottom="@dimen/activity_vertical_margin" - android:paddingLeft="@dimen/activity_horizontal_margin" - android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingTop="@dimen/activity_vertical_margin" - tools:context=".RealmGridLayoutActivity"> + tools:context=".GridViewExampleActivity"> + android:text="@string/my_favorite_city" + android:textSize="22sp" /> + android:orientation="horizontal" + android:padding="4dp"> + android:layout_margin="12dp" + android:textColor="#121212" + android:textSize="20sp" + tools:text="Barcelona"/> + android:layout_height="wrap_content" + android:layout_margin="12dp" + android:textSize="18sp" + android:textColor="#3A3A3A" + tools:text="19"/> diff --git a/examples/gridViewExample/src/main/res/values-w820dp/dimens.xml b/examples/gridViewExample/src/main/res/values-w820dp/dimens.xml deleted file mode 100644 index 63fc816444..0000000000 --- a/examples/gridViewExample/src/main/res/values-w820dp/dimens.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 64dp - diff --git a/examples/gridViewExample/src/main/res/values/dimens.xml b/examples/gridViewExample/src/main/res/values/dimens.xml deleted file mode 100644 index 47c8224673..0000000000 --- a/examples/gridViewExample/src/main/res/values/dimens.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 16dp - 16dp - diff --git a/examples/gridViewExample/src/main/res/values/strings.xml b/examples/gridViewExample/src/main/res/values/strings.xml index 9f14c47ace..b7469afddc 100644 --- a/examples/gridViewExample/src/main/res/values/strings.xml +++ b/examples/gridViewExample/src/main/res/values/strings.xml @@ -1,8 +1,6 @@ - - Gridview example - My Favorite City + GridView example + My Favorite City Click to Add A Vote - From fbfdb41a6904de4363ee29a8ab9648e27a87cbb8 Mon Sep 17 00:00:00 2001 From: Gabor Varadi Date: Fri, 16 Mar 2018 10:14:01 +0100 Subject: [PATCH 1211/2110] Update json example (#5842) --- examples/jsonExample/build.gradle | 9 +- .../io/realm/examples/json/CityAdapter.java | 57 +++++----- .../examples/json/JsonExampleActivity.java | 105 ++++++++---------- .../res/layout/activity_realm_example.xml | 6 +- 4 files changed, 85 insertions(+), 92 deletions(-) diff --git a/examples/jsonExample/build.gradle b/examples/jsonExample/build.gradle index 8f9242eef1..f8c3917a5b 100644 --- a/examples/jsonExample/build.gradle +++ b/examples/jsonExample/build.gradle @@ -5,6 +5,10 @@ android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } defaultConfig { applicationId 'io.realm.examples.json' targetSdkVersion rootProject.sdkVersion @@ -27,6 +31,7 @@ android { } dependencies { - compileOnly 'org.projectlombok:lombok:1.16.6' - annotationProcessor 'org.projectlombok:lombok:1.16.6' + compileOnly 'org.projectlombok:lombok:1.16.18' + compileOnly 'javax.annotation:javax.annotation-api:1.3.1' + annotationProcessor 'org.projectlombok:lombok:1.16.18' } diff --git a/examples/jsonExample/src/main/java/io/realm/examples/json/CityAdapter.java b/examples/jsonExample/src/main/java/io/realm/examples/json/CityAdapter.java index 73dbc279b7..891d8613ed 100644 --- a/examples/jsonExample/src/main/java/io/realm/examples/json/CityAdapter.java +++ b/examples/jsonExample/src/main/java/io/realm/examples/json/CityAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,50 +16,37 @@ package io.realm.examples.json; -import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; +import java.util.Collections; import java.util.List; // This adapter is strictly to interface with the GridView and doesn't // particular show much interesting Realm functionality. - -// Alternatively from this example, -// a developer could update the getView() to pull items from the Realm. - public class CityAdapter extends BaseAdapter { + public static final String TAG = "CityAdapter"; - public static final String TAG = JsonExampleActivity.class.getName(); - - private LayoutInflater inflater; - - private List cities = null; + private List cities = Collections.emptyList(); - public CityAdapter(Context context) { - inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + public CityAdapter() { } public void setData(List details) { this.cities = details; + notifyDataSetChanged(); } @Override public int getCount() { - if (cities == null) { - return 0; - } - return cities.size(); + return cities == null ? 0 : cities.size(); } @Override - public Object getItem(int position) { - if (cities == null || cities.get(position) == null) { - return null; - } + public City getItem(int position) { return cities.get(position); } @@ -68,18 +55,36 @@ public long getItemId(int i) { return i; } + private static class ViewHolder { + private TextView name; + private TextView votes; + + public ViewHolder(View view) { + this.name = view.findViewById(R.id.name); + this.votes = view.findViewById(R.id.votes); + } + + public void bind(City city) { + name.setText(city.getName()); + votes.setText(String.valueOf(city.getVotes())); + } + } + @Override public View getView(int position, View currentView, ViewGroup parent) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + ViewHolder viewHolder; + if (currentView == null) { currentView = inflater.inflate(R.layout.city_listitem, parent, false); + viewHolder = new ViewHolder(currentView); + currentView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder)currentView.getTag(); } City city = cities.get(position); - - if (city != null) { - ((TextView) currentView.findViewById(R.id.name)).setText(city.getName()); - ((TextView) currentView.findViewById(R.id.votes)).setText(String.valueOf(city.getVotes())); - } + viewHolder.bind(city); return currentView; } diff --git a/examples/jsonExample/src/main/java/io/realm/examples/json/JsonExampleActivity.java b/examples/jsonExample/src/main/java/io/realm/examples/json/JsonExampleActivity.java index 1e00b55e85..2910fd7034 100644 --- a/examples/jsonExample/src/main/java/io/realm/examples/json/JsonExampleActivity.java +++ b/examples/jsonExample/src/main/java/io/realm/examples/json/JsonExampleActivity.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,11 +25,11 @@ import java.io.IOException; import java.io.InputStream; import java.util.HashMap; -import java.util.List; import java.util.Map; import io.realm.Realm; -import io.realm.RealmConfiguration; +import io.realm.RealmChangeListener; +import io.realm.RealmResults; /** * This example demonstrates how to import RealmObjects as JSON. Realm supports JSON represented @@ -37,76 +37,69 @@ */ public class JsonExampleActivity extends Activity { - private GridView mGridView; - private CityAdapter mAdapter; + private GridView gridView; + private CityAdapter adapter; + private Realm realm; + private RealmResults cities; + private RealmChangeListener> realmChangeListener = (cities) -> { + adapter.setData(cities); + }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_realm_example); - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(realmConfiguration); - realm = Realm.getInstance(realmConfiguration); - } + Realm.deleteRealm(Realm.getDefaultConfiguration()); - @Override - public void onResume() { - super.onResume(); + realm = Realm.getDefaultInstance(); - // Load from file "cities.json" first time - if(mAdapter == null) { - List cities = null; - try { - cities = loadCities(); - } catch (IOException e) { - e.printStackTrace(); - } + gridView = findViewById(R.id.cities_list); - //This is the GridView adapter - mAdapter = new CityAdapter(this); - mAdapter.setData(cities); + cities = realm.where(City.class).findAllAsync(); + cities.addChangeListener(realmChangeListener); - //This is the GridView which will display the list of cities - mGridView = (GridView) findViewById(R.id.cities_list); - mGridView.setAdapter(mAdapter); - mAdapter.notifyDataSetChanged(); - mGridView.invalidate(); - } + adapter = new CityAdapter(); + gridView.setAdapter(adapter); + + // Load from file "cities.json" first time + loadCities(); } + @Override protected void onDestroy() { super.onDestroy(); + cities.removeAllChangeListeners(); realm.close(); } - public List loadCities() throws IOException { - - loadJsonFromStream(); - loadJsonFromJsonObject(); - loadJsonFromString(); - - return realm.where(City.class).findAll(); + public void loadCities() { + try { + loadJsonFromStream(); + loadJsonFromJsonObject(); + loadJsonFromString(); + } catch(IOException e) { + throw new RuntimeException(e); + } } private void loadJsonFromStream() throws IOException { // Use streams if you are worried about the size of the JSON whether it was persisted on disk // or received from the network. - InputStream stream = getAssets().open("cities.json"); - - // Open a transaction to store items into the realm - realm.beginTransaction(); - try { - realm.createAllFromJson(City.class, stream); - realm.commitTransaction(); - } catch (IOException e) { - // Remember to cancel the transaction if anything goes wrong. - realm.cancelTransaction(); - } finally { - if (stream != null) { - stream.close(); + try(InputStream stream = getAssets().open("cities.json")) { + try { + // Open a transaction to store items into the realm + realm.beginTransaction(); + realm.createAllFromJson(City.class, stream); + realm.commitTransaction(); + } catch (IOException e) { + // Remember to cancel the transaction if anything goes wrong. + if(realm.isInTransaction()) { + realm.cancelTransaction(); + } + throw new RuntimeException(e); } } } @@ -117,22 +110,12 @@ private void loadJsonFromJsonObject() { city.put("votes", "9"); final JSONObject json = new JSONObject(city); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObjectFromJson(City.class, json); - } - }); + realm.executeTransaction(realm -> realm.createObjectFromJson(City.class, json)); } private void loadJsonFromString() { final String json = "{ name: \"Aarhus\", votes: 99 }"; - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObjectFromJson(City.class, json); - } - }); + realm.executeTransaction(realm -> realm.createObjectFromJson(City.class, json)); } } diff --git a/examples/jsonExample/src/main/res/layout/activity_realm_example.xml b/examples/jsonExample/src/main/res/layout/activity_realm_example.xml index c0934adf72..598cebccdb 100644 --- a/examples/jsonExample/src/main/res/layout/activity_realm_example.xml +++ b/examples/jsonExample/src/main/res/layout/activity_realm_example.xml @@ -3,23 +3,23 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingTop="@dimen/activity_vertical_margin" tools:context=".JsonExampleActivity"> From be73c40212edf7627f1a3b99eb22551e91e385e0 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Fri, 16 Mar 2018 21:00:05 +1100 Subject: [PATCH 1212/2110] Add architectureComponentsExample (#4685) --- .../build.gradle | 43 ++++++ .../architectureComponentsExample/lint.xml | 10 ++ .../src/main/AndroidManifest.xml | 21 +++ .../examples/arch/ArchExampleActivity.java | 95 ++++++++++++ .../realm/examples/arch/BackgroundTask.java | 92 ++++++++++++ .../examples/arch/CustomApplication.java | 58 ++++++++ .../realm/examples/arch/PersonFragment.java | 85 +++++++++++ .../examples/arch/PersonListFragment.java | 140 ++++++++++++++++++ .../examples/arch/PersonListViewModel.java | 45 ++++++ .../realm/examples/arch/PersonViewModel.java | 53 +++++++ .../arch/livemodel/LiveRealmObject.java | 105 +++++++++++++ .../arch/livemodel/LiveRealmResults.java | 94 ++++++++++++ .../io/realm/examples/arch/model/Person.java | 43 ++++++ .../examples/arch/utils/ContextUtils.java | 52 +++++++ .../res/drawable/ic_play_arrow_black_24dp.xml | 9 ++ .../main/res/drawable/ic_stop_black_24dp.xml | 9 ++ .../main/res/layout/activity_arch_example.xml | 25 ++++ .../src/main/res/layout/fragment_person.xml | 30 ++++ .../main/res/layout/fragment_person_list.xml | 11 ++ .../src/main/res/layout/item_person.xml | 54 +++++++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4906 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2968 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7076 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11165 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 16078 bytes .../src/main/res/values-w820dp/dimens.xml | 6 + .../src/main/res/values/dimens.xml | 5 + .../src/main/res/values/strings.xml | 6 + .../src/main/res/values/styles.xml | 8 + examples/settings.gradle | 8 +- 30 files changed, 1104 insertions(+), 3 deletions(-) create mode 100644 examples/architectureComponentsExample/build.gradle create mode 100644 examples/architectureComponentsExample/lint.xml create mode 100644 examples/architectureComponentsExample/src/main/AndroidManifest.xml create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/ArchExampleActivity.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/BackgroundTask.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListViewModel.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonViewModel.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmObject.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmResults.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java create mode 100644 examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/utils/ContextUtils.java create mode 100644 examples/architectureComponentsExample/src/main/res/drawable/ic_play_arrow_black_24dp.xml create mode 100644 examples/architectureComponentsExample/src/main/res/drawable/ic_stop_black_24dp.xml create mode 100644 examples/architectureComponentsExample/src/main/res/layout/activity_arch_example.xml create mode 100644 examples/architectureComponentsExample/src/main/res/layout/fragment_person.xml create mode 100644 examples/architectureComponentsExample/src/main/res/layout/fragment_person_list.xml create mode 100644 examples/architectureComponentsExample/src/main/res/layout/item_person.xml create mode 100755 examples/architectureComponentsExample/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100755 examples/architectureComponentsExample/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100755 examples/architectureComponentsExample/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100755 examples/architectureComponentsExample/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100755 examples/architectureComponentsExample/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 examples/architectureComponentsExample/src/main/res/values-w820dp/dimens.xml create mode 100644 examples/architectureComponentsExample/src/main/res/values/dimens.xml create mode 100644 examples/architectureComponentsExample/src/main/res/values/strings.xml create mode 100644 examples/architectureComponentsExample/src/main/res/values/styles.xml diff --git a/examples/architectureComponentsExample/build.gradle b/examples/architectureComponentsExample/build.gradle new file mode 100644 index 0000000000..2bf5eedf9f --- /dev/null +++ b/examples/architectureComponentsExample/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'com.android.application' +apply plugin: 'realm-android' + +repositories { + maven { + url 'https://maven.google.com' + } + google() +} + +android { + compileSdkVersion rootProject.sdkVersion + buildToolsVersion rootProject.buildTools + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + defaultConfig { + applicationId 'io.realm.examples.arch' + targetSdkVersion rootProject.sdkVersion + minSdkVersion 15 + versionCode 1 + versionName "1.0" + + vectorDrawables.useSupportLibrary = true + } + + buildTypes { + release { + minifyEnabled false + } + } +} + +dependencies { + implementation "android.arch.lifecycle:runtime:1.1.0" + implementation "android.arch.lifecycle:extensions:1.1.0" + annotationProcessor "android.arch.lifecycle:compiler:1.1.0" + implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:recyclerview-v7:27.0.2' + implementation 'com.android.support:design:27.0.2' +} diff --git a/examples/architectureComponentsExample/lint.xml b/examples/architectureComponentsExample/lint.xml new file mode 100644 index 0000000000..6a9810cdcb --- /dev/null +++ b/examples/architectureComponentsExample/lint.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/architectureComponentsExample/src/main/AndroidManifest.xml b/examples/architectureComponentsExample/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..6303608067 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/ArchExampleActivity.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/ArchExampleActivity.java new file mode 100644 index 0000000000..740ecb7f69 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/ArchExampleActivity.java @@ -0,0 +1,95 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.examples.arch; + +import android.os.Bundle; +import android.support.annotation.MainThread; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AppCompatActivity; + +public class ArchExampleActivity extends AppCompatActivity { + private FloatingActionButton backgroundJobStartStop; + + private BackgroundTask backgroundTask; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_arch_example); + setupViews(); + + backgroundTask = (BackgroundTask) getLastCustomNonConfigurationInstance(); + if (backgroundTask == null) { // this could also live inside a ViewModel, a singleton job queue, etc. + backgroundTask = new BackgroundTask(); + backgroundTask.start(); // this task will update items in Realm on a background thread. + } + updateJobButton(); + + if (savedInstanceState == null) { + getSupportFragmentManager().beginTransaction() + .add(R.id.container, PersonListFragment.create()) + .addToBackStack(null) + .commit(); + } + } + + @Override + public Object onRetainCustomNonConfigurationInstance() { + return backgroundTask; // retain background task through config changes without ViewModel. + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (isFinishing()) { + if(backgroundTask.isStarted()) { + backgroundTask.stop(); // make sure job is stopped when exiting the app + } + } + } + + @Override + public void onBackPressed() { + if (getSupportFragmentManager().getBackStackEntryCount() <= 1) { + finish(); + } else { + super.onBackPressed(); + } + } + + @MainThread + private void setupViews() { + backgroundJobStartStop = findViewById(R.id.backgroundJobStartStop); + backgroundJobStartStop.setOnClickListener(v -> { + if (!backgroundTask.isStarted()) { + backgroundTask.start(); + } else { + backgroundTask.stop(); + } + updateJobButton(); + }); + } + + private void updateJobButton() { + if (backgroundTask.isStarted()) { + backgroundJobStartStop.setImageResource(R.drawable.ic_stop_black_24dp); + } else { + backgroundJobStartStop.setImageResource(R.drawable.ic_play_arrow_black_24dp); + } + } +} diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/BackgroundTask.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/BackgroundTask.java new file mode 100644 index 0000000000..b99389a69b --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/BackgroundTask.java @@ -0,0 +1,92 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.examples.arch; + +import android.annotation.SuppressLint; +import android.os.SystemClock; +import android.support.annotation.MainThread; +import android.util.Log; + +import io.realm.Realm; +import io.realm.RealmResults; +import io.realm.examples.arch.model.Person; + + +public class BackgroundTask { + private static final Object lock = new Object(); + + private static final String TAG = "BackgroundTask"; + + private boolean isStarted; + + private volatile Thread thread; + + @MainThread + public boolean isStarted() { + return isStarted; + } + + @MainThread + public void start() { + synchronized (lock) { + if (isStarted) { + return; + } + thread = new IncrementThread(); + thread.start(); + isStarted = true; + Log.i(TAG, "Background job started."); + } + } + + @MainThread + public void stop() { + synchronized (lock) { + if (thread != null) { + thread.interrupt(); + thread = null; + } + isStarted = false; + } + } + + private static final class IncrementThread extends Thread { + IncrementThread() { + super("Aging thread"); + } + + @Override + @SuppressLint("NewApi") + public void run() { + try (Realm realm = Realm.getDefaultInstance()) { + final RealmResults persons = realm.where(Person.class).findAll(); + Realm.Transaction transaction = (Realm r) -> { + for (Person person : persons) { + person.setAge(person.getAge() + 1); // updates the Persons in the Realm. + } + }; + + while (!isInterrupted()) { + realm.executeTransaction(transaction); + SystemClock.sleep(1000L); + } + } + Log.i(TAG, "Background job stopped."); + } + } +} + diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java new file mode 100644 index 0000000000..b3c8333f70 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.examples.arch; + +import android.app.Application; +import android.support.annotation.NonNull; + +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.examples.arch.model.Person; + + +public class CustomApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + + Realm.init(this); + Realm.setDefaultConfiguration(new RealmConfiguration.Builder() + .deleteRealmIfMigrationNeeded() + .initialData(new Realm.Transaction() { + @Override + public void execute(@NonNull Realm realm) { + Person person = realm.createObject(Person.class); + person.setName("Makoto Yamazaki"); + person.setAge(32); + + person = realm.createObject(Person.class); + person.setName("Christian Melchior"); + person.setAge(34); + + person = realm.createObject(Person.class); + person.setName("Chen Mulong"); + person.setAge(29); + + person = realm.createObject(Person.class); + person.setName("Nabil Hachicha"); + person.setAge(31); + } + }) + .build()); + } +} diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java new file mode 100644 index 0000000000..194553cae3 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java @@ -0,0 +1,85 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.examples.arch; + +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProvider; +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class PersonFragment extends Fragment { + private static final String ARG_PERSON_NAME = "personName"; + + public static PersonFragment create(String personName) { + PersonFragment personFragment = new PersonFragment(); + Bundle bundle = new Bundle(); + bundle.putString(ARG_PERSON_NAME, personName); + personFragment.setArguments(bundle); + return personFragment; + } + + private PersonViewModel personViewModel; + + private TextView name; + private TextView age; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + @SuppressWarnings("ConstantConditions") final String personName = getArguments().getString(ARG_PERSON_NAME); + personViewModel = ViewModelProviders.of(this, new ViewModelProvider.Factory() { + @NonNull + @Override + public T create(@NonNull Class modelClass) { + if (modelClass == PersonViewModel.class) { + PersonViewModel personViewModel = new PersonViewModel(); + personViewModel.setup(personName); // we use a Factory to ensure `setup` is called before use. + //noinspection unchecked + return (T) personViewModel; + } + //noinspection ConstantConditions + return null; + } + }).get(PersonViewModel.class); + + personViewModel.getPerson().observe(this, person -> { + if (person != null) { // null would mean the object was deleted. + name.setText(person.getName()); + age.setText(String.valueOf(person.getAge())); + } + }); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_person, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + name = view.findViewById(R.id.personName); + age = view.findViewById(R.id.personAge); + } +} diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java new file mode 100644 index 0000000000..b879caced3 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java @@ -0,0 +1,140 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.examples.arch; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import java.util.Collections; +import java.util.List; + +import io.realm.examples.arch.model.Person; +import io.realm.examples.arch.utils.ContextUtils; + +public class PersonListFragment extends Fragment { + public static PersonListFragment create() { + return new PersonListFragment(); + } + + private RecyclerView recyclerView; + private Adapter adapter; + + private PersonListViewModel personListViewModel; + private List personList = Collections.emptyList(); + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Fragments should start listening in `onCreate()` + // to ensure single observer instance, even if detached (for example in FragmentPagerAdapter). + personListViewModel = ViewModelProviders.of(this).get(PersonListViewModel.class); + personListViewModel.getPersons().observe(this, people -> { + personList = people; + if (adapter != null) { + adapter.updateItems(people); + } + }); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_person_list, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + recyclerView = view.findViewById(R.id.recyclerView); + recyclerView.setHasFixedSize(true); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); + adapter = new Adapter(personList); + recyclerView.setAdapter(adapter); + } + + static class Adapter extends RecyclerView.Adapter { + private List persons; + + public Adapter(List persons) { + this.persons = persons; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_person, parent, false)); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + holder.bind(persons.get(position)); + } + + @Override + public int getItemCount() { + return persons == null ? 0 : persons.size(); + } + + public void updateItems(List persons) { + this.persons = persons; + notifyDataSetChanged(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + TextView name; + TextView age; + + Person person; + + private final View.OnClickListener onClick = (view) -> { + if (person == null) { + return; + } + AppCompatActivity activity = ContextUtils.findActivity(view.getContext()); + PersonFragment personFragment = PersonFragment.create(person.getName()); + activity.getSupportFragmentManager() + .beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .replace(R.id.container, personFragment) + .addToBackStack(null) + .commit(); + }; + + public ViewHolder(View itemView) { + super(itemView); + name = itemView.findViewById(R.id.personName); + age = itemView.findViewById(R.id.personAge); + itemView.setOnClickListener(onClick); + } + + public void bind(Person person) { + this.person = person; + name.setText(person.getName()); + age.setText(String.valueOf(person.getAge())); + } + } + } +} diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListViewModel.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListViewModel.java new file mode 100644 index 0000000000..e45edc4bd5 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListViewModel.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.examples.arch; + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; + +import java.util.List; + +import io.realm.Realm; +import io.realm.examples.arch.livemodel.LiveRealmResults; +import io.realm.examples.arch.model.Person; + +public class PersonListViewModel extends ViewModel { + private final Realm realm; + private final LiveData> persons; + + public PersonListViewModel() { + realm = Realm.getDefaultInstance(); // Realm is bound to the lifecycle of the ViewModel, and stays alive as long as it is needed. + persons = new LiveRealmResults<>(realm.where(Person.class).sort("age").findAllAsync()); + } + + public LiveData> getPersons() { + return persons; + } + + @Override + protected void onCleared() { + realm.close(); // Realm is bound to the lifecycle of the ViewModel, and is destroyed when no longer needed. + super.onCleared(); + } +} diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonViewModel.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonViewModel.java new file mode 100644 index 0000000000..666a938742 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonViewModel.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.examples.arch; + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; + +import io.realm.Realm; +import io.realm.examples.arch.livemodel.LiveRealmObject; +import io.realm.examples.arch.model.Person; + + +public class PersonViewModel extends ViewModel { + private final Realm realm; + + private LiveData livePerson; + + public PersonViewModel() { + realm = Realm.getDefaultInstance(); // Realm is bound to the lifecycle of the ViewModel, and stays alive as long as it is needed. + } + + public LiveData getPerson() { + return livePerson; + } + + @Override + protected void onCleared() { + realm.close(); // Realm is bound to the lifecycle of the ViewModel, and is destroyed when no longer needed. + super.onCleared(); + } + + public void setup(String personName) { + Person person = realm.where(Person.class).equalTo("name", personName).findFirst(); + if (person == null) { + throw new IllegalStateException("The person was not found, it shouldn't be deleted!"); + } + livePerson = new LiveRealmObject<>(person); + } +} diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmObject.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmObject.java new file mode 100644 index 0000000000..97690d596f --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmObject.java @@ -0,0 +1,105 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.examples.arch.livemodel; + +import android.arch.lifecycle.LiveData; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; + +import io.realm.ObjectChangeSet; +import io.realm.RealmModel; +import io.realm.RealmObject; +import io.realm.RealmObjectChangeListener; + +/** + * This class represents a RealmObject wrapped inside a LiveData. + * + * It is expected that the provided RealmObject is a managed object, and exists in the Realm on creation. + * + * This allows observing the RealmObject in such a way, that the listener that will be automatically unsubscribed when the enclosing LifecycleOwner is killed. + * + * Realm will keep the managed RealmObject up-to-date whenever a change occurs on any thread, + * and when that happens, the observer will be notified. + * + * The object will be observed until it is invalidated - deleted, or all local Realm instances are closed. + * + * @param the type of the RealmModel + */ +public class LiveRealmObject extends LiveData { + // The listener will listen until the object is deleted. + // An invalidated object shouldn't be set in LiveData, null is set instead. + private RealmObjectChangeListener listener = new RealmObjectChangeListener() { + @Override + public void onChange(@NonNull T object, ObjectChangeSet objectChangeSet) { + if (!objectChangeSet.isDeleted()) { + setValue(object); + } else { + setValue(null); + } + } + }; + + /** + * Wraps the provided managed RealmObject as a LiveData. + * + * The provided object should not be null, should be managed, and should be valid. + * + * @param object the managed RealmModel to wrap as LiveData + */ + @MainThread + public LiveRealmObject(@NonNull T object) { + //noinspection ConstantConditions + if (object == null) { + throw new IllegalArgumentException("The object cannot be null!"); + } + if (!RealmObject.isManaged(object)) { + throw new IllegalArgumentException("LiveRealmObject only supports managed RealmModel instances!"); + } + if (!RealmObject.isValid(object)) { + throw new IllegalArgumentException("The provided RealmObject is no longer valid, and therefore cannot be observed for changes."); + } + setValue(object); + } + + // We should start observing and stop observing, depending on whether we have observers. + // Deleted objects can no longer be observed. + // We can also no longer observe the object if all local Realm instances on this thread (the UI thread) are closed. + + /** + * Starts observing the RealmObject, if it is still valid. + */ + @Override + protected void onActive() { + super.onActive(); + T object = getValue(); + if (object != null && RealmObject.isValid(object)) { + RealmObject.addChangeListener(object, listener); + } + } + + /** + * Stops observing the RealmObject. + */ + @Override + protected void onInactive() { + super.onInactive(); + T object = getValue(); + if (object != null && RealmObject.isValid(object)) { + RealmObject.removeChangeListener(object, listener); + } + } +} diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmResults.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmResults.java new file mode 100644 index 0000000000..8b54563cec --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/livemodel/LiveRealmResults.java @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.examples.arch.livemodel; + +import android.arch.lifecycle.LiveData; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; + +import java.util.List; + +import javax.annotation.Nullable; + +import io.realm.OrderedCollectionChangeSet; +import io.realm.OrderedRealmCollectionChangeListener; +import io.realm.RealmModel; +import io.realm.RealmResults; + +/** + * This class represents a RealmResults wrapped inside a LiveData. + * + * Realm will always keep the RealmResults up-to-date whenever a change occurs on any thread, + * and when that happens, the observer will be notified. + * + * The RealmResults will be observed until it is invalidated - meaning all local Realm instances on this thread are closed. + * + * @param the type of the RealmModel + */ +public class LiveRealmResults extends LiveData> { + private final RealmResults results; + + // The listener will notify the observers whenever a change occurs. + // The results are modified in change. This could be expanded to also return the change set in a pair. + private OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(@NonNull RealmResults results, @Nullable OrderedCollectionChangeSet changeSet) { + LiveRealmResults.this.setValue(results); + } + }; + + @MainThread + public LiveRealmResults(@NonNull RealmResults results) { + //noinspection ConstantConditions + if (results == null) { + throw new IllegalArgumentException("Results cannot be null!"); + } + if (!results.isValid()) { + throw new IllegalArgumentException("The provided RealmResults is no longer valid, the Realm instance it belongs to is closed. It can no longer be observed for changes."); + } + this.results = results; + if (results.isLoaded()) { + // we should not notify observers when results aren't ready yet (async query). + // however, synchronous query should be set explicitly. + setValue(results); + } + } + + // We should start observing and stop observing, depending on whether we have observers. + + /** + * Starts observing the RealmResults, if it is still valid. + */ + @Override + protected void onActive() { + super.onActive(); + if (results.isValid()) { // invalidated results can no longer be observed. + results.addChangeListener(listener); + } + } + + /** + * Stops observing the RealmResults. + */ + @Override + protected void onInactive() { + super.onInactive(); + if (results.isValid()) { + results.removeChangeListener(listener); + } + } +} diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java new file mode 100644 index 0000000000..fac53a7398 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.examples.arch.model; + +import io.realm.RealmObject; +import io.realm.annotations.Index; + +public class Person extends RealmObject { + @Index + private String name; + + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } +} diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/utils/ContextUtils.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/utils/ContextUtils.java new file mode 100644 index 0000000000..1a4cf1cc26 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/utils/ContextUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.examples.arch.utils; + +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; + +/** + * This is a helper class to look up an Activity inside a View's context chain in a reliable/safe manner. + */ +public class ContextUtils { + private ContextUtils() { + } + + /** + * Finds the Activity inside the hierarchy of the provided Context. + * + * @param context the context + * @param the expected type of the Activity + * @return the activity + * + * @throws IllegalArgumentException if the context has no Activity in its base context hierarchy + */ + public static T findActivity(Context context) { + if (context instanceof Activity) { + //noinspection unchecked + return (T) context; + } + while (context != null && context instanceof ContextWrapper) { + context = ((ContextWrapper) context).getBaseContext(); + if (context instanceof Activity) { + //noinspection unchecked + return (T) context; + } + } + throw new IllegalArgumentException("No activity found in context hierarchy."); + } +} diff --git a/examples/architectureComponentsExample/src/main/res/drawable/ic_play_arrow_black_24dp.xml b/examples/architectureComponentsExample/src/main/res/drawable/ic_play_arrow_black_24dp.xml new file mode 100644 index 0000000000..bf9b895aca --- /dev/null +++ b/examples/architectureComponentsExample/src/main/res/drawable/ic_play_arrow_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/examples/architectureComponentsExample/src/main/res/drawable/ic_stop_black_24dp.xml b/examples/architectureComponentsExample/src/main/res/drawable/ic_stop_black_24dp.xml new file mode 100644 index 0000000000..c428d728dd --- /dev/null +++ b/examples/architectureComponentsExample/src/main/res/drawable/ic_stop_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/examples/architectureComponentsExample/src/main/res/layout/activity_arch_example.xml b/examples/architectureComponentsExample/src/main/res/layout/activity_arch_example.xml new file mode 100644 index 0000000000..45bbf88ac8 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/res/layout/activity_arch_example.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/examples/architectureComponentsExample/src/main/res/layout/fragment_person.xml b/examples/architectureComponentsExample/src/main/res/layout/fragment_person.xml new file mode 100644 index 0000000000..48d7965ee9 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/res/layout/fragment_person.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/examples/architectureComponentsExample/src/main/res/layout/fragment_person_list.xml b/examples/architectureComponentsExample/src/main/res/layout/fragment_person_list.xml new file mode 100644 index 0000000000..a4eb4e4289 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/res/layout/fragment_person_list.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/examples/architectureComponentsExample/src/main/res/layout/item_person.xml b/examples/architectureComponentsExample/src/main/res/layout/item_person.xml new file mode 100644 index 0000000000..4b1de1a9b2 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/res/layout/item_person.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/architectureComponentsExample/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/architectureComponentsExample/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..58303aff5b97f3c5a1757573ef73347d3475e16c GIT binary patch literal 4906 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 O0000 literal 0 HcmV?d00001 diff --git a/examples/architectureComponentsExample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/architectureComponentsExample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..eb9ece04b26b69f1d98f9294716e8c982a4577b9 GIT binary patch literal 11165 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 + + 64dp + diff --git a/examples/architectureComponentsExample/src/main/res/values/dimens.xml b/examples/architectureComponentsExample/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..47c8224673 --- /dev/null +++ b/examples/architectureComponentsExample/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/examples/architectureComponentsExample/src/main/res/values/strings.xml b/examples/architectureComponentsExample/src/main/res/values/strings.xml new file mode 100644 index 0000000000..fcfdd7f1bb --- /dev/null +++ b/examples/architectureComponentsExample/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + Lifecycle example + + diff --git a/examples/architectureComponentsExample/src/main/res/values/styles.xml b/examples/architectureComponentsExample/src/main/res/values/styles.xml new file mode 100644 index 0000000000..54b991006c --- /dev/null +++ b/examples/architectureComponentsExample/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/examples/settings.gradle b/examples/settings.gradle index 42ff9775fe..4a5ac90600 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -1,5 +1,5 @@ rootProject.name = 'realm-examples' -include 'secureTokenAndroidKeyStore' +include 'architectureComponentsExample' include 'encryptionExample' include 'gridViewExample' include 'introExample' @@ -8,9 +8,11 @@ include 'kotlinExample' include 'migrationExample' include 'moduleExample:app' include 'moduleExample:library' -include 'threadExample' -include 'unitTestExample' include 'newsreaderExample' include 'rxJavaExample' +include 'secureTokenAndroidKeyStore' +include 'threadExample' +include 'unitTestExample' include 'objectServerExample' include 'multiprocessExample' + From c8dbd04b7805dda90f1266a91cc9e492e7977e30 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 16 Mar 2018 13:46:49 +0000 Subject: [PATCH 1213/2110] SyncConfiguration#automatic to use the host port (#5844) * port is inferred for automatic SyncConfiguration --- CHANGELOG.md | 6 ++++++ .../java/io/realm/SyncConfigurationTests.java | 8 ++++++-- .../src/objectServer/java/io/realm/SyncConfiguration.java | 4 ++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a845bf37..f0820934bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.0.1 (YYYY-MM-DD) + +### Enhancements + +* [ObjectServer] `SyncConfiguration.automatic()` will make use of the host port to work out the default Realm URL. + ## 5.0.0 (2018-03-15) This release is compatible with the Realm Object Server 3.0.0-beta.3 or later. diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index fe5195774f..bf05c006ab 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -528,14 +528,18 @@ public void automatic_convertsAuthUrl() { Object[][] input = { // AuthUrl -> Expected Realm URL { "http://ros.realm.io/auth", "realm://ros.realm.io/default" }, - { "http://ros.realm.io:7777", "realm://ros.realm.io/default" }, + { "http://ros.realm.io:7777", "realm://ros.realm.io:7777/default" }, { "http://127.0.0.1/auth", "realm://127.0.0.1/default" }, { "HTTP://ros.realm.io" , "realm://ros.realm.io/default" }, { "https://ros.realm.io/auth", "realms://ros.realm.io/default" }, - { "https://ros.realm.io:7777", "realms://ros.realm.io/default" }, + { "https://ros.realm.io:7777", "realms://ros.realm.io:7777/default" }, { "https://127.0.0.1/auth", "realms://127.0.0.1/default" }, { "HTTPS://ros.realm.io" , "realms://ros.realm.io/default" }, + // with port + { "http://192.168.1.65:9080" , "realm://192.168.1.65:9080/default" }, + { "http://192.168.1.65:9080/auth" , "realm://192.168.1.65:9080/default" }, + { "https://192.168.1.65:9080/auth" , "realms://192.168.1.65:9080/default" }, }; for (Object[] test : input) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 362c9022b1..7cbf711b85 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -233,6 +233,10 @@ private static String createUrl(SyncUser user) { URL url = user.getAuthenticationUrl(); String protocol = url.getProtocol(); String host = url.getHost(); + int port = url.getPort(); + if (port != -1) { // port set + host += ":" + port; + } if (protocol.equalsIgnoreCase("https")) { protocol = "realms"; From cf4021b11018ffc0725ed09be09a6b486e012494 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 21 Mar 2018 10:00:12 +0100 Subject: [PATCH 1214/2110] Fix findFirst with sorting (#5848) --- CHANGELOG.md | 5 +++ .../java/io/realm/RealmAsyncQueryTests.java | 19 +++++++++ .../java/io/realm/RealmQueryTests.java | 41 +++++++++++++++++++ .../src/main/java/io/realm/RealmQuery.java | 14 +++++-- .../src/main/java/io/realm/internal/Row.java | 3 ++ 5 files changed, 79 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0820934bd..a35b2a1c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ * [ObjectServer] `SyncConfiguration.automatic()` will make use of the host port to work out the default Realm URL. +### Bug Fixes + +* `RealmQuery.findFirst()` and `RealmQuery.findFirstAsync()` not working correctly with sorting (#5714). + + ## 5.0.0 (2018-03-15) This release is compatible with the Realm Object Server 3.0.0-beta.3 or later. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 34b356ae5c..09a982160e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -761,6 +761,25 @@ public void onChange(AllTypes element) { }); } + @Test + @RunTestInLooperThread + public void findFirstAsync_withSorting() { + Realm realm = looperThread.getRealm(); + realm.beginTransaction(); + realm.insert(new Dog("Milo")); + realm.insert(new Dog("Fido")); + realm.insert(new Dog("Bella")); + realm.commitTransaction(); + + Dog dog = realm.where(Dog.class).sort("name").findFirstAsync(); + dog.addChangeListener((Dog d) -> { + assertTrue(d.isValid()); + assertEquals("Bella", d.getName()); + looperThread.testComplete(); + }); + looperThread.keepStrongReference(dog); + } + // ************************************** // *** 'findAllSorted' async queries *** // ************************************** diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 68a4bc5f95..cfd7dd48f6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -1366,6 +1366,47 @@ public void findFirst() { assertEquals(dog4, dog); } + @Test + public void findFirst_withSorting() { + realm.beginTransaction(); + realm.insert(new Dog("Milo")); + realm.insert(new Dog("Fido")); + realm.insert(new Dog("Bella")); + realm.commitTransaction(); + + Dog dog = realm.where(Dog.class).sort("name").findFirst(); + assertEquals("Bella", dog.getName()); + } + + @Test + public void findFirst_withSortedConstrictingView() { + realm.beginTransaction(); + realm.insert(new Dog("Milo")); + realm.insert(new Dog("Fido")); + realm.insert(new Dog("Bella")); + realm.commitTransaction(); + + RealmResults dogs = realm.where(Dog.class) + .in("name", new String[] { "Fido", "Bella" }) + .sort("name", Sort.ASCENDING) + .findAll(); + Dog dog = dogs.where().findFirst(); + assertEquals("Bella", dog.getName()); + } + + @Test + public void findFirst_subQuery_withSorting() { + realm.beginTransaction(); + realm.insert(new Dog("Milo")); + realm.insert(new Dog("Fido")); + realm.insert(new Dog("Bella")); + realm.commitTransaction(); + + RealmResults dogs = realm.where(Dog.class).in("name", new String[] { "Fido", "Bella" }).findAll(); + Dog dog = dogs.where().sort("name", Sort.ASCENDING).findFirst(); + assertEquals("Bella", dog.getName()); + } + @Test public void georgian() { String words[] = {"მონაცემთა ბაზა", "მიწისქვეშა გადასასვლელი", "რუსთაველის გამზირი", diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index d2597933f0..7fec99ead8 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1752,7 +1752,6 @@ public long count() { @SuppressWarnings("unchecked") public RealmResults findAll() { realm.checkIfValid(); - return createRealmResults(query, sortDescriptor, distinctDescriptor, true, SubscriptionAction.NO_SUBSCRIPTION); } @@ -1980,7 +1979,7 @@ public E findFirstAsync() { // TODO: The performance by the pending query will be a little bit worse than directly calling core's // Query.find(). The overhead comes with core needs to add all the row indices to the vector. However this // can be optimized by adding support of limit in OS's Results which is supported by core already. - row = new PendingRow(realm.sharedRealm, query, null, isDynamicQuery()); + row = new PendingRow(realm.sharedRealm, query, sortDescriptor, isDynamicQuery()); } final E result; if (isDynamicQuery()) { @@ -2030,7 +2029,16 @@ private RealmResults createRealmResults(TableQuery query, } private long getSourceRowIndexForFirstObject() { - return this.query.find(); + if (sortDescriptor != null || distinctDescriptor != null) { + RealmObjectProxy obj = (RealmObjectProxy) findAll().first(null); + if (obj != null) { + return obj.realmGet$proxyState().getRow$realm().getIndex(); + } else { + return -1; + } + } else { + return this.query.find(); + } } private SchemaConnector getSchemaConnector() { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index 681d818999..1256c37f8f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -61,6 +61,9 @@ public interface Row { Table getTable(); + /** + * Returns the index in the original source table, not the tableview. + */ long getIndex(); long getLong(long columnIndex); From 04c82222ab539fd16bcfd228aa3f8e3563fcc451 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 22 Mar 2018 13:45:37 +0100 Subject: [PATCH 1215/2110] Fix SyncSession tests (#5853) --- dependencies.list | 2 +- .../io/realm/internal/ObjectServerFacade.java | 1 + .../java/io/realm/SyncManager.java | 13 ++- .../network/ExponentialBackoffTask.java | 8 +- .../java/io/realm/SyncSessionTests.java | 29 +++-- .../io/realm/SyncedRealmIntegrationTests.java | 4 +- ...ObjectLevelPermissionIntegrationTests.java | 107 +++--------------- .../realm/objectserver/utils/Constants.java | 2 +- .../realm/objectserver/utils/UserFactory.java | 21 ++-- .../testUtils/java/io/realm/TestHelper.java | 51 +++++++++ .../java/io/realm/rule/RunInLooperThread.java | 2 + 11 files changed, 115 insertions(+), 125 deletions(-) diff --git a/dependencies.list b/dependencies.list index f58dbb65cb..6a3c2d6332 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,4 +5,4 @@ REALM_SYNC_SHA256=9141177ccc92d8f9282625dace61eee5c3d971d2daca7593266e175b610a24 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=3.0.0-rc.1 +REALM_OBJECT_SERVER_DE_VERSION=3.0.0 diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 509ffa8c99..df194d2f83 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -126,4 +126,5 @@ public void addSupportForObjectLevelPermissions(RealmConfiguration.Builder build public OsResults createSubscriptionAwareResults(OsSharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor, String name) { throw new IllegalStateException("Should only be called by builds supporting Sync"); } + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index cc45224295..775b89954d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -88,10 +88,15 @@ public static class Debug { */ public static String APP_ID = null; - // Thread pool used when doing network requests against the Realm Authentication Server. - // FIXME Set proper parameters - static final ThreadPoolExecutor NETWORK_POOL_EXECUTOR = new ThreadPoolExecutor( - 10, 10, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(100)); + /** + * Thread pool used when doing network requests against the Realm Object Server. + *

            + * This pool is only exposed for testing purposes and replacing it while the queue is not + * empty will result in undefined behaviour. + */ + @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + public static ThreadPoolExecutor NETWORK_POOL_EXECUTOR = new ThreadPoolExecutor( + 10, 10, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100)); private static final SyncSession.ErrorHandler SESSION_NO_OP_ERROR_HANDLER = new SyncSession.ErrorHandler() { @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java index a5686ae139..f23dc5022b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java @@ -19,6 +19,7 @@ import java.util.concurrent.TimeUnit; import io.realm.ErrorCode; +import io.realm.log.RealmLog; /** * Abstracts the concept of running an network task with incremental backoff. It will run forever until interrupted. @@ -46,7 +47,9 @@ protected boolean isSuccess(T result) { protected boolean shouldAbortTask(T response) { // Only retry in case of IO exceptions, since that might be network timeouts etc. // All other errors indicate a bigger problem, so just stop the task. - if (!response.isValid()) { + if (Thread.interrupted()) { + return true; + } else if (!response.isValid()) { return response.getError().getErrorCode() != ErrorCode.IO_EXCEPTION; } else { return false; @@ -62,13 +65,14 @@ protected boolean shouldAbortTask(T response) { @Override public void run() { int attempt = 0; - while (true) { + while (!Thread.interrupted()) { attempt++; long sleep = calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); if (sleep > 0) { try { Thread.sleep(sleep); } catch (InterruptedException e) { + RealmLog.debug("Incremental backoff was interrupted."); return; // Abort if interrupted } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 8b33e38896..6c73bcd8a2 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -21,6 +21,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; import io.realm.internal.OsRealmConfig; +import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; @@ -327,8 +328,8 @@ public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { Realm realm = Realm.getInstance(syncConfiguration); realm.beginTransaction(); - // upload 50MB - for (int i = 0; i < 25; i++) { + // upload 10MB + for (int i = 0; i < 5; i++) { realm.createObject(StringOnly.class).setChars(twoMBString); } realm.commitTransaction(); @@ -345,33 +346,29 @@ public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { public void run() { // using an admin user to open the Realm on different path on the device to monitor when all the uploads are done SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); - SyncUser adminUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL); - SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) + SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(admin, syncConfiguration.getServerUrl().toString()) .modules(new StringOnlyModule()) .build(); final Realm adminRealm = Realm.getInstance(adminConfig); RealmResults all = adminRealm.where(StringOnly.class).findAll(); strongRefs.add(all); - RealmChangeListener> realmChangeListener = new RealmChangeListener>() { - @Override - public void onChange(RealmResults stringOnlies) { - if (stringOnlies.size() == 25) { - for (int i = 0; i < 25; i++) { - assertEquals(1_000_000, stringOnlies.get(i).getChars().length()); - } - adminRealm.close(); - testCompleted.countDown(); - handlerThread.quit(); + OrderedRealmCollectionChangeListener> realmChangeListener = (results, changeSet) -> { + RealmLog.info("Size: " + results.size() + ", state: " + changeSet.getState().toString()); + if (results.size() == 5) { + for (int i = 0; i < 5; i++) { + assertEquals(1_000_000, results.get(i).getChars().length()); } + adminRealm.close(); + testCompleted.countDown(); + handlerThread.quit(); } }; all.addChangeListener(realmChangeListener); } }); - TestHelper.awaitOrFail(testCompleted, 60); + TestHelper.awaitOrFail(testCompleted, TestHelper.STANDARD_WAIT_SECS); handlerThread.join(); user.logOut(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 083ee4b359..24cab081af 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -324,7 +324,7 @@ public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { @Test public void defaultRealm() throws InterruptedException { - SyncCredentials credentials = SyncCredentials.nickname("test", true); + SyncCredentials credentials = SyncCredentials.nickname("test", false); SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); SyncConfiguration config = SyncConfiguration.automatic(); Realm realm = Realm.getInstance(config); @@ -332,7 +332,7 @@ public void defaultRealm() throws InterruptedException { realm.refresh(); try { - assertFalse(realm.isEmpty()); + assertTrue(realm.isEmpty()); } finally { realm.close(); user.logOut(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java index c3f7979701..29c9c89cbe 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java @@ -15,8 +15,6 @@ */ package io.realm.objectserver; -import android.os.Handler; -import android.os.HandlerThread; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; @@ -24,29 +22,21 @@ import java.util.Arrays; import java.util.List; -import java.util.concurrent.CountDownLatch; -import io.realm.ObjectServerError; -import io.realm.PermissionManager; +import io.realm.IsolatedIntegrationTests; import io.realm.Realm; import io.realm.RealmResults; -import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.SyncUser; -import io.realm.TestHelper; import io.realm.annotations.RealmModule; import io.realm.entities.AllJavaTypes; -import io.realm.internal.android.AndroidCapabilities; -import io.realm.internal.permissions.PermissionModule; import io.realm.internal.sync.permissions.ObjectPermissionsModule; +import io.realm.log.RealmLog; import io.realm.objectserver.model.PermissionObject; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; -import io.realm.permissions.AccessLevel; -import io.realm.permissions.PermissionRequest; -import io.realm.permissions.UserCondition; import io.realm.rule.RunTestInLooperThread; import io.realm.sync.permissions.ClassPrivileges; import io.realm.sync.permissions.ObjectPrivileges; @@ -55,12 +45,16 @@ import io.realm.sync.permissions.Role; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +/** + * Integration tests for Object Level Permissions. + * Each test is run in isolation as we use the the global default Realm for each test. + * It is currently not possible to manually create a world readable Realm as + * {@link io.realm.PermissionManager} is unstable on CI. + */ @RunWith(AndroidJUnit4.class) -public class ObjectLevelPermissionIntegrationTests extends StandardIntegrationTest { +public class ObjectLevelPermissionIntegrationTests extends IsolatedIntegrationTests { @RealmModule(classes = {AllJavaTypes.class}) public static class ObjectLevelTestModule { @@ -74,12 +68,9 @@ public static class OLPermissionModule { @Test @RunTestInLooperThread() public void getPrivileges_serverDefaults() throws InterruptedException { - String realmUrl = Constants.GLOBAL_REALM + "_getPrivileges_serverDefaults"; List schemaModule = Arrays.asList(new ObjectLevelTestModule()); - createWorldReadableRealm(realmUrl, schemaModule); - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, realmUrl) + SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM) .modules(schemaModule) .partialRealm() .build(); @@ -117,25 +108,16 @@ public void getPrivileges_serverDefaults() throws InterruptedException { looperThread.testComplete(); } -// @Test -// @RunTestInLooperThread -// public void getRoles() { -// fail("FIXME"); -// looperThread.testComplete(); -// } - // Restrict read/write permission, only the owner of the object can see/modify it @Test @RunTestInLooperThread() public void restrictAccessToOwner() throws InterruptedException { - String realmUrl = Constants.GLOBAL_REALM + "_restrictAccessToOwner"; List schemaModules = Arrays.asList(new StringOnlyModule(), new OLPermissionModule(), new ObjectPermissionsModule()); - createWorldReadableRealm(realmUrl, schemaModules); // connect with user1 SyncUser user1 = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncConfiguration user1SyncConfig = configurationFactory - .createSyncConfigurationBuilder(user1, realmUrl) + .createSyncConfigurationBuilder(user1, Constants.DEFAULT_REALM) .modules(schemaModules) .partialRealm() .build(); @@ -166,7 +148,7 @@ public void restrictAccessToOwner() throws InterruptedException { // Connect with admin user and verify that user1 object is visible (non-partial Realm) SyncUser adminUser = UserFactory.createNicknameUser(Constants.AUTH_URL, "admin2", true); - SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, realmUrl) + SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, Constants.DEFAULT_REALM) .modules(schemaModules) .waitForInitialRemoteData() .build(); @@ -182,7 +164,7 @@ public void restrictAccessToOwner() throws InterruptedException { // Connect with user 2 and verify that user1 object is not visible SyncUser user2 = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfig2 = configurationFactory.createSyncConfigurationBuilder(user2, realmUrl) + SyncConfiguration syncConfig2 = configurationFactory.createSyncConfigurationBuilder(user2, Constants.DEFAULT_REALM) .modules(schemaModules) .partialRealm() .build(); @@ -192,17 +174,10 @@ public void restrictAccessToOwner() throws InterruptedException { looperThread.keepStrongReference(allAsync); // new object should not be visible for user2 partial sync allAsync.addChangeListener((permissionObjects2, changeSet) -> { - switch (changeSet.getState()) { - case INITIAL: - assertEquals(0, permissionObjects2.size()); - break; - case UPDATE: - assertEquals(0, permissionObjects2.size()); - looperThread.testComplete(); - break; - case ERROR: - fail("Unexpected error callback"); - break; + RealmLog.info("State: " + changeSet.getState().toString() + ", complete: " + changeSet.isCompleteResult()); + if (changeSet.isCompleteResult()) { + assertEquals(0, permissionObjects2.size()); + looperThread.testComplete(); } }); } @@ -239,52 +214,4 @@ private void assertFullAccess(ObjectPrivileges privileges) { assertTrue(privileges.canSetPermissions()); } - private void createWorldReadableRealm(String realmUrl, List modules) { - HandlerThread t = new HandlerThread("create-realm-thread"); - t.start(); - Handler handler = new Handler(t.getLooper()); - CountDownLatch setupRealm = new CountDownLatch(1); - handler.post(() -> { - final boolean oldValue = AndroidCapabilities.EMULATE_MAIN_THREAD; - SyncUser adminUser = UserFactory.createNicknameUser(Constants.AUTH_URL, "admin", true); - SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, realmUrl) - .modules(modules) - .addModule(new PermissionModule()) - .partialRealm() - .waitForInitialRemoteData() - .build(); - Realm.getInstanceAsync(syncConfig, new Realm.Callback() { - @Override - public void onSuccess(Realm realm) { - AndroidCapabilities.EMULATE_MAIN_THREAD = true; - PermissionManager pm = adminUser.getPermissionManager(); - pm.applyPermissions(new PermissionRequest(UserCondition.noExistingPermissions(), realmUrl, AccessLevel.WRITE), new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - handler.post(() -> { - AndroidCapabilities.EMULATE_MAIN_THREAD = oldValue; - pm.close(); - realm.close(); - adminUser.logOut(); - setupRealm.countDown(); - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Override - public void onError(Throwable exception) { - fail(exception.toString()); - } - }); - }); - TestHelper.awaitOrFail(setupRealm); - } - } - diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java index 9414bb4fcd..d39ae9dd24 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java @@ -25,7 +25,7 @@ public class Constants { public static final String USER_REALM_SECURE = "realms://" + HOST + ":9443/~/tests"; public static final String SYNC_SERVER_URL = "realm://" + HOST + ":9080/~/tests"; public static final String SYNC_SERVER_URL_2 = "realm://" + HOST + ":9080/~/tests2"; - + public static final String DEFAULT_REALM = "realm://" + HOST + ":9080/default"; public static final String AUTH_SERVER_URL = "http://" + HOST + ":9080/"; public static final String AUTH_URL = AUTH_SERVER_URL + "auth"; } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index ccfd1c2f85..88c4ab3ee5 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import io.realm.Realm; import io.realm.RealmConfiguration; @@ -145,18 +146,20 @@ public static void logoutAllUsers() { final HandlerThread ht = new HandlerThread("LoggingOutUsersThread"); ht.start(); Handler handler = new Handler(ht.getLooper()); - handler.post(new Runnable() { - @Override - public void run() { - Map users = SyncUser.all(); - for (SyncUser user : users.values()) { - user.logOut(); - } - allUsersLoggedOut.countDown(); - + handler.post(() -> { + Map users = SyncUser.all(); + for (SyncUser user : users.values()) { + user.logOut(); } + TestHelper.waitForNetworkThreadExecutorToFinish(); + allUsersLoggedOut.countDown(); }); TestHelper.awaitOrFail(allUsersLoggedOut); ht.quit(); + try { + ht.join(TimeUnit.SECONDS.toMillis(TestHelper.SHORT_WAIT_SECS)); + } catch (InterruptedException e) { + throw new AssertionError("LoggingOutUsersThread failed to finish in time"); + } } } diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index a765dc60c2..a9db3a9934 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -40,8 +40,10 @@ import java.util.Locale; import java.util.Random; import java.util.UUID; +import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -59,6 +61,7 @@ import io.realm.internal.OsObject; import io.realm.internal.OsSharedRealm; import io.realm.internal.Table; +import io.realm.internal.Util; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.LogLevel; import io.realm.log.RealmLogger; @@ -1249,4 +1252,52 @@ public static void populateLinkedDataSet(Realm realm) { realm.commitTransaction(); } + /** + * This method will kill all tasks then shutdown and replace the SyncManager.NETWORK_POOL_EXECUTOR + * with a fresh and empty instance. This should only be called when exiting tests. + * + * If the build does not support Sync, this method will do nothing + */ + private static final Field networkPoolExecutorField; + static { + Class syncManager = null; + try { + syncManager = Class.forName("io.realm.SyncManager"); + } catch (ClassNotFoundException e) { + // Ignore + } + + try { + networkPoolExecutorField = (syncManager != null) ? syncManager.getDeclaredField("NETWORK_POOL_EXECUTOR") : null; + } catch (NoSuchFieldException e) { + throw new AssertionError("Could not find field: NETWORK_POOL_EXECUTOR\n" + Util.getStackTrace(e)); + } + } + + public static void waitForNetworkThreadExecutorToFinish() { + if (networkPoolExecutorField == null) { + return; // This build do not support Sync + } + try { + ThreadPoolExecutor pool = (ThreadPoolExecutor) networkPoolExecutorField.get(null); + // Since this method should only be called when exiting a test, it should be safe to just + // cancel all ongoing network requests and shut down the pool as soon as possible. + // When shut down we replace it with a new, now empty, pool that can be used by future + // tests + pool.shutdownNow(); + try { + pool.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new AssertionError("NetworkPoolExecutor was not shut down in time:\n" + Util.getStackTrace(e)); + } finally { + // Replace the executor, since the old one is now dead. + // The setup of this should mirror what is done in SyncManager. + networkPoolExecutorField.set(null, new ThreadPoolExecutor( + 10, 10, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(100))); + } + } catch (IllegalAccessException e) { + throw new AssertionError(Util.getStackTrace(e)); + } + } + } diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java index 31d73961f2..baef25a0f1 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java @@ -40,6 +40,7 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.TestHelper; +import io.realm.internal.ObjectServerFacade; import io.realm.internal.android.AndroidCapabilities; @@ -285,6 +286,7 @@ protected void after() { // Wait for all async tasks to have completed to ensure a successful deleteRealm call. // If it times out, it will throw. TestHelper.waitRealmThreadExecutorFinish(); + TestHelper.waitForNetworkThreadExecutorToFinish(); AndroidCapabilities.EMULATE_MAIN_THREAD = false; super.after(); From 823c60f95affeb9d54ca1b82c7cfa78c5a54d2df Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 3 Apr 2018 13:39:49 +0200 Subject: [PATCH 1216/2110] RealmObject.isValid now returns the correct value for the null argument. (#5871) --- CHANGELOG.md | 7 +++++++ .../src/androidTest/java/io/realm/RealmObjectTests.java | 6 ++++++ .../realm-library/src/main/java/io/realm/RealmObject.java | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a35b2a1c72..d0c483024a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.0.2 (YYYY-MM-DD) + +### Bug Fixes + +* `RealmObject.isValid()` not correctly returns `false` if `null` is provided as an argument (#5865). + + ## 5.0.1 (YYYY-MM-DD) ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 8d99101714..a6b3d4792c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -986,6 +986,12 @@ public void isValid_managedObject() { assertTrue(allTypes.isValid()); } + @Test + public void isValid_null() { + //noinspection ConstantConditions + assertFalse(RealmObject.isValid(null)); + } + // Stores and retrieves null values for nullable fields. @Test public void set_get_nullOnNullableFields() { diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 2b6162e6aa..de4a42443f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -151,7 +151,8 @@ public static boolean isValid(E object) { Row row = proxy.realmGet$proxyState().getRow$realm(); return row != null && row.isAttached(); } else { - return true; + //noinspection ConstantConditions + return object != null; } } From f04563a8486f87f38b36b0aa716408b9f54d7557 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 4 Apr 2018 09:12:12 +0200 Subject: [PATCH 1217/2110] Fix list.move for unmanaged lists. (#5872) --- CHANGELOG.md | 1 + .../java/io/realm/RealmListTests.java | 28 ++++++++++++------- .../src/main/java/io/realm/RealmList.java | 6 +--- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c483024a..107656beba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Bug Fixes +* `RealmList.move()` did not move items correctly for unmanaged lists (#5860). * `RealmObject.isValid()` not correctly returns `false` if `null` is provided as an argument (#5865). diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 6547300fed..3bcb03b5eb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -281,26 +281,29 @@ public void remove_unmanagedMode() { public void move_down() { Owner owner = realm.where(Owner.class).findFirst(); Dog dog1 = owner.getDogs().get(1); + Dog dog2 = owner.getDogs().get(0); realm.beginTransaction(); owner.getDogs().move(1, 0); realm.commitTransaction(); + assertEquals(TEST_SIZE, owner.getDogs().size()); assertEquals(0, owner.getDogs().indexOf(dog1)); + assertEquals(1, owner.getDogs().indexOf(dog2)); } // Tests move where oldPosition < newPosition. @Test public void move_up() { Owner owner = realm.where(Owner.class).findFirst(); - int oldIndex = TEST_SIZE / 2; - int newIndex = oldIndex + 1; - Dog dog = owner.getDogs().get(oldIndex); + Dog dog1 = owner.getDogs().get(0); + Dog dog2 = owner.getDogs().get(1); realm.beginTransaction(); - owner.getDogs().move(oldIndex, newIndex); // This doesn't do anything as oldIndex is now empty so the index's above gets shifted to the left. + owner.getDogs().move(0, 1); realm.commitTransaction(); assertEquals(TEST_SIZE, owner.getDogs().size()); - assertEquals(newIndex, owner.getDogs().indexOf(dog)); + assertEquals(1, owner.getDogs().indexOf(dog1)); + assertEquals(0, owner.getDogs().indexOf(dog2)); } // Tests move where oldPosition > newPosition. @@ -308,22 +311,27 @@ public void move_up() { public void move_downInUnmanagedMode() { RealmList dogs = createUnmanagedDogList(); Dog dog1 = dogs.get(1); + Dog dog2 = dogs.get(0); + dogs.move(1, 0); + assertEquals(TEST_SIZE, dogs.size()); assertEquals(0, dogs.indexOf(dog1)); + assertEquals(1, dogs.indexOf(dog2)); } // Tests move where oldPosition < newPosition. @Test public void move_upInUnmanagedMode() { RealmList dogs = createUnmanagedDogList(); - int oldIndex = TEST_SIZE / 2; - int newIndex = oldIndex + 1; - Dog dog = dogs.get(oldIndex); - dogs.move(oldIndex, newIndex); // This doesn't do anything as oldIndex is now empty so the index's above gets shifted to the left. + Dog dog1 = dogs.get(0); + Dog dog2 = dogs.get(1); + + dogs.move(0, 1); assertEquals(TEST_SIZE, dogs.size()); - assertEquals(oldIndex, dogs.indexOf(dog)); + assertEquals(1, dogs.indexOf(dog1)); + assertEquals(0, dogs.indexOf(dog2)); } /********************************************************* diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index af87170e3c..a1cb1dae20 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -267,11 +267,7 @@ public void move(int oldPos, int newPos) { throw new IndexOutOfBoundsException("Invalid index " + newPos + ", size is " + listSize); } E object = unmanagedList.remove(oldPos); - if (newPos > oldPos) { - unmanagedList.add(newPos - 1, object); - } else { - unmanagedList.add(newPos, object); - } + unmanagedList.add(newPos, object); } } From b7b33ceb3e9e6782accd7f608e8956ea646903a0 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 4 Apr 2018 11:55:26 +0100 Subject: [PATCH 1218/2110] Expose the per-user private role (#5858) * Expose the per-user private role --- CHANGELOG.md | 3 +- .../io/realm/ObjectLevelPermissionsTest.java | 30 +++++++++++++++++++ .../sync/permissions/PermissionUser.java | 12 ++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 107656beba..31f208cafe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ ### Enhancements -* [ObjectServer] `SyncConfiguration.automatic()` will make use of the host port to work out the default Realm URL. +* [ObjectServer] `SyncConfiguration.automatic()` will make use of the host port to work out the default Realm URL. +* [ObjectServer] A role is now automatically created for each user with that user as its only member. This simplifies the common use case of restricting access to specific objects to a single user. This role can be accessed at `PermissionUser.getRole()`. ### Bug Fixes diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java index 111733af79..045f290af4 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java @@ -32,6 +32,7 @@ import io.realm.sync.permissions.ClassPrivileges; import io.realm.sync.permissions.ObjectPrivileges; import io.realm.sync.permissions.Permission; +import io.realm.sync.permissions.PermissionUser; import io.realm.sync.permissions.RealmPermissions; import io.realm.sync.permissions.RealmPrivileges; import io.realm.sync.permissions.Role; @@ -39,6 +40,8 @@ import static io.realm.util.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -430,6 +433,33 @@ public void getClassPermissions_closedRealmThrows() { // } } + @Test + public void userPrivateRole() { + RealmResults permissionUsers = realm.where(PermissionUser.class).findAll(); + assertEquals(1, permissionUsers.size()); + + PermissionUser permissionUser = permissionUsers.get(0); + assertNotNull(permissionUser); + Role role = permissionUser.getPrivateRole(); + assertNotNull(role); + + assertEquals("__User:" + user.getIdentity(), role.getName()); + assertTrue(role.hasMember(user.getIdentity())); + } + + @Test + public void userPrivateRoleNotAvailableBeforeSyncClientCreated() { + realm.beginTransaction(); + PermissionUser permissionUser = realm.createObject(PermissionUser.class, "id123"); + realm.commitTransaction(); + + Role builtInRole = permissionUser.getPrivateRole(); + assertNull(builtInRole); + permissionUser = realm.where(PermissionUser.class).equalTo("id", "id123").findFirst(); + assertNull(permissionUser.getPrivateRole()); + assertTrue(permissionUser.getRoles().isEmpty()); + } + @Test public void getRoles() { RealmResults roles = realm.getRoles(); diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java index 7be7f168ba..1146f947a5 100644 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java @@ -39,6 +39,8 @@ public class PermissionUser extends RealmObject { @Required private String id; + private Role role; + @LinkingObjects("members") final RealmResults roles = null; @@ -72,4 +74,14 @@ public String getId() { public @Nullable RealmResults getRoles() { return roles; } + + /** + * The user's private role. This will be initialized to a role named for the user's + * identity that contains this user as its only member. + * + * @return User private {@link Role}. + */ + public Role getPrivateRole() { + return role; + } } From daebc8fb015688f5eb0089da76ee2e378646e018 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Sun, 8 Apr 2018 20:22:23 +0100 Subject: [PATCH 1219/2110] Nh/fixes local ref overflow (#5881) * Closing local JNI references to avoid the local table overflow * Fix changelog section & update internal section --- CHANGELOG.md | 17 ++++++++--------- dependencies.list | 6 +++--- .../cpp/io_realm_internal_OsRealmConfig.cpp | 18 ++++++++++++++---- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 107656beba..b9f2d6a08b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,20 @@ -## 5.0.2 (YYYY-MM-DD) - -### Bug Fixes - -* `RealmList.move()` did not move items correctly for unmanaged lists (#5860). -* `RealmObject.isValid()` not correctly returns `false` if `null` is provided as an argument (#5865). - - ## 5.0.1 (YYYY-MM-DD) ### Enhancements -* [ObjectServer] `SyncConfiguration.automatic()` will make use of the host port to work out the default Realm URL. +* `RealmList.move()` did not move items correctly for unmanaged lists (#5860). +* `RealmObject.isValid()` not correctly returns `false` if `null` is provided as an argument (#5865). +* Fixes an issue caused by JNI local table reference overflow (#5880). +* [ObjectServer] `SyncConfiguration.automatic()` will make use of the host port to work out the default Realm URL. ### Bug Fixes * `RealmQuery.findFirst()` and `RealmQuery.findFirstAsync()` not working correctly with sorting (#5714). +### Internal + +* Upgraded to Realm Sync 3.0.1 +* Upgraded to Realm Core 5.4.2 ## 5.0.0 (2018-03-15) diff --git a/dependencies.list b/dependencies.list index 6a3c2d6332..d48cd91181 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.0.0 -REALM_SYNC_SHA256=9141177ccc92d8f9282625dace61eee5c3d971d2daca7593266e175b610a24cf +REALM_SYNC_VERSION=3.0.1 +REALM_SYNC_SHA256=7764304d5dc7db7b4b9be9916f753c14c61c40e9f09fd1d92abeee3d8474405f # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=3.0.0 +REALM_OBJECT_SERVER_DE_VERSION=3.1.0 diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 928e244ca5..cdd27c396a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -281,8 +281,12 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe } JNIEnv* env = realm::jni_util::JniUtils::get_env(true); - env->CallStaticVoidMethod(sync_manager_class, java_error_callback_method, error_code, - to_jstring(env, error_message), to_jstring(env, session.get()->path())); + jstring jerror_message = to_jstring(env, error_message); + jstring jsession_path = to_jstring(env, session.get()->path()); + env->CallStaticVoidMethod(sync_manager_class, java_error_callback_method, error_code, jerror_message, + jsession_path); + env->DeleteLocalRef(jerror_message); + env->DeleteLocalRef(jsession_path); }; // path on disk of the Realm file. @@ -294,14 +298,18 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe JNIEnv* env = realm::jni_util::JniUtils::get_env(true); + jstring jpath = to_jstring(env, path.c_str()); + jstring jrefresh_token = to_jstring(env, session->user()->refresh_token().c_str()); jstring access_token_string = (jstring)env->CallStaticObjectMethod( - sync_manager_class, java_bind_session_method, to_jstring(env, path.c_str()), - to_jstring(env, session->user()->refresh_token().c_str())); + sync_manager_class, java_bind_session_method, jpath, jrefresh_token); if (access_token_string) { // reusing cached valid token JStringAccessor access_token(env, access_token_string); session->refresh_access_token(access_token, realm::util::Optional(syncConfig.realm_url())); + env->DeleteLocalRef(access_token_string); } + env->DeleteLocalRef(jpath); + env->DeleteLocalRef(jrefresh_token); }; // Get logged in user @@ -377,6 +385,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigS bool isValid = env->CallStaticBooleanMethod(sync_manager_class, java_ssl_verify_callback, jserver_address, jpem, depth) == JNI_TRUE; + env->DeleteLocalRef(jserver_address); + env->DeleteLocalRef(jpem); return isValid; }; config.sync_config->ssl_verify_callback = std::move(ssl_verify_callback); From 6c6b625db98964bf17899b885ee5e3c14af1eaa5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 9 Apr 2018 10:35:11 +0200 Subject: [PATCH 1220/2110] Fix AAR size not being reported correctly (#5875) --- Jenkinsfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a6eeb60267..8412e67f04 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -27,10 +27,10 @@ try { // A full build is done on `master`. // TODO Once Android emulators are available on all nodes, we can switch to x86 builds // on PR's for even more throughput. - def ABIs = "" + def abiFilter = "" def instrumentationTestTarget = "connectedAndroidTest" if (!['master', 'next-major'].contains(env.BRANCH_NAME)) { - ABIs = "armeabi-v7a" + abiFilter = "-PbuildTargetABIs=armeabi-v7a" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting } @@ -60,7 +60,7 @@ try { stage('JVM tests') { try { withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} -PbuildTargetABIs=${ABIs}" + sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter}" } } finally { storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' @@ -87,7 +87,7 @@ try { stage('Static code analysis') { try { - gradle('realm', 'findbugs pmd checkstyle -PbuildTargetABIs=${ABIs}') + gradle('realm', "findbugs pmd checkstyle ${abiFilter}") } finally { publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) @@ -122,7 +122,7 @@ try { stage('Collect metrics') { collectAarMetrics() } - } + } if (['master', 'next-major'].contains(env.BRANCH_NAME)) { stage('Publish to OJO') { From 83200815a6eee966f7256a5ad992d5eecf8e736f Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 9 Apr 2018 11:39:14 +0100 Subject: [PATCH 1221/2110] Permissions fixes (#5879) * Fix a bug where Permission noPrivileges & allPrivileges were returning opposite privileges, exposed Role.getMembers --- CHANGELOG.md | 9 ++++-- .../io/realm/ObjectLevelPermissionsTest.java | 23 +++++++++++++++ .../io/realm/sync/permissions/Permission.java | 28 +++++++++---------- .../java/io/realm/sync/permissions/Role.java | 9 ++++++ 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f2d6a08b..6fb7588b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,17 @@ ### Enhancements -* `RealmList.move()` did not move items correctly for unmanaged lists (#5860). -* `RealmObject.isValid()` not correctly returns `false` if `null` is provided as an argument (#5865). -* Fixes an issue caused by JNI local table reference overflow (#5880). * [ObjectServer] `SyncConfiguration.automatic()` will make use of the host port to work out the default Realm URL. +* [ObjectServer] A role is now automatically created for each user with that user as its only member. This simplifies the common use case of restricting access to specific objects to a single user. This role can be accessed at `PermissionUser.getRole()`. +* [ObjectServer] Expose `Role.getMembers()` to access the list of associated `UserPermission`. ### Bug Fixes +* `RealmList.move()` did not move items correctly for unmanaged lists (#5860). +* `RealmObject.isValid()` not correctly returns `false` if `null` is provided as an argument (#5865). * `RealmQuery.findFirst()` and `RealmQuery.findFirstAsync()` not working correctly with sorting (#5714). +* Permission `noPrivileges` and `allPrivileges` were returning opposite privileges. +* Fixes an issue caused by JNI local table reference overflow (#5880). ### Internal diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java index 111733af79..326fda95ec 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java @@ -477,6 +477,19 @@ public void getRoles_closedRealmThrows() { // } catch (IllegalStateException ignore) { // } } + @Test + public void noPrivileges() { + Role role = new Role("foo"); + Permission admin = new Permission.Builder(role).allPrivileges().build(); + assertFullAccess(admin); + } + + @Test + public void allPrivileges() { + Role role = new Role("foo"); + Permission nobody = new Permission.Builder(role).noPrivileges().build(); + assertNoAccess(nobody); + } private void assertFullAccess(RealmPrivileges privileges) { assertTrue(privileges.canRead()); @@ -510,6 +523,16 @@ private void assertFullAccess(Permission permission) { assertTrue(permission.canModifySchema()); } + private void assertNoAccess(Permission permission) { + assertFalse(permission.canCreate()); + assertFalse(permission.canRead()); + assertFalse(permission.canUpdate()); + assertFalse(permission.canDelete()); + assertFalse(permission.canQuery()); + assertFalse(permission.canSetPermissions()); + assertFalse(permission.canModifySchema()); + } + private void assertNoAccess(RealmPrivileges privileges) { assertFalse(privileges.canRead()); assertFalse(privileges.canUpdate()); diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java index d0838b4287..50f8ce2d87 100644 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java @@ -69,20 +69,6 @@ public Builder(Role role) { * Enables all privileges. */ public Builder allPrivileges() { - canRead = false; - canUpdate = false; - canDelete = false; - canSetPermissions = false; - canQuery = false; - canCreate = false; - canModifySchema = false; - return this; - } - - /** - * Disables all privileges. - */ - public Builder noPrivileges() { canRead = true; canUpdate = true; canDelete = true; @@ -93,6 +79,20 @@ public Builder noPrivileges() { return this; } + /** + * Disables all privileges. + */ + public Builder noPrivileges() { + canRead = false; + canUpdate = false; + canDelete = false; + canSetPermissions = false; + canQuery = false; + canCreate = false; + canModifySchema = false; + return this; + } + /** * Defines if this role can read from given resource or not. * diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java index 629ab67ff3..dc72ac906a 100644 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java @@ -110,4 +110,13 @@ public boolean removeMember(String userId) { public boolean hasMember(String userId) { return members.where().equalTo("id", userId).count() > 0; } + + /** + * Returns the list of {@link PermissionUser} within this role. + * + * @return list of members associated with this role. + */ + public RealmList getMembers() { + return members; + } } From a3cdf0dd32b9e0caf08296878ea833ede357523e Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 9 Apr 2018 17:35:46 +0100 Subject: [PATCH 1222/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fb7588b41..d86e153834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.0.1 (YYYY-MM-DD) +## 5.0.1 (2018-04-09) ### Enhancements From 580f4a1ac784307d56fd8b565bcfeb28e0cc2ff2 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 9 Apr 2018 17:35:50 +0100 Subject: [PATCH 1223/2110] Release v5.0.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 3fa3b389a5..32f3eaad0d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.0.1-SNAPSHOT \ No newline at end of file +5.0.1 \ No newline at end of file From dd2e255fee2a9cf81f8a79c4fd40dde7bc64a781 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 9 Apr 2018 17:35:51 +0100 Subject: [PATCH 1224/2110] Prepare next release v5.0.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 32f3eaad0d..c0c9edb016 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.0.1 \ No newline at end of file +5.0.2-SNAPSHOT \ No newline at end of file From f755bbcf7c89223b9c999c40789100d9af648e12 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 12 Apr 2018 16:56:29 +0200 Subject: [PATCH 1225/2110] Add script to help unrolling crash reports (#5895) --- tools/unroll_stacktrace.sh | 90 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tools/unroll_stacktrace.sh diff --git a/tools/unroll_stacktrace.sh b/tools/unroll_stacktrace.sh new file mode 100644 index 0000000000..f3f3dd8ec9 --- /dev/null +++ b/tools/unroll_stacktrace.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +# This script will attempt to manually unroll a stack trace using the unstripped jni libs that are available on S3. +# It will do so using ndk-stack, so read https://developer.android.com/ndk/guides/ndk-stack.html first +# +# The location of ndk-stack will be infered from the ndk.dir property in `/realm/local.properties`. +# +# Usage: > sh unroll_stacktrace.sh +# Example: > sh unroll_stacktrace.sh 5.0.0 armeabi-v7a ./dump.txt +# + +set -euo pipefail +IFS=$'\n\t' + +usage() { +cat < + - version: version number on Bintray + - abi: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips + - flavor: base, objectServer + - stacktrace: Path to file with dump + +Example: $0 base 5.0.0 armeabi-v7a ./dump.txt +EOF +} + +###################################### +# Input Validation +###################################### + +if [ "$#" -eq 0 ] || [ "$#" -lt 4 ] ; then + usage + exit 1 +fi + +HERE=$(pwd) +REALM_JAVA_TOOLS_DIR=$(dirname "$0") +FLAVOR="$1" +VERSION="$2" +ABI="$3" +STACKTRACE="$HERE/$4" +NDK_STACK="" +STRIPPED_LIBS_DIR="" + +find_ndkstack() { + PROPS_FILE="$REALM_JAVA_TOOLS_DIR/../realm/local.properties" + if [ ! -f "$PROPS_FILE" ]; then + echo "$PROPS_FILE not found! NDK location cannot be determined" + exit 1 + fi + NDK_STACK=$(grep "ndk.dir" "$PROPS_FILE" | cut -d = -f2)/ndk-stack +} + +download_and_unzip_stripped_libs() { + # Define location for unstripped libs. + # Use the standard REALM_CORE if defined, otherwise treat it as a temporary file. + CACHED_LIBS_DIR="$REALM_CORE_DOWNLOAD_DIR" + if [[ -z "${REALM_CORE_DOWNLOAD_DIR}" ]]; then + CACHED_LIBS_DIR="/tmp" + fi + + # Check if we already have the unstripped libs downloaded + STRIPPED_LIBS_FILE="$CACHED_LIBS_DIR/realm-java-jni-libs-unstripped-$VERSION.zip" + if [ ! -f "$STRIPPED_LIBS_FILE" ]; then + echo "$STRIPPED_LIBS_FILE not found! Downloading from S3" + STRIPPED_LIBS_DOWNLOAD_LOCATION="https://static.realm.io/downloads/java/realm-java-jni-libs-unstripped-$VERSION.zip" + curl -o "$STRIPPED_LIBS_FILE" "$STRIPPED_LIBS_DOWNLOAD_LOCATION" + fi + + # Exact files if needed + STRIPPED_LIBS_DIR="$CACHED_LIBS_DIR/realm-java-jni-libs-unstripped-$VERSION" + if [ ! -d "$STRIPPED_LIBS_DIR" ]; then + echo "Extracting archive file with unstripped libraries" + unzip "$STRIPPED_LIBS_FILE" -d "$STRIPPED_LIBS_DIR" + fi +} + +unroll_stacktrace() { + DIR="$STRIPPED_LIBS_DIR/$FLAVOR/$ABI" + if [ ! -d "$DIR" ]; then + echo "Directory containing .so file could not be found: ${DIR}" + exit 1 + fi + $NDK_STACK -sym "$DIR" -dump "$STACKTRACE" +} + +echo "Unrolling $STACKTRACE from Realm Java $VERSION ($FLAVOR) using ABI $ABI" +find_ndkstack +download_and_unzip_stripped_libs +unroll_stacktrace From ad88afe897a9fe6ca6f00fb04586e78f06b1b539 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 17 Apr 2018 11:43:56 +0200 Subject: [PATCH 1226/2110] Support empty input to in and alwaysTrue and alwaysFalse (#5898) * Support empty input to in and two new predicates: alwaysTrue and alwaysFalse * Fix spelling mistakes in docs * Kotlin docs should match java docs --- CHANGELOG.md | 8 + .../io/realm/kotlin/RealmQueryExtensions.kt | 43 ++-- .../java/io/realm/RealmQueryTests.java | 168 +++++++------- .../main/cpp/io_realm_internal_TableQuery.cpp | 21 ++ .../src/main/java/io/realm/RealmQuery.java | 219 ++++++++++-------- .../java/io/realm/internal/TableQuery.java | 12 + 6 files changed, 269 insertions(+), 202 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d86e153834..fa6aa8705c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 5.1.0 (YYYY-MM-DD) + +### Enhancements + +* `RealmQuery.in()` now support `null` which will always return no matches (#4011). +* Added support for `RealmQuery.alwaysTrue()` and `RealmQuery.alwaysFalse()`. + + ## 5.0.1 (2018-04-09) ### Enhancements diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt index 6b7f3c73a1..c20990ad15 100644 --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmQueryExtensions.kt @@ -25,7 +25,8 @@ import java.util.* * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If `null` or the empty array is provided the query will never + * match any results. * @param casing how casing is handled. [Case.INSENSITIVE] works only for the Latin-1 characters. * @return the query object. * @throws java.lang.IllegalArgumentException if the field isn't a String field or `values` is `null` or @@ -42,9 +43,10 @@ fun RealmQuery.oneOf(propertyName: String, * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If `null` or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Byte field or `values` is `null` or + * @throws java.lang.IllegalArgumentException if the field isn't a Byte field. * empty. */ fun RealmQuery.oneOf(propertyName: String, @@ -56,9 +58,10 @@ fun RealmQuery.oneOf(propertyName: String, * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If `null` or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Short field or `values` is `null` or + * @throws java.lang.IllegalArgumentException if the field isn't a Short field. * empty. */ fun RealmQuery.oneOf(propertyName: String, @@ -70,9 +73,10 @@ fun RealmQuery.oneOf(propertyName: String, * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If `null` or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Integer field or `values` is `null` + * @throws java.lang.IllegalArgumentException if the field isn't a Integer field. * or empty. */ fun RealmQuery.oneOf(propertyName: String, @@ -84,9 +88,10 @@ fun RealmQuery.oneOf(propertyName: String, * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If `null` or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Long field or `values` is `null` or + * @throws java.lang.IllegalArgumentException if the field isn't a Long field. * empty. */ fun RealmQuery.oneOf(propertyName: String, @@ -98,9 +103,10 @@ fun RealmQuery.oneOf(propertyName: String, * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If `null` or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Double field or `values` is `null` or + * @throws java.lang.IllegalArgumentException if the field isn't a Double field. * empty. */ fun RealmQuery.oneOf(propertyName: String, @@ -113,9 +119,10 @@ fun RealmQuery.oneOf(propertyName: String, * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If `null` or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Float field or `values` is `null` or + * @throws java.lang.IllegalArgumentException if the field isn't a Float field. * empty. */ fun RealmQuery.oneOf(propertyName: String, @@ -128,9 +135,10 @@ fun RealmQuery.oneOf(propertyName: String, * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If `null` or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Boolean field or `values` is `null` + * @throws java.lang.IllegalArgumentException if the field isn't a Boolean field. * or empty. */ fun RealmQuery.oneOf(propertyName: String, @@ -142,9 +150,10 @@ fun RealmQuery.oneOf(propertyName: String, * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If `null` or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Date field or `values` is `null` or + * @throws java.lang.IllegalArgumentException if the field isn't a Date field. * empty. */ fun RealmQuery.oneOf(propertyName: String, diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index cfd7dd48f6..180c38011c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -700,16 +700,6 @@ public void equalTo_nonLatinCharacters() { private void doTestForInString(String targetField) { populateNoPrimaryKeyNullTypesRows(); - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (String[]) null).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new String[]{}).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new String[]{"test data 14"}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new String[]{"test data 14", "test data 118", "test data 31", "test data 199"}).findAll(); @@ -720,20 +710,16 @@ private void doTestForInString(String targetField) { assertEquals(196, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new String[]{"TEST data 14", "test data 118", "test data 31", "test DATA 199"}, Case.INSENSITIVE).findAll(); assertEquals(196, resultList.size()); + + // Empty input always produces zero results + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (String[]) null).findAll(); + assertTrue(resultList.isEmpty()); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new String[]{}).findAll(); + assertTrue(resultList.isEmpty()); } private void doTestForInBoolean(String targetField, int expected1, int expected2, int expected3, int expected4) { populateNoPrimaryKeyNullTypesRows(); - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Boolean[]) null).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Boolean[]{}).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Boolean[]{false}).findAll(); assertEquals(expected1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Boolean[]{true}).findAll(); @@ -742,20 +728,16 @@ private void doTestForInBoolean(String targetField, int expected1, int expected2 assertEquals(expected3, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Boolean[]{true, false}).findAll(); assertEquals(expected4, resultList.size()); + + // Empty input always produces zero results + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Boolean[]) null).findAll(); + assertTrue(resultList.isEmpty()); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Boolean[]{}).findAll(); + assertTrue(resultList.isEmpty()); } private void doTestForInDate(String targetField) { populateNoPrimaryKeyNullTypesRows(); - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Date[]) null).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Date[]{}).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Date[]{new Date(DECADE_MILLIS * -80)}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Date[]{new Date(0)}).findAll(); @@ -764,20 +746,16 @@ private void doTestForInDate(String targetField) { assertEquals(2, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Date[]{new Date(DECADE_MILLIS * -80), new Date(0)}).findAll(); assertEquals(198, resultList.size()); + + // Empty input always produces zero results + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Date[]) null).findAll(); + assertTrue(resultList.isEmpty()); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Date[]{}).findAll(); + assertTrue(resultList.isEmpty()); } private void doTestForInDouble(String targetField) { populateNoPrimaryKeyNullTypesRows(); - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Double[]) null).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{}).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{Math.PI + 1}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{Math.PI + 2}).findAll(); @@ -786,20 +764,16 @@ private void doTestForInDouble(String targetField) { assertEquals(2, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Double[]{Math.PI + 1, Math.PI + 2}).findAll(); assertEquals(198, resultList.size()); + + // Empty input always produces zero results + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Double[]) null).findAll(); + assertTrue(resultList.isEmpty()); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[]{}).findAll(); + assertTrue(resultList.isEmpty()); } private void doTestForInFloat(String targetField) { populateNoPrimaryKeyNullTypesRows(); - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Float[]) null).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{}).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{1.2345f + 1}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{1.2345f + 2}).findAll(); @@ -808,20 +782,16 @@ private void doTestForInFloat(String targetField) { assertEquals(2, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Float[]{1.2345f + 1, 1.2345f + 2}).findAll(); assertEquals(198, resultList.size()); + + // Empty input always produces zero results + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Float[]) null).findAll(); + assertTrue(resultList.isEmpty()); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{}).findAll(); + assertTrue(resultList.isEmpty()); } private void doTestForInByte(String targetField) { populateNoPrimaryKeyNullTypesRows(); - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Byte[]) null).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Byte[]{}).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Byte[]{11}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Byte[]{13}).findAll(); @@ -830,20 +800,16 @@ private void doTestForInByte(String targetField) { assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Byte[]{11, 13, 16, 98}).findAll(); assertEquals(196, resultList.size()); + + // Empty input always produces zero results + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Byte[]) null).findAll(); + assertTrue(resultList.isEmpty()); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Byte[]{}).findAll(); + assertTrue(resultList.isEmpty()); } private void doTestForInShort(String targetField) { populateNoPrimaryKeyNullTypesRows(); - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Short[]) null).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Short[]{}).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Short[]{11}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Short[]{4}).findAll(); @@ -852,20 +818,16 @@ private void doTestForInShort(String targetField) { assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Short[]{2, 4, 5, 8}).findAll(); assertEquals(196, resultList.size()); + + // Empty input always produces zero results + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Float[]) null).findAll(); + assertTrue(resultList.isEmpty()); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[]{}).findAll(); + assertTrue(resultList.isEmpty()); } private void doTestForInInteger(String targetField) { populateNoPrimaryKeyNullTypesRows(); - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Integer[]) null).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Integer[]{}).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Integer[]{11}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Integer[]{1}).findAll(); @@ -874,20 +836,16 @@ private void doTestForInInteger(String targetField) { assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Integer[]{1, 2, 4, 5}).findAll(); assertEquals(196, resultList.size()); + + // Empty input always produces zero results + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Integer[]) null).findAll(); + assertTrue(resultList.isEmpty()); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Integer[]{}).findAll(); + assertTrue(resultList.isEmpty()); } private void doTestForInLong(String targetField) { populateNoPrimaryKeyNullTypesRows(); - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Long[]) null).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - try { - realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Long[]{}).findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Long[]{11l}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Long[]{13l}).findAll(); @@ -896,6 +854,12 @@ private void doTestForInLong(String targetField) { assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Long[]{13l, 14l, 16l, 98l}).findAll(); assertEquals(196, resultList.size()); + + // Empty input always produces zero results + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Long[]) null).findAll(); + assertTrue(resultList.isEmpty()); + resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Long[]{}).findAll(); + assertTrue(resultList.isEmpty()); } @Test @@ -3487,4 +3451,28 @@ public void beginGroup_missingEndGroup() { public void endGroup_missingBeginGroup() { realm.where(AllTypes.class).endGroup().findAll(); } + + @Test + public void alwaysTrue() { + populateTestRealm(); + assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).alwaysTrue().findAll().size()); + } + + @Test + public void alwaysTrue_inverted() { + populateTestRealm(); + assertEquals(0, realm.where(AllTypes.class).not().alwaysTrue().findAll().size()); + } + + @Test + public void alwaysFalse() { + populateTestRealm(); + assertEquals(0, realm.where(AllTypes.class).alwaysFalse().findAll().size()); + } + + @Test + public void alwaysFalse_inverted() { + populateTestRealm(); + assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).not().alwaysFalse().findAll().size()); + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index d8706d945c..03e09dea91 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -1796,6 +1796,27 @@ Java_io_realm_internal_TableQuery_nativeIsNotEmpty(JNIEnv *env, jobject, jlong n CATCH_STD() } +JNIEXPORT void JNICALL +Java_io_realm_internal_TableQuery_nativeAlwaysFalse(JNIEnv *env, jobject, jlong nativeQueryPtr) { + TR_ENTER_PTR(nativeQueryPtr); + try { + Query* query = reinterpret_cast(nativeQueryPtr); + query->and_query(std::unique_ptr(new FalseExpression)); + } + CATCH_STD() + +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_TableQuery_nativeAlwaysTrue(JNIEnv *env, jobject, jlong nativeQueryPtr) { + TR_ENTER_PTR(nativeQueryPtr); + try { + Query* query = reinterpret_cast(nativeQueryPtr); + query->and_query(std::unique_ptr(new TrueExpression)); + } + CATCH_STD() +} + static void finalize_table_query(jlong ptr) { TR_ENTER_PTR(ptr) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 7fec99ead8..67ae7832ff 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -25,7 +25,6 @@ import io.realm.annotations.Beta; import io.realm.annotations.Required; -import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsList; import io.realm.internal.OsResults; import io.realm.internal.PendingRow; @@ -522,12 +521,12 @@ private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a String field or {@code values} is {@code null} or - * empty. + * @throws java.lang.IllegalArgumentException if the field isn't a String field. */ - public RealmQuery in(String fieldName, String[] values) { + public RealmQuery in(String fieldName, @Nullable String[] values) { return in(fieldName, values, Case.SENSITIVE); } @@ -535,18 +534,18 @@ public RealmQuery in(String fieldName, String[] values) { * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @param casing how casing is handled. {@link Case#INSENSITIVE} works only for the Latin-1 characters. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a String field or {@code values} is {@code null} or - * empty. + * @throws java.lang.IllegalArgumentException if the field isn't a String field. */ - public RealmQuery in(String fieldName, String[] values, Case casing) { + public RealmQuery in(String fieldName, @Nullable String[] values, Case casing) { realm.checkIfValid(); - //noinspection ConstantConditions if (values == null || values.length == 0) { - throw new IllegalArgumentException(EMPTY_VALUES); + alwaysFalse(); + return this; } beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0], casing); for (int i = 1; i < values.length; i++) { @@ -559,184 +558,196 @@ public RealmQuery in(String fieldName, String[] values, Case casing) { * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Byte field or {@code values} is {@code null} or - * empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Byte field. */ - public RealmQuery in(String fieldName, Byte[] values) { + public RealmQuery in(String fieldName, @Nullable Byte[] values) { realm.checkIfValid(); - //noinspection ConstantConditions if (values == null || values.length == 0) { - throw new IllegalArgumentException(EMPTY_VALUES); - } - beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); - for (int i = 1; i < values.length; i++) { - orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + alwaysFalse(); + return this; + } else { + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); + for (int i = 1; i < values.length; i++) { + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + } + return endGroupWithoutThreadValidation(); } - return endGroupWithoutThreadValidation(); } /** * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Short field or {@code values} is {@code null} or - * empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Short field. */ - public RealmQuery in(String fieldName, Short[] values) { + public RealmQuery in(String fieldName, @Nullable Short[] values) { realm.checkIfValid(); - //noinspection ConstantConditions if (values == null || values.length == 0) { - throw new IllegalArgumentException(EMPTY_VALUES); - } - beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); - for (int i = 1; i < values.length; i++) { - orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + alwaysFalse(); + return this; + } else { + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); + for (int i = 1; i < values.length; i++) { + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + } + return endGroupWithoutThreadValidation(); } - return endGroupWithoutThreadValidation(); } /** * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Integer field or {@code values} is {@code null} - * or empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Integer field. */ - public RealmQuery in(String fieldName, Integer[] values) { + public RealmQuery in(String fieldName, @Nullable Integer[] values) { realm.checkIfValid(); - //noinspection ConstantConditions if (values == null || values.length == 0) { - throw new IllegalArgumentException(EMPTY_VALUES); - } - beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); - for (int i = 1; i < values.length; i++) { - orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + alwaysFalse(); + return this; + } else { + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); + for (int i = 1; i < values.length; i++) { + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + } + return endGroupWithoutThreadValidation(); } - return endGroupWithoutThreadValidation(); } /** * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Long field or {@code values} is {@code null} or + * @throws java.lang.IllegalArgumentException if the field isn't a Long field. * empty. */ - public RealmQuery in(String fieldName, Long[] values) { + public RealmQuery in(String fieldName, @Nullable Long[] values) { realm.checkIfValid(); - //noinspection ConstantConditions if (values == null || values.length == 0) { - throw new IllegalArgumentException(EMPTY_VALUES); - } - beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); - for (int i = 1; i < values.length; i++) { - orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + alwaysFalse(); + return this; + } else { + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); + for (int i = 1; i < values.length; i++) { + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + } + return endGroupWithoutThreadValidation(); } - return endGroupWithoutThreadValidation(); } /** * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Double field or {@code values} is {@code null} or + * @throws java.lang.IllegalArgumentException if the field isn't a Double field. * empty. */ - public RealmQuery in(String fieldName, Double[] values) { + public RealmQuery in(String fieldName, @Nullable Double[] values) { realm.checkIfValid(); - //noinspection ConstantConditions if (values == null || values.length == 0) { - throw new IllegalArgumentException(EMPTY_VALUES); - } - beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); - for (int i = 1; i < values.length; i++) { - orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + alwaysFalse(); + return this; + } else { + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); + for (int i = 1; i < values.length; i++) { + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + } + return endGroupWithoutThreadValidation(); } - return endGroupWithoutThreadValidation(); } /** * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Float field or {@code values} is {@code null} or - * empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Float field. */ - public RealmQuery in(String fieldName, Float[] values) { + public RealmQuery in(String fieldName, @Nullable Float[] values) { realm.checkIfValid(); - //noinspection ConstantConditions if (values == null || values.length == 0) { - throw new IllegalArgumentException(EMPTY_VALUES); - } - beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); - for (int i = 1; i < values.length; i++) { - orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + alwaysFalse(); + return this; + } else { + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); + for (int i = 1; i < values.length; i++) { + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + } + return endGroupWithoutThreadValidation(); } - return endGroupWithoutThreadValidation(); } /** * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Boolean field or {@code values} is {@code null} + * @throws java.lang.IllegalArgumentException if the field isn't a Boolean. * or empty. */ - public RealmQuery in(String fieldName, Boolean[] values) { + public RealmQuery in(String fieldName, @Nullable Boolean[] values) { realm.checkIfValid(); //noinspection ConstantConditions if (values == null || values.length == 0) { - throw new IllegalArgumentException(EMPTY_VALUES); - } - beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); - for (int i = 1; i < values.length; i++) { - orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + alwaysFalse(); + return this; + } else { + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); + for (int i = 1; i < values.length; i++) { + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + } + return endGroupWithoutThreadValidation(); } - return endGroupWithoutThreadValidation(); } /** * In comparison. This allows you to test if objects match any value in an array of values. * * @param fieldName the field to compare. - * @param values array of values to compare with and it cannot be null or empty. + * @param values array of values to compare with. If {@code null} or the empty array is provided the query will never + * match any results. * @return the query object. - * @throws java.lang.IllegalArgumentException if the field isn't a Date field or {@code values} is {@code null} or - * empty. + * @throws java.lang.IllegalArgumentException if the field isn't a Date field. */ - public RealmQuery in(String fieldName, Date[] values) { + public RealmQuery in(String fieldName, @Nullable Date[] values) { realm.checkIfValid(); - //noinspection ConstantConditions if (values == null || values.length == 0) { - throw new IllegalArgumentException(EMPTY_VALUES); - } - beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); - for (int i = 1; i < values.length; i++) { - orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + alwaysFalse(); + return this; + } else { + beginGroupWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[0]); + for (int i = 1; i < values.length; i++) { + orWithoutThreadValidation().equalToWithoutThreadValidation(fieldName, values[i]); + } + return endGroupWithoutThreadValidation(); } - return endGroupWithoutThreadValidation(); } /** @@ -1925,6 +1936,24 @@ public RealmQuery distinct(String firstFieldName, String... remainingFieldNam return this; } + /** + * This predicate will always match. + */ + public RealmQuery alwaysTrue() { + realm.checkIfValid(); + query.alwaysTrue(); + return this; + } + + /** + * This predicate will never match, resulting in the query always returning 0 results. + */ + public RealmQuery alwaysFalse() { + realm.checkIfValid(); + query.alwaysFalse(); + return this; + } + private boolean isDynamicQuery() { return className != null; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 9de705eae5..1f9ba3d387 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -629,6 +629,14 @@ private void throwImmutable() { throw new IllegalStateException("Mutable method call during read transaction."); } + public void alwaysTrue() { + nativeAlwaysTrue(nativePtr); + } + + public void alwaysFalse() { + nativeAlwaysFalse(nativePtr); + } + private native String nativeValidateQuery(long nativeQueryPtr); private native void nativeGroup(long nativeQueryPtr); @@ -717,6 +725,10 @@ private void throwImmutable() { private native void nativeIsNotEmpty(long nativePtr, long[] columnIndices, long[] tablePtrs); + private native void nativeAlwaysTrue(long nativeQueryPtr); + + private native void nativeAlwaysFalse(long nativeQueryPtr); + private native long nativeFind(long nativeQueryPtr, long fromTableRow); private native long nativeFindAll(long nativeQueryPtr, long start, long end, long limit); From 816239686fa101b3d582b7f3bb390692dcd4eb51 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 25 Apr 2018 09:56:11 +0200 Subject: [PATCH 1227/2110] Add support for resetting password and email confirmation. (#5907) --- CHANGELOG.md | 7 + .../objectServer/java/io/realm/SyncUser.java | 297 +++++++++++++++++- .../network/AuthenticationServer.java | 20 ++ .../network/OkHttpAuthenticationServer.java | 51 +++ .../network/UpdateAccountRequest.java | 80 +++++ .../network/UpdateAccountResponse.java | 60 ++++ .../java/io/realm/objectserver/AuthTests.java | 118 ++++++- .../realm/objectserver/utils/UserFactory.java | 2 +- 8 files changed, 619 insertions(+), 16 deletions(-) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountRequest.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountResponse.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fa6aa8705c..29ac7f1027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ### Enhancements +* [ObjectServer] Added support for `SyncUser.requestPasswordReset()`, `SyncUser.completePasswordReset()` + and their async variants. This makes it possible to reset the password for users created using + `Credentials.usernamePassword()` where they used their email as username (#5821). +* [ObjectServer] Added support for `SyncUser.requestEmailConfirmation()`, `SyncUser.confirmEmail()` + and their async variants. This makes it possible to ask users to confirm their email. This is only + supported for users created using `Credentials.usernamePassword()` who have used an email as their + username (#5821). * `RealmQuery.in()` now support `null` which will always return no matches (#4011). * Added support for `RealmQuery.alwaysTrue()` and `RealmQuery.alwaysFalse()`. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index c7f98d65b9..08f553c743 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -44,6 +44,8 @@ import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.LogoutResponse; import io.realm.internal.network.LookupUserIdResponse; +import io.realm.internal.network.UpdateAccountRequest; +import io.realm.internal.network.UpdateAccountResponse; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; @@ -135,16 +137,7 @@ public static SyncUser fromJson(String user) { * @throws IllegalArgumentException if the URL is malformed. */ public static SyncUser logIn(final SyncCredentials credentials, final String authenticationUrl) throws ObjectServerError { - URL authUrl; - try { - authUrl = new URL(authenticationUrl); - // If no path segment is provided append `/auth` which is the standard location. - if (authUrl.getPath().equals("")) { - authUrl = new URL(authUrl.toString() + "/auth"); - } - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e); - } + URL authUrl = getUrl(authenticationUrl); ObjectServerError error; try { @@ -177,6 +170,27 @@ public static SyncUser logIn(final SyncCredentials credentials, final String aut throw error; } + /** + * Converts the input URL to a Realm Authentication URL + * + * @param authenticationUrl user provided url string. + * + * @return normalized authentication url. + * @throws IllegalArgumentException if something was wrong with the URL. + */ + private static URL getUrl(String authenticationUrl) { + try { + URL authUrl = new URL(authenticationUrl); + // If no path segment is provided append `/auth` which is the standard location. + if (authUrl.getPath().equals("")) { + authUrl = new URL(authUrl.toString() + "/auth"); + } + return authUrl; + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e); + } + } + /** * Logs in the user to the Realm Object Server. A logged in user is required to be able to create a * {@link SyncConfiguration}. @@ -392,6 +406,261 @@ public SyncUser run() { }.start(); } + + /** + * Request a password reset email to be sent to a user's email. + * This will not fail, even if the email doesn't belong to a Realm Object Server user. + *

            + * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} + * provider, and passed a valid email address as a username. + * + * @param email email that corresponds to the user's username. + * @param authenticationUrl the url used to authenticate the user. + * @throws IllegalStateException if this method is called on the UI thread. + * @throws IllegalArgumentException if no email or authenticationUrl was provided. + * @throws ObjectServerError if an error happened on the server. + */ + public static void requestPasswordReset(String email, String authenticationUrl) throws ObjectServerError { + if (Util.isEmptyString(email)) { + throw new IllegalArgumentException("Not-null 'email' required."); + } + URL authUrl = getUrl(authenticationUrl); + AuthenticationServer authServer = SyncManager.getAuthServer(); + UpdateAccountResponse response = authServer.requestPasswordReset(email, authUrl); + if (!response.isValid()) { + throw response.getError(); + } + } + + /** + * Request a password reset email to be sent to a user's email. + * This will not fail, even if the email doesn't belong to a Realm Object Server user. + *

            + * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} + * provider, and passed a valid email address as a username. + * + * @param email email that corresponds to the user's username. + * @param authenticationUrl the url used to authenticate the user. + * @param callback callback when the request has completed or failed. The callback will always happen on the same thread + * as this method is called on. + * @return representation of the async task that can be used to cancel it if needed. + * @throws IllegalStateException if this method is called on a non-looper thread. + * @throws IllegalArgumentException if no email or authenticationUrl was provided. + */ + public static RealmAsyncTask requestPasswordResetAsync(final String email, final String authenticationUrl, final Callback callback) { + checkLooperThread("Asynchronous requesting a password reset is only possible from looper threads."); + //noinspection ConstantConditions + if (callback == null) { + throw new IllegalArgumentException("Non-null 'callback' required."); + } + + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() { + requestPasswordReset(email, authenticationUrl); + return null; + } + }.start(); + } + + /** + * Complete the password reset flow by using the reset token sent to the user's email as a one-time authorization + * token to change the password. + *

            + * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} + * provider, and passed a valid email address as a username. + *

            + * By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where + * they can enter their new password. If you wish to provide a native UX, you may wish to modify the password + * authentication provider to use a custom URL with deep linking, so you can open the app, extract the token, and + * navigate to a view that allows to change the password within the app. + * + * @param resetToken the token that was sent to the user's email address. + * @param newPassword the user's new password. + * @param authenticationUrl the url used to authenticate the user. + * @throws IllegalStateException if this method is called on the UI thread. + * @throws IllegalArgumentException if no {@code token} or {@code newPassword} was provided. + * @throws ObjectServerError if an error happened on the server. + */ + public static void completePasswordReset(String resetToken, String newPassword, String authenticationUrl) { + if (Util.isEmptyString(resetToken)) { + throw new IllegalArgumentException("Not-null 'token' required."); + } + if (Util.isEmptyString(newPassword)) { + throw new IllegalArgumentException("Not-null 'newPassword' required."); + } + URL authUrl = getUrl(authenticationUrl); + AuthenticationServer authServer = SyncManager.getAuthServer(); + UpdateAccountResponse response = authServer.completePasswordReset(resetToken, newPassword, authUrl); + if (!response.isValid()) { + throw response.getError(); + } + } + + /** + * Complete the password reset flow by using the reset token sent to the user's email as a one-time authorization + * token to change the password. + *

            + * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} + * provider, and passed a valid email address as a username. + *

            + * By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where + * they can enter their new password. If you wish to provide a native UX, you may wish to modify the password + * authentication provider to use a custom URL with deep linking, so you can open the app, extract the token, and + * navigate to a view that allows to change the password within the app. + * + * @param resetToken the token that was sent to the user's email address. + * @param newPassword the user's new password. + * @param authenticationUrl the url used to authenticate the user. + * @param callback callback when the server has accepted the new password or failed. The callback will always happen on the same thread + * as this method is called on. + * @return representation of the async task that can be used to cancel it if needed. + * @throws IllegalStateException if this method is called on a non-looper thread. + * @throws IllegalArgumentException if no {@code token} or {@code newPassword} was provided. + */ + public static RealmAsyncTask completePasswordResetAsync(final String resetToken, + final String newPassword, + final String authenticationUrl, + final Callback callback) throws ObjectServerError { + checkLooperThread("Asynchronously completing a password reset is only possible from looper threads."); + //noinspection ConstantConditions + if (callback == null) { + throw new IllegalArgumentException("Non-null 'callback' required."); + } + + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() { + completePasswordReset(resetToken, newPassword, authenticationUrl); + return null; + } + }.start(); + } + + /** + * Request an email confirmation email to be sent to a user's email. + * This will not fail, even if the email doesn't belong to a Realm Object Server user. + *

            + * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} + * provider, and passed a valid email address as a username. + * + * @param email the email that corresponds to the user's username. + * @param authenticationUrl the url used to authenticate the user. + * @throws IllegalStateException if this method is called on the UI thread. + * @throws IllegalArgumentException if no {@code email} was provided. + * @throws ObjectServerError if an error happened on the server. + */ + public static void requestEmailConfirmation(String email, String authenticationUrl) throws ObjectServerError { + if (Util.isEmptyString(email)) { + throw new IllegalArgumentException("Not-null 'email' required."); + } + URL authUrl = getUrl(authenticationUrl); + AuthenticationServer authServer = SyncManager.getAuthServer(); + UpdateAccountResponse response = authServer.requestEmailConfirmation(email, authUrl); + if (!response.isValid()) { + throw response.getError(); + } + } + + /** + * Request an email confirmation email to be sent to a user's email. + * This will not fail, even if the email doesn't belong to a Realm Object Server user. + *

            + * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} + * provider, and passed a valid email address as a username. + * + * @param email the email that corresponds to the user's username. + * @param authenticationUrl the url used to authenticate the user. + * @param callback callback when the request has completed or failed. The callback will always happen on the same thread + * as this method is called on. + * @return representation of the async task that can be used to cancel it if needed. + * @throws IllegalStateException if this method is called on a non-looper thread. + * @throws IllegalArgumentException if no {@code email} was provided. + */ + public static RealmAsyncTask requestEmailConfirmationAsync(final String email, final String authenticationUrl, final Callback callback) { + checkLooperThread("Asynchronously requesting an email confirmation is only possible from looper threads."); + //noinspection ConstantConditions + if (callback == null) { + throw new IllegalArgumentException("Non-null 'callback' required."); + } + + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() { + requestEmailConfirmation(email, authenticationUrl); + return null; + } + }.start(); + } + + /** + * Complete the email confirmation flow by using the confirmation token sent to the user's email as a one-time + * authorization token to confirm their email. + *

            + * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} + * provider, and passed a valid email address as a username. + *

            + * By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where + * they can enter their new password. If you wish to provide a native UX, you may wish to modify the password + * authentication provider to use a custom URL with deep linking, so you can open the app, extract the token, + * and navigate to a view that allows to confirm the email within the app. + * + * @param confirmationToken the token that was sent to the user's email address. + * @param authenticationUrl the url used to authenticate the user. + * @throws IllegalStateException if this method is called on the UI thread. + * @throws IllegalArgumentException if no {@code confirmationToken} was provided. + * @throws ObjectServerError if an error happened on the server. + */ + public static void confirmEmail(String confirmationToken, String authenticationUrl) throws ObjectServerError { + if (Util.isEmptyString(confirmationToken)) { + throw new IllegalArgumentException("Not-null 'confirmationToken' required."); + } + URL authUrl = getUrl(authenticationUrl); + AuthenticationServer authServer = SyncManager.getAuthServer(); + UpdateAccountResponse response = authServer.confirmEmail(confirmationToken, authUrl); + if (!response.isValid()) { + throw response.getError(); + } + } + + /** + * Complete the email confirmation flow by using the confirmation token sent to the user's email as a one-time + * authorization token to confirm their email. This functionalit + *

            + * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} + * provider, and passed a valid email address as a username. + *

            + * By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where + * they can enter their new password. If you wish to provide a native UX, you may wish to modify the password + * authentication provider to use a custom URL with deep linking, so you can open the app, extract the token, + * and navigate to a view that allows to confirm the email within the app. + * + * @param confirmationToken the token that was sent to the user's email address. + * @param authenticationUrl the url used to authenticate the user. + * @param callback callback when the server has confirmed the email or failed. The callback will always happen on the same thread + * as this method is called on. + * @return representation of the async task that can be used to cancel it if needed. + * @throws IllegalStateException if this method is called on a non-looper thread. + * @throws IllegalArgumentException if no {@code confirmationToken} was provided. + */ + public static RealmAsyncTask confirmEmailAsync(final String confirmationToken, + final String authenticationUrl, + final Callback callback) { + checkLooperThread("Asynchronously confirming an email is only possible from looper threads."); + //noinspection ConstantConditions + if (callback == null) { + throw new IllegalArgumentException("Non-null 'callback' required."); + } + + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() { + confirmEmail(confirmationToken, authenticationUrl); + return null; + } + }.start(); + } + /** * Given a Realm Object Server authentication provider and a provider identifier for a user (for example, a username), look up and return user information for that user. * @@ -401,7 +670,9 @@ public SyncUser run() { * * @return {@code SyncUser} associated with the given identity provider and providerId, or {@code null} in case * of an {@code invalid} provider or {@code providerId}. - * @throws ObjectServerError in case of an error. + * @throws IllegalStateException if this method is called on the UI thread. + * @throws IllegalArgumentException if no {@code providerUserIdentity} or {@code provider} string was provided. + * @throws ObjectServerError if an error happened on the server. */ public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final String provider) throws ObjectServerError { if (Util.isEmptyString(providerUserIdentity)) { @@ -435,9 +706,7 @@ public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final * @param providerUserIdentity The username or identity of the user as issued by the authentication provider. * In most cases this is different from the Realm Object Server-issued identity. * @param provider The authentication provider {@link io.realm.SyncCredentials.IdentityProvider} that manages the user whose information is desired. - * - * @return {@code SyncUser} associated with the given identity provider and providerId, or {@code null} in case - * of an {@code invalid} provider or {@code providerId}. + * @return representation of the async task that can be used to cancel it if needed. * @param callback callback when the lookup has completed or failed. The callback will always happen on the same thread * as this method is called on. * @return representation of the async task that can be used to cancel it if needed. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java index 8906515e83..1e33ab0d0f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java @@ -73,4 +73,24 @@ public interface AuthenticationServer { * what is needed will depend on what type of {@link SyncCredentials} was used. */ LookupUserIdResponse retrieveUser(Token adminToken, String provider, String providerId, URL authenticationUrl); + + /** + * Request a password reset for the user identified by the provided email. + */ + UpdateAccountResponse requestPasswordReset(String email, URL authenticationUrl); + + /** + * Complete a password reset by sending the one-time token and the new password. + */ + UpdateAccountResponse completePasswordReset(String token, String newPassword, URL authenticationUrl); + + /** + * Request an email confirmation. + */ + UpdateAccountResponse requestEmailConfirmation(String email, URL authenticationUrl); + + /** + * Complete an email confirmation by sending the token contained in the email. + */ + UpdateAccountResponse confirmEmail(String confirmationToken, URL authenticationUrl); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 4783b3d42e..0b3fc3ab44 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -39,6 +39,7 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { private static final String ACTION_LOGOUT = "revoke"; // Auth end point for logging out users private static final String ACTION_CHANGE_PASSWORD = "password"; // Auth end point for changing passwords private static final String ACTION_LOOKUP_USER_ID = "users/:provider:/:providerId:"; // Auth end point for looking up user id + private static final String ACTION_UPDATE_ACCOUNT = "password/updateAccount"; // Password reset and email confirmation private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) @@ -125,6 +126,46 @@ public LookupUserIdResponse retrieveUser(Token adminToken, String provider, Stri } } + @Override + public UpdateAccountResponse requestPasswordReset(String email, URL authenticationUrl) { + try { + String requestBody = UpdateAccountRequest.requestPasswordReset(email).toJson(); + return updateAccount(buildActionUrl(authenticationUrl, ACTION_UPDATE_ACCOUNT), requestBody); + } catch (Exception e) { + return UpdateAccountResponse.from(e); + } + } + + @Override + public UpdateAccountResponse completePasswordReset(String token, String newPassword, URL authenticationUrl) { + try { + String requestBody = UpdateAccountRequest.completePasswordReset(token, newPassword).toJson(); + return updateAccount(buildActionUrl(authenticationUrl, ACTION_UPDATE_ACCOUNT), requestBody); + } catch (Exception e) { + return UpdateAccountResponse.from(e); + } + } + + @Override + public UpdateAccountResponse requestEmailConfirmation(String email, URL authenticationUrl) { + try { + String requestBody = UpdateAccountRequest.requestEmailConfirmation(email).toJson(); + return updateAccount(buildActionUrl(authenticationUrl, ACTION_UPDATE_ACCOUNT), requestBody); + } catch (Exception e) { + return UpdateAccountResponse.from(e); + } + } + + @Override + public UpdateAccountResponse confirmEmail(String confirmationToken, URL authenticationUrl) { + try { + String requestBody = UpdateAccountRequest.completeEmailConfirmation(confirmationToken).toJson(); + return updateAccount(buildActionUrl(authenticationUrl, ACTION_UPDATE_ACCOUNT), requestBody); + } catch (Exception e) { + return UpdateAccountResponse.from(e); + } + } + // Builds the URL for a specific auth endpoint private static URL buildActionUrl(URL authenticationUrl, String action) { final String baseUrlString = authenticationUrl.toExternalForm(); @@ -176,6 +217,16 @@ private LookupUserIdResponse lookupUserId(URL lookupUserIdUrl, String authToken) return LookupUserIdResponse.from(response); } + private UpdateAccountResponse updateAccount(URL updateAccountUrl, String requestBody) throws Exception { + RealmLog.debug("Network request (updateAccount): " + updateAccountUrl); + Request request = newAuthRequest(updateAccountUrl) + .post(RequestBody.create(JSON, requestBody)) + .build(); + Call call = client.newCall(request); + Response response = call.execute(); + return UpdateAccountResponse.from(response); + } + private Request.Builder newAuthRequest(URL url) { return newAuthRequest(url, null); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountRequest.java new file mode 100644 index 0000000000..5669e6cf0f --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountRequest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import io.realm.internal.Util; + +/** + * This class encapsulates the JSON request body when either doing a password reset or an email confirmation + * flow. + */ +public class UpdateAccountRequest { + + private static final Map NO_DATA = new HashMap<>(); + + private final String action; + private final Map data; + private final String providerId; // Should be an email address, but let server validate that. + + public static UpdateAccountRequest requestPasswordReset(String email) { + return new UpdateAccountRequest("reset_password", NO_DATA, email); + } + + public static UpdateAccountRequest completePasswordReset(String resetPasswordToken, String newPassword) { + Map data = new HashMap<>(); + data.put("token", resetPasswordToken); + data.put("new_password", newPassword); + return new UpdateAccountRequest("complete_reset", data, null); + } + + public static UpdateAccountRequest requestEmailConfirmation(String email) { + return new UpdateAccountRequest("request_email_confirmation", NO_DATA, email); + } + + public static UpdateAccountRequest completeEmailConfirmation(String confirmEmailToken) { + Map data = new HashMap<>(); + data.put("token", confirmEmailToken); + return new UpdateAccountRequest("confirm_email", data, null); + } + + private UpdateAccountRequest(String action, Map data, String providerId) { + this.action = action; + this.data = data; + this.providerId = providerId; + } + + /** + * Converts the request into a JSON payload. + */ + public String toJson() { + Map payload = new HashMap() {{ + if (!Util.isEmptyString(providerId)) { + put("provider_id", providerId); + } + data.put("action", action); + put("data", data); + }}; + + return new JSONObject(payload).toString(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountResponse.java new file mode 100644 index 0000000000..cdb528bd86 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountResponse.java @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.internal.network; + +import java.io.IOException; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import okhttp3.Response; + +/** + * This class represents the response from an {@link UpdateAccountRequest} network call. + */ +public class UpdateAccountResponse extends AuthServerResponse { + + public static UpdateAccountResponse from(Exception exception) { + return new UpdateAccountResponse(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + + public static UpdateAccountResponse from(Response response) { + if (response.isSuccessful()) { + return new UpdateAccountResponse(); + } else { + try { + String serverResponse = response.body().string(); + return new UpdateAccountResponse(AuthServerResponse.createError(serverResponse, response.code())); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new UpdateAccountResponse(error); + } + } + } + + /** + * Create a failure response object. + */ + public UpdateAccountResponse(ObjectServerError error) { + this.error = error; + } + + /** + * Create a successful response object. + */ + public UpdateAccountResponse() { + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 009a988e86..26c68b52fa 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -787,7 +787,7 @@ public void retrieve_notAdmin() { try { user1.retrieveInfoForUser(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, username2); fail("It should not be possible to lookup a user using non admin token"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException ignored) { } } @@ -824,4 +824,120 @@ public void onError(ObjectServerError error) { } }); } + + @Test + @RunTestInLooperThread + @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + public void requestPasswordResetAsync() { + String email = "foo@bar.baz"; + UserFactory.createUser(email).logOut(); + + // Currently no easy way to see if we actually get an email. + // Just verify that the network request can complete successfully. + SyncUser.requestPasswordResetAsync(email, Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(Void result) { + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + @Test + @RunTestInLooperThread + @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + public void requestResetPassword_unknownEmail() { + SyncUser.requestPasswordResetAsync("unknown@realm.io", Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(Void result) { + // Server will respond with SUCCESS if the email is incorrect (for security reasons). + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + @Test + @RunTestInLooperThread + @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + public void completeResetPassword_invalidToken() { + SyncUser.completePasswordResetAsync("invalidToken","newPassword", Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(Void result) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); + looperThread.testComplete(); + } + }); + } + + @Test + @RunTestInLooperThread + @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + public void requestEmailConfirmation() { + String email = "foo@bar.baz"; + UserFactory.createUser(email).logOut(); + + // Currently no easy way to see if we actually get an email. + // Just verify that the network request can complete successfully. + SyncUser.requestEmailConfirmationAsync(email, Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(Void result) { + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + @Test + @RunTestInLooperThread + @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + public void requestEmailConfirmation_invalidEmail() { + SyncUser.requestEmailConfirmationAsync("unknown@realm.io", Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(Void result) { + // Server will respond with SUCCESS if the email is incorrect (for security reasons). + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + + @Test + @RunTestInLooperThread + @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + public void confirmEmail_invalidToken() { + SyncUser.confirmEmailAsync("invalidToken", Constants.AUTH_URL, new SyncUser.Callback() { + @Override + public void onSuccess(Void result) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); + looperThread.testComplete(); + } + }); + } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java index 88c4ab3ee5..d8d7d6de35 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java @@ -39,7 +39,7 @@ // Must be in `io.realm.objectserver` to work around package protected methods. // This require Realm.init() to be called before using this class. public class UserFactory { - private static final String PASSWORD = "myPassw0rd"; + public static final String PASSWORD = "myPassw0rd"; // Since the integration tests need to use the same user for different processes, we create a new user name when the // test starts and store it in a Realm. Then it can be retrieved for every process. private String userName; From 4f06af428b0ebab5bf3b66f741b98846fbd7ff8d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 25 Apr 2018 11:29:54 +0200 Subject: [PATCH 1228/2110] Migration that changes primary key deletes rows (#5902) --- CHANGELOG.md | 7 +++ .../java/io/realm/RealmObjectSchemaTests.java | 37 ++++++++++++- .../src/main/cpp/io_realm_internal_Table.cpp | 52 ++++++++----------- .../java/io/realm/internal/OsSharedRealm.java | 10 +++- .../main/java/io/realm/internal/Table.java | 6 +++ 5 files changed, 79 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d86e153834..f7b9e6ce3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.0.2 (YYYY-MM-DD) + +### Bug Fixes + +* Changing a primary key from being nullable to being required could result in objects being deleted (##5899). + + ## 5.0.1 (2018-04-09) ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 74c3b6e0cd..8ea8719c14 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -884,7 +884,7 @@ public void binaryData_nullabilityConversions() { assertEquals(0, list.get(0).length); assertArrayEquals(new byte[] {1, 2, 3}, list.get(1)); } - + @Test public void setRequired_true_onPrimaryKeyField_containsNullValues_shouldThrow() { if (type == ObjectSchemaType.IMMUTABLE) { @@ -1349,4 +1349,39 @@ public void addList_modelClassThrowsWithProperError() { private interface FieldRunnable { void run(String fieldName); } + + // Tests https://github.com/realm/realm-studio/issues/5899 + @Test + public void setRequired_keepExistingRowsIfPrimaryKey() { + if (type == ObjectSchemaType.IMMUTABLE) { + return; + } + DynamicRealm dynRealm = (DynamicRealm) realm; + String className = "NewClass"; + String fieldName = "field"; + + // Check all primary key types + for (PrimaryKeyFieldType fieldType : PrimaryKeyFieldType.values()) { + schema.addField(fieldName, fieldType.getType(), FieldAttribute.PRIMARY_KEY); // primary key field + + // Hackish way to add sample data, only treat string differently + for (int i = 0; i < 5; i++) { + Object primaryKeyValue = (fieldType.getType() == String.class) ? Integer.toString(i) : i; + dynRealm.createObject(className, primaryKeyValue); + } + + // Verify that sample data is intact before swapping nullability state + String errMsg = String.format(String.format("Count mismatch for FieldType = %s and Nullable = %s", fieldType.getType(), schema.isNullable(fieldName))); + assertEquals(errMsg, 5, dynRealm.where(className).count()); + + // Swap nullability state + schema.setRequired(fieldName, !schema.isRequired(fieldName)); + errMsg = String.format(String.format("Count mismatch for FieldType = %s and Nullable = %s", fieldType.getType(), schema.isNullable(fieldName))); + assertEquals(errMsg, 5, dynRealm.where(className).count()); + + // Cleanup + dynRealm.delete(className); + schema.removeField(fieldName); + } + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index d85ad5e1d9..077b0de3e9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -215,10 +215,13 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable(J // 4d. the column to be converted will index shifted one place to column_index + 1 // 5. search indexing must be preserved // 6. removing the original column and renaming the temporary column will make it look like original is being modified +// +// WARNING: These methods do NOT work on primary key columns if the Realm is synchronized. +// // Converts a table to allow for nullable values // Works on both normal table columns and sub tables -static void convert_column_to_nullable(JNIEnv* env, Table* old_table, size_t old_col_ndx, Table* new_table, size_t new_col_ndx, bool is_primary_key) +static void convert_column_to_nullable(JNIEnv* env, Table* old_table, size_t old_col_ndx, Table* new_table, size_t new_col_ndx) { DataType column_type = old_table->get_column_type(old_col_ndx); if (old_table != new_table) { @@ -229,12 +232,7 @@ static void convert_column_to_nullable(JNIEnv* env, Table* old_table, size_t old case type_String: { // Payload copy is needed StringData sd(old_table->get_string(old_col_ndx, i)); - if (is_primary_key) { - new_table->set_string_unique(new_col_ndx, i, sd); - } - else { - new_table->set_string(new_col_ndx, i, sd); - } + new_table->set_string(new_col_ndx, i, sd); break; } case type_Binary: { @@ -243,12 +241,7 @@ static void convert_column_to_nullable(JNIEnv* env, Table* old_table, size_t old break; } case type_Int: - if (is_primary_key) { - new_table->set_int_unique(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); - } - else { - new_table->set_int(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); - } + new_table->set_int(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); break; case type_Bool: new_table->set_bool(new_col_ndx, i, old_table->get_bool(old_col_ndx, i)); @@ -313,8 +306,11 @@ static void create_new_column(Table* table, size_t column_index, bool nullable) JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullable(JNIEnv* env, jobject obj, jlong native_table_ptr, jlong j_column_index, - jboolean is_primary_key) + jboolean) { +#if REALM_ENABLE_SYNC + REALM_ASSERT(false); +#endif Table* table = TBL(native_table_ptr); if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { return; @@ -352,11 +348,11 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl for (size_t i = 0; i < table->size(); ++i) { TableRef new_subtable = table->get_subtable(column_index, i); TableRef old_subtable = table->get_subtable(column_index + 1, i); - convert_column_to_nullable(env, old_subtable.get(), 0, new_subtable.get(), 0, is_primary_key); + convert_column_to_nullable(env, old_subtable.get(), 0, new_subtable.get(), 0); } } else { - convert_column_to_nullable(env, table, column_index + 1, table, column_index, is_primary_key); + convert_column_to_nullable(env, table, column_index + 1, table, column_index); } // Cleanup @@ -373,12 +369,14 @@ static void convert_column_to_not_nullable(JNIEnv* env, Table* old_table, size_t { DataType column_type = old_table->get_column_type(old_col_ndx); std::string column_name = old_table->get_column_name(old_col_ndx); + size_t no_rows = old_table->size(); if (old_table != new_table) { - new_table->add_empty_row(old_table->size()); + new_table->add_empty_row(no_rows); } - for (size_t i = 0; i < old_table->size(); ++i) { + for (size_t i = 0; i < no_rows; ++i) { switch (column_type) { // FIXME: respect user-specified default values case type_String: { + // Payload copy is needed StringData sd = old_table->get_string(old_col_ndx, i); if (sd == realm::null()) { if (is_primary_key) { @@ -390,13 +388,7 @@ static void convert_column_to_not_nullable(JNIEnv* env, Table* old_table, size_t } } else { - // Payload copy is needed - if (is_primary_key) { - new_table->set_string_unique(new_col_ndx, i, sd); - } - else { - new_table->set_string(new_col_ndx, i, sd); - } + new_table->set_string(new_col_ndx, i, sd); } break; } @@ -423,12 +415,7 @@ static void convert_column_to_not_nullable(JNIEnv* env, Table* old_table, size_t } } else { - if (is_primary_key) { - new_table->set_int_unique(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); - } - else { - new_table->set_int(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); - } + new_table->set_int(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); } break; case type_Bool: @@ -483,6 +470,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull jlong j_column_index, jboolean is_primary_key) { +#if REALM_ENABLE_SYNC + REALM_ASSERT(false); +#endif try { Table* table = TBL(native_table_ptr); if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index b897c13f5c..e9be90c80e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -445,12 +445,20 @@ public void registerSchemaChangedCallback(SchemaChangedCallback callback) { } /** - * Returns {@code true} if this Realm is a partially synchronized Realm. + * Returns {@code true} if this Realm is a query-based synchronized Realm. */ public boolean isPartial() { return nativeIsPartial(nativePtr); } + /** + * Returns {@code true} if this Realm is a synchronized Realm, either query-based or fully + * synchronized. + */ + public boolean isSyncRealm() { + return osRealmConfig.getResolvedRealmURI() != null; + } + // addIterator(), detachIterators() and invalidateIterators() are used to make RealmResults stable iterators work. // The iterator will iterate on a snapshot Results if it is accessed inside a transaction. // See https://github.com/realm/realm-java/issues/3883 for more information. diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 533603d665..063e580c75 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -226,6 +226,9 @@ public boolean isColumnNullable(long columnIndex) { * @param columnIndex the column index. */ public void convertColumnToNullable(long columnIndex) { + if (sharedRealm.isSyncRealm()) { + throw new IllegalStateException("This method is only available for non-synchronized Realms"); + } nativeConvertColumnToNullable(nativePtr, columnIndex, isPrimaryKey(columnIndex)); } @@ -235,6 +238,9 @@ public void convertColumnToNullable(long columnIndex) { * @param columnIndex the column index. */ public void convertColumnToNotNullable(long columnIndex) { + if (sharedRealm.isSyncRealm()) { + throw new IllegalStateException("This method is only available for non-synchronized Realms"); + } nativeConvertColumnToNotNullable(nativePtr, columnIndex, isPrimaryKey(columnIndex)); } From 6fee93af92ec346ac69d7cacf49dc1e331d3cd3b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 25 Apr 2018 11:39:13 +0200 Subject: [PATCH 1229/2110] Release v5.1.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b0fc294f07..acf69b48b8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.1.0-SNAPSHOT +5.1.0 \ No newline at end of file From efdfd57ee9e07bd6ae1ef5117bab71d9680d9754 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 25 Apr 2018 11:39:13 +0200 Subject: [PATCH 1230/2110] Prepare next release v5.1.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index acf69b48b8..d509cc92aa 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.1.0 \ No newline at end of file +5.1.1-SNAPSHOT \ No newline at end of file From 2de0081468f30b59d363b79075574e7db411cbdd Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 25 Apr 2018 14:38:46 +0200 Subject: [PATCH 1231/2110] Fix release build for architecture components example --- examples/architectureComponentsExample/build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/architectureComponentsExample/build.gradle b/examples/architectureComponentsExample/build.gradle index 2bf5eedf9f..5371c339cf 100644 --- a/examples/architectureComponentsExample/build.gradle +++ b/examples/architectureComponentsExample/build.gradle @@ -28,7 +28,11 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + minifyEnabled true } } } From efb60ff329cd80471ca49f3e2cbadd5c35d0c1bb Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 25 Apr 2018 14:48:17 +0200 Subject: [PATCH 1232/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index d509cc92aa..6555596f93 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.1.1-SNAPSHOT \ No newline at end of file +5.2.0-SNAPSHOT \ No newline at end of file From c3d642372f8b3b7ac927ad81b8082e51ecf0a180 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 26 Apr 2018 14:19:24 +0200 Subject: [PATCH 1233/2110] Use standalone server project for integration tests (#5909) --- Jenkinsfile | 6 +- dependencies.list | 2 +- tools/sync_test_server/Dockerfile | 29 +- tools/sync_test_server/configuration.yml | 320 ------------------ .../integration-test-command-server.js | 204 +++++++++++ tools/sync_test_server/ros-testing-server.js | 171 ---------- tools/sync_test_server/ros/package.json | 17 + tools/sync_test_server/ros/src/index.ts | 99 ++++++ tools/sync_test_server/ros/tsconfig.json | 32 ++ tools/sync_test_server/start_server.sh | 4 +- 10 files changed, 379 insertions(+), 505 deletions(-) delete mode 100644 tools/sync_test_server/configuration.yml create mode 100755 tools/sync_test_server/integration-test-command-server.js delete mode 100755 tools/sync_test_server/ros-testing-server.js create mode 100644 tools/sync_test_server/ros/package.json create mode 100644 tools/sync_test_server/ros/src/index.ts create mode 100644 tools/sync_test_server/ros/tsconfig.json diff --git a/Jenkinsfile b/Jenkinsfile index 8412e67f04..4a7876c51d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,8 +41,8 @@ try { buildEnv = docker.build 'realm-java:snapshot' // Docker image for testing Realm Object Server def dependProperties = readProperties file: 'dependencies.list' - def rosDeVersion = dependProperties["REALM_OBJECT_SERVER_DE_VERSION"] - rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_DE_VERSION=${rosDeVersion} tools/sync_test_server" + def rosVersion = dependProperties["REALM_OBJECT_SERVER_VERSION"] + rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_VERSION=${rosVersion} tools/sync_test_server" } rosContainer = rosEnv.run() @@ -190,7 +190,7 @@ def stopLogCatCollector(String backgroundPid) { } def archiveRosLog(String id) { - sh "docker cp ${id}:/tmp/ros-testing-server.log ./ros.log" + sh "docker cp ${id}:/tmp/integration-test-command-server.log ./ros.log" zip([ 'zipFile': 'roslog.zip', 'archive': true, diff --git a/dependencies.list b/dependencies.list index d48cd91181..949504a8b7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,4 +5,4 @@ REALM_SYNC_SHA256=7764304d5dc7db7b4b9be9916f753c14c61c40e9f09fd1d92abeee3d847440 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_DE_VERSION=3.1.0 +REALM_OBJECT_SERVER_VERSION=3.1.5 diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 29f8e46fad..fbb9bb0f86 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -4,19 +4,32 @@ FROM node:6.11.4 RUN cp /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime RUN echo "Europe/Copenhagen" > /etc/timezone -ARG ROS_DE_VERSION +ARG ROS_VERSION -# Install realm object server -RUN npm install -g realm-object-server@$ROS_DE_VERSION -S +# Install netstat (used for debugging) +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + net-tools \ + psmisc \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +## Copy ROS node template project to image. Then configure and prepare it for usage. +COPY ros /ros +WORKDIR "/ros" +RUN sed -i -e "s/%ROS_VERSION%/$ROS_VERSION/g" package.json +RUN npm install +WORKDIR "/" # Install test server dependencies -RUN npm install winston@2.4.0 temp httpdispatcher@1.0.0 fs-extra moment +RUN npm install winston@2.4.0 temp httpdispatcher@1.0.0 fs-extra moment is-port-available@0.1.5 -COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem configuration.yml / -COPY ros-testing-server.js /usr/bin/ +COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem / +COPY integration-test-command-server.js /usr/bin/ -#Bypass the ROS license check +# Bypass the ROS license check ENV DOCKER_DATA_PATH / ENV ROS_TOS_EMAIL_ADDRESS 'ci@realm.io' -CMD /usr/bin/ros-testing-server.js /tmp/ros-testing-server.log +# Run integration test server +CMD /usr/bin/integration-test-command-server.js /tmp/integration-test-command-server.log diff --git a/tools/sync_test_server/configuration.yml b/tools/sync_test_server/configuration.yml deleted file mode 100644 index 9b73a0c68c..0000000000 --- a/tools/sync_test_server/configuration.yml +++ /dev/null @@ -1,320 +0,0 @@ -# Realm Object Server Configuration -# -# For each possible setting, the commented out values are the default values -# unless another default is mentioned explicitly. -# -# Paths specified in this file can be either absolute or relative. -# Relative paths are relative to the current working directory. - - -## ---------------------------------------------------------------------------- -## The following options are MANDATORY, either by providing them in this file, -## or as command-line options: -## - storage: root_path -## - auth:public_key_path -## - auth:private_key_path -## ---------------------------------------------------------------------------- - - -storage: - ## The directory in which the realm server will store all its data files. - ## This configuration option is MANDATORY. - root_path: '/var/realm/sync-services' - -## ---------------------------------------------------------------------------- - -auth: - ## The path to the public and private keys (in PEM format) that will be used - ## to validate identity tokens sent by clients. - ## These configuration options are MANDATORY. - public_key_path: '/public.pem' - private_key_path: '/private.pem' - - sync_hosts: - ## The hosts for which the authentication service will consider itself - ## authoritative. It will decline to process any kind of requests for Realm - ## files at other URLs. Addresses specified here must include host and port - ## (authority part of the URL according to RFC 3986) on which the sync - ## server is externally reachable. In addition to hosts configured here, - ## the authentication service will always accept the following hosts: - # - localhost:27800 - # - # Additionally if a proxy server for the given protocol is configured, it - # will also accept requests for Realm files at these hosts: - # - ${proxy:http:listen_address}:${proxy:http:listen_port} - # - ${proxy:https:listen_address}:${proxy:https:listen_port} - # - # The derived hosts will also include aliases for local addresses - # with the following host names: '127.0.0.1', 'localhost' and '::'. - - ttls: - ## The validity duration for Refresh Tokens. This can be a fairly high - ## value, ranging from a single day to multiple years, depending on - ## individual needs. Whenever the Refresh Token expires, clients will be - ## forced to delegate again to the authorizing party. If the credentials - ## there can be revoked by the user or are not opaquely managed by the - ## client, then this would force the user to manual intervention after the - ## expiration. Depending on the use case, this can be either desired or - ## should be prevented. This value is represented in seconds. - ## Default: 10 years. - # refresh_token: 315360000 - - ## The validity duration for Access Tokens. This should be a fairly small - ## number, especially if you are concerned with revocations being applied - ## quickly. This value is represented in seconds. Default: 1 minute. - ## - ## WARNING : Changing this value may impact the timeout of the refresh - ## token test (AuthTests#preemptiveTokenRefresh) - access_token: 20 - - providers: - ## Providers of authentication tokens. Each provider has a configuration - ## object associated with it. If a provider is included here and its - ## configuration is valid, it will be enabled. - - ## Possible providers: cloudkit, debug, google, facebook, realm, password - ## Providers 'realm' and 'password' are always enabled: - ## - The 'realm' provider is used to derive access tokens from a refresh token. - ## - The 'password' provider is required for the dashboard to work. It supports - ## authentication through username/password and uses a PBKDF2 implementation. - - ## This enables login via CloudKit's user record name. - # cloudkit: - ## The key ID retrieved when adding the public key derived from the - ## specified private_key_path in CloudKit's Server-to-Server Keys, - ## available through the API Access settings in the CloudKit dashboard. - # key_id: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' - - ## The path to the certificate. - # private_key_path: 'cloudkit_eckey.pem' - - ## The container identifier in reverse domain name notation. - # container: "iCloud.io.realm.exampleApp.ios" - - ## The environment in which CloudKit should be used. The default is - ## 'development'. For the production deployment for apps on the AppStore - ## you must specify 'production'. - # environment: 'development' - - ## This enables authentication via a Google Sign-In access token for a - ## specific app. - # google: - ## The client ID as retrieved when setting up the app in the Google - ## Developer Console. - # clientId: '012345678901-abcdefghijklmnopqrstvuvwxyz01234.apps.googleusercontent.com' - - ## This enables authentication via a Facebook access token for a specific app. - ## This provider needs no configuration (uncommenting the next line enables it). - # facebook: {} - - ## This enables authentication via an Azure Active Directory access token for a specific directory. - # azuread: - ## The Directory Id as retrieved from the Active Directory properties in the Azure portal. - # tenant_id: '01234567-89ab-cdef-0123-4567890a' - - ## This would enable a custom authentication provider with the name "custom/twitter". - ## The prefix "custom/" is necessary for all authentication providers using a custom - ## implementation to ensure forwards-compatiblity and avoid name clashes. - # custom/twitter: - ## The implementation to be used. This can be either one of the predefined - ## implementations under a custom name or a custom implementation found at - ## the include_path or if not given at the auth:providers_include_path. - # implementation: custom_provider_implementation.js - - ## The include path to use for this providers custom implementation. - # include_path: /~/.realm/auth - - ## Beyond that custom implementations can define custom configuration - ## options which will be populated to their configuration and merged with - ## the default values they can define. - debug: {} - -## ---------------------------------------------------------------------------- - -proxy: - ## Network settings for the externally accessible proxy module. - ## This can be enabled for both HTTP and HTTPS traffic simultaneously, and - ## forwards traffic to the sync and services internal modules. - ## It is possible to disable and replace the proxy module by another reverse proxy. - ## - ## Note: The proxy module forwards traffic to the internal modules on the - ## addresses and ports they listen on (as configured in the `network' section below). - ## - ## Shown below is a diagram of the default network configuration: - ## - ## +----------------------+ - ## | | - ## | Sync module | - ## | | - ## | (internal) | - ## | | - ## +-> | Defaults: | - ## +----------------+ +---------------------------+ | | Listen: 127.0.0.1 | - ## | | | | | | Ports: | - ## | Realm Client +------+ | Proxy module | | | WS: tcp/27800 | - ## | | | | | | | | - ## +----------------- | | (externally accessible) | | +----------------------+ - ## +----> | | | - ## | Defaults: | | - ## +----> | Listen: 0.0.0.0 +----+ - ## +------------ | | Ports: | | +----------------------+ - ## | | | | HTTP & WS: tcp/9080 | | | | - ## | Browser +------+ | HTTPS & WSS: tcp/9443 | | | Services module | - ## | | | | | | | - ## +-----------+ +---------------------------+ | | (internal) | - ## +-> | | - ## | Defaults: | - ## Note: The proxy module can be | Listen: 127.0.0.1 | - ## replaced by NGINX or other | Ports: | - ## reverse proxies | HTTP: tcp/27080 | - ## | | - ## +----------------------+ - - http: - ## Whether or not to enable the HTTP proxy module. It enables multiplexing requests - ## by forwarding incoming requests on a single port to all services. - # enable: true - - ## The address/interface on which the HTTP proxy module should listen. This defaults - ## to 127.0.0.1. If you wish to listen on all available interfaces, - ## uncomment the following line. - listen_address: '0.0.0.0' - - ## The port that the HTTP proxy module should bind to. - listen_port: 9080 - - https: - ## Whether or not to enable the HTTPS proxy module. It enables multiplexing requests - ## by forwarding incoming requests on a single port to all services. - ## Note that even if it enabled, the HTTPS proxy will only start if supplied - ## with a valid pair of certificates through certificate_path and private_key_path below. - enable: true - - ## The path to the certificate and private keys (in PEM format) that will be used - ## to set up the HTTPS server accepting connections. - ## These configuration options are MANDATORY to start the HTTPS proxy module. - certificate_path: '/127_0_0_1-chain.crt.pem' - private_key_path: '/127_0_0_1-server.key.pem' - - ## The address/interface on which the HTTPS proxy module should listen. This defaults - ## to 127.0.0.1. If you wish to listen on all available interfaces, - ## uncomment the following line. - listen_address: '0.0.0.0' - - ## The port that the HTTPS proxy module should bind to. - listen_port: 9443 - -## ---------------------------------------------------------------------------- - -network: - ## Network settings for internal modules, to which traffic is forwarded from - ## the proxy module. The proxy module will automatically forward traffic to the - ## internal modules on the ports they are configured to listen on in this section. - - http: - ## The address/interface on which the server should listen for HTTP - ## services. This includes Dashboard and Authentication APIs. - ## This defaults to 127.0.0.1. If you wish to listen on all available - ## interfaces, uncomment the following line. - # listen_address: '0.0.0.0' - - ## The port on which to listen for incoming requests to the Dashboard - ## and authentication APIs. This defaults to 27080. - # listen_port: 27080 - -## ---------------------------------------------------------------------------- - -sync: - ## Synchronization service settings, including clustering and load balancing. - - servers: - ## An array of entries describing the cluster configuration. - ## - ## If no servers are configured, a default entry is inserted with the - ## following settings: - ## - id: '0' - ## address: '0.0.0.0' - ## port: 27800 - ## - ## Each entry must contain the following entries: - ## - ## 'id': A unique string ID used to distinguish between backend servers. - ## This must remain stable, even if the particular backend server - ## is moved to a different address or port. - ## - ## 'address': The address of the cluster participant. If '0.0.0.0' or - ## '::', a sync server will be started on localhost (listening - ## on '127.0.0.1' or '::1', respectively). Otherwise, it is - ## assumed that the sync server is an external process, - ## potentially on a separate machine. - ## - ## 'port': The port on which to connect to the particular cluster node. - ## If address was '0.0.0.0' or '::', this is also the port number - ## on which the local cluster node will listen for connections. - -## ---------------------------------------------------------------------------- - -logging: - ## The logging level of the server. - ## - ## Note: This used to be an integer, but has been updated to be more - ## descriptive. The integer values are no longer supported. - ## - ## Possible values (from most to least verbose): - ## - ## all: no filtering - ## trace - ## debug - ## detail - ## info: good for production (default) - ## warn - ## error - ## fatal - ## off: all output suppressed - level: 'detail' - - ## The file to which the synchronisation server should log. This should - ## be a writable path from the perspective of the user under which the - ## server runs. If no path is specified, the server will log to stdout. - # path: '/var/log/realm-object-server.log' - -## ---------------------------------------------------------------------------- - -performance: - ## The maximum number of Realm files that the server will have open - ## concurrently (LRU cache). The default is 256. - ## Only change this option if directed to by Realm support. - # max_open_files: 256 - -## ---------------------------------------------------------------------------- - -backup: - ## The backup is a server that delivers continuous backup of the Realms in - ## storage.root_path specified above. The backup is delivered to all connected - ## backup clients. Backup clients must be started separately with network - ## configuration parameters matching those of the server. - - enable: - ## Whether or not to enable the backup server. - # enable: true - - network: - ## The address/interface on which the backup server should listen. This - ## defaults to 127.0.0.1. If you wish to listen on all available interfaces, - ## uncomment the following line. - # listen_address: '0.0.0.0' - - ## The port on which to listen. The backup server uses port 27810 by - ## default. For most deployments, there should not be a need to change this. - # listen_port: 27810 - - logging: - ## The logging level of the backup server. - ## The values are identical to the logging levels described above. - ## The default level is 'info'. - # level: 'info' - - ## The file to which the synchronisation server should log. This should - ## be a writable path from the perspective of the user under which the - ## server runs. If no path is specified, the server will log to stdout. - # path: '/var/log/realm-object-server-backup.log' diff --git a/tools/sync_test_server/integration-test-command-server.js b/tools/sync_test_server/integration-test-command-server.js new file mode 100755 index 0000000000..71d4fa6179 --- /dev/null +++ b/tools/sync_test_server/integration-test-command-server.js @@ -0,0 +1,204 @@ +#!/usr/bin/env nodejs + +/** + * This script controls the Command Server responsible for starting and stopping + * ROS instances. The integration tests running on the device will communicate + * with it using a predefined port in order to say when the ROS instance + * should be started and stopped. + * + * This script is responsible for cleaning up any server state after it has been + * stopped, so a new integration test will start from a clean slate. + */ + +var winston = require('winston'); //logging +const spawn = require('child_process').spawn; +const exec = require('child_process').exec; +const isPortAvailable = require('is-port-available'); +var http = require('http'); +var dispatcher = require('httpdispatcher'); +var fs = require('fs-extra'); +var moment = require('moment') + +if (process. argv. length <= 2) { + console.log("Usage: " + __filename + " somefile.log"); + process.exit(-1); +} + +const logFile = process.argv[2]; +winston.level = 'debug'; +winston.add(winston.transports.File, { + filename: logFile, + json: false, + formatter: function(options) { + return moment().format('YYYY-MM-DD HH:mm:ss.SSSS') + ' ' + (undefined !== options.message ? options.message : ''); + } +}); + +const PORT = 8888; +var syncServerChildProcess = null; + +// When starting ROS, it isn't ready immediately. This method will wait until /health/ +// returns OK indicating that ROS is now fully initialized and ready. +function waitForRosToInitialize(attempts, onSuccess, onError, startSequence) { + if (attempts == 0) { + onError("Could not get ROS to start. See Docker log."); + return; + } + + http.get("http://0.0.0.0:9080/health", function(res) { + if (res.statusCode != 200) { + winston.warn("command-server: ROS /health/ returned: " + res.statusCode) + setTimeout(function() { + waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); + }, 500); + } else { + onSuccess(startSequence); + } + }).on('error', function(err) { + winston.warn("command-server: ROS /health/ returned an error: " + err) + // ROS not accepting any connections yet. + // Errors like ECONNREFUSED 0.0.0.0:9080 will be reported here. + // Wait a little before trying again (common startup is ~1 second). + setTimeout(function() { + waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); + }, 500); + }); +} + +// When starting a new ROS instance, an old one might still be in the process of being +// torn down. This can sometimes cause the new server to fail to start due to the +// port still being used. To prevent that, we wait for the port to be ready +// before trying to start the server. +function waitForPortToBeReady(attempts, onSuccess, onError) { + if (attempts == 0) { + // Log as much info as possible in order to help debugging + exec('ps auxw', (error, stdout, stderr) => { + winston.info(`command-server:\n ${stdout}`); + }); + exec('netstat -tulpn', (error, stdout, stderr) => { + winston.info(`command-server:\n ${stdout}`); + }); + onError("Port failed to become ready in time"); + return; + } + + // Port 9080 and 9443 are being used by ROS + isPortAvailable("9443").then( status => { + if (status) { + onSuccess(); + } else { + winston.info("command-server: Port still in use. Retrying.") + setTimeout(function() { + waitForPortToBeReady(attempts - 1, onSuccess, onError); + }, 500); + } + }); +} + +function startRealmObjectServer(onSuccess, onError) { + stopRealmObjectServer(() => { + waitForPortToBeReady(20, function() { + winston.info("command-server: Starting ROS in /ros"); + var env = Object.create( process.env ); + winston.info(env.NODE_ENV); + env.NODE_ENV = 'development'; + + // Cleanup any previous server state + winston.info("command-server: Cleaning old server state"); + fs.removeSync('/ros/data'); + fs.removeSync('/ros/realm-object-server'); + fs.removeSync('/ros/log.txt'); + if (fs.existsSync('/ros/data')) { + onError("Could not delete data directory: " + globalNotifierDir); + return; + } + if (fs.existsSync('/ros/realm-object-server')) { + onError("Could not delete global notifier directory: " + globalNotifierDir); + return; + } + + // Start ROS + syncServerChildProcess = spawn('npm', ['start'], { env: env, cwd: '/ros' }); + + // Route logs from ROS to the Command Server log so we can save it + syncServerChildProcess.stdout.on('data', (data) => { + winston.info(`ros: ${data}`); + }); + + syncServerChildProcess.stderr.on('data', (data) => { + winston.info(`ros: ${data}`); + }); + + // The interval between every health check is 0.5 second. Give the ROS 30 seconds to get fully initialized. + waitForRosToInitialize(60, onSuccess, onError, Date.now()); + + }, onError); + }, onError) +} + +function stopRealmObjectServer(onSuccess, onError) { + if(syncServerChildProcess == null || syncServerChildProcess.killed) { + onSuccess("No ROS process found or the process has been killed before"); + } + if (syncServerChildProcess) { + syncServerChildProcess.on('exit', function(code) { + // Manually kill sub process started by node that actually runs ROS. + // It is not killed when killing the process running NPM + exec('fuser -k 9443/tcp', (error, stdout, stderr) => { + if (error) { + onError(error) + return; + } + winston.info(`command-server: Stopping process: '${stdout}'`) + syncServerChildProcess.removeAllListeners('exit'); + syncServerChildProcess = null; + onSuccess(); + }); + }); + syncServerChildProcess.kill('SIGINT'); + } +} + +// Command Server endpoint: Start a new instance of ROS +dispatcher.onGet("/start", function(req, res) { + winston.info("command-server: Attempting to start ROS"); + startRealmObjectServer((startSequence) => { + res.writeHead(200, {'Content-Type': 'text/plain'}); + let response = `ROS started after ${Date.now() - startSequence} ms`; + res.end(response); + winston.info("command-server: " + response); + }, function (err) { + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end('Starting ROS failed: ' + err); + winston.error('command-server: Starting ROS failed: ' + err); + }); +}); + +// Command Server endpoint: Stop a running instance of ROS. +dispatcher.onGet("/stop", function(req, res) { + winston.info("command-server: Attempting to stop ROS"); + stopRealmObjectServer(function() { + winston.info("command-server: ROS stopped"); + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('ROS stopped'); + }, function(err) { + winston.error('command-server: Stopping ROS failed: ' + err); + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end('Stopping ROS failed: ' + err); + }); +}); + +function handleRequest(request, response) { + try { + winston.info('command-server: ' + request.url); + dispatcher.dispatch(request, response); + } catch(err) { + winston.error('command-server: ' + err); + } +} + +//Create and start the Http server +var server = http.createServer(handleRequest); +server.listen(PORT, function() { + winston.info("command-server: Integration test server listening on: 127.0.0.1:%s", PORT); +}); diff --git a/tools/sync_test_server/ros-testing-server.js b/tools/sync_test_server/ros-testing-server.js deleted file mode 100755 index 25561bbb7e..0000000000 --- a/tools/sync_test_server/ros-testing-server.js +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env nodejs - -var winston = require('winston'); //logging -const temp = require('temp'); -const spawn = require('child_process').spawn; -const exec = require('child_process').exec; -var http = require('http'); -var dispatcher = require('httpdispatcher'); -var fs = require('fs-extra'); -var moment = require('moment') - -// Automatically track and cleanup files at exit -temp.track(); - -if (process. argv. length <= 2) { - console.log("Usage: " + __filename + " somefile.log"); - process.exit(-1); -} - -const logFile = process.argv[2]; -winston.level = 'debug'; -winston.add(winston.transports.File, { - filename: logFile, - json: false, - formatter: function(options) { - return moment().format('YYYY-MM-DD HH:mm:ss.SSSS') + ' ' + (undefined !== options.message ? options.message : ''); - } -}); - -const PORT = 8888; - -function handleRequest(request, response) { - try { - //log the request on console - winston.log(request.url); - //Dispatch - dispatcher.dispatch(request, response); - } catch(err) { - console.log(err); - } -} - -var syncServerChildProcess = null; - -// Waits for ROS to be fully initialized. -function waitForRosToInitialize(attempts, onSuccess, onError, startSequence) { - if (attempts == 0) { - onError("Could not get ROS to start. See Docker log."); - return; - } - http.get("http://0.0.0.0:9080/health", function(res) { - if (res.statusCode != 200) { - winston.warn("ROS /health/ returned: " + res.statusCode) - setTimeout(function() { - waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); - }, 500); - } else { - onSuccess(startSequence); - } - }).on('error', function(err) { - winston.warn("ROS /health/ returned an error: " + err) - // ROS not accepting any connections yet. - // Errors like ECONNREFUSED 0.0.0.0:9080 will be reported here. - // Wait a little before trying again (common startup is ~1 second). - setTimeout(function() { - waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); - }, 500); - }); -} - -function startRealmObjectServer(onSuccess, onError) { - stopRealmObjectServer(() => { - doStartRealmObjectServer(onSuccess, onError) - }, onError) -} - -function doStartRealmObjectServer(onSuccess, onError) { - temp.mkdir('ros', function(err, path) { - if (!err) { - winston.info("Starting sync server in ", path); - var env = Object.create( process.env ); - winston.info(env.NODE_ENV); - env.NODE_ENV = 'development'; - - // Manually cleanup Global Notifier State - // See https://github.com/realm/ros/issues/437#issuecomment-335380095 - var globalNotifierDir = path + '/realm-object-server'; - winston.info('Cleaning state in: ' + globalNotifierDir); - fs.removeSync(globalNotifierDir) - if (fs.existsSync(globalNotifierDir)) { - onError("Could not delete the global notifier directory: " + globalNotifierDir); - return; - } - fs.mkdirsSync(path + '/realm-object-server/io.realm.object-server-utility/metadata/') - - // Start ROS - syncServerChildProcess = spawn('ros', - ['start', - '--data', path, - '--loglevel', 'detail', - '--https', - '--https-key', '/127_0_0_1-server.key.pem', - '--https-cert', '/127_0_0_1-chain.crt.pem', - '--https-port', '9443', - '--access-token-ttl', '20' //WARNING : Changing this value may impact the timeout of the refresh token test (AuthTests#preemptiveTokenRefresh) - ], - { env: env, cwd: path}); - - // local config: - syncServerChildProcess.stdout.on('data', (data) => { - winston.info(`${data}`); - }); - - syncServerChildProcess.stderr.on('data', (data) => { - winston.info(`${data}`); - }); - - // The interval between every health check is 0.5 second. Give the ROS 30 seconds to get fully initialized. - waitForRosToInitialize(60, onSuccess, onError, Date.now()); - } - }); -} - -function stopRealmObjectServer(onSuccess, onError) { - if(syncServerChildProcess == null || syncServerChildProcess.killed) { - onSuccess("No ROS process found or the process has been killed before"); - } - if (syncServerChildProcess) { - syncServerChildProcess.on('exit', function(code) { - winston.info("ROS server stopped due to process being killed. Exit code: " + code); - syncServerChildProcess.removeAllListeners('exit'); - syncServerChildProcess = null; - onSuccess(); - }); - - syncServerChildProcess.kill('SIGKILL'); - } -} - -// start sync server -dispatcher.onGet("/start", function(req, res) { - winston.info("Attempting to start ROS"); - startRealmObjectServer((startSequence) => { - res.writeHead(200, {'Content-Type': 'text/plain'}); - let response = `ROS started after ${Date.now() - startSequence} ms`; - winston.info(response); - res.end(response); - }, function (err) { - winston.error('Starting ROS failed: ' + err); - res.writeHead(500, {'Content-Type': 'text/plain'}); - res.end('Starting ROS failed: ' + err); - }); -}); - -// stop a previously started sync server -dispatcher.onGet("/stop", function(req, res) { - winston.info("Attempting to stop ROS") - stopRealmObjectServer(function() { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('ROS stopped'); - }, function(err) { - res.writeHead(500, {'Content-Type': 'text/plain'}); - res.end('Stopping ROS failed: ' + err); - }); -}); - -//Create and start the Http server -var server = http.createServer(handleRequest); -server.listen(PORT, function() { - winston.info("Integration test server listening on: 127.0.0.1:%s", PORT); -}); diff --git a/tools/sync_test_server/ros/package.json b/tools/sync_test_server/ros/package.json new file mode 100644 index 0000000000..c1baabf349 --- /dev/null +++ b/tools/sync_test_server/ros/package.json @@ -0,0 +1,17 @@ +{ + "name": "ros-integration-test-server", + "version": "1.0.0", + "description": "ROS instance used by integration tests", + "main": "src/index.js", + "scripts": { + "build": "rm -rf dist; ./node_modules/.bin/tsc", + "clean": "rm -rf dist", + "start": "npm run build && node dist/index.js" + }, + "devDependencies": { + "typescript": "2.5.3" + }, + "dependencies": { + "realm-object-server": "%ROS_VERSION%" + } +} diff --git a/tools/sync_test_server/ros/src/index.ts b/tools/sync_test_server/ros/src/index.ts new file mode 100644 index 0000000000..2501cf5cd3 --- /dev/null +++ b/tools/sync_test_server/ros/src/index.ts @@ -0,0 +1,99 @@ +import { BasicServer, FileConsoleLogger } from 'realm-object-server' +import * as path from 'path' + +const server = new BasicServer() + +server.start({ + // For all the full list of configuration parameters see: + // https://realm.io/docs/realm-object-server/latest/api/ros/interfaces/serverconfig.html + + // This is the location where ROS will store its runtime data + dataPath: path.join(__dirname, '../data'), + + // A logger to pipe ROS information. You can also specify the log level. + // The log level can be one of: all, trace, debug, detail, info, warn, error, fatal, off. + logger: new FileConsoleLogger(path.join(__dirname, '../log.txt'), 'all', { + file: { + timestamp: true, + level: 'detail' + }, + console: { + level: 'info' + } + }), + + // The address on which to listen for connections + // address?: string = '0.0.0.0' + // address: '0.0.0.0', + + // The port on which to listen for connections + // port?: number = 9080 + // port: 9080, + + // Override the default list of authentication providers + // the default has PasswordAuthProvider, AnonymousAuthProvider, and NicknameAuthProvider + // you will need to add `import { auth, BasicServer } from 'realm-object-server' + // authProviders?: IAuthProvider[] + // authProviders: [new auth.PasswordAuthProvider({ autoCreateAdminUser: true }), new auth.NicknameAuthProvider(), new auth.AnonymousAuthProvider()] + + // Autogenerate public and private keys on startup + // autoKeyGen?: boolean = true + autoKeyGen: false, + + // Specify an alternative path to the private key. Otherwise, it is expected to be under the data path. + // privateKeyPath?: string + privateKeyPath: '/private.pem', + + // Specify an alternative path to the public key. Otherwise, it is expected to be under the data path. + // publicKeyPath?: string + publicKeyPath: '/public.pem', + + // The desired logging threshold. Can be one of: all, trace, debug, detail, info, warn, error, fatal, off) + // logLevel?: string = 'info' + logLevel: 'detail', + + // Enable the HTTPS Server. + // https?: boolean = false + https: true, + + // The port on which to listen for HTTPS connections. + // httpsAddress?: string = '0.0.0.0', + // httpsAddress: '0.0.0.0', + + // The address on which to listen for HTTPS connections. + // httpsPort?: number = 9443 + httpsPort: 9443, + + // The path to your HTTPS private key in PEM format. Required if HTTPS is enabled. + // httpsKeyPath?: string + httpsKeyPath: '/127_0_0_1-server.key.pem', + + // The path to your HTTPS certificate chain in PEM format. Required if HTTPS is enabled. + // httpsCertChainPath?: string + httpsCertChainPath: '/127_0_0_1-chain.crt.pem', + + // Specify the length of time (in seconds) in which access tokens are valid. + // accessTokenTtl?: number = 600 (ten minutes) + accessTokenTtl: 20, + + // Specify the length of time (in seconds) in which refresh tokens are valid. + // refreshTokenTtl?: number = 3153600000 (ten years) + // refreshTokenTtl: 3153600000, + + // Enable Log Compaction to save on bandwidth + // read more at https://docs.realm.io/platform/learn/advanced/log-compaction + // enableLogCompaction?: boolean = true + // enableLogCompaction: true + + // Increase or decrease the max download + // This affects how the Log Compaction works + // read more at https://docs.realm.io/platform/learn/advanced/log-compaction + // maxDownloadSize?: number 16000000 (16 megabytes) + // maxDownloadSize: 16000000 + }) + .then(() => { + console.log(`Realm Object Server was started on ${server.address}`) + }) + .catch(err => { + console.error(`Error starting Realm Object Server: ${err.message}`) + }) diff --git a/tools/sync_test_server/ros/tsconfig.json b/tools/sync_test_server/ros/tsconfig.json new file mode 100644 index 0000000000..8a5ed49104 --- /dev/null +++ b/tools/sync_test_server/ros/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "noImplicitAny": false, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true, + "outDir": "dist", + "sourceRoot": "src", + "declaration": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "dom", + "es6", + "dom.iterable", + "scripthost", + "esnext", + "esnext.asynciterable" + ] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] + } + \ No newline at end of file diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 37828b38d7..d8ff9a0e16 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -3,7 +3,7 @@ # Get the script dir which contains the Dockerfile DOCKERFILE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -ROS_DE_VERSION=$(grep REALM_OBJECT_SERVER_DE_VERSION $DOCKERFILE_DIR/../../dependencies.list | cut -d'=' -f2) +ROS_VERSION=$(grep REALM_OBJECT_SERVER_VERSION $DOCKERFILE_DIR/../../dependencies.list | cut -d'=' -f2) TMP_DIR=$(mktemp -d /tmp/sync-test.XXXX) || { echo "Failed to mktemp $TEST_TEMP_DIR" ; exit 1 ; } @@ -11,7 +11,7 @@ adb reverse tcp:9443 tcp:9443 && \ adb reverse tcp:9080 tcp:9080 && \ adb reverse tcp:8888 tcp:8888 || { echo "Failed to reverse adb port." ; exit 1 ; } -docker build $DOCKERFILE_DIR --build-arg ROS_DE_VERSION=$ROS_DE_VERSION -t sync-test-server || { echo "Failed to build Docker image." ; exit 1 ; } +docker build $DOCKERFILE_DIR --build-arg ROS_VERSION=$ROS_VERSION -t sync-test-server || { echo "Failed to build Docker image." ; exit 1 ; } echo "See log files in $TMP_DIR" docker run -p 9080:9080 -p 9443:9443 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server From 54e9587980a39875255ba3e3d717d6db7856ad6a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 30 Apr 2018 10:37:38 +0200 Subject: [PATCH 1234/2110] Fix wrong descriptiopn in usage info --- tools/unroll_stacktrace.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/unroll_stacktrace.sh b/tools/unroll_stacktrace.sh index f3f3dd8ec9..6c2d993fa5 100644 --- a/tools/unroll_stacktrace.sh +++ b/tools/unroll_stacktrace.sh @@ -14,11 +14,11 @@ IFS=$'\n\t' usage() { cat < +Usage: $0 - version: version number on Bintray - abi: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips - flavor: base, objectServer - - stacktrace: Path to file with dump + - stacktrace: absolute or relative path to file with dump information Example: $0 base 5.0.0 armeabi-v7a ./dump.txt EOF From e25198f0a16a158c1f82b8cd337c48b9247b5427 Mon Sep 17 00:00:00 2001 From: Makoto Yamazaki Date: Thu, 17 May 2018 00:54:56 +0900 Subject: [PATCH 1235/2110] Fix wrong markup in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c645665385..b2ab370eff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,7 @@ This release is compatible with the Realm Object Server 3.0.0-beta.3 or later. * [ObjectServer] Renamed `SyncUser.logout()` to `SyncUser.logOut()`. * The `OrderedCollectionChangeSet` parameter in `OrderedRealmCollectionChangeListener.onChange()` is no longer nullable. Use `changeSet.getState()` instead (#5619). * `realm.subscribeForObjects()` have been removed. Use `RealmQuery.findAllAsync(String subscriptionName)` and `RealmQuery.findAllAsync()` instead. -* Removed previously deprecated `RealmQuery.findAllSorted()`, `RealmQuery.findAllSortedAsync()` `RealmQuery.distinct() and `RealmQuery.distinctAsync()`. +* Removed previously deprecated `RealmQuery.findAllSorted()`, `RealmQuery.findAllSortedAsync()` `RealmQuery.distinct()` and `RealmQuery.distinctAsync()`. * Renamed `RealmQuery.distinctValues()` to `RealmQuery.distinct()` ### Enhancements From 1ae66530162b965e3b00588d02560c1cf530068f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 24 May 2018 13:18:03 +0200 Subject: [PATCH 1236/2110] Ignore tests involving root certs --- .../java/io/realm/TrustManagerCertificateValidationTests.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java index 4ed36faccc..81964c00fd 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java @@ -4,6 +4,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,6 +27,7 @@ public static void setUp() { // adb push /tools/sync_test_server/keys/android_test_certificate.crt /sdcard/ // then import the certificate from the device (Settings/Security/Install from storage) @Test + @Ignore("Root certificate have expired. Replace with newer one. https://github.com/realm/realm-java/issues/5970") public void sslVerifyCallback_certificateChainWithRootCAInstalledShouldValidate() { // simulating the following certificate chain // --- @@ -109,6 +111,7 @@ public void sslVerifyCallback_certificateChainWithRootCAInstalledShouldValidate( } @Test + @Ignore("Root certificate have expired. Replace with newer one. https://github.com/realm/realm-java/issues/5970") public void sslVerifyCallback_shouldVerifyHostname() { // simulating the following certificate chain // --- From d68d56b857836988d8d52bb7f0358aab8b916216 Mon Sep 17 00:00:00 2001 From: Emanuele Zattin Date: Thu, 24 May 2018 16:32:43 +0200 Subject: [PATCH 1237/2110] Push the data to our own influxdb instance (#5950) --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4a7876c51d..557e7f03f4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -202,7 +202,7 @@ def archiveRosLog(String id) { def sendMetrics(String metricName, String metricValue, Map tags) { def tagsString = getTagsString(tags) withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: '5b8ad2d9-61a4-43b5-b4df-b8ff6b1f16fa', passwordVariable: 'influx_pass', usernameVariable: 'influx_user']]) { - sh "curl -i -XPOST 'https://greatscott-pinheads-70.c.influxdb.com:8086/write?db=realm' --data-binary '${metricName},${tagsString} value=${metricValue}i' --user '${env.influx_user}:${env.influx_pass}'" + sh "curl -i -XPOST 'https://influxdb.realmlab.net/write?db=realm' --data-binary '${metricName},${tagsString} value=${metricValue}i' --user '${env.influx_user}:${env.influx_pass}'" } } From c274673c84d3349b4934ff744dc8479813795ec5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 24 May 2018 17:23:07 +0200 Subject: [PATCH 1238/2110] Improved performance when parsing field descriptions (#5952) --- CHANGELOG.md | 6 ++++++ .../realm/internal/fields/FieldDescriptor.java | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ab370eff..55ffbf9fe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.1.1 (YYYY-MM-DD) + +### Enhancements + +* Improved speed and allocations when parsing field descriptions in queries (#5547). + ## 5.1.0 (2018-04-25) ### Enhancements diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java index 6d3857bb88..a9f1b9cbe4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java @@ -15,12 +15,14 @@ */ package io.realm.internal.fields; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.regex.Pattern; import io.realm.RealmFieldType; import io.realm.internal.ColumnInfo; @@ -38,6 +40,9 @@ * */ public abstract class FieldDescriptor { + + private static final Pattern FIELD_SEPARATOR = Pattern.compile("\\."); + public interface SchemaProxy { boolean hasCache(); @@ -273,10 +278,17 @@ private List parseFieldDescription(String fieldDescription) { if (fieldDescription == null || fieldDescription.equals("")) { throw new IllegalArgumentException("Invalid query: field name is empty"); } - if (fieldDescription.endsWith(".")) { + + int lastDotIndex = fieldDescription.lastIndexOf("."); + if (lastDotIndex == fieldDescription.length() - 1) { throw new IllegalArgumentException("Invalid query: field name must not end with a period ('.')"); } - return Arrays.asList(fieldDescription.split("\\.")); + + if (lastDotIndex > -1) { + return Arrays.asList(FIELD_SEPARATOR.split(fieldDescription)); + } else { + return Collections.singletonList(fieldDescription); + } } private void verifyColumnType(String className, String columnName, RealmFieldType columnType, Set validTypes) { From 4cedeb961bab66ded4e9e9312ba1523a7c80e0ea Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 1 Jun 2018 11:51:37 +0200 Subject: [PATCH 1239/2110] Move creating a SyncConfiguration to SyncUser (#5975) --- CHANGELOG.md | 18 ++ .../io/realm/ObjectLevelPermissionsTest.java | 3 +- .../java/io/realm/SessionTests.java | 2 +- .../java/io/realm/SyncConfigurationTests.java | 127 ++++++------- .../java/io/realm/SyncManagerTests.java | 7 +- .../java/io/realm/SyncUserTests.java | 12 +- .../java/io/realm/SyncedRealmTests.java | 2 +- .../realm/TestSyncConfigurationFactory.java | 2 +- .../io/realm/OrderedCollectionChangeSet.java | 3 +- .../src/main/java/io/realm/Realm.java | 8 +- .../src/main/java/io/realm/RealmQuery.java | 11 +- .../java/io/realm/PermissionManager.java | 12 +- .../java/io/realm/SyncConfiguration.java | 169 ++++++++++-------- .../objectServer/java/io/realm/SyncUser.java | 76 +++++++- .../internal/SyncObjectServerFacade.java | 4 +- .../java/io/realm/BaseIntegrationTest.java | 2 +- .../java/io/realm/PermissionManagerTests.java | 8 +- .../java/io/realm/SSLConfigurationTests.java | 8 + .../java/io/realm/SyncSessionTests.java | 14 +- .../io/realm/SyncedRealmIntegrationTests.java | 24 ++- .../java/io/realm/objectserver/AuthTests.java | 8 +- .../EncryptedSynchronizedRealmTests.java | 4 + ...ObjectLevelPermissionIntegrationTests.java | 4 +- .../objectserver/ProcessCommitTests.java | 12 +- .../objectserver/ProgressListenerTests.java | 11 +- ...yncTests.java => QueryBasedSyncTests.java} | 11 +- version.txt | 2 +- 27 files changed, 350 insertions(+), 214 deletions(-) rename realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/{PartialSyncTests.java => QueryBasedSyncTests.java} (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ffbf9fe6..fb7f7f0c09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,27 @@ +## 5.2.0 (YYYY-MM-DD) + +The feature previously named Partial Sync is now called Query-Based Sync and is now the default mode when synchronizing Realms. +This has impacted a number of API's. See below for the details. + +### Deprecated + +* [ObjectServer] `SyncConfiguration.automatic()` has been removed in favour of `SyncUser.getDefaultConfiguration()`. +* [ObjectServer] `new SyncConfiguration.Builder(user, url)` has been deprecated in favour of `SyncUser.createConfiguration(url)`. NOTE: Creating configurations using `SyncUser` will default to using query-based Realms, while creating them using `new SyncConfiguration.Builder(user, url)` will default to fully synchronized Realms. +* [ObjectServer] With query-based sync being the default `SyncConfiguration.Builder.partialRealm()` has been deprecated. Use ``SyncConfiguration.Builder.fullSynchronization()` if you want full synchronisation instead. + +### Enhancements + +* [ObjectServer] Added `SyncUser.createConfiguration(url)`. Realms created this way are query-based Realms by default. +* [ObjectServer] Added `SyncUser.getDefaultConfiguration()`. + + ## 5.1.1 (YYYY-MM-DD) ### Enhancements * Improved speed and allocations when parsing field descriptions in queries (#5547). + ## 5.1.0 (2018-04-25) ### Enhancements diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java index 7c156bf79d..21471813c2 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java @@ -68,8 +68,7 @@ public static class TestModule { @Before public void setUp() { user = createTestUser(); - configuration = new SyncConfiguration.Builder(user, REALM_URI) - .partialRealm() + configuration = user.createConfiguration(REALM_URI) .modules(new TestModule()) .build(); realm = Realm.getInstance(configuration); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index c9caeaedc9..458807bab3 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -65,7 +65,7 @@ public class SessionTests { @Before public void setUp() { user = createTestUser(); - configuration = new SyncConfiguration.Builder(user, REALM_URI).addModule(new ObjectPermissionsModule()).build(); + configuration = user.createConfiguration(REALM_URI).addModule(new ObjectPermissionsModule()).build(); } @Test diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index bf05c006ab..ae0d52c770 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -20,6 +20,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.After; +import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -61,12 +62,17 @@ public class SyncConfigurationTests { @Rule public final ExpectedException thrown = ExpectedException.none(); + @Before + public void setUp() { + Realm.init(InstrumentationRegistry.getTargetContext()); + } + @After public void tearDown() { - for (SyncUser syncUser : SyncUser.all().values()) { - syncUser.logOut(); + UserStore userStore = SyncManager.getUserStore(); + for (SyncUser syncUser : userStore.allUsers()) { + userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); } - SyncManager.reset(); } @Test @@ -98,7 +104,7 @@ public void serverUrl_setsFolderAndFileName() { String expectedFolder = validUrl[1]; String expectedFileName = validUrl[2]; - SyncConfiguration config = new SyncConfiguration.Builder(user, serverUrl).build(); + SyncConfiguration config = user.createConfiguration(serverUrl).build(); assertEquals(new File(InstrumentationRegistry.getContext().getFilesDir(), expectedFolder), config.getRealmDirectory()); assertEquals(expectedFileName, config.getRealmFileName()); @@ -139,7 +145,7 @@ public void serverUrl_flexibleInput() { String serverUrlInput = (String) test[1]; String resolvedServerUrl = ((String) test[2]).replace("~", user.getIdentity()); - SyncConfiguration config = new SyncConfiguration.Builder(user, serverUrlInput).build(); + SyncConfiguration config = user.createConfiguration(serverUrlInput).build(); assertEquals(String.format("Input '%s' did not resolve correctly.", serverUrlInput), resolvedServerUrl, config.getServerUrl().toString()); @@ -165,7 +171,7 @@ public void serverUrl_invalidUrlThrows() { for (String invalidUrl : invalidUrls) { try { - new SyncConfiguration.Builder(createTestUser(), invalidUrl); + createTestUser().createConfiguration(invalidUrl); fail(invalidUrl + " should have failed."); } catch (IllegalArgumentException ignore) { } @@ -186,7 +192,7 @@ public void serverUrl_length() { SyncConfiguration.MAX_FILE_NAME_LENGTH, SyncConfiguration.MAX_FILE_NAME_LENGTH + 1, 1000}; for (int len : lengths) { - SyncConfiguration config = new SyncConfiguration.Builder(createTestUser(), makeServerUrl(len)).build(); + SyncConfiguration config = createTestUser().createConfiguration(makeServerUrl(len)).build(); assertTrue("Length: " + len, config.getRealmFileName().length() <= SyncConfiguration.MAX_FILE_NAME_LENGTH); assertTrue("Length: " + len, config.getPath().length() <= SyncConfiguration.MAX_FULL_PATH_LENGTH); } @@ -194,7 +200,7 @@ public void serverUrl_length() { @Test public void serverUrl_invalidChars() { - SyncConfiguration.Builder builder = new SyncConfiguration.Builder(createTestUser(), "realm://objectserver.realm.io/~/?"); + SyncConfiguration.Builder builder = createTestUser().createConfiguration("realm://objectserver.realm.io/~/?"); SyncConfiguration config = builder.build(); assertFalse(config.getRealmFileName().contains("?")); } @@ -208,14 +214,14 @@ public void serverUrl_port() { urlPort.put("realms://objectserver.realm.io:2443/~/default", 2443); for (String url : urlPort.keySet()) { - SyncConfiguration config = new SyncConfiguration.Builder(createTestUser(), url).build(); + SyncConfiguration config = createTestUser().createConfiguration(url).build(); assertEquals(urlPort.get(url).intValue(), config.getServerUrl().getPort()); } } @Test public void errorHandler() { - SyncConfiguration.Builder builder = new SyncConfiguration.Builder(createTestUser(), "realm://objectserver.realm.io/default"); + SyncConfiguration.Builder builder = createTestUser().createConfiguration("realm://objectserver.realm.io/default"); SyncSession.ErrorHandler errorHandler = new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { @@ -240,7 +246,7 @@ public void onError(SyncSession session, ObjectServerError error) { // Create configuration using the default handler SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + SyncConfiguration config = user.createConfiguration(url).build(); assertEquals(errorHandler, config.getErrorHandler()); SyncManager.setDefaultSessionErrorHandler(null); } @@ -250,7 +256,7 @@ public void onError(SyncSession session, ObjectServerError error) { public void errorHandler_nullThrows() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration.Builder builder = new SyncConfiguration.Builder(user, url); + SyncConfiguration.Builder builder = user.createConfiguration(url); try { builder.errorHandler(null); @@ -262,7 +268,7 @@ public void errorHandler_nullThrows() { public void equals() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url) + SyncConfiguration config = user.createConfiguration(url) .build(); assertTrue(config.equals(config)); } @@ -271,8 +277,8 @@ public void equals() { public void equals_same() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config1 = new SyncConfiguration.Builder(user, url).build(); - SyncConfiguration config2 = new SyncConfiguration.Builder(user, url).build(); + SyncConfiguration config1 = user.createConfiguration(url).build(); + SyncConfiguration config2 = user.createConfiguration(url).build(); assertTrue(config1.equals(config2)); } @@ -282,8 +288,8 @@ public void equals_not() { SyncUser user = createTestUser(); String url1 = "realm://objectserver.realm.io/default1"; String url2 = "realm://objectserver.realm.io/default2"; - SyncConfiguration config1 = new SyncConfiguration.Builder(user, url1).build(); - SyncConfiguration config2 = new SyncConfiguration.Builder(user, url2).build(); + SyncConfiguration config1 = user.createConfiguration(url1).build(); + SyncConfiguration config2 = user.createConfiguration(url2).build(); assertFalse(config1.equals(config2)); } @@ -291,7 +297,7 @@ public void equals_not() { public void hashCode_equal() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url) + SyncConfiguration config = user.createConfiguration(url) .build(); assertEquals(config.hashCode(), config.hashCode()); @@ -302,8 +308,8 @@ public void hashCode_notEquals() { SyncUser user = createTestUser(); String url1 = "realm://objectserver.realm.io/default1"; String url2 = "realm://objectserver.realm.io/default2"; - SyncConfiguration config1 = new SyncConfiguration.Builder(user, url1).build(); - SyncConfiguration config2 = new SyncConfiguration.Builder(user, url2).build(); + SyncConfiguration config1 = user.createConfiguration(url1).build(); + SyncConfiguration config2 = user.createConfiguration(url2).build(); assertNotEquals(config1.hashCode(), config2.hashCode()); } @@ -311,7 +317,7 @@ public void hashCode_notEquals() { public void get_syncSpecificValues() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + SyncConfiguration config = user.createConfiguration(url).build(); assertTrue(user.equals(config.getUser())); assertEquals("realm://objectserver.realm.io/default", config.getServerUrl().toString()); assertFalse(config.shouldDeleteRealmOnLogout()); @@ -322,7 +328,7 @@ public void get_syncSpecificValues() { public void encryption() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url) + SyncConfiguration config = user.createConfiguration(url) .encryptionKey(TestHelper.getRandomKey()) .build(); assertNotNull(config.getEncryptionKey()); @@ -333,7 +339,7 @@ public void encryption_invalid_null() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - new SyncConfiguration.Builder(user, url).encryptionKey(null); + user.createConfiguration(url).encryptionKey(null); } @Test(expected = IllegalArgumentException.class) @@ -341,14 +347,14 @@ public void encryption_invalid_wrong_length() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - new SyncConfiguration.Builder(user, url).encryptionKey(new byte[]{1, 2, 3}); + user.createConfiguration(url).encryptionKey(new byte[]{1, 2, 3}); } @Test(expected = IllegalArgumentException.class) public void directory_null() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - new SyncConfiguration.Builder(user, url).directory(null); + user.createConfiguration(url).directory(null); } @Test(expected = IllegalArgumentException.class) @@ -357,7 +363,7 @@ public void directory_writeProtectedDir() { String url = "realm://objectserver.realm.io/default"; File dir = new File("/"); - new SyncConfiguration.Builder(user, url).directory(dir); + user.createConfiguration(url).directory(dir); } @Test @@ -369,7 +375,7 @@ public void directory_dirIsAFile() throws IOException { File file = new File(dir, "dummyfile"); assertTrue(file.createNewFile()); thrown.expect(IllegalArgumentException.class); - new SyncConfiguration.Builder(user, url).directory(file); + user.createConfiguration(url).directory(file); file.delete(); // clean up } @@ -379,7 +385,7 @@ public void deleteOnLogout() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url) + SyncConfiguration config = user.createConfiguration(url) //.deleteRealmOnLogout() .build(); assertTrue(config.shouldDeleteRealmOnLogout()); @@ -420,7 +426,7 @@ public void execute(Realm realm) { public void defaultRxFactory() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + SyncConfiguration config = user.createConfiguration(url).build(); assertNotNull(config.getRxFactory()); } @@ -429,7 +435,7 @@ public void defaultRxFactory() { public void toString_nonEmpty() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + SyncConfiguration config = user.createConfiguration(url).build(); String configStr = config.toString(); assertTrue(configStr != null && !configStr.isEmpty()); @@ -440,7 +446,7 @@ public void toString_nonEmpty() { public void compact_NotAllowed() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); + SyncConfiguration config = user.createConfiguration(url).build(); Realm.compactRealm(config); } @@ -452,9 +458,13 @@ public void multipleUsersReferenceSameRealm() { SyncUser user1 = createNamedTestUser("user1"); SyncUser user2 = createNamedTestUser("user2"); String sharedUrl = "realm://ros.realm.io/42/default"; - SyncConfiguration config1 = new SyncConfiguration.Builder(user1, sharedUrl).modules(new StringOnlyModule()).build(); + SyncConfiguration config1 = user1.createConfiguration(sharedUrl) + .modules(new StringOnlyModule()) + .build(); Realm realm1 = Realm.getInstance(config1); - SyncConfiguration config2 = new SyncConfiguration.Builder(user2, sharedUrl).modules(new StringOnlyModule()).build(); + SyncConfiguration config2 = user2.createConfiguration(sharedUrl) + .modules(new StringOnlyModule()) + .build(); Realm realm2 = null; // Verify that two different configurations can be used for the same URL @@ -472,55 +482,22 @@ public void multipleUsersReferenceSameRealm() { } @Test - public void automatic_throwsIfNoUserIsLoggedIn() { - try { - SyncConfiguration.automatic(); - fail(); - } catch (IllegalStateException e) { - assertTrue(e.getMessage().startsWith("No user was logged in")); - } - } - - @Test - public void automatic_throwsIfMultipleUsersIsLoggedIn() { - SyncTestUtils.createTestUser(); - SyncTestUtils.createTestUser(); - try { - SyncConfiguration.automatic(); - fail(); - } catch (IllegalStateException e) { - assertEquals("Current user is not valid if more that one valid, logged-in user exists.", e.getMessage()); - } - } - - @Test - public void automaticWithUser_throwsIfNullOrInvalid() { - try { - //noinspection ConstantConditions - SyncConfiguration.automatic(null); - fail(); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().startsWith("Non-null 'user' required.")); - } - SyncUser user = SyncTestUtils.createTestUser(); + public void getDefaultConfiguration_throwsIfNotLoggedIn() { + SyncUser user = createTestUser(); user.logOut(); try { - SyncConfiguration.automatic(user); + user.getDefaultConfiguration(); fail(); - } catch (IllegalArgumentException e) { - assertEquals("User is no logger valid. Log the user in again.", e.getMessage()); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().startsWith("The default configuration can only be created for users that are logged in.")); } } @Test - public void automatic_isPartial() { + public void getDefaultConfiguration_isFullySynchronized() { SyncUser user = SyncTestUtils.createTestUser(); - - SyncConfiguration config = SyncConfiguration.automatic(); - assertTrue(config.isPartialRealm()); - - config = SyncConfiguration.automatic(user); - assertTrue(config.isPartialRealm()); + SyncConfiguration config = user.getDefaultConfiguration(); + assertFalse(config.isFullySynchronizedRealm()); } @Test @@ -547,7 +524,7 @@ public void automatic_convertsAuthUrl() { String realmUrl = (String) test[1]; SyncUser user = SyncTestUtils.createTestUser(authUrl); - SyncConfiguration config = SyncConfiguration.automatic(); + SyncConfiguration config = user.getDefaultConfiguration(); URI url = config.getServerUrl(); assertEquals(realmUrl, url.toString()); user.logOut(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 89c13dd672..2ec72220e6 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -83,7 +83,10 @@ public boolean isActive(String identity, String authenticationUrl) { @After public void tearDown() { UserFactory.logoutAllUsers(); - SyncManager.reset(); + UserStore userStore = SyncManager.getUserStore(); + for (SyncUser syncUser : userStore.allUsers()) { + userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); + } } @Test @@ -159,7 +162,7 @@ public void loggedOut(SyncUser user) { public void session() throws IOException { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = new SyncConfiguration.Builder(user, url) + SyncConfiguration config = user.createConfiguration(url) .modules(new StringOnlyModule()) .build(); // This will trigger the creation of the session diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 603eae46ae..136470a0bc 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -103,7 +103,10 @@ public static void initUserStore() { @Before public void setUp() { - SyncManager.reset(); + UserStore userStore = SyncManager.getUserStore(); + for (SyncUser syncUser : userStore.allUsers()) { + userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); + } } @After @@ -469,7 +472,7 @@ public void allSessions() { SyncUser user = createTestUser(); assertEquals(0, user.allSessions().size()); - SyncConfiguration configuration1 = new SyncConfiguration.Builder(user, url1).modules(new AllTypesModelModule()).build(); + SyncConfiguration configuration1 = user.createConfiguration(url1).modules(new AllTypesModelModule()).build(); Realm realm1 = Realm.getInstance(configuration1); List allSessions = user.allSessions(); assertEquals(1, allSessions.size()); @@ -478,7 +481,7 @@ public void allSessions() { assertEquals(user, session.getUser()); assertEquals(url1, session.getServerUrl().toString()); - SyncConfiguration configuration2 = new SyncConfiguration.Builder(user, url2).modules(new AllTypesModelModule()).build(); + SyncConfiguration configuration2 = user.createConfiguration(url2).modules(new AllTypesModelModule()).build(); Realm realm2 = Realm.getInstance(configuration2); allSessions = user.allSessions(); assertEquals(2, allSessions.size()); @@ -541,8 +544,7 @@ public void logoutUserShouldDeleteRealmAfterRestart() throws InterruptedExceptio Realm.init(InstrumentationRegistry.getTargetContext()); SyncUser user = createTestUser(); - SyncConfiguration syncConfiguration = new SyncConfiguration - .Builder(user, "realm://127.0.0.1:9080/~/tests") + SyncConfiguration syncConfiguration = user.createConfiguration("realm://127.0.0.1:9080/~/tests") .modules(new StringOnlyModule()) .build(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index 6c6cab9b26..93e5974d27 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -62,7 +62,6 @@ private Realm getNormalRealm() { private Realm getPartialRealm() { SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/fullsync") - .partialRealm() .build(); realm = Realm.getInstance(config); return realm; @@ -70,6 +69,7 @@ private Realm getPartialRealm() { private Realm getFullySyncRealm() { SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/fullsync") + .fullSynchronization() .build(); realm = Realm.getInstance(config); return realm; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java index 36d0625db0..938c29072f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java @@ -27,7 +27,7 @@ public class TestSyncConfigurationFactory extends TestRealmConfigurationFactory { public SyncConfiguration.Builder createSyncConfigurationBuilder(SyncUser user, String url) { - return new SyncConfiguration.Builder(user, url) + return user.createConfiguration(url) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .addModule(new ObjectPermissionsModule()) .directory(getRoot()); diff --git a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java index 97c1fcf9cd..54abab36e7 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java @@ -127,8 +127,7 @@ public enum State { * Returns {@code true} if the query result is considered "complete". For all local Realms, or * fully synchronized Realms, this method will always return {@code true}. *

            - * This method thus only makes sense for partially synchronized Realms (as defined by setting - * {@link SyncConfiguration.Builder#partialRealm()}. + * This method thus only makes sense for query-based synchronized Realms. *

            * For those Realms, data is only downloaded when queried which means that until the data is * downloaded, a local query might return a query result that would not have been possible on a diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 3bf2ba64fd..7ff6e42b79 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1590,7 +1590,7 @@ public void run() { * Deletes all objects of the specified class from the Realm. * * @param clazz the class which objects should be removed. - * @throws IllegalStateException if the corresponding Realm is a partially synchronized Realm, is + * @throws IllegalStateException if the corresponding Realm is a query-based synchronized Realm, is * closed or called from an incorrect thread. */ public void delete(Class clazz) { @@ -1728,7 +1728,7 @@ public static boolean compactRealm(RealmConfiguration configuration) { * @return a {@link RealmAsyncTask} representing a cancellable task. * @throws IllegalArgumentException if no {@code subscriptionName} or {@code callback} was provided. * @throws IllegalStateException if called on a non-looper thread. - * @throws UnsupportedOperationException if the Realm is not a partially synchronized Realm. + * @throws UnsupportedOperationException if the Realm is not a query-based synchronized Realm. */ @Beta public RealmAsyncTask unsubscribeAsync(String subscriptionName, Realm.UnsubscribeCallback callback) { @@ -1741,7 +1741,7 @@ public RealmAsyncTask unsubscribeAsync(String subscriptionName, Realm.Unsubscrib } sharedRealm.capabilities.checkCanDeliverNotification("This method is only available from a Looper thread."); if (!ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(configuration)) { - throw new UnsupportedOperationException("Realm is not a partially synchronized Realm: " + configuration.getPath()); + throw new UnsupportedOperationException("Realm is fully synchronized Realm. This method is only available when using query-based synchronization: " + configuration.getPath()); } return executeTransactionAsync(new Transaction() { @@ -1944,7 +1944,7 @@ interface OnError { } /** - * Interface used when canceling partial sync subscriptions. + * Interface used when canceling query-based sync subscriptions. * * @see #unsubscribeAsync(String, UnsubscribeCallback) */ diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 67ae7832ff..82f884decc 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1769,11 +1769,8 @@ public RealmResults findAll() { /** * Finds all objects that fulfill the query conditions. This method is only available from a Looper thread. *

            - * On partially synchronized Realms, defined by setting {@link SyncConfiguration.Builder#partialRealm()}, - * this method will also create an anonymous subscription that will download all server data matching - * the query. - *

            - * + * If the Realm is a Query-based synchronized Realms, this method will also create an anonymous subscription + * that will download all server data matching the query. * * @return immediately an empty {@link RealmResults}. Users need to register a listener * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. @@ -1797,14 +1794,14 @@ public RealmResults findAllAsync() { /** * Finds all objects that fulfill the query condition(s). This method is only available from a Looper thread. *

            - * This method is only available on partially synchronized Realms and will also create a named subscription + * This method is only available on query-based synchronized Realms and will also create a named subscription * that will synchronize all server data matching the query. Named subscriptions can be removed again by * calling {@code Realm.unsubscribe(subscriptionName}. * * @return immediately an empty {@link RealmResults}. Users need to register a listener * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. * @see io.realm.RealmResults - * @throws IllegalStateException If the Realm is a not a partially synchronized Realm. + * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm. */ public RealmResults findAllAsync(String subscriptionName) { realm.checkIfValid(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java index c9471a7df1..e8f088abd1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java @@ -178,8 +178,8 @@ public boolean isGlobalRealm() { private PermissionManager(SyncUser user) { this.user = user; threadId = Thread.currentThread().getId(); - managementRealmConfig = new SyncConfiguration.Builder( - user, getRealmUrl(RealmType.MANAGEMENT_REALM, user.getAuthenticationUrl())) + managementRealmConfig = user.createConfiguration(getRealmUrl(RealmType.MANAGEMENT_REALM, user.getAuthenticationUrl())) + .fullSynchronization() .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { @@ -192,8 +192,8 @@ public void onError(SyncSession session, ObjectServerError error) { .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); - permissionRealmConfig = new SyncConfiguration.Builder( - user, getRealmUrl(RealmType.PERMISSION_REALM, user.getAuthenticationUrl())) + permissionRealmConfig = user.createConfiguration(getRealmUrl(RealmType.PERMISSION_REALM, user.getAuthenticationUrl())) + .fullSynchronization() .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { @@ -209,8 +209,8 @@ public void onError(SyncSession session, ObjectServerError error) { .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); - defaultPermissionRealmConfig = new SyncConfiguration.Builder( - user, getRealmUrl(RealmType.DEFAULT_PERMISSION_REALM, user.getAuthenticationUrl())) + defaultPermissionRealmConfig = user.createConfiguration(getRealmUrl(RealmType.DEFAULT_PERMISSION_REALM, user.getAuthenticationUrl())) + .fullSynchronization() .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 7cbf711b85..9496aa33ea 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -22,7 +22,6 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -34,7 +33,7 @@ import javax.annotation.Nullable; -import io.realm.annotations.Beta; +import io.reactivex.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.OsRealmConfig; @@ -46,23 +45,41 @@ import io.realm.rx.RxObservableFactory; /** - * An {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm + * A {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm * Object Server. *

            * A valid {@link SyncUser} is required to create a {@link SyncConfiguration}. See {@link SyncCredentials} and - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)} for more information on - * how to get a user object. + * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)} for more information on how to get a user object. *

            * A minimal {@link SyncConfiguration} can be found below. *

              * {@code
            - * SyncConfiguration config = new SyncConfiguration.Builder(context)
            - *   .serverUrl("realm://objectserver.realm.io/~/default")
            - *   .user(myUser)
            - *   .build();
            + * SyncUser user = SyncUser.current();
            + * String url = "realm://myinstance.cloud.realm.io/default";
            + * SyncConfiguration config = new SyncConfiguration.Builder(user, url).build();
              * }
              * 
            * + * Synchronized Realms come in two forms: + *
              + *
            • + * Query-based synchronization: + * This is the default mode. The Realm will only synchronize data you have queried for. + * This means the Realm on the device is initially empty and will gradually fill up as + * you start to query for data. This is useful if the server side Realm is too large + * to fit on the device or contains data from multiple users. Data synchronized this way + * can also be removed from the device again without being deleted on the server. + *
            • + *
            • + * Full synchronization + * Enable this mode by setting {@link Builder#fullSynchronization()}. In this mode + * the entire Realm is synchronized in the background without having to query for + * data first. This means that data generally will be available quicker but should only + * be used if the server side Realm is small and doesn't contain data the device is not + * allowed to see. + *
            • + *
            + *

            * Synchronized Realms only support additive migrations which can be detected and performed automatically, so * the following builder options are not accessible compared to a normal Realm: * @@ -73,6 +90,9 @@ * * Synchronized Realms are created by using {@link Realm#getInstance(RealmConfiguration)} and * {@link Realm#getDefaultInstance()} like ordinary unsynchronized Realms. + * + * @see The docs for more + * information about the two types of synchronization. */ public class SyncConfiguration extends RealmConfiguration { @@ -183,6 +203,32 @@ public static RealmConfiguration forRecovery(String canonicalPath, @Nullable byt return forRecovery(canonicalPath, encryptionKey, schemaMediator); } + /** + * Returns a {@link RealmConfiguration} appropriate to open a read-only, non-synced Realm to recover any pending changes. + * This is useful when trying to open a backup/recovery Realm (after a client reset). + * + * Note: This will use the default Realm module (composed of all {@link RealmModel}), and + * assume no encryption should be used as well. + * + * @param canonicalPath the absolute path to the Realm file defined by this configuration. + * @return RealmConfiguration that can be used offline + */ + public static RealmConfiguration forRecovery(String canonicalPath) { + return forRecovery(canonicalPath, null); + } + + static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) { + return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true); + } + + static URI resolveServerUrl(URI serverUrl, String userIdentifier) { + try { + return new URI(serverUrl.toString().replace("/~/", "/" + userIdentifier + "/")); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Could not replace '/~/' with a valid user ID.", e); + } + } + /** * Creates an automatic default configuration based on the the currently logged in user. *

            @@ -192,14 +238,16 @@ public static RealmConfiguration forRecovery(String canonicalPath, @Nullable byt * @throws IllegalStateException if no user are logged in, or multiple users have. Only one should * be logged in when calling this method. * @return The constructed {@link SyncConfiguration}. + * @deprecated use {@link SyncUser#getDefaultConfiguration()} instead. */ + @Deprecated @Beta public static SyncConfiguration automatic() { SyncUser user = SyncUser.current(); if (user == null) { throw new IllegalStateException("No user was logged in."); } - return getDefaultConfig(user); + return user.getDefaultConfiguration(); } /** @@ -210,7 +258,9 @@ public static SyncConfiguration automatic() { * * @throws IllegalArgumentException if no user was provided or the user isn't valid. * @return The constructed {@link SyncConfiguration}. + * @deprecated use {@link SyncUser#getDefaultConfiguration()} instead. */ + @Deprecated @Beta public static SyncConfiguration automatic(SyncUser user) { if (user == null) { @@ -219,58 +269,7 @@ public static SyncConfiguration automatic(SyncUser user) { if (!user.isValid()) { throw new IllegalArgumentException("User is no logger valid. Log the user in again."); } - return getDefaultConfig(user); - } - - private static SyncConfiguration getDefaultConfig(SyncUser user) { - return new SyncConfiguration.Builder(user, createUrl(user)) - .partialRealm() - .build(); - } - - // Infer the URL to the default Realm based on the server used to login the user - private static String createUrl(SyncUser user) { - URL url = user.getAuthenticationUrl(); - String protocol = url.getProtocol(); - String host = url.getHost(); - int port = url.getPort(); - if (port != -1) { // port set - host += ":" + port; - } - - if (protocol.equalsIgnoreCase("https")) { - protocol = "realms"; - } else { - protocol = "realm"; - } - - return protocol + "://" + host + "/default"; - } - - /** - * Returns a {@link RealmConfiguration} appropriate to open a read-only, non-synced Realm to recover any pending changes. - * This is useful when trying to open a backup/recovery Realm (after a client reset). - * - * Note: This will use the default Realm module (composed of all {@link RealmModel}), and - * assume no encryption should be used as well. - * - * @param canonicalPath the absolute path to the Realm file defined by this configuration. - * @return RealmConfiguration that can be used offline - */ - public static RealmConfiguration forRecovery(String canonicalPath) { - return forRecovery(canonicalPath, null); - } - - static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) { - return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true); - } - - static URI resolveServerUrl(URI serverUrl, String userIdentifier) { - try { - return new URI(serverUrl.toString().replace("/~/", "/" + userIdentifier + "/")); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Could not replace '/~/' with a valid user ID.", e); - } + return user.getDefaultConfiguration(); } // Extract the full server path, minus the file name @@ -430,18 +429,28 @@ public OsRealmConfig.SyncSessionStopPolicy getSessionStopPolicy() { } /** - * Whether this configuration is for a partial synchronization Realm. + * Whether this configuration is for a query-based Realm. *

            - * Partial synchronization allows a synchronized Realm to be opened in such a way that + * Query-based synchronization allows a synchronized Realm to be opened in such a way that * only objects queried by the user are synchronized to the device. * - * @return {@code true} to open a partial synchronization Realm {@code false} otherwise. - * @see Builder#partialRealm() for more details. + * @return {@code true} to open a query-based Realm {@code false} otherwise. + * @deprecated use {@link #isFullySynchronizedRealm()} instead. */ + @Deprecated public boolean isPartialRealm() { return isPartial; } + /** + * Returns whether this configuration is for a fully synchronized Realm or not. + * + * @see Builder#fullSynchronization() for more details. + */ + public boolean isFullySynchronizedRealm() { + return !isPartial; + } + /** * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ @@ -477,9 +486,10 @@ public static final class Builder { @Nullable private String serverCertificateFilePath; private OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy = OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED; - private boolean isPartial = false; + private boolean isPartial = true; // Partial Synchronization is enabled by default /** - * Creates an instance of the Builder for the SyncConfiguration. + * Creates an instance of the Builder for the SyncConfiguration. This SyncConfiguration + * will be for a fully synchronized Realm. *

            * Opening a synchronized Realm requires a valid user and an unique URI that identifies that Realm. In URIs, * {@code /~/} can be used as a placeholder for a user ID in case the Realm should only be available to one @@ -504,9 +514,12 @@ public static final class Builder { * assume the file is located on the same server returned by {@link SyncUser#getAuthenticationUrl()}. * * @see SyncUser#isValid() + * @deprecated Use {@link SyncUser#createConfiguration(String)} instead. */ + @Deprecated public Builder(SyncUser user, String uri) { this(BaseRealm.applicationContext, user, uri); + fullSynchronization(); } Builder(Context context, SyncUser user, String url) { @@ -941,14 +954,30 @@ public SyncConfiguration.Builder readOnly() { } /** - * Setting this will open a partially synchronized Realm. + * Setting this will open a query-based Realm. + * * @see #isPartialRealm() + * @deprecated Use {@link SyncUser#createConfiguration(String)} instead. */ + @Deprecated public SyncConfiguration.Builder partialRealm() { this.isPartial = true; return this; } + /** + * Define this Realm as a fully synchronized Realm. + *

            + * Full synchronization, unlike the default query-based synchronization, will transparently + * synchronize the entire Realm without needing to query for the data. This option is + * useful if the serverside Realm is small and all the data in the Realm should be + * available to the user. + */ + public SyncConfiguration.Builder fullSynchronization() { + this.isPartial = false; + return this; + } + private String MD5(String in) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); @@ -1069,7 +1098,7 @@ public SyncConfiguration build() { } } - // If partial sync is enabled, also add support for Object Level Permissions + // If query based sync is enabled, also add support for Object Level Permissions if (isPartial) { addModule(new ObjectPermissionsModule()); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 08f553c743..8c55cd7138 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -19,6 +19,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.io.File; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -44,7 +45,6 @@ import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.LogoutResponse; import io.realm.internal.network.LookupUserIdResponse; -import io.realm.internal.network.UpdateAccountRequest; import io.realm.internal.network.UpdateAccountResponse; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; @@ -66,6 +66,7 @@ public class SyncUser { private final URL authenticationUrl; // maps all RealmConfiguration and accessToken, using this SyncUser. private final Map realms = new HashMap(); + private SyncConfiguration defaultConfiguration; SyncUser(Token refreshToken, URL authenticationUrl) { this.identity = refreshToken.identity(); @@ -212,6 +213,79 @@ public SyncUser run() throws ObjectServerError { }.start(); } + /** + * Opening a synchronized Realm requires a {@link SyncConfiguration}. This method creates a + * {@link SyncConfiguration.Builder} that can be used to create it by calling {@link SyncConfiguration.Builder#build()}. + *

            + * The default synchronization mode for this Realm is query-based synchronizaton, + * but see the {@link SyncConfiguration.Builder} class for more details on how to configure a Realm. + *

            + * A synchronized Realm is identified by an unique URI. In the URI, {@code /~/} can be used as a placeholder for + * a user ID in case the Realm should only be available to one user e.g., {@code "realm://objectserver.realm.io/~/default"}. + *

            + * The URL cannot end with {@code .realm}, {@code .realm.lock} or {@code .realm.management}. + *

            + * The {@code /~/} will automatically be replaced with the user ID when creating the {@link SyncConfiguration}. + *

            + * Moreover, the URI defines the local location on disk. The location of a synchronized Realm file is + * {@code /data/data//files/realm-object-server//}, but this behavior + * can be overwritten using {@link SyncConfiguration.Builder#name(String)} and {@link SyncConfiguration.Builder#directory(File)}. + *

            + * Many Android devices are using FAT32 file systems. FAT32 file systems have a limitation that + * file names cannot be longer than 255 characters. Moreover, the entire URI should not exceed 256 characters. + * If the file name and underlying path are too long to handle for FAT32, a shorter unique name will be generated. + * See also @{link https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx}. + * + * @param uri URI identifying the Realm. If only a path like {@code /~/default} is given, the configuration will + * assume the file is located on the same server returned by {@link #getAuthenticationUrl()}. + * + * @throws IllegalStateException if the user isn't valid. See {@link #isValid()}. + */ + public SyncConfiguration.Builder createConfiguration(String uri) { + if (!isValid()) { + throw new IllegalStateException("Configurations can only be created from valid users"); + } + return new SyncConfiguration.Builder(this, uri).partialRealm(); + } + + /** + * Returns the default configuration for this user. The default configuration points to the + * default query-based Realm on the server the user authenticated against. + * + * @return the default configuration for this user. + * @throws IllegalStateException if the user isn't valid. See {@link #isValid()}. + */ + public SyncConfiguration getDefaultConfiguration() { + if (!isValid()) { + throw new IllegalStateException("The default configuration can only be created for users that are logged in."); + } + if (defaultConfiguration == null) { + defaultConfiguration = new SyncConfiguration.Builder(this, createUrl(this)) + .partialRealm() + .build(); + } + return defaultConfiguration; + } + + // Infer the URL to the default Realm based on the server used to login the user + private static String createUrl(SyncUser user) { + URL url = user.getAuthenticationUrl(); + String protocol = url.getProtocol(); + String host = url.getHost(); + int port = url.getPort(); + if (port != -1) { // port set + host += ":" + port; + } + + if (protocol.equalsIgnoreCase("https")) { + protocol = "realms"; + } else { + protocol = "realm"; + } + + return protocol + "://" + host + "/default"; + } + /** * Log a user out, destroying their server state, unregistering them from the SDK, and removing * any synced Realms associated with them, from on-disk storage on next app launch (or directly diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index b0ac440bbf..fc34b1df5d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -94,7 +94,7 @@ public Object[] getUserAndServerUrl(RealmConfiguration config) { String syncRealmAuthUrl = user.getAuthenticationUrl().toString(); String rosSerializedUser = user.toJson(); byte sessionStopPolicy = syncConfig.getSessionStopPolicy().getNativeValue(); - return new Object[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosSerializedUser, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath(), sessionStopPolicy, syncConfig.isPartialRealm()}; + return new Object[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosSerializedUser, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath(), sessionStopPolicy, !syncConfig.isFullySynchronizedRealm()}; } else { return new Object[8]; } @@ -180,7 +180,7 @@ public boolean wasDownloadInterrupted(Throwable throwable) { public boolean isPartialRealm(RealmConfiguration configuration) { if (configuration instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) configuration; - return syncConfig.isPartialRealm(); + return !syncConfig.isFullySynchronizedRealm(); } return false; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 160ac6c8e3..90c95223cd 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -151,7 +151,7 @@ protected static class ConfigurationWrapper { } public SyncConfiguration.Builder createSyncConfigurationBuilder(SyncUser user, String url) { - return new SyncConfiguration.Builder(user, url) + return user.createConfiguration(url) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .modules(Realm.getDefaultModule(), new ObjectPermissionsModule()) .directory(looperThread.getRoot()); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 37dc8a475f..ce297cdd15 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -118,7 +118,7 @@ public void onSuccess(RealmResults permissions) { assertInitialPermissions(permissions); // Create new Realm, which should create a new Permission entry - SyncConfiguration config2 = new SyncConfiguration.Builder(user, Constants.USER_REALM_2) + SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM_2) .schema(AllJavaTypes.class) .errorHandler(new SyncSession.ErrorHandler() { @Override @@ -166,7 +166,7 @@ public void onSuccess(RealmResults permissions) { assertInitialPermissions(permissions); for (int i = 0; i < TEST_SIZE; i++) { - SyncConfiguration configNew = new SyncConfiguration.Builder(user, "realm://" + Constants.HOST + "/~/test" + i) + SyncConfiguration configNew = user.createConfiguration("realm://" + Constants.HOST + "/~/test" + i) .schema(AllJavaTypes.class) .build(); Realm newRealm = Realm.getInstance(configNew); @@ -776,7 +776,7 @@ public void applyPermissions_usersWithNoExistingPermissions() { public void onSuccess() { // Default permissions are not recorded in the __permission Realm for user2 // Only way to check is by opening the Realm. - SyncConfiguration config = new SyncConfiguration.Builder(user2, url) + SyncConfiguration config = user2.createConfiguration(url) .schema(AllJavaTypes.class) .waitForInitialRemoteData() .errorHandler(new SyncSession.ErrorHandler() { @@ -1184,7 +1184,7 @@ public void run() { */ private String createRemoteRealm(SyncUser user, String realmName) { String url = Constants.AUTH_SERVER_URL + "~/" + realmName; - SyncConfiguration config = new SyncConfiguration.Builder(user, url) + SyncConfiguration config = user.createConfiguration(url) .name(realmName) .schema(AllJavaTypes.class) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index a899730c7e..08c58ce930 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -58,6 +58,7 @@ public void trustedRootCA() throws InterruptedException { // 1. Copy a valid Realm to the server //noinspection unchecked final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() .schema(StringOnly.class) .build(); Realm realm = Realm.getInstance(syncConfig); @@ -76,6 +77,7 @@ public void trustedRootCA() throws InterruptedException { user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .fullSynchronization() .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() @@ -103,6 +105,7 @@ public void withoutSSLVerification() throws InterruptedException { // 1. Copy a valid Realm to the server //noinspection unchecked final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() .schema(StringOnly.class) .build(); Realm realm = Realm.getInstance(syncConfig); @@ -121,6 +124,7 @@ public void withoutSSLVerification() throws InterruptedException { user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .fullSynchronization() .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() @@ -238,6 +242,7 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt // 1. Copy a valid Realm to the server using ssl_verify_path option //noinspection unchecked final SyncConfiguration syncConfigWithCertificate = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .fullSynchronization() .schema(StringOnly.class) .trustedRootCA("trusted_ca.pem") .build(); @@ -257,6 +262,7 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigDisableSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) + .fullSynchronization() .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() @@ -288,6 +294,7 @@ public void sslVerifyCallback_isUsed() throws InterruptedException { // 1. Copy a valid Realm to the server using ssl_verify_path option //noinspection unchecked final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() .schema(StringOnly.class) .build(); Realm realm = Realm.getInstance(syncConfig); @@ -307,6 +314,7 @@ public void sslVerifyCallback_isUsed() throws InterruptedException { //noinspection unchecked SyncConfiguration syncConfigSecure = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") + .fullSynchronization() .schema(StringOnly.class) .waitForInitialRemoteData() .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 6c73bcd8a2..7ccccc9e46 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -79,6 +79,7 @@ public void getState_loggedOut() { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .fullSynchronization() .build(); Realm realm = Realm.getInstance(syncConfiguration); @@ -98,9 +99,11 @@ public void uploadDownloadAllChanges() throws InterruptedException { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); SyncConfiguration userConfig = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .fullSynchronization() .build(); SyncConfiguration adminConfig = configFactory .createSyncConfigurationBuilder(adminUser, userConfig.getServerUrl().toString()) + .fullSynchronization() .build(); Realm userRealm = Realm.getInstance(userConfig); @@ -123,9 +126,11 @@ public void interruptWaits() throws InterruptedException { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); final SyncConfiguration userConfig = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .fullSynchronization() .build(); final SyncConfiguration adminConfig = configFactory .createSyncConfigurationBuilder(adminUser, userConfig.getServerUrl().toString()) + .fullSynchronization() .build(); Thread t = new Thread(new Runnable() { @@ -227,13 +232,14 @@ public void logout_sameSyncUserMultipleSessions() { // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. @Test - public void logBackResumeUpload() throws InterruptedException, NoSuchFieldException, IllegalAccessException { + public void logBackResumeUpload() throws InterruptedException { final String uniqueName = UUID.randomUUID().toString(); SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); final SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .build(); @@ -275,6 +281,7 @@ public void run() { SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) .modules(new StringOnlyModule()) + .fullSynchronization() .waitForInitialRemoteData() .build(); final Realm adminRealm = Realm.getInstance(adminConfig); @@ -322,6 +329,7 @@ public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { final SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .fullSynchronization() .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED) .modules(new StringOnlyModule()) .build(); @@ -348,6 +356,7 @@ public void run() { SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(admin, syncConfiguration.getServerUrl().toString()) + .fullSynchronization() .modules(new StringOnlyModule()) .build(); final Realm adminRealm = Realm.getInstance(adminConfig); @@ -383,6 +392,7 @@ public void downloadChangesWhenRealmOutOfScope() throws InterruptedException { final SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .fullSynchronization() .modules(new StringOnlyModule()) .build(); Realm realm = Realm.getInstance(syncConfiguration); @@ -417,6 +427,7 @@ public void run() { SyncUser adminUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) + .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .build(); @@ -460,6 +471,7 @@ public void clientReset_manualTriggerAllowSessionToRestart() { final AtomicReference configRef = new AtomicReference<>(null); final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .directory(looperThread.getRoot()) + .fullSynchronization() .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 24cab081af..d5a4e520c2 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -50,15 +50,16 @@ @RunWith(AndroidJUnit4.class) public class SyncedRealmIntegrationTests extends StandardIntegrationTest { - @Test + @RunTestInLooperThread public void loginLogoutResumeSyncing() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) .schema(StringOnly.class) + .fullSynchronization() .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); @@ -83,7 +84,8 @@ public void loginLogoutResumeSyncing() throws InterruptedException { } user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); - SyncConfiguration config2 = new SyncConfiguration.Builder(user, Constants.USER_REALM) + SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM) + .fullSynchronization() .schema(StringOnly.class) .build(); @@ -92,13 +94,15 @@ public void loginLogoutResumeSyncing() throws InterruptedException { realm2.refresh(); assertEquals(1, realm2.where(StringOnly.class).count()); realm2.close(); + looperThread.testComplete(); } @Test @UiThreadTest public void waitForInitialRemoteData_mainThreadThrows() { final SyncUser user = SyncTestUtils.createTestUser(Constants.AUTH_URL); - SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) + .fullSynchronization() .waitForInitialRemoteData() .build(); @@ -124,6 +128,7 @@ public void waitForInitialRemoteData() throws InterruptedException { // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() .schema(StringOnly.class) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); @@ -143,8 +148,9 @@ public void execute(Realm realm) { // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) .name("newRealm") + .fullSynchronization() .schema(StringOnly.class) .waitForInitialRemoteData() .build(); @@ -174,7 +180,7 @@ public void execute(Realm realm) { public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) .waitForInitialRemoteData() .build(); @@ -211,7 +217,7 @@ public void run() { public void waitForInitialData_resilientInCaseOfRetriesAsync() { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM) + final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .directory(configurationFactory.getRoot()) .waitForInitialRemoteData() @@ -244,6 +250,7 @@ public void waitForInitialRemoteData_readOnlyTrue() throws InterruptedException // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() .schema(StringOnly.class) .build(); Realm realm = Realm.getInstance(configOld); @@ -264,6 +271,7 @@ public void execute(Realm realm) { user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .name("newRealm") + .fullSynchronization() .waitForInitialRemoteData() .readOnly() .schema(StringOnly.class) @@ -326,7 +334,7 @@ public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { public void defaultRealm() throws InterruptedException { SyncCredentials credentials = SyncCredentials.nickname("test", false); SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncConfiguration config = SyncConfiguration.automatic(); + SyncConfiguration config = user.getDefaultConfiguration(); Realm realm = Realm.getInstance(config); SyncManager.getSession(config).downloadAllServerChanges(); realm.refresh(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 26c68b52fa..61f6ea1d15 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -399,13 +399,13 @@ public void cachedInstanceShouldNotThrowIfRefreshTokenExpires() throws Interrupt SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); final SyncUser user = spy(SyncUser.logIn(credentials, Constants.AUTH_URL)); - when(user.isValid()).thenReturn(true, false); + when(user.isValid()).thenReturn(true, true, false); final RealmConfiguration configuration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); Realm realm = Realm.getInstance(configuration); assertFalse(user.isValid()); - verify(user, times(2)).isValid(); + verify(user, times(3)).isValid(); final CountDownLatch backgroundThread = new CountDownLatch(1); // Should not throw when using an expired refresh_token form a different thread @@ -446,7 +446,7 @@ public void buildingSyncConfigurationShouldThrowIfInvalidUser() { // We should not be able to build a configuration with an invalid/logged out user configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); fail("Invalid user, it should not be possible to create a SyncConfiguration"); - } catch (IllegalArgumentException expected) { + } catch (IllegalStateException expected) { // User not authenticated or authentication expired. } @@ -454,7 +454,7 @@ public void buildingSyncConfigurationShouldThrowIfInvalidUser() { // We should not be able to build a configuration with an invalid/logged out user configurationFactory.createSyncConfigurationBuilder(currentUser, Constants.USER_REALM).build(); fail("Invalid currentUser, it should not be possible to create a SyncConfiguration"); - } catch (IllegalArgumentException expected) { + } catch (IllegalStateException expected) { // User not authenticated or authentication expired. } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 7c8b64a2f7..c0e1ae5cea 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -48,6 +48,7 @@ public void setEncryptionKey_canReOpenRealmWithoutKey() { final byte[] randomKey = TestHelper.getRandomKey(); SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .errorHandler(new SyncSession.ErrorHandler() { @@ -75,6 +76,7 @@ public void onError(SyncSession session, ObjectServerError error) { // fail user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); SyncConfiguration configWithoutEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() .name("newName") .modules(new StringOnlyModule()) .waitForInitialRemoteData() @@ -165,6 +167,7 @@ public void setEncryptionKey_differentClientsWithDifferentKeys() throws Interrup final byte[] randomKey = TestHelper.getRandomKey(); SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .errorHandler(new SyncSession.ErrorHandler() { @@ -195,6 +198,7 @@ public void onError(SyncSession session, ObjectServerError error) { final byte[] adminRandomKey = TestHelper.getRandomKey(); SyncConfiguration adminConfigWithEncryption = configurationFactory.createSyncConfigurationBuilder(adminUser, configWithEncryption.getServerUrl().toString()) + .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .errorHandler(new SyncSession.ErrorHandler() { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java index 29c9c89cbe..437957706f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java @@ -72,7 +72,6 @@ public void getPrivileges_serverDefaults() throws InterruptedException { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM) .modules(schemaModule) - .partialRealm() .build(); Realm realm = Realm.getInstance(syncConfig); @@ -119,7 +118,6 @@ public void restrictAccessToOwner() throws InterruptedException { SyncConfiguration user1SyncConfig = configurationFactory .createSyncConfigurationBuilder(user1, Constants.DEFAULT_REALM) .modules(schemaModules) - .partialRealm() .build(); Realm user1Realm = Realm.getInstance(user1SyncConfig); user1Realm.beginTransaction(); @@ -149,6 +147,7 @@ public void restrictAccessToOwner() throws InterruptedException { // Connect with admin user and verify that user1 object is visible (non-partial Realm) SyncUser adminUser = UserFactory.createNicknameUser(Constants.AUTH_URL, "admin2", true); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, Constants.DEFAULT_REALM) + .fullSynchronization() .modules(schemaModules) .waitForInitialRemoteData() .build(); @@ -166,7 +165,6 @@ public void restrictAccessToOwner() throws InterruptedException { SyncUser user2 = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncConfiguration syncConfig2 = configurationFactory.createSyncConfigurationBuilder(user2, Constants.DEFAULT_REALM) .modules(schemaModules) - .partialRealm() .build(); Realm user2Realm = Realm.getInstance(syncConfig2); looperThread.closeAfterTest(user2Realm); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 214af8c97d..e20642aa6c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -73,7 +73,8 @@ protected void run() { user = UserFactory.getInstance().loginWithDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + final SyncConfiguration syncConfig = user.createConfiguration(realmUrl) + .fullSynchronization() .modules(new ProcessCommitTestsModule()) .directory(getService().getRoot()) .build(); @@ -124,7 +125,8 @@ public void expectSimpleCommit() { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user,realmUrl) + final SyncConfiguration syncConfig = user.createConfiguration(realmUrl) + .fullSynchronization() .modules(new ProcessCommitTestsModule()) .directory(looperThread.getRoot()) .build(); @@ -157,7 +159,8 @@ protected void run() { user = UserFactory.getInstance().loginWithDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, realmUrl) + final SyncConfiguration syncConfig = user.createConfiguration(realmUrl) + .fullSynchronization() .modules(new ProcessCommitTestsModule()) .directory(getService().getRoot()) .name(UUID.randomUUID().toString() + ".realm") @@ -204,7 +207,8 @@ public void expectALot() throws Throwable { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; - final SyncConfiguration syncConfig = new SyncConfiguration.Builder(user,realmUrl) + final SyncConfiguration syncConfig = user.createConfiguration(realmUrl) + .fullSynchronization() .modules(new ProcessCommitTestsModule()) .directory(looperThread.getRoot()) .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index 5efddd5eb7..228a5f08e1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -61,7 +61,9 @@ public class ProgressListenerTests extends StandardIntegrationTest { @Nonnull private SyncConfiguration createSyncConfig() { SyncUser user = UserFactory.createAdminUser(Constants.AUTH_URL); - return configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL).build(); + return configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .fullSynchronization() + .build(); } private void writeSampleData(Realm realm) { @@ -135,11 +137,14 @@ public void downloadProgressListener_changesOnly() { final CountDownLatch allChangesDownloaded = new CountDownLatch(1); SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncConfiguration userWithDataConfig = configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM) + .fullSynchronization() .build(); URI serverUrl = createRemoteData(userWithDataConfig); SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()).build(); + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()) + .fullSynchronization() + .build(); Realm realm = Realm.getInstance(config); SyncSession session = SyncManager.getSession(config); session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { @@ -164,6 +169,7 @@ public void downloadProgressListener_indefinitely() throws InterruptedException final SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); final SyncConfiguration userWithDataConfig = configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM) .name("remote") + .fullSynchronization() .build(); URI serverUrl = createRemoteData(userWithDataConfig); @@ -182,6 +188,7 @@ public void run() { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); final SyncConfiguration adminConfig = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()) .name("local") + .fullSynchronization() .build(); Realm adminRealm = Realm.getInstance(adminConfig); SyncSession session = SyncManager.getSession(adminConfig); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java similarity index 97% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java rename to realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java index accd479d25..6865121afa 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/PartialSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java @@ -33,7 +33,7 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) -public class PartialSyncTests extends StandardIntegrationTest { +public class QueryBasedSyncTests extends StandardIntegrationTest { private static final int TEST_SIZE = 10; @@ -42,7 +42,6 @@ public class PartialSyncTests extends StandardIntegrationTest { public void invalidQuery() { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .partialRealm() .build(); final Realm realm = Realm.getInstance(partialSyncConfig); looperThread.closeAfterTest(realm); @@ -68,7 +67,6 @@ public void invalidQuery() { public void listQueries_doNotCreateSubscriptions() { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .partialRealm() .build(); final DynamicRealm dRealm = DynamicRealm.getInstance(partialSyncConfig); @@ -159,6 +157,7 @@ public void namedSubscription() throws InterruptedException { public void partialSync_namedSubscriptionThrowsOnNonPartialRealms() { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); final SyncConfiguration fullSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .fullSynchronization() .name("fullySynchronizedRealm") .build(); @@ -248,7 +247,7 @@ public void onError(String subscriptionName, Throwable error) { @Test @RunTestInLooperThread - public void unsubscribeAsync_nonExistingIdThrows() throws InterruptedException { + public void unsubscribeAsync_nonExistingIdThrows() { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); Realm realm = getPartialRealm(user); looperThread.closeAfterTest(realm); @@ -271,7 +270,7 @@ public void onError(String subscriptionName, Throwable error) { @Test @RunTestInLooperThread - public void clearTable() throws InterruptedException { + public void clearTable() { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); Realm realm = getPartialRealm(user); looperThread.closeAfterTest(realm); @@ -301,7 +300,6 @@ private Realm getPartialRealm(SyncUser user) { final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .name("partialSync") .modules(new PartialSyncModule()) - .partialRealm() .build(); return Realm.getInstance(partialSyncConfig); } @@ -309,7 +307,6 @@ private Realm getPartialRealm(SyncUser user) { private void createServerData(SyncUser user, String url) throws InterruptedException { final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, url) .waitForInitialRemoteData() - .partialRealm() .modules(new PartialSyncModule()) .build(); diff --git a/version.txt b/version.txt index 6555596f93..92baa8632a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.2.0-SNAPSHOT \ No newline at end of file +5.2.0-SNAPSHOT From 539054e7923f7efda693733344a5cf436d377b0e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Jun 2018 11:45:06 +0200 Subject: [PATCH 1240/2110] Enable incremental builds in the RealmTransformer (#5925) --- .gitignore | 1 + CHANGELOG.md | 9 + .../build.gradle | 2 +- realm-transformer/build.gradle | 12 +- .../realm/transformer/BytecodeModifier.groovy | 150 ---------- .../io/realm/transformer/GroovyUtil.groovy | 50 ++++ .../realm/transformer/RealmTransformer.groovy | 273 ------------------ .../io/realm/transformer/ByteCodeModifier.kt | 194 +++++++++++++ .../io/realm/transformer/ManagedClassPool.kt} | 31 +- .../io/realm/transformer/RealmTransformer.kt | 164 +++++++++++ .../kotlin/io/realm/transformer/Stopwatch.kt | 63 ++++ .../realm/transformer/build/BuildTemplate.kt | 168 +++++++++++ .../io/realm/transformer/build/FullBuild.kt | 147 ++++++++++ .../transformer/build/IncrementalBuild.kt | 153 ++++++++++ .../io/realm/transformer/ext/CtClassExt.kt | 67 +++++ .../transformer/BytecodeModifierTest.groovy | 4 +- realm/build.gradle | 2 +- realm/kotlin-extensions/build.gradle | 2 +- .../io/realm/processor/ClassMetaData.java | 11 +- .../processor/DefaultModuleGenerator.java | 3 +- .../io/realm/processor/ModuleMetaData.java | 15 +- realm/realm-library/build.gradle | 4 +- .../io/realm/internal/sync/BaseModule.java | 25 ++ 23 files changed, 1089 insertions(+), 461 deletions(-) delete mode 100644 realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy create mode 100644 realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy delete mode 100644 realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy create mode 100644 realm-transformer/src/main/kotlin/io/realm/transformer/ByteCodeModifier.kt rename realm-transformer/src/main/{groovy/io/realm/transformer/ManagedClassPool.groovy => kotlin/io/realm/transformer/ManagedClassPool.kt} (77%) create mode 100644 realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt create mode 100644 realm-transformer/src/main/kotlin/io/realm/transformer/Stopwatch.kt create mode 100644 realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt create mode 100644 realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt create mode 100644 realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt create mode 100644 realm-transformer/src/main/kotlin/io/realm/transformer/ext/CtClassExt.kt create mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/BaseModule.java diff --git a/.gitignore b/.gitignore index 7d38506f03..483d1a7ced 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Gradle build artifacts build realm/build +!realm-transformer/src/main/kotlin/io/realm/transformer/build # Gradle cache .gradle diff --git a/CHANGELOG.md b/CHANGELOG.md index fb7f7f0c09..4d068433a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,15 @@ This has impacted a number of API's. See below for the details. * [ObjectServer] Added `SyncUser.createConfiguration(url)`. Realms created this way are query-based Realms by default. * [ObjectServer] Added `SyncUser.getDefaultConfiguration()`. +* The Realm bytecode transformer now supports incremental builds (#3034). + +### Bug Fixes + +* Having files that ends with `RealmProxy` will no longer break the Realm Transformer (#3709). + +### Internal + +* Module mediator classes being generated now produces a stable output enabling better support for incremental builds (#3034). ## 5.1.1 (YYYY-MM-DD) diff --git a/examples/architectureComponentsExample/build.gradle b/examples/architectureComponentsExample/build.gradle index 5371c339cf..d44c3d0292 100644 --- a/examples/architectureComponentsExample/build.gradle +++ b/examples/architectureComponentsExample/build.gradle @@ -32,7 +32,7 @@ android { signingConfig signingConfigs.debug } debug { - minifyEnabled true + minifyEnabled false } } } diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index eb4789361d..9026350842 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -1,4 +1,5 @@ buildscript { + ext.kotlin_version = '1.2.40' repositories { google() jcenter() @@ -6,6 +7,7 @@ buildscript { dependencies { classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.2' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -17,6 +19,7 @@ allprojects { } } +apply plugin: 'kotlin' apply plugin: 'groovy' apply plugin: 'java' apply plugin: 'maven' @@ -51,7 +54,7 @@ sourceSets { main { compileClasspath += configurations.provided java { - srcDir 'build/generated-src/main/java' + srcDirs += ['build/generated-src/main/java', 'src/main/kotlin'] } } } @@ -60,8 +63,9 @@ dependencies { compile localGroovy() compile gradleApi() compile "io.realm:realm-annotations:${version}" - compileOnly 'com.android.tools.build:gradle:3.1.0-alpha06' + compileOnly 'com.android.tools.build:gradle:3.1.1' compile 'org.javassist:javassist:3.21.0-GA' + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { exclude module: 'groovy-all' @@ -79,6 +83,10 @@ task generateVersionClass(type: Copy) { } compileJava.dependsOn generateVersionClass +compileGroovy.dependsOn = compileGroovy.taskDependencies.values - 'compileJava' +compileKotlin.dependsOn compileGroovy +compileKotlin.classpath += files(compileGroovy.destinationDir) +classes.dependsOn compileKotlin def commonPom = { licenses { diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy deleted file mode 100644 index c00055369d..0000000000 --- a/realm-transformer/src/main/groovy/io/realm/transformer/BytecodeModifier.groovy +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.transformer - -import io.realm.annotations.Ignore -import javassist.* -import javassist.expr.ExprEditor -import javassist.expr.FieldAccess -import org.slf4j.Logger -import org.slf4j.LoggerFactory -/** - * This class encapsulates the bytecode manipulation code needed to transform model classes - * and the classes using them. - */ -class BytecodeModifier { - - private static final Logger logger = LoggerFactory.getLogger('realm-logger') - - static boolean isModelField(CtField field) { - return !field.hasAnnotation(Ignore.class) && !Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()) - } - - /** - * Adds Realm specific accessors to a model class. - * All the declared fields will be associated with a getter and a setter. - * - * @param clazz the CtClass to add accessors to. - */ - public static void addRealmAccessors(CtClass clazz) { - logger.debug " Realm: Adding accessors to ${clazz.simpleName}" - def methods = clazz.getDeclaredMethods()*.name - clazz.declaredFields.each { CtField field -> - if (isModelField(field)) { - if (!methods.contains("realmGet\$${field.name}".toString())) { - clazz.addMethod(CtNewMethod.getter("realmGet\$${field.name}", field)) - } - if (!methods.contains("realmSet\$${field.name}".toString())) { - clazz.addMethod(CtNewMethod.setter("realmSet\$${field.name}", field)) - } - } - } - } - - /** - * Modifies a class replacing field accesses with the appropriate Realm accessors. - * - * @param clazz The CtClass to modify - * @param managedFields List of fields whose access should be replaced - */ - public static void useRealmAccessors(CtClass clazz, List managedFields) { - clazz.getDeclaredBehaviors().each { behavior -> - logger.debug " Behavior: ${behavior.name}" - if ( - ( - behavior instanceof CtMethod && - !behavior.name.startsWith('realmGet$') && - !behavior.name.startsWith('realmSet$') - ) || ( - behavior instanceof CtConstructor - ) - ) { - behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior)) - } - } - } - - /** - * Modifies a class adding its RealmProxy interface. - * - * @param clazz The CtClass to modify - * @param classPool the Javassist class pool - */ - public static void addRealmProxyInterface(CtClass clazz, ClassPool classPool) { - def proxyInterface = classPool.get("io.realm.${clazz.getName().replace(".", "_")}RealmProxyInterface") - clazz.addInterface(proxyInterface) - } - - public static void callInjectObjectContextFromConstructors(CtClass clazz) { - clazz.getConstructors().each { - it.insertBeforeBody('if ($0 instanceof io.realm.internal.RealmObjectProxy) {' + - ' ((io.realm.internal.RealmObjectProxy) $0).realm$injectObjectContext();' + - ' }') - } - } - - /** - * This class goes through all the field access behaviours of a class and replaces field accesses with - * the appropriate accessor. - */ - private static class FieldAccessToAccessorConverter extends ExprEditor { - final List managedFields - final CtClass ctClass - final CtBehavior behavior - - FieldAccessToAccessorConverter(List managedFields, - CtClass ctClass, - CtBehavior behavior) { - this.managedFields = managedFields - this.ctClass = ctClass - this.behavior = behavior - } - - @Override - void edit(FieldAccess fieldAccess) throws CannotCompileException { - logger.debug " Field being accessed: ${fieldAccess.className}.${fieldAccess.fieldName}" - def isRealmFieldAccess = managedFields.find { - fieldAccess.className.equals(it.declaringClass.name) && fieldAccess.fieldName.equals(it.name) - } - if (isRealmFieldAccess != null) { - logger.debug " Realm: Manipulating ${ctClass.simpleName}.${behavior.name}(): ${fieldAccess.fieldName}" - logger.debug " Methods: ${ctClass.declaredMethods}" - def fieldName = fieldAccess.fieldName - if (fieldAccess.isReader()) { - fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();') - } else if (fieldAccess.isWriter()) { - fieldAccess.replace('$0.realmSet$' + fieldName + '($1);') - } - } - } - } - - /** - * Adds a method to indicate that Realm transformer has been applied. - * - * @param clazz The CtClass to modify. - */ - public static void overrideTransformedMarker(CtClass clazz) { - logger.debug " Realm: Marking as transformed ${clazz.simpleName}" - try { - clazz.getDeclaredMethod("transformerApplied", new CtClass[0]) - } catch (NotFoundException ignored) { - clazz.addMethod(CtNewMethod.make(Modifier.PUBLIC, CtClass.booleanType, "transformerApplied", - new CtClass[0], new CtClass[0], "{return true;}", clazz)) - } - } -} diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy new file mode 100644 index 0000000000..1fc1028f2d --- /dev/null +++ b/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ + +package io.realm.transformer + +import kotlin.collections.EmptyList +import org.gradle.api.Project + +import javax.annotation.Nonnull +import javax.annotation.Nullable; + +/** + * Helper methods for functionality that is really hard to port to Java/Kotlin + */ +class GroovyUtil { + + @Nullable + static String getTargetSdk(@Nonnull Project project) { + return project?.android?.defaultConfig?.targetSdkVersion?.mApiLevel as String + } + + @Nullable + static String getMinSdk(@Nonnull Project project) { + return project?.android?.defaultConfig?.minSdkVersion?.mApiLevel as String + } + + @Nonnull + static boolean isSyncEnabled(@Nonnull Project project) { + return project.realm?.syncEnabled != null && project.realm.syncEnabled + } + + @Nonnull + static Collection getBootClasspath(@Nonnull Project project) { + def classpath = project.android.bootClasspath as Collection + return (classpath != null) ? classpath : Collections.emptyList() + } +} diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy deleted file mode 100644 index 53d9889db0..0000000000 --- a/realm-transformer/src/main/groovy/io/realm/transformer/RealmTransformer.groovy +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.transformer - -import com.android.SdkConstants -import com.android.build.api.transform.* -import com.google.common.collect.ImmutableSet -import com.google.common.collect.Sets -import com.google.common.io.Files -import groovy.io.FileType -import io.realm.annotations.RealmClass -import javassist.ClassPool -import javassist.CtClass -import org.gradle.api.Project -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -import java.util.jar.JarFile -import java.util.regex.Pattern - -import static com.android.build.api.transform.QualifiedContent.* - -/** - * This class implements the Transform API provided by the Android Gradle plugin. - */ -@SuppressWarnings("GroovyUnusedDeclaration") -class RealmTransformer extends Transform { - - private Logger logger = LoggerFactory.getLogger('realm-logger') - private Project project - - public RealmTransformer(Project project) { - this.project = project - } - - @Override - String getName() { - return "RealmTransformer" - } - - @Override - Set getInputTypes() { - return ImmutableSet. of(DefaultContentType.CLASSES) - } - - @Override - Set getScopes() { - return Sets.immutableEnumSet(Scope.PROJECT) - } - - @Override - Set getReferencedScopes() { - // Scope.PROJECT_LOCAL_DEPS and Scope.SUB_PROJECTS_LOCAL_DEPS is only for compatibility with AGP 1.x, 2.x - return Sets.immutableEnumSet(Scope.EXTERNAL_LIBRARIES, Scope.PROJECT_LOCAL_DEPS, - Scope.SUB_PROJECTS, Scope.SUB_PROJECTS_LOCAL_DEPS, Scope.TESTED_CODE) - } - - @Override - boolean isIncremental() { - return false - } - - @Override - void transform(Context context, Collection inputs, Collection referencedInputs, - TransformOutputProvider outputProvider, boolean isIncremental) - throws IOException, TransformException, InterruptedException { - - def tic = System.currentTimeMillis() - - // Find all the class names - def inputClassNames = getClassNames(inputs) - def referencedClassNames = getClassNames(referencedInputs) - def allClassNames = merge(inputClassNames, referencedClassNames) - - // Create and populate the Javassist class pool - ClassPool classPool = new ManagedClassPool(inputs, referencedInputs) - // Append android.jar to class pool. We don't need the class names of them but only the class in the pool for - // javassist. See https://github.com/realm/realm-java/issues/2703. - addBootClassesToClassPool(classPool) - - logger.debug "ClassPool contains Realm classes: ${classPool.getOrNull('io.realm.RealmList') != null}" - - // mark as transformed - def baseProxyMediator = classPool.get('io.realm.internal.RealmProxyMediator') - def mediatorPattern = Pattern.compile('^io\\.realm\\.[^.]+Mediator$') - def proxyMediatorClasses = inputClassNames - .findAll { it.matches(mediatorPattern) } - .collect { classPool.getCtClass(it) } - .findAll { it.superclass?.equals(baseProxyMediator) } - logger.debug "Proxy Mediator Classes: ${proxyMediatorClasses*.name}" - proxyMediatorClasses.each { - BytecodeModifier.overrideTransformedMarker(it) - } - - // Find the model classes - def allModelClasses = allClassNames - .findAll { it.endsWith('RealmProxy') } - .collect { classPool.getCtClass(it).superclass } - .findAll { it.hasAnnotation(RealmClass.class) || it.superclass.hasAnnotation(RealmClass.class) } - def inputModelClasses = allModelClasses.findAll { - inputClassNames.contains(it.name) - } - logger.debug "Model Classes: ${allModelClasses*.name}" - - // Populate a list of the fields that need to be managed with bytecode manipulation - def allManagedFields = [] - allModelClasses.each { - allManagedFields.addAll(it.declaredFields.findAll { - BytecodeModifier.isModelField(it) - }) - } - logger.debug "Managed Fields: ${allManagedFields*.name}" - - // Add accessors to the model classes in the target project - inputModelClasses.each { - BytecodeModifier.addRealmAccessors(it) - BytecodeModifier.addRealmProxyInterface(it, classPool) - BytecodeModifier.callInjectObjectContextFromConstructors(it) - } - - // Use accessors instead of direct field access - inputClassNames.each { - logger.debug " Modifying class ${it}" - def ctClass = classPool.getCtClass(it) - BytecodeModifier.useRealmAccessors(ctClass, allManagedFields) - ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) - } - - copyResourceFiles(inputs, outputProvider) - - def toc = System.currentTimeMillis() - logger.debug "Realm Transform time: ${toc-tic} milliseconds" - - this.sendAnalytics(inputs, inputModelClasses) - classPool.close() - } - - /** - * Sends the analytics - * @param inputs the inputs provided by the Transform API - * @param inputModelClasses a list of ctClasses describing the Realm models - */ - private sendAnalytics(Collection inputs, List inputModelClasses) { - def containsKotlin = false - inputs.each { - it.directoryInputs.each { - def path = it.file.absolutePath - def index = path.indexOf('build' + File.separator + 'intermediates' + File.separator + 'classes') - if (index != -1) { - def projectPath = path.substring(0, index) - def buildFile = new File(projectPath + 'build.gradle') - if (buildFile.exists() && buildFile.text.contains('kotlin')) { - containsKotlin = true - } - } - } - } - - def packages = inputModelClasses.collect { - it.getPackageName() - } - - def targetSdk = project?.android?.defaultConfig?.targetSdkVersion?.mApiLevel as String - def minSdk = project?.android?.defaultConfig?.minSdkVersion?.mApiLevel as String - - def env = System.getenv() - def disableAnalytics = env["REALM_DISABLE_ANALYTICS"] - if (disableAnalytics == null || disableAnalytics != "true") { - boolean sync = project?.realm?.syncEnabled != null && project.realm.syncEnabled - def analytics = new RealmAnalytics(packages as Set, containsKotlin, sync, targetSdk, minSdk) - analytics.execute() - } - } - - private static Set getClassNames(Collection inputs) { - Set classNames = new HashSet() - - inputs.each { - it.directoryInputs.each { - def dirPath = it.file.absolutePath - it.file.eachFileRecurse(FileType.FILES) { - if (it.absolutePath.endsWith(SdkConstants.DOT_CLASS)) { - def className = - it.absolutePath.substring( - dirPath.length() + 1, - it.absolutePath.length() - SdkConstants.DOT_CLASS.length() - ).replace(File.separatorChar, '.' as char) - classNames.add(className) - } - } - } - - it.jarInputs.each { - def jarFile = new JarFile(it.file) - jarFile.entries().findAll { - !it.directory && it.name.endsWith(SdkConstants.DOT_CLASS) - }.each { - def path = it.name - // The jar might not using File.separatorChar as the path separator. So we just replace both `\` and - // `/`. It depends on how the jar file was created. - // See http://stackoverflow.com/questions/13846000/file-separators-of-path-name-of-zipentry - String className = path.substring(0, path.length() - SdkConstants.DOT_CLASS.length()) - .replace('/' as char , '.' as char) - .replace('\\' as char , '.' as char) - classNames.add(className) - } - jarFile.close() // Crash transformer if this fails - } - } - return classNames - } - - private copyResourceFiles(Collection inputs, TransformOutputProvider outputProvider) { - inputs.each { - it.directoryInputs.each { - def dirPath = it.file.absolutePath - it.file.eachFileRecurse(FileType.FILES) { - if (!it.absolutePath.endsWith(SdkConstants.DOT_CLASS)) { - logger.debug " Copying resource ${it}" - def dest = new File(getOutputFile(outputProvider), - it.absolutePath.substring(dirPath.length())) - dest.parentFile.mkdirs() - Files.copy(it, dest) - } - } - } - - // no need to implement the code for `it.jarInputs.each` since PROJECT SCOPE does not use jar input. - } - } - - private File getOutputFile(TransformOutputProvider outputProvider) { - return outputProvider.getContentLocation( - 'realm', getInputTypes(), getScopes(), Format.DIRECTORY) - } - - private static Set merge(Set set1, Set set2) { - Set merged = new HashSet() - merged.addAll(set1) - merged.addAll(set2) - return merged - } - - // There is no official way to get the path to android.jar for transform. - // See https://code.google.com/p/android/issues/detail?id=209426 - private void addBootClassesToClassPool(ClassPool classPool) { - try { - project.android.bootClasspath.each { - String path = it.absolutePath - logger.debug "Add boot class " + path + " to class pool." - classPool.appendClassPath(path) - } - } catch (Exception e) { - // Just log it. It might not impact the transforming if the method which needs to be transformer doesn't - // contain classes from android.jar. - logger.debug("Cannot get bootClasspath caused by:", e) - } - } -} diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/ByteCodeModifier.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/ByteCodeModifier.kt new file mode 100644 index 0000000000..7839c95b22 --- /dev/null +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/ByteCodeModifier.kt @@ -0,0 +1,194 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.transformer + +import io.realm.annotations.Ignore +import io.realm.annotations.RealmClass +import io.realm.transformer.ext.safeSubtypeOf +import javassist.* +import javassist.expr.ExprEditor +import javassist.expr.FieldAccess + +/** + * This class encapsulates the bytecode manipulation code needed to transform model classes + * and the classes using them. + */ +class BytecodeModifier { + + companion object { + + fun isModelField(field: CtField): Boolean { + return !field.hasAnnotation(Ignore::class.java) && !Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()) + } + + /** + * Adds Realm specific accessors to a model class. + * All the declared fields will be associated with a getter and a setter. + * + * @param clazz the CtClass to add accessors to. + */ + @JvmStatic + fun addRealmAccessors(clazz: CtClass) { + val methods: List = clazz.declaredMethods.map { it.name } + clazz.declaredFields.forEach { field: CtField -> + if (isModelField(field)) { + if (!methods.contains("realmGet\$${field.name}")) { + clazz.addMethod(CtNewMethod.getter("realmGet\$${field.name}", field)) + } + if (!methods.contains("realmSet\$${field.name}")) { + clazz.addMethod(CtNewMethod.setter("realmSet\$${field.name}", field)) + } + } + } + } + + /** + * Modifies a class replacing field accesses with the appropriate Realm accessors. + * + * @param clazz The CtClass to modify + * @param managedFields List of fields whose access should be replaced + */ + @JvmStatic + fun useRealmAccessors(classPool: ClassPool, clazz: CtClass, managedFields: List?) { + clazz.declaredBehaviors.forEach { behavior -> + logger.debug(" Behavior: ${behavior.name}") + if ( + ( + behavior is CtMethod && + !behavior.name.startsWith("realmGet$") && + !behavior.name.startsWith("realmSet$") + ) || ( + behavior is CtConstructor + ) + ) { + if (managedFields != null) { + behavior.instrument(FieldAccessToAccessorConverterUsingList(managedFields, clazz, behavior)) + } else { + behavior.instrument(FieldAccessToAccessorConverterUsingClassPool(classPool, clazz, behavior)) + } + + } + } + } + + /** + * Modifies a class adding its RealmProxy interface. + * + * @param clazz The CtClass to modify + * @param classPool the Javassist class pool + */ + @JvmStatic + fun addRealmProxyInterface(clazz: CtClass, classPool: ClassPool) { + val proxyInterface: CtClass = classPool.get("io.realm.${clazz.getName().replace(".", "_")}RealmProxyInterface") + clazz.addInterface(proxyInterface) + } + + fun callInjectObjectContextFromConstructors(clazz: CtClass) { + clazz.constructors.forEach { + it.insertBeforeBody("if ($0 instanceof io.realm.internal.RealmObjectProxy) {" + + " ((io.realm.internal.RealmObjectProxy) $0).realm\$injectObjectContext();" + + " }") + } + } + + + /** + * Adds a method to indicate that Realm transformer has been applied. + * + * @param clazz The CtClass to modify. + */ + fun overrideTransformedMarker(clazz: CtClass) { + logger.debug(" Realm: Marking as transformed ${clazz.simpleName}") + try { + clazz.getDeclaredMethod("transformerApplied") + } catch (ignored: NotFoundException) { + clazz.addMethod(CtNewMethod.make(Modifier.PUBLIC, CtClass.booleanType, "transformerApplied", + arrayOf(), arrayOf(), "{return true;}", clazz)) + } + } + + } + + /** + * This class goes through all the field access behaviours of a class and replaces field accesses with + * the appropriate accessor. + */ + private class FieldAccessToAccessorConverterUsingList(val managedFields: List, + val ctClass: CtClass, + val behaviour: CtBehavior) : ExprEditor() { + + @Throws(CannotCompileException::class) + override fun edit(fieldAccess: FieldAccess) { + logger.debug(" Field being accessed: ${fieldAccess.className}.${fieldAccess.fieldName}") + managedFields.find { + fieldAccess.className.equals(it.declaringClass.name) && fieldAccess.fieldName.equals(it.name) + }?.run { + logger.debug(" Realm: Manipulating ${ctClass.simpleName}.${behaviour.name}(): ${fieldAccess.fieldName}") + logger.debug(" Methods: ${ctClass.declaredMethods}") + val fieldName: String = fieldAccess.fieldName + if (fieldAccess.isReader) { + fieldAccess.replace("\$_ = $0.realmGet\$$fieldName();") + } else if (fieldAccess.isWriter()) { + fieldAccess.replace("\$0.realmSet\$$fieldName(\$1);") + } + } + } + } + + /** + * This class goes through all the field access behaviours of a class and replaces field accesses with + * the appropriate accessor. + */ + private class FieldAccessToAccessorConverterUsingClassPool(val classPool: ClassPool, + val ctClass: CtClass, + val behaviour: CtBehavior) : ExprEditor() { + + val realmObjectProxyInterface: CtClass = classPool.get("io.realm.internal.RealmObjectProxy") + + @Throws(CannotCompileException::class) + override fun edit(fieldAccess: FieldAccess) { + logger.debug(" Field being accessed: ${fieldAccess.className}.${fieldAccess.fieldName}") + if (isRealmModelClass(fieldAccess.enclosingClass) && isModelField(fieldAccess.field)) { + logger.debug(" Realm: Manipulating ${ctClass.simpleName}.${behaviour.name}(): ${fieldAccess.fieldName}") + logger.debug(" Methods: ${ctClass.declaredMethods}") + val fieldName: String = fieldAccess . fieldName + if (fieldAccess.isReader) { + fieldAccess.replace("\$_ = \$0.realmGet\$$fieldName();") + } else if (fieldAccess.isWriter) { + fieldAccess.replace("\$0.realmSet\$$fieldName(\$1);") + } + } + } + + fun isRealmModelClass(clazz: CtClass): Boolean { + return arrayOf(clazz).filter { + if (it.hasAnnotation(RealmClass::class.java)) { + return true + } else { + try { + return it.superclass?.hasAnnotation(RealmClass::class.java) == true + } catch (ignored: NotFoundException) { + return false + } + } + } + .filter { !it.safeSubtypeOf(realmObjectProxyInterface) } + .any { it.name != "io.realm.RealmObject" } + } + } + +} diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/ManagedClassPool.groovy b/realm-transformer/src/main/kotlin/io/realm/transformer/ManagedClassPool.kt similarity index 77% rename from realm-transformer/src/main/groovy/io/realm/transformer/ManagedClassPool.groovy rename to realm-transformer/src/main/kotlin/io/realm/transformer/ManagedClassPool.kt index c3a0389d26..ce337d9a53 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/ManagedClassPool.groovy +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/ManagedClassPool.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017 Realm Inc. + * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,15 +19,15 @@ package io.realm.transformer import com.android.build.api.transform.TransformInput import javassist.ClassPath import javassist.ClassPool +import java.io.Closeable /** * This class is a wrapper around JavaAssists {@code ClassPool} class that allows for correct cleanup * of the resources used. */ -@SuppressWarnings("GroovyUnusedDeclaration") -class ManagedClassPool extends ClassPool implements Closeable { +class ManagedClassPool(inputs: Collection, referencedInputs: Collection) : ClassPool(), Closeable { - def List pathElements = new ArrayList() + val pathElements: ArrayList = arrayListOf() /** * Constructor for creating and populating the JavAssist class pool. @@ -37,28 +37,27 @@ class ManagedClassPool extends ClassPool implements Closeable { * @param referencedInputs the referencedInputs provided by the Transform API * @return the populated ClassPool instance */ - ManagedClassPool(Collection inputs, Collection referencedInputs) { + init { // Don't use ClassPool.getDefault(). Doing consecutive builds in the same run (e.g. debug+release) // will use a cached object and all the classes will be frozen. - super(null) appendSystemPath() - inputs.each { - it.directoryInputs.each { + inputs.forEach{ + it.directoryInputs.forEach { pathElements.add(appendClassPath(it.file.absolutePath)) } - it.jarInputs.each { + it.jarInputs.forEach { pathElements.add(appendClassPath(it.file.absolutePath)) } } - referencedInputs.each { - it.directoryInputs.each { + referencedInputs.forEach { + it.directoryInputs.forEach { pathElements.add(appendClassPath(it.file.absolutePath)) } - it.jarInputs.each { + it.jarInputs.forEach { pathElements.add(appendClassPath(it.file.absolutePath)) } } @@ -67,16 +66,16 @@ class ManagedClassPool extends ClassPool implements Closeable { /** * Detach all ClassPath elements, effectively closing the class pool. */ - @Override - void close() throws IOException { + override fun close() { // Cleanup class pool. Internally it keeps a list of JarFile references that are only // cleaned up if the the ClassPath element wrapping it is manually removed. // See https://github.com/jboss-javassist/javassist/issues/165 - def iter = pathElements.iterator() + val iter: MutableIterator = pathElements.iterator() while (iter.hasNext()) { - def cp = iter.next() + val cp = iter.next() removeClassPath(cp) iter.remove() } } + } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt new file mode 100644 index 0000000000..7fdd2db56b --- /dev/null +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -0,0 +1,164 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.transformer + +import com.android.build.api.transform.* +import io.realm.transformer.build.FullBuild +import io.realm.transformer.build.IncrementalBuild +import io.realm.transformer.build.BuildTemplate +import javassist.CtClass +import org.gradle.api.Project +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.File + +// Package level logger +val logger: Logger = LoggerFactory.getLogger("realm-logger") + +/** + * This class implements the Transform API provided by the Android Gradle plugin. + */ +class RealmTransformer(val project: Project) : Transform() { + + val logger: Logger = LoggerFactory.getLogger("realm-logger") + + override fun getName(): String { + return "RealmTransformer" + } + + override fun getInputTypes(): Set { + return setOf(QualifiedContent.DefaultContentType.CLASSES) + } + + override fun isIncremental(): Boolean { + return true + } + + override fun getScopes(): MutableSet { + return mutableSetOf(QualifiedContent.Scope.PROJECT) + } + + override fun getReferencedScopes(): MutableSet { + // Scope.PROJECT_LOCAL_DEPS and Scope.SUB_PROJECTS_LOCAL_DEPS is only for compatibility with AGP 1.x, 2.x + return mutableSetOf( + QualifiedContent.Scope.EXTERNAL_LIBRARIES, + QualifiedContent.Scope.PROJECT_LOCAL_DEPS, + QualifiedContent.Scope.SUB_PROJECTS, + QualifiedContent.Scope.SUB_PROJECTS_LOCAL_DEPS, + QualifiedContent.Scope.TESTED_CODE + ) + } + + /** + * Implements the transform algorithm. The heaviest part of the transform is loading the + * {@code CtClass} from JavaAssist, so this should be avoided as much as possible. + * + * This is also the reason that there are significant changes between a full build and a + * incremental build. In a full build, we can use text matching to go from a proxy class + * to the model class. Something we cannot do when building incrementally. In that case + * we have to deduce all information from the class at hand. + * + * @param context + * @param inputs + * @param referencedInputs + * @param outputProvider + * @param isIncremental + * @throws IOException + * @throws TransformException + * @throws InterruptedException + */ + override fun transform(context: Context?, inputs: MutableCollection?, + referencedInputs: Collection?, + outputProvider: TransformOutputProvider?, isIncremental: Boolean) { + + val timer = Stopwatch() + timer.start("Realm Transform time") + + val build: BuildTemplate = if (isIncremental) IncrementalBuild(project, outputProvider!!, this) + else FullBuild(project, outputProvider!!, this) + + build.prepareOutputClasses(inputs!!) + timer.splitTime("Prepare output classes") + if (build.hasNoOutput()) { + // Abort transform as quickly as possible if no files where found for processing. + exitTransform(emptySet(), emptyList(), timer) + return + } + build.prepareReferencedClasses(referencedInputs!!); + timer.splitTime("Prepare referenced classes") + build.markMediatorsAsTransformed() + timer.splitTime("Mark mediators as transformed") + build.transformModelClasses(); + timer.splitTime("Transform model classes") + build.transformDirectAccessToModelFields(); + timer.splitTime("Transform references to model fields") + build.copyResourceFiles(); + timer.splitTime("Copy resource files") + exitTransform(inputs, build.getOutputModelClasses(), timer) + } + + private fun exitTransform(inputs: Collection, outputModelClasses: Collection, timer: Stopwatch) { + timer.stop() + this.sendAnalytics(inputs, outputModelClasses) + } + + /** + * Sends the analytics + * + * @param inputs the inputs provided by the Transform API + * @param inputModelClasses a list of ctClasses describing the Realm models + */ + private fun sendAnalytics(inputs: Collection, outputModelClasses: Collection) { + val disableAnalytics: Boolean = "true".equals(System.getenv()["REALM_DISABLE_ANALYTICS"]) + if (inputs.isEmpty() || disableAnalytics) { + // Don't send analytics for incremental builds or if they have ben explicitly disabled. + return + } + + var containsKotlin = false + + outer@ + for(input: TransformInput in inputs) { + for (di: DirectoryInput in input.directoryInputs) { + val path: String = di.file.absolutePath + val index: Int = path.indexOf("build${File.separator}intermediates${File.separator}classes") + if (index != -1) { + val projectPath: String = path.substring(0, index) + val buildFile = File(projectPath + "build.gradle") + if (buildFile.exists() && buildFile.readText().contains("kotlin")) { + containsKotlin = true + break@outer + } + } + } + } + + val packages: Collection = outputModelClasses.map { + it.packageName + } + + val targetSdk: String? = GroovyUtil.getTargetSdk(project) + val minSdk: String? = GroovyUtil.getMinSdk(project) + + if (disableAnalytics) { + val sync: Boolean = GroovyUtil.isSyncEnabled(project) + val analytics = RealmAnalytics(packages as Set, containsKotlin, sync, targetSdk, minSdk) + analytics.execute() + } + } + +} diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/Stopwatch.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/Stopwatch.kt new file mode 100644 index 0000000000..d9670a35f5 --- /dev/null +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/Stopwatch.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.transformer + +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.util.concurrent.TimeUnit + +class Stopwatch { + + val logger: Logger = LoggerFactory.getLogger("realm-stopwatch") + + var start: Long = -1L + var lastSplit: Long = -1L + lateinit var label: String + + /* + * Start the stopwatch. + */ + fun start(label: String) { + if (start != -1L) { + throw IllegalStateException("Stopwatch was already started"); + } + this.label = label + start = System.nanoTime(); + lastSplit = start; + } + + /* + * Reports the split time. + * + * @param label Label to use when printing split time + * @param reportDiffFromLastSplit if `true` report the time from last split instead of the start + */ + fun splitTime(label: String, reportDiffFromLastSplit: Boolean = true) { + val split = System.nanoTime() + val diff = if (reportDiffFromLastSplit) { split - lastSplit } else { split - start } + lastSplit = split; + logger.debug("$label: ${TimeUnit.NANOSECONDS.toMillis(diff)} ms.") + } + + /** + * Stops the timer and report the result. + */ + fun stop() { + val stop = System.nanoTime() + val diff = stop - start + logger.debug("$label: ${TimeUnit.NANOSECONDS.toMillis(diff)} ms.") + } +} \ No newline at end of file diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt new file mode 100644 index 0000000000..fc77a5c215 --- /dev/null +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -0,0 +1,168 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.transformer.build + +import com.android.SdkConstants +import com.android.build.api.transform.Format +import com.android.build.api.transform.Transform +import com.android.build.api.transform.TransformInput +import com.android.build.api.transform.TransformOutputProvider +import com.google.common.io.Files +import io.realm.transformer.BytecodeModifier +import io.realm.transformer.GroovyUtil +import io.realm.transformer.ManagedClassPool +import io.realm.transformer.logger +import javassist.ClassPool +import javassist.CtClass +import org.gradle.api.Project +import java.io.File +import java.util.regex.Pattern + +/** + * Abstract class defining the structure of doing different types of builds. + * + */ +abstract class BuildTemplate(val project: Project, val outputProvider: TransformOutputProvider, val transform: Transform) { + + protected lateinit var inputs: MutableCollection + protected lateinit var classPool: ManagedClassPool + protected val outputClassNames: MutableSet = hashSetOf() + protected val outputReferencedClassNames: MutableSet = hashSetOf() + protected val outputModelClasses: ArrayList = arrayListOf() + + /** + * Find all the class names available for transforms as well as all referenced classes. + */ + abstract fun prepareOutputClasses(inputs: MutableCollection) + + /** + * Helper method for going through all `TransformInput` and sort classes into buckets of + * source files in the current project or source files found in jar files. + * + * @param inputs set of input files + * @param directoryFiles the set of files in directories getting compiled. These are potential + * candidates for the transformer. + * @param jaFiles the set of files found in jar files. These will never be transformed. This should + * already be done when creating the jar file. + */ + protected abstract fun categorizeClassNames(inputs: Collection, + directoryFiles: MutableSet, + referencedFiles: MutableSet) + + /** + * Returns `true` if this build contains no relevant classes to transform. + */ + fun hasNoOutput(): Boolean { + return outputClassNames.isEmpty() + } + + + fun prepareReferencedClasses(referencedInputs: Collection) { + categorizeClassNames(referencedInputs, outputReferencedClassNames, outputReferencedClassNames) // referenced files + + // Create and populate the Javassist class pool + this.classPool = ManagedClassPool(inputs, referencedInputs) + // Append android.jar to class pool. We don't need the class names of them but only the class in the pool for + // javassist. See https://github.com/realm/realm-java/issues/2703. + addBootClassesToClassPool(classPool) + logger.debug("ClassPool contains Realm classes: ${classPool.getOrNull("io.realm.RealmList") != null}") + + filterForModelClasses(outputClassNames, outputReferencedClassNames) + } + + protected abstract fun filterForModelClasses(outputClassNames: Set, outputReferencedClassNames: Set) + + + fun markMediatorsAsTransformed() { + val baseProxyMediator: CtClass = classPool.get("io.realm.internal.RealmProxyMediator") + val mediatorPattern: Pattern = Pattern.compile("^io\\.realm\\.[^.]+Mediator$") + val proxyMediatorClasses: Collection = outputClassNames + .filter { mediatorPattern.matcher(it).find() } + .map { classPool.getCtClass(it) } + .filter { it.superclass.equals(baseProxyMediator) } + + logger.debug("Proxy Mediator Classes: ${proxyMediatorClasses.joinToString(",") { it.name }}") + proxyMediatorClasses.forEach { + BytecodeModifier.overrideTransformedMarker(it) + } + } + + fun transformModelClasses() { + // Add accessors to the model classes in the target project + outputModelClasses.forEach { + logger.debug("Modify model class: ${it.name}") + BytecodeModifier.addRealmAccessors(it) + BytecodeModifier.addRealmProxyInterface(it, classPool) + BytecodeModifier.callInjectObjectContextFromConstructors(it) + } + } + + abstract fun transformDirectAccessToModelFields() + + fun copyResourceFiles() { + copyResourceFiles(inputs) + classPool.close(); + } + + private fun copyResourceFiles(inputs: MutableCollection) { + inputs.forEach { + it.directoryInputs.forEach { + val dirPath: String = it.file.absolutePath + it.file.walkTopDown().forEach { + if (it.isFile) { + if (!it.absolutePath.endsWith(SdkConstants.DOT_CLASS)) { + logger.debug(" Copying resource $it") + val dest = File(getOutputFile(outputProvider), it.absolutePath.substring(dirPath.length)) + dest.parentFile.mkdirs() + Files.copy(it, dest) + } + } + } + } + // no need to implement the code for `it.jarInputs.each` since PROJECT SCOPE does not use jar input. + } + } + + protected fun getOutputFile(outputProvider: TransformOutputProvider): File { + return outputProvider.getContentLocation( + "realm", transform.inputTypes, transform.scopes, Format.DIRECTORY) + } + + /** + * There is no official way to get the path to android.jar for transform. + * See https://code.google.com/p/android/issues/detail?id=209426 + */ + private fun addBootClassesToClassPool(classPool: ClassPool) { + try { + GroovyUtil.getBootClasspath(project).forEach { + val path: String = it.absolutePath + logger.debug("Add boot class $path to class pool.") + classPool.appendClassPath(path) + } + } catch (e: Exception) { + // Just log it. It might not impact the transforming if the method which needs to be transformer doesn't + // contain classes from android.jar. + logger.debug("Cannot get bootClasspath caused by: ", e) + } + } + + fun getOutputModelClasses(): Collection { + return outputModelClasses + } + + protected abstract fun findModelClasses(classNames: Set): Collection + +} diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt new file mode 100644 index 0000000000..3bbcd1bfef --- /dev/null +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.transformer.build + +import com.android.SdkConstants +import com.android.build.api.transform.TransformInput +import com.android.build.api.transform.TransformOutputProvider +import io.realm.transformer.BytecodeModifier +import io.realm.transformer.RealmTransformer +import io.realm.transformer.ext.safeSubtypeOf +import io.realm.transformer.logger +import javassist.CtClass +import javassist.CtField +import org.gradle.api.Project +import java.io.File +import java.util.jar.JarFile + +class FullBuild(project: Project, outputProvider: TransformOutputProvider, transformer: RealmTransformer) + : BuildTemplate(project, outputProvider, transformer) { + + private val allModelClasses: ArrayList = arrayListOf() + + override fun prepareOutputClasses(inputs: MutableCollection) { + this.inputs = inputs; + categorizeClassNames(inputs, outputClassNames, outputReferencedClassNames) + logger.debug("Full build. Files being processed: ${outputClassNames.size}.") + } + + override fun categorizeClassNames(inputs: Collection, + directoryFiles: MutableSet, + jarFiles: MutableSet) { + inputs.forEach { + it.directoryInputs.forEach { + val dirPath: String = it.file.absolutePath + // Non-incremental build: Include all files + it.file.walkTopDown().forEach { + if (it.isFile) { + if (it.absolutePath.endsWith(SdkConstants.DOT_CLASS)) { + val className: String = it.absolutePath + .substring(dirPath.length + 1, it.absolutePath.length - SdkConstants.DOT_CLASS.length) + .replace(File.separatorChar, '.') + directoryFiles.add(className) + } + } + } + } + + it.jarInputs.forEach { + val jarFile = JarFile(it.file) + jarFile.entries() + .toList() + .filter { + !it.isDirectory && it.name.endsWith(SdkConstants.DOT_CLASS) + } + .forEach { + val path: String = it.name + // The jar might not using File.separatorChar as the path separator. So we just replace both `\` and + // `/`. It depends on how the jar file was created. + // See http://stackoverflow.com/questions/13846000/file-separators-of-path-name-of-zipentry + val className: String = path + .substring(0, path.length - SdkConstants.DOT_CLASS.length) + .replace('/', '.') + .replace('\\', '.') + jarFiles.add(className) + } + jarFile.close() // Crash transformer if this fails + } + } + } + + override fun findModelClasses(classNames: Set): Collection { + val realmObjectProxyInterface: CtClass = classPool.get("io.realm.internal.RealmObjectProxy") + + // For full builds, we are currently finding model classes by assuming that only + // the annotation processor is generating files ending with `RealmProxy`. This is + // a lot faster as we only need to compare the name of the type before we load + // the CtClass. + // Find the model classes + return classNames + // Quick and loose filter where we assume that classes ending with RealmProxy are + // a Realm model proxy class generated by the annotation processor. This can + // produce false positives: https://github.com/realm/realm-java/issues/3709 + .filter { it.endsWith("RealmProxy") } + .mapNotNull { + // Verify the file is in fact a proxy class, in which case the super + // class is always present and is the real model class. + val clazz: CtClass = classPool.getCtClass(it) + if (clazz.safeSubtypeOf(realmObjectProxyInterface)) { + return@mapNotNull clazz.superclass; + } else { + return@mapNotNull null + } + } + } + + override fun filterForModelClasses(classNames: Set, extraClassNames: Set) { + + val allClassNames: Set = merge(classNames, extraClassNames) + + allModelClasses.addAll(findModelClasses(allClassNames)) + + outputModelClasses.addAll(allModelClasses.filter { + outputClassNames.contains(it.name) + }) + } + + override fun transformDirectAccessToModelFields() { + // Populate a list of the fields that need to be managed with bytecode manipulation + val allManagedFields: ArrayList = arrayListOf() + allModelClasses.forEach { + allManagedFields.addAll(it.declaredFields.filter { + BytecodeModifier.isModelField(it) + }) + } + logger.debug("Managed Fields: ${allManagedFields.joinToString(",") { it.name }}") + + // Use accessors instead of direct field access + outputClassNames.forEach { + logger.debug("Modify accessors in class: $it") + val ctClass: CtClass = classPool.getCtClass(it) + BytecodeModifier.useRealmAccessors(classPool, ctClass, allManagedFields) + ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) + } + } + + private fun merge(set1: Set, set2: Set): Set { + val merged: MutableSet = hashSetOf() + merged.addAll(set1) + merged.addAll(set2) + return merged + } + +} \ No newline at end of file diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt new file mode 100644 index 0000000000..8a9c9b9783 --- /dev/null +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.transformer.build + +import com.android.SdkConstants +import com.android.build.api.transform.Status +import com.android.build.api.transform.TransformInput +import com.android.build.api.transform.TransformOutputProvider +import io.realm.annotations.RealmClass +import io.realm.transformer.BytecodeModifier +import io.realm.transformer.RealmTransformer +import io.realm.transformer.ext.safeSubtypeOf +import io.realm.transformer.logger +import javassist.CtClass +import javassist.NotFoundException +import org.gradle.api.Project +import java.io.File +import java.util.jar.JarFile + +class IncrementalBuild(project: Project, outputProvider: TransformOutputProvider, transform: RealmTransformer) + : BuildTemplate(project, outputProvider, transform) { + + override fun prepareOutputClasses(inputs: MutableCollection) { + this.inputs = inputs; + categorizeClassNames(inputs, outputClassNames, outputReferencedClassNames) // Output files + logger.debug("Incremental build. Files being processed: ${outputClassNames.size}.") + logger.debug("Incremental files: ${outputClassNames.joinToString(",")}") + } + + override fun filterForModelClasses(outputClassNames: Set, outputReferencedClassNames: Set) { + outputModelClasses.addAll(findModelClasses(outputClassNames)) + } + + override fun transformDirectAccessToModelFields() { + // Use accessors instead of direct field access + outputClassNames.forEach { + logger.debug("Modify accessors in class: $it") + val ctClass: CtClass = classPool.getCtClass(it) + BytecodeModifier.useRealmAccessors(classPool, ctClass, null) + ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) + } + + } + + + /** + * Categorize the transform input into its two main categorizes: `directoryFiles` which are + * source files in the current project and `jarFiles` which are source files found in jars. + * + * @param inputs set of input files + * @param directoryFiles the set of files in directories getting compiled. These are candidates for the transformer. + * @param jarFiles the set of files that are possible referenced but never transformed (required by JavaAssist). + * @param isIncremental `true` if build is incremental. + */ + override fun categorizeClassNames(inputs: Collection, + directoryFiles: MutableSet, + jarFiles: MutableSet) { + inputs.forEach { + it.directoryInputs.forEach { + val dirPath: String = it.file.absolutePath + + it.changedFiles.entries.forEach { + if (it.value == Status.NOTCHANGED || it.value == Status.REMOVED) { + return@forEach + } + val filePath: String = it.key.absolutePath + if (filePath.endsWith(SdkConstants.DOT_CLASS)) { + val className = filePath + .substring(dirPath.length + 1, filePath.length - SdkConstants.DOT_CLASS.length) + .replace(File.separatorChar, '.') + directoryFiles.add(className) + } + } + } + + it.jarInputs.forEach { + if (it.status == Status.REMOVED) { + return@forEach + } + + val jarFile = JarFile(it.file) + jarFile.entries() + .toList() + .filter { + !it.isDirectory && it.name.endsWith(SdkConstants.DOT_CLASS) + } + .forEach { + val path: String = it.name + // The jar might not using File.separatorChar as the path separator. So we just replace both `\` and + // `/`. It depends on how the jar file was created. + // See http://stackoverflow.com/questions/13846000/file-separators-of-path-name-of-zipentry + val className: String = path + .substring(0, path.length - SdkConstants.DOT_CLASS.length) + .replace('/', '.') + .replace('\\', '.') + jarFiles.add(className) + } + jarFile.close() // Crash transformer if this fails + } + } + } + + override fun findModelClasses(classNames: Set): Collection { + val realmObjectProxyInterface: CtClass = classPool.get("io.realm.internal.RealmObjectProxy") + // For incremental builds we need to determine if a class is a model class file + // based on information in the file itself. This require checks that are only + // possible once we loaded the CtClass from the ClassPool and is slower + // than the approach used when doing full builds. + return classNames + // Map strings to CtClass'es. + .map { classPool.getCtClass(it) } + // Model classes either have the @RealmClass annotation directly (if implementing RealmModel) + // or their superclass has it (if extends RealmObject). The annotation processor + // will have ensured the annotation is only present in these cases. + .filter { + var result: Boolean + if (it.hasAnnotation(RealmClass::class.java)) { + result = true + } else { + try { + result = it.superclass?.hasAnnotation(RealmClass::class.java) == true + } catch (e: NotFoundException) { + // Can happen if the super class is part of the `android.jar` which might + // not have been loaded. In any case, any base class part of Android cannot + // be a Realm model class. + result = false + } + } + return@filter result + } + // Proxy classes are generated by the Realm Annotation Processor and might accidentally + // pass the above check (e.g. if the model class has the @RealmClass annotation), so + // ignore them. + .filter { !it.safeSubtypeOf(realmObjectProxyInterface) } + // Unfortunately the RealmObject base class passes all above checks, so explicitly + // ignore it. + .filter { !it.name.equals("io.realm.RealmObject") } + } +} \ No newline at end of file diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/ext/CtClassExt.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/ext/CtClassExt.kt new file mode 100644 index 0000000000..24a8511672 --- /dev/null +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/ext/CtClassExt.kt @@ -0,0 +1,67 @@ +package io.realm.transformer.ext + +import javassist.CtClass +import javassist.NotFoundException +import javassist.bytecode.ClassFile + + +/** + * Returns {@code true} if 'clazz' is considered a subtype of 'superType'. + * + * This function is different than {@link CtClass#subtypeOf(CtClass)} in the sense + * that it will never crash even if classes are missing from the class pool, instead + * it will just return {@code false}. + * + * This e.g. happens with RxJava classes which are optional, but JavaAssist will try + * to load them and then crash. + * + * @param typeToCheckAgainst the type we want to check against + * @return `true` if `clazz` is a subtype of `typeToCheckAgainst`, `false` otherwise. + */ +fun CtClass.safeSubtypeOf(typeToCheckAgainst: CtClass): Boolean { + val typeToCheckAgainstQualifiedName: String = typeToCheckAgainst.name + if (this == typeToCheckAgainst || this.name.equals(typeToCheckAgainstQualifiedName)) { + return true + } + + val file: ClassFile = this.classFile2 + + // Check direct super class + val superName: String? = file.superclass + if (superName.equals(typeToCheckAgainstQualifiedName)) { + return true + } + + // Check direct interfaces + val ifs: Array = file.interfaces + ifs.forEach { + if (it == typeToCheckAgainstQualifiedName) { + return true + } + } + + // Check other inherited super classes + if (superName != null) { + var nextSuper: CtClass + try { + nextSuper = classPool.get(superName) + if (nextSuper.safeSubtypeOf(typeToCheckAgainst)) { + return true + } + } catch (ignored: NotFoundException) { + } + } + + // Check other inherited interfaces + ifs.forEach { interfaceName -> + try { + val interfaceClass: CtClass = classPool.get(interfaceName) + if (interfaceClass.safeSubtypeOf(typeToCheckAgainst)) { + return true + } + } catch (ignored: NotFoundException) { + } + } + + return false +} diff --git a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy index 2e2e6cd279..e9abb6b751 100644 --- a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy +++ b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy @@ -155,7 +155,7 @@ class BytecodeModifierTest extends Specification { BytecodeModifier.addRealmAccessors(ctClass) when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(ctClass, [ctField]) + BytecodeModifier.useRealmAccessors(classPool, ctClass, [ctField]) then: 'the field is not used and getter is called in the method ' !isFieldRead(ctMethod) && hasMethodCall(ctMethod) @@ -186,7 +186,7 @@ class BytecodeModifierTest extends Specification { BytecodeModifier.addRealmAccessors(ctClass) when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(ctClass, [ctField]) + BytecodeModifier.useRealmAccessors(classPool, ctClass, [ctField]) then: 'the field is not used in the method anymore' !isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && diff --git a/realm/build.gradle b/realm/build.gradle index 82f7c806cf..f18e16dbc4 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -4,7 +4,7 @@ project.ext.buildToolsVersion = '27.0.2' buildscript { ext.kotlin_version = '1.2.10' - ext.dokka_version = '0.9.15' + ext.dokka_version = '0.9.16' repositories { mavenLocal() google() diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 3ba0e38c80..532b468d5f 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -15,7 +15,7 @@ apply plugin: 'org.jetbrains.dokka' //apply plugin: 'net.ltgt.errorprone' import io.realm.transformer.RealmTransformer -android.registerTransform(new RealmTransformer()) +android.registerTransform(new RealmTransformer(project)) android { compileSdkVersion rootProject.compileSdkVersion diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index 8292873163..bcbb25cf26 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Set; @@ -60,11 +61,11 @@ public class ClassMetaData { private final TypeElement classType; // Reference to model class. private final String javaClassName; // Model class simple name as defined in Java. - private final List fields = new ArrayList(); // List of all fields in the class except those @Ignored. - private final List indexedFields = new ArrayList(); // list of all fields marked @Index. - private final Set backlinks = new HashSet(); - private final Set nullableFields = new HashSet(); // Set of fields which can be nullable - private final Set nullableValueListFields = new HashSet(); // Set of fields whose elements can be nullable + private final List fields = new ArrayList<>(); // List of all fields in the class except those @Ignored. + private final List indexedFields = new ArrayList<>(); // list of all fields marked @Index. + private final Set backlinks = new LinkedHashSet<>(); + private final Set nullableFields = new LinkedHashSet<>(); // Set of fields which can be nullable + private final Set nullableValueListFields = new LinkedHashSet<>(); // Set of fields whose elements can be nullable private String packageName; // package name for model class. private boolean hasDefaultConstructor; // True if model has a public no-arg constructor. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java index cdae9807cc..948f662225 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; @@ -53,7 +54,7 @@ public void generate() throws IOException { writer.emitPackage(Constants.REALM_PACKAGE_NAME); writer.emitEmptyLine(); - Map attributes = new HashMap(); + Map attributes = new LinkedHashMap<>(); attributes.put("allClasses", Boolean.TRUE); writer.emitAnnotation(RealmModule.class, attributes); writer.beginType( diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java index d9488d92e6..c713790379 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -63,16 +64,16 @@ public class ModuleMetaData { // Pre-processing // - private Set globalModules = new HashSet<>(); // All modules with `allClasses = true` set - private Map> specificClassesModules = new HashMap<>(); // Modules with classes specifically named - private Map classNamingPolicy = new HashMap(); - private Map fieldNamingPolicy = new HashMap(); + private Set globalModules = new LinkedHashSet<>(); // All modules with `allClasses = true` set + private Map> specificClassesModules = new LinkedHashMap<>(); // Modules with classes specifically named + private Map classNamingPolicy = new LinkedHashMap<>(); + private Map fieldNamingPolicy = new LinkedHashMap<>(); private Map moduleAnnotations = new HashMap<>(); // Post-processing // - private Map> modules = new HashMap>(); - private Map> libraryModules = new HashMap>(); + private Map> modules = new LinkedHashMap<>(); + private Map> libraryModules = new LinkedHashMap<>(); private boolean shouldCreateDefaultModule; @@ -386,7 +387,7 @@ private AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror) { * Returns all module classes and the RealmObjects they know of. */ public Map> getAllModules() { - Map> allModules = new HashMap>(); + Map> allModules = new LinkedHashMap<>(); allModules.putAll(modules); allModules.putAll(libraryModules); return allModules; diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 20fa873053..7cb75bd181 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -188,7 +188,7 @@ coveralls.jacocoReportPath = "${buildDir}/reports/coverage/debug/report.xml" import io.realm.transformer.RealmTransformer -android.registerTransform(new RealmTransformer()) +android.registerTransform(new RealmTransformer(project)) repositories { maven { url "https://jitpack.io" } @@ -204,7 +204,7 @@ dependencies { implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.getkeepsafe.relinker:relinker:1.2.2' - kaptObjectServer project(':realm-annotations-processor') + kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 objectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' kaptAndroidTest project(':realm-annotations-processor') diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/BaseModule.java b/realm/realm-library/src/main/java/io/realm/internal/sync/BaseModule.java new file mode 100644 index 0000000000..91f1e68b7a --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/BaseModule.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.internal.sync; + +import io.realm.annotations.RealmModule; + +// Workaround preventing `io.realm.DefaultRealmModuleMediator` being generated in the +// Realm JAR. Related to `https://github.com/realm/realm-java/issues/5799 +@RealmModule(library = true, allClasses = true) +public class BaseModule { +} From fce1de7db99a74d5ca1a021822c7477cc57ee9e3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Jun 2018 11:57:12 +0200 Subject: [PATCH 1241/2110] Add release date --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d068433a9..e0a44d4fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ -## 5.2.0 (YYYY-MM-DD) +## 5.2.0 (2018-06-06) The feature previously named Partial Sync is now called Query-Based Sync and is now the default mode when synchronizing Realms. This has impacted a number of API's. See below for the details. ### Deprecated -* [ObjectServer] `SyncConfiguration.automatic()` has been removed in favour of `SyncUser.getDefaultConfiguration()`. +* [ObjectServer] `SyncConfiguration.automatic()` has been deprecated in favour of `SyncUser.getDefaultConfiguration()`. * [ObjectServer] `new SyncConfiguration.Builder(user, url)` has been deprecated in favour of `SyncUser.createConfiguration(url)`. NOTE: Creating configurations using `SyncUser` will default to using query-based Realms, while creating them using `new SyncConfiguration.Builder(user, url)` will default to fully synchronized Realms. * [ObjectServer] With query-based sync being the default `SyncConfiguration.Builder.partialRealm()` has been deprecated. Use ``SyncConfiguration.Builder.fullSynchronization()` if you want full synchronisation instead. From 4740f54edadccfa35752352c2af6b50b363949e5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Jun 2018 11:57:43 +0200 Subject: [PATCH 1242/2110] Release v5.2.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 92baa8632a..7cbea073be 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.2.0-SNAPSHOT +5.2.0 \ No newline at end of file From e7d1d26b1b697804b0de1cacd43488c44cbcd911 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Jun 2018 11:57:43 +0200 Subject: [PATCH 1243/2110] Prepare next release v5.2.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7cbea073be..862529f8ca 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.2.0 \ No newline at end of file +5.2.1-SNAPSHOT \ No newline at end of file From f9ef2824a3b6d67095b4b60c9e1e65100843b0db Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Jun 2018 16:04:47 +0200 Subject: [PATCH 1244/2110] Prepare for next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 862529f8ca..4b448de535 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.2.1-SNAPSHOT \ No newline at end of file +5.3.0-SNAPSHOT \ No newline at end of file From 97988ec2f28fbd6d615a9a63b87bbcc44a0c3f69 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 6 Jun 2018 19:01:31 +0200 Subject: [PATCH 1245/2110] Use new API for getting configurations. (#5995) --- .../java/io/realm/examples/objectserver/CounterActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index 6984f15bf9..ce6b051beb 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -95,7 +95,7 @@ protected void onStart() { if (user == null) { return; } // Create a RealmConfiguration for our user - SyncConfiguration config = new SyncConfiguration.Builder(user, REALM_URL) + SyncConfiguration config = user.createConfiguration(REALM_URL) .initialData(new Realm.Transaction() { @Override public void execute(@Nonnull Realm realm) { From 3aaf5db578083ffacf858bdb95075ddfd7fa3458 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 8 Jun 2018 14:45:40 +0200 Subject: [PATCH 1246/2110] Add getRealm methods (#6000) --- CHANGELOG.md | 7 ++++ .../java/io/realm/RealmListTests.java | 35 +++++++++++++++++++ .../java/io/realm/RealmQueryTests.java | 28 +++++++++++++++ .../java/io/realm/RealmResultsTests.java | 31 ++++++++++++++++ .../io/realm/OrderedRealmCollectionImpl.java | 19 ++++++++++ .../src/main/java/io/realm/RealmList.java | 22 ++++++++++++ .../src/main/java/io/realm/RealmQuery.java | 22 ++++++++++++ 7 files changed, 164 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0a44d4fd6..477e54c306 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.3.0 (YYYY-MM-DD) + +### Enhancements + +* Added `RealmQuery.getRealm()`, `RealmResults.getRealm()`, `RealmList.getRealm()` and `OrderedRealmCollectionSnapshot.getRealm()` (#5997). + + ## 5.2.0 (2018-06-06) The feature previously named Partial Sync is now called Query-Based Sync and is now the default mode when synchronizing Realms. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 3bcb03b5eb..ebe627ac31 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -46,6 +46,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1113,4 +1114,38 @@ public void createSnapshot_shouldUseTargetTable() { assertNotNull(collection.getOsList()); assertEquals(collection.getOsList().getTargetTable().getName(), snapshot.getTable().getName()); } + + @Test + public void getRealm() { + assertTrue(realm == collection.getRealm()); + } + + @Test + public void getRealm_throwsIfDynamicRealm() { + DynamicRealm dRealm = DynamicRealm.getInstance(realm.getConfiguration()); + DynamicRealmObject obj = dRealm.where(Owner.CLASS_NAME).findFirst(); + RealmList list = obj.getList("dogs"); + try { + list.getRealm(); + fail(); + } catch (IllegalStateException ignore) { + } finally { + dRealm.close(); + } + } + + @Test + public void getRealm_throwsIfRealmClosed() { + realm.close(); + try { + collection.getRealm(); + fail(); + } catch (IllegalStateException ignore) { + } + } + + @Test + public void getRealm_returnsNullForUnmanagedList() { + assertNull(new RealmList().getRealm()); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 180c38011c..103038444d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -3475,4 +3475,32 @@ public void alwaysFalse_inverted() { populateTestRealm(); assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).not().alwaysFalse().findAll().size()); } + + @Test + public void getRealm() { + assertTrue(realm == realm.where(AllTypes.class).getRealm()); + } + + @Test + public void getRealm_throwsIfDynamicRealm() { + DynamicRealm dRealm = DynamicRealm.getInstance(realm.getConfiguration()); + try { + dRealm.where(AllTypes.CLASS_NAME).getRealm(); + fail(); + } catch (IllegalStateException ignore) { + } finally { + dRealm.close(); + } + } + + @Test + public void getRealm_throwsIfRealmClosed() { + RealmQuery query = realm.where(AllTypes.class); + realm.close(); + try { + query.getRealm(); + fail(); + } catch (IllegalStateException ignore) { + } + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 6a7e1cdbf3..69364eb7a7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -678,4 +678,35 @@ public void execute(Realm realm) { assertEquals(1, obj.getFieldList().size()); assertEquals(fieldListIntValue, obj.getFieldList().first().getFieldInt()); } + + @Test + public void getRealm() { + RealmResults collection = realm.where(AllTypes.class).findAll(); + assertTrue(realm == collection.getRealm()); + } + + @Test + public void getRealm_throwsIfDynamicRealm() { + DynamicRealm dRealm = DynamicRealm.getInstance(realm.getConfiguration()); + RealmResults collection = dRealm.where(AllTypes.CLASS_NAME).findAll(); + + try { + collection.getRealm(); + fail(); + } catch (IllegalStateException ignore) { + } finally { + dRealm.close(); + } + } + + @Test + public void getRealm_throwsIfRealmClosed() { + RealmResults collection = realm.where(AllTypes.class).findAll(); + realm.close(); + try { + collection.getRealm(); + fail(); + } catch (IllegalStateException ignore) { + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index da24bf45d9..2ce0fc42b3 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -580,6 +580,25 @@ public OrderedRealmCollectionSnapshot createSnapshot() { } } + /** + * Returns the {@link Realm} instance to which this collection belongs. + *

            + * Calling {@link Realm#close()} on the returned instance is discouraged as it is the same as + * calling it on the original Realm instance which may cause the Realm to fully close invalidating the + * query result. + * + * @return {@link Realm} instance this collection belongs to. + * @throws IllegalStateException if the Realm is an instance of {@link DynamicRealm} or the + * {@link Realm} was already closed. + */ + public Realm getRealm() { + realm.checkIfValid(); + if (!(realm instanceof Realm)) { + throw new IllegalStateException("This method is only available for typed Realms"); + } + return (Realm) realm; + } + // Custom RealmResults list iterator. private class RealmCollectionListIterator extends OsResults.ListIterator { RealmCollectionListIterator(int start) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index a1cb1dae20..4642356f34 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -766,6 +766,28 @@ public OrderedRealmCollectionSnapshot createSnapshot() { } } + /** + * Returns the {@link Realm} instance to which this collection belongs. + *

            + * Calling {@link Realm#close()} on the returned instance is discouraged as it is the same as + * calling it on the original Realm instance which may cause the Realm to fully close invalidating the + * list. + * + * @return {@link Realm} instance this collection belongs to or {@code null} if the collection is unmanaged. + * @throws IllegalStateException if the Realm is an instance of {@link DynamicRealm} or the + * {@link Realm} was already closed. + */ + public Realm getRealm() { + if (realm == null) { + return null; + } + realm.checkIfValid(); + if (!(realm instanceof Realm)) { + throw new IllegalStateException("This method is only available for typed Realms"); + } + return (Realm) realm; + } + @Override public String toString() { final String separator = ","; diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 82f884decc..32ddf59c87 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1951,6 +1951,28 @@ public RealmQuery alwaysFalse() { return this; } + /** + * Returns the {@link Realm} instance to which this query belongs. + *

            + * Calling {@link Realm#close()} on the returned instance is discouraged as it is the same as + * calling it on the original Realm instance which may cause the Realm to fully close invalidating the + * query. + * + * @return {@link Realm} instance this query belongs to. + * @throws IllegalStateException if the Realm is an instance of {@link DynamicRealm} or the + * {@link Realm} was already closed. + */ + public Realm getRealm() { + if (realm == null) { + return null; + } + realm.checkIfValid(); + if (!(realm instanceof Realm)) { + throw new IllegalStateException("This method is only available for typed Realms"); + } + return (Realm) realm; + } + private boolean isDynamicQuery() { return className != null; } From 27145b9eb42785a9f3fa2a501bdc8113d1622b17 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 8 Jun 2018 14:46:58 +0200 Subject: [PATCH 1247/2110] Removed 5.1.1 which was never released --- CHANGELOG.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 477e54c306..38c10f6b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This has impacted a number of API's. See below for the details. * [ObjectServer] Added `SyncUser.createConfiguration(url)`. Realms created this way are query-based Realms by default. * [ObjectServer] Added `SyncUser.getDefaultConfiguration()`. * The Realm bytecode transformer now supports incremental builds (#3034). +* Improved speed and allocations when parsing field descriptions in queries (#5547). ### Bug Fixes @@ -31,13 +32,6 @@ This has impacted a number of API's. See below for the details. * Module mediator classes being generated now produces a stable output enabling better support for incremental builds (#3034). -## 5.1.1 (YYYY-MM-DD) - -### Enhancements - -* Improved speed and allocations when parsing field descriptions in queries (#5547). - - ## 5.1.0 (2018-04-25) ### Enhancements From 2176187b8695301bb33fb1227f8094c3a2850570 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 11 Jun 2018 09:19:59 +0200 Subject: [PATCH 1248/2110] Prevent monkey from using system events (#5993) --- examples/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/build.gradle b/examples/build.gradle index d7299615a6..85a4b448a7 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -56,7 +56,7 @@ allprojects { doLast { def numberOfEvents = 2000 def appId = getAppId("${project.projectDir}/build.gradle") - def process = "adb shell monkey -p ${appId} ${numberOfEvents}".execute([], project.rootDir) + def process = "adb shell monkey -p ${appId} --pct-syskeys 0 ${numberOfEvents}".execute([], project.rootDir) def sout = new StringBuilder(), serr = new StringBuilder() process.consumeProcessOutput(sout, serr) From 21a020734803a2bb6a30ca0de8708073d1df6019 Mon Sep 17 00:00:00 2001 From: mansonheart Date: Mon, 11 Jun 2018 10:26:56 +0300 Subject: [PATCH 1249/2110] Removed volatile keyword (#6013) --- .../src/main/java/io/realm/internal/RealmCore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java index f2e3eb300d..0b63b16965 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmCore.java @@ -37,7 +37,7 @@ public class RealmCore { private static final String BINARIES_PATH = "lib" + PATH_SEP + ".." + FILE_SEP + "lib"; private static final String JAVA_LIBRARY_PATH = "java.library.path"; - private static volatile boolean libraryIsLoaded = false; + private static boolean libraryIsLoaded = false; public static boolean osIsWindows() { String os = System.getProperty("os.name").toLowerCase(Locale.getDefault()); From 949f63fcc8b4f5dbf09ae1b0b44ce92a71bb131e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 12 Jun 2018 11:41:10 +0200 Subject: [PATCH 1250/2110] Enable compact for synchronized Realms (#6002) --- CHANGELOG.md | 7 +++ Jenkinsfile | 4 +- dependencies.list | 6 +- .../java/io/realm/SyncConfigurationTests.java | 10 ---- .../java/io/realm/SyncedRealmTests.java | 59 +++++++++++++++++++ .../src/main/cpp/io_realm_internal_Table.cpp | 6 -- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/Realm.java | 5 -- .../java/io/realm/SyncConfiguration.java | 35 ++++++++++- tools/sync_test_server/Dockerfile | 4 ++ tools/sync_test_server/ros/src/index.ts | 2 + tools/sync_test_server/start_server.sh | 8 ++- 12 files changed, 118 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38c10f6b6d..166a68ce38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,15 @@ ### Enhancements +* [ObjectServer] `Realm.compactRealm(config)` now works on synchronized Realms (#5937). +* [ObjectServer] `SyncConfiguration.compactOnLaunch()` and `SyncConfiguration.compactOnLaunch(callback)` has been added (#5937). * Added `RealmQuery.getRealm()`, `RealmResults.getRealm()`, `RealmList.getRealm()` and `OrderedRealmCollectionSnapshot.getRealm()` (#5997). +### Internal + +* Upgraded to Realm Core 5.6.0 +* Upgraded to Realm Sync 3.5.2 + ## 5.2.0 (2018-06-06) diff --git a/Jenkinsfile b/Jenkinsfile index 557e7f03f4..444e84db5f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -42,7 +42,9 @@ try { // Docker image for testing Realm Object Server def dependProperties = readProperties file: 'dependencies.list' def rosVersion = dependProperties["REALM_OBJECT_SERVER_VERSION"] - rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_VERSION=${rosVersion} tools/sync_test_server" + withCredentials([string(credentialsId: 'realm-sync-feature-token-enterprise', variable: 'realmFeatureToken')]) { + rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_VERSION=${rosVersion} --build-arg REALM_FEATURE_TOKEN=${realmFeatureToken} tools/sync_test_server" + } } rosContainer = rosEnv.run() diff --git a/dependencies.list b/dependencies.list index 949504a8b7..1ff0933175 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.0.1 -REALM_SYNC_SHA256=7764304d5dc7db7b4b9be9916f753c14c61c40e9f09fd1d92abeee3d8474405f +REALM_SYNC_VERSION=3.5.2 +REALM_SYNC_SHA256=a056338471770ee915f1bdc3efb69177de18710b1cf98193822520ccf326ba2c # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.1.5 +REALM_OBJECT_SERVER_VERSION=3.6.6 diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index ae0d52c770..c1d65abc22 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -441,16 +441,6 @@ public void toString_nonEmpty() { assertTrue(configStr != null && !configStr.isEmpty()); } - // FIXME: This test can be removed when https://github.com/realm/realm-core/issues/2345 is resolved - @Test(expected = UnsupportedOperationException.class) - public void compact_NotAllowed() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = user.createConfiguration(url).build(); - - Realm.compactRealm(config); - } - // Check that it is possible for multiple users to reference the same Realm URL while each user still use their // own copy on the filesystem. This is e.g. what happens if a Realm is shared using a PermissionOffer. @Test diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index 93e5974d27..0494a395ae 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -23,11 +23,20 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.io.File; +import java.io.IOException; + +import io.realm.entities.AllJavaTypes; +import io.realm.entities.AllTypes; +import io.realm.internal.util.Pair; import io.realm.objectserver.model.PartialSyncObjectA; +import io.realm.objectserver.utils.Constants; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.util.SyncTestUtils; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -52,6 +61,9 @@ public void tearDown() { if (realm != null && !realm.isClosed()) { realm.close(); } + for (SyncUser user : SyncUser.all().values()) { + user.logOut(); + } } private Realm getNormalRealm() { @@ -196,4 +208,51 @@ public void delete_throws() { } } + @Test + public void compactRealm_populatedRealm() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), Constants.DEFAULT_REALM).build(); + realm = Realm.getInstance(config); + realm.executeTransaction(r -> { + for (int i = 0; i < 10; i++) { + r.insert(new AllJavaTypes(i)); + } + }); + realm.close(); + assertTrue(Realm.compactRealm(config)); + realm = Realm.getInstance(config); + assertEquals(10, realm.where(AllJavaTypes.class).count()); + } + + @Test + public void compactOnLaunch_shouldCompact() throws IOException { + SyncUser user = SyncTestUtils.createTestUser(); + + // Fill Realm with data and record size + SyncConfiguration config1 = configFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM).build(); + realm = Realm.getInstance(config1); + byte[] oneMBData = new byte[1024 * 1024]; + realm.beginTransaction(); + for (int i = 0; i < 10; i++) { + realm.createObject(AllTypes.class).setColumnBinary(oneMBData); + } + realm.commitTransaction(); + realm.close(); + long originalSize = new File(realm.getPath()).length(); + + // Open Realm with CompactOnLaunch + SyncConfiguration config2 = configFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM) + .compactOnLaunch(new CompactOnLaunchCallback() { + @Override + public boolean shouldCompact(long totalBytes, long usedBytes) { + return true; + } + }) + .build(); + realm = Realm.getInstance(config2); + realm.close(); + long compactedSize = new File(realm.getPath()).length(); + + assertTrue(originalSize > compactedSize); + } + } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 077b0de3e9..16cd4cad8f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -308,9 +308,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullabl jlong j_column_index, jboolean) { -#if REALM_ENABLE_SYNC - REALM_ASSERT(false); -#endif Table* table = TBL(native_table_ptr); if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { return; @@ -470,9 +467,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNull jlong j_column_index, jboolean is_primary_key) { -#if REALM_ENABLE_SYNC - REALM_ASSERT(false); -#endif try { Table* table = TBL(native_table_ptr); if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index f2a536d29d..58f106676f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit f2a536d29de48e34e60799a5bf3f36e13806387e +Subproject commit 58f106676f96d0a5dcb52b6d705cf20db797d5c6 diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 7ff6e42b79..30125037cd 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1702,13 +1702,8 @@ public static boolean deleteRealm(RealmConfiguration configuration) { * * @param configuration a {@link RealmConfiguration} pointing to a Realm file. * @return {@code true} if successful, {@code false} if any file operation failed. - * @throws UnsupportedOperationException if Realm is synchronized. */ public static boolean compactRealm(RealmConfiguration configuration) { - // FIXME: remove this restriction when https://github.com/realm/realm-core/issues/2345 is resolved - if (configuration.isSyncConfiguration()) { - throw new UnsupportedOperationException("Compacting is not supported yet on synced Realms. See https://github.com/realm/realm-core/issues/2345"); - } return BaseRealm.compactRealm(configuration); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 9496aa33ea..876c10cac7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -143,7 +143,8 @@ private SyncConfiguration(File directory, String serverCertificateFilePath, boolean waitForInitialData, OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy, - boolean isPartial + boolean isPartial, + CompactOnLaunchCallback compactOnLaunch ) { super(directory, filename, @@ -158,7 +159,7 @@ private SyncConfiguration(File directory, rxFactory, initialDataTransaction, readOnly, - null, + compactOnLaunch, false ); @@ -487,6 +488,8 @@ public static final class Builder { private String serverCertificateFilePath; private OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy = OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED; private boolean isPartial = true; // Partial Synchronization is enabled by default + private CompactOnLaunchCallback compactOnLaunch; + /** * Creates an instance of the Builder for the SyncConfiguration. This SyncConfiguration * will be for a fully synchronized Realm. @@ -978,6 +981,31 @@ public SyncConfiguration.Builder fullSynchronization() { return this; } + /** + * Setting this will cause Realm to compact the Realm file if the Realm file has grown too large and a + * significant amount of space can be recovered. See {@link DefaultCompactOnLaunchCallback} for details. + */ + public SyncConfiguration.Builder compactOnLaunch() { + return compactOnLaunch(new DefaultCompactOnLaunchCallback()); + } + + /** + * Sets this to determine if the Realm file should be compacted before returned to the user. It is passed the + * total file size (data + free space) and the bytes used by data in the file. + * + * @param compactOnLaunch a callback called when opening a Realm for the first time during the life of a process + * to determine if it should be compacted before being returned to the user. It is passed + * the total file size (data + free space) and the bytes used by data in the file. + */ + public SyncConfiguration.Builder compactOnLaunch(CompactOnLaunchCallback compactOnLaunch) { + //noinspection ConstantConditions + if (compactOnLaunch == null) { + throw new IllegalArgumentException("A non-null compactOnLaunch must be provided"); + } + this.compactOnLaunch = compactOnLaunch; + return this; + } + private String MD5(String in) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); @@ -1129,7 +1157,8 @@ public SyncConfiguration build() { serverCertificateFilePath, waitForServerChanges, sessionStopPolicy, - isPartial + isPartial, + compactOnLaunch ); } diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index fbb9bb0f86..0516aa6fde 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -5,6 +5,9 @@ RUN cp /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime RUN echo "Europe/Copenhagen" > /etc/timezone ARG ROS_VERSION +ARG REALM_FEATURE_TOKEN +RUN if [ "x$ROS_VERSION" = "x" ] ; then echo Non-empty ROS_VERSION required ; exit 1; fi +RUN if [ "x$REALM_FEATURE_TOKEN" = "x" ] ; then echo Non-empty REALM_FEATURE_TOKEN required ; exit 1; fi # Install netstat (used for debugging) RUN apt-get update \ @@ -18,6 +21,7 @@ RUN apt-get update \ COPY ros /ros WORKDIR "/ros" RUN sed -i -e "s/%ROS_VERSION%/$ROS_VERSION/g" package.json +RUN sed -i -e "s/%REALM_FEATURE_TOKEN%/$REALM_FEATURE_TOKEN/g" src/index.ts RUN npm install WORKDIR "/" diff --git a/tools/sync_test_server/ros/src/index.ts b/tools/sync_test_server/ros/src/index.ts index 2501cf5cd3..9ae228133e 100644 --- a/tools/sync_test_server/ros/src/index.ts +++ b/tools/sync_test_server/ros/src/index.ts @@ -7,6 +7,8 @@ server.start({ // For all the full list of configuration parameters see: // https://realm.io/docs/realm-object-server/latest/api/ros/interfaces/serverconfig.html + featureToken: '%REALM_FEATURE_TOKEN%', + // This is the location where ROS will store its runtime data dataPath: path.join(__dirname, '../data'), diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index d8ff9a0e16..4d4eb01889 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -1,5 +1,11 @@ #!/bin/sh +if [ -z "$REALM_FEATURE_TOKEN" ] +then + echo 'The environment variable $REALM_FEATURE_TOKEN was not set' + exit 1 +fi + # Get the script dir which contains the Dockerfile DOCKERFILE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -11,7 +17,7 @@ adb reverse tcp:9443 tcp:9443 && \ adb reverse tcp:9080 tcp:9080 && \ adb reverse tcp:8888 tcp:8888 || { echo "Failed to reverse adb port." ; exit 1 ; } -docker build $DOCKERFILE_DIR --build-arg ROS_VERSION=$ROS_VERSION -t sync-test-server || { echo "Failed to build Docker image." ; exit 1 ; } +docker build $DOCKERFILE_DIR --build-arg ROS_VERSION=$ROS_VERSION --build-arg REALM_FEATURE_TOKEN=$REALM_FEATURE_TOKEN -t sync-test-server || { echo "Failed to build Docker image." ; exit 1 ; } echo "See log files in $TMP_DIR" docker run -p 9080:9080 -p 9443:9443 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server From 550fa0c0b137b39e5f33069440579027334712bc Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 12 Jun 2018 13:31:12 +0200 Subject: [PATCH 1251/2110] Updated release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 166a68ce38..7bb2d659fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.3.0 (YYYY-MM-DD) +## 5.3.0 (2018-06-12) ### Enhancements From e42d712bb48963675f8bafb79e9feb6e391c039d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 12 Jun 2018 13:31:45 +0200 Subject: [PATCH 1252/2110] Release v5.3.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 4b448de535..e230c8396d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.3.0-SNAPSHOT \ No newline at end of file +5.3.0 \ No newline at end of file From 45b110039e747c6ba500ce8e614f38a08093d0d2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 12 Jun 2018 13:31:45 +0200 Subject: [PATCH 1253/2110] Prepare next release v5.3.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e230c8396d..4077803655 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.3.0 \ No newline at end of file +5.3.1-SNAPSHOT \ No newline at end of file From 71a0e9a6a1122bda5b9ab88fc609f56ff9b8435e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 12 Jun 2018 14:48:44 +0200 Subject: [PATCH 1254/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 4077803655..0984c4c1ad 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.3.1-SNAPSHOT \ No newline at end of file +5.4.0-SNAPSHOT \ No newline at end of file From d66d73c0a1e3f72d13bc71ad4d8cc981febbc04a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 15 Jun 2018 20:52:08 +0200 Subject: [PATCH 1255/2110] Fix rare native crash (#6022) --- CHANGELOG.md | 12 ++++++++++++ dependencies.list | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bb2d659fd..a7e175fbf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 5.3.1 (YYYY-MM-DD) + +### Bug Fixes + +* Fixed rare native crash materializing as `Assertion failed: ref + size <= after_ref with (ref, size, after_ref, ndx, m_free_positions.size())` (#5300). + +### Internal + +* Upgraded to Realm Core 5.6.2 +* Upgraded to Realm Core 3.5.4 + + ## 5.3.0 (2018-06-12) ### Enhancements diff --git a/dependencies.list b/dependencies.list index 1ff0933175..f5c32f339a 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.5.2 -REALM_SYNC_SHA256=a056338471770ee915f1bdc3efb69177de18710b1cf98193822520ccf326ba2c +REALM_SYNC_VERSION=3.5.4 +REALM_SYNC_SHA256=72ff45e9e8d285c1baa13f83492652de2199542dfe80d075951d6277957baaed # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. From 5330e5c224265f76028dcaf8f568cab7efda2912 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 19 Jun 2018 14:29:57 +0100 Subject: [PATCH 1256/2110] Upgrading to Realm Sync v3.5.6 --- CHANGELOG.md | 4 +++- dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e175fbf9..65016510d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,13 @@ ### Bug Fixes * Fixed rare native crash materializing as `Assertion failed: ref + size <= after_ref with (ref, size, after_ref, ndx, m_free_positions.size())` (#5300). +* [ObjectServer] Fixed a bug which could potentially flood Realm Object Server with PING messages. ### Internal * Upgraded to Realm Core 5.6.2 -* Upgraded to Realm Core 3.5.4 +* Upgraded to Realm Sync 3.5.6 +* Upgraded to Object Store commit `0bcb9643b8fb14323df697999b79c4a5341a8a21` ## 5.3.0 (2018-06-12) diff --git a/dependencies.list b/dependencies.list index f5c32f339a..38c280404a 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.5.4 -REALM_SYNC_SHA256=72ff45e9e8d285c1baa13f83492652de2199542dfe80d075951d6277957baaed +REALM_SYNC_VERSION=3.5.6 +REALM_SYNC_SHA256=f24e404bbd649d3f5071ece7eb8ab7d667a3d2a95dc83766ebf8e0534b83125d # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 58f106676f..0bcb9643b8 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 58f106676f96d0a5dcb52b6d705cf20db797d5c6 +Subproject commit 0bcb9643b8fb14323df697999b79c4a5341a8a21 From 72da7be42a6eb1aae214c085993256a437b8120b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 19 Jun 2018 17:00:08 +0200 Subject: [PATCH 1257/2110] Fix Realm.deleteAll() and Realm.isEmpty() (#6024) --- CHANGELOG.md | 4 ++- .../androidTest/java/io/realm/RealmTests.java | 36 +++++++++++++++++++ realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/BaseRealm.java | 5 +-- .../src/main/java/io/realm/DynamicRealm.java | 9 +++++ .../java/io/realm/ImmutableRealmSchema.java | 18 ++++++++++ .../java/io/realm/MutableRealmSchema.java | 16 +++++++++ .../src/main/java/io/realm/Realm.java | 14 ++++++++ .../src/main/java/io/realm/RealmSchema.java | 13 ++----- 9 files changed, 100 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65016510d1..e5d792f42a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ ### Bug Fixes -* Fixed rare native crash materializing as `Assertion failed: ref + size <= after_ref with (ref, size, after_ref, ndx, m_free_positions.size())` (#5300). * [ObjectServer] Fixed a bug which could potentially flood Realm Object Server with PING messages. +* Calling `Realm.deleteAll()` on a Realm file that contains more classes than in the schema throws exception (#5745). +* `Realm.isEmpty()` returning false in some cases, even if all tables part of the schema are empty (#5745). +* Fixed rare native crash materializing as `Assertion failed: ref + size <= after_ref with (ref, size, after_ref, ndx, m_free_positions.size())` (#5300). ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 25f5176911..b3ca5ba035 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -96,6 +96,7 @@ import io.realm.entities.PrimaryKeyRequiredAsBoxedShort; import io.realm.entities.PrimaryKeyRequiredAsString; import io.realm.entities.RandomPrimaryKey; +import io.realm.entities.StringAndInt; import io.realm.entities.StringOnly; import io.realm.entities.StringOnlyReadOnly; import io.realm.exceptions.RealmException; @@ -3790,6 +3791,41 @@ public void deleteAll() { assertTrue(realm.isEmpty()); } + // Test for https://github.com/realm/realm-java/issues/5745 + @Test + public void deleteAll_realmWithMoreTables() { + realm.close(); + RealmConfiguration config1 = configFactory.createConfigurationBuilder() + .name("deleteAllTest.realm") + .schema(StringOnly.class, StringAndInt.class) + .build(); + realm = Realm.getInstance(config1); + realm.executeTransaction(r -> { + r.createObject(StringOnly.class); + r.createObject(StringAndInt.class); + }); + realm.close(); + + RealmConfiguration config2 = configFactory.createConfigurationBuilder() + .name("deleteAllTest.realm") + .schema(StringOnly.class) + .build(); + + realm = Realm.getInstance(config2); + realm.beginTransaction(); + realm.deleteAll(); + realm.commitTransaction(); + assertTrue(realm.isEmpty()); + realm.close(); + + // deleteAll() will only delete tables part of the schema, so reopening with the old + // should reveal the old data + realm = Realm.getInstance(config1); + assertFalse(realm.isEmpty()); + assertEquals(1, realm.where(StringAndInt.class).count()); + } + + @Test public void waitForChange_emptyDataChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 0bcb9643b8..58f106676f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 0bcb9643b8fb14323df697999b79c4a5341a8a21 +Subproject commit 58f106676f96d0a5dcb52b6d705cf20db797d5c6 diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 885ba03f55..2ec27f65bd 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -589,10 +589,7 @@ public boolean isClosed() { * * @return {@code true} if empty, @{code false} otherwise. */ - public boolean isEmpty() { - checkIfValid(); - return sharedRealm.isEmpty(); - } + abstract public boolean isEmpty(); /** * Returns the schema for this Realm. diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index c8e164bc64..92d6e12c1f 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -295,6 +295,15 @@ public Flowable asFlowable() { return configuration.getRxFactory().from(this); } + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() { + checkIfValid(); + return sharedRealm.isEmpty(); + } + // FIXME: Depends on a typed schema. Find a work-around // /** // * {@inheritDoc} diff --git a/realm/realm-library/src/main/java/io/realm/ImmutableRealmSchema.java b/realm/realm-library/src/main/java/io/realm/ImmutableRealmSchema.java index 7740f4899e..35ca6cc8a6 100644 --- a/realm/realm-library/src/main/java/io/realm/ImmutableRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/ImmutableRealmSchema.java @@ -16,7 +16,11 @@ package io.realm; +import java.util.LinkedHashSet; +import java.util.Set; + import io.realm.internal.ColumnIndices; +import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; /** @@ -43,6 +47,20 @@ public RealmObjectSchema get(String className) { return new ImmutableRealmObjectSchema(realm, this, table, getColumnInfo(className)); } + @Override + public Set getAll() { + // Only return schema objects for classes defined by the schema in the RealmConfiguration + RealmProxyMediator schemaMediator = realm.getConfiguration().getSchemaMediator(); + Set> classes = schemaMediator.getModelClasses(); + Set schemas = new LinkedHashSet<>(classes.size()); + for (Class clazz : classes) { + String className = schemaMediator.getSimpleClassName(clazz); + RealmObjectSchema objectSchema = get(className); + schemas.add(objectSchema); + } + return schemas; + } + @Override public RealmObjectSchema create(String className) { throw new UnsupportedOperationException(SCHEMA_IMMUTABLE_EXCEPTION_MSG); diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java index 83c0c72ec0..5506207945 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java @@ -16,7 +16,9 @@ package io.realm; +import java.util.LinkedHashSet; import java.util.Locale; +import java.util.Set; import io.realm.internal.OsObjectStore; import io.realm.internal.Table; @@ -42,6 +44,20 @@ public RealmObjectSchema get(String className) { return new MutableRealmObjectSchema(realm, this, table); } + @Override + public Set getAll() { + // Return all tables prefixed with class__ in the Realm file + int tableCount = (int) realm.getSharedRealm().size(); + Set schemas = new LinkedHashSet<>(tableCount); + for (int i = 0; i < tableCount; i++) { + RealmObjectSchema objectSchema = get(Table.getClassNameForTable(realm.getSharedRealm().getTableName(i))); + if (objectSchema != null) { + schemas.add(objectSchema); + } + } + return schemas; + } + @Override public RealmObjectSchema create(String className) { // Adding a class is always permitted. diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 30125037cd..d6f25dbc53 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -201,6 +201,20 @@ public Flowable asFlowable() { return configuration.getRxFactory().from(this); } + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() { + checkIfValid(); + for (RealmObjectSchema clazz : schema.getAll()) { + if (!clazz.getClassName().startsWith("__") && clazz.getTable().size() > 0) { + return false; + } + } + return true; + } + /** * Returns the schema for this Realm. The schema is immutable. * Any attempt to modify it will result in an {@link UnsupportedOperationException}. diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index bd789c569f..6f0045c270 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -25,6 +25,7 @@ import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; +import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; import io.realm.internal.Util; import io.realm.internal.util.Pair; @@ -81,17 +82,7 @@ public abstract class RealmSchema { * * @return the set of all classes in this Realm or no RealmObject classes can be saved in the Realm. */ - public Set getAll() { - int tableCount = (int) realm.getSharedRealm().size(); - Set schemas = new LinkedHashSet<>(tableCount); - for (int i = 0; i < tableCount; i++) { - RealmObjectSchema objectSchema = get(Table.getClassNameForTable(realm.getSharedRealm().getTableName(i))); - if (objectSchema != null) { - schemas.add(objectSchema); - } - } - return schemas; - } + public abstract Set getAll(); /** * Adds a new class to the Realm. From 5e9e960386301834c376358776c6da38089d62f6 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 19 Jun 2018 16:10:46 +0100 Subject: [PATCH 1258/2110] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5d792f42a..243efa1555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.3.1 (YYYY-MM-DD) +## 5.3.1 (2018-06-19) ### Bug Fixes From 4ee27d5c3f4050fdef48d1fc637c7fd93938e2cb Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 19 Jun 2018 16:10:49 +0100 Subject: [PATCH 1259/2110] Release v5.3.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 4077803655..7d3cdbf0dd 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.3.1-SNAPSHOT \ No newline at end of file +5.3.1 \ No newline at end of file From 1134cca91f9558fbf61ba0a688f20deec161a331 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 19 Jun 2018 16:10:49 +0100 Subject: [PATCH 1260/2110] Prepare next release v5.3.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7d3cdbf0dd..b9d374d027 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.3.1 \ No newline at end of file +5.3.2-SNAPSHOT \ No newline at end of file From ced57ca97312b51a8a17b9529ba8758789595776 Mon Sep 17 00:00:00 2001 From: Gautam Korlam Date: Sat, 23 Jun 2018 10:32:09 -0700 Subject: [PATCH 1261/2110] Remove reliance on groovy in realm-transformer (#6025) --- examples/kotlinExample/build.gradle | 2 +- .../main/groovy/io/realm/gradle/Realm.groovy | 36 +++++++++- .../realm/gradle/RealmPluginExtension.groovy | 68 ------------------- .../groovy/io/realm/gradle/PluginTest.groovy | 19 +++++- realm-transformer/build.gradle | 2 +- .../io/realm/transformer/GroovyUtil.groovy | 50 -------------- .../io/realm/gradle/RealmPluginExtension.java | 25 +++++++ .../main/java/io/realm/transformer/Utils.java | 27 +++++++- .../io/realm/transformer/RealmTransformer.kt | 6 +- .../realm/transformer/build/BuildTemplate.kt | 4 +- realm/build.gradle | 2 +- 11 files changed, 108 insertions(+), 133 deletions(-) delete mode 100644 gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy delete mode 100644 realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy create mode 100644 realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index aa27cdec51..0f7148e338 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.2.10' + ext.kotlin_version = '1.2.40' repositories { jcenter() mavenCentral() diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 6064b95cf9..b566c100bc 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -41,7 +41,6 @@ class Realm implements Plugin { } def syncEnabledDefault = false - def dependencyConfigurationName = getDependencyConfigurationName(project) def usesAptPlugin = project.plugins.findPlugin('com.neenbedankt.android-apt') != null def isKotlinProject = project.plugins.findPlugin('kotlin-android') != null def useKotlinExtensionsDefault = isKotlinProject @@ -49,8 +48,8 @@ class Realm implements Plugin { // TODO add a parameter in 'realm' block if this should be specified by users def preferAptOnKotlinProject = false - - project.extensions.create('realm', RealmPluginExtension, project, syncEnabledDefault, useKotlinExtensionsDefault, dependencyConfigurationName) + def extension = project.extensions.create('realm', RealmPluginExtension) + extension.kotlinExtensionsEnabled = useKotlinExtensionsDefault if (shouldApplyAndroidAptPlugin(usesAptPlugin, isKotlinProject, hasAnnotationProcessorConfiguration, preferAptOnKotlinProject)) { @@ -59,6 +58,7 @@ class Realm implements Plugin { } project.android.registerTransform(new RealmTransformer(project)) + def dependencyConfigurationName = getDependencyConfigurationName(project) project.repositories.add(project.getRepositories().jcenter()) project.dependencies.add(dependencyConfigurationName, "io.realm:realm-annotations:${Version.VERSION}") @@ -73,6 +73,10 @@ class Realm implements Plugin { project.dependencies.add("annotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}") project.dependencies.add("androidTestAnnotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}") } + + project.afterEvaluate { + setDependencies(project, dependencyConfigurationName, extension.syncEnabled, extension.kotlinExtensionsEnabled) + } } private static boolean isTransformAvailable() { @@ -114,4 +118,30 @@ class Realm implements Plugin { // for any Java Projects where user did not apply 'android-apt' plugin manually. return !hasAnnotationProcessorConfiguration } + + private static void setDependencies(Project project, String dependencyConfigurationName, boolean syncEnabled, boolean kotlinExtensionsEnabled) { + // remove libraries first + + def iterator = project.getConfigurations().getByName(dependencyConfigurationName).getDependencies().iterator() + while (iterator.hasNext()) { + def item = iterator.next() + if (item.group == 'io.realm') { + if (item.name.startsWith('realm-android-library')) { + iterator.remove() + } + if (item.name.startsWith('realm-android-kotlin-extensions')) { + iterator.remove() + } + } + } + + // then add again + def syncArtifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" + project.dependencies.add(dependencyConfigurationName, "io.realm:${syncArtifactName}:${Version.VERSION}") + + if (kotlinExtensionsEnabled) { + def kotlinExtArtifactName = "realm-android-kotlin-extensions${syncEnabled ? '-object-server' : ''}" + project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") + } + } } diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy deleted file mode 100644 index fc343412dd..0000000000 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/RealmPluginExtension.groovy +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.gradle - -import org.gradle.api.Project - -class RealmPluginExtension { - private Project project - def boolean syncEnabled - def boolean kotlinExtensionsEnabled - private String dependencyConfigurationName - - RealmPluginExtension(Project project, boolean syncEnabledDefault, boolean useKotlinExtensionsDefault, String dependencyConfigurationName) { - this.project = project - this.dependencyConfigurationName = dependencyConfigurationName - setSyncEnabled(syncEnabledDefault) - setKotlinExtensionsEnabled(useKotlinExtensionsDefault) - } - - void setSyncEnabled(value) { - this.syncEnabled = value - setDependencies(syncEnabled, kotlinExtensionsEnabled) - } - - void setKotlinExtensionsEnabled(value) { - this.kotlinExtensionsEnabled = value - setDependencies(syncEnabled, kotlinExtensionsEnabled) - } - - void setDependencies(boolean syncEnabled, boolean kotlinExtensionsEnabled) { - // remove libraries first - def iterator = project.getConfigurations().getByName(dependencyConfigurationName).getDependencies().iterator() - while (iterator.hasNext()) { - def item = iterator.next() - if (item.group == 'io.realm') { - if (item.name.startsWith('realm-android-library')) { - iterator.remove() - } - if (item.name.startsWith('realm-android-kotlin-extensions')) { - iterator.remove() - } - } - } - - // then add again - def syncArtifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" - project.dependencies.add(dependencyConfigurationName, "io.realm:${syncArtifactName}:${Version.VERSION}") - - if (kotlinExtensionsEnabled) { - def kotlinExtArtifactName = "realm-android-kotlin-extensions${syncEnabled ? '-object-server' : ''}" - project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") - } - } -} diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index e07e0e66f1..1c203b3f04 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -51,21 +51,35 @@ class PluginTest { project.buildscript { repositories { mavenLocal() + google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.1.0-alpha03' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' - classpath "io.realm:realm-gradle-plugin:${currentVersion}" } } + def manifest = project.file("src/main/AndroidManifest.xml") + manifest.parentFile.mkdirs() + manifest.text = '' + project.apply plugin: 'com.android.application' project.apply plugin: 'realm-android' + project.android { + compileSdkVersion 27 + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 27 + } + } + + project.evaluate() + assertTrue(containsDependency(project.dependencies, 'io.realm', 'realm-android-library', currentVersion)) assertTrue(containsDependency(project.dependencies, 'io.realm', 'realm-annotations', currentVersion)) - assertTrue(containsTransform(project.android.transforms, RealmTransformer.class)) } @@ -79,7 +93,6 @@ class PluginTest { dependencies { classpath 'com.android.tools.build:gradle:3.1.0-alpha03' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' - classpath "io.realm:realm-gradle-plugin:${currentVersion}" } } diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 9026350842..c92b91f248 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -60,13 +60,13 @@ sourceSets { } dependencies { - compile localGroovy() compile gradleApi() compile "io.realm:realm-annotations:${version}" compileOnly 'com.android.tools.build:gradle:3.1.1' compile 'org.javassist:javassist:3.21.0-GA' compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" + testCompile localGroovy() testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { exclude module: 'groovy-all' } diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy deleted file mode 100644 index 1fc1028f2d..0000000000 --- a/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.transformer - -import kotlin.collections.EmptyList -import org.gradle.api.Project - -import javax.annotation.Nonnull -import javax.annotation.Nullable; - -/** - * Helper methods for functionality that is really hard to port to Java/Kotlin - */ -class GroovyUtil { - - @Nullable - static String getTargetSdk(@Nonnull Project project) { - return project?.android?.defaultConfig?.targetSdkVersion?.mApiLevel as String - } - - @Nullable - static String getMinSdk(@Nonnull Project project) { - return project?.android?.defaultConfig?.minSdkVersion?.mApiLevel as String - } - - @Nonnull - static boolean isSyncEnabled(@Nonnull Project project) { - return project.realm?.syncEnabled != null && project.realm.syncEnabled - } - - @Nonnull - static Collection getBootClasspath(@Nonnull Project project) { - def classpath = project.android.bootClasspath as Collection - return (classpath != null) ? classpath : Collections.emptyList() - } -} diff --git a/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java b/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java new file mode 100644 index 0000000000..3ec0d8ee73 --- /dev/null +++ b/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.gradle; + +import org.gradle.api.tasks.Input; + +public class RealmPluginExtension { + + @Input public boolean syncEnabled = false; + @Input public boolean kotlinExtensionsEnabled = false; +} diff --git a/realm-transformer/src/main/java/io/realm/transformer/Utils.java b/realm-transformer/src/main/java/io/realm/transformer/Utils.java index a6647b9ac9..8d50ec7ac8 100644 --- a/realm-transformer/src/main/java/io/realm/transformer/Utils.java +++ b/realm-transformer/src/main/java/io/realm/transformer/Utils.java @@ -16,10 +16,15 @@ package io.realm.transformer; -import javax.xml.bind.DatatypeConverter; +import com.android.build.gradle.BaseExtension; +import io.realm.gradle.RealmPluginExtension; +import java.io.File; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.List; +import javax.xml.bind.DatatypeConverter; +import org.gradle.api.Project; public class Utils { @@ -57,4 +62,24 @@ public static String hexStringify(byte[] data) { return stringBuilder.toString(); } + + public static String getTargetSdk(Project project) { + return getAndroidExtension(project).getDefaultConfig().getTargetSdkVersion().getApiString(); + } + + public static String getMinSdk(Project project) { + return getAndroidExtension(project).getDefaultConfig().getMinSdkVersion().getApiString(); + } + + public static boolean isSyncEnabled(Project project) { + return ((RealmPluginExtension) project.getExtensions().getByName("realm")).syncEnabled; + } + + public static List getBootClasspath(Project project) { + return getAndroidExtension(project).getBootClasspath(); + } + + private static BaseExtension getAndroidExtension(Project project) { + return (BaseExtension) project.getExtensions().getByName("android"); + } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 7fdd2db56b..ffbd4d11ea 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -151,11 +151,11 @@ class RealmTransformer(val project: Project) : Transform() { it.packageName } - val targetSdk: String? = GroovyUtil.getTargetSdk(project) - val minSdk: String? = GroovyUtil.getMinSdk(project) + val targetSdk: String? = Utils.getTargetSdk(project) + val minSdk: String? = Utils.getMinSdk(project) if (disableAnalytics) { - val sync: Boolean = GroovyUtil.isSyncEnabled(project) + val sync: Boolean = Utils.isSyncEnabled(project) val analytics = RealmAnalytics(packages as Set, containsKotlin, sync, targetSdk, minSdk) analytics.execute() } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt index fc77a5c215..7d8cd8c9ec 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -22,9 +22,9 @@ import com.android.build.api.transform.TransformInput import com.android.build.api.transform.TransformOutputProvider import com.google.common.io.Files import io.realm.transformer.BytecodeModifier -import io.realm.transformer.GroovyUtil import io.realm.transformer.ManagedClassPool import io.realm.transformer.logger +import io.realm.transformer.Utils import javassist.ClassPool import javassist.CtClass import org.gradle.api.Project @@ -147,7 +147,7 @@ abstract class BuildTemplate(val project: Project, val outputProvider: Transform */ private fun addBootClassesToClassPool(classPool: ClassPool) { try { - GroovyUtil.getBootClasspath(project).forEach { + Utils.getBootClasspath(project).forEach { val path: String = it.absolutePath logger.debug("Add boot class $path to class pool.") classPool.appendClassPath(path) diff --git a/realm/build.gradle b/realm/build.gradle index f18e16dbc4..a1599ab796 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -3,7 +3,7 @@ project.ext.compileSdkVersion = 26 project.ext.buildToolsVersion = '27.0.2' buildscript { - ext.kotlin_version = '1.2.10' + ext.kotlin_version = '1.2.40' ext.dokka_version = '0.9.16' repositories { mavenLocal() From 7e5f3e58fee63768f0ee2104f342b4bb803a25f1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 23 Jun 2018 19:50:48 +0200 Subject: [PATCH 1262/2110] Add credits for Realm Transformer --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 243efa1555..61977723f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.4.0 (YYYY-MM-DD) + +### Credits + +* Thanks to @kageiit for removing Groovy from the Realm Transformer (#3971). + + ## 5.3.1 (2018-06-19) ### Bug Fixes From 0e1464114782f88165309bd0d73d13a8fde72ab7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 25 Jun 2018 09:59:12 +0100 Subject: [PATCH 1263/2110] fixes #5855 (#6010) * Fixes #5855 --- CHANGELOG.md | 6 ++ .../io/realm/RealmChangeListenerTests.java | 56 ++++++++++++++++++- .../androidTest/java/io/realm/RealmTests.java | 51 +++++++---------- .../src/main/java/io/realm/BaseRealm.java | 14 +++-- .../src/main/java/io/realm/RealmObject.java | 13 +++-- .../src/main/java/io/realm/RealmResults.java | 26 ++++++--- 6 files changed, 117 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61977723f0..d24e74b28c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 5.4.0 (YYYY-MM-DD) +### Enhancements + +* Removing a ChangeListener on invalid objects or `RealmResults` should warn instead of throwing (fixes #5855). + ### Credits * Thanks to @kageiit for removing Groovy from the Realm Transformer (#3971). @@ -28,6 +32,8 @@ * [ObjectServer] `Realm.compactRealm(config)` now works on synchronized Realms (#5937). * [ObjectServer] `SyncConfiguration.compactOnLaunch()` and `SyncConfiguration.compactOnLaunch(callback)` has been added (#5937). * Added `RealmQuery.getRealm()`, `RealmResults.getRealm()`, `RealmList.getRealm()` and `OrderedRealmCollectionSnapshot.getRealm()` (#5997). +* Removing a ChangeListener on invalid objects or `RealmResults` should warn instead of throwing (fixes #5855). + ### Internal diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java index 4ac5b1521a..740dddad4f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java @@ -19,6 +19,7 @@ import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; +import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -29,11 +30,14 @@ import io.realm.entities.BacklinksSource; import io.realm.entities.BacklinksTarget; import io.realm.entities.Cat; +import io.realm.entities.StringOnly; import io.realm.entities.pojo.AllTypesRealmModel; +import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -271,7 +275,8 @@ public void onChange(RealmResults backlinksTargets) { // 1. adding a listener if on the parent // 2. modify child // 3. listener is triggered (forward link) - @Test@RunTestInLooperThread + @Test + @RunTestInLooperThread public void listenerOnParentChangeChild() { final long[] nCalls = {0}; final Realm realm = Realm.getInstance(looperThread.getConfiguration()); @@ -299,4 +304,53 @@ public void onChange(RealmResults backlinksSources) { realm.close(); looperThread.testComplete(); } + + @Test + @RunTestInLooperThread + public void removeListenerOnInvalidObjectShouldWarn() { + realm = Realm.getInstance(realmConfig); + RealmChangeListener listener = realmModel -> { + }; + RealmChangeListener> listenerAll = realmModel -> { + }; + + realm.beginTransaction(); + StringOnly stringOnly = realm.createObject(StringOnly.class); + realm.commitTransaction(); + + stringOnly.addChangeListener(listener); + + RealmResults all = realm.where(StringOnly.class).findAll(); + all.addChangeListener(listenerAll); + + realm.close(); + + // add a custom logger to capture expected warning message + TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); + RealmLog.add(testLogger); + + stringOnly.removeChangeListener(listener); + assertThat(testLogger.message, CoreMatchers.containsString( + "Calling removeChangeListener on a closed Realm " + realm.getPath() + ", make sure to close all listeners before closing the Realm.")); + + testLogger.message = ""; + stringOnly.removeAllChangeListeners(); + assertThat(testLogger.message, CoreMatchers.containsString( + "Calling removeChangeListener on a closed Realm " + realm.getPath() + ", make sure to close all listeners before closing the Realm.")); + + + testLogger.message = ""; + all.removeChangeListener(listenerAll); + assertThat(testLogger.message, CoreMatchers.containsString( + "Calling removeChangeListener on a closed Realm " + realm.getPath() + ", make sure to close all listeners before closing the Realm.")); + + testLogger.message = ""; + all.removeAllChangeListeners(); + assertThat(testLogger.message, CoreMatchers.containsString( + "Calling removeChangeListener on a closed Realm " + realm.getPath() + ", make sure to close all listeners before closing the Realm.")); + + RealmLog.remove(testLogger); + + looperThread.testComplete(); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index b3ca5ba035..8ab30fc5a1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -3715,50 +3715,38 @@ public void run() throws Exception { } @Test - public void removeChangeListenerThrowExceptionOnNonLooperThread() { + public void removeChangeListenerThrowExceptionOnWrongThread() { final CountDownLatch signalTestFinished = new CountDownLatch(1); - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - Realm realm = Realm.getInstance(realmConfig); - try { - realm.removeChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm object) { - } - }); - fail("Should not be able to invoke removeChangeListener"); - } catch (IllegalStateException ignored) { - } finally { - realm.close(); - signalTestFinished.countDown(); - } + Realm realm = Realm.getInstance(realmConfig); + Thread thread = new Thread(() -> { + try { + realm.removeChangeListener(object -> {}); + fail("Should not be able to invoke removeChangeListener"); + } catch (IllegalStateException ignored) { + } finally { + signalTestFinished.countDown(); } }); thread.start(); - try { TestHelper.awaitOrFail(signalTestFinished); } finally { thread.interrupt(); + realm.close(); } } @Test - public void removeAllChangeListenersThrowExceptionOnNonLooperThread() { + public void removeAllChangeListenersThrowExceptionOnWrongThreadThread() { final CountDownLatch signalTestFinished = new CountDownLatch(1); - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - Realm realm = Realm.getInstance(realmConfig); - try { - realm.removeAllChangeListeners(); - fail("Should not be able to invoke removeChangeListener"); - } catch (IllegalStateException ignored) { - } finally { - realm.close(); - signalTestFinished.countDown(); - } + Realm realm = Realm.getInstance(realmConfig); + Thread thread = new Thread(() -> { + try { + realm.removeAllChangeListeners(); + fail("Should not be able to invoke removeChangeListener"); + } catch (IllegalStateException ignored) { + } finally { + signalTestFinished.countDown(); } }); thread.start(); @@ -3767,6 +3755,7 @@ public void run() { TestHelper.awaitOrFail(signalTestFinished); } finally { thread.interrupt(); + realm.close(); } } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 2ec27f65bd..83b0bd14bd 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -51,9 +51,7 @@ import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.RealmLog; import io.realm.sync.permissions.ObjectPrivileges; -import io.realm.sync.permissions.RealmPermissions; import io.realm.sync.permissions.RealmPrivileges; -import io.realm.sync.permissions.Role; /** * Base class for all Realm instances. @@ -227,8 +225,10 @@ protected void removeListener(RealmChangeListener liste if (listener == null) { throw new IllegalArgumentException("Listener should not be null"); } - checkIfValid(); - sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); + if (isClosed()) { + RealmLog.warn("Calling removeChangeListener on a closed Realm %s, " + + "make sure to close all listeners before closing the Realm.", configuration.getPath()); + } //noinspection unchecked sharedRealm.realmNotifier.removeChangeListener((T) this, listener); } @@ -260,8 +260,10 @@ protected void removeListener(RealmChangeListener liste * @see io.realm.RealmChangeListener */ protected void removeAllListeners() { - checkIfValid(); - sharedRealm.capabilities.checkCanDeliverNotification("removeListener cannot be called on current thread."); + if (isClosed()) { + RealmLog.warn("Calling removeChangeListener on a closed Realm %s, " + + "make sure to close all listeners before closing the Realm.", configuration.getPath()); + } sharedRealm.realmNotifier.removeChangeListeners(this); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index de4a42443f..06b79e8dfd 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -25,6 +25,7 @@ import io.realm.internal.ManagableObject; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; +import io.realm.log.RealmLog; import io.realm.rx.ObjectChange; /** @@ -584,8 +585,10 @@ public static void removeChangeListener(E object, RealmOb if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); - realm.checkIfValid(); - realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); + if (realm.isClosed()) { + RealmLog.warn("Calling removeChangeListener on a closed Realm %s, " + + "make sure to close all listeners before closing the Realm.", realm.configuration.getPath()); + } //noinspection unchecked proxy.realmGet$proxyState().removeChangeListener(listener); } else { @@ -623,8 +626,10 @@ public static void removeAllChangeListeners(E object) { if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); - realm.checkIfValid(); - realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); + if (realm.isClosed()) { + RealmLog.warn("Calling removeChangeListener on a closed Realm %s, " + + "make sure to close all listeners before closing the Realm.", realm.configuration.getPath()); + } proxy.realmGet$proxyState().removeAllChangeListeners(); } else { throw new IllegalArgumentException("Cannot remove listeners from this unmanaged RealmObject (created outside of Realm)"); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index a2596932d2..0698d6571d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -29,6 +29,7 @@ import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; +import io.realm.log.RealmLog; import io.realm.rx.CollectionChange; /** @@ -169,7 +170,7 @@ public boolean load() { * {@link android.app.IntentService} thread. */ public void addChangeListener(RealmChangeListener> listener) { - checkForAddRemoveListener(listener, true); + checkForAddListener(listener); osResults.addListener(this, listener); } @@ -207,18 +208,29 @@ public void addChangeListener(RealmChangeListener> listener) { * {@link android.app.IntentService} thread. */ public void addChangeListener(OrderedRealmCollectionChangeListener> listener) { - checkForAddRemoveListener(listener, true); + checkForAddListener(listener); osResults.addListener(this, listener); } - private void checkForAddRemoveListener(@Nullable Object listener, boolean checkListener) { - if (checkListener && listener == null) { + private void checkForAddListener(@Nullable Object listener) { + if (listener == null) { throw new IllegalArgumentException("Listener should not be null"); } realm.checkIfValid(); realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); } + private void checkForRemoveListener(@Nullable Object listener, boolean checkListener) { + if (checkListener && listener == null) { + throw new IllegalArgumentException("Listener should not be null"); + } + + if (realm.isClosed()) { + RealmLog.warn("Calling removeChangeListener on a closed Realm %s, " + + "make sure to close all listeners before closing the Realm.", realm.configuration.getPath()); + } + } + /** * Removes all user-defined change listeners. * @@ -226,7 +238,7 @@ private void checkForAddRemoveListener(@Nullable Object listener, boolean checkL * @see io.realm.RealmChangeListener */ public void removeAllChangeListeners() { - checkForAddRemoveListener(null, false); + checkForRemoveListener(null, false); osResults.removeAllListeners(); } @@ -239,7 +251,7 @@ public void removeAllChangeListeners() { * @see io.realm.RealmChangeListener */ public void removeChangeListener(RealmChangeListener> listener) { - checkForAddRemoveListener(listener, true); + checkForRemoveListener(listener, true); osResults.removeListener(this, listener); } @@ -252,7 +264,7 @@ public void removeChangeListener(RealmChangeListener> listener) * @see io.realm.RealmChangeListener */ public void removeChangeListener(OrderedRealmCollectionChangeListener> listener) { - checkForAddRemoveListener(listener, true); + checkForRemoveListener(listener, true); osResults.removeListener(this, listener); } From 780a8527c6739318d5640fc2e28c18abf3f22395 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 26 Jun 2018 15:37:18 +0100 Subject: [PATCH 1264/2110] Fixes #5970 (#6043) * Using Android Network Security Configuration to setup the test certificate for SSL tests --- CHANGELOG.md | 7 + .../src/androidTest/AndroidManifest.xml | 3 +- .../res/raw/android_test_certificate | Bin 0 -> 1484 bytes .../res/xml/network_security_config.xml | 9 ++ ...rustManagerCertificateValidationTests.java | 143 ++++++++++++++++-- .../java/io/realm/SyncManager.java | 4 +- 6 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 realm/realm-library/src/androidTest/res/raw/android_test_certificate create mode 100644 realm/realm-library/src/androidTest/res/xml/network_security_config.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 243efa1555..c16de3c6e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.3.2 (YYYY-MM-DD) + +### Bug Fixes + +* [ObjectServer] Using Android Network Security Configuration is necessary to install the custom root CA for tests (API >= 24) (#5970). + + ## 5.3.1 (2018-06-19) ### Bug Fixes diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index f706bfdeae..e9bb60d73a 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -15,7 +15,8 @@ + android:largeHeap="true" + android:networkSecurityConfig="@xml/network_security_config"> kJHZIOdYMqlm>7+19 ziZNy88;TkTqlmEL#wKYXC(dhVW?*SxZeVF*WMmvA&TE9s9VlltF)1Mj5F;xCa}yIkgFzD$7gG}x zBf}l%O_Mh6`+v*%^Ny?DPV-pn{q}R}GZ{;%xcnEYGo5pGb7BS8hUSK*H;ZD@KGa^h z|8vg1t$xq46C$6#~9&5iRpW^n%(j*bZ8ZvD97-#+_Yui^sqU&*%0A8OYs5Mi|~ z$a#Exd1JPnVEXa78?*OKGy8T#w)u6(-C64V_tWLh8ZazONwW<=TyozQuF<&Y@Ch+?*gLSs>p0EF!r#MI~obJ{W7roBw=;V^6 z^W`q32yeL7oZy%FU5u6crR$rWK@~#53D-NmMwBW%S?%@s^v2kxa36bjmx2jw%L_zo zvYUTuunRSQD_~O54Ju%qCR47No)gz_Bk=E2=h%d`I`LCimFRxzb2fOH{`z<-m$&r> z`*p3i4AiuCCE5)Jr(3160M({&};l7x2G5-R3OI zeH;Bl$21QVNN$dr_m97}d1~|0+l)7kOw50$ntirUZ&PN6{LAq7K1U?ZUUKp~D7m-y zq~G(+tiSS(8gU)|u=mYnmn#h%YPv$FrI*a*JjKoQI%-=;;F<}^FMSR*X)O0ox-a1`(>=NBvr#9P-NC(Gu9`pUC43f^KYX<1b%0@Z`PB-csVAAP zCo&7??fZAP*;L!-j%#DSSiRcwY%BQW%HoeWob4%U&_zCM>R~)TY#k?)#7U*D|r+9ScVXH@r?*06F z?A$D|eG-l;BJbSGKHc6p;}hRsiFM0Ad<+e%n)RtAp!^fZ@h|pYrJ1LDIzC>gefZti ZJ$vSATUZK|L`zK4`gOqD?ZAJxFaV;QXXpR` literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/res/xml/network_security_config.xml b/realm/realm-library/src/androidTest/res/xml/network_security_config.xml new file mode 100644 index 0000000000..40f1d9a749 --- /dev/null +++ b/realm/realm-library/src/androidTest/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java index 81964c00fd..521b5443fa 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java @@ -4,7 +4,6 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,7 +26,6 @@ public static void setUp() { // adb push /tools/sync_test_server/keys/android_test_certificate.crt /sdcard/ // then import the certificate from the device (Settings/Security/Install from storage) @Test - @Ignore("Root certificate have expired. Replace with newer one. https://github.com/realm/realm-java/issues/5970") public void sslVerifyCallback_certificateChainWithRootCAInstalledShouldValidate() { // simulating the following certificate chain // --- @@ -111,9 +109,8 @@ public void sslVerifyCallback_certificateChainWithRootCAInstalledShouldValidate( } @Test - @Ignore("Root certificate have expired. Replace with newer one. https://github.com/realm/realm-java/issues/5970") - public void sslVerifyCallback_shouldVerifyHostname() { - // simulating the following certificate chain + public void sslVerifyCallback_shouldFailOnExpiredCert() { + // simulating the following certificate chain (one of the // --- // Certificate chain // 0 s:/CN=*.ie1.realmlab.net @@ -126,7 +123,7 @@ public void sslVerifyCallback_shouldVerifyHostname() { // i:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority // --- - // ie1.realmlab.net + // ie1.realmlab.net (!!!! EXPIRED on May 3, 2018) String pem_depth0 = "-----BEGIN CERTIFICATE-----\n" + "MIIEWDCCA0CgAwIBAgIQBE6+74j1z/Z88OEsSc3VIzANBgkqhkiG9w0BAQsFADBG\n" + "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRUwEwYDVQQLEwxTZXJ2ZXIg\n" + @@ -239,6 +236,135 @@ public void sslVerifyCallback_shouldVerifyHostname() { String serverAddress = "nabil-test.ie1.realmlab.net"; + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth3, 3)); + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth2, 2)); + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth1, 1)); + assertFalse(SyncManager.sslVerifyCallback(serverAddress, pem_depth0, 0)); + } + + @Test + public void sslVerifyCallback_shouldVerifyHostname() { + // simulating the following certificate chain + + // 0 s:/CN=us1a.cloud.realm.io + // i:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon + String pem_depth0 = "-----BEGIN CERTIFICATE-----\n" + + "MIIEfjCCA2agAwIBAgIQAuZyKHDOzYP160MtNtRBEjANBgkqhkiG9w0BAQsFADBG\n" + + "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRUwEwYDVQQLEwxTZXJ2ZXIg\n" + + "Q0EgMUIxDzANBgNVBAMTBkFtYXpvbjAeFw0xODAyMTkwMDAwMDBaFw0xOTAzMTkx\n" + + "MjAwMDBaMB4xHDAaBgNVBAMTE3VzMWEuY2xvdWQucmVhbG0uaW8wggEiMA0GCSqG\n" + + "SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6XER+3bFiK4TCc5lQv/O3xTc9oC/bcPVr\n" + + "zs52mzcGW/wNH6dxW3i3T3gz3Pit8TDkDf0tzoZNdfr7PYs+BPtinM3ZbKSSnF6G\n" + + "5F8HNpe/1p1blko22wJDa9OyZD4tZ3f6hBlUU+8tHFC2B7BGEzuVKf3Aacap0wdh\n" + + "KsAAaF/mbtLQaelRFtHcIOz2B28e7Fub/iwJGCW79Keq+lDRLG+xayEsBqO3+FJ3\n" + + "h4FxbhsKW/O5tb/5B4dZfgJopWZfcmTUZ89ZX2IYaukfwkrV+/09ZAr87jMi9E7+\n" + + "zU37qHtrWVWQV48BxdWiMmmvJb0ytYM0rxal2YuXi6NOBTP0sbxVAgMBAAGjggGO\n" + + "MIIBijAfBgNVHSMEGDAWgBRZpGYGUqB7lZI8o5QHJ5Z0W/k90DAdBgNVHQ4EFgQU\n" + + "ZNEE3UPcZg2ZOJd4eMZryxUTvKswNQYDVR0RBC4wLIITdXMxYS5jbG91ZC5yZWFs\n" + + "bS5pb4IVKi51czFhLmNsb3VkLnJlYWxtLmlvMA4GA1UdDwEB/wQEAwIFoDAdBgNV\n" + + "HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwOwYDVR0fBDQwMjAwoC6gLIYqaHR0\n" + + "cDovL2NybC5zY2ExYi5hbWF6b250cnVzdC5jb20vc2NhMWIuY3JsMCAGA1UdIAQZ\n" + + "MBcwCwYJYIZIAYb9bAECMAgGBmeBDAECATB1BggrBgEFBQcBAQRpMGcwLQYIKwYB\n" + + "BQUHMAGGIWh0dHA6Ly9vY3NwLnNjYTFiLmFtYXpvbnRydXN0LmNvbTA2BggrBgEF\n" + + "BQcwAoYqaHR0cDovL2NydC5zY2ExYi5hbWF6b250cnVzdC5jb20vc2NhMWIuY3J0\n" + + "MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAAserhwXWohdFjImCcCh\n" + + "0XGW7s47vygasV4kE7vg59dz5RQrVuu+U0HFKTuPw6d4xSaQrUq1wo76RJtZalpG\n" + + "ek9vOvS0GWxjSsts2D0oWZXq772bhlXRfj21NsgwzfWMXIrUaV32l5qDhin1wx7x\n" + + "oZL7mNQ75qFB56jv5zzsX2woFv1GN0a03nFgy9Jk6aWCM5Q3oujrxJJWsgXIMloj\n" + + "uqg+I4MfhTEC1ZnGOEoO4Rq3i1rSLa59mv4lhcO/+yrEENKESgx8/8DnIjQoEuRp\n" + + "QtbxCVxPYfnjBuRuvyTfSo1GMK6SuhvkqVbDhBbRDDCh2T8Nmea3BcFi1kcpImOr\n" + + "MI4=\n" + + "-----END CERTIFICATE-----"; + + // 1 s:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon + // i:/C=US/O=Amazon/CN=Amazon Root CA 1 + String pem_depth1 = "-----BEGIN CERTIFICATE-----\n" + + "MIIESTCCAzGgAwIBAgITBn+UV4WH6Kx33rJTMlu8mYtWDTANBgkqhkiG9w0BAQsF\n" + + "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" + + "b24gUm9vdCBDQSAxMB4XDTE1MTAyMjAwMDAwMFoXDTI1MTAxOTAwMDAwMFowRjEL\n" + + "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEVMBMGA1UECxMMU2VydmVyIENB\n" + + "IDFCMQ8wDQYDVQQDEwZBbWF6b24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" + + "AoIBAQDCThZn3c68asg3Wuw6MLAd5tES6BIoSMzoKcG5blPVo+sDORrMd4f2AbnZ\n" + + "cMzPa43j4wNxhplty6aUKk4T1qe9BOwKFjwK6zmxxLVYo7bHViXsPlJ6qOMpFge5\n" + + "blDP+18x+B26A0piiQOuPkfyDyeR4xQghfj66Yo19V+emU3nazfvpFA+ROz6WoVm\n" + + "B5x+F2pV8xeKNR7u6azDdU5YVX1TawprmxRC1+WsAYmz6qP+z8ArDITC2FMVy2fw\n" + + "0IjKOtEXc/VfmtTFch5+AfGYMGMqqvJ6LcXiAhqG5TI+Dr0RtM88k+8XUBCeQ8IG\n" + + "KuANaL7TiItKZYxK1MMuTJtV9IblAgMBAAGjggE7MIIBNzASBgNVHRMBAf8ECDAG\n" + + "AQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUWaRmBlKge5WSPKOUByeW\n" + + "dFv5PdAwHwYDVR0jBBgwFoAUhBjMhTTsvAyUlC4IWZzHshBOCggwewYIKwYBBQUH\n" + + "AQEEbzBtMC8GCCsGAQUFBzABhiNodHRwOi8vb2NzcC5yb290Y2ExLmFtYXpvbnRy\n" + + "dXN0LmNvbTA6BggrBgEFBQcwAoYuaHR0cDovL2NydC5yb290Y2ExLmFtYXpvbnRy\n" + + "dXN0LmNvbS9yb290Y2ExLmNlcjA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3Js\n" + + "LnJvb3RjYTEuYW1hem9udHJ1c3QuY29tL3Jvb3RjYTEuY3JsMBMGA1UdIAQMMAow\n" + + "CAYGZ4EMAQIBMA0GCSqGSIb3DQEBCwUAA4IBAQCFkr41u3nPo4FCHOTjY3NTOVI1\n" + + "59Gt/a6ZiqyJEi+752+a1U5y6iAwYfmXss2lJwJFqMp2PphKg5625kXg8kP2CN5t\n" + + "6G7bMQcT8C8xDZNtYTd7WPD8UZiRKAJPBXa30/AbwuZe0GaFEQ8ugcYQgSn+IGBI\n" + + "8/LwhBNTZTUVEWuCUUBVV18YtbAiPq3yXqMB48Oz+ctBWuZSkbvkNodPLamkB2g1\n" + + "upRyzQ7qDn1X8nn8N8V7YJ6y68AtkHcNSRAnpTitxBKjtKPISLMVCx7i4hncxHZS\n" + + "yLyKQXhw2W2Xs0qLeC1etA+jTGDK4UfLeC0SF7FSi8o5LL21L8IzApar2pR/\n" + + "-----END CERTIFICATE-----"; + + // 2 s:/C=US/O=Amazon/CN=Amazon Root CA 1 + // i:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 + String pem_depth2 = "-----BEGIN CERTIFICATE-----\n" + + "MIIEkjCCA3qgAwIBAgITBn+USionzfP6wq4rAfkI7rnExjANBgkqhkiG9w0BAQsF\n" + + "ADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNj\n" + + "b3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4x\n" + + "OzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1\n" + + "dGhvcml0eSAtIEcyMB4XDTE1MDUyNTEyMDAwMFoXDTM3MTIzMTAxMDAwMFowOTEL\n" + + "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" + + "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" + + "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" + + "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" + + "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" + + "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" + + "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" + + "jgSubJrIqg0CAwEAAaOCATEwggEtMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\n" + + "BAQDAgGGMB0GA1UdDgQWBBSEGMyFNOy8DJSULghZnMeyEE4KCDAfBgNVHSMEGDAW\n" + + "gBScXwDfqgHXMCs4iKK4bUqc8hGRgzB4BggrBgEFBQcBAQRsMGowLgYIKwYBBQUH\n" + + "MAGGImh0dHA6Ly9vY3NwLnJvb3RnMi5hbWF6b250cnVzdC5jb20wOAYIKwYBBQUH\n" + + "MAKGLGh0dHA6Ly9jcnQucm9vdGcyLmFtYXpvbnRydXN0LmNvbS9yb290ZzIuY2Vy\n" + + "MD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwucm9vdGcyLmFtYXpvbnRydXN0\n" + + "LmNvbS9yb290ZzIuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQsF\n" + + "AAOCAQEAYjdCXLwQtT6LLOkMm2xF4gcAevnFWAu5CIw+7bMlPLVvUOTNNWqnkzSW\n" + + "MiGpSESrnO09tKpzbeR/FoCJbM8oAxiDR3mjEH4wW6w7sGDgd9QIpuEdfF7Au/ma\n" + + "eyKdpwAJfqxGF4PcnCZXmTA5YpaP7dreqsXMGz7KQ2hsVxa81Q4gLv7/wmpdLqBK\n" + + "bRRYh5TmOTFffHPLkIhqhBGWJ6bt2YFGpn6jcgAKUj6DiAdjd4lpFw85hdKrCEVN\n" + + "0FE6/V1dN2RMfjCyVSRCnTawXZwXgWHxyvkQAiSr6w10kY17RSlQOYiypok1JR4U\n" + + "akcjMS9cmvqtmg5iUaQqqcT5NJ0hGA==\n" + + "-----END CERTIFICATE-----"; + + // 3 s:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 + // i:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority + String pem_depth3 = "-----BEGIN CERTIFICATE-----\n" + + "MIIEdTCCA12gAwIBAgIJAKcOSkw0grd/MA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNV\n" + + "BAYTAlVTMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTIw\n" + + "MAYDVQQLEylTdGFyZmllbGQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0\n" + + "eTAeFw0wOTA5MDIwMDAwMDBaFw0zNDA2MjgxNzM5MTZaMIGYMQswCQYDVQQGEwJV\n" + + "UzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UE\n" + + "ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZp\n" + + "ZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEi\n" + + "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVDDrEKvlO4vW+GZdfjohTsR8/\n" + + "y8+fIBNtKTrID30892t2OGPZNmCom15cAICyL1l/9of5JUOG52kbUpqQ4XHj2C0N\n" + + "Tm/2yEnZtvMaVq4rtnQU68/7JuMauh2WLmo7WJSJR1b/JaCTcFOD2oR0FMNnngRo\n" + + "Ot+OQFodSk7PQ5E751bWAHDLUu57fa4657wx+UX2wmDPE1kCK4DMNEffud6QZW0C\n" + + "zyyRpqbn3oUYSXxmTqM6bam17jQuug0DuDPfR+uxa40l2ZvOgdFFRjKWcIfeAg5J\n" + + "Q4W2bHO7ZOphQazJ1FTfhy/HIrImzJ9ZVGif/L4qL8RVHHVAYBeFAlU5i38FAgMB\n" + + "AAGjgfAwge0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0O\n" + + "BBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtV\n" + + "rNzXEMIOqYjnME8GCCsGAQUFBwEBBEMwQTAcBggrBgEFBQcwAYYQaHR0cDovL28u\n" + + "c3MyLnVzLzAhBggrBgEFBQcwAoYVaHR0cDovL3guc3MyLnVzL3guY2VyMCYGA1Ud\n" + + "HwQfMB0wG6AZoBeGFWh0dHA6Ly9zLnNzMi51cy9yLmNybDARBgNVHSAECjAIMAYG\n" + + "BFUdIAAwDQYJKoZIhvcNAQELBQADggEBACMd44pXyn3pF3lM8R5V/cxTbj5HD9/G\n" + + "VfKyBDbtgB9TxF00KGu+x1X8Z+rLP3+QsjPNG1gQggL4+C/1E2DUBc7xgQjB3ad1\n" + + "l08YuW3e95ORCLp+QCztweq7dp4zBncdDQh/U90bZKuCJ/Fp1U1ervShw3WnWEQt\n" + + "8jxwmKy6abaVd38PMV4s/KCHOkdp8Hlf9BRUpJVeEXgSYCfOn8J3/yNTd126/+pZ\n" + + "59vPr5KW7ySaNRB6nJHGDn2Z9j8Z3/VyVOEVqQdZe4O/Ui5GjLIAZHYcSNPYeehu\n" + + "VsyuLAOQ1xk4meTKCRlb/weWsKh/NEnfVqn3sF/tM+2MR7cwA130A4w=\n" + + "-----END CERTIFICATE-----"; + + String serverAddress = "foo.us1a.cloud.realm.io"; + assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth3, 3)); assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth2, 2)); assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth1, 1)); @@ -247,12 +373,11 @@ public void sslVerifyCallback_shouldVerifyHostname() { // reaching depth0 will validate (or not) the entire chain, then removing the PEMs from memory // make sure the hostname verify works - String wrongServerAddress = "hax0r-test.realmlab.net"; + String wrongServerAddress = "hax0r-us1a.cloud2.realm.io"; assertTrue(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth3, 3)); assertTrue(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth2, 2)); assertTrue(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth1, 1)); - // Note hax0r-test.ie1.realmlab.net is valid since the certificate allow *.ie1.realmlab.net - // but the method fails because of the hostname verification + // the method fails because of the hostname verification assertFalse(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth0, 0)); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 775b89954d..390e44782e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -481,7 +481,7 @@ synchronized static boolean sslVerifyCallback(String serverAddress, String pemDa // verify the entire chain try { - TRUST_MANAGER.checkServerTrusted(chain, "RSA"); + TRUST_MANAGER.checkClientTrusted(chain, "RSA"); // verify the hostname boolean isValid = OkHostnameVerifier.INSTANCE.verify(serverAddress, chain[0]); if (isValid) { @@ -521,7 +521,7 @@ private static X509TrustManager systemDefaultTrustManager() { } return (X509TrustManager) trustManagers[0]; } catch (GeneralSecurityException e) { - throw new AssertionError(); // The system has no TLS. Just give up. + throw new IllegalStateException("No System TLS", e); // The system has no TLS. Just give up. } } From aa6ebc71d2f2713fb9d72ee58150393626edf54a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 17 Jul 2018 16:31:17 +0200 Subject: [PATCH 1265/2110] Fix analytics not being sent (#6064) --- .../groovy/io/realm/transformer/GroovyUtil.groovy | 3 +-- .../io/realm/transformer/RealmTransformer.kt | 14 +++++++------- .../io/realm/transformer/build/BuildTemplate.kt | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy index 1fc1028f2d..823fb4e32e 100644 --- a/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy +++ b/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy @@ -16,7 +16,6 @@ package io.realm.transformer -import kotlin.collections.EmptyList import org.gradle.api.Project import javax.annotation.Nonnull @@ -39,7 +38,7 @@ class GroovyUtil { @Nonnull static boolean isSyncEnabled(@Nonnull Project project) { - return project.realm?.syncEnabled != null && project.realm.syncEnabled + return (project.hasProperty("realm")) ? project.realm.syncEnabled : false } @Nonnull diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 7fdd2db56b..8e3c51fd61 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -95,7 +95,7 @@ class RealmTransformer(val project: Project) : Transform() { timer.splitTime("Prepare output classes") if (build.hasNoOutput()) { // Abort transform as quickly as possible if no files where found for processing. - exitTransform(emptySet(), emptyList(), timer) + exitTransform(emptySet(), emptySet(), timer) return } build.prepareReferencedClasses(referencedInputs!!); @@ -111,7 +111,7 @@ class RealmTransformer(val project: Project) : Transform() { exitTransform(inputs, build.getOutputModelClasses(), timer) } - private fun exitTransform(inputs: Collection, outputModelClasses: Collection, timer: Stopwatch) { + private fun exitTransform(inputs: Collection, outputModelClasses: Set, timer: Stopwatch) { timer.stop() this.sendAnalytics(inputs, outputModelClasses) } @@ -122,7 +122,7 @@ class RealmTransformer(val project: Project) : Transform() { * @param inputs the inputs provided by the Transform API * @param inputModelClasses a list of ctClasses describing the Realm models */ - private fun sendAnalytics(inputs: Collection, outputModelClasses: Collection) { + private fun sendAnalytics(inputs: Collection, outputModelClasses: Set) { val disableAnalytics: Boolean = "true".equals(System.getenv()["REALM_DISABLE_ANALYTICS"]) if (inputs.isEmpty() || disableAnalytics) { // Don't send analytics for incremental builds or if they have ben explicitly disabled. @@ -147,16 +147,16 @@ class RealmTransformer(val project: Project) : Transform() { } } - val packages: Collection = outputModelClasses.map { + val packages: Set = outputModelClasses.map { it.packageName - } + }.toSet() val targetSdk: String? = GroovyUtil.getTargetSdk(project) val minSdk: String? = GroovyUtil.getMinSdk(project) - if (disableAnalytics) { + if (!disableAnalytics) { val sync: Boolean = GroovyUtil.isSyncEnabled(project) - val analytics = RealmAnalytics(packages as Set, containsKotlin, sync, targetSdk, minSdk) + val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk) analytics.execute() } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt index fc77a5c215..d2c3920e6c 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -159,8 +159,8 @@ abstract class BuildTemplate(val project: Project, val outputProvider: Transform } } - fun getOutputModelClasses(): Collection { - return outputModelClasses + fun getOutputModelClasses(): Set { + return outputModelClasses.toSet() } protected abstract fun findModelClasses(classNames: Set): Collection From 64b75eda21706a082a38bd8c7e6e0e7a41f2d685 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 17 Jul 2018 16:34:14 +0200 Subject: [PATCH 1266/2110] Remove groovy code --- .../io/realm/transformer/GroovyUtil.groovy | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy diff --git a/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy b/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy deleted file mode 100644 index 823fb4e32e..0000000000 --- a/realm-transformer/src/main/groovy/io/realm/transformer/GroovyUtil.groovy +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.transformer - -import org.gradle.api.Project - -import javax.annotation.Nonnull -import javax.annotation.Nullable; - -/** - * Helper methods for functionality that is really hard to port to Java/Kotlin - */ -class GroovyUtil { - - @Nullable - static String getTargetSdk(@Nonnull Project project) { - return project?.android?.defaultConfig?.targetSdkVersion?.mApiLevel as String - } - - @Nullable - static String getMinSdk(@Nonnull Project project) { - return project?.android?.defaultConfig?.minSdkVersion?.mApiLevel as String - } - - @Nonnull - static boolean isSyncEnabled(@Nonnull Project project) { - return (project.hasProperty("realm")) ? project.realm.syncEnabled : false - } - - @Nonnull - static Collection getBootClasspath(@Nonnull Project project) { - def classpath = project.android.bootClasspath as Collection - return (classpath != null) ? classpath : Collections.emptyList() - } -} From 21e6d193ecacfac65070993c37ab6c829eaaba9c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 17 Jul 2018 19:12:02 +0200 Subject: [PATCH 1267/2110] Fix extension check --- .../src/main/java/io/realm/transformer/Utils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/realm-transformer/src/main/java/io/realm/transformer/Utils.java b/realm-transformer/src/main/java/io/realm/transformer/Utils.java index 8d50ec7ac8..1357940076 100644 --- a/realm-transformer/src/main/java/io/realm/transformer/Utils.java +++ b/realm-transformer/src/main/java/io/realm/transformer/Utils.java @@ -72,7 +72,8 @@ public static String getMinSdk(Project project) { } public static boolean isSyncEnabled(Project project) { - return ((RealmPluginExtension) project.getExtensions().getByName("realm")).syncEnabled; + RealmPluginExtension realmExtension = (RealmPluginExtension) project.getExtensions().findByName("realm"); + return realmExtension != null && realmExtension.syncEnabled; } public static List getBootClasspath(Project project) { From 8b8a00632f19dd2ad1ab5d010d6ae0f7d34471f1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 17 Jul 2018 21:38:17 +0200 Subject: [PATCH 1268/2110] Fix bug when using distinct() and count() (#6062) --- CHANGELOG.md | 2 +- .../java/io/realm/RealmQueryTests.java | 15 +++++++++++ .../src/main/java/io/realm/RealmQuery.java | 26 +++++++++++++++++-- .../java/io/realm/internal/TableQuery.java | 7 +++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c16de3c6e7..cc98c23121 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Bug Fixes * [ObjectServer] Using Android Network Security Configuration is necessary to install the custom root CA for tests (API >= 24) (#5970). - +* `RealmQuery.distinct()` is now correctly applied when calling `RealmQuery.count()` (#5958). ## 5.3.1 (2018-06-19) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 103038444d..1601b59ea7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -24,6 +24,7 @@ import java.lang.reflect.Field; import java.util.Date; import java.util.Locale; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -2278,6 +2279,20 @@ public void count() { assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).count()); } + // Verify that count correctly when using distinct. + // See https://github.com/realm/realm-java/issues/5958 + @Test + public void distinctCount() { + realm.executeTransaction(r -> { + for (int i = 0; i < 5; i++) { + AllTypes obj = new AllTypes(); + obj.setColumnString("Foo"); + realm.copyToRealm(obj); + } + }); + assertEquals(1, realm.where(AllTypes.class).distinct(AllTypes.FIELD_STRING).count()); + } + // Tests isNull on link's nullable field. @Test public void isNull_linkField() { diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 32ddf59c87..10327254d4 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -1749,8 +1749,12 @@ public Date maximumDate(String fieldName) { */ public long count() { realm.checkIfValid(); - - return this.query.count(); + // The fastest way of doing `count()` is going through `TableQuery.count()`. Unfortunately + // doing this does not correctly apply all side effects of queries (like subscriptions). Also + // some queries constructs, like doing distinct is not easily supported this way. + // In order to get the best of both worlds we thus need to create a Java RealmResults object + // and then directly access the `Results` class from Object Store. + return lazyFindAll().size(); } /** @@ -1766,6 +1770,24 @@ public RealmResults findAll() { return createRealmResults(query, sortDescriptor, distinctDescriptor, true, SubscriptionAction.NO_SUBSCRIPTION); } + /** + * The same as {@link #findAll()} expect the RealmResult is not forcefully evaluated. This + * means this method will return a more "pure" wrapper around the Object Store Results class. + * + * This can be useful for internal usage where we still want to take advantage of optimizations + * and additional functionality provided by Object Store, but do not wish to trigger the query + * unless needed. + */ + private OsResults lazyFindAll() { + realm.checkIfValid(); + return createRealmResults( + query, + sortDescriptor, + distinctDescriptor, + false, + SubscriptionAction.NO_SUBSCRIPTION).osResults; + } + /** * Finds all objects that fulfill the query conditions. This method is only available from a Looper thread. *

            diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 1f9ba3d387..2750ad689e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -614,6 +614,13 @@ public long count(long start, long end, long limit) { return nativeCount(nativePtr, start, end, limit); } + /** + * Returns only the number of matching objects. + * This method is very fast compared to evaluating a query completely, but it does not + * goes around any logic implemented in Object Store and other parts of the API that works + * on query results. So the primary use case for this method is testing. + */ + @Deprecated public long count() { validateQuery(); return nativeCount(nativePtr, 0, Table.INFINITE, Table.INFINITE); From 842176d2193de9486c72a35129ebcb06975752d5 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 18 Jul 2018 11:35:58 +0100 Subject: [PATCH 1269/2110] Incremental build causing direct access to model without accessor to fail (#6058) --- CHANGELOG.md | 1 + .../main/kotlin/io/realm/transformer/ByteCodeModifier.kt | 9 ++++++++- .../main/kotlin/io/realm/transformer/RealmTransformer.kt | 8 ++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc98c23121..6bfc2e7945 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Bug Fixes * [ObjectServer] Using Android Network Security Configuration is necessary to install the custom root CA for tests (API >= 24) (#5970). +* Fixes issue with the incremental build causing direct access to model without accessor to fail (#6056). * `RealmQuery.distinct()` is now correctly applied when calling `RealmQuery.count()` (#5958). ## 5.3.1 (2018-06-19) diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/ByteCodeModifier.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/ByteCodeModifier.kt index 7839c95b22..0d972f6784 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/ByteCodeModifier.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/ByteCodeModifier.kt @@ -162,9 +162,16 @@ class BytecodeModifier { @Throws(CannotCompileException::class) override fun edit(fieldAccess: FieldAccess) { logger.debug(" Field being accessed: ${fieldAccess.className}.${fieldAccess.fieldName}") - if (isRealmModelClass(fieldAccess.enclosingClass) && isModelField(fieldAccess.field)) { + + val fieldAccessCtClass: CtClass? = try { classPool.get(fieldAccess.className) } catch (e: NotFoundException) { null } + if (fieldAccessCtClass != null && isRealmModelClass(fieldAccessCtClass) && isModelField(fieldAccess.field)) { logger.debug(" Realm: Manipulating ${ctClass.simpleName}.${behaviour.name}(): ${fieldAccess.fieldName}") logger.debug(" Methods: ${ctClass.declaredMethods}") + + // make sure accessors are added, otherwise javassist will fail with + // javassist.CannotCompileException: [source error] realmGet$id() not found in 'foo.Model' + addRealmAccessors(fieldAccessCtClass) + val fieldName: String = fieldAccess . fieldName if (fieldAccess.isReader) { fieldAccess.replace("\$_ = \$0.realmGet\$$fieldName();") diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 8e3c51fd61..cf7b5767f9 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -98,15 +98,15 @@ class RealmTransformer(val project: Project) : Transform() { exitTransform(emptySet(), emptySet(), timer) return } - build.prepareReferencedClasses(referencedInputs!!); + build.prepareReferencedClasses(referencedInputs!!) timer.splitTime("Prepare referenced classes") build.markMediatorsAsTransformed() timer.splitTime("Mark mediators as transformed") - build.transformModelClasses(); + build.transformModelClasses() timer.splitTime("Transform model classes") - build.transformDirectAccessToModelFields(); + build.transformDirectAccessToModelFields() timer.splitTime("Transform references to model fields") - build.copyResourceFiles(); + build.copyResourceFiles() timer.splitTime("Copy resource files") exitTransform(inputs, build.getOutputModelClasses(), timer) } From b8219c69c9bf728004f513e58d7fbd7e3d097bc4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 22 Jul 2018 13:53:45 +0200 Subject: [PATCH 1270/2110] Upgraded Sync (#6075) --- CHANGELOG.md | 8 ++++++++ dependencies.list | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d56fa1432b..6f174a299b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ * Removing a ChangeListener on invalid objects or `RealmResults` should warn instead of throwing (fixes #5855). +### Internal + +* Upgraded to Realm Core 5.7.2 +* Upgraded to Realm Sync 3.8.1 +* [ObjectServer] Improved performance when integrating changes from the server. +* Added extra information about the state of the Realm file if an exception is thrown due to Realm not being able to open it. +* Removed internal dependency on Groovy in the Realm Transformer (#3971). + ### Credits * Thanks to @kageiit for removing Groovy from the Realm Transformer (#3971). diff --git a/dependencies.list b/dependencies.list index 38c280404a..34ad7bb009 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.5.6 -REALM_SYNC_SHA256=f24e404bbd649d3f5071ece7eb8ab7d667a3d2a95dc83766ebf8e0534b83125d +REALM_SYNC_VERSION=3.8.1 +REALM_SYNC_SHA256=346bc1cfe8e77d20d268ca002a4990dceb29e9d5aa08f9201be80143ebde85a3 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.6.6 +REALM_OBJECT_SERVER_VERSION=3.9.2 From cc1038dc962444e6bbdce86a7e5a4ced86a78437 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 22 Jul 2018 17:57:30 +0200 Subject: [PATCH 1271/2110] Update release date --- CHANGELOG.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f174a299b..873b3d9c6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ -## 5.4.0 (YYYY-MM-DD) +## 5.4.0 (2018-07-22) ### Enhancements * Removing a ChangeListener on invalid objects or `RealmResults` should warn instead of throwing (fixes #5855). +### Bug Fixes + +* [ObjectServer] Using Android Network Security Configuration is necessary to install the custom root CA for tests (API >= 24) (#5970). +* Fixes issue with the incremental build causing direct access to model without accessor to fail (#6056). +* `RealmQuery.distinct()` is now correctly applied when calling `RealmQuery.count()` (#5958). + ### Internal * Upgraded to Realm Core 5.7.2 @@ -17,14 +23,6 @@ * Thanks to @kageiit for removing Groovy from the Realm Transformer (#3971). -## 5.3.2 (YYYY-MM-DD) - -### Bug Fixes - -* [ObjectServer] Using Android Network Security Configuration is necessary to install the custom root CA for tests (API >= 24) (#5970). -* Fixes issue with the incremental build causing direct access to model without accessor to fail (#6056). -* `RealmQuery.distinct()` is now correctly applied when calling `RealmQuery.count()` (#5958). - ## 5.3.1 (2018-06-19) ### Bug Fixes From 9ba855f6fd7a4919eca79145d95697c859758d27 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 22 Jul 2018 17:58:24 +0200 Subject: [PATCH 1272/2110] Release v5.4.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7daa13a179..1e20ec35c6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.4.0-SNAPSHOT +5.4.0 \ No newline at end of file From e9a77112061b975193e6ee9239bbfe7fa5e61135 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 22 Jul 2018 17:58:24 +0200 Subject: [PATCH 1273/2110] Prepare next release v5.4.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 1e20ec35c6..34caa15f76 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.4.0 \ No newline at end of file +5.4.1-SNAPSHOT \ No newline at end of file From 878a17253addc40f3838a72f9f66a1b267a00d62 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 22 Jul 2018 21:23:49 +0200 Subject: [PATCH 1274/2110] Prepare for next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 34caa15f76..a26d4d9728 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.4.1-SNAPSHOT \ No newline at end of file +5.5.0-SNAPSHOT \ No newline at end of file From 789740d8050ba020c8990fd7983fb709b748f7b3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 31 Jul 2018 13:19:59 +0200 Subject: [PATCH 1275/2110] Fix dependencies being changed too late. (#6088) --- CHANGELOG.md | 7 +++ .../main/groovy/io/realm/gradle/Realm.groovy | 24 ++++++--- .../io/realm/gradle/RealmPluginExtension.java | 50 ++++++++++++++++++- .../main/java/io/realm/transformer/Utils.java | 2 +- 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 873b3d9c6f..0e4cbe9a70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.4.1 (YYYY-MM-DD) + +### Bug Fixes + +* Fix Realm Gradle Plugin adding dependencies in a way incompatible with Kotlin Android Extensions. This was introduced in Realm Java 5.4.0 (#6080). + + ## 5.4.0 (2018-07-22) ### Enhancements diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index b566c100bc..5331cb1cd2 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -19,6 +19,7 @@ package io.realm.gradle import com.android.build.gradle.AppPlugin import com.android.build.gradle.LibraryPlugin import com.neenbedankt.gradle.androidapt.AndroidAptPlugin +import io.realm.gradle.RealmPluginExtension import io.realm.transformer.RealmTransformer import org.gradle.api.GradleException import org.gradle.api.Plugin @@ -47,8 +48,20 @@ class Realm implements Plugin { def hasAnnotationProcessorConfiguration = project.getConfigurations().findByName('annotationProcessor') != null // TODO add a parameter in 'realm' block if this should be specified by users def preferAptOnKotlinProject = false - + def dependencyConfigurationName = getDependencyConfigurationName(project) def extension = project.extensions.create('realm', RealmPluginExtension) + extension.addPropertyListener(RealmPluginExtension.KEY_KOTLIN_EXTENSIONS_ENABLED, new RealmPluginExtension.PropertyChangedListener() { + @Override + void onChange(Boolean checked) { + setDependencies(project, dependencyConfigurationName, extension.syncEnabled, extension.kotlinExtensionsEnabled) + } + }) + extension.addPropertyListener(RealmPluginExtension.KEY_SYNC_ENABLED, new RealmPluginExtension.PropertyChangedListener() { + @Override + void onChange(Boolean checked) { + setDependencies(project, dependencyConfigurationName, extension.syncEnabled, extension.kotlinExtensionsEnabled) + } + }) extension.kotlinExtensionsEnabled = useKotlinExtensionsDefault if (shouldApplyAndroidAptPlugin(usesAptPlugin, isKotlinProject, @@ -58,7 +71,6 @@ class Realm implements Plugin { } project.android.registerTransform(new RealmTransformer(project)) - def dependencyConfigurationName = getDependencyConfigurationName(project) project.repositories.add(project.getRepositories().jcenter()) project.dependencies.add(dependencyConfigurationName, "io.realm:realm-annotations:${Version.VERSION}") @@ -73,10 +85,6 @@ class Realm implements Plugin { project.dependencies.add("annotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}") project.dependencies.add("androidTestAnnotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}") } - - project.afterEvaluate { - setDependencies(project, dependencyConfigurationName, extension.syncEnabled, extension.kotlinExtensionsEnabled) - } } private static boolean isTransformAvailable() { @@ -119,9 +127,11 @@ class Realm implements Plugin { return !hasAnnotationProcessorConfiguration } + // This will setup the required dependencies. + // Due to how Gradle works, we have no choice but to run this code every time any of the parameters + // in the Realm extension is changed. private static void setDependencies(Project project, String dependencyConfigurationName, boolean syncEnabled, boolean kotlinExtensionsEnabled) { // remove libraries first - def iterator = project.getConfigurations().getByName(dependencyConfigurationName).getDependencies().iterator() while (iterator.hasNext()) { def item = iterator.next() diff --git a/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java b/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java index 3ec0d8ee73..a83fed35f0 100644 --- a/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java +++ b/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java @@ -18,8 +18,54 @@ import org.gradle.api.tasks.Input; +import java.util.LinkedHashMap; +import java.util.Map; + public class RealmPluginExtension { - @Input public boolean syncEnabled = false; - @Input public boolean kotlinExtensionsEnabled = false; + public static final String KEY_SYNC_ENABLED = "syncEnabled"; + public static final String KEY_KOTLIN_EXTENSIONS_ENABLED = "kotlinExtensionsEnabled"; + + private boolean syncEnabled; + private boolean kotlinExtensionsEnabled; + private Map listeners = new LinkedHashMap<>(); + + @Input + public boolean isSyncEnabled() { + return syncEnabled; + } + + public void setSyncEnabled(boolean syncEnabled) { + this.syncEnabled = syncEnabled; + notifyChange(KEY_SYNC_ENABLED, syncEnabled); + } + + @Input + public boolean isKotlinExtensionsEnabled() { + return kotlinExtensionsEnabled; + } + + public void setKotlinExtensionsEnabled(boolean kotlinExtensionsEnabled) { + this.kotlinExtensionsEnabled = kotlinExtensionsEnabled; + notifyChange(KEY_KOTLIN_EXTENSIONS_ENABLED, kotlinExtensionsEnabled); + } + + public void addPropertyListener(String property, PropertyChangedListener listener) { + listeners.put(property, listener); + } + + private void notifyChange(String key, Object value) { + PropertyChangedListener listener = listeners.get(key); + if (listener != null) { + // Up to users of the API to use the correct generic type, otherwise it will crash + // at runtime. + //noinspection unchecked + listener.onChange(value); + } + } + + // Callback triggered when the extension property is changed + public interface PropertyChangedListener { + void onChange(T value); + } } diff --git a/realm-transformer/src/main/java/io/realm/transformer/Utils.java b/realm-transformer/src/main/java/io/realm/transformer/Utils.java index 1357940076..9a7dc63359 100644 --- a/realm-transformer/src/main/java/io/realm/transformer/Utils.java +++ b/realm-transformer/src/main/java/io/realm/transformer/Utils.java @@ -73,7 +73,7 @@ public static String getMinSdk(Project project) { public static boolean isSyncEnabled(Project project) { RealmPluginExtension realmExtension = (RealmPluginExtension) project.getExtensions().findByName("realm"); - return realmExtension != null && realmExtension.syncEnabled; + return realmExtension != null && realmExtension.isSyncEnabled(); } public static List getBootClasspath(Project project) { From fc730667fcf862495800aabdf3a3fc34f2b04363 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 31 Jul 2018 13:49:54 +0200 Subject: [PATCH 1276/2110] Fix nullpointer when using RealmTransformer (#6087) --- CHANGELOG.md | 1 + latest | 0 .../main/java/io/realm/transformer/Utils.java | 15 ----- .../io/realm/transformer/RealmTransformer.kt | 63 ++++++++++--------- .../realm/transformer/build/BuildTemplate.kt | 3 +- .../io/realm/transformer/ext/CtClassExt.kt | 15 +++++ .../io/realm/transformer/ext/ProjectExt.kt | 48 ++++++++++++++ 7 files changed, 101 insertions(+), 44 deletions(-) create mode 100644 latest create mode 100644 realm-transformer/src/main/kotlin/io/realm/transformer/ext/ProjectExt.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4cbe9a70..7f01af61e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Bug Fixes +* Compile time crash if no `targetSdk` was defined in Gradle. This was introduced in 5.4.0 (#6082). * Fix Realm Gradle Plugin adding dependencies in a way incompatible with Kotlin Android Extensions. This was introduced in Realm Java 5.4.0 (#6080). diff --git a/latest b/latest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/realm-transformer/src/main/java/io/realm/transformer/Utils.java b/realm-transformer/src/main/java/io/realm/transformer/Utils.java index 9a7dc63359..042fe7c6eb 100644 --- a/realm-transformer/src/main/java/io/realm/transformer/Utils.java +++ b/realm-transformer/src/main/java/io/realm/transformer/Utils.java @@ -63,24 +63,9 @@ public static String hexStringify(byte[] data) { return stringBuilder.toString(); } - public static String getTargetSdk(Project project) { - return getAndroidExtension(project).getDefaultConfig().getTargetSdkVersion().getApiString(); - } - - public static String getMinSdk(Project project) { - return getAndroidExtension(project).getDefaultConfig().getMinSdkVersion().getApiString(); - } - public static boolean isSyncEnabled(Project project) { RealmPluginExtension realmExtension = (RealmPluginExtension) project.getExtensions().findByName("realm"); return realmExtension != null && realmExtension.isSyncEnabled(); } - public static List getBootClasspath(Project project) { - return getAndroidExtension(project).getBootClasspath(); - } - - private static BaseExtension getAndroidExtension(Project project) { - return (BaseExtension) project.getExtensions().getByName("android"); - } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 3ee6c117dd..74efc31a08 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -20,6 +20,8 @@ import com.android.build.api.transform.* import io.realm.transformer.build.FullBuild import io.realm.transformer.build.IncrementalBuild import io.realm.transformer.build.BuildTemplate +import io.realm.transformer.ext.getMinSdk +import io.realm.transformer.ext.getTargetSdk import javassist.CtClass import org.gradle.api.Project import org.slf4j.Logger @@ -123,41 +125,46 @@ class RealmTransformer(val project: Project) : Transform() { * @param inputModelClasses a list of ctClasses describing the Realm models */ private fun sendAnalytics(inputs: Collection, outputModelClasses: Set) { - val disableAnalytics: Boolean = "true".equals(System.getenv()["REALM_DISABLE_ANALYTICS"]) - if (inputs.isEmpty() || disableAnalytics) { - // Don't send analytics for incremental builds or if they have ben explicitly disabled. - return - } + try { + val disableAnalytics: Boolean = "true".equals(System.getenv()["REALM_DISABLE_ANALYTICS"], ignoreCase = true) + if (inputs.isEmpty() || disableAnalytics) { + // Don't send analytics for incremental builds or if they have been explicitly disabled. + return + } - var containsKotlin = false - - outer@ - for(input: TransformInput in inputs) { - for (di: DirectoryInput in input.directoryInputs) { - val path: String = di.file.absolutePath - val index: Int = path.indexOf("build${File.separator}intermediates${File.separator}classes") - if (index != -1) { - val projectPath: String = path.substring(0, index) - val buildFile = File(projectPath + "build.gradle") - if (buildFile.exists() && buildFile.readText().contains("kotlin")) { - containsKotlin = true - break@outer + var containsKotlin = false + + outer@ + for(input: TransformInput in inputs) { + for (di: DirectoryInput in input.directoryInputs) { + val path: String = di.file.absolutePath + val index: Int = path.indexOf("build${File.separator}intermediates${File.separator}classes") + if (index != -1) { + val projectPath: String = path.substring(0, index) + val buildFile = File(projectPath + "build.gradle") + if (buildFile.exists() && buildFile.readText().contains("kotlin")) { + containsKotlin = true + break@outer + } } } } - } - val packages: Set = outputModelClasses.map { - it.packageName - }.toSet() + val packages: Set = outputModelClasses.map { + it.packageName + }.toSet() - val targetSdk: String? = Utils.getTargetSdk(project) - val minSdk: String? = Utils.getMinSdk(project) + val targetSdk: String? = project.getTargetSdk() + val minSdk: String? = project.getMinSdk() - if (!disableAnalytics) { - val sync: Boolean = Utils.isSyncEnabled(project) - val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk) - analytics.execute() + if (!disableAnalytics) { + val sync: Boolean = Utils.isSyncEnabled(project) + val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk) + analytics.execute() + } + } catch (e: Exception) { + // Analytics failing for any reason should not crash the build + logger.debug("Could not send analytics: $e") } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt index 3e7c1e9d09..0a73198128 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -25,6 +25,7 @@ import io.realm.transformer.BytecodeModifier import io.realm.transformer.ManagedClassPool import io.realm.transformer.logger import io.realm.transformer.Utils +import io.realm.transformer.ext.getBootClasspath import javassist.ClassPool import javassist.CtClass import org.gradle.api.Project @@ -147,7 +148,7 @@ abstract class BuildTemplate(val project: Project, val outputProvider: Transform */ private fun addBootClassesToClassPool(classPool: ClassPool) { try { - Utils.getBootClasspath(project).forEach { + project.getBootClasspath().forEach { val path: String = it.absolutePath logger.debug("Add boot class $path to class pool.") classPool.appendClassPath(path) diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/ext/CtClassExt.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/ext/CtClassExt.kt index 24a8511672..6a0a106674 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/ext/CtClassExt.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/ext/CtClassExt.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ package io.realm.transformer.ext import javassist.CtClass diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/ext/ProjectExt.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/ext/ProjectExt.kt new file mode 100644 index 0000000000..d33fc33e98 --- /dev/null +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/ext/ProjectExt.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.transformer.ext + +import com.android.build.gradle.BaseExtension +import org.gradle.api.Project +import java.io.File + +/** + * Returns the `targetSdk` property for this project if it is available. + */ +fun Project.getTargetSdk(): String? { + return getAndroidExtension(this).defaultConfig?.targetSdkVersion?.apiString +} + +/** + * Returns the `minSdk` property for this project if it is available. + */ +fun Project.getMinSdk(): String? { + return getAndroidExtension(this).defaultConfig?.minSdkVersion?.apiString +} + +/** + * Returns the `bootClasspath` for this project + */ +fun Project.getBootClasspath(): List { + return getAndroidExtension(this).bootClasspath ?: listOf() +} + +private fun getAndroidExtension(project: Project): BaseExtension { + // This will always be present, otherwise the android build would not be able to + // trigger the transformer code in the first place. + return project.extensions.getByName("android") as BaseExtension +} From 8d0a35708518c040688fb1098e1e6cbf38ad02be Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 3 Aug 2018 07:01:54 +0200 Subject: [PATCH 1277/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f01af61e2..ff2e6fc339 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.4.1 (YYYY-MM-DD) +## 5.4.1 (2018-08-03) ### Bug Fixes From 92d9b2b665278ff80f50d63c67ae74c9a09434e5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 3 Aug 2018 07:02:24 +0200 Subject: [PATCH 1278/2110] Release v5.4.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 34caa15f76..04edabda28 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.4.1-SNAPSHOT \ No newline at end of file +5.4.1 \ No newline at end of file From 7e85e706a5cf7b90768655cdbc0eb018ced36b89 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 3 Aug 2018 07:02:24 +0200 Subject: [PATCH 1279/2110] Prepare next release v5.4.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 04edabda28..86b9b4ba7f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.4.1 \ No newline at end of file +5.4.2-SNAPSHOT \ No newline at end of file From 04ed68237d0adeab6806bf3a042e59dc1f8c614e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 9 Aug 2018 11:08:00 +0200 Subject: [PATCH 1280/2110] Upgrade Sync and ROS (#6101) Upgrade Sync to 3.8.8 and ROS to 3.9.9 --- CHANGELOG.md | 11 +++++++++++ dependencies.list | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff2e6fc339..3945f5bfb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 5.4.2 (YYYY-MM-DD) + +### Bug Fixes + +* [ObjectServer] Fixed bugs in the Sync Client that could lead to memory corruption and crashes. + +### Internal + +* Upgraded to Realm Sync 3.8.8 + + ## 5.4.1 (2018-08-03) ### Bug Fixes diff --git a/dependencies.list b/dependencies.list index 34ad7bb009..9becc34d10 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,8 +1,8 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.8.1 -REALM_SYNC_SHA256=346bc1cfe8e77d20d268ca002a4990dceb29e9d5aa08f9201be80143ebde85a3 +REALM_SYNC_VERSION=3.8.8 +REALM_SYNC_SHA256=a0cbc5b46dbc3a9351bd002ccbcb07bf2a5fd13f9f1b7ef6ed51df931b46587b # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.9.2 +REALM_OBJECT_SERVER_VERSION=3.9.9 From c4efca2f2002d55c6328aa69d1026367fc3dc35a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 9 Aug 2018 11:18:57 +0200 Subject: [PATCH 1281/2110] Add release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3945f5bfb8..7f4774b781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.4.2 (YYYY-MM-DD) +## 5.4.2 (2018-08-09) ### Bug Fixes From 669f67579e141ed5ca438045c1bbe1d3bc439a92 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 9 Aug 2018 11:20:20 +0200 Subject: [PATCH 1282/2110] Release v5.4.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 86b9b4ba7f..f430587706 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.4.2-SNAPSHOT \ No newline at end of file +5.4.2 \ No newline at end of file From 95ea892b5494040d796a9af5015c3935aad60b48 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 9 Aug 2018 11:20:20 +0200 Subject: [PATCH 1283/2110] Prepare next release v5.4.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f430587706..dffb051794 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.4.2 \ No newline at end of file +5.4.3-SNAPSHOT \ No newline at end of file From 047b2080b0288c16bf8bd5e681317d21ae8bada5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 10 Aug 2018 11:20:49 +0200 Subject: [PATCH 1284/2110] Add support for Connection State listeners (#6091) --- CHANGELOG.md | 13 ++ examples/settings.gradle | 1 - .../java/io/realm/SessionTests.java | 12 ++ .../src/main/cpp/io_realm_SyncSession.cpp | 101 ++++++++++- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/ConnectionListener.java | 39 +++++ .../java/io/realm/ConnectionState.java | 59 +++++++ .../java/io/realm/SyncManager.java | 16 ++ .../java/io/realm/SyncSession.java | 163 ++++++++++++++++-- .../java/io/realm/SyncSessionTests.java | 64 +++++++ 10 files changed, 453 insertions(+), 17 deletions(-) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/ConnectionListener.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/ConnectionState.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f4774b781..dd33ec97a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 5.5.0 (YYYY-MM-DD) + +### Enhancements + +* [ObjectServer] Added `ConnectionState` enum describing the states a connection can be in. +* [ObjectServer] Added `SyncSession.isConnected()`. +* [ObjectServer] Added support for observing connection changes for a session using `SyncSession.addConnectionChangeListener()` and `SyncSession.removeConnectionChangeListener()`. + +### Internal + +* Updated to Object Store commit: 97fd03819f398b3c81c8b007feaca8636629050b + + ## 5.4.2 (2018-08-09) ### Bug Fixes diff --git a/examples/settings.gradle b/examples/settings.gradle index 4a5ac90600..15f6d6c37d 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -15,4 +15,3 @@ include 'threadExample' include 'unitTestExample' include 'objectServerExample' include 'multiprocessExample' - diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 458807bab3..6ea9d4956b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -457,4 +457,16 @@ public void getSessionThrowsOnNonExistingSession() { "No SyncSession found using the path : ")); } } + + @Test + public void isConnected_falseForInvalidUser() { + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getSession(configuration); + try { + assertFalse(session.isConnected()); + } finally { + realm.close(); + } + } + } diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 20c8f764cd..140e8e5f1d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -47,8 +47,15 @@ static_assert(SyncSession::PublicState::Dying == static_assert(SyncSession::PublicState::Inactive == static_cast(io_realm_SyncSession_STATE_VALUE_INACTIVE), ""); -static_assert(SyncSession::PublicState::Error == - static_cast(io_realm_SyncSession_STATE_VALUE_ERROR), + +static_assert(SyncSession::ConnectionState::Disconnected == + static_cast(io_realm_SyncSession_CONNECTION_VALUE_DISCONNECTED), + ""); +static_assert(SyncSession::ConnectionState::Connecting == + static_cast(io_realm_SyncSession_CONNECTION_VALUE_CONNECTING), + ""); +static_assert(SyncSession::ConnectionState::Connected == + static_cast(io_realm_SyncSession_CONNECTION_VALUE_CONNECTED), ""); JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JNIEnv* env, jclass, @@ -229,11 +236,97 @@ JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetState(JNIEnv* env, jc return io_realm_SyncSession_STATE_VALUE_DYING; case SyncSession::PublicState::Inactive: return io_realm_SyncSession_STATE_VALUE_INACTIVE; - case SyncSession::PublicState::Error: - return io_realm_SyncSession_STATE_VALUE_ERROR; } } } CATCH_STD() return -1; } + +JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetConnectionState(JNIEnv* env, jclass, jstring j_local_realm_path) +{ + TR_ENTER() + try { + JStringAccessor local_realm_path(env, j_local_realm_path); + auto session = SyncManager::shared().get_existing_session(local_realm_path); + + if (session) { + switch (session->connection_state()) { + case SyncSession::ConnectionState::Disconnected: + return io_realm_SyncSession_CONNECTION_VALUE_DISCONNECTED; + case SyncSession::ConnectionState::Connecting: + return io_realm_SyncSession_CONNECTION_VALUE_CONNECTING; + case SyncSession::ConnectionState::Connected: + return io_realm_SyncSession_CONNECTION_VALUE_CONNECTED; + } + } + } + CATCH_STD() + return -1; +} + +static jlong get_connection_value(SyncSession::ConnectionState state) { + switch (state) { + case SyncSession::ConnectionState::Disconnected: return static_cast(io_realm_SyncSession_CONNECTION_VALUE_DISCONNECTED); + case SyncSession::ConnectionState::Connecting: return static_cast(io_realm_SyncSession_CONNECTION_VALUE_CONNECTING); + case SyncSession::ConnectionState::Connected: return static_cast(io_realm_SyncSession_CONNECTION_VALUE_CONNECTED); + } + return static_cast(-1); +} + +JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddConnectionListener(JNIEnv* env, jclass, jstring j_local_realm_path) +{ + try { + // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda + std::string local_realm_path(JStringAccessor(env, j_local_realm_path)); + std::shared_ptr session = SyncManager::shared().get_existing_session(local_realm_path); + if (!session) { + // FIXME: We should lift this restriction + ThrowException(env, IllegalState, + "Cannot register a connection listener before a session is " + "created. A session will be created after the first call to Realm.getInstance()."); + return 0; + } + + static JavaClass java_syncmanager_class(env, "io/realm/SyncManager"); + static JavaMethod java_notify_connection_listener(env, java_syncmanager_class, "notifyConnectionListeners", "(Ljava/lang/String;JJ)V", true); + + std::function callback = [local_realm_path](SyncSession::ConnectionState old_state, SyncSession::ConnectionState new_state) { + JNIEnv* local_env = jni_util::JniUtils::get_env(true); + + jlong old_connection_value = get_connection_value(old_state); + jlong new_connection_value = get_connection_value(new_state); + + JavaLocalRef path(local_env, to_jstring(local_env, local_realm_path)); + local_env->CallStaticVoidMethod(java_syncmanager_class, java_notify_connection_listener, path.get(), + old_connection_value, new_connection_value); + + // All exceptions will be caught on the Java side of handlers, but Errors will still end + // up here, so we need to do something sensible with them. + // Throwing a C++ exception will terminate the sync thread and cause the pending Java + // exception to become visible. For some (unknown) reason Logcat will not see the C++ + // exception, only the Java one. + if (local_env->ExceptionCheck()) { + local_env->ExceptionDescribe(); + throw std::runtime_error("An unexpected Error was thrown from Java. See LogCat"); + } + }; + uint64_t token = session->register_connection_change_callback(callback); + return static_cast(token); + } + CATCH_STD() + return 0; +} + +JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveConnectionListener(JNIEnv* env, jclass, jlong listener_id, jstring j_local_realm_path) +{ + try { + // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda + std::string local_realm_path(JStringAccessor(env, j_local_realm_path)); + std::shared_ptr session = SyncManager::shared().get_existing_session(local_realm_path); + if (session) { + session->unregister_connection_change_callback(static_cast(listener_id)); + } + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 58f106676f..97fd03819f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 58f106676f96d0a5dcb52b6d705cf20db797d5c6 +Subproject commit 97fd03819f398b3c81c8b007feaca8636629050b diff --git a/realm/realm-library/src/objectServer/java/io/realm/ConnectionListener.java b/realm/realm-library/src/objectServer/java/io/realm/ConnectionListener.java new file mode 100644 index 0000000000..43bed189a7 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/ConnectionListener.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm; + +/** + * Interface used when reporting changes that happened to the connection used by the session. + *

            + * Multiple sessions might re-use the same connection. In that case, any connection + * change will be reported to all sessions. + *

            + * If a disconnect happened due to an error, that error will be reported to the sessions + * {@link io.realm.SyncSession.ErrorHandler}. + * + * @see SyncSession#isConnected() + * @see SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler) + */ +public interface ConnectionListener { + + /** + * A change in the connection to the server was detected. + * + * @param oldState the state the connection transitioned from. + * @param newState the state the connection transitioned to. + */ + void onChange(ConnectionState oldState, ConnectionState newState); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ConnectionState.java b/realm/realm-library/src/objectServer/java/io/realm/ConnectionState.java new file mode 100644 index 0000000000..e603160f93 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/ConnectionState.java @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm; + +/** + * Enum describing the states of the underlying connection used by a {@link SyncSession}. + */ +public enum ConnectionState { + + /** + * No connection to the server exists. No data is being transferred even if the session + * is {@link SyncSession.State#ACTIVE}. If the connection entered this state due to an error, this + * error will be reported to the {@link SyncSession.ErrorHandler}. + */ + DISCONNECTED(SyncSession.CONNECTION_VALUE_DISCONNECTED), + + /** + * A connection is currently in progress of being established. If successful the next + * state is {@link #CONNECTED}. If the connection fails it will be {@link #DISCONNECTED}. + */ + CONNECTING(SyncSession.CONNECTION_VALUE_CONNECTING), + + /** + * A connection was successfully established to the server. If the SyncSession is {@link SyncSession.State#ACTIVE} + * data will now be transferred between the device and the server. + */ + CONNECTED(SyncSession.CONNECTION_VALUE_CONNECTED); + + final int value; + + ConnectionState(int value) { + this.value = value; + } + + static ConnectionState fromNativeValue(long value) { + ConnectionState[] stateCodes = values(); + for (ConnectionState state : stateCodes) { + if (state.value == value) { + return state; + } + } + + throw new IllegalArgumentException("Unknown connection state code: " + value); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 390e44782e..ce556daad1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -400,6 +400,22 @@ private static synchronized void notifyProgressListener(String localRealmPath, l } } + /** + * Called from native code. This method is not allowed to throw as it would be swallowed + * by the native Sync Client thread. Instead log all exceptions to logcat. + */ + @SuppressWarnings("unused") + private static synchronized void notifyConnectionListeners(String localRealmPath, long oldState, long newState) { + SyncSession session = sessions.get(localRealmPath); + if (session != null) { + try { + session.notifyConnectionListeners(ConnectionState.fromNativeValue(oldState), ConnectionState.fromNativeValue(newState)); + } catch (Exception exception) { + RealmLog.error(exception); + } + } + } + /** * This is called from the Object Store (through JNI) to request an {@code access_token} for * the session specified by sessionPath. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index f05656a86f..f099649332 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.Locale; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; @@ -36,6 +37,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; + import io.realm.internal.Keep; import io.realm.internal.SyncObjectServerFacade; import io.realm.internal.Util; @@ -50,15 +53,23 @@ import io.realm.log.RealmLog; /** - * This class represents the connection to the Realm Object Server for one {@link SyncConfiguration}. + * A session controls how data is synchronized between a single Realm on the device and the server + * Realm on the Realm Object Server. *

            - * A Session is created by opening a Realm instance using that configuration. Once a session has been created, + * A Session is created by opening a Realm instance using a {@link SyncConfiguration}. Once a session has been created, * it will continue to exist until the app is closed or all threads using this {@link SyncConfiguration} closes their respective {@link Realm}s. *

            - * A session is fully controlled by Realm, but can provide additional information in case of errors. - * It is passed along in all {@link SyncSession.ErrorHandler}s. + * A session is controlled by Realm, but can provide additional information in case of errors. + * These errors are passed along in the {@link SyncSession.ErrorHandler}. + *

            + * When creating a session, Realm will establish a connection to the server. This connection is + * controlled by Realm and might be shared between multiple sessions. It is possible to get insight + * into the connection using {@link #addConnectionChangeListener(ConnectionListener)} and {@link #isConnected()}. + *

            + * The session itself has a different lifecycle than the underlying connection. The state of the session + * can be found using {@link #getState()}. *

            - * This object is thread safe. + * The {@link SyncSession} object is thread safe. */ @Keep public class SyncSession { @@ -101,13 +112,67 @@ public class SyncSession { private static final byte STATE_VALUE_INACTIVE = 3; private static final byte STATE_VALUE_ERROR = 4; + // List of Java connection change listeners + private final CopyOnWriteArrayList connectionListeners = new CopyOnWriteArrayList<>(); + + // Reference to the token representing the native listener for connection changes + // Only one native listener is used for all Java listeners + private long nativeConnectionListenerToken; + + // represent different states as defined in SyncSession::PublicConnectionState 'sync_session.hpp' + // saved here instead of as constants in ConnectionState.java to enable static checking by JNI + static final byte CONNECTION_VALUE_DISCONNECTED = 0; + static final byte CONNECTION_VALUE_CONNECTING = 1; + static final byte CONNECTION_VALUE_CONNECTED = 2; + private URI resolvedRealmURI; + /** + * Enum describing the states a SyncSession can be in. The initial state is + * {@link State#INACTIVE}. + *

            + * A Realm will automatically synchronize data with the server if the session is either {@link State#ACTIVE} + * or {@link State#DYING} and {@link #isConnected()} returns {@code true}. + */ public enum State { + + /** + * This is the initial state. The session is closed. No data is being synchronized. The session + * will automatically transition to {@link #WAITING_FOR_ACCESS_TOKEN} when a Realm is opened. + */ + INACTIVE(STATE_VALUE_INACTIVE), + + /** + * The user is attempting to synchronize data but needs a valid access token to do so. Realm + * will either use a cached token or automatically try to acquire one based on the current + * users login. This requires a network connection. + *

            + * Data cannot be synchronized in this state. + *

            + * Once a valid token is acquired, the session will transition to {@link #ACTIVE}. + */ WAITING_FOR_ACCESS_TOKEN(STATE_VALUE_WAITING_FOR_ACCESS_TOKEN), + + /** + * The Realm is open and data will be synchronized between the device and the server + * if the underlying connection is {@link ConnectionState#CONNECTED}. + *

            + * The session will remain in this state until either the current login expires or the Realm + * is closed. In the first case, the session will transition to {@link #WAITING_FOR_ACCESS_TOKEN}, + * in the second case, it will become {@link #DYING}. + */ ACTIVE(STATE_VALUE_ACTIVE), + + /** + * The Realm was closed, but still contains data that needs to be synchronized to the server. + * The session will attempt to upload all local data before going {@link #INACTIVE}. + */ DYING(STATE_VALUE_DYING), - INACTIVE(STATE_VALUE_INACTIVE), + + /** + * DEPRECATED: This is never used. Errors are reported to {@link ErrorHandler} instead. + */ + @Deprecated ERROR(STATE_VALUE_ERROR); final byte value; @@ -116,7 +181,7 @@ public enum State { this.value = value; } - static State fromByte(byte value) { + static State fromNativeValue(long value) { State[] stateCodes = values(); for (State state : stateCodes) { if (state.value == value) { @@ -124,7 +189,7 @@ static State fromByte(byte value) { } } - throw new IllegalArgumentException("Unknown state code: " + value); + throw new IllegalArgumentException("Unknown session state code: " + value); } } @@ -188,14 +253,45 @@ void notifySessionError(int errorCode, String errorMessage) { * @return the state of the session. * @see SyncSession.State */ - @SuppressWarnings("unused") public State getState() { byte state = nativeGetState(configuration.getPath()); if (state == -1) { // session was not found, probably the Realm was closed throw new IllegalStateException("Could not find session, Realm was probably closed"); } - return State.fromByte(state); + return State.fromNativeValue(state); + } + + /** + * Get the current state of the connection used by the session as defined in {@link ConnectionState}. + * + * @return the state of connection used by the session. + * @see ConnectionState + */ + public ConnectionState getConnectionState() { + byte state = nativeGetConnectionState(configuration.getPath()); + if (state == -1) { + // session was not found, probably the Realm was closed + throw new IllegalStateException("Could not find session, Realm was probably closed"); + } + return ConnectionState.fromNativeValue(state); + } + + /** + * Checks if the session is connected to the server and can synchronize data. + * + * This is a best guess effort. To conserve battery the underlying implementation uses heartbeats + * to detect if the connection is still available. So if no data is actively being synced + * and some time has elapsed since the last heartbeat, the connection could have been dropped but + * this method will still return {@code true}. + * + * @return {@code true} if the session is connected and ready to synchronize data, {@code false} + * if not or if it is in the process of connecting. + */ + public boolean isConnected() { + ConnectionState connectionState = ConnectionState.fromNativeValue(nativeGetConnectionState(configuration.getPath())); + State sessionState = getState(); + return (sessionState == State.ACTIVE || sessionState == State.DYING) && connectionState == ConnectionState.CONNECTED; } synchronized void notifyProgressListener(long listenerId, long transferredBytes, long transferableBytes) { @@ -210,7 +306,13 @@ synchronized void notifyProgressListener(long listenerId, long transferredBytes, RealmLog.debug("Trying unknown listener failed: " + listenerId); } } - + + void notifyConnectionListeners(ConnectionState oldState, ConnectionState newState) { + for (ConnectionListener listener : connectionListeners) { + listener.onChange(oldState, newState); + } + } + /** * Adds a progress listener tracking changes that need to be downloaded from the Realm Object * Server. @@ -297,6 +399,36 @@ private void checkProgressListenerArguments(ProgressMode mode, ProgressListener } } + /** + * Adds a listener tracking changes to the connection backing this session. See {@link ConnectionState} + * for further details. + * + * @param listener the listener to register. + * @throws IllegalArgumentException if the listener is {@code null}. + * @see ConnectionState + */ + public synchronized void addConnectionChangeListener(ConnectionListener listener) { + checkNonNullListener(listener); + if (connectionListeners.isEmpty()) { + nativeConnectionListenerToken = nativeAddConnectionListener(configuration.getPath()); + } + connectionListeners.add(listener); + } + + /** + * Removes a previously registered {@link ConnectionListener}. + * + * @param listener listener to remove + * @throws IllegalArgumentException if the listener is {@code null}. + */ + public synchronized void removeConnectionChangeListener(ConnectionListener listener) { + checkNonNullListener(listener); + connectionListeners.remove(listener); + if (connectionListeners.isEmpty()) { + nativeRemoveConnectionListener(nativeConnectionListenerToken, configuration.getPath()); + } + } + void close() { isClosed = true; if (networkRequest != null) { @@ -438,6 +570,12 @@ private void checkIfNotOnMainThread(String errorMessage) { } } + private void checkNonNullListener(@Nullable Object listener) { + if (listener == null) { + throw new IllegalArgumentException("Non-null 'listener' required."); + } + } + /** * Interface used to report any session errors. * @@ -712,10 +850,13 @@ public void throwExceptionIfNeeded() { } } + private static native long nativeAddConnectionListener(String localRealmPath); + private static native void nativeRemoveConnectionListener(long listenerId, String localRealmPath); private static native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); private static native void nativeRemoveProgressListener(String localRealmPath, long listenerToken); private static native boolean nativeRefreshAccessToken(String localRealmPath, String accessToken, String realmUrl); private native boolean nativeWaitForDownloadCompletion(int callbackId, String localRealmPath); private native boolean nativeWaitForUploadCompletion(int callbackId, String localRealmPath); private static native byte nativeGetState(String localRealmPath); + private static native byte nativeGetConnectionState(String localRealmPath); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 7ccccc9e46..c14df626b9 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -510,4 +510,68 @@ public void run() { SyncManager.simulateClientReset(SyncManager.getSession(config)); } + @Test + @RunTestInLooperThread + public void registerConnectionListener() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build(); + Realm realm = Realm.getInstance(syncConfiguration); + SyncSession session = SyncManager.getSession(syncConfiguration); + session.addConnectionChangeListener((oldState, newState) -> { + if (newState == ConnectionState.DISCONNECTED) { + looperThread.testComplete(); + } + }); + realm.close(); + } + + @Test + @RunTestInLooperThread + public void removeConnectionListener() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build(); + Realm realm = Realm.getInstance(syncConfiguration); + SyncSession session = SyncManager.getSession(syncConfiguration); + ConnectionListener listener1 = (oldState, newState) -> { + if (newState == ConnectionState.DISCONNECTED) { + fail("Listener should have been removed"); + } + }; + ConnectionListener listener2 = (oldState, newState) -> { + if (newState == ConnectionState.DISCONNECTED) { + looperThread.testComplete(); + } + }; + + session.addConnectionChangeListener(listener1); + session.addConnectionChangeListener(listener2); + session.removeConnectionChangeListener(listener1); + realm.close(); + } + + @Test + @RunTestInLooperThread + public void isConnected() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build(); + looperThread.closeAfterTest(Realm.getInstance(syncConfiguration)); + SyncSession session = SyncManager.getSession(syncConfiguration); + if (session.isConnected()) { + looperThread.testComplete(); + } else { + session.addConnectionChangeListener(((oldState, newState) -> { + if (newState == ConnectionState.CONNECTED) { + assertEquals(session.getConnectionState(), ConnectionState.CONNECTED); + assertTrue(session.isConnected()); + looperThread.testComplete(); + } + })); + } + } } From 7d8569355a24bf79c84c2970f88a62c20261c3f5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 14 Aug 2018 10:13:00 +0200 Subject: [PATCH 1285/2110] Add Build Transformer stripping Sync methods (#6069) --- CHANGELOG.md | 4 + build.gradle | 9 + library-build-transformer/.gitignore | 1 + library-build-transformer/README.md | 41 +++++ library-build-transformer/build.gradle | 106 +++++++++++ library-build-transformer/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + library-build-transformer/gradlew | 172 ++++++++++++++++++ library-build-transformer/gradlew.bat | 84 +++++++++ library-build-transformer/settings.gradle | 1 + .../buildtransformer/RealmBuildTransformer.kt | 160 ++++++++++++++++ .../asm/ClassPoolTransformer.kt | 98 ++++++++++ .../asm/visitors/AnnotatedCodeStripVisitor.kt | 84 +++++++++ .../asm/visitors/AnnotationVisitor.kt | 68 +++++++ .../io/realm/buildtransformer/ext/FileExt.kt | 45 +++++ .../realm/buildtransformer/util/Stopwatch.kt | 63 +++++++ .../testclasses/NestedTestClass.java | 43 +++++ .../testclasses/SimpleTestClass.java | 27 +++ .../testclasses/SimpleTestFields.java | 25 +++ .../testclasses/SimpleTestMethods.java | 36 ++++ .../testclasses/SubClass.java | 20 ++ .../testclasses/SuperClass.java | 22 +++ .../internal/annotations/ObjectServer.java | 29 +++ .../buildtransformer/DynamicClassLoader.kt | 34 ++++ .../io/realm/buildtransformer/VisitorTests.kt | 142 +++++++++++++++ realm/build.gradle | 1 + realm/kotlin-extensions/build.gradle | 7 +- realm/realm-library/build.gradle | 15 +- 29 files changed, 1336 insertions(+), 7 deletions(-) create mode 100644 library-build-transformer/.gitignore create mode 100644 library-build-transformer/README.md create mode 100644 library-build-transformer/build.gradle create mode 100644 library-build-transformer/gradle.properties create mode 100644 library-build-transformer/gradle/wrapper/gradle-wrapper.jar create mode 100644 library-build-transformer/gradle/wrapper/gradle-wrapper.properties create mode 100755 library-build-transformer/gradlew create mode 100755 library-build-transformer/gradlew.bat create mode 100644 library-build-transformer/settings.gradle create mode 100644 library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt create mode 100644 library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/ClassPoolTransformer.kt create mode 100644 library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt create mode 100644 library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt create mode 100644 library-build-transformer/src/main/kotlin/io/realm/buildtransformer/ext/FileExt.kt create mode 100644 library-build-transformer/src/main/kotlin/io/realm/buildtransformer/util/Stopwatch.kt create mode 100644 library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/NestedTestClass.java create mode 100644 library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestClass.java create mode 100644 library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestFields.java create mode 100644 library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestMethods.java create mode 100644 library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SubClass.java create mode 100644 library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SuperClass.java create mode 100644 library-build-transformer/src/test/java/io/realm/internal/annotations/ObjectServer.java create mode 100644 library-build-transformer/src/test/kotlin/io/realm/buildtransformer/DynamicClassLoader.kt create mode 100644 library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index dd33ec97a5..f4aa85b6ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ * [ObjectServer] Added `SyncSession.isConnected()`. * [ObjectServer] Added support for observing connection changes for a session using `SyncSession.addConnectionChangeListener()` and `SyncSession.removeConnectionChangeListener()`. +### Bug Fixes + +* Methods and classes requiring synchronized Realms have been removed from the standard AAR package. They are now only visible when enabling synchronized Realms in Gradle. The methods and classes will still be visible in the source files and docs, but annotated with `@ObjectServer` (#5799). + ### Internal * Updated to Object Store commit: 97fd03819f398b3c81c8b007feaca8636629050b diff --git a/build.gradle b/build.gradle index 12370c0abf..87999ef4df 100644 --- a/build.gradle +++ b/build.gradle @@ -47,11 +47,19 @@ task installTransformer(type:GradleBuild) { tasks = ['publishToMavenLocal'] } +task installBuildTransformer(type:GradleBuild) { + group = 'Install' + description = 'Install the jar realm-library-build-transformer into mavenLocal()' + buildFile = file('library-build-transformer/build.gradle') + tasks = ['publishToMavenLocal'] +} + task assembleRealm(type:GradleBuild) { group = 'Build' description = 'Assemble the Realm project' dependsOn installAnnotations dependsOn installTransformer + dependsOn installBuildTransformer buildFile = file('realm/build.gradle') tasks = ['assemble', 'javadocJar', 'sourcesJar'] if (project.hasProperty('buildTargetABIs')) { @@ -137,6 +145,7 @@ task installRealm(type:GradleBuild) { group = 'Install' description = 'Install the artifacts of Realm libraries into mavenLocal()' dependsOn installTransformer + dependsOn installBuildTransformer buildFile = file('realm/build.gradle') tasks = ['publishToMavenLocal'] if (project.hasProperty('buildTargetABIs')) { diff --git a/library-build-transformer/.gitignore b/library-build-transformer/.gitignore new file mode 100644 index 0000000000..89f9ac04aa --- /dev/null +++ b/library-build-transformer/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/library-build-transformer/README.md b/library-build-transformer/README.md new file mode 100644 index 0000000000..9a9c5ed32d --- /dev/null +++ b/library-build-transformer/README.md @@ -0,0 +1,41 @@ +# Library Transformer + +This project contains a transformer that removes all classes, methods and fields annotated with a +given annotation. + +This can be used to emulate Kotlin extension methods in cases where separating the code into flavour +folders is not feasible, like e.g. when the `Realm` class is shared between the `base` and +`objectServer` flavour. + +## Usage + +Register the transformer as normal and provide it with the flavor to strip and annotation to detect + +``` +import io.realm.buildtransformer.RealmBuildTransformer +android.registerTransform(new RealmBuildTransformer("base", "io.realm.internal.annotations.ObjectServer", [ + "explicit_files_to_remove" +])) +``` + +It is also possible to provide a specific list of files that will be removed whether or not they +have the annotation. This is used to remove some files created by the annotation processor that do +not carry over annotations. + +## Warning + +There are no checks in place with regard to it being safe or not to remove classes and methods, so +only apply the transformer when it is safe to do so (i.e. the classes/methods/fields are not in use). +Any errors will only be caught at runtime when the actual code is accessed. + +## Known limitations + +* If all constructors are stripped by this transformer, a new default constructor will not be + created. This will result in invalid byte code being generated. + +* If the top-level class is removed, all inner classes, enums and interfaces must also be annotated, + otherwise they are not removed, resulting in valid bytecode being generated. + +* Annotations on super classes will also remove subclasses, but only the first level of inheritance. + +* Single enum values cannot be stripped, only the entire enum class. \ No newline at end of file diff --git a/library-build-transformer/build.gradle b/library-build-transformer/build.gradle new file mode 100644 index 0000000000..a08d4481c9 --- /dev/null +++ b/library-build-transformer/build.gradle @@ -0,0 +1,106 @@ +group 'io.realm' +version '1.0.0' + +buildscript { + ext.kotlin_version = '1.2.51' + + repositories { + mavenCentral() + jcenter() + } + dependencies { + classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.2' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + def props = new Properties() + props.load(new FileInputStream("${rootDir}/../realm.properties")) + props.each { key, val -> + project.ext.set(key, val) + } +} + +group = 'io.realm' +version = file("${projectDir}/../version.txt").text.trim() + +apply plugin: 'kotlin' +apply plugin: 'maven' +apply plugin: 'maven-publish' +apply plugin: 'com.jfrog.artifactory' +apply plugin: 'com.jfrog.bintray' + +repositories { + google() + mavenCentral() +} + +dependencies { + compile gradleApi() + compileOnly 'com.android.tools.build:gradle:3.1.1' + compile 'org.ow2.asm:asm:6.2' + compile 'org.ow2.asm:asm-util:6.2' + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" + + testCompile group:'junit', name:'junit', version:'4.12' + testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" + +} +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +def commonPom = { + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + issueManagement { + system 'github' + url 'https://github.com/realm/realm-java/issues' + } + scm { + url 'scm:https://github.com/realm/realm-java' + connection 'scm:git@github.com:realm/realm-java.git' + developerConnection 'scm:git@github.com:realm/realm-java.git' + } +} + +publishing { + publications { + realmPublication(MavenPublication) { + groupId 'io.realm' + artifactId = 'realm-library-build-transformer' + from components.java + pom.withXml { + Node root = asNode() + root.appendNode('name', 'realm-library-build-transformer') + root.appendNode('description', 'Transform library for Realm Java that will strip unwanted files at build time.') + root.appendNode('url', 'http://realm.io') + root.children().last() + commonPom + } + } + } + repositories { + maven { + credentials(AwsCredentials) { + accessKey project.hasProperty('s3AccessKey') ? s3AccessKey : 'noAccessKey' + secretKey project.hasProperty('s3SecretKey') ? s3SecretKey : 'noSecretKey' + } + if(project.version.endsWith('-SNAPSHOT')) { + url "s3://realm-ci-artifacts/maven/snapshots/" + } else { + url "s3://realm-ci-artifacts/maven/releases/" + } + } + } +} diff --git a/library-build-transformer/gradle.properties b/library-build-transformer/gradle.properties new file mode 100644 index 0000000000..160890028a --- /dev/null +++ b/library-build-transformer/gradle.properties @@ -0,0 +1 @@ +org.gradle.caching=true diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.jar b/library-build-transformer/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..01b8bf6b1f99cad9213fc495b33ad5bbab8efd20 GIT binary patch literal 54329 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

            <5G2M1WZP5 zzqSuO91lJod(SBDDw<*sX(+F6Uq~YAeYV#2A;XQu_p=N5X+#cmu19Qk>QAnV=k!?wbk5I;tDWgFc}0NkvC*G=V+Yh1cyeJVq~9czZiDXe+S=VfL2g`LWo8om z$Y~FQc6MFjV-t1Y`^D9XMwY*U_re2R?&(O~68T&D4S{X`6JYU-pz=}ew-)V0AOUT1 zVOkHAB-8uBcRjLvz<9HS#a@X*Kc@|W)nyiSgi|u5$Md|P()%2(?olGg@ypoJwp6>m z*dnfjjWC>?_1p;%1brqZyDRR;8EntVA92EJ3ByOxj6a+bhPl z;a?m4rQAV1@QU^#M1HX)0+}A<7TCO`ZR_RzF}X9-M>cRLyN4C+lCk2)kT^3gN^`IT zNP~fAm(wyIoR+l^lQDA(e1Yv}&$I!n?&*p6?lZcQ+vGLLd~fM)qt}wsbf3r=tmVYe zl)ntf#E!P7wlakP9MXS7m0nsAmqxZ*)#j;M&0De`oNmFgi$ov#!`6^4)iQyxg5Iuj zjLAhzQ)r`^hf7`*1`Rh`X;LVBtDSz@0T?kkT1o!ijeyTGt5vc^Cd*tmNgiNo^EaWvaC8$e+nb_{W01j3%=1Y&92YacjCi>eNbwk%-gPQ@H-+4xskQ}f_c=jg^S-# zYFBDf)2?@5cy@^@FHK5$YdAK9cI;!?Jgd}25lOW%xbCJ>By3=HiK@1EM+I46A)Lsd zeT|ZH;KlCml=@;5+hfYf>QNOr^XNH%J-lvev)$Omy8MZ`!{`j>(J5cG&ZXXgv)TaF zg;cz99i$4CX_@3MIb?GL0s*8J=3`#P(jXF(_(6DXZjc@(@h&=M&JG)9&Te1?(^XMW zjjC_70|b=9hB6pKQi`S^Ls7JyJw^@P>Ko^&q8F&?>6i;#CbxUiLz1ZH4lNyd@QACd zu>{!sqjB!2Dg}pbAXD>d!3jW}=5aN0b;rw*W>*PAxm7D)aw(c*RX2@bTGEI|RRp}vw7;NR2wa;rXN{L{Q#=Fa z$x@ms6pqb>!8AuV(prv>|aU8oWV={C&$c zMa=p=CDNOC2tISZcd8~18GN5oTbKY+Vrq;3_obJlfSKRMk;Hdp1`y`&LNSOqeauR_ z^j*Ojl3Ohzb5-a49A8s|UnM*NM8tg}BJXdci5%h&;$afbmRpN0&~9rCnBA`#lG!p zc{(9Y?A0Y9yo?wSYn>iigf~KP$0*@bGZ>*YM4&D;@{<%Gg5^uUJGRrV4 z(aZOGB&{_0f*O=Oi0k{@8vN^BU>s3jJRS&CJOl3o|BE{FAA&a#2YYiX3pZz@|Go-F z|Fly;7eX2OTs>R}<`4RwpHFs9nwh)B28*o5qK1Ge=_^w0m`uJOv!=&!tzt#Save(C zgKU=Bsgql|`ui(e1KVxR`?>Dx>(rD1$iWp&m`v)3A!j5(6vBm*z|aKm*T*)mo(W;R zNGo2`KM!^SS7+*9YxTm6YMm_oSrLceqN*nDOAtagULuZl5Q<7mOnB@Hq&P|#9y{5B z!2x+2s<%Cv2Aa0+u{bjZXS);#IFPk(Ph-K7K?3i|4ro> zRbqJoiOEYo(Im^((r}U4b8nvo_>4<`)ut`24?ILnglT;Pd&U}$lV3U$F9#PD(O=yV zgNNA=GW|(E=&m_1;uaNmipQe?pon4{T=zK!N!2_CJL0E*R^XXIKf*wi!>@l}3_P9Z zF~JyMbW!+n-+>!u=A1ESxzkJy$DRuG+$oioG7(@Et|xVbJ#BCt;J43Nvj@MKvTxzy zMmjNuc#LXBxFAwIGZJk~^!q$*`FME}yKE8d1f5Mp}KHNq(@=Z8YxV}0@;YS~|SpGg$_jG7>_8WWYcVx#4SxpzlV9N4aO>K{c z$P?a_fyDzGX$Of3@ykvedGd<@-R;M^Shlj*SswJLD+j@hi_&_>6WZ}#AYLR0iWMK|A zH_NBeu(tMyG=6VO-=Pb>-Q#$F*or}KmEGg*-n?vWQREURdB#+6AvOj*I%!R-4E_2$ zU5n9m>RWs|Wr;h2DaO&mFBdDb-Z{APGQx$(L`if?C|njd*fC=rTS%{o69U|meRvu?N;Z|Y zbT|ojL>j;q*?xXmnHH#3R4O-59NV1j=uapkK7}6@Wo*^Nd#(;$iuGsb;H315xh3pl zHaJ>h-_$hdNl{+|Zb%DZH%ES;*P*v0#}g|vrKm9;j-9e1M4qX@zkl&5OiwnCz=tb6 zz<6HXD+rGIVpGtkb{Q^LIgExOm zz?I|oO9)!BOLW#krLmWvX5(k!h{i>ots*EhpvAE;06K|u_c~y{#b|UxQ*O@Ks=bca z^_F0a@61j3I(Ziv{xLb8AXQj3;R{f_l6a#H5ukg5rxwF9A$?Qp-Mo54`N-SKc}fWp z0T)-L@V$$&my;l#Ha{O@!fK4-FSA)L&3<${Hcwa7ue`=f&YsXY(NgeDU#sRlT3+9J z6;(^(sjSK@3?oMo$%L-nqy*E;3pb0nZLx6 z;h5)T$y8GXK1DS-F@bGun8|J(v-9o=42&nLJy#}M5D0T^5VWBNn$RpC zZzG6Bt66VY4_?W=PX$DMpKAI!d`INr) zkMB{XPQ<52rvWVQqgI0OL_NWxoe`xxw&X8yVftdODPj5|t}S6*VMqN$-h9)1MBe0N zYq?g0+e8fJCoAksr0af1)FYtz?Me!Cxn`gUx&|T;)695GG6HF7!Kg1zzRf_{VWv^bo81v4$?F6u2g|wxHc6eJQAg&V z#%0DnWm2Rmu71rPJ8#xFUNFC*V{+N_qqFH@gYRLZ6C?GAcVRi>^n3zQxORPG)$-B~ z%_oB?-%Zf7d*Fe;cf%tQwcGv2S?rD$Z&>QC2X^vwYjnr5pa5u#38cHCt4G3|efuci z@3z=#A13`+ztmp;%zjXwPY_aq-;isu*hecWWX_=Z8paSqq7;XYnUjK*T>c4~PR4W7 z#C*%_H&tfGx`Y$w7`dXvVhmovDnT>btmy~SLf>>~84jkoQ%cv=MMb+a{JV&t0+1`I z32g_Y@yDhKe|K^PevP~MiiVl{Ou7^Mt9{lOnXEQ`xY^6L8D$705GON{!1?1&YJEl#fTf5Z)da=yiEQ zGgtC-soFGOEBEB~ZF_{7b(76En>d}mI~XIwNw{e>=Fv)sgcw@qOsykWr?+qAOZSVrQfg}TNI ztKNG)1SRrAt6#Q?(me%)>&A_^DM`pL>J{2xu>xa$3d@90xR61TQDl@fu%_85DuUUA za9tn64?At;{`BAW6oykwntxHeDpXsV#{tmt5RqdN7LtcF4vR~_kZNT|wqyR#z^Xcd zFdymVRZvyLfTpBT>w9<)Ozv@;Yk@dOSVWbbtm^y@@C>?flP^EgQPAwsy75bveo=}T zFxl(f)s)j(0#N_>Or(xEuV(n$M+`#;Pc$1@OjXEJZumkaekVqgP_i}p`oTx;terTx zZpT+0dpUya2hqlf`SpXN{}>PfhajNk_J0`H|2<5E;U5Vh4F8er z;RxLSFgpGhkU>W?IwdW~NZTyOBrQ84H7_?gviIf71l`EETodG9a1!8e{jW?DpwjL? zGEM&eCzwoZt^P*8KHZ$B<%{I}>46IT%jJ3AnnB5P%D2E2Z_ z1M!vr#8r}1|KTqWA4%67ZdbMW2YJ81b(KF&SQ2L1Qn(y-=J${p?xLMx3W7*MK;LFQ z6Z`aU;;mTL4XrrE;HY*Rkh6N%?qviUGNAKiCB~!P}Z->IpO6E(gGd7I#eDuT7j|?nZ zK}I(EJ>$Kb&@338M~O+em9(L!+=0zBR;JAQesx|3?Ok90)D1aS9P?yTh6Poh8Cr4X zk3zc=f2rE7jj+aP7nUsr@~?^EGP>Q>h#NHS?F{Cn`g-gD<8F&dqOh-0sa%pfL`b+1 zUsF*4a~)KGb4te&K0}bE>z3yb8% zibb5Q%Sfiv7feb1r0tfmiMv z@^4XYwg@KZI=;`wC)`1jUA9Kv{HKe2t$WmRcR4y8)VAFjRi zaz&O7Y2tDmc5+SX(bj6yGHYk$dBkWc96u3u&F)2yEE~*i0F%t9Kg^L6MJSb&?wrXi zGSc;_rln$!^ybwYBeacEFRsVGq-&4uC{F)*Y;<0y7~USXswMo>j4?~5%Zm!m@i@-> zXzi82sa-vpU{6MFRktJy+E0j#w`f`>Lbog{zP|9~hg(r{RCa!uGe>Yl536cn$;ouH za#@8XMvS-kddc1`!1LVq;h57~zV`7IYR}pp3u!JtE6Q67 zq3H9ZUcWPm2V4IukS}MCHSdF0qg2@~ufNx9+VMjQP&exiG_u9TZAeAEj*jw($G)zL zq9%#v{wVyOAC4A~AF=dPX|M}MZV)s(qI9@aIK?Pe+~ch|>QYb+78lDF*Nxz2-vpRbtQ*F4$0fDbvNM#CCatgQ@z1+EZWrt z2dZfywXkiW=no5jus-92>gXn5rFQ-COvKyegmL=4+NPzw6o@a?wGE-1Bt;pCHe;34K%Z z-FnOb%!nH;)gX+!a3nCk?5(f1HaWZBMmmC@lc({dUah+E;NOros{?ui1zPC-Q0);w zEbJmdE$oU$AVGQPdm{?xxI_0CKNG$LbY*i?YRQ$(&;NiA#h@DCxC(U@AJ$Yt}}^xt-EC_ z4!;QlLkjvSOhdx!bR~W|Ezmuf6A#@T`2tsjkr>TvW*lFCMY>Na_v8+{Y|=MCu1P8y z89vPiH5+CKcG-5lzk0oY>~aJC_0+4rS@c@ZVKLAp`G-sJB$$)^4*A!B zmcf}lIw|VxV9NSoJ8Ag3CwN&d7`|@>&B|l9G8tXT^BDHOUPrtC70NgwN4${$k~d_4 zJ@eo6%YQnOgq$th?0{h`KnqYa$Nz@vlHw<%!C5du6<*j1nwquk=uY}B8r7f|lY+v7 zm|JU$US08ugor8E$h3wH$c&i~;guC|3-tqJy#T;v(g( zBZtPMSyv%jzf->435yM(-UfyHq_D=6;ouL4!ZoD+xI5uCM5ay2m)RPmm$I}h>()hS zO!0gzMxc`BPkUZ)WXaXam%1;)gedA7SM8~8yIy@6TPg!hR0=T>4$Zxd)j&P-pXeSF z9W`lg6@~YDhd19B9ETv(%er^Xp8Yj@AuFVR_8t*KS;6VHkEDKI#!@l!l3v6`W1`1~ zP{C@keuV4Q`Rjc08lx?zmT$e$!3esc9&$XZf4nRL(Z*@keUbk!GZi(2Bmyq*saOD? z3Q$V<*P-X1p2}aQmuMw9nSMbOzuASsxten7DKd6A@ftZ=NhJ(0IM|Jr<91uAul4JR zADqY^AOVT3a(NIxg|U;fyc#ZnSzw2cr}#a5lZ38>nP{05D)7~ad7JPhw!LqOwATXtRhK!w0X4HgS1i<%AxbFmGJx9?sEURV+S{k~g zGYF$IWSlQonq6}e;B(X(sIH|;52+(LYW}v_gBcp|x%rEAVB`5LXg_d5{Q5tMDu0_2 z|LOm$@K2?lrLNF=mr%YP|U-t)~9bqd+wHb4KuPmNK<}PK6e@aosGZK57=Zt+kcszVOSbe;`E^dN! ze7`ha3WUUU7(nS0{?@!}{0+-VO4A{7+nL~UOPW9_P(6^GL0h${SLtqG!} zKl~Ng5#@Sy?65wk9z*3SA`Dpd4b4T^@C8Fhd8O)k_4%0RZL5?#b~jmgU+0|DB%0Z) zql-cPC>A9HPjdOTpPC` zQwvF}uB5kG$Xr4XnaH#ruSjM*xG?_hT7y3G+8Ox`flzU^QIgb_>2&-f+XB6MDr-na zSi#S+c!ToK84<&m6sCiGTd^8pNdXo+$3^l3FL_E`0 z>8it5YIDxtTp2Tm(?}FX^w{fbfgh7>^8mtvN>9fWgFN_*a1P`Gz*dyOZF{OV7BC#j zQV=FQM5m>47xXgapI$WbPM5V`V<7J9tD)oz@d~MDoM`R^Y6-Na(lO~uvZlpu?;zw6 zVO1faor3dg#JEb5Q*gz4<W8tgC3nE2BG2jeIQs1)<{In&7hJ39x=;ih;CJDy)>0S1at*7n?Wr0ahYCpFjZ|@u91Zl7( zv;CSBRC65-6f+*JPf4p1UZ)k=XivKTX6_bWT~7V#rq0Xjas6hMO!HJN8GdpBKg_$B zwDHJF6;z?h<;GXFZan8W{XFNPpOj!(&I1`&kWO86p?Xz`a$`7qV7Xqev|7nn_lQuX ziGpU1MMYt&5dE2A62iX3;*0WzNB9*nSTzI%62A+N?f?;S>N@8M=|ef3gtQTIA*=yq zQAAjOqa!CkHOQo4?TsqrrsJLclXcP?dlAVv?v`}YUjo1Htt;6djP@NPFH+&p1I+f_ z)Y279{7OWomY8baT(4TAOlz1OyD{4P?(DGv3XyJTA2IXe=kqD)^h(@*E3{I~w;ws8 z)ZWv7E)pbEM zd3MOXRH3mQhks9 zv6{s;k0y5vrcjXaVfw8^>YyPo=oIqd5IGI{)+TZq5Z5O&hXAw%ZlL}^6FugH;-%vP zAaKFtt3i^ag226=f0YjzdPn6|4(C2sC5wHFX{7QF!tG1E-JFA`>eZ`}$ymcRJK?0c zN363o{&ir)QySOFY0vcu6)kX#;l??|7o{HBDVJN+17rt|w3;(C_1b>d;g9Gp=8YVl zYTtA52@!7AUEkTm@P&h#eg+F*lR zQ7iotZTcMR1frJ0*V@Hw__~CL>_~2H2cCtuzYIUD24=Cv!1j6s{QS!v=PzwQ(a0HS zBKx04KA}-Ue+%9d`?PG*hIij@54RDSQpA7|>qYVIrK_G6%6;#ZkR}NjUgmGju)2F`>|WJoljo)DJgZr4eo1k1i1+o z1D{>^RlpIY8OUaOEf5EBu%a&~c5aWnqM zxBpJq98f=%M^{4mm~5`CWl%)nFR64U{(chmST&2jp+-r z3675V<;Qi-kJud%oWnCLdaU-)xTnMM%rx%Jw6v@=J|Ir=4n-1Z23r-EVf91CGMGNz zb~wyv4V{H-hkr3j3WbGnComiqmS0vn?n?5v2`Vi>{Ip3OZUEPN7N8XeUtF)Ry6>y> zvn0BTLCiqGroFu|m2zG-;Xb6;W`UyLw)@v}H&(M}XCEVXZQoWF=Ykr5lX3XWwyNyF z#jHv)A*L~2BZ4lX?AlN3X#axMwOC)PoVy^6lCGse9bkGjb=qz%kDa6}MOmSwK`cVO zt(e*MW-x}XtU?GY5}9{MKhRhYOlLhJE5=ca+-RmO04^ z66z{40J=s=ey9OCdc(RCzy zd7Zr1%!y3}MG(D=wM_ebhXnJ@MLi7cImDkhm0y{d-Vm81j`0mbi4lF=eirlr)oW~a zCd?26&j^m4AeXEsIUXiTal)+SPM4)HX%%YWF1?(FV47BaA`h9m67S9x>hWMVHx~Hg z1meUYoLL(p@b3?x|9DgWeI|AJ`Ia84*P{Mb%H$ZRROouR4wZhOPX15=KiBMHl!^JnCt$Az`KiH^_d>cev&f zaG2>cWf$=A@&GP~DubsgYb|L~o)cn5h%2`i^!2)bzOTw2UR!>q5^r&2Vy}JaWFUQE04v>2;Z@ZPwXr?y&G(B^@&y zsd6kC=hHdKV>!NDLIj+3rgZJ|dF`%N$DNd;B)9BbiT9Ju^Wt%%u}SvfM^=|q-nxDG zuWCQG9e#~Q5cyf8@y76#kkR^}{c<_KnZ0QsZcAT|YLRo~&tU|N@BjxOuy`#>`X~Q< z?R?-Gsk$$!oo(BveQLlUrcL#eirhgBLh`qHEMg`+sR1`A=1QX7)ZLMRT+GBy?&mM8 zQG^z-!Oa&J-k7I(3_2#Q6Bg=NX<|@X&+YMIOzfEO2$6Mnh}YV!m!e^__{W@-CTprr zbdh3f=BeCD$gHwCrmwgM3LAv3!Mh$wM)~KWzp^w)Cu6roO7uUG5z*}i0_0j47}pK; ztN530`ScGatLOL06~zO)Qmuv`h!gq5l#wx(EliKe&rz-5qH(hb1*fB#B+q`9=jLp@ zOa2)>JTl7ovxMbrif`Xe9;+fqB1K#l=Dv!iT;xF zdkCvS>C5q|O;}ns3AgoE({Ua-zNT-9_5|P0iANmC6O76Sq_(AN?UeEQJ>#b54fi3k zFmh+P%b1x3^)0M;QxXLP!BZ^h|AhOde*{9A=f3|Xq*JAs^Y{eViF|=EBfS6L%k4ip zk+7M$gEKI3?bQg?H3zaE@;cyv9kv;cqK$VxQbFEsy^iM{XXW0@2|DOu$!-k zSFl}Y=jt-VaT>Cx*KQnHTyXt}f9XswFB9ibYh+k2J!ofO+nD?1iw@mwtrqI4_i?nE zhLkPp41ED62me}J<`3RN80#vjW;wt`pP?%oQ!oqy7`miL>d-35a=qotK$p{IzeSk# ze_$CFYp_zIkrPFVaW^s#U4xT1lI^A0IBe~Y<4uS%zSV=wcuLr%gQT=&5$&K*bwqx| zWzCMiz>7t^Et@9CRUm9E+@hy~sBpm9fri$sE1zgLU((1?Yg{N1Sars=DiW&~Zw=3I zi7y)&oTC?UWD2w97xQ&5vx zRXEBGeJ(I?Y}eR0_O{$~)bMJRTsNUPIfR!xU9PE7A>AMNr_wbrFK>&vVw=Y;RH zO$mlpmMsQ}-FQ2cSj7s7GpC+~^Q~dC?y>M}%!-3kq(F3hGWo9B-Gn02AwUgJ>Z-pKOaj zysJBQx{1>Va=*e@sLb2z&RmQ7ira;aBijM-xQ&cpR>X3wP^foXM~u1>sv9xOjzZpX z0K;EGouSYD~oQ&lAafj3~EaXfFShC+>VsRlEMa9cg9i zFxhCKO}K0ax6g4@DEA?dg{mo>s+~RPI^ybb^u--^nTF>**0l5R9pocwB?_K)BG_)S zyLb&k%XZhBVr7U$wlhMqwL)_r&&n%*N$}~qijbkfM|dIWP{MyLx}X&}ES?}7i;9bW zmTVK@zR)7kE2+L42Q`n4m0VVg5l5(W`SC9HsfrLZ=v%lpef=Gj)W59VTLe+Z$8T8i z4V%5+T0t8LnM&H>Rsm5C%qpWBFqgTwL{=_4mE{S3EnBXknM&u8n}A^IIM4$s3m(Rd z>zq=CP-!9p9es2C*)_hoL@tDYABn+o#*l;6@7;knWIyDrt5EuakO99S$}n((Fj4y} zD!VvuRzghcE{!s;jC*<_H$y6!6QpePo2A3ZbX*ZzRnQq*b%KK^NF^z96CHaWmzU@f z#j;y?X=UP&+YS3kZx7;{ zDA{9(wfz7GF`1A6iB6fnXu0?&d|^p|6)%3$aG0Uor~8o? z*e}u#qz7Ri?8Uxp4m_u{a@%bztvz-BzewR6bh*1Xp+G=tQGpcy|4V_&*aOqu|32CM zz3r*E8o8SNea2hYJpLQ-_}R&M9^%@AMx&`1H8aDx4j%-gE+baf2+9zI*+Pmt+v{39 zDZ3Ix_vPYSc;Y;yn68kW4CG>PE5RoaV0n@#eVmk?p$u&Fy&KDTy!f^Hy6&^-H*)#u zdrSCTJPJw?(hLf56%2;_3n|ujUSJOU8VPOTlDULwt0jS@j^t1WS z!n7dZIoT+|O9hFUUMbID4Ec$!cc($DuQWkocVRcYSikFeM&RZ=?BW)mG4?fh#)KVG zcJ!<=-8{&MdE)+}?C8s{k@l49I|Zwswy^ZN3;E!FKyglY~Aq?4m74P-0)sMTGXqd5(S<-(DjjM z&7dL-Mr8jhUCAG$5^mI<|%`;JI5FVUnNj!VO2?Jiqa|c2;4^n!R z`5KK0hyB*F4w%cJ@Un6GC{mY&r%g`OX|1w2$B7wxu97%<@~9>NlXYd9RMF2UM>(z0 zouu4*+u+1*k;+nFPk%ly!nuMBgH4sL5Z`@Rok&?Ef=JrTmvBAS1h?C0)ty5+yEFRz zY$G=coQtNmT@1O5uk#_MQM1&bPPnspy5#>=_7%WcEL*n$;t3FUcXxMpcXxMpA@1(( z32}FUxI1xoH;5;M_i@j?f6mF_p3Cd1DTb=dTK#qJneN`*d+pvYD*L?M(1O%DEmB>$ zs6n;@Lcm9c7=l6J&J(yBnm#+MxMvd-VKqae7;H7p-th(nwc}?ov%$8ckwY%n{RAF3 zTl^SF7qIWdSa7%WJ@B^V-wD|Z)9IQkl$xF>ebi>0AwBv5oh5$D*C*Pyj?j_*pT*IMgu3 z$p#f0_da0~Wq(H~yP##oQ}x66iYFc0O@JFgyB>ul@qz{&<14#Jy@myMM^N%oy0r|b zDPBoU!Y$vUxi%_kPeb4Hrc>;Zd^sftawKla0o|3mk@B)339@&p6inAo(Su3qlK2a) zf?EU`oSg^?f`?y=@Vaq4Dps8HLHW zIe~fHkXwT>@)r+5W7#pW$gzbbaJ$9e;W-u#VF?D=gsFfFlBJ5wR>SB;+f)sFJsYJ| z29l2Ykg+#1|INd=uj3&d)m@usb;VbGnoI1RHvva@?i&>sP&;Lt!ZY=e!=d-yZ;QV% zP@(f)+{|<*XDq%mvYKwIazn8HS`~mW%9+B|`&x*n?Y$@l{uy@ z^XxQnuny+p0JG0h)#^7}C|Btyp7=P#A2ed1vP0KGw9+~-^y4~S$bRm3gCT{+7Z<(A zJ&tg=7X|uKPKd6%z@IcZ@FgQe=rS&&1|O!s#>B_z!M_^B`O(SqE>|x- zh{~)$RW_~jXj)}mO>_PZvGdD|vtN44=Tp!oCP0>)gYeJ;n*&^BZG{$>y%Yb|L zeBUI#470!F`GM-U$?+~k+g9lj5C-P_i1%c3Zbo!@EjMJDoxQ7%jHHKeMVw&_(aoL? z%*h*aIt9-De$J>ZRLa7aWcLn<=%D+u0}RV9ys#TBGLAE%Vh`LWjWUi`Q3kpW;bd)YD~f(#$jfNdx}lOAq=#J*aV zz;K>I?)4feI+HrrrhDVkjePq;L7r87;&vm|7qaN z_>XhM8GU6I5tSr3O2W4W%m6wDH#=l32!%LRho(~*d3GfA6v-ND^0trp-qZs(B(ewD z3y3@ZV!2`DZ6b6c(Ftqg-s715;=lZqGF>H+z+c&7NeDz!We+7WNk>X*b7OZmlcTnf z{C1CB67e@xbWprDhN+t!B%4od#|>yQA$5mBM>XdhP?1U^%aD&^=PYWQEY*8Mr%h~R zOVzrd9}6RSl}Lt42r166_*s|U<1}`{l(H}m8H=D+oG>*=+=W^%IMB&CHZ-?)78G2b z)9kj_ldMecB_65eV&R+(yQ$2`ol&&7$&ns_{%A6cC2C*C6dY7qyWrHSYyOBl$0=$> z-YgkNlH{1MR-FXx7rD=4;l%6Ub3OMx9)A|Y7KLnvb`5OB?hLb#o@Wu(k|;_b!fbq( zX|rh*D3ICnZF{5ipmz8`5UV3Otwcso0I#;Q(@w+Pyj&Qa(}Uq2O(AcLU(T`+x_&~?CFLly*`fdP6NU5A|ygPXM>}(+) zkTRUw*cD<% zzFnMeB(A4A9{|Zx2*#!sRCFTk2|AMy5+@z8ws0L-{mt(9;H#}EGePUWxLabB_fFcp zLiT)TDLUXPbV2$Cde<9gv4=;u5aQ$kc9|GE2?AQZsS~D%AR`}qP?-kS_bd>C2r(I; zOc&r~HB7tUOQgZOpH&7C&q%N612f?t(MAe(B z@A!iZi)0qo^Nyb`#9DkzKjoI4rR1ghi1wJU5Tejt!ISGE93m@qDNYd|gg9(s|8-&G zcMnsX0=@2qQQ__ujux#EJ=veg&?3U<`tIWk~F=vm+WTviUvueFk&J@TcoGO{~C%6NiiNJ*0FJBQ!3Ab zm59ILI24e8!=;-k%yEf~YqN_UJ8k z0GVIS0n^8Yc)UK1eQne}<0XqzHkkTl*8VrWr zo}y?WN5@TL*1p>@MrUtxq0Vki($sn_!&;gR2e$?F4^pe@J_BQS&K3{4n+f7tZX4wQn z*Z#0eBs&H8_t`w^?ZYx=BGgyUI;H$i*t%(~8BRZ4gH+nJT0R-3lzdn4JY=xfs!YpF zQdi3kV|NTMB}uxx^KP!`=S(}{s*kfb?6w^OZpU?Wa~7f@Q^pV}+L@9kfDE`c@h5T* zY@@@?HJI)j;Y#l8z|k8y#lNTh2r?s=X_!+jny>OsA7NM~(rh3Tj7?e&pD!Jm28*UL zmRgopf0sV~MzaHDTW!bPMNcymg=!OS2bD@6Z+)R#227ET3s+2m-(W$xXBE#L$Whsi zjz6P+4cGBQkJY*vc1voifsTD}?H$&NoN^<=zK~75d|WSU4Jaw`!GoPr$b>4AjbMy+ z%4;Kt7#wwi)gyzL$R97(N?-cKygLClUk{bBPjSMLdm|MG-;oz70mGNDus zdGOi}L59=uz=VR2nIux^(D85f)1|tK&c!z1KS6tgYd^jgg6lT^5h42tZCn#Q-9k>H zVby-zby2o_GjI!zKn8ZuQ`asmp6R@=FR9kJ_Vja#I#=wtQWTes>INZynAoj$5 zN^9Ws&hvDhu*lY=De$Zby12$N&1#U2W1OHzuh;fSZH4igQodAG1K*;%>P9emF7PPD z>XZ&_hiFcX9rBXQ8-#bgSQ!5coh=(>^8gL%iOnnR>{_O#bF>l+6yZQ4R42{Sd#c7G zHy!)|g^tmtT4$YEk9PUIM8h)r?0_f=aam-`koGL&0Zp*c3H2SvrSr60s|0VtFPF^) z-$}3C94MKB)r#398;v@)bMN#qH}-%XAyJ_V&k@k+GHJ^+YA<*xmxN8qT6xd+3@i$( z0`?f(la@NGP*H0PT#Od3C6>0hxarvSr3G;0P=rG^v=nB5sfJ}9&klYZ>G1BM2({El zg0i|%d~|f2e(yWsh%r)XsV~Fm`F*Gsm;yTQV)dW!c8^WHRfk~@iC$w^h=ICTD!DD;~TIlIoVUh*r@aS|%Ae3Io zU~>^l$P8{6Ro~g26!@NToOZ(^5f8p`*6ovpcQdIDf%)?{NPPwHB>l*f_prp9XDCM8 zG`(I8xl|w{x(c`}T_;LJ!%h6L=N=zglX2Ea+2%Q8^GA>jow-M>0w{XIE-yz|?~M+; zeZO2F3QK@>(rqR|i7J^!1YGH^9MK~IQPD}R<6^~VZWErnek^xHV>ZdiPc4wesiYVL z2~8l7^g)X$kd}HC74!Y=Uq^xre22Osz!|W@zsoB9dT;2Dx8iSuK!Tj+Pgy0-TGd)7 zNy)m@P3Le@AyO*@Z2~+K9t2;=7>-*e(ZG`dBPAnZLhl^zBIy9G+c)=lq0UUNV4+N% zu*Nc4_cDh$ou3}Re}`U&(e^N?I_T~#42li13_LDYm`bNLC~>z0ZG^o6=IDdbIf+XFTfe>SeLw4UzaK#4CM4HNOs- zz>VBRkL@*A7+XY8%De)|BYE<%pe~JzZN-EU4-s_P9eINA^Qvy3z?DOTlkS!kfBG_7 zg{L6N2(=3y=iY)kang=0jClzAWZqf+fDMy-MH&Px&6X36P^!0gj%Z0JLvg~oB$9Z| zgl=6_$4LSD#(2t{Eg=2|v_{w7op+)>ehcvio@*>XM!kz+xfJees9(ObmZ~rVGH>K zWaiBlWGEV{JU=KQ>{!0+EDe-+Z#pO zv{^R<7A^gloN;Tx$g`N*Z5OG!5gN^Xj=2<4D;k1QuN5N{4O`Pfjo3Ht_RRYSzsnhTK?YUf)z4WjNY z>R04WTIh4N(RbY*hPsjKGhKu;&WI)D53RhTUOT}#QBDfUh%lJSy88oqBFX)1pt>;M z>{NTkPPk8#}DUO;#AV8I7ZQsC?Wzxn|3ubiQYI|Fn_g4r)%eNZ~ zSvTYKS*9Bcw{!=C$=1` zGQ~1D97;N!8rzKPX5WoqDHosZIKjc!MS+Q9ItJK?6Wd%STS2H!*A#a4t5 zJ-Rz_`n>>Up%|81tJR2KND<6Uoe82l={J~r*D5c_bThxVxJ<}?b0Sy}L1u|Yk=e&t z0b5c2X(#x^^fI)l<2=3b=|1OH_)-2beVEH9IzpS*Es0!4Or+xE$%zdgY+VTK2}#fpxSPtD^1a6Z)S%5eqVDzs`rL1U;Zep@^Y zWf#dJzp_iWP{z=UEepfZ4ltYMb^%H7_m4Pu81CP@Ra)ds+|Oi~a>Xi(RBCy2dTu-R z$dw(E?$QJUA3tTIf;uZq!^?_edu~bltHs!5WPM-U=R74UsBwN&nus2c?`XAzNUYY|fasp?z$nFwXQYnT`iSR<=N`1~h3#L#lF-Fc1D#UZhC2IXZ{#IDYl_r8 z?+BRvo_fPGAXi+bPVzp=nKTvN_v*xCrb^n=3cQ~No{JzfPo@YWh=7K(M_$Jk*+9u* zEY4Ww3A|JQ`+$z(hec&3&3wxV{q>D{fj!Euy2>tla^LP_2T8`St2em~qQp zm{Tk<>V3ecaP1ghn}kzS7VtKksV*27X+;Y6#I$urr=25xuC=AIP7#Jp+)L67G6>EZ zA~n}qEWm6A8GOK!3q9Yw*Z07R(qr{YBOo5&4#pD_O(O^y0a{UlC6w@ZalAN0Rq_E0 zVA!pI-6^`?nb7`y(3W5OsoVJ^MT!7r57Jm{FS{(GWAWwAh$dBpffjcOZUpPv$tTc} zv~jnA{+|18GmMDq7VK6Sb=-2nzz^7TDiixA{mf%8eQC|x>*=)((3}twJCoh~V4m3) zM5fwDbrTpnYR`lIO7Il7Eq@)St{h>Nllv+5Hk2FAE8fdD*YT|zJix?!cZ-=Uqqieb z-~swMc+yvTu(h?fT4K_UuVDqTup3%((3Q!0*Tfwyl`3e27*p{$ zaJMMF-Pb=3imlQ*%M6q5dh3tT+^%wG_r)q5?yHvrYAmc-zUo*HtP&qP#@bfcX~jwn!$k~XyC#Ox9i7dO7b4}b^f zrVEPkeD%)l0-c_gazzFf=__#Q6Pwv_V=B^h=)CYCUszS6g!}T!r&pL)E*+2C z5KCcctx6Otpf@x~7wZz*>qB_JwO!uI@9wL0_F>QAtg3fvwj*#_AKvsaD?!gcj+zp) zl2mC)yiuumO+?R2`iiVpf_E|9&}83;^&95y96F6T#E1}DY!|^IW|pf-3G0l zE&_r{24TQAa`1xj3JMev)B_J-K2MTo{nyRKWjV#+O}2ah2DZ>qnYF_O{a6Gy{aLJi#hWo3YT3U7yVxoNrUyw31163sHsCUQG|rriZFeoTcP` zFV<&;-;5x0n`rqMjx2^_7y)dHPV@tJC*jHQo!~1h`#z)Gu7m@0@z*e?o|S#5#Ht~%GC|r zd?EY_E0XKUQ2o7*e3D9{Lt7s#x~`hjzwQ{TYw;Fq8la&)%4Vj_N@ivmaSNw9X3M$MAG97a&m1SODLZ-#$~7&@ zrB~0E+38b6sfezlmhDej*KRVbzptE0Xg%$xpjqoeL;-LwmKIR#%+EZ7U|&;9rS6lo8u9iOD;-3HF{Gm=EL@W zG8L9&8=FxGHICO+MX@lC?DpY4GAE9!S+7hKsTmr8%hFI9QGI4sCj&?Of-yA98KvLsP z|k5cP?Z zay4&3t8e5RgA_@c7z{RX6d`;{B~l03#AD@RJD1{;4x93d7mD15wnFLi^LI%`Z~6@ zq9}|AG1Lq-1~Fb{1b?}bFLaSnWm!7L)P8#%g{{}}u@Q`4N{s3LiD4kSqTnM8UNN4XQi57LZRzkkL9+rJ{_?juO;cZL=MIT2H1q-=Tt1G666hVaPojp^(AM>6 zDQQf0_>1u=rvT+6(5 zAQR5%mlLdhkl4MpIyY0GN9VrGYkq?1sF8F(VeB0u3{p`h6IgEBC}Jr!^-)@5@<8s( zXyiL`ENayjlbGx}3q2T;y&|@~&$+T=hN0iS4BAARQ_JBclEeBW7}$3lx|!Ee&vs&o z=A4b##+t=rylLD-dc(X)^d?KbmU^9uZ)zXbIPC%pD{s(>p9*fu8&(?$LE67%%b-e) z!IU|lpUpK`<&YPqJnj5wb8(;a)JoC~+Kb`Fq-HL<>X@DYPqu4t9tLfS9C>Kn*Ho zl3Zz2y8;bCi@KYchQ;1JTPXL`ZMCb4R7fLlP_qKJ`aTs3H2Q6`g3GdtURX%yk`~xS z#|RDc0Y|%b+$^QYCSEG~ZF;*rT;@T=Ko6uwRJ&RasW^4$W<^nS^v|}UmIHe`P{(x| zI&y@A&b6=G2#r*st8^|19`Yw20=}MF9@@6zIuB%!vd7J%E|@zK(MRvFif-szGX^db zIvb}^{t9g(lZhLP&h6;2p>69mWE3ss6di_-KeYjPVskOMEu?5m_A>;o`6 z5ot9G8pI8Jwi@yJExKVZVw-3FD7TW3Ya{_*rS5+LicF^BX(Mq)H&l_B5o9^ zpcL6s^X}J-_9RAs(wk7s1J$cjO~jo*4l3!1V)$J+_j7t8g4A=ab`L(-{#G?z>z@KneXt&ZOv>m);*lTA}gRhYxtJt;0QZ<#l+OWu6(%(tdZ`LkXb}TQjhal;1vd{D+b@g7G z25i;qgu#ieYC?Fa?iwzeLiJa|vAU1AggN5q{?O?J9YU|xHi}PZb<6>I7->aWA4Y7-|a+7)RQagGQn@cj+ED7h6!b>XIIVI=iT(

              xR8>x!-hF($8?9?2$_G0!Ov-PHdEZo(@$?ZcCM)7YB>$ZH zMWhPJRjqPm%P_V5#UMfZ_L}+C(&-@fiUm`Gvj-V2YSM@AwZ4+@>lf-7*yxYxYzJG9 z8Z>T-V-h|PI-K8#1LBs++!+=;G&ed}>Qgs%CA|)bQd$SYzJ8U?H+Pb2&Bf=hSo*HL zELt9Z&2dz8&QQ^NY<~PP+wu57Eu>N@zkBFwO!w+BO}S0Xa(XN?BY)~WGZ<~bbZC&C zlJR|EK1_BLx*FK@OvkyG#ANGZbW~h5*xsx24d9toyTm-JUKo$r%(W42t>}}xax;qL zaw}VpEIzc=)VsC}Yx9kb@Fhh4bEWXlb4-DIH+tzLMlaT-I#A!e zKkZtQ^c@m*;P`&@?i@8tZ&Nel~z27L^F*m1}Rg^-xTzqy}3Mmq4jjJ zJC;ZK#U6QdBoE~b+-^xIyHSxNAYFGGB2WifSL_@3*CnzN18{kDvLM;dN50Jan0*YL zysmN}*Wyag#N?qeBO*E})kZMhzVKMFI zDJmEG_Wsed#Z_9T6Bi+-#s5oCG_$W<;8y%ubb!E>m!Z=HcX$Bn<&6a4a2Chp>^pAB zp^7;RF-lQa$1Ct5l88Ak4)(sYu$IRd5RwLPKa|y3wT%gBAk>pg*z=8s4UmZK(jK)g9^;e+#jYwF69JTFlz)U-(XXg zVD)U0B}ikjXJzsrW~I@l1yli*n|ww}_xpCY3<26Dc~n-dpoOqM{Yl-J@$IpVw7>YtzDZx zm}rqKSP(PM@M<^E+@ndf@wwxe$H(}rbzF`SGkwj1!{}Q6TTpZBhPDXdbCOaApGUN{ zp2q!e{c-`;@|>B9}2F<0G^h<$k%JitT<6nO`x0+K5ENk(~hYea8D*w-By=7s}!4= zEoMdOGi9B3%80sqaGRk?gj6fRr0Fa>BuM;1>R*i3bMU5rwG3r+@a~dnKMBZ_F6p*D zSRYfrDus5nFWJ%X>N6PgH~k zoB<3qHH^YyRy53{hNY>5xN6Eca!2jh-~3)NhoknTATWJ!&07-OYK-DUfkw!51UCML zP%@F<)A4~r{TkOKV9%x#edO(7H_Ke!J~A!tmmodA8dcLhhp0O@++ z35`8{H{So#b*sdgj8}LRCS%J zMNaioFbuoChaX&t7Y?OKWH~o|eKoy3#xH1@U=XTh@!Q~vn|%by)=@}Z~4PJ z#rEgEqtziT(C6b(ZY(f6TML12y;4W&hc|Wk^qF-Z1s^|{r;$!-$%|%?L5*qkt|0_#E8Vm^z>=DH zA)i=K;T0iy&HZUpgwtjWd=X{jWOQ{Vfx1iEWh^jM_jtfULMGKh;?UFn9d2W&&uVkI znCG!maf1t{Up0-*%Tdhm0F4C37_#;%@ma4c@(iAP_aZ){`hdlr=SCOwrW zCS`?8iWZGp-Jd2JaP~we_KLo04??+L+utj7_Ns~95mHW&?m6N)fbK6{TH82eKPdw* zyvp48VDX+auZ&A=LBr9ZzGzH+JHsC3p)|Bj{LquB=03Jv#0I!^36fe2=|kle_y}%Y zZMUr8YRuvpM(Yn?ik*}SUI%Qksmt(!<}vZl9k#%ZmL*phd>@;KK(izsGu1Pw3@gi% z8p#5HtQ8`>v<~M9-&pH{t`g;c>K?mcz8tk)kZB8|dc;byKSO&A!E(z=xHg{sp{>G+ zouA_g>SkebBfF}|RJUj274Y^1>;6s-eX)HzLvOD>Y1B#-Z854a=er5qqP4DvqU1IL z@VWKv&GuY%VqR$Y*Q&i3TF>jL@Uz_aKXQO$@3>X%wo>f-m<~=ye(bo_NNgIUKCT^* z3um;yNvFYd2dz%BImY}j_l*DvAuvj3Ev^cyap}Y4*`r*cE2i-e{jAGR`}Mk3WH}a5 zZ?mR>|=Izi2&RGE4_MJ(~Dz6D>7h=alt^eb2+Vd5Zh# zp`ZKBEzPQQHhds7y$?({(za}(Eve7P)~cR7yl$!N-j!maYX4zTjm{bu4*V@u)GYCA zM4{J97aDL`0J*tw;)~ZEF#Tb49m(s})Pxg}Nd_LQK2|8U9)fM!kz0rtUWz7dL{eUi zA(b07DqfmE9{hbrwrw#y?>ka@(p<#%J;XUWD6y;uZzKIrj231k^Xv>aV8O>(sDfCg@6$-_BI1rTWK3XbZ0xiZX`!QGFhWH$?;sOH?B<_4`KXd2TyX zViEvhZ!60PDc_QlVMh@e4$G?8P#0=6f2ve4d0S>Azth>50p#~Cx_~lOT&)vK%v9Mz z9J4WWMsU+Uul}8}SS9#=J9-0CXJo`-pjDLU{>Ut8dKIHMr}mW4{g_CwL^6n^%lNrb zN!T9a5yXWgpW9HnvbeE=II_8QZSPJxkw0IYBm}N!rT;bC8HRp?=|!5H)2+jsgyiqRIXnfwga8gMYN&vNAS~9r)D$peKR(j{E{TdRFU#B z<;Vl20JSOBn1$@~*W?Zk!!15f4HO>})HqKDn9MIH(`G?tN}H#xiehlE(3um>iCb$N zLD+Q@#TMJT8(G@h4UmfJ2+Ox`jD@Re{595tBwu5LH=ttNH@_8_$z5^-t4Cyf*bi)u ztx%NyZm=*{*DMOO^o6gJmm@E+WRd8yRwGaR^akm04&0lK=jL?hhqr%e6Mwx?Ws&JD zaQ5_EPnl}{ZoPhs$$2Ev?e{KIke~}D2u(QPJLV%&5@#~7@6T1jfD9g!cQaM9JgX&|LGoQE{Lh@=M65w z9alK+Q1=Ih4>Sg+ZLzH&q|WF$&FbK5JpOv|ddHyKj)r~3TH&<^x)VSPx8`PQ35i7NJ=jp(aN%iIR}7#z`P(|}jD1o% zZF9~T^QZ0Fdqv{mM8A#sSiZ(v9LGKCOtm-kiVCd#@<6s%wu#1Q1#=~%w> zrl?pthDR))hp&>qly?jMHL=53fPJ`lM?glcJuEH}CM{V{6U>hf73S~4!KXMEw^&Y7 z4{w&iLu_}AAbxDH1M=J~?GrWLND238JO$zVat1B%^L*33e$7|XA zls1r#cuaQ>#;0;+D!~HTl_8AL&$j%g1Kx7v24#aF{Q+p+h31$*S9%rXT9jjF=TNc( z23%Sr1IG1osJ(uAL_m04g~L~_ZYydDSj5l zGP6t#d5z@uBUZa|u?}9>N3u}1gNGOygP5L5Cxf4go3x?Kq#b7GTk=gZnnUuN++0zn z27%%V!d$FubU`2K2%!}ctgD)j;4nflhF2PE(VywWALKM&Bd+m+2=?>R0Il#dv;m)5 zts4r(Yp$l4crwsdomvk;s7a)g6-~uvQR3Y?Ik8WR*yTg??;)sRiuEjn-If_YydA%m z@wRljzltj_#crXi3e*T*B9(2_xD4t6{=Vn7Z$-=5jeAG2;u_ib`CIw}_3i1&CW+@f zX(6!tCnX8~j$!`DJUo6vF#C%afu3<0ZHR4vJx?6K84-%V@7nxrT>s+`+#jQRguME{ zj)XKcQl8)yXdv*CAm>mHg(A1flmgS@n)c*_`dRa{s|H#)r>#)JdP9yAb=+o$h(!x{ zUIRALkEsd}L_Jb6SRXRZJl0t0KmG9d@k$4loYX)@MpgpXm+$>OO;+wsU}%~sMSk>$ z%sxsAB3pH@vyV;WpKi8m@;5s|!64z>M=WfWc?)ZXuaj55`WGwvA5oI;7ejXIX$@~c z8nt*O`PL3n@K?G;R)z1-6%dGZ!D*@TGHA~$z^KL_W-Su$|ysw+^L+E~k@$rgI{Q!?8-0E!8 zxM1)H2Ia=)v|0=5#_nsENYw|{A9NH0eDY*iW-h?79B5slt`(DXoRbW$9~>amy7XH( zR-_o?F9f>fNlmVQ^tlEa>bob+eGEz(iwrysCSL_qHaOvz>oZ6-<@`Yk78*~=-Hf$7iBwJ~-ifEs1-!r|d|(zgR~z=> zIInVoYz>zLUx*dIZu&Jxh2EDv?C$#LQdB!Yf)-q_53BkF4K;_jvD{(WFzkHqQ9ZE( z<%u`;VW(gpeXol(ZIc;%&59NBvTpl}`LN(IXOb3Y`bn`aN{<|3e{9BH#Zzp66|u)| z>Do<1WAqZyBC5Fv!I~<^5quNgk63qfCf|)FV#V)}!AAc&xWZuMf$Ct)-zP^xj()iw z>-*+o^?QRy{iMFTcM%H>ovhdiFL(aKco{7`0B1p=0B1qje(@IAS(_Q^JN%B4Y(}iO zbQcdoz&Hr703cSVJNNiAFdDq$7QSpac`gCU4L^G#tz{7O8;Bob%0yI;ubxP@5K3t0 z1-2+o57JrJE}aUk&!{VbuB+8~kkDN%cB>PFNrO%>oWK|0VIe(*M3l{){UzjE(yNx? za6e&zYF1dO&M}XviL;G-(iao>Hb1hTi2@U;Cg<8vlze2rbP=$k^wo!bQ6!6;@-~~) z??Zr9ow zA=l~)->N9Co}($XV}|D~o6=y>dJmYt?dtS?7h%KVm*EViR=vieKx2H$jfN_7sarUf zmSPznK6b+CmpQ@@2_jz$Z;uI8h*b0{FAUxTVwhGVYU5Jv&=!=^lYd%!U+i^irr>bM zzS-;46hU%`k9W?*#aA!loZ^7kQ-1d8BjD@C`u9G4nf&WdYnK}MH0^Y2s{gf9993(*A|G`f;iqo97N*~28;L6JPpJBBH4?^SgR5% zu%Yg3cJXp&_F-)NWGW0&J!R=tA3n=wK`qsRV6vO2y`u-y#hGk}Ulzti1=T!l`GPJS z=G4qAj~5F6ni1Vl57OFmut_+3a`qw0K}a<${V#*R`Rh!Ar%Rgw)+{Uc~8t-%Ihbq z-j+|>cbi;~yfyxkl4}LS^4QNXjSeB$4N@c%^hvmKtx z0pRve5B^)M{%_1@ZfZ$qfJ)8)TIgpItLK6NcyoUNz-Mjk@Ka&lMpD<*3J{3+tSkSr zZYI74MtK0d8Nh}Aj0?C^0))Z*0$Ko|4`5-fYw#Ztx|e`M)@=6g0nNk%s4v4`0NDV3 zk$(aNj2kYlyp9eg0Cite{bxChmkiMtuw(CkDy9OY{&D}pkOpXIL^z{~#&0%1E{ zK>kKWfRLbwwWXniwY9mU&99s0sLU*`5Fi`R0H`V1bHxF7)Oh~@{qLkxKW*>VxO>Mc z_9Xz6CBOv$`cuIK{DNOpS@b_v_iMb2Qk2^-fHr0VWM=p)9vIcH@vQ6}bS*6Yn+<0` zHS-Vv-qdTr#{}n3wF3e|XZ$C;U)Qd{m8L}r&_O_ewZqTP@pJJM`6Zf!wef%L?Uz~3 zpTS_ne+l+mInQ6()XNOo&n#$?|C{C4&G0hQ=rg7e;4A)%PJcP|_)Ff=moW%6^ug z8A_gu6#(#0?fWxw=jFpM^OZb5obmUE|C2J}zt06c~G6javMT=uh?kFRJn{;a>`(Kf~)={S*9)sq#zMmpb6ju-(@G1p8+%!%NJUqO#AJ zLyrH1`9}=EfBQ1Nly7}TZE*Sx)c-E#`m*{jB`KeY#NB?E=#S?4w?O4ff|v4t&jdW4 zzd`U1Vt_B1UW$Z0Gx_`c2GegzhP~u`sr&TIN$CF@od2W(^^)qPP{uQrcGz!F{ex`A zOQx5i1kX&Gk-x$8hdJ>6Qlj7`)yr7$XDZp4-=+e5Uu^!Y>-Li5WoYd)iE;dIll<|% z{z+`)CCkeg&Sw^b#NTH5b42G$f|v1g&jg|=|DOc^tHoYMG(A({rT+%i|7@$5p)Jq& zu9?4q|IdLgFWc>9B)~ISBVax9V!-~>SoO!R`1K^~<^J \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/library-build-transformer/gradlew.bat b/library-build-transformer/gradlew.bat new file mode 100755 index 0000000000..e95643d6a2 --- /dev/null +++ b/library-build-transformer/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/library-build-transformer/settings.gradle b/library-build-transformer/settings.gradle new file mode 100644 index 0000000000..8b32573375 --- /dev/null +++ b/library-build-transformer/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'library-build-transformer' diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt new file mode 100644 index 0000000000..38511246c4 --- /dev/null +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt @@ -0,0 +1,160 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer + +import com.android.build.api.transform.* +import com.google.common.collect.ImmutableSet +import io.realm.buildtransformer.asm.ClassPoolTransformer +import io.realm.buildtransformer.ext.packageHierarchyRootDir +import io.realm.buildtransformer.ext.shouldBeDeleted +import io.realm.buildtransformer.util.Stopwatch +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.File + +// Type aliases for improving readability +typealias ByteCodeTypeDescriptor = String +typealias QualifiedName = String +typealias ByteCodeMethodName = String + +// Package level logger +val logger: Logger = LoggerFactory.getLogger("realm-build-logger") + +/** + * Transformer that will strip all classes, methods and fields annotated with a given annotation from + * a specific Android flavour. It is also possible to provide a list of files to delete whether or + * not they have the annotation. These files will only be deleted from the defined flavour. + */ +class RealmBuildTransformer(private val flavorToStrip: String, + private val annotationQualifiedName: QualifiedName, + private val specificFilesToStrip: Set = setOf()) : Transform() { + + override fun getName(): String { + return "RealmBuildTransformer" + } + + override fun isIncremental(): Boolean { + return true + } + + override fun getScopes(): MutableSet { + return mutableSetOf(QualifiedContent.Scope.PROJECT) + } + + override fun getInputTypes(): Set { + return setOf(QualifiedContent.DefaultContentType.CLASSES) + } + + override fun getReferencedScopes(): MutableSet { + return ImmutableSet.of() + } + + override fun transform(context: Context?, + inputs: MutableCollection?, + referencedInputs: MutableCollection?, + outputProvider: TransformOutputProvider?, + isIncremental: Boolean) { + @Suppress("DEPRECATION") + super.transform(context, inputs, referencedInputs, outputProvider, isIncremental) + + // Poor mans version of detecting variants, since the Gradle API does not allow us to + // register a transformer for only a single build variant + // https://issuetracker.google.com/issues/37072849 + val transformClasses: Boolean = context?.variantName?.startsWith(flavorToStrip.toLowerCase()) == true + + val timer = Stopwatch() + timer.start("Build Transform time") + if (isIncremental) { + runIncrementalTransform(inputs!!, outputProvider!!, transformClasses) + } else { + runFullTransform(inputs!!, outputProvider!!, transformClasses) + } + timer.stop() + } + + private fun runFullTransform(inputs: MutableCollection, outputProvider: TransformOutputProvider, transformClasses: Boolean) { + logger.debug("Run full transform") + val outputDir = outputProvider.getContentLocation("realmlibrarytransformer", outputTypes, scopes, Format.DIRECTORY) + val inputFiles: MutableSet = mutableSetOf() + inputs.forEach { + it.directoryInputs.forEach { + // Non-incremental build: Include all files + val dirPath: String = it.file.absolutePath + it.file.walkTopDown() + .filter { it.isFile } + .filter { it.name.endsWith(".class") } + .forEach { file -> + file.packageHierarchyRootDir = dirPath + file.shouldBeDeleted = transformClasses && specificFilesToStrip.find { file.absolutePath.endsWith(it) } != null + inputFiles.add(file) + } + } + } + + transformClassFiles(outputDir, inputFiles, transformClasses) + } + + private fun runIncrementalTransform(inputs: MutableCollection, outputProvider: TransformOutputProvider, transformClasses: Boolean) { + logger.debug("Run incremental transform") + val outputDir = outputProvider.getContentLocation("realmlibrarytransformer", outputTypes, scopes, Format.DIRECTORY) + val inputFiles: MutableSet = mutableSetOf() + inputs.forEach { + it.directoryInputs.forEach iterateDirs@{ + if (!it.file.exists()) { + return@iterateDirs // Directory was deleted + } + val dirPath: String = it.file.absolutePath + it.changedFiles.entries + .filter { it.key.isFile } + .filter { it.key.name.endsWith(".class") } + .filterNot { it.value == Status.REMOVED } + .forEach { + val file: File = it.key + file.packageHierarchyRootDir = dirPath + file.shouldBeDeleted = transformClasses && specificFilesToStrip.find { file.absolutePath.endsWith(it) } != null + inputFiles.add(file) + } + } + } + + transformClassFiles(outputDir, inputFiles, transformClasses) + } + + private fun transformClassFiles(outputDir: File, inputFiles: MutableSet, transformClasses: Boolean) { + val files: Set = if (transformClasses) { + val transformer = ClassPoolTransformer(annotationQualifiedName, inputFiles) + transformer.transform() + } else { + inputFiles + } + + copyToOutput(outputDir, files) + } + + private fun copyToOutput(outputDir: File, files: Set) { + files.forEach { + val outputFile = File(outputDir, it.absolutePath.substring(it.packageHierarchyRootDir.length)) + if (it.shouldBeDeleted) { + if (outputFile.exists()) { + outputFile.delete() + } + } else { + it.copyTo(outputFile, overwrite = true) + } + } + } + +} diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/ClassPoolTransformer.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/ClassPoolTransformer.kt new file mode 100644 index 0000000000..49ba7b17c6 --- /dev/null +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/ClassPoolTransformer.kt @@ -0,0 +1,98 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.asm + +import io.realm.buildtransformer.ByteCodeMethodName +import io.realm.buildtransformer.ByteCodeTypeDescriptor +import io.realm.buildtransformer.QualifiedName +import io.realm.buildtransformer.asm.visitors.AnnotatedCodeStripVisitor +import io.realm.buildtransformer.asm.visitors.AnnotationVisitor +import io.realm.buildtransformer.ext.shouldBeDeleted +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import java.io.File + +/** + * Transformer that will transform a pool of classes by removing all classes, methods and fields annotated with + * a given annotation. + * + * It does so in 2 passes using the ASM Visitor API. The first pass will gather metadata about the class hierarchy, + * the 2nd pass will do the actual transform. The ASM Tree API was considered as well, but it does not provide + * access to referenced classes easily, so two passes would be required here as well and the Visitor API is faster and + * requires less memory. + */ +class ClassPoolTransformer(annotationQualifiedName: QualifiedName, private val inputClasses: Set) { + + private val annotationDescriptor: String = createDescriptor(annotationQualifiedName) + + /** + * Transform files + * + * @return All input files, both those that have been modified and those that have not. + */ + fun transform(): Set { + val (markedClasses, markedMethods) = pass1() + return pass2(markedClasses, markedMethods) + } + + /** + * Pass 1: Collect all classes, interfaces and enums that contain the given annotation. This include both top-level + * and inner types. + */ + private fun pass1(): Pair, Map>> { + val metadataCollector = AnnotationVisitor(annotationDescriptor) + inputClasses.forEach { + it.inputStream().use { + val classReader = ClassReader(it) + classReader.accept(metadataCollector, 0) + } + } + return Pair(metadataCollector.annotatedClasses, metadataCollector.annotatedMethods) + } + + /** + * Pass 2: Remove methods and fields marked with the annotation. Classes that are removed + * are instead marked for deletion as deleting the File is the responsibility of the + * transform API. + */ + private fun pass2(markedClasses: Set, markedMethods: Map>): Set { + inputClasses.forEach { classFile -> + var result = ByteArray(0) + if (!classFile.shouldBeDeleted) { // Respect previously set delete flag, so avoid doing any work + classFile.inputStream().use { inputStream -> + val writer = ClassWriter(0) // We don't modify methods so no reason to re-calculate method frames + val classRemover = AnnotatedCodeStripVisitor(annotationDescriptor, markedClasses, markedMethods, writer) + val reader = ClassReader(inputStream) + reader.accept(classRemover, 0) + result = if (classRemover.deleteClass) ByteArray(0) else writer.toByteArray() + } + if (result.isNotEmpty()) { + classFile.outputStream().use { outputStream -> outputStream.write(result) } + } else { + classFile.shouldBeDeleted = true + } + } + } + return inputClasses + } + + /** + * Creates the descriptor used by ASM to identify types. + */ + private fun createDescriptor(qualifiedName: String): String { + return "L${qualifiedName.replace(".", "/")};" + } +} diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt new file mode 100644 index 0000000000..c8ac8c359b --- /dev/null +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.asm.visitors + +import io.realm.buildtransformer.ByteCodeMethodName +import io.realm.buildtransformer.ByteCodeTypeDescriptor +import io.realm.buildtransformer.logger +import org.objectweb.asm.* +import org.objectweb.asm.AnnotationVisitor + +/** + * Visitor that will remove all classes, methods and fields annotated with given annotation. + * Doing this requires a pre-processing step performed by the [AnnotationVisitor]. + */ +class AnnotatedCodeStripVisitor(private val annotationDescriptor: String, + private val markedClasses: Set, + private val markedMethods: Map>, + classWriter: ClassVisitor) : ClassVisitor(Opcodes.ASM6, classWriter) { + + var deleteClass: Boolean = false + private lateinit var markedMethodsInClass: Set + + override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array?) { + // Only process this class if it or its super class doesn't have the given annotation + markedMethodsInClass = markedMethods[name!!]!! + deleteClass = (markedClasses.contains(name) || markedClasses.contains(superName)) + if (!deleteClass) { + super.visit(version, access, name, signature, superName, interfaces) + } else { + logger.debug("Removing top level class: $name") + } + } + + // Remove INNERCLASS definitions from the bytecode in the top level class. It isn't clear if + // these are used by any relevant API's, but better remove them just in case. + override fun visitInnerClass(name: String?, outerName: String?, innerName: String?, access: Int) { + if (!markedClasses.contains(name)) { + super.visitInnerClass(name, outerName, innerName, access) + } else { + logger.debug("Removing inner class description: $name") + } + } + + override fun visitField(access: Int, name: String?, descriptor: String?, signature: String?, value: Any?): FieldVisitor { + return object: FieldVisitor(api) { + var ignoreField = false + override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { + ignoreField = (annotationDescriptor == descriptor) + return null + } + override fun visitEnd() { + if (!ignoreField) { + // Call super ClassVisitor directly + this@AnnotatedCodeStripVisitor.cv.visitField(access, name, descriptor, signature, value) + } else { + logger.debug("Removing field: $name") + } + } + } + } + + override fun visitMethod(access: Int, name: ByteCodeMethodName?, descriptor: String?, signature: String?, exceptions: Array?): MethodVisitor? { + return if (!markedMethodsInClass.contains(name)) { + super.visitMethod(access, name, descriptor, signature, exceptions) + } else { + logger.debug("Removing method: $name") + null + } + } + +} \ No newline at end of file diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt new file mode 100644 index 0000000000..75a761c976 --- /dev/null +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.asm.visitors; + +import io.realm.buildtransformer.ByteCodeMethodName +import io.realm.buildtransformer.ByteCodeTypeDescriptor +import io.realm.buildtransformer.logger +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +/** + * ClassVisitor that gather all classes and methods with the given annotation. This is the first + * pass and is required for correctly identifying them in the 2nd pass before any byte code is + * written. + */ +class AnnotationVisitor(private val annotationDescriptor: String) : ClassVisitor(Opcodes.ASM6) { + + val annotatedClasses: MutableSet = mutableSetOf() + val annotatedMethods: MutableMap> = mutableMapOf() + private var internalQualifiedName: String = "" + private val annotatedMethodsInClass = mutableSetOf() + + override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array?) { + internalQualifiedName = name!! + annotatedMethods[internalQualifiedName] = annotatedMethodsInClass + super.visit(version, access, name, signature, superName, interfaces) + } + + override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { + if (descriptor == annotationDescriptor) { + annotatedClasses.add(internalQualifiedName) + } + return super.visitAnnotation(descriptor, visible) + } + + override fun visitMethod(access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array?): MethodVisitor { + val parentVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) + return object: MethodVisitor(api, parentVisitor) { + override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { + if (descriptor == annotationDescriptor) { + annotatedMethodsInClass.add(name!!) + } + return super.visitAnnotation(descriptor, visible) + } + } + } + + override fun visitEnd() { + annotatedMethods[internalQualifiedName] = annotatedMethodsInClass + super.visitEnd() + } + +} diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/ext/FileExt.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/ext/FileExt.kt new file mode 100644 index 0000000000..f989bbf183 --- /dev/null +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/ext/FileExt.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.ext + +import java.io.File +import java.util.* +import kotlin.reflect.KProperty + +// Add support for extension properties +// Credit: https://stackoverflow.com/questions/36502413/extension-fields-in-kotlin +class FieldProperty(val initializer: (R) -> T = { throw IllegalStateException("Field Property not initialized.") }) { + private val map = WeakHashMap() + + operator fun getValue(thisRef: R, property: KProperty<*>): T = + map[thisRef] ?: setValue(thisRef, property, initializer(thisRef)) + + operator fun setValue(thisRef: R, property: KProperty<*>, value: T): T { + map[thisRef] = value + return value + } +} + +/** + * If the file points to a compiled `.class` file, this property stores the path leading to the root folder of the + * package hierarchy. + */ +var File.packageHierarchyRootDir: String by FieldProperty() + +/** + * `true` if the file is marked for deletion after being processed. + */ +var File.shouldBeDeleted: Boolean by FieldProperty() diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/util/Stopwatch.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/util/Stopwatch.kt new file mode 100644 index 0000000000..52f4a2447c --- /dev/null +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/util/Stopwatch.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.util + +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.util.concurrent.TimeUnit + +class Stopwatch { + + val logger: Logger = LoggerFactory.getLogger("realm-stopwatch") + + var start: Long = -1L + var lastSplit: Long = -1L + lateinit var label: String + + /** + * Start the stopwatch. + */ + fun start(label: String) { + if (start != -1L) { + throw IllegalStateException("Stopwatch was already started"); + } + this.label = label + start = System.nanoTime(); + lastSplit = start; + } + + /** + * Reports the split time. + * + * @param label Label to use when printing split time + * @param reportDiffFromLastSplit if `true` report the time from last split instead of the start + */ + fun splitTime(label: String, reportDiffFromLastSplit: Boolean = true) { + val split = System.nanoTime() + val diff = if (reportDiffFromLastSplit) { split - lastSplit } else { split - start } + lastSplit = split; + logger.debug("$label: ${TimeUnit.NANOSECONDS.toMillis(diff)} ms.") + } + + /** + * Stops the timer and report the result. + */ + fun stop() { + val stop = System.nanoTime() + val diff = stop - start + logger.debug("$label: ${TimeUnit.NANOSECONDS.toMillis(diff)} ms.") + } +} \ No newline at end of file diff --git a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/NestedTestClass.java b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/NestedTestClass.java new file mode 100644 index 0000000000..0ef811df1e --- /dev/null +++ b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/NestedTestClass.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.testclasses; + +import io.realm.internal.annotations.ObjectServer; + +public class NestedTestClass { + public String name; + + @ObjectServer + public static class StaticInnerClass { + public String foo; + } + + + @ObjectServer + public class InnerClass { + public String foo; + } + + @ObjectServer + public enum Enum { + FOO + } + + @ObjectServer + public interface Interface { + void foo(); + } +} diff --git a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestClass.java b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestClass.java new file mode 100644 index 0000000000..54bcdb4c10 --- /dev/null +++ b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestClass.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.testclasses; + +import io.realm.internal.annotations.ObjectServer; + +@ObjectServer +public class SimpleTestClass { + public String name; + + public static class Foo { + public String bar; + } +} diff --git a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestFields.java b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestFields.java new file mode 100644 index 0000000000..3020f7fbd0 --- /dev/null +++ b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestFields.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.testclasses; + +import io.realm.internal.annotations.ObjectServer; + +public class SimpleTestFields { + + @ObjectServer + public String field1; + public String field2; +} diff --git a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestMethods.java b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestMethods.java new file mode 100644 index 0000000000..45c3377faf --- /dev/null +++ b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestMethods.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.testclasses; + +import io.realm.internal.annotations.ObjectServer; + +public class SimpleTestMethods { + + @ObjectServer + public String foo() { + return "foo"; + } + + @ObjectServer + public String foo1(String input) { + return "foo1"; + } + + public String bar() { + return "bar"; + } + +} diff --git a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SubClass.java b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SubClass.java new file mode 100644 index 0000000000..db3eca0d98 --- /dev/null +++ b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SubClass.java @@ -0,0 +1,20 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.testclasses; + +public class SubClass extends SuperClass { + public String foo; +} diff --git a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SuperClass.java b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SuperClass.java new file mode 100644 index 0000000000..7c00aa001a --- /dev/null +++ b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SuperClass.java @@ -0,0 +1,22 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer.testclasses; + +import io.realm.internal.annotations.ObjectServer; + +@ObjectServer +public class SuperClass { +} diff --git a/library-build-transformer/src/test/java/io/realm/internal/annotations/ObjectServer.java b/library-build-transformer/src/test/java/io/realm/internal/annotations/ObjectServer.java new file mode 100644 index 0000000000..58e8ec1069 --- /dev/null +++ b/library-build-transformer/src/test/java/io/realm/internal/annotations/ObjectServer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.internal.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Copy of an interface from the Realm Library (to break cyclic dependencies) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +public @interface ObjectServer { +} diff --git a/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/DynamicClassLoader.kt b/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/DynamicClassLoader.kt new file mode 100644 index 0000000000..b7ff85bc5f --- /dev/null +++ b/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/DynamicClassLoader.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer + +import java.io.File + +/** + * Custom ClassLoader that can be used to dynamically load a class that have been dynamically modified, i.e. we + * load it using the ByteArray that represents it. + */ +class DynamicClassLoader(parentLoader: ClassLoader) : ClassLoader(parentLoader) { + + fun loadClass(qualifiedClassName: String, pool: Set): Class<*> { + val classFile: File = pool.find { + it.absolutePath.endsWith("${qualifiedClassName.replace(".", "/")}.class") + } ?: throw IllegalStateException("Class pool does not contain: $qualifiedClassName") + + val classBytes: ByteArray = classFile.readBytes() + return defineClass(qualifiedClassName, classBytes, 0, classBytes.size) + } +} \ No newline at end of file diff --git a/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt b/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt new file mode 100644 index 0000000000..bd74b33d03 --- /dev/null +++ b/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.buildtransformer + +import io.realm.buildtransformer.asm.ClassPoolTransformer +import io.realm.buildtransformer.testclasses.* +import org.junit.Before +import org.junit.Test +import java.io.File +import kotlin.reflect.KClass +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +class VisitorTests { + + private lateinit var classLoader: DynamicClassLoader + private val qualifiedAnnotationName = "io.realm.internal.annotations.ObjectServer" + + @Before + fun setUp () { + classLoader = DynamicClassLoader(this::class.java.classLoader) + } + + @Test + fun removeFields() { + val c: Class = modifyClass(SimpleTestFields::class) + assetDefaultConstructorExists(c) + assertFieldExists("field2", c) + assertFieldRemoved("field1", c) + } + + @Test + fun removeMethods() { + val c: Class = modifyClass(SimpleTestMethods::class) + assetDefaultConstructorExists(c) + assertMethodExists("bar", c) + assertMethodRemoved("foo", c) + } + + @Test + fun removeTopLevelClass() { + try { + modifyClass(SimpleTestClass::class) + fail() + } catch(e: IllegalStateException) { + assertTrue(e.message?.contains("Class pool does not contain") == true) + } + } + + @Test + fun removeClassIfSuperClassIsAnnotated() { + try { + modifyClass(SubClass::class, setOf(SubClass::class, SuperClass::class)) + fail() + } catch(e: IllegalStateException) { + assertTrue(e.message?.contains("Class pool does not contain") == true) + } + } + + @Test + fun removeInnerClasses() { + // The reflection API does not make it possible to find inner classes, so we need to inspect the bytecode + // instead. We do this by checking the output of the transformer which will only output files modified and + // not classes deleted. + val inputClasses: MutableSet = mutableSetOf() + setOf>( + NestedTestClass::class, + NestedTestClass.InnerClass::class, + NestedTestClass.StaticInnerClass::class, + NestedTestClass.Enum::class, + NestedTestClass.Interface::class + ).forEach { inputClasses.add(getClassFile(it)) } + val transformer = ClassPoolTransformer(qualifiedAnnotationName, inputClasses) + val outputFiles: Set = transformer.transform() + assertEquals(1, outputFiles.size) // Only top level file is saved. + assertTrue(outputFiles.first().name.endsWith("NestedTestClass.class")) + } + + private fun assetDefaultConstructorExists(clazz: Class<*>) { + clazz.getConstructor() + } + + private fun assertFieldRemoved(fieldName: String, clazz: Class<*>) { + try { + clazz.getField(fieldName) + fail("Field $fieldName has not been removed"); + } catch (e: NoSuchFieldException) { + } + } + + private fun assertFieldExists(fieldName: String, clazz: Class<*>) { + clazz.getField(fieldName) + } + + private fun assertMethodRemoved(methodName: String, clazz: Class<*>) { + try { + clazz.getMethod(methodName) + fail("Method $methodName has not been removed"); + } catch (e: NoSuchMethodException) { + } + } + + private fun assertMethodExists(methodName: String, clazz: Class<*>) { + clazz.getMethod(methodName) // Will throw exception if it doesn't + } + + private fun modifyClass(clazz: KClass): Class { + return this.modifyClass(clazz, setOf(clazz)) + } + + private fun modifyClass(clazz: KClass, pool: Set>): Class { + val inputClasses: MutableSet = mutableSetOf() + pool.forEach { inputClasses.add(getClassFile(it)) } + val transformer = ClassPoolTransformer(qualifiedAnnotationName, inputClasses) + val outputFiles: Set = transformer.transform() + @Suppress("UNCHECKED_CAST") + return classLoader.loadClass(clazz.java.name, outputFiles) as Class + } + + private fun getClassFile(clazz: KClass<*>): File { + return getClassFile(clazz.java) + } + + private fun getClassFile(clazz: Class<*>): File { + val filePath = "${clazz.name.replace(".", "/")}.class" + return File(classLoader.getResource(filePath).file) + } +} diff --git a/realm/build.gradle b/realm/build.gradle index a1599ab796..af6f32dd91 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -23,6 +23,7 @@ buildscript { classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.4' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" + classpath "io.realm:realm-library-build-transformer:${file('../version.txt').text.trim()}" classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.13' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 532b468d5f..e3da497fe3 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -14,8 +14,7 @@ apply plugin: 'org.jetbrains.dokka' //apply plugin: 'com.github.kt3k.coveralls' //apply plugin: 'net.ltgt.errorprone' -import io.realm.transformer.RealmTransformer -android.registerTransform(new RealmTransformer(project)) +android.registerTransform(new io.realm.transformer.RealmTransformer(project)) android { compileSdkVersion rootProject.compileSdkVersion @@ -63,8 +62,8 @@ dependencies { implementation project(':realm-library') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" androidTestImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test:rules:1.0.1' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test:rules:1.0.2' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 7cb75bd181..7573d814bf 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -186,9 +186,18 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { coveralls.jacocoReportPath = "${buildDir}/reports/coverage/debug/report.xml" -import io.realm.transformer.RealmTransformer - -android.registerTransform(new RealmTransformer(project)) +android.registerTransform(new io.realm.transformer.RealmTransformer(project)) +android.registerTransform(new io.realm.buildtransformer.RealmBuildTransformer( + "base", + "io.realm.internal.annotations.ObjectServer", + [ + "io_realm_sync_permissions_ClassPermissionsRealmProxyInterface.class", + "io_realm_sync_permissions_PermissionRealmProxyInterface.class", + "io_realm_sync_permissions_PermissionUserRealmProxyInterface.class", + "io_realm_sync_permissions_RealmPermissionsRealmProxyInterface.class", + "io_realm_sync_permissions_RoleRealmProxyInterface.class" + ].toSet() +)) repositories { maven { url "https://jitpack.io" } From 97a19bb9b4b2e17c45e89f0610838c1893414330 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 17 Aug 2018 07:43:23 +0200 Subject: [PATCH 1286/2110] Correctly keep members of OsSubscription (#6110) --- CHANGELOG.md | 7 +++++++ .../main/java/io/realm/internal/sync/OsSubscription.java | 1 + 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f4774b781..ed71dbe768 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.4.3 (2018-08-09) + +### Bug Fixes + +* [ObjectServer] ProGuard was not configured correctly when working with Subscriptions for Query-based Realms. + + ## 5.4.2 (2018-08-09) ### Bug Fixes diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java b/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java index 608f91f601..adbc7b4804 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java @@ -24,6 +24,7 @@ import io.realm.internal.ObserverPairList; import io.realm.internal.OsResults; +@KeepMember public class OsSubscription implements NativeObject { private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); From 3a36b1ab058c443427dad5ae55400e116e9e3c88 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 17 Aug 2018 10:02:17 +0200 Subject: [PATCH 1287/2110] Make ObjectServerExample more accessible (#6111) --- examples/objectServerExample/build.gradle | 41 ++++++------------- .../objectserver/CounterActivity.java | 24 +++++++---- .../examples/objectserver/LoginActivity.java | 4 +- 3 files changed, 29 insertions(+), 40 deletions(-) diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index f7520d4618..1dbf552f12 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -1,26 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'realm-android' -// Credit: http://jeremie-martinez.com/2015/05/05/inject-host-gradle/ -def getIP() { - InetAddress result = null - Enumeration interfaces = NetworkInterface.getNetworkInterfaces() - while (interfaces.hasMoreElements()) { - Enumeration addresses = interfaces.nextElement().getInetAddresses() - while (addresses.hasMoreElements()) { - InetAddress address = addresses.nextElement() - if (!address.isLoopbackAddress()) { - if (address.isSiteLocalAddress()) { - return address.getHostAddress() - } else if (result == null) { - result = address - } - } - } - } - return (result != null ? result : InetAddress.getLocalHost()).getHostAddress() -} - android { compileSdkVersion rootProject.sdkVersion buildToolsVersion rootProject.buildTools @@ -34,18 +14,23 @@ android { } buildTypes { - // This will automatically try to detect the IP address of the machine - // building the example. It is assumed that this machine is also running - // the Object Server. If not, replace 'host' with the IP of the machine - // hosting the server. In some cases the wrong IP address will also - // be detected. In that case also insert the IP address manually. - def host = getIP() + // Go to https://cloud.realm.io and copy the URL to your instance. Insert it below. + // It will look something like "https://test.us1.cloud.realm.io" + // + // If you're running a self-hosted version, use the hostname/IP address of the Realm Object + // Server, e.g "http://127.0.0.1:9080". + def rosUrl = "" + def realmAuthUrl = "\"${rosUrl}/auth\"" + def realmUrl = "\"${rosUrl.replace("http", "realm")}/default\"" + debug { - buildConfigField "String", "OBJECT_SERVER_IP", "\"${host}\"" + buildConfigField "String", "REALM_AUTH_URL", "${realmAuthUrl}" + buildConfigField "String", "REALM_URL", "${realmUrl}" minifyEnabled true } release { - buildConfigField "String", "OBJECT_SERVER_IP", "\"${host}\"" + buildConfigField "String", "REALM_AUTH_URL", "${realmAuthUrl}" + buildConfigField "String", "REALM_URL", "${realmUrl}" minifyEnabled true signingConfig signingConfigs.debug } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java index ce6b051beb..26d129cd2d 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java @@ -34,11 +34,13 @@ import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; +import io.realm.OrderedCollectionChangeSet; +import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Progress; import io.realm.ProgressListener; import io.realm.ProgressMode; import io.realm.Realm; -import io.realm.RealmChangeListener; +import io.realm.RealmResults; import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.SyncSession; @@ -47,7 +49,6 @@ import me.zhanghai.android.materialprogressbar.MaterialProgressBar; public class CounterActivity extends AppCompatActivity { - private static final String REALM_URL = "realm://" + BuildConfig.OBJECT_SERVER_IP + ":9080/~/default"; private final ProgressListener downloadListener = new ProgressListener() { @Override @@ -79,7 +80,7 @@ public void run() { @BindView(R.id.text_counter) TextView counterView; @BindView(R.id.progressbar) MaterialProgressBar progressBar; - private CRDTCounter counter; // Keep strong reference to counter to keep change listeners alive. + private RealmResults counters; // Keep strong reference to counter to keep change listeners alive. @Override protected void onCreate(Bundle savedInstanceState) { @@ -95,7 +96,7 @@ protected void onStart() { if (user == null) { return; } // Create a RealmConfiguration for our user - SyncConfiguration config = user.createConfiguration(REALM_URL) + SyncConfiguration config = user.createConfiguration(BuildConfig.REALM_URL) .initialData(new Realm.Transaction() { @Override public void execute(@Nonnull Realm realm) { @@ -108,11 +109,16 @@ public void execute(@Nonnull Realm realm) { realm = Realm.getInstance(config); counterView.setText("-"); - counter = realm.where(CRDTCounter.class).equalTo("name", user.getIdentity()).findFirstAsync(); - counter.addChangeListener(new RealmChangeListener() { + counters = realm.where(CRDTCounter.class).equalTo("name", user.getIdentity()).findAllAsync(); + counters.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override - public void onChange(@Nonnull CRDTCounter counter) { - counterView.setText((!counter.isValid()) ? "-" : String.format(Locale.US, "%d", counter.getCount())); + public void onChange(RealmResults counters, OrderedCollectionChangeSet changeSet) { + if (counters.isValid() && !counters.isEmpty()) { + CRDTCounter counter = counters.first(); + counterView.setText(String.format(Locale.US, "%d", counter.getCount())); + } else { + counterView.setText("-"); + } } }); @@ -132,7 +138,7 @@ protected void onStop() { } closeRealm(); user = null; - counter = null; + counters = null; } @Override diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java index 2f87b3a0bd..d16b593802 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java @@ -34,8 +34,6 @@ public class LoginActivity extends AppCompatActivity { - private static final String REALM_AUTH_URL = "http://" + BuildConfig.OBJECT_SERVER_IP + ":9080/auth"; - @BindView(R.id.input_username) EditText username; @BindView(R.id.input_password) EditText password; @BindView(R.id.button_login) Button loginButton; @@ -103,7 +101,7 @@ public void onError(@Nonnull ObjectServerError error) { } }; - SyncUser.logInAsync(creds, REALM_AUTH_URL, callback); + SyncUser.logInAsync(creds, BuildConfig.REALM_AUTH_URL, callback); } @Override From 8908f5b02aa00f6c5828ca23084a472a00f8a187 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 17 Aug 2018 10:27:39 +0200 Subject: [PATCH 1288/2110] Fix changelog --- CHANGELOG.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f52b79f778..301df21c5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,3 @@ -<<<<<<< HEAD -## 5.4.3 (2018-08-09) - -### Bug Fixes - -* [ObjectServer] ProGuard was not configured correctly when working with Subscriptions for Query-based Realms. -======= ## 5.5.0 (YYYY-MM-DD) ### Enhancements @@ -20,7 +13,13 @@ ### Internal * Updated to Object Store commit: 97fd03819f398b3c81c8b007feaca8636629050b ->>>>>>> master + + +## 5.4.3 (YYYY-MM-DD) + +### Bug Fixes + +* [ObjectServer] ProGuard was not configured correctly when working with Subscriptions for Query-based Realms. ## 5.4.2 (2018-08-09) From ddd8fb96ac677e6ba29a31ce3c274663d1e7524b Mon Sep 17 00:00:00 2001 From: Lucas Dornelas Vieira Date: Fri, 24 Aug 2018 09:42:31 -0300 Subject: [PATCH 1289/2110] Optimizing the methods 'copyFromRealm', 'copyToRealmOrUpdate' and 'copyToRealm' using a List with initialCapacity (#6124) --- .../src/main/java/io/realm/Realm.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index d6f25dbc53..c6713a71c0 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1057,8 +1057,13 @@ public List copyToRealm(Iterable objects) { if (objects == null) { return new ArrayList<>(); } + ArrayList realmObjects; + if (objects instanceof Collection) { + realmObjects = new ArrayList<>(((Collection) objects).size()); + } else { + realmObjects = new ArrayList<>(); + } Map cache = new HashMap<>(); - ArrayList realmObjects = new ArrayList<>(); for (E object : objects) { checkNotNullObject(object); realmObjects.add(copyOrUpdate(object, false, cache)); @@ -1228,8 +1233,13 @@ public List copyToRealmOrUpdate(Iterable objects) { return new ArrayList<>(0); } + ArrayList realmObjects; + if (objects instanceof Collection) { + realmObjects = new ArrayList<>(((Collection) objects).size()); + } else { + realmObjects = new ArrayList<>(); + } Map cache = new HashMap<>(); - ArrayList realmObjects = new ArrayList<>(); for (E object : objects) { checkNotNullObject(object); realmObjects.add(copyOrUpdate(object, true, cache)); @@ -1287,7 +1297,12 @@ public List copyFromRealm(Iterable realmObjects, in return new ArrayList<>(0); } - ArrayList unmanagedObjects = new ArrayList<>(); + ArrayList unmanagedObjects; + if (realmObjects instanceof Collection) { + unmanagedObjects = new ArrayList<>(((Collection) realmObjects).size()); + } else { + unmanagedObjects = new ArrayList<>(); + } Map> listCache = new HashMap<>(); for (E object : realmObjects) { checkValidObjectForDetach(object); From 48abf007be3754d5038e9a797b1e865130a426d5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 24 Aug 2018 14:46:11 +0200 Subject: [PATCH 1290/2110] Add credits to changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 301df21c5c..b006372eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ * Updated to Object Store commit: 97fd03819f398b3c81c8b007feaca8636629050b +### Credits + +* Thanks to @lucasdornelasv for improving the performance of `Realm.copyToRealm()`, `Realm.copyToRealmOrUpdate()` and `Realm.copyFromRealm()` #(6124). + ## 5.4.3 (YYYY-MM-DD) From afc79e568d3f710fcdf99d30bf67b33c8134f6c6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 27 Aug 2018 13:21:32 +0200 Subject: [PATCH 1291/2110] Add support for Realm.syncSession as an extension method (#6038) --- CHANGELOG.md | 4 +- realm/kotlin-extensions/build.gradle | 12 +++- .../io/realm/kotlin/KotlinSyncedRealmTests.kt | 71 +++++++++++++++++++ .../io/realm/kotlin/SyncedRealmExtensions.kt | 53 ++++++++++++++ realm/realm-library/build.gradle | 4 +- .../src/main/java/io/realm/Realm.java | 3 - .../realm/TestSyncConfigurationFactory.java | 0 .../realm/objectserver/utils/Constants.java | 0 .../java/io/realm/util/SyncTestUtils.java | 0 .../io/realm/entities/AllKotlinTypes.kt | 0 10 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt create mode 100644 realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt rename realm/realm-library/src/{androidTestObjectServer => syncTestUtils}/java/io/realm/TestSyncConfigurationFactory.java (100%) rename realm/realm-library/src/{syncIntegrationTest => syncTestUtils}/java/io/realm/objectserver/utils/Constants.java (100%) rename realm/realm-library/src/{androidTestObjectServer => syncTestUtils}/java/io/realm/util/SyncTestUtils.java (100%) rename realm/realm-library/src/{androidTest => testUtils}/kotlin/io/realm/entities/AllKotlinTypes.kt (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b006372eb8..2278469ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ * [ObjectServer] Added `ConnectionState` enum describing the states a connection can be in. * [ObjectServer] Added `SyncSession.isConnected()`. * [ObjectServer] Added support for observing connection changes for a session using `SyncSession.addConnectionChangeListener()` and `SyncSession.removeConnectionChangeListener()`. - +* [ObjectServer] Added Kotlin extension property `Realm.syncSession` for synchronized Realms. +* [ObjectServer] Added Kotlin extension method `Realm.classPermissions()`. + ### Bug Fixes * Methods and classes requiring synchronized Realms have been removed from the standard AAR package. They are now only visible when enabling synchronized Realms in Gradle. The methods and classes will still be visible in the source files and docs, but annotated with `@ObjectServer` (#5799). diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index e3da497fe3..19047d58ba 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -54,7 +54,12 @@ android { main.java.srcDirs += 'src/main/kotlin' androidTest.java.srcDirs += ['src/androidTest/kotlin', '../realm-library/src/testUtils/java'] objectServer.java.srcDirs += 'src/objectServer/kotlin' - androidTestObjectServer.java.srcDirs += 'src/androidTestObjectServer/kotlin' + androidTestObjectServer.java.srcDirs += [ + 'src/androidTestObjectServer/kotlin', + '../realm-library/src/testUtils/java', + '../realm-library/src/testUtils/kotlin', + '../realm-library/src/syncTestUtils/java', + ] } } @@ -66,6 +71,11 @@ dependencies { androidTestImplementation 'com.android.support.test:rules:1.0.2' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' + androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' + androidTestObjectServerImplementation 'com.google.code.findbugs:jsr305:3.0.2' + + } repositories { diff --git a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt new file mode 100644 index 0000000000..3ae5cf5d6a --- /dev/null +++ b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt @@ -0,0 +1,71 @@ +package io.realm.kotlin + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 +import io.realm.Realm +import io.realm.SyncConfiguration +import io.realm.SyncManager +import io.realm.TestSyncConfigurationFactory +import io.realm.entities.SimpleClass +import io.realm.objectserver.utils.Constants +import io.realm.util.SyncTestUtils +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class KotlinSyncedRealmTests { + + @get:Rule + val configFactory = TestSyncConfigurationFactory() + + + private lateinit var realm: Realm + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getTargetContext()) + val user = SyncTestUtils.createTestUser() + realm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM).build()) + } + + @After + fun tearDown() { + realm.close() + } + + @Test + fun syncSession() { + assertEquals(SyncManager.getSession(realm.configuration as SyncConfiguration), realm.syncSession) + } + + @Test + fun syncSession_throwsForNonSyncRealm() { + realm.close() + realm = Realm.getInstance(configFactory.createConfiguration()) + try { + realm.syncSession + fail() + } catch (ignored: IllegalStateException) { + } + } + + @Test + fun classPermissions() { + assertNotNull(realm.classPermissions()) + } + + @Test + fun classPermissions_throwsForNonSyncRealm() { + realm.close() + realm = Realm.getInstance(configFactory.createConfiguration()) + try { + realm.classPermissions() + fail() + } catch (ignored: IllegalStateException) { + } + } +} diff --git a/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt b/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt new file mode 100644 index 0000000000..b7a29fe64b --- /dev/null +++ b/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.kotlin + +import io.realm.Realm +import io.realm.RealmModel +import io.realm.SyncConfiguration +import io.realm.SyncManager +import io.realm.SyncSession +import io.realm.sync.permissions.ClassPermissions + + +/** + * Returns the [SyncSession] associated with this Realm. + * + * @return the [SyncSession] associated with this Realm. + * @throws IllegalStateException if the Realm is not a synchronized Realm. + */ +val Realm.syncSession: SyncSession + get() { + if (!(this.configuration is SyncConfiguration)) { + throw IllegalStateException("This method is only available on synchronized Realms") + } + return SyncManager.getSession(this.configuration as SyncConfiguration) + } + +/** + * Returns all permissions associated with the given class. Attach a change listener using + * [ClassPermissions.addChangeListener] to be notified about any future changes. + * + * @return the permissions for the given class or `null` if no permissions where found. + * @throws RealmException if the class is not part of this Realms schema. + * @throws IllegalStateException if the Realm is not a synchronized Realm. + */ +inline fun Realm.classPermissions(): ClassPermissions { + if (!(this.configuration is SyncConfiguration)) { + throw java.lang.IllegalStateException("This method is only available on synchronized Realms") + } + return this.getPermissions(T::class.java) +} diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 7573d814bf..b36f137ced 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -103,10 +103,10 @@ android { sourceSets { androidTest { - java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java'] + java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java', 'src/testUtils/kotlin'] } androidTestObjectServer { - java.srcDirs += 'src/syncIntegrationTest/java' + java.srcDirs += ['src/syncIntegrationTest/java', 'src/syncTestUtils/java'] assets.srcDirs += ['src/syncIntegrationTest/assets/'] } } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index c6713a71c0..f1d21efeaf 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -66,10 +66,8 @@ import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; -import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.TableQuery; -import io.realm.internal.UncheckedRow; import io.realm.internal.Util; import io.realm.internal.annotations.ObjectServer; import io.realm.internal.async.RealmAsyncTaskImpl; @@ -77,7 +75,6 @@ import io.realm.sync.permissions.ClassPermissions; import io.realm.sync.permissions.ClassPrivileges; import io.realm.sync.permissions.RealmPermissions; -import io.realm.sync.permissions.RealmPrivileges; import io.realm.sync.permissions.Role; /** diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java similarity index 100% rename from realm/realm-library/src/androidTestObjectServer/java/io/realm/TestSyncConfigurationFactory.java rename to realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java similarity index 100% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/Constants.java rename to realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/util/SyncTestUtils.java similarity index 100% rename from realm/realm-library/src/androidTestObjectServer/java/io/realm/util/SyncTestUtils.java rename to realm/realm-library/src/syncTestUtils/java/io/realm/util/SyncTestUtils.java diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/AllKotlinTypes.kt b/realm/realm-library/src/testUtils/kotlin/io/realm/entities/AllKotlinTypes.kt similarity index 100% rename from realm/realm-library/src/androidTest/kotlin/io/realm/entities/AllKotlinTypes.kt rename to realm/realm-library/src/testUtils/kotlin/io/realm/entities/AllKotlinTypes.kt From 32ba016da14963d5015af192b324d482ff4c0caf Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 30 Aug 2018 11:31:26 +0200 Subject: [PATCH 1292/2110] Added support for stopping and starting a session (#6135) --- CHANGELOG.md | 3 +- .../java/io/realm/SessionTests.java | 8 ++ .../src/main/cpp/io_realm_SyncSession.cpp | 31 ++++++ .../java/io/realm/SyncSession.java | 33 +++++++ .../java/io/realm/SyncSessionTests.java | 97 ++++++++++++++----- 5 files changed, 148 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2278469ae2..c8b750fdf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ * [ObjectServer] Added support for observing connection changes for a session using `SyncSession.addConnectionChangeListener()` and `SyncSession.removeConnectionChangeListener()`. * [ObjectServer] Added Kotlin extension property `Realm.syncSession` for synchronized Realms. * [ObjectServer] Added Kotlin extension method `Realm.classPermissions()`. - +* [ObjectServer] Added support for starting and stopping synchronization using `SyncSession.start()` and `SyncSession.stop()` (#6135). + ### Bug Fixes * Methods and classes requiring synchronized Realms have been removed from the standard AAR package. They are now only visible when enabling synchronized Realms in Gradle. The methods and classes will still be visible in the source files and docs, but annotated with `@ObjectServer` (#5799). diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 6ea9d4956b..f28ab6460c 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -469,4 +469,12 @@ public void isConnected_falseForInvalidUser() { } } + @Test + public void close_doesNotThrowIfCalledWhenRealmIsClosed() { + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getSession(configuration); + realm.close(); + session.stop(); + assertEquals(SyncSession.State.INACTIVE, session.getState()); + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 140e8e5f1d..89849efc4f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -330,3 +330,34 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveConnectionListener( } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStart(JNIEnv* env, jclass, jstring j_local_realm_path) +{ + TR_ENTER() + try { + JStringAccessor local_realm_path(env, j_local_realm_path); + auto session = SyncManager::shared().get_existing_session(local_realm_path); + if (!session) { + // FIXME: We should lift this restriction + ThrowException(env, IllegalState, + "Cannot call start() before a session is " + "created. A session will be created after the first call to Realm.getInstance()."); + return; + } + session->revive_if_needed(); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStop(JNIEnv* env, jclass, jstring j_local_realm_path) +{ + TR_ENTER() + try { + JStringAccessor local_realm_path(env, j_local_realm_path); + auto session = SyncManager::shared().get_existing_session(local_realm_path); + if (session) { + session->log_out(); + } + } + CATCH_STD() +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index f099649332..08cfd937e2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -510,6 +510,37 @@ public void uploadAllLocalChanges() throws InterruptedException { } } + /** + * Attempts to start the session and enable synchronization with the Realm Object Server. + *

              + * This happens automatically when opening the Realm instance, so doing it manually should only + * be needed if the session was stopped using {@link #stop()}. + *

              + * If the session was already started, calling this method will do nothing. + *

              + * A session is considered started if {@link #getState()} returns either {@link State#ACTIVE} or + * {@link State#WAITING_FOR_ACCESS_TOKEN}. If the session is {@link State#DYING}, the session + * will be moved back to {@link State#ACTIVE}. + * + * @see #getState() + * @see #stop() + */ + public synchronized void start() { + nativeStart(configuration.getPath()); + } + + /** + * Stops any synchronization with the Realm Object Server until the Realm is re-opened again + * after fully closing it. + *

              + * Synchronization can be re-enabled by calling {@link #start()} again. + *

              + * If the session is already stopped, calling this method will do nothing. + */ + public synchronized void stop() { + nativeStop(configuration.getPath()); + } + void setResolvedRealmURI(URI resolvedRealmURI) { this.resolvedRealmURI = resolvedRealmURI; } @@ -859,4 +890,6 @@ public void throwExceptionIfNeeded() { private native boolean nativeWaitForUploadCompletion(int callbackId, String localRealmPath); private static native byte nativeGetState(String localRealmPath); private static native byte nativeGetConnectionState(String localRealmPath); + private static native void nativeStart(String localRealmPath); + private static native void nativeStop(String localRealmPath); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index c14df626b9..84928ea24b 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -36,9 +36,40 @@ @RunWith(AndroidJUnit4.class) public class SyncSessionTests extends StandardIntegrationTest { + @Rule public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + private interface SessionCallback { + void onReady(SyncSession session); + } + + private SyncSession getSession() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build(); + looperThread.closeAfterTest(Realm.getInstance(syncConfiguration)); + return SyncManager.getSession(syncConfiguration); + } + + private void getActiveSession(SessionCallback callback) { + SyncSession session = getSession(); + if (session.isConnected()) { + callback.onReady(session); + } else { + session.addConnectionChangeListener(new ConnectionListener() { + @Override + public void onChange(ConnectionState oldState, ConnectionState newState) { + if (newState == ConnectionState.CONNECTED) { + session.removeConnectionChangeListener(this); + callback.onReady(session); + } + } + }); + } + } + @Test(timeout=3000) public void getState_active() { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); @@ -513,18 +544,13 @@ public void run() { @Test @RunTestInLooperThread public void registerConnectionListener() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - Realm realm = Realm.getInstance(syncConfiguration); - SyncSession session = SyncManager.getSession(syncConfiguration); + SyncSession session = getSession(); session.addConnectionChangeListener((oldState, newState) -> { if (newState == ConnectionState.DISCONNECTED) { looperThread.testComplete(); } }); - realm.close(); + session.stop(); } @Test @@ -556,22 +582,47 @@ public void removeConnectionListener() { @Test @RunTestInLooperThread public void isConnected() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - looperThread.closeAfterTest(Realm.getInstance(syncConfiguration)); - SyncSession session = SyncManager.getSession(syncConfiguration); - if (session.isConnected()) { + getActiveSession(session -> { + assertEquals(session.getConnectionState(), ConnectionState.CONNECTED); + assertTrue(session.isConnected()); looperThread.testComplete(); - } else { - session.addConnectionChangeListener(((oldState, newState) -> { - if (newState == ConnectionState.CONNECTED) { - assertEquals(session.getConnectionState(), ConnectionState.CONNECTED); - assertTrue(session.isConnected()); - looperThread.testComplete(); - } - })); - } + }); + } + + @Test + @RunTestInLooperThread + public void stopStartSession() { + getActiveSession(session -> { + assertEquals(SyncSession.State.ACTIVE, session.getState()); + session.stop(); + assertEquals(SyncSession.State.INACTIVE, session.getState()); + session.start(); + assertNotEquals(SyncSession.State.INACTIVE, session.getState()); + looperThread.testComplete(); + }); + } + + @Test + @RunTestInLooperThread + public void start_multipleTimes() { + getActiveSession(session -> { + session.start(); + assertEquals(SyncSession.State.ACTIVE, session.getState()); + session.start(); + assertEquals(SyncSession.State.ACTIVE, session.getState()); + looperThread.testComplete(); + }); + } + + + @Test + @RunTestInLooperThread + public void stop_multipleTimes() { + SyncSession session = getSession(); + session.stop(); + assertEquals(SyncSession.State.INACTIVE, session.getState()); + session.stop(); + assertEquals(SyncSession.State.INACTIVE, session.getState()); + looperThread.testComplete(); } } From 4f9365f2a958a0d65d8514899d18b34f1fe0aff3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 30 Aug 2018 16:31:25 +0200 Subject: [PATCH 1293/2110] Add support for Sync 3.9.3 (#6136) --- CHANGELOG.md | 4 +++- dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8b750fdf5..e70acb50c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,9 @@ ### Internal -* Updated to Object Store commit: 97fd03819f398b3c81c8b007feaca8636629050b +* Updated to Realm Sync 3.9.3 +* Updated to Realm Core 5.8.0 +* Updated to Object Store commit: b0fc2814d9e6061ce5ba1da887aab6cfba4755ca ### Credits diff --git a/dependencies.list b/dependencies.list index 9becc34d10..1b4518033e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.8.8 -REALM_SYNC_SHA256=a0cbc5b46dbc3a9351bd002ccbcb07bf2a5fd13f9f1b7ef6ed51df931b46587b +REALM_SYNC_VERSION=3.9.3 +REALM_SYNC_SHA256=fa407408f2dd53d1cb3d3664cb3001b280d6c772d969a0a31112b132972a6973 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 97fd03819f..b0fc2814d9 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 97fd03819f398b3c81c8b007feaca8636629050b +Subproject commit b0fc2814d9e6061ce5ba1da887aab6cfba4755ca From bf8776dd8bc978cfff499fc8eb4b0e80be78bbdc Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 31 Aug 2018 07:37:20 +0200 Subject: [PATCH 1294/2110] Add support for custom headers for improved proxy support. (#6131) --- CHANGELOG.md | 10 +- realm/kotlin-extensions/build.gradle | 4 + .../io/realm/kotlin/KotlinSyncedRealmTests.kt | 2 +- .../io/realm/internal/OsObjectStoreTests.java | 18 ++ .../io/realm/AuthenticateRequestTests.java | 1 - .../io/realm/ObjectLevelPermissionsTest.java | 2 +- .../java/io/realm/SchemaTests.java | 1 - .../java/io/realm/SessionTests.java | 3 +- .../java/io/realm/SyncConfigurationTests.java | 9 +- .../java/io/realm/SyncManagerTests.java | 175 +++++++++++++++++- .../java/io/realm/SyncUserTests.java | 28 ++- .../io/realm/SyncedRealmMigrationTests.java | 3 +- .../java/io/realm/SyncedRealmTests.java | 2 - .../cpp/io_realm_internal_OsRealmConfig.cpp | 26 ++- .../io/realm/internal/ObjectServerFacade.java | 4 +- .../java/io/realm/internal/OsRealmConfig.java | 39 +++- .../java/io/realm/SyncConfiguration.java | 94 ++++++---- .../java/io/realm/SyncManager.java | 166 +++++++++++++++++ .../internal/SyncObjectServerFacade.java | 22 ++- .../network/AuthenticationServer.java | 15 ++ .../network/OkHttpAuthenticationServer.java | 87 ++++++++- .../java/io/realm/BaseIntegrationTest.java | 51 +---- .../io/realm/IsolatedIntegrationTests.java | 6 +- .../io/realm/StandardIntegrationTest.java | 6 +- .../java/io/realm/SyncSessionTests.java | 1 - .../io/realm/SyncedRealmIntegrationTests.java | 95 +++++++++- .../java/io/realm/objectserver/AuthTests.java | 3 +- .../EncryptedSynchronizedRealmTests.java | 2 +- .../objectserver/QueryBasedSyncTests.java | 2 +- .../io/realm/{util => }/SyncTestUtils.java | 59 +++++- .../realm/objectserver/utils/UserFactory.java | 0 .../objectserver/utils/UserFactoryStore.java | 0 32 files changed, 789 insertions(+), 147 deletions(-) rename realm/realm-library/src/syncTestUtils/java/io/realm/{util => }/SyncTestUtils.java (71%) rename realm/realm-library/src/{syncIntegrationTest => syncTestUtils}/java/io/realm/objectserver/utils/UserFactory.java (100%) rename realm/realm-library/src/{syncIntegrationTest => syncTestUtils}/java/io/realm/objectserver/utils/UserFactoryStore.java (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e70acb50c7..1396fdc119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,15 @@ * [ObjectServer] Added Kotlin extension property `Realm.syncSession` for synchronized Realms. * [ObjectServer] Added Kotlin extension method `Realm.classPermissions()`. * [ObjectServer] Added support for starting and stopping synchronization using `SyncSession.start()` and `SyncSession.stop()` (#6135). - +* [ObjectServer] Added API's for making it easier to work with network proxies (#6163): + * `SyncManager.setAuthorizationHeaderName(String headerName)` + * `SyncManager.setAuthorizationHeaderName(String headerName, String host)` + * `SyncManager.addCustomRequestHeader(String headerName, String headerValue)` + * `SyncManager.addCustomRequestHeader(String headerName, String headerValue, String host)` + * `SyncManager.addCustomRequestHeaders(Map headers)` + * `SyncManager.addCustomRequestHeaders(Map headers, String host)` + * `SyncConfiguration.Builder.urlPrefix(String prefix)` + ### Bug Fixes * Methods and classes requiring synchronized Realms have been removed from the standard AAR package. They are now only visible when enabling synchronized Realms in Gradle. The methods and classes will still be visible in the source files and docs, but annotated with `@ObjectServer` (#5799). diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 19047d58ba..4f7841a9ae 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -61,6 +61,10 @@ android { '../realm-library/src/syncTestUtils/java', ] } + compileOptions { + targetCompatibility 1.8 + sourceCompatibility 1.8 + } } dependencies { diff --git a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt index 3ae5cf5d6a..9750686ee9 100644 --- a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt +++ b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt @@ -8,7 +8,7 @@ import io.realm.SyncManager import io.realm.TestSyncConfigurationFactory import io.realm.entities.SimpleClass import io.realm.objectserver.utils.Constants -import io.realm.util.SyncTestUtils +import io.realm.SyncTestUtils import org.junit.After import org.junit.Assert.* import org.junit.Before diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java index 290a2133e4..ed671e2e0b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java @@ -17,14 +17,20 @@ import android.support.test.runner.AndroidJUnit4; +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import io.realm.RealmConfiguration; +import io.realm.SyncTestUtils; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; import io.realm.rule.TestRealmConfigurationFactory; import static junit.framework.Assert.assertEquals; @@ -40,6 +46,18 @@ public class OsObjectStoreTests { @Rule public final ExpectedException thrown = ExpectedException.none(); + + @Before + public void setUp() throws IOException { + SyncTestUtils.prepareEnvironmentForTest(); + RealmLog.setLevel(LogLevel.ERROR); + } + + @After + public void tearDown() { + SyncTestUtils.restoreEnvironmentAfterTest(); + } + @Test public void callWithLock() { RealmConfiguration config = configFactory.createConfiguration(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index 957a1f196a..492acda0ea 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -18,7 +18,6 @@ import io.realm.internal.network.AuthenticateRequest; import io.realm.internal.network.AuthenticationServer; import io.realm.internal.objectserver.Token; -import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java index 21471813c2..79eb7fd407 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java @@ -37,7 +37,7 @@ import io.realm.sync.permissions.RealmPrivileges; import io.realm.sync.permissions.Role; -import static io.realm.util.SyncTestUtils.createTestUser; +import static io.realm.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java index e1c613eb86..f91bd6520e 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java @@ -28,7 +28,6 @@ import java.util.Set; import io.realm.entities.StringOnly; -import io.realm.util.SyncTestUtils; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index f28ab6460c..472c26a581 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -30,14 +30,13 @@ import io.realm.entities.StringOnly; import io.realm.exceptions.RealmFileException; -import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.log.RealmLog; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import static io.realm.util.SyncTestUtils.createTestUser; +import static io.realm.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index c1d65abc22..8b3438af3c 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -37,10 +37,9 @@ import io.realm.entities.StringOnly; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; -import io.realm.util.SyncTestUtils; -import static io.realm.util.SyncTestUtils.createNamedTestUser; -import static io.realm.util.SyncTestUtils.createTestUser; +import static io.realm.SyncTestUtils.createNamedTestUser; +import static io.realm.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -485,7 +484,7 @@ public void getDefaultConfiguration_throwsIfNotLoggedIn() { @Test public void getDefaultConfiguration_isFullySynchronized() { - SyncUser user = SyncTestUtils.createTestUser(); + SyncUser user = createTestUser(); SyncConfiguration config = user.getDefaultConfiguration(); assertFalse(config.isFullySynchronizedRealm()); } @@ -513,7 +512,7 @@ public void automatic_convertsAuthUrl() { String authUrl = (String) test[0]; String realmUrl = (String) test[1]; - SyncUser user = SyncTestUtils.createTestUser(authUrl); + SyncUser user = createTestUser(authUrl); SyncConfiguration config = user.getDefaultConfiguration(); URI url = config.getServerUrl(); assertEquals(realmUrl, url.toString()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 2ec72220e6..82be4408c3 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -16,6 +16,7 @@ package io.realm; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.After; @@ -26,14 +27,19 @@ import org.junit.runner.RunWith; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.TestRealmConfigurationFactory; -import static io.realm.util.SyncTestUtils.createTestUser; +import static io.realm.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -78,6 +84,7 @@ public boolean isActive(String identity, String authenticationUrl) { return true; } }; + SyncManager.reset(); } @After @@ -87,6 +94,7 @@ public void tearDown() { for (SyncUser syncUser : userStore.allUsers()) { userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); } + SyncManager.reset(); } @Test @@ -160,6 +168,8 @@ public void loggedOut(SyncUser user) { @Test public void session() throws IOException { + BaseRealm.applicationContext = null; + Realm.init(InstrumentationRegistry.getTargetContext()); SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = user.createConfiguration(url) @@ -172,4 +182,167 @@ public void session() throws IOException { realm.close(); } + + private void tryCase(Runnable runnable) { + try { + runnable.run(); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void setAuthorizationHeaderName_illegalArgumentsThrows() { + //noinspection ConstantConditions + tryCase(() -> SyncManager.setAuthorizationHeaderName(null)); + tryCase(() -> SyncManager.setAuthorizationHeaderName("")); + //noinspection ConstantConditions + tryCase(() -> SyncManager.setAuthorizationHeaderName(null, "myhost")); + tryCase(() -> SyncManager.setAuthorizationHeaderName("", "myhost")); + //noinspection ConstantConditions + tryCase(() -> SyncManager.setAuthorizationHeaderName("myheader", null)); + tryCase(() -> SyncManager.setAuthorizationHeaderName("myheader", "")); + } + + @Test + public void setAuthorizationHeaderName() throws URISyntaxException { + SyncManager.setAuthorizationHeaderName("foo"); + assertEquals("foo", SyncManager.getAuthorizationHeaderName(new URI("http://localhost"))); + } + + @Test + public void setAuthorizationHeaderName_hostOverrideGlobal() throws URISyntaxException { + SyncManager.setAuthorizationHeaderName("foo"); + SyncManager.setAuthorizationHeaderName("bar", "localhost"); + assertEquals("bar", SyncManager.getAuthorizationHeaderName(new URI("http://localhost"))); + } + + @Test + public void getAuthorizationHeaderName_ignoreHostCasing() throws URISyntaxException { + SyncManager.setAuthorizationHeaderName("foo", "lOcAlHoSt"); + assertEquals("foo", SyncManager.getAuthorizationHeaderName(new URI("http://localhost"))); + assertEquals("foo", SyncManager.getAuthorizationHeaderName(new URI("http://LOCALHOST"))); + } + + @Test + public void addCustomRequestHeader_illegalArgumentThrows() { + //noinspection ConstantConditions + tryCase(() -> SyncManager.addCustomRequestHeader(null, "val")); + tryCase(() -> SyncManager.addCustomRequestHeader("", "val")); + //noinspection ConstantConditions + tryCase(() -> SyncManager.addCustomRequestHeader("header", null)); + + //noinspection ConstantConditions + tryCase(() -> SyncManager.addCustomRequestHeader(null, "val", "localhost")); + tryCase(() -> SyncManager.addCustomRequestHeader("", "val", "localhost")); + //noinspection ConstantConditions + tryCase(() -> SyncManager.addCustomRequestHeader("header", "value", null)); + tryCase(() -> SyncManager.addCustomRequestHeader("header", "value", "")); + } + + @Test + public void addCustomRequestHeaders_illegalArgumentThrows() { + tryCase(() -> SyncManager.addCustomRequestHeaders(null)); + tryCase(() -> SyncManager.addCustomRequestHeaders(Collections.emptyMap(), null)); + tryCase(() -> SyncManager.addCustomRequestHeaders(Collections.emptyMap(), "")); + } + + @Test + public void addCustomRequestHeader() throws URISyntaxException { + SyncManager.addCustomRequestHeader("header1", "val1"); + SyncManager.addCustomRequestHeader("header2", "val2"); + Map headers = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); + assertEquals(2, headers.size()); + Map.Entry header = headers.entrySet().iterator().next(); + assertEquals("header1", header.getKey()); + assertEquals("val1", header.getValue()); + } + + @Test + public void addCustomRequestHeader_hostOverrideGlobal() throws URISyntaxException { + SyncManager.addCustomRequestHeader("header1", "val1"); + SyncManager.addCustomRequestHeader("header1", "val2", "localhost"); + Map headers = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); + assertEquals(1, headers.size()); + Map.Entry header = headers.entrySet().iterator().next(); + assertEquals("header1", header.getKey()); + assertEquals("val2", header.getValue()); + } + + @Test + public void addCustomRequestHeader_ignoreCasingForHost() throws URISyntaxException { + SyncManager.addCustomRequestHeader("header1", "val1", "lOcAlHoSt"); + SyncManager.addCustomRequestHeader("header2", "val2", "LOCALHOST"); + Map headers = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); + assertEquals(2, headers.size()); + } + + @Test + public void addCustomHeaders() throws URISyntaxException { + Map inputHeaders = new LinkedHashMap<>(); + inputHeaders.put("header1", "value1"); + inputHeaders.put("header2", "value2"); + SyncManager.addCustomRequestHeaders(inputHeaders); + Map outputHeaders = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); + assertEquals(2, outputHeaders.size()); + Iterator> it = outputHeaders.entrySet().iterator(); + Map.Entry header1 = it.next(); + assertEquals("header1", header1.getKey()); + assertEquals("value1", header1.getValue()); + Map.Entry header2 = it.next(); + assertEquals("header2", header2.getKey()); + assertEquals("value2", header2.getValue()); + } + + @Test + public void addCustomHeaders_hostOverrideGlobal() throws URISyntaxException { + Map inputHeaders = new LinkedHashMap<>(); + inputHeaders.put("header1", "val1"); + SyncManager.addCustomRequestHeaders(inputHeaders); + inputHeaders.put("header1", "val2"); + SyncManager.addCustomRequestHeaders(inputHeaders, "localhost"); + Map outputHeaders = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); + assertEquals(1, outputHeaders.size()); + Map.Entry header = outputHeaders.entrySet().iterator().next(); + assertEquals("header1", header.getKey()); + assertEquals("val2", header.getValue()); + } + + @Test + public void addCustomHeader_combinesSingleAndMultiple() throws URISyntaxException { + Map inputHeaders1 = new LinkedHashMap<>(); + inputHeaders1.put("header1", "val1"); + Map inputHeaders2 = new LinkedHashMap<>(); + inputHeaders2.put("header2", "val2"); + + SyncManager.addCustomRequestHeader("header3", "val3"); + SyncManager.addCustomRequestHeaders(inputHeaders1); + SyncManager.addCustomRequestHeader("header4", "val4", "realm.io"); + SyncManager.addCustomRequestHeaders(inputHeaders2, "realm.io"); + + Map localhostHeaders = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); + assertEquals(2, localhostHeaders.size()); + Iterator> it = localhostHeaders.entrySet().iterator(); + Map.Entry item = it.next(); + assertEquals("header3", item.getKey()); + assertEquals("val3", item.getValue()); + item = it.next(); + assertEquals("header1", item.getKey()); + assertEquals("val1", item.getValue()); + + Map realmioHeaders = SyncManager.getCustomRequestHeaders(new URI("http://realm.io")); + it = realmioHeaders.entrySet().iterator(); + assertEquals(4, realmioHeaders.size()); + item = it.next(); + assertEquals("header3", item.getKey()); + assertEquals("val3", item.getValue()); + item = it.next(); + assertEquals("header1", item.getKey()); + assertEquals("val1", item.getValue()); + item = it.next(); + assertEquals("header4", item.getKey()); + assertEquals("val4", item.getValue()); + item = it.next(); + assertEquals("header2", item.getKey()); + assertEquals("val2", item.getValue()); + } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 136470a0bc..8388761106 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -53,11 +53,10 @@ import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import io.realm.util.SyncTestUtils; -import static io.realm.util.SyncTestUtils.createNamedTestUser; -import static io.realm.util.SyncTestUtils.createTestAdminUser; -import static io.realm.util.SyncTestUtils.createTestUser; +import static io.realm.SyncTestUtils.createNamedTestUser; +import static io.realm.SyncTestUtils.createTestAdminUser; +import static io.realm.SyncTestUtils.createTestUser; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -94,15 +93,10 @@ public class SyncUserTests { @Rule public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); - @BeforeClass - public static void initUserStore() { - Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); - UserStore userStore = new RealmFileUserStore(); - SyncManager.setUserStore(userStore); - } - @Before public void setUp() { + BaseRealm.applicationContext = null; + Realm.init(InstrumentationRegistry.getTargetContext()); UserStore userStore = SyncManager.getUserStore(); for (SyncUser syncUser : userStore.allUsers()) { userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); @@ -174,7 +168,7 @@ public void toAndFromJson() { public void currentUser_returnsNullIfUserExpired() { // Add an expired user to the user store UserStore userStore = SyncManager.getUserStore(); - userStore.put(SyncTestUtils.createTestUser(Long.MIN_VALUE)); + userStore.put(createTestUser(Long.MIN_VALUE)); // Invalid users should not be returned when asking the for the current user assertNull(SyncUser.current()); @@ -219,7 +213,7 @@ private AuthenticateResponse getNewRandomUser() { @Test public void currentUser_clearedOnLogout() { // Add 1 valid user to the user store - SyncUser user = SyncTestUtils.createTestUser(Long.MAX_VALUE); + SyncUser user = createTestUser(Long.MAX_VALUE); UserStore userStore = SyncManager.getUserStore(); userStore.put(user); @@ -242,8 +236,8 @@ public void all_empty() { public void all_validUsers() { // Add 1 expired user and 1 valid user to the user store UserStore userStore = SyncManager.getUserStore(); - userStore.put(SyncTestUtils.createTestUser(Long.MIN_VALUE)); - userStore.put(SyncTestUtils.createTestUser(Long.MAX_VALUE)); + userStore.put(createTestUser(Long.MIN_VALUE)); + userStore.put(createTestUser(Long.MAX_VALUE)); Map users = SyncUser.all(); assertEquals(1, users.size()); @@ -262,7 +256,7 @@ public void isAdmin() { @Test public void isAdmin_allUsers() { UserStore userStore = SyncManager.getUserStore(); - SyncUser user = SyncTestUtils.createTestAdminUser(); + SyncUser user = createTestAdminUser(); assertTrue(user.isAdmin()); userStore.put(user); @@ -284,7 +278,7 @@ public void currentUser_returnsUserAfterLogin() { @Test public void toString_returnDescription() { - SyncUser user = SyncTestUtils.createTestUser("http://objectserver.realm.io/auth"); + SyncUser user = createTestUser("http://objectserver.realm.io/auth"); String str = user.toString(); assertTrue(str != null && !str.isEmpty()); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index af07ef5986..138d4a239d 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -35,13 +35,12 @@ import io.realm.entities.IndexedFields; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; +import io.realm.exceptions.IncompatibleSyncedFileException; import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSchemaInfo; import io.realm.internal.OsSharedRealm; -import io.realm.exceptions.IncompatibleSyncedFileException; import io.realm.objectserver.utils.StringOnlyModule; -import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index 0494a395ae..d1f1f9e5a7 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -28,12 +28,10 @@ import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; -import io.realm.internal.util.Pair; import io.realm.objectserver.model.PartialSyncObjectA; import io.realm.objectserver.utils.Constants; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index cdd27c396a..78c3e4d2dc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -250,7 +250,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNo #if REALM_ENABLE_SYNC JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( JNIEnv* env, jclass, jlong native_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, - jstring j_refresh_token, jboolean j_is_partial, jbyte j_session_stop_policy) + jstring j_refresh_token, jboolean j_is_partial, jbyte j_session_stop_policy, jstring j_url_prefix, + jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array) { TR_ENTER_PTR(native_ptr) auto& config = *reinterpret_cast(native_ptr); @@ -323,8 +324,6 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe user = SyncManager::shared().get_user(sync_user_identifier, refresh_token); } - - SyncSessionStopPolicy session_stop_policy = static_cast(j_session_stop_policy); JStringAccessor realm_url(env, j_sync_realm_url); @@ -333,13 +332,32 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe config.sync_config->bind_session_handler = std::move(bind_handler); config.sync_config->error_handler = std::move(error_handler); config.sync_config->is_partial = (j_is_partial == JNI_TRUE); + + if (j_url_prefix) { + JStringAccessor url_prefix(env, j_url_prefix); + config.sync_config->url_prefix = realm::util::Optional(url_prefix); + } + + if (j_custom_auth_header_name) { + JStringAccessor custom_auth_header_name(env, j_custom_auth_header_name); + config.sync_config->authorization_header_name = realm::util::Optional(custom_auth_header_name); + } + + if (j_custom_headers_array) { + jsize count = env->GetArrayLength(j_custom_headers_array); + for (int i = 0; i < count; i = i + 2) { + JStringAccessor key(env, (jstring) env->GetObjectArrayElement(j_custom_headers_array, i)); + JStringAccessor value(env, (jstring) env->GetObjectArrayElement(j_custom_headers_array, i + 1)); + config.sync_config->custom_http_headers[std::string(key)] = std::string(value); + } + } + if (!config.encryption_key.empty()) { config.sync_config->realm_encryption_key = std::array(); std::copy_n(config.encryption_key.begin(), 64, config.sync_config->realm_encryption_key->begin()); } return to_jstring(env, config.sync_config->realm_url().c_str()); - } CATCH_STD() return nullptr; diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index df194d2f83..c4982b0d82 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -66,8 +66,8 @@ public void init(Context context) { public void realmClosed(RealmConfiguration configuration) { } - public Object[] getUserAndServerUrl(RealmConfiguration config) { - return new Object[8]; + public Object[] getSyncConfigurationOptions(RealmConfiguration config) { + return new Object[11]; } public static ObjectServerFacade getFacade(boolean needSyncFacade) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 8b731071c6..3240ddd7e8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -18,6 +18,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.util.Map; import javax.annotation.Nullable; @@ -189,7 +190,7 @@ private OsRealmConfig(final RealmConfiguration config, NativeContext.dummyContext.addReference(this); // Retrieve Sync settings first. We need syncRealmUrl to identify if this is a SyncConfig - Object[] syncConfigurationOptions = ObjectServerFacade.getSyncFacadeIfPossible().getUserAndServerUrl(realmConfiguration); + Object[] syncConfigurationOptions = ObjectServerFacade.getSyncFacadeIfPossible().getSyncConfigurationOptions(realmConfiguration); String syncUserIdentifier = (String) syncConfigurationOptions[0]; String syncRealmUrl = (String) syncConfigurationOptions[1]; String syncRealmAuthUrl = (String) syncConfigurationOptions[2]; @@ -198,6 +199,22 @@ private OsRealmConfig(final RealmConfiguration config, String syncSslTrustCertificatePath = (String) syncConfigurationOptions[5]; Byte sessionStopPolicy = (Byte) syncConfigurationOptions[6]; boolean isPartial = (Boolean.TRUE.equals(syncConfigurationOptions[7])); + String urlPrefix = (String)(syncConfigurationOptions[8]); + String customAuthorizationHeaderName = (String)(syncConfigurationOptions[9]); + + // Convert the headers into a String array to make it easier to send through JNI + // [key1, value1, key2, value2, ...] + //noinspection unchecked + Map customHeadersMap = (Map) (syncConfigurationOptions[10]); + String[] customHeaders = new String[customHeadersMap != null ? customHeadersMap.size() * 2 : 0]; + if (customHeadersMap != null) { + int i = 0; + for (Map.Entry entry : customHeadersMap.entrySet()) { + customHeaders[i] = entry.getKey(); + customHeaders[i + 1] = entry.getValue(); + i = i + 2; + } + } // Set encryption key byte[] key = config.getEncryptionKey(); @@ -242,8 +259,17 @@ private OsRealmConfig(final RealmConfiguration config, URI resolvedRealmURI = null; // Set sync config if (syncRealmUrl != null) { - String resolvedSyncRealmUrl = nativeCreateAndSetSyncConfig(nativePtr, syncRealmUrl, syncRealmAuthUrl, syncUserIdentifier, - syncRefreshToken, isPartial, sessionStopPolicy); + String resolvedSyncRealmUrl = nativeCreateAndSetSyncConfig( + nativePtr, + syncRealmUrl, + syncRealmAuthUrl, + syncUserIdentifier, + syncRefreshToken, + isPartial, + sessionStopPolicy, + urlPrefix, + customAuthorizationHeaderName, + customHeaders); try { resolvedRealmURI = new URI(resolvedSyncRealmUrl); } catch (URISyntaxException e) { @@ -292,8 +318,11 @@ private native void nativeSetSchemaConfig(long nativePtr, byte schemaMode, long private static native void nativeEnableChangeNotification(long nativePtr, boolean enableNotification); - private static native String nativeCreateAndSetSyncConfig(long nativePtr, String syncRealmUrl, - String authUrl, String userId, String refreshToken, boolean isPartial, byte sessionStopPolicy); + private static native String nativeCreateAndSetSyncConfig(long nativePtr, String syncRealmUrl, String authUrl, + String userId, String refreshToken, boolean isPartial, + byte sessionStopPolicy, String urlPrefix, + String customAuthorizationHeaderName, + String[] customHeaders); private static native void nativeSetSyncConfigSslSettings(long nativePtr, boolean validateSsl, String trustCertificatePath); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 876c10cac7..a2ccd4363f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -27,7 +27,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Locale; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -106,46 +108,38 @@ public class SyncConfiguration extends RealmConfiguration { private final SyncSession.ErrorHandler errorHandler; private final boolean deleteRealmOnLogout; private final boolean syncClientValidateSsl; - @Nullable - private final String serverCertificateAssetName; - @Nullable - private final String serverCertificateFilePath; + @Nullable private final String serverCertificateAssetName; + @Nullable private final String serverCertificateFilePath; private final boolean waitForInitialData; private final OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy; private final boolean isPartial; + @Nullable private final String syncUrlPrefix; private SyncConfiguration(File directory, - String filename, - String canonicalPath, - @Nullable - String assetFilePath, - @Nullable - byte[] key, - long schemaVersion, - @Nullable - RealmMigration migration, - boolean deleteRealmIfMigrationNeeded, - OsRealmConfig.Durability durability, - RealmProxyMediator schemaMediator, - @Nullable - RxObservableFactory rxFactory, - @Nullable - Realm.Transaction initialDataTransaction, - boolean readOnly, - SyncUser user, - URI serverUrl, - SyncSession.ErrorHandler errorHandler, - boolean deleteRealmOnLogout, - boolean syncClientValidateSsl, - @Nullable - String serverCertificateAssetName, - @Nullable - String serverCertificateFilePath, - boolean waitForInitialData, - OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy, - boolean isPartial, - CompactOnLaunchCallback compactOnLaunch - ) { + String filename, + String canonicalPath, + @Nullable String assetFilePath, + @Nullable byte[] key, + long schemaVersion, + @Nullable RealmMigration migration, + boolean deleteRealmIfMigrationNeeded, + OsRealmConfig.Durability durability, + RealmProxyMediator schemaMediator, + @Nullable RxObservableFactory rxFactory, + @Nullable Realm.Transaction initialDataTransaction, + boolean readOnly, + SyncUser user, + URI serverUrl, + SyncSession.ErrorHandler errorHandler, + boolean deleteRealmOnLogout, + boolean syncClientValidateSsl, + @Nullable String serverCertificateAssetName, + @Nullable String serverCertificateFilePath, + boolean waitForInitialData, + OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy, + boolean isPartial, + CompactOnLaunchCallback compactOnLaunch, + @Nullable String syncUrlPrefix) { super(directory, filename, canonicalPath, @@ -173,6 +167,7 @@ private SyncConfiguration(File directory, this.waitForInitialData = waitForInitialData; this.sessionStopPolicy = sessionStopPolicy; this.isPartial = isPartial; + this.syncUrlPrefix = syncUrlPrefix; } /** @@ -452,6 +447,14 @@ public boolean isFullySynchronizedRealm() { return !isPartial; } + /** + * Returns the url prefix used when establishing a sync connection to the Realm Object Server. + */ + @Nullable + public String getUrlPrefix() { + return syncUrlPrefix; + } + /** * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ @@ -489,6 +492,7 @@ public static final class Builder { private OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy = OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED; private boolean isPartial = true; // Partial Synchronization is enabled by default private CompactOnLaunchCallback compactOnLaunch; + private String syncUrlPrefix = null; /** * Creates an instance of the Builder for the SyncConfiguration. This SyncConfiguration @@ -1006,6 +1010,23 @@ public SyncConfiguration.Builder compactOnLaunch(CompactOnLaunchCallback compact return this; } + /** + * The prefix that is prepended to the path in the HTTP request that initiates a sync + * connection to the Realm Object Server. The value specified must match the server’s + * configuration otherwise the device will not be able to create a connection. If no value + * is specified then the default {@code /realm-sync} path is used. + * + * @param urlPrefix The prefix to append to the sync connection url. + * @see Adding a custom proxy + */ + public SyncConfiguration.Builder urlPrefix(String urlPrefix) { + if (Util.isEmptyString(urlPrefix)) { + throw new IllegalArgumentException("Non-empty 'urlPrefix' required"); + } + this.syncUrlPrefix = urlPrefix; + return this; + } + private String MD5(String in) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); @@ -1158,7 +1179,8 @@ public SyncConfiguration build() { waitForServerChanges, sessionStopPolicy, isPartial, - compactOnLaunch + compactOnLaunch, + syncUrlPrefix ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index ce556daad1..52701ee84d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -130,6 +131,12 @@ public void onError(SyncSession session, ObjectServerError error) { private static volatile AuthenticationServer authServer = new OkHttpAuthenticationServer(); private static volatile UserStore userStore; + // Header configuration + private static String globalAuthorizationHeaderName = "Authorization"; // authorization header name if no host-defined header is available + private static Map hostRestrictedAuthorizationHeaderName = new HashMap<>(); // authorization header name for the given host + private static Map globalCustomHeaders = new HashMap<>(); + private static Map> hostRestrictedCustomHeaders = new HashMap<>(); + private static NetworkStateReceiver.ConnectionListener networkListener = new NetworkStateReceiver.ConnectionListener() { @Override public void onChange(boolean connectionAvailable) { @@ -276,6 +283,148 @@ public static synchronized SyncSession getOrCreateSession(SyncConfiguration sync return session; } + /** + * Sets the name of the HTTP header used to send authorization data in when making requests to + * all Realm Object Servers used by the app. These servers must have been configured to expect a + * custom authorization header. + *

              + * The default authorization header is named "Authorization". + * + * @param headerName name of the header. + * @throws IllegalArgumentException if a null or empty header is provided. + * @see Adding a custom proxy + */ + public static synchronized void setAuthorizationHeaderName(String headerName) { + checkNotEmpty(headerName, "headerName"); + authServer.setAuthorizationHeaderName(headerName, null); + globalAuthorizationHeaderName = headerName; + } + + /** + * Sets the name of the HTTP header used to send authorization data in when making requests to + * the Realm Object Server running on the defined {@code host}. This server must have been + * configured to expect a custom authorization header. + *

              + * The default authorization header is named "Authorization". + * + * @param headerName name of the header. + * @param host if this is provided, the authorization header name will only be used on this particular host. + * Example of valid values: "localhost", "127.0.0.1" and "myinstance.us1.cloud.realm.io". + * @throws IllegalArgumentException if a {@code null} or empty header and/or host is provided. + * @see Adding a custom proxy + */ + + public static synchronized void setAuthorizationHeaderName(String headerName, String host) { + checkNotEmpty(headerName, "headerName"); + checkNotEmpty(host, "host"); + host = host.toLowerCase(Locale.US); + authServer.setAuthorizationHeaderName(headerName, host); + hostRestrictedAuthorizationHeaderName.put(host, headerName); + } + + /** + * Adds an extra HTTP header to append to every request to a Realm Object Server. + * + * @param headerName the name of the header. + * @param headerValue the value of header. + * @throws IllegalArgumentException if a non-empty {@code headerName} is provided or a null {@code headerValue}. + */ + public static synchronized void addCustomRequestHeader(String headerName, String headerValue) { + checkNotEmpty(headerName, "headerName"); + checkNotNull(headerValue, "headerValue"); + authServer.addHeader(headerName, headerValue, null); + globalCustomHeaders.put(headerName, headerValue); + } + + /** + * Adds an extra HTTP header to append to every request to a Realm Object Server. + * + * @param headerName the name of the header. + * @param headerValue the value of header. + * @param host if this is provided, the this header will only be used on this particular host. + * Example of valid values: "localhost", "127.0.0.1" and "myinstance.us1.cloud.realm.io". + * @throws IllegalArgumentException If an non-empty {@code headerName}, {@code headerValue} or {@code host} is provided. + */ + public static synchronized void addCustomRequestHeader(String headerName, String headerValue, String host) { + checkNotEmpty(headerName, "headerName"); + checkNotNull(headerValue, "headerValue"); + checkNotEmpty(host, "host"); + + // Headers + host = host.toLowerCase(Locale.US); + authServer.addHeader(headerName, headerValue, host); + Map headers = hostRestrictedCustomHeaders.get(host); + if (headers == null) { + headers = new LinkedHashMap<>(); + hostRestrictedCustomHeaders.put(host, headers); + } + headers.put(headerName, headerValue); + } + + /** + * Adds extra HTTP headers to append to every request to a Realm Object Server. + * + * @param headers map of (headerName, headerValue) pairs. + * @throws IllegalArgumentException If any of the headers provided are illegal. + */ + public static synchronized void addCustomRequestHeaders(@Nullable Map headers) { + if (headers != null) { + for (Map.Entry entry : headers.entrySet()) { + addCustomRequestHeader(entry.getKey(), entry.getValue()); + } + } + } + + /** + * Adds extra HTTP headers to append to every request to a Realm Object Server. + * + * @param headers map of (headerName, headerValue) pairs. + * @param host if this is provided, the this header will only be used on this particular host. + * Example of valid values: "localhost", "127.0.0.1" and "myinstance.us1.cloud.realm.io". + * @throws IllegalArgumentException If any of the headers provided are illegal. + */ + public static synchronized void addCustomRequestHeaders(@Nullable Map headers, String host) { + if (Util.isEmptyString(host)) { + throw new IllegalArgumentException("Non-empty 'host' required"); + } + host = host.toLowerCase(Locale.US); + if (headers != null) { + for (Map.Entry entry : headers.entrySet()) { + addCustomRequestHeader(entry.getKey(), entry.getValue(), host); + } + } + } + + /** + * Returns the authentication header name used for the http request to the given url. + * + * @param objectServerUrl Url to get header for. + * @return the authorization header name used by http requests to this url. + */ + public static synchronized String getAuthorizationHeaderName(URI objectServerUrl) { + String host = objectServerUrl.getHost().toLowerCase(Locale.US); + String hostRestrictedHeader = hostRestrictedAuthorizationHeaderName.get(host); + return (hostRestrictedHeader != null) ? hostRestrictedHeader : globalAuthorizationHeaderName; + } + + /** + * Returns all the custom headers added to requests to the given url. + * + * @return all defined custom headers used when making http requests to the given url. + * f + */ + public static synchronized Map getCustomRequestHeaders(URI serverSyncUrl) { + Map headers = new LinkedHashMap<>(globalCustomHeaders); + String host = serverSyncUrl.getHost().toLowerCase(Locale.US); + Map hostHeaders = hostRestrictedCustomHeaders.get(host); + if (hostHeaders != null) { + for (Map.Entry entry : hostHeaders.entrySet()) { + headers.put(entry.getKey(), entry.getValue()); + } + } + return headers; + } + /** * Remove the wrapped Java session. * @param syncConfiguration configuration object for the synchronized Realm. @@ -303,6 +452,7 @@ private static synchronized void removeSession(SyncConfiguration syncConfigurati * @return the all valid sessions belonging to the user. */ static List getAllSessions(SyncUser syncUser) { + //noinspection ConstantConditions if (syncUser == null) { throw new IllegalArgumentException("A non-empty 'syncUser' is required."); } @@ -553,6 +703,18 @@ private static X509Certificate buildCertificateFromPEM(String pem) throws IOExce } } + private static void checkNotEmpty(String headerName, String varName) { + if (Util.isEmptyString(headerName)) { + throw new IllegalArgumentException("Non-empty '" + varName +"' required."); + } + } + + private static void checkNotNull(@Nullable String val, String varName) { + if (val == null) { + throw new IllegalArgumentException("Non-null'" + varName +"' required."); + } + } + /** * Resets the SyncManger and clear all existing users. * This will also terminate all sessions. @@ -562,6 +724,10 @@ private static X509Certificate buildCertificateFromPEM(String pem) throws IOExce static synchronized void reset() { nativeReset(); sessions.clear(); + hostRestrictedAuthorizationHeaderName.clear(); + globalAuthorizationHeaderName = "Authorization"; + hostRestrictedCustomHeaders.clear(); + globalCustomHeaders.clear(); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index fc34b1df5d..c1c05f31c9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Map; import io.realm.RealmConfiguration; import io.realm.SyncConfiguration; @@ -85,7 +86,7 @@ public void realmClosed(RealmConfiguration configuration) { } @Override - public Object[] getUserAndServerUrl(RealmConfiguration config) { + public Object[] getSyncConfigurationOptions(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; SyncUser user = syncConfig.getUser(); @@ -94,9 +95,24 @@ public Object[] getUserAndServerUrl(RealmConfiguration config) { String syncRealmAuthUrl = user.getAuthenticationUrl().toString(); String rosSerializedUser = user.toJson(); byte sessionStopPolicy = syncConfig.getSessionStopPolicy().getNativeValue(); - return new Object[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosSerializedUser, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath(), sessionStopPolicy, !syncConfig.isFullySynchronizedRealm()}; + String urlPrefix = syncConfig.getUrlPrefix(); + String customAuthorizationHeaderName = SyncManager.getAuthorizationHeaderName(syncConfig.getServerUrl()); + Map customHeaders = SyncManager.getCustomRequestHeaders(syncConfig.getServerUrl()); + return new Object[]{ + rosUserIdentity, + rosServerUrl, + syncRealmAuthUrl, + rosSerializedUser, + syncConfig.syncClientValidateSsl(), + syncConfig.getServerCertificateFilePath(), + sessionStopPolicy, + !syncConfig.isFullySynchronizedRealm(), + urlPrefix, + customAuthorizationHeaderName, + customHeaders + }; } else { - return new Object[8]; + return new Object[11]; } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java index 1e33ab0d0f..103f7fa8f3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java @@ -19,6 +19,8 @@ import java.net.URI; import java.net.URL; +import javax.annotation.Nullable; + import io.realm.SyncCredentials; import io.realm.SyncUser; import io.realm.internal.objectserver.Token; @@ -30,6 +32,19 @@ * only responsible for executing a given network request. */ public interface AuthenticationServer { + + /** + * Overrides the default header name used to send Realm Object Server credentials. + * The Realm Object Server must be setup to handle this specifically. + */ + void setAuthorizationHeaderName(String headerName, @Nullable String host); + + /** + * Add a custom header that should be applied to all HTTP requests made by the authentication + * server. + */ + void addHeader(String headerName, String headerValue, @Nullable String host); + /** * Login a User on the Object Server. This will create a "UserToken" (Currently called RefreshToken) that acts as * the users credentials. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 0b3fc3ab44..59415d4788 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -16,22 +16,34 @@ package io.realm.internal.network; +import android.util.Log; + +import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + import io.realm.SyncCredentials; import io.realm.internal.Util; import io.realm.internal.objectserver.Token; +import io.realm.log.LogLevel; import io.realm.log.RealmLog; import okhttp3.Call; import okhttp3.ConnectionPool; +import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okio.Buffer; public class OkHttpAuthenticationServer implements AuthenticationServer { @@ -40,17 +52,71 @@ public class OkHttpAuthenticationServer implements AuthenticationServer { private static final String ACTION_CHANGE_PASSWORD = "password"; // Auth end point for changing passwords private static final String ACTION_LOOKUP_USER_ID = "users/:provider:/:providerId:"; // Auth end point for looking up user id private static final String ACTION_UPDATE_ACCOUNT = "password/updateAccount"; // Password reset and email confirmation + private static final Charset UTF8 = Charset.forName("UTF-8"); private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + if (RealmLog.getLevel() <= LogLevel.TRACE) { + StringBuilder sb = new StringBuilder(request.method()); + sb.append(' '); + sb.append(request.url()); + sb.append('\n'); + sb.append(request.headers()); + if (request.body() != null) { + // Stripped down version of https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java + // We only expect request context to be JSON. + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + sb.append(buffer.readString(UTF8)); + } + RealmLog.trace("HTTP Request = \n%s", sb); + } + return chain.proceed(request); + } + }) // using custom Connection Pool to evict idle connection after 5 seconds rather than 5 minutes (which is the default) // keeping idle connection on the pool will prevent the ROS to be stopped, since the HttpUtils#stopSyncServer query // will not return before the tests timeout (ex 10 seconds for AuthTests) .connectionPool(new ConnectionPool(5, 5, TimeUnit.SECONDS)) .build(); + private Map> customHeaders = new LinkedHashMap<>(); + private Map customAuthorizationHeaders = new HashMap<>(); + + public OkHttpAuthenticationServer() { + customAuthorizationHeaders.put("", "Authorization"); // Default value for authorization header + customHeaders.put("", new LinkedHashMap<>()); // Add holder for headers used across all hosts + } + + @Override + public void setAuthorizationHeaderName(String headerName, @Nullable String host) { + if (Util.isEmptyString(host)) { + customAuthorizationHeaders.put("", headerName); + } else { + customAuthorizationHeaders.put(host, headerName); + } + } + + @Override + public void addHeader(String headerName, String headerValue, @Nullable String host) { + if (Util.isEmptyString(host)) { + customHeaders.get("").put(headerName, headerValue); + } else { + Map headers = customHeaders.get(host); + if (headers == null) { + headers = new LinkedHashMap<>(); + customHeaders.put(host, headers); + } + headers.put(headerName, headerValue); + } + } + /** * Authenticate the given credentials on the specified Realm Authentication Server. */ @@ -237,9 +303,28 @@ private Request.Builder newAuthRequest(URL url, String authToken) { .addHeader("Content-Type", "application/json") .addHeader("Accept", "application/json"); + // Add custom headers used by all hosts + for (Map.Entry entry : customHeaders.get("").entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + + // add custom headers used by specific host (may override g + Map customHeaders = this.customHeaders.get(url.getHost()); + if (customHeaders != null) { + for (Map.Entry entry : customHeaders.entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + } + // Only add Authorization header for those API's that require it. + // Use the defined custom authorization name if one is available for this host. if (!Util.isEmptyString(authToken)) { - builder.addHeader("Authorization", authToken); + String headerName = customAuthorizationHeaders.get(url.getHost()); + if (headerName != null) { + builder.addHeader(headerName, authToken); + } else { + builder.addHeader(customAuthorizationHeaders.get(""), authToken); + } } return builder; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 90c95223cd..8ad28f99e4 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -60,25 +60,6 @@ public abstract class BaseIntegrationTest { // Attempt to combat issues with the sync meta data Realm not being correctly cleaned } - protected void prepareEnvironmentForTest() throws IOException { - deleteRosFiles(); - if (BaseRealm.applicationContext != null) { - // Realm was already initialized. Reset all internal state - // in order to be able fully re-initialize. - - // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' - // causing the SyncUser to remain in memory. - // They're actually not persisted into disk. - // move this call to 'tearDown' to clean in-memory & on-disk users - // once https://github.com/realm/realm-object-store/issues/207 is resolved - SyncManager.reset(); - BaseRealm.applicationContext = null; // Required for Realm.init() to work - } - Realm.init(InstrumentationRegistry.getContext()); - originalLogLevel = RealmLog.getLevel(); - RealmLog.setLevel(LogLevel.DEBUG); - } - /** * Starts a new ROS instance that can be used for testing. */ @@ -102,37 +83,7 @@ protected static void stopSyncServer() { Log.e(HttpUtils.TAG, "Failed to stop Sync Server: " + Util.getStackTrace(e)); } } - - /** - * Tries to restore the environment as best as possible after a test. - */ - protected void restoreEnvironmentAfterTest() { - // Block until all users are logged out - UserFactory.logoutAllUsers(); - - // Reset log level - RealmLog.setLevel(originalLogLevel); - } - - // Cleanup filesystem to make sure nothing lives for the next test. - // Failing to do so might lead to DIVERGENT_HISTORY errors being thrown if Realms from - // previous tests are being accessed. - private static void deleteRosFiles() throws IOException { - File rosFiles = new File(InstrumentationRegistry.getContext().getFilesDir(),"realm-object-server"); - deleteFile(rosFiles); - } - - private static void deleteFile(File file) throws IOException { - if (file.isDirectory()) { - for (File c : file.listFiles()) { - deleteFile(c); - } - } - if (!file.delete()) { - throw new IllegalStateException("Failed to delete file or directory: " + file.getAbsolutePath()); - } - } - + // Returns a valid SyncConfiguration usable by tests // FIXME: WARNING: Do not use `SyncTestRealmConfigurationFactory`, but use this. Refactor later. protected static class ConfigurationWrapper { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java index e25225fbbe..7578de0cf1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java @@ -18,21 +18,21 @@ public class IsolatedIntegrationTests extends BaseIntegrationTest { @Before public void setupTest() throws IOException { startSyncServer(); - prepareEnvironmentForTest(); + SyncTestUtils.prepareEnvironmentForTest(); } @After public void teardownTest() { if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { // Non-looper tests can reset here - restoreEnvironmentAfterTest(); + SyncTestUtils.restoreEnvironmentAfterTest(); stopSyncServer(); } else { // Otherwise we need to wait for the test to complete looperThread.runAfterTest(new Runnable() { @Override public void run() { - restoreEnvironmentAfterTest(); + SyncTestUtils.restoreEnvironmentAfterTest(); stopSyncServer(); } }); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java index aa720b15de..aea2b68327 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java @@ -44,20 +44,20 @@ public static void tearDownTestClass() throws Exception { @Before public void setupTest() throws IOException { - prepareEnvironmentForTest(); + SyncTestUtils.prepareEnvironmentForTest(); } @After public void teardownTest() { if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { // Non-looper tests can reset here - restoreEnvironmentAfterTest(); + SyncTestUtils.restoreEnvironmentAfterTest(); } else { // Otherwise we need to wait for the test to complete looperThread.runAfterTest(new Runnable() { @Override public void run() { - restoreEnvironmentAfterTest(); + SyncTestUtils.restoreEnvironmentAfterTest(); } }); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 84928ea24b..0f47cbd7d1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -26,7 +26,6 @@ import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; -import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index d5a4e520c2..b47a848564 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -33,9 +33,10 @@ import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.OsRealmConfig; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; import io.realm.rule.RunTestInLooperThread; -import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -346,4 +347,96 @@ public void defaultRealm() throws InterruptedException { user.logOut(); } } + + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + @RunTestInLooperThread + public void javaRequestCustomHeaders() { + SyncManager.addCustomRequestHeader("Foo", "bar"); + SyncManager.setAuthorizationHeaderName("RealmAuth"); + runJavaRequestCustomHeadersTest(); + } + + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + @RunTestInLooperThread + public void javaRequestCustomHeaders_specificHost() { + SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST); + SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST); + runJavaRequestCustomHeadersTest(); + } + + private void runJavaRequestCustomHeadersTest() { + SyncCredentials credentials = SyncCredentials.nickname("test", false); + + RealmLog.setLevel(LogLevel.ALL); + RealmLog.add((level, tag, throwable, message) -> { + if (level == LogLevel.TRACE + && message.contains("Foo: bar") + && message.contains("RealmAuth: ")) { + looperThread.testComplete(); + }}); + + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + try { + user.changePassword("foo"); + } catch (ObjectServerError e) { + if (e.getErrorCode() != ErrorCode.INVALID_CREDENTIALS) { + throw e; + } + } + } + + // Test that auth header renaming, custom headers and url prefix are all propagated correctly + // to Sync. There really isn't a way to create a proper integration test since ROS used for testing + // isn't configured to accept such requests. Instead we inspect the log from Sync which will + // output the headers in TRACE mode. + @Test + @RunTestInLooperThread + public void syncAuthHeaderAndUrlPrefix() { + SyncManager.setAuthorizationHeaderName("TestAuth"); + SyncManager.addCustomRequestHeader("Test", "test"); + runSyncAuthHeadersAndUrlPrefixTest(); + } + + // Test that auth header renaming, custom headers and url prefix are all propagated correctly + // to Sync. There really isn't a way to create a proper integration test since ROS used for testing + // isn't configured to accept such requests. Instead we inspect the log from Sync which will + // output the headers in TRACE mode. + @Test + @RunTestInLooperThread + public void syncAuthHeaderAndUrlPrefix_specificHost() { + SyncManager.setAuthorizationHeaderName("TestAuth", Constants.HOST); + SyncManager.addCustomRequestHeader("Test", "test", Constants.HOST); + runSyncAuthHeadersAndUrlPrefixTest(); + } + + private void runSyncAuthHeadersAndUrlPrefixTest() { + SyncCredentials credentials = SyncCredentials.nickname("test", false); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM) + .urlPrefix("/foo") + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + RealmLog.error(error.toString()); + } + }) + .build(); + + RealmLog.setLevel(LogLevel.ALL); + RealmLog.add((level, tag, throwable, message) -> { + if (tag.equals("REALM_SYNC") + && message.contains("GET /foo/%2Fdefault%2F__partial%") + && message.contains("TestAuth: Realm-Access-Token version=1") + && message.contains("Test: test")) { + looperThread.testComplete(); + } + }); + Realm realm = Realm.getInstance(config); + looperThread.closeAfterTest(realm); + } + } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 61f6ea1d15..4784a78eab 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -30,6 +30,7 @@ import io.realm.SyncCredentials; import io.realm.SyncManager; import io.realm.SyncSession; +import io.realm.SyncTestUtils; import io.realm.SyncUser; import io.realm.SyncUserInfo; import io.realm.TestHelper; @@ -41,7 +42,6 @@ import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; -import io.realm.util.SyncTestUtils; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; @@ -59,6 +59,7 @@ @RunWith(AndroidJUnit4.class) +@Ignore("They break CI but run locally when just running this class. We need to investigate what is going") public class AuthTests extends StandardIntegrationTest { @Test diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index c0e1ae5cea..7f9f185107 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -17,6 +17,7 @@ import io.realm.SyncCredentials; import io.realm.SyncManager; import io.realm.SyncSession; +import io.realm.SyncTestUtils; import io.realm.SyncUser; import io.realm.TestHelper; import io.realm.entities.StringOnly; @@ -24,7 +25,6 @@ import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; -import io.realm.util.SyncTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java index 6865121afa..3585330c64 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java @@ -14,6 +14,7 @@ import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; import io.realm.SyncManager; +import io.realm.SyncTestUtils; import io.realm.SyncUser; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; @@ -24,7 +25,6 @@ import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; -import io.realm.util.SyncTestUtils; import static org.hamcrest.number.OrderingComparison.greaterThan; import static org.junit.Assert.assertEquals; diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/util/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java similarity index 71% rename from realm/realm-library/src/syncTestUtils/java/io/realm/util/SyncTestUtils.java rename to realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java index 61a1dbd3a0..1e43f16424 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/util/SyncTestUtils.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java @@ -14,11 +14,15 @@ * limitations under the License. */ -package io.realm.util; +package io.realm; + +import android.support.test.InstrumentationRegistry; import org.json.JSONException; import org.json.JSONObject; +import java.io.File; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.UUID; @@ -33,6 +37,9 @@ import io.realm.UserStore; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.objectserver.Token; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; +import io.realm.objectserver.utils.UserFactory; public class SyncTestUtils { @@ -41,6 +48,7 @@ public class SyncTestUtils { private final static Method SYNC_MANAGER_GET_USER_STORE_METHOD; private final static Method SYNC_USER_GET_ACCESS_TOKEN_METHOD; + private static int originalLogLevel; // Should only be modified by prepareEnvironmentForTest and restoreEnvironmentAfterTest static { try { SYNC_MANAGER_GET_USER_STORE_METHOD = SyncManager.class.getDeclaredMethod("getUserStore"); @@ -52,6 +60,55 @@ public class SyncTestUtils { } } + public static void prepareEnvironmentForTest() throws IOException { + deleteRosFiles(); + if (BaseRealm.applicationContext != null) { + // Realm was already initialized. Reset all internal state + // in order to be able fully re-initialize. + + // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' + // causing the SyncUser to remain in memory. + // They're actually not persisted into disk. + // move this call to 'tearDown' to clean in-memory & on-disk users + // once https://github.com/realm/realm-object-store/issues/207 is resolved + SyncManager.reset(); + BaseRealm.applicationContext = null; // Required for Realm.init() to work + } + Realm.init(InstrumentationRegistry.getContext()); + originalLogLevel = RealmLog.getLevel(); + RealmLog.setLevel(LogLevel.DEBUG); + } + + /** + * Tries to restore the environment as best as possible after a test. + */ + public static void restoreEnvironmentAfterTest() { + // Block until all users are logged out + UserFactory.logoutAllUsers(); + + // Reset log level + RealmLog.setLevel(originalLogLevel); + } + + // Cleanup filesystem to make sure nothing lives for the next test. + // Failing to do so might lead to DIVERGENT_HISTORY errors being thrown if Realms from + // previous tests are being accessed. + private static void deleteRosFiles() throws IOException { + File rosFiles = new File(InstrumentationRegistry.getContext().getFilesDir(),"realm-object-server"); + deleteFile(rosFiles); + } + + private static void deleteFile(File file) throws IOException { + if (file.isDirectory()) { + for (File c : file.listFiles()) { + deleteFile(c); + } + } + if (!file.delete()) { + throw new IllegalStateException("Failed to delete file or directory: " + file.getAbsolutePath()); + } + } + public static SyncUser createTestAdminUser() { return createTestUser(USER_TOKEN, UUID.randomUUID().toString(), DEFAULT_AUTH_URL, Long.MAX_VALUE, true); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java similarity index 100% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactory.java rename to realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactoryStore.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactoryStore.java similarity index 100% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/UserFactoryStore.java rename to realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactoryStore.java From fa326012564c0ed4010275dda4068d3b3301cc4a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 31 Aug 2018 13:06:31 +0200 Subject: [PATCH 1295/2110] Upgrade to Sync 3.9.4 (#6139) --- CHANGELOG.md | 2 +- dependencies.list | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1396fdc119..c62787cd75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ ### Internal -* Updated to Realm Sync 3.9.3 +* Updated to Realm Sync 3.9.4 * Updated to Realm Core 5.8.0 * Updated to Object Store commit: b0fc2814d9e6061ce5ba1da887aab6cfba4755ca diff --git a/dependencies.list b/dependencies.list index 1b4518033e..b9456906f1 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.9.3 -REALM_SYNC_SHA256=fa407408f2dd53d1cb3d3664cb3001b280d6c772d969a0a31112b132972a6973 +REALM_SYNC_VERSION=3.9.4 +REALM_SYNC_SHA256=f5f52093270c8d26a4b6ba3790c05425d89786033aa6d061fd74e9a24ecb22d7 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. From 92d55a12f8bee149a937465e8ee91c6fe31a9c7c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 31 Aug 2018 15:49:56 +0200 Subject: [PATCH 1296/2110] Update release date --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c62787cd75..7ab3427456 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,9 @@ -## 5.5.0 (YYYY-MM-DD) +## 5.5.0 (2018-08-31) ### Enhancements * [ObjectServer] Added `ConnectionState` enum describing the states a connection can be in. -* [ObjectServer] Added `SyncSession.isConnected()`. +* [ObjectServer] Added `SyncSession.isConnected()` and `SyncSession.getConnectionState()`. * [ObjectServer] Added support for observing connection changes for a session using `SyncSession.addConnectionChangeListener()` and `SyncSession.removeConnectionChangeListener()`. * [ObjectServer] Added Kotlin extension property `Realm.syncSession` for synchronized Realms. * [ObjectServer] Added Kotlin extension method `Realm.classPermissions()`. From b2fc74e53a54ed6be362b1c319be91188b9477d2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 31 Aug 2018 15:50:46 +0200 Subject: [PATCH 1297/2110] Release v5.5.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a26d4d9728..c7ba1e87f7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.5.0-SNAPSHOT \ No newline at end of file +5.5.0 \ No newline at end of file From 8b09975bdd3b6758aafc528622c878ceb4a96287 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 31 Aug 2018 15:50:46 +0200 Subject: [PATCH 1298/2110] Prepare next release v5.5.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index c7ba1e87f7..d90d0bd549 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.5.0 \ No newline at end of file +5.5.1-SNAPSHOT \ No newline at end of file From f2fbfc81329314121e7ff163fc3fa19e36a1b516 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 31 Aug 2018 16:19:35 +0200 Subject: [PATCH 1299/2110] Fix various lifecycle issues with tests. Work-around for logs not being saved. (#6141) --- .../java/io/realm/SyncManager.java | 1 + .../network/AuthenticationServer.java | 6 ++++ .../network/OkHttpAuthenticationServer.java | 11 ++++++ .../java/io/realm/BaseIntegrationTest.java | 4 --- .../java/io/realm/objectserver/AuthTests.java | 1 - .../java/io/realm/SyncTestUtils.java | 2 +- .../realm/objectserver/utils/UserFactory.java | 17 +++++++--- .../integration-test-command-server.js | 34 ++++++++++++------- 8 files changed, 52 insertions(+), 24 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 52701ee84d..93ac40bbfb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -728,6 +728,7 @@ static synchronized void reset() { globalAuthorizationHeaderName = "Authorization"; hostRestrictedCustomHeaders.clear(); globalCustomHeaders.clear(); + authServer.clearCustomHeaderSettings(); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java index 103f7fa8f3..7c608791dd 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java @@ -45,6 +45,11 @@ public interface AuthenticationServer { */ void addHeader(String headerName, String headerValue, @Nullable String host); + /** + * Clear any custom header settings (Authorization and others). + */ + void clearCustomHeaderSettings(); + /** * Login a User on the Object Server. This will create a "UserToken" (Currently called RefreshToken) that acts as * the users credentials. @@ -108,4 +113,5 @@ public interface AuthenticationServer { * Complete an email confirmation by sending the token contained in the email. */ UpdateAccountResponse confirmEmail(String confirmationToken, URL authenticationUrl); + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 59415d4788..3f51da45cc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -90,6 +90,10 @@ public Response intercept(Chain chain) throws IOException { private Map customAuthorizationHeaders = new HashMap<>(); public OkHttpAuthenticationServer() { + initHeaders(); + } + + private void initHeaders() { customAuthorizationHeaders.put("", "Authorization"); // Default value for authorization header customHeaders.put("", new LinkedHashMap<>()); // Add holder for headers used across all hosts } @@ -117,6 +121,13 @@ public void addHeader(String headerName, String headerValue, @Nullable String ho } } + @Override + public void clearCustomHeaderSettings() { + customAuthorizationHeaders.clear(); + customHeaders.clear(); + initHeaders(); + } + /** * Authenticate the given credentials on the specified Realm Authentication Server. */ diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 8ad28f99e4..78954747f7 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -56,10 +56,6 @@ public abstract class BaseIntegrationTest { protected ConfigurationWrapper configurationFactory = new ConfigurationWrapper(looperThread); - static { - // Attempt to combat issues with the sync meta data Realm not being correctly cleaned - } - /** * Starts a new ROS instance that can be used for testing. */ diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 4784a78eab..6dda502edc 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -59,7 +59,6 @@ @RunWith(AndroidJUnit4.class) -@Ignore("They break CI but run locally when just running this class. We need to investigate what is going") public class AuthTests extends StandardIntegrationTest { @Test diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java index 1e43f16424..b11a8ebea1 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java @@ -74,7 +74,7 @@ public static void prepareEnvironmentForTest() throws IOException { SyncManager.reset(); BaseRealm.applicationContext = null; // Required for Realm.init() to work } - Realm.init(InstrumentationRegistry.getContext()); + Realm.init(InstrumentationRegistry.getTargetContext()); originalLogLevel = RealmLog.getLevel(); RealmLog.setLevel(LogLevel.DEBUG); } diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java index d8d7d6de35..dc7aa345cb 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java @@ -45,10 +45,15 @@ public class UserFactory { private String userName; private static UserFactory instance; private static RealmConfiguration configuration; - static { - RealmConfiguration.Builder builder = new RealmConfiguration.Builder().name("user-factory.realm"); - ObjectServerFacade.getSyncFacadeIfPossible().addSupportForObjectLevelPermissions(builder); - configuration = builder.build(); + + // Run initializer here to make it possible to ensure that Realm.init has been called. + // It is unpredictable when the static initializer is running + private static synchronized void initFactory(boolean forceReset) { + if (configuration == null || forceReset) { + RealmConfiguration.Builder builder = new RealmConfiguration.Builder().name("user-factory.realm"); + ObjectServerFacade.getSyncFacadeIfPossible().addSupportForObjectLevelPermissions(builder); + configuration = builder.build(); + } } private UserFactory(String userName) { @@ -102,7 +107,7 @@ public static SyncUser createNicknameUser(String authUrl, String nickname, boole // Since we don't have a reliable way to reset the sync server and client, just use a new user factory for every // test case. public static void resetInstance() { - instance = null; + initFactory(true); Realm realm = Realm.getInstance(configuration); UserFactoryStore store = realm.where(UserFactoryStore.class).findFirst(); realm.beginTransaction(); @@ -112,6 +117,7 @@ public static void resetInstance() { store.setUserName(UUID.randomUUID().toString()); realm.commitTransaction(); realm.close(); + instance = null; } // The @Before method will be called before the looper tests finished. We need to find a better place to call this. @@ -125,6 +131,7 @@ public static void clearInstance() { public static synchronized UserFactory getInstance() { if (instance == null) { + initFactory(false); Realm realm = Realm.getInstance(configuration); UserFactoryStore store = realm.where(UserFactoryStore.class).findFirst(); if (store == null || store.getUserName() == null) { diff --git a/tools/sync_test_server/integration-test-command-server.js b/tools/sync_test_server/integration-test-command-server.js index 71d4fa6179..f11c0cb7a3 100755 --- a/tools/sync_test_server/integration-test-command-server.js +++ b/tools/sync_test_server/integration-test-command-server.js @@ -141,21 +141,29 @@ function stopRealmObjectServer(onSuccess, onError) { onSuccess("No ROS process found or the process has been killed before"); } if (syncServerChildProcess) { - syncServerChildProcess.on('exit', function(code) { - // Manually kill sub process started by node that actually runs ROS. - // It is not killed when killing the process running NPM - exec('fuser -k 9443/tcp', (error, stdout, stderr) => { - if (error) { - onError(error) - return; - } - winston.info(`command-server: Stopping process: '${stdout}'`) - syncServerChildProcess.removeAllListeners('exit'); - syncServerChildProcess = null; - onSuccess(); + + // Work-around for https://github.com/realm/realm-java/issues/6137 + // Pull the log file before removing it and output all of it to this process + // so we can capture it. This means the logs won't show up until ROS is stopped + exec('cat /ros/log.txt', (error, stdout, stderr) => { + winston.info(`Realm Object Server Logs:\n${stdout}`); + syncServerChildProcess.on('exit', function(code) { + // Manually kill sub process started by node that actually runs ROS. + // It is not killed when killing the process running NPM + exec('fuser -k 9443/tcp', (error, stdout, stderr) => { + if (error) { + onError(error) + return; + } + winston.info(`command-server: Stopping process: '${stdout}'`) + syncServerChildProcess.removeAllListeners('exit'); + syncServerChildProcess = null; + onSuccess(); + }); }); + syncServerChildProcess.kill('SIGINT'); }); - syncServerChildProcess.kill('SIGINT'); + } } From 7dddb2cca12eb44c35ad0a7768c05aa40e2c8001 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 31 Aug 2018 16:53:44 +0200 Subject: [PATCH 1300/2110] Prepare for next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index d90d0bd549..df4bca0bb7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.5.1-SNAPSHOT \ No newline at end of file +5.6.0-SNAPSHOT \ No newline at end of file From 581c2a9fb2560c0e850549a91bb561346c66a83c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 4 Sep 2018 13:06:32 +0200 Subject: [PATCH 1301/2110] Add support for using the default parameter when defining custom names (#6149) --- CHANGELOG.md | 7 ++++ .../java/io/realm/annotations/RealmClass.java | 10 ++++++ .../java/io/realm/annotations/RealmField.java | 9 +++++ .../io/realm/processor/ClassMetaData.java | 19 ++++++----- .../some/test/NamePolicyClassOnly.java | 2 +- .../some/test/NamePolicyFieldNameOnly.java | 2 +- .../java/io/realm/CustomRealmNameTests.java | 14 ++++++++ .../realmname/ClassWithValueDefinedNames.java | 33 +++++++++++++++++++ .../realmname/CustomRealmNamesModule.java | 13 +++++--- 9 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassWithValueDefinedNames.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ab3427456..40d9faee78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.6.0 (YYYY-MM-DD) + +### Enhancements + +* `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom name mappings (#6145). + + ## 5.5.0 (2018-08-31) ### Enhancements diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java index b4a3abb695..6f284714e8 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java @@ -31,6 +31,16 @@ @Inherited public @interface RealmClass { + /** + * Manually set the internal name used by Realm for this class. If this class is part of + * any modules, this will also override any name policy set using + * {@link RealmModule#classNamingPolicy()}. + * + * @see io.realm.annotations.RealmNamingPolicy for more information about what setting the name means. + * @see #name() + */ + String value() default ""; + /** * Manually set the internal name used by Realm for this class. If this class is part of * any modules, this will also override any name policy set using diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmField.java b/realm-annotations/src/main/java/io/realm/annotations/RealmField.java index 0cdc67f6fd..bd665ee0d9 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/RealmField.java +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmField.java @@ -30,6 +30,15 @@ @Inherited public @interface RealmField { + /** + * Manually set the internal name used by Realm for this field. This will override any + * {@link RealmNamingPolicy} set on the class or the module. + * + * @see io.realm.annotations.RealmNamingPolicy for more information about what setting the name means. + * @see #name() + */ + String value() default ""; + /** * Manually set the internal name used by Realm for this field. This will override any * {@link RealmNamingPolicy} set on the class or the module. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index bcbb25cf26..03f3fa523f 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -307,8 +307,10 @@ public boolean generate(ModuleMetaData moduleMetaData) { RealmClass realmClassAnnotation = classType.getAnnotation(RealmClass.class); // If name has been specifically set, it should override any module policy. - if (!realmClassAnnotation.name().equals("")) { + if (!realmClassAnnotation.name().isEmpty()) { internalClassName = realmClassAnnotation.name(); + } else if (!realmClassAnnotation.value().isEmpty()) { + internalClassName = realmClassAnnotation.value(); } else { internalClassName = moduleClassNameFormatter.convert(javaClassName); } @@ -581,14 +583,15 @@ private boolean categorizeField(Element element) { private String getInternalFieldName(VariableElement field, NameConverter defaultConverter) { RealmField nameAnnotation = field.getAnnotation(RealmField.class); if (nameAnnotation != null) { - String declaredName = nameAnnotation.name(); - if (!declaredName.equals("")) { - return declaredName; - } else { - Utils.note(String.format("Empty internal name defined on @RealmField. " + - "Falling back to named used by Java model class: %s", field.getSimpleName()), field); - return field.getSimpleName().toString(); + if (!nameAnnotation.name().isEmpty()) { + return nameAnnotation.name(); } + if (!nameAnnotation.value().isEmpty()) { + return nameAnnotation.value(); + } + Utils.note(String.format("Empty internal name defined on @RealmField. " + + "Falling back to named used by Java model class: %s", field.getSimpleName()), field); + return field.getSimpleName().toString(); } else { return defaultConverter.convert(field.getSimpleName().toString()); } diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyClassOnly.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyClassOnly.java index 0b0972fa0f..e7507ee3f5 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyClassOnly.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyClassOnly.java @@ -23,7 +23,7 @@ /** * Class with only a custom name */ -@RealmClass(name = "customName") +@RealmClass("customName") public class NamePolicyClassOnly extends RealmObject { public String firstName; diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyFieldNameOnly.java b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyFieldNameOnly.java index 1109fb5e21..a4c2259126 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyFieldNameOnly.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NamePolicyFieldNameOnly.java @@ -25,7 +25,7 @@ */ public class NamePolicyFieldNameOnly extends RealmObject { - @RealmField(name = "first_name") + @RealmField("first_name") public String firstName; public String lastName; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java b/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java index e4c23cbce0..829c3386db 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java @@ -25,11 +25,13 @@ import io.realm.entities.realmname.ClassNameOverrideModulePolicy; import io.realm.entities.realmname.ClassWithPolicy; +import io.realm.entities.realmname.ClassWithValueDefinedNames; import io.realm.entities.realmname.CustomRealmNamesModule; import io.realm.entities.realmname.FieldNameOverrideClassPolicy; import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -206,6 +208,18 @@ public void dynamicSchemaReturnsInternalNames() { } } + // Verify that names using the default value() parameter on annotations are used correctly + @Test + public void valueParameterDefinedNamesInsteadOfExplicit() { + RealmSchema schema = realm.getSchema(); + assertTrue(schema.contains(ClassWithValueDefinedNames.REALM_CLASS_NAME)); + assertFalse(schema.contains(ClassWithValueDefinedNames.JAVA_CLASS_NAME)); + + RealmObjectSchema classSchema = schema.get(ClassWithValueDefinedNames.REALM_CLASS_NAME); + assertTrue(classSchema.hasField(ClassWithValueDefinedNames.REALM_FIELD_NAME)); + assertFalse(classSchema.hasField(ClassWithValueDefinedNames.JAVA_FIELD_NAME)); + } + // // Dynamic Realm tests // diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassWithValueDefinedNames.java b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassWithValueDefinedNames.java new file mode 100644 index 0000000000..fd35858876 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/ClassWithValueDefinedNames.java @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.entities.realmname; + +import io.realm.RealmObject; +import io.realm.annotations.RealmClass; +import io.realm.annotations.RealmField; + +@RealmClass("my-class-name") +public class ClassWithValueDefinedNames extends RealmObject { + + public static final String JAVA_CLASS_NAME = "ClassWithValueDefinedNames"; + public static final String REALM_CLASS_NAME = "my-class-name"; + + public static final String JAVA_FIELD_NAME = "field"; + public static final String REALM_FIELD_NAME = "my-field-name"; + + @RealmField("my-field-name") + public String field; +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/CustomRealmNamesModule.java b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/CustomRealmNamesModule.java index ec11780ecf..89e6bc5b2f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/CustomRealmNamesModule.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/realmname/CustomRealmNamesModule.java @@ -18,11 +18,14 @@ import io.realm.annotations.RealmModule; import io.realm.annotations.RealmNamingPolicy; -@RealmModule(classes = { - ClassNameOverrideModulePolicy.class, - ClassWithPolicy.class, - DefaultPolicyFromModule.class, - FieldNameOverrideClassPolicy.class }, +@RealmModule(classes = + { + ClassNameOverrideModulePolicy.class, + ClassWithPolicy.class, + ClassWithValueDefinedNames.class, + DefaultPolicyFromModule.class, + FieldNameOverrideClassPolicy.class + }, classNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES, fieldNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES ) From 8ea639ca40cdf0e097b196ac39ed500f435da7ca Mon Sep 17 00:00:00 2001 From: Henning Dodenhof Date: Wed, 12 Sep 2018 14:19:06 +0200 Subject: [PATCH 1302/2110] Bump ReLinker to 1.3.0 (#6156) --- CHANGELOG.md | 6 ++++++ realm/realm-library/build.gradle | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ab3427456..07bf5a0b9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.5.1 (YYYY-MM-DD) + +### Internal + +* Updated ReLinker to 1.3.0. + ## 5.5.0 (2018-08-31) ### Enhancements diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index b36f137ced..6bfae29f9a 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -211,7 +211,7 @@ dependencies { api "io.realm:realm-annotations:${version}" implementation 'com.google.code.findbugs:jsr305:3.0.2' - implementation 'com.getkeepsafe.relinker:relinker:1.2.2' + implementation 'com.getkeepsafe.relinker:relinker:1.3.0' kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 objectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' From b32b8004b708136f2143ef020d7b3272b9fa02b4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 12 Sep 2018 14:21:27 +0200 Subject: [PATCH 1303/2110] Add mention of Android App Bundle bug fix --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07bf5a0b9f..1af3901541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ ## 5.5.1 (YYYY-MM-DD) +### Bug Fixes + +* Building with Android App Bundle enabled should now work correctly (#5977). + ### Internal * Updated ReLinker to 1.3.0. + ## 5.5.0 (2018-08-31) ### Enhancements From a1cbe18982052cc04766b32af9ce1c0c8ecbb239 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 16 Sep 2018 23:24:19 +0200 Subject: [PATCH 1304/2110] Upgrade build tool dependencies. (#5965) --- README.md | 2 +- dependencies.list | 5 + .../build.gradle | 12 +- examples/build.gradle | 6 +- examples/gradle.properties | 8 +- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/multiprocessExample/build.gradle | 2 +- examples/newsreaderExample/build.gradle | 4 +- examples/objectServerExample/build.gradle | 4 +- examples/rxJavaExample/build.gradle | 6 +- .../secureTokenAndroidKeyStore/build.gradle | 2 +- examples/threadExample/build.gradle | 3 +- .../examples/threads/ReceivingActivity.java | 4 +- .../threads/ThreadExampleActivity.java | 4 +- examples/unitTestExample/build.gradle | 18 +- gradle-plugin/build.gradle | 7 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../groovy/io/realm/gradle/PluginTest.groovy | 7 +- gradle.properties | 6 +- gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 54333 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- library-benchmarks/build.gradle | 7 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-transformer/build.gradle | 13 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../transformer/BytecodeModifierTest.groovy | 223 ---------------- .../realm/transformer/ByteCodeModifierTest.kt | 238 ++++++++++++++++++ realm.properties | 2 +- realm/build.gradle | 17 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 54333 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/realm-library/build.gradle | 4 +- 40 files changed, 329 insertions(+), 291 deletions(-) delete mode 100644 realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy create mode 100644 realm-transformer/src/test/kotlin/io/realm/transformer/ByteCodeModifierTest.kt diff --git a/README.md b/README.md index 7fb11c6441..231131ca89 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,9 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. + * The latest stable version of Android Studio. Currently [3.1.4](https://developer.android.com/studio/). * Download & install the Android SDK **Build-Tools 27.0.2**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). - * If you use Android Studio, Android Studio 3.0 or higher is required. * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library/Android`. The download will unzip as the directory `android-ndk-r10e`. diff --git a/dependencies.list b/dependencies.list index b9456906f1..e3cf808ef9 100644 --- a/dependencies.list +++ b/dependencies.list @@ -6,3 +6,8 @@ REALM_SYNC_SHA256=f5f52093270c8d26a4b6ba3790c05425d89786033aa6d061fd74e9a24ecb22 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. REALM_OBJECT_SERVER_VERSION=3.9.9 + +# Common Android settings across projects +GRADLE_BUILD_TOOLS=3.1.4 +ANDROID_BUILD_TOOLS=27.0.3 + diff --git a/examples/architectureComponentsExample/build.gradle b/examples/architectureComponentsExample/build.gradle index d44c3d0292..9af0f5f9a3 100644 --- a/examples/architectureComponentsExample/build.gradle +++ b/examples/architectureComponentsExample/build.gradle @@ -38,10 +38,10 @@ android { } dependencies { - implementation "android.arch.lifecycle:runtime:1.1.0" - implementation "android.arch.lifecycle:extensions:1.1.0" - annotationProcessor "android.arch.lifecycle:compiler:1.1.0" - implementation 'com.android.support:appcompat-v7:27.0.2' - implementation 'com.android.support:recyclerview-v7:27.0.2' - implementation 'com.android.support:design:27.0.2' + implementation "android.arch.lifecycle:runtime:1.1.1" + implementation "android.arch.lifecycle:extensions:1.1.1" + annotationProcessor "android.arch.lifecycle:compiler:1.1.1" + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:recyclerview-v7:27.1.1' + implementation 'com.android.support:design:27.1.1' } diff --git a/examples/build.gradle b/examples/build.gradle index 85a4b448a7..9e9f47be35 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,6 +1,8 @@ +def projectDependencies = new Properties() +projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) project.ext.sdkVersion = 27 project.ext.minSdkVersion = 15 -project.ext.buildTools = '27.0.2' +project.ext.buildTools = projectDependencies.get("ANDROID_BUILD_TOOLS") // Don't cache SNAPSHOT (changing) dependencies. configurations.all { @@ -33,7 +35,7 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha06' + classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath "io.realm:realm-gradle-plugin:${currentVersion}" } diff --git a/examples/gradle.properties b/examples/gradle.properties index 9c2c6c4094..94789e069a 100644 --- a/examples/gradle.properties +++ b/examples/gradle.properties @@ -2,13 +2,13 @@ org.gradle.jvmargs=-Xmx2048M org.gradle.caching=true android.enableD8=true -# disable AAPT2 to work around an issue of Robolectric in unitTestExample https://github.com/robolectric/robolectric/issues/3169 -android.enableAapt2=false - # Gradle sync failed: Due to a limitation of Gradle’s new variant-aware dependency management, loading the Android Gradle plugin in different class loaders leads to a build error. # This can occur when the buildscript classpaths that contain the Android Gradle plugin in sub-projects, or included projects in the case of composite builds, are set differently. # To resolve this issue, add the Android Gradle plugin to only the buildscript classpath of the top-level build.gradle file. # In the case of composite builds, also make sure the build script classpaths that contain the Android Gradle plugin are identical across the main and included projects. # If you are using a version of Gradle that has fixed the issue, you can disable this check by setting android.enableBuildScriptClasspathCheck=false in the gradle.properties file. # To learn more about this issue, go to https://d.android.com/r/tools/buildscript-classpath-check.html. -android.enableBuildScriptClasspathCheck=false \ No newline at end of file +android.enableBuildScriptClasspathCheck=false + +# See https://developer.android.com/studio/build/optimize-your-build#configuration_on_demand +org.gradle.configureondemand=false diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index 01b8bf6b1f99cad9213fc495b33ad5bbab8efd20..0d4a9516871afd710a9d84d89e31ba77745607bd 100644 GIT binary patch delta 7399 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 KPzU?}WBw1fHw(7_ diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 57c7d2d22b..7dc503f149 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/examples/multiprocessExample/build.gradle b/examples/multiprocessExample/build.gradle index 8950b28f94..1a5d7ebf33 100644 --- a/examples/multiprocessExample/build.gradle +++ b/examples/multiprocessExample/build.gradle @@ -25,6 +25,6 @@ android { } dependencies { - implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:appcompat-v7:27.1.1' } diff --git a/examples/newsreaderExample/build.gradle b/examples/newsreaderExample/build.gradle index 6bfde58102..58eca8669f 100644 --- a/examples/newsreaderExample/build.gradle +++ b/examples/newsreaderExample/build.gradle @@ -47,8 +47,8 @@ dependencies { implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' implementation 'com.squareup.retrofit2:converter-jackson:2.3.0' implementation 'com.squareup.retrofit2:retrofit:2.3.0' - implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' - implementation 'io.reactivex.rxjava2:rxjava:2.1.5' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation 'io.reactivex.rxjava2:rxjava:2.1.13' implementation 'me.zhanghai.android.materialprogressbar:library:1.1.4' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' //TODO:Can be refactored with Native Android Data Binding } diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 1dbf552f12..ff0d0d5ccd 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -42,8 +42,8 @@ realm { } dependencies { - implementation 'com.android.support:appcompat-v7:27.0.2' - implementation 'com.android.support:design:27.0.2' + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:design:27.1.1' implementation 'me.zhanghai.android.materialprogressbar:library:1.3.0' implementation 'com.jakewharton:butterknife:8.8.1'//TODO:Can be refactored with Native Android Data Binding annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'//TODO:Can be refactored with Native Android Data Binding diff --git a/examples/rxJavaExample/build.gradle b/examples/rxJavaExample/build.gradle index 36ccf169ca..63fddcb435 100644 --- a/examples/rxJavaExample/build.gradle +++ b/examples/rxJavaExample/build.gradle @@ -35,9 +35,9 @@ android { } dependencies { - implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' - implementation 'io.reactivex.rxjava2:rxjava:2.1.0' - implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation 'io.reactivex.rxjava2:rxjava:2.1.13' + implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' implementation 'com.squareup.retrofit2:converter-jackson:2.3.0' diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle index fc69210908..fd08fb3f9a 100644 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ b/examples/secureTokenAndroidKeyStore/build.gradle @@ -33,7 +33,7 @@ dependencies { androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', { exclude group: 'com.android.support', module: 'support-annotations' }) - implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:appcompat-v7:27.1.1' testImplementation 'junit:junit:4.12' implementation 'io.realm:secure-userstore:1.0.1' } diff --git a/examples/threadExample/build.gradle b/examples/threadExample/build.gradle index c0188a8bb5..967b71f998 100644 --- a/examples/threadExample/build.gradle +++ b/examples/threadExample/build.gradle @@ -24,6 +24,5 @@ android { } dependencies { - //noinspection GradleDependency - implementation 'com.android.support:appcompat-v7:24.0.0' + implementation 'com.android.support:appcompat-v7:27.1.1' } diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/ReceivingActivity.java b/examples/threadExample/src/main/java/io/realm/examples/threads/ReceivingActivity.java index 5e2c6fa749..28afaa64c1 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/ReceivingActivity.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/ReceivingActivity.java @@ -17,13 +17,13 @@ package io.realm.examples.threads; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import android.widget.TextView; import io.realm.Realm; import io.realm.examples.threads.model.Person; -public class ReceivingActivity extends ActionBarActivity { +public class ReceivingActivity extends AppCompatActivity { private Realm realm; diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/ThreadExampleActivity.java b/examples/threadExample/src/main/java/io/realm/examples/threads/ThreadExampleActivity.java index c3774acca1..ce9ce4e35b 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/ThreadExampleActivity.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/ThreadExampleActivity.java @@ -23,12 +23,12 @@ import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import java.util.Locale; -public class ThreadExampleActivity extends ActionBarActivity implements android.support.v7.app.ActionBar.TabListener { +public class ThreadExampleActivity extends AppCompatActivity implements android.support.v7.app.ActionBar.TabListener { private ViewPager viewPager; diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index ddab123c16..ad9010c919 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -22,7 +22,7 @@ android { signingConfig signingConfigs.debug } debug { - minifyEnabled true + minifyEnabled false } } @@ -30,13 +30,19 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + testOptions { + unitTests { + includeAndroidResources = true + } + } } dependencies { - implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:appcompat-v7:27.1.1' - testImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' + testImplementation 'io.reactivex.rxjava2:rxjava:2.1.13' // Testing testImplementation 'junit:junit:4.12' @@ -50,9 +56,9 @@ dependencies { testImplementation "org.powermock:powermock-classloading-xstream:1.6.5" - androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test:runner:1.0.2' // Set this dependency to use JUnit 4 rules - androidTestImplementation 'com.android.support.test:rules:1.0.1' + androidTestImplementation 'com.android.support.test:rules:1.0.2' // Set this dependency to build and run Espresso tests - androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index f4eb2c691c..6737f0d1f4 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -20,6 +20,9 @@ props.each { key, val -> project.ext.set(key, val) } +def projectDependencies = new Properties() +projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) + repositories { mavenLocal() google() @@ -52,11 +55,11 @@ dependencies { and this https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ for more info. */ compile 'com.neenbedankt.gradle.plugins:android-apt:1.8' //TODO: https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ - compileOnly 'com.android.tools.build:gradle:3.1.0-alpha06' + compileOnly "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" testCompile gradleTestKit() testCompile 'junit:junit:4.12' - testCompile 'com.android.tools.build:gradle:3.1.0-alpha06' + testCompile "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" } //for Ant filter diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 01b8bf6b1f99cad9213fc495b33ad5bbab8efd20..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 57c7d2d22b..bd24854fe8 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index 1c203b3f04..374d1e4f35 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -39,11 +39,14 @@ class PluginTest { private Project project private String currentVersion + private Properties projectDependencies @Before void setUp() { project = ProjectBuilder.builder().build() currentVersion = new File("../version.txt").text.trim() + projectDependencies = new Properties() + projectDependencies.load(new FileInputStream("../dependencies.list")) } @Test @@ -55,7 +58,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha03' + classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' } } @@ -91,7 +94,7 @@ class PluginTest { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha03' + classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' } } diff --git a/gradle.properties b/gradle.properties index 09e1425217..9306f6c0a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-XX:MaxPermSize=512m org.gradle.caching=true -android.enableD8=true \ No newline at end of file +android.enableD8=true + +# See https://issuetracker.google.com/issues/80464216 +# Can be removed when we upgrade to Android Build Tools 3.3.0 +org.gradle.workers.max=1 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 01b8bf6b1f99cad9213fc495b33ad5bbab8efd20..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 57c7d2d22b..bd24854fe8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 0382c346d3..243bdbfef7 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -1,3 +1,6 @@ +def projectDependencies = new Properties() +projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) + buildscript { repositories { mavenLocal() @@ -5,7 +8,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha06' + classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" } } @@ -28,7 +31,7 @@ apply plugin: 'realm-android' android { compileSdkVersion 27 - buildToolsVersion "27.0.2" + buildToolsVersion ${projectDependencies.get("ANDROID_BUILD_TOOLS")} defaultConfig { minSdkVersion 15 diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.jar b/library-benchmarks/gradle/wrapper/gradle-wrapper.jar index 01b8bf6b1f99cad9213fc495b33ad5bbab8efd20..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index 57c7d2d22b..bd24854fe8 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.jar b/library-build-transformer/gradle/wrapper/gradle-wrapper.jar index 01b8bf6b1f99cad9213fc495b33ad5bbab8efd20..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties index 57c7d2d22b..bd24854fe8 100644 --- a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index 01b8bf6b1f99cad9213fc495b33ad5bbab8efd20..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 57c7d2d22b..bd24854fe8 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index c92b91f248..35b575198a 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.2.40' + ext.kotlin_version = '1.2.50' repositories { google() jcenter() @@ -20,7 +20,6 @@ allprojects { } apply plugin: 'kotlin' -apply plugin: 'groovy' apply plugin: 'java' apply plugin: 'maven' apply plugin: 'maven-publish' @@ -62,14 +61,12 @@ sourceSets { dependencies { compile gradleApi() compile "io.realm:realm-annotations:${version}" + compileOnly "com.android.tools.build:gradle:${properties.get("GRADLE_BUILD_TOOLS")}" compileOnly 'com.android.tools.build:gradle:3.1.1' compile 'org.javassist:javassist:3.21.0-GA' compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - testCompile localGroovy() - testCompile('org.spockframework:spock-core:1.0-groovy-2.4') { - exclude module: 'groovy-all' - } + testCompile 'junit:junit:4.12' } // for Ant filter @@ -83,10 +80,6 @@ task generateVersionClass(type: Copy) { } compileJava.dependsOn generateVersionClass -compileGroovy.dependsOn = compileGroovy.taskDependencies.values - 'compileJava' -compileKotlin.dependsOn compileGroovy -compileKotlin.classpath += files(compileGroovy.destinationDir) -classes.dependsOn compileKotlin def commonPom = { licenses { diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 01b8bf6b1f99cad9213fc495b33ad5bbab8efd20..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 57c7d2d22b..bd24854fe8 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy b/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy deleted file mode 100644 index e9abb6b751..0000000000 --- a/realm-transformer/src/test/groovy/io/realm/transformer/BytecodeModifierTest.groovy +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.transformer - -import io.realm.annotations.Ignore -import javassist.* -import javassist.bytecode.AnnotationsAttribute -import javassist.bytecode.CodeIterator -import javassist.bytecode.ConstPool -import javassist.bytecode.Opcode -import javassist.bytecode.annotation.Annotation -import spock.lang.Specification - -import java.lang.reflect.Modifier - -class BytecodeModifierTest extends Specification { - def "AddRealmAccessors"() { - setup: 'generate an empty class' - def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('testClass') - - and: 'add a field' - def ctField = new CtField(CtClass.intType, 'age', ctClass) - ctClass.addField(ctField) - - when: 'the accessors are added' - BytecodeModifier.addRealmAccessors(ctClass) - - then: 'the accessors are generated' - def ctMethods = ctClass.getDeclaredMethods() - def methodNames = ctMethods.name - methodNames.contains('realmGet$age') - methodNames.contains('realmSet$age') - - and: 'the accessors are public' - ctMethods.each { - it.getModifiers() == Modifier.PUBLIC - } - } - - // https://github.com/realm/realm-java/issues/3469 - def "AddRealmAccessors_duplicateSetter"() { - setup: 'generate an empty class' - def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('testClass') - - and: 'add a field' - def ctField = new CtField(CtClass.intType, 'age', ctClass) - ctClass.addField(ctField) - - and: 'add a setter' - def setter = CtNewMethod.setter('realmSet$age', ctField) - ctClass.addMethod(setter) - - when: 'addRealmAccessors is called' - BytecodeModifier.addRealmAccessors(ctClass) - - then: 'a getter for the field is generated' - def ctMethods = ctClass.getDeclaredMethods() - def methodNames = ctMethods.name - methodNames.contains('realmGet$age') - - and: 'the setter is not changed' - ctMethods.find {it.name.equals('realmSet$age')} == setter - - and: 'the accessors are public' - ctMethods.each { - it.getModifiers() == Modifier.PUBLIC - } - } - - // https://github.com/realm/realm-java/issues/3469 - def "AddRealmAccessors_duplicateGetter"() { - setup: 'generate an empty class' - def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('testClass') - - and: 'add a field' - def ctField = new CtField(CtClass.intType, 'age', ctClass) - ctClass.addField(ctField) - - and: 'add a getter' - def getter = CtNewMethod.getter('realmGet$age', ctField) - ctClass.addMethod(getter) - - when: 'addRealmAccessors is called' - BytecodeModifier.addRealmAccessors(ctClass) - - then: 'a setter for the field is generated' - def ctMethods = ctClass.getDeclaredMethods() - def methodNames = ctMethods.name - methodNames.contains('realmSet$age') - - and: 'the getter is not changed' - ctMethods.find {it.name.equals('realmGet$age')} == getter - - and: 'the accessors are public' - ctMethods.each { - it.getModifiers() == Modifier.PUBLIC - } - } - - def "AddRealmAccessors_IgnoreAnnotation"() { - setup: 'generate an empty class' - def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('TestClass') - def constPool = new ConstPool('TestClass') - - and: 'add a field with @Ignore' - def ctField = new CtField(CtClass.intType, 'age', ctClass) - ctClass.addField(ctField) - def attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag) - def ignoreAnnotation = new Annotation(Ignore.class.name, constPool) - attr.addAnnotation(ignoreAnnotation) - ctField.fieldInfo.addAttribute(attr) - - when: 'Try to add the accessor' - BytecodeModifier.addRealmAccessors(ctClass) - - then: 'the accessor should not be generated' - def ctMethods = ctClass.getDeclaredMethods() - def methodNames = ctMethods.name - !methodNames.contains('realmGet$age') - !methodNames.contains('realmSet$age') - } - - def "UseRealmAccessors"() { - setup: 'generate an empty class' - def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('TestClass') - - and: 'add a field' - def ctField = new CtField(CtClass.intType, 'age', ctClass) - ctClass.addField(ctField) - - and: 'add a method that uses such field' - def ctMethod = CtNewMethod.make('public boolean canDrive() { return this.age >= 18; }', ctClass) - ctClass.addMethod(ctMethod) - - and: 'realm accessors are added' - BytecodeModifier.addRealmAccessors(ctClass) - - when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(classPool, ctClass, [ctField]) - - then: 'the field is not used and getter is called in the method ' - !isFieldRead(ctMethod) && hasMethodCall(ctMethod) - } - - def "UseRealmAccessors_fieldAccessConstructorIsTransformed"() { - setup: 'generate an empty class' - def classPool = ClassPool.getDefault() - def ctClass = classPool.makeClass('TestClass') - - and: 'add a field' - def ctField = new CtField(CtClass.intType, 'age', ctClass) - ctClass.addField(ctField) - - and: 'add a method that sets such field' - def ctMethod = CtNewMethod.make('private void setupAge(int age) { this.age = age; }', ctClass) - ctClass.addMethod(ctMethod) - - and: 'add a default constructor that uses the method' - def ctDefaultConstructor = CtNewConstructor.make('public TestClass() { int myAge = this.age; }', ctClass) - ctClass.addConstructor(ctDefaultConstructor) - - and: 'add a non-default constructor that uses the method' - def ctNonDefaultConstructor = CtNewConstructor.make('public TestClass(TestClass other) { int otherAge = other.age; }', ctClass) - ctClass.addConstructor(ctNonDefaultConstructor) - - and: 'realm accessors are added' - BytecodeModifier.addRealmAccessors(ctClass) - - when: 'the field use is replaced by the accessor' - BytecodeModifier.useRealmAccessors(classPool, ctClass, [ctField]) - - then: 'the field is not used in the method anymore' - !isFieldRead(ctDefaultConstructor) && hasMethodCall(ctDefaultConstructor) && - !isFieldRead(ctNonDefaultConstructor) && hasMethodCall(ctNonDefaultConstructor) - } - - private static def isFieldRead(CtBehavior behavior) { - def methodInfo = behavior.getMethodInfo() - def codeAttribute = methodInfo.getCodeAttribute() - - for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { - int index = ci.next() - int op = ci.byteAt(index) - if (op == Opcode.GETFIELD) { - return true - } - } - return false - } - - private static def hasMethodCall(CtBehavior behavior) { - def methodInfo = behavior.getMethodInfo() - def codeAttribute = methodInfo.getCodeAttribute() - - for (CodeIterator ci = codeAttribute.iterator(); ci.hasNext();) { - int index = ci.next() - int op = ci.byteAt(index) - if (op == Opcode.INVOKEVIRTUAL) { - return true - } - } - return false - } -} diff --git a/realm-transformer/src/test/kotlin/io/realm/transformer/ByteCodeModifierTest.kt b/realm-transformer/src/test/kotlin/io/realm/transformer/ByteCodeModifierTest.kt new file mode 100644 index 0000000000..0abb726cec --- /dev/null +++ b/realm-transformer/src/test/kotlin/io/realm/transformer/ByteCodeModifierTest.kt @@ -0,0 +1,238 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.transformer + +import io.realm.annotations.Ignore +import javassist.* +import javassist.bytecode.AnnotationsAttribute +import javassist.bytecode.ConstPool +import javassist.bytecode.Opcode +import javassist.bytecode.annotation.Annotation +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import java.lang.reflect.Modifier + +class ByteCodeModifierTest { + + @Test + fun addRealmAccessors() { + // Generate an empty class + val classPool = ClassPool.getDefault() + val ctClass = classPool.makeClass("testClass") + + // Add a field + val ctField = CtField(CtClass.intType, "age", ctClass) + ctClass.addField(ctField) + + // The accessors are added + BytecodeModifier.addRealmAccessors(ctClass) + + // The accessors are generated + val ctMethods = ctClass.declaredMethods + val methodNames = ctMethods.map { it.name } + assertTrue(methodNames.contains("realmGet\$age")) + assertTrue(methodNames.contains("realmSet\$age")) + + // The accessors are public + ctMethods.forEach { + assertTrue(it.modifiers == Modifier.PUBLIC) + } + } + + // https://github.com/realm/realm-java/issues/3469 + @Test + fun addRealmAccessors_duplicateSetter() { + // Generate an empty class + val classPool = ClassPool.getDefault() + val ctClass = classPool.makeClass("testClass") + + // Add a field + val ctField = CtField(CtClass.intType, "age", ctClass) + ctClass.addField(ctField) + + // Add a setter + val setter = CtNewMethod.setter("realmSet\$age", ctField) + ctClass.addMethod(setter) + + // addRealmAccessors is called + BytecodeModifier.addRealmAccessors(ctClass) + + // a getter for the field is generated + val ctMethods = ctClass.declaredMethods + val methodNames = ctMethods.map { it.name } + assertTrue(methodNames.contains("realmGet\$age")) + + // the setter is not changed + assertTrue(ctMethods.find { it.name == "realmSet\$age" } == setter) + + // accessors are public + ctMethods.forEach { + assertTrue(it.modifiers == Modifier.PUBLIC) + } + } + + // https://github.com/realm/realm-java/issues/3469 + @Test + fun addRealmAccessors_duplicateGetter() { + // Generate an empty class + val classPool = ClassPool.getDefault() + val ctClass = classPool.makeClass("testClass") + + // Add a field + val ctField = CtField(CtClass.intType, "age", ctClass) + ctClass.addField(ctField) + + // Add a getter + val setter = CtNewMethod.setter("realmGet\$age", ctField) + ctClass.addMethod(setter) + + // addRealmAccessors is called + BytecodeModifier.addRealmAccessors(ctClass) + + // a setter for the field is generated + val ctMethods = ctClass.declaredMethods + val methodNames = ctMethods.map { it.name } + assertTrue(methodNames.contains("realmSet\$age")) + + // the getter is not changed + assertTrue(ctMethods.find { it.name == "realmGet\$age" } == setter) + + // accessors are public + ctMethods.forEach { + assertTrue(it.modifiers == Modifier.PUBLIC) + } + } + + @Test + fun addRealmAccessors_ignoreAnnotation() { + // Generate an empty class + val classPool = ClassPool.getDefault() + val ctClass = classPool.makeClass("testClass") + val constPool = ConstPool("TestClass") + + // Add a field with @Ignore + val ctField = CtField(CtClass.intType, "age", ctClass) + ctClass.addField(ctField) + val attr = AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag) + val ignoreAnnotation = Annotation(Ignore::class.java.name, constPool) + attr.addAnnotation(ignoreAnnotation) + ctField.fieldInfo.addAttribute(attr) + + // Try to add the accessor + BytecodeModifier.addRealmAccessors(ctClass) + + // the accessor should not be generated + // a setter for the field is generated + val ctMethods = ctClass.declaredMethods + val methodNames = ctMethods.map { it.name } + assertFalse(methodNames.contains("realmSet\$age")) + assertFalse(methodNames.contains("realmGet\$age")) + } + + @Test + fun userRealmAccessors() { + // Generate an empty class + val classPool = ClassPool.getDefault() + val ctClass = classPool.makeClass("testClass") + + // Add a field + val ctField = CtField(CtClass.intType, "age", ctClass) + ctClass.addField(ctField) + + // Add a method that uses such field + val ctMethod = CtNewMethod.make("public boolean canDrive() { return this.age >= 18; }", ctClass) + ctClass.addMethod(ctMethod) + + // Realm accessors are called + BytecodeModifier.addRealmAccessors(ctClass) + + // the field use is replaced by the accessor + BytecodeModifier.useRealmAccessors(classPool, ctClass, listOf(ctField)) + + // the field is not used and getter is called in the method + assertTrue(!isFieldRead(ctMethod) && hasMethodCall(ctMethod)) + } + + fun userRealmAccessors_fieldAccessConstructorIsTransformed() { + // Generate an empty class + val classPool = ClassPool.getDefault() + val ctClass = classPool.makeClass("testClass") + val constPool = ConstPool("TestClass") + + // Add a field with @Ignore + val ctField = CtField(CtClass.intType, "age", ctClass) + ctClass.addField(ctField) + + // Add a method sets such field + val ctMethod = CtNewMethod.make("private void setupAge(int age) { this.age = age; }", ctClass) + ctClass.addMethod(ctMethod) + + // Add a default constructor that uses the method + val ctDefaultConstructor = CtNewConstructor.make("public TestClass() { int myAge = this.age; }", ctClass) + ctClass.addConstructor(ctDefaultConstructor) + + // Add a non-default constructor that uses the method + val ctNonDefaultConstructor = CtNewConstructor.make("public TestClass(TestClass other) { int otherAge = other.age; }", ctClass) + ctClass.addConstructor(ctNonDefaultConstructor) + + // Realm accessors are added + BytecodeModifier.addRealmAccessors(ctClass) + + // the field use is replaced by the accessor + BytecodeModifier.useRealmAccessors(classPool, ctClass, listOf(ctField)) + + // the field is not used in the method anymore + assertTrue(!isFieldRead(ctDefaultConstructor) + && hasMethodCall(ctDefaultConstructor) + && !isFieldRead(ctNonDefaultConstructor) + && hasMethodCall(ctNonDefaultConstructor)) + } + + private fun isFieldRead(behavior: CtBehavior): Boolean { + val methodInfo = behavior.methodInfo + val codeAttribute = methodInfo.codeAttribute + + val it = codeAttribute.iterator() + var index = 0; + while (it.hasNext()) { + val op: Int = it.byteAt(index) + index = it.next() + if (op == Opcode.GETFIELD) { + return true; + } + } + return false + } + + private fun hasMethodCall(behavior: CtBehavior): Boolean { + val methodInfo = behavior.methodInfo + val codeAttribute = methodInfo.codeAttribute + + val it = codeAttribute.iterator() + var index = 0; + while (it.hasNext()) { + val op: Int = it.byteAt(index) + index = it.next() + if (op == Opcode.INVOKEVIRTUAL) { + return true; + } + } + return false + } + +} diff --git a/realm.properties b/realm.properties index 4567c707cc..1a1be59f2f 100644 --- a/realm.properties +++ b/realm.properties @@ -1,2 +1,2 @@ -gradleVersion=4.4.1 +gradleVersion=4.9 ndkVersion=r10e diff --git a/realm/build.gradle b/realm/build.gradle index af6f32dd91..9c4ab0164d 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,9 +1,8 @@ -project.ext.minSdkVersion = 9 -project.ext.compileSdkVersion = 26 -project.ext.buildToolsVersion = '27.0.2' - buildscript { - ext.kotlin_version = '1.2.40' + def projectDependencies = new Properties() + projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) + ext.gradle_build_tools = projectDependencies.get("GRADLE_BUILD_TOOLS") + ext.kotlin_version = '1.2.50' ext.dokka_version = '0.9.16' repositories { mavenLocal() @@ -14,7 +13,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0-alpha06' + classpath "com.android.tools.build:gradle:${gradle_build_tools}" classpath 'de.undercouch:gradle-download-task:3.3.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' @@ -37,6 +36,12 @@ allprojects { project.ext.set(key, val) } + def projectDependencies = new Properties() + projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) + project.ext.minSdkVersion = 9 + project.ext.compileSdkVersion = 26 + project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") + group = 'io.realm' version = file("${rootDir}/../version.txt").text.trim() repositories { diff --git a/realm/gradle/wrapper/gradle-wrapper.jar b/realm/gradle/wrapper/gradle-wrapper.jar index 01b8bf6b1f99cad9213fc495b33ad5bbab8efd20..99340b4ad18d3c7e764794d300ffd35017036793 100644 GIT binary patch delta 705 zcmYL{-%Ha`7{+&AWj{#sYo*!I8LT4tis2GVX_1Sf<(d@^X%;mvW(t-i=tUtSNEcn$ zJKQoh=RHaqT|Wz+?>zzJn#F$IlR8g-+?W`RyK*Q zpqVY;Zm@xU!BuT3J_l8*WC(;x;9wk&;|iQ1PG_{js)SdogDaxIrc*YEk!#0-g-{3G zH;57=n!>fKqr&Ie2dsqM>?X`h?PNB|k#5sg_?A@KUL$2oO%V(cl?;2NY>qUNv0mbd z`a(Ps$&gLTr?vvb5(@I8oHp5|Wp@;!#a@Q?s7e(MY2Aw+B%4#>*XS{-a!GW=j!^!& zHPqo*tbvL>sP^o_#<Bg|M zex^>+?h%29@Ft_+_A+N3m|5R==LMSZ y_n`_hSx)_a-11xGS2X50I}T^AlT~0ox3_$K+r(~^{aX-uUxNN^wKlrAD*gax&lWEL delta 757 zcmY+CUr3Wt7{+&AW@@Q3QCL9d}lUxc3_mF^p| z)(+$?$+!?!XmgL2Dl--GUMhVw3CmctAKN1;-QLI1jP}S0b_l-6c`B|Fb$eAl@1l~v zTHx8GV`M%eWMHYDMq7n^)@tNUWaLH_l5N6z7|ny-WQqmSHX?qdcxqq90zj1+I8A zyNsE52L%W4d7y&#lJUtS8~MDmE9Mc&%#a?}S88c~D2pZ7SW(F~l0_p9C+f*Ms)c=t zlQibl@JFJJ4vcB&PPUM1T*F7>X52~2*tV+Br3uXrOz7pkHdv1Pb+ly1;!Uj y(VecLYYSXfi_v^YE9?d`=^l0po$FWm=X$$dXa4_}n*PpBoP999uarWIf8aNW=qTF& diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 57c7d2d22b..bd24854fe8 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 6bfae29f9a..791b2bc3d7 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -219,8 +219,8 @@ dependencies { kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation fileTree(dir: 'testLibs', include: ['*.jar']) androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test:rules:1.0.1' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test:rules:1.0.2' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' From ed6b6c26c723612bd4847971ee3ba559e934349a Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Wed, 19 Sep 2018 13:35:25 +0200 Subject: [PATCH 1305/2110] Update CHANGELOG.md (#6167) --- CHANGELOG.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1af3901541..346c2b00fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,17 @@ ## 5.5.1 (YYYY-MM-DD) -### Bug Fixes +### Enhancements +* Building with Android App Bundle is now supported ([#5977](https://github.com/realm/realm-java/issues/5977)). -* Building with Android App Bundle enabled should now work correctly (#5977). +### Fixes +* None -### Internal +### Compatibility +* File format: ver. 7 (upgrades automatically from previous formats) +* Realm Object Server: 3.0.0 or later. +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. +### Internal * Updated ReLinker to 1.3.0. From 5824835c3861fde7d5d47ee12b63bf2c8eb49fe8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 20 Sep 2018 11:59:45 +0200 Subject: [PATCH 1306/2110] Fix base unit tests (#6162) --- library-build-transformer/README.md | 19 +++++++++- library-build-transformer/build.gradle | 6 ++-- .../buildtransformer/RealmBuildTransformer.kt | 1 + .../asm/ClassPoolTransformer.kt | 13 +++---- .../asm/visitors/AnnotatedCodeStripVisitor.kt | 25 ++++++------- .../asm/visitors/AnnotationVisitor.kt | 20 +++++++++-- .../testclasses/SimpleTestFields.java | 3 ++ .../annotations/CustomAnnotation.java | 11 ++++++ .../io/realm/buildtransformer/VisitorTests.kt | 20 +++++++---- .../kotlin/io/realm/KotlinRealmTests.kt | 4 +-- realm/realm-library/gradle.properties | 0 .../io/realm/internal/OsObjectStoreTests.java | 8 ++--- .../realm/internal/android/JsonUtilsTest.java | 35 ++++++++++++++----- 13 files changed, 111 insertions(+), 54 deletions(-) create mode 100644 library-build-transformer/src/test/java/io/realm/internal/annotations/CustomAnnotation.java create mode 100644 realm/realm-library/gradle.properties diff --git a/library-build-transformer/README.md b/library-build-transformer/README.md index 9a9c5ed32d..557bb81777 100644 --- a/library-build-transformer/README.md +++ b/library-build-transformer/README.md @@ -38,4 +38,21 @@ Any errors will only be caught at runtime when the actual code is accessed. * Annotations on super classes will also remove subclasses, but only the first level of inheritance. -* Single enum values cannot be stripped, only the entire enum class. \ No newline at end of file +* Single enum values cannot be stripped, only the entire enum class. + +## Running unit tests + +Running unit tests can be done from the command line by using: + + >./gradlew test + +It should also be possible to run tests from IntelliJ CE, however in some cases it will report + + "Cannot find io.realm.buildtransformer.VistitorTests" + +This is a bug in IntelliJ and can be fixed by: + +1) Close the project in IntelliJ +2) Delete the folder `.idea` +3) Re-import the project into IntelliJ + diff --git a/library-build-transformer/build.gradle b/library-build-transformer/build.gradle index a08d4481c9..17a3d9a4bc 100644 --- a/library-build-transformer/build.gradle +++ b/library-build-transformer/build.gradle @@ -2,7 +2,7 @@ group 'io.realm' version '1.0.0' buildscript { - ext.kotlin_version = '1.2.51' + ext.kotlin_version = '1.2.61' repositories { mavenCentral() @@ -45,10 +45,8 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" testCompile group:'junit', name:'junit', version:'4.12' - testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" - testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - } + compileKotlin { kotlinOptions.jvmTarget = "1.8" } diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt index 38511246c4..c213b96fed 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt @@ -29,6 +29,7 @@ import java.io.File typealias ByteCodeTypeDescriptor = String typealias QualifiedName = String typealias ByteCodeMethodName = String +typealias FieldName = String // Package level logger val logger: Logger = LoggerFactory.getLogger("realm-build-logger") diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/ClassPoolTransformer.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/ClassPoolTransformer.kt index 49ba7b17c6..bb61436cae 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/ClassPoolTransformer.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/ClassPoolTransformer.kt @@ -17,6 +17,7 @@ package io.realm.buildtransformer.asm import io.realm.buildtransformer.ByteCodeMethodName import io.realm.buildtransformer.ByteCodeTypeDescriptor +import io.realm.buildtransformer.FieldName import io.realm.buildtransformer.QualifiedName import io.realm.buildtransformer.asm.visitors.AnnotatedCodeStripVisitor import io.realm.buildtransformer.asm.visitors.AnnotationVisitor @@ -44,15 +45,15 @@ class ClassPoolTransformer(annotationQualifiedName: QualifiedName, private val i * @return All input files, both those that have been modified and those that have not. */ fun transform(): Set { - val (markedClasses, markedMethods) = pass1() - return pass2(markedClasses, markedMethods) + val (markedClasses, markedMethods, markedFields) = pass1() + return pass2(markedClasses, markedMethods, markedFields) } /** * Pass 1: Collect all classes, interfaces and enums that contain the given annotation. This include both top-level * and inner types. */ - private fun pass1(): Pair, Map>> { + private fun pass1(): Triple, Map>, Map>> { val metadataCollector = AnnotationVisitor(annotationDescriptor) inputClasses.forEach { it.inputStream().use { @@ -60,7 +61,7 @@ class ClassPoolTransformer(annotationQualifiedName: QualifiedName, private val i classReader.accept(metadataCollector, 0) } } - return Pair(metadataCollector.annotatedClasses, metadataCollector.annotatedMethods) + return Triple(metadataCollector.annotatedClasses, metadataCollector.annotatedMethods, metadataCollector.annotatedFields) } /** @@ -68,13 +69,13 @@ class ClassPoolTransformer(annotationQualifiedName: QualifiedName, private val i * are instead marked for deletion as deleting the File is the responsibility of the * transform API. */ - private fun pass2(markedClasses: Set, markedMethods: Map>): Set { + private fun pass2(markedClasses: Set, markedMethods: Map>, markedFields: Map>): Set { inputClasses.forEach { classFile -> var result = ByteArray(0) if (!classFile.shouldBeDeleted) { // Respect previously set delete flag, so avoid doing any work classFile.inputStream().use { inputStream -> val writer = ClassWriter(0) // We don't modify methods so no reason to re-calculate method frames - val classRemover = AnnotatedCodeStripVisitor(annotationDescriptor, markedClasses, markedMethods, writer) + val classRemover = AnnotatedCodeStripVisitor(annotationDescriptor, markedClasses, markedMethods, markedFields, writer) val reader = ClassReader(inputStream) reader.accept(classRemover, 0) result = if (classRemover.deleteClass) ByteArray(0) else writer.toByteArray() diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt index c8ac8c359b..a33f38acb2 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt @@ -17,6 +17,7 @@ package io.realm.buildtransformer.asm.visitors import io.realm.buildtransformer.ByteCodeMethodName import io.realm.buildtransformer.ByteCodeTypeDescriptor +import io.realm.buildtransformer.FieldName import io.realm.buildtransformer.logger import org.objectweb.asm.* import org.objectweb.asm.AnnotationVisitor @@ -28,14 +29,17 @@ import org.objectweb.asm.AnnotationVisitor class AnnotatedCodeStripVisitor(private val annotationDescriptor: String, private val markedClasses: Set, private val markedMethods: Map>, + private val markedFields: Map>, classWriter: ClassVisitor) : ClassVisitor(Opcodes.ASM6, classWriter) { var deleteClass: Boolean = false private lateinit var markedMethodsInClass: Set + private lateinit var markedFieldsInClass: Set override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array?) { // Only process this class if it or its super class doesn't have the given annotation markedMethodsInClass = markedMethods[name!!]!! + markedFieldsInClass = markedFields[name]!! deleteClass = (markedClasses.contains(name) || markedClasses.contains(superName)) if (!deleteClass) { super.visit(version, access, name, signature, superName, interfaces) @@ -54,21 +58,12 @@ class AnnotatedCodeStripVisitor(private val annotationDescriptor: String, } } - override fun visitField(access: Int, name: String?, descriptor: String?, signature: String?, value: Any?): FieldVisitor { - return object: FieldVisitor(api) { - var ignoreField = false - override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { - ignoreField = (annotationDescriptor == descriptor) - return null - } - override fun visitEnd() { - if (!ignoreField) { - // Call super ClassVisitor directly - this@AnnotatedCodeStripVisitor.cv.visitField(access, name, descriptor, signature, value) - } else { - logger.debug("Removing field: $name") - } - } + override fun visitField(access: Int, name: String?, descriptor: String?, signature: String?, value: Any?): FieldVisitor? { + return if (!markedFieldsInClass.contains(name)) { + super.visitField(access, name, descriptor, signature, value) + } else { + logger.debug("Removing field: $name") + null } } diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt index 75a761c976..b572090148 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt @@ -17,11 +17,10 @@ package io.realm.buildtransformer.asm.visitors; import io.realm.buildtransformer.ByteCodeMethodName import io.realm.buildtransformer.ByteCodeTypeDescriptor +import io.realm.buildtransformer.FieldName import io.realm.buildtransformer.logger +import org.objectweb.asm.* import org.objectweb.asm.AnnotationVisitor -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes /** * ClassVisitor that gather all classes and methods with the given annotation. This is the first @@ -32,12 +31,16 @@ class AnnotationVisitor(private val annotationDescriptor: String) : ClassVisitor val annotatedClasses: MutableSet = mutableSetOf() val annotatedMethods: MutableMap> = mutableMapOf() + val annotatedFields: MutableMap> = mutableMapOf() + private var internalQualifiedName: String = "" private val annotatedMethodsInClass = mutableSetOf() + private val annotatedFieldsInClass = mutableSetOf() override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array?) { internalQualifiedName = name!! annotatedMethods[internalQualifiedName] = annotatedMethodsInClass + annotatedFields[internalQualifiedName] = annotatedFieldsInClass super.visit(version, access, name, signature, superName, interfaces) } @@ -60,6 +63,17 @@ class AnnotationVisitor(private val annotationDescriptor: String) : ClassVisitor } } + override fun visitField(access: Int, name: String?, descriptor: String?, signature: String?, value: Any?): FieldVisitor { + return object: FieldVisitor(api) { + override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { + if (descriptor == annotationDescriptor) { + annotatedFieldsInClass.add(name!!) + } + return super.visitAnnotation(descriptor, visible) + } + } + } + override fun visitEnd() { annotatedMethods[internalQualifiedName] = annotatedMethodsInClass super.visitEnd() diff --git a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestFields.java b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestFields.java index 3020f7fbd0..b13f4cb492 100644 --- a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestFields.java +++ b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestFields.java @@ -16,10 +16,13 @@ package io.realm.buildtransformer.testclasses; import io.realm.internal.annotations.ObjectServer; +import io.realm.internal.annotations.CustomAnnotation; public class SimpleTestFields { @ObjectServer public String field1; + + @CustomAnnotation // Annotations must be written back as well public String field2; } diff --git a/library-build-transformer/src/test/java/io/realm/internal/annotations/CustomAnnotation.java b/library-build-transformer/src/test/java/io/realm/internal/annotations/CustomAnnotation.java new file mode 100644 index 0000000000..c8014e0c4a --- /dev/null +++ b/library-build-transformer/src/test/java/io/realm/internal/annotations/CustomAnnotation.java @@ -0,0 +1,11 @@ +package io.realm.internal.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +public @interface CustomAnnotation { +} diff --git a/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt b/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt index bd74b33d03..e0f8d25278 100644 --- a/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt +++ b/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt @@ -16,14 +16,15 @@ package io.realm.buildtransformer import io.realm.buildtransformer.asm.ClassPoolTransformer +import io.realm.buildtransformer.ext.packageHierarchyRootDir +import io.realm.buildtransformer.ext.shouldBeDeleted import io.realm.buildtransformer.testclasses.* +import io.realm.internal.annotations.CustomAnnotation +import org.junit.Assert.* import org.junit.Before import org.junit.Test import java.io.File import kotlin.reflect.KClass -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlin.test.fail class VisitorTests { @@ -41,6 +42,7 @@ class VisitorTests { assetDefaultConstructorExists(c) assertFieldExists("field2", c) assertFieldRemoved("field1", c) + assertTrue(c.getField("field2").isAnnotationPresent(CustomAnnotation::class.java)) } @Test @@ -86,7 +88,7 @@ class VisitorTests { ).forEach { inputClasses.add(getClassFile(it)) } val transformer = ClassPoolTransformer(qualifiedAnnotationName, inputClasses) val outputFiles: Set = transformer.transform() - assertEquals(1, outputFiles.size) // Only top level file is saved. + assertEquals(1, outputFiles.filter { !it.shouldBeDeleted }.size) // Only top level file is saved. assertTrue(outputFiles.first().name.endsWith("NestedTestClass.class")) } @@ -97,7 +99,7 @@ class VisitorTests { private fun assertFieldRemoved(fieldName: String, clazz: Class<*>) { try { clazz.getField(fieldName) - fail("Field $fieldName has not been removed"); + fail("Field $fieldName has not been removed") } catch (e: NoSuchFieldException) { } } @@ -126,7 +128,7 @@ class VisitorTests { val inputClasses: MutableSet = mutableSetOf() pool.forEach { inputClasses.add(getClassFile(it)) } val transformer = ClassPoolTransformer(qualifiedAnnotationName, inputClasses) - val outputFiles: Set = transformer.transform() + val outputFiles: Set = transformer.transform().filter { !it.shouldBeDeleted }.toSet() @Suppress("UNCHECKED_CAST") return classLoader.loadClass(clazz.java.name, outputFiles) as Class } @@ -137,6 +139,10 @@ class VisitorTests { private fun getClassFile(clazz: Class<*>): File { val filePath = "${clazz.name.replace(".", "/")}.class" - return File(classLoader.getResource(filePath).file) + val file = File(classLoader.getResource(filePath).file) + // Extension properties must be initialized before they can be read + file.shouldBeDeleted = false + file.packageHierarchyRootDir = "" + return file } } diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt index eebd34cf20..e15e68fe5b 100644 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt @@ -19,14 +19,12 @@ import org.junit.runner.RunWith class KotlinRealmTests { @Suppress("MemberVisibilityCanPrivate") - @get:Rule - val configFactory = TestRealmConfigurationFactory() + @Rule @JvmField val configFactory = TestRealmConfigurationFactory() private lateinit var realm: Realm @Before fun setUp() { - Realm.init(InstrumentationRegistry.getTargetContext()) realm = Realm.getInstance(configFactory.createConfiguration()) } diff --git a/realm/realm-library/gradle.properties b/realm/realm-library/gradle.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java index ed671e2e0b..ad2fdd9539 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java @@ -24,11 +24,9 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; -import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import io.realm.RealmConfiguration; -import io.realm.SyncTestUtils; import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.rule.TestRealmConfigurationFactory; @@ -46,16 +44,14 @@ public class OsObjectStoreTests { @Rule public final ExpectedException thrown = ExpectedException.none(); - @Before - public void setUp() throws IOException { - SyncTestUtils.prepareEnvironmentForTest(); + public void setUp() { RealmLog.setLevel(LogLevel.ERROR); } @After public void tearDown() { - SyncTestUtils.restoreEnvironmentAfterTest(); + RealmLog.setLevel(LogLevel.WARN); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java index 9d8a431a99..e9333fb789 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java @@ -16,7 +16,10 @@ */ package io.realm.internal.android; -import android.test.AndroidTestCase; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; import java.text.ParseException; import java.util.Calendar; @@ -26,9 +29,17 @@ import io.realm.exceptions.RealmException; -public class JsonUtilsTest extends AndroidTestCase { +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class JsonUtilsTest { - public void testParseNullAndEmptyDateIsNull() { + @Test + public void parseNullAndEmptyDateIsNull() { Date output = JsonUtils.stringToDate(null); assertNull("Null input should output a null date object", output); @@ -36,7 +47,8 @@ public void testParseNullAndEmptyDateIsNull() { assertNull("Empty string input should output a null date object", output); } - public void testParseMillisToDate() { + @Test + public void parseMillisToDate() { Date originalDate = Calendar.getInstance().getTime(); long dateTimeInMillis = originalDate.getTime(); Date output = JsonUtils.stringToDate(String.valueOf(dateTimeInMillis)); @@ -44,21 +56,24 @@ public void testParseMillisToDate() { assertTrue("Dates should match", output.equals(originalDate)); } - public void testParseJsonDateToDate() { + @Test + public void parseJsonDateToDate() { String jsonDate = "/Date(1198908717056)/"; // 2007-12-27T23:11:57.056 Date output = JsonUtils.stringToDate(jsonDate); assertEquals(1198908717056L, output.getTime()); } - public void testNegativeLongDate() { + @Test + public void negativeLongDate() { long timeInMillis = -631152000L; // Jan 1, 1950 Date output = JsonUtils.stringToDate(String.valueOf(timeInMillis)); assertEquals("Should be Jan 1, 1950 in millis", timeInMillis, output.getTime()); } - public void testParseInvalidDateShouldThrowRealmException() { + @Test + public void parseInvalidDateShouldThrowRealmException() { String invalidLongDate = "123abc"; try { Date d = JsonUtils.stringToDate(invalidLongDate); @@ -69,7 +84,8 @@ public void testParseInvalidDateShouldThrowRealmException() { } } - public void testParseInvalidNumericDateShouldThrowRealmException() { + @Test + public void parseInvalidNumericDateShouldThrowRealmException() { String invalidLongDate = "2342347289374398342759873495743"; // not a date. try { Date d = JsonUtils.stringToDate(invalidLongDate); @@ -80,7 +96,8 @@ public void testParseInvalidNumericDateShouldThrowRealmException() { } } - public void testParseISO8601Dates() throws ParseException { + @Test + public void parseISO8601Dates() throws ParseException { Calendar cal = new GregorianCalendar(2007, 8 - 1, 13, 19, 51, 23); cal.setTimeZone(TimeZone.getTimeZone("GMT")); cal.set(Calendar.MILLISECOND, 789); From 768d6f49924e977c7b210ec18120b1c5233242e1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 21 Sep 2018 13:39:39 +0200 Subject: [PATCH 1307/2110] Fix OJO upload by bumping plugin dependencies. (#6175) --- dependencies.list | 3 +++ examples/build.gradle | 5 +++-- gradle-plugin/build.gradle | 15 ++++++++------- library-build-transformer/build.gradle | 6 ++++-- realm-annotations/build.gradle | 7 +++++-- realm-transformer/build.gradle | 7 +++++-- realm/build.gradle | 7 +++---- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/dependencies.list b/dependencies.list index e3cf808ef9..9a517e46ef 100644 --- a/dependencies.list +++ b/dependencies.list @@ -11,3 +11,6 @@ REALM_OBJECT_SERVER_VERSION=3.9.9 GRADLE_BUILD_TOOLS=3.1.4 ANDROID_BUILD_TOOLS=27.0.3 +# Common classpath dependencies +BUILD_INFO_EXTRACTOR_GRADLE=4.7.5 +GRADLE_BINTRAY_PLUGIN=1.8.4 diff --git a/examples/build.gradle b/examples/build.gradle index 9e9f47be35..79fefbdc2d 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -23,6 +23,7 @@ allprojects { def props = new Properties() props.load(new FileInputStream("${rootDir}/../realm.properties")) + props.load(new FileInputStream("${rootDir}/../dependencies.list")) props.each { key, val -> project.ext.set(key, val) } @@ -35,8 +36,8 @@ allprojects { maven { url 'https://jitpack.io' } } dependencies { - classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath "com.android.tools.build:gradle:${props.get("GRADLE_BUILD_TOOLS")}" + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:${props.get('GRADLE_BINTRAY_PLUGIN')}" classpath "io.realm:realm-gradle-plugin:${currentVersion}" } } diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 6737f0d1f4..bd154d1785 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -1,10 +1,13 @@ buildscript { + def properties = new Properties() + properties.load(new FileInputStream("${rootDir}/../dependencies.list")) + repositories { jcenter() } dependencies { - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.2' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath "org.jfrog.buildinfo:build-info-extractor-gradle:${properties.get('BUILD_INFO_EXTRACTOR_GRADLE')}" + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:${properties.get('GRADLE_BINTRAY_PLUGIN')}" } } @@ -16,13 +19,11 @@ apply plugin: 'com.jfrog.bintray' def props = new Properties() props.load(new FileInputStream("${rootDir}/../realm.properties")) +props.load(new FileInputStream("${rootDir}/../dependencies.list")) props.each { key, val -> project.ext.set(key, val) } -def projectDependencies = new Properties() -projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) - repositories { mavenLocal() google() @@ -55,11 +56,11 @@ dependencies { and this https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ for more info. */ compile 'com.neenbedankt.gradle.plugins:android-apt:1.8' //TODO: https://www.littlerobots.nl/blog/Whats-next-for-android-apt/ - compileOnly "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" + compileOnly "com.android.tools.build:gradle:${props.get("GRADLE_BUILD_TOOLS")}" testCompile gradleTestKit() testCompile 'junit:junit:4.12' - testCompile "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" + testCompile "com.android.tools.build:gradle:${props.get("GRADLE_BUILD_TOOLS")}" } //for Ant filter diff --git a/library-build-transformer/build.gradle b/library-build-transformer/build.gradle index 17a3d9a4bc..c744d5219c 100644 --- a/library-build-transformer/build.gradle +++ b/library-build-transformer/build.gradle @@ -2,6 +2,8 @@ group 'io.realm' version '1.0.0' buildscript { + def properties = new Properties() + properties.load(new FileInputStream("${projectDir}/../dependencies.list")) ext.kotlin_version = '1.2.61' repositories { @@ -9,8 +11,8 @@ buildscript { jcenter() } dependencies { - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.2' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath "org.jfrog.buildinfo:build-info-extractor-gradle:${properties.get('BUILD_INFO_EXTRACTOR_GRADLE')}" + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:${properties.get('GRADLE_BINTRAY_PLUGIN')}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index 43628a8f8a..f785066656 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -1,10 +1,13 @@ buildscript { + def properties = new Properties() + properties.load(new FileInputStream("${projectDir}/../dependencies.list")) + repositories { jcenter() } dependencies { - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.2' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath "org.jfrog.buildinfo:build-info-extractor-gradle:${properties.get('BUILD_INFO_EXTRACTOR_GRADLE')}" + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:${properties.get('GRADLE_BINTRAY_PLUGIN')}" } } diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 35b575198a..c0fc6bc89b 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -1,12 +1,15 @@ buildscript { + def properties = new Properties() + properties.load(new FileInputStream("${projectDir}/../dependencies.list")) + ext.kotlin_version = '1.2.50' repositories { google() jcenter() } dependencies { - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.2' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath "org.jfrog.buildinfo:build-info-extractor-gradle:${properties.get('BUILD_INFO_EXTRACTOR_GRADLE')}" + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:${properties.get('GRADLE_BINTRAY_PLUGIN')}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/realm/build.gradle b/realm/build.gradle index 9c4ab0164d..53ffc2de3a 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,7 +1,6 @@ buildscript { def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) - ext.gradle_build_tools = projectDependencies.get("GRADLE_BUILD_TOOLS") ext.kotlin_version = '1.2.50' ext.dokka_version = '0.9.16' repositories { @@ -13,14 +12,14 @@ buildscript { } dependencies { - classpath "com.android.tools.build:gradle:${gradle_build_tools}" + classpath "com.android.tools.build:gradle:${projectDependencies.get('GRADLE_BUILD_TOOLS')}" classpath 'de.undercouch:gradle-download-task:3.3.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.4' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2' - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.5.4' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath "org.jfrog.buildinfo:build-info-extractor-gradle:${projectDependencies.get('BUILD_INFO_EXTRACTOR_GRADLE')}" + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:${projectDependencies.get('GRADLE_BINTRAY_PLUGIN')}" classpath "io.realm:realm-transformer:${file('../version.txt').text.trim()}" classpath "io.realm:realm-library-build-transformer:${file('../version.txt').text.trim()}" classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.13' From e3cb81cf5707e5fc8ad1bd83729bc80f901ebde4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 12:10:51 +0200 Subject: [PATCH 1308/2110] Support LIMIT (#6126) --- .gitignore | 1 + CHANGELOG.md | 10 ++ .../java/io/realm/NotificationsTest.java | 110 +++++++++++++++++ .../java/io/realm/RealmQueryTests.java | 96 ++++++++++++++- .../androidTest/java/io/realm/SortTest.java | 4 +- .../io/realm/internal/OsResultsTests.java | 16 ++- ...orTests.java => QueryDescriptorTests.java} | 41 ++++--- .../realm-library/src/main/cpp/CMakeLists.txt | 1 + .../main/cpp/io_realm_internal_OsResults.cpp | 20 ++- ...realm_internal_core_DescriptorOrdering.cpp | 101 ++++++++++++++++ ...scriptor.cpp => java_query_descriptor.cpp} | 16 +-- ...scriptor.hpp => java_query_descriptor.hpp} | 27 +++-- realm/realm-library/src/main/cpp/object-store | 2 +- .../io/realm/OrderedRealmCollectionImpl.java | 14 +-- .../src/main/java/io/realm/RealmQuery.java | 63 ++++++---- .../io/realm/internal/ObjectServerFacade.java | 4 - .../java/io/realm/internal/OsResults.java | 26 ++-- .../java/io/realm/internal/PendingRow.java | 5 +- .../internal/SubscriptionAwareOsResults.java | 6 +- .../internal/core/DescriptorOrdering.java | 114 ++++++++++++++++++ .../QueryDescriptor.java} | 47 ++++---- .../io/realm/internal/core/package-info.java | 18 +++ .../io/realm/SyncedRealmIntegrationTests.java | 2 +- .../objectserver/QueryBasedSyncTests.java | 40 ++++++ 24 files changed, 640 insertions(+), 144 deletions(-) rename realm/realm-library/src/androidTest/java/io/realm/internal/{SortDescriptorTests.java => QueryDescriptorTests.java} (82%) create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp rename realm/realm-library/src/main/cpp/{java_sort_descriptor.cpp => java_query_descriptor.cpp} (82%) rename realm/realm-library/src/main/cpp/{java_sort_descriptor.hpp => java_query_descriptor.hpp} (64%) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java rename realm/realm-library/src/main/java/io/realm/internal/{SortDescriptor.java => core/QueryDescriptor.java} (70%) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/core/package-info.java diff --git a/.gitignore b/.gitignore index 483d1a7ced..2e810308be 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ local.properties core core-* realm-sync-android-* +!realm/realm-library/src/main/java/io/realm/internal/core/ # Android Studio .idea diff --git a/CHANGELOG.md b/CHANGELOG.md index ade6fbc4ff..0ed1f010f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,18 @@ ## 5.6.0 (YYYY-MM-DD) +### Breaking changes + +* When building a `RealmQuery`, `sort()`, `distinct()` and `limit()` will now be applied in the order they are called. Before + this release, `sort()` and `distinct()` could be called any order, but `sort()` would always be applied before `distinct()`. + ### Enhancements * `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom name mappings (#6145). +* Added support for `RealmQuery.limit(long limit)` (#544). + +### Internal + +* Updated to Object Store commit: 7e19c51af72c3343b453b8a13c82dfda148e4bbc ## 5.5.1 (YYYY-MM-DD) diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 9c0ed01755..fe5e347d47 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -28,6 +28,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,6 +46,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.Dog; +import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.log.RealmLogger; import io.realm.rule.RunInLooperThread; @@ -988,4 +990,112 @@ public void onChange(RealmModel element) { } catch (IllegalStateException ignored) { } } + + // Checks that we can attach change listeners to queries involving `limit()` and that + // they do the right thing + @Test + @RunTestInLooperThread + public void limitedQueryResult_fromTable_finegrainedListener() { + realm = looperThread.getRealm(); + realm.executeTransaction(r -> { + for (int i = 0; i < 5; i++) { + r.createObject(AllTypes.class).setColumnLong(i % 5); + } + }); + RealmResults results = realm.where(AllTypes.class) + .sort(AllTypes.FIELD_LONG, Sort.DESCENDING) // [4, 4, 3, 3, 2, 2, 1, 1, 0, 0] + .distinct(AllTypes.FIELD_LONG) // [4, 3, 2, 1, 0] + .limit(2) // [4, 3] + .findAll(); + looperThread.keepStrongReference(results); + results.addChangeListener((objects, changeSet) -> { + assertEquals(2, objects.size()); + assertEquals(5, objects.first().getColumnLong()); + assertEquals(4, objects.last().getColumnLong()); + assertEquals(1, changeSet.getInsertions().length); + assertEquals(0, changeSet.getInsertions()[0]); + assertEquals(1, changeSet.getDeletions().length); + assertEquals(1, changeSet.getDeletions()[0]); + assertEquals(0, changeSet.getChanges().length); + looperThread.testComplete(); + }); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class).setColumnLong(5); + } + }); + } + + @Test + @RunTestInLooperThread + public void limitedQueryResult_fromTable_finegrainedListener_withModifications() { + realm = looperThread.getRealm(); + realm.executeTransaction(r -> { + for (int i = 0; i < 5; i++) { + r.createObject(AllTypes.class).setColumnLong(i % 5); + } + }); + RealmResults results = realm.where(AllTypes.class) + .sort(AllTypes.FIELD_LONG, Sort.DESCENDING) // [4, 4, 3, 3, 2, 2, 1, 1, 0, 0] + .distinct(AllTypes.FIELD_LONG) // [4, 3, 2, 1, 0] + .limit(2) // [4, 3] + .findAll(); + looperThread.keepStrongReference(results); + results.addChangeListener((objects, changeSet) -> { + assertEquals(2, objects.size()); + assertEquals(6, objects.first().getColumnLong()); + assertEquals(5, objects.last().getColumnLong()); + assertEquals(1, changeSet.getInsertions().length); + assertEquals(0, changeSet.getInsertions()[0]); + assertEquals(1, changeSet.getDeletions().length); + assertEquals(1, changeSet.getDeletions()[0]); + assertEquals(1, changeSet.getChanges().length); + assertEquals(1, changeSet.getChanges()[0]); + looperThread.testComplete(); + }); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class).setColumnLong(6); + for (AllTypes obj : realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 4).findAll()) { + obj.setColumnLong(5); + } + } + }); + } + + // Checks that we can attach change listeners to queries involving `limit()` and that + // they do the right thing + @Test + @RunTestInLooperThread + public void limitedQueryResult_fromTable_simpleChangeListener() { + realm = looperThread.getRealm(); + realm.executeTransaction(r -> { + for (int i = 0; i < 5; i++) { + r.createObject(AllTypes.class).setColumnLong(i % 5); + } + }); + RealmResults results = realm.where(AllTypes.class) + .sort(AllTypes.FIELD_LONG, Sort.DESCENDING) // [4, 4, 3, 3, 2, 2, 1, 1, 0, 0] + .distinct(AllTypes.FIELD_LONG) // [4, 3, 2, 1, 0] + .limit(2) // [4, 3] + .findAll(); + looperThread.keepStrongReference(results); + results.addChangeListener((objects) -> { + assertEquals(2, objects.size()); + assertEquals(5, objects.first().getColumnLong()); + assertEquals(4, objects.last().getColumnLong()); + looperThread.testComplete(); + }); + + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.createObject(AllTypes.class).setColumnLong(5); + } + }); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 1601b59ea7..e0bf51bfbc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -24,7 +24,6 @@ import java.lang.reflect.Field; import java.util.Date; import java.util.Locale; -import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -3518,4 +3517,99 @@ public void getRealm_throwsIfRealmClosed() { } catch (IllegalStateException ignore) { } } + + @Test + public void limit() { + populateTestRealm(realm, TEST_DATA_SIZE); + RealmResults results = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).limit(5).findAll(); + assertEquals(5, results.size()); + for (int i = 0; i < 5; i++) { + assertEquals(i, results.get(i).getColumnLong()); + } + } + + @Test + public void limit_withSortAndDistinct() { + // The order of operators matter when using limit() + // If applying sort/distinct without limit, any order will result in the same query result. + + realm.beginTransaction(); + RealmList list = realm.createObject(AllJavaTypes.class, -1).getFieldList(); // Root object; + for (int i = 0; i < 5; i++) { + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, i); + obj.setFieldLong(i); + list.add(obj); + } + realm.commitTransaction(); + + RealmResults results = list.where() + .sort(AllJavaTypes.FIELD_LONG, Sort.DESCENDING) // [4, 4, 3, 3, 2, 2, 1, 1, 0, 0] + .distinct(AllJavaTypes.FIELD_LONG) // [4, 3, 2, 1, 0] + .limit(2) // [4, 3] + .findAll(); + assertEquals(2, results.size()); + assertEquals(4, results.first().getFieldLong()); + assertEquals(3, results.last().getFieldLong()); + + results = list.where() + .limit(2) // [0, 1] + .distinct(AllJavaTypes.FIELD_LONG) // [ 0, 1] + .sort(AllJavaTypes.FIELD_LONG, Sort.DESCENDING) // [1, 0] + .findAll(); + assertEquals(2, results.size()); + assertEquals(1, results.first().getFieldLong()); + assertEquals(0, results.last().getFieldLong()); + + results = list.where() + .distinct(AllJavaTypes.FIELD_LONG) // [ 0, 1, 2, 3, 4] + .limit(2) // [0, 1] + .sort(AllJavaTypes.FIELD_LONG, Sort.DESCENDING) // [1, 0] + .findAll(); + assertEquals(2, results.size()); + assertEquals(1, results.first().getFieldLong()); + assertEquals(0, results.last().getFieldLong()); + } + + // Checks that https://github.com/realm/realm-object-store/pull/679/files#diff-c0354faf99b53cc5d3c9e6a58ed9ae85R610 + // Do not apply to Realm Java as we do not lazy-execute queries. + @Test + public void limit_asSubQuery() { + realm.executeTransaction(r -> { + for (int i = 0; i < 10; i++) { + r.createObject(AllTypes.class).setColumnLong(i % 5); + } + }); + + RealmResults results = realm.where(AllTypes.class) + .sort(AllTypes.FIELD_LONG, Sort.DESCENDING) + .findAll() // [4, 4, 3, 3, 2, 2, 1, 1, 0, 0] + .where() + .distinct(AllTypes.FIELD_LONG) + .findAll() // [4, 3, 2, 1, 0] + .where() + .limit(2) // [4, 3] + .findAll(); + assertEquals(2, results.size()); + assertEquals(4, results.first().getColumnLong()); + assertEquals(3, results.last().getColumnLong()); + } + + @Test + public void limit_invalidValuesThrows() { + RealmQuery query = realm.where(AllTypes.class); + + try { + query.limit(0); + fail(); + } catch (IllegalArgumentException ignored) { + } + + try { + query.limit(-1); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + } diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 6f41a48b9b..45b23de504 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -585,7 +585,7 @@ public void sortAndDistinctMixed() { // (2, 1, "B") // (3, 1, "C") // Depending on the sorting, distinct should pick the first element encountered. - // The order of sort/distinct in the query should not matter + // The order of sort/distinct in the query matters // Case 1: Selecting highest numbers RealmResults results1a = realm.where(AnnotationIndexTypes.class) @@ -600,7 +600,7 @@ public void sortAndDistinctMixed() { .sort(AnnotationIndexTypes.FIELD_INDEX_LONG, Sort.DESCENDING) .findAll(); assertEquals(1, results1b.size()); - assertEquals(3, results1b.get(0).getIndexLong()); + assertEquals(1, results1b.get(0).getIndexLong()); // Case 1: Selecting lowest number numbers RealmResults results2a = realm.where(AnnotationIndexTypes.class) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java index de67a7e5df..31eb7ec756 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java @@ -36,6 +36,8 @@ import io.realm.RealmConfiguration; import io.realm.RealmFieldType; import io.realm.TestHelper; +import io.realm.internal.core.DescriptorOrdering; +import io.realm.internal.core.QueryDescriptor; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; @@ -151,8 +153,9 @@ private void addRow(OsSharedRealm sharedRealm) { @Test public void constructor_withDistinct() { - SortDescriptor distinctDescriptor = SortDescriptor.getInstanceForDistinct(null, table, "firstName"); - OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where(), null, distinctDescriptor); + DescriptorOrdering queryDescriptors = new DescriptorOrdering(); + queryDescriptors.appendDistinct(QueryDescriptor.getInstanceForDistinct(null, table, "firstName")); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where(), queryDescriptors); assertEquals(3, osResults.size()); assertEquals("John", osResults.getUncheckedRow(0).getString(0)); @@ -202,7 +205,7 @@ public void where() { @Test public void sort() { OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where().greaterThan(new long[] {2}, oneNullTable, 1)); - SortDescriptor sortDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); + QueryDescriptor sortDescriptor = QueryDescriptor.getTestInstance(table, new long[] {2}); OsResults osResults2 = osResults.sort(sortDescriptor); @@ -234,9 +237,10 @@ public void contains() { @Test public void indexOf() { - SortDescriptor sortDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); + DescriptorOrdering queryDescriptors = new DescriptorOrdering(); + queryDescriptors.appendSort(QueryDescriptor.getTestInstance(table, new long[] {2})); - OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where(), sortDescriptor, null); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where(), queryDescriptors); UncheckedRow row = table.getUncheckedRow(0); assertEquals(3, osResults.indexOf(row)); } @@ -245,7 +249,7 @@ public void indexOf() { public void distinct() { OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where().lessThan(new long[] {2}, oneNullTable, 4)); - SortDescriptor distinctDescriptor = SortDescriptor.getTestInstance(table, new long[] {2}); + QueryDescriptor distinctDescriptor = QueryDescriptor.getTestInstance(table, new long[] {2}); OsResults osResults2 = osResults.distinct(distinctDescriptor); // A new native Results should be created. diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java similarity index 82% rename from realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java rename to realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java index 6a762423c7..2a15fee6b8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/SortDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java @@ -31,6 +31,7 @@ import io.realm.RealmConfiguration; import io.realm.RealmFieldType; import io.realm.Sort; +import io.realm.internal.core.QueryDescriptor; import io.realm.rule.TestRealmConfigurationFactory; import static junit.framework.Assert.assertEquals; @@ -41,7 +42,7 @@ @RunWith(AndroidJUnit4.class) -public class SortDescriptorTests { +public class QueryDescriptorTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule @@ -65,14 +66,14 @@ public void tearDown() { @Test public void getInstanceForDistinct() { - for (RealmFieldType type : SortDescriptor.DISTINCT_VALID_FIELD_TYPES) { + for (RealmFieldType type : QueryDescriptor.DISTINCT_VALID_FIELD_TYPES) { long column = table.addColumn(type, type.name()); table.addSearchIndex(column); } long i = 0; - for (RealmFieldType type : SortDescriptor.DISTINCT_VALID_FIELD_TYPES) { - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(null, table, type.name()); + for (RealmFieldType type : QueryDescriptor.DISTINCT_VALID_FIELD_TYPES) { + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForDistinct(null, table, type.name()); assertEquals(1, sortDescriptor.getColumnIndices()[0].length); assertEquals(i, sortDescriptor.getColumnIndices()[0][0]); assertNull(sortDescriptor.getAscendings()); @@ -90,13 +91,13 @@ public void getInstanceForDistinct_shouldThrowOnLinkAndListListField() { table.addColumnLink(listType, listType.name(), table); try { - SortDescriptor.getInstanceForDistinct(null, table, String.format("%s.%s", listType.name(), type.name())); + QueryDescriptor.getInstanceForDistinct(null, table, String.format("%s.%s", listType.name(), type.name())); fail(); } catch (IllegalArgumentException ignored) { } try { - SortDescriptor.getInstanceForDistinct(null, table, String.format("%s.%s", objectType.name(), type.name())); + QueryDescriptor.getInstanceForDistinct(null, table, String.format("%s.%s", objectType.name(), type.name())); fail(); } catch (IllegalArgumentException ignored) { } @@ -111,7 +112,7 @@ public void getInstanceForDistinct_multipleFields() { long intColumn = table.addColumn(intType, intType.name()); table.addSearchIndex(intColumn); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForDistinct(null, table, new String[] { + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForDistinct(null, table, new String[] { stringType.name(), intType.name()}); assertEquals(2, sortDescriptor.getColumnIndices().length); assertNull(sortDescriptor.getAscendings()); @@ -123,11 +124,11 @@ public void getInstanceForDistinct_multipleFields() { @Test public void getInstanceForDistinct_shouldThrowOnInvalidField() { - Set types = getValidFieldTypes(SortDescriptor.DISTINCT_VALID_FIELD_TYPES); + Set types = getValidFieldTypes(QueryDescriptor.DISTINCT_VALID_FIELD_TYPES); for (RealmFieldType type : types) { try { - SortDescriptor.getInstanceForDistinct(null, table, type.name()); + QueryDescriptor.getInstanceForDistinct(null, table, type.name()); fail(); } catch (IllegalArgumentException ignored) { assertTrue(ignored.getMessage().contains("Distinct is not supported")); @@ -137,13 +138,13 @@ public void getInstanceForDistinct_shouldThrowOnInvalidField() { @Test public void getInstanceForSort() { - for (RealmFieldType type : SortDescriptor.SORT_VALID_FIELD_TYPES) { + for (RealmFieldType type : QueryDescriptor.SORT_VALID_FIELD_TYPES) { table.addColumn(type, type.name()); } long i = 0; - for (RealmFieldType type : SortDescriptor.SORT_VALID_FIELD_TYPES) { - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(null, table, type.name(), Sort.DESCENDING); + for (RealmFieldType type : QueryDescriptor.SORT_VALID_FIELD_TYPES) { + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(null, table, type.name(), Sort.DESCENDING); assertEquals(1, sortDescriptor.getColumnIndices()[0].length); assertEquals(i, sortDescriptor.getColumnIndices()[0][0]); assertFalse(sortDescriptor.getAscendings()[0]); @@ -153,7 +154,7 @@ public void getInstanceForSort() { @Test public void getInstanceForSort_linkField() { - for (RealmFieldType type : SortDescriptor.DISTINCT_VALID_FIELD_TYPES) { + for (RealmFieldType type : QueryDescriptor.DISTINCT_VALID_FIELD_TYPES) { long column = table.addColumn(type, type.name()); table.addSearchIndex(column); } @@ -161,8 +162,8 @@ public void getInstanceForSort_linkField() { long columnLink = table.addColumnLink(objectType, objectType.name(), table); long i = 0; - for (RealmFieldType type : SortDescriptor.DISTINCT_VALID_FIELD_TYPES) { - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(null, table, + for (RealmFieldType type : QueryDescriptor.DISTINCT_VALID_FIELD_TYPES) { + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(null, table, String.format("%s.%s", objectType.name(), type.name()), Sort.ASCENDING); assertEquals(2, sortDescriptor.getColumnIndices()[0].length); assertEquals(columnLink, sortDescriptor.getColumnIndices()[0][0]); @@ -179,7 +180,7 @@ public void getInstanceForSort_multipleFields() { RealmFieldType intType = RealmFieldType.INTEGER; long intColumn = table.addColumn(intType, intType.name()); - SortDescriptor sortDescriptor = SortDescriptor.getInstanceForSort(null, table, new String[] { + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(null, table, new String[] { stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING, Sort.DESCENDING}); assertEquals(2, sortDescriptor.getAscendings().length); @@ -204,18 +205,18 @@ public void getInstanceForSort_numOfFeildsAndSortOrdersNotMatch() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Number of fields and sort orders do not match."); - SortDescriptor.getInstanceForSort(null, table, + QueryDescriptor.getInstanceForSort(null, table, new String[] {stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING}); } @Test public void getInstanceForSort_shouldThrowOnInvalidField() { - Set types = getValidFieldTypes(SortDescriptor.SORT_VALID_FIELD_TYPES); + Set types = getValidFieldTypes(QueryDescriptor.SORT_VALID_FIELD_TYPES); for (RealmFieldType type : types) { try { - SortDescriptor.getInstanceForSort(null, table, type.name(), Sort.ASCENDING); + QueryDescriptor.getInstanceForSort(null, table, type.name(), Sort.ASCENDING); fail(); } catch (IllegalArgumentException ignored) { assertTrue(ignored.getMessage().contains("Sort is not supported")); @@ -232,7 +233,7 @@ public void getInstanceForSort_shouldThrowOnLinkListField() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Invalid query: field 'LIST' in class 'test_table' is of invalid type 'LIST'."); - SortDescriptor.getInstanceForSort(null, table, String.format("%s.%s", listType.name(), type.name()), Sort.ASCENDING); + QueryDescriptor.getInstanceForSort(null, table, String.format("%s.%s", listType.name(), type.name()), Sort.ASCENDING); } private Set getValidFieldTypes(Set filter) { diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index aa67d94199..5430185427 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -64,6 +64,7 @@ set(classes_LIST io.realm.internal.NativeObjectReference io.realm.internal.OsCollectionChangeSet io.realm.internal.OsObject io.realm.internal.OsRealmConfig io.realm.internal.OsList io.realm.internal.OsObjectStore io.realm.internal.sync.OsSubscription + io.realm.internal.core.DescriptorOrdering ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index 9c3670b388..47254d1d60 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -22,7 +22,7 @@ #include #include "java_class_global_def.hpp" -#include "java_sort_descriptor.hpp" +#include "java_query_descriptor.hpp" #include "observable_collection_wrapper.hpp" #include "util.hpp" @@ -41,9 +41,9 @@ static void finalize_results(jlong ptr) } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResults(JNIEnv* env, jclass, - jlong shared_realm_ptr, jlong query_ptr, - jobject j_sort_desc, - jobject j_distinct_desc) + jlong shared_realm_ptr, + jlong query_ptr, + jlong descriptor_ordering_ptr) { TR_ENTER() try { @@ -53,13 +53,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResults(JNI } auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - DescriptorOrdering descriptor_ordering; - if (j_sort_desc) { - descriptor_ordering.append_sort(JavaSortDescriptor(env, j_sort_desc).sort_descriptor()); - } - if (j_distinct_desc) { - descriptor_ordering.append_distinct(JavaSortDescriptor(env, j_distinct_desc).distinct_descriptor()); - } + auto descriptor_ordering = *(reinterpret_cast(descriptor_ordering_ptr)); Results results(shared_realm, *query, descriptor_ordering); auto wrapper = new ResultsWrapper(results); @@ -219,7 +213,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeSort(JNIEnv* env, TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto sorted_result = wrapper->collection().sort(JavaSortDescriptor(env, j_sort_desc).sort_descriptor()); + auto sorted_result = wrapper->collection().sort(JavaQueryDescriptor(env, j_sort_desc).sort_descriptor()); return reinterpret_cast(new ResultsWrapper(sorted_result)); } CATCH_STD() @@ -233,7 +227,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeDistinct(JNIEnv* try { auto wrapper = reinterpret_cast(native_ptr); auto distinct_result = - wrapper->collection().distinct(JavaSortDescriptor(env, j_distinct_desc).distinct_descriptor()); + wrapper->collection().distinct(JavaQueryDescriptor(env, j_distinct_desc).distinct_descriptor()); return reinterpret_cast(new ResultsWrapper(distinct_result)); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp new file mode 100644 index 0000000000..062074fd93 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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 "io_realm_internal_core_DescriptorOrdering.h" + +#include + +#include "java_query_descriptor.hpp" +#include "util.hpp" + +using namespace realm; +using namespace realm::util; +using namespace realm::_impl; + +static void finalize_descriptor(jlong ptr); +static void finalize_descriptor(jlong ptr) +{ + TR_ENTER_PTR(ptr) + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) +{ + TR_ENTER() + return reinterpret_cast(&finalize_descriptor); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeCreate(JNIEnv* env, jclass) +{ + TR_ENTER() + try { + return reinterpret_cast(new DescriptorOrdering()); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppendSort(JNIEnv* env, jclass, + jlong descriptor_ptr, + jobject j_sort_descriptor) +{ + TR_ENTER() + try { + auto descriptor = reinterpret_cast(descriptor_ptr); + if (j_sort_descriptor) { + descriptor->append_sort(JavaQueryDescriptor(env, j_sort_descriptor).sort_descriptor()); + } + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppendDistinct(JNIEnv* env, jclass, + jlong descriptor_ptr, + jobject j_distinct_descriptor) +{ + TR_ENTER() + try { + auto descriptor = reinterpret_cast(descriptor_ptr); + if (j_distinct_descriptor) { + descriptor->append_distinct(JavaQueryDescriptor(env, j_distinct_descriptor).distinct_descriptor()); + } + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppendLimit(JNIEnv* env, jclass, + jlong descriptor_ptr, + jlong limit) +{ + TR_ENTER() + try { + auto descriptor = reinterpret_cast(descriptor_ptr); + descriptor->append_limit(limit); + } + CATCH_STD() +} + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeIsEmpty(JNIEnv* env, jclass, + jlong descriptor_ptr) +{ + TR_ENTER() + try { + auto descriptor = reinterpret_cast(descriptor_ptr); + return descriptor->is_empty() ? JNI_TRUE : JNI_FALSE; + } + CATCH_STD() + return JNI_TRUE; +} diff --git a/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp b/realm/realm-library/src/main/cpp/java_query_descriptor.cpp similarity index 82% rename from realm/realm-library/src/main/cpp/java_sort_descriptor.cpp rename to realm/realm-library/src/main/cpp/java_query_descriptor.cpp index 90adec02ef..0c85d29728 100644 --- a/realm/realm-library/src/main/cpp/java_sort_descriptor.cpp +++ b/realm/realm-library/src/main/cpp/java_query_descriptor.cpp @@ -16,7 +16,7 @@ #include "java_accessor.hpp" -#include "java_sort_descriptor.hpp" +#include "java_query_descriptor.hpp" #include "util.hpp" #include "jni_util/java_class.hpp" #include "jni_util/java_method.hpp" @@ -25,7 +25,7 @@ using namespace realm; using namespace realm::_impl; using namespace realm::jni_util; -SortDescriptor JavaSortDescriptor::sort_descriptor() const noexcept +SortDescriptor JavaQueryDescriptor::sort_descriptor() const noexcept { if (m_sort_desc_obj == nullptr) { return SortDescriptor(); @@ -34,7 +34,7 @@ SortDescriptor JavaSortDescriptor::sort_descriptor() const noexcept return SortDescriptor(*get_table_ptr(), get_column_indices(), get_ascendings()); } -DistinctDescriptor JavaSortDescriptor::distinct_descriptor() const noexcept +DistinctDescriptor JavaQueryDescriptor::distinct_descriptor() const noexcept { if (m_sort_desc_obj == nullptr) { return DistinctDescriptor(); @@ -42,14 +42,14 @@ DistinctDescriptor JavaSortDescriptor::distinct_descriptor() const noexcept return DistinctDescriptor(*get_table_ptr(), get_column_indices()); } -Table* JavaSortDescriptor::get_table_ptr() const noexcept +Table* JavaQueryDescriptor::get_table_ptr() const noexcept { static JavaMethod get_table_ptr_method(m_env, get_sort_desc_class(), "getTablePtr", "()J"); jlong table_ptr = m_env->CallLongMethod(m_sort_desc_obj, get_table_ptr_method); return reinterpret_cast(table_ptr); } -std::vector> JavaSortDescriptor::get_column_indices() const noexcept +std::vector> JavaQueryDescriptor::get_column_indices() const noexcept { static JavaMethod get_column_indices_method(m_env, get_sort_desc_class(), "getColumnIndices", "()[[J"); jobjectArray column_indices = @@ -69,7 +69,7 @@ std::vector> JavaSortDescriptor::get_column_indices() const return indices; } -std::vector JavaSortDescriptor::get_ascendings() const noexcept +std::vector JavaQueryDescriptor::get_ascendings() const noexcept { static JavaMethod get_ascendings_method(m_env, get_sort_desc_class(), "getAscendings", "()[Z"); @@ -90,9 +90,9 @@ std::vector JavaSortDescriptor::get_ascendings() const noexcept return ascending_list; } -JavaClass const& JavaSortDescriptor::get_sort_desc_class() const noexcept +JavaClass const& JavaQueryDescriptor::get_sort_desc_class() const noexcept { - static JavaClass sort_desc_class(m_env, "io/realm/internal/SortDescriptor"); + static JavaClass sort_desc_class(m_env, "io/realm/internal/core/QueryDescriptor"); return sort_desc_class; } diff --git a/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp b/realm/realm-library/src/main/cpp/java_query_descriptor.hpp similarity index 64% rename from realm/realm-library/src/main/cpp/java_sort_descriptor.hpp rename to realm/realm-library/src/main/cpp/java_query_descriptor.hpp index 92893bdca0..077b5f92c8 100644 --- a/realm/realm-library/src/main/cpp/java_sort_descriptor.hpp +++ b/realm/realm-library/src/main/cpp/java_query_descriptor.hpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef JAVA_SORT_DESCRIPTOR_HPP -#define JAVA_SORT_DESCRIPTOR_HPP +#ifndef JAVA_QUERY_DESCRIPTOR_HPP +#define JAVA_QUERY_DESCRIPTOR_HPP #include @@ -27,23 +27,24 @@ class JavaClass; namespace _impl { -// For converting a Java SortDescriptor object to realm::SortDescriptor. +// For converting a Java QueryDescriptor object to realm::SortDescriptor or realm::DistinctDescriptor. +// // This class is not designed to be used across JNI calls. So it doesn't acquire a reference to the given Java object. -// We don't hold a pointer to the SortDescriptor in the Java object like normally we do, because the ObjectStore -// always consumes the SortDescriptor by calling the move constructor. Holding an empty SortDescriptor in Java level -// doesn't make too much sense and causes troubles with memory management. -class JavaSortDescriptor { +// We don't hold a pointer to the native Sort/DistinctDescriptor in the Java object like normally we do, because the +// ObjectStore always consumes the descriptor by calling the move constructor. Holding an empty descriptor at Java level +// thus doesn't make much sense and causes problems with memory management. +class JavaQueryDescriptor { public: - JavaSortDescriptor(JNIEnv* env, jobject sort_desc_obj) + JavaQueryDescriptor(JNIEnv* env, jobject sort_desc_obj) : m_env(env) , m_sort_desc_obj(sort_desc_obj) { } - JavaSortDescriptor(const JavaSortDescriptor&) = delete; - JavaSortDescriptor& operator=(const JavaSortDescriptor&) = delete; - JavaSortDescriptor(JavaSortDescriptor&&) = delete; - JavaSortDescriptor& operator=(JavaSortDescriptor&&) = delete; + JavaQueryDescriptor(const JavaQueryDescriptor&) = delete; + JavaQueryDescriptor& operator=(const JavaQueryDescriptor&) = delete; + JavaQueryDescriptor(JavaQueryDescriptor&&) = delete; + JavaQueryDescriptor& operator=(JavaQueryDescriptor&&) = delete; // Prevent heap allocation static void *operator new (size_t) = delete; @@ -67,4 +68,4 @@ class JavaSortDescriptor { } // namespace _impl } // namespace realm -#endif // JAVA_SORT_DESCRIPTOR_HPP +#endif // JAVA_QUERY_DESCRIPTOR_HPP diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index b0fc2814d9..7e19c51af7 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit b0fc2814d9e6061ce5ba1da887aab6cfba4755ca +Subproject commit 7e19c51af72c3343b453b8a13c82dfda148e4bbc diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index 2ce0fc42b3..715c7541c0 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -13,7 +13,7 @@ import io.realm.internal.OsResults; import io.realm.internal.InvalidRow; import io.realm.internal.RealmObjectProxy; -import io.realm.internal.SortDescriptor; +import io.realm.internal.core.QueryDescriptor; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; @@ -289,8 +289,8 @@ private long getColumnIndexForSort(String fieldName) { */ @Override public RealmResults sort(String fieldName) { - SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldName, Sort.ASCENDING); + QueryDescriptor sortDescriptor = + QueryDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldName, Sort.ASCENDING); OsResults sortedOsResults = osResults.sort(sortDescriptor); return createLoadedResults(sortedOsResults); @@ -301,8 +301,8 @@ public RealmResults sort(String fieldName) { */ @Override public RealmResults sort(String fieldName, Sort sortOrder) { - SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldName, sortOrder); + QueryDescriptor sortDescriptor = + QueryDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldName, sortOrder); OsResults sortedOsResults = osResults.sort(sortDescriptor); return createLoadedResults(sortedOsResults); @@ -313,8 +313,8 @@ public RealmResults sort(String fieldName, Sort sortOrder) { */ @Override public RealmResults sort(String fieldNames[], Sort sortOrders[]) { - SortDescriptor sortDescriptor = - SortDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldNames, sortOrders); + QueryDescriptor sortDescriptor = + QueryDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldNames, sortOrders); OsResults sortedOsResults = osResults.sort(sortDescriptor); return createLoadedResults(sortedOsResults); diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 10327254d4..d1e51d662d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -23,18 +23,18 @@ import javax.annotation.Nullable; -import io.realm.annotations.Beta; import io.realm.annotations.Required; import io.realm.internal.OsList; import io.realm.internal.OsResults; import io.realm.internal.PendingRow; +import io.realm.internal.core.QueryDescriptor; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SortDescriptor; import io.realm.internal.SubscriptionAwareOsResults; import io.realm.internal.Table; import io.realm.internal.TableQuery; import io.realm.internal.Util; +import io.realm.internal.core.DescriptorOrdering; import io.realm.internal.fields.FieldDescriptor; import io.realm.internal.sync.SubscriptionAction; @@ -67,8 +67,7 @@ public class RealmQuery { private String className; private final boolean forValues; private final OsList osList; - private SortDescriptor sortDescriptor; - private SortDescriptor distinctDescriptor; + private DescriptorOrdering queryDescriptors = new DescriptorOrdering(); private static final String TYPE_MISMATCH = "Field '%s': type mismatch - %s expected."; private static final String EMPTY_VALUES = "Non-empty 'values' must be provided."; @@ -1767,7 +1766,7 @@ public long count() { @SuppressWarnings("unchecked") public RealmResults findAll() { realm.checkIfValid(); - return createRealmResults(query, sortDescriptor, distinctDescriptor, true, SubscriptionAction.NO_SUBSCRIPTION); + return createRealmResults(query, queryDescriptors, true, SubscriptionAction.NO_SUBSCRIPTION); } /** @@ -1782,8 +1781,7 @@ private OsResults lazyFindAll() { realm.checkIfValid(); return createRealmResults( query, - sortDescriptor, - distinctDescriptor, + queryDescriptors, false, SubscriptionAction.NO_SUBSCRIPTION).osResults; } @@ -1810,7 +1808,7 @@ public RealmResults findAllAsync() { } else { subscriptionAction = SubscriptionAction.NO_SUBSCRIPTION; } - return createRealmResults(query, sortDescriptor, distinctDescriptor, false, subscriptionAction); + return createRealmResults(query, queryDescriptors, false, subscriptionAction); } /** @@ -1836,7 +1834,7 @@ public RealmResults findAllAsync(String subscriptionName) { } realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - return createRealmResults(query, sortDescriptor, distinctDescriptor, false, SubscriptionAction.create(subscriptionName)); + return createRealmResults(query, queryDescriptors, false, SubscriptionAction.create(subscriptionName)); } /** @@ -1903,10 +1901,8 @@ public RealmQuery sort(String fieldName1, Sort sortOrder1, String fieldName2, */ public RealmQuery sort(String[] fieldNames, Sort[] sortOrders) { realm.checkIfValid(); - if (sortDescriptor != null) { - throw new IllegalStateException("A sorting order was already defined."); - } - sortDescriptor = SortDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldNames, sortOrders); + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(getSchemaConnector(), query.getTable(), fieldNames, sortOrders); + queryDescriptors.appendSort(sortDescriptor); return this; } @@ -1921,7 +1917,6 @@ public RealmQuery sort(String[] fieldNames, Sort[] sortOrders) { * to linked fields. * @throws IllegalStateException if distinct field names were already defined. */ - @Beta public RealmQuery distinct(String fieldName) { return distinct(fieldName, new String[]{}); } @@ -1938,20 +1933,37 @@ public RealmQuery distinct(String fieldName) { * is an unsupported type, or points to a linked field. * @throws IllegalStateException if distinct field names were already defined. */ - @Beta public RealmQuery distinct(String firstFieldName, String... remainingFieldNames) { realm.checkIfValid(); - if (distinctDescriptor != null) { - throw new IllegalStateException("Distinct fields have already been defined."); - } + QueryDescriptor distinctDescriptor; if (remainingFieldNames.length == 0) { - distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), table, firstFieldName); + distinctDescriptor = QueryDescriptor.getInstanceForDistinct(getSchemaConnector(), table, firstFieldName); } else { String[] fieldNames = new String[1 + remainingFieldNames.length]; fieldNames[0] = firstFieldName; System.arraycopy(remainingFieldNames, 0, fieldNames, 1, remainingFieldNames.length); - distinctDescriptor = SortDescriptor.getInstanceForDistinct(getSchemaConnector(), table, fieldNames); + distinctDescriptor = QueryDescriptor.getInstanceForDistinct(getSchemaConnector(), table, fieldNames); + } + queryDescriptors.appendDistinct(distinctDescriptor); + return this; + } + + /** + * Limits the number of objects returned in case the query matched more objects. + *

              + * Note that when using this method in combination with {@link #sort(String)} and + * {@link #distinct(String)} they will be executed in the order they where added which can + * affect the end result. + * + * @param limit a limit that is {@code ≥ 1}. + * @throws IllegalArgumentException if the provided {@code limit} is less than 1. + */ + public RealmQuery limit(long limit) { + realm.checkIfValid(); + if (limit < 1) { + throw new IllegalArgumentException("Only positive numbers above 0 is allowed. Yours was: " + limit); } + queryDescriptors.setLimit(limit); return this; } @@ -2049,7 +2061,7 @@ public E findFirstAsync() { // TODO: The performance by the pending query will be a little bit worse than directly calling core's // Query.find(). The overhead comes with core needs to add all the row indices to the vector. However this // can be optimized by adding support of limit in OS's Results which is supported by core already. - row = new PendingRow(realm.sharedRealm, query, sortDescriptor, isDynamicQuery()); + row = new PendingRow(realm.sharedRealm, query, queryDescriptors, isDynamicQuery()); } final E result; if (isDynamicQuery()) { @@ -2074,16 +2086,15 @@ public E findFirstAsync() { private RealmResults createRealmResults(TableQuery query, - @Nullable SortDescriptor sortDescriptor, - @Nullable SortDescriptor distinctDescriptor, + DescriptorOrdering queryDescriptors, boolean loadResults, SubscriptionAction subscriptionAction) { RealmResults results; OsResults osResults; if (subscriptionAction.shouldCreateSubscriptions()) { - osResults = SubscriptionAwareOsResults.createFromQuery(realm.sharedRealm, query, sortDescriptor, distinctDescriptor, subscriptionAction.getName()); + osResults = SubscriptionAwareOsResults.createFromQuery(realm.sharedRealm, query, queryDescriptors, subscriptionAction.getName()); } else { - osResults = OsResults.createFromQuery(realm.sharedRealm, query, sortDescriptor, distinctDescriptor); + osResults = OsResults.createFromQuery(realm.sharedRealm, query, queryDescriptors); } if (isDynamicQuery()) { @@ -2099,7 +2110,7 @@ private RealmResults createRealmResults(TableQuery query, } private long getSourceRowIndexForFirstObject() { - if (sortDescriptor != null || distinctDescriptor != null) { + if (!queryDescriptors.isEmpty()) { RealmObjectProxy obj = (RealmObjectProxy) findAll().first(null); if (obj != null) { return obj.realmGet$proxyState().getRow$realm().getIndex(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index c4982b0d82..4b6aaf46eb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -123,8 +123,4 @@ public void addSupportForObjectLevelPermissions(RealmConfiguration.Builder build // Do nothing } - public OsResults createSubscriptionAwareResults(OsSharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, SortDescriptor distinctDescriptor, String name) { - throw new IllegalStateException("Should only be called by builds supporting Sync"); - } - } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 55690afbd1..56701527d6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -16,17 +16,16 @@ package io.realm.internal; -import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Date; -import java.util.List; import java.util.NoSuchElementException; import javax.annotation.Nullable; import io.realm.OrderedRealmCollectionChangeListener; import io.realm.RealmChangeListener; -import io.realm.internal.sync.OsSubscription; +import io.realm.internal.core.DescriptorOrdering; +import io.realm.internal.core.QueryDescriptor; /** @@ -284,18 +283,14 @@ public static OsResults createForBacklinks(OsSharedRealm realm, UncheckedRow row return new OsResults(realm, srcTable, backlinksPtr); } - public static OsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query, - @Nullable SortDescriptor sortDescriptor, - @Nullable SortDescriptor distinctDescriptor) { + public static OsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query, DescriptorOrdering queryDescriptors) { query.validateQuery(); - long ptr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), - sortDescriptor, - distinctDescriptor); + long ptr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), queryDescriptors.getNativePtr()); return new OsResults(sharedRealm, query.getTable(), ptr); } public static OsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query) { - return createFromQuery(sharedRealm, query, null, null); + return createFromQuery(sharedRealm, query, new DescriptorOrdering()); } OsResults(OsSharedRealm sharedRealm, Table table, long nativePtr) { @@ -371,11 +366,11 @@ public void clear() { nativeClear(nativePtr); } - public OsResults sort(SortDescriptor sortDescriptor) { + public OsResults sort(QueryDescriptor sortDescriptor) { return new OsResults(sharedRealm, table, nativeSort(nativePtr, sortDescriptor)); } - public OsResults distinct(SortDescriptor distinctDescriptor) { + public OsResults distinct(QueryDescriptor distinctDescriptor) { return new OsResults(sharedRealm, table, nativeDistinct(nativePtr, distinctDescriptor)); } @@ -480,8 +475,7 @@ public void load() { private static native long nativeGetFinalizerPtr(); - protected static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, - @Nullable SortDescriptor sortDesc, @Nullable SortDescriptor distinctDesc); + protected static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long descriptorOrderingPtr); private static native long nativeCreateSnapshot(long nativePtr); @@ -499,9 +493,9 @@ protected static native long nativeCreateResults(long sharedRealmNativePtr, long private static native Object nativeAggregate(long nativePtr, long columnIndex, byte aggregateFunc); - private static native long nativeSort(long nativePtr, SortDescriptor sortDesc); + private static native long nativeSort(long nativePtr, QueryDescriptor sortDesc); - private static native long nativeDistinct(long nativePtr, SortDescriptor distinctDesc); + private static native long nativeDistinct(long nativePtr, QueryDescriptor distinctDesc); private static native boolean nativeDeleteFirst(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index bfa733cd33..5fb25b5b5e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -7,6 +7,7 @@ import io.realm.RealmChangeListener; import io.realm.RealmFieldType; +import io.realm.internal.core.DescriptorOrdering; /** @@ -37,10 +38,10 @@ public interface FrontEnd { private WeakReference frontEndRef; private boolean returnCheckedRow; - public PendingRow(OsSharedRealm sharedRealm, TableQuery query, @Nullable SortDescriptor sortDescriptor, + public PendingRow(OsSharedRealm sharedRealm, TableQuery query, DescriptorOrdering queryDescriptors, final boolean returnCheckedRow) { this.sharedRealm = sharedRealm; - pendingOsResults = OsResults.createFromQuery(sharedRealm, query, sortDescriptor, null); + pendingOsResults = OsResults.createFromQuery(sharedRealm, query, queryDescriptors); listener = new RealmChangeListener() { @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java index 18d91b4626..c9ab8d4e0e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java @@ -19,6 +19,7 @@ import javax.annotation.Nullable; import io.realm.RealmChangeListener; +import io.realm.internal.core.DescriptorOrdering; import io.realm.internal.sync.OsSubscription; /** @@ -38,11 +39,10 @@ public class SubscriptionAwareOsResults extends OsResults { private boolean firstCallback; public static SubscriptionAwareOsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query, - @Nullable SortDescriptor sortDescriptor, - @Nullable SortDescriptor distinctDescriptor, + DescriptorOrdering queryDescriptors, String subscriptionName) { query.validateQuery(); - long ptr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), sortDescriptor, distinctDescriptor); + long ptr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), queryDescriptors.getNativePtr()); return new SubscriptionAwareOsResults(sharedRealm, query.getTable(), ptr, subscriptionName); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java b/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java new file mode 100644 index 0000000000..2777a63a08 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java @@ -0,0 +1,114 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.internal.core; + +import io.realm.internal.NativeObject; +import io.realm.internal.OsSharedRealm; +import io.realm.internal.TableQuery; + +/** + * Java class wrapping the native {@code realm::DescriptorOrdering} class. This class + * is used to track sort/distinct/limit criterias on a query. + */ +public class DescriptorOrdering implements NativeObject { + + private static final long nativeFinalizerMethodPtr = nativeGetFinalizerMethodPtr(); + private final long nativePtr; + + // Used to track if constraints are already set, and throw if they are. + // This is just to mirror old behaviour. We should consider lifting this restriction, + // although it seems hard to find a use case that is not a logical bug in nested query + // construction. + private boolean sortDefined = false; + private boolean distinctDefined = false; + private boolean limitDefined = false; + + /** + * Creates a standalone DescriptorOrdering. This only achieves meaning when combined with + * a RealmQuery object. + * + * @see io.realm.internal.OsResults#createFromQuery(OsSharedRealm, TableQuery, DescriptorOrdering) + */ + public DescriptorOrdering() { + nativePtr = nativeCreate(); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerMethodPtr; + } + + /** + * Append a sort criteria. + * + * @param descriptor description of the sort. + */ + public void appendSort(QueryDescriptor descriptor) { + if (sortDefined) { + throw new IllegalStateException("A sorting order was already defined. It cannot be redefined"); + } + nativeAppendSort(nativePtr, descriptor); + sortDefined = true; + } + + /** + * Append a distinct criteria. + * + * @param descriptor description of the distinct criteria. + */ + public void appendDistinct(QueryDescriptor descriptor) { + if (distinctDefined) { + throw new IllegalStateException("A distinct field was already defined. It cannot be redefined"); + } + nativeAppendDistinct(nativePtr, descriptor); + distinctDefined = true; + } + + /** + * Sets a limit criteria. + * + * @param limit the maximum amount of objects returned. + */ + public void setLimit(long limit) { + if (limitDefined) { + throw new IllegalStateException("A limit was already set. It cannot be redefined."); + } + nativeAppendLimit(nativePtr, limit); + limitDefined = true; + } + + /** + * Returns true if no descriptors or limits have been added. + */ + public boolean isEmpty() { + return nativeIsEmpty(nativePtr); + } + + + private static native long nativeGetFinalizerMethodPtr(); + private static native long nativeCreate(); + private static native void nativeAppendSort(long descriptorPtr, QueryDescriptor sortDesc); + private static native void nativeAppendDistinct(long descriptorPtr, QueryDescriptor sortDesc); + private static native void nativeAppendLimit(long descriptorPtr, long limit); + private static native boolean nativeIsEmpty(long descriptorPtr); + +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java similarity index 70% rename from realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java rename to realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java index ff80eb74d9..e3e8b597d0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SortDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.internal; +package io.realm.internal.core; import java.util.Arrays; import java.util.Collections; @@ -24,37 +24,40 @@ import javax.annotation.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.RealmFieldType; import io.realm.Sort; +import io.realm.internal.Keep; +import io.realm.internal.Table; import io.realm.internal.fields.FieldDescriptor; /** - * Java class to present the same name core class in Java. This can be converted to a cpp realm::SortDescriptor object - * through realm::_impl::JavaSortDescriptor. + * Java wrapper class around `realm::SortDescriptor` and `realm::DistinctDescriptor` classes in C++. + * They can be converted between each other using realm::_impl::JavaQueryDescriptor. *

              * NOTE: Since the column indices are determined when constructing the object with the given table's status, the indices - * could be wrong when schema changes. Always create and consume the instance when needed, DON'T store a SortDescriptor - * and use it whenever the ShareGroup can be in different versions. + * could be wrong when schema changes. Always create and consume the instance when needed, DON'T store a QueryDescriptor + * since it can be wrong if the SharedGroup has a different version. *

              - * Sort descriptors do not support Linking Objects, either internally or as terminal types. + * Query descriptors do not support Linking Objects, either internally or as terminal types. */ @Keep -public class SortDescriptor { +public class QueryDescriptor { //@VisibleForTesting - final static Set SORT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + public final static Set SORT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, RealmFieldType.STRING, RealmFieldType.DATE))); //@VisibleForTesting - final static Set DISTINCT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + public final static Set DISTINCT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE))); - public static SortDescriptor getInstanceForSort(FieldDescriptor.SchemaProxy proxy, Table table, String fieldDescription, Sort sortOrder) { + public static QueryDescriptor getInstanceForSort(FieldDescriptor.SchemaProxy proxy, Table table, String fieldDescription, Sort sortOrder) { return getInstanceForSort(proxy, table, new String[] {fieldDescription}, new Sort[] {sortOrder}); } - public static SortDescriptor getInstanceForSort(FieldDescriptor.SchemaProxy proxy, Table table, String[] fieldDescriptions, Sort[] sortOrders) { + public static QueryDescriptor getInstanceForSort(FieldDescriptor.SchemaProxy proxy, Table table, String[] fieldDescriptions, Sort[] sortOrders) { //noinspection ConstantConditions if (sortOrders == null || sortOrders.length == 0) { throw new IllegalArgumentException("You must provide at least one sort order."); @@ -65,15 +68,15 @@ public static SortDescriptor getInstanceForSort(FieldDescriptor.SchemaProxy prox return getInstance(proxy, table, fieldDescriptions, sortOrders, FieldDescriptor.OBJECT_LINK_FIELD_TYPE, SORT_VALID_FIELD_TYPES, "Sort is not supported"); } - public static SortDescriptor getInstanceForDistinct(FieldDescriptor.SchemaProxy proxy, Table table, String fieldDescription) { + public static QueryDescriptor getInstanceForDistinct(FieldDescriptor.SchemaProxy proxy, Table table, String fieldDescription) { return getInstanceForDistinct(proxy, table, new String[] {fieldDescription}); } - public static SortDescriptor getInstanceForDistinct(FieldDescriptor.SchemaProxy proxy, Table table, String[] fieldDescriptions) { + public static QueryDescriptor getInstanceForDistinct(FieldDescriptor.SchemaProxy proxy, Table table, String[] fieldDescriptions) { return getInstance(proxy, table, fieldDescriptions, null, FieldDescriptor.NO_LINK_FIELD_TYPE, DISTINCT_VALID_FIELD_TYPES, "Distinct is not supported"); } - private static SortDescriptor getInstance( + private static QueryDescriptor getInstance( FieldDescriptor.SchemaProxy proxy, Table table, String[] fieldDescriptions, @@ -89,20 +92,20 @@ private static SortDescriptor getInstance( long[][] columnIndices = new long[fieldDescriptions.length][]; - // Force aggressive parsing of the FieldDescriptors, so that only valid SortDescriptor objects are created. + // Force aggressive parsing of the FieldDescriptors, so that only valid QueryDescriptor objects are created. for (int i = 0; i < fieldDescriptions.length; i++) { FieldDescriptor descriptor = FieldDescriptor.createFieldDescriptor(proxy, table, fieldDescriptions[i], legalInternalTypes, null); checkFieldType(descriptor, legalTerminalTypes, message, fieldDescriptions[i]); columnIndices[i] = descriptor.getColumnIndices(); } - return new SortDescriptor(table, columnIndices, sortOrders); + return new QueryDescriptor(table, columnIndices, sortOrders); } // Internal use only. For JNI testing. //@VisibleForTesting - static SortDescriptor getTestInstance(Table table, long[] columnIndices) { - return new SortDescriptor(table, new long[][] {columnIndices}, null); + public static QueryDescriptor getTestInstance(Table table, long[] columnIndices) { + return new QueryDescriptor(table, new long[][] {columnIndices}, null); } // could do this in the field descriptor, but this provides a better error message @@ -118,7 +121,7 @@ private static void checkFieldType(FieldDescriptor descriptor, Set results = realm.where(PartialSyncObjectA.class) + .notEqualTo("string", "") + .distinct("string") + .sort("string", Sort.ASCENDING) + .limit(2) + .findAllAsync(); + looperThread.keepStrongReference(results); + + results.addChangeListener((objects, changeSet) -> { + RealmLog.error(changeSet.getState().toString()); + if (changeSet.getState() == OrderedCollectionChangeSet.State.ERROR) { + RealmLog.error(changeSet.getError().toString()); + } + if (changeSet.isCompleteResult()) { + assertEquals(2, results.size()); + PartialSyncObjectA obj = objects.first(); + assertEquals(6, obj.getNumber()); + assertEquals("partial", obj.getString()); + obj = objects.last(); + assertEquals(0, obj.getNumber()); + assertEquals("realm", obj.getString()); + looperThread.testComplete(); + } + }); + } + private Realm getPartialRealm(SyncUser user) { final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .name("partialSync") From f9b665a19251ef152220ebab8032a5fabcb41dfa Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 12:26:03 +0200 Subject: [PATCH 1309/2110] Add findOrCreate helper methods (#6168) --- CHANGELOG.md | 2 +- .../io/realm/ObjectLevelPermissionsTest.java | 112 ++++++++++++++++++ .../realm/internal/sync/PermissionHelper.java | 65 ++++++++++ .../sync/permissions/ClassPermissions.java | 24 ++++ .../sync/permissions/RealmPermissions.java | 23 ++++ 5 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed1f010f4..52f9b8771c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Enhancements +* [ObjectServer] Added `RealmPermissions.findOrCreate(String roleName)` and `ClassPermissions.findOrCreate(String roleName)` (#6168). * `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom name mappings (#6145). * Added support for `RealmQuery.limit(long limit)` (#544). @@ -14,7 +15,6 @@ * Updated to Object Store commit: 7e19c51af72c3343b453b8a13c82dfda148e4bbc - ## 5.5.1 (YYYY-MM-DD) ### Enhancements diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java index 79eb7fd407..13183c24a0 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java @@ -520,6 +520,118 @@ public void allPrivileges() { assertNoAccess(nobody); } + @Test + public void findOrCreate_unmanagedObjectThrows() { + RealmPermissions realmPermissions = new RealmPermissions(); + try { + realmPermissions.findOrCreate("foo"); + fail(); + } catch (IllegalStateException ignored) { + } + + ClassPermissions classPermissions = new ClassPermissions(); + try { + classPermissions.findOrCreate("foo"); + fail(); + } catch (IllegalStateException ignored) { + } + } + + @Test + public void findOrCreate_notInTransactionThrows() { + RealmPermissions realmPermissions = realm.getPermissions(); + try { + realmPermissions.findOrCreate("foo"); + fail(); + } catch (IllegalStateException ignored) { + } + + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + try { + classPermissions.findOrCreate("foo"); + fail(); + } catch (IllegalStateException ignored) { + } + } + + @Test + public void findOrCreate_nullThrows() { + realm.beginTransaction(); + RealmPermissions realmPermissions = realm.getPermissions(); + try { + //noinspection ConstantConditions + realmPermissions.findOrCreate(null); + fail(); + } catch (IllegalArgumentException ignored) { + } + + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + try { + //noinspection ConstantConditions + classPermissions.findOrCreate(null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } + + @Test + public void findOrCreate_createRole() { + realm.beginTransaction(); + assertNull(realm.where(Role.class).equalTo("name", "role1").findFirst()); + assertNull(realm.where(Role.class).equalTo("name", "role2").findFirst()); + + // Realm permissions + RealmPermissions realmPermissions = realm.getPermissions(); + Permission p = realmPermissions.findOrCreate("role1"); + assertEquals("role1", p.getRole().getName()); + assertTrue(p.getRole().getMembers().isEmpty()); + + // Class permissions + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + p = classPermissions.findOrCreate("role2"); + assertEquals("role2", p.getRole().getName()); + assertTrue(p.getRole().getMembers().isEmpty()); + } + + @Test + public void findOrCreate_createPermission() { + realm.beginTransaction(); + realm.createObject(Role.class, "role1"); + + RealmPermissions realmPermissions = realm.getPermissions(); + assertEquals(1, realmPermissions.getPermissions().size()); + Permission p = realmPermissions.findOrCreate("role1"); + assertNoAccess(p); + assertEquals("role1", p.getRole().getName()); + assertEquals(2, realmPermissions.getPermissions().size()); + assertTrue(p.equals(realmPermissions.getPermissions().last())); + + // Class permissions + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + assertEquals(1, classPermissions.getPermissions().size()); + p = classPermissions.findOrCreate("role2"); + assertNoAccess(p); + assertEquals("role2", p.getRole().getName()); + assertEquals(2, classPermissions.getPermissions().size()); + assertTrue(p.equals(classPermissions.getPermissions().last())); + } + + @Test + public void findOrCreate_findExistingPermission() { + realm.beginTransaction(); + + RealmPermissions realmPermissions = realm.getPermissions(); + Permission p = realmPermissions.findOrCreate("everyone"); + assertFullAccess(p); + assertEquals("everyone", p.getRole().getName()); + + ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); + p = classPermissions.findOrCreate("everyone"); + assertFullAccess(p); + assertEquals("everyone", p.getRole().getName()); + } + + private void assertFullAccess(RealmPrivileges privileges) { assertTrue(privileges.canRead()); assertTrue(privileges.canUpdate()); diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java b/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java new file mode 100644 index 0000000000..880d28439e --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.internal.sync; + +import io.realm.Realm; +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.internal.annotations.ObjectServer; +import io.realm.sync.permissions.Permission; +import io.realm.sync.permissions.Role; + +/** + * Helper class for working with fine-grained permissions + */ +@ObjectServer +public class PermissionHelper { + + /** + * Finds or creates the permission object for a given role. Creating objects if they cannot + * be found. + * + * @param container RealmObject containg the permission objects + * @param permissions the list of permissions + * @param roleName the role to search for + * @return + */ + public static Permission findOrCreatePermissionForRole(RealmObject container, RealmList permissions, String roleName) { + if (!container.isManaged()) { + throw new IllegalStateException("'findOrCreate()' can only be called on managed objects."); + } + Realm realm = container.getRealm(); + if (!realm.isInTransaction()) { + throw new IllegalStateException("'findOrCreate()' can only be called inside a write transaction."); + } + + // Find existing permission object or create new one + Permission permission = permissions.where().equalTo("role.name", roleName).findFirst(); + if (permission == null) { + + // Find existing role or create new one + Role role = realm.where(Role.class).equalTo("name", roleName).findFirst(); + if (role == null) { + role = realm.createObject(Role.class, roleName); + } + + permission = realm.copyToRealm(new Permission.Builder(role).noPrivileges().build()); + permissions.add(permission); + } + + return permission; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java index aadd98706f..d2a4d94854 100644 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java @@ -23,6 +23,7 @@ import io.realm.annotations.RealmClass; import io.realm.annotations.Required; import io.realm.internal.annotations.ObjectServer; +import io.realm.internal.sync.PermissionHelper; /** * Class describing all permissions related to a given Realm model class. These permissions will @@ -61,6 +62,7 @@ public ClassPermissions() { * @param clazz class to create permissions. */ public ClassPermissions(Class clazz) { + //noinspection ConstantConditions if (clazz == null) { throw new IllegalArgumentException("Non-null 'clazz' required."); } @@ -89,4 +91,26 @@ public String getName() { public RealmList getPermissions() { return permissions; } + + /** + * Finds the permissions associated with a given {@link Role}. If either the role or the permission + * object doesn't exists, it will be created. + *

              + * If the {@link Permission} object is created because one didn't exist already, it will be + * created with all privileges disabled. + *

              + * If the the {@link Role} object is created because one didn't exists, it will be created + * with no members. + * + * @param roleName name of the role to find. + * @return permission object for the given role. + * @throws IllegalStateException if this object is not managed by Realm. + * @throws IllegalStateException if this method is not called inside a write transaction. + * @throws IllegalArgumentException if a {@code null} or empty + */ + public Permission findOrCreate(String roleName) { + // Error handling done in the helper class + return PermissionHelper.findOrCreatePermissionForRole(this, permissions, roleName); + } + } diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java index 41b0f05009..1b8915e245 100644 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java +++ b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java @@ -15,11 +15,13 @@ */ package io.realm.sync.permissions; +import io.realm.Realm; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.internal.annotations.ObjectServer; +import io.realm.internal.sync.PermissionHelper; /** * Class describing all permissions related to a given Realm. Permissions attached to this class @@ -48,4 +50,25 @@ public RealmPermissions() { public RealmList getPermissions() { return permissions; } + + /** + * Finds the permissions associated with a given {@link Role}. If either the role or the permission + * object doesn't exists, it will be created. + *

              + * If the {@link Permission} object is created because one didn't exist already, it will be + * created with all privileges disabled. + *

              + * If the role {@link Role} object is created because one didn't exists, it will be created + * with no members. + * + * @param roleName name of the role to find. + * @return permission object for the given role. + * @throws IllegalStateException if this object is not managed by Realm. + * @throws IllegalStateException if this method is not called inside a write transaction. + * @throws IllegalArgumentException if a {@code null} or empty + */ + public Permission findOrCreate(String roleName) { + // Error handling done in the helper class + return PermissionHelper.findOrCreatePermissionForRole(this, permissions, roleName); + } } From 40ba594fad2652d6bd93bdcd46c703ceb814bfce Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 12:39:16 +0200 Subject: [PATCH 1310/2110] Update changelog --- CHANGELOG.md | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f9b8771c..a7ad0c0566 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,15 @@ -## 5.6.0 (YYYY-MM-DD) - -### Breaking changes - -* When building a `RealmQuery`, `sort()`, `distinct()` and `limit()` will now be applied in the order they are called. Before - this release, `sort()` and `distinct()` could be called any order, but `sort()` would always be applied before `distinct()`. +## 5.6.0 (2018-09-24) ### Enhancements -* [ObjectServer] Added `RealmPermissions.findOrCreate(String roleName)` and `ClassPermissions.findOrCreate(String roleName)` (#6168). -* `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom name mappings (#6145). -* Added support for `RealmQuery.limit(long limit)` (#544). - -### Internal - -* Updated to Object Store commit: 7e19c51af72c3343b453b8a13c82dfda148e4bbc - -## 5.5.1 (YYYY-MM-DD) - -### Enhancements +* [ObjectServer] Added `RealmPermissions.findOrCreate(String roleName)` and + `ClassPermissions.findOrCreate(String roleName)` ([#6168](https://github.com/realm/realm-java/issues/6168)). +* `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom + name mappings ([#6145]((https://github.com/realm/realm-java/issues/6145))). +* Added support for `RealmQuery.limit(long limit)` ([#544]((https://github.com/realm/realm-java/issues/544)). + When building a `RealmQuery`, `sort()`, `distinct()` and `limit()` will now be applied in the order + they are called. Before this release, `sort()` and `distinct()` could be called any order, but + `sort()` would always be applied before `distinct()`. * Building with Android App Bundle is now supported ([#5977](https://github.com/realm/realm-java/issues/5977)). ### Fixes @@ -30,6 +22,7 @@ ### Internal * Updated ReLinker to 1.3.0. +* Updated to Object Store commit: 7e19c51af72c3343b453b8a13c82dfda148e4bbc ## 5.5.0 (2018-08-31) From 30b587ec9c14609ddc3e218cfe983d96c1e3769e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 12:44:27 +0200 Subject: [PATCH 1311/2110] Release v5.6.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index df4bca0bb7..4cc0e35cb3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.6.0-SNAPSHOT \ No newline at end of file +5.6.0 \ No newline at end of file From a1dcb2ba68ebf0e9651d8420178d88ee9f0c278f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 12:44:27 +0200 Subject: [PATCH 1312/2110] Prepare next release v5.6.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 4cc0e35cb3..e1f821e584 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.6.0 \ No newline at end of file +5.6.1-SNAPSHOT \ No newline at end of file From d7ea88646eaaae0f9e6421c7a22d9c91379d812a Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Mon, 24 Sep 2018 13:02:07 +0200 Subject: [PATCH 1313/2110] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7ad0c0566..d6c94c894c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,8 @@ * [ObjectServer] Added `RealmPermissions.findOrCreate(String roleName)` and `ClassPermissions.findOrCreate(String roleName)` ([#6168](https://github.com/realm/realm-java/issues/6168)). * `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom - name mappings ([#6145]((https://github.com/realm/realm-java/issues/6145))). -* Added support for `RealmQuery.limit(long limit)` ([#544]((https://github.com/realm/realm-java/issues/544)). + name mappings ([#6145](https://github.com/realm/realm-java/issues/6145)). +* Added support for `RealmQuery.limit(long limit)` ([#544](https://github.com/realm/realm-java/issues/544)). When building a `RealmQuery`, `sort()`, `distinct()` and `limit()` will now be applied in the order they are called. Before this release, `sort()` and `distinct()` could be called any order, but `sort()` would always be applied before `distinct()`. From b1512e1dcd4a09ae071ca2aa8f24b273103efd95 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 17:25:34 +0200 Subject: [PATCH 1314/2110] Support ROS protocol v25 (#6183) --- CHANGELOG.md | 22 +++++++++++++++++++++- dependencies.list | 6 +++--- realm/realm-library/src/main/cpp/util.cpp | 20 ++++++++++---------- tools/sync_test_server/ros/package.json | 2 +- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c94c894c..d799136494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,27 @@ +## 5.7.0 (YYYY-MM-DD) + +## Enhancements +* [ObjectServer] Devices will now report download progress for read-only Realms which + will allow the server to compact files sooner, saving server space. This does not affect + the client. You will need to upgrade your Realm Object Server to at least version 3.11.0 + or use [Realm Cloud](https://cloud.realm.io). If you try to connect to a ROS v3.10.x or + previous, you will see an error like `Wrong protocol version in Sync HTTP request, + client protocol version = 25, server protocol version = 24`. + +### Compatibility +* File format: ver. 7 (upgrades automatically from previous formats) +* Realm Object Server: 3.11.0 or later. +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* Sync Protocol version increased to 25. +* Updated Realm Sync to 3.10.1 +* Updated Realm Core to 5.10.2 + + ## 5.6.0 (2018-09-24) ### Enhancements - * [ObjectServer] Added `RealmPermissions.findOrCreate(String roleName)` and `ClassPermissions.findOrCreate(String roleName)` ([#6168](https://github.com/realm/realm-java/issues/6168)). * `@RealmClass("name")` and `@RealmField("name")` can now be used as a shorthand for defining custom diff --git a/dependencies.list b/dependencies.list index 9a517e46ef..d3e23b5bd8 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,11 +1,11 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.9.4 -REALM_SYNC_SHA256=f5f52093270c8d26a4b6ba3790c05425d89786033aa6d061fd74e9a24ecb22d7 +REALM_SYNC_VERSION=3.10.1 +REALM_SYNC_SHA256=df8fb8506a318faf83e027a442e5ad0a38f458ec44567146e64d5a1c60b424ae # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.9.9 +REALM_OBJECT_SERVER_VERSION=3.11.1 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.1.4 diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 296cac34ff..bc44a9303c 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -50,7 +50,7 @@ void ConvertException(JNIEnv* env, const char* file, int line) catch (JavaExceptionThrower& e) { e.throw_java_exception(env); } - catch (bad_alloc& e) { + catch (std::bad_alloc& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, OutOfMemory, ss.str()); } @@ -62,7 +62,7 @@ void ConvertException(JNIEnv* env, const char* file, int line) ss << e.what() << " in " << file << " line " << line; ThrowException(env, BadVersion, ss.str()); } - catch (invalid_argument& e) { + catch (std::invalid_argument& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalArgument, ss.str()); } @@ -314,7 +314,7 @@ struct JStringCharsAccessor { { size_t size; if (int_cast_with_overflow_detect(e->GetStringLength(s), size)) - throw runtime_error("String size overflow"); + throw std::runtime_error("String size overflow"); return size; } }; @@ -390,7 +390,7 @@ jstring to_jstring(JNIEnv* env, StringData str) if (str.size() <= stack_buf_size) { size_t retcode = Xcode::to_utf16(in_begin, in_end, out_curr, out_end); if (retcode != 0) { - throw runtime_error(string_to_hex("Failure when converting short string to UTF-16", str, in_begin, in_end, + throw std::runtime_error(string_to_hex("Failure when converting short string to UTF-16", str, in_begin, in_end, out_curr, out_end, size_t(0), retcode)); } if (in_begin == in_end) { @@ -403,11 +403,11 @@ jstring to_jstring(JNIEnv* env, StringData str) size_t error_code; size_t size = Xcode::find_utf16_buf_size(in_begin2, in_end, error_code); if (in_begin2 != in_end) { - throw runtime_error(string_to_hex("Failure when computing UTF-16 size", str, in_begin, in_end, out_curr, + throw std::runtime_error(string_to_hex("Failure when computing UTF-16 size", str, in_begin, in_end, out_curr, out_end, size, error_code)); } if (int_add_with_overflow_detect(size, stack_buf_size)) { - throw runtime_error("String size overflow"); + throw std::runtime_error("String size overflow"); } dyn_buf.reset(new jchar[size]); out_curr = copy(out_begin, out_curr, dyn_buf.get()); @@ -415,7 +415,7 @@ jstring to_jstring(JNIEnv* env, StringData str) out_end = dyn_buf.get() + size; size_t retcode = Xcode::to_utf16(in_begin, in_end, out_curr, out_end); if (retcode != 0) { - throw runtime_error(string_to_hex("Failure when converting long string to UTF-16", str, in_begin, in_end, + throw std::runtime_error(string_to_hex("Failure when converting long string to UTF-16", str, in_begin, in_end, out_curr, out_end, size_t(0), retcode)); } REALM_ASSERT(in_begin == in_end); @@ -424,7 +424,7 @@ jstring to_jstring(JNIEnv* env, StringData str) transcode_complete : { jsize out_size; if (int_cast_with_overflow_detect(out_curr - out_begin, out_size)) { - throw runtime_error("String size overflow"); + throw std::runtime_error("String size overflow"); } return env->NewString(out_begin, out_size); @@ -472,11 +472,11 @@ JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) char* out_end = m_data.get() + buf_size; size_t error_code; if (!Xcode::to_utf8(in_begin, in_end, out_begin, out_end, error_code)) { - throw invalid_argument( + throw std::invalid_argument( string_to_hex("Failure when converting to UTF-8", chars.data(), chars.size(), error_code)); } if (in_begin != in_end) { - throw invalid_argument( + throw std::invalid_argument( string_to_hex("in_begin != in_end when converting to UTF-8", chars.data(), chars.size(), error_code)); } m_size = out_begin - m_data.get(); diff --git a/tools/sync_test_server/ros/package.json b/tools/sync_test_server/ros/package.json index c1baabf349..777423ed70 100644 --- a/tools/sync_test_server/ros/package.json +++ b/tools/sync_test_server/ros/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "rm -rf dist; ./node_modules/.bin/tsc", "clean": "rm -rf dist", - "start": "npm run build && node dist/index.js" + "start": "npm run build && NODE_TLS_REJECT_UNAUTHORIZED=0 node dist/index.js" }, "devDependencies": { "typescript": "2.5.3" From 9592fe89f63d9257e05c657278d5d08f7f87c482 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 17:26:13 +0200 Subject: [PATCH 1315/2110] Set proper version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e1f821e584..81264b62b3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.6.1-SNAPSHOT \ No newline at end of file +5.7.0-SNAPSHOT \ No newline at end of file From 6e1abf49a9ee8b12cea5471112a97efa92db45c1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 17:27:40 +0200 Subject: [PATCH 1316/2110] Set release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d799136494..44d2937f5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.7.0 (YYYY-MM-DD) +## 5.7.0 (2017-09-24) ## Enhancements * [ObjectServer] Devices will now report download progress for read-only Realms which From 41523d999addbeef0b484d2f638690a4194d71ea Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 17:28:55 +0200 Subject: [PATCH 1317/2110] Release v5.7.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 81264b62b3..3b867ccd76 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.7.0-SNAPSHOT \ No newline at end of file +5.7.0 \ No newline at end of file From 8bd6ff775087b2710c58230115db90e6756b3e29 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 17:28:55 +0200 Subject: [PATCH 1318/2110] Prepare next release v5.7.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 3b867ccd76..48dde53ec1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.7.0 \ No newline at end of file +5.7.1-SNAPSHOT \ No newline at end of file From c7ef26eacf8ffb0ef980be9b783f46a98bb1acef Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Sep 2018 20:27:23 +0200 Subject: [PATCH 1319/2110] Prepare for next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 48dde53ec1..4f39750113 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.7.1-SNAPSHOT \ No newline at end of file +5.8.0-SNAPSHOT \ No newline at end of file From 8a047475132d33a748ac78ecec3ce3090d44c07c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 25 Sep 2018 14:07:59 +0200 Subject: [PATCH 1320/2110] Fix Bintray upload (#6196) --- build.gradle | 2 +- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54413 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- library-benchmarks/build.gradle | 11 ++++++----- .../gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- library-build-transformer/build.gradle | 5 +++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/gradle.properties | 4 ++++ realm/gradle/wrapper/gradle-wrapper.jar | Bin 54333 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/kotlin-extensions/build.gradle | 14 ++++++++++---- .../realm-annotations-processor/build.gradle | 8 ++++++++ realm/realm-library/build.gradle | 14 ++++++++++---- 22 files changed, 51 insertions(+), 21 deletions(-) diff --git a/build.gradle b/build.gradle index 87999ef4df..2ff457c052 100644 --- a/build.gradle +++ b/build.gradle @@ -392,7 +392,7 @@ task bintrayRealm(type: GradleBuild) { description = 'Publish the Realm AAR and AP to Bintray' group = 'Publishing' buildFile = file('realm/build.gradle') - tasks = ['bintrayUpload'] + tasks = ['bintrayUploadAll'] startParameter.projectProperties = gradle.startParameter.projectProperties if (project.hasProperty('buildTargetABIs')) { startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index 0d4a9516871afd710a9d84d89e31ba77745607bd..1948b9074f1016d15d505d185bc3f73deb82d8c8 100644 GIT binary patch delta 64 zcmeBO$=th=d4f5M=jU(k6RjgzKNNi|Vw`+1ReIx=phH{?3<2Kk9Pfm6L?#Oy_6IB4 PoP7A601H^i^@;}oU3MB9 delta 64 zcmeBO$=th=d4f61&s)=~CR#_bek%G{#5nn2s`SP!L5H|l7y`W6IVzO3lP3!t_6IB4 PoP7A601H^i^@;}ochnli diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 99340b4ad18d3c7e764794d300ffd35017036793..1948b9074f1016d15d505d185bc3f73deb82d8c8 100644 GIT binary patch delta 7645 zcmY+J1yEGq+sBth=@jYiT)J5r36YX+0f_|`q+M$1kY;J5J0yO9G$PHy(nv@P(v8yi z`uhIH|NFl)cjozg&v%}C?lWiRp8K56a`dw)Ey0w zNLByQ3WRMD9crV92>=`-7Ctfo=O*|pWnh^EI z5@uB5aCEifMc|5E#`_$dl)HWycLa4g?%hTwI6_zzITkBtE1MjvX6emFEbqzh`bz@+ zj$R06;t`LUNNnc@{9L$|@`Kyw3Gv#|tp;moB1hI7X>3xL`eos;hXy8(Aj>6?2+b`OveR2Ib-zdC#v&2^rBC zc^j!vwX<1WDEkVABQ~R)D`Ig5nxp)2u<$H?w@f7xv(+KI+}3=%C6x~rvBT+UYsQy1 z=BGR+I$b7Rq|M)XVKi|$SLGg1IEfkxn{2n;SB1PRkbYxFttlf7S9r`m{#Xaz!{zOw zXEZ%JlCOJ#|COvWC(Y)%os>?Kw^Upf3#HuDX>ndkR(K>?O?2er5qonMyOp6>GVC@j z^*UCaZc!*XtvAAcxP=REgqJiwpHPxOdS1ES;idZsQ}*ssUqB=zHri3$TjaagkrtU< z13gkL*|b_SoErTZTzOTK@P^H2-B-_W#zex&=A^qz$hbipW#T_|qM2p!%CW*Gt92vD zZ?WE-c`#gMIJeMkiN@h|!clv8EDYH=%zsq!VazR}Eo8;R+|p|HF5^9DW@Boy?)CPa z#u1rwgWT`Zjvmf3=?o7ApOV-afI*b?XYuRlV3Xx`?Se5l;X)1wmb-hk8#rJPBT#XF|BCvZCNjR#d%;kT-*toY@(h>&RGUZoKP;sbp8&F@LZ0ks9r`Z3pJ+?5VWB2XTUV*CO#rSm>qD8|$zzn^)$?Jg9q&PqQb<@vG~$_@CxZ zG4FH+x!*r-=d((W!tM=XcP^vIePzVi^#T{&iotz$(5HPBVMuq#v;DzOf`_;dz+sN+*1WjWIA}hhpNsQT}o<>NU5=_BA7Bc8_h~`(6s0h~rFy zksjh)J;3lAh#Z9-gOX^>egr)i{xTuj`B=zB#u9tKLs-vg%o?I%#;^=Cgl^9Yplw zx~AHmvtB^;k0mTbhHJiGTHY#fN(fm!jT{vcCidns$N!!KY`FD(<({x#TaWW-yYxZw zL;KpcQRQ61c0QEY;nEVl(f7OAh+3`0JQsoY-RgMtJI*6lSt#Xoo8rgAHXE)~0ggtv z;&%vfK`7xm%3pao+QBq?sS$7soz1<3mTae*?!xvBd@V-q!ihG1(H(52A{*MtPt2 zD=QDNxjfU|gi?`4v@dFi&!bOltMM6$396R`4MIgiO>emxIvQ`8qK9mkO8LX#=4U^A z`^lB6`4-nUy(QI@TGIg(NQ#pXbPF*9tFXyv@>;NvShA2?GO9d`rT*#(GKmRr zls+=xT&lWp%cB~QHyJAzI514GR+SD%-=oB=eF-r-v|cu8 zbI)~`>_cdNo}s)06TH#Nl^cgox@ZhyU5+27~B&-1fqLu@iJe6sAiMK!^W(0CiG;OyBn@wP-Ze~)c4pw zb|6b2I5FeJ)&`{`#|0tCk_w9cRI`#dO?lg#U`qoYtO+vFX1X%~;2REq_bE*Tr?$ETW)IT)S3b*PITkb(SwOCx@p+jP6;yaWqHC!{a> zibV&$wHcGoCaXygKdp8(GHfQOy;+QN%D~Bndpoo@?v;}r zSG&}|U|wl`5Aif-%57|HX)=x}^^}FD4U=nldhfAKHvrMXh<+o48z?HJjmd(!I;T#$n)Lu#E) zQJOD^;U*Ka2HPs{qKP-~9(;yvQ>#9FC)Vi_*1Y^Vb|-iDdUY`&*!~mZ9EdZ5crIJF z-g|ke-0CGb5zA>3l?53(zQ~{36I^TEt$E)sHpx*;7^Y#~aN)}g5deA2=FeXD6woT$ zBWHM+i7hSm#Ftg|Vl?_l*Oy?WQ@U)ASOiN_?Ph zw1h4#`LK9CLDRSIU&oggS$??i|t&=&`mK}G{%6aNHe3xS>hl>G}1;Xza?W{6Ktsi|O z5WN_}Fo(y8O9()oJ>lfWRm%Rc7e+bFGh(q8#}pZ?=_7UK7OyjxjXhjb^-_7Jpl|sF zi85~@WdPRLoNcHpjO@|R_yKXFtO^$jSs%4-wMR}*1STjC@5K&)(+e;ZCHhAMNDgo{s!XRlQX|xf%U# z3z!9mPD-4AN7*>KOaCLd{S{JH*pUmH8cnzR%usdRgS__~t#g+?{^yxS!~~GBapMAe zi%go*1XoLQzqplE(J5)75=?7iiXq0Ah9b}D`96)Rf=T0Iw(=13j%a;dZka{(G?BQED7-Z@I0@q6UVaXY#UaKyuC6BzZ^-}*Lwh3)}G<}OUG}MaEsV?5#1Id zEjRcS!_DxCQ6MKrZk=L|JJM9w&03~P5Aw}EuBeF-oYmqplz$Ae`3%YhkNXrqvjMjd z;_#Q-`g%=}`suSzr9b&92qn!0*A;Ft+`8Qv6n~5%=BnBcVlK6B5SjjY4h>gm4Oeh* zadUU!NS;6xyzRj$xN8(M(@+(y!VxLfT1iaB@?waIyr83*np~NdOrI2*SS580Lx?t(KP*_$ao8Eh#c>5`9Riu2T zdqe{nVVP4q)mI$`lFBCBMq&~!xaq`IIWpn&8xFm}HMZNG&y^a@Y$&ARi*S?0a1IxJ z0e#id;5`?8>Wc0OWhb{PK_i^{)bkOJf9}Ape<9Ew>p-x{C$NP(O^$fOxb1B{65aoU zHcWZT{#qhD&+z&&+BcTVMO-nj^y%45-Rt(d!UJB^Jm0{^{>R z)}DrnN6x8ufjAj(hDi;91a!~DDQQK!y`9|D6+q!4z#30+XGf(J?&#P0G~wCE+H-20 zPZejTcOQ|mYxLGG&AQ(z?wA8rwtOhNc-%qGdN+yi!V@k1hs>6T?YrMQGiDtAR5%*u z*)1@3w>RLUi>j?Cs~CLsyJmEIAm_?$>T~)@`lkR# zF^LUtP6|K{g0ZFFEMVEZUOt@WQ^e6EC`z_szqH-XI#7Z`{RD|MjNAIACrIkZ2W9Sd zKC31BwOSHFF!(A-%p$DfU9L$}RhT;m>!c6f(tJ9YF@!TD@B$rfHHtj@oRA#M#(5P3 z3?XE}LgV0T8#1HVP)#orFtJJwnO91d-iCW;^Es=w>tfod$mUwrz!h<*J3k~MoP>&@ z4xJtI6yf1MGEvsWd35QVWYx2#chj$|eE$Tj@v~v{-E_}n)FAD!%R(~(sjKAw{WJU9 z9;e2oUvQEf&7VejHHw*IlB_o)LgF4k)tAj+S*YXi!{xKFK6fchVODQux55dE&7}5$ z)W{GVg=6D6w~#lEjr3i~@weuy@DSEkW!jsomz$EoiF;GYP9&u(M6)(Sqkz%!mKB>4 zIDssGgFsbx>=<<#-^Xc~Jw5@@rcdaFR}Fpy7%q^56-=`1pa!GXh|)RtVQ$#>#Q34j zNxY?-$Xmu8wcBu&s$%%E!0i1bUy5ntegFO3!-in&@uTWxt+|4GV4yiZj)nhr$`4)n z1;FD)9F@2-!&^0Jg`yE;LgGsUw=jR(R27F&tL)w%4rR%Nht3ChlS+~#f};hQda*0U zo^W(KBu&=EvzsSL%OsieN6~sPZ>`Oiv@OK zYfmvc1N4j#e0kU+GO-B+Hy94qrp6QGqqQ8h2W5paSrv*QB@^{{DclTBtQtjJ zsxsqg9&x=O?(OGoNnJS6XAsI|C9%-wL2$B3P+cvi-^h-^jihikhBV>Cp^->-=``)m z^$zv?VgHy^J!ZO&3cX985kTk`r;Mg#Z4oVN(O^YuI!ju;*cw+n5d?+Fvb98ONL<$wCBlzv>DT?7qm27(?%$R_cJ&@18y6AGi8JR^Cd|UBF4N z$t_?hW96J!WEYdp$I`-F<+0gwD)t{SNGY?X+vdhG6F!mZ-ITwb;HiRu7u6YlmI$YP zM5Fq;t`KLZBZVsA$0IFhs37^yzvI6sPwl{NpmfTuoURk%E8tC<`4L+xKmVW$Q<*Ct z*Fhivam4zGBS&Okrj1Zpq3_k){RJFQOrhSsBQJ^7K=v}3u7`lnL21|OhHSL$i5b28 zc;xYONu||@YA0a%Rth$(mpY)nU&G1R67LWy=z#v0ksSMa)JKbrls zIuCr>Qg*pxDGGfk_s|f6Vt95J>qm)~FBut84Swoic};a;UZqn?iB&C_%*phl8NksUT=qzM)>lFg%0|JD1EaVJi7^Mh;YJi;90qy=W~tdI&@Qbx1?- z#d**V-!_y1gf$}%U;dU69w=kLtEgvTRke_)-w>Spk4OB)qhB!ZV~CWddn#2>C{0OV zRBfNhAprC^Z9Hww!aIm;LwDNjdg)26y;>k~;kpHmKG_yu`RU_;fW|vO1*!b1ZX;8I z$>n$SVR}8h8@p1C6TnOxZC7jP^KM_dd0xd|X8HUiYpim&0p5w4@M3qPONoO#;Coru zA{Q$E8=&tslbONi#YX!3e72R55#Mo2*c0Z}CXHLBhu<5g98aXm=MuoT!Ju*vg2c-c zfQAb?m*uW+w7GK7jWx$vu7qkn?M<7VD0`Blu(yS2t(i=w&p&s1%R zEBRvUpZDv3f_$lMnio6i=j)`?e(VW!Yj;SZt=uKye`OlFjK}{9hJ`<;e`k84&LG`% zGF8-gh`qR?0yoixS^3e2pC6U^A2W^@I2(~Jf-P8JsN6?1FVM&*UnMItqD)H!E=Z(a zpKWGJp|;UfIa^FkTx>dV@`g;tk(8vWC-x}|6BQZtfcahaS{edzcW~rSUlxKo&xd>> zP3KP-=$!-xu7Dlap4YC7(w%XyA22{cj!ZL5b=(7$@Vse1B0s!*zTw4CY5PgOodDC# z5@QcgiRI#VBtq=wBKiw~Ch#O1Lp8mjx>sJmtayc2trJ5MgSpsSTzdr;#1_Lwdd7@n ze#3zg)+Hq8)-5$D`no2s?RHm?=19I4FH_!9mi6)87kR3~pcNdhae8oI48O*3{_4@= z`*HdRxN`d1D?!E_wX}PDz}wr?Wx|C^LaB$9-h*3Vg0{y6%QQFmJU>h&BLgFyuM+Yv z{3dR&BA4mqc9~m_EampKhH`%4Jclc!p43Ap@#_X@+A;#;?yu)XaD@Xgo8x;6P8sjf zopDWVLB1p$QiHu37h0l#lEpV)9uv`gDuYLm!87p|L#`9LSBLY(UcKmk30{do7PNLe zAyxePAESW+TSKF{3=9u>7nZxPDh3PJ4ZnIqIr5kWt~DxW{qT&9cxxilZipJjE%RZM|DqG8Y`%vQOkwJ#UIEvyx^Y+m|KA0>Q zc2FYijWMR!#CTBH?-ZyPF0e{(U3ldub4xJ>35} z=t5BR>Y`;KoO=b(x)8a&;=tYi>$n~Easg@3{xsjT(5Mg<{X__(J}RIA#vf@68w-)t zCkT|p{EJ?gl!)6tE?_qHzY+lge~<`a(oY54CI9PUp&#} ze|Q-+jaXSO9=` zB7aP|gJQr*)jwgKD&Fr06CtpMsDQ84|44a5_y}!v3WUWF7tlrHUr8F6rS~U<>CqyH zhPi;g#(&V*m=kM7$Xl0OI)k zi;Rf1Q7&Mf@4twL5JrgsQv&{)6H(8A^TB`d2Bigrh5bdBF=b#w%wNopp+>wLBSJhH zrvlc;|CMv&e@}6o3s{o$SE?D81GcC9#oTxdL`@3uZx<0la)JujlJ;LiiJ2IP@Cklk zPUe5fYZG#4-U!A?OEeTBc+we&TlnuV2BN5+48cD2d#yAeV%nJywp0AT&+WfM-an<8 touWb;M4U|7qh%wErtQ#N5TB+6ssI1Vrua|gen+1Wcr)A>6uti|{tvEkeOUki delta 7478 zcmZ9Rbx<5IyY*oU6nA$mEG~-{En1-HBE{X^7hBv)vBllp3R@gnyf_QAg#yLBxclwf zxpUudzWnt&CnuRClbOss`B?R$yYfYnFvb}2K&T875|VDRv^ILyF2$M0sRuHw*P7}R z+?)#|uZH|)4h;!O2u91z2@N3DqX<^N;vcgK!8dR-v_mM&bK+nXX<&q6VmQlYNK?H* zvGi_w?S%G9;NTBuM)hS>Yl}mTZkaZA?(}XdkA<6amYvek zw;@-#qAP(J*MTdqS41KAD-AtpW>ilt*oG%<*mS9Dvi#)ysv}A2jL;8ZXacb)Y7T6# zlI3JbXV0uy+?2Ox89!NPCif=yzOx`KW0u6`32jmeOa>E*Fuk&ciA0|>LW+s#dKKl@ zESmzGi0Fn{#ZKP_Do3fay2%U_yj^d;F(NF;FoWH^0jRT_z{Mq*`pE{LRNlUMYKpQI z6t7t0z%;hS?i}_xkAepEu}j!3l=sCg0XO24KQGnR`f_;1sr9no_or7hcJ!RpR>irt zPqx`kQHhop&_JCc6l&zSj>TB(ECvPS)Qg07{0E&70+=mX5(gAzDSOteeUQrcX;)Q0 zcr9lYo)hvc(ScH z)X@Y^B}PKPcE{L>3#W~q60e~*-em!X2RRqE%IDt`iA%rH=qC0#7cn-w+A0RZI^85R z3;4Qb%gL&Fe4(n%8nixBVl9ru#B&(@rB1n^XhVwPdl#T>KA&?_j9+JV?oK5Ky2Q^* zk{_H)*SA_h$5aNYXk;!{4Q8+1xLwnkZQl3mwMX8$gxAvLi&<~8%rx=U?2-o$mi!F* zK$5+l)O#DZla)|FWATKd)Y)k1GB}oFu6$weqcw&SD**a+WYHySOVa9YmfLXfrJ3$ZpCKOxK#C^t>J@zaZREo5iZzs?F7g1-q)(Iucgm3RXhrOeCCsmGh`6 zvA_CpIXS!R=x5wdB^I10Wp`7OAhk9%zS?#kp~D`_9oyLJTXpm5sd%HoPi1&<{Vc>O zcv*{+wZ_1)sxf%Pg3zkpDqkGacS!`dcUxlij}bXmz~*)r3L*dJ)^Z>Zy0F0bGu{C*?TA8{PT7#T2g=x^ZYxep)hznnpJyM+wb zdBk|KxF6-PclrzpCz3ybh%7hHZL*C0IPTq!((fH}oIxJmwp6wsdrIC;QCxEh^8EQa&jf(>`zw5RT57yFRZp8pe|q5ORR=n4UV^F3B05?y7-zQr0kx@oMQYz%`s3b zEyQ~{fvR~FKzRU4usR3p}^@S=#t#lLY^hE`obr?Dp_erDD=Y#MXie9n24$7Sclp{CBl&Cj+9K&#-oQ`(xUI&x zT%D#Nq}>sAR(L-VqDzt;M~rGb6t>Wbz}!$fc7wrR7^jlazH{JCZSZc*Ixf48yX}of zUYojngLx22mEG=PeXLlm=#m6}*Ve5#zRFX#9q_q_i(yZPAY7l9c?$D>Fe4S55KDurJq)%5rF!(B}1rV zP~9y(=f{m!UMI&HrseVnzz>Qw@7s|dZ(HP@nH&X(QOlQ|q_%c3>kY<0_2P2eA5_dl*Uv=lUn zIvyflV`I@&9_qYGE*gFlg=U)ED4bqZe0)ZDk?_56ymL*2VD}5&aQH3CZ@R9WftQ(> zCs3WiDbUS5kA}(%6k;SKbo9Tkp$t+UR=YGa5faiB5DAI?ujA@r3#$=ifRb!|mSVl$ zo}8FyM19j%%S1Rr>jPq=Orih8KyS`Rzs~HN62HVUnWsW{pi8G~a9Hf!O5$5trUNDf zvA1~t{AEzr^5ciCW6)tuP=o7UK!Ypr7;59<;*uisbu+`&re~}6*|X<72y*GFE} znlUaL_A?PA`LxAAaoO!Du%B*{ZL}Y*BW3|%@t(NwK*gE(9x#0y6uA>^hntA!j*$^n znLf7JY?O^@&wkz>S3_cgzZr?ilS1)97F3X-^gKh)1KaT+xb~uezGjk>HzL|3ITII1 zoLWy~kj$CFSgX@r(_t{cH6gEom78Rv6*U^LfXaL`OJh(-T= z+blV-bB-2i_y!&|{1Q?kF8o%TVtt}ZehrJ*CGD-DEIgq#vNDSY>=zJY5aB9dO1w#P z=KcwkhIzo?uq6};U6h4?TnwYeXd>9eO>PxP?3^dAmphhqum~)jHH*Z5I`-cE5!DAR?>Z-zr)+auex?v~SX}&Q zj1*RrSgB96REf=wDp2tsngb>;>}Hs(MMUF`|7k^MER? z6LNv_RR>-Inp{EzdT`rBfwX*HpdB%Cz?hTD$n`5I$0+DG3!U(v{dZK9d}X`=$|@N> zc6&|f;COp2Ft}lg5`5#}CH(Qo#=ygfV~VO7GnZt|$kl+ND9T%xX4`PWhZVz01XUy;;n% zg%qlFCfTPm7wqRhpMJCH=YM_IERjX~89Hxqj?{U^YyQ zXqkjOQCB!CAzxeZO;1Ar`qi(>cH4x_7Ofx9BM)nJMN{JpM2`Tl=e+^H?yn7p@ULKt zdNMQF?q#(-&EZ+dh}@`yFtzv&Rrj(&r?Xscve{fDF?hCXEvc^Bw6zw3p5k0`xSdGp zj=h;_cV;j?+2&HgX<3V;%uTSnx?CuxWo{G_RZR`>yK3 zw=thl__?9fi-TB|Imi2b78zf0)TTOgtY0g$l39B)7#VU(;&dElk&bMq{(GC^hJYb8 zC(hL7&8cYqr_{q3BPJKaAPQ3vpP?Ct%~CXqWM))FD~}MAN&IB*_m3$%5vjnOAWhOw zQGV<;R)n9Y2*&96tXeg=sgwidE!!&?#EK*k%Nc3mnbpeTp0HLh_YX6!g78;RQ!ppl z49$m0!$vb?ZP-q_ApA?77yIa*G5y3Fds3Wc1Gd^O-(d#b{Cnf_N(x4}5_%rwETZW{ zK7+xBMD>HVcI%iQghe_Kes~Kmj|uySDXGvw)*L-ge=XRsf- zq^%-*OY&XW`Fbp~VE=p*jlCDpyMVMid+D-*Sst)|cE08w<=yMF`f{Gi3v`L<8QLDS zc=hVORQbYF`3|+RWv{xvPMx0gFO_HBU5aZwBf}XwJ^8L0w;X{lT6Qcs1YL{p@2f{m zHyUIkop1;yxEvigG)vh6OGbrHh4H^H5H(ql$nBy=y6WDN_ULRa-uR?Jk5^wO8+19M zMILM2<~(qv9dCEDuk>Fldlrmde?Y^le4y_RH#YM-jRG$TSt`6T6fh2~FG)%_X0tp0 z06YJz)TiWd8c|pD0QVNC-#e95ZEjq6#hjyKkF%Gj*Gy#avZ>AH!UC3XWsfCrepjtw ziG6PR(+8tNCphLhw)d(A`r!O~4E~t;rWC^3%UCohnS;VEHNK*0&$C=|hWu1Nx)QF_ zHU?}S_hChLjK!T$iNdA9jmy=d=1FJ;l;nF__!s;rJLZQA^~X52&>p1Y7#{E))>p5$ zX(t)-O&rZQgQA31nwRn$ybUop;rAf@4t2kFr5aVHI(7-%&w@axfuI1!hJG;YuHh#XkE%&fY6pRhJ#^w5@sG>5`lyrW^Elssw;vjRLW#p)IV)4q368o(PqWpL z?3)5U1f@ag%{VEk9PQ%_KA68Sm;IdkL&9j>o(7{v0k(Tz)HhZ0063L_TeE#>9Psb5Z|a#KB|4I^E=!^=7HhU#jk02^7rgfXu}wMp}?e(FYP;v79xgd ze`JF#rp`ZTZWtdWQKWe9~o2&uDe$du;K$J~TZ_qtiQTM)S2X*{ zNs5=PyPN7GviaUi8CwE#qgo~DAfZvuCkzwxLWcnYPv*y1ma-C8l=6?+soq2RTI-PN z9|;u{vk9F#T5VN{Fg*8 z7v`}RwZGlCcXr_Ry^Tm{+i(-sV-wlX=H_=Zi@Hk)KK0rCGQDAD;-8z7G(x+FwTGS< zW9G3~J)>Zkx}UJyk}9?9W=l`S)jlI{y~eoB(|4`{etK~d{K{c zZGiYC@FGR^hy}K7&0B=WO=TG$-}z#wOsRK76C5=^lKe~JZ70HXF8Lq}GG7&o!K~5JlZ{lL;!<7J9V| z|8yxfoJ|xqHI1rrlR2y_>dK0utHx$CsG+IbLJ9dIGI8(5WAKiaR@s#3t*b`gr;&Cn zG3a?|lIXU=+cWF!K82>?JFluyuU{BhR)hGHOGqhmT*BZS1ODf=ZKKzkht779h~^(S-|xi5rMPVd7B6 zMtGNazeJ01w2aqSO%H!I?Pj#7rc!$HhaXQ;c+2?!!2)ExD}Kjnd|B4?&AoM?F%D+1 z_)Z#*hRFhbqyg3$D`M)1j&wKlAerVD2Q3`UNFw}eIqY96jwZejw@46 zu|KqLb)5rm7AOc=>k7_3_!52AKRST==(V5T$Y^}t@(1^wz9FBt0S#rs2A*FLeEaitO4f~pg z;w@g}&%8# z6-Dtib2)y>wcl}PJj`yrFpdO{3J3k>Kzfp$;ELlUJ9l!`382jF)MfSv|(iZc|utDh|H=~p&=@v zqSzQr>GqTx-a&D)cYT`9(PvFu`|h3(-C?~uH~xAlUv~F4)bbbI`Z2`6_g%$JFlPoSRd3yhiNHIJnT1de`woACdB;q^X#-;by%o$P@OVPTbp7kNI$zoG<^x%0cd&aoS!~6v*fe`*7lc z-$BM$e9e^j?c9BnfXsFmNDZ6V+ENn-ubJ0g*5&GizFSS@e`!L} zOp0v!VlxA0Q*HE;X8sVS-#ZuC|EBHqnGhR#!LQ#PX68roDgX!g$D8><&yIkYtSCLi zn9rtL@k_+~1_5o#bdbI$<@hs);^r2GCs&2(>+R9xnx~8UpJ-}?D8e{e(SLmNxSKkd zC2~^o-*p-F@eZkx9U)#IqJm= zs1??d_Pt#$)&p2ywCQ+vqR%VC3oWnJ?c|$`9h`zSL3v786eVURxg@P5jTqu&zLXTKRC|p(XSFS{;pYe!?S{8wi$#@K2JaUBTnf=-ikdd zLRE4g617ZkS`hX;3Kbkp zb~Y_x)Vkr`cg6Sqh9{b1kD6<8MSJd@tD#NG^m#&EHmWKKZ=R8x5lSF`5&?t&3X+D( zOLQ9h_qrs1i)?@EZAh@>ergz~pX0wjQ!u-JZRA{7S3e)}SJ+X%2=W2U9gP$wF~AO> zMTV^nk-&lnW5J&eHsbF8|h+(#4_^`ntN@PCR>X06Qj_Y4WZJ3Vw|9-~skdPSvF8jX? zgZp17|39JsuZZ~H5RKr!(9y6k0IKv4#lHyw2vz=J!8;sS`nNX#>UaO-@iz&868ImE zfoWi7BkTaS_y1(gdrDa6Ux{M$Pdq#IH159hGQcDdm3d2 zu-pAb3YhGe8eqx^)|*TXYZ#M3wuYUK=>i5_{~79T*nellhlP$)0>-`nT1>F7<9vWN zpMMCQK!vIKlE8Q;*a7eS|JM=+yb1b;s}u5osL+3?JgE*~jrfN>lL`Ry5C0H2B@Zx7 z_=`YT)|50rJNYk)!dxfuVI!&d*#A8o`_%trQql-u4O95AxM@m&P5Qr4ya+Vd_i0{$ zO4k2MX=G-YJHiw>0ER#~0osfH+KjOCK~k9S3?(3=8MfTX2>U$43vliHi`@Tq#xhHZ q>w)Ey0w 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 delta 7478 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 q> project.ext.set(key, val) } @@ -31,7 +32,7 @@ apply plugin: 'realm-android' android { compileSdkVersion 27 - buildToolsVersion ${projectDependencies.get("ANDROID_BUILD_TOOLS")} + buildToolsVersion "${project.ext.get("ANDROID_BUILD_TOOLS")}" defaultConfig { minSdkVersion 15 diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.jar b/library-benchmarks/gradle/wrapper/gradle-wrapper.jar index 99340b4ad18d3c7e764794d300ffd35017036793..1948b9074f1016d15d505d185bc3f73deb82d8c8 100644 GIT binary patch delta 7645 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 delta 7478 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 q>w)Ey0w zNLByQ3WRMD9crV92>=`-7Ctfo=O*|pWnh^EI z5@uB5aCEifMc|5E#`_$dl)HWycLa4g?%hTwI6_zzITkBtE1MjvX6emFEbqzh`bz@+ zj$R06;t`LUNNnc@{9L$|@`Kyw3Gv#|tp;moB1hI7X>3xL`eos;hXy8(Aj>6?2+b`OveR2Ib-zdC#v&2^rBC zc^j!vwX<1WDEkVABQ~R)D`Ig5nxp)2u<$H?w@f7xv(+KI+}3=%C6x~rvBT+UYsQy1 z=BGR+I$b7Rq|M)XVKi|$SLGg1IEfkxn{2n;SB1PRkbYxFttlf7S9r`m{#Xaz!{zOw zXEZ%JlCOJ#|COvWC(Y)%os>?Kw^Upf3#HuDX>ndkR(K>?O?2er5qonMyOp6>GVC@j z^*UCaZc!*XtvAAcxP=REgqJiwpHPxOdS1ES;idZsQ}*ssUqB=zHri3$TjaagkrtU< z13gkL*|b_SoErTZTzOTK@P^H2-B-_W#zex&=A^qz$hbipW#T_|qM2p!%CW*Gt92vD zZ?WE-c`#gMIJeMkiN@h|!clv8EDYH=%zsq!VazR}Eo8;R+|p|HF5^9DW@Boy?)CPa z#u1rwgWT`Zjvmf3=?o7ApOV-afI*b?XYuRlV3Xx`?Se5l;X)1wmb-hk8#rJPBT#XF|BCvZCNjR#d%;kT-*toY@(h>&RGUZoKP;sbp8&F@LZ0ks9r`Z3pJ+?5VWB2XTUV*CO#rSm>qD8|$zzn^)$?Jg9q&PqQb<@vG~$_@CxZ zG4FH+x!*r-=d((W!tM=XcP^vIePzVi^#T{&iotz$(5HPBVMuq#v;DzOf`_;dz+sN+*1WjWIA}hhpNsQT}o<>NU5=_BA7Bc8_h~`(6s0h~rFy zksjh)J;3lAh#Z9-gOX^>egr)i{xTuj`B=zB#u9tKLs-vg%o?I%#;^=Cgl^9Yplw zx~AHmvtB^;k0mTbhHJiGTHY#fN(fm!jT{vcCidns$N!!KY`FD(<({x#TaWW-yYxZw zL;KpcQRQ61c0QEY;nEVl(f7OAh+3`0JQsoY-RgMtJI*6lSt#Xoo8rgAHXE)~0ggtv z;&%vfK`7xm%3pao+QBq?sS$7soz1<3mTae*?!xvBd@V-q!ihG1(H(52A{*MtPt2 zD=QDNxjfU|gi?`4v@dFi&!bOltMM6$396R`4MIgiO>emxIvQ`8qK9mkO8LX#=4U^A z`^lB6`4-nUy(QI@TGIg(NQ#pXbPF*9tFXyv@>;NvShA2?GO9d`rT*#(GKmRr zls+=xT&lWp%cB~QHyJAzI514GR+SD%-=oB=eF-r-v|cu8 zbI)~`>_cdNo}s)06TH#Nl^cgox@ZhyU5+27~B&-1fqLu@iJe6sAiMK!^W(0CiG;OyBn@wP-Ze~)c4pw zb|6b2I5FeJ)&`{`#|0tCk_w9cRI`#dO?lg#U`qoYtO+vFX1X%~;2REq_bE*Tr?$ETW)IT)S3b*PITkb(SwOCx@p+jP6;yaWqHC!{a> zibV&$wHcGoCaXygKdp8(GHfQOy;+QN%D~Bndpoo@?v;}r zSG&}|U|wl`5Aif-%57|HX)=x}^^}FD4U=nldhfAKHvrMXh<+o48z?HJjmd(!I;T#$n)Lu#E) zQJOD^;U*Ka2HPs{qKP-~9(;yvQ>#9FC)Vi_*1Y^Vb|-iDdUY`&*!~mZ9EdZ5crIJF z-g|ke-0CGb5zA>3l?53(zQ~{36I^TEt$E)sHpx*;7^Y#~aN)}g5deA2=FeXD6woT$ zBWHM+i7hSm#Ftg|Vl?_l*Oy?WQ@U)ASOiN_?Ph zw1h4#`LK9CLDRSIU&oggS$??i|t&=&`mK}G{%6aNHe3xS>hl>G}1;Xza?W{6Ktsi|O z5WN_}Fo(y8O9()oJ>lfWRm%Rc7e+bFGh(q8#}pZ?=_7UK7OyjxjXhjb^-_7Jpl|sF zi85~@WdPRLoNcHpjO@|R_yKXFtO^$jSs%4-wMR}*1STjC@5K&)(+e;ZCHhAMNDgo{s!XRlQX|xf%U# z3z!9mPD-4AN7*>KOaCLd{S{JH*pUmH8cnzR%usdRgS__~t#g+?{^yxS!~~GBapMAe zi%go*1XoLQzqplE(J5)75=?7iiXq0Ah9b}D`96)Rf=T0Iw(=13j%a;dZka{(G?BQED7-Z@I0@q6UVaXY#UaKyuC6BzZ^-}*Lwh3)}G<}OUG}MaEsV?5#1Id zEjRcS!_DxCQ6MKrZk=L|JJM9w&03~P5Aw}EuBeF-oYmqplz$Ae`3%YhkNXrqvjMjd z;_#Q-`g%=}`suSzr9b&92qn!0*A;Ft+`8Qv6n~5%=BnBcVlK6B5SjjY4h>gm4Oeh* zadUU!NS;6xyzRj$xN8(M(@+(y!VxLfT1iaB@?waIyr83*np~NdOrI2*SS580Lx?t(KP*_$ao8Eh#c>5`9Riu2T zdqe{nVVP4q)mI$`lFBCBMq&~!xaq`IIWpn&8xFm}HMZNG&y^a@Y$&ARi*S?0a1IxJ z0e#id;5`?8>Wc0OWhb{PK_i^{)bkOJf9}Ape<9Ew>p-x{C$NP(O^$fOxb1B{65aoU zHcWZT{#qhD&+z&&+BcTVMO-nj^y%45-Rt(d!UJB^Jm0{^{>R z)}DrnN6x8ufjAj(hDi;91a!~DDQQK!y`9|D6+q!4z#30+XGf(J?&#P0G~wCE+H-20 zPZejTcOQ|mYxLGG&AQ(z?wA8rwtOhNc-%qGdN+yi!V@k1hs>6T?YrMQGiDtAR5%*u z*)1@3w>RLUi>j?Cs~CLsyJmEIAm_?$>T~)@`lkR# zF^LUtP6|K{g0ZFFEMVEZUOt@WQ^e6EC`z_szqH-XI#7Z`{RD|MjNAIACrIkZ2W9Sd zKC31BwOSHFF!(A-%p$DfU9L$}RhT;m>!c6f(tJ9YF@!TD@B$rfHHtj@oRA#M#(5P3 z3?XE}LgV0T8#1HVP)#orFtJJwnO91d-iCW;^Es=w>tfod$mUwrz!h<*J3k~MoP>&@ z4xJtI6yf1MGEvsWd35QVWYx2#chj$|eE$Tj@v~v{-E_}n)FAD!%R(~(sjKAw{WJU9 z9;e2oUvQEf&7VejHHw*IlB_o)LgF4k)tAj+S*YXi!{xKFK6fchVODQux55dE&7}5$ z)W{GVg=6D6w~#lEjr3i~@weuy@DSEkW!jsomz$EoiF;GYP9&u(M6)(Sqkz%!mKB>4 zIDssGgFsbx>=<<#-^Xc~Jw5@@rcdaFR}Fpy7%q^56-=`1pa!GXh|)RtVQ$#>#Q34j zNxY?-$Xmu8wcBu&s$%%E!0i1bUy5ntegFO3!-in&@uTWxt+|4GV4yiZj)nhr$`4)n z1;FD)9F@2-!&^0Jg`yE;LgGsUw=jR(R27F&tL)w%4rR%Nht3ChlS+~#f};hQda*0U zo^W(KBu&=EvzsSL%OsieN6~sPZ>`Oiv@OK zYfmvc1N4j#e0kU+GO-B+Hy94qrp6QGqqQ8h2W5paSrv*QB@^{{DclTBtQtjJ zsxsqg9&x=O?(OGoNnJS6XAsI|C9%-wL2$B3P+cvi-^h-^jihikhBV>Cp^->-=``)m z^$zv?VgHy^J!ZO&3cX985kTk`r;Mg#Z4oVN(O^YuI!ju;*cw+n5d?+Fvb98ONL<$wCBlzv>DT?7qm27(?%$R_cJ&@18y6AGi8JR^Cd|UBF4N z$t_?hW96J!WEYdp$I`-F<+0gwD)t{SNGY?X+vdhG6F!mZ-ITwb;HiRu7u6YlmI$YP zM5Fq;t`KLZBZVsA$0IFhs37^yzvI6sPwl{NpmfTuoURk%E8tC<`4L+xKmVW$Q<*Ct z*Fhivam4zGBS&Okrj1Zpq3_k){RJFQOrhSsBQJ^7K=v}3u7`lnL21|OhHSL$i5b28 zc;xYONu||@YA0a%Rth$(mpY)nU&G1R67LWy=z#v0ksSMa)JKbrls zIuCr>Qg*pxDGGfk_s|f6Vt95J>qm)~FBut84Swoic};a;UZqn?iB&C_%*phl8NksUT=qzM)>lFg%0|JD1EaVJi7^Mh;YJi;90qy=W~tdI&@Qbx1?- z#d**V-!_y1gf$}%U;dU69w=kLtEgvTRke_)-w>Spk4OB)qhB!ZV~CWddn#2>C{0OV zRBfNhAprC^Z9Hww!aIm;LwDNjdg)26y;>k~;kpHmKG_yu`RU_;fW|vO1*!b1ZX;8I z$>n$SVR}8h8@p1C6TnOxZC7jP^KM_dd0xd|X8HUiYpim&0p5w4@M3qPONoO#;Coru zA{Q$E8=&tslbONi#YX!3e72R55#Mo2*c0Z}CXHLBhu<5g98aXm=MuoT!Ju*vg2c-c zfQAb?m*uW+w7GK7jWx$vu7qkn?M<7VD0`Blu(yS2t(i=w&p&s1%R zEBRvUpZDv3f_$lMnio6i=j)`?e(VW!Yj;SZt=uKye`OlFjK}{9hJ`<;e`k84&LG`% zGF8-gh`qR?0yoixS^3e2pC6U^A2W^@I2(~Jf-P8JsN6?1FVM&*UnMItqD)H!E=Z(a zpKWGJp|;UfIa^FkTx>dV@`g;tk(8vWC-x}|6BQZtfcahaS{edzcW~rSUlxKo&xd>> zP3KP-=$!-xu7Dlap4YC7(w%XyA22{cj!ZL5b=(7$@Vse1B0s!*zTw4CY5PgOodDC# z5@QcgiRI#VBtq=wBKiw~Ch#O1Lp8mjx>sJmtayc2trJ5MgSpsSTzdr;#1_Lwdd7@n ze#3zg)+Hq8)-5$D`no2s?RHm?=19I4FH_!9mi6)87kR3~pcNdhae8oI48O*3{_4@= z`*HdRxN`d1D?!E_wX}PDz}wr?Wx|C^LaB$9-h*3Vg0{y6%QQFmJU>h&BLgFyuM+Yv z{3dR&BA4mqc9~m_EampKhH`%4Jclc!p43Ap@#_X@+A;#;?yu)XaD@Xgo8x;6P8sjf zopDWVLB1p$QiHu37h0l#lEpV)9uv`gDuYLm!87p|L#`9LSBLY(UcKmk30{do7PNLe zAyxePAESW+TSKF{3=9u>7nZxPDh3PJ4ZnIqIr5kWt~DxW{qT&9cxxilZipJjE%RZM|DqG8Y`%vQOkwJ#UIEvyx^Y+m|KA0>Q zc2FYijWMR!#CTBH?-ZyPF0e{(U3ldub4xJ>35} z=t5BR>Y`;KoO=b(x)8a&;=tYi>$n~Easg@3{xsjT(5Mg<{X__(J}RIA#vf@68w-)t zCkT|p{EJ?gl!)6tE?_qHzY+lge~<`a(oY54CI9PUp&#} ze|Q-+jaXSO9=` zB7aP|gJQr*)jwgKD&Fr06CtpMsDQ84|44a5_y}!v3WUWF7tlrHUr8F6rS~U<>CqyH zhPi;g#(&V*m=kM7$Xl0OI)k zi;Rf1Q7&Mf@4twL5JrgsQv&{)6H(8A^TB`d2Bigrh5bdBF=b#w%wNopp+>wLBSJhH zrvlc;|CMv&e@}6o3s{o$SE?D81GcC9#oTxdL`@3uZx<0la)JujlJ;LiiJ2IP@Cklk zPUe5fYZG#4-U!A?OEeTBc+we&TlnuV2BN5+48cD2d#yAeV%nJywp0AT&+WfM-an<8 touWb;M4U|7qh%wErtQ#N5TB+6ssI1Vrua|gen+1Wcr)A>6uti|{tvEkeOUki delta 7478 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 q>w)Ey0w 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 delta 7478 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 q>w)Ey0w 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 delta 7478 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 q>w)Ey0w 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 delta 7478 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 q> } } -task bintrayUpload() { +// Cannot override bintrayUpload in Gradle 4.9. Most likely due to a bug in +// the gradle-bintray-plugin. So we use `bintrayUploadAll` instead that can +// then depend on the original bintrayUpload task. +task bintrayUploadAll() { + group = 'Publishing' +} + +project.afterEvaluate { android.productFlavors.all { flavor -> - dependsOn "bintray${flavor.name.capitalize()}" + bintrayUploadAll.dependsOn "bintray${flavor.name.capitalize()}" } - group = 'Publishing' } task ojoUpload() { diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index a272ee6680..50de49ce65 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -115,6 +115,14 @@ bintray { } } +// Cannot override bintrayUpload in Gradle 4.9. Most likely due to a bug in +// the gradle-bintray-plugin. So we use `bintrayUploadAll` instead that can +// then depend on the original bintrayUpload task. +task bintrayUploadAll() { + dependsOn bintrayUpload + group = 'Publishing' +} + artifactory { contextUrl = 'https://oss.jfrog.org/artifactory' publish { diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 791b2bc3d7..b0e7425827 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -4,8 +4,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'com.github.dcendents.android-maven' -apply plugin: 'maven-publish' apply plugin: 'com.jfrog.artifactory' +apply plugin: 'maven-publish' apply plugin: 'findbugs' apply plugin: 'pmd' apply plugin: 'checkstyle' @@ -756,11 +756,17 @@ android.productFlavors.all { flavor -> } } -task bintrayUpload() { +// Cannot override bintrayUpload in Gradle 4.9. Most likely due to a bug in +// the gradle-bintray-plugin. So we use `bintrayUploadAll` instead that can +// then depend on the original bintrayUpload task. +task bintrayUploadAll() { + group = 'Publishing' +} + +project.afterEvaluate { android.productFlavors.all { flavor -> - dependsOn "bintray${flavor.name.capitalize()}" + bintrayUploadAll.dependsOn "bintray${flavor.name.capitalize()}" } - group = 'Publishing' } task ojoUpload() { From 3071cdf380c14298df6e8134506bd1a2d352029c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 1 Oct 2018 14:05:15 +0200 Subject: [PATCH 1321/2110] Better GitHub templates (#6209) --- .github/ISSUE_TEMPLATE.md | 46 ----------------------- .github/ISSUE_TEMPLATE/bug_report.md | 30 +++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 16 ++++++++ .github/ISSUE_TEMPLATE/question.md | 18 +++++++++ 4 files changed, 64 insertions(+), 46 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 94718dbc2f..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,46 +0,0 @@ - - -#### Goal - -> What do you want to achieve? - -#### Expected Results - -> ? - -#### Actual Results - -> E.g. full stack trace with exception - -#### Steps & Code to Reproduce - -> Describe your current debugging efforts. - -#### Code Sample - -```java - -> Your code here. Bigger samples should ideally be as separate Android Studio project, -> in gists/repositories or privately at help@realm.io) - -``` - -#### Version of Realm and tooling -Realm version(s): ? - -Realm sync feature enabled: yes/no - -Android Studio version: ? - -Which Android version and device: ? diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..b81b119fa6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: You think you have found a bug! + +--- + +#### Goal + + +#### Actual Results + + +#### Steps & Code to Reproduce + + + + +#### Version of Realm and tooling + +Realm version(s): ? + +Realm Sync feature enabled: Yes/No + +Android Studio version: ? + +Android Build Tools version: ? + +Gradle version: ? + +Which Android version and device(s): ? diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..e3480bcf09 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature request +about: Suggest an enhacement or new feature for this project + +--- + +#### Describe your problem or use case + + + +#### Describe the solution you'd like + + + +#### Additional context + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000000..988abdc1a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,18 @@ +--- +name: Question? +about: Got a question about how to use Realm? + +--- + +We only use Github issues for bugs or new feature ideas. +Please use one of the following options instead: + +* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=realm) +is good to get specific help about how to do use Realm. + +* [Realm Forums](https://forum.realm.io/) is great for general Realm questions +that are now allowed on StackOverflow like best practices etc. + +* [Realm Support](https://support.realm.io) can be used if you are a paying Realm Cloud user +or have a support contract with Realm. If you would like support contract +you can contact [Sales](sales@realm.io). From f3cab430237b4097aba21e9eb032a81d85febf2d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 1 Oct 2018 14:09:45 +0200 Subject: [PATCH 1322/2110] Spelling --- .github/ISSUE_TEMPLATE/question.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 988abdc1a4..a7d25e37fc 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -14,5 +14,5 @@ is good to get specific help about how to do use Realm. that are now allowed on StackOverflow like best practices etc. * [Realm Support](https://support.realm.io) can be used if you are a paying Realm Cloud user -or have a support contract with Realm. If you would like support contract +or have a support contract with Realm. If you would like a support contract you can contact [Sales](sales@realm.io). From 997cde4e83e7c6fffad18878c813f556f324fa8c Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Fri, 5 Oct 2018 15:00:38 +0200 Subject: [PATCH 1323/2110] Update CHANGELOG.md --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d2937f5d..fe6553ba91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ client protocol version = 25, server protocol version = 24`. ### Compatibility -* File format: ver. 7 (upgrades automatically from previous formats) * Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal @@ -36,8 +36,8 @@ * None ### Compatibility -* File format: ver. 7 (upgrades automatically from previous formats) -* Realm Object Server: 3.0.0 or later. +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal From 3df3e2efdf225c8e89fa677f6b9809177e144209 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 22 Oct 2018 14:35:56 +0200 Subject: [PATCH 1324/2110] Fix initial listener not triggering for query-based listeners (#6236) --- CHANGELOG.md | 6 ++++ .../internal/SubscriptionAwareOsResults.java | 3 +- .../io/realm/SyncedRealmIntegrationTests.java | 28 +++++++++++++++++++ .../EncryptedSynchronizedRealmTests.java | 4 +-- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d2937f5d..89473be557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.7.1 (YYYY-MM-DD) + +### Fixes +* `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline [#6235](https://github.com/realm/realm-java/issues/6235). + + ## 5.7.0 (2017-09-24) ## Enhancements diff --git a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java index c9ab8d4e0e..0f601cde9c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java @@ -16,8 +16,6 @@ package io.realm.internal; -import javax.annotation.Nullable; - import io.realm.RealmChangeListener; import io.realm.internal.core.DescriptorOrdering; import io.realm.internal.sync.OsSubscription; @@ -85,6 +83,7 @@ private void triggerDelayedChangeListener() { // errors and a completed subscription if (delayedNotificationPtr == 0 && subscription != null + && !firstCallback && subscription.getState() != OsSubscription.SubscriptionState.ERROR && subscription.getState() != OsSubscription.SubscriptionState.COMPLETE) { return; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index ff2eeb70b0..551c872067 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -29,6 +29,7 @@ import java.util.Random; import java.util.UUID; +import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmMigrationNeededException; @@ -439,4 +440,31 @@ public void onError(SyncSession session, ObjectServerError error) { looperThread.closeAfterTest(realm); } + + /** + * Tests https://github.com/realm/realm-java/issues/6235 + * This checks that the INITIAL callback is called for query-based notifications even when + * the device is offline. + */ + @Test + @RunTestInLooperThread + public void listenersTriggerWhenOffline() { + SyncUser user = SyncTestUtils.createTestUser(); // Creating a fake user will make it behave as "offline" + String url = "http://foo.com/offlineListeners"; + SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, url) + .build(); + Realm realm = Realm.getInstance(config); + looperThread.closeAfterTest(realm); + + RealmResults results = realm.where(AllTypes.class).findAllAsync(); + + looperThread.keepStrongReference(results); + results.addChangeListener((objects, changeSet) -> { + if(changeSet.getState() == OrderedCollectionChangeSet.State.INITIAL) { + assertTrue(results.isLoaded()); + assertFalse(changeSet.isCompleteResult()); + looperThread.testComplete(); + } + }); + } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 7f9f185107..4c7baeece9 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -126,9 +126,7 @@ public void onError(SyncSession session, ObjectServerError error) { realm.createObject(StringOnly.class).setChars("Hi Alice"); realm.commitTransaction(); - // STEP 2: make sure the changes gets to the server - SyncManager.getSession(configWithEncryption).uploadAllLocalChanges(); - + // STEP 2: Close the Realm and log the user out to forget about it. realm.close(); user.logOut(); From 74b83090e51b31b8ddff3642259941cad01f2b37 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 22 Oct 2018 14:38:05 +0200 Subject: [PATCH 1325/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89473be557..ebf870f427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.7.1 (YYYY-MM-DD) +## 5.7.1 (2017-10-22) ### Fixes * `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline [#6235](https://github.com/realm/realm-java/issues/6235). From 58c3bcd6e4a5dc9faee2d500a710ff9409462156 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 22 Oct 2018 14:38:59 +0200 Subject: [PATCH 1326/2110] Release v5.7.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 48dde53ec1..262122f679 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.7.1-SNAPSHOT \ No newline at end of file +5.7.1 \ No newline at end of file From ce05d08dabb7085c5f35a16373f1647aaa8ff29b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 22 Oct 2018 14:38:59 +0200 Subject: [PATCH 1327/2110] Prepare next release v5.7.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 262122f679..eeb3d06e0e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.7.1 \ No newline at end of file +5.7.2-SNAPSHOT \ No newline at end of file From 068ac09cbeb4537a7ad6022c6deccdcc67d4b5b1 Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Tue, 23 Oct 2018 11:48:00 +0200 Subject: [PATCH 1328/2110] Update CHANGELOG.md (#6245) --- CHANGELOG.md | 51 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d6b99911..894ba9713a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,50 @@ -## 5.7.1 (2017-10-22) +## 5.?.? (2018-mm-dd) -### Fixes -* `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline. Since 5.0.0 [#6235](https://github.com/realm/realm-java/issues/6235). +### Enhancements +* None + +### Fixed +* ?? (Issue [#??](https://github.com/realm/realm-java/issues/??), since ??). + +### Compatibility +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None + + +## 5.7.1 (2018-10-22) + +### Enhancements +* None + +### Fixed +* `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline. (Issue [#6235](https://github.com/realm/realm-java/issues/6235), since 5.0.0). +### Compatibility +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None -## 5.7.0 (2017-09-24) -## Enhancements +## 5.7.0 (2018-09-24) + +### Enhancements * [ObjectServer] Devices will now report download progress for read-only Realms which will allow the server to compact files sooner, saving server space. This does not affect the client. You will need to upgrade your Realm Object Server to at least version 3.11.0 or use [Realm Cloud](https://cloud.realm.io). If you try to connect to a ROS v3.10.x or previous, you will see an error like `Wrong protocol version in Sync HTTP request, client protocol version = 25, server protocol version = 24`. - + +### Fixed +* None + ### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) @@ -38,7 +69,7 @@ `sort()` would always be applied before `distinct()`. * Building with Android App Bundle is now supported ([#5977](https://github.com/realm/realm-java/issues/5977)). -### Fixes +### Fixed * None ### Compatibility @@ -54,7 +85,6 @@ ## 5.5.0 (2018-08-31) ### Enhancements - * [ObjectServer] Added `ConnectionState` enum describing the states a connection can be in. * [ObjectServer] Added `SyncSession.isConnected()` and `SyncSession.getConnectionState()`. * [ObjectServer] Added support for observing connection changes for a session using `SyncSession.addConnectionChangeListener()` and `SyncSession.removeConnectionChangeListener()`. @@ -70,18 +100,15 @@ * `SyncManager.addCustomRequestHeaders(Map headers, String host)` * `SyncConfiguration.Builder.urlPrefix(String prefix)` -### Bug Fixes - +### Fixed * Methods and classes requiring synchronized Realms have been removed from the standard AAR package. They are now only visible when enabling synchronized Realms in Gradle. The methods and classes will still be visible in the source files and docs, but annotated with `@ObjectServer` (#5799). ### Internal - * Updated to Realm Sync 3.9.4 * Updated to Realm Core 5.8.0 * Updated to Object Store commit: b0fc2814d9e6061ce5ba1da887aab6cfba4755ca ### Credits - * Thanks to @lucasdornelasv for improving the performance of `Realm.copyToRealm()`, `Realm.copyToRealmOrUpdate()` and `Realm.copyFromRealm()` #(6124). From 1c71cac78dfe029c6a8b3822fbf338c54693383e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 26 Oct 2018 19:14:49 +0200 Subject: [PATCH 1329/2110] Expose subscriptions more directly (#6231) --- CHANGELOG.md | 24 +- realm/config/findbugs/findbugs-filter.xml | 3 + .../java/io/realm/DynamicRealmTests.java | 26 +- .../io/realm/LinkingObjectsDynamicTests.java | 46 ++-- .../io/realm/LinkingObjectsManagedTests.java | 33 ++- .../java/io/realm/RealmAsyncQueryTests.java | 1 - .../androidTest/java/io/realm/RealmTests.java | 2 + .../RunTestInLooperThreadLifeCycleTest.java | 3 +- .../java/io/realm/RxJavaTests.java | 48 ++-- .../io/realm/internal/RealmNotifierTests.java | 2 +- .../java/io/realm/SyncManagerTests.java | 2 + .../java/io/realm/SyncedRealmQueryTests.java | 180 ++++++++++++++ .../java/io/realm/SyncedRealmTests.java | 56 +++++ .../realm-library/src/main/cpp/CMakeLists.txt | 1 + .../src/main/cpp/io_realm_RealmQuery.cpp | 59 +++++ realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/util.cpp | 13 + .../src/main/java/io/realm/BaseRealm.java | 3 +- .../src/main/java/io/realm/Realm.java | 42 ++++ .../src/main/java/io/realm/RealmCache.java | 81 ++++++- .../src/main/java/io/realm/RealmQuery.java | 69 ++++++ .../io/realm/internal/ObjectServerFacade.java | 16 +- .../main/java/io/realm/sync/Subscription.java | 228 ++++++++++++++++++ .../java/io/realm/SyncManager.java | 5 +- .../DownloadingRealmInterruptedException.java | 3 + .../internal/SyncObjectServerFacade.java | 49 +++- .../permissions/ObjectPermissionsModule.java | 7 +- .../io/realm/IsolatedIntegrationTests.java | 8 +- .../io/realm/StandardIntegrationTest.java | 8 +- .../java/io/realm/SyncSessionTests.java | 90 ++++--- .../io/realm/SyncedRealmIntegrationTests.java | 27 ++- .../java/io/realm/objectserver/AuthTests.java | 8 +- .../objectserver/QueryBasedSyncTests.java | 105 +++++++- .../java/io/realm/SyncTestUtils.java | 32 +-- .../testUtils/java/io/realm/TestHelper.java | 3 - .../java/io/realm/rule/RunInLooperThread.java | 72 ++++-- 36 files changed, 1163 insertions(+), 194 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp create mode 100644 realm/realm-library/src/main/java/io/realm/sync/Subscription.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 894ba9713a..abbd226473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.?.? (2018-mm-dd) +## 5.?.? (2018-MM-DD) ### Enhancements * None @@ -15,6 +15,26 @@ * None +## 5.8.0 (YYYY-MM-DD) + +### Enhancements +* [ObjectServer] Added Subscription class available to Query-based Realms. This exposes a Subscription more directly. This class is in beta. [#6231](https://github.com/realm/realm-java/pull/6231). +* [ObjectServer] Added `Realm.getSubscriptions()`, `Realm.getSubscriptions(String pattern)` and `Realm.getSubscription` to make it easier to find existing subscriptions. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). +* [ObjectServer] Added `RealmQuery.subscribe()` and `RealmQuery.subscribe(String name)` to subscribe immediately inside a transaction. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). +* [ObjectServer] Added support for subscribing directly inside `SyncConfiguration.initialData()`. This can be coupled with `SyncConfiguration.waitForInitialRemoteData()` in order to block a Realm from opening until the initial subscriptions are ready and have downloaded data. This API are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). + +### Fixed +* ?? (Issue [#??](https://github.com/realm/realm-java/issues/??), since ??). + +### Compatibility +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None + + ## 5.7.1 (2018-10-22) ### Enhancements @@ -29,7 +49,7 @@ * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal -* None +* Updated to Object Store commit: 362b886628b3aefc5b7a0bc32293d794dc1d4ad5 ## 5.7.0 (2018-09-24) diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index eb4b1c96ff..5a5d451546 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -39,5 +39,8 @@ + + + diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 573c1e21d7..6dba5227a9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -695,19 +695,27 @@ public void onSuccess(DynamicRealm realm) { @Test @RunTestInLooperThread public void getInstanceAsync_nullConfigShouldThrow() { - thrown.expect(IllegalArgumentException.class); - DynamicRealm.getInstanceAsync(null, new DynamicRealm.Callback() { - @Override - public void onSuccess(DynamicRealm realm) { - fail(); - } - }); + try { + //noinspection ConstantConditions + DynamicRealm.getInstanceAsync(null, new DynamicRealm.Callback() { + @Override + public void onSuccess(DynamicRealm realm) { + fail(); + } + }); + } catch (IllegalArgumentException ignored) { + } + looperThread.testComplete(); } @Test @RunTestInLooperThread public void getInstanceAsync_nullCallbackShouldThrow() { - thrown.expect(IllegalArgumentException.class); - DynamicRealm.getInstanceAsync(defaultConfig, null); + try { + //noinspection ConstantConditions + DynamicRealm.getInstanceAsync(defaultConfig, null); + } catch (IllegalArgumentException ignored) { + } + looperThread.testComplete(); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index 9d881dd773..0280be4916 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -475,17 +475,18 @@ public void execute(Realm realm) { }); final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); - try { - final DynamicRealmObject targetAsync = dynamicRealm.where(BacklinksTarget.CLASS_NAME) - .equalTo(BacklinksTarget.FIELD_ID, 1L).findFirstAsync(); - // precondition - assertFalse(targetAsync.isLoaded()); + looperThread.closeAfterTest(dynamicRealm); + final DynamicRealmObject targetAsync = dynamicRealm.where(BacklinksTarget.CLASS_NAME) + .equalTo(BacklinksTarget.FIELD_ID, 1L).findFirstAsync(); + // precondition + assertFalse(targetAsync.isLoaded()); - thrown.expect(IllegalStateException.class); + try { targetAsync.linkingObjects(BacklinksSource.CLASS_NAME, BacklinksSource.FIELD_CHILD); - } finally { - dynamicRealm.close(); + fail(); + } catch (IllegalStateException ignored) { } + looperThread.testComplete(); } @Test @@ -505,25 +506,26 @@ public void execute(Realm realm) { }); final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); - try { - final DynamicRealmObject target = dynamicRealm.where(BacklinksTarget.CLASS_NAME) - .equalTo(BacklinksTarget.FIELD_ID, 1L).findFirst(); + looperThread.closeAfterTest(dynamicRealm); + final DynamicRealmObject target = dynamicRealm.where(BacklinksTarget.CLASS_NAME) + .equalTo(BacklinksTarget.FIELD_ID, 1L).findFirst(); - dynamicRealm.executeTransaction(new DynamicRealm.Transaction() { - @Override - public void execute(DynamicRealm realm) { - target.deleteFromRealm(); - } - }); + dynamicRealm.executeTransaction(new DynamicRealm.Transaction() { + @Override + public void execute(DynamicRealm realm) { + target.deleteFromRealm(); + } + }); - // precondition - assertFalse(target.isValid()); + // precondition + assertFalse(target.isValid()); - thrown.expect(IllegalStateException.class); + try { target.linkingObjects(BacklinksSource.CLASS_NAME, BacklinksSource.FIELD_CHILD); - } finally { - dynamicRealm.close(); + fail(); + } catch (IllegalStateException ignored) { } + looperThread.testComplete(); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index ef0e68ffbc..2b6b8c5019 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -434,10 +434,13 @@ public void execute(Realm realm) { // precondition assertFalse(targetAsync.isLoaded()); - thrown.expect(IllegalStateException.class); - //noinspection ResultOfMethodCallIgnored - targetAsync.getParents(); - fail(); + try { + //noinspection ResultOfMethodCallIgnored + targetAsync.getParents(); + fail(); + } catch (IllegalStateException ignore) { + } + looperThread.testComplete(); } @Test @@ -471,10 +474,13 @@ public void execute(Realm realm) { // precondition assertFalse(target.isValid()); - thrown.expect(IllegalStateException.class); - //noinspection ResultOfMethodCallIgnored - target.getParents(); - fail(); + try { + //noinspection ResultOfMethodCallIgnored + target.getParents(); + fail(); + } catch (IllegalStateException ignore) { + } + looperThread.testComplete(); } @Test @@ -509,10 +515,13 @@ public void execute(Realm realm) { // precondition assertFalse(target.isValid()); - thrown.expect(IllegalStateException.class); - //noinspection ResultOfMethodCallIgnored - target.getParents(); - fail(); + try { + //noinspection ResultOfMethodCallIgnored + target.getParents(); + fail(); + } catch (IllegalStateException ignore) { + } + looperThread.testComplete(); } /** diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index 09a982160e..bc0c9a3a10 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -462,7 +462,6 @@ public void onSuccess() { public void run() { // Manually call refresh, so the did_change will be triggered. foregroundRealm.sharedRealm.refresh(); - foregroundRealm.setAutoRefresh(true); } }); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 8ab30fc5a1..453cadea35 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -4322,6 +4322,7 @@ public void onSuccess(Realm realm) { fail(); } }); + looperThread.testComplete(); } @Test @@ -4329,6 +4330,7 @@ public void onSuccess(Realm realm) { public void getInstanceAsync_nullCallbackShouldThrow() { thrown.expect(IllegalArgumentException.class); Realm.getInstanceAsync(realmConfig, null); + looperThread.testComplete(); } // Verify that the logic for waiting for the users file dir to be come available isn't totally broken diff --git a/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java b/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java index cc70851ced..f0945fbdc6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java @@ -43,7 +43,8 @@ * - @Before() * - @RunTestInLooperThread/@Test * - @After : This is called when exiting the test method. Warning: Looper test is still running. - * - looperThread.runAfterTest(Runnable) : This is called when the LooperTest either succeed or fails. + * - looperThread.runAfterTest(Runnable) : This is called when `testComplete()` is called. This can + * be both before and after `@After` has run. */ @RunWith(AndroidJUnit4.class) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index 0e3d29d2df..37fd95afd7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -57,15 +57,10 @@ public class RxJavaTests { @Rule public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); + @Rule - public final RunInLooperThread looperThread = new RunInLooperThread() { - @Override - public void looperTearDown() { - if (subscription != null && !subscription.isDisposed()) { - subscription.dispose(); - } - } - }; + public final RunInLooperThread looperThread = new RunInLooperThread(); + @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @@ -76,6 +71,11 @@ public void looperTearDown() { public void setUp() throws Exception { // For non-LooperThread tests. realm = Realm.getInstance(configFactory.createConfiguration()); + looperThread.runAfterTest(() -> { + if (subscription != null && !subscription.isDisposed()) { + subscription.dispose(); + } + }); } @After @@ -936,30 +936,16 @@ public void realmResults_gcStressTest() { realm.commitTransaction(); for (int i = 0; i < TEST_SIZE; i++) { - // Doesn't keep a reference to the Observable. realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, i).findAllAsync().asFlowable() - .filter(new Predicate>() { - @Override - public boolean test(RealmResults results) throws Exception { - return results.isLoaded(); - } - }) + .filter(results -> results.isLoaded()) .take(1) // Unsubscribes from Realm. - .subscribe(new Consumer>() { - @Override - public void accept(RealmResults allTypes) throws Exception { - // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. - Runtime.getRuntime().gc(); - if (innerCounter.incrementAndGet() == TEST_SIZE) { - looperThread.testComplete(); - } - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - fail(throwable.toString()); + .subscribe(allTypes -> { + // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. + Runtime.getRuntime().gc(); + if (innerCounter.incrementAndGet() == TEST_SIZE) { + looperThread.testComplete(); } - }); + }, throwable -> fail(throwable.toString())); } } @@ -972,6 +958,7 @@ public void dynamicRealmResults_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); + looperThread.closeAfterTest(realm); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { @@ -995,7 +982,6 @@ public void accept(RealmResults dynamicRealmObjects) throws // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. Runtime.getRuntime().gc(); if (innerCounter.incrementAndGet() == TEST_SIZE) { - realm.close(); looperThread.testComplete(); } } @@ -1061,6 +1047,7 @@ public void dynamicRealmObject_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); + looperThread.closeAfterTest(realm); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { @@ -1084,7 +1071,6 @@ public void accept(DynamicRealmObject dynamicRealmObject) throws Exception { // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. Runtime.getRuntime().gc(); if (innerCounter.incrementAndGet() == TEST_SIZE) { - realm.close(); looperThread.testComplete(); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 86f42ff0fc..9c5cfcf4eb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -100,8 +100,8 @@ public void addChangeListener_byLocalChanges() { public void onChange(OsSharedRealm sharedRealm) { // Transaction has been committed in core, but commitTransaction hasn't returned in java. assertFalse(commitReturns.get()); - looperThread.testComplete(); sharedRealm.close(); + looperThread.testComplete(); } }); sharedRealm.beginTransaction(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 82be4408c3..101423ac7b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -95,6 +95,8 @@ public void tearDown() { userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); } SyncManager.reset(); + BaseRealm.applicationContext = null; // Required for Realm.init() to work + Realm.init(InstrumentationRegistry.getTargetContext()); } @Test diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java new file mode 100644 index 0000000000..53ff16cdc3 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java @@ -0,0 +1,180 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import io.realm.entities.AllTypes; +import io.realm.entities.Dog; +import io.realm.rule.RunInLooperThread; +import io.realm.sync.Subscription; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Testing sync specific methods on {@link RealmQuery}. + */ +@RunWith(AndroidJUnit4.class) +public class SyncedRealmQueryTests { + + @Rule + public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); + + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Realm realm; + private DynamicRealm dynamicRealm; + + @After + public void tearDown() { + if (realm != null && !realm.isClosed()) { + realm.close(); + } + if (dynamicRealm != null && !dynamicRealm.isClosed()) { + dynamicRealm.close(); + } + for (SyncUser user : SyncUser.all().values()) { + user.logOut(); + } + } + + private Realm getPartialRealm() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/partialSync") + .build(); + realm = Realm.getInstance(config); + return realm; + } + + private Realm getFullySyncRealm() { + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/fullSync") + .fullSynchronization() + .build(); + realm = Realm.getInstance(config); + return realm; + } + + @Test + public void subscribe() { + realm = getPartialRealm(); + realm.beginTransaction(); + RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); + Subscription sub = query.subscribe(); + assertTrue(sub.getName().startsWith("[AllTypes] ")); + assertEquals(Subscription.State.PENDING, sub.getState()); + assertEquals("", sub.getErrorMessage()); + assertEquals(query.getDescription(), sub.getQueryDescription()); + assertEquals("AllTypes", sub.getQueryClassName()); + } + + @Test + public void subscribe_withName() { + realm = getPartialRealm(); + realm.beginTransaction(); + RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); + Subscription sub = query.subscribe("sub"); + assertEquals("sub", sub.getName()); + assertEquals(Subscription.State.PENDING, sub.getState()); + assertEquals("", sub.getErrorMessage()); + assertEquals(query.getDescription(), sub.getQueryDescription()); + assertEquals("AllTypes", sub.getQueryClassName()); + } + + @Test + public void subscribe_throwIfNameIsAlreadyUsed() { + realm = getPartialRealm(); + realm.beginTransaction(); + realm.where(Dog.class).subscribe("foo"); + try { + realm.where(AllTypes.class).subscribe("foo"); + fail(); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void subscribe_throwOnDynamicRealm() { + getPartialRealm().close(); // Build schema + dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); + dynamicRealm.beginTransaction(); + RealmQuery query = dynamicRealm.where(AllTypes.CLASS_NAME); + try { + query.subscribe("sub"); + fail(); + } catch (IllegalStateException ignore) { + } + } + + @Test + public void subscribe_throwIfOutsideWriteTransaction() { + realm = getPartialRealm(); + RealmQuery query = realm.where(AllTypes.class); + try { + query.subscribe("sub"); + fail(); + } catch (IllegalStateException ignore) { + } + } + + @Test + public void subscribe_throwIfBasedOnList() { + realm = getPartialRealm(); + realm.beginTransaction(); + realm.createObject(AllTypes.class).getColumnRealmList().add(new Dog("fido")); + RealmQuery query = realm.where(AllTypes.class).findFirst().getColumnRealmList().where(); + try { + query.subscribe("sub"); + fail(); + } catch (IllegalStateException ignore) { + } + } + + @Test + public void subscribe_throwIfNonPartialRealm() { + realm = getFullySyncRealm(); + realm.beginTransaction(); + RealmQuery query = realm.where(AllTypes.class); + try { + query.subscribe("sub"); + fail(); + } catch (IllegalStateException ignore) { + } + } + + @Test + public void subscribe_throwIfRealmClosed() { + realm = getPartialRealm(); + realm.beginTransaction(); + RealmQuery query = realm.where(AllTypes.class); + realm.close(); + try { + query.subscribe("sub"); + fail(); + } catch (IllegalStateException ignore) { + } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index d1f1f9e5a7..77954d7691 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -32,8 +32,11 @@ import io.realm.objectserver.utils.Constants; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; +import io.realm.sync.Subscription; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -253,4 +256,57 @@ public boolean shouldCompact(long totalBytes, long usedBytes) { assertTrue(originalSize > compactedSize); } + @Test + public void getSubscriptions() { + realm = getPartialRealm(); + RealmResults subscriptions = realm.getSubscriptions(); + assertEquals(0, subscriptions.size()); + + realm.executeTransaction(r -> { + r.where(AllTypes.class).subscribe("sub1"); + }); + + assertEquals(1, subscriptions.size()); + assertEquals("sub1", subscriptions.first().getName()); + } + + @Test + public void getSubscriptions_withPattern() { + realm = getPartialRealm(); + assertEquals(0, realm.getSubscriptions("sub?").size()); + + realm.executeTransaction(r -> { + r.where(AllTypes.class).subscribe("sub1"); + r.where(AllTypes.class).subscribe("sub2"); + }); + + assertEquals(0, realm.getSubscriptions("sub").size()); + assertEquals(2, realm.getSubscriptions("sub?").size()); + assertEquals(2, realm.getSubscriptions("s*").size()); + } + + @Test + public void getSubscriptions_withPattern_throwsIfNullPattern() { + realm = getPartialRealm(); + try { + //noinspection ConstantConditions + realm.getSubscriptions(null); + fail(); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void getSubscription() { + realm = getPartialRealm(); + assertNull(realm.getSubscription("sub")); + + realm.executeTransaction(r -> { + r.where(AllTypes.class).subscribe("sub"); + }); + + Subscription sub = realm.getSubscription("sub"); + assertNotNull(sub); + assertEquals("sub", sub.getName()); + } } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 5430185427..5672cdb318 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -56,6 +56,7 @@ endif() string(TOLOWER ${CMAKE_BUILD_TYPE} build_type_FOLDER) set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/classes/${REALM_FLAVOR}/${build_type_FOLDER}/) set(classes_LIST + io.realm.RealmQuery io.realm.internal.Table io.realm.internal.CheckedRow io.realm.internal.Util io.realm.internal.UncheckedRow io.realm.internal.TableQuery io.realm.internal.OsSharedRealm io.realm.internal.TestUtil diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp new file mode 100644 index 0000000000..03c66b6b49 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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 "io_realm_RealmQuery.h" + +#include +#include +#if REALM_ENABLE_SYNC +#include +#endif + +#include "util.hpp" + + +using namespace realm; + +JNIEXPORT jstring JNICALL Java_io_realm_RealmQuery_nativeSerializeQuery(JNIEnv* env, jclass, jlong table_query_ptr, jlong descriptor_ptr) +{ + TR_ENTER() + try { + auto query = reinterpret_cast(table_query_ptr); + auto descriptor = reinterpret_cast(descriptor_ptr); + std::string serialized_query = query->get_description() + " " + descriptor->get_description(query->get_table()); + return to_jstring(env, serialized_query); + } + CATCH_STD() + return to_jstring(env, ""); +} + +JNIEXPORT jlong JNICALL Java_io_realm_RealmQuery_nativeSubscribe(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_name, jlong table_query_ptr, jlong descriptor_ptr) +{ + TR_ENTER() + try { + auto realm = *reinterpret_cast(shared_realm_ptr); + auto name = util::Optional(JStringAccessor(env, j_name)); + auto query = reinterpret_cast(table_query_ptr); + auto descriptor = reinterpret_cast(descriptor_ptr); + Results r(realm, *query, *descriptor); +#if REALM_ENABLE_SYNC + RowExpr row = partial_sync::subscribe_blocking(r, name); + return to_jlong_or_not_found(row.get_index()); +#endif + } + CATCH_STD() + return realm::npos; +} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 7e19c51af7..1f91c82eb3 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 7e19c51af72c3343b453b8a13c82dfda148e4bbc +Subproject commit 1f91c82eb34cf4eaa2900794a9268390876f19f1 diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index bc44a9303c..23371bb901 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -30,9 +30,14 @@ #include "results.hpp" #include "list.hpp" #include "java_exception_def.hpp" +#if REALM_ENABLE_SYNC +#include "sync/partial_sync.hpp" +#endif #include "jni_util/java_exception_thrower.hpp" + + using namespace std; using namespace realm; using namespace realm::util; @@ -120,6 +125,14 @@ void ConvertException(JNIEnv* env, const char* file, int line) } ThrowException(env, kind, e.what()); } +#if REALM_ENABLE_SYNC + catch (partial_sync::InvalidRealmStateException& e) { + ThrowException(env, IllegalState, e.what()); + } + catch (partial_sync::ExistingSubscriptionException& e) { + ThrowException(env, IllegalArgument, e.what()); + } +#endif catch (std::logic_error e) { ThrowException(env, IllegalState, e.what()); } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 83b0bd14bd..67f6caf905 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -121,7 +121,8 @@ public void onSchemaChanged() { initializationCallback = new OsSharedRealm.InitializationCallback() { @Override public void onInit(OsSharedRealm sharedRealm) { - initialDataTransaction.execute(Realm.createInstance(sharedRealm)); + Realm instance = Realm.createInstance(sharedRealm); + initialDataTransaction.execute(instance); } }; } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index f1d21efeaf..8a4b8b2ff3 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -72,6 +72,7 @@ import io.realm.internal.annotations.ObjectServer; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; +import io.realm.sync.Subscription; import io.realm.sync.permissions.ClassPermissions; import io.realm.sync.permissions.ClassPrivileges; import io.realm.sync.permissions.RealmPermissions; @@ -1866,6 +1867,47 @@ public ClassPermissions getPermissions(Class clazz) { .findFirst(); } + /** + * Returns a list of all known subscriptions, regardless of their status. + * + * @return a list of all known subscriptions. + */ + @Beta + @ObjectServer + public RealmResults getSubscriptions() { + return where(Subscription.class).findAll(); + } + + /** + * Returns a list of all subscriptions that match a given pattern. {@code *} can be used to + * indicate any number of unknown characters and {@code ?} represents a single unknown character. + * + * @param pattern which subscriptions to find. + * @return list of subscriptions that match the pattern. + * @throws IllegalArgumentException if an empty or {@code null} pattern is provided. + */ + @Beta + @ObjectServer + public RealmResults getSubscriptions(String pattern) { + if (Util.isEmptyString(pattern)) { + throw new IllegalArgumentException("Non-empty 'pattern' required"); + } + return where(Subscription.class).like("name", pattern).findAll(); + } + + /** + * Returns the first subscription that matches the given name. + * + * @param name the name of the subscription to find. + * @return returns the subscription that matches the name or {@code null} if no subscription matches the name. + */ + @Beta + @ObjectServer + @Nullable + public Subscription getSubscription(String name) { + return where(Subscription.class).equalTo("name", name).findFirst(); + } + Table getTable(Class clazz) { return schema.getTable(clazz); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 3dac06e938..4434c7d9d1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -15,6 +15,8 @@ */ package io.realm; +import android.os.SystemClock; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -286,35 +288,32 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo Class realmClass) { RefAndCount refAndCount = refAndCountMap.get(RealmCacheType.valueOf(realmClass)); + boolean firstRealmInstanceInProcess = (getTotalGlobalRefCount() == 0); + boolean realmFileIsBeingCreated = !configuration.realmExists(); - if (getTotalGlobalRefCount() == 0) { + if (firstRealmInstanceInProcess) { copyAssetFileIfNeeded(configuration); - boolean fileExists = configuration.realmExists(); - OsSharedRealm sharedRealm = null; try { if (configuration.isSyncConfiguration()) { // If waitForInitialRemoteData() was enabled, we need to make sure that all data is downloaded // before proceeding. We need to open the Realm instance first to start any potential underlying - // SyncSession so this will work. TODO: This needs to be decoupled. - if (!fileExists) { + // SyncSession so this will work. + if (realmFileIsBeingCreated) { sharedRealm = OsSharedRealm.getInstance(configuration); try { - ObjectServerFacade.getSyncFacadeIfPossible().downloadRemoteChanges(configuration); + ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialRemoteChanges(configuration); } catch (Throwable t) { // If an error happened while downloading initial data, we need to reset the file so we can // download it again on the next attempt. sharedRealm.close(); sharedRealm = null; - // FIXME: We don't have a way to ensure that the Realm instance on client thread has been - // closed for now. - // https://github.com/realm/realm-java/issues/5416 - BaseRealm.deleteRealm(configuration); + deleteRealmFileOnDisk(configuration); throw t; } } } else { - if (fileExists) { + if (!realmFileIsBeingCreated) { // Primary key problem only exists before we release sync. sharedRealm = OsSharedRealm.getInstance(configuration); Table.migratePrimaryKeyTableIfNeeded(sharedRealm); @@ -326,7 +325,7 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo } } - // We are holding the lock, and we can set the invalidated configuration since there is no global ref to it. + // We are holding the lock, and we can set the valid configuration since there is no global ref to it. this.configuration = configuration; } else { // Throws exception if validation failed. @@ -340,6 +339,13 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo if (realmClass == Realm.class) { // RealmMigrationNeededException might be thrown here. realm = Realm.createInstance(this); + + // If `waitForInitialRemoteData` data is set, we also want to ensure that all subscriptions + // are fully ACTIVE before proceeding. Most of the Realm is initialized during a write + // transaction. So we cannot download subscription data until all other initializers have run. + // At this point we also have access to all normal APIs as the schema is fully initialized. + synchronizeInitialSubscriptionsIfNeeded((Realm) realm, realmFileIsBeingCreated); + } else if (realmClass == DynamicRealm.class) { realm = DynamicRealm.createInstance(this); } else { @@ -361,6 +367,57 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo return (E) refAndCount.localRealm.get(); } + /** + * Synchronize all initial subscriptions to disk (if needed). + * + * If activating the subscriptions fails for a new Realm file, the file will be deleted so a new + * attempt can be done later. Old Realm files will be left alone. + * + * This method is not threadsafe. Synchronization should happen outside it. + * + * @param realm Realm instance to synchronize instances for. It is safe to close this Realm if an exception is thrown. + * @param {@code true} if the file existed on disk before trying to open the Realm. + */ + private static void synchronizeInitialSubscriptionsIfNeeded(Realm realm, boolean realmFileIsBeingCreated) { + if (realmFileIsBeingCreated) { + try { + ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialSubscriptions(realm); + } catch (Throwable t) { + realm.close(); + deleteRealmFileOnDisk(realm.getConfiguration()); + } + } + } + + /** + * Attempts to delete the underlying Realm. Any errors happening here will just be + * outputted to logcat instead of thrown as this method is only called from other exception + * handlers which have more important exceptions to show to the user. + * + * This method is not threadsafe. Synchronization should happen outside it. + */ + private static void deleteRealmFileOnDisk(RealmConfiguration configuration) { + // FIXME: We don't have a way to ensure that the Realm instance on client thread has been closed for now. + // https://github.com/realm/realm-java/issues/5416 + int attempts = 5; + boolean success = false; + while (attempts > 0 && !success) { + try { + success = BaseRealm.deleteRealm(configuration); + } catch (IllegalStateException e) { + attempts--; + RealmLog.warn("Sync server still holds a reference to the Realm. It cannot be deleted. Retrying " + attempts + " more times"); + if (attempts > 0) { + SystemClock.sleep(15); + } + } + } + + if (!success) { + RealmLog.error("Failed to delete the underlying Realm file: " + configuration.getPath()); + } + } + /** * Releases a given {@link Realm} or {@link DynamicRealm} from cache. The instance will be closed by this method * if there is no more local reference to this Realm instance in current Thread. diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index d1e51d662d..55dff041d2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -17,16 +17,23 @@ package io.realm; +import android.text.TextUtils; + import java.util.Collections; import java.util.Date; import java.util.Locale; import javax.annotation.Nullable; +import io.realm.annotations.Beta; import io.realm.annotations.Required; +import io.realm.internal.CheckedRow; +import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsList; import io.realm.internal.OsResults; import io.realm.internal.PendingRow; +import io.realm.internal.UncheckedRow; +import io.realm.internal.annotations.ObjectServer; import io.realm.internal.core.QueryDescriptor; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; @@ -37,6 +44,7 @@ import io.realm.internal.core.DescriptorOrdering; import io.realm.internal.fields.FieldDescriptor; import io.realm.internal.sync.SubscriptionAction; +import io.realm.sync.Subscription; /** @@ -2007,6 +2015,63 @@ public Realm getRealm() { return (Realm) realm; } + /** + * Creates an anonymous subscription from this query or returns the existing Subscription if + * one already existed. + * + * @return the subscription representing this query. + * @throws IllegalStateException if this method is not called inside a write transaction or if + * the query is on a {@link DynamicRealm} + */ + @ObjectServer + @Beta + public Subscription subscribe() { + StringBuilder sb = new StringBuilder("["); + sb.append((table != null) ? table.getClassName() : ""); + sb.append("] "); + sb.append(nativeSerializeQuery(query.getNativePtr(), queryDescriptors.getNativePtr())); + String name = sb.toString(); + return subscribe(name); + } + + /** + * Creates an anonymous subscription from this query or returns the existing Subscription if + * one already existed. + * + * @return the name of the query. + * @return the subscription representing this query. + * @throws IllegalStateException if this method is not called inside a write transaction, if + * the query is on a {@link DynamicRealm} or a {@link RealmList}. + * @throws IllegalArgumentException if a subscription for a different query with the same name + * already exists. + */ + @ObjectServer + @Beta + public Subscription subscribe(String name) { + realm.checkIfValid(); + if (realm instanceof DynamicRealm) { + throw new IllegalStateException("'subscribe' is not supported for queries on Dynamic Realms."); + } + if (osList != null) { + throw new IllegalStateException("Cannot create subscriptions for queries based on a 'RealmList. Subscribe to the object holding the list instead.'"); + } + if (TextUtils.isEmpty(name)) { + throw new IllegalArgumentException("Non-empty 'name' required."); + } + long rowIndex = nativeSubscribe(realm.getSharedRealm().getNativePtr(), name, query.getNativePtr(), queryDescriptors.getNativePtr()); + CheckedRow row = ((Realm) realm).getTable(Subscription.class).getCheckedRow(rowIndex); + return realm.get(Subscription.class, null, row); + } + + /** + * Returns a textual description of this query. + * + * @return the textual description of the query. + */ + public String getDescription() { + return nativeSerializeQuery(query.getNativePtr(), queryDescriptors.getNativePtr()); + } + private boolean isDynamicQuery() { return className != null; } @@ -2125,4 +2190,8 @@ private long getSourceRowIndexForFirstObject() { private SchemaConnector getSchemaConnector() { return new SchemaConnector(realm.getSchema()); } + + private static native String nativeSerializeQuery(long tableQueryPtr, long descriptorPtr); + private static native long nativeSubscribe(long sharedRealmPtr, String name, long tableQueryPtr, long descriptorPtr); + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 4b6aaf46eb..d2562ab97a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -20,6 +20,7 @@ import java.lang.reflect.InvocationTargetException; +import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.exceptions.RealmException; @@ -98,13 +99,14 @@ public String getSyncServerCertificateFilePath(RealmConfiguration config) { } /** - * Block until all latest changes have been downloaded from the server. + * Block until all latest changes have been downloaded from the server. This should only + * be called the first time a Realm file is created. * * @throws {@code DownloadingRealmInterruptedException} if the thread was interrupted while blocked waiting for * this to complete. */ @SuppressWarnings("JavaDoc") - public void downloadRemoteChanges(RealmConfiguration config) { + public void downloadInitialRemoteChanges(RealmConfiguration config) { // Do nothing } @@ -123,4 +125,14 @@ public void addSupportForObjectLevelPermissions(RealmConfiguration.Builder build // Do nothing } + /** + * If the Realm is a Query-based Realm, ensure that all subscriptions are ACTIVE before + * proceeding. This should only be called when opening a Realm for the first time. + * + * @throws {@code DownloadingRealmInterruptedException} if the thread was interrupted while blocked waiting for + * this to complete. + */ + public void downloadInitialSubscriptions(Realm realm) { + // Do nothing + } } diff --git a/realm/realm-library/src/main/java/io/realm/sync/Subscription.java b/realm/realm-library/src/main/java/io/realm/sync/Subscription.java new file mode 100644 index 0000000000..15429b0eb8 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/sync/Subscription.java @@ -0,0 +1,228 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.sync; + +import io.realm.RealmObject; +import io.realm.RealmQuery; +import io.realm.annotations.Beta; +import io.realm.annotations.Index; +import io.realm.annotations.RealmClass; +import io.realm.annotations.RealmField; +import io.realm.annotations.Required; +import io.realm.internal.annotations.ObjectServer; + +/** + * Subscriptions represents the data from the server that a device is interested in when using + * Query-based Realms. + *

              + * They are created automatically when using {@link RealmQuery#findAllAsync()} or {@link RealmQuery#findAllAsync(String)} + * on those Realms, but can also be created manually using {@link RealmQuery#subscribe()} and {@link RealmQuery#subscribe(String)}. + *

              + * As long as any subscription exist that include an object, that object will be present on the + * device. If an object is not covered by an active subscription it will be removed from the device, + * but not the server. + *

              + * Subscriptions are Realm objects, so deleting them e.g. by calling {@link RealmObject#deleteFromRealm()}, + * is the same as calling {@link #unsubscribe()}. + *

              + * Warning: Instances of this class should never be created directly through + * {@link io.realm.Realm#createObject(Class)} but only by using {@link RealmQuery#subscribe()} or + * {@link RealmQuery#subscribe(String)}. + */ +@ObjectServer +@RealmClass(name = "__ResultSets") +@Beta +public class Subscription extends RealmObject { + + /** + * The different states a Subscription can be in. + */ + public enum State { + /** + * An error occurred while creating or processing the subscription. + * See {@link #getErrorMessage()} for details on what went wrong. + */ + ERROR((byte) -1), + + /** + * The subscription has been created, but has not yet been processed by the sync + * server. + */ + PENDING((byte) 0), + + /** + * The subscription has been processed by the Realm Object Server and data is being synced + * to the device. + */ + ACTIVE((byte) 1), + + /** + * The subscription has been removed. Data is no longer being synchronized from the Realm + * Object Server, and the objects covered by this subscription might be deleted from the + * device if no other subscriptions include them. + */ + INVALIDATED(null); + + + private final Byte nativeValue; + + State(Byte nativeValue) { + this.nativeValue = nativeValue; + } + + /** + * Returns the native value representing this state. + * + * @return the native value representing this state. + */ + public Byte getValue() { + return nativeValue; + } + } + + public Subscription() { + // Required by Realm. + } + + /** + * Creates a unmanaged named subscription from a {@link RealmQuery}. + * This will not take effect until it has been added to the Realm. + * + * @param name name of the query. + * @param query the query to turn into a subscription. + */ + public Subscription(String name, RealmQuery query) { + this.name = name; + this.query = query.getDescription(); + this.status = 0; + this.errorMessage = ""; + this.matchesProperty = ""; + } + + @Index + @Required + private String name; + + /** + * The underlying representation of the State + */ + private byte status; + + @Required + @RealmField("error_message") + private String errorMessage; + + @Required + @RealmField("matches_property") + private String matchesProperty; + + @Required + private String query; + + @RealmField("query_parse_counter") + private int queryParseCounter; + + /** + * Returns the name of the subscription. + * + * @return the name of the subscription. + */ + public String getName() { + return name; + } + + /** + * Returns a textual description of the query that created this subscription. + * + * @return a textual description of the query. + */ + public String getQueryDescription() { + return query; + } + + /** + * Returns the internal name of the Class being queried. + * + * @return the internal name of the of the class being queried. + */ + public String getQueryClassName() { + // Strip the __matches suffix to end up with the class being queried. + String classQueried = matchesProperty; + return classQueried.substring(0, classQueried.length() - "_matches".length()); + } + + /** + * Returns the state of the subscription + * + * @return the state of the subscription. + * @see State + */ + public State getState () { + if (!RealmObject.isValid(this)) { + return State.INVALIDATED; + } else { + switch (status) { + case -1: + return State.ERROR; + case 0: + return State.PENDING; + case 1: + return State.ACTIVE; + default: + throw new IllegalArgumentException("Unknown subscription state value: " + status); + } + } + } + + /** + * Returns the error message if {@link #getState()} returned {@link State#ERROR}, otherwise + * the empty string is returned. + * + * @return the error string if the subscription encountered an error. + */ + public String getErrorMessage() { + return errorMessage; + } + + /** + * Cancels the subscription. After this, if the objects covered by the subscription are not + * part of any other subscription, they will be removed locally from the device (but not on the + * server). + *

              + * The effect of unsubscribing is not immediate. The local Realm must coordinate with the Realm + * Object Server before it can happen. When it happens, any objects removed will trigger a standard + * change notification, and from the perspective of the device it will look like they where + * deleted. + *

              + * Calling this method is the equivalent of calling {@link RealmObject#deleteFromRealm()}. + * + * @throws IllegalStateException if the Realm is not in a write transaction. + */ + public void unsubscribe() { + RealmObject.deleteFromRealm(this); + } + + @Override + public String toString() { + return "Subscription{" + + "name='" + name + '\'' + + ", status=" + getState().toString() + + ", errorMessage='" + errorMessage + '\'' + + ", className='" + getQueryClassName() + '\'' + + ", query='" + query + '\'' + + '}'; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 93ac40bbfb..2c1b4c560a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -16,6 +16,8 @@ package io.realm; +import android.os.SystemClock; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -44,6 +46,7 @@ import javax.net.ssl.X509TrustManager; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.exceptions.RealmError; import io.realm.internal.Keep; import io.realm.internal.Util; import io.realm.internal.network.AuthenticationServer; @@ -446,7 +449,7 @@ private static synchronized void removeSession(SyncConfiguration syncConfigurati } /** - * Retruns the all valid sessions belonging to the user. + * Returns the all valid sessions belonging to the user. * * @param syncUser the user to use. * @return the all valid sessions belonging to the user. diff --git a/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java b/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java index 0460d297d5..a7326edbab 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java +++ b/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java @@ -28,4 +28,7 @@ public DownloadingRealmInterruptedException(SyncConfiguration syncConfig, Throwa super("Realm was interrupted while downloading the latest changes from the server: " + syncConfig.getPath(), exception); } + public DownloadingRealmInterruptedException(SyncConfiguration syncConfig, String message) { + super("Realm was interrupted while downloading the latest changes from the server: " + syncConfig.getPath() + "\n" + message); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index c1c05f31c9..0de162bcc1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -23,9 +23,12 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Map; +import io.realm.Realm; import io.realm.RealmConfiguration; +import io.realm.RealmResults; import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.SyncSession; @@ -34,6 +37,7 @@ import io.realm.exceptions.RealmException; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.sync.permissions.ObjectPermissionsModule; +import io.realm.sync.Subscription; @SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep @@ -173,12 +177,19 @@ private void invokeRemoveSession(SyncConfiguration syncConfig) { } @Override - public void downloadRemoteChanges(RealmConfiguration config) { + public void downloadInitialRemoteChanges(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; if (syncConfig.shouldWaitForInitialRemoteData()) { SyncSession session = SyncManager.getSession(syncConfig); try { + if (!syncConfig.isFullySynchronizedRealm()) { + // For Query-based Realms we want to upload all our local changes + // first since those might include subscriptions the server needs to process. + // This means that once `downloadAllServerChanges` completes all + // initial subscriptions will also have been downloaded. + session.uploadAllLocalChanges(); + } session.downloadAllServerChanges(); } catch (InterruptedException e) { throw new DownloadingRealmInterruptedException(syncConfig, e); @@ -206,4 +217,40 @@ public boolean isPartialRealm(RealmConfiguration configuration) { public void addSupportForObjectLevelPermissions(RealmConfiguration.Builder builder) { builder.addModule(new ObjectPermissionsModule()); } + + @Override + public void downloadInitialSubscriptions(Realm realm) { + if (isPartialRealm(realm.getConfiguration())) { + SyncConfiguration syncConfig = (SyncConfiguration) realm.getConfiguration(); + if (syncConfig.shouldWaitForInitialRemoteData()) { + RealmResults pendingSubscriptions = realm.where(Subscription.class) + .equalTo("status", Subscription.State.PENDING.getValue()) + .findAll(); + SyncSession session = SyncManager.getSession(syncConfig); + + // Continue once all subscriptions are either ACTIVE or ERROR'ed. + while (!pendingSubscriptions.isEmpty()) { + try { + session.uploadAllLocalChanges(); // Uploads subscriptions (if any) + session.downloadAllServerChanges(); // Download subscriptions (if any) + } catch (InterruptedException e) { + throw new DownloadingRealmInterruptedException(syncConfig, e); + } + realm.refresh(); + } + + // If some of the subscriptions failed to become ACTIVE, report them and cancel opening + // the Realm. Note, this should only happen if the client is contacting an older + // version of the server which are lacking query support for features available + // in the client SDK. + RealmResults failedSubscriptions = realm.where(Subscription.class) + .equalTo("status", Subscription.State.ERROR.getValue()) + .findAll(); + if (!failedSubscriptions.isEmpty()) { + String errorMessage = "Some initial subscriptions encountered errors:" + Arrays.toString(failedSubscriptions.toArray()); + throw new DownloadingRealmInterruptedException(syncConfig, errorMessage); + } + } + } + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java b/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java index e1f6f27af3..0dc7279bdb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java @@ -1,18 +1,23 @@ package io.realm.internal.sync.permissions; import io.realm.annotations.RealmModule; +import io.realm.sync.Subscription; import io.realm.sync.permissions.ClassPermissions; import io.realm.sync.permissions.Permission; import io.realm.sync.permissions.RealmPermissions; import io.realm.sync.permissions.PermissionUser; import io.realm.sync.permissions.Role; +/** + * Realm model classses that are always part of Query-based Realms + */ @RealmModule(library = true, classes = { ClassPermissions.class, Permission.class, RealmPermissions.class, Role.class, - PermissionUser.class + PermissionUser.class, + Subscription.class }) public class ObjectPermissionsModule { } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java index 7578de0cf1..36f9622f07 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/IsolatedIntegrationTests.java @@ -22,7 +22,7 @@ public void setupTest() throws IOException { } @After - public void teardownTest() { + public void teardownTest() throws IOException { if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { // Non-looper tests can reset here SyncTestUtils.restoreEnvironmentAfterTest(); @@ -32,7 +32,11 @@ public void teardownTest() { looperThread.runAfterTest(new Runnable() { @Override public void run() { - SyncTestUtils.restoreEnvironmentAfterTest(); + try { + SyncTestUtils.restoreEnvironmentAfterTest(); + } catch (IOException e) { + throw new RuntimeException(e); + } stopSyncServer(); } }); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java index aea2b68327..e2ed36db87 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/StandardIntegrationTest.java @@ -48,7 +48,7 @@ public void setupTest() throws IOException { } @After - public void teardownTest() { + public void teardownTest() throws IOException { if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { // Non-looper tests can reset here SyncTestUtils.restoreEnvironmentAfterTest(); @@ -57,7 +57,11 @@ public void teardownTest() { looperThread.runAfterTest(new Runnable() { @Override public void run() { - SyncTestUtils.restoreEnvironmentAfterTest(); + try { + SyncTestUtils.restoreEnvironmentAfterTest(); + } catch (IOException e) { + throw new RuntimeException(e); + } } }); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 0f47cbd7d1..6300954d20 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -43,30 +43,41 @@ private interface SessionCallback { void onReady(SyncSession session); } - private SyncSession getSession() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - looperThread.closeAfterTest(Realm.getInstance(syncConfiguration)); - return SyncManager.getSession(syncConfiguration); + private void getSession(SessionCallback callback) { + // Work-around for a race conditions happening when shutting down a Looper test and + // Resetting the SyncManager + // The problem is the `@After` block which runs as soon as the test method has completed. + // For integration tests this will attempt to reset the SyncManager which will fail + // if Realms are still open as they hold a reference to a session object. + // By moving this into a Looper callback we ensure that a looper test can shutdown as + // intended. + // Generally it seems that using calling `RunInLooperThread.testComplete()` in a synchronous + looperThread.postRunnable((Runnable) () -> { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build(); + looperThread.closeAfterTest(Realm.getInstance(syncConfiguration)); + callback.onReady(SyncManager.getSession(syncConfiguration)); + }); } private void getActiveSession(SessionCallback callback) { - SyncSession session = getSession(); - if (session.isConnected()) { - callback.onReady(session); - } else { - session.addConnectionChangeListener(new ConnectionListener() { - @Override - public void onChange(ConnectionState oldState, ConnectionState newState) { - if (newState == ConnectionState.CONNECTED) { - session.removeConnectionChangeListener(this); - callback.onReady(session); + getSession(session -> { + if (session.isConnected()) { + callback.onReady(session); + } else { + session.addConnectionChangeListener(new ConnectionListener() { + @Override + public void onChange(ConnectionState oldState, ConnectionState newState) { + if (newState == ConnectionState.CONNECTED) { + session.removeConnectionChangeListener(this); + callback.onReady(session); + } } - } - }); - } + }); + } + }); } @Test(timeout=3000) @@ -323,9 +334,13 @@ public void onChange(RealmResults stringOnlies) { if (stringOnlies.size() == 2) { Assert.assertEquals("1", stringOnlies.get(0).getChars()); Assert.assertEquals("2", stringOnlies.get(1).getChars()); - adminRealm.close(); - testCompleted.countDown(); - handlerThread.quit(); + handler.post(() -> { + // Closing a Realm from inside a listener doesn't seem to remove the + // active session reference in Object Store + adminRealm.close(); + testCompleted.countDown(); + handlerThread.quit(); + }); } } }; @@ -340,7 +355,7 @@ public void onChange(RealmResults stringOnlies) { } }); - TestHelper.awaitOrFail(testCompleted, 60); + TestHelper.awaitOrFail(testCompleted); realm.close(); } @@ -543,13 +558,15 @@ public void run() { @Test @RunTestInLooperThread public void registerConnectionListener() { - SyncSession session = getSession(); - session.addConnectionChangeListener((oldState, newState) -> { - if (newState == ConnectionState.DISCONNECTED) { - looperThread.testComplete(); - } + getSession(session -> { + session.addConnectionChangeListener((oldState, newState) -> { + if (newState == ConnectionState.DISCONNECTED) { + // Closing a Realm inside a connection listener doesn't work: https://github.com/realm/realm-java/issues/6249 + looperThread.postRunnable(() -> looperThread.testComplete()); + } + }); + session.stop(); }); - session.stop(); } @Test @@ -617,11 +634,12 @@ public void start_multipleTimes() { @Test @RunTestInLooperThread public void stop_multipleTimes() { - SyncSession session = getSession(); - session.stop(); - assertEquals(SyncSession.State.INACTIVE, session.getState()); - session.stop(); - assertEquals(SyncSession.State.INACTIVE, session.getState()); - looperThread.testComplete(); + getSession(session -> { + session.stop(); + assertEquals(SyncSession.State.INACTIVE, session.getState()); + session.stop(); + assertEquals(SyncSession.State.INACTIVE, session.getState()); + looperThread.testComplete(); + }); } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 551c872067..8f0e0f604d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -28,6 +28,7 @@ import java.io.File; import java.util.Random; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; @@ -36,6 +37,7 @@ import io.realm.internal.OsRealmConfig; import io.realm.log.LogLevel; import io.realm.log.RealmLog; +import io.realm.log.RealmLogger; import io.realm.objectserver.utils.Constants; import io.realm.rule.RunTestInLooperThread; @@ -76,8 +78,7 @@ public void loginLogoutResumeSyncing() throws InterruptedException { assertTrue(Realm.deleteRealm(config)); } catch (IllegalStateException e) { // FIXME: We don't have a way to ensure that the Realm instance on client thread has been - // closed for now. - // https://github.com/realm/realm-java/issues/5416 + // closed for now https://github.com/realm/realm-java/issues/5416 if (e.getMessage().contains("It's not allowed to delete the file")) { // retry after 1 second SystemClock.sleep(1000); @@ -372,13 +373,19 @@ public void javaRequestCustomHeaders_specificHost() { private void runJavaRequestCustomHeadersTest() { SyncCredentials credentials = SyncCredentials.nickname("test", false); + AtomicBoolean headerSet = new AtomicBoolean(false); RealmLog.setLevel(LogLevel.ALL); - RealmLog.add((level, tag, throwable, message) -> { + RealmLogger logger = (level, tag, throwable, message) -> { if (level == LogLevel.TRACE && message.contains("Foo: bar") && message.contains("RealmAuth: ")) { - looperThread.testComplete(); - }}); + headerSet.set(true); + } + }; + looperThread.runAfterTest(() -> { + RealmLog.remove(logger); + }); + RealmLog.add(logger); SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); try { @@ -388,6 +395,9 @@ private void runJavaRequestCustomHeadersTest() { throw e; } } + + assertTrue(headerSet.get()); + looperThread.testComplete(); } // Test that auth header renaming, custom headers and url prefix are all propagated correctly @@ -427,15 +437,20 @@ public void onError(SyncSession session, ObjectServerError error) { }) .build(); + AtomicBoolean headersSet = new AtomicBoolean(false); RealmLog.setLevel(LogLevel.ALL); - RealmLog.add((level, tag, throwable, message) -> { + RealmLogger logger = (level, tag, throwable, message) -> { if (tag.equals("REALM_SYNC") && message.contains("GET /foo/%2Fdefault%2F__partial%") && message.contains("TestAuth: Realm-Access-Token version=1") && message.contains("Test: test")) { looperThread.testComplete(); } + }; + looperThread.runAfterTest(() -> { + RealmLog.remove(logger); }); + RealmLog.add(logger); Realm realm = Realm.getInstance(config); looperThread.closeAfterTest(realm); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 6dda502edc..24619366d1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -128,7 +128,7 @@ public void onSuccess(SyncUser user) { .build(); final Realm realm = Realm.getInstance(config); - looperThread.addTestRealm(realm); + looperThread.closeAfterTest(realm); assertTrue(config.getUser().isValid()); looperThread.testComplete(); } @@ -153,7 +153,7 @@ public void onSuccess(SyncUser user) { .build(); final Realm realm = Realm.getInstance(config); - looperThread.addTestRealm(realm); + looperThread.closeAfterTest(realm); assertFalse(Util.isEmptyString(config.getUser().getIdentity())); assertTrue(config.getUser().isValid()); looperThread.testComplete(); @@ -179,7 +179,7 @@ public void onSuccess(SyncUser user) { .build(); final Realm realm = Realm.getInstance(config); - looperThread.addTestRealm(realm); + looperThread.closeAfterTest(realm); assertFalse(Util.isEmptyString(config.getUser().getIdentity())); assertTrue(config.getUser().isValid()); looperThread.testComplete(); @@ -205,7 +205,7 @@ public void onSuccess(SyncUser user) { .build(); final Realm realm = Realm.getInstance(config); - looperThread.addTestRealm(realm); + looperThread.closeAfterTest(realm); assertFalse(Util.isEmptyString(config.getUser().getIdentity())); assertTrue(config.getUser().isValid()); looperThread.testComplete(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java index a8ad9278e7..621b7a70cb 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java @@ -21,7 +21,6 @@ import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.Dog; -import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.objectserver.model.PartialSyncModule; import io.realm.objectserver.model.PartialSyncObjectA; @@ -29,9 +28,11 @@ import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; +import io.realm.sync.Subscription; import static org.hamcrest.number.OrderingComparison.greaterThan; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -302,9 +303,7 @@ public void clearTable() { @Test @RunTestInLooperThread - @Ignore("FIXME: We need to use ROS 3.10+ to support limit, but cannot upgrade before https://github.com/realm/realm-js/issues/1971 is fixed") public void downloadLimitedData() throws InterruptedException { - RealmLog.setLevel(LogLevel.TRACE); SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); createServerData(user, Constants.SYNC_SERVER_URL); Realm realm = getPartialRealm(user); @@ -336,6 +335,106 @@ public void downloadLimitedData() throws InterruptedException { }); } + @Test + @RunTestInLooperThread + public void initialDataAndWaitForRemoteInitialData() throws InterruptedException { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + createServerData(user, Constants.SYNC_SERVER_URL); + + // Create partial Realm that will wait for the subscriptions + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .name("partialSync") + .initialData(r -> { + r.where(PartialSyncObjectA.class).greaterThan("number", 5).subscribe("my-sub"); + }) + .waitForInitialRemoteData() + .modules(new PartialSyncModule()) + .build(); + Realm realm = Realm.getInstance(partialSyncConfig); + looperThread.closeAfterTest(realm); + + // Check the state of subscriptions. Sync automatically creates subscriptions for fine-grained permission classes. + assertEquals(6, realm.getSubscriptions().size()); + assertTrue(realm.getSubscriptions().where().equalTo("status", 0).findAll().isEmpty()); + Subscription sub = realm.getSubscription("my-sub"); + assertEquals(Subscription.State.ACTIVE, sub.getState()); + + // Check that data is downloaded + assertFalse(realm.isEmpty()); + assertEquals(4, realm.where(PartialSyncObjectA.class).findAll().size()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void unsubscribe_synchronous() throws InterruptedException { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + createServerData(user, Constants.SYNC_SERVER_URL); + + // Create partial Realm that will wait for the subscriptions + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .name("partialSync") + .initialData(r -> { + r.where(PartialSyncObjectA.class).greaterThan("number", 5).subscribe("my-sub"); + }) + .waitForInitialRemoteData() + .addModule(new PartialSyncModule()) + .build(); + Realm realm = Realm.getInstance(partialSyncConfig); + looperThread.closeAfterTest(realm); + + realm.executeTransaction(r -> { + Subscription sub = r.getSubscription("my-sub"); + assertEquals(Subscription.State.ACTIVE, sub.getState()); + sub.unsubscribe(); + assertEquals(Subscription.State.INVALIDATED, sub.getState()); + }); + + // Objects should eventually disappear from the device + RealmResults results = realm.where(PartialSyncObjectA.class).findAll(); + results.addChangeListener((objects, changeSet) -> { + if (objects.isEmpty()) { + looperThread.testComplete(); + } + }); + } + + + @Test + @RunTestInLooperThread + public void deletingSubscriptionObjectUnsubscribes() throws InterruptedException { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + createServerData(user, Constants.SYNC_SERVER_URL); + + // Create partial Realm that will wait for the subscriptions + final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .name("partialSync") + .initialData(r -> { + r.where(PartialSyncObjectA.class).greaterThan("number", 5).subscribe("my-sub"); + }) + .waitForInitialRemoteData() + .addModule(new PartialSyncModule()) + .build(); + Realm realm = Realm.getInstance(partialSyncConfig); + looperThread.closeAfterTest(realm); + + realm.executeTransaction(r -> { + Subscription sub = r.getSubscription("my-sub"); + assertEquals(Subscription.State.ACTIVE, sub.getState()); + sub.deleteFromRealm(); // Equivalent of calling `sub.unsubscribe()`. + assertEquals(Subscription.State.INVALIDATED, sub.getState()); + }); + + // Objects should eventually disappear from the device + RealmResults results = realm.where(PartialSyncObjectA.class).findAll(); + results.addChangeListener((objects, changeSet) -> { + if (objects.isEmpty()) { + looperThread.testComplete(); + } + }); + } + + private Realm getPartialRealm(SyncUser user) { final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .name("partialSync") diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java index b11a8ebea1..e7243d27f7 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java @@ -60,20 +60,7 @@ public class SyncTestUtils { } } - public static void prepareEnvironmentForTest() throws IOException { - deleteRosFiles(); - if (BaseRealm.applicationContext != null) { - // Realm was already initialized. Reset all internal state - // in order to be able fully re-initialize. - - // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' - // causing the SyncUser to remain in memory. - // They're actually not persisted into disk. - // move this call to 'tearDown' to clean in-memory & on-disk users - // once https://github.com/realm/realm-object-store/issues/207 is resolved - SyncManager.reset(); - BaseRealm.applicationContext = null; // Required for Realm.init() to work - } + public static void prepareEnvironmentForTest(){ Realm.init(InstrumentationRegistry.getTargetContext()); originalLogLevel = RealmLog.getLevel(); RealmLog.setLevel(LogLevel.DEBUG); @@ -82,12 +69,27 @@ public static void prepareEnvironmentForTest() throws IOException { /** * Tries to restore the environment as best as possible after a test. */ - public static void restoreEnvironmentAfterTest() { + public static void restoreEnvironmentAfterTest() throws IOException { // Block until all users are logged out UserFactory.logoutAllUsers(); // Reset log level RealmLog.setLevel(originalLogLevel); + + if (BaseRealm.applicationContext != null) { + // Realm was already initialized. Reset all internal state + // in order to be able fully re-initialize. + + // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' + // causing the SyncUser to remain in memory. + // They're actually not persisted into disk. + // move this call to 'tearDown' to clean in-memory & on-disk users + // once https://github.com/realm/realm-object-store/issues/207 is resolved + SyncManager.reset(); + BaseRealm.applicationContext = null; // Required for Realm.init() to work + } + deleteRosFiles(); + Realm.init(InstrumentationRegistry.getTargetContext()); } // Cleanup filesystem to make sure nothing lives for the next test. diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index a9db3a9934..2d8a6a5096 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -944,7 +944,6 @@ public static void awaitOrFail(CountDownLatch latch, int numberOfSeconds) { } public interface LooperTest { - CountDownLatch getRealmClosedSignal(); Looper getLooper(); Throwable getAssertionError(); } @@ -967,8 +966,6 @@ public static void exitOrThrow(ExecutorService executorService, CountDownLatch t looper.quit(); } - // Waits for the finally block to execute and closes the Realm. - TestHelper.awaitOrFail(test.getRealmClosedSignal()); // Closes the executor. // This needs to be called after waiting since it might interrupt waitRealmThreadExecutorFinish(). executorService.shutdownNow(); diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java index baef25a0f1..c9837cb1fc 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java @@ -55,7 +55,7 @@ * the open Realms). */ public class RunInLooperThread extends TestRealmConfigurationFactory { - private static final long WAIT_TIMEOUT_MS = 60 * 1000; + private static final long WAIT_TIMEOUT_MS = 20 * 1000; // lock protecting objects shared with the test thread private final Object lock = new Object(); @@ -96,6 +96,9 @@ public class RunInLooperThread extends TestRealmConfigurationFactory { // Access guarded by 'lock' private List runAfterTestIsComplete = new ArrayList<>(); + // Used to indicate that a test is being marked as complete, but teardown hasn't fully finished yet. + private boolean testCompletedButNotFullyTornDown; + /** * Get the configuration for the test realm. *

              @@ -219,11 +222,33 @@ public void postRunnableDelayed(Runnable runnable, long delayMillis) { /** * Signal that the test has completed. *

              - * Used on both the main and test threads. - * Valid after {@code before}. + * Can be used on both the main and test threads. */ public void testComplete() { - signalTestCompleted.countDown(); + // Close all resources and run any after test tasks + // Post as runnable to ensure that this code runs on the correct thread. + postRunnable(() -> { + closeTestResources(); + }); + } + + /** + * Internal logic for shutting down a test. + */ + private void closeTestResources() { + testCompletedButNotFullyTornDown = true; + try { + closeResources(); + closeRealms(); + for (Runnable task : runAfterTestIsComplete) { + task.run(); + } + } catch (Throwable t) { + throw new AssertionError("Failed to close test resources correctly", t); + } finally { + testCompletedButNotFullyTornDown = false; + signalTestCompleted.countDown(); + } } /** @@ -231,7 +256,8 @@ public void testComplete() { * * @param latches additional latches to wait on, before setting the test completed flag. */ - public void testComplete(CountDownLatch... latches) { + public void + testComplete(CountDownLatch... latches) { for (CountDownLatch latch : latches) { TestHelper.awaitOrFail(latch); } @@ -245,8 +271,8 @@ private Handler getBackgroundHandler() { while (backgroundHandler == null) { try { lock.wait(WAIT_TIMEOUT_MS); - } catch (InterruptedException ignore) { - break; + } catch (InterruptedException e) { + throw new AssertionError("Could not acquire the test handler.", e); } } return this.backgroundHandler; @@ -315,6 +341,8 @@ public Statement apply(Statement base, Description description) { * This will run on the same thread as the looper test. */ public void looperTearDown() { + // Do nothing + // Override in test classes if needed. } private void initRealm() { @@ -349,11 +377,12 @@ private void closeResources() throws IOException { /** * Checks if the current test is considered completed or not. + * * It is completed if either {@link #testComplete()} was called or an uncaught exception was thrown. */ public boolean isTestComplete() { synchronized (lock) { - return signalTestCompleted.getCount() == 0; + return signalTestCompleted.getCount() <= 0 || testCompletedButNotFullyTornDown; } } @@ -461,11 +490,11 @@ private class TestThread implements Runnable, TestHelper.LooperTest { this.base = base; } - @Override - public CountDownLatch getRealmClosedSignal() { - return signalClosedRealm; - } - +// @Override +// public CountDownLatch getRealmClosedSignal() { +// return signalClosedRealm; +// } +// @Override public synchronized Looper getLooper() { return looper; @@ -499,20 +528,13 @@ public void run() { } catch (Throwable t) { setAssertionError(t); setUnitTestFailed(); - } finally { + + // If an exception occurred, `looperThread.testComplete()` was probably no called. + // Rerun it here, but ignore any failures as the first failure is more important. try { - looperTearDown(); - closeResources(); - for (Runnable task : runAfterTestIsComplete) { - task.run(); - } - } catch (Throwable t) { - setAssertionError(t); - setUnitTestFailed(); + closeTestResources(); + } catch (Throwable ignore) { } - testComplete(); - closeRealms(); - signalClosedRealm.countDown(); } } } From 9c4c5e6b6c528082190c02a6e159fc3eb7929743 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 2 Nov 2018 22:47:30 +0100 Subject: [PATCH 1330/2110] Ignore same values when using copyToRealm (#6224) --- CHANGELOG.md | 55 +- examples/unitTestExample/build.gradle | 2 +- .../benchmarks/CopyToRealmBenchmarks.java | 124 ++++ .../CopyToRealmOrUpdateBenchmarks.java | 144 +++++ .../benchmarks/RealmQueryBenchmarks.java | 2 +- .../entities/AllTypesPrimaryKey.java | 90 +++ .../io/realm/processor/ClassMetaData.java | 24 + .../processor/OsObjectBuilderTypeHelper.java | 105 ++++ .../processor/RealmProxyClassGenerator.java | 305 +++++----- .../RealmProxyMediatorGenerator.java | 26 +- .../java/io/realm/processor/TypeMirrors.java | 4 +- .../main/java/io/realm/processor/Utils.java | 17 + .../io/realm/RealmDefaultModuleMediator.java | 6 +- .../realm/some_test_AllTypesRealmProxy.java | 161 +++--- .../realm/some_test_BooleansRealmProxy.java | 47 +- ...amePolicyMixedClassSettingsRealmProxy.java | 43 +- ...st_NamePolicyModuleDefaultsRealmProxy.java | 43 +- .../realm/some_test_NullTypesRealmProxy.java | 128 +++-- .../io/realm/some_test_SimpleRealmProxy.java | 43 +- .../io/realm/MutableRealmIntegerTests.java | 4 +- .../java/io/realm/NotificationsTest.java | 33 ++ .../androidTest/java/io/realm/RealmTests.java | 45 +- .../realm-library/src/main/cpp/CMakeLists.txt | 1 + .../io_realm_internal_OsObjectSchemaInfo.cpp | 23 + ...m_internal_objectstore_OsObjectBuilder.cpp | 315 +++++++++++ .../src/main/cpp/java_object_accessor.hpp | 533 ++++++++++++++++++ realm/realm-library/src/main/cpp/util.cpp | 10 +- realm/realm-library/src/main/cpp/util.hpp | 4 + .../src/main/java/io/realm/BaseRealm.java | 7 +- .../src/main/java/io/realm/ImportFlag.java | 69 +++ .../src/main/java/io/realm/Realm.java | 95 ++-- .../src/main/java/io/realm/RealmList.java | 26 +- .../src/main/java/io/realm/RealmObject.java | 6 +- .../io/realm/internal/OsObjectSchemaInfo.java | 11 + .../java/io/realm/internal/OsSharedRealm.java | 2 +- .../io/realm/internal/RealmProxyMediator.java | 12 +- .../main/java/io/realm/internal/Table.java | 2 +- .../java/io/realm/internal/UncheckedRow.java | 2 +- .../src/main/java/io/realm/internal/Util.java | 24 + .../internal/modules/CompositeMediator.java | 5 +- .../internal/modules/FilterableMediator.java | 5 +- .../internal/objectstore/OsObjectBuilder.java | 415 ++++++++++++++ .../EncryptedSynchronizedRealmTests.java | 7 +- .../io/realm/entities/PrimaryKeyAsLong.java | 6 + 44 files changed, 2639 insertions(+), 392 deletions(-) create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp create mode 100644 realm/realm-library/src/main/cpp/java_object_accessor.hpp create mode 100644 realm/realm-library/src/main/java/io/realm/ImportFlag.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java diff --git a/CHANGELOG.md b/CHANGELOG.md index abbd226473..fb6e30fcbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.?.? (2018-MM-DD) +## X.Y.Z (YYYY-MM-DD) ### Enhancements * None @@ -17,14 +17,63 @@ ## 5.8.0 (YYYY-MM-DD) +This release also contains all changes in 5.8.0-BETA1 and 5.8.0-BETA2. + ### Enhancements * [ObjectServer] Added Subscription class available to Query-based Realms. This exposes a Subscription more directly. This class is in beta. [#6231](https://github.com/realm/realm-java/pull/6231). * [ObjectServer] Added `Realm.getSubscriptions()`, `Realm.getSubscriptions(String pattern)` and `Realm.getSubscription` to make it easier to find existing subscriptions. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). * [ObjectServer] Added `RealmQuery.subscribe()` and `RealmQuery.subscribe(String name)` to subscribe immediately inside a transaction. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). * [ObjectServer] Added support for subscribing directly inside `SyncConfiguration.initialData()`. This can be coupled with `SyncConfiguration.waitForInitialRemoteData()` in order to block a Realm from opening until the initial subscriptions are ready and have downloaded data. This API are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). +* Added support for `ImportFlag`s to `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate()`. This makes it possible to choose a mode so only fields that actually changed are written to disk. This improves notifications and Object Server performance. [#6224](https://github.com/realm/realm-java/pull/6224). ### Fixed -* ?? (Issue [#??](https://github.com/realm/realm-java/issues/??), since ??). +* All known bugs introduced in 5.8.0-BETA1 and 5.8.0-BETA2. See the release notes for these releases. + +### Compatibility +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* Updated to Object Store commit: 1f91c82eb34cf4eaa2900794a9268390876f19f1 + + +## 5.8.0-BETA2 (2018-10-19) + +### Enhancements +* None + +### Fixed +* `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline [#6235](https://github.com/realm/realm-java/issues/6235). + +### Known Bugs +* `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate` has been rewritten to support import flags. It is currently ~30% slower than in 5.7.0. +* IllegalStateException thrown when trying to create an object with a primary key that already exists when using `Realm.copyToRealm`, will always report "null" instead of the correct primary key value. +* When using `ImportFlag.DO_NOT_SET_SAME_VALUES`, lists will still be written and reported as changed, even if they didn't change. + +### Compatibility +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None + + +## 5.8.0-BETA1 (2018-10-11) + +### Enhancements +* Added new `ImportFlag` class that is used to specify additional behaviour when importing + data into Realm [#6224](https://github.com/realm/realm-java/pull/6224). +* Added support for `ImportFlag` to `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate()` [#6224](https://github.com/realm/realm-java/pull/6224). + +### Fixed +* None + +### Known Bugs +* `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate` has been rewritten to support import flags. It is currently ~30% slower than in 5.7.0. +* IllegalStateException thrown when trying to create an object with a primary key that already exists when using `Realm.copyToRealm`, will always report "null" instead of the correct primary key value. +* When using `ImportFlag.DO_NOT_SET_SAME_VALUES`, lists will still be written and reported as changed, even if they didn't change. ### Compatibility * Realm Object Server: 3.11.0 or later. @@ -41,7 +90,7 @@ * None ### Fixed -* `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline. (Issue [#6235](https://github.com/realm/realm-java/issues/6235), since 5.0.0). +* [ObjectServer] `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline. (Issue [#6235](https://github.com/realm/realm-java/issues/6235), since 5.0.0). ### Compatibility * Realm Object Server: 3.11.0 or later. diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index ad9010c919..059d77e006 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -46,7 +46,7 @@ dependencies { // Testing testImplementation 'junit:junit:4.12' - testImplementation "org.robolectric:robolectric:3.3.2" + testImplementation "org.robolectric:robolectric:3.8" testImplementation "org.mockito:mockito-core:1.10.19" testImplementation 'org.robolectric:shadows-support-v4:3.0' diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java new file mode 100644 index 0000000000..13586e83bc --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java @@ -0,0 +1,124 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks; + +import android.support.test.InstrumentationRegistry; + +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import dk.ilios.spanner.AfterExperiment; +import dk.ilios.spanner.BeforeExperiment; +import dk.ilios.spanner.Benchmark; +import dk.ilios.spanner.BenchmarkConfiguration; +import dk.ilios.spanner.SpannerConfig; +import dk.ilios.spanner.junit.SpannerRunner; +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.RealmList; +import io.realm.benchmarks.config.BenchmarkConfig; +import io.realm.benchmarks.entities.AllTypes; +import io.realm.benchmarks.entities.AllTypesPrimaryKey; + + +@RunWith(SpannerRunner.class) +public class CopyToRealmBenchmarks { + + @BenchmarkConfiguration + public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); + + private Realm realm; + private static final int COLLECTION_SIZE = 100; + private List noPkObjects = new ArrayList<>(COLLECTION_SIZE); + private List pkObjects = new ArrayList<>(COLLECTION_SIZE); + private ArrayList complextTestObjects; + private ArrayList simpleTestObjects; + + @BeforeExperiment + public void before() { + Realm.init(InstrumentationRegistry.getTargetContext()); + RealmConfiguration config = new RealmConfiguration.Builder().build(); + Realm.deleteRealm(config); + + // Create test data + complextTestObjects = new ArrayList<>(); + for (int i = 0; i < COLLECTION_SIZE; i++) { + AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); + obj.setColumnString("obj" + i); + obj.setColumnLong(i); + obj.setColumnFloat(1.23F); + obj.setColumnDouble(1.234); + obj.setColumnBoolean(true); + obj.setColumnDate(new Date(1000)); + obj.setColumnBinary(new byte[] {1,2,3}); + obj.setColumnRealmObject(obj); + obj.setColumnRealmList(new RealmList<>(obj, obj, obj)); + obj.setColumnBooleanList(new RealmList<>(true, false, true)); + obj.setColumnStringList(new RealmList<>("foo", "bar", "baz")); + obj.setColumnBinaryList(new RealmList<>(new byte[]{0,1,2},new byte[]{2,3,4},new byte[]{4,5,6})); + obj.setColumnByteList(new RealmList<>((byte)1,(byte)2,(byte)3)); + obj.setColumnShortList(new RealmList<>((short)1,(short)2,(short)3)); + obj.setColumnIntegerList(new RealmList<>(1,2,3)); + obj.setColumnLongList(new RealmList<>(1L,2L,3L)); + obj.setColumnFloatList(new RealmList<>(1.1F, 1.2F, 1.3F)); + obj.setColumnDoubleList(new RealmList<>(1.111, 1.222, 1.333)); + obj.setColumnDateList(new RealmList<>(new Date(1000), new Date(2000), new Date(3000))); + complextTestObjects.add(obj); + } + + simpleTestObjects = new ArrayList<>(); + for (int i = 0; i < COLLECTION_SIZE; i++) { + AllTypes obj = new AllTypes(); + obj.setColumnString("obj" + i); + obj.setColumnLong(i); + obj.setColumnFloat(1.23F); + obj.setColumnDouble(1.234); + obj.setColumnBoolean(true); + obj.setColumnDate(new Date(1000)); + obj.setColumnBinary(new byte[] {1,2,3}); + simpleTestObjects.add(obj); + } + + // Setup Realm before test + realm = Realm.getInstance(config); + realm.beginTransaction(); + } + + @AfterExperiment + public void after() { + realm.cancelTransaction(); + realm.close(); + } + + @Benchmark + public void copyToRealm_complexObjects(long reps) { + for (long i = 0; i < reps; i++) { + realm.copyToRealmOrUpdate(complextTestObjects); + } + } + + @Benchmark + public void copyToRealm_simpleObjects(long reps) { + for (long i = 0; i < reps; i++) { + realm.copyToRealm(simpleTestObjects); + } + } + +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java new file mode 100644 index 0000000000..45c3baf373 --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java @@ -0,0 +1,144 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks; + +import android.support.test.InstrumentationRegistry; + +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import dk.ilios.spanner.AfterExperiment; +import dk.ilios.spanner.BeforeExperiment; +import dk.ilios.spanner.Benchmark; +import dk.ilios.spanner.BenchmarkConfiguration; +import dk.ilios.spanner.SpannerConfig; +import dk.ilios.spanner.junit.SpannerRunner; +import io.realm.ImportFlag; +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.RealmList; +import io.realm.benchmarks.config.BenchmarkConfig; +import io.realm.benchmarks.entities.AllTypes; +import io.realm.benchmarks.entities.AllTypesPrimaryKey; + + +@RunWith(SpannerRunner.class) +public class CopyToRealmOrUpdateBenchmarks { + + @BenchmarkConfiguration + public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); + + private Realm realm; + private static final int COLLECTION_SIZE = 100; + private List noPkObjects = new ArrayList<>(COLLECTION_SIZE); + private List pkObjects = new ArrayList<>(COLLECTION_SIZE); + private ArrayList complextTestObjects; + private ArrayList simpleTestObjects; + + @BeforeExperiment + public void before() { + Realm.init(InstrumentationRegistry.getTargetContext()); + RealmConfiguration config = new RealmConfiguration.Builder().build(); + Realm.deleteRealm(config); + + // Setup Realm before test + realm = Realm.getInstance(config); + realm.beginTransaction(); + + // Create test data + complextTestObjects = new ArrayList<>(); + for (int i = 0; i < COLLECTION_SIZE; i++) { + AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); + obj.setColumnString("obj" + i); + obj.setColumnLong(i); + obj.setColumnFloat(1.23F); + obj.setColumnDouble(1.234); + obj.setColumnBoolean(true); + obj.setColumnDate(new Date(1000)); + obj.setColumnBinary(new byte[] {1,2,3}); + obj.setColumnRealmObject(obj); + obj.setColumnRealmList(new RealmList<>(obj, obj, obj)); + obj.setColumnBooleanList(new RealmList<>(true, false, true)); + obj.setColumnStringList(new RealmList<>("foo", "bar", "baz")); + obj.setColumnBinaryList(new RealmList<>(new byte[]{0,1,2},new byte[]{2,3,4},new byte[]{4,5,6})); + obj.setColumnByteList(new RealmList<>((byte)1,(byte)2,(byte)3)); + obj.setColumnShortList(new RealmList<>((short)1,(short)2,(short)3)); + obj.setColumnIntegerList(new RealmList<>(1,2,3)); + obj.setColumnLongList(new RealmList<>(1L,2L,3L)); + obj.setColumnFloatList(new RealmList<>(1.1F, 1.2F, 1.3F)); + obj.setColumnDoubleList(new RealmList<>(1.111, 1.222, 1.333)); + obj.setColumnDateList(new RealmList<>(new Date(1000), new Date(2000), new Date(3000))); + complextTestObjects.add(obj); + } + + simpleTestObjects = new ArrayList<>(); + for (int i = 0; i < COLLECTION_SIZE; i++) { + AllTypes obj = new AllTypes(); + obj.setColumnString("obj" + i); + obj.setColumnLong(i); + obj.setColumnFloat(1.23F); + obj.setColumnDouble(1.234); + obj.setColumnBoolean(true); + obj.setColumnDate(new Date(1000)); + obj.setColumnBinary(new byte[] {1,2,3}); + simpleTestObjects.add(obj); + } + + realm.copyToRealmOrUpdate(complextTestObjects); + realm.copyToRealmOrUpdate(simpleTestObjects); + realm.commitTransaction(); + realm.beginTransaction(); + } + + @AfterExperiment + public void after() { + realm.cancelTransaction(); + realm.close(); + } + + @Benchmark + public void copyToRealmOrDiffedUpdate_complexObjects(long reps) { + for (long i = 0; i < reps; i++) { + realm.copyToRealmOrUpdate(complextTestObjects, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET); + } + } + + @Benchmark + public void copyToRealmOrFullUpdate_complexObjects(long reps) { + for (long i = 0; i < reps; i++) { + realm.copyToRealmOrUpdate(complextTestObjects); + } + } + + @Benchmark + public void copyToRealmOrDiffedUpdate_simpleObjects(long reps) { + for (long i = 0; i < reps; i++) { + realm.copyToRealm(simpleTestObjects, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET); + } + } + + @Benchmark + public void copyToRealmOrFullUpdate_simpleObjects(long reps) { + for (long i = 0; i < reps; i++) { + realm.copyToRealm(simpleTestObjects); + } + } + +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java index f1e3571c5d..ef9b9160e0 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java @@ -87,7 +87,7 @@ public void findAll(long reps) { @Benchmark public void findAllSortedOneField(long reps) { for (long i = 0; i < reps; i++) { - RealmResults results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_STRING, Sort.ASCENDING); + RealmResults results = realm.where(AllTypes.class).sort(AllTypes.FIELD_STRING, Sort.ASCENDING).findAll(); } } } diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypesPrimaryKey.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypesPrimaryKey.java index 71dc3d88ae..7f46e8f2e5 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypesPrimaryKey.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/entities/AllTypesPrimaryKey.java @@ -35,6 +35,16 @@ public class AllTypesPrimaryKey extends RealmObject { private AllTypesPrimaryKey columnRealmObject; private RealmList columnRealmList; private Boolean columnBoxedBoolean; + private RealmList columnStringList; + private RealmList columnBinaryList; + private RealmList columnBooleanList; + private RealmList columnLongList; + private RealmList columnIntegerList; + private RealmList columnShortList; + private RealmList columnByteList; + private RealmList columnDoubleList; + private RealmList columnFloatList; + private RealmList columnDateList; public String getColumnString() { return columnString; @@ -115,4 +125,84 @@ public Boolean getColumnBoxedBoolean() { public void setColumnBoxedBoolean(Boolean columnBoxedBoolean) { this.columnBoxedBoolean = columnBoxedBoolean; } + + public RealmList getColumnStringList() { + return columnStringList; + } + + public void setColumnStringList(RealmList columnStringList) { + this.columnStringList = columnStringList; + } + + public RealmList getColumnBinaryList() { + return columnBinaryList; + } + + public void setColumnBinaryList(RealmList columnBinaryList) { + this.columnBinaryList = columnBinaryList; + } + + public RealmList getColumnBooleanList() { + return columnBooleanList; + } + + public void setColumnBooleanList(RealmList columnBooleanList) { + this.columnBooleanList = columnBooleanList; + } + + public RealmList getColumnLongList() { + return columnLongList; + } + + public void setColumnLongList(RealmList columnLongList) { + this.columnLongList = columnLongList; + } + + public RealmList getColumnIntegerList() { + return columnIntegerList; + } + + public void setColumnIntegerList(RealmList columnIntegerList) { + this.columnIntegerList = columnIntegerList; + } + + public RealmList getColumnShortList() { + return columnShortList; + } + + public void setColumnShortList(RealmList columnShortList) { + this.columnShortList = columnShortList; + } + + public RealmList getColumnByteList() { + return columnByteList; + } + + public void setColumnByteList(RealmList columnByteList) { + this.columnByteList = columnByteList; + } + + public RealmList getColumnDoubleList() { + return columnDoubleList; + } + + public void setColumnDoubleList(RealmList columnDoubleList) { + this.columnDoubleList = columnDoubleList; + } + + public RealmList getColumnFloatList() { + return columnFloatList; + } + + public void setColumnFloatList(RealmList columnFloatList) { + this.columnFloatList = columnFloatList; + } + + public RealmList getColumnDateList() { + return columnDateList; + } + + public void setColumnDateList(RealmList columnDateList) { + this.columnDateList = columnDateList; + } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index 03f3fa523f..4eba7a258d 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -63,6 +63,8 @@ public class ClassMetaData { private final String javaClassName; // Model class simple name as defined in Java. private final List fields = new ArrayList<>(); // List of all fields in the class except those @Ignored. private final List indexedFields = new ArrayList<>(); // list of all fields marked @Index. + private final List objectReferenceFields = new ArrayList<>(); // List of all fields that reference a Realm Object either directly or in a List + private final List basicTypeFields = new ArrayList<>(); // List of all fields that reference basic types, i.e. no references to other Realm Objects private final Set backlinks = new LinkedHashSet<>(); private final Set nullableFields = new LinkedHashSet<>(); // Set of fields which can be nullable private final Set nullableValueListFields = new LinkedHashSet<>(); // Set of fields whose elements can be nullable @@ -164,10 +166,27 @@ public String getFullyQualifiedClassName() { return packageName + "." + javaClassName; } + /** + * Returns all persistable fields in this model class + */ public List getFields() { return Collections.unmodifiableList(fields); } + /** + * Returns all persistable fields that reference other Realm objects. + */ + public List getObjectReferenceFields() { + return Collections.unmodifiableList(objectReferenceFields); + } + + /** + * Returns all persistable fields that contain a basic type, this include lists of primitives. + */ + public List getBasicTypeFields() { + return Collections.unmodifiableList(basicTypeFields); + } + public Set getBacklinkFields() { return Collections.unmodifiableSet(backlinks); } @@ -576,6 +595,11 @@ private boolean categorizeField(Element element) { // Standard field that appears to be valid (more fine grained checks might fail later). fields.add(field); + if (Utils.isRealmModel(field) || Utils.isRealmModelList(field)) { + objectReferenceFields.add(field); + } else { + basicTypeFields.add(field); + } return true; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.java new file mode 100644 index 0000000000..f4190e2c63 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.java @@ -0,0 +1,105 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.processor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.lang.model.element.VariableElement; + +/** + * Helper class for creating the correct method calls to the OsObjectBuilder class. + */ +public class OsObjectBuilderTypeHelper { + + private static final Map QUALIFIED_TYPE_TO_BUILDER; + private static final Map QUALIFIED_LIST_TYPE_TO_BUILDER; + + static { + // Map of qualified types to their OsObjectBuilder Type + Map fieldTypes = new HashMap<>(); + fieldTypes.put("byte", "Integer"); + fieldTypes.put("short", "Integer"); + fieldTypes.put("int", "Integer"); + fieldTypes.put("long", "Integer"); + fieldTypes.put("float", "Float"); + fieldTypes.put("double", "Double"); + fieldTypes.put("boolean", "Boolean"); + fieldTypes.put("byte[]", "ByteArray"); + fieldTypes.put("java.lang.Byte", "Integer"); + fieldTypes.put("java.lang.Short", "Integer"); + fieldTypes.put("java.lang.Integer", "Integer"); + fieldTypes.put("java.lang.Long", "Integer"); + fieldTypes.put("java.lang.Float", "Float"); + fieldTypes.put("java.lang.Double", "Double"); + fieldTypes.put("java.lang.Boolean", "Boolean"); + fieldTypes.put("java.lang.String", "String"); + fieldTypes.put("java.util.Date", "Date"); + fieldTypes.put("io.realm.MutableRealmInteger", "MutableRealmInteger"); + QUALIFIED_TYPE_TO_BUILDER = Collections.unmodifiableMap(fieldTypes); + + // Map of qualified types to their OsObjectBuilder Type + Map listTypes = new HashMap<>(); + listTypes.put("byte[]", "ByteArrayList"); + listTypes.put("java.lang.Byte", "ByteList"); + listTypes.put("java.lang.Short", "ShortList"); + listTypes.put("java.lang.Integer", "IntegerList"); + listTypes.put("java.lang.Long", "LongList"); + listTypes.put("java.lang.Float", "FloatList"); + listTypes.put("java.lang.Double", "DoubleList"); + listTypes.put("java.lang.Boolean", "BooleanList"); + listTypes.put("java.lang.String", "StringList"); + listTypes.put("java.util.Date", "DateList"); + listTypes.put("io.realm.MutableRealmInteger", "MutableRealmIntegerList"); + QUALIFIED_LIST_TYPE_TO_BUILDER = Collections.unmodifiableMap(listTypes); + } + + /** + * Returns the method name used by the OsObjectBuilder for the given type, e.g. `addInteger` + * or `addIntegerList`. + */ + public static String getOsObjectBuilderName(VariableElement field) { + if (Utils.isRealmModel(field)) { + return "addObject"; + } else if (Utils.isRealmModelList(field)) { + return "addObjectList"; + } else if (Utils.isRealmValueList(field)) { + return "add" + getListTypeName(Utils.getRealmListType(field)); + } else if (Utils.isRealmResults(field)) { + throw new IllegalStateException("RealmResults are not supported by OsObjectBuilder: " + field); + } else { + return "add" + getBasicTypeName(Utils.getFieldTypeQualifiedName(field)); + } + } + + private static String getBasicTypeName(String qualifiedType) { + String type = QUALIFIED_TYPE_TO_BUILDER.get(qualifiedType); + if (type != null) { + return type; + } + throw new IllegalArgumentException("Unsupported type: " + qualifiedType); + } + + private static String getListTypeName(String qualifiedType) { + String type = QUALIFIED_LIST_TYPE_TO_BUILDER.get(qualifiedType); + if (type != null) { + return type; + } + throw new IllegalArgumentException("Unsupported list type: " + qualifiedType); + } + +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index ef1e771df3..9e3aca471e 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -48,6 +48,7 @@ public class RealmProxyClassGenerator { "android.os.Build", "android.util.JsonReader", "android.util.JsonToken", + "io.realm.ImportFlag", "io.realm.exceptions.RealmMigrationNeededException", "io.realm.internal.ColumnInfo", "io.realm.internal.OsList", @@ -55,6 +56,7 @@ public class RealmProxyClassGenerator { "io.realm.internal.OsSchemaInfo", "io.realm.internal.OsObjectSchemaInfo", "io.realm.internal.Property", + "io.realm.internal.objectstore.OsObjectBuilder", "io.realm.ProxyUtils", "io.realm.internal.RealmObjectProxy", "io.realm.internal.Row", @@ -69,6 +71,7 @@ public class RealmProxyClassGenerator { "java.util.Date", "java.util.Map", "java.util.HashMap", + "java.util.Set", "org.json.JSONObject", "org.json.JSONException", "org.json.JSONArray"); @@ -150,6 +153,7 @@ public void generate() throws IOException, UnsupportedOperationException { emitGetSimpleClassNameMethod(writer); emitCreateOrUpdateUsingJsonObject(writer); emitCreateUsingJsonStream(writer); + emitNewProxyInstance(writer); emitCopyOrUpdateMethod(writer); emitCopyMethod(writer); emitInsertMethod(writer); @@ -176,6 +180,7 @@ private void emitColumnInfoClass(JavaWriter writer) throws IOException { "ColumnInfo"); // base class // fields + writer.emitField("long", "maxColumnIndexValue"); // Must not end with Index as it otherwise could conflict regular fields. for (VariableElement variableElement : metadata.getFields()) { writer.emitField("long", columnIndexVarName(variableElement)); } @@ -201,8 +206,10 @@ private void emitColumnInfoClass(JavaWriter writer) throws IOException { classCollection.getClassFromQualifiedName(backlink.getSourceClass()).getInternalClassName(), backlink.getSourceField()); } - writer.endConstructor() - .emitEmptyLine(); + writer + .emitStatement("this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex()") + .endConstructor() + .emitEmptyLine(); // constructor #2 writer.beginConstructor( @@ -236,6 +243,7 @@ private void emitColumnInfoClass(JavaWriter writer) throws IOException { for (VariableElement variableElement : metadata.getFields()) { writer.emitStatement("dst.%1$s = src.%1$s", columnIndexVarName(variableElement)); } + writer.emitStatement("dst.maxColumnIndexValue = src.maxColumnIndexValue"); writer.endMethod(); writer.endType(); @@ -854,13 +862,37 @@ private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException } //@formatter:on + //@formatter:off + private void emitNewProxyInstance(JavaWriter writer) throws IOException { + writer + .beginMethod(qualifiedGeneratedClassName, + "newProxyInstance", + EnumSet.of(Modifier.PRIVATE, Modifier.STATIC), + "BaseRealm", "realm", + "Row", "row") + .emitSingleLineComment("Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields") + .emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") + .emitStatement("objectContext.set(realm, row, realm.getSchema().getColumnInfo(%s.class), false, Collections.emptyList())", qualifiedJavaClassName) + .emitStatement("%1$s obj = new %1$s()", qualifiedGeneratedClassName) + .emitStatement("objectContext.clear()") + .emitStatement("return obj") + .endMethod() + .emitEmptyLine(); + } + //@formatter:on + //@formatter:off private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer.beginMethod( qualifiedJavaClassName, // Return type "copyOrUpdate", // Method name EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedJavaClassName, "object", "boolean", "update", "Map", "cache" // Argument type & argument name + "Realm", "realm", // Argument type & argument name + columnInfoClassName(), "columnInfo", + qualifiedJavaClassName, "object", + "boolean", "update", + "Map", "cache", + "Set", "flags" ); writer @@ -886,15 +918,13 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { .emitEmptyLine(); if (!metadata.hasPrimaryKey()) { - writer.emitStatement("return copy(realm, object, update, cache)"); + writer.emitStatement("return copy(realm, columnInfo, object, update, cache, flags)"); } else { writer .emitStatement("%s realmObject = null", qualifiedJavaClassName) .emitStatement("boolean canUpdate = update") .beginControlFlow("if (canUpdate)") .emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) - .emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) .emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); String primaryKeyGetter = metadata.getPrimaryKeyGetter(); @@ -930,9 +960,7 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { .emitStatement("canUpdate = false") .nextControlFlow("else") .beginControlFlow("try") - .emitStatement( - "objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", - qualifiedJavaClassName) + .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex), columnInfo, false, Collections. emptyList())") .emitStatement("realmObject = new %s()", qualifiedGeneratedClassName) .emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") .nextControlFlow("finally") @@ -944,7 +972,7 @@ private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer .emitEmptyLine() - .emitStatement("return (canUpdate) ? update(realm, realmObject, object, cache) : copy(realm, object, update, cache)"); + .emitStatement("return (canUpdate) ? update(realm, columnInfo, realmObject, object, cache, flags) : copy(realm, columnInfo, object, update, cache, flags)"); } writer.endMethod() @@ -1541,97 +1569,111 @@ private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfP } private void emitCopyMethod(JavaWriter writer) throws IOException { - writer.beginMethod( - qualifiedJavaClassName, // Return type - "copy", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedJavaClassName, "newObject", "boolean", "update", "Map", "cache"); // Argument type & argument name + writer + .beginMethod( + qualifiedJavaClassName, // Return type + "copy", // Method name + EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers + "Realm", "realm", + columnInfoClassName(), "columnInfo", + qualifiedJavaClassName, "newObject", + "boolean", "update", + "Map", "cache", + "Set", "flags" + + ); // Argument type & argument name - writer.emitStatement("RealmObjectProxy cachedRealmObject = cache.get(newObject)"); - writer.beginControlFlow("if (cachedRealmObject != null)") + writer + .emitStatement("RealmObjectProxy cachedRealmObject = cache.get(newObject)") + .beginControlFlow("if (cachedRealmObject != null)") .emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName) - .endControlFlow(); + .endControlFlow() + .emitEmptyLine(); + writer + .emitStatement("%1$s realmObjectSource = (%1$s) newObject", interfaceName) + .emitEmptyLine() + .emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + .emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags)"); - writer.emitEmptyLine() - .emitSingleLineComment("rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields."); - if (metadata.hasPrimaryKey()) { - writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, ((%s) newObject).%s(), false, Collections.emptyList())", - qualifiedJavaClassName, qualifiedJavaClassName, interfaceName, metadata.getPrimaryKeyGetter()); - } else { - writer.emitStatement("%s realmObject = realm.createObjectInternal(%s.class, false, Collections.emptyList())", - qualifiedJavaClassName, qualifiedJavaClassName); + // Copy basic types + writer + .emitEmptyLine() + .emitSingleLineComment("Add all non-\"object reference\" fields"); + for (RealmFieldElement field : metadata.getBasicTypeFields()) { + String fieldIndex = fieldIndexVariableReference(field); + String fieldName = field.getSimpleName().toString(); + String getter = metadata.getInternalGetter(fieldName); + writer.emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldIndex, getter); } - writer.emitStatement("cache.put(newObject, (RealmObjectProxy) realmObject)"); - - writer.emitEmptyLine() - .emitStatement("%1$s realmObjectSource = (%1$s) newObject", interfaceName) - .emitStatement("%1$s realmObjectCopy = (%1$s) realmObject", interfaceName); + // Create the underlying object + writer + .emitEmptyLine() + .emitSingleLineComment("Create the underlying object and cache it before setting any object/objectlist references") + .emitSingleLineComment("This will allow us to break any circular dependencies by using the object cache.") + .emitStatement("Row row = builder.createNewObject()") + .emitStatement("%s realmObjectCopy = newProxyInstance(realm, row)", qualifiedGeneratedClassName) + .emitStatement("cache.put(newObject, realmObjectCopy)"); + + // Copy all object references or lists-of-objects writer.emitEmptyLine(); - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); + if (!metadata.getObjectReferenceFields().isEmpty()) { + writer.emitSingleLineComment("Finally add all fields that reference other Realm Objects, either directly or through a list"); + } + for (RealmFieldElement field : metadata.getObjectReferenceFields()) { String fieldType = field.asType().toString(); - String setter = metadata.getInternalSetter(fieldName); + String fieldName = field.getSimpleName().toString(); String getter = metadata.getInternalGetter(fieldName); + String setter = metadata.getInternalSetter(fieldName); - if (metadata.isPrimaryKey(field)) { - // PK has been set when creating object. - continue; - } - - //@formatter:off if (Utils.isRealmModel(field)) { - writer.emitEmptyLine() - .emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) - .beginControlFlow("if (%sObj == null)", fieldName) - .emitStatement("realmObjectCopy.%s(null)", setter) + writer + .emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) + .beginControlFlow("if (%sObj == null)", fieldName) + .emitStatement("realmObjectCopy.%s(null)", setter) + .nextControlFlow("else") + .emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) + .beginControlFlow("if (cache%s != null)", fieldName) + .emitStatement("realmObjectCopy.%s(cache%s)", setter, fieldName) .nextControlFlow("else") - .emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("realmObjectCopy.%s(cache%s)", setter, fieldName) - .nextControlFlow("else") - .emitStatement("realmObjectCopy.%s(%s.copyOrUpdate(realm, %sObj, update, cache))", - setter, Utils.getProxyClassSimpleName(field), fieldName) - .endControlFlow() - // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .endControlFlow(); + .emitStatement("realmObjectCopy.%s(%s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, update, cache, flags))", + setter, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) + .endControlFlow() + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. + .endControlFlow() + .emitEmptyLine(); + } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); - writer.emitEmptyLine() - .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) - .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("RealmList<%s> %sRealmList = realmObjectCopy.%s()", - genericType, fieldName, getter) - // Clear is needed. See bug https://github.com/realm/realm-java/issues/4957 - .emitStatement("%sRealmList.clear()", fieldName) - .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) - .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%1$sRealmList.add(cache%1$s)", fieldName) - .nextControlFlow("else") - .emitStatement("%1$sRealmList.add(%2$s.copyOrUpdate(realm, %1$sItem, update, cache))", - fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() + writer + .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) + .beginControlFlow("if (%sList != null)", fieldName) + .emitStatement("RealmList<%s> %sRealmList = realmObjectCopy.%s()", + genericType, fieldName, getter) + // Clear is needed. See bug https://github.com/realm/realm-java/issues/4957 + .emitStatement("%sRealmList.clear()", fieldName) + .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) + .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) + .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) + .beginControlFlow("if (cache%s != null)", fieldName) + .emitStatement("%1$sRealmList.add(cache%1$s)", fieldName) + .nextControlFlow("else") + .emitStatement("%1$sRealmList.add(%2$s.copyOrUpdate(realm, (%3$s) realm.getSchema().getColumnInfo(%4$s.class), %1$sItem, update, cache, flags))", + fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) .endControlFlow() .endControlFlow() - .emitEmptyLine(); - - } else if (Utils.isRealmValueList(field)) { - writer.emitStatement("realmObjectCopy.%s(realmObjectSource.%s())", setter, getter); - } else if (Utils.isMutableRealmInteger(field)) { - writer.emitEmptyLine() - .emitStatement("realmObjectCopy.%1$s().set(realmObjectSource.%1$s().get())", getter); + .endControlFlow() + .emitEmptyLine(); } else { - writer.emitStatement("realmObjectCopy.%s(realmObjectSource.%s())", setter, getter); + throw new IllegalStateException("Unsupported field: " + field); } - //@formatter:on } - writer.emitStatement("return realmObject"); - writer.endMethod(); - writer.emitEmptyLine(); + writer + .emitStatement("return realmObjectCopy") + .endMethod() + .emitEmptyLine(); } //@formatter:off @@ -1722,80 +1764,78 @@ private void emitUpdateMethod(JavaWriter writer) throws IOException { qualifiedJavaClassName, // Return type "update", // Method name EnumSet.of(Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedJavaClassName, "realmObject", qualifiedJavaClassName, "newObject", "Map", "cache"); // Argument type & argument name + "Realm", "realm", // Argument type & argument name + columnInfoClassName(), "columnInfo", + qualifiedJavaClassName, "realmObject", + qualifiedJavaClassName, "newObject", + "Map", "cache", + "Set", "flags" + ); writer .emitStatement("%1$s realmObjectTarget = (%1$s) realmObject", interfaceName) - .emitStatement("%1$s realmObjectSource = (%1$s) newObject", interfaceName); + .emitStatement("%1$s realmObjectSource = (%1$s) newObject", interfaceName) + .emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + .emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags)"); - for (VariableElement field : metadata.getFields()) { + + for (RealmFieldElement field : metadata.getFields()) { + String fieldType = field.asType().toString(); String fieldName = field.getSimpleName().toString(); - String setter = metadata.getInternalSetter(fieldName); String getter = metadata.getInternalGetter(fieldName); - //@formatter:off + String fieldIndex = fieldIndexVariableReference(field); + if (Utils.isRealmModel(field)) { writer - .emitStatement("%s %sObj = realmObjectSource.%s()", - Utils.getFieldTypeQualifiedName(field), fieldName, getter) - .beginControlFlow("if (%sObj == null)", fieldName) - .emitStatement("realmObjectTarget.%s(null)", setter) - .nextControlFlow("else") - .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sObj)", - Utils.getFieldTypeQualifiedName(field), fieldName) + .emitEmptyLine() + .emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) + .beginControlFlow("if (%sObj == null)", fieldName) + .emitStatement("builder.addNull(%s)", fieldIndexVariableReference(field)) + .nextControlFlow("else") + .emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("realmObjectTarget.%s(cache%s)", setter, fieldName) + .emitStatement("builder.addObject(%s, cache%s)", fieldIndex, fieldName) .nextControlFlow("else") - .emitStatement("realmObjectTarget.%s(%s.copyOrUpdate(realm, %sObj, true, cache))", - setter, Utils.getProxyClassSimpleName(field), fieldName) + .emitStatement("builder.addObject(%s, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, true, cache, flags))", + fieldIndex, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) .endControlFlow() - // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .endControlFlow(); + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. + .endControlFlow(); } else if (Utils.isRealmModelList(field)) { final String genericType = Utils.getGenericTypeQualifiedName(field); writer + .emitEmptyLine() .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) - .emitStatement("RealmList<%s> %sRealmList = realmObjectTarget.%s()", genericType, fieldName, getter) - .beginControlFlow("if (%1$sList != null && %1$sList.size() == %1$sRealmList.size())", fieldName) - .emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") - .emitStatement("int objects = %sList.size()", fieldName) - .beginControlFlow("for (int i = 0; i < objects; i++)") + .beginControlFlow("if (%sList != null)", fieldName) + .emitStatement("RealmList<%s> %sManagedCopy = new RealmList<%s>()", genericType, fieldName, genericType) + .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%1$sRealmList.set(i, cache%1$s)", fieldName) + .emitStatement("%1$sManagedCopy.add(cache%1$s)", fieldName) .nextControlFlow("else") - .emitStatement("%1$sRealmList.set(i, %2$s.copyOrUpdate(realm, %1$sItem, true, cache))", fieldName, Utils.getProxyClassSimpleName(field)) + .emitStatement("%1$sManagedCopy.add(%2$s.copyOrUpdate(realm, (%3$s) realm.getSchema().getColumnInfo(%4$s.class), %1$sItem, true, cache, flags))", + fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) .endControlFlow() .endControlFlow() + .emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldIndex, fieldName) .nextControlFlow("else") - .emitStatement("%sRealmList.clear()", fieldName) - .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) - .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%1$sRealmList.add(cache%1$s)", fieldName) - .nextControlFlow("else") - .emitStatement("%1$sRealmList.add(%2$s.copyOrUpdate(realm, %1$sItem, true, cache))", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .endControlFlow() - .endControlFlow() + .emitStatement("builder.addObjectList(%s, new RealmList<%s>())", fieldIndex, genericType) .endControlFlow(); - } else if (Utils.isRealmValueList(field)) { - writer.emitStatement("realmObjectTarget.%s(realmObjectSource.%s())", setter, getter); - } else if (Utils.isMutableRealmInteger(field)) { - writer.emitStatement("realmObjectTarget.%s().set(realmObjectSource.%s().get())", getter, getter); } else { - if (field != metadata.getPrimaryKey()) { - writer.emitStatement("realmObjectTarget.%s(realmObjectSource.%s())", setter, getter); - } + writer + .emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldIndex, getter); } - //@formatter:on } - writer.emitStatement("return realmObject"); - writer.endMethod(); - writer.emitEmptyLine(); + writer + .emitEmptyLine() + .emitStatement("builder.updateExistingObject()") + .emitStatement("return realmObject"); + + writer + .endMethod() + .emitEmptyLine(); } private void emitToStringMethod(JavaWriter writer) throws IOException { @@ -2141,6 +2181,15 @@ private String columnInfoClassName() { return simpleJavaClassName + "ColumnInfo"; } + /** + * Returns the name of the ColumnInfo class for the model class referenced in the field. + * I.e. for `com.test.Person`, it returns `Person.PersonColumnInfo` + */ + private String columnInfoClassName(VariableElement field) { + String qualfiedModelClassName = Utils.getModelClassQualifiedName(field); + return Utils.getSimpleColumnInfoClassName(qualfiedModelClassName); + } + private String columnIndexVarName(VariableElement variableElement) { return variableElement.getSimpleName().toString() + "Index"; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java index d058617c1e..b67ac2bea8 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java @@ -42,6 +42,7 @@ public class RealmProxyMediatorGenerator { private final ProcessingEnvironment processingEnvironment; private final List qualifiedModelClasses = new ArrayList<>(); private final List qualifiedProxyClasses = new ArrayList<>(); + private final List simpleModelClassNames = new ArrayList<>(); private final List internalClassNames = new ArrayList<>(); @@ -52,7 +53,9 @@ public RealmProxyMediatorGenerator(ProcessingEnvironment processingEnvironment, for (ClassMetaData metadata : classesToValidate) { qualifiedModelClasses.add(metadata.getFullyQualifiedClassName()); - qualifiedProxyClasses.add(REALM_PACKAGE_NAME + "." + Utils.getProxyClassName(metadata.getFullyQualifiedClassName())); + String qualifiedProxyClassName = REALM_PACKAGE_NAME + "." + Utils.getProxyClassName(metadata.getFullyQualifiedClassName()); + qualifiedProxyClasses.add(qualifiedProxyClassName); + simpleModelClassNames.add(metadata.getSimpleJavaClassName()); internalClassNames.add(metadata.getInternalClassName()); } } @@ -66,8 +69,7 @@ public void generate() throws IOException { writer.emitPackage(REALM_PACKAGE_NAME); writer.emitEmptyLine(); - writer.emitImports( - "android.util.JsonReader", + List imports = new ArrayList<>(Arrays.asList("android.util.JsonReader", "java.io.IOException", "java.util.Collections", "java.util.HashSet", @@ -77,6 +79,7 @@ public void generate() throws IOException { "java.util.Set", "java.util.Iterator", "java.util.Collection", + "io.realm.ImportFlag", "io.realm.internal.ColumnInfo", "io.realm.internal.RealmObjectProxy", "io.realm.internal.RealmProxyMediator", @@ -84,9 +87,9 @@ public void generate() throws IOException { "io.realm.internal.OsSchemaInfo", "io.realm.internal.OsObjectSchemaInfo", "org.json.JSONException", - "org.json.JSONObject" - ); + "org.json.JSONObject")); + writer.emitImports(imports); writer.emitEmptyLine(); writer.emitAnnotation(RealmModule.class); @@ -103,7 +106,7 @@ public void generate() throws IOException { emitGetSimpleClassNameMethod(writer); emitNewInstanceMethod(writer); emitGetClassModelList(writer); - emitCopyToRealmMethod(writer); + emitCopyOrUpdateMethod(writer); emitInsertObjectToRealmMethod(writer); emitInsertListToRealmMethod(writer); emitInsertOrUpdateObjectToRealmMethod(writer); @@ -223,13 +226,17 @@ private void emitGetClassModelList(JavaWriter writer) throws IOException { writer.emitEmptyLine(); } - private void emitCopyToRealmMethod(JavaWriter writer) throws IOException { + private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { writer.emitAnnotation("Override"); writer.beginMethod( " E", "copyOrUpdate", EnumSet.of(Modifier.PUBLIC), - "Realm", "realm", "E", "obj", "boolean", "update", "Map", "cache" + "Realm", "realm", + "E", "obj", + "boolean", "update", + "Map", "cache", + "Set", "flags" ); writer.emitSingleLineComment("This cast is correct because obj is either"); writer.emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject"); @@ -238,7 +245,8 @@ private void emitCopyToRealmMethod(JavaWriter writer) throws IOException { emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { @Override public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return clazz.cast(%s.copyOrUpdate(realm, (%s) obj, update, cache))", qualifiedProxyClasses.get(i), qualifiedModelClasses.get(i)); + writer.emitStatement("%1$s columnInfo = (%1$s) realm.getSchema().getColumnInfo(%2$s.class)", Utils.getSimpleColumnInfoClassName(qualifiedModelClasses.get(i)), qualifiedModelClasses.get(i)); + writer.emitStatement("return clazz.cast(%s.copyOrUpdate(realm, columnInfo, (%s) obj, update, cache, flags))", qualifiedProxyClasses.get(i), qualifiedModelClasses.get(i)); } }, writer, false); writer.endMethod(); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java index 8757727a14..8182828853 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java @@ -17,6 +17,7 @@ package io.realm.processor; import java.util.Date; +import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.VariableElement; @@ -75,6 +76,7 @@ public static TypeMirror getRealmListElementTypeMirror(VariableElement field) { if (!Utils.isRealmList(field)) { return null; } - return ((DeclaredType) field.asType()).getTypeArguments().get(0); + List typeArguments = ((DeclaredType) field.asType()).getTypeArguments(); + return (!typeArguments.isEmpty()) ? typeArguments.get(0) : null; } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java index 88d3a47abd..d091f6b797 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java @@ -71,6 +71,14 @@ public static String getProxyClassSimpleName(VariableElement field) { } } + public static String getModelClassQualifiedName(VariableElement field) { + if (typeUtils.isAssignable(field.asType(), realmList)) { + return getGenericTypeQualifiedName(field); + } else { + return getFieldTypeQualifiedName(field); + } + } + /** * @return the proxy class name for a given clazz */ @@ -403,4 +411,13 @@ public static String getReferencedTypeInternalClassNameStatement(String qualifie // proxy class, even for files in other jar files. return "io.realm." + Utils.getProxyClassName(qualifiedClassName) + ".ClassNameHelper.INTERNAL_CLASS_NAME"; } + + /** + * Returns a simple reference to the ColumnInfo class inside this model class, i.e. the package + * name is not prefixed. + */ + public static String getSimpleColumnInfoClassName(String qualifiedModelClassName) { + String simpleModelClassName = Utils.stripPackage(qualifiedModelClassName); + return Utils.getProxyClassName(qualifiedModelClassName) + "." + simpleModelClassName + "ColumnInfo"; + } } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 0983dd6b14..9c5b47c9da 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -2,6 +2,7 @@ import android.util.JsonReader; +import io.realm.ImportFlag; import io.realm.internal.ColumnInfo; import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsSchemaInfo; @@ -79,13 +80,14 @@ public Set> getModelClasses() { } @Override - public E copyOrUpdate(Realm realm, E obj, boolean update, Map cache) { + public E copyOrUpdate(Realm realm, E obj, boolean update, Map cache, Set flags) { // This cast is correct because obj is either // generated by RealmProxy or the original type extending directly from RealmObject @SuppressWarnings("unchecked") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass()); if (clazz.equals(some.test.AllTypes.class)) { - return clazz.cast(io.realm.some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some.test.AllTypes) obj, update, cache)); + some_test_AllTypesRealmProxy.AllTypesColumnInfo columnInfo = (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); + return clazz.cast(io.realm.some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnInfo, (some.test.AllTypes) obj, update, cache, flags)); } throw getMissingProxyClassException(clazz); } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index ee1fccac5a..a9899a51d0 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ImportFlag; import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; @@ -18,6 +19,7 @@ import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.android.JsonUtils; +import io.realm.internal.objectstore.OsObjectBuilder; import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; @@ -27,6 +29,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -36,6 +39,7 @@ public class some_test_AllTypesRealmProxy extends some.test.AllTypes implements RealmObjectProxy, some_test_AllTypesRealmProxyInterface { static final class AllTypesColumnInfo extends ColumnInfo { + long maxColumnIndexValue; long columnStringIndex; long columnLongIndex; long columnFloatIndex; @@ -81,6 +85,7 @@ static final class AllTypesColumnInfo extends ColumnInfo { this.columnFloatListIndex = addColumnDetails("columnFloatList", "columnFloatList", objectSchemaInfo); this.columnDateListIndex = addColumnDetails("columnDateList", "columnDateList", objectSchemaInfo); addBacklinkDetails(schemaInfo, "parentObjects", "AllTypes", "columnObject"); + this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); } AllTypesColumnInfo(ColumnInfo src, boolean mutable) { @@ -117,6 +122,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.columnDoubleListIndex = src.columnDoubleListIndex; dst.columnFloatListIndex = src.columnFloatListIndex; dst.columnDateListIndex = src.columnDateListIndex; + dst.maxColumnIndexValue = src.maxColumnIndexValue; } } @@ -1155,7 +1161,16 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r return realm.copyToRealm(obj); } - public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes object, boolean update, Map cache) { + private static some_test_AllTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { + // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections.emptyList()); + io.realm.some_test_AllTypesRealmProxy obj = new io.realm.some_test_AllTypesRealmProxy(); + objectContext.clear(); + return obj; + } + + public static some.test.AllTypes copyOrUpdate(Realm realm, AllTypesColumnInfo columnInfo, some.test.AllTypes object, boolean update, Map cache, Set flags) { if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { @@ -1175,7 +1190,6 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob boolean canUpdate = update; if (canUpdate) { Table table = realm.getTable(some.test.AllTypes.class); - AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); long pkColumnIndex = columnInfo.columnStringIndex; String value = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); long rowIndex = Table.NO_MATCH; @@ -1188,7 +1202,7 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob canUpdate = false; } else { try { - objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); + objectContext.set(realm, table.getUncheckedRow(rowIndex), columnInfo, false, Collections. emptyList()); realmObject = new io.realm.some_test_AllTypesRealmProxy(); cache.put(object, (RealmObjectProxy) realmObject); } finally { @@ -1197,31 +1211,47 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, some.test.AllTypes ob } } - return (canUpdate) ? update(realm, realmObject, object, cache) : copy(realm, object, update, cache); + return (canUpdate) ? update(realm, columnInfo, realmObject, object, cache, flags) : copy(realm, columnInfo, object, update, cache, flags); } - public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, boolean update, Map cache) { + public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo, some.test.AllTypes newObject, boolean update, Map cache, Set flags) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.AllTypes) cachedRealmObject; } - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.AllTypes realmObject = realm.createObjectInternal(some.test.AllTypes.class, ((some_test_AllTypesRealmProxyInterface) newObject).realmGet$columnString(), false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - some_test_AllTypesRealmProxyInterface realmObjectSource = (some_test_AllTypesRealmProxyInterface) newObject; - some_test_AllTypesRealmProxyInterface realmObjectCopy = (some_test_AllTypesRealmProxyInterface) realmObject; - - realmObjectCopy.realmSet$columnLong(realmObjectSource.realmGet$columnLong()); - realmObjectCopy.realmSet$columnFloat(realmObjectSource.realmGet$columnFloat()); - realmObjectCopy.realmSet$columnDouble(realmObjectSource.realmGet$columnDouble()); - realmObjectCopy.realmSet$columnBoolean(realmObjectSource.realmGet$columnBoolean()); - realmObjectCopy.realmSet$columnDate(realmObjectSource.realmGet$columnDate()); - realmObjectCopy.realmSet$columnBinary(realmObjectSource.realmGet$columnBinary()); - - realmObjectCopy.realmGet$columnMutableRealmInteger().set(realmObjectSource.realmGet$columnMutableRealmInteger().get()); + Table table = realm.getTable(some.test.AllTypes.class); + OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + + // Add all non-"object reference" fields + builder.addString(columnInfo.columnStringIndex, realmObjectSource.realmGet$columnString()); + builder.addInteger(columnInfo.columnLongIndex, realmObjectSource.realmGet$columnLong()); + builder.addFloat(columnInfo.columnFloatIndex, realmObjectSource.realmGet$columnFloat()); + builder.addDouble(columnInfo.columnDoubleIndex, realmObjectSource.realmGet$columnDouble()); + builder.addBoolean(columnInfo.columnBooleanIndex, realmObjectSource.realmGet$columnBoolean()); + builder.addDate(columnInfo.columnDateIndex, realmObjectSource.realmGet$columnDate()); + builder.addByteArray(columnInfo.columnBinaryIndex, realmObjectSource.realmGet$columnBinary()); + builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerIndex, realmObjectSource.realmGet$columnMutableRealmInteger()); + builder.addStringList(columnInfo.columnStringListIndex, realmObjectSource.realmGet$columnStringList()); + builder.addByteArrayList(columnInfo.columnBinaryListIndex, realmObjectSource.realmGet$columnBinaryList()); + builder.addBooleanList(columnInfo.columnBooleanListIndex, realmObjectSource.realmGet$columnBooleanList()); + builder.addLongList(columnInfo.columnLongListIndex, realmObjectSource.realmGet$columnLongList()); + builder.addIntegerList(columnInfo.columnIntegerListIndex, realmObjectSource.realmGet$columnIntegerList()); + builder.addShortList(columnInfo.columnShortListIndex, realmObjectSource.realmGet$columnShortList()); + builder.addByteList(columnInfo.columnByteListIndex, realmObjectSource.realmGet$columnByteList()); + builder.addDoubleList(columnInfo.columnDoubleListIndex, realmObjectSource.realmGet$columnDoubleList()); + builder.addFloatList(columnInfo.columnFloatListIndex, realmObjectSource.realmGet$columnFloatList()); + builder.addDateList(columnInfo.columnDateListIndex, realmObjectSource.realmGet$columnDateList()); + + // Create the underlying object and cache it before setting any object/objectlist references + // This will allow us to break any circular dependencies by using the object cache. + Row row = builder.createNewObject(); + io.realm.some_test_AllTypesRealmProxy realmObjectCopy = newProxyInstance(realm, row); + cache.put(newObject, realmObjectCopy); + + // Finally add all fields that reference other Realm Objects, either directly or through a list some.test.AllTypes columnObjectObj = realmObjectSource.realmGet$columnObject(); if (columnObjectObj == null) { realmObjectCopy.realmSet$columnObject(null); @@ -1230,7 +1260,7 @@ public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, if (cachecolumnObject != null) { realmObjectCopy.realmSet$columnObject(cachecolumnObject); } else { - realmObjectCopy.realmSet$columnObject(some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, update, cache)); + realmObjectCopy.realmSet$columnObject(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnObjectObj, update, cache, flags)); } } @@ -1244,22 +1274,12 @@ public static some.test.AllTypes copy(Realm realm, some.test.AllTypes newObject, if (cachecolumnRealmList != null) { columnRealmListRealmList.add(cachecolumnRealmList); } else { - columnRealmListRealmList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, update, cache)); + columnRealmListRealmList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnRealmListItem, update, cache, flags)); } } } - realmObjectCopy.realmSet$columnStringList(realmObjectSource.realmGet$columnStringList()); - realmObjectCopy.realmSet$columnBinaryList(realmObjectSource.realmGet$columnBinaryList()); - realmObjectCopy.realmSet$columnBooleanList(realmObjectSource.realmGet$columnBooleanList()); - realmObjectCopy.realmSet$columnLongList(realmObjectSource.realmGet$columnLongList()); - realmObjectCopy.realmSet$columnIntegerList(realmObjectSource.realmGet$columnIntegerList()); - realmObjectCopy.realmSet$columnShortList(realmObjectSource.realmGet$columnShortList()); - realmObjectCopy.realmSet$columnByteList(realmObjectSource.realmGet$columnByteList()); - realmObjectCopy.realmSet$columnDoubleList(realmObjectSource.realmGet$columnDoubleList()); - realmObjectCopy.realmSet$columnFloatList(realmObjectSource.realmGet$columnFloatList()); - realmObjectCopy.realmSet$columnDateList(realmObjectSource.realmGet$columnDateList()); - return realmObject; + return realmObjectCopy; } public static long insert(Realm realm, some.test.AllTypes object, Map cache) { @@ -2161,65 +2181,60 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje return unmanagedObject; } - static some.test.AllTypes update(Realm realm, some.test.AllTypes realmObject, some.test.AllTypes newObject, Map cache) { + static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, some.test.AllTypes realmObject, some.test.AllTypes newObject, Map cache, Set flags) { some_test_AllTypesRealmProxyInterface realmObjectTarget = (some_test_AllTypesRealmProxyInterface) realmObject; some_test_AllTypesRealmProxyInterface realmObjectSource = (some_test_AllTypesRealmProxyInterface) newObject; - realmObjectTarget.realmSet$columnLong(realmObjectSource.realmGet$columnLong()); - realmObjectTarget.realmSet$columnFloat(realmObjectSource.realmGet$columnFloat()); - realmObjectTarget.realmSet$columnDouble(realmObjectSource.realmGet$columnDouble()); - realmObjectTarget.realmSet$columnBoolean(realmObjectSource.realmGet$columnBoolean()); - realmObjectTarget.realmSet$columnDate(realmObjectSource.realmGet$columnDate()); - realmObjectTarget.realmSet$columnBinary(realmObjectSource.realmGet$columnBinary()); - realmObjectTarget.realmGet$columnMutableRealmInteger().set(realmObjectSource.realmGet$columnMutableRealmInteger().get()); + Table table = realm.getTable(some.test.AllTypes.class); + OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + builder.addString(columnInfo.columnStringIndex, realmObjectSource.realmGet$columnString()); + builder.addInteger(columnInfo.columnLongIndex, realmObjectSource.realmGet$columnLong()); + builder.addFloat(columnInfo.columnFloatIndex, realmObjectSource.realmGet$columnFloat()); + builder.addDouble(columnInfo.columnDoubleIndex, realmObjectSource.realmGet$columnDouble()); + builder.addBoolean(columnInfo.columnBooleanIndex, realmObjectSource.realmGet$columnBoolean()); + builder.addDate(columnInfo.columnDateIndex, realmObjectSource.realmGet$columnDate()); + builder.addByteArray(columnInfo.columnBinaryIndex, realmObjectSource.realmGet$columnBinary()); + builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerIndex, realmObjectSource.realmGet$columnMutableRealmInteger()); + some.test.AllTypes columnObjectObj = realmObjectSource.realmGet$columnObject(); if (columnObjectObj == null) { - realmObjectTarget.realmSet$columnObject(null); + builder.addNull(columnInfo.columnObjectIndex); } else { some.test.AllTypes cachecolumnObject = (some.test.AllTypes) cache.get(columnObjectObj); if (cachecolumnObject != null) { - realmObjectTarget.realmSet$columnObject(cachecolumnObject); + builder.addObject(columnInfo.columnObjectIndex, cachecolumnObject); } else { - realmObjectTarget.realmSet$columnObject(some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnObjectObj, true, cache)); + builder.addObject(columnInfo.columnObjectIndex, some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnObjectObj, true, cache, flags)); } } + RealmList columnRealmListList = realmObjectSource.realmGet$columnRealmList(); - RealmList columnRealmListRealmList = realmObjectTarget.realmGet$columnRealmList(); - if (columnRealmListList != null && columnRealmListList.size() == columnRealmListRealmList.size()) { - // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. - int objects = columnRealmListList.size(); - for (int i = 0; i < objects; i++) { + if (columnRealmListList != null) { + RealmList columnRealmListManagedCopy = new RealmList(); + for (int i = 0; i < columnRealmListList.size(); i++) { some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); if (cachecolumnRealmList != null) { - columnRealmListRealmList.set(i, cachecolumnRealmList); + columnRealmListManagedCopy.add(cachecolumnRealmList); } else { - columnRealmListRealmList.set(i, some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); + columnRealmListManagedCopy.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnRealmListItem, true, cache, flags)); } } + builder.addObjectList(columnInfo.columnRealmListIndex, columnRealmListManagedCopy); } else { - columnRealmListRealmList.clear(); - if (columnRealmListList != null) { - for (int i = 0; i < columnRealmListList.size(); i++) { - some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); - some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); - if (cachecolumnRealmList != null) { - columnRealmListRealmList.add(cachecolumnRealmList); - } else { - columnRealmListRealmList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, columnRealmListItem, true, cache)); - } - } - } - } - realmObjectTarget.realmSet$columnStringList(realmObjectSource.realmGet$columnStringList()); - realmObjectTarget.realmSet$columnBinaryList(realmObjectSource.realmGet$columnBinaryList()); - realmObjectTarget.realmSet$columnBooleanList(realmObjectSource.realmGet$columnBooleanList()); - realmObjectTarget.realmSet$columnLongList(realmObjectSource.realmGet$columnLongList()); - realmObjectTarget.realmSet$columnIntegerList(realmObjectSource.realmGet$columnIntegerList()); - realmObjectTarget.realmSet$columnShortList(realmObjectSource.realmGet$columnShortList()); - realmObjectTarget.realmSet$columnByteList(realmObjectSource.realmGet$columnByteList()); - realmObjectTarget.realmSet$columnDoubleList(realmObjectSource.realmGet$columnDoubleList()); - realmObjectTarget.realmSet$columnFloatList(realmObjectSource.realmGet$columnFloatList()); - realmObjectTarget.realmSet$columnDateList(realmObjectSource.realmGet$columnDateList()); + builder.addObjectList(columnInfo.columnRealmListIndex, new RealmList()); + } + builder.addStringList(columnInfo.columnStringListIndex, realmObjectSource.realmGet$columnStringList()); + builder.addByteArrayList(columnInfo.columnBinaryListIndex, realmObjectSource.realmGet$columnBinaryList()); + builder.addBooleanList(columnInfo.columnBooleanListIndex, realmObjectSource.realmGet$columnBooleanList()); + builder.addLongList(columnInfo.columnLongListIndex, realmObjectSource.realmGet$columnLongList()); + builder.addIntegerList(columnInfo.columnIntegerListIndex, realmObjectSource.realmGet$columnIntegerList()); + builder.addShortList(columnInfo.columnShortListIndex, realmObjectSource.realmGet$columnShortList()); + builder.addByteList(columnInfo.columnByteListIndex, realmObjectSource.realmGet$columnByteList()); + builder.addDoubleList(columnInfo.columnDoubleListIndex, realmObjectSource.realmGet$columnDoubleList()); + builder.addFloatList(columnInfo.columnFloatListIndex, realmObjectSource.realmGet$columnFloatList()); + builder.addDateList(columnInfo.columnDateListIndex, realmObjectSource.realmGet$columnDateList()); + + builder.updateExistingObject(); return realmObject; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java index ac32ff2b20..770ec514e8 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ImportFlag; import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; @@ -17,6 +18,7 @@ import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.android.JsonUtils; +import io.realm.internal.objectstore.OsObjectBuilder; import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; @@ -26,6 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -35,6 +38,7 @@ public class some_test_BooleansRealmProxy extends some.test.Booleans implements RealmObjectProxy, some_test_BooleansRealmProxyInterface { static final class BooleansColumnInfo extends ColumnInfo { + long maxColumnIndexValue; long doneIndex; long isReadyIndex; long mCompletedIndex; @@ -47,6 +51,7 @@ static final class BooleansColumnInfo extends ColumnInfo { this.isReadyIndex = addColumnDetails("isReady", "isReady", objectSchemaInfo); this.mCompletedIndex = addColumnDetails("mCompleted", "mCompleted", objectSchemaInfo); this.anotherBooleanIndex = addColumnDetails("anotherBoolean", "anotherBoolean", objectSchemaInfo); + this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); } BooleansColumnInfo(ColumnInfo src, boolean mutable) { @@ -67,6 +72,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.isReadyIndex = src.isReadyIndex; dst.mCompletedIndex = src.mCompletedIndex; dst.anotherBooleanIndex = src.anotherBooleanIndex; + dst.maxColumnIndexValue = src.maxColumnIndexValue; } } @@ -290,7 +296,16 @@ public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader r return realm.copyToRealm(obj); } - public static some.test.Booleans copyOrUpdate(Realm realm, some.test.Booleans object, boolean update, Map cache) { + private static some_test_BooleansRealmProxy newProxyInstance(BaseRealm realm, Row row) { + // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.Booleans.class), false, Collections.emptyList()); + io.realm.some_test_BooleansRealmProxy obj = new io.realm.some_test_BooleansRealmProxy(); + objectContext.clear(); + return obj; + } + + public static some.test.Booleans copyOrUpdate(Realm realm, BooleansColumnInfo columnInfo, some.test.Booleans object, boolean update, Map cache, Set flags) { if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { @@ -306,27 +321,33 @@ public static some.test.Booleans copyOrUpdate(Realm realm, some.test.Booleans ob return (some.test.Booleans) cachedRealmObject; } - return copy(realm, object, update, cache); + return copy(realm, columnInfo, object, update, cache, flags); } - public static some.test.Booleans copy(Realm realm, some.test.Booleans newObject, boolean update, Map cache) { + public static some.test.Booleans copy(Realm realm, BooleansColumnInfo columnInfo, some.test.Booleans newObject, boolean update, Map cache, Set flags) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.Booleans) cachedRealmObject; } - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.Booleans realmObject = realm.createObjectInternal(some.test.Booleans.class, false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - some_test_BooleansRealmProxyInterface realmObjectSource = (some_test_BooleansRealmProxyInterface) newObject; - some_test_BooleansRealmProxyInterface realmObjectCopy = (some_test_BooleansRealmProxyInterface) realmObject; - realmObjectCopy.realmSet$done(realmObjectSource.realmGet$done()); - realmObjectCopy.realmSet$isReady(realmObjectSource.realmGet$isReady()); - realmObjectCopy.realmSet$mCompleted(realmObjectSource.realmGet$mCompleted()); - realmObjectCopy.realmSet$anotherBoolean(realmObjectSource.realmGet$anotherBoolean()); - return realmObject; + Table table = realm.getTable(some.test.Booleans.class); + OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + + // Add all non-"object reference" fields + builder.addBoolean(columnInfo.doneIndex, realmObjectSource.realmGet$done()); + builder.addBoolean(columnInfo.isReadyIndex, realmObjectSource.realmGet$isReady()); + builder.addBoolean(columnInfo.mCompletedIndex, realmObjectSource.realmGet$mCompleted()); + builder.addBoolean(columnInfo.anotherBooleanIndex, realmObjectSource.realmGet$anotherBoolean()); + + // Create the underlying object and cache it before setting any object/objectlist references + // This will allow us to break any circular dependencies by using the object cache. + Row row = builder.createNewObject(); + io.realm.some_test_BooleansRealmProxy realmObjectCopy = newProxyInstance(realm, row); + cache.put(newObject, realmObjectCopy); + + return realmObjectCopy; } public static long insert(Realm realm, some.test.Booleans object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java index b33b615633..31e9a3b3fe 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ImportFlag; import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; @@ -17,6 +18,7 @@ import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.android.JsonUtils; +import io.realm.internal.objectstore.OsObjectBuilder; import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; @@ -26,6 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -35,6 +38,7 @@ public class some_test_NamePolicyMixedClassSettingsRealmProxy extends some.test. implements RealmObjectProxy, some_test_NamePolicyMixedClassSettingsRealmProxyInterface { static final class NamePolicyMixedClassSettingsColumnInfo extends ColumnInfo { + long maxColumnIndexValue; long firstNameIndex; long lastNameIndex; @@ -43,6 +47,7 @@ static final class NamePolicyMixedClassSettingsColumnInfo extends ColumnInfo { OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("customName"); this.firstNameIndex = addColumnDetails("firstName", "first_name", objectSchemaInfo); this.lastNameIndex = addColumnDetails("lastName", "LastName", objectSchemaInfo); + this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); } NamePolicyMixedClassSettingsColumnInfo(ColumnInfo src, boolean mutable) { @@ -61,6 +66,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final NamePolicyMixedClassSettingsColumnInfo dst = (NamePolicyMixedClassSettingsColumnInfo) rawDst; dst.firstNameIndex = src.firstNameIndex; dst.lastNameIndex = src.lastNameIndex; + dst.maxColumnIndexValue = src.maxColumnIndexValue; } } @@ -226,7 +232,16 @@ public static some.test.NamePolicyMixedClassSettings createUsingJsonStream(Realm return realm.copyToRealm(obj); } - public static some.test.NamePolicyMixedClassSettings copyOrUpdate(Realm realm, some.test.NamePolicyMixedClassSettings object, boolean update, Map cache) { + private static some_test_NamePolicyMixedClassSettingsRealmProxy newProxyInstance(BaseRealm realm, Row row) { + // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class), false, Collections.emptyList()); + io.realm.some_test_NamePolicyMixedClassSettingsRealmProxy obj = new io.realm.some_test_NamePolicyMixedClassSettingsRealmProxy(); + objectContext.clear(); + return obj; + } + + public static some.test.NamePolicyMixedClassSettings copyOrUpdate(Realm realm, NamePolicyMixedClassSettingsColumnInfo columnInfo, some.test.NamePolicyMixedClassSettings object, boolean update, Map cache, Set flags) { if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { @@ -242,25 +257,31 @@ public static some.test.NamePolicyMixedClassSettings copyOrUpdate(Realm realm, s return (some.test.NamePolicyMixedClassSettings) cachedRealmObject; } - return copy(realm, object, update, cache); + return copy(realm, columnInfo, object, update, cache, flags); } - public static some.test.NamePolicyMixedClassSettings copy(Realm realm, some.test.NamePolicyMixedClassSettings newObject, boolean update, Map cache) { + public static some.test.NamePolicyMixedClassSettings copy(Realm realm, NamePolicyMixedClassSettingsColumnInfo columnInfo, some.test.NamePolicyMixedClassSettings newObject, boolean update, Map cache, Set flags) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.NamePolicyMixedClassSettings) cachedRealmObject; } - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.NamePolicyMixedClassSettings realmObject = realm.createObjectInternal(some.test.NamePolicyMixedClassSettings.class, false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - some_test_NamePolicyMixedClassSettingsRealmProxyInterface realmObjectSource = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) newObject; - some_test_NamePolicyMixedClassSettingsRealmProxyInterface realmObjectCopy = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) realmObject; - realmObjectCopy.realmSet$firstName(realmObjectSource.realmGet$firstName()); - realmObjectCopy.realmSet$lastName(realmObjectSource.realmGet$lastName()); - return realmObject; + Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); + OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + + // Add all non-"object reference" fields + builder.addString(columnInfo.firstNameIndex, realmObjectSource.realmGet$firstName()); + builder.addString(columnInfo.lastNameIndex, realmObjectSource.realmGet$lastName()); + + // Create the underlying object and cache it before setting any object/objectlist references + // This will allow us to break any circular dependencies by using the object cache. + Row row = builder.createNewObject(); + io.realm.some_test_NamePolicyMixedClassSettingsRealmProxy realmObjectCopy = newProxyInstance(realm, row); + cache.put(newObject, realmObjectCopy); + + return realmObjectCopy; } public static long insert(Realm realm, some.test.NamePolicyMixedClassSettings object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java index 29a6d97b08..e65a432ff1 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ImportFlag; import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; @@ -17,6 +18,7 @@ import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.android.JsonUtils; +import io.realm.internal.objectstore.OsObjectBuilder; import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; @@ -26,6 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -35,6 +38,7 @@ public class some_test_NamePolicyModuleDefaultsRealmProxy extends some.test.Name implements RealmObjectProxy, some_test_NamePolicyModuleDefaultsRealmProxyInterface { static final class NamePolicyModuleDefaultsColumnInfo extends ColumnInfo { + long maxColumnIndexValue; long firstNameIndex; long lastNameIndex; @@ -43,6 +47,7 @@ static final class NamePolicyModuleDefaultsColumnInfo extends ColumnInfo { OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("NamePolicyModuleDefaults"); this.firstNameIndex = addColumnDetails("firstName", "FirstName", objectSchemaInfo); this.lastNameIndex = addColumnDetails("lastName", "LastName", objectSchemaInfo); + this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); } NamePolicyModuleDefaultsColumnInfo(ColumnInfo src, boolean mutable) { @@ -61,6 +66,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final NamePolicyModuleDefaultsColumnInfo dst = (NamePolicyModuleDefaultsColumnInfo) rawDst; dst.firstNameIndex = src.firstNameIndex; dst.lastNameIndex = src.lastNameIndex; + dst.maxColumnIndexValue = src.maxColumnIndexValue; } } @@ -226,7 +232,16 @@ public static some.test.NamePolicyModuleDefaults createUsingJsonStream(Realm rea return realm.copyToRealm(obj); } - public static some.test.NamePolicyModuleDefaults copyOrUpdate(Realm realm, some.test.NamePolicyModuleDefaults object, boolean update, Map cache) { + private static some_test_NamePolicyModuleDefaultsRealmProxy newProxyInstance(BaseRealm realm, Row row) { + // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class), false, Collections.emptyList()); + io.realm.some_test_NamePolicyModuleDefaultsRealmProxy obj = new io.realm.some_test_NamePolicyModuleDefaultsRealmProxy(); + objectContext.clear(); + return obj; + } + + public static some.test.NamePolicyModuleDefaults copyOrUpdate(Realm realm, NamePolicyModuleDefaultsColumnInfo columnInfo, some.test.NamePolicyModuleDefaults object, boolean update, Map cache, Set flags) { if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { @@ -242,25 +257,31 @@ public static some.test.NamePolicyModuleDefaults copyOrUpdate(Realm realm, some. return (some.test.NamePolicyModuleDefaults) cachedRealmObject; } - return copy(realm, object, update, cache); + return copy(realm, columnInfo, object, update, cache, flags); } - public static some.test.NamePolicyModuleDefaults copy(Realm realm, some.test.NamePolicyModuleDefaults newObject, boolean update, Map cache) { + public static some.test.NamePolicyModuleDefaults copy(Realm realm, NamePolicyModuleDefaultsColumnInfo columnInfo, some.test.NamePolicyModuleDefaults newObject, boolean update, Map cache, Set flags) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.NamePolicyModuleDefaults) cachedRealmObject; } - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.NamePolicyModuleDefaults realmObject = realm.createObjectInternal(some.test.NamePolicyModuleDefaults.class, false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - some_test_NamePolicyModuleDefaultsRealmProxyInterface realmObjectSource = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) newObject; - some_test_NamePolicyModuleDefaultsRealmProxyInterface realmObjectCopy = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) realmObject; - realmObjectCopy.realmSet$firstName(realmObjectSource.realmGet$firstName()); - realmObjectCopy.realmSet$lastName(realmObjectSource.realmGet$lastName()); - return realmObject; + Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); + OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + + // Add all non-"object reference" fields + builder.addString(columnInfo.firstNameIndex, realmObjectSource.realmGet$firstName()); + builder.addString(columnInfo.lastNameIndex, realmObjectSource.realmGet$lastName()); + + // Create the underlying object and cache it before setting any object/objectlist references + // This will allow us to break any circular dependencies by using the object cache. + Row row = builder.createNewObject(); + io.realm.some_test_NamePolicyModuleDefaultsRealmProxy realmObjectCopy = newProxyInstance(realm, row); + cache.put(newObject, realmObjectCopy); + + return realmObjectCopy; } public static long insert(Realm realm, some.test.NamePolicyModuleDefaults object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index 1e5c65b4a4..5d37725d00 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ImportFlag; import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; @@ -17,6 +18,7 @@ import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.android.JsonUtils; +import io.realm.internal.objectstore.OsObjectBuilder; import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; @@ -26,6 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -35,6 +38,7 @@ public class some_test_NullTypesRealmProxy extends some.test.NullTypes implements RealmObjectProxy, some_test_NullTypesRealmProxyInterface { static final class NullTypesColumnInfo extends ColumnInfo { + long maxColumnIndexValue; long fieldStringNotNullIndex; long fieldStringNullIndex; long fieldBooleanNotNullIndex; @@ -121,6 +125,7 @@ static final class NullTypesColumnInfo extends ColumnInfo { this.fieldFloatListNullIndex = addColumnDetails("fieldFloatListNull", "fieldFloatListNull", objectSchemaInfo); this.fieldDateListNotNullIndex = addColumnDetails("fieldDateListNotNull", "fieldDateListNotNull", objectSchemaInfo); this.fieldDateListNullIndex = addColumnDetails("fieldDateListNull", "fieldDateListNull", objectSchemaInfo); + this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); } NullTypesColumnInfo(ColumnInfo src, boolean mutable) { @@ -178,6 +183,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.fieldFloatListNullIndex = src.fieldFloatListNullIndex; dst.fieldDateListNotNullIndex = src.fieldDateListNotNullIndex; dst.fieldDateListNullIndex = src.fieldDateListNullIndex; + dst.maxColumnIndexValue = src.maxColumnIndexValue; } } @@ -2178,7 +2184,16 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader return realm.copyToRealm(obj); } - public static some.test.NullTypes copyOrUpdate(Realm realm, some.test.NullTypes object, boolean update, Map cache) { + private static some_test_NullTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { + // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NullTypes.class), false, Collections.emptyList()); + io.realm.some_test_NullTypesRealmProxy obj = new io.realm.some_test_NullTypesRealmProxy(); + objectContext.clear(); + return obj; + } + + public static some.test.NullTypes copyOrUpdate(Realm realm, NullTypesColumnInfo columnInfo, some.test.NullTypes object, boolean update, Map cache, Set flags) { if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { @@ -2194,43 +2209,69 @@ public static some.test.NullTypes copyOrUpdate(Realm realm, some.test.NullTypes return (some.test.NullTypes) cachedRealmObject; } - return copy(realm, object, update, cache); + return copy(realm, columnInfo, object, update, cache, flags); } - public static some.test.NullTypes copy(Realm realm, some.test.NullTypes newObject, boolean update, Map cache) { + public static some.test.NullTypes copy(Realm realm, NullTypesColumnInfo columnInfo, some.test.NullTypes newObject, boolean update, Map cache, Set flags) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.NullTypes) cachedRealmObject; } - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.NullTypes realmObject = realm.createObjectInternal(some.test.NullTypes.class, false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - some_test_NullTypesRealmProxyInterface realmObjectSource = (some_test_NullTypesRealmProxyInterface) newObject; - some_test_NullTypesRealmProxyInterface realmObjectCopy = (some_test_NullTypesRealmProxyInterface) realmObject; - - realmObjectCopy.realmSet$fieldStringNotNull(realmObjectSource.realmGet$fieldStringNotNull()); - realmObjectCopy.realmSet$fieldStringNull(realmObjectSource.realmGet$fieldStringNull()); - realmObjectCopy.realmSet$fieldBooleanNotNull(realmObjectSource.realmGet$fieldBooleanNotNull()); - realmObjectCopy.realmSet$fieldBooleanNull(realmObjectSource.realmGet$fieldBooleanNull()); - realmObjectCopy.realmSet$fieldBytesNotNull(realmObjectSource.realmGet$fieldBytesNotNull()); - realmObjectCopy.realmSet$fieldBytesNull(realmObjectSource.realmGet$fieldBytesNull()); - realmObjectCopy.realmSet$fieldByteNotNull(realmObjectSource.realmGet$fieldByteNotNull()); - realmObjectCopy.realmSet$fieldByteNull(realmObjectSource.realmGet$fieldByteNull()); - realmObjectCopy.realmSet$fieldShortNotNull(realmObjectSource.realmGet$fieldShortNotNull()); - realmObjectCopy.realmSet$fieldShortNull(realmObjectSource.realmGet$fieldShortNull()); - realmObjectCopy.realmSet$fieldIntegerNotNull(realmObjectSource.realmGet$fieldIntegerNotNull()); - realmObjectCopy.realmSet$fieldIntegerNull(realmObjectSource.realmGet$fieldIntegerNull()); - realmObjectCopy.realmSet$fieldLongNotNull(realmObjectSource.realmGet$fieldLongNotNull()); - realmObjectCopy.realmSet$fieldLongNull(realmObjectSource.realmGet$fieldLongNull()); - realmObjectCopy.realmSet$fieldFloatNotNull(realmObjectSource.realmGet$fieldFloatNotNull()); - realmObjectCopy.realmSet$fieldFloatNull(realmObjectSource.realmGet$fieldFloatNull()); - realmObjectCopy.realmSet$fieldDoubleNotNull(realmObjectSource.realmGet$fieldDoubleNotNull()); - realmObjectCopy.realmSet$fieldDoubleNull(realmObjectSource.realmGet$fieldDoubleNull()); - realmObjectCopy.realmSet$fieldDateNotNull(realmObjectSource.realmGet$fieldDateNotNull()); - realmObjectCopy.realmSet$fieldDateNull(realmObjectSource.realmGet$fieldDateNull()); + Table table = realm.getTable(some.test.NullTypes.class); + OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + + // Add all non-"object reference" fields + builder.addString(columnInfo.fieldStringNotNullIndex, realmObjectSource.realmGet$fieldStringNotNull()); + builder.addString(columnInfo.fieldStringNullIndex, realmObjectSource.realmGet$fieldStringNull()); + builder.addBoolean(columnInfo.fieldBooleanNotNullIndex, realmObjectSource.realmGet$fieldBooleanNotNull()); + builder.addBoolean(columnInfo.fieldBooleanNullIndex, realmObjectSource.realmGet$fieldBooleanNull()); + builder.addByteArray(columnInfo.fieldBytesNotNullIndex, realmObjectSource.realmGet$fieldBytesNotNull()); + builder.addByteArray(columnInfo.fieldBytesNullIndex, realmObjectSource.realmGet$fieldBytesNull()); + builder.addInteger(columnInfo.fieldByteNotNullIndex, realmObjectSource.realmGet$fieldByteNotNull()); + builder.addInteger(columnInfo.fieldByteNullIndex, realmObjectSource.realmGet$fieldByteNull()); + builder.addInteger(columnInfo.fieldShortNotNullIndex, realmObjectSource.realmGet$fieldShortNotNull()); + builder.addInteger(columnInfo.fieldShortNullIndex, realmObjectSource.realmGet$fieldShortNull()); + builder.addInteger(columnInfo.fieldIntegerNotNullIndex, realmObjectSource.realmGet$fieldIntegerNotNull()); + builder.addInteger(columnInfo.fieldIntegerNullIndex, realmObjectSource.realmGet$fieldIntegerNull()); + builder.addInteger(columnInfo.fieldLongNotNullIndex, realmObjectSource.realmGet$fieldLongNotNull()); + builder.addInteger(columnInfo.fieldLongNullIndex, realmObjectSource.realmGet$fieldLongNull()); + builder.addFloat(columnInfo.fieldFloatNotNullIndex, realmObjectSource.realmGet$fieldFloatNotNull()); + builder.addFloat(columnInfo.fieldFloatNullIndex, realmObjectSource.realmGet$fieldFloatNull()); + builder.addDouble(columnInfo.fieldDoubleNotNullIndex, realmObjectSource.realmGet$fieldDoubleNotNull()); + builder.addDouble(columnInfo.fieldDoubleNullIndex, realmObjectSource.realmGet$fieldDoubleNull()); + builder.addDate(columnInfo.fieldDateNotNullIndex, realmObjectSource.realmGet$fieldDateNotNull()); + builder.addDate(columnInfo.fieldDateNullIndex, realmObjectSource.realmGet$fieldDateNull()); + builder.addStringList(columnInfo.fieldStringListNotNullIndex, realmObjectSource.realmGet$fieldStringListNotNull()); + builder.addStringList(columnInfo.fieldStringListNullIndex, realmObjectSource.realmGet$fieldStringListNull()); + builder.addByteArrayList(columnInfo.fieldBinaryListNotNullIndex, realmObjectSource.realmGet$fieldBinaryListNotNull()); + builder.addByteArrayList(columnInfo.fieldBinaryListNullIndex, realmObjectSource.realmGet$fieldBinaryListNull()); + builder.addBooleanList(columnInfo.fieldBooleanListNotNullIndex, realmObjectSource.realmGet$fieldBooleanListNotNull()); + builder.addBooleanList(columnInfo.fieldBooleanListNullIndex, realmObjectSource.realmGet$fieldBooleanListNull()); + builder.addLongList(columnInfo.fieldLongListNotNullIndex, realmObjectSource.realmGet$fieldLongListNotNull()); + builder.addLongList(columnInfo.fieldLongListNullIndex, realmObjectSource.realmGet$fieldLongListNull()); + builder.addIntegerList(columnInfo.fieldIntegerListNotNullIndex, realmObjectSource.realmGet$fieldIntegerListNotNull()); + builder.addIntegerList(columnInfo.fieldIntegerListNullIndex, realmObjectSource.realmGet$fieldIntegerListNull()); + builder.addShortList(columnInfo.fieldShortListNotNullIndex, realmObjectSource.realmGet$fieldShortListNotNull()); + builder.addShortList(columnInfo.fieldShortListNullIndex, realmObjectSource.realmGet$fieldShortListNull()); + builder.addByteList(columnInfo.fieldByteListNotNullIndex, realmObjectSource.realmGet$fieldByteListNotNull()); + builder.addByteList(columnInfo.fieldByteListNullIndex, realmObjectSource.realmGet$fieldByteListNull()); + builder.addDoubleList(columnInfo.fieldDoubleListNotNullIndex, realmObjectSource.realmGet$fieldDoubleListNotNull()); + builder.addDoubleList(columnInfo.fieldDoubleListNullIndex, realmObjectSource.realmGet$fieldDoubleListNull()); + builder.addFloatList(columnInfo.fieldFloatListNotNullIndex, realmObjectSource.realmGet$fieldFloatListNotNull()); + builder.addFloatList(columnInfo.fieldFloatListNullIndex, realmObjectSource.realmGet$fieldFloatListNull()); + builder.addDateList(columnInfo.fieldDateListNotNullIndex, realmObjectSource.realmGet$fieldDateListNotNull()); + builder.addDateList(columnInfo.fieldDateListNullIndex, realmObjectSource.realmGet$fieldDateListNull()); + + // Create the underlying object and cache it before setting any object/objectlist references + // This will allow us to break any circular dependencies by using the object cache. + Row row = builder.createNewObject(); + io.realm.some_test_NullTypesRealmProxy realmObjectCopy = newProxyInstance(realm, row); + cache.put(newObject, realmObjectCopy); + + // Finally add all fields that reference other Realm Objects, either directly or through a list some.test.NullTypes fieldObjectNullObj = realmObjectSource.realmGet$fieldObjectNull(); if (fieldObjectNullObj == null) { realmObjectCopy.realmSet$fieldObjectNull(null); @@ -2239,30 +2280,11 @@ public static some.test.NullTypes copy(Realm realm, some.test.NullTypes newObjec if (cachefieldObjectNull != null) { realmObjectCopy.realmSet$fieldObjectNull(cachefieldObjectNull); } else { - realmObjectCopy.realmSet$fieldObjectNull(some_test_NullTypesRealmProxy.copyOrUpdate(realm, fieldObjectNullObj, update, cache)); - } - } - realmObjectCopy.realmSet$fieldStringListNotNull(realmObjectSource.realmGet$fieldStringListNotNull()); - realmObjectCopy.realmSet$fieldStringListNull(realmObjectSource.realmGet$fieldStringListNull()); - realmObjectCopy.realmSet$fieldBinaryListNotNull(realmObjectSource.realmGet$fieldBinaryListNotNull()); - realmObjectCopy.realmSet$fieldBinaryListNull(realmObjectSource.realmGet$fieldBinaryListNull()); - realmObjectCopy.realmSet$fieldBooleanListNotNull(realmObjectSource.realmGet$fieldBooleanListNotNull()); - realmObjectCopy.realmSet$fieldBooleanListNull(realmObjectSource.realmGet$fieldBooleanListNull()); - realmObjectCopy.realmSet$fieldLongListNotNull(realmObjectSource.realmGet$fieldLongListNotNull()); - realmObjectCopy.realmSet$fieldLongListNull(realmObjectSource.realmGet$fieldLongListNull()); - realmObjectCopy.realmSet$fieldIntegerListNotNull(realmObjectSource.realmGet$fieldIntegerListNotNull()); - realmObjectCopy.realmSet$fieldIntegerListNull(realmObjectSource.realmGet$fieldIntegerListNull()); - realmObjectCopy.realmSet$fieldShortListNotNull(realmObjectSource.realmGet$fieldShortListNotNull()); - realmObjectCopy.realmSet$fieldShortListNull(realmObjectSource.realmGet$fieldShortListNull()); - realmObjectCopy.realmSet$fieldByteListNotNull(realmObjectSource.realmGet$fieldByteListNotNull()); - realmObjectCopy.realmSet$fieldByteListNull(realmObjectSource.realmGet$fieldByteListNull()); - realmObjectCopy.realmSet$fieldDoubleListNotNull(realmObjectSource.realmGet$fieldDoubleListNotNull()); - realmObjectCopy.realmSet$fieldDoubleListNull(realmObjectSource.realmGet$fieldDoubleListNull()); - realmObjectCopy.realmSet$fieldFloatListNotNull(realmObjectSource.realmGet$fieldFloatListNotNull()); - realmObjectCopy.realmSet$fieldFloatListNull(realmObjectSource.realmGet$fieldFloatListNull()); - realmObjectCopy.realmSet$fieldDateListNotNull(realmObjectSource.realmGet$fieldDateListNotNull()); - realmObjectCopy.realmSet$fieldDateListNull(realmObjectSource.realmGet$fieldDateListNull()); - return realmObject; + realmObjectCopy.realmSet$fieldObjectNull(some_test_NullTypesRealmProxy.copyOrUpdate(realm, (some_test_NullTypesRealmProxy.NullTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.NullTypes.class), fieldObjectNullObj, update, cache, flags)); + } + } + + return realmObjectCopy; } public static long insert(Realm realm, some.test.NullTypes object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java index c072b532cb..e7e2e91a29 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java @@ -5,6 +5,7 @@ import android.os.Build; import android.util.JsonReader; import android.util.JsonToken; +import io.realm.ImportFlag; import io.realm.ProxyUtils; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.ColumnInfo; @@ -17,6 +18,7 @@ import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.android.JsonUtils; +import io.realm.internal.objectstore.OsObjectBuilder; import io.realm.log.RealmLog; import java.io.IOException; import java.util.ArrayList; @@ -26,6 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -35,6 +38,7 @@ public class some_test_SimpleRealmProxy extends some.test.Simple implements RealmObjectProxy, some_test_SimpleRealmProxyInterface { static final class SimpleColumnInfo extends ColumnInfo { + long maxColumnIndexValue; long nameIndex; long ageIndex; @@ -43,6 +47,7 @@ static final class SimpleColumnInfo extends ColumnInfo { OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("Simple"); this.nameIndex = addColumnDetails("name", "name", objectSchemaInfo); this.ageIndex = addColumnDetails("age", "age", objectSchemaInfo); + this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); } SimpleColumnInfo(ColumnInfo src, boolean mutable) { @@ -61,6 +66,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final SimpleColumnInfo dst = (SimpleColumnInfo) rawDst; dst.nameIndex = src.nameIndex; dst.ageIndex = src.ageIndex; + dst.maxColumnIndexValue = src.maxColumnIndexValue; } } @@ -218,7 +224,16 @@ public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader rea return realm.copyToRealm(obj); } - public static some.test.Simple copyOrUpdate(Realm realm, some.test.Simple object, boolean update, Map cache) { + private static some_test_SimpleRealmProxy newProxyInstance(BaseRealm realm, Row row) { + // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.Simple.class), false, Collections.emptyList()); + io.realm.some_test_SimpleRealmProxy obj = new io.realm.some_test_SimpleRealmProxy(); + objectContext.clear(); + return obj; + } + + public static some.test.Simple copyOrUpdate(Realm realm, SimpleColumnInfo columnInfo, some.test.Simple object, boolean update, Map cache, Set flags) { if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { @@ -234,25 +249,31 @@ public static some.test.Simple copyOrUpdate(Realm realm, some.test.Simple object return (some.test.Simple) cachedRealmObject; } - return copy(realm, object, update, cache); + return copy(realm, columnInfo, object, update, cache, flags); } - public static some.test.Simple copy(Realm realm, some.test.Simple newObject, boolean update, Map cache) { + public static some.test.Simple copy(Realm realm, SimpleColumnInfo columnInfo, some.test.Simple newObject, boolean update, Map cache, Set flags) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (some.test.Simple) cachedRealmObject; } - // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. - some.test.Simple realmObject = realm.createObjectInternal(some.test.Simple.class, false, Collections.emptyList()); - cache.put(newObject, (RealmObjectProxy) realmObject); - some_test_SimpleRealmProxyInterface realmObjectSource = (some_test_SimpleRealmProxyInterface) newObject; - some_test_SimpleRealmProxyInterface realmObjectCopy = (some_test_SimpleRealmProxyInterface) realmObject; - realmObjectCopy.realmSet$name(realmObjectSource.realmGet$name()); - realmObjectCopy.realmSet$age(realmObjectSource.realmGet$age()); - return realmObject; + Table table = realm.getTable(some.test.Simple.class); + OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + + // Add all non-"object reference" fields + builder.addString(columnInfo.nameIndex, realmObjectSource.realmGet$name()); + builder.addInteger(columnInfo.ageIndex, realmObjectSource.realmGet$age()); + + // Create the underlying object and cache it before setting any object/objectlist references + // This will allow us to break any circular dependencies by using the object cache. + Row row = builder.createNewObject(); + io.realm.some_test_SimpleRealmProxy realmObjectCopy = newProxyInstance(realm, row); + cache.put(newObject, realmObjectCopy); + + return realmObjectCopy; } public static long insert(Realm realm, some.test.Simple object, Map cache) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/MutableRealmIntegerTests.java b/realm/realm-library/src/androidTest/java/io/realm/MutableRealmIntegerTests.java index ae1c0c78f0..ffb65eb4da 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/MutableRealmIntegerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/MutableRealmIntegerTests.java @@ -189,7 +189,7 @@ public void required() { MutableRealmIntegerTypes c2 = realm.copyToRealm(c1); fail("should not be able to copy a null value to a @Required MutableRealmInteger"); } catch(IllegalArgumentException ignore) { - checkException(ignore, "is not nullable"); + checkException(ignore, "Missing value for property"); } realm.commitTransaction(); } @@ -429,7 +429,7 @@ public void testStream() throws IOException { obj = realm.createObjectFromJson(MutableRealmIntegerTypes.class, in); fail("Attempt to set @Required Mutable Realm Integer null, from JSON, should fail"); } catch (IllegalArgumentException ignore) { - checkException(ignore, "is not nullable"); + checkException(ignore, "Missing value for property"); } realm.commitTransaction(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index fe5e347d47..27ef50903e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -33,6 +33,8 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -44,6 +46,9 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nullable; + +import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.Dog; import io.realm.log.LogLevel; @@ -52,6 +57,7 @@ import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; +import kotlin.reflect.jvm.internal.impl.descriptors.deserialization.PlatformDependentDeclarationFilter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1098,4 +1104,31 @@ public void execute(Realm realm) { } }); } + + @Test + @RunTestInLooperThread + public void diffedUpdates_ignoredFieldsAreNotListedAsChanged() { + realm = looperThread.getRealm(); + AllJavaTypes obj; + realm.beginTransaction(); + AllJavaTypes childObject = realm.copyToRealm(new AllJavaTypes(1)); + obj = realm.copyToRealmOrUpdate(new AllJavaTypes(42)); + obj.setFieldObject(childObject); + obj.setFieldList(new RealmList<>(childObject)); + looperThread.keepStrongReference(obj); + realm.commitTransaction(); + obj.addChangeListener((RealmObjectChangeListener) (object, changeSet) -> { + assertEquals(1, changeSet.getChangedFields().length); + assertEquals("fieldString", changeSet.getChangedFields()[0]); + looperThread.testComplete(); + }); + + realm.beginTransaction(); + AllJavaTypes updatedObj = new AllJavaTypes(42); + updatedObj.setFieldString("updated"); + updatedObj.setFieldObject(childObject); + updatedObj.setFieldList(new RealmList<>(childObject)); + realm.copyToRealmOrUpdate(updatedObj, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET); + realm.commitTransaction(); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 453cadea35..c6065d7da8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -33,6 +33,7 @@ import org.junit.After; import org.junit.Assume; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -1499,6 +1500,46 @@ public void copyToRealm_boxedNumberPrimaryKeyIsNull() { } } + @Test + public void copyToRealm_duplicatedPrimaryKeyThrows() { + final String[] PRIMARY_KEY_TYPES = { "String", "BoxedLong", "long" }; + for (String className : PRIMARY_KEY_TYPES) { + String expectedKey = null; + try { + realm.beginTransaction(); + switch (className) { + case "String": { + expectedKey = "foo"; + PrimaryKeyAsString obj = new PrimaryKeyAsString("foo"); + realm.copyToRealm(obj); + realm.copyToRealm(obj); + break; + } + case "BoxedLong": { + expectedKey = Long.toString(Long.MIN_VALUE); + PrimaryKeyAsBoxedLong obj = new PrimaryKeyAsBoxedLong(Long.MIN_VALUE, "boxedlong"); + realm.copyToRealm(obj); + realm.copyToRealm(obj); + break; + } + case "long": + expectedKey = Long.toString(Long.MAX_VALUE); + PrimaryKeyAsLong obj = new PrimaryKeyAsLong(Long.MAX_VALUE); + realm.copyToRealm(obj); + realm.copyToRealm(obj); + break; + default: + } + fail("Null value as primary key already exists, but wasn't detected correctly"); + } catch (RealmPrimaryKeyConstraintException expected) { + assertTrue("Exception message is: " + expected.getMessage(), + expected.getMessage().contains("with an existing primary key value '"+ expectedKey +"'")); + } finally { + realm.cancelTransaction(); + } + } + } + @Test public void copyToRealm_duplicatedNullPrimaryKeyThrows() { final String[] PRIMARY_KEY_TYPES = {"String", "BoxedByte", "BoxedShort", "BoxedInteger", "BoxedLong"}; @@ -1530,10 +1571,10 @@ public void copyToRealm_duplicatedNullPrimaryKeyThrows() { break; default: } - fail("Null value as primary key already exists."); + fail("Null value as primary key already exists, but wasn't detected correctly"); } catch (RealmPrimaryKeyConstraintException expected) { assertTrue("Exception message is: " + expected.getMessage(), - expected.getMessage().contains("Primary key value already exists: 'null' .")); + expected.getMessage().contains("with an existing primary key value 'null'")); } finally { realm.cancelTransaction(); } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 5672cdb318..d1bd38ee21 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -66,6 +66,7 @@ set(classes_LIST io.realm.internal.OsObject io.realm.internal.OsRealmConfig io.realm.internal.OsList io.realm.internal.OsObjectStore io.realm.internal.sync.OsSubscription io.realm.internal.core.DescriptorOrdering + io.realm.internal.objectstore.OsObjectBuilder ) # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp index b291ea3c28..7653455602 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp @@ -137,3 +137,26 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrima CATCH_STD() return reinterpret_cast(nullptr); } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetMaxColumnIndex(JNIEnv* env, jclass, + jlong native_ptr) +{ + TR_ENTER_PTR(native_ptr) + + try { + auto& object_schema = *reinterpret_cast(native_ptr); + if (object_schema.persisted_properties.empty()) { + return static_cast(-1); + } else { + size_t maxIndex = 0; + for (Property p : object_schema.persisted_properties) { + if (p.table_column > maxIndex) { + maxIndex = p.table_column; + } + } + return static_cast(maxIndex); + } + } + CATCH_STD() + return static_cast(-1); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp new file mode 100644 index 0000000000..d23bb0bc59 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp @@ -0,0 +1,315 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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 "io_realm_internal_objectstore_OsObjectBuilder.h" + +#include "java_object_accessor.hpp" +#include "util.hpp" + +#include + +using namespace realm; +using namespace realm::jni_util; +using namespace realm::_impl; + +typedef std::vector OsObjectData; + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeDestroyBuilder(JNIEnv*, jclass, jlong data_ptr) +{ + TR_ENTER() + delete reinterpret_cast(data_ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeCreateBuilder(JNIEnv* env, jclass, jlong size) +{ + TR_ENTER() + try { + auto list = new std::vector(size); + return reinterpret_cast(list); + } + CATCH_STD() + return -1; +} + +static inline void add_property(jlong data_ptr, jlong column_index, JavaValue const& value) +{ + OsObjectData* data = reinterpret_cast(data_ptr); + data->at(column_index) = std::move(value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddNull + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index) +{ + try { + const JavaValue value = JavaValue(); + add_property(data_ptr, column_index, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddString + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jstring j_value) +{ + try { + JStringAccessor value(env, j_value); + std::string string_value(value); + const JavaValue wrapped_value(string_value); + add_property(data_ptr, column_index, wrapped_value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddInteger + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlong j_value) +{ + try { + const JavaValue value(j_value); + add_property(data_ptr, column_index, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddFloat + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jfloat j_value) +{ + try { + const JavaValue value(j_value); + add_property(data_ptr, column_index, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddDouble + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jdouble j_value) +{ + try { + const JavaValue value(j_value); + add_property(data_ptr, column_index, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddBoolean + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jboolean j_value) +{ + try { + const JavaValue value(j_value); + add_property(data_ptr, column_index, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddByteArray + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jbyteArray j_value) +{ + try { + auto data = OwnedBinaryData(JByteArrayAccessor(env, j_value).transform()); + const JavaValue value(data); + add_property(data_ptr, column_index, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddDate + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlong j_value) +{ + try { + const JavaValue value(from_milliseconds(j_value)); + add_property(data_ptr, column_index, value); + } + CATCH_STD() +} + + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddObject + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlong row_ptr) +{ + try { + const JavaValue value(reinterpret_cast(row_ptr)); + add_property(data_ptr, column_index, value); + } + CATCH_STD() +} + +static inline const ObjectSchema& get_schema(const Schema& schema, Table* table) +{ + std::string table_name(table->get_name()); + std::string class_name = std::string(table_name.substr(TABLE_PREFIX.length())); + auto it = schema.find(class_name); + if (it == schema.end()) { + throw std::runtime_error(format("Class '%1' cannot be found in the schema.", class_name.data())); + } + return *it; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeCreateOrUpdate + (JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong builder_ptr, jboolean update_existing, jboolean ignore_same_values) +{ + try { + SharedRealm shared_realm = *(reinterpret_cast(shared_realm_ptr)); + Table* table = reinterpret_cast(table_ptr); + const auto& schema = shared_realm->schema(); + const ObjectSchema& object_schema = get_schema(schema, table); + JavaContext ctx(env, shared_realm, object_schema); + auto list = *reinterpret_cast(builder_ptr); + JavaValue values = JavaValue(list); + Object obj = Object::create(ctx, shared_realm, object_schema, values, update_existing, ignore_same_values); + return reinterpret_cast(new Row(obj.row())); + } + CATCH_STD() + return realm::npos; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeStartList + (JNIEnv* env, jclass, jlong list_size) +{ + try { + auto list = new std::vector(); + list->reserve(list_size); + return reinterpret_cast(list); + } + CATCH_STD() + return realm::npos; +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeStopList + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlong list_ptr) +{ + try { + auto list = reinterpret_cast*>(list_ptr); + const JavaValue value((*list)); + add_property(data_ptr, column_index, value); + delete list; + } + CATCH_STD() +} + + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddObjectList + (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlongArray row_ptrs) +{ + try { + auto rows = JLongArrayAccessor(env, row_ptrs); + auto list = std::vector(); + list.reserve(rows.size()); + for (jsize i = 0; i < rows.size(); ++i) { + auto item = JavaValue(reinterpret_cast(rows[i])); + list.push_back(item); + } + JavaValue value(list); + add_property(data_ptr, column_index, value); + } + CATCH_STD() +} + +static inline void add_list_element(jlong list_ptr, JavaValue const& value) +{ + auto list = reinterpret_cast*>(list_ptr); + list->push_back(std::move(value)); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddNullListItem + (JNIEnv* env, jclass, jlong list_ptr) +{ + try { + const JavaValue value = JavaValue(); + add_list_element(list_ptr, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddIntegerListItem + (JNIEnv* env, jclass, jlong list_ptr, jlong j_value) +{ + try { + const JavaValue value(j_value); + add_list_element(list_ptr, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddStringListItem + (JNIEnv* env, jclass, jlong list_ptr, jstring j_value) +{ + try { + JStringAccessor value(env, j_value); + std::string string_value(value); + const JavaValue wrapped_value(string_value); + add_list_element(list_ptr, wrapped_value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddFloatListItem + (JNIEnv* env, jclass, jlong list_ptr, jfloat j_value) +{ + try { + const JavaValue value(j_value); + add_list_element(list_ptr, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddDoubleListItem + (JNIEnv* env, jclass, jlong list_ptr, jdouble j_value) +{ + try { + const JavaValue value(j_value); + add_list_element(list_ptr, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddBooleanListItem + (JNIEnv* env, jclass, jlong list_ptr, jboolean j_value) +{ + try { + const JavaValue value(j_value); + add_list_element(list_ptr, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddByteArrayListItem + (JNIEnv* env, jclass, jlong list_ptr, jbyteArray j_value) +{ + try { + auto data = OwnedBinaryData(JByteArrayAccessor(env, j_value).transform()); + const JavaValue value(data); + add_list_element(list_ptr, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddDateListItem + (JNIEnv* env, jclass, jlong list_ptr, jlong j_value) +{ + try { + const JavaValue value(from_milliseconds(j_value)); + add_list_element(list_ptr, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddObjectListItem + (JNIEnv* env, jclass, jlong list_ptr, jlong row_ptr) +{ + try { + const JavaValue value(reinterpret_cast(row_ptr)); + add_list_element(list_ptr, value); + } + CATCH_STD() +} \ No newline at end of file diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp new file mode 100644 index 0000000000..5054ec5dc3 --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -0,0 +1,533 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2018 Realm Inc. +// +// 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. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_JAVA_OBJECT_ACCESSOR +#define REALM_JAVA_OBJECT_ACCESSOR + +#include +#include +#include + +#include "java_accessor.hpp" +#include "java_class_global_def.hpp" +#include "object_accessor.hpp" +#include "object-store/src/property.hpp" + +#include +#include + +using namespace realm::_impl; + +#define REALM_FOR_EACH_JAVA_VALUE_TYPE(X) \ + X(Integer) \ + X(String) \ + X(Boolean) \ + X(Float) \ + X(Double) \ + X(Date) \ + X(Binary) \ + X(Object) \ + X(List) \ + + +namespace realm { + +struct JavaValue; + +enum class JavaValueType { + Empty, +#define REALM_DEFINE_JAVA_VALUE_TYPE(x) x, + REALM_FOR_EACH_JAVA_VALUE_TYPE(REALM_DEFINE_JAVA_VALUE_TYPE) +#undef REALM_DEFINE_JAVA_VALUE_TYPE + NumValueTypes +}; + +// Ugly work-around for initializer lists having problems on GCC 4.9 +template constexpr T realm_max(T a) { + return a; +} + +template constexpr T realm_max(T a, T b, Rest... rest) { + return a > realm_max(b, rest...) ? a : realm_max(b, rest...); +} + +template struct JavaValueTypeRepr; +template <> struct JavaValueTypeRepr { using Type = jlong; }; +template <> struct JavaValueTypeRepr { using Type = std::string; }; +template <> struct JavaValueTypeRepr { using Type = jboolean; }; +template <> struct JavaValueTypeRepr { using Type = jfloat; }; +template <> struct JavaValueTypeRepr { using Type = jdouble; }; +template <> struct JavaValueTypeRepr { using Type = Timestamp; }; +template <> struct JavaValueTypeRepr { using Type = OwnedBinaryData; }; +template <> struct JavaValueTypeRepr { using Type = RowExpr*; }; +template <> struct JavaValueTypeRepr { using Type = std::vector; }; + +// Tagged union class representing all the values Java can send to Object Store +struct JavaValue { + using Storage = std::aligned_storage_t::Type), + REALM_FOR_EACH_JAVA_VALUE_TYPE(REALM_GET_SIZE_OF_JAVA_VALUE_TYPE_REPR) + size_t(0) +#undef REALM_GET_SIZE_OF_JAVA_VALUE_TYPE_REPR + ), realm_max( +#define REALM_GET_ALIGN_OF_JAVA_VALUE_TYPE_REPR(x) \ + alignof(JavaValueTypeRepr::Type), + REALM_FOR_EACH_JAVA_VALUE_TYPE(REALM_GET_ALIGN_OF_JAVA_VALUE_TYPE_REPR) + size_t(0) +#undef REALM_GET_ALIGN_OF_JAVA_VALUE_TYPE_REPR + )>; + + Storage m_storage; + JavaValueType m_type; + + // Initializer constructors + JavaValue() : m_type(JavaValueType::Empty) {} + +#define REALM_DEFINE_JAVA_VALUE_TYPE_CONSTRUCTOR(x) \ + explicit JavaValue(JavaValueTypeRepr::Type value) : m_type(JavaValueType::x) \ + { \ + new(&m_storage) JavaValueTypeRepr::Type{std::move(value)}; \ + } + REALM_FOR_EACH_JAVA_VALUE_TYPE(REALM_DEFINE_JAVA_VALUE_TYPE_CONSTRUCTOR) +#undef REALM_DEFINE_JAVA_VALUE_TYPE_CONSTRUCTOR + + // Copy constructors + JavaValue(const JavaValue& jvt) : m_type(JavaValueType::Empty) { + *this = jvt; + } + + // Move constructor + JavaValue(JavaValue&& jvt) : m_type(JavaValueType::Empty) { + *this = std::move(jvt); + } + + ~JavaValue() + { + clear(); + } + + JavaValue& operator=(const JavaValue& rhs) + { + clear(); + switch (rhs.m_type) { +#define REALM_DEFINE_JAVA_VALUE_COPY_ASSIGNMENT(x) \ + case JavaValueType::x: { \ + using T = JavaValueTypeRepr::Type; \ + new(&m_storage) T{*reinterpret_cast(&rhs.m_storage)}; \ + break; \ + } + REALM_FOR_EACH_JAVA_VALUE_TYPE(REALM_DEFINE_JAVA_VALUE_COPY_ASSIGNMENT) +#undef REALM_DEFINE_JAVA_VALUE_COPY_ASSIGNMENT + default: REALM_ASSERT(rhs.m_type == JavaValueType::Empty); + } + m_type = rhs.m_type; + return *this; + } + + JavaValue& operator=(JavaValue&& rhs) + { + clear(); + switch (rhs.m_type) { + case JavaValueType::Empty: break; // Do nothing +#define REALM_DEFINE_JAVA_VALUE_COPY_ASSIGNMENT(x) \ + case JavaValueType::x: { \ + using T = JavaValueTypeRepr::Type; \ + new(&m_storage) T{std::move(*reinterpret_cast(&rhs.m_storage))}; \ + break; \ + } + REALM_FOR_EACH_JAVA_VALUE_TYPE(REALM_DEFINE_JAVA_VALUE_COPY_ASSIGNMENT) +#undef REALM_DEFINE_JAVA_VALUE_COPY_ASSIGNMENT + default: REALM_TERMINATE("Invalid type"); + } + m_type = rhs.m_type; + return *this; + } + + bool has_value() const noexcept + { + return m_type != JavaValueType::Empty; + } + + JavaValueType get_type() const noexcept + { + return m_type; + } + + template + const typename JavaValueTypeRepr::Type& get_as() const noexcept + { + REALM_ASSERT(m_type == type); + return *reinterpret_cast::Type*>(&m_storage); + } + + auto& get_int() const noexcept + { + return get_as(); + } + + auto& get_boolean() const noexcept + { + return get_as(); + } + + auto& get_string() const noexcept + { + return get_as(); + } + + auto& get_float() const noexcept + { + return get_as(); + } + + auto& get_double() const noexcept + { + return get_as(); + } + + auto& get_list() const noexcept + { + return get_as(); + } + + auto& get_date() const noexcept + { + return get_as(); + } + + auto& get_binary() const noexcept + { + return get_as(); + } + + auto& get_object() const noexcept + { + return get_as(); + } + + void clear() noexcept + { + switch (m_type) { + case JavaValueType::Empty: break; // Do nothing +#define REALM_DEFINE_JAVA_VALUE_DESTROY(x) \ + case JavaValueType::x: { \ + using T = JavaValueTypeRepr::Type; \ + reinterpret_cast(&m_storage)->~T(); \ + break; \ + } + REALM_FOR_EACH_JAVA_VALUE_TYPE(REALM_DEFINE_JAVA_VALUE_DESTROY) +#undef REALM_DEFINE_JAVA_VALUE_DESTROY + default: REALM_TERMINATE("Invalid type."); + } + m_type = JavaValueType::Empty; + } + + // Returns a string representation of the value contained in this object. + std::string to_string() const { + std::ostringstream ss; + switch(m_type) { + case JavaValueType::Empty: + return "null"; + case JavaValueType::Integer: + ss << static_cast(get_int()); + return std::string(ss.str()); + case JavaValueType::String: + return get_string(); + case JavaValueType::Boolean: + return (get_boolean() == JNI_TRUE) ? "true" : "false"; + case JavaValueType::Float: + ss << static_cast(get_float()); + return std::string(ss.str()); + case JavaValueType::Double: + ss << static_cast(get_double()); + return std::string(ss.str()); + case JavaValueType::Date: + ss << get_date(); + return std::string(ss.str()); + case JavaValueType::Binary: + ss << "Blob["; + ss << get_binary().size(); + ss << "]"; + return std::string(ss.str()); + case JavaValueType::Object: + ss << "Object[Type: "; + ss << get_object()->get_table()->get_name(); + ss << ", rowIndex: "; + ss << get_object()->get_index(); + ss << "]"; + return std::string(ss.str()); + case JavaValueType::List: + ss << "List[size: "; + ss << get_list().size(); + ss << "]"; + return std::string(ss.str()); + default: REALM_TERMINATE("Invalid type."); + } + } +}; + + +struct RequiredFieldValueNotProvidedException : public std::logic_error { + const std::string object_type; + RequiredFieldValueNotProvidedException(const std::string& object_type) + : std::logic_error("This field is required. A non-null '" + object_type + "' type value is expected.") + { + } +}; + +// This is the Java implementation of the `CppContext` class found in `object_accessor_impl.hpp` +// It is an object accessor context which can be used to create and access objects. +// It will map between JNI types and Cores data types. +class JavaContext { +public: + JavaContext(JNIEnv* env, std::shared_ptr realm, const ObjectSchema& os) + : m_env(env), + realm(std::move(realm)), + object_schema(&os) { } + + // This constructor is the only one used by the object accessor code, and is + // used when recurring into a link or array property during object creation + // (i.e. prop.type will always be Object or Array). + JavaContext(JavaContext& c, Property const& prop) + : m_env(c.m_env), + realm(c.realm) + , object_schema(prop.type == PropertyType::Object ? &*realm->schema().find(prop.object_type) : c.object_schema) + { } + + // The use of util::Optional for the following two functions is not a hard + // requirement; only that it be some type which can be evaluated in a + // boolean context to determine if it contains a value, and if it does + // contain a value it must be dereferencable to obtain that value. + + // Get the value for a property in an input object, or `util::none` if no + // value present. The property is identified both by the name of the + // property and its index within the ObjectScehma's persisted_properties + // array. + util::Optional value_for_property(JavaValue& dict, + Property const& prop, + size_t /*property_index*/) const + { + const std::vector& list = dict.get_list(); + auto property_value = list.at(prop.table_column); + return util::make_optional(property_value); + } + + // Get the default value for the given property in the given object schema, + // or `util::none` if there is none (which is distinct from the default + // being `null`). + // + // This implementation does not support default values; see the default + // value tests for an example of one which does. + util::Optional + default_value_for_property(ObjectSchema const&, Property const&) const + { + return util::none; + } + + // Invoke `fn` with each of the values from an enumerable type + template + void enumerate_list(JavaValue& value, Func&& fn) { + if (value.get_type() == JavaValueType::List) { + for (const auto& v : value.get_list()) { + fn(v); + } + } else { + throw std::logic_error("Type is not a list"); + } + } + + // Determine if `value` boxes the same List as `list` + bool is_same_list(List const& /*list*/, JavaValue const& /*value*/) + { + // Lists from Java are currently never the same as the ones found in Object Store. + return false; + } + + // Convert from core types to the boxed type. These are currently not used as Proxy objects read + // directly from the Row objects. This implementation is thus only here as a reminder of which + // method signatures to add if needed. + // JavaValueType box(BinaryData v) const { return reinterpret_cast(JavaClassGlobalDef::new_byte_array(m_env, v)); } + // JavaValueType box(List /*v*/) const { REALM_TERMINATE("'List' not implemented"); } + // JavaValueType box(Object /*v*/) const { REALM_TERMINATE("'Object' not implemented"); } + // JavaValueType box(Results /*v*/) const { REALM_TERMINATE("'Results' not implemented"); } + // JavaValueType box(StringData v) const { return reinterpret_cast(to_jstring(m_env, v)); } + // JavaValueType box(Timestamp v) const { return JavaClassGlobalDef::new_date(m_env, v); } + // JavaValueType box(bool v) const { return _impl::JavaClassGlobalDef::new_boolean(m_env, v); } + // JavaValueType box(double v) const { return _impl::JavaClassGlobalDef::new_double(m_env, v); } + // JavaValueType box(float v) const { return _impl::JavaClassGlobalDef::new_float(m_env, v); } + // JavaValueType box(int64_t v) const { return _impl::JavaClassGlobalDef::new_long(m_env, v); } + // JavaValueType box(util::Optional v) const { return v ? _impl::JavaClassGlobalDef::new_boolean(m_env, v.value()) : nullptr; } + // JavaValueType box(util::Optional v) const { return v ? _impl::JavaClassGlobalDef::new_double(m_env, v.value()) : nullptr; } + // JavaValueType box(util::Optional v) const { return v ? _impl::JavaClassGlobalDef::new_float(m_env, v.value()) : nullptr; } + // JavaValueType box(util::Optional v) const { return v ? _impl::JavaClassGlobalDef::new_long(m_env, v.value()) : nullptr; } + // JavaValueType box(RowExpr) const { REALM_TERMINATE("'RowExpr' not implemented"); } + + // Mixed type is only supported by the Cocoa binding to enable reading + // old Realm files that may have used them. All other bindings can ignore it. +// JavaValueType box(Mixed) const { REALM_TERMINATE("'Mixed' not supported"); } + + // Convert from the boxed type to core types. This needs to be implemented + // for all of the types which `box()` can take, plus `RowExpr` and optional + // versions of the numeric types, minus `List` and `Results`. + // + // `create` and `update` are only applicable to `unbox`. If + // `create` is false then when given something which is not a managed Realm + // object `unbox()` should simply return a detached row expr, while if it's + // true then `unbox()` should create a new object in the context's Realm + // using the provided value. If `update` is true then upsert semantics + // should be used for this. + template + T unbox(JavaValue const& /*v*/, bool /*create*/= false, bool /*update*/= false, bool /*diff_on_update*/= false, size_t /*current_row*/ = realm::npos) const { + throw std::logic_error("Missing template specialization"); // All types should have specialized templates + } + + bool is_null(JavaValue const& v) const noexcept { return !v.has_value(); } + JavaValue null_value() const noexcept { return {}; } + util::Optional no_value() const noexcept { return {}; } + + // Hooks which will be called before and after modifying a property from + // within Object::create(). These are not currently used. + void will_change(Object const&, Property const&) {} + void did_change() {} + + // Get a string representation of the given value for use in error messages. + // This method is currently only used when printing warnings about primary keys + // which means the output only need to be valid for the primary key types: + // StringData, int64_t and Optional + std::string print(JavaValue const& val) const { + return val.to_string(); + } + + // Cocoa allows supplying fewer values than there are properties when + // creating objects using an array of values. Other bindings should not + // mimick this behavior so just return false here. + bool allow_missing(JavaValue const&) const { return false; } + +private: + JNIEnv* m_env; + std::shared_ptr realm; + const ObjectSchema* object_schema = nullptr; + + inline void check_value_not_null(JavaValue const& v, const char* expected_type) const + { + if (!v.has_value()) { + throw RequiredFieldValueNotProvidedException(std::string(expected_type)); + } + } +}; + +template <> +inline bool JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + check_value_not_null(v, "Boolean"); + return v.get_boolean() == JNI_TRUE; +} + +template <> +inline int64_t JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + check_value_not_null(v, "Long"); + return static_cast(v.get_int()); +} + +template <> +inline double JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + check_value_not_null(v, "Double"); + return static_cast(v.get_double()); +} + +template <> +inline float JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + check_value_not_null(v, "Float"); + return static_cast(v.get_float()); +} + +template <> +inline StringData JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + if (!v.has_value()) { + return StringData(); + } + + return StringData(v.get_string()); +} + +template <> +inline BinaryData JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + if (!v.has_value()) { + return BinaryData(); + } else { + return v.get_binary().get(); + } +} + +template <> +inline Timestamp JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + return v.has_value() ? v.get_date() : Timestamp(); +} + +template <> +inline RowExpr JavaContext::unbox(JavaValue const& v, bool create, bool update, bool diff_on_update, size_t current_row) const +{ + if (v.get_type() == JavaValueType::Object) { + return *v.get_object(); + } else if (!create) { + return RowExpr(); + } + REALM_ASSERT(object_schema); + return Object::create(const_cast(*this), realm, *object_schema, v, update, diff_on_update, current_row).row(); +} + +template <> +inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + return v.has_value() ? util::make_optional(v.get_boolean() == JNI_TRUE) : util::none; +} + +template <> +inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + return v.has_value() ? util::make_optional(static_cast(v.get_int())) : util::none; +} + +template <> +inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + return v.has_value() ? util::make_optional(v.get_double()) : util::none; +} + +template <> +inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +{ + return v.has_value() ? util::make_optional(v.get_float()) : util::none; +} + +template <> +inline Mixed JavaContext::unbox(JavaValue const&, bool, bool, bool, size_t) const +{ + REALM_TERMINATE("'Mixed' not supported"); +} + +} + +#endif // REALM_JAVA_OBJECT_ACCESSOR_HPP diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 23371bb901..4aa3a5474d 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -30,14 +30,14 @@ #include "results.hpp" #include "list.hpp" #include "java_exception_def.hpp" +#include "java_object_accessor.hpp" +#include "object.hpp" #if REALM_ENABLE_SYNC #include "sync/partial_sync.hpp" #endif #include "jni_util/java_exception_thrower.hpp" - - using namespace std; using namespace realm; using namespace realm::util; @@ -125,6 +125,12 @@ void ConvertException(JNIEnv* env, const char* file, int line) } ThrowException(env, kind, e.what()); } + catch(realm::MissingPropertyValueException e) { + ThrowException(env, IllegalArgument, e.what()); + } + catch(realm::RequiredFieldValueNotProvidedException e) { + ThrowException(env, IllegalArgument, e.what()); + } #if REALM_ENABLE_SYNC catch (partial_sync::InvalidRealmStateException& e) { ThrowException(env, IllegalState, e.what()); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 42071658f0..6b9c2d2a3f 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -455,6 +455,10 @@ class JStringAccessor { return m_is_null || m_size == 0; } + bool is_null() { + return m_is_null; + } + operator realm::StringData() const { // To solve the link issue by directly using Table::max_string_size diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 67f6caf905..01f2095b3a 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -844,7 +844,12 @@ public void clear() { } } - // FIXME: This stuff doesn't appear to be used. It should either be explained or deleted. + /** + * CM: This is used when creating new proxy classes directly from the generated proxy code. + * It is a bit unclear exactly how it works, but it seems to be some work-around for some + * constructor shenanigans, i.e. values are set in this object just before the Proxy object + * is created (see `RealmDefaultModuleMediator.newInstance)`). + */ static final class ThreadLocalRealmObjectContext extends ThreadLocal { @Override protected RealmObjectContext initialValue() { diff --git a/realm/realm-library/src/main/java/io/realm/ImportFlag.java b/realm/realm-library/src/main/java/io/realm/ImportFlag.java new file mode 100644 index 0000000000..0b482262f8 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/ImportFlag.java @@ -0,0 +1,69 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm; + +import io.realm.annotations.Beta; + +/** + * This class describe how data is saved to Realm when saving whole objects. + * + * @see Realm#copyToRealm(RealmModel, ImportFlag...) + */ +@Beta +public enum ImportFlag { + + /** + * With this flag enabled, fields will not be written to the Realm file if they contain the same + * value as the value already present in the Realm. + *

              + * For local Realms this only has an impact on change listeners which will not report changes to + * those fields that was not written. + *

              + * For synchronized Realms this also impacts the server, which will see improved performance as + * there is less changes to upload and merge into the server Realm. + *

              + * It also impact how the server merges changes from different devices. Realm uses a + * last-write-wins approach when merging individual fields in an object, so if a field is not + * written it will be considered "older" than other fields modified. + *

              + * E.g: + *

                + *
              1. + * Server starts out with (Field A = 1, Field B = 1) + *
              2. + *
              3. + * Device 1 writes (Field A = 2, Field B = 2). + *
              4. + *
              5. + * Device 2 writes (Field A = 3, Field B = 1) but ignores (Field B = 1), because that is + * the value in the Realm file at this point. + *
              6. + *
              7. + * Device 1 uploads its changes to the server making the server (Field A = 2, Field B = 2). + * Then Device 2 uploads its changes. Due to last-write-wins, the server version now + * becomes (Field A = 3, Field B = 2). + *
              8. + *
              + * This is normally the desired behaviour as the final object is the merged result of the latest + * changes from both devices, however if all the fields in an object are considered an atomic + * unit, then this flag should not be set as it will ensure that all fields are set and thus have + * the same "age" when data are sent to the server. + * + * @see Docs on conflict resolution + */ + CHECK_SAME_VALUES_BEFORE_SET, + +} diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 8a4b8b2ff3..1269ccb364 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -53,6 +53,7 @@ import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.internal.ColumnIndices; import io.realm.internal.NativeObject; import io.realm.internal.ObjectServerFacade; @@ -913,7 +914,7 @@ private Scanner getFullStringScanner(InputStream in) { *

              * This method is only available for model classes with no @PrimaryKey annotation. * If you like to create an object that has a primary key, use {@link #createObject(Class, Object)} - * or {@link #copyToRealm(RealmModel)} instead. + * or {@link #copyToRealm(RealmModel, ImportFlag...)} instead. * * @param clazz the Class of the object to create. * @return the new object. @@ -1008,13 +1009,14 @@ E createObjectInternal( * set to their default value if not provided. * * @param object the {@link io.realm.RealmObject} to copy to the Realm. + * @param flags any flag that modifies the behaviour of inserting the data into the Realm. * @return a managed RealmObject with its properties backed by the Realm. * @throws java.lang.IllegalArgumentException if the object is {@code null} or it belongs to a Realm instance * in a different thread. */ - public E copyToRealm(E object) { + public E copyToRealm(E object, ImportFlag... flags) { checkNotNullObject(object); - return copyOrUpdate(object, false, new HashMap()); + return copyOrUpdate(object, false, new HashMap<>(), Util.toSet(flags)); } /** @@ -1026,15 +1028,16 @@ public E copyToRealm(E object) { * set to their default value if not provided. * * @param object {@link io.realm.RealmObject} to copy or update. + * @param flags any flag that modifies the behaviour of inserting the data into the Realm. * @return the new or updated RealmObject with all its properties backed by the Realm. * @throws java.lang.IllegalArgumentException if the object is {@code null} or doesn't have a Primary key defined * or it belongs to a Realm instance in a different thread. - * @see #copyToRealm(RealmModel) + * @see #copyToRealm(RealmModel, ImportFlag...) */ - public E copyToRealmOrUpdate(E object) { + public E copyToRealmOrUpdate(E object, ImportFlag... flags) { checkNotNullObject(object); checkHasPrimaryKey(object.getClass()); - return copyOrUpdate(object, true, new HashMap()); + return copyOrUpdate(object, true, new HashMap<>(), Util.toSet(flags)); } /** @@ -1046,11 +1049,12 @@ public E copyToRealmOrUpdate(E object) { * set to their default value if not provided. * * @param objects the RealmObjects to copy to the Realm. + * @param flags any flag that modifies the behaviour of inserting the data into the Realm. * @return a list of the the converted RealmObjects that all has their properties managed by the Realm. * @throws io.realm.exceptions.RealmException if any of the objects has already been added to Realm. * @throws java.lang.IllegalArgumentException if any of the elements in the input collection is {@code null}. */ - public List copyToRealm(Iterable objects) { + public List copyToRealm(Iterable objects, ImportFlag... flags) { //noinspection ConstantConditions if (objects == null) { return new ArrayList<>(); @@ -1064,14 +1068,14 @@ public List copyToRealm(Iterable objects) { Map cache = new HashMap<>(); for (E object : objects) { checkNotNullObject(object); - realmObjects.add(copyOrUpdate(object, false, cache)); + realmObjects.add(copyOrUpdate(object, false, cache, Util.toSet(flags))); } return realmObjects; } /** - * Inserts a list of an unmanaged RealmObjects. This is generally faster than {@link #copyToRealm(Iterable)} since it + * Inserts a list of an unmanaged RealmObjects. This is generally faster than {@link #copyToRealm(Iterable, ImportFlag...)} since it * doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original objects will not be persisted. *

              @@ -1085,13 +1089,13 @@ public List copyToRealm(Iterable objects) { *

            • Copying an object will copy all field values. Any unset field in the object and child objects will be set to their default value if not provided
            • *
            *

            - * If you want the managed {@link RealmObject} returned, use {@link #copyToRealm(Iterable)}, otherwise if + * If you want the managed {@link RealmObject} returned, use {@link #copyToRealm(Iterable, ImportFlag...)}, otherwise if * you have a large number of object this method is generally faster. * * @param objects RealmObjects to insert. * @throws IllegalStateException if the corresponding Realm is closed, called from an incorrect thread or not in a * transaction. - * @see #copyToRealm(Iterable) + * @see #copyToRealm(Iterable, ImportFlag...) */ public void insert(Collection objects) { checkIfValidAndInTransaction(); @@ -1106,7 +1110,7 @@ public void insert(Collection objects) { } /** - * Inserts an unmanaged RealmObject. This is generally faster than {@link #copyToRealm(RealmModel)} since it + * Inserts an unmanaged RealmObject. This is generally faster than {@link #copyToRealm(RealmModel, ImportFlag...)} since it * doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original object will not be persisted. *

            @@ -1120,7 +1124,7 @@ public void insert(Collection objects) { *

          • Copying an object will copy all field values. Any unset field in the object and child objects will be set to their default value if not provided
          • * *

            - * If you want the managed {@link RealmObject} returned, use {@link #copyToRealm(RealmModel)}, otherwise if + * If you want the managed {@link RealmObject} returned, use {@link #copyToRealm(RealmModel, ImportFlag...)}, otherwise if * you have a large number of object this method is generally faster. * * @param object RealmObjects to insert. @@ -1128,7 +1132,7 @@ public void insert(Collection objects) { * transaction. * @throws io.realm.exceptions.RealmPrimaryKeyConstraintException if two objects with the same primary key is * inserted or if a primary key value already exists in the Realm. - * @see #copyToRealm(RealmModel) + * @see #copyToRealm(RealmModel, ImportFlag...) */ public void insert(RealmModel object) { checkIfValidAndInTransaction(); @@ -1142,7 +1146,7 @@ public void insert(RealmModel object) { /** * Inserts or updates a list of unmanaged RealmObjects. This is generally faster than - * {@link #copyToRealmOrUpdate(Iterable)} since it doesn't return the inserted elements, and performs minimum + * {@link #copyToRealmOrUpdate(Iterable, ImportFlag...)} since it doesn't return the inserted elements, and performs minimum * allocations and checks. * After being inserted any changes to the original objects will not be persisted. *

            @@ -1156,7 +1160,7 @@ public void insert(RealmModel object) { *

          • Copying an object will copy all field values. Any unset field in the object and child objects will be set to their default value if not provided
          • * *

            - * If you want the managed {@link RealmObject} returned, use {@link #copyToRealm(Iterable)}, otherwise if + * If you want the managed {@link RealmObject} returned, use {@link #copyToRealm(Iterable, ImportFlag...)}, otherwise if * you have a large number of object this method is generally faster. * * @param objects RealmObjects to insert. @@ -1164,7 +1168,7 @@ public void insert(RealmModel object) { * transaction. * @throws io.realm.exceptions.RealmPrimaryKeyConstraintException if two objects with the same primary key is * inserted or if a primary key value already exists in the Realm. - * @see #copyToRealmOrUpdate(Iterable) + * @see #copyToRealmOrUpdate(Iterable, ImportFlag...) */ public void insertOrUpdate(Collection objects) { checkIfValidAndInTransaction(); @@ -1180,7 +1184,7 @@ public void insertOrUpdate(Collection objects) { /** * Inserts or updates an unmanaged RealmObject. This is generally faster than - * {@link #copyToRealmOrUpdate(RealmModel)} since it doesn't return the inserted elements, and performs minimum + * {@link #copyToRealmOrUpdate(RealmModel, ImportFlag...)} since it doesn't return the inserted elements, and performs minimum * allocations and checks. * After being inserted any changes to the original object will not be persisted. *

            @@ -1194,13 +1198,13 @@ public void insertOrUpdate(Collection objects) { *

          • Copying an object will copy all field values. Any unset field in the object and child objects will be set to their default value if not provided
          • * *

            - * If you want the managed {@link RealmObject} returned, use {@link #copyToRealm(RealmModel)}, otherwise if + * If you want the managed {@link RealmObject} returned, use {@link #copyToRealm(RealmModel, ImportFlag...)}, otherwise if * you have a large number of object this method is generally faster. * * @param object RealmObjects to insert. * @throws IllegalStateException if the corresponding Realm is closed, called from an incorrect thread or not in a * transaction. - * @see #copyToRealmOrUpdate(RealmModel) + * @see #copyToRealmOrUpdate(RealmModel, ImportFlag...) */ public void insertOrUpdate(RealmModel object) { checkIfValidAndInTransaction(); @@ -1221,11 +1225,12 @@ public void insertOrUpdate(RealmModel object) { * set to their default value if not provided. * * @param objects a list of objects to update or copy into Realm. + * @param flags any flag that modifies the behaviour of inserting the data into the Realm. * @return a list of all the new or updated RealmObjects. * @throws java.lang.IllegalArgumentException if RealmObject is {@code null} or doesn't have a Primary key defined. - * @see #copyToRealm(Iterable) + * @see #copyToRealm(Iterable, ImportFlag...) */ - public List copyToRealmOrUpdate(Iterable objects) { + public List copyToRealmOrUpdate(Iterable objects, ImportFlag... flags) { //noinspection ConstantConditions if (objects == null) { return new ArrayList<>(0); @@ -1238,9 +1243,10 @@ public List copyToRealmOrUpdate(Iterable objects) { realmObjects = new ArrayList<>(); } Map cache = new HashMap<>(); + Set importFlags = Util.toSet(flags); for (E object : objects) { checkNotNullObject(object); - realmObjects.add(copyOrUpdate(object, true, cache)); + realmObjects.add(copyOrUpdate(object, true, cache, importFlags)); } return realmObjects; @@ -1254,14 +1260,15 @@ public List copyToRealmOrUpdate(Iterable objects) { * that the copied objects might contain data that are no longer consistent with other managed Realm objects. *

            * *WARNING*: Any changes to copied objects can be merged back into Realm using - * {@link #copyToRealmOrUpdate(RealmModel)}, but all fields will be overridden, not just those that were changed. - * This includes references to other objects, and can potentially override changes made by other threads. + * {@link #copyToRealmOrUpdate(RealmModel, ImportFlag...)}, but all fields will be overridden, not just those that + * were changed. This includes references to other objects, and can potentially override changes made by other + * threads. This behaviour can be modified using {@link ImportFlag}s. * * @param realmObjects RealmObjects to copy. * @param type of object. * @return an in-memory detached copy of managed RealmObjects. * @throws IllegalArgumentException if the RealmObject is no longer accessible or it is a {@link DynamicRealmObject}. - * @see #copyToRealmOrUpdate(Iterable) + * @see #copyToRealmOrUpdate(Iterable, ImportFlag...) */ public List copyFromRealm(Iterable realmObjects) { return copyFromRealm(realmObjects, Integer.MAX_VALUE); @@ -1275,9 +1282,10 @@ public List copyFromRealm(Iterable realmObjects) { * that the copied objects might contain data that are no longer consistent with other managed Realm objects. *

            * *WARNING*: Any changes to copied objects can be merged back into Realm using - * {@link #copyToRealmOrUpdate(Iterable)}, but all fields will be overridden, not just those that were changed. + * {@link #copyToRealmOrUpdate(Iterable, ImportFlag...)}, but all fields will be overridden, not just those that were changed. * This includes references to other objects even though they might be {@code null} due to {@code maxDepth} being - * reached. This can also potentially override changes made by other threads. + * reached. This can also potentially override changes made by other threads. This behaviour can be modified using + * {@link ImportFlag}s. * * @param realmObjects RealmObjects to copy. * @param maxDepth limit of the deep copy. All references after this depth will be {@code null}. Starting depth is @@ -1286,7 +1294,7 @@ public List copyFromRealm(Iterable realmObjects) { * @return an in-memory detached copy of the RealmObjects. * @throws IllegalArgumentException if {@code maxDepth < 0}, the RealmObject is no longer accessible or it is a * {@link DynamicRealmObject}. - * @see #copyToRealmOrUpdate(Iterable) + * @see #copyToRealmOrUpdate(Iterable, ImportFlag...) */ public List copyFromRealm(Iterable realmObjects, int maxDepth) { checkMaxDepth(maxDepth); @@ -1318,14 +1326,15 @@ public List copyFromRealm(Iterable realmObjects, in * that the copied objects might contain data that are no longer consistent with other managed Realm objects. *

            * *WARNING*: Any changes to copied objects can be merged back into Realm using - * {@link #copyToRealmOrUpdate(RealmModel)}, but all fields will be overridden, not just those that were changed. + * {@link #copyToRealmOrUpdate(RealmModel, ImportFlag...)}, but all fields will be overridden, not just those that were changed. * This includes references to other objects, and can potentially override changes made by other threads. + * This behaviour can be modified using {@link ImportFlag}s. * * @param realmObject {@link RealmObject} to copy. * @param type of object. * @return an in-memory detached copy of the managed {@link RealmObject}. * @throws IllegalArgumentException if the RealmObject is no longer accessible or it is a {@link DynamicRealmObject}. - * @see #copyToRealmOrUpdate(RealmModel) + * @see #copyToRealmOrUpdate(RealmModel, ImportFlag...) */ public E copyFromRealm(E realmObject) { return copyFromRealm(realmObject, Integer.MAX_VALUE); @@ -1339,9 +1348,10 @@ public E copyFromRealm(E realmObject) { * that the copied objects might contain data that are no longer consistent with other managed Realm objects. *

            * *WARNING*: Any changes to copied objects can be merged back into Realm using - * {@link #copyToRealmOrUpdate(RealmModel)}, but all fields will be overridden, not just those that were changed. + * {@link #copyToRealmOrUpdate(RealmModel, ImportFlag...)}, but all fields will be overridden, not just those that were changed. * This includes references to other objects even though they might be {@code null} due to {@code maxDepth} being - * reached. This can also potentially override changes made by other threads. + * reached. This can also potentially override changes made by other threads. This behaviour can be modified using + * {@link ImportFlag}s. * * @param realmObject {@link RealmObject} to copy. * @param maxDepth limit of the deep copy. All references after this depth will be {@code null}. Starting depth is @@ -1350,7 +1360,7 @@ public E copyFromRealm(E realmObject) { * @return an in-memory detached copy of the managed {@link RealmObject}. * @throws IllegalArgumentException if {@code maxDepth < 0}, the RealmObject is no longer accessible or it is a * {@link DynamicRealmObject}. - * @see #copyToRealmOrUpdate(RealmModel) + * @see #copyToRealmOrUpdate(RealmModel, ImportFlag...) */ public E copyFromRealm(E realmObject, int maxDepth) { checkMaxDepth(maxDepth); @@ -1630,9 +1640,22 @@ public void delete(Class clazz) { @SuppressWarnings("unchecked") - private E copyOrUpdate(E object, boolean update, Map cache) { + private E copyOrUpdate(E object, boolean update, Map cache, Set flags) { checkIfValid(); - return configuration.getSchemaMediator().copyOrUpdate(this, object, update, cache); + if (!isInTransaction()) { + throw new IllegalStateException("`copyOrUpdate` can only be called inside a write transaction."); + } + try { + return configuration.getSchemaMediator().copyOrUpdate(this, object, update, cache, flags); + } catch (IllegalStateException e) { + // See https://github.com/realm/realm-java/issues/6262 + // For now we convert the OS exception using pattern matching on the error message. + if (e.getMessage().startsWith("Attempting to create an object of type")) { + throw new RealmPrimaryKeyConstraintException(e.getMessage()); + } else { + throw e; + } + } } private E createDetachedCopy(E object, int maxDepth, Map> cache) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 4642356f34..dbe8476d54 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -51,7 +51,7 @@ *

            * Unmanaged RealmLists can be created by the user and can contain both managed and unmanaged RealmObjects. This is * useful when dealing with JSON deserializers like GSON or other frameworks that inject values into a class. - * Unmanaged elements in this list can be added to a Realm using the {@link Realm#copyToRealm(Iterable)} method. + * Unmanaged elements in this list can be added to a Realm using the {@link Realm#copyToRealm(Iterable, ImportFlag...)} method. *

            * {@link RealmList} can contain more elements than {@code Integer.MAX_VALUE}. * In that case, you can access only first {@code Integer.MAX_VALUE} elements in it. @@ -80,7 +80,7 @@ public class RealmList extends AbstractList implements OrderedRealmCollect * This effectively makes the RealmList function as a {@link java.util.ArrayList} and it is not possible to query * the objects in this state. *

            - * Use {@link io.realm.Realm#copyToRealm(Iterable)} to properly persist its elements in Realm. + * Use {@link io.realm.Realm#copyToRealm(Iterable, ImportFlag...)} to properly persist its elements in Realm. */ public RealmList() { realm = null; @@ -93,7 +93,7 @@ public RealmList() { * A RealmList in unmanaged mode function as a {@link java.util.ArrayList} and it is not possible to query the * objects in this state. *

            - * Use {@link io.realm.Realm#copyToRealm(Iterable)} to properly persist all unmanaged elements in Realm. + * Use {@link io.realm.Realm#copyToRealm(Iterable, ImportFlag...)} to properly persist all unmanaged elements in Realm. * * @param objects initial objects in the list. */ @@ -165,10 +165,10 @@ private boolean isAttached() { *

              *
            1. Unmanaged RealmLists: It is possible to add both managed and unmanaged objects. If adding managed * objects to an unmanaged RealmList they will not be copied to the Realm again if using - * {@link Realm#copyToRealm(RealmModel)} afterwards.
            2. + * {@link Realm#copyToRealm(RealmModel, ImportFlag...)} afterwards. *
            3. Managed RealmLists: It is possible to add unmanaged objects to a RealmList that is already managed. In - * that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} - * or {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key.
            4. + * that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel, ImportFlag...)} + * or {@link Realm#copyToRealmOrUpdate(RealmModel, ImportFlag...)} if it has a primary key. *
            * * @param location the index at which to insert. @@ -193,10 +193,10 @@ public void add(int location, @Nullable E element) { *
              *
            1. Unmanaged RealmLists: It is possible to add both managed and unmanaged objects. If adding managed * objects to an unmanaged RealmList they will not be copied to the Realm again if using - * {@link Realm#copyToRealm(RealmModel)} afterwards.
            2. + * {@link Realm#copyToRealm(RealmModel, ImportFlag...)} afterwards. *
            3. Managed RealmLists: It is possible to add unmanaged objects to a RealmList that is already managed. In - * that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} - * or {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key.
            4. + * that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel, ImportFlag...)} + * or {@link Realm#copyToRealmOrUpdate(RealmModel, ImportFlag...)} if it has a primary key. *
            * * @param object the object to add. @@ -220,10 +220,10 @@ public boolean add(@Nullable E object) { *
              *
            1. Unmanaged RealmLists: It is possible to add both managed and unmanaged objects. If adding managed * objects to an unmanaged RealmList they will not be copied to the Realm again if using - * {@link Realm#copyToRealm(RealmModel)} afterwards.
            2. + * {@link Realm#copyToRealm(RealmModel, ImportFlag...)} afterwards. *
            3. Managed RealmLists: It is possible to add unmanaged objects to a RealmList that is already managed. - * In that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel)} or - * {@link Realm#copyToRealmOrUpdate(RealmModel)} if it has a primary key.
            4. + * In that case the object will transparently be copied to Realm using {@link Realm#copyToRealm(RealmModel, ImportFlag...)} or + * {@link Realm#copyToRealmOrUpdate(RealmModel, ImportFlag...)} if it has a primary key. *
            * * @param location the index at which to put the specified object. @@ -1220,7 +1220,7 @@ public void set(@Nullable E e) { /** * Adding a new object to the RealmList. If the object is not already manage by Realm it will be transparently - * copied using {@link Realm#copyToRealmOrUpdate(RealmModel)} + * copied using {@link Realm#copyToRealmOrUpdate(RealmModel, ImportFlag...)} * * @see #add(Object) */ diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 06b79e8dfd..6058bca55e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -65,7 +65,7 @@ * A RealmObject cannot be passed between different threads. * * @see Realm#createObject(Class) - * @see Realm#copyToRealm(RealmModel) + * @see Realm#copyToRealm(RealmModel, ImportFlag...) */ @RealmClass @@ -259,7 +259,7 @@ public static boolean isLoaded(E object) { *

            *

            * It is possible to create a managed object from an unmanaged object by using - * {@link Realm#copyToRealm(RealmModel)}. An unmanaged object can be created from a managed object by using + * {@link Realm#copyToRealm(RealmModel, ImportFlag...)}. An unmanaged object can be created from a managed object by using * {@link Realm#copyFromRealm(RealmModel)}. * * @return {@code true} if the object is managed, {@code false} if it is unmanaged. @@ -283,7 +283,7 @@ public boolean isManaged() { *

            *

            * It is possible to create a managed object from an unmanaged object by using - * {@link Realm#copyToRealm(RealmModel)}. An unmanaged object can be created from a managed object by using + * {@link Realm#copyToRealm(RealmModel, ImportFlag...)}. An unmanaged object can be created from a managed object by using * {@link Realm#copyFromRealm(RealmModel)}. * * @return {@code true} if the object is managed, {@code false} if it is unmanaged. diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java index cac92e21df..9f9e6f3b2a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java @@ -185,6 +185,14 @@ public Property getProperty(String propertyName) { return propertyPtr == 0 ? null : new Property(nativeGetPrimaryKeyProperty(nativePtr)); } + /** + * Returns the maximum table index used by core for this schema. + * If this Object has no properties -1 is returned. + */ + public long getMaxColumnIndex() { + return nativeGetMaxColumnIndex(nativePtr); + } + @Override public long getNativePtr() { return nativePtr; @@ -209,4 +217,7 @@ public long getNativeFinalizerPtr() { // Return nullptr if it doesn't have a primary key. private static native long nativeGetPrimaryKeyProperty(long nativePtr); + + private static native long nativeGetMaxColumnIndex(long nativePtr); + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index e9be90c80e..d05d5cfa60 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -141,7 +141,7 @@ public interface SchemaChangedCallback { private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final long nativePtr; private final OsRealmConfig osRealmConfig; - final NativeContext context; + public final NativeContext context; private final OsSchemaInfo schemaInfo; private static volatile File temporaryDirectory; // JNI will only hold a weak global ref to this. diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index 58db9cfe46..fc28b88e67 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Set; +import io.realm.ImportFlag; import io.realm.Realm; import io.realm.RealmModel; import io.realm.RealmObject; @@ -114,12 +115,13 @@ public abstract E newInstance(Class clazz, * @param update {@code true} if object has a primary key and should try to update already existing data, * {@code false} otherwise. * @param cache the cache for mapping between unmanaged objects and their {@link RealmObjectProxy} representation. + * @param flags any special flags controlling the behaviour of the import. * @return the managed Realm object. */ - public abstract E copyOrUpdate(Realm realm, E object, boolean update, Map cache); + public abstract E copyOrUpdate(Realm realm, E object, boolean update, Map cache, Set flags); /** - * Inserts an unmanaged RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} + * Inserts an unmanaged RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map, Set)} * since it doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original object will not be persisted. * @@ -130,7 +132,7 @@ public abstract E newInstance(Class clazz, public abstract void insert(Realm realm, RealmModel object, Map cache); /** - * Inserts or updates a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} + * Inserts or updates a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map, Set)} * since it doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original object will not be persisted. * @@ -141,7 +143,7 @@ public abstract E newInstance(Class clazz, public abstract void insertOrUpdate(Realm realm, RealmModel object, Map cache); /** - * Inserts or updates a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} + * Inserts or updates a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map, Set)} * since it doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original objects will not be persisted. * @@ -151,7 +153,7 @@ public abstract E newInstance(Class clazz, public abstract void insertOrUpdate(Realm realm, Collection objects); /** - * Inserts a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map)} since + * Inserts a RealmObject. This is generally faster than {@link #copyOrUpdate(Realm, RealmModel, boolean, Map, Set)} since * it doesn't return the inserted elements, and performs minimum allocations and checks. * After being inserted any changes to the original objects will not be persisted. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 063e580c75..499d4d201e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -354,7 +354,7 @@ public static void throwDuplicatePrimaryKeyException(Object value) { // Getters // - OsSharedRealm getSharedRealm() { + public OsSharedRealm getSharedRealm() { return sharedRealm; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index c7e035b89c..c35ef9a3bc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -39,7 +39,7 @@ public class UncheckedRow implements NativeObject, Row { private final Table parent; private final long nativePtr; - UncheckedRow(NativeContext context, Table parent, long nativePtr) { + public UncheckedRow(NativeContext context, Table parent, long nativePtr) { this.context = context; this.parent = parent; this.nativePtr = nativePtr; diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 2e99d61680..e9ef4d800a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -22,12 +22,17 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; +import io.realm.ImportFlag; import io.realm.RealmConfiguration; import io.realm.RealmModel; import io.realm.RealmObject; @@ -147,4 +152,23 @@ public static boolean deleteRealm(String canonicalPath, File realmFolder, String } return realmDeleted; } + + /** + * Converts a var arg argument list to a set ignoring any duplicates and null values. + */ + public static Set toSet(T... items) { + //noinspection ConstantConditions + if (items == null) { + return Collections.emptySet(); + } else { + Set set = new LinkedHashSet<>(); + for (int i = 0; i < items.length; i++) { + T item = items[i]; + if (item != null) { + set.add(item); + } + } + return set; + } + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index 88fa860c3e..b95207f4d6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -29,6 +29,7 @@ import java.util.Map; import java.util.Set; +import io.realm.ImportFlag; import io.realm.Realm; import io.realm.RealmModel; import io.realm.exceptions.RealmException; @@ -116,9 +117,9 @@ public Set> getModelClasses() { } @Override - public E copyOrUpdate(Realm realm, E object, boolean update, Map cache) { + public E copyOrUpdate(Realm realm, E object, boolean update, Map cache, Set flags) { RealmProxyMediator mediator = getMediator(Util.getOriginalModelClass(object.getClass())); - return mediator.copyOrUpdate(realm, object, update, cache); + return mediator.copyOrUpdate(realm, object, update, cache, flags); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index b3ec7decb9..ceb23d7c05 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Set; +import io.realm.ImportFlag; import io.realm.Realm; import io.realm.RealmModel; import io.realm.internal.ColumnInfo; @@ -114,9 +115,9 @@ public Set> getModelClasses() { } @Override - public E copyOrUpdate(Realm realm, E object, boolean update, Map cache) { + public E copyOrUpdate(Realm realm, E object, boolean update, Map cache, Set flags) { checkSchemaHasClass(Util.getOriginalModelClass(object.getClass())); - return originalMediator.copyOrUpdate(realm, object, update, cache); + return originalMediator.copyOrUpdate(realm, object, update, cache, flags); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java new file mode 100644 index 0000000000..a5ed0f3c82 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java @@ -0,0 +1,415 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ +package io.realm.internal.objectstore; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import io.realm.ImportFlag; +import io.realm.MutableRealmInteger; +import io.realm.RealmList; +import io.realm.RealmModel; +import io.realm.internal.NativeContext; +import io.realm.internal.OsSharedRealm; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Table; +import io.realm.internal.UncheckedRow; + +/** + * This class is a wrapper around building up object data for calling `Object::create()` + *

            + * Fill the object data by calling the various `addX()` methods, then create a new Object or update + * an existing one by calling {@link #createNewObject()} or {@link #updateExistingObject()}. + *

            + * This class assumes it is only being used from within a write transaction. Using it outside one + * will result in undefined behaviour. + *

            + * The native + * resources are created in the constructor of this class and destroyed when calling either of the + * above two methods. + *

            + *

            Design thoughts

            + *

            + * Ideally we would have sent all properties across in one JNI call, but the only way to do that would + * have been using two `Object[]` arrays which would have resulted in a ton of JNI calls back + * again for resolving the primitive values of boxed types (since JNI do not know about boxed + * primitives). + *

            + * The upside of making a JNI call for each property is that we do minimal allocations on the Java + * side. Also each method call is fairly lightweight as no checks are performed compared to using + * Proxy setters and {@link io.realm.internal.UncheckedRow}. The only downside is the current need for + * sending the key as well. Hopefully we can change that to schema indices at some point. + *

            + * There is quite a few variants we can attempt to optimize this, but at this point we lack data + * that can guide any architectural design and the only way to really find out is to build out each + * solution and benchmark it. + */ +public class OsObjectBuilder { + + private final Table table; + private final long sharedRealmPtr; + private final long builderPtr; + private final long tablePtr; + private final NativeContext context; + + private static ItemCallback objectItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, RealmModel item) { + RealmObjectProxy proxyItem = (RealmObjectProxy) item; + nativeAddIntegerListItem(listPtr, ((UncheckedRow) proxyItem.realmGet$proxyState().getRow$realm()).getNativePtr()); + } + }; + + private static ItemCallback stringItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, String item) { + nativeAddStringListItem(listPtr, item); + } + }; + + private static ItemCallback byteItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, Byte item) { + nativeAddIntegerListItem(listPtr, item.longValue()); + } + }; + + private static ItemCallback shortItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, Short item) { + nativeAddIntegerListItem(listPtr, item); + } + }; + + private static ItemCallback integerItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, Integer item) { + nativeAddIntegerListItem(listPtr, item); + } + }; + + private static ItemCallback longItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, Long item) { + nativeAddIntegerListItem(listPtr, item); + } + }; + + private static ItemCallback booleanItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, Boolean item) { + nativeAddBooleanListItem(listPtr, item); + } + }; + + private static ItemCallback floatItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, Float item) { + nativeAddFloatListItem(listPtr, item); + } + }; + + private static ItemCallback doubleItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, Double item) { + nativeAddDoubleListItem(listPtr, item); + } + }; + + private static ItemCallback dateItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, Date item) { + nativeAddDateListItem(listPtr, item.getTime()); + } + }; + + private static ItemCallback byteArrayItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, byte[] item) { + nativeAddByteArrayListItem(listPtr, item); + } + }; + + private static ItemCallback mutableRealmIntegerItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, MutableRealmInteger item) { + Long value = item.get(); + if (value == null) { + nativeAddNullListItem(listPtr); + } else { + nativeAddIntegerListItem(listPtr, value); + } + } + }; + + // If true, fields will not be updated if the same value would be written to it. + private final boolean ignoreFieldsWithSameValue; + + public OsObjectBuilder(Table table, long maxColumnIndex, Set flags) { + OsSharedRealm sharedRealm = table.getSharedRealm(); + this.sharedRealmPtr = sharedRealm.getNativePtr(); + this.table = table; + this.tablePtr = table.getNativePtr(); + this.builderPtr = nativeCreateBuilder(maxColumnIndex + 1); + this.context = sharedRealm.context; + this.ignoreFieldsWithSameValue = flags.contains(ImportFlag.CHECK_SAME_VALUES_BEFORE_SET); + } + + public void addInteger(long columnIndex, Byte val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddInteger(builderPtr, columnIndex, val); + } + } + + public void addInteger(long columnIndex, Short val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddInteger(builderPtr, columnIndex, val); + } + } + + public void addInteger(long columnIndex, Integer val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddInteger(builderPtr, columnIndex, val); + } + } + + public void addInteger(long columnIndex, Long val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddInteger(builderPtr, columnIndex, val); + } + } + + public void addMutableRealmInteger(long columnIndex, MutableRealmInteger val) { + if (val == null || val.get() == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddInteger(builderPtr, columnIndex, val.get()); + } + } + + public void addString(long columnIndex, String val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddString(builderPtr, columnIndex, val); + } + } + + public void addFloat(long columnIndex, Float val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddFloat(builderPtr, columnIndex, val); + } + } + + public void addDouble(long columnIndex, Double val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddDouble(builderPtr, columnIndex, val); + } + } + + public void addBoolean(long columnIndex, Boolean val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddBoolean(builderPtr, columnIndex, val); + } + } + + public void addDate(long columnIndex, Date val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddDate(builderPtr, columnIndex, val.getTime()); + } + } + + public void addByteArray(long columnIndex, byte[] val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + nativeAddByteArray(builderPtr, columnIndex, val); + } + } + + public void addNull(long columnIndex) { + nativeAddNull(builderPtr, columnIndex); + } + + public void addObject(long columnIndex, RealmModel val) { + if (val == null) { + nativeAddNull(builderPtr, columnIndex); + } else { + RealmObjectProxy proxy = (RealmObjectProxy) val; + UncheckedRow row = (UncheckedRow) proxy.realmGet$proxyState().getRow$realm(); + nativeAddObject(builderPtr, columnIndex, row.getNativePtr()); + } + } + + private void addListItem(long builderPtr, long columnIndex, List list, ItemCallback itemCallback) { + if (list != null) { + long listPtr = nativeStartList(list.size()); + for (int i = 0; i < list.size(); i++) { + T item = list.get(i); + if (item == null) { + nativeAddNullListItem(listPtr); + } else { + itemCallback.handleItem(listPtr, item); + } + } + nativeStopList(builderPtr, columnIndex, listPtr); + } else { + addEmptyList(columnIndex); + } + } + + public void addObjectList(long columnIndex, RealmList list) { + // Null objects references are not allowed. So we can optimize the JNI boundary by + // sending all object references in one long[] array. + if (list != null) { + long[] rowPointers = new long[list.size()]; + for (int i = 0; i < list.size(); i++) { + RealmObjectProxy item = (RealmObjectProxy) list.get(i); + if (item == null) { + throw new IllegalArgumentException("Null values are not allowed in RealmLists containing Realm models"); + } else { + rowPointers[i] = ((UncheckedRow) item.realmGet$proxyState().getRow$realm()).getNativePtr(); + } + } + nativeAddObjectList(builderPtr, columnIndex, rowPointers); + } else { + nativeAddObjectList(builderPtr, columnIndex, new long[0]); + } + } + + public void addStringList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, stringItemCallback); + } + + public void addByteList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, byteItemCallback); + } + + public void addShortList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, shortItemCallback); + } + + public void addIntegerList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, integerItemCallback); + } + + public void addLongList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, longItemCallback); + } + + public void addBooleanList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, booleanItemCallback); + } + + public void addFloatList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, floatItemCallback); + } + + public void addDoubleList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, doubleItemCallback); + } + + public void addDateList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, dateItemCallback); + } + + public void addByteArrayList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, byteArrayItemCallback); + } + + public void addMutableRealmIntegerList(long columnIndex, RealmList list) { + addListItem(builderPtr, columnIndex, list, mutableRealmIntegerItemCallback); + } + + private void addEmptyList(long columnIndex) { + long listPtr = nativeStartList(0); + nativeStopList(builderPtr, columnIndex, listPtr); + } + + public void updateExistingObject() { + try { + nativeCreateOrUpdate(sharedRealmPtr, tablePtr, builderPtr, true, ignoreFieldsWithSameValue); + } finally { + nativeDestroyBuilder(builderPtr); + } + } + + public UncheckedRow createNewObject() { + UncheckedRow row; + try { + long rowPtr = nativeCreateOrUpdate(sharedRealmPtr, tablePtr, builderPtr, false, false); + row = new UncheckedRow(context, table, rowPtr); + } finally { + nativeDestroyBuilder(builderPtr); + } + return row; + } + + private interface ItemCallback { + void handleItem(long listPtr, T item); + } + + private static native long nativeCreateBuilder(long size); + private static native void nativeDestroyBuilder(long builderPtr); + private static native long nativeCreateOrUpdate(long sharedRealmPtr, + long tablePtr, + long builderPtr, + boolean updateExistingObject, + boolean ignoreFieldsWithSameValue); + + // Add simple properties + private static native void nativeAddNull(long builderPtr, long columnIndex); + private static native void nativeAddInteger(long builderPtr, long columnIndex, long val); + private static native void nativeAddString(long builderPtr, long columnIndex, String val); + private static native void nativeAddFloat(long builderPtr, long columnIndex, float val); + private static native void nativeAddDouble(long builderPtr, long columnIndex, double val); + private static native void nativeAddBoolean(long builderPtr, long columnIndex, boolean val); + private static native void nativeAddByteArray(long builderPtr, long columnIndex, byte[] val); + private static native void nativeAddDate(long builderPtr, long columnIndex, long val); + private static native void nativeAddObject(long builderPtr, long columnIndex, long rowPtr); + + // Methods for adding lists + // Lists sent across JNI one element at a time + private static native long nativeStartList(long size); + private static native void nativeStopList(long builderPtr, long columnIndex, long listPtr); + private static native void nativeAddNullListItem(long listPtr); + private static native void nativeAddIntegerListItem(long listPtr, long value); + private static native void nativeAddStringListItem(long listPtr, String val); + private static native void nativeAddFloatListItem(long listPtr, float val); + private static native void nativeAddDoubleListItem(long listPtr, double val); + private static native void nativeAddBooleanListItem(long listPtr, boolean val); + private static native void nativeAddByteArrayListItem(long listPtr, byte[] val); + private static native void nativeAddDateListItem(long listPtr, long val); + private static native void nativeAddObjectListItem(long listPtr, long rowPtr); + private static native void nativeAddObjectList(long builderPtr, long columnIndex, long[] rowPtrs); +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 4c7baeece9..e6b3ba956a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -185,7 +185,7 @@ public void onError(SyncSession session, ObjectServerError error) { realm.commitTransaction(); // STEP 2: make sure the changes gets to the server - SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. + SyncManager.getSession(configWithEncryption).uploadAllLocalChanges(); realm.close(); // STEP 3: prepare a synced Realm for client B (admin user) @@ -216,14 +216,13 @@ public void onError(SyncSession session, ObjectServerError error) { adminRealm.beginTransaction(); adminRealm.createObject(StringOnly.class).setChars("Hi Bob"); adminRealm.commitTransaction(); - - SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); + SyncManager.getSession(adminConfigWithEncryption).uploadAllLocalChanges(); adminRealm.close(); // STEP 4: client A can see changes from client B (although they're using different encryption keys) realm = Realm.getInstance(configWithEncryption); SyncManager.getSession(configWithEncryption).downloadAllServerChanges();// force download latest commits from ROS - realm.refresh();//FIXME not calling refresh will still point to the previous version of the Realm without the latest admin commit "Hi Bob" + realm.refresh(); assertEquals(2, realm.where(StringOnly.class).count()); adminRealm = Realm.getInstance(adminConfigWithEncryption); diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsLong.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsLong.java index 19fd734383..c20e60ed5e 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsLong.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsLong.java @@ -23,6 +23,12 @@ public class PrimaryKeyAsLong extends RealmObject { public static final String CLASS_NAME = "PrimaryKeyAsLong"; public static final String FIELD_ID = "id"; + public static final String FIELD_NAME = "name"; + + public PrimaryKeyAsLong() { } + public PrimaryKeyAsLong(long id) { + this.id = id; + } @PrimaryKey private long id; From 03e74870750dea24fa05a2dc8401eee196252898 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 3 Nov 2018 18:23:50 +0100 Subject: [PATCH 1331/2110] Add support for latest version of Sync (#6266) --- CHANGELOG.md | 8 ++++---- Jenkinsfile | 2 +- dependencies.list | 6 +++--- examples/unitTestExample/build.gradle | 6 +++--- realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp | 3 ++- realm/realm-library/src/main/cpp/object-store | 2 +- .../objectserver/EncryptedSynchronizedRealmTests.java | 8 ++++---- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb6e30fcbc..bde269a797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This release also contains all changes in 5.8.0-BETA1 and 5.8.0-BETA2. * [ObjectServer] Added `Realm.getSubscriptions()`, `Realm.getSubscriptions(String pattern)` and `Realm.getSubscription` to make it easier to find existing subscriptions. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). * [ObjectServer] Added `RealmQuery.subscribe()` and `RealmQuery.subscribe(String name)` to subscribe immediately inside a transaction. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). * [ObjectServer] Added support for subscribing directly inside `SyncConfiguration.initialData()`. This can be coupled with `SyncConfiguration.waitForInitialRemoteData()` in order to block a Realm from opening until the initial subscriptions are ready and have downloaded data. This API are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). +* [ObjectServer] Improved performance when merging changes from the server. * Added support for `ImportFlag`s to `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate()`. This makes it possible to choose a mode so only fields that actually changed are written to disk. This improves notifications and Object Server performance. [#6224](https://github.com/realm/realm-java/pull/6224). ### Fixed @@ -35,7 +36,9 @@ This release also contains all changes in 5.8.0-BETA1 and 5.8.0-BETA2. * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal -* Updated to Object Store commit: 1f91c82eb34cf4eaa2900794a9268390876f19f1 +* Updated to Object Store commit: f0dfe6c03be49194bc40777901059eaf55e7bff6 +* Updated Realm Sync to 3.13.1 +* Updated Realm Core to 5.12.0 ## 5.8.0-BETA2 (2018-10-19) @@ -80,9 +83,6 @@ This release also contains all changes in 5.8.0-BETA1 and 5.8.0-BETA2. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. -### Internal -* None - ## 5.7.1 (2018-10-22) diff --git a/Jenkinsfile b/Jenkinsfile index 444e84db5f..e65e707d01 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -62,7 +62,7 @@ try { stage('JVM tests') { try { withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter}" + sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" } } finally { storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' diff --git a/dependencies.list b/dependencies.list index d3e23b5bd8..fe31846029 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,11 +1,11 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.10.1 -REALM_SYNC_SHA256=df8fb8506a318faf83e027a442e5ad0a38f458ec44567146e64d5a1c60b424ae +REALM_SYNC_VERSION=3.13.1 +REALM_SYNC_SHA256=4d21d7eb3cff254261da835dd88ab8dd8c2c376d7e81e111206f27225a55cf07 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.11.1 +REALM_OBJECT_SERVER_VERSION=3.12.4 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.1.4 diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index 059d77e006..9b6bb2c11d 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -56,9 +56,9 @@ dependencies { testImplementation "org.powermock:powermock-classloading-xstream:1.6.5" - androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test:runner:1.0.1' // Set this dependency to use JUnit 4 rules - androidTestImplementation 'com.android.support.test:rules:1.0.2' + androidTestImplementation 'com.android.support.test:rules:1.0.1' // Set this dependency to build and run Espresso tests - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2' } diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index d34e026d87..bab28afc69 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -97,7 +97,8 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNI TR_ENTER() try { JStringAccessor base_file_path(env, sync_base_dir); // throws - SyncManager::shared().configure_file_system(base_file_path, SyncManager::MetadataMode::NoEncryption); + std::string user_agent_info(""); // TODO Add support for this + SyncManager::shared().configure(base_file_path, SyncManager::MetadataMode::NoEncryption, user_agent_info); static AndroidClientListener client_thread_listener(env); // Register Sync Client thread start/stop callback diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 1f91c82eb3..f0dfe6c03b 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 1f91c82eb34cf4eaa2900794a9268390876f19f1 +Subproject commit f0dfe6c03be49194bc40777901059eaf55e7bff6 diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index e6b3ba956a..0469153779 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -33,12 +33,12 @@ public class EncryptedSynchronizedRealmTests extends StandardIntegrationTest { @Rule - public Timeout globalTimeout = Timeout.seconds(10); + public Timeout globalTimeout = Timeout.seconds(30); // Make sure the encryption is local, i.e after deleting a synced Realm // re-open it again with no (or different) key, should be possible. @Test - public void setEncryptionKey_canReOpenRealmWithoutKey() { + public void setEncryptionKey_canReOpenRealmWithoutKey() throws InterruptedException { // STEP 1: open a synced Realm using a local encryption key String username = UUID.randomUUID().toString(); @@ -68,7 +68,7 @@ public void onError(SyncSession session, ObjectServerError error) { realm.commitTransaction(); // STEP 2: make sure the changes gets to the server - SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); // FIXME: Replace with Sync Progress Notifications once available. + SyncManager.getSession(configWithEncryption).uploadAllLocalChanges(); realm.close(); user.logOut(); @@ -222,7 +222,7 @@ public void onError(SyncSession session, ObjectServerError error) { // STEP 4: client A can see changes from client B (although they're using different encryption keys) realm = Realm.getInstance(configWithEncryption); SyncManager.getSession(configWithEncryption).downloadAllServerChanges();// force download latest commits from ROS - realm.refresh(); + realm.refresh(); // Not calling refresh will still point to the previous version of the Realm without the latest admin commit "Hi Bob" assertEquals(2, realm.where(StringOnly.class).count()); adminRealm = Realm.getInstance(adminConfigWithEncryption); From 81ea78bfd242785e81375b0733d51886053f8618 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 3 Nov 2018 18:31:50 +0100 Subject: [PATCH 1332/2110] Add support for setting a custom User-Agent (#6270) --- .../examples/objectserver/MyApplication.java | 2 +- .../asm/visitors/AnnotatedCodeStripVisitor.kt | 4 +- .../asm/visitors/AnnotationVisitor.kt | 4 +- .../testclasses/SimpleTestMethods.java | 4 ++ .../io/realm/buildtransformer/VisitorTests.kt | 16 +++--- .../src/main/cpp/io_realm_SyncManager.cpp | 6 +-- .../src/main/java/io/realm/BaseRealm.java | 2 + .../src/main/java/io/realm/Realm.java | 53 ++++++++++++++++++- .../io/realm/internal/ObjectServerFacade.java | 4 +- .../java/io/realm/ObjectServer.java | 34 ++++++++++-- .../java/io/realm/SyncManager.java | 2 +- .../internal/SyncObjectServerFacade.java | 6 +-- 12 files changed, 113 insertions(+), 24 deletions(-) diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java index e4511eeb78..417fd9d9f5 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java @@ -27,7 +27,7 @@ public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); - Realm.init(this); + Realm.init(this, "ObjectServerExample/" + BuildConfig.VERSION_NAME); // Enable full log output when debugging if (BuildConfig.DEBUG) { diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt index a33f38acb2..0892d82cba 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt @@ -68,10 +68,10 @@ class AnnotatedCodeStripVisitor(private val annotationDescriptor: String, } override fun visitMethod(access: Int, name: ByteCodeMethodName?, descriptor: String?, signature: String?, exceptions: Array?): MethodVisitor? { - return if (!markedMethodsInClass.contains(name)) { + return if (!markedMethodsInClass.contains(name + descriptor)) { super.visitMethod(access, name, descriptor, signature, exceptions) } else { - logger.debug("Removing method: $name") + logger.debug("Removing method: $name $descriptor") null } } diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt index b572090148..a34825b972 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt @@ -53,10 +53,12 @@ class AnnotationVisitor(private val annotationDescriptor: String) : ClassVisitor override fun visitMethod(access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array?): MethodVisitor { val parentVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) + val methodDescriptor: String = descriptor!! + val methodName: String = name!!; return object: MethodVisitor(api, parentVisitor) { override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { if (descriptor == annotationDescriptor) { - annotatedMethodsInClass.add(name!!) + annotatedMethodsInClass.add(methodName + methodDescriptor) // Use name + return type + parameters to uniquely identify method } return super.visitAnnotation(descriptor, visible) } diff --git a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestMethods.java b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestMethods.java index 45c3377faf..308aed3335 100644 --- a/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestMethods.java +++ b/library-build-transformer/src/test/java/io/realm/buildtransformer/testclasses/SimpleTestMethods.java @@ -29,6 +29,10 @@ public String foo1(String input) { return "foo1"; } + public String foo1(String input, String donRemoveThis) { + return "foo1"; // Only methods matching the exact signature should be removed + } + public String bar() { return "bar"; } diff --git a/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt b/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt index e0f8d25278..fffc249779 100644 --- a/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt +++ b/library-build-transformer/src/test/kotlin/io/realm/buildtransformer/VisitorTests.kt @@ -24,6 +24,7 @@ import org.junit.Assert.* import org.junit.Before import org.junit.Test import java.io.File +import java.lang.reflect.Method import kotlin.reflect.KClass class VisitorTests { @@ -49,8 +50,10 @@ class VisitorTests { fun removeMethods() { val c: Class = modifyClass(SimpleTestMethods::class) assetDefaultConstructorExists(c) - assertMethodExists("bar", c) - assertMethodRemoved("foo", c) + assertMethodExists(c, "bar", emptyArray()); + assertMethodExists(c, "foo1", arrayOf(String::class.java, String::class.java)) + assertMethodRemoved(c, "foo", emptyArray()); + assertMethodRemoved(c, "foo1", arrayOf(String::class.java)) } @Test @@ -108,16 +111,17 @@ class VisitorTests { clazz.getField(fieldName) } - private fun assertMethodRemoved(methodName: String, clazz: Class<*>) { + private fun assertMethodRemoved(clazz: Class<*>, methodName: String, parameterTypes: Array>) { try { - clazz.getMethod(methodName) + clazz.getMethod(methodName, *parameterTypes); fail("Method $methodName has not been removed"); } catch (e: NoSuchMethodException) { } } - private fun assertMethodExists(methodName: String, clazz: Class<*>) { - clazz.getMethod(methodName) // Will throw exception if it doesn't + private fun assertMethodExists(clazz: Class<*>, methodName: String, parameterTypes: Array>) { + val method: Method = clazz.getMethod(methodName, *parameterTypes) // Will throw exception if it doesn't exists + assertNotNull(method) } private fun modifyClass(clazz: KClass): Class { diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index bab28afc69..ea9aedeba9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -92,12 +92,12 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNIEnv* env, jclass, jstring sync_base_dir) +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNIEnv* env, jclass, jstring j_sync_base_dir, jstring j_user_agent_info) { TR_ENTER() try { - JStringAccessor base_file_path(env, sync_base_dir); // throws - std::string user_agent_info(""); // TODO Add support for this + JStringAccessor base_file_path(env, j_sync_base_dir); // throws + JStringAccessor user_agent_info(env, j_user_agent_info); // throws SyncManager::shared().configure(base_file_path, SyncManager::MetadataMode::NoEncryption, user_agent_info); static AndroidClientListener client_thread_listener(env); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 01f2095b3a..1c94cc6413 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -525,6 +525,8 @@ public RealmPrivileges getPrivileges() { * @return the privileges granted the current user for the object. * @throws IllegalArgumentException if the object is either null, unmanaged or not part of this Realm. */ + @Beta + @ObjectServer public ObjectPrivileges getPrivileges(RealmModel object) { checkIfValid(); //noinspection ConstantConditions diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 1269ccb364..1dd7a7315b 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -262,6 +262,57 @@ public RealmSchema getSchema() { * @see #getDefaultInstance() */ public static synchronized void init(Context context) { + initializeRealm(context, ""); + } + + + /** + * Initializes the Realm library and creates a default configuration that is ready to use. It is required to call + * this method before interacting with any other of the Realm API's. + *

            + * A good place is in an {@link android.app.Application} subclass: + *

            +     * {@code
            +     * public class MyApplication extends Application {
            +     *   \@Override
            +     *   public void onCreate() {
            +     *     super.onCreate();
            +     *     Realm.init(this, "MyApp/" + BuildConfig.VERSION_NAME);
            +     *   }
            +     * }
            +     * }
            +     * 
            + *

            + * Remember to register it in the {@code AndroidManifest.xml} file: + *

            +     * {@code
            +     * 
            +     * 
            +     * 
            +     *   // ...
            +     * 
            +     * 
            +     * }
            +     * 
            + * + * @param context the Application Context. + * @param userAgent optional user defined string that will be sent to the Realm Object Server + * as part of a {@code User-Agent} header when a session is established. This setting will not be + * used by non-synchronized Realms. + * @throws IllegalArgumentException if a {@code null} context or userAgent is provided. + * @throws IllegalStateException if {@link Context#getFilesDir()} could not be found. + * @see #getDefaultInstance() + */ + @ObjectServer + public static synchronized void init(Context context, String userAgent) { + //noinspection ConstantConditions + if (userAgent == null) { + throw new IllegalArgumentException("Non-null 'userAgent' required."); + } + initializeRealm(context, userAgent); + } + + private static void initializeRealm(Context context, String userAgent) { if (BaseRealm.applicationContext == null) { //noinspection ConstantConditions if (context == null) { @@ -270,7 +321,7 @@ public static synchronized void init(Context context) { checkFilesDirAvailable(context); RealmCore.loadLibrary(context); setDefaultConfiguration(new RealmConfiguration.Builder(context).build()); - ObjectServerFacade.getSyncFacadeIfPossible().init(context); + ObjectServerFacade.getSyncFacadeIfPossible().init(context, userAgent); if (context.getApplicationContext() != null) { BaseRealm.applicationContext = context.getApplicationContext(); } else { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index d2562ab97a..a1df724e0d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -55,10 +55,8 @@ public class ObjectServerFacade { /** * Initializes the Object Server library - * - * @param context */ - public void init(Context context) { + public void init(Context context, String userAgent) { } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index 401a01f9af..4249445439 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -18,12 +18,15 @@ import android.content.Context; import android.content.pm.PackageInfo; +import android.os.Build; import java.io.File; import java.io.IOException; import java.util.Locale; import io.realm.internal.Keep; +import io.realm.internal.Util; +import io.realm.log.RealmLog; /** * Internal initializer class for the Object Server. @@ -33,7 +36,7 @@ @Keep class ObjectServer { - public static void init(Context context) { + public static void init(Context context, String appDefinedUserAgent) { // Setup AppID String appId = "unknown"; try { @@ -42,6 +45,31 @@ public static void init(Context context) { } catch (Exception ignore) { } + // Setup Realm part of User-Agent string + String userAgent = "Unknown"; // Fallback in case of anything going wrong + try { + StringBuilder sb = new StringBuilder(); + sb.append("RealmJava/"); + sb.append(BuildConfig.VERSION_NAME); + sb.append(" ("); + sb.append(Util.isEmptyString(Build.DEVICE) ? "unknown-device" : Build.DEVICE); + sb.append(", "); + sb.append(Util.isEmptyString(Build.MODEL) ? "unknown-model" : Build.MODEL); + sb.append(", v"); + sb.append(Build.VERSION.SDK_INT); + sb.append(")"); + + // Setup User part of User-Agent string + if (!Util.isEmptyString(appDefinedUserAgent)) { + sb.append(" "); + sb.append(appDefinedUserAgent); + } + userAgent = sb.toString(); + } catch (Exception e) { + // Failures to construct the user agent should never cause the system itself to crash. + RealmLog.warn("Constructing User-Agent description failed.", e); + } + // init the "sync_manager.cpp" metadata Realm, this is also needed later, when re try // to schedule a client reset. in realm-java#master this is already done, when initialising // the RealmFileUserStore (not available now on releases) @@ -59,12 +87,12 @@ public static void init(Context context) { "Directory '%s' for SyncManager cannot be created. ", dir.getPath())); } - SyncManager.nativeInitializeSyncManager(dir.getPath()); + SyncManager.nativeInitializeSyncManager(dir.getPath(), userAgent); } catch (IOException e) { throw new IllegalStateException(e); } } else { - SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath()); + SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath(), userAgent); } // Configure default UserStore diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 2c1b4c560a..0587d3a414 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -749,7 +749,7 @@ static void simulateClientReset(SyncSession session) { true); } - protected static native void nativeInitializeSyncManager(String syncBaseDir); + protected static native void nativeInitializeSyncManager(String syncBaseDir, String userAgent); private static native void nativeReset(); private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); private static native void nativeReconnect(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 0de162bcc1..e16ecd8db4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -50,16 +50,16 @@ public class SyncObjectServerFacade extends ObjectServerFacade { private static volatile Method removeSessionMethod; @Override - public void init(Context context) { + public void init(Context context, String userAgent) { // Trying to keep things out the public API is no fun :/ // Just use reflection on init. It is a one-time method call so should be acceptable. //noinspection TryWithIdenticalCatches try { // FIXME: Reflection can be avoided by moving some functions of SyncManager and ObjectServer out of public Class syncManager = Class.forName("io.realm.ObjectServer"); - Method method = syncManager.getDeclaredMethod("init", Context.class); + Method method = syncManager.getDeclaredMethod("init", Context.class, String.class); method.setAccessible(true); - method.invoke(null, context); + method.invoke(null, context, userAgent); } catch (NoSuchMethodException e) { throw new RealmException("Could not initialize the Realm Object Server", e); } catch (InvocationTargetException e) { From d8f65132d0292a85eba8c88f933eb6e81793bbc8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 3 Nov 2018 22:03:24 +0100 Subject: [PATCH 1333/2110] Add support for uploading and downloading changes with a timeout (#6073) --- CHANGELOG.md | 2 + .../java/io/realm/SessionTests.java | 94 +++++++++++++++++++ .../java/io/realm/SyncConfiguration.java | 51 +++++++++- .../java/io/realm/SyncSession.java | 93 ++++++++++++++++-- .../internal/SyncObjectServerFacade.java | 20 +++- .../java/io/realm/SyncSessionTests.java | 24 +++++ 6 files changed, 267 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bde269a797..a0f18f81d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ This release also contains all changes in 5.8.0-BETA1 and 5.8.0-BETA2. * [ObjectServer] Added `RealmQuery.subscribe()` and `RealmQuery.subscribe(String name)` to subscribe immediately inside a transaction. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). * [ObjectServer] Added support for subscribing directly inside `SyncConfiguration.initialData()`. This can be coupled with `SyncConfiguration.waitForInitialRemoteData()` in order to block a Realm from opening until the initial subscriptions are ready and have downloaded data. This API are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). * [ObjectServer] Improved performance when merging changes from the server. +* [ObjectServer] Added support for timeouts when uploading or downloading data manually using `SyncSession.downloadAllServerChanges(long timeout, TimeUnit unit)` and `SyncSession.uploadAllLocalChanges(long timeout, TimeUnit unit)`. [#6073](https://github.com/realm/realm-java/pull/6073) +* [ObjectServer] Added support for timing out when downloading initial data for synchronized Realms using `SyncConfiguration.waitForInitialRemoteData(long timeout, TimeUnit unit)`. [#6247](https://github.com/realm/realm-java/issues/6247) * Added support for `ImportFlag`s to `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate()`. This makes it possible to choose a mode so only fields that actually changed are written to disk. This improves notifications and Object Server performance. [#6224](https://github.com/realm/realm-java/pull/6224). ### Fixed diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 472c26a581..e86333d6cf 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import io.realm.entities.StringOnly; @@ -400,6 +401,52 @@ public void uploadAllLocalChanges_throwsOnUiThread() throws InterruptedException } } + @Test + @UiThreadTest + public void uploadAllLocalChanges_withTimeout_throwsOnUiThread() throws InterruptedException { + Realm realm = Realm.getInstance(configuration); + try { + SyncManager.getOrCreateSession(configuration, null).uploadAllLocalChanges(30, TimeUnit.SECONDS); + fail("Should throw an IllegalStateException on Ui Thread"); + } catch (IllegalStateException ignored) { + } finally { + realm.close(); + } + } + + @Test + public void uploadAllLocalChanges_withTimeout_invalidParametersThrows() throws InterruptedException { + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getOrCreateSession(configuration, null); + try { + try { + session.uploadAllLocalChanges(-1, TimeUnit.SECONDS); + fail(); + } catch (IllegalArgumentException ignored) { + } + + try { + //noinspection ConstantConditions + session.uploadAllLocalChanges(1, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } finally { + realm.close(); + } + } + + @Test + public void uploadAllLocalChanges_returnFalseWhenTimedOut() throws InterruptedException { + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getOrCreateSession(configuration, null); + try { + assertFalse(session.uploadAllLocalChanges(100, TimeUnit.MILLISECONDS)); + } finally { + realm.close(); + } + } + @Test @UiThreadTest public void downloadAllServerChanges_throwsOnUiThread() throws InterruptedException { @@ -413,6 +460,53 @@ public void downloadAllServerChanges_throwsOnUiThread() throws InterruptedExcept } } + @Test + @UiThreadTest + public void downloadAllServerChanges_withTimeout_throwsOnUiThread() throws InterruptedException { + Realm realm = Realm.getInstance(configuration); + try { + SyncManager.getOrCreateSession(configuration, null).downloadAllServerChanges(30, TimeUnit.SECONDS); + fail("Should throw an IllegalStateException on Ui Thread"); + } catch (IllegalStateException ignored) { + } finally { + realm.close(); + } + } + + + @Test + public void downloadAllServerChanges_withTimeout_invalidParametersThrows() throws InterruptedException { + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getOrCreateSession(configuration, null); + try { + try { + session.downloadAllServerChanges(-1, TimeUnit.SECONDS); + fail(); + } catch (IllegalArgumentException ignored) { + } + + try { + //noinspection ConstantConditions + session.downloadAllServerChanges(1, null); + fail(); + } catch (IllegalArgumentException ignored) { + } + } finally { + realm.close(); + } + } + + @Test + public void downloadAllServerChanges_returnFalseWhenTimedOut() throws InterruptedException { + Realm realm = Realm.getInstance(configuration); + SyncSession session = SyncManager.getOrCreateSession(configuration, null); + try { + assertFalse(session.downloadAllServerChanges(100, TimeUnit.MILLISECONDS)); + } finally { + realm.close(); + } + } + @Test @UiThreadTest public void unrecognizedErrorCode_errorHandler() { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index a2ccd4363f..ed437ef308 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -27,9 +27,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.Locale; -import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -111,6 +110,7 @@ public class SyncConfiguration extends RealmConfiguration { @Nullable private final String serverCertificateAssetName; @Nullable private final String serverCertificateFilePath; private final boolean waitForInitialData; + private final long initialDataTimeoutMillis; private final OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy; private final boolean isPartial; @Nullable private final String syncUrlPrefix; @@ -136,6 +136,7 @@ private SyncConfiguration(File directory, @Nullable String serverCertificateAssetName, @Nullable String serverCertificateFilePath, boolean waitForInitialData, + long initialDataTimeoutMillis, OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy, boolean isPartial, CompactOnLaunchCallback compactOnLaunch, @@ -165,6 +166,7 @@ private SyncConfiguration(File directory, this.serverCertificateAssetName = serverCertificateAssetName; this.serverCertificateFilePath = serverCertificateFilePath; this.waitForInitialData = waitForInitialData; + this.initialDataTimeoutMillis = initialDataTimeoutMillis; this.sessionStopPolicy = sessionStopPolicy; this.isPartial = isPartial; this.syncUrlPrefix = syncUrlPrefix; @@ -408,6 +410,18 @@ public boolean shouldWaitForInitialRemoteData() { return waitForInitialData; } + /** + * Returns the timeout defined when downloading any initial data the first time the Realm is opened. + *

            + * This value is only applicable if {@link #shouldWaitForInitialRemoteData()} returns {@code true}. + * + * @return the time Realm will wait for all changes to be downloaded before it is aborted and an exception is thrown. + * @see SyncConfiguration.Builder#waitForInitialRemoteData(long, TimeUnit) + */ + public long getInitialRemoteDataTimeout(TimeUnit unit) { + return unit.convert(initialDataTimeoutMillis, TimeUnit.MILLISECONDS); + } + @Override boolean isSyncConfiguration() { return true; @@ -479,6 +493,7 @@ public static final class Builder { private final Pattern pattern = Pattern.compile("^[A-Za-z0-9_\\-\\.]+$"); // for checking serverUrl private boolean readOnly = false; private boolean waitForServerChanges = false; + private long initialDataTimeoutMillis = Long.MAX_VALUE; // sync specific private boolean deleteRealmOnLogout = false; private URI serverUrl; @@ -928,7 +943,7 @@ public Builder disableSSLVerification() { return this; } - /* + /** * Setting this will cause the Realm to download all known changes from the server the first time a Realm is * opened. The Realm will not open until all the data has been downloaded. This means that if a device is * offline the Realm will not open. @@ -942,6 +957,35 @@ public Builder disableSSLVerification() { */ public Builder waitForInitialRemoteData() { this.waitForServerChanges = true; + this.initialDataTimeoutMillis = Long.MAX_VALUE; + return this; + } + + /** + * Setting this will cause the Realm to download all known changes from the server the first time a Realm is + * opened. The Realm will not open until all the data has been downloaded. This means that if a device is + * offline the Realm will not open. + *

            + * Since downloading all changes can be an lengthy operation that might block the UI thread, Realms with this + * setting enabled should only be opened on background threads or with + * {@link Realm#getInstanceAsync(RealmConfiguration, Realm.Callback)} on the UI thread. + *

            + * This check is only enforced the first time a Realm is created. If you otherwise want to make sure a Realm + * has the latest changes, use {@link SyncSession#downloadAllServerChanges()}. + * + * @param timeout how long to wait for the download to complete before an {@link io.realm.exceptions.DownloadingRealmInterruptedException} is thrown. + * @param unit the unit of time used to define the timeout. + */ + public Builder waitForInitialRemoteData(long timeout, TimeUnit unit) { + if (timeout < 0) { + throw new IllegalArgumentException("'timeout' must be >= 0. It was: " + timeout); + } + //noinspection ConstantConditions + if (unit == null) { + throw new IllegalArgumentException("Non-null 'unit' required"); + } + this.waitForServerChanges = true; + this.initialDataTimeoutMillis = unit.toMillis(timeout); return this; } @@ -1177,6 +1221,7 @@ public SyncConfiguration build() { serverCertificateAssetName, serverCertificateFilePath, waitForServerChanges, + initialDataTimeoutMillis, sessionStopPolicy, isPartial, compactOnLaunch, diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 08cfd937e2..d98d49df83 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -481,7 +481,35 @@ public void downloadAllServerChanges() throws InterruptedException { // In Java we cannot lock on the Session object either since it will prevent any attempt at modifying the // lifecycle while it is in a waiting state. Thus we use a specialised mutex. synchronized (waitForChangesMutex) { - waitForChanges(DIRECTION_DOWNLOAD); + waitForChanges(DIRECTION_DOWNLOAD, Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } + } + + /** + * Calling this method will block until all known remote changes have been downloaded and applied to the Realm + * or the specified timeout is hit. This will involve network access, so calling this method should only be done + * from a non-UI thread. + *

            + * This method cannot be called before the Realm has been opened. + * + * @throws IllegalStateException if called on the Android main thread. + * @throws InterruptedException if the download took longer than the specified timeout or the thread was interrupted while downloading was in progress. + * The download will continue in the background even after this exception is thrown. + * @throws IllegalArgumentException if {@code timeout} is less than or equal to {@code 0} or {@code unit} is {@code null}. + * @return {@code true} if the data was downloaded before the timeout. {@code false} if the operation timed out or otherwise failed. + */ + public boolean downloadAllServerChanges(long timeout, TimeUnit unit) throws InterruptedException { + checkIfNotOnMainThread("downloadAllServerChanges() cannot be called from the main thread."); + checkTimeout(timeout, unit); + + // Blocking only happens at the Java layer. To prevent deadlocking the underlying SyncSession we register + // an async listener there and let it callback to the Java Session when done. This feels icky at best, but + // since all operations on the SyncSession operate under a shared mutex, we would prevent all other actions on the + // session, including trying to stop it. + // In Java we cannot lock on the Session object either since it will prevent any attempt at modifying the + // lifecycle while it is in a waiting state. Thus we use a specialised mutex. + synchronized (waitForChangesMutex) { + return waitForChanges(DIRECTION_DOWNLOAD, timeout, unit); } } @@ -491,7 +519,7 @@ public void downloadAllServerChanges() throws InterruptedException { *

            * If the device is offline, this method might never return. *

            - * This method cannot be called before the session has been started. + * This method cannot be called before the Realm has been opened. * * @throws IllegalStateException if called on the Android main thread. * @throws InterruptedException if the thread was interrupted while downloading was in progress. @@ -506,7 +534,35 @@ public void uploadAllLocalChanges() throws InterruptedException { // In Java we cannot lock on the Session object either since it will prevent any attempt at modifying the // lifecycle while it is in a waiting state. Thus we use a specialised mutex. synchronized (waitForChangesMutex) { - waitForChanges(DIRECTION_UPLOAD); + waitForChanges(DIRECTION_UPLOAD, Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } + } + + /** + * Calling this method will block until all known local changes have been uploaded to the server or the specified + * timeout is hit. This will involve network access, so calling this method should only be done from a non-UI + * thread. + *

            + * This method cannot be called before the Realm has been opened. + * + * @throws IllegalStateException if called on the Android main thread. + * @throws InterruptedException if the upload took longer than the specified timeout or the thread was interrupted while uploading was in progress. + * The upload will continue in the background even after this exception is thrown. + * @throws IllegalArgumentException if {@code timeout} is less than or equal to {@code 0} or {@code unit} is {@code null}. + * @return {@code true} if the data was uploaded before the timeout. {@code false} if the operation timed out or otherwise failed. + */ + public boolean uploadAllLocalChanges(long timeout, TimeUnit unit) throws InterruptedException { + checkIfNotOnMainThread("uploadAllLocalChanges() cannot be called from the main thread."); + checkTimeout(timeout, unit); + + // Blocking only happens at the Java layer. To prevent deadlocking the underlying SyncSession we register + // an async listener there and let it callback to the Java Session when done. This feels icky at best, but + // since all operations on the SyncSession operate under a shared mutex, we would prevent all other actions on the + // session, including trying to stop it. + // In Java we cannot lock on the Session object either since it will prevent any attempt at modifying the + // lifecycle while it is in a waiting state. Thus we use a specialised mutex. + synchronized (waitForChangesMutex) { + return waitForChanges(DIRECTION_UPLOAD, timeout, unit); } } @@ -549,12 +605,16 @@ void setResolvedRealmURI(URI resolvedRealmURI) { * This method should only be called when guarded by the {@link #waitForChangesMutex}. * It will block into all changes have been either uploaded or downloaded depending on the chosen direction. * - * @param direction either {@link #DIRECTION_DOWNLOAD} or {@link #DIRECTION_UPLOAD} + * @param direction either {@link #DIRECTION_DOWNLOAD} or {@link #DIRECTION_UPLOAD}. + * @param timeout timeout parameter. + * @param unit timeout unit. + * @return {@code true} if the job completed before the timeout was hit, {@code false} */ - private void waitForChanges(int direction) throws InterruptedException { + private boolean waitForChanges(int direction, long timeout, TimeUnit unit) throws InterruptedException { if (direction != DIRECTION_DOWNLOAD && direction != DIRECTION_UPLOAD) { throw new IllegalArgumentException("Unknown direction: " + direction); } + boolean result = false; if (!isClosed) { String realmPath = configuration.getPath(); WaitForSessionWrapper wrapper = new WaitForSessionWrapper(); @@ -565,7 +625,7 @@ private void waitForChanges(int direction) throws InterruptedException { : nativeWaitForUploadCompletion(callbackId, realmPath); if (!listenerRegistered) { waitingForServerChanges.set(null); - String errorMsg = ""; + String errorMsg; switch (direction) { case DIRECTION_DOWNLOAD: errorMsg = "It was not possible to download all remote changes."; break; case DIRECTION_UPLOAD: errorMsg = "It was not possible upload all local changes."; break; @@ -576,7 +636,7 @@ private void waitForChanges(int direction) throws InterruptedException { throw new ObjectServerError(ErrorCode.UNKNOWN, errorMsg + " Has the SyncClient been started?"); } try { - wrapper.waitForServerChanges(); + result = wrapper.waitForServerChanges(timeout, unit); } catch(InterruptedException e) { waitingForServerChanges.set(null); // Ignore any results being sent if the wait was interrupted. throw e; @@ -593,6 +653,7 @@ private void waitForChanges(int direction) throws InterruptedException { waitingForServerChanges.set(null); } } + return result; } private void checkIfNotOnMainThread(String errorMessage) { @@ -601,6 +662,16 @@ private void checkIfNotOnMainThread(String errorMessage) { } } + private void checkTimeout(long timeout, TimeUnit unit) { + if (timeout <= 0) { + throw new IllegalArgumentException("'timeout' must be > 0. It was: " + timeout); + } + //noinspection ConstantConditions + if (unit == null) { + throw new IllegalArgumentException("Non-null 'unit' required"); + } + } + private void checkNonNullListener(@Nullable Object listener) { if (listener == null) { throw new IllegalArgumentException("Non-null 'listener' required."); @@ -844,12 +915,14 @@ private static class WaitForSessionWrapper { private String errorMessage; /** - * Block until the wait either completes or is terminated for other reasons. + * Block until the wait either completes, timeouts or is terminated for other reasons. + * Timeouts are only applied if `timeout` >= 0. */ - public void waitForServerChanges() throws InterruptedException { + public boolean waitForServerChanges(long timeout, TimeUnit unit) throws InterruptedException { if (!resultReceived) { - waiter.await(); + return waiter.await(timeout, unit); } + return isSuccess(); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index e16ecd8db4..c9febaefc9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -25,6 +25,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; +import java.util.concurrent.TimeUnit; import io.realm.Realm; import io.realm.RealmConfiguration; @@ -183,14 +184,25 @@ public void downloadInitialRemoteChanges(RealmConfiguration config) { if (syncConfig.shouldWaitForInitialRemoteData()) { SyncSession session = SyncManager.getSession(syncConfig); try { + long timeoutMillis = syncConfig.getInitialRemoteDataTimeout(TimeUnit.MILLISECONDS); if (!syncConfig.isFullySynchronizedRealm()) { // For Query-based Realms we want to upload all our local changes // first since those might include subscriptions the server needs to process. - // This means that once `downloadAllServerChanges` completes all - // initial subscriptions will also have been downloaded. - session.uploadAllLocalChanges(); + // This means that once `downloadAllServerChanges` completes, all initial + // subscriptions will also have been downloaded. + // + // Note that we are reusing the same timeout for uploading and downloading. + // This means that in the worst case you end up with 2x the timeout for + // Query-based Realms. This is probably an acceptable trade-of as trying + // to expose this would not only complicate the API surface quite a lot, + // but in most (almost all?) cases the amount of data to upload will be trivial. + if (!session.uploadAllLocalChanges(timeoutMillis, TimeUnit.MILLISECONDS)) { + throw new DownloadingRealmInterruptedException(syncConfig, "Failed to first upload local changes in " + timeoutMillis + " milliseconds"); + }; + } + if (!session.downloadAllServerChanges(timeoutMillis, TimeUnit.MILLISECONDS)) { + throw new DownloadingRealmInterruptedException(syncConfig, "Failed to download remote changes in " + timeoutMillis + " milliseconds"); } - session.downloadAllServerChanges(); } catch (InterruptedException e) { throw new DownloadingRealmInterruptedException(syncConfig, e); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 6300954d20..f137814068 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -16,10 +16,12 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; +import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.internal.OsRealmConfig; import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; @@ -642,4 +644,26 @@ public void stop_multipleTimes() { looperThread.testComplete(); }); } + + @Test + @RunTestInLooperThread + public void waitForInitialRemoteData_throwsOnTimeout() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + SyncConfiguration syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .initialData(bgRealm -> { + for (int i = 0; i < 100; i++) { + bgRealm.createObject(AllTypes.class); + } + }) + .waitForInitialRemoteData(1, TimeUnit.MILLISECONDS) + .build(); + + try { + Realm.getInstance(syncConfiguration); + fail("This should have timed out"); + } catch (DownloadingRealmInterruptedException ignore) { + } + looperThread.testComplete(); + } } From 65adead2a6a5c1151468299a171e2ddcc3e34e37 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 4 Nov 2018 00:01:32 +0100 Subject: [PATCH 1334/2110] Missing PR feedback (#6148) --- realm/kotlin-extensions/build.gradle | 4 ---- .../java/io/realm/SyncManagerTests.java | 4 +++- .../objectServer/java/io/realm/SyncManager.java | 3 +-- .../network/OkHttpAuthenticationServer.java | 2 +- .../io/realm/SyncedRealmIntegrationTests.java | 2 ++ .../io/realm/objectserver/utils/UserFactory.java | 15 +++++++++------ .../java/io/realm/rule/RunInLooperThread.java | 7 +++++-- 7 files changed, 21 insertions(+), 16 deletions(-) diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index ec0e31fdf4..80430f385b 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -61,10 +61,6 @@ android { '../realm-library/src/syncTestUtils/java', ] } - compileOptions { - targetCompatibility 1.8 - sourceCompatibility 1.8 - } } dependencies { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 101423ac7b..70426004ae 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -42,6 +42,7 @@ import static io.realm.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) public class SyncManagerTests { @@ -188,6 +189,7 @@ public void session() throws IOException { private void tryCase(Runnable runnable) { try { runnable.run(); + fail(); } catch (IllegalArgumentException ignored) { } } @@ -243,7 +245,6 @@ public void addCustomRequestHeader_illegalArgumentThrows() { @Test public void addCustomRequestHeaders_illegalArgumentThrows() { - tryCase(() -> SyncManager.addCustomRequestHeaders(null)); tryCase(() -> SyncManager.addCustomRequestHeaders(Collections.emptyMap(), null)); tryCase(() -> SyncManager.addCustomRequestHeaders(Collections.emptyMap(), "")); } @@ -283,6 +284,7 @@ public void addCustomHeaders() throws URISyntaxException { Map inputHeaders = new LinkedHashMap<>(); inputHeaders.put("header1", "value1"); inputHeaders.put("header2", "value2"); + SyncManager.addCustomRequestHeaders(null); SyncManager.addCustomRequestHeaders(inputHeaders); Map outputHeaders = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); assertEquals(2, outputHeaders.size()); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 0587d3a414..15a7adaa7e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -344,7 +344,7 @@ public static synchronized void addCustomRequestHeader(String headerName, String * * @param headerName the name of the header. * @param headerValue the value of header. - * @param host if this is provided, the this header will only be used on this particular host. + * @param host if this is provided, this header will only be used on this particular host. * Example of valid values: "localhost", "127.0.0.1" and "myinstance.us1.cloud.realm.io". * @throws IllegalArgumentException If an non-empty {@code headerName}, {@code headerValue} or {@code host} is provided. */ @@ -414,7 +414,6 @@ public static synchronized String getAuthorizationHeaderName(URI objectServerUrl * Returns all the custom headers added to requests to the given url. * * @return all defined custom headers used when making http requests to the given url. - * f */ public static synchronized Map getCustomRequestHeaders(URI serverSyncUrl) { Map headers = new LinkedHashMap<>(globalCustomHeaders); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java index 3f51da45cc..ebc5b87675 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java @@ -319,7 +319,7 @@ private Request.Builder newAuthRequest(URL url, String authToken) { builder.addHeader(entry.getKey(), entry.getValue()); } - // add custom headers used by specific host (may override g + // add custom headers used by specific host (may override global headers) Map customHeaders = this.customHeaders.get(url.getHost()); if (customHeaders != null) { for (Map.Entry entry : customHeaders.entrySet()) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 8f0e0f604d..b411ed3100 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -30,6 +30,8 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.Nullable; + import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; import io.realm.exceptions.DownloadingRealmInterruptedException; diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java index dc7aa345cb..cc32b5df20 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java @@ -153,13 +153,16 @@ public static void logoutAllUsers() { final HandlerThread ht = new HandlerThread("LoggingOutUsersThread"); ht.start(); Handler handler = new Handler(ht.getLooper()); - handler.post(() -> { - Map users = SyncUser.all(); - for (SyncUser user : users.values()) { - user.logOut(); + handler.post(new Runnable() { + @Override + public void run() { + Map users = SyncUser.all(); + for (SyncUser user : users.values()) { + user.logOut(); + } + TestHelper.waitForNetworkThreadExecutorToFinish(); + allUsersLoggedOut.countDown(); } - TestHelper.waitForNetworkThreadExecutorToFinish(); - allUsersLoggedOut.countDown(); }); TestHelper.awaitOrFail(allUsersLoggedOut); ht.quit(); diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java index c9837cb1fc..5c1b9b4107 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java @@ -227,8 +227,11 @@ public void postRunnableDelayed(Runnable runnable, long delayMillis) { public void testComplete() { // Close all resources and run any after test tasks // Post as runnable to ensure that this code runs on the correct thread. - postRunnable(() -> { - closeTestResources(); + postRunnable(new Runnable() { + @Override + public void run() { + closeTestResources(); + } }); } From 9cab2d0ac26fde577bc98de48313bc881fad633f Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Mon, 5 Nov 2018 10:52:57 +0100 Subject: [PATCH 1335/2110] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 231131ca89..72b599d448 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ The API reference is located at [realm.io/docs/java/api](https://realm.io/docs/j ## Getting Help -- **Need help with your code?**: Look for previous questions on the [#realm tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) — or [ask a new question](http://stackoverflow.com/questions/ask?tags=realm). We actively monitor & answer questions on StackOverflow! -- **Have a bug to report?** [Open an issue](https://github.com/realm/realm-java/issues/new). If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue. -- **Have a feature request?** [Open an issue](https://github.com/realm/realm-java/issues/new). Tell us what the feature should do, and why you want the feature. +- **Got a question?**: Look for previous questions on the [#realm tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) — or [ask a new question](http://stackoverflow.com/questions/ask?tags=realm). We actively monitor & answer questions on StackOverflow! +- **Think you found a bug?** [Open an issue](https://github.com/realm/realm-java/issues/new?template=bug_report.md). If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue. +- **Have a feature request?** [Open an issue](https://github.com/realm/realm-java/issues/new?template=feature_request.md). Tell us what the feature should do, and why you want the feature. - Sign up for our [**Community Newsletter**](https://go.pardot.com/l/210132/2017-04-26/3j74l) to get regular tips, learn about other use-cases and get alerted of blogposts and tutorials about Realm. ## Using Snapshots From 9dfeb14ff6e0cea8f3f5a6418a778f22d1245be0 Mon Sep 17 00:00:00 2001 From: "G. Blake Meike" Date: Mon, 5 Nov 2018 07:19:50 -0800 Subject: [PATCH 1336/2110] Add support for bulk updating fields in query results (#5133) --- CHANGELOG.md | 3 +- examples/moduleExample/app/build.gradle | 5 + .../benchmarks/CopyToRealmBenchmarks.java | 1 - .../CopyToRealmOrUpdateBenchmarks.java | 1 - realm/kotlin-extensions/build.gradle | 6 + .../java/io/realm/RealmResultsTests.java | 955 +++++++++++++++++- .../io/realm/entities/MappedAllJavaTypes.java | 73 ++ .../main/cpp/io_realm_internal_OsResults.cpp | 87 ++ .../src/main/java/io/realm/RealmResults.java | 455 ++++++++- .../java/io/realm/internal/ColumnInfo.java | 15 + .../java/io/realm/internal/NativeContext.java | 4 +- .../java/io/realm/internal/OsResults.java | 163 +++ .../internal/objectstore/OsObjectBuilder.java | 32 +- 13 files changed, 1786 insertions(+), 14 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f18f81d7..a8d4aabea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ ## 5.8.0 (YYYY-MM-DD) -This release also contains all changes in 5.8.0-BETA1 and 5.8.0-BETA2. +This release also contains all changes from 5.8.0-BETA1 and 5.8.0-BETA2. ### Enhancements * [ObjectServer] Added Subscription class available to Query-based Realms. This exposes a Subscription more directly. This class is in beta. [#6231](https://github.com/realm/realm-java/pull/6231). @@ -28,6 +28,7 @@ This release also contains all changes in 5.8.0-BETA1 and 5.8.0-BETA2. * [ObjectServer] Added support for timeouts when uploading or downloading data manually using `SyncSession.downloadAllServerChanges(long timeout, TimeUnit unit)` and `SyncSession.uploadAllLocalChanges(long timeout, TimeUnit unit)`. [#6073](https://github.com/realm/realm-java/pull/6073) * [ObjectServer] Added support for timing out when downloading initial data for synchronized Realms using `SyncConfiguration.waitForInitialRemoteData(long timeout, TimeUnit unit)`. [#6247](https://github.com/realm/realm-java/issues/6247) * Added support for `ImportFlag`s to `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate()`. This makes it possible to choose a mode so only fields that actually changed are written to disk. This improves notifications and Object Server performance. [#6224](https://github.com/realm/realm-java/pull/6224). +* Added support for bulk updating the same property in all objects that are part of a query result using `RealmResults.setValue(String fieldName, Object value)` or one of the specialized overrides that have been added for all supported types, e.g. `RealmResults.setString(String fieldName, String value)` [#762](https://github.com/realm/realm-java/issues/762). ### Fixed * All known bugs introduced in 5.8.0-BETA1 and 5.8.0-BETA2. See the release notes for these releases. diff --git a/examples/moduleExample/app/build.gradle b/examples/moduleExample/app/build.gradle index cfcc837f6a..bcdc55e3a5 100644 --- a/examples/moduleExample/app/build.gradle +++ b/examples/moduleExample/app/build.gradle @@ -29,6 +29,11 @@ android { signingConfig signingConfigs.release } } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } } dependencies { diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java index 13586e83bc..16efd9642e 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java @@ -1,4 +1,3 @@ -/* * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java index 45c3baf373..8490a274a9 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java @@ -1,4 +1,3 @@ -/* * Copyright 2018 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 80430f385b..2a505eed1a 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -61,6 +61,12 @@ android { '../realm-library/src/syncTestUtils/java', ] } + + // Required from Kotlin 1.1.2 + compileOptions { + targetCompatibility 1.8 + sourceCompatibility 1.8 + } } dependencies { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 69364eb7a7..741d372d3f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -34,19 +34,26 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.DefaultValueOfField; import io.realm.entities.Dog; +import io.realm.entities.MappedAllJavaTypes; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.Owner; +import io.realm.entities.PrimaryKeyAsLong; +import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.RandomPrimaryKey; import io.realm.entities.StringOnly; import io.realm.internal.OsResults; +import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -54,7 +61,7 @@ @RunWith(AndroidJUnit4.class) public class RealmResultsTests extends CollectionTests { - private final static int TEST_DATA_SIZE = 2516; + private final static int TEST_DATA_SIZE = 100; private final static long YEAR_MILLIS = TimeUnit.DAYS.toMillis(365); private final static long DECADE_MILLIS = 10 * TimeUnit.DAYS.toMillis(365); @@ -709,4 +716,950 @@ public void getRealm_throwsIfRealmClosed() { } catch (IllegalStateException ignore) { } } + + private void populateMappedAllJavaTypes(int objects) { + realm.beginTransaction(); + realm.deleteAll(); + for (int i = 0; i < objects; ++i) { + MappedAllJavaTypes obj = realm.createObject(MappedAllJavaTypes.class, i); + obj.fieldBoolean = ((i % 2) == 0); + obj.fieldBinary = (new byte[]{1, 2, 3}); + obj.fieldDate = (new Date(YEAR_MILLIS * (i - objects / 2))); + obj.fieldDouble = (Math.PI + i); + obj.fieldFloat = (1.234567f + i); + obj.fieldString = ("test data " + i); + obj.fieldLong = i; + obj.fieldObject = obj; + obj.fieldList.add(obj); + } + realm.commitTransaction(); + } + + private void populateAllJavaTypes(int objects) { + realm.beginTransaction(); + realm.deleteAll(); + for (int i = 0; i < objects; ++i) { + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, i); + obj.setFieldBoolean((i % 2) == 0); + obj.setFieldBinary(new byte[]{1, 2, 3}); + obj.setFieldDate(new Date(YEAR_MILLIS * (i - objects / 2))); + obj.setFieldDouble(Math.PI + i); + obj.setFieldFloat(1.234567f + i); + obj.setFieldString("test data " + i); + obj.setFieldLong(i); + obj.setFieldObject(obj); + obj.getFieldList().add(obj); + } + realm.commitTransaction(); + } + + enum BulkSetMethods { + STRING, + BOOLEAN, + BYTE, + SHORT, + INTEGER, + LONG, + FLOAT, + DOUBLE, + BINARY, + DATE, + OBJECT, + MODEL_LIST, + STRING_VALUE_LIST, + BOOLEAN_VALUE_LIST, + BYTE_VALUE_LIST, + SHORT_VALUE_LIST, + INTEGER_VALUE_LIST, + LONG_VALUE_LIST, + FLOAT_VALUE_LIST, + DOUBLE_VALUE_LIST, + BINARY_VALUE_LIST, + DATE_VALUE_LIST + } + + interface ElementValidator { + void validate(T obj); + } + + private void assertElements(RealmResults collection, ElementValidator validator) { + for (T obj : collection) { + validator.validate(obj); + } + } + + @Test + public void setValue() { + populateAllJavaTypes(5); + RealmResults collection = realm.where(AllJavaTypes.class).findAll(); + realm.beginTransaction(); + for (BulkSetMethods type : BulkSetMethods.values()) { + switch(type) { + case STRING: + collection.setValue(AllJavaTypes.FIELD_STRING, "foo"); + assertElements(collection, obj -> assertEquals("foo", obj.getFieldString())); + collection.setValue(AllJavaTypes.FIELD_STRING, null); + assertElements(collection, obj -> assertEquals(null, obj.getFieldString())); + break; + case BOOLEAN: + collection.setValue(AllJavaTypes.FIELD_BOOLEAN, true); + assertElements(collection, obj -> assertTrue(obj.isFieldBoolean())); + break; + case BYTE: + collection.setValue(AllJavaTypes.FIELD_BYTE, (byte) 1); + assertElements(collection, obj -> assertEquals((byte)1, obj.getFieldByte())); + break; + case SHORT: + collection.setValue(AllJavaTypes.FIELD_SHORT, (short) 2); + assertElements(collection, obj -> assertEquals((short)2, obj.getFieldShort())); + break; + case INTEGER: + collection.setValue(AllJavaTypes.FIELD_INT, 3); + assertElements(collection, obj -> assertEquals(3, obj.getFieldInt())); + break; + case LONG: + collection.setValue(AllJavaTypes.FIELD_LONG, 4L); + assertElements(collection, obj -> assertEquals(4L, obj.getFieldLong())); + break; + case FLOAT: + collection.setValue(AllJavaTypes.FIELD_FLOAT, 1.23F); + assertElements(collection, obj -> assertEquals(1.23F, obj.getFieldFloat(), 0F)); + break; + case DOUBLE: + collection.setValue(AllJavaTypes.FIELD_DOUBLE, 1.234); + assertElements(collection, obj -> assertEquals(1.234, obj.getFieldDouble(), 0F)); + break; + case BINARY: + collection.setValue(AllJavaTypes.FIELD_BINARY, new byte[]{1,2,3}); + assertElements(collection, obj -> assertArrayEquals(new byte[]{1,2,3}, obj.getFieldBinary())); + collection.setValue(AllJavaTypes.FIELD_BINARY, null); + assertElements(collection, obj -> assertNull(obj.getFieldBinary())); + break; + case DATE: + collection.setValue(AllJavaTypes.FIELD_DATE, new Date(1000)); + assertElements(collection, obj -> assertEquals(new Date(1000), obj.getFieldDate())); + collection.setValue(AllJavaTypes.FIELD_DATE, null); + assertElements(collection, obj -> assertEquals(null, obj.getFieldDate())); + break; + case OBJECT: { + AllJavaTypes childObj = realm.createObject(AllJavaTypes.class, 42); + collection.setValue(AllJavaTypes.FIELD_OBJECT, childObj); + assertElements(collection, obj -> assertEquals(childObj, obj.getFieldObject())); + collection.setValue(AllJavaTypes.FIELD_OBJECT, null); + assertElements(collection, obj -> assertNull(obj.getFieldObject())); + break; + } + case MODEL_LIST: { + AllJavaTypes childObj = realm.createObject(AllJavaTypes.class, 43); + collection.setValue(AllJavaTypes.FIELD_LIST, new RealmList<>(childObj)); + assertElements(collection, obj -> { + assertEquals(1, obj.getFieldList().size()); + assertEquals(childObj, obj.getFieldList().first()); + }); + break; + } + case STRING_VALUE_LIST: { + RealmList list = new RealmList<>("Foo", "Bar"); + collection.setValue(AllJavaTypes.FIELD_STRING_LIST, list); + assertElements(collection, obj -> { + assertEquals("Foo", obj.getFieldStringList().first()); + assertEquals("Bar", obj.getFieldStringList().last()); + }); + break; + } + case BOOLEAN_VALUE_LIST: { + RealmList list = new RealmList<>(true, false); + collection.setValue(AllJavaTypes.FIELD_BOOLEAN_LIST, list); + assertElements(collection, obj -> { + assertTrue(obj.getFieldBooleanList().first()); + assertFalse(obj.getFieldBooleanList().last()); + }); + break; + } + case BYTE_VALUE_LIST: { + RealmList list = new RealmList<>((byte) 1, (byte) 2); + collection.setValue(AllJavaTypes.FIELD_BYTE_LIST, list); + assertElements(collection, obj -> { + assertEquals(Byte.valueOf((byte) 1), obj.getFieldByteList().first()); + assertEquals(Byte.valueOf((byte) 2), obj.getFieldByteList().last()); + }); + break; + } + case SHORT_VALUE_LIST: { + RealmList list = new RealmList<>((short) 1, (short) 2); + collection.setValue(AllJavaTypes.FIELD_SHORT_LIST, list); + assertElements(collection, obj -> { + assertEquals(Short.valueOf((short) 1), obj.getFieldShortList().first()); + assertEquals(Short.valueOf((short) 2), obj.getFieldShortList().last()); + }); + break; + } + case INTEGER_VALUE_LIST: { + RealmList list = new RealmList<>(1, 2); + collection.setValue(AllJavaTypes.FIELD_INTEGER_LIST, list); + assertElements(collection, obj -> { + assertEquals(Integer.valueOf(1), obj.getFieldIntegerList().first()); + assertEquals(Integer.valueOf(2), obj.getFieldIntegerList().last()); + }); + break; + } + case LONG_VALUE_LIST: { + RealmList list = new RealmList<>(1L, 2L); + collection.setValue(AllJavaTypes.FIELD_LONG_LIST, list); + assertElements(collection, obj -> { + assertEquals(Long.valueOf(1), obj.getFieldLongList().first()); + assertEquals(Long.valueOf(2), obj.getFieldLongList().last()); + }); + break; + } + case FLOAT_VALUE_LIST: { + RealmList list = new RealmList<>(1.1F, 2.2F); + collection.setValue(AllJavaTypes.FIELD_FLOAT_LIST, list); + assertElements(collection, obj -> { + assertEquals(1.1F, obj.getFieldFloatList().first(), 0F); + assertEquals(2.2F, obj.getFieldFloatList().last(), 0F); + }); + break; + } + case DOUBLE_VALUE_LIST: { + RealmList list = new RealmList<>(1.1D, 2.2D); + collection.setValue(AllJavaTypes.FIELD_DOUBLE_LIST, list); + assertElements(collection, obj -> { + assertEquals(1.1D, obj.getFieldDoubleList().first(), 0D); + assertEquals(2.2D, obj.getFieldDoubleList().last(), 0D); + }); + break; + } + case BINARY_VALUE_LIST: { + RealmList list = new RealmList<>(new byte[] {1,2,3}, new byte[] {2,3,4}); + collection.setValue(AllJavaTypes.FIELD_BINARY_LIST, list); + assertElements(collection, obj -> { + assertArrayEquals(new byte[] {1,2,3}, obj.getFieldBinaryList().first()); + assertArrayEquals(new byte[] {2,3,4}, obj.getFieldBinaryList().last()); + }); + break; + } + case DATE_VALUE_LIST: { + RealmList list = new RealmList<>(new Date(1000), new Date(2000)); + collection.setValue(AllJavaTypes.FIELD_DATE_LIST, list); + assertElements(collection, obj -> { + assertEquals(new Date(1000), obj.getFieldDateList().first()); + assertEquals(new Date(2000), obj.getFieldDateList().last()); + }); + break; + } + default: + fail("Unknown type: " + type); + } + } + } + + @Test + public void setValue_implicitConversions() { + populateAllJavaTypes(5); + RealmResults collection = realm.where(AllJavaTypes.class).findAll(); + realm.beginTransaction(); + for (BulkSetMethods type : BulkSetMethods.values()) { + switch(type) { + case BOOLEAN: + collection.setValue(AllJavaTypes.FIELD_BOOLEAN, "true"); + assertElements(collection, obj -> assertTrue(obj.isFieldBoolean())); + collection.setValue(AllJavaTypes.FIELD_BOOLEAN, "FALSE"); + assertElements(collection, obj -> assertFalse(obj.isFieldBoolean())); + collection.setValue(AllJavaTypes.FIELD_BOOLEAN, "True"); + assertElements(collection, obj -> assertTrue(obj.isFieldBoolean())); + collection.setValue(AllJavaTypes.FIELD_BOOLEAN, "false"); + assertElements(collection, obj -> assertFalse(obj.isFieldBoolean())); + collection.setValue(AllJavaTypes.FIELD_BOOLEAN, "TRUE"); + assertElements(collection, obj -> assertTrue(obj.isFieldBoolean())); + break; + case BYTE: + collection.setValue(AllJavaTypes.FIELD_BYTE, "1"); + assertElements(collection, obj -> assertEquals((byte)1, obj.getFieldByte())); + break; + case SHORT: + collection.setValue(AllJavaTypes.FIELD_SHORT, "2"); + assertElements(collection, obj -> assertEquals((short)2, obj.getFieldShort())); + break; + case INTEGER: + collection.setValue(AllJavaTypes.FIELD_INT, "3"); + assertElements(collection, obj -> assertEquals(3, obj.getFieldInt())); + break; + case LONG: + collection.setValue(AllJavaTypes.FIELD_LONG, Long.toString(Long.MAX_VALUE)); + assertElements(collection, obj -> assertEquals(Long.MAX_VALUE, obj.getFieldLong())); + break; + case FLOAT: + collection.setValue(AllJavaTypes.FIELD_FLOAT, "1.23F"); + assertElements(collection, obj -> assertEquals(1.23F, obj.getFieldFloat(), 0F)); + break; + case DOUBLE: + collection.setValue(AllJavaTypes.FIELD_DOUBLE, "1.234"); + assertElements(collection, obj -> assertEquals(1.234, obj.getFieldDouble(), 0F)); + break; + case DATE: + collection.setValue(AllJavaTypes.FIELD_DATE, "1000"); + assertElements(collection, obj -> assertEquals(new Date(1000), obj.getFieldDate())); + collection.setValue(AllJavaTypes.FIELD_DATE, "/Date(2000+0000)/"); + assertElements(collection, obj -> assertEquals(new Date(2000), obj.getFieldDate())); + break; + + // These types do not offer any implicit conversion + case STRING: + case BINARY: + case OBJECT: + case MODEL_LIST: + case STRING_VALUE_LIST: + case BOOLEAN_VALUE_LIST: + case BYTE_VALUE_LIST: + case SHORT_VALUE_LIST: + case INTEGER_VALUE_LIST: + case LONG_VALUE_LIST: + case FLOAT_VALUE_LIST: + case DOUBLE_VALUE_LIST: + case BINARY_VALUE_LIST: + case DATE_VALUE_LIST: + continue; + + default: + fail("Unknown type: " + type); + } + } + } + + @Test + public void setValue_specificType() { + populateAllJavaTypes(5); + RealmResults collection = realm.where(AllJavaTypes.class).findAll(); + realm.beginTransaction(); + for (BulkSetMethods type : BulkSetMethods.values()) { + switch(type) { + case STRING: + collection.setString(AllJavaTypes.FIELD_STRING, "foo"); + assertElements(collection, obj -> assertEquals("foo", obj.getFieldString())); + collection.setString(AllJavaTypes.FIELD_STRING, null); + assertElements(collection, obj -> assertEquals(null, obj.getFieldString())); + break; + case BOOLEAN: + collection.setBoolean(AllJavaTypes.FIELD_BOOLEAN, true); + assertElements(collection, obj -> assertTrue(obj.isFieldBoolean())); + break; + case BYTE: + collection.setByte(AllJavaTypes.FIELD_BYTE, (byte) 1); + assertElements(collection, obj -> assertEquals((byte)1, obj.getFieldByte())); + break; + case SHORT: + collection.setShort(AllJavaTypes.FIELD_SHORT, (short) 2); + assertElements(collection, obj -> assertEquals((short)2, obj.getFieldShort())); + break; + case INTEGER: + collection.setInt(AllJavaTypes.FIELD_INT, 3); + assertElements(collection, obj -> assertEquals(3, obj.getFieldInt())); + break; + case LONG: + collection.setLong(AllJavaTypes.FIELD_LONG, 4L); + assertElements(collection, obj -> assertEquals(4L, obj.getFieldLong())); + break; + case FLOAT: + collection.setFloat(AllJavaTypes.FIELD_FLOAT, 1.23F); + assertElements(collection, obj -> assertEquals(1.23F, obj.getFieldFloat(), 0F)); + break; + case DOUBLE: + collection.setDouble(AllJavaTypes.FIELD_DOUBLE, 1.234); + assertElements(collection, obj -> assertEquals(1.234, obj.getFieldDouble(), 0F)); + break; + case BINARY: + collection.setBlob(AllJavaTypes.FIELD_BINARY, new byte[]{1,2,3}); + assertElements(collection, obj -> assertArrayEquals(new byte[]{1,2,3}, obj.getFieldBinary())); + collection.setBlob(AllJavaTypes.FIELD_BINARY, null); + assertElements(collection, obj -> assertNull(obj.getFieldBinary())); + break; + case DATE: + collection.setDate(AllJavaTypes.FIELD_DATE, new Date(1000)); + assertElements(collection, obj -> assertEquals(new Date(1000), obj.getFieldDate())); + collection.setDate(AllJavaTypes.FIELD_DATE, null); + assertElements(collection, obj -> assertEquals(null, obj.getFieldDate())); + break; + case OBJECT: { + AllJavaTypes childObj = realm.createObject(AllJavaTypes.class, 42); + collection.setObject(AllJavaTypes.FIELD_OBJECT, childObj); + assertElements(collection, obj -> assertEquals(childObj, obj.getFieldObject())); + collection.setObject(AllJavaTypes.FIELD_OBJECT, null); + assertElements(collection, obj -> assertNull(obj.getFieldObject())); + break; + } + case MODEL_LIST: { + AllJavaTypes childObj = realm.createObject(AllJavaTypes.class, 43); + collection.setList(AllJavaTypes.FIELD_LIST, new RealmList<>(childObj)); + assertElements(collection, obj -> { + assertEquals(1, obj.getFieldList().size()); + assertEquals(childObj, obj.getFieldList().first()); + }); + break; + } + case STRING_VALUE_LIST: { + RealmList list = new RealmList<>("Foo", "Bar"); + collection.setList(AllJavaTypes.FIELD_STRING_LIST, list); + assertElements(collection, obj -> { + assertEquals("Foo", obj.getFieldStringList().first()); + assertEquals("Bar", obj.getFieldStringList().last()); + }); + break; + } + case BOOLEAN_VALUE_LIST: { + RealmList list = new RealmList<>(true, false); + collection.setList(AllJavaTypes.FIELD_BOOLEAN_LIST, list); + assertElements(collection, obj -> { + assertTrue(obj.getFieldBooleanList().first()); + assertFalse(obj.getFieldBooleanList().last()); + }); + break; + } + case BYTE_VALUE_LIST: { + RealmList list = new RealmList<>((byte) 1, (byte) 2); + collection.setList(AllJavaTypes.FIELD_BYTE_LIST, list); + assertElements(collection, obj -> { + assertEquals(Byte.valueOf((byte) 1), obj.getFieldByteList().first()); + assertEquals(Byte.valueOf((byte) 2), obj.getFieldByteList().last()); + }); + break; + } + case SHORT_VALUE_LIST: { + RealmList list = new RealmList<>((short) 1, (short) 2); + collection.setList(AllJavaTypes.FIELD_SHORT_LIST, list); + assertElements(collection, obj -> { + assertEquals(Short.valueOf((short) 1), obj.getFieldShortList().first()); + assertEquals(Short.valueOf((short) 2), obj.getFieldShortList().last()); + }); + break; + } + case INTEGER_VALUE_LIST: { + RealmList list = new RealmList<>(1, 2); + collection.setList(AllJavaTypes.FIELD_INTEGER_LIST, list); + assertElements(collection, obj -> { + assertEquals(Integer.valueOf(1), obj.getFieldIntegerList().first()); + assertEquals(Integer.valueOf(2), obj.getFieldIntegerList().last()); + }); + break; + } + case LONG_VALUE_LIST: { + RealmList list = new RealmList<>(1L, 2L); + collection.setList(AllJavaTypes.FIELD_LONG_LIST, list); + assertElements(collection, obj -> { + assertEquals(Long.valueOf(1), obj.getFieldLongList().first()); + assertEquals(Long.valueOf(2), obj.getFieldLongList().last()); + }); + break; + } + case FLOAT_VALUE_LIST: { + RealmList list = new RealmList<>(1.1F, 2.2F); + collection.setList(AllJavaTypes.FIELD_FLOAT_LIST, list); + assertElements(collection, obj -> { + assertEquals(1.1F, obj.getFieldFloatList().first(), 0F); + assertEquals(2.2F, obj.getFieldFloatList().last(), 0F); + }); + break; + } + case DOUBLE_VALUE_LIST: { + RealmList list = new RealmList<>(1.1D, 2.2D); + collection.setList(AllJavaTypes.FIELD_DOUBLE_LIST, list); + assertElements(collection, obj -> { + assertEquals(1.1D, obj.getFieldDoubleList().first(), 0D); + assertEquals(2.2D, obj.getFieldDoubleList().last(), 0D); + }); + break; + } + case BINARY_VALUE_LIST: { + RealmList list = new RealmList<>(new byte[] {1,2,3}, new byte[] {2,3,4}); + collection.setList(AllJavaTypes.FIELD_BINARY_LIST, list); + assertElements(collection, obj -> { + assertArrayEquals(new byte[] {1,2,3}, obj.getFieldBinaryList().first()); + assertArrayEquals(new byte[] {2,3,4}, obj.getFieldBinaryList().last()); + }); + break; + } + case DATE_VALUE_LIST: { + RealmList list = new RealmList<>(new Date(1000), new Date(2000)); + collection.setList(AllJavaTypes.FIELD_DATE_LIST, list); + assertElements(collection, obj -> { + assertEquals(new Date(1000), obj.getFieldDateList().first()); + assertEquals(new Date(2000), obj.getFieldDateList().last()); + }); + break; + } + default: + fail("Unknown type: " + type); + } + } + } + + @Test + public void setObject_unmanagedObjectThrows() { + RealmResults collection = realm.where(AllTypes.class).findAll(); + realm.beginTransaction(); + try { + collection.setObject(AllTypes.FIELD_REALMOBJECT, new Dog()); + fail(); + } catch (IllegalArgumentException e) { + assertTrue("Wrong error message: " + e.getMessage(), e.getMessage().contains("is not a valid, managed Realm object.")); + } + } + + @Test + public void setObject_wrongObjectTypeThrows() { + RealmResults collection = realm.where(AllTypes.class).findAll(); + realm.beginTransaction(); + try { + collection.setObject(AllTypes.FIELD_REALMOBJECT, realm.createObject(AllTypes.class)); + fail(); + } catch (IllegalArgumentException e) { + assertTrue("Wrong error message: " + e.getMessage(), e.getMessage().equals("Type of object is wrong. Was 'AllTypes', expected 'Dog'")); + } finally { + realm.cancelTransaction(); + } + + DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); + RealmResults dynamicCollection = dynamicRealm.where("AllTypes").findAll(); + dynamicRealm.beginTransaction(); + try { + dynamicCollection.setObject(AllTypes.FIELD_REALMOBJECT, dynamicRealm.createObject("AllTypes")); + fail(); + } catch (IllegalArgumentException e) { + assertTrue("Wrong error message: " + e.getMessage(), e.getMessage().equals("Type of object is wrong. Was 'AllTypes', expected 'Dog'")); + } finally { + dynamicRealm.close(); + } + } + + @Test + public void setList_unmanagedObjectThrows() { + RealmResults collection = realm.where(AllTypes.class).findAll(); + realm.beginTransaction(); + try { + collection.setList(AllTypes.FIELD_REALMLIST, new RealmList<>(new Dog())); + fail(); + } catch (IllegalArgumentException e) { + assertTrue("Wrong error message: " + e.getMessage(), e.getMessage().contains("is not a valid, managed Realm object.")); + } + } + + @Test + public void setList_wrongObjectTypeThrows() { + RealmResults collection = realm.where(AllTypes.class).findAll(); + realm.beginTransaction(); + try { + collection.setList(AllTypes.FIELD_REALMLIST, new RealmList<>(realm.createObject(AllTypes.class))); + fail(); + } catch (IllegalArgumentException e) { + assertTrue("Wrong error message: " + e.getMessage(), e.getMessage().equals("Type of object is wrong. Was 'AllTypes', expected 'Dog'")); + } finally { + realm.cancelTransaction(); + } + + DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); + RealmResults dynamicCollection = dynamicRealm.where("AllTypes").findAll(); + dynamicRealm.beginTransaction(); + try { + dynamicCollection.setList(AllTypes.FIELD_REALMLIST, new RealmList<>(dynamicRealm.createObject("AllTypes"))); + fail(); + } catch (IllegalArgumentException e) { + assertTrue("Wrong error message: " + e.getMessage(), e.getMessage().equals("Type of object is wrong. Was 'AllTypes', expected 'Dog'")); + } finally { + dynamicRealm.close(); + } + } + + @Test + public void setValue_specificType_wrongFieldNameThrows() { + populateAllJavaTypes(5); + RealmResults collection = realm.where(AllTypes.class).findAll(); + realm.beginTransaction(); + for (BulkSetMethods type : BulkSetMethods.values()) { + try { + switch(type) { + case STRING: collection.setString("foo", "bar"); break; + case BOOLEAN: collection.setBoolean("foo", true); break; + case BYTE: collection.setByte("foo", (byte) 1); break; + case SHORT: collection.setShort("foo", (short) 2); break; + case INTEGER: collection.setInt("foo", 3); break; + case LONG: collection.setLong("foo", 4L); break; + case FLOAT: collection.setFloat("foo", 1.23F); break; + case DOUBLE: collection.setDouble("foo", 1.234); break; + case BINARY: collection.setBlob("foo", new byte[]{1,2,3}); break; + case DATE: collection.setDate("foo", new Date(1000)); break; + case OBJECT: collection.setObject("foo", realm.createObject(AllTypes.class)); break; + case MODEL_LIST: collection.setList("foo", new RealmList<>()); break; + case STRING_VALUE_LIST: collection.setList("foo", new RealmList<>("Foo")); break; + case BOOLEAN_VALUE_LIST: collection.setList("foo", new RealmList<>(true)); break; + case BYTE_VALUE_LIST: collection.setList("foo", new RealmList<>((byte) 1)); break; + case SHORT_VALUE_LIST: collection.setList("foo", new RealmList<>((short) 1)); break; + case INTEGER_VALUE_LIST: collection.setList("foo", new RealmList<>(1)); break; + case LONG_VALUE_LIST: collection.setList("foo", new RealmList<>(1L)); break; + case FLOAT_VALUE_LIST: collection.setList("foo", new RealmList<>(1.1F)); break; + case DOUBLE_VALUE_LIST: collection.setList("foo", new RealmList<>(1.1D)); break; + case BINARY_VALUE_LIST: collection.setList("foo", new RealmList<>(new byte[] {})); break; + case DATE_VALUE_LIST: collection.setList("foo", new RealmList<>(new Date())); break; + default: + fail("Unknown type: " + type); + } + fail(type + " should have thrown an exception"); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("does not exist")); + } + } + } + + @Test + public void setValue_specificType_wrongTypeThrows() { + populateAllJavaTypes(5); + RealmResults collection = realm.where(AllJavaTypes.class).findAll(); + realm.beginTransaction(); + for (BulkSetMethods type : BulkSetMethods.values()) { + try { + switch(type) { + case STRING: collection.setString(AllJavaTypes.FIELD_BOOLEAN, "foo"); break; + case BOOLEAN: collection.setBoolean(AllJavaTypes.FIELD_STRING, true); break; + case BYTE: collection.setByte(AllJavaTypes.FIELD_STRING, (byte) 1); break; + case SHORT: collection.setShort(AllJavaTypes.FIELD_STRING, (short) 2); break; + case INTEGER: collection.setInt(AllJavaTypes.FIELD_STRING, 3); break; + case LONG:collection.setLong(AllJavaTypes.FIELD_STRING, 4L); break; + case FLOAT: collection.setFloat(AllJavaTypes.FIELD_STRING, 1.23F); break; + case DOUBLE: collection.setDouble(AllJavaTypes.FIELD_STRING, 1.234); break; + case BINARY: collection.setBlob(AllJavaTypes.FIELD_STRING, new byte[]{1,2,3}); break; + case DATE: collection.setDate(AllJavaTypes.FIELD_STRING, new Date(1000)); break; + case OBJECT: collection.setObject(AllJavaTypes.FIELD_STRING, realm.createObject(AllJavaTypes.class, 42)); break; + case MODEL_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(realm.createObject(AllJavaTypes.class, 43))); break; + case STRING_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>("Foo")); break; + case BOOLEAN_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(true)); break; + case BYTE_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>((byte)1)); break; + case SHORT_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>((short)1)); break; + case INTEGER_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(1)); break; + case LONG_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(1L)); break; + case FLOAT_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(1.1F)); break; + case DOUBLE_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(2.2D)); break; + case BINARY_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(new byte[]{})); break; + case DATE_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(new Date())); break; + default: + fail("Unknown type: " + type); + } + fail(type + " should have thrown an exception"); + } catch (IllegalArgumentException e) { + RealmLog.error(type + " -> " + e.getMessage()); + assertTrue(type + " failed", e.getMessage().contains("is not of the expected type") + || e.getMessage().contains("List contained the wrong type of elements") + || e.getMessage().contains("is not a list")); + } + } + } + + @Test + public void setValue_specificType_primaryKeyFieldThrows() { + populateAllJavaTypes(5); + realm.beginTransaction(); + try { + RealmResults collection = realm.where(PrimaryKeyAsString.class).findAll(); + collection.setString(PrimaryKeyAsString.FIELD_PRIMARY_KEY, "foo"); + fail(); + } catch (IllegalStateException ignore) { + } + + try { + RealmResults collection = realm.where(PrimaryKeyAsLong.class).findAll(); + collection.setLong(PrimaryKeyAsLong.FIELD_ID, 42); + fail(); + } catch (IllegalStateException ignore) { + } + } + + @Test + public void setValue_specificType_modelClassNameOnTypedRealms() { + populateMappedAllJavaTypes(5); + RealmResults collection = realm.where(MappedAllJavaTypes.class).findAll(); + realm.beginTransaction(); + for (BulkSetMethods type : BulkSetMethods.values()) { + switch(type) { + case STRING: + collection.setString("fieldString", "foo"); + assertElements(collection, obj -> assertEquals("foo", obj.fieldString)); + break; + case BOOLEAN: + collection.setBoolean("fieldBoolean", true); + assertElements(collection, obj -> assertTrue(obj.fieldBoolean)); + break; + case BYTE: + collection.setByte("fieldByte", (byte) 1); + assertElements(collection, obj -> assertEquals((byte) 1, obj.fieldByte)); + break; + case SHORT: + collection.setShort("fieldShort", (short) 2); + assertElements(collection, obj -> assertEquals((short) 2, obj.fieldShort)); + break; + case INTEGER: + collection.setInt("fieldInt", 3); + assertElements(collection, obj -> assertEquals(3, obj.fieldInt)); + break; + case LONG: + collection.setLong("fieldLong", 4L); + assertElements(collection, obj -> assertEquals(4L, obj.fieldLong)); + break; + case FLOAT: + collection.setFloat("fieldFloat", 1.23F); + assertElements(collection, obj -> assertEquals(1.23F, obj.fieldFloat, 0F)); + break; + case DOUBLE: + collection.setDouble("fieldDouble", 1.234); + assertElements(collection, obj -> assertEquals(1.234, obj.fieldDouble, 0F)); + break; + case BINARY: + collection.setBlob("fieldBinary", new byte[]{1,2,3}); + assertElements(collection, obj -> assertArrayEquals(new byte[]{1,2,3}, obj.fieldBinary)); + break; + case DATE: + collection.setDate("fieldDate", new Date(1000)); + assertElements(collection, obj -> assertEquals(new Date(1000), obj.fieldDate)); + break; + case OBJECT: { + MappedAllJavaTypes childObj = realm.createObject(MappedAllJavaTypes.class, 42); + collection.setObject("fieldObject", childObj); + assertElements(collection, obj -> assertEquals(childObj, obj.fieldObject)); + break; + } + case MODEL_LIST: { + MappedAllJavaTypes childObj = realm.createObject(MappedAllJavaTypes.class, 43); + collection.setList("fieldList", new RealmList<>(childObj)); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldList.size()); + assertEquals(childObj, obj.fieldList.first()); + }); + break; + } + case STRING_VALUE_LIST: + collection.setList("fieldStringList", new RealmList<>("Foo")); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldStringList.size()); + assertEquals("Foo", obj.fieldStringList.first()); + }); + break; + case BOOLEAN_VALUE_LIST: + collection.setList("fieldBooleanList", new RealmList<>(true)); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldBooleanList.size()); + assertEquals(true, obj.fieldBooleanList.first()); + }); + break; + case BYTE_VALUE_LIST: + collection.setList("fieldByteList", new RealmList<>((byte)1)); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldByteList.size()); + assertEquals(Byte.valueOf((byte) 1), obj.fieldByteList.first()); + }); + break; + case SHORT_VALUE_LIST: + collection.setList("fieldShortList", new RealmList<>((short)1)); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldShortList.size()); + assertEquals(Short.valueOf((short) 1), obj.fieldShortList.first()); + }); + break; + case INTEGER_VALUE_LIST: + collection.setList("fieldIntegerList", new RealmList<>(1)); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldIntegerList.size()); + assertEquals(Integer.valueOf(1), obj.fieldIntegerList.first()); + }); + break; + case LONG_VALUE_LIST: + collection.setList("fieldLongList", new RealmList<>(1L)); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldLongList.size()); + assertEquals(Long.valueOf((byte) 1), obj.fieldLongList.first()); + }); + break; + case FLOAT_VALUE_LIST: + collection.setList("fieldFloatList", new RealmList<>(1.1F)); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldFloatList.size()); + assertEquals(1.1F, obj.fieldFloatList.first(), 0F); + }); + break; + case DOUBLE_VALUE_LIST: + collection.setList("fieldDoubleList", new RealmList<>(1.1D)); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldDoubleList.size()); + assertEquals(1.1D, obj.fieldDoubleList.first(), 0F); + }); + break; + case BINARY_VALUE_LIST: + collection.setList("fieldBinaryList", new RealmList<>(new byte[] {1,2,3})); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldBinaryList.size()); + assertArrayEquals(new byte[] {1,2,3}, obj.fieldBinaryList.first()); + }); + break; + case DATE_VALUE_LIST: + collection.setList("fieldDateList", new RealmList<>(new Date(1000))); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldDateList.size()); + assertEquals(new Date(1000), obj.fieldDateList.first()); + }); + break; + default: + fail("Unknown type: " + type); + } + } + } + + @Test + public void setValue_specificType_internalNameOnDynamicRealms() { + populateMappedAllJavaTypes(5); + DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); + dynamicRealm.beginTransaction(); + try { + RealmResults collection = dynamicRealm.where("MappedAllJavaTypes").findAll(); + for (BulkSetMethods type : BulkSetMethods.values()) { + switch(type) { + case STRING: + collection.setString("field_string", "foo"); + assertElements(collection, obj -> assertEquals("foo", obj.getString("field_string"))); + break; + case BOOLEAN: + collection.setBoolean("field_boolean", true); + assertElements(collection, obj -> assertTrue(obj.getBoolean("field_boolean"))); + break; + case BYTE: + collection.setByte("field_byte", (byte) 1); + assertElements(collection, obj -> assertEquals((byte) 1, obj.getByte("field_byte"))); + break; + case SHORT: + collection.setShort("field_short", (short) 2); + assertElements(collection, obj -> assertEquals((short) 2, obj.getShort("field_short"))); + break; + case INTEGER: + collection.setInt("field_int", 3); + assertElements(collection, obj -> assertEquals(3, obj.getInt("field_int"))); + break; + case LONG: + collection.setLong("field_long", 4L); + assertElements(collection, obj -> assertEquals(4L, obj.getLong("field_long"))); + break; + case FLOAT: + collection.setFloat("field_float", 1.23F); + assertElements(collection, obj -> assertEquals(1.23F, obj.getFloat("field_float"), 0F)); + break; + case DOUBLE: + collection.setDouble("field_double", 1.234); + assertElements(collection, obj -> assertEquals(1.234, obj.getDouble("field_double"), 0F)); + break; + case BINARY: + collection.setBlob("field_binary", new byte[]{1,2,3}); + assertElements(collection, obj -> assertArrayEquals(new byte[]{1,2,3}, obj.getBlob("field_binary"))); + break; + case DATE: + collection.setDate("field_date", new Date(1000)); + assertElements(collection, obj -> assertEquals(new Date(1000), obj.getDate("field_date"))); + break; + case OBJECT: { + DynamicRealmObject childObj = dynamicRealm.createObject("MappedAllJavaTypes", 42); + collection.setObject("field_object", childObj); + assertElements(collection, obj -> assertEquals(childObj, obj.getObject("field_object"))); + break; + } + case MODEL_LIST: { + DynamicRealmObject childObj = dynamicRealm.createObject("MappedAllJavaTypes", 43); + collection.setList("field_list", new RealmList<>(childObj)); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_list"); + assertEquals(1, list.size()); + assertEquals(childObj, list.first()); + }); + break; + } + case STRING_VALUE_LIST: + collection.setList("field_string_list", new RealmList<>("Foo")); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_string_list", String.class); + assertEquals(1, list.size()); + assertEquals("Foo", list.first()); + }); + break; + case BOOLEAN_VALUE_LIST: + collection.setList("field_boolean_list", new RealmList<>(true)); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_boolean_list", Boolean.class); + assertEquals(1, list.size()); + assertEquals(true, list.first()); + }); + break; + case BYTE_VALUE_LIST: + collection.setList("field_byte_list", new RealmList<>((byte)1)); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_byte_list", Byte.class); + assertEquals(1, list.size()); + assertEquals(Byte.valueOf((byte) 1), list.first()); + }); + break; + case SHORT_VALUE_LIST: + collection.setList("field_short_list", new RealmList<>((short)1)); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_short_list", Short.class); + assertEquals(1, list.size()); + assertEquals(Short.valueOf((short) 1), list.first()); + }); + break; + case INTEGER_VALUE_LIST: + collection.setList("field_integer_list", new RealmList<>(1)); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_integer_list", Integer.class); + assertEquals(1, list.size()); + assertEquals(Integer.valueOf(1), list.first()); + }); + break; + case LONG_VALUE_LIST: + collection.setList("field_long_list", new RealmList<>(1L)); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_long_list", Long.class); + assertEquals(1, list.size()); + assertEquals(Long.valueOf((byte) 1), list.first()); + }); + break; + case FLOAT_VALUE_LIST: + collection.setList("field_float_list", new RealmList<>(1.1F)); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_float_list", Float.class); + assertEquals(1, list.size()); + assertEquals(1.1F, list.first(), 0F); + }); + break; + case DOUBLE_VALUE_LIST: + collection.setList("field_double_list", new RealmList<>(1.1D)); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_double_list", Double.class); + assertEquals(1, list.size()); + assertEquals(1.1D, list.first(), 0F); + }); + break; + case BINARY_VALUE_LIST: + collection.setList("field_binary_list", new RealmList<>(new byte[] {1,2,3})); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_binary_list", byte[].class); + assertEquals(1, list.size()); + assertArrayEquals(new byte[] {1,2,3}, list.first()); + }); + break; + case DATE_VALUE_LIST: + collection.setList("field_date_list", new RealmList<>(new Date(1000))); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_date_list", Date.class); + assertEquals(1, list.size()); + assertEquals(new Date(1000), list.first()); + }); + break; + default: + fail("Unknown type: " + type); + } + } + } finally { + dynamicRealm.close(); + } + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java new file mode 100644 index 0000000000..96c9843ad7 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import java.util.Date; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.Ignore; +import io.realm.annotations.Index; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; +import io.realm.annotations.RealmNamingPolicy; + + +@RealmClass(fieldNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) +public class MappedAllJavaTypes extends RealmObject { + + public static final String CLASS_NAME = "MappedAllJavaTypes"; + + @Ignore + public String fieldIgnored; + @Index + public String fieldString; + @PrimaryKey + public long fieldId; + public long fieldLong; + public short fieldShort; + public int fieldInt; + public byte fieldByte; + public float fieldFloat; + public double fieldDouble; + public boolean fieldBoolean; + public Date fieldDate; + public byte[] fieldBinary; + public MappedAllJavaTypes fieldObject; + public RealmList fieldList; + + public RealmList fieldStringList; + public RealmList fieldBinaryList; + public RealmList fieldBooleanList; + public RealmList fieldLongList; + public RealmList fieldIntegerList; + public RealmList fieldShortList; + public RealmList fieldByteList; + public RealmList fieldDoubleList; + public RealmList fieldFloatList; + public RealmList fieldDateList; + + public MappedAllJavaTypes() { + } + + public MappedAllJavaTypes(long fieldLong) { + this.fieldId = fieldLong; + this.fieldLong = fieldLong; + } +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index 47254d1d60..b8b4f19ec0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -22,6 +22,7 @@ #include #include "java_class_global_def.hpp" +#include "java_object_accessor.hpp" #include "java_query_descriptor.hpp" #include "observable_collection_wrapper.hpp" #include "util.hpp" @@ -323,6 +324,92 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeDeleteFirst(JN return JNI_FALSE; } +static inline void update_objects(JNIEnv* env, jlong results_ptr, jstring& j_field_name, JavaValue& value) { + try { + auto wrapper = reinterpret_cast(results_ptr); + JavaContext ctx(env, wrapper->collection().get_realm(), wrapper->collection().get_object_schema()); + JStringAccessor prop_name(env, j_field_name); + wrapper->collection().set_property_value(ctx, prop_name, value); + } + CATCH_STD() +} + + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetNull(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name) +{ + TR_ENTER_PTR(native_ptr) + auto value = JavaValue(); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetBoolean(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jboolean j_value) +{ + TR_ENTER_PTR(native_ptr) + JavaValue value(j_value); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetInt(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong j_value) +{ + TR_ENTER_PTR(native_ptr) + JavaValue value(j_value); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetFloat(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jfloat j_value) +{ + TR_ENTER_PTR(native_ptr) + JavaValue value(j_value); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetDouble(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jdouble j_value) +{ + TR_ENTER_PTR(native_ptr) + JavaValue value(j_value); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetString(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jstring j_value) +{ + TR_ENTER_PTR(native_ptr) + JStringAccessor str(env, j_value); + JavaValue value = str.is_null() ? JavaValue() : JavaValue(std::string(str)); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetBinary(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jbyteArray j_value) +{ + TR_ENTER_PTR(native_ptr) + auto data = OwnedBinaryData(JByteArrayAccessor(env, j_value).transform()); + JavaValue value(data); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetTimestamp(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong j_value) +{ + TR_ENTER_PTR(native_ptr) + JavaValue value(from_milliseconds(j_value)); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetObject(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong row_ptr) +{ + TR_ENTER_PTR(native_ptr) + JavaValue value(reinterpret_cast(row_ptr)); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetList(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong builder_ptr) +{ + // OsObjectBuilder has been used to build up the list we want to insert. This means the + // fake object described by the OsObjectBuilder only contains one property, namely the list we + // want to insert and this list is assumed to be at index = 0. + std::vector builder = *reinterpret_cast*>(builder_ptr); + REALM_ASSERT_DEBUG(builder.size() == 1); + update_objects(env, native_ptr, j_field_name, builder[0]); +} + JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeDelete(JNIEnv* env, jclass, jlong native_ptr, jlong index) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 0698d6571d..3f80013741 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -16,30 +16,40 @@ package io.realm; - import android.annotation.SuppressLint; import android.os.Looper; +import java.util.Date; +import java.util.Iterator; +import java.util.Locale; + import javax.annotation.Nullable; import io.reactivex.Flowable; import io.reactivex.Observable; import io.realm.internal.CheckedRow; +import io.realm.internal.ColumnInfo; +import io.realm.internal.OsList; import io.realm.internal.OsResults; +import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; +import io.realm.internal.Util; +import io.realm.internal.android.JsonUtils; import io.realm.log.RealmLog; import io.realm.rx.CollectionChange; +import static io.realm.RealmFieldType.LIST; + /** * This class holds all the matches of a {@link RealmQuery} for a given Realm. The objects are not copied from * the Realm to the RealmResults list, but are just referenced from the RealmResult instead. This saves memory and * increases speed. *

            * RealmResults are live views, which means that if it is on an {@link Looper} thread, it will automatically - * update its query results after a transaction has been committed. If on a non-looper thread, {@link Realm#waitForChange()} - * must be called to update the results. + * update its query results after a transaction has been committed. If on a non-looper thread, + * {@link Realm#refresh()} must be called to update the results. *

            * Updates to RealmObjects from a RealmResults list must be done from within a transaction and the modified objects are * persisted to the Realm file during the commit of the transaction. @@ -136,6 +146,403 @@ public boolean load() { return true; } + + /** + * Updates the field given by {@code fieldName} in all objects inside the query result. + *

            + * This method will automatically try to convert numbers and booleans that are given as + * {@code String} to their appropriate type. For example {@code "10"} will be converted to + * {@code 10} if the field type is {@link RealmFieldType#INTEGER}. + *

            + * Using the typed setters like {@link #setInt(String, int)} will be faster than using + * this method. + * + * @param fieldName field to update + * @param value value to update with. + * @throws IllegalArgumentException if the field could not be found, could not be updated or + * the argument didn't match the field type or could not be converted to match the underlying + * field type. + */ + public void setValue(String fieldName, @Nullable Object value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + boolean isString = (value instanceof String); + String strValue = isString ? (String) value : null; + + String className = osResults.getTable().getClassName(); + RealmObjectSchema schema = getRealm().getSchema().get(className); + if (!schema.hasField(fieldName)) { + throw new IllegalArgumentException(String.format("Field '%s' could not be found in class '%s'", fieldName, className)); + } + + // null values exit early + if (value == null) { + osResults.setNull(fieldName); + return; + } + + // Does implicit conversion if needed. + RealmFieldType type = schema.getFieldType(fieldName); + if (isString && type != RealmFieldType.STRING) { + switch (type) { + case BOOLEAN: + value = Boolean.parseBoolean(strValue); + break; + case INTEGER: + value = Long.parseLong(strValue); + break; + case FLOAT: + value = Float.parseFloat(strValue); + break; + case DOUBLE: + value = Double.parseDouble(strValue); + break; + case DATE: + value = JsonUtils.stringToDate(strValue); + break; + default: + throw new IllegalArgumentException(String.format(Locale.US, + "Field %s is not a String field, " + + "and the provide value could not be automatically converted: %s. Use a typed" + + "setter instead", fieldName, value)); + } + } + + //noinspection ConstantConditions + Class valueClass = value.getClass(); + if (valueClass == Boolean.class) { + setBoolean(fieldName, (Boolean) value); + } else if (valueClass == Short.class) { + setShort(fieldName, (Short) value); + } else if (valueClass == Integer.class) { + setInt(fieldName, (Integer) value); + } else if (valueClass == Long.class) { + setLong(fieldName, (Long) value); + } else if (valueClass == Byte.class) { + setByte(fieldName, (Byte) value); + } else if (valueClass == Float.class) { + setFloat(fieldName, (Float) value); + } else if (valueClass == Double.class) { + setDouble(fieldName, (Double) value); + } else if (valueClass == String.class) { + //noinspection ConstantConditions + setString(fieldName, (String) value); + } else if (value instanceof Date) { + setDate(fieldName, (Date) value); + } else if (value instanceof byte[]) { + setBlob(fieldName, (byte[]) value); + } else if (value instanceof RealmModel) { + setObject(fieldName, (RealmModel) value); + } else if (valueClass == RealmList.class) { + RealmList list = (RealmList) value; + setList(fieldName, list); + } else { + throw new IllegalArgumentException("Value is of a type not supported: " + value.getClass()); + } + } + + /** + * Sets the value to {@code null} for the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @throws IllegalArgumentException if field name doesn't exist or is a primary key property. + * @throws IllegalStateException if the field cannot hold {@code null} values. + */ + public void setNull(String fieldName) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + osResults.setNull(fieldName); + } + + /** + * Sets the {@code boolean} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a boolean field. + */ + public void setBoolean(String fieldName, boolean value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.BOOLEAN); + osResults.setBoolean(fieldName, value); + } + + /** + * Sets the {@code byte} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a byte field. + */ + public void setByte(String fieldName, byte value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.INTEGER); + osResults.setInt(fieldName, value); + } + + /** + * Sets the {@code short} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a short field. + */ + public void setShort(String fieldName, short value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.INTEGER); + osResults.setInt(fieldName, value); + } + + /** + * Sets the {@code int} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't an integer field. + */ + public void setInt(String fieldName, int value) { + checkNonEmptyFieldName(fieldName); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.INTEGER); + realm.checkIfValidAndInTransaction(); + osResults.setInt(fieldName, value); + } + + /** + * Sets the {@code long} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a long field. + */ + public void setLong(String fieldName, long value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.INTEGER); + osResults.setInt(fieldName, value); + } + + /** + * Sets the {@code float} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a float field. + */ + public void setFloat(String fieldName, float value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.FLOAT); + osResults.setFloat(fieldName, value); + } + + /** + * Sets the {@code double} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a double field. + */ + public void setDouble(String fieldName, double value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.DOUBLE); + osResults.setDouble(fieldName, value); + } + + /** + * Sets the {@code String} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a String field. + */ + public void setString(String fieldName, @Nullable String value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.STRING); + osResults.setString(fieldName, value); + } + + /** + * Sets the binary value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a binary field. + */ + public void setBlob(String fieldName, @Nullable byte[] value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.BINARY); + osResults.setBlob(fieldName, value); + } + + /** + * Sets the {@code Date} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a date field. + */ + public void setDate(String fieldName, @Nullable Date value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.DATE); + osResults.setDate(fieldName, value); + } + + /** + * Sets a reference to another object on the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new object referenced by this field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't an Object reference field. + */ + public void setObject(String fieldName, @Nullable RealmModel value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.OBJECT); + Row row = checkRealmObjectConstraints(fieldName, value); + osResults.setObject(fieldName, row); + } + + private Row checkRealmObjectConstraints(String fieldName, @Nullable RealmModel value) { + if (value != null) { + if (!(RealmObject.isManaged(value) && RealmObject.isValid(value))) { + throw new IllegalArgumentException("'value' is not a valid, managed Realm object."); + } + ProxyState proxyState = ((RealmObjectProxy) value).realmGet$proxyState(); + if (!proxyState.getRealm$realm().getPath().equals(realm.getPath())) { + throw new IllegalArgumentException("'value' does not belong to the same Realm as the RealmResults."); + } + + // Check that type matches the expected one + Table currentTable = osResults.getTable(); + long columnIndex = currentTable.getColumnIndex(fieldName); + Table expectedTable = currentTable.getLinkTarget(columnIndex); + Table inputTable = proxyState.getRow$realm().getTable(); + if (!expectedTable.hasSameSchema(inputTable)) { + throw new IllegalArgumentException(String.format(Locale.US, + "Type of object is wrong. Was '%s', expected '%s'", + inputTable.getClassName(), expectedTable.getClassName())); + } + return proxyState.getRow$realm(); + } + + return null; + } + + /** + * Replaces the RealmList at the given field on all objects in this collection. + * + * + * @param fieldName name of the field to update. + * @param list new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, isn't a RealmList field , if the + * objects in the list are not managed or the type of the objects in the list are wrong. + */ + @SuppressWarnings("unchecked") + public void setList(String fieldName, RealmList list) { + checkNonEmptyFieldName(fieldName); + fieldName = mapFieldNameToInternalName(fieldName); + realm.checkIfValidAndInTransaction(); + + //noinspection ConstantConditions + if (list == null) { + throw new IllegalArgumentException("Non-null 'list' required"); + } + + // Due to type erasure of generics it is not possible to have multiple overloaded methods with the same signature. + // So instead we fake it by checking the first element in the list and verifies that + // against the underlying type. + RealmFieldType columnType = realm.getSchema().getSchemaForClass(osResults.getTable().getClassName()).getFieldType(fieldName); + switch (columnType) { + case LIST: + checkTypeOfListElements(list, RealmModel.class); + checkRealmObjectConstraints(fieldName, (RealmModel) list.first(null)); + osResults.setModelList(fieldName, (RealmList) list); + break; + case INTEGER_LIST: + // Integers are a bit annoying as they are all stored as the same type in Core + // but the Java type system cannot seamlessly translate between e.g Short and Long. + Class listType = getListType(list); + if (listType.equals(Integer.class)) { + osResults.setIntegerList(fieldName, (RealmList) list); + } else if (listType.equals(Long.class)) { + osResults.setLongList(fieldName, (RealmList) list); + } else if (listType.equals(Short.class)) { + osResults.setShortList(fieldName, (RealmList) list); + } else if (listType.equals(Byte.class)) { + osResults.setByteList(fieldName, (RealmList) list); + } else { + throw new IllegalArgumentException(String.format("List contained the wrong type of elements. " + + "Elements that can be mapped to Integers was expected, but the actual type is '%s'", + listType)); + } + break; + case BOOLEAN_LIST: + checkTypeOfListElements(list, Boolean.class); + osResults.setBooleanList(fieldName, (RealmList) list); + break; + case STRING_LIST: + checkTypeOfListElements(list, String.class); + osResults.setStringList(fieldName, (RealmList) list); + break; + case BINARY_LIST: + checkTypeOfListElements(list, byte[].class); + osResults.setByteArrayList(fieldName, (RealmList) list); + break; + case DATE_LIST: + checkTypeOfListElements(list, Date.class); + osResults.setDateList(fieldName, (RealmList) list); + break; + case FLOAT_LIST: + checkTypeOfListElements(list, Float.class); + osResults.setFloatList(fieldName, (RealmList) list); + break; + case DOUBLE_LIST: + checkTypeOfListElements(list, Double.class); + osResults.setDoubleList(fieldName, (RealmList) list); + break; + default: + throw new IllegalArgumentException(String.format("Field '%s' is not a list but a %s", fieldName, columnType)); + } + } + + private Class getListType(RealmList list) { + if (!list.isEmpty()) { + return list.first().getClass(); + } else { + return Long.class; // Any valid type that maps to INTEGER will do. + } + } + + private void checkTypeOfListElements(RealmList list, Class clazz) { + if (!list.isEmpty()) { + T element = list.first(); + Class elementType = element.getClass(); + if (!(clazz.isAssignableFrom(elementType))) { + throw new IllegalArgumentException(String.format("List contained the wrong type of elements. Elements of type '%s' was " + + "expected, but the actual type is '%s'", clazz, elementType)); + } + } + } + /** * Adds a change listener to this {@link RealmResults}. *

            @@ -290,7 +697,8 @@ public void removeChangeListener(OrderedRealmCollectionChangeListenerRxJava and Realm @@ -299,7 +707,9 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener> asFlowable() { if (realm instanceof Realm) { return realm.configuration.getRxFactory().from((Realm) realm, this); - } else if (realm instanceof DynamicRealm) { + } + + if (realm instanceof DynamicRealm) { DynamicRealm dynamicRealm = (DynamicRealm) realm; RealmResults dynamicResults = (RealmResults) this; @SuppressWarnings("UnnecessaryLocalVariable") @@ -338,4 +748,39 @@ public Observable>> asChangesetObservable() { throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2."); } } + + private void checkNonEmptyFieldName(String fieldName) { + if (Util.isEmptyString(fieldName)) { + throw new IllegalArgumentException("Non-empty 'fieldname' required."); + } + } + + private void checkNotNull(@Nullable Object value) { + if (value == null) { + throw new IllegalArgumentException("Non-null 'value' required. Use 'setNull(fieldName)' instead."); + } + } + + private void checkType(String fieldName, RealmFieldType expectedFieldType) { + String className = osResults.getTable().getClassName(); + RealmFieldType fieldType = realm.getSchema().get(className).getFieldType(fieldName); + if (fieldType != expectedFieldType) { + throw new IllegalArgumentException(String.format("The field '%s.%s' is not of the expected type. " + + "Actual: %s, Expected: %s", className, fieldName, fieldType, expectedFieldType)); + } + } + + private String mapFieldNameToInternalName(String fieldName) { + if (realm instanceof Realm) { + // We only need to map field names from typed Realms. + String className = osResults.getTable().getClassName(); + String mappedFieldName = realm.getSchema().getColumnInfo(className).getInternalFieldName(fieldName); + if (mappedFieldName == null) { + throw new IllegalArgumentException(String.format("Field '%s' does not exists.", fieldName)); + } else { + fieldName = mappedFieldName; + } + } + return fieldName; + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java index 3c88cd5f3b..f682e14c1e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java @@ -92,6 +92,7 @@ public String toString() { private final Map indicesFromJavaFieldNames; private final Map indicesFromColumnNames; + private final Map javaFieldNameToInternalNames; private final boolean mutable; /** @@ -120,6 +121,7 @@ protected ColumnInfo(@Nullable ColumnInfo src, boolean mutable) { private ColumnInfo(int mapSize, boolean mutable) { this.indicesFromJavaFieldNames = new HashMap<>(mapSize); this.indicesFromColumnNames = new HashMap<>(mapSize); + this.javaFieldNameToInternalNames = new HashMap<>(mapSize); this.mutable = mutable; } @@ -152,6 +154,16 @@ public ColumnDetails getColumnDetails(String javaFieldName) { return indicesFromJavaFieldNames.get(javaFieldName); } + /** + * Returns the internal field name that corresponds to the name found in the Java model class. + * @param javaFieldName the field name in the Java model class. + * @return the internal field name or {@code null} if the java name doesn't exists. + */ + @Nullable + public String getInternalFieldName(String javaFieldName) { + return javaFieldNameToInternalNames.get(javaFieldName); + } + /** * Makes this ColumnInfo an exact copy of {@code src}. * @@ -171,6 +183,8 @@ public void copyFrom(ColumnInfo src) { indicesFromJavaFieldNames.putAll(src.indicesFromJavaFieldNames); indicesFromColumnNames.clear(); indicesFromColumnNames.putAll(src.indicesFromColumnNames); + javaFieldNameToInternalNames.clear(); + javaFieldNameToInternalNames.putAll(src.javaFieldNameToInternalNames); copy(src, this); } @@ -238,6 +252,7 @@ protected final long addColumnDetails(String javaFieldName, String internalColum ColumnDetails cd = new ColumnDetails(property); indicesFromJavaFieldNames.put(javaFieldName, cd); indicesFromColumnNames.put(internalColumnName, cd); + javaFieldNameToInternalNames.put(javaFieldName, internalColumnName); return property.getColumnIndex(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java b/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java index be40d7ac3f..089ffa5e01 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java +++ b/realm/realm-library/src/main/java/io/realm/internal/NativeContext.java @@ -30,14 +30,14 @@ public class NativeContext { private static final ReferenceQueue referenceQueue = new ReferenceQueue(); private static final Thread finalizingThread = new Thread(new FinalizerRunnable(referenceQueue)); // Dummy context which will be used by native objects which's destructors are always thread safe. - static final NativeContext dummyContext = new NativeContext(); + public static final NativeContext dummyContext = new NativeContext(); static { finalizingThread.setName("RealmFinalizingDaemon"); finalizingThread.start(); } - void addReference(NativeObject referent) { + public void addReference(NativeObject referent) { new NativeObjectReference(this, referent, referenceQueue); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 56701527d6..590f65dba6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -16,16 +16,21 @@ package io.realm.internal; +import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.NoSuchElementException; import javax.annotation.Nullable; +import io.realm.MutableRealmInteger; import io.realm.OrderedRealmCollectionChangeListener; import io.realm.RealmChangeListener; +import io.realm.RealmList; +import io.realm.RealmModel; import io.realm.internal.core.DescriptorOrdering; import io.realm.internal.core.QueryDescriptor; +import io.realm.internal.objectstore.OsObjectBuilder; /** @@ -395,6 +400,144 @@ public boolean deleteLast() { return nativeDeleteLast(nativePtr); } + public void setNull(String fieldName) { + nativeSetNull(nativePtr, fieldName); + } + + public void setBoolean(String fieldName, boolean value) { + nativeSetBoolean(nativePtr, fieldName, value); + } + + public void setInt(String fieldName, long value) { + nativeSetInt(nativePtr, fieldName, value); + } + + public void setFloat(String fieldName, float value) { + nativeSetFloat(nativePtr, fieldName, value); + } + + public void setDouble(String fieldName, double value) { + nativeSetDouble(nativePtr, fieldName, value); + } + + public void setString(String fieldName, @Nullable String value) { + nativeSetString(nativePtr, fieldName, value); + } + + public void setBlob(String fieldName, @Nullable byte[] value) { + nativeSetBinary(nativePtr, fieldName, value); + } + + public void setDate(String fieldName, @Nullable Date timestamp) { + if (timestamp == null) { + nativeSetNull(nativePtr, fieldName); + } else { + nativeSetTimestamp(nativePtr, fieldName, timestamp.getTime()); + } + } + + public void setObject(String fieldName, @Nullable Row row) { + if (row == null) { + setNull(fieldName); + } else { + long rowPtr; + if (row instanceof UncheckedRow) { + // Normal Realms + rowPtr = ((UncheckedRow) row).getNativePtr(); + } else if (row instanceof CheckedRow) { + // Dynamic Realms + rowPtr = ((CheckedRow) row).getNativePtr(); + } else { + // Should never happen, but just in case. + throw new UnsupportedOperationException("Unsupported Row type: " + row.getClass().getCanonicalName()); + } + nativeSetObject(nativePtr, fieldName, rowPtr); + } + } + + // Interface wrapping adding the specific list type + private interface AddListTypeDelegate { + void addList(OsObjectBuilder builder, RealmList list); + } + + // Helper method for adding specific types of lists. + private void addTypeSpecificList(String fieldName, RealmList list, AddListTypeDelegate delegate) { + //noinspection unchecked + OsObjectBuilder builder = new OsObjectBuilder(getTable(), 0, Collections.EMPTY_SET); + delegate.addList(builder, list); + try { + nativeSetList(nativePtr, fieldName, builder.getNativePtr()); + } finally { + builder.close(); + } + } + + public void setStringList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addStringList(0, lst); + }); + } + + public void setByteList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addByteList(0, lst); + }); + } + + public void setShortList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addShortList(0, lst); + }); + } + + public void setIntegerList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addIntegerList(0, lst); + }); + } + + public void setLongList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addLongList(0, lst); + }); + } + + public void setBooleanList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addBooleanList(0, lst); + }); + } + + public void setByteArrayList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addByteArrayList(0, lst); + }); + } + + public void setDateList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addDateList(0, lst); + }); + } + + public void setFloatList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addFloatList(0, lst); + }); + } + + public void setDoubleList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addDoubleList(0, lst); + }); + } + + public void setModelList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, (builder, lst) -> { + builder.addObjectList(0, lst); + }); + } + public void addListener(T observer, OrderedRealmCollectionChangeListener listener) { if (observerPairs.isEmpty()) { nativeStartListening(nativePtr); @@ -503,6 +646,26 @@ public void load() { private static native void nativeDelete(long nativePtr, long index); + private static native void nativeSetNull(long nativePtr, String fieldName); + + private static native void nativeSetBoolean(long nativePtr, String fieldName, boolean value); + + private static native void nativeSetInt(long nativePtr, String fieldName, long value); + + private static native void nativeSetFloat(long nativePtr, String fieldName, float value); + + private static native void nativeSetDouble(long nativePtr, String fieldName, double value); + + private static native void nativeSetString(long nativePtr, String fieldName, @Nullable String value); + + private static native void nativeSetBinary(long nativePtr, String fieldName, @Nullable byte[] value); + + private static native void nativeSetTimestamp(long nativePtr, String fieldName, long value); + + private static native void nativeSetObject(long nativePtr, String fieldName, long rowNativePtr); + + private static native void nativeSetList(long nativePtr, String fieldName, long builderNativePtr); + // Non-static, we need this OsResults object in JNI. private native void nativeStartListening(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java index a5ed0f3c82..069ad20d85 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java @@ -15,6 +15,7 @@ */ package io.realm.internal.objectstore; +import java.io.Closeable; import java.util.Date; import java.util.List; import java.util.Set; @@ -58,7 +59,7 @@ * that can guide any architectural design and the only way to really find out is to build out each * solution and benchmark it. */ -public class OsObjectBuilder { +public class OsObjectBuilder implements Closeable { private final Table table; private final long sharedRealmPtr; @@ -356,25 +357,50 @@ private void addEmptyList(long columnIndex) { nativeStopList(builderPtr, columnIndex, listPtr); } + /** + * Updates any existing object if it exists, otherwise creates a new one. + * + * The builder is automatically closed after calling this method. + */ public void updateExistingObject() { try { nativeCreateOrUpdate(sharedRealmPtr, tablePtr, builderPtr, true, ignoreFieldsWithSameValue); } finally { - nativeDestroyBuilder(builderPtr); + close(); } } + /** + * Create a new object. + * + * The builder is automatically closed after calling this method. + */ public UncheckedRow createNewObject() { UncheckedRow row; try { long rowPtr = nativeCreateOrUpdate(sharedRealmPtr, tablePtr, builderPtr, false, false); row = new UncheckedRow(context, table, rowPtr); } finally { - nativeDestroyBuilder(builderPtr); + close(); } return row; } + /** + * Returns the underlying native pointer representing this builder. + */ + public long getNativePtr() { + return builderPtr; + } + + /** + * Manually closes the underlying Builder + */ + @Override + public void close() { + nativeDestroyBuilder(builderPtr); + } + private interface ItemCallback { void handleItem(long listPtr, T item); } From c1bdaee5a0dfcd3d00a95956c5ffefb016b277c9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 5 Nov 2018 16:32:48 +0100 Subject: [PATCH 1337/2110] Added missing changelog entry --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d4aabea9..d8b9721c92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,14 +21,15 @@ This release also contains all changes from 5.8.0-BETA1 and 5.8.0-BETA2. ### Enhancements * [ObjectServer] Added Subscription class available to Query-based Realms. This exposes a Subscription more directly. This class is in beta. [#6231](https://github.com/realm/realm-java/pull/6231). -* [ObjectServer] Added `Realm.getSubscriptions()`, `Realm.getSubscriptions(String pattern)` and `Realm.getSubscription` to make it easier to find existing subscriptions. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). -* [ObjectServer] Added `RealmQuery.subscribe()` and `RealmQuery.subscribe(String name)` to subscribe immediately inside a transaction. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). -* [ObjectServer] Added support for subscribing directly inside `SyncConfiguration.initialData()`. This can be coupled with `SyncConfiguration.waitForInitialRemoteData()` in order to block a Realm from opening until the initial subscriptions are ready and have downloaded data. This API are in beta. [#6231](https://github.com/realm/realm-java/pull/6231). +* [ObjectServer] Added `Realm.getSubscriptions()`, `Realm.getSubscriptions(String pattern)` and `Realm.getSubscription` to make it easier to find existing subscriptions. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) +* [ObjectServer] Added `RealmQuery.subscribe()` and `RealmQuery.subscribe(String name)` to subscribe immediately inside a transaction. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) +* [ObjectServer] Added support for subscribing directly inside `SyncConfiguration.initialData()`. This can be coupled with `SyncConfiguration.waitForInitialRemoteData()` in order to block a Realm from opening until the initial subscriptions are ready and have downloaded data. This API are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) * [ObjectServer] Improved performance when merging changes from the server. * [ObjectServer] Added support for timeouts when uploading or downloading data manually using `SyncSession.downloadAllServerChanges(long timeout, TimeUnit unit)` and `SyncSession.uploadAllLocalChanges(long timeout, TimeUnit unit)`. [#6073](https://github.com/realm/realm-java/pull/6073) * [ObjectServer] Added support for timing out when downloading initial data for synchronized Realms using `SyncConfiguration.waitForInitialRemoteData(long timeout, TimeUnit unit)`. [#6247](https://github.com/realm/realm-java/issues/6247) -* Added support for `ImportFlag`s to `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate()`. This makes it possible to choose a mode so only fields that actually changed are written to disk. This improves notifications and Object Server performance. [#6224](https://github.com/realm/realm-java/pull/6224). -* Added support for bulk updating the same property in all objects that are part of a query result using `RealmResults.setValue(String fieldName, Object value)` or one of the specialized overrides that have been added for all supported types, e.g. `RealmResults.setString(String fieldName, String value)` [#762](https://github.com/realm/realm-java/issues/762). +* [ObjectServer] Added `Realm.init(Context, String)` which defines a custom User-Agent String sent to the Realm Object Server when a session is created. Using this requires Realm Object Server 3.12.4 or later. [#6267](https://github.com/realm/realm-java/issues/6267) +* Added support for `ImportFlag`s to `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate()`. This makes it possible to choose a mode so only fields that actually changed are written to disk. This improves notifications and Object Server performance. [#6224](https://github.com/realm/realm-java/pull/6224) +* Added support for bulk updating the same property in all objects that are part of a query result using `RealmResults.setValue(String fieldName, Object value)` or one of the specialized overrides that have been added for all supported types, e.g. `RealmResults.setString(String fieldName, String value)`. [#762](https://github.com/realm/realm-java/issues/762) ### Fixed * All known bugs introduced in 5.8.0-BETA1 and 5.8.0-BETA2. See the release notes for these releases. From 8eb8e966ef44b1fb54d6f96e1ac2c306ed141408 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Nov 2018 07:34:33 +0100 Subject: [PATCH 1338/2110] Fix Gradle cache not working in parallel builds --- Jenkinsfile | 98 ++++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e65e707d01..800b636d0c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -59,52 +59,56 @@ try { "-v ${env.HOME}/ccache:/tmp/.ccache " + "-e REALM_CORE_DOWNLOAD_DIR=/tmp/.gradle " + "--network container:${rosContainer.id}") { - stage('JVM tests') { - try { - withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" + + // Lock required around all usages of Gradle as it isn't + // able to share its cache between builds. + lock("${env.NODE_NAME}-android") { + + stage('JVM tests') { + try { + withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { + sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" + } + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) } - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) } - } - stage('Gradle plugin tests') { - try { - gradle('gradle-plugin', 'check') - } finally { - storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + stage('Gradle plugin tests') { + try { + gradle('gradle-plugin', 'check') + } finally { + storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + } } - } - stage('Realm Transformer tests') { - try { - gradle('realm-transformer', 'check') - } finally { - storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + stage('Realm Transformer tests') { + try { + gradle('realm-transformer', 'check') + } finally { + storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + } } - } - stage('Static code analysis') { - try { - gradle('realm', "findbugs pmd checkstyle ${abiFilter}") - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) - step([$class: 'CheckStylePublisher', - canComputeNew: false, - defaultEncoding: '', - healthy: '', - pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - unHealthy: '' - ]) + stage('Static code analysis') { + try { + gradle('realm', "findbugs pmd checkstyle ${abiFilter}") + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) + step([$class: 'CheckStylePublisher', + canComputeNew: false, + defaultEncoding: '', + healthy: '', + pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', + unHealthy: '' + ]) + } } - } - stage('Run instrumented tests') { - lock("${env.NODE_NAME}-android") { + stage('Run instrumented tests') { String backgroundPid try { backgroundPid = startLogCatCollector() @@ -116,20 +120,20 @@ try { storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' } } - } - // TODO: add support for running monkey on the example apps + // TODO: add support for running monkey on the example apps - if (['master'].contains(env.BRANCH_NAME)) { - stage('Collect metrics') { - collectAarMetrics() + if (['master'].contains(env.BRANCH_NAME)) { + stage('Collect metrics') { + collectAarMetrics() + } } - } - if (['master', 'next-major'].contains(env.BRANCH_NAME)) { - stage('Publish to OJO') { - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + if (['master', 'next-major'].contains(env.BRANCH_NAME)) { + stage('Publish to OJO') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + } } } } From f282fcbaed4047be5e7e282e98c74d4a3ee60682 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Nov 2018 08:47:39 +0100 Subject: [PATCH 1339/2110] Fix Gradle cache not working in parallel builds (#6285) --- Jenkinsfile | 98 ++++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e65e707d01..800b636d0c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -59,52 +59,56 @@ try { "-v ${env.HOME}/ccache:/tmp/.ccache " + "-e REALM_CORE_DOWNLOAD_DIR=/tmp/.gradle " + "--network container:${rosContainer.id}") { - stage('JVM tests') { - try { - withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" + + // Lock required around all usages of Gradle as it isn't + // able to share its cache between builds. + lock("${env.NODE_NAME}-android") { + + stage('JVM tests') { + try { + withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { + sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" + } + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) } - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) } - } - stage('Gradle plugin tests') { - try { - gradle('gradle-plugin', 'check') - } finally { - storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + stage('Gradle plugin tests') { + try { + gradle('gradle-plugin', 'check') + } finally { + storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + } } - } - stage('Realm Transformer tests') { - try { - gradle('realm-transformer', 'check') - } finally { - storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + stage('Realm Transformer tests') { + try { + gradle('realm-transformer', 'check') + } finally { + storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + } } - } - stage('Static code analysis') { - try { - gradle('realm', "findbugs pmd checkstyle ${abiFilter}") - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) - step([$class: 'CheckStylePublisher', - canComputeNew: false, - defaultEncoding: '', - healthy: '', - pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - unHealthy: '' - ]) + stage('Static code analysis') { + try { + gradle('realm', "findbugs pmd checkstyle ${abiFilter}") + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) + step([$class: 'CheckStylePublisher', + canComputeNew: false, + defaultEncoding: '', + healthy: '', + pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', + unHealthy: '' + ]) + } } - } - stage('Run instrumented tests') { - lock("${env.NODE_NAME}-android") { + stage('Run instrumented tests') { String backgroundPid try { backgroundPid = startLogCatCollector() @@ -116,20 +120,20 @@ try { storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' } } - } - // TODO: add support for running monkey on the example apps + // TODO: add support for running monkey on the example apps - if (['master'].contains(env.BRANCH_NAME)) { - stage('Collect metrics') { - collectAarMetrics() + if (['master'].contains(env.BRANCH_NAME)) { + stage('Collect metrics') { + collectAarMetrics() + } } - } - if (['master', 'next-major'].contains(env.BRANCH_NAME)) { - stage('Publish to OJO') { - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + if (['master', 'next-major'].contains(env.BRANCH_NAME)) { + stage('Publish to OJO') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + } } } } From dc14781a9d6cd263eedab724c01b584dc627c1eb Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Nov 2018 12:07:17 +0100 Subject: [PATCH 1340/2110] Transformer strips similar named methods (#6286) --- realm/realm-library/src/main/java/io/realm/Realm.java | 2 +- .../src/main/java/io/realm/internal/ObjectServerFacade.java | 2 +- .../java/io/realm/internal/SyncObjectServerFacade.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 1dd7a7315b..f91b89788e 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -321,7 +321,7 @@ private static void initializeRealm(Context context, String userAgent) { checkFilesDirAvailable(context); RealmCore.loadLibrary(context); setDefaultConfiguration(new RealmConfiguration.Builder(context).build()); - ObjectServerFacade.getSyncFacadeIfPossible().init(context, userAgent); + ObjectServerFacade.getSyncFacadeIfPossible().initialize(context, userAgent); if (context.getApplicationContext() != null) { BaseRealm.applicationContext = context.getApplicationContext(); } else { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index a1df724e0d..51fc48ad73 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -56,7 +56,7 @@ public class ObjectServerFacade { /** * Initializes the Object Server library */ - public void init(Context context, String userAgent) { + public void initialize(Context context, String userAgent) { } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index c9febaefc9..8663635ab7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -51,7 +51,7 @@ public class SyncObjectServerFacade extends ObjectServerFacade { private static volatile Method removeSessionMethod; @Override - public void init(Context context, String userAgent) { + public void initialize(Context context, String userAgent) { // Trying to keep things out the public API is no fun :/ // Just use reflection on init. It is a one-time method call so should be acceptable. //noinspection TryWithIdenticalCatches From 0149bd13d393ee82720308cd2062c30dc3f5937c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Nov 2018 13:07:25 +0100 Subject: [PATCH 1341/2110] Add release date --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8b9721c92..8983a736d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,15 +15,15 @@ * None -## 5.8.0 (YYYY-MM-DD) +## 5.8.0 (2018-11-06) This release also contains all changes from 5.8.0-BETA1 and 5.8.0-BETA2. ### Enhancements * [ObjectServer] Added Subscription class available to Query-based Realms. This exposes a Subscription more directly. This class is in beta. [#6231](https://github.com/realm/realm-java/pull/6231). -* [ObjectServer] Added `Realm.getSubscriptions()`, `Realm.getSubscriptions(String pattern)` and `Realm.getSubscription` to make it easier to find existing subscriptions. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) -* [ObjectServer] Added `RealmQuery.subscribe()` and `RealmQuery.subscribe(String name)` to subscribe immediately inside a transaction. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) -* [ObjectServer] Added support for subscribing directly inside `SyncConfiguration.initialData()`. This can be coupled with `SyncConfiguration.waitForInitialRemoteData()` in order to block a Realm from opening until the initial subscriptions are ready and have downloaded data. This API are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) + * [ObjectServer] Added `Realm.getSubscriptions()`, `Realm.getSubscriptions(String pattern)` and `Realm.getSubscription` to make it easier to find existing subscriptions. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) + * [ObjectServer] Added `RealmQuery.subscribe()` and `RealmQuery.subscribe(String name)` to subscribe immediately inside a transaction. These API's are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) + * [ObjectServer] Added support for subscribing directly inside `SyncConfiguration.initialData()`. This can be coupled with `SyncConfiguration.waitForInitialRemoteData()` in order to block a Realm from opening until the initial subscriptions are ready and have downloaded data. This API are in beta. [#6231](https://github.com/realm/realm-java/pull/6231) * [ObjectServer] Improved performance when merging changes from the server. * [ObjectServer] Added support for timeouts when uploading or downloading data manually using `SyncSession.downloadAllServerChanges(long timeout, TimeUnit unit)` and `SyncSession.uploadAllLocalChanges(long timeout, TimeUnit unit)`. [#6073](https://github.com/realm/realm-java/pull/6073) * [ObjectServer] Added support for timing out when downloading initial data for synchronized Realms using `SyncConfiguration.waitForInitialRemoteData(long timeout, TimeUnit unit)`. [#6247](https://github.com/realm/realm-java/issues/6247) From af96a6d6fc2480e0de5d9f65f96e75e13309d77c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Nov 2018 13:07:53 +0100 Subject: [PATCH 1342/2110] Release v5.8.0 --- version.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/version.txt b/version.txt index 6ef65ad751..edb1d397cf 100644 --- a/version.txt +++ b/version.txt @@ -1,2 +1 @@ -5.8.0-SNAPSHOT - +5.8.0 \ No newline at end of file From cdda26c30decf36cf196bc6dbe6785b740566be2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Nov 2018 13:07:53 +0100 Subject: [PATCH 1343/2110] Prepare next release v5.8.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index edb1d397cf..f678e79be4 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.8.0 \ No newline at end of file +5.8.1-SNAPSHOT \ No newline at end of file From 6ac1cb44d230e760c3c3b6621a37f43c419465aa Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Nov 2018 17:17:02 +0100 Subject: [PATCH 1344/2110] Use D8 for calculating method count --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 800b636d0c..0d12486c6b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -232,9 +232,9 @@ def collectAarMetrics() { sh """set -xe cd realm/realm-library/build/outputs/aar unzip realm-android-library-${flavor}-release.aar -d unzipped${flavor} - find \$ANDROID_HOME -name dx | sort -r | head -n 1 > dx - \$(cat dx) --dex --output=temp${flavor}.dex unzipped${flavor}/classes.jar - cat temp${flavor}.dex | head -c 92 | tail -c 4 | hexdump -e '1/4 \"%d\"' > methods${flavor} + find \$ANDROID_HOME -name d8 | sort -r | head -n 1 > d8 + \$(cat d8) --release --output ./unzipped${flavor} unzipped${flavor}/classes.jar + cat ./unzipped${flavor}/temp${flavor}.dex | head -c 92 | tail -c 4 | hexdump -e '1/4 \"%d\"' > methods${flavor} """ def methods = readFile("realm/realm-library/build/outputs/aar/methods${flavor}") From a08e788a10b845ea70b60b66bedc7c8cedca66d3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 6 Nov 2018 23:36:21 +0100 Subject: [PATCH 1345/2110] Use latest build tools --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 94700dcac9..ddcc4b4c7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ RUN sdkmanager --update # Accept all licenses RUN yes y | sdkmanager --licenses RUN sdkmanager 'platform-tools' -RUN sdkmanager 'build-tools;27.0.1' +RUN sdkmanager 'build-tools;28.0.3' RUN sdkmanager 'extras;android;m2repository' RUN sdkmanager 'platforms;android-27' RUN sdkmanager 'cmake;3.6.4111459' From 314d9af4c8ea0a9fba55ad0eb6d870b5ec071f33 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 10 Nov 2018 01:22:15 +0100 Subject: [PATCH 1346/2110] Re-enable PermissionManager tests (#6293) --- .../java/io/realm/PermissionManagerTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index ce297cdd15..d418b763d4 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -55,7 +55,6 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) -@Ignore // FIXME: Temporary disable unit tests due to lates (3.0.0-alpha.2) ROS having issues. Re-enable once ROS is stable again. public class PermissionManagerTests extends StandardIntegrationTest { private SyncUser user; From a5710890dbf34351e1c93cd9ccda557574f93270 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 10 Jan 2019 08:59:54 +0100 Subject: [PATCH 1347/2110] Remove Java8 bytecode (#6372) --- CHANGELOG.md | 2 +- .../src/main/java/io/realm/RealmResults.java | 2 +- .../java/io/realm/internal/OsResults.java | 79 +++++++++++++------ 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8983a736d0..d7ecceb2f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * None ### Fixed -* ?? (Issue [#??](https://github.com/realm/realm-java/issues/??), since ??). +* Removed Java 8 bytecode. Resulted in errors like `D8: Invoke-customs are only supported starting with Android O (--min-api 26)` if not compiled with Java 8. (Issue [#6300](https://github.com/realm/realm-java/issues/6300), since 5.8.0). ### Compatibility * Realm Object Server: 3.11.0 or later. diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 3f80013741..eac557b1d7 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -475,7 +475,7 @@ public void setList(String fieldName, RealmList list) { case LIST: checkTypeOfListElements(list, RealmModel.class); checkRealmObjectConstraints(fieldName, (RealmModel) list.first(null)); - osResults.setModelList(fieldName, (RealmList) list); + osResults.setModelList(fieldName, (RealmList) list); break; case INTEGER_LIST: // Integers are a bit annoying as they are all stored as the same type in Core diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 590f65dba6..47925d1cba 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -473,68 +473,101 @@ private void addTypeSpecificList(String fieldName, RealmList list, AddLis } public void setStringList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addStringList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addStringList(0, list); + } }); } public void setByteList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addByteList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addByteList(0, list); + } }); } public void setShortList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addShortList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addShortList(0, list); + } }); } public void setIntegerList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addIntegerList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addIntegerList(0, list); + } }); } public void setLongList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addLongList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addLongList(0, list); + } }); } public void setBooleanList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addBooleanList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addBooleanList(0, list); + } }); } public void setByteArrayList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addByteArrayList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addByteArrayList(0, list); + } }); } public void setDateList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addDateList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addDateList(0, list); + } }); } public void setFloatList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addFloatList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addFloatList(0, list); + } }); } public void setDoubleList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addDoubleList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addDoubleList(0, list); + } }); } - public void setModelList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addObjectList(0, lst); + public void setModelList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addObjectList(0, list); + } }); } From 0d7b596abdf84dc213230cffc4d3fb8da3db2397 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 10 Jan 2019 14:13:54 +0100 Subject: [PATCH 1348/2110] Update Sync (#6390) * Update Sync * Update ROS and set Sync dependency correctly * Update ObjectStore * Fix bug detecting files being upgraded from old version of ROS --- CHANGELOG.md | 7 +++++-- dependencies.list | 6 +++--- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ecceb2f0..55e14dcb10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ ## X.Y.Z (YYYY-MM-DD) ### Enhancements -* None +* Added better checks for detecting corrupted files, both before and after the file is written to disk. ### Fixed +* [ObjectServer] Query-based Sync queries involving LIMIT, limited the result before permissions were evaluated. This could sometimes result in the wrong number of elements being returned. * Removed Java 8 bytecode. Resulted in errors like `D8: Invoke-customs are only supported starting with Android O (--min-api 26)` if not compiled with Java 8. (Issue [#6300](https://github.com/realm/realm-java/issues/6300), since 5.8.0). ### Compatibility @@ -12,7 +13,9 @@ * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal -* None +* Updated to Object Store commit: f964c2640f635e76839559cb703732e9e906ba4c +* Updated Realm Sync to 3.14.13 +* Updated Realm Core to 5.12.7 ## 5.8.0 (2018-11-06) diff --git a/dependencies.list b/dependencies.list index fe31846029..e7be39bb39 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,11 +1,11 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.13.1 -REALM_SYNC_SHA256=4d21d7eb3cff254261da835dd88ab8dd8c2c376d7e81e111206f27225a55cf07 +REALM_SYNC_VERSION=3.14.13 +REALM_SYNC_SHA256=7e8934a471fa714bf672a9575cd3112470c3294d55e7a13688d04569e707b8cd # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.12.4 +REALM_OBJECT_SERVER_VERSION=3.16.6 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.1.4 diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index f0dfe6c03b..f964c2640f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit f0dfe6c03be49194bc40777901059eaf55e7bff6 +Subproject commit f964c2640f635e76839559cb703732e9e906ba4c From 22e9550594c0a2469d0bc62c5e85ac24f2fae130 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 15 Jan 2019 09:35:15 +0100 Subject: [PATCH 1349/2110] Add support for native error category and code (#6379) --- CHANGELOG.md | 9 +- dependencies.list | 6 +- .../java/io/realm/SessionTests.java | 4 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 43 ++- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/RealmResults.java | 2 +- .../java/io/realm/internal/OsResults.java | 79 +++-- .../objectServer/java/io/realm/ErrorCode.java | 309 ++++++++++++------ .../java/io/realm/ObjectServerError.java | 88 ++++- .../java/io/realm/PermissionManager.java | 2 +- .../java/io/realm/SyncManager.java | 6 +- .../java/io/realm/SyncSession.java | 12 +- .../internal/network/AuthServerResponse.java | 4 +- .../network/AuthenticateResponse.java | 19 +- 14 files changed, 408 insertions(+), 177 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8983a736d0..a5404c4982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ ## X.Y.Z (YYYY-MM-DD) ### Enhancements -* None +* Added better checks for detecting corrupted files, both before and after the file is written to disk. ### Fixed * ?? (Issue [#??](https://github.com/realm/realm-java/issues/??), since ??). +* [ObjectServer] Native errors sometimes mapped to the wrong Java ErrorCode. [#6364](https://github.com/realm/realm-java/issues/6364) +* [ObjectServer] Query-based Sync queries involving LIMIT, limited the result before permissions were evaluated. This could sometimes result in the wrong number of elements being returned. +* Removed Java 8 bytecode. Resulted in errors like `D8: Invoke-customs are only supported starting with Android O (--min-api 26)` if not compiled with Java 8. (Issue [#6300](https://github.com/realm/realm-java/issues/6300), since 5.8.0). ### Compatibility * Realm Object Server: 3.11.0 or later. @@ -12,7 +15,9 @@ * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal -* None +* Updated to Object Store commit: f964c2640f635e76839559cb703732e9e906ba4c +* Updated Realm Sync to 3.14.13 +* Updated Realm Core to 5.12.7 ## 5.8.0 (2018-11-06) diff --git a/dependencies.list b/dependencies.list index fe31846029..e7be39bb39 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,11 +1,11 @@ # Realm Sync Core release used by Realm Java # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.13.1 -REALM_SYNC_SHA256=4d21d7eb3cff254261da835dd88ab8dd8c2c376d7e81e111206f27225a55cf07 +REALM_SYNC_VERSION=3.14.13 +REALM_SYNC_SHA256=7e8934a471fa714bf672a9575cd3112470c3294d55e7a13688d04569e707b8cd # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.12.4 +REALM_OBJECT_SERVER_VERSION=3.16.6 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.1.4 diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index e86333d6cf..914b2e76c4 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -525,11 +525,11 @@ public void unrecognizedErrorCode_errorHandler() { TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); RealmLog.add(testLogger); - session.notifySessionError(3, "Unknown Error"); + session.notifySessionError("unknown", 3, "Unknown Error"); RealmLog.remove(testLogger); assertTrue(errorHandlerCalled.get()); - assertEquals("Unknown error code: 3", testLogger.message); + assertEquals("Unknown error code: 'unknown:3'", testLogger.message); realm.close(); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 78c3e4d2dc..e2b1c7d7bd 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -24,6 +24,9 @@ #endif +#include +#include + #include "java_accessor.hpp" #include "util.hpp" #include "jni_util/java_method.hpp" @@ -263,16 +266,19 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe // Doing the methods lookup from the thread that loaded the lib, to avoid // https://developer.android.com/training/articles/perf-jni.html#faq_FindClass static JavaMethod java_error_callback_method(env, sync_manager_class, "notifyErrorHandler", - "(ILjava/lang/String;Ljava/lang/String;)V", true); + "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V", true); static JavaMethod java_bind_session_method(env, sync_manager_class, "bindSessionWithConfig", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", true); // error handler will be called form the sync client thread auto error_handler = [](std::shared_ptr session, SyncError error) { - realm::jni_util::Log::d("error_handler lambda invoked"); - + auto error_category = error.error_code.category().name(); auto error_message = error.message; auto error_code = error.error_code.value(); + + // All client reset errors will be in the protocol category. Re-assign the error code + // to a value not used by https://github.com/realm/realm-sync/blob/develop/src/realm/sync/protocol.hpp#L232 + // This way we only have one error in Java representing Client Reset. if (error.is_client_reset_requested()) { // Hack the error message to send information about the location of the backup. // If more uses of the user_info map surfaces. Refactor this to send the full @@ -281,10 +287,39 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe error_code = 7; // See ErrorCode.java } + // System/Connection errors are defined by constants in + // https://android.googlesource.com/kernel/lk/+/upstream-master/include/errno.h + // However the integer values are not guaranteed to be stable according to POSIX. + // + // For this reason we manually map the constants to the error integer values defined in Java. + // For simplicity Java re-use the values currently defined in errno.h. + if (std::strcmp(error_category, "realm.basic_system") == 0) { + switch(error_code) { + case ECONNRESET: error_code = 104; break; + case ESHUTDOWN: error_code = 110; break; + case ECONNREFUSED: error_code = 111; break; + case EADDRINUSE: error_code = 112; break; + case ECONNABORTED: error_code = 113; break; + default: + /* Do nothing */ + error_code = error_code; + } + } else if (std::strcmp(error_category, "realm.util.misc_ext") == 0) { + switch (util::MiscExtErrors(error_code)) { + case util::MiscExtErrors::end_of_input: error_code = 1; break; + case util::MiscExtErrors::premature_end_of_input: error_code = 2; break; + case util::MiscExtErrors::delim_not_found: error_code = 3; break; + default: + /* Do nothing */ + error_code = error_code; + } + } + JNIEnv* env = realm::jni_util::JniUtils::get_env(true); + jstring jerror_category = to_jstring(env, error_category); jstring jerror_message = to_jstring(env, error_message); jstring jsession_path = to_jstring(env, session.get()->path()); - env->CallStaticVoidMethod(sync_manager_class, java_error_callback_method, error_code, jerror_message, + env->CallStaticVoidMethod(sync_manager_class, java_error_callback_method, jerror_category, error_code, jerror_message, jsession_path); env->DeleteLocalRef(jerror_message); env->DeleteLocalRef(jsession_path); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index f0dfe6c03b..f964c2640f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit f0dfe6c03be49194bc40777901059eaf55e7bff6 +Subproject commit f964c2640f635e76839559cb703732e9e906ba4c diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 3f80013741..eac557b1d7 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -475,7 +475,7 @@ public void setList(String fieldName, RealmList list) { case LIST: checkTypeOfListElements(list, RealmModel.class); checkRealmObjectConstraints(fieldName, (RealmModel) list.first(null)); - osResults.setModelList(fieldName, (RealmList) list); + osResults.setModelList(fieldName, (RealmList) list); break; case INTEGER_LIST: // Integers are a bit annoying as they are all stored as the same type in Core diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 590f65dba6..47925d1cba 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -473,68 +473,101 @@ private void addTypeSpecificList(String fieldName, RealmList list, AddLis } public void setStringList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addStringList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addStringList(0, list); + } }); } public void setByteList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addByteList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addByteList(0, list); + } }); } public void setShortList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addShortList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addShortList(0, list); + } }); } public void setIntegerList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addIntegerList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addIntegerList(0, list); + } }); } public void setLongList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addLongList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addLongList(0, list); + } }); } public void setBooleanList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addBooleanList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addBooleanList(0, list); + } }); } public void setByteArrayList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addByteArrayList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addByteArrayList(0, list); + } }); } public void setDateList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addDateList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addDateList(0, list); + } }); } public void setFloatList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addFloatList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addFloatList(0, list); + } }); } public void setDoubleList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addDoubleList(0, lst); + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addDoubleList(0, list); + } }); } - public void setModelList(String fieldName, RealmList list) { - addTypeSpecificList(fieldName, list, (builder, lst) -> { - builder.addObjectList(0, lst); + public void setModelList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addObjectList(0, list); + } }); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 8b47653bec..ae521ca8d5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -18,6 +18,7 @@ import java.io.IOException; +import java.util.Locale; import io.realm.log.RealmLog; @@ -30,138 +31,184 @@ public enum ErrorCode { // See https://github.com/realm/realm-object-server/blob/master/object-server/doc/problems.md // See https://github.com/realm/realm-sync/blob/develop/src/realm/sync/protocol.hpp - // Realm Java errors (0-49) - UNKNOWN(-1), // Catch-all - IO_EXCEPTION(0, Category.RECOVERABLE), // Some IO error while either contacting the server or reading the response - JSON_EXCEPTION(1), // JSON input could not be parsed correctly - CLIENT_RESET(7), // Client Reset required. Don't change this value without modifying io_realm_internal_OsSharedRealm.cpp - - // Realm Object Server errors (100 - 199) - // Connection level and protocol errors. - CONNECTION_CLOSED(100), // Connection closed (no error) - OTHER_ERROR(101), // Other connection level error - UNKNOWN_MESSAGE(102), // Unknown type of input message - BAD_SYNTAX(103), // Bad syntax in input message head - LIMITS_EXCEEDED(104), // Limits exceeded in input message - WRONG_PROTOCOL_VERSION(105), // Wrong protocol version (CLIENT) - BAD_SESSION_IDENT(106), // Bad session identifier in input message - REUSE_OF_SESSION_IDENT(107), // Overlapping reuse of session identifier (BIND) - BOUND_IN_OTHER_SESSION(108), // Client file bound in other session (IDENT) - BAD_MESSAGE_ORDER(109), // Bad input message order - BAD_ORIGIN_FILE_IDENT(110), // Bad origin file identifier in changeset header (DOWNLOAD) - BAD_SERVER_VERSION_DOWNLOAD(111),// Bad server version in changeset header (DOWNLOAD) - BAD_CHANGESET_DOWNLOAD(112), // Bad changeset (DOWNLOAD) - BAD_REQUEST_IDENT(113), // Bad request identifier (MARK) - BAD_ERROR_CODE(114), // Bad error code (ERROR) - BAD_COMPRESSION(115), // Bad compression (DOWNLOAD) - BAD_CLIENT_VERSION_DOWNLOAD(116),// Bad last integrated client version in changeset header (DOWNLOAD) - SSL_SERVER_CERT_REJECTED(117), // SSL server certificate rejected - PONG_TIMEOUT(118), // Timeout on reception of PONG response messsage - - // Session level errors (200 - 299) - SESSION_CLOSED(200, Category.RECOVERABLE), // Session closed (no error) - OTHER_SESSION_ERROR(201, Category.RECOVERABLE), // Other session level error - TOKEN_EXPIRED(202, Category.RECOVERABLE), // Access token expired + // Catch-all + // The underlying type and error code should be part of the error message + UNKNOWN(Type.UNKNOWN, -1), + + // Realm Java errors + IO_EXCEPTION(Type.JAVA, 0, Category.RECOVERABLE), // Some IO error while either contacting the server or reading the response + JSON_EXCEPTION(Type.AUTH, 1), // JSON input could not be parsed correctly + CLIENT_RESET(Type.PROTOCOL, 7), // Client Reset required. Don't change this value without modifying io_realm_internal_OsSharedRealm.cpp + + // Connection level and protocol errors from the native Sync Client + CONNECTION_CLOSED(Type.PROTOCOL, 100, Category.RECOVERABLE), // Connection closed (no error) + OTHER_ERROR(Type.PROTOCOL, 101), // Other connection level error + UNKNOWN_MESSAGE(Type.PROTOCOL, 102), // Unknown type of input message + BAD_SYNTAX(Type.PROTOCOL, 103), // Bad syntax in input message head + LIMITS_EXCEEDED(Type.PROTOCOL, 104), // Limits exceeded in input message + WRONG_PROTOCOL_VERSION(Type.PROTOCOL, 105), // Wrong protocol version (CLIENT) + BAD_SESSION_IDENT(Type.PROTOCOL, 106), // Bad session identifier in input message + REUSE_OF_SESSION_IDENT(Type.PROTOCOL, 107), // Overlapping reuse of session identifier (BIND) + BOUND_IN_OTHER_SESSION(Type.PROTOCOL, 108), // Client file bound in other session (IDENT) + BAD_MESSAGE_ORDER(Type.PROTOCOL, 109), // Bad input message order + BAD_DECOMPRESSION(Type.PROTOCOL, 110), // Error in decompression (UPLOAD) + BAD_CHANGESET_HEADER_SYNTAX(Type.PROTOCOL, 111), // Bad server version in changeset header (DOWNLOAD) + BAD_CHANGESET_SIZE(Type.PROTOCOL, 112), // Bad size specified in changeset header (UPLOAD) + BAD_CHANGESETS(Type.PROTOCOL, 113), // Bad changesets (UPLOAD) + + // Session level errors from the native Sync Client + SESSION_CLOSED(Type.PROTOCOL, 200, Category.RECOVERABLE), // Session closed (no error) + OTHER_SESSION_ERROR(Type.PROTOCOL, 201, Category.RECOVERABLE), // Other session level error + TOKEN_EXPIRED(Type.PROTOCOL, 202, Category.RECOVERABLE), // Access token expired // Session fatal: Auth wrong. Cannot be fixed without a new User/SyncConfiguration. - BAD_AUTHENTICATION(203), // Bad user authentication (BIND, REFRESH) - ILLEGAL_REALM_PATH(204), // Illegal Realm path (BIND) - NO_SUCH_PATH(205), // No such Realm (BIND) - PERMISSION_DENIED(206), // Permission denied (BIND, REFRESH) + BAD_AUTHENTICATION(Type.PROTOCOL, 203), // Bad user authentication (BIND, REFRESH) + ILLEGAL_REALM_PATH(Type.PROTOCOL, 204), // Illegal Realm path (BIND) + NO_SUCH_PATH(Type.PROTOCOL, 205), // No such Realm (BIND) + PERMISSION_DENIED(Type.PROTOCOL, 206), // Permission denied (BIND, REFRESH) // Fatal: Wrong server/client versions. Trying to sync incompatible files or the file was corrupted. - BAD_SERVER_FILE_IDENT(207), // Bad server file identifier (IDENT) - BAD_CLIENT_FILE_IDENT(208), // Bad client file identifier (IDENT) - BAD_SERVER_VERSION(209), // Bad server version (IDENT, UPLOAD) - BAD_CLIENT_VERSION(210), // Bad client version (IDENT, UPLOAD) - DIVERGING_HISTORIES(211), // Diverging histories (IDENT) - BAD_CHANGESET(212), // Bad changeset (UPLOAD) - DISABLED_SESSION(213), // Disabled session - PARTIAL_SYNC_DISABLED(214), // Partial sync disabled (BIND) + BAD_SERVER_FILE_IDENT(Type.PROTOCOL, 207), // Bad server file identifier (IDENT) + BAD_CLIENT_FILE_IDENT(Type.PROTOCOL, 208), // Bad client file identifier (IDENT) + BAD_SERVER_VERSION(Type.PROTOCOL, 209), // Bad server version (IDENT, UPLOAD) + BAD_CLIENT_VERSION(Type.PROTOCOL, 210), // Bad client version (IDENT, UPLOAD) + DIVERGING_HISTORIES(Type.PROTOCOL, 211), // Diverging histories (IDENT) + BAD_CHANGESET(Type.PROTOCOL, 212), // Bad changeset (UPLOAD) + DISABLED_SESSION(Type.PROTOCOL, 213), // Disabled session + PARTIAL_SYNC_DISABLED(Type.PROTOCOL, 214), // Partial sync disabled (BIND) + UNSUPPORTED_SESSION_FEATURE(Type.PROTOCOL, 215), // Unsupported session-level feature + BAD_ORIGIN_FILE_IDENT(Type.PROTOCOL, 216), // Bad origin file identifier (UPLOAD) + + // Sync Network Client errors. + // TODO: All enums in here should be prefixed with `CLIENT_`, but in order to avoid + // breaking changes, this is not the case for all of them. This should be fixed in the + // next major release. + // See https://github.com/realm/realm-java/issues/6387 + CLIENT_CONNECTION_CLOSED(Type.SESSION, 100), // Connection closed (no error) + CLIENT_UNKNOWN_MESSAGE(Type.SESSION, 101), // Unknown type of input message + CLIENT_LIMITS_EXCEEDED(Type.SESSION, 103), // Limits exceeded in input message + CLIENT_BAD_SESSION_IDENT(Type.SESSION, 104), // Bad session identifier in input message + CLIENT_BAD_MESSAGE_ORDER(Type.SESSION, 105), // Bad input message order + CLIENT_BAD_CLIENT_FILE_IDENT(Type.SESSION, 106), // Bad client file identifier (IDENT) + CLIENT_BAD_PROGRESS(Type.SESSION, 107), // Bad progress information (DOWNLOAD) + CLIENT_BAD_CHANGESET_HEADER_SYNTAX(Type.SESSION, 108), // Bad syntax in changeset header (DOWNLOAD) + CLIENT_BAD_CHANGESET_SIZE(Type.SESSION, 109), // Bad changeset size in changeset header (DOWNLOAD) + CLIENT_BAD_ORIGIN_FILE_IDENT(Type.SESSION, 110), // Bad origin file identifier in changeset header (DOWNLOAD) + CLIENT_BAD_SERVER_VERSION(Type.SESSION, 111), // Bad server version in changeset header (DOWNLOAD) + CLIENT_BAD_CHANGESET(Type.SESSION, 112), // Bad changeset (DOWNLOAD) + BAD_REQUEST_IDENT(Type.SESSION, 113), // Bad request identifier (MARK) + BAD_ERROR_CODE(Type.SESSION, 114), // Bad error code (ERROR) + BAD_COMPRESSION(Type.SESSION, 115), // Bad compression (DOWNLOAD) + BAD_CLIENT_VERSION_DOWNLOAD(Type.SESSION, 116), // Bad last integrated client version in changeset header (DOWNLOAD) + SSL_SERVER_CERT_REJECTED(Type.SESSION, 117), // SSL server certificate rejected + PONG_TIMEOUT(Type.SESSION, 118), // Timeout on reception of PONG respone message + CLIENT_BAD_CLIENT_FILE_IDENT_SALT(Type.SESSION, 119), // Bad client file identifier salt (IDENT) + CLIENT_FILE_IDENT(Type.SESSION, 120), // Bad file identifier (ALLOC) + CLIENT_CONNECT_TIMEOUT(Type.SESSION, 121), // Sync connection was not fully established in time + CLIENT_BAD_TIMESTAMP(Type.SESSION, 122), // Bad timestamp (PONG) // 300 - 599 Reserved for Standard HTTP error codes - MULTIPLE_CHOICES(300), - MOVED_PERMANENTLY(301), - FOUND(302), - SEE_OTHER(303), - NOT_MODIFIED(304), - USE_PROXY(305), - TEMPORARY_REDIRECT(307), - PERMANENT_REDIRECT(308), - BAD_REQUEST(400), - UNAUTHORIZED(401), - PAYMENT_REQUIRED(402), - FORBIDDEN(403), - NOT_FOUND(404), - METHOD_NOT_ALLOWED(405), - NOT_ACCEPTABLE(406), - PROXY_AUTHENTICATION_REQUIRED(407), - REQUEST_TIMEOUT(408), - CONFLICT(409), - GONE(410), - LENGTH_REQUIRED(411), - PRECONDITION_FAILED(412), - PAYLOAD_TOO_LARGE(413), - URI_TOO_LONG(414), - UNSUPPORTED_MEDIA_TYPE(415), - RANGE_NOT_SATISFIABLE(416), - EXPECTATION_FAILED(417), - MISDIRECTED_REQUEST(421), - UNPROCESSABLE_ENTITY(422), - LOCKED(423), - FAILED_DEPENDENCY(424), - UPGRADE_REQUIRED(426), - PRECONDITION_REQUIRED(428), - TOO_MANY_REQUESTS(429), - REQUEST_HEADER_FIELDS_TOO_LARGE(431), - UNAVAILABLE_FOR_LEGAL_REASONS(451), - INTERNAL_SERVER_ERROR(500), - NOT_IMPLEMENTED(501), - BAD_GATEWAY(502), - SERVICE_UNAVAILABLE(503), - GATEWAY_TIMEOUT(504), - HTTP_VERSION_NOT_SUPPORTED(505), - VARIANT_ALSO_NEGOTIATES(506), - INSUFFICIENT_STORAGE(507), - LOOP_DETECTED(508), - NOT_EXTENDED(510), - NETWORK_AUTHENTICATION_REQUIRED(511), + MULTIPLE_CHOICES(Type.HTTP, 300), + MOVED_PERMANENTLY(Type.HTTP, 301), + FOUND(Type.HTTP, 302), + SEE_OTHER(Type.HTTP, 303), + NOT_MODIFIED(Type.HTTP, 304), + USE_PROXY(Type.HTTP, 305), + TEMPORARY_REDIRECT(Type.HTTP, 307), + PERMANENT_REDIRECT(Type.HTTP, 308), + BAD_REQUEST(Type.HTTP, 400), + UNAUTHORIZED(Type.HTTP, 401), + PAYMENT_REQUIRED(Type.HTTP, 402), + FORBIDDEN(Type.HTTP, 403), + NOT_FOUND(Type.HTTP, 404), + METHOD_NOT_ALLOWED(Type.HTTP, 405), + NOT_ACCEPTABLE(Type.HTTP, 406), + PROXY_AUTHENTICATION_REQUIRED(Type.HTTP, 407), + REQUEST_TIMEOUT(Type.HTTP, 408), + CONFLICT(Type.HTTP, 409), + GONE(Type.HTTP, 410), + LENGTH_REQUIRED(Type.HTTP, 411), + PRECONDITION_FAILED(Type.HTTP, 412), + PAYLOAD_TOO_LARGE(Type.HTTP, 413), + URI_TOO_LONG(Type.HTTP, 414), + UNSUPPORTED_MEDIA_TYPE(Type.HTTP, 415), + RANGE_NOT_SATISFIABLE(Type.HTTP, 416), + EXPECTATION_FAILED(Type.HTTP, 417), + MISDIRECTED_REQUEST(Type.HTTP, 421), + UNPROCESSABLE_ENTITY(Type.HTTP, 422), + LOCKED(Type.HTTP, 423), + FAILED_DEPENDENCY(Type.HTTP, 424), + UPGRADE_REQUIRED(Type.HTTP, 426), + PRECONDITION_REQUIRED(Type.HTTP, 428), + TOO_MANY_REQUESTS(Type.HTTP, 429), + REQUEST_HEADER_FIELDS_TOO_LARGE(Type.HTTP, 431), + UNAVAILABLE_FOR_LEGAL_REASONS(Type.HTTP, 451), + INTERNAL_SERVER_ERROR(Type.HTTP, 500), + NOT_IMPLEMENTED(Type.HTTP, 501), + BAD_GATEWAY(Type.HTTP, 502), + SERVICE_UNAVAILABLE(Type.HTTP, 503), + GATEWAY_TIMEOUT(Type.HTTP, 504), + HTTP_VERSION_NOT_SUPPORTED(Type.HTTP, 505), + VARIANT_ALSO_NEGOTIATES(Type.HTTP, 506), + INSUFFICIENT_STORAGE(Type.HTTP, 507), + LOOP_DETECTED(Type.HTTP, 508), + NOT_EXTENDED(Type.HTTP, 510), + NETWORK_AUTHENTICATION_REQUIRED(Type.HTTP, 511), // Realm Authentication Server response errors (600 - 699) - INVALID_PARAMETERS(601), - MISSING_PARAMETERS(602), - INVALID_CREDENTIALS(611), - UNKNOWN_ACCOUNT(612), - EXISTING_ACCOUNT(613), - ACCESS_DENIED(614), - EXPIRED_REFRESH_TOKEN(615), - INVALID_HOST(616), + INVALID_PARAMETERS(Type.AUTH, 601), + MISSING_PARAMETERS(Type.AUTH, 602), + INVALID_CREDENTIALS(Type.AUTH, 611), + UNKNOWN_ACCOUNT(Type.AUTH, 612), + EXISTING_ACCOUNT(Type.AUTH, 613), + ACCESS_DENIED(Type.AUTH, 614), + EXPIRED_REFRESH_TOKEN(Type.AUTH, 615), + INVALID_HOST(Type.AUTH, 616), + REALM_NOT_FOUND(Type.AUTH, 617), + UNKNOWN_USER(Type.AUTH, 618), + WRONG_REALM_TYPE(Type.AUTH, 619), // The Realm found on the server is of different type than the one requested. // Other Realm Object Server response errors - EXPIRED_PERMISSION_OFFER(701), - AMBIGUOUS_PERMISSION_OFFER_TOKEN(702), - FILE_MAY_NOT_BE_SHARED(703), - SERVER_MISCONFIGURATION(801); + EXPIRED_PERMISSION_OFFER(Type.AUTH, 701), + AMBIGUOUS_PERMISSION_OFFER_TOKEN(Type.AUTH, 702), + FILE_MAY_NOT_BE_SHARED(Type.AUTH, 703), + SERVER_MISCONFIGURATION(Type.AUTH, 801), + // Generic system errors we want to enumerate specifically + CONNECTION_RESET_BY_PEER(Type.CONNECTION, 104, Category.RECOVERABLE), // ECONNRESET: Connection reset by peer + CONNECTION_SOCKET_SHUTDOWN(Type.CONNECTION, 110, Category.RECOVERABLE), // ESHUTDOWN: Can't send after socket shutdown + CONNECTION_REFUSED(Type.CONNECTION, 111, Category.RECOVERABLE), // ECONNREFUSED: Connection refused + CONNECTION_ADDRESS_IN_USE(Type.CONNECTION, 112, Category.RECOVERABLE), // EADDRINUSE: Address already i use + CONNECTION_CONNECTION_ABORTED(Type.CONNECTION, 113, Category.RECOVERABLE), // ECONNABORTED: Connection aborted + + MISC_END_OF_INPUT(Type.MISC, 1), // End of input + MISC_PREMATURE_END_OF_INPUT(Type.MISC, 2), // Premature end of input. That is, end of input at an unexpected, or illegal place in an input stream. + MISC_DELIMITER_NOT_FOUND(Type.MISC, 3); // Delimiter not found + + private final String type; private final int code; private final Category category; - ErrorCode(int errorCode) { - this(errorCode, Category.FATAL); + ErrorCode(String type, int errorCode) { + this(type, errorCode, Category.FATAL); } - ErrorCode(int errorCode, Category category) { + ErrorCode(String type, int errorCode, Category category) { + this.type = type; this.code = errorCode; this.category = category; } @Override - public String toString() { - return super.toString() + "(" + code + ")"; + public String + + toString() { + return super.toString() + "(" + type + ":" + code + ")"; } /** - * Returns the numerical value for this error code. + * Returns the numerical value for this error code. Note that an error is only uniquely + * identified by the {@code (type:value)} pair. * * @return the error code as an unique {@code int} value. */ @@ -186,6 +233,38 @@ public Category getCategory() { return category; } + /** + * Returns the type of error. Note that an error is only uniquely identified by the + * {@code (type:value)} pair. + * + * @return the type of error. + */ + public String getType() { + return type; + } + + /** + * Converts a native error to the appropriate Java equivalent + * + * @param type type of error. This is normally the C++ category. + * @param errorCode specific code within the type + * + * @return the Java error representing the native error. This method will never throw, so in case + * a Java error does not exists. {@link #UNKNOWN} will be returned. + */ + public static ErrorCode fromNativeError(String type, int errorCode) { + ErrorCode[] errorCodes = values(); + for (int i = 0; i < errorCodes.length; i++) { + ErrorCode error = errorCodes[i]; + if (error.intValue() == errorCode && error.type.equals(type)) { + return error; + } + } + RealmLog.warn(String.format(Locale.US, "Unknown error code: '%s:%d'", type, errorCode)); + return UNKNOWN; + } + + @Deprecated public static ErrorCode fromInt(int errorCode) { ErrorCode[] errorCodes = values(); for (int i = 0; i < errorCodes.length; i++) { @@ -212,8 +291,22 @@ public static ErrorCode fromException(Exception exception) { } } + public static class Type { + public static final String AUTH = "auth"; // Errors from the Realm Object Server + public static final String CONNECTION = "realm.basic_system"; // Connection/System errors from the native Sync Client + public static final String DEPRECATED = "deprecated"; // Deprecated errors + public static final String HTTP = "http"; // Errors from the HTTP layer + public static final String JAVA = "java"; // Errors from the Java layer + public static final String MISC = "realm.util.misc_ext"; // Misc errors from the native Sync Client + public static final String PROTOCOL = "realm::sync::ProtocolError"; // Protocol level errors from the native Sync Client + public static final String SESSION = "realm::sync::Client::Error"; // Session level errors from the native Sync Client + public static final String UNKNOWN = "unknown"; // Catch-all category + } + + public enum Category { FATAL, // Abort session as soon as possible RECOVERABLE, // Still possible to recover the session by either rebinding or providing the required information. } + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java index b79eff13b1..4cb2fa961b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java @@ -32,7 +32,14 @@ */ public class ObjectServerError extends RuntimeException { + // The Java representation of the error. private final ErrorCode error; + + // The native error representation. Mostly relevant for ErrorCode.UNKNOWN + // where it can provide more details into the exact error. + private final String nativeErrorType; + private final int nativeErrorIntValue; + private final String errorMessage; private final Throwable exception; @@ -43,7 +50,21 @@ public class ObjectServerError extends RuntimeException { * @param errorMessage detailed error message. */ public ObjectServerError(ErrorCode errorCode, String errorMessage) { - this(errorCode, errorMessage, (Throwable) null); + this(errorCode, errorCode.getType(), errorCode.intValue(), errorMessage, (Throwable) null); + } + + /** + * Creates an unknown error that could not be mapped to any known error case. + *

            + * This means that {@link #getErrorCode()} will return {@link ErrorCode#UNKNOWN}, but + * {@link #getErrorType()} and {@link #getErrorIntValue()} will return the underlying values + * which can help identify the real error. + * + * @param errorCode error code for this type of error. + * @param errorMessage detailed error message. + */ + public ObjectServerError(String errorType, int errorCode, String errorMessage) { + this(ErrorCode.UNKNOWN, errorType, errorCode, errorMessage, null); } /** @@ -56,6 +77,17 @@ public ObjectServerError(ErrorCode errorCode, Throwable exception) { this(errorCode, null, exception); } + /** + * Errors happening while trying to authenticate a user. + * + * @param errorCode error code for this type of error. + * @param title title for this type of error. + * @param hint a hint for resolving the error. + */ + public ObjectServerError(ErrorCode errorCode, String title, @Nullable String hint) { + this(errorCode, (hint != null) ? title + " : " + hint : title, (Throwable) null); + } + /** * Generic error happening that could happen anywhere. * @@ -64,30 +96,49 @@ public ObjectServerError(ErrorCode errorCode, Throwable exception) { * @param exception underlying exception if the error was caused by this. */ public ObjectServerError(ErrorCode errorCode, @Nullable String errorMessage, @Nullable Throwable exception) { + this(errorCode, errorCode.getType(), errorCode.intValue(), errorMessage, exception); + } + + public ObjectServerError(ErrorCode errorCode, String nativeErrorType, int nativeErrorCode, + @Nullable String errorMessage, @Nullable Throwable exception) { this.error = errorCode; + this.nativeErrorType = nativeErrorType; + this.nativeErrorIntValue = nativeErrorCode; this.errorMessage = errorMessage; this.exception = exception; } /** - * Errors happening while trying to authenticate a user. + * Returns the {@link ErrorCode} identifying the type of error. + *

            + * If {@link ErrorCode#UNKNOWN} is returned, it means that the error could not be mapped to any + * known errors. In that case {@link #getErrorType()} and {@link #getErrorIntValue()} will + * return the underlying error information which can better identify the type of error. * - * @param errorCode error code for this type of error. - * @param title Title for this type of error. - * @param hint a hint for resolving the error. + * @return the error code identifying the type of error. + * @see ErrorCode */ - public ObjectServerError(ErrorCode errorCode, String title, @Nullable String hint) { - this(errorCode, (hint != null) ? title + " : " + hint : title, (Throwable) null); + public ErrorCode getErrorCode() { + return error; } /** - * Returns the error code uniquely identifying this type of error. + * Returns a string describing the type of error it is. * - * @return the error code identifying the type of error. - * @see ErrorCode + * @return */ - public ErrorCode getErrorCode() { - return error; + public String getErrorType() { + return nativeErrorType; + } + + /** + * Returns an integer representing this specific type of error. This value is only unique within + * the value provided by {@link #getErrorType()}. + * + * @return the integer value representing this type of error. + */ + public int getErrorIntValue() { + return nativeErrorIntValue; } /** @@ -95,6 +146,7 @@ public ErrorCode getErrorCode() { * * @return a detailed error message or {@code null} if one was not available. */ + @Nullable public String getErrorMessage() { return errorMessage; } @@ -104,6 +156,7 @@ public String getErrorMessage() { * * @return the underlying exception causing this error, or {@code null} if not caused by an exception. */ + @Nullable public Throwable getException() { return exception; } @@ -122,9 +175,16 @@ public ErrorCode.Category getCategory() { @Override public String toString() { - StringBuilder sb = new StringBuilder(getErrorCode().toString()); + StringBuilder sb = new StringBuilder(); + + sb.append(getErrorCode().name()); + sb.append("("); + sb.append(getErrorType()); + sb.append(":"); + sb.append(getErrorIntValue()); + sb.append(')'); if (errorMessage != null) { - sb.append('\n'); + sb.append(": "); sb.append(errorMessage); } if (exception != null) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java index e8f088abd1..f668c6385d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java @@ -1089,7 +1089,7 @@ protected void handleServerStatusChanges(BasePermissionApi obj, Runnable onSucce if (statusCode != null) { RealmObject.removeAllChangeListeners(obj); if (statusCode > 0) { - ErrorCode errorCode = ErrorCode.fromInt(statusCode); + ErrorCode errorCode = ErrorCode.fromNativeError(ErrorCode.Type.AUTH, statusCode); String errorMsg = obj.getStatusMessage(); ObjectServerError error = new ObjectServerError(errorCode, errorMsg); notifyCallbackWithError(error); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 15a7adaa7e..ceb1907170 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -502,12 +502,12 @@ static void notifyUserLoggedOut(SyncUser user) { * session to contact. If {@code path == null} all sessions are effected. */ @SuppressWarnings("unused") - private static synchronized void notifyErrorHandler(int errorCode, String errorMessage, @Nullable String path) { + private static synchronized void notifyErrorHandler(String nativeErrorCategory, int nativeErrorCode, String errorMessage, @Nullable String path) { if (Util.isEmptyString(path)) { // notify all sessions for (SyncSession syncSession : sessions.values()) { try { - syncSession.notifySessionError(errorCode, errorMessage); + syncSession.notifySessionError(nativeErrorCategory, nativeErrorCode, errorMessage); } catch (Exception exception) { RealmLog.error(exception); } @@ -516,7 +516,7 @@ private static synchronized void notifyErrorHandler(int errorCode, String errorM SyncSession syncSession = sessions.get(path); if (syncSession != null) { try { - syncSession.notifySessionError(errorCode, errorMessage); + syncSession.notifySessionError(nativeErrorCategory, nativeErrorCode, errorMessage); } catch (Exception exception) { RealmLog.error(exception); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index d98d49df83..431499422b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -227,11 +227,11 @@ public URI getServerUrl() { } // This callback will happen on the thread running the Sync Client. - void notifySessionError(int errorCode, String errorMessage) { + void notifySessionError(String nativeErrorCategory, int nativeErrorCode, String errorMessage) { if (errorHandler == null) { return; } - ErrorCode errCode = ErrorCode.fromInt(errorCode); + ErrorCode errCode = ErrorCode.fromNativeError(nativeErrorCategory, nativeErrorCode); if (errCode == ErrorCode.CLIENT_RESET) { // errorMessage contains the path to the backed up file RealmConfiguration backupRealmConfiguration = SyncConfiguration.forRecovery(errorMessage, configuration.getEncryptionKey(), configuration.getSchemaMediator()); @@ -239,7 +239,13 @@ void notifySessionError(int errorCode, String errorMessage) { "Read more here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup.", configuration, backupRealmConfiguration)); } else { - errorHandler.onError(this, new ObjectServerError(errCode, errorMessage)); + ObjectServerError wrappedError; + if (errCode == ErrorCode.UNKNOWN) { + wrappedError = new ObjectServerError(nativeErrorCategory, nativeErrorCode, errorMessage); + } else { + wrappedError = new ObjectServerError(errCode, errorMessage); + } + errorHandler.onError(this, wrappedError); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java index 4285b565a6..1f2786c241 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java @@ -65,9 +65,9 @@ public static ObjectServerError createError(String response, int httpErrorCode) String hint = obj.optString("hint", null); ErrorCode errorCode; if (obj.has("code")) { - errorCode = ErrorCode.fromInt(obj.getInt("code")); + errorCode = ErrorCode.fromNativeError(ErrorCode.Type.AUTH, obj.getInt("code")); } else if (obj.has("status")) { - errorCode = ErrorCode.fromInt(obj.getInt("status")); + errorCode = ErrorCode.fromNativeError(ErrorCode.Type.AUTH, obj.getInt("status")); } else { errorCode = ErrorCode.UNKNOWN; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index cf9c8d85fe..5c0374649b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -122,27 +122,26 @@ private AuthenticateResponse(String serverResponse) { ObjectServerError error; Token accessToken; Token refreshToken; - String message; + String debugMessage; try { JSONObject obj = new JSONObject(serverResponse); - accessToken = obj.has(JSON_FIELD_ACCESS_TOKEN) ? - Token.from(obj.getJSONObject(JSON_FIELD_ACCESS_TOKEN)) : null; - refreshToken = obj.has(JSON_FIELD_REFRESH_TOKEN) ? - Token.from(obj.getJSONObject(JSON_FIELD_REFRESH_TOKEN)) : null; + accessToken = obj.has(JSON_FIELD_ACCESS_TOKEN) ? Token.from(obj.getJSONObject(JSON_FIELD_ACCESS_TOKEN)) : null; + refreshToken = obj.has(JSON_FIELD_REFRESH_TOKEN) ? Token.from(obj.getJSONObject(JSON_FIELD_REFRESH_TOKEN)) : null; error = null; if (accessToken == null) { - message = "accessToken = null"; + debugMessage = "accessToken = null"; } else { - message = String.format(Locale.US, "Identity %s; Path %s", accessToken.identity(), accessToken.path()); + debugMessage = String.format(Locale.US, "Identity %s; Path %s", accessToken.identity(), accessToken.path()); } } catch (JSONException ex) { accessToken = null; refreshToken = null; + String exceptionMessage = String.format(Locale.US, "Server response could not be parsed as JSON:%n%s", serverResponse); //noinspection ThrowableInstanceNeverThrown - error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, ex); - message = String.format(Locale.US, "Error %s", error.getErrorMessage()); + error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, exceptionMessage, ex); + debugMessage = String.format(Locale.US, "Error %s", error.getErrorMessage()); } - RealmLog.debug("AuthenticateResponse. " + message); + RealmLog.debug("AuthenticateResponse. " + debugMessage); setError(error); this.accessToken = accessToken; this.refreshToken = refreshToken; From 9ac616d49fb804411b834216b1ebfa37a358a84b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 15 Jan 2019 14:03:48 +0100 Subject: [PATCH 1350/2110] Bump version + add release date to changelog --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fe31b7e64..61423ce1fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## X.Y.Z (YYYY-MM-DD) +## 5.9.0(2019-01-15) ### Enhancements * [ObjectServer] Added `ObjectServerError.getErrorType()` and `ObjectServerError.getErrorType()` which returns the underlying native error information. This is especially relevant if `ObjectServerError.getErrorCode()` returns `UNKNOWN`. [#6364](https://github.com/realm/realm-java/issues/6364) diff --git a/version.txt b/version.txt index f678e79be4..5272c70f85 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.8.1-SNAPSHOT \ No newline at end of file +5.9.0-SNAPSHOT \ No newline at end of file From f1bf0965f6288876d06872c39a5d786d3c72d658 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 15 Jan 2019 14:11:38 +0100 Subject: [PATCH 1351/2110] Release v5.9.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5272c70f85..cf51361190 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.9.0-SNAPSHOT \ No newline at end of file +5.9.0 \ No newline at end of file From 4a16aa85c103f521dac46220908d1cda70407db0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 15 Jan 2019 14:11:38 +0100 Subject: [PATCH 1352/2110] Prepare next release v5.9.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index cf51361190..3f055cf770 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.9.0 \ No newline at end of file +5.9.1-SNAPSHOT \ No newline at end of file From c52ac04cab8e62049106c84af6b435e8546f113d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 15 Jan 2019 15:10:58 +0100 Subject: [PATCH 1353/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 3f055cf770..7ca1081f54 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.9.1-SNAPSHOT \ No newline at end of file +5.10.0-SNAPSHOT \ No newline at end of file From 11d0732ce2b55e283436a6eedbbbed74632ddb77 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 15 Jan 2019 16:05:11 +0100 Subject: [PATCH 1354/2110] Add missing bug information --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61423ce1fa..de2b580084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * Added better checks for detecting corrupted files, both before and after the file is written to disk. ### Fixed -* [ObjectServer] Native errors sometimes mapped to the wrong Java ErrorCode. [#6364](https://github.com/realm/realm-java/issues/6364) +* [ObjectServer] Native errors sometimes mapped to the wrong Java ErrorCode. (Issue [#6364](https://github.com/realm/realm-java/issues/6364), since 2.0.0) * [ObjectServer] Query-based Sync queries involving LIMIT, limited the result before permissions were evaluated. This could sometimes result in the wrong number of elements being returned. * Removed Java 8 bytecode. Resulted in errors like `D8: Invoke-customs are only supported starting with Android O (--min-api 26)` if not compiled with Java 8. (Issue [#6300](https://github.com/realm/realm-java/issues/6300), since 5.8.0). From c26dd2d4ed952923475112f978876631e1d89fef Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Feb 2019 10:07:28 +0100 Subject: [PATCH 1355/2110] Convert ObjectServerExample to Kotlin and use data binding (#6413) --- Dockerfile | 25 +- examples/objectServerExample/README.md | 8 +- examples/objectServerExample/build.gradle | 22 +- .../objectserver/CounterActivity.java | 222 ------------------ .../examples/objectserver/CounterActivity.kt | 177 ++++++++++++++ .../examples/objectserver/LoginActivity.java | 140 ----------- .../examples/objectserver/LoginActivity.kt | 113 +++++++++ .../{MyApplication.java => MyApplication.kt} | 25 +- .../objectserver/model/CRDTCounter.java | 42 ---- .../objectserver/model/CRDTCounter.kt | 38 +++ .../src/main/res/layout/activity_counter.xml | 90 +++---- .../src/main/res/layout/activity_login.xml | 113 ++++----- .../src/main/res/values/strings.xml | 2 + realm/build.gradle | 2 +- 14 files changed, 491 insertions(+), 528 deletions(-) delete mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt delete mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt rename examples/objectServerExample/src/main/java/io/realm/examples/objectserver/{MyApplication.java => MyApplication.kt} (62%) delete mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java create mode 100644 examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt diff --git a/Dockerfile b/Dockerfile index ddcc4b4c7e..2b66ca9912 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,16 +46,23 @@ RUN cd /opt && \ rm -f android-tools-linux.zip # Grab what's needed in the SDK -RUN mkdir "${ANDROID_HOME}/licenses" && \ - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" RUN sdkmanager --update -# Accept all licenses -RUN yes y | sdkmanager --licenses -RUN sdkmanager 'platform-tools' -RUN sdkmanager 'build-tools;28.0.3' -RUN sdkmanager 'extras;android;m2repository' -RUN sdkmanager 'platforms;android-27' -RUN sdkmanager 'cmake;3.6.4111459' + +# Accept licenses before installing components, no need to echo y for each component +# License is valid for all the standard components in versions installed from this file +# Non-standard components: MIPS system images, preview versions, GDK (Google Glass) and Android Google TV require separate licenses, not accepted there +RUN yes | sdkmanager --licenses + +# SDKs +# Please keep these in descending order! +# The `yes` is for accepting all non-standard tool licenses. +# Please keep all sections in descending order! +RUN yes | sdkmanager \ + 'platform-tools' \ + 'build-tools;28.0.3' \ + 'extras;android;m2repository' \ + 'platforms;android-27' \ + 'cmake;3.6.4111459' # Install the NDK RUN mkdir /opt/android-ndk-tmp && \ diff --git a/examples/objectServerExample/README.md b/examples/objectServerExample/README.md index d3c8558940..f6630def1b 100644 --- a/examples/objectServerExample/README.md +++ b/examples/objectServerExample/README.md @@ -10,11 +10,15 @@ injected into the build configuration. To use a different ObjectServer, simply put the server IP Address into the `build.gradle`, as indicated in the comments, on the lines like this: - buildConfigField "String", "OBJECT_SERVER_IP", "\"${host}\"" + def rosUrl = "" For instance: - buildConfigField "String", "OBJECT_SERVER_IP", "192.168.0.1" + def rosUrl = "https://myinstance.us1.cloud.realm.io" + +or: + + def rosUrl = "http://127.0.0.1:9080" To read more about the Realm Object Server and how to deploy it, see https://realm.io/news/introducing-realm-mobile-platform/ diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index ff0d0d5ccd..44535c85a7 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -1,4 +1,19 @@ +buildscript { + ext.kotlin_version = '1.3.20' + repositories { + google() + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + apply plugin: 'com.android.application' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' android { @@ -13,6 +28,10 @@ android { versionName "1.0" } + dataBinding { + enabled = true + } + buildTypes { // Go to https://cloud.realm.io and copy the URL to your instance. Insert it below. // It will look something like "https://test.us1.cloud.realm.io" @@ -45,6 +64,5 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support:design:27.1.1' implementation 'me.zhanghai.android.materialprogressbar:library:1.3.0' - implementation 'com.jakewharton:butterknife:8.8.1'//TODO:Can be refactored with Native Android Data Binding - annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'//TODO:Can be refactored with Native Android Data Binding + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java deleted file mode 100644 index 26d129cd2d..0000000000 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.examples.objectserver; - -import android.content.Intent; -import android.graphics.PorterDuff; -import android.os.Bundle; -import android.support.annotation.ColorRes; -import android.support.v7.app.AppCompatActivity; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; - -import java.util.Locale; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.annotation.Nonnull; - -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import io.realm.OrderedCollectionChangeSet; -import io.realm.OrderedRealmCollectionChangeListener; -import io.realm.Progress; -import io.realm.ProgressListener; -import io.realm.ProgressMode; -import io.realm.Realm; -import io.realm.RealmResults; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncUser; -import io.realm.examples.objectserver.model.CRDTCounter; -import me.zhanghai.android.materialprogressbar.MaterialProgressBar; - -public class CounterActivity extends AppCompatActivity { - - private final ProgressListener downloadListener = new ProgressListener() { - @Override - public void onChange(@Nonnull Progress progress) { - downloadingChanges.set(!progress.isTransferComplete()); - runOnUiThread(updateProgressBar); - } - }; - private final ProgressListener uploadListener = new ProgressListener() { - @Override - public void onChange(@Nonnull Progress progress) { - uploadingChanges.set(!progress.isTransferComplete()); - runOnUiThread(updateProgressBar); - } - }; - private final Runnable updateProgressBar = new Runnable() { - @Override - public void run() { - updateProgressBar(downloadingChanges.get(), uploadingChanges.get()); - } - }; - - private final AtomicBoolean downloadingChanges = new AtomicBoolean(false); - private final AtomicBoolean uploadingChanges = new AtomicBoolean(false); - - private Realm realm; - private SyncSession session; - private SyncUser user; - - @BindView(R.id.text_counter) TextView counterView; - @BindView(R.id.progressbar) MaterialProgressBar progressBar; - private RealmResults counters; // Keep strong reference to counter to keep change listeners alive. - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_counter); - ButterKnife.bind(this); - } - - @Override - protected void onStart() { - super.onStart(); - user = getLoggedInUser(); - if (user == null) { return; } - - // Create a RealmConfiguration for our user - SyncConfiguration config = user.createConfiguration(BuildConfig.REALM_URL) - .initialData(new Realm.Transaction() { - @Override - public void execute(@Nonnull Realm realm) { - realm.createObject(CRDTCounter.class, user.getIdentity()); - } - }) - .build(); - - // This will automatically sync all changes in the background for as long as the Realm is open - realm = Realm.getInstance(config); - - counterView.setText("-"); - counters = realm.where(CRDTCounter.class).equalTo("name", user.getIdentity()).findAllAsync(); - counters.addChangeListener(new OrderedRealmCollectionChangeListener>() { - @Override - public void onChange(RealmResults counters, OrderedCollectionChangeSet changeSet) { - if (counters.isValid() && !counters.isEmpty()) { - CRDTCounter counter = counters.first(); - counterView.setText(String.format(Locale.US, "%d", counter.getCount())); - } else { - counterView.setText("-"); - } - } - }); - - // Setup progress listeners for indeterminate progress bars - session = SyncManager.getSession(config); - session.addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener); - session.addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener); - } - - @Override - protected void onStop() { - super.onStop(); - if (session != null) { - session.removeProgressListener(downloadListener); - session.removeProgressListener(uploadListener); - session = null; - } - closeRealm(); - user = null; - counters = null; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_counter, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch(item.getItemId()) { - case R.id.action_logout: - closeRealm(); - user.logOut(); - user = getLoggedInUser(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } - - @OnClick(R.id.upper) - public void incrementCounter() { - adjustCounter(1); - } - - @OnClick(R.id.lower) - public void decrementCounter() { - adjustCounter(-1); - } - - private void updateProgressBar(boolean downloading, boolean uploading) { - @ColorRes int color = android.R.color.black; - int visibility = View.VISIBLE; - if (downloading && uploading) { - color = R.color.progress_both; - } else if (downloading) { - color = R.color.progress_download; - } else if (uploading) { - color = R.color.progress_upload; - } else { - visibility = View.GONE; - } - progressBar.getIndeterminateDrawable().setColorFilter(getResources().getColor(color), PorterDuff.Mode.SRC_IN); - progressBar.setVisibility(visibility); - } - - private void adjustCounter(final int adjustment) { - // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI - // thread is HIGHLY discouraged as it might block longer than intended. Use only async transactions. - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(@Nonnull Realm realm) { - CRDTCounter counter = realm.where(CRDTCounter.class).findFirst(); - if (counter != null) { - counter.incrementCounter(adjustment); - } - } - }); - } - - private SyncUser getLoggedInUser() { - SyncUser user = null; - - try { user = SyncUser.current(); } - catch (IllegalStateException ignore) { } - - if (user == null) { - startActivity(new Intent(this, LoginActivity.class)); - } - - return user; - } - - private void closeRealm() { - if (realm != null && !realm.isClosed()) { - realm.close(); - } - } -} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt new file mode 100644 index 0000000000..bc069f88c6 --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.examples.objectserver + +import android.content.Intent +import android.databinding.DataBindingUtil +import android.graphics.PorterDuff +import android.os.Bundle +import android.support.annotation.ColorRes +import android.support.v7.app.AppCompatActivity +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.TextView +import io.realm.* +import io.realm.examples.objectserver.databinding.ActivityCounterBinding +import io.realm.examples.objectserver.model.CRDTCounter +import io.realm.kotlin.createObject +import io.realm.kotlin.syncSession +import io.realm.kotlin.where +import io.realm.log.RealmLog +import me.zhanghai.android.materialprogressbar.MaterialProgressBar +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean + +class CounterActivity : AppCompatActivity() { + + private lateinit var binding: ActivityCounterBinding + + private val downloadListener = ProgressListener { progress -> + downloadingChanges.set(!progress.isTransferComplete) + runOnUiThread(updateProgressBar) + } + private val uploadListener = ProgressListener { progress -> + uploadingChanges.set(!progress.isTransferComplete) + runOnUiThread(updateProgressBar) + } + private val updateProgressBar = Runnable { updateProgressBar(downloadingChanges.get(), uploadingChanges.get()) } + + private val downloadingChanges = AtomicBoolean(false) + private val uploadingChanges = AtomicBoolean(false) + + private lateinit var realm: Realm + private lateinit var session: SyncSession + private var user: SyncUser? = null + + private lateinit var counterView: TextView + private lateinit var progressBar: MaterialProgressBar + private lateinit var counters: RealmResults // Keep strong reference to counter to keep change listeners alive. + + private val loggedInUser: SyncUser? + get() { + var user: SyncUser? = null + + try { + user = SyncUser.current() + } catch (e: IllegalStateException) { + RealmLog.warn(e); + } + + if (user == null) { + startActivity(Intent(this, LoginActivity::class.java)) + } + + return user + } + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_counter) + counterView = binding.textCounter + progressBar = binding.progressbar + binding.upper.setOnClickListener { adjustCounter(1) } + binding.lower.setOnClickListener { adjustCounter(-1) } + } + + override fun onStart() { + super.onStart() + user = loggedInUser + val user = user + if (user != null) { + // Create a RealmConfiguration for our user + val config = user.createConfiguration(BuildConfig.REALM_URL) + .initialData { realm -> realm.createObject(user.identity) } + .build() + + // This will automatically sync all changes in the background for as long as the Realm is open + realm = Realm.getInstance(config) + + counterView.text = "-" + counters = realm.where().equalTo("name", user.identity).findAllAsync() + counters.addChangeListener { counters, _ -> + if (counters.isValid && !counters.isEmpty()) { + val counter = counters.first() + counterView.text = String.format(Locale.US, "%d", counter!!.count) + } else { + counterView.text = "-" + } + } + + // Setup progress listeners for indeterminate progress bars + session = realm.syncSession + session.run { + addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener) + addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener) + } + } + } + + override fun onStop() { + super.onStop() + user?.run { + session.run { + removeProgressListener(downloadListener) + removeProgressListener(uploadListener) + } + realm.close() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_counter, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_logout -> { + realm.close() + val user = user + if (user != null) { + user.logOut() + this.user = loggedInUser + } + true + } + + else -> super.onOptionsItemSelected(item) + } + } + + private fun updateProgressBar(downloading: Boolean, uploading: Boolean) { + @ColorRes val color = when { + downloading && uploading -> R.color.progress_both + downloading -> R.color.progress_download + uploading -> R.color.progress_upload + else -> android.R.color.black + } + progressBar.indeterminateDrawable.setColorFilter(resources.getColor(color), PorterDuff.Mode.SRC_IN) + progressBar.visibility = if (color == android.R.color.black) View.GONE else View.VISIBLE + } + + private fun adjustCounter(adjustment: Int) { + // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI + // thread is HIGHLY discouraged as it might block longer than intended. Use only async transactions. + realm.executeTransactionAsync { realm -> + val counter = realm.where().findFirst() + counter?.incrementCounter(adjustment.toLong()) + } + } + +} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java deleted file mode 100644 index d16b593802..0000000000 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.examples.objectserver; - -import android.app.ProgressDialog; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Toast; - -import javax.annotation.Nonnull; - -import butterknife.BindView; -import butterknife.ButterKnife; -import io.realm.SyncCredentials; -import io.realm.ObjectServerError; -import io.realm.SyncUser; - - -public class LoginActivity extends AppCompatActivity { - @BindView(R.id.input_username) EditText username; - @BindView(R.id.input_password) EditText password; - @BindView(R.id.button_login) Button loginButton; - @BindView(R.id.button_create) Button createUserButton; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_login); - ButterKnife.bind(this); - loginButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - login(false); - } - }); - createUserButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - login(true); - } - }); - } - - public void login(boolean createUser) { - if (!validate()) { - onLoginFailed("Invalid username or password"); - return; - } - - createUserButton.setEnabled(false); - loginButton.setEnabled(false); - - final ProgressDialog progressDialog = new ProgressDialog(LoginActivity.this); - progressDialog.setIndeterminate(true); - progressDialog.setMessage("Authenticating..."); - progressDialog.show(); - - String username = this.username.getText().toString(); - String password = this.password.getText().toString(); - - SyncCredentials creds = SyncCredentials.usernamePassword(username, password, createUser); - SyncUser.Callback callback = new SyncUser.Callback() { - @Override - public void onSuccess(@Nonnull SyncUser user) { - progressDialog.dismiss(); - onLoginSuccess(); - } - - @Override - public void onError(@Nonnull ObjectServerError error) { - progressDialog.dismiss(); - String errorMsg; - switch (error.getErrorCode()) { - case UNKNOWN_ACCOUNT: - errorMsg = "Account does not exists."; - break; - case INVALID_CREDENTIALS: - errorMsg = "User name and password does not match"; - break; - default: - errorMsg = error.toString(); - } - onLoginFailed(errorMsg); - } - }; - - SyncUser.logInAsync(creds, BuildConfig.REALM_AUTH_URL, callback); - } - - @Override - public void onBackPressed() { - // Disable going back to the MainActivity - moveTaskToBack(true); - } - - public void onLoginSuccess() { - loginButton.setEnabled(true); - createUserButton.setEnabled(true); - finish(); - } - - public void onLoginFailed(String errorMsg) { - loginButton.setEnabled(true); - createUserButton.setEnabled(true); - Toast.makeText(getBaseContext(), errorMsg, Toast.LENGTH_LONG).show(); - } - - public boolean validate() { - boolean valid = true; - String email = username.getText().toString(); - String password = this.password.getText().toString(); - - if (email.isEmpty()) { - valid = false; - } - - if (password.isEmpty()) { - valid = false; - } - - return valid; - } -} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt new file mode 100644 index 0000000000..1038e795a8 --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.examples.objectserver + +import android.app.ProgressDialog +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.widget.Button +import android.widget.EditText +import android.widget.Toast +import io.realm.ErrorCode +import io.realm.ObjectServerError +import io.realm.SyncCredentials +import io.realm.SyncUser +import io.realm.examples.objectserver.databinding.ActivityLoginBinding + +class LoginActivity : AppCompatActivity() { + + private lateinit var username: EditText + private lateinit var password: EditText + private lateinit var loginButton: Button + private lateinit var createUserButton: Button + + lateinit private var binding: ActivityLoginBinding + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_login) + username = binding.inputUsername + password = binding.inputPassword + loginButton = binding.buttonLogin + createUserButton = binding.buttonCreate + + loginButton.setOnClickListener { login(false) } + createUserButton.setOnClickListener { login(true) } + } + + private fun login(createUser: Boolean) { + if (!validate()) { + onLoginFailed("Invalid username or password") + return + } + + binding.buttonCreate.isEnabled = false + binding.buttonLogin.isEnabled = false + + val progressDialog = ProgressDialog(this@LoginActivity) + progressDialog.isIndeterminate = true + progressDialog.setMessage("Authenticating...") + progressDialog.show() + + val username = this.username.text.toString() + val password = this.password.text.toString() + + val creds = SyncCredentials.usernamePassword(username, password, createUser) + val callback = object : SyncUser.Callback { + override fun onSuccess(user: SyncUser) { + progressDialog.dismiss() + onLoginSuccess() + } + + override fun onError(error: ObjectServerError) { + progressDialog.dismiss() + val errorMsg: String = when (error.errorCode) { + ErrorCode.UNKNOWN_ACCOUNT -> getString(R.string.login_error_unknown_account) + ErrorCode.INVALID_CREDENTIALS -> getString(R.string.login_error_invalid_credentials) + else -> error.toString() + } + onLoginFailed(errorMsg) + } + } + + SyncUser.logInAsync(creds, BuildConfig.REALM_AUTH_URL, callback) + } + + override fun onBackPressed() { + // Disable going back to the MainActivity + moveTaskToBack(true) + } + + private fun onLoginSuccess() { + loginButton.isEnabled = true + createUserButton.isEnabled = true + finish() + } + + private fun onLoginFailed(errorMsg: String) { + loginButton.isEnabled = true + createUserButton.isEnabled = true + Toast.makeText(baseContext, errorMsg, Toast.LENGTH_LONG).show() + } + + private fun validate(): Boolean = when { + username.text.toString().isEmpty() -> false + password.text.toString().isEmpty() -> false + else -> true + } +} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt similarity index 62% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java rename to examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt index 417fd9d9f5..dc7360fb48 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.java +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2019 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,24 +14,23 @@ * limitations under the License. */ -package io.realm.examples.objectserver; +package io.realm.examples.objectserver -import android.app.Application; -import android.util.Log; +import android.app.Application +import android.util.Log -import io.realm.Realm; -import io.realm.log.RealmLog; +import io.realm.Realm +import io.realm.log.RealmLog -public class MyApplication extends Application { +class MyApplication : Application() { - @Override - public void onCreate() { - super.onCreate(); - Realm.init(this, "ObjectServerExample/" + BuildConfig.VERSION_NAME); + override fun onCreate() { + super.onCreate() + Realm.init(this, "ObjectServerExample/" + BuildConfig.VERSION_NAME) - // Enable full log output when debugging + // Enable more if (BuildConfig.DEBUG) { - RealmLog.setLevel(Log.DEBUG); + RealmLog.setLevel(Log.DEBUG) } } } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java deleted file mode 100644 index 0bca8fd53c..0000000000 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ -package io.realm.examples.objectserver.model; - -import io.realm.MutableRealmInteger; -import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.Required; - -/** - * A named, conflict-free replicated data-type. - */ -public class CRDTCounter extends RealmObject { - @PrimaryKey - private String name; - - @Required - public final MutableRealmInteger counter = MutableRealmInteger.valueOf(0L); - - // Required for Realm - public CRDTCounter() {} - - public CRDTCounter(String name) { this.name = name; } - - public String getName() { return name; } - - public long getCount() { return counter.get().longValue(); } - public void incrementCounter(long delta) { counter.increment(delta); } -} diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt new file mode 100644 index 0000000000..6078bb0512 --- /dev/null +++ b/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.examples.objectserver.model + +import io.realm.MutableRealmInteger +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.Required + +open class CRDTCounter : RealmObject() { + + @PrimaryKey + var name: String = "" + + @Required + private val counter = MutableRealmInteger.valueOf(0L) + + val count: Long + get() = this.counter.get()!!.toLong() + + fun incrementCounter(delta: Long) { + counter.increment(delta) + } + +} diff --git a/examples/objectServerExample/src/main/res/layout/activity_counter.xml b/examples/objectServerExample/src/main/res/layout/activity_counter.xml index a1300b2123..215c9fb47f 100644 --- a/examples/objectServerExample/src/main/res/layout/activity_counter.xml +++ b/examples/objectServerExample/src/main/res/layout/activity_counter.xml @@ -1,49 +1,51 @@ - - - + + + + + + android:layout_height="match_parent"> - - + android:layout_height="match_parent" + android:orientation="vertical"> + + + + + - + + - - - - - - - - + android:layout_height="wrap_content" + android:layout_gravity="top" + android:indeterminate="true" + android:visibility="visible" + app:mpb_progressStyle="horizontal" /> + + + + diff --git a/examples/objectServerExample/src/main/res/layout/activity_login.xml b/examples/objectServerExample/src/main/res/layout/activity_login.xml index 375cdbca98..29f078ebc2 100644 --- a/examples/objectServerExample/src/main/res/layout/activity_login.xml +++ b/examples/objectServerExample/src/main/res/layout/activity_login.xml @@ -1,68 +1,75 @@ - + - + - + - + android:orientation="vertical" + android:paddingLeft="24dp" + android:paddingTop="56dp" + android:paddingRight="24dp"> - + + - + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp"> - + + - - + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp"> - + + + + + + + + + + - - - diff --git a/examples/objectServerExample/src/main/res/values/strings.xml b/examples/objectServerExample/src/main/res/values/strings.xml index b4f90b3676..dbf98e52eb 100644 --- a/examples/objectServerExample/src/main/res/values/strings.xml +++ b/examples/objectServerExample/src/main/res/values/strings.xml @@ -7,4 +7,6 @@ Create account and login Login Logout + Account does not exist. + User name and password do not match. diff --git a/realm/build.gradle b/realm/build.gradle index 53ffc2de3a..9ab569ac19 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -38,7 +38,7 @@ allprojects { def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) project.ext.minSdkVersion = 9 - project.ext.compileSdkVersion = 26 + project.ext.compileSdkVersion = 27 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") group = 'io.realm' From 1ba9dbb972395fdfbfe26f3c5783f83b629eaf85 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 21 Feb 2019 11:32:08 +0100 Subject: [PATCH 1356/2110] Fixed JNI reference not released correctly (#6437) --- CHANGELOG.md | 16 ++++++++++++++++ .../main/cpp/io_realm_internal_OsRealmConfig.cpp | 1 + 2 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61423ce1fa..7640a6d39a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 5.9.1(YYYY-MM-DD) + +### Enhancements +* None + +### Fixed +* [ObjectServer] Reporting too many errors from the native layer resulted in a native crash with `local reference table overflow`. (Issue [#249](https://github.com/realm/realm-java-private/issues/249), since 5.9.0) + +### Compatibility +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None + ## 5.9.0(2019-01-15) ### Enhancements diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index e2b1c7d7bd..c801492d88 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -321,6 +321,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe jstring jsession_path = to_jstring(env, session.get()->path()); env->CallStaticVoidMethod(sync_manager_class, java_error_callback_method, jerror_category, error_code, jerror_message, jsession_path); + env->DeleteLocalRef(jerror_category); env->DeleteLocalRef(jerror_message); env->DeleteLocalRef(jsession_path); }; From f42c5f2569d805d1705389b99250819267598280 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 21 Feb 2019 11:36:13 +0100 Subject: [PATCH 1357/2110] Updated release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7640a6d39a..dcf603b27c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.9.1(YYYY-MM-DD) +## 5.9.1(2019-02-21) ### Enhancements * None From 5ec48197edd9ef66861374e8523e9f3a6281b352 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 21 Feb 2019 11:36:47 +0100 Subject: [PATCH 1358/2110] Release v5.9.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 3f055cf770..92666713cf 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.9.1-SNAPSHOT \ No newline at end of file +5.9.1 \ No newline at end of file From 609b3a9c75ce81336e7a14f925ee601b4cb2bfe5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 21 Feb 2019 11:36:48 +0100 Subject: [PATCH 1359/2110] Prepare next release v5.9.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 92666713cf..3452610e7a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.9.1 \ No newline at end of file +5.9.2-SNAPSHOT \ No newline at end of file From 2ee87b598340231bede0f2dc8ad27bb5cca7b098 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 28 Feb 2019 14:51:22 +0100 Subject: [PATCH 1360/2110] Fix ROS not being able to start (#6446) --- dependencies.list | 2 +- tools/sync_test_server/ros/tsconfig.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dependencies.list b/dependencies.list index e7be39bb39..7bae137f55 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=7e8934a471fa714bf672a9575cd3112470c3294d55e7a13688d04569e707b8 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.16.6 +REALM_OBJECT_SERVER_VERSION=3.18.5 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.1.4 diff --git a/tools/sync_test_server/ros/tsconfig.json b/tools/sync_test_server/ros/tsconfig.json index 8a5ed49104..a5aca56049 100644 --- a/tools/sync_test_server/ros/tsconfig.json +++ b/tools/sync_test_server/ros/tsconfig.json @@ -17,7 +17,6 @@ "es6", "dom.iterable", "scripthost", - "esnext", "esnext.asynciterable" ] }, From 4d2a39a98bdbd993655a3f94d8f63c8093a1569d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 1 Mar 2019 19:36:40 +0100 Subject: [PATCH 1361/2110] Upgrade Gradle and Android Build Tools (#6439) --- Dockerfile | 3 +- README.md | 4 +- build.gradle | 11 --- dependencies.list | 7 +- .../architectureComponentsExample/lint.xml | 1 - examples/build.gradle | 6 -- examples/encryptionExample/lint.xml | 2 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/gridViewExample/lint.xml | 1 - examples/introExample/lint.xml | 1 - examples/jsonExample/lint.xml | 2 - examples/kotlinExample/build.gradle | 2 +- examples/kotlinExample/lint.xml | 1 - examples/migrationExample/lint.xml | 1 - examples/moduleExample/app/lint.xml | 1 - examples/moduleExample/library/lint.xml | 1 - examples/newsreaderExample/lint.xml | 1 - examples/objectServerExample/lint.xml | 1 - examples/rxJavaExample/lint.xml | 1 - examples/threadExample/lint.xml | 1 - examples/unitTestExample/lint.xml | 2 +- gradle-plugin/build.gradle | 6 -- .../gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 55190 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle-plugin/gradlew | 2 +- gradle-plugin/gradlew.bat | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- library-benchmarks/build.gradle | 6 -- .../gradle/wrapper/gradle-wrapper.properties | 2 +- library-build-transformer/build.gradle | 13 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-annotations/build.gradle | 13 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 55190 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-annotations/gradlew | 2 +- realm-annotations/gradlew.bat | 2 +- realm-annotations/settings.gradle | 1 + realm-transformer/build.gradle | 14 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm.properties | 2 - realm/build.gradle | 23 ++--- .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/realm-library/build.gradle | 2 +- .../java/io/realm/MediatorTest.java | 24 +++-- .../io/realm/RealmConfigurationTests.java | 9 +- .../java/io/realm/RealmInterprocessTest.java | 90 +++++++++++------- .../java/io/realm/internal/JNIRowTest.java | 6 +- .../internal/android/ISO8601UtilsTest.java | 44 ++++++--- .../realm-library/src/main/cpp/CMakeLists.txt | 19 +++- 51 files changed, 161 insertions(+), 185 deletions(-) create mode 100644 realm-annotations/settings.gradle delete mode 100644 realm.properties diff --git a/Dockerfile b/Dockerfile index ddcc4b4c7e..052db2bfe0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,7 +65,8 @@ RUN mkdir /opt/android-ndk-tmp && \ ./android-ndk.bin && \ mv android-ndk-r10e /opt/android-ndk && \ rm -rf /opt/android-ndk-tmp && \ - chmod -R a+rX /opt/android-ndk + chmod -R a+rX /opt/android-ndk && \ + echo "Pkg.Desc = Android NDK\nPkg.Revision = 10.0.0" > /opt/android-ndk/source.properties # Make the SDK universally writable RUN chmod -R a+rwX ${ANDROID_HOME} diff --git a/README.md b/README.md index 72b599d448..22aac0936d 100644 --- a/README.md +++ b/README.md @@ -161,10 +161,10 @@ Generating the Javadoc using the command above may generate warnings. The Javado ### Upgrading Gradle Wrappers - All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer to `gradleVersion` property defined in `/realm.properties` to determine Gradle Version of generating wrappers. + All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer to `gradleVersion` property defined in `/dependencies.list` to determine Gradle Version of generating wrappers. We have a script `./tools/update_gradle_wrapper.sh` to automate these steps. When you update Gradle Wrappers, please obey the following steps. - 1. Edit `gradleVersion` property in defined in `/realm.properties` to new Gradle Wrapper version. + 1. Edit `gradleVersion` property in defined in `/dependencies.list` to new Gradle Wrapper version. 2. Execute `/tools/update_gradle_wrapper.sh`. ### Gotchas diff --git a/build.gradle b/build.gradle index 2ff457c052..ad0f929a7e 100644 --- a/build.gradle +++ b/build.gradle @@ -11,12 +11,6 @@ apply plugin: 'ch.netzwerg.release' def currentVersion = file("${projectDir}/version.txt").text.trim() -def props = new Properties() -props.load(new FileInputStream("${rootDir}/realm.properties")) -props.each { key, val -> - project.ext.set(key, val) -} - task assembleAnnotations(type:GradleBuild) { group = 'Build' description = 'Assemble the Realm annotations' @@ -484,8 +478,3 @@ release { versionSuffix = '-SNAPSHOT' tagPrefix = 'v' } - -task wrapper(type: Wrapper) { - gradleVersion = project.gradleVersion - distributionType = 'all' -} diff --git a/dependencies.list b/dependencies.list index 7bae137f55..cfc6079634 100644 --- a/dependencies.list +++ b/dependencies.list @@ -8,9 +8,12 @@ REALM_SYNC_SHA256=7e8934a471fa714bf672a9575cd3112470c3294d55e7a13688d04569e707b8 REALM_OBJECT_SERVER_VERSION=3.18.5 # Common Android settings across projects -GRADLE_BUILD_TOOLS=3.1.4 -ANDROID_BUILD_TOOLS=27.0.3 +GRADLE_BUILD_TOOLS=3.3.1 +ANDROID_BUILD_TOOLS=28.0.3 # Common classpath dependencies +# Gradle 5 is not supported yet: https://issuetracker.google.com/issues/126433059 +gradleVersion=4.10.1 +ndkVersion=r10e BUILD_INFO_EXTRACTOR_GRADLE=4.7.5 GRADLE_BINTRAY_PLUGIN=1.8.4 diff --git a/examples/architectureComponentsExample/lint.xml b/examples/architectureComponentsExample/lint.xml index 6a9810cdcb..da1621f226 100644 --- a/examples/architectureComponentsExample/lint.xml +++ b/examples/architectureComponentsExample/lint.xml @@ -5,6 +5,5 @@ - diff --git a/examples/build.gradle b/examples/build.gradle index 79fefbdc2d..5e91e876da 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,6 @@ allprojects { def currentVersion = file("${rootDir}/../version.txt").text.trim() def props = new Properties() - props.load(new FileInputStream("${rootDir}/../realm.properties")) props.load(new FileInputStream("${rootDir}/../dependencies.list")) props.each { key, val -> project.ext.set(key, val) @@ -76,8 +75,3 @@ allprojects { } } } - -task wrapper(type: Wrapper) { - gradleVersion = project.gradleVersion - distributionType = 'all' -} diff --git a/examples/encryptionExample/lint.xml b/examples/encryptionExample/lint.xml index 6793b0702b..6cbeea83b3 100644 --- a/examples/encryptionExample/lint.xml +++ b/examples/encryptionExample/lint.xml @@ -4,7 +4,5 @@ - - diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 7dc503f149..4e974715fd 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/examples/gridViewExample/lint.xml b/examples/gridViewExample/lint.xml index 6a9810cdcb..da1621f226 100644 --- a/examples/gridViewExample/lint.xml +++ b/examples/gridViewExample/lint.xml @@ -5,6 +5,5 @@ - diff --git a/examples/introExample/lint.xml b/examples/introExample/lint.xml index 6a9810cdcb..da1621f226 100644 --- a/examples/introExample/lint.xml +++ b/examples/introExample/lint.xml @@ -5,6 +5,5 @@ - diff --git a/examples/jsonExample/lint.xml b/examples/jsonExample/lint.xml index a443370a1a..2d341a9e76 100644 --- a/examples/jsonExample/lint.xml +++ b/examples/jsonExample/lint.xml @@ -3,7 +3,5 @@ - - diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 0f7148e338..46a1af33a3 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.2.40' + ext.kotlin_version = '1.3.21' repositories { jcenter() mavenCentral() diff --git a/examples/kotlinExample/lint.xml b/examples/kotlinExample/lint.xml index 7d530f741e..a960fabaf6 100644 --- a/examples/kotlinExample/lint.xml +++ b/examples/kotlinExample/lint.xml @@ -4,6 +4,5 @@ - diff --git a/examples/migrationExample/lint.xml b/examples/migrationExample/lint.xml index 1f5e37cb86..2d341a9e76 100644 --- a/examples/migrationExample/lint.xml +++ b/examples/migrationExample/lint.xml @@ -3,6 +3,5 @@ - diff --git a/examples/moduleExample/app/lint.xml b/examples/moduleExample/app/lint.xml index 1f5e37cb86..2d341a9e76 100644 --- a/examples/moduleExample/app/lint.xml +++ b/examples/moduleExample/app/lint.xml @@ -3,6 +3,5 @@ - diff --git a/examples/moduleExample/library/lint.xml b/examples/moduleExample/library/lint.xml index 6a9810cdcb..da1621f226 100644 --- a/examples/moduleExample/library/lint.xml +++ b/examples/moduleExample/library/lint.xml @@ -5,6 +5,5 @@ - diff --git a/examples/newsreaderExample/lint.xml b/examples/newsreaderExample/lint.xml index 1f5e37cb86..2d341a9e76 100644 --- a/examples/newsreaderExample/lint.xml +++ b/examples/newsreaderExample/lint.xml @@ -3,6 +3,5 @@ - diff --git a/examples/objectServerExample/lint.xml b/examples/objectServerExample/lint.xml index 6a9810cdcb..da1621f226 100644 --- a/examples/objectServerExample/lint.xml +++ b/examples/objectServerExample/lint.xml @@ -5,6 +5,5 @@ - diff --git a/examples/rxJavaExample/lint.xml b/examples/rxJavaExample/lint.xml index 7d530f741e..a960fabaf6 100644 --- a/examples/rxJavaExample/lint.xml +++ b/examples/rxJavaExample/lint.xml @@ -4,6 +4,5 @@ - diff --git a/examples/threadExample/lint.xml b/examples/threadExample/lint.xml index 6a9810cdcb..da1621f226 100644 --- a/examples/threadExample/lint.xml +++ b/examples/threadExample/lint.xml @@ -5,6 +5,5 @@ - diff --git a/examples/unitTestExample/lint.xml b/examples/unitTestExample/lint.xml index 1f5e37cb86..a28ca6e6e8 100644 --- a/examples/unitTestExample/lint.xml +++ b/examples/unitTestExample/lint.xml @@ -3,6 +3,6 @@ - + diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index bd154d1785..ea02b04bd6 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -18,7 +18,6 @@ apply plugin: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.bintray' def props = new Properties() -props.load(new FileInputStream("${rootDir}/../realm.properties")) props.load(new FileInputStream("${rootDir}/../dependencies.list")) props.each { key, val -> project.ext.set(key, val) @@ -84,11 +83,6 @@ sourceSets { compileJava.dependsOn generateVersionClass -task wrapper(type: Wrapper) { - gradleVersion = project.gradleVersion - distributionType = 'all' -} - def commonPom = { licenses { license { diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 1948b9074f1016d15d505d185bc3f73deb82d8c8..87b738cbd051603d91cc39de6cb000dd98fe6b02 100644 GIT binary patch literal 55190 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 z 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 project.ext.set(key, val) } } -task wrapper(type: Wrapper) { - gradleVersion = project.gradleVersion - distributionType = 'all' -} - apply plugin: 'com.android.library' apply plugin: 'realm-android' diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index 7dc503f149..4e974715fd 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-build-transformer/build.gradle b/library-build-transformer/build.gradle index eb4f5ed791..f6268d9368 100644 --- a/library-build-transformer/build.gradle +++ b/library-build-transformer/build.gradle @@ -17,14 +17,6 @@ buildscript { } } -allprojects { - def props = new Properties() - props.load(new FileInputStream("${rootDir}/../realm.properties")) - props.each { key, val -> - project.ext.set(key, val) - } -} - group = 'io.realm' version = file("${projectDir}/../version.txt").text.trim() @@ -104,8 +96,3 @@ publishing { } } } - -task wrapper(type: Wrapper) { - gradleVersion = project.gradleVersion - distributionType = 'all' -} diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.jar b/library-build-transformer/gradle/wrapper/gradle-wrapper.jar index 1948b9074f1016d15d505d185bc3f73deb82d8c8..0d4a9516871afd710a9d84d89e31ba77745607bd 100644 GIT binary patch delta 64 zcmeBO$=th=d4f61&s)=~CR#_bek%G{#5nn2s`SP!L5H|l7y`W6IVzO3lP3!t_6IB4 PoP7A601H^i^@;}ochnli delta 64 zcmeBO$=th=d4f5M=jU(k6RjgzKNNi|Vw`+1ReIx=phH{?3<2Kk9Pfm6L?#Oy_6IB4 PoP7A601H^i^@;}oU3MB9 diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties index 7dc503f149..4e974715fd 100644 --- a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index f785066656..ed07be3e45 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -11,14 +11,6 @@ buildscript { } } -allprojects { - def props = new Properties() - props.load(new FileInputStream("${rootDir}/../realm.properties")) - props.each { key, val -> - project.ext.set(key, val) - } -} - apply plugin: 'java' apply plugin: 'maven' apply plugin: 'maven-publish' @@ -114,8 +106,3 @@ artifactory { } } } - -task wrapper(type: Wrapper) { - gradleVersion = project.gradleVersion - distributionType = 'all' -} diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.jar b/realm-annotations/gradle/wrapper/gradle-wrapper.jar index 1948b9074f1016d15d505d185bc3f73deb82d8c8..87b738cbd051603d91cc39de6cb000dd98fe6b02 100644 GIT binary patch literal 55190 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 z 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 - project.ext.set(key, val) - } -} - apply plugin: 'kotlin' apply plugin: 'java' apply plugin: 'maven' @@ -167,9 +159,3 @@ artifactory { } } } - -task wrapper(type: Wrapper) { - gradleVersion = project.gradleVersion - distributionType = 'all' -} - diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 1948b9074f1016d15d505d185bc3f73deb82d8c8..0d4a9516871afd710a9d84d89e31ba77745607bd 100644 GIT binary patch delta 64 zcmeBO$=th=d4f61&s)=~CR#_bek%G{#5nn2s`SP!L5H|l7y`W6IVzO3lP3!t_6IB4 PoP7A601H^i^@;}ochnli delta 64 zcmeBO$=th=d4f5M=jU(k6RjgzKNNi|Vw`+1ReIx=phH{?3<2Kk9Pfm6L?#Oy_6IB4 PoP7A601H^i^@;}oU3MB9 diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 7dc503f149..4e974715fd 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm.properties b/realm.properties deleted file mode 100644 index 1a1be59f2f..0000000000 --- a/realm.properties +++ /dev/null @@ -1,2 +0,0 @@ -gradleVersion=4.9 -ndkVersion=r10e diff --git a/realm/build.gradle b/realm/build.gradle index 53ffc2de3a..8f591b39bb 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,8 +1,8 @@ buildscript { def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) - ext.kotlin_version = '1.2.50' - ext.dokka_version = '0.9.16' + ext.kotlin_version = '1.3.21' + ext.dokka_version = '0.9.17' repositories { mavenLocal() google() @@ -14,7 +14,7 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get('GRADLE_BUILD_TOOLS')}" classpath 'de.undercouch:gradle-download-task:3.3.0' - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' + classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.4' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2' @@ -29,18 +29,14 @@ buildscript { } allprojects { - def props = new Properties() - props.load(new FileInputStream("${rootDir}/../realm.properties")) - props.each { key, val -> - project.ext.set(key, val) - } - def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) + projectDependencies.each { key, val -> + project.ext.set(key, val) + } project.ext.minSdkVersion = 9 - project.ext.compileSdkVersion = 26 + project.ext.compileSdkVersion = 28 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") - group = 'io.realm' version = file("${rootDir}/../version.txt").text.trim() repositories { @@ -49,8 +45,3 @@ allprojects { jcenter() } } - -task wrapper(type: Wrapper) { - gradleVersion = project.gradleVersion - distributionType = 'all' -} diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 7dc503f149..4e974715fd 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index b0e7425827..4562cf3be7 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -286,7 +286,7 @@ task findbugs(type: FindBugs) { effort = "default" reportLevel = "medium" excludeFilter = file("${projectDir}/../config/findbugs/findbugs-filter.xml") - classes = files("${projectDir}/build/intermediates/classes") + classes = files("${projectDir}/build/intermediates/javac") source = fileTree('src/main/java/') classpath = files() reports { diff --git a/realm/realm-library/src/androidTest/java/io/realm/MediatorTest.java b/realm/realm-library/src/androidTest/java/io/realm/MediatorTest.java index 377f19eddd..29c034cfa7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/MediatorTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/MediatorTest.java @@ -16,7 +16,10 @@ package io.realm; -import android.test.AndroidTestCase; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; import java.util.Arrays; @@ -30,10 +33,16 @@ import io.realm.internal.modules.CompositeMediator; import io.realm.internal.modules.FilterableMediator; -public class MediatorTest extends AndroidTestCase { +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class MediatorTest { @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes") - public void testMediatorsEquality() { + @Test + public void mediatorsEquality() { final DefaultRealmModuleMediator defaultMediator = new DefaultRealmModuleMediator(); final CompositeMediator compositeMediator = new CompositeMediator(defaultMediator); final FilterableMediator filterableMediator = new FilterableMediator(defaultMediator, defaultMediator.getModelClasses()); @@ -60,7 +69,8 @@ public void testMediatorsEquality() { assertEquals(filterableMediator.hashCode(), filterableMediator.hashCode()); } - public void testCompositeMediatorModelClassesCount() { + @Test + public void compositeMediatorModelClassesCount() { final CompositeMediator mediator = new CompositeMediator( new HumanModuleMediator(), new AnimalModuleMediator() @@ -72,7 +82,8 @@ public void testCompositeMediatorModelClassesCount() { assertEquals(modelsInHumanModule + modelsInAnimalModule, mediator.getModelClasses().size()); } - public void testFilterableMediatorModelClassesCount() { + @Test + public void filterableMediatorModelClassesCount() { //noinspection unchecked final FilterableMediator mediator = new FilterableMediator(new AnimalModuleMediator(), Arrays.>asList(Cat.class, CatOwner.class)); @@ -83,7 +94,8 @@ public void testFilterableMediatorModelClassesCount() { assertFalse(mediator.getModelClasses().contains(AllTypes.class)); } - public void testDefaultMediatorWasTransformed() { + @Test + public void defaultMediatorWasTransformed() { final DefaultRealmModuleMediator defaultMediator = new DefaultRealmModuleMediator(); assertTrue(defaultMediator.transformerApplied()); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 3e9a2e32d4..06f9cacced 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -19,7 +19,6 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import android.test.MoreAsserts; import org.junit.After; import org.junit.Before; @@ -31,6 +30,7 @@ import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.Set; import io.reactivex.Flowable; @@ -58,6 +58,7 @@ import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -611,12 +612,12 @@ public void encryptionKey_keyStorage() throws Exception { // Generates a different key and assigns it to the same variable. byte[] newKey = TestHelper.getRandomKey(67890); - MoreAsserts.assertNotEqual(key, newKey); + assertFalse(Arrays.equals(key, newKey)); key = newKey; - MoreAsserts.assertEquals(key, newKey); + assertArrayEquals(key, newKey); // Ensures that the stored key did not change. - MoreAsserts.assertEquals(oldKey, config.getEncryptionKey()); + assertArrayEquals(oldKey, config.getEncryptionKey()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java index 9cb082391d..55dba9fe68 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java @@ -29,7 +29,15 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; -import android.test.AndroidTestCase; +import android.support.test.InstrumentationRegistry; +import android.support.test.annotation.UiThreadTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -39,6 +47,10 @@ import io.realm.entities.AllTypesModelModule; import io.realm.services.RemoteProcessService; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + // This is built for testing multi processes related cases. // To build a test case, create an InterprocessHandler in your test case. This handler will run in the newly @@ -52,7 +64,9 @@ // 1. Open two Realms // B. Open three Realms // 2. assertTrue("OK, remote process win. You can open more Realms than I do in the main local process", false); -public class RealmInterprocessTest extends AndroidTestCase { +@Ignore // FIXME Needs to be upgraded to support JUnit4: https://github.com/realm/realm-java/issues/6452 +@RunWith(AndroidJUnit4.class) +public class RealmInterprocessTest { private Realm testRealm; private Messenger remoteMessenger; @@ -81,31 +95,31 @@ public void onServiceDisconnected(ComponentName componentName) { // By overloading this method, we create a new thread and looper to run the real case. And use latch to wait until // it is finished. Then we can get rid of creating the thread in the test method, using array to store exception, many // levels of nested code. Make the test case more nature. - @Override - public void runBare() throws Throwable { - final Throwable[] throwableArray = new Throwable[1]; - final CountDownLatch latch = new CountDownLatch(1); - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - Looper.prepare(); - try { - RealmInterprocessTest.super.runBare(); - } catch (Throwable throwable) { - throwableArray[0] = throwable; - } finally { - latch.countDown(); - } - } - }); - - thread.start(); - TestHelper.awaitOrFail(latch); - - if (throwableArray[0] != null) { - throw throwableArray[0]; - } - } +// @Override +// public void runBare() throws Throwable { +// final Throwable[] throwableArray = new Throwable[1]; +// final CountDownLatch latch = new CountDownLatch(1); +// Thread thread = new Thread(new Runnable() { +// @Override +// public void run() { +// Looper.prepare(); +// try { +// RealmInterprocessTest.super.runBare(); +// } catch (Throwable throwable) { +// throwableArray[0] = throwable; +// } finally { +// latch.countDown(); +// } +// } +// }); +// +// thread.start(); +// TestHelper.awaitOrFail(latch); +// +// if (throwableArray[0] != null) { +// throw throwableArray[0]; +// } +// } // Helper handler to make it easy to interact with remote service process. @SuppressLint("HandlerLeak") // SuppressLint bug, doesn't work @@ -154,9 +168,8 @@ public void handleMessage(Message msg) { } } - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { Realm.deleteRealm(getConfiguration()); // Starts the testing service. @@ -170,8 +183,8 @@ private RealmConfiguration getConfiguration() { return new RealmConfiguration.Builder(getContext()).modules(new AllTypesModelModule()).build(); } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { int counter = 10; if (testRealm != null) { testRealm.close(); @@ -192,7 +205,6 @@ protected void tearDown() throws Exception { Thread.sleep(300); counter--; } - super.tearDown(); } // Calls this to trigger the next step of service process. @@ -221,6 +233,10 @@ private ActivityManager.RunningServiceInfo getServiceInfo() { return null; } + private Context getContext() { + return InstrumentationRegistry.getTargetContext(); + } + // Gets the remote process info if it is alive. private ActivityManager.RunningAppProcessInfo getRemoteProcessInfo() { ActivityManager manager = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); @@ -236,7 +252,9 @@ private ActivityManager.RunningAppProcessInfo getRemoteProcessInfo() { // A. Opens a realm, closes it, then calls Runtime.getRuntime().exit(0). // 1. Waits 3 seconds to see if the service process existed. - public void testExitProcess() { + @Test + @UiThreadTest + public void exitProcess() { new InterprocessHandler(new Runnable() { @Override public void run() { @@ -281,7 +299,9 @@ public void handleMessage(Message msg) { // 1. Main process creates Realm, write one object. // A. Service process opens Realm, check if there is one and only one object. - public void testCreateInitialRealm() throws InterruptedException { + @Test + @UiThreadTest + public void createInitialRealm() throws InterruptedException { new InterprocessHandler(new Runnable() { @Override public void run() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java index 8b62d0765a..3eea1c4efd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java @@ -18,7 +18,6 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; -import android.test.MoreAsserts; import org.junit.After; import org.junit.Before; @@ -37,6 +36,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -97,7 +97,7 @@ public void execute(Table table) { assertEquals(1.3, row.getDouble(3), Double.MIN_NORMAL); assertEquals(true, row.getBoolean(4)); assertEquals(new Date(0), row.getDate(5)); - MoreAsserts.assertEquals(data, row.getBinaryByteArray(6)); + assertArrayEquals(data, row.getBinaryByteArray(6)); row.setString(0, "a"); row.setLong(1, 1); @@ -115,7 +115,7 @@ public void execute(Table table) { assertEquals(9.9, row.getDouble(3), Double.MIN_NORMAL); assertEquals(false, row.getBoolean(4)); assertEquals(new Date(10000), row.getDate(5)); - MoreAsserts.assertEquals(newData, row.getBinaryByteArray(6)); + assertArrayEquals(newData, row.getBinaryByteArray(6)); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java index 492b34b359..63042386a8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java @@ -16,7 +16,11 @@ */ package io.realm.internal.android; -import android.test.AndroidTestCase; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import java.text.ParseException; import java.text.ParsePosition; @@ -26,17 +30,21 @@ import java.util.TimeZone; import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + /** * @see ISO8601Utils * @see Original Source */ -public class ISO8601UtilsTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +public class ISO8601UtilsTest { private Date date; private Date dateWithoutTime; private Date dateZeroMillis; private Date dateZeroSecondAndMillis; - @Override + @Before public void setUp() { Calendar cal = new GregorianCalendar(2007, 8 - 1, 13, 19, 51, 23); cal.setTimeZone(TimeZone.getTimeZone("GMT")); @@ -54,7 +62,8 @@ public void setUp() { } - public void testParse() throws java.text.ParseException { + @Test + public void parse() throws java.text.ParseException { Date d = ISO8601Utils.parse("2007-08-13T19:51:23.789Z", new ParsePosition(0)); assertEquals(date, d); @@ -65,7 +74,8 @@ public void testParse() throws java.text.ParseException { assertEquals(date, d); } - public void testParseShortDate() throws java.text.ParseException { + @Test + public void parseShortDate() throws java.text.ParseException { Date d = ISO8601Utils.parse("20070813T19:51:23.789Z", new ParsePosition(0)); assertEquals(date, d); @@ -76,7 +86,8 @@ public void testParseShortDate() throws java.text.ParseException { assertEquals(date, d); } - public void testParseShortTime() throws java.text.ParseException { + @Test + public void parseShortTime() throws java.text.ParseException { Date d = ISO8601Utils.parse("2007-08-13T195123.789Z", new ParsePosition(0)); assertEquals(date, d); @@ -87,7 +98,8 @@ public void testParseShortTime() throws java.text.ParseException { assertEquals(date, d); } - public void testParseShortDateTime() throws java.text.ParseException { + @Test + public void parseShortDateTime() throws java.text.ParseException { Date d = ISO8601Utils.parse("20070813T195123.789Z", new ParsePosition(0)); assertEquals(date, d); @@ -98,7 +110,8 @@ public void testParseShortDateTime() throws java.text.ParseException { assertEquals(date, d); } - public void testParseWithoutTime() throws ParseException { + @Test + public void parseWithoutTime() throws ParseException { Date d = ISO8601Utils.parse("2007-08-13Z", new ParsePosition(0)); assertEquals(dateWithoutTime, d); @@ -112,7 +125,8 @@ public void testParseWithoutTime() throws ParseException { assertEquals(dateWithoutTime, d); } - public void testParseOptional() throws java.text.ParseException { + @Test + public void parseOptional() throws java.text.ParseException { Date d = ISO8601Utils.parse("2007-08-13T19:51Z", new ParsePosition(0)); assertEquals(dateZeroSecondAndMillis, d); @@ -123,7 +137,8 @@ public void testParseOptional() throws java.text.ParseException { assertEquals(dateZeroSecondAndMillis, d); } - public void testTimeZoneDesignator() throws java.text.ParseException { + @Test + public void timeZoneDesignator() throws java.text.ParseException { Date d = ISO8601Utils.parse("2007-08-13T21:51+02:00", new ParsePosition(0)); assertEquals(dateZeroSecondAndMillis, d); @@ -134,7 +149,8 @@ public void testTimeZoneDesignator() throws java.text.ParseException { assertEquals(dateZeroSecondAndMillis, d); } - public void testParseRfc3339Examples() throws java.text.ParseException { + @Test + public void parseRfc3339Examples() throws java.text.ParseException { // Two digit milliseconds. Date d = ISO8601Utils.parse("1985-04-12T23:20:50.52Z", new ParsePosition(0)); assertEquals(newDate(1985, 4, 12, 23, 20, 50, 520, 0), d); @@ -155,7 +171,8 @@ public void testParseRfc3339Examples() throws java.text.ParseException { assertEquals(newDate(1937, 1, 1, 12, 0, 27, 870, 20), d); } - public void testFractionalSeconds() throws java.text.ParseException { + @Test + public void fractionalSeconds() throws java.text.ParseException { Date d = ISO8601Utils.parse("1970-01-01T00:00:00.9Z", new ParsePosition(0)); assertEquals(newDate(1970, 1, 1, 0, 0, 0, 900, 0), d); @@ -190,7 +207,8 @@ public void testFractionalSeconds() throws java.text.ParseException { assertEquals(newDate(1970, 1, 1, 0, 0, 0, 214, 2 * 60), d); } - public void testDecimalWithoutDecimalPointButNoFractionalSeconds() throws java.text.ParseException { + @Test + public void decimalWithoutDecimalPointButNoFractionalSeconds() throws java.text.ParseException { try { ISO8601Utils.parse("1970-01-01T00:00:00.Z", new ParsePosition(0)); fail(); diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index d1bd38ee21..70311a5dc4 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -17,6 +17,13 @@ ########################################################################### cmake_minimum_required(VERSION 3.6.0) +FUNCTION(capitalizeFirstLetter var value) + string(SUBSTRING ${value} 0 1 firstLetter) + string(TOUPPER ${firstLetter} firstLetter) + string(REGEX REPLACE "^.(.*)" "${firstLetter}\\1" value "${value}") + set(${var} "${value}" PARENT_SCOPE) +ENDFUNCTION(capitalizeFirstLetter) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake") # find javah @@ -52,9 +59,17 @@ else() set(build_SYNC ON) endif() -# Generate JNI header files. Each build has its own JNI header in its build_dir/jni_include. +# Format strings used to represent build parameters: Variant and Type string(TOLOWER ${CMAKE_BUILD_TYPE} build_type_FOLDER) -set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/classes/${REALM_FLAVOR}/${build_type_FOLDER}/) +set(realmFlavorCap "") +set(buildTypeCap "") +capitalizeFirstLetter(realmFlavorCap "${REALM_FLAVOR}") +capitalizeFirstLetter(buildTypeCap "${CMAKE_BUILD_TYPE}") + +# Generate JNI header files. Each build has its own JNI header in its build_dir/jni_include. +# WARNING: The classes_PATH is not part the public API offered by the Android Gradle Plugin +# so it might change without warning when upgrading the plugin. +set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/compile${realmFlavorCap}${buildTypeCap}JavaWithJavac/classes/) set(classes_LIST io.realm.RealmQuery io.realm.internal.Table io.realm.internal.CheckedRow From 06bab97b99abdbacf4fa37713c289c3570f5b6b9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 20 Mar 2019 10:06:03 +0100 Subject: [PATCH 1362/2110] Better nullability docs (#6470) --- .../java/io/realm/annotations/Required.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/realm-annotations/src/main/java/io/realm/annotations/Required.java b/realm-annotations/src/main/java/io/realm/annotations/Required.java index 16750abcf8..7f2c4aa96e 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/Required.java +++ b/realm-annotations/src/main/java/io/realm/annotations/Required.java @@ -21,23 +21,25 @@ import java.lang.annotation.Target; /** - * This annotation will mark the field or the element of a primitive {@link io.realm.RealmList} as not nullable. + * This annotation will mark the field or the element in {@link io.realm.RealmList} as not nullable. *

            * When a field of type {@code Boolean, Byte, Short, Integer, Long, Float, Double, String, byte[], Date} is annotated - * with {@link Required}, it cannot be set to {@code null}. + * with {@link Required}, it cannot be set to {@code null} and Realm will throw an exception if it happens. *

            - * Fields with primitive types are implicitly required. + * Fields with primitive types are implicitly required. Note, {@code String} is not a primitive type, so in Java + * it is default nullable unless it is marked {@code \@Required}. In Kotlin the reverse is true, so a {@code String} is + * non-null. To specify a nullable String in Kotlin you should use {@code String?}. *

            - * When a primitive {@link io.realm.RealmList} ({@code RealmList, RealmList, RealmList, - * RealmList, RealmList, RealmList, RealmList, RealmList, RealmList, - * RealmList}) is annotated with {@link Required}, it cannot contain {@code null} values. + * If this annotation is used on a {@code RealmList}, the annotation is applied to the elements inside + * the list and not the list itself. The list itself is always non-null. This means that a list marked with this + * annotation are never allowed to hold {@code null} values even if the datatype would otherwise allow it. + * Realm will throw an exception if you attempt to store null values into a list marked {@code \@Required}. *

            - * The {@link io.realm.RealmList} field itself is required always. - *

            - * Compiling will fail when fields with other types have {@link Required} annotation. + * Compiling will fail if the {@link Required} annotation is put an a {@link io.realm.RealmList} containing references to other + * Realm objects. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Required { -} \ No newline at end of file +} From 40bd57486076f2bba79f8ccc8891f07137c8dcba Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 21 Mar 2019 20:55:29 +0100 Subject: [PATCH 1363/2110] Better Subscription lifecycle management (#6456) --- CHANGELOG.md | 19 ++ .../java/io/realm/IOSRealmTests.java | 4 +- .../java/io/realm/internal/JNINativeTest.java | 12 + .../java/io/realm/SessionTests.java | 3 +- .../java/io/realm/SyncedRealmQueryTests.java | 152 ++++++++++++- ...rustManagerCertificateValidationTests.java | 2 + .../src/main/cpp/io_realm_RealmQuery.cpp | 5 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 3 + .../main/cpp/io_realm_internal_TestUtil.cpp | 10 + .../io_realm_internal_sync_OsSubscription.cpp | 4 +- realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/util.cpp | 3 + realm/realm-library/src/main/cpp/util.hpp | 30 ++- .../src/main/java/io/realm/BaseRealm.java | 1 + .../src/main/java/io/realm/RealmQuery.java | 212 +++++++++++++++++- .../java/io/realm/internal/OsRealmConfig.java | 14 +- .../internal/SubscriptionAwareOsResults.java | 9 +- .../main/java/io/realm/internal/TestUtil.java | 5 + .../src/main/java/io/realm/internal/Util.java | 9 + .../realm/internal/sync/OsSubscription.java | 7 +- .../internal/sync/SubscriptionAction.java | 30 ++- .../main/java/io/realm/sync/Subscription.java | 157 ++++++++++++- .../objectserver/QueryBasedSyncTests.java | 159 +++++++++++++ .../testUtils/java/io/realm/TestHelper.java | 4 +- 24 files changed, 810 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba50900c1a..6a205113f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## 5.10.0(YYYY-MM-DD) + +## Enhancements +* [ObjectServer] Added 4 new fields to query-based Subscriptions: `createdAt`, `updatedAt`, `expiresAt` and `timeToLive`. These make it possible to better reason about and control current subscriptions. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) +* [ObjectServer] Added the option of updating the query controlled by a Subscription using either `RealmQuery.findAllAsync(String name, boolean update)`, `RealmQuery.subscribe(String name, boolean update)` or `Subscription.setQuery(RealmQuery query)`. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) +* [ObjectServer] Added the option of setting a time-to-live for subscriptions. Setting this will automatically delete the subscription after the provided TTL has expired and the subscription hasn't been used. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) + +## Fixed +* Dates returned from the Realm file no longer overflow or underflow if they exceed `Long.MAX_VALUE` or `Long.MIN_VALUE` but instead clamp to their respective value. (Issue [#2722](https://github.com/realm/realm-java/issues/2722)) + +## Compatibility +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats). +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +## Internal +* Updated to Object Store commit: e9819ed9c77ed87b5d7bed416a76cd5bcf255802 + + ## 5.9.1(2019-02-21) ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index bd1ed35c67..e7ab4d0c69 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -163,7 +163,7 @@ public void iOSDataTypesMinimumValues() throws IOException { assertEquals(-Double.MAX_VALUE, obj.getDoubleCol(), 0D); assertArrayEquals(new byte[0], obj.getByteCol()); assertEquals("", obj.getStringCol()); - assertEquals(0x8000000000000000L * 1000L, obj.getDateCol().getTime()); + assertEquals(Long.MIN_VALUE, obj.getDateCol().getTime()); } } @@ -184,7 +184,7 @@ public void iOSDataTypesMaximumValues() throws IOException { assertEquals(Double.MAX_VALUE, obj.getDoubleCol(), 0D); assertArrayEquals(new byte[0], obj.getByteCol()); assertEquals("", obj.getStringCol()); - assertEquals(0x8000000000000000L * 1000L, obj.getDateCol().getTime()); + assertEquals(Long.MIN_VALUE, obj.getDateCol().getTime()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNINativeTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNINativeTest.java index 8847528a20..b8dbec776b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNINativeTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNINativeTest.java @@ -38,4 +38,16 @@ public void nativeExceptions() { } } } + + @Test + public void clampMaxTimestamps() { + assertEquals(Long.MAX_VALUE, TestUtil.getDateFromTimestamp(Long.MAX_VALUE, 999999999)); + assertEquals(Long.MAX_VALUE, TestUtil.getDateFromTimestamp((Long.MAX_VALUE / 1000) + 1, 0)); // 1 second above MAX in milliseconds + } + + @Test + public void clampMinTimestamps() { + assertEquals(Long.MIN_VALUE, TestUtil.getDateFromTimestamp(Long.MIN_VALUE, -999999999)); + assertEquals(Long.MIN_VALUE, TestUtil.getDateFromTimestamp((Long.MIN_VALUE / 1000) - 1, 0)); // 1 second below MIN in milliseconds + } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 914b2e76c4..a5c0c12c32 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -563,11 +563,10 @@ public void isConnected_falseForInvalidUser() { } @Test - public void close_doesNotThrowIfCalledWhenRealmIsClosed() { + public void stop_doesNotThrowIfCalledWhenRealmIsClosed() { Realm realm = Realm.getInstance(configuration); SyncSession session = SyncManager.getSession(configuration); realm.close(); session.stop(); - assertEquals(SyncSession.State.INACTIVE, session.getState()); } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java index 53ff16cdc3..1037ff8200 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java @@ -15,6 +15,7 @@ */ package io.realm; +import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; import org.junit.After; @@ -23,6 +24,11 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import java.util.Date; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.Dog; import io.realm.rule.RunInLooperThread; @@ -63,6 +69,10 @@ public void tearDown() { } } + private String randomName() { + return UUID.randomUUID().toString(); + } + private Realm getPartialRealm() { SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/partialSync") .build(); @@ -83,27 +93,90 @@ public void subscribe() { realm = getPartialRealm(); realm.beginTransaction(); RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); + Date now = new Date(); + SystemClock.sleep(2); Subscription sub = query.subscribe(); assertTrue(sub.getName().startsWith("[AllTypes] ")); assertEquals(Subscription.State.PENDING, sub.getState()); assertEquals("", sub.getErrorMessage()); assertEquals(query.getDescription(), sub.getQueryDescription()); assertEquals("AllTypes", sub.getQueryClassName()); + assertTrue(now.getTime() < sub.getCreatedAt().getTime()); + assertTrue(now.getTime() < sub.getUpdatedAt().getTime()); + assertTrue(sub.getCreatedAt().getTime() == sub.getUpdatedAt().getTime()); + assertEquals(Long.MAX_VALUE, sub.getTimeToLive()); + assertEquals(new Date(Long.MAX_VALUE), sub.getExpiresAt()); } @Test public void subscribe_withName() { + String name = randomName(); realm = getPartialRealm(); realm.beginTransaction(); RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); - Subscription sub = query.subscribe("sub"); - assertEquals("sub", sub.getName()); + Subscription sub = query.subscribe(name); + assertEquals(name, sub.getName()); assertEquals(Subscription.State.PENDING, sub.getState()); assertEquals("", sub.getErrorMessage()); assertEquals(query.getDescription(), sub.getQueryDescription()); assertEquals("AllTypes", sub.getQueryClassName()); } + @Test + public void subscribe_withTimeToLive() { + realm = getPartialRealm(); + realm.beginTransaction(); + RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); + Date now = new Date(); + SystemClock.sleep(2); + Subscription sub = query.subscribe(randomName(), 0, TimeUnit.MILLISECONDS); + assertTrue(now.getTime() < sub.getCreatedAt().getTime()); + assertEquals(sub.getCreatedAt(), sub.getUpdatedAt()); + assertEquals(sub.getUpdatedAt(), sub.getExpiresAt()); + assertEquals(0, sub.getTimeToLive()); + } + + @Test + public void subscribeOrUpdate() { + String name = randomName(); + realm = getPartialRealm(); + realm.beginTransaction(); + RealmQuery query1 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); + Subscription sub1 = query1.subscribe(name); + Date firstUpdate = sub1.getUpdatedAt(); + RealmQuery query2 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_BOOLEAN, false); + SystemClock.sleep(2); + Subscription sub2 = query2.subscribeOrUpdate(name); + assertEquals(sub1, sub2); + assertEquals(query2.getDescription(), sub2.getQueryDescription()); + assertTrue(firstUpdate.getTime() < sub2.getUpdatedAt().getTime()); + } + + @Test + public void subscribeOrUpdate_failsWithDifferentQueryType() { + String name = randomName(); + realm = getPartialRealm(); + realm.beginTransaction(); + realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo").subscribe(name); + try { + realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_BOOLEAN, false).subscribeOrUpdate(name); + fail(); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void subscribeOrUpdate_withTimeToLive() { + String name = randomName(); + realm = getPartialRealm(); + realm.beginTransaction(); + realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo").subscribe(name, 10, TimeUnit.MILLISECONDS); + RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_BOOLEAN, false); + Subscription sub = query.subscribeOrUpdate(name, 20, TimeUnit.DAYS); + assertEquals(TimeUnit.MILLISECONDS.convert(20, TimeUnit.DAYS), sub.getTimeToLive()); + assertEquals(query.getDescription(), sub.getQueryDescription()); + } + @Test public void subscribe_throwIfNameIsAlreadyUsed() { realm = getPartialRealm(); @@ -177,4 +250,79 @@ public void subscribe_throwIfRealmClosed() { } catch (IllegalStateException ignore) { } } + + @Test + public void subscription_setQuery() { + realm = getPartialRealm(); + realm.beginTransaction(); + RealmQuery query1 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); + Date now = new Date(); + SystemClock.sleep(2); + Subscription sub = query1.subscribe("sub3"); + RealmQuery query2 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_BOOLEAN, false); + assertEquals("AllTypes", sub.getQueryClassName()); + assertTrue(now.getTime() < sub.getUpdatedAt().getTime()); + Date query1Updated = sub.getUpdatedAt(); + SystemClock.sleep(2); + sub.setQuery(query2); + assertEquals(query2.getDescription(), sub.getQueryDescription()); + assertEquals("AllTypes", sub.getQueryClassName()); + assertTrue(query1Updated.getTime() < sub.getUpdatedAt().getTime()); + } + + @Test + public void subscription_setQuery_wrongTypeThrows() { + realm = getPartialRealm(); + realm.beginTransaction(); + RealmQuery query1 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); + Subscription sub = query1.subscribe("sub4"); + RealmQuery query2 = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_BOOLEAN, false); + try { + sub.setQuery(query2); + fail(); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("It is only allowed to replace a query")); + } + } + + @Test + public void subscription_setTimeToLive() { + realm = getPartialRealm(); + realm.beginTransaction(); + + Subscription sub = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo").subscribe(); + assertEquals(Long.MAX_VALUE, sub.getExpiresAt().getTime()); + assertEquals(Long.MAX_VALUE, sub.getTimeToLive()); + + Date now = new Date(); + Date now_plus_1_sec = new Date(now.getTime() + 1000); + Date now_plus_11_sec = new Date(now.getTime() + 11000); + SystemClock.sleep(2); + sub.setTimeToLive(10, TimeUnit.SECONDS); + assertEquals(10000, sub.getTimeToLive()); + assertTrue(now.getTime() < sub.getUpdatedAt().getTime()); + assertTrue(now.getTime() < sub.getExpiresAt().getTime()); + assertTrue(sub.getUpdatedAt().getTime() < now_plus_1_sec.getTime()); + assertTrue(sub.getExpiresAt().getTime() < now_plus_11_sec.getTime()); + } + + @Test + public void subscription_setTimeToLive_illegalValuesThrows() { + realm = getPartialRealm(); + realm.beginTransaction(); + Subscription sub = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo").subscribe(); + try { + sub.setTimeToLive(-1, TimeUnit.SECONDS); + fail(); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("A negative time-to-live is not allowed")); + } + try { + sub.setTimeToLive(0, null); + fail(); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("Non-null 'timeUnit' required")); + } + } + } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java index 521b5443fa..270007ac3e 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java @@ -4,6 +4,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -242,6 +243,7 @@ public void sslVerifyCallback_shouldFailOnExpiredCert() { assertFalse(SyncManager.sslVerifyCallback(serverAddress, pem_depth0, 0)); } + @Ignore("FIXME: Certificate expired") @Test public void sslVerifyCallback_shouldVerifyHostname() { // simulating the following certificate chain diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp index 03c66b6b49..d1671d404c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp @@ -40,7 +40,8 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmQuery_nativeSerializeQuery(JNIEnv* return to_jstring(env, ""); } -JNIEXPORT jlong JNICALL Java_io_realm_RealmQuery_nativeSubscribe(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_name, jlong table_query_ptr, jlong descriptor_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_RealmQuery_nativeSubscribe(JNIEnv* env, jclass, jlong shared_realm_ptr, + jstring j_name, jlong table_query_ptr, jlong descriptor_ptr, REALM_UNUSED jlong time_to_live_ms, REALM_UNUSED jboolean update) { TR_ENTER() try { @@ -50,7 +51,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmQuery_nativeSubscribe(JNIEnv* env, jc auto descriptor = reinterpret_cast(descriptor_ptr); Results r(realm, *query, *descriptor); #if REALM_ENABLE_SYNC - RowExpr row = partial_sync::subscribe_blocking(r, name); + RowExpr row = partial_sync::subscribe_blocking(r, name, util::Optional(time_to_live_ms), update); return to_jlong_or_not_found(row.get_index()); #endif } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index c801492d88..6e83b62c1d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -76,16 +76,19 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsRealmConfig_nativeGetFinalizerP } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreate(JNIEnv* env, jclass, jstring j_realm_path, + jstring j_fifo_fallback_dir, jboolean enable_cache, jboolean enable_format_upgrade) { TR_ENTER() try { JStringAccessor realm_path(env, j_realm_path); + JStringAccessor fifo_fallback_dir(env, j_fifo_fallback_dir); auto* config_ptr = new Realm::Config(); config_ptr->path = realm_path; config_ptr->cache = enable_cache; config_ptr->disable_format_upgrade = !enable_format_upgrade; + config_ptr->fifo_files_fallback_path = fifo_fallback_dir; return reinterpret_cast(config_ptr); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp index 7b7fcb5198..eb111592a8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TestUtil.cpp @@ -1,6 +1,11 @@ +#define __STDC_LIMIT_MACROS // See https://stackoverflow.com/a/3233069/1389357 +#include + #include "io_realm_internal_TestUtil.h" #include "util.hpp" +#include + static jstring throwOrGetExpectedMessage(JNIEnv* env, jlong testcase, bool should_throw); JNIEXPORT jlong JNICALL Java_io_realm_internal_TestUtil_getMaxExceptionNumber(JNIEnv*, jclass) @@ -19,6 +24,11 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TestUtil_testThrowExceptions(JNIEn throwOrGetExpectedMessage(env, exception_kind, true); } +JNIEXPORT jlong JNICALL Java_io_realm_internal_TestUtil_getDateFromTimestamp(JNIEnv*, jclass, jlong seconds, jint nanoseconds) +{ + return to_milliseconds(realm::Timestamp(static_cast(seconds), static_cast(nanoseconds))); +} + static jstring throwOrGetExpectedMessage(JNIEnv* env, jlong testcase, bool should_throw) { std::string expect; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp index 5f9641eea1..d41e02f5c2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp @@ -39,14 +39,14 @@ static void finalize_subscription(jlong ptr) delete reinterpret_cast(ptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeCreate(JNIEnv* env, jclass, jlong results_ptr, jstring j_subscription_name) +JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeCreateOrUpdate(JNIEnv* env, jclass, jlong results_ptr, jstring j_subscription_name, jlong time_to_live, jboolean update) { TR_ENTER() try { const auto results = reinterpret_cast(results_ptr); JStringAccessor subscription_name(env, j_subscription_name); auto key = subscription_name.is_null_or_empty() ? util::none : util::Optional(subscription_name); - auto subscription = partial_sync::subscribe(results->collection(), key); + auto subscription = partial_sync::subscribe(results->collection(), key, util::Optional(time_to_live), update); auto wrapper = new SubscriptionWrapper(std::move(subscription)); return reinterpret_cast(wrapper); } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index f964c2640f..e9819ed9c7 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit f964c2640f635e76839559cb703732e9e906ba4c +Subproject commit e9819ed9c77ed87b5d7bed416a76cd5bcf255802 diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 4aa3a5474d..fd76c6de25 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -138,6 +138,9 @@ void ConvertException(JNIEnv* env, const char* file, int line) catch (partial_sync::ExistingSubscriptionException& e) { ThrowException(env, IllegalArgument, e.what()); } + catch (partial_sync::QueryTypeMismatchException& e) { + ThrowException(env, IllegalArgument, e.what()); + } #endif catch (std::logic_error e) { ThrowException(env, IllegalState, e.what()); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 6b9c2d2a3f..da2d4d5df3 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -70,6 +70,8 @@ std::string num_to_string(T pNumber) #define MAX_JINT 0x7FFFFFFFL #define MAX_JSIZE MAX_JINT +#define MAX_JLONG 0x7fffffffffffffffLL +#define MIN_JLONG (-MAX_JLONG-1) // TODO: Clean up those marcos. Casting with marcos reduces the readability, and it is actually breaking the C++ type // conversion. e.g.: You cannot cast a pointer with S64 below. @@ -496,12 +498,32 @@ class JStringAccessor { inline jlong to_milliseconds(const realm::Timestamp& ts) { - // From core's reference implementation aka unit test - // FIXME: check for overflow/underflow const int64_t seconds = ts.get_seconds(); const int32_t nanoseconds = ts.get_nanoseconds(); - const int64_t milliseconds = seconds * 1000 + nanoseconds / 1000000; // This may overflow - return milliseconds; + int64_t result_ms = seconds; + + // Convert seconds to milliseconds. + // Clamp to MAX/MIN in case of overflow/underflow. + int64_t sec_min_limit = MIN_JLONG/1000LL; + int64_t sec_max_limit = MAX_JLONG/1000LL; + if (seconds < 0 && sec_min_limit > seconds) { + return static_cast(MIN_JLONG); + } else if (seconds > 0 && sec_max_limit < seconds) { + return static_cast(MAX_JLONG); + } else { + result_ms = seconds * 1000; // Here it is safe to convert to milliseconds + } + + // Convert nanoseconds to milliseconds and add to final result. + // Clamp to MAX/MIN in case of the result overflowing/underflowing. + if (realm::util::int_add_with_overflow_detect(result_ms, nanoseconds / 1000000)) { + // The nanoseconds part is at max 1 sec. which means that if overflow/underflow + // is detected we can infer the direction from `result_ms` since we must be close + // to the limit boundary. + return static_cast((result_ms < 0) ? MIN_JLONG : MAX_JLONG); + } + + return static_cast(result_ms); } inline realm::Timestamp from_milliseconds(jlong milliseconds) diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 1c94cc6413..a391381abe 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -128,6 +128,7 @@ public void onInit(OsSharedRealm sharedRealm) { } OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(configuration) + .fifoFallbackDir(new File(BaseRealm.applicationContext.getFilesDir(), ".realm.temp")) .autoUpdateNotification(true) .migrationCallback(migrationCallback) .schemaInfo(schemaInfo) diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 55dff041d2..b1adcbf10c 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.Date; import java.util.Locale; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -144,7 +145,7 @@ private RealmQuery(Realm realm, Class clazz) { this.clazz = clazz; this.forValues = !isClassForRealmModel(clazz); if (forValues) { - // TODO implement this + // TODO Queries on primitive lists are not yet supported this.schema = null; this.table = null; this.osList = null; @@ -163,7 +164,7 @@ private RealmQuery(RealmResults queryResults, Class clazz) { this.clazz = clazz; this.forValues = !isClassForRealmModel(clazz); if (forValues) { - // TODO implement this + // TODO Queries on primitive lists are not yet supported this.schema = null; this.table = null; this.osList = null; @@ -182,7 +183,7 @@ private RealmQuery(BaseRealm realm, OsList osList, Class clazz) { this.clazz = clazz; this.forValues = !isClassForRealmModel(clazz); if (forValues) { - // TODO implement this + // TODO Queries on primitive lists are not yet supported this.schema = null; this.table = null; this.osList = null; @@ -1826,12 +1827,81 @@ public RealmResults findAllAsync() { * that will synchronize all server data matching the query. Named subscriptions can be removed again by * calling {@code Realm.unsubscribe(subscriptionName}. * + * @param subscriptionName name of the underlying subscription being created. * @return immediately an empty {@link RealmResults}. Users need to register a listener * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. * @see io.realm.RealmResults - * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm. + * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm or the query is on a {@link RealmList}. */ + @ObjectServer public RealmResults findAllAsync(String subscriptionName) { + return findAllAsync(subscriptionName, Long.MAX_VALUE, TimeUnit.MILLISECONDS, false); + } + + /** + * Finds all objects that fulfil the query condition(s). This method is only available from a Looper thread. + *

            + * This method is only available on query-based synchronized Realms and will also create a named subscription + * that will synchronize all server data matching the query. Named subscriptions can be removed again by + * calling {@code Realm.unsubscribe(subscriptionName}. + * + * @param subscriptionName name of the underlying subscription being created. + * @param update if an existing subscription exists with a different query. It will be replaced with this + * one instead of an error being reported through {@link OrderedRealmCollectionChangeListener}. + * @return immediately an empty {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * @see io.realm.RealmResults + * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm or the query is on a {@link RealmList}. + */ + @ObjectServer + @Beta + public RealmResults findAllAsync(String subscriptionName, boolean update) { + return findAllAsync(subscriptionName, Long.MAX_VALUE, TimeUnit.MILLISECONDS, update); + } + + /** + * Finds all objects that fulfil the query condition(s). This method is only available from a Looper thread. + *

            + * This method is only available on query-based synchronized Realms and will also create a named subscription + * that will synchronize all server data matching the query. Named subscriptions can be removed again by + * calling {@code Realm.unsubscribe(subscriptionName}. + * + * @param subscriptionName name of the underlying subscription being created. + * @param timeToLive the amount of time the Subscription must be kept alive after last being used. After this + * period Realm will automatically remove it. + * @param timeUnit the unit for {@code timeToLive}. + * @return immediately an empty {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * @see io.realm.RealmResults + * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm or the query is on a {@link RealmList}. + */ + @ObjectServer + @Beta + public RealmResults findAllAsync(String subscriptionName, long timeToLive, TimeUnit timeUnit) { + return findAllAsync(subscriptionName, timeToLive, timeUnit, false); + } + + /** + * Finds all objects that fulfil the query condition(s). This method is only available from a Looper thread. + *

            + * This method is only available on query-based synchronized Realms and will also create a named subscription + * that will synchronize all server data matching the query. Named subscriptions can be removed again by + * calling {@code Realm.unsubscribe(subscriptionName}. + * + * @param subscriptionName name of the underlying subscription being created. + * @param timeToLive the amount of time the Subscription must be kept alive after last being used. After this + * period Realm will automatically remove it. + * @param timeUnit the unit for {@code timeToLive}. + * @param update if an existing subscription exists with a different query. It will be replaced with this + * one instead of an error being reported through {@link OrderedRealmCollectionChangeListener}. + * @return immediately an empty {@link RealmResults}. Users need to register a listener + * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. + * @see io.realm.RealmResults + * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm or the query is on a {@link RealmList}. + */ + @ObjectServer + @Beta + public RealmResults findAllAsync(String subscriptionName, long timeToLive, TimeUnit timeUnit, boolean update) { realm.checkIfValid(); realm.checkIfPartialRealm(); if (osList != null) { @@ -1840,11 +1910,20 @@ public RealmResults findAllAsync(String subscriptionName) { if (Util.isEmptyString(subscriptionName)) { throw new IllegalArgumentException("Non-empty 'subscriptionName' required."); } - + if (timeToLive < 0) { + throw new IllegalArgumentException("Negative values for 'timeToLive' are not allowed: " + timeToLive); + } + //noinspection ConstantConditions + if (timeUnit == null) { + throw new IllegalArgumentException("Non-null 'timeUnit' required."); + } realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - return createRealmResults(query, queryDescriptors, false, SubscriptionAction.create(subscriptionName)); + long timeToLiveMs = timeUnit.toMillis(timeToLive); + SubscriptionAction action = (update) ? SubscriptionAction.update(subscriptionName, timeToLiveMs) : SubscriptionAction.create(subscriptionName, timeToLiveMs); + return createRealmResults(query, queryDescriptors, false, action); } + /** * Sorts the query result by the specific field name in ascending order. *

            @@ -2035,10 +2114,11 @@ public Subscription subscribe() { } /** - * Creates an anonymous subscription from this query or returns the existing Subscription if - * one already existed. + * Creates a named subscription from this query or returns the existing Subscription if + * one already existed. Subscriptions created this way will live forever or until the + * subscription is manually deleted. * - * @return the name of the query. + * @return the name of the subscription representing this query. * @return the subscription representing this query. * @throws IllegalStateException if this method is not called inside a write transaction, if * the query is on a {@link DynamicRealm} or a {@link RealmList}. @@ -2048,6 +2128,93 @@ public Subscription subscribe() { @ObjectServer @Beta public Subscription subscribe(String name) { + return subscribe(name, Long.MAX_VALUE, TimeUnit.MILLISECONDS, false); + } + + /** + * Creates a named subscription from this query or returns the existing Subscription if + * one already exists. + *

            + * {@code timeToLive} indicates for how long Realm must keep the subscription alive after last + * being used. After this period expires Realm are allowed to delete the subscription. + * This happens automatically. The period is reset, whenever someone resubscribes or updates + * the subscription itself. + *

            + * When a subscription is deleted, the data covered by the subscription is removed from the + * device, but not the server. + * + * @param name the name subscription representing this query. + * @param timeToLive the amount of time the Subscription must be kept alive after last being used. After this + * period Realm will automatically remove it. + * @param timeUnit the unit for {@code timeToLive}. + * @return the subscription representing this query. + * @throws IllegalStateException if this method is not called inside a write transaction, if + * the query is on a {@link DynamicRealm} or a {@link RealmList}. + * @throws IllegalArgumentException if a subscription for a different query with the same name + * already exists. + */ + @ObjectServer + @Beta + public Subscription subscribe(String name, long timeToLive, TimeUnit timeUnit) { + return subscribe(name, timeToLive, timeUnit, false); + } + + /** + * Creates a named subscription from this query or returns the existing Subscription if + * one already existed. If an existing subscription already exists and the existing query + * is different, it will be replaced by this query. + *

            + * It is only allowed to update a subscription that queries for objects of the same type. If + * the existing subscription queries for objects of a different type, an {@link IllegalArgumentException} + * is thrown. + * + * @param name the name of the subscription. + * @return the subscription representing this query. + * @throws IllegalStateException if this method is not called inside a write transaction, if + * the query is on a {@link DynamicRealm} or a {@link RealmList}. + * @throws IllegalArgumentException if this query are for other objects than those already being + * returned by an existing subscription. + */ + @ObjectServer + @Beta + public Subscription subscribeOrUpdate(String name) { + return subscribe(name, Long.MAX_VALUE, TimeUnit.MILLISECONDS, true); + } + + /** + * Creates a named subscription from this query or returns the existing Subscription if + * one already existed. If a subscription already exists and the query + * is different, it will be replaced by this query. + *

            + * It is only allowed to update a subscription that queries for objects of the same type. If + * the existing subscription queries for objects of a different type, an {@link IllegalArgumentException} + * is thrown. + *

            + * {@code timeToLive} indicates for how long Realm must keep the subscription alive after last + * being used. After this period expires Realm are allowed to delete the subscription. + * This happens automatically. The period is reset, whenever the subscription is resubscribed or updated + *

            + * When a subscription is deleted, the data covered by the subscription is removed from the + * device, but not the server. + * + * @param name the name of the subscription. + * @param timeToLive the amount of time the Subscription must be kept alive after last being used. + * @param timeUnit the unit for {@code timeToLive}. + * @return the subscription representing this query. + * @throws IllegalStateException if this method is not called inside a write transaction, if + * the query is on a {@link DynamicRealm} or a {@link RealmList}. + * @throws IllegalArgumentException if this query are for other objects than those already being + * returned by an existing subscription. + */ + @ObjectServer + @Beta + public Subscription subscribeOrUpdate(String name, long timeToLive, TimeUnit timeUnit) { + return subscribe(name, timeToLive, timeUnit, true); + } + + + @ObjectServer + private Subscription subscribe(String name, long timeToLive, TimeUnit timeUnit, boolean update) { realm.checkIfValid(); if (realm instanceof DynamicRealm) { throw new IllegalStateException("'subscribe' is not supported for queries on Dynamic Realms."); @@ -2058,11 +2225,21 @@ public Subscription subscribe(String name) { if (TextUtils.isEmpty(name)) { throw new IllegalArgumentException("Non-empty 'name' required."); } - long rowIndex = nativeSubscribe(realm.getSharedRealm().getNativePtr(), name, query.getNativePtr(), queryDescriptors.getNativePtr()); + //noinspection ConstantConditions + if (timeUnit == null) { + throw new IllegalArgumentException("Non-null 'timeUnit' is required."); + } + + // Convert timestamp to milliseconds and clamp at max + long timeToLiveMs = TimeUnit.MILLISECONDS.convert(timeToLive, timeUnit); + + long rowIndex = nativeSubscribe(realm.getSharedRealm().getNativePtr(), name, query.getNativePtr(), + queryDescriptors.getNativePtr(), timeToLiveMs, update); CheckedRow row = ((Realm) realm).getTable(Subscription.class).getCheckedRow(rowIndex); return realm.get(Subscription.class, null, row); } + /** * Returns a textual description of this query. * @@ -2072,6 +2249,16 @@ public String getDescription() { return nativeSerializeQuery(query.getNativePtr(), queryDescriptors.getNativePtr()); } + /** + * Returns the internal Realm name of the type being queried. + * + * @return the internal name of the Realm model class being queried. + */ + public String getTypeQueried() { + // TODO Revisit this when primitve list queries are implemented. + return table.getClassName(); + } + private boolean isDynamicQuery() { return className != null; } @@ -2157,7 +2344,7 @@ private RealmResults createRealmResults(TableQuery query, RealmResults results; OsResults osResults; if (subscriptionAction.shouldCreateSubscriptions()) { - osResults = SubscriptionAwareOsResults.createFromQuery(realm.sharedRealm, query, queryDescriptors, subscriptionAction.getName()); + osResults = SubscriptionAwareOsResults.createFromQuery(realm.sharedRealm, query, queryDescriptors, subscriptionAction); } else { osResults = OsResults.createFromQuery(realm.sharedRealm, query, queryDescriptors); } @@ -2192,6 +2379,7 @@ private SchemaConnector getSchemaConnector() { } private static native String nativeSerializeQuery(long tableQueryPtr, long descriptorPtr); - private static native long nativeSubscribe(long sharedRealmPtr, String name, long tableQueryPtr, long descriptorPtr); + private static native long nativeSubscribe(long sharedRealmPtr, String name, long tableQueryPtr, + long descriptorPtr, long timeToLiveMs, boolean update); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 3240ddd7e8..2ef03d1046 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -16,6 +16,7 @@ package io.realm.internal; +import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.util.Map; @@ -87,6 +88,7 @@ public static class Builder { private OsSharedRealm.MigrationCallback migrationCallback = null; private OsSharedRealm.InitializationCallback initializationCallback = null; private boolean autoUpdateNotification = false; + private String fifoFallbackDir = ""; /** * Initialize a {@link OsRealmConfig.Builder} with a given {@link RealmConfiguration}. @@ -144,9 +146,14 @@ public Builder autoUpdateNotification(boolean autoUpdateNotification) { // Package private because of the OsRealmConfig needs to carry the NativeContext. This should only be called // by the OsSharedRealm. OsRealmConfig build() { - return new OsRealmConfig(configuration, autoUpdateNotification, schemaInfo, + return new OsRealmConfig(configuration, fifoFallbackDir, autoUpdateNotification, schemaInfo, migrationCallback, initializationCallback); } + + public Builder fifoFallbackDir(File dir) { + this.fifoFallbackDir = dir.getAbsolutePath(); + return this; + } } private static final byte SCHEMA_MODE_VALUE_AUTOMATIC = 0; @@ -181,12 +188,13 @@ OsRealmConfig build() { private final OsSharedRealm.InitializationCallback initializationCallback; private OsRealmConfig(final RealmConfiguration config, + String fifoFallbackDir, boolean autoUpdateNotification, @Nullable OsSchemaInfo schemaInfo, @Nullable OsSharedRealm.MigrationCallback migrationCallback, @Nullable OsSharedRealm.InitializationCallback initializationCallback) { this.realmConfiguration = config; - this.nativePtr = nativeCreate(config.getPath(), false, true); + this.nativePtr = nativeCreate(config.getPath(), fifoFallbackDir,false, true); NativeContext.dummyContext.addReference(this); // Retrieve Sync settings first. We need syncRealmUrl to identify if this is a SyncConfig @@ -302,7 +310,7 @@ NativeContext getContext() { return context; } - private static native long nativeCreate(String path, boolean enableCache, boolean enableFormatUpdate); + private static native long nativeCreate(String path, String fifoFallbackDir, boolean enableCache, boolean enableFormatUpdate); private static native void nativeSetEncryptionKey(long nativePtr, byte[] key); diff --git a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java index 0f601cde9c..7ba0cc06d5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java @@ -19,6 +19,7 @@ import io.realm.RealmChangeListener; import io.realm.internal.core.DescriptorOrdering; import io.realm.internal.sync.OsSubscription; +import io.realm.internal.sync.SubscriptionAction; /** * Wrapper around Object Stores Results class that is capable of combining partial sync Subscription @@ -38,17 +39,17 @@ public class SubscriptionAwareOsResults extends OsResults { public static SubscriptionAwareOsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query, DescriptorOrdering queryDescriptors, - String subscriptionName) { + SubscriptionAction subscriptionInfo) { query.validateQuery(); long ptr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), queryDescriptors.getNativePtr()); - return new SubscriptionAwareOsResults(sharedRealm, query.getTable(), ptr, subscriptionName); + return new SubscriptionAwareOsResults(sharedRealm, query.getTable(), ptr, subscriptionInfo); } - SubscriptionAwareOsResults(OsSharedRealm sharedRealm, Table table, long nativePtr, String subscriptionName) { + SubscriptionAwareOsResults(OsSharedRealm sharedRealm, Table table, long nativePtr, SubscriptionAction subscriptionInfo) { super(sharedRealm, table, nativePtr); this.firstCallback = true; - this.subscription = new OsSubscription(this, subscriptionName); + this.subscription = new OsSubscription(this, subscriptionInfo); this.subscription.addChangeListener(new RealmChangeListener() { @Override public void onChange(OsSubscription o) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java index c611cb6674..a945534a33 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TestUtil.java @@ -23,4 +23,9 @@ class TestUtil { public static native String getExpectedMessage(long exceptionKind); public static native void testThrowExceptions(long exceptionKind); + + /** + * Returns the Date representation of a Core timestamp + */ + public static native long getDateFromTimestamp(long seconds, int nanoseconds); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index e9ef4d800a..6850af8c8e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -122,6 +122,9 @@ public static boolean deleteRealm(String canonicalPath, File realmFolder, String final String management = ".management"; File managementFolder = new File(realmFolder, realmFileName + management); File realmFile = new File(canonicalPath); + // This file is not always stored here, but if it is we want to delete it. + // If it isn't found it is placed in a temporary folder, so no reason to delete it. + File fifoFile = new File(canonicalPath + ".note"); // Deletes files in management directory and the directory. // There is no subfolders in the management directory. @@ -150,6 +153,12 @@ public static boolean deleteRealm(String canonicalPath, File realmFolder, String } else { realmDeleted = true; } + + if (fifoFile.exists() && !fifoFile.delete()) { + RealmLog.warn(String.format(Locale.ENGLISH,".note file at %s cannot be deleted", + fifoFile.getAbsolutePath())); + } + return realmDeleted; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java b/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java index adbc7b4804..49313701e0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java @@ -74,8 +74,9 @@ public void onCalled(SubscriptionObserverPair pair, Object observer) { private final long nativePtr; protected final ObserverPairList observerPairs = new ObserverPairList<>(); - public OsSubscription(OsResults results, String subscriptionName) { - this.nativePtr = nativeCreate(results.getNativePtr(), subscriptionName); + public OsSubscription(OsResults results, SubscriptionAction subscriptionInfo) { + this.nativePtr = nativeCreateOrUpdate(results.getNativePtr(), subscriptionInfo.getName(), + subscriptionInfo.getTimeToLiveMs(), subscriptionInfo.isUpdate()); } @Override @@ -117,7 +118,7 @@ private void notifyChangeListeners() { observerPairs.foreach(new Callback()); } - private static native long nativeCreate(long resultsNativePtr, String subscriptionName); + private static native long nativeCreateOrUpdate(long resultsNativePtr, String subscriptionName, long timeToLiveMs, boolean update); private static native long nativeGetFinalizerPtr(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java b/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java index 72c5ea0d72..4a6b91a988 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java @@ -16,21 +16,31 @@ package io.realm.internal.sync; +import java.util.concurrent.TimeUnit; + /** * Wrapper class describing if and how a subscription should be created when creating a query result. */ public class SubscriptionAction { - public static final SubscriptionAction NO_SUBSCRIPTION = new SubscriptionAction(null); - public static final SubscriptionAction ANONYMOUS_SUBSCRIPTION = new SubscriptionAction(""); + public static final SubscriptionAction NO_SUBSCRIPTION = new SubscriptionAction(null, 0, false); + public static final SubscriptionAction ANONYMOUS_SUBSCRIPTION = new SubscriptionAction("", Long.MAX_VALUE, false); + + public static SubscriptionAction create(String subscriptionName, long timeToLiveMs) { + return new SubscriptionAction(subscriptionName, timeToLiveMs, false); + } - public static SubscriptionAction create(String subscriptionName) { - return new SubscriptionAction(subscriptionName); + public static SubscriptionAction update(String subscriptionName, long timeToLiveMs) { + return new SubscriptionAction(subscriptionName, timeToLiveMs, true); } private final String subscriptionName; + private final long timeToLiveMs; + private final boolean update; - private SubscriptionAction(String name) { - this.subscriptionName = name; + public SubscriptionAction(String subscriptionName, long timeToLiveMs, boolean update) { + this.subscriptionName = subscriptionName; + this.timeToLiveMs = timeToLiveMs; + this.update = update; } public boolean shouldCreateSubscriptions() { @@ -40,4 +50,12 @@ public boolean shouldCreateSubscriptions() { public String getName() { return subscriptionName; } + + public long getTimeToLiveMs() { + return timeToLiveMs; + } + + public boolean isUpdate() { + return update; + } } diff --git a/realm/realm-library/src/main/java/io/realm/sync/Subscription.java b/realm/realm-library/src/main/java/io/realm/sync/Subscription.java index 15429b0eb8..aab282847d 100644 --- a/realm/realm-library/src/main/java/io/realm/sync/Subscription.java +++ b/realm/realm-library/src/main/java/io/realm/sync/Subscription.java @@ -15,6 +15,13 @@ */ package io.realm.sync; +import java.lang.reflect.Field; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.RealmObject; import io.realm.RealmQuery; import io.realm.annotations.Beta; @@ -22,6 +29,7 @@ import io.realm.annotations.RealmClass; import io.realm.annotations.RealmField; import io.realm.annotations.Required; +import io.realm.internal.Table; import io.realm.internal.annotations.ObjectServer; /** @@ -135,6 +143,47 @@ public Subscription(String name, RealmQuery query) { @RealmField("query_parse_counter") private int queryParseCounter; + /** + * Field indicating when this subscription was created. + */ + @RealmField("created_at") + private Date createdAt; + + /** + * Field indicating when this subscription was last used or updated. + *

            + * "Used" in this context means that someone resubscribed to the subscription. + *

            + * "Updated" means that someone updated the {@link #query} or some other field part of this class. + *

            + * This field is NOT updated whenever the results of the query changes. + *

            + * This field plus {@link #timeToLive} defines {@link #expiresAt}. + */ + @RealmField("updated_at") + private Date updatedAt; + + /** + * Field indicating when it is safe to delete this subscription. + *

            + * If {@code null} is returned, this subscription will live until manually deleted. + */ + @Nullable + @RealmField("expires_at") + private Date expiresAt; + + /** + * Field indicating for how long after last being used Realm must keep this subscription. After + * the TTL expires, Realm is allowed to remove the subscription. + *

            + * If {@code null} is returned, the subscription should live forever. + *

            + * This field plus {@link #updatedAt} defines {@link #expiresAt}. + */ + @Nullable + @RealmField("time_to_live") + private Long timeToLive; + /** * Returns the name of the subscription. * @@ -144,6 +193,90 @@ public String getName() { return name; } + /** + * Returns when this subscription was initially created. If {@code new Date(0)} is returned, + * it is unknown when the subscription was created. + * + * @return when this subscription was initially created. + */ + @SuppressFBWarnings({"EI_EXPOSE_REP"}) + public Date getCreatedAt() { + return createdAt; + } + + /** + * Returns when this subscription was last used or updated. + *

            + * "Used" in this context means that someone resubscribed to the subscription. + *

            + * "Updated" means that someone updated the {@link #query} or some other field part of this class. + *

            + * This field is NOT updated whenever the results of the query changes. + *

            + * This field plus {@link #timeToLive} defines {@link #expiresAt}. + * + * @return the point in time this subscription was last used or updated. + */ + @SuppressFBWarnings({"EI_EXPOSE_REP"}) + public Date getUpdatedAt() { + return updatedAt; + } + + /** + * Returns the point in time from which Realm can safely delete this subscription. This will + * happen automatically. + *

            + * Realm will attempt to cleanup expired subscriptions when the app is started or whenever + * any subscription is modified, there is no guarantee it will happen immediately after it + * expires. + * + * @return the point in time after which Realm can safely delete this subscription. + */ + @SuppressFBWarnings({"EI_EXPOSE_REP"}) + public Date getExpiresAt() { + if (expiresAt == null) { + return new Date(Long.MAX_VALUE); + } else { + return expiresAt; + } + } + + /** + * Returns for how long the subscription must be kept alive after last being used. The value + * returned are in milliseconds. + * + * @return in milliseconds, for how long the subscription must be kept alive after last being used. + */ + public long getTimeToLive() { + return (timeToLive != null) ? timeToLive : Long.MAX_VALUE; + } + + /** + * Sets the time-to-live in milliseconds for this subscription. This defines for how long Realm + * must keep the subscription alive after last being used. + * + * @param timeToLive for how long Realm must keep the subscription after last being used. + * @param timeUnit time unit for {@code timeToLive}. + * @throws IllegalArgumentException if a negative time-to-live or null timeUnit is provided. + */ + public void setTimeToLive(long timeToLive, TimeUnit timeUnit) { + if (timeToLive < 0) { + throw new IllegalArgumentException("A negative time-to-live is not allowed: " + timeToLive); + } + if (timeUnit == null) { + throw new IllegalArgumentException("Non-null 'timeUnit' required."); + } + this.updatedAt = new Date(System.currentTimeMillis()); + this.timeToLive = TimeUnit.MILLISECONDS.convert(timeToLive, timeUnit); + long expiryTime = this.updatedAt.getTime(); + if (expiryTime + this.timeToLive < expiryTime) { + expiryTime = Long.MAX_VALUE; // Clamp overflow to max + } else { + expiryTime = expiryTime + this.timeToLive; + } + this.expiresAt = new Date(expiryTime); + } + /** * Returns a textual description of the query that created this subscription. * @@ -153,6 +286,23 @@ public String getQueryDescription() { return query; } + /** + * Replaces the current query controlled by this subscription with a new query. + * + * @param query the query which should replace the current one. + */ + public void setQuery(RealmQuery query) { + if (query == null) { + throw new IllegalArgumentException("Non-null 'query' required"); + } + if (!query.getTypeQueried().equals(getQueryClassName())) { + throw new IllegalArgumentException(String.format("It is only allowed to replace a query with another query on the same type." + + "Existing query: '%s'. New query: '%s'", getQueryClassName(), query.getTypeQueried())); + } + this.query = query.getDescription(); + this.updatedAt = new Date(); + } + /** * Returns the internal name of the Class being queried. * @@ -219,10 +369,13 @@ public void unsubscribe() { public String toString() { return "Subscription{" + "name='" + name + '\'' + - ", status=" + getState().toString() + + ", status=" + status + ", errorMessage='" + errorMessage + '\'' + - ", className='" + getQueryClassName() + '\'' + ", query='" + query + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + ", expiresAt=" + expiresAt + + ", timeToLive=" + timeToLive + '}'; } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java index 621b7a70cb..a6e4203a55 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java @@ -1,16 +1,22 @@ package io.realm.objectserver; +import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Date; +import java.util.concurrent.TimeUnit; + import io.realm.DynamicRealm; import io.realm.OrderedCollectionChangeSet; +import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmList; +import io.realm.RealmQuery; import io.realm.RealmResults; import io.realm.Sort; import io.realm.StandardIntegrationTest; @@ -154,7 +160,159 @@ public void namedSubscription() throws InterruptedException { } } }); + } + @Test + @RunTestInLooperThread + public void namedSubscription_update() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + final Realm realm = getPartialRealm(user); + looperThread.closeAfterTest(realm); + + Date now = new Date(); + SystemClock.sleep(2); + + RealmQuery query1 = realm.where(PartialSyncObjectA.class).greaterThan("number", 5); + RealmResults results = query1.findAllAsync("update-test"); + results.addChangeListener((objects1, changeSet1) -> { + if (changeSet1.isCompleteResult()) { + results.removeAllChangeListeners(); + final Subscription sub1 = realm.getSubscription("update-test"); + final Date firstUpdated = sub1.getUpdatedAt(); + assertEquals(query1.getDescription(), sub1.getQueryDescription()); + assertTrue(now.getTime() < sub1.getUpdatedAt().getTime()); + assertEquals(sub1.getCreatedAt(), sub1.getUpdatedAt()); + assertEquals(Long.MAX_VALUE, sub1.getExpiresAt().getTime()); + assertEquals(Long.MAX_VALUE, sub1.getTimeToLive()); + + SystemClock.sleep(2); + RealmQuery query2 = realm.where(PartialSyncObjectA.class).equalTo("string", "foo"); + RealmResults results2 = query2.findAllAsync("update-test", true); + results2.addChangeListener((objects2, changeSet2) -> { + if (changeSet2.isCompleteResult()) { + assertEquals(query2.getDescription(), sub1.getQueryDescription()); + assertTrue(firstUpdated.getTime() < sub1.getUpdatedAt().getTime()); + looperThread.testComplete(); + } + }); + looperThread.keepStrongReference(results2); + } + }); + looperThread.keepStrongReference(results); + } + + @Test + @RunTestInLooperThread + public void namedSubscription_update_timeToLive() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + final Realm realm = getPartialRealm(user); + looperThread.closeAfterTest(realm); + + RealmQuery query1 = realm.where(PartialSyncObjectA.class).greaterThan("number", 5); + RealmResults results = query1.findAllAsync("update-test-ttl"); + results.addChangeListener((objects1, changeSet1) -> { + if (changeSet1.isCompleteResult()) { + results.removeAllChangeListeners(); + final Subscription sub1 = realm.getSubscription("update-test-ttl"); + final Date firstUpdatedAt = sub1.getUpdatedAt(); + final Date firstExpiresAt = sub1.getExpiresAt(); + assertEquals(Long.MAX_VALUE, sub1.getExpiresAt().getTime()); + assertEquals(Long.MAX_VALUE, sub1.getTimeToLive()); + + SystemClock.sleep(2); + RealmQuery query2 = realm.where(PartialSyncObjectA.class).equalTo("string", "foo"); + RealmResults results2 = query2.findAllAsync("update-test-ttl", 10, TimeUnit.MILLISECONDS, true); + results2.addChangeListener((objects2, changeSet2) -> { + if (changeSet2.isCompleteResult()) { + assertEquals(10, sub1.getTimeToLive()); + assertTrue(sub1.getExpiresAt().getTime() < firstExpiresAt.getTime()); + assertTrue(firstUpdatedAt.getTime() < sub1.getUpdatedAt().getTime()); + looperThread.testComplete(); + } + }); + looperThread.keepStrongReference(results2); + } + }); + looperThread.keepStrongReference(results); + } + + @Test + @RunTestInLooperThread + public void namedSubscription_withTimeToLive() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + final Realm realm = getPartialRealm(user); + looperThread.closeAfterTest(realm); + + RealmQuery query = realm.where(PartialSyncObjectA.class); + Date now = new Date(); + Date now_plus_10_sec = new Date(now.getTime() + 10000); + RealmResults results = query.findAllAsync("test-ttl", 5, TimeUnit.SECONDS); + results.addChangeListener((objects, changeSet) -> { + if (changeSet.isCompleteResult()) { + results.removeAllChangeListeners(); + final Subscription sub = realm.getSubscription("test-ttl"); + // Fuzzy check of expiresAt since we don't control exactly when the Subscription is created. + assertTrue(now.getTime() <= sub.getExpiresAt().getTime()); + assertTrue(sub.getExpiresAt().getTime() < now_plus_10_sec.getTime()); + assertEquals(5000, sub.getTimeToLive()); + looperThread.testComplete(); + } + }); + looperThread.keepStrongReference(results); + } + + @Test + @RunTestInLooperThread + public void namedSubscription_update_throwsIfDifferentQueryType() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + final Realm realm = getPartialRealm(user); + looperThread.closeAfterTest(realm); + + RealmResults results = realm.where(PartialSyncObjectA.class).findAllAsync("type-conflict"); + results.addChangeListener((objects1, changeSet1) -> { + if (changeSet1.isCompleteResult()) { + results.removeAllChangeListeners(); + RealmResults results2 = realm.where(PartialSyncObjectB.class).findAllAsync("type-conflict", true); + results2.addChangeListener((objects2, changeSet2) -> { + if (changeSet2.getState() == OrderedCollectionChangeSet.State.ERROR) { + assertTrue(changeSet2.getError() instanceof IllegalArgumentException); + assertTrue(changeSet2.getError().getMessage().startsWith("Replacing an existing query with a query on a different type is not allowed")); + looperThread.testComplete(); + } + }); + looperThread.keepStrongReference(results2); + } + }); + looperThread.keepStrongReference(results); + } + + @RunTestInLooperThread + public void creatingSubscriptionsAlsoCleanupExpiredSubscriptions() { + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + final Realm realm = getPartialRealm(user); + looperThread.closeAfterTest(realm); + + RealmResults results = realm.where(PartialSyncObjectA.class).findAllAsync("sub1", 0, TimeUnit.MILLISECONDS); + results.addChangeListener((objects1, changeSet1) -> { + if (changeSet1.isCompleteResult()) { + results.removeAllChangeListeners(); + assertEquals(1, realm.getSubscriptions().size()); + final Subscription firstSub = realm.getSubscription("sub1"); + SystemClock.sleep(2); + + RealmResults results2 = realm.where(PartialSyncObjectB.class).findAllAsync("sub2"); + results2.addChangeListener((objects2, changeSet2) -> { + if (changeSet2.isCompleteResult()) { + assertEquals(1, realm.getSubscriptions().size()); + assertEquals("sub2", realm.getSubscriptions().first().getName()); + assertFalse(firstSub.isValid()); + looperThread.testComplete(); + } + }); + looperThread.keepStrongReference(results2); + } + }); + looperThread.keepStrongReference(results); } @Test @@ -490,4 +648,5 @@ private void createServerData(SyncUser user, String url) throws InterruptedExcep SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); } + } diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 2d8a6a5096..d65692fb5d 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -1195,7 +1195,9 @@ public static boolean isSelinuxEnforcing() { return false; } try { - final Process process = new ProcessBuilder("/system/bin/getenforce").start(); + final Process process = new ProcessBuilder("/system/bin/getenforce") + .redirectErrorStream(true) + .start(); try { final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8)); //noinspection TryFinallyCanBeTryWithResources From 9ecefb0c027995ba303cefdab3fff6593fd3ed39 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 22 Mar 2019 07:32:09 +0100 Subject: [PATCH 1364/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a205113f8..594942d070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.10.0(YYYY-MM-DD) +## 5.10.0(2019-03-22) ## Enhancements * [ObjectServer] Added 4 new fields to query-based Subscriptions: `createdAt`, `updatedAt`, `expiresAt` and `timeToLive`. These make it possible to better reason about and control current subscriptions. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) From c5257981064d9b0429496ddfd280fa0c4d3d793c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 22 Mar 2019 07:34:25 +0100 Subject: [PATCH 1365/2110] Release v5.10.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index ba94863e02..c355d6e218 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.10.0-SNAPSHOT +5.10.0 \ No newline at end of file From 46e67bd70c29fe0fad5c390315cc607ca0c940bc Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 22 Mar 2019 07:34:25 +0100 Subject: [PATCH 1366/2110] Prepare next release v5.10.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index c355d6e218..185ace5025 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.10.0 \ No newline at end of file +5.10.1-SNAPSHOT \ No newline at end of file From 7ca0c8c4f4715df9bacd586cb4bec66b78faf8b3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 22 Mar 2019 09:05:29 +0100 Subject: [PATCH 1367/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 185ace5025..48092a8f91 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.10.1-SNAPSHOT \ No newline at end of file +5.11.0-SNAPSHOT \ No newline at end of file From 82d6c5b2b315e994068b485d8f337e258f35ac1d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 10 Apr 2019 11:40:30 +0200 Subject: [PATCH 1368/2110] Fix batch updates which removes objects from the query results (#6482) --- CHANGELOG.md | 17 ++++++++++++++ .../java/io/realm/RealmResultsTests.java | 23 +++++++++++++++++++ realm/realm-library/src/main/cpp/object-store | 2 +- tools/sync_test_server/Dockerfile | 7 ++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 594942d070..cbb017878e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 5.10.1(YYYY-MM-DD) + +## Enhancements +* None + +## Fixed +* Native crash happening if bulk updating a field in a `RealmResult` would cause the object to no longer be part of the query result. (Issue [#6478](https://github.com/realm/realm-java/issues/6478), since 5.8.0). + +## Compatibility +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats). +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +## Internal +* Updated to Object Store commit: cc3db611b1c10d2b890a92fa0f4b8291bc0f3ba2 + + ## 5.10.0(2019-03-22) ## Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 741d372d3f..2714b81ba6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1027,6 +1027,29 @@ public void setValue_implicitConversions() { } } + // Test for https://github.com/realm/realm-java/issues/6478 + @Test + public void setDate_updateRemovesObjectFromQuery() { + realm.beginTransaction(); + realm.deleteAll(); + int objects = 10; + for (int i = 0; i < objects; ++i) { + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, i); + obj.setFieldDate(i % 2 == 0 ? null : new Date(1000)); + } + realm.commitTransaction(); + + realm.beginTransaction(); + RealmResults collection = realm.where(AllJavaTypes.class) + .isNull(AllJavaTypes.FIELD_DATE) + .findAll(); + + collection.setDate(AllJavaTypes.FIELD_DATE, new Date(2000)); + realm.commitTransaction(); + + assertTrue(collection.isEmpty()); + } + @Test public void setValue_specificType() { populateAllJavaTypes(5); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index e9819ed9c7..56ffd089e7 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit e9819ed9c77ed87b5d7bed416a76cd5bcf255802 +Subproject commit 56ffd089e78dc6cd299b0234a71b2b9b9dca5957 diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 0516aa6fde..edcdc08940 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -10,6 +10,13 @@ RUN if [ "x$ROS_VERSION" = "x" ] ; then echo Non-empty ROS_VERSION required ; ex RUN if [ "x$REALM_FEATURE_TOKEN" = "x" ] ; then echo Non-empty REALM_FEATURE_TOKEN required ; exit 1; fi # Install netstat (used for debugging) +# Fix https://superuser.com/questions/1420231/how-to-solve-404-error-in-aws-apg-get-for-debian-jessie-fetch +RUN rm etc/apt/sources.list +RUN echo "deb http://archive.debian.org/debian/ jessie main" >> etc/apt/sources.list +RUN echo "deb-src http://archive.debian.org/debian/ jessie main" >> etc/apt/sources.list +RUN echo "deb http://security.debian.org jessie/updates main" >> etc/apt/sources.list +RUN echo "deb-src http://security.debian.org jessie/updates main" >> etc/apt/sources.list + RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y \ net-tools \ From 28cf578442ef31b1b9dd323c0e26e1fd4fbba0a1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 24 Apr 2019 23:32:36 +0200 Subject: [PATCH 1369/2110] Add support for incremental annotation processors (#6496) --- CHANGELOG.md | 35 ++++++++++++++----- .../io/realm/processor/ClassMetaData.java | 27 ++++++++++---- .../processor/RealmProxyClassGenerator.java | 20 ++++++----- .../java/io/realm/processor/TypeMirrors.java | 3 ++ .../gradle/incremental.annotation.processors | 1 + 5 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 realm/realm-annotations-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors diff --git a/CHANGELOG.md b/CHANGELOG.md index cbb017878e..3924864ea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,36 +1,53 @@ +## 5.11.0(YYYY-MM-DD) + +### Enhancements +* Added support for incremental annotation processing added in Gradle 4.7. (Issue [#5906](https://github.com/realm/realm-java/issues/5906)). + +### Fixed +* None. + +### Compatibility +* Realm Object Server: 3.11.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats). +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None. + + ## 5.10.1(YYYY-MM-DD) -## Enhancements -* None +###Enhancements +* None. -## Fixed +### Fixed * Native crash happening if bulk updating a field in a `RealmResult` would cause the object to no longer be part of the query result. (Issue [#6478](https://github.com/realm/realm-java/issues/6478), since 5.8.0). -## Compatibility +### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats). * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. -## Internal +### Internal * Updated to Object Store commit: cc3db611b1c10d2b890a92fa0f4b8291bc0f3ba2 ## 5.10.0(2019-03-22) -## Enhancements +### Enhancements * [ObjectServer] Added 4 new fields to query-based Subscriptions: `createdAt`, `updatedAt`, `expiresAt` and `timeToLive`. These make it possible to better reason about and control current subscriptions. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) * [ObjectServer] Added the option of updating the query controlled by a Subscription using either `RealmQuery.findAllAsync(String name, boolean update)`, `RealmQuery.subscribe(String name, boolean update)` or `Subscription.setQuery(RealmQuery query)`. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) * [ObjectServer] Added the option of setting a time-to-live for subscriptions. Setting this will automatically delete the subscription after the provided TTL has expired and the subscription hasn't been used. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) -## Fixed +### Fixed * Dates returned from the Realm file no longer overflow or underflow if they exceed `Long.MAX_VALUE` or `Long.MIN_VALUE` but instead clamp to their respective value. (Issue [#2722](https://github.com/realm/realm-java/issues/2722)) -## Compatibility +### Compatibility * Realm Object Server: 3.11.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats). * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. -## Internal +### Internal * Updated to Object Store commit: e9819ed9c77ed87b5d7bed416a76cd5bcf255802 diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java index 4eba7a258d..5f8d53ab60 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -400,7 +399,7 @@ private boolean checkCollectionTypes() { private boolean checkRealmListType(VariableElement field) { // Check for missing generic (default back to Object) if (Utils.getGenericTypeQualifiedName(field) == null) { - Utils.error("No generic type supplied for field", field); + Utils.error(getFieldErrorSuffix(field) + "No generic type supplied for field", field); return false; } @@ -411,7 +410,7 @@ private boolean checkRealmListType(VariableElement field) { TypeElement elementTypeElement = (TypeElement) ((DeclaredType) elementTypeMirror).asElement(); if (elementTypeElement.getSuperclass().getKind() == TypeKind.NONE) { Utils.error( - "Only concrete Realm classes are allowed in RealmLists. " + getFieldErrorSuffix(field) + "Only concrete Realm classes are allowed in RealmLists. " + "Neither interfaces nor abstract classes are allowed.", field); return false; @@ -419,9 +418,9 @@ private boolean checkRealmListType(VariableElement field) { } // Check if the actual value class is acceptable - if (!validListValueTypes.contains(elementTypeMirror) && !Utils.isRealmModel(elementTypeMirror)) { + if (!containsType(validListValueTypes, elementTypeMirror) && !Utils.isRealmModel(elementTypeMirror)) { final StringBuilder messageBuilder = new StringBuilder( - "Element type of RealmList must be a class implementing 'RealmModel' or one of the "); + getFieldErrorSuffix(field) + "Element type of RealmList must be a class implementing 'RealmModel' or one of "); final String separator = ", "; for (TypeMirror type : validListValueTypes) { messageBuilder.append('\'').append(type.toString()).append('\'').append(separator); @@ -440,7 +439,7 @@ private boolean checkRealmResultsType(VariableElement field) { // Check for missing generic (default back to Object) if (Utils.getGenericTypeQualifiedName(field) == null) { - Utils.error("No generic type supplied for field", field); + Utils.error(getFieldErrorSuffix(field) + "No generic type supplied for field", field); return false; } @@ -459,13 +458,17 @@ private boolean checkRealmResultsType(VariableElement field) { // Check if the actual value class is acceptable if (!Utils.isRealmModel(elementTypeMirror)) { - Utils.error("Element type of RealmResults must be a class implementing 'RealmModel'.", field); + Utils.error(getFieldErrorSuffix(field) + "Element type of RealmResults must be a class implementing 'RealmModel'.", field); return false; } return true; } + private String getFieldErrorSuffix(VariableElement field) { + return javaClassName + "." + field.getSimpleName() + ": "; + } + private boolean checkReferenceTypes() { for (VariableElement field : fields) { if (Utils.isRealmModel(field)) { @@ -776,6 +779,16 @@ private boolean isValidPrimaryKeyType(TypeMirror type) { return false; } + private boolean containsType(List listOfTypes, TypeMirror type) { + for (int i = 0; i < listOfTypes.size(); i++) { + // Comparing TypeMirror's using `equals()` breaks when using incremental annotation processing. + if (typeUtils.isSameType(listOfTypes.get(i), type)) { + return true; + } + } + return false; + } + public Element getClassElement() { return classType; } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java index 9e3aca471e..8e6304e741 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java @@ -622,26 +622,30 @@ private String getStatementForAppendingValueToOsList( @SuppressWarnings("SameParameterValue") String osListVariableName, @SuppressWarnings("SameParameterValue") String valueVariableName, TypeMirror elementTypeMirror) { - if (elementTypeMirror == typeMirrors.STRING_MIRROR) { + + Types typeUtils = processingEnvironment.getTypeUtils(); + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.STRING_MIRROR)) { return osListVariableName + ".addString(" + valueVariableName + ")"; } - if (elementTypeMirror == typeMirrors.LONG_MIRROR || elementTypeMirror == typeMirrors.INTEGER_MIRROR - || elementTypeMirror == typeMirrors.SHORT_MIRROR || elementTypeMirror == typeMirrors.BYTE_MIRROR) { + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.LONG_MIRROR) + || typeUtils.isSameType(elementTypeMirror, typeMirrors.INTEGER_MIRROR) + || typeUtils.isSameType(elementTypeMirror, typeMirrors.SHORT_MIRROR) + || typeUtils.isSameType(elementTypeMirror, typeMirrors.BYTE_MIRROR)) { return osListVariableName + ".addLong(" + valueVariableName + ".longValue())"; } - if (elementTypeMirror.equals(typeMirrors.BINARY_MIRROR)) { + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.BINARY_MIRROR)) { return osListVariableName + ".addBinary(" + valueVariableName + ")"; } - if (elementTypeMirror == typeMirrors.DATE_MIRROR) { + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DATE_MIRROR)) { return osListVariableName + ".addDate(" + valueVariableName + ")"; } - if (elementTypeMirror == typeMirrors.BOOLEAN_MIRROR) { + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.BOOLEAN_MIRROR)) { return osListVariableName + ".addBoolean(" + valueVariableName + ")"; } - if (elementTypeMirror == typeMirrors.DOUBLE_MIRROR) { + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DOUBLE_MIRROR)) { return osListVariableName + ".addDouble(" + valueVariableName + ".doubleValue())"; } - if (elementTypeMirror == typeMirrors.FLOAT_MIRROR) { + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.FLOAT_MIRROR)) { return osListVariableName + ".addFloat(" + valueVariableName + ".floatValue())"; } throw new RuntimeException("unexpected element type: " + elementTypeMirror.toString()); diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java index 8182828853..d6a0c84c69 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java @@ -30,6 +30,9 @@ /** * This class provides {@link TypeMirror} instances used in annotation processor. + * + * WARNING: Comparing type mirrors using either `==` or `equal()` can break when using incremental + * annotation processing. Always use `Types.isSameType()` instead when comparing them. */ class TypeMirrors { final TypeMirror STRING_MIRROR; diff --git a/realm/realm-annotations-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors b/realm/realm-annotations-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors new file mode 100644 index 0000000000..78b92db5bd --- /dev/null +++ b/realm/realm-annotations-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors @@ -0,0 +1 @@ +io.realm.processor.RealmProcessor,aggregating \ No newline at end of file From 3e2f9d724eb599da3d6d9ba3c7952ad638dde663 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 May 2019 10:25:35 +0200 Subject: [PATCH 1370/2110] Including LinkingObjects in subscriptions (#6489) --- CHANGELOG.md | 20 ++- dependencies.list | 10 +- .../java/io/realm/RealmQueryTests.java | 11 ++ .../res/xml/network_security_config.xml | 6 +- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../src/main/cpp/io_realm_RealmQuery.cpp | 10 +- ...realm_internal_core_DescriptorOrdering.cpp | 15 +- ..._realm_internal_core_IncludeDescriptor.cpp | 81 ++++++++++ .../io_realm_internal_sync_OsSubscription.cpp | 7 +- .../src/main/cpp/java_query_descriptor.cpp | 1 - realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/util.cpp | 4 + .../src/main/java/io/realm/RealmQuery.java | 40 ++++- .../internal/core/DescriptorOrdering.java | 17 +- .../internal/core/IncludeDescriptor.java | 62 ++++++++ .../internal/fields/FieldDescriptor.java | 2 +- .../objectserver/QueryBasedSyncTests.java | 149 +++++++++++++++++- .../objectserver/model/PartialSyncModule.java | 5 +- tools/sync_test_server/ros/package.json | 2 +- 19 files changed, 419 insertions(+), 27 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp create mode 100644 realm/realm-library/src/main/java/io/realm/internal/core/IncludeDescriptor.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3924864ea4..d3c30020bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,28 @@ ## 5.11.0(YYYY-MM-DD) -### Enhancements +### Enhancements +* [ObjectServer] Added `RealmQuery.includeLinkingObjects()`. This is only relevant for Query-based Realms and tells subscriptions to include objects linked through `@LinkingObjects` fields as part of the subscription as well. Objects referenced through objects and lists are always included as a default. (Issue [#6426](https://github.com/realm/realm-java/issues/6426)) +* Encryption now uses hardware optimized functions, which significantly improves the performance of encrypted Realms. ([Realm Core PR #3241](https://github.com/realm/realm-core/pull/3241)) +* Improved query performance when using `RealmQuery.in()` queries. ([Realm Core PR #3250](https://github.com/realm/realm-core/pull/3250)). +* Improved query performance when querying Integer fields with indexes, e.g. primary key fields. ([Realm Core PR #3272](https://github.com/realm/realm-core/pull/3272)). +* Improved write performance when writing changes to disk ([Realm Core PR #2927](https://github.com/realm/realm-sync/issues/2927)) * Added support for incremental annotation processing added in Gradle 4.7. (Issue [#5906](https://github.com/realm/realm-java/issues/5906)). ### Fixed -* None. +* [ObjectServer] Fix an error in the calculation of the `downloadableBytes` value sent by `ProgressListeners`. +* [ObjectServer] HTTP requests made by the Sync client now always include a Host: header, as required by HTTP/1.1, although its value will be empty if no value is specified by the application. +* [ObjectServer] The server no longer rejects subscriptions based on queries with distinct and/or limit clauses. +* [ObjectServer] If a user had `canCreate` but not `canUpdate` privileges on a class, the user would be able to create the object, but not actually set any meaningful values on that object, despite the rule that objects created within the same transaction can always be modified. ### Compatibility -* Realm Object Server: 3.11.0 or later. -* File format: Generates Realms with format v9 (Reads and upgrades all previous formats). +* Realm Object Server: 3.21.0-rc1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal -* None. +* Updated to Realm Core 5.19.1. +* Updated to Relm Sync 4.4.2. +* Updated to Object Store commit e4b1314d21b521fd604af7f1aacf3ca94272c19a ## 5.10.1(YYYY-MM-DD) diff --git a/dependencies.list b/dependencies.list index cfc6079634..48b5b292ce 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,14 +1,14 @@ -# Realm Sync Core release used by Realm Java +# Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=3.14.13 -REALM_SYNC_SHA256=7e8934a471fa714bf672a9575cd3112470c3294d55e7a13688d04569e707b8cd +REALM_SYNC_VERSION=4.4.2 +REALM_SYNC_SHA256=7f3386bc9e590788afc6fd61744dd187148862513ddbd7e65c32d1d9c371e1ce # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.18.5 +REALM_OBJECT_SERVER_VERSION=3.21.0-rc1 # Common Android settings across projects -GRADLE_BUILD_TOOLS=3.3.1 +GRADLE_BUILD_TOOLS=3.3.2 ANDROID_BUILD_TOOLS=28.0.3 # Common classpath dependencies diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index e0bf51bfbc..511cc0f518 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -44,6 +44,8 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; import static org.junit.Assert.assertEquals; @@ -2993,6 +2995,15 @@ private void populateForDistinctInvalidTypesLinked(Realm realm) { realm.commitTransaction(); } + @Test + public void includeLinkingObjects_throwsForNonQueryBasedRealms() { + try { + realm.where(AllJavaTypes.class).includeLinkingObjects(AllJavaTypes.FIELD_STRING); + fail(); + } catch (IllegalStateException ignore) { + } + } + @Test public void distinct() { final long numberOfBlocks = 3; diff --git a/realm/realm-library/src/androidTest/res/xml/network_security_config.xml b/realm/realm-library/src/androidTest/res/xml/network_security_config.xml index 40f1d9a749..9f14d34442 100644 --- a/realm/realm-library/src/androidTest/res/xml/network_security_config.xml +++ b/realm/realm-library/src/androidTest/res/xml/network_security_config.xml @@ -6,4 +6,8 @@ - \ No newline at end of file + + localhost + 127.0.0.1 + + diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 70311a5dc4..035b359150 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -80,7 +80,7 @@ set(classes_LIST io.realm.internal.NativeObjectReference io.realm.internal.OsCollectionChangeSet io.realm.internal.OsObject io.realm.internal.OsRealmConfig io.realm.internal.OsList io.realm.internal.OsObjectStore io.realm.internal.sync.OsSubscription - io.realm.internal.core.DescriptorOrdering + io.realm.internal.core.DescriptorOrdering io.realm.internal.core.IncludeDescriptor io.realm.internal.objectstore.OsObjectBuilder ) # /./ is the workaround for the problem that AS cannot find the jni headers. diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp index d1671d404c..67faf4e526 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp @@ -33,8 +33,14 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmQuery_nativeSerializeQuery(JNIEnv* try { auto query = reinterpret_cast(table_query_ptr); auto descriptor = reinterpret_cast(descriptor_ptr); - std::string serialized_query = query->get_description() + " " + descriptor->get_description(query->get_table()); - return to_jstring(env, serialized_query); + std::string serialized_query = query->get_description(); + std::string serialized_descriptor = descriptor->get_description(query->get_table()); + if (serialized_descriptor.empty()) { + return to_jstring(env, serialized_query); + } else { + std::string result = serialized_query + " " + serialized_descriptor; + return to_jstring(env, result); + } } CATCH_STD() return to_jstring(env, ""); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp index 062074fd93..5dcf6bc849 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp @@ -56,7 +56,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppe try { auto descriptor = reinterpret_cast(descriptor_ptr); if (j_sort_descriptor) { - descriptor->append_sort(JavaQueryDescriptor(env, j_sort_descriptor).sort_descriptor()); + descriptor->append_sort(JavaQueryDescriptor(env, j_sort_descriptor).sort_descriptor()); } } CATCH_STD() @@ -88,6 +88,19 @@ JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppe CATCH_STD() } +JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppendInclude(JNIEnv* env, jclass, + jlong descriptor_ptr, + jlong include_descriptor_ptr) +{ + TR_ENTER() + try { + auto descriptor = reinterpret_cast(descriptor_ptr); + auto include_descriptor = reinterpret_cast(include_descriptor_ptr); + descriptor->append_include(*include_descriptor); + } + CATCH_STD() +} + JNIEXPORT jboolean JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeIsEmpty(JNIEnv* env, jclass, jlong descriptor_ptr) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp new file mode 100644 index 0000000000..19f23577e2 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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 "io_realm_internal_core_IncludeDescriptor.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "java_accessor.hpp" +#include "java_query_descriptor.hpp" +#include "util.hpp" + + +using namespace realm; +using namespace realm::util; +using namespace realm::_impl; + +static void finalize_descriptor(jlong ptr) +{ + TR_ENTER_PTR(ptr) + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_core_IncludeDescriptor_nativeGetFinalizerMethodPtr(JNIEnv* env, jclass) +{ + TR_ENTER() + try { + return reinterpret_cast(&finalize_descriptor); + } + CATCH_STD() + return 0; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_core_IncludeDescriptor_nativeCreate(JNIEnv* env, jclass, jlong starting_table_ptr, jlongArray column_indexes, jlongArray table_pointers) +{ + TR_ENTER() + try { + JLongArrayAccessor table_arr(env, table_pointers); + JLongArrayAccessor index_arr(env, column_indexes); + auto starting_table = reinterpret_cast(starting_table_ptr); + std::vector parts; + parts.reserve(index_arr.size()); + for (int i = 0; i < index_arr.size(); ++i) { + auto col_index = static_cast(index_arr[i]); + auto table_ptr = reinterpret_cast

            (table_arr[i]); + if (table_ptr == nullptr) { + parts.emplace_back(LinkPathPart(col_index)); + } + else { + const ConstTableRef ref = table_ptr->get_table_ref(); + parts.emplace_back(LinkPathPart(col_index, ref)); + } + } + + std::vector> include_path; + include_path.reserve(1); + include_path.emplace_back(parts); + return reinterpret_cast(new IncludeDescriptor(*starting_table, include_path)); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp index d41e02f5c2..963e53e770 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp @@ -21,6 +21,7 @@ #include "subscription_wrapper.hpp" #include "jni_util/java_class.hpp" #include "jni_util/java_method.hpp" +#include "object-store/src/sync/partial_sync.hpp" #include #include @@ -46,7 +47,11 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeCreateO const auto results = reinterpret_cast(results_ptr); JStringAccessor subscription_name(env, j_subscription_name); auto key = subscription_name.is_null_or_empty() ? util::none : util::Optional(subscription_name); - auto subscription = partial_sync::subscribe(results->collection(), key, util::Optional(time_to_live), update); + partial_sync::SubscriptionOptions options; + options.user_provided_name = key; + options.time_to_live_ms = util::Optional(time_to_live); + options.update = update; + auto subscription = partial_sync::subscribe(results->collection(), options); auto wrapper = new SubscriptionWrapper(std::move(subscription)); return reinterpret_cast(wrapper); } diff --git a/realm/realm-library/src/main/cpp/java_query_descriptor.cpp b/realm/realm-library/src/main/cpp/java_query_descriptor.cpp index 0c85d29728..d20fd9609f 100644 --- a/realm/realm-library/src/main/cpp/java_query_descriptor.cpp +++ b/realm/realm-library/src/main/cpp/java_query_descriptor.cpp @@ -95,4 +95,3 @@ JavaClass const& JavaQueryDescriptor::get_sort_desc_class() const noexcept static JavaClass sort_desc_class(m_env, "io/realm/internal/core/QueryDescriptor"); return sort_desc_class; } - diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 56ffd089e7..e4b1314d21 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 56ffd089e78dc6cd299b0234a71b2b9b9dca5957 +Subproject commit e4b1314d21b521fd604af7f1aacf3ca94272c19a diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index fd76c6de25..dc1188dc0d 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -63,6 +63,10 @@ void ConvertException(JNIEnv* env, const char* file, int line) ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalState, ss.str()); } + catch(InvalidPathError& e) { + ss << e.what() << " in " << file << " line " << line; + ThrowException(env, IllegalArgument, ss.str()); + } catch (SharedGroup::BadVersion& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, BadVersion, ss.str()); diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index b1adcbf10c..c18ff2dcc5 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -33,8 +33,8 @@ import io.realm.internal.OsList; import io.realm.internal.OsResults; import io.realm.internal.PendingRow; -import io.realm.internal.UncheckedRow; import io.realm.internal.annotations.ObjectServer; +import io.realm.internal.core.IncludeDescriptor; import io.realm.internal.core.QueryDescriptor; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; @@ -2054,6 +2054,42 @@ public RealmQuery limit(long limit) { return this; } + /** + * This predicate is only relevant for Query-based Realms. + *

            + * Objects referenced through fields marked with {@link io.realm.annotations.LinkingObjects} are normally not downloaded + * as part of the subscription in Query-based Realms, but by using this predicate, it is possible to specify which linking + * objects relationships should also be included in the subscription as well. + *

            + * Note, that all "forward" object references like object references and lists are always downloaded as part of the + * subscription by default. + *

            + * This predicate can be called multiple times, in which case all fields will be added to the subscription. + * + * @param firstIncludePath the first {@link io.realm.annotations.LinkingObjects} field to add. + * @param remainingFieldPaths any remaining {@link io.realm.annotations.LinkingObjects} fields to add. + * @throws IllegalStateException if called on a non-query-based Realm. + * @throws IllegalArgumentException if the path does not end with a field marked with {@link io.realm.annotations.LinkingObjects}. + */ + @ObjectServer + public RealmQuery includeLinkingObjects(String firstIncludePath, @Nullable String... remainingFieldPaths) { + realm.checkIfValid(); + if (!ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(realm.getConfiguration())) { + throw new IllegalStateException("This method is only available for Query-based Realms."); + } + if (Util.isEmptyString(firstIncludePath)) { + throw new IllegalArgumentException("Non-empty 'firstIncludePath' required."); + } + queryDescriptors.appendIncludes(IncludeDescriptor.createInstance(getSchemaConnector(), table, firstIncludePath)); + if (remainingFieldPaths != null) { + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < remainingFieldPaths.length; i++) { + queryDescriptors.appendIncludes(IncludeDescriptor.createInstance(getSchemaConnector(), table, remainingFieldPaths[i])); + } + } + return this; + } + /** * This predicate will always match. */ @@ -2255,7 +2291,7 @@ public String getDescription() { * @return the internal name of the Realm model class being queried. */ public String getTypeQueried() { - // TODO Revisit this when primitve list queries are implemented. + // TODO Revisit this when primitive list queries are implemented. return table.getClassName(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java b/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java index 2777a63a08..604b4efdb6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java +++ b/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java @@ -17,8 +17,11 @@ package io.realm.internal.core; import io.realm.internal.NativeObject; +import io.realm.internal.OsSchemaInfo; import io.realm.internal.OsSharedRealm; +import io.realm.internal.Table; import io.realm.internal.TableQuery; +import io.realm.internal.fields.FieldDescriptor; /** * Java class wrapping the native {@code realm::DescriptorOrdering} class. This class @@ -96,6 +99,15 @@ public void setLimit(long limit) { limitDefined = true; } + /** + * Add a linkingObject reference that should be fetched from the server. + * This only makes sense for Query-based Realms. It is up to callers of this method + * to ensure this. + */ + public void appendIncludes(IncludeDescriptor descriptor) { + nativeAppendInclude(nativePtr, descriptor.getNativePtr()); + } + /** * Returns true if no descriptors or limits have been added. */ @@ -106,9 +118,10 @@ public boolean isEmpty() { private static native long nativeGetFinalizerMethodPtr(); private static native long nativeCreate(); - private static native void nativeAppendSort(long descriptorPtr, QueryDescriptor sortDesc); - private static native void nativeAppendDistinct(long descriptorPtr, QueryDescriptor sortDesc); + private static native void nativeAppendSort(long descriptorPtr, QueryDescriptor includeDescriptor); + private static native void nativeAppendDistinct(long descriptorPtr, QueryDescriptor includeDescriptor); private static native void nativeAppendLimit(long descriptorPtr, long limit); + private static native void nativeAppendInclude(long descriptorPtr, long includeDescriptorPtr); private static native boolean nativeIsEmpty(long descriptorPtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/IncludeDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/core/IncludeDescriptor.java new file mode 100644 index 0000000000..8c97f20e33 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/core/IncludeDescriptor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.internal.core; + +import java.util.EnumSet; + +import io.realm.RealmFieldType; +import io.realm.internal.NativeObject; +import io.realm.internal.Table; +import io.realm.internal.fields.FieldDescriptor; + +/** + * Creates the Java wrapper for a `realm::IncludeDescriptor`. + */ +public class IncludeDescriptor implements NativeObject { + + private static final long nativeFinalizerMethodPtr = nativeGetFinalizerMethodPtr(); + private final long nativePtr; + + public static IncludeDescriptor createInstance(FieldDescriptor.SchemaProxy schemaConnector, Table table, String includePath) { + EnumSet supportedIntermediateColumnTypes = EnumSet.of(RealmFieldType.OBJECT, RealmFieldType.LIST, RealmFieldType.LINKING_OBJECTS); + EnumSet supportedFinalColumnType = EnumSet.of(RealmFieldType.LINKING_OBJECTS); + FieldDescriptor fieldDescriptor = FieldDescriptor.createFieldDescriptor( + schemaConnector, + table, + includePath, + supportedIntermediateColumnTypes, + supportedFinalColumnType); + return new IncludeDescriptor(table, fieldDescriptor.getColumnIndices(), fieldDescriptor.getNativeTablePointers()); + } + + private IncludeDescriptor(Table table, long[] columnIndices, long[] nativeTablePointers) { + nativePtr = nativeCreate(table.getNativePtr(), columnIndices, nativeTablePointers); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerMethodPtr; + } + + private static native long nativeGetFinalizerMethodPtr(); + private static native long nativeCreate(long tablePtr, long[] columnIndices, long[] tablePtrIndices); +} + diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java index a9f1b9cbe4..81cb17d36d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java @@ -244,7 +244,7 @@ protected final void verifyInternalColumnType(String tableName, String columnNam /** * Store the results of compiling the field description. - * Subclasses call this as the last action in + * Subclasses call this as the last action after `compileFieldDescription` is called. * * @param finalClassName the name of the final table in the field description. * @param finalColumnName the name of the final column in the field description. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java index a6e4203a55..6f2e8c0d20 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java @@ -3,16 +3,18 @@ import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.TimeUnit; import io.realm.DynamicRealm; import io.realm.OrderedCollectionChangeSet; -import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmList; @@ -22,10 +24,13 @@ import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; import io.realm.SyncManager; +import io.realm.SyncSession; import io.realm.SyncTestUtils; import io.realm.SyncUser; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; +import io.realm.entities.BacklinksSource; +import io.realm.entities.BacklinksTarget; import io.realm.entities.Dog; import io.realm.log.RealmLog; import io.realm.objectserver.model.PartialSyncModule; @@ -592,6 +597,146 @@ public void deletingSubscriptionObjectUnsubscribes() throws InterruptedException }); } + @Test + @RunTestInLooperThread + public void includeLinkingObjects_throwsOnInvalidTypes() { + SyncUser user1 = UserFactory.createUniqueUser(Constants.AUTH_URL); + + Realm realm = getPartialRealm(user1); + realm.executeTransaction(r -> { + RealmQuery query = r.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_STRING, "child"); + + Set invalidFields = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + AllJavaTypes.FIELD_IGNORED, AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_SHORT, AllJavaTypes.FIELD_INT, + AllJavaTypes.FIELD_LONG, AllJavaTypes.FIELD_ID, AllJavaTypes.FIELD_BYTE, AllJavaTypes.FIELD_FLOAT, AllJavaTypes.FIELD_DOUBLE, + AllJavaTypes.FIELD_BOOLEAN, AllJavaTypes.FIELD_DATE, AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_OBJECT, + AllJavaTypes.FIELD_LIST, AllJavaTypes.FIELD_STRING_LIST, AllJavaTypes.FIELD_BINARY_LIST, AllJavaTypes.FIELD_BOOLEAN_LIST, + AllJavaTypes.FIELD_LONG_LIST, AllJavaTypes.FIELD_INTEGER_LIST, AllJavaTypes.FIELD_SHORT_LIST, AllJavaTypes.FIELD_BYTE_LIST, + AllJavaTypes.FIELD_DOUBLE_LIST, AllJavaTypes.FIELD_FLOAT_LIST, AllJavaTypes.FIELD_DATE_LIST))); + + for (String field : invalidFields) { + try { + query.includeLinkingObjects(field); + fail(field + " failed."); + } catch (IllegalArgumentException ignore) { + } + } + }); + realm.close(); + looperThread.postRunnable(() -> { + looperThread.testComplete(); + }); + } + + @Test + @RunTestInLooperThread + public void includeLinkingObjects_differentTable() throws InterruptedException { + // Upload data + SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); + Realm realm1 = getPartialRealm(admin); + realm1.executeTransaction(realm -> { + BacklinksTarget child1 = new BacklinksTarget(); + child1.setId(1); + BacklinksTarget child2 = new BacklinksTarget(); + child2.setId(2); + + BacklinksSource parent1 = new BacklinksSource(); + parent1.setName("parent-1"); + parent1.setChild(child1); + BacklinksSource parent2 = new BacklinksSource(); + parent2.setName("parent-2"); + + realm.insert(parent1); + realm.insert(parent2); + realm.insert(child2); + }); + SyncManager.getSession((SyncConfiguration) realm1.getConfiguration()).uploadAllLocalChanges(); + realm1.close(); + + // Create subscription with includes + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + Realm realm2 = getPartialRealm(admin); + SyncSession session = SyncManager.getSession((SyncConfiguration) realm2.getConfiguration()); + assertEquals(0, realm2.where(AllJavaTypes.class).count()); + realm2.executeTransaction(realm -> { + Subscription sub = realm.where(BacklinksTarget.class) + .equalTo(BacklinksTarget.FIELD_ID, 1) + .subscribe("my-sub"); + assertEquals("id == 1", sub.getQueryDescription()); + }); + session.uploadAllLocalChanges(); + session.downloadAllServerChanges(); + realm2.refresh(); + assertEquals(1, realm2.where(BacklinksTarget.class).count()); + assertEquals(0, realm2.where(BacklinksSource.class).count()); + + // Update subscription to include parent objects + realm2.executeTransaction(realm -> { + Subscription sub = realm.where(BacklinksTarget.class) + .equalTo(BacklinksTarget.FIELD_ID, 1) + .includeLinkingObjects(BacklinksTarget.FIELD_PARENTS) + .subscribeOrUpdate("my-sub"); + assertEquals("id == 1 INCLUDE(@links.class_BacklinksSource.child)", sub.getQueryDescription()); + }); + session.uploadAllLocalChanges(); + session.downloadAllServerChanges(); + realm2.refresh(); + assertEquals(1, realm2.where(BacklinksTarget.class).count()); + assertEquals(1, realm2.where(BacklinksSource.class).count()); + realm2.close(); + looperThread.postRunnable(() -> { + looperThread.testComplete(); + }); + } + + @Test + @RunTestInLooperThread + public void includeLinkingObjects_sameTable() throws InterruptedException { + // Upload data + SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); + Realm realm1 = getPartialRealm(admin); + realm1.executeTransaction(realm -> { + AllJavaTypes obj1 = new AllJavaTypes(1); + obj1.setFieldString("parent"); + AllJavaTypes obj2 = new AllJavaTypes(2); + obj2.setFieldString("child"); + obj1.setFieldObject(obj2); + realm.insert(obj1); + }); + SyncManager.getSession((SyncConfiguration) realm1.getConfiguration()).uploadAllLocalChanges(); + realm1.close(); + + // Create subscription with includes + SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); + Realm realm2 = getPartialRealm(admin); + SyncSession session = SyncManager.getSession((SyncConfiguration) realm2.getConfiguration()); + assertEquals(0, realm2.where(AllJavaTypes.class).count()); + realm2.executeTransaction(realm -> { + realm.where(AllJavaTypes.class) + .equalTo(AllJavaTypes.FIELD_STRING, "child") + .subscribe("my-sub"); + }); + session.uploadAllLocalChanges(); + session.downloadAllServerChanges(); + realm2.refresh(); + assertEquals(1, realm2.where(AllJavaTypes.class).count()); + + // Update subscription to include parent objects + realm2.executeTransaction(realm -> { + realm.where(AllJavaTypes.class) + .equalTo(AllJavaTypes.FIELD_STRING, "child") + .includeLinkingObjects(AllJavaTypes.FIELD_LO_OBJECT) + .subscribeOrUpdate("my-sub"); + }); + session.uploadAllLocalChanges(); + session.downloadAllServerChanges(); + realm2.refresh(); + assertEquals(2, realm2.where(AllJavaTypes.class).count()); + realm2.close(); + looperThread.postRunnable(() -> { + looperThread.testComplete(); + }); + } private Realm getPartialRealm(SyncUser user) { final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncModule.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncModule.java index 6552293cf7..103a256d21 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncModule.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PartialSyncModule.java @@ -17,7 +17,10 @@ package io.realm.objectserver.model; import io.realm.annotations.RealmModule; +import io.realm.entities.AllJavaTypes; +import io.realm.entities.BacklinksSource; +import io.realm.entities.BacklinksTarget; -@RealmModule(classes = {PartialSyncObjectA.class, PartialSyncObjectB.class}) +@RealmModule(classes = {PartialSyncObjectA.class, PartialSyncObjectB.class, AllJavaTypes.class, BacklinksSource.class, BacklinksTarget.class}) public class PartialSyncModule { } diff --git a/tools/sync_test_server/ros/package.json b/tools/sync_test_server/ros/package.json index 777423ed70..a661d3f5de 100644 --- a/tools/sync_test_server/ros/package.json +++ b/tools/sync_test_server/ros/package.json @@ -9,7 +9,7 @@ "start": "npm run build && NODE_TLS_REJECT_UNAUTHORIZED=0 node dist/index.js" }, "devDependencies": { - "typescript": "2.5.3" + "typescript": "3.4.3" }, "dependencies": { "realm-object-server": "%ROS_VERSION%" From 6a91a7cbfbf99dfaec9173ffc8e6b3b61640e504 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 May 2019 10:29:55 +0200 Subject: [PATCH 1371/2110] Fix monkey output --- examples/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 5e91e876da..4b3907a7d9 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -65,10 +65,9 @@ allprojects { process.waitFor() if (process.exitValue() != 0 - || serr?.toString()?.trim()?.size() > 0 || !sout?.toString()?.trim()?.contains("Events injected: ${numberOfEvents}")) { // fail Gradle build - throw new GradleException("monkey failed for AppID: ${appId} \nStd out: ${sout}\nStd err: ${serr}") + throw new GradleException("monkey failed for AppID: ${appId} \nExit code: ${process.exitValue()}\nStd out: ${sout}\nStd err: ${serr}") } } } From dd520f410231a6a9f5d3ace7a597b3be76d6451d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 May 2019 10:33:42 +0200 Subject: [PATCH 1372/2110] Update changelog release date --- CHANGELOG.md | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3c30020bb..1bce63b83f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.11.0(YYYY-MM-DD) +## 5.11.0(2019-05-01) ### Enhancements * [ObjectServer] Added `RealmQuery.includeLinkingObjects()`. This is only relevant for Query-based Realms and tells subscriptions to include objects linked through `@LinkingObjects` fields as part of the subscription as well. Objects referenced through objects and lists are always included as a default. (Issue [#6426](https://github.com/realm/realm-java/issues/6426)) @@ -13,6 +13,7 @@ * [ObjectServer] HTTP requests made by the Sync client now always include a Host: header, as required by HTTP/1.1, although its value will be empty if no value is specified by the application. * [ObjectServer] The server no longer rejects subscriptions based on queries with distinct and/or limit clauses. * [ObjectServer] If a user had `canCreate` but not `canUpdate` privileges on a class, the user would be able to create the object, but not actually set any meaningful values on that object, despite the rule that objects created within the same transaction can always be modified. +* Native crash happening if bulk updating a field in a `RealmResult` would cause the object to no longer be part of the query result. (Issue [#6478](https://github.com/realm/realm-java/issues/6478), since 5.8.0). ### Compatibility * Realm Object Server: 3.21.0-rc1 or later. @@ -25,23 +26,6 @@ * Updated to Object Store commit e4b1314d21b521fd604af7f1aacf3ca94272c19a -## 5.10.1(YYYY-MM-DD) - -###Enhancements -* None. - -### Fixed -* Native crash happening if bulk updating a field in a `RealmResult` would cause the object to no longer be part of the query result. (Issue [#6478](https://github.com/realm/realm-java/issues/6478), since 5.8.0). - -### Compatibility -* Realm Object Server: 3.11.0 or later. -* File format: Generates Realms with format v9 (Reads and upgrades all previous formats). -* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. - -### Internal -* Updated to Object Store commit: cc3db611b1c10d2b890a92fa0f4b8291bc0f3ba2 - - ## 5.10.0(2019-03-22) ### Enhancements From 1455bffd885cbf2ae32ccc9036b17f7aa3d15f6e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 May 2019 10:35:05 +0200 Subject: [PATCH 1373/2110] Release v5.11.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 48092a8f91..57f82f727c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.11.0-SNAPSHOT \ No newline at end of file +5.11.0 \ No newline at end of file From e4d7db462c0daa7d023811b6cb69f24b8cf8c9b4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 May 2019 10:35:05 +0200 Subject: [PATCH 1374/2110] Prepare next release v5.11.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 57f82f727c..51ce1e379b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.11.0 \ No newline at end of file +5.11.1-SNAPSHOT \ No newline at end of file From e5b4c2cb04ac94fcd6e54e86873f4b4eeecc7fae Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 May 2019 12:58:27 +0200 Subject: [PATCH 1375/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 51ce1e379b..647e7292a9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.11.1-SNAPSHOT \ No newline at end of file +5.12.0-SNAPSHOT \ No newline at end of file From e7b4e424539db4c96bd7eca83d5eeb08024729c3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 3 May 2019 11:58:37 +0200 Subject: [PATCH 1376/2110] Improve compatibility section in changelog and API docs (#6504) --- CHANGELOG.md | 4 +++- realm/realm-library/src/main/java/io/realm/RealmQuery.java | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bce63b83f..4efbb5d1ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 5.11.0(2019-05-01) +NOTE: This version is only compatible with Realm Object Server 3.21.0 or later. + ### Enhancements * [ObjectServer] Added `RealmQuery.includeLinkingObjects()`. This is only relevant for Query-based Realms and tells subscriptions to include objects linked through `@LinkingObjects` fields as part of the subscription as well. Objects referenced through objects and lists are always included as a default. (Issue [#6426](https://github.com/realm/realm-java/issues/6426)) * Encryption now uses hardware optimized functions, which significantly improves the performance of encrypted Realms. ([Realm Core PR #3241](https://github.com/realm/realm-core/pull/3241)) @@ -16,7 +18,7 @@ * Native crash happening if bulk updating a field in a `RealmResult` would cause the object to no longer be part of the query result. (Issue [#6478](https://github.com/realm/realm-java/issues/6478), since 5.8.0). ### Compatibility -* Realm Object Server: 3.21.0-rc1 or later. +* Realm Object Server: 3.21.0 or later. * File format: Generates Realms with format v9 (Reads and upgrades all previous formats) * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index c18ff2dcc5..3f57236013 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -2065,7 +2065,10 @@ public RealmQuery limit(long limit) { * subscription by default. *

            * This predicate can be called multiple times, in which case all fields will be added to the subscription. - * + *

            + * NOTE: This method is only supported when connecting to Realm Object Server 3.21.0 or later. If you use it with previous + * versions of Realm Object Server, an {@link IllegalArgumentException} will be sent to {@link OrderedCollectionChangeSet#getError()}. + * * @param firstIncludePath the first {@link io.realm.annotations.LinkingObjects} field to add. * @param remainingFieldPaths any remaining {@link io.realm.annotations.LinkingObjects} fields to add. * @throws IllegalStateException if called on a non-query-based Realm. From fa111cd983ebb8fc09d0c3615f21bbe3ff8e0f72 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 4 May 2019 00:12:11 +0200 Subject: [PATCH 1377/2110] PermissionManager should ignore intermittent errors (#6506) --- CHANGELOG.md | 17 +++++ .../java/io/realm/PermissionManager.java | 60 ++++++++++++--- .../java/io/realm/SyncManager.java | 15 ++++ .../java/io/realm/PermissionManagerTests.java | 74 +++++++++++++++++-- 4 files changed, 150 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4efbb5d1ae..499c1a45cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 5.12.0(YYYY-MM-DD) + +### Enhancements +* [ObjectServer] Added `SyncManager.refreshConnections()` that can be used to manually trigger a reconnect for all sessions. This is useful if the device has been offline for a long time or fail to detect that it regained connectivity. (Issue [#259](https://github.com/realm/realm-java-private/issues/259)) + +### Fixed +* [ObjectServer] `PermissionManager` stopped working if an intermittent network error was reported. (Issue [#6492](https://github.com/realm/realm-java/issues/6492), since 3.7.0) + +### Compatibility +* Realm Object Server: 3.21.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None. + + ## 5.11.0(2019-05-01) NOTE: This version is only compatible with Realm Object Server 3.21.0 or later. diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java index f668c6385d..6ffd9ad5c3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java @@ -29,6 +29,8 @@ import java.util.List; import java.util.Map; +import javax.annotation.Nullable; + import io.realm.internal.OsRealmConfig; import io.realm.internal.Util; import io.realm.internal.permissions.BasePermissionApi; @@ -454,13 +456,14 @@ private void checkIfRealmsAreOpenedAndRunDelayedTasks() { } private void checkCallbackNotNull(PermissionManagerBaseCallback callback) { + //noinspection ConstantConditions if (callback == null) { throw new IllegalArgumentException("Non-null 'callback' required."); } } private boolean isReady() { - return managementRealm != null && permissionRealm != null; // && defaultPermissionRealm != null; + return managementRealm != null && permissionRealm != null && defaultPermissionRealm != null; } private void checkIfValid() { @@ -588,7 +591,6 @@ public void run() { loadingPermissions.addChangeListener(new RealmChangeListener >() { @Override public void onChange(RealmResults loadedPermissions) { - RealmLog.error(String.format("1stCallback: Size: %s, Permissions: %s", loadedPermissions.size(), Arrays.toString(loadedPermissions.toArray()))); // Don't report ready until both __permission and __management Realm are there if (loadedPermissions.size() > 1) { loadingPermissions.removeChangeListener(this); @@ -1012,17 +1014,34 @@ protected final boolean checkAndReportInvalidState() { return true; } - // We are juggling two different Realms. If only one fail, expose that error directly. - // Otherwise try to sensible join the two error messages before returning it to the user. - // TODO: Should we expose the underlying Realm errors directly? What else would make sense? + // We are juggling three different Realms. If only one fail, expose that error directly. + // Otherwise try to sensible join the three error messages before returning it to the user. boolean managementErrorHappened; boolean permissionErrorHappened; boolean defaultPermissionErrorHappened; ObjectServerError managementError; ObjectServerError permissionError; ObjectServerError defaultPermissionError; + + // Only hold lock while making a safe copy of current error state synchronized (permissionManager.errorLock) { - // Only hold lock while making a safe copy of current error state + + // Check if errors are only intermittent. In that case, just ignore them as + // we expect them to resolve eventually. So no reason to cause extra work for + // users of the PermissionManager. + if (permissionManager.managementRealmError != null && isIntermittentError(permissionManager.managementRealmError)) { + RealmLog.debug("Ignore Management Realm error: " + permissionManager.managementRealmError.toString()); + permissionManager.managementRealmError = null; + } + if (permissionManager.permissionRealmError != null && isIntermittentError(permissionManager.permissionRealmError)) { + RealmLog.debug("Ignore Permission Realm error: " + permissionManager.permissionRealmError.toString()); + permissionManager.permissionRealmError = null; + } + if (permissionManager.defaultPermissionRealmError != null && isIntermittentError(permissionManager.defaultPermissionRealmError)) { + RealmLog.debug("Ignore Default Permission Realm error: " + permissionManager.defaultPermissionRealmError.toString()); + permissionManager.defaultPermissionRealmError = null; + } + managementErrorHappened = (permissionManager.managementRealmError != null); permissionErrorHappened = (permissionManager.permissionRealmError != null); defaultPermissionErrorHappened = (permissionManager.defaultPermissionRealmError != null); @@ -1032,7 +1051,7 @@ protected final boolean checkAndReportInvalidState() { } // Everything seems valid - if (!permissionErrorHappened && !managementErrorHappened) {// && !defaultPermissionErrorHappened) { + if (!permissionErrorHappened && !managementErrorHappened && !defaultPermissionErrorHappened) { return false; } @@ -1081,6 +1100,31 @@ protected final boolean checkAndReportInvalidState() { return true; } + private boolean isIntermittentError(@Nullable ObjectServerError error) { + // Unknown errors normally have a undefined category as well. All serious errors + // should already be covered by known categories, so expect unknown categories + // to be intermittent. + if (error == null || error.getErrorCode() == ErrorCode.UNKNOWN) { + return true; + } + + switch (error.getErrorType()) { + case ErrorCode.Type.CONNECTION: + case ErrorCode.Type.HTTP: + case ErrorCode.Type.MISC: + case ErrorCode.Type.UNKNOWN: + return true; + + case ErrorCode.Type.AUTH: + case ErrorCode.Type.DEPRECATED: + case ErrorCode.Type.JAVA: + case ErrorCode.Type.PROTOCOL: + case ErrorCode.Type.SESSION: + default: + return false; + } + } + /** * Handle the status change from ROS and either call error or success callbacks. */ @@ -1118,10 +1162,8 @@ protected final void notifyCallbackWithError(ObjectServerError e) { // we are forced to report back UNKNOWN as error code. The real error codes // will be always part of the exception message. private ObjectServerError combineRealmErrors(Map errors) { - String errorMsg = combineErrorMessage(errors); ErrorCode errorCode = combineErrorCodes(errors); - return new ObjectServerError(errorCode, errorMsg); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index ceb1907170..5d948929d3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -594,6 +594,21 @@ private synchronized static String bindSessionWithConfig(String sessionPath, Str return null; } + /** + * Realm will automatically detect when a device gets connectivity after being offline and + * resume syncing. + *

            + * However, as some of these checks are performed using incremental backoff, this will in some + * cases not happen immediately. + *

            + * In those cases it can be beneficial to call this method manually, which will force all + * sessions to attempt to reconnect immediately and reset any timers they are using for + * incremental backoff. + */ + public static void refreshConnections() { + notifyNetworkIsBack(); + } + // Holds the certificate chain (per hostname). We need to keep the order of each certificate // according to it's depth in the chain. The depth of the last // certificate is 0. The depth of the first certificate is chain diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index d418b763d4..4582e819c1 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -506,7 +506,7 @@ public void permissionManagerAsyncTask_handlePermissionRealmError() throws NoSuc // Simulate error in the permission Realm Field permissionConfigField = pm.getClass().getDeclaredField("permissionRealmError"); permissionConfigField.setAccessible(true); - final ObjectServerError error = new ObjectServerError(ErrorCode.UNKNOWN, "Boom"); + final ObjectServerError error = new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom"); permissionConfigField.set(pm, error); PermissionManager.ApplyPermissionsCallback callback = new PermissionManager.ApplyPermissionsCallback() { @@ -519,7 +519,7 @@ public void onSuccess() { public void onError(ObjectServerError error) { assertTrue(error.getErrorMessage().startsWith("Error occurred in Realm")); assertTrue(error.getErrorMessage().contains("Permission Realm")); - assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); + assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); looperThread.testComplete(); } }; @@ -535,7 +535,7 @@ public void permissionManagerAsyncTask_handleManagementRealmError() throws NoSuc looperThread.closeAfterTest(pm); // Simulate error in the permission Realm - final ObjectServerError error = new ObjectServerError(ErrorCode.UNKNOWN, "Boom"); + final ObjectServerError error = new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom"); setRealmError(pm, "managementRealmError", error); PermissionManager.ApplyPermissionsCallback callback = new PermissionManager.ApplyPermissionsCallback() { @@ -548,7 +548,7 @@ public void onSuccess() { public void onError(ObjectServerError error) { assertTrue(error.getErrorMessage().startsWith("Error occurred in Realm")); assertTrue(error.getErrorMessage().contains("Management Realm")); - assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); + assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); looperThread.testComplete(); } }; @@ -564,10 +564,10 @@ public void permissionManagerAsyncTask_handleTwoErrorsSameErrorCode() throws NoS looperThread.closeAfterTest(pm); // Simulate error in the permission Realm - setRealmError(pm, "managementRealmError", new ObjectServerError(ErrorCode.CONNECTION_CLOSED, "Boom1")); + setRealmError(pm, "managementRealmError", new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom1")); // Simulate error in the management Realm - setRealmError(pm, "permissionRealmError", new ObjectServerError(ErrorCode.CONNECTION_CLOSED, "Boom2")); + setRealmError(pm, "permissionRealmError", new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom2")); PermissionManager.ApplyPermissionsCallback callback = new PermissionManager.ApplyPermissionsCallback() { @Override @@ -577,7 +577,7 @@ public void onSuccess() { @Override public void onError(ObjectServerError error) { - assertEquals(ErrorCode.CONNECTION_CLOSED, error.getErrorCode()); + assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); assertTrue(error.toString().contains("Boom1")); assertTrue(error.toString().contains("Boom2")); looperThread.testComplete(); @@ -588,6 +588,66 @@ public void onError(ObjectServerError error) { runTask(pm, callback); } + @Test + @RunTestInLooperThread(emulateMainThread = true) + public void permissionManagerAsyncTask_doNotReportIntermittentErrors() throws NoSuchFieldException, IllegalAccessException { + PermissionManager pm = user.getPermissionManager(); + looperThread.closeAfterTest(pm); + + // Simulate intermittent error in the management Realm that is possible to recover from + // These kind of errors should never reach the end user as we should recover automatically. + setRealmError(pm, "managementRealmError", new ObjectServerError(ErrorCode.UNKNOWN, "Boom1")); + + pm.getPermissions(new PermissionManager.PermissionsCallback() { + @Override + public void onSuccess(RealmResults permissions) { + assertEquals(3, permissions.size()); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(); + } + }); + } + + @Test + @RunTestInLooperThread(emulateMainThread = true) + public void permissionManagerAsyncTask_keepReportingFatalErrors() throws NoSuchFieldException, IllegalAccessException { + PermissionManager pm = user.getPermissionManager(); + looperThread.closeAfterTest(pm); + + // Simulate fatal error in the management Realm that is not possible to recover from + // This should be reported for all tasks, not just the first one. + setRealmError(pm, "managementRealmError", new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom1")); + + pm.getPermissions(new PermissionManager.PermissionsCallback() { + @Override + public void onSuccess(RealmResults permissions) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); + pm.getPermissions(new PermissionManager.PermissionsCallback() { + @Override + public void onSuccess(RealmResults permissions) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); + looperThread.testComplete(); + } + }); + } + }); + } + + @Test @RunTestInLooperThread(emulateMainThread = true) public void permissionManagerAsyncTask_handleTwoErrorsDifferentErrorCode() throws NoSuchFieldException, IllegalAccessException { From 38e7b4aea718bde5f9af1bf86f52fef4d6eec902 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 9 May 2019 12:31:47 +0200 Subject: [PATCH 1378/2110] Remove unused imports --- .../java/io/realm/RealmQueryTests.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 511cc0f518..a80cc62293 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -27,6 +27,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import io.realm.annotations.RealmClass; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AnnotationIndexTypes; @@ -44,8 +45,6 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; import static org.junit.Assert.assertEquals; @@ -1875,6 +1874,36 @@ public void isNotNull_nullableFields() { assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_OBJECT_NULL).count()); } + @Test + public void isNull_differentThanEmpty() { + // Make sure that isNull doesn't match empty string "" + realm.executeTransaction(r -> { + r.delete(NullTypes.class); + NullTypes obj = new NullTypes(); + obj.setId(1); + obj.setFieldStringNull(null); + r.insert(obj); + obj = new NullTypes(); + obj.setId(2); + obj.setFieldStringNull(""); + r.insert(obj); + obj = new NullTypes(); + obj.setId(3); + obj.setFieldStringNull("foo"); + r.insert(obj); + }); + + assertEquals(3, realm.where(NullTypes.class).findAll().size()); + + RealmResults results = realm.where(NullTypes.class).isNull(NullTypes.FIELD_STRING_NULL).findAll(); + assertEquals(1, results.size()); + assertNull(results.first().getFieldStringNull()); + + results = realm.where(NullTypes.class).isEmpty(NullTypes.FIELD_STRING_NULL).findAll(); + assertEquals(1, results.size()); + assertEquals("", results.first().getFieldStringNull()); + } + // Queries nullable field with beginsWith - all strings begin with null. @Test public void beginWith_nullForNullableStrings() { From 9f66c9aa26d74af836e32aa41eb7234a6fcdde76 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 9 May 2019 22:15:15 +0200 Subject: [PATCH 1379/2110] Fix tests --- .../src/androidTest/java/io/realm/RealmQueryTests.java | 9 --------- .../java/io/realm/SyncedRealmTests.java | 10 ++++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index a80cc62293..a29e98a8c5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -3024,15 +3024,6 @@ private void populateForDistinctInvalidTypesLinked(Realm realm) { realm.commitTransaction(); } - @Test - public void includeLinkingObjects_throwsForNonQueryBasedRealms() { - try { - realm.where(AllJavaTypes.class).includeLinkingObjects(AllJavaTypes.FIELD_STRING); - fail(); - } catch (IllegalStateException ignore) { - } - } - @Test public void distinct() { final long numberOfBlocks = 3; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index 77954d7691..51180a7fda 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -309,4 +309,14 @@ public void getSubscription() { assertNotNull(sub); assertEquals("sub", sub.getName()); } + + @Test + public void includeLinkingObjects_throwsForNonQueryBasedRealms() { + realm = getFullySyncRealm(); + try { + realm.where(AllJavaTypes.class).includeLinkingObjects(AllJavaTypes.FIELD_STRING); + fail(); + } catch (IllegalStateException ignore) { + } + } } From 94a41b09818a0816f0bde7288280747feb999cac Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 22 May 2019 10:03:14 +0200 Subject: [PATCH 1380/2110] Use AndroidX Benchmark library (#6516) --- library-benchmarks/build.gradle | 19 ++- .../src/androidTest/AndroidManifest.xml | 11 ++ .../benchmarks/CopyToRealmBenchmarks.java | 123 --------------- .../realm/benchmarks/CopyToRealmBenchmarks.kt | 114 ++++++++++++++ .../CopyToRealmOrUpdateBenchmarks.java | 143 ------------------ .../CopyToRealmOrUpdateBenchmarks.kt | 138 +++++++++++++++++ .../benchmarks/RealmAllocBenchmarks.java | 90 ----------- .../realm/benchmarks/RealmAllocBenchmarks.kt | 87 +++++++++++ .../io/realm/benchmarks/RealmBenchmarks.java | 84 ---------- .../io/realm/benchmarks/RealmBenchmarks.kt | 78 ++++++++++ .../benchmarks/RealmInsertBenchmark.java | 106 ------------- .../realm/benchmarks/RealmInsertBenchmark.kt | 104 +++++++++++++ .../benchmarks/RealmObjectReadBenchmarks.java | 86 ----------- .../benchmarks/RealmObjectReadBenchmarks.kt | 83 ++++++++++ .../RealmObjectWriteBenchmarks.java | 91 ----------- .../benchmarks/RealmObjectWriteBenchmarks.kt | 90 +++++++++++ .../benchmarks/RealmQueryBenchmarks.java | 93 ------------ .../realm/benchmarks/RealmQueryBenchmarks.kt | 91 +++++++++++ .../benchmarks/RealmResultsBenchmarks.java | 118 --------------- .../benchmarks/RealmResultsBenchmarks.kt | 116 ++++++++++++++ .../benchmarks/config/BenchmarkConfig.java | 76 ---------- .../benchmarks/config/CSVResultProcessor.java | 106 ------------- .../src/main/AndroidManifest.xml | 17 +-- 23 files changed, 931 insertions(+), 1133 deletions(-) create mode 100644 library-benchmarks/src/androidTest/AndroidManifest.xml delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.java create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/BenchmarkConfig.java delete mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/CSVResultProcessor.java diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 287910c0e6..dfd0d86713 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -1,4 +1,5 @@ buildscript { + ext.kotlin_version = '1.3.31' def properties = new Properties() properties.load(new FileInputStream("${rootDir}/../dependencies.list")) @@ -10,6 +11,7 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:${properties.get("GRADLE_BUILD_TOOLS")}" classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -22,18 +24,21 @@ allprojects { } apply plugin: 'com.android.library' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' android { - compileSdkVersion 27 + compileSdkVersion 28 buildToolsVersion "${project.ext.get("ANDROID_BUILD_TOOLS")}" defaultConfig { minSdkVersion 15 - targetSdkVersion 27 + targetSdkVersion 28 versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.benchmark.AndroidBenchmarkRunner" } buildTypes { @@ -52,10 +57,8 @@ repositories { } dependencies { - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test:rules:1.0.1' - androidTestImplementation 'junit:junit:4.12' - androidTestImplementation 'dk.ilios:spanner:0.6.0' - androidTestImplementation 'com.opencsv:opencsv:3.4' + androidTestImplementation 'androidx.test.ext:junit:1.1.0' + androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01" androidTestImplementation 'junit:junit:4.12' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/library-benchmarks/src/androidTest/AndroidManifest.xml b/library-benchmarks/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..e22ad7e7eb --- /dev/null +++ b/library-benchmarks/src/androidTest/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java deleted file mode 100644 index 16efd9642e..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.java +++ /dev/null @@ -1,123 +0,0 @@ - * Copyright 2018 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks; - -import android.support.test.InstrumentationRegistry; - -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import dk.ilios.spanner.AfterExperiment; -import dk.ilios.spanner.BeforeExperiment; -import dk.ilios.spanner.Benchmark; -import dk.ilios.spanner.BenchmarkConfiguration; -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.junit.SpannerRunner; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.RealmList; -import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.benchmarks.entities.AllTypes; -import io.realm.benchmarks.entities.AllTypesPrimaryKey; - - -@RunWith(SpannerRunner.class) -public class CopyToRealmBenchmarks { - - @BenchmarkConfiguration - public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); - - private Realm realm; - private static final int COLLECTION_SIZE = 100; - private List noPkObjects = new ArrayList<>(COLLECTION_SIZE); - private List pkObjects = new ArrayList<>(COLLECTION_SIZE); - private ArrayList complextTestObjects; - private ArrayList simpleTestObjects; - - @BeforeExperiment - public void before() { - Realm.init(InstrumentationRegistry.getTargetContext()); - RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(config); - - // Create test data - complextTestObjects = new ArrayList<>(); - for (int i = 0; i < COLLECTION_SIZE; i++) { - AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); - obj.setColumnString("obj" + i); - obj.setColumnLong(i); - obj.setColumnFloat(1.23F); - obj.setColumnDouble(1.234); - obj.setColumnBoolean(true); - obj.setColumnDate(new Date(1000)); - obj.setColumnBinary(new byte[] {1,2,3}); - obj.setColumnRealmObject(obj); - obj.setColumnRealmList(new RealmList<>(obj, obj, obj)); - obj.setColumnBooleanList(new RealmList<>(true, false, true)); - obj.setColumnStringList(new RealmList<>("foo", "bar", "baz")); - obj.setColumnBinaryList(new RealmList<>(new byte[]{0,1,2},new byte[]{2,3,4},new byte[]{4,5,6})); - obj.setColumnByteList(new RealmList<>((byte)1,(byte)2,(byte)3)); - obj.setColumnShortList(new RealmList<>((short)1,(short)2,(short)3)); - obj.setColumnIntegerList(new RealmList<>(1,2,3)); - obj.setColumnLongList(new RealmList<>(1L,2L,3L)); - obj.setColumnFloatList(new RealmList<>(1.1F, 1.2F, 1.3F)); - obj.setColumnDoubleList(new RealmList<>(1.111, 1.222, 1.333)); - obj.setColumnDateList(new RealmList<>(new Date(1000), new Date(2000), new Date(3000))); - complextTestObjects.add(obj); - } - - simpleTestObjects = new ArrayList<>(); - for (int i = 0; i < COLLECTION_SIZE; i++) { - AllTypes obj = new AllTypes(); - obj.setColumnString("obj" + i); - obj.setColumnLong(i); - obj.setColumnFloat(1.23F); - obj.setColumnDouble(1.234); - obj.setColumnBoolean(true); - obj.setColumnDate(new Date(1000)); - obj.setColumnBinary(new byte[] {1,2,3}); - simpleTestObjects.add(obj); - } - - // Setup Realm before test - realm = Realm.getInstance(config); - realm.beginTransaction(); - } - - @AfterExperiment - public void after() { - realm.cancelTransaction(); - realm.close(); - } - - @Benchmark - public void copyToRealm_complexObjects(long reps) { - for (long i = 0; i < reps; i++) { - realm.copyToRealmOrUpdate(complextTestObjects); - } - } - - @Benchmark - public void copyToRealm_simpleObjects(long reps) { - for (long i = 0; i < reps; i++) { - realm.copyToRealm(simpleTestObjects); - } - } - -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt new file mode 100644 index 0000000000..9f884e76ca --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt @@ -0,0 +1,114 @@ +/* Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.BenchmarkRule +import androidx.benchmark.measureRepeated +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.RealmList +import io.realm.benchmarks.entities.AllTypes +import io.realm.benchmarks.entities.AllTypesPrimaryKey +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* + + +@RunWith(AndroidJUnit4::class) +class CopyToRealmBenchmarks { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private val COLLECTION_SIZE = 100 + private lateinit var realm: Realm + private val noPkObjects = ArrayList(COLLECTION_SIZE) + private val pkObjects = ArrayList(COLLECTION_SIZE) + private lateinit var complextTestObjects: ArrayList + private lateinit var simpleTestObjects: ArrayList + + @Before + fun before() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + val config = RealmConfiguration.Builder().build() + Realm.deleteRealm(config) + + // Create test data + complextTestObjects = ArrayList() + for (i in 0 until COLLECTION_SIZE) { + val obj = AllTypesPrimaryKey() + obj.columnString = "obj$i" + obj.columnLong = i.toLong() + obj.columnFloat = 1.23f + obj.columnDouble = 1.234 + obj.isColumnBoolean = true + obj.columnDate = Date(1000) + obj.columnBinary = byteArrayOf(1, 2, 3) + obj.columnRealmObject = obj + obj.columnRealmList = RealmList(obj, obj, obj) + obj.columnBooleanList = RealmList(true, false, true) + obj.columnStringList = RealmList("foo", "bar", "baz") + obj.columnBinaryList = RealmList(byteArrayOf(0, 1, 2), byteArrayOf(2, 3, 4), byteArrayOf(4, 5, 6)) + obj.columnByteList = RealmList(1.toByte(), 2.toByte(), 3.toByte()) + obj.columnShortList = RealmList(1.toShort(), 2.toShort(), 3.toShort()) + obj.columnIntegerList = RealmList(1, 2, 3) + obj.columnLongList = RealmList(1L, 2L, 3L) + obj.columnFloatList = RealmList(1.1f, 1.2f, 1.3f) + obj.columnDoubleList = RealmList(1.111, 1.222, 1.333) + obj.columnDateList = RealmList(Date(1000), Date(2000), Date(3000)) + complextTestObjects.add(obj) + } + + simpleTestObjects = ArrayList() + for (i in 0 until COLLECTION_SIZE) { + val obj = AllTypes() + obj.columnString = "obj$i" + obj.columnLong = i.toLong() + obj.columnFloat = 1.23f + obj.columnDouble = 1.234 + obj.isColumnBoolean = true + obj.columnDate = Date(1000) + obj.columnBinary = byteArrayOf(1, 2, 3) + simpleTestObjects.add(obj) + } + + // Setup Realm before test + realm = Realm.getInstance(config) + realm.beginTransaction() + } + + @After + fun after() { + realm.cancelTransaction() + realm.close() + } + + @Test + fun copyToRealm_complexObjects() = benchmarkRule.measureRepeated { + realm.copyToRealmOrUpdate(complextTestObjects) + } + + @Test + fun copyToRealm_simpleObjects() = benchmarkRule.measureRepeated { + realm.copyToRealm(simpleTestObjects) + } + +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java deleted file mode 100644 index 8490a274a9..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.java +++ /dev/null @@ -1,143 +0,0 @@ - * Copyright 2018 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks; - -import android.support.test.InstrumentationRegistry; - -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import dk.ilios.spanner.AfterExperiment; -import dk.ilios.spanner.BeforeExperiment; -import dk.ilios.spanner.Benchmark; -import dk.ilios.spanner.BenchmarkConfiguration; -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.junit.SpannerRunner; -import io.realm.ImportFlag; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.RealmList; -import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.benchmarks.entities.AllTypes; -import io.realm.benchmarks.entities.AllTypesPrimaryKey; - - -@RunWith(SpannerRunner.class) -public class CopyToRealmOrUpdateBenchmarks { - - @BenchmarkConfiguration - public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); - - private Realm realm; - private static final int COLLECTION_SIZE = 100; - private List noPkObjects = new ArrayList<>(COLLECTION_SIZE); - private List pkObjects = new ArrayList<>(COLLECTION_SIZE); - private ArrayList complextTestObjects; - private ArrayList simpleTestObjects; - - @BeforeExperiment - public void before() { - Realm.init(InstrumentationRegistry.getTargetContext()); - RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(config); - - // Setup Realm before test - realm = Realm.getInstance(config); - realm.beginTransaction(); - - // Create test data - complextTestObjects = new ArrayList<>(); - for (int i = 0; i < COLLECTION_SIZE; i++) { - AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); - obj.setColumnString("obj" + i); - obj.setColumnLong(i); - obj.setColumnFloat(1.23F); - obj.setColumnDouble(1.234); - obj.setColumnBoolean(true); - obj.setColumnDate(new Date(1000)); - obj.setColumnBinary(new byte[] {1,2,3}); - obj.setColumnRealmObject(obj); - obj.setColumnRealmList(new RealmList<>(obj, obj, obj)); - obj.setColumnBooleanList(new RealmList<>(true, false, true)); - obj.setColumnStringList(new RealmList<>("foo", "bar", "baz")); - obj.setColumnBinaryList(new RealmList<>(new byte[]{0,1,2},new byte[]{2,3,4},new byte[]{4,5,6})); - obj.setColumnByteList(new RealmList<>((byte)1,(byte)2,(byte)3)); - obj.setColumnShortList(new RealmList<>((short)1,(short)2,(short)3)); - obj.setColumnIntegerList(new RealmList<>(1,2,3)); - obj.setColumnLongList(new RealmList<>(1L,2L,3L)); - obj.setColumnFloatList(new RealmList<>(1.1F, 1.2F, 1.3F)); - obj.setColumnDoubleList(new RealmList<>(1.111, 1.222, 1.333)); - obj.setColumnDateList(new RealmList<>(new Date(1000), new Date(2000), new Date(3000))); - complextTestObjects.add(obj); - } - - simpleTestObjects = new ArrayList<>(); - for (int i = 0; i < COLLECTION_SIZE; i++) { - AllTypes obj = new AllTypes(); - obj.setColumnString("obj" + i); - obj.setColumnLong(i); - obj.setColumnFloat(1.23F); - obj.setColumnDouble(1.234); - obj.setColumnBoolean(true); - obj.setColumnDate(new Date(1000)); - obj.setColumnBinary(new byte[] {1,2,3}); - simpleTestObjects.add(obj); - } - - realm.copyToRealmOrUpdate(complextTestObjects); - realm.copyToRealmOrUpdate(simpleTestObjects); - realm.commitTransaction(); - realm.beginTransaction(); - } - - @AfterExperiment - public void after() { - realm.cancelTransaction(); - realm.close(); - } - - @Benchmark - public void copyToRealmOrDiffedUpdate_complexObjects(long reps) { - for (long i = 0; i < reps; i++) { - realm.copyToRealmOrUpdate(complextTestObjects, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET); - } - } - - @Benchmark - public void copyToRealmOrFullUpdate_complexObjects(long reps) { - for (long i = 0; i < reps; i++) { - realm.copyToRealmOrUpdate(complextTestObjects); - } - } - - @Benchmark - public void copyToRealmOrDiffedUpdate_simpleObjects(long reps) { - for (long i = 0; i < reps; i++) { - realm.copyToRealm(simpleTestObjects, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET); - } - } - - @Benchmark - public void copyToRealmOrFullUpdate_simpleObjects(long reps) { - for (long i = 0; i < reps; i++) { - realm.copyToRealm(simpleTestObjects); - } - } - -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt new file mode 100644 index 0000000000..912c9266bb --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt @@ -0,0 +1,138 @@ +/* Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.BenchmarkRule +import androidx.benchmark.measureRepeated +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.ImportFlag +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.RealmList +import io.realm.benchmarks.entities.AllTypes +import io.realm.benchmarks.entities.AllTypesPrimaryKey +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* + + +@RunWith(AndroidJUnit4::class) +class CopyToRealmOrUpdateBenchmarks { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private val COLLECTION_SIZE = 100 + private lateinit var realm: Realm + private val noPkObjects = ArrayList(COLLECTION_SIZE) + private val pkObjects = ArrayList(COLLECTION_SIZE) + private var complextTestObjects: ArrayList? = null + private var simpleTestObjects: ArrayList? = null + + @Before + fun before() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + val config = RealmConfiguration.Builder().build() + Realm.deleteRealm(config) + + // Setup Realm before test + realm = Realm.getInstance(config) + realm.beginTransaction() + + // Create test data + complextTestObjects = ArrayList() + for (i in 0 until COLLECTION_SIZE) { + val obj = AllTypesPrimaryKey() + obj.columnString = "obj$i" + obj.columnLong = i.toLong() + obj.columnFloat = 1.23f + obj.columnDouble = 1.234 + obj.isColumnBoolean = true + obj.columnDate = Date(1000) + obj.columnBinary = byteArrayOf(1, 2, 3) + obj.columnRealmObject = obj + obj.columnRealmList = RealmList(obj, obj, obj) + obj.columnBooleanList = RealmList(true, false, true) + obj.columnStringList = RealmList("foo", "bar", "baz") + obj.columnBinaryList = RealmList(byteArrayOf(0, 1, 2), byteArrayOf(2, 3, 4), byteArrayOf(4, 5, 6)) + obj.columnByteList = RealmList(1.toByte(), 2.toByte(), 3.toByte()) + obj.columnShortList = RealmList(1.toShort(), 2.toShort(), 3.toShort()) + obj.columnIntegerList = RealmList(1, 2, 3) + obj.columnLongList = RealmList(1L, 2L, 3L) + obj.columnFloatList = RealmList(1.1f, 1.2f, 1.3f) + obj.columnDoubleList = RealmList(1.111, 1.222, 1.333) + obj.columnDateList = RealmList(Date(1000), Date(2000), Date(3000)) + complextTestObjects!!.add(obj) + } + + simpleTestObjects = ArrayList() + for (i in 0 until COLLECTION_SIZE) { + val obj = AllTypes() + obj.columnString = "obj$i" + obj.columnLong = i.toLong() + obj.columnFloat = 1.23f + obj.columnDouble = 1.234 + obj.isColumnBoolean = true + obj.columnDate = Date(1000) + obj.columnBinary = byteArrayOf(1, 2, 3) + simpleTestObjects!!.add(obj) + } + + realm.copyToRealmOrUpdate(complextTestObjects) + realm.copyToRealmOrUpdate(simpleTestObjects) + realm.commitTransaction() + realm.beginTransaction() + } + + @After + fun after() { + realm.cancelTransaction() + realm.close() + } + + @Test + fun copyToRealmOrDiffedUpdate_complexObjects() { + benchmarkRule.measureRepeated { + realm.copyToRealmOrUpdate(complextTestObjects, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET) + } + } + + @Test + fun copyToRealmOrFullUpdate_complexObjects() { + benchmarkRule.measureRepeated { + realm.copyToRealmOrUpdate(complextTestObjects) + } + } + + @Test + fun copyToRealmOrDiffedUpdate_simpleObjects() { + benchmarkRule.measureRepeated { + realm.copyToRealm(simpleTestObjects, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET) + } + } + + @Test + fun copyToRealmOrFullUpdate_simpleObjects() { + benchmarkRule.measureRepeated { + realm.copyToRealm(simpleTestObjects) + } + } + +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.java deleted file mode 100644 index 3365dabc55..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks; - -import android.support.test.InstrumentationRegistry; - -import org.junit.runner.RunWith; - -import dk.ilios.spanner.AfterExperiment; -import dk.ilios.spanner.BeforeExperiment; -import dk.ilios.spanner.Benchmark; -import dk.ilios.spanner.BenchmarkConfiguration; -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.junit.SpannerRunner; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.RealmQuery; -import io.realm.RealmResults; -import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.benchmarks.entities.AllTypes; - - -@RunWith(SpannerRunner.class) -public class RealmAllocBenchmarks { - @BenchmarkConfiguration - public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); - - private Realm realm; - - @BeforeExperiment - public void before() { - Realm.init(InstrumentationRegistry.getTargetContext()); - RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(config); - realm = Realm.getInstance(config); - realm.beginTransaction(); - realm.createObject(AllTypes.class).getColumnRealmList().add(realm.createObject(AllTypes.class)); - realm.commitTransaction(); - } - - @AfterExperiment - public void after() { - realm.close(); - } - - @Benchmark - public void createObjects(long reps) { - RealmResults results = realm.where(AllTypes.class).findAll(); - for (long i = 0; i < reps; i++) { - results.first(); - } - } - - @Benchmark - public void createQueries(long reps) { - for (long i = 0; i < reps; i++) { - realm.where(AllTypes.class); - } - } - @Benchmark - public void createRealmResults(long reps) { - RealmQuery query = realm.where(AllTypes.class); - for (long i = 0; i < reps; i++) { - query.findAll(); - } - } - - @Benchmark - public void createRealmLists(long reps) { - AllTypes allTypes = realm.where(AllTypes.class).findFirst(); - for (long i = 0; i < reps; i++) { - //noinspection ConstantConditions - allTypes.getColumnRealmList(); - } - } -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt new file mode 100644 index 0000000000..cd69171559 --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.BenchmarkRule +import androidx.benchmark.measureRepeated +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.benchmarks.entities.AllTypes +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class RealmAllocBenchmarks { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private lateinit var realm: Realm + + @Before + fun before() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + val config = RealmConfiguration.Builder().build() + Realm.deleteRealm(config) + realm = Realm.getInstance(config) + realm.beginTransaction() + realm.createObject(AllTypes::class.java).columnRealmList.add(realm.createObject(AllTypes::class.java)) + realm.commitTransaction() + } + + @After + fun after() { + realm.close() + } + + @Test + fun createObjects() { + val results = realm.where(AllTypes::class.java).findAll() + benchmarkRule.measureRepeated { + results.first() + } + } + + @Test + fun createQueries() { + benchmarkRule.measureRepeated { + realm.where(AllTypes::class.java) + } + } + + @Test + fun createRealmResults() { + val query = realm.where(AllTypes::class.java) + benchmarkRule.measureRepeated { + query.findAll() + } + } + + @Test + fun createRealmLists() { + val allTypes = realm.where(AllTypes::class.java).findFirst()!! + benchmarkRule.measureRepeated { + allTypes.columnRealmList + } + } +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.java deleted file mode 100644 index a05d2676e8..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks; - -import android.support.test.InstrumentationRegistry; - -import org.junit.runner.RunWith; - -import dk.ilios.spanner.AfterExperiment; -import dk.ilios.spanner.BeforeExperiment; -import dk.ilios.spanner.Benchmark; -import dk.ilios.spanner.BenchmarkConfiguration; -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.junit.SpannerRunner; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.benchmarks.entities.AllTypes; - - -@RunWith(SpannerRunner.class) -public class RealmBenchmarks { - - @BenchmarkConfiguration - public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); - - private Realm realm; - private AllTypes readObject; - private RealmConfiguration coldConfig; - - @BeforeExperiment - public void before() { - Realm.init(InstrumentationRegistry.getTargetContext()); - coldConfig = new RealmConfiguration.Builder().name("cold").build(); - RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(coldConfig); - Realm.deleteRealm(config); - realm = Realm.getInstance(config); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - readObject = realm.createObject(AllTypes.class); - readObject.setColumnString("Foo"); - readObject.setColumnLong(42); - readObject.setColumnDouble(1.234D); - } - }); - } - - @AfterExperiment - public void after() { - realm.close(); - } - - @Benchmark - public void coldCreateAndClose(long reps) { - for (long i = 0; i < reps; i++) { - Realm realm = Realm.getInstance(coldConfig); - realm.close(); - } - } - - @Benchmark - public void emptyTransaction(long reps) { - for (long i = 0; i < reps; i++) { - realm.beginTransaction(); - realm.commitTransaction(); - } - } -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt new file mode 100644 index 0000000000..ec7862954f --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.BenchmarkRule +import androidx.benchmark.measureRepeated +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.benchmarks.entities.AllTypes +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RealmBenchmarks { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private lateinit var realm: Realm + private lateinit var readObject: AllTypes + private lateinit var coldConfig: RealmConfiguration + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + coldConfig = RealmConfiguration.Builder().name("cold").build() + val config = RealmConfiguration.Builder().build() + Realm.deleteRealm(coldConfig) + Realm.deleteRealm(config) + realm = Realm.getInstance(config) + realm.executeTransaction { realm -> + readObject = realm.createObject(AllTypes::class.java) + readObject.columnString = "Foo" + readObject.columnLong = 42 + readObject.columnDouble = 1.234 + } + } + + @After + fun tearDown() { + realm.close() + } + + @Test + fun coldCreateAndClose() { + benchmarkRule.measureRepeated { + val realm = Realm.getInstance(coldConfig) + realm.close() + } + } + + @Test + fun emptyTransaction() { + benchmarkRule.measureRepeated { + realm.beginTransaction() + realm.commitTransaction() + } + } +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.java deleted file mode 100644 index b8151e9e95..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks; - -import android.support.test.InstrumentationRegistry; - -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -import dk.ilios.spanner.AfterExperiment; -import dk.ilios.spanner.BeforeExperiment; -import dk.ilios.spanner.Benchmark; -import dk.ilios.spanner.BenchmarkConfiguration; -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.junit.SpannerRunner; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.benchmarks.entities.AllTypes; -import io.realm.benchmarks.entities.AllTypesPrimaryKey; - - -@RunWith(SpannerRunner.class) -public class RealmInsertBenchmark { - - @BenchmarkConfiguration - public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); - - private Realm realm; - private static final int COLLECTION_SIZE = 100; - private List noPkObjects = new ArrayList<>(COLLECTION_SIZE); - private List pkObjects = new ArrayList<>(COLLECTION_SIZE); - - @BeforeExperiment - public void before() { - Realm.init(InstrumentationRegistry.getTargetContext()); - RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(config); - realm = Realm.getInstance(config); - - for (int i = 0; i < COLLECTION_SIZE; i++) { - noPkObjects.add(new AllTypes()); - } - - for (int i = 0; i < COLLECTION_SIZE; i++) { - AllTypesPrimaryKey allTypesPrimaryKey = new AllTypesPrimaryKey(); - allTypesPrimaryKey.setColumnLong(i); - pkObjects.add(allTypesPrimaryKey); - } - - realm.beginTransaction(); - } - - @AfterExperiment - public void after() { - realm.cancelTransaction(); - realm.close(); - } - - @Benchmark - public void insertNoPrimaryKey(long reps) { - AllTypes allTypes = new AllTypes(); - for (long i = 0; i < reps; i++) { - realm.insert(allTypes); - } - } - - @Benchmark - public void insertNoPrimaryKeyList(long reps) { - for (long i = 0; i < reps; i++) { - realm.insert(noPkObjects); - } - } - - @Benchmark - public void insertWithPrimaryKey(long reps) { - AllTypesPrimaryKey allTypesPrimaryKey = new AllTypesPrimaryKey(); - for (long i = 0; i < reps; i++) { - allTypesPrimaryKey.setColumnLong(i); - realm.insertOrUpdate(allTypesPrimaryKey); - } - } - - @Benchmark - public void insertOrUpdateWithPrimaryKeyList(long reps) { - for (long i = 0; i < reps; i++) { - realm.insertOrUpdate(pkObjects); - } - } -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt new file mode 100644 index 0000000000..dc22dd4e1c --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.BenchmarkRule +import androidx.benchmark.measureRepeated +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.benchmarks.entities.AllTypes +import io.realm.benchmarks.entities.AllTypesPrimaryKey +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* + + +@RunWith(AndroidJUnit4::class) +class RealmInsertBenchmark { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private val COLLECTION_SIZE = 100 + private lateinit var realm: Realm + private val noPkObjects = ArrayList(COLLECTION_SIZE) + private val pkObjects = ArrayList(COLLECTION_SIZE) + + @Before + fun before() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + val config = RealmConfiguration.Builder().build() + Realm.deleteRealm(config) + realm = Realm.getInstance(config) + + for (i in 0 until COLLECTION_SIZE) { + noPkObjects.add(AllTypes()) + } + + for (i in 0 until COLLECTION_SIZE) { + val allTypesPrimaryKey = AllTypesPrimaryKey() + allTypesPrimaryKey.columnLong = i.toLong() + pkObjects.add(allTypesPrimaryKey) + } + + realm.beginTransaction() + } + + @After + fun after() { + realm.cancelTransaction() + realm.close() + } + + @Test + fun insertNoPrimaryKey() { + val allTypes = AllTypes() + benchmarkRule.measureRepeated { + realm.insert(allTypes) + } + } + + @Test + fun insertNoPrimaryKeyList() { + benchmarkRule.measureRepeated { + realm.insert(noPkObjects) + } + } + + @Test + fun insertWithPrimaryKey() { + val allTypesPrimaryKey = AllTypesPrimaryKey() + var i: Long = 0 + benchmarkRule.measureRepeated { + allTypesPrimaryKey.columnLong = (i++) + realm.insertOrUpdate(allTypesPrimaryKey) + } + } + + @Test + fun insertOrUpdateWithPrimaryKeyList() { + benchmarkRule.measureRepeated { + realm.insertOrUpdate(pkObjects) + } + } + +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java deleted file mode 100644 index 974fe33815..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks; - -import android.support.test.InstrumentationRegistry; - -import org.junit.runner.RunWith; - -import dk.ilios.spanner.AfterExperiment; -import dk.ilios.spanner.BeforeExperiment; -import dk.ilios.spanner.Benchmark; -import dk.ilios.spanner.BenchmarkConfiguration; -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.junit.SpannerRunner; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.benchmarks.entities.AllTypes; - - -@RunWith(SpannerRunner.class) -public class RealmObjectReadBenchmarks { - - @BenchmarkConfiguration - public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); - - private Realm realm; - private AllTypes readObject; - - @BeforeExperiment - public void before() { - Realm.init(InstrumentationRegistry.getTargetContext()); - RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(config); - realm = Realm.getInstance(config); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - readObject = realm.createObject(AllTypes.class); - readObject.setColumnString("Foo"); - readObject.setColumnLong(42); - readObject.setColumnDouble(1.234D); - } - }); - } - - @AfterExperiment - public void after() { - realm.close(); - } - - @Benchmark - public void readString(long reps) { - for (long i = 0; i < reps; i++) { - String value = readObject.getColumnString(); - } - } - - @Benchmark - public void readLong(long reps) { - for (long i = 0; i < reps; i++) { - long value = readObject.getColumnLong(); - } - } - - @Benchmark - public void readDouble(long reps) { - for (long i = 0; i < reps; i++) { - double value = readObject.getColumnDouble(); - } - } -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt new file mode 100644 index 0000000000..42642f4d91 --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.BenchmarkRule +import androidx.benchmark.measureRepeated +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.runner.RunWith + +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.benchmarks.entities.AllTypes +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + + +@RunWith(AndroidJUnit4::class) +class RealmObjectReadBenchmarks { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private lateinit var realm: Realm + private lateinit var readObject: AllTypes + + @Before + fun before() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + val config = RealmConfiguration.Builder().build() + Realm.deleteRealm(config) + realm = Realm.getInstance(config) + realm.executeTransaction { realm -> + readObject = realm.createObject(AllTypes::class.java) + readObject.columnString = "Foo" + readObject.columnLong = 42 + readObject.columnDouble = 1.234 + } + } + + @After + fun after() { + realm.close() + } + + @Test + fun readString() { + benchmarkRule.measureRepeated { + val value = readObject.columnString + } + } + + @Test + fun readLong() { + benchmarkRule.measureRepeated { + val value = readObject.columnLong + } + } + + @Test + fun readDouble() { + benchmarkRule.measureRepeated { + val value = readObject.columnDouble + } + } +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java deleted file mode 100644 index 3cf64b0d3b..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks; - -import org.junit.runner.RunWith; - -import dk.ilios.spanner.AfterExperiment; -import dk.ilios.spanner.BeforeExperiment; -import dk.ilios.spanner.Benchmark; -import dk.ilios.spanner.BenchmarkConfiguration; -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.junit.SpannerRunner; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.benchmarks.entities.AllTypes; - - -@RunWith(SpannerRunner.class) -public class RealmObjectWriteBenchmarks { - - @BenchmarkConfiguration - public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); - - private Realm realm; - private AllTypes writeObject; - - @BeforeExperiment - public void before() { - RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(config); - realm = Realm.getInstance(config); - realm.beginTransaction(); - writeObject = realm.createObject(AllTypes.class); - } - - @AfterExperiment - public void after() { - realm.cancelTransaction(); - realm.close(); - } - - @Benchmark - public void writeShortString(long reps) { - for (long i = 0; i < reps; i++) { - writeObject.setColumnString("Foo"); - } - } - - @Benchmark - public void writeMediumString(long reps) { - for (long i = 0; i < reps; i++) { - writeObject.setColumnString("ABCDEFHIJKLMNOPQ"); - } - } - - @Benchmark - public void writeLongString(long reps) { - for (long i = 0; i < reps; i++) { - writeObject.setColumnString("ABCDEFHIJKLMNOPQABCDEFHIJKLMNOPQABCDEFHIJKLMNOPQABCDEFHIJKLMNOPQ"); - } - } - - @Benchmark - public void writeLong(long reps) { - for (long i = 0; i < reps; i++) { - writeObject.setColumnLong(42); - } - } - - @Benchmark - public void writeDouble(long reps) { - for (long i = 0; i < reps; i++) { - writeObject.setColumnDouble(1.234D); - } - } -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt new file mode 100644 index 0000000000..d09efc10bf --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.BenchmarkRule +import androidx.benchmark.measureRepeated +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.benchmarks.entities.AllTypes +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class RealmObjectWriteBenchmarks { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private lateinit var realm: Realm + private lateinit var writeObject: AllTypes + + @Before + fun before() { + val config = RealmConfiguration.Builder().build() + Realm.deleteRealm(config) + realm = Realm.getInstance(config) + realm.beginTransaction() + writeObject = realm.createObject(AllTypes::class.java) + } + + @After + fun after() { + realm.cancelTransaction() + realm.close() + } + + @Test + fun writeShortString() { + benchmarkRule.measureRepeated { + writeObject.columnString = "Foo" + } + } + + @Test + fun writeMediumString() { + benchmarkRule.measureRepeated { + writeObject.columnString = "ABCDEFHIJKLMNOPQ" + } + } + + @Test + fun writeLongString() { + benchmarkRule.measureRepeated { + writeObject.columnString = "ABCDEFHIJKLMNOPQABCDEFHIJKLMNOPQABCDEFHIJKLMNOPQABCDEFHIJKLMNOPQ" + } + } + + @Test + fun writeLong() { + benchmarkRule.measureRepeated { + writeObject.columnLong = 42 + } + } + + @Test + fun writeDouble() { + benchmarkRule.measureRepeated { + writeObject.columnDouble = 1.234 + } + } +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java deleted file mode 100644 index ef9b9160e0..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks; - -import org.junit.runner.RunWith; - -import dk.ilios.spanner.AfterExperiment; -import dk.ilios.spanner.BeforeExperiment; -import dk.ilios.spanner.Benchmark; -import dk.ilios.spanner.BenchmarkConfiguration; -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.junit.SpannerRunner; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.RealmResults; -import io.realm.Sort; -import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.benchmarks.entities.AllTypes; - - -@RunWith(SpannerRunner.class) -public class RealmQueryBenchmarks { - - private static final int DATA_SIZE = 1000; - - @BenchmarkConfiguration - public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); - - private Realm realm; - - @BeforeExperiment - public void before() { - RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(config); - realm = Realm.getInstance(config); - realm.beginTransaction(); - for (int i = 0; i < DATA_SIZE; i++) { - AllTypes obj = realm.createObject(AllTypes.class); - obj.setColumnLong(i); - obj.setColumnBoolean(i % 2 == 0); - obj.setColumnString("Foo " + i); - obj.setColumnDouble(i + 1.234D); - } - realm.commitTransaction(); - } - - @AfterExperiment - public void after() { - realm.close(); - } - - @Benchmark - public void containsQuery(long reps) { - for (long i = 0; i < reps; i++) { - RealmResults realmResults = realm.where(AllTypes.class).contains(AllTypes.FIELD_STRING, "Foo 1").findAll(); - } - } - - @Benchmark - public void count(long reps) { - for (long i = 0; i < reps; i++) { - long size = realm.where(AllTypes.class).count(); - } - } - - @Benchmark - public void findAll(long reps) { - for (long i = 0; i < reps; i++) { - RealmResults results = realm.where(AllTypes.class).findAll(); - } - } - - @Benchmark - public void findAllSortedOneField(long reps) { - for (long i = 0; i < reps; i++) { - RealmResults results = realm.where(AllTypes.class).sort(AllTypes.FIELD_STRING, Sort.ASCENDING).findAll(); - } - } -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt new file mode 100644 index 0000000000..cc5da680d4 --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.BenchmarkRule +import androidx.benchmark.measureRepeated +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.Sort +import io.realm.benchmarks.entities.AllTypes +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class RealmQueryBenchmarks { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private val DATA_SIZE = 1000 + private lateinit var realm: Realm + + @Before + fun before() { + val config = RealmConfiguration.Builder().build() + Realm.deleteRealm(config) + realm = Realm.getInstance(config) + realm.beginTransaction() + for (i in 0 until DATA_SIZE) { + val obj = realm.createObject(AllTypes::class.java) + obj.columnLong = i.toLong() + obj.isColumnBoolean = i % 2 == 0 + obj.columnString = "Foo $i" + obj.columnDouble = i + 1.234 + } + realm.commitTransaction() + } + + @After + fun after() { + realm.close() + } + + @Test + fun containsQuery() { + benchmarkRule.measureRepeated { + val realmResults = realm.where(AllTypes::class.java).contains(AllTypes.FIELD_STRING, "Foo 1").findAll() + } + } + + @Test + fun count() { + benchmarkRule.measureRepeated { + val size = realm.where(AllTypes::class.java).count() + } + } + + @Test + fun findAll() { + benchmarkRule.measureRepeated { + val results = realm.where(AllTypes::class.java).findAll() + } + } + + @Test + fun findAllSortedOneField() { + benchmarkRule.measureRepeated { + val results = realm.where(AllTypes::class.java).sort(AllTypes.FIELD_STRING, Sort.ASCENDING).findAll() + } + } + +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.java deleted file mode 100644 index e33aeed563..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks; - -import android.support.test.InstrumentationRegistry; - -import org.junit.runner.RunWith; - -import dk.ilios.spanner.AfterExperiment; -import dk.ilios.spanner.BeforeExperiment; -import dk.ilios.spanner.Benchmark; -import dk.ilios.spanner.BenchmarkConfiguration; -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.junit.SpannerRunner; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.RealmResults; -import io.realm.benchmarks.config.BenchmarkConfig; -import io.realm.benchmarks.entities.AllTypes; - - -@RunWith(SpannerRunner.class) -public class RealmResultsBenchmarks { - - private static final int DATA_SIZE = 1000; - - @BenchmarkConfiguration - public SpannerConfig configuration = BenchmarkConfig.getConfiguration(this.getClass().getCanonicalName()); - - private Realm realm; - private RealmResults results; - - @BeforeExperiment - public void before() { - Realm.init(InstrumentationRegistry.getTargetContext()); - RealmConfiguration config = new RealmConfiguration.Builder().build(); - Realm.deleteRealm(config); - realm = Realm.getInstance(config); - realm.beginTransaction(); - for (int i = 0; i < DATA_SIZE; i++) { - AllTypes obj = realm.createObject(AllTypes.class); - obj.setColumnLong(i); - obj.setColumnBoolean(i % 2 == 0); - obj.setColumnString("Foo " + i); - obj.setColumnDouble(i + 1.234D); - } - realm.commitTransaction(); - results = realm.where(AllTypes.class).findAll(); - } - - @AfterExperiment - public void after() { - realm.close(); - } - - @Benchmark - public void get(long reps) { - for (long i = 0; i < reps; i++) { - AllTypes item = results.get(0); - } - } - - @Benchmark - public void size(long reps) { - for (long i = 0; i < reps; i++) { - long size = results.size(); - } - } - - @Benchmark - public void min(long reps) { - for (long i = 0; i < reps; i++) { - Number min = results.min(AllTypes.FIELD_LONG); - } - } - - @Benchmark - public void max(long reps) { - for (long i = 0; i < reps; i++) { - Number max = results.max(AllTypes.FIELD_LONG); - } - } - - @Benchmark - public void average(long reps) { - for (long i = 0; i < reps; i++) { - Number average = results.average(AllTypes.FIELD_LONG); - } - } - - @Benchmark - public void sum(long reps) { - for (long i = 0; i < reps; i++) { - Number sum = results.sum(AllTypes.FIELD_LONG); - } - } - - @Benchmark - public void sort(long reps) { - for (long i = 0; i < reps; i++) { - RealmResults sorted = results.sort(AllTypes.FIELD_STRING); - } - } -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt new file mode 100644 index 0000000000..0b5f293bbb --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.BenchmarkRule +import androidx.benchmark.measureRepeated +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.RealmResults +import io.realm.benchmarks.entities.AllTypes +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class RealmResultsBenchmarks { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private val DATA_SIZE = 1000 + private lateinit var realm: Realm + private lateinit var results: RealmResults + + @Before + fun before() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + val config = RealmConfiguration.Builder().build() + Realm.deleteRealm(config) + realm = Realm.getInstance(config) + realm.beginTransaction() + for (i in 0 until DATA_SIZE) { + val obj = realm.createObject(AllTypes::class.java) + obj.columnLong = i.toLong() + obj.isColumnBoolean = i % 2 == 0 + obj.columnString = "Foo $i" + obj.columnDouble = i + 1.234 + } + realm.commitTransaction() + results = realm.where(AllTypes::class.java).findAll() + } + + @After + fun after() { + realm.close() + } + + @Test + fun get() { + benchmarkRule.measureRepeated { + val item = results[0] + } + } + + @Test + fun size() { + benchmarkRule.measureRepeated { + val size = results.size.toLong() + } + } + + @Test + fun min() { + benchmarkRule.measureRepeated { + val min = results.min(AllTypes.FIELD_LONG) + } + } + + @Test + fun max() { + benchmarkRule.measureRepeated { + val max = results.max(AllTypes.FIELD_LONG) + } + } + + @Test + fun average() { + benchmarkRule.measureRepeated { + val average = results.average(AllTypes.FIELD_LONG) + } + } + + @Test + fun sum() { + benchmarkRule.measureRepeated { + val sum = results.sum(AllTypes.FIELD_LONG) + } + } + + @Test + fun sort() { + benchmarkRule.measureRepeated { + val sorted = results.sort(AllTypes.FIELD_STRING) + } + } + +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/BenchmarkConfig.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/BenchmarkConfig.java deleted file mode 100644 index 5515532e22..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/BenchmarkConfig.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks.config; - -import android.os.Environment; - -import java.io.File; -import java.util.concurrent.TimeUnit; - -import dk.ilios.spanner.SpannerConfig; -import dk.ilios.spanner.config.RuntimeInstrumentConfig; -import dk.ilios.spanner.output.ResultProcessor; - -/** - * Static helper class for creating benchmark configurations - * */ -public class BenchmarkConfig { - - public static SpannerConfig getConfiguration(String className) { - // Document directory is located at: /sdcard/realm-benchmarks - // Benchmarks results should be saved in /results/.json - // Baseline data should be found in /baselines/.json - // Custom CSV files should be found in /csv/.csv - File externalDocuments = new File(Environment.getExternalStorageDirectory(), "realm-benchmarks"); - if (!externalDocuments.exists() && !externalDocuments.mkdir()) { - throw new RuntimeException("Could not create benchmark directory: " + externalDocuments); - } - File resultsDir = new File(externalDocuments, "results"); - File baselineDir = new File(externalDocuments, "baselines"); - File baselineFile = new File(baselineDir, className + ".json"); - File csvDir = new File(externalDocuments, "csv"); - csvDir.mkdir(); - File csvFile = new File(csvDir, className + ".csv"); - ResultProcessor csvResultProcessor = new CSVResultProcessor(csvFile); - - // General configuration for running benchmarks. - // Always saves result files. CI will determine if it wants to store them. - SpannerConfig.Builder builder = new SpannerConfig.Builder() - .saveResults(resultsDir, className + ".json") - .trialsPrExperiment(1) - .maxBenchmarkThreads(1) - .addInstrument(new RuntimeInstrumentConfig.Builder() - .gcBeforeEachMeasurement(true) - .warmupTime(0, TimeUnit.SECONDS) - .timingInterval(500, TimeUnit.MILLISECONDS) - .measurements(9) - .build() - ) - .addResultProcessor(csvResultProcessor); - - // Only uses baseline file if it exists. - if (baselineFile.exists()) { - builder.useBaseline(baselineFile); - // Tests that 25. , 50. and 75. percentile doesn't change by more than 15%. - builder.percentileFailureLimit(25f, 0.15f); - builder.percentileFailureLimit(50f, 0.15f); - builder.percentileFailureLimit(75f, 0.15f); - } - - return builder.build(); - } -} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/CSVResultProcessor.java b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/CSVResultProcessor.java deleted file mode 100644 index d241934afa..0000000000 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/config/CSVResultProcessor.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.benchmarks.config; - -import com.google.common.io.Files; -import com.opencsv.CSVWriter; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.text.DecimalFormat; - -import dk.ilios.spanner.model.Trial; -import dk.ilios.spanner.output.ResultProcessor; - -/** - * Converts the result of a benchmark to CSV for easier processing by other data/graph programs. - * - * Output is the following. - * methodname, trialNumber, params, measurements, min, max, average, 25pct, 50pct, 75pct. - */ -public class CSVResultProcessor implements ResultProcessor { - - private static final boolean APPLY_QUOTES = true; - private static final DecimalFormat decimalFormater = new DecimalFormat("#.00"); - - private final File resultFile; - private final File workFile; - private final CSVWriter writer; - - public CSVResultProcessor(File resultFile) { - this.resultFile = resultFile; - this.workFile = new File(resultFile.getPath() + ".tmp"); - try { - writer = new CSVWriter(Files.newWriter(resultFile, Charset.forName("UTF-8"))); - addLabels(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void addLabels() { - String[] labels = new String[] { - "Method name", - "Trial", - "Measurements", - "Min.", - "Max.", - "Mean", - "25pct.", - "50pct.", - "75pct.", - }; - - writer.writeNext(labels, APPLY_QUOTES); - } - - @Override - public void processTrial(Trial trial) { - String methodName = trial.experiment().instrumentation().benchmarkMethod().getName(); - int trialNo = trial.getTrialNumber(); - int measurements = trial.measurements().size(); - double min = trial.getMin(); - double max = trial.getMax(); - double mean = trial.getMean(); - double percentile25 = trial.getPercentile(25); - double percentile50 = trial.getMedian(); - double percentile75 = trial.getPercentile(75); - - String[] resultLine = new String[] { - methodName, - Integer.toString(trialNo), - Integer.toString(measurements), - decimalFormater.format(min), - decimalFormater.format(max), - decimalFormater.format(mean), - decimalFormater.format(percentile25), - decimalFormater.format(percentile50), - decimalFormater.format(percentile75) - }; - - writer.writeNext(resultLine); - } - - @Override - public void close() throws IOException { - writer.close(); - if (workFile.exists()) { - Files.move(workFile, resultFile); - } - } -} diff --git a/library-benchmarks/src/main/AndroidManifest.xml b/library-benchmarks/src/main/AndroidManifest.xml index f05a423d71..3c99ebf72b 100644 --- a/library-benchmarks/src/main/AndroidManifest.xml +++ b/library-benchmarks/src/main/AndroidManifest.xml @@ -1,13 +1,12 @@ - - - + xmlns:tools="http://schemas.android.com/tools" + package="io.realm.benchmarks"> - - + tools:ignore="HardcodedDebugMode" + tools:replace="android:debuggable" + android:debuggable="false" + android:label="@string/app_name" /> + + From 93de40d513b9978dc8a0e28eedfca698ea5a760f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 22 May 2019 10:33:48 +0200 Subject: [PATCH 1381/2110] Convert annotation processor to Kotlin (#6515) --- realm/build.gradle | 3 +- .../realm-annotations-processor/build.gradle | 18 +- .../java/io/realm/processor/Backlink.java | 246 -- .../main/java/io/realm/processor/Backlink.kt | 214 ++ .../io/realm/processor/ClassCollection.java | 60 - .../io/realm/processor/ClassCollection.kt | 50 + .../io/realm/processor/ClassMetaData.java | 797 ------ .../java/io/realm/processor/ClassMetaData.kt | 775 ++++++ .../java/io/realm/processor/Constants.java | 139 - .../main/java/io/realm/processor/Constants.kt | 103 + .../processor/DefaultModuleGenerator.java | 71 - .../realm/processor/DefaultModuleGenerator.kt | 66 + .../io/realm/processor/ModuleMetaData.java | 482 ---- .../java/io/realm/processor/ModuleMetaData.kt | 440 ++++ .../processor/OsObjectBuilderTypeHelper.java | 105 - .../processor/OsObjectBuilderTypeHelper.kt | 104 + .../io/realm/processor/RealmFieldElement.java | 124 - .../io/realm/processor/RealmFieldElement.kt | 95 + .../realm/processor/RealmJsonTypeHelper.java | 394 --- .../io/realm/processor/RealmJsonTypeHelper.kt | 372 +++ .../io/realm/processor/RealmProcessor.java | 304 --- .../java/io/realm/processor/RealmProcessor.kt | 339 +++ .../processor/RealmProxyClassGenerator.java | 2251 ----------------- .../processor/RealmProxyClassGenerator.kt | 2012 +++++++++++++++ .../RealmProxyInterfaceGenerator.java | 95 - .../processor/RealmProxyInterfaceGenerator.kt | 74 + .../RealmProxyMediatorGenerator.java | 488 ---- .../processor/RealmProxyMediatorGenerator.kt | 463 ++++ .../realm/processor/RealmVersionChecker.java | 101 - .../io/realm/processor/RealmVersionChecker.kt | 94 + .../java/io/realm/processor/TypeMirrors.java | 85 - .../java/io/realm/processor/TypeMirrors.kt | 85 + .../main/java/io/realm/processor/Utils.java | 423 ---- .../src/main/java/io/realm/processor/Utils.kt | 434 ++++ .../io/realm/processor/ext/JavaWriterExt.kt | 55 + ...seConverter.java => CamelCaseConverter.kt} | 31 +- ...ityConverter.java => IdentityConverter.kt} | 11 +- .../LowerCaseWithSeparatorConverter.java | 44 - .../LowerCaseWithSeparatorConverter.kt | 38 + .../{NameConverter.java => NameConverter.kt} | 8 +- ...eConverter.java => PascalCaseConverter.kt} | 25 +- .../nameconverter/WordTokenizer.java | 145 -- .../processor/nameconverter/WordTokenizer.kt | 135 + ...rustManagerCertificateValidationTests.java | 1 + .../java/io/realm/SSLConfigurationTests.java | 4 + 45 files changed, 6007 insertions(+), 6396 deletions(-) delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/ext/JavaWriterExt.kt rename realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/{CamelCaseConverter.java => CamelCaseConverter.kt} (57%) rename realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/{IdentityConverter.java => IdentityConverter.kt} (74%) delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.kt rename realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/{NameConverter.java => NameConverter.kt} (79%) rename realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/{PascalCaseConverter.java => PascalCaseConverter.kt} (60%) delete mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.java create mode 100644 realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.kt diff --git a/realm/build.gradle b/realm/build.gradle index 8f591b39bb..31076940cb 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,7 +1,7 @@ buildscript { def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) - ext.kotlin_version = '1.3.21' + ext.kotlin_version = '1.3.31' ext.dokka_version = '0.9.17' repositories { mavenLocal() @@ -45,3 +45,4 @@ allprojects { jcenter() } } + diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 50de49ce65..ae79c01a89 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -1,4 +1,4 @@ -apply plugin: 'java' +apply plugin: 'kotlin' apply plugin: 'maven' apply plugin: 'maven-publish' apply plugin: 'com.jfrog.artifactory' @@ -16,6 +16,7 @@ dependencies { testCompile group:'junit', name:'junit', version:'4.12' testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' testCompile files(file("${System.env.ANDROID_HOME}/platforms/android-27/android.jar")) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } // for Ant filter @@ -37,7 +38,7 @@ sourceSets { } } -compileJava.dependsOn generateVersionClass +compileKotlin.dependsOn generateVersionClass compileTestJava.dependsOn ':realm-library:assembleBaseRelease' task ojoUpload() { @@ -136,3 +137,16 @@ artifactory { } } } + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs += ["-XXLanguage:+InlineClasses"] + } +} + +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java deleted file mode 100644 index e158871457..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import java.util.Locale; - -import javax.lang.model.element.Modifier; -import javax.lang.model.element.VariableElement; - -import io.realm.annotations.LinkingObjects; -import io.realm.annotations.Required; - - -/** - * A Backlink is an implicit backwards reference. If field sourceField in instance I - * of type SourceClass holds a reference to instance J of type TargetClass, - * then a "backlink" is the automatically created reference from J to I. - * Backlinks are automatically created and destroyed when the forward references to which they correspond are - * created and destroyed. This can dramatically reduce the complexity of client code. - *

            - * To expose backlinks for use, create a declaration as follows: - * - * class TargetClass { - * // ... - * {@literal @}LinkingObjects("sourceField") - * final RealmResults<SourceClass> targetField = null; - * } - * . - *

            - * The targetField, the field annotated with the {@literal @}LinkingObjects annotation must be final. - * Its type must be RealmResults whose generic argument is the SourceClass, - * the class with the sourceField that will hold the forward reference to an instance of - * TargetClass - *

            - * The sourceField must be either of type TargetClass - * or RealmList<TargetClass> - *

            - * In the code link direction is from the perspective of the link, not the backlink: the source is the - * instance to which the backlink points, the target is the instance holding the pointer. - * This is consistent with the use of terms in the Realm Core. - *

            - * As should be obvious, from the declaration, backlinks are useful only on managed objects. - * An unmanaged Model object will have, as the value of its backlink field, the value with which - * the field is initialized (typically null). - */ -final class Backlink { - private final VariableElement backlinkField; - - /** - * The fully-qualified name of the class containing the targetField, - * which is the field annotated with the {@literal @}LinkingObjects annotation. - */ - private final String targetClass; - - /** - * The name of the backlink field, in targetClass. - * A RealmResults<> field annotated with a {@literal @}LinkingObjects annotation. - */ - private final String targetField; - - /** - * The fully-qualified name of the class to which the backlinks, from targetField, - * point. - */ - private final String sourceClass; - - /** - * The name of the field, in SourceClass that has a normal link to targetClass. - * Making this field, in an instance I of SourceClass, - * a reference to an instance J of TargetClass - * will cause the targetField of J to contain a backlink to I. - */ - private final String sourceField; - - - public Backlink(ClassMetaData clazz, VariableElement backlinkField) { - if ((null == clazz) || (null == backlinkField)) { - throw new NullPointerException(String.format(Locale.US, "null parameter: %s, %s", clazz, backlinkField)); - } - - this.backlinkField = backlinkField; - this.targetClass = clazz.getFullyQualifiedClassName(); - this.targetField = backlinkField.getSimpleName().toString(); - this.sourceClass = Utils.getRealmResultsType(backlinkField); - this.sourceField = backlinkField.getAnnotation(LinkingObjects.class).value(); - } - - public String getTargetClass() { - return targetClass; - } - - public String getTargetField() { - return targetField; - } - - public String getSourceClass() { - return sourceClass; - } - - public String getSourceField() { - return sourceField; - } - - public String getTargetFieldType() { - return backlinkField.asType().toString(); - } - - /** - * Validate the source side of the backlink. - * - * @return true if the backlink source looks good. - */ - public boolean validateSource() { - // A @LinkingObjects cannot be @Required - if (backlinkField.getAnnotation(Required.class) != null) { - Utils.error(String.format( - Locale.US, - "The @LinkingObjects field \"%s.%s\" cannot be @Required.", - targetClass, - targetField)); - return false; - } - - // The annotation must have an argument, identifying the linked field - if ((sourceField == null) || sourceField.equals("")) { - Utils.error(String.format( - Locale.US, - "The @LinkingObjects annotation for the field \"%s.%s\" must have a parameter identifying the link target.", - targetClass, - targetField)); - return false; - } - - // Using link syntax to try to reference a linked field is not possible. - if (sourceField.contains(".")) { - Utils.error(String.format( - Locale.US, - "The parameter to the @LinkingObjects annotation for the field \"%s.%s\" contains a '.'. The use of '.' to specify fields in referenced classes is not supported.", - targetClass, - targetField)); - return false; - } - - // The annotated element must be a RealmResult - if (!Utils.isRealmResults(backlinkField)) { - Utils.error(String.format( - Locale.US, - "The field \"%s.%s\" is a \"%s\". Fields annotated with @LinkingObjects must be RealmResults.", - targetClass, - targetField, - backlinkField.asType())); - return false; - } - - if (sourceClass == null) { - Utils.error(String.format( - Locale.US, - "\"The field \"%s.%s\", annotated with @LinkingObjects, must specify a generic type.", - targetClass, - targetField)); - return false; - } - - // A @LinkingObjects field must be final - if (!backlinkField.getModifiers().contains(Modifier.FINAL)) { - Utils.error(String.format( - Locale.US, - "A @LinkingObjects field \"%s.%s\" must be final.", - targetClass, - targetField)); - return false; - } - - return true; - } - - public boolean validateTarget(ClassMetaData clazz) { - VariableElement field = clazz.getDeclaredField(sourceField); - - if (field == null) { - Utils.error(String.format(Locale.US, - "Field \"%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", does not exist in class \"%s\".", - sourceField, - targetClass, - targetField, - sourceClass)); - return false; - } - - String fieldType = field.asType().toString(); - if (!(targetClass.equals(fieldType) || targetClass.equals(Utils.getRealmListType(field)))) { - Utils.error(String.format(Locale.US, - "Field \"%s.%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", has type \"%s\" instead of \"%3$s\".", - sourceClass, - sourceField, - targetClass, - targetField, - fieldType)); - return false; - } - - return true; - } - - @Override - public String toString() { - return "Backlink{" + sourceClass + "." + sourceField + " ==> " + targetClass + "." + targetField + "}"; - } - - @Override - public boolean equals(Object o) { - if (null == o) { return false; } - if (this == o) { return true; } - - if (!(o instanceof Backlink)) { return false; } - Backlink backlink = (Backlink) o; - - return targetClass.equals(backlink.targetClass) - && targetField.equals(backlink.targetField) - && sourceClass.equals(backlink.sourceClass) - && sourceField.equals(backlink.sourceField); - } - - @Override - public int hashCode() { - int result = targetClass.hashCode(); - result = 31 * result + targetField.hashCode(); - result = 31 * result + sourceClass.hashCode(); - result = 31 * result + sourceField.hashCode(); - return result; - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt new file mode 100644 index 0000000000..862db60de5 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt @@ -0,0 +1,214 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import java.util.Locale + +import javax.lang.model.element.Modifier +import javax.lang.model.element.VariableElement + +import io.realm.annotations.LinkingObjects +import io.realm.annotations.Required + +/** + * A **Backlink** is an implicit backwards reference. If field `sourceField` in instance `I` + * of type `SourceClass` holds a reference to instance `J` of type `TargetClass`, + * then a "backlink" is the automatically created reference from `J` to `I`. + * + * Backlinks are automatically created and destroyed when the forward references to which they + * correspond are created and destroyed. This can dramatically reduce the complexity of client + * code. + * + * To expose backlinks for use, create a declaration as follows: + * + * ``` + * class TargetClass { + * // ... + * @LinkingObjects("sourceField") + * final RealmResults targetField = null; + * } + *``` + * + * The `targetField`, the field annotated with the @LinkingObjects annotation must be final. + * Its type must be `RealmResults` whose generic argument is the `SourceClass`, the class with the + * `sourceField` that will hold the forward reference to an instance of `TargetClass` + * + * The `sourceField` must be either of type `TargetClass` or `RealmList` + * + * In the code link direction is from the perspective of the link, not the backlink: the source is + * the instance to which the backlink points, the target is the instance holding the pointer. + * This is consistent with the use of terms in the Realm Core. + * + * As should be obvious, from the declaration, backlinks are useful only on managed objects. + * An unmanaged Model object will have, as the value of its backlink field, the value with which + * the field is initialized (typically null). + */ +class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) { + + /** + * The fully-qualified name of the class containing the `targetField`, which is the field + * annotated with the @LinkingObjects annotation. + */ + val targetClass: QualifiedClassName = clazz.qualifiedClassName + + /** + * The name of the backlink field, in `targetClass`. + * A `RealmResults<>` field annotated with a @LinkingObjects annotation. + */ + val targetField: String = backlinkField.simpleName.toString() + + /** + * The fully-qualified name of the class to which the backlinks, from `targetField`, point. + */ + val sourceClass: QualifiedClassName? = Utils.getRealmResultsType(backlinkField) + + /** + * The name of the field, in `SourceClass` that has a normal link to `targetClass`. + * Making this field, in an instance I of `SourceClass`, a reference to an instance J of + * `TargetClass` will cause the `targetField` of J to contain a backlink to I. + */ + val sourceField: String? = backlinkField.getAnnotation(LinkingObjects::class.java)?.value + + val targetFieldType: String + get() = backlinkField.asType().toString() + + /** + * Validate the source side of the backlink. + * + * @return true if the backlink source looks good. + */ + fun validateSource(): Boolean { + // A @LinkingObjects cannot be @Required + if (backlinkField.getAnnotation(Required::class.java) != null) { + Utils.error(String.format( + Locale.US, + "The @LinkingObjects field \"%s.%s\" cannot be @Required.", + targetClass, + targetField)) + return false + } + + // The annotation must have an argument, identifying the linked field + if (sourceField == null || sourceField == "") { + Utils.error(String.format( + Locale.US, + "The @LinkingObjects annotation for the field \"%s.%s\" must have a parameter identifying the link target.", + targetClass, + targetField)) + return false + } + + // Using link syntax to try to reference a linked field is not possible. + if (sourceField.contains(".")) { + Utils.error(String.format( + Locale.US, + "The parameter to the @LinkingObjects annotation for the field \"%s.%s\" contains a '.'. The use of '.' to specify fields in referenced classes is not supported.", + targetClass, + targetField)) + return false + } + + // The annotated element must be a RealmResult + if (!Utils.isRealmResults(backlinkField)) { + Utils.error(String.format( + Locale.US, + "The field \"%s.%s\" is a \"%s\". Fields annotated with @LinkingObjects must be RealmResults.", + targetClass, + targetField, + backlinkField.asType())) + return false + } + + if (sourceClass == null) { + Utils.error(String.format( + Locale.US, + "\"The field \"%s.%s\", annotated with @LinkingObjects, must specify a generic type.", + targetClass, + targetField)) + return false + } + + // A @LinkingObjects field must be final + if (!backlinkField.modifiers.contains(Modifier.FINAL)) { + Utils.error(String.format( + Locale.US, + "A @LinkingObjects field \"%s.%s\" must be final.", + targetClass, + targetField)) + return false + } + + return true + } + + fun validateTarget(clazz: ClassMetaData): Boolean { + val field = clazz.getDeclaredField(sourceField) + + if (field == null) { + Utils.error(String.format(Locale.US, + "Field \"%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", does not exist in class \"%s\".", + sourceField, + targetClass, + targetField, + sourceClass)) + return false + } + + val fieldType = QualifiedClassName(field.asType().toString()) + if (!(targetClass == fieldType || targetClass == Utils.getRealmListType(field))) { + Utils.error(String.format(Locale.US, + "Field \"%s.%s\", the target of the @LinkedObjects annotation on field \"%s.%s\", has type \"%s\" instead of \"%3\$s\".", + sourceClass, + sourceField, + targetClass, + targetField, + fieldType)) + return false + } + + return true + } + + override fun toString(): String { + return "Backlink{$sourceClass.$sourceField ==> $targetClass.$targetField}" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Backlink + + if (backlinkField != other.backlinkField) return false + if (targetClass != other.targetClass) return false + if (targetField != other.targetField) return false + if (sourceClass != other.sourceClass) return false + if (sourceField != other.sourceField) return false + + return true + } + + override fun hashCode(): Int { + var result = backlinkField.hashCode() + result = 31 * result + targetClass.hashCode() + result = 31 * result + targetField.hashCode() + result = 31 * result + (sourceClass?.hashCode() ?: 0) + result = 31 * result + sourceField.hashCode() + return result + } + +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java deleted file mode 100644 index 095314f1b9..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.processor; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -/** - * Wrapper around all Realm model classes metadata found during processing. It also - * allows easy lookup for specific class data. - */ -public class ClassCollection { - - // These three collections should always stay in sync - private Map qualifiedNameClassMap = new LinkedHashMap<>(); - private Set classSet = new LinkedHashSet<>(); - - public void addClass(ClassMetaData metadata) { - classSet.add(metadata); - qualifiedNameClassMap.put(metadata.getFullyQualifiedClassName(), metadata); - } - - public Set getClasses() { - return Collections.unmodifiableSet(classSet); - } - - public ClassMetaData getClassFromQualifiedName(String qualifiedJavaClassName) { - ClassMetaData data = qualifiedNameClassMap.get(qualifiedJavaClassName); - if (data == null) { - throw new IllegalArgumentException("Class " + qualifiedJavaClassName + " was not found"); - } - return data; - } - - public int size() { - return classSet.size(); - } - - public boolean containsQualifiedClass(String qualifiedClassName) { - return qualifiedNameClassMap.containsKey(qualifiedClassName); - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.kt new file mode 100644 index 0000000000..14faeab559 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.processor + +import java.util.LinkedHashMap +import java.util.LinkedHashSet + +/** + * Wrapper around all Realm model classes metadata found during processing. It also allows easy + * lookup for specific class data. + */ +class ClassCollection { + + // These three collections should always stay in sync + private val qualifiedNameClassMap = LinkedHashMap() + private val classSet = LinkedHashSet() + val classes: Set + get() = classSet.toSet() + + fun addClass(metadata: ClassMetaData) { + classSet.add(metadata) + qualifiedNameClassMap[metadata.qualifiedClassName] = metadata + } + + fun getClassFromQualifiedName(className: QualifiedClassName): ClassMetaData { + return qualifiedNameClassMap[className] + ?: throw IllegalArgumentException("Class $className was not found") + } + + fun size(): Int { + return classSet.size + } + + fun containsQualifiedClass(qualifiedClassName: QualifiedClassName?): Boolean { + return qualifiedNameClassMap.containsKey(qualifiedClassName) + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java deleted file mode 100644 index 5f8d53ab60..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.java +++ /dev/null @@ -1,797 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - -import io.realm.annotations.Ignore; -import io.realm.annotations.Index; -import io.realm.annotations.LinkingObjects; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmClass; -import io.realm.annotations.RealmField; -import io.realm.annotations.RealmNamingPolicy; -import io.realm.annotations.Required; -import io.realm.processor.nameconverter.NameConverter; - - -/** - * Utility class for holding metadata for RealmProxy classes. - */ -public class ClassMetaData { - private static final String OPTION_IGNORE_KOTLIN_NULLABILITY = "realm.ignoreKotlinNullability"; - private static final int MAX_CLASSNAME_LENGTH = 57; - - private final TypeElement classType; // Reference to model class. - private final String javaClassName; // Model class simple name as defined in Java. - private final List fields = new ArrayList<>(); // List of all fields in the class except those @Ignored. - private final List indexedFields = new ArrayList<>(); // list of all fields marked @Index. - private final List objectReferenceFields = new ArrayList<>(); // List of all fields that reference a Realm Object either directly or in a List - private final List basicTypeFields = new ArrayList<>(); // List of all fields that reference basic types, i.e. no references to other Realm Objects - private final Set backlinks = new LinkedHashSet<>(); - private final Set nullableFields = new LinkedHashSet<>(); // Set of fields which can be nullable - private final Set nullableValueListFields = new LinkedHashSet<>(); // Set of fields whose elements can be nullable - - private String packageName; // package name for model class. - private boolean hasDefaultConstructor; // True if model has a public no-arg constructor. - private VariableElement primaryKey; // Reference to field used as primary key, if any. - private boolean containsToString; - private boolean containsEquals; - private boolean containsHashCode; - private String internalClassName; - - private final List validPrimaryKeyTypes; - private final List validListValueTypes; - private final Types typeUtils; - private final Elements elements; - private NameConverter defaultFieldNameFormatter; - - private final boolean ignoreKotlinNullability; - - public ClassMetaData(ProcessingEnvironment env, TypeMirrors typeMirrors, TypeElement clazz) { - this.classType = clazz; - this.javaClassName = clazz.getSimpleName().toString(); - typeUtils = env.getTypeUtils(); - elements = env.getElementUtils(); - - - validPrimaryKeyTypes = Arrays.asList( - typeMirrors.STRING_MIRROR, - typeMirrors.PRIMITIVE_LONG_MIRROR, - typeMirrors.PRIMITIVE_INT_MIRROR, - typeMirrors.PRIMITIVE_SHORT_MIRROR, - typeMirrors.PRIMITIVE_BYTE_MIRROR - ); - - validListValueTypes = Arrays.asList( - typeMirrors.STRING_MIRROR, - typeMirrors.BINARY_MIRROR, - typeMirrors.BOOLEAN_MIRROR, - typeMirrors.LONG_MIRROR, - typeMirrors.INTEGER_MIRROR, - typeMirrors.SHORT_MIRROR, - typeMirrors.BYTE_MIRROR, - typeMirrors.DOUBLE_MIRROR, - typeMirrors.FLOAT_MIRROR, - typeMirrors.DATE_MIRROR - ); - - for (Element element : classType.getEnclosedElements()) { - if (element instanceof ExecutableElement) { - Name name = element.getSimpleName(); - if (name.contentEquals("toString")) { - this.containsToString = true; - } else if (name.contentEquals("equals")) { - this.containsEquals = true; - } else if (name.contentEquals("hashCode")) { - this.containsHashCode = true; - } - } - } - - ignoreKotlinNullability = Boolean.valueOf( - env.getOptions().getOrDefault(OPTION_IGNORE_KOTLIN_NULLABILITY, "false")); - } - - @Override - public String toString() { - return "class " + getFullyQualifiedClassName(); - } - - public String getSimpleJavaClassName() { - return javaClassName; - } - - /** - * Returns the name that Realm Core uses when saving data from this Java class. - */ - public String getInternalClassName() { - return internalClassName; - } - - /** - * Returns the internal field name that matches the one in the Java model class. - */ - public String getInternalFieldName(String javaFieldName) { - for (RealmFieldElement field : fields) { - if (field.getJavaName().equals(javaFieldName)) { - return field.getInternalFieldName(); - } - } - throw new IllegalArgumentException("Could not find fieldname: " + javaFieldName); - } - - public String getPackageName() { - return packageName; - } - - public String getFullyQualifiedClassName() { - return packageName + "." + javaClassName; - } - - /** - * Returns all persistable fields in this model class - */ - public List getFields() { - return Collections.unmodifiableList(fields); - } - - /** - * Returns all persistable fields that reference other Realm objects. - */ - public List getObjectReferenceFields() { - return Collections.unmodifiableList(objectReferenceFields); - } - - /** - * Returns all persistable fields that contain a basic type, this include lists of primitives. - */ - public List getBasicTypeFields() { - return Collections.unmodifiableList(basicTypeFields); - } - - public Set getBacklinkFields() { - return Collections.unmodifiableSet(backlinks); - } - - public String getInternalGetter(String fieldName) { - return "realmGet$" + fieldName; - } - - public String getInternalSetter(String fieldName) { - return "realmSet$" + fieldName; - } - - public List getIndexedFields() { - return Collections.unmodifiableList(indexedFields); - } - - public boolean hasPrimaryKey() { - return primaryKey != null; - } - - public VariableElement getPrimaryKey() { - return primaryKey; - } - - public String getPrimaryKeyGetter() { - return getInternalGetter(primaryKey.getSimpleName().toString()); - } - - public boolean containsToString() { - return containsToString; - } - - public boolean containsEquals() { - return containsEquals; - } - - public boolean containsHashCode() { - return containsHashCode; - } - - /** - * Checks if a VariableElement is nullable. - * - * @return {@code true} if a VariableElement is nullable type, {@code false} otherwise. - */ - public boolean isNullable(VariableElement variableElement) { - return nullableFields.contains(variableElement); - } - - /** - * Checks if the element of {@code RealmList} designated by {@code realmListVariableElement} is nullable. - * - * @return {@code true} if the element is nullable type, {@code false} otherwise. - */ - public boolean isElementNullable(VariableElement realmListVariableElement) { - return nullableValueListFields.contains(realmListVariableElement); - } - - /** - * Checks if a VariableElement is indexed. - * - * @param variableElement the element/field - * @return {@code true} if a VariableElement is indexed, {@code false} otherwise. - */ - public boolean isIndexed(VariableElement variableElement) { - return indexedFields.contains(variableElement); - } - - /** - * Checks if a VariableElement is a primary key. - * - * @param variableElement the element/field - * @return {@code true} if a VariableElement is primary key, {@code false} otherwise. - */ - public boolean isPrimaryKey(VariableElement variableElement) { - return primaryKey != null && primaryKey.equals(variableElement); - } - - /** - * Returns {@code true} if the class is considered to be a valid RealmObject class. - * RealmObject and Proxy classes also have the @RealmClass annotation but are not considered valid - * RealmObject classes. - */ - public boolean isModelClass() { - String type = classType.toString(); - return !type.equals("io.realm.DynamicRealmObject") && !type.endsWith(".RealmObject") && !type.endsWith("RealmProxy"); - } - - /** - * Find the named field in this classes list of fields. - * This method is called only during backlink checking, - * so creating a map, even lazily, doesn't seem like a worthwhile optimization. - * If it gets used more widely, that decision should be revisited. - * - * @param fieldName The name of the sought field - * @return the named field's VariableElement, or null if not found - */ - public VariableElement getDeclaredField(String fieldName) { - if (fieldName == null) { return null; } - for (VariableElement field : fields) { - if (field.getSimpleName().toString().equals(fieldName)) { - return field; - } - } - return null; - } - - /** - * Builds the meta data structures for this class. Any errors or messages will be - * posted on the provided Messager. - * - * @param moduleMetaData pre-processed module meta data. - * @return True if meta data was correctly created and processing can continue, false otherwise. - */ - public boolean generate(ModuleMetaData moduleMetaData) { - // Get the package of the class - Element enclosingElement = classType.getEnclosingElement(); - if (!enclosingElement.getKind().equals(ElementKind.PACKAGE)) { - Utils.error("The RealmClass annotation does not support nested classes.", classType); - return false; - } - - // Check if the @RealmClass is considered valid with respect to the type hierarchy - TypeElement parentElement = (TypeElement) Utils.getSuperClass(classType); - if (!parentElement.toString().equals("java.lang.Object") && !parentElement.toString().equals("io.realm.RealmObject")) { - Utils.error("Valid model classes must either extend RealmObject or implement RealmModel.", classType); - return false; - } - - PackageElement packageElement = (PackageElement) enclosingElement; - packageName = packageElement.getQualifiedName().toString(); - - // Determine naming rules for this class - String qualifiedClassName = packageName + "." + javaClassName; - NameConverter moduleClassNameFormatter = moduleMetaData.getClassNameFormatter(qualifiedClassName); - defaultFieldNameFormatter = moduleMetaData.getFieldNameFormatter(qualifiedClassName); - - RealmClass realmClassAnnotation = classType.getAnnotation(RealmClass.class); - // If name has been specifically set, it should override any module policy. - if (!realmClassAnnotation.name().isEmpty()) { - internalClassName = realmClassAnnotation.name(); - } else if (!realmClassAnnotation.value().isEmpty()) { - internalClassName = realmClassAnnotation.value(); - } else { - internalClassName = moduleClassNameFormatter.convert(javaClassName); - } - if (internalClassName.length() > MAX_CLASSNAME_LENGTH) { - Utils.error(String.format(Locale.US, "Internal class name is too long. Class '%s' " + - "is converted to '%s', which is longer than the maximum allowed of %d characters", - javaClassName, internalClassName, 57)); - return false; - } - - // If field name policy has been explicitly set, override the module field name policy - if (realmClassAnnotation.fieldNamingPolicy() != RealmNamingPolicy.NO_POLICY) { - defaultFieldNameFormatter = Utils.getNameFormatter(realmClassAnnotation.fieldNamingPolicy()); - } - - // Categorize and check the rest of the file - if (!categorizeClassElements()) { return false; } - if (!checkCollectionTypes()) { return false; } - if (!checkReferenceTypes()) { return false; } - if (!checkDefaultConstructor()) { return false; } - if (!checkForFinalFields()) { return false; } - if (!checkForVolatileFields()) { return false; } - - return true; // Meta data was successfully generated - } - - // Iterate through all class elements and add them to the appropriate internal data structures. - // Returns true if all elements could be categorized and false otherwise. - private boolean categorizeClassElements() { - for (Element element : classType.getEnclosedElements()) { - ElementKind elementKind = element.getKind(); - switch (elementKind) { - case CONSTRUCTOR: - if (Utils.isDefaultConstructor(element)) { hasDefaultConstructor = true; } - break; - - case FIELD: - if (!categorizeField(element)) { return false; } - break; - - default: - } - } - - if (fields.size() == 0) { - Utils.error(String.format(Locale.US, "Class \"%s\" must contain at least 1 persistable field.", javaClassName)); - } - - return true; - } - - private boolean checkCollectionTypes() { - for (VariableElement field : fields) { - if (Utils.isRealmList(field)) { - if (!checkRealmListType(field)) { - return false; - } - } else if (Utils.isRealmResults(field)) { - if (!checkRealmResultsType(field)) { - return false; - } - } - } - - return true; - } - - private boolean checkRealmListType(VariableElement field) { - // Check for missing generic (default back to Object) - if (Utils.getGenericTypeQualifiedName(field) == null) { - Utils.error(getFieldErrorSuffix(field) + "No generic type supplied for field", field); - return false; - } - - // Check that the referenced type is a concrete class and not an interface - TypeMirror fieldType = field.asType(); - final TypeMirror elementTypeMirror = ((DeclaredType) fieldType).getTypeArguments().get(0); - if (elementTypeMirror.getKind() == TypeKind.DECLARED /* class of interface*/) { - TypeElement elementTypeElement = (TypeElement) ((DeclaredType) elementTypeMirror).asElement(); - if (elementTypeElement.getSuperclass().getKind() == TypeKind.NONE) { - Utils.error( - getFieldErrorSuffix(field) + "Only concrete Realm classes are allowed in RealmLists. " - + "Neither interfaces nor abstract classes are allowed.", - field); - return false; - } - } - - // Check if the actual value class is acceptable - if (!containsType(validListValueTypes, elementTypeMirror) && !Utils.isRealmModel(elementTypeMirror)) { - final StringBuilder messageBuilder = new StringBuilder( - getFieldErrorSuffix(field) + "Element type of RealmList must be a class implementing 'RealmModel' or one of "); - final String separator = ", "; - for (TypeMirror type : validListValueTypes) { - messageBuilder.append('\'').append(type.toString()).append('\'').append(separator); - } - messageBuilder.setLength(messageBuilder.length() - separator.length()); - messageBuilder.append('.'); - Utils.error(messageBuilder.toString(), field); - return false; - } - - return true; - } - - private boolean checkRealmResultsType(VariableElement field) { - // Only classes implementing RealmModel are allowed since RealmResults field is used only for backlinks. - - // Check for missing generic (default back to Object) - if (Utils.getGenericTypeQualifiedName(field) == null) { - Utils.error(getFieldErrorSuffix(field) + "No generic type supplied for field", field); - return false; - } - - TypeMirror fieldType = field.asType(); - final TypeMirror elementTypeMirror = ((DeclaredType) fieldType).getTypeArguments().get(0); - if (elementTypeMirror.getKind() == TypeKind.DECLARED /* class or interface*/) { - TypeElement elementTypeElement = (TypeElement) ((DeclaredType) elementTypeMirror).asElement(); - if (elementTypeElement.getSuperclass().getKind() == TypeKind.NONE) { - Utils.error( - "Only concrete Realm classes are allowed in RealmResults. " - + "Neither interfaces nor abstract classes are allowed.", - field); - return false; - } - } - - // Check if the actual value class is acceptable - if (!Utils.isRealmModel(elementTypeMirror)) { - Utils.error(getFieldErrorSuffix(field) + "Element type of RealmResults must be a class implementing 'RealmModel'.", field); - return false; - } - - return true; - } - - private String getFieldErrorSuffix(VariableElement field) { - return javaClassName + "." + field.getSimpleName() + ": "; - } - - private boolean checkReferenceTypes() { - for (VariableElement field : fields) { - if (Utils.isRealmModel(field)) { - // Check that the referenced type is a concrete class and not an interface - TypeElement typeElement = elements.getTypeElement(field.asType().toString()); - if (typeElement.getSuperclass().getKind() == TypeKind.NONE) { - Utils.error( - "Only concrete Realm classes can be referenced from model classes. " - + "Neither interfaces nor abstract classes are allowed.", - field); - return false; - } - } - } - - return true; - } - - // Report if the default constructor is missing - private boolean checkDefaultConstructor() { - if (!hasDefaultConstructor) { - Utils.error(String.format(Locale.US, - "Class \"%s\" must declare a public constructor with no arguments if it contains custom constructors.", - javaClassName)); - return false; - } else { - return true; - } - } - - private boolean checkForFinalFields() { - for (VariableElement field : fields) { - if (!field.getModifiers().contains(Modifier.FINAL)) { - continue; - } - if (Utils.isMutableRealmInteger(field)) { - continue; - } - - Utils.error(String.format(Locale.US, "Class \"%s\" contains illegal final field \"%s\".", javaClassName, - field.getSimpleName().toString())); - - return false; - } - return true; - } - - private boolean checkForVolatileFields() { - for (VariableElement field : fields) { - if (field.getModifiers().contains(Modifier.VOLATILE)) { - Utils.error(String.format(Locale.US, - "Class \"%s\" contains illegal volatile field \"%s\".", - javaClassName, - field.getSimpleName().toString())); - return false; - } - } - return true; - } - - private boolean categorizeField(Element element) { - VariableElement fieldRef = (VariableElement) element; - - // completely ignore any static fields - if (fieldRef.getModifiers().contains(Modifier.STATIC)) { return true; } - - // Ignore fields marked with @Ignore or if they are transient - if (fieldRef.getAnnotation(Ignore.class) != null || fieldRef.getModifiers().contains(Modifier.TRANSIENT)) { - return true; - } - - // Determine name for field - String internalFieldName = getInternalFieldName(fieldRef, defaultFieldNameFormatter); - RealmFieldElement field = new RealmFieldElement(fieldRef, internalFieldName); - - if (field.getAnnotation(Index.class) != null) { - if (!categorizeIndexField(element, field)) { return false; } - } - - // @Required annotation of RealmList field only affects its value type, not field itself. - if (Utils.isRealmList(field)) { - boolean hasRequiredAnnotation = hasRequiredAnnotation(field); - final List listGenericType = ((DeclaredType) field.asType()).getTypeArguments(); - boolean containsRealmModelClasses = (!listGenericType.isEmpty() && Utils.isRealmModel(listGenericType.get(0))); - - // @Required not allowed if the list contains Realm model classes - if (hasRequiredAnnotation && containsRealmModelClasses) { - Utils.error("@Required not allowed on RealmList's that contain other Realm model classes."); - return false; - } - - // @Required thus only makes sense for RealmLists with primitive types - // We only check @Required annotation. @org.jetbrains.annotations.NotNull annotation should not affect nullability of the list values. - if (!hasRequiredAnnotation) { - if (!containsRealmModelClasses) { - nullableValueListFields.add(field); - } - } - } else if (isRequiredField(field)) { - if (!checkBasicRequiredAnnotationUsage(element, field)) { - return false; - } - } else { - // The field doesn't have the @Required and @org.jetbrains.annotations.NotNull annotation. - // Without @Required annotation, boxed types/RealmObject/Date/String/bytes should be added to - // nullableFields. - // RealmList of models, RealmResults(backlinks) and primitive types are NOT nullable. @Required annotation is not supported. - if (!Utils.isPrimitiveType(field) && !Utils.isRealmResults(field)) { - nullableFields.add(field); - } - } - - if (field.getAnnotation(PrimaryKey.class) != null) { - if (!categorizePrimaryKeyField(field)) { return false; } - } - - // @LinkingObjects cannot be @PrimaryKey or @Index. - if (field.getAnnotation(LinkingObjects.class) != null) { - // Do not add backlinks to fields list. - return categorizeBacklinkField(field); - } - - // Similarly, a MutableRealmInteger cannot be a @PrimaryKey or @LinkingObject. - if (Utils.isMutableRealmInteger(field)) { - if (!categorizeMutableRealmIntegerField(field)) { return false; } - } - - // Standard field that appears to be valid (more fine grained checks might fail later). - fields.add(field); - if (Utils.isRealmModel(field) || Utils.isRealmModelList(field)) { - objectReferenceFields.add(field); - } else { - basicTypeFields.add(field); - } - - return true; - } - - private String getInternalFieldName(VariableElement field, NameConverter defaultConverter) { - RealmField nameAnnotation = field.getAnnotation(RealmField.class); - if (nameAnnotation != null) { - if (!nameAnnotation.name().isEmpty()) { - return nameAnnotation.name(); - } - if (!nameAnnotation.value().isEmpty()) { - return nameAnnotation.value(); - } - Utils.note(String.format("Empty internal name defined on @RealmField. " + - "Falling back to named used by Java model class: %s", field.getSimpleName()), field); - return field.getSimpleName().toString(); - } else { - return defaultConverter.convert(field.getSimpleName().toString()); - } - } - - /** - * This method only checks if the field has {@code @Required} annotation. - * In most cases, you should use {@link #isRequiredField(VariableElement)} to take into account - * Kotlin's annotation as well. - * - * @param field target field. - * @return {@code true} if the field has {@code @Required} annotation, {@code false} otherwise. - * @see #isRequiredField(VariableElement) - */ - private boolean hasRequiredAnnotation(VariableElement field) { - return field.getAnnotation(Required.class) != null; - } - - /** - * Checks if the field is annotated as required. - * @param field target field. - * @return {@code true} if the field is annotated as required, {@code false} otherwise. - */ - private boolean isRequiredField(VariableElement field) { - if (hasRequiredAnnotation(field)) { - return true; - } - - if (ignoreKotlinNullability) { - return false; - } - - // Kotlin uses the `org.jetbrains.annotations.NotNull` annotation to mark non-null fields. - // In order to fully support the Kotlin type system we interpret `@NotNull` as an alias - // for `@Required` - for (AnnotationMirror annotation : field.getAnnotationMirrors()) { - if (annotation.getAnnotationType().toString().equals("org.jetbrains.annotations.NotNull")) { - return true; - } - } - - return false; - } - - // The field has the @Index annotation. It's only valid for column types: - // STRING, DATE, INTEGER, BOOLEAN, and RealmMutableInteger - private boolean categorizeIndexField(Element element, RealmFieldElement fieldElement) { - boolean indexable = false; - - if (Utils.isMutableRealmInteger(fieldElement)) { - indexable = true; - } else { - Constants.RealmFieldType realmType = Constants.JAVA_TO_REALM_TYPES.get(fieldElement.asType().toString()); - if (realmType != null) { - switch (realmType) { - case STRING: - case DATE: - case INTEGER: - case BOOLEAN: - indexable = true; - } - } - } - - if (indexable) { - indexedFields.add(fieldElement); - return true; - } - - Utils.error(String.format(Locale.US, "Field \"%s\" of type \"%s\" cannot be an @Index.", element, element.asType())); - return false; - } - - // The field has the @Required annotation - // Returns `true` if the field could be correctly validated, `false` if an error was reported. - private boolean checkBasicRequiredAnnotationUsage(Element element, VariableElement variableElement) { - if (Utils.isPrimitiveType(variableElement)) { - Utils.error(String.format(Locale.US, - "@Required or @NotNull annotation is unnecessary for primitive field \"%s\".", element)); - return false; - } - - if (Utils.isRealmModel(variableElement)) { - Utils.error(String.format(Locale.US, - "Field \"%s\" with type \"%s\" cannot be @Required or @NotNull.", element, element.asType())); - return false; - } - - // Should never get here - user should remove @Required - if (nullableFields.contains(variableElement)) { - Utils.error(String.format(Locale.US, - "Field \"%s\" with type \"%s\" appears to be nullable. Consider removing @Required.", - element, - element.asType())); - - return false; - } - - return true; - } - - // The field has the @PrimaryKey annotation. It is only valid for - // String, short, int, long and must only be present one time - private boolean categorizePrimaryKeyField(RealmFieldElement fieldElement) { - if (primaryKey != null) { - Utils.error(String.format(Locale.US, - "A class cannot have more than one @PrimaryKey. Both \"%s\" and \"%s\" are annotated as @PrimaryKey.", - primaryKey.getSimpleName().toString(), - fieldElement.getSimpleName().toString())); - return false; - } - - TypeMirror fieldType = fieldElement.asType(); - if (!isValidPrimaryKeyType(fieldType)) { - Utils.error(String.format(Locale.US, - "Field \"%s\" with type \"%s\" cannot be used as primary key. See @PrimaryKey for legal types.", - fieldElement.getSimpleName().toString(), - fieldType)); - return false; - } - - primaryKey = fieldElement; - - // Also add as index. All types of primary key can be indexed. - if (!indexedFields.contains(fieldElement)) { - indexedFields.add(fieldElement); - } - - return true; - } - - private boolean categorizeBacklinkField(VariableElement variableElement) { - Backlink backlink = new Backlink(this, variableElement); - if (!backlink.validateSource()) { return false; } - - backlinks.add(backlink); - - return true; - } - - private boolean categorizeMutableRealmIntegerField(VariableElement field) { - if (field.getModifiers().contains(Modifier.FINAL)) { - return true; - } - - Utils.error(String.format(Locale.US, - "Field \"%s\", a MutableRealmInteger, must be final.", - field.getSimpleName().toString())); - return false; - } - - private boolean isValidPrimaryKeyType(TypeMirror type) { - for (TypeMirror validType : validPrimaryKeyTypes) { - if (typeUtils.isAssignable(type, validType)) { - return true; - } - } - return false; - } - - private boolean containsType(List listOfTypes, TypeMirror type) { - for (int i = 0; i < listOfTypes.size(); i++) { - // Comparing TypeMirror's using `equals()` breaks when using incremental annotation processing. - if (typeUtils.isSameType(listOfTypes.get(i), type)) { - return true; - } - } - return false; - } - - public Element getClassElement() { - return classType; - } - -} - diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt new file mode 100644 index 0000000000..ff8c33d6a0 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt @@ -0,0 +1,775 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import java.util.ArrayList +import java.util.Arrays +import java.util.Collections +import java.util.LinkedHashSet +import java.util.Locale + +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.Element +import javax.lang.model.element.ElementKind +import javax.lang.model.element.ExecutableElement +import javax.lang.model.element.Modifier +import javax.lang.model.element.PackageElement +import javax.lang.model.element.TypeElement +import javax.lang.model.element.VariableElement +import javax.lang.model.type.DeclaredType +import javax.lang.model.type.TypeKind +import javax.lang.model.type.TypeMirror +import javax.lang.model.util.Elements +import javax.lang.model.util.Types + +import io.realm.annotations.Ignore +import io.realm.annotations.Index +import io.realm.annotations.LinkingObjects +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmClass +import io.realm.annotations.RealmField +import io.realm.annotations.RealmNamingPolicy +import io.realm.annotations.Required +import io.realm.processor.nameconverter.NameConverter + +/** + * Utility class for holding metadata for RealmProxy classes. + */ +class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, private val classType: TypeElement /* Reference to model class. */) { + + val simpleJavaClassName = SimpleClassName(classType.simpleName) // Model class simple name as defined in Java. + val fields = ArrayList() // List of all fields in the class except those @Ignored. + private val indexedFields = ArrayList() // list of all fields marked @Index. + private val _objectReferenceFields = ArrayList() // List of all fields that reference a Realm Object either directly or in a List + private val basicTypeFields = ArrayList() // List of all fields that reference basic types, i.e. no references to other Realm Objects + private val backlinks = LinkedHashSet() + private val nullableFields = LinkedHashSet() // Set of fields which can be nullable + private val nullableValueListFields = LinkedHashSet() // Set of fields whose elements can be nullable + + // package name for model class. + private lateinit var packageName: String + + // True if model has a public no-arg constructor. + private var hasDefaultConstructor: Boolean = false + + // Reference to field used as primary key + var primaryKey: VariableElement? = null + private set + + private var containsToString: Boolean = false + private var containsEquals: Boolean = false + private var containsHashCode: Boolean = false + + // Returns the name that Realm Core uses when saving data from this Java class. + lateinit var internalClassName: String + private set + + private val validPrimaryKeyTypes: List = Arrays.asList( + typeMirrors.STRING_MIRROR, + typeMirrors.PRIMITIVE_LONG_MIRROR, + typeMirrors.PRIMITIVE_INT_MIRROR, + typeMirrors.PRIMITIVE_SHORT_MIRROR, + typeMirrors.PRIMITIVE_BYTE_MIRROR + ) + private val validListValueTypes: List = Arrays.asList( + typeMirrors.STRING_MIRROR, + typeMirrors.BINARY_MIRROR, + typeMirrors.BOOLEAN_MIRROR, + typeMirrors.LONG_MIRROR, + typeMirrors.INTEGER_MIRROR, + typeMirrors.SHORT_MIRROR, + typeMirrors.BYTE_MIRROR, + typeMirrors.DOUBLE_MIRROR, + typeMirrors.FLOAT_MIRROR, + typeMirrors.DATE_MIRROR + ) + private val typeUtils: Types = env.typeUtils + private val elements: Elements = env.elementUtils + private lateinit var defaultFieldNameFormatter: NameConverter + + private val ignoreKotlinNullability: Boolean + + val qualifiedClassName: QualifiedClassName + get() = QualifiedClassName("$packageName.$simpleJavaClassName") + + val backlinkFields: Set + get() = backlinks.toSet() + + val primaryKeyGetter: String + get() = getInternalGetter(primaryKey!!.simpleName.toString()) + + /** + * Returns `true if the class is considered to be a valid RealmObject class. + * RealmObject and Proxy classes also have the @RealmClass annotation but are not considered valid + * RealmObject classes. + */ + val isModelClass: Boolean + get() { + val type = classType.toString() + return type != "io.realm.DynamicRealmObject" && !type.endsWith(".RealmObject") && !type.endsWith("RealmProxy") + } + + val classElement: Element + get() = classType + + init { + + + for (element in classType.enclosedElements) { + if (element is ExecutableElement) { + val name = element.getSimpleName() + when { + name.contentEquals("toString") -> this.containsToString = true + name.contentEquals("equals") -> this.containsEquals = true + name.contentEquals("hashCode") -> this.containsHashCode = true + } + } + } + + ignoreKotlinNullability = java.lang.Boolean.valueOf( + (env.options as MutableMap).getOrDefault(OPTION_IGNORE_KOTLIN_NULLABILITY, "false")) + } + + override fun toString(): String { + return "class $qualifiedClassName" + } + + /** + * Returns the internal field name that matches the one in the Java model class. + */ + fun getInternalFieldName(javaFieldName: String): String { + for (field in fields) { + if (field.javaName == javaFieldName) { + return field.internalFieldName + } + } + throw IllegalArgumentException("Could not find fieldname: $javaFieldName") + } + + /** + * Returns all persistable fields that reference other Realm objects. + */ + val objectReferenceFields: List + get() = _objectReferenceFields.toList() + + /** + * Returns all persistable fields that contain a basic type, this include lists of primitives. + */ + fun getBasicTypeFields(): List { + return Collections.unmodifiableList(basicTypeFields) + } + + fun getInternalGetter(fieldName: String): String { + return "realmGet$$fieldName" + } + + fun getInternalSetter(fieldName: String): String { + return "realmSet$$fieldName" + } + + fun hasPrimaryKey(): Boolean { + return primaryKey != null + } + + fun containsToString(): Boolean { + return containsToString + } + + fun containsEquals(): Boolean { + return containsEquals + } + + fun containsHashCode(): Boolean { + return containsHashCode + } + + /** + * Checks if a VariableElement is nullable. + * + * @return `true` if a VariableElement is nullable type, `false` otherwise. + */ + fun isNullable(variableElement: VariableElement): Boolean { + return nullableFields.contains(variableElement) + } + + /** + * Checks if the element of `RealmList` designated by `realmListVariableElement` is nullable. + * + * @return `true` if the element is nullable type, `false` otherwise. + */ + fun isElementNullable(realmListVariableElement: VariableElement): Boolean { + return nullableValueListFields.contains(realmListVariableElement) + } + + /** + * Checks if a VariableElement is indexed. + * + * @param variableElement the element/field + * @return `true` if a VariableElement is indexed, `false` otherwise. + */ + fun isIndexed(variableElement: VariableElement): Boolean { + return indexedFields.contains(variableElement) + } + + /** + * Checks if a VariableElement is a primary key. + * + * @param variableElement the element/field + * @return `true` if a VariableElement is primary key, `false` otherwise. + */ + fun isPrimaryKey(variableElement: VariableElement): Boolean { + return primaryKey != null && primaryKey == variableElement + } + + /** + * Find the named field in this classes list of fields. + * This method is called only during backlink checking, + * so creating a map, even lazily, doesn't seem like a worthwhile optimization. + * If it gets used more widely, that decision should be revisited. + * + * @param fieldName The name of the sought field + * @return the named field's VariableElement, or null if not found + */ + fun getDeclaredField(fieldName: String?): VariableElement? { + if (fieldName == null) { + return null + } + for (field in fields) { + if (field.simpleName.toString() == fieldName) { + return field + } + } + return null + } + + /** + * Builds the meta data structures for this class. Any errors or messages will be + * posted on the provided Messager. + * + * @param moduleMetaData pre-processed module meta data. + * @return True if meta data was correctly created and processing can continue, false otherwise. + */ + fun generate(moduleMetaData: ModuleMetaData): Boolean { + // Get the package of the class + val enclosingElement = classType.enclosingElement + if (enclosingElement.kind != ElementKind.PACKAGE) { + Utils.error("The RealmClass annotation does not support nested classes.", classType) + return false + } + + // Check if the @RealmClass is considered valid with respect to the type hierarchy + val parentElement = Utils.getSuperClass(classType) as TypeElement + if (parentElement.toString() != "java.lang.Object" && parentElement.toString() != "io.realm.RealmObject") { + Utils.error("Valid model classes must either extend RealmObject or implement RealmModel.", classType) + return false + } + + val packageElement = enclosingElement as PackageElement + packageName = packageElement.qualifiedName.toString() + + // Determine naming rules for this class + val qualifiedClassName = QualifiedClassName("$packageName.$simpleJavaClassName") + val moduleClassNameFormatter = moduleMetaData.getClassNameFormatter(qualifiedClassName) + defaultFieldNameFormatter = moduleMetaData.getFieldNameFormatter(qualifiedClassName) + + val realmClassAnnotation = classType.getAnnotation(RealmClass::class.java) + // If name has been specifically set, it should override any module policy. + internalClassName = when { + realmClassAnnotation.name.isNotEmpty() -> realmClassAnnotation.name + realmClassAnnotation.value.isNotEmpty() -> realmClassAnnotation.value + else -> moduleClassNameFormatter.convert(simpleJavaClassName.toString()) + } + if (internalClassName.length > MAX_CLASSNAME_LENGTH) { + Utils.error(String.format(Locale.US, "Internal class name is too long. Class '%s' " + "is converted to '%s', which is longer than the maximum allowed of %d characters", + simpleJavaClassName, internalClassName, MAX_CLASSNAME_LENGTH)) + return false + } + + // If field name policy has been explicitly set, override the module field name policy + if (realmClassAnnotation.fieldNamingPolicy != RealmNamingPolicy.NO_POLICY) { + defaultFieldNameFormatter = Utils.getNameFormatter(realmClassAnnotation.fieldNamingPolicy) + } + + // Categorize and check the rest of the file + if (!categorizeClassElements()) { + return false + } + if (!checkCollectionTypes()) { + return false + } + if (!checkReferenceTypes()) { + return false + } + if (!checkDefaultConstructor()) { + return false + } + if (!checkForFinalFields()) { + return false + } + if (!checkForVolatileFields()) { + return false + } + + // Meta data was successfully generated + return true + } + + // Iterate through all class elements and add them to the appropriate internal data structures. + // Returns true if all elements could be categorized and false otherwise. + private fun categorizeClassElements(): Boolean { + for (element in classType.enclosedElements) { + when (element.kind) { + ElementKind.CONSTRUCTOR -> if (Utils.isDefaultConstructor(element)) { + hasDefaultConstructor = true + } + + ElementKind.FIELD -> if (!categorizeField(element)) { + return false + } + else -> { + /* Ignore */ + } + } + } + + if (fields.isEmpty()) { + Utils.error(String.format(Locale.US, "Class \"%s\" must contain at least 1 persistable field.", simpleJavaClassName)) + } + + return true + } + + private fun checkCollectionTypes(): Boolean { + for (field in fields) { + if (Utils.isRealmList(field)) { + if (!checkRealmListType(field)) { + return false + } + } else if (Utils.isRealmResults(field)) { + if (!checkRealmResultsType(field)) { + return false + } + } + } + + return true + } + + private fun checkRealmListType(field: VariableElement): Boolean { + // Check for missing generic (default back to Object) + if (Utils.getGenericTypeQualifiedName(field) == null) { + Utils.error(getFieldErrorSuffix(field) + "No generic type supplied for field", field) + return false + } + + // Check that the referenced type is a concrete class and not an interface + val fieldType = field.asType() + val elementTypeMirror = (fieldType as DeclaredType).typeArguments[0] + if (elementTypeMirror.kind == TypeKind.DECLARED /* class of interface*/) { + val elementTypeElement = (elementTypeMirror as DeclaredType).asElement() as TypeElement + if (elementTypeElement.superclass.kind == TypeKind.NONE) { + Utils.error( + getFieldErrorSuffix(field) + "Only concrete Realm classes are allowed in RealmLists. " + + "Neither interfaces nor abstract classes are allowed.", + field) + return false + } + } + + // Check if the actual value class is acceptable + if (!containsType(validListValueTypes, elementTypeMirror) && !Utils.isRealmModel(elementTypeMirror)) { + val messageBuilder = StringBuilder( + getFieldErrorSuffix(field) + "Element type of RealmList must be a class implementing 'RealmModel' or one of ") + val separator = ", " + for (type in validListValueTypes) { + messageBuilder.append('\'').append(type.toString()).append('\'').append(separator) + } + messageBuilder.setLength(messageBuilder.length - separator.length) + messageBuilder.append('.') + Utils.error(messageBuilder.toString(), field) + return false + } + + return true + } + + private fun checkRealmResultsType(field: VariableElement): Boolean { + // Only classes implementing RealmModel are allowed since RealmResults field is used only for backlinks. + + // Check for missing generic (default back to Object) + if (Utils.getGenericTypeQualifiedName(field) == null) { + Utils.error(getFieldErrorSuffix(field) + "No generic type supplied for field", field) + return false + } + + val fieldType = field.asType() + val elementTypeMirror = (fieldType as DeclaredType).typeArguments[0] + if (elementTypeMirror.kind == TypeKind.DECLARED /* class or interface*/) { + val elementTypeElement = (elementTypeMirror as DeclaredType).asElement() as TypeElement + if (elementTypeElement.superclass.kind == TypeKind.NONE) { + Utils.error( + ("Only concrete Realm classes are allowed in RealmResults. " + "Neither interfaces nor abstract classes are allowed."), + field) + return false + } + } + + // Check if the actual value class is acceptable + if (!Utils.isRealmModel(elementTypeMirror)) { + Utils.error(getFieldErrorSuffix(field) + "Element type of RealmResults must be a class implementing 'RealmModel'.", field) + return false + } + + return true + } + + private fun getFieldErrorSuffix(field: VariableElement): String { + return "$simpleJavaClassName.${field.simpleName}: " + } + + private fun checkReferenceTypes(): Boolean { + for (field in fields) { + if (Utils.isRealmModel(field)) { + // Check that the referenced type is a concrete class and not an interface + val typeElement = elements.getTypeElement(field.asType().toString()) + if (typeElement.superclass.kind == TypeKind.NONE) { + Utils.error( + ("Only concrete Realm classes can be referenced from model classes. " + "Neither interfaces nor abstract classes are allowed."), + field) + return false + } + } + } + + return true + } + + // Report if the default constructor is missing + private fun checkDefaultConstructor(): Boolean { + return if (!hasDefaultConstructor) { + Utils.error(String.format(Locale.US, + "Class \"%s\" must declare a public constructor with no arguments if it contains custom constructors.", + simpleJavaClassName)) + false + } else { + true + } + } + + private fun checkForFinalFields(): Boolean { + for (field in fields) { + if (!field.modifiers.contains(Modifier.FINAL)) { + continue + } + if (Utils.isMutableRealmInteger(field)) { + continue + } + + Utils.error(String.format(Locale.US, "Class \"%s\" contains illegal final field \"%s\".", simpleJavaClassName, + field.simpleName.toString())) + + return false + } + return true + } + + private fun checkForVolatileFields(): Boolean { + for (field in fields) { + if (field.modifiers.contains(Modifier.VOLATILE)) { + Utils.error(String.format(Locale.US, + "Class \"%s\" contains illegal volatile field \"%s\".", + simpleJavaClassName, + field.simpleName.toString())) + return false + } + } + return true + } + + private fun categorizeField(element: Element): Boolean { + val fieldRef = element as VariableElement + + // completely ignore any static fields + if (fieldRef.modifiers.contains(Modifier.STATIC)) { + return true + } + + // Ignore fields marked with @Ignore or if they are transient + if (fieldRef.getAnnotation(Ignore::class.java) != null || fieldRef.modifiers.contains(Modifier.TRANSIENT)) { + return true + } + + // Determine name for field + val internalFieldName = getInternalFieldName(fieldRef, defaultFieldNameFormatter) + val field = RealmFieldElement(fieldRef, internalFieldName) + + if (field.getAnnotation(Index::class.java) != null) { + if (!categorizeIndexField(element, field)) { + return false + } + } + + // @Required annotation of RealmList field only affects its value type, not field itself. + if (Utils.isRealmList(field)) { + val hasRequiredAnnotation = hasRequiredAnnotation(field) + val listGenericType = (field.asType() as DeclaredType).typeArguments + val containsRealmModelClasses = (listGenericType.isNotEmpty() && Utils.isRealmModel(listGenericType[0])) + + // @Required not allowed if the list contains Realm model classes + if (hasRequiredAnnotation && containsRealmModelClasses) { + Utils.error("@Required not allowed on RealmList's that contain other Realm model classes.") + return false + } + + // @Required thus only makes sense for RealmLists with primitive types + // We only check @Required annotation. @org.jetbrains.annotations.NotNull annotation should not affect nullability of the list values. + if (!hasRequiredAnnotation) { + if (!containsRealmModelClasses) { + nullableValueListFields.add(field) + } + } + } else if (isRequiredField(field)) { + if (!checkBasicRequiredAnnotationUsage(element, field)) { + return false + } + } else { + // The field doesn't have the @Required and @org.jetbrains.annotations.NotNull annotation. + // Without @Required annotation, boxed types/RealmObject/Date/String/bytes should be added to + // nullableFields. + // RealmList of models, RealmResults(backlinks) and primitive types are NOT nullable. @Required annotation is not supported. + if (!Utils.isPrimitiveType(field) && !Utils.isRealmResults(field)) { + nullableFields.add(field) + } + } + + if (field.getAnnotation(PrimaryKey::class.java) != null) { + if (!categorizePrimaryKeyField(field)) { + return false + } + } + + // @LinkingObjects cannot be @PrimaryKey or @Index. + if (field.getAnnotation(LinkingObjects::class.java) != null) { + // Do not add backlinks to fields list. + return categorizeBacklinkField(field) + } + + // Similarly, a MutableRealmInteger cannot be a @PrimaryKey or @LinkingObject. + if (Utils.isMutableRealmInteger(field)) { + if (!categorizeMutableRealmIntegerField(field)) { + return false + } + } + + // Standard field that appears to be valid (more fine grained checks might fail later). + fields.add(field) + if (Utils.isRealmModel(field) || Utils.isRealmModelList(field)) { + _objectReferenceFields.add(field) + } else { + basicTypeFields.add(field) + } + + return true + } + + private fun getInternalFieldName(field: VariableElement, defaultConverter: NameConverter): String { + val nameAnnotation: RealmField? = field.getAnnotation(RealmField::class.java) + if (nameAnnotation != null) { + if (nameAnnotation.name.isNotEmpty()) { + return nameAnnotation.name + } + if (nameAnnotation.value.isNotEmpty()) { + return nameAnnotation.value + } + Utils.note(String.format(("Empty internal name defined on @RealmField. " + "Falling back to named used by Java model class: %s"), field.simpleName), field) + return field.simpleName.toString() + } else { + return defaultConverter.convert(field.simpleName.toString()) + } + } + + /** + * This method only checks if the field has `@Required` annotation. + * In most cases, you should use [.isRequiredField] to take into account + * Kotlin's annotation as well. + * + * @param field target field. + * @return `true` if the field has `@Required` annotation, `false` otherwise. + * @see .isRequiredField + */ + private fun hasRequiredAnnotation(field: VariableElement): Boolean { + return field.getAnnotation(Required::class.java) != null + } + + /** + * Checks if the field is annotated as required. + * @param field target field. + * @return `true` if the field is annotated as required, `false` otherwise. + */ + private fun isRequiredField(field: VariableElement): Boolean { + if (hasRequiredAnnotation(field)) { + return true + } + + if (ignoreKotlinNullability) { + return false + } + + // Kotlin uses the `org.jetbrains.annotations.NotNull` annotation to mark non-null fields. + // In order to fully support the Kotlin type system we interpret `@NotNull` as an alias + // for `@Required` + for (annotation in field.annotationMirrors) { + if (annotation.annotationType.toString() == "org.jetbrains.annotations.NotNull") { + return true + } + } + + return false + } + + // The field has the @Index annotation. It's only valid for column types: + // STRING, DATE, INTEGER, BOOLEAN, and RealmMutableInteger + private fun categorizeIndexField(element: Element, fieldElement: RealmFieldElement): Boolean { + var indexable = false + + if (Utils.isMutableRealmInteger(fieldElement)) { + indexable = true + } else { + when (Constants.JAVA_TO_REALM_TYPES[fieldElement.asType().toString()]) { + Constants.RealmFieldType.STRING, + Constants.RealmFieldType.DATE, + Constants.RealmFieldType.INTEGER, + Constants.RealmFieldType.BOOLEAN -> { indexable = true } + else -> { /* Ignore */ } + } + } + + if (indexable) { + indexedFields.add(fieldElement) + return true + } + + Utils.error(String.format(Locale.US, "Field \"%s\" of type \"%s\" cannot be an @Index.", element, element.asType())) + return false + } + + // The field has the @Required annotation + // Returns `true` if the field could be correctly validated, `false` if an error was reported. + private fun checkBasicRequiredAnnotationUsage(element: Element, variableElement: VariableElement): Boolean { + if (Utils.isPrimitiveType(variableElement)) { + Utils.error(String.format(Locale.US, + "@Required or @NotNull annotation is unnecessary for primitive field \"%s\".", element)) + return false + } + + if (Utils.isRealmModel(variableElement)) { + Utils.error(String.format(Locale.US, + "Field \"%s\" with type \"%s\" cannot be @Required or @NotNull.", element, element.asType())) + return false + } + + // Should never get here - user should remove @Required + if (nullableFields.contains(variableElement)) { + Utils.error(String.format(Locale.US, + "Field \"%s\" with type \"%s\" appears to be nullable. Consider removing @Required.", + element, + element.asType())) + + return false + } + + return true + } + + // The field has the @PrimaryKey annotation. It is only valid for + // String, short, int, long and must only be present one time + private fun categorizePrimaryKeyField(fieldElement: RealmFieldElement): Boolean { + if (primaryKey != null) { + Utils.error(String.format(Locale.US, + "A class cannot have more than one @PrimaryKey. Both \"%s\" and \"%s\" are annotated as @PrimaryKey.", + primaryKey!!.simpleName.toString(), + fieldElement.simpleName.toString())) + return false + } + + val fieldType = fieldElement.asType() + if (!isValidPrimaryKeyType(fieldType)) { + Utils.error(String.format(Locale.US, + "Field \"%s\" with type \"%s\" cannot be used as primary key. See @PrimaryKey for legal types.", + fieldElement.simpleName.toString(), + fieldType)) + return false + } + + primaryKey = fieldElement + + // Also add as index. All types of primary key can be indexed. + if (!indexedFields.contains(fieldElement)) { + indexedFields.add(fieldElement) + } + + return true + } + + private fun categorizeBacklinkField(variableElement: VariableElement): Boolean { + val backlink = Backlink(this, variableElement) + if (!backlink.validateSource()) { + return false + } + + backlinks.add(backlink) + + return true + } + + private fun categorizeMutableRealmIntegerField(field: VariableElement): Boolean { + if (field.modifiers.contains(Modifier.FINAL)) { + return true + } + + Utils.error(String.format(Locale.US, + "Field \"%s\", a MutableRealmInteger, must be final.", + field.simpleName.toString())) + return false + } + + private fun isValidPrimaryKeyType(type: TypeMirror): Boolean { + for (validType in validPrimaryKeyTypes) { + if (typeUtils.isAssignable(type, validType)) { + return true + } + } + return false + } + + private fun containsType(listOfTypes: List, type: TypeMirror): Boolean { + for (i in listOfTypes.indices) { + // Comparing TypeMirror's using `equals()` breaks when using incremental annotation processing. + if (typeUtils.isSameType(listOfTypes[i], type)) { + return true + } + } + return false + } + + companion object { + private val OPTION_IGNORE_KOTLIN_NULLABILITY = "realm.ignoreKotlinNullability" + private val MAX_CLASSNAME_LENGTH = 57 + } + +} + diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java deleted file mode 100644 index 0c6d09306c..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - - -public class Constants { - public static final String REALM_PACKAGE_NAME = "io.realm"; - public static final String PROXY_SUFFIX = "RealmProxy"; - public static final String INTERFACE_SUFFIX = "RealmProxyInterface"; - public static final String INDENT = " "; - public static final String DEFAULT_MODULE_CLASS_NAME = "DefaultRealmModule"; - static final String STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE = - "throw new IllegalArgumentException(\"Trying to set non-nullable field '%s' to null.\")"; - static final String STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON = - "throw new IllegalArgumentException(\"JSON object doesn't have the primary key field '%s'.\")"; - static final String STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED = - "throw new io.realm.exceptions.RealmException(\"Primary key field '%s' cannot be changed after object" + - " was created.\")"; - static final String STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD = - "throw new io.realm.exceptions.RealmException(\"\\\"%s\\\" field \\\"%s\\\" cannot be loaded from json\")"; - - - /** - * Realm types and their corresponding Java types - */ - public enum RealmFieldType { - NOTYPE(null, "Void"), - INTEGER("INTEGER", "Long"), - FLOAT("FLOAT", "Float"), - DOUBLE("DOUBLE", "Double"), - BOOLEAN("BOOLEAN", "Boolean"), - STRING("STRING", "String"), - DATE("DATE", "Date"), - BINARY("BINARY", "BinaryByteArray"), - REALM_INTEGER("INTEGER", "Long"), - OBJECT("OBJECT", "Object"), - LIST("LIST", "List"), - - BACKLINK("LINKING_OBJECTS", null), - - INTEGER_LIST("INTEGER_LIST", "List"), - BOOLEAN_LIST("BOOLEAN_LIST", "List"), - STRING_LIST("STRING_LIST", "List"), - BINARY_LIST("BINARY_LIST", "List"), - DATE_LIST("DATE_LIST", "List"), - FLOAT_LIST("FLOAT_LIST", "List"), - DOUBLE_LIST("DOUBLE_LIST", "List"); - - private final String realmType; - private final String javaType; - - /** - * @param realmType The simple name of the Enum type used in the Java bindings, to represent this type. - * @param javaType The simple name of the Java type needed to store this Realm Type - */ - RealmFieldType(String realmType, String javaType) { - this.realmType = "RealmFieldType." + realmType; - this.javaType = javaType; - } - - /** - * Get the name of the enum, used in the Java bindings, used to represent the corresponding type. - * @return the name of the enum used to represent this Realm Type - */ - public String getRealmType() { - return realmType; - } - - /** - * Get the name of the Java type needed to store this Realm Type - * @return the simple name for the corresponding Java type - */ - public String getJavaType() { - return javaType; - } - } - - - static final Map JAVA_TO_REALM_TYPES; - - static { - Map m = new HashMap(); - m.put("byte", RealmFieldType.INTEGER); - m.put("short", RealmFieldType.INTEGER); - m.put("int", RealmFieldType.INTEGER); - m.put("long", RealmFieldType.INTEGER); - m.put("float", RealmFieldType.FLOAT); - m.put("double", RealmFieldType.DOUBLE); - m.put("boolean", RealmFieldType.BOOLEAN); - m.put("java.lang.Byte", RealmFieldType.INTEGER); - m.put("java.lang.Short", RealmFieldType.INTEGER); - m.put("java.lang.Integer", RealmFieldType.INTEGER); - m.put("java.lang.Long", RealmFieldType.INTEGER); - m.put("java.lang.Float", RealmFieldType.FLOAT); - m.put("java.lang.Double", RealmFieldType.DOUBLE); - m.put("java.lang.Boolean", RealmFieldType.BOOLEAN); - m.put("java.lang.String", RealmFieldType.STRING); - m.put("java.util.Date", RealmFieldType.DATE); - m.put("byte[]", RealmFieldType.BINARY); - // TODO: add support for char and Char - JAVA_TO_REALM_TYPES = Collections.unmodifiableMap(m); - } - - - static final Map LIST_ELEMENT_TYPE_TO_REALM_TYPES; - - static { - Map m = new HashMap(); - m.put("java.lang.Byte", RealmFieldType.INTEGER_LIST); - m.put("java.lang.Short", RealmFieldType.INTEGER_LIST); - m.put("java.lang.Integer", RealmFieldType.INTEGER_LIST); - m.put("java.lang.Long", RealmFieldType.INTEGER_LIST); - m.put("java.lang.Float", RealmFieldType.FLOAT_LIST); - m.put("java.lang.Double", RealmFieldType.DOUBLE_LIST); - m.put("java.lang.Boolean", RealmFieldType.BOOLEAN_LIST); - m.put("java.lang.String", RealmFieldType.STRING_LIST); - m.put("java.util.Date", RealmFieldType.DATE_LIST); - m.put("byte[]", RealmFieldType.BINARY_LIST); - LIST_ELEMENT_TYPE_TO_REALM_TYPES = Collections.unmodifiableMap(m); - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt new file mode 100644 index 0000000000..053a4de5ef --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +object Constants { + + const val REALM_PACKAGE_NAME = "io.realm" + const val PROXY_SUFFIX = "RealmProxy" + const val INTERFACE_SUFFIX = "RealmProxyInterface" + const val INDENT = " " + const val DEFAULT_MODULE_CLASS_NAME = "DefaultRealmModule" + const val STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE = + "throw new IllegalArgumentException(\"Trying to set non-nullable field '%s' to null.\")" + const val STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON = + "throw new IllegalArgumentException(\"JSON object doesn't have the primary key field '%s'.\")" + const val STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED = + "throw new io.realm.exceptions.RealmException(\"Primary key field '%s' cannot be changed after object was created.\")" + const val STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD + = "throw new io.realm.exceptions.RealmException(\"\\\"%s\\\" field \\\"%s\\\" cannot be loaded from json\")" + val JAVA_TO_REALM_TYPES = hashMapOf() + val LIST_ELEMENT_TYPE_TO_REALM_TYPES = hashMapOf() + + /** + * Realm types and their corresponding Java types. + * + * @param realmType The simple name of the Enum type used in the Java bindings, to represent this type. + * @param javaType The simple name of the Java type needed to store this Realm Type + */ + enum class RealmFieldType(realmType: String?, val javaType: String?) { + NOTYPE(null, "Void"), + INTEGER("INTEGER", "Long"), + FLOAT("FLOAT", "Float"), + DOUBLE("DOUBLE", "Double"), + BOOLEAN("BOOLEAN", "Boolean"), + STRING("STRING", "String"), + DATE("DATE", "Date"), + BINARY("BINARY", "BinaryByteArray"), + REALM_INTEGER("INTEGER", "Long"), + OBJECT("OBJECT", "Object"), + LIST("LIST", "List"), + + BACKLINK("LINKING_OBJECTS", null), + + INTEGER_LIST("INTEGER_LIST", "List"), + BOOLEAN_LIST("BOOLEAN_LIST", "List"), + STRING_LIST("STRING_LIST", "List"), + BINARY_LIST("BINARY_LIST", "List"), + DATE_LIST("DATE_LIST", "List"), + FLOAT_LIST("FLOAT_LIST", "List"), + DOUBLE_LIST("DOUBLE_LIST", "List"); + + /** + * The name of the enum, used in the Java bindings, used to represent the corresponding type. + */ + val realmType: String = "RealmFieldType.$realmType" + } + + init { + JAVA_TO_REALM_TYPES["byte"] = RealmFieldType.INTEGER + JAVA_TO_REALM_TYPES["short"] = RealmFieldType.INTEGER + JAVA_TO_REALM_TYPES["int"] = RealmFieldType.INTEGER + JAVA_TO_REALM_TYPES["long"] = RealmFieldType.INTEGER + JAVA_TO_REALM_TYPES["float"] = RealmFieldType.FLOAT + JAVA_TO_REALM_TYPES["double"] = RealmFieldType.DOUBLE + JAVA_TO_REALM_TYPES["boolean"] = RealmFieldType.BOOLEAN + JAVA_TO_REALM_TYPES["java.lang.Byte"] = RealmFieldType.INTEGER + JAVA_TO_REALM_TYPES["java.lang.Short"] = RealmFieldType.INTEGER + JAVA_TO_REALM_TYPES["java.lang.Integer"] = RealmFieldType.INTEGER + JAVA_TO_REALM_TYPES["java.lang.Long"] = RealmFieldType.INTEGER + JAVA_TO_REALM_TYPES["java.lang.Float"] = RealmFieldType.FLOAT + JAVA_TO_REALM_TYPES["java.lang.Double"] = RealmFieldType.DOUBLE + JAVA_TO_REALM_TYPES["java.lang.Boolean"] = RealmFieldType.BOOLEAN + JAVA_TO_REALM_TYPES["java.lang.String"] = RealmFieldType.STRING + JAVA_TO_REALM_TYPES["java.util.Date"] = RealmFieldType.DATE + JAVA_TO_REALM_TYPES["byte[]"] = RealmFieldType.BINARY + // TODO: add support for char and Char + + LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Byte"] = RealmFieldType.INTEGER_LIST + LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Short"] = RealmFieldType.INTEGER_LIST + LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Integer"] = RealmFieldType.INTEGER_LIST + LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Long"] = RealmFieldType.INTEGER_LIST + LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Float"] = RealmFieldType.FLOAT_LIST + LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Double"] = RealmFieldType.DOUBLE_LIST + LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Boolean"] = RealmFieldType.BOOLEAN_LIST + LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.String"] = RealmFieldType.STRING_LIST + LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.util.Date"] = RealmFieldType.DATE_LIST + LIST_ELEMENT_TYPE_TO_REALM_TYPES["byte[]"] = RealmFieldType.BINARY_LIST + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java deleted file mode 100644 index 948f662225..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import com.squareup.javawriter.JavaWriter; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Modifier; -import javax.tools.JavaFileObject; - -import io.realm.annotations.RealmModule; - - -/** - * This class is responsible for creating the DefaultRealmModule that contains all known - * {@link io.realm.annotations.RealmClass}' known at compile time. - */ -public class DefaultModuleGenerator { - - private final ProcessingEnvironment env; - - public DefaultModuleGenerator(ProcessingEnvironment env) { - this.env = env; - } - - public void generate() throws IOException { - String qualifiedGeneratedClassName = String.format(Locale.US, "%s.%s", Constants.REALM_PACKAGE_NAME, Constants.DEFAULT_MODULE_CLASS_NAME); - JavaFileObject sourceFile = env.getFiler().createSourceFile(qualifiedGeneratedClassName); - JavaWriter writer = new JavaWriter(new BufferedWriter(sourceFile.openWriter())); - writer.setIndent(" "); - - writer.emitPackage(Constants.REALM_PACKAGE_NAME); - writer.emitEmptyLine(); - - Map attributes = new LinkedHashMap<>(); - attributes.put("allClasses", Boolean.TRUE); - writer.emitAnnotation(RealmModule.class, attributes); - writer.beginType( - qualifiedGeneratedClassName, // full qualified name of the item to generate - "class", // the type of the item - Collections.emptySet(), // modifiers to apply - null); // class to extend - writer.emitEmptyLine(); - - writer.endType(); - writer.close(); - } -} - diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.kt new file mode 100644 index 0000000000..488890f2de --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/DefaultModuleGenerator.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import com.squareup.javawriter.JavaWriter + +import java.io.BufferedWriter +import java.io.IOException +import java.util.LinkedHashMap +import java.util.Locale + +import javax.annotation.processing.ProcessingEnvironment + +import io.realm.annotations.RealmModule + +/** + * This class is responsible for creating the DefaultRealmModule that contains all known + * [io.realm.annotations.RealmClass]' known at compile time. + */ +class DefaultModuleGenerator(private val env: ProcessingEnvironment) { + + @Throws(IOException::class) + fun generate() { + val qualifiedGeneratedClassName = String.format(Locale.US, "%s.%s", Constants.REALM_PACKAGE_NAME, Constants.DEFAULT_MODULE_CLASS_NAME) + val sourceFile = env.filer.createSourceFile(qualifiedGeneratedClassName) + val writer = JavaWriter(BufferedWriter(sourceFile.openWriter())) + + /** + * Defines the [io.realm.annotations.RealmModule.allClasses] attribute + */ + val attributes = LinkedHashMap() + attributes["allClasses"] = java.lang.Boolean.TRUE + + // Build minimal class with the required `@RealmModule` annotation for including all + // known Realm model classes in this compilation unit. + writer.apply { + indent = Constants.INDENT + emitPackage(Constants.REALM_PACKAGE_NAME) + emitEmptyLine() + emitAnnotation(RealmModule::class.java, attributes) + beginType( + qualifiedGeneratedClassName, // full qualified name of the item to generate + "class", // the type of the item + emptySet(), // modifiers to apply + null) // class to extend + emitEmptyLine() + endType() + close() + } + + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java deleted file mode 100644 index c713790379..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.java +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; - -import io.realm.annotations.RealmModule; -import io.realm.annotations.RealmNamingPolicy; -import io.realm.processor.nameconverter.NameConverter; - - -/** - * Utility class for holding metadata for the Realm modules. - *

            - * Modules are inherently difficult to process because a model class can be part of multiple modules - * that contain information required by the model class (e.g. class/field naming policies). At the - * same time, the module will need the data from processed model classes to fully complete its - * analysis (e.g. to ensure that only valid Realm model classes are added to the module). - *

            - * For this reason, processing modules are separated into 3 steps: - *

              - *
            1. - * Pre-processing. Done by calling {@link #preProcess(Set)}, which will do an initial parse - * of the modules and build up all information it can before processing any model classes. - *
            2. - *
            3. - * Process model classes. See {@link ClassMetaData#generate(ModuleMetaData)}. - *
            4. - *
            5. - * Post-processing. Done by calling {@link #postProcess(ClassCollection)}. All modules can now - * be fully verified, and all metadata required to output module files can be generated. - *
            6. - *
            - */ -public class ModuleMetaData { - - // Pre-processing - // - private Set globalModules = new LinkedHashSet<>(); // All modules with `allClasses = true` set - private Map> specificClassesModules = new LinkedHashMap<>(); // Modules with classes specifically named - private Map classNamingPolicy = new LinkedHashMap<>(); - private Map fieldNamingPolicy = new LinkedHashMap<>(); - private Map moduleAnnotations = new HashMap<>(); - - // Post-processing - // - private Map> modules = new LinkedHashMap<>(); - private Map> libraryModules = new LinkedHashMap<>(); - - private boolean shouldCreateDefaultModule; - - /** - * Builds all meta data structures that can be calculated before processing any model classes. - * Any errors or messages will be posted on the provided Messager. - * - * @return True if meta data was correctly created and processing of model classes can continue, false otherwise. - */ - public boolean preProcess(Set moduleClasses) { - - // Tracks all module settings with `allClasses` enabled - Set globalModuleInfo = new HashSet<>(); - - // Tracks which modules a class was mentioned in by name using `classes = { ... }` - // > classSpecificModuleInfo = new HashMap<>(); - - // Check that modules are setup correctly - for (Element classElement : moduleClasses) { - String classSimpleName = classElement.getSimpleName().toString(); - - // Check that the annotation is only applied to a class - if (!classElement.getKind().equals(ElementKind.CLASS)) { - Utils.error("The RealmModule annotation can only be applied to classes", classElement); - return false; - } - - // Check that allClasses and classes are not set at the same time - RealmModule moduleAnnotation = classElement.getAnnotation(RealmModule.class); - Utils.note("Processing module " + classSimpleName); - if (moduleAnnotation.allClasses() && hasCustomClassList(classElement)) { - Utils.error("Setting @RealmModule(allClasses=true) will override @RealmModule(classes={...}) in " + classSimpleName); - return false; - } - - // Validate that naming policies are correctly configured. - if (!validateNamingPolicies(globalModuleInfo, classSpecificModuleInfo, (TypeElement) classElement, moduleAnnotation)) { - return false; - } - - moduleAnnotations.put(((TypeElement) classElement).getQualifiedName().toString(), moduleAnnotation); - } - - return true; - } - - /** - * Validates that the class/field naming policy for this module is correct. - * - * @param globalModuleInfo list of all modules with `allClasses` set - * @param classSpecificModuleInfo map of explicit classes and which modules they are explicitly mentioned in. - * @param classElement class element currently being validated - * @param moduleAnnotation annotation on this class. - * @return {@code true} if everything checks out, {@code false} if an error was found and reported. - */ - private boolean validateNamingPolicies(Set globalModuleInfo, Map> classSpecificModuleInfo, TypeElement classElement, RealmModule moduleAnnotation) { - RealmNamingPolicy classNamePolicy = moduleAnnotation.classNamingPolicy(); - RealmNamingPolicy fieldNamePolicy = moduleAnnotation.fieldNamingPolicy(); - String qualifiedModuleClassName = classElement.getQualifiedName().toString(); - ModulePolicyInfo moduleInfo = new ModulePolicyInfo(qualifiedModuleClassName, classNamePolicy, fieldNamePolicy); - - // The difference between `allClasses` and a list of classes is a bit tricky at this stage - // as we haven't processed the full list of classes yet. We therefore need to treat - // each case specifically :( - // We do not compare against the default module as it is always configured correctly - // with NO_POLICY, meaning it will not trigger any errors. - if (moduleAnnotation.allClasses()) { - // Check for conflicts with all other modules with `allClasses` set. - for (ModulePolicyInfo otherModuleInfo : globalModuleInfo) { - if (checkAndReportPolicyConflict(moduleInfo, otherModuleInfo)) { - return false; - } - } - - // Check for conflicts with specifically named classes. This can happen if another - // module is listing specific classes with another policy. - for (Map.Entry> classPolicyInfo : classSpecificModuleInfo.entrySet()) { - for (ModulePolicyInfo otherModuleInfo : classPolicyInfo.getValue()) { - if (checkAndReportPolicyConflict(moduleInfo, otherModuleInfo)) { - return false; - } - } - } - - // Everything checks out. Add moduleInfo so we can track it for the next module. - globalModuleInfo.add(moduleInfo); - globalModules.add(qualifiedModuleClassName); - - } else { - // We need to verify each class in the modules class list - Set classNames = getClassListFromModule(classElement); - for (String qualifiedClassName : classNames) { - - // Check that no other module with `allClasses` conflict with this specific - // class configuration - for (ModulePolicyInfo otherModuleInfo : globalModuleInfo) { - if (checkAndReportPolicyConflict(moduleInfo, otherModuleInfo)) { - return false; - } - } - - // Check that this specific class isn't conflicting with another module - // specifically mentioning it using `classes = { ... }` - List otherModules = classSpecificModuleInfo.get(qualifiedClassName); - if (otherModules != null) { - for (ModulePolicyInfo otherModuleInfo : otherModules) { - if (checkAndReportPolicyConflict(qualifiedClassName, moduleInfo, otherModuleInfo)) { - return false; - } - } - } - - // Keep track of the specific class for other module checks. We only - // need to track the latest module seen as previous errors would have been - // caught in a previous iteration of the loop. - if (!classSpecificModuleInfo.containsKey(qualifiedClassName)) { - classSpecificModuleInfo.put(qualifiedClassName, new ArrayList<>()); - } - classSpecificModuleInfo.get(qualifiedClassName).add(moduleInfo); - } - specificClassesModules.put(qualifiedModuleClassName, classNames); - } - - classNamingPolicy.put(qualifiedModuleClassName, classNamePolicy); - fieldNamingPolicy.put(qualifiedModuleClassName, fieldNamePolicy); - return true; - } - - /** - * All model classes have now been processed and the final validation of modules can occur. - * Any errors or messages will be posted on the provided Messager. - * - * @param modelClasses all Realm model classes found by the annotation processor. - * @return {@code true} if the module is valid, {@code false} otherwise. - */ - public boolean postProcess(ClassCollection modelClasses) { - - // Process all global modules - for (String qualifiedModuleClassName : globalModules) { - Set classData = new LinkedHashSet<>(); - classData.addAll(modelClasses.getClasses()); - defineModule(qualifiedModuleClassName, classData); - } - - // Process all modules with specific classes - for (Map.Entry> module : specificClassesModules.entrySet()) { - String qualifiedModuleClassName = module.getKey(); - Set classData = new LinkedHashSet<>(); - for (String qualifiedModelClassName : module.getValue()) { - if (!modelClasses.containsQualifiedClass(qualifiedModelClassName)) { - Utils.error(Utils.stripPackage(qualifiedModelClassName) + " could not be added to the module. " + - "Only classes extending RealmObject or implementing RealmModel, which are part of this project, can be added."); - return false; - - } - classData.add(modelClasses.getClassFromQualifiedName(qualifiedModelClassName)); - } - defineModule(qualifiedModuleClassName, classData); - } - - // Check that app and library modules are not mixed - if (modules.size() > 0 && libraryModules.size() > 0) { - StringBuilder sb = new StringBuilder(); - sb.append("Normal modules and library modules cannot be mixed in the same project."); - sb.append('\n'); - sb.append("Normal module(s):\n"); - for (String module : modules.keySet()) { - sb.append(" "); - sb.append(module); - sb.append('\n'); - } - sb.append("Library module(s):\n"); - for (String module : libraryModules.keySet()) { - sb.append(" "); - sb.append(module); - sb.append('\n'); - } - Utils.error(sb.toString()); - return false; - } - - // Create default Realm module if needed. - // Note: Kotlin will trigger the annotation processor even if no Realm annotations are used. - // The DefaultRealmModule should not be created in this case either. - if (libraryModules.size() == 0 && modelClasses.size() > 0) { - shouldCreateDefaultModule = true; - String defaultModuleName = Constants.REALM_PACKAGE_NAME + "." + Constants.DEFAULT_MODULE_CLASS_NAME; - modules.put(defaultModuleName, modelClasses.getClasses()); - } - - return true; - } - - private void defineModule(String qualifiedModuleClassName, Set classData) { - if (!classData.isEmpty()) { - if (moduleAnnotations.get(qualifiedModuleClassName).library()) { - libraryModules.put(qualifiedModuleClassName, classData); - } else { - modules.put(qualifiedModuleClassName, classData); - } - } - } - - // Checks if two modules have policy conflicts. Returns true if a conflict was found and reported. - private boolean checkAndReportPolicyConflict(ModulePolicyInfo moduleInfo, ModulePolicyInfo otherModuleInfo) { - return checkAndReportPolicyConflict(null, moduleInfo, otherModuleInfo); - } - - /** - * Check for name policy conflicts and report the error if found. - * - * @param className optional class name if a specific class is being checked. - * @param moduleInfo current module. - * @param otherModuleInfo already processed module. - * @return {@code true} if any errors was reported, {@code false} otherwise. - */ - private boolean checkAndReportPolicyConflict(String className, ModulePolicyInfo moduleInfo, ModulePolicyInfo otherModuleInfo) { - boolean foundErrors = false; - - // Check class naming policy - RealmNamingPolicy classPolicy = moduleInfo.classNamePolicy; - RealmNamingPolicy otherClassPolicy = otherModuleInfo.classNamePolicy; - if (classPolicy != RealmNamingPolicy.NO_POLICY - && otherClassPolicy != RealmNamingPolicy.NO_POLICY - && classPolicy != otherClassPolicy) { - Utils.error(String.format("The modules %s and %s disagree on the class naming policy%s: %s vs. %s. " + - "They same policy must be used.", - moduleInfo.qualifiedModuleClassName, - otherModuleInfo.qualifiedModuleClassName, - (className != null) ? " for " + className : "", - classPolicy, - otherClassPolicy)); - foundErrors = true; - } - - // Check field naming policy - RealmNamingPolicy fieldPolicy = moduleInfo.fieldNamePolicy; - RealmNamingPolicy otherFieldPolicy = otherModuleInfo.fieldNamePolicy; - if (fieldPolicy != RealmNamingPolicy.NO_POLICY - && otherFieldPolicy != RealmNamingPolicy.NO_POLICY - && fieldPolicy != otherFieldPolicy) { - Utils.error(String.format("The modules %s and %s disagree on the field naming policy%s: %s vs. %s. " + - "They same policy should be used.", - moduleInfo.qualifiedModuleClassName, - otherModuleInfo.qualifiedModuleClassName, - (className != null) ? " for " + className : "", - fieldPolicy, - otherFieldPolicy)); - foundErrors = true; - } - - return foundErrors; - } - - // Detour needed to access the class elements in the array - // See http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ - @SuppressWarnings("unchecked") - private Set getClassListFromModule(Element classElement) { - AnnotationMirror annotationMirror = getAnnotationMirror(classElement); - AnnotationValue annotationValue = getAnnotationValue(annotationMirror); - Set classes = new HashSet(); - List moduleClasses = (List) annotationValue.getValue(); - for (AnnotationValue classMirror : moduleClasses) { - String fullyQualifiedClassName = classMirror.getValue().toString(); - classes.add(fullyQualifiedClassName); - } - return classes; - } - - // Work-around for asking for a Class primitive array which would otherwise throw a TypeMirrorException - // https://community.oracle.com/thread/1184190 - @SuppressWarnings("unchecked") - private boolean hasCustomClassList(Element classElement) { - AnnotationMirror annotationMirror = getAnnotationMirror(classElement); - AnnotationValue annotationValue = getAnnotationValue(annotationMirror); - if (annotationValue == null) { - return false; - } else { - List moduleClasses = (List) annotationValue.getValue(); - return moduleClasses.size() > 0; - } - } - - private AnnotationMirror getAnnotationMirror(Element classElement) { - AnnotationMirror annotationMirror = null; - for (AnnotationMirror am : classElement.getAnnotationMirrors()) { - if (am.getAnnotationType().toString().equals(RealmModule.class.getCanonicalName())) { - annotationMirror = am; - break; - } - } - return annotationMirror; - } - - private AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror) { - if (annotationMirror == null) { - return null; - } - AnnotationValue annotationValue = null; - for (Map.Entry entry : annotationMirror.getElementValues().entrySet()) { - if (entry.getKey().getSimpleName().toString().equals("classes")) { - annotationValue = entry.getValue(); - break; - } - } - return annotationValue; - } - - /** - * Returns all module classes and the RealmObjects they know of. - */ - public Map> getAllModules() { - Map> allModules = new LinkedHashMap<>(); - allModules.putAll(modules); - allModules.putAll(libraryModules); - return allModules; - } - - /** - * Returns {@code true} if the DefaultRealmModule.java file should be created. - */ - public boolean shouldCreateDefaultModule() { - return shouldCreateDefaultModule; - } - - /** - * Only available after {@link #preProcess(Set)} has run. - * Returns the module name policy the given name. - */ - public NameConverter getClassNameFormatter(String qualifiedClassName) { - // We already validated that module definitions all agree on the same name policy - // so just find first match - if (!globalModules.isEmpty()) { - return Utils.getNameFormatter(classNamingPolicy.get(globalModules.iterator().next())); - } - - // No global modules found, so find match in modules specifically listing the class. - // We already validated that all modules agree on the converter, so just find first match. - for (Map.Entry> moduleInfo : specificClassesModules.entrySet()) { - if (moduleInfo.getValue().contains(qualifiedClassName)) { - return Utils.getNameFormatter(classNamingPolicy.get(moduleInfo.getKey())); - } - } - - // No policy was provided anywhere for this class - return Utils.getNameFormatter(RealmNamingPolicy.NO_POLICY); - } - - - /** - * Only available after {@link #preProcess(Set)} has run. - * - * Returns the module name policy the field names. - * - * @param qualifiedClassName - */ - public NameConverter getFieldNameFormatter(String qualifiedClassName) { - // We already validated that module definitions all agree on the same name policy - // so just find first match - if (!globalModules.isEmpty()) { - return Utils.getNameFormatter(fieldNamingPolicy.get(globalModules.iterator().next())); - } - - for (Map.Entry> moduleInfo : specificClassesModules.entrySet()) { - if (moduleInfo.getValue().contains(qualifiedClassName)) { - return Utils.getNameFormatter(fieldNamingPolicy.get(moduleInfo.getKey())); - } - } - - return Utils.getNameFormatter(RealmNamingPolicy.NO_POLICY); - } - - // Tuple helper class - private class ModulePolicyInfo { - public final String qualifiedModuleClassName; - public final RealmNamingPolicy classNamePolicy; - public final RealmNamingPolicy fieldNamePolicy; - - public ModulePolicyInfo(String qualifiedModuleClassName, RealmNamingPolicy classNamePolicy, RealmNamingPolicy fieldNamePolicy) { - this.qualifiedModuleClassName = qualifiedModuleClassName; - this.classNamePolicy = classNamePolicy; - this.fieldNamePolicy = fieldNamePolicy; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ModulePolicyInfo that = (ModulePolicyInfo) o; - - if (!qualifiedModuleClassName.equals(that.qualifiedModuleClassName)) return false; - if (classNamePolicy != that.classNamePolicy) return false; - return fieldNamePolicy == that.fieldNamePolicy; - } - - @Override - public int hashCode() { - int result = qualifiedModuleClassName.hashCode(); - result = 31 * result + classNamePolicy.hashCode(); - result = 31 * result + fieldNamePolicy.hashCode(); - return result; - } - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.kt new file mode 100644 index 0000000000..0795ad6ce6 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ModuleMetaData.kt @@ -0,0 +1,440 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import java.util.ArrayList +import java.util.HashMap +import java.util.HashSet +import java.util.LinkedHashMap +import java.util.LinkedHashSet + +import javax.lang.model.element.AnnotationMirror +import javax.lang.model.element.AnnotationValue +import javax.lang.model.element.Element +import javax.lang.model.element.ElementKind +import javax.lang.model.element.TypeElement + +import io.realm.annotations.RealmModule +import io.realm.annotations.RealmNamingPolicy +import io.realm.processor.nameconverter.NameConverter + +/** + * Utility class for holding metadata for the Realm modules. + * + * Modules are inherently difficult to process because a model class can be part of multiple modules + * that contain information required by the model class (e.g. class/field naming policies). At the + * same time, the module will need the data from processed model classes to fully complete its + * analysis (e.g. to ensure that only valid Realm model classes are added to the module). + * + * For this reason, processing modules are separated into 3 steps: + * + * 1. Pre-processing. Done by calling [ModuleMetaData.preProcess], which will do an initial parse of the modules + * and build up all information it can before processing any model classes. + * + * 2. Process model classes. See [ClassMetaData.generate]. + * + * 3. Post-processing. Done by calling [ModuleMetaData.postProcess]. All modules can now be fully verified, and + * all metadata required to output module files can be generated. + */ +class ModuleMetaData { + + // Pre-processing + private val globalModules = LinkedHashSet() // All modules with `allClasses = true` set + private val specificClassesModules = LinkedHashMap>() // Modules with classes specifically named + private val classNamingPolicy = LinkedHashMap() + private val fieldNamingPolicy = LinkedHashMap() + private val moduleAnnotations = HashMap() + + // Post-processing + private val modules = LinkedHashMap>() + private val libraryModules = LinkedHashMap>() + + private var shouldCreateDefaultModule: Boolean = false + + /** + * Returns all module classes and the RealmObjects they know of. + */ + val allModules: Map> + get() { + val allModules = LinkedHashMap>() + allModules.putAll(modules) + allModules.putAll(libraryModules) + return allModules + } + + /** + * Builds all meta data structures that can be calculated before processing any model classes. + * Any errors or messages will be posted on the provided Messager. + * + * @return True if meta data was correctly created and processing of model classes can continue, false otherwise. + */ + fun preProcess(moduleClasses: Set): Boolean { + + // Tracks all module settings with `allClasses` enabled + val globalModuleInfo = HashSet() + + // Tracks which modules a class was mentioned in by name using `classes = { ... }` + // >() + + // Check that modules are setup correctly + for (classElement in moduleClasses) { + val classSimpleName = classElement.simpleName.toString() + + // Check that the annotation is only applied to a class + if (classElement.kind != ElementKind.CLASS) { + Utils.error("The RealmModule annotation can only be applied to classes", classElement) + return false + } + + // Check that allClasses and classes are not set at the same time + val moduleAnnotation = classElement.getAnnotation(RealmModule::class.java) + Utils.note("Processing module $classSimpleName") + if (moduleAnnotation.allClasses && hasCustomClassList(classElement)) { + Utils.error("Setting @RealmModule(allClasses=true) will override @RealmModule(classes={...}) in $classSimpleName") + return false + } + + // Validate that naming policies are correctly configured. + if (!validateNamingPolicies(globalModuleInfo, classSpecificModuleInfo, classElement as TypeElement, moduleAnnotation)) { + return false + } + + moduleAnnotations[QualifiedClassName(classElement.qualifiedName)] = moduleAnnotation + } + + return true + } + + /** + * Validates that the class/field naming policy for this module is correct. + * + * @param globalModuleInfo list of all modules with `allClasses` set + * @param classSpecificModuleInfo map of explicit classes and which modules they are explicitly mentioned in. + * @param classElement class element currently being validated + * @param moduleAnnotation annotation on this class. + * @return `true` if everything checks out, `false` if an error was found and reported. + */ + private fun validateNamingPolicies(globalModuleInfo: MutableSet, + classSpecificModuleInfo: HashMap>, + classElement: TypeElement, + moduleAnnotation: RealmModule): Boolean { + val classNamePolicy = moduleAnnotation.classNamingPolicy + val fieldNamePolicy = moduleAnnotation.fieldNamingPolicy + val moduleClassName = QualifiedClassName(classElement.qualifiedName) + val moduleInfo = ModulePolicyInfo(moduleClassName, classNamePolicy, fieldNamePolicy) + + // The difference between `allClasses` and a list of classes is a bit tricky at this stage + // as we haven't processed the full list of classes yet. We therefore need to treat + // each case specifically :( + // We do not compare against the default module as it is always configured correctly + // with NO_POLICY, meaning it will not trigger any errors. + if (moduleAnnotation.allClasses) { + // Check for conflicts with all other modules with `allClasses` set. + for (otherModuleInfo in globalModuleInfo) { + if (checkAndReportPolicyConflict(moduleInfo, otherModuleInfo)) { + return false + } + } + + // Check for conflicts with specifically named classes. This can happen if another + // module is listing specific classes with another policy. + for ((_, value) in classSpecificModuleInfo) { + for (otherModuleInfo in value) { + if (checkAndReportPolicyConflict(moduleInfo, otherModuleInfo)) { + return false + } + } + } + + // Everything checks out. Add moduleInfo so we can track it for the next module. + globalModuleInfo.add(moduleInfo) + globalModules.add(moduleClassName) + + } else { + // We need to verify each class in the modules class list + val classNames = getClassListFromModule(classElement) + for (className in classNames) { + + // Check that no other module with `allClasses` conflict with this specific + // class configuration + for (otherModuleInfo in globalModuleInfo) { + if (checkAndReportPolicyConflict(moduleInfo, otherModuleInfo)) { + return false + } + } + + // Check that this specific class isn't conflicting with another module + // specifically mentioning it using `classes = { ... }` + val otherModules= classSpecificModuleInfo[className] + if (otherModules != null) { + for (otherModuleInfo in otherModules) { + if (checkAndReportPolicyConflict(className, moduleInfo, otherModuleInfo)) { + return false + } + } + } + + // Keep track of the specific class for other module checks. We only + // need to track the latest module seen as previous errors would have been + // caught in a previous iteration of the loop. + if (!classSpecificModuleInfo.containsKey(className)) { + classSpecificModuleInfo[className] = ArrayList() + } + classSpecificModuleInfo[className]!!.add(moduleInfo) + } + specificClassesModules[moduleClassName] = classNames + } + + classNamingPolicy[moduleClassName] = classNamePolicy + fieldNamingPolicy[moduleClassName] = fieldNamePolicy + return true + } + + /** + * All model classes have now been processed and the final validation of modules can occur. + * Any errors or messages will be posted on the provided Messager. + * + * @param modelClasses all Realm model classes found by the annotation processor. + * @return `true` if the module is valid, `false` otherwise. + */ + fun postProcess(modelClasses: ClassCollection): Boolean { + + // Process all global modules + for (qualifiedModuleClassName: QualifiedClassName in globalModules) { + val classData = LinkedHashSet() + classData.addAll(modelClasses.classes) + defineModule(qualifiedModuleClassName, classData) + } + + // Process all modules with specific classes + for ((qualifiedModuleClassName, value) in specificClassesModules) { + val classData = LinkedHashSet() + for (modelClassName: QualifiedClassName in value) { + if (!modelClasses.containsQualifiedClass(modelClassName)) { + Utils.error("${modelClassName.getSimpleName()} could not be added to the module. " + + "Only classes extending RealmObject or implementing RealmModel, which are part of this project, can be added.") + return false + + } + classData.add(modelClasses.getClassFromQualifiedName(modelClassName)) + } + defineModule(qualifiedModuleClassName, classData) + } + + // Check that app and library modules are not mixed + if (modules.size > 0 && libraryModules.size > 0) { + val sb = StringBuilder() + sb.append("Normal modules and library modules cannot be mixed in the same project.") + sb.append('\n') + sb.append("Normal module(s):\n") + for (module in modules.keys) { + sb.append(" ") + sb.append(module) + sb.append('\n') + } + sb.append("Library module(s):\n") + for (module in libraryModules.keys) { + sb.append(" ") + sb.append(module) + sb.append('\n') + } + Utils.error(sb.toString()) + return false + } + + // Create default Realm module if needed. + // Note: Kotlin will trigger the annotation processor even if no Realm annotations are used. + // The DefaultRealmModule should not be created in this case either. + if (libraryModules.size == 0 && modelClasses.size() > 0) { + shouldCreateDefaultModule = true + val defaultModuleName = QualifiedClassName("${Constants.REALM_PACKAGE_NAME}.${Constants.DEFAULT_MODULE_CLASS_NAME}") + modules[defaultModuleName] = modelClasses.classes + } + + return true + } + + private fun defineModule(moduleClassName: QualifiedClassName, classData: Set) { + if (classData.isNotEmpty()) { + if (moduleAnnotations[moduleClassName]!!.library) { + libraryModules[moduleClassName] = classData + } else { + modules[moduleClassName] = classData + } + } + } + + // Checks if two modules have policy conflicts. Returns true if a conflict was found and reported. + private fun checkAndReportPolicyConflict(moduleInfo: ModulePolicyInfo, otherModuleInfo: ModulePolicyInfo): Boolean { + return checkAndReportPolicyConflict(null, moduleInfo, otherModuleInfo) + } + + /** + * Check for name policy conflicts and report the error if found. + * + * @param className optional class name if a specific class is being checked. + * @param moduleInfo current module. + * @param otherModuleInfo already processed module. + * @return `true` if any errors was reported, `false` otherwise. + */ + private fun checkAndReportPolicyConflict(className: QualifiedClassName?, moduleInfo: ModulePolicyInfo, otherModuleInfo: ModulePolicyInfo): Boolean { + var foundErrors = false + + // Check class naming policy + val classPolicy = moduleInfo.classNamePolicy + val otherClassPolicy = otherModuleInfo.classNamePolicy + if (classPolicy != RealmNamingPolicy.NO_POLICY + && otherClassPolicy != RealmNamingPolicy.NO_POLICY + && classPolicy != otherClassPolicy) { + Utils.error(String.format("The modules %s and %s disagree on the class naming policy%s: %s vs. %s. " + "They same policy must be used.", + moduleInfo.moduleClassName, + otherModuleInfo.moduleClassName, + if (className != null) " for $className" else "", + classPolicy, + otherClassPolicy)) + foundErrors = true + } + + // Check field naming policy + val fieldPolicy = moduleInfo.fieldNamePolicy + val otherFieldPolicy = otherModuleInfo.fieldNamePolicy + if (fieldPolicy != RealmNamingPolicy.NO_POLICY + && otherFieldPolicy != RealmNamingPolicy.NO_POLICY + && fieldPolicy != otherFieldPolicy) { + Utils.error(String.format("The modules %s and %s disagree on the field naming policy%s: %s vs. %s. " + "They same policy should be used.", + moduleInfo.moduleClassName, + otherModuleInfo.moduleClassName, + if (className != null) " for $className" else "", + fieldPolicy, + otherFieldPolicy)) + foundErrors = true + } + + return foundErrors + } + + // Detour needed to access the class elements in the array + // See http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ + private fun getClassListFromModule(classElement: Element): Set { + val annotationMirror: AnnotationMirror? = getAnnotationMirror(classElement) + val annotationValue: AnnotationValue? = getAnnotationValue(annotationMirror) + val classes = HashSet() + val moduleClasses = annotationValue!!.value as List<*> + for (classMirror in moduleClasses) { + // FIXME: Something is fishy about this. Figure out how to get the proper types in Kotlin here + val className = QualifiedClassName(classMirror.toString().removeSuffix(".class")) + classes.add(className) + } + return classes + } + + // Work-around for asking for a Class primitive array which would otherwise throw a TypeMirrorException + // https://community.oracle.com/thread/1184190 + private fun hasCustomClassList(classElement: Element): Boolean { + val annotationMirror: AnnotationMirror? = getAnnotationMirror(classElement) + val annotationValue: AnnotationValue? = getAnnotationValue(annotationMirror) + return if (annotationValue == null) { + false + } else { + val moduleClasses = annotationValue.value as List<*> + moduleClasses.isNotEmpty() + } + } + + private fun getAnnotationMirror(classElement: Element): AnnotationMirror? { + var annotationMirror: AnnotationMirror? = null + for (am in classElement.annotationMirrors) { + if (am.annotationType.toString() == RealmModule::class.java.canonicalName) { + annotationMirror = am + break + } + } + return annotationMirror + } + + private fun getAnnotationValue(annotationMirror: AnnotationMirror?): AnnotationValue? { + if (annotationMirror == null) { + return null + } + var annotationValue: AnnotationValue? = null + for ((key, value) in annotationMirror.elementValues) { + if (key.simpleName.toString() == "classes") { + annotationValue = value + break + } + } + return annotationValue + } + + /** + * Returns `true` if the DefaultRealmModule.java file should be created. + */ + fun shouldCreateDefaultModule(): Boolean { + return shouldCreateDefaultModule + } + + /** + * Only available after [.preProcess] has run. + * Returns the module name policy the given name. + */ + fun getClassNameFormatter(className: QualifiedClassName): NameConverter { + // We already validated that module definitions all agree on the same name policy + // so just find first match + if (globalModules.isNotEmpty()) { + return Utils.getNameFormatter(classNamingPolicy[globalModules.iterator().next()]) + } + + // No global modules found, so find match in modules specifically listing the class. + // We already validated that all modules agree on the converter, so just find first match. + for ((key, value) in specificClassesModules) { + if (value.contains(className)) { + return Utils.getNameFormatter(classNamingPolicy[key]) + } + } + + // No policy was provided anywhere for this class + return Utils.getNameFormatter(RealmNamingPolicy.NO_POLICY) + } + + + /** + * Only available after [ModuleMetaData.preProcess] has run. + * + * Returns the module name policy the field names. + */ + fun getFieldNameFormatter(className: QualifiedClassName): NameConverter { + // We already validated that module definitions all agree on the same name policy + // so just find first match + if (globalModules.isNotEmpty()) { + return Utils.getNameFormatter(fieldNamingPolicy[globalModules.iterator().next()]) + } + + for ((key, value) in specificClassesModules) { + if (value.contains(className)) { + return Utils.getNameFormatter(fieldNamingPolicy[key]) + } + } + + return Utils.getNameFormatter(RealmNamingPolicy.NO_POLICY) + } + + // Tuple helper class + private data class ModulePolicyInfo(val moduleClassName: QualifiedClassName, + val classNamePolicy: RealmNamingPolicy, + val fieldNamePolicy: RealmNamingPolicy) +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.java deleted file mode 100644 index f4190e2c63..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.processor; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.lang.model.element.VariableElement; - -/** - * Helper class for creating the correct method calls to the OsObjectBuilder class. - */ -public class OsObjectBuilderTypeHelper { - - private static final Map QUALIFIED_TYPE_TO_BUILDER; - private static final Map QUALIFIED_LIST_TYPE_TO_BUILDER; - - static { - // Map of qualified types to their OsObjectBuilder Type - Map fieldTypes = new HashMap<>(); - fieldTypes.put("byte", "Integer"); - fieldTypes.put("short", "Integer"); - fieldTypes.put("int", "Integer"); - fieldTypes.put("long", "Integer"); - fieldTypes.put("float", "Float"); - fieldTypes.put("double", "Double"); - fieldTypes.put("boolean", "Boolean"); - fieldTypes.put("byte[]", "ByteArray"); - fieldTypes.put("java.lang.Byte", "Integer"); - fieldTypes.put("java.lang.Short", "Integer"); - fieldTypes.put("java.lang.Integer", "Integer"); - fieldTypes.put("java.lang.Long", "Integer"); - fieldTypes.put("java.lang.Float", "Float"); - fieldTypes.put("java.lang.Double", "Double"); - fieldTypes.put("java.lang.Boolean", "Boolean"); - fieldTypes.put("java.lang.String", "String"); - fieldTypes.put("java.util.Date", "Date"); - fieldTypes.put("io.realm.MutableRealmInteger", "MutableRealmInteger"); - QUALIFIED_TYPE_TO_BUILDER = Collections.unmodifiableMap(fieldTypes); - - // Map of qualified types to their OsObjectBuilder Type - Map listTypes = new HashMap<>(); - listTypes.put("byte[]", "ByteArrayList"); - listTypes.put("java.lang.Byte", "ByteList"); - listTypes.put("java.lang.Short", "ShortList"); - listTypes.put("java.lang.Integer", "IntegerList"); - listTypes.put("java.lang.Long", "LongList"); - listTypes.put("java.lang.Float", "FloatList"); - listTypes.put("java.lang.Double", "DoubleList"); - listTypes.put("java.lang.Boolean", "BooleanList"); - listTypes.put("java.lang.String", "StringList"); - listTypes.put("java.util.Date", "DateList"); - listTypes.put("io.realm.MutableRealmInteger", "MutableRealmIntegerList"); - QUALIFIED_LIST_TYPE_TO_BUILDER = Collections.unmodifiableMap(listTypes); - } - - /** - * Returns the method name used by the OsObjectBuilder for the given type, e.g. `addInteger` - * or `addIntegerList`. - */ - public static String getOsObjectBuilderName(VariableElement field) { - if (Utils.isRealmModel(field)) { - return "addObject"; - } else if (Utils.isRealmModelList(field)) { - return "addObjectList"; - } else if (Utils.isRealmValueList(field)) { - return "add" + getListTypeName(Utils.getRealmListType(field)); - } else if (Utils.isRealmResults(field)) { - throw new IllegalStateException("RealmResults are not supported by OsObjectBuilder: " + field); - } else { - return "add" + getBasicTypeName(Utils.getFieldTypeQualifiedName(field)); - } - } - - private static String getBasicTypeName(String qualifiedType) { - String type = QUALIFIED_TYPE_TO_BUILDER.get(qualifiedType); - if (type != null) { - return type; - } - throw new IllegalArgumentException("Unsupported type: " + qualifiedType); - } - - private static String getListTypeName(String qualifiedType) { - String type = QUALIFIED_LIST_TYPE_TO_BUILDER.get(qualifiedType); - if (type != null) { - return type; - } - throw new IllegalArgumentException("Unsupported list type: " + qualifiedType); - } - -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt new file mode 100644 index 0000000000..6470087c91 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.processor + +import java.util.Collections +import java.util.HashMap + +import javax.lang.model.element.VariableElement + +/** + * Helper class for creating the correct method calls to the OsObjectBuilder class. + */ +object OsObjectBuilderTypeHelper { + + private val QUALIFIED_TYPE_TO_BUILDER: Map + private val QUALIFIED_LIST_TYPE_TO_BUILDER: Map + + init { + // Map of qualified types to their OsObjectBuilder Type + val fieldTypes = HashMap() + fieldTypes[QualifiedClassName("byte")] = "Integer" + fieldTypes[QualifiedClassName("short")] = "Integer" + fieldTypes[QualifiedClassName("int")] = "Integer" + fieldTypes[QualifiedClassName("long")] = "Integer" + fieldTypes[QualifiedClassName("float")] = "Float" + fieldTypes[QualifiedClassName("double")] = "Double" + fieldTypes[QualifiedClassName("boolean")] = "Boolean" + fieldTypes[QualifiedClassName("byte[]")] = "ByteArray" + fieldTypes[QualifiedClassName("java.lang.Byte")] = "Integer" + fieldTypes[QualifiedClassName("java.lang.Short")] = "Integer" + fieldTypes[QualifiedClassName("java.lang.Integer")] = "Integer" + fieldTypes[QualifiedClassName("java.lang.Long")] = "Integer" + fieldTypes[QualifiedClassName("java.lang.Float")] = "Float" + fieldTypes[QualifiedClassName("java.lang.Double")] = "Double" + fieldTypes[QualifiedClassName("java.lang.Boolean")] = "Boolean" + fieldTypes[QualifiedClassName("java.lang.String")] = "String" + fieldTypes[QualifiedClassName("java.util.Date")] = "Date" + fieldTypes[QualifiedClassName("io.realm.MutableRealmInteger")] = "MutableRealmInteger" + QUALIFIED_TYPE_TO_BUILDER = Collections.unmodifiableMap(fieldTypes) + + // Map of qualified types to their OsObjectBuilder Type + val listTypes = HashMap() + listTypes[QualifiedClassName("byte[]")] = "ByteArrayList" + listTypes[QualifiedClassName("java.lang.Byte")] = "ByteList" + listTypes[QualifiedClassName("java.lang.Short")] = "ShortList" + listTypes[QualifiedClassName("java.lang.Integer")] = "IntegerList" + listTypes[QualifiedClassName("java.lang.Long")] = "LongList" + listTypes[QualifiedClassName("java.lang.Float")] = "FloatList" + listTypes[QualifiedClassName("java.lang.Double")] = "DoubleList" + listTypes[QualifiedClassName("java.lang.Boolean")] = "BooleanList" + listTypes[QualifiedClassName("java.lang.String")] = "StringList" + listTypes[QualifiedClassName("java.util.Date")] = "DateList" + listTypes[QualifiedClassName("io.realm.MutableRealmInteger")] = "MutableRealmIntegerList" + QUALIFIED_LIST_TYPE_TO_BUILDER = Collections.unmodifiableMap(listTypes) + } + + /** + * Returns the method name used by the OsObjectBuilder for the given type, e.g. `addInteger` + * or `addIntegerList`. + */ + fun getOsObjectBuilderName(field: VariableElement): String { + return if (Utils.isRealmModel(field)) { + "addObject" + } else if (Utils.isRealmModelList(field)) { + "addObjectList" + } else if (Utils.isRealmValueList(field)) { + "add" + getListTypeName(Utils.getRealmListType(field)) + } else if (Utils.isRealmResults(field)) { + throw IllegalStateException("RealmResults are not supported by OsObjectBuilder: $field") + } else { + "add" + getBasicTypeName(Utils.getFieldTypeQualifiedName(field)) + } + } + + private fun getBasicTypeName(qualifiedType: QualifiedClassName): String { + val type = QUALIFIED_TYPE_TO_BUILDER[qualifiedType] + if (type != null) { + return type + } + throw IllegalArgumentException("Unsupported type: $qualifiedType") + } + + private fun getListTypeName(typeName: QualifiedClassName?): String { + val type = QUALIFIED_LIST_TYPE_TO_BUILDER[typeName] + if (type != null) { + return type + } + throw IllegalArgumentException("Unsupported list type: $type") + } + +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.java deleted file mode 100644 index 578766bc81..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.processor; - -import java.lang.annotation.Annotation; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ElementVisitor; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - -/** - * Wrapper for {@link javax.lang.model.element.VariableElement} that makes it possible to add - * additional metadata. - */ -public class RealmFieldElement implements VariableElement { - - private final VariableElement fieldReference; - private final String internalFieldName; // Name used for this field internally in Realm. - - public RealmFieldElement(VariableElement fieldReference, String internalFieldName) { - this.fieldReference = fieldReference; - this.internalFieldName = internalFieldName; - } - - public VariableElement getFieldReference() { - return fieldReference; - } - - /** - * Returns the name that Realm Core uses internally when saving data to this field. - * {@link #getSimpleName()} returns the name in the Java class. - */ - public String getInternalFieldName() { - return internalFieldName; - } - - public Set getModifiers() { - return fieldReference.getModifiers(); - } - - public TypeMirror asType() { - return fieldReference.asType(); - } - - @Override - public ElementKind getKind() { - return null; - } - - @Override - public Object getConstantValue() { - return fieldReference.getConstantValue(); - } - - /** - * Returns the name for this field in the Java class. - * {@link #getInternalFieldName()} returns the name used by Realm Core for the same field. - */ - @Override - public Name getSimpleName() { - return fieldReference.getSimpleName(); - } - - @Override - public Element getEnclosingElement() { - return fieldReference.getEnclosingElement(); - } - - @Override - public List getEnclosedElements() { - return fieldReference.getEnclosedElements(); - } - - @Override - public List getAnnotationMirrors() { - return fieldReference.getAnnotationMirrors(); - } - - @Override - public A getAnnotation(Class aClass) { - return fieldReference.getAnnotation(aClass); - } - - @Override - public A[] getAnnotationsByType(Class aClass) { - return fieldReference.getAnnotationsByType(aClass); - } - - @Override - public R accept(ElementVisitor elementVisitor, P p) { - return fieldReference.accept(elementVisitor, p); - } - - @Override - public String toString() { - // Mimics the behaviour of the standard implementation of VariableElement `toString()` - // Some methods in RealmProxyClassGenerator depended on this. - return getSimpleName().toString(); - } - - public String getJavaName() { - return getSimpleName().toString(); - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.kt new file mode 100644 index 0000000000..87ee7f7a32 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmFieldElement.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.processor + +import javax.lang.model.element.AnnotationMirror +import javax.lang.model.element.Element +import javax.lang.model.element.ElementKind +import javax.lang.model.element.ElementVisitor +import javax.lang.model.element.Modifier +import javax.lang.model.element.Name +import javax.lang.model.element.VariableElement +import javax.lang.model.type.TypeMirror + +/** + * Wrapper for [javax.lang.model.element.VariableElement] that makes it possible to add + * additional metadata. + */ +class RealmFieldElement(val fieldReference: VariableElement, + /** + * Returns the name that Realm Core uses internally when saving data to this field. + * [RealmFieldElement.getSimpleName] returns the name in the Java class. + */ + val internalFieldName: String // Name used for this field internally in Realm. +) : VariableElement { + + val javaName: String + get() = simpleName.toString() + + override fun getModifiers(): Set { + return fieldReference.modifiers + } + + override fun asType(): TypeMirror { + return fieldReference.asType() + } + + override fun getKind(): ElementKind? { + return null + } + + override fun getConstantValue(): Any { + return fieldReference.constantValue + } + + /** + * Returns the name for this field in the Java class. + * [RealmFieldElement.internalFieldName] returns the name used by Realm Core for the same field. + */ + override fun getSimpleName(): Name { + return fieldReference.simpleName + } + + override fun getEnclosingElement(): Element { + return fieldReference.enclosingElement + } + + override fun getEnclosedElements(): List { + return fieldReference.enclosedElements + } + + override fun getAnnotationMirrors(): List { + return fieldReference.annotationMirrors + } + + override fun getAnnotation(aClass: Class): A? { + return fieldReference.getAnnotation(aClass) + } + + override fun getAnnotationsByType(aClass: Class): Array { + return fieldReference.getAnnotationsByType(aClass) + } + + override fun accept(elementVisitor: ElementVisitor, p: P): R { + return fieldReference.accept(elementVisitor, p) + } + + override fun toString(): String { + // Mimics the behaviour of the standard implementation of VariableElement `toString()` + // Some methods in RealmProxyClassGenerator depended on this. + return simpleName.toString() + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java deleted file mode 100644 index dcaa1fd63b..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import com.squareup.javawriter.JavaWriter; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - - -/** - * Helper class for converting between Json types and data types in Java that are supported by Realm. - */ -public class RealmJsonTypeHelper { - private static final Map JAVA_TO_JSON_TYPES; - - static { - Map m = new HashMap(); - m.put("byte", new SimpleTypeConverter("byte", "Int")); - m.put("short", new SimpleTypeConverter("short", "Int")); - m.put("int", new SimpleTypeConverter("int", "Int")); - m.put("long", new SimpleTypeConverter("long", "Long")); - m.put("float", new SimpleTypeConverter("float", "Double")); - m.put("double", new SimpleTypeConverter("double", "Double")); - m.put("boolean", new SimpleTypeConverter("boolean", "Boolean")); - m.put("byte[]", new ByteArrayTypeConverter()); - m.put("java.lang.Byte", m.get("byte")); - m.put("java.lang.Short", m.get("short")); - m.put("java.lang.Integer", m.get("int")); - m.put("java.lang.Long", m.get("long")); - m.put("java.lang.Float", m.get("float")); - m.put("java.lang.Double", m.get("double")); - m.put("java.lang.Boolean", m.get("boolean")); - m.put("java.lang.String", new SimpleTypeConverter("String", "String")); - m.put("java.util.Date", new DateTypeConverter()); - m.put("io.realm.MutableRealmInteger", new MutableRealmIntegerTypeConverter()); - JAVA_TO_JSON_TYPES = Collections.unmodifiableMap(m); - } - - // Static helper class - private RealmJsonTypeHelper() { } - - // @formatter:off - public static void emitIllegalJsonValueException(String fieldType, String fieldName, JavaWriter writer) - throws IOException { - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD, fieldType, fieldName) - .endControlFlow(); - } - // @formatter:on - - public static void emitCreateObjectWithPrimaryKeyValue( - String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String qualifiedFieldType, String fieldName, JavaWriter writer) - throws IOException { - JsonToRealmFieldTypeConverter typeEmitter = JAVA_TO_JSON_TYPES.get(qualifiedFieldType); - if (typeEmitter != null) { - typeEmitter.emitGetObjectWithPrimaryKeyValue( - qualifiedRealmObjectClass, qualifiedRealmObjectProxyClass, fieldName, writer); - } - } - - // @formatter:off - public static void emitFillRealmObjectWithJsonValue( - String varName, String setter, String fieldName, String qualifiedFieldType, String proxyClass, JavaWriter writer) - throws IOException { - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - .emitStatement("%s.%s(null)", varName, setter) - .nextControlFlow("else") - .emitStatement( - "%s %sObj = %s.createOrUpdateUsingJsonObject(realm, json.getJSONObject(\"%s\"), update)", - qualifiedFieldType, fieldName, proxyClass, fieldName) - .emitStatement("%s.%s(%sObj)", varName, setter, fieldName) - .endControlFlow() - .endControlFlow(); - } - // @formatter:on - - // @formatter:off - public static void emitFillRealmListWithJsonValue( - String varName, String getter, String setter, String fieldName, String fieldTypeCanonicalName, String proxyClass, JavaWriter writer) - throws IOException { - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - .emitStatement("%s.%s(null)", varName, setter) - .nextControlFlow("else") - .emitStatement("%s.%s().clear()", varName, getter) - .emitStatement("JSONArray array = json.getJSONArray(\"%s\")", fieldName) - .beginControlFlow("for (int i = 0; i < array.length(); i++)") - .emitStatement( - "%s item = %s.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update)", - fieldTypeCanonicalName, proxyClass, fieldTypeCanonicalName) - .emitStatement("%s.%s().add(item)", varName, getter) - .endControlFlow() - .endControlFlow() - .endControlFlow(); - } - // @formatter:on - - public static void emitFillJavaTypeWithJsonValue( - String varName, String accessor, String fieldName, String qualifiedFieldType, JavaWriter writer) - throws IOException { - JsonToRealmFieldTypeConverter typeEmitter = JAVA_TO_JSON_TYPES.get(qualifiedFieldType); - if (typeEmitter != null) { - typeEmitter.emitTypeConversion(varName, accessor, fieldName, qualifiedFieldType, writer); - } - } - - // @formatter:off - public static void emitFillRealmObjectFromStream( - String varName, String setter, String fieldName, String fieldTypeCanonicalName, String proxyClass, JavaWriter writer) - throws IOException { - writer - .beginControlFlow("if (reader.peek() == JsonToken.NULL)") - .emitStatement("reader.skipValue()") - .emitStatement("%s.%s(null)", varName, setter) - .nextControlFlow("else") - .emitStatement( - "%s %sObj = %s.createUsingJsonStream(realm, reader)", - fieldTypeCanonicalName, fieldName, proxyClass) - .emitStatement("%s.%s(%sObj)", varName, setter, fieldName) - .endControlFlow(); - } - // @formatter:on - - // @formatter:off - public static void emitFillRealmListFromStream( - String varName, String getter, String setter, String fieldTypeCanonicalName, String proxyClass, JavaWriter writer) - throws IOException { - writer - .beginControlFlow("if (reader.peek() == JsonToken.NULL)") - .emitStatement("reader.skipValue()") - .emitStatement("%s.%s(null)", varName, setter) - .nextControlFlow("else") - .emitStatement("%s.%s(new RealmList<%s>())", varName, setter, fieldTypeCanonicalName) - .emitStatement("reader.beginArray()") - .beginControlFlow("while (reader.hasNext())") - .emitStatement("%s item = %s.createUsingJsonStream(realm, reader)", fieldTypeCanonicalName, proxyClass) - .emitStatement("%s.%s().add(item)", varName, getter) - .endControlFlow() - .emitStatement("reader.endArray()") - .endControlFlow(); - } - // @formatter:on - - public static void emitFillJavaTypeFromStream( - String varName, ClassMetaData metaData, String accessor, String fieldName, String fieldType, JavaWriter writer) - throws IOException { - boolean isPrimaryKey = metaData.hasPrimaryKey() && metaData.getPrimaryKey().getSimpleName().toString().equals(fieldName); - JsonToRealmFieldTypeConverter typeEmitter = JAVA_TO_JSON_TYPES.get(fieldType); - if (typeEmitter != null) { - typeEmitter.emitStreamTypeConversion(varName, accessor, fieldName, fieldType, writer, isPrimaryKey); - } - } - - private static class SimpleTypeConverter implements JsonToRealmFieldTypeConverter { - private final String castType; - private final String jsonType; - - /** - * Creates a conversion between simple types which can be expressed as RealmObject.setFieldName(() - * json.get) or RealmObject.setFieldName(() reader.next - * - * @param castType Java type to cast to. - * @param jsonType JsonType to get data from. - */ - private SimpleTypeConverter(String castType, String jsonType) { - this.castType = castType; - this.jsonType = jsonType; - } - - @Override - public void emitTypeConversion( - String varName, String accessor, String fieldName, String fieldType, JavaWriter writer) - throws IOException { - // Only throw exception for primitive types. - // For boxed types and String, exception will be thrown in the setter. - String statementSetNullOrThrow = Utils.isPrimitiveType(fieldType) ? - String.format(Locale.US, Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) : - String.format(Locale.US, "%s.%s(null)", varName, accessor); - - // @formatter:off - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - .emitStatement(statementSetNullOrThrow) - .nextControlFlow("else") - .emitStatement("%s.%s((%s) json.get%s(\"%s\"))", varName, accessor, castType, jsonType, fieldName) - .endControlFlow() - .endControlFlow(); - // @formatter:on - } - - @Override - public void emitStreamTypeConversion( - String varName, String setter, String fieldName, String fieldType, JavaWriter writer, boolean isPrimaryKey) - throws IOException { - // Only throw exception for primitive types. - // For boxed types and String, exception will be thrown in the setter. - String statementSetNullOrThrow = (Utils.isPrimitiveType(fieldType)) ? - String.format(Locale.US, Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) : - String.format(Locale.US, "%s.%s(null)", varName, setter); - - // @formatter:off - writer - .beginControlFlow("if (reader.peek() != JsonToken.NULL)") - .emitStatement("%s.%s((%s) reader.next%s())", varName, setter, castType, jsonType) - .nextControlFlow("else") - .emitStatement("reader.skipValue()") - .emitStatement(statementSetNullOrThrow) - .endControlFlow(); - // @formatter:on - - if (isPrimaryKey) { - writer.emitStatement("jsonHasPrimaryKey = true"); - } - } - - // @formatter:off - @Override - public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, - String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) throws IOException { - // No error checking is done here for valid primary key types. - // This should be done by the annotation processor. - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - .emitStatement("obj = (%1$s) realm.createObjectInternal(%2$s.class, null, true, excludeFields)", - qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass) - .nextControlFlow("else") - .emitStatement( - "obj = (%1$s) realm.createObjectInternal(%2$s.class, json.get%3$s(\"%4$s\"), true, excludeFields)", - qualifiedRealmObjectProxyClass, qualifiedRealmObjectClass, jsonType, fieldName) - .endControlFlow() - .nextControlFlow("else") - .emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) - .endControlFlow(); - } - // @formatter:on - } - - private static class ByteArrayTypeConverter implements JsonToRealmFieldTypeConverter { - // @formatter:off - @Override - public void emitTypeConversion(String varName, String accessor, String fieldName, String fieldType, JavaWriter writer) - throws IOException { - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - .emitStatement("%s.%s(null)", varName, accessor) - .nextControlFlow("else") - .emitStatement("%s.%s(JsonUtils.stringToBytes(json.getString(\"%s\")))", varName, accessor, fieldName) - .endControlFlow() - .endControlFlow(); - } - // @formatter:on - - // @formatter:off - @Override - public void emitStreamTypeConversion(String varName, String accessor, String fieldName, String fieldType, JavaWriter writer, boolean isPrimaryKey) - throws IOException { - writer - .beginControlFlow("if (reader.peek() != JsonToken.NULL)") - .emitStatement("%s.%s(JsonUtils.stringToBytes(reader.nextString()))", varName, accessor) - .nextControlFlow("else") - .emitStatement("reader.skipValue()") - .emitStatement("%s.%s(null)", varName, accessor) - .endControlFlow(); - } - // @formatter:on - - @Override - public void emitGetObjectWithPrimaryKeyValue( - String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) - throws IOException { - throw new IllegalArgumentException("'byte[]' is not allowed as a primary key value."); - } - } - - private static class DateTypeConverter implements JsonToRealmFieldTypeConverter { - // @formatter:off - @Override - public void emitTypeConversion( - String varName, String accessor, String fieldName, String fieldType, JavaWriter writer) - throws IOException { - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - .emitStatement("%s.%s(null)", varName, accessor) - .nextControlFlow("else") - .emitStatement("Object timestamp = json.get(\"%s\")", fieldName) - .beginControlFlow("if (timestamp instanceof String)") - .emitStatement("%s.%s(JsonUtils.stringToDate((String) timestamp))", varName, accessor) - .nextControlFlow("else") - .emitStatement("%s.%s(new Date(json.getLong(\"%s\")))", varName, accessor, fieldName) - .endControlFlow() - .endControlFlow() - .endControlFlow(); - } - // @formatter:on - - // @formatter:off - @Override - public void emitStreamTypeConversion( - String varName, String accessor, String fieldName, String fieldType, JavaWriter writer, boolean isPrimaryKey) - throws IOException { - writer - .beginControlFlow("if (reader.peek() == JsonToken.NULL)") - .emitStatement("reader.skipValue()") - .emitStatement("%s.%s(null)", varName, accessor) - .nextControlFlow("else if (reader.peek() == JsonToken.NUMBER)") - .emitStatement("long timestamp = reader.nextLong()", fieldName) - .beginControlFlow("if (timestamp > -1)") - .emitStatement("%s.%s(new Date(timestamp))", varName, accessor) - .endControlFlow() - .nextControlFlow("else") - .emitStatement("%s.%s(JsonUtils.stringToDate(reader.nextString()))", varName, accessor) - .endControlFlow(); - } - // @formatter:on - - @Override - public void emitGetObjectWithPrimaryKeyValue( - String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) - throws IOException { - throw new IllegalArgumentException("'Date' is not allowed as a primary key value."); - } - } - - private static class MutableRealmIntegerTypeConverter implements JsonToRealmFieldTypeConverter { - // @formatter:off - @Override - public void emitTypeConversion(String varName, String accessor, String fieldName, String fieldType, JavaWriter writer) - throws IOException { - writer - .beginControlFlow("if (json.has(\"%s\"))", fieldName) - .emitStatement("%1$s.%2$s().set((json.isNull(\"%3$s\")) ? null : json.getLong(\"%3$s\"))", varName, accessor, fieldName) - .endControlFlow(); - } - // @formatter:on - - // @formatter:off - @Override - public void emitStreamTypeConversion(String varName, String accessor, String fieldName, String fieldType, JavaWriter writer, boolean isPrimaryKey) - throws IOException { - writer - .emitStatement("Long val = null") - .beginControlFlow("if (reader.peek() != JsonToken.NULL)") - .emitStatement("val = reader.nextLong()") - .nextControlFlow("else") - .emitStatement("reader.skipValue()") - .endControlFlow() - .emitStatement("%1$s.%2$s().set(val)", varName, accessor); - } - // @formatter:on - - @Override - public void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) - throws IOException { - throw new IllegalArgumentException("'MutableRealmInteger' is not allowed as a primary key value."); - } - } - - private interface JsonToRealmFieldTypeConverter { - void emitTypeConversion(String varName, String accessor, String fieldName, String fieldType, JavaWriter writer) - throws IOException; - - void emitStreamTypeConversion(String varName, String accessor, String fieldName, String fieldType, JavaWriter writer, boolean isPrimaryKey) - throws IOException; - - void emitGetObjectWithPrimaryKeyValue(String qualifiedRealmObjectClass, String qualifiedRealmObjectProxyClass, String fieldName, JavaWriter writer) - throws IOException; - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt new file mode 100644 index 0000000000..3bdebfa9a9 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt @@ -0,0 +1,372 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import com.squareup.javawriter.JavaWriter + +import java.io.IOException +import java.util.Collections +import java.util.HashMap +import java.util.Locale + + +/** + * Helper class for converting between Json types and data types in Java that are supported by Realm. + */ +object RealmJsonTypeHelper { + private val JAVA_TO_JSON_TYPES: Map + + init { + val m = HashMap() + m[QualifiedClassName("byte")] = SimpleTypeConverter("byte", "Int") + m[QualifiedClassName("short")] = SimpleTypeConverter("short", "Int") + m[QualifiedClassName("int")] = SimpleTypeConverter("int", "Int") + m[QualifiedClassName("long")] = SimpleTypeConverter("long", "Long") + m[QualifiedClassName("float")] = SimpleTypeConverter("float", "Double") + m[QualifiedClassName("double")] = SimpleTypeConverter("double", "Double") + m[QualifiedClassName("boolean")] = SimpleTypeConverter("boolean", "Boolean") + m[QualifiedClassName("byte[]")] = ByteArrayTypeConverter() + m[QualifiedClassName("java.lang.Byte")] = m[QualifiedClassName("byte")] as JsonToRealmFieldTypeConverter + m[QualifiedClassName("java.lang.Short")] = m[QualifiedClassName("short")] as JsonToRealmFieldTypeConverter + m[QualifiedClassName("java.lang.Integer")] = m[QualifiedClassName("int")] as JsonToRealmFieldTypeConverter + m[QualifiedClassName("java.lang.Long")] = m[QualifiedClassName("long")] as JsonToRealmFieldTypeConverter + m[QualifiedClassName("java.lang.Float")] = m[QualifiedClassName("float")] as JsonToRealmFieldTypeConverter + m[QualifiedClassName("java.lang.Double")] = m[QualifiedClassName("double")] as JsonToRealmFieldTypeConverter + m[QualifiedClassName("java.lang.Boolean")] = m[QualifiedClassName("boolean")] as JsonToRealmFieldTypeConverter + m[QualifiedClassName("java.lang.String")] = SimpleTypeConverter("String", "String") + m[QualifiedClassName("java.util.Date")] = DateTypeConverter() + m[QualifiedClassName("io.realm.MutableRealmInteger")] = MutableRealmIntegerTypeConverter() + JAVA_TO_JSON_TYPES = Collections.unmodifiableMap(m) + } + + @Throws(IOException::class) + fun emitIllegalJsonValueException(fieldType: String, fieldName: String, writer: JavaWriter) { + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD, fieldType, fieldName) + endControlFlow() + } + } + + @Throws(IOException::class) + fun emitCreateObjectWithPrimaryKeyValue(realmObjectClass: QualifiedClassName, + realmObjectProxyClass: QualifiedClassName, + fieldType: QualifiedClassName, + fieldName: String, + writer: JavaWriter) { + val typeEmitter = JAVA_TO_JSON_TYPES[fieldType] + typeEmitter?.emitGetObjectWithPrimaryKeyValue(realmObjectClass, realmObjectProxyClass, fieldName, writer) + } + + @Throws(IOException::class) + fun emitFillRealmObjectWithJsonValue(varName: String, + setter: String, + fieldName: String, + qualifiedFieldType: QualifiedClassName, + proxyClass: SimpleClassName, + writer: JavaWriter) { + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("%s.%s(null)", varName, setter) + nextControlFlow("else") + emitStatement("%s %sObj = %s.createOrUpdateUsingJsonObject(realm, json.getJSONObject(\"%s\"), update)", qualifiedFieldType, fieldName, proxyClass, fieldName) + emitStatement("%s.%s(%sObj)", varName, setter, fieldName) + endControlFlow() + endControlFlow() + } + } + + @Throws(IOException::class) + fun emitFillRealmListWithJsonValue(varName: String, + getter: String, + setter: String, + fieldName: String, + fieldTypeCanonicalName: String, + proxyClass: SimpleClassName, + writer: JavaWriter) { + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("%s.%s(null)", varName, setter) + nextControlFlow("else") + emitStatement("%s.%s().clear()", varName, getter) + emitStatement("JSONArray array = json.getJSONArray(\"%s\")", fieldName) + beginControlFlow("for (int i = 0; i < array.length(); i++)") + emitStatement("%s item = %s.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update)", fieldTypeCanonicalName, proxyClass, fieldTypeCanonicalName) + emitStatement("%s.%s().add(item)", varName, getter) + endControlFlow() + endControlFlow() + endControlFlow() + } + } + + @Throws(IOException::class) + fun emitFillJavaTypeWithJsonValue(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { + val typeEmitter = JAVA_TO_JSON_TYPES[fieldType] + typeEmitter?.emitTypeConversion(varName, accessor, fieldName, fieldType, writer) + } + + @Throws(IOException::class) + fun emitFillRealmObjectFromStream(varName: String, + setter: String, + fieldName: String, + fieldType: QualifiedClassName, + proxyClass: SimpleClassName, + writer: JavaWriter) { + writer.apply { + beginControlFlow("if (reader.peek() == JsonToken.NULL)") + emitStatement("reader.skipValue()") + emitStatement("%s.%s(null)", varName, setter) + nextControlFlow("else") + emitStatement("%s %sObj = %s.createUsingJsonStream(realm, reader)", fieldType, fieldName, proxyClass) + emitStatement("%s.%s(%sObj)", varName, setter, fieldName) + endControlFlow() + } + } + + @Throws(IOException::class) + fun emitFillRealmListFromStream(varName: String, + getter: String, + setter: String, + fieldType: QualifiedClassName, + proxyClass: SimpleClassName, + writer: JavaWriter) { + writer.apply { + beginControlFlow("if (reader.peek() == JsonToken.NULL)") + emitStatement("reader.skipValue()") + emitStatement("%s.%s(null)", varName, setter) + nextControlFlow("else") + emitStatement("%s.%s(new RealmList<%s>())", varName, setter, fieldType) + emitStatement("reader.beginArray()") + beginControlFlow("while (reader.hasNext())") + emitStatement("%s item = %s.createUsingJsonStream(realm, reader)", fieldType, proxyClass) + emitStatement("%s.%s().add(item)", varName, getter) + endControlFlow() + emitStatement("reader.endArray()") + endControlFlow() + } + } + + @Throws(IOException::class) + fun emitFillJavaTypeFromStream(varName: String, + metaData: ClassMetaData, + accessor: String, + fieldName: String, + fieldType: QualifiedClassName, + writer: JavaWriter) { + val isPrimaryKey = metaData.hasPrimaryKey() && metaData.primaryKey!!.simpleName.toString() == fieldName + val typeEmitter = JAVA_TO_JSON_TYPES[fieldType] + typeEmitter?.emitStreamTypeConversion(varName, accessor, fieldName, fieldType, writer, isPrimaryKey) + } + + /** + * Creates a conversion between simple types which can be expressed as RealmObject.setFieldName(() + * json.get) or RealmObject.setFieldName(() reader.next + * + * @param castType Java type to cast to. + * @param jsonType JsonType to get data from. + */ + private class SimpleTypeConverter(private val castType: String, private val jsonType: String) : JsonToRealmFieldTypeConverter { + + @Throws(IOException::class) + override fun emitTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { + // Only throw exception for primitive types. + // For boxed types and String, exception will be thrown in the setter. + val statementSetNullOrThrow = if (Utils.isPrimitiveType(fieldType)) + String.format(Locale.US, Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + else + String.format(Locale.US, "%s.%s(null)", varName, accessor) + + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement(statementSetNullOrThrow) + nextControlFlow("else") + emitStatement("%s.%s((%s) json.get%s(\"%s\"))", varName, accessor, castType, jsonType, fieldName) + endControlFlow() + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitStreamTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter, isPrimaryKey: Boolean) { + // Only throw exception for primitive types. + // For boxed types and String, exception will be thrown in the setter. + val statementSetNullOrThrow = if (Utils.isPrimitiveType(fieldType)) + String.format(Locale.US, Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + else + String.format(Locale.US, "%s.%s(null)", varName, accessor) + + writer.apply { + beginControlFlow("if (reader.peek() != JsonToken.NULL)") + emitStatement("%s.%s((%s) reader.next%s())", varName, accessor, castType, jsonType) + nextControlFlow("else") + emitStatement("reader.skipValue()") + emitStatement(statementSetNullOrThrow) + endControlFlow() + + if (isPrimaryKey) { + emitStatement("jsonHasPrimaryKey = true") + } + } + } + + @Throws(IOException::class) + override fun emitGetObjectWithPrimaryKeyValue(realmObjectClass: QualifiedClassName, realmObjectProxyClass: QualifiedClassName, fieldName: String, writer: JavaWriter) { + // No error checking is done here for valid primary key types. + // This should be done by the annotation processor. + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, null, true, excludeFields)", realmObjectProxyClass, realmObjectClass) + nextControlFlow("else") + emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, json.get%3\$s(\"%4\$s\"), true, excludeFields)", realmObjectProxyClass, realmObjectClass, jsonType, fieldName) + endControlFlow() + nextControlFlow("else") + emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) + endControlFlow() + } + } + } + + private class ByteArrayTypeConverter : JsonToRealmFieldTypeConverter { + @Throws(IOException::class) + override fun emitTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("%s.%s(null)", varName, accessor) + nextControlFlow("else") + emitStatement("%s.%s(JsonUtils.stringToBytes(json.getString(\"%s\")))", varName, accessor, fieldName) + endControlFlow() + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitStreamTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter, isPrimaryKey: Boolean) { + writer.apply { + beginControlFlow("if (reader.peek() != JsonToken.NULL)") + emitStatement("%s.%s(JsonUtils.stringToBytes(reader.nextString()))", varName, accessor) + nextControlFlow("else") + emitStatement("reader.skipValue()") + emitStatement("%s.%s(null)", varName, accessor) + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitGetObjectWithPrimaryKeyValue(realmObjectClass: QualifiedClassName, realmObjectProxyClass: QualifiedClassName, fieldName: String, writer: JavaWriter) { + throw IllegalArgumentException("'byte[]' is not allowed as a primary key value.") + } + } + + private class DateTypeConverter : JsonToRealmFieldTypeConverter { + @Throws(IOException::class) + override fun emitTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("%s.%s(null)", varName, accessor) + nextControlFlow("else") + emitStatement("Object timestamp = json.get(\"%s\")", fieldName) + beginControlFlow("if (timestamp instanceof String)") + emitStatement("%s.%s(JsonUtils.stringToDate((String) timestamp))", varName, accessor) + nextControlFlow("else") + emitStatement("%s.%s(new Date(json.getLong(\"%s\")))", varName, accessor, fieldName) + endControlFlow() + endControlFlow() + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitStreamTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter, isPrimaryKey: Boolean) { + writer.apply { + beginControlFlow("if (reader.peek() == JsonToken.NULL)") + emitStatement("reader.skipValue()") + emitStatement("%s.%s(null)", varName, accessor) + nextControlFlow("else if (reader.peek() == JsonToken.NUMBER)") + emitStatement("long timestamp = reader.nextLong()", fieldName) + beginControlFlow("if (timestamp > -1)") + emitStatement("%s.%s(new Date(timestamp))", varName, accessor) + endControlFlow() + nextControlFlow("else") + emitStatement("%s.%s(JsonUtils.stringToDate(reader.nextString()))", varName, accessor) + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitGetObjectWithPrimaryKeyValue(realmObjectClass: QualifiedClassName, realmObjectProxyClass: QualifiedClassName, fieldName: String, writer: JavaWriter) { + throw IllegalArgumentException("'Date' is not allowed as a primary key value.") + } + } + + private class MutableRealmIntegerTypeConverter : JsonToRealmFieldTypeConverter { + @Throws(IOException::class) + override fun emitTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + emitStatement("%1\$s.%2\$s().set((json.isNull(\"%3\$s\")) ? null : json.getLong(\"%3\$s\"))", varName, accessor, fieldName) + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitStreamTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter, isPrimaryKey: Boolean) { + writer.apply { + emitStatement("Long val = null") + beginControlFlow("if (reader.peek() != JsonToken.NULL)") + emitStatement("val = reader.nextLong()") + nextControlFlow("else") + emitStatement("reader.skipValue()") + endControlFlow() + emitStatement("%1\$s.%2\$s().set(val)", varName, accessor) + } + } + + @Throws(IOException::class) + override fun emitGetObjectWithPrimaryKeyValue(realmObjectClass: QualifiedClassName, realmObjectProxyClass: QualifiedClassName, fieldName: String, writer: JavaWriter) { + throw IllegalArgumentException("'MutableRealmInteger' is not allowed as a primary key value.") + } + } + + private interface JsonToRealmFieldTypeConverter { + @Throws(IOException::class) + fun emitTypeConversion(varName: String, + accessor: String, + fieldName: String, + fieldType: QualifiedClassName, + writer: JavaWriter) + + @Throws(IOException::class) + fun emitStreamTypeConversion(varName: String, + accessor: String, + fieldName: String, + fieldType: QualifiedClassName, + writer: JavaWriter, + isPrimaryKey: Boolean) + + @Throws(IOException::class) + fun emitGetObjectWithPrimaryKeyValue(realmObjectClass: QualifiedClassName, + realmObjectProxyClass: QualifiedClassName, + fieldName: String, + writer: JavaWriter) + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java deleted file mode 100644 index 9edeea9902..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedOptions; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; - -import io.realm.annotations.RealmClass; -import io.realm.annotations.RealmModule; - - -/** - * The RealmProcessor is responsible for creating the plumbing that connects the RealmObjects to a Realm. The process - * for doing so is summarized below and then described in more detail. - *

            - *

            - *

            DESIGN GOALS

            - *

            - * The processor should support the following design goals: - *

              - *
            • Minimize reflection.
            • - *
            • Realm code can be obfuscated as much as possible.
            • - *
            • Library projects must be able to use Realm without interfering with app code.
            • - *
            • App code must be able to use RealmObject classes provided by library code.
            • - *
            • It should work for app developers out of the box (ie. put the burden on the library developer)
            • - *
            - *

            - *

            SUMMARY

            - *

            - *

              - *
            1. Create proxy classes for all classes marked with @RealmClass. They are named <className>RealmProxy.java
            2. - *
            3. Create a DefaultRealmModule containing all RealmObject classes (if needed).
            4. - *
            5. Create a RealmProxyMediator class for all classes marked with {@code @RealmModule}. They are named {@code Mediator.java}
            6. - *
            - *

            - *

            WHY

            - *

            - *

              - *
            1. A RealmObjectProxy object is created for each class annotated with {@link io.realm.annotations.RealmClass}. This - * proxy extends the original RealmObject class and rewires all field access to point to the native Realm memory instead of - * Java memory. It also adds some static helper methods to the class.
            2. - *
            3. The annotation processor is either in "library" mode or in "app" mode. This is defined by having a class - * annotated with @RealmModule(library = true). It is not allowed to have both a class with library = true and - * library = false in the same IntelliJ module and it will cause the annotation processor to throw an exception. If no - * library modules are defined, we will create a DefaultRealmModule containing all known RealmObjects and with the - * {@code @RealmModule} annotation. Realm automatically knows about this module, but it is still possible for users to create - * their own modules with a subset of model classes.
            4. - *
            5. For each class annotated with @RealmModule a matching Mediator class is created (including the default one). This - * class has an interface that matches the static helper methods for the proxy classes. All access to these static - * helper methods should be done through this Mediator.
            6. - *
            - *

            - * This allows ProGuard to obfuscate all RealmObject and proxy classes as all access to the static methods now happens through - * the Mediator, and the only requirement is now that only RealmModule and Mediator class names cannot be obfuscated. - *

            - *

            - *

            CREATING A REALM

            - *

            - * This means the workflow when instantiating a Realm on runtime is the following: - *

            - *

              - *
            1. Open a Realm.
            2. - *
            3. Assign one or more modules (that are allowed to overlap). If no module is assigned, the default module is used.
            4. - *
            5. The Realm schema is now defined as all RealmObject classes known by these modules.
            6. - *
            7. Each time a static helper method is needed, Realm can now delegate these method calls to the appropriate - * Mediator which in turn will delegate the method call to the appropriate RealmObjectProxy class.
            8. - *
            - *

            - *

            CREATING A MANAGED RealmObject

            - *

            - * To allow to specify default values by model's constructor or direct field assignment, - * the flow of creating the proxy object is a bit complicated. This section illustrates - * how proxy object should be created. - *

            - *

              - *
            1. Get the thread local {@code io.realm.BaseRealm.RealmObjectContext} instance by {@code BaseRealm.objectContext.get()}
            2. - *
            3. Set the object context information to the {@code RealmObjectContext} those should be set to the creating proxy object.
            4. - *
            5. Create proxy object ({@code new io.realm.FooRealmProxy()}).
            6. - *
            7. Set the object context information to the created proxy when the first access of its accessors (or in its constructor if accessors are not used in the model's constructor).
            8. - *
            9. Clear the object context information in the thread local {@code io.realm.BaseRealm.RealmObjectContext} instance by calling {@code - * #clear()} method.
            10. - *
            - *

            - * The reason of this complicated step is that we can't pass these context information - * via the constructor of the proxy. It's because the constructor of the proxy is executed - * after the constructor of the model class. The access to the fields in the model's - * constructor happens before the assignment of the context information to the 'proxyState'. - * This will cause the {@link NullPointerException} if getters/setter is accessed in the model's - * constructor (see https://github.com/realm/realm-java/issues/2536 ). - */ -@SupportedAnnotationTypes({ - "io.realm.annotations.RealmClass", - "io.realm.annotations.RealmField", - "io.realm.annotations.Ignore", - "io.realm.annotations.Index", - "io.realm.annotations.PrimaryKey", - "io.realm.annotations.RealmModule", - "io.realm.annotations.Required" -}) -@SupportedOptions(value = {"realm.suppressWarnings", "realm.ignoreKotlinNullability"}) -public class RealmProcessor extends AbstractProcessor { - - // Don't consume annotations. This allows 3rd party annotation processors to run. - private static final boolean CONSUME_ANNOTATIONS = false; - private static final boolean ABORT = true; // Abort the annotation processor by consuming all annotations - - private final ClassCollection classCollection = new ClassCollection(); // Metadata for all classes found - private ModuleMetaData moduleMetaData; // Metadata for all modules found - - // List of backlinks - private final Set backlinksToValidate = new HashSet(); - - private boolean hasProcessedModules = false; - private int round = -1; - - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - round++; - - if (round == 0) { - RealmVersionChecker.getInstance(processingEnv).executeRealmVersionUpdate(); - } - - if (roundEnv.errorRaised()) { return ABORT; } - - if (!hasProcessedModules) { - Utils.initialize(processingEnv); - TypeMirrors typeMirrors = new TypeMirrors(processingEnv); - - // Build up internal metadata while validating as much as possible - if (!preProcessModules(roundEnv)) { return ABORT; } - if (!processClassAnnotations(roundEnv, typeMirrors)) { return ABORT; } - if (!postProcessModules()) { return ABORT; } - if (!validateBacklinks()) { return ABORT; } - hasProcessedModules = true; - - // Create all files - if (!createProxyClassFiles(typeMirrors)) { return ABORT; } - if (!createModuleFiles(roundEnv)) { return ABORT; } - } - - return CONSUME_ANNOTATIONS; - } - - // Create all proxy classes - private boolean processClassAnnotations(RoundEnvironment roundEnv, TypeMirrors typeMirrors) { - - for (Element classElement : roundEnv.getElementsAnnotatedWith(RealmClass.class)) { - - // The class must either extend RealmObject or implement RealmModel - if (!Utils.isImplementingMarkerInterface(classElement)) { - Utils.error("A RealmClass annotated object must implement RealmModel or derive from RealmObject.", classElement); - return false; - } - - // Check the annotation was applied to a Class - if (!classElement.getKind().equals(ElementKind.CLASS)) { - Utils.error("The RealmClass annotation can only be applied to classes.", classElement); - return false; - } - - ClassMetaData metadata = new ClassMetaData(processingEnv, typeMirrors, (TypeElement) classElement); - if (!metadata.isModelClass()) { continue; } - - Utils.note("Processing class " + metadata.getSimpleJavaClassName()); - if (!metadata.generate(moduleMetaData)) { return false; } - - classCollection.addClass(metadata); - backlinksToValidate.addAll(metadata.getBacklinkFields()); - } - - return true; - } - - // Returns true if modules were processed successfully, false otherwise - private boolean preProcessModules(RoundEnvironment roundEnv) { - moduleMetaData = new ModuleMetaData(); - return moduleMetaData.preProcess(roundEnv.getElementsAnnotatedWith(RealmModule.class)); - } - - // Returns true of modules where successfully validated, false otherwise - private boolean postProcessModules() { - return moduleMetaData.postProcess(classCollection); - } - - private boolean createModuleFiles(RoundEnvironment roundEnv) { - // Create default module if needed - if (moduleMetaData.shouldCreateDefaultModule()) { - if (!createDefaultModule()) { - return false; - } - } - - // Create RealmProxyMediators for all Realm modules - for (Map.Entry> module : moduleMetaData.getAllModules().entrySet()) { - if (!createMediator(Utils.stripPackage(module.getKey()), module.getValue())) { - return false; - } - } - - return true; - } - - private boolean createProxyClassFiles(TypeMirrors typeMirrors) { - for (ClassMetaData metadata : classCollection.getClasses()) { - RealmProxyInterfaceGenerator interfaceGenerator = new RealmProxyInterfaceGenerator(processingEnv, metadata); - try { - interfaceGenerator.generate(); - } catch (IOException e) { - Utils.error(e.getMessage(), metadata.getClassElement()); - return false; - } - - RealmProxyClassGenerator sourceCodeGenerator = new RealmProxyClassGenerator(processingEnv, typeMirrors, metadata, classCollection); - try { - sourceCodeGenerator.generate(); - } catch (IOException | UnsupportedOperationException e) { - Utils.error(e.getMessage(), metadata.getClassElement()); - return false; - } - } - return true; - } - - private boolean createDefaultModule() { - Utils.note("Creating DefaultRealmModule"); - DefaultModuleGenerator defaultModuleGenerator = new DefaultModuleGenerator(processingEnv); - try { - defaultModuleGenerator.generate(); - } catch (IOException e) { - Utils.error(e.getMessage()); - return false; - } - - return true; - } - - private boolean createMediator(String simpleModuleName, Set moduleClasses) { - RealmProxyMediatorGenerator mediatorImplGenerator = new RealmProxyMediatorGenerator(processingEnv, - simpleModuleName, moduleClasses); - try { - mediatorImplGenerator.generate(); - } catch (IOException e) { - Utils.error(e.getMessage()); - return false; - } - - return true; - } - - // Because library classes are processed separately, there is no guarantee - // that this method can see all of the classes necessary to completely validate - // all of the backlinks. If it can find the fully-qualified class, though, - // and prove that the class either does not contain the necessary field, or - // that it does contain the field, but the field is of the wrong type, it can - // catch the error at compile time. - // Give all failure messages before failing - private boolean validateBacklinks() { - boolean allValid = true; - - for (Backlink backlink : backlinksToValidate) { - ClassMetaData clazz = classCollection.getClassFromQualifiedName(backlink.getSourceClass()); - - // If the class is not here it might be part of some other compilation unit. - if (clazz == null) { continue; } - - // If the class is here, we can validate it. - if (!backlink.validateTarget(clazz) && allValid) { allValid = false; } - } - - return allValid; - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt new file mode 100644 index 0000000000..47123ba632 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt @@ -0,0 +1,339 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import java.io.IOException +import java.util.HashSet + +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.RoundEnvironment +import javax.annotation.processing.SupportedAnnotationTypes +import javax.annotation.processing.SupportedOptions +import javax.lang.model.SourceVersion +import javax.lang.model.element.ElementKind +import javax.lang.model.element.TypeElement + +import io.realm.annotations.RealmClass +import io.realm.annotations.RealmModule +import javax.lang.model.element.Name + + +/** + * The RealmProcessor is responsible for creating the plumbing that connects the RealmObjects to a + * Realm. The process for doing so is summarized below and then described in more detail. + * + *

            DESIGN GOALS

            + * + * The processor should support the following design goals: + * + * * Minimize reflection. + * * Realm code can be obfuscated as much as possible. + * * Library projects must be able to use Realm without interfering with app code. + * * App code must be able to use RealmObject classes provided by library code. + * * It should work for app developers out of the box (ie. put the burden on the library developer) + * + *

            SUMMARY

            + * + * 1. Create proxy classes for all classes marked with @RealmClass. They are named + * `RealmProxy.java`. + * 2. Create a DefaultRealmModule containing all RealmObject classes (if needed). + * 3. Create a RealmProxyMediator class for all classes marked with `@RealmModule`. They are named + * `Mediator.java` + * + *

            WHY

            + * + * 1. A RealmObjectProxy object is created for each class annotated with + * [io.realm.annotations.RealmClass]. This proxy extends the original RealmObject class and + * rewires all field access to point to the native Realm memory instead of Java memory. It also + * adds some static helper methods to the class. + * + * 2. The annotation processor is either in "library" mode or in "app" mode. This is defined by + * having a class annotated with @RealmModule(library = true). It is not allowed to have both a + * class with `library = true` and `library = false` in the same IntelliJ module and it will + * cause the annotation processor to throw an exception. If no library modules are defined, we + * will create a DefaultRealmModule containing all known RealmObjects and with the + * `@RealmModule` annotation. Realm automatically knows about this module, but it is still + * possible for users to create their own modules with a subset of model classes. + * + * 3. For each class annotated with @RealmModule a matching Mediator class is created (including + * the default one). This class has an interface that matches the static helper methods for the + * proxy classes. All access to these static helper methods should be done through this Mediator. + * + * This allows ProGuard to obfuscate all RealmObject and proxy classes as all access to the static + * methods now happens through the Mediator, and the only requirement is now that only RealmModule + * and Mediator class names cannot be obfuscated. + * + *

            CREATING A REALM

            + * + * This means the workflow when instantiating a Realm on runtime is the following: + * + * 1. Open a Realm. + * 2. Assign one or more modules (that are allowed to overlap). If no module is assigned, the + * default module is used. + * 3. The Realm schema is now defined as all RealmObject classes known by these modules. + * 4. Each time a static helper method is needed, Realm can now delegate these method calls to the + * appropriate Mediator which in turn will delegate the method call to the appropriate + * RealmObjectProxy class. + * + *

            CREATING A MANAGED RealmObject

            + * + * To allow to specify default values by model's constructor or direct field assignment, the flow of + * creating the proxy object is a bit complicated. This section illustrates how proxy object should + * be created. + * + * 1. Get the thread local `io.realm.BaseRealm.RealmObjectContext` instance by + * `BaseRealm.objectContext.get()` + * 2. Set the object context information to the `RealmObjectContext` those should be set to the + * creating proxy object. + * 3. Create proxy object (`new io.realm.FooRealmProxy()`). + * 4. Set the object context information to the created proxy when the first access of its + * accessors (or in its constructor if accessors are not used in the model's constructor). + * 5. Clear the object context information in the thread local + * `io.realm.BaseRealm.RealmObjectContext` instance by calling `#clear()` method. + * + * The reason of this complicated step is that we can't pass these context information + * via the constructor of the proxy. It's because the constructor of the proxy is executed + * **after** the constructor of the model class. The access to the fields in the model's + * constructor happens before the assignment of the context information to the 'proxyState'. + * This will cause the [NullPointerException] if getters/setter is accessed in the model's + * constructor (see [Issue #2536](https://github.com/realm/realm-java/issues/2536)). + */ + +inline class QualifiedClassName(val name: String) { + constructor(name: Name): this(name.toString()) + fun getSimpleName(): SimpleClassName { + return SimpleClassName(Utils.stripPackage(name)) + } + override fun toString(): String { + return name + } +} +inline class SimpleClassName(val name: String) { + constructor(name: Name): this(name.toString()) + override fun toString(): String { + return name + } +} + +@SupportedAnnotationTypes( + "io.realm.annotations.RealmClass", + "io.realm.annotations.RealmField", + "io.realm.annotations.Ignore", + "io.realm.annotations.Index", + "io.realm.annotations.PrimaryKey", + "io.realm.annotations.RealmModule", + "io.realm.annotations.Required") +@SupportedOptions(value = ["realm.suppressWarnings", "realm.ignoreKotlinNullability"]) +class RealmProcessor : AbstractProcessor() { + + // Don't consume annotations. This allows 3rd party annotation processors to run. + private val CONSUME_ANNOTATIONS = false + private val ABORT = true // Abort the annotation processor by consuming all annotations + + private val classCollection = ClassCollection() // Metadata for all classes found + private lateinit var moduleMetaData: ModuleMetaData // Metadata for all modules found + + // List of backlinks + private val backlinksToValidate = HashSet() + + private var hasProcessedModules = false + private var round = -1 + + override fun getSupportedSourceVersion(): SourceVersion { + return SourceVersion.latestSupported() + } + + override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { + round++ + + if (round == 0) { + RealmVersionChecker.getInstance(processingEnv).executeRealmVersionUpdate() + } + + if (roundEnv.errorRaised()) { + return ABORT + } + + if (!hasProcessedModules) { + Utils.initialize(processingEnv) + val typeMirrors = TypeMirrors(processingEnv) + + // Build up internal metadata while validating as much as possible + if (!preProcessModules(roundEnv)) { + return ABORT + } + if (!processClassAnnotations(roundEnv, typeMirrors)) { + return ABORT + } + if (!postProcessModules()) { + return ABORT + } + if (!validateBacklinks()) { + return ABORT + } + hasProcessedModules = true + + // Create all files + if (!createProxyClassFiles(typeMirrors)) { + return ABORT + } + if (!createModuleFiles()) { + return ABORT + } + } + + return CONSUME_ANNOTATIONS + } + + // Create all proxy classes + private fun processClassAnnotations(roundEnv: RoundEnvironment, typeMirrors: TypeMirrors): Boolean { + + for (classElement in roundEnv.getElementsAnnotatedWith(RealmClass::class.java)) { + + // The class must either extend RealmObject or implement RealmModel + if (!Utils.isImplementingMarkerInterface(classElement)) { + Utils.error("A RealmClass annotated object must implement RealmModel or derive from RealmObject.", classElement) + return false + } + + // Check the annotation was applied to a Class + if (classElement.kind != ElementKind.CLASS) { + Utils.error("The RealmClass annotation can only be applied to classes.", classElement) + return false + } + + val metadata = ClassMetaData(processingEnv, typeMirrors, classElement as TypeElement) + if (!metadata.isModelClass) { + continue + } + + Utils.note("Processing class " + metadata.simpleJavaClassName) + if (!metadata.generate(moduleMetaData)) { + return false + } + + classCollection.addClass(metadata) + backlinksToValidate.addAll(metadata.backlinkFields) + } + + return true + } + + // Returns true if modules were processed successfully, false otherwise + private fun preProcessModules(roundEnv: RoundEnvironment): Boolean { + moduleMetaData = ModuleMetaData() + return moduleMetaData.preProcess(roundEnv.getElementsAnnotatedWith(RealmModule::class.java)) + } + + // Returns true of modules where successfully validated, false otherwise + private fun postProcessModules(): Boolean { + return moduleMetaData.postProcess(classCollection) + } + + private fun createModuleFiles(): Boolean { + // Create default module if needed + if (moduleMetaData.shouldCreateDefaultModule()) { + if (!createDefaultModule()) { + return false + } + } + + // Create RealmProxyMediators for all Realm modules + for ((key, value) in moduleMetaData.allModules) { + if (!createMediator(key.getSimpleName(), value)) { + return false + } + } + + return true + } + + private fun createProxyClassFiles(typeMirrors: TypeMirrors): Boolean { + for (metadata in classCollection.classes) { + val interfaceGenerator = RealmProxyInterfaceGenerator(processingEnv, metadata) + try { + interfaceGenerator.generate() + } catch (e: IOException) { + Utils.error(e.message, metadata.classElement) + return false + } + + val sourceCodeGenerator = RealmProxyClassGenerator(processingEnv, typeMirrors, metadata, classCollection) + try { + sourceCodeGenerator.generate() + } catch (e: IOException) { + Utils.error(e.message, metadata.classElement) + return false + } catch (e: UnsupportedOperationException) { + Utils.error(e.message, metadata.classElement) + return false + } + + } + return true + } + + private fun createDefaultModule(): Boolean { + Utils.note("Creating DefaultRealmModule") + val defaultModuleGenerator = DefaultModuleGenerator(processingEnv) + try { + defaultModuleGenerator.generate() + } catch (e: IOException) { + Utils.error(e.message) + return false + } + + return true + } + + private fun createMediator(moduleName: SimpleClassName, moduleClasses: Set): Boolean { + val mediatorImplGenerator = RealmProxyMediatorGenerator(processingEnv, moduleName, moduleClasses) + try { + mediatorImplGenerator.generate() + } catch (e: IOException) { + Utils.error(e.message) + return false + } + + return true + } + + // Because library classes are processed separately, there is no guarantee that this method can + // see all of the classes necessary to completely validate all of the backlinks. If it can find + // the fully-qualified class, though, and prove that the class either does not contain the + // necessary field, or that it does contain the field, but the field is of the wrong type, it + // can catch the error at compile time. Otherwise it is caught at runtime, when validating the + // schema. + private fun validateBacklinks(): Boolean { + var allValid = true + + for (backlink in backlinksToValidate) { + // If the class is not here it might be part of some other compilation unit. + if (!classCollection.containsQualifiedClass(backlink.sourceClass)) { + continue + } + val clazz = classCollection.getClassFromQualifiedName(backlink.sourceClass!!) + + // If the class is here, we can validate it. + if (!backlink.validateTarget(clazz) && allValid) { + allValid = false + } + } + + return allValid + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java deleted file mode 100644 index 8e6304e741..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.java +++ /dev/null @@ -1,2251 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import com.squareup.javawriter.JavaWriter; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Locale; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; -import javax.tools.JavaFileObject; - - -public class RealmProxyClassGenerator { - private static final String OPTION_SUPPRESS_WARNINGS = "realm.suppressWarnings"; - private static final String BACKLINKS_FIELD_EXTENSION = "Backlinks"; - - private static final List IMPORTS; - static { - List l = Arrays.asList( - "android.annotation.TargetApi", - "android.os.Build", - "android.util.JsonReader", - "android.util.JsonToken", - "io.realm.ImportFlag", - "io.realm.exceptions.RealmMigrationNeededException", - "io.realm.internal.ColumnInfo", - "io.realm.internal.OsList", - "io.realm.internal.OsObject", - "io.realm.internal.OsSchemaInfo", - "io.realm.internal.OsObjectSchemaInfo", - "io.realm.internal.Property", - "io.realm.internal.objectstore.OsObjectBuilder", - "io.realm.ProxyUtils", - "io.realm.internal.RealmObjectProxy", - "io.realm.internal.Row", - "io.realm.internal.Table", - "io.realm.internal.android.JsonUtils", - "io.realm.log.RealmLog", - "java.io.IOException", - "java.util.ArrayList", - "java.util.Collections", - "java.util.List", - "java.util.Iterator", - "java.util.Date", - "java.util.Map", - "java.util.HashMap", - "java.util.Set", - "org.json.JSONObject", - "org.json.JSONException", - "org.json.JSONArray"); - IMPORTS = Collections.unmodifiableList(l); - } - - private final ProcessingEnvironment processingEnvironment; - private final TypeMirrors typeMirrors; - private final ClassMetaData metadata; - private final ClassCollection classCollection; - private final String simpleJavaClassName; - private final String qualifiedJavaClassName; - private final String internalClassName; - private final String interfaceName; - private final String qualifiedGeneratedClassName; - private final boolean suppressWarnings; - - public RealmProxyClassGenerator(ProcessingEnvironment processingEnvironment, TypeMirrors typeMirrors, ClassMetaData metadata, ClassCollection classes) { - this.processingEnvironment = processingEnvironment; - this.typeMirrors = typeMirrors; - this.metadata = metadata; - this.classCollection = classes; - this.simpleJavaClassName = metadata.getSimpleJavaClassName(); - this.qualifiedJavaClassName = metadata.getFullyQualifiedClassName(); - this.internalClassName = metadata.getInternalClassName(); - this.interfaceName = Utils.getProxyInterfaceName(qualifiedJavaClassName); - this.qualifiedGeneratedClassName = String.format(Locale.US, "%s.%s", - Constants.REALM_PACKAGE_NAME, Utils.getProxyClassName(qualifiedJavaClassName)); - - // See the configuration for the debug build type, - // in the realm-library project, for an example of how to set this flag. - this.suppressWarnings = !"false".equalsIgnoreCase(processingEnvironment.getOptions().get(OPTION_SUPPRESS_WARNINGS)); - } - - public void generate() throws IOException, UnsupportedOperationException { - JavaFileObject sourceFile = processingEnvironment.getFiler().createSourceFile(qualifiedGeneratedClassName); - JavaWriter writer = new JavaWriter(new BufferedWriter(sourceFile.openWriter())); - - // Set source code indent - writer.setIndent(Constants.INDENT); - - writer.emitPackage(Constants.REALM_PACKAGE_NAME) - .emitEmptyLine(); - - List imports = new ArrayList(IMPORTS); - if (!metadata.getBacklinkFields().isEmpty()) { - imports.add("io.realm.internal.UncheckedRow"); - } - writer.emitImports(imports) - .emitEmptyLine(); - - // Begin the class definition - if (suppressWarnings) { - writer.emitAnnotation("SuppressWarnings(\"all\")"); - } - writer - .beginType( - qualifiedGeneratedClassName, // full qualified name of the item to generate - "class", // the type of the item - EnumSet.of(Modifier.PUBLIC), // modifiers to apply - qualifiedJavaClassName, // class to extend - "RealmObjectProxy", // interfaces to implement - interfaceName) - .emitEmptyLine(); - - emitColumnInfoClass(writer); - - emitClassFields(writer); - - emitInstanceFields(writer); - emitConstructor(writer); - - emitInjectContextMethod(writer); - emitPersistedFieldAccessors(writer); - emitBacklinkFieldAccessors(writer); - emitCreateExpectedObjectSchemaInfo(writer); - emitGetExpectedObjectSchemaInfo(writer); - emitCreateColumnInfoMethod(writer); - emitGetSimpleClassNameMethod(writer); - emitCreateOrUpdateUsingJsonObject(writer); - emitCreateUsingJsonStream(writer); - emitNewProxyInstance(writer); - emitCopyOrUpdateMethod(writer); - emitCopyMethod(writer); - emitInsertMethod(writer); - emitInsertListMethod(writer); - emitInsertOrUpdateMethod(writer); - emitInsertOrUpdateListMethod(writer); - emitCreateDetachedCopyMethod(writer); - emitUpdateMethod(writer); - emitToStringMethod(writer); - emitRealmObjectProxyImplementation(writer); - emitHashcodeMethod(writer); - emitEqualsMethod(writer); - - // End the class definition - writer.endType(); - writer.close(); - } - - private void emitColumnInfoClass(JavaWriter writer) throws IOException { - writer.beginType( - columnInfoClassName(), // full qualified name of the item to generate - "class", // the type of the item - EnumSet.of(Modifier.STATIC, Modifier.FINAL), // modifiers to apply - "ColumnInfo"); // base class - - // fields - writer.emitField("long", "maxColumnIndexValue"); // Must not end with Index as it otherwise could conflict regular fields. - for (VariableElement variableElement : metadata.getFields()) { - writer.emitField("long", columnIndexVarName(variableElement)); - } - writer.emitEmptyLine(); - - // constructor #1 - writer.beginConstructor( - EnumSet.noneOf(Modifier.class), - "OsSchemaInfo", "schemaInfo"); - writer.emitStatement("super(%s)", metadata.getFields().size()); - writer.emitStatement("OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo(\"%1$s\")", - internalClassName); - for (RealmFieldElement field : metadata.getFields()) { - writer.emitStatement( - "this.%1$sIndex = addColumnDetails(\"%1$s\", \"%2$s\", objectSchemaInfo)", - field.getJavaName(), - field.getInternalFieldName()); - } - for (Backlink backlink : metadata.getBacklinkFields()) { - writer.emitStatement( - "addBacklinkDetails(schemaInfo, \"%s\", \"%s\", \"%s\")", - backlink.getTargetField(), - classCollection.getClassFromQualifiedName(backlink.getSourceClass()).getInternalClassName(), - backlink.getSourceField()); - } - writer - .emitStatement("this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex()") - .endConstructor() - .emitEmptyLine(); - - // constructor #2 - writer.beginConstructor( - EnumSet.noneOf(Modifier.class), - "ColumnInfo", "src", "boolean", "mutable"); - writer.emitStatement("super(src, mutable)") - .emitStatement("copy(src, this)"); - writer.endConstructor() - .emitEmptyLine(); - - // no-args copy method - writer.emitAnnotation("Override") - .beginMethod( - "ColumnInfo", // return type - "copy", // method name - EnumSet.of(Modifier.PROTECTED, Modifier.FINAL), // modifiers - "boolean", "mutable"); // parameters - writer.emitStatement("return new %s(this, mutable)", columnInfoClassName()); - writer.endMethod() - .emitEmptyLine(); - - // copy method - writer.emitAnnotation("Override") - .beginMethod( - "void", // return type - "copy", // method name - EnumSet.of(Modifier.PROTECTED, Modifier.FINAL), // modifiers - "ColumnInfo", "rawSrc", "ColumnInfo", "rawDst"); // parameters - writer.emitStatement("final %1$s src = (%1$s) rawSrc", columnInfoClassName()); - writer.emitStatement("final %1$s dst = (%1$s) rawDst", columnInfoClassName()); - for (VariableElement variableElement : metadata.getFields()) { - writer.emitStatement("dst.%1$s = src.%1$s", columnIndexVarName(variableElement)); - } - writer.emitStatement("dst.maxColumnIndexValue = src.maxColumnIndexValue"); - writer.endMethod(); - - writer.endType(); - } - - //@formatter:off - private void emitClassFields(JavaWriter writer) throws IOException { - writer.emitEmptyLine() - .emitField("OsObjectSchemaInfo", "expectedObjectSchemaInfo", - EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL), "createExpectedObjectSchemaInfo()"); - } - //@formatter:on - - //@formatter:off - private void emitInstanceFields(JavaWriter writer) throws IOException { - writer.emitEmptyLine() - .emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE)) - .emitField("ProxyState<" + qualifiedJavaClassName + ">", "proxyState", EnumSet.of(Modifier.PRIVATE)); - - for (VariableElement variableElement : metadata.getFields()) { - if (Utils.isMutableRealmInteger(variableElement)) { - emitMutableRealmIntegerField(writer, variableElement); - } else if (Utils.isRealmList(variableElement)) { - String genericType = Utils.getGenericTypeQualifiedName(variableElement); - writer.emitField("RealmList<" + genericType + ">", variableElement.getSimpleName().toString() + "RealmList", EnumSet.of(Modifier.PRIVATE)); - } - } - - for (Backlink backlink : metadata.getBacklinkFields()) { - writer.emitField(backlink.getTargetFieldType(), backlink.getTargetField() + BACKLINKS_FIELD_EXTENSION, - EnumSet.of(Modifier.PRIVATE)); - } - } - //@formatter:on - - // The anonymous subclass of MutableRealmInteger.Managed holds a reference to this proxy. - // Even if all other references to the proxy are dropped, the proxy will not be GCed until - // the MutableInteger that it owns, also becomes unreachable. - //@formatter:off - private void emitMutableRealmIntegerField(JavaWriter writer, VariableElement variableElement) throws IOException{ - writer.emitField("MutableRealmInteger.Managed", - mutableRealmIntegerFieldName(variableElement), - EnumSet.of(Modifier.PRIVATE, Modifier.FINAL), - String.format( - "new MutableRealmInteger.Managed<%1$s>() {\n" - + " @Override protected ProxyState<%1$s> getProxyState() { return proxyState; }\n" - + " @Override protected long getColumnIndex() { return columnInfo.%2$s; }\n" - + "}", - qualifiedJavaClassName, columnIndexVarName(variableElement))); - } - //@formatter:on - - //@formatter:off - private void emitConstructor(JavaWriter writer) throws IOException { - // FooRealmProxy(ColumnInfo) - writer.emitEmptyLine() - .beginConstructor(EnumSet.noneOf(Modifier.class)) - .emitStatement("proxyState.setConstructionFinished()") - .endConstructor() - .emitEmptyLine(); - } - //@formatter:on - - private void emitPersistedFieldAccessors(final JavaWriter writer) throws IOException { - for (final VariableElement field : metadata.getFields()) { - final String fieldName = field.getSimpleName().toString(); - final String fieldTypeCanonicalName = field.asType().toString(); - - if (Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeCanonicalName)) { - emitPrimitiveType(writer, field, fieldName, fieldTypeCanonicalName); - } else if (Utils.isMutableRealmInteger(field)) { - emitMutableRealmInteger(writer, field, fieldName, fieldTypeCanonicalName); - } else if (Utils.isRealmModel(field)) { - emitRealmModel(writer, field, fieldName, fieldTypeCanonicalName); - } else if (Utils.isRealmList(field)) { - final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); - emitRealmList(writer, field, fieldName, fieldTypeCanonicalName, elementTypeMirror); - } else { - throw new UnsupportedOperationException(String.format(Locale.US, - "Field \"%s\" of type \"%s\" is not supported.", fieldName, fieldTypeCanonicalName)); - } - - writer.emitEmptyLine(); - } - } - - /** - * Primitives and boxed types - */ - private void emitPrimitiveType( - JavaWriter writer, - final VariableElement field, - final String fieldName, - String fieldTypeCanonicalName) throws IOException { - - final String fieldJavaType = getRealmTypeChecked(field).getJavaType(); - - // Getter - //@formatter:off - writer.emitAnnotation("Override"); - writer.emitAnnotation("SuppressWarnings", "\"cast\"") - .beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) - .emitStatement("proxyState.getRealm$realm().checkIfValid()"); - - // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here. - if (metadata.isNullable(field) && !Utils.isString(field) && !Utils.isByteArray(field)) { - writer.beginControlFlow("if (proxyState.getRow$realm().isNull(%s))", fieldIndexVariableReference(field)) - .emitStatement("return null") - .endControlFlow(); - } - //@formatter:on - - // For Boxed types, this should be the corresponding primitive types. Others remain the same. - String castingBackType; - if (Utils.isBoxedType(fieldTypeCanonicalName)) { - Types typeUtils = processingEnvironment.getTypeUtils(); - castingBackType = typeUtils.unboxedType(field.asType()).toString(); - } else { - castingBackType = fieldTypeCanonicalName; - } - writer.emitStatement( - "return (%s) proxyState.getRow$realm().get%s(%s)", - castingBackType, fieldJavaType, fieldIndexVariableReference(field)); - writer.endMethod() - .emitEmptyLine(); - - // Setter - writer.emitAnnotation("Override"); - writer.beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { - @Override - public void emit(JavaWriter writer) throws IOException { - // set value as default value - writer.emitStatement("final Row row = proxyState.getRow$realm()"); - - //@formatter:off - if (metadata.isNullable(field)) { - writer.beginControlFlow("if (value == null)") - .emitStatement("row.getTable().setNull(%s, row.getIndex(), true)", - fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); - } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { - writer.beginControlFlow("if (value == null)") - .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) - .endControlFlow(); - } - //@formatter:on - - writer.emitStatement( - "row.getTable().set%s(%s, row.getIndex(), value, true)", - fieldJavaType, fieldIndexVariableReference(field)); - writer.emitStatement("return"); - } - }); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - // Although setting null value for String and bytes[] can be handled by the JNI code, we still generate the same code here. - // Compared with getter, null value won't trigger more native calls in setter which is relatively cheaper. - if (metadata.isPrimaryKey(field)) { - // Primary key is not allowed to be changed after object created. - writer.emitStatement(Constants.STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED, fieldName); - } else { - //@formatter:off - if (metadata.isNullable(field)) { - writer.beginControlFlow("if (value == null)") - .emitStatement("proxyState.getRow$realm().setNull(%s)", fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); - } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { - // Same reason, throw IAE earlier. - writer - .beginControlFlow("if (value == null)") - .emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) - .endControlFlow(); - } - //@formatter:on - writer.emitStatement( - "proxyState.getRow$realm().set%s(%s, value)", - fieldJavaType, fieldIndexVariableReference(field)); - } - writer.endMethod(); - } - - //@formatter:off - private void emitMutableRealmInteger(JavaWriter writer, VariableElement field, String fieldName, String fieldTypeCanonicalName) throws IOException { - writer.emitAnnotation("Override") - .beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) - .emitStatement("proxyState.getRealm$realm().checkIfValid()") - .emitStatement("return this.%s", mutableRealmIntegerFieldName(field)) - .endMethod(); - } - //@formatter:on - - /** - * Links - */ - //@formatter:off - private void emitRealmModel( - JavaWriter writer, - final VariableElement field, - String fieldName, - String fieldTypeCanonicalName) throws IOException { - - // Getter - writer.emitAnnotation("Override"); - writer.beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) - .emitStatement("proxyState.getRealm$realm().checkIfValid()") - .beginControlFlow("if (proxyState.getRow$realm().isNullLink(%s))", fieldIndexVariableReference(field)) - .emitStatement("return null") - .endControlFlow() - .emitStatement("return proxyState.getRealm$realm().get(%s.class, proxyState.getRow$realm().getLink(%s), false, Collections.emptyList())", - fieldTypeCanonicalName, fieldIndexVariableReference(field)) - .endMethod() - .emitEmptyLine(); - - // Setter - writer.emitAnnotation("Override"); - writer.beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { - @Override - public void emit(JavaWriter writer) throws IOException { - // check excludeFields - writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", - field.getSimpleName().toString()) - .emitStatement("return") - .endControlFlow(); - writer.beginControlFlow("if (value != null && !RealmObject.isManaged(value))") - .emitStatement("value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value)") - .endControlFlow(); - - // set value as default value - writer.emitStatement("final Row row = proxyState.getRow$realm()"); - writer.beginControlFlow("if (value == null)") - .emitSingleLineComment("Table#nullifyLink() does not support default value. Just using Row.") - .emitStatement("row.nullifyLink(%s)", fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow(); - writer.emitStatement("proxyState.checkValidObject(value)"); - writer.emitStatement("row.getTable().setLink(%s, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true)", - fieldIndexVariableReference(field)); - writer.emitStatement("return"); - } - }); - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()") - .beginControlFlow("if (value == null)") - .emitStatement("proxyState.getRow$realm().nullifyLink(%s)", fieldIndexVariableReference(field)) - .emitStatement("return") - .endControlFlow() - .emitStatement("proxyState.checkValidObject(value)") - .emitStatement("proxyState.getRow$realm().setLink(%s, ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex())", fieldIndexVariableReference(field)) - .endMethod(); - } - //@formatter:on - - /** - * ModelList, ValueList - */ - //@formatter:off - private void emitRealmList( - JavaWriter writer, - final VariableElement field, - String fieldName, - String fieldTypeCanonicalName, - final TypeMirror elementTypeMirror) throws IOException { - final String genericType = Utils.getGenericTypeQualifiedName(field); - final boolean forRealmModel = Utils.isRealmModel(elementTypeMirror); - - // Getter - writer.emitAnnotation("Override"); - writer.beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) - .emitStatement("proxyState.getRealm$realm().checkIfValid()") - .emitSingleLineComment("use the cached value if available") - .beginControlFlow("if (" + fieldName + "RealmList != null)") - .emitStatement("return " + fieldName + "RealmList") - .nextControlFlow("else"); - if (Utils.isRealmModelList(field)) { - writer.emitStatement("OsList osList = proxyState.getRow$realm().getModelList(%s)", - fieldIndexVariableReference(field)); - } else { - writer.emitStatement("OsList osList = proxyState.getRow$realm().getValueList(%1$s, RealmFieldType.%2$s)", - fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name()); - } - writer.emitStatement(fieldName + "RealmList = new RealmList<%s>(%s.class, osList, proxyState.getRealm$realm())", - genericType, genericType) - .emitStatement("return " + fieldName + "RealmList") - .endControlFlow() - .endMethod() - .emitEmptyLine(); - - // Setter - writer.emitAnnotation("Override"); - writer.beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value"); - emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field), new CodeEmitter() { - @Override - public void emit(JavaWriter writer) throws IOException { - // check excludeFields - writer.beginControlFlow("if (proxyState.getExcludeFields$realm().contains(\"%1$s\"))", - field.getSimpleName().toString()) - .emitStatement("return") - .endControlFlow(); - - if (!forRealmModel) { - return; - } - - writer.emitSingleLineComment("if the list contains unmanaged RealmObjects, convert them to managed.") - .beginControlFlow("if (value != null && !value.isManaged())") - .emitStatement("final Realm realm = (Realm) proxyState.getRealm$realm()") - .emitStatement("final RealmList<%1$s> original = value", genericType) - .emitStatement("value = new RealmList<%1$s>()", genericType) - .beginControlFlow("for (%1$s item : original)", genericType) - .beginControlFlow("if (item == null || RealmObject.isManaged(item))") - .emitStatement("value.add(item)") - .nextControlFlow("else") - .emitStatement("value.add(realm.copyToRealm(item))") - .endControlFlow() - .endControlFlow() - .endControlFlow(); - - // LinkView currently does not support default value feature. Just fallback to normal code. - } - }); - - writer.emitStatement("proxyState.getRealm$realm().checkIfValid()"); - if (Utils.isRealmModelList(field)) { - writer.emitStatement("OsList osList = proxyState.getRow$realm().getModelList(%s)", - fieldIndexVariableReference(field)); - } else { - writer.emitStatement("OsList osList = proxyState.getRow$realm().getValueList(%1$s, RealmFieldType.%2$s)", - fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name()); - } - if (forRealmModel) { - // Model lists. - writer - .emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") - .beginControlFlow("if (value != null && value.size() == osList.size())") - .emitStatement("int objects = value.size()") - .beginControlFlow("for (int i = 0; i < objects; i++)") - .emitStatement("%s linkedObject = value.get(i)", genericType) - .emitStatement("proxyState.checkValidObject(linkedObject)") - .emitStatement("osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex())") - .endControlFlow() - .nextControlFlow("else") - .emitStatement("osList.removeAll()") - .beginControlFlow("if (value == null)") - .emitStatement("return") - .endControlFlow() - .emitStatement("int objects = value.size()") - .beginControlFlow("for (int i = 0; i < objects; i++)") - .emitStatement("%s linkedObject = value.get(i)", genericType) - .emitStatement("proxyState.checkValidObject(linkedObject)") - .emitStatement("osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex())") - .endControlFlow() - .endControlFlow(); - } else { - // Value lists - writer - .emitStatement("osList.removeAll()") - .beginControlFlow("if (value == null)") - .emitStatement("return") - .endControlFlow() - .beginControlFlow("for (%1$s item : value)", genericType) - .beginControlFlow("if (item == null)") - .emitStatement(metadata.isElementNullable(field) ? "osList.addNull()" : "throw new IllegalArgumentException(\"Storing 'null' into " + fieldName + "' is not allowed by the schema.\")") - .nextControlFlow("else") - .emitStatement(getStatementForAppendingValueToOsList("osList", "item", elementTypeMirror)) - .endControlFlow() - .endControlFlow(); - } - writer.endMethod(); - - } - //@formatter:on - - private String getStatementForAppendingValueToOsList( - @SuppressWarnings("SameParameterValue") String osListVariableName, - @SuppressWarnings("SameParameterValue") String valueVariableName, - TypeMirror elementTypeMirror) { - - Types typeUtils = processingEnvironment.getTypeUtils(); - if (typeUtils.isSameType(elementTypeMirror, typeMirrors.STRING_MIRROR)) { - return osListVariableName + ".addString(" + valueVariableName + ")"; - } - if (typeUtils.isSameType(elementTypeMirror, typeMirrors.LONG_MIRROR) - || typeUtils.isSameType(elementTypeMirror, typeMirrors.INTEGER_MIRROR) - || typeUtils.isSameType(elementTypeMirror, typeMirrors.SHORT_MIRROR) - || typeUtils.isSameType(elementTypeMirror, typeMirrors.BYTE_MIRROR)) { - return osListVariableName + ".addLong(" + valueVariableName + ".longValue())"; - } - if (typeUtils.isSameType(elementTypeMirror, typeMirrors.BINARY_MIRROR)) { - return osListVariableName + ".addBinary(" + valueVariableName + ")"; - } - if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DATE_MIRROR)) { - return osListVariableName + ".addDate(" + valueVariableName + ")"; - } - if (typeUtils.isSameType(elementTypeMirror, typeMirrors.BOOLEAN_MIRROR)) { - return osListVariableName + ".addBoolean(" + valueVariableName + ")"; - } - if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DOUBLE_MIRROR)) { - return osListVariableName + ".addDouble(" + valueVariableName + ".doubleValue())"; - } - if (typeUtils.isSameType(elementTypeMirror, typeMirrors.FLOAT_MIRROR)) { - return osListVariableName + ".addFloat(" + valueVariableName + ".floatValue())"; - } - throw new RuntimeException("unexpected element type: " + elementTypeMirror.toString()); - } - - private interface CodeEmitter { - void emit(JavaWriter writer) throws IOException; - } - - private void emitCodeForUnderConstruction(JavaWriter writer, boolean isPrimaryKey, - CodeEmitter defaultValueCodeEmitter) throws IOException { - writer.beginControlFlow("if (proxyState.isUnderConstruction())"); - if (isPrimaryKey) { - writer.emitSingleLineComment("default value of the primary key is always ignored.") - .emitStatement("return"); - } else { - writer.beginControlFlow("if (!proxyState.getAcceptDefaultValue$realm())") - .emitStatement("return") - .endControlFlow(); - defaultValueCodeEmitter.emit(writer); - } - writer.endControlFlow() - .emitEmptyLine(); - } - - // Note that because of bytecode hackery, this method may run before the constructor! - // It may even run before fields have been initialized. - //@formatter:off - private void emitInjectContextMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "void", // Return type - "realm$injectObjectContext", // Method name - EnumSet.of(Modifier.PUBLIC) // Modifiers - ); // Argument type & argument name - - writer.beginControlFlow("if (this.proxyState != null)") - .emitStatement("return") - .endControlFlow() - .emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()") - .emitStatement("this.columnInfo = (%1$s) context.getColumnInfo()", columnInfoClassName()) - .emitStatement("this.proxyState = new ProxyState<%1$s>(this)", qualifiedJavaClassName) - .emitStatement("proxyState.setRealm$realm(context.getRealm())") - .emitStatement("proxyState.setRow$realm(context.getRow())") - .emitStatement("proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue())") - .emitStatement("proxyState.setExcludeFields$realm(context.getExcludeFields())") - .endMethod() - .emitEmptyLine(); - } - //@formatter:on - - //@formatter:off - private void emitBacklinkFieldAccessors(JavaWriter writer) throws IOException { - for (Backlink backlink : metadata.getBacklinkFields()) { - String cacheFieldName = backlink.getTargetField() + BACKLINKS_FIELD_EXTENSION; - String realmResultsType = "RealmResults<" + backlink.getSourceClass() + ">"; - - // Getter, no setter - writer.emitAnnotation("Override"); - writer.beginMethod(realmResultsType, metadata.getInternalGetter(backlink.getTargetField()), EnumSet.of(Modifier.PUBLIC)) - .emitStatement("BaseRealm realm = proxyState.getRealm$realm()") - .emitStatement("realm.checkIfValid()") - .emitStatement("proxyState.getRow$realm().checkIfAttached()") - .beginControlFlow("if (" + cacheFieldName + " == null)") - .emitStatement(cacheFieldName + " = RealmResults.createBacklinkResults(realm, proxyState.getRow$realm(), %s.class, \"%s\")", - backlink.getSourceClass(), backlink.getSourceField()) - .endControlFlow() - .emitStatement("return " + cacheFieldName) - .endMethod() - .emitEmptyLine(); - } - } - //@formatter:on - - //@formatter:off - private void emitRealmObjectProxyImplementation(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override") - .beginMethod("ProxyState", "realmGet$proxyState", EnumSet.of(Modifier.PUBLIC)) - .emitStatement("return proxyState") - .endMethod() - .emitEmptyLine(); - } - //@formatter:on - - private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOException { - writer.beginMethod( - "OsObjectSchemaInfo", // Return type - "createExpectedObjectSchemaInfo", // Method name - EnumSet.of(Modifier.PRIVATE, Modifier.STATIC)); // Modifiers - - // Guess capacity for Arrays used by OsObjectSchemaInfo. - // Used to prevent array resizing at runtime - int persistedFields = metadata.getFields().size(); - int computedFields = metadata.getBacklinkFields().size(); - - writer.emitStatement( - "OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\", %s, %s)", - internalClassName, persistedFields, computedFields); - - // For each field generate corresponding table index constant - for (RealmFieldElement field : metadata.getFields()) { - String fieldName = field.getInternalFieldName(); - - Constants.RealmFieldType fieldType = getRealmTypeChecked(field); - switch (fieldType) { - case NOTYPE: { - // Perhaps this should fail quickly? - break; - } - case OBJECT: { - String fieldTypeQualifiedName = Utils.getFieldTypeQualifiedName(field); - String internalClassName = Utils.getReferencedTypeInternalClassNameStatement(fieldTypeQualifiedName, classCollection); - writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.OBJECT, %s)", - fieldName, internalClassName); - break; - } - case LIST: { - String genericTypeQualifiedName = Utils.getGenericTypeQualifiedName(field); - String internalClassName = Utils.getReferencedTypeInternalClassNameStatement(genericTypeQualifiedName, classCollection); - writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.LIST, %s)", - fieldName, internalClassName); - break; - } - case INTEGER_LIST: - case BOOLEAN_LIST: - case STRING_LIST: - case BINARY_LIST: - case DATE_LIST: - case FLOAT_LIST: - case DOUBLE_LIST: - writer.emitStatement("builder.addPersistedValueListProperty(\"%s\", %s, %s)", - fieldName, fieldType.getRealmType(), metadata.isElementNullable(field) ? "!Property.REQUIRED" : "Property.REQUIRED"); - break; - - case BACKLINK: - throw new IllegalArgumentException("LinkingObject field should not be added to metadata"); - - case INTEGER: - case FLOAT: - case DOUBLE: - case BOOLEAN: - case STRING: - case DATE: - case BINARY: - case REALM_INTEGER: - String nullableFlag = (metadata.isNullable(field) ? "!" : "") + "Property.REQUIRED"; - String indexedFlag = (metadata.isIndexed(field) ? "" : "!") + "Property.INDEXED"; - String primaryKeyFlag = (metadata.isPrimaryKey(field) ? "" : "!") + "Property.PRIMARY_KEY"; - writer.emitStatement("builder.addPersistedProperty(\"%s\", %s, %s, %s, %s)", - fieldName, - fieldType.getRealmType(), - primaryKeyFlag, - indexedFlag, - nullableFlag); - break; - - default: - throw new IllegalArgumentException("'fieldType' " + fieldName + " is not handled"); - } - } - for (Backlink backlink: metadata.getBacklinkFields()) { - // Backlinks can only be created between classes in the current round of annotation processing - // as the forward link cannot be created unless you know the type already. - ClassMetaData sourceClass = classCollection.getClassFromQualifiedName(backlink.getSourceClass()); - String targetField = backlink.getTargetField(); // Only in the model, so no internal name exists - String internalSourceField = sourceClass.getInternalFieldName(backlink.getSourceField()); - writer.emitStatement("builder.addComputedLinkProperty(\"%s\", \"%s\", \"%s\")", - targetField, sourceClass.getInternalClassName(), internalSourceField); - } - writer.emitStatement("return builder.build()"); - writer.endMethod() - .emitEmptyLine(); - } - - private void emitGetExpectedObjectSchemaInfo(JavaWriter writer) throws IOException { - writer.beginMethod( - "OsObjectSchemaInfo", // Return type - "getExpectedObjectSchemaInfo", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)); // Modifiers - - writer.emitStatement("return expectedObjectSchemaInfo"); - - writer.endMethod() - .emitEmptyLine(); - } - - private void emitCreateColumnInfoMethod(JavaWriter writer) throws IOException { - writer.beginMethod( - columnInfoClassName(), // Return type - "createColumnInfo", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "OsSchemaInfo", "schemaInfo"); // Argument type & argument name - - // create an instance of ColumnInfo - writer.emitStatement("return new %1$s(schemaInfo)", columnInfoClassName()); - - writer.endMethod(); - writer.emitEmptyLine(); - } - - //@formatter:off - private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException { - writer.beginMethod("String", "getSimpleClassName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) - .emitStatement("return \"%s\"", internalClassName) - .endMethod() - .emitEmptyLine(); - - // Helper class for the annotation processor so it can access the internal class name - // without needing to load the parent class (which we cannot do as it transitively loads - // native code, which cannot be loaded on the JVM). - writer.beginType( - "ClassNameHelper", // full qualified name of the item to generate - "class", // the type of the item - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)); // modifiers to apply - writer.emitField("String", "INTERNAL_CLASS_NAME", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL), "\""+ internalClassName+"\""); - writer.endType(); - writer.emitEmptyLine(); - } - //@formatter:on - - //@formatter:off - private void emitNewProxyInstance(JavaWriter writer) throws IOException { - writer - .beginMethod(qualifiedGeneratedClassName, - "newProxyInstance", - EnumSet.of(Modifier.PRIVATE, Modifier.STATIC), - "BaseRealm", "realm", - "Row", "row") - .emitSingleLineComment("Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields") - .emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") - .emitStatement("objectContext.set(realm, row, realm.getSchema().getColumnInfo(%s.class), false, Collections.emptyList())", qualifiedJavaClassName) - .emitStatement("%1$s obj = new %1$s()", qualifiedGeneratedClassName) - .emitStatement("objectContext.clear()") - .emitStatement("return obj") - .endMethod() - .emitEmptyLine(); - } - //@formatter:on - - //@formatter:off - private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { - writer.beginMethod( - qualifiedJavaClassName, // Return type - "copyOrUpdate", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", // Argument type & argument name - columnInfoClassName(), "columnInfo", - qualifiedJavaClassName, "object", - "boolean", "update", - "Map", "cache", - "Set", "flags" - ); - - writer - .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null)") - .emitStatement("final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm()") - .beginControlFlow("if (otherRealm.threadId != realm.threadId)") - .emitStatement("throw new IllegalArgumentException(\"Objects which belong to Realm instances in other threads cannot be copied into this Realm instance.\")") - .endControlFlow() - - // If object is already in the Realm there is nothing to update - .beginControlFlow("if (otherRealm.getPath().equals(realm.getPath()))") - .emitStatement("return object") - .endControlFlow() - .endControlFlow(); - - - writer.emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()"); - - writer.emitStatement("RealmObjectProxy cachedRealmObject = cache.get(object)") - .beginControlFlow("if (cachedRealmObject != null)") - .emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName) - .endControlFlow() - .emitEmptyLine(); - - if (!metadata.hasPrimaryKey()) { - writer.emitStatement("return copy(realm, columnInfo, object, update, cache, flags)"); - } else { - writer - .emitStatement("%s realmObject = null", qualifiedJavaClassName) - .emitStatement("boolean canUpdate = update") - .beginControlFlow("if (canUpdate)") - .emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) - .emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); - - String primaryKeyGetter = metadata.getPrimaryKeyGetter(); - VariableElement primaryKeyElement = metadata.getPrimaryKey(); - if (metadata.isNullable(primaryKeyElement)) { - if (Utils.isString(primaryKeyElement)) { - writer - .emitStatement("String value = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = Table.NO_MATCH") - .beginControlFlow("if (value == null)") - .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") - .nextControlFlow("else") - .emitStatement("rowIndex = table.findFirstString(pkColumnIndex, value)") - .endControlFlow(); - } else { - writer - .emitStatement("Number value = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = Table.NO_MATCH") - .beginControlFlow("if (value == null)") - .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") - .nextControlFlow("else") - .emitStatement("rowIndex = table.findFirstLong(pkColumnIndex, value.longValue())") - .endControlFlow(); - } - } else { - String pkType = Utils.isString(metadata.getPrimaryKey()) ? "String" : "Long"; - writer.emitStatement("long rowIndex = table.findFirst%s(pkColumnIndex, ((%s) object).%s())", - pkType, interfaceName, primaryKeyGetter); - } - - writer - .beginControlFlow("if (rowIndex == Table.NO_MATCH)") - .emitStatement("canUpdate = false") - .nextControlFlow("else") - .beginControlFlow("try") - .emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex), columnInfo, false, Collections. emptyList())") - .emitStatement("realmObject = new %s()", qualifiedGeneratedClassName) - .emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") - .nextControlFlow("finally") - .emitStatement("objectContext.clear()") - .endControlFlow() - .endControlFlow(); - - writer.endControlFlow(); - - writer - .emitEmptyLine() - .emitStatement("return (canUpdate) ? update(realm, columnInfo, realmObject, object, cache, flags) : copy(realm, columnInfo, object, update, cache, flags)"); - } - - writer.endMethod() - .emitEmptyLine(); - } - //@formatter:on - - //@formatter:off - private void setTableValues(JavaWriter writer, String fieldType, String fieldName, String interfaceName, String getter, boolean isUpdate) throws IOException { - if ("long".equals(fieldType) - || "int".equals(fieldType) - || "short".equals(fieldType) - || "byte".equals(fieldType)) { - writer.emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter); - - } else if ("java.lang.Long".equals(fieldType) - || "java.lang.Integer".equals(fieldType) - || "java.lang.Short".equals(fieldType) - || "java.lang.Byte".equals(fieldType)) { - writer - .emitStatement("Number %s = ((%s) object).%s()", getter, interfaceName, getter) - .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); - - } else if ("io.realm.MutableRealmInteger".equals(fieldType)) { - writer - .emitStatement("Long %s = ((%s) object).%s().get()", getter, interfaceName, getter) - .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); - - } else if ("double".equals(fieldType)) { - writer.emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter); - - } else if ("java.lang.Double".equals(fieldType)) { - writer - .emitStatement("Double %s = ((%s) object).%s()", getter, interfaceName, getter) - .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); - - } else if ("float".equals(fieldType)) { - writer.emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter); - - } else if ("java.lang.Float".equals(fieldType)) { - writer - .emitStatement("Float %s = ((%s) object).%s()", getter, interfaceName, getter) - .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); - - } else if ("boolean".equals(fieldType)) { - writer.emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter); - - } else if ("java.lang.Boolean".equals(fieldType)) { - writer - .emitStatement("Boolean %s = ((%s) object).%s()", getter, interfaceName, getter) - .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); - - } else if ("byte[]".equals(fieldType)) { - writer - .emitStatement("byte[] %s = ((%s) object).%s()", getter, interfaceName, getter) - .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); - - - } else if ("java.util.Date".equals(fieldType)) { - writer - .emitStatement("java.util.Date %s = ((%s) object).%s()", getter, interfaceName, getter) - .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.getTime(), false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); - - } else if ("java.lang.String".equals(fieldType)) { - writer - .emitStatement("String %s = ((%s) object).%s()", getter, interfaceName, getter) - .beginControlFlow("if (%s != null)", getter) - .emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter); - if (isUpdate) { - writer.nextControlFlow("else") - .emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName); - } - writer.endControlFlow(); - } else { - throw new IllegalStateException("Unsupported type " + fieldType); - } - } - //@formatter:on - - private void emitInsertMethod(JavaWriter writer) throws IOException { - writer.beginMethod( - "long", // Return type - "insert", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedJavaClassName, "object", "Map", "cache" // Argument type & argument name - ); - - // If object is already in the Realm there is nothing to update - writer - .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") - .emitStatement("return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()") - .endControlFlow(); - - writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName); - writer.emitStatement("long tableNativePtr = table.getNativePtr()"); - writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName); - - if (metadata.hasPrimaryKey()) { - writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); - } - addPrimaryKeyCheckIfNeeded(metadata, true, writer); - - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); - String fieldType = field.asType().toString(); - String getter = metadata.getInternalGetter(fieldName); - - //@formatter:off - if (Utils.isRealmModel(field)) { - writer - .emitEmptyLine() - .emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) - .beginControlFlow("if (cache%s == null)", fieldName) - .emitStatement("cache%s = %s.insert(realm, %sObj, cache)", - fieldName, - Utils.getProxyClassSimpleName(field), - fieldName) - .endControlFlow() - .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) - .endControlFlow(); - } else if (Utils.isRealmModelList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - writer - .emitEmptyLine() - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", - genericType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insert(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("%1$sOsList.addRow(cacheItemIndex%1$s)", fieldName) - .endControlFlow() - .endControlFlow(); - } else if (Utils.isRealmValueList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); - writer - .emitEmptyLine() - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", - genericType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .beginControlFlow("if (%1$sItem == null)", fieldName) - .emitStatement(fieldName + "OsList.addNull()") - .nextControlFlow("else") - .emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) - .endControlFlow() - .endControlFlow() - .endControlFlow(); - } else { - if (metadata.getPrimaryKey() != field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, false); - } - } - //@formatter:on - } - - writer.emitStatement("return rowIndex"); - writer.endMethod() - .emitEmptyLine(); - } - - private void emitInsertListMethod(JavaWriter writer) throws IOException { - writer.beginMethod( - "void", // Return type - "insert", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", "Iterator", "objects", "Map", "cache" // Argument type & argument name - ); - - writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName); - writer.emitStatement("long tableNativePtr = table.getNativePtr()"); - writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName); - if (metadata.hasPrimaryKey()) { - writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); - } - writer.emitStatement("%s object = null", qualifiedJavaClassName); - - writer.beginControlFlow("while (objects.hasNext())") - .emitStatement("object = (%s) objects.next()", qualifiedJavaClassName); - writer.beginControlFlow("if (cache.containsKey(object))") - .emitStatement("continue") - .endControlFlow(); - - writer.beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))"); - writer.emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex())") - .emitStatement("continue"); - writer.endControlFlow(); - - addPrimaryKeyCheckIfNeeded(metadata, true, writer); - - //@formatter:off - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); - String fieldType = field.asType().toString(); - String getter = metadata.getInternalGetter(fieldName); - - if (Utils.isRealmModel(field)) { - writer - .emitEmptyLine() - .emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) - .beginControlFlow("if (cache%s == null)", fieldName) - .emitStatement("cache%s = %s.insert(realm, %sObj, cache)", - fieldName, - Utils.getProxyClassSimpleName(field), - fieldName) - .endControlFlow() - .emitStatement("table.setLink(columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) - .endControlFlow(); - } else if (Utils.isRealmModelList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - writer - .emitEmptyLine() - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", - genericType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insert(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("%1$sOsList.addRow(cacheItemIndex%1$s)", fieldName) - .endControlFlow() - .endControlFlow(); - - } else if (Utils.isRealmValueList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); - writer - .emitEmptyLine() - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", - genericType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .beginControlFlow("if (%1$sItem == null)", fieldName) - .emitStatement("%1$sOsList.addNull()", fieldName) - .nextControlFlow("else") - .emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) - .endControlFlow() - .endControlFlow() - .endControlFlow(); - } else { - if (metadata.getPrimaryKey() != field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, false); - } - } - } - //@formatter:on - - writer.endControlFlow(); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitInsertOrUpdateMethod(JavaWriter writer) throws IOException { - writer.beginMethod( - "long", // Return type - "insertOrUpdate", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", qualifiedJavaClassName, "object", "Map", "cache" // Argument type & argument name - ); - - // If object is already in the Realm there is nothing to update - writer - .beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))") - .emitStatement("return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()") - .endControlFlow(); - - writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName); - writer.emitStatement("long tableNativePtr = table.getNativePtr()"); - writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName); - - if (metadata.hasPrimaryKey()) { - writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); - } - addPrimaryKeyCheckIfNeeded(metadata, false, writer); - - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); - String fieldType = field.asType().toString(); - String getter = metadata.getInternalGetter(fieldName); - - //@formatter:off - if (Utils.isRealmModel(field)) { - writer - .emitEmptyLine() - .emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) - .beginControlFlow("if (cache%s == null)", fieldName) - .emitStatement("cache%1$s = %2$s.insertOrUpdate(realm, %1$sObj, cache)", - fieldName, - Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) - .nextControlFlow("else") - // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) - .endControlFlow(); - } else if (Utils.isRealmModelList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - writer - .emitEmptyLine() - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) - .beginControlFlow("if (%1$sList != null && %1$sList.size() == %1$sOsList.size())", fieldName) - .emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") - .emitStatement("int objects = %1$sList.size()", fieldName) - .beginControlFlow("for (int i = 0; i < objects; i++)") - .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("%1$sOsList.setRow(i, cacheItemIndex%1$s)", fieldName) - .endControlFlow() - .nextControlFlow("else") - .emitStatement("%1$sOsList.removeAll()", fieldName) - .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("%1$sOsList.addRow(cacheItemIndex%1$s)", fieldName) - .endControlFlow() - .endControlFlow() - .endControlFlow() - .emitEmptyLine(); - - } else if (Utils.isRealmValueList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); - writer - .emitEmptyLine() - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) - .emitStatement("%1$sOsList.removeAll()", fieldName) - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", - genericType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .beginControlFlow("if (%1$sItem == null)", fieldName) - .emitStatement("%1$sOsList.addNull()", fieldName) - .nextControlFlow("else") - .emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) - .endControlFlow() - .endControlFlow() - .endControlFlow() - .emitEmptyLine(); - } else { - if (metadata.getPrimaryKey() != field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, true); - } - } - //@formatter:on - } - - writer.emitStatement("return rowIndex"); - - writer.endMethod() - .emitEmptyLine(); - } - - private void emitInsertOrUpdateListMethod(JavaWriter writer) throws IOException { - writer.beginMethod( - "void", // Return type - "insertOrUpdate", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", "Iterator", "objects", "Map", "cache" // Argument type & argument name - ); - - writer.emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName); - writer.emitStatement("long tableNativePtr = table.getNativePtr()"); - writer.emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName); - if (metadata.hasPrimaryKey()) { - writer.emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())); - } - writer.emitStatement("%s object = null", qualifiedJavaClassName); - - writer.beginControlFlow("while (objects.hasNext())"); - writer.emitStatement("object = (%s) objects.next()", qualifiedJavaClassName); - writer.beginControlFlow("if (cache.containsKey(object))") - .emitStatement("continue") - .endControlFlow(); - - writer.beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath()))"); - writer.emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex())") - .emitStatement("continue"); - writer.endControlFlow(); - addPrimaryKeyCheckIfNeeded(metadata, false, writer); - - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); - String fieldType = field.asType().toString(); - String getter = metadata.getInternalGetter(fieldName); - - //@formatter:off - if (Utils.isRealmModel(field)) { - writer - .emitEmptyLine() - .emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sObj != null)", fieldName) - .emitStatement("Long cache%1$s = cache.get(%1$sObj)", fieldName) - .beginControlFlow("if (cache%s == null)", fieldName) - .emitStatement("cache%1$s = %2$s.insertOrUpdate(realm, %1$sObj, cache)", - fieldName, - Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1$sIndex, rowIndex, cache%1$s, false)", fieldName) - .nextControlFlow("else") - // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) - .endControlFlow(); - } else if (Utils.isRealmModelList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - writer - .emitEmptyLine() - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) - .beginControlFlow("if (%1$sList != null && %1$sList.size() == %1$sOsList.size())", fieldName) - .emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") - .emitStatement("int objectCount = %1$sList.size()", fieldName) - .beginControlFlow("for (int i = 0; i < objectCount; i++)") - .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("%1$sOsList.setRow(i, cacheItemIndex%1$s)", fieldName) - .endControlFlow() - .nextControlFlow("else") - .emitStatement("%1$sOsList.removeAll()", fieldName) - .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .emitStatement("Long cacheItemIndex%1$s = cache.get(%1$sItem)", fieldName) - .beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - .emitStatement("cacheItemIndex%1$s = %2$s.insertOrUpdate(realm, %1$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - .endControlFlow() - .emitStatement("%1$sOsList.addRow(cacheItemIndex%1$s)", fieldName) - .endControlFlow() - .endControlFlow() - .endControlFlow() - .emitEmptyLine(); - - } else if (Utils.isRealmValueList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); - writer - .emitEmptyLine() - .emitStatement("OsList %1$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1$sIndex)", fieldName) - .emitStatement("%1$sOsList.removeAll()", fieldName) - .emitStatement("RealmList<%s> %sList = ((%s) object).%s()", - genericType, fieldName, interfaceName, getter) - .beginControlFlow("if (%sList != null)", fieldName) - .beginControlFlow("for (%1$s %2$sItem : %2$sList)", genericType, fieldName) - .beginControlFlow("if (%1$sItem == null)", fieldName) - .emitStatement("%1$sOsList.addNull()", fieldName) - .nextControlFlow("else") - .emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", - fieldName + "Item", elementTypeMirror)) - .endControlFlow() - .endControlFlow() - .endControlFlow() - .emitEmptyLine(); - } else { - if (metadata.getPrimaryKey() != field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, true); - } - } - //@formatter:on - } - writer.endControlFlow(); - - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void addPrimaryKeyCheckIfNeeded(ClassMetaData metadata, boolean throwIfPrimaryKeyDuplicate, JavaWriter writer) throws IOException { - if (metadata.hasPrimaryKey()) { - String primaryKeyGetter = metadata.getPrimaryKeyGetter(); - VariableElement primaryKeyElement = metadata.getPrimaryKey(); - if (metadata.isNullable(primaryKeyElement)) { - //@formatter:off - if (Utils.isString(primaryKeyElement)) { - writer - .emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = Table.NO_MATCH") - .beginControlFlow("if (primaryKeyValue == null)") - .emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") - .nextControlFlow("else") - .emitStatement("rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue)") - .endControlFlow(); - } else { - writer - .emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - .emitStatement("long rowIndex = Table.NO_MATCH") - .beginControlFlow("if (primaryKeyValue == null)") - .emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") - .nextControlFlow("else") - .emitStatement("rowIndex = Table.nativeFindFirstInt(tableNativePtr, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) - .endControlFlow(); - } - //@formatter:on - } else { - writer.emitStatement("long rowIndex = Table.NO_MATCH"); - writer.emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter); - writer.beginControlFlow("if (primaryKeyValue != null)"); - - if (Utils.isString(metadata.getPrimaryKey())) { - writer.emitStatement("rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, (String)primaryKeyValue)"); - } else { - writer.emitStatement("rowIndex = Table.nativeFindFirstInt(tableNativePtr, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter); - } - writer.endControlFlow(); - } - - writer.beginControlFlow("if (rowIndex == Table.NO_MATCH)"); - if (Utils.isString(metadata.getPrimaryKey())) { - writer.emitStatement( - "rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue)"); - } else { - writer.emitStatement( - "rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, ((%s) object).%s())", - interfaceName, primaryKeyGetter); - } - - if (throwIfPrimaryKeyDuplicate) { - writer.nextControlFlow("else"); - writer.emitStatement("Table.throwDuplicatePrimaryKeyException(primaryKeyValue)"); - } - - writer.endControlFlow(); - writer.emitStatement("cache.put(object, rowIndex)"); - } else { - writer.emitStatement("long rowIndex = OsObject.createRow(table)"); - writer.emitStatement("cache.put(object, rowIndex)"); - } - } - - private void emitCopyMethod(JavaWriter writer) throws IOException { - writer - .beginMethod( - qualifiedJavaClassName, // Return type - "copy", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - "Realm", "realm", - columnInfoClassName(), "columnInfo", - qualifiedJavaClassName, "newObject", - "boolean", "update", - "Map", "cache", - "Set", "flags" - - ); // Argument type & argument name - - writer - .emitStatement("RealmObjectProxy cachedRealmObject = cache.get(newObject)") - .beginControlFlow("if (cachedRealmObject != null)") - .emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName) - .endControlFlow() - .emitEmptyLine(); - - writer - .emitStatement("%1$s realmObjectSource = (%1$s) newObject", interfaceName) - .emitEmptyLine() - .emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) - .emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags)"); - - // Copy basic types - writer - .emitEmptyLine() - .emitSingleLineComment("Add all non-\"object reference\" fields"); - for (RealmFieldElement field : metadata.getBasicTypeFields()) { - String fieldIndex = fieldIndexVariableReference(field); - String fieldName = field.getSimpleName().toString(); - String getter = metadata.getInternalGetter(fieldName); - writer.emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldIndex, getter); - } - - // Create the underlying object - writer - .emitEmptyLine() - .emitSingleLineComment("Create the underlying object and cache it before setting any object/objectlist references") - .emitSingleLineComment("This will allow us to break any circular dependencies by using the object cache.") - .emitStatement("Row row = builder.createNewObject()") - .emitStatement("%s realmObjectCopy = newProxyInstance(realm, row)", qualifiedGeneratedClassName) - .emitStatement("cache.put(newObject, realmObjectCopy)"); - - // Copy all object references or lists-of-objects - writer.emitEmptyLine(); - if (!metadata.getObjectReferenceFields().isEmpty()) { - writer.emitSingleLineComment("Finally add all fields that reference other Realm Objects, either directly or through a list"); - } - for (RealmFieldElement field : metadata.getObjectReferenceFields()) { - String fieldType = field.asType().toString(); - String fieldName = field.getSimpleName().toString(); - String getter = metadata.getInternalGetter(fieldName); - String setter = metadata.getInternalSetter(fieldName); - - if (Utils.isRealmModel(field)) { - writer - .emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) - .beginControlFlow("if (%sObj == null)", fieldName) - .emitStatement("realmObjectCopy.%s(null)", setter) - .nextControlFlow("else") - .emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("realmObjectCopy.%s(cache%s)", setter, fieldName) - .nextControlFlow("else") - .emitStatement("realmObjectCopy.%s(%s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, update, cache, flags))", - setter, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) - .endControlFlow() - // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .endControlFlow() - .emitEmptyLine(); - - } else if (Utils.isRealmModelList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - writer - .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) - .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("RealmList<%s> %sRealmList = realmObjectCopy.%s()", - genericType, fieldName, getter) - // Clear is needed. See bug https://github.com/realm/realm-java/issues/4957 - .emitStatement("%sRealmList.clear()", fieldName) - .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) - .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%1$sRealmList.add(cache%1$s)", fieldName) - .nextControlFlow("else") - .emitStatement("%1$sRealmList.add(%2$s.copyOrUpdate(realm, (%3$s) realm.getSchema().getColumnInfo(%4$s.class), %1$sItem, update, cache, flags))", - fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) - .endControlFlow() - .endControlFlow() - .endControlFlow() - .emitEmptyLine(); - } else { - throw new IllegalStateException("Unsupported field: " + field); - } - } - - writer - .emitStatement("return realmObjectCopy") - .endMethod() - .emitEmptyLine(); - } - - //@formatter:off - private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException { - writer.beginMethod( - qualifiedJavaClassName, // Return type - "createDetachedCopy", // Method name - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), // Modifiers - qualifiedJavaClassName, "realmObject", "int", "currentDepth", "int", "maxDepth", "Map>", "cache"); - writer - .beginControlFlow("if (currentDepth > maxDepth || realmObject == null)") - .emitStatement("return null") - .endControlFlow() - .emitStatement("CacheData cachedObject = cache.get(realmObject)") - .emitStatement("%s unmanagedObject", qualifiedJavaClassName) - .beginControlFlow("if (cachedObject == null)") - .emitStatement("unmanagedObject = new %s()", qualifiedJavaClassName) - .emitStatement("cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject))") - .nextControlFlow("else") - .emitSingleLineComment("Reuse cached object or recreate it because it was encountered at a lower depth.") - .beginControlFlow("if (currentDepth >= cachedObject.minDepth)") - .emitStatement("return (%s) cachedObject.object", qualifiedJavaClassName) - .endControlFlow() - .emitStatement("unmanagedObject = (%s) cachedObject.object", qualifiedJavaClassName) - .emitStatement("cachedObject.minDepth = currentDepth") - .endControlFlow(); - - // may cause an unused variable warning if the object contains only null lists - writer.emitStatement("%1$s unmanagedCopy = (%1$s) unmanagedObject", interfaceName) - .emitStatement("%1$s realmSource = (%1$s) realmObject", interfaceName); - - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); - String setter = metadata.getInternalSetter(fieldName); - String getter = metadata.getInternalGetter(fieldName); - - if (Utils.isRealmModel(field)) { - writer - .emitEmptyLine() - .emitSingleLineComment("Deep copy of %s", fieldName) - .emitStatement("unmanagedCopy.%s(%s.createDetachedCopy(realmSource.%s(), currentDepth + 1, maxDepth, cache))", - setter, Utils.getProxyClassSimpleName(field), getter); - } else if (Utils.isRealmModelList(field)) { - writer - .emitEmptyLine() - .emitSingleLineComment("Deep copy of %s", fieldName) - .beginControlFlow("if (currentDepth == maxDepth)") - .emitStatement("unmanagedCopy.%s(null)", setter) - .nextControlFlow("else") - .emitStatement("RealmList<%s> managed%sList = realmSource.%s()", - Utils.getGenericTypeQualifiedName(field), fieldName, getter) - .emitStatement("RealmList<%1$s> unmanaged%2$sList = new RealmList<%1$s>()", Utils.getGenericTypeQualifiedName(field), fieldName) - .emitStatement("unmanagedCopy.%s(unmanaged%sList)", setter, fieldName) - .emitStatement("int nextDepth = currentDepth + 1") - .emitStatement("int size = managed%sList.size()", fieldName) - .beginControlFlow("for (int i = 0; i < size; i++)") - .emitStatement("%s item = %s.createDetachedCopy(managed%sList.get(i), nextDepth, maxDepth, cache)", - Utils.getGenericTypeQualifiedName(field), Utils.getProxyClassSimpleName(field), fieldName) - .emitStatement("unmanaged%sList.add(item)", fieldName) - .endControlFlow() - .endControlFlow(); - } else if (Utils.isRealmValueList(field)) { - writer - .emitEmptyLine() - .emitStatement("unmanagedCopy.%1$s(new RealmList<%2$s>())", setter, Utils.getGenericTypeQualifiedName(field)) - .emitStatement("unmanagedCopy.%1$s().addAll(realmSource.%1$s())", getter); - } else if (Utils.isMutableRealmInteger(field)) { - // If the user initializes the unmanaged MutableRealmInteger to null, this will fail mysteriously. - writer.emitStatement("unmanagedCopy.%s().set(realmSource.%s().get())", getter, getter); - } else { - writer.emitStatement("unmanagedCopy.%s(realmSource.%s())", setter, getter); - } - } - - writer.emitEmptyLine(); - writer.emitStatement("return unmanagedObject"); - writer.endMethod(); - writer.emitEmptyLine(); - } - //@formatter:on - - private void emitUpdateMethod(JavaWriter writer) throws IOException { - if (!metadata.hasPrimaryKey()) { - return; - } - - writer.beginMethod( - qualifiedJavaClassName, // Return type - "update", // Method name - EnumSet.of(Modifier.STATIC), // Modifiers - "Realm", "realm", // Argument type & argument name - columnInfoClassName(), "columnInfo", - qualifiedJavaClassName, "realmObject", - qualifiedJavaClassName, "newObject", - "Map", "cache", - "Set", "flags" - ); - - writer - .emitStatement("%1$s realmObjectTarget = (%1$s) realmObject", interfaceName) - .emitStatement("%1$s realmObjectSource = (%1$s) newObject", interfaceName) - .emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) - .emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags)"); - - - for (RealmFieldElement field : metadata.getFields()) { - String fieldType = field.asType().toString(); - String fieldName = field.getSimpleName().toString(); - String getter = metadata.getInternalGetter(fieldName); - String fieldIndex = fieldIndexVariableReference(field); - - if (Utils.isRealmModel(field)) { - writer - .emitEmptyLine() - .emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) - .beginControlFlow("if (%sObj == null)", fieldName) - .emitStatement("builder.addNull(%s)", fieldIndexVariableReference(field)) - .nextControlFlow("else") - .emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("builder.addObject(%s, cache%s)", fieldIndex, fieldName) - .nextControlFlow("else") - .emitStatement("builder.addObject(%s, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, true, cache, flags))", - fieldIndex, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) - .endControlFlow() - // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - .endControlFlow(); - } else if (Utils.isRealmModelList(field)) { - final String genericType = Utils.getGenericTypeQualifiedName(field); - writer - .emitEmptyLine() - .emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) - .beginControlFlow("if (%sList != null)", fieldName) - .emitStatement("RealmList<%s> %sManagedCopy = new RealmList<%s>()", genericType, fieldName, genericType) - .beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - .emitStatement("%1$s %2$sItem = %2$sList.get(i)", genericType, fieldName) - .emitStatement("%1$s cache%2$s = (%1$s) cache.get(%2$sItem)", genericType, fieldName) - .beginControlFlow("if (cache%s != null)", fieldName) - .emitStatement("%1$sManagedCopy.add(cache%1$s)", fieldName) - .nextControlFlow("else") - .emitStatement("%1$sManagedCopy.add(%2$s.copyOrUpdate(realm, (%3$s) realm.getSchema().getColumnInfo(%4$s.class), %1$sItem, true, cache, flags))", - fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) - .endControlFlow() - .endControlFlow() - .emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldIndex, fieldName) - .nextControlFlow("else") - .emitStatement("builder.addObjectList(%s, new RealmList<%s>())", fieldIndex, genericType) - .endControlFlow(); - } else { - writer - .emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldIndex, getter); - } - } - - writer - .emitEmptyLine() - .emitStatement("builder.updateExistingObject()") - .emitStatement("return realmObject"); - - writer - .endMethod() - .emitEmptyLine(); - } - - private void emitToStringMethod(JavaWriter writer) throws IOException { - if (metadata.containsToString()) { - return; - } - writer.emitAnnotation("Override"); - writer.emitAnnotation("SuppressWarnings", "\"ArrayToString\"") - .beginMethod("String", "toString", EnumSet.of(Modifier.PUBLIC)) - .beginControlFlow("if (!RealmObject.isValid(this))") - .emitStatement("return \"Invalid object\"") - .endControlFlow(); - writer.emitStatement("StringBuilder stringBuilder = new StringBuilder(\"%s = proxy[\")", simpleJavaClassName); - - Collection fields = metadata.getFields(); - int i = fields.size() - 1; - for (VariableElement field : fields) { - String fieldName = field.getSimpleName().toString(); - - writer.emitStatement("stringBuilder.append(\"{%s:\")", fieldName); - if (Utils.isRealmModel(field)) { - String fieldTypeSimpleName = Utils.stripPackage(Utils.getFieldTypeQualifiedName(field)); - writer.emitStatement( - "stringBuilder.append(%s() != null ? \"%s\" : \"null\")", - metadata.getInternalGetter(fieldName), - fieldTypeSimpleName - ); - } else if (Utils.isRealmList(field)) { - String genericTypeSimpleName = Utils.stripPackage(Utils.getGenericTypeQualifiedName(field)); - writer.emitStatement("stringBuilder.append(\"RealmList<%s>[\").append(%s().size()).append(\"]\")", - genericTypeSimpleName, - metadata.getInternalGetter(fieldName)); - } else if (Utils.isMutableRealmInteger(field)) { - writer.emitStatement("stringBuilder.append(%s().get())", metadata.getInternalGetter(fieldName)); - } else { - if (metadata.isNullable(field)) { - writer.emitStatement("stringBuilder.append(%s() != null ? %s() : \"null\")", - metadata.getInternalGetter(fieldName), - metadata.getInternalGetter(fieldName) - ); - } else { - writer.emitStatement("stringBuilder.append(%s())", metadata.getInternalGetter(fieldName)); - } - } - writer.emitStatement("stringBuilder.append(\"}\")"); - - if (i-- > 0) { - writer.emitStatement("stringBuilder.append(\",\")"); - } - } - - writer.emitStatement("stringBuilder.append(\"]\")"); - writer.emitStatement("return stringBuilder.toString()"); - writer.endMethod() - .emitEmptyLine(); - } - - /** - * Currently, the hash value emitted from this could suddenly change as an object's index might - * alternate due to Realm Java using {@code Table#moveLastOver()}. Hash codes should therefore not - * be considered stable, i.e. don't save them in a HashSet or use them as a key in a HashMap. - */ - //@formatter:off - private void emitHashcodeMethod(JavaWriter writer) throws IOException { - if (metadata.containsHashCode()) { - return; - } - writer.emitAnnotation("Override") - .beginMethod("int", "hashCode", EnumSet.of(Modifier.PUBLIC)) - .emitStatement("String realmName = proxyState.getRealm$realm().getPath()") - .emitStatement("String tableName = proxyState.getRow$realm().getTable().getName()") - .emitStatement("long rowIndex = proxyState.getRow$realm().getIndex()") - .emitEmptyLine() - .emitStatement("int result = 17") - .emitStatement("result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0)") - .emitStatement("result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0)") - .emitStatement("result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32))") - .emitStatement("return result") - .endMethod() - .emitEmptyLine(); - } - //@formatter:on - - //@formatter:off - private void emitEqualsMethod(JavaWriter writer) throws IOException { - if (metadata.containsEquals()) { - return; - } - String proxyClassName = Utils.getProxyClassName(qualifiedJavaClassName); - String otherObjectVarName = "a" + simpleJavaClassName; - writer.emitAnnotation("Override") - .beginMethod("boolean", "equals", EnumSet.of(Modifier.PUBLIC), "Object", "o") - .emitStatement("if (this == o) return true") - .emitStatement("if (o == null || getClass() != o.getClass()) return false") - .emitStatement("%s %s = (%s)o", proxyClassName, otherObjectVarName, proxyClassName) // FooRealmProxy aFoo = (FooRealmProxy)o - .emitEmptyLine() - .emitStatement("String path = proxyState.getRealm$realm().getPath()") - .emitStatement("String otherPath = %s.proxyState.getRealm$realm().getPath()", otherObjectVarName) - .emitStatement("if (path != null ? !path.equals(otherPath) : otherPath != null) return false") - .emitEmptyLine() - .emitStatement("String tableName = proxyState.getRow$realm().getTable().getName()") - .emitStatement("String otherTableName = %s.proxyState.getRow$realm().getTable().getName()", otherObjectVarName) - .emitStatement("if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false") - .emitEmptyLine() - .emitStatement("if (proxyState.getRow$realm().getIndex() != %s.proxyState.getRow$realm().getIndex()) return false", otherObjectVarName) - .emitEmptyLine() - .emitStatement("return true") - .endMethod(); - } - //@formatter:on - - private void emitCreateOrUpdateUsingJsonObject(JavaWriter writer) throws IOException { - writer.emitAnnotation("SuppressWarnings", "\"cast\""); - writer.beginMethod( - qualifiedJavaClassName, - "createOrUpdateUsingJsonObject", - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), - Arrays.asList("Realm", "realm", "JSONObject", "json", "boolean", "update"), - Collections.singletonList("JSONException")); - - final int modelOrListCount = countModelOrListFields(metadata.getFields()); - if (modelOrListCount == 0) { - writer.emitStatement("final List excludeFields = Collections. emptyList()"); - } else { - writer.emitStatement("final List excludeFields = new ArrayList(%1$d)", - modelOrListCount); - } - - //@formatter:off - if (!metadata.hasPrimaryKey()) { - buildExcludeFieldsList(writer, metadata.getFields()); - writer.emitStatement("%s obj = realm.createObjectInternal(%s.class, true, excludeFields)", - qualifiedJavaClassName, qualifiedJavaClassName); - } else { - String pkType = Utils.isString(metadata.getPrimaryKey()) ? "String" : "Long"; - writer - .emitStatement("%s obj = null", qualifiedJavaClassName) - .beginControlFlow("if (update)") - .emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) - .emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", - columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) - .emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.getPrimaryKey())) - .emitStatement("long rowIndex = Table.NO_MATCH"); - if (metadata.isNullable(metadata.getPrimaryKey())) { - writer - .beginControlFlow("if (json.isNull(\"%s\"))", metadata.getPrimaryKey().getSimpleName()) - .emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") - .nextControlFlow("else") - .emitStatement( - "rowIndex = table.findFirst%s(pkColumnIndex, json.get%s(\"%s\"))", - pkType, pkType, metadata.getPrimaryKey().getSimpleName()) - .endControlFlow(); - } else { - writer - .beginControlFlow("if (!json.isNull(\"%s\"))", metadata.getPrimaryKey().getSimpleName()) - .emitStatement( - "rowIndex = table.findFirst%s(pkColumnIndex, json.get%s(\"%s\"))", - pkType, pkType, metadata.getPrimaryKey().getSimpleName()) - .endControlFlow(); - } - writer - .beginControlFlow("if (rowIndex != Table.NO_MATCH)") - .emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") - .beginControlFlow("try") - .emitStatement( - "objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", - qualifiedJavaClassName) - .emitStatement("obj = new %s()", qualifiedGeneratedClassName) - .nextControlFlow("finally") - .emitStatement("objectContext.clear()") - .endControlFlow() - .endControlFlow() - .endControlFlow(); - - writer.beginControlFlow("if (obj == null)"); - buildExcludeFieldsList(writer, metadata.getFields()); - String primaryKeyFieldType = metadata.getPrimaryKey().asType().toString(); - String primaryKeyFieldName = metadata.getPrimaryKey().getSimpleName().toString(); - RealmJsonTypeHelper.emitCreateObjectWithPrimaryKeyValue( - qualifiedJavaClassName, qualifiedGeneratedClassName, primaryKeyFieldType, primaryKeyFieldName, writer); - writer.endControlFlow(); - } - //@formatter:on - - writer - .emitEmptyLine() - .emitStatement("final %1$s objProxy = (%1$s) obj", interfaceName); - for (VariableElement field : metadata.getFields()) { - String fieldName = field.getSimpleName().toString(); - String qualifiedFieldType = field.asType().toString(); - if (metadata.isPrimaryKey(field)) { - // Primary key has already been set when adding new row or finding the existing row. - continue; - } - if (Utils.isRealmModel(field)) { - RealmJsonTypeHelper.emitFillRealmObjectWithJsonValue( - "objProxy", - metadata.getInternalSetter(fieldName), - fieldName, - qualifiedFieldType, - Utils.getProxyClassSimpleName(field), - writer - ); - - } else if (Utils.isRealmModelList(field)) { - RealmJsonTypeHelper.emitFillRealmListWithJsonValue( - "objProxy", - metadata.getInternalGetter(fieldName), - metadata.getInternalSetter(fieldName), - fieldName, - ((DeclaredType) field.asType()).getTypeArguments().get(0).toString(), - Utils.getProxyClassSimpleName(field), - writer); - - } else if (Utils.isRealmValueList(field)) { - writer.emitStatement("ProxyUtils.setRealmListWithJsonObject(objProxy.%1$s(), json, \"%2$s\")", - metadata.getInternalGetter(fieldName), fieldName); - } else if (Utils.isMutableRealmInteger(field)) { - RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( - "objProxy", - metadata.getInternalGetter(fieldName), - fieldName, - qualifiedFieldType, - writer); - - } else { - RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( - "objProxy", - metadata.getInternalSetter(fieldName), - fieldName, - qualifiedFieldType, - writer - ); - } - } - - writer.emitStatement("return obj"); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void buildExcludeFieldsList(JavaWriter writer, Collection fields) throws IOException { - for (VariableElement field : fields) { - if (Utils.isRealmModel(field) || Utils.isRealmList(field)) { - final String fieldName = field.getSimpleName().toString(); - writer.beginControlFlow("if (json.has(\"%1$s\"))", fieldName) - .emitStatement("excludeFields.add(\"%1$s\")", fieldName) - .endControlFlow(); - } - } - } - - // Since we need to check the PK in stream before creating the object, this is now using copyToRealm - // instead of createObject() to avoid parsing the stream twice. - private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { - writer.emitAnnotation("SuppressWarnings", "\"cast\""); - writer.emitAnnotation("TargetApi", "Build.VERSION_CODES.HONEYCOMB"); - writer.beginMethod( - qualifiedJavaClassName, - "createUsingJsonStream", - EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), - Arrays.asList("Realm", "realm", "JsonReader", "reader"), - Collections.singletonList("IOException")); - - if (metadata.hasPrimaryKey()) { - writer.emitStatement("boolean jsonHasPrimaryKey = false"); - } - writer.emitStatement("final %s obj = new %s()", qualifiedJavaClassName, qualifiedJavaClassName); - writer.emitStatement("final %1$s objProxy = (%1$s) obj", interfaceName); - writer.emitStatement("reader.beginObject()"); - writer.beginControlFlow("while (reader.hasNext())"); - writer.emitStatement("String name = reader.nextName()"); - writer.beginControlFlow("if (false)"); - Collection fields = metadata.getFields(); - for (VariableElement field : fields) { - String fieldName = field.getSimpleName().toString(); - String qualifiedFieldType = field.asType().toString(); - writer.nextControlFlow("else if (name.equals(\"%s\"))", fieldName); - - if (Utils.isRealmModel(field)) { - RealmJsonTypeHelper.emitFillRealmObjectFromStream( - "objProxy", - metadata.getInternalSetter(fieldName), - fieldName, - qualifiedFieldType, - Utils.getProxyClassSimpleName(field), - writer - ); - - } else if (Utils.isRealmModelList(field)) { - RealmJsonTypeHelper.emitFillRealmListFromStream( - "objProxy", - metadata.getInternalGetter(fieldName), - metadata.getInternalSetter(fieldName), - ((DeclaredType) field.asType()).getTypeArguments().get(0).toString(), - Utils.getProxyClassSimpleName(field), - writer); - - } else if (Utils.isRealmValueList(field)) { - writer.emitStatement("objProxy.%1$s(ProxyUtils.createRealmListWithJsonStream(%2$s.class, reader))", - metadata.getInternalSetter(fieldName), - Utils.getRealmListType(field)); - } else if (Utils.isMutableRealmInteger(field)) { - RealmJsonTypeHelper.emitFillJavaTypeFromStream( - "objProxy", - metadata, - metadata.getInternalGetter(fieldName), - fieldName, - qualifiedFieldType, - writer - ); - } else { - RealmJsonTypeHelper.emitFillJavaTypeFromStream( - "objProxy", - metadata, - metadata.getInternalSetter(fieldName), - fieldName, - qualifiedFieldType, - writer - ); - } - } - - writer.nextControlFlow("else"); - writer.emitStatement("reader.skipValue()"); - writer.endControlFlow(); - - writer.endControlFlow(); - writer.emitStatement("reader.endObject()"); - - if (metadata.hasPrimaryKey()) { - writer.beginControlFlow("if (!jsonHasPrimaryKey)") - .emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.getPrimaryKey()) - .endControlFlow(); - } - - writer.emitStatement("return realm.copyToRealm(obj)"); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private String columnInfoClassName() { - return simpleJavaClassName + "ColumnInfo"; - } - - /** - * Returns the name of the ColumnInfo class for the model class referenced in the field. - * I.e. for `com.test.Person`, it returns `Person.PersonColumnInfo` - */ - private String columnInfoClassName(VariableElement field) { - String qualfiedModelClassName = Utils.getModelClassQualifiedName(field); - return Utils.getSimpleColumnInfoClassName(qualfiedModelClassName); - } - - private String columnIndexVarName(VariableElement variableElement) { - return variableElement.getSimpleName().toString() + "Index"; - } - - private String mutableRealmIntegerFieldName(VariableElement variableElement) { - return variableElement.getSimpleName().toString() + "MutableRealmInteger"; - } - - private String fieldIndexVariableReference(VariableElement variableElement) { - return "columnInfo." + columnIndexVarName(variableElement); - } - - private static int countModelOrListFields(Collection fields) { - int count = 0; - for (VariableElement f : fields) { - if (Utils.isRealmModel(f) || Utils.isRealmList(f)) { - count++; - } - } - return count; - } - - private Constants.RealmFieldType getRealmType(VariableElement field) { - String fieldTypeCanonicalName = field.asType().toString(); - Constants.RealmFieldType type = Constants.JAVA_TO_REALM_TYPES.get(fieldTypeCanonicalName); - if (type != null) { - return type; - } - if (Utils.isMutableRealmInteger(field)) { - return Constants.RealmFieldType.REALM_INTEGER; - } - if (Utils.isRealmModel(field)) { - return Constants.RealmFieldType.OBJECT; - } - if (Utils.isRealmModelList(field)) { - return Constants.RealmFieldType.LIST; - } - if (Utils.isRealmValueList(field)) { - final Constants.RealmFieldType fieldType = Utils.getValueListFieldType(field); - if (fieldType == null) { - return Constants.RealmFieldType.NOTYPE; - } - return fieldType; - } - return Constants.RealmFieldType.NOTYPE; - } - - private Constants.RealmFieldType getRealmTypeChecked(VariableElement field) { - Constants.RealmFieldType type = getRealmType(field); - if (type == Constants.RealmFieldType.NOTYPE) { - throw new IllegalStateException("Unsupported type " + field.asType().toString()); - } - return type; - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt new file mode 100644 index 0000000000..985a6ec120 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -0,0 +1,2012 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import com.squareup.javawriter.JavaWriter + +import java.io.BufferedWriter +import java.io.IOException +import java.util.ArrayList +import java.util.Arrays +import java.util.Collections +import java.util.EnumSet +import java.util.Locale + +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.Modifier +import javax.lang.model.element.VariableElement +import javax.lang.model.type.DeclaredType +import javax.lang.model.type.TypeMirror + +import io.realm.processor.ext.beginMethod +import io.realm.processor.ext.beginType + +/** + * This class is responsible for generating the Realm Proxy classes for each model class defined + * by the user. This is the main entrypoint for users interacting with Realm, but it is hidden + * from them as an implementation detail generated by the annotation processor. + * + * See [RealmProcessor] for a more detailed description on what files Realm creates internally and + * why. + * + * NOTE: This file will look strangely formatted to you. This is on purpose. The intent of the + * formatting it is to better represent the outputted code, not make _this_ code as readable as + * possible. This mean two things: + * + * 1. Attempt to keep code that emit a single line to one line here. + * 2. Attempt to indent the emit functions that would emulate the blocks created by the generated code. + */ +class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvironment, + private val typeMirrors: TypeMirrors, + private val metadata: ClassMetaData, + private val classCollection: ClassCollection) { + + private val simpleJavaClassName: SimpleClassName = metadata.simpleJavaClassName + private val qualifiedJavaClassName: QualifiedClassName = metadata.qualifiedClassName + private val internalClassName: String = metadata.internalClassName + private val interfaceName: SimpleClassName = Utils.getProxyInterfaceName(qualifiedJavaClassName) + private val generatedClassName: QualifiedClassName = QualifiedClassName(String.format(Locale.US, "%s.%s", Constants.REALM_PACKAGE_NAME, Utils.getProxyClassName(qualifiedJavaClassName))) + // See the configuration for the Android debug build type, + // in the realm-library project, for an example of how to set this flag. + private val suppressWarnings: Boolean = !"false".equals(processingEnvironment.options[OPTION_SUPPRESS_WARNINGS], ignoreCase = true) + + @Throws(IOException::class, UnsupportedOperationException::class) + fun generate() { + val sourceFile = processingEnvironment.filer.createSourceFile(generatedClassName.toString()) + + val imports = ArrayList(IMPORTS) + if (metadata.backlinkFields.isNotEmpty()) { + imports.add("io.realm.internal.UncheckedRow") + } + + val writer = JavaWriter(BufferedWriter(sourceFile.openWriter())) + writer.apply { + indent = Constants.INDENT // Set source code indent + emitPackage(Constants.REALM_PACKAGE_NAME) + emitEmptyLine() + emitImports(imports) + emitEmptyLine() + + // Begin the class definition + if (suppressWarnings) { + emitAnnotation("SuppressWarnings(\"all\")") + } + beginType(generatedClassName, "class", setOf(Modifier.PUBLIC), qualifiedJavaClassName, arrayOf("RealmObjectProxy", interfaceName.toString())) + emitEmptyLine() + + // Emit class content + emitColumnInfoClass(writer) + emitClassFields(writer) + emitInstanceFields(writer) + emitConstructor(writer) + emitInjectContextMethod(writer) + emitPersistedFieldAccessors(writer) + emitBacklinkFieldAccessors(writer) + emitCreateExpectedObjectSchemaInfo(writer) + emitGetExpectedObjectSchemaInfo(writer) + emitCreateColumnInfoMethod(writer) + emitGetSimpleClassNameMethod(writer) + emitCreateOrUpdateUsingJsonObject(writer) + emitCreateUsingJsonStream(writer) + emitNewProxyInstance(writer) + emitCopyOrUpdateMethod(writer) + emitCopyMethod(writer) + emitInsertMethod(writer) + emitInsertListMethod(writer) + emitInsertOrUpdateMethod(writer) + emitInsertOrUpdateListMethod(writer) + emitCreateDetachedCopyMethod(writer) + emitUpdateMethod(writer) + emitToStringMethod(writer) + emitRealmObjectProxyImplementation(writer) + emitHashcodeMethod(writer) + emitEqualsMethod(writer) + + // End the class definition + endType() + close() + } + } + + @Throws(IOException::class) + private fun emitColumnInfoClass(writer: JavaWriter) { + writer.apply { + beginType(columnInfoClassName(), "class", EnumSet.of(Modifier.STATIC, Modifier.FINAL), "ColumnInfo") // base class + + // fields + emitField("long", "maxColumnIndexValue") // Must not end with Index as it otherwise could conflict regular fields. + for (variableElement in metadata.fields) { + emitField("long", columnIndexVarName(variableElement)) + } + emitEmptyLine() + + // constructor #1 + beginConstructor(EnumSet.noneOf(Modifier::class.java), "OsSchemaInfo", "schemaInfo") + emitStatement("super(%s)", metadata.fields.size) + emitStatement("OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo(\"%1\$s\")", internalClassName) + for (field in metadata.fields) { + emitStatement("this.%1\$sIndex = addColumnDetails(\"%1\$s\", \"%2\$s\", objectSchemaInfo)", field.javaName, field.internalFieldName) + } + for (backlink in metadata.backlinkFields) { + emitStatement("addBacklinkDetails(schemaInfo, \"%s\", \"%s\", \"%s\")", backlink.targetField, classCollection.getClassFromQualifiedName(backlink.sourceClass!!).internalClassName, backlink.sourceField) + } + emitStatement("this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex()") + endConstructor() + emitEmptyLine() + + // constructor #2 + beginConstructor(EnumSet.noneOf(Modifier::class.java),"ColumnInfo", "src", "boolean", "mutable") + emitStatement("super(src, mutable)") + emitStatement("copy(src, this)") + endConstructor() + emitEmptyLine() + + // no-args copy method + emitAnnotation("Override") + beginMethod("ColumnInfo", "copy", EnumSet.of(Modifier.PROTECTED, Modifier.FINAL), "boolean", "mutable") + emitStatement("return new %s(this, mutable)", columnInfoClassName()) + endMethod() + emitEmptyLine() + + // copy method + emitAnnotation("Override") + beginMethod("void", "copy", EnumSet.of(Modifier.PROTECTED, Modifier.FINAL), "ColumnInfo", "rawSrc", "ColumnInfo", "rawDst") + emitStatement("final %1\$s src = (%1\$s) rawSrc", columnInfoClassName()) + emitStatement("final %1\$s dst = (%1\$s) rawDst", columnInfoClassName()) + for (variableElement in metadata.fields) { + emitStatement("dst.%1\$s = src.%1\$s", columnIndexVarName(variableElement)) + } + emitStatement("dst.maxColumnIndexValue = src.maxColumnIndexValue") + endMethod() + endType() + } + } + + @Throws(IOException::class) + private fun emitClassFields(writer: JavaWriter) { + writer.apply { + emitEmptyLine() + emitField("OsObjectSchemaInfo", "expectedObjectSchemaInfo", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL),"createExpectedObjectSchemaInfo()") + } + } + + @Throws(IOException::class) + private fun emitInstanceFields(writer: JavaWriter) { + writer.apply { + emitEmptyLine() + emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE)) + emitField("ProxyState<$qualifiedJavaClassName>", "proxyState", EnumSet.of(Modifier.PRIVATE)) + + for (variableElement in metadata.fields) { + if (Utils.isMutableRealmInteger(variableElement)) { + emitMutableRealmIntegerField(writer, variableElement) + } else if (Utils.isRealmList(variableElement)) { + val genericType = Utils.getGenericTypeQualifiedName(variableElement) + emitField("RealmList<$genericType>", variableElement.simpleName.toString() + "RealmList", EnumSet.of(Modifier.PRIVATE)) + } + } + + for (backlink in metadata.backlinkFields) { + emitField(backlink.targetFieldType, backlink.targetField + BACKLINKS_FIELD_EXTENSION, EnumSet.of(Modifier.PRIVATE)) + } + } + } + + // The anonymous subclass of MutableRealmInteger.Managed holds a reference to this proxy. + // Even if all other references to the proxy are dropped, the proxy will not be GCed until + // the MutableInteger that it owns, also becomes unreachable. + @Throws(IOException::class) + private fun emitMutableRealmIntegerField(writer: JavaWriter, variableElement: VariableElement) { + writer.apply { + emitField("MutableRealmInteger.Managed", + mutableRealmIntegerFieldName(variableElement), + EnumSet.of(Modifier.PRIVATE, Modifier.FINAL), + String.format( + "new MutableRealmInteger.Managed<%1\$s>() {\n" + + " @Override protected ProxyState<%1\$s> getProxyState() { return proxyState; }\n" + + " @Override protected long getColumnIndex() { return columnInfo.%2\$s; }\n" + + "}", + qualifiedJavaClassName, columnIndexVarName(variableElement))) + } + } + + @Throws(IOException::class) + private fun emitConstructor(writer: JavaWriter) { + writer.apply { + emitEmptyLine() + beginConstructor(EnumSet.noneOf(Modifier::class.java)) + emitStatement("proxyState.setConstructionFinished()") + endConstructor() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitPersistedFieldAccessors(writer: JavaWriter) { + for (field in metadata.fields) { + val fieldName = field.simpleName.toString() + val fieldTypeCanonicalName = field.asType().toString() + when { + Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeCanonicalName) -> emitPrimitiveType(writer, field, fieldName, fieldTypeCanonicalName) + Utils.isMutableRealmInteger(field) -> emitMutableRealmInteger(writer, field, fieldName, fieldTypeCanonicalName) + Utils.isRealmModel(field) -> emitRealmModel(writer, field, fieldName, fieldTypeCanonicalName) + Utils.isRealmList(field) -> { + val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) + emitRealmList(writer, field, fieldName, fieldTypeCanonicalName, elementTypeMirror) + } + else -> throw UnsupportedOperationException(String.format(Locale.US, "Field \"%s\" of type \"%s\" is not supported.", fieldName, fieldTypeCanonicalName)) + } + writer.emitEmptyLine() + } + } + + /** + * Emit Set/Get methods for Primitives and boxed types + */ + @Throws(IOException::class) + private fun emitPrimitiveType( + writer: JavaWriter, + field: VariableElement, + fieldName: String, + fieldTypeCanonicalName: String) { + + val fieldJavaType: String? = getRealmTypeChecked(field).javaType + + writer.apply { + // Getter - Start + emitAnnotation("Override") + emitAnnotation("SuppressWarnings", "\"cast\"") + beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) + emitStatement("proxyState.getRealm\$realm().checkIfValid()") + + // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here. + if (metadata.isNullable(field) && !Utils.isString(field) && !Utils.isByteArray(field)) { + beginControlFlow("if (proxyState.getRow\$realm().isNull(%s))", fieldIndexVariableReference(field)) + emitStatement("return null") + endControlFlow() + } + + // For Boxed types, this should be the corresponding primitive types. Others remain the same. + val castingBackType: String = if (Utils.isBoxedType(fieldTypeCanonicalName)) { + val typeUtils = processingEnvironment.typeUtils + typeUtils.unboxedType(field.asType()).toString() + } else { + fieldTypeCanonicalName + } + + emitStatement("return (%s) proxyState.getRow\$realm().get%s(%s)", castingBackType, fieldJavaType, fieldIndexVariableReference(field)) + endMethod() + emitEmptyLine() + // Getter - End + + // Setter - Start + emitAnnotation("Override") + beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value") + emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) { + // set value as default value + emitStatement("final Row row = proxyState.getRow\$realm()") + if (metadata.isNullable(field)) { + beginControlFlow("if (value == null)") + emitStatement("row.getTable().setNull(%s, row.getIndex(), true)", fieldIndexVariableReference(field)) + emitStatement("return") + endControlFlow() + } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { + beginControlFlow("if (value == null)") + emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + endControlFlow() + } + emitStatement("row.getTable().set%s(%s, row.getIndex(), value, true)", fieldJavaType, fieldIndexVariableReference(field)) + emitStatement("return") + } + emitStatement("proxyState.getRealm\$realm().checkIfValid()") + // Although setting null value for String and bytes[] can be handled by the JNI code, we still generate the same code here. + // Compared with getter, null value won't trigger more native calls in setter which is relatively cheaper. + if (metadata.isPrimaryKey(field)) { + // Primary key is not allowed to be changed after object created. + emitStatement(Constants.STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED, fieldName) + } else { + if (metadata.isNullable(field)) { + beginControlFlow("if (value == null)") + emitStatement("proxyState.getRow\$realm().setNull(%s)", fieldIndexVariableReference(field)) + emitStatement("return") + endControlFlow() + } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { + // Same reason, throw IAE earlier. + beginControlFlow("if (value == null)") + emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) + endControlFlow() + } + emitStatement("proxyState.getRow\$realm().set%s(%s, value)", fieldJavaType, fieldIndexVariableReference(field)) + } + endMethod() + // Setter - End + } + } + + /** + * Emit Get method for mutable Realm Integer fields. + */ + @Throws(IOException::class) + private fun emitMutableRealmInteger(writer: JavaWriter, field: VariableElement, fieldName: String, fieldTypeCanonicalName: String) { + writer.apply { + emitAnnotation("Override") + beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) + emitStatement("proxyState.getRealm\$realm().checkIfValid()") + emitStatement("return this.%s", mutableRealmIntegerFieldName(field)) + endMethod() + } + } + + /** + * Emit Set/Get methods for RealmModel fields. + */ + @Throws(IOException::class) + private fun emitRealmModel(writer: JavaWriter, + field: VariableElement, + fieldName: String, + fieldTypeCanonicalName: String) { + writer.apply { + // Getter - Start + emitAnnotation("Override") + beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) + emitStatement("proxyState.getRealm\$realm().checkIfValid()") + beginControlFlow("if (proxyState.getRow\$realm().isNullLink(%s))", fieldIndexVariableReference(field)) + emitStatement("return null") + endControlFlow() + emitStatement("return proxyState.getRealm\$realm().get(%s.class, proxyState.getRow\$realm().getLink(%s), false, Collections.emptyList())", fieldTypeCanonicalName, fieldIndexVariableReference(field)) + endMethod() + emitEmptyLine() + // Getter - End + + // Setter - Start + emitAnnotation("Override") + beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value") + emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) { + // check excludeFields + beginControlFlow("if (proxyState.getExcludeFields\$realm().contains(\"%1\$s\"))", field.simpleName.toString()) + emitStatement("return") + endControlFlow() + beginControlFlow("if (value != null && !RealmObject.isManaged(value))") + emitStatement("value = ((Realm) proxyState.getRealm\$realm()).copyToRealm(value)") + endControlFlow() + + // set value as default value + emitStatement("final Row row = proxyState.getRow\$realm()") + beginControlFlow("if (value == null)") + emitSingleLineComment("Table#nullifyLink() does not support default value. Just using Row.") + emitStatement("row.nullifyLink(%s)", fieldIndexVariableReference(field)) + emitStatement("return") + endControlFlow() + emitStatement("proxyState.checkValidObject(value)") + emitStatement("row.getTable().setLink(%s, row.getIndex(), ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getIndex(), true)", fieldIndexVariableReference(field)) + emitStatement("return") + } + emitStatement("proxyState.getRealm\$realm().checkIfValid()") + beginControlFlow("if (value == null)") + emitStatement("proxyState.getRow\$realm().nullifyLink(%s)", fieldIndexVariableReference(field)) + emitStatement("return") + endControlFlow() + emitStatement("proxyState.checkValidObject(value)") + emitStatement("proxyState.getRow\$realm().setLink(%s, ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getIndex())", fieldIndexVariableReference(field)) + endMethod() + // Setter - End + } + } + + /** + * Emit Set/Get methods for Realm Model Lists and Lists of primitives. + */ + @Throws(IOException::class) + private fun emitRealmList( + writer: JavaWriter, + field: VariableElement, + fieldName: String, + fieldTypeCanonicalName: String, + elementTypeMirror: TypeMirror?) { + + val genericType: QualifiedClassName? = Utils.getGenericTypeQualifiedName(field) + val forRealmModel: Boolean = Utils.isRealmModel(elementTypeMirror) + + writer.apply { + // Getter - Start + emitAnnotation("Override") + beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) + emitStatement("proxyState.getRealm\$realm().checkIfValid()") + emitSingleLineComment("use the cached value if available") + beginControlFlow("if (${fieldName}RealmList != null)") + emitStatement("return ${fieldName}RealmList") + nextControlFlow("else") + if (Utils.isRealmModelList(field)) { + emitStatement("OsList osList = proxyState.getRow\$realm().getModelList(%s)", fieldIndexVariableReference(field)) + } else { + emitStatement("OsList osList = proxyState.getRow\$realm().getValueList(%1\$s, RealmFieldType.%2\$s)", fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name) + } + emitStatement("${fieldName}RealmList = new RealmList<%s>(%s.class, osList, proxyState.getRealm\$realm())", genericType, genericType) + emitStatement("return ${fieldName}RealmList") + endControlFlow() + endMethod() + emitEmptyLine() + // Getter - End + + // Setter - Start + emitAnnotation("Override") + beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value") + emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) emitter@{ + // check excludeFields + beginControlFlow("if (proxyState.getExcludeFields\$realm().contains(\"%1\$s\"))", field.simpleName.toString()) + emitStatement("return") + endControlFlow() + + if (!forRealmModel) { + return@emitter + } + + emitSingleLineComment("if the list contains unmanaged RealmObjects, convert them to managed.") + beginControlFlow("if (value != null && !value.isManaged())") + emitStatement("final Realm realm = (Realm) proxyState.getRealm\$realm()") + emitStatement("final RealmList<%1\$s> original = value", genericType) + emitStatement("value = new RealmList<%1\$s>()", genericType) + beginControlFlow("for (%1\$s item : original)", genericType) + beginControlFlow("if (item == null || RealmObject.isManaged(item))") + emitStatement("value.add(item)") + nextControlFlow("else") + emitStatement("value.add(realm.copyToRealm(item))") + endControlFlow() + endControlFlow() + endControlFlow() + + // LinkView currently does not support default value feature. Just fallback to normal code. + } + + emitStatement("proxyState.getRealm\$realm().checkIfValid()") + if (Utils.isRealmModelList(field)) { + emitStatement("OsList osList = proxyState.getRow\$realm().getModelList(%s)", fieldIndexVariableReference(field)) + } else { + emitStatement("OsList osList = proxyState.getRow\$realm().getValueList(%1\$s, RealmFieldType.%2\$s)", fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name) + } + if (forRealmModel) { + // Model lists. + emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") + beginControlFlow("if (value != null && value.size() == osList.size())") + emitStatement("int objects = value.size()") + beginControlFlow("for (int i = 0; i < objects; i++)") + emitStatement("%s linkedObject = value.get(i)", genericType) + emitStatement("proxyState.checkValidObject(linkedObject)") + emitStatement("osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet\$proxyState().getRow\$realm().getIndex())") + endControlFlow() + nextControlFlow("else") + emitStatement("osList.removeAll()") + beginControlFlow("if (value == null)") + emitStatement("return") + endControlFlow() + emitStatement("int objects = value.size()") + beginControlFlow("for (int i = 0; i < objects; i++)") + emitStatement("%s linkedObject = value.get(i)", genericType) + emitStatement("proxyState.checkValidObject(linkedObject)") + emitStatement("osList.addRow(((RealmObjectProxy) linkedObject).realmGet\$proxyState().getRow\$realm().getIndex())") + endControlFlow() + endControlFlow() + } else { + // Value lists + emitStatement("osList.removeAll()") + beginControlFlow("if (value == null)") + emitStatement("return") + endControlFlow() + beginControlFlow("for (%1\$s item : value)", genericType) + beginControlFlow("if (item == null)") + emitStatement(if (metadata.isElementNullable(field)) "osList.addNull()" else "throw new IllegalArgumentException(\"Storing 'null' into $fieldName' is not allowed by the schema.\")") + nextControlFlow("else") + emitStatement(getStatementForAppendingValueToOsList("osList", "item", elementTypeMirror)) + endControlFlow() + endControlFlow() + } + endMethod() + // Setter - End + } + } + + private fun getStatementForAppendingValueToOsList( + osListVariableName: String, + valueVariableName: String, + elementTypeMirror: TypeMirror?): String { + + val typeUtils = processingEnvironment.typeUtils + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.STRING_MIRROR)) { + return "$osListVariableName.addString($valueVariableName)" + } + if ((typeUtils.isSameType(elementTypeMirror, typeMirrors.LONG_MIRROR) + || typeUtils.isSameType(elementTypeMirror, typeMirrors.INTEGER_MIRROR) + || typeUtils.isSameType(elementTypeMirror, typeMirrors.SHORT_MIRROR) + || typeUtils.isSameType(elementTypeMirror, typeMirrors.BYTE_MIRROR))) { + return "$osListVariableName.addLong($valueVariableName.longValue())" + } + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.BINARY_MIRROR)) { + return "$osListVariableName.addBinary($valueVariableName)" + } + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DATE_MIRROR)) { + return "$osListVariableName.addDate($valueVariableName)" + } + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.BOOLEAN_MIRROR)) { + return "$osListVariableName.addBoolean($valueVariableName)" + } + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DOUBLE_MIRROR)) { + return "$osListVariableName.addDouble($valueVariableName.doubleValue())" + } + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.FLOAT_MIRROR)) { + return "$osListVariableName.addFloat($valueVariableName.floatValue())" + } + throw RuntimeException("unexpected element type: $elementTypeMirror") + } + + @Throws(IOException::class) + private fun emitCodeForUnderConstruction(writer: JavaWriter, isPrimaryKey: Boolean, emitCode: () -> Unit) { + writer.apply { + beginControlFlow("if (proxyState.isUnderConstruction())") + if (isPrimaryKey) { + emitSingleLineComment("default value of the primary key is always ignored.") + emitStatement("return") + } else { + beginControlFlow("if (!proxyState.getAcceptDefaultValue\$realm())") + emitStatement("return") + endControlFlow() + emitCode() + } + endControlFlow() + emitEmptyLine() + } + } + + // Note that because of bytecode hackery, this method may run before the constructor! + // It may even run before fields have been initialized. + @Throws(IOException::class) + private fun emitInjectContextMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod("void","realm\$injectObjectContext", EnumSet.of(Modifier.PUBLIC)) + beginControlFlow("if (this.proxyState != null)") + emitStatement("return") + endControlFlow() + emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()") + emitStatement("this.columnInfo = (%1\$s) context.getColumnInfo()", columnInfoClassName()) + emitStatement("this.proxyState = new ProxyState<%1\$s>(this)", qualifiedJavaClassName) + emitStatement("proxyState.setRealm\$realm(context.getRealm())") + emitStatement("proxyState.setRow\$realm(context.getRow())") + emitStatement("proxyState.setAcceptDefaultValue\$realm(context.getAcceptDefaultValue())") + emitStatement("proxyState.setExcludeFields\$realm(context.getExcludeFields())") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitBacklinkFieldAccessors(writer: JavaWriter) { + for (backlink in metadata.backlinkFields) { + val cacheFieldName = backlink.targetField + BACKLINKS_FIELD_EXTENSION + val realmResultsType = "RealmResults<" + backlink.sourceClass + ">" + // Getter, no setter + writer.apply { + emitAnnotation("Override") + beginMethod(realmResultsType, metadata.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC)) + emitStatement("BaseRealm realm = proxyState.getRealm\$realm()") + emitStatement("realm.checkIfValid()") + emitStatement("proxyState.getRow\$realm().checkIfAttached()") + beginControlFlow("if ($cacheFieldName == null)") + emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\")", backlink.sourceClass, backlink.sourceField) + endControlFlow() + emitStatement("return $cacheFieldName") + endMethod() + emitEmptyLine() + } + } + } + + @Throws(IOException::class) + private fun emitRealmObjectProxyImplementation(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod("ProxyState", "realmGet\$proxyState", EnumSet.of(Modifier.PUBLIC)) + emitStatement("return proxyState") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitCreateExpectedObjectSchemaInfo(writer: JavaWriter) { + writer.apply { + beginMethod("OsObjectSchemaInfo", "createExpectedObjectSchemaInfo", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC)) + // Guess capacity for Arrays used by OsObjectSchemaInfo. + // Used to prevent array resizing at runtime + val persistedFields = metadata.fields.size + val computedFields = metadata.backlinkFields.size + + emitStatement("OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\", %s, %s)", internalClassName, persistedFields, computedFields) + + // For each field generate corresponding table index constant + for (field in metadata.fields) { + val fieldName = field.internalFieldName + + when (val fieldType = getRealmTypeChecked(field)) { + Constants.RealmFieldType.NOTYPE -> { + // Perhaps this should fail quickly? + } + Constants.RealmFieldType.OBJECT -> { + val fieldTypeQualifiedName = Utils.getFieldTypeQualifiedName(field) + val internalClassName = Utils.getReferencedTypeInternalClassNameStatement(fieldTypeQualifiedName, classCollection) + emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.OBJECT, %s)", fieldName, internalClassName) + } + Constants.RealmFieldType.LIST -> { + val genericTypeQualifiedName = Utils.getGenericTypeQualifiedName(field) + val internalClassName = Utils.getReferencedTypeInternalClassNameStatement(genericTypeQualifiedName, classCollection) + emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.LIST, %s)", fieldName, internalClassName) + } + Constants.RealmFieldType.INTEGER_LIST, + Constants.RealmFieldType.BOOLEAN_LIST, + Constants.RealmFieldType.STRING_LIST, + Constants.RealmFieldType.BINARY_LIST, + Constants.RealmFieldType.DATE_LIST, + Constants.RealmFieldType.FLOAT_LIST, + Constants.RealmFieldType.DOUBLE_LIST -> { + val requiredFlag = if (metadata.isElementNullable(field)) "!Property.REQUIRED" else "Property.REQUIRED" + emitStatement("builder.addPersistedValueListProperty(\"%s\", %s, %s)", fieldName, fieldType.realmType, requiredFlag) + } + Constants.RealmFieldType.BACKLINK -> { + throw IllegalArgumentException("LinkingObject field should not be added to metadata") + } + Constants.RealmFieldType.INTEGER, + Constants.RealmFieldType.FLOAT, + Constants.RealmFieldType.DOUBLE, + Constants.RealmFieldType.BOOLEAN, + Constants.RealmFieldType.STRING, + Constants.RealmFieldType.DATE, + Constants.RealmFieldType.BINARY, + Constants.RealmFieldType.REALM_INTEGER -> { + val nullableFlag = (if (metadata.isNullable(field)) "!" else "") + "Property.REQUIRED" + val indexedFlag = (if (metadata.isIndexed(field)) "" else "!") + "Property.INDEXED" + val primaryKeyFlag = (if (metadata.isPrimaryKey(field)) "" else "!") + "Property.PRIMARY_KEY" + emitStatement("builder.addPersistedProperty(\"%s\", %s, %s, %s, %s)", fieldName, fieldType.realmType, primaryKeyFlag, indexedFlag, nullableFlag) + } + } + } + for (backlink in metadata.backlinkFields) { + // Backlinks can only be created between classes in the current round of annotation processing + // as the forward link cannot be created unless you know the type already. + val sourceClass = classCollection.getClassFromQualifiedName(backlink.sourceClass!!) + val targetField = backlink.targetField // Only in the model, so no internal name exists + val internalSourceField = sourceClass.getInternalFieldName(backlink.sourceField!!) + emitStatement("builder.addComputedLinkProperty(\"%s\", \"%s\", \"%s\")", targetField, sourceClass.internalClassName, internalSourceField) + } + emitStatement("return builder.build()") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitGetExpectedObjectSchemaInfo(writer: JavaWriter) { + writer.apply { + beginMethod("OsObjectSchemaInfo", "getExpectedObjectSchemaInfo", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) + emitStatement("return expectedObjectSchemaInfo") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitCreateColumnInfoMethod(writer: JavaWriter) { + writer.apply { + beginMethod(columnInfoClassName(), "createColumnInfo", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "OsSchemaInfo", "schemaInfo") + emitStatement("return new %1\$s(schemaInfo)", columnInfoClassName()) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitGetSimpleClassNameMethod(writer: JavaWriter) { + writer.apply { + beginMethod("String", "getSimpleClassName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC)) + emitStatement("return \"%s\"", internalClassName) + endMethod() + emitEmptyLine() + + // Helper class for the annotation processor so it can access the internal class name + // without needing to load the parent class (which we cannot do as it transitively loads + // native code, which cannot be loaded on the JVM). + beginType("ClassNameHelper", "class", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)) + emitField("String", "INTERNAL_CLASS_NAME", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL), "\"$internalClassName\"") + endType() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitNewProxyInstance(writer: JavaWriter) { + writer.apply { + beginMethod(generatedClassName, "newProxyInstance", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC), "BaseRealm", "realm", "Row", "row") + emitSingleLineComment("Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields") + emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") + emitStatement("objectContext.set(realm, row, realm.getSchema().getColumnInfo(%s.class), false, Collections.emptyList())", qualifiedJavaClassName) + emitStatement("%1\$s obj = new %1\$s()", generatedClassName) + emitStatement("objectContext.clear()") + emitStatement("return obj") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitCopyOrUpdateMethod(writer: JavaWriter) { + writer.apply { + beginMethod(qualifiedJavaClassName,"copyOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), + "Realm", "realm", + columnInfoClassName(), "columnInfo", + qualifiedJavaClassName.toString(), "object", + "boolean", "update", + "Map", "cache", + "Set", "flags") + + beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null)") + emitStatement("final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm()") + beginControlFlow("if (otherRealm.threadId != realm.threadId)") + emitStatement("throw new IllegalArgumentException(\"Objects which belong to Realm instances in other threads cannot be copied into this Realm instance.\")") + endControlFlow() + + // If object is already in the Realm there is nothing to update + beginControlFlow("if (otherRealm.getPath().equals(realm.getPath()))") + emitStatement("return object") + endControlFlow() + endControlFlow() + emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") + emitStatement("RealmObjectProxy cachedRealmObject = cache.get(object)") + beginControlFlow("if (cachedRealmObject != null)") + emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName) + endControlFlow() + emitEmptyLine() + + if (!metadata.hasPrimaryKey()) { + emitStatement("return copy(realm, columnInfo, object, update, cache, flags)") + } else { + emitStatement("%s realmObject = null", qualifiedJavaClassName) + emitStatement("boolean canUpdate = update") + beginControlFlow("if (canUpdate)") + emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + + val primaryKeyGetter = metadata.primaryKeyGetter + val primaryKeyElement = metadata.primaryKey + if (metadata.isNullable(primaryKeyElement!!)) { + if (Utils.isString(primaryKeyElement)) { + emitStatement("String value = ((%s) object).%s()", interfaceName, primaryKeyGetter) + emitStatement("long rowIndex = Table.NO_MATCH") + beginControlFlow("if (value == null)") + emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") + nextControlFlow("else") + emitStatement("rowIndex = table.findFirstString(pkColumnIndex, value)") + endControlFlow() + } else { + emitStatement("Number value = ((%s) object).%s()", interfaceName, primaryKeyGetter) + emitStatement("long rowIndex = Table.NO_MATCH") + beginControlFlow("if (value == null)") + emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") + nextControlFlow("else") + emitStatement("rowIndex = table.findFirstLong(pkColumnIndex, value.longValue())") + endControlFlow() + } + } else { + val pkType = if (Utils.isString(metadata.primaryKey)) "String" else "Long" + emitStatement("long rowIndex = table.findFirst%s(pkColumnIndex, ((%s) object).%s())", pkType, interfaceName, primaryKeyGetter) + } + + beginControlFlow("if (rowIndex == Table.NO_MATCH)") + emitStatement("canUpdate = false") + nextControlFlow("else") + beginControlFlow("try") + emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex), columnInfo, false, Collections. emptyList())") + emitStatement("realmObject = new %s()", generatedClassName) + emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") + nextControlFlow("finally") + emitStatement("objectContext.clear()") + endControlFlow() + endControlFlow() + endControlFlow() + emitEmptyLine() + emitStatement("return (canUpdate) ? update(realm, columnInfo, realmObject, object, cache, flags) : copy(realm, columnInfo, object, update, cache, flags)") + } + + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun setTableValues(writer: JavaWriter, fieldType: String, fieldName: String, interfaceName: SimpleClassName, getter: String, isUpdate: Boolean) { + writer.apply { + when(fieldType) { + "long", + "int", + "short", + "byte" -> { + emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + } + "java.lang.Long", + "java.lang.Integer", + "java.lang.Short", + "java.lang.Byte" -> { + emitStatement("Number %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + } + endControlFlow() + } + "io.realm.MutableRealmInteger" -> { + emitStatement("Long %s = ((%s) object).%s().get()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + } + endControlFlow() + } + "double" -> { + emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + } + "java.lang.Double" -> { + emitStatement("Double %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + } + endControlFlow() + } + "float" -> { + emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + } + "java.lang.Float" -> { + emitStatement("Float %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + } + endControlFlow() + } + "boolean" -> { + emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + } + "java.lang.Boolean" -> { + emitStatement("Boolean %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + } + endControlFlow() + } + "byte[]" -> { + emitStatement("byte[] %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + } + endControlFlow() + } + "java.util.Date" -> { + emitStatement("java.util.Date %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.getTime(), false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + } + endControlFlow() + } + "java.lang.String" -> { + emitStatement("String %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + } + endControlFlow() + } + else -> { + throw IllegalStateException("Unsupported type $fieldType") + } + } + } + } + + @Throws(IOException::class) + private fun emitInsertMethod(writer: JavaWriter) { + writer.apply { + beginMethod("long","insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", qualifiedJavaClassName.toString(), "object", "Map", "cache") + + // If object is already in the Realm there is nothing to update + beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getIndex()") + endControlFlow() + + emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + emitStatement("long tableNativePtr = table.getNativePtr()") + emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) + + if (metadata.hasPrimaryKey()) { + emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + } + addPrimaryKeyCheckIfNeeded(metadata, true, writer) + + for (field in metadata.fields) { + val fieldName = field.simpleName.toString() + val fieldType = field.asType().toString() + val getter = metadata.getInternalGetter(fieldName) + + when { + Utils.isRealmModel(field) -> { + emitEmptyLine() + emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) + beginControlFlow("if (%sObj != null)", fieldName) + emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sIndex, rowIndex, cache%1\$s, false)", fieldName) + endControlFlow() + } + Utils.isRealmModelList(field) -> { + val genericType = Utils.getGenericTypeQualifiedName(field) + emitEmptyLine() + emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + beginControlFlow("if (%sList != null)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) + emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + endControlFlow() + endControlFlow() + } + Utils.isRealmValueList(field) -> { + val genericType = Utils.getGenericTypeQualifiedName(field) + val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) + emitEmptyLine() + emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + beginControlFlow("if (%sList != null)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) + beginControlFlow("if (%1\$sItem == null)", fieldName) + emitStatement(fieldName + "OsList.addNull()") + nextControlFlow("else") + emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) + endControlFlow() + endControlFlow() + endControlFlow() + } + else -> { + if (metadata.primaryKey !== field) { + setTableValues(writer, fieldType, fieldName, interfaceName, getter, false) + } + } + } + } + + emitStatement("return rowIndex") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitInsertListMethod(writer: JavaWriter) { + writer.apply { + beginMethod("void", "insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", "Iterator", "objects", "Map", "cache") + emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + emitStatement("long tableNativePtr = table.getNativePtr()") + emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) + if (metadata.hasPrimaryKey()) { + emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + } + emitStatement("%s object = null", qualifiedJavaClassName) + + beginControlFlow("while (objects.hasNext())") + emitStatement("object = (%s) objects.next()", qualifiedJavaClassName) + beginControlFlow("if (cache.containsKey(object))") + emitStatement("continue") + endControlFlow() + beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getIndex())") + emitStatement("continue") + endControlFlow() + + addPrimaryKeyCheckIfNeeded(metadata, true, writer) + + for (field in metadata.fields) { + val fieldName = field.simpleName.toString() + val fieldType = field.asType().toString() + val getter = metadata.getInternalGetter(fieldName) + + if (Utils.isRealmModel(field)) { + emitEmptyLine() + emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) + beginControlFlow("if (%sObj != null)", fieldName) + emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + emitStatement("table.setLink(columnInfo.%1\$sIndex, rowIndex, cache%1\$s, false)", fieldName) + endControlFlow() + } else if (Utils.isRealmModelList(field)) { + val genericType = Utils.getGenericTypeQualifiedName(field) + emitEmptyLine() + emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + beginControlFlow("if (%sList != null)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) + emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + endControlFlow() + endControlFlow() + } else if (Utils.isRealmValueList(field)) { + val genericType = Utils.getGenericTypeQualifiedName(field) + val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) + emitEmptyLine() + emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + beginControlFlow("if (%sList != null)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) + beginControlFlow("if (%1\$sItem == null)", fieldName) + emitStatement("%1\$sOsList.addNull()", fieldName) + nextControlFlow("else") + emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) + endControlFlow() + endControlFlow() + endControlFlow() + } else { + if (metadata.primaryKey !== field) { + setTableValues(writer, fieldType, fieldName, interfaceName, getter, false) + } + } + } + endControlFlow() + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitInsertOrUpdateMethod(writer: JavaWriter) { + writer.apply { + beginMethod("long", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", qualifiedJavaClassName.toString(), "object", "Map", "cache") + + // If object is already in the Realm there is nothing to update + beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getIndex()") + endControlFlow() + emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + emitStatement("long tableNativePtr = table.getNativePtr()") + emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) + + if (metadata.hasPrimaryKey()) { + emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + } + addPrimaryKeyCheckIfNeeded(metadata, false, writer) + + for (field in metadata.fields) { + val fieldName = field.simpleName.toString() + val fieldType = field.asType().toString() + val getter = metadata.getInternalGetter(fieldName) + + if (Utils.isRealmModel(field)) { + emitEmptyLine() + emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) + beginControlFlow("if (%sObj != null)", fieldName) + emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sIndex, rowIndex, cache%1\$s, false)", fieldName) + nextControlFlow("else") + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. + emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) + endControlFlow() + } else if (Utils.isRealmModelList(field)) { + val genericType = Utils.getGenericTypeQualifiedName(field) + emitEmptyLine() + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + beginControlFlow("if (%1\$sList != null && %1\$sList.size() == %1\$sOsList.size())", fieldName) + emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") + emitStatement("int objects = %1\$sList.size()", fieldName) + beginControlFlow("for (int i = 0; i < objects; i++)") + emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) + emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + endControlFlow() + nextControlFlow("else") + emitStatement("%1\$sOsList.removeAll()", fieldName) + beginControlFlow("if (%sList != null)", fieldName) + beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) + emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + endControlFlow() + endControlFlow() + endControlFlow() + emitEmptyLine() + } else if (Utils.isRealmValueList(field)) { + val genericType = Utils.getGenericTypeQualifiedName(field) + val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) + emitEmptyLine() + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("%1\$sOsList.removeAll()", fieldName) + emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + beginControlFlow("if (%sList != null)", fieldName) + beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) + beginControlFlow("if (%1\$sItem == null)", fieldName) + emitStatement("%1\$sOsList.addNull()", fieldName) + nextControlFlow("else") + emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) + endControlFlow() + endControlFlow() + endControlFlow() + emitEmptyLine() + } else { + if (metadata.primaryKey !== field) { + setTableValues(writer, fieldType, fieldName, interfaceName, getter, true) + } + } + } + + emitStatement("return rowIndex") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitInsertOrUpdateListMethod(writer: JavaWriter) { + writer.apply { + beginMethod("void", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", "Iterator", "objects", "Map", "cache") + emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + emitStatement("long tableNativePtr = table.getNativePtr()") + emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) + if (metadata.hasPrimaryKey()) { + emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + } + emitStatement("%s object = null", qualifiedJavaClassName) + beginControlFlow("while (objects.hasNext())") + emitStatement("object = (%s) objects.next()", qualifiedJavaClassName) + beginControlFlow("if (cache.containsKey(object))") + emitStatement("continue") + endControlFlow() + + beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getIndex())") + emitStatement("continue") + endControlFlow() + addPrimaryKeyCheckIfNeeded(metadata, false, writer) + + for (field in metadata.fields) { + val fieldName = field.simpleName.toString() + val fieldType = field.asType().toString() + val getter = metadata.getInternalGetter(fieldName) + + when { + Utils.isRealmModel(field) -> { + emitEmptyLine() + emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) + beginControlFlow("if (%sObj != null)", fieldName) + emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sIndex, rowIndex, cache%1\$s, false)", fieldName) + nextControlFlow("else") + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. + emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) + endControlFlow() + } + Utils.isRealmModelList(field) -> { + val genericType = Utils.getGenericTypeQualifiedName(field) + emitEmptyLine() + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + beginControlFlow("if (%1\$sList != null && %1\$sList.size() == %1\$sOsList.size())", fieldName) + emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") + emitStatement("int objectCount = %1\$sList.size()", fieldName) + beginControlFlow("for (int i = 0; i < objectCount; i++)") + emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) + emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + endControlFlow() + nextControlFlow("else") + emitStatement("%1\$sOsList.removeAll()", fieldName) + beginControlFlow("if (%sList != null)", fieldName) + beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) + emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + endControlFlow() + endControlFlow() + endControlFlow() + emitEmptyLine() + } + Utils.isRealmValueList(field) -> { + val genericType = Utils.getGenericTypeQualifiedName(field) + val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) + emitEmptyLine() + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("%1\$sOsList.removeAll()", fieldName) + emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) + beginControlFlow("if (%sList != null)", fieldName) + beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) + beginControlFlow("if (%1\$sItem == null)", fieldName) + emitStatement("%1\$sOsList.addNull()", fieldName) + nextControlFlow("else") + emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror)) + endControlFlow() + endControlFlow() + endControlFlow() + emitEmptyLine() + } + else -> { + if (metadata.primaryKey !== field) { + setTableValues(writer, fieldType, fieldName, interfaceName, getter, true) + } + } + } + } + endControlFlow() + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun addPrimaryKeyCheckIfNeeded(metadata: ClassMetaData, throwIfPrimaryKeyDuplicate: Boolean, writer: JavaWriter) { + writer.apply { + if (metadata.hasPrimaryKey()) { + val primaryKeyGetter = metadata.primaryKeyGetter + val primaryKeyElement = metadata.primaryKey + if (metadata.isNullable(primaryKeyElement!!)) { + if (Utils.isString(primaryKeyElement)) { + emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) + emitStatement("long rowIndex = Table.NO_MATCH") + beginControlFlow("if (primaryKeyValue == null)") + emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") + nextControlFlow("else") + emitStatement("rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue)") + endControlFlow() + } else { + emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) + emitStatement("long rowIndex = Table.NO_MATCH") + beginControlFlow("if (primaryKeyValue == null)") + emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") + nextControlFlow("else") + emitStatement("rowIndex = Table.nativeFindFirstInt(tableNativePtr, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) + endControlFlow() + } + } else { + emitStatement("long rowIndex = Table.NO_MATCH") + emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) + beginControlFlow("if (primaryKeyValue != null)") + if (Utils.isString(metadata.primaryKey)) { + emitStatement("rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, (String)primaryKeyValue)") + } else { + emitStatement("rowIndex = Table.nativeFindFirstInt(tableNativePtr, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) + } + endControlFlow() + } + + beginControlFlow("if (rowIndex == Table.NO_MATCH)") + if (Utils.isString(metadata.primaryKey)) { + emitStatement("rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue)") + } else { + emitStatement("rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) + } + + if (throwIfPrimaryKeyDuplicate) { + nextControlFlow("else") + emitStatement("Table.throwDuplicatePrimaryKeyException(primaryKeyValue)") + } + endControlFlow() + emitStatement("cache.put(object, rowIndex)") + } else { + emitStatement("long rowIndex = OsObject.createRow(table)") + emitStatement("cache.put(object, rowIndex)") + } + } + } + + @Throws(IOException::class) + private fun emitCopyMethod(writer: JavaWriter) { + writer.apply { + beginMethod(qualifiedJavaClassName, "copy", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), + "Realm", "realm", + columnInfoClassName(), "columnInfo", + qualifiedJavaClassName.toString(), "newObject", + "boolean", "update", + "Map", "cache", + "Set", "flags" + ) + emitStatement("RealmObjectProxy cachedRealmObject = cache.get(newObject)") + beginControlFlow("if (cachedRealmObject != null)") + emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName) + endControlFlow() + emitEmptyLine() + emitStatement("%1\$s realmObjectSource = (%1\$s) newObject", interfaceName) + emitEmptyLine() + emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags)") + + // Copy basic types + emitEmptyLine() + emitSingleLineComment("Add all non-\"object reference\" fields") + for (field in metadata.getBasicTypeFields()) { + val fieldIndex = fieldIndexVariableReference(field) + val fieldName = field.simpleName.toString() + val getter = metadata.getInternalGetter(fieldName) + emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldIndex, getter) + } + + // Create the underlying object + emitEmptyLine() + emitSingleLineComment("Create the underlying object and cache it before setting any object/objectlist references") + emitSingleLineComment("This will allow us to break any circular dependencies by using the object cache.") + emitStatement("Row row = builder.createNewObject()") + emitStatement("%s realmObjectCopy = newProxyInstance(realm, row)", generatedClassName) + emitStatement("cache.put(newObject, realmObjectCopy)") + + // Copy all object references or lists-of-objects + emitEmptyLine() + if (metadata.objectReferenceFields.isNotEmpty()) { + emitSingleLineComment("Finally add all fields that reference other Realm Objects, either directly or through a list") + } + for (field in metadata.objectReferenceFields) { + val fieldType = field.asType().toString() + val fieldName = field.simpleName.toString() + val getter = metadata.getInternalGetter(fieldName) + val setter = metadata.getInternalSetter(fieldName) + + when { + Utils.isRealmModel(field) -> { + emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) + beginControlFlow("if (%sObj == null)", fieldName) + emitStatement("realmObjectCopy.%s(null)", setter) + nextControlFlow("else") + emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("realmObjectCopy.%s(cache%s)", setter, fieldName) + nextControlFlow("else") + emitStatement("realmObjectCopy.%s(%s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, update, cache, flags))", setter, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) + endControlFlow() + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. + endControlFlow() + emitEmptyLine() + } + Utils.isRealmModelList(field) -> { + val genericType = Utils.getGenericTypeQualifiedName(field) + emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) + beginControlFlow("if (%sList != null)", fieldName) + emitStatement("RealmList<%s> %sRealmList = realmObjectCopy.%s()", genericType, fieldName, getter) + // Clear is needed. See bug https://github.com/realm/realm-java/issues/4957 + emitStatement("%sRealmList.clear()", fieldName) + beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) + emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) + emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sItem)", genericType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("%1\$sRealmList.add(cache%1\$s)", fieldName) + nextControlFlow("else") + emitStatement("%1\$sRealmList.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, update, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) + endControlFlow() + endControlFlow() + endControlFlow() + emitEmptyLine() + } + else -> { + throw IllegalStateException("Unsupported field: $field") + } + } + } + emitStatement("return realmObjectCopy") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitCreateDetachedCopyMethod(writer: JavaWriter) { + writer.apply { + beginMethod(qualifiedJavaClassName, "createDetachedCopy", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), qualifiedJavaClassName.toString(), "realmObject", "int", "currentDepth", "int", "maxDepth", "Map>", "cache") + beginControlFlow("if (currentDepth > maxDepth || realmObject == null)") + emitStatement("return null") + endControlFlow() + emitStatement("CacheData cachedObject = cache.get(realmObject)") + emitStatement("%s unmanagedObject", qualifiedJavaClassName) + beginControlFlow("if (cachedObject == null)") + emitStatement("unmanagedObject = new %s()", qualifiedJavaClassName) + emitStatement("cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject))") + nextControlFlow("else") + emitSingleLineComment("Reuse cached object or recreate it because it was encountered at a lower depth.") + beginControlFlow("if (currentDepth >= cachedObject.minDepth)") + emitStatement("return (%s) cachedObject.object", qualifiedJavaClassName) + endControlFlow() + emitStatement("unmanagedObject = (%s) cachedObject.object", qualifiedJavaClassName) + emitStatement("cachedObject.minDepth = currentDepth") + endControlFlow() + + // may cause an unused variable warning if the object contains only null lists + emitStatement("%1\$s unmanagedCopy = (%1\$s) unmanagedObject", interfaceName) + emitStatement("%1\$s realmSource = (%1\$s) realmObject", interfaceName) + + for (field in metadata.fields) { + val fieldName = field.simpleName.toString() + val setter = metadata.getInternalSetter(fieldName) + val getter = metadata.getInternalGetter(fieldName) + when { + Utils.isRealmModel(field) -> { + emitEmptyLine() + emitSingleLineComment("Deep copy of %s", fieldName) + emitStatement("unmanagedCopy.%s(%s.createDetachedCopy(realmSource.%s(), currentDepth + 1, maxDepth, cache))", setter, Utils.getProxyClassSimpleName(field), getter) + } + Utils.isRealmModelList(field) -> { + emitEmptyLine() + emitSingleLineComment("Deep copy of %s", fieldName) + beginControlFlow("if (currentDepth == maxDepth)") + emitStatement("unmanagedCopy.%s(null)", setter) + nextControlFlow("else") + emitStatement("RealmList<%s> managed%sList = realmSource.%s()", Utils.getGenericTypeQualifiedName(field), fieldName, getter) + emitStatement("RealmList<%1\$s> unmanaged%2\$sList = new RealmList<%1\$s>()", Utils.getGenericTypeQualifiedName(field), fieldName) + emitStatement("unmanagedCopy.%s(unmanaged%sList)", setter, fieldName) + emitStatement("int nextDepth = currentDepth + 1") + emitStatement("int size = managed%sList.size()", fieldName) + beginControlFlow("for (int i = 0; i < size; i++)") + emitStatement("%s item = %s.createDetachedCopy(managed%sList.get(i), nextDepth, maxDepth, cache)", Utils.getGenericTypeQualifiedName(field), Utils.getProxyClassSimpleName(field), fieldName) + emitStatement("unmanaged%sList.add(item)", fieldName) + endControlFlow() + endControlFlow() + } + Utils.isRealmValueList(field) -> { + emitEmptyLine() + emitStatement("unmanagedCopy.%1\$s(new RealmList<%2\$s>())", setter, Utils.getGenericTypeQualifiedName(field)) + emitStatement("unmanagedCopy.%1\$s().addAll(realmSource.%1\$s())", getter) + } + Utils.isMutableRealmInteger(field) -> // If the user initializes the unmanaged MutableRealmInteger to null, this will fail mysteriously. + emitStatement("unmanagedCopy.%s().set(realmSource.%s().get())", getter, getter) + else -> { + emitStatement("unmanagedCopy.%s(realmSource.%s())", setter, getter) + } + } + } + emitEmptyLine() + emitStatement("return unmanagedObject") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitUpdateMethod(writer: JavaWriter) { + if (!metadata.hasPrimaryKey()) { + return + } + writer.apply { + beginMethod(qualifiedJavaClassName, "update", EnumSet.of(Modifier.STATIC), + "Realm", "realm", // Argument type & argument name + columnInfoClassName(), "columnInfo", + qualifiedJavaClassName.toString(), "realmObject", + qualifiedJavaClassName.toString(), "newObject", + "Map", "cache", + "Set", "flags" + ) + emitStatement("%1\$s realmObjectTarget = (%1\$s) realmObject", interfaceName) + emitStatement("%1\$s realmObjectSource = (%1\$s) newObject", interfaceName) + emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags)") + for (field in metadata.fields) { + val fieldType = field.asType().toString() + val fieldName = field.simpleName.toString() + val getter = metadata.getInternalGetter(fieldName) + val fieldIndex = fieldIndexVariableReference(field) + + when { + Utils.isRealmModel(field) -> { + emitEmptyLine() + emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) + beginControlFlow("if (%sObj == null)", fieldName) + emitStatement("builder.addNull(%s)", fieldIndexVariableReference(field)) + nextControlFlow("else") + emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("builder.addObject(%s, cache%s)", fieldIndex, fieldName) + nextControlFlow("else") + emitStatement("builder.addObject(%s, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, true, cache, flags))", fieldIndex, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) + endControlFlow() + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. + endControlFlow() + } + Utils.isRealmModelList(field) -> { + val genericType = Utils.getGenericTypeQualifiedName(field) + emitEmptyLine() + emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) + beginControlFlow("if (%sList != null)", fieldName) + emitStatement("RealmList<%s> %sManagedCopy = new RealmList<%s>()", genericType, fieldName, genericType) + beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) + emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) + emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sItem)", genericType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("%1\$sManagedCopy.add(cache%1\$s)", fieldName) + nextControlFlow("else") + emitStatement("%1\$sManagedCopy.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, true, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) + endControlFlow() + endControlFlow() + emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldIndex, fieldName) + nextControlFlow("else") + emitStatement("builder.addObjectList(%s, new RealmList<%s>())", fieldIndex, genericType) + endControlFlow() + } + else -> { + emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldIndex, getter) + } + } + } + emitEmptyLine() + emitStatement("builder.updateExistingObject()") + emitStatement("return realmObject") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitToStringMethod(writer: JavaWriter) { + if (metadata.containsToString()) { + return + } + writer.apply { + emitAnnotation("Override") + emitAnnotation("SuppressWarnings", "\"ArrayToString\"") + beginMethod("String", "toString", EnumSet.of(Modifier.PUBLIC)) + beginControlFlow("if (!RealmObject.isValid(this))") + emitStatement("return \"Invalid object\"") + endControlFlow() + emitStatement("StringBuilder stringBuilder = new StringBuilder(\"%s = proxy[\")", simpleJavaClassName) + + val fields = metadata.fields + var i = fields.size - 1 + for (field in fields) { + val fieldName = field.simpleName.toString() + emitStatement("stringBuilder.append(\"{%s:\")", fieldName) + when { + Utils.isRealmModel(field) -> { + val fieldTypeSimpleName = Utils.getFieldTypeQualifiedName(field).getSimpleName() + emitStatement("stringBuilder.append(%s() != null ? \"%s\" : \"null\")", metadata.getInternalGetter(fieldName), fieldTypeSimpleName) + } + Utils.isRealmList(field) -> { + val genericTypeSimpleName = Utils.getGenericTypeQualifiedName(field)?.getSimpleName() + emitStatement("stringBuilder.append(\"RealmList<%s>[\").append(%s().size()).append(\"]\")", genericTypeSimpleName, metadata.getInternalGetter(fieldName)) + } + Utils.isMutableRealmInteger(field) -> { + emitStatement("stringBuilder.append(%s().get())", metadata.getInternalGetter(fieldName)) + } + else -> { + if (metadata.isNullable(field)) { + emitStatement("stringBuilder.append(%s() != null ? %s() : \"null\")", metadata.getInternalGetter(fieldName), metadata.getInternalGetter(fieldName)) + } else { + emitStatement("stringBuilder.append(%s())", metadata.getInternalGetter(fieldName)) + } + } + } + emitStatement("stringBuilder.append(\"}\")") + + if (i-- > 0) { + emitStatement("stringBuilder.append(\",\")") + } + } + + emitStatement("stringBuilder.append(\"]\")") + emitStatement("return stringBuilder.toString()") + endMethod() + emitEmptyLine() + } + } + + /** + * Currently, the hash value emitted from this could suddenly change as an object's index might + * alternate due to Realm Java using `Table#moveLastOver()`. Hash codes should therefore not + * be considered stable, i.e. don't save them in a HashSet or use them as a key in a HashMap. + */ + @Throws(IOException::class) + private fun emitHashcodeMethod(writer: JavaWriter) { + if (metadata.containsHashCode()) { + return + } + writer.apply { + emitAnnotation("Override") + beginMethod("int", "hashCode", EnumSet.of(Modifier.PUBLIC)) + emitStatement("String realmName = proxyState.getRealm\$realm().getPath()") + emitStatement("String tableName = proxyState.getRow\$realm().getTable().getName()") + emitStatement("long rowIndex = proxyState.getRow\$realm().getIndex()") + emitEmptyLine() + emitStatement("int result = 17") + emitStatement("result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0)") + emitStatement("result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0)") + emitStatement("result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32))") + emitStatement("return result") + endMethod() + emitEmptyLine() + } + } + + + + @Throws(IOException::class) + private fun emitEqualsMethod(writer: JavaWriter) { + if (metadata.containsEquals()) { + return + } + val proxyClassName = Utils.getProxyClassName(qualifiedJavaClassName) + val otherObjectVarName = "a$simpleJavaClassName" + writer.apply { + emitAnnotation("Override") + beginMethod("boolean", "equals", EnumSet.of(Modifier.PUBLIC), "Object", "o") + emitStatement("if (this == o) return true") + emitStatement("if (o == null || getClass() != o.getClass()) return false") + emitStatement("%s %s = (%s)o", proxyClassName, otherObjectVarName, proxyClassName) // FooRealmProxy aFoo = (FooRealmProxy)o + emitEmptyLine() + emitStatement("String path = proxyState.getRealm\$realm().getPath()") + emitStatement("String otherPath = %s.proxyState.getRealm\$realm().getPath()", otherObjectVarName) + emitStatement("if (path != null ? !path.equals(otherPath) : otherPath != null) return false") + emitEmptyLine() + emitStatement("String tableName = proxyState.getRow\$realm().getTable().getName()") + emitStatement("String otherTableName = %s.proxyState.getRow\$realm().getTable().getName()", otherObjectVarName) + emitStatement("if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false") + emitEmptyLine() + emitStatement("if (proxyState.getRow\$realm().getIndex() != %s.proxyState.getRow\$realm().getIndex()) return false", otherObjectVarName) + emitEmptyLine() + emitStatement("return true") + endMethod() + } + } + + @Throws(IOException::class) + private fun emitCreateOrUpdateUsingJsonObject(writer: JavaWriter) { + writer.apply { + emitAnnotation("SuppressWarnings", "\"cast\"") + beginMethod(qualifiedJavaClassName,"createOrUpdateUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "JSONObject", "json", "boolean", "update"), listOf("JSONException")) + val modelOrListCount = countModelOrListFields(metadata.fields) + if (modelOrListCount == 0) { + emitStatement("final List excludeFields = Collections. emptyList()") + } else { + emitStatement("final List excludeFields = new ArrayList(%1\$d)", modelOrListCount) + } + + if (!metadata.hasPrimaryKey()) { + buildExcludeFieldsList(writer, metadata.fields) + emitStatement("%s obj = realm.createObjectInternal(%s.class, true, excludeFields)", qualifiedJavaClassName, qualifiedJavaClassName) + } else { + val pkType = if (Utils.isString(metadata.primaryKey)) "String" else "Long" + emitStatement("%s obj = null", qualifiedJavaClassName) + beginControlFlow("if (update)") + emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) + emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + emitStatement("long rowIndex = Table.NO_MATCH") + if (metadata.isNullable(metadata.primaryKey!!)) { + beginControlFlow("if (json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) + emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") + nextControlFlow("else") + emitStatement("rowIndex = table.findFirst%s(pkColumnIndex, json.get%s(\"%s\"))", pkType, pkType, metadata.primaryKey!!.simpleName) + endControlFlow() + } else { + beginControlFlow("if (!json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) + emitStatement("rowIndex = table.findFirst%s(pkColumnIndex, json.get%s(\"%s\"))", pkType, pkType, metadata.primaryKey!!.simpleName) + endControlFlow() + } + beginControlFlow("if (rowIndex != Table.NO_MATCH)") + emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") + beginControlFlow("try") + emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", qualifiedJavaClassName) + emitStatement("obj = new %s()", generatedClassName) + nextControlFlow("finally") + emitStatement("objectContext.clear()") + endControlFlow() + endControlFlow() + endControlFlow() + + beginControlFlow("if (obj == null)") + buildExcludeFieldsList(writer, metadata.fields) + val primaryKeyFieldType = QualifiedClassName(metadata.primaryKey!!.asType().toString()) + val primaryKeyFieldName = metadata.primaryKey!!.simpleName.toString() + RealmJsonTypeHelper.emitCreateObjectWithPrimaryKeyValue(qualifiedJavaClassName, generatedClassName, primaryKeyFieldType, primaryKeyFieldName, writer) + endControlFlow() + } + emitEmptyLine() + emitStatement("final %1\$s objProxy = (%1\$s) obj", interfaceName) + for (field in metadata.fields) { + val fieldName = field.simpleName.toString() + val qualifiedFieldType = QualifiedClassName(field.asType().toString()) + if (metadata.isPrimaryKey(field)) { + continue // Primary key has already been set when adding new row or finding the existing row. + } + when { + Utils.isRealmModel(field) -> RealmJsonTypeHelper.emitFillRealmObjectWithJsonValue( + "objProxy", + metadata.getInternalSetter(fieldName), + fieldName, + qualifiedFieldType, + Utils.getProxyClassSimpleName(field), + writer) + Utils.isRealmModelList(field) -> RealmJsonTypeHelper.emitFillRealmListWithJsonValue( + "objProxy", + metadata.getInternalGetter(fieldName), + metadata.getInternalSetter(fieldName), + fieldName, + (field.asType() as DeclaredType).typeArguments[0].toString(), + Utils.getProxyClassSimpleName(field), + writer) + Utils.isRealmValueList(field) -> emitStatement("ProxyUtils.setRealmListWithJsonObject(objProxy.%1\$s(), json, \"%2\$s\")", metadata.getInternalGetter(fieldName), fieldName) + Utils.isMutableRealmInteger(field) -> RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( + "objProxy", + metadata.getInternalGetter(fieldName), + fieldName, + qualifiedFieldType, + writer) + else -> RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( + "objProxy", + metadata.getInternalSetter(fieldName), + fieldName, + qualifiedFieldType, + writer + ) + } + } + emitStatement("return obj") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun buildExcludeFieldsList(writer: JavaWriter, fields: Collection) { + writer.apply { + for (field in fields) { + if (Utils.isRealmModel(field) || Utils.isRealmList(field)) { + val fieldName = field.simpleName.toString() + beginControlFlow("if (json.has(\"%1\$s\"))", fieldName) + emitStatement("excludeFields.add(\"%1\$s\")", fieldName) + endControlFlow() + } + } + } + } + + // Since we need to check the PK in stream before creating the object, this is now using copyToRealm + // instead of createObject() to avoid parsing the stream twice. + @Throws(IOException::class) + private fun emitCreateUsingJsonStream(writer: JavaWriter) { + writer.apply { + emitAnnotation("SuppressWarnings", "\"cast\"") + emitAnnotation("TargetApi", "Build.VERSION_CODES.HONEYCOMB") + beginMethod(qualifiedJavaClassName,"createUsingJsonStream", setOf(Modifier.PUBLIC, Modifier.STATIC), listOf("Realm", "realm", "JsonReader", "reader"), listOf("IOException")) + if (metadata.hasPrimaryKey()) { + emitStatement("boolean jsonHasPrimaryKey = false") + } + emitStatement("final %s obj = new %s()", qualifiedJavaClassName, qualifiedJavaClassName) + emitStatement("final %1\$s objProxy = (%1\$s) obj", interfaceName) + emitStatement("reader.beginObject()") + beginControlFlow("while (reader.hasNext())") + emitStatement("String name = reader.nextName()") + beginControlFlow("if (false)") + val fields = metadata.fields + for (field in fields) { + val fieldName = field.simpleName.toString() + val fieldType = QualifiedClassName(field.asType().toString()) + nextControlFlow("else if (name.equals(\"%s\"))", fieldName) + + when { + Utils.isRealmModel(field) -> { + RealmJsonTypeHelper.emitFillRealmObjectFromStream( + "objProxy", + metadata.getInternalSetter(fieldName), + fieldName, + fieldType, + Utils.getProxyClassSimpleName(field), + writer) + } + Utils.isRealmModelList(field) -> { + RealmJsonTypeHelper.emitFillRealmListFromStream( + "objProxy", + metadata.getInternalGetter(fieldName), + metadata.getInternalSetter(fieldName), + QualifiedClassName((field.asType() as DeclaredType).typeArguments[0].toString()), + Utils.getProxyClassSimpleName(field), + writer) + } + Utils.isRealmValueList(field) -> { + emitStatement("objProxy.%1\$s(ProxyUtils.createRealmListWithJsonStream(%2\$s.class, reader))", metadata.getInternalSetter(fieldName), Utils.getRealmListType(field)) + } + Utils.isMutableRealmInteger(field) -> { + RealmJsonTypeHelper.emitFillJavaTypeFromStream( + "objProxy", + metadata, + metadata.getInternalGetter(fieldName), + fieldName, + fieldType, + writer) + } + else -> { + RealmJsonTypeHelper.emitFillJavaTypeFromStream( + "objProxy", + metadata, + metadata.getInternalSetter(fieldName), + fieldName, + fieldType, + writer) + } + } + } + + nextControlFlow("else") + emitStatement("reader.skipValue()") + endControlFlow() + endControlFlow() + emitStatement("reader.endObject()") + if (metadata.hasPrimaryKey()) { + beginControlFlow("if (!jsonHasPrimaryKey)") + emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.primaryKey) + endControlFlow() + } + emitStatement("return realm.copyToRealm(obj)") + endMethod() + emitEmptyLine() + } + } + + private fun columnInfoClassName(): String { + return "${simpleJavaClassName}ColumnInfo" + } + + /** + * Returns the name of the ColumnInfo class for the model class referenced in the field. + * I.e. for `com.test.Person`, it returns `Person.PersonColumnInfo` + */ + private fun columnInfoClassName(field: VariableElement): String { + val qualifiedModelClassName = Utils.getModelClassQualifiedName(field) + return Utils.getSimpleColumnInfoClassName(qualifiedModelClassName) + } + + private fun columnIndexVarName(variableElement: VariableElement): String { + return "${variableElement.simpleName}Index" + } + + private fun mutableRealmIntegerFieldName(variableElement: VariableElement): String { + return "${variableElement.simpleName}MutableRealmInteger" + } + + private fun fieldIndexVariableReference(variableElement: VariableElement?): String { + return "columnInfo.${columnIndexVarName(variableElement!!)}" + } + + private fun getRealmType(field: VariableElement): Constants.RealmFieldType { + val fieldTypeCanonicalName: String = field.asType().toString() + val type: Constants.RealmFieldType? = Constants.JAVA_TO_REALM_TYPES[fieldTypeCanonicalName] + if (type != null) { + return type + } + if (Utils.isMutableRealmInteger(field)) { + return Constants.RealmFieldType.REALM_INTEGER + } + if (Utils.isRealmModel(field)) { + return Constants.RealmFieldType.OBJECT + } + if (Utils.isRealmModelList(field)) { + return Constants.RealmFieldType.LIST + } + if (Utils.isRealmValueList(field)) { + return Utils.getValueListFieldType(field) + } + return Constants.RealmFieldType.NOTYPE + } + + private fun getRealmTypeChecked(field: VariableElement): Constants.RealmFieldType { + val type = getRealmType(field) + if (type === Constants.RealmFieldType.NOTYPE) { + throw IllegalStateException("Unsupported type " + field.asType().toString()) + } + return type + } + + companion object { + private val OPTION_SUPPRESS_WARNINGS = "realm.suppressWarnings" + private val BACKLINKS_FIELD_EXTENSION = "Backlinks" + + private val IMPORTS: List + + init { + val l = Arrays.asList( + "android.annotation.TargetApi", + "android.os.Build", + "android.util.JsonReader", + "android.util.JsonToken", + "io.realm.ImportFlag", + "io.realm.exceptions.RealmMigrationNeededException", + "io.realm.internal.ColumnInfo", + "io.realm.internal.OsList", + "io.realm.internal.OsObject", + "io.realm.internal.OsSchemaInfo", + "io.realm.internal.OsObjectSchemaInfo", + "io.realm.internal.Property", + "io.realm.internal.objectstore.OsObjectBuilder", + "io.realm.ProxyUtils", + "io.realm.internal.RealmObjectProxy", + "io.realm.internal.Row", + "io.realm.internal.Table", + "io.realm.internal.android.JsonUtils", + "io.realm.log.RealmLog", + "java.io.IOException", + "java.util.ArrayList", + "java.util.Collections", + "java.util.List", + "java.util.Iterator", + "java.util.Date", + "java.util.Map", + "java.util.HashMap", + "java.util.Set", + "org.json.JSONObject", + "org.json.JSONException", + "org.json.JSONArray") + IMPORTS = Collections.unmodifiableList(l) + } + + private fun countModelOrListFields(fields: Collection): Int { + var count = 0 + for (f in fields) { + if (Utils.isRealmModel(f) || Utils.isRealmList(f)) { + count++ + } + } + return count + } + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java deleted file mode 100644 index 94e3727902..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ -package io.realm.processor; - -import com.squareup.javawriter.JavaWriter; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.util.EnumSet; -import java.util.Locale; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.VariableElement; -import javax.tools.JavaFileObject; - -import io.realm.annotations.Ignore; - - -public class RealmProxyInterfaceGenerator { - private ProcessingEnvironment processingEnvironment; - private ClassMetaData metaData; - private final String className; - - public RealmProxyInterfaceGenerator(ProcessingEnvironment processingEnvironment, ClassMetaData metaData) { - this.processingEnvironment = processingEnvironment; - this.metaData = metaData; - this.className = metaData.getFullyQualifiedClassName(); - } - - public void generate() throws IOException { - String qualifiedGeneratedInterfaceName = - String.format(Locale.US, "%s.%s", Constants.REALM_PACKAGE_NAME, Utils.getProxyInterfaceName(className)); - JavaFileObject sourceFile = processingEnvironment.getFiler().createSourceFile(qualifiedGeneratedInterfaceName); - JavaWriter writer = new JavaWriter(new BufferedWriter(sourceFile.openWriter())); - - writer.setIndent(Constants.INDENT); - - writer - .emitPackage(Constants.REALM_PACKAGE_NAME) - .emitEmptyLine() - .beginType(qualifiedGeneratedInterfaceName, "interface", EnumSet.of(Modifier.PUBLIC)); - for (VariableElement field : metaData.getFields()) { - if (field.getModifiers().contains(Modifier.STATIC) || (field.getAnnotation(Ignore.class) != null)) { - continue; - } - // The field is neither static nor ignored - String fieldName = field.getSimpleName().toString(); - String fieldTypeCanonicalName = field.asType().toString(); - writer - .beginMethod( - fieldTypeCanonicalName, - metaData.getInternalGetter(fieldName), - EnumSet.of(Modifier.PUBLIC)) - .endMethod(); - - // MutableRealmIntegers do not have setters. - if (Utils.isMutableRealmInteger(field)) { continue; } - writer - .beginMethod( - "void", - metaData.getInternalSetter(fieldName), - EnumSet.of(Modifier.PUBLIC), - fieldTypeCanonicalName, - "value") - .endMethod(); - } - - // backlinks are final and have only a getter. - for (Backlink backlink : metaData.getBacklinkFields()) { - writer - .beginMethod( - backlink.getTargetFieldType(), - metaData.getInternalGetter(backlink.getTargetField()), - EnumSet.of(Modifier.PUBLIC)) - .endMethod(); - } - - writer.endType(); - writer.close(); - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.kt new file mode 100644 index 0000000000..4f0ec896c0 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyInterfaceGenerator.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.processor + +import com.squareup.javawriter.JavaWriter + +import java.io.BufferedWriter +import java.io.IOException +import java.util.EnumSet +import java.util.Locale + +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.Modifier + +import io.realm.annotations.Ignore + + +class RealmProxyInterfaceGenerator(private val processingEnvironment: ProcessingEnvironment, private val metaData: ClassMetaData) { + + private val className: QualifiedClassName = metaData.qualifiedClassName + + @Throws(IOException::class) + fun generate() { + val qualifiedGeneratedInterfaceName = String.format(Locale.US, "%s.%s", Constants.REALM_PACKAGE_NAME, Utils.getProxyInterfaceName(className)) + val sourceFile = processingEnvironment.filer.createSourceFile(qualifiedGeneratedInterfaceName) + val writer = JavaWriter(BufferedWriter(sourceFile.openWriter()!!)) + writer.apply { + indent = Constants.INDENT + emitPackage(Constants.REALM_PACKAGE_NAME) + emitEmptyLine() + beginType(qualifiedGeneratedInterfaceName, "interface", EnumSet.of(Modifier.PUBLIC)) + + for (field in metaData.fields) { + if (field.modifiers.contains(Modifier.STATIC) || field.getAnnotation(Ignore::class.java) != null) { + continue + } + // The field is neither static nor ignored + val fieldName = field.simpleName.toString() + val fieldTypeCanonicalName = field.asType().toString() + beginMethod(fieldTypeCanonicalName, metaData.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) + endMethod() + + // MutableRealmIntegers do not have setters. + if (Utils.isMutableRealmInteger(field)) { + continue + } + beginMethod("void", metaData.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value") + endMethod() + } + + // backlinks are final and have only a getter. + for (backlink in metaData.backlinkFields) { + beginMethod(backlink.targetFieldType, metaData.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC)) + endMethod() + } + + endType() + close() + } + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java deleted file mode 100644 index b67ac2bea8..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.java +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import com.squareup.javawriter.JavaWriter; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Modifier; -import javax.tools.JavaFileObject; - -import io.realm.annotations.RealmModule; - -import static io.realm.processor.Constants.REALM_PACKAGE_NAME; - - -public class RealmProxyMediatorGenerator { - private final String className; - private final ProcessingEnvironment processingEnvironment; - private final List qualifiedModelClasses = new ArrayList<>(); - private final List qualifiedProxyClasses = new ArrayList<>(); - private final List simpleModelClassNames = new ArrayList<>(); - private final List internalClassNames = new ArrayList<>(); - - - public RealmProxyMediatorGenerator(ProcessingEnvironment processingEnvironment, - String className, Set classesToValidate) { - this.processingEnvironment = processingEnvironment; - this.className = className; - - for (ClassMetaData metadata : classesToValidate) { - qualifiedModelClasses.add(metadata.getFullyQualifiedClassName()); - String qualifiedProxyClassName = REALM_PACKAGE_NAME + "." + Utils.getProxyClassName(metadata.getFullyQualifiedClassName()); - qualifiedProxyClasses.add(qualifiedProxyClassName); - simpleModelClassNames.add(metadata.getSimpleJavaClassName()); - internalClassNames.add(metadata.getInternalClassName()); - } - } - - public void generate() throws IOException { - String qualifiedGeneratedClassName = String.format(Locale.US, "%s.%sMediator", REALM_PACKAGE_NAME, className); - JavaFileObject sourceFile = processingEnvironment.getFiler().createSourceFile(qualifiedGeneratedClassName); - JavaWriter writer = new JavaWriter(new BufferedWriter(sourceFile.openWriter())); - writer.setIndent(" "); - - writer.emitPackage(REALM_PACKAGE_NAME); - writer.emitEmptyLine(); - - List imports = new ArrayList<>(Arrays.asList("android.util.JsonReader", - "java.io.IOException", - "java.util.Collections", - "java.util.HashSet", - "java.util.List", - "java.util.Map", - "java.util.HashMap", - "java.util.Set", - "java.util.Iterator", - "java.util.Collection", - "io.realm.ImportFlag", - "io.realm.internal.ColumnInfo", - "io.realm.internal.RealmObjectProxy", - "io.realm.internal.RealmProxyMediator", - "io.realm.internal.Row", - "io.realm.internal.OsSchemaInfo", - "io.realm.internal.OsObjectSchemaInfo", - "org.json.JSONException", - "org.json.JSONObject")); - - writer.emitImports(imports); - writer.emitEmptyLine(); - - writer.emitAnnotation(RealmModule.class); - writer.beginType( - qualifiedGeneratedClassName, // full qualified name of the item to generate - "class", // the type of the item - Collections.emptySet(), // modifiers to apply - "RealmProxyMediator"); // class to extend - writer.emitEmptyLine(); - - emitFields(writer); - emitGetExpectedObjectSchemaInfoMap(writer); - emitCreateColumnInfoMethod(writer); - emitGetSimpleClassNameMethod(writer); - emitNewInstanceMethod(writer); - emitGetClassModelList(writer); - emitCopyOrUpdateMethod(writer); - emitInsertObjectToRealmMethod(writer); - emitInsertListToRealmMethod(writer); - emitInsertOrUpdateObjectToRealmMethod(writer); - emitInsertOrUpdateListToRealmMethod(writer); - emitCreteOrUpdateUsingJsonObject(writer); - emitCreateUsingJsonStream(writer); - emitCreateDetachedCopyMethod(writer); - writer.endType(); - writer.close(); - } - - private void emitFields(JavaWriter writer) throws IOException { - writer.emitField("Set>", "MODEL_CLASSES", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)); - writer.beginInitializer(true); - writer.emitStatement("Set> modelClasses = new HashSet>(%s)", qualifiedModelClasses.size()); - for (String clazz : qualifiedModelClasses) { - writer.emitStatement("modelClasses.add(%s.class)", clazz); - } - writer.emitStatement("MODEL_CLASSES = Collections.unmodifiableSet(modelClasses)"); - writer.endInitializer(); - writer.emitEmptyLine(); - } - - private void emitGetExpectedObjectSchemaInfoMap(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "Map, OsObjectSchemaInfo>", - "getExpectedObjectSchemaInfoMap", - EnumSet.of(Modifier.PUBLIC)); - - writer.emitStatement( - "Map, OsObjectSchemaInfo> infoMap = " + - "new HashMap, OsObjectSchemaInfo>(%s)", qualifiedProxyClasses.size()); - for (int i = 0; i < qualifiedProxyClasses.size(); i++) { - writer.emitStatement("infoMap.put(%s.class, %s.getExpectedObjectSchemaInfo())", - qualifiedModelClasses.get(i), qualifiedProxyClasses.get(i)); - } - writer.emitStatement("return infoMap"); - - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitCreateColumnInfoMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "ColumnInfo", - "createColumnInfo", - EnumSet.of(Modifier.PUBLIC), - "Class", "clazz", // Argument type & argument name - "OsSchemaInfo", "schemaInfo" - ); - - emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return %s.createColumnInfo(schemaInfo)", - qualifiedProxyClasses.get(i)); - } - }, writer); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitGetSimpleClassNameMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "String", - "getSimpleClassNameImpl", - EnumSet.of(Modifier.PUBLIC), - "Class", "clazz" - ); - emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return \"%s\"", internalClassNames.get(i)); - } - }, writer); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitNewInstanceMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - " E", - "newInstance", - EnumSet.of(Modifier.PUBLIC), - "Class", "clazz", - "Object", "baseRealm", - "Row", "row", - "ColumnInfo", "columnInfo", - "boolean", "acceptDefaultValue", - "List", "excludeFields" - ); - writer.emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()"); - writer.beginControlFlow("try") - .emitStatement("objectContext.set((BaseRealm) baseRealm, row, columnInfo, acceptDefaultValue, excludeFields)"); - emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return clazz.cast(new %s())", qualifiedProxyClasses.get(i)); - } - }, writer); - writer.nextControlFlow("finally") - .emitStatement("objectContext.clear()") - .endControlFlow(); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitGetClassModelList(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod("Set>", "getModelClasses", EnumSet.of(Modifier.PUBLIC)); - writer.emitStatement("return MODEL_CLASSES"); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitCopyOrUpdateMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - " E", - "copyOrUpdate", - EnumSet.of(Modifier.PUBLIC), - "Realm", "realm", - "E", "obj", - "boolean", "update", - "Map", "cache", - "Set", "flags" - ); - writer.emitSingleLineComment("This cast is correct because obj is either"); - writer.emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject"); - writer.emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass())"); - writer.emitEmptyLine(); - emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("%1$s columnInfo = (%1$s) realm.getSchema().getColumnInfo(%2$s.class)", Utils.getSimpleColumnInfoClassName(qualifiedModelClasses.get(i)), qualifiedModelClasses.get(i)); - writer.emitStatement("return clazz.cast(%s.copyOrUpdate(realm, columnInfo, (%s) obj, update, cache, flags))", qualifiedProxyClasses.get(i), qualifiedModelClasses.get(i)); - } - }, writer, false); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitInsertObjectToRealmMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "void", - "insert", - EnumSet.of(Modifier.PUBLIC), - "Realm", "realm", "RealmModel", "object", "Map", "cache"); - writer.emitSingleLineComment("This cast is correct because obj is either"); - writer.emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject"); - writer.emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())"); - writer.emitEmptyLine(); - emitMediatorSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses.get(i), qualifiedModelClasses.get(i)); - } - }, writer, false); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitInsertOrUpdateObjectToRealmMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "void", - "insertOrUpdate", - EnumSet.of(Modifier.PUBLIC), - "Realm", "realm", "RealmModel", "obj", "Map", "cache"); - writer.emitSingleLineComment("This cast is correct because obj is either"); - writer.emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject"); - writer.emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass())"); - writer.emitEmptyLine(); - emitMediatorSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("%s.insertOrUpdate(realm, (%s) obj, cache)", qualifiedProxyClasses.get(i), qualifiedModelClasses.get(i)); - } - }, writer, false); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitInsertOrUpdateListToRealmMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "void", - "insertOrUpdate", - EnumSet.of(Modifier.PUBLIC), - "Realm", "realm", "Collection", "objects"); - - writer.emitStatement("Iterator iterator = objects.iterator()"); - writer.emitStatement("RealmModel object = null"); - writer.emitStatement("Map cache = new HashMap(objects.size())"); - - writer.beginControlFlow("if (iterator.hasNext())") - .emitSingleLineComment(" access the first element to figure out the clazz for the routing below") - .emitStatement("object = iterator.next()") - .emitSingleLineComment("This cast is correct because obj is either") - .emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") - .emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") - .emitEmptyLine(); - - emitMediatorSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("%s.insertOrUpdate(realm, (%s) object, cache)", qualifiedProxyClasses.get(i), qualifiedModelClasses.get(i)); - } - }, writer, false); - - writer.beginControlFlow("if (iterator.hasNext())"); - emitMediatorSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("%s.insertOrUpdate(realm, iterator, cache)", qualifiedProxyClasses.get(i)); - } - }, writer, false); - writer.endControlFlow(); - writer.endControlFlow(); - - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitInsertListToRealmMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - "void", - "insert", - EnumSet.of(Modifier.PUBLIC), - "Realm", "realm", "Collection", "objects"); - - writer.emitStatement("Iterator iterator = objects.iterator()"); - writer.emitStatement("RealmModel object = null"); - writer.emitStatement("Map cache = new HashMap(objects.size())"); - - writer.beginControlFlow("if (iterator.hasNext())") - .emitSingleLineComment(" access the first element to figure out the clazz for the routing below") - .emitStatement("object = iterator.next()") - .emitSingleLineComment("This cast is correct because obj is either") - .emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") - .emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") - .emitEmptyLine(); - - emitMediatorSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses.get(i), qualifiedModelClasses.get(i)); - } - }, writer, false); - - writer.beginControlFlow("if (iterator.hasNext())"); - emitMediatorSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("%s.insert(realm, iterator, cache)", qualifiedProxyClasses.get(i)); - } - }, writer, false); - writer.endControlFlow(); - writer.endControlFlow(); - - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitCreteOrUpdateUsingJsonObject(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - " E", - "createOrUpdateUsingJsonObject", - EnumSet.of(Modifier.PUBLIC), - Arrays.asList("Class", "clazz", "Realm", "realm", "JSONObject", "json", "boolean", "update"), - Arrays.asList("JSONException") - ); - emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return clazz.cast(%s.createOrUpdateUsingJsonObject(realm, json, update))", qualifiedProxyClasses.get(i)); - } - }, writer); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitCreateUsingJsonStream(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - " E", - "createUsingJsonStream", - EnumSet.of(Modifier.PUBLIC), - Arrays.asList("Class", "clazz", "Realm", "realm", "JsonReader", "reader"), - Arrays.asList("java.io.IOException") - ); - emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return clazz.cast(%s.createUsingJsonStream(realm, reader))", qualifiedProxyClasses.get(i)); - } - }, writer); - writer.endMethod(); - writer.emitEmptyLine(); - } - - private void emitCreateDetachedCopyMethod(JavaWriter writer) throws IOException { - writer.emitAnnotation("Override"); - writer.beginMethod( - " E", - "createDetachedCopy", - EnumSet.of(Modifier.PUBLIC), - "E", "realmObject", "int", "maxDepth", "Map>", "cache" - ); - writer.emitSingleLineComment("This cast is correct because obj is either"); - writer.emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject"); - writer.emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) realmObject.getClass().getSuperclass()"); - writer.emitEmptyLine(); - emitMediatorShortCircuitSwitch(new ProxySwitchStatement() { - @Override - public void emitStatement(int i, JavaWriter writer) throws IOException { - writer.emitStatement("return clazz.cast(%s.createDetachedCopy((%s) realmObject, 0, maxDepth, cache))", - qualifiedProxyClasses.get(i), qualifiedModelClasses.get(i)); - } - }, writer, false); - writer.endMethod(); - writer.emitEmptyLine(); - } - - // Emits the control flow for selecting the appropriate proxy class based on the model class - // Currently it is just if..else, which is inefficient for large amounts amounts of model classes. - // Consider switching to HashMap or similar. - private void emitMediatorSwitch(ProxySwitchStatement statement, JavaWriter writer, boolean nullPointerCheck) - throws IOException { - if (nullPointerCheck) { - writer.emitStatement("checkClass(clazz)"); - writer.emitEmptyLine(); - } - if (qualifiedModelClasses.size() == 0) { - writer.emitStatement("throw getMissingProxyClassException(clazz)"); - } else { - writer.beginControlFlow("if (clazz.equals(%s.class))", qualifiedModelClasses.get(0)); - statement.emitStatement(0, writer); - for (int i = 1; i < qualifiedModelClasses.size(); i++) { - writer.nextControlFlow("else if (clazz.equals(%s.class))", qualifiedModelClasses.get(i)); - statement.emitStatement(i, writer); - } - writer.nextControlFlow("else"); - writer.emitStatement("throw getMissingProxyClassException(clazz)"); - writer.endControlFlow(); - } - } - - // Identical to the above, but eliminates the un-needed "else" clauses for, e.g., return statements - private void emitMediatorShortCircuitSwitch(ProxySwitchStatement statement, JavaWriter writer) throws IOException { - emitMediatorShortCircuitSwitch(statement, writer, true); - } - - private void emitMediatorShortCircuitSwitch(ProxySwitchStatement statement, JavaWriter writer, boolean nullPointerCheck) - throws IOException { - if (nullPointerCheck) { - writer.emitStatement("checkClass(clazz)"); - writer.emitEmptyLine(); - } - for (int i = 0; i < qualifiedModelClasses.size(); i++) { - writer.beginControlFlow("if (clazz.equals(%s.class))", qualifiedModelClasses.get(i)); - statement.emitStatement(i, writer); - writer.endControlFlow(); - } - writer.emitStatement("throw getMissingProxyClassException(clazz)"); - } - - - private interface ProxySwitchStatement { - void emitStatement(int i, JavaWriter writer) throws IOException; - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt new file mode 100644 index 0000000000..18fd5dea62 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt @@ -0,0 +1,463 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import com.squareup.javawriter.JavaWriter + +import java.io.BufferedWriter +import java.io.IOException +import java.util.ArrayList +import java.util.Arrays +import java.util.EnumSet +import java.util.Locale + +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.Modifier + +import io.realm.annotations.RealmModule +import javax.tools.JavaFileObject + +class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingEnvironment, + private val className: SimpleClassName, + classesToValidate: Set) { + + private val qualifiedModelClasses = ArrayList() + private val qualifiedProxyClasses = ArrayList() + private val simpleModelClassNames = ArrayList() + private val internalClassNames = ArrayList() + + init { + for (metadata in classesToValidate) { + qualifiedModelClasses.add(metadata.qualifiedClassName) + val qualifiedProxyClassName = QualifiedClassName("${Constants.REALM_PACKAGE_NAME}.${Utils.getProxyClassName(metadata.qualifiedClassName)}") + qualifiedProxyClasses.add(qualifiedProxyClassName) + simpleModelClassNames.add(metadata.simpleJavaClassName) + internalClassNames.add(metadata.internalClassName) + } + } + + @Throws(IOException::class) + fun generate() { + val qualifiedGeneratedClassName: String = String.format(Locale.US, "%s.%sMediator", Constants.REALM_PACKAGE_NAME, className) + val sourceFile: JavaFileObject = processingEnvironment.filer.createSourceFile(qualifiedGeneratedClassName) + val imports = ArrayList(Arrays.asList("android.util.JsonReader", + "java.io.IOException", + "java.util.Collections", + "java.util.HashSet", + "java.util.List", + "java.util.Map", + "java.util.HashMap", + "java.util.Set", + "java.util.Iterator", + "java.util.Collection", + "io.realm.ImportFlag", + "io.realm.internal.ColumnInfo", + "io.realm.internal.RealmObjectProxy", + "io.realm.internal.RealmProxyMediator", + "io.realm.internal.Row", + "io.realm.internal.OsSchemaInfo", + "io.realm.internal.OsObjectSchemaInfo", + "org.json.JSONException", + "org.json.JSONObject")) + + val writer = JavaWriter(BufferedWriter(sourceFile.openWriter())) + writer.apply { + indent = " " + emitPackage(Constants.REALM_PACKAGE_NAME) + emitEmptyLine() + emitImports(imports) + emitEmptyLine() + emitAnnotation(RealmModule::class.java) + beginType(qualifiedGeneratedClassName, // full qualified name of the item to generate + "class", // the type of the item + emptySet(), // modifiers to apply + "RealmProxyMediator") // class to extend + emitEmptyLine() + emitFields(this) + emitGetExpectedObjectSchemaInfoMap(this) + emitCreateColumnInfoMethod(this) + emitGetSimpleClassNameMethod(this) + emitNewInstanceMethod(this) + emitGetClassModelList(this) + emitCopyOrUpdateMethod(this) + emitInsertObjectToRealmMethod(this) + emitInsertListToRealmMethod(this) + emitInsertOrUpdateObjectToRealmMethod(this) + emitInsertOrUpdateListToRealmMethod(this) + emitCreteOrUpdateUsingJsonObject(this) + emitCreateUsingJsonStream(this) + emitCreateDetachedCopyMethod(this) + endType() + close() + } + } + + @Throws(IOException::class) + private fun emitFields(writer: JavaWriter) { + writer.apply { + emitField("Set>", "MODEL_CLASSES", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)) + beginInitializer(true) + emitStatement("Set> modelClasses = new HashSet>(%s)", qualifiedModelClasses.size) + for (clazz in qualifiedModelClasses) { + emitStatement("modelClasses.add(%s.class)", clazz) + } + emitStatement("MODEL_CLASSES = Collections.unmodifiableSet(modelClasses)") + endInitializer() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitGetExpectedObjectSchemaInfoMap(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod("Map, OsObjectSchemaInfo>","getExpectedObjectSchemaInfoMap", EnumSet.of(Modifier.PUBLIC)) + emitStatement("Map, OsObjectSchemaInfo> infoMap = new HashMap, OsObjectSchemaInfo>(%s)", qualifiedProxyClasses.size) + for (i in qualifiedProxyClasses.indices) { + emitStatement("infoMap.put(%s.class, %s.getExpectedObjectSchemaInfo())", qualifiedModelClasses[i], qualifiedProxyClasses[i]) + } + emitStatement("return infoMap") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitCreateColumnInfoMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + "ColumnInfo", + "createColumnInfo", + EnumSet.of(Modifier.PUBLIC), + "Class", "clazz", // Argument type & argument name + "OsSchemaInfo", "schemaInfo" + ) + emitMediatorShortCircuitSwitch({ i: Int -> + emitStatement("return %s.createColumnInfo(schemaInfo)", qualifiedProxyClasses[i]) + }, writer) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitGetSimpleClassNameMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + "String", + "getSimpleClassNameImpl", + EnumSet.of(Modifier.PUBLIC), + "Class", "clazz" + ) + emitMediatorShortCircuitSwitch({ i: Int -> + emitStatement("return \"%s\"", internalClassNames[i]) + }, writer) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitNewInstanceMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + " E", + "newInstance", + EnumSet.of(Modifier.PUBLIC), + "Class", "clazz", + "Object", "baseRealm", + "Row", "row", + "ColumnInfo", "columnInfo", + "boolean", "acceptDefaultValue", + "List", "excludeFields" + ) + emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") + beginControlFlow("try") + emitStatement("objectContext.set((BaseRealm) baseRealm, row, columnInfo, acceptDefaultValue, excludeFields)") + emitMediatorShortCircuitSwitch({ i: Int -> + emitStatement("return clazz.cast(new %s())", qualifiedProxyClasses[i]) + }, writer) + nextControlFlow("finally") + emitStatement("objectContext.clear()") + endControlFlow() + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitGetClassModelList(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod("Set>", "getModelClasses", EnumSet.of(Modifier.PUBLIC)) + emitStatement("return MODEL_CLASSES") + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitCopyOrUpdateMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + " E", + "copyOrUpdate", + EnumSet.of(Modifier.PUBLIC), + "Realm", "realm", + "E", "obj", + "boolean", "update", + "Map", "cache", + "Set", "flags" + ) + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass())") + emitEmptyLine() + emitMediatorShortCircuitSwitch({i: Int -> + emitStatement("%1\$s columnInfo = (%1\$s) realm.getSchema().getColumnInfo(%2\$s.class)", Utils.getSimpleColumnInfoClassName(qualifiedModelClasses[i]), qualifiedModelClasses[i]) + emitStatement("return clazz.cast(%s.copyOrUpdate(realm, columnInfo, (%s) obj, update, cache, flags))", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + }, writer, false) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitInsertObjectToRealmMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + "void", + "insert", + EnumSet.of(Modifier.PUBLIC), + "Realm", "realm", "RealmModel", "object", "Map", "cache") + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") + emitEmptyLine() + emitMediatorSwitch({ i: Int -> + emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + }, writer, false) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitInsertOrUpdateObjectToRealmMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + "void", + "insertOrUpdate", + EnumSet.of(Modifier.PUBLIC), + "Realm", "realm", "RealmModel", "obj", "Map", "cache") + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass())") + emitEmptyLine() + emitMediatorSwitch({ i: Int -> + emitStatement("%s.insertOrUpdate(realm, (%s) obj, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + }, writer, false) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitInsertOrUpdateListToRealmMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + "void", + "insertOrUpdate", + EnumSet.of(Modifier.PUBLIC), + "Realm", "realm", "Collection", "objects") + + emitStatement("Iterator iterator = objects.iterator()") + emitStatement("RealmModel object = null") + emitStatement("Map cache = new HashMap(objects.size())") + + beginControlFlow("if (iterator.hasNext())") + emitSingleLineComment(" access the first element to figure out the clazz for the routing below") + emitStatement("object = iterator.next()") + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") + emitEmptyLine() + + emitMediatorSwitch({ i: Int -> + emitStatement("%s.insertOrUpdate(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + }, writer, false) + + beginControlFlow("if (iterator.hasNext())") + emitMediatorSwitch({ i: Int -> + emitStatement("%s.insertOrUpdate(realm, iterator, cache)", qualifiedProxyClasses[i]) + }, writer, false) + endControlFlow() + endControlFlow() + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitInsertListToRealmMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + "void", + "insert", + EnumSet.of(Modifier.PUBLIC), + "Realm", "realm", "Collection", "objects") + + emitStatement("Iterator iterator = objects.iterator()") + emitStatement("RealmModel object = null") + emitStatement("Map cache = new HashMap(objects.size())") + + beginControlFlow("if (iterator.hasNext())") + .emitSingleLineComment(" access the first element to figure out the clazz for the routing below") + .emitStatement("object = iterator.next()") + .emitSingleLineComment("This cast is correct because obj is either") + .emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + .emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") + .emitEmptyLine() + + emitMediatorSwitch({ i: Int -> + emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + }, writer, false) + + beginControlFlow("if (iterator.hasNext())") + emitMediatorSwitch({ i: Int -> + emitStatement("%s.insert(realm, iterator, cache)", qualifiedProxyClasses[i]) + }, writer, false) + endControlFlow() + endControlFlow() + + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitCreteOrUpdateUsingJsonObject(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + " E", + "createOrUpdateUsingJsonObject", + EnumSet.of(Modifier.PUBLIC), + Arrays.asList("Class", "clazz", "Realm", "realm", "JSONObject", "json", "boolean", "update"), + Arrays.asList("JSONException") + ) + emitMediatorShortCircuitSwitch({ i: Int -> + emitStatement("return clazz.cast(%s.createOrUpdateUsingJsonObject(realm, json, update))", qualifiedProxyClasses[i]) + }, writer) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitCreateUsingJsonStream(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + " E", + "createUsingJsonStream", + EnumSet.of(Modifier.PUBLIC), + Arrays.asList("Class", "clazz", "Realm", "realm", "JsonReader", "reader"), + Arrays.asList("java.io.IOException") + ) + emitMediatorShortCircuitSwitch({ i: Int -> + emitStatement("return clazz.cast(%s.createUsingJsonStream(realm, reader))", qualifiedProxyClasses[i]) + }, writer) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitCreateDetachedCopyMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + " E", + "createDetachedCopy", + EnumSet.of(Modifier.PUBLIC), + "E", "realmObject", "int", "maxDepth", "Map>", "cache" + ) + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) realmObject.getClass().getSuperclass()") + emitEmptyLine() + emitMediatorShortCircuitSwitch({ i: Int -> + emitStatement("return clazz.cast(%s.createDetachedCopy((%s) realmObject, 0, maxDepth, cache))", + qualifiedProxyClasses[i], qualifiedModelClasses[i]) + }, writer, false) + endMethod() + emitEmptyLine() + } + } + + // Emits the control flow for selecting the appropriate proxy class based on the model class + // Currently it is just if..else, which is inefficient for large amounts amounts of model classes. + // Consider switching to HashMap or similar. + @Throws(IOException::class) + private fun emitMediatorSwitch(emitStatement: (index: Int) -> Unit, writer: JavaWriter, nullPointerCheck: Boolean) { + writer.apply { + if (nullPointerCheck) { + emitStatement("checkClass(clazz)") + emitEmptyLine() + } + if (qualifiedModelClasses.isEmpty()) { + emitStatement("throw getMissingProxyClassException(clazz)") + } else { + beginControlFlow("if (clazz.equals(%s.class))", qualifiedModelClasses[0]) + emitStatement(0) + for (i in 1 until qualifiedModelClasses.size) { + nextControlFlow("else if (clazz.equals(%s.class))", qualifiedModelClasses[i]) + emitStatement(i) + } + nextControlFlow("else") + emitStatement("throw getMissingProxyClassException(clazz)") + endControlFlow() + } + } + } + + @Throws(IOException::class) + private fun emitMediatorShortCircuitSwitch(emitStatement: (index: Int) -> Unit, writer: JavaWriter, nullPointerCheck: Boolean = true) { + writer.apply { + if (nullPointerCheck) { + emitStatement("checkClass(clazz)") + emitEmptyLine() + } + for (i in qualifiedModelClasses.indices) { + beginControlFlow("if (clazz.equals(%s.class))", qualifiedModelClasses[i]) + emitStatement(i) + endControlFlow() + } + emitStatement("throw getMissingProxyClassException(clazz)") + } + } + +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java deleted file mode 100644 index f59358c298..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2014 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.tools.Diagnostic; - - -public class RealmVersionChecker { - public static final String REALM_ANDROID_DOWNLOAD_URL = "https://static.realm.io/downloads/java/latest"; - - private static final String VERSION_URL = "https://static.realm.io/update/java?"; - private static final String REALM_VERSION = Version.VERSION; - private static final String REALM_VERSION_PATTERN = "\\d+\\.\\d+\\.\\d+"; - private static final int READ_TIMEOUT = 2000; - private static final int CONNECT_TIMEOUT = 4000; - - private static RealmVersionChecker instance = null; - - private ProcessingEnvironment processingEnvironment; - - public static RealmVersionChecker getInstance(ProcessingEnvironment processingEnvironment) { - if (instance == null) { - instance = new RealmVersionChecker(processingEnvironment); - } - return instance; - } - - private RealmVersionChecker(ProcessingEnvironment processingEnvironment) { - this.processingEnvironment = processingEnvironment; - } - - public void executeRealmVersionUpdate() { - Thread backgroundThread = new Thread(new Runnable() { - @Override - public void run() { - launchRealmCheck(); - } - }); - - backgroundThread.start(); - - try { - backgroundThread.join(CONNECT_TIMEOUT + READ_TIMEOUT); - } catch (InterruptedException ignore) { - // We ignore this exception on purpose not to break the build system if this class fails - } - } - - private void launchRealmCheck() { - //Check Realm version server - String latestVersionStr = checkLatestVersion(); - if (!latestVersionStr.equals(REALM_VERSION)) { - printMessage("Version " + latestVersionStr + " of Realm is now available: " + REALM_ANDROID_DOWNLOAD_URL); - } - } - - private String checkLatestVersion() { - String result = REALM_VERSION; - try { - URL url = new URL(VERSION_URL + REALM_VERSION); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(CONNECT_TIMEOUT); - conn.setReadTimeout(READ_TIMEOUT); - BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String latestVersion = rd.readLine(); - // if the obtained string does not match the pattern, we are in a separate network. - if (latestVersion.matches(REALM_VERSION_PATTERN)) { - result = latestVersion; - } - rd.close(); - } catch (IOException e) { - // We ignore this exception on purpose not to break the build system if this class fails - } - return result; - } - - private void printMessage(String message) { - processingEnvironment.getMessager().printMessage(Diagnostic.Kind.OTHER, message); - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.kt new file mode 100644 index 0000000000..3525e42f15 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmVersionChecker.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL + +import javax.annotation.processing.ProcessingEnvironment +import javax.tools.Diagnostic + + +class RealmVersionChecker private constructor(private val processingEnvironment: ProcessingEnvironment) { + + private val REALM_ANDROID_DOWNLOAD_URL = "https://static.realm.io/downloads/java/latest" + private val VERSION_URL = "https://static.realm.io/update/java?" + private val REALM_VERSION = Version.VERSION + private val REALM_VERSION_PATTERN = "\\d+\\.\\d+\\.\\d+" + private val READ_TIMEOUT = 2000 + private val CONNECT_TIMEOUT = 4000 + + fun executeRealmVersionUpdate() { + val backgroundThread = Thread(Runnable { launchRealmCheck() }) + backgroundThread.start() + try { + backgroundThread.join((CONNECT_TIMEOUT + READ_TIMEOUT).toLong()) + } catch (ignore: InterruptedException) { + // We ignore this exception on purpose not to break the build system if this class fails + } + } + + private fun launchRealmCheck() { + //Check Realm version server + val latestVersionStr = checkLatestVersion() + if (latestVersionStr != REALM_VERSION) { + printMessage("Version $latestVersionStr of Realm is now available: $REALM_ANDROID_DOWNLOAD_URL") + } + } + + private fun checkLatestVersion(): String { + var result = REALM_VERSION + try { + val url = URL(VERSION_URL + REALM_VERSION) + val conn = url.openConnection() as HttpURLConnection + conn.connectTimeout = CONNECT_TIMEOUT + conn.readTimeout = READ_TIMEOUT + val rd = BufferedReader(InputStreamReader(conn.inputStream)) + val latestVersion = rd.readLine() + // if the obtained string does not match the pattern, we are in a separate network. + if (latestVersion.matches(REALM_VERSION_PATTERN.toRegex())) { + result = latestVersion + } + rd.close() + } catch (e: IOException) { + // We ignore this exception on purpose not to break the build system if this class fails + } + + return result + } + + private fun printMessage(message: String) { + processingEnvironment.messager.printMessage(Diagnostic.Kind.OTHER, message) + } + + companion object { + private var instance: RealmVersionChecker? = null + fun getInstance(env: ProcessingEnvironment): RealmVersionChecker { + if (instance == null) { + synchronized(RealmVersionChecker::class.java) { + if (instance == null) { + instance = RealmVersionChecker(env) + } + } + } + return instance!! + } + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java deleted file mode 100644 index d6a0c84c69..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.processor; - -import java.util.Date; -import java.util.List; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - - -/** - * This class provides {@link TypeMirror} instances used in annotation processor. - * - * WARNING: Comparing type mirrors using either `==` or `equal()` can break when using incremental - * annotation processing. Always use `Types.isSameType()` instead when comparing them. - */ -class TypeMirrors { - final TypeMirror STRING_MIRROR; - final TypeMirror BINARY_MIRROR; - final TypeMirror BOOLEAN_MIRROR; - final TypeMirror LONG_MIRROR; - final TypeMirror INTEGER_MIRROR; - final TypeMirror SHORT_MIRROR; - final TypeMirror BYTE_MIRROR; - final TypeMirror DOUBLE_MIRROR; - final TypeMirror FLOAT_MIRROR; - final TypeMirror DATE_MIRROR; - - final TypeMirror PRIMITIVE_LONG_MIRROR; - final TypeMirror PRIMITIVE_INT_MIRROR; - final TypeMirror PRIMITIVE_SHORT_MIRROR; - final TypeMirror PRIMITIVE_BYTE_MIRROR; - - TypeMirrors(ProcessingEnvironment env) { - final Types typeUtils = env.getTypeUtils(); - final Elements elementUtils = env.getElementUtils(); - - STRING_MIRROR = elementUtils.getTypeElement("java.lang.String").asType(); - BINARY_MIRROR = typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.BYTE)); - BOOLEAN_MIRROR = elementUtils.getTypeElement(Boolean.class.getName()).asType(); - LONG_MIRROR = elementUtils.getTypeElement(Long.class.getName()).asType(); - INTEGER_MIRROR = elementUtils.getTypeElement(Integer.class.getName()).asType(); - SHORT_MIRROR = elementUtils.getTypeElement(Short.class.getName()).asType(); - BYTE_MIRROR = elementUtils.getTypeElement(Byte.class.getName()).asType(); - DOUBLE_MIRROR = elementUtils.getTypeElement(Double.class.getName()).asType(); - FLOAT_MIRROR = elementUtils.getTypeElement(Float.class.getName()).asType(); - DATE_MIRROR = elementUtils.getTypeElement(Date.class.getName()).asType(); - - PRIMITIVE_LONG_MIRROR = typeUtils.getPrimitiveType(TypeKind.LONG); - PRIMITIVE_INT_MIRROR = typeUtils.getPrimitiveType(TypeKind.INT); - PRIMITIVE_SHORT_MIRROR = typeUtils.getPrimitiveType(TypeKind.SHORT); - PRIMITIVE_BYTE_MIRROR = typeUtils.getPrimitiveType(TypeKind.BYTE); - } - - /** - * @return the {@link TypeMirror} of the elements in {@code RealmList}. - */ - public static TypeMirror getRealmListElementTypeMirror(VariableElement field) { - if (!Utils.isRealmList(field)) { - return null; - } - List typeArguments = ((DeclaredType) field.asType()).getTypeArguments(); - return (!typeArguments.isEmpty()) ? typeArguments.get(0) : null; - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt new file mode 100644 index 0000000000..e917487f0a --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +@file:JvmName("TypeMirrors") +package io.realm.processor + +import java.util.Date + +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.VariableElement +import javax.lang.model.type.DeclaredType +import javax.lang.model.type.TypeKind +import javax.lang.model.type.TypeMirror + +/** + * This class provides [TypeMirror] instances used in annotation processor. + * + * WARNING: Comparing type mirrors using either `==` or `equal()` can break when using incremental + * annotation processing. Always use `Types.isSameType()` instead when comparing them. + */ +class TypeMirrors(env: ProcessingEnvironment) { + + @JvmField val STRING_MIRROR: TypeMirror + @JvmField val BINARY_MIRROR: TypeMirror + @JvmField val BOOLEAN_MIRROR: TypeMirror + @JvmField val LONG_MIRROR: TypeMirror + @JvmField val INTEGER_MIRROR: TypeMirror + @JvmField val SHORT_MIRROR: TypeMirror + @JvmField val BYTE_MIRROR: TypeMirror + @JvmField val DOUBLE_MIRROR: TypeMirror + @JvmField val FLOAT_MIRROR: TypeMirror + @JvmField val DATE_MIRROR: TypeMirror + + @JvmField val PRIMITIVE_LONG_MIRROR: TypeMirror + @JvmField val PRIMITIVE_INT_MIRROR: TypeMirror + @JvmField val PRIMITIVE_SHORT_MIRROR: TypeMirror + @JvmField val PRIMITIVE_BYTE_MIRROR: TypeMirror + + init { + val typeUtils = env.typeUtils + val elementUtils = env.elementUtils + + STRING_MIRROR = elementUtils.getTypeElement("java.lang.String").asType() + BINARY_MIRROR = typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.BYTE)) + BOOLEAN_MIRROR = elementUtils.getTypeElement(Boolean::class.javaObjectType.name).asType() + LONG_MIRROR = elementUtils.getTypeElement(Long::class.javaObjectType.name).asType() + INTEGER_MIRROR = elementUtils.getTypeElement(Int::class.javaObjectType.name).asType() + SHORT_MIRROR = elementUtils.getTypeElement(Short::class.javaObjectType.name).asType() + BYTE_MIRROR = elementUtils.getTypeElement(Byte::class.javaObjectType.name).asType() + DOUBLE_MIRROR = elementUtils.getTypeElement(Double::class.javaObjectType.name).asType() + FLOAT_MIRROR = elementUtils.getTypeElement(Float::class.javaObjectType.name).asType() + DATE_MIRROR = elementUtils.getTypeElement(Date::class.javaObjectType.name).asType() + + PRIMITIVE_LONG_MIRROR = typeUtils.getPrimitiveType(TypeKind.LONG) + PRIMITIVE_INT_MIRROR = typeUtils.getPrimitiveType(TypeKind.INT) + PRIMITIVE_SHORT_MIRROR = typeUtils.getPrimitiveType(TypeKind.SHORT) + PRIMITIVE_BYTE_MIRROR = typeUtils.getPrimitiveType(TypeKind.BYTE) + } + + companion object { + /** + * @return the [TypeMirror] of the elements in `RealmList`. + */ + @JvmStatic + fun getRealmListElementTypeMirror(field: VariableElement): TypeMirror? { + if (!Utils.isRealmList(field)) { + return null + } + val typeArguments = (field.asType() as DeclaredType).typeArguments + return if (!typeArguments.isEmpty()) typeArguments[0] else null + } + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java deleted file mode 100644 index d091f6b797..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.java +++ /dev/null @@ -1,423 +0,0 @@ -package io.realm.processor; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; - -import javax.annotation.processing.Messager; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ReferenceType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; - -import io.realm.annotations.RealmNamingPolicy; -import io.realm.processor.nameconverter.CamelCaseConverter; -import io.realm.processor.nameconverter.IdentityConverter; -import io.realm.processor.nameconverter.LowerCaseWithSeparatorConverter; -import io.realm.processor.nameconverter.NameConverter; -import io.realm.processor.nameconverter.PascalCaseConverter; - -/** - * Utility methods working with the Realm processor. - */ -public class Utils { - - private static Types typeUtils; - private static Messager messager; - private static TypeMirror realmInteger; - private static DeclaredType realmList; - private static DeclaredType realmResults; - private static DeclaredType markerInterface; - private static TypeMirror realmModel; - - public static void initialize(ProcessingEnvironment env) { - Elements elementUtils = env.getElementUtils(); - typeUtils = env.getTypeUtils(); - messager = env.getMessager(); - realmInteger = elementUtils.getTypeElement("io.realm.MutableRealmInteger").asType(); - realmList = typeUtils.getDeclaredType( - elementUtils.getTypeElement("io.realm.RealmList"), typeUtils.getWildcardType(null, null)); - realmResults = typeUtils.getDeclaredType( - env.getElementUtils().getTypeElement("io.realm.RealmResults"), typeUtils.getWildcardType(null, null)); - realmModel = elementUtils.getTypeElement("io.realm.RealmModel").asType(); - markerInterface = typeUtils.getDeclaredType(elementUtils.getTypeElement("io.realm.RealmModel")); - } - - /** - * @return true if the given element is the default public no arg constructor for a class. - */ - public static boolean isDefaultConstructor(Element constructor) { - if (constructor.getModifiers().contains(Modifier.PUBLIC)) { - return ((ExecutableElement) constructor).getParameters().isEmpty(); - } - return false; - } - - public static String getProxyClassSimpleName(VariableElement field) { - if (typeUtils.isAssignable(field.asType(), realmList)) { - return getProxyClassName(getGenericTypeQualifiedName(field)); - } else { - return getProxyClassName(getFieldTypeQualifiedName(field)); - } - } - - public static String getModelClassQualifiedName(VariableElement field) { - if (typeUtils.isAssignable(field.asType(), realmList)) { - return getGenericTypeQualifiedName(field); - } else { - return getFieldTypeQualifiedName(field); - } - } - - /** - * @return the proxy class name for a given clazz - */ - public static String getProxyClassName(String qualifiedClassName) { - return qualifiedClassName.replace(".", "_") + Constants.PROXY_SUFFIX; - } - - /** - * @return {@code true} if a field is of type "java.lang.String", {@code false} otherwise. - * @throws IllegalArgumentException if the field is {@code null}. - */ - public static boolean isString(VariableElement field) { - if (field == null) { - throw new IllegalArgumentException("Argument 'field' cannot be null."); - } - return getFieldTypeQualifiedName(field).equals("java.lang.String"); - } - - /** - * @return {@code true} if a field is a primitive type, {@code false} otherwise. - * @throws IllegalArgumentException if the typeString is {@code null}. - */ - public static boolean isPrimitiveType(String typeString) { - if (typeString == null) { - throw new IllegalArgumentException("Argument 'typeString' cannot be null."); - } - return typeString.equals("byte") || typeString.equals("short") || typeString.equals("int") || - typeString.equals("long") || typeString.equals("float") || typeString.equals("double") || - typeString.equals("boolean") || typeString.equals("char"); - } - - /** - * @return {@code true} if a field is a boxed type, {@code false} otherwise. - * @throws IllegalArgumentException if the typeString is {@code null}. - */ - public static boolean isBoxedType(String typeString) { - if (typeString == null) { - throw new IllegalArgumentException("Argument 'typeString' cannot be null."); - } - return typeString.equals(Byte.class.getName()) || typeString.equals(Short.class.getName()) || - typeString.equals(Integer.class.getName()) || typeString.equals(Long.class.getName()) || - typeString.equals(Float.class.getName()) || typeString.equals(Double.class.getName()) || - typeString.equals(Boolean.class.getName()); - } - - /** - * @return {@code true} if a field is a type of primitive types, {@code false} otherwise. - * @throws IllegalArgumentException if the field is {@code null}. - */ - public static boolean isPrimitiveType(VariableElement field) { - if (field == null) { - throw new IllegalArgumentException("Argument 'field' cannot be null."); - } - return field.asType().getKind().isPrimitive(); - } - - /** - * @return {@code true} if a field is of type "byte[]", {@code false} otherwise. - * @throws IllegalArgumentException if the field is {@code null}. - */ - public static boolean isByteArray(VariableElement field) { - if (field == null) { - throw new IllegalArgumentException("Argument 'field' cannot be null."); - } - return getFieldTypeQualifiedName(field).equals("byte[]"); - } - - /** - * @return {@code true} if a given field type string is "java.lang.String", {@code false} otherwise. - * @throws IllegalArgumentException if the fieldType is {@code null}. - */ - public static boolean isString(String fieldType) { - if (fieldType == null) { - throw new IllegalArgumentException("Argument 'fieldType' cannot be null."); - } - return String.class.getName().equals(fieldType); - } - - /** - * @return {@code true} if a given type implement {@code RealmModel}, {@code false} otherwise. - */ - public static boolean isImplementingMarkerInterface(Element classElement) { - return typeUtils.isAssignable(classElement.asType(), markerInterface); - } - - /** - * @return {@code true} if a given field type is {@code MutableRealmInteger}, {@code false} otherwise. - */ - public static boolean isMutableRealmInteger(VariableElement field) { - return typeUtils.isAssignable(field.asType(), realmInteger); - } - - /** - * @return {@code true} if a given field type is {@code RealmList}, {@code false} otherwise. - */ - public static boolean isRealmList(VariableElement field) { - return typeUtils.isAssignable(field.asType(), realmList); - } - - /** - * @param field {@link VariableElement} of a value list field. - * @return element type of the list field. - */ - public static Constants.RealmFieldType getValueListFieldType(VariableElement field) { - final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); - return Constants.LIST_ELEMENT_TYPE_TO_REALM_TYPES.get(elementTypeMirror.toString()); - } - - /** - * @return {@code true} if a given field type is {@code RealmList} and its element type is {@code RealmObject}, - * {@code false} otherwise. - */ - public static boolean isRealmModelList(VariableElement field) { - final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); - if (elementTypeMirror == null) { - return false; - } - return isRealmModel(elementTypeMirror); - } - - /** - * @return {@code true} if a given field type is {@code RealmList} and its element type is value type, - * {@code false} otherwise. - */ - public static boolean isRealmValueList(VariableElement field) { - final TypeMirror elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field); - if (elementTypeMirror == null) { - return false; - } - return !isRealmModel(elementTypeMirror); - } - - /** - * @return {@code true} if a given field type is {@code RealmModel}, {@code false} otherwise. - */ - public static boolean isRealmModel(Element field) { - return isRealmModel(field.asType()); - } - - /** - * @return {@code true} if a given type is {@code RealmModel}, {@code false} otherwise. - */ - public static boolean isRealmModel(TypeMirror type) { - // This will return the wrong result if a model class doesn't exist at all, but - // the compiler will catch that eventually. - return typeUtils.isAssignable(type, realmModel); -// // Not sure what is happening here, but typeUtils.isAssignable("Foo", realmModel) -// // returns true even if Foo doesn't exist. No idea why this is happening. -// // For now punt on the problem and check the direct supertype which should be either -// // RealmObject or RealmModel. -// // Original implementation: `` -// // -// // Theory: It looks like if `type` has the internal TypeTag.ERROR (internal API) it -// // automatically translate to being assignable to everything. Possible some Java Specification -// // rule taking effect. In our case, however we can do better since all Realm classes -// // must be in the same compilation unit, so we should be able to look the type up. -// for (TypeMirror typeMirror : typeUtils.directSupertypes(type)) { -// String supertype = typeMirror.toString(); -// if (supertype.equals("io.realm.RealmObject") || supertype.equals("io.realm.RealmModel")) { -// return true; -// } -// } -// return false; - } - - public static boolean isRealmResults(VariableElement field) { - return typeUtils.isAssignable(field.asType(), realmResults); - } - - // get the fully-qualified type name for the generic type of a RealmResults - public static String getRealmResultsType(VariableElement field) { - if (!Utils.isRealmResults(field)) { return null; } - ReferenceType type = getGenericTypeForContainer(field); - if (null == type) { return null; } - return type.toString(); - } - - // get the fully-qualified type name for the generic type of a RealmList - public static String getRealmListType(VariableElement field) { - if (!Utils.isRealmList(field)) { return null; } - ReferenceType type = getGenericTypeForContainer(field); - if (null == type) { return null; } - return type.toString(); - } - - // Note that, because subclassing subclasses of RealmObject is forbidden, - // there is no need to deal with constructs like: RealmResults<? extends Foos<. - public static ReferenceType getGenericTypeForContainer(VariableElement field) { - TypeMirror fieldType = field.asType(); - TypeKind kind = fieldType.getKind(); - if (kind != TypeKind.DECLARED) { return null; } - - List args = ((DeclaredType) fieldType).getTypeArguments(); - if (args.size() <= 0) { return null; } - - fieldType = args.get(0); - kind = fieldType.getKind(); - // We also support RealmList - if (kind != TypeKind.DECLARED && kind != TypeKind.ARRAY) { return null; } - - return (ReferenceType) fieldType; - } - - /** - * @return the qualified type name for a field. - */ - public static String getFieldTypeQualifiedName(VariableElement field) { - return field.asType().toString(); - } - - /** - * @return the generic type for Lists of the form {@code List} - */ - public static String getGenericTypeQualifiedName(VariableElement field) { - TypeMirror fieldType = field.asType(); - List typeArguments = ((DeclaredType) fieldType).getTypeArguments(); - if (typeArguments.size() == 0) { - return null; - } - return typeArguments.get(0).toString(); - } - - /** - * Strips the package name from a fully qualified class name. - */ - public static String stripPackage(String fullyQualifiedClassName) { - String[] parts = fullyQualifiedClassName.split("\\."); - if (parts.length > 0) { - return parts[parts.length - 1]; - } else { - return fullyQualifiedClassName; - } - } - - public static void error(String message, Element element) { - if (element instanceof RealmFieldElement) { - // Element is being cast to Symbol internally which breaks any implementors of the - // Element interface. This is a hack to work around that. Bad bad Oracle - element = ((RealmFieldElement) element).getFieldReference(); - } - messager.printMessage(Diagnostic.Kind.ERROR, message, element); - } - - public static void error(String message) { - messager.printMessage(Diagnostic.Kind.ERROR, message); - } - - public static void note(String message, Element element) { - if (element instanceof RealmFieldElement) { - // Element is being cast to Symbol internally which breaks any implementors of the - // Element interface. This is a hack to work around that. Bad bad Oracle - element = ((RealmFieldElement) element).getFieldReference(); - } - messager.printMessage(Diagnostic.Kind.NOTE, message, element); - } - - public static void note(String message) { - messager.printMessage(Diagnostic.Kind.NOTE, message); - } - - public static Element getSuperClass(TypeElement classType) { - return typeUtils.asElement(classType.getSuperclass()); - } - - /** - * Returns the interface name for proxy class interfaces - */ - public static String getProxyInterfaceName(String qualifiedClassName) { - return qualifiedClassName.replace(".", "_") + Constants.INTERFACE_SUFFIX; - } - - public static NameConverter getNameFormatter(RealmNamingPolicy policy) { - if (policy == null) { - return new IdentityConverter(); - } - switch (policy) { - case NO_POLICY: return new IdentityConverter(); - case IDENTITY: return new IdentityConverter(); - case LOWER_CASE_WITH_UNDERSCORES: return new LowerCaseWithSeparatorConverter('_'); - case CAMEL_CASE: return new CamelCaseConverter(); - case PASCAL_CASE: return new PascalCaseConverter(); - default: - throw new IllegalArgumentException("Unknown policy: " + policy); - } - } - - /** - * Tries to find the internal class name for a referenced type. In model classes this can - * happen with either direct object references or using `RealmList` or `RealmResults`. - *

            - * This name is required by schema builders that operate on internal names and not the public ones. - *

            - * Finding the internal name is easy if the referenced type is included in the current round - * of annotation processing. In that case the internal name was also calculated in the same round - *

            - * If the referenced type was already compiled, e.g being included from library, then we need - * to get the name from the proxy class. Fortunately ProGuard should not have obfuscated any - * class files at this point, meaning we can look it up dynamically. - *

            - * If a name is looked up using the class loader, it also means that developers need to - * combine a library and app module of model classes at runtime in the RealmConfiguration, but - * this should be a valid use case. - * - * @param qualifiedClassName type to lookup the internal name for. - * @param classCollection collection of classes found in the current round of annotation processing. - * @throws IllegalArgumentException If the internal name could not be looked up - * @return the statement that evalutes to the internal class name. This will either be a string - * constant or a reference to a static field in another class. In both cases, the return result - * should not be put in quotes. - */ - public static String getReferencedTypeInternalClassNameStatement(String qualifiedClassName, ClassCollection classCollection) { - - // Attempt to lookup internal name in current round - if (classCollection.containsQualifiedClass(qualifiedClassName)) { - ClassMetaData metadata = classCollection.getClassFromQualifiedName(qualifiedClassName); - return "\"" + metadata.getInternalClassName() + "\""; - } - - // If we cannot find the name in the current processor round, we have to defer resolving the - // name to runtime. The reason being that the annotation processor can only access the - // compile type class path using Elements and Types which do not allow us to read - // field values. - // - // Doing it this way unfortunately means that if the class is not on the apps classpath - // a rather obscure class-not-found exception will be thrown when starting the app, but since - // this is probably a very niche use case that is acceptable for now. - // - // TODO: We could probably create an internal annotation like `@InternalName("__Permission")` - // which should make it possible for the annotation processor to read the value from the - // proxy class, even for files in other jar files. - return "io.realm." + Utils.getProxyClassName(qualifiedClassName) + ".ClassNameHelper.INTERNAL_CLASS_NAME"; - } - - /** - * Returns a simple reference to the ColumnInfo class inside this model class, i.e. the package - * name is not prefixed. - */ - public static String getSimpleColumnInfoClassName(String qualifiedModelClassName) { - String simpleModelClassName = Utils.stripPackage(qualifiedModelClassName); - return Utils.getProxyClassName(qualifiedModelClassName) + "." + simpleModelClassName + "ColumnInfo"; - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt new file mode 100644 index 0000000000..a84b674d8e --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt @@ -0,0 +1,434 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor + +import javax.annotation.processing.Messager +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.Element +import javax.lang.model.element.ExecutableElement +import javax.lang.model.element.Modifier +import javax.lang.model.element.TypeElement +import javax.lang.model.element.VariableElement +import javax.lang.model.type.DeclaredType +import javax.lang.model.type.ReferenceType +import javax.lang.model.type.TypeKind +import javax.lang.model.type.TypeMirror +import javax.lang.model.util.Types +import javax.tools.Diagnostic + +import io.realm.annotations.RealmNamingPolicy +import io.realm.processor.nameconverter.CamelCaseConverter +import io.realm.processor.nameconverter.IdentityConverter +import io.realm.processor.nameconverter.LowerCaseWithSeparatorConverter +import io.realm.processor.nameconverter.NameConverter +import io.realm.processor.nameconverter.PascalCaseConverter + +/** + * Utility methods working with the Realm processor. + */ +object Utils { + + private lateinit var typeUtils: Types + private lateinit var messager: Messager + private lateinit var realmInteger: TypeMirror + private lateinit var realmList: DeclaredType + private lateinit var realmResults: DeclaredType + private lateinit var markerInterface: DeclaredType + private lateinit var realmModel: TypeMirror + + fun initialize(env: ProcessingEnvironment) { + val elementUtils = env.elementUtils + typeUtils = env.typeUtils + messager = env.messager + realmInteger = elementUtils.getTypeElement("io.realm.MutableRealmInteger").asType() + realmList = typeUtils.getDeclaredType(elementUtils.getTypeElement("io.realm.RealmList"), typeUtils.getWildcardType(null, null)) + realmResults = typeUtils.getDeclaredType(env.elementUtils.getTypeElement("io.realm.RealmResults"), typeUtils.getWildcardType(null, null)) + realmModel = elementUtils.getTypeElement("io.realm.RealmModel").asType() + markerInterface = typeUtils.getDeclaredType(elementUtils.getTypeElement("io.realm.RealmModel")) + } + + /** + * @return true if the given element is the default public no arg constructor for a class. + */ + fun isDefaultConstructor(constructor: Element): Boolean { + return if (constructor.modifiers.contains(Modifier.PUBLIC)) { + (constructor as ExecutableElement).parameters.isEmpty() + } else false + } + + fun getProxyClassSimpleName(field: VariableElement): SimpleClassName { + return if (typeUtils.isAssignable(field.asType(), realmList)) { + getProxyClassName(getGenericTypeQualifiedName(field)!!) + } else { + getProxyClassName(getFieldTypeQualifiedName(field)) + } + } + + fun getModelClassQualifiedName(field: VariableElement): QualifiedClassName { + return if (typeUtils.isAssignable(field.asType(), realmList)) { + getGenericTypeQualifiedName(field)!! + } else { + getFieldTypeQualifiedName(field) + } + } + + /** + * @return the proxy class name for a given clazz + */ + fun getProxyClassName(className: QualifiedClassName): SimpleClassName { + return SimpleClassName(className.toString().replace(".", "_") + Constants.PROXY_SUFFIX) + } + + /** + * @return `true` if a field is of type "java.lang.String", `false` otherwise. + * @throws IllegalArgumentException if the field is `null`. + */ + fun isString(field: VariableElement?): Boolean { + if (field == null) { + throw IllegalArgumentException("Argument 'field' cannot be null.") + } + return getFieldTypeQualifiedName(field).toString() == "java.lang.String" + } + + /** + * @return `true` if a field is a primitive type, `false` otherwise. + * @throws IllegalArgumentException if the typeString is `null`. + */ + fun isPrimitiveType(typeString: String): Boolean { + return typeString == "byte" || typeString == "short" || typeString == "int" || + typeString == "long" || typeString == "float" || typeString == "double" || + typeString == "boolean" || typeString == "char" + } + + fun isPrimitiveType(type: QualifiedClassName): Boolean { + return isPrimitiveType(type.toString()) + } + + /** + * @return `true` if a field is a boxed type, `false` otherwise. + * @throws IllegalArgumentException if the typeString is `null`. + */ + fun isBoxedType(typeString: String?): Boolean { + if (typeString == null) { + throw IllegalArgumentException("Argument 'typeString' cannot be null.") + } + return typeString == Byte::class.javaObjectType.name || typeString == Short::class.javaObjectType.name || + typeString == Int::class.javaObjectType.name || typeString == Long::class.javaObjectType.name || + typeString == Float::class.javaObjectType.name || typeString == Double::class.javaObjectType.name || + typeString == Boolean::class.javaObjectType.name + } + + /** + * @return `true` if a field is a type of primitive types, `false` otherwise. + * @throws IllegalArgumentException if the field is `null`. + */ + fun isPrimitiveType(field: VariableElement?): Boolean { + if (field == null) { + throw IllegalArgumentException("Argument 'field' cannot be null.") + } + return field.asType().kind.isPrimitive + } + + /** + * @return `true` if a field is of type "byte[]", `false` otherwise. + * @throws IllegalArgumentException if the field is `null`. + */ + fun isByteArray(field: VariableElement?): Boolean { + if (field == null) { + throw IllegalArgumentException("Argument 'field' cannot be null.") + } + return getFieldTypeQualifiedName(field).toString() == "byte[]" + } + + /** + * @return `true` if a given field type string is "java.lang.String", `false` otherwise. + * @throws IllegalArgumentException if the fieldType is `null`. + */ + fun isString(fieldType: String?): Boolean { + if (fieldType == null) { + throw IllegalArgumentException("Argument 'fieldType' cannot be null.") + } + return String::class.java.name == fieldType + } + + /** + * @return `true` if a given type implement `RealmModel`, `false` otherwise. + */ + fun isImplementingMarkerInterface(classElement: Element): Boolean { + return typeUtils.isAssignable(classElement.asType(), markerInterface) + } + + /** + * @return `true` if a given field type is `MutableRealmInteger`, `false` otherwise. + */ + fun isMutableRealmInteger(field: VariableElement): Boolean { + return typeUtils.isAssignable(field.asType(), realmInteger) + } + + /** + * @return `true` if a given field type is `RealmList`, `false` otherwise. + */ + fun isRealmList(field: VariableElement): Boolean { + return typeUtils.isAssignable(field.asType(), realmList) + } + + /** + * @param field [VariableElement] of a value list field. + * @return element type of the list field. + */ + fun getValueListFieldType(field: VariableElement): Constants.RealmFieldType { + val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) + return Constants.LIST_ELEMENT_TYPE_TO_REALM_TYPES[elementTypeMirror!!.toString()]!! + } + + /** + * @return `true` if a given field type is `RealmList` and its element type is `RealmObject`, + * `false` otherwise. + */ + fun isRealmModelList(field: VariableElement): Boolean { + val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) ?: return false + return isRealmModel(elementTypeMirror) + } + + /** + * @return `true` if a given field type is `RealmList` and its element type is value type, + * `false` otherwise. + */ + fun isRealmValueList(field: VariableElement): Boolean { + val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) ?: return false + return !isRealmModel(elementTypeMirror) + } + + /** + * @return `true` if a given field type is `RealmModel`, `false` otherwise. + */ + fun isRealmModel(field: Element): Boolean { + return isRealmModel(field.asType()) + } + + /** + * @return `true` if a given type is `RealmModel`, `false` otherwise. + */ + fun isRealmModel(type: TypeMirror?): Boolean { + // This will return the wrong result if a model class doesn't exist at all, but + // the compiler will catch that eventually. + return typeUtils.isAssignable(type, realmModel) + // // Not sure what is happening here, but typeUtils.isAssignable("Foo", realmModel) + // // returns true even if Foo doesn't exist. No idea why this is happening. + // // For now punt on the problem and check the direct supertype which should be either + // // RealmObject or RealmModel. + // // Original implementation: `` + // // + // // Theory: It looks like if `type` has the internal TypeTag.ERROR (internal API) it + // // automatically translate to being assignable to everything. Possible some Java Specification + // // rule taking effect. In our case, however we can do better since all Realm classes + // // must be in the same compilation unit, so we should be able to look the type up. + // for (TypeMirror typeMirror : typeUtils.directSupertypes(type)) { + // String supertype = typeMirror.toString(); + // if (supertype.equals("io.realm.RealmObject") || supertype.equals("io.realm.RealmModel")) { + // return true; + // } + // } + // return false; + } + + fun isRealmResults(field: VariableElement): Boolean { + return typeUtils.isAssignable(field.asType(), realmResults) + } + + // get the fully-qualified type name for the generic type of a RealmResults + fun getRealmResultsType(field: VariableElement): QualifiedClassName? { + if (!isRealmResults(field)) { + return null + } + val type = getGenericTypeForContainer(field) ?: return null + return QualifiedClassName(type.toString()) + } + + // get the fully-qualified type name for the generic type of a RealmList + fun getRealmListType(field: VariableElement): QualifiedClassName? { + if (!isRealmList(field)) { + return null + } + val type = getGenericTypeForContainer(field) ?: return null + return QualifiedClassName(type.toString()) + } + + // Note that, because subclassing subclasses of RealmObject is forbidden, + // there is no need to deal with constructs like: RealmResults<? extends Foos<. + fun getGenericTypeForContainer(field: VariableElement): ReferenceType? { + var fieldType = field.asType() + var kind = fieldType.kind + if (kind != TypeKind.DECLARED) { + return null + } + + val args = (fieldType as DeclaredType).typeArguments + if (args.size <= 0) { + return null + } + + fieldType = args[0] + kind = fieldType.kind + // We also support RealmList + return if (kind != TypeKind.DECLARED && kind != TypeKind.ARRAY) { + null + } else fieldType as ReferenceType + + } + + /** + * @return the qualified type name for a field. + */ + fun getFieldTypeQualifiedName(field: VariableElement): QualifiedClassName { + return QualifiedClassName(field.asType().toString()) + } + + /** + * @return the generic type for Lists of the form `List` + */ + fun getGenericTypeQualifiedName(field: VariableElement): QualifiedClassName? { + val fieldType = field.asType() + val typeArguments = (fieldType as DeclaredType).typeArguments + return if (typeArguments.isEmpty()) null else QualifiedClassName(typeArguments[0].toString()) + } + + /** + * Strips the package name from a fully qualified class name. + */ + fun stripPackage(fullyQualifiedClassName: String): String { + val parts = fullyQualifiedClassName.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + return if (parts.isNotEmpty()) { + parts[parts.size - 1] + } else { + fullyQualifiedClassName + } + } + + fun error(message: String?, element: Element) { + var e = element + if (element is RealmFieldElement) { + // Element is being cast to Symbol internally which breaks any implementors of the + // Element interface. This is a hack to work around that. Bad bad Oracle + e = element.fieldReference + } + messager.printMessage(Diagnostic.Kind.ERROR, message, e) + } + + fun error(message: String?) { + messager.printMessage(Diagnostic.Kind.ERROR, message) + } + + fun note(message: String?, element: Element) { + var e = element + if (element is RealmFieldElement) { + // Element is being cast to Symbol internally which breaks any implementors of the + // Element interface. This is a hack to work around that. Bad bad Oracle + e = element.fieldReference + } + messager.printMessage(Diagnostic.Kind.NOTE, message, e) + } + + fun note(message: String?) { + messager.printMessage(Diagnostic.Kind.NOTE, message) + } + + fun getSuperClass(classType: TypeElement): Element { + return typeUtils.asElement(classType.superclass) + } + + /** + * Returns the interface name for proxy class interfaces + */ + fun getProxyInterfaceName(qualifiedClassName: QualifiedClassName): SimpleClassName { + return SimpleClassName(qualifiedClassName.toString().replace(".", "_") + Constants.INTERFACE_SUFFIX) + } + + fun getNameFormatter(policy: RealmNamingPolicy?): NameConverter { + if (policy == null) { + return IdentityConverter() + } + when (policy) { + RealmNamingPolicy.NO_POLICY -> return IdentityConverter() + RealmNamingPolicy.IDENTITY -> return IdentityConverter() + RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES -> return LowerCaseWithSeparatorConverter('_') + RealmNamingPolicy.CAMEL_CASE -> return CamelCaseConverter() + RealmNamingPolicy.PASCAL_CASE -> return PascalCaseConverter() + else -> throw IllegalArgumentException("Unknown policy: $policy") + } + } + + /** + * Tries to find the internal class name for a referenced type. In model classes this can + * happen with either direct object references or using `RealmList` or `RealmResults`. + * + * + * This name is required by schema builders that operate on internal names and not the public ones. + * + * + * Finding the internal name is easy if the referenced type is included in the current round + * of annotation processing. In that case the internal name was also calculated in the same round + * + * + * If the referenced type was already compiled, e.g being included from library, then we need + * to get the name from the proxy class. Fortunately ProGuard should not have obfuscated any + * class files at this point, meaning we can look it up dynamically. + * + * + * If a name is looked up using the class loader, it also means that developers need to + * combine a library and app module of model classes at runtime in the RealmConfiguration, but + * this should be a valid use case. + * + * @param className type to lookup the internal name for. + * @param classCollection collection of classes found in the current round of annotation processing. + * @throws IllegalArgumentException If the internal name could not be looked up + * @return the statement that evalutes to the internal class name. This will either be a string + * constant or a reference to a static field in another class. In both cases, the return result + * should not be put in quotes. + */ + fun getReferencedTypeInternalClassNameStatement(className: QualifiedClassName?, classCollection: ClassCollection): String { + + // Attempt to lookup internal name in current round + if (classCollection.containsQualifiedClass(className)) { + val metadata = classCollection.getClassFromQualifiedName(className!!) + return "\"" + metadata.internalClassName + "\"" + } + + // If we cannot find the name in the current processor round, we have to defer resolving the + // name to runtime. The reason being that the annotation processor can only access the + // compile type class path using Elements and Types which do not allow us to read + // field values. + // + // Doing it this way unfortunately means that if the class is not on the apps classpath + // a rather obscure class-not-found exception will be thrown when starting the app, but since + // this is probably a very niche use case that is acceptable for now. + // + // TODO: We could probably create an internal annotation like `@InternalName("__Permission")` + // which should make it possible for the annotation processor to read the value from the + // proxy class, even for files in other jar files. + return "io.realm.${getProxyClassName(className!!)}.ClassNameHelper.INTERNAL_CLASS_NAME" + } + + /** + * Returns a simple reference to the ColumnInfo class inside this model class, i.e. the package + * name is not prefixed. + */ + fun getSimpleColumnInfoClassName(className: QualifiedClassName): String { + val simpleModelClassName = className.getSimpleName() + return "${getProxyClassName(className)}.${simpleModelClassName}ColumnInfo" + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ext/JavaWriterExt.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ext/JavaWriterExt.kt new file mode 100644 index 0000000000..9067238f4a --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ext/JavaWriterExt.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.processor.ext + +import com.squareup.javawriter.JavaWriter +import io.realm.processor.QualifiedClassName +import io.realm.processor.SimpleClassName +import javax.lang.model.element.Modifier + +fun JavaWriter.beginType(type: QualifiedClassName, + kind: String, + modifiers: Set, + extendsType: QualifiedClassName, + implementsType: Array): JavaWriter { + return this.beginType(type.toString(), kind, modifiers, extendsType.toString(), *implementsType) +} + +fun JavaWriter.beginType(type: QualifiedClassName, + kind: String, + modifiers: Set, + extendsType: QualifiedClassName, + implementsType: Array): JavaWriter { + val types: Array = implementsType.map { it.toString() }.toTypedArray() + return this.beginType(type.toString(), kind, modifiers, extendsType.toString(), *types) +} + +fun JavaWriter.beginMethod(returnType: QualifiedClassName, + name: String, + modifiers: Set, + vararg parameters: String): JavaWriter { + return this.beginMethod(returnType.toString(), name, modifiers, *parameters) +} + +fun JavaWriter.beginMethod(returnType: QualifiedClassName, + name: String, + modifiers: Set, + parameters: List, + throwsTypes: List): JavaWriter { + return this.beginMethod(returnType.toString(), name, modifiers, parameters, throwsTypes) +} + diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.kt similarity index 57% rename from realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.java rename to realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.kt index f060cf2a41..d131746700 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/CamelCaseConverter.kt @@ -13,32 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm.processor.nameconverter; +package io.realm.processor.nameconverter /** * Converter that converts input to "camelCase". */ -public class CamelCaseConverter implements NameConverter { +class CamelCaseConverter : NameConverter { - private final WordTokenizer tokenizer = new WordTokenizer(); + private val tokenizer = WordTokenizer() - @Override - public String convert(String name) { - String[] words = tokenizer.split(name); - StringBuilder output = new StringBuilder(); - boolean firstWordEmitted = false; - for (int i = 0; i < words.length; i++) { - String word = words[i].toLowerCase(); + override fun convert(name: String): String { + val words = tokenizer.split(name) + val output = StringBuilder() + var firstWordEmitted = false + for (i in words.indices) { + val word = words[i].toLowerCase() if (firstWordEmitted) { - int codepoint = word.codePointAt(0); - output.appendCodePoint(Character.toUpperCase(codepoint)); - output.append(word.substring(Character.charCount(codepoint))); + val codepoint = word.codePointAt(0) + output.appendCodePoint(Character.toUpperCase(codepoint)) + output.append(word.substring(Character.charCount(codepoint))) } else { - output.append(word); - firstWordEmitted = true; + output.append(word) + firstWordEmitted = true } } - return output.toString(); + return output.toString() } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.kt similarity index 74% rename from realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.java rename to realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.kt index 4408407c3a..13e134da8b 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/IdentityConverter.kt @@ -13,18 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm.processor.nameconverter; +package io.realm.processor.nameconverter /** * Converter that doesn't do any conversion when translating from Java to Realm. * - * @see io.realm.annotations.RealmNamingPolicy#IDENTITY + * @see io.realm.annotations.RealmNamingPolicy.IDENTITY */ -public class IdentityConverter implements NameConverter { +class IdentityConverter : NameConverter { - @Override - public String convert(String name) { - return name; + override fun convert(name: String): String { + return name } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.java deleted file mode 100644 index 3be7b191a3..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.processor.nameconverter; - -/** - * Converter that converts input to lower case with a defined separator character. - */ -public class LowerCaseWithSeparatorConverter implements NameConverter { - - private final WordTokenizer tokenizer = new WordTokenizer(); - private final char separator; - - public LowerCaseWithSeparatorConverter(char separator) { - this.separator = separator; - } - - @Override - public String convert(String name) { - String[] words = tokenizer.split(name); - StringBuilder output = new StringBuilder(); - for (int i = 0; i < words.length; i++) { - String word = words[i].toLowerCase(); - output.append(word); - if (i < words.length - 1) { - output.append(separator); - } - } - - return output.toString(); - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.kt new file mode 100644 index 0000000000..7f32ecb554 --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/LowerCaseWithSeparatorConverter.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.processor.nameconverter + +/** + * Converter that converts input to lower case with a defined separator character. + */ +class LowerCaseWithSeparatorConverter(private val separator: Char) : NameConverter { + + private val tokenizer = WordTokenizer() + + override fun convert(name: String): String { + val words = tokenizer.split(name) + val output = StringBuilder() + for (i in words.indices) { + val word = words[i].toLowerCase() + output.append(word) + if (i < words.size - 1) { + output.append(separator) + } + } + + return output.toString() + } +} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.kt similarity index 79% rename from realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.java rename to realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.kt index 593adf48ce..71db29acef 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/NameConverter.kt @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm.processor.nameconverter; +package io.realm.processor.nameconverter /** * Interface for converters that can implement a given naming policy. * * @see io.realm.annotations.RealmNamingPolicy */ -public interface NameConverter { +interface NameConverter { /** - * Converts the {@code name} so it matches the {@link io.realm.annotations.RealmNamingPolicy}. + * Converts the `name` so it matches the [io.realm.annotations.RealmNamingPolicy]. * * @param name string to convert. * @return the converted string. */ - String convert(String name); + fun convert(name: String): String } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.kt similarity index 60% rename from realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.java rename to realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.kt index 3849a72f64..41bdfa2359 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.java +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/PascalCaseConverter.kt @@ -13,26 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm.processor.nameconverter; +package io.realm.processor.nameconverter /** * Converter that converts input to "PascalCase". */ -public class PascalCaseConverter implements NameConverter { +class PascalCaseConverter : NameConverter { - private final WordTokenizer tokenizer = new WordTokenizer(); + private val tokenizer = WordTokenizer() - @Override - public String convert(String name) { - String[] words = tokenizer.split(name); - StringBuilder output = new StringBuilder(); - for (int i = 0; i < words.length; i++) { - String word = words[i].toLowerCase(); - int codepoint = word.codePointAt(0); - output.appendCodePoint(Character.toUpperCase(codepoint)); - output.append(word.substring(Character.charCount(codepoint))); + override fun convert(name: String): String { + val words = tokenizer.split(name) + val output = StringBuilder() + for (i in words.indices) { + val word = words[i].toLowerCase() + val codepoint = word.codePointAt(0) + output.appendCodePoint(Character.toUpperCase(codepoint)) + output.append(word.substring(Character.charCount(codepoint))) } - return output.toString(); + return output.toString() } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.java b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.java deleted file mode 100644 index 9a1e3caa27..0000000000 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.processor.nameconverter; - -import java.util.ArrayList; -import java.util.List; - -/** - * Segments a Java variable name into component words. - * - * Java variable names must follow the rules described in: - * https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.8 - * - * In this implementation we treat word separators as any of the following: - *

              - *
            1. - * Anytime a {@code _} or {@code $} is encountered. - * Example is "_FooBar" or "_Foo$Bar" which both becomes "Foo" and "Bar". - *
            2. - *
            3. - * Anytime you switch from a lower case character to an upper case character as - * identified by a `Character.isUpperCase(codepoint)` and `Character.isLowerCase(codepoint)`. - * Example is "FooBar" which becomes "Foo" and "Bar". - *
            4. - *
            5. - * Anytime you switch from more than one uppercase character to a lower case one. As - * identified by `Character.isUpperCase(codepoint)` and `Character.isLowerCase(codepoint)`. - * Example is "FOOBar" which becomes "FOO" and "Bar. - *
            6. - *
            7. - * Some characters like emojiis are neither uppercase or lowercase characters, so they will - * not trigger any of the above rules. - * Examples are "my😁" and "MY😁" which are both treated as one word. - *
            8. - *
            9. - * Hungarian notation, i.e. strings starting with lowercase "m" followed by uppercase letter - * is stripped and not considered part of any word. - *
            10. - *
            - */ -public class WordTokenizer { - - /** - * Segments a string into words as described above - */ - String[] split(String str) { - if (str == null || str.isEmpty()) { - return new String[0]; - } - - Integer previousCodepoint; - Integer currentCodepoint = null; - int length = str.length(); - int offset = 0; - StringBuilder currentWord = new StringBuilder(); - List words = new ArrayList<>(); - Boolean wordAllUpperCase = null; - int lastCodePointCharLength = 0; - while (offset < length) { - previousCodepoint = currentCodepoint; - currentCodepoint = str.codePointAt(offset); - int currentCharCount = Character.charCount(currentCodepoint); - boolean previousCodePointUpperCase = previousCodepoint != null && Character.isUpperCase(previousCodepoint); - boolean previousCodePointLowerCase = previousCodepoint != null && Character.isLowerCase(previousCodepoint); - boolean currentCodePointUpperCase = Character.isUpperCase(currentCodepoint); - boolean currentCodePointLowerCase = Character.isLowerCase(currentCodepoint); - - // Separator char encountered not part of any word, but indicate a boundary - if (currentCodepoint == '_' || currentCodepoint == '$') { - if (currentWord.length() > 0) { - words.add(currentWord.toString()); - currentWord.setLength(0); - } - - wordAllUpperCase = null; - offset += currentCharCount; - lastCodePointCharLength = 0; - continue; - } - - // Change between lower case and upper case indicate a word boundary - if (previousCodePointLowerCase && currentCodePointUpperCase) { - if (currentWord.length() > 0) { - words.add(currentWord.toString()); - currentWord.setLength(0); - currentWord.appendCodePoint(currentCodepoint); - } - - wordAllUpperCase = true; - offset += currentCharCount; - lastCodePointCharLength = currentCharCount; - continue; - } - - // Change between upper case and lower case indicated a word boundary on the previous - // char if multiple upper case characters where encountered. - if (currentWord.length() > 1 - && (wordAllUpperCase != null && wordAllUpperCase) - && previousCodePointUpperCase && currentCodePointLowerCase) { - words.add(currentWord.substring(0, currentWord.length() - lastCodePointCharLength)); - currentWord.substring(0, currentWord.length() - lastCodePointCharLength); - currentWord.delete(0, currentWord.length() - lastCodePointCharLength); - currentWord.appendCodePoint(currentCodepoint); - - wordAllUpperCase = false; - offset += currentCharCount; - lastCodePointCharLength = currentCharCount; - continue; - } - - // Add codepoint to current word - currentWord.appendCodePoint(currentCodepoint); - wordAllUpperCase = currentCodePointUpperCase && (wordAllUpperCase == null || wordAllUpperCase); - offset += currentCharCount; - lastCodePointCharLength = currentCharCount; - } - - // Add final word when exiting loop - if (currentWord.length() > 0) { - words.add(currentWord.toString()); - } - - // Remove hungarian notation if found - if (words.get(0).equals("m")) { - words.remove(0); - } - - String[] result = new String[words.size()]; - words.toArray(result); - return result; - } -} diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.kt new file mode 100644 index 0000000000..c676c8033c --- /dev/null +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/nameconverter/WordTokenizer.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.processor.nameconverter + +import java.util.ArrayList + +/** + * Segments a Java variable name into component words. + * + * Java variable names must follow the rules described in: + * https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.8 + * + * In this implementation we treat word separators as any of the following: + * + * 1. Anytime a `_` or `$` is encountered. + * Example is "_FooBar" or "_Foo$Bar" which both becomes "Foo" and "Bar". + * + * 2. Anytime you switch from a lower case character to an upper case character as identified by a + * `Character.isUpperCase(codepoint)` and `Character.isLowerCase(codepoint)`. + * Example is "FooBar" which becomes "Foo" and "Bar". + * + * 3. Anytime you switch from more than one uppercase character to a lower case one. As identified + * by `Character.isUpperCase(codepoint)` and `Character.isLowerCase(codepoint)`. + * Example is "FOOBar" which becomes "FOO" and "Bar. + * + * 4. Some characters like emojiis are neither uppercase or lowercase characters, so they will + * not trigger any of the above rules. + * Examples are "my😁" and "MY😁" which are both treated as one word. + * + * 5. Hungarian notation, i.e. strings starting with lowercase "m" followed by uppercase letter + * is stripped and not considered part of any word. + */ +class WordTokenizer { + + /** + * Segments a string into words as described above + */ + internal fun split(str: String?): Array { + if (str == null || str.isEmpty()) { + return arrayOf() + } + + var previousCodepoint: Int? + var currentCodepoint: Int? = null + val length = str.length + var offset = 0 + val currentWord = StringBuilder() + val words = ArrayList() + var wordAllUpperCase: Boolean? = null + var lastCodePointCharLength = 0 + while (offset < length) { + previousCodepoint = currentCodepoint + currentCodepoint = str.codePointAt(offset) + val currentCharCount = Character.charCount(currentCodepoint) + val previousCodePointUpperCase = previousCodepoint != null && Character.isUpperCase(previousCodepoint) + val previousCodePointLowerCase = previousCodepoint != null && Character.isLowerCase(previousCodepoint) + val currentCodePointUpperCase = Character.isUpperCase(currentCodepoint) + val currentCodePointLowerCase = Character.isLowerCase(currentCodepoint) + + // Separator char encountered not part of any word, but indicate a boundary + if (currentCodepoint == '_'.toInt() || currentCodepoint == '$'.toInt()) { + if (currentWord.isNotEmpty()) { + words.add(currentWord.toString()) + currentWord.setLength(0) + } + + wordAllUpperCase = null + offset += currentCharCount + lastCodePointCharLength = 0 + continue + } + + // Change between lower case and upper case indicate a word boundary + if (previousCodePointLowerCase && currentCodePointUpperCase) { + if (currentWord.isNotEmpty()) { + words.add(currentWord.toString()) + currentWord.setLength(0) + currentWord.appendCodePoint(currentCodepoint) + } + + wordAllUpperCase = true + offset += currentCharCount + lastCodePointCharLength = currentCharCount + continue + } + + // Change between upper case and lower case indicated a word boundary on the previous + // char if multiple upper case characters where encountered. + if (currentWord.length > 1 + && wordAllUpperCase != null && wordAllUpperCase + && previousCodePointUpperCase && currentCodePointLowerCase) { + words.add(currentWord.substring(0, currentWord.length - lastCodePointCharLength)) + currentWord.substring(0, currentWord.length - lastCodePointCharLength) + currentWord.delete(0, currentWord.length - lastCodePointCharLength) + currentWord.appendCodePoint(currentCodepoint) + + wordAllUpperCase = false + offset += currentCharCount + lastCodePointCharLength = currentCharCount + continue + } + + // Add codepoint to current word + currentWord.appendCodePoint(currentCodepoint) + wordAllUpperCase = currentCodePointUpperCase && (wordAllUpperCase == null || wordAllUpperCase) + offset += currentCharCount + lastCodePointCharLength = currentCharCount + } + + // Add final word when exiting loop + if (currentWord.length > 0) { + words.add(currentWord.toString()) + } + + // Remove hungarian notation if found + if (words[0] == "m") { + words.removeAt(0) + } + + return words.toTypedArray() + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java index 270007ac3e..b7ef785f6e 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java @@ -27,6 +27,7 @@ public static void setUp() { // adb push /tools/sync_test_server/keys/android_test_certificate.crt /sdcard/ // then import the certificate from the device (Settings/Security/Install from storage) @Test + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") public void sslVerifyCallback_certificateChainWithRootCAInstalledShouldValidate() { // simulating the following certificate chain // --- diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index 08c58ce930..4624b3881f 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -19,6 +19,7 @@ import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; @@ -50,6 +51,7 @@ public class SSLConfigurationTests extends StandardIntegrationTest { @Test @RunTestInLooperThread + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") public void trustedRootCA() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; @@ -234,6 +236,7 @@ public void trustedRootCA_notExisting_certificate_willThrow() { @Test @RunTestInLooperThread + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") public void combiningTrustedRootCA_and_disableSSLVerification() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; @@ -286,6 +289,7 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt // then import the certificate from the device (Settings/Security/Install from storage) @Test @RunTestInLooperThread + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") public void sslVerifyCallback_isUsed() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; From c810a3e6c3d2172678ea5624822904391ac85371 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 20 Jun 2019 10:23:07 -0400 Subject: [PATCH 1382/2110] Remove app_name from Kotlin Extensions (#6541) --- CHANGELOG.md | 16 ++++++++++++++++ .../src/main/res/values/strings.xml | 3 --- .../TrustManagerCertificateValidationTests.java | 1 + .../java/io/realm/SSLConfigurationTests.java | 5 +++++ 4 files changed, 22 insertions(+), 3 deletions(-) delete mode 100644 realm/kotlin-extensions/src/main/res/values/strings.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bce63b83f..833e75d27f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 5.11.1(YYYY-MM-DD) + +### Enhancements +* None. + +### Fixed +* The Kotlin extensions library no longer defines a `app_name`, which in some cases conflicted with the `app_name` defined by applications. (Issue [#6536](https://github.com/realm/realm-java/issues/6536), since 4.3.0) + +### Compatibility +* Realm Object Server: 3.21.0-rc1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None. + ## 5.11.0(2019-05-01) ### Enhancements diff --git a/realm/kotlin-extensions/src/main/res/values/strings.xml b/realm/kotlin-extensions/src/main/res/values/strings.xml deleted file mode 100644 index 764b07b814..0000000000 --- a/realm/kotlin-extensions/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - kotlin-extensions - diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java index 270007ac3e..b7ef785f6e 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java @@ -27,6 +27,7 @@ public static void setUp() { // adb push /tools/sync_test_server/keys/android_test_certificate.crt /sdcard/ // then import the certificate from the device (Settings/Security/Install from storage) @Test + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") public void sslVerifyCallback_certificateChainWithRootCAInstalledShouldValidate() { // simulating the following certificate chain // --- diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index 08c58ce930..19817ab1b4 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -19,6 +19,7 @@ import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; @@ -50,6 +51,7 @@ public class SSLConfigurationTests extends StandardIntegrationTest { @Test @RunTestInLooperThread + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") public void trustedRootCA() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; @@ -214,6 +216,7 @@ public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { @Test @RunTestInLooperThread + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") public void trustedRootCA_notExisting_certificate_willThrow() { String username = UUID.randomUUID().toString(); String password = "password"; @@ -234,6 +237,7 @@ public void trustedRootCA_notExisting_certificate_willThrow() { @Test @RunTestInLooperThread + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") public void combiningTrustedRootCA_and_disableSSLVerification() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; @@ -286,6 +290,7 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt // then import the certificate from the device (Settings/Security/Install from storage) @Test @RunTestInLooperThread + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") public void sslVerifyCallback_isUsed() throws InterruptedException { String username = UUID.randomUUID().toString(); String password = "password"; From 97ee36d8aefc7fea0c991c68577f2cbcb3486f0b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 20 Jun 2019 17:14:37 +0100 Subject: [PATCH 1383/2110] exposing to_json from Core (#6540) * exposing to_json from Core --- CHANGELOG.md | 7 +- dependencies.list | 4 +- realm/realm-library/build.gradle | 1 + .../java/io/realm/RealmResultsTests.java | 242 ++++++++++++++++++ .../main/cpp/io_realm_internal_OsResults.cpp | 15 ++ realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/RealmResults.java | 22 +- .../java/io/realm/internal/OsResults.java | 6 + 8 files changed, 290 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 499c1a45cf..0a69d27f04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Enhancements * [ObjectServer] Added `SyncManager.refreshConnections()` that can be used to manually trigger a reconnect for all sessions. This is useful if the device has been offline for a long time or fail to detect that it regained connectivity. (Issue [#259](https://github.com/realm/realm-java-private/issues/259)) +* Added `RealmResults.asJson()` in `@Beta` that returns the result of the query as a JSON payload (#6540). + ### Fixed * [ObjectServer] `PermissionManager` stopped working if an intermittent network error was reported. (Issue [#6492](https://github.com/realm/realm-java/issues/6492), since 3.7.0) @@ -12,7 +14,10 @@ * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal -* None. +* Updated to Realm Core 5.22.0. +* Updated to Relm Sync 4.6.1. +* Updated to Object Store commit 7c3ff8235579550a3e3c6060c47140b2005174f5 + ## 5.11.0(2019-05-01) diff --git a/dependencies.list b/dependencies.list index 48b5b292ce..78968bcff3 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=4.4.2 -REALM_SYNC_SHA256=7f3386bc9e590788afc6fd61744dd187148862513ddbd7e65c32d1d9c371e1ce +REALM_SYNC_VERSION=4.6.1 +REALM_SYNC_SHA256=eb4fbf83717156fabae6357199b22ef25546f2da24ded5668290351613faf9d0 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 4562cf3be7..25a384aab7 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -226,6 +226,7 @@ dependencies { androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + androidTestImplementation "org.skyscreamer:jsonassert:1.5.0" // specify error prone version to prevent sudden failure errorprone 'com.google.errorprone:error_prone_core:2.1.2' diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 2714b81ba6..fa62390c24 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -19,6 +19,7 @@ import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; +import org.json.JSONException; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -26,16 +27,21 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONAssert; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; +import io.realm.entities.CyclicType; import io.realm.entities.DefaultValueOfField; import io.realm.entities.Dog; import io.realm.entities.MappedAllJavaTypes; @@ -1685,4 +1691,240 @@ public void setValue_specificType_internalNameOnDynamicRealms() { dynamicRealm.close(); } } + + @Test + public void asJSON() throws JSONException { + Date date = Calendar.getInstance().getTime(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); // Core return dates in UTC time + String now = sdf.format(date); + + realm.beginTransaction(); + + AllTypes allTypes = realm.createObject(AllTypes.class); + Dog dog1 = realm.createObject(Dog.class); + Dog dog2 = realm.createObject(Dog.class); + Dog dog3 = realm.createObject(Dog.class); + + dog1.setName("dog1"); + dog1.setAge(1); + dog1.setBirthday(date); + dog1.setHasTail(true); + dog1.setHeight(1.1f); + dog1.setWeight(10.1f); + + dog2.setName("dog2"); + dog2.setAge(2); + dog2.setBirthday(date); + dog2.setHasTail(false); + dog2.setHeight(2.1f); + dog2.setWeight(20.1f); + + dog3.setName("dog3"); + dog3.setAge(3); + dog3.setBirthday(date); + dog3.setHasTail(true); + dog3.setHeight(3.1f); + dog3.setWeight(30.1f); + + Owner owner = realm.createObject(Owner.class); + owner.setName("Dog owner 1"); + dog3.setOwner(owner); + + allTypes.setColumnString("alltypes1"); + allTypes.setColumnLong(1337L); + allTypes.setColumnFloat(3.14f); + allTypes.setColumnDouble(0.89123); + allTypes.setColumnBoolean(false); + allTypes.setColumnDate(date); + allTypes.setColumnBinary(new byte[]{1, 2, 3}); + allTypes.setColumnRealmObject(dog1); + allTypes.getColumnRealmList().add(dog2); + allTypes.getColumnRealmList().add(dog3); + allTypes.getColumnStringList().add("Foo"); + allTypes.getColumnStringList().add("Bar"); + allTypes.getColumnBooleanList().add(false); + allTypes.getColumnBooleanList().add(true); + allTypes.getColumnLongList().add(1000L); + allTypes.getColumnLongList().add(2000L); + allTypes.getColumnDoubleList().add(1.123); + allTypes.getColumnDoubleList().add(5.321); + allTypes.getColumnFloatList().add(0.12f); + allTypes.getColumnFloatList().add(0.13f); + allTypes.getColumnDateList().add(date); + allTypes.getColumnDateList().add(date); + + AllTypes allTypes2 = realm.createObject(AllTypes.class); + allTypes2.setColumnString("alltypes2"); + realm.commitTransaction(); + + RealmResults all = realm.where(AllTypes.class) + .equalTo("columnString", "alltypes1").findAll(); + assertEquals(1, all.size()); + String json = all.asJSON(); + final String expectedJSON = "[\n" + + " {\n" + + " \"columnString\": \"alltypes1\",\n" + + " \"columnLong\": 1337,\n" + + " \"columnFloat\": 3.1400001,\n" + + " \"columnDouble\": 0.89122999999999997,\n" + + " \"columnBoolean\": false,\n" + + " \"columnDate\": \"" + now + "\",\n" + + " \"columnBinary\": \"010203\",\n" + + " \"columnMutableRealmInteger\": 0,\n" + + " \"columnRealmObject\": [\n" + + " {\n" + + " \"name\": \"dog1\",\n" + + " \"age\": 1,\n" + + " \"height\": 1.1,\n" + + " \"weight\": 10.100000381469727,\n" + + " \"hasTail\": true,\n" + + " \"birthday\": \"" + now + "\",\n" + + " \"owner\": []\n" + + " }\n" + + " ],\n" + + " \"columnRealmList\": [\n" + + " {\n" + + " \"name\": \"dog2\",\n" + + " \"age\": 2,\n" + + " \"height\": 2.0999999,\n" + + " \"weight\": 20.100000381469727,\n" + + " \"hasTail\": false,\n" + + " \"birthday\": \"" + now + "\",\n" + + " \"owner\": []\n" + + " },\n" + + " {\n" + + " \"name\": \"dog3\",\n" + + " \"age\": 3,\n" + + " \"height\": 3.0999999,\n" + + " \"weight\": 30.100000381469727,\n" + + " \"hasTail\": true,\n" + + " \"birthday\": \"" + now + "\",\n" + + " \"owner\": [\n" + + " {\n" + + " \"name\": \"Dog owner 1\",\n" + + " \"dogs\": [],\n" + + " \"cat\": []\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"columnStringList\": [\n" + + " {\n" + + " \"!ARRAY_VALUE\": \"Foo\"\n" + + " },\n" + + " {\n" + + " \"!ARRAY_VALUE\": \"Bar\"\n" + + " }\n" + + " ],\n" + + " \"columnBinaryList\": [],\n" + + " \"columnBooleanList\": [\n" + + " {\n" + + " \"!ARRAY_VALUE\": false\n" + + " },\n" + + " {\n" + + " \"!ARRAY_VALUE\": true\n" + + " }\n" + + " ],\n" + + " \"columnLongList\": [\n" + + " {\n" + + " \"!ARRAY_VALUE\": 1000\n" + + " },\n" + + " {\n" + + " \"!ARRAY_VALUE\": 2000\n" + + " }\n" + + " ],\n" + + " \"columnDoubleList\": [\n" + + " {\n" + + " \"!ARRAY_VALUE\": 1.123\n" + + " },\n" + + " {\n" + + " \"!ARRAY_VALUE\": 5.3209999999999997\n" + + " }\n" + + " ],\n" + + " \"columnFloatList\": [\n" + + " {\n" + + " \"!ARRAY_VALUE\": 0.12\n" + + " },\n" + + " {\n" + + " \"!ARRAY_VALUE\": 0.13\n" + + " }\n" + + " ],\n" + + " \"columnDateList\": [\n" + + " {\n" + + " \"!ARRAY_VALUE\": \"" + now + "\"\n" + + " },\n" + + " {\n" + + " \"!ARRAY_VALUE\": \"" + now + "\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "]"; + JSONAssert.assertEquals(expectedJSON, json, false); + } + + @Test + public void asJSON_cycles() throws JSONException { + Date date = Calendar.getInstance().getTime(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); // Core return dates in UTC time + String now = sdf.format(date); + + CyclicType oneCyclicType = new CyclicType(); + oneCyclicType.setName("One"); + oneCyclicType.setDate(date); + + CyclicType anotherCyclicType = new CyclicType(); + anotherCyclicType.setName("Two"); + anotherCyclicType.setDate(date); + + oneCyclicType.setObject(anotherCyclicType); + anotherCyclicType.setObject(oneCyclicType); + + realm.beginTransaction(); + realm.insert(Arrays.asList(oneCyclicType, anotherCyclicType)); + realm.commitTransaction(); + + RealmResults realmObjects = realm.where(CyclicType.class).sort(CyclicType.FIELD_NAME).findAll(); + assertEquals(2, realmObjects.size()); + String json = realmObjects.asJSON(); + String expectedJSON = "[\n" + + " {\n" + + " \"id\": 0,\n" + + " \"name\": \"One\",\n" + + " \"date\": \"" + now + "\",\n" + + " \"object\": [\n" + + " {\n" + + " \"id\": 0,\n" + + " \"name\": \"Two\",\n" + + " \"date\": \"" + now + "\",\n" + + " \"object\": \"0\",\n" + + " \"otherObject\": [],\n" + + " \"objects\": []\n" + + " }\n" + + " ],\n" + + " \"otherObject\": [],\n" + + " \"objects\": []\n" + + " },\n" + + " {\n" + + " \"id\": 0,\n" + + " \"name\": \"Two\",\n" + + " \"date\": \"" + now + "\",\n" + + " \"object\": [\n" + + " {\n" + + " \"id\": 0,\n" + + " \"name\": \"One\",\n" + + " \"date\": \"" + now + "\",\n" + + " \"object\": \"1\",\n" + + " \"otherObject\": [],\n" + + " \"objects\": []\n" + + " }\n" + + " ],\n" + + " \"otherObject\": [],\n" + + " \"objects\": []\n" + + " }\n" + + "]"; + JSONAssert.assertEquals(expectedJSON, json, false); + } + } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index b8b4f19ec0..e03f7d0c56 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -279,6 +279,21 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeWhere(JNIEnv* env return 0; } +JNIEXPORT jstring JNICALL Java_io_realm_internal_OsResults_toJSON(JNIEnv* env, jclass, jlong native_ptr, jint maxDepth) +{ + TR_ENTER_PTR(native_ptr) + try { + auto wrapper = reinterpret_cast(native_ptr); + + auto table_view = wrapper->collection().get_tableview(); + std::stringstream ss; + table_view.to_json(ss, maxDepth); + return to_jstring(env, ss.str().c_str()); + } + CATCH_STD() + return nullptr; +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeIndexOf(JNIEnv* env, jclass, jlong native_ptr, jlong row_native_ptr) { diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index e4b1314d21..7c3ff82355 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit e4b1314d21b521fd604af7f1aacf3ca94272c19a +Subproject commit 7c3ff8235579550a3e3c6060c47140b2005174f5 diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index eac557b1d7..802ab27c60 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -20,16 +20,14 @@ import android.os.Looper; import java.util.Date; -import java.util.Iterator; import java.util.Locale; import javax.annotation.Nullable; import io.reactivex.Flowable; import io.reactivex.Observable; +import io.realm.annotations.Beta; import io.realm.internal.CheckedRow; -import io.realm.internal.ColumnInfo; -import io.realm.internal.OsList; import io.realm.internal.OsResults; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; @@ -40,8 +38,6 @@ import io.realm.log.RealmLog; import io.realm.rx.CollectionChange; -import static io.realm.RealmFieldType.LIST; - /** * This class holds all the matches of a {@link RealmQuery} for a given Realm. The objects are not copied from * the Realm to the RealmResults list, but are just referenced from the RealmResult instead. This saves memory and @@ -749,6 +745,22 @@ public Observable>> asChangesetObservable() { } } + /** + * Returns a JSON representation of the matches of a {@link RealmQuery}. Cycles will be returned as row indices. + * + * This is a helper method used to inspect data, or for debugging purpose, this method could pull a large string which + * could cause an OutOfMemory error. + * + * @return string representation of a JSON array containing entries of the resulting {@link RealmQuery}. + */ + @Beta // until https://github.com/realm/realm-core/issues/3305 is fixed + public String asJSON() { + // maxDepth = -1: + // Follow links to infinite depth, but only follow each link exactly once. + // Cycle links are printed as a simple sequence of integers of row indexes in the link column. + return osResults.toJSON(-1); + } + private void checkNonEmptyFieldName(String fieldName) { if (Util.isEmptyString(fieldName)) { throw new IllegalArgumentException("Non-empty 'fieldname' required."); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 47925d1cba..9994ed1884 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -355,6 +355,10 @@ public TableQuery where() { return new TableQuery(this.context, this.table, nativeQueryPtr); } + public String toJSON(int maxDepth) { + return toJSON(nativePtr, maxDepth); + } + public Number aggregateNumber(Aggregate aggregateMethod, long columnIndex) { return (Number) nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); } @@ -706,6 +710,8 @@ public void load() { private static native long nativeWhere(long nativePtr); + private static native String toJSON(long nativePtr, int maxDepth); + private static native long nativeIndexOf(long nativePtr, long rowNativePtr); private static native boolean nativeIsValid(long nativePtr); From 9f6d95e5ab99ca3391f2050b8ddfe86e6c8f4ae4 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 20 Jun 2019 18:03:35 +0100 Subject: [PATCH 1384/2110] Release v5.12.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 647e7292a9..1a65f3bdfb 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.12.0-SNAPSHOT \ No newline at end of file +5.12.0 \ No newline at end of file From 85527b6060fb3f3678b79734b9081196a0c7d43b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 20 Jun 2019 18:03:35 +0100 Subject: [PATCH 1385/2110] Prepare next release v5.12.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 1a65f3bdfb..cd02dea0bf 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.12.0 \ No newline at end of file +5.12.1-SNAPSHOT \ No newline at end of file From 2145449fa49501ae3a762a8faaf1bdb82770c7e0 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 21 Jun 2019 00:45:04 +0100 Subject: [PATCH 1386/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index cd02dea0bf..9e323a785b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.12.1-SNAPSHOT \ No newline at end of file +5.13.0-SNAPSHOT From 81568ef06d9d595fb5249083ca01ae2c718f5ae9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 3 Jul 2019 16:22:53 +0200 Subject: [PATCH 1387/2110] Better logging for sessions (#6552) --- CHANGELOG.md | 17 +++++++++++++++++ .../cpp/io_realm_internal_OsRealmConfig.cpp | 2 +- .../objectServer/java/io/realm/SyncManager.java | 4 +++- .../internal/network/AuthenticateResponse.java | 1 - 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa0065b4f5..6f1a46d781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 5.13.0(YYYY-MM-DD) + +### Enhancements +* [ObjectServer] Improved session lifecycle debug output. (Issue [#6552](https://github.com/realm/realm-java/pull/6552)). + +### Fixed +* None. + +### Compatibility +* Realm Object Server: 3.21.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None. + + ## 5.12.0(2019-06-20) ### Enhancements diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 6e83b62c1d..8e0f4f6511 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -334,7 +334,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe // the session which should be bound. auto bind_handler = [](const std::string& path, const SyncConfig& syncConfig, std::shared_ptr session) { - realm::jni_util::Log::d("Callback to Java requesting token for path"); + realm::jni_util::Log::d("Callback to Java requesting token for path: %1", path.c_str()); JNIEnv* env = realm::jni_util::JniUtils::get_env(true); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 5d948929d3..8031816a87 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -266,6 +266,7 @@ public static synchronized SyncSession getOrCreateSession(SyncConfiguration sync SyncSession session = sessions.get(syncConfiguration.getPath()); if (session == null) { + RealmLog.debug("Creating session for: %s", syncConfiguration.getPath()); session = new SyncSession(syncConfiguration); sessions.put(syncConfiguration.getPath(), session); if (sessions.size() == 1) { @@ -437,12 +438,13 @@ private static synchronized void removeSession(SyncConfiguration syncConfigurati if (syncConfiguration == null) { throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); } + RealmLog.debug("Removing session for: %s", syncConfiguration.getPath()); SyncSession syncSession = sessions.remove(syncConfiguration.getPath()); if (syncSession != null) { syncSession.close(); } if (sessions.isEmpty()) { - RealmLog.debug("last session dropped, remove network listener"); + RealmLog.debug("Last session dropped. Remove network listener."); NetworkStateReceiver.removeListener(networkListener); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index 5c0374649b..fc968d6b12 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -106,7 +106,6 @@ public static AuthenticateResponse createValidResponseWithUser(String identifier * @param error the network or I/O error. */ private AuthenticateResponse(ObjectServerError error) { - RealmLog.debug("AuthenticateResponse - Error: " + error); setError(error); this.accessToken = null; this.refreshToken = null; From 4371f7cf3c1b5fd114757618be5f630851de355f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 23 Jul 2019 22:18:09 +0200 Subject: [PATCH 1388/2110] Add support for AsyncOpen (#6548) * Add support for AsyncOpen --- CHANGELOG.md | 5 +- .../realm-library/src/main/cpp/CMakeLists.txt | 3 +- ...m_internal_objectstore_OsAsyncOpenTask.cpp | 83 +++++++++++++++++++ realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/RealmCache.java | 32 +++++-- .../io/realm/internal/ObjectServerFacade.java | 1 + .../java/io/realm/internal/OsRealmConfig.java | 2 +- .../main/java/io/realm/sync/Subscription.java | 2 + .../java/io/realm/SyncManager.java | 3 - .../internal/SyncObjectServerFacade.java | 70 +++++++++++----- .../internal/objectstore/OsAsyncOpenTask.java | 70 ++++++++++++++++ .../java/io/realm/SyncSessionTests.java | 27 +++--- .../io/realm/SyncedRealmIntegrationTests.java | 6 +- 13 files changed, 251 insertions(+), 55 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f1a46d781..c9f1938811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 5.13.0(YYYY-MM-DD) ### Enhancements +* [ObjectServer] Added support for faster initial synchronization for fully synchronized Realms. (Issue [#6469](https://github.com/realm/realm-java/issues/6469)) * [ObjectServer] Improved session lifecycle debug output. (Issue [#6552](https://github.com/realm/realm-java/pull/6552)). ### Fixed @@ -12,7 +13,9 @@ * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal -* None. +* Updated to Realm Core 5.22.0. +* Updated to Realm Sync 4.6.1. +* Updated to Object Store commit f0d75261fc8d332c20dc82f643dd795c0f4c7aec ## 5.12.0(2019-06-20) diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 035b359150..95e1fbfded 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -90,11 +90,11 @@ if (build_SYNC) list(APPEND classes_LIST io.realm.ClientResetRequiredError io.realm.RealmFileUserStore io.realm.SyncManager io.realm.SyncSession io.realm.SyncUser + io.realm.internal.objectstore.OsAsyncOpenTask ) endif() create_javah(TARGET jni_headers CLASSES ${classes_LIST} - CLASSPATH ${classes_PATH} OUTPUT_DIR ${jni_headers_PATH} DEPENDS ${classes_PATH} @@ -188,6 +188,7 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFileUserStore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAsyncOpenTask.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_sync_OsSubscription.cpp ) endif() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp new file mode 100644 index 0000000000..f48435bf64 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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 "io_realm_internal_objectstore_OsAsyncOpenTask.h" + +#include "util.hpp" +#include "thread_safe_reference.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/java_class.hpp" +#include "jni_util/jni_utils.hpp" +#include "object-store/src/sync/async_open_task.hpp" +#include "object-store/src/sync/sync_config.hpp" + +#include +#include +#include + +using namespace realm; +using namespace realm::jni_util; +using namespace realm::_impl; + +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAsyncOpenTask_start(JNIEnv* env, jobject obj, jlong config_ptr) +{ + TR_ENTER() + try { + + static JavaClass java_async_open_task_class(env, "io/realm/internal/objectstore/OsAsyncOpenTask"); + static JavaMethod java_notify_realm_ready(env, java_async_open_task_class, "notifyRealmReady", "()V", false); + static JavaMethod java_notify_error(env, java_async_open_task_class, "notifyError", "(Ljava/lang/String;)V", false); + + auto global_obj = env->NewGlobalRef(obj); + auto& config = *reinterpret_cast(config_ptr); + + std::shared_ptr task = Realm::get_synchronized_realm(config); + + auto deleter = [](jobject obj) { + jni_util::JniUtils::get_env(true)->DeleteGlobalRef(obj); + }; + std::shared_ptr<_jobject> task_obj(env->NewGlobalRef(global_obj), deleter); + task->start([task=std::move(task_obj)](realm::ThreadSafeReference realm_ref, std::exception_ptr error) { + JNIEnv* local_env = jni_util::JniUtils::get_env(true); + if (error) { + try { + std::rethrow_exception(error); + } + catch (const std::exception& e) { + jstring j_error_msg = to_jstring(local_env, e.what()); + local_env->CallObjectMethod(task.get(), java_notify_error, j_error_msg); + local_env->DeleteLocalRef(j_error_msg); + } + } + else { + auto realm = Realm::get_shared_realm(std::move(realm_ref)); + realm->close(); + local_env->CallVoidMethod(task.get(), java_notify_realm_ready); + } + const_cast&>(task).reset(); + }); + return reinterpret_cast(&(*task)); + } + CATCH_STD() + return 0; +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsAsyncOpenTask_cancel(JNIEnv*, jobject, jlong task_ptr) +{ + TR_ENTER() + AsyncOpenTask* task = reinterpret_cast(task_ptr); + task->cancel(); +} \ No newline at end of file diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 7c3ff82355..f0d75261fc 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 7c3ff8235579550a3e3c6060c47140b2005174f5 +Subproject commit f0d75261fc8d332c20dc82f643dd795c0f4c7aec diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 4434c7d9d1..4a2ac0a7c1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -38,6 +38,7 @@ import io.realm.internal.Capabilities; import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsObjectStore; +import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSharedRealm; import io.realm.internal.RealmNotifier; import io.realm.internal.Table; @@ -300,16 +301,29 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo // before proceeding. We need to open the Realm instance first to start any potential underlying // SyncSession so this will work. if (realmFileIsBeingCreated) { - sharedRealm = OsSharedRealm.getInstance(configuration); - try { + + // Manually create the Java session wrapper session as this might otherwise + // not be created + OsRealmConfig osConfig = new OsRealmConfig.Builder(configuration).build(); + ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(osConfig); + + if (ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(configuration)) { + // Partial Realms are not supported by async open yet, so continue to + // use the old way of opening those Realms. + sharedRealm = OsSharedRealm.getInstance(configuration); + try { + ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialRemoteChanges(configuration); + } catch (Throwable t) { + // If an error happened while downloading initial data, we need to reset the file so we can + // download it again on the next attempt. + sharedRealm.close(); + sharedRealm = null; + deleteRealmFileOnDisk(configuration); + throw t; + } + } else { + // Fully synchronized Realms are supported by AsyncOpen ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialRemoteChanges(configuration); - } catch (Throwable t) { - // If an error happened while downloading initial data, we need to reset the file so we can - // download it again on the next attempt. - sharedRealm.close(); - sharedRealm = null; - deleteRealmFileOnDisk(configuration); - throw t; } } } else { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 51fc48ad73..4d1914053b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -102,6 +102,7 @@ public String getSyncServerCertificateFilePath(RealmConfiguration config) { * * @throws {@code DownloadingRealmInterruptedException} if the thread was interrupted while blocked waiting for * this to complete. + * @throws {@code ObjectServerException } In any other kind of error is reported. */ @SuppressWarnings("JavaDoc") public void downloadInitialRemoteChanges(RealmConfiguration config) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 2ef03d1046..2ad76beb4a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -145,7 +145,7 @@ public Builder autoUpdateNotification(boolean autoUpdateNotification) { // Package private because of the OsRealmConfig needs to carry the NativeContext. This should only be called // by the OsSharedRealm. - OsRealmConfig build() { + public OsRealmConfig build() { return new OsRealmConfig(configuration, fifoFallbackDir, autoUpdateNotification, schemaInfo, migrationCallback, initializationCallback); } diff --git a/realm/realm-library/src/main/java/io/realm/sync/Subscription.java b/realm/realm-library/src/main/java/io/realm/sync/Subscription.java index aab282847d..ff2f5e9fa3 100644 --- a/realm/realm-library/src/main/java/io/realm/sync/Subscription.java +++ b/realm/realm-library/src/main/java/io/realm/sync/Subscription.java @@ -146,6 +146,7 @@ public Subscription(String name, RealmQuery query) { /** * Field indicating when this subscription was created. */ + @Required @RealmField("created_at") private Date createdAt; @@ -160,6 +161,7 @@ public Subscription(String name, RealmQuery query) { *

            * This field plus {@link #timeToLive} defines {@link #expiresAt}. */ + @Required @RealmField("updated_at") private Date updatedAt; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 8031816a87..eb2c6622aa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -16,8 +16,6 @@ package io.realm; -import android.os.SystemClock; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -46,7 +44,6 @@ import javax.net.ssl.X509TrustManager; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.exceptions.RealmError; import io.realm.internal.Keep; import io.realm.internal.Util; import io.realm.internal.network.AuthenticationServer; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 8663635ab7..a5949843ab 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -36,7 +36,9 @@ import io.realm.SyncUser; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; +import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.network.NetworkStateReceiver; +import io.realm.internal.objectstore.OsAsyncOpenTask; import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.sync.Subscription; @@ -182,30 +184,52 @@ public void downloadInitialRemoteChanges(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; if (syncConfig.shouldWaitForInitialRemoteData()) { - SyncSession session = SyncManager.getSession(syncConfig); - try { - long timeoutMillis = syncConfig.getInitialRemoteDataTimeout(TimeUnit.MILLISECONDS); - if (!syncConfig.isFullySynchronizedRealm()) { - // For Query-based Realms we want to upload all our local changes - // first since those might include subscriptions the server needs to process. - // This means that once `downloadAllServerChanges` completes, all initial - // subscriptions will also have been downloaded. - // - // Note that we are reusing the same timeout for uploading and downloading. - // This means that in the worst case you end up with 2x the timeout for - // Query-based Realms. This is probably an acceptable trade-of as trying - // to expose this would not only complicate the API surface quite a lot, - // but in most (almost all?) cases the amount of data to upload will be trivial. - if (!session.uploadAllLocalChanges(timeoutMillis, TimeUnit.MILLISECONDS)) { - throw new DownloadingRealmInterruptedException(syncConfig, "Failed to first upload local changes in " + timeoutMillis + " milliseconds"); - }; - } - if (!session.downloadAllServerChanges(timeoutMillis, TimeUnit.MILLISECONDS)) { - throw new DownloadingRealmInterruptedException(syncConfig, "Failed to download remote changes in " + timeoutMillis + " milliseconds"); - } - } catch (InterruptedException e) { - throw new DownloadingRealmInterruptedException(syncConfig, e); + if (new AndroidCapabilities().isMainThread()) { + throw new IllegalStateException("waitForInitialRemoteData() cannot be used synchronously on the main thread. Use Realm.getInstanceAsync() instead."); + } + if (syncConfig.isFullySynchronizedRealm()) { + downloadInitialFullRealm(syncConfig); + } else { + downloadInitialQueryBasedRealm(syncConfig); + } + } + } + } + + private void downloadInitialFullRealm(SyncConfiguration syncConfig) { + OsAsyncOpenTask task = new OsAsyncOpenTask(new OsRealmConfig.Builder(syncConfig).build()); + try { + task.start(syncConfig.getInitialRemoteDataTimeout(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new DownloadingRealmInterruptedException(syncConfig, e); + } + } + + private void downloadInitialQueryBasedRealm(SyncConfiguration syncConfig) { + if (syncConfig.shouldWaitForInitialRemoteData()) { + SyncSession session = SyncManager.getSession(syncConfig); + try { + long timeoutMillis = syncConfig.getInitialRemoteDataTimeout(TimeUnit.MILLISECONDS); + if (!syncConfig.isFullySynchronizedRealm()) { + // For Query-based Realms we want to upload all our local changes + // first since those might include subscriptions the server needs to process. + // This means that once `downloadAllServerChanges` completes, all initial + // subscriptions will also have been downloaded. + // + // Note that we are reusing the same timeout for uploading and downloading. + // This means that in the worst case you end up with 2x the timeout for + // Query-based Realms. This is probably an acceptable trade-of as trying + // to expose this would not only complicate the API surface quite a lot, + // but in most (almost all?) cases the amount of data to upload will be trivial. + if (!session.uploadAllLocalChanges(timeoutMillis, TimeUnit.MILLISECONDS)) { + throw new DownloadingRealmInterruptedException(syncConfig, "Failed to first upload local changes in " + timeoutMillis + " milliseconds"); + }; + } + if (!session.downloadAllServerChanges(timeoutMillis, TimeUnit.MILLISECONDS)) { + throw new DownloadingRealmInterruptedException(syncConfig, "Failed to download remote changes in " + timeoutMillis + " milliseconds"); } + } catch (InterruptedException e) { + throw new DownloadingRealmInterruptedException(syncConfig, e); } } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java new file mode 100644 index 0000000000..115022d3a0 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java @@ -0,0 +1,70 @@ +package io.realm.internal.objectstore; + + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.internal.Keep; +import io.realm.internal.KeepMember; +import io.realm.internal.OsRealmConfig; + +/** + * Wrapper for the ASyncOpenTask in ObjectStore, which also support timeouts. + * + * This ObjectStore class controls its own lifecycle, i.e. discards itself once complete, so the + * Java object does not need to implement {@link io.realm.internal.NativeObject}. + */ +@KeepMember +public class OsAsyncOpenTask { + + private final OsRealmConfig config; + private long nativePtr; + private final CountDownLatch taskComplete = new CountDownLatch(1); + private final AtomicReference error = new AtomicReference<>(null); + + public OsAsyncOpenTask(OsRealmConfig config) { + this.config = config; + } + + public void start(long timeOut, TimeUnit unit) throws InterruptedException { + this.nativePtr = start(config.getNativePtr()); + + try { + taskComplete.await(timeOut, unit); + } catch (InterruptedException e) { + cancel(nativePtr); + throw e; + } + + String errorMessage = error.get(); + if (errorMessage != null) { + throw new ObjectServerError(ErrorCode.UNKNOWN, errorMessage); + } + } + + /** + * Called from JNI when the underlying async task has successfully downloaded the Realm. + */ + @KeepMember + @SuppressWarnings("unused") + private void notifyRealmReady() { + error.set(null); + taskComplete.countDown(); + } + + /** + * Called from JNI when the underlying async task encounters an error. + */ + @KeepMember + @SuppressWarnings("unused") + private void notifyError(String errorMessage) { + error.set(errorMessage); + taskComplete.countDown(); + } + + private native long start(long configPtr); + private native void cancel(long nativePtr); +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index f137814068..a4bbc0edfb 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -408,19 +408,22 @@ public void run() { .build(); final Realm adminRealm = Realm.getInstance(adminConfig); RealmResults all = adminRealm.where(StringOnly.class).findAll(); - strongRefs.add(all); - OrderedRealmCollectionChangeListener> realmChangeListener = (results, changeSet) -> { - RealmLog.info("Size: " + results.size() + ", state: " + changeSet.getState().toString()); - if (results.size() == 5) { - for (int i = 0; i < 5; i++) { - assertEquals(1_000_000, results.get(i).getChars().length()); + + if (all.size() == 5) { + adminRealm.close(); + testCompleted.countDown(); + handlerThread.quit(); + } else { + strongRefs.add(all); + OrderedRealmCollectionChangeListener> realmChangeListener = (results, changeSet) -> { + if (results.size() == 5) { + adminRealm.close(); + testCompleted.countDown(); + handlerThread.quit(); } - adminRealm.close(); - testCompleted.countDown(); - handlerThread.quit(); - } - }; - all.addChangeListener(realmChangeListener); + }; + all.addChangeListener(realmChangeListener); + } } }); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index b411ed3100..f57fdca082 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -115,9 +115,7 @@ public void waitForInitialRemoteData_mainThreadThrows() { try { realm = Realm.getInstance(config); fail(); - } catch (IllegalStateException expected) { - assertThat(expected.getMessage(), CoreMatchers.containsString( - "downloadAllServerChanges() cannot be called from the main thread.")); + } catch (IllegalStateException ignore) { } finally { if (realm != null) { realm.close(); @@ -306,7 +304,7 @@ public void waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { // schema. realm = Realm.getInstance(configNew); fail(); - } catch (RealmMigrationNeededException ignored) { + } catch (IllegalStateException ignored) { } finally { if (realm != null) { realm.close(); From f2b7594765352cfb0e53efdc3bdc4371fe8b617f Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 23 Jul 2019 21:44:26 +0100 Subject: [PATCH 1389/2110] update release date in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f1938811..b6be996ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.13.0(YYYY-MM-DD) +## 5.13.0(2019-07-23) ### Enhancements * [ObjectServer] Added support for faster initial synchronization for fully synchronized Realms. (Issue [#6469](https://github.com/realm/realm-java/issues/6469)) From 180e09d75d3b813a0c6d27517b53ac9dfd203f2e Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 23 Jul 2019 21:45:07 +0100 Subject: [PATCH 1390/2110] Release v5.13.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 9e323a785b..22f097e28e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.13.0-SNAPSHOT +5.13.0 \ No newline at end of file From 39202ee6bde28afeaa2db8e11a964d1ac361821f Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 23 Jul 2019 21:45:07 +0100 Subject: [PATCH 1391/2110] Prepare next release v5.13.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 22f097e28e..fc61833038 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.13.0 \ No newline at end of file +5.13.1-SNAPSHOT \ No newline at end of file From 3fa349a0902f6cd1e03c678a537f767a474376f8 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 25 Jul 2019 15:24:36 +0100 Subject: [PATCH 1392/2110] Update version.txt --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index fc61833038..7041f18668 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.13.1-SNAPSHOT \ No newline at end of file +5.14.0-SNAPSHOT From ab067ea2029dfee96e7e2c7d09e68f4083ef4d87 Mon Sep 17 00:00:00 2001 From: Tobias Preuss Date: Thu, 1 Aug 2019 13:09:14 +0200 Subject: [PATCH 1393/2110] Fix minor typo. (#6572) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6be996ce7..6b5538f8cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ ### Internal * Updated to Realm Core 5.22.0. -* Updated to Relm Sync 4.6.1. +* Updated to Realm Sync 4.6.1. * Updated to Object Store commit 7c3ff8235579550a3e3c6060c47140b2005174f5 ## 5.11.0(2019-05-01) From 8af502c85e5828f5a867511ca9c115bae90e845e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 2 Aug 2019 13:19:19 +0200 Subject: [PATCH 1394/2110] Add instructions for building Realm from a local source (#6573) --- README.md | 19 +++++++++++++++++++ realm/realm-library/build.gradle | 14 ++++++-------- .../src/main/cpp/CMake/RealmCore.cmake | 3 +++ .../cpp/io_realm_internal_OsRealmConfig.cpp | 3 +-- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 22aac0936d..87398a1aa6 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,25 @@ That command will generate: The full build may take an hour or more, to complete. +### Building from source + +It is possible to build Realm Java against a local checked out version of Realm Core. This is done by providing the following parameter when building: `-PcoreSourcePath=`. + +E.g in the case where the `realm-java` and `realm-core` repos are checked out next to each other you can build from source using: + +``` +git clone https://github.com/realm/realm-java.git +git clone https://github.com/realm/realm-core.git +cd realm-java/realm +./gradlew assembleBase -PcoreSourcePath=../../realm-core +``` + +Note: If the `realm-core` project has already been compiled for non-Android builds and CMake files have been generated, this might conflict with `realm-java` trying to build it. Cleanup the `realm-core` project by calling `git clean -xfd` inside it (beware that all unsaved changes will be lost). + +Note: Building from source with Realm Sync is not enabled yet. Only building the `Base` variant is supported. + +Note: If you want to build from source inside Android Studio, you need to update the Gradle parameters by going into the Realm projects settings `Settings > Build, Execution, Deployment > Compiler > Command-line options` and add `-PcoreSourcePath=` to it. + ### Other Commands * `./gradlew tasks` will show all the available tasks diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 25a384aab7..5ba2270ef9 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -19,21 +19,19 @@ properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) ext.coreVersion = properties.getProperty('REALM_SYNC_VERSION') // empty or comment out this to disable hash checking ext.coreSha256Hash = properties.getProperty('REALM_SYNC_SHA256') -ext.forceDownloadCore = - project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false +ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false + // Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from // core source code. -ext.coreSourcePath = project.hasProperty('coreSourcePath') ? project.getProperty('coreSourcePath') : null -// The location of core archive. +ext.coreSourcePath = project.hasProperty('coreSourcePath') ? file(project.getProperty('coreSourcePath')) : null +// The location of pre-compiled Realm Core/Sync archive. ext.coreArchiveDir = System.getenv("REALM_CORE_DOWNLOAD_DIR") if (!ext.coreArchiveDir) { ext.coreArchiveDir = ".." } ext.coreArchiveFile = rootProject.file("${ext.coreArchiveDir}/realm-sync-android-${project.coreVersion}.tar.gz") ext.coreDistributionDir = file("${projectDir}/distribution/realm-core/") -ext.coreDir = file(project.coreSourcePath ? - "${project.coreSourcePath}/android-lib" : - "${project.coreDistributionDir.getAbsolutePath()}/core-${project.coreVersion}") +ext.coreDir = file("${project.coreDistributionDir.getAbsolutePath()}/core-${project.coreVersion}") ext.ccachePath = project.findProperty('ccachePath') ?: System.getenv('NDK_CCACHE') ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE') // Set to true to enable linking with debug core. @@ -61,7 +59,7 @@ android { "-DENABLE_DEBUG_CORE=$project.enableDebugCore" if (project.ccachePath) arguments "-DNDK_CCACHE=$project.ccachePath" if (project.lcachePath) arguments "-DNDK_LCACHE=$project.lcachePath" - if (project.coreSourcePath) arguments "-DCORE_SOURCE_PATH=$project.coreSourcePath" + if (project.coreSourcePath) arguments "-DCORE_SOURCE_PATH=${project.coreSourcePath.getAbsolutePath()}" if (project.hasProperty('buildTargetABIs') && !project.getProperty('buildTargetABIs').trim().isEmpty()) { abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*')) } else { diff --git a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake index bedbba6b6b..ee22a08fc9 100644 --- a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake +++ b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake @@ -25,6 +25,8 @@ function(build_existing_realm_core core_source_path) add_compile_options(-DNDEBUG) endif() + # We mirror relevant flags from this script + # https://github.com/realm/realm-core/blob/master/tools/cross_compile.sh#L68 ExternalProject_Add(realm-core SOURCE_DIR ${core_source_path} PREFIX ${core_source_path}/build-android-${ANDROID_ABI}-${CMAKE_BUILD_TYPE} @@ -118,6 +120,7 @@ endfunction() # FIXME: Build from sync source is not supported yet. function(use_realm_core enable_sync sync_dist_path core_source_path) if (core_source_path) + message("Building Realm Core from local source in ${core_source_path}.") build_existing_realm_core(${core_source_path}) else() use_sync_release(${enable_sync} ${sync_dist_path}) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 8e0f4f6511..8e45a0430e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -21,11 +21,10 @@ #include #include #include - +#include #endif #include -#include #include "java_accessor.hpp" #include "util.hpp" From b08989a02d6123f91d13fd866398a2867b303ec9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 5 Aug 2019 15:32:20 +0200 Subject: [PATCH 1395/2110] Correctly copy JAR files in the Realm transformer (#6575) --- CHANGELOG.md | 17 +++++++++++++ realm-transformer/build.gradle | 6 ++--- .../io/realm/transformer/RealmTransformer.kt | 5 +--- .../realm/transformer/build/BuildTemplate.kt | 25 ++++++++++++++----- .../io/realm/transformer/build/FullBuild.kt | 13 +++++++--- .../transformer/build/IncrementalBuild.kt | 5 ++-- 6 files changed, 50 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6be996ce7..22b5e3970f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 5.13.1(YYYY-MM-DD) + +### Enhancements +* None. + +### Fixed +* The Realm bytecode transformer now works correctly with Android Gradle Plugin 3.6.0-alpha01 and beyond. (Issue [#6531](https://github.com/realm/realm-java/issues/6531)). + +### Compatibility +* Realm Object Server: 3.21.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* Updated JavaAssist in the Realm Transformer to 3.25.0-GA. + + ## 5.13.0(2019-07-23) ### Enhancements diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 92d70f7526..d5cac03d0e 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -47,9 +47,7 @@ configurations { sourceSets { main { compileClasspath += configurations.provided - java { - srcDirs += ['build/generated-src/main/java', 'src/main/kotlin'] - } + java.srcDirs += ['build/generated-src/main/java', 'src/main/kotlin'] } } @@ -58,7 +56,7 @@ dependencies { compile "io.realm:realm-annotations:${version}" compileOnly "com.android.tools.build:gradle:${properties.get("GRADLE_BUILD_TOOLS")}" compileOnly 'com.android.tools.build:gradle:3.1.1' - compile 'org.javassist:javassist:3.21.0-GA' + compile 'org.javassist:javassist:3.25.0-GA' compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" testCompile 'junit:junit:4.12' diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 74efc31a08..2e4a38aa2b 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -150,10 +150,7 @@ class RealmTransformer(val project: Project) : Transform() { } } - val packages: Set = outputModelClasses.map { - it.packageName - }.toSet() - + val packages: Set = outputModelClasses.map { it.packageName }.toSet() val targetSdk: String? = project.getTargetSdk() val minSdk: String? = project.getMinSdk() diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt index 0a73198128..463b3e06ea 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -125,21 +125,34 @@ abstract class BuildTemplate(val project: Project, val outputProvider: Transform it.file.walkTopDown().forEach { if (it.isFile) { if (!it.absolutePath.endsWith(SdkConstants.DOT_CLASS)) { - logger.debug(" Copying resource $it") - val dest = File(getOutputFile(outputProvider), it.absolutePath.substring(dirPath.length)) + logger.debug(" Copying resource file: $it") + val dest = File(getOutputFile(outputProvider, Format.DIRECTORY), it.absolutePath.substring(dirPath.length)) + dest.parentFile.mkdirs() + Files.copy(it, dest) + } + } + } + } + + it.jarInputs.forEach { + logger.debug("Found JAR file: ${it.file.absolutePath}") + val dirPath: String = it.file.absolutePath + it.file.walkTopDown().forEach { + if (it.isFile) { + if (it.absolutePath.endsWith(SdkConstants.DOT_JAR)) { + logger.debug(" Copying jar file: $it") + val dest = File(getOutputFile(outputProvider, Format.JAR), it.absolutePath.substring(dirPath.length)) dest.parentFile.mkdirs() Files.copy(it, dest) } } } } - // no need to implement the code for `it.jarInputs.each` since PROJECT SCOPE does not use jar input. } } - protected fun getOutputFile(outputProvider: TransformOutputProvider): File { - return outputProvider.getContentLocation( - "realm", transform.inputTypes, transform.scopes, Format.DIRECTORY) + protected fun getOutputFile(outputProvider: TransformOutputProvider, format: Format): File { + return outputProvider.getContentLocation("realm", transform.inputTypes, transform.scopes, format) } /** diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt index 3bbcd1bfef..0536bbb4ff 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt @@ -17,6 +17,7 @@ package io.realm.transformer.build import com.android.SdkConstants +import com.android.build.api.transform.Format import com.android.build.api.transform.TransformInput import com.android.build.api.transform.TransformOutputProvider import io.realm.transformer.BytecodeModifier @@ -130,10 +131,14 @@ class FullBuild(project: Project, outputProvider: TransformOutputProvider, trans // Use accessors instead of direct field access outputClassNames.forEach { - logger.debug("Modify accessors in class: $it") - val ctClass: CtClass = classPool.getCtClass(it) - BytecodeModifier.useRealmAccessors(classPool, ctClass, allManagedFields) - ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) + logger.debug("Modifying accessors in class: $it") + try { + val ctClass: CtClass = classPool.getCtClass(it) + BytecodeModifier.useRealmAccessors(classPool, ctClass, allManagedFields) + ctClass.writeFile(getOutputFile(outputProvider, Format.DIRECTORY).canonicalPath) + } catch (e: Exception) { + throw RuntimeException("Failed to transform $it.", e) + } } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt index 8a9c9b9783..4bdb65193e 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt @@ -17,6 +17,7 @@ package io.realm.transformer.build import com.android.SdkConstants +import com.android.build.api.transform.Format import com.android.build.api.transform.Status import com.android.build.api.transform.TransformInput import com.android.build.api.transform.TransformOutputProvider @@ -51,12 +52,10 @@ class IncrementalBuild(project: Project, outputProvider: TransformOutputProvider logger.debug("Modify accessors in class: $it") val ctClass: CtClass = classPool.getCtClass(it) BytecodeModifier.useRealmAccessors(classPool, ctClass, null) - ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) + ctClass.writeFile(getOutputFile(outputProvider, Format.DIRECTORY).canonicalPath) } - } - /** * Categorize the transform input into its two main categorizes: `directoryFiles` which are * source files in the current project and `jarFiles` which are source files found in jars. From beff07199509ea7d3d5d3ebdb30be90bd45120a0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 5 Aug 2019 17:13:20 +0200 Subject: [PATCH 1396/2110] Add support for configuring sync with the system proxy (#6576) --- CHANGELOG.md | 13 +++++ dependencies.list | 4 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 25 ++++++++++ realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/internal/OsRealmConfig.java | 48 +++++++++++++++++++ 5 files changed, 89 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b5538f8cf..33a340d567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 5.13.1(YYYY-MM-DD) + +### Enhancements +* None. + +### Fixed +* [ObjectServer] The C++ networking layer now correctly uses any system defined proxy the same way the Java networking layer does. (Issue [#6574](https://github.com/realm/realm-java/pull/6574)). + +### Internal +* Updated to Realm Core 5.23.1. +* Updated to Realm Sync 4.7.1. +* Updated to Object Store commit: bcc6a7524e52071bfcd35cf740f506e0cc6a595e + ## 5.13.0(2019-07-23) ### Enhancements diff --git a/dependencies.list b/dependencies.list index 78968bcff3..6c5ad0f65f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=4.6.1 -REALM_SYNC_SHA256=eb4fbf83717156fabae6357199b22ef25546f2da24ded5668290351613faf9d0 +REALM_SYNC_VERSION=4.7.1 +REALM_SYNC_SHA256=28c37d53e63d80db6be1f3e566adac7eea291f496be2b5274cfb94658b435d3d # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 8e45a0430e..f49b8a57ba 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -451,4 +451,29 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigS CATCH_STD() } +static_assert(SyncConfig::ProxyConfig::Type::HTTP == static_cast(io_realm_internal_OsRealmConfig_PROXYCONFIG_TYPE_VALUE_HTTP), + ""); + +JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigProxySettings( + JNIEnv* env, jclass, jlong native_ptr, jbyte proxy_type, + jstring j_proxy_address, jint proxy_port) +{ + TR_ENTER_PTR(native_ptr); + + auto& config = *reinterpret_cast(native_ptr); + // To ensure the sync_config has been created and this function won't be called multiple time on the same config. + REALM_ASSERT(config.sync_config); + REALM_ASSERT(!config.sync_config->proxy_config); + + try { + SyncConfig::ProxyConfig proxy_config; + proxy_config.type = static_cast(proxy_type); + proxy_config.address = JStringAccessor(env, j_proxy_address); + proxy_config.port = proxy_port; + + config.sync_config->proxy_config.emplace(std::move(proxy_config)); + } + CATCH_STD() +} + #endif diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index f0d75261fc..bcc6a7524e 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit f0d75261fc8d332c20dc82f643dd795c0f4c7aec +Subproject commit bcc6a7524e52071bfcd35cf740f506e0cc6a595e diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 2ad76beb4a..95643c1ce4 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -17,9 +17,11 @@ package io.realm.internal; import java.io.File; +import java.net.ProxySelector; import java.net.URI; import java.net.URISyntaxException; import java.util.Map; +import java.util.List; import javax.annotation.Nullable; @@ -165,6 +167,7 @@ public Builder fifoFallbackDir(File dir) { private static final byte SYNCSESSION_STOP_POLICY_VALUE_IMMEDIATELY = 0; private static final byte SYNCSESSION_STOP_POLICY_VALUE_LIVE_INDEFINETELY = 1; private static final byte SYNCSESSION_STOP_POLICY_VALUE_AFTER_CHANGES_UPLOADED = 2; + private static final byte PROXYCONFIG_TYPE_VALUE_HTTP = 0; private final static long nativeFinalizerPtr = nativeGetFinalizerPtr(); @@ -284,6 +287,49 @@ private OsRealmConfig(final RealmConfiguration config, RealmLog.error(e, "Cannot create a URI from the Realm URL address"); } nativeSetSyncConfigSslSettings(nativePtr, syncClientValidateSsl, syncSslTrustCertificatePath); + + // TODO: maybe expose the option for a custom Proxy or ProxySelector in the config? + ProxySelector proxySelector = ProxySelector.getDefault(); + if (resolvedRealmURI != null && proxySelector != null) { + URI websocketUrl = null; + try { + // replace scheme in URI so that a proxy selector won't be confused by 'realm://' + websocketUrl = new URI(resolvedSyncRealmUrl.replaceFirst("realm", "http")); + } catch (URISyntaxException e) { + // we shouldn't ever get here if parsing the resolved url above worked + RealmLog.error(e, "Cannot create a URI from the Realm URL address"); + } + List proxies = proxySelector.select(websocketUrl); + if (proxies != null && !proxies.isEmpty()) { + java.net.Proxy proxy = proxies.get(0); + if (proxy.type() != java.net.Proxy.Type.DIRECT) { + byte proxyType = -1; + switch (proxy.type()) { + case HTTP: + proxyType = PROXYCONFIG_TYPE_VALUE_HTTP; + break; + default: + // this should never happen + } + + if (proxy.type() == java.net.Proxy.Type.HTTP) { + java.net.SocketAddress address = proxy.address(); + if (address instanceof java.net.InetSocketAddress) { + java.net.InetSocketAddress inetAddress = (java.net.InetSocketAddress) address; + nativeSetSyncConfigProxySettings(nativePtr, proxyType, + inetAddress.getHostString(), inetAddress.getPort()); + } else { + RealmLog.error("Unsupported proxy socket address type: " + address.getClass().getName()); + } + } else { + // FIXME: enable once realm-sync adds support for SOCKS proxies + RealmLog.error("SOCKS proxies are not supported."); + } + } + } + + } + } this.resolvedRealmURI = resolvedRealmURI; } @@ -335,5 +381,7 @@ private static native String nativeCreateAndSetSyncConfig(long nativePtr, String private static native void nativeSetSyncConfigSslSettings(long nativePtr, boolean validateSsl, String trustCertificatePath); + private static native void nativeSetSyncConfigProxySettings(long nativePtr, byte type, String address, int port); + private static native long nativeGetFinalizerPtr(); } From 7d90396b292346ba39ac91c10b3e9fc453a533f5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 5 Aug 2019 22:04:11 +0200 Subject: [PATCH 1397/2110] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aacc8bc81d..7ab6f2e6af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixed * [ObjectServer] The C++ networking layer now correctly uses any system defined proxy the same way the Java networking layer does. (Issue [#6574](https://github.com/realm/realm-java/pull/6574)). * The Realm bytecode transformer now works correctly with Android Gradle Plugin 3.6.0-alpha01 and beyond. (Issue [#6531](https://github.com/realm/realm-java/issues/6531)). +* Queries on RealmLists with objects containing indexed integers could return the wrong result. (Issue [#6522](https://github.com/realm/realm-java/issues/6522), since 5.11.0) ### Compatibility * Realm Object Server: 3.21.0 or later. From 5f735dad5ec4585557c4f6921fbfacc25aa966b8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 5 Aug 2019 22:06:39 +0200 Subject: [PATCH 1398/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ab6f2e6af..4b4581228d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.13.1(YYYY-MM-DD) +## 5.13.1(2019-08-05) ### Enhancements * None. From 1844788c3cd478a1d1a13452a15823394363e722 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 5 Aug 2019 22:07:16 +0200 Subject: [PATCH 1399/2110] Set proper version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7041f18668..70a327741c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.14.0-SNAPSHOT +5.13.1-SNAPSHOT From 5d001370280101e5ca7df870ae6f9dbf2513a556 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 5 Aug 2019 22:07:44 +0200 Subject: [PATCH 1400/2110] Release v5.13.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 70a327741c..47c78569cc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.13.1-SNAPSHOT +5.13.1 \ No newline at end of file From e3e27931e16b2223677a31989c4835e08957cbdb Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 5 Aug 2019 22:07:44 +0200 Subject: [PATCH 1401/2110] Prepare next release v5.13.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 47c78569cc..c04bef7870 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.13.1 \ No newline at end of file +5.13.2-SNAPSHOT \ No newline at end of file From 141c9eba1c76c6e79ca1dd377d939323b0941808 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 5 Aug 2019 23:12:45 +0200 Subject: [PATCH 1402/2110] Prepare for next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index c04bef7870..d077af9147 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.13.2-SNAPSHOT \ No newline at end of file +5.14.0-SNAPSHOT \ No newline at end of file From 92c239ca32fcf4132bad2e2673c55b8f71976d6c Mon Sep 17 00:00:00 2001 From: Jason Flax Date: Wed, 7 Aug 2019 12:47:28 +0100 Subject: [PATCH 1403/2110] ROS-49 Deprecate nickname provider (#6579) --- CHANGELOG.md | 4 ++++ .../src/objectServer/java/io/realm/SyncCredentials.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b4581228d..d50771c0d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 5.13.1(2019-08-05) +### Deprecated +* `SyncCredentials.nickname()` has been deprecated in favour of `SyncCredentials.usernamePassword()`. +* `SyncCredentials.IdentityProvider.NICKNAME` has been deprecated in favour of `SyncCredentials.IdentityProvider.USERNAME_PASSWORD`. + ### Enhancements * None. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index 5b8945feab..dc22d2def6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -136,7 +136,9 @@ public static SyncCredentials anonymous() { * @return a set of credentials that can be used to log into the Object Server using * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. * @throws IllegalArgumentException if the nickname is either {@code null} or empty. + * @deprecated Use {@link SyncCredentials#usernamePassword(String, String)} instead. */ + @Deprecated public static SyncCredentials nickname(String nickname, boolean isAdmin) { assertStringNotEmpty(nickname, "nickname"); Map userInfo = new HashMap(); @@ -323,7 +325,9 @@ public static final class IdentityProvider { /** * Credentials will be verified with a nickname. + * @deprecated Use {@link IdentityProvider#USERNAME_PASSWORD} instead. */ + @Deprecated public static final String NICKNAME = "nickname"; /** From b8436c394f2213ea01f292a22884d7cfd25f2a18 Mon Sep 17 00:00:00 2001 From: Jason Flax Date: Mon, 12 Aug 2019 11:31:06 +0100 Subject: [PATCH 1404/2110] update release date in CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d50771c0d4..5d552102af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ -## 5.13.1(2019-08-05) +## 5.14.0(2019-08-12) ### Deprecated * `SyncCredentials.nickname()` has been deprecated in favour of `SyncCredentials.usernamePassword()`. * `SyncCredentials.IdentityProvider.NICKNAME` has been deprecated in favour of `SyncCredentials.IdentityProvider.USERNAME_PASSWORD`. +## 5.13.1(2019-08-05) + ### Enhancements * None. From 05d3dcee03c9ae85e6847f081ef58100fe1fb5e3 Mon Sep 17 00:00:00 2001 From: Jason Flax Date: Mon, 12 Aug 2019 11:33:26 +0100 Subject: [PATCH 1405/2110] Release v5.14.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index d077af9147..5d4f567eb6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.14.0-SNAPSHOT \ No newline at end of file +5.14.0 \ No newline at end of file From 4176427cd7920c2acf84f1925093827f0c42ce62 Mon Sep 17 00:00:00 2001 From: Jason Flax Date: Mon, 12 Aug 2019 11:33:27 +0100 Subject: [PATCH 1406/2110] Prepare next release v5.14.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5d4f567eb6..a4a35bfee5 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.14.0 \ No newline at end of file +5.14.1-SNAPSHOT \ No newline at end of file From 221f076256ebd4aebd55e56cbbbebec84bb3f372 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 14 Aug 2019 08:44:43 +0200 Subject: [PATCH 1407/2110] Fix crash if combining @RealmField and @LinkingObjects (#6582) --- CHANGELOG.md | 36 ++++++++++++++- .../processor/RealmProxyClassGenerator.kt | 5 ++- .../io/realm/LinkingObjectsManagedTests.java | 28 ++++++++++++ .../entities/BacklinkWithOverridenNames.java | 44 +++++++++++++++++++ 4 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 realm/realm-library/src/testUtils/java/io/realm/entities/BacklinkWithOverridenNames.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d552102af..0249ad15df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,40 @@ +## 5.14.1(YYYY-MM-DD) + +### Enhancements +* None. + +### Fixed +* `Realm.copyToRealm()` and `Realm.insertOrUpdate()` crashed on model classes if `@LinkingObjects` was used to target a field with a re-defined internal name in the parent class (e.g. by using `@RealmField`). (Issue [#6581](https://github.com/realm/realm-java/issues/6581)) + +### Compatibility +* Realm Object Server: 3.21.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None. + + ## 5.14.0(2019-08-12) ### Deprecated -* `SyncCredentials.nickname()` has been deprecated in favour of `SyncCredentials.usernamePassword()`. -* `SyncCredentials.IdentityProvider.NICKNAME` has been deprecated in favour of `SyncCredentials.IdentityProvider.USERNAME_PASSWORD`. +* [ObjectServer] `SyncCredentials.nickname()` has been deprecated in favour of `SyncCredentials.usernamePassword()`. +* [ObjectServer] `SyncCredentials.IdentityProvider.NICKNAME` has been deprecated in favour of `SyncCredentials.IdentityProvider.USERNAME_PASSWORD`. + +### Enhancements +* None. + +### Fixed +* None. + +### Compatibility +* Realm Object Server: 3.21.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None. + ## 5.13.1(2019-08-05) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index 985a6ec120..d19f6dcf60 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -142,7 +142,10 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("this.%1\$sIndex = addColumnDetails(\"%1\$s\", \"%2\$s\", objectSchemaInfo)", field.javaName, field.internalFieldName) } for (backlink in metadata.backlinkFields) { - emitStatement("addBacklinkDetails(schemaInfo, \"%s\", \"%s\", \"%s\")", backlink.targetField, classCollection.getClassFromQualifiedName(backlink.sourceClass!!).internalClassName, backlink.sourceField) + val sourceClass = classCollection.getClassFromQualifiedName(backlink.sourceClass!!) + val internalSourceClassName = sourceClass.internalClassName + val internalSourceFieldName = sourceClass.getInternalFieldName(backlink.sourceField!!) + emitStatement("addBacklinkDetails(schemaInfo, \"%s\", \"%s\", \"%s\")", backlink.targetField, internalSourceClassName, internalSourceFieldName) } emitStatement("this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex()") endConstructor() diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 2b6b8c5019..3cd48ae070 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -31,9 +31,11 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import io.realm.entities.AllJavaTypes; +import io.realm.entities.BacklinkWithOverridenNames; import io.realm.entities.BacklinksSource; import io.realm.entities.BacklinksTarget; import io.realm.exceptions.RealmException; @@ -692,6 +694,32 @@ public void query_multipleReferencesWithDistinct() { assertTrue(child.getListParents().contains(parent)); } + + @Test + public void copyToRealm_modelWithRenamedTargetFields() { + realm.beginTransaction(); + BacklinkWithOverridenNames obj = new BacklinkWithOverridenNames(UUID.randomUUID().toString()); + realm.copyToRealmOrUpdate(obj); + realm.commitTransaction(); + assertEquals(1, realm.where(BacklinkWithOverridenNames.class).count()); + } + + @Test + public void insert_modelWithRenamedTargetFields() { + realm.beginTransaction(); + BacklinkWithOverridenNames obj = new BacklinkWithOverridenNames(UUID.randomUUID().toString()); + realm.insertOrUpdate(obj); + realm.commitTransaction(); + assertEquals(1, realm.where(BacklinkWithOverridenNames.class).count()); + } + + @Test + public void query_modelWithRenamedFields() { + assertEquals(0, realm.where(BacklinkWithOverridenNames.class).equalTo("child.id", "foo").count()); + assertEquals(0, realm.where(BacklinkWithOverridenNames.class).equalTo("parents.id", "foo").count()); + } + + // Based on a quick conversation with Christian Melchior and Mark Rowe, // it appears that notifications are enqueued, briefly, on a non-Java // thread. That makes their delivery onto the looper thread unpredictable. diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/BacklinkWithOverridenNames.java b/realm/realm-library/src/testUtils/java/io/realm/entities/BacklinkWithOverridenNames.java new file mode 100644 index 0000000000..513526364d --- /dev/null +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/BacklinkWithOverridenNames.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.entities; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; +import io.realm.annotations.RealmField; + +@RealmClass(name = "backlink_override_name") +public class BacklinkWithOverridenNames extends RealmObject { + + @PrimaryKey + public String id; + + @RealmField(name = "forward_link") + public BacklinkWithOverridenNames child; + + @LinkingObjects("child") + public final RealmResults parents = null; + + public BacklinkWithOverridenNames() { + + } + + public BacklinkWithOverridenNames(String id) { + this.id = id; + } +} From 7c9a3c9ff449c9728379141ef5c8cdc15dfcbdbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Wed, 28 Aug 2019 14:20:38 +0200 Subject: [PATCH 1408/2110] Fix #6597 - Don't enforce jCenter repository (#6598) --- .../main/groovy/io/realm/gradle/Realm.groovy | 6 +- .../groovy/io/realm/gradle/PluginTest.groovy | 65 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 5331cb1cd2..8eaf910d65 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -72,7 +72,11 @@ class Realm implements Plugin { project.android.registerTransform(new RealmTransformer(project)) - project.repositories.add(project.getRepositories().jcenter()) + if (project.repositories.isEmpty()) { + // If no repository was defined, we add jCenter + project.repositories.add(project.getRepositories().jcenter()) + } + project.dependencies.add(dependencyConfigurationName, "io.realm:realm-annotations:${Version.VERSION}") if (usesAptPlugin) { project.dependencies.add("apt", "io.realm:realm-annotations-processor:${Version.VERSION}") diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index 374d1e4f35..64e7208c02 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -108,6 +108,71 @@ class PluginTest { } } + @Test + void pluginAddsRightRepositories_noRepositorySet() { + project.buildscript { + dependencies { + classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" + classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' + } + } + + def manifest = project.file("src/main/AndroidManifest.xml") + manifest.parentFile.mkdirs() + manifest.text = '' + + project.apply plugin: 'com.android.application' + project.apply plugin: 'realm-android' + + project.android { + compileSdkVersion 27 + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 27 + } + } + + project.evaluate() + + assertTrue(project.repositories.size() == 1) + assertTrue(project.repositories.contains(project.getRepositories().jcenter())) + } + + @Test + void pluginAddsRightRepositories_withRepositoriesSet() { + project.buildscript { + repositories { + google() + } + dependencies { + classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" + classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' + } + } + + def manifest = project.file("src/main/AndroidManifest.xml") + manifest.parentFile.mkdirs() + manifest.text = '' + + project.apply plugin: 'com.android.application' + project.apply plugin: 'realm-android' + + project.android { + compileSdkVersion 27 + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 27 + } + } + + project.evaluate() + + assertTrue(project.getRepositories().size() == 1) + assertTrue(project.repositories.contains(project.getRepositories().google())) + } + private static boolean containsUrl(RepositoryHandler repositories, String url) { for (repo in repositories) { if (repo.properties.get('url').toString() == url) { From e32b141b6a68236192f7c235d317f9a350c38a5e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 29 Aug 2019 10:18:08 +0200 Subject: [PATCH 1409/2110] Add support for Client Resync (#6596) --- CHANGELOG.md | 17 +++ .../java/io/realm/SessionTests.java | 5 + .../java/io/realm/SyncConfigurationTests.java | 49 +++++++++ .../src/main/cpp/io_realm_SyncSession.cpp | 55 +++++----- .../cpp/io_realm_internal_OsRealmConfig.cpp | 9 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../io/realm/internal/ObjectServerFacade.java | 2 +- .../java/io/realm/internal/OsRealmConfig.java | 11 +- .../java/io/realm/ClientResyncMode.java | 74 +++++++++++++ .../java/io/realm/SyncConfiguration.java | 103 +++++++++++++++--- .../internal/SyncObjectServerFacade.java | 5 +- .../java/io/realm/PermissionManagerTests.java | 50 +-------- .../java/io/realm/SyncSessionTests.java | 1 + version.txt | 2 +- 14 files changed, 280 insertions(+), 105 deletions(-) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0249ad15df..2ed5d77cf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 5.15.0(YYYY-MM-DD) + +### Enhancements +* [ObjectServer] Added support for Client Resync which automatically will recover the local Realm in case the server is rolled back. This largely replaces the Client Reset mechanism. Can be configured using `SyncConfiguration.Builder.clientResyncMode()`. (Issue [#6487](https://github.com/realm/realm-java/issues/6487)) + +### Fixed +* None. + +### Compatibility +* Realm Object Server: 3.21.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* Updated to Object Store commit: 2786752758a63c8d9c77b8caee0a97d9eddb11ca. + + ## 5.14.1(YYYY-MM-DD) ### Enhancements diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index a5c0c12c32..0a3977ebd0 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -125,6 +125,7 @@ public void errorHandler_clientResetReported() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + .clientResyncMode(ClientResyncMode.MANUAL) .errorHandler((session, error) -> { if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { fail("Wrong error " + error.toString()); @@ -156,6 +157,7 @@ public void errorHandler_manualExecuteClientReset() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + .clientResyncMode(ClientResyncMode.MANUAL) .errorHandler((session, error) -> { if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { fail("Wrong error " + error.toString()); @@ -194,6 +196,7 @@ public void errorHandler_useBackupSyncConfigurationForClientReset() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + .clientResyncMode(ClientResyncMode.MANUAL) .schema(StringOnly.class) .errorHandler((session, error) -> { if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { @@ -253,6 +256,7 @@ public void errorHandler_useBackupSyncConfigurationAfterClientReset() { SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + .clientResyncMode(ClientResyncMode.MANUAL) .errorHandler((session, error) -> { if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { fail("Wrong error " + error.toString()); @@ -335,6 +339,7 @@ public void errorHandler_useClientResetEncrypted() { String url = "realm://objectserver.realm.io/default"; final byte[] randomKey = TestHelper.getRandomKey(); final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + .clientResyncMode(ClientResyncMode.MANUAL) .encryptionKey(randomKey) .modules(new StringOnlyModule()) .errorHandler((session, error) -> { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 8b3438af3c..a1205d6a06 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -519,4 +519,53 @@ public void automatic_convertsAuthUrl() { user.logOut(); } } + + @Test + public void clientResyncMode() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + + // Default mode for full Realms + SyncConfiguration config = user.createConfiguration(url) + .fullSynchronization() + .build(); + assertEquals(ClientResyncMode.RECOVER_LOCAL_REALM, config.getClientResyncMode()); + + // Default mode for query-based Realms + config = user.createConfiguration(url).build(); + assertEquals(ClientResyncMode.MANUAL, config.getClientResyncMode()); + + // Manually set the mode + config = user.createConfiguration(url) + .clientResyncMode(ClientResyncMode.MANUAL) + .build(); + assertEquals(ClientResyncMode.MANUAL, config.getClientResyncMode()); + } + + @Test + public void clientResyncMode_throwsOnNull() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration.Builder config = user.createConfiguration(url); + try { + //noinspection ConstantConditions + config.clientResyncMode(null); + fail(); + } catch (IllegalArgumentException ignore) { + } + } + + @Test + public void clientResyncMode_throwsIfNotManualForQueryBasedRealms() { + SyncUser user = createTestUser(); + String url = "realm://objectserver.realm.io/default"; + SyncConfiguration.Builder config = user.createConfiguration(url) + .clientResyncMode(ClientResyncMode.RECOVER_LOCAL_REALM); + try { + //noinspection ConstantConditions + config.build(); + fail(); + } catch (IllegalStateException ignore) { + } + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 89849efc4f..82133d6a88 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -160,22 +160,19 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", "(ILjava/lang/Long;Ljava/lang/String;)V"); JavaGlobalRef java_session_object_ref(env, session_object); - - bool listener_registered = - session->wait_for_download_completion([java_session_object_ref, callback_id](std::error_code error) { - JNIEnv* env = JniUtils::get_env(true); - JavaLocalRef java_error_code; - JavaLocalRef java_error_message; - if (error != std::error_code{}) { - java_error_code = - JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); - java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); - } - env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, - callback_id, java_error_code.get(), java_error_message.get()); - }); - - return to_jbool(listener_registered); + session->wait_for_download_completion([java_session_object_ref, callback_id](std::error_code error) { + JNIEnv* env = JniUtils::get_env(true); + JavaLocalRef java_error_code; + JavaLocalRef java_error_message; + if (error != std::error_code{}) { + java_error_code = + JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); + java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); + } + env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, + callback_id, java_error_code.get(), java_error_message.get()); + }); + return to_jbool(JNI_TRUE); } } CATCH_STD() @@ -198,20 +195,18 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompleti "(ILjava/lang/Long;Ljava/lang/String;)V"); JavaGlobalRef java_session_object_ref(env, session_object); - bool listener_registered = - session->wait_for_upload_completion([java_session_object_ref, callback_id](std::error_code error) { - JNIEnv* env = JniUtils::get_env(true); - JavaLocalRef java_error_code; - JavaLocalRef java_error_message; - if (error != std::error_code{}) { - java_error_code = JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); - java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); - } - env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, - callback_id, java_error_code.get(), java_error_message.get()); - }); - - return to_jbool(listener_registered); + session->wait_for_upload_completion([java_session_object_ref, callback_id](std::error_code error) { + JNIEnv* env = JniUtils::get_env(true); + JavaLocalRef java_error_code; + JavaLocalRef java_error_message; + if (error != std::error_code{}) { + java_error_code = JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); + java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); + } + env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, + callback_id, java_error_code.get(), java_error_message.get()); + }); + return JNI_TRUE; } } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index f49b8a57ba..dac48e2756 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -256,7 +256,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNo JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( JNIEnv* env, jclass, jlong native_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, jstring j_refresh_token, jboolean j_is_partial, jbyte j_session_stop_policy, jstring j_url_prefix, - jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array) + jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array, jbyte j_client_reset_mode) { TR_ENTER_PTR(native_ptr) auto& config = *reinterpret_cast(native_ptr); @@ -370,6 +370,12 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe config.sync_config->bind_session_handler = std::move(bind_handler); config.sync_config->error_handler = std::move(error_handler); config.sync_config->is_partial = (j_is_partial == JNI_TRUE); + switch (j_client_reset_mode) { + case io_realm_internal_OsRealmConfig_CLIENT_RESYNC_MODE_RECOVER: config.sync_config->client_resync_mode = realm::ClientResyncMode::Recover; break; + case io_realm_internal_OsRealmConfig_CLIENT_RESYNC_MODE_DISCARD: config.sync_config->client_resync_mode = realm::ClientResyncMode::DiscardLocal; break; + case io_realm_internal_OsRealmConfig_CLIENT_RESYNC_MODE_MANUAL: config.sync_config->client_resync_mode = realm::ClientResyncMode::Manual; break; + default: throw std::logic_error(util::format("Unsupported value for ClientResyncMode: %1", j_client_reset_mode)); + } if (j_url_prefix) { JStringAccessor url_prefix(env, j_url_prefix); @@ -475,5 +481,4 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigP } CATCH_STD() } - #endif diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index bcc6a7524e..2786752758 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit bcc6a7524e52071bfcd35cf740f506e0cc6a595e +Subproject commit 2786752758a63c8d9c77b8caee0a97d9eddb11ca diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 4d1914053b..5cc12b9ca5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -66,7 +66,7 @@ public void realmClosed(RealmConfiguration configuration) { } public Object[] getSyncConfigurationOptions(RealmConfiguration config) { - return new Object[11]; + return new Object[12]; } public static ObjectServerFacade getFacade(boolean needSyncFacade) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 95643c1ce4..7891f469d3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -169,6 +169,11 @@ public Builder fifoFallbackDir(File dir) { private static final byte SYNCSESSION_STOP_POLICY_VALUE_AFTER_CHANGES_UPLOADED = 2; private static final byte PROXYCONFIG_TYPE_VALUE_HTTP = 0; + // Public to be usable from the io.realm package + public static final byte CLIENT_RESYNC_MODE_RECOVER = 0; + public static final byte CLIENT_RESYNC_MODE_DISCARD = 1; + public static final byte CLIENT_RESYNC_MODE_MANUAL = 2; + private final static long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final RealmConfiguration realmConfiguration; @@ -212,6 +217,7 @@ private OsRealmConfig(final RealmConfiguration config, boolean isPartial = (Boolean.TRUE.equals(syncConfigurationOptions[7])); String urlPrefix = (String)(syncConfigurationOptions[8]); String customAuthorizationHeaderName = (String)(syncConfigurationOptions[9]); + Byte clientResyncMode = (Byte) syncConfigurationOptions[11]; // Convert the headers into a String array to make it easier to send through JNI // [key1, value1, key2, value2, ...] @@ -280,7 +286,8 @@ private OsRealmConfig(final RealmConfiguration config, sessionStopPolicy, urlPrefix, customAuthorizationHeaderName, - customHeaders); + customHeaders, + clientResyncMode); try { resolvedRealmURI = new URI(resolvedSyncRealmUrl); } catch (URISyntaxException e) { @@ -376,7 +383,7 @@ private static native String nativeCreateAndSetSyncConfig(long nativePtr, String String userId, String refreshToken, boolean isPartial, byte sessionStopPolicy, String urlPrefix, String customAuthorizationHeaderName, - String[] customHeaders); + String[] customHeaders, byte clientResetMode); private static native void nativeSetSyncConfigSslSettings(long nativePtr, boolean validateSsl, String trustCertificatePath); diff --git a/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java b/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java new file mode 100644 index 0000000000..d94f7c40f2 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm; + +import io.realm.internal.OsRealmConfig; + +/** + * Enum describing what should happen in case of a Client Resync. + *

            + * A Client Resync is triggered if the device and server cannot agree on a common shared history + * for the Realm file, thus making it impossible for the device to upload or receive any changes. + * This can happen if the server is rolled back or restored from backup. + *

            + * IMPORTANT: Just having the device offline will not trigger a Client Resync. + */ +public enum ClientResyncMode { + + /** + * Realm will compare the local Realm with the Realm on the server and automatically transfer + * any changes from the local Realm that makes sense to the Realm provided by the server. + *

            + * This is the default mode for fully synchronized Realms. It is not yet supported by + * Query-based Realms. + */ + RECOVER_LOCAL_REALM(OsRealmConfig.CLIENT_RESYNC_MODE_RECOVER), + + /** + * The local Realm will be discarded and replaced with the server side Realm. + * All local changes will be lost. + *

            + * This mode is not yet supported by Query-based Realms. + */ + DISCARD_LOCAL_REALM(OsRealmConfig.CLIENT_RESYNC_MODE_DISCARD), + + /** + * A manual Client Resync is also known as a Client Reset. + *

            + * A {@link io.realm.ClientResetRequiredError} will be sent to + * {@link io.realm.SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError)}, triggering + * a Client Reset. Doing this provides a handle to both the old and new Realm file, enabling + * full control of which changes to move, if any. + *

            + * This is the only supported mode for Query-based Realms. + * + * @see io.realm.SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more + * information about when and why Client Reset occurs and how to deal with it. + */ + MANUAL(OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL); + + final byte value; + + ClientResyncMode(byte value) { + this.value = value; + } + + public byte getNativeValue() { + return value; + } + + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index ed437ef308..832fc5ec06 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -114,6 +114,7 @@ public class SyncConfiguration extends RealmConfiguration { private final OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy; private final boolean isPartial; @Nullable private final String syncUrlPrefix; + private final ClientResyncMode clientResyncMode; private SyncConfiguration(File directory, String filename, @@ -140,7 +141,8 @@ private SyncConfiguration(File directory, OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy, boolean isPartial, CompactOnLaunchCallback compactOnLaunch, - @Nullable String syncUrlPrefix) { + @Nullable String syncUrlPrefix, + ClientResyncMode clientResyncMode) { super(directory, filename, canonicalPath, @@ -170,6 +172,7 @@ private SyncConfiguration(File directory, this.sessionStopPolicy = sessionStopPolicy; this.isPartial = isPartial; this.syncUrlPrefix = syncUrlPrefix; + this.clientResyncMode = clientResyncMode; } /** @@ -293,13 +296,20 @@ public boolean equals(Object o) { if (deleteRealmOnLogout != that.deleteRealmOnLogout) return false; if (syncClientValidateSsl != that.syncClientValidateSsl) return false; + if (waitForInitialData != that.waitForInitialData) return false; + if (initialDataTimeoutMillis != that.initialDataTimeoutMillis) return false; + if (isPartial != that.isPartial) return false; if (!serverUrl.equals(that.serverUrl)) return false; if (!user.equals(that.user)) return false; if (!errorHandler.equals(that.errorHandler)) return false; - if (serverCertificateAssetName != null ? !serverCertificateAssetName.equals(that.serverCertificateAssetName) : that.serverCertificateAssetName != null) return false; - if (serverCertificateFilePath != null ? !serverCertificateFilePath.equals(that.serverCertificateFilePath) : that.serverCertificateFilePath != null) return false; - if (waitForInitialData != that.waitForInitialData) return false; - return true; + if (serverCertificateAssetName != null ? !serverCertificateAssetName.equals(that.serverCertificateAssetName) : that.serverCertificateAssetName != null) + return false; + if (serverCertificateFilePath != null ? !serverCertificateFilePath.equals(that.serverCertificateFilePath) : that.serverCertificateFilePath != null) + return false; + if (sessionStopPolicy != that.sessionStopPolicy) return false; + if (syncUrlPrefix != null ? !syncUrlPrefix.equals(that.syncUrlPrefix) : that.syncUrlPrefix != null) + return false; + return clientResyncMode == that.clientResyncMode; } @Override @@ -313,23 +323,44 @@ public int hashCode() { result = 31 * result + (serverCertificateAssetName != null ? serverCertificateAssetName.hashCode() : 0); result = 31 * result + (serverCertificateFilePath != null ? serverCertificateFilePath.hashCode() : 0); result = 31 * result + (waitForInitialData ? 1 : 0); + result = 31 * result + (int) (initialDataTimeoutMillis ^ (initialDataTimeoutMillis >>> 32)); + result = 31 * result + sessionStopPolicy.hashCode(); + result = 31 * result + (isPartial ? 1 : 0); + result = 31 * result + (syncUrlPrefix != null ? syncUrlPrefix.hashCode() : 0); + result = 31 * result + clientResyncMode.hashCode(); return result; } @Override public String toString() { - StringBuilder stringBuilder = new StringBuilder(super.toString()); - stringBuilder.append("\n"); - stringBuilder.append("serverUrl: " + serverUrl); - stringBuilder.append("\n"); - stringBuilder.append("user: " + user); - stringBuilder.append("\n"); - stringBuilder.append("errorHandler: " + errorHandler); - stringBuilder.append("\n"); - stringBuilder.append("deleteRealmOnLogout: " + deleteRealmOnLogout); - stringBuilder.append("\n"); - stringBuilder.append("waitForInitialRemoteData: " + waitForInitialData); - return stringBuilder.toString(); + StringBuilder sb = new StringBuilder(super.toString()); + sb.append("\n"); + sb.append("serverUrl: ").append(serverUrl); + sb.append("\n"); + sb.append("user: ").append(user); + sb.append("\n"); + sb.append("errorHandler: ").append(errorHandler); + sb.append("\n"); + sb.append("deleteRealmOnLogout: ").append(deleteRealmOnLogout); + sb.append("\n"); + sb.append("syncClientValidateSsl: ").append(syncClientValidateSsl); + sb.append("\n"); + sb.append("serverCertificateAssetName: ").append(serverCertificateAssetName); + sb.append("\n"); + sb.append("serverCertificateFilePath: ").append(serverCertificateFilePath); + sb.append("\n"); + sb.append("waitForInitialData: ").append(waitForInitialData); + sb.append("\n"); + sb.append("initialDataTimeoutMillis: ").append(initialDataTimeoutMillis); + sb.append("\n"); + sb.append("sessionStopPolicy: ").append(sessionStopPolicy); + sb.append("\n"); + sb.append("isPartial: ").append(isPartial); + sb.append("\n"); + sb.append("syncUrlPrefix: ").append(syncUrlPrefix); + sb.append("\n"); + sb.append("clientResyncMode: ").append(clientResyncMode); + return sb.toString(); } /** @@ -469,6 +500,13 @@ public String getUrlPrefix() { return syncUrlPrefix; } + /** + * Returns what happens in case of a Client Resync. + */ + public ClientResyncMode getClientResyncMode() { + return clientResyncMode; + } + /** * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ @@ -508,6 +546,8 @@ public static final class Builder { private boolean isPartial = true; // Partial Synchronization is enabled by default private CompactOnLaunchCallback compactOnLaunch; private String syncUrlPrefix = null; + @Nullable // null means the user hasn't explicitly set one. An appropriate default is chosen when calling build() + private ClientResyncMode clientResyncMode = null; /** * Creates an instance of the Builder for the SyncConfiguration. This SyncConfiguration @@ -1101,6 +1141,23 @@ public Builder deleteRealmOnLogout() { } */ + /** + * Configure the behavior in case of a Client Resync. + *

            + * The default mode is {@link ClientResyncMode#RECOVER_LOCAL_REALM}. + * + * @param mode what should happen when a Client Resync happens + * @see ClientResyncMode for more information about what a Client Resync is. + */ + public Builder clientResyncMode(ClientResyncMode mode) { + //noinspection ConstantConditions + if (mode == null) { + throw new IllegalArgumentException("Non-null 'mode' required."); + } + clientResyncMode = mode; + return this; + } + /** * Creates the RealmConfiguration based on the builder parameters. * @@ -1132,6 +1189,15 @@ public SyncConfiguration build() { " access token. Use a path without /~/."); } + // Set the default Client Resync Mode based on the current type of Realm. + // Eventually RECOVER_LOCAL_REALM should be the default for all types. + if (clientResyncMode == null) { + clientResyncMode = (isPartial) ? ClientResyncMode.MANUAL : ClientResyncMode.RECOVER_LOCAL_REALM; + } + if (isPartial && clientResyncMode != ClientResyncMode.MANUAL) { + throw new IllegalStateException("Query-based sync only supports manual Client Resync. It was: " + clientResyncMode); + } + if (rxFactory == null && isRxJavaAvailable()) { rxFactory = new RealmObservableFactory(); } @@ -1225,7 +1291,8 @@ public SyncConfiguration build() { sessionStopPolicy, isPartial, compactOnLaunch, - syncUrlPrefix + syncUrlPrefix, + clientResyncMode ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index a5949843ab..3ad43b189a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -116,10 +116,11 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { !syncConfig.isFullySynchronizedRealm(), urlPrefix, customAuthorizationHeaderName, - customHeaders + customHeaders, + syncConfig.getClientResyncMode().getNativeValue() }; } else { - return new Object[11]; + return new Object[12]; } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java index 4582e819c1..6ead0af6ea 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java @@ -227,59 +227,13 @@ public void onSuccess(RealmResults permissions) { pm.getPermissions(new PermissionManager.PermissionsCallback() { @Override public void onSuccess(RealmResults permissions) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.CLIENT_RESET, error.getErrorCode()); + assertEquals(3, permissions.size()); looperThread.testComplete(); } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getPermissions_addTaskAfterClientReset() { - final PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - // Simulate reset after first request succeeded to make sure that session is - // alive. - SyncManager.simulateClientReset(SyncManager.getSession(pm.permissionRealmConfig)); - - // 1. Run task that fail - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - fail(); - } @Override public void onError(ObjectServerError error) { - assertEquals(ErrorCode.CLIENT_RESET, error.getErrorCode()); - // 2. Then try to add another - pm.getDefaultPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.CLIENT_RESET, error.getErrorCode()); - looperThread.testComplete(); - } - }); + fail(error.toString()); } }); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index a4bbc0edfb..9d69886f25 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -520,6 +520,7 @@ public void clientReset_manualTriggerAllowSessionToRestart() { final AtomicReference configRef = new AtomicReference<>(null); final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .clientResyncMode(ClientResyncMode.MANUAL) .directory(looperThread.getRoot()) .fullSynchronization() .errorHandler(new SyncSession.ErrorHandler() { diff --git a/version.txt b/version.txt index a4a35bfee5..7d9938c921 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.14.1-SNAPSHOT \ No newline at end of file +5.15.0-SNAPSHOT \ No newline at end of file From 17290057cd808e4dea7c3612b9b92d76e6ee7f6e Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Thu, 29 Aug 2019 16:24:52 +0200 Subject: [PATCH 1410/2110] [RJAVA-4] Bypass the sync proxy on Cloud (#6599) * Bypass the sync proxy on Cloud * changelog * new line --- CHANGELOG.md | 1 + .../src/main/cpp/io_realm_SyncSession.cpp | 14 ++++++ .../java/io/realm/SyncSession.java | 8 ++++ .../network/AuthenticateResponse.java | 11 +++++ .../internal/objectserver/SyncWorker.java | 44 +++++++++++++++++++ 5 files changed, 78 insertions(+) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncWorker.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed5d77cf2..a61380393b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Internal * Updated to Object Store commit: 2786752758a63c8d9c77b8caee0a97d9eddb11ca. +* Implemented direct access to sync workers on Cloud, bypassing the Sync Proxy: the binding will override the sync session's url prefix if the token refresh response for a realm contains a sync worker path field. ## 5.14.1(YYYY-MM-DD) diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 82133d6a88..35ef67a028 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -356,3 +356,17 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStop(JNIEnv* env, jclass, } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeSetUrlPrefix(JNIEnv* env, jclass, jstring j_local_realm_path, jstring j_url_prefix) +{ + TR_ENTER() + try { + JStringAccessor local_realm_path(env, j_local_realm_path); + auto session = SyncManager::shared().get_existing_session(local_realm_path); + if (session) { + JStringAccessor url_prefix(env, j_url_prefix); + session->set_url_prefix(url_prefix); + } + } + CATCH_STD() +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 431499422b..d8686a8ae4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -49,6 +49,7 @@ import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.objectserver.Token; +import io.realm.internal.objectserver.SyncWorker; import io.realm.internal.util.Pair; import io.realm.log.RealmLog; @@ -879,6 +880,12 @@ protected void onSuccess(AuthenticateResponse response) { synchronized (SyncSession.this) { if (!isClosed && !Thread.currentThread().isInterrupted() && !refreshTokenNetworkRequest.isCancelled()) { RealmLog.debug("Access Token refreshed successfully, Sync URL: " + configuration.getServerUrl()); + + SyncWorker syncWorker = response.getSyncWorker(); + if (syncWorker != null) { + nativeSetUrlPrefix(configuration.getPath(), syncWorker.path()); + } + URI realmUrl = configuration.getServerUrl(); if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), realmUrl.toString())) { // replace the user old access_token @@ -971,4 +978,5 @@ public void throwExceptionIfNeeded() { private static native byte nativeGetConnectionState(String localRealmPath); private static native void nativeStart(String localRealmPath); private static native void nativeStop(String localRealmPath); + private static native void nativeSetUrlPrefix(String localRealmPath, String urlPrefix); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index fc968d6b12..14b205cc06 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -25,6 +25,7 @@ import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.internal.objectserver.Token; +import io.realm.internal.objectserver.SyncWorker; import io.realm.log.RealmLog; import okhttp3.Response; @@ -35,9 +36,11 @@ public class AuthenticateResponse extends AuthServerResponse { private static final String JSON_FIELD_ACCESS_TOKEN = "access_token"; private static final String JSON_FIELD_REFRESH_TOKEN = "refresh_token"; + private static final String JSON_FIELD_SYNC_WORKER = "sync_worker"; private final Token accessToken; private final Token refreshToken; + private final SyncWorker syncWorker; /** * Helper method for creating the proper Authenticate response. This method will set the appropriate error @@ -109,6 +112,7 @@ private AuthenticateResponse(ObjectServerError error) { setError(error); this.accessToken = null; this.refreshToken = null; + this.syncWorker = null; } /** @@ -121,11 +125,13 @@ private AuthenticateResponse(String serverResponse) { ObjectServerError error; Token accessToken; Token refreshToken; + SyncWorker syncWorker; String debugMessage; try { JSONObject obj = new JSONObject(serverResponse); accessToken = obj.has(JSON_FIELD_ACCESS_TOKEN) ? Token.from(obj.getJSONObject(JSON_FIELD_ACCESS_TOKEN)) : null; refreshToken = obj.has(JSON_FIELD_REFRESH_TOKEN) ? Token.from(obj.getJSONObject(JSON_FIELD_REFRESH_TOKEN)) : null; + syncWorker = obj.has(JSON_FIELD_SYNC_WORKER) ? SyncWorker.from(obj.getJSONObject(JSON_FIELD_SYNC_WORKER)) : null; error = null; if (accessToken == null) { debugMessage = "accessToken = null"; @@ -135,6 +141,7 @@ private AuthenticateResponse(String serverResponse) { } catch (JSONException ex) { accessToken = null; refreshToken = null; + syncWorker = null; String exceptionMessage = String.format(Locale.US, "Server response could not be parsed as JSON:%n%s", serverResponse); //noinspection ThrowableInstanceNeverThrown error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, exceptionMessage, ex); @@ -144,6 +151,7 @@ private AuthenticateResponse(String serverResponse) { setError(error); this.accessToken = accessToken; this.refreshToken = refreshToken; + this.syncWorker = syncWorker; } public Token getAccessToken() { @@ -154,4 +162,7 @@ public Token getRefreshToken() { return refreshToken; } + public SyncWorker getSyncWorker() { + return syncWorker; + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncWorker.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncWorker.java new file mode 100644 index 0000000000..3fca1710d5 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/SyncWorker.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.internal.objectserver; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * This class represents a value describing a sync worker on the Realm Cloud. + */ +public class SyncWorker { + + private static final String KEY_PATH = "path"; + + private final String path; + + public static SyncWorker from(JSONObject syncWorker) throws JSONException { + String path = syncWorker.getString(KEY_PATH); + + return new SyncWorker(path); + } + + public SyncWorker(String path) { + this.path = path; + } + + public String path() { + return path; + } +} From 76a71e386aa8629739a5a62c023fca8930052add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Mon, 2 Sep 2019 11:54:20 +0200 Subject: [PATCH 1411/2110] Fix #6597 - Don't enforce jCenter repository (followup #6598) (#6602) --- .../main/groovy/io/realm/gradle/Realm.groovy | 9 ++- .../groovy/io/realm/gradle/PluginTest.groovy | 62 +++++++++++++++++-- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 8eaf910d65..7abd2ba74a 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -72,9 +72,12 @@ class Realm implements Plugin { project.android.registerTransform(new RealmTransformer(project)) - if (project.repositories.isEmpty()) { - // If no repository was defined, we add jCenter - project.repositories.add(project.getRepositories().jcenter()) + project.afterEvaluate { + if (project.repositories.isEmpty()) { + // If no repository was defined, we add jCenter + // Calling this automatically adds jCenter to the list of repositories + project.getRepositories().jcenter() + } } project.dependencies.add(dependencyConfigurationName, "io.realm:realm-annotations:${Version.VERSION}") diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index 64e7208c02..f6e7b97594 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -135,15 +135,62 @@ class PluginTest { project.evaluate() + assertTrue(project.buildscript.repositories.isEmpty()) + assertTrue(project.repositories.size() == 1) - assertTrue(project.repositories.contains(project.getRepositories().jcenter())) + assertTrue(project.repositories.first().url.host == 'jcenter.bintray.com') } @Test void pluginAddsRightRepositories_withRepositoriesSet() { project.buildscript { repositories { - google() + maven { + url 'https://maven.google.com/' + } + } + dependencies { + classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" + classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' + } + } + + repositories { + google() + } + + def manifest = project.file("src/main/AndroidManifest.xml") + manifest.parentFile.mkdirs() + manifest.text = '' + + project.apply plugin: 'com.android.application' + project.apply plugin: 'realm-android' + + project.android { + compileSdkVersion 27 + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 27 + } + } + + project.evaluate() + + assertTrue(project.buildscript.repositories.size() == 1) + assertTrue(project.buildscript.repositories.first().url.host == 'maven.google.com') + + assertTrue(project.repositories.size() == 1) + assertTrue(project.repositories.first().url.host == 'dl.google.com') + } + + @Test + void pluginAddsRightRepositories_withRepositoriesSetAfterPluginIsApplied() { + project.buildscript { + repositories { + maven { + url 'https://maven.google.com/' + } } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" @@ -158,6 +205,10 @@ class PluginTest { project.apply plugin: 'com.android.application' project.apply plugin: 'realm-android' + repositories { + google() + } + project.android { compileSdkVersion 27 @@ -169,8 +220,11 @@ class PluginTest { project.evaluate() - assertTrue(project.getRepositories().size() == 1) - assertTrue(project.repositories.contains(project.getRepositories().google())) + assertTrue(project.buildscript.repositories.size() == 1) + assertTrue(project.buildscript.repositories.first().url.host == 'maven.google.com') + + assertTrue(project.repositories.size() == 1) + assertTrue(project.repositories.first().url.host == 'dl.google.com') } private static boolean containsUrl(RepositoryHandler repositories, String url) { From 00698d17d1348160e002af961f4f5722f7faaf26 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 4 Sep 2019 15:12:37 +0200 Subject: [PATCH 1412/2110] Upgrade to Sync 4.7.4 (#6603) --- CHANGELOG.md | 6 ++- Jenkinsfile | 18 +++++---- README.md | 9 ++++- dependencies.list | 4 +- gradle-plugin/build.gradle | 4 ++ .../main/groovy/io/realm/gradle/Realm.groovy | 12 +++++- .../groovy/io/realm/gradle/PluginTest.groovy | 39 ++++++++++--------- realm/realm-library/src/main/cpp/object-store | 2 +- 8 files changed, 61 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a61380393b..61c0767699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * [ObjectServer] Added support for Client Resync which automatically will recover the local Realm in case the server is rolled back. This largely replaces the Client Reset mechanism. Can be configured using `SyncConfiguration.Builder.clientResyncMode()`. (Issue [#6487](https://github.com/realm/realm-java/issues/6487)) ### Fixed -* None. +* Huawei devices reporting `Permission denied` when opening a Realm file after an app upgrade or factory reset. This does not automatically fix already existing Realm files. See [this FAQ entry](https://realm.io/docs/java/latest/#huawei-permission-denied) for more details. (Issue [#5715](https://github.com/realm/realm-java/issues/5715)) ### Compatibility * Realm Object Server: 3.21.0 or later. @@ -12,8 +12,10 @@ * APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. ### Internal -* Updated to Object Store commit: 2786752758a63c8d9c77b8caee0a97d9eddb11ca. * Implemented direct access to sync workers on Cloud, bypassing the Sync Proxy: the binding will override the sync session's url prefix if the token refresh response for a realm contains a sync worker path field. +* Updated to Object Store commit: 9f19d79fde248ba37cef0bd52fe64984f9d71be0. +* Updated to Realm Sync 4.7.4. +* Updated to Realm Core 5.23.2. ## 5.14.1(YYYY-MM-DD) diff --git a/Jenkinsfile b/Jenkinsfile index 0d12486c6b..a820d8a31f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -76,14 +76,6 @@ try { } } - stage('Gradle plugin tests') { - try { - gradle('gradle-plugin', 'check') - } finally { - storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' - } - } - stage('Realm Transformer tests') { try { gradle('realm-transformer', 'check') @@ -121,6 +113,16 @@ try { } } + // Gradle plugin tests require that artifacts are available, so this + // step needs to be after the instrumentation tests + stage('Gradle plugin tests') { + try { + gradle('gradle-plugin', 'check --debug') + } finally { + storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + } + } + // TODO: add support for running monkey on the example apps if (['master'].contains(env.BRANCH_NAME)) { diff --git a/README.md b/README.md index 87398a1aa6..de05f96a84 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,17 @@ You may unzip the file wherever you choose. For macOS, a suggested location is * If you will be building with Android Studio, you will need to tell it to use the correct NDK. To do this, define the variable `ndk.dir` in `realm/local.properties` and assign it the full pathname of the directory that you unzipped above. Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited. ``` - ndk.dir=/Users/brian/Library/Android/android-ndk-r10e/r10e + ndk.dir=/Users/brian/Library/Android/android-ndk-r10e ``` +* You also need a file called `source.properties` to the `android-ndk-r10e` folder with the following content: + + ``` + Pkg.Desc = Android NDK + Pkg.Revision = 10.0.0 + ``` + * Add two environment variables to your profile (presuming you installed the NDK in `~/Library/android-ndk-r10e`): ``` diff --git a/dependencies.list b/dependencies.list index 6c5ad0f65f..1255617b1f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=4.7.1 -REALM_SYNC_SHA256=28c37d53e63d80db6be1f3e566adac7eea291f496be2b5274cfb94658b435d3d +REALM_SYNC_VERSION=4.7.4 +REALM_SYNC_SHA256=a7ec9b32a137760c317df15279cc33ffdb63b8663e9ce789a345628afb8424a7 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index ea02b04bd6..867ff9fb5c 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -40,6 +40,10 @@ configurations { compile.extendsFrom provided } +test { + testLogging.showStandardStreams = true +} + sourceSets { main { compileClasspath += configurations.provided diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 7abd2ba74a..f1eff15f9c 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -73,7 +73,17 @@ class Realm implements Plugin { project.android.registerTransform(new RealmTransformer(project)) project.afterEvaluate { - if (project.repositories.isEmpty()) { + // The Android Gradle Plugin automatically adds the local maven repository + // found in the Android SDK, so we need to filter that out. + if (project.repositories.findAll { + def url = it.url.toString() + if (url.endsWith('/')) { + url = url.substring(0, url.length() - 1) + } + return (!url.endsWith("extras/m2repository") + && !url.endsWith("extras/android/m2repository") + && !url.endsWith("extras/google/m2repository")) + }.isEmpty()) { // If no repository was defined, we add jCenter // Calling this automatically adds jCenter to the list of repositories project.getRepositories().jcenter() diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index f6e7b97594..cfe500208a 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -59,7 +59,6 @@ class PluginTest { } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" - classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' } } @@ -111,9 +110,14 @@ class PluginTest { @Test void pluginAddsRightRepositories_noRepositorySet() { project.buildscript { + repositories { + maven { + url 'https://maven.google.com/' + } + jcenter() + } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" - classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' } } @@ -135,27 +139,26 @@ class PluginTest { project.evaluate() - assertTrue(project.buildscript.repositories.isEmpty()) - - assertTrue(project.repositories.size() == 1) - assertTrue(project.repositories.first().url.host == 'jcenter.bintray.com') + assertEquals(2, project.buildscript.repositories.size()) + assertEquals(4, project.repositories.size()) // The Android plugin adds 3 different local repos + assertEquals('jcenter.bintray.com', project.repositories.last().url.host) } @Test void pluginAddsRightRepositories_withRepositoriesSet() { project.buildscript { repositories { + jcenter() maven { url 'https://maven.google.com/' } } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" - classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' } } - repositories { + project.repositories { google() } @@ -177,11 +180,11 @@ class PluginTest { project.evaluate() - assertTrue(project.buildscript.repositories.size() == 1) - assertTrue(project.buildscript.repositories.first().url.host == 'maven.google.com') + assertEquals(2, project.buildscript.repositories.size()) + assertEquals('maven.google.com', project.buildscript.repositories.last().url.host) - assertTrue(project.repositories.size() == 1) - assertTrue(project.repositories.first().url.host == 'dl.google.com') + assertEquals(4, project.repositories.size()) + assertEquals('dl.google.com', project.repositories.last().url.host) } @Test @@ -191,10 +194,10 @@ class PluginTest { maven { url 'https://maven.google.com/' } + jcenter() } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" - classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' } } @@ -205,7 +208,7 @@ class PluginTest { project.apply plugin: 'com.android.application' project.apply plugin: 'realm-android' - repositories { + project.repositories { google() } @@ -220,11 +223,11 @@ class PluginTest { project.evaluate() - assertTrue(project.buildscript.repositories.size() == 1) - assertTrue(project.buildscript.repositories.first().url.host == 'maven.google.com') + assertEquals(2, project.buildscript.repositories.size()) + assertEquals('maven.google.com', project.buildscript.repositories.first().url.host) - assertTrue(project.repositories.size() == 1) - assertTrue(project.repositories.first().url.host == 'dl.google.com') + assertEquals(4, project.repositories.size()) + assertEquals('dl.google.com', project.repositories.last().url.host) } private static boolean containsUrl(RepositoryHandler repositories, String url) { diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 2786752758..9f19d79fde 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 2786752758a63c8d9c77b8caee0a97d9eddb11ca +Subproject commit 9f19d79fde248ba37cef0bd52fe64984f9d71be0 From 889a57fc5bd1ca123b27ee754400be67402230c8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 5 Sep 2019 12:57:08 +0200 Subject: [PATCH 1413/2110] Updated release date --- CHANGELOG.md | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c0767699..427252ca7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ -## 5.15.0(YYYY-MM-DD) +## 5.15.0(2019-09-05) ### Enhancements -* [ObjectServer] Added support for Client Resync which automatically will recover the local Realm in case the server is rolled back. This largely replaces the Client Reset mechanism. Can be configured using `SyncConfiguration.Builder.clientResyncMode()`. (Issue [#6487](https://github.com/realm/realm-java/issues/6487)) +* [ObjectServer] Added support for Client Resync for fully synchronized Realms which automatically will recover the local Realm in case the server is rolled back. This largely replaces the Client Reset mechanism. Can be configured using `SyncConfiguration.Builder.clientResyncMode()`. (Issue [#6487](https://github.com/realm/realm-java/issues/6487)) ### Fixed * Huawei devices reporting `Permission denied` when opening a Realm file after an app upgrade or factory reset. This does not automatically fix already existing Realm files. See [this FAQ entry](https://realm.io/docs/java/latest/#huawei-permission-denied) for more details. (Issue [#5715](https://github.com/realm/realm-java/issues/5715)) +* `Realm.copyToRealm()` and `Realm.insertOrUpdate()` crashed on model classes if `@LinkingObjects` was used to target a field with a re-defined internal name in the parent class (e.g. by using `@RealmField`). (Issue [#6581](https://github.com/realm/realm-java/issues/6581)) ### Compatibility * Realm Object Server: 3.21.0 or later. @@ -18,23 +19,6 @@ * Updated to Realm Core 5.23.2. -## 5.14.1(YYYY-MM-DD) - -### Enhancements -* None. - -### Fixed -* `Realm.copyToRealm()` and `Realm.insertOrUpdate()` crashed on model classes if `@LinkingObjects` was used to target a field with a re-defined internal name in the parent class (e.g. by using `@RealmField`). (Issue [#6581](https://github.com/realm/realm-java/issues/6581)) - -### Compatibility -* Realm Object Server: 3.21.0 or later. -* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) -* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. - -### Internal -* None. - - ## 5.14.0(2019-08-12) ### Deprecated From a4afa55eb122999dfe8497a33433e5a9a0245637 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 5 Sep 2019 12:58:01 +0200 Subject: [PATCH 1414/2110] Release v5.15.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7d9938c921..e0cb00460a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.15.0-SNAPSHOT \ No newline at end of file +5.15.0 \ No newline at end of file From 7805374b0631ad1aa9aa3eaba6a5ec00f43a6c5a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 5 Sep 2019 12:58:01 +0200 Subject: [PATCH 1415/2110] Prepare next release v5.15.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e0cb00460a..aa366adb46 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.15.0 \ No newline at end of file +5.15.1-SNAPSHOT \ No newline at end of file From 61c38bdaec9d8f518b998483a06f375dd76ce55d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 5 Sep 2019 15:07:34 +0200 Subject: [PATCH 1416/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index aa366adb46..4f9633e8a5 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.15.1-SNAPSHOT \ No newline at end of file +5.16.0-SNAPSHOT \ No newline at end of file From 44a9c882392b88fa76868716f9d720bc3a76becf Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 9 Sep 2019 10:45:50 +0200 Subject: [PATCH 1417/2110] Fix support for flatDirs (#6612) --- CHANGELOG.md | 17 +++++++ .../main/groovy/io/realm/gradle/Realm.groovy | 2 +- .../groovy/io/realm/gradle/PluginTest.groovy | 48 +++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 427252ca7d..b0e3fdf20c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 5.15.1(2019-09-09) + +### Enhancements +* None. + +### Fixed +* Projects with `flatDirs` repositories defined crashed the build with `MissingPropertyException`. (Issue [#6610](https://github.com/realm/realm-java/issues/6610), since 5.15.0). + +### Compatibility +* Realm Object Server: 3.21.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* None. + + ## 5.15.0(2019-09-05) ### Enhancements diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index f1eff15f9c..41398bfdc5 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -76,7 +76,7 @@ class Realm implements Plugin { // The Android Gradle Plugin automatically adds the local maven repository // found in the Android SDK, so we need to filter that out. if (project.repositories.findAll { - def url = it.url.toString() + def url = (it.hasProperty("url")) ? it.url.toString() : "" if (url.endsWith('/')) { url = url.substring(0, url.length() - 1) } diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index cfe500208a..3ced4b57ed 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -187,6 +187,54 @@ class PluginTest { assertEquals('dl.google.com', project.repositories.last().url.host) } + // Test for https://github.com/realm/realm-java/issues/6610 + @Test + void pluginAddsRightRepositories_withFlatDirs() { + project.buildscript { + repositories { + jcenter() + maven { + url 'https://maven.google.com/' + } + } + dependencies { + classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" + } + } + + project.repositories { + flatDir { + dirs 'libs' + } + google() + } + + def manifest = project.file("src/main/AndroidManifest.xml") + manifest.parentFile.mkdirs() + manifest.text = '' + + project.apply plugin: 'com.android.application' + project.apply plugin: 'realm-android' + + project.android { + compileSdkVersion 27 + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 27 + } + } + + project.evaluate() + + assertEquals(2, project.buildscript.repositories.size()) + assertEquals('maven.google.com', project.buildscript.repositories.last().url.host) + + assertEquals(5, project.repositories.size()) + assertEquals('dl.google.com', project.repositories.last().url.host) + } + + @Test void pluginAddsRightRepositories_withRepositoriesSetAfterPluginIsApplied() { project.buildscript { From 25e838af836db25645838fa28300880d52339455 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 9 Sep 2019 10:53:11 +0200 Subject: [PATCH 1418/2110] Release v5.15.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index aa366adb46..1a1f2cf53f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.15.1-SNAPSHOT \ No newline at end of file +5.15.1 \ No newline at end of file From 521f2fff5b1a8f4278e25ae8fd6ae2f5d442d0c6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 9 Sep 2019 10:53:11 +0200 Subject: [PATCH 1419/2110] Prepare next release v5.15.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 1a1f2cf53f..116697322f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.15.1 \ No newline at end of file +5.15.2-SNAPSHOT \ No newline at end of file From ebd5044c6bd9c2002b1fbb8f3e5af4b77cb3d48b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 13 Sep 2019 12:36:34 +0200 Subject: [PATCH 1420/2110] Make setList Javadoc more accurate. (#6617) --- .../src/main/java/io/realm/DynamicRealmObject.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 85fbbaecdf..c4e79b583d 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -755,6 +755,10 @@ public void setObject(String fieldName, @Nullable DynamicRealmObject value) { /** * Sets the reference to a {@link RealmList} on the given field. + *

            + * This will copy all the elements in the list into Realm, but any further changes to the list + * will not be reflected in the Realm. Use {@link #getList(String)} in order to get a reference to + * the managed list. * * @param fieldName field name. * @param list list of objects. Must either be primitive types or {@link DynamicRealmObject}s. From ad900696fe02a3f3c3e26151c5bd19e479b3f415 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 30 Sep 2019 12:20:55 +0200 Subject: [PATCH 1421/2110] IllegalStateException when opening old synchronized Realm (#6621) --- CHANGELOG.md | 22 ++++++++++++ dependencies.list | 4 +-- .../assets/optionalsubscriptionfields.realm | Bin 0 -> 28672 bytes .../java/io/realm/SyncedRealmTests.java | 32 ++++++++++++++++++ .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../main/java/io/realm/sync/Subscription.java | 2 -- .../rule/TestRealmConfigurationFactory.java | 6 ++-- tools/sync_test_server/Dockerfile | 2 +- .../integration-test-command-server.js | 2 +- 10 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/assets/optionalsubscriptionfields.realm diff --git a/CHANGELOG.md b/CHANGELOG.md index b0e3fdf20c..3bd32d6bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## 5.15.2(YYYY-MM-DD) + +### Enhancements +* None. + +### Fixed +* `null` values were not printed correctly when using `RealmResults.asJSON()` (Realm Core Issue [#3399](https://github.com/realm/realm-core/pull/3399)) +* [ObjectServer] Queries with nullable `Date`'s did not serialize correctly. Only relevant if using Query-based Synchronization. (Realm Core issue [#3388](https://github.com/realm/realm-core/pull/3388)) +* [ObjectServer] Fixed crash with `java.lang.IllegalStateException: The following changes cannot be made in additive-only schema mode` when opening an old Realm created between Realm Java 5.10.0 and Realm Java 5.13.0. (Issue [#6619](https://github.com/realm/realm-java/issues/6619), since 5.13.0). + +### Compatibility +* Realm Object Server: 3.21.0 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 5.x.y series. + +### Internal +* Updated to Object Store commit: 8416010e4be5e32ba552ff3fb29e500f3102d3db. +* Updated to Realm Sync 4.7.8. +* Updated to Realm Core 5.23.5. +* Updated Docker image used on CI to Node 10. + + ## 5.15.1(2019-09-09) ### Enhancements diff --git a/dependencies.list b/dependencies.list index 1255617b1f..eda9336372 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=4.7.4 -REALM_SYNC_SHA256=a7ec9b32a137760c317df15279cc33ffdb63b8663e9ce789a345628afb8424a7 +REALM_SYNC_VERSION=4.7.8 +REALM_SYNC_SHA256=d7453f2296e23fd29a9c7f6fd1225e6905bbc05b8c24d4f45308ad5dd8918f03 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/androidTestObjectServer/assets/optionalsubscriptionfields.realm b/realm/realm-library/src/androidTestObjectServer/assets/optionalsubscriptionfields.realm new file mode 100644 index 0000000000000000000000000000000000000000..3868f2f68d2f32cf36003224355963987d30cda2 GIT binary patch literal 28672 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 ZpuvF#2O1n`aG=3~1_v4(_ Date: Mon, 30 Sep 2019 12:23:02 +0200 Subject: [PATCH 1422/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bd32d6bc6..ba996c2271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 5.15.2(YYYY-MM-DD) +## 5.15.2(2019-09-30) ### Enhancements * None. From 7a31cbe8a76c90fb88a67256bfc5e852514c5029 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 30 Sep 2019 12:23:19 +0200 Subject: [PATCH 1423/2110] Release v5.15.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 116697322f..57ba4a811b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.15.2-SNAPSHOT \ No newline at end of file +5.15.2 \ No newline at end of file From 8967f6a2aaaa8260c4f14ab8c97c745422ac7cc2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 30 Sep 2019 12:23:19 +0200 Subject: [PATCH 1424/2110] Prepare next release v5.15.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 57ba4a811b..91932211fa 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.15.2 \ No newline at end of file +5.15.3-SNAPSHOT \ No newline at end of file From 4fbdcfcf845442150a7d688c50634dcaeb10d24a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 30 Sep 2019 12:37:17 +0200 Subject: [PATCH 1425/2110] Replace Permissions with REST API (#6615) --- CHANGELOG.md | 21 +- dependencies.list | 2 +- .../io/realm/AuthenticateRequestTests.java | 6 +- .../java/io/realm/SyncUserTests.java | 63 +- .../java/io/realm/PermissionManager.java | 1426 ----------------- .../java/io/realm/SyncManager.java | 10 +- .../java/io/realm/SyncSession.java | 10 +- .../objectServer/java/io/realm/SyncUser.java | 386 ++++- .../AcceptPermissionsOfferResponse.java | 90 ++ .../network/ApplyPermissionsRequest.java | 71 + .../network/ApplyPermissionsResponse.java | 75 + .../network/GetPermissionsOffersResponse.java | 108 ++ .../InvalidatePermissionsOfferResponse.java | 79 + .../network/MakePermissionsOfferRequest.java | 46 + .../network/MakePermissionsOfferResponse.java | 102 ++ ...rver.java => OkHttpRealmObjectServer.java} | 114 +- ...tionServer.java => RealmObjectServer.java} | 33 +- .../network/RetrievePermissionsResponse.java | 108 ++ .../permissions/BasePermissionApi.java | 67 - .../permissions/ManagementModule.java | 24 - .../permissions/PermissionChange.java | 194 --- .../permissions/PermissionModule.java | 24 - .../permissions/PermissionOfferResponse.java | 100 +- .../io/realm/permissions/AccessLevel.java | 29 +- .../java/io/realm/permissions/Permission.java | 89 +- .../io/realm/permissions/PermissionOffer.java | 162 +- .../realm/permissions/PermissionRequest.java | 12 +- .../io/realm/permissions/UserCondition.java | 5 +- .../io/realm/PathLevelPermissionsTests.java | 507 ++++++ .../java/io/realm/PermissionManagerTests.java | 1241 -------------- tools/sync_test_server/Dockerfile | 2 +- .../integration-test-command-server.js | 3 +- version.txt | 2 +- 33 files changed, 1896 insertions(+), 3315 deletions(-) delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/AcceptPermissionsOfferResponse.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsRequest.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsResponse.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/GetPermissionsOffersResponse.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/InvalidatePermissionsOfferResponse.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferRequest.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferResponse.java rename realm/realm-library/src/objectServer/java/io/realm/internal/network/{OkHttpAuthenticationServer.java => OkHttpRealmObjectServer.java} (73%) rename realm/realm-library/src/objectServer/java/io/realm/internal/network/{AuthenticationServer.java => RealmObjectServer.java} (80%) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/RetrievePermissionsResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/permissions/BasePermissionApi.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/permissions/ManagementModule.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionChange.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionModule.java create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b0e3fdf20c..805dbba7fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## 6.0.0(YYYY-MM-DD) + +### Breaking Changes +* [ObjectServer] The `PermissionManager` is no longer backed by Realms but instead a REST API. This means that the `PermissionManager` class has been removed and all methods have been moved to `SyncUser`. Some method names have been renamed slightly and return values for methods have changed from `RealmResults` to `List`. This should only have an impact if change listeners were used to listen for changes. In these cases, you must now manually retry the request. + +### Enhancements +None. + +### Fixed +None. + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. + +### Internal +* [ObjectServer] The OKHttp client will now follow redirects from the Realm Object Server. + + ## 5.15.1(2019-09-09) ### Enhancements @@ -14,7 +34,6 @@ ### Internal * None. - ## 5.15.0(2019-09-05) ### Enhancements diff --git a/dependencies.list b/dependencies.list index 1255617b1f..5bfc54fe96 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=a7ec9b32a137760c317df15279cc33ffdb63b8663e9ce789a345628afb8424 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.21.0-rc1 +REALM_OBJECT_SERVER_VERSION=3.23.1 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.3.2 diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index 492acda0ea..cccf23178f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -16,7 +16,7 @@ import java.net.URL; import io.realm.internal.network.AuthenticateRequest; -import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.RealmObjectServer; import io.realm.internal.objectserver.Token; import static org.junit.Assert.assertEquals; @@ -71,8 +71,8 @@ public void userRefresh() throws URISyntaxException, JSONException { @Test public void errorsNotWrapped() { - AuthenticationServer originalAuthServer = SyncManager.getAuthServer(); - AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + RealmObjectServer originalAuthServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createErrorResponse(ErrorCode.ACCESS_DENIED)); SyncManager.setAuthServerImpl(authServer); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 8388761106..4e6a04fa53 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -22,7 +22,6 @@ import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -46,7 +45,7 @@ import io.realm.entities.AllTypesModelModule; import io.realm.entities.StringOnly; import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.RealmObjectServer; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; import io.realm.objectserver.utils.StringOnlyModule; @@ -54,7 +53,6 @@ import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import static io.realm.SyncTestUtils.createNamedTestUser; import static io.realm.SyncTestUtils.createTestAdminUser; import static io.realm.SyncTestUtils.createTestUser; import static junit.framework.Assert.assertEquals; @@ -176,8 +174,8 @@ public void currentUser_returnsNullIfUserExpired() { @Test public void currentUser_throwsIfMultipleUsersLoggedIn() { - AuthenticationServer originalAuthServer = SyncManager.getAuthServer(); - AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + RealmObjectServer originalAuthServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); SyncManager.setAuthServerImpl(authServer); try { @@ -269,7 +267,7 @@ public void isAdmin_allUsers() { @Ignore("This test fails because of wrong JSON string.") @Test public void currentUser_returnsUserAfterLogin() { - AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(Long.MAX_VALUE)); SyncUser user = SyncUser.logIn(SyncCredentials.facebook("foo"), "http://bar.com/auth"); @@ -286,9 +284,9 @@ public void toString_returnDescription() { // Test that a login with an access token logs the user in directly without touching the network @Test public void login_withAccessToken() { - AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); + RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenThrow(new AssertionError("Server contacted.")); - AuthenticationServer originalServer = SyncManager.getAuthServer(); + RealmObjectServer originalServer = SyncManager.getAuthServer(); SyncManager.setAuthServerImpl(authServer); try { SyncCredentials credentials = SyncCredentials.accessToken("foo", "bar"); @@ -302,8 +300,8 @@ public void login_withAccessToken() { // Checks that `/auth` is correctly added to any URL without a path @Test public void login_appendAuthSegment() { - AuthenticationServer authServer = Mockito.mock(AuthenticationServer.class); - AuthenticationServer originalServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); + RealmObjectServer originalServer = SyncManager.getAuthServer(); SyncManager.setAuthServerImpl(authServer); String[][] urls = { {"http://ros.realm.io", "http://ros.realm.io/auth"}, @@ -413,51 +411,6 @@ public void changePassword_noneAdminThrows() { user.changePassword("user-id", "new-password"); } - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getPermissionManager_isReferenceCounted() { - SyncUser user = createTestUser(); - PermissionManager pm1 = user.getPermissionManager(); - PermissionManager pm2 = user.getPermissionManager(); - assertTrue(pm1 == pm2); - assertFalse(pm1.isClosed()); - pm1.close(); - assertFalse(pm1.isClosed()); - pm1.close(); - assertTrue(pm1.isClosed()); - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getPermissionManger_instanceUniqueToUser() { - SyncUser user1 = createNamedTestUser("user1"); - SyncUser user2 = createNamedTestUser("user2"); - PermissionManager pm1 = user1.getPermissionManager(); - PermissionManager pm2 = user2.getPermissionManager(); - - try { - assertFalse(pm1 == pm2); - assertFalse(pm1.equals(pm2)); - looperThread.testComplete(); - } finally { - pm1.close(); - pm2.close(); - user1.logOut(); - user2.logOut(); - } - } - - @Test - public void getPermissionManager_throwOnNonLooperThread() { - SyncUser user = createTestUser(); - try { - user.getPermissionManager(); - fail(); - } catch (IllegalStateException e) { - } - } - @Test public void allSessions() { String url1 = "realm://objectserver.realm.io/default"; diff --git a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java b/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java deleted file mode 100644 index 6ffd9ad5c3..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java +++ /dev/null @@ -1,1426 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.os.Handler; - -import java.io.Closeable; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; - -import io.realm.internal.OsRealmConfig; -import io.realm.internal.Util; -import io.realm.internal.permissions.BasePermissionApi; -import io.realm.internal.permissions.ManagementModule; -import io.realm.internal.permissions.PermissionChange; -import io.realm.internal.permissions.PermissionModule; -import io.realm.internal.permissions.PermissionOfferResponse; -import io.realm.log.RealmLog; -import io.realm.permissions.Permission; -import io.realm.permissions.PermissionOffer; -import io.realm.permissions.PermissionRequest; - - -/** - * Helper class for interacting with Realm Object Server permissions for a {@link SyncUser}. - *

            - * Current functionality supported by this class: - *

              - *
            • List users existing permissions.
            • - *
            • List default permissions.
            • - *
            • Modify permissions for a Realm.
            • - *
            • Create a permission offer that can be sent to others.
            • - *
            • Accept permission offers sent by other users.
            • - *
            - *

            - * This class depends on underlying Realms, so all data coming from this class is thread-confined and must be - * closed after use to avoid leaking resources. - * - * @see How to work with Access Controls - */ -public class PermissionManager implements Closeable { - - // Reference counted cache equivalent to how Realm instances work. - private static Map> cache = new HashMap<>(); - - private static class Cache { - public PermissionManager pm = null; - public Integer instanceCounter = Integer.valueOf(0); - } - - private static final Object cacheLock = new Object(); - - /** - * Return a thread confined, reference counted instance of the PermissionManager. - * - * @param syncUser user to create the PermissionManager for. - * @return a thread confined PermissionManager instance for the provided user. - */ - static PermissionManager getInstance(SyncUser syncUser) { - synchronized (cacheLock) { - String userId = syncUser.getIdentity(); - ThreadLocal threadLocalCache = cache.get(userId); - if (threadLocalCache == null) { - threadLocalCache = new ThreadLocal() { - @Override - protected Cache initialValue() { - return new Cache(); - } - }; - cache.put(userId, threadLocalCache); - } - Cache c = threadLocalCache.get(); - if (c.instanceCounter == 0) { - c.pm = new PermissionManager(syncUser); - } - c.instanceCounter++; - return c.pm; - } - } - - private enum RealmType { - DEFAULT_PERMISSION_REALM("__wildcardpermissions", true), - PERMISSION_REALM("__permission", false), - MANAGEMENT_REALM("__management", false); - - private final String name; - private final boolean globalRealm; - - RealmType(String realmName, boolean globalRealm) { - this.name = realmName; - this.globalRealm = globalRealm; - } - - public String getName() { - return name; - } - - public boolean isGlobalRealm() { - return globalRealm; - } - } - - private final SyncUser user; - - // Used to track the lifecycle of the PermissionManager - private RealmAsyncTask managementRealmOpenTask; - private RealmAsyncTask permissionRealmOpenTask; - private RealmAsyncTask defaultPermissionRealmOpenTask; - private boolean openInProgress = false; - private boolean closed; - - private final long threadId; - private Handler handler = new Handler(); - final SyncConfiguration managementRealmConfig; - final SyncConfiguration permissionRealmConfig; - final SyncConfiguration defaultPermissionRealmConfig; - private Realm permissionRealm; - private Realm managementRealm; - private Realm defaultPermissionRealm; - - // Task list used to queue tasks until the underlying Realms are done opening (or failed doing so). - private List delayedTasks = new ArrayList<>(); - - // List of tasks that are being processed. Used to keep strong references for listeners to work. - // The task must remove itself from this list once it either completes - // or fails. - private List activeTasks = new ArrayList<>(); - - // Object Server Errors might be reported on another thread than the one running this PermissionManager - // In order to prevent race conditions, all blocks of code that read/write these errors should do - // so while holding the errorLock - private final Object errorLock = new Object(); - private volatile ObjectServerError permissionRealmError = null; - private volatile ObjectServerError managementRealmError = null; - private volatile ObjectServerError defaultPermissionRealmError = null; - - // A client reset was encountered in one of the Realms. - // This has invalidated the PermissionManager and it must be closed as soon as possible. - // This flag purely used to be able to send a proper error message to users. - private boolean clientReset = false; - - - // Cached result of the permission query. This will be filled, once the first PermissionAsyncTask has loaded - // the result. - private RealmResults userPermissions; - private RealmResults defaultPermissions; - private RealmResults offers; - - /** - * Creates a PermissionManager for the given user. - * - * This class is thread confined, so thread safety is not a concern since all internal - * communication is routed through the original Handler thread. - * - * @param user user to create manager for. - */ - private PermissionManager(SyncUser user) { - this.user = user; - threadId = Thread.currentThread().getId(); - managementRealmConfig = user.createConfiguration(getRealmUrl(RealmType.MANAGEMENT_REALM, user.getAuthenticationUrl())) - .fullSynchronization() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - synchronized (errorLock) { - managementRealmError = error; - } - } - }) - .modules(new ManagementModule()) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - - permissionRealmConfig = user.createConfiguration(getRealmUrl(RealmType.PERMISSION_REALM, user.getAuthenticationUrl())) - .fullSynchronization() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - RealmLog.error("Error in __permission:\n" + error.toString()); - synchronized (errorLock) { - permissionRealmError = error; - } - } - }) - .modules(new PermissionModule()) - .waitForInitialRemoteData() - // .readOnly() Temporarily disabled due to issues with ROS 3.0.0-alpha.X - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - - defaultPermissionRealmConfig = user.createConfiguration(getRealmUrl(RealmType.DEFAULT_PERMISSION_REALM, user.getAuthenticationUrl())) - .fullSynchronization() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - RealmLog.error("Error in __wildcardpermissions:\n" + error.toString()); - synchronized (errorLock) { - defaultPermissionRealmError = error; - } - } - }) - .modules(new PermissionModule()) - .waitForInitialRemoteData() - .readOnly() - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - } - - /** - * Retrieves the list of permissions for all Realms available to this user. - * - * @param callback callback notified when the permissions are ready. The returned {@link RealmResults} is a fully - * live query result, that will be auto-updated like any other {@link RealmResults}. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - */ - public RealmAsyncTask getPermissions(PermissionsCallback callback) { - checkIfValid(); - checkCallbackNotNull(callback); - return addTask(new GetPermissionsAsyncTask(this, callback)); - } - - /** - * NOTE: Moved out of the public API until we know for sure how this is going to work. - * - * Returns default permissions for all Realms. The default permissions are the ones that will be used if no - * user specific permissions is in effect. - * - * @param callback callback notified when the permissions are ready. The returned {@link RealmResults} is a fully - * live query result, that will be auto-updated like any other {@link RealmResults}. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - */ - public RealmAsyncTask getDefaultPermissions(PermissionsCallback callback) { - checkIfValid(); - checkCallbackNotNull(callback); - return addTask(new GetDefaultPermissionsAsyncTask(this, callback)); - } - - /** - * Applies a given set of permissions to a Realm. - *

            - * A {@link PermissionRequest} object encapsulates a description of which users are granted what - * {@link io.realm.permissions.AccessLevel}s for which Realm(s). - *

            - * Once the request is successfully handled, a {@link Permission} entry is created in each user's - * {@link PermissionManager} and can be found using {@link PermissionManager#getPermissions(PermissionsCallback)}. - * - * @param request request object describing which permissions to grant and to what Realm(s). - * @param callback callback when the request either succeeded or failed. - * @return async task representing the request. This can be used to cancel it if needed. - */ - public RealmAsyncTask applyPermissions(PermissionRequest request, ApplyPermissionsCallback callback) { - checkIfValid(); - checkCallbackNotNull(callback); - return addTask(new ApplyPermissionTask(this, request, callback)); - } - - /** - * Makes a permission offer to users. The offer is represented by an offer token and the permission changes - * described in the {@link PermissionOffer} do not take effect until the offer has been accepted by a user - * calling {@link #acceptOffer(String, AcceptOfferCallback)}. - *

            - * A permission offer can be used as a flexible way of sharing Realms with other users that might not be known at the time - * of making the offer as well as enabling sharing across other channels like e-mail. If a specific user should be - * granted access, using {@link #applyPermissions(PermissionRequest, ApplyPermissionsCallback)} will be faster and quicker. - *

            - * An offer can be accepted by multiple users. - * - * @param callback callback to be notified with the offer token once it is ready. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - * @see Permissions description for general - * documentation. - * @see Modifying permissions for a more - * high level description. - */ - public RealmAsyncTask makeOffer(PermissionOffer offer, MakeOfferCallback callback) { - checkIfValid(); - checkCallbackNotNull(callback); - if (offer.isOfferCreated()) { - throw new IllegalStateException("Offer has already been created: " + offer); - } - return addTask(new MakeOfferAsyncTask(this, offer, callback)); - } - - /** - * Accepts a permission offer sent by another user. Once this offer is accepted successfully, the permissions - * described by the token will be granted. - * - * @param offerToken token representing the permission offer. - * @param callback with the permission details that were accepted. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - */ - public RealmAsyncTask acceptOffer(String offerToken, AcceptOfferCallback callback) { - checkIfValid(); - checkCallbackNotNull(callback); - if (Util.isEmptyString(offerToken)) { - throw new IllegalArgumentException("Non-empty 'offerToken' required."); - } - return addTask(new AcceptOfferAsyncTask(this, offerToken, callback)); - } - - /** - * Revokes an existing offer. This will prevent any other users from accepting it. Users that already accepted it, - * will not be affected. Revocation cannot happen until the device has talked to the server. The callback will - * not be notified until this has happened. - * - * @param offerToken token that should be revoked. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - */ - public RealmAsyncTask revokeOffer(String offerToken, RevokeOfferCallback callback) { - checkIfValid(); - checkCallbackNotNull(callback); - return addTask(new RevokeOfferAsyncTask(this, offerToken, callback)); - } - - /** - * Returns the list of offers created by this user. These offers can be revoked again by calling - * {@link #revokeOffer(String, RevokeOfferCallback)} or sent to other users by sending the - * {@link PermissionOffer#getToken()}. - * - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - */ - public RealmAsyncTask getCreatedOffers(OffersCallback callback) { - checkIfValid(); - checkCallbackNotNull(callback); - return addTask(new GetOffersAsyncTask(this, callback)); - } - - // Queue the task if the underlying Realms are not ready yet, otherwise - // start the task by sending it to this thread handler. This is done - // in order to be able to provide the user with a RealmAsyncTask representation - // of the work being done. - private RealmAsyncTask addTask(PermissionManagerTask task) { - if (isReady()) { - activateTask(task); - } else { - delayTask(task); - openRealms(); - } - - return task; - } - - // Park the task until all underlying Realms are ready - private void delayTask(PermissionManagerTask task) { - delayedTasks.add(task); - } - - // Run any tasks that were delayed while the underlying Realms were being opened. - // PRECONDITION: Underlying Realms are no longer in the process of being opened. - private void runDelayedTasks() { - for (PermissionManagerTask delayedTask : delayedTasks) { - activateTask(delayedTask); - } - delayedTasks.clear(); - } - - // Activate a task. All tasks are controlled by the Handler in order to make it asynchronous. - // PRECONDITION: Underlying Realms are no longer in the process of being opened. - private void activateTask(PermissionManagerTask task) { - activeTasks.add(task); - handler.post(task); - } - - // Open all underlying Realms asynchronously. Once they are all ready, all tasks added in the meantime are - // started. Any error will be reported through the `Callback.onError` callback if the Realms failed to open - // correctly. - private void openRealms() { - if (!openInProgress) { - openInProgress = true; - managementRealmOpenTask = Realm.getInstanceAsync(managementRealmConfig, new Realm.Callback() { - @Override - public void onSuccess(Realm realm) { - managementRealm = realm; - managementRealmOpenTask = null; - checkIfRealmsAreOpenedAndRunDelayedTasks(); - } - - @Override - public void onError(Throwable exception) { - synchronized (errorLock) { - managementRealmError = new ObjectServerError(ErrorCode.UNKNOWN, exception); - managementRealmOpenTask = null; - checkIfRealmsAreOpenedAndRunDelayedTasks(); - } - } - }); - permissionRealmOpenTask = Realm.getInstanceAsync(permissionRealmConfig, new Realm.Callback() { - @Override - public void onSuccess(Realm realm) { - permissionRealm = realm; - permissionRealmOpenTask = null; - checkIfRealmsAreOpenedAndRunDelayedTasks(); - } - - @Override - public void onError(Throwable exception) { - synchronized (errorLock) { - permissionRealmError = new ObjectServerError(ErrorCode.UNKNOWN, exception); - permissionRealmOpenTask = null; - checkIfRealmsAreOpenedAndRunDelayedTasks(); - } - } - }); - defaultPermissionRealmOpenTask = Realm.getInstanceAsync(defaultPermissionRealmConfig, new Realm.Callback() { - @Override - public void onSuccess(Realm realm) { - defaultPermissionRealm = realm; - defaultPermissionRealmOpenTask = null; - checkIfRealmsAreOpenedAndRunDelayedTasks(); - } - - @Override - public void onError(Throwable exception) { - synchronized (errorLock) { - defaultPermissionRealmError = new ObjectServerError(ErrorCode.UNKNOWN, exception); - defaultPermissionRealmOpenTask = null; - checkIfRealmsAreOpenedAndRunDelayedTasks(); - } - } - }); - } - } - - private void checkIfRealmsAreOpenedAndRunDelayedTasks() { - synchronized (errorLock) { - if ((permissionRealm != null || permissionRealmError != null) - && (defaultPermissionRealm != null || defaultPermissionRealmError != null) - && (managementRealm != null || managementRealmError != null)) { - openInProgress = false; - runDelayedTasks(); - } - } - } - - private void checkCallbackNotNull(PermissionManagerBaseCallback callback) { - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - } - - private boolean isReady() { - return managementRealm != null && permissionRealm != null && defaultPermissionRealm != null; - } - - private void checkIfValid() { - // Checks if we are in thread that created the PermissionManager. - if (threadId != Thread.currentThread().getId()) { - throw new IllegalStateException("PermissionManager was accessed from the wrong thread. It can only be " + - "accessed on the thread it was created on."); - } - - if (closed) { - throw new IllegalStateException("PermissionManager has been closed. No further actions are possible."); - } - } - - /** - * Closes the PermissionManager as well as any underlying Realms. - * Any active tasks in progress will be canceled. - */ - @Override - public void close() { - checkIfValid(); - - // Multiple instances open, just decrement the reference count - synchronized (cacheLock) { - Cache cache = PermissionManager.cache.get(user.getIdentity()).get(); - if (cache.instanceCounter > 1) { - cache.instanceCounter--; - return; - } - - // Only one instance open. Do a full close - cache.instanceCounter = 0; - cache.pm = null; - } - closed = true; - delayedTasks.clear(); - - // If Realms are still being opened, abort that task - if (managementRealmOpenTask != null) { - managementRealmOpenTask.cancel(); - managementRealmOpenTask = null; - } - if (permissionRealmOpenTask != null) { - permissionRealmOpenTask.cancel(); - permissionRealmOpenTask = null; - } - if (defaultPermissionRealmOpenTask != null) { - defaultPermissionRealmOpenTask.cancel(); - defaultPermissionRealmOpenTask = null; - } - - // If Realms are opened. Close them. - if (managementRealm != null) { - managementRealm.close(); - } - - if (permissionRealm != null) { - permissionRealm.close(); - } - if (defaultPermissionRealm != null) { - defaultPermissionRealm.close(); - } - } - - /** - * Checks if this PermissionManager is closed or not. If it is closed, all methods will report back an error. - * - * @return {@code true} if the PermissionManager is closed, {@code false} if it is still open. - */ - public boolean isClosed() { - // Don't use `checkIfValid()` as it throws because closed might be false. - if (threadId != Thread.currentThread().getId()) { - throw new IllegalStateException("PermissionManager was accessed from the wrong thread. It can only be " + - "accessed on the thread it was created on."); - } - return closed; - } - - @Override - protected void finalize() throws Throwable { - if (!closed) { - RealmLog.warn("PermissionManager was not correctly closed before being finalized."); - } - super.finalize(); - } - - // Creates the URL to the permission/management Realm based on the authentication URL. - private static String getRealmUrl(RealmType type, URL authUrl) { - String scheme = "realm"; - if (authUrl.getProtocol().equalsIgnoreCase("https")) { - scheme = "realms"; - } - try { - String path = (type.isGlobalRealm() ? "/" : "/~/") + type.getName(); - return new URI(scheme, authUrl.getUserInfo(), authUrl.getHost(), authUrl.getPort(), path, null, null).toString(); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Could not create URL to the " + type + " Realm", e); - } - } - - // Task responsible for loading the Permissions result and returning it to the user. - // The Permission result is not considered available until the query has completed. - private class GetPermissionsAsyncTask extends PermissionManagerTask> { - - private final PermissionsCallback callback; - // Prevent permissions from being GC'ed until fully loaded. - private RealmResults loadingPermissions; - - GetPermissionsAsyncTask(PermissionManager permissionManager, PermissionsCallback callback) { - super(permissionManager, callback); - this.callback = callback; - } - - @Override - public void run() { - if (checkAndReportInvalidState()) { return; } - if (userPermissions != null) { - // Permissions already loaded - notifyCallbackWithSuccess(userPermissions); - } else { - // TODO Right now multiple getPermission() calls will result in multiple - // queries being executed. The first one to return will be the one returned - // by all callbacks. - loadingPermissions = permissionRealm.where(Permission.class).findAllAsync(); - loadingPermissions.addChangeListener(new RealmChangeListener >() { - @Override - public void onChange(RealmResults loadedPermissions) { - // Don't report ready until both __permission and __management Realm are there - if (loadedPermissions.size() > 1) { - loadingPermissions.removeChangeListener(this); - loadingPermissions = null; - if (checkAndReportInvalidState()) { return; } - if (userPermissions == null) { - userPermissions = loadedPermissions; - } - notifyCallbackWithSuccess(userPermissions); - } - } - }); - } - } - - void notifyCallbackWithSuccess(RealmResults permissions) { - try { - callback.onSuccess(permissions); - } finally { - activeTasks.remove(this); - } - } - } - - // Task responsible for loading the Default Permissions result and returning it to the user. - // The Permission result is not considered available until the query has completed. - private class GetDefaultPermissionsAsyncTask extends PermissionManagerTask> { - - private final PermissionsCallback callback; - // Prevent permissions from being GC'ed until fully loaded. - private RealmResults loadingPermissions; - - GetDefaultPermissionsAsyncTask(PermissionManager permissionManager, PermissionsCallback callback) { - super(permissionManager, callback); - this.callback = callback; - } - - @Override - public void run() { - if (checkAndReportInvalidState()) { return; } - if (defaultPermissions != null) { - notifyCallbackWithSuccess(defaultPermissions); - } else { - // Start loading permissions. - // TODO Right now multiple getPermission() calls will result in multiple - // queries being executed. The first one to return will be the one returned - // by all callbacks. - loadingPermissions = defaultPermissionRealm.where(Permission.class).findAllAsync(); - loadingPermissions.addChangeListener(new RealmChangeListener >() { - @Override - public void onChange(RealmResults loadedPermissions) { - // Wildcard permissions should contain 1 Realm as the default, namely __wildcardpermissions - if (loadedPermissions.size() > 0) { - loadingPermissions.removeChangeListener(this); - if (checkAndReportInvalidState()) { return; } - if (defaultPermissions == null) { - defaultPermissions = loadedPermissions; - } - notifyCallbackWithSuccess(defaultPermissions); - } - } - }); - } - } - - void notifyCallbackWithSuccess(RealmResults permissions) { - try { - callback.onSuccess(permissions); - } finally { - activeTasks.remove(this); - } - } - } - - // Class encapsulating setting a Permission by writing a PermissionChange and waiting for it to - // be processed. - private class ApplyPermissionTask extends PermissionManagerTask { - - private final PermissionChange unmanagedChangeRequest; - private final ApplyPermissionsCallback callback; - private final String changeRequestId; - private PermissionChange managedChangeRequest; - private RealmAsyncTask transactionTask; - - public ApplyPermissionTask(PermissionManager manager, PermissionRequest request, ApplyPermissionsCallback callback) { - super(manager, callback); - this.unmanagedChangeRequest = PermissionChange.fromRequest(request); - this.changeRequestId = unmanagedChangeRequest.getId(); - this.callback = callback; - } - - @Override - public void run() { - if (checkAndReportInvalidState()) { - return; - } - - // Save PermissionChange object. It will be synchronized to the server where it will be processed. - Realm.Transaction transaction = new Realm.Transaction() { - @Override - public void execute(Realm realm) { - if (checkAndReportInvalidState()) { return; } - realm.insertOrUpdate(unmanagedChangeRequest); - } - }; - - // If the PermissionChange was successfully written to Realm, we need to wait for it to be processed. - // Register a ChangeListener on the object and wait for the proper response code, which can then be - // converted to a proper response to the user. - Realm.Transaction.OnSuccess onSuccess = new Realm.Transaction.OnSuccess() { - @Override - public void onSuccess() { - if (checkAndReportInvalidState()) { return; } - - // Find PermissionChange object we just added - managedChangeRequest = managementRealm.where(PermissionChange.class) - .equalTo("id", changeRequestId) - .findFirstAsync(); - - - // Wait for it to be processed - RealmObject.addChangeListener(managedChangeRequest, new RealmChangeListener() { - @Override - public void onChange(PermissionChange permissionChange) { - if (checkAndReportInvalidState()) { - RealmObject.removeChangeListener(managedChangeRequest, this); - return; - } - handleServerStatusChanges(permissionChange, new Runnable() { - @Override - public void run() { - notifyCallbackWithSuccess(); - } - }); - } - }); - } - }; - - // Critical error: The PermissionChange could not be written to the Realm. - // Report it back to the user. - Realm.Transaction.OnError onError = new Realm.Transaction.OnError() { - @Override - public void onError(Throwable error) { - if (checkAndReportInvalidState()) { return; } - notifyCallbackWithError(new ObjectServerError(ErrorCode.UNKNOWN, error)); - } - }; - - // Run - transactionTask = managementRealm.executeTransactionAsync(transaction, onSuccess, onError); - } - - void notifyCallbackWithSuccess() { - try { - callback.onSuccess(); - } finally { - activeTasks.remove(this); - } - } - - @Override - public void cancel() { - super.cancel(); - if (transactionTask != null) { - cancel(); - } - } - } - - private class MakeOfferAsyncTask extends PermissionManagerTask { - - private final PermissionOffer unmanagedOffer; - private final String offerId; - private final MakeOfferCallback callback; - private PermissionOffer managedOffer; - private RealmAsyncTask transactionTask; - - public MakeOfferAsyncTask(PermissionManager permissionManager, PermissionOffer offer, MakeOfferCallback callback) { - super(permissionManager, callback); - this.unmanagedOffer = offer; - this.offerId = offer.getId(); - this.callback = callback; - } - - @Override - public void run() { - if (checkAndReportInvalidState()) { - return; - } - - // Save PermissionOffer object. It will be synchronized to the server where it will be processed. - Realm.Transaction transaction = new Realm.Transaction() { - @Override - public void execute(Realm realm) { - if (checkAndReportInvalidState()) { return; } - realm.insertOrUpdate(unmanagedOffer); - } - }; - - // If the PermissionOffer was successfully written to Realm, we need to wait for it to be processed. - // Register a ChangeListener on the object and wait for the proper response code, which can then be - // converted to a proper response to the user. - Realm.Transaction.OnSuccess onSuccess = new Realm.Transaction.OnSuccess() { - @Override - public void onSuccess() { - if (checkAndReportInvalidState()) { return; } - - // Find PermissionChange object we just added - // Wait for it to be processed - managedOffer = managementRealm.where(PermissionOffer.class).equalTo("id", offerId).findFirstAsync(); - RealmObject.addChangeListener(managedOffer, new RealmChangeListener() { - @Override - public void onChange(final PermissionOffer permissionOffer) { - if (checkAndReportInvalidState()) { - RealmObject.removeChangeListener(managedOffer, this); - return; - } - handleServerStatusChanges(permissionOffer, new Runnable() { - @Override - public void run() { - notifyCallbackWithSuccess(permissionOffer.getToken()); - } - }); - } - }); - } - }; - - // Critical error: The PermissionChange could not be written to the Realm. - // Report it back to the user. - Realm.Transaction.OnError onError = new Realm.Transaction.OnError() { - @Override - public void onError(Throwable error) { - if (checkAndReportInvalidState()) { return; } - notifyCallbackWithError(new ObjectServerError(ErrorCode.UNKNOWN, error)); - } - }; - - // Run - transactionTask = managementRealm.executeTransactionAsync(transaction, onSuccess, onError); - } - - void notifyCallbackWithSuccess(String token) { - try { - callback.onSuccess(token); - } finally { - activeTasks.remove(this); - } - } - - @Override - public void cancel() { - super.cancel(); - if (transactionTask != null) { - transactionTask.cancel(); - transactionTask = null; - } - } - } - - private class AcceptOfferAsyncTask extends PermissionManagerTask { - - private final PermissionOfferResponse unmanagedResponse; - private final String responseId; - private final AcceptOfferCallback callback; - private PermissionOfferResponse managedResponse; - private RealmAsyncTask transactionTask; - public RealmResults grantedPermissionResults; - - public AcceptOfferAsyncTask(PermissionManager permissionManager, String offerToken, AcceptOfferCallback callback) { - super(permissionManager, callback); - this.unmanagedResponse = new PermissionOfferResponse(offerToken); - this.responseId = unmanagedResponse.getId(); - this.callback = callback; - } - - @Override - public void run() { - if (checkAndReportInvalidState()) { - return; - } - - // Save response object. It will be synchronized to the server where it will be processed. - Realm.Transaction transaction = new Realm.Transaction() { - @Override - public void execute(Realm realm) { - if (checkAndReportInvalidState()) { return; } - realm.insertOrUpdate(unmanagedResponse); - } - }; - - // If the response was successfully written to Realm, we need to wait for it to be processed. - // Register a ChangeListener on the object and wait for the proper response code, which can then be - // converted to a proper response to the user. - Realm.Transaction.OnSuccess onSuccess = new Realm.Transaction.OnSuccess() { - @Override - public void onSuccess() { - if (checkAndReportInvalidState()) { return; } - - // Find PermissionOffer object we just added - // Wait for it to be processed - managedResponse = managementRealm.where(PermissionOfferResponse.class).equalTo("id", responseId).findFirstAsync(); - RealmObject.addChangeListener(managedResponse, new RealmChangeListener() { - @Override - public void onChange(final PermissionOfferResponse response) { - if (checkAndReportInvalidState()) { - RealmObject.removeChangeListener(managedResponse, this); - return; - } - handleServerStatusChanges(response, new Runnable() { - @Override - public void run() { - grantedPermissionResults = permissionRealm.where(Permission.class).equalTo("path", response.getPath()).findAllAsync(); - grantedPermissionResults.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults permissions) { - if (!permissions.isEmpty()) { - grantedPermissionResults.removeChangeListener(this); - //noinspection ConstantConditions - notifyCallbackWithSuccess(managedResponse.getRealmUrl(), permissions.first()); - } - } - }); - } - }); - } - }); - } - }; - - // Critical error: The PermissionChange could not be written to the Realm. - // Report it back to the user. - Realm.Transaction.OnError onError = new Realm.Transaction.OnError() { - @Override - public void onError(Throwable error) { - if (checkAndReportInvalidState()) { return; } - notifyCallbackWithError(new ObjectServerError(ErrorCode.UNKNOWN, error)); - } - }; - - // Run - transactionTask = managementRealm.executeTransactionAsync(transaction, onSuccess, onError); - } - - void notifyCallbackWithSuccess(String url, Permission permission) { - try { - callback.onSuccess(url, permission); - } finally { - activeTasks.remove(this); - } - } - - @Override - public void cancel() { - super.cancel(); - if (transactionTask != null) { - transactionTask.cancel(); - transactionTask = null; - } - } - } - - // Class encapsulating all async tasks exposed by the PermissionManager. - // Made package protected instead of private to facilitate testing - // IMPORTANT: - // - All subclasses are responsible for removing themselves from the activeTaskList when done. - // - All callbacks should start by checking `if (checkAndReportInvalidState()) { return; }` - // This will abort the task if it was canceled or failed. It will also remove the task from the activeTaskList. - abstract static class PermissionManagerTask implements RealmAsyncTask, Runnable { - - private final PermissionManagerBaseCallback callback; - private final PermissionManager permissionManager; - private volatile boolean canceled = false; - private static final String ERROR_MESSAGE_CLIENT_RESET = "The PermissionManager " + - "has been invalidated due to a server conflict. No further tasks can be scheduled. " + - "The app needs to be restarted to allow the PermissionManager to work again."; - - public PermissionManagerTask(PermissionManager permissionManager, PermissionManagerBaseCallback callback) { - this.callback = callback; - this.permissionManager = permissionManager; - } - - @Override - public abstract void run(); - - @Override - public void cancel() { - canceled = true; - } - - @Override - public boolean isCancelled() { - return canceled; - } - - /** - * Checks if we are in a state where we are not allowed to continue executing. - * If an invalid state is encountered, it will be reported to the error callback. - * - * This method will return {@code true} if an invalid state was encountered, {@code false} - * if it looks ok to continue. - - * @return {@code true} if in a invalid state, {@code false} if in a valid one. - */ - protected final boolean checkAndReportInvalidState() { - if (isCancelled()) { - permissionManager.activeTasks.remove(this); - return true; - } - // Closed check need to work around thread confinement - if (permissionManager.closed) { - ObjectServerError error = new ObjectServerError(ErrorCode.UNKNOWN, - new IllegalStateException("PermissionManager has been closed")); - notifyCallbackWithError(error); // This will remove the task from the task list - return true; - } - if (permissionManager.clientReset) { - ObjectServerError error = new ObjectServerError(ErrorCode.CLIENT_RESET, ERROR_MESSAGE_CLIENT_RESET); - notifyCallbackWithError(error); - return true; - } - - // We are juggling three different Realms. If only one fail, expose that error directly. - // Otherwise try to sensible join the three error messages before returning it to the user. - boolean managementErrorHappened; - boolean permissionErrorHappened; - boolean defaultPermissionErrorHappened; - ObjectServerError managementError; - ObjectServerError permissionError; - ObjectServerError defaultPermissionError; - - // Only hold lock while making a safe copy of current error state - synchronized (permissionManager.errorLock) { - - // Check if errors are only intermittent. In that case, just ignore them as - // we expect them to resolve eventually. So no reason to cause extra work for - // users of the PermissionManager. - if (permissionManager.managementRealmError != null && isIntermittentError(permissionManager.managementRealmError)) { - RealmLog.debug("Ignore Management Realm error: " + permissionManager.managementRealmError.toString()); - permissionManager.managementRealmError = null; - } - if (permissionManager.permissionRealmError != null && isIntermittentError(permissionManager.permissionRealmError)) { - RealmLog.debug("Ignore Permission Realm error: " + permissionManager.permissionRealmError.toString()); - permissionManager.permissionRealmError = null; - } - if (permissionManager.defaultPermissionRealmError != null && isIntermittentError(permissionManager.defaultPermissionRealmError)) { - RealmLog.debug("Ignore Default Permission Realm error: " + permissionManager.defaultPermissionRealmError.toString()); - permissionManager.defaultPermissionRealmError = null; - } - - managementErrorHappened = (permissionManager.managementRealmError != null); - permissionErrorHappened = (permissionManager.permissionRealmError != null); - defaultPermissionErrorHappened = (permissionManager.defaultPermissionRealmError != null); - managementError = permissionManager.managementRealmError; - permissionError = permissionManager.permissionRealmError; - defaultPermissionError = permissionManager.defaultPermissionRealmError; - } - - // Everything seems valid - if (!permissionErrorHappened && !managementErrorHappened && !defaultPermissionErrorHappened) { - return false; - } - - // Handle Client Reset if it happened in any of the Realms. - // A Client Reset is a fatal error for the PermissionManager, so all current and - // future tasks will exit as soon as possible after this event happened and report it - // through the error callback. Only action a user can take is to close the - // PermissionManager and re-open it again. Some data might be lost (like permission - // offers not yet processed). This is currently unavoidable. - // TODO: Eventually we might be able to recover the permission manager from this event - // but it will require some serious task management as we would need to do a full - // close, reschedule all tasks, and re-open behind users back. This is out of scope for - // now. - if (managementErrorHappened && managementError instanceof ClientResetRequiredError) { - ClientResetRequiredError cr = (ClientResetRequiredError) managementError; - permissionManager.managementRealm.close(); - cr.executeClientReset(); - permissionManager.clientReset = true; - } - - if (permissionErrorHappened && permissionError instanceof ClientResetRequiredError) { - ClientResetRequiredError cr = (ClientResetRequiredError) permissionError; - permissionManager.permissionRealm.close(); - cr.executeClientReset(); - permissionManager.clientReset = true; - } - - if (defaultPermissionErrorHappened && defaultPermissionError instanceof ClientResetRequiredError) { - ClientResetRequiredError cr = (ClientResetRequiredError) defaultPermissionError; - permissionManager.defaultPermissionRealm.close(); - cr.executeClientReset(); - permissionManager.clientReset = true; - } - - // Handle errors - Map errors = new LinkedHashMap<>(); - if (permissionManager.clientReset) { - errors.put("ClientReset", new ObjectServerError(ErrorCode.CLIENT_RESET, ERROR_MESSAGE_CLIENT_RESET)); - } else { - if (managementErrorHappened) { errors.put("Management Realm", managementError); } - if (permissionErrorHappened) { errors.put("Permission Realm", permissionError); } - if (defaultPermissionErrorHappened) { errors.put("Default Permission Realm", defaultPermissionError); } - } - notifyCallbackWithError(combineRealmErrors(errors)); // This will remove the task from the task list - - return true; - } - - private boolean isIntermittentError(@Nullable ObjectServerError error) { - // Unknown errors normally have a undefined category as well. All serious errors - // should already be covered by known categories, so expect unknown categories - // to be intermittent. - if (error == null || error.getErrorCode() == ErrorCode.UNKNOWN) { - return true; - } - - switch (error.getErrorType()) { - case ErrorCode.Type.CONNECTION: - case ErrorCode.Type.HTTP: - case ErrorCode.Type.MISC: - case ErrorCode.Type.UNKNOWN: - return true; - - case ErrorCode.Type.AUTH: - case ErrorCode.Type.DEPRECATED: - case ErrorCode.Type.JAVA: - case ErrorCode.Type.PROTOCOL: - case ErrorCode.Type.SESSION: - default: - return false; - } - } - - /** - * Handle the status change from ROS and either call error or success callbacks. - */ - protected void handleServerStatusChanges(BasePermissionApi obj, Runnable onSuccessDelegate) { - Integer statusCode = obj.getStatusCode(); - if (statusCode != null) { - RealmObject.removeAllChangeListeners(obj); - if (statusCode > 0) { - ErrorCode errorCode = ErrorCode.fromNativeError(ErrorCode.Type.AUTH, statusCode); - String errorMsg = obj.getStatusMessage(); - ObjectServerError error = new ObjectServerError(errorCode, errorMsg); - notifyCallbackWithError(error); - } else if (statusCode == 0) { - onSuccessDelegate.run(); - } else { - ErrorCode errorCode = ErrorCode.UNKNOWN; - String errorMsg = "Illegal status code: " + statusCode; - ObjectServerError error = new ObjectServerError(errorCode, errorMsg); - notifyCallbackWithError(error); - } - } - } - - protected final void notifyCallbackWithError(ObjectServerError e) { - RealmLog.debug("Error happened in PermissionManager for %s: %s", - permissionManager.user.getIdentity(), e.toString()); - try { - callback.onError(e); - } finally { - permissionManager.activeTasks.remove(this); - } - } - - // Combine error messages. If they have the same ErrorCode, it will be re-used, otherwise - // we are forced to report back UNKNOWN as error code. The real error codes - // will be always part of the exception message. - private ObjectServerError combineRealmErrors(Map errors) { - String errorMsg = combineErrorMessage(errors); - ErrorCode errorCode = combineErrorCodes(errors); - return new ObjectServerError(errorCode, errorMsg); - } - - // Combine the text based error message from two ObjectServerErrrors. - private String combineErrorMessage(Map errors) { - boolean multipleErrors = errors.size() > 1; - StringBuilder errorMsg = new StringBuilder(multipleErrors ? "Multiple errors occurred: " : "Error occurred in Realm: "); - for (Map.Entry entry : errors.entrySet()) { - errorMsg.append('\n'); - errorMsg.append(entry.getKey()); - errorMsg.append('\n'); - errorMsg.append(entry.getValue().toString()); - } - return errorMsg.toString(); - } - - private ErrorCode combineErrorCodes(Map errors) { - ErrorCode finalErrorCode = null; - for (ObjectServerError error : errors.values()) { - ErrorCode errorCode = error.getErrorCode(); - if (finalErrorCode == null) { - finalErrorCode = errorCode; - continue; - } - if (errorCode == finalErrorCode) { - continue; - } - - // Multiple error codes. No good way to report this. - // The real error codes will still be in the error text. - finalErrorCode = ErrorCode.UNKNOWN; - break; - } - return finalErrorCode; - } - - } - - // Task responsible for loading the Permissions result and returning it to the user. - // The Permission result is not considered available until the query has completed. - private class GetOffersAsyncTask extends PermissionManagerTask> { - - private final OffersCallback callback; - // Prevent permissions from being GC'ed until fully loaded. - private RealmResults loadingOffers; - - GetOffersAsyncTask(PermissionManager permissionManager, OffersCallback callback) { - super(permissionManager, callback); - this.callback = callback; - } - - @Override - public void run() { - if (checkAndReportInvalidState()) { return; } - if (offers != null) { - notifyCallbackWithSuccess(offers); - } else { - // We only want offers that have been created. - loadingOffers = managementRealm.where(PermissionOffer.class) - .equalTo("statusCode", 0) - .findAllAsync(); - loadingOffers.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults loadedOffers) { - loadedOffers.removeChangeListener(this); - if (checkAndReportInvalidState()) { return; } - if (offers == null) { - offers = loadedOffers; - } - notifyCallbackWithSuccess(offers); - } - }); - } - } - - void notifyCallbackWithSuccess(RealmResults permissions) { - try { - callback.onSuccess(permissions); - } finally { - activeTasks.remove(this); - } - } - } - - private class RevokeOfferAsyncTask extends PermissionManagerTask { - - private final String offerToken; - private final RevokeOfferCallback callback; - private RealmResults matchingOffers; - - public RevokeOfferAsyncTask(PermissionManager permissionManager, String offerToken, RevokeOfferCallback callback) { - super(permissionManager, callback); - this.offerToken = offerToken; - this.callback = callback; - } - - @Override - public void run() { - if (checkAndReportInvalidState()) { - return; - } - matchingOffers = managementRealm.where(PermissionOffer.class) - .equalTo("token", offerToken) - .findAllAsync(); - matchingOffers.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(final RealmResults offers) { - if (checkAndReportInvalidState()) { return; } - if (!offers.isEmpty()) { - managementRealm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - if (checkAndReportInvalidState()) { return; } - // Make 100% sure the offer is still in the Realm. - // It could have been deleted between querying for it and the - // transaction running. We will still call OnSuccess if the - // offer was removed by someone else. - RealmResults offers = realm.where(PermissionOffer.class) - .equalTo("token", offerToken) - .findAll(); - if (!offers.isEmpty()) { - offers.deleteAllFromRealm(); - } - } - }, new Realm.Transaction.OnSuccess() { - @Override - public void onSuccess() { - // Don't notify user about success before changes have been uploaded to the server. - matchingOffers.removeAllChangeListeners(); - if (checkAndReportInvalidState()) { return; } - final SyncSession session = SyncManager.getSession(managementRealmConfig); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - session.removeProgressListener(this); - handler.post(new Runnable() { - @Override - public void run() { - if (checkAndReportInvalidState()) { return; } - notifyCallbackWithSuccess(); - } - }); - } - } - }); - } - }, new Realm.Transaction.OnError() { - @Override - public void onError(Throwable error) { - matchingOffers.removeAllChangeListeners(); - notifyCallbackWithError(new ObjectServerError(ErrorCode.UNKNOWN, error)); - - } - }); - } - } - }); - } - - void notifyCallbackWithSuccess() { - try { - callback.onSuccess(); - } finally { - activeTasks.remove(this); - } - } - } - - private interface PermissionManagerBaseCallback { - /** - * Called if an error happened while executing the task. The PermissionManager uses different underlying Realms, - * and this error will report errors from all of these Realms combining them as best as possible. - *

            - * This means that if all Realms fail with the same error code, {@link ObjectServerError#getErrorCode()} will - * return that error code. If the underlying Realms fail for different reasons, {@link ErrorCode#UNKNOWN} will - * be returned. {@link ObjectServerError#getErrorMessage()} will always contain the full description of errors - * including the specific error code for each underlying Realm that failed. - * - * @param error error object describing what happened. - */ - void onError(ObjectServerError error); - } - - /** - * Callback used when loading a set of permissions. - */ - public interface PermissionsCallback extends PermissionManagerBaseCallback { - /** - * Called when all known permissions are successfully loaded. - *

            - * These permissions will continue to synchronize with the server in the background. Register a - * {@link RealmChangeListener} to be notified about any further changes. - * - * @param permissions The set of currently known permissions. - */ - void onSuccess(RealmResults permissions); - } - - /** - * Callback used when modifying or creating new permissions. - */ - public interface ApplyPermissionsCallback extends PermissionManagerBaseCallback { - /** - * Called when the permissions where successfully modified. - */ - void onSuccess(); - } - - /** - * Callback used when making a permission offer for other users. - */ - public interface MakeOfferCallback extends PermissionManagerBaseCallback { - /** - * Called when the offer was successfully created. - * - * @param offerToken token representing the offer that can be sent to other users. - */ - void onSuccess(String offerToken); - } - - /** - * Callback used when accepting a permission offer. - */ - public interface AcceptOfferCallback extends PermissionManagerBaseCallback { - /** - * Called when the offer was successfully accepted. This means that this user can now access this Realm. - * - * @param realmUrl The url pointing to the Realm for which the offer was created. - * @param permission The permissions granted. - */ - void onSuccess(String realmUrl, Permission permission); - } - - /** - * Callback used when loading the list of {@link PermissionOffer}'s created by the user. - */ - public interface OffersCallback extends PermissionManagerBaseCallback { - /** - * Called when all known offers are successfully loaded. - *

            - * These offers will continue to synchronize with the server in the background. Register a - * {@link RealmChangeListener} to be notified about any further changes. - * - * @param offers The set of currently known offers. - */ - void onSuccess(RealmResults offers); - } - - /** - * Callback used when revoking an existing offer. - */ - public interface RevokeOfferCallback extends PermissionManagerBaseCallback { - /** - * Called when the offer was successfully revoked successfully modified. - */ - void onSuccess(); - } - -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index eb2c6622aa..38f08495ad 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -46,9 +46,9 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.Keep; import io.realm.internal.Util; -import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.RealmObjectServer; import io.realm.internal.network.NetworkStateReceiver; -import io.realm.internal.network.OkHttpAuthenticationServer; +import io.realm.internal.network.OkHttpRealmObjectServer; import io.realm.log.RealmLog; import okhttp3.internal.tls.OkHostnameVerifier; @@ -128,7 +128,7 @@ public void onError(SyncSession session, ObjectServerError error) { // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. // Right now it just lives and dies together with the process. - private static volatile AuthenticationServer authServer = new OkHttpAuthenticationServer(); + private static volatile RealmObjectServer authServer = new OkHttpRealmObjectServer(); private static volatile UserStore userStore; // Header configuration @@ -466,14 +466,14 @@ static List getAllSessions(SyncUser syncUser) { return allSessions; } - static AuthenticationServer getAuthServer() { + static RealmObjectServer getAuthServer() { return authServer; } /** * Sets the auth server implementation used when validating credentials. */ - static void setAuthServerImpl(AuthenticationServer authServerImpl) { + static void setAuthServerImpl(RealmObjectServer authServerImpl) { authServer = authServerImpl; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index d8686a8ae4..6d0f3a04a7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -45,7 +45,7 @@ import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.RealmObjectServer; import io.realm.internal.network.ExponentialBackoffTask; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.objectserver.Token; @@ -741,7 +741,7 @@ public interface ErrorHandler { } // Return the access token for the Realm this Session is connected to. - String getAccessToken(final AuthenticationServer authServer, String refreshToken) { + String getAccessToken(final RealmObjectServer authServer, String refreshToken) { // check first if there's a valid access_token we can return immediately if (getUser().isRealmAuthenticated(configuration)) { Token accessToken = getUser().getAccessToken(configuration); @@ -773,7 +773,7 @@ String getAccessToken(final AuthenticationServer authServer, String refreshToken } // Authenticate by getting access tokens for the specific Realm - private void authenticateRealm(final AuthenticationServer authServer) { + private void authenticateRealm(final RealmObjectServer authServer) { if (networkRequest != null) { networkRequest.cancel(); } @@ -828,7 +828,7 @@ protected void onError(AuthenticateResponse response) { networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); } - private void scheduleRefreshAccessToken(final AuthenticationServer authServer, long expireDateInMs) { + private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long expireDateInMs) { onGoingAccessTokenQuery.set(true); // calculate the delay time before which we should refresh the access_token, // we adjust to 10 second to proactively refresh the access_token before the session @@ -862,7 +862,7 @@ public void run() { } // Authenticate by getting access tokens for the specific Realm - private void refreshAccessToken(final AuthenticationServer authServer) { + private void refreshAccessToken(final RealmObjectServer authServer) { // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. clearScheduledAccessTokenRefresh(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 8c55cd7138..f40b33f594 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -39,15 +39,24 @@ import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmAsyncTaskImpl; +import io.realm.internal.network.AcceptPermissionsOfferResponse; +import io.realm.internal.network.ApplyPermissionsResponse; import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.network.AuthenticationServer; +import io.realm.internal.network.RealmObjectServer; import io.realm.internal.network.ChangePasswordResponse; import io.realm.internal.network.ExponentialBackoffTask; +import io.realm.internal.network.GetPermissionsOffersResponse; +import io.realm.internal.network.InvalidatePermissionsOfferResponse; import io.realm.internal.network.LogoutResponse; +import io.realm.internal.network.RetrievePermissionsResponse; import io.realm.internal.network.LookupUserIdResponse; +import io.realm.internal.network.MakePermissionsOfferResponse; import io.realm.internal.network.UpdateAccountResponse; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; +import io.realm.permissions.Permission; +import io.realm.permissions.PermissionOffer; +import io.realm.permissions.PermissionRequest; /** * This class represents a user on the Realm Object Server. The credentials are provided by various 3rd party @@ -63,6 +72,7 @@ public class SyncUser { private final String identity; private Token refreshToken; + private final URL baseUrl; private final URL authenticationUrl; // maps all RealmConfiguration and accessToken, using this SyncUser. private final Map realms = new HashMap(); @@ -71,6 +81,12 @@ public class SyncUser { SyncUser(Token refreshToken, URL authenticationUrl) { this.identity = refreshToken.identity(); this.authenticationUrl = authenticationUrl; + try { + this.baseUrl = new URL(authenticationUrl.getProtocol(), authenticationUrl.getHost(), authenticationUrl.getPort(), ""); + } catch (MalformedURLException e) { + // Should never happen + throw new RuntimeException(e); + } this.refreshToken = refreshToken; } @@ -152,7 +168,7 @@ public static SyncUser logIn(final SyncCredentials credentials, final String aut boolean isAdmin = (Boolean) credentials.getUserInfo().get("_isAdmin"); result = AuthenticateResponse.createValidResponseWithUser(userIdentifier, token, isAdmin); } else { - final AuthenticationServer server = SyncManager.getAuthServer(); + final RealmObjectServer server = SyncManager.getAuthServer(); result = server.loginUser(credentials, authUrl); } if (result.isValid()) { @@ -335,7 +351,7 @@ public void logOut() { realms.clear(); // Finally revoke server token. The local user is logged out in any case. - final AuthenticationServer server = SyncManager.getAuthServer(); + final RealmObjectServer server = SyncManager.getAuthServer(); // don't reference directly the refreshToken inside the revoke request // as it may revoke the newly acquired refresh_token final Token refreshTokenToBeRevoked = refreshToken; @@ -376,7 +392,7 @@ public void changePassword(final String newPassword) throws ObjectServerError { if (newPassword == null) { throw new IllegalArgumentException("Not-null 'newPassword' required."); } - AuthenticationServer authServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = SyncManager.getAuthServer(); ChangePasswordResponse response = authServer.changePassword(refreshToken, newPassword, getAuthenticationUrl()); if (!response.isValid()) { throw response.getError(); @@ -414,7 +430,7 @@ public void changePassword(final String userId, final String newPassword) throws throw new IllegalStateException("User need to be admin in order to change another user's password."); } - AuthenticationServer authServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = SyncManager.getAuthServer(); ChangePasswordResponse response = authServer.changePassword(refreshToken, userId, newPassword, getAuthenticationUrl()); if (!response.isValid()) { throw response.getError(); @@ -499,7 +515,7 @@ public static void requestPasswordReset(String email, String authenticationUrl) throw new IllegalArgumentException("Not-null 'email' required."); } URL authUrl = getUrl(authenticationUrl); - AuthenticationServer authServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = SyncManager.getAuthServer(); UpdateAccountResponse response = authServer.requestPasswordReset(email, authUrl); if (!response.isValid()) { throw response.getError(); @@ -564,7 +580,7 @@ public static void completePasswordReset(String resetToken, String newPassword, throw new IllegalArgumentException("Not-null 'newPassword' required."); } URL authUrl = getUrl(authenticationUrl); - AuthenticationServer authServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = SyncManager.getAuthServer(); UpdateAccountResponse response = authServer.completePasswordReset(resetToken, newPassword, authUrl); if (!response.isValid()) { throw response.getError(); @@ -629,7 +645,7 @@ public static void requestEmailConfirmation(String email, String authenticationU throw new IllegalArgumentException("Not-null 'email' required."); } URL authUrl = getUrl(authenticationUrl); - AuthenticationServer authServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = SyncManager.getAuthServer(); UpdateAccountResponse response = authServer.requestEmailConfirmation(email, authUrl); if (!response.isValid()) { throw response.getError(); @@ -690,7 +706,7 @@ public static void confirmEmail(String confirmationToken, String authenticationU throw new IllegalArgumentException("Not-null 'confirmationToken' required."); } URL authUrl = getUrl(authenticationUrl); - AuthenticationServer authServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = SyncManager.getAuthServer(); UpdateAccountResponse response = authServer.confirmEmail(confirmationToken, authUrl); if (!response.isValid()) { throw response.getError(); @@ -761,7 +777,7 @@ public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final throw new IllegalArgumentException("SyncUser needs to be admin in order to lookup other users ID."); } - AuthenticationServer authServer = SyncManager.getAuthServer(); + RealmObjectServer authServer = SyncManager.getAuthServer(); LookupUserIdResponse response = authServer.retrieveUser(refreshToken, provider, providerUserIdentity, getAuthenticationUrl()); if (!response.isValid()) { if (response.getError().getErrorCode() == ErrorCode.UNKNOWN_ACCOUNT) { @@ -928,25 +944,325 @@ private static String getManagementRealmUrl(URL authUrl) { } /** - * Returns an instance of the {@link PermissionManager} for this user that makes it possible to see, modify and create - * permissions related to this users Realms. + * Retrieves the list of permissions granted to this user. The data is fetched directly from + * the Realm Object Server and requires a network connection. + * + * @return the list of permissions granted to this user. + * @throws ObjectServerError if an error happened while trying to retrieve the list of permissions on the Realm Object Server. + * @throws android.os.NetworkOnMainThreadException if called from the UI thread. + */ + public List retrieveGrantedPermissions() { + ObjectServerError error; + try { + final RealmObjectServer server = SyncManager.getAuthServer(); + RetrievePermissionsResponse result = server.getPermissions(refreshToken, baseUrl); + if (result.isValid()) { + return result.getPermissions(); + } else { + error = result.getError(); + } + } catch (Throwable e) { + throw new ObjectServerError(ErrorCode.UNKNOWN, e); + } + throw error; + } + + /** + * Retrieves the list of permissions granted to this user. The data is fetched directly from + * the Realm Object Server and requires a network connection. + * + * @param callback callback notified when list the permissions are ready. + * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. + * + * @throws IllegalStateException if this method is called from a thread without a looper. + */ + public RealmAsyncTask retrieveGrantedPermissionsAsync(Callback> callback) { + checkLooperThread("Asynchronously retrieving permissions is only possible from looper threads."); + checkCallbackNotNull(callback); + return new Request>(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public List run() throws ObjectServerError { + return retrieveGrantedPermissions(); + } + }.start(); + } + + + /** + * Applies a given set of permissions to a Realm. Only a user with {@link io.realm.permissions.AccessLevel#ADMIN} + * privileges to the Realm can use this method. *

            - * Every instance returned by this method must be closed by calling {@link PermissionManager#close()} when it - * no longer is needed. + * A {@link PermissionRequest} object encapsulates a description of which users are granted what + * {@link io.realm.permissions.AccessLevel}s for which Realm(s). *

            - * The {@link PermissionManager} can only be opened from the main tread, calling this method from any other thread - * will throw an {@link IllegalStateException}. + * Once the request is successfully handled, a {@link Permission} entry is created for each + * affected user and can be found by them using {@link #retrieveGrantedPermissions()}. + * + * @param request request object describing which permissions to grant and to what Realm(s). + * @throws ObjectServerError if an error happened while trying to apply the permission changes on the Realm Object Server. + * @throws android.os.NetworkOnMainThreadException if called from the UI thread. + */ + public void applyPermissions(PermissionRequest request) { + ObjectServerError error; + try { + final RealmObjectServer server = SyncManager.getAuthServer(); + ApplyPermissionsResponse result = server.applyPermissions(request, refreshToken, baseUrl); + if (!result.isValid()) { + error = result.getError(); + } else { + return; + } + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.UNKNOWN, e); + } + throw error; + } + + /** + * Applies a given set of permissions to a Realm. Only a user with {@link io.realm.permissions.AccessLevel#ADMIN} + * privileges to the Realm can use this method. + *

            + * A {@link PermissionRequest} object encapsulates a description of which users are granted what + * {@link io.realm.permissions.AccessLevel}s for which Realm(s). + *

            + * Once the request is successfully handled, a {@link Permission} entry is created for each + * affected user and can be found by them using {@link #retrieveGrantedPermissionsAsync(Callback)}. + * + * @param request request object describing which permissions to grant and to what Realm(s). + * @param callback callback when the request either succeeded or failed. + * @return async task representing the request. This can be used to cancel it if needed. + * + * @throws IllegalStateException if this method is called from a thread without a looper. + */ + public RealmAsyncTask applyPermissionsAsync(PermissionRequest request, Callback callback) { + checkLooperThread("Asynchronously updating permissions is only possible from looper threads."); + checkCallbackNotNull(callback); + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + applyPermissions(request); + return null; + } + }.start(); + } + + /** + * Makes a permissions offer to users. The offer is represented by an offer token and the permission changes + * described in the {@link PermissionOffer} do not take effect until the offer has been accepted by a user + * calling {@link #acceptPermissionsOfferAsync(String, Callback)}. + *

            + * A permission offer can be used as a flexible way of sharing Realms with other users that might not be known at the time + * of making the offer as well as enabling sharing across other channels like e-mail. If a specific user should be + * granted access, using {@link #applyPermissionsAsync(PermissionRequest, Callback)} will be faster and quicker. + *

            + * An offer can be accepted by multiple users. + * + * @param offer the object description the kind of permissions that should be offered to other users. + * @return the offer token representing the offer. + * @throws ObjectServerError if an error happened while trying to create the permissions offer. + * @throws android.os.NetworkOnMainThreadException if called from the UI thread. + * @see Permissions description for general + * documentation. + * @see Modifying permissions for a more + * high level description. + */ + public String makePermissionsOffer(PermissionOffer offer) { + ObjectServerError error; + try { + final RealmObjectServer server = SyncManager.getAuthServer(); + MakePermissionsOfferResponse result = server.makeOffer(offer, refreshToken, baseUrl); + if (!result.isValid()) { + error = result.getError(); + } else { + return result.getToken(); + } + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.UNKNOWN, e); + } + throw error; + } + + /** + * Makes a permission offer to users. The offer is represented by an offer token and the permission changes + * described in the {@link PermissionOffer} do not take effect until the offer has been accepted by a user + * calling {@link #acceptPermissionsOfferAsync(String, Callback)}. + *

            + * A permission offer can be used as a flexible way of sharing Realms with other users that might not be known at the time + * of making the offer as well as enabling sharing across other channels like e-mail. If a specific user should be + * granted access, using {@link #applyPermissionsAsync(PermissionRequest, Callback)} will be faster and quicker. + *

            + * An offer can be accepted by multiple users. + * + * @return the path to the Realm affected by this permission. + * @throws android.os.NetworkOnMainThreadException if called from the UI thread. + * @see Permissions description for general + * documentation. + * @see Modifying permissions for a more + * high level description. + * + * @param offer the object description the kind of permissions that should be offered to other users. + * @param callback callback to be notified with the offer token once it is ready. + * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. + * @throws IllegalStateException if this method is called from a Thread without a looper. + * @see Permissions description for general + * documentation. + * @see Modifying permissions for a more + * high level description. + */ + public RealmAsyncTask makePermissionsOfferAsync(PermissionOffer offer, Callback callback) { + checkLooperThread("Asynchronously making an offer is only possible from looper threads."); + checkCallbackNotNull(callback); + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public String run() throws ObjectServerError { + return makePermissionsOffer(offer); + } + }.start(); + } + + /** + * Accepts a permission offer sent by another user. Once this offer is accepted successfully, the permissions + * described by the token will be granted. * - * @throws IllegalStateException if this method is not called from the UI thread. - * @return an instance of the PermissionManager. + * @param offerToken token representing the permission offer. + * @return the path to the Realm affected by the offer. + * @throws ObjectServerError if an error happened while trying to accept the offer. + * @throws android.os.NetworkOnMainThreadException if called from the UI thread. */ - public PermissionManager getPermissionManager() { - if (!new AndroidCapabilities().isMainThread()) { - throw new IllegalStateException("The PermissionManager can only be opened from the main thread."); + public String acceptPermissionsOffer(String offerToken) { + if (Util.isEmptyString(offerToken)) { + throw new IllegalArgumentException("Non-empty 'offerToken' required."); + } + ObjectServerError error; + try { + final RealmObjectServer server = SyncManager.getAuthServer(); + AcceptPermissionsOfferResponse result = server.acceptOffer(offerToken, refreshToken, baseUrl); + if (!result.isValid()) { + error = result.getError(); + } else { + return result.getPath(); + } + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.UNKNOWN, e); } - return PermissionManager.getInstance(this); + throw error; + } + + /** + * Accepts a permission offer sent by another user. Once this offer is accepted successfully, the permissions + * described by the token will be granted. + * + * @param offerToken token representing the permission offer. + * @param callback with the permission details that were accepted. + * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. + * @throws IllegalStateException if this method is called from a thread without a looper. + */ + public RealmAsyncTask acceptPermissionsOfferAsync(String offerToken, Callback callback) { + checkLooperThread("Asynchronously accepting an permissions offer is only possible from looper threads."); + checkCallbackNotNull(callback); + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public String run() throws ObjectServerError { + return acceptPermissionsOffer(offerToken); + } + }.start(); } + /** + * Invalidates an existing offer. This will prevent any other users from accepting it. Users that already accepted it, + * will not be affected. + * + * @param offerToken token that should be invalidated. + * @throws ObjectServerError if an error happened while trying to invalidate the offer on the Realm Object Server. + * @throws android.os.NetworkOnMainThreadException if called from the UI thread. + */ + public void invalidatePermissionsOffer(String offerToken) { + if (Util.isEmptyString(offerToken)) { + throw new IllegalArgumentException("Non-empty 'offerToken' required."); + } + ObjectServerError error; + try { + final RealmObjectServer server = SyncManager.getAuthServer(); + InvalidatePermissionsOfferResponse result = server.invalidateOffer(offerToken, refreshToken, baseUrl); + if (!result.isValid()) { + error = result.getError(); + } else { + return; + } + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.UNKNOWN, e); + } + throw error; + } + + /** + * Invalidates an existing offer. This will prevent any other users from accepting it. Users that already accepted it, + * will not be affected. + * + * @param offerToken token that should be invalidated. + * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. + * @throws IllegalStateException if this method is called from a thread without a looper. + */ + public RealmAsyncTask invalidatePermissionsOfferAsync(String offerToken, SyncUser.Callback callback) { + checkLooperThread("Asynchronously accepting an permissions offer is only possible from looper threads."); + checkCallbackNotNull(callback); + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + invalidatePermissionsOffer(offerToken); + return null; + } + }.start(); + } + + /** + * Returns the list of offers created by this user. These offers can be revoked again by calling + * {@link #invalidatePermissionsOfferAsync(String, Callback)} or sent to other users by sending the + * {@link PermissionOffer#getToken()}. + * + * @return the list of available offers. + * @throws ObjectServerError if an error occured while trying retrieve the list of offers from the Realm Object Server. + * @throws android.os.NetworkOnMainThreadException if called from the UI thread. + */ + public List retrieveCreatedPermissionsOffers() { + ObjectServerError error; + try { + final RealmObjectServer server = SyncManager.getAuthServer(); + GetPermissionsOffersResponse result = server.getPermissionOffers(refreshToken, baseUrl); + if (!result.isValid()) { + error = result.getError(); + } else { + return result.getOffers(); + } + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.UNKNOWN, e); + } + throw error; + } + + + /** + * Returns the list of offers created by this user. These offers can be revoked again by calling + * {@link #invalidatePermissionsOfferAsync(String, Callback)} or sent to other users by sending the + * {@link PermissionOffer#getToken()}. + * + * @param callback that will receive the list of available offers. + * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. + * @throws IllegalStateException if this method is called from a thread without a looper. + */ + public RealmAsyncTask retrieveCreatedPermissionsOffersAsync(Callback> callback) { + checkLooperThread("Asynchronously getting all permission offers is only possible from looper threads."); + checkCallbackNotNull(callback); + return new Request>(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public List run() throws ObjectServerError { + return retrieveCreatedPermissionsOffers(); + } + }.start(); + } + + + // what defines a user is it's identity(Token) and authURL (as required by the constructor) // // not the list of Realms it's managing, furthermore, trying to include the `realms` in the `hashCode` will @@ -979,6 +1295,13 @@ public String toString() { return sb.toString(); } + private void checkCallbackNotNull(Callback callback) { + //noinspection ConstantConditions + if (callback == null) { + throw new IllegalArgumentException("Non-null 'callback' required."); + } + } + // Class wrapping requests made against the auth server. Is also responsible for calling with success/error on the // correct thread. private static abstract class Request { @@ -1042,9 +1365,24 @@ public void run() { } } + /** + * Callback for async methods available to the {@link SyncUser}. + * + * @param Type returned if the request was a success. + */ public interface Callback { - void onSuccess(T result); - + /** + * The request was a success. + * @param t The object representing the successful request. See each method for details. + */ + void onSuccess(T t); + + /** + * The request failed for some reason, either because there was a network error or the Realm + * Object Server returned an error. + * + * @param error the error that was detected. + */ void onError(ObjectServerError error); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AcceptPermissionsOfferResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AcceptPermissionsOfferResponse.java new file mode 100644 index 0000000000..293f7226d3 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AcceptPermissionsOfferResponse.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.log.RealmLog; +import okhttp3.Response; + +/** + * Class wrapping the response from `POST permissions/offers/:token:/accept` + */ +public class AcceptPermissionsOfferResponse extends AuthServerResponse { + + private String path; + + /** + * Helper method for creating the proper lookup user response. This method will set the appropriate error + * depending on any HTTP response codes or I/O errors. + * + * @param response the server response. + * @return the user lookup response. + */ + static AcceptPermissionsOfferResponse from(Response response) { + String serverResponse; + try { + serverResponse = response.body().string(); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new AcceptPermissionsOfferResponse(error); + } + if (!response.isSuccessful()) { + return new AcceptPermissionsOfferResponse(AuthServerResponse.createError(serverResponse, response.code())); + } else { + return new AcceptPermissionsOfferResponse(serverResponse); + } + } + + /** + * Helper method for creating a failed response. + */ + public static AcceptPermissionsOfferResponse from(ObjectServerError objectServerError) { + return new AcceptPermissionsOfferResponse(objectServerError); + } + + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static AcceptPermissionsOfferResponse from(Exception exception) { + return AcceptPermissionsOfferResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + + private AcceptPermissionsOfferResponse(ObjectServerError error) { + RealmLog.debug("AcceptPermissionsOffer - Error: %s", error); + setError(error); + this.error = error; + } + + private AcceptPermissionsOfferResponse(String serverResponse) { + RealmLog.debug("AcceptPermissionsOffer - Success: %s", serverResponse); + try { + JSONObject obj = new JSONObject(serverResponse); + path = obj.getString("path"); + } catch (JSONException e) { + error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); + } + } + + public String getPath() { + return path; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsRequest.java new file mode 100644 index 0000000000..4c5923a260 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsRequest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import io.realm.permissions.AccessLevel; +import io.realm.permissions.PermissionRequest; +import io.realm.permissions.UserCondition; + +/** + * Class wrapping a request for updating/setting permissions `POST permissions/apply` + */ +public class ApplyPermissionsRequest { + + private final AccessLevel level; + private final String realmUrl; + private final String userId; + private final String metadataKey; + private final String metadataValue; + + public ApplyPermissionsRequest(PermissionRequest request) { + UserCondition condition = request.getCondition(); + level = request.getAccessLevel(); + realmUrl = request.getUrl(); + + switch (condition.getType()) { + case USER_ID: + userId = condition.getValue(); + metadataKey = null; + metadataValue = null; + break; + case METADATA: + userId = null; + metadataKey = condition.getKey(); + metadataValue = condition.getValue(); + break; + default: + throw new IllegalArgumentException("Unsupported type: " + condition.getType()); + } + } + + public String toJson() throws JSONException { + JSONObject request = new JSONObject(); + request.put("realmPath", realmUrl); + request.put("accessLevel", level.getKey()); + JSONObject condition = new JSONObject(); + if (userId != null) { + condition.put("userId", userId); + } else { + condition.put("metadataKey", metadataKey); + condition.put("metadataValue", metadataValue); + } + request.put("condition", condition); + return request.toString(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsResponse.java new file mode 100644 index 0000000000..70e150815a --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsResponse.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import java.io.IOException; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.log.RealmLog; +import okhttp3.Response; + +/** + * Class wrapping the response from `POST permissions/apply` + */ +public class ApplyPermissionsResponse extends AuthServerResponse { + + /** + * Helper method for creating the proper lookup user response. This method will set the appropriate error + * depending on any HTTP response codes or I/O errors. + * + * @param response the server response. + * @return the user lookup response. + */ + static ApplyPermissionsResponse from(Response response) { + String serverResponse; + try { + serverResponse = response.body().string(); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new ApplyPermissionsResponse(error); + } + if (!response.isSuccessful()) { + return new ApplyPermissionsResponse(AuthServerResponse.createError(serverResponse, response.code())); + } else { + return new ApplyPermissionsResponse(serverResponse); + } + } + + /** + * Helper method for creating a failed response. + */ + public static ApplyPermissionsResponse from(ObjectServerError objectServerError) { + return new ApplyPermissionsResponse(objectServerError); + } + + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static ApplyPermissionsResponse from(Exception exception) { + return ApplyPermissionsResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + + private ApplyPermissionsResponse(ObjectServerError error) { + RealmLog.debug("ApplyPermissions - Error: %s", error); + setError(error); + this.error = error; + } + + private ApplyPermissionsResponse(String serverResponse) { + RealmLog.debug("ApplyPermissions - Success: %s", serverResponse); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/GetPermissionsOffersResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/GetPermissionsOffersResponse.java new file mode 100644 index 0000000000..73df15990e --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/GetPermissionsOffersResponse.java @@ -0,0 +1,108 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.internal.android.JsonUtils; +import io.realm.log.RealmLog; +import io.realm.permissions.AccessLevel; +import io.realm.permissions.Permission; +import io.realm.permissions.PermissionOffer; +import okhttp3.Response; + +/** + * Class wrapping the response from `GET permissions/offers` + */ +public class GetPermissionsOffersResponse extends AuthServerResponse { + + private final List offers = new ArrayList<>(); + + /** + * Helper method for creating the proper lookup user response. This method will set the appropriate error + * depending on any HTTP response codes or I/O errors. + * + * @param response the server response. + * @return the user lookup response. + */ + static GetPermissionsOffersResponse from(Response response) { + String serverResponse; + try { + serverResponse = response.body().string(); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new GetPermissionsOffersResponse(error); + } + if (!response.isSuccessful()) { + return new GetPermissionsOffersResponse(AuthServerResponse.createError(serverResponse, response.code())); + } else { + return new GetPermissionsOffersResponse(serverResponse); + } + } + + /** + * Helper method for creating a failed response. + */ + public static GetPermissionsOffersResponse from(ObjectServerError objectServerError) { + return new GetPermissionsOffersResponse(objectServerError); + } + + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static GetPermissionsOffersResponse from(Exception exception) { + return GetPermissionsOffersResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + + private GetPermissionsOffersResponse(ObjectServerError error) { + RealmLog.debug("GetPermissionOffers - Error: %s", error); + setError(error); + this.error = error; + } + + private GetPermissionsOffersResponse(String serverResponse) { + RealmLog.debug("GetPermissionOffers - Success: %s", serverResponse); + try { + JSONObject responseObject = new JSONObject(serverResponse); + JSONArray responseOffersList = responseObject.getJSONArray("offers"); + for (int i = 0; i < responseOffersList.length(); i++) { + JSONObject obj = responseOffersList.getJSONObject(i); + String path = obj.getString("realmPath"); + Date expiresAt = obj.isNull("expiresAt") ? null : JsonUtils.stringToDate(obj.getString("expiresAt")); + AccessLevel accessLevel = AccessLevel.fromKey(obj.getString("accessLevel")); + Date createdAt = JsonUtils.stringToDate(obj.getString("createdAt")); + String userId = obj.getString("userId"); + String token = obj.getString("token"); + offers.add(new PermissionOffer(path, accessLevel, expiresAt, createdAt, userId, token)); + } + } catch (JSONException e) { + error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); + } + } + + public List getOffers() { + return offers; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/InvalidatePermissionsOfferResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/InvalidatePermissionsOfferResponse.java new file mode 100644 index 0000000000..f676531891 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/InvalidatePermissionsOfferResponse.java @@ -0,0 +1,79 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.log.RealmLog; +import io.realm.permissions.Permission; +import okhttp3.Response; + +/** + * Class wrapping the response from `DELETE /permissions/offers/:token:` + */ +public class InvalidatePermissionsOfferResponse extends AuthServerResponse { + + /** + * Helper method for creating the proper response. This method will set the appropriate error + * depending on any HTTP response codes or I/O errors. + * + * @param response the server response. + * @return the user lookup response. + */ + static InvalidatePermissionsOfferResponse from(Response response) { + String serverResponse; + try { + serverResponse = response.body().string(); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new InvalidatePermissionsOfferResponse(error); + } + if (!response.isSuccessful()) { + return new InvalidatePermissionsOfferResponse(AuthServerResponse.createError(serverResponse, response.code())); + } else { + return new InvalidatePermissionsOfferResponse(serverResponse); + } + } + + /** + * Helper method for creating a failed response. + */ + public static InvalidatePermissionsOfferResponse from(ObjectServerError objectServerError) { + return new InvalidatePermissionsOfferResponse(objectServerError); + } + + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static InvalidatePermissionsOfferResponse from(Exception exception) { + return InvalidatePermissionsOfferResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + + private InvalidatePermissionsOfferResponse(ObjectServerError error) { + RealmLog.debug("InvalidatePermissionOffer - Error: %s", error); + setError(error); + this.error = error; + } + + private InvalidatePermissionsOfferResponse(String serverResponse) { + RealmLog.debug("InvalidatePermissionOffer - Success: %s", serverResponse); + // No data to store + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferRequest.java new file mode 100644 index 0000000000..c4d60995bd --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferRequest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Date; + +import io.realm.permissions.PermissionOffer; + +/** + * Class wrapping request to `POST /auth/permissions/offers` + */ +public class MakePermissionsOfferRequest { + + private final PermissionOffer offer; + + public MakePermissionsOfferRequest(PermissionOffer offer) { + this.offer = offer; + } + + public String toJson() throws JSONException { + JSONObject request = new JSONObject(); + Date expires = offer.getExpiresAt(); + if (expires != null) { + request.put("expiresAt", expires.toString()); + } + request.put("realmPath", offer.getRealmUrl()); + request.put("accessLevel", offer.getAccessLevel().getKey()); + return request.toString(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferResponse.java new file mode 100644 index 0000000000..aed7a4a43f --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferResponse.java @@ -0,0 +1,102 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Date; + +import javax.annotation.Nonnull; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.internal.android.JsonUtils; +import io.realm.internal.permissions.PermissionOfferResponse; +import io.realm.log.RealmLog; +import io.realm.permissions.AccessLevel; +import okhttp3.Response; + +/** + * Class wrapping the response from `POST permissions/offers` + */ +public class MakePermissionsOfferResponse extends AuthServerResponse { + + private PermissionOfferResponse response; + + /** + * Helper method for creating the proper lookup user response. This method will set the appropriate error + * depending on any HTTP response codes or I/O errors. + * + * @param response the server response. + * @return the user lookup response. + */ + static MakePermissionsOfferResponse from(Response response) { + String serverResponse; + try { + serverResponse = response.body().string(); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new MakePermissionsOfferResponse(error); + } + if (!response.isSuccessful()) { + return new MakePermissionsOfferResponse(AuthServerResponse.createError(serverResponse, response.code())); + } else { + return new MakePermissionsOfferResponse(serverResponse); + } + } + + /** + * Helper method for creating a failed response. + */ + public static MakePermissionsOfferResponse from(ObjectServerError objectServerError) { + return new MakePermissionsOfferResponse(objectServerError); + } + + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static MakePermissionsOfferResponse from(Exception exception) { + return MakePermissionsOfferResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + + private MakePermissionsOfferResponse(ObjectServerError error) { + RealmLog.debug("MakePermissionsOffer - Error: %s", error); + setError(error); + this.error = error; + } + + private MakePermissionsOfferResponse(String serverResponse) { + RealmLog.debug("MakePermissionsOffer - Success: %s", serverResponse); + try { + JSONObject obj = new JSONObject(serverResponse); + @Nonnull String path = obj.getString("realmPath"); + Date expiresAt = obj.isNull("expiresAt") ? null : JsonUtils.stringToDate(obj.getString("expiresAt")); + AccessLevel accessLevel = AccessLevel.fromKey(obj.getString("accessLevel")); + Date createdAt = JsonUtils.stringToDate(obj.getString("createdAt")); + String userId = obj.getString("userId"); + String token = obj.getString("token"); + response = new PermissionOfferResponse(path, expiresAt, accessLevel, createdAt, userId, token); + } catch (JSONException e) { + error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); + } + } + + public String getToken() { + return response.getToken(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java similarity index 73% rename from realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java index ebc5b87675..d6e2785cf4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpAuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java @@ -16,8 +16,6 @@ package io.realm.internal.network; -import android.util.Log; - import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; @@ -35,6 +33,8 @@ import io.realm.internal.objectserver.Token; import io.realm.log.LogLevel; import io.realm.log.RealmLog; +import io.realm.permissions.PermissionOffer; +import io.realm.permissions.PermissionRequest; import okhttp3.Call; import okhttp3.ConnectionPool; import okhttp3.Interceptor; @@ -45,19 +45,27 @@ import okhttp3.Response; import okio.Buffer; -public class OkHttpAuthenticationServer implements AuthenticationServer { +public class OkHttpRealmObjectServer implements RealmObjectServer { public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final String ACTION_LOGOUT = "revoke"; // Auth end point for logging out users private static final String ACTION_CHANGE_PASSWORD = "password"; // Auth end point for changing passwords private static final String ACTION_LOOKUP_USER_ID = "users/:provider:/:providerId:"; // Auth end point for looking up user id private static final String ACTION_UPDATE_ACCOUNT = "password/updateAccount"; // Password reset and email confirmation + private static final String ACTION_GET_PERMISSIONS = "permissions"; + private static final String ACTION_UPDATE_PERMISSIONS = "permissions/apply"; + private static final String ACTION_OFFER_PERMISSIONS = "permissions/offers"; + private static final String ACTION_ACCEPT_PERMISSIONS_OFFER = "permissions/offers/:token:/accept"; + private static final String ACTION_DELETE_PERMISSIONS_OFFER = "permissions/offers/:token:"; + private static final String ACTION_GET_PERMISSION_OFFERS = "permissions/offers"; + private static final Charset UTF8 = Charset.forName("UTF-8"); private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) + .followRedirects(true) .addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { @@ -89,7 +97,7 @@ public Response intercept(Chain chain) throws IOException { private Map> customHeaders = new LinkedHashMap<>(); private Map customAuthorizationHeaders = new HashMap<>(); - public OkHttpAuthenticationServer() { + public OkHttpRealmObjectServer() { initHeaders(); } @@ -243,6 +251,104 @@ public UpdateAccountResponse confirmEmail(String confirmationToken, URL authenti } } + @Override + public RetrievePermissionsResponse getPermissions(Token userToken, URL baseUrl) { + try { + URL url = buildActionUrl(baseUrl, ACTION_GET_PERMISSIONS); + RealmLog.debug("Network request (retrieveGrantedPermissions): " + url); + Request request = newAuthRequest(url, userToken.value()) + .get() + .build(); + Call call = client.newCall(request); + Response response = call.execute(); + return RetrievePermissionsResponse.from(response); + } catch (Exception e) { + return RetrievePermissionsResponse.from(e); + } + } + + @Override + public ApplyPermissionsResponse applyPermissions(PermissionRequest permissionRequest, Token refreshToken, URL baseUrl) { + try { + URL url = buildActionUrl(baseUrl, ACTION_UPDATE_PERMISSIONS); + RealmLog.debug("Network request (applyPermissions): " + url); + Request request = newAuthRequest(url, refreshToken.value()) + .post(RequestBody.create(JSON, new ApplyPermissionsRequest(permissionRequest).toJson())) + .build(); + Call call = client.newCall(request); + Response response = call.execute(); + return ApplyPermissionsResponse.from(response); + } catch (Exception e) { + return ApplyPermissionsResponse.from(e); + } + } + + @Override + public MakePermissionsOfferResponse makeOffer(PermissionOffer offer, Token refreshToken, URL baseUrl) { + try { + URL url = buildActionUrl(baseUrl, ACTION_OFFER_PERMISSIONS); + RealmLog.debug("Network request (offerPermissions): " + url); + Request request = newAuthRequest(url, refreshToken.value()) + .post(RequestBody.create(JSON, new MakePermissionsOfferRequest(offer).toJson())) + .build(); + Call call = client.newCall(request); + Response response = call.execute(); + return MakePermissionsOfferResponse.from(response); + } catch (Exception e) { + return MakePermissionsOfferResponse.from(e); + } + } + + @Override + public AcceptPermissionsOfferResponse acceptOffer(String offerToken, Token refreshToken, URL baseUrl) { + try { + String action = ACTION_ACCEPT_PERMISSIONS_OFFER.replace(":token:", offerToken); + URL url = buildActionUrl(baseUrl, action); + RealmLog.debug("Network request (acceptPermissionOffer): " + url); + Request request = newAuthRequest(url, refreshToken.value()) + .post(RequestBody.create(JSON, "")) + .build(); + Call call = client.newCall(request); + Response response = call.execute(); + return AcceptPermissionsOfferResponse.from(response); + } catch (Exception e) { + return AcceptPermissionsOfferResponse.from(e); + } + } + + @Override + public InvalidatePermissionsOfferResponse invalidateOffer(String offerToken, Token refreshToken, URL baseUrl) { + try { + String action = ACTION_DELETE_PERMISSIONS_OFFER.replace(":token:", offerToken); + URL url = buildActionUrl(baseUrl, action); + RealmLog.debug("Network request (invalidatePermissionOffer): " + url); + Request request = newAuthRequest(url, refreshToken.value()) + .delete() + .build(); + Call call = client.newCall(request); + Response response = call.execute(); + return InvalidatePermissionsOfferResponse.from(response); + } catch (Exception e) { + return InvalidatePermissionsOfferResponse.from(e); + } + } + + @Override + public GetPermissionsOffersResponse getPermissionOffers(Token refreshToken, URL baseUrl) { + try { + URL url = buildActionUrl(baseUrl, ACTION_GET_PERMISSION_OFFERS); + RealmLog.debug("Network request (GetPermissionsOffers): " + url); + Request request = newAuthRequest(url, refreshToken.value()) + .get() + .build(); + Call call = client.newCall(request); + Response response = call.execute(); + return GetPermissionsOffersResponse.from(response); + } catch (Exception e) { + return GetPermissionsOffersResponse.from(e); + } + } + // Builds the URL for a specific auth endpoint private static URL buildActionUrl(URL authenticationUrl, String action) { final String baseUrlString = authenticationUrl.toExternalForm(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java similarity index 80% rename from realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java index 7c608791dd..c0b3151612 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticationServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java @@ -24,6 +24,8 @@ import io.realm.SyncCredentials; import io.realm.SyncUser; import io.realm.internal.objectserver.Token; +import io.realm.permissions.PermissionOffer; +import io.realm.permissions.PermissionRequest; /** * Interface for handling communication with Realm Object Servers. @@ -31,7 +33,7 @@ * Note, no implementation of this class is responsible for handling retries or error handling. It is * only responsible for executing a given network request. */ -public interface AuthenticationServer { +public interface RealmObjectServer { /** * Overrides the default header name used to send Realm Object Server credentials. @@ -114,4 +116,33 @@ public interface AuthenticationServer { */ UpdateAccountResponse confirmEmail(String confirmationToken, URL authenticationUrl); + /** + * Retrieves a list of all permissions for the given user. + */ + RetrievePermissionsResponse getPermissions(Token userToken, URL baseUrl); + + /** + * Updates a given set of permissions for a single Realm + */ + ApplyPermissionsResponse applyPermissions(PermissionRequest request, Token refreshToken, URL baseUrl); + + /** + * Creates an permissions offer for a Realm. + */ + MakePermissionsOfferResponse makeOffer(PermissionOffer offer, Token refreshToken, URL baseUrl); + + /** + * Accept a given permissions offer. + */ + AcceptPermissionsOfferResponse acceptOffer(String offerToken, Token refreshToken, URL baseUrl); + + /** + * Invalidates an already created permissions offer. + */ + InvalidatePermissionsOfferResponse invalidateOffer(String id, Token refreshToken, URL baseUrl); + + /** + * Retrieves a list of all permissions offers that has been created. + */ + GetPermissionsOffersResponse getPermissionOffers(Token refreshToken, URL baseUrl); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/RetrievePermissionsResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/RetrievePermissionsResponse.java new file mode 100644 index 0000000000..46649b5bfd --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/RetrievePermissionsResponse.java @@ -0,0 +1,108 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm.internal.network; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.internal.android.JsonUtils; +import io.realm.log.RealmLog; +import io.realm.permissions.AccessLevel; +import io.realm.permissions.Permission; +import okhttp3.Response; + +/** + * Class wrapping the response from `GET /permissions` + */ +public class RetrievePermissionsResponse extends AuthServerResponse { + + private final List permissions = new ArrayList<>(); + + /** + * Helper method for creating the proper response. This method will set the appropriate error + * depending on any HTTP response codes or I/O errors. + * + * @param response the server response. + * @return the user lookup response. + */ + static RetrievePermissionsResponse from(Response response) { + String serverResponse; + try { + serverResponse = response.body().string(); + } catch (IOException e) { + ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); + return new RetrievePermissionsResponse(error); + } + if (!response.isSuccessful()) { + return new RetrievePermissionsResponse(AuthServerResponse.createError(serverResponse, response.code())); + } else { + return new RetrievePermissionsResponse(serverResponse); + } + } + + /** + * Helper method for creating a failed response. + */ + public static RetrievePermissionsResponse from(ObjectServerError objectServerError) { + return new RetrievePermissionsResponse(objectServerError); + } + + /** + * Helper method for creating a failed response from an {@link Exception}. + */ + public static RetrievePermissionsResponse from(Exception exception) { + return RetrievePermissionsResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); + } + + private RetrievePermissionsResponse(ObjectServerError error) { + RealmLog.debug("LookupUserIdResponse - Error: %s", error); + setError(error); + this.error = error; + } + + private RetrievePermissionsResponse(String serverResponse) { + RealmLog.debug("RetrievePermissionsResponse - Success: %s", serverResponse); + try { + JSONObject obj = new JSONObject(serverResponse); + JSONArray array = obj.getJSONArray("permissions"); + for (int i = 0; i < array.length(); i++) { + JSONObject permission = array.getJSONObject(i); + String userId = (permission.isNull("userId")) ? null : permission.getString("userId"); + String path = permission.getString("path"); + AccessLevel accessLevel = AccessLevel.fromKey(permission.getString("accessLevel")); + boolean mayRead = accessLevel.mayRead(); + boolean mayWrite = accessLevel.mayWrite(); + boolean mayManage = accessLevel.mayManage(); + Date updatedAt = JsonUtils.stringToDate(permission.getString("updatedAt")); + permissions.add(new Permission(userId, path, accessLevel, mayRead, mayWrite, mayManage, updatedAt)); + } + } catch (JSONException e) { + error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); + } + } + + public List getPermissions() { + return permissions; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/BasePermissionApi.java b/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/BasePermissionApi.java deleted file mode 100644 index ea7e6fc103..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/BasePermissionApi.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.internal.permissions; - -import java.util.Date; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.RealmModel; - - -/** - * Common methods shared between most Realm model classes used in the Permission Realm API. - */ -public interface BasePermissionApi extends RealmModel { - - /** - * Returns the unique id for this object. - * - * @return the unique id for this object. - */ - String getId(); - - /** - * Returns the timestamp on the Client that created this object. - * - * @return {@link Date} this object was created. The timestamp will use the device clock it was created on. - */ - @SuppressFBWarnings("EI_EXPOSE_REP") - Date getCreatedAt(); - - /** - * Returns the timestamp this object was last updated. The timstamp can be both a server timestamp and a device - * timestamp. - * - * @return {@link Date} this object was last modified. - */ - @SuppressFBWarnings("EI_EXPOSE_REP") - Date getUpdatedAt(); - - /** - * Returns the status code for this change. - * - * @return {@code null} if not yet processed. {@code 0} if successful, {@code >0} if an error happened. See {@link #getStatusMessage()}. - */ - Integer getStatusCode(); - - /** - * Returns the servers status message, if an error occurred. Otherwise it will return {@code null}. - * - * @return The servers status message in case of an error, {@code null} otherwise. - */ - String getStatusMessage(); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/ManagementModule.java b/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/ManagementModule.java deleted file mode 100644 index 358330d92b..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/ManagementModule.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.internal.permissions; - -import io.realm.annotations.RealmModule; -import io.realm.permissions.PermissionOffer; - -@RealmModule(library = true, classes = { PermissionChange.class, PermissionOffer.class, PermissionOfferResponse.class }) -public class ManagementModule { -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionChange.java b/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionChange.java deleted file mode 100644 index df839523d4..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionChange.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ -package io.realm.internal.permissions; - -import java.util.Date; -import java.util.UUID; - -import javax.annotation.Nullable; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmClass; -import io.realm.annotations.Required; -import io.realm.permissions.AccessLevel; -import io.realm.permissions.UserCondition; -import io.realm.permissions.PermissionRequest; - - -/** - * This class is used for requesting changes to a Realm's permissions. - * - * @see Controlling Permissions - */ -@RealmClass -public class PermissionChange implements BasePermissionApi { - - // Base fields - @PrimaryKey - @Required - private String id = UUID.randomUUID().toString(); - @Required - private Date createdAt = new Date(); - @Required - private Date updatedAt = new Date(); - private Integer statusCode = null; // null=not processed, 0=success, >0=error - private String statusMessage; - - @Required - private String realmUrl; - @Required - private String userId; - - private String metadataKey; - private String metadataValue; - private String metadataNameSpace; - private Boolean mayRead = false; - private Boolean mayWrite = false; - private Boolean mayManage = false; - - /** - * Maps between a PermissionRequest and a PermissionChange object. - * - * @param request request to map to a PermissionChange. - */ - public static PermissionChange fromRequest(PermissionRequest request) { - // PRE-CONDITION: All input are verified to be valid from the perspective of the Client. - UserCondition condition = request.getCondition(); - AccessLevel level = request.getAccessLevel(); - String realmUrl = request.getUrl(); - - String userId = ""; - String metadataKey = null; - String metadataValue = null; - switch (condition.getType()) { - case USER_ID: - userId = condition.getValue(); - break; - case METADATA: - metadataKey = condition.getKey(); - metadataValue = condition.getValue(); - break; - } - - return new PermissionChange(realmUrl, userId, metadataKey, metadataValue, level.mayRead(), level.mayWrite(), - level.mayManage()); - } - - public PermissionChange() { - // Default constructor required by Realm - } - - /** - * Construct a Permission Change Object. - * - * @param realmUrl Realm to change permissions for. Use {@code *} to change the permissions of all Realms. - * @param userId User or users to effect. Use {@code *} to change the permissions for all users. - * @param mayRead Define read access. {@code true} or {@code false} to request this new value. {@code null} to - * keep current value. - * @param mayWrite Define write access. {@code true} or {@code false} to request this new value. {@code null} to - * keep current value. - * @param mayManage Define manage access. {@code true} or {@code false} to request this new value. {@code null} to - * keep current value. - * - * @see Controlling Permissions - */ - public PermissionChange(String realmUrl, String userId, - @Nullable Boolean mayRead, @Nullable Boolean mayWrite, @Nullable Boolean mayManage) { - this.realmUrl = realmUrl; - this.userId = userId; - this.mayRead = mayRead; - this.mayWrite = mayWrite; - this.mayManage = mayManage; - } - - public PermissionChange(String realmUrl, String userId, String metadataKey, String metadataValue, Boolean mayRead, - Boolean mayWrite, Boolean mayManage) { - this.realmUrl = realmUrl; - this.userId = userId; - this.metadataKey = metadataKey; - this.metadataValue = metadataValue; - this.mayRead = mayRead; - this.mayWrite = mayWrite; - this.mayManage = mayManage; - } - - @Override - public String getId() { - return id; - } - - @Override - @SuppressFBWarnings("EI_EXPOSE_REP") - public Date getCreatedAt() { - return createdAt; - } - - @Override - @SuppressFBWarnings("EI_EXPOSE_REP") - public Date getUpdatedAt() { - return updatedAt; - } - - /** - * Returns the status code for this change. - * - * @return {@code null} if not yet processed. {@code 0} if successful, {@code >0} if an error happened. See {@link #getStatusMessage()}. - */ - @Override - @Nullable - public Integer getStatusCode() { - return statusCode; - } - - @Override - @Nullable - public String getStatusMessage() { - return statusMessage; - } - - public String getRealmUrl() { - return realmUrl; - } - - public String getUserId() { - return userId; - } - - @Nullable - public Boolean mayRead() { - return mayRead; - } - - @Nullable - public Boolean mayWrite() { - return mayWrite; - } - - @Nullable - public Boolean mayManage() { - return mayManage; - } - - public String getMetadataKey() { - return metadataKey; - } - - public String getMetadataValue() { - return metadataValue; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionModule.java b/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionModule.java deleted file mode 100644 index 9ba21bba00..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionModule.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.internal.permissions; - -import io.realm.annotations.RealmModule; -import io.realm.permissions.Permission; - -@RealmModule(library = true, classes = { Permission.class }) -public class PermissionModule { -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionOfferResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionOfferResponse.java index 812c67d7e8..c4f850dab3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionOfferResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionOfferResponse.java @@ -18,14 +18,12 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Date; -import java.util.UUID; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmClass; -import io.realm.annotations.Required; +import io.realm.permissions.AccessLevel; import io.realm.permissions.PermissionOffer; @@ -39,93 +37,33 @@ * @see Permissions description for general * documentation. */ -@RealmClass -public class PermissionOfferResponse implements BasePermissionApi { - - // Base fields - @PrimaryKey - @Required - private String id = UUID.randomUUID().toString(); - @Required - private Date createdAt = new Date(); - @Required - private Date updatedAt = new Date(); - private Integer statusCode; // nil=not processed, 0=success, >0=error - private String statusMessage; - - // Request fields - @Required - private String token; - private String realmUrl; - - public PermissionOfferResponse() { - // No args constructor required by Realm - } - - /** - * Construct a permission offer response object used to apply permission changes - * defined in the permission offer object represented by the specified token, - * which was created by another user's {@link PermissionOffer} object. - * - * @param token The received token which uniquely identifies another user's - * {@link PermissionOffer}. - */ - public PermissionOfferResponse(String token) { - //noinspection ConstantConditions - if (token == null) { - throw new IllegalArgumentException("Non-null 'token' required."); - } +public final class PermissionOfferResponse { + + @Nonnull private final String userId; + @Nonnull private final Date createdAt; + private final Date expiresAt; + @Nonnull private final String token; + @Nonnull private final String realmUrl; + @Nonnull private final AccessLevel accessLevel; + + public PermissionOfferResponse(String path, Date expiresAt, AccessLevel accessLevel, Date createdAt, String userId, String token) { + this.realmUrl = path; + this.expiresAt = (expiresAt != null) ? (Date) expiresAt.clone() : null; + this.accessLevel = accessLevel; + this.createdAt = (Date) createdAt.clone(); + this.userId = userId; this.token = token; } - public void setToken(String token) { - this.token = token; + public String getUserId() { + return userId; } - @Override - public String getId() { - return id; - } - - @Override @SuppressFBWarnings("EI_EXPOSE_REP") public Date getCreatedAt() { return createdAt; } - @Override - @SuppressFBWarnings("EI_EXPOSE_REP") - public Date getUpdatedAt() { - return updatedAt; - } - - /** - * Returns the status code for this change. - * - * @return {@code null} if not yet processed. {@code 0} if successful, {@code >0} if an error happened. See {@link #getStatusMessage()}. - */ - @Override - @Nullable - public Integer getStatusCode() { - return statusCode; - } - - /** - * Check if the request was successfully handled by the Realm Object Server. - * - * @return {@code true} if request was handled successfully. {@code false} if not. See {@link #getStatusMessage()} - * for the full error message. - */ - public boolean isSuccessful() { - return statusCode != null && statusCode == 0; - } - - @Override - @Nullable - public String getStatusMessage() { - return statusMessage; - } - public String getToken() { return token; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/AccessLevel.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/AccessLevel.java index 79afe55b95..689700eb1f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/AccessLevel.java +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/AccessLevel.java @@ -16,9 +16,9 @@ package io.realm.permissions; -import io.realm.PermissionManager; import io.realm.Realm; import io.realm.RealmConfiguration; +import io.realm.SyncUser; /** @@ -30,14 +30,14 @@ * access can always read or write from the Realm. This means that {@code NONE < READ < WRITE < ADMIN}. * * @see PermissionRequest - * @see io.realm.PermissionManager#applyPermissions(PermissionRequest, PermissionManager.ApplyPermissionsCallback) + * @see SyncUser#applyPermissionsAsync(PermissionRequest, SyncUser.Callback) */ public enum AccessLevel { /** * The user does not have access to this Realm. */ - NONE(false, false, false), + NONE("none", false, false, false), /** * User can only read the contents of the Realm. @@ -56,29 +56,40 @@ public enum AccessLevel { * } * */ - READ(true, false, false), + READ("read", true, false, false), /** * User can read and write the contents of the Realm. */ - WRITE(true, true, false), + WRITE("write", true, true, false), /** * User can read, write, and administer the Realm. This includes both granting permissions as well as removing them * again. */ - ADMIN(true, true, true); + ADMIN( "admin", true, true, true); + private final String key; // JSON description used by the Realm Object Server private final boolean mayRead; private final boolean mayWrite; private final boolean mayManage; - AccessLevel(boolean mayRead, boolean mayWrite, boolean mayManage) { + AccessLevel(String serverKey, boolean mayRead, boolean mayWrite, boolean mayManage) { + this.key = serverKey; this.mayRead = mayRead; this.mayWrite = mayWrite; this.mayManage = mayManage; } + public static AccessLevel fromKey(String accessLevel) { + for (AccessLevel level : values()) { + if (level.getKey().equals(accessLevel)) { + return level; + } + } + throw new IllegalArgumentException("Unknown access level: " + accessLevel); + } + /** * Returns {@code true} if the user is allowed to read a Realm, {@code false} if not. */ @@ -102,4 +113,8 @@ public boolean mayWrite() { public boolean mayManage() { return mayManage; } + + public String getKey() { + return key; + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/Permission.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/Permission.java index 2e975c7ff5..ed9e21a0ce 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/Permission.java +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/Permission.java @@ -18,44 +18,43 @@ import java.util.Date; +import javax.annotation.Nullable; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.PermissionManager; -import io.realm.RealmObject; import io.realm.SyncUser; -import io.realm.annotations.Required; /** - * This class represents a given set of permissions for one user on one Realm. + * This class represents the given set of permissions provided to a user for the Realm identified by + * {@link #path}. *

            - * Permissions can be changed by users with administrative rights using the {@link PermissionManager}. - * - * @see SyncUser#getPermissionManager() + * Permissions can be changed by users with administrative rights using {@link SyncUser#applyPermissions(PermissionRequest)}. */ -public class Permission extends RealmObject { - - @Required - private String userId; - @Required - private String path; - private boolean mayRead; - private boolean mayWrite; - private boolean mayManage; - @Required - private Date updatedAt; +public final class Permission { - /** - * Required by Realm. Do not use. - */ - public Permission() { - // Required by Realm + @Nullable private final String userId; + private final String path; + private final AccessLevel accessLevel; + private final boolean mayRead; + private final boolean mayWrite; + private final boolean mayManage; + private final Date updatedAt; + + public Permission(@Nullable String userId, String path, AccessLevel accessLevel, boolean mayRead, boolean mayWrite, boolean mayManage, Date updatedAt) { + this.userId = userId; + this.path = path; + this.accessLevel = accessLevel; + this.mayRead = mayRead; + this.mayWrite = mayWrite; + this.mayManage = mayManage; + this.updatedAt = (Date) updatedAt.clone(); } /** - * Returns the {@link SyncUser#getIdentity()} of the user effected by this permission.˚ - *

            + * Returns the {@link SyncUser#getIdentity()} of the user effected by this permission or + * {@code null} if this permissions applies to all users. * - * @return the user effected by this permission. + * @return the user(s) effected by this permission. */ public String getUserId() { return userId; @@ -70,6 +69,15 @@ public String getPath() { return path; } + /** + * Returns the access level granted by this permission. + * + * @return access level granted by this permission. + */ + public AccessLevel getAccessLevel() { + return accessLevel; + } + /** * Checks whether or not the user defined by this permission is allowed to read the Realm defined by * {@link #getPath()}. @@ -116,10 +124,39 @@ public String toString() { return "Permission{" + "userId='" + userId + '\'' + ", path='" + path + '\'' + + ", accessLevel=" + accessLevel + ", mayRead=" + mayRead + ", mayWrite=" + mayWrite + ", mayManage=" + mayManage + ", updatedAt=" + updatedAt + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Permission that = (Permission) o; + + if (mayRead != that.mayRead) return false; + if (mayWrite != that.mayWrite) return false; + if (mayManage != that.mayManage) return false; + if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false; + if (!path.equals(that.path)) return false; + if (accessLevel != that.accessLevel) return false; + return updatedAt.equals(that.updatedAt); + } + + @Override + public int hashCode() { + int result = userId != null ? userId.hashCode() : 0; + result = 31 * result + path.hashCode(); + result = 31 * result + accessLevel.hashCode(); + result = 31 * result + (mayRead ? 1 : 0); + result = 31 * result + (mayWrite ? 1 : 0); + result = 31 * result + (mayManage ? 1 : 0); + result = 31 * result + updatedAt.hashCode(); + return result; + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java index a42e5b9e77..5baeaba7e7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java @@ -18,18 +18,13 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Date; -import java.util.UUID; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.PermissionManager; -import io.realm.annotations.Index; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmClass; -import io.realm.annotations.Required; +import io.realm.SyncUser; import io.realm.internal.Util; -import io.realm.internal.permissions.BasePermissionApi; /** @@ -40,42 +35,19 @@ *

            * Permission offers can only be created by users that can manage the Realm, the offer is about. * - * @see PermissionManager#makeOffer(PermissionOffer, PermissionManager.MakeOfferCallback) - * @see PermissionManager#acceptOffer(String, PermissionManager.AcceptOfferCallback) + * @see SyncUser#makePermissionsOfferAsync(PermissionOffer, SyncUser.Callback) + * @see SyncUser#acceptPermissionsOfferAsync(String, SyncUser.Callback) * @see Permissions description for general * documentation. */ +public final class PermissionOffer { -@RealmClass -public class PermissionOffer implements BasePermissionApi { - - // Base fields - @PrimaryKey - @Required - private String id = UUID.randomUUID().toString(); - @Required - private Date createdAt = new Date(); - @Required - private Date updatedAt = new Date(); - private Integer statusCode; // nil=not processed, 0=success, >0=error - private String statusMessage; - - // Offer fields - @Index - private String token; - @Required - private String realmUrl; - private boolean mayRead; - private boolean mayWrite; - private boolean mayManage; - private Date expiresAt; - - /** - * Constructor required by Realm. Should not be used. - */ - public PermissionOffer() { - // No args constructor required by Realm - } + @Nonnull private final Date createdAt; + private final String userId; + private final String token; + @Nonnull private final String realmUrl; + @Nonnull private final AccessLevel accessLevel; + private final Date expiresAt; /** * Creates a request for an permission offer that last until it is manually revoked. @@ -83,7 +55,7 @@ public PermissionOffer() { * @param url specific url to Realm effected this offer encompasses all Realms manged by the user making the offer. * @param accessLevel the {@link AccessLevel} granted to the user accepting the offer. * - * @see PermissionManager#revokeOffer(String, PermissionManager.RevokeOfferCallback) + * @see SyncUser#invalidatePermissionsOfferAsync(String, SyncUser.Callback) */ @SuppressFBWarnings("EI_EXPOSE_REP2") public PermissionOffer(String url, AccessLevel accessLevel) { @@ -99,18 +71,23 @@ public PermissionOffer(String url, AccessLevel accessLevel) { * @param expiresAt the date and time when this offer expires. If {@code null} is provided the offer never expires. * * - * @see PermissionManager#revokeOffer(String, PermissionManager.RevokeOfferCallback) + * @see SyncUser#invalidatePermissionsOfferAsync(String, SyncUser.Callback) */ @SuppressFBWarnings("EI_EXPOSE_REP2") public PermissionOffer(String url, AccessLevel accessLevel, @Nullable Date expiresAt) { - validateUrl(url); + this(url, accessLevel, expiresAt, new Date(), null, null); + } + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public PermissionOffer(String path, AccessLevel accessLevel, @Nullable Date expiresAt, Date createdAt, @Nullable String userId, @Nullable String token) { + validateUrl(path); validateAccessLevel(accessLevel); - this.mayRead = accessLevel.mayRead(); - this.mayWrite = accessLevel.mayWrite(); - this.mayManage = accessLevel.mayManage(); - this.realmUrl = url; - //noinspection ConstantConditions + this.realmUrl = path; + this.accessLevel = accessLevel; this.expiresAt = (expiresAt != null) ? (Date) expiresAt.clone() : null; + this.createdAt = (Date) createdAt.clone(); + this.userId = userId; + this.token = token; } private void validateUrl(String url) { @@ -132,72 +109,16 @@ private void validateAccessLevel(AccessLevel accessLevel) { } } - /** - * Returns the id uniquely identifying this offer. - * - * @return the id uniquely identifying this offer. - */ - @Override - public String getId() { - return id; - } - /** * Returns the timestamp when this offer was created. * * @return the timstamp when this offer was created. */ - @Override @SuppressFBWarnings("EI_EXPOSE_REP") public Date getCreatedAt() { return createdAt; } - /** - * Returns the timestamp this offer was last updated. - * - * @return the timestamp when this offer was last updated. - */ - @Override - @SuppressFBWarnings("EI_EXPOSE_REP") - public Date getUpdatedAt() { - return updatedAt; - } - - - /** - * Returns the server status code for this change. - * - * @return {@code null} if not yet processed. {@code 0} if successful, {@code >0} if an error happened. - * See {@link #getStatusMessage()}. - */ - @Override - @Nullable - public Integer getStatusCode() { - return statusCode; - } - - /** - * Returns the servers status message, if an error occurred. Otherwise it will return {@code null}. - * - * @return The servers status message in case of an error, {@code null} otherwise. - */ - @Override - @Nullable - public String getStatusMessage() { - return statusMessage; - } - - /** - * Checks if the request was successfully handled by the Realm Object Server. - * - * @return {@code true} if the request was handled successfully. {@code false} if not. See {@link #getStatusMessage()} - * for the full error message. - */ - public boolean isOfferCreated() { - return !Util.isEmptyString(token); - } - /** * Returns the offer token if this offer was successfully created. * @@ -223,7 +144,7 @@ public String getRealmUrl() { * @return {@code true} if the user accepting this offer is granted read permission, {@code false} if not. */ public boolean mayRead() { - return mayRead; + return accessLevel.mayRead(); } /** @@ -232,7 +153,7 @@ public boolean mayRead() { * @return {@code true} if the user accepting this offer is granted write permission, {@code false} if not. */ public boolean mayWrite() { - return mayWrite; + return accessLevel.mayWrite(); } /** @@ -242,7 +163,25 @@ public boolean mayWrite() { * @return {@code true} if the user accepting this offer is granted mange permission, {@code false} if not. */ public boolean mayManage() { - return mayManage; + return accessLevel.mayManage(); + } + + /** + * Returns the access level granted by this offer. + * + * @return access level granted by this offer. + */ + public AccessLevel getAccessLevel() { + return accessLevel; + } + + /** + * Checks if the offer was successfully handled by the Realm Object Server. + * + * @return {@code true} if the request has been created, {@code false} if not. + */ + public boolean isOfferCreated() { + return !Util.isEmptyString(token); } /** @@ -259,16 +198,13 @@ public Date getExpiresAt() { @Override public String toString() { return "PermissionOffer{" + - "id='" + id + '\'' + + "userId='" + userId + '\'' + ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - ", statusCode=" + statusCode + - ", statusMessage='" + statusMessage + '\'' + ", token='" + token + '\'' + ", realmUrl='" + realmUrl + '\'' + - ", mayRead=" + mayRead + - ", mayWrite=" + mayWrite + - ", mayManage=" + mayManage + + ", mayRead=" + accessLevel.mayRead() + + ", mayWrite=" + accessLevel.mayWrite() + + ", mayManage=" + accessLevel.mayManage() + ", expiresAt=" + expiresAt + '}'; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java index c10a9af401..f72f9e4e6d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java @@ -19,19 +19,17 @@ import java.net.URI; import java.net.URISyntaxException; -import io.realm.PermissionManager; +import io.realm.SyncUser; import io.realm.internal.Util; - /** * This class represents the intent of giving a set of permissions to some users for some Realm(s). *

            - * If the request is successful, a {@link io.realm.permissions.Permission} entry will be added to each affected users - * {@link PermissionManager}, where it can be fetched using - * {@link PermissionManager#getPermissions(PermissionManager.PermissionsCallback)} + * If the request is successful, a {@link io.realm.permissions.Permission} entry will be added to each affected users, + * where it can be fetched using {@link SyncUser#retrieveGrantedPermissionsAsync(SyncUser.Callback)} * - * @see PermissionManager#applyPermissions(PermissionRequest, PermissionManager.ApplyPermissionsCallback) - * @see PermissionManager#getPermissions(PermissionManager.PermissionsCallback) + * @see SyncUser#applyPermissionsAsync(PermissionRequest, SyncUser.Callback) + * @see SyncUser#retrieveGrantedPermissionsAsync(SyncUser.Callback) */ public final class PermissionRequest { diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/UserCondition.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/UserCondition.java index 57026e7ed4..168e753e03 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/UserCondition.java +++ b/realm/realm-library/src/objectServer/java/io/realm/permissions/UserCondition.java @@ -16,7 +16,6 @@ package io.realm.permissions; -import io.realm.PermissionManager; import io.realm.SyncUser; import io.realm.internal.Util; @@ -26,7 +25,7 @@ * It is used when a request for changing existing permissions is made. * * @see PermissionRequest - * @see io.realm.PermissionManager#applyPermissions(PermissionRequest, PermissionManager.ApplyPermissionsCallback) + * @see SyncUser#applyPermissionsAsync(PermissionRequest, SyncUser.Callback) */ public final class UserCondition { @@ -66,7 +65,7 @@ public static UserCondition userId(String userId) { * The {@link AccessLevel} defined alongside this condition, will also be used as the default access level * for future new users that might be given access to the Realm. * - * @see PermissionManager#makeOffer(PermissionOffer, PermissionManager.MakeOfferCallback) + * @see SyncUser#makePermissionsOfferAsync(PermissionOffer, SyncUser.Callback) */ public static UserCondition noExistingPermissions() { return userId("*"); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java new file mode 100644 index 0000000000..16cedc4cae --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java @@ -0,0 +1,507 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.os.SystemClock; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Date; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; + +import io.realm.entities.AllJavaTypes; +import io.realm.internal.OsRealmConfig; +import io.realm.objectserver.utils.Constants; +import io.realm.objectserver.utils.UserFactory; +import io.realm.permissions.AccessLevel; +import io.realm.permissions.Permission; +import io.realm.permissions.PermissionOffer; +import io.realm.permissions.PermissionRequest; +import io.realm.permissions.UserCondition; +import io.realm.rule.RunTestInLooperThread; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(AndroidJUnit4.class) +public class PathLevelPermissionsTests extends StandardIntegrationTest { + + private SyncUser user; + + @Before + public void setUpTest() { + user = UserFactory.createUniqueUser(); + } + + @Test + @RunTestInLooperThread() + public void retrieveGrantedPermissions_returnLoadedResults() { + user.retrieveGrantedPermissionsAsync(new SyncUser.Callback>() { + @Override + public void onSuccess(List permissions) { + assertInitialPermissions(permissions); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + + @Test + @RunTestInLooperThread + public void retrieveGrantedPermissions_updatedWithNewRealms() { + user.retrieveGrantedPermissionsAsync(new SyncUser.Callback>() { + @Override + public void onSuccess(List permissions) { + assertInitialPermissions(permissions); + + // Create new Realm, which should create a new Permission entry + SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM_2) + .schema(AllJavaTypes.class) + .fullSynchronization() + .errorHandler((session, error) -> fail(error.toString())) + .build(); + final Realm secondRealm = Realm.getInstance(config2); + looperThread.closeAfterTest(secondRealm); + try { + SyncManager.getSession(config2).uploadAllLocalChanges(); + } catch (InterruptedException e) { + fail(e.toString()); + } + + // Wait for the permission Result to report the new Realms + List permissions2 = user.retrieveGrantedPermissions(); + assertEquals(1, permissions.size()); + assertEquals(2, permissions2.size()); + Permission permission = permissions2.get(1); + assertTrue(permission.getPath().endsWith("tests2")); + assertTrue(permission.mayRead()); + assertTrue(permission.mayWrite()); + assertTrue(permission.mayManage()); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + @Test + @RunTestInLooperThread() + public void getPermissions_updatedWithNewRealms_stressTest() { + final int TEST_SIZE = 10; + List permissions = user.retrieveGrantedPermissions(); + assertInitialPermissions(permissions); + + for (int i = 0; i < TEST_SIZE; i++) { + SyncConfiguration configNew = user.createConfiguration("realm://" + Constants.HOST + "/~/test" + i) + .fullSynchronization() + .schema(AllJavaTypes.class) + .build(); + Realm newRealm = Realm.getInstance(configNew); + looperThread.closeAfterTest(newRealm); + } + + List perms = permissions; + while(perms.size() < TEST_SIZE + 1) { // +1 is __wildcardpermissions + perms = user.retrieveGrantedPermissions(); + } + + Permission p = perms.get(TEST_SIZE); + assertTrue(p.getPath().endsWith("test" + (TEST_SIZE - 1))); + assertTrue(p.mayRead()); + assertTrue(p.mayWrite()); + assertTrue(p.mayManage()); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread(emulateMainThread = true) + public void applyPermissions_nonAdminUserFails() { + SyncUser user2 = UserFactory.createUniqueUser(); + String otherUsersUrl = createRemoteRealm(user2, "test"); + + // Create request for setting permissions on another users Realm, + // i.e. user making the request do not have manage rights. + UserCondition condition = UserCondition.userId(user.getIdentity()); + AccessLevel accessLevel = AccessLevel.WRITE; + PermissionRequest request = new PermissionRequest(condition, otherUsersUrl, accessLevel); + + user.applyPermissionsAsync(request, new SyncUser.Callback() { + @Override + public void onSuccess(Void success) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); + looperThread.testComplete(); + } + }); + } + + @Test + @RunTestInLooperThread + public void applyPermissions_wrongUrlFails() { + String wrongUrl = createRemoteRealm(user, "test") + "-notexisting"; + + // Create request for setting permissions on another users Realm, + // i.e. user making the request do not have manage rights. + UserCondition condition = UserCondition.userId(user.getIdentity()); + AccessLevel accessLevel = AccessLevel.WRITE; + PermissionRequest request = new PermissionRequest(condition, wrongUrl, accessLevel); + user.applyPermissionsAsync(request, new SyncUser.Callback() { + @Override + public void onSuccess(Void ignore) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.INVALID_PARAMETERS, error.getErrorCode()); + looperThread.testComplete(); + } + }); + } + + @Test + @RunTestInLooperThread(emulateMainThread = true) + public void applyPermissions_withUserId() { + final SyncUser user2 = UserFactory.createUniqueUser(); + String url = createRemoteRealm(user2, "test"); + + // Create request for giving `user` WRITE permissions to `user2`'s Realm. + UserCondition condition = UserCondition.userId(user.getIdentity()); + AccessLevel accessLevel = AccessLevel.WRITE; + PermissionRequest request = new PermissionRequest(condition, url, accessLevel); + + user2.applyPermissionsAsync(request, new SyncUser.Callback() { + @Override + public void onSuccess(Void ignore) { + List permissions = user.retrieveGrantedPermissions(); + assertPermissionPresent(permissions, user, "/test", AccessLevel.WRITE); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + @Test + @RunTestInLooperThread + public void applyPermissions_withUsername() { + String user1Username = TestHelper.getRandomEmail(); + String user2Username = TestHelper.getRandomEmail(); + final SyncUser user1 = UserFactory.createUser(user1Username); + final SyncUser user2 = UserFactory.createUser(user2Username); + + // Create request for giving `user2` WRITE permissions to `user1`'s Realm. + UserCondition condition = UserCondition.username(user2Username); + AccessLevel accessLevel = AccessLevel.WRITE; + String url = createRemoteRealm(user1, "test"); + PermissionRequest request = new PermissionRequest(condition, url, accessLevel); + + user1.applyPermissions(request); + List user2Permissions = user2.retrieveGrantedPermissions(); + assertPermissionPresent(user2Permissions, user2, user1.getIdentity() + "/test", AccessLevel.WRITE); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void applyPermissions_usersWithNoExistingPermissions() { + final SyncUser user1 = UserFactory.createUser("user1@realm.io"); + final SyncUser user2 = UserFactory.createUser("user2@realm.io"); + + // Create request for giving all users with no existing permissions WRITE permissions to `user1`'s Realm. + UserCondition condition = UserCondition.noExistingPermissions(); + AccessLevel accessLevel = AccessLevel.WRITE; + final String url = createRemoteRealm(user1, "test"); + PermissionRequest request = new PermissionRequest(condition, url, accessLevel); + + user1.applyPermissions(request); + List user2Permissions = user2.retrieveGrantedPermissions(); + assertPermissionPresent(user2Permissions, null, "/" + user1.getIdentity() + "/test", AccessLevel.WRITE); + + // Remove wildcard permission to prevent them from interfering with other tests + user1.applyPermissions(new PermissionRequest(UserCondition.noExistingPermissions(), url, AccessLevel.NONE)); + + } + + @Test + @RunTestInLooperThread + public void makeOffer() { + String url = createRemoteRealm(user, "test"); + + PermissionOffer offer = new PermissionOffer(url, AccessLevel.WRITE); + user.makePermissionsOfferAsync(offer, new SyncUser.Callback() { + @Override + public void onSuccess(String token) { + assertNotNull(token); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + @Test + @RunTestInLooperThread + public void makeOffer_noManageAccessThrows() { + // User 2 creates a Realm + SyncUser user2 = UserFactory.createUniqueUser(); + String url = createRemoteRealm(user2, "test"); + + // User 1 tries to create an offer for it. + PermissionOffer offer = new PermissionOffer(url, AccessLevel.WRITE); + user.makePermissionsOfferAsync(offer, new SyncUser.Callback() { + @Override + public void onSuccess(String s) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); + looperThread.testComplete(); + } + }); + } + + @Test + @RunTestInLooperThread + public void acceptOffer() { + final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); + final SyncUser user2 = UserFactory.createUniqueUser(); + user2.acceptPermissionsOfferAsync(offerToken, new SyncUser.Callback() { + @Override + public void onSuccess(String realmPath) { + assertEquals("/" + user.getIdentity() + "/test", realmPath); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + @Test + @RunTestInLooperThread + public void acceptOffer_invalidToken() { + user.acceptPermissionsOfferAsync("wrong-token", new SyncUser.Callback() { + @Override + public void onSuccess(String s) { + fail(); + } + + @Override + public void onError(ObjectServerError error) { + assertEquals(ErrorCode.INVALID_PARAMETERS, error.getErrorCode()); + looperThread.testComplete(); + } + }); + } + + @Test + @RunTestInLooperThread + public void acceptOffer_multipleUsers() { + final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); + final SyncUser user2 = UserFactory.createUniqueUser(); + final SyncUser user3 = UserFactory.createUniqueUser(); + + final AtomicInteger offersAccepted = new AtomicInteger(0); + SyncUser.Callback callback = new SyncUser.Callback() { + @Override + public void onSuccess(String url) { + assertEquals("/" + user.getIdentity() + "/test", url); + if (offersAccepted.incrementAndGet() == 2) { + looperThread.testComplete(); + } + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }; + + user2.acceptPermissionsOfferAsync(offerToken, callback); + user2.acceptPermissionsOfferAsync(offerToken, callback); + } + + @Test + @RunTestInLooperThread + public void getCreatedOffers() { + final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); + + user.retrieveCreatedPermissionsOffersAsync(new SyncUser.Callback>() { + @Override + public void onSuccess(List permissionOffers) { + assertEquals(1, permissionOffers.size()); + assertEquals(offerToken, permissionOffers.get(0).getToken()); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + @Test + @RunTestInLooperThread(emulateMainThread = true) + public void revokeOffer() { + // createOffer validates that the offer is actually in the __management Realm. + final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); + + user.invalidatePermissionsOfferAsync(offerToken, new SyncUser.Callback() { + @Override + public void onSuccess(Void aVoid) { + List offers = user.retrieveCreatedPermissionsOffers(); + assertEquals(0, offers.size()); + looperThread.testComplete(); + } + + @Override + public void onError(ObjectServerError error) { + fail(error.toString()); + } + }); + } + + @Test + @RunTestInLooperThread + public void revokeOffer_afterOneAcceptEdit() { + final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); + SyncUser user2 = UserFactory.createUniqueUser(); + SyncUser user3 = UserFactory.createUniqueUser(); + + String path = user2.acceptPermissionsOffer(offerToken); + assertTrue(path.endsWith("test")); + user.invalidatePermissionsOffer(offerToken); + try { + user3.acceptPermissionsOffer(offerToken); + fail(); + } catch (ObjectServerError error) { + assertEquals(ErrorCode.EXPIRED_PERMISSION_OFFER, error.getErrorCode()); + looperThread.testComplete(); + } + } + + /** + * Creates an offer for a newly created Realm. + * + * @param user User that should create the offer + * @param realmName Realm to create + * @param level accessLevel to offer + * @param expires when the offer expires + */ + private String createOffer(final SyncUser user, final String realmName, final AccessLevel level, final Date expires) { + String url = createRemoteRealm(user, realmName); + return user.makePermissionsOffer(new PermissionOffer(url, level, expires)); + } + + /** + * Wait for a given permission to be present. + * + * @param permissions permission results. + * @param user user that is being granted the permission. + * @param urlSuffix the url suffix to listen for. + * @param accessLevel the expected access level for 'user'. + */ + private void assertPermissionPresent(List permissions, @Nullable final SyncUser user, String urlSuffix, final AccessLevel accessLevel) { + for (Permission p : permissions) { + if (p.getPath().endsWith(urlSuffix)) { + assertEquals(accessLevel.mayRead(), p.mayRead()); + assertEquals(accessLevel.mayWrite(), p.mayWrite()); + assertEquals(accessLevel.mayManage(), p.mayManage()); + if (user != null) { + // Specific permissions + assertEquals(user.getIdentity(), p.getUserId()); + } else { + // Default permissions + assertNull(p.getUserId()); + } + looperThread.testComplete(); + return; + } + } + throw new AssertionError("No matching permissions"); + } + + /** + * Creates an empty remote Realm on ROS owned by the provided user + */ + private String createRemoteRealm(SyncUser user, String realmName) { + String url = Constants.AUTH_SERVER_URL + "~/" + realmName; + SyncConfiguration config = user.createConfiguration(url) + .name(realmName) + .schema(AllJavaTypes.class) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build(); + + Realm realm = Realm.getInstance(config); + SyncSession session = SyncManager.getSession(config); + final CountDownLatch uploadLatch = new CountDownLatch(1); + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + uploadLatch.countDown(); + } + } + }); + TestHelper.awaitOrFail(uploadLatch); + realm.close(); + return config.getServerUrl().toString(); + } + + /** + * The initial set of permissions from ROS. + */ + private void assertInitialPermissions(List permissions) { + assertEquals(1, permissions.size()); + assertEquals("/__wildcardpermissions", permissions.get(0).getPath()); + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java deleted file mode 100644 index 6ead0af6ea..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PermissionManagerTests.java +++ /dev/null @@ -1,1241 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.os.Handler; -import android.os.HandlerThread; -import android.os.SystemClock; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Date; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import io.realm.entities.AllJavaTypes; -import io.realm.internal.OsRealmConfig; -import io.realm.log.RealmLog; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; -import io.realm.permissions.AccessLevel; -import io.realm.permissions.Permission; -import io.realm.permissions.PermissionOffer; -import io.realm.permissions.PermissionRequest; -import io.realm.permissions.UserCondition; -import io.realm.rule.RunTestInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class PermissionManagerTests extends StandardIntegrationTest { - - private SyncUser user; - - @Before - public void setUpTest() { - user = UserFactory.createUniqueUser(); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getPermissions_returnLoadedResults() { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertTrue(permissions.isLoaded()); - assertInitialPermissions(permissions); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getPermissions_noLongerValidWhenPermissionManagerIsClosed() { - final PermissionManager pm = user.getPermissionManager(); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertTrue(permissions.isValid()); - pm.close(); - assertFalse(permissions.isValid()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - pm.close(); - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getPermissions_updatedWithNewRealms() { - final PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertTrue(permissions.isLoaded()); - assertInitialPermissions(permissions); - - // Create new Realm, which should create a new Permission entry - SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM_2) - .schema(AllJavaTypes.class) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail(error.toString()); - } - }) - .build(); - final Realm secondRealm = Realm.getInstance(config2); - looperThread.closeAfterTest(secondRealm); - // Wait for the permission Result to report the new Realms - looperThread.keepStrongReference(permissions); - permissions.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults permissions) { - RealmLog.error(String.format("2ndCallback: Size: %s, Permissions: %s", permissions.size(), Arrays.toString(permissions.toArray()))); - Permission p = permissions.where().endsWith("path", "tests2").findFirst(); - if (p != null) { - assertTrue(p.mayRead()); - assertTrue(p.mayWrite()); - assertTrue(p.mayManage()); - looperThread.testComplete(); - } - } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail("Could not open Realm: " + error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getPermissions_updatedWithNewRealms_stressTest() { - final int TEST_SIZE = 10; - final PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertTrue(permissions.isLoaded()); - assertInitialPermissions(permissions); - - for (int i = 0; i < TEST_SIZE; i++) { - SyncConfiguration configNew = user.createConfiguration("realm://" + Constants.HOST + "/~/test" + i) - .schema(AllJavaTypes.class) - .build(); - Realm newRealm = Realm.getInstance(configNew); - looperThread.closeAfterTest(newRealm); - } - - // Wait for the permission Result to report the new Realms - looperThread.keepStrongReference(permissions); - permissions.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults permissions) { - RealmLog.error(String.format("Size: %s, Permissions: %s", permissions.size(), Arrays.toString(permissions.toArray()))); - Permission p = permissions.where().endsWith("path", "test" + (TEST_SIZE - 1)).findFirst(); - if (p != null) { - assertTrue(p.mayRead()); - assertTrue(p.mayWrite()); - assertTrue(p.mayManage()); - looperThread.testComplete(); - } - } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getPermissions_closed() throws IOException { - PermissionManager pm = user.getPermissionManager(); - pm.close(); - - thrown.expect(IllegalStateException.class); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - fail(); - } - @Override - public void onError(ObjectServerError error) { fail(); } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getPermissions_clientReset() { - final PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - // Simulate reset after first request succeeded to make sure that session is - // alive. - SyncManager.simulateClientReset(SyncManager.getSession(pm.permissionRealmConfig)); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertEquals(3, permissions.size()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Ignore("The PermissionManager can only be opened from the main thread") - @Test - public void clientResetOnMultipleThreads() { - - HandlerThread thread1 = new HandlerThread("handler1"); - thread1.start(); - Handler handler1 = new Handler(thread1.getLooper()); - - HandlerThread thread2 = new HandlerThread("handler2"); - thread2.start(); - Handler handler2 = new Handler(thread1.getLooper()); - - final AtomicReference pm1 = new AtomicReference<>(null); - final AtomicReference pm2 = new AtomicReference<>(null); - - final CountDownLatch pmsOpened = new CountDownLatch(1); - - // 1) Thread 1: Open PermissionManager and check permissions - handler1.post(new Runnable() { - @Override - public void run() { - PermissionManager pm = user.getPermissionManager(); - pm1.set(pm); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertInitialPermissions(permissions); - pmsOpened.countDown(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - }); - - // 2) Thread 2: Open PermissionManager and check permissions - handler2.post(new Runnable() { - @Override - public void run() { - PermissionManager pm = user.getPermissionManager(); - pm2.set(pm); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertInitialPermissions(permissions); - pmsOpened.countDown(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - }); - - TestHelper.awaitOrFail(pmsOpened); - - // 3) Trigger Client Reset - SyncManager.simulateClientReset(SyncManager.getSession(pm1.get().permissionRealmConfig)); - SyncManager.simulateClientReset(SyncManager.getSession(pm2.get().permissionRealmConfig)); - - // 4) Thread 1: Attempt to get permissions should trigger a Client Reset - final CountDownLatch clientResetThread1 = new CountDownLatch(1); - final CountDownLatch clientResetThread2 = new CountDownLatch(1); - handler1.post(new Runnable() { - @Override - public void run() { - final PermissionManager pm = pm1.get(); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - fail("Client reset should have been triggered"); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.CLIENT_RESET, error.getErrorCode()); - pm.close(); - assertFalse(new File(pm.permissionRealmConfig.getPath()).exists()); - clientResetThread1.countDown(); - } - }); - } - }); - - // 5) Thread 2: Attempting to get permissions should also trigger a Client Reset even though - // Thread 1 just executed it - TestHelper.awaitOrFail(clientResetThread1); - handler2.post(new Runnable() { - @Override - public void run() { - final PermissionManager pm = pm2.get(); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - fail("Client reset should have been triggered"); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.CLIENT_RESET, error.getErrorCode()); - pm.close(); - clientResetThread2.countDown(); - } - }); - } - }); - TestHelper.awaitOrFail(clientResetThread2); - - // 6) After closing the PermissionManager, re-opening it again should work fine - final CountDownLatch newPmOpenedAndReady = new CountDownLatch(1); - handler1.post(new Runnable() { - @Override - public void run() { - final PermissionManager pm = user.getPermissionManager(); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertInitialPermissions(permissions); - pm.close(); - newPmOpenedAndReady.countDown(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - }); - - TestHelper.awaitOrFail(newPmOpenedAndReady); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getDefaultPermissions_returnLoadedResults() { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - pm.getDefaultPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertTrue(permissions.isLoaded()); - assertInitialDefaultPermissions(permissions); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getDefaultPermissions_noLongerValidWhenPermissionManagerIsClosed() { - final PermissionManager pm = user.getPermissionManager(); - pm.getDefaultPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - try { - assertTrue(permissions.isValid()); - } finally { - pm.close(); - } - assertFalse(permissions.isValid()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - pm.close(); - fail(error.toString()); - } - }); - } - - @Test - @Ignore("FIXME Add once `setPermissions` are implemented") - @RunTestInLooperThread(emulateMainThread = true) - public void getDefaultPermissions_updatedWithNewRealms() { - - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getDefaultPermissions_closed() throws IOException { - PermissionManager pm = user.getPermissionManager(); - pm.close(); - - thrown.expect(IllegalStateException.class); - pm.getDefaultPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - fail(); - } - @Override - public void onError(ObjectServerError error) { fail(); } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void permissionManagerAsyncTask_handlePermissionRealmError() throws NoSuchFieldException, IllegalAccessException { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - // Simulate error in the permission Realm - Field permissionConfigField = pm.getClass().getDeclaredField("permissionRealmError"); - permissionConfigField.setAccessible(true); - final ObjectServerError error = new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom"); - permissionConfigField.set(pm, error); - - PermissionManager.ApplyPermissionsCallback callback = new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertTrue(error.getErrorMessage().startsWith("Error occurred in Realm")); - assertTrue(error.getErrorMessage().contains("Permission Realm")); - assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); - looperThread.testComplete(); - } - }; - - // Create dummy task that can trigger the error reporting - runTask(pm, callback); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void permissionManagerAsyncTask_handleManagementRealmError() throws NoSuchFieldException, IllegalAccessException { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - // Simulate error in the permission Realm - final ObjectServerError error = new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom"); - setRealmError(pm, "managementRealmError", error); - - PermissionManager.ApplyPermissionsCallback callback = new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertTrue(error.getErrorMessage().startsWith("Error occurred in Realm")); - assertTrue(error.getErrorMessage().contains("Management Realm")); - assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); - looperThread.testComplete(); - } - }; - - // Create dummy task that can trigger the error reporting - runTask(pm, callback); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void permissionManagerAsyncTask_handleTwoErrorsSameErrorCode() throws NoSuchFieldException, IllegalAccessException { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - // Simulate error in the permission Realm - setRealmError(pm, "managementRealmError", new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom1")); - - // Simulate error in the management Realm - setRealmError(pm, "permissionRealmError", new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom2")); - - PermissionManager.ApplyPermissionsCallback callback = new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); - assertTrue(error.toString().contains("Boom1")); - assertTrue(error.toString().contains("Boom2")); - looperThread.testComplete(); - } - }; - - // Create dummy task that can trigger the error reporting - runTask(pm, callback); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void permissionManagerAsyncTask_doNotReportIntermittentErrors() throws NoSuchFieldException, IllegalAccessException { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - // Simulate intermittent error in the management Realm that is possible to recover from - // These kind of errors should never reach the end user as we should recover automatically. - setRealmError(pm, "managementRealmError", new ObjectServerError(ErrorCode.UNKNOWN, "Boom1")); - - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertEquals(3, permissions.size()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void permissionManagerAsyncTask_keepReportingFatalErrors() throws NoSuchFieldException, IllegalAccessException { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - // Simulate fatal error in the management Realm that is not possible to recover from - // This should be reported for all tasks, not just the first one. - setRealmError(pm, "managementRealmError", new ObjectServerError(ErrorCode.WRONG_PROTOCOL_VERSION, "Boom1")); - - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.WRONG_PROTOCOL_VERSION, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - }); - } - - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void permissionManagerAsyncTask_handleTwoErrorsDifferentErrorCode() throws NoSuchFieldException, IllegalAccessException { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - // Simulate error in the permission Realm - setRealmError(pm, "managementRealmError", new ObjectServerError(ErrorCode.CONNECTION_CLOSED, "Boom1")); - - // Simulate error in the management Realm - setRealmError(pm, "permissionRealmError", new ObjectServerError(ErrorCode.SESSION_CLOSED, "Boom2")); - - PermissionManager.ApplyPermissionsCallback callback = new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertTrue(error.toString().contains(ErrorCode.CONNECTION_CLOSED.toString())); - assertTrue(error.toString().contains(ErrorCode.SESSION_CLOSED.toString())); - looperThread.testComplete(); - } - }; - - // Create dummy task that can trigger the error reporting - runTask(pm, callback); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void applyPermissions_nonAdminUserFails() { - SyncUser user2 = UserFactory.createUniqueUser(); - String otherUsersUrl = createRemoteRealm(user2, "test"); - - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - // Create request for setting permissions on another users Realm, - // i.e. user making the request do not have manage rights. - UserCondition condition = UserCondition.userId(user.getIdentity()); - AccessLevel accessLevel = AccessLevel.WRITE; - PermissionRequest request = new PermissionRequest(condition, otherUsersUrl, accessLevel); - - pm.applyPermissions(request, new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void applyPermissions_wrongUrlFails() { - String wrongUrl = createRemoteRealm(user, "test") + "-notexisting"; - - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - // Create request for setting permissions on another users Realm, - // i.e. user making the request do not have manage rights. - UserCondition condition = UserCondition.userId(user.getIdentity()); - AccessLevel accessLevel = AccessLevel.WRITE; - PermissionRequest request = new PermissionRequest(condition, wrongUrl, accessLevel); - - pm.applyPermissions(request, new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - // FIXME: Should be 614, see https://github.com/realm/ros/issues/429 - assertEquals(ErrorCode.INVALID_PARAMETERS, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void applyPermissions_withUserId() { - final SyncUser user2 = UserFactory.createUniqueUser(); - String url = createRemoteRealm(user2, "test"); - PermissionManager pm2 = user2.getPermissionManager(); - looperThread.closeAfterTest(pm2); - - // Create request for giving `user` WRITE permissions to `user2`'s Realm. - UserCondition condition = UserCondition.userId(user.getIdentity()); - AccessLevel accessLevel = AccessLevel.WRITE; - PermissionRequest request = new PermissionRequest(condition, url, accessLevel); - - pm2.applyPermissions(request, new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - pm.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertPermissionPresent(permissions, user, "/test", AccessLevel.WRITE); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void applyPermissions_withUsername() { - String user1Username = TestHelper.getRandomEmail(); - String user2Username = TestHelper.getRandomEmail(); - final SyncUser user1 = UserFactory.createUser(user1Username); - final SyncUser user2 = UserFactory.createUser(user2Username); - PermissionManager pm1 = user1.getPermissionManager(); - looperThread.closeAfterTest(pm1); - - // Create request for giving `user2` WRITE permissions to `user1`'s Realm. - UserCondition condition = UserCondition.username(user2Username); - AccessLevel accessLevel = AccessLevel.WRITE; - String url = createRemoteRealm(user1, "test"); - PermissionRequest request = new PermissionRequest(condition, url, accessLevel); - - pm1.applyPermissions(request, new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - PermissionManager pm2 = user2.getPermissionManager(); - looperThread.closeAfterTest(pm2); - pm2.getPermissions(new PermissionManager.PermissionsCallback() { - @Override - public void onSuccess(RealmResults permissions) { - assertPermissionPresent(permissions, user2, user1.getIdentity() + "/test", AccessLevel.WRITE); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void applyPermissions_usersWithNoExistingPermissions() { - final SyncUser user1 = UserFactory.createUser("user1@realm.io"); - final SyncUser user2 = UserFactory.createUser("user2@realm.io"); - PermissionManager pm1 = user1.getPermissionManager(); - looperThread.closeAfterTest(pm1); - - // Create request for giving all users with no existing permissions WRITE permissions to `user1`'s Realm. - UserCondition condition = UserCondition.noExistingPermissions(); - AccessLevel accessLevel = AccessLevel.WRITE; - final String url = createRemoteRealm(user1, "test"); - PermissionRequest request = new PermissionRequest(condition, url, accessLevel); - - pm1.applyPermissions(request, new PermissionManager.ApplyPermissionsCallback() { - @Override - public void onSuccess() { - // Default permissions are not recorded in the __permission Realm for user2 - // Only way to check is by opening the Realm. - SyncConfiguration config = user2.createConfiguration(url) - .schema(AllJavaTypes.class) - .waitForInitialRemoteData() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail(error.toString()); - } - }) - .build(); - - RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { - @Override - public void onSuccess(Realm realm) { - realm.close(); - looperThread.testComplete(); - } - - @Override - public void onError(Throwable exception) { - fail(exception.toString()); - } - }); - looperThread.keepStrongReference(task); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void makeOffer() { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - String url = createRemoteRealm(user, "test"); - - PermissionOffer offer = new PermissionOffer(url, AccessLevel.WRITE); - pm.makeOffer(offer, new PermissionManager.MakeOfferCallback() { - @Override - public void onSuccess(String offerToken) { - assertNotNull(offerToken); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void makeOffer_noManageAccessThrows() { - // User 2 creates a Realm - SyncUser user2 = UserFactory.createUniqueUser(); - String url = createRemoteRealm(user2, "test"); - - // User 1 tries to create an offer for it. - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - PermissionOffer offer = new PermissionOffer(url, AccessLevel.WRITE); - pm.makeOffer(offer, new PermissionManager.MakeOfferCallback() { - @Override - public void onSuccess(String offerToken) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void acceptOffer() { - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - - final SyncUser user2 = UserFactory.createUniqueUser(); - final PermissionManager pm = user2.getPermissionManager(); - looperThread.closeAfterTest(pm); - - pm.acceptOffer(offerToken, new PermissionManager.AcceptOfferCallback() { - @Override - public void onSuccess(String url, Permission permission) { - assertEquals("/" + user.getIdentity() + "/test", permission.getPath()); - assertTrue(permission.mayRead()); - assertTrue(permission.mayWrite()); - assertFalse(permission.mayManage()); - assertEquals(user2.getIdentity(), permission.getUserId()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void acceptOffer_invalidToken() { - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - pm.acceptOffer("wrong-token", new PermissionManager.AcceptOfferCallback() { - @Override - public void onSuccess(String url, Permission permission) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.INVALID_PARAMETERS, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - @Ignore("The offer is randomly accepted mostly on docker-02 SHIELD K1") - public void acceptOffer_expiredThrows() { - // Trying to guess how long CI is to process this. The offer cannot be created if it - // already expired. - long delayMillis = TimeUnit.SECONDS.toMillis(10); - Date expiresAt = new Date(new Date().getTime() + delayMillis); - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, expiresAt); - SystemClock.sleep(delayMillis); // Make sure that the offer expires. - final SyncUser user2 = UserFactory.createUniqueUser(); - final PermissionManager pm = user2.getPermissionManager(); - looperThread.closeAfterTest(pm); - - pm.acceptOffer(offerToken, new PermissionManager.AcceptOfferCallback() { - @Override - public void onSuccess(String url, Permission permission) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.EXPIRED_PERMISSION_OFFER, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void acceptOffer_multipleUsers() { - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - - final SyncUser user2 = UserFactory.createUniqueUser(); - final SyncUser user3 = UserFactory.createUniqueUser(); - final PermissionManager pm2 = user2.getPermissionManager(); - final PermissionManager pm3 = user3.getPermissionManager(); - looperThread.closeAfterTest(pm2); - looperThread.closeAfterTest(pm3); - - final AtomicInteger offersAccepted = new AtomicInteger(0); - PermissionManager.AcceptOfferCallback callback = new PermissionManager.AcceptOfferCallback() { - @Override - public void onSuccess(String url, Permission permission) { - assertEquals("/" + user.getIdentity() + "/test", permission.getPath()); - assertTrue(permission.mayRead()); - assertTrue(permission.mayWrite()); - assertFalse(permission.mayManage()); - if (offersAccepted.incrementAndGet() == 2) { - looperThread.testComplete(); - } - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }; - - pm2.acceptOffer(offerToken, callback); - pm3.acceptOffer(offerToken, callback); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void getCreatedOffers() { - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - pm.getCreatedOffers(new PermissionManager.OffersCallback() { - @Override - public void onSuccess(RealmResults offers) { - RealmResults filteredOffers = offers.where() - .equalTo("token", offerToken) - .findAllAsync(); - looperThread.keepStrongReference(offers); - filteredOffers.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(RealmResults results) { - switch (results.size()) { - case 0: return; - case 1: - looperThread.testComplete(); - break; - default: - fail("To many offers: " + Arrays.toString(results.toArray())); - } - } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void revokeOffer() { - // createOffer validates that the offer is actually in the __management Realm. - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - final PermissionManager pm = user.getPermissionManager(); - looperThread.closeAfterTest(pm); - - pm.revokeOffer(offerToken, new PermissionManager.RevokeOfferCallback() { - @Override - public void onSuccess() { - pm.getCreatedOffers(new PermissionManager.OffersCallback() { - @Override - public void onSuccess(RealmResults offers) { - assertEquals(0, offers.size()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void revokeOffer_afterOneAcceptEdit() { - // createOffer validates that the offer is actually in the __management Realm. - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - - SyncUser user2 = UserFactory.createUniqueUser(); - SyncUser user3 = UserFactory.createUniqueUser(); - final PermissionManager pm1 = user.getPermissionManager(); - PermissionManager pm2 = user2.getPermissionManager(); - final PermissionManager pm3 = user3.getPermissionManager(); - looperThread.closeAfterTest(pm1); - looperThread.closeAfterTest(pm2); - looperThread.closeAfterTest(pm3); - - pm2.acceptOffer(offerToken, new PermissionManager.AcceptOfferCallback() { - @Override - public void onSuccess(String realmUrl, Permission permission) { - pm1.revokeOffer(offerToken, new PermissionManager.RevokeOfferCallback() { - @Override - public void onSuccess() { - pm3.acceptOffer(offerToken, new PermissionManager.AcceptOfferCallback() { - @Override - public void onSuccess(String realmUrl, Permission permission) { - fail("Offer should have been revoked"); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.INVALID_PARAMETERS, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - @Ignore("Figure out why clocks on server/emulator on CI seem to differ") - public void revokeOffer_alreadyExpired() { - fail("Implement this"); - } - - /** - * Creates a offer for a newly created Realm. - * - * @param user User that should create the offer - * @param realmName Realm to create - * @param level accessLevel to offer - * @param expires when the offer expires - */ - private String createOffer(final SyncUser user, final String realmName, final AccessLevel level, final Date expires) { - final CountDownLatch offerReady = new CountDownLatch(1); - final AtomicReference offer = new AtomicReference<>(null); - final HandlerThread ht = new HandlerThread("OfferThread"); - ht.start(); - Handler handler = new Handler(ht.getLooper()); - handler.post(new Runnable() { - @Override - public void run() { - String url = createRemoteRealm(user, realmName); - final PermissionManager pm = user.getPermissionManager(); - pm.makeOffer(new PermissionOffer(url, level, expires), new PermissionManager.MakeOfferCallback() { - @Override - public void onSuccess(String offerToken) { - offer.set(offerToken); - pm.close(); - offerReady.countDown(); - } - - @Override - public void onError(ObjectServerError error) { - pm.close(); - fail(error.toString()); - } - }); - } - }); - TestHelper.awaitOrFail(offerReady); - ht.quit(); - return offer.get(); - } - - /** - * Wait for a given permission to be present. - * - * @param permissions permission results. - * @param user user that is being granted the permission. - * @param urlSuffix the url suffix to listen for. - * @param accessLevel the expected access level for 'user'. - */ - private void assertPermissionPresent(RealmResults permissions, final SyncUser user, String urlSuffix, final AccessLevel accessLevel) { - RealmResults filteredPermissions = permissions.where().endsWith("path", urlSuffix).findAllAsync(); - looperThread.keepStrongReference(permissions); - filteredPermissions.addChangeListener(new RealmChangeListener>() { - @Override - public void onChange(RealmResults permissions) { - switch(permissions.size()) { - case 0: return; - case 1: - Permission p = permissions.first(); - assertEquals(accessLevel.mayRead(), p.mayRead()); - assertEquals(accessLevel.mayWrite(), p.mayWrite()); - assertEquals(accessLevel.mayManage(), p.mayManage()); - assertEquals(user.getIdentity(), p.getUserId()); - looperThread.testComplete(); - break; - default: - fail("To many permissions matched: " + Arrays.toString(permissions.toArray())); - } - } - }); - } - - private void setRealmError(PermissionManager pm, String fieldName, ObjectServerError error) throws NoSuchFieldException, - IllegalAccessException { - Field managementRealmErrorField = pm.getClass().getDeclaredField(fieldName); - managementRealmErrorField.setAccessible(true); - managementRealmErrorField.set(pm, error); - } - - private void runTask(final PermissionManager pm, final PermissionManager.ApplyPermissionsCallback callback) { - new PermissionManager.PermissionManagerTask(pm, callback) { - @Override - public void run() { - if (!checkAndReportInvalidState()) { - fail(); - } - } - }.run(); - } - - /** - * Creates an empty remote Realm on ROS owned by the provided user - */ - private String createRemoteRealm(SyncUser user, String realmName) { - String url = Constants.AUTH_SERVER_URL + "~/" + realmName; - SyncConfiguration config = user.createConfiguration(url) - .name(realmName) - .schema(AllJavaTypes.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - - Realm realm = Realm.getInstance(config); - SyncSession session = SyncManager.getSession(config); - final CountDownLatch uploadLatch = new CountDownLatch(1); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - uploadLatch.countDown(); - } - } - }); - TestHelper.awaitOrFail(uploadLatch); - realm.close(); - return config.getServerUrl().toString(); - } - - /** - * The initial set of permissions of ROS is timing dependant. This method will identify the possible known starting - * states and fail if neither of these can be verified. - */ - private void assertInitialPermissions(RealmResults permissions) { - assertEquals("Unexpected count() for __permission Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__permission").count()); - assertEquals("Unexpected count() for __management Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__management").count()); - } - - private void assertInitialDefaultPermissions(RealmResults permissions) { - assertEquals("Unexpected count() for __wildcardpermissions Realm: " + Arrays.toString(permissions.toArray()), 1, permissions.where().endsWith("path", "__wildcardpermissions").count()); - } - - private void assertGreaterThan(String error, int base, long count) { - if (count <= base) { - throw new AssertionError(error); - } - } - -} diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index edcdc08940..24fde4982f 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6.11.4 +FROM node:10 # set timezone to Copenhagen (by default it's using UTC) to match Android's device time. RUN cp /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime diff --git a/tools/sync_test_server/integration-test-command-server.js b/tools/sync_test_server/integration-test-command-server.js index f11c0cb7a3..826635747f 100755 --- a/tools/sync_test_server/integration-test-command-server.js +++ b/tools/sync_test_server/integration-test-command-server.js @@ -136,6 +136,7 @@ function startRealmObjectServer(onSuccess, onError) { }, onError) } +// FIXME: This method seems broken in Node 10 and/or latest version of ROS function stopRealmObjectServer(onSuccess, onError) { if(syncServerChildProcess == null || syncServerChildProcess.killed) { onSuccess("No ROS process found or the process has been killed before"); @@ -161,7 +162,7 @@ function stopRealmObjectServer(onSuccess, onError) { onSuccess(); }); }); - syncServerChildProcess.kill('SIGINT'); + syncServerChildProcess.kill('SIGTERM'); }); } diff --git a/version.txt b/version.txt index 4f9633e8a5..5f68295fc1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.16.0-SNAPSHOT \ No newline at end of file +6.0.0-SNAPSHOT \ No newline at end of file From 62325ca5ae668b1e53266a54e0a9ac47c6147b84 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 30 Sep 2019 14:34:00 +0200 Subject: [PATCH 1426/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df3a5b213d..8dd61bf7b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 6.0.0(YYYY-MM-DD) +## 6.0.0(2019-10-01) ### Breaking Changes * [ObjectServer] The `PermissionManager` is no longer backed by Realms but instead a REST API. This means that the `PermissionManager` class has been removed and all methods have been moved to `SyncUser`. Some method names have been renamed slightly and return values for methods have changed from `RealmResults` to `List`. This should only have an impact if change listeners were used to listen for changes. In these cases, you must now manually retry the request. From 8a022573a5b095c2ec887720bd73098475829766 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 30 Sep 2019 14:39:25 +0200 Subject: [PATCH 1427/2110] Release v6.0.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5f68295fc1..f4965a313a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.0.0-SNAPSHOT \ No newline at end of file +6.0.0 \ No newline at end of file From 242bd56a2cdefb987377aa0aa5e61516ed492937 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 30 Sep 2019 14:39:25 +0200 Subject: [PATCH 1428/2110] Prepare next release v6.0.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f4965a313a..89648de331 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.0.0 \ No newline at end of file +6.0.1-SNAPSHOT \ No newline at end of file From 3383237cd83990c667788d6e95283d0ff85d914b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 1 Oct 2019 09:01:45 +0200 Subject: [PATCH 1429/2110] Prepare for next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 89648de331..66672d4e9d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.0.1-SNAPSHOT \ No newline at end of file +6.1.0-SNAPSHOT \ No newline at end of file From b96e28a0fcaa9f795b02efbc1a795ba750c797af Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 1 Nov 2019 09:14:43 +0100 Subject: [PATCH 1430/2110] Upgrade benchmarks to use latest release (#6651) --- library-benchmarks/build.gradle | 22 ++++++++++++++----- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../realm/benchmarks/CopyToRealmBenchmarks.kt | 4 ++-- .../CopyToRealmOrUpdateBenchmarks.kt | 4 ++-- .../realm/benchmarks/RealmAllocBenchmarks.kt | 4 ++-- .../io/realm/benchmarks/RealmBenchmarks.kt | 4 ++-- .../realm/benchmarks/RealmInsertBenchmark.kt | 4 ++-- .../benchmarks/RealmObjectReadBenchmarks.kt | 4 ++-- .../benchmarks/RealmObjectWriteBenchmarks.kt | 4 ++-- .../realm/benchmarks/RealmQueryBenchmarks.kt | 4 ++-- .../benchmarks/RealmResultsBenchmarks.kt | 4 ++-- 11 files changed, 36 insertions(+), 24 deletions(-) diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index dfd0d86713..4f85e462c6 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.31' + ext.kotlin_version = '1.3.50' def properties = new Properties() properties.load(new FileInputStream("${rootDir}/../dependencies.list")) @@ -9,6 +9,7 @@ buildscript { jcenter() } dependencies { + classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0-rc01" classpath "com.android.tools.build:gradle:${properties.get("GRADLE_BUILD_TOOLS")}" classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" @@ -24,10 +25,11 @@ allprojects { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' apply plugin: 'realm-android' +apply plugin: 'androidx.benchmark' android { compileSdkVersion 28 @@ -38,7 +40,8 @@ android { targetSdkVersion 28 versionCode 1 versionName "1.0" - testInstrumentationRunner "androidx.benchmark.AndroidBenchmarkRunner" + testInstrumentationRunner "androidx.benchmark.junit4.AndroidBenchmarkRunner" + testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'EMULATOR,UNLOCKED' } buildTypes { @@ -48,6 +51,15 @@ android { debuggable = false } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } } repositories { @@ -57,8 +69,8 @@ repositories { } dependencies { - androidTestImplementation 'androidx.test.ext:junit:1.1.0' - androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01" + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0-rc01" androidTestImplementation 'junit:junit:4.12' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..3a54a3332e 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt index 9f884e76ca..9f26af4bf4 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt @@ -15,8 +15,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt index 912c9266bb..c0466e36f2 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt @@ -15,8 +15,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.ImportFlag diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt index cd69171559..2ed2f0c479 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt index ec7862954f..b8e197c0f8 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt index dc22dd4e1c..43cd6741e7 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt index 42642f4d91..2e8bf9f83e 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt index d09efc10bf..8146565558 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm import io.realm.RealmConfiguration diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt index cc5da680d4..cff511474c 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm import io.realm.RealmConfiguration diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt index 0b5f293bbb..8151e1890e 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm From 0c80dfc9a467914b845954aee74f7ffbcc13d60d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 5 Nov 2019 11:34:52 +0100 Subject: [PATCH 1431/2110] Upgrade sync + fix encryption race (#6647) --- CHANGELOG.md | 24 ++++++++ dependencies.list | 4 +- .../java/io/realm/RealmResultsTests.java | 55 +++--------------- .../androidTest/java/io/realm/RealmTests.java | 56 +++++++++++++++++++ .../java/io/realm/entities/AllTypes.java | 4 ++ 5 files changed, 93 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd61bf7b3..c36367b051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 6.0.1(YYYY-MM-DD) + +NOTE: Anyone using encrypted Realms are strongly advised to upgrade to this version. + +### Enhancements +* None + +### Fixed +* When using encrypted Realms a race condition could lead to the Realm ending up corrupted when the file increased in size. This could manifest as a wide array of different error messages. Most commonly seen has been "Fatal signal 11 (SIGSEGV) from Java_io_realm_internal_UncheckedRow_nativeGetString", "RealmFileException: Top ref outside file" and "Unable to open a realm at path. ACCESS_ERROR: Invalid mnemonic". ([#6152](https://github.com/realm/realm-java/issues/6152), since 5.0.0) +* `RealmResults.asJSON()` now prints lists with primitive values directly instead of wrapping each value in an object with an `!ARRAY_VALUE` property. + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. + +### Internal +* Updated to Realm Sync 4.7.12. +* Updated to Realm Core 5.23.6. + +### Credits +* Thanks to Vladimir Konkov (@vladimirfx) for help with isolating ([#6152]()). + + ## 6.0.0(2019-10-01) ### Breaking Changes diff --git a/dependencies.list b/dependencies.list index 777e0f4081..3b8e7253fb 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=4.7.8 -REALM_SYNC_SHA256=d7453f2296e23fd29a9c7f6fd1225e6905bbc05b8c24d4f45308ad5dd8918f03 +REALM_SYNC_VERSION=4.7.12 +REALM_SYNC_SHA256=2c684c76165f3fafe14fa48a328a5b89ca837f3026e6856cbb8a651c55fc5cb8 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index fa62390c24..b3f3ec079e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1738,6 +1738,7 @@ public void asJSON() throws JSONException { allTypes.setColumnBoolean(false); allTypes.setColumnDate(date); allTypes.setColumnBinary(new byte[]{1, 2, 3}); + allTypes.setColumnMutableRealmInteger(0); allTypes.setColumnRealmObject(dog1); allTypes.getColumnRealmList().add(dog2); allTypes.getColumnRealmList().add(dog3); @@ -1809,55 +1810,13 @@ public void asJSON() throws JSONException { " ]\n" + " }\n" + " ],\n" + - " \"columnStringList\": [\n" + - " {\n" + - " \"!ARRAY_VALUE\": \"Foo\"\n" + - " },\n" + - " {\n" + - " \"!ARRAY_VALUE\": \"Bar\"\n" + - " }\n" + - " ],\n" + + " \"columnStringList\": [ \"Foo\", \"Bar\" ]," + " \"columnBinaryList\": [],\n" + - " \"columnBooleanList\": [\n" + - " {\n" + - " \"!ARRAY_VALUE\": false\n" + - " },\n" + - " {\n" + - " \"!ARRAY_VALUE\": true\n" + - " }\n" + - " ],\n" + - " \"columnLongList\": [\n" + - " {\n" + - " \"!ARRAY_VALUE\": 1000\n" + - " },\n" + - " {\n" + - " \"!ARRAY_VALUE\": 2000\n" + - " }\n" + - " ],\n" + - " \"columnDoubleList\": [\n" + - " {\n" + - " \"!ARRAY_VALUE\": 1.123\n" + - " },\n" + - " {\n" + - " \"!ARRAY_VALUE\": 5.3209999999999997\n" + - " }\n" + - " ],\n" + - " \"columnFloatList\": [\n" + - " {\n" + - " \"!ARRAY_VALUE\": 0.12\n" + - " },\n" + - " {\n" + - " \"!ARRAY_VALUE\": 0.13\n" + - " }\n" + - " ],\n" + - " \"columnDateList\": [\n" + - " {\n" + - " \"!ARRAY_VALUE\": \"" + now + "\"\n" + - " },\n" + - " {\n" + - " \"!ARRAY_VALUE\": \"" + now + "\"\n" + - " }\n" + - " ]\n" + + " \"columnBooleanList\": [ false, true ],\n" + + " \"columnLongList\": [ 1000, 2000 ],\n" + + " \"columnDoubleList\": [ 1.123, 5.3209999999999997 ],\n" + + " \"columnFloatList\": [ 0.12, 0.13 ],\n" + + " \"columnDateList\": [ \"" + now + "\", \"" + now + "\"]\n" + " }\n" + "]"; JSONAssert.assertEquals(expectedJSON, json, false); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index c6065d7da8..84ff3c5112 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -4573,4 +4573,60 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Realm.deleteRealm(config); } } + + // Test for https://github.com/realm/realm-java/issues/6152 + @Test + @RunTestInLooperThread + public void encryption_stressTest() { + final int WRITER_TRANSACTIONS = 50; + final int TEST_OBJECTS = 100_000; + final int MAX_STRING_LENGTH = 1000; + final AtomicInteger id = new AtomicInteger(0); + long seed = System.nanoTime(); + Random random = new Random(seed); + + RealmConfiguration config = looperThread.createConfigurationBuilder() + .encryptionKey(TestHelper.getRandomKey(seed)) + .build(); + + Thread t = new Thread(new Runnable() { + @Override + public void run() { + Realm realm = Realm.getInstance(config); + for (int i = 0; i < WRITER_TRANSACTIONS; i++) { + realm.executeTransaction(r -> { + for (int j = 0; j < (TEST_OBJECTS / WRITER_TRANSACTIONS); j++) { + AllJavaTypes obj = new AllJavaTypes(id.incrementAndGet()); + obj.setFieldString(TestHelper.getRandomString(random.nextInt(MAX_STRING_LENGTH))); + r.insert(obj); + } + }); + } + realm.close(); + } + }); + t.start(); + + Realm realm = Realm.getInstance(config); + looperThread.closeAfterTest(realm); + RealmResults results = realm.where(AllJavaTypes.class).findAllAsync(); + looperThread.keepStrongReference(results); + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults results, OrderedCollectionChangeSet changeSet) { + for (AllJavaTypes obj : results) { + String s = obj.getFieldString(); + } + + if (results.size() == TEST_OBJECTS) { + try { + t.join(5000); + } catch (InterruptedException e) { + fail("workerthread failed to finish in time."); + } + looperThread.testComplete(); + } + } + }); + } } diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java index f4322445aa..c91fdbe2b3 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java @@ -129,6 +129,10 @@ public MutableRealmInteger getColumnRealmInteger() { return columnMutableRealmInteger; } + public void setColumnMutableRealmInteger(int value) { + columnMutableRealmInteger.set(value); + } + public void setColumnBinary(byte[] columnBinary) { this.columnBinary = columnBinary; } From becb496de8eb293c55317d37bacfbfd7f2b79b79 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 11 Nov 2019 09:02:41 +0100 Subject: [PATCH 1432/2110] Update release date --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c36367b051..016f4bd390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 6.0.1(YYYY-MM-DD) +## 6.0.1(2019-11-11) NOTE: Anyone using encrypted Realms are strongly advised to upgrade to this version. @@ -19,7 +19,7 @@ NOTE: Anyone using encrypted Realms are strongly advised to upgrade to this vers * Updated to Realm Core 5.23.6. ### Credits -* Thanks to Vladimir Konkov (@vladimirfx) for help with isolating ([#6152]()). +* Thanks to Vladimir Konkov (@vladimirfx) for help with isolating ([#6152](https://github.com/realm/realm-java/issues/6152)). ## 6.0.0(2019-10-01) From ee3c88c6d53c2795f4d41206dc8fab47b6f411b0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 11 Nov 2019 09:03:49 +0100 Subject: [PATCH 1433/2110] Release v6.0.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 89648de331..6d54bbd775 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.0.1-SNAPSHOT \ No newline at end of file +6.0.1 \ No newline at end of file From 121f96576209faec5e923ee9c6489e65ab499865 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 11 Nov 2019 09:03:49 +0100 Subject: [PATCH 1434/2110] Prepare next release v6.0.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 6d54bbd775..d72928291c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.0.1 \ No newline at end of file +6.0.2-SNAPSHOT \ No newline at end of file From b475b254d7847b27afe880dd242ebb35a1e1dfd3 Mon Sep 17 00:00:00 2001 From: Anoop S S Date: Wed, 20 Nov 2019 21:58:16 +0530 Subject: [PATCH 1435/2110] Updated README (#6662) Updated to point to latest stable version of Android Studio. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de05f96a84..df53f892bf 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * The latest stable version of Android Studio. Currently [3.1.4](https://developer.android.com/studio/). + * The latest stable version of Android Studio. Currently [3.5.2](https://developer.android.com/studio/). * Download & install the Android SDK **Build-Tools 27.0.2**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). From 92202120a59716c6e2053fdb26cdb6fd34765553 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 20 Nov 2019 20:21:56 +0100 Subject: [PATCH 1436/2110] Enable progress listeners for Realms using waitForInitialRemoteData (#6659) --- CHANGELOG.md | 17 ++++ library-benchmarks/build.gradle | 22 +++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../realm/benchmarks/CopyToRealmBenchmarks.kt | 4 +- .../CopyToRealmOrUpdateBenchmarks.kt | 4 +- .../realm/benchmarks/RealmAllocBenchmarks.kt | 4 +- .../io/realm/benchmarks/RealmBenchmarks.kt | 4 +- .../realm/benchmarks/RealmInsertBenchmark.kt | 4 +- .../benchmarks/RealmObjectReadBenchmarks.kt | 4 +- .../benchmarks/RealmObjectWriteBenchmarks.kt | 4 +- .../realm/benchmarks/RealmQueryBenchmarks.kt | 4 +- .../benchmarks/RealmResultsBenchmarks.kt | 4 +- .../src/main/cpp/io_realm_SyncManager.cpp | 10 +++ .../src/main/cpp/io_realm_SyncSession.cpp | 4 +- ...m_internal_objectstore_OsAsyncOpenTask.cpp | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/RealmCache.java | 5 ++ .../io/realm/internal/ObjectServerFacade.java | 4 + .../java/io/realm/SyncManager.java | 13 ++- .../internal/SyncObjectServerFacade.java | 9 ++ .../io/realm/SyncedRealmIntegrationTests.java | 82 +++++++++++++++++-- 21 files changed, 173 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 016f4bd390..c57098d60b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 6.0.2(YYYY-MM-DD) + +### Enhancements +* None + +### Fixed +* [ObjectServer] `SyncSession` progress listeners now work correctly in combination with `SyncConfiguration.waitForInitialRemoteData()`. + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. + +### Internal +* Updated to Object Store commit: ad96a4c334b475dd67d50c1ca419e257d7a21e18. + + ## 6.0.1(2019-11-11) NOTE: Anyone using encrypted Realms are strongly advised to upgrade to this version. diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index dfd0d86713..4f85e462c6 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.31' + ext.kotlin_version = '1.3.50' def properties = new Properties() properties.load(new FileInputStream("${rootDir}/../dependencies.list")) @@ -9,6 +9,7 @@ buildscript { jcenter() } dependencies { + classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0-rc01" classpath "com.android.tools.build:gradle:${properties.get("GRADLE_BUILD_TOOLS")}" classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" @@ -24,10 +25,11 @@ allprojects { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' apply plugin: 'realm-android' +apply plugin: 'androidx.benchmark' android { compileSdkVersion 28 @@ -38,7 +40,8 @@ android { targetSdkVersion 28 versionCode 1 versionName "1.0" - testInstrumentationRunner "androidx.benchmark.AndroidBenchmarkRunner" + testInstrumentationRunner "androidx.benchmark.junit4.AndroidBenchmarkRunner" + testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'EMULATOR,UNLOCKED' } buildTypes { @@ -48,6 +51,15 @@ android { debuggable = false } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } } repositories { @@ -57,8 +69,8 @@ repositories { } dependencies { - androidTestImplementation 'androidx.test.ext:junit:1.1.0' - androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01" + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0-rc01" androidTestImplementation 'junit:junit:4.12' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..3a54a3332e 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt index 9f884e76ca..9f26af4bf4 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmBenchmarks.kt @@ -15,8 +15,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt index 912c9266bb..c0466e36f2 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/CopyToRealmOrUpdateBenchmarks.kt @@ -15,8 +15,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.ImportFlag diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt index cd69171559..2ed2f0c479 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt index ec7862954f..b8e197c0f8 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt index dc22dd4e1c..43cd6741e7 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmInsertBenchmark.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt index 42642f4d91..2e8bf9f83e 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectReadBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt index d09efc10bf..8146565558 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmObjectWriteBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm import io.realm.RealmConfiguration diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt index cc5da680d4..cff511474c 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmQueryBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm import io.realm.RealmConfiguration diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt index 0b5f293bbb..8151e1890e 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmResultsBenchmarks.kt @@ -16,8 +16,8 @@ package io.realm.benchmarks -import androidx.benchmark.BenchmarkRule -import androidx.benchmark.measureRepeated +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.Realm diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index ea9aedeba9..273e25e667 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -138,3 +139,12 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReconnect(JNIEnv* env, jc } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeCreateSession(JNIEnv* env, jclass, jlong j_native_config_ptr) +{ + try { + auto& config = *reinterpret_cast(j_native_config_ptr); + _impl::RealmCoordinator::get_coordinator(config)->create_session(config); + } + CATCH_STD() +} \ No newline at end of file diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 35ef67a028..5276742aca 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -90,7 +90,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIE try { // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda std::string local_realm_path(JStringAccessor(env, j_local_realm_path)); - std::shared_ptr session = SyncManager::shared().get_existing_active_session(local_realm_path); + std::shared_ptr session = SyncManager::shared().get_existing_session(local_realm_path); if (!session) { // FIXME: We should lift this restriction ThrowException(env, IllegalState, @@ -137,7 +137,7 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveProgressListener(JN { try { JStringAccessor local_realm_path(env, j_local_realm_path); - std::shared_ptr session = SyncManager::shared().get_existing_active_session(local_realm_path); + std::shared_ptr session = SyncManager::shared().get_existing_session(local_realm_path); if (session) { session->unregister_progress_notifier(static_cast(listener_token)); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp index f48435bf64..1432ab4715 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp @@ -58,7 +58,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAsyncOpenTask_start } catch (const std::exception& e) { jstring j_error_msg = to_jstring(local_env, e.what()); - local_env->CallObjectMethod(task.get(), java_notify_error, j_error_msg); + local_env->CallVoidMethod(task.get(), java_notify_error, j_error_msg); local_env->DeleteLocalRef(j_error_msg); } } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 8416010e4b..ad96a4c334 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 8416010e4be5e32ba552ff3fb29e500f3102d3db +Subproject commit ad96a4c334b475dd67d50c1ca419e257d7a21e18 diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 4a2ac0a7c1..863de72413 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -268,6 +268,11 @@ private synchronized RealmAsyncTask doCreateRealmOrGetFrom Future future = BaseRealm.asyncTaskExecutor.submitTransaction(createRealmRunnable); createRealmRunnable.setFuture(future); + // For Realms using Async Open on the server, we need to create the session right away + // in order to interact with it in a imperative way, e.g. by attaching download progress + // listeners + ObjectServerFacade.getSyncFacadeIfPossible().createNativeSyncSession(configuration); + return new RealmAsyncTaskImpl(future, BaseRealm.asyncTaskExecutor); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 5cc12b9ca5..a345f74170 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -134,4 +134,8 @@ public void addSupportForObjectLevelPermissions(RealmConfiguration.Builder build public void downloadInitialSubscriptions(Realm realm) { // Do nothing } + + public void createNativeSyncSession(RealmConfiguration configuration) { + // Do nothing + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 38f08495ad..27f5e4975b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -45,6 +45,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.Keep; +import io.realm.internal.OsRealmConfig; import io.realm.internal.Util; import io.realm.internal.network.RealmObjectServer; import io.realm.internal.network.NetworkStateReceiver; @@ -253,7 +254,7 @@ public static synchronized SyncSession getSession(SyncConfiguration syncConfigur public static synchronized SyncSession getOrCreateSession(SyncConfiguration syncConfiguration, @Nullable URI resolvedRealmURL) { // This will not create a new native (Object Store) session, this will only associate a Realm's path // with a SyncSession. Object Store's SyncManager is responsible of the life cycle (including creation) - // of the native session, the provided Java wrap, helps interact with the native session, when reporting error + // of the native session. The provided Java wrap, helps interact with the native session, when reporting error // or requesting an access_token for example. //noinspection ConstantConditions @@ -267,7 +268,7 @@ public static synchronized SyncSession getOrCreateSession(SyncConfiguration sync session = new SyncSession(syncConfiguration); sessions.put(syncConfiguration.getPath(), session); if (sessions.size() == 1) { - RealmLog.debug("first session created add network listener"); + RealmLog.debug("First session created. Adding network listener."); NetworkStateReceiver.addListener(networkListener); } if (resolvedRealmURL != null) { @@ -279,6 +280,13 @@ public static synchronized SyncSession getOrCreateSession(SyncConfiguration sync // syncing. session.getAccessToken(authServer, ""); } + + // The underlying session will be created as part of opening the Realm, but this approach + // does not work when using `Realm.getInstanceAsync()` in combination with AsyncOpen. + // + // So instead we manually create the underlying native session. + OsRealmConfig config = new OsRealmConfig.Builder(syncConfiguration).build(); + nativeCreateSession(config.getNativePtr()); } return session; @@ -766,4 +774,5 @@ static void simulateClientReset(SyncSession session) { private static native void nativeReset(); private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); private static native void nativeReconnect(); + private static native void nativeCreateSession(long nativeConfigPtr); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 3ad43b189a..62f38ff221 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -290,4 +290,13 @@ public void downloadInitialSubscriptions(Realm realm) { } } } + + @Override + public void createNativeSyncSession(RealmConfiguration configuration) { + if (configuration instanceof SyncConfiguration) { + SyncConfiguration syncConfig = (SyncConfiguration) configuration; + OsRealmConfig config = new OsRealmConfig.Builder(syncConfig).build(); + SyncManager.getOrCreateSession(syncConfig, config.getResolvedRealmURI()); + } + } } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index f57fdca082..2ac3f5a273 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -20,7 +20,6 @@ import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; -import org.hamcrest.CoreMatchers; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,12 +29,9 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; -import javax.annotation.Nullable; - import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; import io.realm.exceptions.DownloadingRealmInterruptedException; -import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.OsRealmConfig; import io.realm.log.LogLevel; import io.realm.log.RealmLog; @@ -45,7 +41,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -482,4 +477,81 @@ public void listenersTriggerWhenOffline() { } }); } + + @Test + @RunTestInLooperThread + public void progressListenersWorkWhenUsingWaitForInitialRemoteData() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() + .schema(StringOnly.class) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build(); + Realm realm = Realm.getInstance(configOld); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + for (int i = 0; i < 10; i++) { + realm.createObject(StringOnly.class).setChars("Foo" + i); + } + } + }); + SyncManager.getSession(configOld).uploadAllLocalChanges(); + realm.close(); + user.logOut(); + assertTrue(SyncManager.getAllSessions(user).isEmpty()); + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) + .name("newRealm") + .fullSynchronization() + .schema(StringOnly.class) + .waitForInitialRemoteData() + .build(); + assertFalse(config.realmExists()); + AtomicBoolean indefineteListenerComplete = new AtomicBoolean(false); + AtomicBoolean currentChangesListenerComplete = new AtomicBoolean(false); + RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { + + @Override + public void onSuccess(Realm realm) { + realm.close(); + if (!indefineteListenerComplete.get()) { + fail("Indefinete progress listener did not report complete."); + } + if (!currentChangesListenerComplete.get()) { + fail("Current changes progress listener did not report complete."); + } + looperThread.testComplete(); + } + + @Override + public void onError(Throwable exception) { + fail(exception.toString()); + } + }); + looperThread.keepStrongReference(task); + SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + indefineteListenerComplete.set(true); + } + } + }); + SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + currentChangesListenerComplete.set(true); + } + } + }); + } } From 9fb012b0d3e9a553b60363ff028ccf91de3bb33f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 20 Nov 2019 20:31:29 +0100 Subject: [PATCH 1437/2110] Fix Proguard setup for release builds using R8 (#6663) --- CHANGELOG.md | 3 ++- realm/realm-library/proguard-rules-consumer-common.pro | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c57098d60b..545fd6f207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ ## 6.0.2(YYYY-MM-DD) ### Enhancements -* None +* None. ### Fixed * [ObjectServer] `SyncSession` progress listeners now work correctly in combination with `SyncConfiguration.waitForInitialRemoteData()`. +* The `@RealmModule` annotation would be stripped on an empty class when using R8 resulting in apps crashing on startup with `io.realm.DefaultRealmModule is not a RealmModule. Add @RealmModule to the class definition.`. ([#6449](https://github.com/realm/realm-java/issues/6449)) ### Compatibility * Realm Object Server: 3.23.1 or later. diff --git a/realm/realm-library/proguard-rules-consumer-common.pro b/realm/realm-library/proguard-rules-consumer-common.pro index fb972bb245..010e4b2bcd 100644 --- a/realm/realm-library/proguard-rules-consumer-common.pro +++ b/realm/realm-library/proguard-rules-consumer-common.pro @@ -1,5 +1,7 @@ -keep class io.realm.annotations.RealmModule -keep @io.realm.annotations.RealmModule class * +-keep @interface io.realm.annotations.RealmModule { *; } +-keep class io.realm.annotations.RealmModule { *; } -keep class io.realm.internal.Keep -keep @io.realm.internal.Keep class * { *; } From 1c9bb7da7080ed4f3ed728266b3934565131dbb0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 21 Nov 2019 13:22:28 +0100 Subject: [PATCH 1438/2110] Update to latest Sync release (#6664) --- CHANGELOG.md | 2 +- dependencies.list | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 545fd6f207..8ec9489394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ ### Internal * Updated to Object Store commit: ad96a4c334b475dd67d50c1ca419e257d7a21e18. - +* Updated to Realm Sync v4.8.3. ## 6.0.1(2019-11-11) diff --git a/dependencies.list b/dependencies.list index 3b8e7253fb..bc3605e4e0 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=4.7.12 -REALM_SYNC_SHA256=2c684c76165f3fafe14fa48a328a5b89ca837f3026e6856cbb8a651c55fc5cb8 +REALM_SYNC_VERSION=4.8.3 +REALM_SYNC_SHA256=b3fa91562eb83a2d90fa600240546e41d8672bdac365ed0d0b55ae1726f2f2bb # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. From 8cf6fe95c01a80f4df3853af70f7c0b92a807487 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 21 Nov 2019 13:25:48 +0100 Subject: [PATCH 1439/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ec9489394..e02b216d31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 6.0.2(YYYY-MM-DD) +## 6.0.2(2019-11-21) ### Enhancements * None. From 72b82f50077fa34e76163c194342064fb4c83232 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 21 Nov 2019 13:26:07 +0100 Subject: [PATCH 1440/2110] Release v6.0.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index d72928291c..7a9f89d81a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.0.2-SNAPSHOT \ No newline at end of file +6.0.2 \ No newline at end of file From 32a80d6f6f2c4a5273bef53e3860a0f61f716640 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 21 Nov 2019 13:26:07 +0100 Subject: [PATCH 1441/2110] Prepare next release v6.0.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7a9f89d81a..48157b2452 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.0.2 \ No newline at end of file +6.0.3-SNAPSHOT \ No newline at end of file From 291eab42c41a4c0ce7e175e00b25b8543de547d1 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair <34319766+sellmair@users.noreply.github.com> Date: Tue, 26 Nov 2019 16:04:20 +0100 Subject: [PATCH 1442/2110] Realm plugin: consider kotlin-multiplatform plugin (#6652) --- gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy index 41398bfdc5..2e70c44ab7 100644 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy @@ -43,7 +43,7 @@ class Realm implements Plugin { def syncEnabledDefault = false def usesAptPlugin = project.plugins.findPlugin('com.neenbedankt.android-apt') != null - def isKotlinProject = project.plugins.findPlugin('kotlin-android') != null + def isKotlinProject = project.plugins.findPlugin('kotlin-android') != null || project.plugins.findPlugin("kotlin-multiplatform") != null def useKotlinExtensionsDefault = isKotlinProject def hasAnnotationProcessorConfiguration = project.getConfigurations().findByName('annotationProcessor') != null // TODO add a parameter in 'realm' block if this should be specified by users From 4e82766ba451acd79fe91bcb2485f9ce5c82d2ac Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 26 Nov 2019 16:10:34 +0100 Subject: [PATCH 1443/2110] Add credits for KMP improvements --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e02b216d31..b7f665b57e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 6.1.0(YYYY-MM-DD) + +### Enhancements +* The Realm Gradle plugin now applies `kapt` when used in Kotlin Multiplatform projects. Note, Realm Java still only works for the Android part of a Kotlin Multiplatform project. (Issue [#6653](https://github.com/realm/realm-java/issues/6653)) + +### Fixed +* None. + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. + +### Internal +* Updated to Object Store commit: ad96a4c334b475dd67d50c1ca419e257d7a21e18. +* Updated to Realm Sync v4.8.3. + +### Credits +* Thanks to @sellmair (Sebastian Sellmair) for improving Kotlin Multiplatform support. + + ## 6.0.2(2019-11-21) ### Enhancements From 16a08c94b71223f48c5cea5cddd9db571c96bcd1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 15 Mar 2018 02:02:25 +0100 Subject: [PATCH 1444/2110] Prepare for next major version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 08565a9fc0..757e674004 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.1.0-SNAPSHOT +7.0.0-SNAPSHOT \ No newline at end of file From b434348c5598ed232cc140047c0765b392b85286 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 17 Apr 2018 14:16:10 +0200 Subject: [PATCH 1445/2110] Query-based sync are now the default mode for Sync (#5893) --- CHANGELOG.md | 7 + .../java/io/realm/SyncConfigurationTests.java | 13 +- .../java/io/realm/SyncConfiguration.java | 28 +- .../objectserver/QueryBasedSyncTests.java | 445 +----------------- 4 files changed, 33 insertions(+), 460 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f665b57e..20dc8decd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 7.0.0 (YYYY-MM-DD) + +### Breaking Changes + +* [ObjectServer] Query-based Sync is now the default mode of synchronization. To enable Full Realm synchronization use `SyncConfiguration.Builder.fullSynchronization()`. `SyncConfiguration.Builder.partialRealm()` has been deprecated. +* [ObjectServer] `SyncConfiguration.isPartialRealm()` has been replaced by `SyncConfiguration.isFullySynchronizedRealm()`. + ## 6.1.0(YYYY-MM-DD) ### Enhancements diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index a1205d6a06..5aa50218f6 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -482,6 +482,17 @@ public void getDefaultConfiguration_throwsIfNotLoggedIn() { } } + @Test + public void automatic_isFullySynchronized() { + SyncUser user = SyncTestUtils.createTestUser(); + + SyncConfiguration config = SyncConfiguration.automatic(); + assertFalse(config.isFullySynchronizedRealm()); + + config = SyncConfiguration.automatic(user); + assertFalse(config.isFullySynchronizedRealm()); + } + @Test public void getDefaultConfiguration_isFullySynchronized() { SyncUser user = createTestUser(); @@ -530,7 +541,7 @@ public void clientResyncMode() { .fullSynchronization() .build(); assertEquals(ClientResyncMode.RECOVER_LOCAL_REALM, config.getClientResyncMode()); - + // Default mode for query-based Realms config = user.createConfiguration(url).build(); assertEquals(ClientResyncMode.MANUAL, config.getClientResyncMode()); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 832fc5ec06..b69cd379bc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -470,17 +470,12 @@ public OsRealmConfig.SyncSessionStopPolicy getSessionStopPolicy() { } /** - * Whether this configuration is for a query-based Realm. - *

            - * Query-based synchronization allows a synchronized Realm to be opened in such a way that - * only objects queried by the user are synchronized to the device. + * Returns whether this configuration is for a fully synchronized Realm or not. * - * @return {@code true} to open a query-based Realm {@code false} otherwise. - * @deprecated use {@link #isFullySynchronizedRealm()} instead. + * @see Builder#fullSynchronization() for more details. */ - @Deprecated - public boolean isPartialRealm() { - return isPartial; + public boolean isFullySynchronizedRealm() { + return !isPartial; } /** @@ -1045,14 +1040,17 @@ public SyncConfiguration.Builder readOnly() { } /** - * Setting this will open a query-based Realm. + * Define this Realm as a fully synchronized Realm. + *

            + * Full synchronization, unlike the default query-based synchronization, will transparently + * synchronize the entire Realm without needing to query for the data. This option is + * useful if the serverside Realm is small and all the data in the Realm should be + * available to the user. * - * @see #isPartialRealm() - * @deprecated Use {@link SyncUser#createConfiguration(String)} instead. + * @see #isFullySynchronizedRealm() () */ - @Deprecated - public SyncConfiguration.Builder partialRealm() { - this.isPartial = true; + public SyncConfiguration.Builder fullSynchronization() { + this.isPartial = false; return this; } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java index 6f2e8c0d20..6865121afa 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java @@ -1,49 +1,33 @@ package io.realm.objectserver; -import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeUnit; - import io.realm.DynamicRealm; import io.realm.OrderedCollectionChangeSet; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmList; -import io.realm.RealmQuery; import io.realm.RealmResults; -import io.realm.Sort; import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncTestUtils; import io.realm.SyncUser; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; -import io.realm.entities.BacklinksSource; -import io.realm.entities.BacklinksTarget; import io.realm.entities.Dog; -import io.realm.log.RealmLog; import io.realm.objectserver.model.PartialSyncModule; import io.realm.objectserver.model.PartialSyncObjectA; import io.realm.objectserver.model.PartialSyncObjectB; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; -import io.realm.sync.Subscription; +import io.realm.util.SyncTestUtils; import static org.hamcrest.number.OrderingComparison.greaterThan; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -165,159 +149,7 @@ public void namedSubscription() throws InterruptedException { } } }); - } - - @Test - @RunTestInLooperThread - public void namedSubscription_update() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - - Date now = new Date(); - SystemClock.sleep(2); - - RealmQuery query1 = realm.where(PartialSyncObjectA.class).greaterThan("number", 5); - RealmResults results = query1.findAllAsync("update-test"); - results.addChangeListener((objects1, changeSet1) -> { - if (changeSet1.isCompleteResult()) { - results.removeAllChangeListeners(); - final Subscription sub1 = realm.getSubscription("update-test"); - final Date firstUpdated = sub1.getUpdatedAt(); - assertEquals(query1.getDescription(), sub1.getQueryDescription()); - assertTrue(now.getTime() < sub1.getUpdatedAt().getTime()); - assertEquals(sub1.getCreatedAt(), sub1.getUpdatedAt()); - assertEquals(Long.MAX_VALUE, sub1.getExpiresAt().getTime()); - assertEquals(Long.MAX_VALUE, sub1.getTimeToLive()); - - SystemClock.sleep(2); - RealmQuery query2 = realm.where(PartialSyncObjectA.class).equalTo("string", "foo"); - RealmResults results2 = query2.findAllAsync("update-test", true); - results2.addChangeListener((objects2, changeSet2) -> { - if (changeSet2.isCompleteResult()) { - assertEquals(query2.getDescription(), sub1.getQueryDescription()); - assertTrue(firstUpdated.getTime() < sub1.getUpdatedAt().getTime()); - looperThread.testComplete(); - } - }); - looperThread.keepStrongReference(results2); - } - }); - looperThread.keepStrongReference(results); - } - - @Test - @RunTestInLooperThread - public void namedSubscription_update_timeToLive() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - - RealmQuery query1 = realm.where(PartialSyncObjectA.class).greaterThan("number", 5); - RealmResults results = query1.findAllAsync("update-test-ttl"); - results.addChangeListener((objects1, changeSet1) -> { - if (changeSet1.isCompleteResult()) { - results.removeAllChangeListeners(); - final Subscription sub1 = realm.getSubscription("update-test-ttl"); - final Date firstUpdatedAt = sub1.getUpdatedAt(); - final Date firstExpiresAt = sub1.getExpiresAt(); - assertEquals(Long.MAX_VALUE, sub1.getExpiresAt().getTime()); - assertEquals(Long.MAX_VALUE, sub1.getTimeToLive()); - - SystemClock.sleep(2); - RealmQuery query2 = realm.where(PartialSyncObjectA.class).equalTo("string", "foo"); - RealmResults results2 = query2.findAllAsync("update-test-ttl", 10, TimeUnit.MILLISECONDS, true); - results2.addChangeListener((objects2, changeSet2) -> { - if (changeSet2.isCompleteResult()) { - assertEquals(10, sub1.getTimeToLive()); - assertTrue(sub1.getExpiresAt().getTime() < firstExpiresAt.getTime()); - assertTrue(firstUpdatedAt.getTime() < sub1.getUpdatedAt().getTime()); - looperThread.testComplete(); - } - }); - looperThread.keepStrongReference(results2); - } - }); - looperThread.keepStrongReference(results); - } - - @Test - @RunTestInLooperThread - public void namedSubscription_withTimeToLive() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - - RealmQuery query = realm.where(PartialSyncObjectA.class); - Date now = new Date(); - Date now_plus_10_sec = new Date(now.getTime() + 10000); - RealmResults results = query.findAllAsync("test-ttl", 5, TimeUnit.SECONDS); - results.addChangeListener((objects, changeSet) -> { - if (changeSet.isCompleteResult()) { - results.removeAllChangeListeners(); - final Subscription sub = realm.getSubscription("test-ttl"); - // Fuzzy check of expiresAt since we don't control exactly when the Subscription is created. - assertTrue(now.getTime() <= sub.getExpiresAt().getTime()); - assertTrue(sub.getExpiresAt().getTime() < now_plus_10_sec.getTime()); - assertEquals(5000, sub.getTimeToLive()); - looperThread.testComplete(); - } - }); - looperThread.keepStrongReference(results); - } - - @Test - @RunTestInLooperThread - public void namedSubscription_update_throwsIfDifferentQueryType() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - - RealmResults results = realm.where(PartialSyncObjectA.class).findAllAsync("type-conflict"); - results.addChangeListener((objects1, changeSet1) -> { - if (changeSet1.isCompleteResult()) { - results.removeAllChangeListeners(); - RealmResults results2 = realm.where(PartialSyncObjectB.class).findAllAsync("type-conflict", true); - results2.addChangeListener((objects2, changeSet2) -> { - if (changeSet2.getState() == OrderedCollectionChangeSet.State.ERROR) { - assertTrue(changeSet2.getError() instanceof IllegalArgumentException); - assertTrue(changeSet2.getError().getMessage().startsWith("Replacing an existing query with a query on a different type is not allowed")); - looperThread.testComplete(); - } - }); - looperThread.keepStrongReference(results2); - } - }); - looperThread.keepStrongReference(results); - } - - @RunTestInLooperThread - public void creatingSubscriptionsAlsoCleanupExpiredSubscriptions() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - RealmResults results = realm.where(PartialSyncObjectA.class).findAllAsync("sub1", 0, TimeUnit.MILLISECONDS); - results.addChangeListener((objects1, changeSet1) -> { - if (changeSet1.isCompleteResult()) { - results.removeAllChangeListeners(); - assertEquals(1, realm.getSubscriptions().size()); - final Subscription firstSub = realm.getSubscription("sub1"); - SystemClock.sleep(2); - - RealmResults results2 = realm.where(PartialSyncObjectB.class).findAllAsync("sub2"); - results2.addChangeListener((objects2, changeSet2) -> { - if (changeSet2.isCompleteResult()) { - assertEquals(1, realm.getSubscriptions().size()); - assertEquals("sub2", realm.getSubscriptions().first().getName()); - assertFalse(firstSub.isValid()); - looperThread.testComplete(); - } - }); - looperThread.keepStrongReference(results2); - } - }); - looperThread.keepStrongReference(results); } @Test @@ -464,280 +296,6 @@ public void clearTable() { looperThread.testComplete(); } - @Test - @RunTestInLooperThread - public void downloadLimitedData() throws InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - createServerData(user, Constants.SYNC_SERVER_URL); - Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - - RealmResults results = realm.where(PartialSyncObjectA.class) - .notEqualTo("string", "") - .distinct("string") - .sort("string", Sort.ASCENDING) - .limit(2) - .findAllAsync(); - looperThread.keepStrongReference(results); - - results.addChangeListener((objects, changeSet) -> { - RealmLog.error(changeSet.getState().toString()); - if (changeSet.getState() == OrderedCollectionChangeSet.State.ERROR) { - RealmLog.error(changeSet.getError().toString()); - } - if (changeSet.isCompleteResult()) { - assertEquals(2, results.size()); - PartialSyncObjectA obj = objects.first(); - assertEquals(6, obj.getNumber()); - assertEquals("partial", obj.getString()); - obj = objects.last(); - assertEquals(0, obj.getNumber()); - assertEquals("realm", obj.getString()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread - public void initialDataAndWaitForRemoteInitialData() throws InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - createServerData(user, Constants.SYNC_SERVER_URL); - - // Create partial Realm that will wait for the subscriptions - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .name("partialSync") - .initialData(r -> { - r.where(PartialSyncObjectA.class).greaterThan("number", 5).subscribe("my-sub"); - }) - .waitForInitialRemoteData() - .modules(new PartialSyncModule()) - .build(); - Realm realm = Realm.getInstance(partialSyncConfig); - looperThread.closeAfterTest(realm); - - // Check the state of subscriptions. Sync automatically creates subscriptions for fine-grained permission classes. - assertEquals(6, realm.getSubscriptions().size()); - assertTrue(realm.getSubscriptions().where().equalTo("status", 0).findAll().isEmpty()); - Subscription sub = realm.getSubscription("my-sub"); - assertEquals(Subscription.State.ACTIVE, sub.getState()); - - // Check that data is downloaded - assertFalse(realm.isEmpty()); - assertEquals(4, realm.where(PartialSyncObjectA.class).findAll().size()); - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - public void unsubscribe_synchronous() throws InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - createServerData(user, Constants.SYNC_SERVER_URL); - - // Create partial Realm that will wait for the subscriptions - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .name("partialSync") - .initialData(r -> { - r.where(PartialSyncObjectA.class).greaterThan("number", 5).subscribe("my-sub"); - }) - .waitForInitialRemoteData() - .addModule(new PartialSyncModule()) - .build(); - Realm realm = Realm.getInstance(partialSyncConfig); - looperThread.closeAfterTest(realm); - - realm.executeTransaction(r -> { - Subscription sub = r.getSubscription("my-sub"); - assertEquals(Subscription.State.ACTIVE, sub.getState()); - sub.unsubscribe(); - assertEquals(Subscription.State.INVALIDATED, sub.getState()); - }); - - // Objects should eventually disappear from the device - RealmResults results = realm.where(PartialSyncObjectA.class).findAll(); - results.addChangeListener((objects, changeSet) -> { - if (objects.isEmpty()) { - looperThread.testComplete(); - } - }); - } - - - @Test - @RunTestInLooperThread - public void deletingSubscriptionObjectUnsubscribes() throws InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - createServerData(user, Constants.SYNC_SERVER_URL); - - // Create partial Realm that will wait for the subscriptions - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .name("partialSync") - .initialData(r -> { - r.where(PartialSyncObjectA.class).greaterThan("number", 5).subscribe("my-sub"); - }) - .waitForInitialRemoteData() - .addModule(new PartialSyncModule()) - .build(); - Realm realm = Realm.getInstance(partialSyncConfig); - looperThread.closeAfterTest(realm); - - realm.executeTransaction(r -> { - Subscription sub = r.getSubscription("my-sub"); - assertEquals(Subscription.State.ACTIVE, sub.getState()); - sub.deleteFromRealm(); // Equivalent of calling `sub.unsubscribe()`. - assertEquals(Subscription.State.INVALIDATED, sub.getState()); - }); - - // Objects should eventually disappear from the device - RealmResults results = realm.where(PartialSyncObjectA.class).findAll(); - results.addChangeListener((objects, changeSet) -> { - if (objects.isEmpty()) { - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread - public void includeLinkingObjects_throwsOnInvalidTypes() { - SyncUser user1 = UserFactory.createUniqueUser(Constants.AUTH_URL); - - Realm realm = getPartialRealm(user1); - realm.executeTransaction(r -> { - RealmQuery query = r.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_STRING, "child"); - - Set invalidFields = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( - AllJavaTypes.FIELD_IGNORED, AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_SHORT, AllJavaTypes.FIELD_INT, - AllJavaTypes.FIELD_LONG, AllJavaTypes.FIELD_ID, AllJavaTypes.FIELD_BYTE, AllJavaTypes.FIELD_FLOAT, AllJavaTypes.FIELD_DOUBLE, - AllJavaTypes.FIELD_BOOLEAN, AllJavaTypes.FIELD_DATE, AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_OBJECT, - AllJavaTypes.FIELD_LIST, AllJavaTypes.FIELD_STRING_LIST, AllJavaTypes.FIELD_BINARY_LIST, AllJavaTypes.FIELD_BOOLEAN_LIST, - AllJavaTypes.FIELD_LONG_LIST, AllJavaTypes.FIELD_INTEGER_LIST, AllJavaTypes.FIELD_SHORT_LIST, AllJavaTypes.FIELD_BYTE_LIST, - AllJavaTypes.FIELD_DOUBLE_LIST, AllJavaTypes.FIELD_FLOAT_LIST, AllJavaTypes.FIELD_DATE_LIST))); - - for (String field : invalidFields) { - try { - query.includeLinkingObjects(field); - fail(field + " failed."); - } catch (IllegalArgumentException ignore) { - } - } - }); - realm.close(); - looperThread.postRunnable(() -> { - looperThread.testComplete(); - }); - } - - @Test - @RunTestInLooperThread - public void includeLinkingObjects_differentTable() throws InterruptedException { - // Upload data - SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - Realm realm1 = getPartialRealm(admin); - realm1.executeTransaction(realm -> { - BacklinksTarget child1 = new BacklinksTarget(); - child1.setId(1); - BacklinksTarget child2 = new BacklinksTarget(); - child2.setId(2); - - BacklinksSource parent1 = new BacklinksSource(); - parent1.setName("parent-1"); - parent1.setChild(child1); - BacklinksSource parent2 = new BacklinksSource(); - parent2.setName("parent-2"); - - realm.insert(parent1); - realm.insert(parent2); - realm.insert(child2); - }); - SyncManager.getSession((SyncConfiguration) realm1.getConfiguration()).uploadAllLocalChanges(); - realm1.close(); - - // Create subscription with includes - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - Realm realm2 = getPartialRealm(admin); - SyncSession session = SyncManager.getSession((SyncConfiguration) realm2.getConfiguration()); - assertEquals(0, realm2.where(AllJavaTypes.class).count()); - realm2.executeTransaction(realm -> { - Subscription sub = realm.where(BacklinksTarget.class) - .equalTo(BacklinksTarget.FIELD_ID, 1) - .subscribe("my-sub"); - assertEquals("id == 1", sub.getQueryDescription()); - }); - session.uploadAllLocalChanges(); - session.downloadAllServerChanges(); - realm2.refresh(); - assertEquals(1, realm2.where(BacklinksTarget.class).count()); - assertEquals(0, realm2.where(BacklinksSource.class).count()); - - // Update subscription to include parent objects - realm2.executeTransaction(realm -> { - Subscription sub = realm.where(BacklinksTarget.class) - .equalTo(BacklinksTarget.FIELD_ID, 1) - .includeLinkingObjects(BacklinksTarget.FIELD_PARENTS) - .subscribeOrUpdate("my-sub"); - assertEquals("id == 1 INCLUDE(@links.class_BacklinksSource.child)", sub.getQueryDescription()); - }); - session.uploadAllLocalChanges(); - session.downloadAllServerChanges(); - realm2.refresh(); - assertEquals(1, realm2.where(BacklinksTarget.class).count()); - assertEquals(1, realm2.where(BacklinksSource.class).count()); - realm2.close(); - looperThread.postRunnable(() -> { - looperThread.testComplete(); - }); - } - - @Test - @RunTestInLooperThread - public void includeLinkingObjects_sameTable() throws InterruptedException { - // Upload data - SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - Realm realm1 = getPartialRealm(admin); - realm1.executeTransaction(realm -> { - AllJavaTypes obj1 = new AllJavaTypes(1); - obj1.setFieldString("parent"); - AllJavaTypes obj2 = new AllJavaTypes(2); - obj2.setFieldString("child"); - obj1.setFieldObject(obj2); - realm.insert(obj1); - }); - SyncManager.getSession((SyncConfiguration) realm1.getConfiguration()).uploadAllLocalChanges(); - realm1.close(); - - // Create subscription with includes - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - Realm realm2 = getPartialRealm(admin); - SyncSession session = SyncManager.getSession((SyncConfiguration) realm2.getConfiguration()); - assertEquals(0, realm2.where(AllJavaTypes.class).count()); - realm2.executeTransaction(realm -> { - realm.where(AllJavaTypes.class) - .equalTo(AllJavaTypes.FIELD_STRING, "child") - .subscribe("my-sub"); - }); - session.uploadAllLocalChanges(); - session.downloadAllServerChanges(); - realm2.refresh(); - assertEquals(1, realm2.where(AllJavaTypes.class).count()); - - // Update subscription to include parent objects - realm2.executeTransaction(realm -> { - realm.where(AllJavaTypes.class) - .equalTo(AllJavaTypes.FIELD_STRING, "child") - .includeLinkingObjects(AllJavaTypes.FIELD_LO_OBJECT) - .subscribeOrUpdate("my-sub"); - }); - session.uploadAllLocalChanges(); - session.downloadAllServerChanges(); - realm2.refresh(); - assertEquals(2, realm2.where(AllJavaTypes.class).count()); - realm2.close(); - looperThread.postRunnable(() -> { - looperThread.testComplete(); - }); - } - private Realm getPartialRealm(SyncUser user) { final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) .name("partialSync") @@ -793,5 +351,4 @@ private void createServerData(SyncUser user, String url) throws InterruptedExcep SyncManager.getSession(syncConfig).uploadAllLocalChanges(); realm.close(); } - } From 38831c509515cf1a4b068d51d4e5b97c5a0b65d1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 7 Jun 2018 09:21:06 +0200 Subject: [PATCH 1446/2110] Fix bad merge --- .../src/objectServer/java/io/realm/SyncConfiguration.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index b69cd379bc..4bb2420e24 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -1049,8 +1049,9 @@ public SyncConfiguration.Builder readOnly() { * * @see #isFullySynchronizedRealm() () */ - public SyncConfiguration.Builder fullSynchronization() { - this.isPartial = false; + @Deprecated + public SyncConfiguration.Builder partialRealm() { + this.isPartial = true; return this; } From 417096da07bd0d761091e531cb514d993f697a7d Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 29 Nov 2019 15:21:12 +0000 Subject: [PATCH 1447/2110] Cleanup & Fixing failing tests --- .../java/io/realm/SyncManagerTests.java | 24 +++++++++++++------ .../java/io/realm/SyncConfiguration.java | 9 ------- .../io/realm/PathLevelPermissionsTests.java | 3 --- .../objectserver/QueryBasedSyncTests.java | 2 +- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 70426004ae..a4f577bd34 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -256,8 +256,8 @@ public void addCustomRequestHeader() throws URISyntaxException { Map headers = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); assertEquals(2, headers.size()); Map.Entry header = headers.entrySet().iterator().next(); - assertEquals("header1", header.getKey()); - assertEquals("val1", header.getValue()); + String expected = header.getKey().equals("header1") ? "val1" : "val2"; + assertEquals(expected, header.getValue()); } @Test @@ -290,11 +290,21 @@ public void addCustomHeaders() throws URISyntaxException { assertEquals(2, outputHeaders.size()); Iterator> it = outputHeaders.entrySet().iterator(); Map.Entry header1 = it.next(); - assertEquals("header1", header1.getKey()); - assertEquals("value1", header1.getValue()); - Map.Entry header2 = it.next(); - assertEquals("header2", header2.getKey()); - assertEquals("value2", header2.getValue()); + + if (header1.getKey().equals("header1")) { + assertEquals("header1", header1.getKey()); + assertEquals("value1", header1.getValue()); + Map.Entry header2 = it.next(); + assertEquals("header2", header2.getKey()); + assertEquals("value2", header2.getValue()); + + } else { + assertEquals("header2", header1.getKey()); + assertEquals("value2", header1.getValue()); + Map.Entry header2 = it.next(); + assertEquals("header1", header2.getKey()); + assertEquals("value1", header2.getValue()); + } } @Test diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 4bb2420e24..7b3dde3bac 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -478,15 +478,6 @@ public boolean isFullySynchronizedRealm() { return !isPartial; } - /** - * Returns whether this configuration is for a fully synchronized Realm or not. - * - * @see Builder#fullSynchronization() for more details. - */ - public boolean isFullySynchronizedRealm() { - return !isPartial; - } - /** * Returns the url prefix used when establishing a sync connection to the Realm Object Server. */ diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java index 16cedc4cae..73a11de644 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java @@ -16,18 +16,15 @@ package io.realm; -import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Date; import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nullable; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java index 6865121afa..3585330c64 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java @@ -14,6 +14,7 @@ import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; import io.realm.SyncManager; +import io.realm.SyncTestUtils; import io.realm.SyncUser; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; @@ -24,7 +25,6 @@ import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.RunTestInLooperThread; -import io.realm.util.SyncTestUtils; import static org.hamcrest.number.OrderingComparison.greaterThan; import static org.junit.Assert.assertEquals; From bc6e3a003b706be5b316ad8ae067f3d2b40c971c Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 12 Dec 2019 12:41:48 +0000 Subject: [PATCH 1448/2110] Core 6 upgrade (#6107) * Add support for frozen objects --- CHANGELOG.md | 51 +- build.gradle | 77 +- dependencies.list | 4 +- .../build.gradle | 2 +- examples/build.gradle | 2 +- examples/multiprocessExample/build.gradle | 3 +- examples/objectServerExample/build.gradle | 5 +- library-benchmarks/build.gradle | 2 +- .../src/androidTest/AndroidManifest.xml | 1 - .../benchmarks/FrozenObjectsBenchmarks.kt | 119 ++ .../realm/benchmarks/RealmAllocBenchmarks.kt | 2 +- .../src/main/AndroidManifest.xml | 9 +- .../src/main/res/values/strings.xml | 3 - realm/build.gradle | 3 +- realm/kotlin-extensions/build.gradle | 4 +- .../kotlin/io/realm/KotlinRealmModelTests.kt | 6 +- .../kotlin/io/realm/KotlinRealmQueryTests.kt | 3 +- .../java/io/realm/processor/ClassMetaData.kt | 8 +- .../processor/RealmProxyClassGenerator.kt | 255 ++-- .../realm/some_test_AllTypesRealmProxy.java | 601 ++++---- .../realm/some_test_BooleansRealmProxy.java | 149 +- ...amePolicyMixedClassSettingsRealmProxy.java | 121 +- ...st_NamePolicyModuleDefaultsRealmProxy.java | 121 +- .../realm/some_test_NullTypesRealmProxy.java | 1067 +++++++------- .../io/realm/some_test_SimpleRealmProxy.java | 97 +- realm/realm-library/build.gradle | 16 +- .../proguard-rules-consumer-common.pro | 2 + .../proguard-rules-consumer-objectServer.pro | 3 + .../src/androidTest/AndroidManifest.xml | 2 +- .../assets/080_annotationtypes.realm | Bin 4096 -> 4096 bytes .../assets/0841_annotationtypes.realm | Bin 4096 -> 4096 bytes .../assets/0841_pk_migration.realm | Bin 8192 -> 8192 bytes .../src/androidTest/assets/asset_file.realm | Bin 8192 -> 8192 bytes .../assets/backlinks-fieldInUse.realm | Bin 4096 -> 4096 bytes .../assets/core6_string_pk_indexed.realm | Bin 0 -> 4096 bytes .../default-notnullable-primarykey.realm | Bin 8192 -> 8192 bytes .../assets/default-nullable-primarykey.realm | Bin 4096 -> 8192 bytes .../src/androidTest/assets/default0.realm | Bin 4096 -> 4096 bytes .../src/androidTest/assets/readonly.realm | Bin 4096 -> 4096 bytes .../assets/rename-and-add-indexed.realm | Bin 4096 -> 4096 bytes .../androidTest/assets/rename-and-add.realm | Bin 4096 -> 4096 bytes .../assets/string-only-pre-null-0.82.2.realm | Bin 4096 -> 4096 bytes ...string-only-required-pre-null-0.82.2.realm | Bin 4096 -> 4096 bytes .../java/io/realm/CollectionTests.java | 6 +- .../java/io/realm/ColumnInfoTests.java | 121 +- .../java/io/realm/FrozenObjectsTests.java | 754 ++++++++++ .../java/io/realm/IOSRealmTests.java | 7 +- .../ManagedOrderedRealmCollectionTests.java | 2 - .../io/realm/ManagedRealmCollectionTests.java | 10 +- .../OrderedRealmCollectionIteratorTests.java | 3 + .../java/io/realm/RealmAnnotationTests.java | 38 +- .../java/io/realm/RealmAsyncQueryTests.java | 34 + .../io/realm/RealmConfigurationTests.java | 26 +- .../java/io/realm/RealmJsonTests.java | 31 + .../java/io/realm/RealmListTests.java | 6 +- .../java/io/realm/RealmMigrationTests.java | 48 +- .../java/io/realm/RealmObjectSchemaTests.java | 6 +- .../java/io/realm/RealmObjectTests.java | 12 +- .../io/realm/RealmProxyMediatorTests.java | 22 +- .../java/io/realm/RealmQueryTests.java | 1 - .../java/io/realm/RealmResultsTests.java | 65 +- .../java/io/realm/RealmSchemaTests.java | 8 +- .../androidTest/java/io/realm/RealmTests.java | 108 +- .../java/io/realm/RxJavaTests.java | 1247 +++++++++-------- .../realm/UnManagedRealmCollectionTests.java | 2 + ...igrationCore6PKStringIndexedByDefault.java | 25 + .../io/realm/internal/JNIColumnInfoTest.java | 25 +- .../java/io/realm/internal/JNIQueryTest.java | 761 +++------- .../java/io/realm/internal/JNIRowTest.java | 76 +- .../io/realm/internal/JNITableInsertTest.java | 6 +- .../java/io/realm/internal/JNITableTest.java | 935 +++++------- .../java/io/realm/internal/OsListTests.java | 58 +- .../io/realm/internal/OsObjectStoreTests.java | 2 +- .../io/realm/internal/OsResultsTests.java | 86 +- .../io/realm/internal/OsSharedRealmTests.java | 13 +- .../io/realm/internal/PrimaryKeyTests.java | 106 +- .../realm/internal/QueryDescriptorTests.java | 112 +- .../io/realm/internal/RealmNotifierTests.java | 2 +- .../internal/TableIndexAndDistinctTest.java | 71 +- .../java/io/realm/SchemaTests.java | 5 - .../io/realm/SyncedRealmMigrationTests.java | 60 +- .../cpp/io_realm_ClientResetRequiredError.cpp | 1 - .../main/cpp/io_realm_RealmFileUserStore.cpp | 6 - .../src/main/cpp/io_realm_RealmQuery.cpp | 6 +- .../src/main/cpp/io_realm_SyncManager.cpp | 6 - .../src/main/cpp/io_realm_SyncSession.cpp | 8 - .../main/cpp/io_realm_internal_CheckedRow.cpp | 144 +- ...o_realm_internal_OsCollectionChangeSet.cpp | 4 - .../src/main/cpp/io_realm_internal_OsList.cpp | 100 +- .../main/cpp/io_realm_internal_OsObject.cpp | 165 +-- .../io_realm_internal_OsObjectSchemaInfo.cpp | 31 - .../cpp/io_realm_internal_OsObjectStore.cpp | 26 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 23 +- .../main/cpp/io_realm_internal_OsResults.cpp | 144 +- .../cpp/io_realm_internal_OsSchemaInfo.cpp | 5 - .../cpp/io_realm_internal_OsSharedRealm.cpp | 172 ++- .../main/cpp/io_realm_internal_Property.cpp | 12 +- .../src/main/cpp/io_realm_internal_Table.cpp | 1106 +++++---------- .../main/cpp/io_realm_internal_TableQuery.cpp | 962 ++++++------- .../cpp/io_realm_internal_UncheckedRow.cpp | 273 ++-- ...realm_internal_core_DescriptorOrdering.cpp | 10 - ..._realm_internal_core_IncludeDescriptor.cpp | 24 +- ...m_internal_objectstore_OsAsyncOpenTask.cpp | 4 +- ...m_internal_objectstore_OsObjectBuilder.cpp | 82 +- .../io_realm_internal_sync_OsSubscription.cpp | 7 - .../src/main/cpp/java_accessor.hpp | 30 +- .../src/main/cpp/java_object_accessor.hpp | 59 +- .../src/main/cpp/java_query_descriptor.cpp | 27 +- .../src/main/cpp/java_query_descriptor.hpp | 5 +- .../src/main/cpp/jni_util/log.hpp | 9 - realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/util.cpp | 43 +- realm/realm-library/src/main/cpp/util.hpp | 324 +---- .../src/main/java/io/realm/BaseRealm.java | 81 +- .../src/main/java/io/realm/DynamicRealm.java | 28 +- .../java/io/realm/DynamicRealmObject.java | 225 ++- .../main/java/io/realm/FrozenPendingRow.java | 201 +++ .../io/realm/ImmutableRealmObjectSchema.java | 2 +- .../java/io/realm/MutableRealmInteger.java | 16 +- .../io/realm/MutableRealmObjectSchema.java | 83 +- .../java/io/realm/MutableRealmSchema.java | 17 +- .../io/realm/OrderedRealmCollectionImpl.java | 25 +- .../realm/OrderedRealmCollectionSnapshot.java | 19 +- .../src/main/java/io/realm/ProxyState.java | 6 +- .../src/main/java/io/realm/ProxyUtils.java | 2 +- .../src/main/java/io/realm/Realm.java | 22 +- .../src/main/java/io/realm/RealmCache.java | 353 +++-- .../main/java/io/realm/RealmCollection.java | 19 + .../java/io/realm/RealmConfiguration.java | 50 +- .../src/main/java/io/realm/RealmList.java | 75 +- .../src/main/java/io/realm/RealmObject.java | 171 ++- .../main/java/io/realm/RealmObjectSchema.java | 49 +- .../src/main/java/io/realm/RealmQuery.java | 272 ++-- .../src/main/java/io/realm/RealmResults.java | 74 +- .../src/main/java/io/realm/RealmSchema.java | 11 +- .../java/io/realm/internal/CheckedRow.java | 17 +- .../java/io/realm/internal/ColumnInfo.java | 64 +- .../java/io/realm/internal/InvalidRow.java | 66 +- .../io/realm/internal/ManagableObject.java | 7 + .../main/java/io/realm/internal/OsList.java | 22 +- .../main/java/io/realm/internal/OsObject.java | 35 +- .../io/realm/internal/OsObjectSchemaInfo.java | 10 - .../java/io/realm/internal/OsRealmConfig.java | 4 +- .../java/io/realm/internal/OsResults.java | 36 +- .../java/io/realm/internal/OsSharedRealm.java | 72 +- .../java/io/realm/internal/PendingRow.java | 69 +- .../main/java/io/realm/internal/Property.java | 6 +- .../src/main/java/io/realm/internal/Row.java | 80 +- .../main/java/io/realm/internal/Table.java | 461 +++--- .../java/io/realm/internal/TableQuery.java | 386 ++--- .../java/io/realm/internal/UncheckedRow.java | 196 +-- .../internal/core/IncludeDescriptor.java | 6 +- .../realm/internal/core/QueryDescriptor.java | 16 +- .../fields/CachedFieldDescriptor.java | 6 +- .../fields/DynamicFieldDescriptor.java | 14 +- .../internal/fields/FieldDescriptor.java | 15 +- .../internal/objectstore/OsObjectBuilder.java | 7 +- .../io/realm/rx/RealmObservableFactory.java | 317 +++-- .../java/io/realm/SyncConfiguration.java | 37 +- .../java/io/realm/SyncSession.java | 1 + .../IncompatibleSyncedFileException.java | 96 -- .../syncIntegrationTest/assets/sync-1.x.realm | Bin 8192 -> 0 bytes .../java/io/realm/BaseIntegrationTest.java | 6 - .../java/io/realm/SyncSessionTests.java | 13 +- .../java/io/realm/objectserver/AuthTests.java | 26 +- .../EncryptedSynchronizedRealmTests.java | 7 +- .../java/io/realm/SyncTestUtils.java | 8 - .../testUtils/java/io/realm/TestHelper.java | 52 +- .../rule/TestRealmConfigurationFactory.java | 2 +- version.txt | 2 +- 170 files changed, 7732 insertions(+), 7689 deletions(-) create mode 100644 library-benchmarks/src/androidTest/java/io/realm/benchmarks/FrozenObjectsBenchmarks.kt delete mode 100644 library-benchmarks/src/main/res/values/strings.xml create mode 100644 realm/realm-library/src/androidTest/assets/core6_string_pk_indexed.realm create mode 100644 realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationCore6PKStringIndexedByDefault.java create mode 100644 realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/exceptions/IncompatibleSyncedFileException.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/assets/sync-1.x.realm diff --git a/CHANGELOG.md b/CHANGELOG.md index 20dc8decd1..c2bd5be64c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,30 @@ -## 7.0.0 (YYYY-MM-DD) +## 7.0.0-beta.0 (YYYY-MM-DD) ### Breaking Changes * [ObjectServer] Query-based Sync is now the default mode of synchronization. To enable Full Realm synchronization use `SyncConfiguration.Builder.fullSynchronization()`. `SyncConfiguration.Builder.partialRealm()` has been deprecated. * [ObjectServer] `SyncConfiguration.isPartialRealm()` has been replaced by `SyncConfiguration.isFullySynchronizedRealm()`. +* RxJava Flowables and Observables are now subscribed to and unsubscribed to asynchronously on the thread holding the live Realm, instead of previously where this was done synchronously. +* All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(true|false))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. +* MIPS is not supported anymore. +* Realm now requires `minSdkVersion` 16. Up from 9. +* `IncompatibleSyncedFileException` is removed as it is no longer used. + +### Enhancements +* Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) +* Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. +* Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. +* `RealmResults.asJSON()` is no longer `@Beta` + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. + +### Internal +* `OsSharedRealm.VersionID.hashCode()` was not implemented correctly and included the memory location in the hashcode. +* OKHttp was upgraded to 3.10.0 from 3.9.0. + ## 6.1.0(YYYY-MM-DD) @@ -174,7 +195,7 @@ None. * None. ### Fixed -* [ObjectServer] The C++ networking layer now correctly uses any system defined proxy the same way the Java networking layer does. (Issue [#6574](https://github.com/realm/realm-java/pull/6574)). +* [ObjectServer] The C++ networking layer now correctly uses any system defined proxy the same way the Java networking layer does. (Issue [#6574](https://github.com/realm/realm-java/pull/6574)). * The Realm bytecode transformer now works correctly with Android Gradle Plugin 3.6.0-alpha01 and beyond. (Issue [#6531](https://github.com/realm/realm-java/issues/6531)). * Queries on RealmLists with objects containing indexed integers could return the wrong result. (Issue [#6522](https://github.com/realm/realm-java/issues/6522), since 5.11.0) @@ -193,7 +214,7 @@ None. ## 5.13.0(2019-07-23) ### Enhancements -* [ObjectServer] Added support for faster initial synchronization for fully synchronized Realms. (Issue [#6469](https://github.com/realm/realm-java/issues/6469)) +* [ObjectServer] Added support for faster initial synchronization for fully synchronized Realms. (Issue [#6469](https://github.com/realm/realm-java/issues/6469)) * [ObjectServer] Improved session lifecycle debug output. (Issue [#6552](https://github.com/realm/realm-java/pull/6552)). ### Fixed @@ -218,7 +239,7 @@ None. ### Fixed * [ObjectServer] `PermissionManager` stopped working if an intermittent network error was reported. (Issue [#6492](https://github.com/realm/realm-java/issues/6492), since 3.7.0) -* The Kotlin extensions library no longer defines a `app_name`, which in some cases conflicted with the `app_name` defined by applications. (Issue [#6536](https://github.com/realm/realm-java/issues/6536), since 4.3.0) +* The Kotlin extensions library no longer defines a `app_name`, which in some cases conflicted with the `app_name` defined by applications. (Issue [#6536](https://github.com/realm/realm-java/issues/6536), since 4.3.0) ### Compatibility * Realm Object Server: 3.21.0 or later. @@ -243,7 +264,7 @@ NOTE: This version is only compatible with Realm Object Server 3.21.0 or later. * Added support for incremental annotation processing added in Gradle 4.7. (Issue [#5906](https://github.com/realm/realm-java/issues/5906)). ### Fixed -* [ObjectServer] Fix an error in the calculation of the `downloadableBytes` value sent by `ProgressListeners`. +* [ObjectServer] Fix an error in the calculation of the `downloadableBytes` value sent by `ProgressListeners`. * [ObjectServer] HTTP requests made by the Sync client now always include a Host: header, as required by HTTP/1.1, although its value will be empty if no value is specified by the application. * [ObjectServer] The server no longer rejects subscriptions based on queries with distinct and/or limit clauses. * [ObjectServer] If a user had `canCreate` but not `canUpdate` privileges on a class, the user would be able to create the object, but not actually set any meaningful values on that object, despite the rule that objects created within the same transaction can always be modified. @@ -268,7 +289,7 @@ NOTE: This version is only compatible with Realm Object Server 3.21.0 or later. * [ObjectServer] Added the option of setting a time-to-live for subscriptions. Setting this will automatically delete the subscription after the provided TTL has expired and the subscription hasn't been used. (Issue [#6453](https://github.com/realm/realm-java/issues/6453)) ### Fixed -* Dates returned from the Realm file no longer overflow or underflow if they exceed `Long.MAX_VALUE` or `Long.MIN_VALUE` but instead clamp to their respective value. (Issue [#2722](https://github.com/realm/realm-java/issues/2722)) +* Dates returned from the Realm file no longer overflow or underflow if they exceed `Long.MAX_VALUE` or `Long.MIN_VALUE` but instead clamp to their respective value. (Issue [#2722](https://github.com/realm/realm-java/issues/2722)) ### Compatibility * Realm Object Server: 3.11.0 or later. @@ -353,7 +374,7 @@ This release also contains all changes from 5.8.0-BETA1 and 5.8.0-BETA2. * None ### Fixed -* `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline [#6235](https://github.com/realm/realm-java/issues/6235). +* `RealmResults` listeners not triggering the initial callback for Query-based Realm when the device is offline [#6235](https://github.com/realm/realm-java/issues/6235). ### Known Bugs * `Realm.copyToRealm()` and `Realm.copyToRealmOrUpdate` has been rewritten to support import flags. It is currently ~30% slower than in 5.7.0. @@ -410,11 +431,11 @@ This release also contains all changes from 5.8.0-BETA1 and 5.8.0-BETA2. ## 5.7.0 (2018-09-24) ### Enhancements -* [ObjectServer] Devices will now report download progress for read-only Realms which - will allow the server to compact files sooner, saving server space. This does not affect - the client. You will need to upgrade your Realm Object Server to at least version 3.11.0 - or use [Realm Cloud](https://cloud.realm.io). If you try to connect to a ROS v3.10.x or - previous, you will see an error like `Wrong protocol version in Sync HTTP request, +* [ObjectServer] Devices will now report download progress for read-only Realms which + will allow the server to compact files sooner, saving server space. This does not affect + the client. You will need to upgrade your Realm Object Server to at least version 3.11.0 + or use [Realm Cloud](https://cloud.realm.io). If you try to connect to a ROS v3.10.x or + previous, you will see an error like `Wrong protocol version in Sync HTTP request, client protocol version = 25, server protocol version = 24`. ### Fixed @@ -466,7 +487,7 @@ This release also contains all changes from 5.8.0-BETA1 and 5.8.0-BETA2. * [ObjectServer] Added Kotlin extension property `Realm.syncSession` for synchronized Realms. * [ObjectServer] Added Kotlin extension method `Realm.classPermissions()`. * [ObjectServer] Added support for starting and stopping synchronization using `SyncSession.start()` and `SyncSession.stop()` (#6135). -* [ObjectServer] Added API's for making it easier to work with network proxies (#6163): +* [ObjectServer] Added API's for making it easier to work with network proxies (#6163): * `SyncManager.setAuthorizationHeaderName(String headerName)` * `SyncManager.setAuthorizationHeaderName(String headerName, String host)` * `SyncManager.addCustomRequestHeader(String headerName, String headerValue)` @@ -474,7 +495,7 @@ This release also contains all changes from 5.8.0-BETA1 and 5.8.0-BETA2. * `SyncManager.addCustomRequestHeaders(Map headers)` * `SyncManager.addCustomRequestHeaders(Map headers, String host)` * `SyncConfiguration.Builder.urlPrefix(String prefix)` - + ### Fixed * Methods and classes requiring synchronized Realms have been removed from the standard AAR package. They are now only visible when enabling synchronized Realms in Gradle. The methods and classes will still be visible in the source files and docs, but annotated with `@ObjectServer` (#5799). @@ -484,7 +505,7 @@ This release also contains all changes from 5.8.0-BETA1 and 5.8.0-BETA2. * Updated to Object Store commit: b0fc2814d9e6061ce5ba1da887aab6cfba4755ca ### Credits -* Thanks to @lucasdornelasv for improving the performance of `Realm.copyToRealm()`, `Realm.copyToRealmOrUpdate()` and `Realm.copyFromRealm()` #(6124). +* Thanks to @lucasdornelasv for improving the performance of `Realm.copyToRealm()`, `Realm.copyToRealmOrUpdate()` and `Realm.copyFromRealm()` #(6124). ## 5.4.3 (YYYY-MM-DD) diff --git a/build.gradle b/build.gradle index ad0f929a7e..f7bc47804c 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,22 @@ apply plugin: 'ch.netzwerg.release' def currentVersion = file("${projectDir}/version.txt").text.trim() +// Shared configuration that copies relevant properties from the root level and parse them on to +// child projects. +def copyProperties = { + if (project.hasProperty('buildTargetABIs')) { + // Valid options: armeabi-v7a, arm64-v8a, x86, x86_64 + startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] + } + if (project.hasProperty('coreSourcePath')) { + def absolutePath = file(project.getProperty('coreSourcePath')).absolutePath + startParameter.projectProperties += [coreSourcePath: absolutePath] + } + if (project.hasProperty('s3cfg')) { + startParameter.projectProperties += [s3cfg: project.getProperty('s3cfg')] + } +} + task assembleAnnotations(type:GradleBuild) { group = 'Build' description = 'Assemble the Realm annotations' @@ -56,12 +72,7 @@ task assembleRealm(type:GradleBuild) { dependsOn installBuildTransformer buildFile = file('realm/build.gradle') tasks = ['assemble', 'javadocJar', 'sourcesJar'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } - if (project.hasProperty('s3cfg')) { - startParameter.projectProperties += [s3cfg: project.getProperty('s3cfg')] - } + configure copyProperties } task checkExamples(type:GradleBuild) { @@ -69,9 +80,7 @@ task checkExamples(type:GradleBuild) { description = 'Run the JVM tests and checks the examples' buildFile = file('examples/build.gradle') tasks = ['check'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task checkRealm(type:GradleBuild) { @@ -79,9 +88,7 @@ task checkRealm(type:GradleBuild) { description = 'Run the JVM tests and checks Realm project' buildFile = file('realm/build.gradle') tasks = ['check'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task check { @@ -97,9 +104,7 @@ task assembleUnitTests(type:GradleBuild) { dependsOn installTransformer buildFile = file('realm/build.gradle') tasks = ['assembleAndroidTest'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task connectedUnitTests(type:GradleBuild) { @@ -108,9 +113,7 @@ task connectedUnitTests(type:GradleBuild) { dependsOn installTransformer buildFile = file('realm/build.gradle') tasks = ['connectedAndroidTest'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task assembleBenchmarks(type:GradleBuild) { @@ -119,9 +122,7 @@ task assembleBenchmarks(type:GradleBuild) { dependsOn installTransformer buildFile = file('library-benchmarks/build.gradle') tasks = ['assembleAndroidTest'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task connectedBenchmarks(type:GradleBuild) { @@ -130,9 +131,7 @@ task connectedBenchmarks(type:GradleBuild) { dependsOn installTransformer buildFile = file('library-benchmarks/build.gradle') tasks = ['connectedAndroidTest'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task installRealm(type:GradleBuild) { @@ -142,12 +141,7 @@ task installRealm(type:GradleBuild) { dependsOn installBuildTransformer buildFile = file('realm/build.gradle') tasks = ['publishToMavenLocal'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } - if (project.hasProperty('s3cfg')) { - startParameter.projectProperties += [s3cfg: project.getProperty('s3cfg')] - } + configure copyProperties } task assembleGradlePlugin(type:GradleBuild) { @@ -198,9 +192,7 @@ task javadoc(type:GradleBuild) { group = 'Docs' buildFile = file('realm/build.gradle') tasks = ['javadocJar'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task sourcesJar(type:GradleBuild) { @@ -208,9 +200,7 @@ task sourcesJar(type:GradleBuild) { group = 'Docs' buildFile = file('realm/build.gradle') tasks = ['sourcesJar'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task assemble { @@ -266,12 +256,7 @@ task cleanRealm(type:GradleBuild) { group = 'Clean' buildFile = file('realm/build.gradle') tasks = ['clean'] - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } - if (project.hasProperty('dontCleanJniFiles')) { - startParameter.projectProperties += [dontCleanJniFiles: project.getProperty('dontCleanJniFiles')] - } + configure copyProperties } task cleanGradlePlugin(type:GradleBuild) { @@ -388,9 +373,7 @@ task bintrayRealm(type: GradleBuild) { buildFile = file('realm/build.gradle') tasks = ['bintrayUploadAll'] startParameter.projectProperties = gradle.startParameter.projectProperties - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task bintrayAnnotations(type: GradleBuild) { @@ -432,9 +415,7 @@ task ojoRealm(type: GradleBuild) { buildFile = file('realm/build.gradle') tasks = ['ojoUpload'] startParameter.projectProperties = gradle.startParameter.projectProperties - if (project.hasProperty('buildTargetABIs')) { - startParameter.projectProperties += [buildTargetABIs: project.getProperty('buildTargetABIs')] - } + configure copyProperties } task ojoAnnotations(type: GradleBuild) { diff --git a/dependencies.list b/dependencies.list index bc3605e4e0..bd1e93f0a7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=4.8.3 -REALM_SYNC_SHA256=b3fa91562eb83a2d90fa600240546e41d8672bdac365ed0d0b55ae1726f2f2bb +REALM_SYNC_VERSION=5.0.0-beta.0 +REALM_SYNC_SHA256=9f8079c45a42691a3085b7674adc31b97c1fe357e30451a114b20c997e2153e4 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/examples/architectureComponentsExample/build.gradle b/examples/architectureComponentsExample/build.gradle index 9af0f5f9a3..1f410c0cd6 100644 --- a/examples/architectureComponentsExample/build.gradle +++ b/examples/architectureComponentsExample/build.gradle @@ -19,7 +19,7 @@ android { defaultConfig { applicationId 'io.realm.examples.arch' targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion 16 versionCode 1 versionName "1.0" diff --git a/examples/build.gradle b/examples/build.gradle index 4b3907a7d9..0436277664 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,7 +1,7 @@ def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) project.ext.sdkVersion = 27 -project.ext.minSdkVersion = 15 +project.ext.minSdkVersion = 16 project.ext.buildTools = projectDependencies.get("ANDROID_BUILD_TOOLS") // Don't cache SNAPSHOT (changing) dependencies. diff --git a/examples/multiprocessExample/build.gradle b/examples/multiprocessExample/build.gradle index 1a5d7ebf33..cdd92c6f90 100644 --- a/examples/multiprocessExample/build.gradle +++ b/examples/multiprocessExample/build.gradle @@ -8,7 +8,7 @@ android { defaultConfig { applicationId "io.realm.examples.realmmultiprocessexample" targetSdkVersion rootProject.sdkVersion - minSdkVersion 15 + minSdkVersion 16 versionCode 1 versionName "1.0" } @@ -27,4 +27,3 @@ android { dependencies { implementation 'com.android.support:appcompat-v7:27.1.1' } - diff --git a/examples/objectServerExample/build.gradle b/examples/objectServerExample/build.gradle index 44535c85a7..f0055aba23 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/objectServerExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.20' + ext.kotlin_version = '1.3.50' repositories { google() jcenter() @@ -11,9 +11,9 @@ buildscript { } apply plugin: 'com.android.application' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' apply plugin: 'realm-android' android { @@ -45,7 +45,6 @@ android { debug { buildConfigField "String", "REALM_AUTH_URL", "${realmAuthUrl}" buildConfigField "String", "REALM_URL", "${realmUrl}" - minifyEnabled true } release { buildConfigField "String", "REALM_AUTH_URL", "${realmAuthUrl}" diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 4f85e462c6..8217ff06be 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -36,7 +36,7 @@ android { buildToolsVersion "${project.ext.get("ANDROID_BUILD_TOOLS")}" defaultConfig { - minSdkVersion 15 + minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" diff --git a/library-benchmarks/src/androidTest/AndroidManifest.xml b/library-benchmarks/src/androidTest/AndroidManifest.xml index e22ad7e7eb..ffee04fe12 100644 --- a/library-benchmarks/src/androidTest/AndroidManifest.xml +++ b/library-benchmarks/src/androidTest/AndroidManifest.xml @@ -8,4 +8,3 @@ tools:replace="android:debuggable"/> - diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/FrozenObjectsBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/FrozenObjectsBenchmarks.kt new file mode 100644 index 0000000000..5fe82ed630 --- /dev/null +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/FrozenObjectsBenchmarks.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.benchmarks + +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.RealmList +import io.realm.RealmResults +import io.realm.benchmarks.entities.AllTypes +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import io.realm.log.RealmLog +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* + +@RunWith(AndroidJUnit4::class) +class FrozenObjectsBenchmarks { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + private lateinit var realm: Realm + private lateinit var readObject: AllTypes + private lateinit var realmConfig: RealmConfiguration + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().context) + RealmLog.error("SETUP") + realmConfig = RealmConfiguration.Builder().name("frozen${Random().nextLong()}.realm").build() + realm = Realm.getInstance(realmConfig) + realm.executeTransaction { realm -> + readObject = realm.createObject(AllTypes::class.java) + readObject.columnString = "Foo" + readObject.columnLong = 42 + readObject.columnDouble = 1.234 + } + } + + @After + fun tearDown() { + RealmLog.error("TEAR_DOWN"); + realm.close() + } + + @Test + fun freezeRealm() { + benchmarkRule.measureRepeated { + // Skip caching in Java and directly measure how fast it is to freeze the SharedRealm. + // ObjectStore do not cache it, so it should be safe to run in a loop. + realm.sharedRealm.freeze() + } + } + + @Test + fun freezeResults() { + realm.executeTransaction { r -> + for (i in 0..10_0000) { + val obj = r.createObject() + obj.columnString= "String: " + i + obj.columnLong = i.toLong() + obj.isColumnBoolean = (i % 2 == 0) + } + } + + var results: RealmResults = realm.where().findAll() + benchmarkRule.measureRepeated { + results.freeze() + } + } + + @Test + fun freezeList() { + var list: RealmList = RealmList() + realm.executeTransaction { r -> + for (i in 0..10_0000) { + list = readObject.columnRealmList + val obj = r.createObject() + obj.columnString= "String: " + i + obj.columnLong = i.toLong() + obj.isColumnBoolean = (i % 2 == 0) + list.add(obj) + } + } + + benchmarkRule.measureRepeated { + list.freeze() + } + } + + @Test + fun freezeObject() { + benchmarkRule.measureRepeated { + readObject.freeze() + } + } +} diff --git a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt index 2ed2f0c479..8d9acb4ab2 100644 --- a/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt +++ b/library-benchmarks/src/androidTest/java/io/realm/benchmarks/RealmAllocBenchmarks.kt @@ -18,8 +18,8 @@ package io.realm.benchmarks import androidx.benchmark.junit4.BenchmarkRule import androidx.benchmark.junit4.measureRepeated -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm import io.realm.RealmConfiguration import io.realm.benchmarks.entities.AllTypes diff --git a/library-benchmarks/src/main/AndroidManifest.xml b/library-benchmarks/src/main/AndroidManifest.xml index 3c99ebf72b..f44dbc5594 100644 --- a/library-benchmarks/src/main/AndroidManifest.xml +++ b/library-benchmarks/src/main/AndroidManifest.xml @@ -2,11 +2,6 @@ xmlns:tools="http://schemas.android.com/tools" package="io.realm.benchmarks"> - - - + + diff --git a/library-benchmarks/src/main/res/values/strings.xml b/library-benchmarks/src/main/res/values/strings.xml deleted file mode 100644 index dbe19ccec8..0000000000 --- a/library-benchmarks/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Realm Benchmarks - diff --git a/realm/build.gradle b/realm/build.gradle index 31076940cb..673c8b0e92 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -34,7 +34,7 @@ allprojects { projectDependencies.each { key, val -> project.ext.set(key, val) } - project.ext.minSdkVersion = 9 + project.ext.minSdkVersion = 16 project.ext.compileSdkVersion = 28 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") group = 'io.realm' @@ -45,4 +45,3 @@ allprojects { jcenter() } } - diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 2a505eed1a..9025bc552b 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -80,8 +80,6 @@ dependencies { androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' androidTestObjectServerImplementation 'com.google.code.findbugs:jsr305:3.0.2' - - } repositories { @@ -225,7 +223,7 @@ artifactory { password = project.hasProperty('bintrayKey') ? bintrayKey : 'noKey' } defaults { - publications('basePublication', 'objectServerPublication') + publications('basePublication', 'objectServerPublication') publishPom = true publishIvy = false } diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt index 4130e260d3..4596fa3135 100644 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt @@ -17,10 +17,12 @@ import org.junit.runner.RunWith class KotlinRealmModelTests { @Suppress("MemberVisibilityCanPrivate") - @get:Rule + @Rule + @JvmField val configFactory = TestRealmConfigurationFactory() - @get:Rule + @Rule + @JvmField val looperThread = RunInLooperThread() private lateinit var realm: Realm diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt index 19da173b66..b909c3a4fe 100644 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt @@ -20,7 +20,8 @@ import java.util.* class KotlinRealmQueryTests { @Suppress("MemberVisibilityCanPrivate") - @get:Rule + @Rule + @JvmField val configFactory = TestRealmConfigurationFactory() private lateinit var realm: Realm diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt index ff8c33d6a0..6c6c75205b 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt @@ -97,6 +97,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat typeMirrors.FLOAT_MIRROR, typeMirrors.DATE_MIRROR ) + private val stringType = typeMirrors.STRING_MIRROR + private val typeUtils: Types = env.typeUtils private val elements: Elements = env.elementUtils private lateinit var defaultFieldNameFormatter: NameConverter @@ -717,8 +719,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat primaryKey = fieldElement - // Also add as index. All types of primary key can be indexed. - if (!indexedFields.contains(fieldElement)) { + // Also add as index. All non string types of primary key can be indexed. + if (!isStringPrimaryKeyType(fieldType) && !indexedFields.contains(fieldElement)) { indexedFields.add(fieldElement) } @@ -756,6 +758,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat return false } + private fun isStringPrimaryKeyType(type: TypeMirror): Boolean = typeUtils.isAssignable(type, stringType) + private fun containsType(listOfTypes: List, type: TypeMirror): Boolean { for (i in listOfTypes.indices) { // Comparing TypeMirror's using `equals()` breaks when using incremental annotation processing. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index d19f6dcf60..757dd3094b 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -128,9 +128,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginType(columnInfoClassName(), "class", EnumSet.of(Modifier.STATIC, Modifier.FINAL), "ColumnInfo") // base class // fields - emitField("long", "maxColumnIndexValue") // Must not end with Index as it otherwise could conflict regular fields. for (variableElement in metadata.fields) { - emitField("long", columnIndexVarName(variableElement)) + emitField("long", columnKeyVarName(variableElement)) } emitEmptyLine() @@ -139,7 +138,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("super(%s)", metadata.fields.size) emitStatement("OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo(\"%1\$s\")", internalClassName) for (field in metadata.fields) { - emitStatement("this.%1\$sIndex = addColumnDetails(\"%1\$s\", \"%2\$s\", objectSchemaInfo)", field.javaName, field.internalFieldName) + emitStatement("this.%1\$sColKey = addColumnDetails(\"%1\$s\", \"%2\$s\", objectSchemaInfo)", field.javaName, field.internalFieldName) } for (backlink in metadata.backlinkFields) { val sourceClass = classCollection.getClassFromQualifiedName(backlink.sourceClass!!) @@ -147,7 +146,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val internalSourceFieldName = sourceClass.getInternalFieldName(backlink.sourceField!!) emitStatement("addBacklinkDetails(schemaInfo, \"%s\", \"%s\", \"%s\")", backlink.targetField, internalSourceClassName, internalSourceFieldName) } - emitStatement("this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex()") endConstructor() emitEmptyLine() @@ -171,9 +169,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("final %1\$s src = (%1\$s) rawSrc", columnInfoClassName()) emitStatement("final %1\$s dst = (%1\$s) rawDst", columnInfoClassName()) for (variableElement in metadata.fields) { - emitStatement("dst.%1\$s = src.%1\$s", columnIndexVarName(variableElement)) + emitStatement("dst.%1\$s = src.%1\$s", columnKeyVarName(variableElement)) } - emitStatement("dst.maxColumnIndexValue = src.maxColumnIndexValue") endMethod() endType() } @@ -223,7 +220,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi + " @Override protected ProxyState<%1\$s> getProxyState() { return proxyState; }\n" + " @Override protected long getColumnIndex() { return columnInfo.%2\$s; }\n" + "}", - qualifiedJavaClassName, columnIndexVarName(variableElement))) + qualifiedJavaClassName, columnKeyVarName(variableElement))) } } @@ -278,7 +275,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here. if (metadata.isNullable(field) && !Utils.isString(field) && !Utils.isByteArray(field)) { - beginControlFlow("if (proxyState.getRow\$realm().isNull(%s))", fieldIndexVariableReference(field)) + beginControlFlow("if (proxyState.getRow\$realm().isNull(%s))", fieldColKeyVariableReference(field)) emitStatement("return null") endControlFlow() } @@ -291,7 +288,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi fieldTypeCanonicalName } - emitStatement("return (%s) proxyState.getRow\$realm().get%s(%s)", castingBackType, fieldJavaType, fieldIndexVariableReference(field)) + emitStatement("return (%s) proxyState.getRow\$realm().get%s(%s)", castingBackType, fieldJavaType, fieldColKeyVariableReference(field)) endMethod() emitEmptyLine() // Getter - End @@ -304,7 +301,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("final Row row = proxyState.getRow\$realm()") if (metadata.isNullable(field)) { beginControlFlow("if (value == null)") - emitStatement("row.getTable().setNull(%s, row.getIndex(), true)", fieldIndexVariableReference(field)) + emitStatement("row.getTable().setNull(%s, row.getObjectKey(), true)", fieldColKeyVariableReference(field)) emitStatement("return") endControlFlow() } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { @@ -312,7 +309,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) endControlFlow() } - emitStatement("row.getTable().set%s(%s, row.getIndex(), value, true)", fieldJavaType, fieldIndexVariableReference(field)) + emitStatement("row.getTable().set%s(%s, row.getObjectKey(), value, true)", fieldJavaType, fieldColKeyVariableReference(field)) emitStatement("return") } emitStatement("proxyState.getRealm\$realm().checkIfValid()") @@ -324,7 +321,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } else { if (metadata.isNullable(field)) { beginControlFlow("if (value == null)") - emitStatement("proxyState.getRow\$realm().setNull(%s)", fieldIndexVariableReference(field)) + emitStatement("proxyState.getRow\$realm().setNull(%s)", fieldColKeyVariableReference(field)) emitStatement("return") endControlFlow() } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) { @@ -333,7 +330,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName) endControlFlow() } - emitStatement("proxyState.getRow\$realm().set%s(%s, value)", fieldJavaType, fieldIndexVariableReference(field)) + emitStatement("proxyState.getRow\$realm().set%s(%s, value)", fieldJavaType, fieldColKeyVariableReference(field)) } endMethod() // Setter - End @@ -367,10 +364,10 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitAnnotation("Override") beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC)) emitStatement("proxyState.getRealm\$realm().checkIfValid()") - beginControlFlow("if (proxyState.getRow\$realm().isNullLink(%s))", fieldIndexVariableReference(field)) + beginControlFlow("if (proxyState.getRow\$realm().isNullLink(%s))", fieldColKeyVariableReference(field)) emitStatement("return null") endControlFlow() - emitStatement("return proxyState.getRealm\$realm().get(%s.class, proxyState.getRow\$realm().getLink(%s), false, Collections.emptyList())", fieldTypeCanonicalName, fieldIndexVariableReference(field)) + emitStatement("return proxyState.getRealm\$realm().get(%s.class, proxyState.getRow\$realm().getLink(%s), false, Collections.emptyList())", fieldTypeCanonicalName, fieldColKeyVariableReference(field)) endMethod() emitEmptyLine() // Getter - End @@ -391,20 +388,20 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("final Row row = proxyState.getRow\$realm()") beginControlFlow("if (value == null)") emitSingleLineComment("Table#nullifyLink() does not support default value. Just using Row.") - emitStatement("row.nullifyLink(%s)", fieldIndexVariableReference(field)) + emitStatement("row.nullifyLink(%s)", fieldColKeyVariableReference(field)) emitStatement("return") endControlFlow() emitStatement("proxyState.checkValidObject(value)") - emitStatement("row.getTable().setLink(%s, row.getIndex(), ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getIndex(), true)", fieldIndexVariableReference(field)) + emitStatement("row.getTable().setLink(%s, row.getObjectKey(), ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getObjectKey(), true)", fieldColKeyVariableReference(field)) emitStatement("return") } emitStatement("proxyState.getRealm\$realm().checkIfValid()") beginControlFlow("if (value == null)") - emitStatement("proxyState.getRow\$realm().nullifyLink(%s)", fieldIndexVariableReference(field)) + emitStatement("proxyState.getRow\$realm().nullifyLink(%s)", fieldColKeyVariableReference(field)) emitStatement("return") endControlFlow() emitStatement("proxyState.checkValidObject(value)") - emitStatement("proxyState.getRow\$realm().setLink(%s, ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getIndex())", fieldIndexVariableReference(field)) + emitStatement("proxyState.getRow\$realm().setLink(%s, ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getObjectKey())", fieldColKeyVariableReference(field)) endMethod() // Setter - End } @@ -434,9 +431,9 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("return ${fieldName}RealmList") nextControlFlow("else") if (Utils.isRealmModelList(field)) { - emitStatement("OsList osList = proxyState.getRow\$realm().getModelList(%s)", fieldIndexVariableReference(field)) + emitStatement("OsList osList = proxyState.getRow\$realm().getModelList(%s)", fieldColKeyVariableReference(field)) } else { - emitStatement("OsList osList = proxyState.getRow\$realm().getValueList(%1\$s, RealmFieldType.%2\$s)", fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name) + emitStatement("OsList osList = proxyState.getRow\$realm().getValueList(%1\$s, RealmFieldType.%2\$s)", fieldColKeyVariableReference(field), Utils.getValueListFieldType(field).name) } emitStatement("${fieldName}RealmList = new RealmList<%s>(%s.class, osList, proxyState.getRealm\$realm())", genericType, genericType) emitStatement("return ${fieldName}RealmList") @@ -477,9 +474,9 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("proxyState.getRealm\$realm().checkIfValid()") if (Utils.isRealmModelList(field)) { - emitStatement("OsList osList = proxyState.getRow\$realm().getModelList(%s)", fieldIndexVariableReference(field)) + emitStatement("OsList osList = proxyState.getRow\$realm().getModelList(%s)", fieldColKeyVariableReference(field)) } else { - emitStatement("OsList osList = proxyState.getRow\$realm().getValueList(%1\$s, RealmFieldType.%2\$s)", fieldIndexVariableReference(field), Utils.getValueListFieldType(field).name) + emitStatement("OsList osList = proxyState.getRow\$realm().getValueList(%1\$s, RealmFieldType.%2\$s)", fieldColKeyVariableReference(field), Utils.getValueListFieldType(field).name) } if (forRealmModel) { // Model lists. @@ -489,7 +486,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("for (int i = 0; i < objects; i++)") emitStatement("%s linkedObject = value.get(i)", genericType) emitStatement("proxyState.checkValidObject(linkedObject)") - emitStatement("osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet\$proxyState().getRow\$realm().getIndex())") + emitStatement("osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet\$proxyState().getRow\$realm().getObjectKey())") endControlFlow() nextControlFlow("else") emitStatement("osList.removeAll()") @@ -500,7 +497,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("for (int i = 0; i < objects; i++)") emitStatement("%s linkedObject = value.get(i)", genericType) emitStatement("proxyState.checkValidObject(linkedObject)") - emitStatement("osList.addRow(((RealmObjectProxy) linkedObject).realmGet\$proxyState().getRow\$realm().getIndex())") + emitStatement("osList.addRow(((RealmObjectProxy) linkedObject).realmGet\$proxyState().getRow\$realm().getObjectKey())") endControlFlow() endControlFlow() } else { @@ -763,7 +760,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi "Map", "cache", "Set", "flags") - beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null)") + beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null)") emitStatement("final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm()") beginControlFlow("if (otherRealm.threadId != realm.threadId)") emitStatement("throw new IllegalArgumentException(\"Objects which belong to Realm instances in other threads cannot be copied into this Realm instance.\")") @@ -788,38 +785,38 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("boolean canUpdate = update") beginControlFlow("if (canUpdate)") emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) - emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey)) val primaryKeyGetter = metadata.primaryKeyGetter val primaryKeyElement = metadata.primaryKey if (metadata.isNullable(primaryKeyElement!!)) { if (Utils.isString(primaryKeyElement)) { emitStatement("String value = ((%s) object).%s()", interfaceName, primaryKeyGetter) - emitStatement("long rowIndex = Table.NO_MATCH") + emitStatement("long colKey = Table.NO_MATCH") beginControlFlow("if (value == null)") - emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") + emitStatement("colKey = table.findFirstNull(pkColumnKey)") nextControlFlow("else") - emitStatement("rowIndex = table.findFirstString(pkColumnIndex, value)") + emitStatement("colKey = table.findFirstString(pkColumnKey, value)") endControlFlow() } else { emitStatement("Number value = ((%s) object).%s()", interfaceName, primaryKeyGetter) - emitStatement("long rowIndex = Table.NO_MATCH") + emitStatement("long colKey = Table.NO_MATCH") beginControlFlow("if (value == null)") - emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") + emitStatement("colKey = table.findFirstNull(pkColumnKey)") nextControlFlow("else") - emitStatement("rowIndex = table.findFirstLong(pkColumnIndex, value.longValue())") + emitStatement("colKey = table.findFirstLong(pkColumnKey, value.longValue())") endControlFlow() } } else { val pkType = if (Utils.isString(metadata.primaryKey)) "String" else "Long" - emitStatement("long rowIndex = table.findFirst%s(pkColumnIndex, ((%s) object).%s())", pkType, interfaceName, primaryKeyGetter) + emitStatement("long colKey = table.findFirst%s(pkColumnKey, ((%s) object).%s())", pkType, interfaceName, primaryKeyGetter) } - beginControlFlow("if (rowIndex == Table.NO_MATCH)") + beginControlFlow("if (colKey == Table.NO_MATCH)") emitStatement("canUpdate = false") nextControlFlow("else") beginControlFlow("try") - emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex), columnInfo, false, Collections. emptyList())") + emitStatement("objectContext.set(realm, table.getUncheckedRow(colKey), columnInfo, false, Collections. emptyList())") emitStatement("realmObject = new %s()", generatedClassName) emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") nextControlFlow("finally") @@ -844,7 +841,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi "int", "short", "byte" -> { - emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, colKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) } "java.lang.Long", "java.lang.Integer", @@ -852,89 +849,89 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi "java.lang.Byte" -> { emitStatement("Number %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter) + emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, colKey, %s.longValue(), false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) } endControlFlow() } "io.realm.MutableRealmInteger" -> { emitStatement("Long %s = ((%s) object).%s().get()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.longValue(), false)", fieldName, getter) + emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, colKey, %s.longValue(), false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) } endControlFlow() } "double" -> { - emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sColKey, colKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) } "java.lang.Double" -> { emitStatement("Double %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) } endControlFlow() } "float" -> { - emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sColKey, colKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) } "java.lang.Float" -> { emitStatement("Float %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) } endControlFlow() } "boolean" -> { - emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sColKey, colKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) } "java.lang.Boolean" -> { emitStatement("Boolean %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) } endControlFlow() } "byte[]" -> { emitStatement("byte[] %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) } endControlFlow() } "java.util.Date" -> { emitStatement("java.util.Date %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sIndex, rowIndex, %s.getTime(), false)", fieldName, getter) + emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sColKey, colKey, %s.getTime(), false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) } endControlFlow() } "java.lang.String" -> { emitStatement("String %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sIndex, rowIndex, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sIndex, rowIndex, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) } endControlFlow() } @@ -951,8 +948,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginMethod("long","insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", qualifiedJavaClassName.toString(), "object", "Map", "cache") // If object is already in the Realm there is nothing to update - beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") - emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getIndex()") + beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey()") endControlFlow() emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) @@ -960,7 +957,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) if (metadata.hasPrimaryKey()) { - emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey)) } addPrimaryKeyCheckIfNeeded(metadata, true, writer) @@ -978,7 +975,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache%s == null)", fieldName) emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sIndex, rowIndex, cache%1\$s, false)", fieldName) + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, colKey, cache%1\$s, false)", fieldName) endControlFlow() } Utils.isRealmModelList(field) -> { @@ -986,7 +983,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) beginControlFlow("if (cacheItemIndex%s == null)", fieldName) @@ -1002,7 +999,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) beginControlFlow("if (%1\$sItem == null)", fieldName) emitStatement(fieldName + "OsList.addNull()") @@ -1020,7 +1017,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } } - emitStatement("return rowIndex") + emitStatement("return colKey") endMethod() emitEmptyLine() } @@ -1034,7 +1031,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("long tableNativePtr = table.getNativePtr()") emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) if (metadata.hasPrimaryKey()) { - emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey)) } emitStatement("%s object = null", qualifiedJavaClassName) @@ -1043,8 +1040,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache.containsKey(object))") emitStatement("continue") endControlFlow() - beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") - emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getIndex())") + beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey())") emitStatement("continue") endControlFlow() @@ -1063,14 +1060,14 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache%s == null)", fieldName) emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) endControlFlow() - emitStatement("table.setLink(columnInfo.%1\$sIndex, rowIndex, cache%1\$s, false)", fieldName) + emitStatement("table.setLink(columnInfo.%1\$sColKey, colKey, cache%1\$s, false)", fieldName) endControlFlow() } else if (Utils.isRealmModelList(field)) { val genericType = Utils.getGenericTypeQualifiedName(field) emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) beginControlFlow("if (cacheItemIndex%s == null)", fieldName) @@ -1085,7 +1082,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) beginControlFlow("if (%1\$sItem == null)", fieldName) emitStatement("%1\$sOsList.addNull()", fieldName) @@ -1112,15 +1109,15 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginMethod("long", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", qualifiedJavaClassName.toString(), "object", "Map", "cache") // If object is already in the Realm there is nothing to update - beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") - emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getIndex()") + beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey()") endControlFlow() emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("long tableNativePtr = table.getNativePtr()") emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) if (metadata.hasPrimaryKey()) { - emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey)) } addPrimaryKeyCheckIfNeeded(metadata, false, writer) @@ -1137,15 +1134,15 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache%s == null)", fieldName) emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sIndex, rowIndex, cache%1\$s, false)", fieldName) + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, colKey, cache%1\$s, false)", fieldName) nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) + emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, colKey)", fieldName) endControlFlow() } else if (Utils.isRealmModelList(field)) { val genericType = Utils.getGenericTypeQualifiedName(field) emitEmptyLine() - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%1\$sList != null && %1\$sList.size() == %1\$sOsList.size())", fieldName) emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") @@ -1175,7 +1172,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val genericType = Utils.getGenericTypeQualifiedName(field) val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) emitEmptyLine() - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("%1\$sOsList.removeAll()", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) @@ -1195,7 +1192,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } } - emitStatement("return rowIndex") + emitStatement("return colKey") endMethod() emitEmptyLine() } @@ -1209,7 +1206,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("long tableNativePtr = table.getNativePtr()") emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) if (metadata.hasPrimaryKey()) { - emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) + emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey)) } emitStatement("%s object = null", qualifiedJavaClassName) beginControlFlow("while (objects.hasNext())") @@ -1218,8 +1215,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("continue") endControlFlow() - beginControlFlow("if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") - emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getIndex())") + beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey())") emitStatement("continue") endControlFlow() addPrimaryKeyCheckIfNeeded(metadata, false, writer) @@ -1238,16 +1235,16 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache%s == null)", fieldName) emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sIndex, rowIndex, cache%1\$s, false)", fieldName) + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, colKey, cache%1\$s, false)", fieldName) nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sIndex, rowIndex)", fieldName) + emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, colKey)", fieldName) endControlFlow() } Utils.isRealmModelList(field) -> { val genericType = Utils.getGenericTypeQualifiedName(field) emitEmptyLine() - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%1\$sList != null && %1\$sList.size() == %1\$sOsList.size())", fieldName) emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") @@ -1278,7 +1275,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val genericType = Utils.getGenericTypeQualifiedName(field) val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) emitEmptyLine() - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.%1\$sIndex)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("%1\$sOsList.removeAll()", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) @@ -1314,38 +1311,38 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi if (metadata.isNullable(primaryKeyElement!!)) { if (Utils.isString(primaryKeyElement)) { emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - emitStatement("long rowIndex = Table.NO_MATCH") + emitStatement("long colKey = Table.NO_MATCH") beginControlFlow("if (primaryKeyValue == null)") - emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") + emitStatement("colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") nextControlFlow("else") - emitStatement("rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue)") + emitStatement("colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue)") endControlFlow() } else { emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - emitStatement("long rowIndex = Table.NO_MATCH") + emitStatement("long colKey = Table.NO_MATCH") beginControlFlow("if (primaryKeyValue == null)") - emitStatement("rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex)") + emitStatement("colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") nextControlFlow("else") - emitStatement("rowIndex = Table.nativeFindFirstInt(tableNativePtr, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) + emitStatement("colKey = Table.nativeFindFirstInt(tableNativePtr, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) endControlFlow() } } else { - emitStatement("long rowIndex = Table.NO_MATCH") + emitStatement("long colKey = Table.NO_MATCH") emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) beginControlFlow("if (primaryKeyValue != null)") if (Utils.isString(metadata.primaryKey)) { - emitStatement("rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, (String)primaryKeyValue)") + emitStatement("colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, (String)primaryKeyValue)") } else { - emitStatement("rowIndex = Table.nativeFindFirstInt(tableNativePtr, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) + emitStatement("colKey = Table.nativeFindFirstInt(tableNativePtr, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) } endControlFlow() } - beginControlFlow("if (rowIndex == Table.NO_MATCH)") + beginControlFlow("if (colKey == Table.NO_MATCH)") if (Utils.isString(metadata.primaryKey)) { - emitStatement("rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue)") + emitStatement("colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue)") } else { - emitStatement("rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, ((%s) object).%s())", interfaceName, primaryKeyGetter) + emitStatement("colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) } if (throwIfPrimaryKeyDuplicate) { @@ -1353,10 +1350,10 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("Table.throwDuplicatePrimaryKeyException(primaryKeyValue)") } endControlFlow() - emitStatement("cache.put(object, rowIndex)") + emitStatement("cache.put(object, colKey)") } else { - emitStatement("long rowIndex = OsObject.createRow(table)") - emitStatement("cache.put(object, rowIndex)") + emitStatement("long colKey = OsObject.createRow(table)") + emitStatement("cache.put(object, colKey)") } } } @@ -1380,16 +1377,16 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("%1\$s realmObjectSource = (%1\$s) newObject", interfaceName) emitEmptyLine() emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) - emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags)") + emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, flags)") // Copy basic types emitEmptyLine() emitSingleLineComment("Add all non-\"object reference\" fields") for (field in metadata.getBasicTypeFields()) { - val fieldIndex = fieldIndexVariableReference(field) + val fieldColKey = fieldColKeyVariableReference(field) val fieldName = field.simpleName.toString() val getter = metadata.getInternalGetter(fieldName) - emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldIndex, getter) + emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldColKey, getter) } // Create the underlying object @@ -1545,25 +1542,25 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("%1\$s realmObjectTarget = (%1\$s) realmObject", interfaceName) emitStatement("%1\$s realmObjectSource = (%1\$s) newObject", interfaceName) emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) - emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags)") + emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, flags)") for (field in metadata.fields) { val fieldType = field.asType().toString() val fieldName = field.simpleName.toString() val getter = metadata.getInternalGetter(fieldName) - val fieldIndex = fieldIndexVariableReference(field) + val fieldColKey = fieldColKeyVariableReference(field) when { Utils.isRealmModel(field) -> { emitEmptyLine() emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) beginControlFlow("if (%sObj == null)", fieldName) - emitStatement("builder.addNull(%s)", fieldIndexVariableReference(field)) + emitStatement("builder.addNull(%s)", fieldColKeyVariableReference(field)) nextControlFlow("else") emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) beginControlFlow("if (cache%s != null)", fieldName) - emitStatement("builder.addObject(%s, cache%s)", fieldIndex, fieldName) + emitStatement("builder.addObject(%s, cache%s)", fieldColKey, fieldName) nextControlFlow("else") - emitStatement("builder.addObject(%s, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, true, cache, flags))", fieldIndex, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) + emitStatement("builder.addObject(%s, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, true, cache, flags))", fieldColKey, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) endControlFlow() // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. endControlFlow() @@ -1583,13 +1580,13 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("%1\$sManagedCopy.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, true, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) endControlFlow() endControlFlow() - emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldIndex, fieldName) + emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldColKey, fieldName) nextControlFlow("else") - emitStatement("builder.addObjectList(%s, new RealmList<%s>())", fieldIndex, genericType) + emitStatement("builder.addObjectList(%s, new RealmList<%s>())", fieldColKey, genericType) endControlFlow() } else -> { - emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldIndex, getter) + emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldColKey, getter) } } } @@ -1669,12 +1666,12 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginMethod("int", "hashCode", EnumSet.of(Modifier.PUBLIC)) emitStatement("String realmName = proxyState.getRealm\$realm().getPath()") emitStatement("String tableName = proxyState.getRow\$realm().getTable().getName()") - emitStatement("long rowIndex = proxyState.getRow\$realm().getIndex()") + emitStatement("long colKey = proxyState.getRow\$realm().getObjectKey()") emitEmptyLine() emitStatement("int result = 17") emitStatement("result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0)") emitStatement("result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0)") - emitStatement("result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32))") + emitStatement("result = 31 * result + (int) (colKey ^ (colKey >>> 32))") emitStatement("return result") endMethod() emitEmptyLine() @@ -1697,15 +1694,21 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("if (o == null || getClass() != o.getClass()) return false") emitStatement("%s %s = (%s)o", proxyClassName, otherObjectVarName, proxyClassName) // FooRealmProxy aFoo = (FooRealmProxy)o emitEmptyLine() - emitStatement("String path = proxyState.getRealm\$realm().getPath()") - emitStatement("String otherPath = %s.proxyState.getRealm\$realm().getPath()", otherObjectVarName) + emitStatement("BaseRealm realm = proxyState.getRealm\$realm()") + emitStatement("BaseRealm otherRealm = %s.proxyState.getRealm\$realm()", otherObjectVarName) + emitStatement("String path = realm.getPath()") + emitStatement("String otherPath = otherRealm.getPath()") emitStatement("if (path != null ? !path.equals(otherPath) : otherPath != null) return false") + emitStatement("if (realm.isFrozen() != otherRealm.isFrozen()) return false") + beginControlFlow("if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID()))") + emitStatement("return false") + endControlFlow() emitEmptyLine() emitStatement("String tableName = proxyState.getRow\$realm().getTable().getName()") emitStatement("String otherTableName = %s.proxyState.getRow\$realm().getTable().getName()", otherObjectVarName) emitStatement("if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false") emitEmptyLine() - emitStatement("if (proxyState.getRow\$realm().getIndex() != %s.proxyState.getRow\$realm().getIndex()) return false", otherObjectVarName) + emitStatement("if (proxyState.getRow\$realm().getObjectKey() != %s.proxyState.getRow\$realm().getObjectKey()) return false", otherObjectVarName) emitEmptyLine() emitStatement("return true") endMethod() @@ -1733,23 +1736,23 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (update)") emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) - emitStatement("long pkColumnIndex = %s", fieldIndexVariableReference(metadata.primaryKey)) - emitStatement("long rowIndex = Table.NO_MATCH") + emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey)) + emitStatement("long colKey = Table.NO_MATCH") if (metadata.isNullable(metadata.primaryKey!!)) { beginControlFlow("if (json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) - emitStatement("rowIndex = table.findFirstNull(pkColumnIndex)") + emitStatement("colKey = table.findFirstNull(pkColumnKey)") nextControlFlow("else") - emitStatement("rowIndex = table.findFirst%s(pkColumnIndex, json.get%s(\"%s\"))", pkType, pkType, metadata.primaryKey!!.simpleName) + emitStatement("colKey = table.findFirst%s(pkColumnKey, json.get%s(\"%s\"))", pkType, pkType, metadata.primaryKey!!.simpleName) endControlFlow() } else { beginControlFlow("if (!json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) - emitStatement("rowIndex = table.findFirst%s(pkColumnIndex, json.get%s(\"%s\"))", pkType, pkType, metadata.primaryKey!!.simpleName) + emitStatement("colKey = table.findFirst%s(pkColumnKey, json.get%s(\"%s\"))", pkType, pkType, metadata.primaryKey!!.simpleName) endControlFlow() } - beginControlFlow("if (rowIndex != Table.NO_MATCH)") + beginControlFlow("if (colKey != Table.NO_MATCH)") emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") beginControlFlow("try") - emitStatement("objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", qualifiedJavaClassName) + emitStatement("objectContext.set(realm, table.getUncheckedRow(colKey), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", qualifiedJavaClassName) emitStatement("obj = new %s()", generatedClassName) nextControlFlow("finally") emitStatement("objectContext.clear()") @@ -1919,16 +1922,16 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi return Utils.getSimpleColumnInfoClassName(qualifiedModelClassName) } - private fun columnIndexVarName(variableElement: VariableElement): String { - return "${variableElement.simpleName}Index" + private fun columnKeyVarName(variableElement: VariableElement): String { + return "${variableElement.simpleName}ColKey" } private fun mutableRealmIntegerFieldName(variableElement: VariableElement): String { return "${variableElement.simpleName}MutableRealmInteger" } - private fun fieldIndexVariableReference(variableElement: VariableElement?): String { - return "columnInfo.${columnIndexVarName(variableElement!!)}" + private fun fieldColKeyVariableReference(variableElement: VariableElement?): String { + return "columnInfo.${columnKeyVarName(variableElement!!)}" } private fun getRealmType(field: VariableElement): Constants.RealmFieldType { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index a9899a51d0..2da49cede1 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -39,53 +39,51 @@ public class some_test_AllTypesRealmProxy extends some.test.AllTypes implements RealmObjectProxy, some_test_AllTypesRealmProxyInterface { static final class AllTypesColumnInfo extends ColumnInfo { - long maxColumnIndexValue; - long columnStringIndex; - long columnLongIndex; - long columnFloatIndex; - long columnDoubleIndex; - long columnBooleanIndex; - long columnDateIndex; - long columnBinaryIndex; - long columnMutableRealmIntegerIndex; - long columnObjectIndex; - long columnRealmListIndex; - long columnStringListIndex; - long columnBinaryListIndex; - long columnBooleanListIndex; - long columnLongListIndex; - long columnIntegerListIndex; - long columnShortListIndex; - long columnByteListIndex; - long columnDoubleListIndex; - long columnFloatListIndex; - long columnDateListIndex; + long columnStringColKey; + long columnLongColKey; + long columnFloatColKey; + long columnDoubleColKey; + long columnBooleanColKey; + long columnDateColKey; + long columnBinaryColKey; + long columnMutableRealmIntegerColKey; + long columnObjectColKey; + long columnRealmListColKey; + long columnStringListColKey; + long columnBinaryListColKey; + long columnBooleanListColKey; + long columnLongListColKey; + long columnIntegerListColKey; + long columnShortListColKey; + long columnByteListColKey; + long columnDoubleListColKey; + long columnFloatListColKey; + long columnDateListColKey; AllTypesColumnInfo(OsSchemaInfo schemaInfo) { super(20); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("AllTypes"); - this.columnStringIndex = addColumnDetails("columnString", "columnString", objectSchemaInfo); - this.columnLongIndex = addColumnDetails("columnLong", "columnLong", objectSchemaInfo); - this.columnFloatIndex = addColumnDetails("columnFloat", "columnFloat", objectSchemaInfo); - this.columnDoubleIndex = addColumnDetails("columnDouble", "columnDouble", objectSchemaInfo); - this.columnBooleanIndex = addColumnDetails("columnBoolean", "columnBoolean", objectSchemaInfo); - this.columnDateIndex = addColumnDetails("columnDate", "columnDate", objectSchemaInfo); - this.columnBinaryIndex = addColumnDetails("columnBinary", "columnBinary", objectSchemaInfo); - this.columnMutableRealmIntegerIndex = addColumnDetails("columnMutableRealmInteger", "columnMutableRealmInteger", objectSchemaInfo); - this.columnObjectIndex = addColumnDetails("columnObject", "columnObject", objectSchemaInfo); - this.columnRealmListIndex = addColumnDetails("columnRealmList", "columnRealmList", objectSchemaInfo); - this.columnStringListIndex = addColumnDetails("columnStringList", "columnStringList", objectSchemaInfo); - this.columnBinaryListIndex = addColumnDetails("columnBinaryList", "columnBinaryList", objectSchemaInfo); - this.columnBooleanListIndex = addColumnDetails("columnBooleanList", "columnBooleanList", objectSchemaInfo); - this.columnLongListIndex = addColumnDetails("columnLongList", "columnLongList", objectSchemaInfo); - this.columnIntegerListIndex = addColumnDetails("columnIntegerList", "columnIntegerList", objectSchemaInfo); - this.columnShortListIndex = addColumnDetails("columnShortList", "columnShortList", objectSchemaInfo); - this.columnByteListIndex = addColumnDetails("columnByteList", "columnByteList", objectSchemaInfo); - this.columnDoubleListIndex = addColumnDetails("columnDoubleList", "columnDoubleList", objectSchemaInfo); - this.columnFloatListIndex = addColumnDetails("columnFloatList", "columnFloatList", objectSchemaInfo); - this.columnDateListIndex = addColumnDetails("columnDateList", "columnDateList", objectSchemaInfo); + this.columnStringColKey = addColumnDetails("columnString", "columnString", objectSchemaInfo); + this.columnLongColKey = addColumnDetails("columnLong", "columnLong", objectSchemaInfo); + this.columnFloatColKey = addColumnDetails("columnFloat", "columnFloat", objectSchemaInfo); + this.columnDoubleColKey = addColumnDetails("columnDouble", "columnDouble", objectSchemaInfo); + this.columnBooleanColKey = addColumnDetails("columnBoolean", "columnBoolean", objectSchemaInfo); + this.columnDateColKey = addColumnDetails("columnDate", "columnDate", objectSchemaInfo); + this.columnBinaryColKey = addColumnDetails("columnBinary", "columnBinary", objectSchemaInfo); + this.columnMutableRealmIntegerColKey = addColumnDetails("columnMutableRealmInteger", "columnMutableRealmInteger", objectSchemaInfo); + this.columnObjectColKey = addColumnDetails("columnObject", "columnObject", objectSchemaInfo); + this.columnRealmListColKey = addColumnDetails("columnRealmList", "columnRealmList", objectSchemaInfo); + this.columnStringListColKey = addColumnDetails("columnStringList", "columnStringList", objectSchemaInfo); + this.columnBinaryListColKey = addColumnDetails("columnBinaryList", "columnBinaryList", objectSchemaInfo); + this.columnBooleanListColKey = addColumnDetails("columnBooleanList", "columnBooleanList", objectSchemaInfo); + this.columnLongListColKey = addColumnDetails("columnLongList", "columnLongList", objectSchemaInfo); + this.columnIntegerListColKey = addColumnDetails("columnIntegerList", "columnIntegerList", objectSchemaInfo); + this.columnShortListColKey = addColumnDetails("columnShortList", "columnShortList", objectSchemaInfo); + this.columnByteListColKey = addColumnDetails("columnByteList", "columnByteList", objectSchemaInfo); + this.columnDoubleListColKey = addColumnDetails("columnDoubleList", "columnDoubleList", objectSchemaInfo); + this.columnFloatListColKey = addColumnDetails("columnFloatList", "columnFloatList", objectSchemaInfo); + this.columnDateListColKey = addColumnDetails("columnDateList", "columnDateList", objectSchemaInfo); addBacklinkDetails(schemaInfo, "parentObjects", "AllTypes", "columnObject"); - this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); } AllTypesColumnInfo(ColumnInfo src, boolean mutable) { @@ -102,27 +100,26 @@ protected final ColumnInfo copy(boolean mutable) { protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final AllTypesColumnInfo src = (AllTypesColumnInfo) rawSrc; final AllTypesColumnInfo dst = (AllTypesColumnInfo) rawDst; - dst.columnStringIndex = src.columnStringIndex; - dst.columnLongIndex = src.columnLongIndex; - dst.columnFloatIndex = src.columnFloatIndex; - dst.columnDoubleIndex = src.columnDoubleIndex; - dst.columnBooleanIndex = src.columnBooleanIndex; - dst.columnDateIndex = src.columnDateIndex; - dst.columnBinaryIndex = src.columnBinaryIndex; - dst.columnMutableRealmIntegerIndex = src.columnMutableRealmIntegerIndex; - dst.columnObjectIndex = src.columnObjectIndex; - dst.columnRealmListIndex = src.columnRealmListIndex; - dst.columnStringListIndex = src.columnStringListIndex; - dst.columnBinaryListIndex = src.columnBinaryListIndex; - dst.columnBooleanListIndex = src.columnBooleanListIndex; - dst.columnLongListIndex = src.columnLongListIndex; - dst.columnIntegerListIndex = src.columnIntegerListIndex; - dst.columnShortListIndex = src.columnShortListIndex; - dst.columnByteListIndex = src.columnByteListIndex; - dst.columnDoubleListIndex = src.columnDoubleListIndex; - dst.columnFloatListIndex = src.columnFloatListIndex; - dst.columnDateListIndex = src.columnDateListIndex; - dst.maxColumnIndexValue = src.maxColumnIndexValue; + dst.columnStringColKey = src.columnStringColKey; + dst.columnLongColKey = src.columnLongColKey; + dst.columnFloatColKey = src.columnFloatColKey; + dst.columnDoubleColKey = src.columnDoubleColKey; + dst.columnBooleanColKey = src.columnBooleanColKey; + dst.columnDateColKey = src.columnDateColKey; + dst.columnBinaryColKey = src.columnBinaryColKey; + dst.columnMutableRealmIntegerColKey = src.columnMutableRealmIntegerColKey; + dst.columnObjectColKey = src.columnObjectColKey; + dst.columnRealmListColKey = src.columnRealmListColKey; + dst.columnStringListColKey = src.columnStringListColKey; + dst.columnBinaryListColKey = src.columnBinaryListColKey; + dst.columnBooleanListColKey = src.columnBooleanListColKey; + dst.columnLongListColKey = src.columnLongListColKey; + dst.columnIntegerListColKey = src.columnIntegerListColKey; + dst.columnShortListColKey = src.columnShortListColKey; + dst.columnByteListColKey = src.columnByteListColKey; + dst.columnDoubleListColKey = src.columnDoubleListColKey; + dst.columnFloatListColKey = src.columnFloatListColKey; + dst.columnDateListColKey = src.columnDateListColKey; } } @@ -132,7 +129,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private ProxyState proxyState; private final MutableRealmInteger.Managed columnMutableRealmIntegerMutableRealmInteger = new MutableRealmInteger.Managed() { @Override protected ProxyState getProxyState() { return proxyState; } - @Override protected long getColumnIndex() { return columnInfo.columnMutableRealmIntegerIndex; } + @Override protected long getColumnIndex() { return columnInfo.columnMutableRealmIntegerColKey; } }; private RealmList columnRealmListRealmList; private RealmList columnStringListRealmList; @@ -169,7 +166,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @SuppressWarnings("cast") public String realmGet$columnString() { proxyState.getRealm$realm().checkIfValid(); - return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.columnStringIndex); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.columnStringColKey); } @Override @@ -187,7 +184,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @SuppressWarnings("cast") public long realmGet$columnLong() { proxyState.getRealm$realm().checkIfValid(); - return (long) proxyState.getRow$realm().getLong(columnInfo.columnLongIndex); + return (long) proxyState.getRow$realm().getLong(columnInfo.columnLongColKey); } @Override @@ -197,19 +194,19 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } final Row row = proxyState.getRow$realm(); - row.getTable().setLong(columnInfo.columnLongIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.columnLongColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); - proxyState.getRow$realm().setLong(columnInfo.columnLongIndex, value); + proxyState.getRow$realm().setLong(columnInfo.columnLongColKey, value); } @Override @SuppressWarnings("cast") public float realmGet$columnFloat() { proxyState.getRealm$realm().checkIfValid(); - return (float) proxyState.getRow$realm().getFloat(columnInfo.columnFloatIndex); + return (float) proxyState.getRow$realm().getFloat(columnInfo.columnFloatColKey); } @Override @@ -219,19 +216,19 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } final Row row = proxyState.getRow$realm(); - row.getTable().setFloat(columnInfo.columnFloatIndex, row.getIndex(), value, true); + row.getTable().setFloat(columnInfo.columnFloatColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); - proxyState.getRow$realm().setFloat(columnInfo.columnFloatIndex, value); + proxyState.getRow$realm().setFloat(columnInfo.columnFloatColKey, value); } @Override @SuppressWarnings("cast") public double realmGet$columnDouble() { proxyState.getRealm$realm().checkIfValid(); - return (double) proxyState.getRow$realm().getDouble(columnInfo.columnDoubleIndex); + return (double) proxyState.getRow$realm().getDouble(columnInfo.columnDoubleColKey); } @Override @@ -241,19 +238,19 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } final Row row = proxyState.getRow$realm(); - row.getTable().setDouble(columnInfo.columnDoubleIndex, row.getIndex(), value, true); + row.getTable().setDouble(columnInfo.columnDoubleColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); - proxyState.getRow$realm().setDouble(columnInfo.columnDoubleIndex, value); + proxyState.getRow$realm().setDouble(columnInfo.columnDoubleColKey, value); } @Override @SuppressWarnings("cast") public boolean realmGet$columnBoolean() { proxyState.getRealm$realm().checkIfValid(); - return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.columnBooleanIndex); + return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.columnBooleanColKey); } @Override @@ -263,19 +260,19 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } final Row row = proxyState.getRow$realm(); - row.getTable().setBoolean(columnInfo.columnBooleanIndex, row.getIndex(), value, true); + row.getTable().setBoolean(columnInfo.columnBooleanColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); - proxyState.getRow$realm().setBoolean(columnInfo.columnBooleanIndex, value); + proxyState.getRow$realm().setBoolean(columnInfo.columnBooleanColKey, value); } @Override @SuppressWarnings("cast") public Date realmGet$columnDate() { proxyState.getRealm$realm().checkIfValid(); - return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.columnDateIndex); + return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.columnDateColKey); } @Override @@ -288,7 +285,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'columnDate' to null."); } - row.getTable().setDate(columnInfo.columnDateIndex, row.getIndex(), value, true); + row.getTable().setDate(columnInfo.columnDateColKey, row.getObjectKey(), value, true); return; } @@ -296,14 +293,14 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'columnDate' to null."); } - proxyState.getRow$realm().setDate(columnInfo.columnDateIndex, value); + proxyState.getRow$realm().setDate(columnInfo.columnDateColKey, value); } @Override @SuppressWarnings("cast") public byte[] realmGet$columnBinary() { proxyState.getRealm$realm().checkIfValid(); - return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.columnBinaryIndex); + return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.columnBinaryColKey); } @Override @@ -316,7 +313,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'columnBinary' to null."); } - row.getTable().setBinaryByteArray(columnInfo.columnBinaryIndex, row.getIndex(), value, true); + row.getTable().setBinaryByteArray(columnInfo.columnBinaryColKey, row.getObjectKey(), value, true); return; } @@ -324,7 +321,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'columnBinary' to null."); } - proxyState.getRow$realm().setBinaryByteArray(columnInfo.columnBinaryIndex, value); + proxyState.getRow$realm().setBinaryByteArray(columnInfo.columnBinaryColKey, value); } @Override @@ -336,10 +333,10 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @Override public some.test.AllTypes realmGet$columnObject() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNullLink(columnInfo.columnObjectIndex)) { + if (proxyState.getRow$realm().isNullLink(columnInfo.columnObjectColKey)) { return null; } - return proxyState.getRealm$realm().get(some.test.AllTypes.class, proxyState.getRow$realm().getLink(columnInfo.columnObjectIndex), false, Collections.emptyList()); + return proxyState.getRealm$realm().get(some.test.AllTypes.class, proxyState.getRow$realm().getLink(columnInfo.columnObjectColKey), false, Collections.emptyList()); } @Override @@ -357,21 +354,21 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final Row row = proxyState.getRow$realm(); if (value == null) { // Table#nullifyLink() does not support default value. Just using Row. - row.nullifyLink(columnInfo.columnObjectIndex); + row.nullifyLink(columnInfo.columnObjectColKey); return; } proxyState.checkValidObject(value); - row.getTable().setLink(columnInfo.columnObjectIndex, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true); + row.getTable().setLink(columnInfo.columnObjectColKey, row.getObjectKey(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getObjectKey(), true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().nullifyLink(columnInfo.columnObjectIndex); + proxyState.getRow$realm().nullifyLink(columnInfo.columnObjectColKey); return; } proxyState.checkValidObject(value); - proxyState.getRow$realm().setLink(columnInfo.columnObjectIndex, ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex()); + proxyState.getRow$realm().setLink(columnInfo.columnObjectColKey, ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getObjectKey()); } @Override @@ -381,7 +378,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnRealmListRealmList != null) { return columnRealmListRealmList; } else { - OsList osList = proxyState.getRow$realm().getModelList(columnInfo.columnRealmListIndex); + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.columnRealmListColKey); columnRealmListRealmList = new RealmList(some.test.AllTypes.class, osList, proxyState.getRealm$realm()); return columnRealmListRealmList; } @@ -412,14 +409,14 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getModelList(columnInfo.columnRealmListIndex); + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.columnRealmListColKey); // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. if (value != null && value.size() == osList.size()) { int objects = value.size(); for (int i = 0; i < objects; i++) { some.test.AllTypes linkedObject = value.get(i); proxyState.checkValidObject(linkedObject); - osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex()); + osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getObjectKey()); } } else { osList.removeAll(); @@ -430,7 +427,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { for (int i = 0; i < objects; i++) { some.test.AllTypes linkedObject = value.get(i); proxyState.checkValidObject(linkedObject); - osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getIndex()); + osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getObjectKey()); } } } @@ -442,7 +439,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnStringListRealmList != null) { return columnStringListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnStringListIndex, RealmFieldType.STRING_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnStringListColKey, RealmFieldType.STRING_LIST); columnStringListRealmList = new RealmList(java.lang.String.class, osList, proxyState.getRealm$realm()); return columnStringListRealmList; } @@ -460,7 +457,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnStringListIndex, RealmFieldType.STRING_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnStringListColKey, RealmFieldType.STRING_LIST); osList.removeAll(); if (value == null) { return; @@ -481,7 +478,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnBinaryListRealmList != null) { return columnBinaryListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBinaryListIndex, RealmFieldType.BINARY_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBinaryListColKey, RealmFieldType.BINARY_LIST); columnBinaryListRealmList = new RealmList(byte[].class, osList, proxyState.getRealm$realm()); return columnBinaryListRealmList; } @@ -499,7 +496,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBinaryListIndex, RealmFieldType.BINARY_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBinaryListColKey, RealmFieldType.BINARY_LIST); osList.removeAll(); if (value == null) { return; @@ -520,7 +517,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnBooleanListRealmList != null) { return columnBooleanListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBooleanListIndex, RealmFieldType.BOOLEAN_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBooleanListColKey, RealmFieldType.BOOLEAN_LIST); columnBooleanListRealmList = new RealmList(java.lang.Boolean.class, osList, proxyState.getRealm$realm()); return columnBooleanListRealmList; } @@ -538,7 +535,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBooleanListIndex, RealmFieldType.BOOLEAN_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnBooleanListColKey, RealmFieldType.BOOLEAN_LIST); osList.removeAll(); if (value == null) { return; @@ -559,7 +556,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnLongListRealmList != null) { return columnLongListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnLongListIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnLongListColKey, RealmFieldType.INTEGER_LIST); columnLongListRealmList = new RealmList(java.lang.Long.class, osList, proxyState.getRealm$realm()); return columnLongListRealmList; } @@ -577,7 +574,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnLongListIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnLongListColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -598,7 +595,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnIntegerListRealmList != null) { return columnIntegerListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnIntegerListIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnIntegerListColKey, RealmFieldType.INTEGER_LIST); columnIntegerListRealmList = new RealmList(java.lang.Integer.class, osList, proxyState.getRealm$realm()); return columnIntegerListRealmList; } @@ -616,7 +613,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnIntegerListIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnIntegerListColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -637,7 +634,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnShortListRealmList != null) { return columnShortListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnShortListIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnShortListColKey, RealmFieldType.INTEGER_LIST); columnShortListRealmList = new RealmList(java.lang.Short.class, osList, proxyState.getRealm$realm()); return columnShortListRealmList; } @@ -655,7 +652,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnShortListIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnShortListColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -676,7 +673,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnByteListRealmList != null) { return columnByteListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnByteListIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnByteListColKey, RealmFieldType.INTEGER_LIST); columnByteListRealmList = new RealmList(java.lang.Byte.class, osList, proxyState.getRealm$realm()); return columnByteListRealmList; } @@ -694,7 +691,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnByteListIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnByteListColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -715,7 +712,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnDoubleListRealmList != null) { return columnDoubleListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDoubleListIndex, RealmFieldType.DOUBLE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDoubleListColKey, RealmFieldType.DOUBLE_LIST); columnDoubleListRealmList = new RealmList(java.lang.Double.class, osList, proxyState.getRealm$realm()); return columnDoubleListRealmList; } @@ -733,7 +730,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDoubleListIndex, RealmFieldType.DOUBLE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDoubleListColKey, RealmFieldType.DOUBLE_LIST); osList.removeAll(); if (value == null) { return; @@ -754,7 +751,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnFloatListRealmList != null) { return columnFloatListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnFloatListIndex, RealmFieldType.FLOAT_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnFloatListColKey, RealmFieldType.FLOAT_LIST); columnFloatListRealmList = new RealmList(java.lang.Float.class, osList, proxyState.getRealm$realm()); return columnFloatListRealmList; } @@ -772,7 +769,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnFloatListIndex, RealmFieldType.FLOAT_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnFloatListColKey, RealmFieldType.FLOAT_LIST); osList.removeAll(); if (value == null) { return; @@ -793,7 +790,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (columnDateListRealmList != null) { return columnDateListRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDateListIndex, RealmFieldType.DATE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDateListColKey, RealmFieldType.DATE_LIST); columnDateListRealmList = new RealmList(java.util.Date.class, osList, proxyState.getRealm$realm()); return columnDateListRealmList; } @@ -811,7 +808,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDateListIndex, RealmFieldType.DATE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDateListColKey, RealmFieldType.DATE_LIST); osList.removeAll(); if (value == null) { return; @@ -838,7 +835,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", 20, 1); - builder.addPersistedProperty("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, Property.INDEXED, !Property.REQUIRED); + builder.addPersistedProperty("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnDouble", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -886,17 +883,17 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON if (update) { Table table = realm.getTable(some.test.AllTypes.class); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); - long pkColumnIndex = columnInfo.columnStringIndex; - long rowIndex = Table.NO_MATCH; + long pkColumnKey = columnInfo.columnStringColKey; + long colKey = Table.NO_MATCH; if (json.isNull("columnString")) { - rowIndex = table.findFirstNull(pkColumnIndex); + colKey = table.findFirstNull(pkColumnKey); } else { - rowIndex = table.findFirstString(pkColumnIndex, json.getString("columnString")); + colKey = table.findFirstString(pkColumnKey, json.getString("columnString")); } - if (rowIndex != Table.NO_MATCH) { + if (colKey != Table.NO_MATCH) { final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); try { - objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); + objectContext.set(realm, table.getUncheckedRow(colKey), realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); obj = new io.realm.some_test_AllTypesRealmProxy(); } finally { objectContext.clear(); @@ -1162,7 +1159,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } private static some_test_AllTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { - // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections.emptyList()); io.realm.some_test_AllTypesRealmProxy obj = new io.realm.some_test_AllTypesRealmProxy(); @@ -1171,7 +1168,7 @@ private static some_test_AllTypesRealmProxy newProxyInstance(BaseRealm realm, Ro } public static some.test.AllTypes copyOrUpdate(Realm realm, AllTypesColumnInfo columnInfo, some.test.AllTypes object, boolean update, Map cache, Set flags) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); @@ -1190,19 +1187,19 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, AllTypesColumnInfo co boolean canUpdate = update; if (canUpdate) { Table table = realm.getTable(some.test.AllTypes.class); - long pkColumnIndex = columnInfo.columnStringIndex; + long pkColumnKey = columnInfo.columnStringColKey; String value = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = Table.NO_MATCH; + long colKey = Table.NO_MATCH; if (value == null) { - rowIndex = table.findFirstNull(pkColumnIndex); + colKey = table.findFirstNull(pkColumnKey); } else { - rowIndex = table.findFirstString(pkColumnIndex, value); + colKey = table.findFirstString(pkColumnKey, value); } - if (rowIndex == Table.NO_MATCH) { + if (colKey == Table.NO_MATCH) { canUpdate = false; } else { try { - objectContext.set(realm, table.getUncheckedRow(rowIndex), columnInfo, false, Collections. emptyList()); + objectContext.set(realm, table.getUncheckedRow(colKey), columnInfo, false, Collections. emptyList()); realmObject = new io.realm.some_test_AllTypesRealmProxy(); cache.put(object, (RealmObjectProxy) realmObject); } finally { @@ -1223,27 +1220,27 @@ public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo some_test_AllTypesRealmProxyInterface realmObjectSource = (some_test_AllTypesRealmProxyInterface) newObject; Table table = realm.getTable(some.test.AllTypes.class); - OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.columnStringIndex, realmObjectSource.realmGet$columnString()); - builder.addInteger(columnInfo.columnLongIndex, realmObjectSource.realmGet$columnLong()); - builder.addFloat(columnInfo.columnFloatIndex, realmObjectSource.realmGet$columnFloat()); - builder.addDouble(columnInfo.columnDoubleIndex, realmObjectSource.realmGet$columnDouble()); - builder.addBoolean(columnInfo.columnBooleanIndex, realmObjectSource.realmGet$columnBoolean()); - builder.addDate(columnInfo.columnDateIndex, realmObjectSource.realmGet$columnDate()); - builder.addByteArray(columnInfo.columnBinaryIndex, realmObjectSource.realmGet$columnBinary()); - builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerIndex, realmObjectSource.realmGet$columnMutableRealmInteger()); - builder.addStringList(columnInfo.columnStringListIndex, realmObjectSource.realmGet$columnStringList()); - builder.addByteArrayList(columnInfo.columnBinaryListIndex, realmObjectSource.realmGet$columnBinaryList()); - builder.addBooleanList(columnInfo.columnBooleanListIndex, realmObjectSource.realmGet$columnBooleanList()); - builder.addLongList(columnInfo.columnLongListIndex, realmObjectSource.realmGet$columnLongList()); - builder.addIntegerList(columnInfo.columnIntegerListIndex, realmObjectSource.realmGet$columnIntegerList()); - builder.addShortList(columnInfo.columnShortListIndex, realmObjectSource.realmGet$columnShortList()); - builder.addByteList(columnInfo.columnByteListIndex, realmObjectSource.realmGet$columnByteList()); - builder.addDoubleList(columnInfo.columnDoubleListIndex, realmObjectSource.realmGet$columnDoubleList()); - builder.addFloatList(columnInfo.columnFloatListIndex, realmObjectSource.realmGet$columnFloatList()); - builder.addDateList(columnInfo.columnDateListIndex, realmObjectSource.realmGet$columnDateList()); + builder.addString(columnInfo.columnStringColKey, realmObjectSource.realmGet$columnString()); + builder.addInteger(columnInfo.columnLongColKey, realmObjectSource.realmGet$columnLong()); + builder.addFloat(columnInfo.columnFloatColKey, realmObjectSource.realmGet$columnFloat()); + builder.addDouble(columnInfo.columnDoubleColKey, realmObjectSource.realmGet$columnDouble()); + builder.addBoolean(columnInfo.columnBooleanColKey, realmObjectSource.realmGet$columnBoolean()); + builder.addDate(columnInfo.columnDateColKey, realmObjectSource.realmGet$columnDate()); + builder.addByteArray(columnInfo.columnBinaryColKey, realmObjectSource.realmGet$columnBinary()); + builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, realmObjectSource.realmGet$columnMutableRealmInteger()); + builder.addStringList(columnInfo.columnStringListColKey, realmObjectSource.realmGet$columnStringList()); + builder.addByteArrayList(columnInfo.columnBinaryListColKey, realmObjectSource.realmGet$columnBinaryList()); + builder.addBooleanList(columnInfo.columnBooleanListColKey, realmObjectSource.realmGet$columnBooleanList()); + builder.addLongList(columnInfo.columnLongListColKey, realmObjectSource.realmGet$columnLongList()); + builder.addIntegerList(columnInfo.columnIntegerListColKey, realmObjectSource.realmGet$columnIntegerList()); + builder.addShortList(columnInfo.columnShortListColKey, realmObjectSource.realmGet$columnShortList()); + builder.addByteList(columnInfo.columnByteListColKey, realmObjectSource.realmGet$columnByteList()); + builder.addDoubleList(columnInfo.columnDoubleListColKey, realmObjectSource.realmGet$columnDoubleList()); + builder.addFloatList(columnInfo.columnFloatListColKey, realmObjectSource.realmGet$columnFloatList()); + builder.addDateList(columnInfo.columnDateListColKey, realmObjectSource.realmGet$columnDateList()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. @@ -1283,41 +1280,41 @@ public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo } public static long insert(Realm realm, some.test.AllTypes object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.AllTypes.class); long tableNativePtr = table.getNativePtr(); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); - long pkColumnIndex = columnInfo.columnStringIndex; + long pkColumnKey = columnInfo.columnStringColKey; String primaryKeyValue = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = Table.NO_MATCH; + long colKey = Table.NO_MATCH; if (primaryKeyValue == null) { - rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); + colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); } else { - rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); + colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); } - if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue); + if (colKey == Table.NO_MATCH) { + colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } - cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + cache.put(object, colKey); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, colKey, realmGet$columnDate.getTime(), false); } byte[] realmGet$columnBinary = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryColKey, colKey, realmGet$columnBinary, false); } Long realmGet$columnMutableRealmInteger = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); if (realmGet$columnMutableRealmInteger != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerIndex, rowIndex, realmGet$columnMutableRealmInteger.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, colKey, realmGet$columnMutableRealmInteger.longValue(), false); } some.test.AllTypes columnObjectObj = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObject(); @@ -1326,12 +1323,12 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null) { - OsList columnRealmListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnRealmListIndex); + OsList columnRealmListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnRealmListColKey); for (some.test.AllTypes columnRealmListItem : columnRealmListList) { Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { @@ -1343,7 +1340,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { - OsList columnStringListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnStringListIndex); + OsList columnStringListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnStringListColKey); for (java.lang.String columnStringListItem : columnStringListList) { if (columnStringListItem == null) { columnStringListOsList.addNull(); @@ -1355,7 +1352,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { - OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBinaryListColKey); for (byte[] columnBinaryListItem : columnBinaryListList) { if (columnBinaryListItem == null) { columnBinaryListOsList.addNull(); @@ -1367,7 +1364,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { - OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBooleanListColKey); for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { if (columnBooleanListItem == null) { columnBooleanListOsList.addNull(); @@ -1379,7 +1376,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { - OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); + OsList columnLongListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnLongListColKey); for (java.lang.Long columnLongListItem : columnLongListList) { if (columnLongListItem == null) { columnLongListOsList.addNull(); @@ -1391,7 +1388,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { - OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnIntegerListColKey); for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { if (columnIntegerListItem == null) { columnIntegerListOsList.addNull(); @@ -1403,7 +1400,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { - OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); + OsList columnShortListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnShortListColKey); for (java.lang.Short columnShortListItem : columnShortListList) { if (columnShortListItem == null) { columnShortListOsList.addNull(); @@ -1415,7 +1412,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { - OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); + OsList columnByteListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnByteListColKey); for (java.lang.Byte columnByteListItem : columnByteListList) { if (columnByteListItem == null) { columnByteListOsList.addNull(); @@ -1427,7 +1424,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { - OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDoubleListColKey); for (java.lang.Double columnDoubleListItem : columnDoubleListList) { if (columnDoubleListItem == null) { columnDoubleListOsList.addNull(); @@ -1439,7 +1436,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { - OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnFloatListColKey); for (java.lang.Float columnFloatListItem : columnFloatListList) { if (columnFloatListItem == null) { columnFloatListOsList.addNull(); @@ -1451,7 +1448,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { - OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); + OsList columnDateListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDateListColKey); for (java.util.Date columnDateListItem : columnDateListList) { if (columnDateListItem == null) { columnDateListOsList.addNull(); @@ -1460,52 +1457,52 @@ public static long insert(Realm realm, some.test.AllTypes object, Map objects, Map cache) { Table table = realm.getTable(some.test.AllTypes.class); long tableNativePtr = table.getNativePtr(); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); - long pkColumnIndex = columnInfo.columnStringIndex; + long pkColumnKey = columnInfo.columnStringColKey; some.test.AllTypes object = null; while (objects.hasNext()) { object = (some.test.AllTypes) objects.next(); if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } String primaryKeyValue = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = Table.NO_MATCH; + long colKey = Table.NO_MATCH; if (primaryKeyValue == null) { - rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); + colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); } else { - rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); + colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); } - if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue); + if (colKey == Table.NO_MATCH) { + colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } - cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + cache.put(object, colKey); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, colKey, realmGet$columnDate.getTime(), false); } byte[] realmGet$columnBinary = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryColKey, colKey, realmGet$columnBinary, false); } Long realmGet$columnMutableRealmInteger = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); if (realmGet$columnMutableRealmInteger != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerIndex, rowIndex, realmGet$columnMutableRealmInteger.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, colKey, realmGet$columnMutableRealmInteger.longValue(), false); } some.test.AllTypes columnObjectObj = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObject(); @@ -1514,12 +1511,12 @@ public static void insert(Realm realm, Iterator objects, M if (cachecolumnObject == null) { cachecolumnObject = some_test_AllTypesRealmProxy.insert(realm, columnObjectObj, cache); } - table.setLink(columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); + table.setLink(columnInfo.columnObjectColKey, colKey, cachecolumnObject, false); } RealmList columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null) { - OsList columnRealmListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnRealmListIndex); + OsList columnRealmListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnRealmListColKey); for (some.test.AllTypes columnRealmListItem : columnRealmListList) { Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { @@ -1531,7 +1528,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { - OsList columnStringListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnStringListIndex); + OsList columnStringListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnStringListColKey); for (java.lang.String columnStringListItem : columnStringListList) { if (columnStringListItem == null) { columnStringListOsList.addNull(); @@ -1543,7 +1540,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { - OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBinaryListColKey); for (byte[] columnBinaryListItem : columnBinaryListList) { if (columnBinaryListItem == null) { columnBinaryListOsList.addNull(); @@ -1555,7 +1552,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { - OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBooleanListColKey); for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { if (columnBooleanListItem == null) { columnBooleanListOsList.addNull(); @@ -1567,7 +1564,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { - OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); + OsList columnLongListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnLongListColKey); for (java.lang.Long columnLongListItem : columnLongListList) { if (columnLongListItem == null) { columnLongListOsList.addNull(); @@ -1579,7 +1576,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { - OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnIntegerListColKey); for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { if (columnIntegerListItem == null) { columnIntegerListOsList.addNull(); @@ -1591,7 +1588,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { - OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); + OsList columnShortListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnShortListColKey); for (java.lang.Short columnShortListItem : columnShortListList) { if (columnShortListItem == null) { columnShortListOsList.addNull(); @@ -1603,7 +1600,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { - OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); + OsList columnByteListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnByteListColKey); for (java.lang.Byte columnByteListItem : columnByteListList) { if (columnByteListItem == null) { columnByteListOsList.addNull(); @@ -1615,7 +1612,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { - OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDoubleListColKey); for (java.lang.Double columnDoubleListItem : columnDoubleListList) { if (columnDoubleListItem == null) { columnDoubleListOsList.addNull(); @@ -1627,7 +1624,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { - OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnFloatListColKey); for (java.lang.Float columnFloatListItem : columnFloatListList) { if (columnFloatListItem == null) { columnFloatListOsList.addNull(); @@ -1639,7 +1636,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { - OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); + OsList columnDateListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDateListColKey); for (java.util.Date columnDateListItem : columnDateListList) { if (columnDateListItem == null) { columnDateListOsList.addNull(); @@ -1652,45 +1649,45 @@ public static void insert(Realm realm, Iterator objects, M } public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.AllTypes.class); long tableNativePtr = table.getNativePtr(); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); - long pkColumnIndex = columnInfo.columnStringIndex; + long pkColumnKey = columnInfo.columnStringColKey; String primaryKeyValue = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = Table.NO_MATCH; + long colKey = Table.NO_MATCH; if (primaryKeyValue == null) { - rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); + colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); } else { - rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); + colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); } - if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue); + if (colKey == Table.NO_MATCH) { + colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); } - cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + cache.put(object, colKey); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, colKey, realmGet$columnDate.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnDateIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.columnDateColKey, colKey, false); } byte[] realmGet$columnBinary = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryColKey, colKey, realmGet$columnBinary, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryColKey, colKey, false); } Long realmGet$columnMutableRealmInteger = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); if (realmGet$columnMutableRealmInteger != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerIndex, rowIndex, realmGet$columnMutableRealmInteger.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, colKey, realmGet$columnMutableRealmInteger.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnMutableRealmIntegerIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, colKey, false); } some.test.AllTypes columnObjectObj = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObject(); @@ -1699,12 +1696,12 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null && columnRealmListList.size() == columnRealmListOsList.size()) { // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. @@ -1731,7 +1728,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { @@ -1745,7 +1742,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { @@ -1759,7 +1756,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { @@ -1773,7 +1770,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { @@ -1787,7 +1784,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { @@ -1801,7 +1798,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { @@ -1815,7 +1812,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { @@ -1829,7 +1826,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { @@ -1843,7 +1840,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { @@ -1857,7 +1854,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { @@ -1870,56 +1867,56 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map objects, Map cache) { Table table = realm.getTable(some.test.AllTypes.class); long tableNativePtr = table.getNativePtr(); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); - long pkColumnIndex = columnInfo.columnStringIndex; + long pkColumnKey = columnInfo.columnStringColKey; some.test.AllTypes object = null; while (objects.hasNext()) { object = (some.test.AllTypes) objects.next(); if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } String primaryKeyValue = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); - long rowIndex = Table.NO_MATCH; + long colKey = Table.NO_MATCH; if (primaryKeyValue == null) { - rowIndex = Table.nativeFindFirstNull(tableNativePtr, pkColumnIndex); + colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); } else { - rowIndex = Table.nativeFindFirstString(tableNativePtr, pkColumnIndex, primaryKeyValue); + colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); } - if (rowIndex == Table.NO_MATCH) { - rowIndex = OsObject.createRowWithPrimaryKey(table, pkColumnIndex, primaryKeyValue); + if (colKey == Table.NO_MATCH) { + colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); } - cache.put(object, rowIndex); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanIndex, rowIndex, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + cache.put(object, colKey); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateIndex, rowIndex, realmGet$columnDate.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, colKey, realmGet$columnDate.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnDateIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.columnDateColKey, colKey, false); } byte[] realmGet$columnBinary = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, realmGet$columnBinary, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryColKey, colKey, realmGet$columnBinary, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryColKey, colKey, false); } Long realmGet$columnMutableRealmInteger = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); if (realmGet$columnMutableRealmInteger != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerIndex, rowIndex, realmGet$columnMutableRealmInteger.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, colKey, realmGet$columnMutableRealmInteger.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnMutableRealmIntegerIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, colKey, false); } some.test.AllTypes columnObjectObj = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObject(); @@ -1928,12 +1925,12 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cachecolumnObject == null) { cachecolumnObject = some_test_AllTypesRealmProxy.insertOrUpdate(realm, columnObjectObj, cache); } - Table.nativeSetLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex, cachecolumnObject, false); + Table.nativeSetLink(tableNativePtr, columnInfo.columnObjectColKey, colKey, cachecolumnObject, false); } else { - Table.nativeNullifyLink(tableNativePtr, columnInfo.columnObjectIndex, rowIndex); + Table.nativeNullifyLink(tableNativePtr, columnInfo.columnObjectColKey, colKey); } - OsList columnRealmListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnRealmListIndex); + OsList columnRealmListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnRealmListColKey); RealmList columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null && columnRealmListList.size() == columnRealmListOsList.size()) { // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. @@ -1960,7 +1957,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnStringListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnStringListIndex); + OsList columnStringListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnStringListColKey); columnStringListOsList.removeAll(); RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { @@ -1974,7 +1971,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBinaryListIndex); + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBinaryListColKey); columnBinaryListOsList.removeAll(); RealmList columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { @@ -1988,7 +1985,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnBooleanListIndex); + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBooleanListColKey); columnBooleanListOsList.removeAll(); RealmList columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { @@ -2002,7 +1999,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnLongListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnLongListIndex); + OsList columnLongListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnLongListColKey); columnLongListOsList.removeAll(); RealmList columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { @@ -2016,7 +2013,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnIntegerListIndex); + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnIntegerListColKey); columnIntegerListOsList.removeAll(); RealmList columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { @@ -2030,7 +2027,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnShortListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnShortListIndex); + OsList columnShortListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnShortListColKey); columnShortListOsList.removeAll(); RealmList columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { @@ -2044,7 +2041,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnByteListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnByteListIndex); + OsList columnByteListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnByteListColKey); columnByteListOsList.removeAll(); RealmList columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { @@ -2058,7 +2055,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDoubleListIndex); + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDoubleListColKey); columnDoubleListOsList.removeAll(); RealmList columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { @@ -2072,7 +2069,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnFloatListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnFloatListIndex); + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnFloatListColKey); columnFloatListOsList.removeAll(); RealmList columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { @@ -2086,7 +2083,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnDateListOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.columnDateListIndex); + OsList columnDateListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDateListColKey); columnDateListOsList.removeAll(); RealmList columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { @@ -2185,25 +2182,25 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som some_test_AllTypesRealmProxyInterface realmObjectTarget = (some_test_AllTypesRealmProxyInterface) realmObject; some_test_AllTypesRealmProxyInterface realmObjectSource = (some_test_AllTypesRealmProxyInterface) newObject; Table table = realm.getTable(some.test.AllTypes.class); - OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); - builder.addString(columnInfo.columnStringIndex, realmObjectSource.realmGet$columnString()); - builder.addInteger(columnInfo.columnLongIndex, realmObjectSource.realmGet$columnLong()); - builder.addFloat(columnInfo.columnFloatIndex, realmObjectSource.realmGet$columnFloat()); - builder.addDouble(columnInfo.columnDoubleIndex, realmObjectSource.realmGet$columnDouble()); - builder.addBoolean(columnInfo.columnBooleanIndex, realmObjectSource.realmGet$columnBoolean()); - builder.addDate(columnInfo.columnDateIndex, realmObjectSource.realmGet$columnDate()); - builder.addByteArray(columnInfo.columnBinaryIndex, realmObjectSource.realmGet$columnBinary()); - builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerIndex, realmObjectSource.realmGet$columnMutableRealmInteger()); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); + builder.addString(columnInfo.columnStringColKey, realmObjectSource.realmGet$columnString()); + builder.addInteger(columnInfo.columnLongColKey, realmObjectSource.realmGet$columnLong()); + builder.addFloat(columnInfo.columnFloatColKey, realmObjectSource.realmGet$columnFloat()); + builder.addDouble(columnInfo.columnDoubleColKey, realmObjectSource.realmGet$columnDouble()); + builder.addBoolean(columnInfo.columnBooleanColKey, realmObjectSource.realmGet$columnBoolean()); + builder.addDate(columnInfo.columnDateColKey, realmObjectSource.realmGet$columnDate()); + builder.addByteArray(columnInfo.columnBinaryColKey, realmObjectSource.realmGet$columnBinary()); + builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, realmObjectSource.realmGet$columnMutableRealmInteger()); some.test.AllTypes columnObjectObj = realmObjectSource.realmGet$columnObject(); if (columnObjectObj == null) { - builder.addNull(columnInfo.columnObjectIndex); + builder.addNull(columnInfo.columnObjectColKey); } else { some.test.AllTypes cachecolumnObject = (some.test.AllTypes) cache.get(columnObjectObj); if (cachecolumnObject != null) { - builder.addObject(columnInfo.columnObjectIndex, cachecolumnObject); + builder.addObject(columnInfo.columnObjectColKey, cachecolumnObject); } else { - builder.addObject(columnInfo.columnObjectIndex, some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnObjectObj, true, cache, flags)); + builder.addObject(columnInfo.columnObjectColKey, some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnObjectObj, true, cache, flags)); } } @@ -2219,20 +2216,20 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som columnRealmListManagedCopy.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnRealmListItem, true, cache, flags)); } } - builder.addObjectList(columnInfo.columnRealmListIndex, columnRealmListManagedCopy); + builder.addObjectList(columnInfo.columnRealmListColKey, columnRealmListManagedCopy); } else { - builder.addObjectList(columnInfo.columnRealmListIndex, new RealmList()); - } - builder.addStringList(columnInfo.columnStringListIndex, realmObjectSource.realmGet$columnStringList()); - builder.addByteArrayList(columnInfo.columnBinaryListIndex, realmObjectSource.realmGet$columnBinaryList()); - builder.addBooleanList(columnInfo.columnBooleanListIndex, realmObjectSource.realmGet$columnBooleanList()); - builder.addLongList(columnInfo.columnLongListIndex, realmObjectSource.realmGet$columnLongList()); - builder.addIntegerList(columnInfo.columnIntegerListIndex, realmObjectSource.realmGet$columnIntegerList()); - builder.addShortList(columnInfo.columnShortListIndex, realmObjectSource.realmGet$columnShortList()); - builder.addByteList(columnInfo.columnByteListIndex, realmObjectSource.realmGet$columnByteList()); - builder.addDoubleList(columnInfo.columnDoubleListIndex, realmObjectSource.realmGet$columnDoubleList()); - builder.addFloatList(columnInfo.columnFloatListIndex, realmObjectSource.realmGet$columnFloatList()); - builder.addDateList(columnInfo.columnDateListIndex, realmObjectSource.realmGet$columnDateList()); + builder.addObjectList(columnInfo.columnRealmListColKey, new RealmList()); + } + builder.addStringList(columnInfo.columnStringListColKey, realmObjectSource.realmGet$columnStringList()); + builder.addByteArrayList(columnInfo.columnBinaryListColKey, realmObjectSource.realmGet$columnBinaryList()); + builder.addBooleanList(columnInfo.columnBooleanListColKey, realmObjectSource.realmGet$columnBooleanList()); + builder.addLongList(columnInfo.columnLongListColKey, realmObjectSource.realmGet$columnLongList()); + builder.addIntegerList(columnInfo.columnIntegerListColKey, realmObjectSource.realmGet$columnIntegerList()); + builder.addShortList(columnInfo.columnShortListColKey, realmObjectSource.realmGet$columnShortList()); + builder.addByteList(columnInfo.columnByteListColKey, realmObjectSource.realmGet$columnByteList()); + builder.addDoubleList(columnInfo.columnDoubleListColKey, realmObjectSource.realmGet$columnDoubleList()); + builder.addFloatList(columnInfo.columnFloatListColKey, realmObjectSource.realmGet$columnFloatList()); + builder.addDateList(columnInfo.columnDateListColKey, realmObjectSource.realmGet$columnDateList()); builder.updateExistingObject(); return realmObject; @@ -2337,12 +2334,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long rowIndex = proxyState.getRow$realm().getIndex(); + long colKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32)); + result = 31 * result + (int) (colKey ^ (colKey >>> 32)); return result; } @@ -2352,15 +2349,21 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; some_test_AllTypesRealmProxy aAllTypes = (some_test_AllTypesRealmProxy)o; - String path = proxyState.getRealm$realm().getPath(); - String otherPath = aAllTypes.proxyState.getRealm$realm().getPath(); + BaseRealm realm = proxyState.getRealm$realm(); + BaseRealm otherRealm = aAllTypes.proxyState.getRealm$realm(); + String path = realm.getPath(); + String otherPath = otherRealm.getPath(); if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + if (realm.isFrozen() != otherRealm.isFrozen()) return false; + if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID())) { + return false; + } String tableName = proxyState.getRow$realm().getTable().getName(); String otherTableName = aAllTypes.proxyState.getRow$realm().getTable().getName(); if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; - if (proxyState.getRow$realm().getIndex() != aAllTypes.proxyState.getRow$realm().getIndex()) return false; + if (proxyState.getRow$realm().getObjectKey() != aAllTypes.proxyState.getRow$realm().getObjectKey()) return false; return true; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java index 770ec514e8..96d244c47e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java @@ -38,20 +38,18 @@ public class some_test_BooleansRealmProxy extends some.test.Booleans implements RealmObjectProxy, some_test_BooleansRealmProxyInterface { static final class BooleansColumnInfo extends ColumnInfo { - long maxColumnIndexValue; - long doneIndex; - long isReadyIndex; - long mCompletedIndex; - long anotherBooleanIndex; + long doneColKey; + long isReadyColKey; + long mCompletedColKey; + long anotherBooleanColKey; BooleansColumnInfo(OsSchemaInfo schemaInfo) { super(4); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("Booleans"); - this.doneIndex = addColumnDetails("done", "done", objectSchemaInfo); - this.isReadyIndex = addColumnDetails("isReady", "isReady", objectSchemaInfo); - this.mCompletedIndex = addColumnDetails("mCompleted", "mCompleted", objectSchemaInfo); - this.anotherBooleanIndex = addColumnDetails("anotherBoolean", "anotherBoolean", objectSchemaInfo); - this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); + this.doneColKey = addColumnDetails("done", "done", objectSchemaInfo); + this.isReadyColKey = addColumnDetails("isReady", "isReady", objectSchemaInfo); + this.mCompletedColKey = addColumnDetails("mCompleted", "mCompleted", objectSchemaInfo); + this.anotherBooleanColKey = addColumnDetails("anotherBoolean", "anotherBoolean", objectSchemaInfo); } BooleansColumnInfo(ColumnInfo src, boolean mutable) { @@ -68,11 +66,10 @@ protected final ColumnInfo copy(boolean mutable) { protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final BooleansColumnInfo src = (BooleansColumnInfo) rawSrc; final BooleansColumnInfo dst = (BooleansColumnInfo) rawDst; - dst.doneIndex = src.doneIndex; - dst.isReadyIndex = src.isReadyIndex; - dst.mCompletedIndex = src.mCompletedIndex; - dst.anotherBooleanIndex = src.anotherBooleanIndex; - dst.maxColumnIndexValue = src.maxColumnIndexValue; + dst.doneColKey = src.doneColKey; + dst.isReadyColKey = src.isReadyColKey; + dst.mCompletedColKey = src.mCompletedColKey; + dst.anotherBooleanColKey = src.anotherBooleanColKey; } } @@ -103,7 +100,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @SuppressWarnings("cast") public boolean realmGet$done() { proxyState.getRealm$realm().checkIfValid(); - return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.doneIndex); + return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.doneColKey); } @Override @@ -113,19 +110,19 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } final Row row = proxyState.getRow$realm(); - row.getTable().setBoolean(columnInfo.doneIndex, row.getIndex(), value, true); + row.getTable().setBoolean(columnInfo.doneColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); - proxyState.getRow$realm().setBoolean(columnInfo.doneIndex, value); + proxyState.getRow$realm().setBoolean(columnInfo.doneColKey, value); } @Override @SuppressWarnings("cast") public boolean realmGet$isReady() { proxyState.getRealm$realm().checkIfValid(); - return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.isReadyIndex); + return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.isReadyColKey); } @Override @@ -135,19 +132,19 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } final Row row = proxyState.getRow$realm(); - row.getTable().setBoolean(columnInfo.isReadyIndex, row.getIndex(), value, true); + row.getTable().setBoolean(columnInfo.isReadyColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); - proxyState.getRow$realm().setBoolean(columnInfo.isReadyIndex, value); + proxyState.getRow$realm().setBoolean(columnInfo.isReadyColKey, value); } @Override @SuppressWarnings("cast") public boolean realmGet$mCompleted() { proxyState.getRealm$realm().checkIfValid(); - return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.mCompletedIndex); + return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.mCompletedColKey); } @Override @@ -157,19 +154,19 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } final Row row = proxyState.getRow$realm(); - row.getTable().setBoolean(columnInfo.mCompletedIndex, row.getIndex(), value, true); + row.getTable().setBoolean(columnInfo.mCompletedColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); - proxyState.getRow$realm().setBoolean(columnInfo.mCompletedIndex, value); + proxyState.getRow$realm().setBoolean(columnInfo.mCompletedColKey, value); } @Override @SuppressWarnings("cast") public boolean realmGet$anotherBoolean() { proxyState.getRealm$realm().checkIfValid(); - return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.anotherBooleanIndex); + return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.anotherBooleanColKey); } @Override @@ -179,12 +176,12 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } final Row row = proxyState.getRow$realm(); - row.getTable().setBoolean(columnInfo.anotherBooleanIndex, row.getIndex(), value, true); + row.getTable().setBoolean(columnInfo.anotherBooleanColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); - proxyState.getRow$realm().setBoolean(columnInfo.anotherBooleanIndex, value); + proxyState.getRow$realm().setBoolean(columnInfo.anotherBooleanColKey, value); } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { @@ -297,7 +294,7 @@ public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader r } private static some_test_BooleansRealmProxy newProxyInstance(BaseRealm realm, Row row) { - // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.Booleans.class), false, Collections.emptyList()); io.realm.some_test_BooleansRealmProxy obj = new io.realm.some_test_BooleansRealmProxy(); @@ -306,7 +303,7 @@ private static some_test_BooleansRealmProxy newProxyInstance(BaseRealm realm, Ro } public static some.test.Booleans copyOrUpdate(Realm realm, BooleansColumnInfo columnInfo, some.test.Booleans object, boolean update, Map cache, Set flags) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); @@ -333,13 +330,13 @@ public static some.test.Booleans copy(Realm realm, BooleansColumnInfo columnInfo some_test_BooleansRealmProxyInterface realmObjectSource = (some_test_BooleansRealmProxyInterface) newObject; Table table = realm.getTable(some.test.Booleans.class); - OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addBoolean(columnInfo.doneIndex, realmObjectSource.realmGet$done()); - builder.addBoolean(columnInfo.isReadyIndex, realmObjectSource.realmGet$isReady()); - builder.addBoolean(columnInfo.mCompletedIndex, realmObjectSource.realmGet$mCompleted()); - builder.addBoolean(columnInfo.anotherBooleanIndex, realmObjectSource.realmGet$anotherBoolean()); + builder.addBoolean(columnInfo.doneColKey, realmObjectSource.realmGet$done()); + builder.addBoolean(columnInfo.isReadyColKey, realmObjectSource.realmGet$isReady()); + builder.addBoolean(columnInfo.mCompletedColKey, realmObjectSource.realmGet$mCompleted()); + builder.addBoolean(columnInfo.anotherBooleanColKey, realmObjectSource.realmGet$anotherBoolean()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. @@ -351,19 +348,19 @@ public static some.test.Booleans copy(Realm realm, BooleansColumnInfo columnInfo } public static long insert(Realm realm, some.test.Booleans object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.Booleans.class); long tableNativePtr = table.getNativePtr(); BooleansColumnInfo columnInfo = (BooleansColumnInfo) realm.getSchema().getColumnInfo(some.test.Booleans.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); - return rowIndex; + long colKey = OsObject.createRow(table); + cache.put(object, colKey); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); + return colKey; } public static void insert(Realm realm, Iterator objects, Map cache) { @@ -376,33 +373,33 @@ public static void insert(Realm realm, Iterator objects, M if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); } } public static long insertOrUpdate(Realm realm, some.test.Booleans object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.Booleans.class); long tableNativePtr = table.getNativePtr(); BooleansColumnInfo columnInfo = (BooleansColumnInfo) realm.getSchema().getColumnInfo(some.test.Booleans.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); - return rowIndex; + long colKey = OsObject.createRow(table); + cache.put(object, colKey); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); + return colKey; } public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { @@ -415,16 +412,16 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanIndex, rowIndex, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); } } @@ -490,12 +487,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long rowIndex = proxyState.getRow$realm().getIndex(); + long colKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32)); + result = 31 * result + (int) (colKey ^ (colKey >>> 32)); return result; } @@ -505,15 +502,21 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; some_test_BooleansRealmProxy aBooleans = (some_test_BooleansRealmProxy)o; - String path = proxyState.getRealm$realm().getPath(); - String otherPath = aBooleans.proxyState.getRealm$realm().getPath(); + BaseRealm realm = proxyState.getRealm$realm(); + BaseRealm otherRealm = aBooleans.proxyState.getRealm$realm(); + String path = realm.getPath(); + String otherPath = otherRealm.getPath(); if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + if (realm.isFrozen() != otherRealm.isFrozen()) return false; + if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID())) { + return false; + } String tableName = proxyState.getRow$realm().getTable().getName(); String otherTableName = aBooleans.proxyState.getRow$realm().getTable().getName(); if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; - if (proxyState.getRow$realm().getIndex() != aBooleans.proxyState.getRow$realm().getIndex()) return false; + if (proxyState.getRow$realm().getObjectKey() != aBooleans.proxyState.getRow$realm().getObjectKey()) return false; return true; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java index 31e9a3b3fe..fcab04337d 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java @@ -38,16 +38,14 @@ public class some_test_NamePolicyMixedClassSettingsRealmProxy extends some.test. implements RealmObjectProxy, some_test_NamePolicyMixedClassSettingsRealmProxyInterface { static final class NamePolicyMixedClassSettingsColumnInfo extends ColumnInfo { - long maxColumnIndexValue; - long firstNameIndex; - long lastNameIndex; + long firstNameColKey; + long lastNameColKey; NamePolicyMixedClassSettingsColumnInfo(OsSchemaInfo schemaInfo) { super(2); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("customName"); - this.firstNameIndex = addColumnDetails("firstName", "first_name", objectSchemaInfo); - this.lastNameIndex = addColumnDetails("lastName", "LastName", objectSchemaInfo); - this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); + this.firstNameColKey = addColumnDetails("firstName", "first_name", objectSchemaInfo); + this.lastNameColKey = addColumnDetails("lastName", "LastName", objectSchemaInfo); } NamePolicyMixedClassSettingsColumnInfo(ColumnInfo src, boolean mutable) { @@ -64,9 +62,8 @@ protected final ColumnInfo copy(boolean mutable) { protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final NamePolicyMixedClassSettingsColumnInfo src = (NamePolicyMixedClassSettingsColumnInfo) rawSrc; final NamePolicyMixedClassSettingsColumnInfo dst = (NamePolicyMixedClassSettingsColumnInfo) rawDst; - dst.firstNameIndex = src.firstNameIndex; - dst.lastNameIndex = src.lastNameIndex; - dst.maxColumnIndexValue = src.maxColumnIndexValue; + dst.firstNameColKey = src.firstNameColKey; + dst.lastNameColKey = src.lastNameColKey; } } @@ -97,7 +94,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @SuppressWarnings("cast") public String realmGet$firstName() { proxyState.getRealm$realm().checkIfValid(); - return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.firstNameIndex); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.firstNameColKey); } @Override @@ -108,26 +105,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.firstNameIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.firstNameColKey, row.getObjectKey(), true); return; } - row.getTable().setString(columnInfo.firstNameIndex, row.getIndex(), value, true); + row.getTable().setString(columnInfo.firstNameColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.firstNameIndex); + proxyState.getRow$realm().setNull(columnInfo.firstNameColKey); return; } - proxyState.getRow$realm().setString(columnInfo.firstNameIndex, value); + proxyState.getRow$realm().setString(columnInfo.firstNameColKey, value); } @Override @SuppressWarnings("cast") public String realmGet$lastName() { proxyState.getRealm$realm().checkIfValid(); - return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.lastNameIndex); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.lastNameColKey); } @Override @@ -138,19 +135,19 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.lastNameIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.lastNameColKey, row.getObjectKey(), true); return; } - row.getTable().setString(columnInfo.lastNameIndex, row.getIndex(), value, true); + row.getTable().setString(columnInfo.lastNameColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.lastNameIndex); + proxyState.getRow$realm().setNull(columnInfo.lastNameColKey); return; } - proxyState.getRow$realm().setString(columnInfo.lastNameIndex, value); + proxyState.getRow$realm().setString(columnInfo.lastNameColKey, value); } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { @@ -233,7 +230,7 @@ public static some.test.NamePolicyMixedClassSettings createUsingJsonStream(Realm } private static some_test_NamePolicyMixedClassSettingsRealmProxy newProxyInstance(BaseRealm realm, Row row) { - // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class), false, Collections.emptyList()); io.realm.some_test_NamePolicyMixedClassSettingsRealmProxy obj = new io.realm.some_test_NamePolicyMixedClassSettingsRealmProxy(); @@ -242,7 +239,7 @@ private static some_test_NamePolicyMixedClassSettingsRealmProxy newProxyInstance } public static some.test.NamePolicyMixedClassSettings copyOrUpdate(Realm realm, NamePolicyMixedClassSettingsColumnInfo columnInfo, some.test.NamePolicyMixedClassSettings object, boolean update, Map cache, Set flags) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); @@ -269,11 +266,11 @@ public static some.test.NamePolicyMixedClassSettings copy(Realm realm, NamePolic some_test_NamePolicyMixedClassSettingsRealmProxyInterface realmObjectSource = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) newObject; Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); - OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.firstNameIndex, realmObjectSource.realmGet$firstName()); - builder.addString(columnInfo.lastNameIndex, realmObjectSource.realmGet$lastName()); + builder.addString(columnInfo.firstNameColKey, realmObjectSource.realmGet$firstName()); + builder.addString(columnInfo.lastNameColKey, realmObjectSource.realmGet$lastName()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. @@ -285,23 +282,23 @@ public static some.test.NamePolicyMixedClassSettings copy(Realm realm, NamePolic } public static long insert(Realm realm, some.test.NamePolicyMixedClassSettings object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); long tableNativePtr = table.getNativePtr(); NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); } String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); } - return rowIndex; + return colKey; } public static void insert(Realm realm, Iterator objects, Map cache) { @@ -314,45 +311,45 @@ public static void insert(Realm realm, Iterator objects, M if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); } String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); } } } public static long insertOrUpdate(Realm realm, some.test.NamePolicyMixedClassSettings object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); long tableNativePtr = table.getNativePtr(); NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, colKey, false); } String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.lastNameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, colKey, false); } - return rowIndex; + return colKey; } public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { @@ -365,23 +362,23 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, colKey, false); } String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.lastNameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, colKey, false); } } } @@ -438,12 +435,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long rowIndex = proxyState.getRow$realm().getIndex(); + long colKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32)); + result = 31 * result + (int) (colKey ^ (colKey >>> 32)); return result; } @@ -453,15 +450,21 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; some_test_NamePolicyMixedClassSettingsRealmProxy aNamePolicyMixedClassSettings = (some_test_NamePolicyMixedClassSettingsRealmProxy)o; - String path = proxyState.getRealm$realm().getPath(); - String otherPath = aNamePolicyMixedClassSettings.proxyState.getRealm$realm().getPath(); + BaseRealm realm = proxyState.getRealm$realm(); + BaseRealm otherRealm = aNamePolicyMixedClassSettings.proxyState.getRealm$realm(); + String path = realm.getPath(); + String otherPath = otherRealm.getPath(); if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + if (realm.isFrozen() != otherRealm.isFrozen()) return false; + if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID())) { + return false; + } String tableName = proxyState.getRow$realm().getTable().getName(); String otherTableName = aNamePolicyMixedClassSettings.proxyState.getRow$realm().getTable().getName(); if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; - if (proxyState.getRow$realm().getIndex() != aNamePolicyMixedClassSettings.proxyState.getRow$realm().getIndex()) return false; + if (proxyState.getRow$realm().getObjectKey() != aNamePolicyMixedClassSettings.proxyState.getRow$realm().getObjectKey()) return false; return true; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java index e65a432ff1..c4880c7813 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java @@ -38,16 +38,14 @@ public class some_test_NamePolicyModuleDefaultsRealmProxy extends some.test.Name implements RealmObjectProxy, some_test_NamePolicyModuleDefaultsRealmProxyInterface { static final class NamePolicyModuleDefaultsColumnInfo extends ColumnInfo { - long maxColumnIndexValue; - long firstNameIndex; - long lastNameIndex; + long firstNameColKey; + long lastNameColKey; NamePolicyModuleDefaultsColumnInfo(OsSchemaInfo schemaInfo) { super(2); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("NamePolicyModuleDefaults"); - this.firstNameIndex = addColumnDetails("firstName", "FirstName", objectSchemaInfo); - this.lastNameIndex = addColumnDetails("lastName", "LastName", objectSchemaInfo); - this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); + this.firstNameColKey = addColumnDetails("firstName", "FirstName", objectSchemaInfo); + this.lastNameColKey = addColumnDetails("lastName", "LastName", objectSchemaInfo); } NamePolicyModuleDefaultsColumnInfo(ColumnInfo src, boolean mutable) { @@ -64,9 +62,8 @@ protected final ColumnInfo copy(boolean mutable) { protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final NamePolicyModuleDefaultsColumnInfo src = (NamePolicyModuleDefaultsColumnInfo) rawSrc; final NamePolicyModuleDefaultsColumnInfo dst = (NamePolicyModuleDefaultsColumnInfo) rawDst; - dst.firstNameIndex = src.firstNameIndex; - dst.lastNameIndex = src.lastNameIndex; - dst.maxColumnIndexValue = src.maxColumnIndexValue; + dst.firstNameColKey = src.firstNameColKey; + dst.lastNameColKey = src.lastNameColKey; } } @@ -97,7 +94,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @SuppressWarnings("cast") public String realmGet$firstName() { proxyState.getRealm$realm().checkIfValid(); - return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.firstNameIndex); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.firstNameColKey); } @Override @@ -108,26 +105,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.firstNameIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.firstNameColKey, row.getObjectKey(), true); return; } - row.getTable().setString(columnInfo.firstNameIndex, row.getIndex(), value, true); + row.getTable().setString(columnInfo.firstNameColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.firstNameIndex); + proxyState.getRow$realm().setNull(columnInfo.firstNameColKey); return; } - proxyState.getRow$realm().setString(columnInfo.firstNameIndex, value); + proxyState.getRow$realm().setString(columnInfo.firstNameColKey, value); } @Override @SuppressWarnings("cast") public String realmGet$lastName() { proxyState.getRealm$realm().checkIfValid(); - return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.lastNameIndex); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.lastNameColKey); } @Override @@ -138,19 +135,19 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.lastNameIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.lastNameColKey, row.getObjectKey(), true); return; } - row.getTable().setString(columnInfo.lastNameIndex, row.getIndex(), value, true); + row.getTable().setString(columnInfo.lastNameColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.lastNameIndex); + proxyState.getRow$realm().setNull(columnInfo.lastNameColKey); return; } - proxyState.getRow$realm().setString(columnInfo.lastNameIndex, value); + proxyState.getRow$realm().setString(columnInfo.lastNameColKey, value); } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { @@ -233,7 +230,7 @@ public static some.test.NamePolicyModuleDefaults createUsingJsonStream(Realm rea } private static some_test_NamePolicyModuleDefaultsRealmProxy newProxyInstance(BaseRealm realm, Row row) { - // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class), false, Collections.emptyList()); io.realm.some_test_NamePolicyModuleDefaultsRealmProxy obj = new io.realm.some_test_NamePolicyModuleDefaultsRealmProxy(); @@ -242,7 +239,7 @@ private static some_test_NamePolicyModuleDefaultsRealmProxy newProxyInstance(Bas } public static some.test.NamePolicyModuleDefaults copyOrUpdate(Realm realm, NamePolicyModuleDefaultsColumnInfo columnInfo, some.test.NamePolicyModuleDefaults object, boolean update, Map cache, Set flags) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); @@ -269,11 +266,11 @@ public static some.test.NamePolicyModuleDefaults copy(Realm realm, NamePolicyMod some_test_NamePolicyModuleDefaultsRealmProxyInterface realmObjectSource = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) newObject; Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); - OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.firstNameIndex, realmObjectSource.realmGet$firstName()); - builder.addString(columnInfo.lastNameIndex, realmObjectSource.realmGet$lastName()); + builder.addString(columnInfo.firstNameColKey, realmObjectSource.realmGet$firstName()); + builder.addString(columnInfo.lastNameColKey, realmObjectSource.realmGet$lastName()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. @@ -285,23 +282,23 @@ public static some.test.NamePolicyModuleDefaults copy(Realm realm, NamePolicyMod } public static long insert(Realm realm, some.test.NamePolicyModuleDefaults object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); long tableNativePtr = table.getNativePtr(); NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); } String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); } - return rowIndex; + return colKey; } public static void insert(Realm realm, Iterator objects, Map cache) { @@ -314,45 +311,45 @@ public static void insert(Realm realm, Iterator objects, M if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); } String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); } } } public static long insertOrUpdate(Realm realm, some.test.NamePolicyModuleDefaults object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); long tableNativePtr = table.getNativePtr(); NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, colKey, false); } String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.lastNameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, colKey, false); } - return rowIndex; + return colKey; } public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { @@ -365,23 +362,23 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameIndex, rowIndex, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.firstNameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, colKey, false); } String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameIndex, rowIndex, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.lastNameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, colKey, false); } } } @@ -438,12 +435,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long rowIndex = proxyState.getRow$realm().getIndex(); + long colKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32)); + result = 31 * result + (int) (colKey ^ (colKey >>> 32)); return result; } @@ -453,15 +450,21 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; some_test_NamePolicyModuleDefaultsRealmProxy aNamePolicyModuleDefaults = (some_test_NamePolicyModuleDefaultsRealmProxy)o; - String path = proxyState.getRealm$realm().getPath(); - String otherPath = aNamePolicyModuleDefaults.proxyState.getRealm$realm().getPath(); + BaseRealm realm = proxyState.getRealm$realm(); + BaseRealm otherRealm = aNamePolicyModuleDefaults.proxyState.getRealm$realm(); + String path = realm.getPath(); + String otherPath = otherRealm.getPath(); if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + if (realm.isFrozen() != otherRealm.isFrozen()) return false; + if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID())) { + return false; + } String tableName = proxyState.getRow$realm().getTable().getName(); String otherTableName = aNamePolicyModuleDefaults.proxyState.getRow$realm().getTable().getName(); if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; - if (proxyState.getRow$realm().getIndex() != aNamePolicyModuleDefaults.proxyState.getRow$realm().getIndex()) return false; + if (proxyState.getRow$realm().getObjectKey() != aNamePolicyModuleDefaults.proxyState.getRow$realm().getObjectKey()) return false; return true; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index 5d37725d00..86080c0cef 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -38,94 +38,92 @@ public class some_test_NullTypesRealmProxy extends some.test.NullTypes implements RealmObjectProxy, some_test_NullTypesRealmProxyInterface { static final class NullTypesColumnInfo extends ColumnInfo { - long maxColumnIndexValue; - long fieldStringNotNullIndex; - long fieldStringNullIndex; - long fieldBooleanNotNullIndex; - long fieldBooleanNullIndex; - long fieldBytesNotNullIndex; - long fieldBytesNullIndex; - long fieldByteNotNullIndex; - long fieldByteNullIndex; - long fieldShortNotNullIndex; - long fieldShortNullIndex; - long fieldIntegerNotNullIndex; - long fieldIntegerNullIndex; - long fieldLongNotNullIndex; - long fieldLongNullIndex; - long fieldFloatNotNullIndex; - long fieldFloatNullIndex; - long fieldDoubleNotNullIndex; - long fieldDoubleNullIndex; - long fieldDateNotNullIndex; - long fieldDateNullIndex; - long fieldObjectNullIndex; - long fieldStringListNotNullIndex; - long fieldStringListNullIndex; - long fieldBinaryListNotNullIndex; - long fieldBinaryListNullIndex; - long fieldBooleanListNotNullIndex; - long fieldBooleanListNullIndex; - long fieldLongListNotNullIndex; - long fieldLongListNullIndex; - long fieldIntegerListNotNullIndex; - long fieldIntegerListNullIndex; - long fieldShortListNotNullIndex; - long fieldShortListNullIndex; - long fieldByteListNotNullIndex; - long fieldByteListNullIndex; - long fieldDoubleListNotNullIndex; - long fieldDoubleListNullIndex; - long fieldFloatListNotNullIndex; - long fieldFloatListNullIndex; - long fieldDateListNotNullIndex; - long fieldDateListNullIndex; + long fieldStringNotNullColKey; + long fieldStringNullColKey; + long fieldBooleanNotNullColKey; + long fieldBooleanNullColKey; + long fieldBytesNotNullColKey; + long fieldBytesNullColKey; + long fieldByteNotNullColKey; + long fieldByteNullColKey; + long fieldShortNotNullColKey; + long fieldShortNullColKey; + long fieldIntegerNotNullColKey; + long fieldIntegerNullColKey; + long fieldLongNotNullColKey; + long fieldLongNullColKey; + long fieldFloatNotNullColKey; + long fieldFloatNullColKey; + long fieldDoubleNotNullColKey; + long fieldDoubleNullColKey; + long fieldDateNotNullColKey; + long fieldDateNullColKey; + long fieldObjectNullColKey; + long fieldStringListNotNullColKey; + long fieldStringListNullColKey; + long fieldBinaryListNotNullColKey; + long fieldBinaryListNullColKey; + long fieldBooleanListNotNullColKey; + long fieldBooleanListNullColKey; + long fieldLongListNotNullColKey; + long fieldLongListNullColKey; + long fieldIntegerListNotNullColKey; + long fieldIntegerListNullColKey; + long fieldShortListNotNullColKey; + long fieldShortListNullColKey; + long fieldByteListNotNullColKey; + long fieldByteListNullColKey; + long fieldDoubleListNotNullColKey; + long fieldDoubleListNullColKey; + long fieldFloatListNotNullColKey; + long fieldFloatListNullColKey; + long fieldDateListNotNullColKey; + long fieldDateListNullColKey; NullTypesColumnInfo(OsSchemaInfo schemaInfo) { super(41); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("NullTypes"); - this.fieldStringNotNullIndex = addColumnDetails("fieldStringNotNull", "fieldStringNotNull", objectSchemaInfo); - this.fieldStringNullIndex = addColumnDetails("fieldStringNull", "fieldStringNull", objectSchemaInfo); - this.fieldBooleanNotNullIndex = addColumnDetails("fieldBooleanNotNull", "fieldBooleanNotNull", objectSchemaInfo); - this.fieldBooleanNullIndex = addColumnDetails("fieldBooleanNull", "fieldBooleanNull", objectSchemaInfo); - this.fieldBytesNotNullIndex = addColumnDetails("fieldBytesNotNull", "fieldBytesNotNull", objectSchemaInfo); - this.fieldBytesNullIndex = addColumnDetails("fieldBytesNull", "fieldBytesNull", objectSchemaInfo); - this.fieldByteNotNullIndex = addColumnDetails("fieldByteNotNull", "fieldByteNotNull", objectSchemaInfo); - this.fieldByteNullIndex = addColumnDetails("fieldByteNull", "fieldByteNull", objectSchemaInfo); - this.fieldShortNotNullIndex = addColumnDetails("fieldShortNotNull", "fieldShortNotNull", objectSchemaInfo); - this.fieldShortNullIndex = addColumnDetails("fieldShortNull", "fieldShortNull", objectSchemaInfo); - this.fieldIntegerNotNullIndex = addColumnDetails("fieldIntegerNotNull", "fieldIntegerNotNull", objectSchemaInfo); - this.fieldIntegerNullIndex = addColumnDetails("fieldIntegerNull", "fieldIntegerNull", objectSchemaInfo); - this.fieldLongNotNullIndex = addColumnDetails("fieldLongNotNull", "fieldLongNotNull", objectSchemaInfo); - this.fieldLongNullIndex = addColumnDetails("fieldLongNull", "fieldLongNull", objectSchemaInfo); - this.fieldFloatNotNullIndex = addColumnDetails("fieldFloatNotNull", "fieldFloatNotNull", objectSchemaInfo); - this.fieldFloatNullIndex = addColumnDetails("fieldFloatNull", "fieldFloatNull", objectSchemaInfo); - this.fieldDoubleNotNullIndex = addColumnDetails("fieldDoubleNotNull", "fieldDoubleNotNull", objectSchemaInfo); - this.fieldDoubleNullIndex = addColumnDetails("fieldDoubleNull", "fieldDoubleNull", objectSchemaInfo); - this.fieldDateNotNullIndex = addColumnDetails("fieldDateNotNull", "fieldDateNotNull", objectSchemaInfo); - this.fieldDateNullIndex = addColumnDetails("fieldDateNull", "fieldDateNull", objectSchemaInfo); - this.fieldObjectNullIndex = addColumnDetails("fieldObjectNull", "fieldObjectNull", objectSchemaInfo); - this.fieldStringListNotNullIndex = addColumnDetails("fieldStringListNotNull", "fieldStringListNotNull", objectSchemaInfo); - this.fieldStringListNullIndex = addColumnDetails("fieldStringListNull", "fieldStringListNull", objectSchemaInfo); - this.fieldBinaryListNotNullIndex = addColumnDetails("fieldBinaryListNotNull", "fieldBinaryListNotNull", objectSchemaInfo); - this.fieldBinaryListNullIndex = addColumnDetails("fieldBinaryListNull", "fieldBinaryListNull", objectSchemaInfo); - this.fieldBooleanListNotNullIndex = addColumnDetails("fieldBooleanListNotNull", "fieldBooleanListNotNull", objectSchemaInfo); - this.fieldBooleanListNullIndex = addColumnDetails("fieldBooleanListNull", "fieldBooleanListNull", objectSchemaInfo); - this.fieldLongListNotNullIndex = addColumnDetails("fieldLongListNotNull", "fieldLongListNotNull", objectSchemaInfo); - this.fieldLongListNullIndex = addColumnDetails("fieldLongListNull", "fieldLongListNull", objectSchemaInfo); - this.fieldIntegerListNotNullIndex = addColumnDetails("fieldIntegerListNotNull", "fieldIntegerListNotNull", objectSchemaInfo); - this.fieldIntegerListNullIndex = addColumnDetails("fieldIntegerListNull", "fieldIntegerListNull", objectSchemaInfo); - this.fieldShortListNotNullIndex = addColumnDetails("fieldShortListNotNull", "fieldShortListNotNull", objectSchemaInfo); - this.fieldShortListNullIndex = addColumnDetails("fieldShortListNull", "fieldShortListNull", objectSchemaInfo); - this.fieldByteListNotNullIndex = addColumnDetails("fieldByteListNotNull", "fieldByteListNotNull", objectSchemaInfo); - this.fieldByteListNullIndex = addColumnDetails("fieldByteListNull", "fieldByteListNull", objectSchemaInfo); - this.fieldDoubleListNotNullIndex = addColumnDetails("fieldDoubleListNotNull", "fieldDoubleListNotNull", objectSchemaInfo); - this.fieldDoubleListNullIndex = addColumnDetails("fieldDoubleListNull", "fieldDoubleListNull", objectSchemaInfo); - this.fieldFloatListNotNullIndex = addColumnDetails("fieldFloatListNotNull", "fieldFloatListNotNull", objectSchemaInfo); - this.fieldFloatListNullIndex = addColumnDetails("fieldFloatListNull", "fieldFloatListNull", objectSchemaInfo); - this.fieldDateListNotNullIndex = addColumnDetails("fieldDateListNotNull", "fieldDateListNotNull", objectSchemaInfo); - this.fieldDateListNullIndex = addColumnDetails("fieldDateListNull", "fieldDateListNull", objectSchemaInfo); - this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); + this.fieldStringNotNullColKey = addColumnDetails("fieldStringNotNull", "fieldStringNotNull", objectSchemaInfo); + this.fieldStringNullColKey = addColumnDetails("fieldStringNull", "fieldStringNull", objectSchemaInfo); + this.fieldBooleanNotNullColKey = addColumnDetails("fieldBooleanNotNull", "fieldBooleanNotNull", objectSchemaInfo); + this.fieldBooleanNullColKey = addColumnDetails("fieldBooleanNull", "fieldBooleanNull", objectSchemaInfo); + this.fieldBytesNotNullColKey = addColumnDetails("fieldBytesNotNull", "fieldBytesNotNull", objectSchemaInfo); + this.fieldBytesNullColKey = addColumnDetails("fieldBytesNull", "fieldBytesNull", objectSchemaInfo); + this.fieldByteNotNullColKey = addColumnDetails("fieldByteNotNull", "fieldByteNotNull", objectSchemaInfo); + this.fieldByteNullColKey = addColumnDetails("fieldByteNull", "fieldByteNull", objectSchemaInfo); + this.fieldShortNotNullColKey = addColumnDetails("fieldShortNotNull", "fieldShortNotNull", objectSchemaInfo); + this.fieldShortNullColKey = addColumnDetails("fieldShortNull", "fieldShortNull", objectSchemaInfo); + this.fieldIntegerNotNullColKey = addColumnDetails("fieldIntegerNotNull", "fieldIntegerNotNull", objectSchemaInfo); + this.fieldIntegerNullColKey = addColumnDetails("fieldIntegerNull", "fieldIntegerNull", objectSchemaInfo); + this.fieldLongNotNullColKey = addColumnDetails("fieldLongNotNull", "fieldLongNotNull", objectSchemaInfo); + this.fieldLongNullColKey = addColumnDetails("fieldLongNull", "fieldLongNull", objectSchemaInfo); + this.fieldFloatNotNullColKey = addColumnDetails("fieldFloatNotNull", "fieldFloatNotNull", objectSchemaInfo); + this.fieldFloatNullColKey = addColumnDetails("fieldFloatNull", "fieldFloatNull", objectSchemaInfo); + this.fieldDoubleNotNullColKey = addColumnDetails("fieldDoubleNotNull", "fieldDoubleNotNull", objectSchemaInfo); + this.fieldDoubleNullColKey = addColumnDetails("fieldDoubleNull", "fieldDoubleNull", objectSchemaInfo); + this.fieldDateNotNullColKey = addColumnDetails("fieldDateNotNull", "fieldDateNotNull", objectSchemaInfo); + this.fieldDateNullColKey = addColumnDetails("fieldDateNull", "fieldDateNull", objectSchemaInfo); + this.fieldObjectNullColKey = addColumnDetails("fieldObjectNull", "fieldObjectNull", objectSchemaInfo); + this.fieldStringListNotNullColKey = addColumnDetails("fieldStringListNotNull", "fieldStringListNotNull", objectSchemaInfo); + this.fieldStringListNullColKey = addColumnDetails("fieldStringListNull", "fieldStringListNull", objectSchemaInfo); + this.fieldBinaryListNotNullColKey = addColumnDetails("fieldBinaryListNotNull", "fieldBinaryListNotNull", objectSchemaInfo); + this.fieldBinaryListNullColKey = addColumnDetails("fieldBinaryListNull", "fieldBinaryListNull", objectSchemaInfo); + this.fieldBooleanListNotNullColKey = addColumnDetails("fieldBooleanListNotNull", "fieldBooleanListNotNull", objectSchemaInfo); + this.fieldBooleanListNullColKey = addColumnDetails("fieldBooleanListNull", "fieldBooleanListNull", objectSchemaInfo); + this.fieldLongListNotNullColKey = addColumnDetails("fieldLongListNotNull", "fieldLongListNotNull", objectSchemaInfo); + this.fieldLongListNullColKey = addColumnDetails("fieldLongListNull", "fieldLongListNull", objectSchemaInfo); + this.fieldIntegerListNotNullColKey = addColumnDetails("fieldIntegerListNotNull", "fieldIntegerListNotNull", objectSchemaInfo); + this.fieldIntegerListNullColKey = addColumnDetails("fieldIntegerListNull", "fieldIntegerListNull", objectSchemaInfo); + this.fieldShortListNotNullColKey = addColumnDetails("fieldShortListNotNull", "fieldShortListNotNull", objectSchemaInfo); + this.fieldShortListNullColKey = addColumnDetails("fieldShortListNull", "fieldShortListNull", objectSchemaInfo); + this.fieldByteListNotNullColKey = addColumnDetails("fieldByteListNotNull", "fieldByteListNotNull", objectSchemaInfo); + this.fieldByteListNullColKey = addColumnDetails("fieldByteListNull", "fieldByteListNull", objectSchemaInfo); + this.fieldDoubleListNotNullColKey = addColumnDetails("fieldDoubleListNotNull", "fieldDoubleListNotNull", objectSchemaInfo); + this.fieldDoubleListNullColKey = addColumnDetails("fieldDoubleListNull", "fieldDoubleListNull", objectSchemaInfo); + this.fieldFloatListNotNullColKey = addColumnDetails("fieldFloatListNotNull", "fieldFloatListNotNull", objectSchemaInfo); + this.fieldFloatListNullColKey = addColumnDetails("fieldFloatListNull", "fieldFloatListNull", objectSchemaInfo); + this.fieldDateListNotNullColKey = addColumnDetails("fieldDateListNotNull", "fieldDateListNotNull", objectSchemaInfo); + this.fieldDateListNullColKey = addColumnDetails("fieldDateListNull", "fieldDateListNull", objectSchemaInfo); } NullTypesColumnInfo(ColumnInfo src, boolean mutable) { @@ -142,48 +140,47 @@ protected final ColumnInfo copy(boolean mutable) { protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final NullTypesColumnInfo src = (NullTypesColumnInfo) rawSrc; final NullTypesColumnInfo dst = (NullTypesColumnInfo) rawDst; - dst.fieldStringNotNullIndex = src.fieldStringNotNullIndex; - dst.fieldStringNullIndex = src.fieldStringNullIndex; - dst.fieldBooleanNotNullIndex = src.fieldBooleanNotNullIndex; - dst.fieldBooleanNullIndex = src.fieldBooleanNullIndex; - dst.fieldBytesNotNullIndex = src.fieldBytesNotNullIndex; - dst.fieldBytesNullIndex = src.fieldBytesNullIndex; - dst.fieldByteNotNullIndex = src.fieldByteNotNullIndex; - dst.fieldByteNullIndex = src.fieldByteNullIndex; - dst.fieldShortNotNullIndex = src.fieldShortNotNullIndex; - dst.fieldShortNullIndex = src.fieldShortNullIndex; - dst.fieldIntegerNotNullIndex = src.fieldIntegerNotNullIndex; - dst.fieldIntegerNullIndex = src.fieldIntegerNullIndex; - dst.fieldLongNotNullIndex = src.fieldLongNotNullIndex; - dst.fieldLongNullIndex = src.fieldLongNullIndex; - dst.fieldFloatNotNullIndex = src.fieldFloatNotNullIndex; - dst.fieldFloatNullIndex = src.fieldFloatNullIndex; - dst.fieldDoubleNotNullIndex = src.fieldDoubleNotNullIndex; - dst.fieldDoubleNullIndex = src.fieldDoubleNullIndex; - dst.fieldDateNotNullIndex = src.fieldDateNotNullIndex; - dst.fieldDateNullIndex = src.fieldDateNullIndex; - dst.fieldObjectNullIndex = src.fieldObjectNullIndex; - dst.fieldStringListNotNullIndex = src.fieldStringListNotNullIndex; - dst.fieldStringListNullIndex = src.fieldStringListNullIndex; - dst.fieldBinaryListNotNullIndex = src.fieldBinaryListNotNullIndex; - dst.fieldBinaryListNullIndex = src.fieldBinaryListNullIndex; - dst.fieldBooleanListNotNullIndex = src.fieldBooleanListNotNullIndex; - dst.fieldBooleanListNullIndex = src.fieldBooleanListNullIndex; - dst.fieldLongListNotNullIndex = src.fieldLongListNotNullIndex; - dst.fieldLongListNullIndex = src.fieldLongListNullIndex; - dst.fieldIntegerListNotNullIndex = src.fieldIntegerListNotNullIndex; - dst.fieldIntegerListNullIndex = src.fieldIntegerListNullIndex; - dst.fieldShortListNotNullIndex = src.fieldShortListNotNullIndex; - dst.fieldShortListNullIndex = src.fieldShortListNullIndex; - dst.fieldByteListNotNullIndex = src.fieldByteListNotNullIndex; - dst.fieldByteListNullIndex = src.fieldByteListNullIndex; - dst.fieldDoubleListNotNullIndex = src.fieldDoubleListNotNullIndex; - dst.fieldDoubleListNullIndex = src.fieldDoubleListNullIndex; - dst.fieldFloatListNotNullIndex = src.fieldFloatListNotNullIndex; - dst.fieldFloatListNullIndex = src.fieldFloatListNullIndex; - dst.fieldDateListNotNullIndex = src.fieldDateListNotNullIndex; - dst.fieldDateListNullIndex = src.fieldDateListNullIndex; - dst.maxColumnIndexValue = src.maxColumnIndexValue; + dst.fieldStringNotNullColKey = src.fieldStringNotNullColKey; + dst.fieldStringNullColKey = src.fieldStringNullColKey; + dst.fieldBooleanNotNullColKey = src.fieldBooleanNotNullColKey; + dst.fieldBooleanNullColKey = src.fieldBooleanNullColKey; + dst.fieldBytesNotNullColKey = src.fieldBytesNotNullColKey; + dst.fieldBytesNullColKey = src.fieldBytesNullColKey; + dst.fieldByteNotNullColKey = src.fieldByteNotNullColKey; + dst.fieldByteNullColKey = src.fieldByteNullColKey; + dst.fieldShortNotNullColKey = src.fieldShortNotNullColKey; + dst.fieldShortNullColKey = src.fieldShortNullColKey; + dst.fieldIntegerNotNullColKey = src.fieldIntegerNotNullColKey; + dst.fieldIntegerNullColKey = src.fieldIntegerNullColKey; + dst.fieldLongNotNullColKey = src.fieldLongNotNullColKey; + dst.fieldLongNullColKey = src.fieldLongNullColKey; + dst.fieldFloatNotNullColKey = src.fieldFloatNotNullColKey; + dst.fieldFloatNullColKey = src.fieldFloatNullColKey; + dst.fieldDoubleNotNullColKey = src.fieldDoubleNotNullColKey; + dst.fieldDoubleNullColKey = src.fieldDoubleNullColKey; + dst.fieldDateNotNullColKey = src.fieldDateNotNullColKey; + dst.fieldDateNullColKey = src.fieldDateNullColKey; + dst.fieldObjectNullColKey = src.fieldObjectNullColKey; + dst.fieldStringListNotNullColKey = src.fieldStringListNotNullColKey; + dst.fieldStringListNullColKey = src.fieldStringListNullColKey; + dst.fieldBinaryListNotNullColKey = src.fieldBinaryListNotNullColKey; + dst.fieldBinaryListNullColKey = src.fieldBinaryListNullColKey; + dst.fieldBooleanListNotNullColKey = src.fieldBooleanListNotNullColKey; + dst.fieldBooleanListNullColKey = src.fieldBooleanListNullColKey; + dst.fieldLongListNotNullColKey = src.fieldLongListNotNullColKey; + dst.fieldLongListNullColKey = src.fieldLongListNullColKey; + dst.fieldIntegerListNotNullColKey = src.fieldIntegerListNotNullColKey; + dst.fieldIntegerListNullColKey = src.fieldIntegerListNullColKey; + dst.fieldShortListNotNullColKey = src.fieldShortListNotNullColKey; + dst.fieldShortListNullColKey = src.fieldShortListNullColKey; + dst.fieldByteListNotNullColKey = src.fieldByteListNotNullColKey; + dst.fieldByteListNullColKey = src.fieldByteListNullColKey; + dst.fieldDoubleListNotNullColKey = src.fieldDoubleListNotNullColKey; + dst.fieldDoubleListNullColKey = src.fieldDoubleListNullColKey; + dst.fieldFloatListNotNullColKey = src.fieldFloatListNotNullColKey; + dst.fieldFloatListNullColKey = src.fieldFloatListNullColKey; + dst.fieldDateListNotNullColKey = src.fieldDateListNotNullColKey; + dst.fieldDateListNullColKey = src.fieldDateListNullColKey; } } @@ -234,7 +231,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @SuppressWarnings("cast") public String realmGet$fieldStringNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNotNullIndex); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNotNullColKey); } @Override @@ -247,7 +244,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldStringNotNull' to null."); } - row.getTable().setString(columnInfo.fieldStringNotNullIndex, row.getIndex(), value, true); + row.getTable().setString(columnInfo.fieldStringNotNullColKey, row.getObjectKey(), value, true); return; } @@ -255,14 +252,14 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldStringNotNull' to null."); } - proxyState.getRow$realm().setString(columnInfo.fieldStringNotNullIndex, value); + proxyState.getRow$realm().setString(columnInfo.fieldStringNotNullColKey, value); } @Override @SuppressWarnings("cast") public String realmGet$fieldStringNull() { proxyState.getRealm$realm().checkIfValid(); - return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNullIndex); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.fieldStringNullColKey); } @Override @@ -273,26 +270,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldStringNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldStringNullColKey, row.getObjectKey(), true); return; } - row.getTable().setString(columnInfo.fieldStringNullIndex, row.getIndex(), value, true); + row.getTable().setString(columnInfo.fieldStringNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldStringNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldStringNullColKey); return; } - proxyState.getRow$realm().setString(columnInfo.fieldStringNullIndex, value); + proxyState.getRow$realm().setString(columnInfo.fieldStringNullColKey, value); } @Override @SuppressWarnings("cast") public Boolean realmGet$fieldBooleanNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.fieldBooleanNotNullIndex); + return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.fieldBooleanNotNullColKey); } @Override @@ -305,7 +302,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldBooleanNotNull' to null."); } - row.getTable().setBoolean(columnInfo.fieldBooleanNotNullIndex, row.getIndex(), value, true); + row.getTable().setBoolean(columnInfo.fieldBooleanNotNullColKey, row.getObjectKey(), value, true); return; } @@ -313,17 +310,17 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldBooleanNotNull' to null."); } - proxyState.getRow$realm().setBoolean(columnInfo.fieldBooleanNotNullIndex, value); + proxyState.getRow$realm().setBoolean(columnInfo.fieldBooleanNotNullColKey, value); } @Override @SuppressWarnings("cast") public Boolean realmGet$fieldBooleanNull() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNull(columnInfo.fieldBooleanNullIndex)) { + if (proxyState.getRow$realm().isNull(columnInfo.fieldBooleanNullColKey)) { return null; } - return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.fieldBooleanNullIndex); + return (boolean) proxyState.getRow$realm().getBoolean(columnInfo.fieldBooleanNullColKey); } @Override @@ -334,26 +331,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldBooleanNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldBooleanNullColKey, row.getObjectKey(), true); return; } - row.getTable().setBoolean(columnInfo.fieldBooleanNullIndex, row.getIndex(), value, true); + row.getTable().setBoolean(columnInfo.fieldBooleanNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldBooleanNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldBooleanNullColKey); return; } - proxyState.getRow$realm().setBoolean(columnInfo.fieldBooleanNullIndex, value); + proxyState.getRow$realm().setBoolean(columnInfo.fieldBooleanNullColKey, value); } @Override @SuppressWarnings("cast") public byte[] realmGet$fieldBytesNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNotNullIndex); + return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNotNullColKey); } @Override @@ -366,7 +363,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldBytesNotNull' to null."); } - row.getTable().setBinaryByteArray(columnInfo.fieldBytesNotNullIndex, row.getIndex(), value, true); + row.getTable().setBinaryByteArray(columnInfo.fieldBytesNotNullColKey, row.getObjectKey(), value, true); return; } @@ -374,14 +371,14 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldBytesNotNull' to null."); } - proxyState.getRow$realm().setBinaryByteArray(columnInfo.fieldBytesNotNullIndex, value); + proxyState.getRow$realm().setBinaryByteArray(columnInfo.fieldBytesNotNullColKey, value); } @Override @SuppressWarnings("cast") public byte[] realmGet$fieldBytesNull() { proxyState.getRealm$realm().checkIfValid(); - return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNullIndex); + return (byte[]) proxyState.getRow$realm().getBinaryByteArray(columnInfo.fieldBytesNullColKey); } @Override @@ -392,26 +389,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldBytesNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldBytesNullColKey, row.getObjectKey(), true); return; } - row.getTable().setBinaryByteArray(columnInfo.fieldBytesNullIndex, row.getIndex(), value, true); + row.getTable().setBinaryByteArray(columnInfo.fieldBytesNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldBytesNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldBytesNullColKey); return; } - proxyState.getRow$realm().setBinaryByteArray(columnInfo.fieldBytesNullIndex, value); + proxyState.getRow$realm().setBinaryByteArray(columnInfo.fieldBytesNullColKey, value); } @Override @SuppressWarnings("cast") public Byte realmGet$fieldByteNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (byte) proxyState.getRow$realm().getLong(columnInfo.fieldByteNotNullIndex); + return (byte) proxyState.getRow$realm().getLong(columnInfo.fieldByteNotNullColKey); } @Override @@ -424,7 +421,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldByteNotNull' to null."); } - row.getTable().setLong(columnInfo.fieldByteNotNullIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.fieldByteNotNullColKey, row.getObjectKey(), value, true); return; } @@ -432,17 +429,17 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldByteNotNull' to null."); } - proxyState.getRow$realm().setLong(columnInfo.fieldByteNotNullIndex, value); + proxyState.getRow$realm().setLong(columnInfo.fieldByteNotNullColKey, value); } @Override @SuppressWarnings("cast") public Byte realmGet$fieldByteNull() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNull(columnInfo.fieldByteNullIndex)) { + if (proxyState.getRow$realm().isNull(columnInfo.fieldByteNullColKey)) { return null; } - return (byte) proxyState.getRow$realm().getLong(columnInfo.fieldByteNullIndex); + return (byte) proxyState.getRow$realm().getLong(columnInfo.fieldByteNullColKey); } @Override @@ -453,26 +450,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldByteNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldByteNullColKey, row.getObjectKey(), true); return; } - row.getTable().setLong(columnInfo.fieldByteNullIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.fieldByteNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldByteNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldByteNullColKey); return; } - proxyState.getRow$realm().setLong(columnInfo.fieldByteNullIndex, value); + proxyState.getRow$realm().setLong(columnInfo.fieldByteNullColKey, value); } @Override @SuppressWarnings("cast") public Short realmGet$fieldShortNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (short) proxyState.getRow$realm().getLong(columnInfo.fieldShortNotNullIndex); + return (short) proxyState.getRow$realm().getLong(columnInfo.fieldShortNotNullColKey); } @Override @@ -485,7 +482,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldShortNotNull' to null."); } - row.getTable().setLong(columnInfo.fieldShortNotNullIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.fieldShortNotNullColKey, row.getObjectKey(), value, true); return; } @@ -493,17 +490,17 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldShortNotNull' to null."); } - proxyState.getRow$realm().setLong(columnInfo.fieldShortNotNullIndex, value); + proxyState.getRow$realm().setLong(columnInfo.fieldShortNotNullColKey, value); } @Override @SuppressWarnings("cast") public Short realmGet$fieldShortNull() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNull(columnInfo.fieldShortNullIndex)) { + if (proxyState.getRow$realm().isNull(columnInfo.fieldShortNullColKey)) { return null; } - return (short) proxyState.getRow$realm().getLong(columnInfo.fieldShortNullIndex); + return (short) proxyState.getRow$realm().getLong(columnInfo.fieldShortNullColKey); } @Override @@ -514,26 +511,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldShortNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldShortNullColKey, row.getObjectKey(), true); return; } - row.getTable().setLong(columnInfo.fieldShortNullIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.fieldShortNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldShortNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldShortNullColKey); return; } - proxyState.getRow$realm().setLong(columnInfo.fieldShortNullIndex, value); + proxyState.getRow$realm().setLong(columnInfo.fieldShortNullColKey, value); } @Override @SuppressWarnings("cast") public Integer realmGet$fieldIntegerNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (int) proxyState.getRow$realm().getLong(columnInfo.fieldIntegerNotNullIndex); + return (int) proxyState.getRow$realm().getLong(columnInfo.fieldIntegerNotNullColKey); } @Override @@ -546,7 +543,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldIntegerNotNull' to null."); } - row.getTable().setLong(columnInfo.fieldIntegerNotNullIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.fieldIntegerNotNullColKey, row.getObjectKey(), value, true); return; } @@ -554,17 +551,17 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldIntegerNotNull' to null."); } - proxyState.getRow$realm().setLong(columnInfo.fieldIntegerNotNullIndex, value); + proxyState.getRow$realm().setLong(columnInfo.fieldIntegerNotNullColKey, value); } @Override @SuppressWarnings("cast") public Integer realmGet$fieldIntegerNull() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNull(columnInfo.fieldIntegerNullIndex)) { + if (proxyState.getRow$realm().isNull(columnInfo.fieldIntegerNullColKey)) { return null; } - return (int) proxyState.getRow$realm().getLong(columnInfo.fieldIntegerNullIndex); + return (int) proxyState.getRow$realm().getLong(columnInfo.fieldIntegerNullColKey); } @Override @@ -575,26 +572,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldIntegerNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldIntegerNullColKey, row.getObjectKey(), true); return; } - row.getTable().setLong(columnInfo.fieldIntegerNullIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.fieldIntegerNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldIntegerNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldIntegerNullColKey); return; } - proxyState.getRow$realm().setLong(columnInfo.fieldIntegerNullIndex, value); + proxyState.getRow$realm().setLong(columnInfo.fieldIntegerNullColKey, value); } @Override @SuppressWarnings("cast") public Long realmGet$fieldLongNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (long) proxyState.getRow$realm().getLong(columnInfo.fieldLongNotNullIndex); + return (long) proxyState.getRow$realm().getLong(columnInfo.fieldLongNotNullColKey); } @Override @@ -607,7 +604,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldLongNotNull' to null."); } - row.getTable().setLong(columnInfo.fieldLongNotNullIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.fieldLongNotNullColKey, row.getObjectKey(), value, true); return; } @@ -615,17 +612,17 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldLongNotNull' to null."); } - proxyState.getRow$realm().setLong(columnInfo.fieldLongNotNullIndex, value); + proxyState.getRow$realm().setLong(columnInfo.fieldLongNotNullColKey, value); } @Override @SuppressWarnings("cast") public Long realmGet$fieldLongNull() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNull(columnInfo.fieldLongNullIndex)) { + if (proxyState.getRow$realm().isNull(columnInfo.fieldLongNullColKey)) { return null; } - return (long) proxyState.getRow$realm().getLong(columnInfo.fieldLongNullIndex); + return (long) proxyState.getRow$realm().getLong(columnInfo.fieldLongNullColKey); } @Override @@ -636,26 +633,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldLongNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldLongNullColKey, row.getObjectKey(), true); return; } - row.getTable().setLong(columnInfo.fieldLongNullIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.fieldLongNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldLongNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldLongNullColKey); return; } - proxyState.getRow$realm().setLong(columnInfo.fieldLongNullIndex, value); + proxyState.getRow$realm().setLong(columnInfo.fieldLongNullColKey, value); } @Override @SuppressWarnings("cast") public Float realmGet$fieldFloatNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (float) proxyState.getRow$realm().getFloat(columnInfo.fieldFloatNotNullIndex); + return (float) proxyState.getRow$realm().getFloat(columnInfo.fieldFloatNotNullColKey); } @Override @@ -668,7 +665,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldFloatNotNull' to null."); } - row.getTable().setFloat(columnInfo.fieldFloatNotNullIndex, row.getIndex(), value, true); + row.getTable().setFloat(columnInfo.fieldFloatNotNullColKey, row.getObjectKey(), value, true); return; } @@ -676,17 +673,17 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldFloatNotNull' to null."); } - proxyState.getRow$realm().setFloat(columnInfo.fieldFloatNotNullIndex, value); + proxyState.getRow$realm().setFloat(columnInfo.fieldFloatNotNullColKey, value); } @Override @SuppressWarnings("cast") public Float realmGet$fieldFloatNull() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNull(columnInfo.fieldFloatNullIndex)) { + if (proxyState.getRow$realm().isNull(columnInfo.fieldFloatNullColKey)) { return null; } - return (float) proxyState.getRow$realm().getFloat(columnInfo.fieldFloatNullIndex); + return (float) proxyState.getRow$realm().getFloat(columnInfo.fieldFloatNullColKey); } @Override @@ -697,26 +694,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldFloatNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldFloatNullColKey, row.getObjectKey(), true); return; } - row.getTable().setFloat(columnInfo.fieldFloatNullIndex, row.getIndex(), value, true); + row.getTable().setFloat(columnInfo.fieldFloatNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldFloatNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldFloatNullColKey); return; } - proxyState.getRow$realm().setFloat(columnInfo.fieldFloatNullIndex, value); + proxyState.getRow$realm().setFloat(columnInfo.fieldFloatNullColKey, value); } @Override @SuppressWarnings("cast") public Double realmGet$fieldDoubleNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (double) proxyState.getRow$realm().getDouble(columnInfo.fieldDoubleNotNullIndex); + return (double) proxyState.getRow$realm().getDouble(columnInfo.fieldDoubleNotNullColKey); } @Override @@ -729,7 +726,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDoubleNotNull' to null."); } - row.getTable().setDouble(columnInfo.fieldDoubleNotNullIndex, row.getIndex(), value, true); + row.getTable().setDouble(columnInfo.fieldDoubleNotNullColKey, row.getObjectKey(), value, true); return; } @@ -737,17 +734,17 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDoubleNotNull' to null."); } - proxyState.getRow$realm().setDouble(columnInfo.fieldDoubleNotNullIndex, value); + proxyState.getRow$realm().setDouble(columnInfo.fieldDoubleNotNullColKey, value); } @Override @SuppressWarnings("cast") public Double realmGet$fieldDoubleNull() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNull(columnInfo.fieldDoubleNullIndex)) { + if (proxyState.getRow$realm().isNull(columnInfo.fieldDoubleNullColKey)) { return null; } - return (double) proxyState.getRow$realm().getDouble(columnInfo.fieldDoubleNullIndex); + return (double) proxyState.getRow$realm().getDouble(columnInfo.fieldDoubleNullColKey); } @Override @@ -758,26 +755,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldDoubleNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldDoubleNullColKey, row.getObjectKey(), true); return; } - row.getTable().setDouble(columnInfo.fieldDoubleNullIndex, row.getIndex(), value, true); + row.getTable().setDouble(columnInfo.fieldDoubleNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldDoubleNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldDoubleNullColKey); return; } - proxyState.getRow$realm().setDouble(columnInfo.fieldDoubleNullIndex, value); + proxyState.getRow$realm().setDouble(columnInfo.fieldDoubleNullColKey, value); } @Override @SuppressWarnings("cast") public Date realmGet$fieldDateNotNull() { proxyState.getRealm$realm().checkIfValid(); - return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.fieldDateNotNullIndex); + return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.fieldDateNotNullColKey); } @Override @@ -790,7 +787,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDateNotNull' to null."); } - row.getTable().setDate(columnInfo.fieldDateNotNullIndex, row.getIndex(), value, true); + row.getTable().setDate(columnInfo.fieldDateNotNullColKey, row.getObjectKey(), value, true); return; } @@ -798,17 +795,17 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (value == null) { throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDateNotNull' to null."); } - proxyState.getRow$realm().setDate(columnInfo.fieldDateNotNullIndex, value); + proxyState.getRow$realm().setDate(columnInfo.fieldDateNotNullColKey, value); } @Override @SuppressWarnings("cast") public Date realmGet$fieldDateNull() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNull(columnInfo.fieldDateNullIndex)) { + if (proxyState.getRow$realm().isNull(columnInfo.fieldDateNullColKey)) { return null; } - return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.fieldDateNullIndex); + return (java.util.Date) proxyState.getRow$realm().getDate(columnInfo.fieldDateNullColKey); } @Override @@ -819,28 +816,28 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.fieldDateNullIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.fieldDateNullColKey, row.getObjectKey(), true); return; } - row.getTable().setDate(columnInfo.fieldDateNullIndex, row.getIndex(), value, true); + row.getTable().setDate(columnInfo.fieldDateNullColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.fieldDateNullIndex); + proxyState.getRow$realm().setNull(columnInfo.fieldDateNullColKey); return; } - proxyState.getRow$realm().setDate(columnInfo.fieldDateNullIndex, value); + proxyState.getRow$realm().setDate(columnInfo.fieldDateNullColKey, value); } @Override public some.test.NullTypes realmGet$fieldObjectNull() { proxyState.getRealm$realm().checkIfValid(); - if (proxyState.getRow$realm().isNullLink(columnInfo.fieldObjectNullIndex)) { + if (proxyState.getRow$realm().isNullLink(columnInfo.fieldObjectNullColKey)) { return null; } - return proxyState.getRealm$realm().get(some.test.NullTypes.class, proxyState.getRow$realm().getLink(columnInfo.fieldObjectNullIndex), false, Collections.emptyList()); + return proxyState.getRealm$realm().get(some.test.NullTypes.class, proxyState.getRow$realm().getLink(columnInfo.fieldObjectNullColKey), false, Collections.emptyList()); } @Override @@ -858,21 +855,21 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final Row row = proxyState.getRow$realm(); if (value == null) { // Table#nullifyLink() does not support default value. Just using Row. - row.nullifyLink(columnInfo.fieldObjectNullIndex); + row.nullifyLink(columnInfo.fieldObjectNullColKey); return; } proxyState.checkValidObject(value); - row.getTable().setLink(columnInfo.fieldObjectNullIndex, row.getIndex(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex(), true); + row.getTable().setLink(columnInfo.fieldObjectNullColKey, row.getObjectKey(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getObjectKey(), true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().nullifyLink(columnInfo.fieldObjectNullIndex); + proxyState.getRow$realm().nullifyLink(columnInfo.fieldObjectNullColKey); return; } proxyState.checkValidObject(value); - proxyState.getRow$realm().setLink(columnInfo.fieldObjectNullIndex, ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getIndex()); + proxyState.getRow$realm().setLink(columnInfo.fieldObjectNullColKey, ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getObjectKey()); } @Override @@ -882,7 +879,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldStringListNotNullRealmList != null) { return fieldStringListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNotNullIndex, RealmFieldType.STRING_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNotNullColKey, RealmFieldType.STRING_LIST); fieldStringListNotNullRealmList = new RealmList(java.lang.String.class, osList, proxyState.getRealm$realm()); return fieldStringListNotNullRealmList; } @@ -900,7 +897,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNotNullIndex, RealmFieldType.STRING_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNotNullColKey, RealmFieldType.STRING_LIST); osList.removeAll(); if (value == null) { return; @@ -921,7 +918,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldStringListNullRealmList != null) { return fieldStringListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNullIndex, RealmFieldType.STRING_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNullColKey, RealmFieldType.STRING_LIST); fieldStringListNullRealmList = new RealmList(java.lang.String.class, osList, proxyState.getRealm$realm()); return fieldStringListNullRealmList; } @@ -939,7 +936,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNullIndex, RealmFieldType.STRING_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldStringListNullColKey, RealmFieldType.STRING_LIST); osList.removeAll(); if (value == null) { return; @@ -960,7 +957,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldBinaryListNotNullRealmList != null) { return fieldBinaryListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNotNullIndex, RealmFieldType.BINARY_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNotNullColKey, RealmFieldType.BINARY_LIST); fieldBinaryListNotNullRealmList = new RealmList(byte[].class, osList, proxyState.getRealm$realm()); return fieldBinaryListNotNullRealmList; } @@ -978,7 +975,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNotNullIndex, RealmFieldType.BINARY_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNotNullColKey, RealmFieldType.BINARY_LIST); osList.removeAll(); if (value == null) { return; @@ -999,7 +996,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldBinaryListNullRealmList != null) { return fieldBinaryListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNullIndex, RealmFieldType.BINARY_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNullColKey, RealmFieldType.BINARY_LIST); fieldBinaryListNullRealmList = new RealmList(byte[].class, osList, proxyState.getRealm$realm()); return fieldBinaryListNullRealmList; } @@ -1017,7 +1014,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNullIndex, RealmFieldType.BINARY_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBinaryListNullColKey, RealmFieldType.BINARY_LIST); osList.removeAll(); if (value == null) { return; @@ -1038,7 +1035,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldBooleanListNotNullRealmList != null) { return fieldBooleanListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNotNullIndex, RealmFieldType.BOOLEAN_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNotNullColKey, RealmFieldType.BOOLEAN_LIST); fieldBooleanListNotNullRealmList = new RealmList(java.lang.Boolean.class, osList, proxyState.getRealm$realm()); return fieldBooleanListNotNullRealmList; } @@ -1056,7 +1053,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNotNullIndex, RealmFieldType.BOOLEAN_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNotNullColKey, RealmFieldType.BOOLEAN_LIST); osList.removeAll(); if (value == null) { return; @@ -1077,7 +1074,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldBooleanListNullRealmList != null) { return fieldBooleanListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNullIndex, RealmFieldType.BOOLEAN_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNullColKey, RealmFieldType.BOOLEAN_LIST); fieldBooleanListNullRealmList = new RealmList(java.lang.Boolean.class, osList, proxyState.getRealm$realm()); return fieldBooleanListNullRealmList; } @@ -1095,7 +1092,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNullIndex, RealmFieldType.BOOLEAN_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldBooleanListNullColKey, RealmFieldType.BOOLEAN_LIST); osList.removeAll(); if (value == null) { return; @@ -1116,7 +1113,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldLongListNotNullRealmList != null) { return fieldLongListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNotNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNotNullColKey, RealmFieldType.INTEGER_LIST); fieldLongListNotNullRealmList = new RealmList(java.lang.Long.class, osList, proxyState.getRealm$realm()); return fieldLongListNotNullRealmList; } @@ -1134,7 +1131,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNotNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNotNullColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -1155,7 +1152,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldLongListNullRealmList != null) { return fieldLongListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNullColKey, RealmFieldType.INTEGER_LIST); fieldLongListNullRealmList = new RealmList(java.lang.Long.class, osList, proxyState.getRealm$realm()); return fieldLongListNullRealmList; } @@ -1173,7 +1170,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldLongListNullColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -1194,7 +1191,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldIntegerListNotNullRealmList != null) { return fieldIntegerListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNotNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNotNullColKey, RealmFieldType.INTEGER_LIST); fieldIntegerListNotNullRealmList = new RealmList(java.lang.Integer.class, osList, proxyState.getRealm$realm()); return fieldIntegerListNotNullRealmList; } @@ -1212,7 +1209,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNotNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNotNullColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -1233,7 +1230,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldIntegerListNullRealmList != null) { return fieldIntegerListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNullColKey, RealmFieldType.INTEGER_LIST); fieldIntegerListNullRealmList = new RealmList(java.lang.Integer.class, osList, proxyState.getRealm$realm()); return fieldIntegerListNullRealmList; } @@ -1251,7 +1248,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldIntegerListNullColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -1272,7 +1269,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldShortListNotNullRealmList != null) { return fieldShortListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNotNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNotNullColKey, RealmFieldType.INTEGER_LIST); fieldShortListNotNullRealmList = new RealmList(java.lang.Short.class, osList, proxyState.getRealm$realm()); return fieldShortListNotNullRealmList; } @@ -1290,7 +1287,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNotNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNotNullColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -1311,7 +1308,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldShortListNullRealmList != null) { return fieldShortListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNullColKey, RealmFieldType.INTEGER_LIST); fieldShortListNullRealmList = new RealmList(java.lang.Short.class, osList, proxyState.getRealm$realm()); return fieldShortListNullRealmList; } @@ -1329,7 +1326,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldShortListNullColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -1350,7 +1347,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldByteListNotNullRealmList != null) { return fieldByteListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNotNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNotNullColKey, RealmFieldType.INTEGER_LIST); fieldByteListNotNullRealmList = new RealmList(java.lang.Byte.class, osList, proxyState.getRealm$realm()); return fieldByteListNotNullRealmList; } @@ -1368,7 +1365,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNotNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNotNullColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -1389,7 +1386,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldByteListNullRealmList != null) { return fieldByteListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNullColKey, RealmFieldType.INTEGER_LIST); fieldByteListNullRealmList = new RealmList(java.lang.Byte.class, osList, proxyState.getRealm$realm()); return fieldByteListNullRealmList; } @@ -1407,7 +1404,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNullIndex, RealmFieldType.INTEGER_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldByteListNullColKey, RealmFieldType.INTEGER_LIST); osList.removeAll(); if (value == null) { return; @@ -1428,7 +1425,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldDoubleListNotNullRealmList != null) { return fieldDoubleListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNotNullIndex, RealmFieldType.DOUBLE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNotNullColKey, RealmFieldType.DOUBLE_LIST); fieldDoubleListNotNullRealmList = new RealmList(java.lang.Double.class, osList, proxyState.getRealm$realm()); return fieldDoubleListNotNullRealmList; } @@ -1446,7 +1443,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNotNullIndex, RealmFieldType.DOUBLE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNotNullColKey, RealmFieldType.DOUBLE_LIST); osList.removeAll(); if (value == null) { return; @@ -1467,7 +1464,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldDoubleListNullRealmList != null) { return fieldDoubleListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNullIndex, RealmFieldType.DOUBLE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNullColKey, RealmFieldType.DOUBLE_LIST); fieldDoubleListNullRealmList = new RealmList(java.lang.Double.class, osList, proxyState.getRealm$realm()); return fieldDoubleListNullRealmList; } @@ -1485,7 +1482,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNullIndex, RealmFieldType.DOUBLE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDoubleListNullColKey, RealmFieldType.DOUBLE_LIST); osList.removeAll(); if (value == null) { return; @@ -1506,7 +1503,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldFloatListNotNullRealmList != null) { return fieldFloatListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNotNullIndex, RealmFieldType.FLOAT_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNotNullColKey, RealmFieldType.FLOAT_LIST); fieldFloatListNotNullRealmList = new RealmList(java.lang.Float.class, osList, proxyState.getRealm$realm()); return fieldFloatListNotNullRealmList; } @@ -1524,7 +1521,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNotNullIndex, RealmFieldType.FLOAT_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNotNullColKey, RealmFieldType.FLOAT_LIST); osList.removeAll(); if (value == null) { return; @@ -1545,7 +1542,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldFloatListNullRealmList != null) { return fieldFloatListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNullIndex, RealmFieldType.FLOAT_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNullColKey, RealmFieldType.FLOAT_LIST); fieldFloatListNullRealmList = new RealmList(java.lang.Float.class, osList, proxyState.getRealm$realm()); return fieldFloatListNullRealmList; } @@ -1563,7 +1560,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNullIndex, RealmFieldType.FLOAT_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldFloatListNullColKey, RealmFieldType.FLOAT_LIST); osList.removeAll(); if (value == null) { return; @@ -1584,7 +1581,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldDateListNotNullRealmList != null) { return fieldDateListNotNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNotNullIndex, RealmFieldType.DATE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNotNullColKey, RealmFieldType.DATE_LIST); fieldDateListNotNullRealmList = new RealmList(java.util.Date.class, osList, proxyState.getRealm$realm()); return fieldDateListNotNullRealmList; } @@ -1602,7 +1599,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNotNullIndex, RealmFieldType.DATE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNotNullColKey, RealmFieldType.DATE_LIST); osList.removeAll(); if (value == null) { return; @@ -1623,7 +1620,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { if (fieldDateListNullRealmList != null) { return fieldDateListNullRealmList; } else { - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNullIndex, RealmFieldType.DATE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNullColKey, RealmFieldType.DATE_LIST); fieldDateListNullRealmList = new RealmList(java.util.Date.class, osList, proxyState.getRealm$realm()); return fieldDateListNullRealmList; } @@ -1641,7 +1638,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } proxyState.getRealm$realm().checkIfValid(); - OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNullIndex, RealmFieldType.DATE_LIST); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDateListNullColKey, RealmFieldType.DATE_LIST); osList.removeAll(); if (value == null) { return; @@ -2185,7 +2182,7 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader } private static some_test_NullTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { - // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NullTypes.class), false, Collections.emptyList()); io.realm.some_test_NullTypesRealmProxy obj = new io.realm.some_test_NullTypesRealmProxy(); @@ -2194,7 +2191,7 @@ private static some_test_NullTypesRealmProxy newProxyInstance(BaseRealm realm, R } public static some.test.NullTypes copyOrUpdate(Realm realm, NullTypesColumnInfo columnInfo, some.test.NullTypes object, boolean update, Map cache, Set flags) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); @@ -2221,49 +2218,49 @@ public static some.test.NullTypes copy(Realm realm, NullTypesColumnInfo columnIn some_test_NullTypesRealmProxyInterface realmObjectSource = (some_test_NullTypesRealmProxyInterface) newObject; Table table = realm.getTable(some.test.NullTypes.class); - OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.fieldStringNotNullIndex, realmObjectSource.realmGet$fieldStringNotNull()); - builder.addString(columnInfo.fieldStringNullIndex, realmObjectSource.realmGet$fieldStringNull()); - builder.addBoolean(columnInfo.fieldBooleanNotNullIndex, realmObjectSource.realmGet$fieldBooleanNotNull()); - builder.addBoolean(columnInfo.fieldBooleanNullIndex, realmObjectSource.realmGet$fieldBooleanNull()); - builder.addByteArray(columnInfo.fieldBytesNotNullIndex, realmObjectSource.realmGet$fieldBytesNotNull()); - builder.addByteArray(columnInfo.fieldBytesNullIndex, realmObjectSource.realmGet$fieldBytesNull()); - builder.addInteger(columnInfo.fieldByteNotNullIndex, realmObjectSource.realmGet$fieldByteNotNull()); - builder.addInteger(columnInfo.fieldByteNullIndex, realmObjectSource.realmGet$fieldByteNull()); - builder.addInteger(columnInfo.fieldShortNotNullIndex, realmObjectSource.realmGet$fieldShortNotNull()); - builder.addInteger(columnInfo.fieldShortNullIndex, realmObjectSource.realmGet$fieldShortNull()); - builder.addInteger(columnInfo.fieldIntegerNotNullIndex, realmObjectSource.realmGet$fieldIntegerNotNull()); - builder.addInteger(columnInfo.fieldIntegerNullIndex, realmObjectSource.realmGet$fieldIntegerNull()); - builder.addInteger(columnInfo.fieldLongNotNullIndex, realmObjectSource.realmGet$fieldLongNotNull()); - builder.addInteger(columnInfo.fieldLongNullIndex, realmObjectSource.realmGet$fieldLongNull()); - builder.addFloat(columnInfo.fieldFloatNotNullIndex, realmObjectSource.realmGet$fieldFloatNotNull()); - builder.addFloat(columnInfo.fieldFloatNullIndex, realmObjectSource.realmGet$fieldFloatNull()); - builder.addDouble(columnInfo.fieldDoubleNotNullIndex, realmObjectSource.realmGet$fieldDoubleNotNull()); - builder.addDouble(columnInfo.fieldDoubleNullIndex, realmObjectSource.realmGet$fieldDoubleNull()); - builder.addDate(columnInfo.fieldDateNotNullIndex, realmObjectSource.realmGet$fieldDateNotNull()); - builder.addDate(columnInfo.fieldDateNullIndex, realmObjectSource.realmGet$fieldDateNull()); - builder.addStringList(columnInfo.fieldStringListNotNullIndex, realmObjectSource.realmGet$fieldStringListNotNull()); - builder.addStringList(columnInfo.fieldStringListNullIndex, realmObjectSource.realmGet$fieldStringListNull()); - builder.addByteArrayList(columnInfo.fieldBinaryListNotNullIndex, realmObjectSource.realmGet$fieldBinaryListNotNull()); - builder.addByteArrayList(columnInfo.fieldBinaryListNullIndex, realmObjectSource.realmGet$fieldBinaryListNull()); - builder.addBooleanList(columnInfo.fieldBooleanListNotNullIndex, realmObjectSource.realmGet$fieldBooleanListNotNull()); - builder.addBooleanList(columnInfo.fieldBooleanListNullIndex, realmObjectSource.realmGet$fieldBooleanListNull()); - builder.addLongList(columnInfo.fieldLongListNotNullIndex, realmObjectSource.realmGet$fieldLongListNotNull()); - builder.addLongList(columnInfo.fieldLongListNullIndex, realmObjectSource.realmGet$fieldLongListNull()); - builder.addIntegerList(columnInfo.fieldIntegerListNotNullIndex, realmObjectSource.realmGet$fieldIntegerListNotNull()); - builder.addIntegerList(columnInfo.fieldIntegerListNullIndex, realmObjectSource.realmGet$fieldIntegerListNull()); - builder.addShortList(columnInfo.fieldShortListNotNullIndex, realmObjectSource.realmGet$fieldShortListNotNull()); - builder.addShortList(columnInfo.fieldShortListNullIndex, realmObjectSource.realmGet$fieldShortListNull()); - builder.addByteList(columnInfo.fieldByteListNotNullIndex, realmObjectSource.realmGet$fieldByteListNotNull()); - builder.addByteList(columnInfo.fieldByteListNullIndex, realmObjectSource.realmGet$fieldByteListNull()); - builder.addDoubleList(columnInfo.fieldDoubleListNotNullIndex, realmObjectSource.realmGet$fieldDoubleListNotNull()); - builder.addDoubleList(columnInfo.fieldDoubleListNullIndex, realmObjectSource.realmGet$fieldDoubleListNull()); - builder.addFloatList(columnInfo.fieldFloatListNotNullIndex, realmObjectSource.realmGet$fieldFloatListNotNull()); - builder.addFloatList(columnInfo.fieldFloatListNullIndex, realmObjectSource.realmGet$fieldFloatListNull()); - builder.addDateList(columnInfo.fieldDateListNotNullIndex, realmObjectSource.realmGet$fieldDateListNotNull()); - builder.addDateList(columnInfo.fieldDateListNullIndex, realmObjectSource.realmGet$fieldDateListNull()); + builder.addString(columnInfo.fieldStringNotNullColKey, realmObjectSource.realmGet$fieldStringNotNull()); + builder.addString(columnInfo.fieldStringNullColKey, realmObjectSource.realmGet$fieldStringNull()); + builder.addBoolean(columnInfo.fieldBooleanNotNullColKey, realmObjectSource.realmGet$fieldBooleanNotNull()); + builder.addBoolean(columnInfo.fieldBooleanNullColKey, realmObjectSource.realmGet$fieldBooleanNull()); + builder.addByteArray(columnInfo.fieldBytesNotNullColKey, realmObjectSource.realmGet$fieldBytesNotNull()); + builder.addByteArray(columnInfo.fieldBytesNullColKey, realmObjectSource.realmGet$fieldBytesNull()); + builder.addInteger(columnInfo.fieldByteNotNullColKey, realmObjectSource.realmGet$fieldByteNotNull()); + builder.addInteger(columnInfo.fieldByteNullColKey, realmObjectSource.realmGet$fieldByteNull()); + builder.addInteger(columnInfo.fieldShortNotNullColKey, realmObjectSource.realmGet$fieldShortNotNull()); + builder.addInteger(columnInfo.fieldShortNullColKey, realmObjectSource.realmGet$fieldShortNull()); + builder.addInteger(columnInfo.fieldIntegerNotNullColKey, realmObjectSource.realmGet$fieldIntegerNotNull()); + builder.addInteger(columnInfo.fieldIntegerNullColKey, realmObjectSource.realmGet$fieldIntegerNull()); + builder.addInteger(columnInfo.fieldLongNotNullColKey, realmObjectSource.realmGet$fieldLongNotNull()); + builder.addInteger(columnInfo.fieldLongNullColKey, realmObjectSource.realmGet$fieldLongNull()); + builder.addFloat(columnInfo.fieldFloatNotNullColKey, realmObjectSource.realmGet$fieldFloatNotNull()); + builder.addFloat(columnInfo.fieldFloatNullColKey, realmObjectSource.realmGet$fieldFloatNull()); + builder.addDouble(columnInfo.fieldDoubleNotNullColKey, realmObjectSource.realmGet$fieldDoubleNotNull()); + builder.addDouble(columnInfo.fieldDoubleNullColKey, realmObjectSource.realmGet$fieldDoubleNull()); + builder.addDate(columnInfo.fieldDateNotNullColKey, realmObjectSource.realmGet$fieldDateNotNull()); + builder.addDate(columnInfo.fieldDateNullColKey, realmObjectSource.realmGet$fieldDateNull()); + builder.addStringList(columnInfo.fieldStringListNotNullColKey, realmObjectSource.realmGet$fieldStringListNotNull()); + builder.addStringList(columnInfo.fieldStringListNullColKey, realmObjectSource.realmGet$fieldStringListNull()); + builder.addByteArrayList(columnInfo.fieldBinaryListNotNullColKey, realmObjectSource.realmGet$fieldBinaryListNotNull()); + builder.addByteArrayList(columnInfo.fieldBinaryListNullColKey, realmObjectSource.realmGet$fieldBinaryListNull()); + builder.addBooleanList(columnInfo.fieldBooleanListNotNullColKey, realmObjectSource.realmGet$fieldBooleanListNotNull()); + builder.addBooleanList(columnInfo.fieldBooleanListNullColKey, realmObjectSource.realmGet$fieldBooleanListNull()); + builder.addLongList(columnInfo.fieldLongListNotNullColKey, realmObjectSource.realmGet$fieldLongListNotNull()); + builder.addLongList(columnInfo.fieldLongListNullColKey, realmObjectSource.realmGet$fieldLongListNull()); + builder.addIntegerList(columnInfo.fieldIntegerListNotNullColKey, realmObjectSource.realmGet$fieldIntegerListNotNull()); + builder.addIntegerList(columnInfo.fieldIntegerListNullColKey, realmObjectSource.realmGet$fieldIntegerListNull()); + builder.addShortList(columnInfo.fieldShortListNotNullColKey, realmObjectSource.realmGet$fieldShortListNotNull()); + builder.addShortList(columnInfo.fieldShortListNullColKey, realmObjectSource.realmGet$fieldShortListNull()); + builder.addByteList(columnInfo.fieldByteListNotNullColKey, realmObjectSource.realmGet$fieldByteListNotNull()); + builder.addByteList(columnInfo.fieldByteListNullColKey, realmObjectSource.realmGet$fieldByteListNull()); + builder.addDoubleList(columnInfo.fieldDoubleListNotNullColKey, realmObjectSource.realmGet$fieldDoubleListNotNull()); + builder.addDoubleList(columnInfo.fieldDoubleListNullColKey, realmObjectSource.realmGet$fieldDoubleListNull()); + builder.addFloatList(columnInfo.fieldFloatListNotNullColKey, realmObjectSource.realmGet$fieldFloatListNotNull()); + builder.addFloatList(columnInfo.fieldFloatListNullColKey, realmObjectSource.realmGet$fieldFloatListNull()); + builder.addDateList(columnInfo.fieldDateListNotNullColKey, realmObjectSource.realmGet$fieldDateListNotNull()); + builder.addDateList(columnInfo.fieldDateListNullColKey, realmObjectSource.realmGet$fieldDateListNull()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. @@ -2288,93 +2285,93 @@ public static some.test.NullTypes copy(Realm realm, NullTypesColumnInfo columnIn } public static long insert(Realm realm, some.test.NullTypes object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.NullTypes.class); long tableNativePtr = table.getNativePtr(); NullTypesColumnInfo columnInfo = (NullTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.NullTypes.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$fieldStringNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullColKey, colKey, realmGet$fieldStringNotNull, false); } String realmGet$fieldStringNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullColKey, colKey, realmGet$fieldStringNull, false); } Boolean realmGet$fieldBooleanNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, colKey, realmGet$fieldBooleanNotNull, false); } Boolean realmGet$fieldBooleanNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullColKey, colKey, realmGet$fieldBooleanNull, false); } byte[] realmGet$fieldBytesNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullColKey, colKey, realmGet$fieldBytesNotNull, false); } byte[] realmGet$fieldBytesNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullColKey, colKey, realmGet$fieldBytesNull, false); } Number realmGet$fieldByteNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullColKey, colKey, realmGet$fieldByteNotNull.longValue(), false); } Number realmGet$fieldByteNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullColKey, colKey, realmGet$fieldByteNull.longValue(), false); } Number realmGet$fieldShortNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullColKey, colKey, realmGet$fieldShortNotNull.longValue(), false); } Number realmGet$fieldShortNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullColKey, colKey, realmGet$fieldShortNull.longValue(), false); } Number realmGet$fieldIntegerNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, colKey, realmGet$fieldIntegerNotNull.longValue(), false); } Number realmGet$fieldIntegerNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullColKey, colKey, realmGet$fieldIntegerNull.longValue(), false); } Number realmGet$fieldLongNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullColKey, colKey, realmGet$fieldLongNotNull.longValue(), false); } Number realmGet$fieldLongNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullColKey, colKey, realmGet$fieldLongNull.longValue(), false); } Float realmGet$fieldFloatNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullColKey, colKey, realmGet$fieldFloatNotNull, false); } Float realmGet$fieldFloatNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullColKey, colKey, realmGet$fieldFloatNull, false); } Double realmGet$fieldDoubleNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, colKey, realmGet$fieldDoubleNotNull, false); } Double realmGet$fieldDoubleNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullColKey, colKey, realmGet$fieldDoubleNull, false); } java.util.Date realmGet$fieldDateNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullColKey, colKey, realmGet$fieldDateNotNull.getTime(), false); } java.util.Date realmGet$fieldDateNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullColKey, colKey, realmGet$fieldDateNull.getTime(), false); } some.test.NullTypes fieldObjectNullObj = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); @@ -2383,12 +2380,12 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { - OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNotNullIndex); + OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNotNullColKey); for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { if (fieldStringListNotNullItem == null) { fieldStringListNotNullOsList.addNull(); @@ -2400,7 +2397,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { - OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNullColKey); for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { if (fieldStringListNullItem == null) { fieldStringListNullOsList.addNull(); @@ -2412,7 +2409,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { - OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNotNullColKey); for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { if (fieldBinaryListNotNullItem == null) { fieldBinaryListNotNullOsList.addNull(); @@ -2424,7 +2421,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { - OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNullColKey); for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { if (fieldBinaryListNullItem == null) { fieldBinaryListNullOsList.addNull(); @@ -2436,7 +2433,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { - OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNotNullColKey); for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { if (fieldBooleanListNotNullItem == null) { fieldBooleanListNotNullOsList.addNull(); @@ -2448,7 +2445,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { - OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNullColKey); for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { if (fieldBooleanListNullItem == null) { fieldBooleanListNullOsList.addNull(); @@ -2460,7 +2457,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { - OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNotNullColKey); for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { if (fieldLongListNotNullItem == null) { fieldLongListNotNullOsList.addNull(); @@ -2472,7 +2469,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { - OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNullColKey); for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { if (fieldLongListNullItem == null) { fieldLongListNullOsList.addNull(); @@ -2484,7 +2481,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { - OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNotNullColKey); for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { if (fieldIntegerListNotNullItem == null) { fieldIntegerListNotNullOsList.addNull(); @@ -2496,7 +2493,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { - OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNullColKey); for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { if (fieldIntegerListNullItem == null) { fieldIntegerListNullOsList.addNull(); @@ -2508,7 +2505,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { - OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNotNullColKey); for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { if (fieldShortListNotNullItem == null) { fieldShortListNotNullOsList.addNull(); @@ -2520,7 +2517,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { - OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNullColKey); for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { if (fieldShortListNullItem == null) { fieldShortListNullOsList.addNull(); @@ -2532,7 +2529,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { - OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNotNullColKey); for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { if (fieldByteListNotNullItem == null) { fieldByteListNotNullOsList.addNull(); @@ -2544,7 +2541,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { - OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNullColKey); for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { if (fieldByteListNullItem == null) { fieldByteListNullOsList.addNull(); @@ -2556,7 +2553,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { - OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNotNullColKey); for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { if (fieldDoubleListNotNullItem == null) { fieldDoubleListNotNullOsList.addNull(); @@ -2568,7 +2565,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { - OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNullColKey); for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { if (fieldDoubleListNullItem == null) { fieldDoubleListNullOsList.addNull(); @@ -2580,7 +2577,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { - OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNotNullColKey); for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { if (fieldFloatListNotNullItem == null) { fieldFloatListNotNullOsList.addNull(); @@ -2592,7 +2589,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { - OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNullColKey); for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { if (fieldFloatListNullItem == null) { fieldFloatListNullOsList.addNull(); @@ -2604,7 +2601,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { - OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNotNullColKey); for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { if (fieldDateListNotNullItem == null) { fieldDateListNotNullOsList.addNull(); @@ -2616,7 +2613,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { - OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNullColKey); for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { if (fieldDateListNullItem == null) { fieldDateListNullOsList.addNull(); @@ -2625,7 +2622,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map objects, Map cache) { @@ -2638,91 +2635,91 @@ public static void insert(Realm realm, Iterator objects, M if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$fieldStringNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullColKey, colKey, realmGet$fieldStringNotNull, false); } String realmGet$fieldStringNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullColKey, colKey, realmGet$fieldStringNull, false); } Boolean realmGet$fieldBooleanNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, colKey, realmGet$fieldBooleanNotNull, false); } Boolean realmGet$fieldBooleanNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullColKey, colKey, realmGet$fieldBooleanNull, false); } byte[] realmGet$fieldBytesNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullColKey, colKey, realmGet$fieldBytesNotNull, false); } byte[] realmGet$fieldBytesNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullColKey, colKey, realmGet$fieldBytesNull, false); } Number realmGet$fieldByteNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullColKey, colKey, realmGet$fieldByteNotNull.longValue(), false); } Number realmGet$fieldByteNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullColKey, colKey, realmGet$fieldByteNull.longValue(), false); } Number realmGet$fieldShortNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullColKey, colKey, realmGet$fieldShortNotNull.longValue(), false); } Number realmGet$fieldShortNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullColKey, colKey, realmGet$fieldShortNull.longValue(), false); } Number realmGet$fieldIntegerNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, colKey, realmGet$fieldIntegerNotNull.longValue(), false); } Number realmGet$fieldIntegerNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullColKey, colKey, realmGet$fieldIntegerNull.longValue(), false); } Number realmGet$fieldLongNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullColKey, colKey, realmGet$fieldLongNotNull.longValue(), false); } Number realmGet$fieldLongNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullColKey, colKey, realmGet$fieldLongNull.longValue(), false); } Float realmGet$fieldFloatNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullColKey, colKey, realmGet$fieldFloatNotNull, false); } Float realmGet$fieldFloatNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullColKey, colKey, realmGet$fieldFloatNull, false); } Double realmGet$fieldDoubleNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, colKey, realmGet$fieldDoubleNotNull, false); } Double realmGet$fieldDoubleNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullColKey, colKey, realmGet$fieldDoubleNull, false); } java.util.Date realmGet$fieldDateNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullColKey, colKey, realmGet$fieldDateNotNull.getTime(), false); } java.util.Date realmGet$fieldDateNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullColKey, colKey, realmGet$fieldDateNull.getTime(), false); } some.test.NullTypes fieldObjectNullObj = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); @@ -2731,12 +2728,12 @@ public static void insert(Realm realm, Iterator objects, M if (cachefieldObjectNull == null) { cachefieldObjectNull = some_test_NullTypesRealmProxy.insert(realm, fieldObjectNullObj, cache); } - table.setLink(columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); + table.setLink(columnInfo.fieldObjectNullColKey, colKey, cachefieldObjectNull, false); } RealmList fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { - OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNotNullIndex); + OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNotNullColKey); for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { if (fieldStringListNotNullItem == null) { fieldStringListNotNullOsList.addNull(); @@ -2748,7 +2745,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { - OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNullColKey); for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { if (fieldStringListNullItem == null) { fieldStringListNullOsList.addNull(); @@ -2760,7 +2757,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { - OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNotNullColKey); for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { if (fieldBinaryListNotNullItem == null) { fieldBinaryListNotNullOsList.addNull(); @@ -2772,7 +2769,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { - OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNullColKey); for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { if (fieldBinaryListNullItem == null) { fieldBinaryListNullOsList.addNull(); @@ -2784,7 +2781,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { - OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNotNullColKey); for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { if (fieldBooleanListNotNullItem == null) { fieldBooleanListNotNullOsList.addNull(); @@ -2796,7 +2793,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { - OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNullColKey); for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { if (fieldBooleanListNullItem == null) { fieldBooleanListNullOsList.addNull(); @@ -2808,7 +2805,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { - OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNotNullColKey); for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { if (fieldLongListNotNullItem == null) { fieldLongListNotNullOsList.addNull(); @@ -2820,7 +2817,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { - OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNullColKey); for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { if (fieldLongListNullItem == null) { fieldLongListNullOsList.addNull(); @@ -2832,7 +2829,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { - OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNotNullColKey); for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { if (fieldIntegerListNotNullItem == null) { fieldIntegerListNotNullOsList.addNull(); @@ -2844,7 +2841,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { - OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNullColKey); for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { if (fieldIntegerListNullItem == null) { fieldIntegerListNullOsList.addNull(); @@ -2856,7 +2853,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { - OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNotNullColKey); for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { if (fieldShortListNotNullItem == null) { fieldShortListNotNullOsList.addNull(); @@ -2868,7 +2865,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { - OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNullColKey); for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { if (fieldShortListNullItem == null) { fieldShortListNullOsList.addNull(); @@ -2880,7 +2877,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { - OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNotNullColKey); for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { if (fieldByteListNotNullItem == null) { fieldByteListNotNullOsList.addNull(); @@ -2892,7 +2889,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { - OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNullColKey); for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { if (fieldByteListNullItem == null) { fieldByteListNullOsList.addNull(); @@ -2904,7 +2901,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { - OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNotNullColKey); for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { if (fieldDoubleListNotNullItem == null) { fieldDoubleListNotNullOsList.addNull(); @@ -2916,7 +2913,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { - OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNullColKey); for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { if (fieldDoubleListNullItem == null) { fieldDoubleListNullOsList.addNull(); @@ -2928,7 +2925,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { - OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNotNullColKey); for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { if (fieldFloatListNotNullItem == null) { fieldFloatListNotNullOsList.addNull(); @@ -2940,7 +2937,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { - OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNullColKey); for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { if (fieldFloatListNullItem == null) { fieldFloatListNullOsList.addNull(); @@ -2952,7 +2949,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { - OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNotNullColKey); for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { if (fieldDateListNotNullItem == null) { fieldDateListNotNullOsList.addNull(); @@ -2964,7 +2961,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { - OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNullColKey); for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { if (fieldDateListNullItem == null) { fieldDateListNullOsList.addNull(); @@ -2977,133 +2974,133 @@ public static void insert(Realm realm, Iterator objects, M } public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.NullTypes.class); long tableNativePtr = table.getNativePtr(); NullTypesColumnInfo columnInfo = (NullTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.NullTypes.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$fieldStringNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullColKey, colKey, realmGet$fieldStringNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullColKey, colKey, false); } String realmGet$fieldStringNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullColKey, colKey, realmGet$fieldStringNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullColKey, colKey, false); } Boolean realmGet$fieldBooleanNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, colKey, realmGet$fieldBooleanNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, colKey, false); } Boolean realmGet$fieldBooleanNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullColKey, colKey, realmGet$fieldBooleanNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullColKey, colKey, false); } byte[] realmGet$fieldBytesNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullColKey, colKey, realmGet$fieldBytesNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullColKey, colKey, false); } byte[] realmGet$fieldBytesNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullColKey, colKey, realmGet$fieldBytesNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullColKey, colKey, false); } Number realmGet$fieldByteNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullColKey, colKey, realmGet$fieldByteNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullColKey, colKey, false); } Number realmGet$fieldByteNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullColKey, colKey, realmGet$fieldByteNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullColKey, colKey, false); } Number realmGet$fieldShortNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullColKey, colKey, realmGet$fieldShortNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullColKey, colKey, false); } Number realmGet$fieldShortNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullColKey, colKey, realmGet$fieldShortNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullColKey, colKey, false); } Number realmGet$fieldIntegerNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, colKey, realmGet$fieldIntegerNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, colKey, false); } Number realmGet$fieldIntegerNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullColKey, colKey, realmGet$fieldIntegerNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullColKey, colKey, false); } Number realmGet$fieldLongNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullColKey, colKey, realmGet$fieldLongNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullColKey, colKey, false); } Number realmGet$fieldLongNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullColKey, colKey, realmGet$fieldLongNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullColKey, colKey, false); } Float realmGet$fieldFloatNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullColKey, colKey, realmGet$fieldFloatNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullColKey, colKey, false); } Float realmGet$fieldFloatNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullColKey, colKey, realmGet$fieldFloatNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullColKey, colKey, false); } Double realmGet$fieldDoubleNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, colKey, realmGet$fieldDoubleNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, colKey, false); } Double realmGet$fieldDoubleNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullColKey, colKey, realmGet$fieldDoubleNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullColKey, colKey, false); } java.util.Date realmGet$fieldDateNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullColKey, colKey, realmGet$fieldDateNotNull.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullColKey, colKey, false); } java.util.Date realmGet$fieldDateNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullColKey, colKey, realmGet$fieldDateNull.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullColKey, colKey, false); } some.test.NullTypes fieldObjectNullObj = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); @@ -3112,12 +3109,12 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { @@ -3131,7 +3128,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { @@ -3145,7 +3142,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { @@ -3159,7 +3156,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { @@ -3173,7 +3170,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { @@ -3187,7 +3184,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { @@ -3201,7 +3198,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { @@ -3215,7 +3212,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { @@ -3229,7 +3226,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { @@ -3243,7 +3240,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { @@ -3257,7 +3254,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { @@ -3271,7 +3268,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { @@ -3285,7 +3282,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { @@ -3299,7 +3296,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { @@ -3313,7 +3310,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { @@ -3327,7 +3324,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { @@ -3341,7 +3338,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { @@ -3355,7 +3352,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { @@ -3369,7 +3366,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { @@ -3383,7 +3380,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { @@ -3396,7 +3393,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map objects, Map cache) { @@ -3409,131 +3406,131 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$fieldStringNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, realmGet$fieldStringNotNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullColKey, colKey, realmGet$fieldStringNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullColKey, colKey, false); } String realmGet$fieldStringNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, realmGet$fieldStringNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullColKey, colKey, realmGet$fieldStringNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullColKey, colKey, false); } Boolean realmGet$fieldBooleanNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, realmGet$fieldBooleanNotNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, colKey, realmGet$fieldBooleanNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, colKey, false); } Boolean realmGet$fieldBooleanNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, realmGet$fieldBooleanNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullColKey, colKey, realmGet$fieldBooleanNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullColKey, colKey, false); } byte[] realmGet$fieldBytesNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, realmGet$fieldBytesNotNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullColKey, colKey, realmGet$fieldBytesNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullColKey, colKey, false); } byte[] realmGet$fieldBytesNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, realmGet$fieldBytesNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullColKey, colKey, realmGet$fieldBytesNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullColKey, colKey, false); } Number realmGet$fieldByteNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, realmGet$fieldByteNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullColKey, colKey, realmGet$fieldByteNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullColKey, colKey, false); } Number realmGet$fieldByteNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, realmGet$fieldByteNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullColKey, colKey, realmGet$fieldByteNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullColKey, colKey, false); } Number realmGet$fieldShortNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, realmGet$fieldShortNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullColKey, colKey, realmGet$fieldShortNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullColKey, colKey, false); } Number realmGet$fieldShortNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, realmGet$fieldShortNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullColKey, colKey, realmGet$fieldShortNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullColKey, colKey, false); } Number realmGet$fieldIntegerNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, realmGet$fieldIntegerNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, colKey, realmGet$fieldIntegerNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, colKey, false); } Number realmGet$fieldIntegerNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, realmGet$fieldIntegerNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullColKey, colKey, realmGet$fieldIntegerNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullColKey, colKey, false); } Number realmGet$fieldLongNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, realmGet$fieldLongNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullColKey, colKey, realmGet$fieldLongNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullColKey, colKey, false); } Number realmGet$fieldLongNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, realmGet$fieldLongNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullColKey, colKey, realmGet$fieldLongNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullColKey, colKey, false); } Float realmGet$fieldFloatNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, realmGet$fieldFloatNotNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullColKey, colKey, realmGet$fieldFloatNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullColKey, colKey, false); } Float realmGet$fieldFloatNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, realmGet$fieldFloatNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullColKey, colKey, realmGet$fieldFloatNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullColKey, colKey, false); } Double realmGet$fieldDoubleNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, realmGet$fieldDoubleNotNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, colKey, realmGet$fieldDoubleNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, colKey, false); } Double realmGet$fieldDoubleNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, realmGet$fieldDoubleNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullColKey, colKey, realmGet$fieldDoubleNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullColKey, colKey, false); } java.util.Date realmGet$fieldDateNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, realmGet$fieldDateNotNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullColKey, colKey, realmGet$fieldDateNotNull.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullColKey, colKey, false); } java.util.Date realmGet$fieldDateNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, realmGet$fieldDateNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullColKey, colKey, realmGet$fieldDateNull.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullColKey, colKey, false); } some.test.NullTypes fieldObjectNullObj = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); @@ -3542,12 +3539,12 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cachefieldObjectNull == null) { cachefieldObjectNull = some_test_NullTypesRealmProxy.insertOrUpdate(realm, fieldObjectNullObj, cache); } - Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex, cachefieldObjectNull, false); + Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullColKey, colKey, cachefieldObjectNull, false); } else { - Table.nativeNullifyLink(tableNativePtr, columnInfo.fieldObjectNullIndex, rowIndex); + Table.nativeNullifyLink(tableNativePtr, columnInfo.fieldObjectNullColKey, colKey); } - OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNotNullIndex); + OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNotNullColKey); fieldStringListNotNullOsList.removeAll(); RealmList fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { @@ -3561,7 +3558,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldStringListNullIndex); + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNullColKey); fieldStringListNullOsList.removeAll(); RealmList fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { @@ -3575,7 +3572,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNotNullIndex); + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNotNullColKey); fieldBinaryListNotNullOsList.removeAll(); RealmList fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { @@ -3589,7 +3586,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBinaryListNullIndex); + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNullColKey); fieldBinaryListNullOsList.removeAll(); RealmList fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { @@ -3603,7 +3600,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNotNullIndex); + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNotNullColKey); fieldBooleanListNotNullOsList.removeAll(); RealmList fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { @@ -3617,7 +3614,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldBooleanListNullIndex); + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNullColKey); fieldBooleanListNullOsList.removeAll(); RealmList fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { @@ -3631,7 +3628,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNotNullIndex); + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNotNullColKey); fieldLongListNotNullOsList.removeAll(); RealmList fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { @@ -3645,7 +3642,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldLongListNullIndex); + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNullColKey); fieldLongListNullOsList.removeAll(); RealmList fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { @@ -3659,7 +3656,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNotNullIndex); + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNotNullColKey); fieldIntegerListNotNullOsList.removeAll(); RealmList fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { @@ -3673,7 +3670,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldIntegerListNullIndex); + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNullColKey); fieldIntegerListNullOsList.removeAll(); RealmList fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { @@ -3687,7 +3684,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNotNullIndex); + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNotNullColKey); fieldShortListNotNullOsList.removeAll(); RealmList fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { @@ -3701,7 +3698,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldShortListNullIndex); + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNullColKey); fieldShortListNullOsList.removeAll(); RealmList fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { @@ -3715,7 +3712,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNotNullIndex); + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNotNullColKey); fieldByteListNotNullOsList.removeAll(); RealmList fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { @@ -3729,7 +3726,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldByteListNullIndex); + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNullColKey); fieldByteListNullOsList.removeAll(); RealmList fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { @@ -3743,7 +3740,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNotNullIndex); + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNotNullColKey); fieldDoubleListNotNullOsList.removeAll(); RealmList fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { @@ -3757,7 +3754,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDoubleListNullIndex); + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNullColKey); fieldDoubleListNullOsList.removeAll(); RealmList fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { @@ -3771,7 +3768,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNotNullIndex); + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNotNullColKey); fieldFloatListNotNullOsList.removeAll(); RealmList fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { @@ -3785,7 +3782,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldFloatListNullIndex); + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNullColKey); fieldFloatListNullOsList.removeAll(); RealmList fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { @@ -3799,7 +3796,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNotNullIndex); + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNotNullColKey); fieldDateListNotNullOsList.removeAll(); RealmList fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { @@ -3813,7 +3810,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(rowIndex), columnInfo.fieldDateListNullIndex); + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNullColKey); fieldDateListNullOsList.removeAll(); RealmList fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { @@ -4118,12 +4115,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long rowIndex = proxyState.getRow$realm().getIndex(); + long colKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (rowIndex ^ (rowIndex >>> 32)); + result = 31 * result + (int) (colKey ^ (colKey >>> 32)); return result; } @@ -4133,15 +4130,21 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; some_test_NullTypesRealmProxy aNullTypes = (some_test_NullTypesRealmProxy)o; - String path = proxyState.getRealm$realm().getPath(); - String otherPath = aNullTypes.proxyState.getRealm$realm().getPath(); + BaseRealm realm = proxyState.getRealm$realm(); + BaseRealm otherRealm = aNullTypes.proxyState.getRealm$realm(); + String path = realm.getPath(); + String otherPath = otherRealm.getPath(); if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + if (realm.isFrozen() != otherRealm.isFrozen()) return false; + if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID())) { + return false; + } String tableName = proxyState.getRow$realm().getTable().getName(); String otherTableName = aNullTypes.proxyState.getRow$realm().getTable().getName(); if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; - if (proxyState.getRow$realm().getIndex() != aNullTypes.proxyState.getRow$realm().getIndex()) return false; + if (proxyState.getRow$realm().getObjectKey() != aNullTypes.proxyState.getRow$realm().getObjectKey()) return false; return true; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java index e7e2e91a29..c86513d900 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java @@ -38,16 +38,14 @@ public class some_test_SimpleRealmProxy extends some.test.Simple implements RealmObjectProxy, some_test_SimpleRealmProxyInterface { static final class SimpleColumnInfo extends ColumnInfo { - long maxColumnIndexValue; - long nameIndex; - long ageIndex; + long nameColKey; + long ageColKey; SimpleColumnInfo(OsSchemaInfo schemaInfo) { super(2); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("Simple"); - this.nameIndex = addColumnDetails("name", "name", objectSchemaInfo); - this.ageIndex = addColumnDetails("age", "age", objectSchemaInfo); - this.maxColumnIndexValue = objectSchemaInfo.getMaxColumnIndex(); + this.nameColKey = addColumnDetails("name", "name", objectSchemaInfo); + this.ageColKey = addColumnDetails("age", "age", objectSchemaInfo); } SimpleColumnInfo(ColumnInfo src, boolean mutable) { @@ -64,9 +62,8 @@ protected final ColumnInfo copy(boolean mutable) { protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { final SimpleColumnInfo src = (SimpleColumnInfo) rawSrc; final SimpleColumnInfo dst = (SimpleColumnInfo) rawDst; - dst.nameIndex = src.nameIndex; - dst.ageIndex = src.ageIndex; - dst.maxColumnIndexValue = src.maxColumnIndexValue; + dst.nameColKey = src.nameColKey; + dst.ageColKey = src.ageColKey; } } @@ -97,7 +94,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @SuppressWarnings("cast") public String realmGet$name() { proxyState.getRealm$realm().checkIfValid(); - return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.nameIndex); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.nameColKey); } @Override @@ -108,26 +105,26 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } final Row row = proxyState.getRow$realm(); if (value == null) { - row.getTable().setNull(columnInfo.nameIndex, row.getIndex(), true); + row.getTable().setNull(columnInfo.nameColKey, row.getObjectKey(), true); return; } - row.getTable().setString(columnInfo.nameIndex, row.getIndex(), value, true); + row.getTable().setString(columnInfo.nameColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); if (value == null) { - proxyState.getRow$realm().setNull(columnInfo.nameIndex); + proxyState.getRow$realm().setNull(columnInfo.nameColKey); return; } - proxyState.getRow$realm().setString(columnInfo.nameIndex, value); + proxyState.getRow$realm().setString(columnInfo.nameColKey, value); } @Override @SuppressWarnings("cast") public int realmGet$age() { proxyState.getRealm$realm().checkIfValid(); - return (int) proxyState.getRow$realm().getLong(columnInfo.ageIndex); + return (int) proxyState.getRow$realm().getLong(columnInfo.ageColKey); } @Override @@ -137,12 +134,12 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } final Row row = proxyState.getRow$realm(); - row.getTable().setLong(columnInfo.ageIndex, row.getIndex(), value, true); + row.getTable().setLong(columnInfo.ageColKey, row.getObjectKey(), value, true); return; } proxyState.getRealm$realm().checkIfValid(); - proxyState.getRow$realm().setLong(columnInfo.ageIndex, value); + proxyState.getRow$realm().setLong(columnInfo.ageColKey, value); } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { @@ -225,7 +222,7 @@ public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader rea } private static some_test_SimpleRealmProxy newProxyInstance(BaseRealm realm, Row row) { - // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.Simple.class), false, Collections.emptyList()); io.realm.some_test_SimpleRealmProxy obj = new io.realm.some_test_SimpleRealmProxy(); @@ -234,7 +231,7 @@ private static some_test_SimpleRealmProxy newProxyInstance(BaseRealm realm, Row } public static some.test.Simple copyOrUpdate(Realm realm, SimpleColumnInfo columnInfo, some.test.Simple object, boolean update, Map cache, Set flags) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); if (otherRealm.threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); @@ -261,11 +258,11 @@ public static some.test.Simple copy(Realm realm, SimpleColumnInfo columnInfo, so some_test_SimpleRealmProxyInterface realmObjectSource = (some_test_SimpleRealmProxyInterface) newObject; Table table = realm.getTable(some.test.Simple.class); - OsObjectBuilder builder = new OsObjectBuilder(table, columnInfo.maxColumnIndexValue, flags); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.nameIndex, realmObjectSource.realmGet$name()); - builder.addInteger(columnInfo.ageIndex, realmObjectSource.realmGet$age()); + builder.addString(columnInfo.nameColKey, realmObjectSource.realmGet$name()); + builder.addInteger(columnInfo.ageColKey, realmObjectSource.realmGet$age()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. @@ -277,20 +274,20 @@ public static some.test.Simple copy(Realm realm, SimpleColumnInfo columnInfo, so } public static long insert(Realm realm, some.test.Simple object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.Simple.class); long tableNativePtr = table.getNativePtr(); SimpleColumnInfo columnInfo = (SimpleColumnInfo) realm.getSchema().getColumnInfo(some.test.Simple.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$name = ((some_test_SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, colKey, realmGet$name, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); - return rowIndex; + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, colKey, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); + return colKey; } public static void insert(Realm realm, Iterator objects, Map cache) { @@ -303,37 +300,37 @@ public static void insert(Realm realm, Iterator objects, M if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$name = ((some_test_SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, colKey, realmGet$name, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, colKey, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); } } public static long insertOrUpdate(Realm realm, some.test.Simple object, Map cache) { - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex(); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); } Table table = realm.getTable(some.test.Simple.class); long tableNativePtr = table.getNativePtr(); SimpleColumnInfo columnInfo = (SimpleColumnInfo) realm.getSchema().getColumnInfo(some.test.Simple.class); - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$name = ((some_test_SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, colKey, realmGet$name, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.nameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.nameColKey, colKey, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); - return rowIndex; + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, colKey, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); + return colKey; } public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { @@ -346,19 +343,19 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cache.containsKey(object)) { continue; } - if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getIndex()); + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long rowIndex = OsObject.createRow(table); - cache.put(object, rowIndex); + long colKey = OsObject.createRow(table); + cache.put(object, colKey); String realmGet$name = ((some_test_SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameIndex, rowIndex, realmGet$name, false); + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, colKey, realmGet$name, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.nameIndex, rowIndex, false); + Table.nativeSetNull(tableNativePtr, columnInfo.nameColKey, colKey, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageIndex, rowIndex, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, colKey, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); } } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 5ba2270ef9..4844a6c295 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -64,7 +64,7 @@ android { abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*')) } else { // armeabi is not supported anymore. - abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a', 'mips' + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' } } } @@ -210,13 +210,16 @@ dependencies { api "io.realm:realm-annotations:${version}" implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.getkeepsafe.relinker:relinker:1.3.0' + implementation('io.reactivex.rxjava2:rxandroid:2.1.1') { + exclude group: 'io.reactivex.rxjava2', module: 'rxjava' + } kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 - objectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' + objectServerImplementation 'com.squareup.okhttp3:okhttp:3.10.0' kaptAndroidTest project(':realm-annotations-processor') - androidTestImplementation fileTree(dir: 'testLibs', include: ['*.jar']) androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' + androidTestImplementation 'io.reactivex.rxjava2:rxandroid:2.1.1' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:rules:1.0.2' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' @@ -477,6 +480,11 @@ task downloadCore() { } def shouldDownloadCore = { + if (coreSourcePath) { + println "shouldDownloadCore: skipping, using local Core at: ${coreSourcePath}." + return false + } + if (!project.coreArchiveFile.exists()) { return true } @@ -484,7 +492,7 @@ task downloadCore() { return true } if (!isHashCheckingEnabled()) { - println "Skipping hash check(empty \'coreSha256Hash\')." + println "shouldDownloadCore: skipping hash check(empty \'coreSha256Hash\')." return false } diff --git a/realm/realm-library/proguard-rules-consumer-common.pro b/realm/realm-library/proguard-rules-consumer-common.pro index 010e4b2bcd..6a99bdbc66 100644 --- a/realm/realm-library/proguard-rules-consumer-common.pro +++ b/realm/realm-library/proguard-rules-consumer-common.pro @@ -11,6 +11,8 @@ -dontwarn javax.** -dontwarn io.realm.** +-dontwarn io.reactivex.android.** + -keep class io.realm.RealmCollection -keep class io.realm.OrderedRealmCollection -keepclasseswithmembernames class io.realm.** { diff --git a/realm/realm-library/proguard-rules-consumer-objectServer.pro b/realm/realm-library/proguard-rules-consumer-objectServer.pro index d4b249abb8..30502c0f9b 100644 --- a/realm/realm-library/proguard-rules-consumer-objectServer.pro +++ b/realm/realm-library/proguard-rules-consumer-objectServer.pro @@ -5,3 +5,6 @@ -dontnote com.android.org.conscrypt.SSLParametersImpl -dontnote org.apache.harmony.xnet.provider.jsse.SSLParametersImpl -dontnote sun.security.ssl.SSLContextImpl + +# See https://github.com/square/okhttp/issues/3922 +-dontwarn okhttp3.internal.platform.* \ No newline at end of file diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index e9bb60d73a..f7c35d6a71 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -10,7 +10,7 @@ EW6iZo7komsft%Mn6SB)Y8KQj_tO)wC6bjj%XYAj0t delta 43 ocmZorXi$)7U}gY=1||p{qU++s1Y~an%4|+yo5Q)efb9$u0GB@qZU6uP diff --git a/realm/realm-library/src/androidTest/assets/0841_annotationtypes.realm b/realm/realm-library/src/androidTest/assets/0841_annotationtypes.realm index c605500aab3df986097bcd2fcbefee8ee5f7407e..22f6b7a211d0aa9fb55c3e0d89fd2be429abf3df 100644 GIT binary patch delta 243 zcmZorXi$(SU}gY=6-*F1MAyZM6DYP(Vg*yZBM_uAFfejJC1HFGB)%&H11kg50VV~e z3rr5E;!GbHAAm(b`he<~PcU>a{s4-A_(1CzL9`%{0it1ikU9xw1!fIq1|P5lD+2?I z0Fwce2h#**kU}t@L4cuxp@0X(2b#xX4{kMg<4v0OkZ{cA)zVm>D-8 IWGdwW0Goy({{R30 delta 42 ocmZorXi$(~fPxiF5JrfuixV?51LH=C6-=9_Fz4`0Uch_>0D`s%Pyhe` diff --git a/realm/realm-library/src/androidTest/assets/0841_pk_migration.realm b/realm/realm-library/src/androidTest/assets/0841_pk_migration.realm index 2b55bc51aacb9a39681a88f6bb78fea105b76d4b..e0f2938792afeeb3eea0ec060287475ae8b9228b 100644 GIT binary patch delta 788 zcmZvap>Kmg6viKvgX1{Tn>&d-vjmA+=&~mJ112gbwgg#O*UHK!GnGgrOAQi9rY0*p zS6Nxt-J|0g$Uqz)-|xHk4&EwVr7{=LUg~dsxqo`}M*yF)X3L4b-T|!Axk9L?0CwT* z8NTWC);XfyphjMimTxuk5urinqZULOjr@7QpGboK(rj=b&%MCh?H9=0%=lPf<}QrJ z0>=S=aJR-s{@X2#M!xoN|K;^W2vlBU0Mv!IHyQ&8?Kc$Dv+2aL?-x-1tQU}eW;8Zb4EWq%8y^d7{H@W*N9_;29ayr8zjYmBfU?E}v=*8D z>YSMh3Nj1UK^4rsV<7o6U+{wOdCqVabl$@CPCww=e@Rm9sT>xm>XGZd#dyBe&92 RWMU&cAfQ4E?cG#N#=n)ci{JnN delta 51 xcmZp0XmF5VfC3Ex2qQ$-#fh1jfpMe60j|vl1+-WvGYQyjJ|bYlKRJN61pv^53Qhn3 diff --git a/realm/realm-library/src/androidTest/assets/asset_file.realm b/realm/realm-library/src/androidTest/assets/asset_file.realm index c5f75da4f85ecbbc31bb96b05f07248775274df9..4e8468916e9bf2d5925d1f308f4029867c4389fb 100644 GIT binary patch delta 348 zcmY+9A#cMl5QWc)eL8obH)dE^GAYtXNF{g6q{4y$!xmJUG*Vc$z{0Gku&{7bNnl~% zV1LHo;B42Kw396B{JnSYT-(|j%Mk0B?&rJeL1_@5W=(HXUn>A{lvh#cp8)hhZs{yL z`6Vy%TY@6>k7teCQ+Vn91|y#*|A4Wh0!y?w!#Td-2J5IuJOjK0|1=KpfjAatLA0Qj ze*Ok~fe41%6x8=#pO5rd7kWXbcWQCR?llPgk~fE}$v-5k5pJ=?HO(||=fB?b1{^RH o@i;KhqJwgH<+=CYSJk6Ufj($pfV8lVH@s&J3Q8?bOP&mW0du!GQ~&?~ delta 50 wcmZp0XmF5l5MTg@YL!zL6 znw{VC`T8tM!&aDv_soCwxf>1!#|NEu5~iKF_a;uFmxu4(r%9*Re%otB!>F}=u@ij> zzjo7q)Of6O2$3u}r{X2x{^@ZVe(FZm0qVCHOHfmc7wdbMXrWFM;l?DG2G>Ca+~XiywfYwkw-?7n zp5DWwINr;7P*)zAG9G+FreX3uDn5}Hwi8#+?qw4vim{hv3|A@>6?W<164B z%BHT(JYu3L9B0P=30SgQ>{4|Fe@4B*x- zn|pUcLtB4i<&naETO;!g+s$*%5F+yd?=3I*CyB6pjyw7AD35Z5a1*c} zExyQbim~mbYrbNUfN~VFeHu!%gLTw?d>D4C9JM;~WDR-A2QuO(eox1Lcw}**9`ZBC zPpg4+L>F>KPXZH`|0}RLGqCk`5`OX0|2p#4ph!sSy|P%QaxEcnM%qvVn)?S)*uwHg z7W_jQQr?LV(Aas3j_HB|3z_i^k9p1uo`{v$3Xn!dL0{!6*Z5D^-vkBKPHg02d@C^a qX^K9Kpi{?wYWuS+d&ccDgy)K1eP6)3``Jl4yd>* z0|Rph%M6wkED&)ZpQ(an1CYnSF!=(@1J(}K8LX_6V_CyD^RVq91F#;Doj^0#Js2C9Zm>tN2e2Jr31E#6oy1|Vd86P3_Dup3jGMOz`tVH- H;G6>hM#3j; diff --git a/realm/realm-library/src/androidTest/assets/default-nullable-primarykey.realm b/realm/realm-library/src/androidTest/assets/default-nullable-primarykey.realm index 0611f90efdfbb0c1c8eba7452c1c882fbce1dfd0..11811738ad2d3a75686620d06e608ca2d0e14395 100644 GIT binary patch delta 433 zcmZ8dElkBQ5We-=Yr(7}6HPFaGY}+l5J)DUAY>8+lPGwO$>)c75;;p|kdseNPCf+5 z6nS!zc0Z56HNEz``|j)Y@-UChorb|`Ii5EsSCobzj{t2TGAl0or}8F=@`fwrZqqorYg5%oOua^pr<|FoPV^dl(|3h>;V0zXKN2~?&{D$ Zq`Vk(q!-!;0f`!ZRei~0Rqf=x`~d)hQJ??- delta 255 zcmZp0Xi$&{U}pe>15i3d*TspMnSpVmgb0fxP=?VF2-tuO5S3AEV7YKlO5WXt| f1J?wGAB>C&3~Ul?25hVhtPBen88-(qFXaRP|EL== delta 88 zcmZorXizXPU}gY=8%z*7MAyZM3CLyy0$w1MoRe5w93PNcRGgm&RKdK_pny>TBg4U7RSn+w<`a86EOE&u?5lo4P6 diff --git a/realm/realm-library/src/androidTest/assets/readonly.realm b/realm/realm-library/src/androidTest/assets/readonly.realm index d2d3637a8f60b3cd0cfb1bc5783be3c02a824274..7b2dc233555be5ffa4902ffbbe177abc1b54bfb2 100644 GIT binary patch delta 519 zcmY+AEl&eM5Qg8G-9373O2C~wLxLeW97v#$$`cI+63&pE>mtxSi9j@|K~`2EO85gO zktY!dB!7a0*^fdPb9rZHo@e)(yveg2179|NCkLa`GzGn6&$Rg9^7^m1l&+^UH_ZBJ%k0Y@N^^K)r zqs3Dzg-@4-I*e4~sK=sreMfDSB{#QNsdg5Je-CTzufBAB1E^L;JLzq5Mm;J9AuBmQ z^W!1QrTQ64MR}v=dZ|+wSRMXF+&7lxEvnXqz@=szu&MEY9@Xgjf#?=QV9Lo~4uT`5 zywhJAW*~x!{|S@TkX)9DH@HR+Sb0HV#-=hu5j-h+ Iz0>#oAHXPAL;wH) delta 382 zcmYL_y-LJT5QWdo^=`sS3fV;if~K@k@u#s=S;fZAem81Fjhagm1S|6hD*@3irG>@H zHkMmjTJ9tG2$oKMz-fl>&Yd%7Ci61SJ(@ukpIqmYwcfsE0Uz!=J=*fNCvgDKgoad5 zq8jEAeUZY;al`5xhNk3K|L1^=0Q)vb{c}I{HPAs9uMz1omHZG^qhf(BhIAR|y h235G}FmZ`o;g+N1K!U2mZ6|+Ilku~@ZjTGSPJiXpLnZ(K diff --git a/realm/realm-library/src/androidTest/assets/rename-and-add-indexed.realm b/realm/realm-library/src/androidTest/assets/rename-and-add-indexed.realm index 851be9725a888bbe239b3c31b68d52bb1bcfd079..e9c4e4bfee66208f4c4da0f3aea4c5c1b0b2c27a 100644 GIT binary patch delta 137 zcmZorXi$*gU}XS<0w^7#>*B=5$-uZ#LVz{N5eNi<(jYpOfq_v2qDTP52Qrx(m{zb% z05V|w1&j?01zZq5Gf>_cVm4Ubm4Sgvf?);=qk;o#0BZs(Cj%>k0qbT%)-o;tpn delta 42 ocmZorXi$*gU}b;+76=uh>*B=5#=x*qLV$Jibk2qQ$-#fgE9fpMdR0rTeV%$_`x4>0Eg0B>>!6#xJL diff --git a/realm/realm-library/src/androidTest/assets/string-only-pre-null-0.82.2.realm b/realm/realm-library/src/androidTest/assets/string-only-pre-null-0.82.2.realm index 9995d001ea4cb1865ede68cd05ac5b0c551c64f4..d0df943037e6467bb28b11074cfdccb670508f0c 100644 GIT binary patch delta 158 zcmZorXi$(?!NdRt25b;IMAyZMiIahGBTz=o5eR_7j38PN$NVSVpL#Y U6JS$d<78lExWU8-G;k{?08+ggm;e9( delta 43 ocmZorXi$(?!NdRt9~dEYh^~th6Hsg;P-b%o+ZxWz32alC0jm=T 0) { for (int i = 0; i < objects; i++) { AllJavaTypes obj = realm.createObject(AllJavaTypes.class, i); diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java index 02ef67f017..9022d49b87 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java @@ -31,6 +31,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.fail; +import static org.junit.Assert.assertFalse; @RunWith(AndroidJUnit4.class) @@ -66,36 +67,36 @@ public void copyColumnInfoFrom_checkIndex() { // Checks precondition. assertNotSame(sourceColumnInfo, targetColumnInfo); - assertNotSame(sourceColumnInfo.getIndicesMap(), targetColumnInfo.getIndicesMap()); - - sourceColumnInfo.nameIndex = 1; - sourceColumnInfo.ageIndex = 2; - sourceColumnInfo.heightIndex = 3; - sourceColumnInfo.weightIndex = 4; - sourceColumnInfo.hasTailIndex = 5; - sourceColumnInfo.birthdayIndex = 6; - sourceColumnInfo.ownerIndex = 7; - sourceColumnInfo.scaredOfDogIndex = 8; - - targetColumnInfo.nameIndex = 0; - targetColumnInfo.ageIndex = 0; - targetColumnInfo.heightIndex = 0; - targetColumnInfo.weightIndex = 0; - targetColumnInfo.hasTailIndex = 0; - targetColumnInfo.birthdayIndex = 0; - targetColumnInfo.ownerIndex = 0; - targetColumnInfo.scaredOfDogIndex = 0; + assertFalse(sourceColumnInfo.getColumnKeysMap().equals(targetColumnInfo.getColumnKeysMap())); + + sourceColumnInfo.nameColKey = 1; + sourceColumnInfo.ageColKey = 2; + sourceColumnInfo.heightColKey = 3; + sourceColumnInfo.weightColKey = 4; + sourceColumnInfo.hasTailColKey = 5; + sourceColumnInfo.birthdayColKey = 6; + sourceColumnInfo.ownerColKey = 7; + sourceColumnInfo.scaredOfDogColKey = 8; + + targetColumnInfo.nameColKey = 0; + targetColumnInfo.ageColKey = 0; + targetColumnInfo.heightColKey = 0; + targetColumnInfo.weightColKey = 0; + targetColumnInfo.hasTailColKey = 0; + targetColumnInfo.birthdayColKey = 0; + targetColumnInfo.ownerColKey = 0; + targetColumnInfo.scaredOfDogColKey = 0; targetColumnInfo.copyFrom(sourceColumnInfo); - assertEquals(sourceColumnInfo.nameIndex, targetColumnInfo.nameIndex); - assertEquals(sourceColumnInfo.ageIndex, targetColumnInfo.ageIndex); - assertEquals(sourceColumnInfo.heightIndex, targetColumnInfo.heightIndex); - assertEquals(sourceColumnInfo.weightIndex, targetColumnInfo.weightIndex); - assertEquals(sourceColumnInfo.hasTailIndex, targetColumnInfo.hasTailIndex); - assertEquals(sourceColumnInfo.birthdayIndex, targetColumnInfo.birthdayIndex); - assertEquals(sourceColumnInfo.ownerIndex, targetColumnInfo.ownerIndex); - assertEquals(sourceColumnInfo.scaredOfDogIndex, targetColumnInfo.scaredOfDogIndex); + assertEquals(sourceColumnInfo.nameColKey, targetColumnInfo.nameColKey); + assertEquals(sourceColumnInfo.ageColKey, targetColumnInfo.ageColKey); + assertEquals(sourceColumnInfo.heightColKey, targetColumnInfo.heightColKey); + assertEquals(sourceColumnInfo.weightColKey, targetColumnInfo.weightColKey); + assertEquals(sourceColumnInfo.hasTailColKey, targetColumnInfo.hasTailColKey); + assertEquals(sourceColumnInfo.birthdayColKey, targetColumnInfo.birthdayColKey); + assertEquals(sourceColumnInfo.ownerColKey, targetColumnInfo.ownerColKey); + assertEquals(sourceColumnInfo.scaredOfDogColKey, targetColumnInfo.scaredOfDogColKey); } @Test @@ -103,48 +104,48 @@ public void copy_differentInstanceSameValues() { final io_realm_entities_CatRealmProxy.CatColumnInfo columnInfo = (io_realm_entities_CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); - columnInfo.nameIndex = 1; - columnInfo.ageIndex = 2; - columnInfo.heightIndex = 3; - columnInfo.weightIndex = 4; - columnInfo.hasTailIndex = 5; - columnInfo.birthdayIndex = 6; - columnInfo.ownerIndex = 7; - columnInfo.scaredOfDogIndex = 8; + columnInfo.nameColKey = 1; + columnInfo.ageColKey = 2; + columnInfo.heightColKey = 3; + columnInfo.weightColKey = 4; + columnInfo.hasTailColKey = 5; + columnInfo.birthdayColKey = 6; + columnInfo.ownerColKey = 7; + columnInfo.scaredOfDogColKey = 8; io_realm_entities_CatRealmProxy.CatColumnInfo copy = (io_realm_entities_CatRealmProxy.CatColumnInfo) columnInfo.copy(true); // verify that the copy is identical assertNotSame(columnInfo, copy); - assertEquals(columnInfo.getIndicesMap(), copy.getIndicesMap()); - assertEquals(columnInfo.nameIndex, copy.nameIndex); - assertEquals(columnInfo.ageIndex, copy.ageIndex); - assertEquals(columnInfo.heightIndex, copy.heightIndex); - assertEquals(columnInfo.weightIndex, copy.weightIndex); - assertEquals(columnInfo.hasTailIndex, copy.hasTailIndex); - assertEquals(columnInfo.birthdayIndex, copy.birthdayIndex); - assertEquals(columnInfo.ownerIndex, copy.ownerIndex); - assertEquals(columnInfo.scaredOfDogIndex, copy.scaredOfDogIndex); + assertEquals(columnInfo.getColumnKeysMap(), copy.getColumnKeysMap()); + assertEquals(columnInfo.nameColKey, copy.nameColKey); + assertEquals(columnInfo.ageColKey, copy.ageColKey); + assertEquals(columnInfo.heightColKey, copy.heightColKey); + assertEquals(columnInfo.weightColKey, copy.weightColKey); + assertEquals(columnInfo.hasTailColKey, copy.hasTailColKey); + assertEquals(columnInfo.birthdayColKey, copy.birthdayColKey); + assertEquals(columnInfo.ownerColKey, copy.ownerColKey); + assertEquals(columnInfo.scaredOfDogColKey, copy.scaredOfDogColKey); // Modify original object - columnInfo.nameIndex = 0; - columnInfo.ageIndex = 0; - columnInfo.heightIndex = 0; - columnInfo.weightIndex = 0; - columnInfo.hasTailIndex = 0; - columnInfo.birthdayIndex = 0; - columnInfo.ownerIndex = 0; - columnInfo.scaredOfDogIndex = 0; + columnInfo.nameColKey = 0; + columnInfo.ageColKey = 0; + columnInfo.heightColKey = 0; + columnInfo.weightColKey = 0; + columnInfo.hasTailColKey = 0; + columnInfo.birthdayColKey = 0; + columnInfo.ownerColKey = 0; + columnInfo.scaredOfDogColKey = 0; // the copy should not change - assertEquals(1, copy.nameIndex); - assertEquals(2, copy.ageIndex); - assertEquals(3, copy.heightIndex); - assertEquals(4, copy.weightIndex); - assertEquals(5, copy.hasTailIndex); - assertEquals(6, copy.birthdayIndex); - assertEquals(7, copy.ownerIndex); - assertEquals(8, copy.scaredOfDogIndex); + assertEquals(1, copy.nameColKey); + assertEquals(2, copy.ageColKey); + assertEquals(3, copy.heightColKey); + assertEquals(4, copy.weightColKey); + assertEquals(5, copy.hasTailColKey); + assertEquals(6, copy.birthdayColKey); + assertEquals(7, copy.ownerColKey); + assertEquals(8, copy.scaredOfDogColKey); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java b/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java new file mode 100644 index 0000000000..ee2c0786ab --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java @@ -0,0 +1,754 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +import javax.annotation.Nullable; + +import io.realm.entities.AllJavaTypes; +import io.realm.entities.AllTypes; +import io.realm.entities.Dog; +import io.realm.rule.RunInLooperThread; +import io.realm.rule.RunTestInLooperThread; +import io.realm.rule.TestRealmConfigurationFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Class testing that the frozen objects feature works correctly. + */ +@RunWith(AndroidJUnit4.class) +public class FrozenObjectsTests { + + private static final int DATA_SIZE = 10; + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final RunInLooperThread looperThread = new RunInLooperThread(); + + private RealmConfiguration realmConfig; + private Realm realm; + private Realm frozenRealm; + + @Before + public void setUp() { + realmConfig = configFactory.createConfiguration(); + realm = Realm.getInstance(realmConfig); + frozenRealm = realm.freeze(); + } + + @After + public void tearDown() { + realm.close(); // This also closes the frozen Realm + } + + @Test + public void deleteFrozenRealm() { + RealmConfiguration config = configFactory.createConfigurationBuilder().name("deletable.realm").build(); + Realm realm = Realm.getInstance(config); + frozenRealm = realm.freeze(); + try { + Realm.deleteRealm(config); + } catch (IllegalStateException ignore) { + } + realm.close(); + assertTrue(Realm.deleteRealm(config)); + } + + @Test + public void freezeRealm() { + assertFalse(realm.isFrozen()); + Realm frozenRealm = realm.freeze(); + assertEquals(realm.getPath(), frozenRealm.getPath()); + assertTrue(frozenRealm.isFrozen()); + frozenRealm.close(); + } + + @Test + public void freezeDynamicRealm() { + DynamicRealm dynamicRealm = DynamicRealm.getInstance(realmConfig); + DynamicRealm frozenDynamicRealm = dynamicRealm.freeze(); + assertEquals(dynamicRealm.getPath(), frozenDynamicRealm.getPath()); + assertTrue(frozenRealm.isFrozen()); + dynamicRealm.close(); + assertFalse(frozenDynamicRealm.isClosed()); + frozenDynamicRealm.close(); + } + + @Test + public void frozenRealmsCannotStartTransactions() { + try { + frozenRealm.beginTransaction(); + fail(); + } catch (IllegalStateException ignore) { + } + } + + @Test + @RunTestInLooperThread + public void addingRealmChangeListenerThrows() { + try { + frozenRealm.addChangeListener(new RealmChangeListener() { + @Override + public void onChange(Realm realm) { + } + }); + fail(); + } catch (IllegalStateException ignore) { + looperThread.testComplete(); + } + } + + @Test + @RunTestInLooperThread + public void addingResultsChangeListenerThrows() { + RealmResults results = frozenRealm.where(AllTypes.class).findAll(); + try { + results.addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmResults allTypes, OrderedCollectionChangeSet changeSet) { + } + }); + fail(); + } catch (IllegalStateException ignore) { + looperThread.testComplete(); + } + } + + @Test + @RunTestInLooperThread + public void addingListChangeListenerThrows() { + realm.executeTransaction(r -> { + r.createObject(AllTypes.class); + }); + + Realm frozenRealm = realm.freeze(); + AllTypes obj = frozenRealm.where(AllTypes.class).findFirst(); + try { + obj.getColumnStringList().addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList strings, OrderedCollectionChangeSet changeSet) { + } + }); + fail(); + } catch (IllegalStateException ignore) { + } + + try { + obj.getColumnRealmList().addChangeListener(new OrderedRealmCollectionChangeListener>() { + @Override + public void onChange(RealmList dogs, OrderedCollectionChangeSet changeSet) { + } + }); + fail(); + } catch (IllegalStateException ignore) { + } + frozenRealm.close(); + looperThread.testComplete(); + } + + @Test + @RunTestInLooperThread + public void addingObjectChangeListenerThrows() { + realm.executeTransaction(r -> { + r.createObject(AllTypes.class); + }); + Realm frozenRealm = realm.freeze(); + AllTypes obj = frozenRealm.where(AllTypes.class).findFirst(); + try { + obj.addChangeListener(new RealmObjectChangeListener() { + @Override + public void onChange(RealmModel realmModel, @Nullable ObjectChangeSet changeSet) { + + } + }); + fail(); + } catch (IllegalStateException ignore) { + frozenRealm.close(); + looperThread.testComplete(); + } + } + + @Test + public void removingChangeListeners() { + frozenRealm.removeAllChangeListeners(); + frozenRealm.removeChangeListener(new RealmChangeListener() { + @Override + public void onChange(Realm realm) { + } + }); + } + + @Test + public void refreshThrows() { + try { + frozenRealm.refresh(); + fail(); + } catch (IllegalStateException ignore) { + } + } + + @Test + public void writeToFrozenObjectThrows() { + realm.beginTransaction(); + try { + frozenRealm.createObject(AllTypes.class); + fail(); + } catch (IllegalStateException ignore) { + } finally { + realm.cancelTransaction(); + } + + realm.beginTransaction(); + try { + frozenRealm.insert(new AllTypes()); + fail(); + } catch (IllegalStateException ignore) { + } finally { + realm.cancelTransaction(); + } + + realm.beginTransaction(); + try { + frozenRealm.copyToRealm(new AllTypes()); + fail(); + } catch (IllegalStateException ignore) { + } finally { + realm.cancelTransaction(); + } + + realm.executeTransaction(r -> { + r.createObject(AllTypes.class); + }); + Realm frozenRealm = realm.freeze(); + AllTypes obj = frozenRealm.where(AllTypes.class).findFirst(); + realm.beginTransaction(); + try { + obj.setColumnString("Foo"); + fail(); + } catch (IllegalStateException ignore) { + } finally { + realm.cancelTransaction(); + } + + try { + frozenRealm.executeTransactionAsync(r -> { /* Do nothing */ }); + } catch (IllegalStateException ignore) { + } finally { + frozenRealm.close(); + } + } + + @Test + public void freezingPinsRealmVersion() { + assertTrue(frozenRealm.isEmpty()); + assertTrue(realm.isEmpty()); + + realm.executeTransaction(r -> { + r.createObject(AllTypes.class); + }); + + assertTrue(frozenRealm.isEmpty()); + assertFalse(realm.isEmpty()); + } + + @Test + public void readFrozenRealmAcrossThreads() throws InterruptedException { + Thread t = new Thread(() -> { + assertTrue(frozenRealm.isEmpty()); + assertTrue(frozenRealm.isFrozen()); + }); + t.start(); + t.join(); + } + + @Test + public void queryFrozenRealmAcrossThreads() throws InterruptedException { + final Realm frozenRealm = createDataForFrozenRealm(DATA_SIZE); + Thread t = new Thread(() -> { + RealmResults results = frozenRealm.where(AllTypes.class).findAll(); + assertEquals(DATA_SIZE, results.size()); + }); + t.start(); + t.join(); + } + + @Test + public void canReadFrozenResultsAcrossThreads() throws InterruptedException { + Realm frozenRealm = createDataForFrozenRealm(DATA_SIZE); + RealmResults results = frozenRealm.where(AllTypes.class).findAll(); + Thread t = new Thread(() -> { + assertEquals(DATA_SIZE, results.size()); + assertTrue(results.isFrozen()); + }); + t.start(); + t.join(); + } + + @Test + public void canReadFrozenListsAcrossThreads() throws InterruptedException { + Realm frozenRealm = createDataForFrozenRealm(DATA_SIZE); + RealmList list = frozenRealm.where(AllTypes.class).findFirst().getColumnRealmList(); + Thread t = new Thread(() -> { + assertEquals(5, list.size()); + assertTrue(list.isFrozen()); + }); + t.start(); + t.join(); + } + + @Test + public void canReadFrozenObjectsAcrossThreads() throws InterruptedException { + Realm frozenRealm = createDataForFrozenRealm(DATA_SIZE); + AllTypes obj = frozenRealm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst(); + Thread t = new Thread(() -> { + assertEquals(0, obj.getColumnLong()); + assertEquals("String 0", obj.getColumnString()); + assertTrue(obj.isFrozen()); + }); + t.start(); + t.join(); + } + + @Test + public void frozenObjectsReturnsFrozenRealms() { + Realm frozenRealm = createDataForFrozenRealm(DATA_SIZE); + RealmResults results = frozenRealm.where(AllTypes.class).findAll(); + AllTypes obj = results.first(); + RealmList list = obj.getColumnRealmList(); + + assertTrue(results.getRealm().isFrozen()); + assertTrue(obj.getRealm().isFrozen()); + assertTrue(list.getRealm().isFrozen()); + } + + @Test + public void freezeResults() throws InterruptedException { + Realm realm = createDataForLiveRealm(DATA_SIZE); + RealmResults results = realm.where(AllTypes.class).findAll(); + RealmResults frozenResults = results.freeze(); + assertEquals(DATA_SIZE, frozenResults.size()); + assertTrue(frozenResults.isFrozen()); + assertTrue(frozenResults.isValid()); + assertTrue(frozenResults.isLoaded()); + + Thread t = new Thread(() -> { + assertEquals(DATA_SIZE, frozenResults.size()); + assertTrue(frozenResults.isFrozen()); + assertEquals(1, frozenResults.where().equalTo(AllTypes.FIELD_LONG, 1).findAll().size()); + }); + t.start(); + t.join(); + } + + @Test + public void freezeDynamicResults() throws InterruptedException { + Realm realm = createDataForLiveRealm(DATA_SIZE); + DynamicRealm dynRealm = DynamicRealm.getInstance(realm.getConfiguration()); + RealmResults results = dynRealm.where(AllTypes.CLASS_NAME).findAll(); + RealmResults frozenResults = results.freeze(); + assertEquals(DATA_SIZE, frozenResults.size()); + assertTrue(frozenResults.isFrozen()); + assertTrue(frozenResults.isValid()); + assertTrue(frozenResults.isLoaded()); + + Thread t = new Thread(() -> { + assertEquals(DATA_SIZE, frozenResults.size()); + assertTrue(frozenResults.isFrozen()); + assertEquals(1, frozenResults.where().equalTo(AllTypes.FIELD_LONG, 1).findAll().size()); + }); + t.start(); + t.join(); + dynRealm.close(); + } + + @Test + public void freezeSnapshot() { + Realm realm = createDataForLiveRealm(DATA_SIZE); + RealmResults results = realm.where(AllTypes.class).findAll(); + OrderedRealmCollectionSnapshot snapshot = results.createSnapshot(); + try { + snapshot.freeze(); + fail(); + } catch(UnsupportedOperationException ignored) { + } + } + + @Test + public void freezeDynamicSnapshot() { + Realm realm = createDataForLiveRealm(DATA_SIZE); + DynamicRealm dynRealm = DynamicRealm.getInstance(realm.getConfiguration()); + RealmResults results = dynRealm.where(AllTypes.CLASS_NAME).findAll(); + OrderedRealmCollectionSnapshot snapshot = results.createSnapshot(); + try { + snapshot.freeze(); + fail(); + } catch(UnsupportedOperationException ignored) { + } finally { + dynRealm.close(); + } + } + + @Test + public void freezeLists() throws InterruptedException { + Realm realm = createDataForLiveRealm(DATA_SIZE); + AllTypes obj = realm.where(AllTypes.class).findFirst(); + RealmList frozenObjectList = obj.getColumnRealmList().freeze(); + RealmList frozenStringList = obj.getColumnStringList().freeze(); + Thread t = new Thread(() -> { + assertEquals(5, frozenObjectList.size()); + assertTrue(frozenObjectList.isFrozen()); + assertEquals(1, frozenObjectList.where().equalTo(Dog.FIELD_NAME, "Dog 1").findAll().size()); + + assertEquals(3, frozenStringList.size()); + assertTrue(frozenStringList.isFrozen()); + assertEquals("Foo", frozenStringList.first()); + }); + t.start(); + t.join(); + } + + @Test + public void freezeDynamicList() throws InterruptedException { + Realm realm = createDataForLiveRealm(DATA_SIZE); + DynamicRealm dynRealm = DynamicRealm.getInstance(realm.getConfiguration()); + DynamicRealmObject obj = dynRealm.where(AllTypes.CLASS_NAME).findFirst(); + RealmList frozenObjectList = obj.getList(AllTypes.FIELD_REALMLIST).freeze(); + RealmList frozenStringList = obj.getList(AllTypes.FIELD_STRING_LIST, String.class).freeze(); + Thread t = new Thread(() -> { + assertEquals(5, frozenObjectList.size()); + assertTrue(frozenObjectList.isFrozen()); + assertEquals(1, frozenObjectList.where().equalTo(Dog.FIELD_NAME, "Dog 1").findAll().size()); + + assertEquals(3, frozenStringList.size()); + assertTrue(frozenStringList.isFrozen()); + assertEquals("Foo", frozenStringList.first()); + }); + t.start(); + t.join(); + dynRealm.close(); + } + + @Test + public void freezeObject() throws InterruptedException { + Realm realm = createDataForLiveRealm(DATA_SIZE); + AllTypes obj = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst(); + AllTypes frozenObj = obj.freeze(); + Thread t = new Thread(() -> { + assertTrue(frozenObj.isFrozen()); + assertEquals(0, frozenObj.getColumnLong()); + assertTrue(frozenObj.getColumnRealmList().isFrozen()); + assertTrue(frozenObj.getColumnRealmObject().isFrozen()); + }); + t.start(); + t.join(); + } + + @Test + public void freezeDynamicObject() throws InterruptedException { + Realm realm = createDataForLiveRealm(DATA_SIZE); + DynamicRealm dynRealm = DynamicRealm.getInstance(realm.getConfiguration()); + DynamicRealmObject obj = dynRealm.where(AllTypes.CLASS_NAME).sort(AllTypes.FIELD_LONG).findFirst(); + DynamicRealmObject frozenObj = obj.freeze(); + Thread t = new Thread(() -> { + assertTrue(frozenObj.isFrozen()); + assertEquals(0, frozenObj.getLong(AllTypes.FIELD_LONG)); + assertTrue(frozenObj.getList(AllTypes.FIELD_REALMLIST).isFrozen()); + assertTrue(frozenObj.getObject(AllTypes.FIELD_REALMOBJECT).isFrozen()); + }); + t.start(); + t.join(); + dynRealm.close(); + assertTrue(Realm.getGlobalInstanceCount(realm.getConfiguration()) > 0); + } + + @Test + public void freezeDeletedObject() { + Realm realm = createDataForLiveRealm(DATA_SIZE); + AllTypes obj = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst(); + realm.executeTransaction(r -> { + obj.deleteFromRealm(); + }); + AllTypes frozenObj = obj.freeze(); + assertFalse(frozenObj.isValid()); + assertTrue(frozenObj.isFrozen()); + assertTrue(frozenObj.isLoaded()); + } + + @Test + @RunTestInLooperThread + public void freezePendingObject() { + Realm realm = createDataForLiveRealm(DATA_SIZE); + AllTypes obj = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirstAsync(); + + AllTypes frozenObj = obj.freeze(); + assertFalse(frozenObj.isValid()); + assertFalse(frozenObj.isLoaded()); + assertTrue(frozenObj.isFrozen()); + looperThread.testComplete(); + } + + @Test + public void frozenRealms_notEqualToLiveRealm() { + assertNotEquals(realm, frozenRealm); + } + + @Test + public void frozenRealm_notEqualToFrozenRealmAtOtherVersion() { + realm.beginTransaction(); + realm.commitTransaction(); + Realm otherFrozenRealm = realm.freeze(); + try { + assertNotEquals(frozenRealm, otherFrozenRealm); + } finally { + otherFrozenRealm.close(); + } + } + + @Test + public void frozenRealm_equalToFrozenRealmAtSameVersion() throws InterruptedException { + Realm otherFrozenRealm = realm.freeze(); + assertEquals(frozenRealm, otherFrozenRealm); // Same thread + + Thread t = new Thread(() -> { + Realm bgRealm = Realm.getInstance(realmConfig); + Realm otherThreadFrozenRealm = bgRealm.freeze(); + try { + assertEquals(frozenRealm, otherThreadFrozenRealm); + } finally { + bgRealm.close(); + } + }); + t.start(); + t.join(); + } + + @Test + public void frozenRealm_closeFromOtherThread() throws InterruptedException { + assertFalse(frozenRealm.isClosed()); + Thread t = new Thread(() -> { + frozenRealm.close(); + assertTrue(frozenRealm.isClosed()); + }); + t.start(); + t.join(); + } + + @Test + public void copyToRealm() throws InterruptedException { + Realm realm = createDataForLiveRealm(DATA_SIZE); + AllTypes frozenObject = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst().freeze(); + + Thread t = new Thread(() -> { + Realm bgRealm = Realm.getInstance(realm.getConfiguration()); + bgRealm.beginTransaction(); + AllTypes copiedObject = bgRealm.copyToRealm(frozenObject); + bgRealm.commitTransaction(); + + assertEquals(DATA_SIZE + 1, bgRealm.where(AllTypes.class).count()); + assertEquals(frozenObject.getColumnLong(), copiedObject.getColumnLong()); + assertEquals(frozenObject.getColumnString(), copiedObject.getColumnString()); + assertEquals(frozenObject.getColumnRealmList().size(), copiedObject.getColumnRealmList().size()); + bgRealm.close(); + }); + t.start(); + t.join(); + } + + @Test + public void copyToRealmOrUpdate() throws InterruptedException { + realm.executeTransaction(r -> { + r.createObject(AllJavaTypes.class, 42); + }); + AllJavaTypes frozenObject = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_ID, 42).findFirst().freeze(); + + Thread t = new Thread(() -> { + Realm bgRealm = Realm.getInstance(realm.getConfiguration()); + bgRealm.beginTransaction(); + AllJavaTypes copiedObject = bgRealm.copyToRealmOrUpdate(frozenObject); + bgRealm.commitTransaction(); + + assertEquals(1, bgRealm.where(AllJavaTypes.class).count()); + assertEquals(frozenObject.getFieldLong(), copiedObject.getFieldLong()); + bgRealm.close(); + }); + t.start(); + t.join(); + } + + @Test + public void insert() throws InterruptedException { + Realm realm = createDataForLiveRealm(DATA_SIZE); + AllTypes frozenObject = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst().freeze(); + + Thread t = new Thread(() -> { + Realm bgRealm = Realm.getInstance(realm.getConfiguration()); + bgRealm.beginTransaction(); + bgRealm.insert(frozenObject); + bgRealm.commitTransaction(); + + assertEquals(DATA_SIZE + 1, bgRealm.where(AllTypes.class).count()); + bgRealm.close(); + }); + t.start(); + t.join(); + } + + @Test + public void insertList() throws InterruptedException { + Realm realm = createDataForLiveRealm(DATA_SIZE); + AllTypes frozenObject1 = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG, Sort.ASCENDING).findFirst().freeze(); + AllTypes frozenObject2 = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG, Sort.DESCENDING).findFirst().freeze(); + + Thread t = new Thread(() -> { + Realm bgRealm = Realm.getInstance(realm.getConfiguration()); + bgRealm.beginTransaction(); + bgRealm.insert(Arrays.asList(frozenObject1, frozenObject2)); + bgRealm.commitTransaction(); + + assertEquals(DATA_SIZE + 2, bgRealm.where(AllTypes.class).count()); + bgRealm.close(); + }); + t.start(); + t.join(); + } + + @Test + public void insertOrUpdate() throws InterruptedException { + realm.executeTransaction(r -> { + r.createObject(AllJavaTypes.class, 42); + }); + AllJavaTypes frozenObject = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_ID, 42).findFirst().freeze(); + + Thread t = new Thread(() -> { + Realm bgRealm = Realm.getInstance(realm.getConfiguration()); + bgRealm.beginTransaction(); + bgRealm.insertOrUpdate(frozenObject); + bgRealm.commitTransaction(); + assertEquals(1, bgRealm.where(AllJavaTypes.class).count()); + bgRealm.close(); + }); + t.start(); + t.join(); + } + + @Test + public void insertOrUpdateList() throws InterruptedException { + realm.executeTransaction(r -> { + r.createObject(AllJavaTypes.class, 42); + r.createObject(AllJavaTypes.class, 43); + }); + + // Create two Java objects pointing to the same underlying Realm object in order to verify + // that insertOrUpdate works correctly both for the same Java object but also for two + // different Java objects representing the same Realm Object. + AllJavaTypes frozenObject1 = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_ID, 42).findFirst().freeze(); + AllJavaTypes frozenObject2 = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_ID, 42).findFirst().freeze(); + + Thread t = new Thread(() -> { + Realm bgRealm = Realm.getInstance(realm.getConfiguration()); + bgRealm.beginTransaction(); + bgRealm.insertOrUpdate(Arrays.asList(frozenObject1, frozenObject1, frozenObject2)); + bgRealm.commitTransaction(); + assertEquals(2, bgRealm.where(AllJavaTypes.class).count()); + bgRealm.close(); + }); + t.start(); + t.join(); + } + + @Test + public void realmObject_equals() throws InterruptedException { + realm = createDataForLiveRealm(DATA_SIZE); + AllTypes obj1 = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst(); + AllTypes obj2 = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst(); + AllTypes obj1Frozen = obj1.freeze(); + AllTypes obj2Frozen = obj2.freeze(); + + assertEquals(obj1, obj2); + assertEquals(obj1Frozen, obj2Frozen); + assertFalse(obj1.equals(obj1Frozen)); + Thread t = new Thread(() -> { + Realm bgRealm = Realm.getInstance(realm.getConfiguration()); + AllTypes bgObj1 = bgRealm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst(); + AllTypes bgObj1Frozen = bgObj1.freeze(); + assertEquals(obj1Frozen, obj2Frozen); + assertEquals(obj1Frozen, bgObj1Frozen); + bgRealm.close(); + }); + t.start(); + t.join(); + } + + @Test + public void realmObject_returnsFrozenRealm() { + realm = createDataForLiveRealm(DATA_SIZE); + AllTypes obj = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst().freeze(); + assertTrue(obj.getRealm().isFrozen()); + } + + @Test + public void realmList_returnsFrozenRealm() { + realm = createDataForLiveRealm(DATA_SIZE); + RealmResults results = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findAll().freeze(); + assertTrue(results.getRealm().isFrozen()); + } + + @Test + public void realmResults_returnsFrozenRealm() { + realm = createDataForLiveRealm(DATA_SIZE); + RealmList list = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findFirst().getColumnRealmList().freeze(); + assertTrue(list.getRealm().isFrozen()); + } + + private Realm createDataForFrozenRealm(int dataSize) { + return createDataForLiveRealm(dataSize).freeze(); + } + + private Realm createDataForLiveRealm(int dataSize) { + realm.executeTransaction(r -> { + + RealmList list = new RealmList<>(); + for (int i = 0; i < 5; i++) { + list.add(r.copyToRealm(new Dog("Dog " + i))); + } + for (int i = 0; i < dataSize; i++) { + AllTypes obj = new AllTypes(); + obj.setColumnString("String " + i); + obj.setColumnLong(i); + obj.setColumnRealmList(list); + obj.setColumnStringList(new RealmList("Foo", "Bar", "Baz")); + obj.setColumnRealmObject(r.copyToRealm(new Dog("Dog 42"))); + r.insert(obj); + } + }); + return realm; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index e7ab4d0c69..4eace7ac15 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -22,6 +22,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,6 +46,10 @@ * This class test interoperability with Realms created on iOS. */ @RunWith(AndroidJUnit4.class) +@Ignore("__CORE6__: asset file, Upgrade interrupted https://github.com/realm/realm-core-private/issues/201 also need " + + "to regenerate the iOS Realm files using the realm-java/realm/realm-library/src/androidTest/assets/ios/README.md" + + "Generate iOS files once Cocoa complets the migration to Core6") +//FIXME this is using primarily Realm files of format version 3 now we have sync to test interop between platform ... these tests should be disabled public class IOSRealmTests { @Rule @@ -83,7 +88,7 @@ public void iOSDataTypes() throws IOException { // Verifies metadata. Table table = realm.getTable(IOSAllTypes.class); assertEquals("id", OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), IOSAllTypes.CLASS_NAME)); - assertTrue(table.hasSearchIndex(table.getColumnIndex("id"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("id"))); // Iterative check. for (int i = 0; i < 10; i++) { IOSAllTypes obj = result.get(i); diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index 5fa3c49f3e..56b6c6970d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -34,7 +34,6 @@ import java.util.concurrent.Future; import io.realm.entities.AllJavaTypes; -import io.realm.entities.AllTypes; import io.realm.entities.Dog; import io.realm.entities.NullTypes; import io.realm.entities.Owner; @@ -321,7 +320,6 @@ private void doTestSortOnColumnWithPartialNullValues(String fieldName, assertEquals(2, sortedList.last().getId()); } - // Tests sort on nullable fields with null values partially. @Test public void sort_rowsWithPartialNullValues() { if (isSnapshot(collectionClass)) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java index 7b02a12d96..3e12477c9e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmCollectionTests.java @@ -19,7 +19,6 @@ import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -606,6 +605,8 @@ public void realmMethods_invalidFieldNames() { case DELETE_ALL_FROM_REALM: case IS_VALID: case IS_MANAGED: + case IS_FROZEN: + case FREEZE: continue; default: @@ -638,6 +639,8 @@ public void realmMethods_invalidFieldType() { case DELETE_ALL_FROM_REALM: case IS_VALID: case IS_MANAGED: + case IS_FROZEN: + case FREEZE: continue; default: @@ -824,6 +827,11 @@ public Boolean call() throws Exception { case DELETE_ALL_FROM_REALM: collection.deleteAllFromRealm(); break; case IS_VALID: collection.isValid(); break; case IS_MANAGED: collection.isManaged(); return true; + + // These methods are threadsafe pr. design + case IS_FROZEN: + case FREEZE: + return true; } return false; } catch (IllegalStateException ignored) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index a5d15ad8e1..a1bfd55443 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -772,8 +772,11 @@ public void iterator_outsideChangeToSizeThrowsConcurrentModification_managedColl case MAX_DATE: case IS_VALID: case IS_MANAGED: + case IS_FROZEN: + case FREEZE: realm.cancelTransaction(); continue; + default: fail("Unknown method: " + method); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java index e2d0a4b959..d159f306ba 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java @@ -70,8 +70,8 @@ public void tearDown() { @Test public void ignore() { Table table = realm.getTable(AnnotationTypes.class); - assertEquals(-1, table.getColumnIndex(AnnotationTypes.FIELD_IGNORE_STRING)); - assertEquals(-1, table.getColumnIndex(AnnotationTypes.FIELD_TRANSIENT_STRING)); + assertEquals(-1, table.getColumnKey(AnnotationTypes.FIELD_IGNORE_STRING)); + assertEquals(-1, table.getColumnKey(AnnotationTypes.FIELD_TRANSIENT_STRING)); } // Tests if "index" annotation works with supported types. @@ -79,26 +79,26 @@ public void ignore() { public void index() { Table table = realm.getTable(AnnotationIndexTypes.class); - assertTrue(table.hasSearchIndex(table.getColumnIndex("indexString"))); - assertFalse(table.hasSearchIndex(table.getColumnIndex("notIndexString"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("indexString"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("notIndexString"))); - assertTrue(table.hasSearchIndex(table.getColumnIndex("indexInt"))); - assertFalse(table.hasSearchIndex(table.getColumnIndex("notIndexInt"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("indexInt"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("notIndexInt"))); - assertTrue(table.hasSearchIndex(table.getColumnIndex("indexByte"))); - assertFalse(table.hasSearchIndex(table.getColumnIndex("notIndexByte"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("indexByte"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("notIndexByte"))); - assertTrue(table.hasSearchIndex(table.getColumnIndex("indexShort"))); - assertFalse(table.hasSearchIndex(table.getColumnIndex("notIndexShort"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("indexShort"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("notIndexShort"))); - assertTrue(table.hasSearchIndex(table.getColumnIndex("indexLong"))); - assertFalse(table.hasSearchIndex(table.getColumnIndex("notIndexLong"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("indexLong"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("notIndexLong"))); - assertTrue(table.hasSearchIndex(table.getColumnIndex("indexBoolean"))); - assertFalse(table.hasSearchIndex(table.getColumnIndex("notIndexBoolean"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("indexBoolean"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("notIndexBoolean"))); - assertTrue(table.hasSearchIndex(table.getColumnIndex("indexDate"))); - assertFalse(table.hasSearchIndex(table.getColumnIndex("notIndexDate"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("indexDate"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("notIndexDate"))); } @Test @@ -127,14 +127,14 @@ public void primaryKey_errorOnInsertingSameObject() { } @Test - public void primaryKey_isIndexed() { + public void string_primaryKey_isNotIndexed() { Table table = realm.getTable(PrimaryKeyAsString.class); assertNotNull(OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), PrimaryKeyAsString.CLASS_NAME)); - assertTrue(table.hasSearchIndex(table.getColumnIndex("name"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("name"))); table = realm.getTable(PrimaryKeyAsLong.class); assertNotNull(OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), PrimaryKeyAsLong.CLASS_NAME)); - assertTrue(table.hasSearchIndex(table.getColumnIndex("id"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("id"))); } // Annotation processor honors common naming conventions. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index bc0c9a3a10..b0a58f1085 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -1377,6 +1377,40 @@ public void execute(Realm realm) { looperThread.keepStrongReference(results); } + @Test + @RunTestInLooperThread + public void freezeAsyncResults() { + int DATA_SIZE = 10; + Realm realm = looperThread.getRealm(); + populateTestRealm(realm, DATA_SIZE); + RealmResults results = realm.where(AllTypes.class).findAllAsync(); + looperThread.keepStrongReference(results); + assertFalse(results.isLoaded()); + assertTrue(results.isValid()); + assertEquals(0, results.size()); + assertFalse(results.isFrozen()); + + RealmResults frozenResults = results.freeze(); + assertTrue(frozenResults.isFrozen()); + assertFalse(frozenResults.isLoaded()); + assertTrue(frozenResults.isValid()); + assertEquals(0, frozenResults.size()); + + results.addChangeListener(new RealmChangeListener>() { + @Override + public void onChange(RealmResults results) { + assertTrue(results.isLoaded()); + assertTrue(results.isValid()); + assertEquals(DATA_SIZE, results.size()); + + assertFalse(frozenResults.isLoaded()); + assertTrue(frozenResults.isValid()); + assertEquals(0, frozenResults.size()); + looperThread.testComplete(); + } + }); + } + // *** Helper methods *** private void populateTestRealm(final Realm testRealm, int objects) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 06f9cacced..45cbb1cdd3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -471,11 +471,11 @@ public void hashCode_withCustomModules() { public void hashCode_withDifferentRxObservableFactory() { RealmConfiguration config1 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) - .rxFactory(new RealmObservableFactory()) + .rxFactory(new RealmObservableFactory(false)) .build(); RealmConfiguration config2 = configFactory.createConfigurationBuilder() .directory(configFactory.getRoot()) - .rxFactory(new RealmObservableFactory() { + .rxFactory(new RealmObservableFactory(false) { @Override public int hashCode() { return super.hashCode() + 1; @@ -1095,4 +1095,26 @@ public boolean shouldCompact(long totalBytes, long usedBytes) { } catch (IllegalStateException ignored) { } } + + @Test + public void maxNumberOfActiveVersions() { + RealmConfiguration config = new RealmConfiguration.Builder() + .maxNumberOfActiveVersions(42) + .build(); + assertEquals(42, config.getMaxNumberOfActiveVersions()); + } + + @Test + public void maxNumberOfActiveVersions_throwsIfZeroOrNegative() { + RealmConfiguration.Builder builder = new RealmConfiguration.Builder(); + try { + builder.maxNumberOfActiveVersions(0); + } catch (IllegalArgumentException ignore) { + } + + try { + builder.maxNumberOfActiveVersions(-1); + } catch (IllegalArgumentException ignore) { + } + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index d1edfba2a0..dff06ec723 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -1825,6 +1825,23 @@ private void testRequiredPrimitiveListWithNullValue(String fieldName) throws JSO } } + private void testOptionalPrimitiveListWithNullValue(String fieldName) throws JSONException, IOException { + JSONObject jsonObject = new JSONObject(); + JSONArray jsonArray =new JSONArray(); + jsonArray.put(null); + jsonObject.put(fieldName, jsonArray); + + // Test from JSONObject + realm.beginTransaction(); + realm.createObjectFromJson(PrimitiveListTypes.class, jsonObject); + realm.cancelTransaction(); + + // Test from JSONStream + realm.beginTransaction(); + PrimitiveListTypes objectFromJson = realm.createObjectFromJson(PrimitiveListTypes.class, convertJsonObjectToStream(jsonObject)); + realm.cancelTransaction(); + } + @Test public void createObjectFromJson_primitiveList_nullValueForRequiredField() throws IOException, JSONException { testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_STRING_LIST); @@ -1838,4 +1855,18 @@ public void createObjectFromJson_primitiveList_nullValueForRequiredField() throw testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_DATE_LIST); testRequiredPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_REQUIRED_BYTE_LIST); } + + @Test + public void createObjectFromJson_primitiveList_nullValueForOptionalField() throws IOException, JSONException { + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_STRING_LIST); + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_BOOLEAN_LIST); + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_DOUBLE_LIST); + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_FLOAT_LIST); + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_BYTE_LIST); + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_SHORT_LIST); + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_INT_LIST); + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_LONG_LIST); + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_DATE_LIST); + testOptionalPrimitiveListWithNullValue(PrimitiveListTypes.FIELD_BYTE_LIST); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index ebe627ac31..74f75dddbf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -716,10 +716,10 @@ public void toString_AfterContainerObjectRemoved() { public void toString_managedMode() { StringBuilder sb = new StringBuilder("RealmList@["); for (int i = 0; i < collection.size() - 1; i++) { - sb.append(((RealmObjectProxy) (collection.get(i))).realmGet$proxyState().getRow$realm().getIndex()); + sb.append(((RealmObjectProxy) (collection.get(i))).realmGet$proxyState().getRow$realm().getObjectKey()); sb.append(","); } - sb.append(((RealmObjectProxy)collection.get(TEST_SIZE - 1)).realmGet$proxyState().getRow$realm().getIndex()); + sb.append(((RealmObjectProxy)collection.get(TEST_SIZE - 1)).realmGet$proxyState().getRow$realm().getObjectKey()); sb.append("]"); assertEquals(sb.toString(), collection.toString()); @@ -784,6 +784,8 @@ public void realmMethods_onDeletedLinkView() { case DELETE_ALL_FROM_REALM: results.deleteAllFromRealm(); break; case IS_VALID: continue; // Does not throw. case IS_MANAGED: continue; // Does not throw. + case IS_FROZEN: continue; // Does not throw + case FREEZE: results.freeze(); break; } fail(method + " should have thrown an Exception."); } catch (IllegalStateException ignored) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 2c56308eca..114e77e6c8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -54,6 +54,7 @@ import io.realm.entities.StringOnlyRequired; import io.realm.entities.Thread; import io.realm.entities.migration.MigrationClassRenamed; +import io.realm.entities.migration.MigrationCore6PKStringIndexedByDefault; import io.realm.entities.migration.MigrationFieldRenameAndAdd; import io.realm.entities.migration.MigrationFieldRenamed; import io.realm.entities.migration.MigrationFieldTypeToInt; @@ -99,16 +100,13 @@ public void tearDown() { } } - private void assertPKField(Realm realm, String className, String expectedName, long expectedIndex) { + private void assertPKField(Realm realm, String className, String expectedName) { String pkField = OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, className); assertNotNull(pkField); RealmObjectSchema objectSchema = realm.getSchema().get(className); assertNotNull(objectSchema); assertTrue(objectSchema.hasField(expectedName)); assertEquals(expectedName, pkField); - //noinspection ConstantConditions - assertEquals(expectedIndex, - realm.sharedRealm.getTable(Table.getTableNameForClass(className)).getColumnIndex(pkField)); } @Test @@ -385,8 +383,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Table table = realm.getSchema().getTable(MigrationClassRenamed.class); assertEquals(MigrationClassRenamed.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertPKField(realm, MigrationClassRenamed.CLASS_NAME, MigrationClassRenamed.FIELD_PRIMARY, - MigrationClassRenamed.DEFAULT_PRIMARY_INDEX); + assertPKField(realm, MigrationClassRenamed.CLASS_NAME, MigrationClassRenamed.FIELD_PRIMARY); // Old schema does not exist. assertNull(realm.getSchema().get(MigrationPrimaryKey.CLASS_NAME)); } @@ -453,8 +450,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Table table = realm.getSchema().getTable(MigrationClassRenamed.class); assertEquals(MigrationClassRenamed.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertPKField(realm, MigrationClassRenamed.CLASS_NAME, MigrationClassRenamed.FIELD_PRIMARY, - MigrationClassRenamed.DEFAULT_PRIMARY_INDEX); + assertPKField(realm, MigrationClassRenamed.CLASS_NAME, MigrationClassRenamed.FIELD_PRIMARY); // Old schema does not exist. assertNull(realm.getSchema().get(MigrationPrimaryKey.CLASS_NAME)); } @@ -565,8 +561,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Table table = realm.getSchema().getTable(MigrationPosteriorIndexOnly.class); assertEquals(MigrationPosteriorIndexOnly.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertPKField(realm, MigrationPosteriorIndexOnly.CLASS_NAME, MigrationPosteriorIndexOnly.FIELD_PRIMARY - , MigrationPosteriorIndexOnly.DEFAULT_PRIMARY_INDEX); + assertPKField(realm, MigrationPosteriorIndexOnly.CLASS_NAME, MigrationPosteriorIndexOnly.FIELD_PRIMARY); } // Removing fields after a pk field does not affect the pk. @@ -591,8 +586,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Table table = realm.getSchema().getTable(MigrationPriorIndexOnly.class); assertEquals(MigrationPriorIndexOnly.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertPKField(realm, MigrationPriorIndexOnly.CLASS_NAME, MigrationPriorIndexOnly.FIELD_PRIMARY - , MigrationPriorIndexOnly.DEFAULT_PRIMARY_INDEX); + assertPKField(realm, MigrationPriorIndexOnly.CLASS_NAME, MigrationPriorIndexOnly.FIELD_PRIMARY); } // Renaming the class should also rename the the class entry in the pk metadata table that tracks primary keys. @@ -616,8 +610,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Table table = realm.getSchema().getTable(MigrationFieldRenamed.class); assertEquals(MigrationFieldRenamed.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertPKField(realm, MigrationFieldRenamed.CLASS_NAME, MigrationFieldRenamed.FIELD_PRIMARY, - MigrationFieldRenamed.DEFAULT_PRIMARY_INDEX); + assertPKField(realm, MigrationFieldRenamed.CLASS_NAME, MigrationFieldRenamed.FIELD_PRIMARY); } private void createObjectsWithOldPrimaryKey(final String className, final boolean insertNullValue) { @@ -678,8 +671,7 @@ public void apply(DynamicRealmObject obj) { Table table = realm.getSchema().getTable(MigrationFieldTypeToInt.class); assertEquals(MigrationFieldTypeToInt.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertPKField(realm, MigrationFieldTypeToInt.CLASS_NAME, MigrationFieldTypeToInt.FIELD_PRIMARY, - MigrationFieldTypeToInt.DEFAULT_PRIMARY_INDEX); + assertPKField(realm, MigrationFieldTypeToInt.CLASS_NAME, MigrationFieldTypeToInt.FIELD_PRIMARY); assertEquals(1, realm.where(MigrationFieldTypeToInt.class).count()); assertEquals(12, realm.where(MigrationFieldTypeToInt.class).findFirst().fieldIntPrimary); @@ -723,8 +715,7 @@ public void apply(DynamicRealmObject obj) { Table table = realm.getSchema().getTable(MigrationFieldTypeToInteger.class); assertEquals(MigrationFieldTypeToInteger.DEFAULT_FIELDS_COUNT, table.getColumnCount()); - assertPKField(realm, MigrationFieldTypeToInteger.CLASS_NAME, MigrationFieldTypeToInteger.FIELD_PRIMARY, - MigrationFieldTypeToInteger.DEFAULT_PRIMARY_INDEX); + assertPKField(realm, MigrationFieldTypeToInteger.CLASS_NAME, MigrationFieldTypeToInteger.FIELD_PRIMARY); assertEquals(2, realm.where(MigrationFieldTypeToInteger.class).count()); @@ -895,8 +886,8 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Table table = realm.getTable(AnnotationTypes.class); assertEquals(3, table.getColumnCount()); assertEquals("id", OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), "AnnotationTypes")); - assertTrue(table.hasSearchIndex(table.getColumnIndex("id"))); - assertTrue(table.hasSearchIndex(table.getColumnIndex("indexString"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("id"))); + assertTrue(table.hasSearchIndex(table.getColumnKey("indexString"))); } @Test @@ -1442,6 +1433,23 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { } } + // File format 9 (up to Core5) added an index automatically to the primary key, in Core6 string based PK are not + // indexed because the search index is derived from the ObjectKey. + @Test + public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException { + configFactory.copyRealmFromAssets(context, + "core6_string_pk_indexed.realm", "core6.realm"); + Realm realm = Realm.getInstance(configFactory.createConfigurationBuilder() + .name("core6.realm") + .schema(MigrationCore6PKStringIndexedByDefault.class) + .build()); + assertFalse(realm.isEmpty()); + assertTrue(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); + MigrationCore6PKStringIndexedByDefault first = realm.where(MigrationCore6PKStringIndexedByDefault.class).findFirst(); + assertNotNull(first); + assertEquals("Foo", first.name); + } + // TODO Add unit tests for default nullability // TODO Add unit tests for default Indexing for Primary keys } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 8ea8719c14..c7df496551 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -1312,14 +1312,14 @@ public void getFieldIndex() { dynamicRealm.beginTransaction(); RealmObjectSchema objectSchema = dynamicRealm.getSchema().create(className); - assertTrue(objectSchema.getFieldIndex(fieldName) < 0); + assertTrue(objectSchema.getFieldColumnKey(fieldName) < 0); objectSchema.addField(fieldName, long.class); //noinspection ConstantConditions - assertTrue(objectSchema.getFieldIndex(fieldName) >= 0); + assertTrue(objectSchema.getFieldColumnKey(fieldName) >= 0); objectSchema.removeField(fieldName); - assertTrue(objectSchema.getFieldIndex(fieldName) < 0); + assertTrue(objectSchema.getFieldColumnKey(fieldName) < 0); dynamicRealm.cancelTransaction(); dynamicRealm.close(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index a6b3d4792c..ce0b20bb71 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -22,7 +22,6 @@ import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -1360,16 +1359,15 @@ private RealmConfiguration prepareColumnSwappedRealm() throws FileNotFoundExcept @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { final Table table = realm.getSchema().getTable(StringAndInt.class); - final long strIndex = table.getColumnIndex("str"); - final long numberIndex = table.getColumnIndex("number"); + final long strColKey = table.getColumnKey("str"); + final long numberColKey = table.getColumnKey("number"); - while (0 < table.getColumnCount()) { - table.removeColumn(0); + for (String columnName :table.getColumnNames()) { + table.removeColumn(table.getColumnKey(columnName)); } - final long newStrIndex; // Swaps column indices. - if (strIndex < numberIndex) { + if (strColKey < numberColKey) { table.addColumn(RealmFieldType.INTEGER, "number"); newStrIndex = table.addColumn(RealmFieldType.STRING, "str"); } else { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java index 8158986c35..1b029f415f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java @@ -65,21 +65,21 @@ public void createColumnInfo_noDuplicateIndexInIndexFields() { final Set indexSet = new HashSet(); int indexCount = 0; - indexSet.add(columnInfo.nameIndex); + indexSet.add(columnInfo.nameColKey); indexCount++; - indexSet.add(columnInfo.ageIndex); + indexSet.add(columnInfo.ageColKey); indexCount++; - indexSet.add(columnInfo.heightIndex); + indexSet.add(columnInfo.heightColKey); indexCount++; - indexSet.add(columnInfo.weightIndex); + indexSet.add(columnInfo.weightColKey); indexCount++; - indexSet.add(columnInfo.hasTailIndex); + indexSet.add(columnInfo.hasTailColKey); indexCount++; - indexSet.add(columnInfo.birthdayIndex); + indexSet.add(columnInfo.birthdayColKey); indexCount++; - indexSet.add(columnInfo.ownerIndex); + indexSet.add(columnInfo.ownerColKey); indexCount++; - indexSet.add(columnInfo.scaredOfDogIndex); + indexSet.add(columnInfo.scaredOfDogColKey); indexCount++; assertEquals(indexCount, indexSet.size()); @@ -91,7 +91,7 @@ public void createColumnInfo_noDuplicateIndexInIndicesMap() { io_realm_entities_CatRealmProxy.CatColumnInfo columnInfo; columnInfo = (io_realm_entities_CatRealmProxy.CatColumnInfo) mediator.createColumnInfo(Cat.class, realm.sharedRealm.getSchemaInfo()); - final Set indexSet = new HashSet(); + final Set columnKeySet = new HashSet(); int indexCount = 0; // Gets index for each field and then put into set. @@ -99,11 +99,11 @@ public void createColumnInfo_noDuplicateIndexInIndicesMap() { if (Modifier.isStatic(field.getModifiers())) { continue; } - indexSet.add(columnInfo.getColumnIndex(field.getName())); + columnKeySet.add(columnInfo.getColumnKey(field.getName())); indexCount++; } assertEquals("if no duplicates, size of set equals to field count.", - indexCount, indexSet.size()); + indexCount, columnKeySet.size()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index a29e98a8c5..742f71ebe0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -27,7 +27,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; -import io.realm.annotations.RealmClass; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AnnotationIndexTypes; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index b3f3ec079e..2bbe758dce 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1765,36 +1765,40 @@ public void asJSON() throws JSONException { String json = all.asJSON(); final String expectedJSON = "[\n" + " {\n" + + " \"_key\": 100,\n" + " \"columnString\": \"alltypes1\",\n" + " \"columnLong\": 1337,\n" + " \"columnFloat\": 3.1400001,\n" + " \"columnDouble\": 0.89122999999999997,\n" + " \"columnBoolean\": false,\n" + " \"columnDate\": \"" + now + "\",\n" + - " \"columnBinary\": \"010203\",\n" + + " \"columnBinary\": \"AQID\",\n" + " \"columnMutableRealmInteger\": 0,\n" + " \"columnRealmObject\": [\n" + " {\n" + + " \"_key\": 100,\n" + " \"name\": \"dog1\",\n" + " \"age\": 1,\n" + " \"height\": 1.1,\n" + " \"weight\": 10.100000381469727,\n" + " \"hasTail\": true,\n" + " \"birthday\": \"" + now + "\",\n" + - " \"owner\": []\n" + + " \"owner\": null\n" + " }\n" + " ],\n" + " \"columnRealmList\": [\n" + " {\n" + + " \"_key\": 101,\n" + " \"name\": \"dog2\",\n" + " \"age\": 2,\n" + " \"height\": 2.0999999,\n" + " \"weight\": 20.100000381469727,\n" + " \"hasTail\": false,\n" + " \"birthday\": \"" + now + "\",\n" + - " \"owner\": []\n" + + " \"owner\": null\n" + " },\n" + " {\n" + + " \"_key\": 102,\n" + " \"name\": \"dog3\",\n" + " \"age\": 3,\n" + " \"height\": 3.0999999,\n" + @@ -1803,20 +1807,39 @@ public void asJSON() throws JSONException { " \"birthday\": \"" + now + "\",\n" + " \"owner\": [\n" + " {\n" + + " \"_key\": 0,\n" + " \"name\": \"Dog owner 1\",\n" + " \"dogs\": [],\n" + - " \"cat\": []\n" + + " \"cat\": null\n" + " }\n" + " ]\n" + " }\n" + " ],\n" + - " \"columnStringList\": [ \"Foo\", \"Bar\" ]," + + " \"columnStringList\": [\n" + + " \"Foo\",\n" + + " \"Bar\"\n" + + " ],\n" + " \"columnBinaryList\": [],\n" + - " \"columnBooleanList\": [ false, true ],\n" + - " \"columnLongList\": [ 1000, 2000 ],\n" + - " \"columnDoubleList\": [ 1.123, 5.3209999999999997 ],\n" + - " \"columnFloatList\": [ 0.12, 0.13 ],\n" + - " \"columnDateList\": [ \"" + now + "\", \"" + now + "\"]\n" + + " \"columnBooleanList\": [\n" + + " false,\n" + + " true\n" + + " ],\n" + + " \"columnLongList\": [\n" + + " 1000,\n" + + " 2000\n" + + " ],\n" + + " \"columnDoubleList\": [\n" + + " 1.123,\n" + + " 5.3209999999999997\n" + + " ],\n" + + " \"columnFloatList\": [\n" + + " 0.12,\n" + + " 0.13\n" + + " ],\n" + + " \"columnDateList\": [\n" + + " \"" + now + "\",\n" + + " \"" + now + "\"\n" + + " ]\n" + " }\n" + "]"; JSONAssert.assertEquals(expectedJSON, json, false); @@ -1849,37 +1872,47 @@ public void asJSON_cycles() throws JSONException { String json = realmObjects.asJSON(); String expectedJSON = "[\n" + " {\n" + + " \"_key\": 0,\n" + " \"id\": 0,\n" + " \"name\": \"One\",\n" + " \"date\": \"" + now + "\",\n" + " \"object\": [\n" + " {\n" + + " \"_key\": 1,\n" + " \"id\": 0,\n" + " \"name\": \"Two\",\n" + " \"date\": \"" + now + "\",\n" + - " \"object\": \"0\",\n" + - " \"otherObject\": [],\n" + + " \"object\": {\n" + + " \"table\": \"class_CyclicType\",\n" + + " \"key\": 0\n" + + " },\n" + + " \"otherObject\": null,\n" + " \"objects\": []\n" + " }\n" + " ],\n" + - " \"otherObject\": [],\n" + + " \"otherObject\": null,\n" + " \"objects\": []\n" + " },\n" + " {\n" + + " \"_key\": 1,\n" + " \"id\": 0,\n" + " \"name\": \"Two\",\n" + " \"date\": \"" + now + "\",\n" + " \"object\": [\n" + " {\n" + + " \"_key\": 0,\n" + " \"id\": 0,\n" + " \"name\": \"One\",\n" + " \"date\": \"" + now + "\",\n" + - " \"object\": \"1\",\n" + - " \"otherObject\": [],\n" + + " \"object\": {\n" + + " \"table\": \"class_CyclicType\",\n" + + " \"key\": 1\n" + + " },\n" + + " \"otherObject\": null,\n" + " \"objects\": []\n" + " }\n" + " ],\n" + - " \"otherObject\": [],\n" + + " \"otherObject\": null,\n" + " \"objects\": []\n" + " }\n" + "]"; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index 73db5a1f65..9e00db42c1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -193,7 +193,9 @@ public void createWithPrimaryKeyField_string() { assertEquals("pkField", objectSchema.getPrimaryKey()); assertEquals(RealmFieldType.STRING, objectSchema.getFieldType("pkField")); assertFalse(objectSchema.isNullable("pkField")); - assertTrue(objectSchema.hasIndex("pkField")); + // Search index is not added to primary key string columns. Will compute key directly from primary key value. + // since Core6.0.0-alpha.25 + assertFalse(objectSchema.hasIndex("pkField")); realmSchema.remove(validClassName); @@ -203,7 +205,7 @@ public void createWithPrimaryKeyField_string() { assertEquals("pkField", objectSchema.getPrimaryKey()); assertEquals(RealmFieldType.STRING, objectSchema.getFieldType("pkField")); assertTrue(objectSchema.isNullable("pkField")); - assertTrue(objectSchema.hasIndex("pkField")); + assertFalse(objectSchema.hasIndex("pkField")); } } @@ -541,7 +543,7 @@ public void remove_shouldClearDynamicCache() { assertNotSame(previousFoo, newFoo); try { - previousFoo.getClassName(); + assertEquals("foo", previousFoo.getClassName()); fail(); } catch (IllegalStateException ignored) { } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 84ff3c5112..2028a668aa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -33,7 +33,6 @@ import org.junit.After; import org.junit.Assume; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -105,7 +104,6 @@ import io.realm.exceptions.RealmMigrationNeededException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.internal.OsSharedRealm; -import io.realm.internal.Table; import io.realm.internal.util.Pair; import io.realm.log.RealmLog; import io.realm.objectid.NullPrimaryKey; @@ -1161,8 +1159,8 @@ public boolean shouldCompact(long totalBytes, long usedBytes) { assertEquals(1, compactOnLaunchCount.get()); realm = Realm.getInstance(realmConfig); - // Called 2 more times. The PK table migration logic (the old PK bug) needs to open/close the Realm once. - assertEquals(3, compactOnLaunchCount.get()); + + assertEquals(2, compactOnLaunchCount.get()); Thread thread = new Thread(new Runnable() { @Override @@ -1170,7 +1168,7 @@ public void run() { Realm bgRealm = Realm.getInstance(realmConfig); bgRealm.close(); // compactOnLaunch should not be called anymore! - assertEquals(3, compactOnLaunchCount.get()); + assertEquals(2, compactOnLaunchCount.get()); } }); thread.start(); @@ -1183,7 +1181,7 @@ public void run() { realm.close(); - assertEquals(3, compactOnLaunchCount.get()); + assertEquals(2, compactOnLaunchCount.get()); } @Test @@ -4010,9 +4008,8 @@ public void run() { assertFalse(bgRealmSecondWaitResult.get()); } - // Tests if waitForChange still blocks if stopWaitForChange has been called for a realm in a different thread. @Test - public void waitForChange_blockSpecificThreadOnly() throws InterruptedException { + public void waitForChange_stopWaitForChangeReleasesAllWaitingThreads() throws InterruptedException { final CountDownLatch bgRealmsOpened = new CountDownLatch(2); final CountDownLatch bgRealmsClosed = new CountDownLatch(2); final AtomicBoolean bgRealmFirstWaitResult = new AtomicBoolean(true); @@ -4038,7 +4035,8 @@ public void run() { public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmsOpened.countDown(); - bgRealmSecondWaitResult.set(realm.waitForChange()); + bgRealmSecondWaitResult.set(realm.waitForChange());//In Core 6 calling stopWaitForChange will release all waiting threads + // which causes query below to run before `populateTestRealm` happens bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmsClosed.countDown(); @@ -4054,8 +4052,8 @@ public void run() { populateTestRealm(); TestHelper.awaitOrFail(bgRealmsClosed); assertFalse(bgRealmFirstWaitResult.get()); - assertTrue(bgRealmSecondWaitResult.get()); - assertEquals(TEST_DATA_SIZE, bgRealmWaitForChangeResult.get()); + assertFalse(bgRealmSecondWaitResult.get()); + assertEquals(0, bgRealmWaitForChangeResult.get()); } // Checks if waitForChange() does not respond to Thread.interrupt(). @@ -4199,53 +4197,6 @@ public void run(Realm realm) { assertFalse(bgRealmChangeResult.get()); } - // Check if the column indices cache is refreshed if the index of a defined column is changed by another Realm - // instance. - @Test - public void nonAdditiveSchemaChangesWhenTypedRealmExists() throws InterruptedException { - final String TEST_CHARS = "TEST_CHARS"; - final RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() - .schema(StringOnly.class) - .name("schemaChangeTest") - .build(); - Realm realm = Realm.getInstance(realmConfig); - io_realm_entities_StringOnlyRealmProxy.StringOnlyColumnInfo columnInfo - = (io_realm_entities_StringOnlyRealmProxy.StringOnlyColumnInfo) realm.getSchema().getColumnInfo(StringOnly.class); - assertEquals(0, columnInfo.charsIndex); - - realm.beginTransaction(); - StringOnly stringOnly = realm.createObject(StringOnly.class); - stringOnly.setChars(TEST_CHARS); - realm.commitTransaction(); - - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - // Here we try to change the column index of FIELD_CHARS from 0 to 1. - DynamicRealm realm = DynamicRealm.getInstance(realmConfig); - realm.beginTransaction(); - RealmObjectSchema stringOnlySchema = realm.getSchema().get(StringOnly.CLASS_NAME); - assertEquals(0, stringOnlySchema.getColumnIndex(StringOnly.FIELD_CHARS)); - Table table = stringOnlySchema.getTable(); - // Please notice that we cannot do it by removing/adding a column since it is not allowed by Object - // Store. Do it by using the internal API insertColumn. - table.insertColumn(0, RealmFieldType.INTEGER, "NewColumn"); - assertEquals(1, stringOnlySchema.getColumnIndex(StringOnly.FIELD_CHARS)); - realm.commitTransaction(); - realm.close(); - } - }); - thread.start(); - thread.join(); - realm.refresh(); - - // The columnInfo object never changes, only the indexes it references will. - assertSame(columnInfo, realm.getSchema().getColumnInfo(StringOnly.class)); - assertEquals(TEST_CHARS, stringOnly.getChars()); - assertEquals(1, columnInfo.charsIndex); - realm.close(); - } - @Test public void getGlobalInstanceCount() { final CountDownLatch bgDone = new CountDownLatch(1); @@ -4257,27 +4208,45 @@ public void getGlobalInstanceCount() { Realm realm = Realm.getInstance(config); assertEquals(1, Realm.getGlobalInstanceCount(config)); + Realm realm1 = Realm.getInstance(config); + assertEquals(1, Realm.getGlobalInstanceCount(config)); + + // Even though each Realm type points to the same Realm on disk, we report them as + // multiple global instances + // Opens thread local DynamicRealm. DynamicRealm dynRealm = DynamicRealm.getInstance(config); assertEquals(2, Realm.getGlobalInstanceCount(config)); + // Create frozen Realms. + Realm frozenRealm = realm.freeze(); + assertTrue(frozenRealm.isFrozen()); + assertEquals(3, Realm.getGlobalInstanceCount(config)); + + DynamicRealm frozenDynamicRealm = dynRealm.freeze(); + assertTrue(frozenDynamicRealm.isFrozen()); + assertEquals(4, Realm.getGlobalInstanceCount(config)); + // Opens Realm in another thread. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(config); - assertEquals(3, Realm.getGlobalInstanceCount(config)); + assertEquals(5, Realm.getGlobalInstanceCount(config)); realm.close(); - assertEquals(2, Realm.getGlobalInstanceCount(config)); + assertEquals(4, Realm.getGlobalInstanceCount(config)); bgDone.countDown(); } }).start(); TestHelper.awaitOrFail(bgDone); dynRealm.close(); - assertEquals(1, Realm.getGlobalInstanceCount(config)); + assertEquals(3, Realm.getGlobalInstanceCount(config)); realm.close(); + realm1.close(); // Fully closing the live Realm also closes all frozen Realms assertEquals(0, Realm.getGlobalInstanceCount(config)); + assertTrue(frozenRealm.isClosed()); + assertTrue(frozenDynamicRealm.isClosed()); } @Test @@ -4574,6 +4543,23 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { } } + @Test + public void hittingMaxNumberOfVersionsThrows() { + RealmConfiguration config = configFactory.createConfigurationBuilder() + .name("versions-test.realm") + .maxNumberOfActiveVersions(1) + .build(); + Realm realm = Realm.getInstance(config); + try { + realm.beginTransaction(); + fail(); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().contains("Number of active versions (2) in the Realm exceeded the limit of 1")); + } finally { + realm.close(); + } + } + // Test for https://github.com/realm/realm-java/issues/6152 @Test @RunTestInLooperThread diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index 37fd95afd7..d77a9feb0d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -16,433 +16,369 @@ package io.realm; -import android.support.test.annotation.UiThreadTest; -import android.support.test.rule.UiThreadTestRule; +import android.os.SystemClock; import android.support.test.runner.AndroidJUnit4; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import io.reactivex.Flowable; import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; import io.reactivex.functions.Consumer; -import io.reactivex.functions.Predicate; +import io.reactivex.schedulers.Schedulers; +import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.CyclicType; import io.realm.entities.Dog; +import io.realm.internal.util.Pair; +import io.realm.log.RealmLog; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import io.realm.rule.TestRealmConfigurationFactory; -import io.realm.rx.CollectionChange; -import io.realm.rx.ObjectChange; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +/** + * This class contains tests for the RxJava integration. + * + * Note that all tests must be run using @RunTestInLooperThread due to how the Observables + * are constructed. + */ @RunWith(AndroidJUnit4.class) public class RxJavaTests { - @Rule - public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); - @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); - @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - private Realm realm; private Disposable subscription; @Before - public void setUp() throws Exception { + public void setUp() { // For non-LooperThread tests. - realm = Realm.getInstance(configFactory.createConfiguration()); + realm = looperThread.getRealm(); looperThread.runAfterTest(() -> { if (subscription != null && !subscription.isDisposed()) { subscription.dispose(); + realm.close(); + + + + // Wait for Realm Observables to fully close + while (Realm.getGlobalInstanceCount(realm.configuration) > 0) { + RealmLog.error("Counter: " + Realm.getGlobalInstanceCount(realm.configuration)); + SystemClock.sleep(10); + } } }); } - @After - public void tearDown() throws Exception { - // For non-LooperThread tests. - if (realm != null) { - realm.close(); - } + private void disposeSuccessfulTest(BaseRealm testRealm) { + looperThread.postRunnable(() -> { + if (subscription != null) { + subscription.dispose(); + } + if (!testRealm.getConfiguration().equals(realm.getConfiguration())) { + throw new IllegalStateException("This method only works for Realms with the same configuration as the looper Realm"); + } + testRealm.close(); + if (!realm.equals(testRealm)) { + realm.close(); + } + looperThread.postRunnable(new Runnable() { + @Override + public void run() { + // Wait for Subscription to dispose of external resources + if (Realm.getGlobalInstanceCount(testRealm.getConfiguration()) == 0) { + looperThread.testComplete(); + } else { + RealmLog.error("" + Realm.getGlobalInstanceCount(testRealm.getConfiguration())); + looperThread.postRunnable(this); + } + } + }); + }); } @Test - @UiThreadTest + @RunTestInLooperThread public void realmObject_emittedOnSubscribe() { realm.beginTransaction(); - final AllTypes obj = realm.createObject(AllTypes.class); + final AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 42); realm.commitTransaction(); - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = obj.asFlowable().subscribe(new Consumer () { - @Override - public void accept(AllTypes rxObject) throws Exception { - assertTrue(rxObject == obj); - subscribedNotified.set(true); - } + subscription = obj.asFlowable().subscribe(rxObject -> { + assertTrue(rxObject.isFrozen()); + assertNotEquals(rxObject, obj); // Frozen objects are not equal to their live counter parts. + assertEquals(rxObject.getFieldId(), obj.getFieldId()); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test - @UiThreadTest + @RunTestInLooperThread public void realmObject_emitChangesetOnSubscribe() { realm.beginTransaction(); - final AllTypes obj = realm.createObject(AllTypes.class); + final AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 42); realm.commitTransaction(); - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = obj.asChangesetObservable().subscribe(new Consumer>() { - @Override - public void accept(ObjectChange change) throws Exception { - assertTrue(change.getObject() == obj); - assertNull(change.getChangeset()); - subscribedNotified.set(true); - } + subscription = obj.asChangesetObservable().subscribe(change -> { + assertTrue(change.getObject().isFrozen()); + assertEquals(change.getObject().getFieldId(), obj.getFieldId()); + assertNull(change.getChangeset()); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test - @UiThreadTest + @RunTestInLooperThread public void dynamicRealmObject_emitChangesetOnSubscribe() { - DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); + DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); dynamicRealm.beginTransaction(); - final DynamicRealmObject obj = dynamicRealm.createObject(AllTypes.CLASS_NAME); + final DynamicRealmObject obj = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 42); dynamicRealm.commitTransaction(); - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = obj.asChangesetObservable().subscribe(new Consumer>() { - @Override - public void accept(ObjectChange change) throws Exception { - assertTrue(change.getObject() == obj); - assertNull(change.getChangeset()); - subscribedNotified.set(true); - } + subscription = obj.asChangesetObservable() + .subscribe(change -> { + assertTrue(change.getObject().isFrozen()); + assertEquals(change.getObject().getLong(AllJavaTypes.FIELD_ID), obj.getLong(AllJavaTypes.FIELD_ID)); + assertNull(change.getChangeset()); + disposeSuccessfulTest(dynamicRealm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); - dynamicRealm.close(); } @Test @RunTestInLooperThread public void realmObject_emittedOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.getRealm(); realm.beginTransaction(); final AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); - subscription = obj.asFlowable().subscribe(new Consumer() { - @Override - public void accept(AllTypes allTypes) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - looperThread.testComplete(); - } + subscription = obj.asFlowable().subscribe(rxObject -> { + assertTrue(rxObject.isFrozen()); + if (rxObject.isLoaded() && rxObject.getColumnLong() == 0) { + realm.beginTransaction(); + obj.setColumnLong(1); + realm.commitTransaction(); + } else if (rxObject.getColumnLong() == 1) { + disposeSuccessfulTest(realm); } }); - - realm.beginTransaction(); - obj.setColumnLong(1); - realm.commitTransaction(); } @Test @RunTestInLooperThread public void realmObject_emittedChangesetOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.getRealm(); realm.beginTransaction(); final AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); - subscription = obj.asChangesetObservable().subscribe(new Consumer>() { - @Override - public void accept(ObjectChange change) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - assertNotNull(change.getChangeset()); - assertTrue(change.getChangeset().isFieldChanged(AllTypes.FIELD_LONG)); - looperThread.testComplete(); - } + subscription = obj.asChangesetObservable().subscribe(change -> { + AllTypes rxObject = change.getObject(); + assertTrue(rxObject.isFrozen()); + if (rxObject.getColumnLong() == 0) { + realm.beginTransaction(); + obj.setColumnLong(1); + realm.commitTransaction(); + } else if (rxObject.getColumnLong() == 1) { + assertNotNull(change.getChangeset()); + assertTrue(change.getChangeset().isFieldChanged(AllTypes.FIELD_LONG)); + disposeSuccessfulTest(realm); } }); - - realm.beginTransaction(); - obj.setColumnLong(1); - realm.commitTransaction(); } @Test @RunTestInLooperThread public void dynamicRealmObject_emittedChangesetOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); - looperThread.closeAfterTest(realm); - realm.beginTransaction(); - final DynamicRealmObject obj = realm.createObject(AllTypes.CLASS_NAME); - realm.commitTransaction(); + DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); - subscription = obj.asChangesetObservable().subscribe(new Consumer>() { - @Override - public void accept(ObjectChange change) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - assertNotNull(change.getChangeset()); - assertTrue(change.getChangeset().isFieldChanged(AllTypes.FIELD_LONG)); - looperThread.testComplete(); - } + dynamicRealm.beginTransaction(); + final DynamicRealmObject obj = dynamicRealm.createObject(AllTypes.CLASS_NAME); + dynamicRealm.commitTransaction(); + + subscription = obj.asChangesetObservable().subscribe(change -> { + DynamicRealmObject rxObject = change.getObject(); + assertTrue(rxObject.isFrozen()); + if (rxObject.getLong(AllTypes.FIELD_LONG) == 0) { + dynamicRealm.beginTransaction(); + obj.setLong(AllTypes.FIELD_LONG, 1); + dynamicRealm.commitTransaction(); + } else if (rxObject.getLong(AllTypes.FIELD_LONG) == 1) { + assertNotNull(change.getChangeset()); + assertTrue(change.getChangeset().isFieldChanged(AllTypes.FIELD_LONG)); + disposeSuccessfulTest(dynamicRealm); } }); - realm.beginTransaction(); - obj.setLong(AllTypes.FIELD_LONG, 1); - realm.commitTransaction(); } @Test - @UiThreadTest + @RunTestInLooperThread public void findFirst_emittedOnSubscribe() { realm.beginTransaction(); realm.createObject(AllTypes.class).setColumnLong(42); realm.commitTransaction(); - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); subscription = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 42).findFirst().asFlowable() - .subscribe(new Consumer () { - @Override - public void accept(AllTypes allTypes) throws Exception { - subscribedNotified.set(true); - } + .subscribe(rxObject -> { + assertTrue(rxObject.isFrozen()); + assertEquals(42, rxObject.getColumnLong()); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test - @UiThreadTest + @RunTestInLooperThread public void findFirstAsync_emittedOnSubscribe() { realm.beginTransaction(); - realm.createObject(AllTypes.class); + realm.createObject(AllTypes.class).setColumnLong(42); realm.commitTransaction(); - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final AllTypes asyncObj = realm.where(AllTypes.class).findFirstAsync(); - subscription = asyncObj.asFlowable().subscribe(new Consumer() { - @Override - public void accept(AllTypes rxObject) throws Exception { - assertTrue(rxObject == asyncObj); - subscribedNotified.set(true); - } + subscription = asyncObj.asFlowable().subscribe(rxObject -> { + assertTrue(rxObject.isFrozen()); + assertEquals(42, rxObject.getColumnLong()); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test @RunTestInLooperThread public void findFirstAsync_emittedOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.getRealm(); realm.beginTransaction(); - AllTypes obj = realm.createObject(AllTypes.class); + realm.createObject(AllTypes.class).setColumnLong(1); realm.commitTransaction(); - subscription = realm.where(AllTypes.class).findFirstAsync().asFlowable().subscribe(new Consumer() { - @Override - public void accept(AllTypes rxObject) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - looperThread.testComplete(); - } + + subscription = realm.where(AllTypes.class).findFirstAsync().asFlowable().subscribe(rxObject -> { + assertTrue(rxObject.isFrozen()); + if (rxObject.getColumnLong() == 1) { + realm.executeTransaction(r -> realm.where(AllTypes.class).findFirst().setColumnLong(42)); + } else if (rxObject.getColumnLong() == 42) { + disposeSuccessfulTest(realm); } }); - - realm.beginTransaction(); - obj.setColumnLong(1); - realm.commitTransaction(); } @Test @RunTestInLooperThread public void findFirstAsync_emittedOnDelete() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - final Realm realm = looperThread.getRealm(); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); - subscription = realm.where(AllTypes.class).findFirstAsync().asFlowable().subscribe(new Consumer() { - @Override - public void accept(AllTypes rxObject) throws Exception { - switch (subscriberCalled.incrementAndGet()) { - case 1: - assertFalse(rxObject.isLoaded()); - break; - case 2: - assertTrue(rxObject.isLoaded()); - assertTrue(rxObject.isValid()); - realm.executeTransactionAsync(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.delete(AllTypes.class); - } - }); - break; - case 3: - assertTrue(rxObject.isLoaded()); - assertFalse(rxObject.isValid()); - looperThread.testComplete(); - break; - default: - fail(); - } + subscription = realm.where(AllTypes.class).findFirstAsync().asFlowable().subscribe(rxObject -> { + assertTrue(rxObject.isFrozen()); + if (!rxObject.isLoaded()) { + //noinspection UnnecessaryReturnStatement + return; + } else if (rxObject.isValid()) { + realm.executeTransactionAsync(r -> r.delete(AllTypes.class)); + } else if (!rxObject.isValid()) { + disposeSuccessfulTest(realm); } }); } @Test - @UiThreadTest + @RunTestInLooperThread public void realmResults_emittedOnSubscribe() { - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final RealmResults results = realm.where(AllTypes.class).findAll(); - subscription = results.asFlowable().subscribe(new Consumer>() { - @Override - @SuppressWarnings("ReferenceEquality") - public void accept(RealmResults rxResults) throws Exception { - assertTrue(rxResults == results); - subscribedNotified.set(true); - } + subscription = results.asFlowable().subscribe(rxResults -> { + assertTrue(rxResults.isFrozen()); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test - @UiThreadTest + @RunTestInLooperThread public void realmResults_emittedChangesetOnSubscribe() { - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final RealmResults results = realm.where(AllTypes.class).findAll(); - subscription = results.asChangesetObservable().subscribe(new Consumer>>() { - @Override - public void accept(CollectionChange> change) throws Exception { - assertEquals(results, change.getCollection()); - subscribedNotified.set(true); - } + subscription = results.asChangesetObservable().subscribe(change -> { + RealmResults rxResults = change.getCollection(); + assertTrue(rxResults.isFrozen()); + assertEquals(results, rxResults); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test - @UiThreadTest + @RunTestInLooperThread public void realmList_emittedOnSubscribe() { - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); realm.beginTransaction(); final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + list.add(new Dog("dog")); realm.commitTransaction(); - subscription = list.asFlowable().subscribe(new Consumer>() { - @Override - @SuppressWarnings("ReferenceEquality") - public void accept(RealmList rxList) throws Exception { - assertTrue(rxList == list); - subscribedNotified.set(true); - } + + subscription = list.asFlowable().subscribe(rxList -> { + assertTrue(rxList.isFrozen()); + assertEquals(1, rxList.size()); + assertEquals("dog", rxList.first().getName()); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test - @UiThreadTest + @RunTestInLooperThread public void realmList_emittedChangesetOnSubscribe() { - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); realm.beginTransaction(); final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + list.add(new Dog("dog")); realm.commitTransaction(); - subscription = list.asChangesetObservable().subscribe(new Consumer>>() { - @Override - public void accept(CollectionChange> change) throws Exception { - assertEquals(list, change.getCollection()); - assertNull(change.getChangeset()); - subscribedNotified.set(true); - } + + subscription = list.asChangesetObservable().subscribe(change -> { + RealmList rxList = change.getCollection(); + assertTrue(rxList.isFrozen()); + assertEquals(1, rxList.size()); + assertEquals("dog", rxList.first().getName()); + assertNull(change.getChangeset()); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test - @UiThreadTest + @RunTestInLooperThread public void dynamicRealmResults_emittedOnSubscribe() { - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); final RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); - subscription = results.asFlowable().subscribe(new Consumer>() { - @Override - @SuppressWarnings("ReferenceEquality") - public void accept(RealmResults rxResults) throws Exception { - assertTrue(rxResults == results); - subscribedNotified.set(true); - } + subscription = results.asFlowable().subscribe(rxResults -> { + assertTrue(rxResults.isFrozen()); + assertTrue(rxResults.equals(results)); + disposeSuccessfulTest(dynamicRealm); }); - assertTrue(subscribedNotified.get()); - dynamicRealm.close(); - subscription.dispose(); } @Test - @UiThreadTest + @RunTestInLooperThread public void dynamicRealmResults_emittedChangesetOnSubscribe() { final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); - subscription = results.asChangesetObservable().subscribe(new Consumer>>() { - @Override - public void accept(CollectionChange> change) throws Exception { - assertEquals(results, change.getCollection()); - assertNull(change.getChangeset()); - subscribedNotified.set(true); - } + subscription = results.asChangesetObservable().subscribe(change -> { + assertTrue(change.getCollection().isFrozen()); + assertEquals(results, change.getCollection()); + assertNull(change.getChangeset()); + disposeSuccessfulTest(dynamicRealm); }); - assertTrue(subscribedNotified.get()); - dynamicRealm.close(); - subscription.dispose(); } @Test @RunTestInLooperThread public void realmResults_emittedOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.getRealm(); - realm.beginTransaction(); RealmResults results = realm.where(AllTypes.class).findAll(); - realm.commitTransaction(); - subscription = results.asFlowable().subscribe(new Consumer>() { - @Override - public void accept(RealmResults allTypes) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - looperThread.testComplete(); - } + subscription = results.asFlowable().subscribe(rxResults -> { + assertTrue(rxResults.isFrozen()); + if (rxResults.size() == 1) { + disposeSuccessfulTest(realm); } }); @@ -454,91 +390,69 @@ public void accept(RealmResults allTypes) throws Exception { @Test @RunTestInLooperThread public void realmResults_emittedChangesetOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.getRealm(); - realm.beginTransaction(); RealmResults results = realm.where(AllTypes.class).findAll(); - realm.commitTransaction(); - subscription = results.asChangesetObservable().subscribe(new Consumer>>() { - @Override - public void accept(CollectionChange> change) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - assertEquals(1, change.getChangeset().getInsertions().length); - looperThread.testComplete(); - } + subscription = results.asChangesetObservable().subscribe(change -> { + RealmResults rxResults = change.getCollection(); + assertTrue(rxResults.isFrozen()); + if (rxResults.isEmpty()) { + realm.executeTransaction(r -> r.createObject(AllTypes.class)); + } else if (rxResults.size() == 1) { + assertEquals(1, change.getChangeset().getInsertions().length); + disposeSuccessfulTest(realm); } }); - realm.beginTransaction(); - realm.createObject(AllTypes.class); - realm.commitTransaction(); } @Test @RunTestInLooperThread public void realmList_emittedOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.getRealm(); realm.beginTransaction(); final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); realm.commitTransaction(); - subscription = list.asFlowable().subscribe(new Consumer>() { - @Override - public void accept(RealmList dogs) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - assertEquals(1, list.size()); - looperThread.testComplete(); - } + subscription = list.asFlowable().subscribe(rxList -> { + assertTrue(rxList.isFrozen()); + if (rxList.isEmpty()) { + realm.executeTransaction(r -> list.add(new Dog())); + } else { + assertEquals(1, list.size()); + disposeSuccessfulTest(realm); } }); - - realm.beginTransaction(); - list.add(new Dog()); - realm.commitTransaction(); } @Test @RunTestInLooperThread public void realmList_emittedChangesetOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.getRealm(); realm.beginTransaction(); final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); realm.commitTransaction(); - subscription = list.asChangesetObservable().subscribe(new Consumer>>() { - @Override - public void accept(CollectionChange> change) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - assertEquals(1, list.size()); - assertEquals(1, change.getChangeset().getInsertions().length); - looperThread.testComplete(); - } + subscription = list.asChangesetObservable().subscribe(change -> { + RealmList rxList = change.getCollection(); + assertTrue(rxList.isFrozen()); + if (rxList.isLoaded() && rxList.size() == 0) { + realm.beginTransaction(); + list.add(new Dog()); + realm.commitTransaction(); + } else if (rxList.size() == 1) { + assertEquals(1, change.getChangeset().getInsertions().length); + disposeSuccessfulTest(realm); } }); - - realm.beginTransaction(); - list.add(new Dog()); - realm.commitTransaction(); } @Test @RunTestInLooperThread public void dynamicRealmResults_emittedOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); - dynamicRealm.beginTransaction(); RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); - dynamicRealm.commitTransaction(); - subscription = results.asFlowable().subscribe(new Consumer>() { - @Override - public void accept(RealmResults dynamicRealmObjects) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - dynamicRealm.close(); - looperThread.testComplete(); - } + subscription = results.asFlowable().subscribe(rxResults -> { + assertTrue(rxResults.isFrozen()); + if (rxResults.isLoaded() && rxResults.size() == 1) { + disposeSuccessfulTest(dynamicRealm); } }); @@ -550,43 +464,37 @@ public void accept(RealmResults dynamicRealmObjects) throws @Test @RunTestInLooperThread public void dynamicRealmResults_emittedChangesetOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); - looperThread.closeAfterTest(dynamicRealm); - dynamicRealm.beginTransaction(); RealmResults results = dynamicRealm.where(AllTypes.CLASS_NAME).findAll(); - dynamicRealm.commitTransaction(); - subscription = results.asChangesetObservable().subscribe(new Consumer>>() { - @Override - public void accept(CollectionChange> change) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - assertEquals(1, change.getChangeset().getInsertions().length); - looperThread.testComplete(); + subscription = results.asChangesetObservable() + .subscribe(change -> { + RealmResults collection = change.getCollection(); + if (collection.isLoaded() && collection.isEmpty()) { + looperThread.postRunnable(() -> { + DynamicRealm dynRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + dynRealm.executeTransaction(dr -> { + dr.createObject(AllTypes.CLASS_NAME); + }); + dynRealm.close(); + }); } - } - }); - dynamicRealm.beginTransaction(); - dynamicRealm.createObject(AllTypes.CLASS_NAME); - dynamicRealm.commitTransaction(); + if (collection.isLoaded() && collection.size() == 1) { + assertEquals(1, change.getChangeset().getInsertions().length); + disposeSuccessfulTest(dynamicRealm); + } + }); } @Test - @UiThreadTest + @RunTestInLooperThread public void findAllAsync_emittedOnSubscribe() { - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); final RealmResults results = realm.where(AllTypes.class).findAllAsync(); - subscription = results.asFlowable().subscribe(new Consumer>() { - @Override - @SuppressWarnings("ReferenceEquality") - public void accept(RealmResults rxResults) throws Exception { - assertTrue(rxResults == results); - subscribedNotified.set(true); - } + subscription = results.asFlowable().subscribe(rxResults -> { + assertTrue(rxResults.isFrozen()); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test @@ -594,12 +502,10 @@ public void accept(RealmResults rxResults) throws Exception { public void findAllAsync_emittedOnUpdate() { final AtomicInteger subscriberCalled = new AtomicInteger(0); Realm realm = looperThread.getRealm(); - subscription = realm.where(AllTypes.class).findAllAsync().asFlowable().subscribe(new Consumer>() { - @Override - public void accept(RealmResults allTypes) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - looperThread.testComplete(); - } + subscription = realm.where(AllTypes.class).findAllAsync().asFlowable().subscribe(rxResults -> { + assertTrue(rxResults.isFrozen()); + if (subscriberCalled.incrementAndGet() == 2) { + disposeSuccessfulTest(realm); } }); @@ -609,314 +515,244 @@ public void accept(RealmResults allTypes) throws Exception { } @Test - @UiThreadTest + @RunTestInLooperThread public void realm_emittedOnSubscribe() { - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = realm.asFlowable().subscribe(new Consumer() { - @Override - public void accept(Realm rxRealm) throws Exception { - assertTrue(rxRealm == realm); - subscribedNotified.set(true); - } + subscription = realm.asFlowable().subscribe(rxRealm -> { + assertTrue(rxRealm.isFrozen()); + assertEquals(realm.getPath(), rxRealm.getPath()); + disposeSuccessfulTest(realm); }); - assertTrue(subscribedNotified.get()); - subscription.dispose(); } @Test @RunTestInLooperThread public void realm_emittedOnUpdate() { - final AtomicInteger subscriberCalled = new AtomicInteger(0); - Realm realm = looperThread.getRealm(); - subscription = realm.asFlowable().subscribe(new Consumer() { - @Override - public void accept(Realm realm) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - looperThread.testComplete(); - } + subscription = realm.asFlowable().subscribe(rxRealm -> { + assertTrue(rxRealm.isFrozen()); + if (rxRealm.isEmpty()) { + realm.executeTransaction(r -> r.createObject(AllTypes.class)); + } else { + assertEquals(1, realm.where(AllTypes.class).count()); + disposeSuccessfulTest(realm); } }); - - realm.beginTransaction(); - realm.createObject(AllTypes.class); - realm.commitTransaction(); } @Test - @UiThreadTest + @RunTestInLooperThread + @SuppressWarnings("ReferenceEquality") public void dynamicRealm_emittedOnSubscribe() { final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = dynamicRealm.asFlowable().subscribe(new Consumer() { - @Override - public void accept(DynamicRealm rxRealm) throws Exception { - assertTrue(rxRealm == dynamicRealm); - subscribedNotified.set(true); - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - throwable.printStackTrace(); - fail(); - } + subscription = dynamicRealm.asFlowable().subscribe(rxRealm -> { + assertTrue(rxRealm.isFrozen()); + assertEquals(rxRealm.getPath(), dynamicRealm.getPath()); + assertEquals(rxRealm.sharedRealm.getVersionID(), dynamicRealm.sharedRealm.getVersionID()); + disposeSuccessfulTest(dynamicRealm); }); - - assertTrue(subscribedNotified.get()); - dynamicRealm.close(); - subscription.dispose(); } @Test @RunTestInLooperThread public void dynamicRealm_emittedOnUpdate() { final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); - final AtomicInteger subscriberCalled = new AtomicInteger(0); - subscription = dynamicRealm.asFlowable().subscribe(new Consumer() { - @Override - public void accept(DynamicRealm dynamicRealm) throws Exception { - if (subscriberCalled.incrementAndGet() == 2) { - dynamicRealm.close(); - looperThread.testComplete(); - } + subscription = dynamicRealm.asFlowable().subscribe(rxRealm -> { + assertTrue(rxRealm.isFrozen()); + if (rxRealm.isEmpty()) { + dynamicRealm.executeTransaction(r -> r.createObject("AllTypes")); + } else { + assertEquals(1, rxRealm.where(AllTypes.CLASS_NAME).count()); + disposeSuccessfulTest(dynamicRealm); } }); - - dynamicRealm.beginTransaction(); - dynamicRealm.createObject("AllTypes"); - dynamicRealm.commitTransaction(); } @Test - @UiThreadTest + @RunTestInLooperThread + @SuppressWarnings("ReferenceEquality") public void unsubscribe_sameThread() { - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - subscription = realm.asFlowable().subscribe(new Consumer() { - @Override - public void accept(Realm rxRealm) throws Exception { - assertTrue(rxRealm == realm); - subscribedNotified.set(true); - } + subscription = realm.asFlowable() + .doOnCancel(() -> { + disposeSuccessfulTest(realm); + }) + .subscribe(rxRealm -> { + assertTrue(rxRealm.isFrozen()); + assertEquals(rxRealm.getPath(), realm.getPath()); + assertEquals(rxRealm.sharedRealm.getVersionID(), realm.sharedRealm.getVersionID()); }); - assertEquals(1, realm.sharedRealm.realmNotifier.getListenersListSize()); subscription.dispose(); - assertEquals(0, realm.sharedRealm.realmNotifier.getListenersListSize()); } @Test - @UiThreadTest + @RunTestInLooperThread + @SuppressWarnings("ReferenceEquality") public void unsubscribe_fromOtherThread() { - final CountDownLatch unsubscribeCompleted = new CountDownLatch(1); - final AtomicBoolean subscribedNotified = new AtomicBoolean(false); - final Disposable subscription = realm.asFlowable().subscribe(new Consumer() { - @Override - public void accept(Realm rxRealm) throws Exception { - assertTrue(rxRealm == realm); - subscribedNotified.set(true); - } - }); - assertTrue(subscribedNotified.get()); - assertEquals(1, realm.sharedRealm.realmNotifier.getListenersListSize()); - new Thread(new Runnable() { - @Override - public void run() { - try { - subscription.dispose(); - fail(); - } catch (IllegalStateException ignored) { - } finally { - unsubscribeCompleted.countDown(); + subscription = realm.asFlowable() + .doFinally(() -> { + disposeSuccessfulTest(realm); + }) + .subscribe(new Consumer() { + @Override + public void accept(Realm rxRealm) { + assertTrue(rxRealm.isFrozen()); + assertEquals(rxRealm.getPath(), realm.getPath()); + assertEquals(rxRealm.sharedRealm.getVersionID(), realm.sharedRealm.getVersionID()); + looperThread.postRunnable(() -> { + Thread t = new Thread(() -> subscription.dispose()); + t.start(); + looperThread.keepStrongReference(t); + }); } - } - }).start(); - TestHelper.awaitOrFail(unsubscribeCompleted); - assertEquals(1, realm.sharedRealm.realmNotifier.getListenersListSize()); - // We cannot call subscription.dispose() again, so manually close the extra Realm instance opened by - // the Observable. - realm.close(); + }); } @Test - @UiThreadTest + @RunTestInLooperThread public void wrongGenericClassThrows() { realm.beginTransaction(); final AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); Flowable obs = obj.asFlowable(); - @SuppressWarnings("unused") - Disposable subscription = obs.subscribe(new Consumer() { - @Override - public void accept(CyclicType cyclicType) throws Exception { - fail(); - } - }, new Consumer() { - @Override - public void accept(Throwable ignored) throws Exception { - } - }); + subscription = obs.subscribe( + cyclicType -> fail(), + ignoredError -> { + disposeSuccessfulTest(realm); + } + ); } @Test - @UiThreadTest + @RunTestInLooperThread public void realm_closeInDoOnUnsubscribe() { Flowable observable = realm.asFlowable() - .doOnCancel(new Action() { - @Override - public void run() throws Exception { - realm.close(); - } + .doOnCancel(() -> realm.close()) + .doFinally(() -> { + looperThread.postRunnable(() -> { + assertTrue(realm.isClosed()); + disposeSuccessfulTest(realm); + }); }); - subscription = observable.subscribe(new Consumer() { - @Override - public void accept(Realm realm) throws Exception { - assertEquals(2, Realm.getLocalInstanceCount(realm.getConfiguration())); - } + subscription = observable.subscribe(ignore -> { + assertEquals(3, Realm.getLocalInstanceCount(realm.getConfiguration())); + subscription.dispose(); }); - - subscription.dispose(); - assertTrue(realm.isClosed()); } @Test - @UiThreadTest + @RunTestInLooperThread public void dynamicRealm_closeInDoOnUnsubscribe() { final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); Flowable observable = dynamicRealm.asFlowable() - .doOnCancel(new Action() { - @Override - public void run() throws Exception { - dynamicRealm.close(); - } + .doOnCancel(() -> { + dynamicRealm.close(); + }) + .doFinally(() -> { + assertFalse(dynamicRealm.isClosed()); + looperThread.postRunnable(() -> { + assertTrue(dynamicRealm.isClosed()); + disposeSuccessfulTest(dynamicRealm); + }); }); - subscription = observable.subscribe(new Consumer() { - @Override - public void accept(DynamicRealm ignored) throws Exception { - } + subscription = observable.subscribe(ignored -> { + subscription.dispose(); }); - - subscription.dispose(); - assertTrue(dynamicRealm.isClosed()); } @Test - @UiThreadTest + @RunTestInLooperThread public void realmResults_closeInDoOnUnsubscribe() { Flowable> observable = realm.where(AllTypes.class).findAll().asFlowable() - .doOnCancel(new Action() { - @Override - public void run() throws Exception { - realm.close(); - } - }); - - subscription = observable.subscribe(new Consumer>() { - @Override - public void accept(RealmResults ignored) throws Exception { - } - }); + .doOnCancel(() -> realm.close()); + subscription = observable.subscribe(ignored -> {}); subscription.dispose(); assertTrue(realm.isClosed()); + disposeSuccessfulTest(realm); } @Test - @UiThreadTest + @RunTestInLooperThread public void realmList_closeInDoOnUnsubscribe() { realm.beginTransaction(); RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); realm.commitTransaction(); - Flowable> observable = list.asFlowable().doOnCancel(new Action() { - @Override - public void run() throws Exception { - realm.close(); - } - }); - subscription = observable.subscribe(new Consumer>() { - @Override - public void accept(RealmList ignored) throws Exception { - } - }); + Flowable> observable = list.asFlowable() + .doOnCancel(() -> realm.close()) + .doFinally(() -> { + looperThread.postRunnable(() -> { + assertTrue(realm.isClosed()); + disposeSuccessfulTest(realm); + }); + }); - subscription.dispose(); - assertTrue(realm.isClosed()); + subscription = observable.subscribe(ignored -> { + subscription.dispose(); + }); } @Test - @UiThreadTest + @RunTestInLooperThread public void dynamicRealmResults_closeInDoOnUnsubscribe() { - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); Flowable> flowable = dynamicRealm.where(AllTypes.CLASS_NAME).findAll().asFlowable() - .doOnCancel(new Action() { - @Override - public void run() throws Exception { - dynamicRealm.close(); - } + .doOnCancel(() -> { + dynamicRealm.close(); + }) + .doFinally(() -> { + looperThread.postRunnable(() -> { + assertTrue(dynamicRealm.isClosed()); + disposeSuccessfulTest(dynamicRealm); + }); }); - subscription = flowable.subscribe(new Consumer>() { - @Override - public void accept(RealmResults ignored) throws Exception { - } + subscription = flowable.subscribe(ignored -> { + subscription.dispose(); }); - - subscription.dispose(); - assertTrue(dynamicRealm.isClosed()); } @Test - @UiThreadTest + @RunTestInLooperThread public void realmObject_closeInDoOnUnsubscribe() { realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); Flowable flowable = realm.where(AllTypes.class).findFirst().asFlowable() - .doOnCancel(new Action() { - @Override - public void run() throws Exception { - realm.close(); - } + .doOnCancel(() -> realm.close()) + .doFinally(() -> { + looperThread.postRunnable(() -> { + assertTrue(realm.isClosed()); + disposeSuccessfulTest(realm); + }); }); - subscription = flowable.subscribe(new Consumer() { - @Override - public void accept(AllTypes ignored) throws Exception { - } + subscription = flowable.subscribe(ignored -> { + subscription.dispose(); }); - - subscription.dispose(); - assertTrue(realm.isClosed()); } @Test - @UiThreadTest + @RunTestInLooperThread public void dynamicRealmObject_closeInDoOnUnsubscribe() { realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); - final DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); - - Flowable flowable = dynamicRealm.where(AllTypes.CLASS_NAME).findFirst().asFlowable() - .doOnCancel(new Action() { - @Override - public void run() throws Exception { - dynamicRealm.close(); - } - }); - subscription = flowable.subscribe(new Consumer() { - @Override - public void accept(DynamicRealmObject ignored) throws Exception { - } - }); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); - subscription.dispose(); - assertTrue(dynamicRealm.isClosed()); + subscription = dynamicRealm.where(AllTypes.CLASS_NAME).findFirst().asFlowable() + .doOnCancel(() -> dynamicRealm.close()) + .doFinally(() -> { + looperThread.postRunnable(() -> { + assertTrue(dynamicRealm.isClosed()); + disposeSuccessfulTest(dynamicRealm); + }); + }).subscribe(ignored -> subscription.dispose()); } // Tests that Observables keep strong references to their parent, so they are not accidentally GC'ed while @@ -943,7 +779,7 @@ public void realmResults_gcStressTest() { // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. Runtime.getRuntime().gc(); if (innerCounter.incrementAndGet() == TEST_SIZE) { - looperThread.testComplete(); + disposeSuccessfulTest(realm); } }, throwable -> fail(throwable.toString())); } @@ -957,40 +793,26 @@ public void realmResults_gcStressTest() { public void dynamicRealmResults_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); - looperThread.closeAfterTest(realm); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); - realm.beginTransaction(); + dynamicRealm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { - realm.createObject(AllTypes.CLASS_NAME).set(AllTypes.FIELD_LONG, i); + dynamicRealm.createObject(AllTypes.CLASS_NAME).set(AllTypes.FIELD_LONG, i); } - realm.commitTransaction(); + dynamicRealm.commitTransaction(); for (int i = 0; i < TEST_SIZE; i++) { // Doesn't keep a reference to the Observable. - realm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findAllAsync().asFlowable() - .filter(new Predicate>() { - @Override - public boolean test(RealmResults results) throws Exception { - return results.isLoaded(); - } - }) + dynamicRealm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findAllAsync().asFlowable() + .filter(results -> results.isLoaded()) .take(1) // Unsubscribes from Realm. - .subscribe(new Consumer>() { - @Override - public void accept(RealmResults dynamicRealmObjects) throws Exception { - // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. - Runtime.getRuntime().gc(); - if (innerCounter.incrementAndGet() == TEST_SIZE) { - looperThread.testComplete(); - } - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - fail(throwable.toString()); + .subscribe(dynamicRealmObjects -> { + // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. + Runtime.getRuntime().gc(); + if (innerCounter.incrementAndGet() == TEST_SIZE) { + disposeSuccessfulTest(dynamicRealm); } - }); + }, throwable -> fail(throwable.toString())); } } @@ -1002,7 +824,6 @@ public void accept(Throwable throwable) throws Exception { public void realmObject_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final Realm realm = looperThread.getRealm(); realm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { @@ -1013,28 +834,15 @@ public void realmObject_gcStressTest() { for (int i = 0; i < TEST_SIZE; i++) { // Doesn't keep a reference to the Observable. realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, i).findFirstAsync().asFlowable() - .filter(new Predicate() { - @Override - public boolean test(AllTypes obj) throws Exception { - return obj.isLoaded(); - } - }) + .filter(obj -> obj.isLoaded()) .take(1) // Unsubscribes from Realm. - .subscribe(new Consumer() { - @Override - public void accept(AllTypes allTypes) throws Exception { - // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. - Runtime.getRuntime().gc(); - if (innerCounter.incrementAndGet() == TEST_SIZE) { - looperThread.testComplete(); - } - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - fail(throwable.toString()); + .subscribe(allTypes -> { + // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. + Runtime.getRuntime().gc(); + if (innerCounter.incrementAndGet() == TEST_SIZE) { + disposeSuccessfulTest(realm); } - }); + }, throwable -> fail(throwable.toString())); } } @@ -1046,41 +854,282 @@ public void accept(Throwable throwable) throws Exception { public void dynamicRealmObject_gcStressTest() { final int TEST_SIZE = 50; final AtomicLong innerCounter = new AtomicLong(); - final DynamicRealm realm = DynamicRealm.getInstance(looperThread.getConfiguration()); - looperThread.closeAfterTest(realm); + final DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); - realm.beginTransaction(); + dynamicRealm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { - realm.createObject(AllTypes.CLASS_NAME).set(AllTypes.FIELD_LONG, i); + dynamicRealm.createObject(AllTypes.CLASS_NAME).set(AllTypes.FIELD_LONG, i); } - realm.commitTransaction(); + dynamicRealm.commitTransaction(); for (int i = 0; i < TEST_SIZE; i++) { // Doesn't keep a reference to the Observable. - realm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findFirstAsync().asFlowable() - .filter(new Predicate() { - @Override - public boolean test(DynamicRealmObject obj) throws Exception { - return obj.isLoaded(); - } - }) + dynamicRealm.where(AllTypes.CLASS_NAME).equalTo(AllTypes.FIELD_LONG, i).findFirstAsync().asFlowable() + .filter(obj -> obj.isLoaded()) .take(1) // Unsubscribes from Realm. - .subscribe(new Consumer() { - @Override - public void accept(DynamicRealmObject dynamicRealmObject) throws Exception { - // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. - Runtime.getRuntime().gc(); - if (innerCounter.incrementAndGet() == TEST_SIZE) { - looperThread.testComplete(); - } - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - fail(throwable.toString()); + .subscribe(dynamicRealmObject -> { + // Not guaranteed, but can result in the GC of other RealmResults waiting for a result. + Runtime.getRuntime().gc(); + if (innerCounter.incrementAndGet() == TEST_SIZE) { + disposeSuccessfulTest(dynamicRealm); } - }); + }, throwable -> fail(throwable.toString())); } } + + @Test + @RunTestInLooperThread + public void asFlowable_frozenRealm() { + Realm frozenRealm = realm.freeze(); + subscription = frozenRealm.asFlowable() + .subscribe(rxRealm -> { + assertEquals(frozenRealm, rxRealm); + disposeSuccessfulTest(realm); + }); + } + + @Test + @RunTestInLooperThread + public void asFlowable_frozenDynamicRealm() { + DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + DynamicRealm frozenDynamicRealm = dynamicRealm.freeze(); + subscription = frozenDynamicRealm.asFlowable() + .subscribe(rxRealm -> { + assertEquals(frozenDynamicRealm, rxRealm); + dynamicRealm.close(); + disposeSuccessfulTest(dynamicRealm); + }); + } + + @Test + @RunTestInLooperThread + public void asFlowable_frozenRealmResults() { + final RealmResults frozenResults = realm.where(AllTypes.class).findAll().freeze(); + subscription = frozenResults.asFlowable().subscribe(rxResults -> { + assertTrue(rxResults.isFrozen()); + assertEquals(frozenResults, rxResults); + disposeSuccessfulTest(realm); + }); + } + + @Test + @RunTestInLooperThread + public void asChangesetObservable_frozenRealmResults() { + final RealmResults frozenResults = realm.where(AllTypes.class).findAll().freeze(); + subscription = frozenResults.asChangesetObservable().subscribe(change -> { + RealmResults rxResults = change.getCollection(); + assertTrue(rxResults.isFrozen()); + assertEquals(frozenResults, rxResults); + assertNull(change.getChangeset()); + disposeSuccessfulTest(realm); + }); + } + + @Test + @RunTestInLooperThread + public void asFlowable_frozenDynamicRealmResults() { + DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + final RealmResults frozenResults = dynamicRealm.where(AllTypes.CLASS_NAME).findAll().freeze(); + subscription = frozenResults.asFlowable().subscribe(rxResults -> { + assertTrue(rxResults.isFrozen()); + assertEquals(frozenResults, rxResults); + disposeSuccessfulTest(dynamicRealm); + }); + } + + @Test + @RunTestInLooperThread + public void asChangesetObservable_frozenDynamicRealmResults() { + DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + final RealmResults frozenResults = dynamicRealm.where(AllTypes.CLASS_NAME).findAll().freeze(); + subscription = frozenResults.asChangesetObservable().subscribe(change -> { + RealmResults rxResults = change.getCollection(); + assertTrue(rxResults.isFrozen()); + assertEquals(frozenResults, rxResults); + assertNull(change.getChangeset()); + disposeSuccessfulTest(dynamicRealm); + }); + } + + @Test + @RunTestInLooperThread + public void asFlowable_frozenRealmList() { + realm.beginTransaction(); + final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + list.add(new Dog("dog")); + realm.commitTransaction(); + + RealmList frozenList = list.freeze(); + subscription = frozenList.asFlowable().subscribe(rxList -> { + assertTrue(rxList.isFrozen()); + assertEquals(frozenList, rxList); + disposeSuccessfulTest(realm); + }); + } + + @Test + @RunTestInLooperThread + public void asChangesetObservable_frozenRealmList() { + realm.beginTransaction(); + final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + list.add(new Dog("dog")); + realm.commitTransaction(); + + RealmList frozenList = list.freeze(); + subscription = frozenList.asChangesetObservable().subscribe(change -> { + RealmList rxList = change.getCollection(); + assertTrue(rxList.isFrozen()); + assertEquals(frozenList, rxList); + assertNull(change.getChangeset()); + disposeSuccessfulTest(realm); + }); + } + + @Test + @RunTestInLooperThread + public void asFlowable_frozenDynamicRealmList() { + realm.beginTransaction(); + final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + list.add(new Dog("dog")); + realm.commitTransaction(); + + DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + RealmList frozenList = dynamicRealm.where(AllTypes.CLASS_NAME).findFirst().getList(AllTypes.FIELD_REALMLIST).freeze(); + + subscription = frozenList.asFlowable().subscribe(rxList -> { + assertTrue(rxList.isFrozen()); + assertEquals(frozenList, rxList); + disposeSuccessfulTest(dynamicRealm); + }); + } + + @Test + @RunTestInLooperThread + public void asChangesetObservable_frozenDynamicRealmList() { + realm.beginTransaction(); + final RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); + list.add(new Dog("dog")); + realm.commitTransaction(); + + DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + RealmList frozenList = dynamicRealm.where(AllTypes.CLASS_NAME).findFirst().getList(AllTypes.FIELD_REALMLIST).freeze(); + + subscription = frozenList.asChangesetObservable().subscribe(change -> { + RealmList rxList = change.getCollection(); + assertTrue(rxList.isFrozen()); + assertEquals(frozenList, rxList); + assertNull(change.getChangeset()); + disposeSuccessfulTest(dynamicRealm); + }); + } + + @Test + @RunTestInLooperThread + public void asFlowable_frozenRealmObject() { + realm.beginTransaction(); + final AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 42); + realm.commitTransaction(); + + subscription = obj.freeze().asFlowable().subscribe(rxObject -> { + assertTrue(rxObject.isFrozen()); + assertNotEquals(rxObject, obj); + assertEquals(rxObject.getFieldId(), obj.getFieldId()); + disposeSuccessfulTest(realm); + }); + } + + @Test + @RunTestInLooperThread + public void asChangesetObservable_frozenRealmObject() { + realm.beginTransaction(); + final AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 42); + realm.commitTransaction(); + + subscription = obj.freeze().asChangesetObservable().subscribe(change -> { + AllJavaTypes rxObject = change.getObject(); + assertTrue(rxObject.isFrozen()); + assertNotEquals(rxObject, obj); + assertEquals(rxObject.getFieldId(), obj.getFieldId()); + disposeSuccessfulTest(realm); + }); + } + + @Test + @RunTestInLooperThread + public void asFlowable_frozenDynamicRealmObject() { + realm.beginTransaction(); + realm.createObject(AllTypes.class); + realm.commitTransaction(); + + DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + DynamicRealmObject obj = dynamicRealm.where(AllTypes.CLASS_NAME).findFirst(); + + DynamicRealmObject frozenObject = obj.freeze(); + subscription = frozenObject.asFlowable().subscribe(rxObject -> { + assertTrue(rxObject.isFrozen()); + assertEquals(rxObject, frozenObject); + disposeSuccessfulTest(dynamicRealm); + }); + } + + @Test + @RunTestInLooperThread + public void asChangesetObservable_frozenDynamicRealmObject() { + realm.beginTransaction(); + realm.createObject(AllTypes.class); + realm.commitTransaction(); + + DynamicRealm dynamicRealm = DynamicRealm.getInstance(looperThread.getConfiguration()); + DynamicRealmObject obj = dynamicRealm.where(AllTypes.CLASS_NAME).findFirst(); + + DynamicRealmObject frozenObject = obj.freeze(); + subscription = frozenObject.asChangesetObservable().subscribe(change -> { + RealmObject rxObject = change.getObject(); + assertTrue(rxObject.isFrozen()); + assertEquals(rxObject, frozenObject); + assertNull(change.getChangeset()); + disposeSuccessfulTest(dynamicRealm); + }); + } + + @Test + @RunTestInLooperThread + public void realmResults_readableAcrossThreads() { + final long TEST_SIZE = 10; + Realm realm = looperThread.getRealm(); + + realm.beginTransaction(); + for (int i = 0; i < TEST_SIZE; i++) { + realm.createObject(AllTypes.class).setColumnLong(1); + } + realm.commitTransaction(); + + AtomicLong startingThread = new AtomicLong(Thread.currentThread().getId()); + AtomicLong subscriberThread = new AtomicLong(); + subscription = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).findAllAsync().asFlowable() + .doOnSubscribe((r) -> { + subscriberThread.set(Thread.currentThread().getId()); + }) + // Note that Realm automatically subscribes on the thread with the live Realm + // so calling `subscribeOn()` has very little effect. + // In most cases you probably want to call `observeOn` directly after `asFlowable()`. + .subscribeOn(Schedulers.io()) + .filter(results -> { + assertNotEquals(startingThread.get(), subscriberThread.get()); + return results.isLoaded(); + }) + .map(results -> new Pair<>(results.size(), new Pair<>(results, results.first()))) + .observeOn(Schedulers.computation()) + .subscribe( + pair -> { + assertNotEquals(startingThread.get(), Thread.currentThread().getId()); + assertNotEquals(subscriberThread.get(), Thread.currentThread().getId()); + assertEquals(TEST_SIZE, pair.first.intValue()); + assertEquals(TEST_SIZE, pair.second.first.size()); + assertEquals(pair.second.second.getColumnLong(), pair.second.first.first().getColumnLong()); + disposeSuccessfulTest(realm); + } + ); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java index 48e5847ef3..1a4926ae96 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/UnManagedRealmCollectionTests.java @@ -97,10 +97,12 @@ public void unsupportedMethods_unManagedCollections() { case MIN_DATE: collection.minDate(AllJavaTypes.FIELD_DATE); break; case MAX_DATE: collection.maxDate(AllJavaTypes.FIELD_DATE); break; case DELETE_ALL_FROM_REALM: collection.deleteAllFromRealm(); break; + case FREEZE: collection.freeze(); break; // Supported methods. case IS_VALID: assertTrue(collection.isValid()); continue; case IS_MANAGED: assertFalse(collection.isManaged()); continue; + case IS_FROZEN: assertFalse(collection.isFrozen()); continue; } fail(method + " should have thrown an exception."); } catch (UnsupportedOperationException ignored) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationCore6PKStringIndexedByDefault.java b/realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationCore6PKStringIndexedByDefault.java new file mode 100644 index 0000000000..4ef91c9acb --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/migration/MigrationCore6PKStringIndexedByDefault.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.entities.migration; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + +public class MigrationCore6PKStringIndexedByDefault extends RealmObject { + @PrimaryKey + public String name; +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java index 2363200822..ddbe875804 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java @@ -31,6 +31,7 @@ import io.realm.TestHelper; import io.realm.rule.TestRealmConfigurationFactory; +import static junit.framework.Assert.assertNotSame; import static junit.framework.TestCase.assertEquals; @@ -47,7 +48,7 @@ public class JNIColumnInfoTest { public void setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); RealmConfiguration config = configFactory.createConfiguration(); - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override @@ -67,28 +68,12 @@ public void tearDown() { @Test public void shouldGetColumnInformation() { - - assertEquals(2, table.getColumnCount()); - - assertEquals("lastName", table.getColumnName(1)); - - assertEquals(1, table.getColumnIndex("lastName")); - - assertEquals(RealmFieldType.STRING, table.getColumnType(1)); - - } - - @Test - public void validateColumnInfo() { - assertEquals(2, table.getColumnCount()); - assertEquals("lastName", table.getColumnName(1)); - - assertEquals(1, table.getColumnIndex("lastName")); - - assertEquals(RealmFieldType.STRING, table.getColumnType(1)); + long columnKey = table.getColumnKey("lastName"); + assertNotSame(Table.NO_MATCH, columnKey); + assertEquals(RealmFieldType.STRING, table.getColumnType(columnKey)); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index 6e662d46a1..3e3668c074 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -21,14 +21,15 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Date; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; -import io.realm.Case; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmFieldType; @@ -38,7 +39,6 @@ import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.fail; - @RunWith(AndroidJUnit4.class) public class JNIQueryTest { @@ -56,7 +56,7 @@ public class JNIQueryTest { public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); config = configFactory.createConfiguration(); - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); } @After @@ -70,15 +70,15 @@ private void init() { table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table table) { - table.addColumn(RealmFieldType.INTEGER, "number"); - table.addColumn(RealmFieldType.STRING, "name"); - - TestHelper.addRowWithValues(table, 10, "A"); - TestHelper.addRowWithValues(table, 11, "B"); - TestHelper.addRowWithValues(table, 12, "C"); - TestHelper.addRowWithValues(table, 13, "B"); - TestHelper.addRowWithValues(table, 14, "D"); - TestHelper.addRowWithValues(table, 16, "D"); + long colKey1 = table.addColumn(RealmFieldType.INTEGER, "number"); + long colKey2 = table.addColumn(RealmFieldType.STRING, "name"); + + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{10, "A"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{11, "B"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{12, "C"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{13, "B"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{14, "D"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{16, "D"}); } }); @@ -90,19 +90,22 @@ public void shouldQuery() { init(); TableQuery query = table.where(); - long cnt = query.equalTo(new long[]{1}, oneNullTable, "D").count(); + long colKey1 = table.getColumnKey("number"); + long colKey2 = table.getColumnKey("name"); + + long cnt = query.equalTo(new long[]{colKey2}, oneNullTable, "D").count(); assertEquals(2, cnt); - cnt = query.minimumInt(0); + cnt = query.minimumInt(colKey1); assertEquals(14, cnt); - cnt = query.maximumInt(0); + cnt = query.maximumInt(colKey1); assertEquals(16, cnt); - cnt = query.sumInt(0); + cnt = query.sumInt(colKey1); assertEquals(14+16, cnt); - double avg = query.averageInt(0); + double avg = query.averageInt(colKey1); assertEquals(15.0, avg, Double.MIN_NORMAL); // TODO: Add tests with all parameters @@ -130,291 +133,6 @@ public void nonCompleteQuery() { try { table.where().equalTo(new long[]{0}, oneNullTable, 1).endGroup().validateQuery(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} try { table.where().equalTo(new long[]{0}, oneNullTable, 1).endGroup().find(); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - try { table.where().equalTo(new long[]{0}, oneNullTable, 1).endGroup().find(0); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - try { table.where().equalTo(new long[]{0}, oneNullTable, 1).endGroup().find(1); fail("ends group, no start"); } catch (UnsupportedOperationException ignore) {} - } - - @Test - public void invalidColumnIndexEqualTo() { - Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); - TableQuery query = table.where(); - - // Boolean - try { query.equalTo(new long[]{-1}, oneNullTable, true); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{9}, oneNullTable, true); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{10}, oneNullTable, true); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Date - try { query.equalTo(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Double - try { query.equalTo(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - - // Float - try { query.equalTo(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Int / long - try { query.equalTo(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // String - try { query.equalTo(new long[]{-1}, oneNullTable, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{9}, oneNullTable, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{10}, oneNullTable, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // String case true - try { query.equalTo(new long[]{-1}, oneNullTable, "a", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{9}, oneNullTable, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{10}, oneNullTable, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // String case false - try { query.equalTo(new long[]{-1}, oneNullTable, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{9}, oneNullTable, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.equalTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - } - - @Test - public void invalidColumnIndexNotEqualTo() { - Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); - TableQuery query = table.where(); - - - // Date - try { query.notEqualTo(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Double - try { query.notEqualTo(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - - // Float - try { query.notEqualTo(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Int / long - try { query.notEqualTo(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // String - try { query.notEqualTo(new long[]{-1}, oneNullTable, "a"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, "a"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, "a"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // String case true - try { query.notEqualTo(new long[]{-1}, oneNullTable, "a", Case.SENSITIVE); fail("-1column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, "a", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, "a", Case.SENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // String case false - try { query.notEqualTo(new long[]{-1}, oneNullTable, "a", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{9}, oneNullTable, "a", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{10}, oneNullTable, "a", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - } - - @Test - public void invalidColumnIndexGreaterThan() { - Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); - TableQuery query = table.where(); - - // Date - try { query.greaterThan(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Double - try { query.greaterThan(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - - // Float - try { query.greaterThan(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Int / long - try { query.greaterThan(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - } - - @Test - public void invalidColumnIndexGreaterThanOrEqual() { - Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); - TableQuery query = table.where(); - - // Date - try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Double - try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - - // Float - try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Int / long - try { query.greaterThanOrEqual(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - } - - @Test - public void invalidColumnIndexLessThan() { - Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); - TableQuery query = table.where(); - - // Date - try { query.lessThan(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Double - try { query.lessThan(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - - // Float - try { query.lessThan(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Int / long - try { query.lessThan(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - } - - @Test - public void invalidColumnIndexLessThanOrEqual() { - Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); - TableQuery query = table.where(); - - // Date - try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{9}, oneNullTable, new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{10}, oneNullTable, new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Double - try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 4.5d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 4.5d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 4.5d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - - // Float - try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 1.4f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 1.4f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 1.4f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Int / long - try { query.lessThanOrEqual(new long[]{-1}, oneNullTable, 1); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{9}, oneNullTable, 1); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{10}, oneNullTable, 1); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - } - - @Test - public void invalidColumnIndexBetween() { - Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); - TableQuery query = table.where(); - - // Date - try { query.between(new long[]{-1}, new Date(), new Date()); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{9}, new Date(), new Date()); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{10}, new Date(), new Date()); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Double - try { query.between(new long[]{-1}, 4.5d, 6.0d); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{9}, 4.5d, 6.0d); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{10}, 4.5d, 6.0d); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - - // Float - try { query.between(new long[]{-1}, 1.4f, 5.8f); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{9}, 1.4f, 5.8f); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{10}, 1.4f, 5.8f); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // Int / long - try { query.between(new long[]{-1}, 1, 10); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{9}, 1, 10); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{10}, 1, 10); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - } - - @Test - public void invalidColumnIndexContains() { - Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); - TableQuery query = table.where(); - - // String - try { query.contains(new long[]{-1}, oneNullTable, "hey"); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.contains(new long[]{9}, oneNullTable, "hey"); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.contains(new long[]{10}, oneNullTable, "hey"); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // String case true - try { query.contains(new long[]{-1}, oneNullTable, "hey", Case.SENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.contains(new long[]{9}, oneNullTable, "hey", Case.SENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.contains(new long[]{10}, oneNullTable, "hey", Case.SENSITIVE); fail("-0 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - - // String case false - try { query.contains(new long[]{-1}, oneNullTable, "hey", Case.INSENSITIVE); fail("-1 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.contains(new long[]{9}, oneNullTable, "hey", Case.INSENSITIVE); fail("9 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - try { query.contains(new long[]{10}, oneNullTable, "hey", Case.INSENSITIVE); fail("10 column index"); } catch (ArrayIndexOutOfBoundsException ignore) {} - } - - @SuppressWarnings("ConstantConditions") - @Test - public void nullInputQuery() { - Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { - @Override - public void execute(Table t) { - t.addColumn(RealmFieldType.DATE, "dateCol"); - t.addColumn(RealmFieldType.STRING, "stringCol"); - } - }); - - Date nullDate = null; - try { t.where().equalTo(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().notEqualTo(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().greaterThan(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().greaterThanOrEqual(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().lessThan(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().lessThanOrEqual(new long[]{0}, oneNullTable, nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().between(new long[]{0}, nullDate, new Date()); fail("Date is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().between(new long[]{0}, new Date(), nullDate); fail("Date is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().between(new long[]{0}, nullDate, nullDate); fail("Dates are null"); } catch (IllegalArgumentException ignore) { } - - String nullString = null; - try { t.where().equalTo(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().equalTo(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().notEqualTo(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().notEqualTo(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().contains(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().contains(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().beginsWith(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().beginsWith(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().endsWith(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().endsWith(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().like(new long[]{1}, oneNullTable, nullString); fail("String is null"); } catch (IllegalArgumentException ignore) { } - try { t.where().like(new long[]{1}, oneNullTable, nullString, Case.INSENSITIVE); fail("String is null"); } catch (IllegalArgumentException ignore) { } } @Test @@ -423,58 +141,47 @@ public void shouldFind() { Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table table) { - table.addColumn(RealmFieldType.STRING, "username"); - table.addColumn(RealmFieldType.INTEGER, "score"); - table.addColumn(RealmFieldType.BOOLEAN, "completed"); + long colKey1 = table.addColumn(RealmFieldType.STRING, "username"); + long colKey2 = table.addColumn(RealmFieldType.INTEGER, "score"); + long colKey3 = table.addColumn(RealmFieldType.BOOLEAN, "completed"); // Inserts some values. - TestHelper.addRowWithValues(table, "Arnold", 420, false); // 0 - TestHelper.addRowWithValues(table, "Jane", 770, false); // 1 * - TestHelper.addRowWithValues(table, "Erik", 600, false); // 2 - TestHelper.addRowWithValues(table, "Henry", 601, false); // 3 * - TestHelper.addRowWithValues(table, "Bill", 564, true); // 4 - TestHelper.addRowWithValues(table, "Janet", 875, false); // 5 * + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2, colKey3}, new Object[]{"Arnold", 420, false}); // 0 + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2, colKey3}, new Object[]{"Jane", 770, false}); // 1 * + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2, colKey3}, new Object[]{"Erik", 600, false}); // 2 + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2, colKey3}, new Object[]{"Henry", 601, false}); // 3 * + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2, colKey3}, new Object[]{"Bill", 564, true}); // 4 + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2, colKey3}, new Object[]{"Janet", 875, false}); // 5 * } }); - TableQuery query = table.where().greaterThan(new long[]{1}, oneNullTable, 600); + TableQuery query = table.where().greaterThan(new long[]{table.getColumnKey("score")}, oneNullTable, 600); // Finds first match. assertEquals(1, query.find()); - assertEquals(1, query.find()); - assertEquals(1, query.find(0)); - assertEquals(1, query.find(1)); - // Finds next. - assertEquals(3, query.find(2)); - assertEquals(3, query.find(3)); - // Finds next. - assertEquals(5, query.find(4)); - assertEquals(5, query.find(5)); - - // Tests backwards. - assertEquals(5, query.find(4)); - assertEquals(3, query.find(3)); - assertEquals(3, query.find(2)); - assertEquals(1, query.find(1)); - assertEquals(1, query.find(0)); - - // Tests out of range. - assertEquals(-1, query.find(6)); - try { query.find(7); fail("Exception expected"); } catch (ArrayIndexOutOfBoundsException ignore) { } } @Test public void queryTestForNoMatches() { Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); + long columnKey1 = t.getColumnKey("binary"); + long columnKey2 = t.getColumnKey("boolean"); + long columnKey3 = t.getColumnKey("date"); + long columnKey4 = t.getColumnKey("double"); + long columnKey5 = t.getColumnKey("float"); + long columnKey6 = t.getColumnKey("long"); + long columnKey7 = t.getColumnKey("string"); + + sharedRealm.beginTransaction(); - TestHelper.addRowWithValues(t, new byte[]{1,2,3}, true, new Date(1384423149761L), 4.5d, 5.7f, 100, "string"); + TestHelper.addRowWithValues(t, new long[]{columnKey1, columnKey2, columnKey3, columnKey4, columnKey5, columnKey6, columnKey7}, + new Object[]{new byte[]{1,2,3}, true, new Date(1384423149761L), 4.5d, 5.7f, 100, "string"}); sharedRealm.commitTransaction(); - TableQuery q = t.where().greaterThan(new long[]{5}, oneNullTable, 1000); // No matches + TableQuery q = t.where().greaterThan(new long[]{columnKey6}, oneNullTable, 1000); // No matches assertEquals(-1, q.find()); - assertEquals(-1, q.find(1)); } @Test @@ -482,65 +189,72 @@ public void queryWithWrongDataType() { Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); + long columnKey1 = table.getColumnKey("binary"); + long columnKey2 = table.getColumnKey("boolean"); + long columnKey3 = table.getColumnKey("date"); + long columnKey4 = table.getColumnKey("double"); + long columnKey5 = table.getColumnKey("float"); + long columnKey6 = table.getColumnKey("long"); + long columnKey7 = table.getColumnKey("string"); + long[] columnKeys = new long[]{columnKey1, columnKey2, columnKey3, columnKey4, columnKey5, columnKey6, columnKey7}; + // Queries the table. TableQuery query = table.where(); // Compares strings in non string columns. - for (int i = 0; i <= 6; i++) { - if (i != 6) { - try { query.equalTo(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} - try { query.notEqualTo(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} - try { query.beginsWith(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} - try { query.endsWith(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} - try { query.like(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} - try { query.contains(new long[]{i}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} - } + for (int i = 0; i < 6; i++) { + try { query.equalTo(new long[]{columnKeys[i]}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.notEqualTo(new long[]{columnKeys[i]}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.beginsWith(new long[]{columnKeys[i]}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.endsWith(new long[]{columnKeys[i]}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.like(new long[]{columnKeys[i]}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} + try { query.contains(new long[]{columnKeys[i]}, oneNullTable, "string"); fail(); } catch(IllegalArgumentException ignore) {} } // Compares integer in non integer columns. for (int i = 0; i <= 6; i++) { if (i != 5) { - try { query.equalTo(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} - try { query.notEqualTo(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} - try { query.lessThan(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} - try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} - try { query.greaterThan(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} - try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} - try { query.between(new long[]{i}, 123, 321); fail(); } catch(IllegalArgumentException ignore) {} + try { query.equalTo(new long[]{columnKeys[i]}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.notEqualTo(new long[]{columnKeys[i]}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThan(new long[]{columnKeys[i]}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThanOrEqual(new long[]{columnKeys[i]}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThan(new long[]{columnKeys[i]}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThanOrEqual(new long[]{columnKeys[i]}, oneNullTable, 123); fail(); } catch(IllegalArgumentException ignore) {} + try { query.between(new long[]{columnKeys[i]}, 123, 321); fail(); } catch(IllegalArgumentException ignore) {} } } // Compares float in non float columns. for (int i = 0; i <= 6; i++) { if (i != 4) { - try { query.equalTo(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} - try { query.notEqualTo(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} - try { query.lessThan(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} - try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} - try { query.greaterThan(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} - try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} - try { query.between(new long[]{i}, 123F, 321F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.equalTo(new long[]{columnKeys[i]}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.notEqualTo(new long[]{columnKeys[i]}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThan(new long[]{columnKeys[i]}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThanOrEqual(new long[]{columnKeys[i]}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThan(new long[]{columnKeys[i]}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThanOrEqual(new long[]{columnKeys[i]}, oneNullTable, 123F); fail(); } catch(IllegalArgumentException ignore) {} + try { query.between(new long[]{columnKeys[i]}, 123F, 321F); fail(); } catch(IllegalArgumentException ignore) {} } } // Compares double in non double columns. for (int i = 0; i <= 6; i++) { if (i != 3) { - try { query.equalTo(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} - try { query.notEqualTo(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} - try { query.lessThan(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} - try { query.lessThanOrEqual(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} - try { query.greaterThan(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} - try { query.greaterThanOrEqual(new long[]{i}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} - try { query.between(new long[]{i}, 123D, 321D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.equalTo(new long[]{columnKeys[i]}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.notEqualTo(new long[]{columnKeys[i]}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThan(new long[]{columnKeys[i]}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThanOrEqual(new long[]{columnKeys[i]}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThan(new long[]{columnKeys[i]}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThanOrEqual(new long[]{columnKeys[i]}, oneNullTable, 123D); fail(); } catch(IllegalArgumentException ignore) {} + try { query.between(new long[]{columnKeys[i]}, 123D, 321D); fail(); } catch(IllegalArgumentException ignore) {} } } // Compares boolean in non boolean columns. for (int i = 0; i <= 6; i++) { if (i != 1) { - try { query.equalTo(new long[]{i}, oneNullTable, true); fail(); } catch(IllegalArgumentException ignore) {} + try { query.equalTo(new long[]{columnKeys[i]}, oneNullTable, true); fail(); } catch(IllegalArgumentException ignore) {} } } @@ -559,139 +273,38 @@ public void queryWithWrongDataType() { */ } - @Test - public void columnIndexOutOfBounds() { - Table table = TestHelper.createTableWithAllColumnTypes(sharedRealm); - - // Queries the table. - TableQuery query = table.where(); - - try { query.minimumInt(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumFloat(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumDouble(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumInt(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumFloat(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumDouble(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumInt(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumFloat(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumDouble(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumInt(6); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumFloat(6); fail(); } catch(IllegalArgumentException ignore) {} - try { query.minimumDouble(6); fail(); } catch(IllegalArgumentException ignore) {} - - try { query.maximumInt(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumFloat(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumDouble(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumInt(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumFloat(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumDouble(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumInt(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumFloat(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumDouble(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumInt(6); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumFloat(6); fail(); } catch(IllegalArgumentException ignore) {} - try { query.maximumDouble(6); fail(); } catch(IllegalArgumentException ignore) {} - - try { query.sumInt(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumFloat(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumDouble(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumInt(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumFloat(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumDouble(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumInt(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumFloat(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumDouble(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumInt(6); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumFloat(6); fail(); } catch(IllegalArgumentException ignore) {} - try { query.sumDouble(6); fail(); } catch(IllegalArgumentException ignore) {} - - try { query.averageInt(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageFloat(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageDouble(0); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageInt(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageFloat(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageDouble(1); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageInt(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageFloat(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageDouble(2); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageInt(6); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageFloat(6); fail(); } catch(IllegalArgumentException ignore) {} - try { query.averageDouble(6); fail(); } catch(IllegalArgumentException ignore) {} - // Out of bounds for string - try { query.equalTo(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.beginsWith(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.endsWith(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.like(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.contains(new long[]{7}, oneNullTable, "string"); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - - - // Out of bounds for integer - try { query.equalTo(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{7}, 123, 321); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - - - // Out of bounds for float - try { query.equalTo(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{7}, 123F, 321F); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - - - // Out of bounds for double - try { query.equalTo(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.notEqualTo(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThan(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.lessThanOrEqual(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThan(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.greaterThanOrEqual(new long[]{7}, oneNullTable, 123D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - try { query.between(new long[]{7}, 123D, 321D); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - - - // Out of bounds for boolean - try { query.equalTo(new long[]{7}, oneNullTable, true); fail(); } catch(ArrayIndexOutOfBoundsException ignore) {} - } - @Test public void maximumDate() { - + final AtomicLong columnKey = new AtomicLong(-1); Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table table) { - table.addColumn(RealmFieldType.DATE, "date"); + columnKey.set(table.addColumn(RealmFieldType.DATE, "date")); - TestHelper.addRowWithValues(table, new Date(0)); - TestHelper.addRowWithValues(table, new Date(10000)); - TestHelper.addRowWithValues(table, new Date(1000)); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{new Date(0)}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{new Date(10000)}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{new Date(1000)}); } }); - assertEquals(new Date(10000), table.where().maximumDate(0)); + assertEquals(new Date(10000), table.where().maximumDate(columnKey.get())); } @Test public void minimumDate() { - + final AtomicLong columnKey = new AtomicLong(-1); Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table table) { - table.addColumn(RealmFieldType.DATE, "date"); + columnKey.set(table.addColumn(RealmFieldType.DATE, "date")); - TestHelper.addRowWithValues(table, new Date(10000)); - TestHelper.addRowWithValues(table, new Date(0)); - TestHelper.addRowWithValues(table, new Date(1000)); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{new Date(10000)}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{new Date(0)}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{new Date(1000)}); } }); - assertEquals(new Date(0), table.where().minimumDate(0)); + assertEquals(new Date(0), table.where().minimumDate(columnKey.get())); } @Test @@ -702,99 +315,101 @@ public void dateQuery() throws Exception { final Date distantPast = new Date(Long.MIN_VALUE); final Date distantFuture = new Date(Long.MAX_VALUE); + final AtomicLong columnKey = new AtomicLong(-1); + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table table) { - table.addColumn(RealmFieldType.DATE, "date"); - - TestHelper.addRowWithValues(table, new Date(10000)); - TestHelper.addRowWithValues(table, new Date(0)); - TestHelper.addRowWithValues(table, new Date(1000)); - TestHelper.addRowWithValues(table, future); - TestHelper.addRowWithValues(table, distantFuture); - TestHelper.addRowWithValues(table, past); - TestHelper.addRowWithValues(table, distantPast); + columnKey.set(table.addColumn(RealmFieldType.DATE, "date")); + + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{new Date(10000)}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{new Date(0)}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{new Date(1000)}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{future}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{distantFuture}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{past}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{distantPast}); } }); - assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, distantPast).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, distantPast).count()); - assertEquals(0L, table.where().lessThan(new long[]{0}, oneNullTable, distantPast).count()); - assertEquals(1L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, distantPast).count()); - assertEquals(6L, table.where().greaterThan(new long[]{0}, oneNullTable, distantPast).count()); - assertEquals(7L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, distantPast).count()); - - assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, past).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, past).count()); - assertEquals(1L, table.where().lessThan(new long[]{0}, oneNullTable, past).count()); - assertEquals(2L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, past).count()); - assertEquals(5L, table.where().greaterThan(new long[]{0}, oneNullTable, past).count()); - assertEquals(6L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, past).count()); - - assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, new Date(0)).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, new Date(0)).count()); - assertEquals(2L, table.where().lessThan(new long[]{0}, oneNullTable, new Date(0)).count()); - assertEquals(3L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, new Date(0)).count()); - assertEquals(4L, table.where().greaterThan(new long[]{0}, oneNullTable, new Date(0)).count()); - assertEquals(5L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, new Date(0)).count()); - - assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, future).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, future).count()); - assertEquals(5L, table.where().lessThan(new long[]{0}, oneNullTable, future).count()); - assertEquals(6L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, future).count()); - assertEquals(1L, table.where().greaterThan(new long[]{0}, oneNullTable, future).count()); - assertEquals(2L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, future).count()); - - assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, distantFuture).count()); - assertEquals(6L, table.where().notEqualTo(new long[]{0}, oneNullTable, distantFuture).count()); - assertEquals(6L, table.where().lessThan(new long[]{0}, oneNullTable, distantFuture).count()); - assertEquals(7L, table.where().lessThanOrEqual(new long[]{0}, oneNullTable, distantFuture).count()); - assertEquals(0L, table.where().greaterThan(new long[]{0}, oneNullTable, distantFuture).count()); - assertEquals(1L, table.where().greaterThanOrEqual(new long[]{0}, oneNullTable, distantFuture).count()); + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, distantPast).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, distantPast).count()); + assertEquals(0L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, distantPast).count()); + assertEquals(1L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, distantPast).count()); + assertEquals(6L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, distantPast).count()); + assertEquals(7L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, distantPast).count()); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, past).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, past).count()); + assertEquals(1L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, past).count()); + assertEquals(2L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, past).count()); + assertEquals(5L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, past).count()); + assertEquals(6L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, past).count()); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, new Date(0)).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, new Date(0)).count()); + assertEquals(2L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, new Date(0)).count()); + assertEquals(3L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, new Date(0)).count()); + assertEquals(4L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, new Date(0)).count()); + assertEquals(5L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, new Date(0)).count()); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, future).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, future).count()); + assertEquals(5L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, future).count()); + assertEquals(6L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, future).count()); + assertEquals(1L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, future).count()); + assertEquals(2L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, future).count()); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, distantFuture).count()); + assertEquals(6L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, distantFuture).count()); + assertEquals(6L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, distantFuture).count()); + assertEquals(7L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, distantFuture).count()); + assertEquals(0L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, distantFuture).count()); + assertEquals(1L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, distantFuture).count()); // between - assertEquals(1L, table.where().between(new long[]{0}, distantPast, distantPast).count()); - assertEquals(2L, table.where().between(new long[]{0}, distantPast, past).count()); - assertEquals(3L, table.where().between(new long[]{0}, distantPast, new Date(0)).count()); - assertEquals(5L, table.where().between(new long[]{0}, distantPast, new Date(10000)).count()); - assertEquals(6L, table.where().between(new long[]{0}, distantPast, future).count()); - assertEquals(7L, table.where().between(new long[]{0}, distantPast, distantFuture).count()); - - assertEquals(0L, table.where().between(new long[]{0}, past, distantPast).count()); - assertEquals(1L, table.where().between(new long[]{0}, past, past).count()); - assertEquals(2L, table.where().between(new long[]{0}, past, new Date(0)).count()); - assertEquals(4L, table.where().between(new long[]{0}, past, new Date(10000)).count()); - assertEquals(5L, table.where().between(new long[]{0}, past, future).count()); - assertEquals(6L, table.where().between(new long[]{0}, past, distantFuture).count()); - - assertEquals(0L, table.where().between(new long[]{0}, new Date(0), distantPast).count()); - assertEquals(0L, table.where().between(new long[]{0}, new Date(0), past).count()); - assertEquals(1L, table.where().between(new long[]{0}, new Date(0), new Date(0)).count()); - assertEquals(3L, table.where().between(new long[]{0}, new Date(0), new Date(10000)).count()); - assertEquals(4L, table.where().between(new long[]{0}, new Date(0), future).count()); - assertEquals(5L, table.where().between(new long[]{0}, new Date(0), distantFuture).count()); - - assertEquals(0L, table.where().between(new long[]{0}, new Date(10000), distantPast).count()); - assertEquals(0L, table.where().between(new long[]{0}, new Date(10000), past).count()); - assertEquals(0L, table.where().between(new long[]{0}, new Date(10000), new Date(0)).count()); - assertEquals(1L, table.where().between(new long[]{0}, new Date(10000), new Date(10000)).count()); - assertEquals(2L, table.where().between(new long[]{0}, new Date(10000), future).count()); - assertEquals(3L, table.where().between(new long[]{0}, new Date(10000), distantFuture).count()); - - assertEquals(0L, table.where().between(new long[]{0}, future, distantPast).count()); - assertEquals(0L, table.where().between(new long[]{0}, future, past).count()); - assertEquals(0L, table.where().between(new long[]{0}, future, new Date(0)).count()); - assertEquals(0L, table.where().between(new long[]{0}, future, new Date(10000)).count()); - assertEquals(1L, table.where().between(new long[]{0}, future, future).count()); - assertEquals(2L, table.where().between(new long[]{0}, future, distantFuture).count()); - - assertEquals(0L, table.where().between(new long[]{0}, distantFuture, distantPast).count()); - assertEquals(0L, table.where().between(new long[]{0}, distantFuture, past).count()); - assertEquals(0L, table.where().between(new long[]{0}, distantFuture, new Date(0)).count()); - assertEquals(0L, table.where().between(new long[]{0}, distantFuture, new Date(10000)).count()); - assertEquals(0L, table.where().between(new long[]{0}, distantFuture, future).count()); - assertEquals(1L, table.where().between(new long[]{0}, distantFuture, distantFuture).count()); + assertEquals(1L, table.where().between(new long[]{columnKey.get()}, distantPast, distantPast).count()); + assertEquals(2L, table.where().between(new long[]{columnKey.get()}, distantPast, past).count()); + assertEquals(3L, table.where().between(new long[]{columnKey.get()}, distantPast, new Date(0)).count()); + assertEquals(5L, table.where().between(new long[]{columnKey.get()}, distantPast, new Date(10000)).count()); + assertEquals(6L, table.where().between(new long[]{columnKey.get()}, distantPast, future).count()); + assertEquals(7L, table.where().between(new long[]{columnKey.get()}, distantPast, distantFuture).count()); + + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, past, distantPast).count()); + assertEquals(1L, table.where().between(new long[]{columnKey.get()}, past, past).count()); + assertEquals(2L, table.where().between(new long[]{columnKey.get()}, past, new Date(0)).count()); + assertEquals(4L, table.where().between(new long[]{columnKey.get()}, past, new Date(10000)).count()); + assertEquals(5L, table.where().between(new long[]{columnKey.get()}, past, future).count()); + assertEquals(6L, table.where().between(new long[]{columnKey.get()}, past, distantFuture).count()); + + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, new Date(0), distantPast).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, new Date(0), past).count()); + assertEquals(1L, table.where().between(new long[]{columnKey.get()}, new Date(0), new Date(0)).count()); + assertEquals(3L, table.where().between(new long[]{columnKey.get()}, new Date(0), new Date(10000)).count()); + assertEquals(4L, table.where().between(new long[]{columnKey.get()}, new Date(0), future).count()); + assertEquals(5L, table.where().between(new long[]{columnKey.get()}, new Date(0), distantFuture).count()); + + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, new Date(10000), distantPast).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, new Date(10000), past).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, new Date(10000), new Date(0)).count()); + assertEquals(1L, table.where().between(new long[]{columnKey.get()}, new Date(10000), new Date(10000)).count()); + assertEquals(2L, table.where().between(new long[]{columnKey.get()}, new Date(10000), future).count()); + assertEquals(3L, table.where().between(new long[]{columnKey.get()}, new Date(10000), distantFuture).count()); + + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, future, distantPast).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, future, past).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, future, new Date(0)).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, future, new Date(10000)).count()); + assertEquals(1L, table.where().between(new long[]{columnKey.get()}, future, future).count()); + assertEquals(2L, table.where().between(new long[]{columnKey.get()}, future, distantFuture).count()); + + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, distantFuture, distantPast).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, distantFuture, past).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, distantFuture, new Date(0)).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, distantFuture, new Date(10000)).count()); + assertEquals(0L, table.where().between(new long[]{columnKey.get()}, distantFuture, future).count()); + assertEquals(1L, table.where().between(new long[]{columnKey.get()}, distantFuture, distantFuture).count()); } @Test @@ -805,26 +420,28 @@ public void byteArrayQuery() throws Exception { final byte[] binary3 = new byte[] {0x09, 0x0a, 0x0b, 0x04}; final byte[] binary4 = new byte[] {0x05, 0x0a, 0x0b, 0x10}; + final AtomicLong columnKey = new AtomicLong(-1); + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table table) { - table.addColumn(RealmFieldType.BINARY, "binary"); + columnKey.set(table.addColumn(RealmFieldType.BINARY, "binary")); - TestHelper.addRowWithValues(table, (Object) binary1); - TestHelper.addRowWithValues(table, (Object) binary2); - TestHelper.addRowWithValues(table, (Object) binary3); - TestHelper.addRowWithValues(table, (Object) binary4); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{(Object) binary1}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{(Object) binary2}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{(Object) binary3}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{(Object) binary4}); } }); // Equal to - assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, binary1).count()); - assertEquals(1L, table.where().equalTo(new long[]{0}, oneNullTable, binary3).count()); + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, binary1).count()); + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, binary3).count()); // Not equal to - assertEquals(3L, table.where().notEqualTo(new long[]{0}, oneNullTable, binary2).count()); - assertEquals(3L, table.where().notEqualTo(new long[]{0}, oneNullTable, binary4).count()); + assertEquals(3L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, binary2).count()); + assertEquals(3L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, binary4).count()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java index 3eea1c4efd..4f50e12414 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java @@ -26,6 +26,7 @@ import org.junit.runner.RunWith; import java.util.Date; +import java.util.concurrent.atomic.AtomicLong; import io.realm.Realm; import io.realm.RealmConfiguration; @@ -39,7 +40,6 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; - @RunWith(AndroidJUnit4.class) public class JNIRowTest { @@ -54,7 +54,7 @@ public class JNIRowTest { public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); config = configFactory.createConfiguration(); - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); sharedRealm.beginTransaction(); } @@ -74,48 +74,56 @@ public void tearDown() { public void nonNullValues() { final byte[] data = new byte[2]; + final AtomicLong colKey1 = new AtomicLong(-1); + final AtomicLong colKey2 = new AtomicLong(-1); + final AtomicLong colKey3 = new AtomicLong(-1); + final AtomicLong colKey4 = new AtomicLong(-1); + final AtomicLong colKey5 = new AtomicLong(-1); + final AtomicLong colKey6 = new AtomicLong(-1); + final AtomicLong colKey7 = new AtomicLong(-1); + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table table) { - table.addColumn(RealmFieldType.STRING, "string"); - table.addColumn(RealmFieldType.INTEGER, "integer"); - table.addColumn(RealmFieldType.FLOAT, "float"); - table.addColumn(RealmFieldType.DOUBLE, "double"); - table.addColumn(RealmFieldType.BOOLEAN, "boolean"); - table.addColumn(RealmFieldType.DATE, "date"); - table.addColumn(RealmFieldType.BINARY, "binary"); - - TestHelper.addRowWithValues(table, "abc", 3, (float) 1.2, 1.3, true, new Date(0), data); + colKey1.set(table.addColumn(RealmFieldType.STRING, "string")); + colKey2.set(table.addColumn(RealmFieldType.INTEGER, "integer")); + colKey3.set(table.addColumn(RealmFieldType.FLOAT, "float")); + colKey4.set(table.addColumn(RealmFieldType.DOUBLE, "double")); + colKey5.set(table.addColumn(RealmFieldType.BOOLEAN, "boolean")); + colKey6.set(table.addColumn(RealmFieldType.DATE, "date")); + colKey7.set(table.addColumn(RealmFieldType.BINARY, "binary")); + + TestHelper.addRowWithValues(table, new long[]{colKey1.get(), colKey2.get(), colKey3.get(), colKey4.get(), colKey5.get(), colKey6.get(), colKey7.get()}, new Object[]{"abc", 3, (float) 1.2, 1.3, true, new Date(0), data}); } }); UncheckedRow row = table.getUncheckedRow(0); - assertEquals("abc", row.getString(0)); - assertEquals(3, row.getLong(1)); - assertEquals(1.2F, row.getFloat(2), Float.MIN_NORMAL); - assertEquals(1.3, row.getDouble(3), Double.MIN_NORMAL); - assertEquals(true, row.getBoolean(4)); - assertEquals(new Date(0), row.getDate(5)); - assertArrayEquals(data, row.getBinaryByteArray(6)); - - row.setString(0, "a"); - row.setLong(1, 1); - row.setFloat(2, (float) 8.8); - row.setDouble(3, 9.9); - row.setBoolean(4, false); - row.setDate(5, new Date(10000)); + assertEquals("abc", row.getString(colKey1.get())); + assertEquals(3, row.getLong(colKey2.get())); + assertEquals(1.2F, row.getFloat(colKey3.get()), Float.MIN_NORMAL); + assertEquals(1.3, row.getDouble(colKey4.get()), Double.MIN_NORMAL); + assertEquals(true, row.getBoolean(colKey5.get())); + assertEquals(new Date(0), row.getDate(colKey6.get())); + assertArrayEquals(data, row.getBinaryByteArray(colKey7.get())); + + row.setString(colKey1.get(), "a"); + row.setLong(colKey2.get(), 1); + row.setFloat(colKey3.get(), (float) 8.8); + row.setDouble(colKey4.get(), 9.9); + row.setBoolean(colKey5.get(), false); + row.setDate(colKey6.get(), new Date(10000)); byte[] newData = new byte[3]; - row.setBinaryByteArray(6, newData); - - assertEquals("a", row.getString(0)); - assertEquals(1, row.getLong(1)); - assertEquals(8.8F, row.getFloat(2), Float.MIN_NORMAL); - assertEquals(9.9, row.getDouble(3), Double.MIN_NORMAL); - assertEquals(false, row.getBoolean(4)); - assertEquals(new Date(10000), row.getDate(5)); - assertArrayEquals(newData, row.getBinaryByteArray(6)); + row.setBinaryByteArray(colKey7.get(), newData); + + assertEquals("a", row.getString(colKey1.get())); + assertEquals(1, row.getLong(colKey2.get())); + assertEquals(8.8F, row.getFloat(colKey3.get()), Float.MIN_NORMAL); + assertEquals(9.9, row.getDouble(colKey4.get()), Double.MIN_NORMAL); + assertEquals(false, row.getBoolean(colKey5.get())); + assertEquals(new Date(10000), row.getDate(colKey6.get())); + assertArrayEquals(newData, row.getBinaryByteArray(colKey7.get())); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java index cd1794b669..c7d411f6f7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java @@ -56,7 +56,7 @@ public class JNITableInsertTest { public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); config = configFactory.createConfiguration(); - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); } @After @@ -101,10 +101,10 @@ public void execute(Table t) { assertTrue(true); } else { // Adds column. - t.addColumn(TestHelper.getColumnType(valueJ), valueJ.getClass().getSimpleName()); + long colKey = t.addColumn(TestHelper.getColumnType(valueJ), valueJ.getClass().getSimpleName()); // Adds value. try { - TestHelper.addRowWithValues(t, valueI); + TestHelper.addRowWithValues(t, new long[]{colKey}, new Object[]{valueI}); fail("No matching type"); } catch (IllegalArgumentException ignored) { } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index 6e92ce4b72..7e6ecc3580 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -17,7 +17,6 @@ package io.realm.internal; import android.support.test.runner.AndroidJUnit4; -import android.util.Pair; import org.junit.After; import org.junit.Before; @@ -27,8 +26,6 @@ import java.util.Arrays; import java.util.Date; -import java.util.List; -import java.util.ListIterator; import java.util.Locale; import java.util.concurrent.atomic.AtomicLong; @@ -39,8 +36,6 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -57,7 +52,7 @@ public class JNITableTest { @Before public void setUp() { config = configFactory.createConfiguration(); - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); } @After @@ -72,12 +67,12 @@ public void tableToString() { Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table t) { - t.addColumn(RealmFieldType.STRING, "stringCol"); - t.addColumn(RealmFieldType.INTEGER, "intCol"); - t.addColumn(RealmFieldType.BOOLEAN, "boolCol"); + long colKey1 = t.addColumn(RealmFieldType.STRING, "stringCol"); + long colKey2 = t.addColumn(RealmFieldType.INTEGER, "intCol"); + long colKey3 = t.addColumn(RealmFieldType.BOOLEAN, "boolCol"); - TestHelper.addRowWithValues(t, "s1", 1, true); - TestHelper.addRowWithValues(t, "s2", 2, false); + TestHelper.addRowWithValues(t, new long[]{colKey1, colKey2, colKey3}, new Object[]{"s1", 1, true}); + TestHelper.addRowWithValues(t, new long[]{colKey1, colKey2, colKey3}, new Object[]{"s2", 2, false}); } }); @@ -85,191 +80,65 @@ public void execute(Table t) { assertEquals(expected, t.toString()); } - @Test - public void rowOperationsOnZeroRow() { - Table t = TestHelper.createTable(sharedRealm, "temp"); - - sharedRealm.beginTransaction(); - // Removes rows without columns. - try { t.moveLastOver(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - try { t.moveLastOver(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - - // Column added, remove rows again. - t.addColumn(RealmFieldType.STRING, ""); - try { t.moveLastOver(0); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - try { t.moveLastOver(10); fail("No rows in table"); } catch (ArrayIndexOutOfBoundsException ignored) {} - sharedRealm.commitTransaction(); - } - - @Test - public void zeroColOperations() { - Table tableZeroCols = TestHelper.createTable(sharedRealm, "temp"); - - sharedRealm.beginTransaction(); - // Col operations - try { - tableZeroCols.removeColumn(0); - fail("No columns in table"); - } catch (ArrayIndexOutOfBoundsException ignored) {} - try { - tableZeroCols.renameColumn(0, "newName"); - fail("No columns in table"); - } catch (ArrayIndexOutOfBoundsException ignored) {} - try { - tableZeroCols.removeColumn(10); - fail("No columns in table"); - } catch (ArrayIndexOutOfBoundsException ignored) {} - try { - tableZeroCols.renameColumn(10, "newName"); - fail("No columns in table"); - } catch (ArrayIndexOutOfBoundsException ignored) {} - sharedRealm.commitTransaction(); - } - @Test public void findFirstNonExisting() { Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); + long colKey1 = t.getColumnKey("binary"); + long colKey2 = t.getColumnKey("boolean"); + long colKey3 = t.getColumnKey("date"); + long colKey4 = t.getColumnKey("double"); + long colKey5 = t.getColumnKey("float"); + long colKey6 = t.getColumnKey("long"); + long colKey7 = t.getColumnKey("string"); + sharedRealm.beginTransaction(); - TestHelper.addRowWithValues(t, new byte[] {1, 2, 3}, true, new Date(1384423149761L), 4.5D, 5.7F, 100, "string"); + TestHelper.addRowWithValues(t, new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7}, new Object[] {new byte[] {1, 2, 3}, true, new Date(1384423149761L), 4.5D, 5.7F, 100, "string"}); sharedRealm.commitTransaction(); - assertEquals(-1, t.findFirstBoolean(1, false)); - assertEquals(-1, t.findFirstDate(2, new Date(138442314986L))); - assertEquals(-1, t.findFirstDouble(3, 1.0D)); - assertEquals(-1, t.findFirstFloat(4, 1.0F)); - assertEquals(-1, t.findFirstLong(5, 50)); + assertEquals(-1, t.findFirstBoolean(colKey2, false)); + assertEquals(-1, t.findFirstDate(colKey3, new Date(138442314986L))); + assertEquals(-1, t.findFirstDouble(colKey4, 1.0D)); + assertEquals(-1, t.findFirstFloat(colKey5, 1.0F)); + assertEquals(-1, t.findFirstLong(colKey6, 50)); } @Test public void findFirst() { final int TEST_SIZE = 10; Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); + long colKey1 = t.getColumnKey("binary"); + long colKey2 = t.getColumnKey("boolean"); + long colKey3 = t.getColumnKey("date"); + long colKey4 = t.getColumnKey("double"); + long colKey5 = t.getColumnKey("float"); + long colKey6 = t.getColumnKey("long"); + long colKey7 = t.getColumnKey("string"); sharedRealm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { - TestHelper.addRowWithValues(t, new byte[] {1, 2, 3}, true, new Date(i), (double) i, (float) i, i, "string " + i); + TestHelper.addRowWithValues(t, new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7}, new Object[] {new byte[] {1, 2, 3}, true, new Date(i), (double) i, (float) i, i, "string " + i}); } - TestHelper.addRowWithValues(t, new byte[] {1, 2, 3}, true, new Date(TEST_SIZE), (double) TEST_SIZE, (float) TEST_SIZE, TEST_SIZE, ""); + TestHelper.addRowWithValues(t, new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7}, new Object[] {new byte[] {1, 2, 3}, true, new Date(TEST_SIZE), (double) TEST_SIZE, (float) TEST_SIZE, TEST_SIZE, ""}); sharedRealm.commitTransaction(); - assertEquals(0, t.findFirstBoolean(1, true)); + assertEquals(0, t.findFirstBoolean(colKey2, true)); for (int i = 0; i < TEST_SIZE; i++) { - assertEquals(i, t.findFirstDate(2, new Date(i))); - assertEquals(i, t.findFirstDouble(3, (double) i)); - assertEquals(i, t.findFirstFloat(4, (float) i)); - assertEquals(i, t.findFirstLong(5, i)); + assertEquals(i, t.findFirstDate(colKey3, new Date(i))); + assertEquals(i, t.findFirstDouble(colKey4, (double) i)); + assertEquals(i, t.findFirstFloat(colKey5, (float) i)); + assertEquals(i, t.findFirstLong(colKey6, i)); } try { - t.findFirstString(6, null); + t.findFirstString(colKey7, null); fail(); } catch (IllegalArgumentException ignored) {} try { - t.findFirstDate(2, null); + t.findFirstDate(colKey3, null); fail(); } catch (IllegalArgumentException ignored) {} } - @Test - public void getValuesFromNonExistingColumn() { - Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); - sharedRealm.beginTransaction(); - for (int i = 0; i < 10; i++) { - OsObject.createRow(t); - } - sharedRealm.commitTransaction(); - - try { - t.getBinaryByteArray(-1, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getBinaryByteArray(-10, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getBinaryByteArray(9, 0); - fail("Column does not exist"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - - try { - t.getBoolean(-1, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getBoolean(-10, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getBoolean(9, 0); - fail("Column does not exist"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - - try { - t.getDate(-1, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getDate(-10, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getDate(9, 0); - fail("Column does not exist"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - - try { - t.getDouble(-1, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getDouble(-10, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getDouble(9, 0); - fail("Column does not exist"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - - try { - t.getFloat(-1, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getFloat(-10, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getFloat(9, 0); - fail("Column does not exist"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - - try { - t.getLong(-1, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getLong(-10, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getLong(9, 0); - fail("Column does not exist"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - - try { - t.getString(-1, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getString(-10, 0); - fail("Column is less than 0"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - try { - t.getString(9, 0); - fail("Column does not exist"); - } catch (ArrayIndexOutOfBoundsException ignored) { } - } @Test public void getNonExistingColumn() { @@ -280,32 +149,37 @@ public void execute(Table t) { } }); - assertEquals(-1, t.getColumnIndex("non-existing column")); + assertEquals(-1, t.getColumnKey("non-existing column")); try { - t.getColumnIndex(null); + t.getColumnKey(null); fail("column name null"); } catch (IllegalArgumentException ignored) { } } @Test public void setNulls() { + final AtomicLong colKey1 = new AtomicLong(-1); + final AtomicLong colKey2 = new AtomicLong(-1); + final AtomicLong colKey3 = new AtomicLong(-1); + final AtomicLong rowKey = new AtomicLong(-1); Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table t) { - t.addColumn(RealmFieldType.STRING, ""); - t.addColumn(RealmFieldType.DATE, ""); - t.addColumn(RealmFieldType.BINARY, ""); - TestHelper.addRowWithValues(t, "String val", new Date(), new byte[] {1, 2, 3}); + colKey1.set(t.addColumn(RealmFieldType.STRING, "col1")); + colKey2.set(t.addColumn(RealmFieldType.DATE, "col2")); + colKey3.set(t.addColumn(RealmFieldType.BINARY, "col3")); + rowKey.set(TestHelper.addRowWithValues(t, new long[]{colKey1.get(), colKey2.get(), colKey3.get()}, + new Object[]{"String val", new Date(), new byte[] {1, 2, 3}})); } }); sharedRealm.beginTransaction(); try { - t.setString(0, 0, null, false); + t.setString(colKey1.get(), rowKey.get(), null, false); fail("null string not allowed"); } catch (IllegalArgumentException ignored) { } try { - t.setDate(1, 0, null, false); + t.setDate(colKey2.get(), rowKey.get(), null, false); fail("null Date not allowed"); } catch (IllegalArgumentException ignored) { } sharedRealm.commitTransaction(); @@ -332,21 +206,32 @@ public void getName() { @Test public void shouldThrowWhenSetIndexOnWrongRealmFieldType() { Table t = TestHelper.createTableWithAllColumnTypes(sharedRealm); - for (long colIndex = 0; colIndex < t.getColumnCount(); colIndex++) { + + long columnKey1 = t.getColumnKey("binary"); + long columnKey2 = t.getColumnKey("boolean"); + long columnKey3 = t.getColumnKey("date"); + long columnKey4 = t.getColumnKey("double"); + long columnKey5 = t.getColumnKey("float"); + long columnKey6 = t.getColumnKey("long"); + long columnKey7 = t.getColumnKey("string"); + + long[] columnsKeys = new long[]{columnKey1, columnKey2, columnKey3, columnKey4, columnKey5, columnKey6, columnKey7}; + + for (int i = 0; i < columnsKeys.length; i++) { // All types supported addSearchIndex and removeSearchIndex. boolean exceptionExpected = ( - t.getColumnType(colIndex) != RealmFieldType.STRING && - t.getColumnType(colIndex) != RealmFieldType.INTEGER && - t.getColumnType(colIndex) != RealmFieldType.BOOLEAN && - t.getColumnType(colIndex) != RealmFieldType.DATE); + t.getColumnType(columnsKeys[i]) != RealmFieldType.STRING && + t.getColumnType(columnsKeys[i]) != RealmFieldType.INTEGER && + t.getColumnType(columnsKeys[i]) != RealmFieldType.BOOLEAN && + t.getColumnType(columnsKeys[i]) != RealmFieldType.DATE); // Tries to addSearchIndex(). sharedRealm.beginTransaction(); try { - t.addSearchIndex(colIndex); + t.addSearchIndex(columnsKeys[i]); if (exceptionExpected) { - fail("Expected exception for colIndex " + colIndex); + fail("Expected exception for colIndex " + columnsKeys[i]); } } catch (IllegalArgumentException ignored) { } @@ -356,16 +241,16 @@ public void shouldThrowWhenSetIndexOnWrongRealmFieldType() { sharedRealm.beginTransaction(); try { // Currently core will do nothing if the column doesn't have a search index. - t.removeSearchIndex(colIndex); + t.removeSearchIndex(columnsKeys[i]); if (exceptionExpected) { - fail("Expected exception for colIndex " + colIndex); + fail("Expected exception for colIndex " + columnsKeys[i]); } } catch (IllegalArgumentException ignored) { } sharedRealm.commitTransaction(); // Tries to hasSearchIndex() for all columnTypes. - t.hasSearchIndex(colIndex); + t.hasSearchIndex(columnsKeys[i]); } } @@ -385,339 +270,157 @@ public void execute(Table t) { @Test public void tableNumbers() { + final AtomicLong colKey1 = new AtomicLong(-1); + final AtomicLong colKey2 = new AtomicLong(-1); + final AtomicLong colKey3 = new AtomicLong(-1); + final AtomicLong colKey4 = new AtomicLong(-1); + + final AtomicLong rowKey0 = new AtomicLong(-1); + final AtomicLong rowKey1 = new AtomicLong(-1); + final AtomicLong rowKey2 = new AtomicLong(-1); + final AtomicLong rowKey3 = new AtomicLong(-1); + final AtomicLong rowKey4 = new AtomicLong(-1); + final AtomicLong rowKey5 = new AtomicLong(-1); + final AtomicLong rowKey6 = new AtomicLong(-1); Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table t) { - t.addColumn(RealmFieldType.INTEGER, "intCol"); - t.addColumn(RealmFieldType.DOUBLE, "doubleCol"); - t.addColumn(RealmFieldType.FLOAT, "floatCol"); - t.addColumn(RealmFieldType.STRING, "StringCol"); + colKey1.set(t.addColumn(RealmFieldType.INTEGER, "intCol")); + colKey2.set(t.addColumn(RealmFieldType.DOUBLE, "doubleCol")); + colKey3.set(t.addColumn(RealmFieldType.FLOAT, "floatCol")); + colKey4.set(t.addColumn(RealmFieldType.STRING, "StringCol")); // Adds 3 rows of data with same values in each column. - TestHelper.addRowWithValues(t, 1, 2.0D, 3.0F, "s1"); - TestHelper.addRowWithValues(t, 1, 2.0D, 3.0F, "s1"); - TestHelper.addRowWithValues(t, 1, 2.0D, 3.0F, "s1"); + rowKey0.set(TestHelper.addRowWithValues(t, new long[]{colKey1.get(), colKey2.get(), colKey3.get(), colKey4.get()}, new Object[]{1, 2.0D, 3.0F, "s1"})); + rowKey1.set(TestHelper.addRowWithValues(t, new long[]{colKey1.get(), colKey2.get(), colKey3.get(), colKey4.get()}, new Object[]{1, 2.0D, 3.0F, "s1"})); + rowKey2.set(TestHelper.addRowWithValues(t, new long[]{colKey1.get(), colKey2.get(), colKey3.get(), colKey4.get()}, new Object[]{1, 2.0D, 3.0F, "s1"})); // Adds other values. - TestHelper.addRowWithValues(t, 10, 20.0D, 30.0F, "s10"); - TestHelper.addRowWithValues(t, 100, 200.0D, 300.0F, "s100"); - TestHelper.addRowWithValues(t, 1000, 2000.0D, 3000.0F, "s1000"); + rowKey3.set(TestHelper.addRowWithValues(t, new long[]{colKey1.get(), colKey2.get(), colKey3.get(), colKey4.get()}, new Object[]{10, 20.0D, 30.0F, "s10"})); + rowKey4.set(TestHelper.addRowWithValues(t, new long[]{colKey1.get(), colKey2.get(), colKey3.get(), colKey4.get()}, new Object[]{100, 200.0D, 300.0F, "s100"})); + rowKey5.set(TestHelper.addRowWithValues(t, new long[]{colKey1.get(), colKey2.get(), colKey3.get(), colKey4.get()}, new Object[]{1000, 2000.0D, 3000.0F, "s1000"})); } }); // Counts instances of values added in the first 3 rows. - assertEquals(3, t.count(0, 1)); - assertEquals(3, t.count(1, 2.0D)); - assertEquals(3, t.count(2, 3.0F)); - assertEquals(3, t.count(3, "s1")); + assertEquals(3, t.count(colKey1.get(), 1)); + assertEquals(3, t.count(colKey2.get(), 2.0D)); + assertEquals(3, t.count(colKey3.get(), 3.0F)); + assertEquals(3, t.count(colKey4.get(), "s1")); - assertEquals(3, t.findFirstDouble(1, 20.0D)); // Find rows index for first double value of 20.0 in column 1. - assertEquals(4, t.findFirstFloat(2, 300.0F)); // Find rows index for first float value of 300.0 in column 2. + assertEquals(3, t.findFirstDouble(colKey2.get(), 20.0D)); // Find rows index for first double value of 20.0 in column 1. + assertEquals(4, t.findFirstFloat(colKey3.get(), 300.0F)); // Find rows index for first float value of 300.0 in column 2. // Sets double and float. sharedRealm.beginTransaction(); - t.setDouble(1, 2, -2.0D, false); - t.setFloat(2, 2, -3.0F, false); + t.setDouble(colKey2.get(), rowKey2.get(), -2.0D, false); + t.setFloat(colKey3.get(), rowKey2.get(), -3.0F, false); sharedRealm.commitTransaction(); // Gets double tests. - assertEquals(-2.0D, t.getDouble(1, 2)); - assertEquals(20.0D, t.getDouble(1, 3)); - assertEquals(200.0D, t.getDouble(1, 4)); - assertEquals(2000.0D, t.getDouble(1, 5)); + assertEquals(-2.0D, t.getDouble(colKey2.get(), rowKey2.get())); + assertEquals(20.0D, t.getDouble(colKey2.get(), rowKey3.get())); + assertEquals(200.0D, t.getDouble(colKey2.get(), rowKey4.get())); + assertEquals(2000.0D, t.getDouble(colKey2.get(), rowKey5.get())); // Gets float test. - assertEquals(-3.0F, t.getFloat(2, 2)); - assertEquals(30.0F, t.getFloat(2, 3)); - assertEquals(300.0F, t.getFloat(2, 4)); - assertEquals(3000.0F, t.getFloat(2, 5)); - } - - // Tests the migration of a string column to be nullable. - @Test - public void convertToNullable() { - RealmFieldType[] columnTypes = {RealmFieldType.BOOLEAN, RealmFieldType.DATE, RealmFieldType.DOUBLE, - RealmFieldType.FLOAT, RealmFieldType.INTEGER, RealmFieldType.BINARY, RealmFieldType.STRING}; - int tableIndex = 0; - for (final RealmFieldType columnType : columnTypes) { - // Tests various combinations of column names and nullability. - String[] columnNames = {"foobar", "__TMP__0"}; - for (final boolean nullable : new boolean[] {Table.NOT_NULLABLE, Table.NULLABLE}) { - for (final String columnName : columnNames) { - final AtomicLong colIndexRef = new AtomicLong(); - Table table = TestHelper.createTable(sharedRealm, "temp" + tableIndex, new TestHelper.AdditionalTableSetup() { - @Override - public void execute(Table table) { - long colIndex = table.addColumn(columnType, columnName, nullable); - colIndexRef.set(colIndex); - table.addColumn(RealmFieldType.BOOLEAN, "bool"); - OsObject.createRow(table); - if (columnType == RealmFieldType.BOOLEAN) { - table.setBoolean(colIndex, 0, true, false); - } else if (columnType == RealmFieldType.DATE) { - table.setDate(colIndex, 0, new Date(0), false); - } else if (columnType == RealmFieldType.DOUBLE) { - table.setDouble(colIndex, 0, 1.0, false); - } else if (columnType == RealmFieldType.FLOAT) { - table.setFloat(colIndex, 0, 1.0F, false); - } else if (columnType == RealmFieldType.INTEGER) { - table.setLong(colIndex, 0, 1, false); - } else if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 0, new byte[] {0}, false); - } else if (columnType == RealmFieldType.STRING) { - table.setString(colIndex, 0, "Foo", false); - } - try { - OsObject.createRow(table); - if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 1, null, false); - } else if (columnType == RealmFieldType.STRING) { - table.setString(colIndex, 1, null, false); - } else { - table.getCheckedRow(1).setNull(colIndex); - } - - if (!nullable) { - fail(); - } - } catch (IllegalArgumentException ignored) { - } - table.moveLastOver(table.size() - 1); - } - }); - assertEquals(1, table.size()); - - long colIndex = colIndexRef.get(); - - sharedRealm.beginTransaction(); - table.convertColumnToNullable(colIndex); - sharedRealm.commitTransaction(); - assertTrue(table.isColumnNullable(colIndex)); - assertEquals(1, table.size()); - assertEquals(2, table.getColumnCount()); - assertTrue(table.getColumnIndex(columnName) >= 0); - assertEquals(colIndex, table.getColumnIndex(columnName)); - - sharedRealm.beginTransaction(); - OsObject.createRow(table); - if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 0, null, false); - } else if (columnType == RealmFieldType.STRING) { - table.setString(colIndex, 0, null, false); - } else { - table.getCheckedRow(0).setNull(colIndex); - } - sharedRealm.commitTransaction(); - - assertEquals(2, table.size()); - - if (columnType == RealmFieldType.BINARY) { - assertNull(table.getBinaryByteArray(colIndex, 1)); - } else if (columnType == RealmFieldType.STRING) { - assertNull(table.getString(colIndex, 1)); - } else { - assertTrue(table.getUncheckedRow(1).isNull(colIndex)); - } - tableIndex++; - } - } - } - } - - @Test - public void convertToNotNullable() { - RealmFieldType[] columnTypes = {RealmFieldType.BOOLEAN, RealmFieldType.DATE, RealmFieldType.DOUBLE, - RealmFieldType.FLOAT, RealmFieldType.INTEGER, RealmFieldType.BINARY, RealmFieldType.STRING}; - int tableIndex = 0; - for (final RealmFieldType columnType : columnTypes) { - // Tests various combinations of column names and nullability. - String[] columnNames = {"foobar", "__TMP__0"}; - for (final boolean nullable : new boolean[] {Table.NOT_NULLABLE, Table.NULLABLE}) { - for (final String columnName : columnNames) { - final AtomicLong colIndexRef = new AtomicLong(); - Table table = TestHelper.createTable(sharedRealm, "temp" + tableIndex, new TestHelper.AdditionalTableSetup() { - @Override - public void execute(Table table) { - long colIndex = table.addColumn(columnType, columnName, nullable); - colIndexRef.set(colIndex); - table.addColumn(RealmFieldType.BOOLEAN, "bool"); - OsObject.createRow(table); - if (columnType == RealmFieldType.BOOLEAN) { - table.setBoolean(colIndex, 0, true, false); - } else if (columnType == RealmFieldType.DATE) { - table.setDate(colIndex, 0, new Date(1), false); - } else if (columnType == RealmFieldType.DOUBLE) { - table.setDouble(colIndex, 0, 1.0, false); - } else if (columnType == RealmFieldType.FLOAT) { - table.setFloat(colIndex, 0, 1.0F, false); - } else if (columnType == RealmFieldType.INTEGER) { - table.setLong(colIndex, 0, 1, false); - } else if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 0, new byte[] {0}, false); - } else if (columnType == RealmFieldType.STRING) { table.setString(colIndex, 0, "Foo", false); } - try { - OsObject.createRow(table); - if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 1, null, false); - } else if (columnType == RealmFieldType.STRING) { - table.setString(colIndex, 1, null, false); - } else { - table.getCheckedRow(1).setNull(colIndex); - } - - if (!nullable) { - fail(); - } - } catch (IllegalArgumentException ignored) { - } - } - }); - assertEquals(2, table.size()); - - long colIndex = colIndexRef.get(); - - sharedRealm.beginTransaction(); - table.convertColumnToNotNullable(colIndex); - sharedRealm.commitTransaction(); - assertFalse(table.isColumnNullable(colIndex)); - assertEquals(2, table.size()); - assertEquals(2, table.getColumnCount()); - assertTrue(table.getColumnIndex(columnName) >= 0); - assertEquals(colIndex, table.getColumnIndex(columnName)); - - sharedRealm.beginTransaction(); - OsObject.createRow(table); - try { - if (columnType == RealmFieldType.BINARY) { - table.setBinaryByteArray(colIndex, 0, null, false); - } else if (columnType == RealmFieldType.STRING) { - table.setString(colIndex, 0, null, false); - } else { - table.getCheckedRow(0).setNull(colIndex); - } - if (!nullable) { - fail(); - } - } catch (IllegalArgumentException ignored) { - } - table.moveLastOver(table.size() -1); - sharedRealm.commitTransaction(); - - assertEquals(2, table.size()); - - if (columnType == RealmFieldType.BINARY) { - assertNotNull(table.getBinaryByteArray(colIndex, 1)); - } else if (columnType == RealmFieldType.STRING) { - assertNotNull(table.getString(colIndex, 1)); - assertEquals("", table.getString(colIndex, 1)); - } else { - assertFalse(table.getUncheckedRow(1).isNull(colIndex)); - if (columnType == RealmFieldType.BOOLEAN) { - assertEquals(false, table.getBoolean(colIndex, 1)); - } else if (columnType == RealmFieldType.DATE) { - assertEquals(0, table.getDate(colIndex, 1).getTime()); - } else if (columnType == RealmFieldType.DOUBLE) { - assertEquals(0.0, table.getDouble(colIndex, 1)); - } else if (columnType == RealmFieldType.FLOAT) { - assertEquals(0.0F, table.getFloat(colIndex, 1)); - } else if (columnType == RealmFieldType.INTEGER) { - assertEquals(0, table.getLong(colIndex, 1)); - } - } - tableIndex++; - } - } - } + assertEquals(-3.0F, t.getFloat(colKey3.get(), rowKey2.get())); + assertEquals(30.0F, t.getFloat(colKey3.get(), rowKey3.get())); + assertEquals(300.0F, t.getFloat(colKey3.get(), rowKey4.get())); + assertEquals(3000.0F, t.getFloat(colKey3.get(), rowKey5.get())); } // Adds column and read back if it is nullable or not. @Test public void isNullable() { + final AtomicLong columnKey0 = new AtomicLong(-1); + final AtomicLong columnKey1 = new AtomicLong(-1); Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table table) { - table.addColumn(RealmFieldType.STRING, "string1", Table.NOT_NULLABLE); - table.addColumn(RealmFieldType.STRING, "string2", Table.NULLABLE); + columnKey0.set(table.addColumn(RealmFieldType.STRING, "string1", Table.NOT_NULLABLE)); + columnKey1.set(table.addColumn(RealmFieldType.STRING, "string2", Table.NULLABLE)); } }); - assertFalse(table.isColumnNullable(0)); - assertTrue(table.isColumnNullable(1)); + assertFalse(table.isColumnNullable(columnKey0.get())); + assertTrue(table.isColumnNullable(columnKey1.get())); } @Test public void defaultValue_setAndGet() { - final OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configFactory.createConfiguration()); + final OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configFactory.createConfiguration(), OsSharedRealm.VersionID.LIVE); //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); final Table table = sharedRealm.createTable(Table.getTableNameForClass("DefaultValueTest")); - sharedRealm.commitTransaction(); - - List> columnInfoList = Arrays.asList( - new Pair(RealmFieldType.STRING, "string value"), - new Pair(RealmFieldType.INTEGER, 100L), - new Pair(RealmFieldType.BOOLEAN, true), - new Pair(RealmFieldType.BINARY, new byte[] {123}), - new Pair(RealmFieldType.DATE, new Date(123456)), - new Pair(RealmFieldType.FLOAT, 1.234F), - new Pair(RealmFieldType.DOUBLE, Math.PI), - new Pair(RealmFieldType.OBJECT, 0L) - // FIXME: Currently, LIST does not support default value. - // new CollectionChange(RealmFieldType.LIST, ) - ); - - for (Pair columnInfo : columnInfoList) { - final RealmFieldType type = columnInfo.first; - if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { - table.addColumnLink(type, type.name().toLowerCase(Locale.ENGLISH) + "Col", table); - } else { - table.addColumn(type, type.name().toLowerCase(Locale.ENGLISH) + "Col"); - } - } - - sharedRealm.beginTransaction(); - OsObject.createRow(table); - - ListIterator> it = columnInfoList.listIterator(); - for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { - Pair columnInfo = it.next(); - final RealmFieldType type = columnInfo.first; - final Object value = columnInfo.second; + long colKey1 = table.addColumn(RealmFieldType.STRING, RealmFieldType.STRING.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey2 = table.addColumn(RealmFieldType.INTEGER, RealmFieldType.INTEGER.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey3 = table.addColumn(RealmFieldType.BOOLEAN, RealmFieldType.BOOLEAN.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey4 = table.addColumn(RealmFieldType.BINARY, RealmFieldType.BINARY.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey5 = table.addColumn(RealmFieldType.DATE, RealmFieldType.DATE.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey6 = table.addColumn(RealmFieldType.FLOAT, RealmFieldType.FLOAT.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey7 = table.addColumn(RealmFieldType.DOUBLE, RealmFieldType.DOUBLE.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey8 = table.addColumnLink(RealmFieldType.OBJECT, RealmFieldType.OBJECT.name().toLowerCase(Locale.ENGLISH) + "Col", table); + + long[] columnKeys = new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7, colKey8}; + Object[] datas = new Object[]{"string value", + 100L, + true, + new byte[]{123}, + new Date(123456), + 1.234F, + Math.PI, + 0L}; + + RealmFieldType[] types = new RealmFieldType[]{RealmFieldType.STRING, + RealmFieldType.INTEGER, + RealmFieldType.BOOLEAN, + RealmFieldType.BINARY, + RealmFieldType.DATE, + RealmFieldType.FLOAT, + RealmFieldType.DOUBLE, + RealmFieldType.OBJECT}; + + long rowKey = OsObject.createRow(table); + + for (int i = 0; i < columnKeys.length; i++) { + final RealmFieldType type = types[i]; + final Object value = datas[i]; switch (type) { case STRING: - table.setString(columnIndex, 0, (String) value, true); - assertEquals(value, table.getString(columnIndex, 0)); + table.setString(columnKeys[i], rowKey, (String) value, true); + assertEquals(value, table.getString(columnKeys[i], rowKey)); break; case INTEGER: - table.setLong(columnIndex, 0, (long) value, true); - assertEquals(value, table.getLong(columnIndex, 0)); + table.setLong(columnKeys[i], rowKey, (long) value, true); + assertEquals(value, table.getLong(columnKeys[i], rowKey)); break; case BOOLEAN: - table.setBoolean(columnIndex, 0, (boolean) value, true); - assertEquals(value, table.getBoolean(columnIndex, 0)); + table.setBoolean(columnKeys[i], rowKey, (boolean) value, true); + assertEquals(value, table.getBoolean(columnKeys[i], rowKey)); break; case BINARY: - table.setBinaryByteArray(columnIndex, 0, (byte[]) value, true); - assertTrue(Arrays.equals((byte[]) value, table.getBinaryByteArray(columnIndex, 0))); + table.setBinaryByteArray(columnKeys[i], rowKey, (byte[]) value, true); + assertTrue(Arrays.equals((byte[]) value, table.getBinaryByteArray(columnKeys[i], rowKey))); break; case DATE: - table.setDate(columnIndex, 0, (Date) value, true); - assertEquals(value, table.getDate(columnIndex, 0)); + table.setDate(columnKeys[i], rowKey, (Date) value, true); + assertEquals(value, table.getDate(columnKeys[i], rowKey)); break; case FLOAT: - table.setFloat(columnIndex, 0, (float) value, true); - assertEquals(value, table.getFloat(columnIndex, 0)); + table.setFloat(columnKeys[i], rowKey, (float) value, true); + assertEquals(value, table.getFloat(columnKeys[i], rowKey)); break; case DOUBLE: - table.setDouble(columnIndex, 0, (double) value, true); - assertEquals(value, table.getDouble(columnIndex, 0)); + table.setDouble(columnKeys[i], rowKey, (double) value, true); + assertEquals(value, table.getDouble(columnKeys[i], rowKey)); break; case OBJECT: - table.setLink(columnIndex, 0, (long) value, true); - assertEquals(value, table.getLink(columnIndex, 0)); + table.setLink(columnKeys[i], rowKey, (long) value, true); + assertEquals(value, table.getLink(columnKeys[i], rowKey)); break; default: throw new RuntimeException("unexpected field type: " + type); @@ -726,36 +429,34 @@ public void defaultValue_setAndGet() { sharedRealm.commitTransaction(); // Checks if the value can be read after committing transaction. - it = columnInfoList.listIterator(); - for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { - Pair columnInfo = it.next(); - final RealmFieldType type = columnInfo.first; - final Object value = columnInfo.second; + for (int i = 0; i < columnKeys.length; i++) { + final RealmFieldType type = types[i]; + final Object value = datas[i]; switch (type) { case STRING: - assertEquals(value, table.getString(columnIndex, 0)); + assertEquals(value, table.getString(columnKeys[i], rowKey)); break; case INTEGER: - assertEquals(value, table.getLong(columnIndex, 0)); + assertEquals(value, table.getLong(columnKeys[i], rowKey)); break; case BOOLEAN: - assertEquals(value, table.getBoolean(columnIndex, 0)); + assertEquals(value, table.getBoolean(columnKeys[i], rowKey)); break; case BINARY: - assertTrue(Arrays.equals((byte[]) value, table.getBinaryByteArray(columnIndex, 0))); + assertTrue(Arrays.equals((byte[]) value, table.getBinaryByteArray(columnKeys[i], rowKey))); break; case DATE: - assertEquals(value, table.getDate(columnIndex, 0)); + assertEquals(value, table.getDate(columnKeys[i], rowKey)); break; case FLOAT: - assertEquals(value, table.getFloat(columnIndex, 0)); + assertEquals(value, table.getFloat(columnKeys[i], rowKey)); break; case DOUBLE: - assertEquals(value, table.getDouble(columnIndex, 0)); + assertEquals(value, table.getDouble(columnKeys[i], rowKey)); break; case OBJECT: - assertEquals(value, table.getLink(columnIndex, 0)); + assertEquals(value, table.getLink(columnKeys[i], rowKey)); break; default: throw new RuntimeException("unexpected field type: " + type); @@ -769,86 +470,89 @@ public void defaultValue_setAndGet() { @Test public void defaultValue_setMultipleTimes() { - final OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configFactory.createConfiguration()); + final OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configFactory.createConfiguration(), OsSharedRealm.VersionID.LIVE); //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); final Table table = sharedRealm.createTable(Table.getTableNameForClass("DefaultValueTest")); - sharedRealm.commitTransaction(); - - List> columnInfoList = Arrays.asList( - new Pair(RealmFieldType.STRING, new String[] {"string value1", "string value2"}), - new Pair(RealmFieldType.INTEGER, new Long[] {100L, 102L}), - new Pair(RealmFieldType.BOOLEAN, new Boolean[] {false, true}), - new Pair(RealmFieldType.BINARY, new byte[][] {new byte[] {123}, new byte[] {-123}}), - new Pair(RealmFieldType.DATE, new Date[] {new Date(123456), new Date(13579)}), - new Pair(RealmFieldType.FLOAT, new Float[] {1.234F, 100F}), - new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}), - new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L}) - // FIXME: Currently, LIST does not support default value. - // new CollectionChange(RealmFieldType.LIST, ) - ); - - for (Pair columnInfo : columnInfoList) { - final RealmFieldType type = columnInfo.first; - if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { - table.addColumnLink(type, type.name().toLowerCase(Locale.ENGLISH) + "Col", table); - } else { - table.addColumn(type, type.name().toLowerCase(Locale.ENGLISH) + "Col"); - } - } - - sharedRealm.beginTransaction(); - OsObject.createRow(table); + long colKey1 = table.addColumn(RealmFieldType.STRING, RealmFieldType.STRING.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey2 = table.addColumn(RealmFieldType.INTEGER, RealmFieldType.INTEGER.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey3 = table.addColumn(RealmFieldType.BOOLEAN, RealmFieldType.BOOLEAN.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey4 = table.addColumn(RealmFieldType.BINARY, RealmFieldType.BINARY.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey5 = table.addColumn(RealmFieldType.DATE, RealmFieldType.DATE.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey6 = table.addColumn(RealmFieldType.FLOAT, RealmFieldType.FLOAT.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey7 = table.addColumn(RealmFieldType.DOUBLE, RealmFieldType.DOUBLE.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey8 = table.addColumnLink(RealmFieldType.OBJECT, RealmFieldType.OBJECT.name().toLowerCase(Locale.ENGLISH) + "Col", table); + + + long[] columnKeys = new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7, colKey8}; + Object[] datas = new Object[]{new String[] {"string value1", "string value2"}, + new Long[] {100L, 102L}, + new Boolean[] {false, true}, + new byte[][] {new byte[] {123}, new byte[] {-123}}, + new Date[] {new Date(123456), new Date(13579)}, + new Float[] {1.234F, 100F}, + new Double[] {Math.PI, Math.E}, + new Long[] {0L, 1L} + + }; + RealmFieldType[] types = new RealmFieldType[]{RealmFieldType.STRING, + RealmFieldType.INTEGER, + RealmFieldType.BOOLEAN, + RealmFieldType.BINARY, + RealmFieldType.DATE, + RealmFieldType.FLOAT, + RealmFieldType.DOUBLE, + RealmFieldType.OBJECT}; + + long rowKey = OsObject.createRow(table); OsObject.createRow(table); // For link field update. - ListIterator> it = columnInfoList.listIterator(); - for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { - Pair columnInfo = it.next(); - final RealmFieldType type = columnInfo.first; - final Object value1 = ((Object[]) columnInfo.second)[0]; - final Object value2 = ((Object[]) columnInfo.second)[1]; + for (int i = 0; i < columnKeys.length; i++) { + final RealmFieldType type = types[i]; + final Object value1 = ((Object[]) datas[i])[0]; + final Object value2 = ((Object[]) datas[i])[1]; switch (type) { case STRING: - table.setString(columnIndex, 0, (String) value1, true); - table.setString(columnIndex, 0, (String) value2, true); - assertEquals(value2, table.getString(columnIndex, 0)); + table.setString(columnKeys[i], rowKey, (String) value1, true); + table.setString(columnKeys[i], rowKey, (String) value2, true); + assertEquals(value2, table.getString(columnKeys[i], rowKey)); break; case INTEGER: - table.setLong(columnIndex, 0, (long) value1, true); - table.setLong(columnIndex, 0, (long) value2, true); - assertEquals(value2, table.getLong(columnIndex, 0)); + table.setLong(columnKeys[i], rowKey, (long) value1, true); + table.setLong(columnKeys[i], rowKey, (long) value2, true); + assertEquals(value2, table.getLong(columnKeys[i], rowKey)); break; case BOOLEAN: - table.setBoolean(columnIndex, 0, (boolean) value1, true); - table.setBoolean(columnIndex, 0, (boolean) value2, true); - assertEquals(value2, table.getBoolean(columnIndex, 0)); + table.setBoolean(columnKeys[i], rowKey, (boolean) value1, true); + table.setBoolean(columnKeys[i], rowKey, (boolean) value2, true); + assertEquals(value2, table.getBoolean(columnKeys[i], rowKey)); break; case BINARY: - table.setBinaryByteArray(columnIndex, 0, (byte[]) value1, true); - table.setBinaryByteArray(columnIndex, 0, (byte[]) value2, true); - assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnIndex, 0))); + table.setBinaryByteArray(columnKeys[i], rowKey, (byte[]) value1, true); + table.setBinaryByteArray(columnKeys[i], rowKey, (byte[]) value2, true); + assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnKeys[i], rowKey))); break; case DATE: - table.setDate(columnIndex, 0, (Date) value1, true); - table.setDate(columnIndex, 0, (Date) value2, true); - assertEquals(value2, table.getDate(columnIndex, 0)); + table.setDate(columnKeys[i], rowKey, (Date) value1, true); + table.setDate(columnKeys[i], rowKey, (Date) value2, true); + assertEquals(value2, table.getDate(columnKeys[i], rowKey)); break; case FLOAT: - table.setFloat(columnIndex, 0, (float) value1, true); - table.setFloat(columnIndex, 0, (float) value2, true); - assertEquals(value2, table.getFloat(columnIndex, 0)); + table.setFloat(columnKeys[i], rowKey, (float) value1, true); + table.setFloat(columnKeys[i], rowKey, (float) value2, true); + assertEquals(value2, table.getFloat(columnKeys[i], rowKey)); break; case DOUBLE: - table.setDouble(columnIndex, 0, (double) value1, true); - table.setDouble(columnIndex, 0, (double) value2, true); - assertEquals(value2, table.getDouble(columnIndex, 0)); + table.setDouble(columnKeys[i], rowKey, (double) value1, true); + table.setDouble(columnKeys[i], rowKey, (double) value2, true); + assertEquals(value2, table.getDouble(columnKeys[i], rowKey)); break; case OBJECT: - table.setLink(columnIndex, 0, (long) value1, true); - table.setLink(columnIndex, 0, (long) value2, true); - assertEquals(value2, table.getLink(columnIndex, 0)); + table.setLink(columnKeys[i], rowKey, (long) value1, true); + table.setLink(columnKeys[i], rowKey, (long) value2, true); + assertEquals(value2, table.getLink(columnKeys[i], rowKey)); break; default: throw new RuntimeException("unexpected field type: " + type); @@ -857,36 +561,34 @@ public void defaultValue_setMultipleTimes() { sharedRealm.commitTransaction(); // Checks if the value can be read after committing transaction. - it = columnInfoList.listIterator(); - for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { - Pair columnInfo = it.next(); - final RealmFieldType type = columnInfo.first; - final Object value2 = ((Object[]) columnInfo.second)[1]; + for (int i = 0; i < columnKeys.length; i++) { + final RealmFieldType type = types[i]; + final Object value2 = ((Object[]) datas[i])[1]; switch (type) { case STRING: - assertEquals(value2, table.getString(columnIndex, 0)); + assertEquals(value2, table.getString(columnKeys[i], rowKey)); break; case INTEGER: - assertEquals(value2, table.getLong(columnIndex, 0)); + assertEquals(value2, table.getLong(columnKeys[i], rowKey)); break; case BOOLEAN: - assertEquals(value2, table.getBoolean(columnIndex, 0)); + assertEquals(value2, table.getBoolean(columnKeys[i], rowKey)); break; case BINARY: - assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnIndex, 0))); + assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnKeys[i], rowKey))); break; case DATE: - assertEquals(value2, table.getDate(columnIndex, 0)); + assertEquals(value2, table.getDate(columnKeys[i], rowKey)); break; case FLOAT: - assertEquals(value2, table.getFloat(columnIndex, 0)); + assertEquals(value2, table.getFloat(columnKeys[i], rowKey)); break; case DOUBLE: - assertEquals(value2, table.getDouble(columnIndex, 0)); + assertEquals(value2, table.getDouble(columnKeys[i], rowKey)); break; case OBJECT: - assertEquals(value2, table.getLink(columnIndex, 0)); + assertEquals(value2, table.getLink(columnKeys[i], rowKey)); break; default: throw new RuntimeException("unexpected field type: " + type); @@ -899,70 +601,71 @@ public void defaultValue_setMultipleTimes() { @Test public void defaultValue_overwrittenByNonDefault() { - final OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configFactory.createConfiguration()); + final OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configFactory.createConfiguration(), OsSharedRealm.VersionID.LIVE); //noinspection TryFinallyCanBeTryWithResources try { sharedRealm.beginTransaction(); final Table table = sharedRealm.createTable(Table.getTableNameForClass("DefaultValueTest")); - sharedRealm.commitTransaction(); - - List> columnInfoList = Arrays.asList( - new Pair(RealmFieldType.STRING, new String[] {"string value1", "string value2"}), - new Pair(RealmFieldType.INTEGER, new Long[] {100L, 102L}), - new Pair(RealmFieldType.BOOLEAN, new Boolean[] {false, true}), - new Pair(RealmFieldType.BINARY, new byte[][] {new byte[] {123}, new byte[] {-123}}), - new Pair(RealmFieldType.DATE, new Date[] {new Date(123456), new Date(13579)}), - new Pair(RealmFieldType.FLOAT, new Float[] {1.234F, 100F}), - new Pair(RealmFieldType.DOUBLE, new Double[] {Math.PI, Math.E}), - new Pair(RealmFieldType.OBJECT, new Long[] {0L, 1L}) - // FIXME: Currently, LIST does not support default value. - // new CollectionChange(RealmFieldType.LIST, ) - ); - - for (Pair columnInfo : columnInfoList) { - final RealmFieldType type = columnInfo.first; - if (type == RealmFieldType.OBJECT || type == RealmFieldType.LIST) { - table.addColumnLink(type, type.name().toLowerCase(Locale.ENGLISH) + "Col", table); - } else { - table.addColumn(type, type.name().toLowerCase(Locale.ENGLISH) + "Col"); - } - } - - sharedRealm.beginTransaction(); - OsObject.createRow(table); + long colKey1 = table.addColumn(RealmFieldType.STRING, RealmFieldType.STRING.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey2 = table.addColumn(RealmFieldType.INTEGER, RealmFieldType.INTEGER.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey3 = table.addColumn(RealmFieldType.BOOLEAN, RealmFieldType.BOOLEAN.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey4 = table.addColumn(RealmFieldType.BINARY, RealmFieldType.BINARY.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey5 = table.addColumn(RealmFieldType.DATE, RealmFieldType.DATE.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey6 = table.addColumn(RealmFieldType.FLOAT, RealmFieldType.FLOAT.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey7 = table.addColumn(RealmFieldType.DOUBLE, RealmFieldType.DOUBLE.name().toLowerCase(Locale.ENGLISH) + "Col"); + long colKey8 = table.addColumnLink(RealmFieldType.OBJECT, RealmFieldType.OBJECT.name().toLowerCase(Locale.ENGLISH) + "Col", table); + + long[] columnKeys = new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7, colKey8}; + Object[] datas = new Object[]{new String[] {"string value1", "string value2"}, + new Long[] {100L, 102L}, + new Boolean[] {false, true}, + new byte[][] {new byte[] {123}, new byte[] {-123}}, + new Date[] {new Date(123456), new Date(13579)}, + new Float[] {1.234F, 100F}, + new Double[] {Math.PI, Math.E}, + new Long[] {0L, 1L} + + }; + RealmFieldType[] types = new RealmFieldType[]{RealmFieldType.STRING, + RealmFieldType.INTEGER, + RealmFieldType.BOOLEAN, + RealmFieldType.BINARY, + RealmFieldType.DATE, + RealmFieldType.FLOAT, + RealmFieldType.DOUBLE, + RealmFieldType.OBJECT}; + long rowKey = OsObject.createRow(table); OsObject.createRow(table); // For link field update. // Sets as default. - ListIterator> it = columnInfoList.listIterator(); - for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { - Pair columnInfo = it.next(); - final RealmFieldType type = columnInfo.first; - final Object value1 = ((Object[]) columnInfo.second)[0]; + for (int i = 0; i< columnKeys.length; i++) { + final RealmFieldType type = types[i]; + final Object value1 = ((Object[]) datas[i])[0]; switch (type) { case STRING: - table.setString(columnIndex, 0, (String) value1, true); + table.setString(columnKeys[i], rowKey, (String) value1, true); break; case INTEGER: - table.setLong(columnIndex, 0, (long) value1, true); + table.setLong(columnKeys[i], rowKey, (long) value1, true); break; case BOOLEAN: - table.setBoolean(columnIndex, 0, (boolean) value1, true); + table.setBoolean(columnKeys[i], rowKey, (boolean) value1, true); break; case BINARY: - table.setBinaryByteArray(columnIndex, 0, (byte[]) value1, true); + table.setBinaryByteArray(columnKeys[i], rowKey, (byte[]) value1, true); break; case DATE: - table.setDate(columnIndex, 0, (Date) value1, true); + table.setDate(columnKeys[i], rowKey, (Date) value1, true); break; case FLOAT: - table.setFloat(columnIndex, 0, (float) value1, true); + table.setFloat(columnKeys[i], rowKey, (float) value1, true); break; case DOUBLE: - table.setDouble(columnIndex, 0, (double) value1, true); + table.setDouble(columnKeys[i], rowKey, (double) value1, true); break; case OBJECT: - table.setLink(columnIndex, 0, (long) value1, true); + table.setLink(columnKeys[i], rowKey, (long) value1, true); break; default: throw new RuntimeException("unexpected field type: " + type); @@ -972,44 +675,42 @@ public void defaultValue_overwrittenByNonDefault() { // Updates as non default. sharedRealm.beginTransaction(); - it = columnInfoList.listIterator(); - for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { - Pair columnInfo = it.next(); - final RealmFieldType type = columnInfo.first; - final Object value2 = ((Object[]) columnInfo.second)[1]; + for (int i = 0; i< columnKeys.length; i++) { + final RealmFieldType type = types[i]; + final Object value2 = ((Object[]) datas[i])[1]; switch (type) { case STRING: - table.setString(columnIndex, 0, (String) value2, false); - assertEquals(value2, table.getString(columnIndex, 0)); + table.setString(columnKeys[i], rowKey, (String) value2, false); + assertEquals(value2, table.getString(columnKeys[i], rowKey)); break; case INTEGER: - table.setLong(columnIndex, 0, (long) value2, false); - assertEquals(value2, table.getLong(columnIndex, 0)); + table.setLong(columnKeys[i], rowKey, (long) value2, false); + assertEquals(value2, table.getLong(columnKeys[i], rowKey)); break; case BOOLEAN: - table.setBoolean(columnIndex, 0, (boolean) value2, false); - assertEquals(value2, table.getBoolean(columnIndex, 0)); + table.setBoolean(columnKeys[i], rowKey, (boolean) value2, false); + assertEquals(value2, table.getBoolean(columnKeys[i], rowKey)); break; case BINARY: - table.setBinaryByteArray(columnIndex, 0, (byte[]) value2, false); - assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnIndex, 0))); + table.setBinaryByteArray(columnKeys[i], rowKey, (byte[]) value2, false); + assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnKeys[i], rowKey))); break; case DATE: - table.setDate(columnIndex, 0, (Date) value2, false); - assertEquals(value2, table.getDate(columnIndex, 0)); + table.setDate(columnKeys[i], rowKey, (Date) value2, false); + assertEquals(value2, table.getDate(columnKeys[i], rowKey)); break; case FLOAT: - table.setFloat(columnIndex, 0, (float) value2, false); - assertEquals(value2, table.getFloat(columnIndex, 0)); + table.setFloat(columnKeys[i], rowKey, (float) value2, false); + assertEquals(value2, table.getFloat(columnKeys[i], rowKey)); break; case DOUBLE: - table.setDouble(columnIndex, 0, (double) value2, false); - assertEquals(value2, table.getDouble(columnIndex, 0)); + table.setDouble(columnKeys[i], rowKey, (double) value2, false); + assertEquals(value2, table.getDouble(columnKeys[i], rowKey)); break; case OBJECT: - table.setLink(columnIndex, 0, (long) value2, false); - assertEquals(value2, table.getLink(columnIndex, 0)); + table.setLink(columnKeys[i], 0, (long) value2, false); + assertEquals(value2, table.getLink(columnKeys[i], rowKey)); break; default: throw new RuntimeException("unexpected field type: " + type); @@ -1018,36 +719,34 @@ public void defaultValue_overwrittenByNonDefault() { sharedRealm.commitTransaction(); // Checks if the value was overwritten. - it = columnInfoList.listIterator(); - for (int columnIndex = 0; columnIndex < columnInfoList.size(); columnIndex++) { - Pair columnInfo = it.next(); - final RealmFieldType type = columnInfo.first; - final Object value2 = ((Object[]) columnInfo.second)[1]; + for (int i = 0; i < columnKeys.length; i++) { + final RealmFieldType type = types[i]; + final Object value2 = ((Object[]) datas[i])[1]; switch (type) { case STRING: - assertEquals(value2, table.getString(columnIndex, 0)); + assertEquals(value2, table.getString(columnKeys[i], rowKey)); break; case INTEGER: - assertEquals(value2, table.getLong(columnIndex, 0)); + assertEquals(value2, table.getLong(columnKeys[i], rowKey)); break; case BOOLEAN: - assertEquals(value2, table.getBoolean(columnIndex, 0)); + assertEquals(value2, table.getBoolean(columnKeys[i], rowKey)); break; case BINARY: - assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnIndex, 0))); + assertTrue(Arrays.equals((byte[]) value2, table.getBinaryByteArray(columnKeys[i], rowKey))); break; case DATE: - assertEquals(value2, table.getDate(columnIndex, 0)); + assertEquals(value2, table.getDate(columnKeys[i], rowKey)); break; case FLOAT: - assertEquals(value2, table.getFloat(columnIndex, 0)); + assertEquals(value2, table.getFloat(columnKeys[i], rowKey)); break; case DOUBLE: - assertEquals(value2, table.getDouble(columnIndex, 0)); + assertEquals(value2, table.getDouble(columnKeys[i], rowKey)); break; case OBJECT: - assertEquals(value2, table.getLink(columnIndex, 0)); + assertEquals(value2, table.getLink(columnKeys[i], rowKey)); break; default: throw new RuntimeException("unexpected field type: " + type); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java index 030a8f9667..98f35e85dc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java @@ -78,7 +78,7 @@ public void setUp() { OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config) .autoUpdateNotification(true) .schemaInfo(schemaInfo); - sharedRealm = OsSharedRealm.getInstance(configBuilder); + sharedRealm = OsSharedRealm.getInstance(configBuilder, OsSharedRealm.VersionID.LIVE); sharedRealm.beginTransaction(); Table table = sharedRealm.getTable(Table.getTableNameForClass("TestModel")); row = table.getUncheckedRow(OsObject.createRow(table)); @@ -154,8 +154,8 @@ private void add_insert_set_values_long(OsList osList) { @Test public void add_insert_set_get_Long() { - long index = testObjectSchemaInfo.getProperty("longList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("longList").getColumnKey(); + OsList osList = new OsList(row, columnKey); add_insert_set_values_long(osList); addNull_insertNull_setNull_nullableList(osList); @@ -163,8 +163,8 @@ public void add_insert_set_get_Long() { @Test public void add_insert_get_set_required_Long() { - long index = testObjectSchemaInfo.getProperty("requiredLongList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("requiredLongList").getColumnKey(); + OsList osList = new OsList(row, columnKey); add_insert_set_values_long(osList); addNull_insertNull_setNull_requiredList(osList); @@ -189,8 +189,8 @@ private void add_insert_set_values_double(OsList osList) { @Test public void add_insert_set_get_Double() { - long index = testObjectSchemaInfo.getProperty("doubleList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("doubleList").getColumnKey(); + OsList osList = new OsList(row, columnKey); add_insert_set_values_double(osList); addNull_insertNull_setNull_nullableList(osList); @@ -198,8 +198,8 @@ public void add_insert_set_get_Double() { @Test public void add_insert_set_get_required_Double() { - long index = testObjectSchemaInfo.getProperty("requiredDoubleList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("requiredDoubleList").getColumnKey(); + OsList osList = new OsList(row, columnKey); add_insert_set_values_double(osList); addNull_insertNull_setNull_requiredList(osList); @@ -224,8 +224,8 @@ private void add_insert_set_values_float(OsList osList) { @Test public void add_insert_get_Float() { - long index = testObjectSchemaInfo.getProperty("floatList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("floatList").getColumnKey(); + OsList osList = new OsList(row, columnKey); add_insert_set_values_float(osList); addNull_insertNull_setNull_nullableList(osList); @@ -233,8 +233,8 @@ public void add_insert_get_Float() { @Test public void add_insert_get_required_Float() { - long index = testObjectSchemaInfo.getProperty("requiredFloatList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("requiredFloatList").getColumnKey(); + OsList osList = new OsList(row, columnKey); add_insert_set_values_float(osList); addNull_insertNull_setNull_requiredList(osList); @@ -259,8 +259,8 @@ private void add_insert_set_values_boolean(OsList osList) { @Test public void add_insert_set_get_Boolean() { - long index = testObjectSchemaInfo.getProperty("booleanList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("booleanList").getColumnKey(); + OsList osList = new OsList(row, columnKey); add_insert_set_values_boolean(osList); addNull_insertNull_setNull_nullableList(osList); @@ -268,8 +268,8 @@ public void add_insert_set_get_Boolean() { @Test public void add_insert_set_get_required_Boolean() { - long index = testObjectSchemaInfo.getProperty("requiredBooleanList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("requiredBooleanList").getColumnKey(); + OsList osList = new OsList(row, columnKey); add_insert_set_values_boolean(osList); addNull_insertNull_setNull_requiredList(osList); @@ -277,8 +277,8 @@ public void add_insert_set_get_required_Boolean() { @Test public void add_insert_set_get_Date() { - long index = testObjectSchemaInfo.getProperty("dateList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("dateList").getColumnKey(); + OsList osList = new OsList(row, columnKey); Date date42 = new Date(42); Date date24 = new Date(24); @@ -320,8 +320,8 @@ public void add_insert_set_get_Date() { @Test public void add_insert_set_null_required_Date() { - long index = testObjectSchemaInfo.getProperty("requiredDateList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("requiredDateList").getColumnKey(); + OsList osList = new OsList(row, columnKey); addNull_insertNull_setNull_requiredList(osList); @@ -346,8 +346,8 @@ public void add_insert_set_null_required_Date() { @Test public void add_insert_get_String() { - long index = testObjectSchemaInfo.getProperty("stringList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("stringList").getColumnKey(); + OsList osList = new OsList(row, columnKey); osList.addString(null); String value = (String) osList.getValue(0); @@ -386,8 +386,8 @@ public void add_insert_get_String() { @Test public void add_insert_set_null_required_String() { - long index = testObjectSchemaInfo.getProperty("requiredStringList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("requiredStringList").getColumnKey(); + OsList osList = new OsList(row, columnKey); addNull_insertNull_setNull_requiredList(osList); @@ -412,8 +412,8 @@ public void add_insert_set_null_required_String() { @Test public void add_insert_get_Binary() { - long index = testObjectSchemaInfo.getProperty("binaryList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("binaryList").getColumnKey(); + OsList osList = new OsList(row, columnKey); byte[] bytes42 = new byte[1]; bytes42[0] = 42; @@ -458,8 +458,8 @@ public void add_insert_get_Binary() { @Test public void add_insert_set_null_required_Binary() { - long index = testObjectSchemaInfo.getProperty("requiredBinaryList").getColumnIndex(); - OsList osList = new OsList(row, index); + long columnKey = testObjectSchemaInfo.getProperty("requiredBinaryList").getColumnKey(); + OsList osList = new OsList(row, columnKey); addNull_insertNull_setNull_requiredList(osList); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java index ad2fdd9539..be314b5baa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java @@ -59,7 +59,7 @@ public void callWithLock() { RealmConfiguration config = configFactory.createConfiguration(); // Return false if there are opened OsSharedRealm instance - OsSharedRealm sharedRealm = OsSharedRealm.getInstance(config); + OsSharedRealm sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); assertFalse(OsObjectStore.callWithLock(config, new Runnable() { @Override public void run() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java index 31eb7ec756..d04bd2c8cc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java @@ -62,6 +62,14 @@ public class OsResultsTests { private OsSharedRealm sharedRealm; private Table table; + private long colKey0 = -1; + private long colKey1 = -1; + private long colKey2 = -1; + private long rowKey0 = -1; + private long rowKey1 = -1; + private long rowKey2 = -1; + private long rowKey3 = -1; + @Before public void setUp() { sharedRealm = getSharedRealm(); @@ -86,7 +94,7 @@ private OsSharedRealm getSharedRealmForLooper() { private OsSharedRealm getSharedRealm(RealmConfiguration config) { OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config) .autoUpdateNotification(true); - OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configBuilder); + OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configBuilder, OsSharedRealm.VersionID.LIVE); sharedRealm.beginTransaction(); OsObjectStore.setSchemaVersion(sharedRealm, OsObjectStore.SCHEMA_NOT_VERSIONED); sharedRealm.commitTransaction(); @@ -101,31 +109,31 @@ private void populateData(OsSharedRealm sharedRealm) { sharedRealm.beginTransaction(); table = sharedRealm.createTable(Table.getTableNameForClass("test_table")); // Specify the column types and names - long columnIdx = table.addColumn(RealmFieldType.STRING, "firstName"); - table.addSearchIndex(columnIdx); - table.addColumn(RealmFieldType.STRING, "lastName"); - table.addColumn(RealmFieldType.INTEGER, "age"); + colKey0 = table.addColumn(RealmFieldType.STRING, "firstName"); + table.addSearchIndex(colKey0); + colKey1 = table.addColumn(RealmFieldType.STRING, "lastName"); + colKey2 = table.addColumn(RealmFieldType.INTEGER, "age"); // Add data to the table - long row = OsObject.createRow(table); - table.setString(0, row, "John", false); - table.setString(1, row, "Lee", false); - table.setLong(2, row, 4, false); - - row = OsObject.createRow(table); - table.setString(0, row, "John", false); - table.setString(1, row, "Anderson", false); - table.setLong(2, row, 3, false); - - row = OsObject.createRow(table); - table.setString(0, row, "Erik", false); - table.setString(1, row, "Lee", false); - table.setLong(2, row, 1, false); - - row = OsObject.createRow(table); - table.setString(0, row, "Henry", false); - table.setString(1, row, "Anderson", false); - table.setLong(2, row, 1, false); + rowKey0 = OsObject.createRow(table); + table.setString(colKey0, rowKey0, "John", false); + table.setString(colKey1, rowKey0, "Lee", false); + table.setLong(colKey2, rowKey0, 4, false); + + rowKey1 = OsObject.createRow(table); + table.setString(colKey0, rowKey1, "John", false); + table.setString(colKey1, rowKey1, "Anderson", false); + table.setLong(colKey2, rowKey1, 3, false); + + rowKey2 = OsObject.createRow(table); + table.setString(colKey0, rowKey2, "Erik", false); + table.setString(colKey1, rowKey2, "Lee", false); + table.setLong(colKey2, rowKey2, 1, false); + + rowKey3 = OsObject.createRow(table); + table.setString(colKey0, rowKey3, "Henry", false); + table.setString(colKey1, rowKey3, "Anderson", false); + table.setLong(colKey2, rowKey3, 1, false); sharedRealm.commitTransaction(); } @@ -158,9 +166,9 @@ public void constructor_withDistinct() { OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where(), queryDescriptors); assertEquals(3, osResults.size()); - assertEquals("John", osResults.getUncheckedRow(0).getString(0)); - assertEquals("Erik", osResults.getUncheckedRow(1).getString(0)); - assertEquals("Henry", osResults.getUncheckedRow(2).getString(0)); + assertEquals("John", osResults.getUncheckedRow(0).getString(colKey0)); + assertEquals("Erik", osResults.getUncheckedRow(1).getString(colKey0)); + assertEquals("Henry", osResults.getUncheckedRow(2).getString(colKey0)); } @@ -190,8 +198,8 @@ public void size() { @Test public void where() { OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where()); - OsResults osResults2 = OsResults.createFromQuery(sharedRealm, osResults.where().equalTo(new long[] {0}, oneNullTable, "John")); - OsResults osResults3 = OsResults.createFromQuery(sharedRealm, osResults2.where().equalTo(new long[] {1}, oneNullTable, "Anderson")); + OsResults osResults2 = OsResults.createFromQuery(sharedRealm, osResults.where().equalTo(new long[] {colKey0}, oneNullTable, "John")); + OsResults osResults3 = OsResults.createFromQuery(sharedRealm, osResults2.where().equalTo(new long[] {colKey1}, oneNullTable, "Anderson")); // A new native Results should be created. assertTrue(osResults.getNativePtr() != osResults2.getNativePtr()); @@ -204,8 +212,8 @@ public void where() { @Test public void sort() { - OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where().greaterThan(new long[] {2}, oneNullTable, 1)); - QueryDescriptor sortDescriptor = QueryDescriptor.getTestInstance(table, new long[] {2}); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where().greaterThan(new long[] {colKey2}, oneNullTable, 1)); + QueryDescriptor sortDescriptor = QueryDescriptor.getTestInstance(table, new long[] {colKey2}); OsResults osResults2 = osResults.sort(sortDescriptor); @@ -214,8 +222,8 @@ public void sort() { assertEquals(2, osResults.size()); assertEquals(2, osResults2.size()); - assertEquals(3, osResults2.getUncheckedRow(0).getLong(2)); - assertEquals(4, osResults2.getUncheckedRow(1).getLong(2)); + assertEquals(3, osResults2.getUncheckedRow(0).getLong(colKey2)); + assertEquals(4, osResults2.getUncheckedRow(1).getLong(colKey2)); } @Test @@ -238,18 +246,18 @@ public void contains() { @Test public void indexOf() { DescriptorOrdering queryDescriptors = new DescriptorOrdering(); - queryDescriptors.appendSort(QueryDescriptor.getTestInstance(table, new long[] {2})); + queryDescriptors.appendSort(QueryDescriptor.getTestInstance(table, new long[] {colKey2})); OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where(), queryDescriptors); - UncheckedRow row = table.getUncheckedRow(0); + UncheckedRow row = table.getUncheckedRow(rowKey0); assertEquals(3, osResults.indexOf(row)); } @Test public void distinct() { - OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where().lessThan(new long[] {2}, oneNullTable, 4)); + OsResults osResults = OsResults.createFromQuery(sharedRealm, table.where().lessThan(new long[] {colKey2}, oneNullTable, 4)); - QueryDescriptor distinctDescriptor = QueryDescriptor.getTestInstance(table, new long[] {2}); + QueryDescriptor distinctDescriptor = QueryDescriptor.getTestInstance(table, new long[] {colKey2}); OsResults osResults2 = osResults.distinct(distinctDescriptor); // A new native Results should be created. @@ -257,8 +265,8 @@ public void distinct() { assertEquals(3, osResults.size()); assertEquals(2, osResults2.size()); - assertEquals(3, osResults2.getUncheckedRow(0).getLong(2)); - assertEquals(1, osResults2.getUncheckedRow(1).getLong(2)); + assertEquals(3, osResults2.getUncheckedRow(0).getLong(colKey2)); + assertEquals(1, osResults2.getUncheckedRow(1).getLong(colKey2)); } // 1. Create a results and add listener. diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java index 8aee472032..5d5b7aa180 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java @@ -46,7 +46,7 @@ public class OsSharedRealmTests { @Before public void setUp() { config = configFactory.createConfiguration(); - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); } @After @@ -57,12 +57,9 @@ public void tearDown() { } @Test - public void getVersionID() { - OsSharedRealm.VersionID versionID1 = sharedRealm.getVersionID(); - sharedRealm.beginTransaction(); - sharedRealm.commitTransaction(); - OsSharedRealm.VersionID versionID2 = sharedRealm.getVersionID(); - assertFalse(versionID1.equals(versionID2)); + public void getVersionID_without_read_or_write_transaction_throws() { + thrown.expectMessage("Cannot get versionId, this could be related to a non existing read/write transaction"); + sharedRealm.getVersionID(); } @Test @@ -138,7 +135,7 @@ public void renameTable_tableNotExist() { private void changeSchemaByAnotherRealm() { - OsSharedRealm sharedRealm = OsSharedRealm.getInstance(config); + OsSharedRealm sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); sharedRealm.beginTransaction(); sharedRealm.createTable("NewTable"); sharedRealm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index 04c431318f..29abbe9a82 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -25,10 +25,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - import io.realm.DynamicRealm; import io.realm.DynamicRealmObject; import io.realm.FieldAttribute; @@ -38,9 +34,7 @@ import io.realm.RealmSchema; import io.realm.rule.TestRealmConfigurationFactory; -import static junit.framework.Assert.assertFalse; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -49,14 +43,12 @@ public class PrimaryKeyTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - private android.content.Context context; private RealmConfiguration config; private OsSharedRealm sharedRealm; @Before - public void setUp() throws Exception { + public void setUp() { config = configFactory.createConfiguration(); - context = InstrumentationRegistry.getInstrumentation().getContext(); } @After @@ -67,7 +59,7 @@ public void tearDown() { } private Table getTableWithStringPrimaryKey() { - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); sharedRealm.beginTransaction(); OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table Table t = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); @@ -78,7 +70,7 @@ private Table getTableWithStringPrimaryKey() { } private Table getTableWithIntegerPrimaryKey() { - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); sharedRealm.beginTransaction(); OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table Table t = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); @@ -158,7 +150,7 @@ public void addEmptyRowWithPrimaryKeyString() { Table t = getTableWithStringPrimaryKey(); UncheckedRow row = OsObject.createWithPrimaryKey(t, "Foo"); assertEquals(1, t.size()); - assertEquals("Foo", row.getString(0)); + assertEquals("Foo", row.getString(row.getColumnKey("colName"))); sharedRealm.cancelTransaction(); } @@ -167,95 +159,7 @@ public void addEmptyRowWithPrimaryKeyLong() { Table t = getTableWithIntegerPrimaryKey(); UncheckedRow row = OsObject.createWithPrimaryKey(t, 42); assertEquals(1, t.size()); - assertEquals(42L, row.getLong(0)); + assertEquals(42L, row.getLong(row.getColumnKey("colName"))); sharedRealm.cancelTransaction(); } - - @Test - public void migratePrimaryKeyTableIfNeeded_first() throws IOException { - configFactory.copyRealmFromAssets(context, "080_annotationtypes.realm", "default.realm"); - sharedRealm = OsSharedRealm.getInstance(config); - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); - Table t = sharedRealm.getTable("class_AnnotationTypes"); - assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); - assertEquals(RealmFieldType.STRING, sharedRealm.getTable("pk").getColumnType(0)); - } - - @Test - public void migratePrimaryKeyTableIfNeeded_second() throws IOException { - configFactory.copyRealmFromAssets(context, "0841_annotationtypes.realm", "default.realm"); - sharedRealm = OsSharedRealm.getInstance(config); - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); - Table t = sharedRealm.getTable("class_AnnotationTypes"); - assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); - assertEquals("AnnotationTypes", sharedRealm.getTable("pk").getString(0, 0)); - } - - // See https://github.com/realm/realm-java/issues/1775 - // Before 0.84.2, pk table added prefix "class_" to every class's name. - // After 0.84.2, the pk table should be migrated automatically to remove the "class_". - // In 0.84.2, the class names in pk table has been renamed to some incorrect names like "Thclass", "Mclass", - // "NClass", "Meclass" and etc.. - // The 0841_pk_migration.realm is made to produce the issue. - @Test - public void migratePrimaryKeyTableIfNeeded_primaryKeyTableMigratedWithRightName() throws IOException { - List tableNames = Arrays.asList( - "ChatList", "Drafts", "Member", "Message", "Notifs", "NotifyLink", "PopularPost", - "Post", "Tags", "Threads", "User"); - - configFactory.copyRealmFromAssets(context, "0841_pk_migration.realm", "default.realm"); - sharedRealm = OsSharedRealm.getInstance(config); - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); - - Table table = sharedRealm.getTable("pk"); - for (int i = 0; i < table.size(); i++) { - UncheckedRow row = table.getUncheckedRow(i); - // io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX 0LL - assertTrue(tableNames.contains(row.getString(0))); - } - } - - // PK table's column 'pk_table' needs search index in order to use set_string_unique. - // See https://github.com/realm/realm-java/pull/3488 - @Test - public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { - sharedRealm = OsSharedRealm.getInstance(config); - sharedRealm.beginTransaction(); - OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table - Table table = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); - long column = table.addColumn(RealmFieldType.INTEGER, "PKColumn"); - table.addSearchIndex(column); - OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable", "PKColumn"); - sharedRealm.commitTransaction(); - - assertEquals("PKColumn", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "TestTable")); - // Now we have a pk table with search index. - - sharedRealm.beginTransaction(); - Table pkTable = sharedRealm.getTable("pk"); - long classColumn = pkTable.getColumnIndex("pk_table"); - pkTable.removeSearchIndex(classColumn); - - // Tries to add a pk for another table. - Table table2 = sharedRealm.createTable(Table.getTableNameForClass("TestTable2")); - long column2 = table2.addColumn(RealmFieldType.INTEGER, "PKColumn"); - table2.addSearchIndex(column2); - try { - OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable2", "PKColumn"); - } catch (IllegalStateException ignored) { - // Column has no search index. - } - sharedRealm.commitTransaction(); - - assertFalse(pkTable.hasSearchIndex(classColumn)); - - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); - assertTrue(pkTable.hasSearchIndex(classColumn)); - - sharedRealm.beginTransaction(); - // Now it works. - table2.addSearchIndex(column2); - OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable2", "PKColumn"); - sharedRealm.commitTransaction(); - } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java index 2a15fee6b8..5e22340d79 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java @@ -54,7 +54,7 @@ public class QueryDescriptorTests { @Before public void setUp() { RealmConfiguration config = configFactory.createConfiguration(); - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); sharedRealm.beginTransaction(); table = sharedRealm.createTable("test_table"); } @@ -66,18 +66,26 @@ public void tearDown() { @Test public void getInstanceForDistinct() { - for (RealmFieldType type : QueryDescriptor.DISTINCT_VALID_FIELD_TYPES) { - long column = table.addColumn(type, type.name()); - table.addSearchIndex(column); - } - - long i = 0; - for (RealmFieldType type : QueryDescriptor.DISTINCT_VALID_FIELD_TYPES) { - QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForDistinct(null, table, type.name()); - assertEquals(1, sortDescriptor.getColumnIndices()[0].length); - assertEquals(i, sortDescriptor.getColumnIndices()[0][0]); + RealmFieldType[] types = new RealmFieldType[]{RealmFieldType.BOOLEAN, + RealmFieldType.INTEGER, + RealmFieldType.STRING, + RealmFieldType.DATE}; + long columnKey1 = table.addColumn(RealmFieldType.BOOLEAN, RealmFieldType.BOOLEAN.name()); + long columnKey2 = table.addColumn(RealmFieldType.INTEGER, RealmFieldType.INTEGER.name()); + long columnKey3 = table.addColumn(RealmFieldType.STRING, RealmFieldType.STRING.name()); + long columnKey4 = table.addColumn(RealmFieldType.DATE, RealmFieldType.DATE.name()); + + table.addSearchIndex(columnKey1); + table.addSearchIndex(columnKey2); + table.addSearchIndex(columnKey3); + table.addSearchIndex(columnKey4); + long[] columnsKey = new long[]{columnKey1, columnKey2, columnKey3, columnKey4}; + + for (int i = 0; i < columnsKey.length; i++) { + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForDistinct(null, table, types[i].name()); + assertEquals(1, sortDescriptor.getColumnKeys()[0].length); + assertEquals(columnsKey[i], sortDescriptor.getColumnKeys()[0][0]); assertNull(sortDescriptor.getAscendings()); - i++; } } @@ -112,14 +120,14 @@ public void getInstanceForDistinct_multipleFields() { long intColumn = table.addColumn(intType, intType.name()); table.addSearchIndex(intColumn); - QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForDistinct(null, table, new String[] { + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForDistinct(null, table, new String[]{ stringType.name(), intType.name()}); - assertEquals(2, sortDescriptor.getColumnIndices().length); + assertEquals(2, sortDescriptor.getColumnKeys().length); assertNull(sortDescriptor.getAscendings()); - assertEquals(1, sortDescriptor.getColumnIndices()[0].length); - assertEquals(stringColumn, sortDescriptor.getColumnIndices()[0][0]); - assertEquals(1, sortDescriptor.getColumnIndices()[1].length); - assertEquals(intColumn, sortDescriptor.getColumnIndices()[1][0]); + assertEquals(1, sortDescriptor.getColumnKeys()[0].length); + assertEquals(stringColumn, sortDescriptor.getColumnKeys()[0][0]); + assertEquals(1, sortDescriptor.getColumnKeys()[1].length); + assertEquals(intColumn, sortDescriptor.getColumnKeys()[1][0]); } @Test @@ -138,38 +146,48 @@ public void getInstanceForDistinct_shouldThrowOnInvalidField() { @Test public void getInstanceForSort() { - for (RealmFieldType type : QueryDescriptor.SORT_VALID_FIELD_TYPES) { - table.addColumn(type, type.name()); - } - - long i = 0; - for (RealmFieldType type : QueryDescriptor.SORT_VALID_FIELD_TYPES) { - QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(null, table, type.name(), Sort.DESCENDING); - assertEquals(1, sortDescriptor.getColumnIndices()[0].length); - assertEquals(i, sortDescriptor.getColumnIndices()[0][0]); + RealmFieldType[] types = new RealmFieldType[]{RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, + RealmFieldType.STRING, RealmFieldType.DATE}; + long columnKey1 = table.addColumn(RealmFieldType.BOOLEAN, RealmFieldType.BOOLEAN.name()); + long columnKey2 = table.addColumn(RealmFieldType.INTEGER, RealmFieldType.INTEGER.name()); + long columnKey3 = table.addColumn(RealmFieldType.FLOAT, RealmFieldType.FLOAT.name()); + long columnKey4 = table.addColumn(RealmFieldType.DOUBLE, RealmFieldType.DOUBLE.name()); + long columnKey5 = table.addColumn(RealmFieldType.STRING, RealmFieldType.STRING.name()); + long columnKey6 = table.addColumn(RealmFieldType.DATE, RealmFieldType.DATE.name()); + + long[] columnsKey = new long[]{columnKey1, columnKey2, columnKey3, columnKey4, columnKey5, columnKey6}; + + for (int i = 0; i < columnsKey.length; i++) { + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(null, table, types[i].name(), Sort.DESCENDING); + assertEquals(1, sortDescriptor.getColumnKeys()[0].length); + assertEquals(columnsKey[i], sortDescriptor.getColumnKeys()[0][0]); assertFalse(sortDescriptor.getAscendings()[0]); - i++; } } @Test public void getInstanceForSort_linkField() { - for (RealmFieldType type : QueryDescriptor.DISTINCT_VALID_FIELD_TYPES) { - long column = table.addColumn(type, type.name()); - table.addSearchIndex(column); - } + RealmFieldType[] types = new RealmFieldType[]{RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE}; + long columnKey1 = table.addColumn(RealmFieldType.BOOLEAN, RealmFieldType.BOOLEAN.name()); + long columnKey2 = table.addColumn(RealmFieldType.INTEGER, RealmFieldType.INTEGER.name()); + long columnKey3 = table.addColumn(RealmFieldType.STRING, RealmFieldType.STRING.name()); + long columnKey4 = table.addColumn(RealmFieldType.DATE, RealmFieldType.DATE.name()); + table.addSearchIndex(columnKey1); + table.addSearchIndex(columnKey2); + table.addSearchIndex(columnKey3); + table.addSearchIndex(columnKey4); + long[] columnsKey = new long[]{columnKey1, columnKey2, columnKey3, columnKey4}; + RealmFieldType objectType = RealmFieldType.OBJECT; long columnLink = table.addColumnLink(objectType, objectType.name(), table); - long i = 0; - for (RealmFieldType type : QueryDescriptor.DISTINCT_VALID_FIELD_TYPES) { + for (int j = 0; j < columnsKey.length; j++) { QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(null, table, - String.format("%s.%s", objectType.name(), type.name()), Sort.ASCENDING); - assertEquals(2, sortDescriptor.getColumnIndices()[0].length); - assertEquals(columnLink, sortDescriptor.getColumnIndices()[0][0]); - assertEquals(i, sortDescriptor.getColumnIndices()[0][1]); + String.format("%s.%s", objectType.name(), types[j].name()), Sort.ASCENDING); + assertEquals(2, sortDescriptor.getColumnKeys()[0].length); + assertEquals(columnLink, sortDescriptor.getColumnKeys()[0][0]); + assertEquals(columnsKey[j], sortDescriptor.getColumnKeys()[0][1]); assertTrue(sortDescriptor.getAscendings()[0]); - i++; } } @@ -180,18 +198,18 @@ public void getInstanceForSort_multipleFields() { RealmFieldType intType = RealmFieldType.INTEGER; long intColumn = table.addColumn(intType, intType.name()); - QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(null, table, new String[] { - stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING, Sort.DESCENDING}); + QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(null, table, new String[]{ + stringType.name(), intType.name()}, new Sort[]{Sort.ASCENDING, Sort.DESCENDING}); assertEquals(2, sortDescriptor.getAscendings().length); - assertEquals(2, sortDescriptor.getColumnIndices().length); + assertEquals(2, sortDescriptor.getColumnKeys().length); - assertEquals(1, sortDescriptor.getColumnIndices()[0].length); - assertEquals(stringColumn, sortDescriptor.getColumnIndices()[0][0]); + assertEquals(1, sortDescriptor.getColumnKeys()[0].length); + assertEquals(stringColumn, sortDescriptor.getColumnKeys()[0][0]); assertTrue(sortDescriptor.getAscendings()[0]); - assertEquals(1, sortDescriptor.getColumnIndices()[1].length); - assertEquals(intColumn, sortDescriptor.getColumnIndices()[1][0]); + assertEquals(1, sortDescriptor.getColumnKeys()[1].length); + assertEquals(intColumn, sortDescriptor.getColumnKeys()[1][0]); assertFalse(sortDescriptor.getAscendings()[1]); } @@ -206,7 +224,7 @@ public void getInstanceForSort_numOfFeildsAndSortOrdersNotMatch() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Number of fields and sort orders do not match."); QueryDescriptor.getInstanceForSort(null, table, - new String[] {stringType.name(), intType.name()}, new Sort[] {Sort.ASCENDING}); + new String[]{stringType.name(), intType.name()}, new Sort[]{Sort.ASCENDING}); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index 9c5cfcf4eb..bdd33a580b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -74,7 +74,7 @@ public void tearDown() { private OsSharedRealm getSharedRealm(RealmConfiguration config) { OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config) .autoUpdateNotification(true); - return OsSharedRealm.getInstance(configBuilder); + return OsSharedRealm.getInstance(configBuilder, OsSharedRealm.VersionID.LIVE); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java index 2d8ea55467..f7e2e5c75d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java @@ -31,7 +31,6 @@ import io.realm.TestHelper; import io.realm.rule.TestRealmConfigurationFactory; - import static org.junit.Assert.assertEquals; @@ -46,11 +45,14 @@ public class TableIndexAndDistinctTest { private OsSharedRealm sharedRealm; private Table table; + private long colKey1; + private long colKey2; + @Before public void setUp() throws Exception { Realm.init(InstrumentationRegistry.getInstrumentation().getContext()); config = configFactory.createConfiguration(); - sharedRealm = OsSharedRealm.getInstance(config); + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); sharedRealm.beginTransaction(); } @@ -70,16 +72,16 @@ private void init() { table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table table) { - table.addColumn(RealmFieldType.INTEGER, "number"); - table.addColumn(RealmFieldType.STRING, "name"); - - TestHelper.addRowWithValues(table, 0, "A"); - TestHelper.addRowWithValues(table, 1, "B"); - TestHelper.addRowWithValues(table, 2, "C"); - TestHelper.addRowWithValues(table, 3, "B"); - TestHelper.addRowWithValues(table, 4, "D"); - TestHelper.addRowWithValues(table, 5, "D"); - TestHelper.addRowWithValues(table, 6, "D"); + colKey1 = table.addColumn(RealmFieldType.INTEGER, "number"); + colKey2 = table.addColumn(RealmFieldType.STRING, "name"); + + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{0, "A"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{1, "B"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{2, "C"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{3, "B"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{4, "D"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{5, "D"}); + TestHelper.addRowWithValues(table, new long[]{colKey1, colKey2}, new Object[]{6, "D"}); } }); @@ -92,27 +94,28 @@ public void execute(Table table) { */ @Test public void shouldTestSettingIndexOnMultipleColumns() { - + long[] columnsKey = new long[5]; // Creates a table only with String type columns Table t = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override public void execute(Table t) { - t.addColumn(RealmFieldType.STRING, "col1"); - t.addColumn(RealmFieldType.STRING, "col2"); - t.addColumn(RealmFieldType.STRING, "col3"); - t.addColumn(RealmFieldType.STRING, "col4"); - t.addColumn(RealmFieldType.STRING, "col5"); - TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); - TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); - TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); - TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); - TestHelper.addRowWithValues(t, "row1", "row2", "row3", "row4", "row5"); + columnsKey[0] = t.addColumn(RealmFieldType.STRING, "col1"); + columnsKey[1] = t.addColumn(RealmFieldType.STRING, "col2"); + columnsKey[2] = t.addColumn(RealmFieldType.STRING, "col3"); + columnsKey[3] = t.addColumn(RealmFieldType.STRING, "col4"); + columnsKey[4] = t.addColumn(RealmFieldType.STRING, "col5"); + + TestHelper.addRowWithValues(t, columnsKey, new Object[]{"row1", "row2", "row3", "row4", "row5"}); + TestHelper.addRowWithValues(t, columnsKey, new Object[]{"row1", "row2", "row3", "row4", "row5"}); + TestHelper.addRowWithValues(t, columnsKey, new Object[]{"row1", "row2", "row3", "row4", "row5"}); + TestHelper.addRowWithValues(t, columnsKey, new Object[]{"row1", "row2", "row3", "row4", "row5"}); + TestHelper.addRowWithValues(t, columnsKey, new Object[]{"row1", "row2", "row3", "row4", "row5"}); } }); - for (long c=0;c all = backupRealm.where(StringOnly.class).findAll(); - assertEquals(1, all.size()); - assertEquals("Hello from ROS 1.X", all.get(0).getChars()); - - // make sure it's read only - try { - backupRealm.beginTransaction(); - fail("Backup Realm should be read-only, we should throw"); - } catch (IllegalStateException ignored) { - } - backupRealm.close(); - - // we can open in dynamic mode - DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); - dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); - RealmResults allDynamic = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); - assertEquals(1, allDynamic.size()); - assertEquals("Hello from ROS 1.X", allDynamic.first().getString(StringOnly.FIELD_CHARS)); - dynamicRealm.close(); - } - - Realm realm = Realm.getInstance(config); - assertTrue(realm.isEmpty()); - realm.close(); - } } diff --git a/realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp b/realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp index 39e5484af4..7cedf00bc2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp @@ -26,7 +26,6 @@ using namespace realm; JNIEXPORT void JNICALL Java_io_realm_ClientResetRequiredError_nativeExecuteClientReset(JNIEnv* env, jobject, jstring localRealmPath) { - TR_ENTER() try { JStringAccessor local_realm_path(env, localRealmPath); if (!SyncManager::shared().immediately_run_file_actions(std::string(local_realm_path))) { diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index a3e8b7d57d..bcc446bb25 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -47,7 +47,6 @@ static SyncUserIdentifier create_sync_user_identifier(JNIEnv* env, jstring j_use JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetCurrentUser(JNIEnv* env, jclass) { - TR_ENTER() try { auto user = SyncManager::shared().get_current_user(); return to_user_string_or_null(env, user); @@ -59,7 +58,6 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetCurrentUser( JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetUser(JNIEnv* env, jclass, jstring j_user_id, jstring j_auth_url) { - TR_ENTER() try { auto user = SyncManager::shared().get_existing_logged_in_user( create_sync_user_identifier(env, j_user_id, j_auth_url)); @@ -73,7 +71,6 @@ JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser jstring j_user_id, jstring json_token, jstring j_auth_url) { - TR_ENTER() try { JStringAccessor user_json_token(env, json_token); // throws SyncManager::shared().get_user(create_sync_user_identifier(env, j_user_id, j_auth_url), user_json_token); @@ -84,7 +81,6 @@ JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeLogoutUser(JNIEnv* env, jclass, jstring j_user_id, jstring j_auth_url) { - TR_ENTER() try { auto user = SyncManager::shared().get_existing_logged_in_user( create_sync_user_identifier(env, j_user_id, j_auth_url)); @@ -98,7 +94,6 @@ JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeLogoutUser(JNIEnv* JNIEXPORT jboolean JNICALL Java_io_realm_RealmFileUserStore_nativeIsActive(JNIEnv* env, jclass, jstring j_user_id, jstring j_auth_url) { - TR_ENTER() try { auto user = SyncManager::shared().get_existing_logged_in_user( create_sync_user_identifier(env, j_user_id, j_auth_url)); @@ -112,7 +107,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_RealmFileUserStore_nativeIsActive(JNIEn JNIEXPORT jobjectArray JNICALL Java_io_realm_RealmFileUserStore_nativeGetAllUsers(JNIEnv* env, jclass) { - TR_ENTER() auto all_users = SyncManager::shared().all_logged_in_users(); if (!all_users.empty()) { size_t len = all_users.size(); diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp index 67faf4e526..267ccff02e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp @@ -29,7 +29,6 @@ using namespace realm; JNIEXPORT jstring JNICALL Java_io_realm_RealmQuery_nativeSerializeQuery(JNIEnv* env, jclass, jlong table_query_ptr, jlong descriptor_ptr) { - TR_ENTER() try { auto query = reinterpret_cast(table_query_ptr); auto descriptor = reinterpret_cast(descriptor_ptr); @@ -49,7 +48,6 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmQuery_nativeSerializeQuery(JNIEnv* JNIEXPORT jlong JNICALL Java_io_realm_RealmQuery_nativeSubscribe(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_name, jlong table_query_ptr, jlong descriptor_ptr, REALM_UNUSED jlong time_to_live_ms, REALM_UNUSED jboolean update) { - TR_ENTER() try { auto realm = *reinterpret_cast(shared_realm_ptr); auto name = util::Optional(JStringAccessor(env, j_name)); @@ -57,8 +55,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmQuery_nativeSubscribe(JNIEnv* env, jc auto descriptor = reinterpret_cast(descriptor_ptr); Results r(realm, *query, *descriptor); #if REALM_ENABLE_SYNC - RowExpr row = partial_sync::subscribe_blocking(r, name, util::Optional(time_to_live_ms), update); - return to_jlong_or_not_found(row.get_index()); + Obj obj = partial_sync::subscribe_blocking(r, name, util::Optional(time_to_live_ms), update); + return to_jlong_or_not_found(obj.get_key()); #endif } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index 273e25e667..80940e4885 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -16,8 +16,6 @@ #include "io_realm_SyncManager.h" -#include - #include #include #include @@ -86,7 +84,6 @@ struct AndroidSyncLoggerFactory : public realm::SyncLoggerFactory { JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass) { - TR_ENTER() try { SyncManager::shared().reset_for_testing(); } @@ -95,7 +92,6 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNIEnv* env, jclass, jstring j_sync_base_dir, jstring j_user_agent_info) { - TR_ENTER() try { JStringAccessor base_file_path(env, j_sync_base_dir); // throws JStringAccessor user_agent_info(env, j_user_agent_info); // throws @@ -115,7 +111,6 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeSimulateSyncError(JNIEnv* jint err_code, jstring err_message, jboolean is_fatal) { - TR_ENTER() try { JStringAccessor path(env, local_realm_path); JStringAccessor message(env, err_message); @@ -133,7 +128,6 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeSimulateSyncError(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReconnect(JNIEnv* env, jclass) { - TR_ENTER() try { SyncManager::shared().reconnect(); } diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 5276742aca..06ef7cca18 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -63,7 +63,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JN jstring j_access_token, jstring j_sync_realm_url) { - TR_ENTER() try { JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); @@ -150,7 +149,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple jint callback_id, jstring j_local_realm_path) { - TR_ENTER() try { JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); @@ -184,7 +182,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompleti jint callback_id, jstring j_local_realm_path) { - TR_ENTER() try { JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); @@ -216,7 +213,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompleti JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetState(JNIEnv* env, jclass, jstring j_local_realm_path) { - TR_ENTER() try { JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); @@ -240,7 +236,6 @@ JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetState(JNIEnv* env, jc JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetConnectionState(JNIEnv* env, jclass, jstring j_local_realm_path) { - TR_ENTER() try { JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); @@ -328,7 +323,6 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveConnectionListener( JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStart(JNIEnv* env, jclass, jstring j_local_realm_path) { - TR_ENTER() try { JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); @@ -346,7 +340,6 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStart(JNIEnv* env, jclass JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStop(JNIEnv* env, jclass, jstring j_local_realm_path) { - TR_ENTER() try { JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); @@ -359,7 +352,6 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStop(JNIEnv* env, jclass, JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeSetUrlPrefix(JNIEnv* env, jclass, jstring j_local_realm_path, jstring j_url_prefix) { - TR_ENTER() try { JStringAccessor local_realm_path(env, j_local_realm_path); auto session = SyncManager::shared().get_existing_session(local_realm_path); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp index 988e938528..34bf82c550 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp @@ -24,230 +24,214 @@ using namespace realm; JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnCount(JNIEnv* env, jobject obj, jlong nativeRowPtr) { - if (!ROW(nativeRowPtr)->is_attached()) { + if (!OBJ(nativeRowPtr)->is_valid()) { return 0; } return Java_io_realm_internal_UncheckedRow_nativeGetColumnCount(env, obj, nativeRowPtr); } -JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnName(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex) -{ - if (!ROW_AND_COL_INDEX_VALID(env, ROW(nativeRowPtr), columnIndex)) { - return nullptr; - } - - return Java_io_realm_internal_UncheckedRow_nativeGetColumnName(env, obj, nativeRowPtr, columnIndex); -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnIndex(JNIEnv* env, jobject obj, +JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnKey(JNIEnv* env, jobject obj, jlong nativeRowPtr, jstring columnName) { - if (!ROW(nativeRowPtr)->is_attached()) - return 0; + if (!OBJ(nativeRowPtr)->is_valid()) { + ThrowException(env, IllegalArgument, "Object passed is not valid"); + } - jlong ndx = Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex(env, obj, nativeRowPtr, columnName); - if (ndx == to_jlong_or_not_found(realm::not_found)) { + ColKey col_key (Java_io_realm_internal_UncheckedRow_nativeGetColumnKey(env, obj, nativeRowPtr, columnName)); + if (!bool(col_key)) { JStringAccessor column_name(env, columnName); ThrowException(env, IllegalArgument, concat_stringdata("Field not found: ", column_name)); - return 0; - } - else { - return ndx; } + return col_key.value; } JNIEXPORT jint JNICALL Java_io_realm_internal_CheckedRow_nativeGetColumnType(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - if (!ROW_AND_COL_INDEX_VALID(env, ROW(nativeRowPtr), columnIndex)) { - return 0; - } - - return Java_io_realm_internal_UncheckedRow_nativeGetColumnType(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeGetColumnType(env, obj, nativeRowPtr, columnKey); } JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLong(JNIEnv* env, jobject obj, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Int)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Int)) { return 0; } - return Java_io_realm_internal_UncheckedRow_nativeGetLong(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeGetLong(env, obj, nativeRowPtr, columnKey); } JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeGetBoolean(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Bool)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Bool)) { return JNI_FALSE; } - return Java_io_realm_internal_UncheckedRow_nativeGetBoolean(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeGetBoolean(env, obj, nativeRowPtr, columnKey); } JNIEXPORT jfloat JNICALL Java_io_realm_internal_CheckedRow_nativeGetFloat(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Float)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Float)) { return 0; } - return Java_io_realm_internal_UncheckedRow_nativeGetFloat(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeGetFloat(env, obj, nativeRowPtr, columnKey); } JNIEXPORT jdouble JNICALL Java_io_realm_internal_CheckedRow_nativeGetDouble(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Double)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Double)) { return 0; } - return Java_io_realm_internal_UncheckedRow_nativeGetDouble(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeGetDouble(env, obj, nativeRowPtr, columnKey); } JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetTimestamp(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Timestamp)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Timestamp)) { return 0; } - return Java_io_realm_internal_UncheckedRow_nativeGetTimestamp(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeGetTimestamp(env, obj, nativeRowPtr, columnKey); } JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetString(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_String)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_String)) { return nullptr; } - return Java_io_realm_internal_UncheckedRow_nativeGetString(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeGetString(env, obj, nativeRowPtr, columnKey); } JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_CheckedRow_nativeGetByteArray(JNIEnv* env, jobject obj, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Binary)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Binary)) { return nullptr; } - return Java_io_realm_internal_UncheckedRow_nativeGetByteArray(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeGetByteArray(env, obj, nativeRowPtr, columnKey); } JNIEXPORT jlong JNICALL Java_io_realm_internal_CheckedRow_nativeGetLink(JNIEnv* env, jobject obj, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Link)) { return 0; } - return Java_io_realm_internal_UncheckedRow_nativeGetLink(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeGetLink(env, obj, nativeRowPtr, columnKey); } JNIEXPORT jboolean JNICALL Java_io_realm_internal_CheckedRow_nativeIsNullLink(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Link)) { return JNI_FALSE; } - return Java_io_realm_internal_UncheckedRow_nativeIsNullLink(env, obj, nativeRowPtr, columnIndex); + return Java_io_realm_internal_UncheckedRow_nativeIsNullLink(env, obj, nativeRowPtr, columnKey); } JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetLong(JNIEnv* env, jobject obj, jlong nativeRowPtr, - jlong columnIndex, jlong value) + jlong columnKey, jlong value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Int)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Int)) { return; } - Java_io_realm_internal_UncheckedRow_nativeSetLong(env, obj, nativeRowPtr, columnIndex, value); + Java_io_realm_internal_UncheckedRow_nativeSetLong(env, obj, nativeRowPtr, columnKey, value); } JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetBoolean(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex, + jlong nativeRowPtr, jlong columnKey, jboolean value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Bool)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Bool)) { return; } - Java_io_realm_internal_UncheckedRow_nativeSetBoolean(env, obj, nativeRowPtr, columnIndex, value); + Java_io_realm_internal_UncheckedRow_nativeSetBoolean(env, obj, nativeRowPtr, columnKey, value); } JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetFloat(JNIEnv* env, jobject obj, jlong nativeRowPtr, - jlong columnIndex, jfloat value) + jlong columnKey, jfloat value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Float)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Float)) { return; } - Java_io_realm_internal_UncheckedRow_nativeSetFloat(env, obj, nativeRowPtr, columnIndex, value); + Java_io_realm_internal_UncheckedRow_nativeSetFloat(env, obj, nativeRowPtr, columnKey, value); } JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetDouble(JNIEnv* env, jobject obj, jlong nativeRowPtr, - jlong columnIndex, jdouble value) + jlong columnKey, jdouble value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Double)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Double)) { return; } - Java_io_realm_internal_UncheckedRow_nativeSetDouble(env, obj, nativeRowPtr, columnIndex, value); + Java_io_realm_internal_UncheckedRow_nativeSetDouble(env, obj, nativeRowPtr, columnKey, value); } JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetTimestamp(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex, + jlong nativeRowPtr, jlong columnKey, jlong value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Timestamp)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Timestamp)) { return; } - Java_io_realm_internal_UncheckedRow_nativeSetTimestamp(env, obj, nativeRowPtr, columnIndex, value); + Java_io_realm_internal_UncheckedRow_nativeSetTimestamp(env, obj, nativeRowPtr, columnKey, value); } JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetString(JNIEnv* env, jobject obj, jlong nativeRowPtr, - jlong columnIndex, jstring value) + jlong columnKey, jstring value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_String)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_String)) { return; } - Java_io_realm_internal_UncheckedRow_nativeSetString(env, obj, nativeRowPtr, columnIndex, value); + Java_io_realm_internal_UncheckedRow_nativeSetString(env, obj, nativeRowPtr, columnKey, value); } JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetByteArray(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex, + jlong nativeRowPtr, jlong columnKey, jbyteArray value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Binary)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Binary)) { return; } - Java_io_realm_internal_UncheckedRow_nativeSetByteArray(env, obj, nativeRowPtr, columnIndex, value); + Java_io_realm_internal_UncheckedRow_nativeSetByteArray(env, obj, nativeRowPtr, columnKey, value); } JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetLink(JNIEnv* env, jobject obj, jlong nativeRowPtr, - jlong columnIndex, jlong value) + jlong columnKey, jlong value) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Link)) { return; } - Java_io_realm_internal_UncheckedRow_nativeSetLink(env, obj, nativeRowPtr, columnIndex, value); + Java_io_realm_internal_UncheckedRow_nativeSetLink(env, obj, nativeRowPtr, columnKey, value); } JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeNullifyLink(JNIEnv* env, jobject obj, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - if (!ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ROW(nativeRowPtr), columnIndex, type_Link)) { + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Link)) { return; } - Java_io_realm_internal_UncheckedRow_nativeNullifyLink(env, obj, nativeRowPtr, columnIndex); + Java_io_realm_internal_UncheckedRow_nativeNullifyLink(env, obj, nativeRowPtr, columnKey); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp index e79ae3f0d8..7048bc12e6 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp @@ -28,7 +28,6 @@ static jintArray index_set_to_indices_array(JNIEnv* env, const IndexSet& index_s static void finalize_changeset(jlong ptr) { - TR_ENTER_PTR(ptr); delete reinterpret_cast(ptr); } @@ -82,14 +81,12 @@ static jintArray index_set_to_indices_array(JNIEnv* env, const IndexSet& index_s JNIEXPORT jlong JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_changeset); } JNIEXPORT jintArray JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetRanges(JNIEnv* env, jclass, jlong native_ptr, jint type) { - TR_ENTER_PTR(native_ptr) // no throws auto& change_set = *reinterpret_cast(native_ptr); switch (type) { @@ -107,7 +104,6 @@ JNIEXPORT jintArray JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeG JNIEXPORT jintArray JNICALL Java_io_realm_internal_OsCollectionChangeSet_nativeGetIndices(JNIEnv* env, jclass, jlong native_ptr, jint type) { - TR_ENTER_PTR(native_ptr) // no throws auto& change_set = *reinterpret_cast(native_ptr); switch (type) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp index ccee1c9720..b750764291 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp @@ -35,7 +35,6 @@ typedef ObservableCollectionWrapper ListWrapper; namespace { void finalize_list(jlong ptr) { - TR_ENTER_PTR(ptr) delete reinterpret_cast(ptr); } @@ -76,34 +75,26 @@ inline void check_nullable(JNIEnv* env, jlong list_ptr, jobject jobject_ptr = nu JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_list); } JNIEXPORT jlongArray JNICALL Java_io_realm_internal_OsList_nativeCreate(JNIEnv* env, jclass, jlong shared_realm_ptr, - jlong row_ptr, jlong column_index) + jlong obj_ptr, jlong column_key) { - TR_ENTER_PTR(row_ptr) - try { - auto& row = *reinterpret_cast(row_ptr); - - if (!ROW_AND_COL_INDEX_VALID(env, &row, column_index)) { - return 0; - } + auto& obj = *reinterpret_cast(obj_ptr); auto& shared_realm = *reinterpret_cast(shared_realm_ptr); jlong ret[2]; - List list(shared_realm, *row.get_table(), column_index, row.get_index()); + List list(shared_realm, obj, ColKey(column_key)); ListWrapper* wrapper_ptr = new ListWrapper(list); ret[0] = reinterpret_cast(wrapper_ptr); if (wrapper_ptr->collection().get_type() == PropertyType::Object) { - LinkViewRef link_view_ref(row.get_linklist(column_index)); + auto link_view_ref = obj.get_linklist(ColKey(column_key)); - Table* target_table_ptr = &(link_view_ref)->get_target_table(); - LangBindHelper::bind_table_ptr(target_table_ptr); + TableRef* target_table_ptr = new TableRef(link_view_ref.get_target_table()); ret[1] = reinterpret_cast(target_table_ptr); } else { @@ -125,49 +116,42 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_OsList_nativeCreate(JNIEnv* JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeGetRow(JNIEnv* env, jclass, jlong list_ptr, jlong column_index) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); - auto row = wrapper.collection().get(column_index); - return reinterpret_cast(new Row(std::move(row))); + auto obj = wrapper.collection().get(column_index); + return reinterpret_cast(new Obj(std::move(obj))); } CATCH_STD() return reinterpret_cast(nullptr); } JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddRow(JNIEnv* env, jclass, jlong list_ptr, - jlong target_row_index) + jlong target_obj_key) { - TR_ENTER_PTR(list_ptr) try { auto& wrapper = *reinterpret_cast(list_ptr); - wrapper.collection().add(static_cast(target_row_index)); + wrapper.collection().add(ObjKey(target_obj_key)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertRow(JNIEnv* env, jclass, jlong list_ptr, jlong pos, - jlong target_row_index) + jlong target_obj_key) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); - wrapper.collection().insert(static_cast(pos), static_cast(target_row_index)); + wrapper.collection().insert(static_cast(pos), ObjKey(target_obj_key)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetRow(JNIEnv* env, jclass, jlong list_ptr, jlong pos, - jlong target_row_index) + jlong target_obj_key) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); - wrapper.collection().set(static_cast(pos), static_cast(target_row_index)); + wrapper.collection().set(static_cast(pos), ObjKey(target_obj_key)); } CATCH_STD() } @@ -175,8 +159,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetRow(JNIEnv* env, j JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeMove(JNIEnv* env, jclass, jlong list_ptr, jlong source_index, jlong target_index) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); wrapper.collection().move(source_index, target_index); @@ -186,8 +168,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeMove(JNIEnv* env, jcl JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeRemove(JNIEnv* env, jclass, jlong list_ptr, jlong index) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); wrapper.collection().remove(index); @@ -197,8 +177,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeRemove(JNIEnv* env, j JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeRemoveAll(JNIEnv* env, jclass, jlong list_ptr) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); wrapper.collection().remove_all(); @@ -208,8 +186,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeRemoveAll(JNIEnv* env JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeSize(JNIEnv* env, jclass, jlong list_ptr) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); return wrapper.collection().size(); @@ -220,8 +196,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeSize(JNIEnv* env, jc JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeGetQuery(JNIEnv* env, jclass, jlong list_ptr) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); auto query = wrapper.collection().get_query(); @@ -233,8 +207,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeGetQuery(JNIEnv* env JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsList_nativeIsValid(JNIEnv* env, jclass, jlong list_ptr) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); return wrapper.collection().is_valid(); @@ -245,8 +217,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsList_nativeIsValid(JNIEnv* e JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeDelete(JNIEnv* env, jclass, jlong list_ptr, jlong index) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); wrapper.collection().delete_at(S(index)); @@ -256,8 +226,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeDelete(JNIEnv* env, j JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeDeleteAll(JNIEnv* env, jclass, jlong list_ptr) { - TR_ENTER_PTR(list_ptr) - try { auto& wrapper = *reinterpret_cast(list_ptr); wrapper.collection().delete_all(); @@ -268,8 +236,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeDeleteAll(JNIEnv* env JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeStartListening(JNIEnv* env, jobject instance, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - try { auto wrapper = reinterpret_cast(native_ptr); wrapper->start_listening(env, instance); @@ -279,8 +245,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeStartListening(JNIEnv JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - try { auto wrapper = reinterpret_cast(native_ptr); wrapper->stop_listening(); @@ -290,7 +254,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeStopListening(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddNull(JNIEnv* env, jclass, jlong list_ptr) { - TR_ENTER_PTR(list_ptr) try { check_nullable(env, list_ptr); add_value(env, list_ptr, Any()); @@ -300,7 +263,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddNull(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertNull(JNIEnv* env, jclass, jlong list_ptr, jlong pos) { - TR_ENTER_PTR(list_ptr) try { check_nullable(env, list_ptr); insert_value(env, list_ptr, pos, Any()); @@ -310,7 +272,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertNull(JNIEnv* en JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetNull(JNIEnv* env, jclass, jlong list_ptr, jlong pos) { - TR_ENTER_PTR(list_ptr) try { check_nullable(env, list_ptr); set_value(env, list_ptr, pos, Any()); @@ -320,7 +281,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetNull(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddLong(JNIEnv* env, jclass, jlong list_ptr, jlong value) { - TR_ENTER_PTR(list_ptr) try { add_value(env, list_ptr, Any(value)); } @@ -330,7 +290,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddLong(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertLong(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jlong value) { - TR_ENTER_PTR(list_ptr) try { insert_value(env, list_ptr, pos, Any(value)); } @@ -340,7 +299,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertLong(JNIEnv* en JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetLong(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jlong value) { - TR_ENTER_PTR(list_ptr) try { set_value(env, list_ptr, pos, Any(value)); } @@ -350,7 +308,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetLong(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddDouble(JNIEnv* env, jclass, jlong list_ptr, jdouble value) { - TR_ENTER_PTR(list_ptr) try { add_value(env, list_ptr, Any(value)); } @@ -360,7 +317,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddDouble(JNIEnv* env JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertDouble(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jdouble value) { - TR_ENTER_PTR(list_ptr) try { insert_value(env, list_ptr, pos, Any(value)); } @@ -370,7 +326,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertDouble(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetDouble(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jdouble value) { - TR_ENTER_PTR(list_ptr) try { set_value(env, list_ptr, pos, Any(value)); } @@ -379,7 +334,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetDouble(JNIEnv* env JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddFloat(JNIEnv* env, jclass, jlong list_ptr, jfloat value) { - TR_ENTER_PTR(list_ptr) try { add_value(env, list_ptr, Any(value)); } @@ -389,7 +343,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddFloat(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertFloat(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jfloat value) { - TR_ENTER_PTR(list_ptr) try { insert_value(env, list_ptr, pos, Any(value)); } @@ -399,7 +352,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertFloat(JNIEnv* e JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetFloat(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jfloat value) { - TR_ENTER_PTR(list_ptr) try { set_value(env, list_ptr, pos, Any(value)); } @@ -409,7 +361,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetFloat(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddBoolean(JNIEnv* env, jclass, jlong list_ptr, jboolean value) { - TR_ENTER_PTR(list_ptr) try { add_value(env, list_ptr, Any(value)); } @@ -419,7 +370,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddBoolean(JNIEnv* en JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertBoolean(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jboolean value) { - TR_ENTER_PTR(list_ptr) try { insert_value(env, list_ptr, pos, Any(value)); } @@ -429,7 +379,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertBoolean(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetBoolean(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jboolean value) { - TR_ENTER_PTR(list_ptr) try { set_value(env, list_ptr, pos, Any(value)); } @@ -439,7 +388,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetBoolean(JNIEnv* en JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddBinary(JNIEnv* env, jclass, jlong list_ptr, jbyteArray value) { - TR_ENTER_PTR(list_ptr) try { check_nullable(env, list_ptr, value); JByteArrayAccessor accessor(env, value); @@ -451,7 +399,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddBinary(JNIEnv* env JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertBinary(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jbyteArray value) { - TR_ENTER_PTR(list_ptr) try { check_nullable(env, list_ptr, value); JByteArrayAccessor accessor(env, value); @@ -463,7 +410,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertBinary(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetBinary(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jbyteArray value) { - TR_ENTER_PTR(list_ptr) try { check_nullable(env, list_ptr, value); JByteArrayAccessor accessor(env, value); @@ -474,7 +420,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetBinary(JNIEnv* env JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddDate(JNIEnv* env, jclass, jlong list_ptr, jlong value) { - TR_ENTER_PTR(list_ptr) try { add_value(env, list_ptr, Any(value)); } @@ -484,7 +429,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddDate(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertDate(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jlong value) { - TR_ENTER_PTR(list_ptr) try { insert_value(env, list_ptr, pos, Any(value)); } @@ -494,7 +438,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertDate(JNIEnv* en JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetDate(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jlong value) { - TR_ENTER_PTR(list_ptr) try { set_value(env, list_ptr, pos, Any(value)); } @@ -504,7 +447,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetDate(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddString(JNIEnv* env, jclass, jlong list_ptr, jstring value) { - TR_ENTER_PTR(list_ptr) try { check_nullable(env, list_ptr, value); JStringAccessor accessor(env, value); @@ -516,7 +458,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddString(JNIEnv* env JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertString(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jstring value) { - TR_ENTER_PTR(list_ptr) try { check_nullable(env, list_ptr, value); JStringAccessor accessor(env, value); @@ -528,7 +469,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertString(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetString(JNIEnv* env, jclass, jlong list_ptr, jlong pos, jstring value) { - TR_ENTER_PTR(list_ptr) try { check_nullable(env, list_ptr, value); JStringAccessor accessor(env, value); @@ -539,7 +479,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetString(JNIEnv* env JNIEXPORT jobject JNICALL Java_io_realm_internal_OsList_nativeGetValue(JNIEnv* env, jclass, jlong list_ptr, jlong pos) { - TR_ENTER_PTR(list_ptr) try { auto& wrapper = *reinterpret_cast(list_ptr); JavaAccessorContext context(env); @@ -549,3 +488,16 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_OsList_nativeGetValue(JNIEnv* e return nullptr; } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeFreeze(JNIEnv* env, jclass, jlong native_list_ptr, jlong frozen_realm_native_ptr) +{ + try { + auto& wrapper = *reinterpret_cast(native_list_ptr); + auto frozen_realm = *(reinterpret_cast(frozen_realm_native_ptr)); + List list = wrapper.collection().freeze(frozen_realm); + return reinterpret_cast(new ListWrapper(list)); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 66278fbcfd..2866bb4023 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -16,11 +16,9 @@ #include "io_realm_internal_OsObject.h" -#include #if REALM_ENABLE_SYNC #include #endif -#include #include #include @@ -86,14 +84,14 @@ struct ChangeCallback { // The local ref of jstring needs to be released to avoid reach the local ref table size limitation. std::vector field_names; - auto table = m_wrapper->m_object.row().get_table(); - for (size_t i = 0; i < change_set.columns.size(); ++i) { - if (change_set.columns[i].empty()) { + auto table = m_wrapper->m_object.obj().get_table(); + for (const auto& col: change_set.columns) { + if (col.second.empty()) { continue; } // FIXME: After full integration of the OS schema, parse the column name from // wrapper->m_object.get_object_schema() will be faster. - field_names.push_back(JavaGlobalRef(env, to_jstring(env, table->get_column_name(i)), true)); + field_names.push_back(JavaGlobalRef(env, to_jstring(env, table->get_column_name(ColKey(col.first))), true)); } m_field_names_array = env->NewObjectArray(field_names.size(), JavaClassGlobalDef::java_lang_string(), 0); for (size_t i = 0; i < field_names.size(); ++i) { @@ -160,133 +158,84 @@ struct ChangeCallback { static void finalize_object(jlong ptr) { - TR_ENTER_PTR(ptr); delete reinterpret_cast(ptr); } -static inline size_t do_create_row(jlong shared_realm_ptr, jlong table_ptr) +static inline Obj do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ref_ptr, + jlong pk_column_key, jlong pk_value, jboolean is_pk_null) { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto& table = *(reinterpret_cast(table_ptr)); - shared_realm->verify_in_write(); -#if REALM_ENABLE_SYNC - return sync::create_object(shared_realm->read_group(), table); -#else - return table.add_empty_row(); -#endif -} - -static inline size_t do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ptr, - jlong pk_column_ndx, jlong pk_value, jboolean is_pk_null) -{ - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto& table = *(reinterpret_cast(table_ptr)); + TableRef table = TBL_REF(table_ref_ptr); + ColKey col_key(pk_column_key); shared_realm->verify_in_write(); // throws - if (is_pk_null && !TBL_AND_COL_NULLABLE(env, &table, pk_column_ndx)) { - return realm::npos; + if (is_pk_null && !COL_NULLABLE(env, table, pk_column_key)) { + return Obj(); } if (is_pk_null) { - if (table.find_first_null(pk_column_ndx) != npos) { + + if (bool(table->find_first_null(col_key))) { THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, "'null'")); } } else { - if (table.find_first_int(pk_column_ndx, pk_value) != npos) { + if (bool(table->find_first_int(col_key, pk_value))) { THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, reinterpret_cast(pk_value))); } } - size_t row_ndx; -#if REALM_ENABLE_SYNC - if (is_pk_null) { - row_ndx = sync::create_object_with_primary_key(shared_realm->read_group(), table, util::none); - } - else { - row_ndx = sync::create_object_with_primary_key(shared_realm->read_group(), table, - util::Optional(pk_value)); - } -#else - row_ndx = table.add_empty_row(); - - if (is_pk_null) { - table.set_null_unique(pk_column_ndx, row_ndx); - } - else { - table.set_int_unique(pk_column_ndx, row_ndx, pk_value); - } -#endif - return row_ndx; + Mixed pk_val = is_pk_null ? Mixed() : Mixed(util::Optional(pk_value)); + return table->create_object_with_primary_key(pk_val); } -static inline size_t do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ptr, - jlong pk_column_ndx, jstring pk_value) +static inline Obj do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ref_ptr, + jlong pk_column_key, jstring pk_value) { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto& table = *(reinterpret_cast(table_ptr)); + TableRef table = TBL_REF(table_ref_ptr); + ColKey col_key(pk_column_key); shared_realm->verify_in_write(); // throws JStringAccessor str_accessor(env, pk_value); // throws - if (!pk_value && !TBL_AND_COL_NULLABLE(env, &table, pk_column_ndx)) { - return realm::npos; + if (!pk_value && !COL_NULLABLE(env, table, pk_column_key)) { + return Obj(); } if (pk_value) { - if (table.find_first_string(pk_column_ndx, str_accessor) != npos) { + if (bool(table->find_first_string(col_key, str_accessor))) { THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, str_accessor.operator std::string())); } } else { - if (table.find_first_null(pk_column_ndx) != npos) { + if (bool(table->find_first_null(col_key))) { THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, "'null'")); } } - - size_t row_ndx; -#if REALM_ENABLE_SYNC - row_ndx = sync::create_object_with_primary_key(shared_realm->read_group(), table, str_accessor); -#else - row_ndx = table.add_empty_row(); - if (pk_value) { - table.set_string_unique(pk_column_ndx, row_ndx, str_accessor); - } - else { - table.set_string_unique(pk_column_ndx, row_ndx, null{}); - } -#endif - - return row_ndx; + return table->create_object_with_primary_key(StringData(str_accessor)); } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_object); } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreate(JNIEnv*, jclass, jlong shared_realm_ptr, - jlong row_ptr) + jlong obj_ptr) { - TR_ENTER_PTR(row_ptr) - // FIXME: Currently OsObject is only used for object notifications. Since the Object Store's schema has not been // fully integrated with realm-java, we pass a dummy ObjectSchema to create Object. static const ObjectSchema dummy_object_schema; - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto& row = *(reinterpret_cast(row_ptr)); - Object object(shared_realm, dummy_object_schema, row); // no throw + auto& obj = *(reinterpret_cast(obj_ptr)); + Object object(shared_realm, dummy_object_schema, obj); // no throw auto wrapper = new ObjectWrapper(object); // no throw - return reinterpret_cast(wrapper); } JNIEXPORT void JNICALL Java_io_realm_internal_OsObject_nativeStartListening(JNIEnv* env, jobject instance, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - try { auto wrapper = reinterpret_cast(native_ptr); if (!wrapper->m_row_object_weak_ref) { @@ -306,8 +255,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObject_nativeStartListening(JNIE JNIEXPORT void JNICALL Java_io_realm_internal_OsObject_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - try { auto wrapper = reinterpret_cast(native_ptr); wrapper->m_notification_token = {}; @@ -315,38 +262,37 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObject_nativeStopListening(JNIEn CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRow(JNIEnv* env, jclass, jlong shared_realm_ptr, - jlong table_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRow(JNIEnv* env, jclass, jlong table_ref_ptr) { try { - return do_create_row(shared_realm_ptr, table_ptr); + TableRef table = TBL_REF(table_ref_ptr); + Obj obj = table->create_object(); + return (jlong)(obj.get_key().value); } CATCH_STD() return -1; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObject(JNIEnv* env, jclass, - jlong shared_realm_ptr, jlong table_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObject(JNIEnv* env, jclass, jlong table_ref_ptr) { try { - size_t row_ndx = do_create_row(shared_realm_ptr, table_ptr); - auto& table = *(reinterpret_cast(table_ptr)); - return reinterpret_cast(new Row(table[row_ndx])); + TableRef table = TBL_REF(table_ref_ptr); + Obj* obj = new Obj(table->create_object()); + return reinterpret_cast(obj); } CATCH_STD() return 0; } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWithLongPrimaryKey( - JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong pk_column_ndx, jlong pk_value, + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jlong pk_value, jboolean is_pk_null) { try { - auto& table = *(reinterpret_cast(table_ptr)); - size_t row_ndx = - do_create_row_with_primary_key(env, shared_realm_ptr, table_ptr, pk_column_ndx, pk_value, is_pk_null); - if (row_ndx != realm::npos) { - return reinterpret_cast(new Row(table[row_ndx])); + Obj obj = + do_create_row_with_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value, is_pk_null); + if (bool(obj)) { + return reinterpret_cast(new Obj(obj)); } } CATCH_STD() @@ -354,24 +300,24 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWit } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithLongPrimaryKey( - JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong pk_column_ndx, jlong pk_value, + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jlong pk_value, jboolean is_pk_null) { try { - return do_create_row_with_primary_key(env, shared_realm_ptr, table_ptr, pk_column_ndx, pk_value, is_pk_null); + Obj obj = do_create_row_with_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value, is_pk_null); + return (jlong)(obj.get_key().value); } CATCH_STD() return realm::npos; } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWithStringPrimaryKey( - JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong pk_column_ndx, jstring pk_value) + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) { try { - auto& table = *(reinterpret_cast(table_ptr)); - size_t row_ndx = do_create_row_with_primary_key(env, shared_realm_ptr, table_ptr, pk_column_ndx, pk_value); - if (row_ndx != realm::npos) { - return reinterpret_cast(new Row(table[row_ndx])); + Obj obj = do_create_row_with_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value); + if (bool(obj)) { + return reinterpret_cast(new Obj(obj)); } } CATCH_STD() @@ -380,23 +326,12 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWit } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithStringPrimaryKey( - JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong pk_column_ndx, jstring pk_value) + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) { try { - return do_create_row_with_primary_key(env, shared_realm_ptr, table_ptr, pk_column_ndx, pk_value); + Obj obj = do_create_row_with_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value); + return (jlong)(obj.get_key().value); } CATCH_STD() - return realm::npos; } - -JNIEXPORT jstring JNICALL Java_io_realm_internal_OsObject_nativeGetObjectIdColumName(JNIEnv* env, jclass) -{ -// TODO: Remove the macro and get the name from core when core has stable ID support. -#if REALM_ENABLE_SYNC - const char* object_id_column_name = sync::object_id_column_name; -#else - const char* object_id_column_name = "!OID"; -#endif - return to_jstring(env, object_id_column_name); -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp index 7653455602..b5ea26d925 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp @@ -32,14 +32,12 @@ using namespace realm::_impl; static void finalize_object_schema(jlong ptr) { - TR_ENTER_PTR(ptr); delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeCreateRealmObjectSchema(JNIEnv* env, jclass, jstring j_name_str) { - TR_ENTER() try { JStringAccessor name(env, j_name_str); ObjectSchema* object_schema = new ObjectSchema(); @@ -52,7 +50,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeCreateRe JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_object_schema); } @@ -61,7 +58,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeAddProper jlongArray j_persisted_properties, jlongArray j_computed_properties) { - TR_ENTER_PTR(native_ptr) try { ObjectSchema& object_schema = *reinterpret_cast(native_ptr); JLongArrayAccessor persisted_properties(env, j_persisted_properties); @@ -91,7 +87,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeAddProper JNIEXPORT jstring JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetClassName(JNIEnv* env, jclass, jlong nativePtr) { - TR_ENTER_PTR(nativePtr) try { ObjectSchema* object_schema = reinterpret_cast(nativePtr); auto name = object_schema->name; @@ -106,7 +101,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrope jlong native_ptr, jstring j_property_name) { - TR_ENTER_PTR(native_ptr) try { auto& object_schema = *reinterpret_cast(native_ptr); JStringAccessor property_name_accessor(env, j_property_name); @@ -125,8 +119,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrope JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrimaryKeyProperty(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - try { auto& object_schema = *reinterpret_cast(native_ptr); auto* property = object_schema.primary_key_property(); @@ -137,26 +129,3 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrima CATCH_STD() return reinterpret_cast(nullptr); } - -JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetMaxColumnIndex(JNIEnv* env, jclass, - jlong native_ptr) -{ - TR_ENTER_PTR(native_ptr) - - try { - auto& object_schema = *reinterpret_cast(native_ptr); - if (object_schema.persisted_properties.empty()) { - return static_cast(-1); - } else { - size_t maxIndex = 0; - for (Property p : object_schema.persisted_properties) { - if (p.table_column > maxIndex) { - maxIndex = p.table_column; - } - } - return static_cast(maxIndex); - } - } - CATCH_STD() - return static_cast(-1); -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp index 4ffff87662..2caca07a8b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp @@ -37,7 +37,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetPrimaryKeyF jstring j_class_name, jstring j_pk_field_name) { - TR_ENTER_PTR(shared_realm_ptr) try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); JStringAccessor class_name_accessor(env, j_class_name); @@ -48,18 +47,20 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetPrimaryKeyF THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, format("Class '%1' doesn't exist.", StringData(class_name_accessor))); } + shared_realm->verify_in_write(); if (j_pk_field_name) { // Not removal, check the column. - auto pk_column_ndx = table->get_column_index(pk_field_name_accessor); - if (pk_column_ndx == realm::npos) { + ColKey pk_column_col = table->get_column_key(pk_field_name_accessor); + if (!table->valid_column(pk_column_col)) { + THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, format("Field '%1' doesn't exist in Class '%2'.", StringData(pk_field_name_accessor), StringData(class_name_accessor))); } // Check valid column type - auto field_type = table->get_column_type(pk_column_ndx); + auto field_type = table->get_column_type(pk_column_col); if (field_type != type_Int && field_type != type_String) { THROW_JAVA_EXCEPTION( env, JavaExceptionDef::IllegalArgument, @@ -67,7 +68,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetPrimaryKeyF } // Check duplicated values. The pk field must have been indexed before set as a PK. - if (table->get_distinct_view(pk_column_ndx).size() != table->size()) { + // __CORE6__ work around until table->contains_unique_values is provided by Core6 + // since calling get_distinct_view is not possible on non indexed column (throws) + auto tv = table->where().find_all(); + tv.distinct(pk_column_col); + if (tv.size() != table->size()) { + // if (table->get_distinct_view(pk_column_col).size() != table->size()) { THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, format("Field '%1' cannot be set as primary key since there are duplicated " "values for field '%1' in Class '%2'.", @@ -85,12 +91,10 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsObjectStore_nativeGetPrimaryK jlong shared_realm_ptr, jstring j_class_name) { - TR_ENTER_PTR(shared_realm_ptr) try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); JStringAccessor class_name_accessor(env, j_class_name); - StringData pk_field_name = - ObjectStore::get_primary_key_for_object(shared_realm->read_group(), class_name_accessor); + StringData pk_field_name = ObjectStore::get_primary_key_for_object(shared_realm->read_group(), class_name_accessor); return pk_field_name.size() == 0 ? nullptr : to_jstring(env, pk_field_name); } CATCH_STD() @@ -101,7 +105,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetSchemaVersi jlong shared_realm_ptr, jlong schema_version) { - TR_ENTER_PTR(shared_realm_ptr) try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); shared_realm->verify_in_write(); @@ -113,7 +116,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetSchemaVersi JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectStore_nativeGetSchemaVersion(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return ObjectStore::get_schema_version(shared_realm->read_group()); @@ -126,7 +128,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsObjectStore_nativeDeleteTabl jlong shared_realm_ptr, jstring j_class_name) { - TR_ENTER_PTR(shared_realm_ptr) try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); JStringAccessor class_name_accessor(env, j_class_name); @@ -145,13 +146,12 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsObjectStore_nativeCallWithLo jstring j_realm_path, jobject j_runnable) { - TR_ENTER(); try { JStringAccessor path_accessor(env, j_realm_path); std::string realm_path(path_accessor); static JavaClass runnable_class(env, "java/lang/Runnable"); static JavaMethod run_method(env, runnable_class, "run", "()V"); - bool result = SharedGroup::call_with_lock(realm_path, [&](std::string path) { + bool result = DB::call_with_lock(realm_path, [&](std::string path) { REALM_ASSERT_RELEASE_EX(realm_path.compare(path) == 0, realm_path.c_str(), path.c_str()); env->CallVoidMethod(j_runnable, run_method); TERMINATE_JNI_IF_JAVA_EXCEPTION_OCCURRED(env, nullptr); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index dac48e2756..48678d7e65 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -58,7 +58,6 @@ static_assert(SchemaMode::Manual == static_cast(io_realm_internal_Os static void finalize_realm_config(jlong ptr) { - TR_ENTER_PTR(ptr) delete reinterpret_cast(ptr); } @@ -70,24 +69,22 @@ static JavaClass& get_shared_realm_class(JNIEnv* env) JNIEXPORT jlong JNICALL Java_io_realm_internal_OsRealmConfig_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_realm_config); } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreate(JNIEnv* env, jclass, jstring j_realm_path, jstring j_fifo_fallback_dir, - jboolean enable_cache, - jboolean enable_format_upgrade) + jboolean enable_format_upgrade, + jlong j_max_number_of_active_versions) { - TR_ENTER() try { JStringAccessor realm_path(env, j_realm_path); JStringAccessor fifo_fallback_dir(env, j_fifo_fallback_dir); auto* config_ptr = new Realm::Config(); config_ptr->path = realm_path; - config_ptr->cache = enable_cache; config_ptr->disable_format_upgrade = !enable_format_upgrade; config_ptr->fifo_files_fallback_path = fifo_fallback_dir; + config_ptr->max_number_of_active_versions = j_max_number_of_active_versions; return reinterpret_cast(config_ptr); } CATCH_STD() @@ -98,7 +95,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetEncryptionK jlong native_ptr, jbyteArray j_key_array) { - TR_ENTER_PTR(native_ptr) try { JByteArrayAccessor jarray_accessor(env, j_key_array); auto& config = *reinterpret_cast(native_ptr); @@ -112,7 +108,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetEncryptionK JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetInMemory(JNIEnv*, jclass, jlong native_ptr, jboolean in_mem) { - TR_ENTER_PTR(native_ptr) auto& config = *reinterpret_cast(native_ptr); config.in_memory = in_mem; // no throw } @@ -123,7 +118,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSchemaConfi jlong schema_info_ptr, jobject j_migration_callback) { - TR_ENTER_PTR(native_ptr) try { auto& config = *reinterpret_cast(native_ptr); config.schema_mode = static_cast(schema_mode); @@ -173,8 +167,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSchemaConfi JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetCompactOnLaunchCallback( JNIEnv* env, jclass, jlong native_ptr, jobject j_compact_on_launch) { - TR_ENTER_PTR(native_ptr) - try { auto& config = *reinterpret_cast(native_ptr); if (j_compact_on_launch) { @@ -207,8 +199,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetInitializat jlong native_ptr, jobject j_init_callback) { - TR_ENTER_PTR(native_ptr) - try { auto& config = *reinterpret_cast(native_ptr); @@ -245,8 +235,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetInitializat JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNotification( JNIEnv*, jclass, jlong native_ptr, jboolean enable_auto_change_notification) { - TR_ENTER_PTR(native_ptr) - // No throws auto& config = *reinterpret_cast(native_ptr); config.automatic_change_notifications = enable_auto_change_notification; @@ -258,7 +246,6 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe jstring j_refresh_token, jboolean j_is_partial, jbyte j_session_stop_policy, jstring j_url_prefix, jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array, jbyte j_client_reset_mode) { - TR_ENTER_PTR(native_ptr) auto& config = *reinterpret_cast(native_ptr); // sync_config should only be initialized once! REALM_ASSERT(!config.sync_config); @@ -411,8 +398,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigS JNIEnv* env, jclass, jlong native_ptr, jboolean sync_client_validate_ssl, jstring j_sync_ssl_trust_certificate_path) { - TR_ENTER_PTR(native_ptr); - auto& config = *reinterpret_cast(native_ptr); // To ensure the sync_config has been created and this function won't be called multiple time on the same config. REALM_ASSERT(config.sync_config); @@ -464,8 +449,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigP JNIEnv* env, jclass, jlong native_ptr, jbyte proxy_type, jstring j_proxy_address, jint proxy_port) { - TR_ENTER_PTR(native_ptr); - auto& config = *reinterpret_cast(native_ptr); // To ensure the sync_config has been created and this function won't be called multiple time on the same config. REALM_ASSERT(config.sync_config); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index e03f7d0c56..b73ab5c6fc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -37,7 +37,6 @@ static void finalize_results(jlong ptr); static void finalize_results(jlong ptr) { - TR_ENTER_PTR(ptr); delete reinterpret_cast(ptr); } @@ -46,10 +45,9 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResults(JNI jlong query_ptr, jlong descriptor_ordering_ptr) { - TR_ENTER() try { auto query = reinterpret_cast(query_ptr); - if (!QUERY_VALID(env, query)) { + if (!TABLE_VALID(env, query->get_table())) { return reinterpret_cast(nullptr); } @@ -66,7 +64,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResults(JNI JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr); try { auto wrapper = reinterpret_cast(native_ptr); auto snapshot_results = wrapper->collection().snapshot(); @@ -78,13 +75,12 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateSnapshot(JN } JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeContains(JNIEnv* env, jclass, jlong native_ptr, - jlong native_row_ptr) + jlong native_obj_ptr) { - TR_ENTER_PTR(native_ptr); try { auto wrapper = reinterpret_cast(native_ptr); - auto row = reinterpret_cast(native_row_ptr); - size_t index = wrapper->collection().index_of(RowExpr(*row)); + const Obj* obj = reinterpret_cast(native_obj_ptr); + size_t index = wrapper->collection().index_of(*obj); return to_jbool(index != not_found); } CATCH_STD(); @@ -94,11 +90,10 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeContains(JNIEn JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeGetRow(JNIEnv* env, jclass, jlong native_ptr, jint index) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->collection().get(static_cast(index)); - return reinterpret_cast(new Row(std::move(row))); + auto obj = wrapper->collection().get(static_cast(index)); + return reinterpret_cast(new Obj(std::move(obj))); } CATCH_STD() return reinterpret_cast(nullptr); @@ -106,26 +101,25 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeGetRow(JNIEnv* en JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeFirstRow(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto optional_row = wrapper->collection().first(); - if (optional_row) { - return reinterpret_cast(new Row(std::move(optional_row.value()))); + auto optional_obj = wrapper->collection().first(); + if (optional_obj) { + return reinterpret_cast(new Obj(std::move(optional_obj.value()))); } } CATCH_STD() return reinterpret_cast(nullptr); } + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeLastRow(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto optional_row = wrapper->collection().last(); - if (optional_row) { - return reinterpret_cast(new Row(std::move(optional_row.value()))); + auto optional_obj = wrapper->collection().last(); + if (optional_obj) { + return reinterpret_cast(new Obj(optional_obj.value())); } } CATCH_STD() @@ -134,7 +128,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeLastRow(JNIEnv* e JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeClear(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); wrapper->collection().clear(); @@ -144,7 +137,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeClear(JNIEnv* env, JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeSize(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); return static_cast(wrapper->collection().size()); @@ -154,23 +146,22 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeSize(JNIEnv* env, } JNIEXPORT jobject JNICALL Java_io_realm_internal_OsResults_nativeAggregate(JNIEnv* env, jclass, jlong native_ptr, - jlong column_index, jbyte agg_func) + jlong column_key, jbyte agg_func) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - size_t index = S(column_index); + ColKey col_key(column_key); Optional value; switch (agg_func) { case io_realm_internal_OsResults_AGGREGATE_FUNCTION_MINIMUM: - value = wrapper->collection().min(index); + value = wrapper->collection().min(col_key); break; case io_realm_internal_OsResults_AGGREGATE_FUNCTION_MAXIMUM: - value = wrapper->collection().max(index); + value = wrapper->collection().max(col_key); break; case io_realm_internal_OsResults_AGGREGATE_FUNCTION_AVERAGE: { - Optional value_count(wrapper->collection().average(index)); + Optional value_count(wrapper->collection().average(col_key)); if (value_count) { value = Optional(Mixed(value_count.value())); } @@ -180,7 +171,7 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_OsResults_nativeAggregate(JNIEn break; } case io_realm_internal_OsResults_AGGREGATE_FUNCTION_SUM: - value = wrapper->collection().sum(index); + value = wrapper->collection().sum(col_key); break; default: REALM_UNREACHABLE(); @@ -211,7 +202,6 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_OsResults_nativeAggregate(JNIEn JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeSort(JNIEnv* env, jclass, jlong native_ptr, jobject j_sort_desc) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); auto sorted_result = wrapper->collection().sort(JavaQueryDescriptor(env, j_sort_desc).sort_descriptor()); @@ -224,7 +214,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeSort(JNIEnv* env, JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeDistinct(JNIEnv* env, jclass, jlong native_ptr, jobject j_distinct_desc) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); auto distinct_result = @@ -238,8 +227,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeDistinct(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeStartListening(JNIEnv* env, jobject instance, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - try { auto wrapper = reinterpret_cast(native_ptr); wrapper->start_listening(env, instance); @@ -249,8 +236,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeStartListening(JNI JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - try { auto wrapper = reinterpret_cast(native_ptr); wrapper->stop_listening(); @@ -260,19 +245,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeStopListening(JNIE JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_results); } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeWhere(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); auto table_view = wrapper->collection().get_tableview(); Query* query = - new Query(table_view.get_parent(), std::unique_ptr(new TableView(std::move(table_view)))); + new Query(table_view.get_parent(), std::unique_ptr(new TableView(std::move(table_view)))); return reinterpret_cast(query); } CATCH_STD() @@ -281,7 +264,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeWhere(JNIEnv* env JNIEXPORT jstring JNICALL Java_io_realm_internal_OsResults_toJSON(JNIEnv* env, jclass, jlong native_ptr, jint maxDepth) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); @@ -295,14 +277,13 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsResults_toJSON(JNIEnv* env, j } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeIndexOf(JNIEnv* env, jclass, jlong native_ptr, - jlong row_native_ptr) + jlong obj_native_ptr) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto row = reinterpret_cast(row_native_ptr); + Obj* obj = reinterpret_cast(obj_native_ptr); - return static_cast(wrapper->collection().index_of(RowExpr(*row))); + return static_cast(wrapper->collection().index_of(*obj)); } CATCH_STD() return npos; @@ -310,12 +291,11 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeIndexOf(JNIEnv* e JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeDeleteLast(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->collection().last(); - if (row && row->is_attached()) { - row->move_last_over(); + auto obj = wrapper->collection().last(); + if (obj && obj->is_valid()) { + obj->remove(); return JNI_TRUE; } } @@ -325,13 +305,11 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeDeleteLast(JNI JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeDeleteFirst(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) - try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->collection().first(); - if (row && row->is_attached()) { - row->move_last_over(); + auto obj = wrapper->collection().first(); + if (obj && obj->is_valid()) { + obj->remove(); return JNI_TRUE; } } @@ -352,42 +330,36 @@ static inline void update_objects(JNIEnv* env, jlong results_ptr, jstring& j_fie JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetNull(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name) { - TR_ENTER_PTR(native_ptr) auto value = JavaValue(); update_objects(env, native_ptr, j_field_name, value); } JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetBoolean(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jboolean j_value) { - TR_ENTER_PTR(native_ptr) JavaValue value(j_value); update_objects(env, native_ptr, j_field_name, value); } JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetInt(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong j_value) { - TR_ENTER_PTR(native_ptr) JavaValue value(j_value); update_objects(env, native_ptr, j_field_name, value); } JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetFloat(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jfloat j_value) { - TR_ENTER_PTR(native_ptr) JavaValue value(j_value); update_objects(env, native_ptr, j_field_name, value); } JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetDouble(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jdouble j_value) { - TR_ENTER_PTR(native_ptr) JavaValue value(j_value); update_objects(env, native_ptr, j_field_name, value); } JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetString(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jstring j_value) { - TR_ENTER_PTR(native_ptr) JStringAccessor str(env, j_value); JavaValue value = str.is_null() ? JavaValue() : JavaValue(std::string(str)); update_objects(env, native_ptr, j_field_name, value); @@ -395,7 +367,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetString(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetBinary(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jbyteArray j_value) { - TR_ENTER_PTR(native_ptr) auto data = OwnedBinaryData(JByteArrayAccessor(env, j_value).transform()); JavaValue value(data); update_objects(env, native_ptr, j_field_name, value); @@ -403,15 +374,13 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetBinary(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetTimestamp(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong j_value) { - TR_ENTER_PTR(native_ptr) JavaValue value(from_milliseconds(j_value)); update_objects(env, native_ptr, j_field_name, value); } JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetObject(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong row_ptr) { - TR_ENTER_PTR(native_ptr) - JavaValue value(reinterpret_cast(row_ptr)); + JavaValue value(reinterpret_cast(row_ptr)); update_objects(env, native_ptr, j_field_name, value); } @@ -420,21 +389,19 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetList(JNIEnv* en // OsObjectBuilder has been used to build up the list we want to insert. This means the // fake object described by the OsObjectBuilder only contains one property, namely the list we // want to insert and this list is assumed to be at index = 0. - std::vector builder = *reinterpret_cast*>(builder_ptr); + std::map builder = *reinterpret_cast*>(builder_ptr); REALM_ASSERT_DEBUG(builder.size() == 1); - update_objects(env, native_ptr, j_field_name, builder[0]); + update_objects(env, native_ptr, j_field_name, builder.begin()->second); } JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeDelete(JNIEnv* env, jclass, jlong native_ptr, jlong index) { - TR_ENTER_PTR(native_ptr) - try { auto wrapper = reinterpret_cast(native_ptr); - auto row = wrapper->collection().get(index); - if (row.is_attached()) { - row.move_last_over(); + auto obj = wrapper->collection().get(index); + if (obj.is_valid()) { + obj.remove(); } } CATCH_STD() @@ -442,7 +409,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeDelete(JNIEnv* env JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeIsValid(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); return wrapper->collection().is_valid(); @@ -453,7 +419,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsResults_nativeIsValid(JNIEnv JNIEXPORT jbyte JNICALL Java_io_realm_internal_OsResults_nativeGetMode(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); switch (wrapper->collection().get_mode()) { @@ -461,32 +426,35 @@ JNIEXPORT jbyte JNICALL Java_io_realm_internal_OsResults_nativeGetMode(JNIEnv* e return io_realm_internal_OsResults_MODE_EMPTY; case Results::Mode::Table: return io_realm_internal_OsResults_MODE_TABLE; + case Results::Mode::List: + return io_realm_internal_OsResults_MODE_LIST; case Results::Mode::Query: return io_realm_internal_OsResults_MODE_QUERY; - case Results::Mode::LinkView: - return io_realm_internal_OsResults_MODE_LINKVIEW; + case Results::Mode::LinkList: + return io_realm_internal_OsResults_MODE_LINK_LIST; case Results::Mode::TableView: return io_realm_internal_OsResults_MODE_TABLEVIEW; + default: + throw std::logic_error(util::format("Unexpected state: %1", static_cast(wrapper->collection().get_mode()))); } } CATCH_STD() - return -1; // Invalid mode value + return -1; } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResultsFromBacklinks(JNIEnv *env, jclass, jlong shared_realm_ptr, - jlong row_ptr, - jlong src_table_ptr, - jlong src_col_index) + jlong obj_ptr, + jlong src_table_ref_ptr, + jlong src_col_key) { - TR_ENTER_PTR(row_ptr) - Row* row = ROW(row_ptr); - if (!ROW_VALID(env, row)) { + Obj* obj = OBJ(obj_ptr); + if (!ROW_VALID(env, obj)) { return reinterpret_cast(nullptr); } try { - Table* src_table = TBL(src_table_ptr); - TableView backlink_view = row->get_table()->get_backlink_view(row->get_index(), src_table, src_col_index); + TableRef src_table = TBL_REF(src_table_ref_ptr); + TableView backlink_view = obj->get_backlink_view(src_table, ColKey(src_col_key)); auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); Results results(shared_realm, std::move(backlink_view)); auto wrapper = new ResultsWrapper(results); @@ -500,10 +468,22 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeEvaluateQueryIfNee jlong native_ptr, jboolean wants_notifications) { - TR_ENTER_PTR(native_ptr) try { auto wrapper = reinterpret_cast(native_ptr); wrapper->collection().evaluate_query_if_needed(wants_notifications); } CATCH_STD() } + + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeFreeze(JNIEnv* env, jclass, jlong native_ptr, jlong frozen_realm_native_ptr) +{ + try { + auto wrapper = reinterpret_cast(native_ptr); + auto frozen_realm = *(reinterpret_cast(frozen_realm_native_ptr)); + Results results = wrapper->collection().freeze(frozen_realm); + return reinterpret_cast(new ResultsWrapper(results)); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp index 744a6ea7af..8675e9e18a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp @@ -31,14 +31,12 @@ using namespace realm::_impl; static void finalize_schema(jlong ptr) { - TR_ENTER_PTR(ptr); delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSchemaInfo_nativeCreateFromList(JNIEnv* env, jclass, jlongArray objectSchemaPtrs_) { - TR_ENTER() try { std::vector object_schemas; JLongArrayAccessor array(env, objectSchemaPtrs_); @@ -54,7 +52,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSchemaInfo_nativeCreateFromList JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSchemaInfo_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_schema); } @@ -62,8 +59,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSchemaInfo_nativeGetObjectSchem jlong native_ptr, jstring j_class_name) { - TR_ENTER_PTR(native_ptr) - try { JStringAccessor class_name_accessor(env, j_class_name); StringData class_name(class_name_accessor); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 034578b0b5..0dc28892da 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -52,23 +52,28 @@ typedef ObservableCollectionWrapper ResultsWrapper; JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeInit(JNIEnv* env, jclass, jstring temporary_directory_path) { - TR_ENTER() - try { JStringAccessor path(env, temporary_directory_path); // throws - SharedGroupOptions::set_sys_tmp_dir(std::string(path)); // throws + DBOptions::set_sys_tmp_dir(std::string(path)); // throws } CATCH_STD() } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSharedRealm(JNIEnv* env, jclass, jlong config_ptr, + jlong j_version_no, jlong j_version_index, jobject realm_notifier) { - TR_ENTER_PTR(config_ptr) - auto& config = *reinterpret_cast(config_ptr); try { - auto shared_realm = Realm::get_shared_realm(config); + SharedRealm shared_realm; + if (j_version_no == -1 && j_version_index == -1) { + shared_realm = Realm::get_shared_realm(config); + } + else { + VersionID version(static_cast(j_version_no), static_cast(j_version_index)); + shared_realm = Realm::get_frozen_realm(config, version); + } + // The migration callback & initialization callback could throw. if (env->ExceptionCheck()) { return reinterpret_cast(nullptr); @@ -105,8 +110,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSharedReal JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); // Close the SharedRealm only. Let the finalizer daemon thread free the SharedRealm if (!shared_realm->is_closed()) { @@ -117,8 +120,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCloseSharedRea JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeBeginTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->begin_transaction(); @@ -129,8 +130,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeBeginTransacti JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCommitTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->commit_transaction(); @@ -147,8 +146,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCommitTransact JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCancelTransaction(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->cancel_transaction(); @@ -160,8 +157,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCancelTransact JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsInTransaction(JNIEnv*, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return static_cast(shared_realm->is_in_transaction()); } @@ -169,8 +164,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsInTransa JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsEmpty(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(ObjectStore::is_empty(shared_realm->read_group())); @@ -181,8 +174,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsEmpty(JN JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRefresh(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { shared_realm->refresh(); @@ -193,13 +184,14 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRefresh(JNIEnv JNIEXPORT jlongArray JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetVersionID(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { - using rf = realm::_impl::RealmFriend; - SharedGroup::VersionID version_id = rf::get_shared_group(*shared_realm).get_version_of_current_transaction(); + util::Optional opt_version_id = shared_realm->current_transaction_version(); + if (!opt_version_id) { + return NULL; + } + DB::VersionID version_id = opt_version_id.value(); jlong version_array[2]; version_array[0] = static_cast(version_id.version); version_array[1] = static_cast(version_id.index); @@ -220,22 +212,19 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetVersi JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return static_cast(shared_realm->is_closed()); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTable(JNIEnv* env, jclass, jlong shared_realm_ptr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTableRef(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring table_name) { - TR_ENTER_PTR(shared_realm_ptr) - try { JStringAccessor name(env, table_name); // throws auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - if (!shared_realm->read_group().has_table(name)) { + auto& group = shared_realm->read_group(); + if (!group.has_table(name)) { std::string name_str = name; if (name_str.find(TABLE_PREFIX) == 0) { name_str = name_str.substr(TABLE_PREFIX.length()); @@ -243,8 +232,9 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTable(JNIE THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, format("The class '%1' doesn't exist in this Realm.", name_str)); } - Table* table = LangBindHelper::get_table(shared_realm->read_group(), name); - return reinterpret_cast(table); + + TableRef* tableRef = new TableRef(group.get_table(name)); + return reinterpret_cast(tableRef); } CATCH_STD() @@ -255,14 +245,12 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTable(J jlong shared_realm_ptr, jstring j_table_name) { - TR_ENTER_PTR(shared_realm_ptr) - std::string table_name; try { table_name = JStringAccessor(env, j_table_name); // throws auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); shared_realm->verify_in_write(); // throws - Table* table; + TableRef table; auto& group = shared_realm->read_group(); #if REALM_ENABLE_SYNC // Sync doesn't throw when table exists. @@ -270,12 +258,11 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTable(J THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, format(c_table_name_exists_exception_msg, table_name.substr(TABLE_PREFIX.length()))); } - auto table_ref = sync::create_table(group, table_name); // throws - table = LangBindHelper::get_table(group, table_ref->get_index_in_group()); + table = sync::create_table(static_cast(group), table_name); // throws #else - table = LangBindHelper::add_table(group, table_name); // throws + table = group.add_table(table_name); // throws #endif - return reinterpret_cast(table); + return reinterpret_cast(new TableRef(table)); } catch (TableNameInUse& e) { // We need to print the table name, so catch the exception here. @@ -291,8 +278,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_table_name, jstring j_field_name, jboolean is_string_type, jboolean is_nullable) { - TR_ENTER_PTR(shared_realm_ptr) - std::string class_name_str; try { std::string table_name(JStringAccessor(env, j_table_name)); @@ -301,7 +286,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); shared_realm->verify_in_write(); // throws DataType pkType = is_string_type ? DataType::type_String : DataType::type_Int; - Table* table; + TableRef table; auto& group = shared_realm->read_group(); #if REALM_ENABLE_SYNC // Sync doesn't throw when table exists. @@ -309,16 +294,15 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, format(c_table_name_exists_exception_msg, class_name_str)); } - auto table_ref = - sync::create_table_with_primary_key(group, table_name, pkType, field_name, is_nullable); - table = LangBindHelper::get_table(group, table_ref->get_index_in_group()); + table = + sync::create_table_with_primary_key(static_cast(group), table_name, pkType, field_name, is_nullable); #else - table = LangBindHelper::add_table(group, table_name); - size_t column_idx = table->add_column(pkType, field_name, is_nullable); - table->add_search_index(column_idx); + table = group.add_table(table_name); + ColKey column_key = table->add_column(pkType, field_name, is_nullable); + table->add_search_index(column_key); + table->set_primary_key_column(column_key); #endif - ObjectStore::set_primary_key_for_object(group, class_name_str, field_name); - return reinterpret_cast(table); + return reinterpret_cast(new TableRef(table)); } catch (TableNameInUse& e) { // We need to print the table name, so catch the exception here. @@ -329,26 +313,37 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi return reinterpret_cast(nullptr); } -JNIEXPORT jstring JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTableName(JNIEnv* env, jclass, - jlong shared_realm_ptr, jint index) +JNIEXPORT jobjectArray JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTablesName(JNIEnv* env, jclass, + jlong shared_realm_ptr) { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - TR_ENTER_PTR(shared_realm_ptr) + auto& group = shared_realm->read_group(); - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - try { - return to_jstring(env, shared_realm->read_group().get_table_name(static_cast(index))); + auto keys = group.get_table_keys(); + if (!keys.empty()) { + size_t len = keys.size(); + jobjectArray table_names = env->NewObjectArray(len, JavaClassGlobalDef::java_lang_string(), 0); + + if (table_names == nullptr) { + ThrowException(env, OutOfMemory, "Could not allocate memory to return tables names"); + return nullptr; + } + + for (size_t i = 0; i < len; ++i) { + StringData name = group.get_table_name(keys[i]); + env->SetObjectArrayElement(table_names, i, to_jstring(env, name.data())); + } + + return table_names; } - CATCH_STD() - return NULL; + return nullptr; } JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeHasTable(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring table_name) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { JStringAccessor name(env, table_name); @@ -363,8 +358,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRenameTable(JN jstring old_table_name, jstring new_table_name) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { JStringAccessor old_name(env, old_table_name); @@ -382,8 +375,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRenameTable(JN JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeSize(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(shared_realm->read_group().size()); @@ -396,8 +387,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeSize(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeWriteCopy(JNIEnv* env, jclass, jlong shared_realm_ptr, jstring path, jbyteArray key) { - TR_ENTER_PTR(shared_realm_ptr); - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { JStringAccessor path_str(env, path); @@ -410,12 +399,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeWriteCopy(JNIE JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeWaitForChange(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr); - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { - using rf = realm::_impl::RealmFriend; - return static_cast(rf::get_shared_group(*shared_realm).wait_for_change()); + return static_cast(shared_realm->wait_for_change()); } CATCH_STD() @@ -425,12 +411,9 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeWaitForCha JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeStopWaitForChange(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr); - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { - using rf = realm::_impl::RealmFriend; - rf::get_shared_group(*shared_realm).wait_for_change_release(); + shared_realm->wait_for_change_release(); } CATCH_STD() } @@ -438,8 +421,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeStopWaitForCha JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeCompact(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr); - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); try { return static_cast(shared_realm->compact()); @@ -451,13 +432,11 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeCompact(JN static void finalize_shared_realm(jlong ptr) { - TR_ENTER_PTR(ptr) delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_shared_realm); } @@ -465,7 +444,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeSetAutoRefresh jlong shared_realm_ptr, jboolean enabled) { - TR_ENTER_PTR(shared_realm_ptr) try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); shared_realm->set_auto_refresh(to_bool(enabled)); @@ -476,7 +454,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeSetAutoRefresh JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsAutoRefresh(JNIEnv* env, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return to_jbool(shared_realm->auto_refresh()); @@ -488,8 +465,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsAutoRefr JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSchemaInfo(JNIEnv*, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) - // No throws auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return reinterpret_cast(&shared_realm->schema()); @@ -498,8 +473,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSchemaInfo JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterSchemaChangedCallback( JNIEnv* env, jclass, jlong shared_realm_ptr, jobject j_schema_changed_callback) { - TR_ENTER_PTR(shared_realm_ptr) - // No throws auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); JavaGlobalWeakRef callback_weak_ref(env, j_schema_changed_callback); @@ -514,7 +487,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterSchema JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetRealmPrivileges( JNIEnv*, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return static_cast(shared_realm->get_privileges()); } @@ -522,7 +494,6 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetRealmPrivil JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetClassPrivileges( JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_class_name) { - TR_ENTER_PTR(shared_realm_ptr) try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); JStringAccessor class_name(env, j_class_name); @@ -535,13 +506,11 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetClassPrivil JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetObjectPrivileges( JNIEnv* env, jclass, jlong shared_realm_ptr, jlong row_ptr) { - TR_ENTER_PTR(shared_realm_ptr) try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto r = reinterpret_cast(row_ptr); - RowExpr row = r->get_table()->get(r->get_index()); - - return static_cast(shared_realm->get_privileges(row)); + auto r = reinterpret_cast(row_ptr); + auto obj = r->get_table()->get_object(r->get_key()); + return static_cast(shared_realm->get_privileges(obj)); } CATCH_STD() return 0; @@ -550,8 +519,27 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetObjectPrivi JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsPartial(JNIEnv*, jclass, jlong shared_realm_ptr) { - TR_ENTER_PTR(shared_realm_ptr) // No throws auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); return to_jbool(shared_realm->is_partial()); } + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsFrozen(JNIEnv* env, jclass, jlong shared_realm_ptr) +{ + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return to_jbool(shared_realm->is_frozen()); + } + CATCH_STD() + return false; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeFreeze(JNIEnv* env, jclass, jlong shared_realm_ptr) +{ + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return reinterpret_cast(new SharedRealm(std::move(shared_realm->freeze()))); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp index 8362e7c559..efd57bce9b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp @@ -38,7 +38,6 @@ static_assert(io_realm_internal_Property_TYPE_ARRAY == static_cast(Propert static void finalize_property(jlong ptr) { - TR_ENTER_PTR(ptr); delete reinterpret_cast(ptr); } @@ -47,7 +46,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeCreatePersistedPro jboolean is_primary, jboolean is_indexed) { - TR_ENTER() try { JStringAccessor str(env, j_name_str); PropertyType p_type = static_cast(static_cast(type)); @@ -72,7 +70,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeCreatePersistedLin jint type, jstring j_target_class_name) { - TR_ENTER() try { JStringAccessor name(env, j_name_str); JStringAccessor link_name(env, j_target_class_name); @@ -88,7 +85,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeCreateComputedLink jstring j_source_class_name, jstring j_source_field_name) { - TR_ENTER() try { JStringAccessor name(env, j_name_str); JStringAccessor target_class_name(env, j_source_class_name); @@ -103,28 +99,24 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeCreateComputedLink JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_property); } JNIEXPORT jint JNICALL Java_io_realm_internal_Property_nativeGetType(JNIEnv*, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr); auto& property = *reinterpret_cast(native_ptr); return static_cast(property.type); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeGetColumnIndex(JNIEnv*, jclass, jlong native_ptr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeGetColumnKey(JNIEnv*, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr); auto& property = *reinterpret_cast(native_ptr); - return static_cast(property.table_column); + return static_cast(property.column_key.value); } JNIEXPORT jstring JNICALL Java_io_realm_internal_Property_nativeGetLinkedObjectName(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER_PTR(native_ptr); try { auto& property = *reinterpret_cast(native_ptr); std::string name = property.object_type; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 16cd4cad8f..eeffa27781 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -36,13 +36,6 @@ using namespace realm::util; static_assert(io_realm_internal_Table_MAX_STRING_SIZE == Table::max_string_size, ""); static_assert(io_realm_internal_Table_MAX_BINARY_SIZE == Table::max_binary_size, ""); -static const char* c_null_values_cannot_set_required_msg = "The primary key field '%1' has 'null' values stored. It " - "cannot be converted to a '@Required' primary key field."; -static const char* const PK_TABLE_NAME = "pk"; // ObjectStore::c_primaryKeyTableName -static const size_t CLASS_COLUMN_INDEX = 0; // ObjectStore::c_primaryKeyObjectClassColumnIndex -static const size_t FIELD_COLUMN_INDEX = 1; // ObjectStore::c_primaryKeyPropertyNameColumnIndex - - static void finalize_table(jlong ptr); inline static bool is_allowed_to_index(JNIEnv* env, DataType column_type) @@ -60,17 +53,9 @@ inline static bool is_allowed_to_index(JNIEnv* env, DataType column_type) // A spec is shared on subtables that are not in Mixed columns. // -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumn(JNIEnv* env, jobject, jlong nativeTablePtr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumn(JNIEnv* env, jobject, jlong nativeTableRefPtr, jint colType, jstring name, jboolean isNullable) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) { - return 0; - } - if (TBL(nativeTablePtr)->has_shared_type()) { - ThrowException(env, UnsupportedOperation, - "Not allowed to add field in subtable. Use getSubtableSchema() on root table instead."); - return 0; - } try { JStringAccessor name2(env, name); // throws bool is_column_nullable = to_bool(isNullable); @@ -78,7 +63,9 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumn(JNIEnv* env if (is_column_nullable && dataType == type_LinkList) { ThrowException(env, IllegalArgument, "List fields cannot be nullable."); } - return static_cast(TBL(nativeTablePtr)->add_column(dataType, name2, is_column_nullable)); + TableRef table = TBL_REF(nativeTableRefPtr); + ColKey col_key = table->add_column(dataType, name2, is_column_nullable); + return (jlong)(col_key.value); } CATCH_STD() return 0; @@ -88,449 +75,116 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddPrimitiveListColum jlong native_table_ptr, jint j_col_type, jstring j_name, jboolean j_is_nullable) { - if (!TABLE_VALID(env, TBL(native_table_ptr))) { - return 0; - } try { JStringAccessor name(env, j_name); // throws bool is_column_nullable = to_bool(j_is_nullable); DataType data_type = DataType(j_col_type); - Table* table = TBL(native_table_ptr); - size_t col = table->add_column(type_Table, name); - return table->get_subdescriptor(col)->add_column(data_type, ObjectStore::ArrayColumnName, nullptr, is_column_nullable); + TableRef table = TBL_REF(native_table_ptr); + return (jlong)(table->add_column_list(data_type, name, is_column_nullable).value); } CATCH_STD() return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumnLink(JNIEnv* env, jobject, jlong nativeTablePtr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumnLink(JNIEnv* env, jobject, jlong nativeTableRefPtr, jint colType, jstring name, - jlong targetTablePtr) + jlong targetTableRefPtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) { - return 0; - } - if (TBL(nativeTablePtr)->has_shared_type()) { - ThrowException(env, UnsupportedOperation, - "Not allowed to add field in subtable. Use getSubtableSchema() on root table instead."); - return 0; - } - if (!TBL(targetTablePtr)->is_group_level()) { + TableRef targetTableRef = TBL_REF(targetTableRefPtr); + if (!targetTableRef->is_group_level()) { ThrowException(env, UnsupportedOperation, "Links can only be made to toplevel tables."); return 0; } try { JStringAccessor name2(env, name); // throws - return static_cast(TBL(nativeTablePtr)->add_column_link(DataType(colType), name2, *TBL(targetTablePtr))); + TableRef table = TBL_REF(nativeTableRefPtr); + return static_cast(table->add_column_link(DataType(colType), name2, *targetTableRef).value); } CATCH_STD() return 0; } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveColumn(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveColumn(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey) { - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { - return; - } - if (TBL(nativeTablePtr)->has_shared_type()) { - ThrowException(env, UnsupportedOperation, - "Not allowed to remove field in subtable. Use getSubtableSchema() on root table instead."); - return; - } try { - TBL(nativeTablePtr)->remove_column(S(columnIndex)); + TableRef table = TBL_REF(nativeTableRefPtr); + table->remove_column(ColKey(columnKey)); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeInsertColumn(JNIEnv* env, jclass, jlong native_table_ptr, - jlong column_index, jint type, jstring j_name) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRenameColumn(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jstring name) { - auto table_ptr = reinterpret_cast(native_table_ptr); - if (!TABLE_VALID(env, table_ptr)) { - return; - } - try { - JStringAccessor name(env, j_name); // throws - - DataType data_type = DataType(type); - table_ptr->insert_column(column_index, data_type, name); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRenameColumn(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jstring name) -{ - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { - return; - } - if (TBL(nativeTablePtr)->has_shared_type()) { - ThrowException(env, UnsupportedOperation, - "Not allowed to rename field in subtable. Use getSubtableSchema() on root table instead."); - return; - } try { JStringAccessor name2(env, name); // throws - TBL(nativeTablePtr)->rename_column(S(columnIndex), name2); + TableRef table = TBL_REF(nativeTableRefPtr); + table->rename_column(ColKey(columnKey), name2); } CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable(JNIEnv* env, jobject, - jlong nativeTablePtr, - jlong columnIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsColumnNullable(JNIEnv*, jobject, + jlong nativeTableRefPtr, + jlong columnKey) { - Table* table = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, table, columnIndex)) { - return JNI_FALSE; - } - if (table->has_shared_type()) { - ThrowException(env, UnsupportedOperation, "Not allowed to convert field in subtable."); - return JNI_FALSE; - } - - if (table->get_column_type(S(columnIndex)) != type_Table) { - // for other than primitive list (including object, object list). - return to_jbool(table->is_nullable(S(columnIndex))); // noexcept - } - // For primitive list - return to_jbool(table->get_descriptor()->get_subdescriptor(S(columnIndex))->is_nullable(S(0))); // noexcept + TableRef table = TBL_REF(nativeTableRefPtr); + return to_jbool(table->is_nullable(ColKey(columnKey))); // noexcept } - -// General comments about the implementation of -// Java_io_realm_internal_Table_nativeConvertColumnToNullable and -// Java_io_realm_internal_Table_nativeConvertColumnToNotNullable -// -// 1. converting a (not-)nullable column is idempotent (and is implemented as a no-op) -// 2. not all column types can be converted (cannot be (not-)nullable) -// 3. converting to not-nullable, null values are converted to (core's) default values of the type -// 4. as temporary column is __inserted__ just before the column to be converted -// 4a. __TMP__number is used as name of the temporary column -// 4b. with N columns, at most N __TMP__i (0 <= i < N) must be tried, and while (true) { .. } will always terminate -// 4c. the temporary column will have index columnIndex (or column_index) -// 4d. the column to be converted will index shifted one place to column_index + 1 -// 5. search indexing must be preserved -// 6. removing the original column and renaming the temporary column will make it look like original is being modified -// -// WARNING: These methods do NOT work on primary key columns if the Realm is synchronized. -// - -// Converts a table to allow for nullable values -// Works on both normal table columns and sub tables -static void convert_column_to_nullable(JNIEnv* env, Table* old_table, size_t old_col_ndx, Table* new_table, size_t new_col_ndx) -{ - DataType column_type = old_table->get_column_type(old_col_ndx); - if (old_table != new_table) { - new_table->add_empty_row(old_table->size()); - } - for (size_t i = 0; i < old_table->size(); ++i) { - switch (column_type) { - case type_String: { - // Payload copy is needed - StringData sd(old_table->get_string(old_col_ndx, i)); - new_table->set_string(new_col_ndx, i, sd); - break; - } - case type_Binary: { - BinaryData bd = old_table->get_binary(old_col_ndx, i); - new_table->set_binary(new_col_ndx, i, BinaryData(bd.data(), bd.size())); - break; - } - case type_Int: - new_table->set_int(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); - break; - case type_Bool: - new_table->set_bool(new_col_ndx, i, old_table->get_bool(old_col_ndx, i)); - break; - case type_Timestamp: - new_table->set_timestamp(new_col_ndx, i, old_table->get_timestamp(old_col_ndx, i)); - break; - case type_Float: - new_table->set_float(new_col_ndx, i, old_table->get_float(old_col_ndx, i)); - break; - case type_Double: - new_table->set_double(new_col_ndx, i, old_table->get_double(old_col_ndx, i)); - break; - case type_Link: - case type_LinkList: - case type_Mixed: - case type_Table: - // checked previously - break; - case type_OldDateTime: - ThrowException(env, UnsupportedOperation, "The old DateTime type is not supported."); - return; - } - } -} - -// Creates the new column into which all old data is copied when switching between nullable and non-nullable. -static void create_new_column(Table* table, size_t column_index, bool nullable) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullable(JNIEnv* env, jobject, + jlong native_table_ref_ptr, + jlong j_column_key, + jboolean is_primary_key) { - std::string column_name = table->get_column_name(column_index); - DataType column_type = table->get_column_type(column_index); - bool is_subtable = table->get_column_type(column_index) == DataType::type_Table; - size_t j = 0; - while (true) { - std::ostringstream ss; - ss << std::string("__TMP__") << j; - std::string str = ss.str(); - StringData tmp_column_name(str); - if (table->get_column_index(tmp_column_name) == realm::not_found) { - if (is_subtable) { - DataType original_type = table->get_subdescriptor(column_index)->get_column_type(0); - table->insert_column(column_index, type_Table, tmp_column_name, true); - table->get_subdescriptor(column_index)->add_column(original_type, ObjectStore::ArrayColumnName, nullptr, nullable); - } - else { - table->insert_column(column_index, column_type, tmp_column_name, nullable); - } - break; - } - j++; - } - - // Search index has too be added first since if it is a PK field, add_xxx_unique will check it. - if (!is_subtable) { - // TODO indexes on sub tables not supported yet? - if (table->has_search_index(column_index + 1)) { - table->add_search_index(column_index); - } - } -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNullable(JNIEnv* env, jobject obj, - jlong native_table_ptr, - jlong j_column_index, - jboolean) -{ - Table* table = TBL(native_table_ptr); - if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { - return; - } try { - Table* table = TBL(native_table_ptr); - if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { - return; - } - if (table->has_shared_type()) { - ThrowException(env, UnsupportedOperation, "Not allowed to convert field in subtable."); - return; - } - - size_t column_index = S(j_column_index); - DataType column_type = table->get_column_type(column_index); - std::string column_name = table->get_column_name(column_index); - bool is_subtable = (column_type == DataType::type_Table); - - // Cannot convert Object links or lists of objects - if (column_type == type_Link || column_type == type_LinkList || column_type == type_Mixed) { - ThrowException(env, IllegalArgument, "Wrong type - cannot be converted to nullable."); - } - - // Exit quickly if column is already nullable - if (Java_io_realm_internal_Table_nativeIsColumnNullable(env, obj, native_table_ptr, j_column_index)) { - return; - } - - // 1. Create temporary table - create_new_column(table, column_index, true); - - // Move all values - if (is_subtable) { - for (size_t i = 0; i < table->size(); ++i) { - TableRef new_subtable = table->get_subtable(column_index, i); - TableRef old_subtable = table->get_subtable(column_index + 1, i); - convert_column_to_nullable(env, old_subtable.get(), 0, new_subtable.get(), 0); - } - } - else { - convert_column_to_nullable(env, table, column_index + 1, table, column_index); + TableRef table = TBL_REF(native_table_ref_ptr); + ColKey col_key(j_column_key); + bool nullable = true; + bool throw_on_value_conversion = false; + ColKey newCol = table->set_nullability(col_key, nullable, throw_on_value_conversion); + if (to_bool(is_primary_key)) { + table->set_primary_key_column(newCol); } - // Cleanup - table->remove_column(column_index + 1); - table->rename_column(column_index, column_name); - } CATCH_STD() } -// Convert a tables values to not nullable, but converting all null values to the defaul value for the type -// Works on both normal table columns and sub tables -static void convert_column_to_not_nullable(JNIEnv* env, Table* old_table, size_t old_col_ndx, Table* new_table, size_t new_col_ndx, bool is_primary_key) -{ - DataType column_type = old_table->get_column_type(old_col_ndx); - std::string column_name = old_table->get_column_name(old_col_ndx); - size_t no_rows = old_table->size(); - if (old_table != new_table) { - new_table->add_empty_row(no_rows); - } - for (size_t i = 0; i < no_rows; ++i) { - switch (column_type) { // FIXME: respect user-specified default values - case type_String: { - // Payload copy is needed - StringData sd = old_table->get_string(old_col_ndx, i); - if (sd == realm::null()) { - if (is_primary_key) { - THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, - format(c_null_values_cannot_set_required_msg, column_name)); - } - else { - new_table->set_string(new_col_ndx, i, ""); - } - } - else { - new_table->set_string(new_col_ndx, i, sd); - } - break; - } - case type_Binary: { - BinaryData bd = old_table->get_binary(old_col_ndx, i); - if (bd.is_null()) { - new_table->set_binary(new_col_ndx, i, BinaryData("", 0)); - } - else { - // Payload copy is needed - std::vector bd_copy(bd.data(), bd.data() + bd.size()); - new_table->set_binary(new_col_ndx, i, BinaryData(bd_copy.data(), bd_copy.size())); - } - break; - } - case type_Int: - if (old_table->is_null(old_col_ndx, i)) { - if (is_primary_key) { - THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, - format(c_null_values_cannot_set_required_msg, column_name)); - } - else { - new_table->set_int(new_col_ndx, i, 0); - } - } - else { - new_table->set_int(new_col_ndx, i, old_table->get_int(old_col_ndx, i)); - } - break; - case type_Bool: - if (old_table->is_null(old_col_ndx, i)) { - new_table->set_bool(new_col_ndx, i, false); - } - else { - new_table->set_bool(new_col_ndx, i, old_table->get_bool(old_col_ndx, i)); - } - break; - case type_Timestamp: - if (old_table->is_null(old_col_ndx, i)) { - new_table->set_timestamp(new_col_ndx, i, Timestamp(0, 0)); - } - else { - new_table->set_timestamp(new_col_ndx, i, old_table->get_timestamp(old_col_ndx, i)); - } - break; - case type_Float: - if (old_table->is_null(old_col_ndx, i)) { - new_table->set_float(new_col_ndx, i, 0.0); - } - else { - new_table->set_float(new_col_ndx, i, old_table->get_float(old_col_ndx, i)); - } - break; - case type_Double: - if (old_table->is_null(old_col_ndx, i)) { - new_table->set_double(new_col_ndx, i, 0.0); - } - else { - new_table->set_double(new_col_ndx, i, old_table->get_double(old_col_ndx, i)); - } - break; - case type_Link: - case type_LinkList: - case type_Mixed: - case type_Table: - // checked previously - break; - case type_OldDateTime: - // not used - ThrowException(env, UnsupportedOperation, "The old DateTime type is not supported."); - return; - } - } -} - - -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNullable(JNIEnv* env, jobject obj, - jlong native_table_ptr, - jlong j_column_index, +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeConvertColumnToNotNullable(JNIEnv* env, jobject, + jlong native_table_ref_ptr, + jlong j_column_key, jboolean is_primary_key) { try { - Table* table = TBL(native_table_ptr); - if (!TBL_AND_COL_INDEX_VALID(env, table, j_column_index)) { - return; - } - if (table->has_shared_type()) { - ThrowException(env, UnsupportedOperation, "Not allowed to convert field in subtable."); - return; + TableRef table = TBL_REF(native_table_ref_ptr); + ColKey col_key(j_column_key); + bool nullable = false; + bool throw_on_value_conversion = is_primary_key; + ColKey newCol = table->set_nullability(col_key, nullable, throw_on_value_conversion); + if (to_bool(is_primary_key)) { + table->set_primary_key_column(newCol); } - - // Exit quickly if column is already non-nullable - if (!Java_io_realm_internal_Table_nativeIsColumnNullable(env, obj, native_table_ptr, j_column_index)) { - return; - } - - size_t column_index = S(j_column_index); - std::string column_name = table->get_column_name(column_index); - DataType column_type = table->get_column_type(column_index); - bool is_subtable = (column_type == DataType::type_Table); - - if (column_type == type_Link || column_type == type_LinkList || column_type == type_Mixed) { - ThrowException(env, IllegalArgument, "Wrong type - cannot be converted to nullable."); - } - - // 1. Create temporary table - create_new_column(table, column_index, false); - - // 2. Move all values - if (is_subtable) { - for (size_t i = 0; i < table->size(); ++i) { - TableRef new_subtable = table->get_subtable(column_index, i); - TableRef old_subtable = table->get_subtable(column_index + 1, i); - convert_column_to_not_nullable(env, old_subtable.get(), 0, new_subtable.get(), 0, is_primary_key); - } - } - else { - convert_column_to_not_nullable(env, table, column_index + 1, table, column_index, is_primary_key); - } - - // 3. Delete old values - table->remove_column(column_index + 1); - table->rename_column(column_index, column_name); } CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSize(JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSize(JNIEnv*, jobject, jlong nativeTableRefPtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) { - return 0; - } - return static_cast(TBL(nativeTablePtr)->size()); // noexcept + TableRef table = TBL_REF(nativeTableRefPtr); + return static_cast(table->size()); // noexcept } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear(JNIEnv* env, jobject, jlong nativeTablePtr, jboolean is_partial_realm) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear(JNIEnv* env, jobject, jlong nativeTableRefPtr, jboolean is_partial_realm) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) { - return; - } try { + TableRef table = TBL_REF(nativeTableRefPtr); if (is_partial_realm) { - TBL(nativeTablePtr)->where().find_all().clear(RemoveMode::unordered); + table->where().find_all().clear(); } else { - TBL(nativeTablePtr)->clear(); + table->clear(); } } CATCH_STD() @@ -540,375 +194,378 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear(JNIEnv* env, job // -------------- Column information -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnCount(JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnCount(JNIEnv*, jobject, jlong nativeTableRefPtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) { - return 0; - } - return static_cast(TBL(nativeTablePtr)->get_column_count()); // noexcept + TableRef table = TBL_REF(nativeTableRefPtr); + return static_cast(table->get_column_count()); // noexcept } -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetColumnName(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) +JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetColumnName(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey) { - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { - return nullptr; + try { + TableRef table = TBL_REF(nativeTableRefPtr); + ColKey col_key(columnKey); + StringData stringData = table->get_column_name(col_key); + return to_jstring(env, stringData); } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jobjectArray JNICALL Java_io_realm_internal_Table_nativeGetColumnNames(JNIEnv* env, jobject, jlong nativeTableRefPtr) +{ try { - return to_jstring(env, TBL(nativeTablePtr)->get_column_name(S(columnIndex))); + TableRef table = TBL_REF(nativeTableRefPtr); + ColKeys col_keys = table->get_column_keys(); + size_t size = col_keys.size(); + jobjectArray col_keys_array = env->NewObjectArray(size, JavaClassGlobalDef::java_lang_string(), 0); + if (col_keys_array == NULL) { + ThrowException(env, OutOfMemory, "Could not allocate memory to return column names."); + return NULL; + } + for (size_t i = 0; i < size; ++i) { + env->SetObjectArrayElement(col_keys_array, i, to_jstring(env, table->get_column_name(col_keys[i]))); + } + + return col_keys_array; } CATCH_STD(); - REALM_UNREACHABLE(); + return NULL; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnIndex(JNIEnv* env, jobject, jlong nativeTablePtr, +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetColumnKey(JNIEnv* env, jobject, jlong nativeTableRefPtr, jstring columnName) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) { - return 0; - } try { JStringAccessor columnName2(env, columnName); // throws - return to_jlong_or_not_found(TBL(nativeTablePtr)->get_column_index(columnName2)); // noexcept + TableRef table = TBL_REF(nativeTableRefPtr); + ColKey col_key = table->get_column_key(columnName2); + if (table->valid_column(col_key)) { + return col_key.value; + } + return -1; } CATCH_STD() - return 0; + return -1; } -JNIEXPORT jint JNICALL Java_io_realm_internal_Table_nativeGetColumnType(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) +JNIEXPORT jint JNICALL Java_io_realm_internal_Table_nativeGetColumnType(JNIEnv*, jobject, jlong nativeTableRefPtr, + jlong columnKey) { - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { - return 0; + ColKey column_key (columnKey); + TableRef table = TBL_REF(nativeTableRefPtr); + jint column_type = table->get_column_type(column_key); + if (table->is_list(column_key) && column_type < type_LinkList) { + // add the offset so it can be mapped correctly in Java (RealmFieldType#fromNativeValue) + column_type += 128; } - auto column_type = TBL(nativeTablePtr)->get_column_type(S(columnIndex)); // noexcept - if (column_type != type_Table) { - // For other than primitive list (including object, object list). - return static_cast(column_type); - } + return column_type; // For primitive list // FIXME: Add test in https://github.com/realm/realm-java/pull/5221 before merging to master // FIXME: Add method in Object Store to return a PropertyType. - return static_cast(TBL(nativeTablePtr)->get_descriptor()->get_subdescriptor(S(columnIndex))->get_column_type(S(0)) - + io_realm_internal_Property_TYPE_ARRAY); // noexcept } // ---------------- Row handling -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMoveLastOver(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong rowIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMoveLastOver(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong rowKey) { - if (!TBL_AND_ROW_INDEX_VALID_OFFSET(env, TBL(nativeTablePtr), rowIndex, false)) { - return; - } try { - TBL(nativeTablePtr)->move_last_over(S(rowIndex)); + TableRef table = TBL_REF(nativeTableRefPtr); + table->remove_object(ObjKey(rowKey)); } CATCH_STD() } // ----------------- Get cell -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLong(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLong(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Int)) { return 0; } - return TBL(nativeTablePtr)->get_int(S(columnIndex), S(rowIndex)); // noexcept + return table->get_object(ObjKey(rowKey)).get(ColKey(columnKey)); // noexcept } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeGetBoolean(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeGetBoolean(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Bool)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Bool)) { return JNI_FALSE; } - return to_jbool(TBL(nativeTablePtr)->get_bool(S(columnIndex), S(rowIndex))); // noexcept + return to_jbool(table->get_object(ObjKey(rowKey)).get(ColKey(columnKey))); } -JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeGetFloat(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT jfloat JNICALL Java_io_realm_internal_Table_nativeGetFloat(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Float)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Float)) { return 0; } - return TBL(nativeTablePtr)->get_float(S(columnIndex), S(rowIndex)); // noexcept + return table->get_object(ObjKey(rowKey)).get(ColKey(columnKey)); } -JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeGetDouble(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT jdouble JNICALL Java_io_realm_internal_Table_nativeGetDouble(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Double)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Double)) { return 0; } - return TBL(nativeTablePtr)->get_double(S(columnIndex), S(rowIndex)); // noexcept + return table->get_object(ObjKey(rowKey)).get(ColKey(columnKey)); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetTimestamp(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetTimestamp(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Timestamp)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Timestamp)) { return 0; } try { - return to_milliseconds(TBL(nativeTablePtr)->get_timestamp(S(columnIndex), S(rowIndex))); + return to_milliseconds(table->get_object(ObjKey(rowKey)).get(ColKey(columnKey))); } CATCH_STD() return 0; } -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetString(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetString(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_String)) { return nullptr; } try { - return to_jstring(env, TBL(nativeTablePtr)->get_string(S(columnIndex), S(rowIndex))); + return to_jstring(env, table->get_object(ObjKey(rowKey)).get(ColKey(columnKey))); } CATCH_STD() return nullptr; } -/* -JNIEXPORT jobject JNICALL Java_io_realm_internal_Table_nativeGetByteBuffer( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex) -{ - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) - return NULL; - - BinaryData bin = TBL(nativeTablePtr)->get_binary( S(columnIndex), S(rowIndex)); - return env->NewDirectByteBuffer(const_cast(bin.data()), bin.size()); // throws -} -*/ - JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_Table_nativeGetByteArray(JNIEnv* env, jobject, - jlong nativeTablePtr, jlong columnIndex, - jlong rowIndex) + jlong nativeTableRefPtr, jlong columnKey, + jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Binary)) { return nullptr; } try { - realm::BinaryData bin = TBL(nativeTablePtr)->get_binary(S(columnIndex), S(rowIndex)); + realm::BinaryData bin = table->get_object(ObjKey(rowKey)).get(ColKey(columnKey)); return JavaClassGlobalDef::new_byte_array(env, bin); } CATCH_STD() return nullptr; } - -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLink(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLink(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Link)) { return 0; } - return static_cast(TBL(nativeTablePtr)->get_link(S(columnIndex), S(rowIndex))); // noexcept + return static_cast(table->get_object(ObjKey(rowKey)).get(ColKey(columnKey)).value); // noexcept } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLinkTarget(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetLinkTarget(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey) { try { - Table* pTable = &(*TBL(nativeTablePtr)->get_link_target(S(columnIndex))); - LangBindHelper::bind_table_ptr(pTable); - return reinterpret_cast(pTable); + TableRef table_ref = TBL_REF(nativeTableRefPtr); + return reinterpret_cast(new TableRef(table_ref->get_link_target(ColKey(columnKey)))); } CATCH_STD() return 0; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNull(JNIEnv*, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNull(JNIEnv*, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - return to_jbool(TBL(nativeTablePtr)->is_null(S(columnIndex), S(rowIndex))); // noexcept + TableRef table = TBL_REF(nativeTableRefPtr); + return to_jbool(table->get_object(ObjKey(rowKey)).is_null(ColKey(columnKey))); // noexcept } // ----------------- Set cell -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLink(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, - jlong targetRowIndex, jboolean isDefault) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLink(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, + jlong targetRowKey, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Link)) { return; } try { - TBL(nativeTablePtr)->set_link(S(columnIndex), S(rowIndex), S(targetRowIndex), B(isDefault)); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), ObjKey(targetRowKey), B(isDefault)); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLong(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, jlong value, +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetLong(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jlong value, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Int)) { return; } try { - TBL(nativeTablePtr)->set_int(S(columnIndex), S(rowIndex), value, B(isDefault)); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), value, B(isDefault)); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeIncrementLong(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, jlong value) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeIncrementLong(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jlong value) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Int)) { + + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Int)) { return; } try { - Table* table = TBL(nativeTablePtr); - if (table->is_null(columnIndex, rowIndex)) { + auto obj = table->get_object(ObjKey(rowKey)); + if (obj.is_null(ColKey(columnKey))) { THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, "Cannot increment a MutableRealmInteger whose value is null. Set its value first."); } - table->add_int(S(columnIndex), S(rowIndex), value); + obj.add_int(ColKey(columnKey), value); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetBoolean(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jboolean value, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Bool)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Bool)) { return; } try { - TBL(nativeTablePtr)->set_bool(S(columnIndex), S(rowIndex), B(value), B(isDefault)); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), B(value), B(isDefault)); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetFloat(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, jfloat value, +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetFloat(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jfloat value, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Float)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Float)) { return; } try { - TBL(nativeTablePtr)->set_float(S(columnIndex), S(rowIndex), value, B(isDefault)); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), value, B(isDefault)); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetDouble(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, jdouble value, +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetDouble(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jdouble value, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Double)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Double)) { return; } try { - TBL(nativeTablePtr)->set_double(S(columnIndex), S(rowIndex), value, B(isDefault)); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), value, B(isDefault)); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, jstring value, +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetString(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jstring value, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_String)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_String)) { return; } try { if (value == nullptr) { - if (!TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { + if (!COL_NULLABLE(env, table, columnKey)) { return; } } JStringAccessor value2(env, value); // throws - TBL(nativeTablePtr)->set_string(S(columnIndex), S(rowIndex), value2, B(isDefault)); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), StringData(value2), B(isDefault)); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetTimestamp(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jlong timestampValue, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Timestamp)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Timestamp)) { return; } try { - TBL(nativeTablePtr) - ->set_timestamp(S(columnIndex), S(rowIndex), from_milliseconds(timestampValue), B(isDefault)); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), from_milliseconds(timestampValue), B(isDefault)); } CATCH_STD() } -/* -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteBuffer( - JNIEnv* env, jobject, jlong nativeTablePtr, jlong columnIndex, jlong rowIndex, jobject byteBuffer) -{ - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) - return; - try { - tbl_nativeDoBinary(&Table::set_binary, TBL(nativeTablePtr), env, columnIndex, rowIndex, byteBuffer); - } CATCH_STD() -} -*/ - -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jbyteArray dataArray, jboolean isDefault) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Binary)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Binary)) { return; } try { - if (dataArray == nullptr && !TBL_AND_COL_NULLABLE(env, TBL(nativeTablePtr), columnIndex)) { + if (dataArray == nullptr && !COL_NULLABLE(env, table, columnKey)) { return; } JByteArrayAccessor jarray_accessor(env, dataArray); - TBL(nativeTablePtr) - ->set_binary(S(columnIndex), S(rowIndex), jarray_accessor.transform(), B(isDefault)); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), jarray_accessor.transform(), B(isDefault)); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex, +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jboolean isDefault) { - Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { - return; - } - if (!TBL_AND_ROW_INDEX_VALID(env, pTable, rowIndex)) { - return; - } - if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!COL_NULLABLE(env, table, columnKey)) { return; } try { - pTable->set_null(S(columnIndex), S(rowIndex), B(isDefault)); + table->get_object(ObjKey(rowKey)).set_null(ColKey(columnKey), B(isDefault)); } CATCH_STD() } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetRowPtr(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong index) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetRowPtr(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong key) { try { - Row* row = new Row((*TBL(nativeTablePtr))[S(index)]); - return reinterpret_cast(row); + TableRef table = TBL_REF(nativeTableRefPtr); + Obj* obj = new Obj(table->get_object(ObjKey(key))); + return reinterpret_cast(obj); } CATCH_STD() return reinterpret_cast(nullptr); @@ -916,141 +573,137 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetRowPtr(JNIEnv* env //--------------------- Indexing methods: -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeAddSearchIndex(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeAddSearchIndex(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey) { - Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { - return; - } - - DataType column_type = pTable->get_column_type(S(columnIndex)); + TableRef table = TBL_REF(nativeTableRefPtr); + ColKey colKey(columnKey); + DataType column_type = table->get_column_type(colKey); if (!is_allowed_to_index(env, column_type)) { return; } try { - pTable->add_search_index(S(columnIndex)); + table->add_search_index(colKey); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeRemoveSearchIndex(JNIEnv* env, jobject, - jlong nativeTablePtr, jlong columnIndex) + jlong nativeTableRefPtr, jlong columnKey) { - Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { - return; - } - DataType column_type = pTable->get_column_type(S(columnIndex)); + TableRef table = TBL_REF(nativeTableRefPtr); + DataType column_type = table->get_column_type(ColKey(columnKey)); if (!is_allowed_to_index(env, column_type)) { return; } try { - pTable->remove_search_index(S(columnIndex)); + table->remove_search_index(ColKey(columnKey)); } CATCH_STD() } JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSearchIndex(JNIEnv* env, jobject, - jlong nativeTablePtr, jlong columnIndex) + jlong nativeTableRefPtr, jlong columnKey) { - if (!TBL_AND_COL_INDEX_VALID(env, TBL(nativeTablePtr), columnIndex)) { - return JNI_FALSE; - } try { - return to_jbool(TBL(nativeTablePtr)->has_search_index(S(columnIndex))); + TableRef table = TBL_REF(nativeTableRefPtr); + return to_jbool(table->has_search_index(ColKey(columnKey))); } CATCH_STD() return JNI_FALSE; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNullLink(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsNullLink(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Link)) { return JNI_FALSE; } - return to_jbool(TBL(nativeTablePtr)->is_null_link(S(columnIndex), S(rowIndex))); + return to_jbool(table->get_object(ObjKey(rowKey)).is_null(ColKey(columnKey))); } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeNullifyLink(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong rowIndex) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeNullifyLink(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) { - if (!TBL_AND_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, rowIndex, type_Link)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Link)) { return; } try { - TBL(nativeTablePtr)->nullify_link(S(columnIndex), S(rowIndex)); + table->get_object(ObjKey(rowKey)).set_null(ColKey(columnKey)); } CATCH_STD() } //---------------------- Count -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountLong(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jlong value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountLong(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Int)) { return 0; } try { - return static_cast(TBL(nativeTablePtr)->count_int(S(columnIndex), value)); + return static_cast(table->count_int(ColKey(columnKey), value)); } CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountFloat(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jfloat value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountFloat(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jfloat value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Float)) { return 0; } try { - return static_cast(TBL(nativeTablePtr)->count_float(S(columnIndex), value)); + return static_cast(table->count_float(ColKey(columnKey), value)); } CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountDouble(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jdouble value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountDouble(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jdouble value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Double)) { return 0; } try { - return static_cast(TBL(nativeTablePtr)->count_double(S(columnIndex), value)); + return static_cast(table->count_double(ColKey(columnKey), value)); } CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountString(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jstring value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeCountString(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jstring value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_String)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_String)) { return 0; } try { JStringAccessor value2(env, value); // throws - return static_cast(TBL(nativeTablePtr)->count_string(S(columnIndex), value2)); + return static_cast(table->count_string(ColKey(columnKey), value2)); } CATCH_STD() return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeWhere(JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeWhere(JNIEnv* env, jobject, jlong nativeTableRefPtr) { - if (!TABLE_VALID(env, TBL(nativeTablePtr))) { - return 0; - } try { - Query* queryPtr = new Query(TBL(nativeTablePtr)->where()); + TableRef table = TBL_REF(nativeTableRefPtr); + Query* queryPtr = new Query(table->where()); return reinterpret_cast(queryPtr); } CATCH_STD() @@ -1059,100 +712,102 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeWhere(JNIEnv* env, jo //----------------------- FindFirst -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstInt(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jlong value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstInt(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Int)) { - return 0; + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Int)) { + return -1; } try { - return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_int(S(columnIndex), value)); + return to_jlong_or_not_found(table->find_first_int(ColKey(columnKey), value)); } CATCH_STD() - return 0; + return -1; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstBool(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jboolean value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstBool(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jboolean value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Bool)) { - return 0; + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Bool)) { + return -1; } try { - return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_bool(S(columnIndex), to_bool(value))); + return to_jlong_or_not_found(table->find_first_bool(ColKey(columnKey), to_bool(value))); } CATCH_STD() - return 0; + return -1; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstFloat(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jfloat value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstFloat(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jfloat value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Float)) { - return 0; + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Float)) { + return -1; } try { - return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_float(S(columnIndex), value)); + return to_jlong_or_not_found(table->find_first_float(ColKey(columnKey), value)); } CATCH_STD() - return 0; + return -1; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstDouble(JNIEnv* env, jobject, jlong nativeTablePtr, - jlong columnIndex, jdouble value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstDouble(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jdouble value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Double)) { - return 0; + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Double)) { + return -1; } try { - return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_double(S(columnIndex), value)); + return to_jlong_or_not_found(table->find_first_double(ColKey(columnKey), value)); } CATCH_STD() - return 0; + return -1; } JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstTimestamp(JNIEnv* env, jobject, - jlong nativeTablePtr, jlong columnIndex, + jlong nativeTableRefPtr, jlong columnKey, jlong dateTimeValue) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_Timestamp)) { - return 0; + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Timestamp)) { + return -1; } try { - size_t res = TBL(nativeTablePtr)->find_first_timestamp(S(columnIndex), from_milliseconds(dateTimeValue)); - return to_jlong_or_not_found(res); + return to_jlong_or_not_found(table->find_first_timestamp(ColKey(columnKey), from_milliseconds(dateTimeValue))); } CATCH_STD() - return 0; + return -1; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstString(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex, jstring value) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstString(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jstring value) { - if (!TBL_AND_COL_INDEX_AND_TYPE_VALID(env, TBL(nativeTablePtr), columnIndex, type_String)) { - return 0; + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_String)) { + return -1; } try { JStringAccessor value2(env, value); // throws - return to_jlong_or_not_found(TBL(nativeTablePtr)->find_first_string(S(columnIndex), value2)); + return to_jlong_or_not_found(table->find_first_string(ColKey(columnKey), value2)); } CATCH_STD() - return 0; + return -1; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull(JNIEnv* env, jclass, jlong nativeTablePtr, - jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey) { - Table* pTable = TBL(nativeTablePtr); - if (!TBL_AND_COL_INDEX_VALID(env, pTable, columnIndex)) { - return static_cast(realm::not_found); - } - if (!TBL_AND_COL_NULLABLE(env, pTable, columnIndex)) { + TableRef table = TBL_REF(nativeTableRefPtr); + if (!COL_NULLABLE(env, table, columnKey)) { return static_cast(realm::not_found); } try { - return to_jlong_or_not_found(pTable->find_first_null(S(columnIndex))); + return to_jlong_or_not_found(table->find_first_null(ColKey(columnKey))); } CATCH_STD() return static_cast(realm::not_found); @@ -1162,153 +817,52 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull(JNIEnv* // -JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetName(JNIEnv* env, jobject, jlong nativeTablePtr) +JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetName(JNIEnv* env, jobject, jlong nativeTableRefPtr) { try { - Table* table = TBL(nativeTablePtr); - if (!TABLE_VALID(env, table)) { + TableRef table = TBL_REF(nativeTableRefPtr); + // Mirror API in Java for now. Before Core 6 this would return null for tables not attached to the group. + if (table) { + return to_jstring(env, table->get_name()); + } else { return nullptr; } - return to_jstring(env, table->get_name()); } CATCH_STD() return nullptr; } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid(JNIEnv*, jobject, jlong nativeTablePtr) -{ - TR_ENTER_PTR(nativeTablePtr) - return to_jbool(TBL(nativeTablePtr)->is_attached()); // noexcept -} - -static bool pk_table_needs_migration(ConstTableRef pk_table) -{ - // Fix wrong types (string, int) -> (string, string) - if (pk_table->get_column_type(FIELD_COLUMN_INDEX) == type_Int) { - return true; - } - - // If needed remove "class_" prefix from class names - size_t number_of_rows = pk_table->size(); - for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { - StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); - if (table_name.begins_with(TABLE_PREFIX)) { - return true; - } - } - // From realm-java 2.0.0, pk table's class column requires a search index. - if (!pk_table->has_search_index(CLASS_COLUMN_INDEX)) { - return true; - } - return false; -} - -// 1) Fixes interop issue with Cocoa Realm where the Primary Key table had different types. -// This affects: -// - All Realms created by Cocoa and used by Realm-android up to 0.80.1 -// - All Realms created by Realm-Android 0.80.1 and below -// See https://github.com/realm/realm-java/issues/1059 -// -// 2) Fix interop issue with Cocoa Realm where primary key tables on Cocoa doesn't have the "class_" prefix. -// This affects: -// - All Realms created by Cocoa and used by Realm-android up to 0.84.1 -// - All Realms created by Realm-Android 0.84.1 and below -// See https://github.com/realm/realm-java/issues/1703 -// -// 3> PK table's column 'pk_table' needs search index in order to use set_string_unique. -// This affects: -// - All Realms created by Cocoa and used by Realm-java before 2.0.0 -// See https://github.com/realm/realm-java/pull/3488 - -// This methods converts the old (wrong) table format (string, integer) to the right (string,string) format and strips -// any class names in the col[0] of their "class_" prefix -static bool migrate_pk_table(const Group& group, TableRef pk_table) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid(JNIEnv*, jobject, jlong nativeTableRefPtr) { - bool changed = false; - - // Fix wrong types (string, int) -> (string, string) - if (pk_table->get_column_type(FIELD_COLUMN_INDEX) == type_Int) { - StringData tmp_col_name = StringData("tmp_field_name"); - size_t tmp_col_ndx = pk_table->add_column(DataType(type_String), tmp_col_name); - - // Create tmp string column with field name instead of column index - size_t number_of_rows = pk_table->size(); - for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { - StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); - size_t col_ndx = static_cast(pk_table->get_int(FIELD_COLUMN_INDEX, row_ndx)); - StringData col_name = group.get_table(table_name)->get_column_name(col_ndx); - // Make a copy of the string - pk_table->set_string(tmp_col_ndx, row_ndx, col_name); - } - - // Delete old int column, and rename tmp column to same name - // The column index for the renamed column will then be the same as the deleted old column - pk_table->remove_column(FIELD_COLUMN_INDEX); - pk_table->rename_column(pk_table->get_column_index(tmp_col_name), StringData("pk_property")); - changed = true; - } - - // If needed remove "class_" prefix from class names - size_t number_of_rows = pk_table->size(); - for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { - StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); - if (table_name.begins_with(TABLE_PREFIX)) { - // New string copy is needed, since the original memory will be changed. - std::string str(table_name.substr(TABLE_PREFIX.length())); - StringData sd(str); - pk_table->set_string(CLASS_COLUMN_INDEX, row_ndx, sd); - changed = true; - } - } - - // From realm-java 2.0.0, pk table's class column requires a search index. - if (!pk_table->has_search_index(CLASS_COLUMN_INDEX)) { - pk_table->add_search_index(CLASS_COLUMN_INDEX); - changed = true; - } - return changed; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded(JNIEnv* env, jclass, - jlong shared_realm_ptr) -{ - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *reinterpret_cast(shared_realm_ptr); - try { - if (!shared_realm->read_group().has_table(PK_TABLE_NAME)) { - return; - } - - auto pk_table = shared_realm->read_group().get_table(PK_TABLE_NAME); - if (!pk_table_needs_migration(pk_table)) { - return; - } - - shared_realm->begin_transaction(); - if (migrate_pk_table(shared_realm->read_group(), pk_table)) { - shared_realm->commit_transaction(); - } - else { - shared_realm->cancel_transaction(); - } + if(TBL_REF(nativeTableRefPtr)) { + return JNI_TRUE; + } else { + return JNI_FALSE; } - CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema(JNIEnv*, jobject, jlong thisTablePtr, - jlong otherTablePtr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema(JNIEnv*, jobject, jlong thisTableRefPtr, + jlong otherTableRefPtr) { - return to_jbool(*TBL(thisTablePtr)->get_descriptor() == *TBL(otherTablePtr)->get_descriptor()); + TableRef this_table = TBL_REF(thisTableRefPtr); + TableRef other_table = TBL_REF(otherTableRefPtr); + return to_jbool(this_table->get_key() == other_table->get_key()); } static void finalize_table(jlong ptr) { - TR_ENTER_PTR(ptr) - LangBindHelper::unbind_table_ptr(TBL(ptr)); + delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_table); } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFreeze(JNIEnv*, jclass, jlong j_frozen_shared_realm_ptr, jlong j_table_ptr) +{ + auto& shared_realm = *(reinterpret_cast(j_frozen_shared_realm_ptr)); + TableRef table = TableRef(TBL_REF(j_table_ptr)); + TableRef* frozen_table = new TableRef(shared_realm->transaction().import_copy_of(table)); + return reinterpret_cast(frozen_table); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 03e09dea91..28c7960ca4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -32,22 +32,9 @@ using namespace realm; using namespace realm::jni_util; using namespace realm::_impl; -#if 1 -#define QUERY_COL_TYPE_VALID(env, jPtr, col, type) query_col_type_valid(env, jPtr, col, type) -#else -#define QUERY_COL_TYPE_VALID(env, jPtr, col, type) (true) -#endif - static void finalize_table_query(jlong ptr); -inline bool query_col_type_valid(JNIEnv* env, jlong nativeQueryPtr, jlong colIndex, DataType type) -{ - return TBL_AND_COL_INDEX_AND_TYPE_VALID(env, Q(nativeQueryPtr)->get_table().get(), colIndex, type); -} - -const char* ERR_IMPORT_CLOSED_REALM = "Can not import results from a closed Realm"; -const char* ERR_SORT_NOT_SUPPORTED = "Sort is not supported on binary data, object references and RealmList"; //------------------------------------------------------- JNIEXPORT jstring JNICALL Java_io_realm_internal_TableQuery_nativeValidateQuery(JNIEnv* env, jobject, @@ -65,118 +52,114 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_TableQuery_nativeValidateQuery( // helper functions -// Return TableRef used for build link queries +// Return LinkChain used to build link queries // Each element in the indicesArray is the index of a column to be used to link to the next TableRef. // If the corresponding entry in tablesArray is anything other than a nullptr, the link is a backlink. // In that case, the tablesArray element is the pointer to the backlink source table and the // indicesArray entry is the source column index in the source table. -static TableRef getTableForLinkQuery(jlong nativeQueryPtr, const JLongArrayAccessor& tablesArray, - const JLongArrayAccessor& indicesArray) +static LinkChain getTableForLinkQuery(jlong nativeQueryPtr, const JLongArrayAccessor& tablesArray, + const JLongArrayAccessor& colKeysArray) { - auto table_ref = reinterpret_cast(nativeQueryPtr)->get_table(); - jsize link_element_count = indicesArray.size() - 1; + LinkChain linkChain(reinterpret_cast(nativeQueryPtr)->get_table()); + jsize link_element_count = colKeysArray.size() - 1; for (int i = 0; i < link_element_count; ++i) { - auto col_index = size_t(indicesArray[i]); - auto table_ptr = reinterpret_cast

            (tablesArray[i]); - if (table_ptr == nullptr) { - table_ref->link(col_index); + auto col_key = ColKey(colKeysArray[i]); + if (tablesArray[i]) { + TableRef linked_table_ref = TBL_REF(tablesArray[i]); + linkChain.backlink(*linked_table_ref, col_key); } else { - table_ref->backlink(*table_ptr, col_index); + linkChain.link(col_key); } } - return table_ref; + return linkChain; } // Return TableRef point to original table or the link table -static TableRef getTableByArray(jlong nativeQueryPtr, const JLongArrayAccessor& tablesArray, - const JLongArrayAccessor& indicesArray) +static ConstTableRef getTableByArray(jlong nativeQueryPtr, const JLongArrayAccessor& tablesArray, + const JLongArrayAccessor& colKeysArray) { - auto table_ref = reinterpret_cast(nativeQueryPtr)->get_table(); - jsize link_element_count = indicesArray.size() - 1; + ConstTableRef table_ref = reinterpret_cast(nativeQueryPtr)->get_table(); + jsize link_element_count = colKeysArray.size() - 1; for (int i = 0; i < link_element_count; ++i) { - auto table_ptr = reinterpret_cast
            (tablesArray[i]); - if (table_ptr == nullptr) { - table_ref = table_ref->get_link_target(static_cast(indicesArray[i])); + if (tablesArray[i]) { + table_ref = TBL_REF(tablesArray[i]); } else { - table_ref = TableRef(table_ptr); + table_ref = table_ref->get_link_target(ColKey(colKeysArray[i])); } } return table_ref; } // I am not at all sure that it is even the right idea, let alone correct code. --gbm -static bool isNullable(JNIEnv* env, Table* src_table_ptr, TableRef table_ref, jlong column_idx) +static bool isNullable(JNIEnv* env, ConstTableRef* src_table_ptr, ConstTableRef table_ref, jlong column_key) { // if table_arr is not a nullptr, this is a backlink and not allowed. - if (src_table_ptr != nullptr) { - ThrowException(env, IllegalArgument, "LinkingObject from field " + std::string(src_table_ptr->get_column_name(column_idx)) + " is not nullable."); - return false; - } - if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), column_idx)) { + if (src_table_ptr) { + ThrowException(env, IllegalArgument, "LinkingObject from field " + std::string((*(src_table_ptr))->get_column_name(ColKey(column_key))) + " is not nullable."); return false; } - return true; + return COL_NULLABLE(env, table_ref, column_key); } template -Query numeric_link_equal(TableRef tbl, jlong columnIndex, javatype value) +Query numeric_link_equal(LinkChain lc, jlong columnKey, javatype value) { - return tbl->column(size_t(columnIndex)) == cpptype(value); + return lc.column(ColKey(columnKey)) == cpptype(value); } template -Query numeric_link_notequal(TableRef tbl, jlong columnIndex, javatype value) +Query numeric_link_notequal(LinkChain lc, jlong columnIndex, javatype value) { - return tbl->column(size_t(columnIndex)) != cpptype(value); + return lc.column(ColKey(columnIndex)) != cpptype(value); } + template -Query numeric_link_greater(TableRef tbl, jlong columnIndex, javatype value) +Query numeric_link_greater(LinkChain lc, jlong columnIndex, javatype value) { - return tbl->column(size_t(columnIndex)) > cpptype(value); + return lc.column(ColKey(columnIndex)) > cpptype(value); } template -Query numeric_link_greaterequal(TableRef tbl, jlong columnIndex, javatype value) +Query numeric_link_greaterequal(LinkChain lc, jlong columnIndex, javatype value) { - return tbl->column(size_t(columnIndex)) >= cpptype(value); + return lc.column(ColKey(columnIndex)) >= cpptype(value); } template -Query numeric_link_less(TableRef tbl, jlong columnIndex, javatype value) +Query numeric_link_less(LinkChain lc, jlong columnIndex, javatype value) { - return tbl->column(size_t(columnIndex)) < cpptype(value); + return lc.column(ColKey(columnIndex)) < cpptype(value); } template -Query numeric_link_lessequal(TableRef tbl, jlong columnIndex, javatype value) +Query numeric_link_lessequal(LinkChain lc, jlong columnIndex, javatype value) { - return tbl->column(size_t(columnIndex)) <= cpptype(value); + return lc.column(ColKey(columnIndex)) <= cpptype(value); } // Integer - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - Q(nativeQueryPtr)->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], value)); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); + Q(nativeQueryPtr)->and_query(numeric_link_equal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -184,24 +167,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JJ(J JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->not_equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->not_equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_notequal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_notequal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -209,23 +192,23 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual_ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->greater(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greater(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_greater(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -233,46 +216,46 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JJ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->greater_equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater_equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greaterequal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_greaterequal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->less(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, index_arr[arr_len - 1], value)); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); + Q(nativeQueryPtr)->and_query(numeric_link_less(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -280,23 +263,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JJ(JN JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3JJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Int)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Int)) { return; } - Q(nativeQueryPtr)->less_equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less_equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_lessequal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_lessequal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -304,46 +287,49 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3 JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JJJ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jlong value1, + jlongArray columnKeys, jlong value1, jlong value2) { - JLongArrayAccessor arr(env, columnIndexes); + JLongArrayAccessor arr(env, columnKeys); jsize arr_len = arr.size(); - if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Int)) { - return; + try { + if (arr_len == 1) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), arr[0], type_Int)) { + return; + } + try { + Q(nativeQueryPtr) + ->between(ColKey(arr[0]), static_cast(value1), static_cast(value2)); + } + CATCH_STD() } - try { - Q(nativeQueryPtr)->between(S(arr[0]), static_cast(value1), static_cast(value2)); + else { + ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); } - CATCH_STD() - } - else { - ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); } + CATCH_STD() } // Float - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jfloat value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_equal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -351,24 +337,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JF(J JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jfloat value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->not_equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->not_equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_notequal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_notequal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -376,23 +362,23 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual_ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jfloat value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->greater(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greater(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_greater(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -400,46 +386,46 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JF JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jfloat value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->greater_equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater_equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greaterequal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_greaterequal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jfloat value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->less(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - Q(nativeQueryPtr)->and_query(numeric_link_less(table_ref, index_arr[arr_len - 1], value)); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); + Q(nativeQueryPtr)->and_query(numeric_link_less(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -447,24 +433,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JF(JN JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3JF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jfloat value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Float)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->less_equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less_equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_lessequal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_lessequal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -472,17 +458,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3 JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JFF(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jfloat value1, jfloat value2) { - JLongArrayAccessor arr(env, columnIndexes); - jsize arr_len = arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Float)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Float)) { return; } - Q(nativeQueryPtr)->between(S(arr[0]), static_cast(value1), static_cast(value2)); + Q(nativeQueryPtr)->between(ColKey(col_key_arr[0]), static_cast(value1), static_cast(value2)); } else { ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); @@ -493,26 +479,25 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JFF(J // Double - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jdouble value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_equal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -520,24 +505,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JD(J JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jdouble value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->not_equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->not_equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_notequal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_notequal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -545,23 +530,23 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual_ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jdouble value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->greater(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greater(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_greater(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -569,47 +554,47 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreater__J_3J_3JD JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqual__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jdouble value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->greater_equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->greater_equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greaterequal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_greaterequal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jdouble value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->less(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_less(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_less(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -617,24 +602,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLess__J_3J_3JD(JN JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3JD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jdouble value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Double)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->less_equal(S(index_arr[0]), static_cast(value)); + Q(nativeQueryPtr)->less_equal(ColKey(col_key_arr[0]), static_cast(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_lessequal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_lessequal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -642,17 +627,17 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqual__J_3J_3 JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JDD(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jdouble value1, jdouble value2) { - JLongArrayAccessor arr(env, columnIndexes); - jsize arr_len = arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Double)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Double)) { return; } - Q(nativeQueryPtr)->between(S(arr[0]), static_cast(value1), static_cast(value2)); + Q(nativeQueryPtr)->between(ColKey(col_key_arr[0]), static_cast(value1), static_cast(value2)); } else { ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); @@ -663,26 +648,25 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetween__J_3JDD(J // Timestamp - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->equal(S(index_arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], + ->and_query(numeric_link_equal(linkChain, col_key_arr[arr_len - 1], from_milliseconds(value))); } } @@ -691,24 +675,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualTimestamp(JN JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->not_equal(S(index_arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->not_equal(ColKey(col_key_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_notequal(table_ref, index_arr[arr_len - 1], + ->and_query(numeric_link_notequal(linkChain, col_key_arr[arr_len - 1], from_milliseconds(value))); } } @@ -717,23 +701,23 @@ JNIEXPORT void JNICALL JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualT JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->greater(S(index_arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->greater(ColKey(col_key_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greater(table_ref, index_arr[arr_len - 1], + ->and_query(numeric_link_greater(linkChain, col_key_arr[arr_len - 1], from_milliseconds(value))); } } @@ -742,24 +726,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterTimestamp( JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->greater_equal(S(index_arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->greater_equal(ColKey(col_key_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_greaterequal(table_ref, index_arr[arr_len - 1], + ->and_query(numeric_link_greaterequal(linkChain, col_key_arr[arr_len - 1], from_milliseconds(value))); } } @@ -768,23 +752,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualTimes JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->less(S(index_arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->less(ColKey(col_key_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_less(table_ref, index_arr[arr_len - 1], + ->and_query(numeric_link_less(linkChain, col_key_arr[arr_len - 1], from_milliseconds(value))); } } @@ -793,24 +777,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessTimestamp(JNI JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jlong value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Timestamp)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Timestamp)) { return; } - Q(nativeQueryPtr)->less_equal(S(index_arr[0]), from_milliseconds(value)); + Q(nativeQueryPtr)->less_equal(ColKey(col_key_arr[0]), from_milliseconds(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_lessequal(table_ref, index_arr[arr_len - 1], + ->and_query(numeric_link_lessequal(linkChain, col_key_arr[arr_len - 1], from_milliseconds(value))); } } @@ -819,19 +803,19 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualTimestam JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlong value1, jlong value2) { - JLongArrayAccessor arr(env, columnIndexes); - jsize arr_len = arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, arr[0], type_Timestamp)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Timestamp)) { return; } Q(nativeQueryPtr) - ->greater_equal(S(arr[0]), from_milliseconds(value1)) - .less_equal(S(arr[0]), from_milliseconds(value2)); + ->greater_equal(ColKey(col_key_arr[0]), from_milliseconds(value1)) + .less_equal(ColKey(col_key_arr[0]), from_milliseconds(value2)); } else { ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); @@ -841,26 +825,25 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenTimestamp( } // Bool - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JZ(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jboolean value) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Bool)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Bool)) { return; } - Q(nativeQueryPtr)->equal(S(index_arr[0]), to_bool(value)); + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), to_bool(value)); } else { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); Q(nativeQueryPtr) - ->and_query(numeric_link_equal(table_ref, index_arr[arr_len - 1], value)); + ->and_query(numeric_link_equal(linkChain, col_key_arr[arr_len - 1], value)); } } CATCH_STD() @@ -870,45 +853,45 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JZ(J enum StringPredicate { StringEqual, StringNotEqual, StringContains, StringBeginsWith, StringEndsWith, StringLike }; - -static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnIndexes, +static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnKeys, jlongArray tablePointers, jstring value, jboolean caseSensitive, StringPredicate predicate) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); try { - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); if (value == NULL) { - if (!TBL_AND_COL_NULLABLE(env, table_ref.get(), index_arr[arr_len - 1])) { + if (!COL_NULLABLE(env, linkChain.get_base_table(), col_key_arr[arr_len - 1])) { return; } } bool is_case_sensitive = to_bool(caseSensitive); JStringAccessor value2(env, value); // throws if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_String)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_String)) { return; } switch (predicate) { - case StringEqual: - Q(nativeQueryPtr)->equal(S(index_arr[0]), value2, is_case_sensitive); + case StringEqual:{ + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), value2, is_case_sensitive); break; + } case StringNotEqual: - Q(nativeQueryPtr)->not_equal(S(index_arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->not_equal(ColKey(col_key_arr[0]), value2, is_case_sensitive); break; case StringContains: - Q(nativeQueryPtr)->contains(S(index_arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->contains(ColKey(col_key_arr[0]), value2, is_case_sensitive); break; case StringBeginsWith: - Q(nativeQueryPtr)->begins_with(S(index_arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->begins_with(ColKey(col_key_arr[0]), value2, is_case_sensitive); break; case StringEndsWith: - Q(nativeQueryPtr)->ends_with(S(index_arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->ends_with(ColKey(col_key_arr[0]), value2, is_case_sensitive); break; case StringLike: - Q(nativeQueryPtr)->like(S(index_arr[0]), value2, is_case_sensitive); + Q(nativeQueryPtr)->like(ColKey(col_key_arr[0]), value2, is_case_sensitive); break; } } @@ -916,32 +899,32 @@ static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA switch (predicate) { case StringEqual: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) .equal(StringData(value2), is_case_sensitive)); break; case StringNotEqual: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) .not_equal(StringData(value2), is_case_sensitive)); break; case StringContains: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) .contains(StringData(value2), is_case_sensitive)); break; case StringBeginsWith: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) .begins_with(StringData(value2), is_case_sensitive)); break; case StringEndsWith: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) .ends_with(StringData(value2), is_case_sensitive)); break; case StringLike: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) .like(StringData(value2), is_case_sensitive)); break; } @@ -951,79 +934,78 @@ static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JLjava_lang_String_2Z( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnKeys, jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringEqual); + TableQuery_StringPredicate(env, nativeQueryPtr, columnKeys, tablePointers, value, caseSensitive, StringEqual); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3JLjava_lang_String_2Z( - JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnIndexes, + JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnKeys, jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringNotEqual); + TableQuery_StringPredicate(env, nativeQueryPtr, columnKeys, tablePointers, value, caseSensitive, StringNotEqual); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBeginsWith(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringBeginsWith); + TableQuery_StringPredicate(env, nativeQueryPtr, columnKeys, tablePointers, value, caseSensitive, StringBeginsWith); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndsWith(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringEndsWith); + TableQuery_StringPredicate(env, nativeQueryPtr, columnKeys, tablePointers, value, caseSensitive, StringEndsWith); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLike(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringLike); + TableQuery_StringPredicate(env, nativeQueryPtr, columnKeys, tablePointers, value, caseSensitive, StringLike); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeContains(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers, jstring value, jboolean caseSensitive) { - TableQuery_StringPredicate(env, nativeQueryPtr, columnIndexes, tablePointers, value, caseSensitive, StringContains); + TableQuery_StringPredicate(env, nativeQueryPtr, columnKeys, tablePointers, value, caseSensitive, StringContains); } // Binary enum BinaryPredicate { BinaryEqual, BinaryNotEqual }; - -static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnIndexes, +static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnKeys, jlongArray tablePointers, jbyteArray value, BinaryPredicate predicate) { try { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); - TableRef table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); - if (value == NULL && !TBL_AND_COL_NULLABLE(env, table_ref.get(), index_arr[arr_len - 1])) { + if (value == NULL && !COL_NULLABLE(env, linkChain.get_base_table(), col_key_arr[arr_len - 1])) { return; } JByteArrayAccessor jarray_accessor(env, value); if (arr_len == 1) { - if (!QUERY_COL_TYPE_VALID(env, nativeQueryPtr, index_arr[0], type_Binary)) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Binary)) { return; } switch (predicate) { case BinaryEqual: - Q(nativeQueryPtr)->equal(S(index_arr[0]), jarray_accessor.transform()); + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), jarray_accessor.transform()); break; case BinaryNotEqual: - Q(nativeQueryPtr)->not_equal(S(index_arr[0]), jarray_accessor.transform()); + Q(nativeQueryPtr)->not_equal(ColKey(col_key_arr[0]), jarray_accessor.transform()); break; } } @@ -1031,12 +1013,12 @@ static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA switch (predicate) { case BinaryEqual: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) == + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) == jarray_accessor.transform()); break; case BinaryNotEqual: Q(nativeQueryPtr) - ->and_query(table_ref->column(size_t(index_arr[arr_len - 1])) != + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) != jarray_accessor.transform()); break; } @@ -1047,20 +1029,20 @@ static void TableQuery_BinaryPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3J_3B(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndices, + jlongArray columnKeys, jlongArray tablePointers, jbyteArray value) { - TableQuery_BinaryPredicate(env, nativeQueryPtr, columnIndices, tablePointers, value, BinaryEqual); + TableQuery_BinaryPredicate(env, nativeQueryPtr, columnKeys, tablePointers, value, BinaryEqual); } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3J_3B(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndices, + jlongArray columnKeys, jlongArray tablePointers, jbyteArray value) { - TableQuery_BinaryPredicate(env, nativeQueryPtr, columnIndices, tablePointers, value, BinaryNotEqual); + TableQuery_BinaryPredicate(env, nativeQueryPtr, columnKeys, tablePointers, value, BinaryNotEqual); } // General ---------------------------------------------------- @@ -1072,9 +1054,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqual__J_3J_3J JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGroup(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) { - return; - } try { pQuery->group(); } @@ -1084,9 +1063,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGroup(JNIEnv* env JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEndGroup(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) { - return; - } try { pQuery->end_group(); } @@ -1097,9 +1073,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeOr(JNIEnv* env, j { // No verification of parameters needed? Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) { - return; - } try { pQuery->Or(); } @@ -1109,9 +1082,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeOr(JNIEnv* env, j JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNot(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) { - return; - } try { pQuery->Not(); } @@ -1120,42 +1090,13 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNot(JNIEnv* env, // Find -------------------------------------- - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFind(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong fromTableRow) +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFind(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery)) { - return -1; - } - // It's valid to go 1 past the end index - if ((fromTableRow < 0) || (S(fromTableRow) > pTable->size())) { - // below check will fail with appropriate exception - (void)ROW_INDEX_VALID(env, pTable, fromTableRow); - return -1; - } - + ConstTableRef pTable = pQuery->get_table(); try { - size_t r = pQuery->find(S(fromTableRow)); - return (r == not_found) ? jlong(-1) : jlong(r); - } - CATCH_STD() - return -1; -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong start, jlong end, jlong limit) -{ - TR_ENTER() - Query* query = Q(nativeQueryPtr); - TableRef table = query->get_table(); - if (!QUERY_VALID(env, query) || !ROW_INDEXES_VALID(env, table.get(), start, end, limit)) { - return -1; - } - try { - TableView* tableView = new TableView(query->find_all(S(start), S(end), S(limit))); - return reinterpret_cast(tableView); + auto r = pQuery->find(); + return to_jlong_or_not_found(r); } CATCH_STD() return -1; @@ -1164,57 +1105,52 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFindAll(JNIEnv* // Integer Aggregates JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeSumInt(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, - jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Int)) { return 0; } try { - return pQuery->sum_int(S(columnIndex), NULL, S(start), S(end), S(limit)); + return pQuery->sum_int(ColKey(columnKey)); } CATCH_STD() return 0; } JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumInt(JNIEnv* env, jobject, - jlong nativeQueryPtr, jlong columnIndex, - jlong start, jlong end, jlong limit) + jlong nativeQueryPtr, jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Int)) { return nullptr; } try { - size_t return_ndx; - int64_t result = pQuery->maximum_int(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); - if (return_ndx != npos) { + ObjKey return_ndx; + int64_t result = pQuery->maximum_int(ColKey(columnKey), &return_ndx); + if (bool(return_ndx)) { return JavaClassGlobalDef::new_long(env, result); } + return 0; } CATCH_STD() return nullptr; } JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumInt(JNIEnv* env, jobject, - jlong nativeQueryPtr, jlong columnIndex, - jlong start, jlong end, jlong limit) + jlong nativeQueryPtr, jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Int)) { return nullptr; } try { - size_t return_ndx; - int64_t result = pQuery->minimum_int(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); - if (return_ndx != npos) { + ObjKey return_ndx; + int64_t result = pQuery->minimum_int(ColKey(columnKey), &return_ndx); + if (bool(return_ndx)) { return JavaClassGlobalDef::new_long(env, result); } } @@ -1223,20 +1159,15 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumInt(JNI } JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageInt(JNIEnv* env, jobject, - jlong nativeQueryPtr, jlong columnIndex, - jlong start, jlong end, jlong limit) + jlong nativeQueryPtr, jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Int) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Int)) { return 0; } try { - size_t resultcount; - // TODO: return resultcount? - double avg = pQuery->average_int(S(columnIndex), &resultcount, S(start), S(end), S(limit)); - // fprintf(stderr, "!!!Average(%d, %d) = %f (%d results)\n", start, end, avg, resultcount); fflush(stderr); + double avg = pQuery->average_int(ColKey(columnKey)); return avg; } CATCH_STD() @@ -1247,17 +1178,15 @@ JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageInt(JNI // float Aggregates JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumFloat(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, jlong end, - jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Float)) { return 0; } try { - return pQuery->sum_float(S(columnIndex), NULL, S(start), S(end), S(limit)); + return pQuery->sum_float(ColKey(columnKey)); } CATCH_STD() return 0; @@ -1265,19 +1194,17 @@ JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumFloat(JNIEn JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumFloat(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, - jlong end, jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Float)) { return nullptr; } try { - size_t return_ndx; - float result = pQuery->maximum_float(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); - if (return_ndx != npos) { + ObjKey return_ndx; + float result = pQuery->maximum_float(ColKey(columnKey), &return_ndx); + if (bool(return_ndx)) { return JavaClassGlobalDef::new_float(env, result); } } @@ -1287,19 +1214,17 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumFloat(J JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumFloat(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, - jlong end, jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Float)) { return nullptr; } try { - size_t return_ndx; - float result = pQuery->minimum_float(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); - if (return_ndx != npos) { + ObjKey return_ndx; + float result = pQuery->minimum_float(ColKey(columnKey), &return_ndx); + if (bool(return_ndx)) { return JavaClassGlobalDef::new_float(env, result); } } @@ -1309,19 +1234,15 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumFloat(J JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageFloat(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, - jlong end, jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Float) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Float)) { return 0; } try { - size_t resultcount; - double avg = pQuery->average_float(S(columnIndex), &resultcount, S(start), S(end), S(limit)); - return avg; + return pQuery->average_float(ColKey(columnKey)); } CATCH_STD() return 0; @@ -1330,17 +1251,15 @@ JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageFloat(J // double Aggregates JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumDouble(JNIEnv* env, jobject, - jlong nativeQueryPtr, jlong columnIndex, - jlong start, jlong end, jlong limit) + jlong nativeQueryPtr, jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Double)) { return 0; } try { - return pQuery->sum_double(S(columnIndex), NULL, S(start), S(end), S(limit)); + return pQuery->sum_double(ColKey(columnKey)); } CATCH_STD() return 0; @@ -1348,19 +1267,17 @@ JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeSumDouble(JNIE JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumDouble(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, - jlong end, jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Double)) { return nullptr; } try { - size_t return_ndx; - double result = pQuery->maximum_double(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); - if (return_ndx != npos) { + ObjKey return_ndx; + double result = pQuery->maximum_double(ColKey(columnKey), &return_ndx); + if (bool(return_ndx)) { return JavaClassGlobalDef::new_double(env, result); } } @@ -1370,19 +1287,17 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumDouble( JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumDouble(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, - jlong end, jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Double)) { return nullptr; } try { - size_t return_ndx; - double result = pQuery->minimum_double(S(columnIndex), NULL, S(start), S(end), S(limit), &return_ndx); - if (return_ndx != npos) { + ObjKey return_ndx; + double result = pQuery->minimum_double(ColKey(columnKey), &return_ndx); + if (bool(return_ndx)) { return JavaClassGlobalDef::new_double(env, result); } } @@ -1392,20 +1307,15 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumDouble( JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageDouble(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, - jlong end, jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Double) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Double)) { return 0; } try { - // TODO: Return resultcount - size_t resultcount; - double avg = pQuery->average_double(S(columnIndex), &resultcount, S(start), S(end), S(limit)); - return avg; + return pQuery->average_double(ColKey(columnKey)); } CATCH_STD() return 0; @@ -1416,19 +1326,17 @@ JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageDouble( // FIXME: This is a rough workaround while waiting for https://github.com/realm/realm-core/issues/1745 to be solved JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, - jlong end, jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Timestamp) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Timestamp)) { return nullptr; } try { - size_t return_ndx; - Timestamp result = pQuery->find_all().maximum_timestamp(S(columnIndex), &return_ndx); - if (return_ndx != npos && !result.is_null()) { + ObjKey return_ndx; + Timestamp result = pQuery->find_all().maximum_timestamp(ColKey(columnKey), &return_ndx); + if (bool(return_ndx) && !result.is_null()) { return JavaClassGlobalDef::new_long(env, to_milliseconds(result)); } } @@ -1438,19 +1346,17 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumTimesta JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumTimestamp(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong columnIndex, jlong start, - jlong end, jlong limit) + jlong columnKey) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !COL_INDEX_AND_TYPE_VALID(env, pTable, columnIndex, type_Timestamp) || - !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Timestamp)) { return nullptr; } try { - size_t return_ndx; - Timestamp result = pQuery->find_all().minimum_timestamp(S(columnIndex), &return_ndx); - if (return_ndx != npos && !result.is_null()) { + ObjKey return_ndx; + Timestamp result = pQuery->find_all().minimum_timestamp(ColKey(columnKey), &return_ndx); + if (bool(return_ndx) && !result.is_null()) { return JavaClassGlobalDef::new_long(env, to_milliseconds(result)); } } @@ -1460,16 +1366,11 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumTimesta // Count, Remove -JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeCount(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlong start, jlong end, jlong limit) +JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeCount(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - Table* pTable = pQuery->get_table().get(); - if (!QUERY_VALID(env, pQuery) || !ROW_INDEXES_VALID(env, pTable, start, end, limit)) { - return 0; - } try { - return static_cast(pQuery->count(S(start), S(end), S(limit))); + return static_cast(pQuery->count()); } CATCH_STD() return 0; @@ -1478,9 +1379,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeCount(JNIEnv* en JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeRemove(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - if (!QUERY_VALID(env, pQuery)) { - return 0; - } try { return static_cast(pQuery->remove()); } @@ -1489,37 +1387,36 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeRemove(JNIEnv* e } // isNull and isNotNull - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers) { try { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); auto pQuery = reinterpret_cast(nativeQueryPtr); - jlong column_idx = index_arr[arr_len - 1]; + jlong column_idx = col_key_arr[arr_len - 1]; - TableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, index_arr); - if (!isNullable(env, reinterpret_cast
            (table_arr[arr_len - 1]), table_ref, column_idx)) { + ConstTableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, col_key_arr); + if (!isNullable(env, reinterpret_cast(table_arr[arr_len - 1]), table_ref, column_idx)) { return; } - TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - DataType col_type = table_ref->get_column_type(S(column_idx)); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); + DataType col_type = table_ref->get_column_type(ColKey(column_idx)); if (arr_len == 1) { switch (col_type) { case type_Link: - pQuery->and_query(src_table_ref->column(S(column_idx)).is_null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)).is_null()); break; case type_LinkList: // Cannot get here. Exception will be thrown in TBL_AND_COL_NULLABLE ThrowException(env, FatalError, "This is not reachable."); break; case type_Binary: - pQuery->equal(S(column_idx), BinaryData()); + pQuery->equal(ColKey(column_idx), BinaryData()); break; case type_String: case type_Bool: @@ -1527,7 +1424,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en case type_Float: case type_Double: case type_Timestamp: - Q(nativeQueryPtr)->equal(S(column_idx), realm::null()); + Q(nativeQueryPtr)->equal(ColKey(column_idx), realm::null()); break; default: REALM_UNREACHABLE(); @@ -1543,25 +1440,25 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en ThrowException(env, FatalError, "This is not reachable."); break; case type_String: - pQuery->and_query(src_table_ref->column(S(column_idx)) == realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); break; case type_Binary: - pQuery->and_query(src_table_ref->column(S(column_idx)) == BinaryData()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) == BinaryData()); break; case type_Bool: - pQuery->and_query(src_table_ref->column(S(column_idx)) == realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); break; case type_Int: - pQuery->and_query(src_table_ref->column(S(column_idx)) == realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); break; case type_Float: - pQuery->and_query(src_table_ref->column(S(column_idx)) == realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); break; case type_Double: - pQuery->and_query(src_table_ref->column(S(column_idx)) == realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); break; case type_Timestamp: - pQuery->and_query(src_table_ref->column(S(column_idx)) == realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); break; default: REALM_UNREACHABLE(); @@ -1570,37 +1467,36 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en } CATCH_STD() } - JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); Query* pQuery = Q(nativeQueryPtr); try { - jlong column_idx = index_arr[arr_len - 1]; + jlong column_idx = col_key_arr[arr_len - 1]; - TableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, index_arr); - if (!isNullable(env, TBL(table_arr[arr_len - 1]), table_ref, column_idx)) { + ConstTableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, col_key_arr); + if (!isNullable(env, reinterpret_cast(table_arr[arr_len - 1]), table_ref, column_idx)) { return; } - TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); - DataType col_type = table_ref->get_column_type(S(column_idx)); + DataType col_type = table_ref->get_column_type(ColKey(column_idx)); if (arr_len == 1) { switch (col_type) { case type_Link: - pQuery->and_query(src_table_ref->column(S(column_idx)).is_not_null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)).is_not_null()); break; case type_LinkList: // Cannot get here. Exception will be thrown in TBL_AND_COL_NULLABLE ThrowException(env, FatalError, "This is not reachable."); break; case type_Binary: - pQuery->not_equal(S(column_idx), realm::BinaryData()); + pQuery->not_equal(ColKey(column_idx), realm::BinaryData()); break; case type_String: case type_Bool: @@ -1608,7 +1504,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* case type_Float: case type_Double: case type_Timestamp: - pQuery->not_equal(S(column_idx), realm::null()); + pQuery->not_equal(ColKey(column_idx), realm::null()); break; default: REALM_UNREACHABLE(); @@ -1625,25 +1521,25 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* ThrowException(env, FatalError, "This is not reachable."); break; case type_String: - pQuery->and_query(src_table_ref->column(S(column_idx)) != realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); break; case type_Binary: - pQuery->and_query(src_table_ref->column(S(column_idx)) != realm::BinaryData()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::BinaryData()); break; case type_Bool: - pQuery->and_query(src_table_ref->column(S(column_idx)) != realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); break; case type_Int: - pQuery->and_query(src_table_ref->column(S(column_idx)) != realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); break; case type_Float: - pQuery->and_query(src_table_ref->column(S(column_idx)) != realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); break; case type_Double: - pQuery->and_query(src_table_ref->column(S(column_idx)) != realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); break; case type_Timestamp: - pQuery->and_query(src_table_ref->column(S(column_idx)) != realm::null()); + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); break; default: REALM_UNREACHABLE(); @@ -1654,25 +1550,24 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* } JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, + jlongArray columnKeys, jlongArray tablePointers) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_arr(env, columnKeys); + jsize arr_len = col_arr.size(); Query* pQuery = reinterpret_cast(nativeQueryPtr); try { - TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - auto column_idx = static_cast(index_arr[arr_len - 1]); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_arr); + ColKey column_idx = ColKey(col_arr[arr_len - 1]); // Support a backlink as the last column in a field descriptor - auto last = reinterpret_cast(table_arr[arr_len-1]); - if (last != nullptr) { - pQuery->and_query(src_table_ref->column(*last, column_idx).count() == 0); + if (table_arr[arr_len-1]) { + pQuery->and_query(linkChain.column(*TBL_REF(table_arr[arr_len-1]), column_idx).count() == 0); return; } - TableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, index_arr); + ConstTableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, col_arr); DataType col_type = table_ref->get_column_type(column_idx); if (arr_len == 1) { // Field queries @@ -1681,7 +1576,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e pQuery->equal(column_idx, BinaryData("", 0)); break; case type_LinkList: - pQuery->and_query(src_table_ref->column(column_idx).count() == 0); + pQuery->and_query(linkChain.column(column_idx).count() == 0); break; case type_String: pQuery->equal(column_idx, ""); @@ -1701,13 +1596,13 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e // Linked queries switch (col_type) { case type_Binary: - pQuery->and_query(src_table_ref->column(column_idx) == BinaryData("", 0)); + pQuery->and_query(linkChain.column(column_idx) == BinaryData("", 0)); break; case type_LinkList: - pQuery->and_query(src_table_ref->column(column_idx).count() == 0); + pQuery->and_query(linkChain.column(column_idx).count() == 0); break; case type_String: - pQuery->and_query(src_table_ref->column(column_idx) == ""); + pQuery->and_query(linkChain.column(column_idx) == ""); break; case type_Link: case type_Bool: @@ -1727,23 +1622,22 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsEmpty(JNIEnv* e JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotEmpty(JNIEnv *env, jobject, jlong nativeQueryPtr, - jlongArray columnIndexes, jlongArray tablePointers) { + jlongArray columnKeys, jlongArray tablePointers) { JLongArrayAccessor table_arr(env, tablePointers); - JLongArrayAccessor index_arr(env, columnIndexes); - jsize arr_len = index_arr.size(); + JLongArrayAccessor col_arr(env, columnKeys); + jsize arr_len = col_arr.size(); Query* pQuery = reinterpret_cast(nativeQueryPtr); try { - TableRef src_table_ref = getTableForLinkQuery(nativeQueryPtr, table_arr, index_arr); - auto column_idx = static_cast(index_arr[arr_len - 1]); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_arr); + ColKey column_idx = ColKey(col_arr[arr_len - 1]); // Support a backlink as the last column in a field descriptor - auto last = reinterpret_cast(table_arr[arr_len-1]); - if (last != nullptr) { - pQuery->and_query(src_table_ref->column(*last, column_idx).count() != 0); + if (table_arr[arr_len-1]) { + pQuery->and_query(linkChain.column(*TBL_REF(table_arr[arr_len-1]), column_idx).count() != 0); return; } - TableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, index_arr); + ConstTableRef table_ref = getTableByArray(nativeQueryPtr, table_arr, col_arr); DataType col_type = table_ref->get_column_type(column_idx); if (arr_len == 1) { // Field queries @@ -1752,7 +1646,7 @@ Java_io_realm_internal_TableQuery_nativeIsNotEmpty(JNIEnv *env, jobject, jlong n pQuery->not_equal(column_idx, BinaryData("", 0)); break; case type_LinkList: - pQuery->and_query(src_table_ref->column(column_idx).count() != 0); + pQuery->and_query(linkChain.column(column_idx).count() != 0); break; case type_String: pQuery->not_equal(column_idx, ""); @@ -1772,13 +1666,13 @@ Java_io_realm_internal_TableQuery_nativeIsNotEmpty(JNIEnv *env, jobject, jlong n // Linked queries switch (col_type) { case type_Binary: - pQuery->and_query(src_table_ref->column(column_idx) != BinaryData("", 0)); + pQuery->and_query(linkChain.column(column_idx) != BinaryData("", 0)); break; case type_LinkList: - pQuery->and_query(src_table_ref->column(column_idx).count() != 0); + pQuery->and_query(linkChain.column(column_idx).count() != 0); break; case type_String: - pQuery->and_query(src_table_ref->column(column_idx) != ""); + pQuery->and_query(linkChain.column(column_idx) != ""); break; case type_Link: case type_Bool: @@ -1798,7 +1692,6 @@ Java_io_realm_internal_TableQuery_nativeIsNotEmpty(JNIEnv *env, jobject, jlong n JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeAlwaysFalse(JNIEnv *env, jobject, jlong nativeQueryPtr) { - TR_ENTER_PTR(nativeQueryPtr); try { Query* query = reinterpret_cast(nativeQueryPtr); query->and_query(std::unique_ptr(new FalseExpression)); @@ -1809,7 +1702,6 @@ Java_io_realm_internal_TableQuery_nativeAlwaysFalse(JNIEnv *env, jobject, jlong JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeAlwaysTrue(JNIEnv *env, jobject, jlong nativeQueryPtr) { - TR_ENTER_PTR(nativeQueryPtr); try { Query* query = reinterpret_cast(nativeQueryPtr); query->and_query(std::unique_ptr(new TrueExpression)); @@ -1819,12 +1711,10 @@ Java_io_realm_internal_TableQuery_nativeAlwaysTrue(JNIEnv *env, jobject, jlong n static void finalize_table_query(jlong ptr) { - TR_ENTER_PTR(ptr) delete Q(ptr); } JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_table_query); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 319597fb17..aa4b80a96d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -27,135 +27,147 @@ static void finalize_unchecked_row(jlong ptr); JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnCount(JNIEnv*, jobject, jlong nativeRowPtr) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW(nativeRowPtr)->is_attached()) { + if (!OBJ(nativeRowPtr)->is_valid()) { return 0; } - return static_cast(ROW(nativeRowPtr)->get_column_count()); // noexcept + return static_cast(OBJ(nativeRowPtr)->get_table()->get_column_count()); // noexcept } -JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnName(JNIEnv* env, jobject, - jlong nativeRowPtr, - jlong columnIndex) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnKey(JNIEnv* env, jobject, + jlong nativeRowPtr, + jstring columnName) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { - return 0; + if (!OBJ(nativeRowPtr)->is_valid()) { + ThrowException(env, IllegalArgument, "Object passed is not valid"); } try { - return to_jstring(env, ROW(nativeRowPtr)->get_column_name(S(columnIndex))); + JStringAccessor columnName2(env, columnName); // throws + ColKey col_key = OBJ(nativeRowPtr)->get_table()->get_column_key(columnName2); + if (bool(col_key)) { + return col_key.value; + } } - CATCH_STD(); - return NULL; + CATCH_STD() + return ColKey().value; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex(JNIEnv* env, jobject, - jlong nativeRowPtr, - jstring columnName) +JNIEXPORT jobjectArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnNames(JNIEnv* env, jobject, + jlong nativeRowPtr) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW(nativeRowPtr)->is_attached()) { - return 0; + if (!OBJ(nativeRowPtr)->is_valid()) { + ThrowException(env, IllegalArgument, "Object passed is not valid"); } try { - JStringAccessor columnName2(env, columnName); // throws - return to_jlong_or_not_found(ROW(nativeRowPtr)->get_column_index(columnName2)); // noexcept + ColKeys col_keys = OBJ(nativeRowPtr)->get_table()->get_column_keys(); + + size_t size = col_keys.size(); + jobjectArray col_keys_array = env->NewObjectArray(size, JavaClassGlobalDef::java_lang_string(), 0); + if (col_keys_array == NULL) { + ThrowException(env, OutOfMemory, "Could not allocate memory to return column keys."); + return NULL; + } + for (size_t i = 0; i < size; ++i) { + env->SetObjectArrayElement(col_keys_array, i, to_jstring(env, OBJ(nativeRowPtr)->get_table()->get_column_name(col_keys[i]))); + } + + return col_keys_array; + } CATCH_STD() - return 0; + return NULL; } + JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType(JNIEnv*, jobject, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - auto column_type = ROW(nativeRowPtr)->get_column_type(S(columnIndex)); // noexcept - if (column_type != type_Table) { - return static_cast(column_type); - } - // FIXME: Add test in https://github.com/realm/realm-java/pull/5221 before merging to master - return static_cast(ROW(nativeRowPtr)->get_table()->get_descriptor()->get_subdescriptor(S(columnIndex))->get_column_type(S(0)) - + io_realm_internal_Property_TYPE_ARRAY); // noexcept + ColKey column_key (columnKey); + auto table = OBJ(nativeRowPtr)->get_table(); + jint column_type = table->get_column_type(column_key); + if (table->is_list(column_key) && column_type < type_LinkList) { + // add the offset so it can be mapped correctly in Java (RealmFieldType#fromNativeValue) + column_type += 128; + } + + return column_type; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetIndex(JNIEnv* env, jobject, jlong nativeRowPtr) +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetObjectKey(JNIEnv* env, jobject, jlong nativeRowPtr) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return 0; } - return static_cast(ROW(nativeRowPtr)->get_index()); + return static_cast(OBJ(nativeRowPtr)->get_key().value); } JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLong(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return 0; } - - return ROW(nativeRowPtr)->get_int(S(columnIndex)); + ColKey col_key(columnKey); + if (col_key.get_attrs().test(col_attr_Nullable)) { + auto val = OBJ(nativeRowPtr)->get>(col_key); + return val.value(); + } else { + return OBJ(nativeRowPtr)->get(col_key); + } } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeGetBoolean(JNIEnv* env, jobject, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return 0; } - return to_jbool(ROW(nativeRowPtr)->get_bool(S(columnIndex))); + return to_jbool(OBJ(nativeRowPtr)->get(ColKey(columnKey))); } JNIEXPORT jfloat JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFloat(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return 0; } - return ROW(nativeRowPtr)->get_float(S(columnIndex)); + return OBJ(nativeRowPtr)->get(ColKey(columnKey)); } JNIEXPORT jdouble JNICALL Java_io_realm_internal_UncheckedRow_nativeGetDouble(JNIEnv* env, jobject, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return 0; } - return ROW(nativeRowPtr)->get_double(S(columnIndex)); + return OBJ(nativeRowPtr)->get(ColKey(columnKey)); } JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetTimestamp(JNIEnv* env, jobject, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return 0; } - return to_milliseconds(ROW(nativeRowPtr)->get_timestamp(S(columnIndex))); + return to_milliseconds(OBJ(nativeRowPtr)->get(ColKey(columnKey))); } JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetString(JNIEnv* env, jobject, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return nullptr; } try { - StringData value = ROW(nativeRowPtr)->get_string(S(columnIndex)); + StringData value = OBJ(nativeRowPtr)->get(ColKey(columnKey)); return to_jstring(env, value); } CATCH_STD() @@ -164,15 +176,14 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetString(JN JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteArray(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return nullptr; } try { - BinaryData bin = ROW(nativeRowPtr)->get_binary(S(columnIndex)); + BinaryData bin = OBJ(nativeRowPtr)->get(ColKey(columnKey)); return JavaClassGlobalDef::new_byte_array(env, bin); } CATCH_STD() @@ -180,224 +191,224 @@ JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetByteAr } JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetLink(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return 0; } - if (ROW(nativeRowPtr)->is_null_link(S(columnIndex))) { + ColKey col_key(columnKey); + if (OBJ(nativeRowPtr)->is_null(col_key)) { return jlong(-1); } - return static_cast(ROW(nativeRowPtr)->get_link(S(columnIndex))); + return static_cast(OBJ(nativeRowPtr)->get(col_key).value); } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNullLink(JNIEnv* env, jobject, - jlong nativeRowPtr, jlong columnIndex) + jlong nativeRowPtr, jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return 0; } - return to_jbool(ROW(nativeRowPtr)->is_null_link(S(columnIndex))); + return to_jbool(OBJ(nativeRowPtr)->is_null(ColKey(columnKey))); } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLong(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex, jlong value) + jlong columnKey, jlong value) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } try { - ROW(nativeRowPtr)->set_int(S(columnIndex), value); + OBJ(nativeRowPtr)->set(ColKey(columnKey), value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetBoolean(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex, jboolean value) + jlong columnKey, jboolean value) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } try { - ROW(nativeRowPtr)->set_bool(S(columnIndex), value); + OBJ(nativeRowPtr)->set(ColKey(columnKey), (value == JNI_TRUE)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetFloat(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex, jfloat value) + jlong columnKey, jfloat value) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } try { - ROW(nativeRowPtr)->set_float(S(columnIndex), value); + OBJ(nativeRowPtr)->set(ColKey(columnKey), value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDouble(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex, jdouble value) + jlong columnKey, jdouble value) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } try { - ROW(nativeRowPtr)->set_double(S(columnIndex), value); + OBJ(nativeRowPtr)->set(ColKey(columnKey), value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetTimestamp(JNIEnv* env, jobject, - jlong nativeRowPtr, jlong columnIndex, + jlong nativeRowPtr, jlong columnKey, jlong value) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } try { - ROW(nativeRowPtr)->set_timestamp(S(columnIndex), from_milliseconds(value)); + OBJ(nativeRowPtr)->set(ColKey(columnKey), from_milliseconds(value)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetString(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex, jstring value) + jlong columnKey, jstring value) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } try { - if ((value == nullptr) && !(ROW(nativeRowPtr)->get_table()->is_nullable(S(columnIndex)))) { - ThrowNullValueException(env, ROW(nativeRowPtr)->get_table(), S(columnIndex)); + ColKey col_key(columnKey); + if ((value == nullptr) && !col_key.get_attrs().test(col_attr_Nullable)) { + ThrowNullValueException(env, OBJ(nativeRowPtr)->get_table(), ColKey(columnKey)); return; } JStringAccessor value2(env, value); // throws - ROW(nativeRowPtr)->set_string(S(columnIndex), value2); + OBJ(nativeRowPtr)->set(col_key, StringData(value2)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetByteArray(JNIEnv* env, jobject, - jlong nativeRowPtr, jlong columnIndex, + jlong nativeRowPtr, jlong columnKey, jbyteArray value) { - TR_ENTER_PTR(nativeRowPtr) - - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } try { - auto& row = *reinterpret_cast(nativeRowPtr); - if (value == nullptr && !(row.get_table()->is_nullable(S(columnIndex)))) { - ThrowNullValueException(env, ROW(nativeRowPtr)->get_table(), S(columnIndex)); + auto& obj = *reinterpret_cast(nativeRowPtr); + ColKey col_key(columnKey); + if ((value == nullptr) && !col_key.get_attrs().test(col_attr_Nullable)) { + ThrowNullValueException(env, OBJ(nativeRowPtr)->get_table(), ColKey(columnKey)); return; } JByteArrayAccessor jarray_accessor(env, value); - row.set_binary(static_cast(columnIndex), jarray_accessor.transform()); + obj.set(col_key, jarray_accessor.transform()); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetLink(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex, jlong value) + jlong columnKey, jlong valueObjKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } try { - ROW(nativeRowPtr)->set_link(S(columnIndex), value); + OBJ(nativeRowPtr)->set(ColKey(columnKey), ObjKey(valueObjKey)); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeNullifyLink(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } try { - ROW(nativeRowPtr)->nullify_link(S(columnIndex)); + OBJ(nativeRowPtr)->set_null(ColKey(columnKey)); } CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsAttached(JNIEnv*, jobject, jlong nativeRowPtr) +JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsValid(JNIEnv*, jobject, jlong nativeRowPtr) { - TR_ENTER_PTR(nativeRowPtr) - return to_jbool(ROW(nativeRowPtr)->is_attached()); + return to_jbool(OBJ(nativeRowPtr)->is_valid()); } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeHasColumn(JNIEnv* env, jobject obj, jlong nativeRowPtr, jstring columnName) { - jlong ndx = Java_io_realm_internal_UncheckedRow_nativeGetColumnIndex(env, obj, nativeRowPtr, columnName); - return to_jbool(ndx != to_jlong_or_not_found(realm::not_found)); + ColKey col_key (Java_io_realm_internal_UncheckedRow_nativeGetColumnKey(env, obj, nativeRowPtr, columnName)); + return to_jbool(bool(col_key)); } JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return JNI_FALSE; } try { - return to_jbool(ROW(nativeRowPtr)->is_null(columnIndex)); + return to_jbool(OBJ(nativeRowPtr)->is_null(ColKey(columnKey))); } CATCH_STD() return JNI_FALSE; } JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetNull(JNIEnv* env, jobject, jlong nativeRowPtr, - jlong columnIndex) + jlong columnKey) { - TR_ENTER_PTR(nativeRowPtr) - if (!ROW_VALID(env, ROW(nativeRowPtr))) { + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return; } - if (!TBL_AND_COL_NULLABLE(env, ROW(nativeRowPtr)->get_table(), columnIndex)) { + if (!COL_NULLABLE(env, OBJ(nativeRowPtr)->get_table(), columnKey)) { return; } try { - ROW(nativeRowPtr)->set_null(columnIndex); + OBJ(nativeRowPtr)->set_null(ColKey(columnKey)); } CATCH_STD() } +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeFreeze(JNIEnv* env, jobject, jlong j_native_row_ptr, + jlong j_frozen_realm_native_ptr) +{ + try { + Obj* obj = reinterpret_cast(j_native_row_ptr); + auto frozen_realm = *(reinterpret_cast(j_frozen_realm_native_ptr)); + auto frozen_obj = new Obj(frozen_realm->transaction().import_copy_of(*obj)); + return reinterpret_cast(frozen_obj); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + + static void finalize_unchecked_row(jlong ptr) { - TR_ENTER_PTR(ptr) - delete ROW(ptr); + delete OBJ(ptr); } JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_unchecked_row); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp index 5dcf6bc849..827a60131e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp @@ -16,8 +16,6 @@ #include "io_realm_internal_core_DescriptorOrdering.h" -#include - #include "java_query_descriptor.hpp" #include "util.hpp" @@ -28,19 +26,16 @@ using namespace realm::_impl; static void finalize_descriptor(jlong ptr); static void finalize_descriptor(jlong ptr) { - TR_ENTER_PTR(ptr) delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_descriptor); } JNIEXPORT jlong JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeCreate(JNIEnv* env, jclass) { - TR_ENTER() try { return reinterpret_cast(new DescriptorOrdering()); } @@ -52,7 +47,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppe jlong descriptor_ptr, jobject j_sort_descriptor) { - TR_ENTER() try { auto descriptor = reinterpret_cast(descriptor_ptr); if (j_sort_descriptor) { @@ -66,7 +60,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppe jlong descriptor_ptr, jobject j_distinct_descriptor) { - TR_ENTER() try { auto descriptor = reinterpret_cast(descriptor_ptr); if (j_distinct_descriptor) { @@ -80,7 +73,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppe jlong descriptor_ptr, jlong limit) { - TR_ENTER() try { auto descriptor = reinterpret_cast(descriptor_ptr); descriptor->append_limit(limit); @@ -92,7 +84,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppe jlong descriptor_ptr, jlong include_descriptor_ptr) { - TR_ENTER() try { auto descriptor = reinterpret_cast(descriptor_ptr); auto include_descriptor = reinterpret_cast(include_descriptor_ptr); @@ -104,7 +95,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppe JNIEXPORT jboolean JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeIsEmpty(JNIEnv* env, jclass, jlong descriptor_ptr) { - TR_ENTER() try { auto descriptor = reinterpret_cast(descriptor_ptr); return descriptor->is_empty() ? JNI_TRUE : JNI_FALSE; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp index 19f23577e2..010e8c8eef 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include #include @@ -36,13 +35,11 @@ using namespace realm::_impl; static void finalize_descriptor(jlong ptr) { - TR_ENTER_PTR(ptr) delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL Java_io_realm_internal_core_IncludeDescriptor_nativeGetFinalizerMethodPtr(JNIEnv* env, jclass) { - TR_ENTER() try { return reinterpret_cast(&finalize_descriptor); } @@ -50,30 +47,29 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_core_IncludeDescriptor_nativeGetF return 0; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_core_IncludeDescriptor_nativeCreate(JNIEnv* env, jclass, jlong starting_table_ptr, jlongArray column_indexes, jlongArray table_pointers) +JNIEXPORT jlong JNICALL Java_io_realm_internal_core_IncludeDescriptor_nativeCreate(JNIEnv* env, jclass, jlong starting_table_ptr, jlongArray column_keys, jlongArray table_pointers) { - TR_ENTER() try { JLongArrayAccessor table_arr(env, table_pointers); - JLongArrayAccessor index_arr(env, column_indexes); - auto starting_table = reinterpret_cast(starting_table_ptr); + JLongArrayAccessor colkeys_arr(env, column_keys); + auto starting_table = reinterpret_cast(starting_table_ptr); std::vector parts; - parts.reserve(index_arr.size()); - for (int i = 0; i < index_arr.size(); ++i) { - auto col_index = static_cast(index_arr[i]); - auto table_ptr = reinterpret_cast
            (table_arr[i]); + parts.reserve(colkeys_arr.size()); + for (int i = 0; i < colkeys_arr.size(); ++i) { + auto col_key = static_cast(colkeys_arr[i]); + auto table_ptr = reinterpret_cast(table_arr[i]); if (table_ptr == nullptr) { - parts.emplace_back(LinkPathPart(col_index)); + parts.emplace_back(LinkPathPart(ColKey(col_key))); } else { - const ConstTableRef ref = table_ptr->get_table_ref(); - parts.emplace_back(LinkPathPart(col_index, ref)); + parts.emplace_back(LinkPathPart(ColKey(col_key), *static_cast(table_ptr))); } } std::vector> include_path; include_path.reserve(1); include_path.emplace_back(parts); + return reinterpret_cast(new IncludeDescriptor(*starting_table, include_path)); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp index 1432ab4715..e69eae0b86 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp @@ -34,7 +34,6 @@ using namespace realm::_impl; JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAsyncOpenTask_start(JNIEnv* env, jobject obj, jlong config_ptr) { - TR_ENTER() try { static JavaClass java_async_open_task_class(env, "io/realm/internal/objectstore/OsAsyncOpenTask"); @@ -50,7 +49,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAsyncOpenTask_start jni_util::JniUtils::get_env(true)->DeleteGlobalRef(obj); }; std::shared_ptr<_jobject> task_obj(env->NewGlobalRef(global_obj), deleter); - task->start([task=std::move(task_obj)](realm::ThreadSafeReference realm_ref, std::exception_ptr error) { + task->start([task=std::move(task_obj)](realm::ThreadSafeReference realm_ref, std::exception_ptr error) { JNIEnv* local_env = jni_util::JniUtils::get_env(true); if (error) { try { @@ -77,7 +76,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAsyncOpenTask_start JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsAsyncOpenTask_cancel(JNIEnv*, jobject, jlong task_ptr) { - TR_ENTER() AsyncOpenTask* task = reinterpret_cast(task_ptr); task->cancel(); } \ No newline at end of file diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp index d23bb0bc59..0b20ee8b79 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp @@ -25,126 +25,124 @@ using namespace realm; using namespace realm::jni_util; using namespace realm::_impl; -typedef std::vector OsObjectData; +typedef std::map OsObjectData; JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeDestroyBuilder(JNIEnv*, jclass, jlong data_ptr) { - TR_ENTER() delete reinterpret_cast(data_ptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeCreateBuilder(JNIEnv* env, jclass, jlong size) +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeCreateBuilder(JNIEnv* env, jclass) { - TR_ENTER() try { - auto list = new std::vector(size); - return reinterpret_cast(list); + auto map = new std::map(); + return reinterpret_cast(map); } CATCH_STD() return -1; } -static inline void add_property(jlong data_ptr, jlong column_index, JavaValue const& value) +static inline void add_property(jlong data_ptr, jlong column_key, JavaValue const& value) { OsObjectData* data = reinterpret_cast(data_ptr); - data->at(column_index) = std::move(value); + (*data)[ColKey(column_key)] = std::move(value); } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddNull - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key) { try { const JavaValue value = JavaValue(); - add_property(data_ptr, column_index, value); + add_property(data_ptr, column_key, value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddString - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jstring j_value) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jstring j_value) { try { JStringAccessor value(env, j_value); std::string string_value(value); const JavaValue wrapped_value(string_value); - add_property(data_ptr, column_index, wrapped_value); + add_property(data_ptr, column_key, wrapped_value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddInteger - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlong j_value) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jlong j_value) { try { const JavaValue value(j_value); - add_property(data_ptr, column_index, value); + add_property(data_ptr, column_key, value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddFloat - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jfloat j_value) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jfloat j_value) { try { const JavaValue value(j_value); - add_property(data_ptr, column_index, value); + add_property(data_ptr, column_key, value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddDouble - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jdouble j_value) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jdouble j_value) { try { const JavaValue value(j_value); - add_property(data_ptr, column_index, value); + add_property(data_ptr, column_key, value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddBoolean - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jboolean j_value) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jboolean j_value) { try { const JavaValue value(j_value); - add_property(data_ptr, column_index, value); + add_property(data_ptr, column_key, value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddByteArray - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jbyteArray j_value) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jbyteArray j_value) { try { auto data = OwnedBinaryData(JByteArrayAccessor(env, j_value).transform()); const JavaValue value(data); - add_property(data_ptr, column_index, value); + add_property(data_ptr, column_key, value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddDate - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlong j_value) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jlong j_value) { try { const JavaValue value(from_milliseconds(j_value)); - add_property(data_ptr, column_index, value); + add_property(data_ptr, column_key, value); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddObject - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlong row_ptr) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jlong row_ptr) { try { - const JavaValue value(reinterpret_cast(row_ptr)); - add_property(data_ptr, column_index, value); + const JavaValue value(reinterpret_cast(row_ptr)); + add_property(data_ptr, column_key, value); } CATCH_STD() } -static inline const ObjectSchema& get_schema(const Schema& schema, Table* table) +static inline const ObjectSchema& get_schema(const Schema& schema, TableRef table) { std::string table_name(table->get_name()); std::string class_name = std::string(table_name.substr(TABLE_PREFIX.length())); @@ -156,18 +154,26 @@ static inline const ObjectSchema& get_schema(const Schema& schema, Table* table) } JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeCreateOrUpdate - (JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ptr, jlong builder_ptr, jboolean update_existing, jboolean ignore_same_values) + (JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong builder_ptr, jboolean update_existing, jboolean ignore_same_values) { try { SharedRealm shared_realm = *(reinterpret_cast(shared_realm_ptr)); - Table* table = reinterpret_cast(table_ptr); + + CreatePolicy policy = CreatePolicy::ForceCreate; + if (update_existing && ignore_same_values) { + policy = CreatePolicy::UpdateModified; + } else if (update_existing) { + policy = CreatePolicy::UpdateAll; + } + + TableRef table = TBL_REF(table_ref_ptr); const auto& schema = shared_realm->schema(); const ObjectSchema& object_schema = get_schema(schema, table); JavaContext ctx(env, shared_realm, object_schema); auto list = *reinterpret_cast(builder_ptr); JavaValue values = JavaValue(list); - Object obj = Object::create(ctx, shared_realm, object_schema, values, update_existing, ignore_same_values); - return reinterpret_cast(new Row(obj.row())); + Object obj = Object::create(ctx, shared_realm, object_schema, values, policy); + return reinterpret_cast(new Obj(obj.obj())); } CATCH_STD() return realm::npos; @@ -186,12 +192,12 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativ } JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeStopList - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlong list_ptr) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jlong list_ptr) { try { auto list = reinterpret_cast*>(list_ptr); const JavaValue value((*list)); - add_property(data_ptr, column_index, value); + add_property(data_ptr, column_key, value); delete list; } CATCH_STD() @@ -199,18 +205,18 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_native JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddObjectList - (JNIEnv* env, jclass, jlong data_ptr, jlong column_index, jlongArray row_ptrs) + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jlongArray row_ptrs) { try { auto rows = JLongArrayAccessor(env, row_ptrs); auto list = std::vector(); list.reserve(rows.size()); for (jsize i = 0; i < rows.size(); ++i) { - auto item = JavaValue(reinterpret_cast(rows[i])); + auto item = JavaValue(reinterpret_cast(rows[i])); list.push_back(item); } JavaValue value(list); - add_property(data_ptr, column_index, value); + add_property(data_ptr, column_key, value); } CATCH_STD() } @@ -308,7 +314,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_native (JNIEnv* env, jclass, jlong list_ptr, jlong row_ptr) { try { - const JavaValue value(reinterpret_cast(row_ptr)); + const JavaValue value(reinterpret_cast(row_ptr)); add_list_element(list_ptr, value); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp index 963e53e770..ab4acc0ef6 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp @@ -36,13 +36,11 @@ static void finalize_subscription(jlong ptr); static void finalize_subscription(jlong ptr) { - TR_ENTER_PTR(ptr); delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeCreateOrUpdate(JNIEnv* env, jclass, jlong results_ptr, jstring j_subscription_name, jlong time_to_live, jboolean update) { - TR_ENTER() try { const auto results = reinterpret_cast(results_ptr); JStringAccessor subscription_name(env, j_subscription_name); @@ -62,13 +60,11 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeCreateO JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetFinalizerPtr(JNIEnv*, jclass) { - TR_ENTER() return reinterpret_cast(&finalize_subscription); } JNIEXPORT void JNICALL Java_io_realm_internal_sync_OsSubscription_nativeStartListening(JNIEnv* env, jobject object, jlong native_ptr) { - TR_ENTER() try { auto wrapper = reinterpret_cast(native_ptr); wrapper->start_listening(env, object); @@ -78,7 +74,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_sync_OsSubscription_nativeStartLis JNIEXPORT void JNICALL Java_io_realm_internal_sync_OsSubscription_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) { - TR_ENTER() try { auto wrapper = reinterpret_cast(native_ptr); wrapper->stop_listening(); @@ -88,7 +83,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_sync_OsSubscription_nativeStopList JNIEXPORT jint JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetState(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER() try { auto wrapper = reinterpret_cast(native_ptr); return static_cast(wrapper->subscription().state()); @@ -99,7 +93,6 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetState JNIEXPORT jobject JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetError(JNIEnv* env, jclass, jlong native_ptr) { - TR_ENTER() try { auto wrapper = reinterpret_cast(native_ptr); auto err = wrapper->subscription().error(); diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index 87eb4ee693..c2730fa706 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -220,7 +220,7 @@ class JavaAccessorContext { { return v ? _impl::JavaClassGlobalDef::new_long(m_env, v.value()) : nullptr; } - util::Any box(RowExpr) const + util::Any box(Obj) const { REALM_TERMINATE("not supported"); } @@ -244,7 +244,7 @@ class JavaAccessorContext { // using the provided value. If `update` is true then upsert semantics // should be used for this. template - T unbox(util::Any& v, bool /*create*/ = false, bool /*update*/ = false) const + T unbox(util::Any& v, CreatePolicy=CreatePolicy::ForceCreate) const { return any_cast(v); } @@ -344,35 +344,35 @@ inline JPrimitiveArrayAccessor::ElementsHolder::~ElementsHold } template <> -inline bool JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline bool JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { check_value_not_null(v, "Boolean"); return any_cast(v) == JNI_TRUE; } template <> -inline int64_t JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline int64_t JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { check_value_not_null(v, "Long"); return static_cast(any_cast(v)); } template <> -inline double JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline double JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { check_value_not_null(v, "Double"); return static_cast(any_cast(v)); } template <> -inline float JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline float JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { check_value_not_null(v, "Float"); return static_cast(any_cast(v)); } template <> -inline StringData JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline StringData JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { if (!v.has_value()) { return StringData(); @@ -382,7 +382,7 @@ inline StringData JavaAccessorContext::unbox(util::Any& v, bool, bool) const } template <> -inline BinaryData JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline BinaryData JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { if (!v.has_value()) return BinaryData(); @@ -391,43 +391,43 @@ inline BinaryData JavaAccessorContext::unbox(util::Any& v, bool, bool) const } template <> -inline Timestamp JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline Timestamp JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? from_milliseconds(any_cast(v)) : Timestamp(); } template <> -inline RowExpr JavaAccessorContext::unbox(util::Any&, bool, bool) const +inline Obj JavaAccessorContext::unbox(util::Any&, CreatePolicy) const { REALM_TERMINATE("not supported"); } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? util::make_optional(any_cast(v) == JNI_TRUE) : util::none; } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? util::make_optional(static_cast(any_cast(v))) : util::none; } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? util::make_optional(any_cast(v)) : util::none; } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? util::make_optional(any_cast(v)) : util::none; } template <> -inline Mixed JavaAccessorContext::unbox(util::Any&, bool, bool) const +inline Mixed JavaAccessorContext::unbox(util::Any&, CreatePolicy) const { REALM_TERMINATE("not supported"); } diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp index 5054ec5dc3..948493e8c5 100644 --- a/realm/realm-library/src/main/cpp/java_object_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -28,7 +28,6 @@ #include "object_accessor.hpp" #include "object-store/src/property.hpp" -#include #include using namespace realm::_impl; @@ -43,7 +42,7 @@ using namespace realm::_impl; X(Binary) \ X(Object) \ X(List) \ - + X(PropertyList) \ namespace realm { @@ -74,8 +73,9 @@ template <> struct JavaValueTypeRepr { using Type = jflo template <> struct JavaValueTypeRepr { using Type = jdouble; }; template <> struct JavaValueTypeRepr { using Type = Timestamp; }; template <> struct JavaValueTypeRepr { using Type = OwnedBinaryData; }; -template <> struct JavaValueTypeRepr { using Type = RowExpr*; }; +template <> struct JavaValueTypeRepr { using Type = Obj*; }; template <> struct JavaValueTypeRepr { using Type = std::vector; }; +template <> struct JavaValueTypeRepr { using Type = std::map; }; // Tagged union class representing all the values Java can send to Object Store struct JavaValue { @@ -206,6 +206,12 @@ struct JavaValue { return get_as(); } + auto& get_property_list() const noexcept + { + return get_as(); + } + + auto& get_date() const noexcept { return get_as(); @@ -268,14 +274,16 @@ struct JavaValue { case JavaValueType::Object: ss << "Object[Type: "; ss << get_object()->get_table()->get_name(); - ss << ", rowIndex: "; - ss << get_object()->get_index(); + ss << ", colKey: "; + ss << get_object()->get_key().value; ss << "]"; return std::string(ss.str()); case JavaValueType::List: ss << "List[size: "; ss << get_list().size(); ss << "]"; + case JavaValueType::PropertyList: + ss << "PropertyList "; return std::string(ss.str()); default: REALM_TERMINATE("Invalid type."); } @@ -323,9 +331,9 @@ class JavaContext { Property const& prop, size_t /*property_index*/) const { - const std::vector& list = dict.get_list(); - auto property_value = list.at(prop.table_column); - return util::make_optional(property_value); + const std::map& map = dict.get_property_list(); + auto it = map.find(prop.column_key); + return it == map.end() ? util::none : util::make_optional(it->second); } // Get the default value for the given property in the given object schema, @@ -393,7 +401,7 @@ class JavaContext { // using the provided value. If `update` is true then upsert semantics // should be used for this. template - T unbox(JavaValue const& /*v*/, bool /*create*/= false, bool /*update*/= false, bool /*diff_on_update*/= false, size_t /*current_row*/ = realm::npos) const { + T unbox(JavaValue const& /*v*/, CreatePolicy = CreatePolicy::Skip, ObjKey /*current_row*/ = ObjKey()) const { throw std::logic_error("Missing template specialization"); // All types should have specialized templates } @@ -433,45 +441,44 @@ class JavaContext { }; template <> -inline bool JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline bool JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Boolean"); return v.get_boolean() == JNI_TRUE; } template <> -inline int64_t JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline int64_t JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Long"); return static_cast(v.get_int()); } template <> -inline double JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline double JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Double"); return static_cast(v.get_double()); } template <> -inline float JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline float JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Float"); return static_cast(v.get_float()); } template <> -inline StringData JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline StringData JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { if (!v.has_value()) { return StringData(); } - return StringData(v.get_string()); } template <> -inline BinaryData JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline BinaryData JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { if (!v.has_value()) { return BinaryData(); @@ -481,49 +488,49 @@ inline BinaryData JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_ } template <> -inline Timestamp JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline Timestamp JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { return v.has_value() ? v.get_date() : Timestamp(); } template <> -inline RowExpr JavaContext::unbox(JavaValue const& v, bool create, bool update, bool diff_on_update, size_t current_row) const +inline Obj JavaContext::unbox(JavaValue const& v, CreatePolicy policy, ObjKey current_row) const { if (v.get_type() == JavaValueType::Object) { return *v.get_object(); - } else if (!create) { - return RowExpr(); + } else if (policy == CreatePolicy::Skip) { + return Obj(); } REALM_ASSERT(object_schema); - return Object::create(const_cast(*this), realm, *object_schema, v, update, diff_on_update, current_row).row(); + return Object::create(const_cast(*this), realm, *object_schema, v, policy, current_row).obj(); } template <> -inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { return v.has_value() ? util::make_optional(v.get_boolean() == JNI_TRUE) : util::none; } template <> -inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { return v.has_value() ? util::make_optional(static_cast(v.get_int())) : util::none; } template <> -inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { return v.has_value() ? util::make_optional(v.get_double()) : util::none; } template <> -inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { return v.has_value() ? util::make_optional(v.get_float()) : util::none; } template <> -inline Mixed JavaContext::unbox(JavaValue const&, bool, bool, bool, size_t) const +inline Mixed JavaContext::unbox(JavaValue const&, CreatePolicy, ObjKey) const { REALM_TERMINATE("'Mixed' not supported"); } diff --git a/realm/realm-library/src/main/cpp/java_query_descriptor.cpp b/realm/realm-library/src/main/cpp/java_query_descriptor.cpp index d20fd9609f..acd85ffe3e 100644 --- a/realm/realm-library/src/main/cpp/java_query_descriptor.cpp +++ b/realm/realm-library/src/main/cpp/java_query_descriptor.cpp @@ -31,7 +31,7 @@ SortDescriptor JavaQueryDescriptor::sort_descriptor() const noexcept return SortDescriptor(); } - return SortDescriptor(*get_table_ptr(), get_column_indices(), get_ascendings()); + return SortDescriptor(get_column_keys(), get_ascendings()); } DistinctDescriptor JavaQueryDescriptor::distinct_descriptor() const noexcept @@ -39,34 +39,27 @@ DistinctDescriptor JavaQueryDescriptor::distinct_descriptor() const noexcept if (m_sort_desc_obj == nullptr) { return DistinctDescriptor(); } - return DistinctDescriptor(*get_table_ptr(), get_column_indices()); + return DistinctDescriptor(get_column_keys()); } -Table* JavaQueryDescriptor::get_table_ptr() const noexcept +std::vector> JavaQueryDescriptor::get_column_keys() const noexcept { - static JavaMethod get_table_ptr_method(m_env, get_sort_desc_class(), "getTablePtr", "()J"); - jlong table_ptr = m_env->CallLongMethod(m_sort_desc_obj, get_table_ptr_method); - return reinterpret_cast(table_ptr); -} - -std::vector> JavaQueryDescriptor::get_column_indices() const noexcept -{ - static JavaMethod get_column_indices_method(m_env, get_sort_desc_class(), "getColumnIndices", "()[[J"); + static JavaMethod get_column_keys_method(m_env, get_sort_desc_class(), "getColumnKeys", "()[[J"); jobjectArray column_indices = - static_cast(m_env->CallObjectMethod(m_sort_desc_obj, get_column_indices_method)); + static_cast(m_env->CallObjectMethod(m_sort_desc_obj, get_column_keys_method)); JObjectArrayAccessor arrays(m_env, column_indices); jsize arr_len = arrays.size(); - std::vector> indices; + std::vector> keys; for (int i = 0; i < arr_len; ++i) { auto jni_long_array = arrays[i]; - std::vector col_indices; + std::vector col_keys; for (int j = 0; j < jni_long_array.size(); ++j) { - col_indices.push_back(static_cast(jni_long_array[j])); + col_keys.push_back(ColKey(jni_long_array[j])); } - indices.push_back(std::move(col_indices)); + keys.push_back(std::move(col_keys)); } - return indices; + return keys; } std::vector JavaQueryDescriptor::get_ascendings() const noexcept diff --git a/realm/realm-library/src/main/cpp/java_query_descriptor.hpp b/realm/realm-library/src/main/cpp/java_query_descriptor.hpp index 077b5f92c8..120db42a43 100644 --- a/realm/realm-library/src/main/cpp/java_query_descriptor.hpp +++ b/realm/realm-library/src/main/cpp/java_query_descriptor.hpp @@ -19,6 +19,8 @@ #include +#include "java_accessor.hpp" + namespace realm { namespace jni_util { @@ -59,8 +61,7 @@ class JavaQueryDescriptor { JNIEnv* m_env; jobject m_sort_desc_obj; - realm::Table* get_table_ptr() const noexcept; - std::vector> get_column_indices() const noexcept; + std::vector> get_column_keys() const noexcept; std::vector get_ascendings() const noexcept; jni_util::JavaClass const& get_sort_desc_class() const noexcept; diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp index db61e5fc7a..ec4c593765 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -28,15 +28,6 @@ #include "realm/util/logger.hpp" -#define TR_ENTER() \ - if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ - realm::jni_util::Log::t(" --> %1", __FUNCTION__); \ - } -#define TR_ENTER_PTR(ptr) \ - if (realm::jni_util::Log::s_level <= realm::jni_util::Log::trace) { \ - realm::jni_util::Log::t(" --> %1 %2", __FUNCTION__, static_cast(ptr)); \ - } - namespace realm { namespace jni_util { diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index ad96a4c334..dc34c655c8 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit ad96a4c334b475dd67d50c1ca419e257d7a21e18 +Subproject commit dc34c655c88a902a66ff407f771efd48e37d29fd diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index dc1188dc0d..95626dfd9d 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -67,11 +67,11 @@ void ConvertException(JNIEnv* env, const char* file, int line) ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalArgument, ss.str()); } - catch (SharedGroup::BadVersion& e) { + catch (DB::BadVersion& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, BadVersion, ss.str()); } - catch (std::invalid_argument& e) { + catch (util::invalid_argument& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalArgument, ss.str()); } @@ -149,6 +149,10 @@ void ConvertException(JNIEnv* env, const char* file, int line) catch (std::logic_error e) { ThrowException(env, IllegalState, e.what()); } + catch (util::runtime_error& e) { + ss << e.what() << " in " << file << " line " << line; + ThrowException(env, RuntimeError, ss.str()); + } catch (exception& e) { ss << e.what() << " in " << file << " line " << line; ThrowException(env, FatalError, ss.str()); @@ -259,20 +263,6 @@ void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::Rea case realm::RealmFileException::Kind::FormatUpgradeRequired: kind_code = io_realm_internal_OsSharedRealm_FILE_EXCEPTION_KIND_FORMAT_UPGRADE_REQUIRED; break; - case realm::RealmFileException::Kind::IncompatibleSyncedRealm: -#if REALM_ENABLE_SYNC - static JavaClass jincompatible_synced_file_cls(env, - "io/realm/exceptions/IncompatibleSyncedFileException"); - static JavaMethod jicompatible_synced_ctor(env, jincompatible_synced_file_cls, "", - "(Ljava/lang/String;Ljava/lang/String;)V"); - jobject jexception = env->NewObject(jincompatible_synced_file_cls, jicompatible_synced_ctor, - to_jstring(env, message), to_jstring(env, path)); - env->Throw(reinterpret_cast(jexception)); - env->DeleteLocalRef(jexception); - return; -#else - REALM_ASSERT_RELEASE_EX(false, "'IncompatibleSyncedRealm' should not be thrown for non-sync realm."); -#endif } jstring jmessage = to_jstring(env, message); jstring jpath = to_jstring(env, path); @@ -281,14 +271,13 @@ void ThrowRealmFileException(JNIEnv* env, const std::string& message, realm::Rea env->DeleteLocalRef(exception); } -void ThrowNullValueException(JNIEnv* env, Table* table, size_t col_ndx) +void ThrowNullValueException(JNIEnv* env, const TableRef table, ColKey col_key) { std::ostringstream ss; - ss << "Trying to set a non-nullable field '" << table->get_column_name(col_ndx) << "' in '" << table->get_name() + ss << "Trying to set a non-nullable field '" << table->get_column_name(col_key) << "' in '" << table->get_name() << "' to null."; ThrowException(env, IllegalArgument, ss.str()); } - //********************************************************************* // String handling //********************************************************************* @@ -340,7 +329,7 @@ struct JStringCharsAccessor { { size_t size; if (int_cast_with_overflow_detect(e->GetStringLength(s), size)) - throw std::runtime_error("String size overflow"); + throw util::runtime_error("String size overflow"); return size; } }; @@ -416,7 +405,7 @@ jstring to_jstring(JNIEnv* env, StringData str) if (str.size() <= stack_buf_size) { size_t retcode = Xcode::to_utf16(in_begin, in_end, out_curr, out_end); if (retcode != 0) { - throw std::runtime_error(string_to_hex("Failure when converting short string to UTF-16", str, in_begin, in_end, + throw util::runtime_error(string_to_hex("Failure when converting short string to UTF-16", str, in_begin, in_end, out_curr, out_end, size_t(0), retcode)); } if (in_begin == in_end) { @@ -429,11 +418,11 @@ jstring to_jstring(JNIEnv* env, StringData str) size_t error_code; size_t size = Xcode::find_utf16_buf_size(in_begin2, in_end, error_code); if (in_begin2 != in_end) { - throw std::runtime_error(string_to_hex("Failure when computing UTF-16 size", str, in_begin, in_end, out_curr, + throw util::runtime_error(string_to_hex("Failure when computing UTF-16 size", str, in_begin, in_end, out_curr, out_end, size, error_code)); } if (int_add_with_overflow_detect(size, stack_buf_size)) { - throw std::runtime_error("String size overflow"); + throw util::runtime_error("String size overflow"); } dyn_buf.reset(new jchar[size]); out_curr = copy(out_begin, out_curr, dyn_buf.get()); @@ -441,7 +430,7 @@ jstring to_jstring(JNIEnv* env, StringData str) out_end = dyn_buf.get() + size; size_t retcode = Xcode::to_utf16(in_begin, in_end, out_curr, out_end); if (retcode != 0) { - throw std::runtime_error(string_to_hex("Failure when converting long string to UTF-16", str, in_begin, in_end, + throw util::runtime_error(string_to_hex("Failure when converting long string to UTF-16", str, in_begin, in_end, out_curr, out_end, size_t(0), retcode)); } REALM_ASSERT(in_begin == in_end); @@ -450,7 +439,7 @@ jstring to_jstring(JNIEnv* env, StringData str) transcode_complete : { jsize out_size; if (int_cast_with_overflow_detect(out_curr - out_begin, out_size)) { - throw std::runtime_error("String size overflow"); + throw util::runtime_error("String size overflow"); } return env->NewString(out_begin, out_size); @@ -498,11 +487,11 @@ JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) char* out_end = m_data.get() + buf_size; size_t error_code; if (!Xcode::to_utf8(in_begin, in_end, out_begin, out_end, error_code)) { - throw std::invalid_argument( + throw util::invalid_argument( string_to_hex("Failure when converting to UTF-8", chars.data(), chars.size(), error_code)); } if (in_begin != in_end) { - throw std::invalid_argument( + throw util::invalid_argument( string_to_hex("in_begin != in_end when converting to UTF-8", chars.data(), chars.size(), error_code)); } m_size = out_begin - m_data.get(); diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index da2d4d5df3..f33d5fbd35 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -27,7 +27,6 @@ #include #include -#include #include #include #include @@ -49,9 +48,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved); } #endif -#define STRINGIZE_DETAIL(x) #x -#define STRINGIZE(x) STRINGIZE_DETAIL(x) - // Exception handling #define CATCH_STD() \ catch (...) \ @@ -59,14 +55,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved); ConvertException(env, __FILE__, __LINE__); \ } -template -std::string num_to_string(T pNumber) -{ - std::ostringstream oOStrStream; - oOStrStream << pNumber; - return oOStrStream.str(); -} - #define MAX_JINT 0x7FFFFFFFL #define MAX_JSIZE MAX_JINT @@ -78,10 +66,9 @@ std::string num_to_string(T pNumber) // Helper macros for better readability #define S(x) static_cast(x) #define B(x) static_cast(x) -#define S64(x) static_cast(x) -#define TBL(x) reinterpret_cast(x) #define Q(x) reinterpret_cast(x) -#define ROW(x) reinterpret_cast(x) +#define OBJ(x) reinterpret_cast(x) +#define TBL_REF(x) *reinterpret_cast(x) // Exception handling enum ExceptionKind { @@ -106,60 +93,22 @@ void ConvertException(JNIEnv* env, const char* file, int line); void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& classStr, const std::string& itemStr = ""); void ThrowException(JNIEnv* env, ExceptionKind exception, const char* classStr); -void ThrowNullValueException(JNIEnv* env, realm::Table* table, size_t col_ndx); +void ThrowNullValueException(JNIEnv* env, const realm::TableRef table, realm::ColKey col_key); // Check parameters #define TABLE_VALID(env, ptr) TableIsValid(env, ptr) #define ROW_VALID(env, ptr) RowIsValid(env, ptr) -#define QUERY_VALID(env, ptr) QueryIsValid(env, ptr) #if CHECK_PARAMETERS -#define ROW_INDEXES_VALID(env, ptr, start, end, range) RowIndexesValid(env, ptr, start, end, range) -#define ROW_INDEX_VALID(env, ptr, row) RowIndexValid(env, ptr, row) -#define ROW_INDEX_VALID_OFFSET(env, ptr, row) RowIndexValid(env, ptr, row, true) -#define TBL_AND_ROW_INDEX_VALID(env, ptr, row) TblRowIndexValid(env, ptr, row) -#define TBL_AND_ROW_INDEX_VALID_OFFSET(env, ptr, row, offset) TblRowIndexValid(env, ptr, row, offset) -#define COL_INDEX_VALID(env, ptr, col) ColIndexValid(env, ptr, col) -#define TBL_AND_COL_INDEX_VALID(env, ptr, col) TblColIndexValid(env, ptr, col) -#define COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) ColIndexAndTypeValid(env, ptr, col, type) -#define TBL_AND_COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) TblColIndexAndTypeValid(env, ptr, col, type) -#define TBL_AND_COL_INDEX_AND_LINK_OR_LINKLIST(env, ptr, col) TblColIndexAndLinkOrLinkList(env, ptr, col) -#define TBL_AND_COL_NULLABLE(env, ptr, col) TblColIndexAndNullable(env, ptr, col) -#define INDEX_VALID(env, ptr, col, row) IndexValid(env, ptr, col, row) -#define TBL_AND_INDEX_VALID(env, ptr, col, row) TblIndexValid(env, ptr, col, row) -#define TBL_AND_INDEX_INSERT_VALID(env, ptr, col, row) TblIndexInsertValid(env, ptr, col, row) -#define INDEX_AND_TYPE_VALID(env, ptr, col, row, type) IndexAndTypeValid(env, ptr, col, row, type) -#define TBL_AND_INDEX_AND_TYPE_VALID(env, ptr, col, row, type) TblIndexAndTypeValid(env, ptr, col, row, type) -#define TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env, ptr, col, row, type) \ - TblIndexAndTypeInsertValid(env, ptr, col, row, type) - -#define ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) RowColIndexAndTypeValid(env, ptr, col, type) -#define ROW_AND_COL_INDEX_VALID(env, ptr, col) RowColIndexValid(env, ptr, col) +#define TYPE_VALID(env, ptr, col, type) TypeValid(env, ptr, col, type) +#define COL_NULLABLE(env, table_ref, columnKey) ColIsNullable(env, table_ref, columnKey) #else -#define ROW_INDEXES_VALID(env, ptr, start, end, range) (true) -#define ROW_INDEX_VALID(env, ptr, row) (true) -#define ROW_INDEX_VALID_OFFSET(env, ptr, row) (true) -#define TBL_AND_ROW_INDEX_VALID(env, ptr, row) (true) -#define TBL_AND_ROW_INDEX_VALID_OFFSET(env, ptr, row, offset) (true) -#define COL_INDEX_VALID(env, ptr, col) (true) -#define TBL_AND_COL_INDEX_VALID(env, ptr, col) (true) -#define COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) (true) -#define TBL_AND_COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) (true) -#define TBL_AND_COL_INDEX_AND_LINK_OR_LINKLIST(env, ptr, col) (true) -#define TBL_AND_COL_NULLABLE(env, ptr, col) (true) -#define INDEX_VALID(env, ptr, col, row) (true) -#define TBL_AND_INDEX_VALID(env, ptr, col, row) (true) -#define TBL_AND_INDEX_INSERT_VALID(env, ptr, col, row) (true) -#define INDEX_AND_TYPE_VALID(env, ptr, col, row, type) (true) -#define TBL_AND_INDEX_AND_TYPE_VALID(env, ptr, col, row, type) (true) -#define TBL_AND_INDEX_AND_TYPE_INSERT_VALID(env, ptr, col, row, type) (true) - -#define ROW_AND_COL_INDEX_AND_TYPE_VALID(env, ptr, col, type) (true) -#define ROW_AND_COL_INDEX_VALID(env, ptr, col) (true) +#define TYPE_VALID(env, ptr, col, type) (true) +#define COL_NULLABLE(env, ptr, col) (true) #endif @@ -169,269 +118,90 @@ inline jlong to_jlong_or_not_found(size_t res) return (res == realm::not_found) ? jlong(-1) : jlong(res); } -template -inline bool TableIsValid(JNIEnv* env, T* objPtr) +inline jlong to_jlong_or_not_found(realm::ColKey key) { - bool valid = (objPtr != nullptr); - if (valid) { - // Check if Table is valid - if (std::is_same::value) { - valid = TBL(objPtr)->is_attached(); - } - // TODO: Add check for TableView - } - if (!valid) { - realm::jni_util::Log::e("Table %1 is no longer attached!", reinterpret_cast(objPtr)); - ThrowException(env, IllegalState, "Table is no longer valid to operate on."); - } - return valid; -} - -inline bool RowIsValid(JNIEnv* env, realm::Row* rowPtr) -{ - bool valid = (rowPtr != NULL && rowPtr->is_attached()); - if (!valid) { - realm::jni_util::Log::e("Row %1 is no longer attached!", reinterpret_cast(rowPtr)); - ThrowException(env, IllegalState, - "Object is no longer valid to operate on. Was it deleted by another thread?"); - } - return valid; + return bool(key) ? jlong(key.value) : jlong(-1); } -inline bool QueryIsValid(JNIEnv* env, realm::Query* query) +inline jlong to_jlong_or_not_found(realm::ObjKey key) { - return TableIsValid(env, query->get_table().get()); + return bool(key) ? jlong(key.value) : jlong(-1); } - -// Requires an attached Table -template -bool RowIndexesValid(JNIEnv* env, T* pTable, jlong startIndex, jlong endIndex, jlong range) +inline bool TableIsValid(JNIEnv* env, const realm::ConstTableRef table) { - size_t maxIndex = pTable->size(); - if (endIndex == -1) { - endIndex = maxIndex; - } - if (startIndex < 0) { - realm::jni_util::Log::e("startIndex %1 < 0 - invalid!", S64(startIndex)); - ThrowException(env, IndexOutOfBounds, "startIndex < 0."); - return false; - } - if (realm::util::int_greater_than(startIndex, maxIndex)) { - realm::jni_util::Log::e("startIndex %1 > %2 - invalid!", S64(startIndex), S64(maxIndex)); - ThrowException(env, IndexOutOfBounds, "startIndex > available rows."); - return false; - } - - if (realm::util::int_greater_than(endIndex, maxIndex)) { - realm::jni_util::Log::e("endIndex %1 > %2 - invalid!", S64(endIndex), S64(maxIndex)); - ThrowException(env, IndexOutOfBounds, "endIndex > available rows."); - return false; - } - if (startIndex > endIndex) { - realm::jni_util::Log::e("startIndex %1 > endIndex %2 - invalid!", S64(startIndex), S64(endIndex)); - ThrowException(env, IndexOutOfBounds, "startIndex > endIndex."); - return false; - } - - if (range != -1 && range < 0) { - realm::jni_util::Log::e("range %1 < 0 - invalid!", S64(range)); - ThrowException(env, IndexOutOfBounds, "range < 0."); - return false; + if (!table) { + realm::jni_util::Log::e("Table is no longer attached!"); + ThrowException(env, IllegalState, "Table is no longer valid to operate on."); } - return true; } -template -inline bool RowIndexValid(JNIEnv* env, T pTable, jlong rowIndex, bool offset = false) -{ - if (rowIndex < 0) { - ThrowException(env, IndexOutOfBounds, "rowIndex is less than 0."); - return false; - } - size_t size = pTable->size(); - if (size > 0 && offset) { - size -= 1; - } - bool rowErr = realm::util::int_greater_than_or_equal(rowIndex, size); - if (rowErr) { - realm::jni_util::Log::e("rowIndex %1 > %2 - invalid!", S64(rowIndex), S64(size)); - ThrowException(env, IndexOutOfBounds, - "rowIndex > available rows: " + num_to_string(rowIndex) + " > " + num_to_string(size)); - } - return !rowErr; -} - -template -inline bool TblRowIndexValid(JNIEnv* env, T* pTable, jlong rowIndex, bool offset = false) -{ - if (std::is_same::value) { - if (!TableIsValid(env, TBL(pTable))) { - return false; - } - } - return RowIndexValid(env, pTable, rowIndex, offset); -} - -template -inline bool ColIndexValid(JNIEnv* env, T* pTable, jlong columnIndex) +inline bool RowIsValid(JNIEnv* env, realm::Obj* rowPtr) { - if (columnIndex < 0) { - ThrowException(env, IndexOutOfBounds, "columnIndex is less than 0."); - return false; - } - bool colErr = realm::util::int_greater_than_or_equal(columnIndex, pTable->get_column_count()); - if (colErr) { - realm::jni_util::Log::e("columnIndex %1 > %2 - invalid!", S64(columnIndex), S64(pTable->get_column_count())); - ThrowException(env, IndexOutOfBounds, "columnIndex > available columns."); - } - return !colErr; -} - -template -inline bool TblColIndexValid(JNIEnv* env, T* pTable, jlong columnIndex) -{ - if (std::is_same::value) { - if (!TableIsValid(env, TBL(pTable))) { - return false; - } + bool valid = (rowPtr != NULL && rowPtr->is_valid()); + if (!valid) { + realm::jni_util::Log::e("Row %1 is no longer attached!", reinterpret_cast(rowPtr)); + ThrowException(env, IllegalState, + "Object is no longer valid to operate on. Was it deleted by another thread?"); } - return ColIndexValid(env, pTable, columnIndex); -} - -inline bool RowColIndexValid(JNIEnv* env, realm::Row* pRow, jlong columnIndex) -{ - return RowIsValid(env, pRow) && ColIndexValid(env, pRow->get_table(), columnIndex); -} - -template -inline bool IndexValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex) -{ - return ColIndexValid(env, pTable, columnIndex) && RowIndexValid(env, pTable, rowIndex); -} - -template -inline bool TblIndexValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex) -{ - return TableIsValid(env, pTable) && IndexValid(env, pTable, columnIndex, rowIndex); + return valid; } template -inline bool TblIndexInsertValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex) +inline bool TypeValid(JNIEnv* env, T* pTable, jlong columnKey, int expectColType) { - if (!TblColIndexValid(env, pTable, columnIndex)) { + realm::ColKey col_key(columnKey); + auto colType = col_key.get_type(); + if (colType != expectColType) { + realm::jni_util::Log::e("Expected columnType %1, but got %2.", expectColType, colType); + ThrowException(env, IllegalArgument, "ColumnType of '" + std::string(pTable->get_column_name(col_key)) + "' is invalid."); return false; } - bool rowErr = realm::util::int_greater_than(rowIndex, pTable->size() + 1); - if (rowErr) { - realm::jni_util::Log::e("rowIndex %1 > %2 - invalid!", S64(rowIndex), S64(pTable->size())); - ThrowException(env, IndexOutOfBounds, "rowIndex " + num_to_string(rowIndex) + " > available rows " + - num_to_string(pTable->size()) + "."); - } - return !rowErr; + return true; } -template -inline bool TypeValid(JNIEnv* env, T* pTable, jlong columnIndex, int expectColType) +inline bool TypeValid(JNIEnv* env, realm::ConstTableRef table, jlong columnKey, int expectColType) { - size_t col = static_cast(columnIndex); - int colType = pTable->get_column_type(col); + realm::ColKey col_key(columnKey); + auto colType = col_key.get_type(); if (colType != expectColType) { - realm::jni_util::Log::e("Expected columnType %1, but got %2.", expectColType, pTable->get_column_type(col)); - ThrowException(env, IllegalArgument, "ColumnType of '" + std::string(pTable->get_column_name(col)) + "' is invalid."); + realm::jni_util::Log::e("Expected columnType %1, but got %2.", expectColType, colType); + ThrowException(env, IllegalArgument, "ColumnType of '" + std::string(table->get_column_name(col_key)) + "' is invalid."); return false; } return true; } template -inline bool TypeIsLinkLike(JNIEnv* env, T* pTable, jlong columnIndex) +inline bool ColIsNullable(JNIEnv* env, T table_ref, jlong columnKey) { - size_t col = static_cast(columnIndex); - int colType = pTable->get_column_type(col); - if (colType == realm::type_Link || colType == realm::type_LinkList) { - return true; - } - - realm::jni_util::Log::e("Expected columnType %1 or %2, but got %3", realm::type_Link, realm::type_LinkList, - colType); - ThrowException(env, IllegalArgument, "ColumnType of '" + std::string(pTable->get_column_name(col)) + "' is invalid:" - " expected type_Link or type_LinkList"); - return false; -} - -template -inline bool ColIsNullable(JNIEnv* env, T* pTable, jlong columnIndex) -{ - size_t col = static_cast(columnIndex); - int colType = pTable->get_column_type(col); + realm::ColKey col = realm::ColKey(columnKey); + int colType = table_ref->get_column_type(col); if (colType == realm::type_Link) { return true; } if (colType == realm::type_LinkList) { - ThrowException(env, IllegalArgument, "RealmList(" + std::string(pTable->get_column_name(col)) + ") is not nullable."); + ThrowException(env, IllegalArgument, "RealmList(" + std::string(table_ref->get_column_name(col)) + ") is not nullable."); + return false; + } + + // checking for primitive list + if (table_ref->is_list(col)) { + ThrowException(env, IllegalArgument, "RealmList(" + std::string(table_ref->get_column_name(col)) + ") is not nullable."); return false; } - if (pTable->is_nullable(col)) { + if (table_ref->is_nullable(col)) { return true; } realm::jni_util::Log::e("Expected nullable column type"); - ThrowException(env, IllegalArgument, "This field(" + std::string(pTable->get_column_name(col)) + ") is not nullable."); + ThrowException(env, IllegalArgument, "This field(" + std::string(table_ref->get_column_name(col)) + ") is not nullable."); return false; } -template -inline bool ColIndexAndTypeValid(JNIEnv* env, T* pTable, jlong columnIndex, int expectColType) -{ - return ColIndexValid(env, pTable, columnIndex) && TypeValid(env, pTable, columnIndex, expectColType); -} -template -inline bool TblColIndexAndTypeValid(JNIEnv* env, T* pTable, jlong columnIndex, int expectColType) -{ - return TableIsValid(env, pTable) && ColIndexAndTypeValid(env, pTable, columnIndex, expectColType); -} - -template -inline bool TblColIndexAndLinkOrLinkList(JNIEnv* env, T* pTable, jlong columnIndex) -{ - return TableIsValid(env, pTable) && TypeIsLinkLike(env, pTable, columnIndex); -} - -// FIXME Usually this is called after TBL_AND_INDEX_AND_TYPE_VALID which will validate Table as well. -// Try to avoid duplicated checks to improve performance. -template -inline bool TblColIndexAndNullable(JNIEnv* env, T* pTable, jlong columnIndex) -{ - return TableIsValid(env, pTable) && ColIsNullable(env, pTable, columnIndex); -} - -inline bool RowColIndexAndTypeValid(JNIEnv* env, realm::Row* pRow, jlong columnIndex, int expectColType) -{ - return RowIsValid(env, pRow) && ColIndexAndTypeValid(env, pRow->get_table(), columnIndex, expectColType); -} - -template -inline bool IndexAndTypeValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex, int expectColType) -{ - return IndexValid(env, pTable, columnIndex, rowIndex) && TypeValid(env, pTable, columnIndex, expectColType); -} -template -inline bool TblIndexAndTypeValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex, int expectColType) -{ - return TableIsValid(env, pTable) && IndexAndTypeValid(env, pTable, columnIndex, rowIndex, expectColType); -} - -template -inline bool TblIndexAndTypeInsertValid(JNIEnv* env, T* pTable, jlong columnIndex, jlong rowIndex, int expectColType) -{ - return TblIndexInsertValid(env, pTable, columnIndex, rowIndex) && - TypeValid(env, pTable, columnIndex, expectColType); -} - // Utility function for appending StringData, which is returned // by a lot of core functions, and might potentially be NULL. std::string concat_stringdata(const char* message, realm::StringData data); @@ -467,7 +237,7 @@ class JStringAccessor { static constexpr size_t max_string_size = realm::Table::max_string_size; if (m_is_null) { - return realm::StringData(NULL); + return realm::StringData(); } else if (m_size > max_string_size) { THROW_JAVA_EXCEPTION( diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index a391381abe..e91c245c1b 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -81,6 +81,7 @@ abstract class BaseRealm implements Closeable { // Thread pool for all async operations (Query & transaction) static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); + final boolean frozen; // Cache the value in Java, since it is accessed frequently and doesn't change. final long threadId; protected final RealmConfiguration configuration; // Which RealmCache is this Realm associated to. It is null if the Realm instance is opened without being put into a @@ -99,13 +100,13 @@ public void onSchemaChanged() { }; // Create a realm instance and associate it to a RealmCache. - BaseRealm(RealmCache cache, @Nullable OsSchemaInfo schemaInfo) { - this(cache.getConfiguration(), schemaInfo); + BaseRealm(RealmCache cache, @Nullable OsSchemaInfo schemaInfo, OsSharedRealm.VersionID version) { + this(cache.getConfiguration(), schemaInfo, version); this.realmCache = cache; } // Create a realm instance without associating it to any RealmCache. - BaseRealm(final RealmConfiguration configuration, @Nullable OsSchemaInfo schemaInfo) { + BaseRealm(final RealmConfiguration configuration, @Nullable OsSchemaInfo schemaInfo, OsSharedRealm.VersionID version) { this.threadId = Thread.currentThread().getId(); this.configuration = configuration; this.realmCache = null; @@ -133,9 +134,9 @@ public void onInit(OsSharedRealm sharedRealm) { .migrationCallback(migrationCallback) .schemaInfo(schemaInfo) .initializationCallback(initializationCallback); - this.sharedRealm = OsSharedRealm.getInstance(configBuilder); + this.sharedRealm = OsSharedRealm.getInstance(configBuilder, version); + this.frozen = sharedRealm.isFrozen(); this.shouldCloseSharedRealm = true; - sharedRealm.registerSchemaChangedCallback(schemaChangedCallback); } @@ -147,6 +148,7 @@ public void onInit(OsSharedRealm sharedRealm) { this.realmCache = null; this.sharedRealm = sharedRealm; + this.frozen = sharedRealm.isFrozen(); this.shouldCloseSharedRealm = false; } @@ -210,6 +212,9 @@ protected void addListener(RealmChangeListener listener } checkIfValid(); sharedRealm.capabilities.checkCanDeliverNotification(LISTENER_NOT_ALLOWED_MESSAGE); + if (frozen) { + throw new IllegalStateException("It is not possible to add a change listener to a frozen Realm since it never changes."); + } //noinspection unchecked sharedRealm.realmNotifier.addChangeListener((T) this, listener); } @@ -240,17 +245,33 @@ protected void removeListener(RealmChangeListener liste * when subscribed to. Items will continually be emitted as the Realm is updated - * {@code onComplete} will never be called. *

            + * Items emitted from Realm Flowables are frozen (See {@link #freeze()}. This means that they + * are immutable and can be read on any thread. + *

            + * Realm Flowables always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommend to observe the values on a computation + * scheduler: + *

            + * {@code + * realm.asFlowable() + * .observeOn(Schedulers.computation()) + * .map(rxRealm -> doExpensiveWork(rxRealm)) + * .observeOn(AndroidSchedulers.mainThread()) + * .subscribe( ... ); + * } + *

            * If you would like the {@code asFlowable()} to stop emitting items, you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: *

            *

                  * {@code
            -     * realm.asFlowable().first().subscribe( ... ) // You only get the results once
            +     * realm.asFlowable().first().subscribe( ... ); // You only get the results once
                  * }
                  * 
            * * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. * @see RxJava and Realm */ public abstract Flowable asFlowable(); @@ -324,7 +345,9 @@ public void writeEncryptedCopyTo(File destination, byte[] key) { * @throws IllegalStateException if calling this from within a transaction or from a Looper thread. * @throws RealmMigrationNeededException on typed {@link Realm} if the latest version contains * incompatible schema changes. + * @deprecated this method will be removed on the next-major release. */ + @Deprecated public boolean waitForChange() { checkIfValid(); if (isInTransaction()) { @@ -349,7 +372,9 @@ public boolean waitForChange() { * called waitForChange. * * @throws IllegalStateException if the {@link io.realm.Realm} instance has already been closed. + * @deprecated this method will be removed in the next-major release */ + @Deprecated public void stopWaitForChange() { if (realmCache != null) { realmCache.invokeWithLock(new RealmCache.Callback0() { @@ -429,6 +454,36 @@ public void cancelTransaction() { sharedRealm.cancelTransaction(); } + /** + * Returns a frozen snapshot of the current Realm. This Realm can be read and queried from any thread without throwing + * an {@link IllegalStateException}. A frozen Realm has its own lifecycle and can be closed by calling {@link #close()}, + * but fully closing the Realm that spawned the frozen copy will also close the frozen Realm. + *

            + * Frozen data can be queried as normal, but trying to mutate it in any way or attempting to register any listener will + * throw an {@link IllegalStateException}. + *

            + * Note: Keeping a large number of Realms with different versions alive can have a negative impact on the filesize + * of the Realm. In order to avoid such a situation, it is possible to set {@link RealmConfiguration.Builder#maxNumberOfActiveVersions(long)}. + * + * @return a frozen copy of this Realm. + * @throws IllegalStateException if this method is called from inside a write transaction. + */ + public abstract BaseRealm freeze(); + + /** + * Returns whether or not this Realm is frozen. + * + * @return {@code true} if the Realm is frozen, {@code false} if it is not. + * @see #freeze() + */ + public boolean isFrozen() { + // This method needs to be threadsafe even for live Realms, so don't call {@link #checkIfValid} + if (sharedRealm == null || sharedRealm.isClosed()) { + throw new IllegalStateException(BaseRealm.CLOSED_REALM_MESSAGE); + } + return frozen; + } + /** * Checks if a Realm's underlying resources are still available or not getting accessed from the wrong thread. */ @@ -438,7 +493,7 @@ protected void checkIfValid() { } // Checks if we are in the right thread. - if (threadId != Thread.currentThread().getId()) { + if (!frozen && threadId != Thread.currentThread().getId()) { throw new IllegalStateException(BaseRealm.INCORRECT_THREAD_MESSAGE); } } @@ -554,7 +609,7 @@ public ObjectPrivileges getPrivileges(RealmModel object) { */ @Override public void close() { - if (this.threadId != Thread.currentThread().getId()) { + if (!frozen && this.threadId != Thread.currentThread().getId()) { throw new IllegalStateException(INCORRECT_THREAD_CLOSE_MESSAGE); } @@ -583,7 +638,7 @@ void doClose() { * @throws IllegalStateException if attempting to close from another thread. */ public boolean isClosed() { - if (this.threadId != Thread.currentThread().getId()) { + if (!frozen && this.threadId != Thread.currentThread().getId()) { throw new IllegalStateException(INCORRECT_THREAD_MESSAGE); } @@ -622,9 +677,9 @@ E get(@Nullable Class clazz, @Nullable String dynamicC return result; } - E get(Class clazz, long rowIndex, boolean acceptDefaultValue, List excludeFields) { + E get(Class clazz, long rowKey, boolean acceptDefaultValue, List excludeFields) { Table table = getSchema().getTable(clazz); - UncheckedRow row = table.getUncheckedRow(rowIndex); + UncheckedRow row = table.getUncheckedRow(rowKey); return configuration.getSchemaMediator().newInstance(clazz, this, row, getSchema().getColumnInfo(clazz), acceptDefaultValue, excludeFields); } @@ -701,7 +756,7 @@ public void run() { * @return {@code true} if compaction succeeded, {@code false} otherwise. */ static boolean compactRealm(final RealmConfiguration configuration) { - OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configuration); + OsSharedRealm sharedRealm = OsSharedRealm.getInstance(configuration, OsSharedRealm.VersionID.LIVE); Boolean result = sharedRealm.compact(); sharedRealm.close(); return result; @@ -760,7 +815,7 @@ public void onResult(int count) { OsSharedRealm sharedRealm = null; try { sharedRealm = - OsSharedRealm.getInstance(configBuilder); + OsSharedRealm.getInstance(configBuilder, OsSharedRealm.VersionID.LIVE); } finally { if (sharedRealm != null) { sharedRealm.close(); diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 92d6e12c1f..9913954226 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -56,8 +56,8 @@ public class DynamicRealm extends BaseRealm { private final RealmSchema schema; - private DynamicRealm(final RealmCache cache) { - super(cache, null); + private DynamicRealm(final RealmCache cache, OsSharedRealm.VersionID version) { + super(cache, null, version); RealmCache.invokeWithGlobalRefCount(cache.getConfiguration(), new RealmCache.Callback() { @Override public void onResult(int count) { @@ -272,8 +272,8 @@ public void executeTransaction(Transaction transaction) { * * @return a {@link DynamicRealm} instance. */ - static DynamicRealm createInstance(RealmCache cache) { - return new DynamicRealm(cache); + static DynamicRealm createInstance(RealmCache cache, OsSharedRealm.VersionID version) { + return new DynamicRealm(cache, version); } /** @@ -343,7 +343,7 @@ public boolean isEmpty() { // } // Table table = sharedRealm.getTable("class___Class"); // TableQuery query = table.where() -// .equalTo(new long[]{table.getColumnIndex("name")}, new long[]{NativeObject.NULLPTR}, className); +// .equalTo(new long[]{table.getObjectKey("name")}, new long[]{NativeObject.NULLPTR}, className); // OsResults result = OsResults.createFromQuery(sharedRealm, query); // return new RealmResults<>(this, result, ClassPermissions.class).first(null); // } @@ -394,6 +394,24 @@ public RealmSchema getSchema() { return schema; } + /** + * {@inheritDoc} + */ + @Override + public DynamicRealm freeze() { + // In some cases a Read transaction has not begun for the Realm, which means + // we cannot read the current version. In that case, do some work that will create the + // read transaction. + OsSharedRealm.VersionID version; + try { + version = sharedRealm.getVersionID(); + } catch (IllegalStateException e) { + getVersion(); + version = sharedRealm.getVersionID(); + } + return RealmCache.createRealmOrGetFromCache(configuration, DynamicRealm.class, version); + } + /** * Set the schema version of this dynamic realm to the given version number. If the meta table doesn't exist, this * will create the meta table first. diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index c4e79b583d..24367b165d 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -93,23 +93,23 @@ public DynamicRealmObject(RealmModel obj) { public E get(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + RealmFieldType type = proxyState.getRow$realm().getColumnType(columnKey); switch (type) { case BOOLEAN: - return (E) Boolean.valueOf(proxyState.getRow$realm().getBoolean(columnIndex)); + return (E) Boolean.valueOf(proxyState.getRow$realm().getBoolean(columnKey)); case INTEGER: - return (E) Long.valueOf(proxyState.getRow$realm().getLong(columnIndex)); + return (E) Long.valueOf(proxyState.getRow$realm().getLong(columnKey)); case FLOAT: - return (E) Float.valueOf(proxyState.getRow$realm().getFloat(columnIndex)); + return (E) Float.valueOf(proxyState.getRow$realm().getFloat(columnKey)); case DOUBLE: - return (E) Double.valueOf(proxyState.getRow$realm().getDouble(columnIndex)); + return (E) Double.valueOf(proxyState.getRow$realm().getDouble(columnKey)); case STRING: - return (E) proxyState.getRow$realm().getString(columnIndex); + return (E) proxyState.getRow$realm().getString(columnKey); case BINARY: - return (E) proxyState.getRow$realm().getBinaryByteArray(columnIndex); + return (E) proxyState.getRow$realm().getBinaryByteArray(columnKey); case DATE: - return (E) proxyState.getRow$realm().getDate(columnIndex); + return (E) proxyState.getRow$realm().getDate(columnKey); case OBJECT: return (E) getObject(fieldName); case LIST: @@ -133,11 +133,11 @@ public E get(String fieldName) { public boolean getBoolean(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); try { - return proxyState.getRow$realm().getBoolean(columnIndex); + return proxyState.getRow$realm().getBoolean(columnKey); } catch (IllegalArgumentException e) { - checkFieldType(fieldName, columnIndex, RealmFieldType.BOOLEAN); + checkFieldType(fieldName, columnKey, RealmFieldType.BOOLEAN); throw e; } } @@ -186,11 +186,11 @@ public short getShort(String fieldName) { public long getLong(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); try { - return proxyState.getRow$realm().getLong(columnIndex); + return proxyState.getRow$realm().getLong(columnKey); } catch (IllegalArgumentException e) { - checkFieldType(fieldName, columnIndex, RealmFieldType.INTEGER); + checkFieldType(fieldName, columnKey, RealmFieldType.INTEGER); throw e; } } @@ -224,11 +224,11 @@ public byte getByte(String fieldName) { public float getFloat(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); try { - return proxyState.getRow$realm().getFloat(columnIndex); + return proxyState.getRow$realm().getFloat(columnKey); } catch (IllegalArgumentException e) { - checkFieldType(fieldName, columnIndex, RealmFieldType.FLOAT); + checkFieldType(fieldName, columnKey, RealmFieldType.FLOAT); throw e; } } @@ -247,11 +247,11 @@ public float getFloat(String fieldName) { public double getDouble(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); try { - return proxyState.getRow$realm().getDouble(columnIndex); + return proxyState.getRow$realm().getDouble(columnKey); } catch (IllegalArgumentException e) { - checkFieldType(fieldName, columnIndex, RealmFieldType.DOUBLE); + checkFieldType(fieldName, columnKey, RealmFieldType.DOUBLE); throw e; } } @@ -266,11 +266,11 @@ public double getDouble(String fieldName) { public byte[] getBlob(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); try { - return proxyState.getRow$realm().getBinaryByteArray(columnIndex); + return proxyState.getRow$realm().getBinaryByteArray(columnKey); } catch (IllegalArgumentException e) { - checkFieldType(fieldName, columnIndex, RealmFieldType.BINARY); + checkFieldType(fieldName, columnKey, RealmFieldType.BINARY); throw e; } } @@ -285,11 +285,11 @@ public byte[] getBlob(String fieldName) { public String getString(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); try { - return proxyState.getRow$realm().getString(columnIndex); + return proxyState.getRow$realm().getString(columnKey); } catch (IllegalArgumentException e) { - checkFieldType(fieldName, columnIndex, RealmFieldType.STRING); + checkFieldType(fieldName, columnKey, RealmFieldType.STRING); throw e; } } @@ -304,12 +304,12 @@ public String getString(String fieldName) { public Date getDate(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - checkFieldType(fieldName, columnIndex, RealmFieldType.DATE); - if (proxyState.getRow$realm().isNull(columnIndex)) { + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + checkFieldType(fieldName, columnKey, RealmFieldType.DATE); + if (proxyState.getRow$realm().isNull(columnKey)) { return null; } else { - return proxyState.getRow$realm().getDate(columnIndex); + return proxyState.getRow$realm().getDate(columnKey); } } @@ -324,13 +324,13 @@ public Date getDate(String fieldName) { public DynamicRealmObject getObject(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - checkFieldType(fieldName, columnIndex, RealmFieldType.OBJECT); - if (proxyState.getRow$realm().isNullLink(columnIndex)) { + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + checkFieldType(fieldName, columnKey, RealmFieldType.OBJECT); + if (proxyState.getRow$realm().isNullLink(columnKey)) { return null; } else { - long linkRowIndex = proxyState.getRow$realm().getLink(columnIndex); - CheckedRow linkRow = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getCheckedRow(linkRowIndex); + long linkObjectKey = proxyState.getRow$realm().getLink(columnKey); + CheckedRow linkRow = proxyState.getRow$realm().getTable().getLinkTarget(columnKey).getCheckedRow(linkObjectKey); return new DynamicRealmObject(proxyState.getRealm$realm(), linkRow); } } @@ -347,15 +347,15 @@ public DynamicRealmObject getObject(String fieldName) { public RealmList getList(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); try { - OsList osList = proxyState.getRow$realm().getModelList(columnIndex); + OsList osList = proxyState.getRow$realm().getModelList(columnKey); //noinspection ConstantConditions @Nonnull String className = osList.getTargetTable().getClassName(); return new RealmList<>(className, osList, proxyState.getRealm$realm()); } catch (IllegalArgumentException e) { - checkFieldType(fieldName, columnIndex, RealmFieldType.LIST); + checkFieldType(fieldName, columnKey, RealmFieldType.LIST); throw e; } } @@ -377,13 +377,13 @@ public RealmList getList(String fieldName, Class primitiveType) { if (primitiveType == null) { throw new IllegalArgumentException("Non-null 'primitiveType' required."); } - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); RealmFieldType realmType = classToRealmType(primitiveType); try { - OsList osList = proxyState.getRow$realm().getValueList(columnIndex, realmType); + OsList osList = proxyState.getRow$realm().getValueList(columnKey, realmType); return new RealmList<>(primitiveType, osList, proxyState.getRealm$realm()); } catch (IllegalArgumentException e) { - checkFieldType(fieldName, columnIndex, realmType); + checkFieldType(fieldName, columnKey, realmType); throw e; } } @@ -421,11 +421,11 @@ private RealmFieldType classToRealmType(Class primitiveType) { public boolean isNull(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + RealmFieldType type = proxyState.getRow$realm().getColumnType(columnKey); switch (type) { case OBJECT: - return proxyState.getRow$realm().isNullLink(columnIndex); + return proxyState.getRow$realm().isNullLink(columnKey); case BOOLEAN: case INTEGER: case FLOAT: @@ -433,7 +433,7 @@ public boolean isNull(String fieldName) { case STRING: case BINARY: case DATE: - return proxyState.getRow$realm().isNull(columnIndex); + return proxyState.getRow$realm().isNull(columnKey); case LIST: case LINKING_OBJECTS: case INTEGER_LIST: @@ -472,12 +472,7 @@ public boolean hasField(String fieldName) { */ public String[] getFieldNames() { proxyState.getRealm$realm().checkIfValid(); - - String[] keys = new String[(int) proxyState.getRow$realm().getColumnCount()]; - for (int i = 0; i < keys.length; i++) { - keys[i] = proxyState.getRow$realm().getColumnName(i); - } - return keys; + return proxyState.getRow$realm().getColumnNames(); } /** @@ -500,8 +495,8 @@ public void set(String fieldName, Object value) { String strValue = isString ? (String) value : null; // Does implicit conversion if needed. - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + RealmFieldType type = proxyState.getRow$realm().getColumnType(columnKey); if (isString && type != RealmFieldType.STRING) { switch (type) { case BOOLEAN: @@ -577,8 +572,8 @@ private void setValue(String fieldName, Object value) { public void setBoolean(String fieldName, boolean value) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - proxyState.getRow$realm().setBoolean(columnIndex, value); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + proxyState.getRow$realm().setBoolean(columnKey, value); } /** @@ -593,8 +588,8 @@ public void setShort(String fieldName, short value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - proxyState.getRow$realm().setLong(columnIndex, value); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + proxyState.getRow$realm().setLong(columnKey, value); } /** @@ -609,8 +604,8 @@ public void setInt(String fieldName, int value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - proxyState.getRow$realm().setLong(columnIndex, value); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + proxyState.getRow$realm().setLong(columnKey, value); } /** @@ -625,8 +620,8 @@ public void setLong(String fieldName, long value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - proxyState.getRow$realm().setLong(columnIndex, value); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + proxyState.getRow$realm().setLong(columnKey, value); } /** @@ -641,8 +636,8 @@ public void setByte(String fieldName, byte value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - proxyState.getRow$realm().setLong(columnIndex, value); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + proxyState.getRow$realm().setLong(columnKey, value); } /** @@ -655,8 +650,8 @@ public void setByte(String fieldName, byte value) { public void setFloat(String fieldName, float value) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - proxyState.getRow$realm().setFloat(columnIndex, value); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + proxyState.getRow$realm().setFloat(columnKey, value); } /** @@ -669,8 +664,8 @@ public void setFloat(String fieldName, float value) { public void setDouble(String fieldName, double value) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - proxyState.getRow$realm().setDouble(columnIndex, value); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + proxyState.getRow$realm().setDouble(columnKey, value); } /** @@ -685,8 +680,8 @@ public void setString(String fieldName, @Nullable String value) { proxyState.getRealm$realm().checkIfValid(); checkIsPrimaryKey(fieldName); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - proxyState.getRow$realm().setString(columnIndex, value); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + proxyState.getRow$realm().setString(columnKey, value); } /** @@ -699,8 +694,8 @@ public void setString(String fieldName, @Nullable String value) { public void setBlob(String fieldName, @Nullable byte[] value) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - proxyState.getRow$realm().setBinaryByteArray(columnIndex, value); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + proxyState.getRow$realm().setBinaryByteArray(columnKey, value); } /** @@ -713,11 +708,11 @@ public void setBlob(String fieldName, @Nullable byte[] value) { public void setDate(String fieldName, @Nullable Date value) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); if (value == null) { - proxyState.getRow$realm().setNull(columnIndex); + proxyState.getRow$realm().setNull(columnKey); } else { - proxyState.getRow$realm().setDate(columnIndex, value); + proxyState.getRow$realm().setDate(columnKey, value); } } @@ -732,9 +727,9 @@ public void setDate(String fieldName, @Nullable Date value) { public void setObject(String fieldName, @Nullable DynamicRealmObject value) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); if (value == null) { - proxyState.getRow$realm().nullifyLink(columnIndex); + proxyState.getRow$realm().nullifyLink(columnKey); } else { if (value.proxyState.getRealm$realm() == null || value.proxyState.getRow$realm() == null) { throw new IllegalArgumentException("Cannot link to objects that are not part of the Realm."); @@ -742,14 +737,14 @@ public void setObject(String fieldName, @Nullable DynamicRealmObject value) { if (proxyState.getRealm$realm() != value.proxyState.getRealm$realm()) { throw new IllegalArgumentException("Cannot add an object from another Realm instance."); } - Table table = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex); + Table table = proxyState.getRow$realm().getTable().getLinkTarget(columnKey); Table inputTable = value.proxyState.getRow$realm().getTable(); if (!table.hasSameSchema(inputTable)) { throw new IllegalArgumentException(String.format(Locale.US, "Type of object is wrong. Was %s, expected %s", inputTable.getName(), table.getName())); } - proxyState.getRow$realm().setLink(columnIndex, value.proxyState.getRow$realm().getIndex()); + proxyState.getRow$realm().setLink(columnKey, value.proxyState.getRow$realm().getObjectKey()); } } @@ -774,8 +769,8 @@ public void setList(String fieldName, RealmList list) { } // Find type of list in Realm - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - final RealmFieldType columnType = proxyState.getRow$realm().getColumnType(columnIndex); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + final RealmFieldType columnType = proxyState.getRow$realm().getColumnType(columnKey); switch (columnType) { case LIST: @@ -807,8 +802,8 @@ public void setList(String fieldName, RealmList list) { } private void setModelList(String fieldName, RealmList list) { - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - OsList osList = proxyState.getRow$realm().getModelList(columnIndex); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + OsList osList = proxyState.getRow$realm().getModelList(columnKey); Table linkTargetTable = osList.getTargetTable(); //noinspection ConstantConditions @Nonnull @@ -846,7 +841,7 @@ private void setModelList(String fieldName, RealmList list) obj.realmGet$proxyState().getRow$realm().getTable().getClassName(), linkTargetTableName)); } - indices[i] = obj.realmGet$proxyState().getRow$realm().getIndex(); + indices[i] = obj.realmGet$proxyState().getRow$realm().getObjectKey(); } osList.removeAll(); @@ -857,8 +852,8 @@ private void setModelList(String fieldName, RealmList list) @SuppressWarnings("unchecked") private void setValueList(String fieldName, RealmList list, RealmFieldType primitiveType) { - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - OsList osList = proxyState.getRow$realm().getValueList(columnIndex, primitiveType); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + OsList osList = proxyState.getRow$realm().getValueList(columnKey, primitiveType); Class elementClass; switch(primitiveType) { @@ -933,13 +928,13 @@ private ManagedListOperator getOperator(BaseRealm realm, OsList osList, R public void setNull(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + RealmFieldType type = proxyState.getRow$realm().getColumnType(columnKey); if (type == RealmFieldType.OBJECT) { - proxyState.getRow$realm().nullifyLink(columnIndex); + proxyState.getRow$realm().nullifyLink(columnKey); } else { checkIsPrimaryKey(fieldName); - proxyState.getRow$realm().setNull(columnIndex); + proxyState.getRow$realm().setNull(columnKey); } } @@ -963,8 +958,8 @@ public String getType() { public RealmFieldType getFieldType(String fieldName) { proxyState.getRealm$realm().checkIfValid(); - long columnIndex = proxyState.getRow$realm().getColumnIndex(fieldName); - return proxyState.getRow$realm().getColumnType(columnIndex); + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + return proxyState.getRow$realm().getColumnType(columnKey); } private void checkFieldType(String fieldName, long columnIndex, RealmFieldType expectedType) { @@ -1003,7 +998,7 @@ public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long rowIndex = proxyState.getRow$realm().getIndex(); + long rowIndex = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); @@ -1038,14 +1033,14 @@ public boolean equals(Object o) { return false; } - return proxyState.getRow$realm().getIndex() == other.proxyState.getRow$realm().getIndex(); + return proxyState.getRow$realm().getObjectKey() == other.proxyState.getRow$realm().getObjectKey(); } @Override public String toString() { proxyState.getRealm$realm().checkIfValid(); - if (!proxyState.getRow$realm().isAttached()) { + if (!proxyState.getRow$realm().isValid()) { return "Invalid object"; } @@ -1053,61 +1048,61 @@ public String toString() { StringBuilder sb = new StringBuilder(className + " = dynamic["); String[] fields = getFieldNames(); for (String field : fields) { - long columnIndex = proxyState.getRow$realm().getColumnIndex(field); - RealmFieldType type = proxyState.getRow$realm().getColumnType(columnIndex); + long columnKey = proxyState.getRow$realm().getColumnKey(field); + RealmFieldType type = proxyState.getRow$realm().getColumnType(columnKey); sb.append("{"); sb.append(field).append(":"); switch (type) { case BOOLEAN: - sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getBoolean(columnIndex)); + sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getBoolean(columnKey)); break; case INTEGER: - sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getLong(columnIndex)); + sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getLong(columnKey)); break; case FLOAT: - sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getFloat(columnIndex)); + sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getFloat(columnKey)); break; case DOUBLE: - sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getDouble(columnIndex)); + sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getDouble(columnKey)); break; case STRING: - sb.append(proxyState.getRow$realm().getString(columnIndex)); + sb.append(proxyState.getRow$realm().getString(columnKey)); break; case BINARY: - sb.append(Arrays.toString(proxyState.getRow$realm().getBinaryByteArray(columnIndex))); + sb.append(Arrays.toString(proxyState.getRow$realm().getBinaryByteArray(columnKey))); break; case DATE: - sb.append(proxyState.getRow$realm().isNull(columnIndex) ? "null" : proxyState.getRow$realm().getDate(columnIndex)); + sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getDate(columnKey)); break; case OBJECT: - sb.append(proxyState.getRow$realm().isNullLink(columnIndex) + sb.append(proxyState.getRow$realm().isNullLink(columnKey) ? "null" - : proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName()); + : proxyState.getRow$realm().getTable().getLinkTarget(columnKey).getClassName()); break; case LIST: - String targetClassName = proxyState.getRow$realm().getTable().getLinkTarget(columnIndex).getClassName(); - sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getModelList(columnIndex).size())); + String targetClassName = proxyState.getRow$realm().getTable().getLinkTarget(columnKey).getClassName(); + sb.append(String.format(Locale.US, "RealmList<%s>[%s]", targetClassName, proxyState.getRow$realm().getModelList(columnKey).size())); break; case INTEGER_LIST: - sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); break; case BOOLEAN_LIST: - sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); break; case STRING_LIST: - sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); break; case BINARY_LIST: - sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); break; case DATE_LIST: - sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); break; case FLOAT_LIST: - sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); break; case DOUBLE_LIST: - sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnIndex, type).size())); + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); break; default: sb.append("?"); diff --git a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java new file mode 100644 index 0000000000..d6bacd2a75 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java @@ -0,0 +1,201 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ +package io.realm; + +import java.util.Date; + +import io.realm.internal.InvalidRow; +import io.realm.internal.OsList; +import io.realm.internal.OsSharedRealm; +import io.realm.internal.Row; +import io.realm.internal.Table; + + +/** + * A PendingRow that has been frozen. This behaves in many ways similar + * to a deleted Row, but will report {@link #isLoaded()} as {@code as false}. + */ +public enum FrozenPendingRow implements Row { + INSTANCE; + + private static final String QUERY_NOT_RETURNED_MESSAGE = + "This object was frozen while a query for it was still running."; + + @Override + public long getColumnCount() { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public String[] getColumnNames() { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public long getColumnKey(String columnName) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public RealmFieldType getColumnType(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public Table getTable() { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public long getObjectKey() { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public long getLong(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public boolean getBoolean(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public float getFloat(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public double getDouble(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public Date getDate(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public String getString(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public byte[] getBinaryByteArray(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public long getLink(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public boolean isNullLink(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public OsList getModelList(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public OsList getValueList(long columnKey, RealmFieldType fieldType) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setLong(long columnKey, long value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setBoolean(long columnKey, boolean value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setFloat(long columnKey, float value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setDouble(long columnKey, double value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setDate(long columnKey, Date date) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setString(long columnKey, String value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setBinaryByteArray(long columnKey, byte[] data) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setLink(long columnKey, long value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void nullifyLink(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public boolean isNull(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setNull(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public void checkIfAttached() { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public boolean hasColumn(String fieldName) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public Row freeze(OsSharedRealm frozenRealm) { + return InvalidRow.INSTANCE; + } + + @Override + public boolean isLoaded() { + return false; + } +} diff --git a/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java index 46b8507beb..b7cc1d25ff 100644 --- a/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java @@ -115,7 +115,7 @@ public RealmObjectSchema transform(Function function) { * @throws IllegalArgumentException if a proper FieldDescriptor could not be created. */ @Override - FieldDescriptor getColumnIndices(String publicJavaNameDescription, RealmFieldType... validColumnTypes) { + FieldDescriptor getFieldDescriptors(String publicJavaNameDescription, RealmFieldType... validColumnTypes) { return FieldDescriptor.createStandardFieldDescriptor(getSchemaConnector(), getTable(), publicJavaNameDescription, validColumnTypes); } } diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmInteger.java b/realm/realm-library/src/main/java/io/realm/MutableRealmInteger.java index c1ec454d80..31ddcaac33 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmInteger.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmInteger.java @@ -114,6 +114,11 @@ public boolean isValid() { return true; } + @Override + public boolean isFrozen() { + return false; + } + @Override public void set(@Nullable Long newValue) { value = newValue; @@ -158,7 +163,7 @@ public final boolean isManaged() { @Override public final boolean isValid() { - return !getRealm().isClosed() && getRow().isAttached(); + return !getRealm().isClosed() && getRow().isValid(); } @Override @@ -190,7 +195,7 @@ public final void set(@Nullable Long value) { public final void increment(long inc) { getRealm().checkIfValidAndInTransaction(); Row row = getRow(); - row.getTable().incrementLong(getColumnIndex(), row.getIndex(), inc); + row.getTable().incrementLong(getColumnIndex(), row.getObjectKey(), inc); } @Override @@ -198,6 +203,11 @@ public final void decrement(long dec) { increment(-dec); } + @Override + public boolean isFrozen() { + return getRealm().isFrozen(); + } + private BaseRealm getRealm() { return getProxyState().getRealm$realm(); } @@ -209,7 +219,7 @@ private Row getRow() { private void setValue(@Nullable Long value, boolean isDefault) { Row row = getRow(); Table table = row.getTable(); - long rowIndex = row.getIndex(); + long rowIndex = row.getObjectKey(); long columnIndex = getColumnIndex(); if (value == null) { table.setNull(columnIndex, rowIndex, isDefault); diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index 277a665446..a088066b50 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -21,7 +21,10 @@ import javax.annotation.Nonnull; import io.realm.internal.OsObjectStore; +import io.realm.internal.OsResults; import io.realm.internal.Table; +import io.realm.internal.UncheckedRow; +import io.realm.internal.core.DescriptorOrdering; import io.realm.internal.fields.FieldDescriptor; /** @@ -109,12 +112,12 @@ public RealmObjectSchema addField(String fieldName, Class fieldType, FieldAtt nullable = false; } - long columnIndex = table.addColumn(metadata.fieldType, fieldName, nullable); + long columnKey = table.addColumn(metadata.fieldType, fieldName, nullable); try { addModifiers(fieldName, attributes); } catch (Exception e) { // Modifiers have been removed by the addModifiers method() - table.removeColumn(columnIndex); + table.removeColumn(columnKey); throw e; } return this; @@ -162,12 +165,12 @@ public RealmObjectSchema removeField(String fieldName) { if (!hasField(fieldName)) { throw new IllegalStateException(fieldName + " does not exist."); } - long columnIndex = getColumnIndex(fieldName); + long columnKey = getColumnKey(fieldName); String className = getClassName(); if (fieldName.equals(OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, className))) { OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, className, fieldName); } - table.removeColumn(columnIndex); + table.removeColumn(columnKey); return this; } @@ -178,10 +181,10 @@ public RealmObjectSchema renameField(String currentFieldName, String newFieldNam checkFieldExists(currentFieldName); checkLegalName(newFieldName); checkFieldNameIsAvailable(newFieldName); - long columnIndex = getColumnIndex(currentFieldName); - table.renameColumn(columnIndex, newFieldName); + long columnKey = getColumnKey(currentFieldName); + table.renameColumn(columnKey, newFieldName); - // ATTENTION: We don't need to re-set the PK table here since the column index won't be changed when renaming. + // ATTENTION: We don't need to re-set the PK table here since the column key won't be changed when renaming. return this; } @@ -190,11 +193,11 @@ public RealmObjectSchema renameField(String currentFieldName, String newFieldNam public RealmObjectSchema addIndex(String fieldName) { checkLegalName(fieldName); checkFieldExists(fieldName); - long columnIndex = getColumnIndex(fieldName); - if (table.hasSearchIndex(columnIndex)) { + long columnKey = getColumnKey(fieldName); + if (table.hasSearchIndex(columnKey)) { throw new IllegalStateException(fieldName + " already has an index."); } - table.addSearchIndex(columnIndex); + table.addSearchIndex(columnKey); return this; } @@ -203,11 +206,11 @@ public RealmObjectSchema removeIndex(String fieldName) { realm.checkNotInSync(); // Destructive modifications are not permitted. checkLegalName(fieldName); checkFieldExists(fieldName); - long columnIndex = getColumnIndex(fieldName); - if (!table.hasSearchIndex(columnIndex)) { + long columnKey = getColumnKey(fieldName); + if (!table.hasSearchIndex(columnKey)) { throw new IllegalStateException("Field is not indexed: " + fieldName); } - table.removeSearchIndex(columnIndex); + table.removeSearchIndex(columnKey); return this; } @@ -222,10 +225,11 @@ public RealmObjectSchema addPrimaryKey(String fieldName) { String.format(Locale.ENGLISH, "Field '%s' has been already defined as primary key.", currentPKField)); } - long columnIndex = getColumnIndex(fieldName); - if (!table.hasSearchIndex(columnIndex)) { + long columnKey = getColumnKey(fieldName); + final RealmFieldType fieldType = getFieldType(fieldName); + if (fieldType != RealmFieldType.STRING && !table.hasSearchIndex(columnKey)) { // No exception will be thrown since adding PrimaryKey implies the column has an index. - table.addSearchIndex(columnIndex); + table.addSearchIndex(columnKey); } OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, getClassName(), fieldName); return this; @@ -238,9 +242,9 @@ public RealmObjectSchema removePrimaryKey() { if (pkField == null) { throw new IllegalStateException(getClassName() + " doesn't have a primary key."); } - long columnIndex = table.getColumnIndex(pkField); - if (table.hasSearchIndex(columnIndex)) { - table.removeSearchIndex(columnIndex); + long columnKey = table.getColumnKey(pkField); + if (table.hasSearchIndex(columnKey)) { + table.removeSearchIndex(columnKey); } OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, getClassName(), null); return this; @@ -248,9 +252,9 @@ public RealmObjectSchema removePrimaryKey() { @Override public RealmObjectSchema setRequired(String fieldName, boolean required) { - long columnIndex = table.getColumnIndex(fieldName); + long columnKey = table.getColumnKey(fieldName); boolean currentColumnRequired = isRequired(fieldName); - RealmFieldType type = table.getColumnType(columnIndex); + RealmFieldType type = table.getColumnType(columnKey); if (type == RealmFieldType.OBJECT) { throw new IllegalArgumentException("Cannot modify the required state for RealmObject references: " + fieldName); @@ -266,9 +270,18 @@ public RealmObjectSchema setRequired(String fieldName, boolean required) { } if (required) { - table.convertColumnToNotNullable(columnIndex); + try { + table.convertColumnToNotNullable(columnKey); + } catch (IllegalArgumentException e) { + // Preserve old behaviour instead of throwing the rather non-descript Core error + if (e.getMessage().contains("Attempted to insert null into non-nullable column")) { + throw new IllegalStateException(String.format("The primary key field '%s' has 'null' values stored.", fieldName)); + } else { + throw e; + } + } } else { - table.convertColumnToNullable(columnIndex); + table.convertColumnToNullable(columnKey); } return this; } @@ -283,10 +296,18 @@ public RealmObjectSchema setNullable(String fieldName, boolean nullable) { public RealmObjectSchema transform(Function function) { //noinspection ConstantConditions if (function != null) { - long size = table.size(); - for (long i = 0; i < size; i++) { - function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i))); - } + + OsResults result = OsResults.createFromQuery(realm.sharedRealm, table.where(), new DescriptorOrdering()); + OsResults snapshot = result.createSnapshot(); + OsResults.ListIterator listIterator = new OsResults.ListIterator(snapshot, 0) { + + @Override + protected Object convertRowToObject(UncheckedRow row) { + function.apply(new DynamicRealmObject(realm, row)); + return null; + } + }; + while (listIterator.hasNext()) listIterator.next(); } return this; @@ -301,7 +322,7 @@ public RealmObjectSchema transform(Function function) { * @throws IllegalArgumentException if a proper FieldDescriptor could not be created. */ @Override - FieldDescriptor getColumnIndices(String internalColumnNameDescription, RealmFieldType... validColumnTypes) { + FieldDescriptor getFieldDescriptors(String internalColumnNameDescription, RealmFieldType... validColumnTypes) { return FieldDescriptor.createStandardFieldDescriptor(getSchemaConnector(), getTable(), internalColumnNameDescription, validColumnTypes); } @@ -326,9 +347,9 @@ private void addModifiers(String fieldName, FieldAttribute[] attributes) { } } catch (Exception e) { // If something went wrong, revert all attributes. - long columnIndex = getColumnIndex(fieldName); + long columnKey = getColumnKey(fieldName); if (indexAdded) { - table.removeSearchIndex(columnIndex); + table.removeSearchIndex(columnKey); } throw (RuntimeException) e; } @@ -353,7 +374,7 @@ private void checkNewFieldName(String fieldName) { } private void checkFieldNameIsAvailable(String fieldName) { - if (table.getColumnIndex(fieldName) != Table.NO_MATCH) { + if (table.getColumnKey(fieldName) != Table.NO_MATCH) { throw new IllegalArgumentException("Field already exists in '" + getClassName() + "': " + fieldName); } } diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java index 5506207945..0c996c9c04 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java @@ -47,10 +47,11 @@ public RealmObjectSchema get(String className) { @Override public Set getAll() { // Return all tables prefixed with class__ in the Realm file - int tableCount = (int) realm.getSharedRealm().size(); + String[] names = realm.getSharedRealm().getTablesNames(); + int tableCount = names.length; Set schemas = new LinkedHashSet<>(tableCount); for (int i = 0; i < tableCount; i++) { - RealmObjectSchema objectSchema = get(Table.getClassNameForTable(realm.getSharedRealm().getTableName(i))); + RealmObjectSchema objectSchema = get(Table.getClassNameForTable(names[i])); if (objectSchema != null) { schemas.add(objectSchema); } @@ -122,20 +123,8 @@ public RealmObjectSchema rename(String oldClassName, String newClassName) { throw new IllegalArgumentException(oldClassName + " cannot be renamed because the new class already exists: " + newClassName); } - // Checks if there is a primary key defined for the old class. - String pkField = OsObjectStore.getPrimaryKeyForObject(realm.sharedRealm, oldClassName); - if (pkField != null) { - OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, oldClassName, null); - } - realm.getSharedRealm().renameTable(oldInternalName, newInternalName); Table table = realm.getSharedRealm().getTable(newInternalName); - - // Sets the primary key for the new class if necessary. - if (pkField != null) { - OsObjectStore.setPrimaryKeyForObject(realm.sharedRealm, newClassName, pkField); - } - RealmObjectSchema objectSchema = removeFromClassNameToSchemaMap(oldInternalName); if (objectSchema == null || !objectSchema.getTable().isValid() || !objectSchema.getClassName().equals(newClassName)) { objectSchema = new MutableRealmObjectSchema(realm, this, table); diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index 715c7541c0..86653d1980 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -21,8 +21,7 @@ /** * General implementation for {@link OrderedRealmCollection} which is based on the {@code Collection}. */ -abstract class OrderedRealmCollectionImpl - extends AbstractList implements OrderedRealmCollection { +abstract class OrderedRealmCollectionImpl extends AbstractList implements OrderedRealmCollection { private final static String NOT_SUPPORTED_MESSAGE = "This method is not supported by 'RealmResults' or" + " 'OrderedRealmCollectionSnapshot'."; @@ -269,7 +268,7 @@ public ListIterator listIterator(int location) { // Sorting // aux. method used by sort methods - private long getColumnIndexForSort(String fieldName) { + private long getColumnKeyForSort(String fieldName) { //noinspection ConstantConditions if (fieldName == null || fieldName.isEmpty()) { throw new IllegalArgumentException("Non-empty field name required."); @@ -277,11 +276,11 @@ private long getColumnIndexForSort(String fieldName) { if (fieldName.contains(".")) { throw new IllegalArgumentException("Aggregates on child object fields are not supported: " + fieldName); } - long columnIndex = osResults.getTable().getColumnIndex(fieldName); - if (columnIndex < 0) { + long columnKey = osResults.getTable().getColumnKey(fieldName); + if (columnKey < 0) { throw new IllegalArgumentException(String.format(Locale.US, "Field '%s' does not exist.", fieldName)); } - return columnIndex; + return columnKey; } /** @@ -350,8 +349,8 @@ public int size() { @Override public Number min(String fieldName) { realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); - return osResults.aggregateNumber(OsResults.Aggregate.MINIMUM, columnIndex); + long columnKey = getColumnKeyForSort(fieldName); + return osResults.aggregateNumber(OsResults.Aggregate.MINIMUM, columnKey); } /** @@ -360,7 +359,7 @@ public Number min(String fieldName) { @Override public Date minDate(String fieldName) { realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); + long columnIndex = getColumnKeyForSort(fieldName); return osResults.aggregateDate(OsResults.Aggregate.MINIMUM, columnIndex); } @@ -370,7 +369,7 @@ public Date minDate(String fieldName) { @Override public Number max(String fieldName) { realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); + long columnIndex = getColumnKeyForSort(fieldName); return osResults.aggregateNumber(OsResults.Aggregate.MAXIMUM, columnIndex); } @@ -388,7 +387,7 @@ public Number max(String fieldName) { @Nullable public Date maxDate(String fieldName) { realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); + long columnIndex = getColumnKeyForSort(fieldName); return osResults.aggregateDate(OsResults.Aggregate.MAXIMUM, columnIndex); } @@ -399,7 +398,7 @@ public Date maxDate(String fieldName) { @Override public Number sum(String fieldName) { realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); + long columnIndex = getColumnKeyForSort(fieldName); return osResults.aggregateNumber(OsResults.Aggregate.SUM, columnIndex); } @@ -409,7 +408,7 @@ public Number sum(String fieldName) { @Override public double average(String fieldName) { realm.checkIfValid(); - long columnIndex = getColumnIndexForSort(fieldName); + long columnIndex = getColumnKeyForSort(fieldName); Number avg = osResults.aggregateNumber(OsResults.Aggregate.AVERAGE, columnIndex); return avg.doubleValue(); diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java index 6b2d9fd30a..67bba47a8f 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java @@ -160,6 +160,14 @@ public OrderedRealmCollectionSnapshot createSnapshot() { return this; } + @Override + public OrderedRealmCollection freeze() { + // Technically, nothing prevents us from supporting this, but there isn't any good use + // case for supporting it, since snapshots should only be used when modifying Results. + // So for now, this is disabled. + throw getUnsupportedException("freeze"); + } + /** * Deletes the object at the given index from the Realm. The object at the given index will become invalid. Just * returns if the object is invalid already. @@ -172,7 +180,7 @@ public OrderedRealmCollectionSnapshot createSnapshot() { public void deleteFromRealm(int location) { realm.checkIfValidAndInTransaction(); UncheckedRow row = osResults.getUncheckedRow(location); - if (row.isAttached()) { + if (row.isValid()) { osResults.delete(location); } } @@ -187,7 +195,7 @@ public void deleteFromRealm(int location) { public boolean deleteFirstFromRealm() { realm.checkIfValidAndInTransaction(); UncheckedRow row = osResults.firstUncheckedRow(); - return row != null && row.isAttached() && osResults.deleteFirst(); + return row != null && row.isValid() && osResults.deleteFirst(); } /** @@ -200,7 +208,7 @@ public boolean deleteFirstFromRealm() { public boolean deleteLastFromRealm() { realm.checkIfValidAndInTransaction(); UncheckedRow row = osResults.lastUncheckedRow(); - return row != null && row.isAttached() && osResults.deleteLast(); + return row != null && row.isValid() && osResults.deleteLast(); } /** @@ -215,4 +223,9 @@ public boolean deleteLastFromRealm() { public boolean deleteAllFromRealm() { return super.deleteAllFromRealm(); } + + @Override + public boolean isFrozen() { + return false; + } } diff --git a/realm/realm-library/src/main/java/io/realm/ProxyState.java b/realm/realm-library/src/main/java/io/realm/ProxyState.java index 7ebdce93e2..87bf4a3bee 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyState.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyState.java @@ -170,7 +170,7 @@ public void setConstructionFinished() { } private void registerToObjectNotifier() { - if (realm.sharedRealm == null || realm.sharedRealm.isClosed() || !row.isAttached()) { + if (realm.sharedRealm == null || realm.sharedRealm.isClosed() || !row.isValid()) { return; } @@ -183,7 +183,7 @@ private void registerToObjectNotifier() { } public boolean isLoaded() { - return !(row instanceof PendingRow); + return row.isLoaded(); } public void load() { @@ -197,7 +197,7 @@ public void onQueryFinished(Row row) { this.row = row; // getTable should return a non-null table since the row should always be valid here. notifyQueryFinished(); - if (row.isAttached()) { + if (row.isValid()) { registerToObjectNotifier(); } } diff --git a/realm/realm-library/src/main/java/io/realm/ProxyUtils.java b/realm/realm-library/src/main/java/io/realm/ProxyUtils.java index e4d4c3648b..83ffc6146b 100644 --- a/realm/realm-library/src/main/java/io/realm/ProxyUtils.java +++ b/realm/realm-library/src/main/java/io/realm/ProxyUtils.java @@ -129,7 +129,7 @@ static void setRealmListWithJsonObject( } /** - * Called by proxy to create a unmanaged {@link RealmList} according to the given {@link JsonReader}. + * Called by proxy to create an unmanaged {@link RealmList} according to the given {@link JsonReader}. * * @param elementClass the type of the {@link RealmList}. * @param jsonReader the JSON stream to be parsed which may contain the data of the list to be set. diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index f91b89788e..d9fb0123e0 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -159,8 +159,8 @@ public class Realm extends BaseRealm { * @param cache the {@link RealmCache} associated to this Realm instance. * @throws IllegalArgumentException if trying to open an encrypted Realm with the wrong key. */ - private Realm(RealmCache cache) { - super(cache, createExpectedSchemaInfo(cache.getConfiguration().getSchemaMediator())); + private Realm(RealmCache cache, OsSharedRealm.VersionID version) { + super(cache, createExpectedSchemaInfo(cache.getConfiguration().getSchemaMediator()), version); schema = new ImmutableRealmSchema(this, new ColumnIndices(configuration.getSchemaMediator(), sharedRealm.getSchemaInfo())); // FIXME: This is to work around the different behaviour between the read only Realms in the Object Store and @@ -495,8 +495,8 @@ public static void removeDefaultConfiguration() { * @param cache the {@link RealmCache} where to create the realm in. * @return a {@link Realm} instance. */ - static Realm createInstance(RealmCache cache) { - return new Realm(cache); + static Realm createInstance(RealmCache cache, OsSharedRealm.VersionID version) { + return new Realm(cache, version); } /** @@ -1570,6 +1570,10 @@ public RealmAsyncTask executeTransactionAsync(final Transaction transaction, throw new IllegalArgumentException("Transaction should not be null"); } + if (isFrozen()) { + throw new IllegalStateException("Write transactions on a frozen Realm is not allowed."); + } + // Avoid to call canDeliverNotification() in bg thread. final boolean canDeliverNotification = sharedRealm.capabilities.canDeliverNotification(); @@ -1848,7 +1852,7 @@ public void execute(Realm realm) { // TODO Add support for DynamicRealm.executeTransactionAsync() Table table = realm.sharedRealm.getTable("class___ResultSets"); TableQuery query = table.where() - .equalTo(new long[]{table.getColumnIndex("name")}, new long[]{NativeObject.NULLPTR}, subscriptionName); + .equalTo(new long[]{table.getColumnKey("name")}, new long[]{NativeObject.NULLPTR}, subscriptionName); OsResults result = OsResults.createFromQuery(realm.sharedRealm, query); long count = result.size(); @@ -1982,6 +1986,14 @@ public Subscription getSubscription(String name) { return where(Subscription.class).equalTo("name", name).findFirst(); } + /** + * {@inheritDoc} + */ + @Override + public Realm freeze() { + return RealmCache.createRealmOrGetFromCache(configuration, Realm.class, sharedRealm.getVersionID()); + } + Table getTable(Class clazz) { return schema.getTable(clazz); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 863de72413..4c972bc953 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -25,14 +25,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.EnumMap; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import io.realm.exceptions.RealmFileException; import io.realm.internal.Capabilities; @@ -41,11 +43,11 @@ import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSharedRealm; import io.realm.internal.RealmNotifier; -import io.realm.internal.Table; import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmAsyncTaskImpl; +import io.realm.internal.util.Pair; import io.realm.log.RealmLog; @@ -66,13 +68,141 @@ interface Callback0 { void onCall(); } - private static class RefAndCount { - // The Realm instance in this thread. - private final ThreadLocal localRealm = new ThreadLocal<>(); + private static abstract class ReferenceCounter { + // How many references to this Realm instance in this thread. - private final ThreadLocal localCount = new ThreadLocal<>(); + protected final ThreadLocal localCount = new ThreadLocal<>(); // How many threads have instances refer to this configuration. - private int globalCount = 0; + protected AtomicInteger globalCount = new AtomicInteger(0); + + // Returns `true` if an instance of the Realm is available on the caller thread. + abstract boolean hasInstanceAvailableForThread(); + + // Increment how many times an instance has been handed out for the current thread. + public void incrementThreadCount(int increment) { + Integer currentCount = localCount.get(); + localCount.set(currentCount != null ? currentCount + increment : increment); + } + + // Returns the Realm instance for the caller thread + abstract BaseRealm getRealmInstance(); + + // Cache the Realm instance. Should only be called when `hasInstanceAvailableForThread` returns false. + abstract void onRealmCreated(BaseRealm realm); + + // Clears the the cache for a given thread when all Realms on that thread are closed. + abstract void clearThreadLocalCache(); + + // Returns the number of instances handed out for the caller thread. + abstract int getThreadLocalCount(); + + // Updates the number of references handed out for a given thread + public void setThreadCount(int refCount) { + localCount.set(refCount); + } + + // Returns the number of gloal instances handed out. This is roughly equivalent + // to the number of threads currently using the Realm as each thread also does + // reference counting of Realm instances. + public int getGlobalCount() { + return globalCount.get(); + } + } + + // Reference counter for Realms that are accessible across all threads + private static class GlobalReferenceCounter extends ReferenceCounter { + private BaseRealm cachedRealm; + + @Override + boolean hasInstanceAvailableForThread() { + return cachedRealm != null; + } + + @Override + BaseRealm getRealmInstance() { + return cachedRealm; + } + + @Override + void onRealmCreated(BaseRealm realm) { + // The Realm instance has been created without exceptions. Cache and reference count can be updated now. + cachedRealm = realm; + + localCount.set(0); + // This is the first instance in current thread, increase the global count. + globalCount.incrementAndGet(); + + } + + @Override + public void clearThreadLocalCache() { + String canonicalPath = cachedRealm.getPath(); + + // The last instance in this thread. + // Clears local ref & counter. + localCount.set(null); + cachedRealm = null; + + // Clears global counter. + if (globalCount.decrementAndGet() < 0) { + // Should never happen. + throw new IllegalStateException("Global reference counter of Realm" + canonicalPath + " not be negative."); + } + } + + @Override + int getThreadLocalCount() { + // For frozen Realms the Realm can be accessed from all threads, so the concept + // of a thread local count doesn't make sense. Just return the global count instead. + return globalCount.get(); + } + } + + // Reference counter for Realms that are thread confined + private static class ThreadConfinedReferenceCounter extends ReferenceCounter { + // The Realm instance in this thread. + private final ThreadLocal localRealm = new ThreadLocal<>(); + + @Override + public boolean hasInstanceAvailableForThread() { + return localRealm.get() != null; + } + + @Override + public BaseRealm getRealmInstance() { + return localRealm.get(); + } + + @Override + public void onRealmCreated(BaseRealm realm) { + // The Realm instance has been created without exceptions. Cache and reference count can be updated now. + localRealm.set(realm); + localCount.set(0); + // This is the first instance in current thread, increase the global count. + globalCount.incrementAndGet(); + } + + @Override + public void clearThreadLocalCache() { + String canonicalPath = localRealm.get().getPath(); + + // The last instance in this thread. + // Clears local ref & counter. + localCount.set(null); + localRealm.set(null); + + // Clears global counter. + if (globalCount.decrementAndGet() < 0) { + // Should never happen. + throw new IllegalStateException("Global reference counter of Realm" + canonicalPath + " can not be negative."); + } + } + + @Override + public int getThreadLocalCount() { + Integer refCount = localCount.get(); + return (refCount != null) ? refCount : 0; + } } private enum RealmCacheType { @@ -188,7 +318,7 @@ public void run() { "The callback cannot be null."; // Separated references and counters for typed Realm and dynamic Realm. - private final EnumMap refAndCountMap; + private final Map, ReferenceCounter> refAndCountMap = new HashMap<>(); // Path to the Realm file to identify this cache. private final String realmPath; @@ -217,10 +347,6 @@ public void run() { private RealmCache(String path) { realmPath = path; - refAndCountMap = new EnumMap<>(RealmCacheType.class); - for (RealmCacheType type : RealmCacheType.values()) { - refAndCountMap.put(type, new RefAndCount()); - } } private static RealmCache getCache(String realmPath, boolean createIfNotExist) { @@ -283,17 +409,18 @@ private synchronized RealmAsyncTask doCreateRealmOrGetFrom * @param realmClass class of {@link Realm} or {@link DynamicRealm} to be created in or gotten from the cache. * @return the {@link Realm} or {@link DynamicRealm} instance. */ - static E createRealmOrGetFromCache(RealmConfiguration configuration, - Class realmClass) { + static E createRealmOrGetFromCache(RealmConfiguration configuration, Class realmClass) { RealmCache cache = getCache(configuration.getPath(), true); - - return cache.doCreateRealmOrGetFromCache(configuration, realmClass); + return cache.doCreateRealmOrGetFromCache(configuration, realmClass, OsSharedRealm.VersionID.LIVE); } - private synchronized E doCreateRealmOrGetFromCache(RealmConfiguration configuration, - Class realmClass) { + static E createRealmOrGetFromCache(RealmConfiguration configuration, Class realmClass, OsSharedRealm.VersionID version) { + RealmCache cache = getCache(configuration.getPath(), true); + return cache.doCreateRealmOrGetFromCache(configuration, realmClass, version); + } - RefAndCount refAndCount = refAndCountMap.get(RealmCacheType.valueOf(realmClass)); + private synchronized E doCreateRealmOrGetFromCache(RealmConfiguration configuration, Class realmClass, OsSharedRealm.VersionID version) { + ReferenceCounter referenceCounter = getRefCounter(realmClass, version); boolean firstRealmInstanceInProcess = (getTotalGlobalRefCount() == 0); boolean realmFileIsBeingCreated = !configuration.realmExists(); @@ -301,41 +428,32 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo copyAssetFileIfNeeded(configuration); OsSharedRealm sharedRealm = null; try { - if (configuration.isSyncConfiguration()) { - // If waitForInitialRemoteData() was enabled, we need to make sure that all data is downloaded - // before proceeding. We need to open the Realm instance first to start any potential underlying - // SyncSession so this will work. - if (realmFileIsBeingCreated) { - - // Manually create the Java session wrapper session as this might otherwise - // not be created - OsRealmConfig osConfig = new OsRealmConfig.Builder(configuration).build(); - ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(osConfig); - - if (ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(configuration)) { - // Partial Realms are not supported by async open yet, so continue to - // use the old way of opening those Realms. - sharedRealm = OsSharedRealm.getInstance(configuration); - try { - ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialRemoteChanges(configuration); - } catch (Throwable t) { - // If an error happened while downloading initial data, we need to reset the file so we can - // download it again on the next attempt. - sharedRealm.close(); - sharedRealm = null; - deleteRealmFileOnDisk(configuration); - throw t; - } - } else { - // Fully synchronized Realms are supported by AsyncOpen + // If waitForInitialRemoteData() was enabled, we need to make sure that all data is downloaded + // before proceeding. We need to open the Realm instance first to start any potential underlying + // SyncSession so this will work. + if (configuration.isSyncConfiguration() && realmFileIsBeingCreated) { + // Manually create the Java session wrapper session as this might otherwise + // not be created + OsRealmConfig osConfig = new OsRealmConfig.Builder(configuration).build(); + ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(osConfig); + + if (ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(configuration)) { + // Partial Realms are not supported by async open yet, so continue to + // use the old way of opening those Realms. + sharedRealm = OsSharedRealm.getInstance(configuration, OsSharedRealm.VersionID.LIVE); + try { ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialRemoteChanges(configuration); + } catch (Throwable t) { + // If an error happened while downloading initial data, we need to reset the file so we can + // download it again on the next attempt. + sharedRealm.close(); + sharedRealm = null; + deleteRealmFileOnDisk(configuration); + throw t; } - } - } else { - if (!realmFileIsBeingCreated) { - // Primary key problem only exists before we release sync. - sharedRealm = OsSharedRealm.getInstance(configuration); - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); + } else { + // Fully synchronized Realms are supported by AsyncOpen + ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialRemoteChanges(configuration); } } } finally { @@ -351,39 +469,57 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo validateConfiguration(configuration); } - if (refAndCount.localRealm.get() == null) { - // Creates a new local Realm instance - BaseRealm realm; + if (!referenceCounter.hasInstanceAvailableForThread()) { + createInstance(realmClass, referenceCounter, realmFileIsBeingCreated, version); + } - if (realmClass == Realm.class) { - // RealmMigrationNeededException might be thrown here. - realm = Realm.createInstance(this); + referenceCounter.incrementThreadCount(1); - // If `waitForInitialRemoteData` data is set, we also want to ensure that all subscriptions - // are fully ACTIVE before proceeding. Most of the Realm is initialized during a write - // transaction. So we cannot download subscription data until all other initializers have run. - // At this point we also have access to all normal APIs as the schema is fully initialized. - synchronizeInitialSubscriptionsIfNeeded((Realm) realm, realmFileIsBeingCreated); + //noinspection unchecked + return (E) referenceCounter.getRealmInstance(); + } - } else if (realmClass == DynamicRealm.class) { - realm = DynamicRealm.createInstance(this); + private ReferenceCounter getRefCounter(Class realmClass, OsSharedRealm.VersionID version) { + RealmCacheType cacheType = RealmCacheType.valueOf(realmClass); + Pair key = new Pair<>(cacheType, version); + ReferenceCounter refCounter = refAndCountMap.get(key); + if (refCounter == null) { + if (version.equals(OsSharedRealm.VersionID.LIVE)) { + refCounter = new ThreadConfinedReferenceCounter(); } else { - throw new IllegalArgumentException(WRONG_REALM_CLASS_MESSAGE); + refCounter = new GlobalReferenceCounter(); } - // The Realm instance has been created without exceptions. Cache and reference count can be updated now. - refAndCount.localRealm.set(realm); - refAndCount.localCount.set(0); + refAndCountMap.put(key, refCounter); + } + return refCounter; + } - // This is the first instance in current thread, increase the global count. - refAndCount.globalCount++; + private void createInstance(Class realmClass, + ReferenceCounter referenceCounter, + boolean realmFileIsBeingCreated, + OsSharedRealm.VersionID version) { + // Creates a new local Realm instance + BaseRealm realm; + + if (realmClass == Realm.class) { + // RealmMigrationNeededException might be thrown here. + realm = Realm.createInstance(this, version); + + // If `waitForInitialRemoteData` data is set, we also want to ensure that all subscriptions + // are fully ACTIVE before proceeding. Most of the Realm is initialized during a write + // transaction. So we cannot download subscription data until all other initializers have run. + // At this point we also have access to all normal APIs as the schema is fully initialized. + synchronizeInitialSubscriptionsIfNeeded((Realm) realm, realmFileIsBeingCreated); + + } else if (realmClass == DynamicRealm.class) { + realm = DynamicRealm.createInstance(this, version); + } else { + throw new IllegalArgumentException(WRONG_REALM_CLASS_MESSAGE); } - Integer refCount = refAndCount.localCount.get(); - refAndCount.localCount.set(refCount + 1); - //noinspection unchecked - return (E) refAndCount.localRealm.get(); + referenceCounter.onRealmCreated(realm); } /** @@ -445,11 +581,8 @@ private static void deleteRealmFileOnDisk(RealmConfiguration configuration) { */ synchronized void release(BaseRealm realm) { String canonicalPath = realm.getPath(); - RefAndCount refAndCount = refAndCountMap.get(RealmCacheType.valueOf(realm.getClass())); - Integer refCount = refAndCount.localCount.get(); - if (refCount == null) { - refCount = 0; - } + ReferenceCounter referenceCounter = getRefCounter(realm.getClass(), (realm.isFrozen()) ? realm.sharedRealm.getVersionID() : OsSharedRealm.VersionID.LIVE); + int refCount = referenceCounter.getThreadLocalCount(); if (refCount <= 0) { RealmLog.warn("%s has been closed already. refCount is %s", canonicalPath, refCount); @@ -460,34 +593,37 @@ synchronized void release(BaseRealm realm) { refCount -= 1; if (refCount == 0) { - // The last instance in this thread. - // Clears local ref & counter. - refAndCount.localCount.set(null); - refAndCount.localRealm.set(null); - - // Clears global counter. - refAndCount.globalCount--; - if (refAndCount.globalCount < 0) { - // Should never happen. - throw new IllegalStateException("Global reference counter of Realm" + canonicalPath + - " got corrupted."); - } + referenceCounter.clearThreadLocalCache(); // No more local reference to this Realm in current thread, close the instance. realm.doClose(); // No more instance of typed Realm and dynamic Realm. - if (getTotalGlobalRefCount() == 0) { + if (getTotalLiveRealmGlobalRefCount() == 0) { // We keep the cache in the caches list even when its global counter reaches 0. It will be reused when // next time a Realm instance with the same path is opened. By not removing it, the lock on // cachesList is not needed here. configuration = null; - ObjectServerFacade.getFacade(realm.getConfiguration().isSyncConfiguration()) - .realmClosed(realm.getConfiguration()); + + // Close all frozen Realms. This can introduce race conditions on other + // threads if the lifecyle of using Realm data is not correctly controlled. + for (ReferenceCounter counter : refAndCountMap.values()) { + if (counter instanceof GlobalReferenceCounter) { + BaseRealm cachedRealm = counter.getRealmInstance(); + // Since we don't remove ReferenceCounters, we need to check if the Realm is still open + if (cachedRealm != null) { + // Gracefully close frozen Realms in a similar way to what a user would normally do. + while (!cachedRealm.isClosed()) { + cachedRealm.close(); + } + } + } + } + ObjectServerFacade.getFacade(realm.getConfiguration().isSyncConfiguration()).realmClosed(realm.getConfiguration()); } } else { - refAndCount.localCount.set(refCount); + referenceCounter.setThreadCount(refCount); } } @@ -655,11 +791,10 @@ static int getLocalThreadCount(RealmConfiguration configuration) { return 0; } - // Access local ref count only, no need to by synchronized. + // Access local ref count only, no need to be synchronized. int totalRefCount = 0; - for (RefAndCount refAndCount : cache.refAndCountMap.values()) { - Integer localCount = refAndCount.localCount.get(); - totalRefCount += (localCount != null) ? localCount : 0; + for (ReferenceCounter referenceCounter : cache.refAndCountMap.values()) { + totalRefCount += referenceCounter.getThreadLocalCount(); } return totalRefCount; } @@ -673,8 +808,22 @@ public RealmConfiguration getConfiguration() { */ private int getTotalGlobalRefCount() { int totalRefCount = 0; - for (RefAndCount refAndCount : refAndCountMap.values()) { - totalRefCount += refAndCount.globalCount; + for (ReferenceCounter referenceCounter : refAndCountMap.values()) { + totalRefCount += referenceCounter.getGlobalCount(); + } + + return totalRefCount; + } + + /** + * Returns the total number of threads containg a reference to a live instance of the Realm. + */ + private int getTotalLiveRealmGlobalRefCount() { + int totalRefCount = 0; + for (ReferenceCounter referenceCounter : refAndCountMap.values()) { + if (referenceCounter instanceof ThreadConfinedReferenceCounter) { + totalRefCount += referenceCounter.getGlobalCount(); + } } return totalRefCount; diff --git a/realm/realm-library/src/main/java/io/realm/RealmCollection.java b/realm/realm-library/src/main/java/io/realm/RealmCollection.java index 7516d2b3bc..cb9922af71 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCollection.java @@ -184,4 +184,23 @@ public interface RealmCollection extends Collection, ManagableObject { */ @Override boolean contains(@Nullable Object object); + + /** + * Returns a frozen snapshot of this collection. The frozen copy can be read and queried from any thread without throwing + * an {@link IllegalStateException}. + *

            + * Freezing a collection also creates a Realm which has its own lifecycle, but if the live Realm that spawned the + * original collection is fully closed (i.e. all instances across all threads are closed), the frozen Realm and this + * collection will be closed as well. + *

            + * Frozen collections can be queried as normal, but trying to mutate it in any way or attempting to register a listener will + * throw an {@link IllegalStateException}. + *

            + * Note: Keeping a large number of frozen collections with different versions alive can have a negative impact on the filesize + * of the Realm. In order to avoid such a situation, it is possible to set {@link RealmConfiguration.Builder#maxNumberOfActiveVersions(long)}. + * + * @return a frozen copy of this collection. + * @throws IllegalStateException if this method is called from inside a write transaction. + */ + RealmCollection freeze(); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 66be10db7a..f0466edf59 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -101,6 +101,8 @@ public class RealmConfiguration { private final Realm.Transaction initialDataTransaction; private final boolean readOnly; private final CompactOnLaunchCallback compactOnLaunch; + private final long maxNumberOfActiveVersions; + /** * Whether this RealmConfiguration is intended to open a * recovery Realm produced after an offline/online client reset. @@ -123,7 +125,8 @@ protected RealmConfiguration(@Nullable File realmDirectory, @Nullable Realm.Transaction initialDataTransaction, boolean readOnly, @Nullable CompactOnLaunchCallback compactOnLaunch, - boolean isRecoveryConfiguration) { + boolean isRecoveryConfiguration, + long maxNumberOfActiveVersions) { this.realmDirectory = realmDirectory; this.realmFileName = realmFileName; this.canonicalPath = canonicalPath; @@ -139,6 +142,7 @@ protected RealmConfiguration(@Nullable File realmDirectory, this.readOnly = readOnly; this.compactOnLaunch = compactOnLaunch; this.isRecoveryConfiguration = isRecoveryConfiguration; + this.maxNumberOfActiveVersions = maxNumberOfActiveVersions; } public File getRealmDirectory() { @@ -281,6 +285,13 @@ public boolean isRecoveryConfiguration() { return isRecoveryConfiguration; } + /** + * @return the maximum number of active versions allowed before an exception is thrown. + */ + public long getMaxNumberOfActiveVersions() { + return maxNumberOfActiveVersions; + } + @Override public boolean equals(Object obj) { if (this == obj) { return true; } @@ -314,7 +325,10 @@ public boolean equals(Object obj) { if (initialDataTransaction != null ? !initialDataTransaction.equals(that.initialDataTransaction) : that.initialDataTransaction != null) { return false; } - return compactOnLaunch != null ? compactOnLaunch.equals(that.compactOnLaunch) : that.compactOnLaunch == null; + if (compactOnLaunch != null ? !compactOnLaunch.equals(that.compactOnLaunch) : that.compactOnLaunch != null) { + return false; + } + return maxNumberOfActiveVersions == that.maxNumberOfActiveVersions; } @Override @@ -334,6 +348,7 @@ public int hashCode() { result = 31 * result + (readOnly ? 1 : 0); result = 31 * result + (compactOnLaunch != null ? compactOnLaunch.hashCode() : 0); result = 31 * result + (isRecoveryConfiguration ? 1 : 0); + result = 31 * result + (int) (maxNumberOfActiveVersions ^ (maxNumberOfActiveVersions >>> 32)); return result; } @@ -409,6 +424,8 @@ public String toString() { stringBuilder.append("readOnly: ").append(readOnly); stringBuilder.append("\n"); stringBuilder.append("compactOnLaunch: ").append(compactOnLaunch); + stringBuilder.append("\n"); + stringBuilder.append("maxNumberOfActiveVersions: ").append(maxNumberOfActiveVersions); return stringBuilder.toString(); } @@ -466,6 +483,7 @@ public static class Builder { private Realm.Transaction initialDataTransaction; private boolean readOnly; private CompactOnLaunchCallback compactOnLaunch; + private long maxNumberOfActiveVersions = Long.MAX_VALUE; /** * Creates an instance of the Builder for the RealmConfiguration. @@ -765,6 +783,28 @@ public Builder compactOnLaunch(CompactOnLaunchCallback compactOnLaunch) { return this; } + /** + * Sets the maximum number of live versions in the Realm file before an {@link IllegalStateException} is thrown when + * attempting to write more data. + *

            + * Realm is capable of concurrently handling many different versions of Realm objects. This can e.g. happen if you + * have a Realm open on many different threads or are freezing objects while data is being written to the file. + *

            + * Under normal circumstances this is not a problem, but if the number of active versions grow too large, it will + * have a negative effect on the filesize on disk. Setting this parameters can therefore be used to prevent uses of + * Realm that can result in very large Realms. + * + * @param number the maximum number of active versions before an exception is thrown. + * @see FAQ + */ + public Builder maxNumberOfActiveVersions(long number) { + if (number < 1) { + throw new IllegalArgumentException("Only positive numbers above 0 are allowed. Yours was: " + number); + } + this.maxNumberOfActiveVersions = number; + return this; + } + /** * DEBUG method. This restricts the Realm schema to only consist of the provided classes without having to * create a module. These classes must be available in the default module. Calling this will remove any @@ -810,10 +850,9 @@ public RealmConfiguration build() { } if (rxFactory == null && isRxJavaAvailable()) { - rxFactory = new RealmObservableFactory(); + rxFactory = new RealmObservableFactory(true); } - return new RealmConfiguration(directory, fileName, getCanonicalPath(new File(directory, fileName)), @@ -828,7 +867,8 @@ public RealmConfiguration build() { initialDataTransaction, readOnly, compactOnLaunch, - false + false, + maxNumberOfActiveVersions ); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index dbe8476d54..e7a1a0884f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -146,6 +146,36 @@ public boolean isValid() { return isAttached(); } + /** + * {@inheritDoc} + */ + @Override + public RealmList freeze() { + if (isManaged()) { + if (!isValid()) { + throw new IllegalStateException("Only valid, managed RealmLists can be frozen."); + } + + BaseRealm frozenRealm = realm.freeze(); + OsList frozenList = getOsList().freeze(frozenRealm.sharedRealm); + if (className != null) { + return new RealmList<>(className, frozenList, frozenRealm); + } else { + return new RealmList<>(clazz, frozenList, frozenRealm); + } + } else { + throw new UnsupportedOperationException(ONLY_IN_MANAGED_MODE_MESSAGE); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isFrozen() { + return (realm != null && realm.isFrozen()); + } + /** * {@inheritDoc} */ @@ -846,7 +876,7 @@ public String toString() { } else if (isClassForRealmModel(clazz)) { for (int i = 0; i < size(); i++) { //noinspection ConstantConditions - sb.append(((RealmObjectProxy) get(i)).realmGet$proxyState().getRow$realm().getIndex()); + sb.append(((RealmObjectProxy) get(i)).realmGet$proxyState().getRow$realm().getObjectKey()); sb.append(separator); } if (0 < size()) { @@ -876,6 +906,21 @@ public String toString() { * subscribed to. RealmList will continually be emitted as the RealmList is updated - * {@code onComplete} will never be called. *

            + * Items emitted from Realm Flowables are frozen (See {@link #freeze()}. This means that they + * are immutable and can be read on any thread. + *

            + * Realm Flowables always emit items from the thread holding the live RealmList. This means that if + * you need to do further processing, it is recommend to observe the values on a computation + * scheduler: + *

            + * {@code + * list.asFlowable() + * .observeOn(Schedulers.computation()) + * .map(rxResults -> doExpensiveWork(rxResults)) + * .observeOn(AndroidSchedulers.mainThread()) + * .subscribe( ... ); + * } + *

            * If you would like the {@code asFlowable()} to stop emitting items you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: *

            @@ -887,9 +932,6 @@ public String toString() { * } * *

            - *

            Note that when the {@link Realm} is accessed from threads other than where it was created, - * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers - * with {@code subscribeOn()} and {@code observeOn()}. * * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the @@ -917,14 +959,25 @@ public Flowable> asFlowable() { *

            * RealmList will continually be emitted as the RealmList is updated - {@code onComplete} will never be called. *

            - * * Note that when the {@link Realm} is accessed from threads other than where it was created, - * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers - * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} - * instead. + * Items emitted from Realm Observables are frozen (See {@link #freeze()}. This means that they + * are immutable and can be read on any thread. + *

            + * Realm Observables always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommend to observe the values on a computation + * scheduler: + *

            + * {@code + * list.asChangesetObservable() + * .observeOn(Schedulers.computation()) + * .map((rxList, changes) -> doExpensiveWork(rxList, changes)) + * .observeOn(AndroidSchedulers.mainThread()) + * .subscribe( ... ); + * } * * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the * corresponding Realm instance doesn't support RxJava. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. * @see RxJava and Realm */ public Observable>> asChangesetObservable() { @@ -1454,7 +1507,7 @@ private void checkInsertIndex(int index) { @Override public void appendValue(Object value) { final RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded((RealmModel) value); - osList.addRow(proxy.realmGet$proxyState().getRow$realm().getIndex()); + osList.addRow(proxy.realmGet$proxyState().getRow$realm().getObjectKey()); } @Override @@ -1468,7 +1521,7 @@ public void insertValue(int index, Object value) { checkInsertIndex(index); RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded((RealmModel) value); - osList.insertRow(index, proxy.realmGet$proxyState().getRow$realm().getIndex()); + osList.insertRow(index, proxy.realmGet$proxyState().getRow$realm().getObjectKey()); } @Override @@ -1479,7 +1532,7 @@ protected void setNull(int index) { @Override protected void setValue(int index, Object value) { RealmObjectProxy proxy = (RealmObjectProxy) copyToRealmIfNeeded((RealmModel) value); - osList.setRow(index, proxy.realmGet$proxyState().getRow$realm().getIndex()); + osList.setRow(index, proxy.realmGet$proxyState().getRow$realm().getObjectKey()); } // Transparently copies an unmanaged object or managed object from another Realm to the Realm backing this RealmList. diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 6058bca55e..bd323f640b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -18,6 +18,8 @@ import android.app.IntentService; +import java.util.Collections; + import io.reactivex.Flowable; import io.reactivex.Observable; import io.realm.annotations.RealmClass; @@ -112,7 +114,7 @@ public static void deleteFromRealm(E object) { proxy.realmGet$proxyState().getRealm$realm().checkIfValid(); Row row = proxy.realmGet$proxyState().getRow$realm(); - row.getTable().moveLastOver(row.getIndex()); + row.getTable().moveLastOver(row.getObjectKey()); proxy.realmGet$proxyState().setRow$realm(InvalidRow.INSTANCE); } @@ -150,13 +152,103 @@ public static boolean isValid(E object) { if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; Row row = proxy.realmGet$proxyState().getRow$realm(); - return row != null && row.isAttached(); + return row != null && row.isValid(); } else { //noinspection ConstantConditions return object != null; } } + /** + * Returns whether or not this RealmObject is frozen. + * + * @return {@code true} if the RealmObject is frozen, {@code false} if it is not. + * @see #freeze() + */ + @Override + public final boolean isFrozen() { + return RealmObject.isFrozen(this); + } + + /** + * Returns a frozen snapshot of this object. The frozen copy can be read and queried from any thread without throwing + * an {@link IllegalStateException}. + *

            + * Freezing a RealmObject also creates a frozen Realm which has its own lifecycle, but if the live Realm that spawned the + * original collection is fully closed (i.e. all instances across all threads are closed), the frozen Realm and + * object will be closed as well. + *

            + * Frozen objects can be queried as normal, but trying to mutate it in any way or attempting to register a listener will + * throw an {@link IllegalStateException}. + *

            + * Note: Keeping a large number of frozen objects with different versions alive can have a negative impact on the filesize + * of the Realm. In order to avoid such a situation it is possible to set {@link RealmConfiguration.Builder#maxNumberOfActiveVersions(long)}. + * + * @return a frozen copy of this object. + * @throws IllegalStateException if this method is called from inside a write transaction. + */ + @SuppressWarnings("TypeParameterUnusedInFormals") // FIXME: Consider adding type parameters to all RealmObject/RealmModel classes? + public final E freeze() { + //noinspection unchecked + return (E) RealmObject.freeze(this); + } + + /** + * Returns whether or not this RealmObject is frozen. + * + * @return {@code true} if the RealmObject is frozen, {@code false} if it is not. + * @see #freeze() + */ + public static boolean isFrozen(E object) { + if (object instanceof RealmObjectProxy) { + RealmObjectProxy proxy = (RealmObjectProxy) object; + return proxy.realmGet$proxyState().getRealm$realm().isFrozen(); + } else { + return false; + } + } + + /** + * Returns a frozen snapshot of this object. The frozen copy can be read and queried from any thread without throwing + * an {@link IllegalStateException}. + *

            + * Freezing a RealmObject also creates a frozen Realm which has its own lifecycle, but if the live Realm that spawned the + * original collection is fully closed (i.e. all instances across all threads are closed), the frozen Realm and + * object will be closed as well. + *

            + * Frozen objects can be queried as normal, but trying to mutate it in any way or attempting to register a listener will + * throw an {@link IllegalStateException}. + *

            + * Note: Keeping a large number of frozen objects with different versions alive can have a negative impact on the filesize + * of the Realm. In order to avoid such a situation it is possible to set {@link RealmConfiguration.Builder#maxNumberOfActiveVersions(long)}. + * + * @return a frozen copy of this object. + * @throws IllegalStateException if this method is called from inside a write transaction. + */ + public static E freeze(E object) { + if (object instanceof RealmObjectProxy) { + RealmObjectProxy proxy = (RealmObjectProxy) object; + BaseRealm realm = proxy.realmGet$proxyState().getRealm$realm(); + BaseRealm frozenRealm = (realm.isFrozen()) ? realm : realm.freeze(); + + Row frozenRow = proxy.realmGet$proxyState().getRow$realm().freeze(frozenRealm.sharedRealm); + if (frozenRealm instanceof DynamicRealm) { + //noinspection unchecked + return (E) new DynamicRealmObject(frozenRealm, frozenRow); + } else if (frozenRealm instanceof Realm) { + //noinspection unchecked + Class modelClass = (Class) object.getClass().getSuperclass(); + return (E) frozenRealm.getConfiguration().getSchemaMediator().newInstance( + modelClass, frozenRealm, frozenRow, realm.getSchema().getColumnInfo(modelClass), + false, Collections.emptyList()); + } else { + throw new UnsupportedOperationException("Unknown Realm type: " + frozenRealm.getClass().getName()); + } + } else { + throw new IllegalArgumentException("It is only possible to freeze valid managed Realm objects."); + } + } + /** * Checks if the query used to find this RealmObject has completed. *

            @@ -644,6 +736,21 @@ public static void removeAllChangeListeners(E object) { * When chaining a RealmObject flowable use {@code obj.asFlowable()} to pass on * type information, otherwise the type of the following observables will be {@code RealmObject}. *

            + * Items emitted from Realm Flowables are frozen (See {@link #freeze()}. This means that they + * are immutable and can be read on any thread. + *

            + * Realm Flowables always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommend to observe the values on a computation + * scheduler: + *

            + * {@code + * obj.asFlowable() + * .observeOn(Schedulers.computation()) + * .map((rxObj) -> doExpensiveWork(rxObj)) + * .observeOn(AndroidSchedulers.mainThread()) + * .subscribe( ... ); + * } + *

            * If you would like the {@code asFlowable()} to stop emitting items you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: *

            @@ -656,16 +763,12 @@ public static void removeAllChangeListeners(E object) { * } * *

            - *

            - * Note that when the {@link Realm} is accessed from threads other than where it was created, - * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers - * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} - * instead. * * @param RealmObject class that is being observed. Must be this class or its super types. * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the * corresponding Realm instance doesn't support RxJava. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. * @see RxJava and Realm */ public final Flowable asFlowable() { @@ -681,14 +784,25 @@ public final Flowable asFlowable() { *

            * The RealmObject will continually be emitted as it is updated - {@code onComplete} will never be called. *

            - * Note that when the {@link Realm} is accessed from threads other than where it was created, - * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers - * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} - * instead. + * Items emitted from Realm Observables are frozen (See {@link #freeze()}. This means that they + * are immutable and can be read on any thread. + *

            + * Realm Observables always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommend to observe the values on a computation + * scheduler: + *

            + * {@code + * obj.asChangesetObservable() + * .observeOn(Schedulers.computation()) + * .map((rxObj, changes) -> doExpensiveWork(rxObj, changeså)) + * .observeOn(AndroidSchedulers.mainThread()) + * .subscribe( ... ); + * } * * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the * corresponding Realm instance doesn't support RxJava. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. * @see RxJava and Realm */ public final Observable> asChangesetObservable() { @@ -703,6 +817,21 @@ public final Observable> asChangesetObse * When chaining a RealmObject observable use {@code obj.asFlowable()} to pass on * type information, otherwise the type of the following observables will be {@code RealmObject}. *

            + * Items emitted from Realm Flowables are frozen (See {@link #freeze()}. This means that they + * are immutable and can be read on any thread. + *

            + * Realm Flowables always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommend to observe the values on a computation + * scheduler: + *

            + * {@code + * obj.asFlowable() + * .observeOn(Schedulers.computation()) + * .map((rxObj) -> doExpensiveWork(rxObj)) + * .observeOn(AndroidSchedulers.mainThread()) + * .subscribe( ... ); + * } + *

            * If you would like the {@code asFlowable()} to stop emitting items you can instruct RxJava to * emit only the first item by using the {@code first()} operator: *

            @@ -718,6 +847,7 @@ public final Observable> asChangesetObse * @param object RealmObject class that is being observed. Must be this class or its super types. * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. * @see RxJava and Realm */ public static Flowable asFlowable(E object) { @@ -751,15 +881,26 @@ public static Flowable asFlowable(E object) { *

            * The RealmObject will continually be emitted as it is updated - {@code onComplete} will never be called. *

            - * Note that when the {@link Realm} is accessed from threads other than where it was created, - * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers - * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} - * instead. + * Items emitted from Realm Observables are frozen (See {@link #freeze()}. This means that they + * are immutable and can be read on any thread. + *

            + * Realm Observables always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommend to observe the values on a computation + * scheduler: + *

            + * {@code + * obj.asChangesetObservable() + * .observeOn(Schedulers.computation()) + * .map((rxObj, changes) -> doExpensiveWork(rxObj, changeså)) + * .observeOn(AndroidSchedulers.mainThread()) + * .subscribe( ... ); + * } * * @param object RealmObject class that is being observed. Must be this class or its super types. * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the * corresponding Realm instance doesn't support RxJava. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. * @see RxJava and Realm */ public static Observable> asChangesetObservable(E object) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 8ce435b4b6..1538e45d4f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -28,7 +28,6 @@ import io.realm.annotations.Required; import io.realm.internal.ColumnInfo; -import io.realm.internal.OsObject; import io.realm.internal.OsObjectStore; import io.realm.internal.Table; import io.realm.internal.fields.FieldDescriptor; @@ -106,6 +105,7 @@ public abstract class RealmObjectSchema { * * * @return the name of the RealmObject class represented by this schema. + * @throws IllegalStateException if this schema defintion is no longer part of the Realm. */ public String getClassName() { return table.getClassName(); @@ -221,7 +221,7 @@ public String getClassName() { * @return {@code true} if the field exists, {@code false} otherwise. */ public boolean hasField(String fieldName) { - return table.getColumnIndex(fieldName) != Table.NO_MATCH; + return table.getColumnKey(fieldName) != Table.NO_MATCH; } /** @@ -247,7 +247,7 @@ public boolean hasField(String fieldName) { public boolean hasIndex(String fieldName) { checkLegalName(fieldName); checkFieldExists(fieldName); - return table.hasSearchIndex(table.getColumnIndex(fieldName)); + return table.hasSearchIndex(table.getColumnKey(fieldName)); } /** @@ -325,7 +325,7 @@ public boolean hasIndex(String fieldName) { * @see #setRequired(String, boolean) */ public boolean isRequired(String fieldName) { - long columnIndex = getColumnIndex(fieldName); + long columnIndex = getColumnKey(fieldName); return !table.isColumnNullable(columnIndex); } @@ -338,7 +338,7 @@ public boolean isRequired(String fieldName) { * @see #setNullable(String, boolean) */ public boolean isNullable(String fieldName) { - long columnIndex = getColumnIndex(fieldName); + long columnIndex = getColumnKey(fieldName); return table.isColumnNullable(columnIndex); } @@ -387,11 +387,8 @@ public String getPrimaryKey() { public Set getFieldNames() { int columnCount = (int) table.getColumnCount(); Set columnNames = new LinkedHashSet<>(columnCount); - for (int i = 0; i < columnCount; i++) { - String name = table.getColumnName(i); - if (!OsObject.isObjectIdColumn(name)) { - columnNames.add(name); - } + for (String column : table.getColumnNames()) { + columnNames.add(column); } return columnNames; } @@ -413,8 +410,8 @@ public Set getFieldNames() { * @return the underlying type used by Realm to represent this field. */ public RealmFieldType getFieldType(String fieldName) { - long columnIndex = getColumnIndex(fieldName); - return table.getColumnType(columnIndex); + long columnKey = getColumnKey(fieldName); + return table.getColumnType(columnKey); } /** @@ -424,7 +421,7 @@ public RealmFieldType getFieldType(String fieldName) { * @param validColumnTypes valid field type for the last field in a linked field * @return a FieldDescriptor */ - abstract FieldDescriptor getColumnIndices(String fieldDescription, RealmFieldType... validColumnTypes); + abstract FieldDescriptor getFieldDescriptors(String fieldDescription, RealmFieldType... validColumnTypes); RealmObjectSchema add(String name, RealmFieldType type, boolean primary, boolean indexed, boolean required) { long columnIndex = table.addColumn(type, name, (required) ? Table.NOT_NULLABLE : Table.NULLABLE); @@ -446,12 +443,12 @@ RealmObjectSchema add(String name, RealmFieldType type, RealmObjectSchema linked return this; } - long getAndCheckFieldIndex(String fieldName) { - long index = columnInfo.getColumnIndex(fieldName); - if (index < 0) { + long getAndCheckFieldColumnKey(String fieldName) { + long columnKey = columnInfo.getColumnKey(fieldName); + if (columnKey < 0) { throw new IllegalArgumentException("Field does not exist: " + fieldName); } - return index; + return columnKey; } Table getTable() { @@ -483,8 +480,8 @@ public interface Function { * @return column index or -1 if it doesn't exists. */ //@VisibleForTesting(otherwise = VisibleForTesting.NONE) - long getFieldIndex(String fieldName) { - return columnInfo.getColumnIndex(fieldName); + long getFieldColumnKey(String fieldName) { + return columnInfo.getColumnKey(fieldName); } static void checkLegalName(String fieldName) { @@ -501,21 +498,21 @@ static void checkLegalName(String fieldName) { } void checkFieldExists(String fieldName) { - if (table.getColumnIndex(fieldName) == Table.NO_MATCH) { + if (table.getColumnKey(fieldName) == Table.NO_MATCH) { throw new IllegalArgumentException("Field name doesn't exist on object '" + getClassName() + "': " + fieldName); } } - long getColumnIndex(String fieldName) { - long columnIndex = table.getColumnIndex(fieldName); - if (columnIndex == -1) { + long getColumnKey(String fieldName) { + long columnKey = table.getColumnKey(fieldName); + if (columnKey == -1) { throw new IllegalArgumentException( String.format(Locale.US, "Field name '%s' does not exist on schema for '%s'", fieldName, getClassName() )); } - return columnIndex; + return columnKey; } static final class DynamicColumnIndices extends ColumnInfo { @@ -527,8 +524,8 @@ static final class DynamicColumnIndices extends ColumnInfo { } @Override - public long getColumnIndex(String columnName) { - return table.getColumnIndex(columnName); + public long getColumnKey(String columnName) { + return table.getColumnKey(columnName); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 3f57236013..9c406bacc3 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -259,10 +259,10 @@ public boolean isValid() { public RealmQuery isNull(String fieldName) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName); // Checks that fieldName has the correct type is done in C++. - this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); return this; } @@ -277,10 +277,10 @@ public RealmQuery isNull(String fieldName) { public RealmQuery isNotNull(String fieldName) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName); // Checks that fieldName has the correct type is done in C++. - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); return this; } @@ -312,8 +312,8 @@ public RealmQuery equalTo(String fieldName, @Nullable String value, Case casi } private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable String value, Case casing) { - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value, casing); return this; } @@ -332,11 +332,11 @@ public RealmQuery equalTo(String fieldName, @Nullable Byte value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable Byte value) { - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -352,11 +352,11 @@ private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable public RealmQuery equalTo(String fieldName, @Nullable byte[] value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.BINARY); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.BINARY); if (value == null) { - this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -376,11 +376,11 @@ public RealmQuery equalTo(String fieldName, @Nullable Short value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable Short value) { - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -400,11 +400,11 @@ public RealmQuery equalTo(String fieldName, @Nullable Integer value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable Integer value) { - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -424,11 +424,11 @@ public RealmQuery equalTo(String fieldName, @Nullable Long value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable Long value) { - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -448,11 +448,11 @@ public RealmQuery equalTo(String fieldName, @Nullable Double value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable Double value) { - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DOUBLE); if (value == null) { - this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -472,11 +472,11 @@ public RealmQuery equalTo(String fieldName, @Nullable Float value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable Float value) { - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.FLOAT); if (value == null) { - this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -496,11 +496,11 @@ public RealmQuery equalTo(String fieldName, @Nullable Boolean value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable Boolean value) { - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.BOOLEAN); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.BOOLEAN); if (value == null) { - this.query.isNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -520,8 +520,8 @@ public RealmQuery equalTo(String fieldName, @Nullable Date value) { } private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable Date value) { - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DATE); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -782,11 +782,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable String value) { public RealmQuery notEqualTo(String fieldName, @Nullable String value, Case casing) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING); if (fd.length() > 1 && !casing.getValue()) { throw new IllegalArgumentException("Link queries cannot be case insensitive - coming soon."); } - this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value, casing); return this; } @@ -801,11 +801,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable String value, Case c public RealmQuery notEqualTo(String fieldName, @Nullable Byte value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -821,11 +821,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable Byte value) { public RealmQuery notEqualTo(String fieldName, @Nullable byte[] value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.BINARY); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.BINARY); if (value == null) { - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -841,11 +841,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable byte[] value) { public RealmQuery notEqualTo(String fieldName, @Nullable Short value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -861,11 +861,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable Short value) { public RealmQuery notEqualTo(String fieldName, @Nullable Integer value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -881,11 +881,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable Integer value) { public RealmQuery notEqualTo(String fieldName, @Nullable Long value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); if (value == null) { - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -901,11 +901,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable Long value) { public RealmQuery notEqualTo(String fieldName, @Nullable Double value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DOUBLE); if (value == null) { - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -921,11 +921,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable Double value) { public RealmQuery notEqualTo(String fieldName, @Nullable Float value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.FLOAT); if (value == null) { - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -941,11 +941,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable Float value) { public RealmQuery notEqualTo(String fieldName, @Nullable Boolean value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.BOOLEAN); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.BOOLEAN); if (value == null) { - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.equalTo(fd.getColumnIndices(), fd.getNativeTablePointers(), !value); + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), !value); } return this; } @@ -961,11 +961,11 @@ public RealmQuery notEqualTo(String fieldName, @Nullable Boolean value) { public RealmQuery notEqualTo(String fieldName, @Nullable Date value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DATE); if (value == null) { - this.query.isNotNull(fd.getColumnIndices(), fd.getNativeTablePointers()); + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); } else { - this.query.notEqualTo(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); } return this; } @@ -981,8 +981,8 @@ public RealmQuery notEqualTo(String fieldName, @Nullable Date value) { public RealmQuery greaterThan(String fieldName, int value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.greaterThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -997,8 +997,8 @@ public RealmQuery greaterThan(String fieldName, int value) { public RealmQuery greaterThan(String fieldName, long value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.greaterThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1013,8 +1013,8 @@ public RealmQuery greaterThan(String fieldName, long value) { public RealmQuery greaterThan(String fieldName, double value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DOUBLE); + this.query.greaterThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1029,8 +1029,8 @@ public RealmQuery greaterThan(String fieldName, double value) { public RealmQuery greaterThan(String fieldName, float value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.FLOAT); + this.query.greaterThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1045,8 +1045,8 @@ public RealmQuery greaterThan(String fieldName, float value) { public RealmQuery greaterThan(String fieldName, Date value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.greaterThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DATE); + this.query.greaterThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1061,8 +1061,8 @@ public RealmQuery greaterThan(String fieldName, Date value) { public RealmQuery greaterThanOrEqualTo(String fieldName, int value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.greaterThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1077,8 +1077,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, int value) { public RealmQuery greaterThanOrEqualTo(String fieldName, long value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.greaterThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1093,8 +1093,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, long value) { public RealmQuery greaterThanOrEqualTo(String fieldName, double value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DOUBLE); + this.query.greaterThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1109,8 +1109,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, double value) { public RealmQuery greaterThanOrEqualTo(String fieldName, float value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.FLOAT); + this.query.greaterThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1125,8 +1125,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, float value) { public RealmQuery greaterThanOrEqualTo(String fieldName, Date value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.greaterThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DATE); + this.query.greaterThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1141,8 +1141,8 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, Date value) { public RealmQuery lessThan(String fieldName, int value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.lessThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1157,8 +1157,8 @@ public RealmQuery lessThan(String fieldName, int value) { public RealmQuery lessThan(String fieldName, long value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.lessThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1173,8 +1173,8 @@ public RealmQuery lessThan(String fieldName, long value) { public RealmQuery lessThan(String fieldName, double value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DOUBLE); + this.query.lessThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1189,8 +1189,8 @@ public RealmQuery lessThan(String fieldName, double value) { public RealmQuery lessThan(String fieldName, float value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.FLOAT); + this.query.lessThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1205,8 +1205,8 @@ public RealmQuery lessThan(String fieldName, float value) { public RealmQuery lessThan(String fieldName, Date value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.lessThan(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DATE); + this.query.lessThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1221,8 +1221,8 @@ public RealmQuery lessThan(String fieldName, Date value) { public RealmQuery lessThanOrEqualTo(String fieldName, int value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.lessThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1237,8 +1237,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, int value) { public RealmQuery lessThanOrEqualTo(String fieldName, long value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.lessThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1253,8 +1253,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, long value) { public RealmQuery lessThanOrEqualTo(String fieldName, double value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DOUBLE); + this.query.lessThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1269,8 +1269,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, double value) { public RealmQuery lessThanOrEqualTo(String fieldName, float value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.FLOAT); + this.query.lessThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1285,8 +1285,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, float value) { public RealmQuery lessThanOrEqualTo(String fieldName, Date value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.lessThanOrEqual(fd.getColumnIndices(), fd.getNativeTablePointers(), value); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DATE); + this.query.lessThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } @@ -1302,8 +1302,8 @@ public RealmQuery lessThanOrEqualTo(String fieldName, Date value) { public RealmQuery between(String fieldName, int from, int to) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.between(fd.getColumnIndices(), from, to); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.between(fd.getColumnKeys(), from, to); return this; } @@ -1319,8 +1319,8 @@ public RealmQuery between(String fieldName, int from, int to) { public RealmQuery between(String fieldName, long from, long to) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.INTEGER); - this.query.between(fd.getColumnIndices(), from, to); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); + this.query.between(fd.getColumnKeys(), from, to); return this; } @@ -1336,8 +1336,8 @@ public RealmQuery between(String fieldName, long from, long to) { public RealmQuery between(String fieldName, double from, double to) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DOUBLE); - this.query.between(fd.getColumnIndices(), from, to); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DOUBLE); + this.query.between(fd.getColumnKeys(), from, to); return this; } @@ -1353,8 +1353,8 @@ public RealmQuery between(String fieldName, double from, double to) { public RealmQuery between(String fieldName, float from, float to) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.FLOAT); - this.query.between(fd.getColumnIndices(), from, to); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.FLOAT); + this.query.between(fd.getColumnKeys(), from, to); return this; } @@ -1370,8 +1370,8 @@ public RealmQuery between(String fieldName, float from, float to) { public RealmQuery between(String fieldName, Date from, Date to) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.DATE); - this.query.between(fd.getColumnIndices(), from, to); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DATE); + this.query.between(fd.getColumnKeys(), from, to); return this; } @@ -1400,8 +1400,8 @@ public RealmQuery contains(String fieldName, String value) { public RealmQuery contains(String fieldName, String value, Case casing) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.contains(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING); + this.query.contains(fd.getColumnKeys(), fd.getNativeTablePointers(), value, casing); return this; } @@ -1429,8 +1429,8 @@ public RealmQuery beginsWith(String fieldName, String value) { public RealmQuery beginsWith(String fieldName, String value, Case casing) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.beginsWith(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING); + this.query.beginsWith(fd.getColumnKeys(), fd.getNativeTablePointers(), value, casing); return this; } @@ -1458,8 +1458,8 @@ public RealmQuery endsWith(String fieldName, String value) { public RealmQuery endsWith(String fieldName, String value, Case casing) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.endsWith(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING); + this.query.endsWith(fd.getColumnKeys(), fd.getNativeTablePointers(), value, casing); return this; } @@ -1495,8 +1495,8 @@ public RealmQuery like(String fieldName, String value) { public RealmQuery like(String fieldName, String value, Case casing) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING); - this.query.like(fd.getColumnIndices(), fd.getNativeTablePointers(), value, casing); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING); + this.query.like(fd.getColumnKeys(), fd.getNativeTablePointers(), value, casing); return this; } @@ -1585,8 +1585,8 @@ public RealmQuery not() { public RealmQuery isEmpty(String fieldName) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST, RealmFieldType.LINKING_OBJECTS); - this.query.isEmpty(fd.getColumnIndices(), fd.getNativeTablePointers()); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST, RealmFieldType.LINKING_OBJECTS); + this.query.isEmpty(fd.getColumnKeys(), fd.getNativeTablePointers()); return this; } @@ -1602,8 +1602,8 @@ public RealmQuery isEmpty(String fieldName) { public RealmQuery isNotEmpty(String fieldName) { realm.checkIfValid(); - FieldDescriptor fd = schema.getColumnIndices(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST, RealmFieldType.LINKING_OBJECTS); - this.query.isNotEmpty(fd.getColumnIndices(), fd.getNativeTablePointers()); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.LIST, RealmFieldType.LINKING_OBJECTS); + this.query.isNotEmpty(fd.getColumnKeys(), fd.getNativeTablePointers()); return this; } @@ -1620,14 +1620,14 @@ public RealmQuery isNotEmpty(String fieldName) { public Number sum(String fieldName) { realm.checkIfValid(); - long columnIndex = schema.getAndCheckFieldIndex(fieldName); - switch (table.getColumnType(columnIndex)) { + long columnKey = schema.getAndCheckFieldColumnKey(fieldName); + switch (table.getColumnType(columnKey)) { case INTEGER: - return query.sumInt(columnIndex); + return query.sumInt(columnKey); case FLOAT: - return query.sumFloat(columnIndex); + return query.sumFloat(columnKey); case DOUBLE: - return query.sumDouble(columnIndex); + return query.sumDouble(columnKey); default: throw new IllegalArgumentException(String.format(Locale.US, TYPE_MISMATCH, fieldName, "int, float or double")); @@ -1647,7 +1647,7 @@ public Number sum(String fieldName) { public double average(String fieldName) { realm.checkIfValid(); - long columnIndex = schema.getAndCheckFieldIndex(fieldName); + long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); switch (table.getColumnType(columnIndex)) { case INTEGER: return query.averageInt(columnIndex); @@ -1674,7 +1674,7 @@ public double average(String fieldName) { public Number min(String fieldName) { realm.checkIfValid(); - long columnIndex = schema.getAndCheckFieldIndex(fieldName); + long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); switch (table.getColumnType(columnIndex)) { case INTEGER: return this.query.minimumInt(columnIndex); @@ -1701,7 +1701,7 @@ public Number min(String fieldName) { public Date minimumDate(String fieldName) { realm.checkIfValid(); - long columnIndex = schema.getAndCheckFieldIndex(fieldName); + long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); return this.query.minimumDate(columnIndex); } @@ -1718,7 +1718,7 @@ public Date minimumDate(String fieldName) { public Number max(String fieldName) { realm.checkIfValid(); - long columnIndex = schema.getAndCheckFieldIndex(fieldName); + long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); switch (table.getColumnType(columnIndex)) { case INTEGER: return this.query.maximumInt(columnIndex); @@ -1745,7 +1745,7 @@ public Number max(String fieldName) { public Date maximumDate(String fieldName) { realm.checkIfValid(); - long columnIndex = schema.getAndCheckFieldIndex(fieldName); + long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); return this.query.maximumDate(columnIndex); } @@ -2272,9 +2272,9 @@ private Subscription subscribe(String name, long timeToLive, TimeUnit timeUnit, // Convert timestamp to milliseconds and clamp at max long timeToLiveMs = TimeUnit.MILLISECONDS.convert(timeToLive, timeUnit); - long rowIndex = nativeSubscribe(realm.getSharedRealm().getNativePtr(), name, query.getNativePtr(), + long objKey = nativeSubscribe(realm.getSharedRealm().getNativePtr(), name, query.getNativePtr(), queryDescriptors.getNativePtr(), timeToLiveMs, update); - CheckedRow row = ((Realm) realm).getTable(Subscription.class).getCheckedRow(rowIndex); + CheckedRow row = ((Realm) realm).getTable(Subscription.class).getCheckedRow(objKey); return realm.get(Subscription.class, null, row); } @@ -2404,7 +2404,7 @@ private long getSourceRowIndexForFirstObject() { if (!queryDescriptors.isEmpty()) { RealmObjectProxy obj = (RealmObjectProxy) findAll().first(null); if (obj != null) { - return obj.realmGet$proxyState().getRow$realm().getIndex(); + return obj.realmGet$proxyState().getRow$realm().getObjectKey(); } else { return -1; } diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 802ab27c60..18979d112b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -20,6 +20,7 @@ import android.os.Looper; import java.util.Date; +import java.util.List; import java.util.Locale; import javax.annotation.Nullable; @@ -429,8 +430,8 @@ private Row checkRealmObjectConstraints(String fieldName, @Nullable RealmModel v // Check that type matches the expected one Table currentTable = osResults.getTable(); - long columnIndex = currentTable.getColumnIndex(fieldName); - Table expectedTable = currentTable.getLinkTarget(columnIndex); + long columnKey = currentTable.getColumnKey(fieldName); + Table expectedTable = currentTable.getLinkTarget(columnKey); Table inputTable = proxyState.getRow$realm().getTable(); if (!expectedTable.hasSameSchema(inputTable)) { throw new IllegalArgumentException(String.format(Locale.US, @@ -520,6 +521,32 @@ public void setList(String fieldName, RealmList list) { } } + /** + * {@inheritDoc} + */ + @Override + public boolean isFrozen() { + return realm != null && realm.isFrozen(); + } + + /** + * {@inheritDoc} + */ + @Override + public RealmResults freeze() { + if (!isValid()) { + throw new IllegalStateException("Only valid, managed RealmResults can be frozen."); + } + + BaseRealm frozenRealm = realm.freeze(); + OsResults frozenResults = osResults.freeze(frozenRealm.sharedRealm); + if (className != null) { + return new RealmResults<>(frozenRealm, frozenResults, className); + } else { + return new RealmResults<>(frozenRealm, frozenResults, classSpec); + } + } + private Class getListType(RealmList list) { if (!list.isEmpty()) { return list.first().getClass(); @@ -676,6 +703,21 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener + * Items emitted from Realm Flowables are frozen (See {@link #freeze()}. This means that they + * are immutable and can be read on any thread. + *

            + * Realm Flowables always emit items from the thread holding the live RealmResults. This means that if + * you need to do further processing, it is recommend to observe the values on a computation + * scheduler: + *

            + * {@code + * realm.where(Foo.class).findAllAsync().asFlowable() + * .observeOn(Schedulers.computation()) + * .map(rxResults -> doExpensiveWork(rxResults)) + * .observeOn(AndroidSchedulers.mainThread()) + * .subscribe( ... ); + * } + *

            * If you would like the {@code asFlowable()} to stop emitting items you can instruct RxJava to * only emit only the first item by using the {@code first()} operator: *

            @@ -688,15 +730,12 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener *

            - *

            Note that when the {@link Realm} is accessed from threads other than where it was created, - * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers - * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} - * instead. * * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} * or {@code OnError}. * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the * corresponding Realm instance doesn't support RxJava. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. * @see RxJava and Realm */ @SuppressWarnings("unchecked") @@ -723,14 +762,26 @@ public Flowable> asFlowable() { * time an RealmResults is emitted. *

            * RealmResults will continually be emitted as the RealmResults are updated - {@code onComplete} will never be called. - *

            Note that when the {@link Realm} is accessed from threads other than where it was created, - * {@link IllegalStateException} will be thrown. Care should be taken when using different schedulers - * with {@code subscribeOn()} and {@code observeOn()}. Consider using {@code Realm.where().find*Async()} - * instead. + *

            + * Items emitted from Realm Observables are frozen (See {@link #freeze()}. This means that they + * are immutable and can be read on any thread. + *

            + * Realm Observables always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommend to observe the values on a computation + * scheduler: + *

            + * {@code + * realm.where(Foo.class).findAllAsync().asChangesetObservable() + * .observeOn(Schedulers.computation()) + * .map((rxResults, changes) -> doExpensiveWork(rxResults, changes)) + * .observeOn(AndroidSchedulers.mainThread()) + * .subscribe( ... ); + * } * * @return RxJava Observable that only calls {@code onNext}. It will never call {@code onComplete} or {@code OnError}. * @throws UnsupportedOperationException if the required RxJava framework is not on the classpath or the * corresponding Realm instance doesn't support RxJava. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. * @see RxJava and Realm */ public Observable>> asChangesetObservable() { @@ -753,11 +804,10 @@ public Observable>> asChangesetObservable() { * * @return string representation of a JSON array containing entries of the resulting {@link RealmQuery}. */ - @Beta // until https://github.com/realm/realm-core/issues/3305 is fixed public String asJSON() { // maxDepth = -1: // Follow links to infinite depth, but only follow each link exactly once. - // Cycle links are printed as a simple sequence of integers of row indexes in the link column. + // Cycle links are printed as a simple sequence of integers of row keys in the link column. return osResults.toJSON(-1); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index 6f0045c270..afdb0f0ca0 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -17,7 +17,6 @@ package io.realm; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -25,10 +24,8 @@ import io.realm.internal.ColumnIndices; import io.realm.internal.ColumnInfo; -import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; import io.realm.internal.Util; -import io.realm.internal.util.Pair; /** * Class for interacting with the Realm schema. This makes it possible to inspect, add, delete and change the classes in @@ -232,12 +229,12 @@ final boolean haveColumnInfo() { } final ColumnInfo getColumnInfo(Class clazz) { - checkIndices(); + checkColumnKeys(); return columnIndices.getColumnInfo(clazz); } protected final ColumnInfo getColumnInfo(String className) { - checkIndices(); + checkColumnKeys(); return columnIndices.getColumnInfo(className); } @@ -249,9 +246,9 @@ final RealmObjectSchema removeFromClassNameToSchemaMap(String name) { return dynamicClassToSchema.remove(name); } - private void checkIndices() { + private void checkColumnKeys() { if (!haveColumnInfo()) { - throw new IllegalStateException("Attempt to use column index before set."); + throw new IllegalStateException("Attempt to use column key before set."); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index 115130670b..9d8ab3164c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -49,11 +49,11 @@ private CheckedRow(UncheckedRow row) { * * @param context the Realm context. * @param table the {@link Table} that holds the row. - * @param index the index of the row. + * @param objKey the object key. * @return an instance of Row for the table and index specified. */ - public static CheckedRow get(NativeContext context, Table table, long index) { - long nativeRowPointer = table.nativeGetRowPtr(table.getNativePtr(), index); + public static CheckedRow get(NativeContext context, Table table, long objKey) { + long nativeRowPointer = table.nativeGetRowPtr(table.getNativePtr(), objKey); return new CheckedRow(context, table, nativeRowPointer); } @@ -120,13 +120,18 @@ public OsList getValueList(long columnIndex, RealmFieldType fieldType) { } @Override - protected native long nativeGetColumnCount(long nativeTablePtr); + public Row freeze(OsSharedRealm frozenRealm) { + if (!isValid()) { + return InvalidRow.INSTANCE; + } + return new CheckedRow(context, parent.freeze(frozenRealm), nativeFreeze(getNativePtr(), frozenRealm.getNativePtr())); + } @Override - protected native String nativeGetColumnName(long nativeTablePtr, long columnIndex); + protected native long nativeGetColumnCount(long nativeTablePtr); @Override - protected native long nativeGetColumnIndex(long nativeTablePtr, String columnName); + protected native long nativeGetColumnKey(long nativeTablePtr, String columnName); @Override protected native int nativeGetColumnType(long nativeTablePtr, long columnIndex); diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java index f682e14c1e..ce36a99a6e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnInfo.java @@ -64,25 +64,25 @@ public abstract class ColumnInfo { // Immutable column information public static final class ColumnDetails { - public final long columnIndex; + public final long columnKey; public final RealmFieldType columnType; public final String linkedClassName; - private ColumnDetails(long columnIndex, RealmFieldType columnType, @Nullable String linkedClassName) { + private ColumnDetails(long columnKey, RealmFieldType columnType, @Nullable String linkedClassName) { // invariant: (columnType == OBJECT || columnType == LIST || columnType == LINKING_OBJECTS) == (linkedClassName != null) - this.columnIndex = columnIndex; + this.columnKey = columnKey; this.columnType = columnType; this.linkedClassName = linkedClassName; } ColumnDetails(Property property) { - this(property.getColumnIndex(), property.getType(), property.getLinkedObjectName()); + this(property.getColumnKey(), property.getType(), property.getLinkedObjectName()); } @Override public String toString() { StringBuilder buf = new StringBuilder("ColumnDetails["); - buf.append(columnIndex); + buf.append(columnKey); buf.append(", ").append(columnType); buf.append(", ").append(linkedClassName); return buf.append("]").toString(); @@ -90,8 +90,8 @@ public String toString() { } - private final Map indicesFromJavaFieldNames; - private final Map indicesFromColumnNames; + private final Map columnkeysFromJavaFieldNames; + private final Map columnKeysFromColumnNames; private final Map javaFieldNameToInternalNames; private final boolean mutable; @@ -111,16 +111,16 @@ protected ColumnInfo(int mapSize) { * @param mutable false to make this instance effectively final */ protected ColumnInfo(@Nullable ColumnInfo src, boolean mutable) { - this((src == null) ? 0 : src.indicesFromJavaFieldNames.size(), mutable); + this((src == null) ? 0 : src.columnkeysFromJavaFieldNames.size(), mutable); // ColumnDetails are immutable and may be re-used. if (src != null) { - indicesFromJavaFieldNames.putAll(src.indicesFromJavaFieldNames); + columnkeysFromJavaFieldNames.putAll(src.columnkeysFromJavaFieldNames); } } private ColumnInfo(int mapSize, boolean mutable) { - this.indicesFromJavaFieldNames = new HashMap<>(mapSize); - this.indicesFromColumnNames = new HashMap<>(mapSize); + this.columnkeysFromJavaFieldNames = new HashMap<>(mapSize); + this.columnKeysFromColumnNames = new HashMap<>(mapSize); this.javaFieldNameToInternalNames = new HashMap<>(mapSize); this.mutable = mutable; } @@ -135,13 +135,13 @@ public final boolean isMutable() { } /** - * Returns the index, in the described table, for the named column. + * Returns the column key, in the described table, for the named column. * - * @return column index. + * @return column key. */ - public long getColumnIndex(String javaFieldName) { - ColumnDetails details = indicesFromJavaFieldNames.get(javaFieldName); - return (details == null) ? -1 : details.columnIndex; + public long getColumnKey(String javaFieldName) { + ColumnDetails details = columnkeysFromJavaFieldNames.get(javaFieldName); + return (details == null) ? -1 : details.columnKey; } /** @@ -151,7 +151,7 @@ public long getColumnIndex(String javaFieldName) { */ @Nullable public ColumnDetails getColumnDetails(String javaFieldName) { - return indicesFromJavaFieldNames.get(javaFieldName); + return columnkeysFromJavaFieldNames.get(javaFieldName); } /** @@ -179,10 +179,10 @@ public void copyFrom(ColumnInfo src) { throw new NullPointerException("Attempt to copy null ColumnInfo"); } - indicesFromJavaFieldNames.clear(); - indicesFromJavaFieldNames.putAll(src.indicesFromJavaFieldNames); - indicesFromColumnNames.clear(); - indicesFromColumnNames.putAll(src.indicesFromColumnNames); + columnkeysFromJavaFieldNames.clear(); + columnkeysFromJavaFieldNames.putAll(src.columnkeysFromJavaFieldNames); + columnKeysFromColumnNames.clear(); + columnKeysFromColumnNames.putAll(src.columnKeysFromColumnNames); javaFieldNameToInternalNames.clear(); javaFieldNameToInternalNames.putAll(src.javaFieldNameToInternalNames); copy(src, this); @@ -192,20 +192,20 @@ public void copyFrom(ColumnInfo src) { public String toString() { StringBuilder buf = new StringBuilder("ColumnInfo["); buf.append("mutable="+mutable).append(","); - if (indicesFromJavaFieldNames != null) { + if (columnkeysFromJavaFieldNames != null) { buf.append("JavaFieldNames=["); boolean commaNeeded = false; - for (Map.Entry entry : indicesFromJavaFieldNames.entrySet()) { + for (Map.Entry entry : columnkeysFromJavaFieldNames.entrySet()) { if (commaNeeded) { buf.append(","); } buf.append(entry.getKey()).append("->").append(entry.getValue()); commaNeeded = true; } buf.append("]"); } - if (indicesFromColumnNames != null) { + if (columnKeysFromColumnNames != null) { buf.append(", InternalFieldNames=["); boolean commaNeeded = false; - for (Map.Entry entry : indicesFromColumnNames.entrySet()) { + for (Map.Entry entry : columnKeysFromColumnNames.entrySet()) { if (commaNeeded) { buf.append(","); } buf.append(entry.getKey()).append("->").append(entry.getValue()); commaNeeded = true; @@ -250,10 +250,10 @@ public String toString() { protected final long addColumnDetails(String javaFieldName, String internalColumnName, OsObjectSchemaInfo objectSchemaInfo) { Property property = objectSchemaInfo.getProperty(internalColumnName); ColumnDetails cd = new ColumnDetails(property); - indicesFromJavaFieldNames.put(javaFieldName, cd); - indicesFromColumnNames.put(internalColumnName, cd); + columnkeysFromJavaFieldNames.put(javaFieldName, cd); + columnKeysFromColumnNames.put(internalColumnName, cd); javaFieldNameToInternalNames.put(javaFieldName, internalColumnName); - return property.getColumnIndex(); + return property.getColumnKey(); } /** @@ -267,8 +267,8 @@ protected final long addColumnDetails(String javaFieldName, String internalColum * @param sourceJavaFieldName The name of the backlink source field. */ protected final void addBacklinkDetails(OsSchemaInfo schemaInfo, String javaFieldName, String sourceTableName, String sourceJavaFieldName) { - long columnIndex = schemaInfo.getObjectSchemaInfo(sourceTableName).getProperty(sourceJavaFieldName).getColumnIndex(); - indicesFromJavaFieldNames.put(javaFieldName, new ColumnDetails(columnIndex, RealmFieldType.LINKING_OBJECTS, sourceTableName)); + long columnKey = schemaInfo.getObjectSchemaInfo(sourceTableName).getProperty(sourceJavaFieldName).getColumnKey(); + columnkeysFromJavaFieldNames.put(javaFieldName, new ColumnDetails(columnKey, RealmFieldType.LINKING_OBJECTS, sourceTableName)); } /** @@ -278,7 +278,7 @@ protected final void addBacklinkDetails(OsSchemaInfo schemaInfo, String javaFiel * @return the column details map. */ @SuppressWarnings("ReturnOfCollectionOrArrayField") - public Map getIndicesMap() { - return indicesFromJavaFieldNames; + public Map getColumnKeysMap() { + return columnkeysFromJavaFieldNames; } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index 42f160d1d9..2d0681b11a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -22,7 +22,7 @@ /** - * Row wrapper that stubs all access with IllegalStateExceptions except for isAttached. This can be used instead of + * Row wrapper that stubs all access with IllegalStateExceptions except for isValid. This can be used instead of * adding null checks everywhere when the underlying Row accessor in Realm's underlying storage engine is no longer * available. */ @@ -35,17 +35,17 @@ public long getColumnCount() { } @Override - public String getColumnName(long columnIndex) { + public String[] getColumnNames() { throw getStubException(); } @Override - public long getColumnIndex(String columnName) { + public long getColumnKey(String columnName) { throw getStubException(); } @Override - public RealmFieldType getColumnType(long columnIndex) { + public RealmFieldType getColumnType(long columnKey) { throw getStubException(); } @@ -55,122 +55,122 @@ public Table getTable() { } @Override - public long getIndex() { + public long getObjectKey() { throw getStubException(); } @Override - public long getLong(long columnIndex) { + public long getLong(long columnKey) { throw getStubException(); } @Override - public boolean getBoolean(long columnIndex) { + public boolean getBoolean(long columnKey) { throw getStubException(); } @Override - public float getFloat(long columnIndex) { + public float getFloat(long columnKey) { throw getStubException(); } @Override - public double getDouble(long columnIndex) { + public double getDouble(long columnKey) { throw getStubException(); } @Override - public Date getDate(long columnIndex) { + public Date getDate(long columnKey) { throw getStubException(); } @Override - public String getString(long columnIndex) { + public String getString(long columnKey) { throw getStubException(); } @Override - public byte[] getBinaryByteArray(long columnIndex) { + public byte[] getBinaryByteArray(long columnKey) { throw getStubException(); } @Override - public long getLink(long columnIndex) { + public long getLink(long columnKey) { throw getStubException(); } @Override - public boolean isNullLink(long columnIndex) { + public boolean isNullLink(long columnKey) { throw getStubException(); } @Override - public OsList getModelList(long columnIndex) { + public OsList getModelList(long columnKey) { throw getStubException(); } @Override - public OsList getValueList(long columnIndex, RealmFieldType fieldType) { + public OsList getValueList(long columnKey, RealmFieldType fieldType) { throw getStubException(); } @Override - public void setLong(long columnIndex, long value) { + public void setLong(long columnKey, long value) { throw getStubException(); } @Override - public void setBoolean(long columnIndex, boolean value) { + public void setBoolean(long columnKey, boolean value) { throw getStubException(); } @Override - public void setFloat(long columnIndex, float value) { + public void setFloat(long columnKey, float value) { throw getStubException(); } @Override - public void setDouble(long columnIndex, double value) { + public void setDouble(long columnKey, double value) { throw getStubException(); } @Override - public void setDate(long columnIndex, Date date) { + public void setDate(long columnKey, Date date) { throw getStubException(); } @Override - public void setString(long columnIndex, String value) { + public void setString(long columnKey, String value) { throw getStubException(); } @Override - public void setBinaryByteArray(long columnIndex, byte[] data) { + public void setBinaryByteArray(long columnKey, byte[] data) { throw getStubException(); } @Override - public void setLink(long columnIndex, long value) { + public void setLink(long columnKey, long value) { throw getStubException(); } @Override - public void nullifyLink(long columnIndex) { + public void nullifyLink(long columnKey) { throw getStubException(); } @Override - public boolean isNull(long columnIndex) { + public boolean isNull(long columnKey) { throw getStubException(); } @Override - public void setNull(long columnIndex) { + public void setNull(long columnKey) { throw getStubException(); } @Override - public boolean isAttached() { + public boolean isValid() { return false; } @@ -184,6 +184,16 @@ public boolean hasColumn(String fieldName) { throw getStubException(); } + @Override + public Row freeze(OsSharedRealm frozenRealm) { + return INSTANCE; + } + + @Override + public boolean isLoaded() { + return true; + } + private RuntimeException getStubException() { return new IllegalStateException("Object is no longer managed by Realm. Has it been deleted?"); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java b/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java index 884da88dbb..5d7f015271 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java @@ -36,4 +36,11 @@ public interface ManagableObject { * @return {@code true} if this object is unmanaged or is still valid for use, {@code false} otherwise. */ boolean isValid(); + + /** + * Returns whether or not this object is frozen. + * + * @return {@code true} if the object is frozen, {@code false} if it is not. + */ + boolean isFrozen(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsList.java b/realm/realm-library/src/main/java/io/realm/internal/OsList.java index 6f30d9d364..71b60650b7 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsList.java @@ -19,9 +19,9 @@ public class OsList implements NativeObject, ObservableCollection { private final ObserverPairList observerPairs = new ObserverPairList(); - public OsList(UncheckedRow row, long columnIndex) { + public OsList(UncheckedRow row, long columnKey) { OsSharedRealm sharedRealm = row.getTable().getSharedRealm(); - long[] ptrs = nativeCreate(sharedRealm.getNativePtr(), row.getNativePtr(), columnIndex); + long[] ptrs = nativeCreate(sharedRealm.getNativePtr(), row.getNativePtr(), columnKey); this.nativePtr = ptrs[0]; this.context = sharedRealm.context; @@ -34,6 +34,14 @@ public OsList(UncheckedRow row, long columnIndex) { } } + // Use for creating a copy of the OsList, e.g when freezing it. + private OsList(OsSharedRealm sharedRealm, long listNativePtr, @Nullable Table targetTable) { + this.nativePtr = listNativePtr; + this.targetTable = targetTable; + this.context = sharedRealm.context; + context.addReference(this); + } + @Override public long getNativePtr() { return nativePtr; @@ -255,12 +263,18 @@ public void notifyChangeListeners(long nativeChangeSetPtr) { observerPairs.foreach(new Callback(changeset)); } + public OsList freeze(OsSharedRealm frozenRealm) { + return new OsList(frozenRealm, + nativeFreeze(nativePtr, frozenRealm.getNativePtr()), + (targetTable != null) ? targetTable.freeze(frozenRealm) : null); + } + private static native long nativeGetFinalizerPtr(); // TODO: nativeTablePtr is not necessary. It is used to create FieldDescriptor which should be generated from // OsSchemaInfo. // Returns {nativeListPtr, nativeTablePtr} - private static native long[] nativeCreate(long nativeSharedRealmPtr, long nativeRowPtr, long columnIndex); + private static native long[] nativeCreate(long nativeSharedRealmPtr, long nativeRowPtr, long columnKey); private static native long nativeGetRow(long nativePtr, long index); @@ -339,4 +353,6 @@ public void notifyChangeListeners(long nativeChangeSetPtr) { private native void nativeStartListening(long nativePtr); private native void nativeStopListening(long nativePtr); + + private static native long nativeFreeze(long nativePtr, long sharedRealmNativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index f332befe50..defa6baabb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -31,8 +31,6 @@ @Keep public class OsObject implements NativeObject { - private static final String OBJECT_ID_COLUMN_NAME = nativeGetObjectIdColumName(); - private static class OsObjectChangeSet implements ObjectChangeSet { final String[] changedFields; final boolean deleted; @@ -158,7 +156,7 @@ public void setObserverPairs(ObserverPairList pairs) { public static UncheckedRow create(Table table) { final OsSharedRealm sharedRealm = table.getSharedRealm(); return new UncheckedRow(sharedRealm.context, table, - nativeCreateNewObject(sharedRealm.getNativePtr(), table.getNativePtr())); + nativeCreateNewObject(table.getNativePtr())); } /** @@ -169,8 +167,7 @@ public static UncheckedRow create(Table table) { * @return a newly created row's index. */ public static long createRow(Table table) { - final OsSharedRealm sharedRealm = table.getSharedRealm(); - return nativeCreateRow(sharedRealm.getNativePtr(), table.getNativePtr()); + return nativeCreateRow(table.getNativePtr()); } private static long getAndVerifyPrimaryKeyColumnIndex(Table table) { @@ -178,7 +175,7 @@ private static long getAndVerifyPrimaryKeyColumnIndex(Table table) { if (pkField == null) { throw new IllegalStateException(table.getName() + " has no primary key defined."); } - return table.getColumnIndex(pkField); + return table.getColumnKey(pkField); } // TODO: consider to return a OsObject instead when integrating with Object Store's object accessor. @@ -190,8 +187,8 @@ private static long getAndVerifyPrimaryKeyColumnIndex(Table table) { * @return a newly created {@code UncheckedRow}. */ public static UncheckedRow createWithPrimaryKey(Table table, @Nullable Object primaryKeyValue) { - long primaryKeyColumnIndex = getAndVerifyPrimaryKeyColumnIndex(table); - RealmFieldType type = table.getColumnType(primaryKeyColumnIndex); + long primaryKeyColumnKey = getAndVerifyPrimaryKeyColumnIndex(table); + RealmFieldType type = table.getColumnType(primaryKeyColumnKey); final OsSharedRealm sharedRealm = table.getSharedRealm(); if (type == RealmFieldType.STRING) { @@ -200,13 +197,13 @@ public static UncheckedRow createWithPrimaryKey(Table table, @Nullable Object pr } return new UncheckedRow(sharedRealm.context, table, nativeCreateNewObjectWithStringPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), - primaryKeyColumnIndex, (String) primaryKeyValue)); + primaryKeyColumnKey, (String) primaryKeyValue)); } else if (type == RealmFieldType.INTEGER) { long value = primaryKeyValue == null ? 0 : Long.parseLong(primaryKeyValue.toString()); return new UncheckedRow(sharedRealm.context, table, nativeCreateNewObjectWithLongPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), - primaryKeyColumnIndex, value, primaryKeyValue == null)); + primaryKeyColumnKey, value, primaryKeyValue == null)); } else { throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); } @@ -243,10 +240,6 @@ public static long createRowWithPrimaryKey(Table table, long primaryKeyColumnInd } } - public static boolean isObjectIdColumn(String columnName) { - return OBJECT_ID_COLUMN_NAME.equals(columnName); - } - // Called by JNI @SuppressWarnings("unused") private void notifyChangeListeners(String[] changedFields) { @@ -261,31 +254,29 @@ private void notifyChangeListeners(String[] changedFields) { private native void nativeStopListening(long nativePtr); - private static native long nativeCreateNewObject(long sharedRealmPtr, long tablePtr); + private static native long nativeCreateNewObject(long tableRefPtr); - private static native long nativeCreateRow(long sharedRealmPtr, long tablePtr); + private static native long nativeCreateRow(long tableRefPtr); // Return a pointer to newly created Row. We may need to return a OsObject pointer in the future. private static native long nativeCreateNewObjectWithLongPrimaryKey(long sharedRealmPtr, - long tablePtr, long pk_column_index, + long tableRefPtr, long pk_column_index, long primaryKeyValue, boolean isNullValue); // Return a index of newly created Row. private static native long nativeCreateRowWithLongPrimaryKey(long sharedRealmPtr, - long tablePtr, long pk_column_index, + long tableRefPtr, long pk_column_index, long primaryKeyValue, boolean isNullValue); // Return a pointer to newly created Row. We may need to return a OsObject pointer in the future. private static native long nativeCreateNewObjectWithStringPrimaryKey(long sharedRealmPtr, - long tablePtr, long pk_column_index, + long tableRefPtr, long pk_column_index, @Nullable String primaryKeyValue); // Return a index of newly created Row. private static native long nativeCreateRowWithStringPrimaryKey(long sharedRealmPtr, - long tablePtr, long pk_column_index, + long tableRefPtr, long pk_column_index, String primaryKeyValue); - // Return sync::object_id_column_name - private static native String nativeGetObjectIdColumName(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java index 9f9e6f3b2a..cf2e4de4b5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java @@ -185,14 +185,6 @@ public Property getProperty(String propertyName) { return propertyPtr == 0 ? null : new Property(nativeGetPrimaryKeyProperty(nativePtr)); } - /** - * Returns the maximum table index used by core for this schema. - * If this Object has no properties -1 is returned. - */ - public long getMaxColumnIndex() { - return nativeGetMaxColumnIndex(nativePtr); - } - @Override public long getNativePtr() { return nativePtr; @@ -218,6 +210,4 @@ public long getNativeFinalizerPtr() { // Return nullptr if it doesn't have a primary key. private static native long nativeGetPrimaryKeyProperty(long nativePtr); - private static native long nativeGetMaxColumnIndex(long nativePtr); - } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 7891f469d3..c2e517b863 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -202,7 +202,7 @@ private OsRealmConfig(final RealmConfiguration config, @Nullable OsSharedRealm.MigrationCallback migrationCallback, @Nullable OsSharedRealm.InitializationCallback initializationCallback) { this.realmConfiguration = config; - this.nativePtr = nativeCreate(config.getPath(), fifoFallbackDir,false, true); + this.nativePtr = nativeCreate(config.getPath(), fifoFallbackDir, true, config.getMaxNumberOfActiveVersions()); NativeContext.dummyContext.addReference(this); // Retrieve Sync settings first. We need syncRealmUrl to identify if this is a SyncConfig @@ -363,7 +363,7 @@ NativeContext getContext() { return context; } - private static native long nativeCreate(String path, String fifoFallbackDir, boolean enableCache, boolean enableFormatUpdate); + private static native long nativeCreate(String path, String fifoFallbackDir, boolean enableFormatUpdate, long maxNumberOfActiveVersions); private static native void nativeSetEncryptionKey(long nativePtr, byte[] key); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 9994ed1884..fe40a175fc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -23,7 +23,6 @@ import javax.annotation.Nullable; -import io.realm.MutableRealmInteger; import io.realm.OrderedRealmCollectionChangeListener; import io.realm.RealmChangeListener; import io.realm.RealmList; @@ -247,17 +246,20 @@ public byte getValue() { @SuppressWarnings("WeakerAccess") public static final byte MODE_TABLE = 1; @SuppressWarnings("WeakerAccess") - public static final byte MODE_QUERY = 2; + public static final byte MODE_LIST = 2; @SuppressWarnings("WeakerAccess") - public static final byte MODE_LINKVIEW = 3; + public static final byte MODE_QUERY = 3; @SuppressWarnings("WeakerAccess") - public static final byte MODE_TABLEVIEW = 4; + public static final byte MODE_LINK_LIST = 4; + @SuppressWarnings("WeakerAccess") + public static final byte MODE_TABLEVIEW = 5; public enum Mode { EMPTY, // Backed by nothing (for missing tables) TABLE, // Backed directly by a Table + PRIMITIVE_LIST, // List of primitives QUERY, // Backed by a query that has not yet been turned into a TableView - LINKVIEW, // Backed directly by a LinkView + LINK_LIST, // Backed directly by a LinkView TABLEVIEW; // Backed by a TableView created from a Query static Mode getByValue(byte value) { @@ -268,8 +270,10 @@ static Mode getByValue(byte value) { return TABLE; case MODE_QUERY: return QUERY; - case MODE_LINKVIEW: - return LINKVIEW; + case MODE_LIST: + return PRIMITIVE_LIST; + case MODE_LINK_LIST: + return LINK_LIST; case MODE_TABLEVIEW: return TABLEVIEW; default: @@ -284,7 +288,7 @@ public static OsResults createForBacklinks(OsSharedRealm realm, UncheckedRow row realm.getNativePtr(), row.getNativePtr(), srcTable.getNativePtr(), - srcTable.getColumnIndex(srcFieldName)); + srcTable.getColumnKey(srcFieldName)); return new OsResults(realm, srcTable, backlinksPtr); } @@ -316,6 +320,14 @@ public OsResults createSnapshot() { return osResults; } + public OsResults freeze(OsSharedRealm frozenRealm) { + OsResults results = new OsResults(frozenRealm, table.freeze(frozenRealm), nativeFreeze(nativePtr, frozenRealm.getNativePtr())); + if (isLoaded()) { + results.load(); + } + return results; + } + @Override public long getNativePtr() { return nativePtr; @@ -359,8 +371,8 @@ public String toJSON(int maxDepth) { return toJSON(nativePtr, maxDepth); } - public Number aggregateNumber(Aggregate aggregateMethod, long columnIndex) { - return (Number) nativeAggregate(nativePtr, columnIndex, aggregateMethod.getValue()); + public Number aggregateNumber(Aggregate aggregateMethod, long columnKey) { + return (Number) nativeAggregate(nativePtr, columnKey, aggregateMethod.getValue()); } public Date aggregateDate(Aggregate aggregateMethod, long columnIndex) { @@ -467,7 +479,7 @@ private interface AddListTypeDelegate { // Helper method for adding specific types of lists. private void addTypeSpecificList(String fieldName, RealmList list, AddListTypeDelegate delegate) { //noinspection unchecked - OsObjectBuilder builder = new OsObjectBuilder(getTable(), 0, Collections.EMPTY_SET); + OsObjectBuilder builder = new OsObjectBuilder(getTable(), Collections.EMPTY_SET); delegate.addList(builder, list); try { nativeSetList(nativePtr, fieldName, builder.getNativePtr()); @@ -659,6 +671,8 @@ public void load() { private static native long nativeCreateSnapshot(long nativePtr); + private static native long nativeFreeze(long nativePtr, long frozenRealmNativePtr); + private static native long nativeGetRow(long nativePtr, int index); private static native long nativeFirstRow(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index d05d5cfa60..d8ddcf0e5d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -37,6 +37,11 @@ public final class OsSharedRealm implements Closeable, NativeObject { public static class VersionID implements Comparable { + // Realm Core uses unsigned integers to represent versions. This means + // they could theoretically hit this value (maximum value of unsigned + overflow) + // but very unlikely + public static final VersionID LIVE = new VersionID(-1, -1); + public final long version; public final long index; @@ -83,8 +88,7 @@ public boolean equals(Object object) { @Override public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (int) (version ^ (version >>> 32)); + int result = (int) (version ^ (version >>> 32)); result = 31 * result + (int) (index ^ (index >>> 32)); return result; } @@ -160,7 +164,7 @@ public interface SchemaChangedCallback { // Package protected for testing final List> iterators = new ArrayList<>(); - private OsSharedRealm(OsRealmConfig osRealmConfig) { + private OsSharedRealm(OsRealmConfig osRealmConfig, VersionID version) { Capabilities capabilities = new AndroidCapabilities(); RealmNotifier realmNotifier = new AndroidRealmNotifier(this, capabilities); @@ -168,7 +172,7 @@ private OsSharedRealm(OsRealmConfig osRealmConfig) { this.context = osRealmConfig.getContext(); sharedRealmsUnderConstruction.add(this); try { - this.nativePtr = nativeGetSharedRealm(osRealmConfig.getNativePtr(), realmNotifier); + this.nativePtr = nativeGetSharedRealm(osRealmConfig.getNativePtr(), version.version, version.index, realmNotifier); } catch (Throwable t) { // The SharedRealm instances have to be closed before throw. for (OsSharedRealm sharedRealm: tempSharedRealmsForCallback) { @@ -187,7 +191,9 @@ private OsSharedRealm(OsRealmConfig osRealmConfig) { this.capabilities = capabilities; this.realmNotifier = realmNotifier; - nativeSetAutoRefresh(nativePtr, capabilities.canDeliverNotification()); + if (version.equals(VersionID.LIVE)) { + nativeSetAutoRefresh(nativePtr, capabilities.canDeliverNotification()); + } } /** @@ -222,23 +228,27 @@ private OsSharedRealm(long nativeSharedRealmPtr, OsRealmConfig osRealmConfig) { } } - /** * Creates a {@code OsSharedRealm} instance in dynamic schema mode. + * + * @param config configuration to use + * @param version which version to use for a frozen instance or {@link VersionID#LIVE} for a live Realm. */ - public static OsSharedRealm getInstance(RealmConfiguration config) { + public static OsSharedRealm getInstance(RealmConfiguration config, VersionID version) { OsRealmConfig.Builder builder = new OsRealmConfig.Builder(config); - return getInstance(builder); + return getInstance(builder, version); } /** * Creates a {@code ShareRealm} instance from the given {@link OsRealmConfig.Builder}. + * + * @param configBuilder configuration to use + * @param version which version to use for a frozen instance or {@link VersionID#LIVE} for a live Realm. */ - public static OsSharedRealm getInstance(OsRealmConfig.Builder configBuilder) { + public static OsSharedRealm getInstance(OsRealmConfig.Builder configBuilder, VersionID version) { OsRealmConfig osRealmConfig = configBuilder.build(); ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(osRealmConfig); - - return new OsSharedRealm(osRealmConfig); + return new OsSharedRealm(osRealmConfig, version); } public static void initialize(File tempDirectory) { @@ -293,8 +303,8 @@ public boolean hasTable(String name) { * @throws IllegalArgumentException if the table doesn't exist. */ public Table getTable(String name) { - long tablePtr = nativeGetTable(nativePtr, name); - return new Table(this, tablePtr); + long tableRefPtr = nativeGetTableRef(nativePtr, name); + return new Table(this, tableRefPtr); } /** @@ -329,8 +339,9 @@ public void renameTable(String oldName, String newName) { nativeRenameTable(nativePtr, oldName, newName); } - public String getTableName(int index) { - return nativeGetTableName(nativePtr, index); + public String[] getTablesNames() { + String[] names = nativeGetTablesName(nativePtr); + return names != null? names : new String[]{}; } public long size() { @@ -346,11 +357,17 @@ public boolean isEmpty() { } public void refresh() { + if (isFrozen()) { + throw new IllegalStateException("It is not possible to refresh frozen Realms."); + } nativeRefresh(nativePtr); } public OsSharedRealm.VersionID getVersionID() { long[] versionId = nativeGetVersionID(nativePtr); + if (versionId == null) { + throw new IllegalStateException("Cannot get versionId, this could be related to a non existing read/write transaction"); + } return new OsSharedRealm.VersionID(versionId[0], versionId[1]); } @@ -459,6 +476,20 @@ public boolean isSyncRealm() { return osRealmConfig.getResolvedRealmURI() != null; } + /** + * Returns whether or not this Realm is frozen. + */ + public boolean isFrozen() { + return nativeIsFrozen(nativePtr); + } + + /** + * Returns a frozen copy of this Realm. + */ + public OsSharedRealm freeze() { + return new OsSharedRealm(osRealmConfig, getVersionID()); + } + // addIterator(), detachIterators() and invalidateIterators() are used to make RealmResults stable iterators work. // The iterator will iterate on a snapshot Results if it is accessed inside a transaction. // See https://github.com/realm/realm-java/issues/3883 for more information. @@ -546,7 +577,7 @@ private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealm private static native void nativeInit(String temporaryDirectoryPath); - private static native long nativeGetSharedRealm(long nativeConfigPtr, RealmNotifier notifier); + private static native long nativeGetSharedRealm(long nativeConfigPtr, long versionNo, long versionIndex, RealmNotifier notifier); private static native void nativeCloseSharedRealm(long nativeSharedRealmPtr); @@ -567,7 +598,7 @@ private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealm private static native long[] nativeGetVersionID(long nativeSharedRealmPtr); // Throw IAE if the table doesn't exist. - private static native long nativeGetTable(long nativeSharedRealmPtr, String tableName); + private static native long nativeGetTableRef(long nativeSharedRealmPtr, String tableName); // Throw IAE if the table exists already. private static native long nativeCreateTable(long nativeSharedRealmPtr, String tableName); @@ -578,7 +609,7 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare String primaryKeyFieldName, boolean isStringType, boolean isNullable); - private static native String nativeGetTableName(long nativeSharedRealmPtr, int index); + private static native String[] nativeGetTablesName(long nativeSharedRealmPtr); private static native boolean nativeHasTable(long nativeSharedRealmPtr, String tableName); @@ -610,6 +641,11 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare private static native int nativeGetClassPrivileges(long nativePtr, String className); private static native int nativeGetObjectPrivileges(long nativePtr, long rowNativePtr); + private static native boolean nativeIsPartial(long nativePtr); + private static native boolean nativeIsFrozen(long nativePtr); + + private static native long nativeFreeze(long nativePtr); + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 5fb25b5b5e..e96a01dc2e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -3,8 +3,7 @@ import java.lang.ref.WeakReference; import java.util.Date; -import javax.annotation.Nullable; - +import io.realm.FrozenPendingRow; import io.realm.RealmChangeListener; import io.realm.RealmFieldType; import io.realm.internal.core.DescriptorOrdering; @@ -65,17 +64,17 @@ public long getColumnCount() { } @Override - public String getColumnName(long columnIndex) { - throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + public String[] getColumnNames() { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public long getColumnIndex(String columnName) { + public long getColumnKey(String columnName) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public RealmFieldType getColumnType(long columnIndex) { + public RealmFieldType getColumnType(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @@ -85,122 +84,122 @@ public Table getTable() { } @Override - public long getIndex() { + public long getObjectKey() { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public long getLong(long columnIndex) { + public long getLong(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public boolean getBoolean(long columnIndex) { + public boolean getBoolean(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public float getFloat(long columnIndex) { + public float getFloat(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public double getDouble(long columnIndex) { + public double getDouble(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public Date getDate(long columnIndex) { + public Date getDate(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public String getString(long columnIndex) { + public String getString(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public byte[] getBinaryByteArray(long columnIndex) { + public byte[] getBinaryByteArray(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public long getLink(long columnIndex) { + public long getLink(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public boolean isNullLink(long columnIndex) { + public boolean isNullLink(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public OsList getModelList(long columnIndex) { + public OsList getModelList(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public OsList getValueList(long columnIndex, RealmFieldType fieldType) { + public OsList getValueList(long columnKey, RealmFieldType fieldType) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void setLong(long columnIndex, long value) { + public void setLong(long columnKey, long value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void setBoolean(long columnIndex, boolean value) { + public void setBoolean(long columnKey, boolean value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void setFloat(long columnIndex, float value) { + public void setFloat(long columnKey, float value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void setDouble(long columnIndex, double value) { + public void setDouble(long columnKey, double value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void setDate(long columnIndex, Date date) { + public void setDate(long columnKey, Date date) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void setString(long columnIndex, String value) { + public void setString(long columnKey, String value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void setBinaryByteArray(long columnIndex, byte[] data) { + public void setBinaryByteArray(long columnKey, byte[] data) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void setLink(long columnIndex, long value) { + public void setLink(long columnKey, long value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void nullifyLink(long columnIndex) { + public void nullifyLink(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public boolean isNull(long columnIndex) { + public boolean isNull(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public void setNull(long columnIndex) { + public void setNull(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override - public boolean isAttached() { + public boolean isValid() { return false; } @@ -214,6 +213,16 @@ public boolean hasColumn(String fieldName) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public Row freeze(OsSharedRealm frozenRealm) { + return FrozenPendingRow.INSTANCE; + } + + @Override + public boolean isLoaded() { + return false; + } + private void clearPendingCollection() { pendingOsResults.removeListener(this, listener); pendingOsResults = null; diff --git a/realm/realm-library/src/main/java/io/realm/internal/Property.java b/realm/realm-library/src/main/java/io/realm/internal/Property.java index 0bcff85f24..c5d4648ebb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Property.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Property.java @@ -189,8 +189,8 @@ public String getLinkedObjectName() { return nativeGetLinkedObjectName(nativePtr); } - public long getColumnIndex() { - return nativeGetColumnIndex(nativePtr); + public long getColumnKey() { + return nativeGetColumnKey(nativePtr); } @Override @@ -217,7 +217,7 @@ static native long nativeCreateComputedLinkProperty( private static native int nativeGetType(long nativePtr); - private static native long nativeGetColumnIndex(long nativePtr); + private static native long nativeGetColumnKey(long nativePtr); // Return null if the property is not OBJECT, LIST or LINKING_OBJECT type. private static native String nativeGetLinkedObjectName(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index 1256c37f8f..a58ad9ce51 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -36,86 +36,85 @@ public interface Row { long getColumnCount(); /** - * Returns the name of a column identified by columnIndex. Notice that the index is zero based. + * Returns all the column names of the tables. * - * @param columnIndex the column index. - * @return the name of the column. + * @return array of column names. */ - String getColumnName(long columnIndex); + String[] getColumnNames(); /** - * Returns the 0-based index of a column based on the name. + * Returns the column key from a column name. * * @param columnName column name - * @return the index, {@code -1} if not found + * @return the column key */ - long getColumnIndex(String columnName); + long getColumnKey(String columnName); /** - * Gets the type of a column identified by the columnIndex. + * Gets the type of a column identified by the columnKey. * - * @param columnIndex index of the column. + * @param columnKey column key. * @return the type of the particular column. */ - RealmFieldType getColumnType(long columnIndex); + RealmFieldType getColumnType(long columnKey); Table getTable(); /** - * Returns the index in the original source table, not the tableview. + * Returns the object key in the original source table, not the tableview. */ - long getIndex(); + long getObjectKey(); - long getLong(long columnIndex); + long getLong(long columnKey); - boolean getBoolean(long columnIndex); + boolean getBoolean(long columnKey); - float getFloat(long columnIndex); + float getFloat(long columnKey); - double getDouble(long columnIndex); + double getDouble(long columnKey); - Date getDate(long columnIndex); + Date getDate(long columnKey); - String getString(long columnIndex); + String getString(long columnKey); - byte[] getBinaryByteArray(long columnIndex); + byte[] getBinaryByteArray(long columnKey); - long getLink(long columnIndex); + long getLink(long columnKey); - boolean isNullLink(long columnIndex); + boolean isNullLink(long columnKey); - OsList getModelList(long columnIndex); + OsList getModelList(long columnKey); - OsList getValueList(long columnIndex, RealmFieldType fieldType); + OsList getValueList(long columnKey, RealmFieldType fieldType); - void setLong(long columnIndex, long value); + void setLong(long columnKey, long value); - void setBoolean(long columnIndex, boolean value); + void setBoolean(long columnKey, boolean value); - void setFloat(long columnIndex, float value); + void setFloat(long columnKey, float value); - void setDouble(long columnIndex, double value); + void setDouble(long columnKey, double value); - void setDate(long columnIndex, Date date); + void setDate(long columnKey, Date date); - void setString(long columnIndex, @Nullable String value); + void setString(long columnKey, @Nullable String value); - void setBinaryByteArray(long columnIndex, @Nullable byte[] data); + void setBinaryByteArray(long columnKey, @Nullable byte[] data); - void setLink(long columnIndex, long value); + void setLink(long columnKey, long value); - void nullifyLink(long columnIndex); + void nullifyLink(long columnKey); - boolean isNull(long columnIndex); + boolean isNull(long columnKey); - void setNull(long columnIndex); + void setNull(long columnKey); /** * Checks if the row is still valid. * * @return {@code true} if the row is still valid and attached to the underlying data. {@code false} otherwise. */ - boolean isAttached(); + boolean isValid(); /** * Throws {@link IllegalStateException} if the row is not attached. @@ -129,4 +128,15 @@ public interface Row { * @return {@code true} if field name exists, {@code false} otherwise. */ boolean hasColumn(String fieldName); + + /** + * Returns a frozen copy of this Row. + */ + Row freeze(OsSharedRealm frozenRealm); + + /** + * Return whether the row is considered to be loaded, i.e. it doesn't represent a query in flight. + * + */ + boolean isLoaded(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 499d4d201e..253bacaf14 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -44,25 +44,21 @@ public class Table implements NativeObject { private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - private final long nativePtr; + private final long nativeTableRefPtr; private final NativeContext context; private final OsSharedRealm sharedRealm; - Table(Table parent, long nativePointer) { - this(parent.sharedRealm, nativePointer); - } - - Table(OsSharedRealm sharedRealm, long nativePointer) { + Table(OsSharedRealm sharedRealm, long nativeTableRefPointer) { this.context = sharedRealm.context; this.sharedRealm = sharedRealm; - this.nativePtr = nativePointer; + this.nativeTableRefPtr = nativeTableRefPointer; context.addReference(this); } @Override public long getNativePtr() { - return nativePtr; + return nativeTableRefPtr; } @Override @@ -81,7 +77,7 @@ public Table getTable() { * The only method you can call is 'isValid()'. */ public boolean isValid() { - return nativePtr != 0 && nativeIsValid(nativePtr); + return nativeTableRefPtr != 0 && nativeIsValid(nativeTableRefPtr); } private void verifyColumnName(String name) { @@ -96,7 +92,7 @@ private void verifyColumnName(String name) { * @param type the column type. * @param name the field/column name. * @param isNullable {@code true} if column can contain null values, {@code false} otherwise. - * @return the index of the new column. + * @return the column key of the new column. */ public long addColumn(RealmFieldType type, String name, boolean isNullable) { verifyColumnName(name); @@ -108,7 +104,7 @@ public long addColumn(RealmFieldType type, String name, boolean isNullable) { case DATE: case FLOAT: case DOUBLE: - return nativeAddColumn(nativePtr, type.getNativeValue(), name, isNullable); + return nativeAddColumn(nativeTableRefPtr, type.getNativeValue(), name, isNullable); case INTEGER_LIST: case BOOLEAN_LIST: @@ -117,7 +113,7 @@ public long addColumn(RealmFieldType type, String name, boolean isNullable) { case DATE_LIST: case FLOAT_LIST: case DOUBLE_LIST: - return nativeAddPrimitiveListColumn(nativePtr, type.getNativeValue() - 128, name, isNullable); + return nativeAddPrimitiveListColumn(nativeTableRefPtr, type.getNativeValue() - 128, name, isNullable); default: throw new IllegalArgumentException("Unsupported type: " + type); @@ -127,7 +123,7 @@ public long addColumn(RealmFieldType type, String name, boolean isNullable) { /** * Adds a non-nullable column to the table dynamically. * - * @return the index of the new column. + * @return the column key of the new column. */ public long addColumn(RealmFieldType type, String name) { return addColumn(type, name, false); @@ -136,31 +132,31 @@ public long addColumn(RealmFieldType type, String name) { /** * Adds a link column to the table dynamically. * - * @return the index of the new column. + * @return the column key of the new column. */ public long addColumnLink(RealmFieldType type, String name, Table table) { verifyColumnName(name); - return nativeAddColumnLink(nativePtr, type.getNativeValue(), name, table.nativePtr); + return nativeAddColumnLink(nativeTableRefPtr, type.getNativeValue(), name, table.nativeTableRefPtr); } /** * Removes a column in the table dynamically. *

            - * It should be noted if {@code columnIndex} is the same as the primary key column index, + * It should be noted if {@code columnKey} is the same as the primary key column key, * the primary key column is removed from the meta table. * - * @param columnIndex the column index to be removed. + * @param columnKey the column key to be removed. */ - public void removeColumn(long columnIndex) { + public void removeColumn(long columnKey) { final String className = getClassName(); - // Checks the PK column index before removing a column. We don't know if we're hitting a PK col, + // Checks the PK column key before removing a column. We don't know if we're hitting a PK col, // but it should be noted that once a column is removed, there is no way we can find whether // a PK exists or not. - final String columnName = getColumnName(columnIndex); + final String columnName = getColumnName(columnKey); final String pkName = OsObjectStore.getPrimaryKeyForObject(sharedRealm, getClassName()); // First removes a column. If there is no error, we can proceed. Otherwise, it will stop here. - nativeRemoveColumn(nativePtr, columnIndex); + nativeRemoveColumn(nativeTableRefPtr, columnKey); // Checks if a PK exists and takes actions if there is. if (columnName.equals(pkName)) { @@ -175,18 +171,18 @@ public void removeColumn(long columnIndex) { * Renames a column in the table. If the column is a primary key column, the corresponding entry * in PrimaryKeyTable will be renamed accordingly. * - * @param columnIndex the column index to be renamed. + * @param columnKey the column to be renamed. * @param newName a new name replacing the old column name. * @throws IllegalArgumentException if {@code newFieldName} is an empty string, or exceeds field name length limit. */ - public void renameColumn(long columnIndex, String newName) { + public void renameColumn(long columnKey, String newName) { verifyColumnName(newName); // Gets the old column name. We'll assume that the old column name is *NOT* an empty string. - final String oldName = nativeGetColumnName(nativePtr, columnIndex); + final String oldName = nativeGetColumnName(nativeTableRefPtr, columnKey); final String pkName = OsObjectStore.getPrimaryKeyForObject(sharedRealm, getClassName()); // Then let's try to rename a column. If an error occurs for some reasons, we'll throw. - nativeRenameColumn(nativePtr, columnIndex, newName); + nativeRenameColumn(nativeTableRefPtr, columnKey, newName); // Renames a primary key. At this point, renaming the column name should have been fine. if (oldName.equals(pkName)) { @@ -195,53 +191,44 @@ public void renameColumn(long columnIndex, String newName) { } catch (Exception e) { // We failed to rename the pk meta table. roll back the column name, not pk meta table // then rethrow. - nativeRenameColumn(nativePtr, columnIndex, oldName); + nativeRenameColumn(nativeTableRefPtr, columnKey, oldName); throw new RuntimeException(e); } } } - /** - * Inserts a column at the given {@code columnIndex}. - * WARNING: This is only for internal testing purpose. Don't expose this to public API. - */ - public void insertColumn(long columnIndex, RealmFieldType type, String name) { - verifyColumnName(name); - nativeInsertColumn(nativePtr, columnIndex, type.getNativeValue(), name); - } - /** * Checks whether the specific column is nullable? * - * @param columnIndex the column index. + * @param columnKey the column to check. * @return {@code true} if column is nullable, {@code false} otherwise. */ - public boolean isColumnNullable(long columnIndex) { - return nativeIsColumnNullable(nativePtr, columnIndex); + public boolean isColumnNullable(long columnKey) { + return nativeIsColumnNullable(nativeTableRefPtr, columnKey); } /** * Converts a column to be nullable. * - * @param columnIndex the column index. + * @param columnKey the key for the column to convert. */ - public void convertColumnToNullable(long columnIndex) { + public void convertColumnToNullable(long columnKey) { if (sharedRealm.isSyncRealm()) { throw new IllegalStateException("This method is only available for non-synchronized Realms"); } - nativeConvertColumnToNullable(nativePtr, columnIndex, isPrimaryKey(columnIndex)); + nativeConvertColumnToNullable(nativeTableRefPtr, columnKey, isPrimaryKey(columnKey)); } /** * Converts a column to be not nullable. null values will be converted to default values. * - * @param columnIndex the column index. + * @param columnKey the key for the column to convert. */ - public void convertColumnToNotNullable(long columnIndex) { + public void convertColumnToNotNullable(long columnKey) { if (sharedRealm.isSyncRealm()) { throw new IllegalStateException("This method is only available for non-synchronized Realms"); } - nativeConvertColumnToNotNullable(nativePtr, columnIndex, isPrimaryKey(columnIndex)); + nativeConvertColumnToNotNullable(nativeTableRefPtr, columnKey, isPrimaryKey(columnKey)); } // Table Size and deletion. AutoGenerated subclasses are nothing to do with this @@ -253,7 +240,7 @@ public void convertColumnToNotNullable(long columnIndex) { * @return the number of rows. */ public long size() { - return nativeSize(nativePtr); + return nativeSize(nativeTableRefPtr); } /** @@ -272,7 +259,7 @@ public boolean isEmpty() { */ public void clear(boolean partialRealm) { checkImmutable(); - nativeClear(nativePtr, partialRealm); + nativeClear(nativeTableRefPtr, partialRealm); } // Column Information. @@ -283,61 +270,59 @@ public void clear(boolean partialRealm) { * @return the number of columns. */ public long getColumnCount() { - return nativeGetColumnCount(nativePtr); + return nativeGetColumnCount(nativeTableRefPtr); } /** - * Returns the name of a column identified by columnIndex. Notice that the index is zero based. + * Returns the name of a column identified by columnKey. * - * @param columnIndex the column index. + * @param columnKey the key of the column to find. * @return the name of the column. */ - public String getColumnName(long columnIndex) { - return nativeGetColumnName(nativePtr, columnIndex); + public String getColumnName(long columnKey) { + return nativeGetColumnName(nativeTableRefPtr, columnKey); } - /** - * Returns the 0-based index of a column based on the name. - * - * @param columnName column name. - * @return the index, {@link #NO_MATCH} if not found. - */ - public long getColumnIndex(String columnName) { + public String[] getColumnNames() { + return nativeGetColumnNames(nativeTableRefPtr); + } + + public long getColumnKey(String columnName) { if (columnName == null) { throw new IllegalArgumentException("Column name can not be null."); } - return nativeGetColumnIndex(nativePtr, columnName); + return nativeGetColumnKey(nativeTableRefPtr, columnName); } /** - * Gets the type of a column identified by the columnIndex. + * Gets the type of a column identified by the columnKey. * - * @param columnIndex index of the column. + * @param columnKey key of the column. * @return the type of the particular column. */ - public RealmFieldType getColumnType(long columnIndex) { - return RealmFieldType.fromNativeValue(nativeGetColumnType(nativePtr, columnIndex)); + public RealmFieldType getColumnType(long columnKey) { + return RealmFieldType.fromNativeValue(nativeGetColumnType(nativeTableRefPtr, columnKey)); } /** - * Removes a row from the specific index. If it is not the last row in the table, it then moves the last row into + * Removes a row from the specific row key. If it is not the last row in the table, it then moves the last row into * the vacated slot. * - * @param rowIndex the row index (starting with 0) + * @param rowKey the row key */ - public void moveLastOver(long rowIndex) { + public void moveLastOver(long rowKey) { checkImmutable(); - nativeMoveLastOver(nativePtr, rowIndex); + nativeMoveLastOver(nativeTableRefPtr, rowKey); } /** * Checks if a given column is a primary key column. * - * @param columnIndex the index of column in the table. + * @param columnKey key of the column. * @return {@code true} if column is a primary key, {@code false} otherwise. */ - private boolean isPrimaryKey(long columnIndex) { - return getColumnName(columnIndex).equals(OsObjectStore.getPrimaryKeyForObject(sharedRealm, getClassName())); + private boolean isPrimaryKey(long columnKey) { + return getColumnName(columnKey).equals(OsObjectStore.getPrimaryKeyForObject(sharedRealm, getClassName())); } /** @@ -358,64 +343,60 @@ public OsSharedRealm getSharedRealm() { return sharedRealm; } - public long getLong(long columnIndex, long rowIndex) { - return nativeGetLong(nativePtr, columnIndex, rowIndex); + public long getLong(long columnKey, long rowKey) { + return nativeGetLong(nativeTableRefPtr, columnKey, rowKey); } - public boolean getBoolean(long columnIndex, long rowIndex) { - return nativeGetBoolean(nativePtr, columnIndex, rowIndex); + public boolean getBoolean(long columnKey, long rowKey) { + return nativeGetBoolean(nativeTableRefPtr, columnKey, rowKey); } - public float getFloat(long columnIndex, long rowIndex) { - return nativeGetFloat(nativePtr, columnIndex, rowIndex); + public float getFloat(long columnKey, long rowKey) { + return nativeGetFloat(nativeTableRefPtr, columnKey, rowKey); } - public double getDouble(long columnIndex, long rowIndex) { - return nativeGetDouble(nativePtr, columnIndex, rowIndex); + public double getDouble(long columnKey, long rowKey) { + return nativeGetDouble(nativeTableRefPtr, columnKey, rowKey); } - public Date getDate(long columnIndex, long rowIndex) { - return new Date(nativeGetTimestamp(nativePtr, columnIndex, rowIndex)); + public Date getDate(long columnKey, long rowKey) { + return new Date(nativeGetTimestamp(nativeTableRefPtr, columnKey, rowKey)); } /** * Gets the value of a (string) cell. * - * @param columnIndex 0 based index value of the column - * @param rowIndex 0 based index of the row. + * @param columnKey column key. + * @param rowKey row key. * @return value of the particular cell */ - public String getString(long columnIndex, long rowIndex) { - return nativeGetString(nativePtr, columnIndex, rowIndex); + public String getString(long columnKey, long rowKey) { + return nativeGetString(nativeTableRefPtr, columnKey, rowKey); } - public byte[] getBinaryByteArray(long columnIndex, long rowIndex) { - return nativeGetByteArray(nativePtr, columnIndex, rowIndex); + public byte[] getBinaryByteArray(long columnKey, long rowKey) { + return nativeGetByteArray(nativeTableRefPtr, columnKey, rowKey); } - public long getLink(long columnIndex, long rowIndex) { - return nativeGetLink(nativePtr, columnIndex, rowIndex); + public long getLink(long columnKey, long rowKey) { + return nativeGetLink(nativeTableRefPtr, columnKey, rowKey); } - public Table getLinkTarget(long columnIndex) { - long nativeTablePointer = nativeGetLinkTarget(nativePtr, columnIndex); + public Table getLinkTarget(long columnKey) { + long nativeTablePointer = nativeGetLinkTarget(nativeTableRefPtr, columnKey); // Copies context reference from parent. return new Table(this.sharedRealm, nativeTablePointer); } - public boolean isNull(long columnIndex, long rowIndex) { - return nativeIsNull(nativePtr, columnIndex, rowIndex); - } - /** * Returns a non-checking Row. Incorrect use of this Row will cause a hard core crash. * If error checking is required, use {@link #getCheckedRow(long)} instead. * - * @param index the index of row to fetch. + * @param rowKey row key to fetch. * @return the unsafe row wrapper object. */ - public UncheckedRow getUncheckedRow(long index) { - return UncheckedRow.getByRowIndex(context, this, index); + public UncheckedRow getUncheckedRow(long rowKey) { + return UncheckedRow.getByRowKey(context, this, rowKey); } /** @@ -435,116 +416,100 @@ public UncheckedRow getUncheckedRowByPointer(long nativeRowPointer) { *

            * If error checking is done elsewhere, consider using {@link #getUncheckedRow(long)} for better performance. * - * @param index the index of row to fetch. + * @param objKey the Object Key. * @return the safe row wrapper object. */ - public CheckedRow getCheckedRow(long index) { - return CheckedRow.get(context, this, index); + public CheckedRow getCheckedRow(long objKey) { + return CheckedRow.get(context, this, objKey); } // // Setters // - public void setLong(long columnIndex, long rowIndex, long value, boolean isDefault) { + public void setLong(long columnKey, long rowKey, long value, boolean isDefault) { checkImmutable(); - nativeSetLong(nativePtr, columnIndex, rowIndex, value, isDefault); + nativeSetLong(nativeTableRefPtr, columnKey, rowKey, value, isDefault); } // must not be called on a primary key field - public void incrementLong(long columnIndex, long rowIndex, long value) { + public void incrementLong(long columnKey, long rowKey, long value) { checkImmutable(); - nativeIncrementLong(nativePtr, columnIndex, rowIndex, value); + nativeIncrementLong(nativeTableRefPtr, columnKey, rowKey, value); } - public void setBoolean(long columnIndex, long rowIndex, boolean value, boolean isDefault) { + public void setBoolean(long columnKey, long rowKey, boolean value, boolean isDefault) { checkImmutable(); - nativeSetBoolean(nativePtr, columnIndex, rowIndex, value, isDefault); + nativeSetBoolean(nativeTableRefPtr, columnKey, rowKey, value, isDefault); } - public void setFloat(long columnIndex, long rowIndex, float value, boolean isDefault) { + public void setFloat(long columnKey, long rowKey, float value, boolean isDefault) { checkImmutable(); - nativeSetFloat(nativePtr, columnIndex, rowIndex, value, isDefault); + nativeSetFloat(nativeTableRefPtr, columnKey, rowKey, value, isDefault); } - public void setDouble(long columnIndex, long rowIndex, double value, boolean isDefault) { + public void setDouble(long columnKey, long rowKey, double value, boolean isDefault) { checkImmutable(); - nativeSetDouble(nativePtr, columnIndex, rowIndex, value, isDefault); + nativeSetDouble(nativeTableRefPtr, columnKey, rowKey, value, isDefault); } - public void setDate(long columnIndex, long rowIndex, Date date, boolean isDefault) { + public void setDate(long columnKey, long rowKey, Date date, boolean isDefault) { if (date == null) { throw new IllegalArgumentException("Null Date is not allowed."); } checkImmutable(); - nativeSetTimestamp(nativePtr, columnIndex, rowIndex, date.getTime(), isDefault); + nativeSetTimestamp(nativeTableRefPtr, columnKey, rowKey, date.getTime(), isDefault); } /** - * Sets a String value to a cell of Table, pointed by column and row index. + * Sets a String value to a cell of Table, pointed by column and row key. * - * @param columnIndex 0 based index value of the cell column. - * @param rowIndex 0 based index value of the cell row. + * @param columnKey cell column. + * @param rowKey cell row. * @param value a String value to set in the cell. */ - public void setString(long columnIndex, long rowIndex, @Nullable String value, boolean isDefault) { + public void setString(long columnKey, long rowKey, @Nullable String value, boolean isDefault) { checkImmutable(); if (value == null) { - nativeSetNull(nativePtr, columnIndex, rowIndex, isDefault); + nativeSetNull(nativeTableRefPtr, columnKey, rowKey, isDefault); } else { - nativeSetString(nativePtr, columnIndex, rowIndex, value, isDefault); + nativeSetString(nativeTableRefPtr, columnKey, rowKey, value, isDefault); } } - public void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data, boolean isDefault) { + public void setBinaryByteArray(long columnKey, long rowKey, byte[] data, boolean isDefault) { checkImmutable(); - nativeSetByteArray(nativePtr, columnIndex, rowIndex, data, isDefault); + nativeSetByteArray(nativeTableRefPtr, columnKey, rowKey, data, isDefault); } - public void setLink(long columnIndex, long rowIndex, long value, boolean isDefault) { + public void setLink(long columnKey, long rowKey, long value, boolean isDefault) { checkImmutable(); - nativeSetLink(nativePtr, columnIndex, rowIndex, value, isDefault); + nativeSetLink(nativeTableRefPtr, columnKey, rowKey, value, isDefault); } - public void setNull(long columnIndex, long rowIndex, boolean isDefault) { + public void setNull(long columnKey, long rowKey, boolean isDefault) { checkImmutable(); - nativeSetNull(nativePtr, columnIndex, rowIndex, isDefault); + nativeSetNull(nativeTableRefPtr, columnKey, rowKey, isDefault); } - public void addSearchIndex(long columnIndex) { + public void addSearchIndex(long columnKey) { checkImmutable(); - nativeAddSearchIndex(nativePtr, columnIndex); + nativeAddSearchIndex(nativeTableRefPtr, columnKey); } - public void removeSearchIndex(long columnIndex) { + public void removeSearchIndex(long columnKey) { checkImmutable(); - nativeRemoveSearchIndex(nativePtr, columnIndex); - } - - /* - * 1) Migration required to fix https://github.com/realm/realm-java/issues/1059 - * This will convert INTEGER column to the corresponding STRING column if needed. - * Any database created on Realm-Java 0.80.1 and below will have this error. - * - * 2) Migration required to fix: https://github.com/realm/realm-java/issues/1703 - * This will remove the prefix "class_" from all table names in the pk_column - * Any database created on Realm-Java 0.84.1 and below will have this error. - * - * The native method will begin a transaction and make the migration if needed. - * This function should not be called in a transaction. - */ - public static void migratePrimaryKeyTableIfNeeded(OsSharedRealm sharedRealm) { - nativeMigratePrimaryKeyTableIfNeeded(sharedRealm.getNativePtr()); + nativeRemoveSearchIndex(nativeTableRefPtr, columnKey); } - public boolean hasSearchIndex(long columnIndex) { - return nativeHasSearchIndex(nativePtr, columnIndex); + public boolean hasSearchIndex(long columnKey) { + return nativeHasSearchIndex(nativeTableRefPtr, columnKey); } - public boolean isNullLink(long columnIndex, long rowIndex) { - return nativeIsNullLink(nativePtr, columnIndex, rowIndex); + public boolean isNullLink(long columnKey, long rowKey) { + return nativeIsNullLink(nativeTableRefPtr, columnKey, rowKey); } - public void nullifyLink(long columnIndex, long rowIndex) { - nativeNullifyLink(nativePtr, columnIndex, rowIndex); + public void nullifyLink(long columnKey, long rowKey) { + nativeNullifyLink(nativeTableRefPtr, columnKey, rowKey); } boolean isImmutable() { @@ -562,20 +527,20 @@ void checkImmutable() { // Count // - public long count(long columnIndex, long value) { - return nativeCountLong(nativePtr, columnIndex, value); + public long count(long columnKey, long value) { + return nativeCountLong(nativeTableRefPtr, columnKey, value); } - public long count(long columnIndex, float value) { - return nativeCountFloat(nativePtr, columnIndex, value); + public long count(long columnKey, float value) { + return nativeCountFloat(nativeTableRefPtr, columnKey, value); } - public long count(long columnIndex, double value) { - return nativeCountDouble(nativePtr, columnIndex, value); + public long count(long columnKey, double value) { + return nativeCountDouble(nativeTableRefPtr, columnKey, value); } - public long count(long columnIndex, String value) { - return nativeCountString(nativePtr, columnIndex, value); + public long count(long columnKey, String value) { + return nativeCountString(nativeTableRefPtr, columnKey, value); } // @@ -583,49 +548,49 @@ public long count(long columnIndex, String value) { // public TableQuery where() { - long nativeQueryPtr = nativeWhere(nativePtr); + long nativeQueryPtr = nativeWhere(nativeTableRefPtr); // Copies context reference from parent. return new TableQuery(this.context, this, nativeQueryPtr); } - public long findFirstLong(long columnIndex, long value) { - return nativeFindFirstInt(nativePtr, columnIndex, value); + public long findFirstLong(long columnKey, long value) { + return nativeFindFirstInt(nativeTableRefPtr, columnKey, value); } - public long findFirstBoolean(long columnIndex, boolean value) { - return nativeFindFirstBool(nativePtr, columnIndex, value); + public long findFirstBoolean(long columnKey, boolean value) { + return nativeFindFirstBool(nativeTableRefPtr, columnKey, value); } - public long findFirstFloat(long columnIndex, float value) { - return nativeFindFirstFloat(nativePtr, columnIndex, value); + public long findFirstFloat(long columnKey, float value) { + return nativeFindFirstFloat(nativeTableRefPtr, columnKey, value); } - public long findFirstDouble(long columnIndex, double value) { - return nativeFindFirstDouble(nativePtr, columnIndex, value); + public long findFirstDouble(long columnKey, double value) { + return nativeFindFirstDouble(nativeTableRefPtr, columnKey, value); } - public long findFirstDate(long columnIndex, Date date) { + public long findFirstDate(long columnKey, Date date) { if (date == null) { throw new IllegalArgumentException("null is not supported"); } - return nativeFindFirstTimestamp(nativePtr, columnIndex, date.getTime()); + return nativeFindFirstTimestamp(nativeTableRefPtr, columnKey, date.getTime()); } - public long findFirstString(long columnIndex, String value) { + public long findFirstString(long columnKey, String value) { if (value == null) { throw new IllegalArgumentException("null is not supported"); } - return nativeFindFirstString(nativePtr, columnIndex, value); + return nativeFindFirstString(nativeTableRefPtr, columnKey, value); } /** * Searches for first occurrence of null. Beware that the order in the column is undefined. * - * @param columnIndex the column to search in. + * @param columnKey the column to search in. * @return the row index for the first match found or {@link #NO_MATCH}. */ - public long findFirstNull(long columnIndex) { - return nativeFindFirstNull(nativePtr, columnIndex); + public long findFirstNull(long columnKey) { + return nativeFindFirstNull(nativeTableRefPtr, columnKey); } // @@ -637,17 +602,21 @@ public long findFirstNull(long columnIndex) { */ @Nullable public String getName() { - return nativeGetName(nativePtr); + return nativeGetName(nativeTableRefPtr); } /** * Returns the class name for the table. * - * @return Name of the the table or {@code null} if it not part of a group. + * @return Name of the the table + * @throws IllegalStateException if the table has been deleted or no longer is part of the group. */ - @Nullable public String getClassName() { - return getClassNameForTable(getName()); + String name = getClassNameForTable(getName()); // Core returns "" if Table is no longer attached + if (Util.isEmptyString(name)) { + throw new IllegalStateException("This object class is no longer part of the schema for the Realm file. It is therefor not possible to access the schema name."); + } + return name; } @Override @@ -663,11 +632,13 @@ public String toString() { stringBuilder.append(columnCount); stringBuilder.append(" columns: "); - for (int i = 0; i < columnCount; i++) { - if (i != 0) { + boolean isFirst = true; + for (String column: getColumnNames()) { + if(!isFirst) { stringBuilder.append(", "); } - stringBuilder.append(getColumnName(i)); + isFirst = false; + stringBuilder.append(column); } stringBuilder.append("."); @@ -692,7 +663,17 @@ public boolean hasSameSchema(Table table) { if (table == null) { throw new IllegalArgumentException("The argument cannot be null"); } - return nativeHasSameSchema(this.nativePtr, table.nativePtr); + return nativeHasSameSchema(this.nativeTableRefPtr, table.nativeTableRefPtr); + } + + /** + * Returns a frozen copy of this table. + */ + public Table freeze(OsSharedRealm frozenRealm) { + if (!frozenRealm.isFrozen()) { + throw new IllegalArgumentException("Frozen Realm required"); + } + return new Table(frozenRealm, nativeFreeze(frozenRealm.getNativePtr(), nativeTableRefPtr)); } @Nullable @@ -710,121 +691,121 @@ public static String getTableNameForClass(String name) { return TABLE_PREFIX + name; } - private native boolean nativeIsValid(long nativeTablePtr); - - private native long nativeAddColumn(long nativeTablePtr, int type, String name, boolean isNullable); + private native boolean nativeIsValid(long nativeTableRefPtr); - private native long nativeAddPrimitiveListColumn(long nativeTablePtr, int type, String name, boolean isNullable); + private native long nativeAddColumn(long nativeTableRefPtr, int type, String name, boolean isNullable); - private native long nativeAddColumnLink(long nativeTablePtr, int type, String name, long targetTablePtr); + private native long nativeAddPrimitiveListColumn(long nativeTableRefPtr, int type, String name, boolean isNullable); - private native void nativeRenameColumn(long nativeTablePtr, long columnIndex, String name); + private native long nativeAddColumnLink(long nativeTableRefPtr, int type, String name, long targetTablePtr); - private native void nativeRemoveColumn(long nativeTablePtr, long columnIndex); + private native void nativeRenameColumn(long nativeTableRefPtr, long columnKey, String name); - private static native void nativeInsertColumn(long nativeTablePtr, long columnIndex, int type, String name); + private native void nativeRemoveColumn(long nativeTableRefPtr, long columnKey); - private native boolean nativeIsColumnNullable(long nativePtr, long columnIndex); + private native boolean nativeIsColumnNullable(long nativePtr, long columnKey); - private native void nativeConvertColumnToNullable(long nativeTablePtr, long columnIndex, boolean isPrimaryKey); + private native void nativeConvertColumnToNullable(long nativeTableRefPtr, long columnKey, boolean isPrimaryKey); - private native void nativeConvertColumnToNotNullable(long nativePtr, long columnIndex, boolean isPrimaryKey); + private native void nativeConvertColumnToNotNullable(long nativePtr, long columnKey, boolean isPrimaryKey); - private native long nativeSize(long nativeTablePtr); + private native long nativeSize(long nativeTableRefPtr); - private native void nativeClear(long nativeTablePtr, boolean partialRealm); + private native void nativeClear(long nativeTableRefPtr, boolean partialRealm); - private native long nativeGetColumnCount(long nativeTablePtr); + private native long nativeGetColumnCount(long nativeTableRefPtr); - private native String nativeGetColumnName(long nativeTablePtr, long columnIndex); + private native String nativeGetColumnName(long nativeTableRefPtr, long columnKey); - private native long nativeGetColumnIndex(long nativeTablePtr, String columnName); + private native String[] nativeGetColumnNames(long nativeTableRefPtr); - private native int nativeGetColumnType(long nativeTablePtr, long columnIndex); + private native long nativeGetColumnKey(long nativeTableRefPtr, String columnName); - private native void nativeMoveLastOver(long nativeTablePtr, long rowIndex); + private native int nativeGetColumnType(long nativeTableRefPtr, long columnKey); - private native long nativeGetLong(long nativeTablePtr, long columnIndex, long rowIndex); + private native void nativeMoveLastOver(long nativeTableRefPtr, long rowKey); - private native boolean nativeGetBoolean(long nativeTablePtr, long columnIndex, long rowIndex); + private native long nativeGetLong(long nativeTableRefPtr, long columnKey, long rowKey); - private native float nativeGetFloat(long nativeTablePtr, long columnIndex, long rowIndex); + private native boolean nativeGetBoolean(long nativeTableRefPtr, long columnKey, long rowKey); - private native double nativeGetDouble(long nativeTablePtr, long columnIndex, long rowIndex); + private native float nativeGetFloat(long nativeTableRefPtr, long columnKey, long rowKey); - private native long nativeGetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex); + private native double nativeGetDouble(long nativeTableRefPtr, long columnKey, long rowKey); - private native String nativeGetString(long nativePtr, long columnIndex, long rowIndex); + private native long nativeGetTimestamp(long nativeTableRefPtr, long columnKey, long rowKey); - private native byte[] nativeGetByteArray(long nativePtr, long columnIndex, long rowIndex); + private native String nativeGetString(long nativePtr, long columnKey, long rowKey); - private native long nativeGetLink(long nativePtr, long columnIndex, long rowIndex); + private native byte[] nativeGetByteArray(long nativePtr, long columnKey, long rowKey); - private native long nativeGetLinkTarget(long nativePtr, long columnIndex); + private native long nativeGetLink(long nativePtr, long columnKey, long rowKey); - private native boolean nativeIsNull(long nativePtr, long columnIndex, long rowIndex); + private native long nativeGetLinkTarget(long nativePtr, long columnKey); - native long nativeGetRowPtr(long nativePtr, long index); + private native boolean nativeIsNull(long nativePtr, long columnKey, long rowKey); - public static native void nativeSetLong(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); + native long nativeGetRowPtr(long nativePtr, long objKey); - public static native void nativeIncrementLong(long nativeTablePtr, long columnIndex, long rowIndex, long value); + public static native void nativeSetLong(long nativeTableRefPtr, long columnKey, long rowKey, long value, boolean isDefault); - public static native void nativeSetBoolean(long nativeTablePtr, long columnIndex, long rowIndex, boolean value, boolean isDefault); + public static native void nativeIncrementLong(long nativeTableRefPtr, long columnKey, long rowKey, long value); - public static native void nativeSetFloat(long nativeTablePtr, long columnIndex, long rowIndex, float value, boolean isDefault); + public static native void nativeSetBoolean(long nativeTableRefPtr, long columnKey, long rowKey, boolean value, boolean isDefault); - public static native void nativeSetDouble(long nativeTablePtr, long columnIndex, long rowIndex, double value, boolean isDefault); + public static native void nativeSetFloat(long nativeTableRefPtr, long columnKey, long rowKey, float value, boolean isDefault); - public static native void nativeSetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex, long dateTimeValue, boolean isDefault); + public static native void nativeSetDouble(long nativeTableRefPtr, long columnKey, long rowKey, double value, boolean isDefault); - public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value, boolean isDefault); + public static native void nativeSetTimestamp(long nativeTableRefPtr, long columnKey, long rowKey, long dateTimeValue, boolean isDefault); - public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex, boolean isDefault); + public static native void nativeSetString(long nativeTableRefPtr, long columnKey, long rowKey, String value, boolean isDefault); - public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data, boolean isDefault); + public static native void nativeSetNull(long nativeTableRefPtr, long columnKey, long rowKey, boolean isDefault); - public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); + public static native void nativeSetByteArray(long nativePtr, long columnKey, long rowKey, byte[] data, boolean isDefault); - private static native void nativeMigratePrimaryKeyTableIfNeeded(long sharedRealmPtr); + public static native void nativeSetLink(long nativeTableRefPtr, long columnKey, long rowKey, long value, boolean isDefault); - private native void nativeAddSearchIndex(long nativePtr, long columnIndex); + private native void nativeAddSearchIndex(long nativePtr, long columnKey); - private native void nativeRemoveSearchIndex(long nativePtr, long columnIndex); + private native void nativeRemoveSearchIndex(long nativePtr, long columnKey); - private native boolean nativeHasSearchIndex(long nativePtr, long columnIndex); + private native boolean nativeHasSearchIndex(long nativePtr, long columnKey); - private native boolean nativeIsNullLink(long nativePtr, long columnIndex, long rowIndex); + private native boolean nativeIsNullLink(long nativePtr, long columnKey, long rowKey); - public static native void nativeNullifyLink(long nativePtr, long columnIndex, long rowIndex); + public static native void nativeNullifyLink(long nativePtr, long columnKey, long rowKey); - private native long nativeCountLong(long nativePtr, long columnIndex, long value); + private native long nativeCountLong(long nativePtr, long columnKey, long value); - private native long nativeCountFloat(long nativePtr, long columnIndex, float value); + private native long nativeCountFloat(long nativePtr, long columnKey, float value); - private native long nativeCountDouble(long nativePtr, long columnIndex, double value); + private native long nativeCountDouble(long nativePtr, long columnKey, double value); - private native long nativeCountString(long nativePtr, long columnIndex, String value); + private native long nativeCountString(long nativePtr, long columnKey, String value); - private native long nativeWhere(long nativeTablePtr); + private native long nativeWhere(long nativeTableRefPtr); - public static native long nativeFindFirstInt(long nativeTablePtr, long columnIndex, long value); + public static native long nativeFindFirstInt(long nativeTableRefPtr, long columnKey, long value); - private native long nativeFindFirstBool(long nativePtr, long columnIndex, boolean value); + private native long nativeFindFirstBool(long nativePtr, long columnKey, boolean value); - private native long nativeFindFirstFloat(long nativePtr, long columnIndex, float value); + private native long nativeFindFirstFloat(long nativePtr, long columnKey, float value); - private native long nativeFindFirstDouble(long nativePtr, long columnIndex, double value); + private native long nativeFindFirstDouble(long nativePtr, long columnKey, double value); - private native long nativeFindFirstTimestamp(long nativeTablePtr, long columnIndex, long dateTimeValue); + private native long nativeFindFirstTimestamp(long nativeTableRefPtr, long columnKey, long dateTimeValue); - public static native long nativeFindFirstString(long nativeTablePtr, long columnIndex, String value); + public static native long nativeFindFirstString(long nativeTableRefPtr, long columnKey, String value); - public static native long nativeFindFirstNull(long nativeTablePtr, long columnIndex); + public static native long nativeFindFirstNull(long nativeTableRefPtr, long columnKey); - private native String nativeGetName(long nativeTablePtr); + private native String nativeGetName(long nativeTableRefPtr); private native boolean nativeHasSameSchema(long thisTable, long otherTable); private static native long nativeGetFinalizerPtr(); + + private static native long nativeFreeze(long frozenSharedRealmPtr, long nativeTableRefPtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 2750ad689e..c1a87a7d6b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -107,140 +107,140 @@ public TableQuery not() { // Queries for integer values. - public TableQuery equalTo(long[] columnIndexes, long[] tablePtrs, long value) { - nativeEqual(nativePtr, columnIndexes, tablePtrs, value); + public TableQuery equalTo(long[] columnKeys, long[] tablePtrs, long value) { + nativeEqual(nativePtr, columnKeys, tablePtrs, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, long value) { - nativeNotEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery notEqualTo(long[] columnKey, long[] tablePtrs, long value) { + nativeNotEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThan(long[] columnIndex, long[] tablePtrs, long value) { - nativeGreater(nativePtr, columnIndex, tablePtrs, value); + public TableQuery greaterThan(long[] columnKey, long[] tablePtrs, long value) { + nativeGreater(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long[] columnIndex, long[] tablePtrs, long value) { - nativeGreaterEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery greaterThanOrEqual(long[] columnKey, long[] tablePtrs, long value) { + nativeGreaterEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThan(long[] columnIndex, long[] tablePtrs, long value) { - nativeLess(nativePtr, columnIndex, tablePtrs, value); + public TableQuery lessThan(long[] columnKey, long[] tablePtrs, long value) { + nativeLess(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long[] columnIndex, long[] tablePtrs, long value) { - nativeLessEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery lessThanOrEqual(long[] columnKey, long[] tablePtrs, long value) { + nativeLessEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery between(long[] columnIndex, long value1, long value2) { - nativeBetween(nativePtr, columnIndex, value1, value2); + public TableQuery between(long[] columnKey, long value1, long value2) { + nativeBetween(nativePtr, columnKey, value1, value2); queryValidated = false; return this; } // Queries for float values. - public TableQuery equalTo(long[] columnIndex, long[] tablePtrs, float value) { - nativeEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery equalTo(long[] columnKey, long[] tablePtrs, float value) { + nativeEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, float value) { - nativeNotEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery notEqualTo(long[] columnKey, long[] tablePtrs, float value) { + nativeNotEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThan(long[] columnIndex, long[] tablePtrs, float value) { - nativeGreater(nativePtr, columnIndex, tablePtrs, value); + public TableQuery greaterThan(long[] columnKey, long[] tablePtrs, float value) { + nativeGreater(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long[] columnIndex, long[] tablePtrs, float value) { - nativeGreaterEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery greaterThanOrEqual(long[] columnKey, long[] tablePtrs, float value) { + nativeGreaterEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThan(long[] columnIndex, long[] tablePtrs, float value) { - nativeLess(nativePtr, columnIndex, tablePtrs, value); + public TableQuery lessThan(long[] columnKey, long[] tablePtrs, float value) { + nativeLess(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long[] columnIndex, long[] tablePtrs, float value) { - nativeLessEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery lessThanOrEqual(long[] columnKey, long[] tablePtrs, float value) { + nativeLessEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery between(long[] columnIndex, float value1, float value2) { - nativeBetween(nativePtr, columnIndex, value1, value2); + public TableQuery between(long[] columnKey, float value1, float value2) { + nativeBetween(nativePtr, columnKey, value1, value2); queryValidated = false; return this; } // Queries for double values. - public TableQuery equalTo(long[] columnIndex, long[] tablePtrs, double value) { - nativeEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery equalTo(long[] columnKey, long[] tablePtrs, double value) { + nativeEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, double value) { - nativeNotEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery notEqualTo(long[] columnKey, long[] tablePtrs, double value) { + nativeNotEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThan(long[] columnIndex, long[] tablePtrs, double value) { - nativeGreater(nativePtr, columnIndex, tablePtrs, value); + public TableQuery greaterThan(long[] columnKey, long[] tablePtrs, double value) { + nativeGreater(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long[] columnIndex, long[] tablePtrs, double value) { - nativeGreaterEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery greaterThanOrEqual(long[] columnKey, long[] tablePtrs, double value) { + nativeGreaterEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThan(long[] columnIndex, long[] tablePtrs, double value) { - nativeLess(nativePtr, columnIndex, tablePtrs, value); + public TableQuery lessThan(long[] columnKey, long[] tablePtrs, double value) { + nativeLess(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long[] columnIndex, long[] tablePtrs, double value) { - nativeLessEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery lessThanOrEqual(long[] columnKey, long[] tablePtrs, double value) { + nativeLessEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } - public TableQuery between(long[] columnIndex, double value1, double value2) { - nativeBetween(nativePtr, columnIndex, value1, value2); + public TableQuery between(long[] columnKey, double value1, double value2) { + nativeBetween(nativePtr, columnKey, value1, value2); queryValidated = false; return this; } // Query for boolean values. - public TableQuery equalTo(long[] columnIndex, long[] tablePtrs, boolean value) { - nativeEqual(nativePtr, columnIndex, tablePtrs, value); + public TableQuery equalTo(long[] columnKey, long[] tablePtrs, boolean value) { + nativeEqual(nativePtr, columnKey, tablePtrs, value); queryValidated = false; return this; } @@ -249,89 +249,89 @@ public TableQuery equalTo(long[] columnIndex, long[] tablePtrs, boolean value) { private static final String DATE_NULL_ERROR_MESSAGE = "Date value in query criteria must not be null."; - public TableQuery equalTo(long[] columnIndex, long[] tablePtrs, @Nullable Date value) { + public TableQuery equalTo(long[] columnKey, long[] tablePtrs, @Nullable Date value) { if (value == null) { - nativeIsNull(nativePtr, columnIndex, tablePtrs); + nativeIsNull(nativePtr, columnKey, tablePtrs); } else { - nativeEqualTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); + nativeEqualTimestamp(nativePtr, columnKey, tablePtrs, value.getTime()); } queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, Date value) { + public TableQuery notEqualTo(long[] columnKey, long[] tablePtrs, Date value) { //noinspection ConstantConditions if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeNotEqualTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); + nativeNotEqualTimestamp(nativePtr, columnKey, tablePtrs, value.getTime()); queryValidated = false; return this; } - public TableQuery greaterThan(long[] columnIndex, long[] tablePtrs, Date value) { + public TableQuery greaterThan(long[] columnKey, long[] tablePtrs, Date value) { //noinspection ConstantConditions if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeGreaterTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); + nativeGreaterTimestamp(nativePtr, columnKey, tablePtrs, value.getTime()); queryValidated = false; return this; } - public TableQuery greaterThanOrEqual(long[] columnIndex, long[] tablePtrs, Date value) { + public TableQuery greaterThanOrEqual(long[] columnKey, long[] tablePtrs, Date value) { //noinspection ConstantConditions if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeGreaterEqualTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); + nativeGreaterEqualTimestamp(nativePtr, columnKey, tablePtrs, value.getTime()); queryValidated = false; return this; } - public TableQuery lessThan(long[] columnIndex, long[] tablePtrs, Date value) { + public TableQuery lessThan(long[] columnKey, long[] tablePtrs, Date value) { //noinspection ConstantConditions if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeLessTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); + nativeLessTimestamp(nativePtr, columnKey, tablePtrs, value.getTime()); queryValidated = false; return this; } - public TableQuery lessThanOrEqual(long[] columnIndex, long[] tablePtrs, Date value) { + public TableQuery lessThanOrEqual(long[] columnKey, long[] tablePtrs, Date value) { //noinspection ConstantConditions if (value == null) { throw new IllegalArgumentException(DATE_NULL_ERROR_MESSAGE); } - nativeLessEqualTimestamp(nativePtr, columnIndex, tablePtrs, value.getTime()); + nativeLessEqualTimestamp(nativePtr, columnKey, tablePtrs, value.getTime()); queryValidated = false; return this; } - public TableQuery between(long[] columnIndex, Date value1, Date value2) { + public TableQuery between(long[] columnKey, Date value1, Date value2) { //noinspection ConstantConditions if (value1 == null || value2 == null) { throw new IllegalArgumentException("Date values in query criteria must not be null."); // Different text } - nativeBetweenTimestamp(nativePtr, columnIndex, value1.getTime(), value2.getTime()); + nativeBetweenTimestamp(nativePtr, columnKey, value1.getTime(), value2.getTime()); queryValidated = false; return this; } // Queries for Binary values. - public TableQuery equalTo(long[] columnIndices, long[] tablePtrs, byte[] value) { - nativeEqual(nativePtr, columnIndices, tablePtrs, value); + public TableQuery equalTo(long[] columnKeys, long[] tablePtrs, byte[] value) { + nativeEqual(nativePtr, columnKeys, tablePtrs, value); queryValidated = false; return this; } - public TableQuery notEqualTo(long[] columnIndices, long[] tablePtrs, byte[] value) { - nativeNotEqual(nativePtr, columnIndices, tablePtrs, value); + public TableQuery notEqualTo(long[] columnKeys, long[] tablePtrs, byte[] value) { + nativeNotEqual(nativePtr, columnKeys, tablePtrs, value); queryValidated = false; return this; } // Equals - public TableQuery equalTo(long[] columnIndexes, long[] tablePtrs, @Nullable String value, Case caseSensitive) { - nativeEqual(nativePtr, columnIndexes, tablePtrs, value, caseSensitive.getValue()); + public TableQuery equalTo(long[] columnKeys, long[] tablePtrs, @Nullable String value, Case caseSensitive) { + nativeEqual(nativePtr, columnKeys, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery equalTo(long[] columnIndexes, long[] tablePtrs, String value) { - nativeEqual(nativePtr, columnIndexes, tablePtrs, value, true); + public TableQuery equalTo(long[] columnKeys, long[] tablePtrs, String value) { + nativeEqual(nativePtr, columnKeys, tablePtrs, value, true); queryValidated = false; return this; } @@ -349,80 +349,74 @@ public TableQuery notEqualTo(long[] columnIndex, long[] tablePtrs, @Nullable Str return this; } - public TableQuery beginsWith(long[] columnIndices, long[] tablePtrs, String value, Case caseSensitive) { - nativeBeginsWith(nativePtr, columnIndices, tablePtrs, value, caseSensitive.getValue()); + public TableQuery beginsWith(long[] columnKeys, long[] tablePtrs, String value, Case caseSensitive) { + nativeBeginsWith(nativePtr, columnKeys, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery beginsWith(long[] columnIndices, long[] tablePtrs, String value) { - nativeBeginsWith(nativePtr, columnIndices, tablePtrs, value, true); + public TableQuery beginsWith(long[] columnKeys, long[] tablePtrs, String value) { + nativeBeginsWith(nativePtr, columnKeys, tablePtrs, value, true); queryValidated = false; return this; } - public TableQuery endsWith(long[] columnIndices, long[] tablePtrs, String value, Case caseSensitive) { - nativeEndsWith(nativePtr, columnIndices, tablePtrs, value, caseSensitive.getValue()); + public TableQuery endsWith(long[] columnKeys, long[] tablePtrs, String value, Case caseSensitive) { + nativeEndsWith(nativePtr, columnKeys, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery endsWith(long[] columnIndices, long[] tablePtrs, String value) { - nativeEndsWith(nativePtr, columnIndices, tablePtrs, value, true); + public TableQuery endsWith(long[] columnKeys, long[] tablePtrs, String value) { + nativeEndsWith(nativePtr, columnKeys, tablePtrs, value, true); queryValidated = false; return this; } - public TableQuery like(long[] columnIndices, long[] tablePtrs, String value, Case caseSensitive) { - nativeLike(nativePtr, columnIndices, tablePtrs, value, caseSensitive.getValue()); + public TableQuery like(long[] columnKeys, long[] tablePtrs, String value, Case caseSensitive) { + nativeLike(nativePtr, columnKeys, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery like(long[] columnIndices, long[] tablePtrs, String value) { - nativeLike(nativePtr, columnIndices, tablePtrs, value, true); + public TableQuery like(long[] columnKeys, long[] tablePtrs, String value) { + nativeLike(nativePtr, columnKeys, tablePtrs, value, true); queryValidated = false; return this; } - public TableQuery contains(long[] columnIndices, long[] tablePtrs, String value, Case caseSensitive) { - nativeContains(nativePtr, columnIndices, tablePtrs, value, caseSensitive.getValue()); + public TableQuery contains(long[] columnKeys, long[] tablePtrs, String value, Case caseSensitive) { + nativeContains(nativePtr, columnKeys, tablePtrs, value, caseSensitive.getValue()); queryValidated = false; return this; } - public TableQuery contains(long[] columnIndices, long[] tablePtrs, String value) { - nativeContains(nativePtr, columnIndices, tablePtrs, value, true); + public TableQuery contains(long[] columnKeys, long[] tablePtrs, String value) { + nativeContains(nativePtr, columnKeys, tablePtrs, value, true); queryValidated = false; return this; } - public TableQuery isEmpty(long[] columnIndices, long[] tablePtrs) { - nativeIsEmpty(nativePtr, columnIndices, tablePtrs); + public TableQuery isEmpty(long[] columnKeys, long[] tablePtrs) { + nativeIsEmpty(nativePtr, columnKeys, tablePtrs); queryValidated = false; return this; } - public TableQuery isNotEmpty(long[] columnIndices, long[] tablePtrs) { - nativeIsNotEmpty(nativePtr, columnIndices, tablePtrs); + public TableQuery isNotEmpty(long[] columnKeys, long[] tablePtrs) { + nativeIsNotEmpty(nativePtr, columnKeys, tablePtrs); queryValidated = false; return this; } // Searching methods. - @Deprecated // Doesn't seem to be used - public long find(long fromTableRow) { - validateQuery(); - return nativeFind(nativePtr, fromTableRow); - } - /** * Returns the table row index for the first element matching the query. */ public long find() { validateQuery(); - return nativeFind(nativePtr, 0); + return nativeFind(nativePtr); } // @@ -431,162 +425,84 @@ public long find() { // Integer aggregation - public long sumInt(long columnIndex, long start, long end, long limit) { - validateQuery(); - return nativeSumInt(nativePtr, columnIndex, start, end, limit); - } - - public long sumInt(long columnIndex) { - validateQuery(); - return nativeSumInt(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); - } - - public Long maximumInt(long columnIndex, long start, long end, long limit) { - validateQuery(); - return nativeMaximumInt(nativePtr, columnIndex, start, end, limit); - } - - public Long maximumInt(long columnIndex) { - validateQuery(); - return nativeMaximumInt(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); - } - - public Long minimumInt(long columnIndex, long start, long end, long limit) { + public long sumInt(long columnKey) { validateQuery(); - return nativeMinimumInt(nativePtr, columnIndex, start, end, limit); + return nativeSumInt(nativePtr, columnKey); } - public Long minimumInt(long columnIndex) { + public Long maximumInt(long columnKey) { validateQuery(); - return nativeMinimumInt(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); + return nativeMaximumInt(nativePtr, columnKey); } - public double averageInt(long columnIndex, long start, long end, long limit) { + public Long minimumInt(long columnKey) { validateQuery(); - return nativeAverageInt(nativePtr, columnIndex, start, end, limit); + return nativeMinimumInt(nativePtr, columnKey); } - public double averageInt(long columnIndex) { + public double averageInt(long columnKey) { validateQuery(); - return nativeAverageInt(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); + return nativeAverageInt(nativePtr, columnKey); } // Float aggregation - public double sumFloat(long columnIndex, long start, long end, long limit) { - validateQuery(); - return nativeSumFloat(nativePtr, columnIndex, start, end, limit); - } - - public double sumFloat(long columnIndex) { - validateQuery(); - return nativeSumFloat(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); - } - - public Float maximumFloat(long columnIndex, long start, long end, long limit) { + public double sumFloat(long columnKey) { validateQuery(); - return nativeMaximumFloat(nativePtr, columnIndex, start, end, limit); + return nativeSumFloat(nativePtr, columnKey); } - public Float maximumFloat(long columnIndex) { + public Float maximumFloat(long columnKey) { validateQuery(); - return nativeMaximumFloat(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); + return nativeMaximumFloat(nativePtr, columnKey); } - public Float minimumFloat(long columnIndex, long start, long end, long limit) { + public Float minimumFloat(long columnKey) { validateQuery(); - return nativeMinimumFloat(nativePtr, columnIndex, start, end, limit); + return nativeMinimumFloat(nativePtr, columnKey); } - public Float minimumFloat(long columnIndex) { + public double averageFloat(long columnKey) { validateQuery(); - return nativeMinimumFloat(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); - } - - public double averageFloat(long columnIndex, long start, long end, long limit) { - validateQuery(); - return nativeAverageFloat(nativePtr, columnIndex, start, end, limit); - } - - public double averageFloat(long columnIndex) { - validateQuery(); - return nativeAverageFloat(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); + return nativeAverageFloat(nativePtr, columnKey); } // Double aggregation - public double sumDouble(long columnIndex, long start, long end, long limit) { - validateQuery(); - return nativeSumDouble(nativePtr, columnIndex, start, end, limit); - } - - public double sumDouble(long columnIndex) { - validateQuery(); - return nativeSumDouble(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); - } - - public Double maximumDouble(long columnIndex, long start, long end, long limit) { - validateQuery(); - return nativeMaximumDouble(nativePtr, columnIndex, start, end, limit); - } - - public Double maximumDouble(long columnIndex) { - validateQuery(); - return nativeMaximumDouble(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); - } - - public Double minimumDouble(long columnIndex, long start, long end, long limit) { + public double sumDouble(long columnKey) { validateQuery(); - return nativeMinimumDouble(nativePtr, columnIndex, start, end, limit); + return nativeSumDouble(nativePtr, columnKey); } - public Double minimumDouble(long columnIndex) { + public Double maximumDouble(long columnKey) { validateQuery(); - return nativeMinimumDouble(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); + return nativeMaximumDouble(nativePtr, columnKey); } - public double averageDouble(long columnIndex, long start, long end, long limit) { + public Double minimumDouble(long columnKey) { validateQuery(); - return nativeAverageDouble(nativePtr, columnIndex, start, end, limit); + return nativeMinimumDouble(nativePtr, columnKey); } - public double averageDouble(long columnIndex) { + public double averageDouble(long columnKey) { validateQuery(); - return nativeAverageDouble(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); + return nativeAverageDouble(nativePtr, columnKey); } // Date aggregation - public Date maximumDate(long columnIndex, long start, long end, long limit) { - validateQuery(); - Long result = nativeMaximumTimestamp(nativePtr, columnIndex, start, end, limit); - if (result != null) { - return new Date(result); - } - return null; - } - - public Date maximumDate(long columnIndex) { + public Date maximumDate(long columnKey) { validateQuery(); - Long result = nativeMaximumTimestamp(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); + Long result = nativeMaximumTimestamp(nativePtr, columnKey); if (result != null) { return new Date(result); } return null; } - public Date minimumDate(long columnIndex, long start, long end, long limit) { - validateQuery(); - Long result = nativeMinimumTimestamp(nativePtr, columnIndex, start, end, limit); - if (result != null) { - return new Date(result * 1000); - } - return null; - } - - public Date minimumDate(long columnIndex) { + public Date minimumDate(long columnKey) { validateQuery(); - Long result = nativeMinimumTimestamp(nativePtr, columnIndex, 0, Table.INFINITE, Table.INFINITE); + Long result = nativeMinimumTimestamp(nativePtr, columnKey); if (result != null) { return new Date(result); } @@ -594,26 +510,20 @@ public Date minimumDate(long columnIndex) { } // isNull and isNotNull - public TableQuery isNull(long[] columnIndices, long[] tablePtrs) { - nativeIsNull(nativePtr, columnIndices, tablePtrs); + public TableQuery isNull(long[] columnKeys, long[] tablePtrs) { + nativeIsNull(nativePtr, columnKeys, tablePtrs); queryValidated = false; return this; } - public TableQuery isNotNull(long[] columnIndices, long[] tablePtrs) { - nativeIsNotNull(nativePtr, columnIndices, tablePtrs); + public TableQuery isNotNull(long[] columnKeys, long[] tablePtrs) { + nativeIsNotNull(nativePtr, columnKeys, tablePtrs); queryValidated = false; return this; } // Count - // TODO: Rename all start, end parameter names to firstRow, lastRow - public long count(long start, long end, long limit) { - validateQuery(); - return nativeCount(nativePtr, start, end, limit); - } - /** * Returns only the number of matching objects. * This method is very fast compared to evaluating a query completely, but it does not @@ -623,7 +533,7 @@ public long count(long start, long end, long limit) { @Deprecated public long count() { validateQuery(); - return nativeCount(nativePtr, 0, Table.INFINITE, Table.INFINITE); + return nativeCount(nativePtr); } public long remove() { @@ -712,67 +622,65 @@ public void alwaysFalse() { private native void nativeBetweenTimestamp(long nativeQueryPtr, long[] columnIndex, long value1, long value2); - private native void nativeEqual(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, byte[] value); + private native void nativeEqual(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, byte[] value); - private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, byte[] value); + private native void nativeNotEqual(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, byte[] value); - private native void nativeEqual(long nativeQueryPtr, long[] columnIndexes, long[] tablePtrs, @Nullable String value, boolean caseSensitive); + private native void nativeEqual(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, @Nullable String value, boolean caseSensitive); private native void nativeNotEqual(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, @Nullable String value, boolean caseSensitive); - private native void nativeBeginsWith(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, String value, boolean caseSensitive); + private native void nativeBeginsWith(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeEndsWith(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, String value, boolean caseSensitive); + private native void nativeEndsWith(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeLike(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, String value, boolean caseSensitive); + private native void nativeLike(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeContains(long nativeQueryPtr, long[] columnIndices, long[] tablePtrs, String value, boolean caseSensitive); + private native void nativeContains(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, String value, boolean caseSensitive); - private native void nativeIsEmpty(long nativePtr, long[] columnIndices, long[] tablePtrs); + private native void nativeIsEmpty(long nativePtr, long[] columnKeys, long[] tablePtrs); - private native void nativeIsNotEmpty(long nativePtr, long[] columnIndices, long[] tablePtrs); + private native void nativeIsNotEmpty(long nativePtr, long[] columnKeys, long[] tablePtrs); private native void nativeAlwaysTrue(long nativeQueryPtr); private native void nativeAlwaysFalse(long nativeQueryPtr); - private native long nativeFind(long nativeQueryPtr, long fromTableRow); - - private native long nativeFindAll(long nativeQueryPtr, long start, long end, long limit); + private native long nativeFind(long nativeQueryPtr); - private native long nativeSumInt(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native long nativeSumInt(long nativeQueryPtr, long columnKey); - private native Long nativeMaximumInt(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Long nativeMaximumInt(long nativeQueryPtr, long columnKey); - private native Long nativeMinimumInt(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Long nativeMinimumInt(long nativeQueryPtr, long columnKey); - private native double nativeAverageInt(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeAverageInt(long nativeQueryPtr, long columnKey); - private native double nativeSumFloat(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeSumFloat(long nativeQueryPtr, long columnKey); - private native Float nativeMaximumFloat(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Float nativeMaximumFloat(long nativeQueryPtr, long columnKey); - private native Float nativeMinimumFloat(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Float nativeMinimumFloat(long nativeQueryPtr, long columnKey); - private native double nativeAverageFloat(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeAverageFloat(long nativeQueryPtr, long columnKey); - private native double nativeSumDouble(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeSumDouble(long nativeQueryPtr, long columnKey); - private native Double nativeMaximumDouble(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Double nativeMaximumDouble(long nativeQueryPtr, long columnKey); - private native Double nativeMinimumDouble(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Double nativeMinimumDouble(long nativeQueryPtr, long columnKey); - private native double nativeAverageDouble(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native double nativeAverageDouble(long nativeQueryPtr, long columnKey); - private native Long nativeMaximumTimestamp(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Long nativeMaximumTimestamp(long nativeQueryPtr, long columnKey); - private native Long nativeMinimumTimestamp(long nativeQueryPtr, long columnIndex, long start, long end, long limit); + private native Long nativeMinimumTimestamp(long nativeQueryPtr, long columnKey); - private native void nativeIsNull(long nativePtr, long[] columnIndices, long[] tablePtrs); + private native void nativeIsNull(long nativePtr, long[] columnKeys, long[] tablePtrs); private native void nativeIsNotNull(long nativePtr, long[] columnIndice, long[] tablePtr); - private native long nativeCount(long nativeQueryPtr, long start, long end, long limit); + private native long nativeCount(long nativeQueryPtr); private native long nativeRemove(long nativeQueryPtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index c35ef9a3bc..dad433f875 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -35,8 +35,8 @@ public class UncheckedRow implements NativeObject, Row { private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - private final NativeContext context; // This is only kept because for now it's needed by the constructor of LinkView - private final Table parent; + protected final NativeContext context; // This is only kept because for now it's needed by the constructor of LinkView + protected final Table parent; private final long nativePtr; public UncheckedRow(NativeContext context, Table parent, long nativePtr) { @@ -66,15 +66,15 @@ public long getNativeFinalizerPtr() { } /** - * Gets the row object associated to an index in a Table. + * Gets the row object associated with a row key in a Table. * * @param context the Realm context. * @param table the Table that holds the row. - * @param index the index of the row. - * @return an instance of Row for the table and index specified. + * @param rowKey Row key. + * @return an instance of Row for the table and row key specified. */ - static UncheckedRow getByRowIndex(NativeContext context, Table table, long index) { - long nativeRowPointer = table.nativeGetRowPtr(table.getNativePtr(), index); + static UncheckedRow getByRowKey(NativeContext context, Table table, long rowKey) { + long nativeRowPointer = table.nativeGetRowPtr(table.getNativePtr(), rowKey); return new UncheckedRow(context, table, nativeRowPointer); } @@ -96,23 +96,22 @@ public long getColumnCount() { } @Override - public String getColumnName(long columnIndex) { - return nativeGetColumnName(nativePtr, columnIndex); + public String[] getColumnNames() { + return nativeGetColumnNames(nativePtr); } - @Override - public long getColumnIndex(String columnName) { + public long getColumnKey(String columnName) { //noinspection ConstantConditions if (columnName == null) { throw new IllegalArgumentException("Column name can not be null."); } - return nativeGetColumnIndex(nativePtr, columnName); + return nativeGetColumnKey(nativePtr, columnName); } @Override - public RealmFieldType getColumnType(long columnIndex) { - return RealmFieldType.fromNativeValue(nativeGetColumnType(nativePtr, columnIndex)); + public RealmFieldType getColumnType(long columnKey) { + return RealmFieldType.fromNativeValue(nativeGetColumnType(nativePtr, columnKey)); } // Getters @@ -123,150 +122,150 @@ public Table getTable() { } @Override - public long getIndex() { - return nativeGetIndex(nativePtr); + public long getObjectKey() { + return nativeGetObjectKey(nativePtr); } @Override - public long getLong(long columnIndex) { - return nativeGetLong(nativePtr, columnIndex); + public long getLong(long columnKey) { + return nativeGetLong(nativePtr, columnKey); } @Override - public boolean getBoolean(long columnIndex) { - return nativeGetBoolean(nativePtr, columnIndex); + public boolean getBoolean(long columnKey) { + return nativeGetBoolean(nativePtr, columnKey); } @Override - public float getFloat(long columnIndex) { - return nativeGetFloat(nativePtr, columnIndex); + public float getFloat(long columnKey) { + return nativeGetFloat(nativePtr, columnKey); } @Override - public double getDouble(long columnIndex) { - return nativeGetDouble(nativePtr, columnIndex); + public double getDouble(long columnKey) { + return nativeGetDouble(nativePtr, columnKey); } @Override - public Date getDate(long columnIndex) { - return new Date(nativeGetTimestamp(nativePtr, columnIndex)); + public Date getDate(long columnKey) { + return new Date(nativeGetTimestamp(nativePtr, columnKey)); } @Override - public String getString(long columnIndex) { - return nativeGetString(nativePtr, columnIndex); + public String getString(long columnKey) { + return nativeGetString(nativePtr, columnKey); } @Override - public byte[] getBinaryByteArray(long columnIndex) { - return nativeGetByteArray(nativePtr, columnIndex); + public byte[] getBinaryByteArray(long columnKey) { + return nativeGetByteArray(nativePtr, columnKey); } @Override - public long getLink(long columnIndex) { - return nativeGetLink(nativePtr, columnIndex); + public long getLink(long columnKey) { + return nativeGetLink(nativePtr, columnKey); } @Override - public boolean isNullLink(long columnIndex) { - return nativeIsNullLink(nativePtr, columnIndex); + public boolean isNullLink(long columnKey) { + return nativeIsNullLink(nativePtr, columnKey); } @Override - public OsList getModelList(long columnIndex) { - return new OsList(this, columnIndex); + public OsList getModelList(long columnKey) { + return new OsList(this, columnKey); } @Override - public OsList getValueList(long columnIndex, RealmFieldType fieldType) { - return new OsList(this, columnIndex); + public OsList getValueList(long columnKey, RealmFieldType fieldType) { + return new OsList(this, columnKey); } // Setters @Override - public void setLong(long columnIndex, long value) { + public void setLong(long columnKey, long value) { parent.checkImmutable(); - nativeSetLong(nativePtr, columnIndex, value); + nativeSetLong(nativePtr, columnKey, value); } @Override - public void setBoolean(long columnIndex, boolean value) { + public void setBoolean(long columnKey, boolean value) { parent.checkImmutable(); - nativeSetBoolean(nativePtr, columnIndex, value); + nativeSetBoolean(nativePtr, columnKey, value); } @Override - public void setFloat(long columnIndex, float value) { + public void setFloat(long columnKey, float value) { parent.checkImmutable(); - nativeSetFloat(nativePtr, columnIndex, value); + nativeSetFloat(nativePtr, columnKey, value); } @Override - public void setDouble(long columnIndex, double value) { + public void setDouble(long columnKey, double value) { parent.checkImmutable(); - nativeSetDouble(nativePtr, columnIndex, value); + nativeSetDouble(nativePtr, columnKey, value); } @Override - public void setDate(long columnIndex, Date date) { + public void setDate(long columnKey, Date date) { parent.checkImmutable(); //noinspection ConstantConditions if (date == null) { throw new IllegalArgumentException("Null Date is not allowed."); } long timestamp = date.getTime(); - nativeSetTimestamp(nativePtr, columnIndex, timestamp); + nativeSetTimestamp(nativePtr, columnKey, timestamp); } /** * Sets a string value to a row pointer. * - * @param columnIndex 0 based index value of the cell column. + * @param columnKey column key. * @param value the value to to a row */ @Override - public void setString(long columnIndex, @Nullable String value) { + public void setString(long columnKey, @Nullable String value) { parent.checkImmutable(); if (value == null) { - nativeSetNull(nativePtr, columnIndex); + nativeSetNull(nativePtr, columnKey); } else { - nativeSetString(nativePtr, columnIndex, value); + nativeSetString(nativePtr, columnKey, value); } } @Override - public void setBinaryByteArray(long columnIndex, @Nullable byte[] data) { + public void setBinaryByteArray(long columnKey, @Nullable byte[] data) { parent.checkImmutable(); - nativeSetByteArray(nativePtr, columnIndex, data); + nativeSetByteArray(nativePtr, columnKey, data); } @Override - public void setLink(long columnIndex, long value) { + public void setLink(long columnKey, long value) { parent.checkImmutable(); - nativeSetLink(nativePtr, columnIndex, value); + nativeSetLink(nativePtr, columnKey, value); } @Override - public void nullifyLink(long columnIndex) { + public void nullifyLink(long columnKey) { parent.checkImmutable(); - nativeNullifyLink(nativePtr, columnIndex); + nativeNullifyLink(nativePtr, columnKey); } @Override - public boolean isNull(long columnIndex) { - return nativeIsNull(nativePtr, columnIndex); + public boolean isNull(long columnKey) { + return nativeIsNull(nativePtr, columnKey); } /** * Sets null to a row pointer. * - * @param columnIndex 0 based index value of the cell column. + * @param columnKey column key. */ @Override - public void setNull(long columnIndex) { + public void setNull(long columnKey) { parent.checkImmutable(); - nativeSetNull(nativePtr, columnIndex); + nativeSetNull(nativePtr, columnKey); } /** @@ -279,13 +278,13 @@ public CheckedRow convertToChecked() { } @Override - public boolean isAttached() { - return nativePtr != 0 && nativeIsAttached(nativePtr); + public boolean isValid() { + return nativePtr != 0 && nativeIsValid(nativePtr); } @Override public void checkIfAttached() { - if (!isAttached()) { + if (!isValid()) { throw new IllegalStateException("Object is no longer managed by Realm. Has it been deleted?"); } } @@ -295,59 +294,74 @@ public boolean hasColumn(String fieldName) { return nativeHasColumn(nativePtr, fieldName); } + @Override + public Row freeze(OsSharedRealm frozenRealm) { + if (!isValid()) { + return InvalidRow.INSTANCE; + } + return new UncheckedRow(context, parent.freeze(frozenRealm), nativeFreeze(nativePtr, frozenRealm.getNativePtr())); + } + + @Override + public boolean isLoaded() { + return true; + } + protected native long nativeGetColumnCount(long nativeTablePtr); - protected native String nativeGetColumnName(long nativeTablePtr, long columnIndex); + protected native long nativeGetColumnKey(long nativeTablePtr, String columnName); - protected native long nativeGetColumnIndex(long nativeTablePtr, String columnName); + protected native String[] nativeGetColumnNames(long nativeTablePtr); - protected native int nativeGetColumnType(long nativeTablePtr, long columnIndex); + protected native int nativeGetColumnType(long nativeTablePtr, long columnKey); - protected native long nativeGetIndex(long nativeRowPtr); + protected native long nativeGetObjectKey(long nativeRowPtr); - protected native long nativeGetLong(long nativeRowPtr, long columnIndex); + protected native long nativeGetLong(long nativeRowPtr, long columnKey); - protected native boolean nativeGetBoolean(long nativeRowPtr, long columnIndex); + protected native boolean nativeGetBoolean(long nativeRowPtr, long columnKey); - protected native float nativeGetFloat(long nativeRowPtr, long columnIndex); + protected native float nativeGetFloat(long nativeRowPtr, long columnKey); - protected native double nativeGetDouble(long nativeRowPtr, long columnIndex); + protected native double nativeGetDouble(long nativeRowPtr, long columnKey); - protected native long nativeGetTimestamp(long nativeRowPtr, long columnIndex); + protected native long nativeGetTimestamp(long nativeRowPtr, long columnKey); - protected native String nativeGetString(long nativePtr, long columnIndex); + protected native String nativeGetString(long nativePtr, long columnKey); - protected native boolean nativeIsNullLink(long nativeRowPtr, long columnIndex); + protected native boolean nativeIsNullLink(long nativeRowPtr, long columnKey); - protected native byte[] nativeGetByteArray(long nativePtr, long columnIndex); + protected native byte[] nativeGetByteArray(long nativePtr, long columnKey); - protected native void nativeSetLong(long nativeRowPtr, long columnIndex, long value); + protected native void nativeSetLong(long nativeRowPtr, long columnKey, long value); - protected native void nativeSetBoolean(long nativeRowPtr, long columnIndex, boolean value); + protected native void nativeSetBoolean(long nativeRowPtr, long columnKey, boolean value); - protected native void nativeSetFloat(long nativeRowPtr, long columnIndex, float value); + protected native void nativeSetFloat(long nativeRowPtr, long columnKey, float value); - protected native long nativeGetLink(long nativeRowPtr, long columnIndex); + protected native long nativeGetLink(long nativeRowPtr, long columnKey); - protected native void nativeSetDouble(long nativeRowPtr, long columnIndex, double value); + protected native void nativeSetDouble(long nativeRowPtr, long columnKey, double value); - protected native void nativeSetTimestamp(long nativeRowPtr, long columnIndex, long dateTimeValue); + protected native void nativeSetTimestamp(long nativeRowPtr, long columnKey, long dateTimeValue); - protected native void nativeSetString(long nativeRowPtr, long columnIndex, String value); + protected native void nativeSetString(long nativeRowPtr, long columnKey, String value); - protected native void nativeSetByteArray(long nativePtr, long columnIndex, @Nullable byte[] data); + protected native void nativeSetByteArray(long nativePtr, long columnKey, @Nullable byte[] data); - protected native void nativeSetLink(long nativeRowPtr, long columnIndex, long value); + protected native void nativeSetLink(long nativeRowPtr, long columnKey, long value); - protected native void nativeNullifyLink(long nativeRowPtr, long columnIndex); + protected native void nativeNullifyLink(long nativeRowPtr, long columnKey); - protected native boolean nativeIsAttached(long nativeRowPtr); + protected native boolean nativeIsValid(long nativeRowPtr); protected native boolean nativeHasColumn(long nativeRowPtr, String columnName); - protected native boolean nativeIsNull(long nativeRowPtr, long columnIndex); + protected native boolean nativeIsNull(long nativeRowPtr, long columnKey); + + protected native void nativeSetNull(long nativeRowPtr, long columnKey); - protected native void nativeSetNull(long nativeRowPtr, long columnIndex); + protected native long nativeFreeze(long nativeRowPtr, long frozenRealmNativePtr); private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/IncludeDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/core/IncludeDescriptor.java index 8c97f20e33..3957a17c04 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/core/IncludeDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/core/IncludeDescriptor.java @@ -39,11 +39,11 @@ public static IncludeDescriptor createInstance(FieldDescriptor.SchemaProxy schem includePath, supportedIntermediateColumnTypes, supportedFinalColumnType); - return new IncludeDescriptor(table, fieldDescriptor.getColumnIndices(), fieldDescriptor.getNativeTablePointers()); + return new IncludeDescriptor(table, fieldDescriptor.getColumnKeys(), fieldDescriptor.getNativeTablePointers()); } - private IncludeDescriptor(Table table, long[] columnIndices, long[] nativeTablePointers) { - nativePtr = nativeCreate(table.getNativePtr(), columnIndices, nativeTablePointers); + private IncludeDescriptor(Table table, long[] columnKeys, long[] nativeTablePointers) { + nativePtr = nativeCreate(table.getNativePtr(), columnKeys, nativeTablePointers); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java index e3e8b597d0..65ee0467ab 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java @@ -90,16 +90,16 @@ private static QueryDescriptor getInstance( throw new IllegalArgumentException("You must provide at least one field name."); } - long[][] columnIndices = new long[fieldDescriptions.length][]; + long[][] columnKeys = new long[fieldDescriptions.length][]; // Force aggressive parsing of the FieldDescriptors, so that only valid QueryDescriptor objects are created. for (int i = 0; i < fieldDescriptions.length; i++) { FieldDescriptor descriptor = FieldDescriptor.createFieldDescriptor(proxy, table, fieldDescriptions[i], legalInternalTypes, null); checkFieldType(descriptor, legalTerminalTypes, message, fieldDescriptions[i]); - columnIndices[i] = descriptor.getColumnIndices(); + columnKeys[i] = descriptor.getColumnKeys(); } - return new QueryDescriptor(table, columnIndices, sortOrders); + return new QueryDescriptor(table, columnKeys, sortOrders); } // Internal use only. For JNI testing. @@ -118,12 +118,12 @@ private static void checkFieldType(FieldDescriptor descriptor, Set fields) { final int nFields = fields.size(); - long[] columnIndices = new long[nFields]; + long[] columnKeys = new long[nFields]; long[] tableNativePointers = new long[nFields]; String currentClassName = className; @@ -86,12 +86,12 @@ protected void compileFieldDescription(List fields) { verifyInternalColumnType(currentClassName, currentColumnName, currentColumnType); currentClassName = details.linkedClassName; } - columnIndices[i] = details.columnIndex; + columnKeys[i] = details.columnKey; tableNativePointers[i] = (currentColumnType != RealmFieldType.LINKING_OBJECTS) ? NativeObject.NULLPTR : schema.getNativeTablePtr(details.linkedClassName); } - setCompilationResults(currentClassName, currentColumnName, currentColumnType, columnIndices, tableNativePointers); + setCompilationResults(currentClassName, currentColumnName, currentColumnType, columnKeys, tableNativePointers); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java index 70997e36a5..5d159d29da 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/DynamicFieldDescriptor.java @@ -49,7 +49,7 @@ class DynamicFieldDescriptor extends FieldDescriptor { @Override protected void compileFieldDescription(List fields) { final int nFields = fields.size(); - long[] columnIndices = new long[nFields]; + long[] columnKeys = new long[nFields]; Table currentTable = table; String currentClassName = null; @@ -64,21 +64,21 @@ protected void compileFieldDescription(List fields) { currentClassName = currentTable.getClassName(); - final long columnIndex = currentTable.getColumnIndex(currentColumnName); - if (columnIndex < 0) { + final long columnKey = currentTable.getColumnKey(currentColumnName); + if (columnKey < 0) { throw new IllegalArgumentException( String.format(Locale.US, "Invalid query: field '%s' not found in table '%s'.", currentColumnName, currentClassName)); } - currentColumnType = currentTable.getColumnType(columnIndex); + currentColumnType = currentTable.getColumnType(columnKey); if (i < nFields - 1) { verifyInternalColumnType(currentClassName, currentColumnName, currentColumnType); - currentTable = currentTable.getLinkTarget(columnIndex); + currentTable = currentTable.getLinkTarget(columnKey); } - columnIndices[i] = columnIndex; + columnKeys[i] = columnKey; } - setCompilationResults(currentClassName, currentColumnName, currentColumnType, columnIndices, new long[nFields]); + setCompilationResults(currentClassName, currentColumnName, currentColumnType, columnKeys, new long[nFields]); } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java index 81cb17d36d..d8bb57c90e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/fields/FieldDescriptor.java @@ -15,7 +15,6 @@ */ package io.realm.internal.fields; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -145,7 +144,7 @@ public static FieldDescriptor createFieldDescriptor( private String finalColumnName; private RealmFieldType finalColumnType; - private long[] columnIndices; + private long[] columnKeys; private long[] nativeTablePointers; /** @@ -169,7 +168,7 @@ protected FieldDescriptor( /** * The number of columnNames in the field description. * The returned number is the size of the array returned by - * {@code getColumnIndices} and {@code getNativeTablePointers} + * {@code getColumnKeys} and {@code getNativeTablePointers} * * @return the number of fields. */ @@ -186,9 +185,9 @@ public final int length() { * * @return an array of column indices. */ - public final long[] getColumnIndices() { + public final long[] getColumnKeys() { compileIfNecessary(); - return Arrays.copyOf(columnIndices, columnIndices.length); + return Arrays.copyOf(columnKeys, columnKeys.length); } /** @@ -249,21 +248,21 @@ protected final void verifyInternalColumnType(String tableName, String columnNam * @param finalClassName the name of the final table in the field description. * @param finalColumnName the name of the final column in the field description. * @param finalColumnType the type of the final column in the field description: MAY NOT BE {@code null}! - * @param columnIndices the array of columnIndices. + * @param columnKeys the array of column keys. * @param nativeTablePointers the array of table pointers */ protected final void setCompilationResults( String finalClassName, String finalColumnName, RealmFieldType finalColumnType, - long[] columnIndices, + long[] columnKeys, long[] nativeTablePointers) { if ((validFinalColumnTypes != null) && (validFinalColumnTypes.size() > 0)) { verifyColumnType(finalClassName, finalColumnName, finalColumnType, validFinalColumnTypes); } this.finalColumnName = finalColumnName; this.finalColumnType = finalColumnType; - this.columnIndices = columnIndices; + this.columnKeys = columnKeys; this.nativeTablePointers = nativeTablePointers; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java index 069ad20d85..9256c3814a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java @@ -160,12 +160,13 @@ public void handleItem(long listPtr, MutableRealmInteger item) { // If true, fields will not be updated if the same value would be written to it. private final boolean ignoreFieldsWithSameValue; - public OsObjectBuilder(Table table, long maxColumnIndex, Set flags) { + public OsObjectBuilder(Table table, Set flags) { OsSharedRealm sharedRealm = table.getSharedRealm(); this.sharedRealmPtr = sharedRealm.getNativePtr(); this.table = table; + this.table.getColumnNames(); this.tablePtr = table.getNativePtr(); - this.builderPtr = nativeCreateBuilder(maxColumnIndex + 1); + this.builderPtr = nativeCreateBuilder(); this.context = sharedRealm.context; this.ignoreFieldsWithSameValue = flags.contains(ImportFlag.CHECK_SAME_VALUES_BEFORE_SET); } @@ -405,7 +406,7 @@ private interface ItemCallback { void handleItem(long listPtr, T item); } - private static native long nativeCreateBuilder(long size); + private static native long nativeCreateBuilder(); private static native void nativeDestroyBuilder(long builderPtr); private static native long nativeCreateOrUpdate(long sharedRealmPtr, long tablePtr, diff --git a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java index 8b72dd0c12..d856351336 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java @@ -16,6 +16,8 @@ package io.realm.rx; +import android.os.Looper; + import java.util.IdentityHashMap; import java.util.Map; @@ -26,7 +28,9 @@ import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; +import io.reactivex.Scheduler; import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposables; import io.realm.DynamicRealm; import io.realm.DynamicRealmObject; @@ -54,6 +58,8 @@ */ public class RealmObservableFactory implements RxObservableFactory { + private final boolean returnFrozenObjects; + // Maps for storing strong references to Realm classes while they are subscribed to. // This is needed if users create Observables without manually maintaining a reference to them. // In that case RealmObjects/RealmResults/RealmLists might be GC'ed too early. @@ -80,7 +86,11 @@ protected StrongReferenceCounter initialValue() { @Override public Flowable from(Realm realm) { + if (realm.isFrozen()) { + return Flowable.just(realm); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Flowable.create(new FlowableOnSubscribe () { @Override public void subscribe(final FlowableEmitter emitter) throws Exception { @@ -90,7 +100,7 @@ public void subscribe(final FlowableEmitter emitter) throws Exception { @Override public void onChange(Realm realm) { if (!emitter.isCancelled()) { - emitter.onNext(realm); + emitter.onNext(returnFrozenObjects ? realm.freeze() : realm); } } }; @@ -100,20 +110,36 @@ public void onChange(Realm realm) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - observableRealm.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + observableRealm.removeChangeListener(listener); + observableRealm.close(); + } } })); // Emit current value immediately - emitter.onNext(observableRealm); + emitter.onNext(returnFrozenObjects ? observableRealm.freeze() : observableRealm); } - }, BACK_PRESSURE_STRATEGY); + }, BACK_PRESSURE_STRATEGY).subscribeOn(scheduler).unsubscribeOn(scheduler); + } + + /** + * Constructs the factory for creating Realm observables for RxJava. + * + * @param emitFrozenObjects {@code true} if all objects should be frozen before being returned + * to the user. {@code false} if they should be live objects. + */ + public RealmObservableFactory(boolean emitFrozenObjects) { + this.returnFrozenObjects = emitFrozenObjects; } @Override public Flowable from(DynamicRealm realm) { + if (realm.isFrozen()) { + return Flowable.just(realm); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Flowable.create(new FlowableOnSubscribe() { @Override public void subscribe(final FlowableEmitter emitter) throws Exception { @@ -123,7 +149,7 @@ public void subscribe(final FlowableEmitter emitter) throws Except @Override public void onChange(DynamicRealm realm) { if (!emitter.isCancelled()) { - emitter.onNext(realm); + emitter.onNext(returnFrozenObjects ? realm.freeze() : realm); } } }; @@ -133,23 +159,32 @@ public void onChange(DynamicRealm realm) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - observableRealm.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + observableRealm.removeChangeListener(listener); + observableRealm.close(); + } } })); // Emit current value immediately - emitter.onNext(observableRealm); + emitter.onNext(returnFrozenObjects ? observableRealm.freeze() : observableRealm); } - }, BACK_PRESSURE_STRATEGY); + }, BACK_PRESSURE_STRATEGY).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Flowable> from(final Realm realm, final RealmResults results) { + if (realm.isFrozen()) { + return Flowable.just(results); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Flowable.create(new FlowableOnSubscribe>() { @Override - public void subscribe(final FlowableEmitter> emitter) throws Exception { + public void subscribe(final FlowableEmitter> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!results.isValid()) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); @@ -158,7 +193,7 @@ public void subscribe(final FlowableEmitter> emitter) throws Exc @Override public void onChange(RealmResults results) { if (!emitter.isCancelled()) { - emitter.onNext(results); + emitter.onNext(returnFrozenObjects ? results.freeze() : results); } } }; @@ -168,25 +203,42 @@ public void onChange(RealmResults results) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - results.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + results.removeChangeListener(listener); + observableRealm.close(); + } resultsRefs.get().releaseReference(results); } })); // Emit current value immediately - emitter.onNext(results); + emitter.onNext(returnFrozenObjects ? results.freeze() : results); } - }, BACK_PRESSURE_STRATEGY); + }, BACK_PRESSURE_STRATEGY).subscribeOn(scheduler).unsubscribeOn(scheduler); + } + + private Scheduler getScheduler() { + Looper looper = Looper.myLooper(); + if (looper == null) { + throw new IllegalStateException("No looper found"); + } + return AndroidSchedulers.from(looper); } @Override public Observable>> changesetsFrom(Realm realm, final RealmResults results) { + if (realm.isFrozen()) { + return Observable.just(new CollectionChange>(results, null)); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Observable.create(new ObservableOnSubscribe>>() { @Override - public void subscribe(final ObservableEmitter>> emitter) throws Exception { + public void subscribe(final ObservableEmitter>> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!results.isValid()) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); @@ -195,7 +247,7 @@ public void subscribe(final ObservableEmitter>> @Override public void onChange(RealmResults e, OrderedCollectionChangeSet changeSet) { if (!emitter.isDisposed()) { - emitter.onNext(new CollectionChange>(results, changeSet)); + emitter.onNext(new CollectionChange>(returnFrozenObjects ? results.freeze() : results, changeSet)); } } }; @@ -205,24 +257,33 @@ public void onChange(RealmResults e, OrderedCollectionChangeSet changeSet) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - results.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + results.removeChangeListener(listener); + observableRealm.close(); + } resultsRefs.get().releaseReference(results); } })); // Emit current value immediately - emitter.onNext(new CollectionChange<>(results, null)); + emitter.onNext(new CollectionChange<>(returnFrozenObjects ? results.freeze() : results, null)); } - }); + }).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Flowable> from(DynamicRealm realm, final RealmResults results) { + if (realm.isFrozen()) { + return Flowable.just(results); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Flowable.create(new FlowableOnSubscribe>() { @Override - public void subscribe(final FlowableEmitter> emitter) throws Exception { + public void subscribe(final FlowableEmitter> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!results.isValid()) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); @@ -231,7 +292,7 @@ public void subscribe(final FlowableEmitter> emitter) throws Exc @Override public void onChange(RealmResults results) { if (!emitter.isCancelled()) { - emitter.onNext(results); + emitter.onNext(returnFrozenObjects ? results.freeze() : results); } } }; @@ -241,25 +302,34 @@ public void onChange(RealmResults results) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - results.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + results.removeChangeListener(listener); + observableRealm.close(); + } resultsRefs.get().releaseReference(results); } })); // Emit current value immediately - emitter.onNext(results); + emitter.onNext(returnFrozenObjects ? results.freeze() : results); } - }, BACK_PRESSURE_STRATEGY); + }, BACK_PRESSURE_STRATEGY).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Observable>> changesetsFrom(DynamicRealm realm, final RealmResults results) { + if (realm.isFrozen()) { + return Observable.just(new CollectionChange>(results, null)); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Observable.create(new ObservableOnSubscribe>>() { @Override - public void subscribe(final ObservableEmitter>> emitter) throws Exception { + public void subscribe(final ObservableEmitter>> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!results.isValid()) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); @@ -268,7 +338,7 @@ public void subscribe(final ObservableEmitter>> @Override public void onChange(RealmResults results, OrderedCollectionChangeSet changeSet) { if (!emitter.isDisposed()) { - emitter.onNext(new CollectionChange<>(results, changeSet)); + emitter.onNext(new CollectionChange<>(returnFrozenObjects ? results.freeze() : results, changeSet)); } } }; @@ -278,33 +348,42 @@ public void onChange(RealmResults results, OrderedCollectionChangeSet changeS emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - results.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + results.removeChangeListener(listener); + observableRealm.close(); + } resultsRefs.get().releaseReference(results); } })); // Emit current value immediately - emitter.onNext(new CollectionChange<>(results, null)); + emitter.onNext(new CollectionChange<>(returnFrozenObjects ? results.freeze() : results, null)); } - }); + }).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Flowable> from(Realm realm, final RealmList list) { + if (realm.isFrozen()) { + return Flowable.just(list); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Flowable.create(new FlowableOnSubscribe>() { @Override - public void subscribe(final FlowableEmitter> emitter) throws Exception { + public void subscribe(final FlowableEmitter> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!list.isValid()) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); listRefs.get().acquireReference(list); final RealmChangeListener> listener = new RealmChangeListener>() { @Override - public void onChange(RealmList results) { + public void onChange(RealmList list) { if (!emitter.isCancelled()) { - emitter.onNext(list); + emitter.onNext(returnFrozenObjects ? list.freeze() : list); } } }; @@ -314,34 +393,43 @@ public void onChange(RealmList results) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - list.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + list.removeChangeListener(listener); + observableRealm.close(); + } listRefs.get().releaseReference(list); } })); // Emit current value immediately - emitter.onNext(list); + emitter.onNext(returnFrozenObjects ? list.freeze() : list); } - }, BACK_PRESSURE_STRATEGY); + }, BACK_PRESSURE_STRATEGY).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Observable>> changesetsFrom(Realm realm, final RealmList list) { + if (realm.isFrozen()) { + return Observable.just(new CollectionChange>(list, null)); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Observable.create(new ObservableOnSubscribe>>() { @Override - public void subscribe(final ObservableEmitter>> emitter) throws Exception { + public void subscribe(final ObservableEmitter>> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!list.isValid()) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); listRefs.get().acquireReference(list); final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { @Override - public void onChange(RealmList results, OrderedCollectionChangeSet changeSet) { + public void onChange(RealmList list, OrderedCollectionChangeSet changeSet) { if (!emitter.isDisposed()) { - emitter.onNext(new CollectionChange<>(results, changeSet)); + emitter.onNext(new CollectionChange<>(returnFrozenObjects ? list.freeze() : list, changeSet)); } } }; @@ -351,33 +439,42 @@ public void onChange(RealmList results, OrderedCollectionChangeSet changeSet) emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - list.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + list.removeChangeListener(listener); + observableRealm.close(); + } listRefs.get().releaseReference(list); } })); // Emit current value immediately - emitter.onNext(new CollectionChange<>(list, null)); + emitter.onNext(new CollectionChange<>(returnFrozenObjects ? list.freeze() : list, null)); } - }); + }).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Flowable> from(DynamicRealm realm, final RealmList list) { + if (realm.isFrozen()) { + return Flowable.just(list); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Flowable.create(new FlowableOnSubscribe>() { @Override - public void subscribe(final FlowableEmitter> emitter) throws Exception { + public void subscribe(final FlowableEmitter> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!list.isValid()) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); listRefs.get().acquireReference(list); final RealmChangeListener> listener = new RealmChangeListener>() { @Override - public void onChange(RealmList results) { + public void onChange(RealmList list) { if (!emitter.isCancelled()) { - emitter.onNext(list); + emitter.onNext(returnFrozenObjects ? list.freeze() : list); } } }; @@ -387,34 +484,43 @@ public void onChange(RealmList results) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - list.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + list.removeChangeListener(listener); + observableRealm.close(); + } listRefs.get().releaseReference(list); } })); // Emit current value immediately - emitter.onNext(list); + emitter.onNext(returnFrozenObjects ? list.freeze() : list); } - }, BACK_PRESSURE_STRATEGY); + }, BACK_PRESSURE_STRATEGY).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Observable>> changesetsFrom(DynamicRealm realm, final RealmList list) { + if (realm.isFrozen()) { + return Observable.just(new CollectionChange>(list, null)); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Observable.create(new ObservableOnSubscribe>>() { @Override - public void subscribe(final ObservableEmitter>> emitter) throws Exception { + public void subscribe(final ObservableEmitter>> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!list.isValid()) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); listRefs.get().acquireReference(list); final OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() { @Override - public void onChange(RealmList results, OrderedCollectionChangeSet changeSet) { + public void onChange(RealmList list, OrderedCollectionChangeSet changeSet) { if (!emitter.isDisposed()) { - emitter.onNext(new CollectionChange<>(results, changeSet)); + emitter.onNext(new CollectionChange<>(returnFrozenObjects ? list.freeze() : list, changeSet)); } } }; @@ -424,24 +530,33 @@ public void onChange(RealmList results, OrderedCollectionChangeSet changeSet) emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - list.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + list.removeChangeListener(listener); + observableRealm.close(); + } listRefs.get().releaseReference(list); } })); // Emit current value immediately - emitter.onNext(new CollectionChange<>(list, null)); + emitter.onNext(new CollectionChange<>(returnFrozenObjects ? list.freeze() : list, null)); } - }); + }).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Flowable from(final Realm realm, final E object) { + if (realm.isFrozen()) { + return Flowable.just(object); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Flowable.create(new FlowableOnSubscribe() { @Override - public void subscribe(final FlowableEmitter emitter) throws Exception { + public void subscribe(final FlowableEmitter emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!RealmObject.isValid(object)) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); @@ -450,7 +565,7 @@ public void subscribe(final FlowableEmitter emitter) throws Exception { @Override public void onChange(E obj) { if (!emitter.isCancelled()) { - emitter.onNext(obj); + emitter.onNext(returnFrozenObjects ? RealmObject.freeze(obj) : obj); } } }; @@ -460,25 +575,34 @@ public void onChange(E obj) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - RealmObject.removeChangeListener(object, listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + RealmObject.removeChangeListener(object, listener); + observableRealm.close(); + } objectRefs.get().releaseReference(object); } })); // Emit current value immediately - emitter.onNext(object); + emitter.onNext(returnFrozenObjects ? RealmObject.freeze(object) : object); } - }, BACK_PRESSURE_STRATEGY); + }, BACK_PRESSURE_STRATEGY).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Observable> changesetsFrom(Realm realm, final E object) { + if (realm.isFrozen()) { + return Observable.just(new ObjectChange(object, null)); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Observable.create(new ObservableOnSubscribe>() { @Override - public void subscribe(final ObservableEmitter> emitter) throws Exception { + public void subscribe(final ObservableEmitter> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!RealmObject.isValid(object)) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final Realm observableRealm = Realm.getInstance(realmConfig); @@ -487,7 +611,7 @@ public void subscribe(final ObservableEmitter> emitter) throws E @Override public void onChange(E obj, ObjectChangeSet changeSet) { if (!emitter.isDisposed()) { - emitter.onNext(new ObjectChange<>(obj, changeSet)); + emitter.onNext(new ObjectChange<>(returnFrozenObjects ? RealmObject.freeze(obj) : obj, changeSet)); } } }; @@ -497,24 +621,33 @@ public void onChange(E obj, ObjectChangeSet changeSet) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - RealmObject.removeChangeListener(object, listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + RealmObject.removeChangeListener(object, listener); + observableRealm.close(); + } objectRefs.get().releaseReference(object); } })); // Emit current value immediately - emitter.onNext(new ObjectChange<>(object, null)); + emitter.onNext(new ObjectChange<>(returnFrozenObjects ? RealmObject.freeze(object) : object, null)); } - }); + }).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Flowable from(DynamicRealm realm, final DynamicRealmObject object) { + if (realm.isFrozen()) { + return Flowable.just(object); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Flowable.create(new FlowableOnSubscribe() { @Override - public void subscribe(final FlowableEmitter emitter) throws Exception { + public void subscribe(final FlowableEmitter emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!RealmObject.isValid(object)) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); @@ -523,7 +656,7 @@ public void subscribe(final FlowableEmitter emitter) throws @Override public void onChange(DynamicRealmObject obj) { if (!emitter.isCancelled()) { - emitter.onNext(obj); + emitter.onNext(returnFrozenObjects ? RealmObject.freeze(obj) : obj); } } }; @@ -533,25 +666,34 @@ public void onChange(DynamicRealmObject obj) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - RealmObject.removeChangeListener(object, listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + RealmObject.removeChangeListener(object, listener); + observableRealm.close(); + } objectRefs.get().releaseReference(object); } })); // Emit current value immediately - emitter.onNext(object); + emitter.onNext(returnFrozenObjects ? RealmObject.freeze(object) : object); } - }, BACK_PRESSURE_STRATEGY); + }, BACK_PRESSURE_STRATEGY).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override public Observable> changesetsFrom(DynamicRealm realm, final DynamicRealmObject object) { + if (realm.isFrozen()) { + return Observable.just(new ObjectChange(object, null)); + } final RealmConfiguration realmConfig = realm.getConfiguration(); + Scheduler scheduler = getScheduler(); return Observable.create(new ObservableOnSubscribe>() { @Override - public void subscribe(final ObservableEmitter> emitter) throws Exception { + public void subscribe(final ObservableEmitter> emitter) { + // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. + if (!RealmObject.isValid(object)) return; + // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. final DynamicRealm observableRealm = DynamicRealm.getInstance(realmConfig); @@ -560,7 +702,7 @@ public void subscribe(final ObservableEmitter> @Override public void onChange(DynamicRealmObject obj, ObjectChangeSet changeSet) { if (!emitter.isDisposed()) { - emitter.onNext(new ObjectChange<>(obj, changeSet)); + emitter.onNext(new ObjectChange<>(returnFrozenObjects ? RealmObject.freeze(obj) : obj, changeSet)); } } }; @@ -570,16 +712,18 @@ public void onChange(DynamicRealmObject obj, ObjectChangeSet changeSet) { emitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - object.removeChangeListener(listener); - observableRealm.close(); + if (!observableRealm.isClosed()) { + RealmObject.removeChangeListener(object, listener); + observableRealm.close(); + } objectRefs.get().releaseReference(object); } })); // Emit current value immediately - emitter.onNext(new ObjectChange<>(object, null)); + emitter.onNext(new ObjectChange<>(returnFrozenObjects ? RealmObject.freeze(object) : object, null)); } - }); + }).subscribeOn(scheduler).unsubscribeOn(scheduler); } @Override @@ -602,7 +746,6 @@ public int hashCode() { return 37; // Random number } - // Helper class for keeping track of strong references to objects. private static class StrongReferenceCounter { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 7b3dde3bac..d1c7e917d9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -34,7 +34,7 @@ import javax.annotation.Nullable; -import io.reactivex.annotations.Beta; +import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.OsRealmConfig; @@ -107,7 +107,8 @@ public class SyncConfiguration extends RealmConfiguration { private final SyncSession.ErrorHandler errorHandler; private final boolean deleteRealmOnLogout; private final boolean syncClientValidateSsl; - @Nullable private final String serverCertificateAssetName; + @Nullable + private final String serverCertificateAssetName; @Nullable private final String serverCertificateFilePath; private final boolean waitForInitialData; private final long initialDataTimeoutMillis; @@ -129,6 +130,7 @@ private SyncConfiguration(File directory, @Nullable RxObservableFactory rxFactory, @Nullable Realm.Transaction initialDataTransaction, boolean readOnly, + long maxNumberOfActiveVersions, SyncUser user, URI serverUrl, SyncSession.ErrorHandler errorHandler, @@ -157,7 +159,8 @@ private SyncConfiguration(File directory, initialDataTransaction, readOnly, compactOnLaunch, - false + false, + maxNumberOfActiveVersions ); this.user = user; @@ -219,7 +222,7 @@ public static RealmConfiguration forRecovery(String canonicalPath) { } static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) { - return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true); + return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true, Long.MAX_VALUE); } static URI resolveServerUrl(URI serverUrl, String userIdentifier) { @@ -534,6 +537,7 @@ public static final class Builder { private String syncUrlPrefix = null; @Nullable // null means the user hasn't explicitly set one. An appropriate default is chosen when calling build() private ClientResyncMode clientResyncMode = null; + private long maxNumberOfActiveVersions = Long.MAX_VALUE; /** * Creates an instance of the Builder for the SyncConfiguration. This SyncConfiguration @@ -1148,6 +1152,28 @@ public Builder clientResyncMode(ClientResyncMode mode) { return this; } + /** + * Sets the maximum number of live versions in the Realm file before an {@link IllegalStateException} is thrown when + * attempting to write more data. + *

            + * Realm is capable of concurrently handling many different versions of Realm objects. This can happen if you + * have a Realm open on many different threads or are freezing objects while data is being written to the file. + *

            + * Under normal circumstances this is not a problem, but if the number of active versions grow too large, it will + * have a negative effect on the filesize on disk. Setting this parameters can therefore be used to prevent uses of + * Realm that can result in very large Realms. + *

            + * Note, the version number will also increase when changes from other devices are integrated on this device, + * so the number of active versions will also depend on what other devices writing to the same Realm are doing. + * + * @param number the maximum number of active versions before an exception is thrown. + * @see FAQ + */ + public Builder maxNumberOfActiveVersions(long number) { + this.maxNumberOfActiveVersions = number; + return this; + } + /** * Creates the RealmConfiguration based on the builder parameters. * @@ -1189,7 +1215,7 @@ public SyncConfiguration build() { } if (rxFactory == null && isRxJavaAvailable()) { - rxFactory = new RealmObservableFactory(); + rxFactory = new RealmObservableFactory(true); } // Determine location on disk @@ -1267,6 +1293,7 @@ public SyncConfiguration build() { rxFactory, initialDataTransaction, readOnly, + maxNumberOfActiveVersions, // Sync Configuration specific user, diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 6d0f3a04a7..b4bbb88574 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -601,6 +601,7 @@ public synchronized void start() { * If the session is already stopped, calling this method will do nothing. */ public synchronized void stop() { + close(); nativeStop(configuration.getPath()); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/exceptions/IncompatibleSyncedFileException.java b/realm/realm-library/src/objectServer/java/io/realm/exceptions/IncompatibleSyncedFileException.java deleted file mode 100644 index 2742b11ac7..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/exceptions/IncompatibleSyncedFileException.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.exceptions; - -import javax.annotation.Nullable; - -import io.realm.RealmConfiguration; -import io.realm.RealmModel; -import io.realm.SyncConfiguration; -import io.realm.internal.Keep; - -/** - * An exception thrown when attempting to open an incompatible Synchronized Realm file. This usually happens - * when the Realm file was created with an older version of the SDK and automatic migration to the current version - * is not possible. When such an exception occurs, the original file is moved to a backup location and a new file is - * created instead. If you wish to migrate any data from the backup location, you can use {@link #getBackupRealmConfiguration()} - * to obtain a {@link RealmConfiguration} that can then be used to open the backup Realm. After that, retry - * opening the original Realm file (which now should be recreated as an empty file) and copy all data from the backup file to the new one. - *

            - * {@code
            - *  SyncConfiguration syncConfig = new SyncConfiguration.Builder(user, serverUri).build();
            - *  try {
            - *      Realm realm = Realm.getInstance(syncConfig);
            - *  } catch (IncompatibleSyncedFileException exception) {
            - *      RealmConfiguration backupConfig = exception.getBackupRealmConfiguration();
            - *      Realm backupRealm = Realm.getInstance(backupConfig);
            - *      realm = Realm.GetInstance(syncConfig);
            - *  }
            - * }
            - * 
            - */ -@Keep -public class IncompatibleSyncedFileException extends RealmFileException { - private final String path; - - public IncompatibleSyncedFileException(String message, String recoveryPath) { - super(Kind.INCOMPATIBLE_SYNC_FILE, message); - this.path = recoveryPath; - } - - /** - * Gets a {@link RealmConfiguration} instance that can be used to open the backup Realm file. - * - * Note: This will use the default Realm module (composed of all {@link RealmModel}), and - * assume no encryption should be used as well. - * - * @return A configuration object for the backup Realm. - */ - public RealmConfiguration getBackupRealmConfiguration() { - return SyncConfiguration.forRecovery(path, null); - } - - /** - * Gets a {@link RealmConfiguration} instance that can be used to open the backup Realm file. - * - * Note: This will use the default Realm module (composed of all {@link RealmModel}). - * - * @param encryptionKey Optional encryption key that was used to encrypt the original Realm file. - * @return A configuration object for the backup Realm. - */ - public RealmConfiguration getBackupRealmConfiguration(@Nullable byte[] encryptionKey) { - return SyncConfiguration.forRecovery(path, encryptionKey); - } - - /** - * Gets a {@link RealmConfiguration} instance that can be used to open the backup Realm file. - * - * @param encryptionKey Optional encryption key that was used to encrypt the original Realm file. - * @param modules restricts Realm schema to the provided module. - * @return A configuration object for the backup Realm. - */ - public RealmConfiguration getBackupRealmConfiguration(@Nullable byte[] encryptionKey, Object... modules) { - return SyncConfiguration.forRecovery(path, encryptionKey, modules); - } - - /** - * @return Absolute path to the backup Realm file. - */ - public String getRecoveryPath() { - return path; - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/assets/sync-1.x.realm b/realm/realm-library/src/syncIntegrationTest/assets/sync-1.x.realm deleted file mode 100644 index 3f7404b42ec9d13290540635119addab571d23fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 zcmeHLJ!~UI6n-;1`;!Ed4LQIPLTm$a1Mw+BG8Kj+y6Z0Du1I$_IUhd7mspN<3KB)i zlv_|3i6W&Xgp>|TP`IE-8HvUcjYZ`K$q@x9`QH2_wy|}09mPH=`}F4he{aS+o8lU2 zI*Sc|_~93yhau5tpx24K=AKz!@O<}KtzCUwZC4lQ-`;Ih4-X$bXt$p3e|>+y@y(3g zD(8_za@i_a&OvVOJ!)6KYSip<2)WrhXx3V79YV34fF1rcvAl(Pk13@dB_YcZG|U6b z5ljrJ6Lf>(%A}tC1~j58>jzn$!*+uyw*B=daysW&i;ugh>|dQ73_3T zHZSyIdqU6g%c61}AN>pbX>xgX>($mF>{#Y_c04*S=Q|duIJNm)pPZ*ZvCetU#QAj| z_KQVR48-js$HnN67{lhC5F&Du^hvDKEuzr2n&V~|!m>}q8X39a+!AtSVleNZ4H?f) z+$XtCZ;|wMONh>~bRRX;do@uXmW8cL>ycMDAbFkZKar{I$mzK_KWtm^$1flJwQl3X z{>>xVl|9*)=MG@#rs;A@E>oO>ea zsIKa%5c8-g6PEh~9@-B5dfe{_weUKYJ?60V(1RwHw;+?B$_R;Bc6N@#)|>73+h1?? z-(Bp#vF$gH7)%Ari&osbLsY}!zrD?s{>=EzU3E|4=o>AK8u5s-~ai?zkm9! z{sZ=oCb#b7Mf=v_b!q)x6)%HUKbq`&9$teemBYx`@$X2w0Ix+j{rUU!IX~x@FlcN! z*>e(Gp10ifZ{Z^+JG|WbtK#Kwa@RBGaWs`L=kg6a zld%(ymng2p+iRu@j)V9)m$!iLf6iPGaZsTqo#CG!Ol*hzx!ujXweb0D{)_l3PS#eD qzr5sbF2*l=W(Z^mWC&ykWC&ykWC&ykWC&ykWC&ykWC;A95cn7NH> allResults = new AtomicReference<>();// notifier could be GC'ed before it get a chance to trigger the second commit, so declaring it outside the Runnable handler.post(new Runnable() { @Override public void run() { @@ -328,8 +329,7 @@ public void run() { .waitForInitialRemoteData() .build(); final Realm adminRealm = Realm.getInstance(adminConfig); - - RealmResults all = adminRealm.where(StringOnly.class).sort(StringOnly.FIELD_CHARS).findAll(); + allResults.set(adminRealm.where(StringOnly.class).sort(StringOnly.FIELD_CHARS).findAll()); RealmChangeListener> realmChangeListener = new RealmChangeListener>() { @Override public void onChange(RealmResults stringOnlies) { @@ -341,12 +341,12 @@ public void onChange(RealmResults stringOnlies) { // active session reference in Object Store adminRealm.close(); testCompleted.countDown(); - handlerThread.quit(); + handlerThread.quitSafely(); }); } } }; - all.addChangeListener(realmChangeListener); + allResults.get().addChangeListener(realmChangeListener); // login again to re-activate the user SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); @@ -563,6 +563,7 @@ public void run() { @Test @RunTestInLooperThread + @Ignore("__CORE6__ this test is flaky in Core6, listener is not triggered") public void registerConnectionListener() { getSession(session -> { session.addConnectionChangeListener((oldState, newState) -> { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 24619366d1..7c6c74d3ee 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -827,12 +827,14 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread - @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + + "to support the requested operation.\",\"status\":501,\"detail\"" + + ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") public void requestPasswordResetAsync() { String email = "foo@bar.baz"; UserFactory.createUser(email).logOut(); - // Currently no easy way to see if we actually get an email. + // Currently no easy way to see if we actually got an email. // Just verify that the network request can complete successfully. SyncUser.requestPasswordResetAsync(email, Constants.AUTH_URL, new SyncUser.Callback() { @Override @@ -849,7 +851,9 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread - @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + + "to support the requested operation.\",\"status\":501,\"detail\"" + + ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") public void requestResetPassword_unknownEmail() { SyncUser.requestPasswordResetAsync("unknown@realm.io", Constants.AUTH_URL, new SyncUser.Callback() { @Override @@ -867,7 +871,9 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread - @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + + "to support the requested operation.\",\"status\":501,\"detail\"" + + ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") public void completeResetPassword_invalidToken() { SyncUser.completePasswordResetAsync("invalidToken","newPassword", Constants.AUTH_URL, new SyncUser.Callback() { @Override @@ -885,7 +891,9 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread - @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + + "to support the requested operation.\",\"status\":501,\"detail\"" + + ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") public void requestEmailConfirmation() { String email = "foo@bar.baz"; UserFactory.createUser(email).logOut(); @@ -906,7 +914,9 @@ public void onError(ObjectServerError error) { } @Test @RunTestInLooperThread - @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + + "to support the requested operation.\",\"status\":501,\"detail\"" + + ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") public void requestEmailConfirmation_invalidEmail() { SyncUser.requestEmailConfirmationAsync("unknown@realm.io", Constants.AUTH_URL, new SyncUser.Callback() { @Override @@ -925,7 +935,9 @@ public void onError(ObjectServerError error) { @Test @RunTestInLooperThread - @Ignore("Depends on https://github.com/realm/realm-java/pull/5909") + @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + + "to support the requested operation.\",\"status\":501,\"detail\"" + + ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") public void confirmEmail_invalidToken() { SyncUser.confirmEmailAsync("invalidToken", Constants.AUTH_URL, new SyncUser.Callback() { @Override diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 0469153779..980543ac79 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -1,13 +1,10 @@ package io.realm.objectserver; -import android.os.SystemClock; - import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; import java.util.UUID; -import java.util.concurrent.TimeUnit; import io.realm.ObjectServerError; import io.realm.Realm; @@ -21,7 +18,7 @@ import io.realm.SyncUser; import io.realm.TestHelper; import io.realm.entities.StringOnly; -import io.realm.exceptions.RealmFileException; +import io.realm.exceptions.RealmError; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; @@ -146,7 +143,7 @@ public void onError(SyncSession session, ObjectServerError error) { try { realm = Realm.getInstance(configWithoutEncryption); fail("It should not be possible to open the Realm without the encryption key set previously."); - } catch (RealmFileException ignored) { + } catch (RealmError ignored) { } finally { if (realm != null) { realm.close(); diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java index e7243d27f7..8a7c228a5c 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java @@ -27,14 +27,6 @@ import java.lang.reflect.Method; import java.util.UUID; -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.Realm; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncUser; -import io.realm.UserStore; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.objectserver.Token; import io.realm.log.LogLevel; diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index d65692fb5d..8bb97d2e84 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -128,8 +128,8 @@ public static RealmFieldType getColumnType(Object o) { * with primary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row * added. */ - public static long addRowWithValues(Table table, Object... values) { - long rowIndex = OsObject.createRow(table); + public static long addRowWithValues(Table table, long[] columnKeys, Object[] values) { + long rowKey = OsObject.createRow(table); // Checks values types. int columns = (int) table.getColumnCount(); @@ -140,10 +140,10 @@ public static long addRowWithValues(Table table, Object... values) { String.valueOf(columns) + ")."); } RealmFieldType[] colTypes = new RealmFieldType[columns]; - for (int columnIndex = 0; columnIndex < columns; columnIndex++) { - Object value = values[columnIndex]; - RealmFieldType colType = table.getColumnType(columnIndex); - colTypes[columnIndex] = colType; + for (int i = 0; i < columnKeys.length; i++) { + Object value = values[i]; + RealmFieldType colType = table.getColumnType(columnKeys[i]); + colTypes[i] = colType; if (!colType.isValid(value)) { // String representation of the provided value type. String providedType; @@ -153,70 +153,70 @@ public static long addRowWithValues(Table table, Object... values) { providedType = value.getClass().toString(); } - throw new IllegalArgumentException("Invalid argument no " + String.valueOf(1 + columnIndex) + + throw new IllegalArgumentException("Invalid argument no " + (i + 1) + ". Expected a value compatible with column type " + colType + ", but got " + providedType + "."); } } // Inserts values. - for (long columnIndex = 0; columnIndex < columns; columnIndex++) { - Object value = values[(int) columnIndex]; - switch (colTypes[(int) columnIndex]) { + for (int i = 0; i < columnKeys.length; i++) { + Object value = values[i]; + switch (colTypes[i]) { case BOOLEAN: if (value == null) { - table.setNull(columnIndex, rowIndex, false); + table.setNull(columnKeys[i], rowKey, false); } else { - table.setBoolean(columnIndex, rowIndex, (Boolean) value, false); + table.setBoolean(columnKeys[i], rowKey, (Boolean) value, false); } break; case INTEGER: if (value == null) { - table.setNull(columnIndex, rowIndex, false); + table.setNull(columnKeys[i], rowKey, false); } else { long longValue = ((Number) value).longValue(); - table.setLong(columnIndex, rowIndex, longValue, false); + table.setLong(columnKeys[i], rowKey, longValue, false); } break; case FLOAT: if (value == null) { - table.setNull(columnIndex, rowIndex, false); + table.setNull(columnKeys[i], rowKey, false); } else { - table.setFloat(columnIndex, rowIndex, (Float) value, false); + table.setFloat(columnKeys[i], rowKey, (Float) value, false); } break; case DOUBLE: if (value == null) { - table.setNull(columnIndex, rowIndex, false); + table.setNull(columnKeys[i], rowKey, false); } else { - table.setDouble(columnIndex, rowIndex, (Double) value, false); + table.setDouble(columnKeys[i], rowKey, (Double) value, false); } break; case STRING: if (value == null) { - table.setNull(columnIndex, rowIndex, false); + table.setNull(columnKeys[i], rowKey, false); } else { - table.setString(columnIndex, rowIndex, (String) value, false); + table.setString(columnKeys[i], rowKey, (String) value, false); } break; case DATE: if (value == null) { - table.setNull(columnIndex, rowIndex, false); + table.setNull(columnKeys[i], rowKey, false); } else { - table.setDate(columnIndex, rowIndex, (Date) value, false); + table.setDate(columnKeys[i], rowKey, (Date) value, false); } break; case BINARY: if (value == null) { - table.setNull(columnIndex, rowIndex, false); + table.setNull(columnKeys[i], rowKey, false); } else { - table.setBinaryByteArray(columnIndex, rowIndex, (byte[]) value, false); + table.setBinaryByteArray(columnKeys[i], rowKey, (byte[]) value, false); } break; default: - throw new RuntimeException("Unexpected columnType: " + String.valueOf(colTypes[(int) columnIndex])); + throw new RuntimeException("Unexpected columnType: " + String.valueOf(colTypes[i])); } } - return rowIndex; + return rowKey; } /** diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java index bfc66bc7aa..94662ecaae 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -74,8 +74,8 @@ public void evaluate() throws Throwable { @Override protected void before() throws Throwable { - super.before(); Realm.init(InstrumentationRegistry.getTargetContext()); + super.before(); } @Override diff --git a/version.txt b/version.txt index 757e674004..09c3376050 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.0-SNAPSHOT \ No newline at end of file +7.0.0-beta.0-SNAPSHOT From ad7b3eb36052605bb703609aa754c31c0adc9ac0 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 13 Dec 2019 10:40:59 +0000 Subject: [PATCH 1449/2110] - Updated Changelog version & notes - Fixed an issue where an index was added to a String PK (on non Sync flavour) --- CHANGELOG.md | 9 +++++---- .../src/main/cpp/io_realm_internal_OsSharedRealm.cpp | 5 +---- version.txt | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bd5be64c..38262821c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,15 @@ -## 7.0.0-beta.0 (YYYY-MM-DD) +## 7.0.0-beta (YYYY-MM-DD) ### Breaking Changes -* [ObjectServer] Query-based Sync is now the default mode of synchronization. To enable Full Realm synchronization use `SyncConfiguration.Builder.fullSynchronization()`. `SyncConfiguration.Builder.partialRealm()` has been deprecated. -* [ObjectServer] `SyncConfiguration.isPartialRealm()` has been replaced by `SyncConfiguration.isFullySynchronizedRealm()`. +* Core 6 only support file upgrades from the file format introduced in Realm Java 2.0. * RxJava Flowables and Observables are now subscribed to and unsubscribed to asynchronously on the thread holding the live Realm, instead of previously where this was done synchronously. * All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(true|false))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. * MIPS is not supported anymore. * Realm now requires `minSdkVersion` 16. Up from 9. * `IncompatibleSyncedFileException` is removed as it is no longer used. +* [ObjectServer] Query-based Sync is now the default mode of synchronization. To enable Full Realm synchronization use `SyncConfiguration.Builder.fullSynchronization()`. `SyncConfiguration.Builder.partialRealm()` has been deprecated. +* [ObjectServer] `SyncConfiguration.isPartialRealm()` has been replaced by `SyncConfiguration.isFullySynchronizedRealm()`. ### Enhancements * Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) @@ -18,7 +19,7 @@ ### Compatibility * Realm Object Server: 3.23.1 or later. -* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* File format: Generates Realms with format v10 (Reads and upgrades all previous formats up to Realm Java 2.0). * APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. ### Internal diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 0dc28892da..b7053c91a6 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -297,10 +297,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi table = sync::create_table_with_primary_key(static_cast(group), table_name, pkType, field_name, is_nullable); #else - table = group.add_table(table_name); - ColKey column_key = table->add_column(pkType, field_name, is_nullable); - table->add_search_index(column_key); - table->set_primary_key_column(column_key); + table = group.add_table_with_primary_key(table_name, pkType, field_name, is_nullable); #endif return reinterpret_cast(new TableRef(table)); } diff --git a/version.txt b/version.txt index 09c3376050..df4c8a93f3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.0-beta.0-SNAPSHOT +7.0.0-beta-SNAPSHOT From 5c2dfdeddb136783305355d6923c5b5349bc5907 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 13 Dec 2019 14:40:59 +0000 Subject: [PATCH 1450/2110] Adding a work around to throw TableNameInUse if we try to create table with primary key using an existing name --- .../src/main/cpp/io_realm_internal_OsSharedRealm.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index b7053c91a6..921fa06b79 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -297,7 +297,12 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi table = sync::create_table_with_primary_key(static_cast(group), table_name, pkType, field_name, is_nullable); #else - table = group.add_table_with_primary_key(table_name, pkType, field_name, is_nullable); + //__CORE6__ work around until we decide if add_table_with_primary_key should throw if called with the same table name + if (!group.has_table(table_name)) { + table = group.add_table_with_primary_key(table_name, pkType, field_name, is_nullable); + } else { + throw TableNameInUse(); + } #endif return reinterpret_cast(new TableRef(table)); } From b87cc8a007304fa7fbac07634e3ee53b3bea2dc1 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Sun, 15 Dec 2019 10:10:07 +0000 Subject: [PATCH 1451/2110] - Update changeling - Fixed tests involving PK String index --- CHANGELOG.md | 5 ++--- dependencies.list | 4 ++-- .../java/io/realm/RealmMigrationTests.java | 4 ++-- .../java/io/realm/RealmObjectSchemaTests.java | 19 ++++++++++++++++--- .../main/cpp/io_realm_internal_TableQuery.cpp | 1 - realm/realm-library/src/main/cpp/object-store | 2 +- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38262821c3..2141c3cdd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ### Breaking Changes -* Core 6 only support file upgrades from the file format introduced in Realm Java 2.0. * RxJava Flowables and Observables are now subscribed to and unsubscribed to asynchronously on the thread holding the live Realm, instead of previously where this was done synchronously. * All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(true|false))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. * MIPS is not supported anymore. @@ -15,11 +14,11 @@ * Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) * Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. -* `RealmResults.asJSON()` is no longer `@Beta` +* `RealmResults.asJSON()` is no longer `@Beta`. ### Compatibility * Realm Object Server: 3.23.1 or later. -* File format: Generates Realms with format v10 (Reads and upgrades all previous formats up to Realm Java 2.0). +* File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. ### Internal diff --git a/dependencies.list b/dependencies.list index bd1e93f0a7..5fb5883232 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.0-beta.0 -REALM_SYNC_SHA256=9f8079c45a42691a3085b7674adc31b97c1fe357e30451a114b20c997e2153e4 +REALM_SYNC_VERSION=5.0.0-beta.1 +REALM_SYNC_SHA256=0fa8fe96a5e018d0bef9299bb4f8cadebead4c8658e5a006d394226ccd51900a # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 114e77e6c8..6d3dc00490 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -1433,7 +1433,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { } } - // File format 9 (up to Core5) added an index automatically to the primary key, in Core6 string based PK are not + // File format 9 (up to Core5) added an index automatically to the primary key, in Core6 (File format 10) string based PK are not // indexed because the search index is derived from the ObjectKey. @Test public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException { @@ -1444,7 +1444,7 @@ public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException .schema(MigrationCore6PKStringIndexedByDefault.class) .build()); assertFalse(realm.isEmpty()); - assertTrue(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); + assertFalse(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); MigrationCore6PKStringIndexedByDefault first = realm.where(MigrationCore6PKStringIndexedByDefault.class).findFirst(); assertNotNull(first); assertEquals("Foo", first.name); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index c7df496551..0fc3381d8a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -927,11 +927,20 @@ private void setRequired_onPrimaryKeyField(boolean isRequired) { ((DynamicRealm)realm).createObject(schema.getClassName(), "1"); ((DynamicRealm)realm).createObject(schema.getClassName(), "2"); assertTrue(schema.hasPrimaryKey()); - assertTrue(schema.hasIndex(fieldName)); + if (fieldType.getType().isAssignableFrom(String.class)) { + assertFalse(schema.hasIndex(fieldName)); + } else { + assertTrue(schema.hasIndex(fieldName)); + } + schema.setRequired(fieldName, isRequired); assertTrue(schema.hasPrimaryKey()); - assertTrue(schema.hasIndex(fieldName)); + if (fieldType.getType().isAssignableFrom(String.class)) { + assertFalse(schema.hasIndex(fieldName)); + } else { + assertTrue(schema.hasIndex(fieldName)); + } RealmResults results = ((DynamicRealm)realm).where(className).sort(fieldName).findAll(); assertEquals(2, results.size()); @@ -1018,7 +1027,11 @@ public void setPrimaryKey_trueAndFalse() { schema.addPrimaryKey(fieldName); assertTrue(schema.hasPrimaryKey()); assertTrue(schema.isPrimaryKey(fieldName)); - assertTrue(schema.hasIndex(fieldName)); + if (fieldType.getType().isAssignableFrom(String.class)) { + assertFalse(schema.hasIndex(fieldName)); + } else { + assertTrue(schema.hasIndex(fieldName)); + } schema.removePrimaryKey(); assertFalse(schema.hasPrimaryKey()); assertFalse(schema.isPrimaryKey(fieldName)); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 28c7960ca4..cd8d3a23cb 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -1093,7 +1093,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNot(JNIEnv* env, JNIEXPORT jlong JNICALL Java_io_realm_internal_TableQuery_nativeFind(JNIEnv* env, jobject, jlong nativeQueryPtr) { Query* pQuery = Q(nativeQueryPtr); - ConstTableRef pTable = pQuery->get_table(); try { auto r = pQuery->find(); return to_jlong_or_not_found(r); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index dc34c655c8..fe6729961a 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit dc34c655c88a902a66ff407f771efd48e37d29fd +Subproject commit fe6729961a9df52dea27ac5a4257088c86c5b82f From 779ea50dc7d40f9fb9228eea6fee0a6588de7ad6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Dec 2019 14:36:41 +0100 Subject: [PATCH 1452/2110] Fix transform crashing when you delete transformed objects (#6680) --- CHANGELOG.md | 17 ++++++++++ .../java/io/realm/RealmObjectSchemaTests.java | 32 +++++++++++++++++-- .../java/io/realm/entities/CyclicType.java | 1 + .../main/cpp/io_realm_internal_OsResults.cpp | 16 ++++++++++ .../io/realm/MutableRealmObjectSchema.java | 18 +++++++++-- .../main/java/io/realm/RealmObjectSchema.java | 4 +++ .../java/io/realm/internal/CheckedRow.java | 2 +- .../java/io/realm/internal/OsResults.java | 7 ++++ 8 files changed, 91 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e02b216d31..31cff18671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 6.0.3(YYYY-MM-DD) + +### Enhancements +* None. + +### Fixed +* `RealmObjectSchema.transform()` would crash if one of the `DynamicRealmObject` provided are deleted from the Realm. (Issue [#6657](https://github.com/realm/realm-java/issues/6657), since 0.86.0) + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. + +### Internal +* None. + + ## 6.0.2(2019-11-21) ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 8ea8719c14..2b49feb253 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -31,6 +31,7 @@ import java.util.Set; import io.realm.entities.AllJavaTypes; +import io.realm.entities.CyclicType; import io.realm.entities.Dog; import io.realm.entities.NonLatinFieldNames; import io.realm.internal.Table; @@ -1219,8 +1220,7 @@ public void apply(DynamicRealmObject obj) { obj.setInt("age", obj.getInt("age") + 1); } }); - assertEquals(5, ((DynamicRealm)realm).where("Dog").sum("age").intValue()); - } + assertEquals(5, ((DynamicRealm)realm).where("Dog").sum("age").intValue()); } @Test public void transformObjectReferences() { @@ -1243,6 +1243,34 @@ public void apply(DynamicRealmObject dog) { assertEquals("John", ((DynamicRealm)realm).where("Dog").findFirst().getObject("owner").getString("name")); } + @Test + public void transform_deleteObjects() { + if (type == ObjectSchemaType.IMMUTABLE) { + return; + } + + RealmObjectSchema classSchema = realm.getSchema().get("CyclicType"); + + Runnable transform = () -> classSchema.transform(obj -> { + if (obj.getInt(CyclicType.FIELD_ID) % 2 == 0) { + obj.getObject(CyclicType.FIELD_OBJECT).deleteFromRealm(); + obj.deleteFromRealm(); + } + }); + + String className = classSchema.getClassName(); + for (int i = 0; i < 10; i++) { + DynamicRealmObject parentObj = ((DynamicRealm)realm).createObject(className); + DynamicRealmObject childObj = ((DynamicRealm)realm).createObject(className); + parentObj.setLong(CyclicType.FIELD_ID, i); + parentObj.setObject(CyclicType.FIELD_OBJECT, childObj); + childObj.setLong(CyclicType.FIELD_ID, i + 100); + } + assertEquals(20, ((DynamicRealm) realm).where((className)).count()); + transform.run(); + assertEquals(10, ((DynamicRealm) realm).where((className)).count()); + } + @Test public void getFieldNames() { Set fieldNames = DOG_SCHEMA.getFieldNames(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/CyclicType.java b/realm/realm-library/src/androidTest/java/io/realm/entities/CyclicType.java index 1f71d52835..536ca50cdc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/CyclicType.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/CyclicType.java @@ -26,6 +26,7 @@ public class CyclicType extends RealmObject { public static final String FIELD_NAME = "name"; public static final String FIELD_ID = "id"; public static final String FIELD_DATE = "date"; + public static final String FIELD_OBJECT = "object"; private long id; private String name; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index e03f7d0c56..5d3683dfd5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -64,6 +64,22 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResults(JNI return reinterpret_cast(nullptr); } +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResultsFromTable(JNIEnv* env, jclass, + jlong shared_realm_ptr, + jlong table_ptr) +{ + TR_ENTER() + try { + auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); + auto table = reinterpret_cast(table_ptr); + Results results(shared_realm, *table); + auto wrapper = new ResultsWrapper(results); + return reinterpret_cast(wrapper); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) { TR_ENTER_PTR(native_ptr); diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index 277a665446..437d3cba29 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -20,7 +20,9 @@ import javax.annotation.Nonnull; +import io.realm.internal.CheckedRow; import io.realm.internal.OsObjectStore; +import io.realm.internal.OsResults; import io.realm.internal.Table; import io.realm.internal.fields.FieldDescriptor; @@ -283,9 +285,19 @@ public RealmObjectSchema setNullable(String fieldName, boolean nullable) { public RealmObjectSchema transform(Function function) { //noinspection ConstantConditions if (function != null) { - long size = table.size(); - for (long i = 0; i < size; i++) { - function.apply(new DynamicRealmObject(realm, table.getCheckedRow(i))); + // Users might delete object being transformed or accidentally delete other objects + // in the same table. E.g. cascading deletes if it is referenced by an object being deleted. + OsResults results = OsResults.createFromTable(realm.sharedRealm, table).createSnapshot(); + long original_size = results.size(); + if (original_size > Integer.MAX_VALUE) { + throw new UnsupportedOperationException("Too many results to iterate: " + original_size); + } + int size = (int) results.size(); + for (int i = 0; i < size; i++) { + DynamicRealmObject obj = new DynamicRealmObject(realm, new CheckedRow(results.getUncheckedRow(i))); + if (obj.isValid()) { + function.apply(obj); + } } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 8ce435b4b6..0d80d1732b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -27,9 +27,11 @@ import javax.annotation.Nullable; import io.realm.annotations.Required; +import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; import io.realm.internal.OsObject; import io.realm.internal.OsObjectStore; +import io.realm.internal.OsResults; import io.realm.internal.Table; import io.realm.internal.fields.FieldDescriptor; @@ -399,6 +401,8 @@ public Set getFieldNames() { /** * Runs a transformation function on each RealmObject instance of the current class. The object will be represented * as a {@link DynamicRealmObject}. + *

            + * There is no guarantees in which order the objects are returned. * * @param function transformation function. * @return this schema. diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index 115130670b..ad7c5bb6d5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -39,7 +39,7 @@ private CheckedRow(NativeContext context, Table parent, long nativePtr) { super(context, parent, nativePtr); } - private CheckedRow(UncheckedRow row) { + public CheckedRow(UncheckedRow row) { super(row); this.originalRow = row; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 9994ed1884..b8537804d7 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -298,6 +298,11 @@ public static OsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery qu return createFromQuery(sharedRealm, query, new DescriptorOrdering()); } + public static OsResults createFromTable(OsSharedRealm sharedRealm, Table table) { + long ptr = nativeCreateResultsFromTable(sharedRealm.getNativePtr(), table.getNativePtr()); + return new OsResults(sharedRealm, table, ptr); + } + OsResults(OsSharedRealm sharedRealm, Table table, long nativePtr) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; @@ -657,6 +662,8 @@ public void load() { protected static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long descriptorOrderingPtr); + private static native long nativeCreateResultsFromTable(long sharedRealmNativePtr, long tablePtr); + private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeGetRow(long nativePtr, int index); From 5a1a25c764bdb4eef71d04103b0b3c75006fa9d7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Dec 2019 18:21:39 +0100 Subject: [PATCH 1453/2110] Add multi-threaded stress test (#6677) * Add multi-threaded stress set * PR feedback. Use thread pool with variable amount of threads for reuse. --- .../androidTest/java/ThreadStressTests.java | 338 ++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 realm/realm-library/src/androidTest/java/ThreadStressTests.java diff --git a/realm/realm-library/src/androidTest/java/ThreadStressTests.java b/realm/realm-library/src/androidTest/java/ThreadStressTests.java new file mode 100644 index 0000000000..7ccf932331 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/ThreadStressTests.java @@ -0,0 +1,338 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.text.TextUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import io.realm.ManagedRealmListForValueTests; +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.RealmResults; +import io.realm.TestHelper; +import io.realm.entities.AllTypes; +import io.realm.entities.NonLatinFieldNames; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; +import io.realm.rule.TestRealmConfigurationFactory; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +/** + * Class used to stress test multiple actions across different threads. + * This doesn't attempt to test correctness beyond "Don't Crash". + * + * Some error level logging is done during the run of this. This is mostly to make + * it clearer what has happened in the case a run actually did crash, and doesn't indicate + * problems with the test as such. + */ +@RunWith(Parameterized.class) +public class ThreadStressTests { + + @Parameterized.Parameters(name = "Encryption: {0}, ReuseThreads: {1}") + public static List parameters() { + ArrayList list = new ArrayList<>(); + list.add(new Boolean[] { Boolean.TRUE, Boolean.TRUE }); + list.add(new Boolean[] { Boolean.TRUE, Boolean.FALSE }); + list.add(new Boolean[] { Boolean.FALSE, Boolean.TRUE }); + list.add(new Boolean[] { Boolean.FALSE, Boolean.FALSE }); + return list; + } + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @Parameterized.Parameter + public boolean reuseThreads; + @Parameterized.Parameter(1) + public boolean useEncryption; + + private int originalLogLevel; + private final static int MAX_THREADS = 100; + private final static int MAX_CREATE = 1000; + private ExecutorService executor; + private RealmConfiguration realmConfig; + private Random random; + private List threads = new CopyOnWriteArrayList<>(); + private AtomicInteger workerThreadId = new AtomicInteger(0); + + enum CRUDAction { + CREATE, + READ, + UPDATE, + DELETE + } + + public interface AsyncTaskRunner { + void run(Realm realm, CountDownLatch success); + } + + public interface TaskRunner { + void run(Realm realm); + } + + @Before + public void setUp() { + originalLogLevel = RealmLog.getLevel(); + RealmLog.setLevel(LogLevel.INFO); + long seed = System.currentTimeMillis(); + RealmLog.info("Starting stress test with seed: " + seed); + random = new Random(seed); + RealmConfiguration.Builder builder = configFactory.createConfigurationBuilder(); + if (useEncryption) { + builder.encryptionKey(TestHelper.getRandomKey(seed)); + } + realmConfig = configFactory.createConfiguration(); + Realm.deleteRealm(realmConfig); + executor = Executors.newFixedThreadPool(reuseThreads ? random.nextInt(MAX_THREADS) : MAX_THREADS); + } + + @After + public void tearDown() { + RealmLog.setLevel(originalLogLevel); + } + + private void populateTestRealm(Realm realm, int objects) { + boolean inTransaction = realm.isInTransaction(); + if (!inTransaction) { + realm.beginTransaction(); + } + realm.deleteAll(); + for (int i = 0; i < objects; ++i) { + AllTypes allTypes = realm.createObject(AllTypes.class); + allTypes.setColumnLong(i); + allTypes.setColumnBoolean((i % 3) == 0); + allTypes.setColumnBinary(new byte[] {1, 2, 3}); + allTypes.setColumnDate(new Date()); + allTypes.setColumnDouble(Math.PI); + allTypes.setColumnFloat(1.234567F + i); + + allTypes.setColumnString("test data " + i); + allTypes.setColumnLong(i); + NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class); + nonLatinFieldNames.set델타(i); + nonLatinFieldNames.setΔέλτα(i); + nonLatinFieldNames.set베타(1.234567F + i); + nonLatinFieldNames.setΒήτα(1.234567F + i); + } + if (!inTransaction) { + realm.commitTransaction(); + } + } + + private void populateTestRealm(Realm realm) { + populateTestRealm(realm, 1000); + } + + @Test + public void threadStressTest() throws ExecutionException, InterruptedException { + Realm realm = Realm.getInstance(realmConfig); + populateTestRealm(realm); + for (int i = 0; i < MAX_THREADS; i++) { + CRUDAction action = CRUDAction.values()[random.nextInt(4)]; + Runnable task = null; + switch(action) { + case CREATE: + task = createObjects(random.nextInt(MAX_CREATE), random.nextBoolean()); + break; + case READ: + task = readObjects(random.nextBoolean()); + break; + case UPDATE: + task = updateObjects(random.nextBoolean(), random.nextBoolean()); + break; + case DELETE: + task = deleteObjects(random.nextBoolean(), random.nextBoolean()); + break; + } + threads.add(executor.submit(task)); + } + for (Future task : threads) { + assertNull(task.get()); + } + realm.close(); + } + + private Runnable createObjects(int objectsCount, boolean asyncTransaction) { + if (asyncTransaction) { + return createTaskInHandlerThread((realm, success) -> { + RealmLog.info("Creating objects (async): " + Thread.currentThread().getName()); + realm.executeTransactionAsync(bgRealm -> populateTestRealm(bgRealm, objectsCount), success::countDown); + }); + } else { + return createTaskInThread((realm) -> { + RealmLog.info("Creating objects: " + Thread.currentThread().getName()); + populateTestRealm(realm, objectsCount); + }); + } + } + + private Runnable deleteObjects(boolean filterObjects, boolean asyncTransaction) { + TaskRunner delete = realm -> { + if (filterObjects) { + realm.where(AllTypes.class) + .lessThan(AllTypes.FIELD_LONG, realm.where(AllTypes.class).count()/2) + .equalTo(AllTypes.FIELD_BOOLEAN, true) + .findAll() + .deleteAllFromRealm(); + } else { + realm.delete(AllTypes.class); + } + }; + + if (asyncTransaction) { + return createTaskInHandlerThread(((realm, success) -> { + RealmLog.info("Deleting objects (async): " + Thread.currentThread().getName()); + realm.executeTransactionAsync(delete::run, success::countDown); + })); + } else { + return createTaskInThread((realm) -> { + RealmLog.info("Deleting objects: " + Thread.currentThread().getName()); + realm.executeTransaction(delete::run); + }); + } + } + + + private Runnable updateObjects(boolean filterObjects, boolean asyncTransaction) { + TaskRunner update = realm -> { + RealmResults results; + if (filterObjects) { + results = realm.where(AllTypes.class) + .lessThan(AllTypes.FIELD_LONG, random.nextInt((int) realm.where(AllTypes.class).count() + 1)) + .equalTo(AllTypes.FIELD_BOOLEAN, random.nextBoolean()) + .findAll(); + } else { + results = realm.where(AllTypes.class).findAll(); + } + + results.setString(AllTypes.FIELD_STRING, "Updated: " + Thread.currentThread().getName()); + results.setBoolean(AllTypes.FIELD_BOOLEAN, random.nextBoolean()); + }; + + if (asyncTransaction) { + return createTaskInHandlerThread(((realm, success) -> { + RealmLog.info("Updating objects (async): " + Thread.currentThread().getName()); + realm.executeTransactionAsync(update::run, success::countDown); + })); + } else { + return createTaskInThread((realm) -> { + RealmLog.info("Updating objects: " + Thread.currentThread().getName()); + realm.executeTransaction(update::run); + }); + } + } + + private Runnable readObjects(boolean asyncQuery) { + if (asyncQuery) { + return createTaskInHandlerThread(new AsyncTaskRunner() { + private RealmResults liveResults; + @Override + public void run(Realm realm, CountDownLatch success) { + RealmLog.info("Reading objects (async): " + Thread.currentThread().getName()); + liveResults = realm.where(AllTypes.class) + .lessThan(AllTypes.FIELD_LONG, random.nextInt((int) realm.where(AllTypes.class).count() + 1)) + .equalTo(AllTypes.FIELD_BOOLEAN, random.nextBoolean()) + .findAllAsync(); + liveResults.addChangeListener((updatedResults, changeSet) -> { + for (AllTypes result : updatedResults) { + assertFalse(TextUtils.isEmpty(result.getColumnString())); + } + if (updatedResults.isLoaded()) { + RealmLog.info("Query finished on: " + Thread.currentThread().getName()); + success.countDown(); + } + }); + } + }); + } else { + return createTaskInThread((realm) -> { + RealmLog.info("Reading objects: " + Thread.currentThread().getName()); + RealmResults results = realm.where(AllTypes.class) + .lessThan(AllTypes.FIELD_LONG, random.nextInt((int) realm.where(AllTypes.class).count() + 1)) + .equalTo(AllTypes.FIELD_BOOLEAN, random.nextBoolean()) + .findAll(); + for (AllTypes result : results) { + assertFalse(TextUtils.isEmpty(result.getColumnString())); + } + }); + } + } + + private Runnable createTaskInThread(TaskRunner runnable) { + return () -> { + Realm realm = Realm.getInstance(realmConfig); + runnable.run(realm); + realm.close(); + }; + } + + private Runnable createTaskInHandlerThread(AsyncTaskRunner wrapper) { + return new Runnable() { + CountDownLatch successLatch = new CountDownLatch(1); + CountDownLatch closeLatch = new CountDownLatch(1); + volatile Handler handler; + volatile HandlerThread handlerThread; + AtomicReference realm = new AtomicReference<>(null); + AsyncTaskRunner wrapperStrongRef = wrapper; + + @Override + public void run() { + handlerThread = new HandlerThread("HandlerWorker: " + workerThreadId.incrementAndGet()); + handlerThread.start(); + Looper looper = handlerThread.getLooper(); + handler = new Handler(looper); + handler.post(() -> { + realm.set(Realm.getInstance(realmConfig)); + wrapperStrongRef.run(realm.get(), successLatch); + }); + + TestHelper.awaitOrFail(successLatch); + handler.post(() -> { + realm.get().close(); + closeLatch.countDown(); + }); + TestHelper.awaitOrFail(closeLatch); + handlerThread.quitSafely(); + } + }; + } +} From a868105f36a2622d74cd1543530fbf34f33b405a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Dec 2019 18:22:02 +0100 Subject: [PATCH 1454/2110] Better support for Gradle offline mode (#6692) --- CHANGELOG.md | 1 + .../kotlin/io/realm/transformer/RealmTransformer.kt | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31cff18671..9281129559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed * `RealmObjectSchema.transform()` would crash if one of the `DynamicRealmObject` provided are deleted from the Realm. (Issue [#6657](https://github.com/realm/realm-java/issues/6657), since 0.86.0) +* The Realm Transformer will no longer attempt to send anonymous metrics when Gradle is invoked with `--offline`. (Issue [#6691](https://github.com/realm/realm-java/issues/6691)) ### Compatibility * Realm Object Server: 3.23.1 or later. diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 2e4a38aa2b..8788019830 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -126,7 +126,7 @@ class RealmTransformer(val project: Project) : Transform() { */ private fun sendAnalytics(inputs: Collection, outputModelClasses: Set) { try { - val disableAnalytics: Boolean = "true".equals(System.getenv()["REALM_DISABLE_ANALYTICS"], ignoreCase = true) + val disableAnalytics: Boolean = project.gradle.startParameter.isOffline || "true".equals(System.getenv()["REALM_DISABLE_ANALYTICS"], ignoreCase = true) if (inputs.isEmpty() || disableAnalytics) { // Don't send analytics for incremental builds or if they have been explicitly disabled. return @@ -153,12 +153,9 @@ class RealmTransformer(val project: Project) : Transform() { val packages: Set = outputModelClasses.map { it.packageName }.toSet() val targetSdk: String? = project.getTargetSdk() val minSdk: String? = project.getMinSdk() - - if (!disableAnalytics) { - val sync: Boolean = Utils.isSyncEnabled(project) - val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk) - analytics.execute() - } + val sync: Boolean = Utils.isSyncEnabled(project) + val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk) + analytics.execute() } catch (e: Exception) { // Analytics failing for any reason should not crash the build logger.debug("Could not send analytics: $e") From c1c45eb9ce12dd25ff41e4c198886387d5c8af54 Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Tue, 17 Dec 2019 12:34:00 +0100 Subject: [PATCH 1455/2110] Update CHANGELOG.md (#6695) Isn't the beta release versioned with a number? "-beta.0"? The date of the release needs update --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2141c3cdd1..0b444bc168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ ## 7.0.0-beta (YYYY-MM-DD) -### Breaking Changes +Based on v6.0.2. + +NOTE: This version bumps the Realm file format to version 10. It is not possible to downgrade version 9 or earlier. Files created with older versions of Realm will be automatically upgraded. +### Breaking Changes * RxJava Flowables and Observables are now subscribed to and unsubscribed to asynchronously on the thread holding the live Realm, instead of previously where this was done synchronously. * All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(true|false))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. * MIPS is not supported anymore. @@ -15,6 +18,8 @@ * Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. * `RealmResults.asJSON()` is no longer `@Beta`. +* Storing large binary blobs in Realm files no longer forces the file to be at least 8x the size of the largest blob. +* Reduce the size of transaction logs stored inside the Realm file, reducing file size growth from large transactions. ### Compatibility * Realm Object Server: 3.23.1 or later. From f323c61b79eeb98a4c09433af2be89263230b9d2 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 17 Dec 2019 12:34:15 +0000 Subject: [PATCH 1456/2110] Throwing a RealmFileException when an invalid encryption key is used --- realm/realm-library/src/main/cpp/util.cpp | 4 ++++ .../realm/objectserver/EncryptedSynchronizedRealmTests.java | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 95626dfd9d..78f4d3b17b 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -75,6 +75,10 @@ void ConvertException(JNIEnv* env, const char* file, int line) ss << e.what() << " in " << file << " line " << line; ThrowException(env, IllegalArgument, ss.str()); } + catch (const InvalidDatabase& e) { + ss << e.what() << " (" << e.get_path() << ") in " << file << " line " << line; + ThrowRealmFileException(env, ss.str(), realm::RealmFileException::Kind::AccessError, e.get_path()); + } catch (RealmFileException& e) { ss << e.what() << " (" << e.underlying() << ") (" << e.path() << ") in " << file << " line " << line; ThrowRealmFileException(env, ss.str(), e.kind(), e.path()); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 980543ac79..23c15d1543 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -18,7 +18,7 @@ import io.realm.SyncUser; import io.realm.TestHelper; import io.realm.entities.StringOnly; -import io.realm.exceptions.RealmError; +import io.realm.exceptions.RealmFileException; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; @@ -143,7 +143,7 @@ public void onError(SyncSession session, ObjectServerError error) { try { realm = Realm.getInstance(configWithoutEncryption); fail("It should not be possible to open the Realm without the encryption key set previously."); - } catch (RealmError ignored) { + } catch (RealmFileException ignored) { } finally { if (realm != null) { realm.close(); From 32e7cabececfb4e823fc4d25239a5bcf9edb9dff Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 17 Dec 2019 12:37:46 +0000 Subject: [PATCH 1457/2110] Confirming flakiness on CI --- .../src/syncIntegrationTest/java/io/realm/SyncSessionTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index bf0d387bfc..402441bebf 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -563,7 +563,7 @@ public void run() { @Test @RunTestInLooperThread - @Ignore("__CORE6__ this test is flaky in Core6, listener is not triggered") +// @Ignore("__CORE6__ this test is flaky in Core6, listener is not triggered") public void registerConnectionListener() { getSession(session -> { session.addConnectionChangeListener((oldState, newState) -> { From 23e7118d5b844de3059a7df5dca67e66ccb7d672 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 17 Dec 2019 18:44:43 +0000 Subject: [PATCH 1458/2110] Updated iOS tests --- .../0.98.0-alltypes-default-encrypted.realm | Bin 8192 -> 0 bytes .../assets/ios/0.98.0-alltypes-default.realm | Bin 4096 -> 0 bytes .../assets/ios/0.98.0-alltypes-max.realm | Bin 4096 -> 0 bytes .../assets/ios/0.98.0-alltypes-min.realm | Bin 4096 -> 0 bytes .../ios/0.98.0-alltypes-null-value.realm | Bin 4096 -> 0 bytes .../assets/ios/0.98.0-alltypes.realm | Bin 8192 -> 0 bytes ....0-beta.2-alltypes-default-encrypted.realm | Bin 0 -> 12288 bytes .../ios/6.0.0-beta.2-alltypes-default.realm | Bin 0 -> 8192 bytes .../ios/6.0.0-beta.2-alltypes-max.realm | Bin 0 -> 8192 bytes .../ios/6.0.0-beta.2-alltypes-max.realm.lock | Bin 0 -> 1184 bytes .../ios/6.0.0-beta.2-alltypes-min.realm | Bin 0 -> 8192 bytes .../6.0.0-beta.2-alltypes-null-value.realm | Bin 0 -> 8192 bytes .../assets/ios/6.0.0-beta.2-alltypes.realm | Bin 0 -> 16384 bytes .../src/androidTest/assets/ios/README.md | 31 +++++------------- .../java/io/realm/IOSRealmTests.java | 14 +++----- .../java/io/realm/entities/IOSAllTypes.java | 6 ++-- 16 files changed, 15 insertions(+), 36 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-default-encrypted.realm delete mode 100644 realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-default.realm delete mode 100644 realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-max.realm delete mode 100644 realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-min.realm delete mode 100644 realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-null-value.realm delete mode 100644 realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes.realm create mode 100644 realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-default-encrypted.realm create mode 100644 realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-default.realm create mode 100644 realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-max.realm create mode 100644 realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-max.realm.lock create mode 100644 realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-min.realm create mode 100644 realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-null-value.realm create mode 100644 realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes.realm diff --git a/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-default-encrypted.realm b/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-default-encrypted.realm deleted file mode 100644 index 6ef6a6ea4d7038f51ec58ee09edbbea4bf31c7f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 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 diff --git a/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-max.realm b/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-max.realm deleted file mode 100644 index e798f85b68f9455cf850f6211388ba02ebf53623..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeHHJ8Tm{5S{(SmwI<8Lbo2_=(Tr>jRC9))m4G)Dh!=lUaS5}qVBcSC>x#p@fQe!$Fc09Rm=RMd;l@rZZGw+? zDN6a$C*o9CdE)w-`7NdX0azNrlIj0-Mc*N=k8X7kIH|;@O-tseq{^zo?*n$vIa>ar ze79n2sfsVJ=;!L0{3b8tSYFDBypp6mwXQZ)Rr#vsvZQ|URMw{$8}%KJFOg)vI*mhI zk4M7sr81TnZ(-ienz@(Ng>&_>8mOTfIsdHAs!O;~pBk%6HA(#tC(qT@v<}X*Eo(@0rL8{J13lCueXhrPqATW+shYZZ>9Vsw z{4fu#dF=XdY`70K=0lyyvc7U23BG{dbAOUP;R&ca(+ku&ANnycqB1lib8b*?E;$1^ V133dZ133dZ133dZ1OF)lzX40>$m9S3 diff --git a/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-min.realm b/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-min.realm deleted file mode 100644 index 0bb0cb607aad6bbad4ca2aee153ccaeb233f5839..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeHHJ#5oJ6n?%tH*O&%Obr842Zk){83Qenk&uW)hit`d42kN-Q4%PfIEkfW$BZ4r z)-fC1=*XC{VyeVIB$jUBdv~^zAS$urr|9m!_jm8U_ne7KM(WD$JCC=YN{O>Wv`(bD z2UdT}`tUFu1kGR&++yF@kAi-G_xa0LPoik&xEuDD9oIAaR;wK~myJO8i8Ki!v6h)M z6@j}4yMtga3KMk!xZ8`nVQ+BED!fMXj!;d{h~yca(}Xp^hf=tvWTQZXz_{k|dU0OkGZE!Or;`&WFTD z6WUNL5q*C2l`96o5A`idaW9UetvE_`UcVLh26IKB-C00Ht5Mu}J2#WZ_{ChO-b8V* z5K(Bx@4#{vv9fnOm?I*uKj^g=GOl12mhAh>ZCx|C7cx;S1?B;~6jNeKCA`>(rY-Ps zl%Rw!HFQRf7DLn5)Ndv655dv}EUErqck~@%`e=3ofo&x^X_}p*k}S(j{yt#$jHBii z<(nNxO*O2%qo3y)lfJB*EQy~OiAIKTkl*p~S+Vog2@df* z9tp#jOj%;Qjd^!B+y`l0xL2LZGkGpAjDK2Z%_Ll^PhQDuIZgZ!C%(y>tPajoaZVg9 zJ;nF9cxMYID;5tec~EIq8y*l>C{tNEn-x`6zCs+t=LL43(66-JP-zNrZ&#(NxgXzW z({gAQry2bXW0=w;n(h2O&t@NI#vPcy)0vT<(9Q3`pA5w3;4~iC_WMSW>zN1mw7O*X zd3eRAAHTjV1!Z{+&oJ3JXeq5u)liMpSY4`#x>jYk;#S?d+pzRlKg_}FMQr##ahd-* z^DoDutft1p;tBL6^XKRjMql1DZ{elE^M^hRU1xLTj@?Ta_2!Z@kTZ}okTZ}okTZ}o KkTdX~Gw>TrA-qcf diff --git a/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-null-value.realm b/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-null-value.realm deleted file mode 100644 index 1cac2cacd30d6c857c252b057291b85c9e66803a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeHHJ8Tm{5S{&8d;}a!6;mL&pm0N<(vTn>5)zh3kuF@~16#THPWJIhw z(q%eQQo6LLQl?DFo83G6j1&n`-^jY%_hx2y_RX%R9$D*ZH}1dKd!-cSf@q6KrzeiT z=lkq5%EDflh4=WkkK=GWK78};{mVGsKTD(Wdf?X5zuO%zh6Cy*BNNz4&hU&oS z$zc{A#ZgYzfYVWuMx*SEDV)aeh*U$biPV%XY0eBV^2J{j+4zUx2R%Nyi$k8gC0`VI zj2F~g`Vob>p+^|MM5FN_8GyM|4EjW*EXIc`k?8x-uW+E8y0 z{doS9D+a(1^_8uDlqB(P5*M65?kA&cMbrkv5+ZsKC&N!GH~Ea;t~mWMPQp?~t(Sa; z%FB$6qqA&}g{{J)OqJIwacjuryDN=)7~caIvXsapI#;P-8y;V;U!Bc_Hr z`0|K;Zq4MioXds0k+gPXrcluvjIye$Mqm-`Fz++)ude{}WK+*oIR0>oIR0>oIR0>oI{FefM0Qm2!^Z)<= diff --git a/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes.realm b/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes.realm deleted file mode 100644 index 1c8b35f4940a3933cf771975b407eee38e3a9a71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 zcmeHKO>7%Q6n?w2>-Ap}r%8!R(_qp7DODi9txwY)ri?!v@yE;qTl zm|1oQ);r}R*|}WWU5HjAfCVam8gz;*s6%^ke!7^N$z`P5Kznhiu$WmYu96E!W0A(H z7MuZKcVH7r@geljrD^0M4(q9V;8*N=WbZ)KDaRTN%9&NU?Z|Fh46kU*@|+)pHfz2j0HFibvQkDJ04Jv(ZC<}uTtt}3WeNA zA!qyi%h|$GvFtGB@)Z_9AXmuGmW#np_W80;yOt}YDry+%!pF$6Q^Pm2S}a=t{^jD* zTm_;J*Mh73s(V_4++NBMQ6vWOfV|Y%#6%);L85;I`M4qC@XPtq)|Cy#QrG#siXsbq zJYzuyS-OuUw*TL1oOcvHk6d#TZP61;PMTOp5f)`J+8Ylo<0`s#@7H1|)up~#3aq5OT<=B{E-{a;JBGy+vwSdE;CBgY(HcMoW z;(FIx^h0*PuwCsIyTdlweI9T3)8v4z{;?gl%l2d()>9v{eWxFU)2ZB4Eaum!9UaG+ z#hegfuM6_fWs#fnfc1O1L*csmhDhT9QHj?h ze>!t*Haqvxd~Sh*aX;_hcYo>%CKVIIq+!xA`7jxn{Fnlms6C`2`03*9q4BMHb9`&e zJa%Ob(`T3-VEVyqzQ=}}?=@jMIt*}&&W+&I;FsIizxre7^VZAh)88tl&;j#eeb*Pa zA4Q~YmO3hRt9g9wsc*i%5tF)I>JF3oLE~Y*K)B(+!RX<(@_6ttZBtbC55N4Z&7)n5 zc*pr<5A|I=;=^GoF*=8W6MWytx@DXp?Vc*Tx61CTviqy-fhzk%m3^|xj#t@(woT)} z-i983vdeyCKe3pYH>d8RnvTMA#;ik zTlia|hn_y%dZg`{m~0vOBL3Ttc0Bj|3!TSaJnoL~dgn1FxRVv&j?#Rm1X$AHIx$AHIx$AHIx$AHIx$AHIx$AHIx M$AHJc|A>LV06Dcb>;M1& diff --git a/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-default-encrypted.realm b/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-default-encrypted.realm new file mode 100644 index 0000000000000000000000000000000000000000..e3136788fbdc7262ff5db6eadfee9ebcf777dd2b GIT binary patch literal 12288 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 literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-default.realm b/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-default.realm new file mode 100644 index 0000000000000000000000000000000000000000..0ff9fc1bd05cbdf914c6eda885b3f6356ca2a01c GIT binary patch literal 8192 zcmeHLJ!~7v6@I(3TymG(HFXhk0&)e56l?;hRJj)`i2@{ua=rtc%A&2kPyi(olrE4f z?>V|V=BH5UQbh`;Q;|ZY5TsBkT;|O;k-#Y z(ckax^>*i#;8sfgqeu4{=MA;I=k(k8aDVUcaIb$bXOuYoM|r2WKc`Y=;{Q>YZZ4)fvL^uz&jkF){$yL_lgkau!Ne8?E?5w!a*K(zL6cZBcIv$ zA?V%R-7{xbEqRFUo>{-f79tHiPYPT{M&1k0&|KrTdtD$Gv>w5k7u;H08M7 z(Qt1jVu;?F(?6MSlUVs-?y6oT4!|>UfHpB&cK^T+TPE;c=7R!{47OPXS13_dl{l+O zuo;QJ8B1-mE{)BGBwO%tbqTM$m)<#?MXq$_dQSgsX&r|2L%%3>eyEM|%$<7lf!Q%# zGeDx`YgP{8kq32l{;7E}v2*fls!u&>nYLLs8zwVbX4^b8$7W>C%(=PH24Otlqw#XY ze?1cqc_cS##mI|e&Oc!!j%Pam52;96FmumRuJ<#MMr~nVmzJN1 z(IBN?Nu9iHc{6X+ zBg`t>m7NML20GI(12;=uzra55WL!C~TvV)H*G0VzdyN+{2brwT-Y4(+-_?Wuh}Ss9 zs+X0DTvvF`ysX@47L)z@U(*?b_IT)gyWdhMQXfptYdzHi+5rb&TBQkj;DGkxb)M7IxXNa;!7uZok>o4*MI2=dhq2a-S5e&;4ROw_oxcz#vsTXvh9I)#xy^ z05Q--J8Py*PrFXr*980??Zoon1AE?zwGbs zefspllWxA>Sq#%8E~roJo(pF{Ik1mCX`rXsAUMrIjirI56-z@)tCrR*b#;zr>dn5| zsSc{gY$Q3=nd@cXNGbhqsEdPj>c}~&Ma^g}YDMj6J=%z}Xe%n?!!G7JXq}k-#1>+2 zzU!($Px}&=@;Tu-M_k~;@l5CMK=HisQ$ph+-^Od8u^mM+i1@r8M`Ps5!F7?Ne_gW5 z^LbzVu6Wn;?zKusn?GL%8P{D{KWxS8@k4xfigaV*MSF6jnWe}R&-;t`%zmE|H|leQ zyNIXnN7(t7u$i%Y=Xo&OA3P5V%>jY!7+aq2bdGDwa}OM>AIB>zzgh4vwcM{1+&uGz zPw+Jl1Iy!m;m%L9pYK1$C#Krr`3R(?X3C@f5cyhnUy>1XaD8B;><7lj{=-4NsK3yp z$90G2;`y1upZ1Qz52yX>`c3^7uZO80^m={+?|{9ABm}wWMStey`p-SL$m^2%`O^3Y zDeZ{wwF43|jv7CbF!!I2^uhE6*$~wbL2rt=e)O_?W-XzmHwNR_wVuc`u7S zfar&Pz}272+wFsT;(Fb9)wtChYRS8BWmeW6XIPeOCEE%44Dm^Uqalv$UtS!PbA5-S ztM6=GiC^0RRl3PT%iptn*Yb<_4Xk{$;BVc~M?pcXnFsb+8+$K|1!N!ou7-J z!Jl@}r$#o?EZs`C(}(F!+D(tsv-CWDnZ8bMw1a4e8oBVgK5+0`_dIcN5hq!91;8Tn zV;To`z;$QkZMa^oSoys3|DEIh@YDa+6C7NtQs;x{d9iq_1xDcco{7bP85IpL@G7xX zRI}=Zv4kpL^Qy5__4WfgeqNju!Rm9mul`==6P>@%`OD%jkbhN-F^`JVvtacP#R&5s zi!;oBD$X(gx%k^<5Q~o>qq7><{~VG=ECFuL&zg{*6io!@e4_IgI)AD2S2~Y4GhO{G|cZjnuwYpBhd#-~teuDy_hCsRqB+*Jp zqNR{TYaxjiLlUiqBw7whv>te+upp9XMI_OZNTM}y>v0|AR&C+eC{BX(^qw@w_z%g+ zx8y>)(v|`K-{9FqIo^BKJ9}=EcQ|&jXX3aH|1DfQxNrt4jsqz!HHa0!sw`GX(w(07Ewv literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-max.realm b/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-max.realm new file mode 100644 index 0000000000000000000000000000000000000000..19bf4f22fd1f9ca2389a271f508163dac5bdd9ab GIT binary patch literal 8192 zcmeHLJ!~V_5uW|wlDp)tC`X6|#085KYyzkh;AZVp5(Q2W#b*PT>cl(kg#sv%pkzZ= zX?>AxI!U4OkcveLrc;qZr4XcGI>FVIhZMn8$(7eB-#7an|LzBN?2-fQo1Oo8GqZ1( z?7bxl&D8>b^pmakqe$dEKq68($c|fkt>ac9eQACBey4SGboUp(zWrXO^U=dzb~LY& zOysxP`<=acA*j_te)sMz`gut#Z&~?H)<4)kI@<3Z&MC!K{$bYY9L$N7nAm^V?PM>D z12 ze*k=Qb8~9-$^{M9&8e}gZNpQ~aU{p($-sH+cvM%vxQpu|fBo?|Ryqc~KVS3sUbox1 z)$OPamda7P+drN%%KL{ilt|d=9{z4BmI~=#PI>-=PPa8cYD>f&Fhj$@&jp?Oxu7{8m4EWBgxr z_x2w=ynVl&9kdqR)L9qwC$i6lRUjX=j~uCCra2%u&4E3GeS=E|2L_i7t{80l9M1Hc zbG2LUl}|WGa;_`mrJ#t({Vv%0L7Xab4l7|jTn`&zGu#L_!!+Cu3;)oI7zd*hah^Cr zoXyu=CCI5?)TMY%c+Rmdupyl4`YkY?H-1W}U*ubRhHC7Dq4YvN?>{%DPdW z!QEvveLq6ay@Jk+-fPc;+4>uH&;XhxnFQnYB- zg^m9;4+BHvePPc}?;tz4hfhq^!}H-wL)DZ<|G{##@2L5#f=B9PDc zwvM*Hvwda#nzm4dncOk-ilN(vUaViw@COU_#td`h=d3mJz&8PzBUSf*y9_2*I?h26ZCfuMg;WfHuC?fd^WBL zYxDLllD2n=-ZKvzVKyyWU-#VyAN-rS72%3_n%_VF{4Rab4*PeXC9-1u*mx=Y8F}K# zW|Ag5$!^k1^5i@jCgbEuQeSDTG_@7fqhIZ|%OUN!s~%WrebyztZig(En))}+b@(QzVi)^dRFxQ-Wz2V#)d9N#HLQc}s96GRdy zf88lZQr6pdX!u!vmiuduXukG)UC(s=T-Pu1zkvT`KEgW4&xiinSNQ8u4lS_uIm@NeyQt#E7PUz8k2p5n{}r8 z2u_eH_sad3(3j0*SN{RA^w=5d2|^n6)bGll;k~{Nq7I3Z;szFVTn$|Cp;wbK+}p;} zp3m*#?~o1CcRq836GK}&`Xem<4zc=$RO95lXB?#QTQmSYM5S|IV(o;)+6sxa7ZPhT zB-U<7tnH9k`+-*q8zQlGL}G1;#M%=Zk8zM&xq)A!I0=&RiquE=56SYko~-oS;r07Z{ym U1(p?9R$y6yWd)WM`2Q;KKOf^^3jhEB literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-max.realm.lock b/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-max.realm.lock new file mode 100644 index 0000000000000000000000000000000000000000..71875c6214472035a1f957467a420698df4e35fe GIT binary patch literal 1184 zcmciA*$#ss5I|9IU)qXGt6lXI{r@MKs%>gg?wcem;S4YYv^Fa9G9^XZ>c2rc;qZr4YDKDY&{)ND*9>s8pwX-|P(k?#^%!18z9L&fE7pZ@zgm zv*gMfqFZyZq#ylc?Wb`p@*Y|$67J;tt*zF6tJJ>KzxlY++S_~h^IzY8uhaSHX)oWK zHc3bNd+qJc*0d7bVyS=l@E+s5q?R|Vek1SiZ13%DcXy|Z6083(Z*_L2RGJz2f7tEh zFRSGT*8k(Zy#Fdaae!Qr)4db&U6 zINpALdm>_h+M3coo-UJE^ZneedWARu&%^>+M{Ah*13yfez^lvw1s)k}GapYNQC5*S z3&mTD#950aT&qfLttQDjeC)aem+HAXgR{t`?m3>-e_L9GA^lJ`6?#al`%CoUPRnl;q?uxtWX70MX;XZH=+<|-Qp1J3`Ko}4B z=y*BeznF-JJdzu2#mKX5%0FQ(e%prtY7jv=KI)nNY=^r44yj05Fmq)n$NQN`Eu5Lx zh2ckH6iDe;QY&v7dE3xZJMOWD1v!obANQX)EG^G-Vz;#en8l?YAzZFK8+qX1I^rCa zl9%(wdIXuj;cxo1=;@w*8MsMm>jn0nlA(X*pZi9y$HjWPtTjHw9Aq>;YoENWf4d&^ zN4(lGM!oQTx$=3=Joj&O7Nhw(U(*?l_SkfenQti+srE+awLaB7+5rbg8l?$&;DGXQ zE+|owT#{0b@q?8h3)(DLInX`jcD~uz4)YrZXRx3ia)%VG!}(%7J74l_z#!Fl(2n(S ztfRxwv~$BFH{C7`PYl^?eOwv&^^AXM#rb#zeK-z$;4K>XI!D$Za8tNJ98yXl|F*G#Pu5&c8 z-mI(5O0ROjLXu`6*)&?RF9UUM%0W}qSYvi)}t~$>|&0C(urA5 zEFsqB>mDC^+LySL`-JBlae)upq3-_&iv7k<2^|;t)?NUOjVO{{#QlB{4UsDw$3>3L zRmp0e`+fGi;&uC7HA+X5Kd*y~Jvq|MP~?gIeiEOW?^EJN zeU5PF@%Vm(opS-33A@*x2b1-|^Ptc%f(=9vC0<4|>(2 zdZLpaRxO^h=Vu0g+FJ@goc1rOH`QBQ4`V;*_4y6FJ=PkM5agmC`ZF(Ee|Fy@uM6hq zNbM_9+7aI?3nXN0AwQDP_~FLZFg$(OEWS-6&kQZK;~wX&w&ghRasM%WAKQBM&AB_e zm&F`F^us*h>P+Qr=RrMjy{cW;Zgmb}@;Y3Zm9fVemL==SMnXPAd{W@(5=YiAA2!N4 zzQxh5@8r4?zorGMbdv{$Uod>z@U!^!jC?TTZ`@EvUO}vx2j&?L)pM^N+y)Yd)pl*D z^Uqms^-@kdn5#+ZQ(r)eMmgrjGz!aOem1Rw`dJ>Of!{xech(*-^Y;+if9_}xxgtdn z8^`AB4%4=KhBc=iN7s}d<2dg;`NhA=FCa!2JwEeOZ|7f8KtIgexlV6W8wX-Vd1ff| zsVl2#maeA{(sp`~4$_nKBE3q(h2@3DLgU@tKhWOHn_+ca;O1;BD6w%7C)uJmu{lbgIL6E^|$niggq!x>Zm$TD4AS>apW$VhsXjvxq{_K+{=m#xO*i#FAeO&Wr}_jT511U%4{)z9gQ-KK zGQ7Z}j;Dc#xbtaJ2Wr=Ye(&r3bh0qJpWyae00zu~OyNnQk&{G2CyB;R5)Gat8a+ug ze3EGVa0L)RNi>3zXb2_I7$ODTIDU?F4i?Mt$s^C8fnSr@$*I)a`X3-GUzc-fOB4T2 zoamE+a@SQ-^npgHC4N%@LR*Fh^jHz#M@&0&@iB S2+R?fBQQr`j==YZz<&XC|4F9+ literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-null-value.realm b/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes-null-value.realm new file mode 100644 index 0000000000000000000000000000000000000000..4adbd7c5282de3026da9e60fa5d91aaba138fc6d GIT binary patch literal 8192 zcmeHLJ!~V#6@Ih3TymG(J<1kh0dc`11)BgWRjz$XtN;n3_-sI_PQ24P6hMgtB?r3d z))(1kehQUWsz||fDpIHvf)pwRS63cVT@_q;o$`HeXZUw_hMfeiIKa;1`<*x6dpkpN z@Q&zcuaxwYpKg8_$08qKq$0J0{J68%IqsC&*XH*g_BuyL_kZ#0dmr?ApFA4mNAo7> zM1Qxt-`ks4f?Fx|_wV0joY&Oyj?-`F!-M^!qy7HjoKfQRALpIk!JJB&iT}s_UjDjT z-gW+;9_7O~>4^j6hO`0t`+TTLkau!Neg9R^04!K*;PC8 znT;QU-rn9mc6Qa0hv@dP^=oV)(!dxg@c1$^PmND=jf+o7Q{-LH zdw2Ui^}$p->h_1nQ$cnAaE23!di}%SP1RB(|I4W?_^j9O%y?Ay`kz1O`D`tM5XFl3(S7p2Y*wNakAQ;$CI zcm1wEK%(SpRt{pwgE~9!%zrwubMkDePmQ$vw!iLg_?f@uZ~Hs`i9hns{R{t68-($I zkH*Up|HVu^2Wxgz#B=4)Vak_ryCb zC9mg=dW2bJyRuuM#Xx8JW#DG1>lfGuM#hzk%4NmsbzRikXRq-g<{*>x+56;O|GRq7 zAMqMzSoNw>k(&z7nP-)kn#E+l-Zyl{pgmqX&+fMriqr>_^IA{!fOf#alU8X$9yp*q zyemqKB-f;rWBqVF%)%}kR!(%L-0e4;+F^g=@B$XpL++7+^|)WG=k`mU0~n-=2kqD& zXBr)bc3fH>x#_sJJTc^O{c&UE&lmhlEAA&K=)-m36TiQ*vhr`sqg86dH{VCj{l^bG zhq%)A9KT4{p#OBh!*fwn@;`!pGwgpWL z)SG>^TOCwS*hq4wGuO+&ky84Gvc;EO{LgOOe#tWda9Yr#T_`aV+W8})gb&;cY zQ?knQeP8@u@wWHfv`R;tKd*y~>n^Mxw&L}82j4qIy7A(@n?Xu{D1Mh&nh9m^J=)?7xm+L?G-l9LRn4c$& zf0ELU_}(}mA>*j=OA-dZxN)>BPah74Z`;ZKj!aa*RK_O?oRGy zu?G2O9He8vNwZ|EjC0ogMLOw%$QsC$lNA@ot4$8T{ z!_n1uwywml?SLxXWXJM%EZ?>KB7OraA1(M>H{pFnteFS)SsR;YK{I?6N?faV8)MBs zXB#(5IqhI>z7Iv|U0S*hV4UM!JP7gYrW}lqeEd<`_0LdK^5|nj3JbmOogeJH%k(OB zc21sId+1YNHqtEJO1IOUbT{p$C+T^5kzS|I(&ySSjEkDM<#j#m+iTu2;^HDs(sF=f zk@+!=13TcjyYeO+uUD*m-uVaTxL^GAe`N#**Q(U{A{rBmSuHRE;~75|AIzv|n7~wG zsiqp@Mjv2Odx`7s{H;_nb=Uut!of}gn#()cY3fEohnB9KHY zA&Hhk60LmuIWBiBY z{!|2KFxQI31BdTY;Z@(#xx?3p;O!+#6UE*_kLisOJfe0F%a^_O&sz!HHa c0!svz2rLm;BCteYiNF$pB?3zX{yPNz1N=2NE&u=k literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes.realm b/realm/realm-library/src/androidTest/assets/ios/6.0.0-beta.2-alltypes.realm new file mode 100644 index 0000000000000000000000000000000000000000..3e650855656bb22849ef8755e01b5f99954b4788 GIT binary patch literal 16384 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 XK#f3+K#f3+K#f3+K#jmpguwp*2jMV! literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/assets/ios/README.md b/realm/realm-library/src/androidTest/assets/ios/README.md index b385f78bb6..3b92e6a30e 100644 --- a/realm/realm-library/src/androidTest/assets/ios/README.md +++ b/realm/realm-library/src/androidTest/assets/ios/README.md @@ -6,8 +6,10 @@ Realm-Android. The databases are generated using the below iOS code. ### HOWTO 1. Checkout realm-cocoa. -2. Open ~/realm-cocoa/RealmExamples.xcodeproj in Xcode. -3. Rename `/Simple/AppDelegate.m` to `/Simple/AppDelegate.mm` and replace the content with the below code. +2. `cd examples/ios/objc` +3. `pod install` +4. `open RealmExamples.xcworkspace` in XCode +5. Rename `/Simple/AppDelegate.m` to `/Simple/AppDelegate.mm` and replace the content with the below code. 4. Run Simple project. 5. Copy/paste output Realm files into Java unit tests asset directory. @@ -22,24 +24,6 @@ Realm-Android. The databases are generated using the below iOS code. See the Log for where the output files are located. ```objective-c -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - #import "AppDelegate.h" #import #include @@ -90,7 +74,7 @@ RLM_ARRAY_TYPE(AllTypes) + (RLMRealm *)appDefaultRealm:(NSString *) realmName { NSString* allTypesRealm = [AppDelegate getRealmFilePath:realmName]; [[NSFileManager defaultManager] removeItemAtPath:allTypesRealm error:nil]; - RLMRealm *realm = [RLMRealm realmWithPath:allTypesRealm]; + RLMRealm *realm = [RLMRealm realmWithURL:[NSURL fileURLWithPath:allTypesRealm]]; return realm; } @@ -102,7 +86,7 @@ RLM_ARRAY_TYPE(AllTypes) NSLog(@"Documents Directory: %@", [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]); - const NSString *version = @"0.98.0"; + const NSString *version = @"6.0.0-beta.2"; const unsigned char no_bytes[] = {}; const unsigned char bytes[] = {1,2,3}; @@ -196,7 +180,7 @@ RLM_ARRAY_TYPE(AllTypes) NSData *keyData = [[NSData alloc] initWithBytes:buffer length:sizeof(buffer)]; // Zerofilled byte array NSError *error; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; - config.path = [AppDelegate getRealmFilePath:[NSString stringWithFormat:@"%@-alltypes-default-encrypted.realm", version]]; + config.fileURL = [NSURL fileURLWithPath:[AppDelegate getRealmFilePath:[NSString stringWithFormat:@"%@-alltypes-default-encrypted.realm", version]]]; config.encryptionKey = keyData; config.readOnly = NO; realm = [RLMRealm realmWithConfiguration:config error:&error]; @@ -212,4 +196,5 @@ RLM_ARRAY_TYPE(AllTypes) return YES; } @end + ``` \ No newline at end of file diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index 4eace7ac15..67a431475c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -22,7 +22,6 @@ import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,16 +45,12 @@ * This class test interoperability with Realms created on iOS. */ @RunWith(AndroidJUnit4.class) -@Ignore("__CORE6__: asset file, Upgrade interrupted https://github.com/realm/realm-core-private/issues/201 also need " + - "to regenerate the iOS Realm files using the realm-java/realm/realm-library/src/androidTest/assets/ios/README.md" + - "Generate iOS files once Cocoa complets the migration to Core6") -//FIXME this is using primarily Realm files of format version 3 now we have sync to test interop between platform ... these tests should be disabled public class IOSRealmTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - private static final String[] IOS_VERSIONS = new String[] {"0.98.0"}; + private static final String[] IOS_VERSIONS = new String[] {"6.0.0-beta.2"}; private static final String REALM_NAME = "alltypes.realm"; private Realm realm; private Context context; @@ -162,7 +157,7 @@ public void iOSDataTypesMinimumValues() throws IOException { assertFalse(obj.isBoolCol()); assertEquals(Short.MIN_VALUE, obj.getShortCol()); assertEquals(Integer.MIN_VALUE, obj.getIntCol()); - assertEquals(Integer.MIN_VALUE, obj.getLongCol()); + assertEquals(Long.MIN_VALUE, obj.getLongCol()); assertEquals(Long.MIN_VALUE, obj.getLongLongCol()); assertEquals(-Float.MAX_VALUE, obj.getFloatCol(), 0F); assertEquals(-Double.MAX_VALUE, obj.getDoubleCol(), 0D); @@ -173,7 +168,6 @@ public void iOSDataTypesMinimumValues() throws IOException { } @Test - @SuppressWarnings("ConstantOverflow") public void iOSDataTypesMaximumValues() throws IOException { for (String iosVersion : IOS_VERSIONS) { configFactory.copyRealmFromAssets(context, @@ -183,13 +177,13 @@ public void iOSDataTypesMaximumValues() throws IOException { IOSAllTypes obj = realm.where(IOSAllTypes.class).findFirst(); assertEquals(Short.MAX_VALUE, obj.getShortCol()); assertEquals(Integer.MAX_VALUE, obj.getIntCol()); - assertEquals(Integer.MAX_VALUE, obj.getLongCol()); + assertEquals(Long.MAX_VALUE, obj.getLongCol()); assertEquals(Long.MAX_VALUE, obj.getLongLongCol()); assertEquals(Float.MAX_VALUE, obj.getFloatCol(), 0F); assertEquals(Double.MAX_VALUE, obj.getDoubleCol(), 0D); assertArrayEquals(new byte[0], obj.getByteCol()); assertEquals("", obj.getStringCol()); - assertEquals(Long.MIN_VALUE, obj.getDateCol().getTime()); + assertEquals(Long.MAX_VALUE, obj.getDateCol().getTime()); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/IOSAllTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/IOSAllTypes.java index 771035d7c7..9b4424065b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/IOSAllTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/IOSAllTypes.java @@ -32,7 +32,7 @@ public class IOSAllTypes extends RealmObject { private boolean boolCol; private short shortCol; private int intCol; - private int longCol; + private long longCol; private long longLongCol; private float floatCol; private double doubleCol; @@ -74,11 +74,11 @@ public void setIntCol(int intCol) { this.intCol = intCol; } - public int getLongCol() { + public long getLongCol() { return longCol; } - public void setLongCol(int longCol) { + public void setLongCol(long longCol) { this.longCol = longCol; } From d4a5db37c1b6254af17c7367a5f937c6f2de5443 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 7 Jan 2020 16:47:55 +0100 Subject: [PATCH 1459/2110] Update ReLinker (#6710) --- CHANGELOG.md | 2 ++ realm/realm-library/build.gradle | 2 +- realm/realm-library/src/androidTest/java/ThreadStressTests.java | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f665b57e..8a6af66257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Enhancements * The Realm Gradle plugin now applies `kapt` when used in Kotlin Multiplatform projects. Note, Realm Java still only works for the Android part of a Kotlin Multiplatform project. (Issue [#6653](https://github.com/realm/realm-java/issues/6653)) +* The error message shown when no native code could be found for the device is now much more descriptive. This is particular helpful if an app is using App Bundle or APK Split and the resulting APK was side-loaded outside the Google Play Store. (Issue [#6673](https://github.com/realm/realm-java/issues/6673)) ### Fixed * None. @@ -12,6 +13,7 @@ * APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. ### Internal +* Updated to ReLinker 1.4.0. * Updated to Object Store commit: ad96a4c334b475dd67d50c1ca419e257d7a21e18. * Updated to Realm Sync v4.8.3. diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 5ba2270ef9..6c2d5fd6e6 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -209,7 +209,7 @@ dependencies { api "io.realm:realm-annotations:${version}" implementation 'com.google.code.findbugs:jsr305:3.0.2' - implementation 'com.getkeepsafe.relinker:relinker:1.3.0' + implementation 'com.getkeepsafe.relinker:relinker:1.4.0' kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 objectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' diff --git a/realm/realm-library/src/androidTest/java/ThreadStressTests.java b/realm/realm-library/src/androidTest/java/ThreadStressTests.java index 7ccf932331..feeb9b1a51 100644 --- a/realm/realm-library/src/androidTest/java/ThreadStressTests.java +++ b/realm/realm-library/src/androidTest/java/ThreadStressTests.java @@ -122,7 +122,7 @@ public void setUp() { } realmConfig = configFactory.createConfiguration(); Realm.deleteRealm(realmConfig); - executor = Executors.newFixedThreadPool(reuseThreads ? random.nextInt(MAX_THREADS) : MAX_THREADS); + executor = Executors.newFixedThreadPool(reuseThreads ? Math.max(random.nextInt(MAX_THREADS), 1) : MAX_THREADS); } @After From 3270ffacaa0c397d73d5ae341dcffc64868ad7ce Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 13 Jan 2020 12:40:34 +0100 Subject: [PATCH 1460/2110] Remove all currently deprecated methods (#6697) --- CHANGELOG.md | 11 ++- .../MainActivity.java | 2 +- .../OrderedRealmCollectionIteratorTests.java | 2 +- .../java/io/realm/RealmInMemoryTest.java | 4 +- .../java/io/realm/RealmObjectTests.java | 2 +- .../io/realm/internal/OsResultsTests.java | 1 - .../java/io/realm/CredentialsTests.java | 21 ----- .../java/io/realm/SyncConfigurationTests.java | 20 +--- .../src/main/java/io/realm/BaseRealm.java | 4 - .../src/main/java/io/realm/DynamicRealm.java | 2 +- .../java/io/realm/MutableRealmInteger.java | 4 +- .../src/main/java/io/realm/Realm.java | 6 +- .../main/java/io/realm/RealmCollection.java | 4 +- .../src/main/java/io/realm/RealmObject.java | 4 +- ...gableObject.java => ManageableObject.java} | 2 +- .../java/io/realm/internal/OsSharedRealm.java | 16 ++-- .../objectServer/java/io/realm/ErrorCode.java | 13 --- .../java/io/realm/SyncConfiguration.java | 94 ------------------- .../java/io/realm/SyncCredentials.java | 28 ------ .../java/io/realm/SyncManager.java | 2 +- .../java/io/realm/SyncSession.java | 9 +- .../objectServer/java/io/realm/SyncUser.java | 5 +- .../io/realm/SyncedRealmIntegrationTests.java | 6 +- .../java/io/realm/objectserver/AuthTests.java | 52 ---------- ...ObjectLevelPermissionIntegrationTests.java | 2 +- .../realm/objectserver/utils/UserFactory.java | 5 - 26 files changed, 39 insertions(+), 282 deletions(-) rename realm/realm-library/src/main/java/io/realm/internal/{ManagableObject.java => ManageableObject.java} (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b444bc168..86e94d3f49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,16 @@ Based on v6.0.2. NOTE: This version bumps the Realm file format to version 10. It is not possible to downgrade version 9 or earlier. Files created with older versions of Realm will be automatically upgraded. ### Breaking Changes +* [ObjectServer] Removed deprecated method `SyncConfiguration.Builder.partialRealm()`. Use `SyncConfiguration.Builder.fullSynchronization()` instead. +* [ObjectServer] Removed deprecated methods `SyncConfiguration.automatic()` and `SyncConfiguration.automatic(User, Uri)`. Use `SyncUser.getDefaultConfiguration()` and `SyncUser.createConfiguration(Url)`. +* [ObjectServer] Removed deprecated method `ErrorCode.fromInt(int)`. +* [ObjectServer] Removed deprecated method `SyncCredentials.nickname(name)` and `SyncCredentials.nickname(name, isAdmin)`. Use `SyncCredentials.usernamePassword(username, password)` instead. +* [ObjectServer] Deprecated state `SyncSession.State.ERROR` has been removed. Use `SyncConfiguration.Builder.errorHandler(ErrorHandler)` instead. +* [ObjectServer] `IncompatibleSyncedFileException` is removed as it is no longer used. * RxJava Flowables and Observables are now subscribed to and unsubscribed to asynchronously on the thread holding the live Realm, instead of previously where this was done synchronously. -* All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(true|false))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. +* All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(boolean))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. * MIPS is not supported anymore. * Realm now requires `minSdkVersion` 16. Up from 9. -* `IncompatibleSyncedFileException` is removed as it is no longer used. -* [ObjectServer] Query-based Sync is now the default mode of synchronization. To enable Full Realm synchronization use `SyncConfiguration.Builder.fullSynchronization()`. `SyncConfiguration.Builder.partialRealm()` has been deprecated. -* [ObjectServer] `SyncConfiguration.isPartialRealm()` has been replaced by `SyncConfiguration.isFullySynchronizedRealm()`. ### Enhancements * Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java index ecce92c122..2763c44b44 100644 --- a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java +++ b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java @@ -90,7 +90,7 @@ private void buildSyncConf() { SyncUser.logInAsync(credentials, urlAuth, new SyncUser.Callback() { @Override public void onSuccess(SyncUser user) { - SyncConfiguration secureConfig = new SyncConfiguration.Builder(user, url).build(); + SyncConfiguration secureConfig = user.createConfiguration(url).build(); Realm realm = Realm.getInstance(secureConfig); // ... } diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index a1bfd55443..1554c9a0ce 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -845,7 +845,7 @@ public void run() { } }).start(); TestHelper.awaitOrFail(bgDone); - realm.waitForChange(); + realm.refresh(); try { it.next(); assertEquals(TEST_SIZE, collection.size()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index 9f7f26123d..0b48697da2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -35,8 +35,8 @@ import io.realm.exceptions.RealmFileException; import io.realm.rule.TestRealmConfigurationFactory; -import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -236,7 +236,7 @@ public void run() { if (threadError[0] != null) { throw threadError[0]; } // Refreshes will be ran in the next loop, manually refreshes it here. - testRealm.waitForChange(); + testRealm.refresh(); assertEquals(1, testRealm.where(Dog.class).count()); // Step 3. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index ce0b20bb71..dee26d5ef6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -1224,7 +1224,7 @@ public void run() { } }).start(); TestHelper.awaitOrFail(bgRealmDone); - realm.waitForChange(); + realm.refresh(); // Object should no longer be available. assertFalse(obj.isValid()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java index d04bd2c8cc..1c57bf704e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java @@ -356,7 +356,6 @@ public void onChange(OsResults element) { addRowAsync(sharedRealm); - sharedRealm.waitForChange(); sharedRealm.refresh(); TestHelper.awaitOrFail(latch); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java index d16d0f5bc7..745b2a940c 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java @@ -24,7 +24,6 @@ import java.util.Map; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -115,26 +114,6 @@ public void anonymous() { assertTrue(creds.getUserInfo().isEmpty()); } - @Test - public void nickname() { - SyncCredentials creds = SyncCredentials.nickname("foo", false); - assertEquals(SyncCredentials.IdentityProvider.NICKNAME, creds.getIdentityProvider()); - assertFalse(creds.getUserInfo().isEmpty()); - assertFalse((Boolean) creds.getUserInfo().get("is_admin")); - } - - @Test - public void nickname_invalidInput() { - String[] invalidInput = {null, ""}; - for (String input : invalidInput) { - try { - SyncCredentials.nickname(input, false); - fail(input + " should have failed"); - } catch (IllegalArgumentException ignored) { - } - } - } - @Test public void usernamePassword_register() { SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar", true); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 5aa50218f6..4c3fccc2c8 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -76,15 +76,10 @@ public void tearDown() { @Test public void user_invalidUserThrows() { - try { - new SyncConfiguration.Builder(null, "realm://ros.realm.io/default"); - } catch (IllegalArgumentException ignore) { - } - SyncUser user = createTestUser(0); // Create user that has expired credentials try { - new SyncConfiguration.Builder(user, "realm://ros.realm.io/default"); - } catch (IllegalArgumentException ignore) { + user.createConfiguration("realm://ros.realm.io/default"); + } catch (IllegalStateException ignore) { } } @@ -482,17 +477,6 @@ public void getDefaultConfiguration_throwsIfNotLoggedIn() { } } - @Test - public void automatic_isFullySynchronized() { - SyncUser user = SyncTestUtils.createTestUser(); - - SyncConfiguration config = SyncConfiguration.automatic(); - assertFalse(config.isFullySynchronizedRealm()); - - config = SyncConfiguration.automatic(user); - assertFalse(config.isFullySynchronizedRealm()); - } - @Test public void getDefaultConfiguration_isFullySynchronized() { SyncUser user = createTestUser(); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index e91c245c1b..5037024d8c 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -345,9 +345,7 @@ public void writeEncryptedCopyTo(File destination, byte[] key) { * @throws IllegalStateException if calling this from within a transaction or from a Looper thread. * @throws RealmMigrationNeededException on typed {@link Realm} if the latest version contains * incompatible schema changes. - * @deprecated this method will be removed on the next-major release. */ - @Deprecated public boolean waitForChange() { checkIfValid(); if (isInTransaction()) { @@ -372,9 +370,7 @@ public boolean waitForChange() { * called waitForChange. * * @throws IllegalStateException if the {@link io.realm.Realm} instance has already been closed. - * @deprecated this method will be removed in the next-major release */ - @Deprecated public void stopWaitForChange() { if (realmCache != null) { realmCache.invokeWithLock(new RealmCache.Callback0() { diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 9913954226..7b9e0b7289 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -195,7 +195,7 @@ public RealmQuery where(String className) { * @see io.realm.RealmChangeListener * @see #removeChangeListener(RealmChangeListener) * @see #removeAllChangeListeners() - * @see #waitForChange() + * @see #refresh() */ public void addChangeListener(RealmChangeListener listener) { addListener(listener); diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmInteger.java b/realm/realm-library/src/main/java/io/realm/MutableRealmInteger.java index 31ddcaac33..db43a9dedd 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmInteger.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmInteger.java @@ -18,7 +18,7 @@ import javax.annotation.Nullable; import io.realm.annotations.Beta; -import io.realm.internal.ManagableObject; +import io.realm.internal.ManageableObject; import io.realm.internal.Row; import io.realm.internal.Table; @@ -91,7 +91,7 @@ * Neither can be GCed until all references to both are unreachable. */ @Beta -public abstract class MutableRealmInteger implements Comparable, ManagableObject { +public abstract class MutableRealmInteger implements Comparable, ManageableObject { /** * Unmanaged Implementation. diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index d9fb0123e0..b6ded6e885 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -99,7 +99,7 @@ * onStart/onStop. *

            * Realm instances coordinate their state across threads using the {@link android.os.Handler} mechanism. This also means - * that Realm instances on threads without a {@link android.os.Looper} cannot receive updates unless {@link #waitForChange()} + * that Realm instances on threads without a {@link android.os.Looper} cannot receive updates unless {@link #refresh()} * is manually called. *

            * A standard pattern for working with Realm in Android activities can be seen below: @@ -655,7 +655,6 @@ public void createOrUpdateAllFromJson(Class clazz, Str * {@link RealmObjectSchema} has a {@link io.realm.annotations.PrimaryKey} defined. * @throws IOException if something was wrong with the input stream. */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void createAllFromJson(Class clazz, InputStream inputStream) throws IOException { //noinspection ConstantConditions if (clazz == null || inputStream == null) { @@ -695,7 +694,6 @@ public void createAllFromJson(Class clazz, InputStream * @throws RealmException if unable to read JSON. * @see #createOrUpdateAllFromJson(Class, java.io.InputStream) */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void createOrUpdateAllFromJson(Class clazz, InputStream in) { //noinspection ConstantConditions if (clazz == null || in == null) { @@ -873,7 +871,6 @@ public E createOrUpdateObjectFromJson(Class clazz, Str * @throws IOException if something went wrong with the input stream. */ @Nullable - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public E createObjectFromJson(Class clazz, InputStream inputStream) throws IOException { //noinspection ConstantConditions if (clazz == null || inputStream == null) { @@ -931,7 +928,6 @@ public E createObjectFromJson(Class clazz, InputStream * @throws RealmException if failure to read JSON. * @see #createObjectFromJson(Class, java.io.InputStream) */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public E createOrUpdateObjectFromJson(Class clazz, InputStream in) { //noinspection ConstantConditions if (clazz == null || in == null) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmCollection.java b/realm/realm-library/src/main/java/io/realm/RealmCollection.java index cb9922af71..629218e59b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCollection.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCollection.java @@ -22,7 +22,7 @@ import javax.annotation.Nullable; -import io.realm.internal.ManagableObject; +import io.realm.internal.ManageableObject; /** @@ -35,7 +35,7 @@ * * @param type of {@link RealmObject} stored in the collection. */ -public interface RealmCollection extends Collection, ManagableObject { +public interface RealmCollection extends Collection, ManageableObject { /** * Returns a {@link RealmQuery}, which can be used to query for specific objects from this collection. diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index bd323f640b..1b2131324a 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -24,7 +24,7 @@ import io.reactivex.Observable; import io.realm.annotations.RealmClass; import io.realm.internal.InvalidRow; -import io.realm.internal.ManagableObject; +import io.realm.internal.ManageableObject; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.log.RealmLog; @@ -71,7 +71,7 @@ */ @RealmClass -public abstract class RealmObject implements RealmModel, ManagableObject { +public abstract class RealmObject implements RealmModel, ManageableObject { static final String MSG_NULL_OBJECT = "'model' is null."; static final String MSG_DELETED_OBJECT = "the object is already deleted."; static final String MSG_DYNAMIC_OBJECT = "the object is an instance of DynamicRealmObject. Use DynamicRealmObject.getDynamicRealm() instead."; diff --git a/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java b/realm/realm-library/src/main/java/io/realm/internal/ManageableObject.java similarity index 97% rename from realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java rename to realm/realm-library/src/main/java/io/realm/internal/ManageableObject.java index 5d7f015271..403e98d4fe 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ManageableObject.java @@ -19,7 +19,7 @@ * This internal interface represents a java object that corresponds to data * that may be managed in the Realm core. It specifies the operations common to all such objects. */ -public interface ManagableObject { +public interface ManageableObject { /** * Checks to see if this object is managed by Realm.. diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index d8ddcf0e5d..314d7a5e64 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -397,14 +397,6 @@ public void writeCopy(File file, @Nullable byte[] key) { nativeWriteCopy(nativePtr, file.getAbsolutePath(), key); } - public boolean waitForChange() { - return nativeWaitForChange(nativePtr); - } - - public void stopWaitForChange() { - nativeStopWaitForChange(nativePtr); - } - public boolean compact() { return nativeCompact(nativePtr); } @@ -414,6 +406,14 @@ public void setAutoRefresh(boolean enabled) { nativeSetAutoRefresh(nativePtr, enabled); } + public boolean waitForChange() { + return nativeWaitForChange(nativePtr); + } + + public void stopWaitForChange() { + nativeStopWaitForChange(nativePtr); + } + public boolean isAutoRefresh() { return nativeIsAutoRefresh(nativePtr); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index ae521ca8d5..9b488a395f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -264,19 +264,6 @@ public static ErrorCode fromNativeError(String type, int errorCode) { return UNKNOWN; } - @Deprecated - public static ErrorCode fromInt(int errorCode) { - ErrorCode[] errorCodes = values(); - for (int i = 0; i < errorCodes.length; i++) { - ErrorCode error = errorCodes[i]; - if (error.intValue() == errorCode) { - return error; - } - } - RealmLog.warn("Unknown error code: " + errorCode); - return UNKNOWN; - } - /** * Helper method for mapping between {@link Exception} and {@link ErrorCode}. * @param exception to be mapped as an {@link ErrorCode}. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index d1c7e917d9..a31b9a11db 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -233,49 +233,6 @@ static URI resolveServerUrl(URI serverUrl, String userIdentifier) { } } - /** - * Creates an automatic default configuration based on the the currently logged in user. - *

            - * This configuration will point to the default Realm on the server where the user was - * authenticated. - * - * @throws IllegalStateException if no user are logged in, or multiple users have. Only one should - * be logged in when calling this method. - * @return The constructed {@link SyncConfiguration}. - * @deprecated use {@link SyncUser#getDefaultConfiguration()} instead. - */ - @Deprecated - @Beta - public static SyncConfiguration automatic() { - SyncUser user = SyncUser.current(); - if (user == null) { - throw new IllegalStateException("No user was logged in."); - } - return user.getDefaultConfiguration(); - } - - /** - * Creates an automatic default configuration for the provided user. - *

            - * This configuration will point to the default Realm on the server where the user was - * authenticated. - * - * @throws IllegalArgumentException if no user was provided or the user isn't valid. - * @return The constructed {@link SyncConfiguration}. - * @deprecated use {@link SyncUser#getDefaultConfiguration()} instead. - */ - @Deprecated - @Beta - public static SyncConfiguration automatic(SyncUser user) { - if (user == null) { - throw new IllegalArgumentException("Non-null 'user' required."); - } - if (!user.isValid()) { - throw new IllegalArgumentException("User is no logger valid. Log the user in again."); - } - return user.getDefaultConfiguration(); - } - // Extract the full server path, minus the file name private static String getServerPath(URI serverUrl) { String path = serverUrl.getPath(); @@ -539,41 +496,6 @@ public static final class Builder { private ClientResyncMode clientResyncMode = null; private long maxNumberOfActiveVersions = Long.MAX_VALUE; - /** - * Creates an instance of the Builder for the SyncConfiguration. This SyncConfiguration - * will be for a fully synchronized Realm. - *

            - * Opening a synchronized Realm requires a valid user and an unique URI that identifies that Realm. In URIs, - * {@code /~/} can be used as a placeholder for a user ID in case the Realm should only be available to one - * user e.g., {@code "realm://objectserver.realm.io/~/default"}. - *

            - * The URL cannot end with {@code .realm}, {@code .realm.lock} or {@code .realm.management}. - *

            - * The {@code /~/} will automatically be replaced with the user ID when creating the {@link SyncConfiguration}. - *

            - * Moreover, the URI defines the local location on disk. The default location of a synchronized Realm file is - * {@code /data/data//files/realm-object-server//}, but this behavior - * can be overwritten using {@link #name(String)} and {@link #directory(File)}. - *

            - * Many Android devices are using FAT32 file systems. FAT32 file systems have a limitation that - * file names cannot be longer than 255 characters. Moreover, the entire URI should not exceed 256 characters. - * If file name and underlying path are too long to handle for FAT32, a shorter unique name will be generated. - * See also @{link https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx}. - * - * @param user the user for this Realm. An authenticated {@link SyncUser} is required to open any Realm managed - * by a Realm Object Server. - * @param uri URI identifying the Realm. If only a path like {@code /~/default} is given, the configuration will - * assume the file is located on the same server returned by {@link SyncUser#getAuthenticationUrl()}. - * - * @see SyncUser#isValid() - * @deprecated Use {@link SyncUser#createConfiguration(String)} instead. - */ - @Deprecated - public Builder(SyncUser user, String uri) { - this(BaseRealm.applicationContext, user, uri); - fullSynchronization(); - } - Builder(Context context, SyncUser user, String url) { //noinspection ConstantConditions if (context == null) { @@ -1034,22 +956,6 @@ public SyncConfiguration.Builder readOnly() { return this; } - /** - * Define this Realm as a fully synchronized Realm. - *

            - * Full synchronization, unlike the default query-based synchronization, will transparently - * synchronize the entire Realm without needing to query for the data. This option is - * useful if the serverside Realm is small and all the data in the Realm should be - * available to the user. - * - * @see #isFullySynchronizedRealm() () - */ - @Deprecated - public SyncConfiguration.Builder partialRealm() { - this.isPartial = true; - return this; - } - /** * Define this Realm as a fully synchronized Realm. *

            diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java index dc22d2def6..dcc13d3912 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java @@ -125,27 +125,6 @@ public static SyncCredentials anonymous() { return new SyncCredentials("", IdentityProvider.ANONYMOUS, null); } - /** - * Creates credentials using a nickname. - * - * Note: This is mainly intended for demo/test, since it's ie. possible to log user - * in by just knowing their "nickname" (no password required). - * This provider should not be used in production. - * - * @param nickname that identifies a user - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. - * @throws IllegalArgumentException if the nickname is either {@code null} or empty. - * @deprecated Use {@link SyncCredentials#usernamePassword(String, String)} instead. - */ - @Deprecated - public static SyncCredentials nickname(String nickname, boolean isAdmin) { - assertStringNotEmpty(nickname, "nickname"); - Map userInfo = new HashMap(); - userInfo.put("is_admin", isAdmin); - return new SyncCredentials(nickname, IdentityProvider.NICKNAME, userInfo); - } - /** * Creates credentials based on a login with username and password. These credentials will only be verified * by the Object Server. @@ -323,13 +302,6 @@ public static final class IdentityProvider { */ public static final String ANONYMOUS = "anonymous"; - /** - * Credentials will be verified with a nickname. - * @deprecated Use {@link IdentityProvider#USERNAME_PASSWORD} instead. - */ - @Deprecated - public static final String NICKNAME = "nickname"; - /** * Credentials will be verified by the Object Server. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 27f5e4975b..55c32305f7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -467,7 +467,7 @@ static List getAllSessions(SyncUser syncUser) { } ArrayList allSessions = new ArrayList(); for (SyncSession syncSession : sessions.values()) { - if (syncSession.getState() != SyncSession.State.ERROR && syncSession.getUser().equals(syncUser)) { + if (syncSession.getUser().equals(syncUser)) { allSessions.add(syncSession); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index b4bbb88574..43a77d53b0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -111,7 +111,6 @@ public class SyncSession { private static final byte STATE_VALUE_ACTIVE = 1; private static final byte STATE_VALUE_DYING = 2; private static final byte STATE_VALUE_INACTIVE = 3; - private static final byte STATE_VALUE_ERROR = 4; // List of Java connection change listeners private final CopyOnWriteArrayList connectionListeners = new CopyOnWriteArrayList<>(); @@ -168,13 +167,7 @@ public enum State { * The Realm was closed, but still contains data that needs to be synchronized to the server. * The session will attempt to upload all local data before going {@link #INACTIVE}. */ - DYING(STATE_VALUE_DYING), - - /** - * DEPRECATED: This is never used. Errors are reported to {@link ErrorHandler} instead. - */ - @Deprecated - ERROR(STATE_VALUE_ERROR); + DYING(STATE_VALUE_DYING); final byte value; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index f40b33f594..d8cdc0847f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -261,7 +261,7 @@ public SyncConfiguration.Builder createConfiguration(String uri) { if (!isValid()) { throw new IllegalStateException("Configurations can only be created from valid users"); } - return new SyncConfiguration.Builder(this, uri).partialRealm(); + return new SyncConfiguration.Builder(Realm.applicationContext, this, uri); } /** @@ -276,8 +276,7 @@ public SyncConfiguration getDefaultConfiguration() { throw new IllegalStateException("The default configuration can only be created for users that are logged in."); } if (defaultConfiguration == null) { - defaultConfiguration = new SyncConfiguration.Builder(this, createUrl(this)) - .partialRealm() + defaultConfiguration = new SyncConfiguration.Builder(Realm.applicationContext, this, createUrl(this)) .build(); } return defaultConfiguration; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 2ac3f5a273..5995ae7beb 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -330,7 +330,7 @@ public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { @Test public void defaultRealm() throws InterruptedException { - SyncCredentials credentials = SyncCredentials.nickname("test", false); + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); SyncConfiguration config = user.getDefaultConfiguration(); Realm realm = Realm.getInstance(config); @@ -366,7 +366,7 @@ public void javaRequestCustomHeaders_specificHost() { } private void runJavaRequestCustomHeadersTest() { - SyncCredentials credentials = SyncCredentials.nickname("test", false); + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); AtomicBoolean headerSet = new AtomicBoolean(false); RealmLog.setLevel(LogLevel.ALL); @@ -420,7 +420,7 @@ public void syncAuthHeaderAndUrlPrefix_specificHost() { } private void runSyncAuthHeadersAndUrlPrefixTest() { - SyncCredentials credentials = SyncCredentials.nickname("test", false); + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM) .urlPrefix("/foo") diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 7c6c74d3ee..8914ac1566 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -166,58 +166,6 @@ public void onError(ObjectServerError error) { }); } - @Test - @RunTestInLooperThread - public void login_withNickname() { - SyncCredentials credentials = SyncCredentials.nickname("foo", false); - SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - assertFalse(user.isAdmin()); - final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .errorHandler((session, error) -> fail("Session failed: " + error)) - .build(); - - final Realm realm = Realm.getInstance(config); - looperThread.closeAfterTest(realm); - assertFalse(Util.isEmptyString(config.getUser().getIdentity())); - assertTrue(config.getUser().isValid()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail("Login failed: " + error); - } - }); - } - - @Test - @RunTestInLooperThread - public void login_withNicknameAsAdmin() { - SyncCredentials credentials = SyncCredentials.nickname("foo", true); - SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - assertTrue(user.isAdmin()); - final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .errorHandler((session, error) -> fail("Session failed: " + error)) - .build(); - - final Realm realm = Realm.getInstance(config); - looperThread.closeAfterTest(realm); - assertFalse(Util.isEmptyString(config.getUser().getIdentity())); - assertTrue(config.getUser().isValid()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail("Login failed: " + error); - } - }); - } - @Test public void loginAsync_errorHandlerThrows() throws InterruptedException { final AtomicBoolean errorThrown = new AtomicBoolean(false); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java index 437957706f..4ba09b1c28 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java @@ -145,7 +145,7 @@ public void restrictAccessToOwner() throws InterruptedException { user1Realm.close(); // Connect with admin user and verify that user1 object is visible (non-partial Realm) - SyncUser adminUser = UserFactory.createNicknameUser(Constants.AUTH_URL, "admin2", true); + SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, Constants.DEFAULT_REALM) .fullSynchronization() .modules(schemaModules) diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java index cc32b5df20..6fe64e459c 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java @@ -99,11 +99,6 @@ public static SyncUser createAdminUser(String authUrl) { return SyncUser.logIn(credentials, authUrl); } - public static SyncUser createNicknameUser(String authUrl, String nickname, boolean isAdmin) { - SyncCredentials credentials = SyncCredentials.nickname(nickname, isAdmin); - return SyncUser.logIn(credentials, authUrl); - } - // Since we don't have a reliable way to reset the sync server and client, just use a new user factory for every // test case. public static void resetInstance() { From 9b7442ace84857561a677bf02df306b538018bf9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 17 Jan 2020 09:26:23 +0100 Subject: [PATCH 1461/2110] Add missing error codes (#6720) --- CHANGELOG.md | 1 + .../objectServer/java/io/realm/ErrorCode.java | 37 ++++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86e94d3f49..fa8403d117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * [ObjectServer] Removed deprecated method `SyncCredentials.nickname(name)` and `SyncCredentials.nickname(name, isAdmin)`. Use `SyncCredentials.usernamePassword(username, password)` instead. * [ObjectServer] Deprecated state `SyncSession.State.ERROR` has been removed. Use `SyncConfiguration.Builder.errorHandler(ErrorHandler)` instead. * [ObjectServer] `IncompatibleSyncedFileException` is removed as it is no longer used. +* [ObjectServer] New error codes thrown by the underlying sync layers now have proper enum mappings in `ErrorCode.java`. A few other errors have been renamed in order to have consistent naming. (Issue [#6387](https://github.com/realm/realm-java/issues/6387)) * RxJava Flowables and Observables are now subscribed to and unsubscribed to asynchronously on the thread holding the live Realm, instead of previously where this was done synchronously. * All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(boolean))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. * MIPS is not supported anymore. diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 9b488a395f..4220636b41 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -27,8 +27,7 @@ */ public enum ErrorCode { - // See Client::Error in https://github.com/realm/realm-sync/blob/master/src/realm/sync/client.hpp - // See https://github.com/realm/realm-object-server/blob/master/object-server/doc/problems.md + // See Client::Error in https://github.com/realm/realm-sync/blob/master/src/realm/sync/client.hpp#L1230 // See https://github.com/realm/realm-sync/blob/develop/src/realm/sync/protocol.hpp // Catch-all @@ -78,12 +77,15 @@ public enum ErrorCode { PARTIAL_SYNC_DISABLED(Type.PROTOCOL, 214), // Partial sync disabled (BIND) UNSUPPORTED_SESSION_FEATURE(Type.PROTOCOL, 215), // Unsupported session-level feature BAD_ORIGIN_FILE_IDENT(Type.PROTOCOL, 216), // Bad origin file identifier (UPLOAD) + BAD_CLIENT_FILE(Type.PROTOCOL, 217), // Synchronization no longer possible for client-side file + SERVER_FILE_DELETED(Type.PROTOCOL, 218), // Server file was deleted while session was bound to it + CLIENT_FILE_BLACKLISTED(Type.PROTOCOL, 219), // Client file has been blacklisted (IDENT) + USER_BLACKLISTED(Type.PROTOCOL, 220), // User has been blacklisted (BIND) + TRANSACT_BEFORE_UPLOAD(Type.PROTOCOL, 221), // Serialized transaction before upload completion + CLIENT_FILE_EXPIRED(Type.PROTOCOL, 222), // Client file has expired // Sync Network Client errors. - // TODO: All enums in here should be prefixed with `CLIENT_`, but in order to avoid - // breaking changes, this is not the case for all of them. This should be fixed in the - // next major release. - // See https://github.com/realm/realm-java/issues/6387 + // See https://github.com/realm/realm-sync/blob/master/src/realm/sync/client.hpp#L1230 CLIENT_CONNECTION_CLOSED(Type.SESSION, 100), // Connection closed (no error) CLIENT_UNKNOWN_MESSAGE(Type.SESSION, 101), // Unknown type of input message CLIENT_LIMITS_EXCEEDED(Type.SESSION, 103), // Limits exceeded in input message @@ -96,16 +98,26 @@ public enum ErrorCode { CLIENT_BAD_ORIGIN_FILE_IDENT(Type.SESSION, 110), // Bad origin file identifier in changeset header (DOWNLOAD) CLIENT_BAD_SERVER_VERSION(Type.SESSION, 111), // Bad server version in changeset header (DOWNLOAD) CLIENT_BAD_CHANGESET(Type.SESSION, 112), // Bad changeset (DOWNLOAD) - BAD_REQUEST_IDENT(Type.SESSION, 113), // Bad request identifier (MARK) - BAD_ERROR_CODE(Type.SESSION, 114), // Bad error code (ERROR) - BAD_COMPRESSION(Type.SESSION, 115), // Bad compression (DOWNLOAD) - BAD_CLIENT_VERSION_DOWNLOAD(Type.SESSION, 116), // Bad last integrated client version in changeset header (DOWNLOAD) - SSL_SERVER_CERT_REJECTED(Type.SESSION, 117), // SSL server certificate rejected - PONG_TIMEOUT(Type.SESSION, 118), // Timeout on reception of PONG respone message + CLIENT_BAD_REQUEST_IDENT(Type.SESSION, 113), // Bad request identifier (MARK) + CLIENT_BAD_ERROR_CODE(Type.SESSION, 114), // Bad error code (ERROR) + CLIENT_BAD_COMPRESSION(Type.SESSION, 115), // Bad compression (DOWNLOAD) + CLIENT_BAD_CLIENT_VERSION_DOWNLOAD(Type.SESSION, 116), // Bad last integrated client version in changeset header (DOWNLOAD) + CLIENT_SSL_SERVER_CERT_REJECTED(Type.SESSION, 117), // SSL server certificate rejected + CLIENT_PONG_TIMEOUT(Type.SESSION, 118), // Timeout on reception of PONG respone message CLIENT_BAD_CLIENT_FILE_IDENT_SALT(Type.SESSION, 119), // Bad client file identifier salt (IDENT) CLIENT_FILE_IDENT(Type.SESSION, 120), // Bad file identifier (ALLOC) CLIENT_CONNECT_TIMEOUT(Type.SESSION, 121), // Sync connection was not fully established in time CLIENT_BAD_TIMESTAMP(Type.SESSION, 122), // Bad timestamp (PONG) + CLIENT_BAD_PROTOCOL_FROM_SERVER(Type.SESSION, 123), // Bad or missing protocol version information from server + CLIENT_TOO_OLD_FOR_SERVER(Type.SESSION, 124), // Protocol version negotiation failed: Client is too old for server + CLIENT_TOO_NEW_FOR_SERVER(Type.SESSION, 125), // Protocol version negotiation failed: Client is too new for server + CLIENT_PROTOCOL_MISMATCH(Type.SESSION, 126), // Protocol version negotiation failed: No version supported by both client and server + CLIENT_BAD_STATE_MESSAGE(Type.SESSION, 127), // Bad values in state message (STATE) + CLIENT_MISSING_PROTOCOL_FEATURE(Type.SESSION, 128), // Requested feature missing in negotiated protocol version + CLIENT_BAD_SERIAL_TRANSACT_STATUS(Type.SESSION, 129), // Bad status of serialized transaction (TRANSACT) + CLIENT_BAD_OBJECT_ID_SUBSTITUTIONS(Type.SESSION, 130), // Bad encoded object identifier substitutions (TRANSACT) + CLIENT_HTTP_TUNNEL_FAILED(Type.SESSION, 131), // Failed to establish HTTP tunnel with configured proxy + // 300 - 599 Reserved for Standard HTTP error codes MULTIPLE_CHOICES(Type.HTTP, 300), @@ -290,7 +302,6 @@ public static class Type { public static final String UNKNOWN = "unknown"; // Catch-all category } - public enum Category { FATAL, // Abort session as soon as possible RECOVERABLE, // Still possible to recover the session by either rebinding or providing the required information. From ea772b530d1006407a7eff46bffb0049c91a3c43 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 17 Jan 2020 13:20:08 +0100 Subject: [PATCH 1462/2110] Upgrade to latest version of Sync and Core (#6723) --- CHANGELOG.md | 8 +++-- dependencies.list | 4 +-- .../java/io/realm/RealmResultsTests.java | 35 +++++++++++++------ .../src/main/cpp/io_realm_SyncManager.cpp | 16 +++++++-- ...m_internal_objectstore_OsObjectBuilder.cpp | 13 ++++++- .../src/main/cpp/java_accessor.hpp | 28 +++++++-------- .../src/main/cpp/java_object_accessor.hpp | 32 ++++++++--------- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/ObjectServer.java | 14 +++----- .../java/io/realm/SyncManager.java | 2 +- 10 files changed, 93 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6af66257..405990384a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,10 @@ ### Enhancements * The Realm Gradle plugin now applies `kapt` when used in Kotlin Multiplatform projects. Note, Realm Java still only works for the Android part of a Kotlin Multiplatform project. (Issue [#6653](https://github.com/realm/realm-java/issues/6653)) * The error message shown when no native code could be found for the device is now much more descriptive. This is particular helpful if an app is using App Bundle or APK Split and the resulting APK was side-loaded outside the Google Play Store. (Issue [#6673](https://github.com/realm/realm-java/issues/6673)) +* `RealmResults.asJson()` now encode binary data as Base64 and null object links are reported as `null` instead of `[]`. ### Fixed -* None. +* Fixed using `RealmList` with a primitive type sometimes crashing with `Destruction of mutex in use`. (Issue [#6689](https://github.com/realm/realm-java/issues/6689)) ### Compatibility * Realm Object Server: 3.23.1 or later. @@ -14,8 +15,9 @@ ### Internal * Updated to ReLinker 1.4.0. -* Updated to Object Store commit: ad96a4c334b475dd67d50c1ca419e257d7a21e18. -* Updated to Realm Sync v4.8.3. +* Updated to Object Store commit: 2a204063e1e1a366efbdd909fbea9effceb7d3c4. +* Updated to Realm Sync 4.9.4. +* Updated to Realm Core 5.23.8. ### Credits * Thanks to @sellmair (Sebastian Sellmair) for improving Kotlin Multiplatform support. diff --git a/dependencies.list b/dependencies.list index bc3605e4e0..d47eeadc24 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=4.8.3 -REALM_SYNC_SHA256=b3fa91562eb83a2d90fa600240546e41d8672bdac365ed0d0b55ae1726f2f2bb +REALM_SYNC_VERSION=4.9.4 +REALM_SYNC_SHA256=077b3ca9240f87bd4902a7e9bf68027f1f44e14d83c2aba5bce616bef4175490 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index b3f3ec079e..d53ab919fa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1765,36 +1765,40 @@ public void asJSON() throws JSONException { String json = all.asJSON(); final String expectedJSON = "[\n" + " {\n" + + " \"_key\":100," + " \"columnString\": \"alltypes1\",\n" + " \"columnLong\": 1337,\n" + " \"columnFloat\": 3.1400001,\n" + " \"columnDouble\": 0.89122999999999997,\n" + " \"columnBoolean\": false,\n" + " \"columnDate\": \"" + now + "\",\n" + - " \"columnBinary\": \"010203\",\n" + + " \"columnBinary\": \"AQID\",\n" + " \"columnMutableRealmInteger\": 0,\n" + " \"columnRealmObject\": [\n" + " {\n" + + " \"_key\": 100,\n" + " \"name\": \"dog1\",\n" + " \"age\": 1,\n" + " \"height\": 1.1,\n" + " \"weight\": 10.100000381469727,\n" + " \"hasTail\": true,\n" + " \"birthday\": \"" + now + "\",\n" + - " \"owner\": []\n" + + " \"owner\": null\n" + " }\n" + " ],\n" + " \"columnRealmList\": [\n" + " {\n" + + " \"_key\": 101,\n" + " \"name\": \"dog2\",\n" + " \"age\": 2,\n" + " \"height\": 2.0999999,\n" + " \"weight\": 20.100000381469727,\n" + " \"hasTail\": false,\n" + " \"birthday\": \"" + now + "\",\n" + - " \"owner\": []\n" + + " \"owner\": null\n" + " },\n" + " {\n" + + " \"_key\": 102,\n" + " \"name\": \"dog3\",\n" + " \"age\": 3,\n" + " \"height\": 3.0999999,\n" + @@ -1803,9 +1807,10 @@ public void asJSON() throws JSONException { " \"birthday\": \"" + now + "\",\n" + " \"owner\": [\n" + " {\n" + + " \"_key\": 0,\n" + " \"name\": \"Dog owner 1\",\n" + " \"dogs\": [],\n" + - " \"cat\": []\n" + + " \"cat\": null\n" + " }\n" + " ]\n" + " }\n" + @@ -1849,37 +1854,47 @@ public void asJSON_cycles() throws JSONException { String json = realmObjects.asJSON(); String expectedJSON = "[\n" + " {\n" + + " \"_key\": 0,\n" + " \"id\": 0,\n" + " \"name\": \"One\",\n" + " \"date\": \"" + now + "\",\n" + " \"object\": [\n" + " {\n" + + " \"_key\": 1,\n" + " \"id\": 0,\n" + " \"name\": \"Two\",\n" + " \"date\": \"" + now + "\",\n" + - " \"object\": \"0\",\n" + - " \"otherObject\": [],\n" + + " \"object\": {\n" + + " \"table\": \"class_CyclicType\",\n" + + " \"key\": 0\n" + + " },\n" + + " \"otherObject\": null,\n" + " \"objects\": []\n" + " }\n" + " ],\n" + - " \"otherObject\": [],\n" + + " \"otherObject\": null,\n" + " \"objects\": []\n" + " },\n" + " {\n" + + " \"_key\": 1,\n" + " \"id\": 0,\n" + " \"name\": \"Two\",\n" + " \"date\": \"" + now + "\",\n" + " \"object\": [\n" + " {\n" + + " \"_key\": 0,\n" + " \"id\": 0,\n" + " \"name\": \"One\",\n" + " \"date\": \"" + now + "\",\n" + - " \"object\": \"1\",\n" + - " \"otherObject\": [],\n" + + " \"object\": {\n" + + " \"table\": \"class_CyclicType\",\n" + + " \"key\": 1\n" + + " },\n" + + " \"otherObject\": null,\n" + " \"objects\": []\n" + " }\n" + " ],\n" + - " \"otherObject\": [],\n" + + " \"otherObject\": null,\n" + " \"objects\": []\n" + " }\n" + "]"; diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index 273e25e667..f79c93f393 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -93,13 +93,23 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNIEnv* env, jclass, jstring j_sync_base_dir, jstring j_user_agent_info) +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNIEnv* env, jclass, + jstring j_sync_base_dir, + jstring j_user_agent_binding_info, + jstring j_user_agent_application_info) { TR_ENTER() try { JStringAccessor base_file_path(env, j_sync_base_dir); // throws - JStringAccessor user_agent_info(env, j_user_agent_info); // throws - SyncManager::shared().configure(base_file_path, SyncManager::MetadataMode::NoEncryption, user_agent_info); + JStringAccessor user_agent_binding_info(env, j_user_agent_binding_info); // throws + JStringAccessor user_agent_application_info(env, j_user_agent_application_info); // throws + + SyncClientConfig client_config; + client_config.base_file_path = base_file_path; + client_config.metadata_mode = SyncManager::MetadataMode::NoEncryption; + client_config.user_agent_binding_info = user_agent_binding_info; + client_config.user_agent_application_info = user_agent_application_info; + SyncManager::shared().configure(client_config); static AndroidClientListener client_thread_listener(env); // Register Sync Client thread start/stop callback diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp index d23bb0bc59..5adb75904d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp @@ -166,7 +166,18 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativ JavaContext ctx(env, shared_realm, object_schema); auto list = *reinterpret_cast(builder_ptr); JavaValue values = JavaValue(list); - Object obj = Object::create(ctx, shared_realm, object_schema, values, update_existing, ignore_same_values); + CreatePolicy policy; + if (ignore_same_values) { + policy = CreatePolicy::UpdateModified; + } + else if (update_existing) { + policy = CreatePolicy::UpdateAll; + } + else { + policy = CreatePolicy::ForceCreate; + } + + Object obj = Object::create(ctx, shared_realm, object_schema, values, policy); return reinterpret_cast(new Row(obj.row())); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index 87eb4ee693..6b274df87b 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -244,7 +244,7 @@ class JavaAccessorContext { // using the provided value. If `update` is true then upsert semantics // should be used for this. template - T unbox(util::Any& v, bool /*create*/ = false, bool /*update*/ = false) const + T unbox(util::Any& v, CreatePolicy = CreatePolicy::Skip) const { return any_cast(v); } @@ -344,35 +344,35 @@ inline JPrimitiveArrayAccessor::ElementsHolder::~ElementsHold } template <> -inline bool JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline bool JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { check_value_not_null(v, "Boolean"); return any_cast(v) == JNI_TRUE; } template <> -inline int64_t JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline int64_t JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { check_value_not_null(v, "Long"); return static_cast(any_cast(v)); } template <> -inline double JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline double JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { check_value_not_null(v, "Double"); return static_cast(any_cast(v)); } template <> -inline float JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline float JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { check_value_not_null(v, "Float"); return static_cast(any_cast(v)); } template <> -inline StringData JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline StringData JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { if (!v.has_value()) { return StringData(); @@ -382,7 +382,7 @@ inline StringData JavaAccessorContext::unbox(util::Any& v, bool, bool) const } template <> -inline BinaryData JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline BinaryData JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { if (!v.has_value()) return BinaryData(); @@ -391,43 +391,43 @@ inline BinaryData JavaAccessorContext::unbox(util::Any& v, bool, bool) const } template <> -inline Timestamp JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline Timestamp JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? from_milliseconds(any_cast(v)) : Timestamp(); } template <> -inline RowExpr JavaAccessorContext::unbox(util::Any&, bool, bool) const +inline RowExpr JavaAccessorContext::unbox(util::Any&, CreatePolicy) const { REALM_TERMINATE("not supported"); } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? util::make_optional(any_cast(v) == JNI_TRUE) : util::none; } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? util::make_optional(static_cast(any_cast(v))) : util::none; } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? util::make_optional(any_cast(v)) : util::none; } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, bool, bool) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const { return v.has_value() ? util::make_optional(any_cast(v)) : util::none; } template <> -inline Mixed JavaAccessorContext::unbox(util::Any&, bool, bool) const +inline Mixed JavaAccessorContext::unbox(util::Any&, CreatePolicy) const { REALM_TERMINATE("not supported"); } diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp index 5054ec5dc3..ec0113af7b 100644 --- a/realm/realm-library/src/main/cpp/java_object_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -393,7 +393,7 @@ class JavaContext { // using the provided value. If `update` is true then upsert semantics // should be used for this. template - T unbox(JavaValue const& /*v*/, bool /*create*/= false, bool /*update*/= false, bool /*diff_on_update*/= false, size_t /*current_row*/ = realm::npos) const { + T unbox(JavaValue const& /*v*/, CreatePolicy = CreatePolicy::Skip, size_t /*current_row*/ = realm::npos) const { throw std::logic_error("Missing template specialization"); // All types should have specialized templates } @@ -433,35 +433,35 @@ class JavaContext { }; template <> -inline bool JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline bool JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { check_value_not_null(v, "Boolean"); return v.get_boolean() == JNI_TRUE; } template <> -inline int64_t JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline int64_t JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { check_value_not_null(v, "Long"); return static_cast(v.get_int()); } template <> -inline double JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline double JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { check_value_not_null(v, "Double"); return static_cast(v.get_double()); } template <> -inline float JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline float JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { check_value_not_null(v, "Float"); return static_cast(v.get_float()); } template <> -inline StringData JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline StringData JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { if (!v.has_value()) { return StringData(); @@ -471,7 +471,7 @@ inline StringData JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_ } template <> -inline BinaryData JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline BinaryData JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { if (!v.has_value()) { return BinaryData(); @@ -481,49 +481,49 @@ inline BinaryData JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_ } template <> -inline Timestamp JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline Timestamp JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { return v.has_value() ? v.get_date() : Timestamp(); } template <> -inline RowExpr JavaContext::unbox(JavaValue const& v, bool create, bool update, bool diff_on_update, size_t current_row) const +inline RowExpr JavaContext::unbox(JavaValue const& v, CreatePolicy policy, size_t current_row) const { if (v.get_type() == JavaValueType::Object) { return *v.get_object(); - } else if (!create) { + } else if (policy == CreatePolicy::Skip) { return RowExpr(); } REALM_ASSERT(object_schema); - return Object::create(const_cast(*this), realm, *object_schema, v, update, diff_on_update, current_row).row(); + return Object::create(const_cast(*this), realm, *object_schema, v, policy, current_row).row(); } template <> -inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { return v.has_value() ? util::make_optional(v.get_boolean() == JNI_TRUE) : util::none; } template <> -inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { return v.has_value() ? util::make_optional(static_cast(v.get_int())) : util::none; } template <> -inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { return v.has_value() ? util::make_optional(v.get_double()) : util::none; } template <> -inline util::Optional JavaContext::unbox(JavaValue const& v, bool, bool, bool, size_t) const +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, size_t) const { return v.has_value() ? util::make_optional(v.get_float()) : util::none; } template <> -inline Mixed JavaContext::unbox(JavaValue const&, bool, bool, bool, size_t) const +inline Mixed JavaContext::unbox(JavaValue const&, CreatePolicy, size_t) const { REALM_TERMINATE("'Mixed' not supported"); } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index ad96a4c334..2a204063e1 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit ad96a4c334b475dd67d50c1ca419e257d7a21e18 +Subproject commit 2a204063e1e1a366efbdd909fbea9effceb7d3c4 diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index 4249445439..49b1317a8e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -46,7 +46,7 @@ public static void init(Context context, String appDefinedUserAgent) { } // Setup Realm part of User-Agent string - String userAgent = "Unknown"; // Fallback in case of anything going wrong + String userAgentBindingInfo = "Unknown"; // Fallback in case of anything going wrong try { StringBuilder sb = new StringBuilder(); sb.append("RealmJava/"); @@ -58,13 +58,7 @@ public static void init(Context context, String appDefinedUserAgent) { sb.append(", v"); sb.append(Build.VERSION.SDK_INT); sb.append(")"); - - // Setup User part of User-Agent string - if (!Util.isEmptyString(appDefinedUserAgent)) { - sb.append(" "); - sb.append(appDefinedUserAgent); - } - userAgent = sb.toString(); + userAgentBindingInfo = sb.toString(); } catch (Exception e) { // Failures to construct the user agent should never cause the system itself to crash. RealmLog.warn("Constructing User-Agent description failed.", e); @@ -87,12 +81,12 @@ public static void init(Context context, String appDefinedUserAgent) { "Directory '%s' for SyncManager cannot be created. ", dir.getPath())); } - SyncManager.nativeInitializeSyncManager(dir.getPath(), userAgent); + SyncManager.nativeInitializeSyncManager(dir.getPath(), userAgentBindingInfo, appDefinedUserAgent); } catch (IOException e) { throw new IllegalStateException(e); } } else { - SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath(), userAgent); + SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath(), userAgentBindingInfo, appDefinedUserAgent); } // Configure default UserStore diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 27f5e4975b..3b4d513922 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -770,7 +770,7 @@ static void simulateClientReset(SyncSession session) { true); } - protected static native void nativeInitializeSyncManager(String syncBaseDir, String userAgent); + protected static native void nativeInitializeSyncManager(String syncBaseDir, String bindingUserAgentInfo, String appUserAgentInfo); private static native void nativeReset(); private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); private static native void nativeReconnect(); From 2354e27c862fae1105cb886166767d1d6a6c3acc Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 17 Jan 2020 13:39:39 +0100 Subject: [PATCH 1463/2110] Release v6.1.0 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 08565a9fc0..358e78e607 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.1.0-SNAPSHOT +6.1.0 \ No newline at end of file From 23b6ede794592b047b507d6fd8f2536005cca147 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 17 Jan 2020 13:39:39 +0100 Subject: [PATCH 1464/2110] Prepare next release v6.1.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 358e78e607..7b7e20b570 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.1.0 \ No newline at end of file +6.1.1-SNAPSHOT \ No newline at end of file From 3bb822bc96f69c3388ed09be291ce9f59593208b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 17 Jan 2020 15:17:21 +0100 Subject: [PATCH 1465/2110] Prepare for next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7b7e20b570..6253dc3c98 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -6.1.1-SNAPSHOT \ No newline at end of file +6.2.0-SNAPSHOT \ No newline at end of file From 3f46367326f77032cdfc42d46ca4162885e440c9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 31 Jan 2020 10:32:17 +0100 Subject: [PATCH 1466/2110] Prepare expected release version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index df4c8a93f3..72d3929c4c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.0-beta-SNAPSHOT +8.0.0-beta-SNAPSHOT From d20ba5bf4689863f6d3f632e140f1e38d3c0e0d7 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 3 Feb 2020 15:15:11 +0000 Subject: [PATCH 1467/2110] Core6 Beta2 (#6729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP Core6 integration * Update tests & OS * Disable file download if custom core path is set * Latest Core 12fd1cdd44eacf7cda7c9d5e8a5618f0704ba657 fixes some Sort disabled tests * Updated disabled tests, with reference to their issue. * Update reference to disabled test * - Removed a test non relevant in Core6 - Update disabled test with reference to issue * Update disabled test with reference to the issue * Enabling a test (passing with new Core/OS 🎉) * - Behaviour of stopWaitForChange changed in Core6 causing all waiting threads to be released, adapting tests accordingly & deprecated the usage of `stopWaitForChange` & `waitForChange` * Disabled tests concerning nullability (issue with test logic and Core crash) * Better kotlin tests * Add WIP support for TableRef instead of Table * Use TableRef instead of Table * Remove TBL macro * Reference open Java issue with regard to changes to fileformat upgrades. * Use Cores set_nullability * Updated core bug references * Fixing library benchmark * Enable tests after fixes in Core * Re-enable test * Re-enable more tests * Fix library benchmark project so it can load in Android Studio * Update test file and re-enable tests * Update various tests & using latest Core-6 branch from OS * - Removed usage of indices via colkey2spec_ndx - Renamed indices based method to column key - Updated OS commit to fix https://github.com/realm/realm-object-store/issues/678 * Fixes to upgrade to origin/master * Parse gradle root level properties to sub projects * - Enabling more tests, now all tests passes, sync integration tests are still failing because of https://github.com/realm/realm-object-store/issues/848 * update OS commit * Adapt to OS being behind master * - Applying disabled C++ compile flag - Cleanup * Test are passing * - Update dependencies - Rename ROW to OBJ * Update OS commit * fixed PMD rule * reverting check on valid ColKey since `bool(ColKey(-1))` returns true instead of false * - Disabling Sync iNtegration Flaky Tests * Making sure TableRef is used instead of Table* * More usage of TableRef vs Table* * remove OsObjectBuilder#maxColumnIndex() & ObjectSchemaInfo#getMaxColumnIndex() * Updated the annotation processor naming to use ColKey instead of index * Enabling/rewrote indicies based tests again * Remove usage of TR_ENTER_PTR * - Renaming Table#isAttached to Table#isValid - Clearing TODO/FIXME * Re-add missing benchmark files * Newlines * Cleanup & update OS Core6 branch * Update OS * Update to latest OS/Core * Disabling failing test on CI * Update to origin/master & uses a temporary OS branch with manual sync session creation commit cherry-picked * Update to latest OS/Core * Adding missing file * Fix flaky test * Added a test to check the Core6 no automatic index addition on String PK on an existing Core5 file * adding missing file * Add support for frozen objects (#6590) * PR feedback Cleanup & update to latest Core/OS versions * Annotation processor: using ColKey instead of index * PR feedback * Bumped minSDK * Update AP test * Fix transform crashing when you delete transformed objects (#6680) * Add multi-threaded stress test (#6677) * Add multi-threaded stress set * PR feedback. Use thread pool with variable amount of threads for reuse. * Better support for Gradle offline mode (#6692) * Update ReLinker (#6710) * Upgrade to latest version of Sync and Core (#6723) * Release v6.1.0 * Prepare next release v6.1.1-SNAPSHOT * Prepare for next dev iteration * cleanup * update deps * Updating tests * Update OS commit * Removed unused import * Update CHANGELOG.md Co-Authored-By: Christian Melchior * Update CHANGELOG.md Co-authored-by: Christian Melchior --- CHANGELOG.md | 23 +- dependencies.list | 4 +- .../io/realm/transformer/RealmTransformer.kt | 11 +- realm/realm-library/build.gradle | 2 +- .../androidTest/java/ThreadStressTests.java | 338 ++++++++++++++++++ .../java/io/realm/RealmMigrationTests.java | 2 +- .../java/io/realm/RealmObjectSchemaTests.java | 32 +- .../java/io/realm/entities/CyclicType.java | 1 + .../src/main/cpp/io_realm_SyncManager.cpp | 16 +- .../cpp/io_realm_internal_OsObjectStore.cpp | 7 +- .../cpp/io_realm_internal_OsSharedRealm.cpp | 8 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/BaseRealm.java | 4 + .../io/realm/MutableRealmObjectSchema.java | 26 +- .../main/java/io/realm/RealmObjectSchema.java | 4 + .../java/io/realm/internal/CheckedRow.java | 2 +- .../java/io/realm/ObjectServer.java | 14 +- .../java/io/realm/SyncConfiguration.java | 1 - .../java/io/realm/SyncManager.java | 2 +- .../java/io/realm/SyncSessionTests.java | 1 - 20 files changed, 436 insertions(+), 64 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/ThreadStressTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fa8403d117..14906a70f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 7.0.0-beta (YYYY-MM-DD) +## 7.0.0(YYYY-MM-DD) Based on v6.0.2. @@ -13,17 +13,16 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * [ObjectServer] `IncompatibleSyncedFileException` is removed as it is no longer used. * [ObjectServer] New error codes thrown by the underlying sync layers now have proper enum mappings in `ErrorCode.java`. A few other errors have been renamed in order to have consistent naming. (Issue [#6387](https://github.com/realm/realm-java/issues/6387)) * RxJava Flowables and Observables are now subscribed to and unsubscribed to asynchronously on the thread holding the live Realm, instead of previously where this was done synchronously. -* All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(boolean))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. +* All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(true|false))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. * MIPS is not supported anymore. * Realm now requires `minSdkVersion` 16. Up from 9. +* [ObjectServer] `IncompatibleSyncedFileException` is removed and no longer thrown. ### Enhancements * Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) * Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. -* `RealmResults.asJSON()` is no longer `@Beta`. -* Storing large binary blobs in Realm files no longer forces the file to be at least 8x the size of the largest blob. -* Reduce the size of transaction logs stored inside the Realm file, reducing file size growth from large transactions. +* `RealmResults.asJSON()` is no longer `@Beta` ### Compatibility * Realm Object Server: 3.23.1 or later. @@ -35,13 +34,17 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * OKHttp was upgraded to 3.10.0 from 3.9.0. -## 6.1.0(YYYY-MM-DD) +## 6.1.0(2020-01-17) ### Enhancements * The Realm Gradle plugin now applies `kapt` when used in Kotlin Multiplatform projects. Note, Realm Java still only works for the Android part of a Kotlin Multiplatform project. (Issue [#6653](https://github.com/realm/realm-java/issues/6653)) +* The error message shown when no native code could be found for the device is now much more descriptive. This is particular helpful if an app is using App Bundle or APK Split and the resulting APK was side-loaded outside the Google Play Store. (Issue [#6673](https://github.com/realm/realm-java/issues/6673)) +* `RealmResults.asJson()` now encode binary data as Base64 and null object links are reported as `null` instead of `[]`. ### Fixed -* None. +* Fixed using `RealmList` with a primitive type sometimes crashing with `Destruction of mutex in use`. (Issue [#6689](https://github.com/realm/realm-java/issues/6689)) +* `RealmObjectSchema.transform()` would crash if one of the `DynamicRealmObject` provided are deleted from the Realm. (Issue [#6657](https://github.com/realm/realm-java/issues/6657), since 0.86.0) +* The Realm Transformer will no longer attempt to send anonymous metrics when Gradle is invoked with `--offline`. (Issue [#6691](https://github.com/realm/realm-java/issues/6691)) ### Compatibility * Realm Object Server: 3.23.1 or later. @@ -49,8 +52,10 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. ### Internal -* Updated to Object Store commit: ad96a4c334b475dd67d50c1ca419e257d7a21e18. -* Updated to Realm Sync v4.8.3. +* Updated to ReLinker 1.4.0. +* Updated to Object Store commit: 2a204063e1e1a366efbdd909fbea9effceb7d3c4. +* Updated to Realm Sync 4.9.4. +* Updated to Realm Core 5.23.8. ### Credits * Thanks to @sellmair (Sebastian Sellmair) for improving Kotlin Multiplatform support. diff --git a/dependencies.list b/dependencies.list index 5fb5883232..135fcda6df 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.0-beta.1 -REALM_SYNC_SHA256=0fa8fe96a5e018d0bef9299bb4f8cadebead4c8658e5a006d394226ccd51900a +REALM_SYNC_VERSION=5.0.0-beta.2 +REALM_SYNC_SHA256=bbe58ad5110d66fe8c55a838486e09593e93ee96db8d07aa93a22abb52ead220 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 2e4a38aa2b..8788019830 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -126,7 +126,7 @@ class RealmTransformer(val project: Project) : Transform() { */ private fun sendAnalytics(inputs: Collection, outputModelClasses: Set) { try { - val disableAnalytics: Boolean = "true".equals(System.getenv()["REALM_DISABLE_ANALYTICS"], ignoreCase = true) + val disableAnalytics: Boolean = project.gradle.startParameter.isOffline || "true".equals(System.getenv()["REALM_DISABLE_ANALYTICS"], ignoreCase = true) if (inputs.isEmpty() || disableAnalytics) { // Don't send analytics for incremental builds or if they have been explicitly disabled. return @@ -153,12 +153,9 @@ class RealmTransformer(val project: Project) : Transform() { val packages: Set = outputModelClasses.map { it.packageName }.toSet() val targetSdk: String? = project.getTargetSdk() val minSdk: String? = project.getMinSdk() - - if (!disableAnalytics) { - val sync: Boolean = Utils.isSyncEnabled(project) - val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk) - analytics.execute() - } + val sync: Boolean = Utils.isSyncEnabled(project) + val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk) + analytics.execute() } catch (e: Exception) { // Analytics failing for any reason should not crash the build logger.debug("Could not send analytics: $e") diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 4844a6c295..360811fc76 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -209,7 +209,7 @@ dependencies { api "io.realm:realm-annotations:${version}" implementation 'com.google.code.findbugs:jsr305:3.0.2' - implementation 'com.getkeepsafe.relinker:relinker:1.3.0' + implementation 'com.getkeepsafe.relinker:relinker:1.4.0' implementation('io.reactivex.rxjava2:rxandroid:2.1.1') { exclude group: 'io.reactivex.rxjava2', module: 'rxjava' } diff --git a/realm/realm-library/src/androidTest/java/ThreadStressTests.java b/realm/realm-library/src/androidTest/java/ThreadStressTests.java new file mode 100644 index 0000000000..feeb9b1a51 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/ThreadStressTests.java @@ -0,0 +1,338 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.text.TextUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import io.realm.ManagedRealmListForValueTests; +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.RealmResults; +import io.realm.TestHelper; +import io.realm.entities.AllTypes; +import io.realm.entities.NonLatinFieldNames; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; +import io.realm.rule.TestRealmConfigurationFactory; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +/** + * Class used to stress test multiple actions across different threads. + * This doesn't attempt to test correctness beyond "Don't Crash". + * + * Some error level logging is done during the run of this. This is mostly to make + * it clearer what has happened in the case a run actually did crash, and doesn't indicate + * problems with the test as such. + */ +@RunWith(Parameterized.class) +public class ThreadStressTests { + + @Parameterized.Parameters(name = "Encryption: {0}, ReuseThreads: {1}") + public static List parameters() { + ArrayList list = new ArrayList<>(); + list.add(new Boolean[] { Boolean.TRUE, Boolean.TRUE }); + list.add(new Boolean[] { Boolean.TRUE, Boolean.FALSE }); + list.add(new Boolean[] { Boolean.FALSE, Boolean.TRUE }); + list.add(new Boolean[] { Boolean.FALSE, Boolean.FALSE }); + return list; + } + + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + + @Parameterized.Parameter + public boolean reuseThreads; + @Parameterized.Parameter(1) + public boolean useEncryption; + + private int originalLogLevel; + private final static int MAX_THREADS = 100; + private final static int MAX_CREATE = 1000; + private ExecutorService executor; + private RealmConfiguration realmConfig; + private Random random; + private List threads = new CopyOnWriteArrayList<>(); + private AtomicInteger workerThreadId = new AtomicInteger(0); + + enum CRUDAction { + CREATE, + READ, + UPDATE, + DELETE + } + + public interface AsyncTaskRunner { + void run(Realm realm, CountDownLatch success); + } + + public interface TaskRunner { + void run(Realm realm); + } + + @Before + public void setUp() { + originalLogLevel = RealmLog.getLevel(); + RealmLog.setLevel(LogLevel.INFO); + long seed = System.currentTimeMillis(); + RealmLog.info("Starting stress test with seed: " + seed); + random = new Random(seed); + RealmConfiguration.Builder builder = configFactory.createConfigurationBuilder(); + if (useEncryption) { + builder.encryptionKey(TestHelper.getRandomKey(seed)); + } + realmConfig = configFactory.createConfiguration(); + Realm.deleteRealm(realmConfig); + executor = Executors.newFixedThreadPool(reuseThreads ? Math.max(random.nextInt(MAX_THREADS), 1) : MAX_THREADS); + } + + @After + public void tearDown() { + RealmLog.setLevel(originalLogLevel); + } + + private void populateTestRealm(Realm realm, int objects) { + boolean inTransaction = realm.isInTransaction(); + if (!inTransaction) { + realm.beginTransaction(); + } + realm.deleteAll(); + for (int i = 0; i < objects; ++i) { + AllTypes allTypes = realm.createObject(AllTypes.class); + allTypes.setColumnLong(i); + allTypes.setColumnBoolean((i % 3) == 0); + allTypes.setColumnBinary(new byte[] {1, 2, 3}); + allTypes.setColumnDate(new Date()); + allTypes.setColumnDouble(Math.PI); + allTypes.setColumnFloat(1.234567F + i); + + allTypes.setColumnString("test data " + i); + allTypes.setColumnLong(i); + NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class); + nonLatinFieldNames.set델타(i); + nonLatinFieldNames.setΔέλτα(i); + nonLatinFieldNames.set베타(1.234567F + i); + nonLatinFieldNames.setΒήτα(1.234567F + i); + } + if (!inTransaction) { + realm.commitTransaction(); + } + } + + private void populateTestRealm(Realm realm) { + populateTestRealm(realm, 1000); + } + + @Test + public void threadStressTest() throws ExecutionException, InterruptedException { + Realm realm = Realm.getInstance(realmConfig); + populateTestRealm(realm); + for (int i = 0; i < MAX_THREADS; i++) { + CRUDAction action = CRUDAction.values()[random.nextInt(4)]; + Runnable task = null; + switch(action) { + case CREATE: + task = createObjects(random.nextInt(MAX_CREATE), random.nextBoolean()); + break; + case READ: + task = readObjects(random.nextBoolean()); + break; + case UPDATE: + task = updateObjects(random.nextBoolean(), random.nextBoolean()); + break; + case DELETE: + task = deleteObjects(random.nextBoolean(), random.nextBoolean()); + break; + } + threads.add(executor.submit(task)); + } + for (Future task : threads) { + assertNull(task.get()); + } + realm.close(); + } + + private Runnable createObjects(int objectsCount, boolean asyncTransaction) { + if (asyncTransaction) { + return createTaskInHandlerThread((realm, success) -> { + RealmLog.info("Creating objects (async): " + Thread.currentThread().getName()); + realm.executeTransactionAsync(bgRealm -> populateTestRealm(bgRealm, objectsCount), success::countDown); + }); + } else { + return createTaskInThread((realm) -> { + RealmLog.info("Creating objects: " + Thread.currentThread().getName()); + populateTestRealm(realm, objectsCount); + }); + } + } + + private Runnable deleteObjects(boolean filterObjects, boolean asyncTransaction) { + TaskRunner delete = realm -> { + if (filterObjects) { + realm.where(AllTypes.class) + .lessThan(AllTypes.FIELD_LONG, realm.where(AllTypes.class).count()/2) + .equalTo(AllTypes.FIELD_BOOLEAN, true) + .findAll() + .deleteAllFromRealm(); + } else { + realm.delete(AllTypes.class); + } + }; + + if (asyncTransaction) { + return createTaskInHandlerThread(((realm, success) -> { + RealmLog.info("Deleting objects (async): " + Thread.currentThread().getName()); + realm.executeTransactionAsync(delete::run, success::countDown); + })); + } else { + return createTaskInThread((realm) -> { + RealmLog.info("Deleting objects: " + Thread.currentThread().getName()); + realm.executeTransaction(delete::run); + }); + } + } + + + private Runnable updateObjects(boolean filterObjects, boolean asyncTransaction) { + TaskRunner update = realm -> { + RealmResults results; + if (filterObjects) { + results = realm.where(AllTypes.class) + .lessThan(AllTypes.FIELD_LONG, random.nextInt((int) realm.where(AllTypes.class).count() + 1)) + .equalTo(AllTypes.FIELD_BOOLEAN, random.nextBoolean()) + .findAll(); + } else { + results = realm.where(AllTypes.class).findAll(); + } + + results.setString(AllTypes.FIELD_STRING, "Updated: " + Thread.currentThread().getName()); + results.setBoolean(AllTypes.FIELD_BOOLEAN, random.nextBoolean()); + }; + + if (asyncTransaction) { + return createTaskInHandlerThread(((realm, success) -> { + RealmLog.info("Updating objects (async): " + Thread.currentThread().getName()); + realm.executeTransactionAsync(update::run, success::countDown); + })); + } else { + return createTaskInThread((realm) -> { + RealmLog.info("Updating objects: " + Thread.currentThread().getName()); + realm.executeTransaction(update::run); + }); + } + } + + private Runnable readObjects(boolean asyncQuery) { + if (asyncQuery) { + return createTaskInHandlerThread(new AsyncTaskRunner() { + private RealmResults liveResults; + @Override + public void run(Realm realm, CountDownLatch success) { + RealmLog.info("Reading objects (async): " + Thread.currentThread().getName()); + liveResults = realm.where(AllTypes.class) + .lessThan(AllTypes.FIELD_LONG, random.nextInt((int) realm.where(AllTypes.class).count() + 1)) + .equalTo(AllTypes.FIELD_BOOLEAN, random.nextBoolean()) + .findAllAsync(); + liveResults.addChangeListener((updatedResults, changeSet) -> { + for (AllTypes result : updatedResults) { + assertFalse(TextUtils.isEmpty(result.getColumnString())); + } + if (updatedResults.isLoaded()) { + RealmLog.info("Query finished on: " + Thread.currentThread().getName()); + success.countDown(); + } + }); + } + }); + } else { + return createTaskInThread((realm) -> { + RealmLog.info("Reading objects: " + Thread.currentThread().getName()); + RealmResults results = realm.where(AllTypes.class) + .lessThan(AllTypes.FIELD_LONG, random.nextInt((int) realm.where(AllTypes.class).count() + 1)) + .equalTo(AllTypes.FIELD_BOOLEAN, random.nextBoolean()) + .findAll(); + for (AllTypes result : results) { + assertFalse(TextUtils.isEmpty(result.getColumnString())); + } + }); + } + } + + private Runnable createTaskInThread(TaskRunner runnable) { + return () -> { + Realm realm = Realm.getInstance(realmConfig); + runnable.run(realm); + realm.close(); + }; + } + + private Runnable createTaskInHandlerThread(AsyncTaskRunner wrapper) { + return new Runnable() { + CountDownLatch successLatch = new CountDownLatch(1); + CountDownLatch closeLatch = new CountDownLatch(1); + volatile Handler handler; + volatile HandlerThread handlerThread; + AtomicReference realm = new AtomicReference<>(null); + AsyncTaskRunner wrapperStrongRef = wrapper; + + @Override + public void run() { + handlerThread = new HandlerThread("HandlerWorker: " + workerThreadId.incrementAndGet()); + handlerThread.start(); + Looper looper = handlerThread.getLooper(); + handler = new Handler(looper); + handler.post(() -> { + realm.set(Realm.getInstance(realmConfig)); + wrapperStrongRef.run(realm.get(), successLatch); + }); + + TestHelper.awaitOrFail(successLatch); + handler.post(() -> { + realm.get().close(); + closeLatch.countDown(); + }); + TestHelper.awaitOrFail(closeLatch); + handlerThread.quitSafely(); + } + }; + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 6d3dc00490..f3de8e9439 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -1444,7 +1444,7 @@ public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException .schema(MigrationCore6PKStringIndexedByDefault.class) .build()); assertFalse(realm.isEmpty()); - assertFalse(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); + assertTrue(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); MigrationCore6PKStringIndexedByDefault first = realm.where(MigrationCore6PKStringIndexedByDefault.class).findFirst(); assertNotNull(first); assertEquals("Foo", first.name); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 0fc3381d8a..5cbb06e2c4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -31,6 +31,7 @@ import java.util.Set; import io.realm.entities.AllJavaTypes; +import io.realm.entities.CyclicType; import io.realm.entities.Dog; import io.realm.entities.NonLatinFieldNames; import io.realm.internal.Table; @@ -1232,8 +1233,7 @@ public void apply(DynamicRealmObject obj) { obj.setInt("age", obj.getInt("age") + 1); } }); - assertEquals(5, ((DynamicRealm)realm).where("Dog").sum("age").intValue()); - } + assertEquals(5, ((DynamicRealm)realm).where("Dog").sum("age").intValue()); } @Test public void transformObjectReferences() { @@ -1256,6 +1256,34 @@ public void apply(DynamicRealmObject dog) { assertEquals("John", ((DynamicRealm)realm).where("Dog").findFirst().getObject("owner").getString("name")); } + @Test + public void transform_deleteObjects() { + if (type == ObjectSchemaType.IMMUTABLE) { + return; + } + + RealmObjectSchema classSchema = realm.getSchema().get("CyclicType"); + + Runnable transform = () -> classSchema.transform(obj -> { + if (obj.getInt(CyclicType.FIELD_ID) % 2 == 0) { + obj.getObject(CyclicType.FIELD_OBJECT).deleteFromRealm(); + obj.deleteFromRealm(); + } + }); + + String className = classSchema.getClassName(); + for (int i = 0; i < 10; i++) { + DynamicRealmObject parentObj = ((DynamicRealm)realm).createObject(className); + DynamicRealmObject childObj = ((DynamicRealm)realm).createObject(className); + parentObj.setLong(CyclicType.FIELD_ID, i); + parentObj.setObject(CyclicType.FIELD_OBJECT, childObj); + childObj.setLong(CyclicType.FIELD_ID, i + 100); + } + assertEquals(20, ((DynamicRealm) realm).where((className)).count()); + transform.run(); + assertEquals(10, ((DynamicRealm) realm).where((className)).count()); + } + @Test public void getFieldNames() { Set fieldNames = DOG_SCHEMA.getFieldNames(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/CyclicType.java b/realm/realm-library/src/androidTest/java/io/realm/entities/CyclicType.java index 1f71d52835..536ca50cdc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/CyclicType.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/CyclicType.java @@ -26,6 +26,7 @@ public class CyclicType extends RealmObject { public static final String FIELD_NAME = "name"; public static final String FIELD_ID = "id"; public static final String FIELD_DATE = "date"; + public static final String FIELD_OBJECT = "object"; private long id; private String name; diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp index 80940e4885..f87c968aad 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp @@ -90,12 +90,22 @@ JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNIEnv* env, jclass, jstring j_sync_base_dir, jstring j_user_agent_info) +JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNIEnv* env, jclass, + jstring j_sync_base_dir, + jstring j_user_agent_binding_info, + jstring j_user_agent_application_info) { try { JStringAccessor base_file_path(env, j_sync_base_dir); // throws - JStringAccessor user_agent_info(env, j_user_agent_info); // throws - SyncManager::shared().configure(base_file_path, SyncManager::MetadataMode::NoEncryption, user_agent_info); + JStringAccessor user_agent_binding_info(env, j_user_agent_binding_info); // throws + JStringAccessor user_agent_application_info(env, j_user_agent_application_info); // throws + + SyncClientConfig client_config; + client_config.base_file_path = base_file_path; + client_config.metadata_mode = SyncManager::MetadataMode::NoEncryption; + client_config.user_agent_binding_info = user_agent_binding_info; + client_config.user_agent_application_info = user_agent_application_info; + SyncManager::shared().configure(client_config); static AndroidClientListener client_thread_listener(env); // Register Sync Client thread start/stop callback diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp index 2caca07a8b..79850f64e6 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp @@ -68,12 +68,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetPrimaryKeyF } // Check duplicated values. The pk field must have been indexed before set as a PK. - // __CORE6__ work around until table->contains_unique_values is provided by Core6 - // since calling get_distinct_view is not possible on non indexed column (throws) - auto tv = table->where().find_all(); - tv.distinct(pk_column_col); - if (tv.size() != table->size()) { - // if (table->get_distinct_view(pk_column_col).size() != table->size()) { + if (!table->contains_unique_values(pk_column_col)) { THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, format("Field '%1' cannot be set as primary key since there are duplicated " "values for field '%1' in Class '%2'.", diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 921fa06b79..a8cdcc95fc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -297,12 +297,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi table = sync::create_table_with_primary_key(static_cast(group), table_name, pkType, field_name, is_nullable); #else - //__CORE6__ work around until we decide if add_table_with_primary_key should throw if called with the same table name - if (!group.has_table(table_name)) { - table = group.add_table_with_primary_key(table_name, pkType, field_name, is_nullable); - } else { - throw TableNameInUse(); - } + table = group.add_table_with_primary_key(table_name, pkType, field_name, + is_nullable); #endif return reinterpret_cast(new TableRef(table)); } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index fe6729961a..82b338f500 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit fe6729961a9df52dea27ac5a4257088c86c5b82f +Subproject commit 82b338f50089890455a2087c2a0c77a634646e14 diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 5037024d8c..e91c245c1b 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -345,7 +345,9 @@ public void writeEncryptedCopyTo(File destination, byte[] key) { * @throws IllegalStateException if calling this from within a transaction or from a Looper thread. * @throws RealmMigrationNeededException on typed {@link Realm} if the latest version contains * incompatible schema changes. + * @deprecated this method will be removed on the next-major release. */ + @Deprecated public boolean waitForChange() { checkIfValid(); if (isInTransaction()) { @@ -370,7 +372,9 @@ public boolean waitForChange() { * called waitForChange. * * @throws IllegalStateException if the {@link io.realm.Realm} instance has already been closed. + * @deprecated this method will be removed in the next-major release */ + @Deprecated public void stopWaitForChange() { if (realmCache != null) { realmCache.invokeWithLock(new RealmCache.Callback0() { diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index a088066b50..d1a43e37bd 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -20,10 +20,10 @@ import javax.annotation.Nonnull; +import io.realm.internal.CheckedRow; import io.realm.internal.OsObjectStore; import io.realm.internal.OsResults; import io.realm.internal.Table; -import io.realm.internal.UncheckedRow; import io.realm.internal.core.DescriptorOrdering; import io.realm.internal.fields.FieldDescriptor; @@ -296,18 +296,20 @@ public RealmObjectSchema setNullable(String fieldName, boolean nullable) { public RealmObjectSchema transform(Function function) { //noinspection ConstantConditions if (function != null) { - - OsResults result = OsResults.createFromQuery(realm.sharedRealm, table.where(), new DescriptorOrdering()); - OsResults snapshot = result.createSnapshot(); - OsResults.ListIterator listIterator = new OsResults.ListIterator(snapshot, 0) { - - @Override - protected Object convertRowToObject(UncheckedRow row) { - function.apply(new DynamicRealmObject(realm, row)); - return null; + // Users might delete object being transformed or accidentally delete other objects + // in the same table. E.g. cascading deletes if it is referenced by an object being deleted. + OsResults result = OsResults.createFromQuery(realm.sharedRealm, table.where(), new DescriptorOrdering()).createSnapshot(); + long original_size = result.size(); + if (original_size > Integer.MAX_VALUE) { + throw new UnsupportedOperationException("Too many results to iterate: " + original_size); + } + int size = (int) result.size(); + for (int i = 0; i < size; i++) { + DynamicRealmObject obj = new DynamicRealmObject(realm, new CheckedRow(result.getUncheckedRow(i))); + if (obj.isValid()) { + function.apply(obj); } - }; - while (listIterator.hasNext()) listIterator.next(); + } } return this; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 1538e45d4f..6aabd33260 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -27,8 +27,10 @@ import javax.annotation.Nullable; import io.realm.annotations.Required; +import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; import io.realm.internal.OsObjectStore; +import io.realm.internal.OsResults; import io.realm.internal.Table; import io.realm.internal.fields.FieldDescriptor; @@ -396,6 +398,8 @@ public Set getFieldNames() { /** * Runs a transformation function on each RealmObject instance of the current class. The object will be represented * as a {@link DynamicRealmObject}. + *

            + * There is no guarantees in which order the objects are returned. * * @param function transformation function. * @return this schema. diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index 9d8ab3164c..ba7d8b1d94 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -39,7 +39,7 @@ private CheckedRow(NativeContext context, Table parent, long nativePtr) { super(context, parent, nativePtr); } - private CheckedRow(UncheckedRow row) { + public CheckedRow(UncheckedRow row) { super(row); this.originalRow = row; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index 4249445439..49b1317a8e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -46,7 +46,7 @@ public static void init(Context context, String appDefinedUserAgent) { } // Setup Realm part of User-Agent string - String userAgent = "Unknown"; // Fallback in case of anything going wrong + String userAgentBindingInfo = "Unknown"; // Fallback in case of anything going wrong try { StringBuilder sb = new StringBuilder(); sb.append("RealmJava/"); @@ -58,13 +58,7 @@ public static void init(Context context, String appDefinedUserAgent) { sb.append(", v"); sb.append(Build.VERSION.SDK_INT); sb.append(")"); - - // Setup User part of User-Agent string - if (!Util.isEmptyString(appDefinedUserAgent)) { - sb.append(" "); - sb.append(appDefinedUserAgent); - } - userAgent = sb.toString(); + userAgentBindingInfo = sb.toString(); } catch (Exception e) { // Failures to construct the user agent should never cause the system itself to crash. RealmLog.warn("Constructing User-Agent description failed.", e); @@ -87,12 +81,12 @@ public static void init(Context context, String appDefinedUserAgent) { "Directory '%s' for SyncManager cannot be created. ", dir.getPath())); } - SyncManager.nativeInitializeSyncManager(dir.getPath(), userAgent); + SyncManager.nativeInitializeSyncManager(dir.getPath(), userAgentBindingInfo, appDefinedUserAgent); } catch (IOException e) { throw new IllegalStateException(e); } } else { - SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath(), userAgent); + SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath(), userAgentBindingInfo, appDefinedUserAgent); } // Configure default UserStore diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index a31b9a11db..3853725c7a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -34,7 +34,6 @@ import javax.annotation.Nullable; -import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.OsRealmConfig; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index 55c32305f7..d28319f723 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -770,7 +770,7 @@ static void simulateClientReset(SyncSession session) { true); } - protected static native void nativeInitializeSyncManager(String syncBaseDir, String userAgent); + protected static native void nativeInitializeSyncManager(String syncBaseDir, String bindingUserAgentInfo, String appUserAgentInfo); private static native void nativeReset(); private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); private static native void nativeReconnect(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index 402441bebf..bebb220ac0 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -563,7 +563,6 @@ public void run() { @Test @RunTestInLooperThread -// @Ignore("__CORE6__ this test is flaky in Core6, listener is not triggered") public void registerConnectionListener() { getSession(session -> { session.addConnectionChangeListener((oldState, newState) -> { From c1c45b46a36e9725f9741cce25732c69536be075 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Wed, 5 Feb 2020 08:54:15 -0500 Subject: [PATCH 1468/2110] Official Gradle Wrapper Validation Action (#6740) See: https://github.com/gradle/wrapper-validation-action --- .github/workflows/gradle-wrapper-validation.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/gradle-wrapper-validation.yml diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 0000000000..405a2b3065 --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,10 @@ +name: "Validate Gradle Wrapper" +on: [push, pull_request] + +jobs: + validation: + name: "Validation" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 From eef5812515ac8c00a985bf9f816236f81f79642d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 7 Feb 2020 09:57:21 +0100 Subject: [PATCH 1469/2110] Remove Permissions and ObjectLevel permissions (#6736) --- CHANGELOG.md | 18 + .../io/realm/kotlin/KotlinSyncedRealmTests.kt | 25 +- .../io/realm/kotlin/SyncedRealmExtensions.kt | 17 - .../io/realm/ObjectLevelPermissionsTest.java | 699 ------------------ .../java/io/realm/PermissionRequestTests.java | 112 --- .../java/io/realm/SessionTests.java | 5 +- .../java/io/realm/SyncedRealmTests.java | 4 +- .../java/io/realm/UserConditionTests.java | 131 ---- .../src/main/java/io/realm/BaseRealm.java | 42 -- .../src/main/java/io/realm/DynamicRealm.java | 84 --- .../src/main/java/io/realm/Realm.java | 74 -- .../java/io/realm/internal/OsSharedRealm.java | 3 - .../realm/internal/sync/PermissionHelper.java | 65 -- .../permissions/ObjectPermissionsModule.java | 13 + .../sync/permissions/ClassPermissions.java | 116 --- .../sync/permissions/ClassPrivileges.java | 158 ---- .../sync/permissions/ObjectPrivileges.java | 136 ---- .../io/realm/sync/permissions/Permission.java | 573 -------------- .../sync/permissions/PermissionUser.java | 87 --- .../sync/permissions/RealmPermissions.java | 74 -- .../sync/permissions/RealmPrivileges.java | 159 ---- .../java/io/realm/sync/permissions/Role.java | 122 --- .../realm/sync/permissions/package-info.java | 18 - .../java/io/realm/SyncConfiguration.java | 3 +- .../objectServer/java/io/realm/SyncUser.java | 331 +-------- .../internal/SyncObjectServerFacade.java | 2 +- .../AcceptPermissionsOfferResponse.java | 90 --- .../network/ApplyPermissionsRequest.java | 71 -- .../network/ApplyPermissionsResponse.java | 75 -- .../network/GetPermissionsOffersResponse.java | 108 --- .../InvalidatePermissionsOfferResponse.java | 79 -- .../network/MakePermissionsOfferRequest.java | 46 -- .../network/MakePermissionsOfferResponse.java | 102 --- .../network/OkHttpRealmObjectServer.java | 100 --- .../internal/network/RealmObjectServer.java | 31 - .../network/RetrievePermissionsResponse.java | 108 --- .../permissions/PermissionOfferResponse.java | 83 --- .../permissions/ObjectPermissionsModule.java | 23 - .../io/realm/permissions/AccessLevel.java | 120 --- .../java/io/realm/permissions/Permission.java | 162 ---- .../io/realm/permissions/PermissionOffer.java | 211 ------ .../realm/permissions/PermissionRequest.java | 150 ---- .../io/realm/permissions/UserCondition.java | 159 ---- .../io/realm/permissions/package-info.java | 18 - .../java/io/realm/BaseIntegrationTest.java | 2 +- .../io/realm/PathLevelPermissionsTests.java | 504 ------------- ...ObjectLevelPermissionIntegrationTests.java | 215 ------ .../objectserver/model/PermissionObject.java | 26 - .../realm/TestSyncConfigurationFactory.java | 2 +- 49 files changed, 45 insertions(+), 5511 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/PermissionRequestTests.java delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/UserConditionTests.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPrivileges.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/ObjectPrivileges.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPrivileges.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/permissions/package-info.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/AcceptPermissionsOfferResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsRequest.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/GetPermissionsOffersResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/InvalidatePermissionsOfferResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferRequest.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/RetrievePermissionsResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionOfferResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/AccessLevel.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/Permission.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/UserCondition.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/permissions/package-info.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PermissionObject.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fa8403d117..e6e08e0c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## 8.0.0 (YYYY-MM-DD) + +### Breaking Changes +* Removed all references and API's releated to permissions. These are now managed through MongoDB Realm. Read more [here](XXX). + +### Enhancements +* None. + +### Fixed +* None. + +### Compatibility +* TODO. + +### Internal +* None. + + ## 7.0.0-beta (YYYY-MM-DD) Based on v6.0.2. diff --git a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt index 9750686ee9..7deb8e8bc5 100644 --- a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt +++ b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt @@ -2,15 +2,11 @@ package io.realm.kotlin import android.support.test.InstrumentationRegistry import android.support.test.runner.AndroidJUnit4 -import io.realm.Realm -import io.realm.SyncConfiguration -import io.realm.SyncManager -import io.realm.TestSyncConfigurationFactory -import io.realm.entities.SimpleClass +import io.realm.* import io.realm.objectserver.utils.Constants -import io.realm.SyncTestUtils import org.junit.After -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.fail import org.junit.Before import org.junit.Rule import org.junit.Test @@ -53,19 +49,4 @@ class KotlinSyncedRealmTests { } } - @Test - fun classPermissions() { - assertNotNull(realm.classPermissions()) - } - - @Test - fun classPermissions_throwsForNonSyncRealm() { - realm.close() - realm = Realm.getInstance(configFactory.createConfiguration()) - try { - realm.classPermissions() - fail() - } catch (ignored: IllegalStateException) { - } - } } diff --git a/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt b/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt index b7a29fe64b..ad3d753760 100644 --- a/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt +++ b/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt @@ -16,11 +16,9 @@ package io.realm.kotlin import io.realm.Realm -import io.realm.RealmModel import io.realm.SyncConfiguration import io.realm.SyncManager import io.realm.SyncSession -import io.realm.sync.permissions.ClassPermissions /** @@ -36,18 +34,3 @@ val Realm.syncSession: SyncSession } return SyncManager.getSession(this.configuration as SyncConfiguration) } - -/** - * Returns all permissions associated with the given class. Attach a change listener using - * [ClassPermissions.addChangeListener] to be notified about any future changes. - * - * @return the permissions for the given class or `null` if no permissions where found. - * @throws RealmException if the class is not part of this Realms schema. - * @throws IllegalStateException if the Realm is not a synchronized Realm. - */ -inline fun Realm.classPermissions(): ClassPermissions { - if (!(this.configuration is SyncConfiguration)) { - throw java.lang.IllegalStateException("This method is only available on synchronized Realms") - } - return this.getPermissions(T::class.java) -} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java deleted file mode 100644 index 13183c24a0..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java +++ /dev/null @@ -1,699 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ -package io.realm; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.realm.annotations.RealmModule; -import io.realm.entities.AllJavaTypes; -import io.realm.entities.Dog; -import io.realm.exceptions.RealmException; -import io.realm.rule.RunInLooperThread; -import io.realm.sync.permissions.ClassPermissions; -import io.realm.sync.permissions.ClassPrivileges; -import io.realm.sync.permissions.ObjectPrivileges; -import io.realm.sync.permissions.Permission; -import io.realm.sync.permissions.PermissionUser; -import io.realm.sync.permissions.RealmPermissions; -import io.realm.sync.permissions.RealmPrivileges; -import io.realm.sync.permissions.Role; - -import static io.realm.SyncTestUtils.createTestUser; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class ObjectLevelPermissionsTest { - - private static String REALM_URI = "realm://objectserver.realm.io/~/default"; - - private SyncConfiguration configuration; - private SyncUser user; - - @Rule - public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - - @Rule - public final RunInLooperThread looperThread = new RunInLooperThread(); - private Realm realm; - private DynamicRealm dynamicRealm; - - @RealmModule(classes = { AllJavaTypes.class }) - public static class TestModule { - } - - @Before - public void setUp() { - user = createTestUser(); - configuration = user.createConfiguration(REALM_URI) - .modules(new TestModule()) - .build(); - realm = Realm.getInstance(configuration); - dynamicRealm = DynamicRealm.getInstance(configuration); - } - - @After - public void tearDown() { - if (realm != null && !realm.isClosed()) { - realm.close(); - } - if (dynamicRealm != null && !dynamicRealm.isClosed()) { - dynamicRealm.close(); - } - } - - @Test - public void getPrivileges_realm_localDefaults() { - RealmPrivileges privileges = realm.getPrivileges(); - assertFullAccess(privileges); - - privileges = dynamicRealm.getPrivileges(); - assertFullAccess(privileges); - } - - @Test - public void getPrivileges_realm_revokeLocally() { - realm.executeTransaction(r -> { - Role role = realm.getRoles().where().equalTo("name", "everyone").findFirst(); - role.removeMember(user.getIdentity()); - }); - - RealmPrivileges privileges = realm.getPrivileges(); - assertNoAccess(privileges); - - privileges = dynamicRealm.getPrivileges(); - assertNoAccess(privileges); - } - - @Test - public void getPrivileges_class_localDefaults() { - ClassPrivileges privileges = realm.getPrivileges(AllJavaTypes.class); - assertFullAccess(privileges); - - privileges = dynamicRealm.getPrivileges(AllJavaTypes.CLASS_NAME); - assertFullAccess(privileges); - } - - @Test - public void getPrivileges_class_revokeLocally() { - realm.executeTransaction(r -> { - Role role = realm.getRoles().where().equalTo("name", "everyone").findFirst(); - role.removeMember(user.getIdentity()); - }); - - ClassPrivileges privileges = realm.getPrivileges(AllJavaTypes.class); - assertNoAccess(privileges); - - privileges = dynamicRealm.getPrivileges(AllJavaTypes.CLASS_NAME); - assertNoAccess(privileges); - } - - @Test - public void getPrivileges_object_localDefaults() { - realm.beginTransaction(); - AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); - realm.commitTransaction(); - assertFullAccess(realm.getPrivileges(obj)); - - dynamicRealm.beginTransaction(); - DynamicRealmObject dynamicObject = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1); - dynamicRealm.commitTransaction(); - assertFullAccess(dynamicRealm.getPrivileges(dynamicObject)); - } - - @Test - public void getPrivileges_object_revokeLocally() { - realm.executeTransaction(r -> { - Role role = realm.getRoles().where().equalTo("name", "everyone").findFirst(); - role.removeMember(user.getIdentity()); - }); - - realm.beginTransaction(); - AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); - realm.commitTransaction(); - assertNoAccess(realm.getPrivileges(obj)); - - dynamicRealm.beginTransaction(); - DynamicRealmObject dynamicObject = dynamicRealm.createObject(AllJavaTypes.CLASS_NAME, 1); - dynamicRealm.commitTransaction(); - assertNoAccess(dynamicRealm.getPrivileges(dynamicObject)); - } - - @Test - public void getPrivileges_closedRealmThrows() { - realm.close(); - try { - realm.getPrivileges(); - fail(); - } catch(IllegalStateException ignored) { - } - - try { - realm.getPrivileges(AllJavaTypes.class); - fail(); - } catch(IllegalStateException ignored) { - } - - try { - //noinspection ConstantConditions - realm.getPrivileges((RealmModel) null); - fail(); - } catch(IllegalStateException ignored) { - } - - dynamicRealm.close(); - try { - dynamicRealm.getPrivileges(); - fail(); - } catch(IllegalStateException ignored) { - } - - try { - dynamicRealm.getPrivileges(AllJavaTypes.CLASS_NAME); - fail(); - } catch(IllegalStateException ignored) { - } - - try { - //noinspection ConstantConditions - dynamicRealm.getPrivileges((RealmModel) null); - fail(); - } catch(IllegalStateException ignored) { - } - } - - @Test - public void getPrivileges_wrongThreadThrows() throws InterruptedException { - Thread thread = new Thread(() -> { - try { - realm.getPrivileges(); - fail(); - } catch(IllegalStateException ignored) { - } - - try { - realm.getPrivileges(AllJavaTypes.class); - fail(); - } catch(IllegalStateException ignored) { - } - - try { - //noinspection ConstantConditions - realm.getPrivileges((RealmModel) null); - fail(); - } catch(IllegalStateException ignored) { - } - - try { - dynamicRealm.getPrivileges(); - fail(); - } catch(IllegalStateException ignored) { - } - - try { - dynamicRealm.getPrivileges(AllJavaTypes.CLASS_NAME); - fail(); - } catch(IllegalStateException ignored) { - } - - try { - //noinspection ConstantConditions - dynamicRealm.getPrivileges((RealmModel) null); - fail(); - } catch(IllegalStateException ignored) { - } - }); - thread.start(); - thread.join(TestHelper.STANDARD_WAIT_SECS * 1000); - } - - @Test - public void getPrivileges_class_notPartofSchemaThrows() { - try { - realm.getPrivileges(Dog.class); - fail(); - } catch (RealmException ignore) { - } - - try { - dynamicRealm.getPrivileges("Dog"); - fail(); - } catch (RealmException ignore) { - } - } - - @Test - public void getPrivileges_class_nullThrows() { - try { - //noinspection ConstantConditions - realm.getPrivileges((Class) null); - fail(); - } catch (IllegalArgumentException ignore) { - } - - try { - //noinspection ConstantConditions - dynamicRealm.getPrivileges((String) null); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - - @Test - public void getPrivileges_object_nullThrows() { - try { - //noinspection ConstantConditions - realm.getPrivileges((RealmModel) null); - fail(); - } catch (IllegalArgumentException ignore) { - } - - try { - //noinspection ConstantConditions - dynamicRealm.getPrivileges((DynamicRealmObject) null); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - - @Test(expected = IllegalArgumentException.class) - public void getPrivileges_object_unmanagedThrows() { - // DynamicRealm do not support unmanaged DynamicRealmObjects - realm.getPrivileges(new AllJavaTypes(0)); - } - - @Test - public void getPrivileges_object_wrongRealmThrows() { - Realm otherRealm = Realm.getInstance(configFactory.createConfiguration("other")); - otherRealm.beginTransaction(); - AllJavaTypes obj = otherRealm.createObject(AllJavaTypes.class, 0); - try { - realm.getPrivileges(obj); - fail(); - } catch (IllegalArgumentException ignored) { - } finally { - otherRealm.close(); - } - } - - - @Test - public void getPermissions() { - // Typed RealmPermissions - RealmPermissions realmPermissions = realm.getPermissions(); - RealmList list = realmPermissions.getPermissions(); - assertEquals(1, list.size()); - assertEquals("everyone", list.first().getRole().getName()); - assertFullAccess(list.first()); - -// // FIXME: Dynamic RealmPermissions - Until support is enabled -// realmPermissions = dynamicRealm.getPermissions(); -// list = realmPermissions.getPermissions(); -// assertEquals(1, list.size()); -// assertEquals("everyone", list.first().getRole().getName()); -// assertFullAccess(list.first()); - } - - @Test - public void getPermissions_wrongThreadThrows() throws InterruptedException { - Thread t = new Thread(() -> { - try { - realm.getPermissions(); - fail(); - } catch (IllegalStateException ignore) { - } - -// FIXME: Disabled until support is enabled -// try { -// dynamicRealm.getPermissions(); -// fail(); -// } catch (IllegalStateException ignore) { -// } - }); - t.start(); - t.join(TestHelper.STANDARD_WAIT_SECS * 1000); - } - - @Test - public void getPermissions_closedRealmThrows() { - realm.close(); - try { - realm.getPermissions(); - fail(); - } catch (IllegalStateException ignore) { - } - -// FIXME Disabled until support is enabled -// dynamicRealm.close(); -// try { -// dynamicRealm.getPermissions(); -// fail(); -// } catch (IllegalStateException ignore) { -// } - } - - @Test - public void getClassPermissions() { - // Typed RealmPermissions - ClassPermissions classPermissions = realm.getPermissions(AllJavaTypes.class); - assertEquals("AllJavaTypes", classPermissions.getName()); - RealmList list = classPermissions.getPermissions(); - assertEquals(1, list.size()); - assertEquals("everyone", list.first().getRole().getName()); - assertFullAccess(list.first()); - - // FIXME: Dynamic RealmPermissions - Disabled until support is enabled -// classPermissions = dynamicRealm.getPermissions(AllJavaTypes.CLASS_NAME); -// assertEquals("AllJavaTypes", classPermissions.getName()); -// list = classPermissions.getPermissions(); -// assertEquals(1, list.size()); -// assertEquals("everyone", list.first().getRole().getName()); -// assertDefaultAccess(list.first()); - } - - @Test - public void getClassPermissions_wrongThreadThrows() throws InterruptedException { - Thread t = new Thread(() -> { - try { - realm.getPermissions(AllJavaTypes.class); - fail(); - } catch (IllegalStateException ignore) { - } - -// FIXME: Disabled until support is enabled -// try { -// dynamicRealm.getPermissions(AllJavaTypes.CLASS_NAME); -// fail(); -// } catch (IllegalStateException ignore) { -// } - }); - t.start(); - t.join(TestHelper.STANDARD_WAIT_SECS * 1000); - } - - @Test - public void getClassPermissions_closedRealmThrows() { - realm.close(); - try { - realm.getPermissions(AllJavaTypes.class); - fail(); - } catch (IllegalStateException ignore) { - } - -// FIXME: Disabled until support is enabled -// dynamicRealm.close(); -// try { -// dynamicRealm.getPermissions(AllJavaTypes.CLASS_NAME); -// fail(); -// } catch (IllegalStateException ignore) { -// } - } - - @Test - public void userPrivateRole() { - RealmResults permissionUsers = realm.where(PermissionUser.class).findAll(); - assertEquals(1, permissionUsers.size()); - - PermissionUser permissionUser = permissionUsers.get(0); - assertNotNull(permissionUser); - Role role = permissionUser.getPrivateRole(); - assertNotNull(role); - - assertEquals("__User:" + user.getIdentity(), role.getName()); - assertTrue(role.hasMember(user.getIdentity())); - } - - @Test - public void userPrivateRoleNotAvailableBeforeSyncClientCreated() { - realm.beginTransaction(); - PermissionUser permissionUser = realm.createObject(PermissionUser.class, "id123"); - realm.commitTransaction(); - - Role builtInRole = permissionUser.getPrivateRole(); - assertNull(builtInRole); - permissionUser = realm.where(PermissionUser.class).equalTo("id", "id123").findFirst(); - assertNull(permissionUser.getPrivateRole()); - assertTrue(permissionUser.getRoles().isEmpty()); - } - - @Test - public void getRoles() { - RealmResults roles = realm.getRoles(); - assertEquals(2, roles.size()); - - roles = roles.where().sort("name").findAll(); - Role role = roles.get(0); - assertEquals("__User:" + user.getIdentity(), role.getName()); - assertTrue(role.hasMember(user.getIdentity())); - - role = roles.get(1); - assertEquals("everyone", role.getName()); - assertTrue(role.hasMember(user.getIdentity())); - - } - - @Test - public void getRoles_wrongThreadThrows() throws InterruptedException { - Thread t = new Thread(() -> { - try { - realm.getRoles(); - fail(); - } catch (IllegalStateException ignore) { - } - }); - t.start(); - t.join(TestHelper.STANDARD_WAIT_SECS * 1000); - - } - - @Test - public void getRoles_closedRealmThrows() { - realm.close(); - try { - realm.getRoles(); - fail(); - } catch (IllegalStateException ignore) { - } - -// FIXME: Until support is enabled -// dynamicRealm.close(); -// try { -// dynamicRealm.getRoles(); -// fail(); -// } catch (IllegalStateException ignore) { -// } - } - @Test - public void noPrivileges() { - Role role = new Role("foo"); - Permission admin = new Permission.Builder(role).allPrivileges().build(); - assertFullAccess(admin); - } - - @Test - public void allPrivileges() { - Role role = new Role("foo"); - Permission nobody = new Permission.Builder(role).noPrivileges().build(); - assertNoAccess(nobody); - } - - @Test - public void findOrCreate_unmanagedObjectThrows() { - RealmPermissions realmPermissions = new RealmPermissions(); - try { - realmPermissions.findOrCreate("foo"); - fail(); - } catch (IllegalStateException ignored) { - } - - ClassPermissions classPermissions = new ClassPermissions(); - try { - classPermissions.findOrCreate("foo"); - fail(); - } catch (IllegalStateException ignored) { - } - } - - @Test - public void findOrCreate_notInTransactionThrows() { - RealmPermissions realmPermissions = realm.getPermissions(); - try { - realmPermissions.findOrCreate("foo"); - fail(); - } catch (IllegalStateException ignored) { - } - - ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); - try { - classPermissions.findOrCreate("foo"); - fail(); - } catch (IllegalStateException ignored) { - } - } - - @Test - public void findOrCreate_nullThrows() { - realm.beginTransaction(); - RealmPermissions realmPermissions = realm.getPermissions(); - try { - //noinspection ConstantConditions - realmPermissions.findOrCreate(null); - fail(); - } catch (IllegalArgumentException ignored) { - } - - ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); - try { - //noinspection ConstantConditions - classPermissions.findOrCreate(null); - fail(); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void findOrCreate_createRole() { - realm.beginTransaction(); - assertNull(realm.where(Role.class).equalTo("name", "role1").findFirst()); - assertNull(realm.where(Role.class).equalTo("name", "role2").findFirst()); - - // Realm permissions - RealmPermissions realmPermissions = realm.getPermissions(); - Permission p = realmPermissions.findOrCreate("role1"); - assertEquals("role1", p.getRole().getName()); - assertTrue(p.getRole().getMembers().isEmpty()); - - // Class permissions - ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); - p = classPermissions.findOrCreate("role2"); - assertEquals("role2", p.getRole().getName()); - assertTrue(p.getRole().getMembers().isEmpty()); - } - - @Test - public void findOrCreate_createPermission() { - realm.beginTransaction(); - realm.createObject(Role.class, "role1"); - - RealmPermissions realmPermissions = realm.getPermissions(); - assertEquals(1, realmPermissions.getPermissions().size()); - Permission p = realmPermissions.findOrCreate("role1"); - assertNoAccess(p); - assertEquals("role1", p.getRole().getName()); - assertEquals(2, realmPermissions.getPermissions().size()); - assertTrue(p.equals(realmPermissions.getPermissions().last())); - - // Class permissions - ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); - assertEquals(1, classPermissions.getPermissions().size()); - p = classPermissions.findOrCreate("role2"); - assertNoAccess(p); - assertEquals("role2", p.getRole().getName()); - assertEquals(2, classPermissions.getPermissions().size()); - assertTrue(p.equals(classPermissions.getPermissions().last())); - } - - @Test - public void findOrCreate_findExistingPermission() { - realm.beginTransaction(); - - RealmPermissions realmPermissions = realm.getPermissions(); - Permission p = realmPermissions.findOrCreate("everyone"); - assertFullAccess(p); - assertEquals("everyone", p.getRole().getName()); - - ClassPermissions classPermissions = realm.getPermissions(ClassPermissions.class); - p = classPermissions.findOrCreate("everyone"); - assertFullAccess(p); - assertEquals("everyone", p.getRole().getName()); - } - - - private void assertFullAccess(RealmPrivileges privileges) { - assertTrue(privileges.canRead()); - assertTrue(privileges.canUpdate()); - assertTrue(privileges.canSetPermissions()); - assertTrue(privileges.canModifySchema()); - } - - private void assertFullAccess(ClassPrivileges privileges) { - assertTrue(privileges.canCreate()); - assertTrue(privileges.canRead()); - assertTrue(privileges.canUpdate()); - assertTrue(privileges.canQuery()); - assertTrue(privileges.canSetPermissions()); - } - - private void assertFullAccess(ObjectPrivileges privileges) { - assertTrue(privileges.canRead()); - assertTrue(privileges.canUpdate()); - assertTrue(privileges.canDelete()); - assertTrue(privileges.canSetPermissions()); - } - - private void assertFullAccess(Permission permission) { - assertTrue(permission.canCreate()); - assertTrue(permission.canRead()); - assertTrue(permission.canUpdate()); - assertTrue(permission.canDelete()); - assertTrue(permission.canQuery()); - assertTrue(permission.canSetPermissions()); - assertTrue(permission.canModifySchema()); - } - - private void assertNoAccess(Permission permission) { - assertFalse(permission.canCreate()); - assertFalse(permission.canRead()); - assertFalse(permission.canUpdate()); - assertFalse(permission.canDelete()); - assertFalse(permission.canQuery()); - assertFalse(permission.canSetPermissions()); - assertFalse(permission.canModifySchema()); - } - - private void assertNoAccess(RealmPrivileges privileges) { - assertFalse(privileges.canRead()); - assertFalse(privileges.canUpdate()); - assertFalse(privileges.canSetPermissions()); - assertFalse(privileges.canModifySchema()); - } - - private void assertNoAccess(ClassPrivileges privileges) { - assertFalse(privileges.canCreate()); - assertFalse(privileges.canRead()); - assertFalse(privileges.canUpdate()); - assertFalse(privileges.canQuery()); - assertFalse(privileges.canSetPermissions()); - } - - private void assertNoAccess(ObjectPrivileges privileges) { - assertFalse(privileges.canRead()); - assertFalse(privileges.canUpdate()); - assertFalse(privileges.canDelete()); - assertFalse(privileges.canSetPermissions()); - } - -} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/PermissionRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/PermissionRequestTests.java deleted file mode 100644 index 5ecde21eca..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/PermissionRequestTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.realm.permissions.AccessLevel; -import io.realm.permissions.PermissionRequest; -import io.realm.permissions.UserCondition; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - - -@RunWith(AndroidJUnit4.class) -public class PermissionRequestTests { - - @Test - public void nullArgumentsThrows() { - try { - new PermissionRequest(null, "*", AccessLevel.ADMIN); - fail(); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains("Non-null 'condition' required.")); - } - - try { - new PermissionRequest(UserCondition.userId("id"), null, AccessLevel.ADMIN); - fail(); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains("Non-empty 'realmUrl' required.")); - } - - try { - new PermissionRequest(UserCondition.userId("id"), "*", null); - fail(); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains("Non-null 'accessLevel' required.")); - } - } - - @Test - public void url_throwsOnInvalidURIs() { - String[] invalidUrls = { "", "\\", "" }; - for (String url : invalidUrls) { - try { - new PermissionRequest(UserCondition.userId("id"), url, AccessLevel.ADMIN); - fail(url + " should have thrown"); - } catch (IllegalArgumentException ignore) { - } - } - } - - @Test - public void url_validURIs() { - // We support "*" and valid URI's - // We don't attempt to do more validation than that and leaves that up to ROS - String[] validUrls = { - "*", - "http://foo/bar/baz", - "https://foo/bar/baz", - "realm://foo.bar/~/default", - "realms://foo.bar/~/default" - }; - for (String url : validUrls) { - PermissionRequest request = new PermissionRequest(UserCondition.userId("id"), url, AccessLevel.ADMIN); - assertEquals(url, request.getUrl()); - } - } - - @Test - public void getters() { - UserCondition condition = UserCondition.userId("id"); - String url = "*"; - AccessLevel accessLevel = AccessLevel.ADMIN; - - PermissionRequest request = new PermissionRequest(condition, url, accessLevel); - - assertEquals(condition, request.getCondition()); - assertEquals(url, request.getUrl()); - assertEquals(accessLevel, request.getAccessLevel()); - } - - @Test - public void equals() { - PermissionRequest r1 = new PermissionRequest(UserCondition.userId("id"), "*", AccessLevel.ADMIN); - PermissionRequest r2 = new PermissionRequest(UserCondition.userId("id"), "*", AccessLevel.ADMIN); - - assertTrue(r1.equals(r2)); - assertTrue(r2.equals(r1)); - assertEquals(r1.hashCode(), r2.hashCode()); - } - -} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 0a3977ebd0..b1a608aee6 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -31,11 +31,12 @@ import io.realm.entities.StringOnly; import io.realm.exceptions.RealmFileException; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; +import io.realm.exceptions.RealmMigrationNeededException; import io.realm.log.RealmLog; import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; import static io.realm.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; @@ -281,7 +282,7 @@ public void errorHandler_useBackupSyncConfigurationAfterClientReset() { try { Realm.getInstance(backupRealmConfiguration); fail("Expected to throw a Migration required"); - } catch (IllegalStateException expected) { + } catch (RealmMigrationNeededException expected) { } // opening a DynamicRealm will work though diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index 4d685b946c..9fd2ab5da1 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -19,6 +19,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.After; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -92,7 +93,8 @@ private Realm getFullySyncRealm() { // Test for https://github.com/realm/realm-java/issues/6619 @Test - public void testUpgragendingOptionalSubscriptionFields() throws IOException { + @Ignore("Going to be removed anyway") + public void testUpgradingOptionalSubscriptionFields() throws IOException { SyncUser user = SyncTestUtils.createTestUser(); // Put an older Realm at the location where Realm would otherwise create a new empty one. diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserConditionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserConditionTests.java deleted file mode 100644 index 173e3dccce..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/UserConditionTests.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.realm.permissions.UserCondition; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - - -@RunWith(AndroidJUnit4.class) -public class UserConditionTests { - - @Test - public void username_nullOrEmptyThrows() { - String[] illegalValues = { null, ""}; - for (String value : illegalValues) { - try { - UserCondition.username(value); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - } - - @Test - public void userId_nullOrEmptyThrows() { - String[] illegalValues = { null, ""}; - for (String value : illegalValues) { - try { - UserCondition.userId(value); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - } - - @Test - public void keyValue_nullOrEmptyThrows() { - // Keys - String[] illegalKeys = { null, ""}; - for (String key : illegalKeys) { - try { - UserCondition.keyValue(key, "value"); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - - // Values - try { - UserCondition.keyValue("key", null); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - - @Test - public void username() { - UserCondition condition = UserCondition.username("a@b.c"); - assertEquals("a@b.c", condition.getValue()); - assertEquals("email", condition.getKey()); - assertEquals(UserCondition.MatcherType.METADATA, condition.getType()); - } - - @Test - public void userId() { - UserCondition condition = UserCondition.userId("foo"); - assertEquals("foo", condition.getValue()); - assertEquals("", condition.getKey()); - assertEquals(UserCondition.MatcherType.USER_ID, condition.getType()); - } - - @Test - public void keyValue() { - UserCondition condition = UserCondition.keyValue("key", "value"); - assertEquals("value", condition.getValue()); - assertEquals("key", condition.getKey()); - assertEquals(UserCondition.MatcherType.METADATA, condition.getType()); - } - - @Test - public void nonExistingPermissions() { - UserCondition condition = UserCondition.noExistingPermissions(); - assertEquals("*", condition.getValue()); - assertEquals("", condition.getKey()); - assertEquals(UserCondition.MatcherType.USER_ID, condition.getType()); - } - - @Test - public void equals() { - UserCondition c1 = UserCondition.username("a@b.c"); - UserCondition c2 = UserCondition.username("a@b.c"); - - assertTrue(c1.equals(c2)); - assertTrue(c2.equals(c1)); - assertEquals(c1.hashCode(), c2.hashCode()); - } - - @Test - public void notEquals() { - UserCondition c1 = UserCondition.username("a@b.c"); - UserCondition c2 = UserCondition.username("a@b.d"); - - assertFalse(c1.equals(c2)); - assertFalse(c2.equals(c1)); - assertNotEquals(c1.hashCode(), c2.hashCode()); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 5037024d8c..8aead97b3a 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -29,7 +29,6 @@ import javax.annotation.Nullable; import io.reactivex.Flowable; -import io.realm.annotations.Beta; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; @@ -41,17 +40,13 @@ import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSchemaInfo; import io.realm.internal.OsSharedRealm; -import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.UncheckedRow; import io.realm.internal.Util; -import io.realm.internal.annotations.ObjectServer; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.log.RealmLog; -import io.realm.sync.permissions.ObjectPrivileges; -import io.realm.sync.permissions.RealmPrivileges; /** * Base class for all Realm instances. @@ -558,43 +553,6 @@ public long getVersion() { return OsObjectStore.getSchemaVersion(sharedRealm); } - /** - * Returns the privileges granted to the current user for this Realm. - * - * @return the privileges granted the current user for this Realm. - */ - @Beta - @ObjectServer - public RealmPrivileges getPrivileges() { - checkIfValid(); - return new RealmPrivileges(sharedRealm.getPrivileges()); - } - - /** - * Returns the privileges granted to the current user for the given object. - * - * @param object Realm object to get privileges for. - * @return the privileges granted the current user for the object. - * @throws IllegalArgumentException if the object is either null, unmanaged or not part of this Realm. - */ - @Beta - @ObjectServer - public ObjectPrivileges getPrivileges(RealmModel object) { - checkIfValid(); - //noinspection ConstantConditions - if (object == null) { - throw new IllegalArgumentException("Non-null 'object' required."); - } - if (!RealmObject.isManaged(object)) { - throw new IllegalArgumentException("Only managed objects have privileges. This is a an unmanaged object: " + object.toString()); - } - if (!((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(getPath())) { - throw new IllegalArgumentException("Object belongs to a different Realm."); - } - UncheckedRow row = (UncheckedRow) ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm(); - return new ObjectPrivileges(sharedRealm.getObjectPrivileges(row)); - } - /** * Closes the Realm instance and all its resources. *

            diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 7b9e0b7289..ecf3a69b5d 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -19,7 +19,6 @@ import java.util.Locale; import io.reactivex.Flowable; -import io.realm.annotations.Beta; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.internal.CheckedRow; @@ -27,10 +26,7 @@ import io.realm.internal.OsObjectStore; import io.realm.internal.OsSharedRealm; import io.realm.internal.Table; -import io.realm.internal.Util; -import io.realm.internal.annotations.ObjectServer; import io.realm.log.RealmLog; -import io.realm.sync.permissions.ClassPrivileges; /** * DynamicRealm is a dynamic variant of {@link io.realm.Realm}. This means that all access to data and/or queries are @@ -304,86 +300,6 @@ public boolean isEmpty() { return sharedRealm.isEmpty(); } -// FIXME: Depends on a typed schema. Find a work-around -// /** -// * {@inheritDoc} -// */ -// @Beta -// @ObjectServer -// @Override -// public RealmPermissions getPermissions() { -// checkIfValid(); -// Table table = sharedRealm.getTable("class___Realm"); -// TableQuery query = table.where(); -// OsResults result = OsResults.createFromQuery(sharedRealm, query); -// return new RealmResults<>(this, result, RealmPermissions.class).first(); -// } - - -// FIXME: Depends on a typed schema. Find a work-around -// /** -// * Returns all permissions associated with the given class. Attach a change listener -// * using {@link ClassPermissions#addChangeListener(RealmChangeListener)} to be notified about -// * any future changes. -// * -// * @param className class to receive permissions for. -// * @return the permissions for the given class or {@code null} if no permissions where found. -// * @throws RealmException if the class is not part of this Realms schema. -// */ -// @Beta -// @ObjectServer -// public ClassPermissions getPermissions(String className) { -// checkIfValid(); -// //noinspection ConstantConditions -// if (Util.isEmptyString(className)) { -// throw new IllegalArgumentException("Non-empty 'className' required."); -// } -// if (!schema.contains(className)) { -// throw new RealmException("Class '" + className + "' is not part of the schema for this Realm."); -// } -// Table table = sharedRealm.getTable("class___Class"); -// TableQuery query = table.where() -// .equalTo(new long[]{table.getObjectKey("name")}, new long[]{NativeObject.NULLPTR}, className); -// OsResults result = OsResults.createFromQuery(sharedRealm, query); -// return new RealmResults<>(this, result, ClassPermissions.class).first(null); -// } - -// FIXME: Depends on a typed schema. Find a work-around -// /** -// * {@inheritDoc} -// */ -// @Beta -// @ObjectServer -// @Override -// public RealmResults getRoles() { -// checkIfValid(); -// //noinspection ConstantConditions -// Table table = sharedRealm.getTable("class___Role"); -// TableQuery query = table.where(); -// OsResults result = OsResults.createFromQuery(sharedRealm, query); -// return new RealmResults<>(this, result, Role.class); -// } - - /** - * Returns the privileges granted the current user for the given class. - * - * @param className class to get privileges for. - * @return the privileges granted the current user for the given class. - */ - @Beta - @ObjectServer - public ClassPrivileges getPrivileges(String className) { - checkIfValid(); - //noinspection ConstantConditions - if (Util.isEmptyString(className)) { - throw new IllegalArgumentException("Non-empty 'className' required."); - } - if (!schema.contains(className)) { - throw new RealmException("Class '" + className + "' is not part of the schema for this Realm"); - } - return new ClassPrivileges(sharedRealm.getClassPrivileges(className)); - } - /** * Returns the mutable schema for this Realm. * diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index b6ded6e885..84b61064c1 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -16,10 +16,8 @@ package io.realm; -import android.annotation.TargetApi; import android.app.IntentService; import android.content.Context; -import android.os.Build; import android.os.SystemClock; import android.util.JsonReader; @@ -74,10 +72,6 @@ import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; import io.realm.sync.Subscription; -import io.realm.sync.permissions.ClassPermissions; -import io.realm.sync.permissions.ClassPrivileges; -import io.realm.sync.permissions.RealmPermissions; -import io.realm.sync.permissions.Role; /** * The Realm class is the storage and transactional manager of your object persistent store. It is in charge of creating @@ -1873,74 +1867,6 @@ public void onError(Throwable error) { }); } - /** - * Returns all permissions associated with the current Realm. Attach a change listener - * using {@link RealmPermissions#addChangeListener(RealmChangeListener)} to be notified about - * any future changes. - * - * @return all permissions for the current Realm. - */ - @Beta - @ObjectServer - public RealmPermissions getPermissions() { - checkIfValid(); - return where(RealmPermissions.class).findFirst(); - } - - /** - * Returns all {@link Role} objects available in this Realm. Attach a change listener - * using {@link Role#addChangeListener(RealmChangeListener)} to be notified about - * any future changes. - * - * @return all roles available in the current Realm. - */ - @Beta - @ObjectServer - public RealmResults getRoles() { - checkIfValid(); - return where(Role.class).sort("name").findAll(); - } - - /** - * Returns the privileges granted the current user for the given class. - * - * @param clazz class to get privileges for. - * @return the privileges granted the current user for the given class. - */ - @Beta - @ObjectServer - public ClassPrivileges getPrivileges(Class clazz) { - checkIfValid(); - //noinspection ConstantConditions - if (clazz == null) { - throw new IllegalArgumentException("Non-null 'clazz' required."); - } - String className = configuration.getSchemaMediator().getSimpleClassName(clazz); - return new ClassPrivileges(sharedRealm.getClassPrivileges(className)); - } - - /** - * Returns all permissions associated with the given class. Attach a change listener - * using {@link ClassPermissions#addChangeListener(RealmChangeListener)} to be notified about - * any future changes. - * - * @param clazz class to receive permissions for. - * @return the permissions for the given class or {@code null} if no permissions where found. - * @throws RealmException if the class is not part of this Realms schema. - */ - @Beta - @ObjectServer - public ClassPermissions getPermissions(Class clazz) { - checkIfValid(); - //noinspection ConstantConditions - if (clazz == null) { - throw new IllegalArgumentException("Non-null 'clazz' required."); - } - return where(ClassPermissions.class) - .equalTo("name", configuration.getSchemaMediator().getSimpleClassName(clazz)) - .findFirst(); - } - /** * Returns a list of all known subscriptions, regardless of their status. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 314d7a5e64..031471df59 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -26,12 +26,9 @@ import javax.annotation.Nullable; import io.realm.RealmConfiguration; -import io.realm.RealmModel; -import io.realm.exceptions.RealmException; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.annotations.ObjectServer; -import io.realm.sync.permissions.RealmPrivileges; @Keep public final class OsSharedRealm implements Closeable, NativeObject { diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java b/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java deleted file mode 100644 index 880d28439e..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.internal.sync; - -import io.realm.Realm; -import io.realm.RealmList; -import io.realm.RealmObject; -import io.realm.internal.annotations.ObjectServer; -import io.realm.sync.permissions.Permission; -import io.realm.sync.permissions.Role; - -/** - * Helper class for working with fine-grained permissions - */ -@ObjectServer -public class PermissionHelper { - - /** - * Finds or creates the permission object for a given role. Creating objects if they cannot - * be found. - * - * @param container RealmObject containg the permission objects - * @param permissions the list of permissions - * @param roleName the role to search for - * @return - */ - public static Permission findOrCreatePermissionForRole(RealmObject container, RealmList permissions, String roleName) { - if (!container.isManaged()) { - throw new IllegalStateException("'findOrCreate()' can only be called on managed objects."); - } - Realm realm = container.getRealm(); - if (!realm.isInTransaction()) { - throw new IllegalStateException("'findOrCreate()' can only be called inside a write transaction."); - } - - // Find existing permission object or create new one - Permission permission = permissions.where().equalTo("role.name", roleName).findFirst(); - if (permission == null) { - - // Find existing role or create new one - Role role = realm.where(Role.class).equalTo("name", roleName).findFirst(); - if (role == null) { - role = realm.createObject(Role.class, roleName); - } - - permission = realm.copyToRealm(new Permission.Builder(role).noPrivileges().build()); - permissions.add(permission); - } - - return permission; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java b/realm/realm-library/src/main/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java new file mode 100644 index 0000000000..3c97b1e8af --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java @@ -0,0 +1,13 @@ +package io.realm.internal.sync.permissions; + +import io.realm.annotations.RealmModule; +import io.realm.sync.Subscription; + +/** + * Realm model classes that are always part of Query-based Realms + */ +@RealmModule(library = true, classes = { + Subscription.class +}) +public class ObjectPermissionsModule { +} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java deleted file mode 100644 index d2a4d94854..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPermissions.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.sync.permissions; - -import io.realm.RealmList; -import io.realm.RealmModel; -import io.realm.RealmObject; -import io.realm.annotations.Ignore; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmClass; -import io.realm.annotations.Required; -import io.realm.internal.annotations.ObjectServer; -import io.realm.internal.sync.PermissionHelper; - -/** - * Class describing all permissions related to a given Realm model class. These permissions will - * be inherited by any concrete objects of the given type. - *

            - * If a class level permission grants a privilege, it is still possible for individual objects - * to revoke them again, i.e. it is possible for the class level permission to grant general read - * access, while the individual objects are still able to revoke them. - *

            - * The opposite is not true, so if a privilege is not granted at the class level, it can never - * be granted at the object level, no matter what kind of permissions are set there. - * - * @see Object Level Permissions for an detailed description of the Realm Object - * Server permission system. - */ -@ObjectServer -@RealmClass(name = "__Class") -public class ClassPermissions extends RealmObject { - - @PrimaryKey - @Required - private String name; // Name of the class in the schema - private RealmList permissions = new RealmList<>(); - - @Ignore - Class modelClassRef; - - public ClassPermissions() { - // Required by Realm - } - - /** - * Creates permissions for the given Realm model class. Only one {@code ClassPermissions} object - * can exist pr Realm model class. - * - * @param clazz class to create permissions. - */ - public ClassPermissions(Class clazz) { - //noinspection ConstantConditions - if (clazz == null) { - throw new IllegalArgumentException("Non-null 'clazz' required."); - } - modelClassRef = clazz; - name = clazz.getSimpleName(); - } - - /** - * Returns the name of the class these permissions apply to. If this object is unmanaged - * this name returned will be the simple name of the Java class. If the object is managed - * it will be the internal name Realm uses to represent the class. - * - * @return the name of the class these permissions apply to. - */ - public String getName() { - return name; - } - - /** - * Returns all Class level permissions for the class defined by {@link #getName()}. This is the - * default set of permissions for the class unless otherwise re-defined by object level - * permissions. - * - * @return all Class level permissions - */ - public RealmList getPermissions() { - return permissions; - } - - /** - * Finds the permissions associated with a given {@link Role}. If either the role or the permission - * object doesn't exists, it will be created. - *

            - * If the {@link Permission} object is created because one didn't exist already, it will be - * created with all privileges disabled. - *

            - * If the the {@link Role} object is created because one didn't exists, it will be created - * with no members. - * - * @param roleName name of the role to find. - * @return permission object for the given role. - * @throws IllegalStateException if this object is not managed by Realm. - * @throws IllegalStateException if this method is not called inside a write transaction. - * @throws IllegalArgumentException if a {@code null} or empty - */ - public Permission findOrCreate(String roleName) { - // Error handling done in the helper class - return PermissionHelper.findOrCreatePermissionForRole(this, permissions, roleName); - } - -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPrivileges.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPrivileges.java deleted file mode 100644 index dbaea92950..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/ClassPrivileges.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.sync.permissions; - -import io.realm.internal.annotations.ObjectServer; - -/** - * This object combines all privileges granted on the Class by all Roles which the - * current User is a member of into the final privileges which will be enforced by - * the server. - * - * The privilege calculation is done locally using cached data, and inherently may - * be stale. It is possible that this method may indicate that an operation is - * permitted but the server will still reject it if permission is revoked before - * the changes have been integrated on the server. If this happens, the server will - * automatically revoke any illegal operations. - * - * Non-synchronized Realms always have permission to perform all operations. - */ -@ObjectServer -public final class ClassPrivileges { - - private boolean canRead; - private boolean canUpdate; - private boolean canDelete; - private boolean canSetPermissions; - private boolean canQuery; - private boolean canCreate; - private boolean canModifySchema; - - public ClassPrivileges(long privileges) { - this.canRead = (privileges & (1 << 0)) != 0; - this.canUpdate = (privileges & (1 << 1)) != 0; - this.canDelete = (privileges & (1 << 2)) != 0; - this.canSetPermissions = (privileges & (1 << 3)) != 0; - this.canQuery = (privileges & (1 << 4)) != 0; - this.canCreate = (privileges & (1 << 5)) != 0; - this.canModifySchema = (privileges & (1 << 6)) != 0; - } - - /** - * Returns whether or not the user can read objects of this type. - *

            - * If {@code false}, the current User is not permitted to see objects of this type, and - + attempting to query this class will always return empty results. - +

            - + Note that Read permissions are transitive, and so it may be possible to read an - + object which the user does not directly have Read permissions for by following a - + link to it from an object they do have Read permissions for. This does not apply - + to any of the other permission types. - * - * @return {@code true} if the user can read objects of the given type, {@code false} if not. - */ - public boolean canRead() { - return canRead; - } - - /** - * Returns whether or not the user can update objects of the given type. - *

            - * If {@code true}, the user is allowed to update properties on all objects of this type in - * the Realm. This does not include updating permissions nor creating or deleting objects. - * - * @return {@code true} if the user can update objects of the given type, {@code false} if not. - */ - public boolean canUpdate() { - return canUpdate; - }; - - /** - * Returns whether or not the user can change the {@link ClassPermissions} object representing - * the given class. See this clas for further details. - * - * @return {@code true} if the user can modify the {@link ClassPermissions} object for the given - * class, {@code false} if not. - * @see ClassPermissions - */ - public boolean canSetPermissions() { - return canSetPermissions; - }; - - /** - * Returns whether or not the user can query the given class. - *

            - * If this returns {@code false}, queries can still be run, but they will always return the - * empty result. This can be useful to prevent people from querying leaf objects in a tree - * structure and force them to only access objects through some parent objects that reference - * them. - * - * @return {@code true} if the user can query the given class, {@code false} if not. - */ - public boolean canQuery() { - return canQuery; - } - - /** - * Returns whether or not this user is allowed to create objects of this type. - * - * @return {@code true} if the user can create objects of this type, {@code false} if not. - */ - public boolean canCreate() { - return canCreate; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ClassPrivileges that = (ClassPrivileges) o; - - if (canRead != that.canRead) return false; - if (canUpdate != that.canUpdate) return false; - if (canDelete != that.canDelete) return false; - if (canSetPermissions != that.canSetPermissions) return false; - if (canQuery != that.canQuery) return false; - if (canCreate != that.canCreate) return false; - return canModifySchema == that.canModifySchema; - } - - @Override - public int hashCode() { - int result = (canRead ? 1 : 0); - result = 31 * result + (canUpdate ? 1 : 0); - result = 31 * result + (canDelete ? 1 : 0); - result = 31 * result + (canSetPermissions ? 1 : 0); - result = 31 * result + (canQuery ? 1 : 0); - result = 31 * result + (canCreate ? 1 : 0); - result = 31 * result + (canModifySchema ? 1 : 0); - return result; - } - - @Override - public String toString() { - return "RealmPrivileges{" + - "canRead=" + canRead + - ", canUpdate=" + canUpdate + - ", canDelete=" + canDelete + - ", canSetPermissions=" + canSetPermissions + - ", canQuery=" + canQuery + - ", canCreate=" + canCreate + - ", canModifySchema=" + canModifySchema + - '}'; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/ObjectPrivileges.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/ObjectPrivileges.java deleted file mode 100644 index 2ffff816da..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/ObjectPrivileges.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.sync.permissions; - -import io.realm.Realm; -import io.realm.RealmModel; -import io.realm.internal.annotations.ObjectServer; - -/** - * This object combines all privileges granted on a Realm object by all Roles which the - * current User is a member of into the final privileges which will be enforced by - * the server. - * - * The privilege calculation is done locally using cached data, and inherently may - * be stale. It is possible that this method may indicate that an operation is - * permitted but the server will still reject it if permission is revoked before - * the changes have been integrated on the server. If this happens, the server will - * automatically revoke any illegal operations. - * - * Non-synchronized Realms always have permission to perform all operations. - */ -@ObjectServer -public final class ObjectPrivileges { - - private boolean canRead; - private boolean canUpdate; - private boolean canDelete; - private boolean canSetPermissions; - private boolean canQuery; - private boolean canCreate; - private boolean canModifySchema; - - public ObjectPrivileges(long privileges) { - this.canRead = (privileges & (1 << 0)) != 0; - this.canUpdate = (privileges & (1 << 1)) != 0; - this.canDelete = (privileges & (1 << 2)) != 0; - this.canSetPermissions = (privileges & (1 << 3)) != 0; - this.canQuery = (privileges & (1 << 4)) != 0; - this.canCreate = (privileges & (1 << 5)) != 0; - this.canModifySchema = (privileges & (1 << 6)) != 0; - } - - /** - * Returns whether or not the user can see/read the object. - * - * @return {@code true} if the user can read the object, {@code false} if not. - */ - public boolean canRead() { - return canRead; - } - - /** - * Returns whether or not the user can update fields on the object. This does not - * include deleting (see {@link #canDelete()} nor if permissions can be updated (see - * {@link #canSetPermissions()}). - * - * @return {@code true} if the user can update fields on the object, {@code false} if not. - */ - public boolean canUpdate() { - return canUpdate; - }; - - - /** - * Returns whether or not the user can delete the object. - * - * @return {@code true} if the user can delete the object, {@code false} if not. - */ - public boolean canDelete() { - return canDelete; - } - - /** - * Returns whether or not the user can change permissions on the object through its custom - * permission field (A field of the type {@code RealmList}). - * - * @return {@code true} if the user can modify the permissions on the object, {@code false} if not. - */ - public boolean canSetPermissions() { - return canSetPermissions; - }; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ObjectPrivileges that = (ObjectPrivileges) o; - - if (canRead != that.canRead) return false; - if (canUpdate != that.canUpdate) return false; - if (canDelete != that.canDelete) return false; - if (canSetPermissions != that.canSetPermissions) return false; - if (canQuery != that.canQuery) return false; - if (canCreate != that.canCreate) return false; - return canModifySchema == that.canModifySchema; - } - - @Override - public int hashCode() { - int result = (canRead ? 1 : 0); - result = 31 * result + (canUpdate ? 1 : 0); - result = 31 * result + (canDelete ? 1 : 0); - result = 31 * result + (canSetPermissions ? 1 : 0); - result = 31 * result + (canQuery ? 1 : 0); - result = 31 * result + (canCreate ? 1 : 0); - result = 31 * result + (canModifySchema ? 1 : 0); - return result; - } - - @Override - public String toString() { - return "RealmPrivileges{" + - "canRead=" + canRead + - ", canUpdate=" + canUpdate + - ", canDelete=" + canDelete + - ", canSetPermissions=" + canSetPermissions + - ", canQuery=" + canQuery + - ", canCreate=" + canCreate + - ", canModifySchema=" + canModifySchema + - '}'; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java deleted file mode 100644 index 50f8ce2d87..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/Permission.java +++ /dev/null @@ -1,573 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.sync.permissions; - -import io.realm.RealmObject; -import io.realm.annotations.RealmClass; -import io.realm.internal.annotations.ObjectServer; - -/** - * This class encapsulates the privileges granted a given {@link Role}. These privileges can be - * applied to either the entire Realm, Classes or individual objects. - *

            - * If no privileges are defined for an individual object, the values {@link ClassPermissions} - * will be inherited, if no values are defined there, the ones from {@link RealmPermissions} will - * be used. If no values can be found there, no privileges are granted. - *

            - * Not all privileges are meaningful all levels, e.g. `canCreate` is only meaningful when applied to - * classes, but it can still be defined at the Realm level. In that case all class permission objects - * will inherit the value unless they specifically override it. See the individual privileges for the - * details. - *

            - * When added to either {@link RealmPermissions}, {@link ClassPermissions} or a {@link RealmObject}, - * only one Permission object can exist for that role. If multiple objects are added the behavior - * is undefined and the Object Server might modify or delete both objects. - * - * @see Object Level Permissions for an detailed description of the Realm Object - * Server permission system. - */ -@ObjectServer -@RealmClass(name = "__Permission") -public class Permission extends RealmObject { - - /** - * Creates a {@link Permission} object in a fluid manner. - */ - public static class Builder { - private Role role; - private boolean canRead = false; - private boolean canUpdate = false; - private boolean canDelete = false; - private boolean canSetPermissions = false; - private boolean canQuery = false; - private boolean canCreate = false; - private boolean canModifySchema = false; - - /** - * Creates the builder. The default state is that no privileges are enabled. - * - * @param role {@link Role} for which these privileges apply. - */ - public Builder(Role role) { - this.role = role; - } - - /** - * Enables all privileges. - */ - public Builder allPrivileges() { - canRead = true; - canUpdate = true; - canDelete = true; - canSetPermissions = true; - canQuery = true; - canCreate = true; - canModifySchema = true; - return this; - } - - /** - * Disables all privileges. - */ - public Builder noPrivileges() { - canRead = false; - canUpdate = false; - canDelete = false; - canSetPermissions = false; - canQuery = false; - canCreate = false; - canModifySchema = false; - return this; - } - - /** - * Defines if this role can read from given resource or not. - * - *

              - *
            1. - * Realm: - * The role is allowed to read all objects from the Realm. If {@code false}, the - * Realm will appear completely empty to the role, effectively making it inaccessible. - *
            2. - *
            3. - * Class: - * The role is allowed to read the objects of this type and all referenced objects, - * even if those objects themselves have set this to {@code false}. - * If {@code false}, the role cannot see any object of this type and all queries - * against the type will return no results. - *
            4. - *
            5. - * Object: - * Determines if a role is allowed to see the individual object or not. - *
            6. - *
            - * - * @param canRead {@code true} if the role is allowed to read this resource, {@code false} if not. - */ - public Builder canRead(boolean canRead) { - this.canRead = canRead; - return this; - } - - /** - * Defines if this role can update the given resource or not. - * - *
              - *
            1. - * Realm: - * If {@code true}, the role is allowed update properties on all objects in the Realm. - * This does not include updating permissions nor creating or deleting objects. - *
            2. - *
            3. - * Class: - * If {@code true}, the role is allowed update properties on all objects of this type in - * the Realm. This does not include updating permissions nor creating or deleting objects. - *
            4. - *
            5. - * Object: - * If {@code true}, the role is allowed to update properties on the object. This - * does not cover updating permissions or deleting the object. - *
            6. - *
            - * - * @param canUpdate {@code true} if the role is allowed to update this resource, {@code false} if not. - */ - public Builder canUpdate(boolean canUpdate) { - this.canUpdate = canUpdate; - return this; - } - - /** - * Defines if this role can delete the given resource or not. - * - *
              - *
            1. - * Realm: - * Not applicable. - *
            2. - *
            3. - * Class: - * Not applicable. - *
            4. - *
            5. - * Object: - * If {@code true}, the role is allowed to delete the object. - *
            6. - *
            - * - * @param canDelete {@code true} if the role is allowed to delete this resource, {@code false} if not. - */ - public Builder canDelete(boolean canDelete) { - this.canDelete = canDelete; - return this; - } - - /** - * Defines if this role is allowed to change permissions on the given resource. - * Permissions can only be granted at the same permission level or below. E.g. if set on - * a Class, it is not possible to change Realm level permissions, but does allow the role to - * change object level permissions for objects of that type. - * - *
              - *
            1. - * Realm: - * The role is allowed to modify the {@link RealmPermissions} object. - *
            2. - *
            3. - * Class: - * The role is allowed the change the {@link ClassPermissions} object. - *
            4. - *
            5. - * Object: - * The role is allowed to change the permissions on this object. - *
            6. - *
            - * - * @param canSetPermissions {@code true} if the role is allowed to change the permissions for this resource. - */ - public Builder canSetPermissions(boolean canSetPermissions) { - this.canSetPermissions = canSetPermissions; - return this; - } - - /** - * Defines if this role is allowed to query the resource or not. - *

            - * Note, that local queries are always possible, but the query result will just be empty. - * - *

              - *
            1. - * Realm: - * Not applicable. - *
            2. - *
            3. - * Class: - * The role is allowed to query objects of this type. - *
            4. - *
            5. - * Object: - * Not applicable. - *
            6. - *
            - * - * @param canQuery {@code true} if the role is allowed to query objects of this type. - */ - public Builder canQuery(boolean canQuery) { - this.canQuery = canQuery; - return this; - } - - - /** - * Defines if this role is allowed to create objects of this type. - * - *
              - *
            1. - * Realm: - * Not applicable. - *
            2. - *
            3. - * Class: - * If {@code true}, the role is allowed to create objects of this type. - *
            4. - *
            5. - * Object: - * Not applicable. - *
            6. - *
            - * - * @param canCreate {@code true} if the role is allowed to create objects of this type. - */ - public Builder canCreate(boolean canCreate) { - this.canCreate = canCreate; - return this; - } - - /** - * Defines if this role is allowed to modify the schema of this resource. - * - *
              - *
            1. - * Realm: - * If {@code true} the role is allowed to create classes in the Realm. - *
            2. - *
            3. - * Class: - * If {@code true}, the role is allowed to add properties to the specified class. - *
            4. - *
            5. - * Object: - * Not applicable. - *
            6. - *
            - * - * @param canModifySchema {@code true} if the role is allowed to modify the schema of this resource. - */ - public Builder canModifySchema(boolean canModifySchema) { - this.canModifySchema = canModifySchema; - return this; - } - - /** - * Creates the unmanaged {@link Permission} object. - */ - public Permission build() { - return new Permission( - role, - canRead, - canUpdate, - canDelete, - canSetPermissions, - canQuery, - canCreate, - canModifySchema - ); - } - } - - private Role role; - private boolean canRead; - private boolean canUpdate; - private boolean canDelete; - private boolean canSetPermissions; - private boolean canQuery; - private boolean canCreate; - private boolean canModifySchema; - - public Permission() { - // Required by Realm - } - - /** - * Creates a set of privileges for the given role. - */ - public Permission(Role role) { - this.role = role; - } - - /** - * Creates a set of privileges for the given role. - */ - private Permission(Role role, boolean canRead, boolean canUpdate, boolean canDelete, boolean canSetPermissions, boolean canQuery, boolean canCreate, boolean canModifySchema) { - this.role = role; - this.canRead = canRead; - this.canUpdate = canUpdate; - this.canDelete = canDelete; - this.canSetPermissions = canSetPermissions; - this.canQuery = canQuery; - this.canCreate = canCreate; - this.canModifySchema = canModifySchema; - } - - /** - * Returns the role these privileges apply to. - * - * @return the role these privileges apply to. - */ - public Role getRole() { - return role; - } - - /** - * Returns {@code true} if the role is allowed to read the resource, {@code false} if not. - */ - public boolean canRead() { - return canRead; - } - - /** - * Defines if this role can read from given resource or not. - * - *
              - *
            1. - * Realm: - * The role is allowed to read all objects from the Realm. If {@code false}, the - * Realm will appear completely empty to the role, effectively making it inaccessible. - *
            2. - *
            3. - * Class: - * The role is allowed to read the objects of this type and all referenced objects, - * even if those objects themselves have set this to {@code false}. - * If {@code false}, the role cannot see any object of this type and all queries - * against the type will return no results. - *
            4. - *
            5. - * Object: - * Determines if a role is allowed to see the individual object or not. - *
            6. - *
            - * - * @param canRead {@code true} if the role is allowed to read this resource, {@code false} if not. - */ - public void setCanRead(boolean canRead) { - this.canRead = canRead; - } - - /** - * Returns {@code true} if the role is allowed to update the resource, {@code false} if not. - */ - public boolean canUpdate() { - return canUpdate; - } - - /** - * Defines if this role can update the given resource or not. - * - *
              - *
            1. - * Realm: - * If {@code true}, the role is allowed update properties on all objects in the Realm. - * This does not include updating permissions nor creating or deleting objects. - *
            2. - *
            3. - * Class: - * If {@code true}, the role is allowed update properties on all objects of this type in - * the Realm. This does not include updating permissions nor creating or deleting objects. - *
            4. - *
            5. - * Object: - * If {@code true}, the role is allowed to update properties on the object. This - * does not cover updating permissions or deleting the object. - *
            6. - *
            - * - * @param canUpdate {@code true} if the role is allowed to update this resource, {@code false} if not. - */ - public void setCanUpdate(boolean canUpdate) { - this.canUpdate = canUpdate; - } - - /** - * Returns {@code true} if the role is allowed to delete the object , {@code false} if not. - */ - public boolean canDelete() { - return canDelete; - } - - /** - * Defines if this role can delete the given resource or not. - * - *
              - *
            1. - * Realm: - * Not applicable. - *
            2. - *
            3. - * Class: - * Not applicable. - *
            4. - *
            5. - * Object: - * If {@code true}, the role is allowed to delete the object. - *
            6. - *
            - * - * @param canDelete {@code true} if the role is allowed to delete this resource, {@code false} if not. - */ - public void setCanDelete(boolean canDelete) { - this.canDelete = canDelete; - } - - /** - * Returns {@code true} if this this role is allowed to change permissions on the given resource. - */ - public boolean canSetPermissions() { - return canSetPermissions; - } - - /** - * Defines if this role is allowed to change permissions on the given resource. - * Permissions can only be granted at the same permission level or below. E.g. if set on - * a Class, it is not possible to change Realm level permissions, but does allow the role to - * change object level permissions for objects of that type. - * - *
              - *
            1. - * Realm: - * The role is allowed to modify the {@link RealmPermissions} object. - *
            2. - *
            3. - * Class: - * The role is allowed the change the {@link ClassPermissions} object. - *
            4. - *
            5. - * Object: - * The role is allowed to change the permissions on this object. - *
            6. - *
            - * - * @param canSetPermissions {@code true} if the role is allowed to change the permissions for this resource. - */ - public void setCanSetPermissions(boolean canSetPermissions) { - this.canSetPermissions = canSetPermissions; - } - - /** - * Returns {@code true} if the role is allowed to query the resource, {@code false} if not. - */ - public boolean canQuery() { - return canQuery; - } - - /** - * Defines if this role is allowed to query the resource or not. - *

            - * Note, that local queries are always possible, but the query result will just be empty. - * - *

              - *
            1. - * Realm: - * Not applicable. - *
            2. - *
            3. - * Class: - * The role is allowed to query objects of this type. - *
            4. - *
            5. - * Object: - * Not applicable. - *
            6. - *
            - * - * @param canQuery {@code true} if the role is allowed to query objects of this type. - */ - public void setCanQuery(boolean canQuery) { - this.canQuery = canQuery; - } - - /** - * Returns {@code true} if the role is allowed to create objects, {@code false} if not. - */ - public boolean canCreate() { - return canCreate; - } - - /** - * Defines if this role is allowed to create objects of this type. - * - *
              - *
            1. - * Realm: - * Not applicable. - *
            2. - *
            3. - * Class: - * If {@code true}, the role is allowed to create objects of this type. - *
            4. - *
            5. - * Object: - * Not applicable. - *
            6. - *
            - * - * @param canCreate {@code true} if the role is allowed to create objects of this type. - */ - public void setCanCreate(boolean canCreate) { - this.canCreate = canCreate; - } - - /** - * Returns {@code true} if the role is allowed to modify the schema of the resource, - * {@code false} if not. - */ - public boolean canModifySchema() { - return canModifySchema; - } - - /** - * Defines if this role is allowed to modify the schema of this resource. - * - *
              - *
            1. - * Realm: - * If {@code true} the role is allowed to create classes in the Realm. - *
            2. - *
            3. - * Class: - * If {@code true}, the role is allowed to add properties to the specified class. - *
            4. - *
            5. - * Object: - * Not applicable. - *
            6. - *
            - * - * @param canModifySchema {@code true} if the role is allowed to modify the schema of this resource. - */ - public void setCanModifySchema(boolean canModifySchema) { - this.canModifySchema = canModifySchema; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java deleted file mode 100644 index 1146f947a5..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/PermissionUser.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.sync.permissions; - -import javax.annotation.Nullable; - -import io.realm.RealmObject; -import io.realm.RealmResults; -import io.realm.annotations.LinkingObjects; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmClass; -import io.realm.annotations.Required; -import io.realm.internal.annotations.ObjectServer; - -/** - * Class describes a user in the Realm Object Servers Permission system. - * The Id should be identical to the value from {@code SyncUser.getIdentity()} - * - * @see Object Level Permissions for an detailed description of the Realm Object - * Server permission system. - */ -@ObjectServer -@RealmClass(name = "__User") -public class PermissionUser extends RealmObject { - @PrimaryKey - @Required - private String id; - - private Role role; - - @LinkingObjects("members") - final RealmResults roles = null; - - public PermissionUser() { - // Required by Realm - } - - /** - * Creates a new user. - * - * @param id identify of the user. Should be identitical to {@code SyncUser.getIdentity()}. - */ - public PermissionUser(String id) { - this.id = id; - } - - /** - * Returns the identify of this user. - * - */ - public String getId() { - return id; - } - - - /** - * Returns all {@link Role}s this user has. - * - * @return all roles this user has. - */ - public @Nullable RealmResults getRoles() { - return roles; - } - - /** - * The user's private role. This will be initialized to a role named for the user's - * identity that contains this user as its only member. - * - * @return User private {@link Role}. - */ - public Role getPrivateRole() { - return role; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java deleted file mode 100644 index 1b8915e245..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPermissions.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.sync.permissions; - -import io.realm.Realm; -import io.realm.RealmList; -import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmClass; -import io.realm.internal.annotations.ObjectServer; -import io.realm.internal.sync.PermissionHelper; - -/** - * Class describing all permissions related to a given Realm. Permissions attached to this class - * are treated as the default permissions if not otherwise overridden by {@link ClassPermissions} - * or object level permissions. - * - * @see Object Level Permissions for an detailed description of the Realm Object - * Server permission system. - */ -@ObjectServer -@RealmClass(name = "__Realm") -public class RealmPermissions extends RealmObject { - @PrimaryKey - private int id = 0; // Singleton object for the Realm file - private RealmList permissions = new RealmList<>(); - - public RealmPermissions() { - // Required by Realm - } - - /** - * Returns all Realm level permissions, i.e. permissions that apply to the Realm as a whole. - * - * @return all Realm level permissions - */ - public RealmList getPermissions() { - return permissions; - } - - /** - * Finds the permissions associated with a given {@link Role}. If either the role or the permission - * object doesn't exists, it will be created. - *

            - * If the {@link Permission} object is created because one didn't exist already, it will be - * created with all privileges disabled. - *

            - * If the role {@link Role} object is created because one didn't exists, it will be created - * with no members. - * - * @param roleName name of the role to find. - * @return permission object for the given role. - * @throws IllegalStateException if this object is not managed by Realm. - * @throws IllegalStateException if this method is not called inside a write transaction. - * @throws IllegalArgumentException if a {@code null} or empty - */ - public Permission findOrCreate(String roleName) { - // Error handling done in the helper class - return PermissionHelper.findOrCreatePermissionForRole(this, permissions, roleName); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPrivileges.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPrivileges.java deleted file mode 100644 index 6213302d28..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/RealmPrivileges.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.sync.permissions; - -import io.realm.Realm; -import io.realm.RealmModel; -import io.realm.internal.annotations.ObjectServer; - -/** - * This object combines all privileges granted on the Realm by all Roles which the - * current User is a member of into the final privileges which will be enforced by - * the server. - * - * The privilege calculation is done locally using cached data, and inherently may - * be stale. It is possible that this method may indicate that an operation is - * permitted but the server will still reject it if permission is revoked before - * the changes have been integrated on the server. If this happens, the server will automatically - * revoke any illegal operations. - * - * Non-synchronized Realms always have permission to perform all operations. - */ -@ObjectServer -public final class RealmPrivileges { - - private boolean canRead; - private boolean canUpdate; - private boolean canDelete; - private boolean canSetPermissions; - private boolean canQuery; - private boolean canCreate; - private boolean canModifySchema; - - public RealmPrivileges(long privileges) { - this.canRead = (privileges & (1 << 0)) != 0; - this.canUpdate = (privileges & (1 << 1)) != 0; - this.canDelete = (privileges & (1 << 2)) != 0; - this.canSetPermissions = (privileges & (1 << 3)) != 0; - this.canQuery = (privileges & (1 << 4)) != 0; - this.canCreate = (privileges & (1 << 5)) != 0; - this.canModifySchema = (privileges & (1 << 6)) != 0; - } - - /** - * Returns whether or not can see this Realm. If {@code true}, the user is allowed to read all - * objects and classes from the Realm. If {@code false}, the Realm will appear completely empty - * (including having no schema), effectively making it inaccessible. - * - * @return {@code true} if the user can see the Realm, {@code false} if not. - */ - public boolean canRead() { - return canRead; - } - - /** - * Returns whether or not the user can update Realm objects. If {@code true}, the user is - * allowed to update properties on all objects in the Realm. This does not include updating - * permissions nor creating or deleting objects. If {@code false}, the Realm is effectively - * read-only. - *

            - * This property also in part control if schema updates are possible. If this returns - * {@code false}, the user is not allowed to update the schema, if {@code true}, schema updates - * are allowed if {@link #canModifySchema()} also returns {@code true}. - * - * @return {@code true} if the user can update this Realm, {@code false} if not. - */ - public boolean canUpdate() { - return canUpdate; - }; - - /** - * Returns whether or not the user can change {@link RealmPermissions}. See this class for - * further information. - * - * @return {@code true} if the user can modify the {@link RealmPermissions} object, - * {@code false} if not. - * @see RealmPermissions - */ - public boolean canSetPermissions() { - return canSetPermissions; - }; - - /** - * Returns whether or not the user can modify the schema of the given resource. - * - *

              - *
            1. - * Realm: - * If {@code true} the user is allowed to create classes in the Realm. - *
            2. - *
            3. - * Class: - * If {@code true}, the user is allowed to add properties to the given class. - *
            4. - *
            5. - * Object: - * Not applicable. - *
            6. - *
            - * - * @return {@code true} if the user can modify the schema of the given resource, {@code false} if not. - */ - public boolean canModifySchema() { - return canModifySchema; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - RealmPrivileges that = (RealmPrivileges) o; - - if (canRead != that.canRead) return false; - if (canUpdate != that.canUpdate) return false; - if (canDelete != that.canDelete) return false; - if (canSetPermissions != that.canSetPermissions) return false; - if (canQuery != that.canQuery) return false; - if (canCreate != that.canCreate) return false; - return canModifySchema == that.canModifySchema; - } - - @Override - public int hashCode() { - int result = (canRead ? 1 : 0); - result = 31 * result + (canUpdate ? 1 : 0); - result = 31 * result + (canDelete ? 1 : 0); - result = 31 * result + (canSetPermissions ? 1 : 0); - result = 31 * result + (canQuery ? 1 : 0); - result = 31 * result + (canCreate ? 1 : 0); - result = 31 * result + (canModifySchema ? 1 : 0); - return result; - } - - @Override - public String toString() { - return "RealmPrivileges{" + - "canRead=" + canRead + - ", canUpdate=" + canUpdate + - ", canDelete=" + canDelete + - ", canSetPermissions=" + canSetPermissions + - ", canQuery=" + canQuery + - ", canCreate=" + canCreate + - ", canModifySchema=" + canModifySchema + - '}'; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java deleted file mode 100644 index dc72ac906a..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/Role.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.sync.permissions; - -import io.realm.Realm; -import io.realm.RealmList; -import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmClass; -import io.realm.annotations.Required; -import io.realm.internal.Util; -import io.realm.internal.annotations.ObjectServer; - -/** - * A role describes a function or area of authority in the Realm Object Server permission system. - * Multiple users can have the same role and a role can be assigned different permissions. - * - * @see Object Level Permissions for an detailed description of the Realm Object - * Server permission system. - */ -@ObjectServer -@RealmClass(name = "__Role") -public class Role extends RealmObject { - @PrimaryKey - @Required - private String name; - private RealmList members = new RealmList<>(); - - public Role() { - // Required by Realm; - } - - /** - * Creates a new named role. The name must be unique. - * - * @param name a unique name for the role. - */ - public Role(String name) { - this.name = name; - } - - /** - * Returns the name of this role. - * - * @return name of this role. - */ - public String getName() { - return name; - } - - /** - * Adds a member to this Role. Must be done from within a write transaction. - * - * @param userId userid of the SyncUser. - * @throws IllegalStateException if not in a write transaction. - * @throws IllegalArgumentException if {@code null} or empty {@code userId} is provided. - */ - public void addMember(String userId) { - if (isManaged()) { - if (Util.isEmptyString(userId)) { - throw new IllegalArgumentException("Non-empty 'userId' required"); - } - Realm realm = getRealm(); - PermissionUser user = realm.where(PermissionUser.class).equalTo("id", userId).findFirst(); - if (user == null) { - user = realm.createObject(PermissionUser.class, userId); - } - members.add(user); - - } else { - throw new IllegalStateException("Can not add a member to a non managed Role"); - } - } - - /** - * Removes a member from this Role. Must be done from within a write transaction. - * - * @param userId userid of the SyncUser to remove. - * @return {@code true} if the user could be removed, {@code false} if not. - * @throws IllegalStateException if not in a write transaction. - */ - public boolean removeMember(String userId) { - PermissionUser user = getRealm().where(PermissionUser.class).equalTo("id", userId).findFirst(); - if (user != null) { - return members.remove(user); - } else { - return false; - } - } - - /** - * Checks if the provided user has this role. - * - * @param userId user to check - * @return {@code true} if the user has this role, {@code false} if not. - */ - public boolean hasMember(String userId) { - return members.where().equalTo("id", userId).count() > 0; - } - - /** - * Returns the list of {@link PermissionUser} within this role. - * - * @return list of members associated with this role. - */ - public RealmList getMembers() { - return members; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/permissions/package-info.java b/realm/realm-library/src/main/java/io/realm/sync/permissions/package-info.java deleted file mode 100644 index 9c4e4ae3be..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/permissions/package-info.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -@javax.annotation.ParametersAreNonnullByDefault -package io.realm.sync.permissions; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index a31b9a11db..48867a013c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -34,16 +34,15 @@ import javax.annotation.Nullable; -import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.OsRealmConfig; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Util; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.log.RealmLog; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; /** * A {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index d8cdc0847f..10c8b4dee4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -39,24 +39,15 @@ import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.internal.network.AcceptPermissionsOfferResponse; -import io.realm.internal.network.ApplyPermissionsResponse; import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.network.RealmObjectServer; import io.realm.internal.network.ChangePasswordResponse; import io.realm.internal.network.ExponentialBackoffTask; -import io.realm.internal.network.GetPermissionsOffersResponse; -import io.realm.internal.network.InvalidatePermissionsOfferResponse; import io.realm.internal.network.LogoutResponse; -import io.realm.internal.network.RetrievePermissionsResponse; import io.realm.internal.network.LookupUserIdResponse; -import io.realm.internal.network.MakePermissionsOfferResponse; +import io.realm.internal.network.RealmObjectServer; import io.realm.internal.network.UpdateAccountResponse; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; -import io.realm.permissions.Permission; -import io.realm.permissions.PermissionOffer; -import io.realm.permissions.PermissionRequest; /** * This class represents a user on the Realm Object Server. The credentials are provided by various 3rd party @@ -942,326 +933,6 @@ private static String getManagementRealmUrl(URL authUrl) { } } - /** - * Retrieves the list of permissions granted to this user. The data is fetched directly from - * the Realm Object Server and requires a network connection. - * - * @return the list of permissions granted to this user. - * @throws ObjectServerError if an error happened while trying to retrieve the list of permissions on the Realm Object Server. - * @throws android.os.NetworkOnMainThreadException if called from the UI thread. - */ - public List retrieveGrantedPermissions() { - ObjectServerError error; - try { - final RealmObjectServer server = SyncManager.getAuthServer(); - RetrievePermissionsResponse result = server.getPermissions(refreshToken, baseUrl); - if (result.isValid()) { - return result.getPermissions(); - } else { - error = result.getError(); - } - } catch (Throwable e) { - throw new ObjectServerError(ErrorCode.UNKNOWN, e); - } - throw error; - } - - /** - * Retrieves the list of permissions granted to this user. The data is fetched directly from - * the Realm Object Server and requires a network connection. - * - * @param callback callback notified when list the permissions are ready. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - * - * @throws IllegalStateException if this method is called from a thread without a looper. - */ - public RealmAsyncTask retrieveGrantedPermissionsAsync(Callback> callback) { - checkLooperThread("Asynchronously retrieving permissions is only possible from looper threads."); - checkCallbackNotNull(callback); - return new Request>(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public List run() throws ObjectServerError { - return retrieveGrantedPermissions(); - } - }.start(); - } - - - /** - * Applies a given set of permissions to a Realm. Only a user with {@link io.realm.permissions.AccessLevel#ADMIN} - * privileges to the Realm can use this method. - *

            - * A {@link PermissionRequest} object encapsulates a description of which users are granted what - * {@link io.realm.permissions.AccessLevel}s for which Realm(s). - *

            - * Once the request is successfully handled, a {@link Permission} entry is created for each - * affected user and can be found by them using {@link #retrieveGrantedPermissions()}. - * - * @param request request object describing which permissions to grant and to what Realm(s). - * @throws ObjectServerError if an error happened while trying to apply the permission changes on the Realm Object Server. - * @throws android.os.NetworkOnMainThreadException if called from the UI thread. - */ - public void applyPermissions(PermissionRequest request) { - ObjectServerError error; - try { - final RealmObjectServer server = SyncManager.getAuthServer(); - ApplyPermissionsResponse result = server.applyPermissions(request, refreshToken, baseUrl); - if (!result.isValid()) { - error = result.getError(); - } else { - return; - } - } catch (Exception e) { - throw new ObjectServerError(ErrorCode.UNKNOWN, e); - } - throw error; - } - - /** - * Applies a given set of permissions to a Realm. Only a user with {@link io.realm.permissions.AccessLevel#ADMIN} - * privileges to the Realm can use this method. - *

            - * A {@link PermissionRequest} object encapsulates a description of which users are granted what - * {@link io.realm.permissions.AccessLevel}s for which Realm(s). - *

            - * Once the request is successfully handled, a {@link Permission} entry is created for each - * affected user and can be found by them using {@link #retrieveGrantedPermissionsAsync(Callback)}. - * - * @param request request object describing which permissions to grant and to what Realm(s). - * @param callback callback when the request either succeeded or failed. - * @return async task representing the request. This can be used to cancel it if needed. - * - * @throws IllegalStateException if this method is called from a thread without a looper. - */ - public RealmAsyncTask applyPermissionsAsync(PermissionRequest request, Callback callback) { - checkLooperThread("Asynchronously updating permissions is only possible from looper threads."); - checkCallbackNotNull(callback); - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public Void run() throws ObjectServerError { - applyPermissions(request); - return null; - } - }.start(); - } - - /** - * Makes a permissions offer to users. The offer is represented by an offer token and the permission changes - * described in the {@link PermissionOffer} do not take effect until the offer has been accepted by a user - * calling {@link #acceptPermissionsOfferAsync(String, Callback)}. - *

            - * A permission offer can be used as a flexible way of sharing Realms with other users that might not be known at the time - * of making the offer as well as enabling sharing across other channels like e-mail. If a specific user should be - * granted access, using {@link #applyPermissionsAsync(PermissionRequest, Callback)} will be faster and quicker. - *

            - * An offer can be accepted by multiple users. - * - * @param offer the object description the kind of permissions that should be offered to other users. - * @return the offer token representing the offer. - * @throws ObjectServerError if an error happened while trying to create the permissions offer. - * @throws android.os.NetworkOnMainThreadException if called from the UI thread. - * @see Permissions description for general - * documentation. - * @see Modifying permissions for a more - * high level description. - */ - public String makePermissionsOffer(PermissionOffer offer) { - ObjectServerError error; - try { - final RealmObjectServer server = SyncManager.getAuthServer(); - MakePermissionsOfferResponse result = server.makeOffer(offer, refreshToken, baseUrl); - if (!result.isValid()) { - error = result.getError(); - } else { - return result.getToken(); - } - } catch (Exception e) { - throw new ObjectServerError(ErrorCode.UNKNOWN, e); - } - throw error; - } - - /** - * Makes a permission offer to users. The offer is represented by an offer token and the permission changes - * described in the {@link PermissionOffer} do not take effect until the offer has been accepted by a user - * calling {@link #acceptPermissionsOfferAsync(String, Callback)}. - *

            - * A permission offer can be used as a flexible way of sharing Realms with other users that might not be known at the time - * of making the offer as well as enabling sharing across other channels like e-mail. If a specific user should be - * granted access, using {@link #applyPermissionsAsync(PermissionRequest, Callback)} will be faster and quicker. - *

            - * An offer can be accepted by multiple users. - * - * @return the path to the Realm affected by this permission. - * @throws android.os.NetworkOnMainThreadException if called from the UI thread. - * @see Permissions description for general - * documentation. - * @see Modifying permissions for a more - * high level description. - * - * @param offer the object description the kind of permissions that should be offered to other users. - * @param callback callback to be notified with the offer token once it is ready. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - * @throws IllegalStateException if this method is called from a Thread without a looper. - * @see Permissions description for general - * documentation. - * @see Modifying permissions for a more - * high level description. - */ - public RealmAsyncTask makePermissionsOfferAsync(PermissionOffer offer, Callback callback) { - checkLooperThread("Asynchronously making an offer is only possible from looper threads."); - checkCallbackNotNull(callback); - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public String run() throws ObjectServerError { - return makePermissionsOffer(offer); - } - }.start(); - } - - /** - * Accepts a permission offer sent by another user. Once this offer is accepted successfully, the permissions - * described by the token will be granted. - * - * @param offerToken token representing the permission offer. - * @return the path to the Realm affected by the offer. - * @throws ObjectServerError if an error happened while trying to accept the offer. - * @throws android.os.NetworkOnMainThreadException if called from the UI thread. - */ - public String acceptPermissionsOffer(String offerToken) { - if (Util.isEmptyString(offerToken)) { - throw new IllegalArgumentException("Non-empty 'offerToken' required."); - } - ObjectServerError error; - try { - final RealmObjectServer server = SyncManager.getAuthServer(); - AcceptPermissionsOfferResponse result = server.acceptOffer(offerToken, refreshToken, baseUrl); - if (!result.isValid()) { - error = result.getError(); - } else { - return result.getPath(); - } - } catch (Exception e) { - throw new ObjectServerError(ErrorCode.UNKNOWN, e); - } - throw error; - } - - /** - * Accepts a permission offer sent by another user. Once this offer is accepted successfully, the permissions - * described by the token will be granted. - * - * @param offerToken token representing the permission offer. - * @param callback with the permission details that were accepted. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - * @throws IllegalStateException if this method is called from a thread without a looper. - */ - public RealmAsyncTask acceptPermissionsOfferAsync(String offerToken, Callback callback) { - checkLooperThread("Asynchronously accepting an permissions offer is only possible from looper threads."); - checkCallbackNotNull(callback); - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public String run() throws ObjectServerError { - return acceptPermissionsOffer(offerToken); - } - }.start(); - } - - /** - * Invalidates an existing offer. This will prevent any other users from accepting it. Users that already accepted it, - * will not be affected. - * - * @param offerToken token that should be invalidated. - * @throws ObjectServerError if an error happened while trying to invalidate the offer on the Realm Object Server. - * @throws android.os.NetworkOnMainThreadException if called from the UI thread. - */ - public void invalidatePermissionsOffer(String offerToken) { - if (Util.isEmptyString(offerToken)) { - throw new IllegalArgumentException("Non-empty 'offerToken' required."); - } - ObjectServerError error; - try { - final RealmObjectServer server = SyncManager.getAuthServer(); - InvalidatePermissionsOfferResponse result = server.invalidateOffer(offerToken, refreshToken, baseUrl); - if (!result.isValid()) { - error = result.getError(); - } else { - return; - } - } catch (Exception e) { - throw new ObjectServerError(ErrorCode.UNKNOWN, e); - } - throw error; - } - - /** - * Invalidates an existing offer. This will prevent any other users from accepting it. Users that already accepted it, - * will not be affected. - * - * @param offerToken token that should be invalidated. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - * @throws IllegalStateException if this method is called from a thread without a looper. - */ - public RealmAsyncTask invalidatePermissionsOfferAsync(String offerToken, SyncUser.Callback callback) { - checkLooperThread("Asynchronously accepting an permissions offer is only possible from looper threads."); - checkCallbackNotNull(callback); - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public Void run() throws ObjectServerError { - invalidatePermissionsOffer(offerToken); - return null; - } - }.start(); - } - - /** - * Returns the list of offers created by this user. These offers can be revoked again by calling - * {@link #invalidatePermissionsOfferAsync(String, Callback)} or sent to other users by sending the - * {@link PermissionOffer#getToken()}. - * - * @return the list of available offers. - * @throws ObjectServerError if an error occured while trying retrieve the list of offers from the Realm Object Server. - * @throws android.os.NetworkOnMainThreadException if called from the UI thread. - */ - public List retrieveCreatedPermissionsOffers() { - ObjectServerError error; - try { - final RealmObjectServer server = SyncManager.getAuthServer(); - GetPermissionsOffersResponse result = server.getPermissionOffers(refreshToken, baseUrl); - if (!result.isValid()) { - error = result.getError(); - } else { - return result.getOffers(); - } - } catch (Exception e) { - throw new ObjectServerError(ErrorCode.UNKNOWN, e); - } - throw error; - } - - - /** - * Returns the list of offers created by this user. These offers can be revoked again by calling - * {@link #invalidatePermissionsOfferAsync(String, Callback)} or sent to other users by sending the - * {@link PermissionOffer#getToken()}. - * - * @param callback that will receive the list of available offers. - * @return {@link RealmAsyncTask} that can be used to cancel the task if needed. - * @throws IllegalStateException if this method is called from a thread without a looper. - */ - public RealmAsyncTask retrieveCreatedPermissionsOffersAsync(Callback> callback) { - checkLooperThread("Asynchronously getting all permission offers is only possible from looper threads."); - checkCallbackNotNull(callback); - return new Request>(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public List run() throws ObjectServerError { - return retrieveCreatedPermissionsOffers(); - } - }.start(); - } - - - // what defines a user is it's identity(Token) and authURL (as required by the constructor) // // not the list of Realms it's managing, furthermore, trying to include the `realms` in the `hashCode` will diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 62f38ff221..84b6352529 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -39,8 +39,8 @@ import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.objectstore.OsAsyncOpenTask; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.sync.Subscription; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; @SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AcceptPermissionsOfferResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AcceptPermissionsOfferResponse.java deleted file mode 100644 index 293f7226d3..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AcceptPermissionsOfferResponse.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2019 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.log.RealmLog; -import okhttp3.Response; - -/** - * Class wrapping the response from `POST permissions/offers/:token:/accept` - */ -public class AcceptPermissionsOfferResponse extends AuthServerResponse { - - private String path; - - /** - * Helper method for creating the proper lookup user response. This method will set the appropriate error - * depending on any HTTP response codes or I/O errors. - * - * @param response the server response. - * @return the user lookup response. - */ - static AcceptPermissionsOfferResponse from(Response response) { - String serverResponse; - try { - serverResponse = response.body().string(); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new AcceptPermissionsOfferResponse(error); - } - if (!response.isSuccessful()) { - return new AcceptPermissionsOfferResponse(AuthServerResponse.createError(serverResponse, response.code())); - } else { - return new AcceptPermissionsOfferResponse(serverResponse); - } - } - - /** - * Helper method for creating a failed response. - */ - public static AcceptPermissionsOfferResponse from(ObjectServerError objectServerError) { - return new AcceptPermissionsOfferResponse(objectServerError); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static AcceptPermissionsOfferResponse from(Exception exception) { - return AcceptPermissionsOfferResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - private AcceptPermissionsOfferResponse(ObjectServerError error) { - RealmLog.debug("AcceptPermissionsOffer - Error: %s", error); - setError(error); - this.error = error; - } - - private AcceptPermissionsOfferResponse(String serverResponse) { - RealmLog.debug("AcceptPermissionsOffer - Success: %s", serverResponse); - try { - JSONObject obj = new JSONObject(serverResponse); - path = obj.getString("path"); - } catch (JSONException e) { - error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); - } - } - - public String getPath() { - return path; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsRequest.java deleted file mode 100644 index 4c5923a260..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsRequest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2019 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; - -import io.realm.permissions.AccessLevel; -import io.realm.permissions.PermissionRequest; -import io.realm.permissions.UserCondition; - -/** - * Class wrapping a request for updating/setting permissions `POST permissions/apply` - */ -public class ApplyPermissionsRequest { - - private final AccessLevel level; - private final String realmUrl; - private final String userId; - private final String metadataKey; - private final String metadataValue; - - public ApplyPermissionsRequest(PermissionRequest request) { - UserCondition condition = request.getCondition(); - level = request.getAccessLevel(); - realmUrl = request.getUrl(); - - switch (condition.getType()) { - case USER_ID: - userId = condition.getValue(); - metadataKey = null; - metadataValue = null; - break; - case METADATA: - userId = null; - metadataKey = condition.getKey(); - metadataValue = condition.getValue(); - break; - default: - throw new IllegalArgumentException("Unsupported type: " + condition.getType()); - } - } - - public String toJson() throws JSONException { - JSONObject request = new JSONObject(); - request.put("realmPath", realmUrl); - request.put("accessLevel", level.getKey()); - JSONObject condition = new JSONObject(); - if (userId != null) { - condition.put("userId", userId); - } else { - condition.put("metadataKey", metadataKey); - condition.put("metadataValue", metadataValue); - } - request.put("condition", condition); - return request.toString(); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsResponse.java deleted file mode 100644 index 70e150815a..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ApplyPermissionsResponse.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2019 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import java.io.IOException; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.log.RealmLog; -import okhttp3.Response; - -/** - * Class wrapping the response from `POST permissions/apply` - */ -public class ApplyPermissionsResponse extends AuthServerResponse { - - /** - * Helper method for creating the proper lookup user response. This method will set the appropriate error - * depending on any HTTP response codes or I/O errors. - * - * @param response the server response. - * @return the user lookup response. - */ - static ApplyPermissionsResponse from(Response response) { - String serverResponse; - try { - serverResponse = response.body().string(); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new ApplyPermissionsResponse(error); - } - if (!response.isSuccessful()) { - return new ApplyPermissionsResponse(AuthServerResponse.createError(serverResponse, response.code())); - } else { - return new ApplyPermissionsResponse(serverResponse); - } - } - - /** - * Helper method for creating a failed response. - */ - public static ApplyPermissionsResponse from(ObjectServerError objectServerError) { - return new ApplyPermissionsResponse(objectServerError); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static ApplyPermissionsResponse from(Exception exception) { - return ApplyPermissionsResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - private ApplyPermissionsResponse(ObjectServerError error) { - RealmLog.debug("ApplyPermissions - Error: %s", error); - setError(error); - this.error = error; - } - - private ApplyPermissionsResponse(String serverResponse) { - RealmLog.debug("ApplyPermissions - Success: %s", serverResponse); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/GetPermissionsOffersResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/GetPermissionsOffersResponse.java deleted file mode 100644 index 73df15990e..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/GetPermissionsOffersResponse.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2019 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.internal.android.JsonUtils; -import io.realm.log.RealmLog; -import io.realm.permissions.AccessLevel; -import io.realm.permissions.Permission; -import io.realm.permissions.PermissionOffer; -import okhttp3.Response; - -/** - * Class wrapping the response from `GET permissions/offers` - */ -public class GetPermissionsOffersResponse extends AuthServerResponse { - - private final List offers = new ArrayList<>(); - - /** - * Helper method for creating the proper lookup user response. This method will set the appropriate error - * depending on any HTTP response codes or I/O errors. - * - * @param response the server response. - * @return the user lookup response. - */ - static GetPermissionsOffersResponse from(Response response) { - String serverResponse; - try { - serverResponse = response.body().string(); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new GetPermissionsOffersResponse(error); - } - if (!response.isSuccessful()) { - return new GetPermissionsOffersResponse(AuthServerResponse.createError(serverResponse, response.code())); - } else { - return new GetPermissionsOffersResponse(serverResponse); - } - } - - /** - * Helper method for creating a failed response. - */ - public static GetPermissionsOffersResponse from(ObjectServerError objectServerError) { - return new GetPermissionsOffersResponse(objectServerError); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static GetPermissionsOffersResponse from(Exception exception) { - return GetPermissionsOffersResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - private GetPermissionsOffersResponse(ObjectServerError error) { - RealmLog.debug("GetPermissionOffers - Error: %s", error); - setError(error); - this.error = error; - } - - private GetPermissionsOffersResponse(String serverResponse) { - RealmLog.debug("GetPermissionOffers - Success: %s", serverResponse); - try { - JSONObject responseObject = new JSONObject(serverResponse); - JSONArray responseOffersList = responseObject.getJSONArray("offers"); - for (int i = 0; i < responseOffersList.length(); i++) { - JSONObject obj = responseOffersList.getJSONObject(i); - String path = obj.getString("realmPath"); - Date expiresAt = obj.isNull("expiresAt") ? null : JsonUtils.stringToDate(obj.getString("expiresAt")); - AccessLevel accessLevel = AccessLevel.fromKey(obj.getString("accessLevel")); - Date createdAt = JsonUtils.stringToDate(obj.getString("createdAt")); - String userId = obj.getString("userId"); - String token = obj.getString("token"); - offers.add(new PermissionOffer(path, accessLevel, expiresAt, createdAt, userId, token)); - } - } catch (JSONException e) { - error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); - } - } - - public List getOffers() { - return offers; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/InvalidatePermissionsOfferResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/InvalidatePermissionsOfferResponse.java deleted file mode 100644 index f676531891..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/InvalidatePermissionsOfferResponse.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2019 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.log.RealmLog; -import io.realm.permissions.Permission; -import okhttp3.Response; - -/** - * Class wrapping the response from `DELETE /permissions/offers/:token:` - */ -public class InvalidatePermissionsOfferResponse extends AuthServerResponse { - - /** - * Helper method for creating the proper response. This method will set the appropriate error - * depending on any HTTP response codes or I/O errors. - * - * @param response the server response. - * @return the user lookup response. - */ - static InvalidatePermissionsOfferResponse from(Response response) { - String serverResponse; - try { - serverResponse = response.body().string(); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new InvalidatePermissionsOfferResponse(error); - } - if (!response.isSuccessful()) { - return new InvalidatePermissionsOfferResponse(AuthServerResponse.createError(serverResponse, response.code())); - } else { - return new InvalidatePermissionsOfferResponse(serverResponse); - } - } - - /** - * Helper method for creating a failed response. - */ - public static InvalidatePermissionsOfferResponse from(ObjectServerError objectServerError) { - return new InvalidatePermissionsOfferResponse(objectServerError); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static InvalidatePermissionsOfferResponse from(Exception exception) { - return InvalidatePermissionsOfferResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - private InvalidatePermissionsOfferResponse(ObjectServerError error) { - RealmLog.debug("InvalidatePermissionOffer - Error: %s", error); - setError(error); - this.error = error; - } - - private InvalidatePermissionsOfferResponse(String serverResponse) { - RealmLog.debug("InvalidatePermissionOffer - Success: %s", serverResponse); - // No data to store - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferRequest.java deleted file mode 100644 index c4d60995bd..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferRequest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2019 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Date; - -import io.realm.permissions.PermissionOffer; - -/** - * Class wrapping request to `POST /auth/permissions/offers` - */ -public class MakePermissionsOfferRequest { - - private final PermissionOffer offer; - - public MakePermissionsOfferRequest(PermissionOffer offer) { - this.offer = offer; - } - - public String toJson() throws JSONException { - JSONObject request = new JSONObject(); - Date expires = offer.getExpiresAt(); - if (expires != null) { - request.put("expiresAt", expires.toString()); - } - request.put("realmPath", offer.getRealmUrl()); - request.put("accessLevel", offer.getAccessLevel().getKey()); - return request.toString(); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferResponse.java deleted file mode 100644 index aed7a4a43f..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/MakePermissionsOfferResponse.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2019 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.util.Date; - -import javax.annotation.Nonnull; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.internal.android.JsonUtils; -import io.realm.internal.permissions.PermissionOfferResponse; -import io.realm.log.RealmLog; -import io.realm.permissions.AccessLevel; -import okhttp3.Response; - -/** - * Class wrapping the response from `POST permissions/offers` - */ -public class MakePermissionsOfferResponse extends AuthServerResponse { - - private PermissionOfferResponse response; - - /** - * Helper method for creating the proper lookup user response. This method will set the appropriate error - * depending on any HTTP response codes or I/O errors. - * - * @param response the server response. - * @return the user lookup response. - */ - static MakePermissionsOfferResponse from(Response response) { - String serverResponse; - try { - serverResponse = response.body().string(); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new MakePermissionsOfferResponse(error); - } - if (!response.isSuccessful()) { - return new MakePermissionsOfferResponse(AuthServerResponse.createError(serverResponse, response.code())); - } else { - return new MakePermissionsOfferResponse(serverResponse); - } - } - - /** - * Helper method for creating a failed response. - */ - public static MakePermissionsOfferResponse from(ObjectServerError objectServerError) { - return new MakePermissionsOfferResponse(objectServerError); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static MakePermissionsOfferResponse from(Exception exception) { - return MakePermissionsOfferResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - private MakePermissionsOfferResponse(ObjectServerError error) { - RealmLog.debug("MakePermissionsOffer - Error: %s", error); - setError(error); - this.error = error; - } - - private MakePermissionsOfferResponse(String serverResponse) { - RealmLog.debug("MakePermissionsOffer - Success: %s", serverResponse); - try { - JSONObject obj = new JSONObject(serverResponse); - @Nonnull String path = obj.getString("realmPath"); - Date expiresAt = obj.isNull("expiresAt") ? null : JsonUtils.stringToDate(obj.getString("expiresAt")); - AccessLevel accessLevel = AccessLevel.fromKey(obj.getString("accessLevel")); - Date createdAt = JsonUtils.stringToDate(obj.getString("createdAt")); - String userId = obj.getString("userId"); - String token = obj.getString("token"); - response = new PermissionOfferResponse(path, expiresAt, accessLevel, createdAt, userId, token); - } catch (JSONException e) { - error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); - } - } - - public String getToken() { - return response.getToken(); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java index d6e2785cf4..2b97a62a6e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java @@ -33,8 +33,6 @@ import io.realm.internal.objectserver.Token; import io.realm.log.LogLevel; import io.realm.log.RealmLog; -import io.realm.permissions.PermissionOffer; -import io.realm.permissions.PermissionRequest; import okhttp3.Call; import okhttp3.ConnectionPool; import okhttp3.Interceptor; @@ -251,104 +249,6 @@ public UpdateAccountResponse confirmEmail(String confirmationToken, URL authenti } } - @Override - public RetrievePermissionsResponse getPermissions(Token userToken, URL baseUrl) { - try { - URL url = buildActionUrl(baseUrl, ACTION_GET_PERMISSIONS); - RealmLog.debug("Network request (retrieveGrantedPermissions): " + url); - Request request = newAuthRequest(url, userToken.value()) - .get() - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return RetrievePermissionsResponse.from(response); - } catch (Exception e) { - return RetrievePermissionsResponse.from(e); - } - } - - @Override - public ApplyPermissionsResponse applyPermissions(PermissionRequest permissionRequest, Token refreshToken, URL baseUrl) { - try { - URL url = buildActionUrl(baseUrl, ACTION_UPDATE_PERMISSIONS); - RealmLog.debug("Network request (applyPermissions): " + url); - Request request = newAuthRequest(url, refreshToken.value()) - .post(RequestBody.create(JSON, new ApplyPermissionsRequest(permissionRequest).toJson())) - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return ApplyPermissionsResponse.from(response); - } catch (Exception e) { - return ApplyPermissionsResponse.from(e); - } - } - - @Override - public MakePermissionsOfferResponse makeOffer(PermissionOffer offer, Token refreshToken, URL baseUrl) { - try { - URL url = buildActionUrl(baseUrl, ACTION_OFFER_PERMISSIONS); - RealmLog.debug("Network request (offerPermissions): " + url); - Request request = newAuthRequest(url, refreshToken.value()) - .post(RequestBody.create(JSON, new MakePermissionsOfferRequest(offer).toJson())) - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return MakePermissionsOfferResponse.from(response); - } catch (Exception e) { - return MakePermissionsOfferResponse.from(e); - } - } - - @Override - public AcceptPermissionsOfferResponse acceptOffer(String offerToken, Token refreshToken, URL baseUrl) { - try { - String action = ACTION_ACCEPT_PERMISSIONS_OFFER.replace(":token:", offerToken); - URL url = buildActionUrl(baseUrl, action); - RealmLog.debug("Network request (acceptPermissionOffer): " + url); - Request request = newAuthRequest(url, refreshToken.value()) - .post(RequestBody.create(JSON, "")) - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return AcceptPermissionsOfferResponse.from(response); - } catch (Exception e) { - return AcceptPermissionsOfferResponse.from(e); - } - } - - @Override - public InvalidatePermissionsOfferResponse invalidateOffer(String offerToken, Token refreshToken, URL baseUrl) { - try { - String action = ACTION_DELETE_PERMISSIONS_OFFER.replace(":token:", offerToken); - URL url = buildActionUrl(baseUrl, action); - RealmLog.debug("Network request (invalidatePermissionOffer): " + url); - Request request = newAuthRequest(url, refreshToken.value()) - .delete() - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return InvalidatePermissionsOfferResponse.from(response); - } catch (Exception e) { - return InvalidatePermissionsOfferResponse.from(e); - } - } - - @Override - public GetPermissionsOffersResponse getPermissionOffers(Token refreshToken, URL baseUrl) { - try { - URL url = buildActionUrl(baseUrl, ACTION_GET_PERMISSION_OFFERS); - RealmLog.debug("Network request (GetPermissionsOffers): " + url); - Request request = newAuthRequest(url, refreshToken.value()) - .get() - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return GetPermissionsOffersResponse.from(response); - } catch (Exception e) { - return GetPermissionsOffersResponse.from(e); - } - } - // Builds the URL for a specific auth endpoint private static URL buildActionUrl(URL authenticationUrl, String action) { final String baseUrlString = authenticationUrl.toExternalForm(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java index c0b3151612..f6695d91ae 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java @@ -24,8 +24,6 @@ import io.realm.SyncCredentials; import io.realm.SyncUser; import io.realm.internal.objectserver.Token; -import io.realm.permissions.PermissionOffer; -import io.realm.permissions.PermissionRequest; /** * Interface for handling communication with Realm Object Servers. @@ -116,33 +114,4 @@ public interface RealmObjectServer { */ UpdateAccountResponse confirmEmail(String confirmationToken, URL authenticationUrl); - /** - * Retrieves a list of all permissions for the given user. - */ - RetrievePermissionsResponse getPermissions(Token userToken, URL baseUrl); - - /** - * Updates a given set of permissions for a single Realm - */ - ApplyPermissionsResponse applyPermissions(PermissionRequest request, Token refreshToken, URL baseUrl); - - /** - * Creates an permissions offer for a Realm. - */ - MakePermissionsOfferResponse makeOffer(PermissionOffer offer, Token refreshToken, URL baseUrl); - - /** - * Accept a given permissions offer. - */ - AcceptPermissionsOfferResponse acceptOffer(String offerToken, Token refreshToken, URL baseUrl); - - /** - * Invalidates an already created permissions offer. - */ - InvalidatePermissionsOfferResponse invalidateOffer(String id, Token refreshToken, URL baseUrl); - - /** - * Retrieves a list of all permissions offers that has been created. - */ - GetPermissionsOffersResponse getPermissionOffers(Token refreshToken, URL baseUrl); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/RetrievePermissionsResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/RetrievePermissionsResponse.java deleted file mode 100644 index 46649b5bfd..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/RetrievePermissionsResponse.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2019 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.internal.android.JsonUtils; -import io.realm.log.RealmLog; -import io.realm.permissions.AccessLevel; -import io.realm.permissions.Permission; -import okhttp3.Response; - -/** - * Class wrapping the response from `GET /permissions` - */ -public class RetrievePermissionsResponse extends AuthServerResponse { - - private final List permissions = new ArrayList<>(); - - /** - * Helper method for creating the proper response. This method will set the appropriate error - * depending on any HTTP response codes or I/O errors. - * - * @param response the server response. - * @return the user lookup response. - */ - static RetrievePermissionsResponse from(Response response) { - String serverResponse; - try { - serverResponse = response.body().string(); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new RetrievePermissionsResponse(error); - } - if (!response.isSuccessful()) { - return new RetrievePermissionsResponse(AuthServerResponse.createError(serverResponse, response.code())); - } else { - return new RetrievePermissionsResponse(serverResponse); - } - } - - /** - * Helper method for creating a failed response. - */ - public static RetrievePermissionsResponse from(ObjectServerError objectServerError) { - return new RetrievePermissionsResponse(objectServerError); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static RetrievePermissionsResponse from(Exception exception) { - return RetrievePermissionsResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - private RetrievePermissionsResponse(ObjectServerError error) { - RealmLog.debug("LookupUserIdResponse - Error: %s", error); - setError(error); - this.error = error; - } - - private RetrievePermissionsResponse(String serverResponse) { - RealmLog.debug("RetrievePermissionsResponse - Success: %s", serverResponse); - try { - JSONObject obj = new JSONObject(serverResponse); - JSONArray array = obj.getJSONArray("permissions"); - for (int i = 0; i < array.length(); i++) { - JSONObject permission = array.getJSONObject(i); - String userId = (permission.isNull("userId")) ? null : permission.getString("userId"); - String path = permission.getString("path"); - AccessLevel accessLevel = AccessLevel.fromKey(permission.getString("accessLevel")); - boolean mayRead = accessLevel.mayRead(); - boolean mayWrite = accessLevel.mayWrite(); - boolean mayManage = accessLevel.mayManage(); - Date updatedAt = JsonUtils.stringToDate(permission.getString("updatedAt")); - permissions.add(new Permission(userId, path, accessLevel, mayRead, mayWrite, mayManage, updatedAt)); - } - } catch (JSONException e) { - error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); - } - } - - public List getPermissions() { - return permissions; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionOfferResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionOfferResponse.java deleted file mode 100644 index c4f850dab3..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/permissions/PermissionOfferResponse.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ -package io.realm.internal.permissions; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Date; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.permissions.AccessLevel; -import io.realm.permissions.PermissionOffer; - - -/** - * This model is used to apply permission changes defined in the permission offer - * object represented by the specified token, which was created by another user's - * {@link PermissionOffer} object. - * - * It should be used in conjunction with an {@link io.realm.SyncUser}'s management Realm. - * - * @see Permissions description for general - * documentation. - */ -public final class PermissionOfferResponse { - - @Nonnull private final String userId; - @Nonnull private final Date createdAt; - private final Date expiresAt; - @Nonnull private final String token; - @Nonnull private final String realmUrl; - @Nonnull private final AccessLevel accessLevel; - - public PermissionOfferResponse(String path, Date expiresAt, AccessLevel accessLevel, Date createdAt, String userId, String token) { - this.realmUrl = path; - this.expiresAt = (expiresAt != null) ? (Date) expiresAt.clone() : null; - this.accessLevel = accessLevel; - this.createdAt = (Date) createdAt.clone(); - this.userId = userId; - this.token = token; - } - - public String getUserId() { - return userId; - } - - @SuppressFBWarnings("EI_EXPOSE_REP") - public Date getCreatedAt() { - return createdAt; - } - - public String getToken() { - return token; - } - - @Nullable - public String getRealmUrl() { - return realmUrl; - } - - public String getPath() { - try { - return new URI(realmUrl).getPath(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java b/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java deleted file mode 100644 index 0dc7279bdb..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.realm.internal.sync.permissions; - -import io.realm.annotations.RealmModule; -import io.realm.sync.Subscription; -import io.realm.sync.permissions.ClassPermissions; -import io.realm.sync.permissions.Permission; -import io.realm.sync.permissions.RealmPermissions; -import io.realm.sync.permissions.PermissionUser; -import io.realm.sync.permissions.Role; - -/** - * Realm model classses that are always part of Query-based Realms - */ -@RealmModule(library = true, classes = { - ClassPermissions.class, - Permission.class, - RealmPermissions.class, - Role.class, - PermissionUser.class, - Subscription.class -}) -public class ObjectPermissionsModule { -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/AccessLevel.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/AccessLevel.java deleted file mode 100644 index 689700eb1f..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/AccessLevel.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.permissions; - -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.SyncUser; - - -/** - * Access levels which can be granted to Realm Mobile Platform users for specific synchronized Realms, using a - * {@link PermissionRequest}. - *

            - * Note that each access level guarantees all allowed actions provided by less permissive access levels. - * Specifically, users with write access to a Realm can always read from that Realm, and users with administrative - * access can always read or write from the Realm. This means that {@code NONE < READ < WRITE < ADMIN}. - * - * @see PermissionRequest - * @see SyncUser#applyPermissionsAsync(PermissionRequest, SyncUser.Callback) - */ -public enum AccessLevel { - - /** - * The user does not have access to this Realm. - */ - NONE("none", false, false, false), - - /** - * User can only read the contents of the Realm. - *

            - * Users who have read-only access to a Realm should open it using `readOnly()` and - * `waitForInitialRemoteData()` on the {@link io.realm.SyncConfiguration}. Attempting to directly open the Realm - * is an error; in this case the Realm must manually be deleted using {@link Realm#deleteRealm(RealmConfiguration)} - * before being re-opened with the correct configuration. - *

            - *

            -     * {@code
            -     * SyncConfiguration config = new SyncConfiguration(getUser(), getUrl())
            -     *     .readOnly()
            -     *     .waitForInitialRemoteData()
            -     *     .build();
            -     * }
            -     * 
            - */ - READ("read", true, false, false), - - /** - * User can read and write the contents of the Realm. - */ - WRITE("write", true, true, false), - - /** - * User can read, write, and administer the Realm. This includes both granting permissions as well as removing them - * again. - */ - ADMIN( "admin", true, true, true); - - private final String key; // JSON description used by the Realm Object Server - private final boolean mayRead; - private final boolean mayWrite; - private final boolean mayManage; - - AccessLevel(String serverKey, boolean mayRead, boolean mayWrite, boolean mayManage) { - this.key = serverKey; - this.mayRead = mayRead; - this.mayWrite = mayWrite; - this.mayManage = mayManage; - } - - public static AccessLevel fromKey(String accessLevel) { - for (AccessLevel level : values()) { - if (level.getKey().equals(accessLevel)) { - return level; - } - } - throw new IllegalArgumentException("Unknown access level: " + accessLevel); - } - - /** - * Returns {@code true} if the user is allowed to read a Realm, {@code false} if not. - */ - public boolean mayRead() { - return mayRead; - } - - /** - * Returns {@code true} if the user is allowed to write to the Realm, {@code false} if not. - */ - public boolean mayWrite() { - return mayWrite; - } - - /** - * Returns {@code true} if the user is allowed to manage the Realm, {@code false} if not. - *

            - * Having this permission, means the user is able to grant permissions to other users as well as remove them - * again. - */ - public boolean mayManage() { - return mayManage; - } - - public String getKey() { - return key; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/Permission.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/Permission.java deleted file mode 100644 index ed9e21a0ce..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/Permission.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.permissions; - -import java.util.Date; - -import javax.annotation.Nullable; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.SyncUser; - - -/** - * This class represents the given set of permissions provided to a user for the Realm identified by - * {@link #path}. - *

            - * Permissions can be changed by users with administrative rights using {@link SyncUser#applyPermissions(PermissionRequest)}. - */ -public final class Permission { - - @Nullable private final String userId; - private final String path; - private final AccessLevel accessLevel; - private final boolean mayRead; - private final boolean mayWrite; - private final boolean mayManage; - private final Date updatedAt; - - public Permission(@Nullable String userId, String path, AccessLevel accessLevel, boolean mayRead, boolean mayWrite, boolean mayManage, Date updatedAt) { - this.userId = userId; - this.path = path; - this.accessLevel = accessLevel; - this.mayRead = mayRead; - this.mayWrite = mayWrite; - this.mayManage = mayManage; - this.updatedAt = (Date) updatedAt.clone(); - } - - /** - * Returns the {@link SyncUser#getIdentity()} of the user effected by this permission or - * {@code null} if this permissions applies to all users. - * - * @return the user(s) effected by this permission. - */ - public String getUserId() { - return userId; - } - - /** - * Returns the path to the Realm on the server effected by this permission. This is not the full URL. - * - * @return the path to the Realm this permission object refers to. - */ - public String getPath() { - return path; - } - - /** - * Returns the access level granted by this permission. - * - * @return access level granted by this permission. - */ - public AccessLevel getAccessLevel() { - return accessLevel; - } - - /** - * Checks whether or not the user defined by this permission is allowed to read the Realm defined by - * {@link #getPath()}. - * - * @return {@code true} if this permission grant read permissions to the Realm, {@code false} if not. - */ - public boolean mayRead() { - return mayRead; - } - - /** - * Checks whether or not the user defined by this permission is allowed to write to the Realm defined by - * {@link #getPath()}. - * - * @return {@code true} if this permission grant write permissions to the Realm, {@code false} if not. - */ - public boolean mayWrite() { - return mayWrite; - } - - /** - * Checks whether or not the user defined by this permission is allowed to manage access to the Realm defined - * by {@link #getPath()}. Having this permission enable those users to add or remove permissions from - * other users, including the one who granted it. - * - * @return {@code true} if this permission grant administrative rights to the Realm, {@code false} if not. - */ - public boolean mayManage() { - return mayManage; - } - - /** - * Returns the timestamp for when this permission object was last updated. - * - * @return the timestamp for when this permission was last updated. - */ - @SuppressFBWarnings({"EI_EXPOSE_REP"}) - public Date getUpdatedAt() { - return updatedAt; - } - - @Override - public String toString() { - return "Permission{" + - "userId='" + userId + '\'' + - ", path='" + path + '\'' + - ", accessLevel=" + accessLevel + - ", mayRead=" + mayRead + - ", mayWrite=" + mayWrite + - ", mayManage=" + mayManage + - ", updatedAt=" + updatedAt + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Permission that = (Permission) o; - - if (mayRead != that.mayRead) return false; - if (mayWrite != that.mayWrite) return false; - if (mayManage != that.mayManage) return false; - if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false; - if (!path.equals(that.path)) return false; - if (accessLevel != that.accessLevel) return false; - return updatedAt.equals(that.updatedAt); - } - - @Override - public int hashCode() { - int result = userId != null ? userId.hashCode() : 0; - result = 31 * result + path.hashCode(); - result = 31 * result + accessLevel.hashCode(); - result = 31 * result + (mayRead ? 1 : 0); - result = 31 * result + (mayWrite ? 1 : 0); - result = 31 * result + (mayManage ? 1 : 0); - result = 31 * result + updatedAt.hashCode(); - return result; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java deleted file mode 100644 index 5baeaba7e7..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionOffer.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ -package io.realm.permissions; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Date; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.SyncUser; -import io.realm.internal.Util; - - -/** - * This class represents a permission offer for a Realm that can be given to other users. - * When an offer is successfully created, it will be represented by an {@code offerToken} that can be sent - * to other users. Once they accept this token, the permissions covered by this offer will take effect for that - * user. - *

            - * Permission offers can only be created by users that can manage the Realm, the offer is about. - * - * @see SyncUser#makePermissionsOfferAsync(PermissionOffer, SyncUser.Callback) - * @see SyncUser#acceptPermissionsOfferAsync(String, SyncUser.Callback) - * @see Permissions description for general - * documentation. - */ -public final class PermissionOffer { - - @Nonnull private final Date createdAt; - private final String userId; - private final String token; - @Nonnull private final String realmUrl; - @Nonnull private final AccessLevel accessLevel; - private final Date expiresAt; - - /** - * Creates a request for an permission offer that last until it is manually revoked. - * - * @param url specific url to Realm effected this offer encompasses all Realms manged by the user making the offer. - * @param accessLevel the {@link AccessLevel} granted to the user accepting the offer. - * - * @see SyncUser#invalidatePermissionsOfferAsync(String, SyncUser.Callback) - */ - @SuppressFBWarnings("EI_EXPOSE_REP2") - public PermissionOffer(String url, AccessLevel accessLevel) { - //noinspection ConstantConditions - this(url, accessLevel, null); - } - - /** - * Creates a request for a permission offer that last until it is manually revoked. - * - * @param url specific url to Realm effected. The user sending the offer must have manage rights to this Realm. - * @param accessLevel the {@link AccessLevel} granted to the user accepting the offer. - * @param expiresAt the date and time when this offer expires. If {@code null} is provided the offer never expires. - * - * - * @see SyncUser#invalidatePermissionsOfferAsync(String, SyncUser.Callback) - */ - @SuppressFBWarnings("EI_EXPOSE_REP2") - public PermissionOffer(String url, AccessLevel accessLevel, @Nullable Date expiresAt) { - this(url, accessLevel, expiresAt, new Date(), null, null); - } - - @SuppressFBWarnings("EI_EXPOSE_REP2") - public PermissionOffer(String path, AccessLevel accessLevel, @Nullable Date expiresAt, Date createdAt, @Nullable String userId, @Nullable String token) { - validateUrl(path); - validateAccessLevel(accessLevel); - this.realmUrl = path; - this.accessLevel = accessLevel; - this.expiresAt = (expiresAt != null) ? (Date) expiresAt.clone() : null; - this.createdAt = (Date) createdAt.clone(); - this.userId = userId; - this.token = token; - } - - private void validateUrl(String url) { - if (Util.isEmptyString(url)) { - throw new IllegalArgumentException("Non-empty 'realmUrl' required."); - } - - try { - // Validate basic syntax. - new URI(url); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid 'realmUrl'.", e); - } - } - - private void validateAccessLevel(AccessLevel accessLevel) { - if (accessLevel == null) { - throw new IllegalArgumentException("Non-null 'accessLevel' required."); - } - } - - /** - * Returns the timestamp when this offer was created. - * - * @return the timstamp when this offer was created. - */ - @SuppressFBWarnings("EI_EXPOSE_REP") - public Date getCreatedAt() { - return createdAt; - } - - /** - * Returns the offer token if this offer was successfully created. - * - * @return the offer token or {@code null} if the offer wasn't created yet. - */ - @Nullable - public String getToken() { - return token; - } - - /** - * Returns the Realm URL for which the permissions are granted. - * - * @return the Realm URL for which the permissions should be granted. - */ - public String getRealmUrl() { - return realmUrl; - } - - /** - * Returns whether or not the user accepting this offer is granted read permission. - * - * @return {@code true} if the user accepting this offer is granted read permission, {@code false} if not. - */ - public boolean mayRead() { - return accessLevel.mayRead(); - } - - /** - * Returns whether or not the user accepting this offer is granted write permission. - * - * @return {@code true} if the user accepting this offer is granted write permission, {@code false} if not. - */ - public boolean mayWrite() { - return accessLevel.mayWrite(); - } - - /** - * Returns whether or not the user accepting this offer is granted manage permission. This will allow this user - * to also grant or remove permission for other users on this Realm. - * - * @return {@code true} if the user accepting this offer is granted mange permission, {@code false} if not. - */ - public boolean mayManage() { - return accessLevel.mayManage(); - } - - /** - * Returns the access level granted by this offer. - * - * @return access level granted by this offer. - */ - public AccessLevel getAccessLevel() { - return accessLevel; - } - - /** - * Checks if the offer was successfully handled by the Realm Object Server. - * - * @return {@code true} if the request has been created, {@code false} if not. - */ - public boolean isOfferCreated() { - return !Util.isEmptyString(token); - } - - /** - * Returns when this offer expires. {@code null} is returned if this offer never expires. - * - * @return the date when this offer expires or {@code null} if it never expires. - */ - @SuppressFBWarnings("EI_EXPOSE_REP") - @Nullable - public Date getExpiresAt() { - return expiresAt; - } - - @Override - public String toString() { - return "PermissionOffer{" + - "userId='" + userId + '\'' + - ", createdAt=" + createdAt + - ", token='" + token + '\'' + - ", realmUrl='" + realmUrl + '\'' + - ", mayRead=" + accessLevel.mayRead() + - ", mayWrite=" + accessLevel.mayWrite() + - ", mayManage=" + accessLevel.mayManage() + - ", expiresAt=" + expiresAt + - '}'; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java deleted file mode 100644 index f72f9e4e6d..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/PermissionRequest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * - * 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. - */ -package io.realm.permissions; - -import java.net.URI; -import java.net.URISyntaxException; - -import io.realm.SyncUser; -import io.realm.internal.Util; - -/** - * This class represents the intent of giving a set of permissions to some users for some Realm(s). - *

            - * If the request is successful, a {@link io.realm.permissions.Permission} entry will be added to each affected users, - * where it can be fetched using {@link SyncUser#retrieveGrantedPermissionsAsync(SyncUser.Callback)} - * - * @see SyncUser#applyPermissionsAsync(PermissionRequest, SyncUser.Callback) - * @see SyncUser#retrieveGrantedPermissionsAsync(SyncUser.Callback) - */ -public final class PermissionRequest { - - private final AccessLevel accessLevel; - private final UserCondition condition; - private final String url; - - /** - * Creates a description of a set of permissions granted to some users for some Realms. - * - * @param realmUrl the Realm URL whose permissions settings should be changed. Use {@code *} to change the - * permissions of all Realms managed by the user sending this request. The user that wants to grant these permissions - * must have administrative rights to those Realms. - * - * @param condition the conditions used to match which users are effected. - * @param accessLevel the {@link AccessLevel} to grant matching users. Setting the access level is absolute i.e., it - * may revoke permissions for users that previously had a higher access level. To revoke all permissions, use - * {@link AccessLevel#NONE}. - * - */ - public PermissionRequest(UserCondition condition, String realmUrl, AccessLevel accessLevel) { - checkCondition(condition); - checkUrl(realmUrl); - checkAccessLevel(accessLevel); - this.condition = condition; - this.accessLevel = accessLevel; - this.url = realmUrl; - } - - private void checkUrl(String url) { - if (Util.isEmptyString(url)) { - throw new IllegalArgumentException("Non-empty 'realmUrl' required."); - } - - if (url.equals("*")) { - return; // Special case for selecting all URL's - } - - try { - // Validate basic syntax. - new URI(url); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid 'realmUrl'.", e); - } - } - - private void checkCondition(UserCondition condition) { - if (condition == null) { - throw new IllegalArgumentException("Non-null 'condition' required."); - } - } - - private void checkAccessLevel(AccessLevel accessLevel) { - if (accessLevel == null) { - throw new IllegalArgumentException("Non-null 'accessLevel' required."); - } - } - - /** - * Returns the access level that users will be granted if the request is successful. - * - * @return the {@link AccessLevel} users will have once this request is successfully handled. - */ - public AccessLevel getAccessLevel() { - return accessLevel; - } - - /** - * Returns the {@link UserCondition} used to match users. Those users that match will be granted the the - * {@link AccessLevel} defined by {@link #getAccessLevel()}. - * - * @return the condition used to match users. - */ - public UserCondition getCondition() { - return condition; - } - - /** - * The Realm URL for which the permissions are granted. {@code *} is returned if the request should match - * all Realms, for which the user sending the request, has administrative rights. - * - * @return the Realm URL for which the permissions should be granted. - * @see io.realm.permissions.Permission#mayManage() - */ - public String getUrl() { - return url; - } - - @Override - public boolean equals(Object o) { - if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { return false; } - - PermissionRequest that = (PermissionRequest) o; - - if (accessLevel != that.accessLevel) { return false; } - if (!condition.equals(that.condition)) { return false; } - return url.equals(that.url); - - } - - @Override - public int hashCode() { - int result = accessLevel.hashCode(); - result = 31 * result + condition.hashCode(); - result = 31 * result + url.hashCode(); - return result; - } - - @Override - public String toString() { - return "PermissionRequest{" + - "accessLevel=" + accessLevel + - ", condition=" + condition + - ", url='" + url + '\'' + - '}'; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/UserCondition.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/UserCondition.java deleted file mode 100644 index 168e753e03..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/UserCondition.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.permissions; - -import io.realm.SyncUser; -import io.realm.internal.Util; - - -/** - * This class represents a condition for matching users on the Realm Object Server. - * It is used when a request for changing existing permissions is made. - * - * @see PermissionRequest - * @see SyncUser#applyPermissionsAsync(PermissionRequest, SyncUser.Callback) - */ -public final class UserCondition { - - private final String key; - private final String value; - private final MatcherType type; - - /** - * Creates a condition for matching, exactly, a users username. The comparison is case-sensitive and wildcards are - * not allowed. - * - * @param username exact username to match against. - */ - public static UserCondition username(String username) { - if (Util.isEmptyString(username)) { - throw new IllegalArgumentException("Non-empty 'username' required."); - } - return new UserCondition(MatcherType.METADATA, "email", username); - } - - /** - * Creates a condition for matching, exactly, a users id. - * - * @param userId user id to match against. No wildcards are allowed. - * @see SyncUser#getIdentity() - */ - public static UserCondition userId(String userId) { - if (Util.isEmptyString(userId)) { - throw new IllegalArgumentException("Non-empty 'userId' required."); - } - return new UserCondition(MatcherType.USER_ID, "", userId); - } - - /** - * Creates a condition that will match all users with no permissions for the Realm. - *

            - * The {@link AccessLevel} defined alongside this condition, will also be used as the default access level - * for future new users that might be given access to the Realm. - * - * @see SyncUser#makePermissionsOfferAsync(PermissionOffer, SyncUser.Callback) - */ - public static UserCondition noExistingPermissions() { - return userId("*"); - } - - /** - * Creates a custom permission condition. - * This will apply the permissions based on a key/value combination in the user's metadata. - * - * @param key key to use. - * @param value value for that field to match. - */ - public static UserCondition keyValue(String key, String value) { - if (Util.isEmptyString(key)) { - throw new IllegalArgumentException("Non-empty 'key' required."); - } - if (value == null) { - throw new IllegalArgumentException("Non-null 'value' required."); - } - return new UserCondition(MatcherType.METADATA, key, value); - } - - private UserCondition(MatcherType type, String key, String value) { - this.type = type; - this.key = key; - this.value = value; - } - - /** - * Returns the they in the users metadata that is used for evaluating this condition. - * - * @return the key in the users metadata. - */ - public String getKey() { - return key; - } - - /** - * Returns the value that is used when matching users. The semantics of the value will be different - * depending on the type of key used. - * - * @return the value to searchh for in the users meta data. - */ - public String getValue() { - return value; - } - - /** - * Returns the type of data this condition matches. - * - * @return the type of data this condition matches. - */ - public MatcherType getType() { - return type; - } - - /** - * Type of matcher this condition represents. - */ - public enum MatcherType { - USER_ID, - METADATA - } - - @Override - public boolean equals(Object o) { - if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { return false; } - - UserCondition that = (UserCondition) o; - - if (!key.equals(that.key)) { return false; } - return value.equals(that.value); - - } - - @Override - public int hashCode() { - int result = key.hashCode(); - result = 31 * result + value.hashCode(); - return result; - } - - @Override - public String toString() { - return "UserCondition{" + - "key='" + key + '\'' + - ", value='" + value + '\'' + - '}'; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/permissions/package-info.java b/realm/realm-library/src/objectServer/java/io/realm/permissions/package-info.java deleted file mode 100644 index 6a34b88722..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/permissions/package-info.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -@javax.annotation.ParametersAreNonnullByDefault -package io.realm.permissions; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index f59ab19c4b..d90db96404 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -27,9 +27,9 @@ import io.realm.internal.OsRealmConfig; import io.realm.internal.Util; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.objectserver.utils.HttpUtils; import io.realm.rule.RunInLooperThread; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; /** diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java deleted file mode 100644 index 73a11de644..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/PathLevelPermissionsTests.java +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Date; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.annotation.Nullable; - -import io.realm.entities.AllJavaTypes; -import io.realm.internal.OsRealmConfig; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; -import io.realm.permissions.AccessLevel; -import io.realm.permissions.Permission; -import io.realm.permissions.PermissionOffer; -import io.realm.permissions.PermissionRequest; -import io.realm.permissions.UserCondition; -import io.realm.rule.RunTestInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class PathLevelPermissionsTests extends StandardIntegrationTest { - - private SyncUser user; - - @Before - public void setUpTest() { - user = UserFactory.createUniqueUser(); - } - - @Test - @RunTestInLooperThread() - public void retrieveGrantedPermissions_returnLoadedResults() { - user.retrieveGrantedPermissionsAsync(new SyncUser.Callback>() { - @Override - public void onSuccess(List permissions) { - assertInitialPermissions(permissions); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - - @Test - @RunTestInLooperThread - public void retrieveGrantedPermissions_updatedWithNewRealms() { - user.retrieveGrantedPermissionsAsync(new SyncUser.Callback>() { - @Override - public void onSuccess(List permissions) { - assertInitialPermissions(permissions); - - // Create new Realm, which should create a new Permission entry - SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM_2) - .schema(AllJavaTypes.class) - .fullSynchronization() - .errorHandler((session, error) -> fail(error.toString())) - .build(); - final Realm secondRealm = Realm.getInstance(config2); - looperThread.closeAfterTest(secondRealm); - try { - SyncManager.getSession(config2).uploadAllLocalChanges(); - } catch (InterruptedException e) { - fail(e.toString()); - } - - // Wait for the permission Result to report the new Realms - List permissions2 = user.retrieveGrantedPermissions(); - assertEquals(1, permissions.size()); - assertEquals(2, permissions2.size()); - Permission permission = permissions2.get(1); - assertTrue(permission.getPath().endsWith("tests2")); - assertTrue(permission.mayRead()); - assertTrue(permission.mayWrite()); - assertTrue(permission.mayManage()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread() - public void getPermissions_updatedWithNewRealms_stressTest() { - final int TEST_SIZE = 10; - List permissions = user.retrieveGrantedPermissions(); - assertInitialPermissions(permissions); - - for (int i = 0; i < TEST_SIZE; i++) { - SyncConfiguration configNew = user.createConfiguration("realm://" + Constants.HOST + "/~/test" + i) - .fullSynchronization() - .schema(AllJavaTypes.class) - .build(); - Realm newRealm = Realm.getInstance(configNew); - looperThread.closeAfterTest(newRealm); - } - - List perms = permissions; - while(perms.size() < TEST_SIZE + 1) { // +1 is __wildcardpermissions - perms = user.retrieveGrantedPermissions(); - } - - Permission p = perms.get(TEST_SIZE); - assertTrue(p.getPath().endsWith("test" + (TEST_SIZE - 1))); - assertTrue(p.mayRead()); - assertTrue(p.mayWrite()); - assertTrue(p.mayManage()); - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void applyPermissions_nonAdminUserFails() { - SyncUser user2 = UserFactory.createUniqueUser(); - String otherUsersUrl = createRemoteRealm(user2, "test"); - - // Create request for setting permissions on another users Realm, - // i.e. user making the request do not have manage rights. - UserCondition condition = UserCondition.userId(user.getIdentity()); - AccessLevel accessLevel = AccessLevel.WRITE; - PermissionRequest request = new PermissionRequest(condition, otherUsersUrl, accessLevel); - - user.applyPermissionsAsync(request, new SyncUser.Callback() { - @Override - public void onSuccess(Void success) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread - public void applyPermissions_wrongUrlFails() { - String wrongUrl = createRemoteRealm(user, "test") + "-notexisting"; - - // Create request for setting permissions on another users Realm, - // i.e. user making the request do not have manage rights. - UserCondition condition = UserCondition.userId(user.getIdentity()); - AccessLevel accessLevel = AccessLevel.WRITE; - PermissionRequest request = new PermissionRequest(condition, wrongUrl, accessLevel); - user.applyPermissionsAsync(request, new SyncUser.Callback() { - @Override - public void onSuccess(Void ignore) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.INVALID_PARAMETERS, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void applyPermissions_withUserId() { - final SyncUser user2 = UserFactory.createUniqueUser(); - String url = createRemoteRealm(user2, "test"); - - // Create request for giving `user` WRITE permissions to `user2`'s Realm. - UserCondition condition = UserCondition.userId(user.getIdentity()); - AccessLevel accessLevel = AccessLevel.WRITE; - PermissionRequest request = new PermissionRequest(condition, url, accessLevel); - - user2.applyPermissionsAsync(request, new SyncUser.Callback() { - @Override - public void onSuccess(Void ignore) { - List permissions = user.retrieveGrantedPermissions(); - assertPermissionPresent(permissions, user, "/test", AccessLevel.WRITE); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread - public void applyPermissions_withUsername() { - String user1Username = TestHelper.getRandomEmail(); - String user2Username = TestHelper.getRandomEmail(); - final SyncUser user1 = UserFactory.createUser(user1Username); - final SyncUser user2 = UserFactory.createUser(user2Username); - - // Create request for giving `user2` WRITE permissions to `user1`'s Realm. - UserCondition condition = UserCondition.username(user2Username); - AccessLevel accessLevel = AccessLevel.WRITE; - String url = createRemoteRealm(user1, "test"); - PermissionRequest request = new PermissionRequest(condition, url, accessLevel); - - user1.applyPermissions(request); - List user2Permissions = user2.retrieveGrantedPermissions(); - assertPermissionPresent(user2Permissions, user2, user1.getIdentity() + "/test", AccessLevel.WRITE); - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - public void applyPermissions_usersWithNoExistingPermissions() { - final SyncUser user1 = UserFactory.createUser("user1@realm.io"); - final SyncUser user2 = UserFactory.createUser("user2@realm.io"); - - // Create request for giving all users with no existing permissions WRITE permissions to `user1`'s Realm. - UserCondition condition = UserCondition.noExistingPermissions(); - AccessLevel accessLevel = AccessLevel.WRITE; - final String url = createRemoteRealm(user1, "test"); - PermissionRequest request = new PermissionRequest(condition, url, accessLevel); - - user1.applyPermissions(request); - List user2Permissions = user2.retrieveGrantedPermissions(); - assertPermissionPresent(user2Permissions, null, "/" + user1.getIdentity() + "/test", AccessLevel.WRITE); - - // Remove wildcard permission to prevent them from interfering with other tests - user1.applyPermissions(new PermissionRequest(UserCondition.noExistingPermissions(), url, AccessLevel.NONE)); - - } - - @Test - @RunTestInLooperThread - public void makeOffer() { - String url = createRemoteRealm(user, "test"); - - PermissionOffer offer = new PermissionOffer(url, AccessLevel.WRITE); - user.makePermissionsOfferAsync(offer, new SyncUser.Callback() { - @Override - public void onSuccess(String token) { - assertNotNull(token); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread - public void makeOffer_noManageAccessThrows() { - // User 2 creates a Realm - SyncUser user2 = UserFactory.createUniqueUser(); - String url = createRemoteRealm(user2, "test"); - - // User 1 tries to create an offer for it. - PermissionOffer offer = new PermissionOffer(url, AccessLevel.WRITE); - user.makePermissionsOfferAsync(offer, new SyncUser.Callback() { - @Override - public void onSuccess(String s) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread - public void acceptOffer() { - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - final SyncUser user2 = UserFactory.createUniqueUser(); - user2.acceptPermissionsOfferAsync(offerToken, new SyncUser.Callback() { - @Override - public void onSuccess(String realmPath) { - assertEquals("/" + user.getIdentity() + "/test", realmPath); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread - public void acceptOffer_invalidToken() { - user.acceptPermissionsOfferAsync("wrong-token", new SyncUser.Callback() { - @Override - public void onSuccess(String s) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.INVALID_PARAMETERS, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread - public void acceptOffer_multipleUsers() { - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - final SyncUser user2 = UserFactory.createUniqueUser(); - final SyncUser user3 = UserFactory.createUniqueUser(); - - final AtomicInteger offersAccepted = new AtomicInteger(0); - SyncUser.Callback callback = new SyncUser.Callback() { - @Override - public void onSuccess(String url) { - assertEquals("/" + user.getIdentity() + "/test", url); - if (offersAccepted.incrementAndGet() == 2) { - looperThread.testComplete(); - } - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }; - - user2.acceptPermissionsOfferAsync(offerToken, callback); - user2.acceptPermissionsOfferAsync(offerToken, callback); - } - - @Test - @RunTestInLooperThread - public void getCreatedOffers() { - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - - user.retrieveCreatedPermissionsOffersAsync(new SyncUser.Callback>() { - @Override - public void onSuccess(List permissionOffers) { - assertEquals(1, permissionOffers.size()); - assertEquals(offerToken, permissionOffers.get(0).getToken()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread(emulateMainThread = true) - public void revokeOffer() { - // createOffer validates that the offer is actually in the __management Realm. - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - - user.invalidatePermissionsOfferAsync(offerToken, new SyncUser.Callback() { - @Override - public void onSuccess(Void aVoid) { - List offers = user.retrieveCreatedPermissionsOffers(); - assertEquals(0, offers.size()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread - public void revokeOffer_afterOneAcceptEdit() { - final String offerToken = createOffer(user, "test", AccessLevel.WRITE, null); - SyncUser user2 = UserFactory.createUniqueUser(); - SyncUser user3 = UserFactory.createUniqueUser(); - - String path = user2.acceptPermissionsOffer(offerToken); - assertTrue(path.endsWith("test")); - user.invalidatePermissionsOffer(offerToken); - try { - user3.acceptPermissionsOffer(offerToken); - fail(); - } catch (ObjectServerError error) { - assertEquals(ErrorCode.EXPIRED_PERMISSION_OFFER, error.getErrorCode()); - looperThread.testComplete(); - } - } - - /** - * Creates an offer for a newly created Realm. - * - * @param user User that should create the offer - * @param realmName Realm to create - * @param level accessLevel to offer - * @param expires when the offer expires - */ - private String createOffer(final SyncUser user, final String realmName, final AccessLevel level, final Date expires) { - String url = createRemoteRealm(user, realmName); - return user.makePermissionsOffer(new PermissionOffer(url, level, expires)); - } - - /** - * Wait for a given permission to be present. - * - * @param permissions permission results. - * @param user user that is being granted the permission. - * @param urlSuffix the url suffix to listen for. - * @param accessLevel the expected access level for 'user'. - */ - private void assertPermissionPresent(List permissions, @Nullable final SyncUser user, String urlSuffix, final AccessLevel accessLevel) { - for (Permission p : permissions) { - if (p.getPath().endsWith(urlSuffix)) { - assertEquals(accessLevel.mayRead(), p.mayRead()); - assertEquals(accessLevel.mayWrite(), p.mayWrite()); - assertEquals(accessLevel.mayManage(), p.mayManage()); - if (user != null) { - // Specific permissions - assertEquals(user.getIdentity(), p.getUserId()); - } else { - // Default permissions - assertNull(p.getUserId()); - } - looperThread.testComplete(); - return; - } - } - throw new AssertionError("No matching permissions"); - } - - /** - * Creates an empty remote Realm on ROS owned by the provided user - */ - private String createRemoteRealm(SyncUser user, String realmName) { - String url = Constants.AUTH_SERVER_URL + "~/" + realmName; - SyncConfiguration config = user.createConfiguration(url) - .name(realmName) - .schema(AllJavaTypes.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - - Realm realm = Realm.getInstance(config); - SyncSession session = SyncManager.getSession(config); - final CountDownLatch uploadLatch = new CountDownLatch(1); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - uploadLatch.countDown(); - } - } - }); - TestHelper.awaitOrFail(uploadLatch); - realm.close(); - return config.getServerUrl().toString(); - } - - /** - * The initial set of permissions from ROS. - */ - private void assertInitialPermissions(List permissions) { - assertEquals(1, permissions.size()); - assertEquals("/__wildcardpermissions", permissions.get(0).getPath()); - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java deleted file mode 100644 index 4ba09b1c28..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.objectserver; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; -import java.util.List; - -import io.realm.IsolatedIntegrationTests; -import io.realm.Realm; -import io.realm.RealmResults; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncUser; -import io.realm.annotations.RealmModule; -import io.realm.entities.AllJavaTypes; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; -import io.realm.log.RealmLog; -import io.realm.objectserver.model.PermissionObject; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.StringOnlyModule; -import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.RunTestInLooperThread; -import io.realm.sync.permissions.ClassPrivileges; -import io.realm.sync.permissions.ObjectPrivileges; -import io.realm.sync.permissions.Permission; -import io.realm.sync.permissions.RealmPrivileges; -import io.realm.sync.permissions.Role; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * Integration tests for Object Level Permissions. - * Each test is run in isolation as we use the the global default Realm for each test. - * It is currently not possible to manually create a world readable Realm as - * {@link io.realm.PermissionManager} is unstable on CI. - */ -@RunWith(AndroidJUnit4.class) -public class ObjectLevelPermissionIntegrationTests extends IsolatedIntegrationTests { - - @RealmModule(classes = {AllJavaTypes.class}) - public static class ObjectLevelTestModule { - } - - @RealmModule(classes = {PermissionObject.class}) - public static class OLPermissionModule { - } - - // Check default privileges after being online for the first time - @Test - @RunTestInLooperThread() - public void getPrivileges_serverDefaults() throws InterruptedException { - List schemaModule = Arrays.asList(new ObjectLevelTestModule()); - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM) - .modules(schemaModule) - .build(); - - Realm realm = Realm.getInstance(syncConfig); - - // Make sure that all objects are part of the Partial Sync transitive closure - realm.where(AllJavaTypes.class).findAllAsync("keep-AllJavaTypes"); - - // Create offline object - realm.beginTransaction(); - AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 0); - realm.commitTransaction(); - assertEquals(1, realm.where(AllJavaTypes.class).count()); - - // Make sure that server permissions have been applied to local object - SyncManager.getSession(syncConfig).uploadAllLocalChanges(); - SyncManager.getSession(syncConfig).downloadAllServerChanges(); - realm.refresh(); - - // Check Realm privileges - RealmPrivileges realmPrivileges = realm.getPrivileges(); - assertFullAccess(realmPrivileges); - - // Check Class privileges - ClassPrivileges classPrivileges = realm.getPrivileges(AllJavaTypes.class); - assertFullAccess(classPrivileges); - - // Check Object privileges - assertEquals(1, realm.where(AllJavaTypes.class).count()); - ObjectPrivileges objectPrivileges = realm.getPrivileges(obj); - assertFullAccess(objectPrivileges); - - realm.close(); - looperThread.testComplete(); - } - - // Restrict read/write permission, only the owner of the object can see/modify it - @Test - @RunTestInLooperThread() - public void restrictAccessToOwner() throws InterruptedException { - List schemaModules = Arrays.asList(new StringOnlyModule(), new OLPermissionModule(), new ObjectPermissionsModule()); - - // connect with user1 - SyncUser user1 = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration user1SyncConfig = configurationFactory - .createSyncConfigurationBuilder(user1, Constants.DEFAULT_REALM) - .modules(schemaModules) - .build(); - Realm user1Realm = Realm.getInstance(user1SyncConfig); - user1Realm.beginTransaction(); - - // added a new Role to restrict access to our objects - Role role = user1Realm.createObject(Role.class, "role_" + user1.getIdentity()); - role.addMember(user1.getIdentity()); - - // add permission so this will be only visible and modifiable from user1 - Permission userPermission = new Permission(role); - userPermission.setCanRead(true); - userPermission.setCanQuery(true); - userPermission.setCanCreate(true); - userPermission.setCanUpdate(true); - userPermission.setCanUpdate(true); - userPermission.setCanDelete(true); - userPermission.setCanSetPermissions(true); - userPermission.setCanModifySchema(true); - - PermissionObject permissionObject1 = user1Realm.createObject(PermissionObject.class, "Foo"); - permissionObject1.getPermissions().add(userPermission); - user1Realm.commitTransaction(); - - SyncManager.getSession(user1SyncConfig).uploadAllLocalChanges(); - user1Realm.close(); - - // Connect with admin user and verify that user1 object is visible (non-partial Realm) - SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, Constants.DEFAULT_REALM) - .fullSynchronization() - .modules(schemaModules) - .waitForInitialRemoteData() - .build(); - Realm adminRealm = Realm.getInstance(adminConfig); - RealmResults allPermissionObjects = adminRealm.where(PermissionObject.class).findAll(); - assertEquals(1, allPermissionObjects.size()); - PermissionObject permissionObject = allPermissionObjects.first(); - assertEquals("Foo", permissionObject.getName()); - assertEquals(1, permissionObject.getPermissions().size()); - Permission permission = permissionObject.getPermissions().get(0); - assertFullAccess(permission); - adminRealm.close(); - - // Connect with user 2 and verify that user1 object is not visible - SyncUser user2 = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfig2 = configurationFactory.createSyncConfigurationBuilder(user2, Constants.DEFAULT_REALM) - .modules(schemaModules) - .build(); - Realm user2Realm = Realm.getInstance(syncConfig2); - looperThread.closeAfterTest(user2Realm); - RealmResults allAsync = user2Realm.where(PermissionObject.class).findAllAsync(); - looperThread.keepStrongReference(allAsync); - // new object should not be visible for user2 partial sync - allAsync.addChangeListener((permissionObjects2, changeSet) -> { - RealmLog.info("State: " + changeSet.getState().toString() + ", complete: " + changeSet.isCompleteResult()); - if (changeSet.isCompleteResult()) { - assertEquals(0, permissionObjects2.size()); - looperThread.testComplete(); - } - }); - } - - private void assertFullAccess(Permission permission) { - assertTrue(permission.canCreate()); - assertTrue(permission.canRead()); - assertTrue(permission.canUpdate()); - assertTrue(permission.canDelete()); - assertTrue(permission.canQuery()); - assertTrue(permission.canSetPermissions()); - assertTrue(permission.canModifySchema()); - } - - private void assertFullAccess(ClassPrivileges privileges) { - assertTrue(privileges.canCreate()); - assertTrue(privileges.canRead()); - assertTrue(privileges.canUpdate()); - assertTrue(privileges.canQuery()); - assertTrue(privileges.canSetPermissions()); - } - - private void assertFullAccess(RealmPrivileges privileges) { - assertTrue(privileges.canRead()); - assertTrue(privileges.canUpdate()); - assertTrue(privileges.canSetPermissions()); - assertTrue(privileges.canModifySchema()); - } - - private void assertFullAccess(ObjectPrivileges privileges) { - assertTrue(privileges.canRead()); - assertTrue(privileges.canUpdate()); - assertTrue(privileges.canDelete()); - assertTrue(privileges.canSetPermissions()); - } - -} diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PermissionObject.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PermissionObject.java deleted file mode 100644 index 53903a2f34..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/model/PermissionObject.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.realm.objectserver.model; - -import io.realm.RealmList; -import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.Required; -import io.realm.sync.permissions.Permission; - -public class PermissionObject extends RealmObject { - @PrimaryKey - @Required - private String name; - private RealmList permissions = new RealmList<>(); - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public RealmList getPermissions() { - return permissions; - } -} diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java index 938c29072f..8c42aa22c0 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java @@ -17,8 +17,8 @@ package io.realm; import io.realm.internal.OsRealmConfig; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; import io.realm.rule.TestRealmConfigurationFactory; +import io.realm.internal.sync.permissions.ObjectPermissionsModule; /** * Test rule used for creating SyncConfigurations. Will ensure that any Realm files are deleted when the From a0aa3b91f3f4d6708f15bccdf2d330ebe00438a9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Feb 2020 23:11:20 +0100 Subject: [PATCH 1470/2110] Add support for NDK21 (#6460) --- CHANGELOG.md | 3 + Dockerfile | 10 +- README.md | 24 +- dependencies.list | 6 +- .../build.gradle | 2 +- examples/multiprocessExample/build.gradle | 2 +- realm/realm-library/build.gradle | 12 +- .../java/io/realm/RealmResultsTests.java | 88 +- .../realm-library/src/main/cpp/CMakeLists.txt | 59 +- .../src/main/cpp/android.toolchain.cmake | 1703 ----------------- .../src/main/cpp/io_realm_SyncSession.cpp | 15 +- .../main/cpp/io_realm_internal_OsObject.cpp | 3 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 4 +- .../cpp/io_realm_internal_OsSharedRealm.cpp | 2 +- .../src/main/cpp/jni_impl/android_logger.hpp | 2 +- .../src/main/cpp/jni_util/log.cpp | 4 + .../src/main/cpp/jni_util/log.hpp | 1 + realm/realm-library/src/main/cpp/object-store | 2 +- .../realm/internal/sync/PermissionHelper.java | 5 +- .../objectserver/ProgressListenerTests.java | 4 +- .../testUtils/java/io/realm/TestHelper.java | 2 +- tools/analyze_realm_metrics.sh | 35 + 22 files changed, 147 insertions(+), 1841 deletions(-) delete mode 100644 realm/realm-library/src/main/cpp/android.toolchain.cmake create mode 100755 tools/analyze_realm_metrics.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 14906a70f8..38ad95ce45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible ### Internal * `OsSharedRealm.VersionID.hashCode()` was not implemented correctly and included the memory location in the hashcode. * OKHttp was upgraded to 3.10.0 from 3.9.0. +* The NDK has been upgraded from r10e to r21. +* The compiler used for C++ code has changed from GCC to Clang. +* OpenSSL used by Realms encryption layer has been upgraded from 1.0.2k to 1.1.1b. ## 6.1.0(2020-01-17) diff --git a/Dockerfile b/Dockerfile index 77fa0e1f09..e41a62a059 100644 --- a/Dockerfile +++ b/Dockerfile @@ -67,13 +67,11 @@ RUN yes | sdkmanager \ # Install the NDK RUN mkdir /opt/android-ndk-tmp && \ cd /opt/android-ndk-tmp && \ - wget -q http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin -O android-ndk.bin && \ - chmod a+x ./android-ndk.bin && \ - ./android-ndk.bin && \ - mv android-ndk-r10e /opt/android-ndk && \ + wget -q https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip -O android-ndk.zip && \ + unzip android-ndk.zip && \ + mv android-ndk-r21 /opt/android-ndk && \ rm -rf /opt/android-ndk-tmp && \ - chmod -R a+rX /opt/android-ndk && \ - echo "Pkg.Desc = Android NDK\nPkg.Revision = 10.0.0" > /opt/android-ndk/source.properties + chmod -R a+rX /opt/android-ndk # Make the SDK universally writable RUN chmod -R a+rwX ${ANDROID_HOME} diff --git a/README.md b/README.md index df53f892bf..055be13bf0 100644 --- a/README.md +++ b/README.md @@ -67,32 +67,24 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * The latest stable version of Android Studio. Currently [3.5.2](https://developer.android.com/studio/). + * The latest stable version of Android Studio. Currently [3.5.3](https://developer.android.com/studio/). * Download & install the Android SDK **Build-Tools 27.0.2**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). + * Install the NDK (currently r21) from the SDK Manager in Android Studio or using the [website](https://developer.android.com/ndk/downloads). If downloaded +You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library`. The download will unzip as the directory `android-ndk-r21`. - * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). -You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library/Android`. The download will unzip as the directory `android-ndk-r10e`. - - * If you will be building with Android Studio, you will need to tell it to use the correct NDK. To do this, define the variable `ndk.dir` in `realm/local.properties` and assign it the full pathname of the directory that you unzipped above. Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited. - - ``` - ndk.dir=/Users/brian/Library/Android/android-ndk-r10e + * If you will be building with Android Studio, you will need to tell it to use the correct NDK. If you installed it using the SDK Manager, it will automatically be detected. Otherwise, you need to define the variable `ndk.dir` in `realm/local.properties` and assign it the full pathname of the directory that you unzipped above. Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited. ``` + ndk.dir=/Users//Library/android-sdk/ndk/21.0.6113669 -* You also need a file called `source.properties` to the `android-ndk-r10e` folder with the following content: - - ``` - Pkg.Desc = Android NDK - Pkg.Revision = 10.0.0 ``` - * Add two environment variables to your profile (presuming you installed the NDK in `~/Library/android-ndk-r10e`): + * Add two environment variables to your profile (presuming you installed the NDK using the SDK Manager): ``` - export ANDROID_HOME=~/Library/Android/sdk - export ANDROID_NDK_HOME=~/Library/Android/android-ndk-r10e + export ANDROID_HOME=~/Library/android-sdk + export ANDROID_NDK_HOME=~/Library/android-sdk/ndk/21.0.6113669 ``` * If you are launching Android Studio from the macOS Finder, you should also run the following two commands: diff --git a/dependencies.list b/dependencies.list index 135fcda6df..39286cc1ff 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.0-beta.2 -REALM_SYNC_SHA256=bbe58ad5110d66fe8c55a838486e09593e93ee96db8d07aa93a22abb52ead220 +REALM_SYNC_VERSION=5.0.0 +REALM_SYNC_SHA256=93825d20e47627eae314d0793380908a65d622c3cdddbc0ca30fce3bb39d21cd # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. @@ -14,6 +14,6 @@ ANDROID_BUILD_TOOLS=28.0.3 # Common classpath dependencies # Gradle 5 is not supported yet: https://issuetracker.google.com/issues/126433059 gradleVersion=4.10.1 -ndkVersion=r10e +ndkVersion=21.0.6113669 BUILD_INFO_EXTRACTOR_GRADLE=4.7.5 GRADLE_BINTRAY_PLUGIN=1.8.4 diff --git a/examples/architectureComponentsExample/build.gradle b/examples/architectureComponentsExample/build.gradle index 1f410c0cd6..ed5c02970e 100644 --- a/examples/architectureComponentsExample/build.gradle +++ b/examples/architectureComponentsExample/build.gradle @@ -19,7 +19,7 @@ android { defaultConfig { applicationId 'io.realm.examples.arch' targetSdkVersion rootProject.sdkVersion - minSdkVersion 16 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" diff --git a/examples/multiprocessExample/build.gradle b/examples/multiprocessExample/build.gradle index cdd92c6f90..155832dcbf 100644 --- a/examples/multiprocessExample/build.gradle +++ b/examples/multiprocessExample/build.gradle @@ -8,7 +8,7 @@ android { defaultConfig { applicationId "io.realm.examples.realmmultiprocessexample" targetSdkVersion rootProject.sdkVersion - minSdkVersion 16 + minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 360811fc76..bc289641a1 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -35,7 +35,7 @@ ext.coreDir = file("${project.coreDistributionDir.getAbsolutePath()}/core-${proj ext.ccachePath = project.findProperty('ccachePath') ?: System.getenv('NDK_CCACHE') ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE') // Set to true to enable linking with debug core. -ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProperty('enableDebugCore') : false //FIXME Use 'false' as default until https://github.com/realm/realm-java/issues/5354 is fixed +ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProperty('enableDebugCore') : true android { compileSdkVersion rootProject.compileSdkVersion @@ -50,12 +50,6 @@ android { externalNativeBuild { cmake { arguments "-DREALM_CORE_DIST_DIR:STRING=${project.coreDir.getAbsolutePath()}", - // FIXME: - // This is copied from https://dl.google.com/android/repository/cmake-3.4.2909474-linux-x86_64.zip - // because of the android.toolchain.cmake shipped with Android SDK CMake 3.6 doesn't work with our - // JNI build currently (lack of lto linking support). - // This file should be removed and use the one from Android SDK cmake package when it supports lto. - "-DCMAKE_TOOLCHAIN_FILE=${project.file('src/main/cpp/android.toolchain.cmake').path}", "-DENABLE_DEBUG_CORE=$project.enableDebugCore" if (project.ccachePath) arguments "-DNDK_CCACHE=$project.ccachePath" if (project.lcachePath) arguments "-DNDK_LCACHE=$project.lcachePath" @@ -63,7 +57,7 @@ android { if (project.hasProperty('buildTargetABIs') && !project.getProperty('buildTargetABIs').trim().isEmpty()) { abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*')) } else { - // armeabi is not supported anymore. + // "armeabi" and "mips" are no longer supported by the NDK abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' } } @@ -799,7 +793,7 @@ def checkNdk(String ndkPath) { } if (detectedNdkVersion != project.ndkVersion) { throw new GradleException("Your NDK version: ${detectedNdkVersion}." - + " Realm JNI must be compiled with the version ${project.ndkVersion} of NDK.") + + " Realm JNI must be compiled with version ${project.ndkVersion} of the NDK.") } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 2bbe758dce..30bc219ed6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1774,18 +1774,16 @@ public void asJSON() throws JSONException { " \"columnDate\": \"" + now + "\",\n" + " \"columnBinary\": \"AQID\",\n" + " \"columnMutableRealmInteger\": 0,\n" + - " \"columnRealmObject\": [\n" + - " {\n" + - " \"_key\": 100,\n" + - " \"name\": \"dog1\",\n" + - " \"age\": 1,\n" + - " \"height\": 1.1,\n" + - " \"weight\": 10.100000381469727,\n" + - " \"hasTail\": true,\n" + - " \"birthday\": \"" + now + "\",\n" + - " \"owner\": null\n" + - " }\n" + - " ],\n" + + " \"columnRealmObject\": {\n" + + " \"_key\": 100,\n" + + " \"name\": \"dog1\",\n" + + " \"age\": 1,\n" + + " \"height\": 1.1,\n" + + " \"weight\": 10.100000381469727,\n" + + " \"hasTail\": true,\n" + + " \"birthday\": \"" + now + "\",\n" + + " \"owner\": null\n" + + " },\n" + " \"columnRealmList\": [\n" + " {\n" + " \"_key\": 101,\n" + @@ -1805,14 +1803,12 @@ public void asJSON() throws JSONException { " \"weight\": 30.100000381469727,\n" + " \"hasTail\": true,\n" + " \"birthday\": \"" + now + "\",\n" + - " \"owner\": [\n" + - " {\n" + - " \"_key\": 0,\n" + - " \"name\": \"Dog owner 1\",\n" + - " \"dogs\": [],\n" + - " \"cat\": null\n" + - " }\n" + - " ]\n" + + " \"owner\": {\n" + + " \"_key\": 0,\n" + + " \"name\": \"Dog owner 1\",\n" + + " \"dogs\": [],\n" + + " \"cat\": null\n" + + " }\n" + " }\n" + " ],\n" + " \"columnStringList\": [\n" + @@ -1876,20 +1872,18 @@ public void asJSON_cycles() throws JSONException { " \"id\": 0,\n" + " \"name\": \"One\",\n" + " \"date\": \"" + now + "\",\n" + - " \"object\": [\n" + - " {\n" + - " \"_key\": 1,\n" + - " \"id\": 0,\n" + - " \"name\": \"Two\",\n" + - " \"date\": \"" + now + "\",\n" + - " \"object\": {\n" + - " \"table\": \"class_CyclicType\",\n" + - " \"key\": 0\n" + - " },\n" + - " \"otherObject\": null,\n" + - " \"objects\": []\n" + - " }\n" + - " ],\n" + + " \"object\": {\n" + + " \"_key\": 1,\n" + + " \"id\": 0,\n" + + " \"name\": \"Two\",\n" + + " \"date\": \"" + now + "\",\n" + + " \"object\": {\n" + + " \"table\": \"class_CyclicType\",\n" + + " \"key\": 0\n" + + " },\n" + + " \"otherObject\": null,\n" + + " \"objects\": []\n" + + " },\n" + " \"otherObject\": null,\n" + " \"objects\": []\n" + " },\n" + @@ -1898,20 +1892,18 @@ public void asJSON_cycles() throws JSONException { " \"id\": 0,\n" + " \"name\": \"Two\",\n" + " \"date\": \"" + now + "\",\n" + - " \"object\": [\n" + - " {\n" + - " \"_key\": 0,\n" + - " \"id\": 0,\n" + - " \"name\": \"One\",\n" + - " \"date\": \"" + now + "\",\n" + - " \"object\": {\n" + - " \"table\": \"class_CyclicType\",\n" + - " \"key\": 1\n" + - " },\n" + - " \"otherObject\": null,\n" + - " \"objects\": []\n" + - " }\n" + - " ],\n" + + " \"object\": {\n" + + " \"_key\": 0,\n" + + " \"id\": 0,\n" + + " \"name\": \"One\",\n" + + " \"date\": \"" + now + "\",\n" + + " \"object\": {\n" + + " \"table\": \"class_CyclicType\",\n" + + " \"key\": 1\n" + + " },\n" + + " \"otherObject\": null,\n" + + " \"objects\": []\n" + + " },\n" + " \"otherObject\": null,\n" + " \"objects\": []\n" + " }\n" + diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index f8bd197a12..677d592023 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -101,30 +101,24 @@ create_javah(TARGET jni_headers ) include(RealmCore) - use_realm_core(${build_SYNC} "${REALM_CORE_DIST_DIR}" "${CORE_SOURCE_PATH}") -# Download openssl lib -#string(TOLOWER "${CMAKE_BUILD_TYPE}" openssl_build_TYPE) -set(openssl_build_TYPE "release") +# Download OpenSSL lib # FIXME Read the openssl version from core when the core/sync release has that information. -set(openssl_VERSION "1.0.2k") -set(openssl_BUILD_NUMBER "1") -set(openssl_FILENAME "openssl-${openssl_build_TYPE}-${openssl_VERSION}-${openssl_BUILD_NUMBER}-Android-${ANDROID_ABI}") -set(openssl_URL "http://static.realm.io/downloads/openssl/${openssl_VERSION}/Android/${ANDROID_ABI}/${openssl_FILENAME}.tar.gz") +set(openssl_VERSION "1.1.1b") +set(openssl_FILENAME "openssl.tgz") +set(openssl_URL "https://static.realm.io/downloads/openssl/${openssl_VERSION}/Android/${ANDROID_ABI}/${openssl_FILENAME}") message(STATUS "Downloading OpenSSL...") -file(DOWNLOAD "${openssl_URL}" "${PROJECT_BINARY_DIR}/${openssl_FILENAME}.tar.gz") - -message(STATUS "Uncompressing OpenSSL...") -execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz "${PROJECT_BINARY_DIR}/${openssl_FILENAME}.tar.gz" - WORKING_DIRECTORY "${PROJECT_BINARY_DIR}") +file(DOWNLOAD "${openssl_URL}" "${PROJECT_BINARY_DIR}/${openssl_FILENAME}") +message(STATUS "Uncompressing OpenSSL: ${PROJECT_BINARY_DIR}/${openssl_FILENAME}") +execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz "${openssl_FILENAME}" WORKING_DIRECTORY "${PROJECT_BINARY_DIR}") message(STATUS "Importing OpenSSL...") -include(${PROJECT_BINARY_DIR}/${openssl_FILENAME}/openssl.cmake) -get_target_property(openssl_include_DIR crypto INTERFACE_INCLUDE_DIRECTORIES) -get_target_property(crypto_LIB crypto IMPORTED_LOCATION) -get_target_property(ssl_LIB ssl IMPORTED_LOCATION) +include(${PROJECT_BINARY_DIR}/lib/cmake/OpenSSL/OpenSSLConfig.cmake) +get_target_property(openssl_include_DIR OpenSSL::Crypto INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(crypto_LIB OpenSSL::Crypto IMPORTED_LOCATION) +get_target_property(ssl_LIB OpenSSL::SSL IMPORTED_LOCATION) # build application's shared lib include_directories( @@ -132,16 +126,6 @@ include_directories( ${jni_headers_PATH} ${CMAKE_SOURCE_DIR}/object-store/src) -set(ANDROID_STL "gnustl_static") -set(ANDROID_NO_UNDEFINED OFF) -set(ANDROID_SO_UNDEFINED ON) - -if (ARMEABI) - set(ABI_CXX_FLAGS "-mthumb") -elseif (ARMEABI_V7A) - set(ABI_CXX_FLAGS "-mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16") -endif() - # Hack the memmove bug on Samsung device. if (ARMEABI OR ARMEABI_V7A) set(REALM_LINKER_FLAGS "${REALM_LINKER_FLAGS} -Wl,--wrap,memmove -Wl,--wrap,memcpy") @@ -157,19 +141,20 @@ endif() # -Wno-missing-field-initializers disable in object store as well. set(WARNING_CXX_FLAGS "-Werror -Wall -Wextra -pedantic -Wmissing-declarations \ -Wempty-body -Wparentheses -Wunknown-pragmas -Wunreachable-code \ - -Wno-missing-field-initializers -Wno-maybe-uninitialized -Wno-uninitialized") -set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -pthread -fvisibility=hidden -std=c++14 -fsigned-char") + -Wno-missing-field-initializers -Wno-unevaluated-expression -Wno-unreachable-code") +set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -fdata-sections -pthread -frtti -fvisibility=hidden -fsigned-char -fno-stack-protector -std=c++14") if (build_SYNC) set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ENABLE_SYNC=1") endif() -# There might be an issue with -Os of ndk gcc 4.9. It will hang the encryption related tests. -# And this issue doesn't seem to impact the core compiling. -set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") -#-ggdb doesn't play well with -flto -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb -Og") +set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Oz") +# -ggdb doesn't play well with -flto +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -glldb -g") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${REALM_COMMON_CXX_FLAGS} ${WARNING_CXX_FLAGS} ${ABI_CXX_FLAGS}") -# Set link flags +# Set Linker flags flags +if (CMAKE_BUILD_TYPE STREQUAL "Release") + set(REALM_LINKER_FLAGS "${REALM_LINKER_FLAGS} -Wl,-gc-sections") +endif() if (build_SYNC) set(REALM_LINKER_FLAGS "${REALM_LINKER_FLAGS} -lz") endif() @@ -215,9 +200,9 @@ add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SR add_dependencies(realm-jni jni_headers) if (build_SYNC) - target_link_libraries(realm-jni log android lib_realm_sync crypto ssl) + target_link_libraries(realm-jni log android lib_realm_sync OpenSSL::SSL OpenSSL::Crypto) else() - target_link_libraries(realm-jni log android lib_realm_core crypto) + target_link_libraries(realm-jni log android lib_realm_core OpenSSL::Crypto) endif() # Strip the release so files and backup the unstripped versions diff --git a/realm/realm-library/src/main/cpp/android.toolchain.cmake b/realm/realm-library/src/main/cpp/android.toolchain.cmake deleted file mode 100644 index 86046dfa45..0000000000 --- a/realm/realm-library/src/main/cpp/android.toolchain.cmake +++ /dev/null @@ -1,1703 +0,0 @@ -# Copyright (c) 2010-2011, Ethan Rublee -# Copyright (c) 2011-2014, Andrey Kamaev -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -# ------------------------------------------------------------------------------ -# Android CMake toolchain file, for use with the Android NDK r5-r10d -# Requires cmake 2.6.3 or newer (2.8.9 or newer is recommended). -# See home page: https://github.com/taka-no-me/android-cmake -# -# Usage Linux: -# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk -# $ mkdir build && cd build -# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake .. -# $ make -j8 -# -# Usage Windows: -# You need native port of make to build your project. -# Android NDK r7 (and newer) already has make.exe on board. -# For older NDK you have to install it separately. -# For example, this one: http://gnuwin32.sourceforge.net/packages/make.htm -# -# $ SET ANDROID_NDK=C:\absolute\path\to\the\android-ndk -# $ mkdir build && cd build -# $ cmake.exe -G"MinGW Makefiles" -# -DCMAKE_TOOLCHAIN_FILE=path\to\the\android.toolchain.cmake -# -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%\prebuilt\windows\bin\make.exe" .. -# $ cmake.exe --build . -# -# -# Options (can be set as cmake parameters: -D=): -# ANDROID_NDK=/opt/android-ndk - path to the NDK root. -# Can be set as environment variable. Can be set only at first cmake run. -# -# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary -# Interface (ABI). This option nearly matches to the APP_ABI variable -# used by ndk-build tool from Android NDK. -# -# Possible targets are: -# "armeabi" - ARMv5TE based CPU with software floating point operations -# "armeabi-v7a" - ARMv7 based devices with hardware FPU instructions -# this ABI target is used by default -# "armeabi-v7a with NEON" - same as armeabi-v7a, but -# sets NEON as floating-point unit -# "armeabi-v7a with VFPV3" - same as armeabi-v7a, but -# sets VFPV3 as floating-point unit (has 32 registers instead of 16) -# "armeabi-v6 with VFP" - tuned for ARMv6 processors having VFP -# "x86" - IA-32 instruction set -# "mips" - MIPS32 instruction set -# -# 64-bit ABIs for NDK r10 and newer: -# "arm64-v8a" - ARMv8 AArch64 instruction set -# "x86_64" - Intel64 instruction set (r1) -# "mips64" - MIPS64 instruction set (r6) -# -# ANDROID_NATIVE_API_LEVEL=android-9 - level of Android API compile for. -# Option is read-only when standalone toolchain is used. -# Note: building for "android-L" requires explicit configuration. -# -# ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.9 - the name of compiler -# toolchain to be used. The list of possible values depends on the NDK -# version. For NDK r10c the possible values are: -# -# * aarch64-linux-android-4.9 -# * aarch64-linux-android-clang3.4 -# * aarch64-linux-android-clang3.5 -# * arm-linux-androideabi-4.6 -# * arm-linux-androideabi-4.8 -# * arm-linux-androideabi-4.9 (default) -# * arm-linux-androideabi-clang3.4 -# * arm-linux-androideabi-clang3.5 -# * mips64el-linux-android-4.9 -# * mips64el-linux-android-clang3.4 -# * mips64el-linux-android-clang3.5 -# * mipsel-linux-android-4.6 -# * mipsel-linux-android-4.8 -# * mipsel-linux-android-4.9 -# * mipsel-linux-android-clang3.4 -# * mipsel-linux-android-clang3.5 -# * x86-4.6 -# * x86-4.8 -# * x86-4.9 -# * x86-clang3.4 -# * x86-clang3.5 -# * x86_64-4.9 -# * x86_64-clang3.4 -# * x86_64-clang3.5 -# -# ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions -# instead of Thumb. Is not available for "armeabi-v6 with VFP" -# (is forced to be ON) ABI. -# -# ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker -# errors even if they are not used. -# -# ANDROID_SO_UNDEFINED=OFF - set ON to allow undefined symbols in shared -# libraries. Automatically turned for NDK r5x and r6x due to GLESv2 -# problems. -# -# ANDROID_STL=gnustl_static - specify the runtime to use. -# -# Possible values are: -# none -> Do not configure the runtime. -# system -> Use the default minimal system C++ runtime library. -# Implies -fno-rtti -fno-exceptions. -# Is not available for standalone toolchain. -# system_re -> Use the default minimal system C++ runtime library. -# Implies -frtti -fexceptions. -# Is not available for standalone toolchain. -# gabi++_static -> Use the GAbi++ runtime as a static library. -# Implies -frtti -fno-exceptions. -# Available for NDK r7 and newer. -# Is not available for standalone toolchain. -# gabi++_shared -> Use the GAbi++ runtime as a shared library. -# Implies -frtti -fno-exceptions. -# Available for NDK r7 and newer. -# Is not available for standalone toolchain. -# stlport_static -> Use the STLport runtime as a static library. -# Implies -fno-rtti -fno-exceptions for NDK before r7. -# Implies -frtti -fno-exceptions for NDK r7 and newer. -# Is not available for standalone toolchain. -# stlport_shared -> Use the STLport runtime as a shared library. -# Implies -fno-rtti -fno-exceptions for NDK before r7. -# Implies -frtti -fno-exceptions for NDK r7 and newer. -# Is not available for standalone toolchain. -# gnustl_static -> Use the GNU STL as a static library. -# Implies -frtti -fexceptions. -# gnustl_shared -> Use the GNU STL as a shared library. -# Implies -frtti -fno-exceptions. -# Available for NDK r7b and newer. -# Silently degrades to gnustl_static if not available. -# -# ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on -# chosen runtime. If disabled, then the user is responsible for settings -# these options. -# -# What?: -# android-cmake toolchain searches for NDK/toolchain in the following order: -# ANDROID_NDK - cmake parameter -# ANDROID_NDK - environment variable -# ANDROID_STANDALONE_TOOLCHAIN - cmake parameter -# ANDROID_STANDALONE_TOOLCHAIN - environment variable -# ANDROID_NDK - default locations -# ANDROID_STANDALONE_TOOLCHAIN - default locations -# -# Make sure to do the following in your scripts: -# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}" ) -# SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}" ) -# The flags will be prepopulated with critical flags, so don't loose them. -# Also be aware that toolchain also sets configuration-specific compiler -# flags and linker flags. -# -# ANDROID and BUILD_ANDROID will be set to true, you may test any of these -# variables to make necessary Android-specific configuration changes. -# -# Also ARMEABI or ARMEABI_V7A or X86 or MIPS or ARM64_V8A or X86_64 or MIPS64 -# will be set true, mutually exclusive. NEON option will be set true -# if VFP is set to NEON. -# -# ------------------------------------------------------------------------------ - -# FIXME: -# This is copied from https://dl.google.com/android/repository/cmake-3.4.2909474-linux-x86_64.zip -# because of the android.toolchain.cmake shipped with Android SDK CMake 3.6 doesn't work with our -# JNI build currently (lack of lto linking support.). Two modifications are made to avoid warnings -# with CMake 3.6 -- disable CMAKE_FORCE_CXX_COMPILER & CMAKE_FORCE_C_COMPILER. -# This file should be removed and use the one from Android SDK cmake package when it supports lto. - -cmake_minimum_required( VERSION 2.6.3 ) - -if( DEFINED CMAKE_CROSSCOMPILING ) - # subsequent toolchain loading is not really needed - return() -endif() - -if( CMAKE_TOOLCHAIN_FILE ) - # touch toolchain variable to suppress "unused variable" warning -endif() - -# inherit settings in recursive loads -get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE ) -if( _CMAKE_IN_TRY_COMPILE ) - include( "${CMAKE_CURRENT_SOURCE_DIR}/../android.toolchain.config.cmake" OPTIONAL ) -endif() - -# this one is important -if( CMAKE_VERSION VERSION_GREATER "3.0.99" ) - set( CMAKE_SYSTEM_NAME Android ) -else() - set( CMAKE_SYSTEM_NAME Linux ) -endif() - -# this one not so much -set( CMAKE_SYSTEM_VERSION 1 ) - -# rpath makes low sense for Android -set( CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "" ) -set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." ) - -# NDK search paths -set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10d -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" ) -if( NOT DEFINED ANDROID_NDK_SEARCH_PATHS ) - if( CMAKE_HOST_WIN32 ) - file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS ) - set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}" "$ENV{SystemDrive}/NVPACK" ) - else() - file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS ) - set( ANDROID_NDK_SEARCH_PATHS /opt "${ANDROID_NDK_SEARCH_PATHS}/NVPACK" ) - endif() -endif() -if( NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) - set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain ) -endif() - -# known ABIs -set( ANDROID_SUPPORTED_ABIS_arm "armeabi-v7a;armeabi;armeabi-v7a with NEON;armeabi-v7a with VFPV3;armeabi-v6 with VFP" ) -set( ANDROID_SUPPORTED_ABIS_arm64 "arm64-v8a" ) -set( ANDROID_SUPPORTED_ABIS_x86 "x86" ) -set( ANDROID_SUPPORTED_ABIS_x86_64 "x86_64" ) -set( ANDROID_SUPPORTED_ABIS_mips "mips" ) -set( ANDROID_SUPPORTED_ABIS_mips64 "mips64" ) - -# API level defaults -set( ANDROID_DEFAULT_NDK_API_LEVEL 9 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_arm64 21 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_x86 9 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_x86_64 21 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_mips 9 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_mips64 21 ) - - -macro( __LIST_FILTER listvar regex ) - if( ${listvar} ) - foreach( __val ${${listvar}} ) - if( __val MATCHES "${regex}" ) - list( REMOVE_ITEM ${listvar} "${__val}" ) - endif() - endforeach() - endif() -endmacro() - -macro( __INIT_VARIABLE var_name ) - set( __test_path 0 ) - foreach( __var ${ARGN} ) - if( __var STREQUAL "PATH" ) - set( __test_path 1 ) - break() - endif() - endforeach() - - if( __test_path AND NOT EXISTS "${${var_name}}" ) - unset( ${var_name} CACHE ) - endif() - - if( " ${${var_name}}" STREQUAL " " ) - set( __values 0 ) - foreach( __var ${ARGN} ) - if( __var STREQUAL "VALUES" ) - set( __values 1 ) - elseif( NOT __var STREQUAL "PATH" ) - if( __var MATCHES "^ENV_.*$" ) - string( REPLACE "ENV_" "" __var "${__var}" ) - set( __value "$ENV{${__var}}" ) - elseif( DEFINED ${__var} ) - set( __value "${${__var}}" ) - elseif( __values ) - set( __value "${__var}" ) - else() - set( __value "" ) - endif() - - if( NOT " ${__value}" STREQUAL " " AND (NOT __test_path OR EXISTS "${__value}") ) - set( ${var_name} "${__value}" ) - break() - endif() - endif() - endforeach() - unset( __value ) - unset( __values ) - endif() - - if( __test_path ) - file( TO_CMAKE_PATH "${${var_name}}" ${var_name} ) - endif() - unset( __test_path ) -endmacro() - -macro( __DETECT_NATIVE_API_LEVEL _var _path ) - set( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" ) - file( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" ) - if( NOT __apiFileContent ) - message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." ) - endif() - string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" ) - unset( __apiFileContent ) - unset( __ndkApiLevelRegex ) -endmacro() - -macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root ) - if( EXISTS "${_root}" ) - file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" ) - __LIST_FILTER( __gccExePath "^[.].*" ) - list( LENGTH __gccExePath __gccExePathsCount ) - if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE ) - message( WARNING "Could not determine machine name for compiler from ${_root}" ) - set( ${_var} "" ) - else() - get_filename_component( __gccExeName "${__gccExePath}" NAME_WE ) - string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" ) - endif() - unset( __gccExePath ) - unset( __gccExePathsCount ) - unset( __gccExeName ) - else() - set( ${_var} "" ) - endif() -endmacro() - - -# fight against cygwin -set( ANDROID_FORBID_SYGWIN TRUE CACHE BOOL "Prevent cmake from working under cygwin and using cygwin tools") -mark_as_advanced( ANDROID_FORBID_SYGWIN ) -if( ANDROID_FORBID_SYGWIN ) - if( CYGWIN ) - message( FATAL_ERROR "Android NDK and android-cmake toolchain are not welcome Cygwin. It is unlikely that this cmake toolchain will work under cygwin. But if you want to try then you can set cmake variable ANDROID_FORBID_SYGWIN to FALSE and rerun cmake." ) - endif() - - if( CMAKE_HOST_WIN32 ) - # remove cygwin from PATH - set( __new_path "$ENV{PATH}") - __LIST_FILTER( __new_path "cygwin" ) - set(ENV{PATH} "${__new_path}") - unset(__new_path) - endif() -endif() - - -# detect current host platform -if( NOT DEFINED ANDROID_NDK_HOST_X64 AND (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64" OR CMAKE_HOST_APPLE) ) - set( ANDROID_NDK_HOST_X64 1 CACHE BOOL "Try to use 64-bit compiler toolchain" ) - mark_as_advanced( ANDROID_NDK_HOST_X64 ) -endif() - -set( TOOL_OS_SUFFIX "" ) -if( CMAKE_HOST_APPLE ) - set( ANDROID_NDK_HOST_SYSTEM_NAME "darwin-x86_64" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME2 "darwin-x86" ) -elseif( CMAKE_HOST_WIN32 ) - set( ANDROID_NDK_HOST_SYSTEM_NAME "windows-x86_64" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME2 "windows" ) - set( TOOL_OS_SUFFIX ".exe" ) -elseif( CMAKE_HOST_UNIX ) - set( ANDROID_NDK_HOST_SYSTEM_NAME "linux-x86_64" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME2 "linux-x86" ) -else() - message( FATAL_ERROR "Cross-compilation on your platform is not supported by this cmake toolchain" ) -endif() - -if( NOT ANDROID_NDK_HOST_X64 ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) -endif() - -# see if we have path to Android NDK -if( NOT ANDROID_NDK AND NOT ANDROID_STANDALONE_TOOLCHAIN ) - __INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK ) -endif() -if( NOT ANDROID_NDK ) - # see if we have path to Android standalone toolchain - __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN ) - - if( NOT ANDROID_STANDALONE_TOOLCHAIN ) - #try to find Android NDK in one of the the default locations - set( __ndkSearchPaths ) - foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} ) - foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} ) - list( APPEND __ndkSearchPaths "${__ndkSearchPath}/android-ndk${suffix}" ) - endforeach() - endforeach() - __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} ) - unset( __ndkSearchPaths ) - - if( ANDROID_NDK ) - message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" ) - message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" ) - else() - #try to find Android standalone toolchain in one of the the default locations - __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) - - if( ANDROID_STANDALONE_TOOLCHAIN ) - message( STATUS "Using default path for standalone toolchain ${ANDROID_STANDALONE_TOOLCHAIN}" ) - message( STATUS " If you prefer to use a different location, please define the variable: ANDROID_STANDALONE_TOOLCHAIN" ) - endif( ANDROID_STANDALONE_TOOLCHAIN ) - endif( ANDROID_NDK ) - endif( NOT ANDROID_STANDALONE_TOOLCHAIN ) -endif( NOT ANDROID_NDK ) - -# remember found paths -if( ANDROID_NDK ) - get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE ) - set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE ) - set( BUILD_WITH_ANDROID_NDK True ) - if( EXISTS "${ANDROID_NDK}/RELEASE.TXT" ) - file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX "r[0-9]+[a-z]?" ) - string( REGEX MATCH "r([0-9]+)([a-z]?)" ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) - else() - set( ANDROID_NDK_RELEASE "r1x" ) - set( ANDROID_NDK_RELEASE_FULL "unreleased" ) - endif() - string( REGEX REPLACE "r([0-9]+)([a-z]?)" "\\1*1000" ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE}" ) - string( FIND " abcdefghijklmnopqastuvwxyz" "${CMAKE_MATCH_2}" __ndkReleaseLetterNum ) - math( EXPR ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE_NUM}+${__ndkReleaseLetterNum}" ) -elseif( ANDROID_STANDALONE_TOOLCHAIN ) - get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE ) - # try to detect change - if( CMAKE_AR ) - string( LENGTH "${ANDROID_STANDALONE_TOOLCHAIN}" __length ) - string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidStandaloneToolchainPreviousPath ) - if( NOT __androidStandaloneToolchainPreviousPath STREQUAL ANDROID_STANDALONE_TOOLCHAIN ) - message( FATAL_ERROR "It is not possible to change path to the Android standalone toolchain on subsequent run." ) - endif() - unset( __androidStandaloneToolchainPreviousPath ) - unset( __length ) - endif() - set( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" CACHE INTERNAL "Path of the Android standalone toolchain" FORCE ) - set( BUILD_WITH_STANDALONE_TOOLCHAIN True ) -else() - list(GET ANDROID_NDK_SEARCH_PATHS 0 ANDROID_NDK_SEARCH_PATH) - message( FATAL_ERROR "Could not find neither Android NDK nor Android standalone toolchain. - You should either set an environment variable: - export ANDROID_NDK=~/my-android-ndk - or - export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain - or put the toolchain or NDK in the default path: - sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}/android-ndk - sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" ) -endif() - -# android NDK layout -if( BUILD_WITH_ANDROID_NDK ) - if( NOT DEFINED ANDROID_NDK_LAYOUT ) - # try to automatically detect the layout - if( EXISTS "${ANDROID_NDK}/RELEASE.TXT") - set( ANDROID_NDK_LAYOUT "RELEASE" ) - elseif( EXISTS "${ANDROID_NDK}/../../linux-x86/toolchain/" ) - set( ANDROID_NDK_LAYOUT "LINARO" ) - elseif( EXISTS "${ANDROID_NDK}/../../gcc/" ) - set( ANDROID_NDK_LAYOUT "ANDROID" ) - endif() - endif() - set( ANDROID_NDK_LAYOUT "${ANDROID_NDK_LAYOUT}" CACHE STRING "The inner layout of NDK" ) - mark_as_advanced( ANDROID_NDK_LAYOUT ) - if( ANDROID_NDK_LAYOUT STREQUAL "LINARO" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment - set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../${ANDROID_NDK_HOST_SYSTEM_NAME}/toolchain" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) - elseif( ANDROID_NDK_LAYOUT STREQUAL "ANDROID" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment - set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../gcc/${ANDROID_NDK_HOST_SYSTEM_NAME}/arm" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) - else() # ANDROID_NDK_LAYOUT STREQUAL "RELEASE" - set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/toolchains" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME2}" ) - endif() - get_filename_component( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK_TOOLCHAINS_PATH}" ABSOLUTE ) - - # try to detect change of NDK - if( CMAKE_AR ) - string( LENGTH "${ANDROID_NDK_TOOLCHAINS_PATH}" __length ) - string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) - if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK_TOOLCHAINS_PATH ) - message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. - " ) - endif() - unset( __androidNdkPreviousPath ) - unset( __length ) - endif() -endif() - - -# get all the details about standalone toolchain -if( BUILD_WITH_STANDALONE_TOOLCHAIN ) - __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" ) - set( ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) - set( __availableToolchains "standalone" ) - __DETECT_TOOLCHAIN_MACHINE_NAME( __availableToolchainMachines "${ANDROID_STANDALONE_TOOLCHAIN}" ) - if( NOT __availableToolchainMachines ) - message( FATAL_ERROR "Could not determine machine name of your toolchain. Probably your Android standalone toolchain is broken." ) - endif() - if( __availableToolchainMachines MATCHES x86_64 ) - set( __availableToolchainArchs "x86_64" ) - elseif( __availableToolchainMachines MATCHES i686 ) - set( __availableToolchainArchs "x86" ) - elseif( __availableToolchainMachines MATCHES aarch64 ) - set( __availableToolchainArchs "arm64" ) - elseif( __availableToolchainMachines MATCHES arm ) - set( __availableToolchainArchs "arm" ) - elseif( __availableToolchainMachines MATCHES mips64el ) - set( __availableToolchainArchs "mips64" ) - elseif( __availableToolchainMachines MATCHES mipsel ) - set( __availableToolchainArchs "mips" ) - endif() - execute_process( COMMAND "${ANDROID_STANDALONE_TOOLCHAIN}/bin/${__availableToolchainMachines}-gcc${TOOL_OS_SUFFIX}" -dumpversion - OUTPUT_VARIABLE __availableToolchainCompilerVersions OUTPUT_STRIP_TRAILING_WHITESPACE ) - string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?" __availableToolchainCompilerVersions "${__availableToolchainCompilerVersions}" ) - if( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/bin/clang${TOOL_OS_SUFFIX}" ) - list( APPEND __availableToolchains "standalone-clang" ) - list( APPEND __availableToolchainMachines ${__availableToolchainMachines} ) - list( APPEND __availableToolchainArchs ${__availableToolchainArchs} ) - list( APPEND __availableToolchainCompilerVersions ${__availableToolchainCompilerVersions} ) - endif() -endif() - -macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __toolchain_subpath ) - foreach( __toolchain ${${__availableToolchainsLst}} ) - if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}${__toolchain_subpath}" ) - SET( __toolchainVersionRegex "^TOOLCHAIN_VERSION[\t ]+:=[\t ]+(.*)$" ) - FILE( STRINGS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}/setup.mk" __toolchainVersionStr REGEX "${__toolchainVersionRegex}" ) - if( __toolchainVersionStr ) - string( REGEX REPLACE "${__toolchainVersionRegex}" "\\1" __toolchainVersionStr "${__toolchainVersionStr}" ) - string( REGEX REPLACE "-clang3[.][0-9]$" "-${__toolchainVersionStr}" __gcc_toolchain "${__toolchain}" ) - else() - string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" ) - endif() - unset( __toolchainVersionStr ) - unset( __toolchainVersionRegex ) - else() - set( __gcc_toolchain "${__toolchain}" ) - endif() - __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK_TOOLCHAINS_PATH}/${__gcc_toolchain}${__toolchain_subpath}" ) - if( __machine ) - string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9x]+)?$" __version "${__gcc_toolchain}" ) - if( __machine MATCHES x86_64 ) - set( __arch "x86_64" ) - elseif( __machine MATCHES i686 ) - set( __arch "x86" ) - elseif( __machine MATCHES aarch64 ) - set( __arch "arm64" ) - elseif( __machine MATCHES arm ) - set( __arch "arm" ) - elseif( __machine MATCHES mips64el ) - set( __arch "mips64" ) - elseif( __machine MATCHES mipsel ) - set( __arch "mips" ) - else() - set( __arch "" ) - endif() - #message("machine: !${__machine}!\narch: !${__arch}!\nversion: !${__version}!\ntoolchain: !${__toolchain}!\n") - if (__arch) - list( APPEND __availableToolchainMachines "${__machine}" ) - list( APPEND __availableToolchainArchs "${__arch}" ) - list( APPEND __availableToolchainCompilerVersions "${__version}" ) - list( APPEND ${__availableToolchainsVar} "${__toolchain}" ) - endif() - endif() - unset( __gcc_toolchain ) - endforeach() -endmacro() - -# get all the details about NDK -if( BUILD_WITH_ANDROID_NDK ) - file( GLOB ANDROID_SUPPORTED_NATIVE_API_LEVELS RELATIVE "${ANDROID_NDK}/platforms" "${ANDROID_NDK}/platforms/android-*" ) - string( REPLACE "android-" "" ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_SUPPORTED_NATIVE_API_LEVELS}" ) - set( __availableToolchains "" ) - set( __availableToolchainMachines "" ) - set( __availableToolchainArchs "" ) - set( __availableToolchainCompilerVersions "" ) - if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_TOOLCHAIN_NAME}/" ) - # do not go through all toolchains if we know the name - set( __availableToolchainsLst "${ANDROID_TOOLCHAIN_NAME}" ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) - if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) - if( __availableToolchains ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) - endif() - endif() - endif() - if( NOT __availableToolchains ) - file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" ) - if( __availableToolchainsLst ) - list(SORT __availableToolchainsLst) # we need clang to go after gcc - endif() - __LIST_FILTER( __availableToolchainsLst "^[.]" ) - __LIST_FILTER( __availableToolchainsLst "llvm" ) - __LIST_FILTER( __availableToolchainsLst "renderscript" ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) - if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) - if( __availableToolchains ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) - endif() - endif() - endif() - if( NOT __availableToolchains ) - message( FATAL_ERROR "Could not find any working toolchain in the NDK. Probably your Android NDK is broken." ) - endif() -endif() - -# build list of available ABIs -set( ANDROID_SUPPORTED_ABIS "" ) -set( __uniqToolchainArchNames ${__availableToolchainArchs} ) -list( REMOVE_DUPLICATES __uniqToolchainArchNames ) -list( SORT __uniqToolchainArchNames ) -foreach( __arch ${__uniqToolchainArchNames} ) - list( APPEND ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${__arch}} ) -endforeach() -unset( __uniqToolchainArchNames ) -if( NOT ANDROID_SUPPORTED_ABIS ) - message( FATAL_ERROR "No one of known Android ABIs is supported by this cmake toolchain." ) -endif() - -# choose target ABI -__INIT_VARIABLE( ANDROID_ABI VALUES ${ANDROID_SUPPORTED_ABIS} ) -# verify that target ABI is supported -list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx ) -if( __androidAbiIdx EQUAL -1 ) - string( REPLACE ";" "\", \"" PRINTABLE_ANDROID_SUPPORTED_ABIS "${ANDROID_SUPPORTED_ABIS}" ) - message( FATAL_ERROR "Specified ANDROID_ABI = \"${ANDROID_ABI}\" is not supported by this cmake toolchain or your NDK/toolchain. - Supported values are: \"${PRINTABLE_ANDROID_SUPPORTED_ABIS}\" - " ) -endif() -unset( __androidAbiIdx ) - -# set target ABI options -if( ANDROID_ABI STREQUAL "x86" ) - set( X86 true ) - set( ANDROID_NDK_ABI_NAME "x86" ) - set( ANDROID_ARCH_NAME "x86" ) - set( ANDROID_LLVM_TRIPLE "i686-none-linux-android" ) - set( CMAKE_SYSTEM_PROCESSOR "i686" ) -elseif( ANDROID_ABI STREQUAL "x86_64" ) - set( X86 true ) - set( X86_64 true ) - set( ANDROID_NDK_ABI_NAME "x86_64" ) - set( ANDROID_ARCH_NAME "x86_64" ) - set( CMAKE_SYSTEM_PROCESSOR "x86_64" ) - set( ANDROID_LLVM_TRIPLE "x86_64-none-linux-android" ) -elseif( ANDROID_ABI STREQUAL "mips64" ) - set( MIPS64 true ) - set( ANDROID_NDK_ABI_NAME "mips64" ) - set( ANDROID_ARCH_NAME "mips64" ) - set( ANDROID_LLVM_TRIPLE "mips64el-none-linux-android" ) - set( CMAKE_SYSTEM_PROCESSOR "mips64" ) -elseif( ANDROID_ABI STREQUAL "mips" ) - set( MIPS true ) - set( ANDROID_NDK_ABI_NAME "mips" ) - set( ANDROID_ARCH_NAME "mips" ) - set( ANDROID_LLVM_TRIPLE "mipsel-none-linux-android" ) - set( CMAKE_SYSTEM_PROCESSOR "mips" ) -elseif( ANDROID_ABI STREQUAL "arm64-v8a" ) - set( ARM64_V8A true ) - set( ANDROID_NDK_ABI_NAME "arm64-v8a" ) - set( ANDROID_ARCH_NAME "arm64" ) - set( ANDROID_LLVM_TRIPLE "aarch64-none-linux-android" ) - set( CMAKE_SYSTEM_PROCESSOR "aarch64" ) - set( VFPV3 true ) - set( NEON true ) -elseif( ANDROID_ABI STREQUAL "armeabi" ) - set( ARMEABI true ) - set( ANDROID_NDK_ABI_NAME "armeabi" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv5te" ) -elseif( ANDROID_ABI STREQUAL "armeabi-v6 with VFP" ) - set( ARMEABI_V6 true ) - set( ANDROID_NDK_ABI_NAME "armeabi" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv6" ) - # need always fallback to older platform - set( ARMEABI true ) -elseif( ANDROID_ABI STREQUAL "armeabi-v7a") - set( ARMEABI_V7A true ) - set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) -elseif( ANDROID_ABI STREQUAL "armeabi-v7a with VFPV3" ) - set( ARMEABI_V7A true ) - set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) - set( VFPV3 true ) -elseif( ANDROID_ABI STREQUAL "armeabi-v7a with NEON" ) - set( ARMEABI_V7A true ) - set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) - set( VFPV3 true ) - set( NEON true ) -else() - message( SEND_ERROR "Unknown ANDROID_ABI=\"${ANDROID_ABI}\" is specified." ) -endif() - -if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" ) - # really dirty hack - # it is not possible to change CMAKE_SYSTEM_PROCESSOR after the first run... - file( APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" "SET(CMAKE_SYSTEM_PROCESSOR \"${CMAKE_SYSTEM_PROCESSOR}\")\n" ) -endif() - -if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 ) - __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD VALUES OFF ) - set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE ) - mark_as_advanced( ANDROID_FORCE_ARM_BUILD ) -else() - unset( ANDROID_FORCE_ARM_BUILD CACHE ) -endif() - -# choose toolchain -if( ANDROID_TOOLCHAIN_NAME ) - list( FIND __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" __toolchainIdx ) - if( __toolchainIdx EQUAL -1 ) - list( SORT __availableToolchains ) - string( REPLACE ";" "\n * " toolchains_list "${__availableToolchains}" ) - set( toolchains_list " * ${toolchains_list}") - message( FATAL_ERROR "Specified toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is missing in your NDK or broken. Please verify that your NDK is working or select another compiler toolchain. -To configure the toolchain set CMake variable ANDROID_TOOLCHAIN_NAME to one of the following values:\n${toolchains_list}\n" ) - endif() - list( GET __availableToolchainArchs ${__toolchainIdx} __toolchainArch ) - if( NOT __toolchainArch STREQUAL ANDROID_ARCH_NAME ) - message( SEND_ERROR "Selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is not able to compile binaries for the \"${ANDROID_ARCH_NAME}\" platform." ) - endif() -else() - set( __toolchainIdx -1 ) - set( __applicableToolchains "" ) - set( __toolchainMaxVersion "0.0.0" ) - list( LENGTH __availableToolchains __availableToolchainsCount ) - math( EXPR __availableToolchainsCount "${__availableToolchainsCount}-1" ) - foreach( __idx RANGE ${__availableToolchainsCount} ) - list( GET __availableToolchainArchs ${__idx} __toolchainArch ) - if( __toolchainArch STREQUAL ANDROID_ARCH_NAME ) - list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion ) - string( REPLACE "x" "99" __toolchainVersion "${__toolchainVersion}") - if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion ) - set( __toolchainMaxVersion "${__toolchainVersion}" ) - set( __toolchainIdx ${__idx} ) - endif() - endif() - endforeach() - unset( __availableToolchainsCount ) - unset( __toolchainMaxVersion ) - unset( __toolchainVersion ) -endif() -unset( __toolchainArch ) -if( __toolchainIdx EQUAL -1 ) - message( FATAL_ERROR "No one of available compiler toolchains is able to compile for ${ANDROID_ARCH_NAME} platform." ) -endif() -list( GET __availableToolchains ${__toolchainIdx} ANDROID_TOOLCHAIN_NAME ) -list( GET __availableToolchainMachines ${__toolchainIdx} ANDROID_TOOLCHAIN_MACHINE_NAME ) -list( GET __availableToolchainCompilerVersions ${__toolchainIdx} ANDROID_COMPILER_VERSION ) - -unset( __toolchainIdx ) -unset( __availableToolchains ) -unset( __availableToolchainMachines ) -unset( __availableToolchainArchs ) -unset( __availableToolchainCompilerVersions ) - -# choose native API level -__INIT_VARIABLE( ANDROID_NATIVE_API_LEVEL ENV_ANDROID_NATIVE_API_LEVEL ANDROID_API_LEVEL ENV_ANDROID_API_LEVEL ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME} ANDROID_DEFAULT_NDK_API_LEVEL ) -string( REPLACE "android-" "" ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" ) -string( STRIP "${ANDROID_NATIVE_API_LEVEL}" ANDROID_NATIVE_API_LEVEL ) -# adjust API level -set( __real_api_level ${ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME}} ) -foreach( __level ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) - if( (__level LESS ANDROID_NATIVE_API_LEVEL OR __level STREQUAL ANDROID_NATIVE_API_LEVEL) AND NOT __level LESS __real_api_level ) - set( __real_api_level ${__level} ) - endif() -endforeach() -if( __real_api_level AND NOT ANDROID_NATIVE_API_LEVEL STREQUAL __real_api_level ) - message( STATUS "Adjusting Android API level 'android-${ANDROID_NATIVE_API_LEVEL}' to 'android-${__real_api_level}'") - set( ANDROID_NATIVE_API_LEVEL ${__real_api_level} ) -endif() -unset(__real_api_level) -# validate -list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_NATIVE_API_LEVEL}" __levelIdx ) -if( __levelIdx EQUAL -1 ) - message( SEND_ERROR "Specified Android native API level 'android-${ANDROID_NATIVE_API_LEVEL}' is not supported by your NDK/toolchain." ) -else() - if( BUILD_WITH_ANDROID_NDK ) - __DETECT_NATIVE_API_LEVEL( __realApiLevel "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}/usr/include/android/api-level.h" ) - if( NOT __realApiLevel EQUAL ANDROID_NATIVE_API_LEVEL AND NOT __realApiLevel GREATER 9000 ) - message( SEND_ERROR "Specified Android API level (${ANDROID_NATIVE_API_LEVEL}) does not match to the level found (${__realApiLevel}). Probably your copy of NDK is broken." ) - endif() - unset( __realApiLevel ) - endif() - set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE ) - set( CMAKE_ANDROID_API ${ANDROID_NATIVE_API_LEVEL} ) - if( CMAKE_VERSION VERSION_GREATER "2.8" ) - list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS ) - set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) - endif() -endif() -unset( __levelIdx ) - - -# remember target ABI -set( ANDROID_ABI "${ANDROID_ABI}" CACHE STRING "The target ABI for Android. If arm, then armeabi-v7a is recommended for hardware floating point." FORCE ) -if( CMAKE_VERSION VERSION_GREATER "2.8" ) - list( SORT ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME} ) - set_property( CACHE ANDROID_ABI PROPERTY STRINGS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME}} ) -endif() - - -# runtime choice (STL, rtti, exceptions) -if( NOT ANDROID_STL ) - set( ANDROID_STL gnustl_static ) -endif() -set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" ) -set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" ) -mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES ) - -if( BUILD_WITH_ANDROID_NDK ) - if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared)$") - message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". -The possible values are: - none -> Do not configure the runtime. - system -> Use the default minimal system C++ runtime library. - system_re -> Same as system but with rtti and exceptions. - gabi++_static -> Use the GAbi++ runtime as a static library. - gabi++_shared -> Use the GAbi++ runtime as a shared library. - stlport_static -> Use the STLport runtime as a static library. - stlport_shared -> Use the STLport runtime as a shared library. - gnustl_static -> (default) Use the GNU STL as a static library. - gnustl_shared -> Use the GNU STL as a shared library. -" ) - endif() -elseif( BUILD_WITH_STANDALONE_TOOLCHAIN ) - if( NOT "${ANDROID_STL}" MATCHES "^(none|gnustl_static|gnustl_shared)$") - message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". -The possible values are: - none -> Do not configure the runtime. - gnustl_static -> (default) Use the GNU STL as a static library. - gnustl_shared -> Use the GNU STL as a shared library. -" ) - endif() -endif() - -unset( ANDROID_RTTI ) -unset( ANDROID_EXCEPTIONS ) -unset( ANDROID_STL_INCLUDE_DIRS ) -unset( __libstl ) -unset( __libsupcxx ) - -if( NOT _CMAKE_IN_TRY_COMPILE AND ANDROID_NDK_RELEASE STREQUAL "r7b" AND ARMEABI_V7A AND NOT VFPV3 AND ANDROID_STL MATCHES "gnustl" ) - message( WARNING "The GNU STL armeabi-v7a binaries from NDK r7b can crash non-NEON devices. The files provided with NDK r7b were not configured properly, resulting in crashes on Tegra2-based devices and others when trying to use certain floating-point functions (e.g., cosf, sinf, expf). -You are strongly recommended to switch to another NDK release. -" ) -endif() - -if( NOT _CMAKE_IN_TRY_COMPILE AND X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" ) - message( WARNING "The x86 system header file from NDK r6 has incorrect definition for ptrdiff_t. You are recommended to upgrade to a newer NDK release or manually patch the header: -See https://android.googlesource.com/platform/development.git f907f4f9d4e56ccc8093df6fee54454b8bcab6c2 - diff --git a/ndk/platforms/android-9/arch-x86/include/machine/_types.h b/ndk/platforms/android-9/arch-x86/include/machine/_types.h - index 5e28c64..65892a1 100644 - --- a/ndk/platforms/android-9/arch-x86/include/machine/_types.h - +++ b/ndk/platforms/android-9/arch-x86/include/machine/_types.h - @@ -51,7 +51,11 @@ typedef long int ssize_t; - #endif - #ifndef _PTRDIFF_T - #define _PTRDIFF_T - -typedef long ptrdiff_t; - +# ifdef __ANDROID__ - + typedef int ptrdiff_t; - +# else - + typedef long ptrdiff_t; - +# endif - #endif -" ) -endif() - - -# setup paths and STL for standalone toolchain -if( BUILD_WITH_STANDALONE_TOOLCHAIN ) - set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) - set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) - set( ANDROID_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot" ) - - if( NOT ANDROID_STL STREQUAL "none" ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/include/c++/${ANDROID_COMPILER_VERSION}" ) - if( NOT EXISTS "${ANDROID_STL_INCLUDE_DIRS}" ) - # old location ( pre r8c ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}" ) - endif() - if( ARMEABI_V7A AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/bits" ) - list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}" ) - elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb/bits" ) - list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb" ) - else() - list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" ) - endif() - # always search static GNU STL to get the location of libsupc++.a - if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb" ) - elseif( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}" ) - elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libstdc++.a" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb" ) - elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libstdc++.a" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib" ) - endif() - if( __libstl ) - set( __libsupcxx "${__libstl}/libsupc++.a" ) - set( __libstl "${__libstl}/libstdc++.a" ) - endif() - if( NOT EXISTS "${__libsupcxx}" ) - message( FATAL_ERROR "The required libstdsupc++.a is missing in your standalone toolchain. - Usually it happens because of bug in make-standalone-toolchain.sh script from NDK r7, r7b and r7c. - You need to either upgrade to newer NDK or manually copy - $ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a - to - ${__libsupcxx} - " ) - endif() - if( ANDROID_STL STREQUAL "gnustl_shared" ) - if( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) - elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) - elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) - endif() - endif() - endif() -endif() - -# clang -if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" ) - set( ANDROID_COMPILER_IS_CLANG 1 ) - execute_process( COMMAND "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/clang${TOOL_OS_SUFFIX}" --version OUTPUT_VARIABLE ANDROID_CLANG_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) - string( REGEX MATCH "[0-9]+[.][0-9]+" ANDROID_CLANG_VERSION "${ANDROID_CLANG_VERSION}") -elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" ) - string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}") - string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-${ANDROID_COMPILER_VERSION}" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) - if( NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}/bin/clang${TOOL_OS_SUFFIX}" ) - message( FATAL_ERROR "Could not find the Clang compiler driver" ) - endif() - set( ANDROID_COMPILER_IS_CLANG 1 ) - set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) -else() - set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) - unset( ANDROID_COMPILER_IS_CLANG CACHE ) -endif() - -string( REPLACE "." "" _clang_name "clang${ANDROID_CLANG_VERSION}" ) -if( NOT EXISTS "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" ) - set( _clang_name "clang" ) -endif() - - -# setup paths and STL for NDK -if( BUILD_WITH_ANDROID_NDK ) - set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) - set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" ) - - if( ANDROID_STL STREQUAL "none" ) - # do nothing - elseif( ANDROID_STL STREQUAL "system" ) - set( ANDROID_RTTI OFF ) - set( ANDROID_EXCEPTIONS OFF ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) - elseif( ANDROID_STL STREQUAL "system_re" ) - set( ANDROID_RTTI ON ) - set( ANDROID_EXCEPTIONS ON ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) - elseif( ANDROID_STL MATCHES "gabi" ) - if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 - message( FATAL_ERROR "gabi++ is not available in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.") - endif() - set( ANDROID_RTTI ON ) - set( ANDROID_EXCEPTIONS OFF ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/gabi++/include" ) - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gabi++/libs/${ANDROID_NDK_ABI_NAME}/libgabi++_static.a" ) - elseif( ANDROID_STL MATCHES "stlport" ) - if( NOT ANDROID_NDK_RELEASE_NUM LESS 8004 ) # before r8d - set( ANDROID_EXCEPTIONS ON ) - else() - set( ANDROID_EXCEPTIONS OFF ) - endif() - if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 - set( ANDROID_RTTI OFF ) - else() - set( ANDROID_RTTI ON ) - endif() - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/stlport/stlport" ) - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/stlport/libs/${ANDROID_NDK_ABI_NAME}/libstlport_static.a" ) - elseif( ANDROID_STL MATCHES "gnustl" ) - set( ANDROID_EXCEPTIONS ON ) - set( ANDROID_RTTI ON ) - if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) - if( ARMEABI_V7A AND ANDROID_COMPILER_VERSION VERSION_EQUAL "4.7" AND ANDROID_NDK_RELEASE STREQUAL "r8d" ) - # gnustl binary for 4.7 compiler is buggy :( - # TODO: look for right fix - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.6" ) - else() - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) - endif() - else() - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" ) - endif() - set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" "${__libstl}/include/backward" ) - if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) - set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) - else() - set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" ) - endif() - else() - message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" ) - endif() - # find libsupc++.a - rtti & exceptions - if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" ) - set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r8b or newer - if( NOT EXISTS "${__libsupcxx}" ) - set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r7-r8 - endif() - if( NOT EXISTS "${__libsupcxx}" ) # before r7 - if( ARMEABI_V7A ) - if( ANDROID_FORCE_ARM_BUILD ) - set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" ) - else() - set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" ) - endif() - elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD ) - set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" ) - else() - set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" ) - endif() - endif() - if( NOT EXISTS "${__libsupcxx}") - message( ERROR "Could not find libsupc++.a for a chosen platform. Either your NDK is not supported or is broken.") - endif() - endif() -endif() - - -# case of shared STL linkage -if( ANDROID_STL MATCHES "shared" AND DEFINED __libstl ) - string( REPLACE "_static.a" "_shared.so" __libstl "${__libstl}" ) - # TODO: check if .so file exists before the renaming -endif() - - -# ccache support -__INIT_VARIABLE( _ndk_ccache NDK_CCACHE ENV_NDK_CCACHE ) -if( _ndk_ccache ) - if( DEFINED NDK_CCACHE AND NOT EXISTS NDK_CCACHE ) - unset( NDK_CCACHE CACHE ) - endif() - find_program( NDK_CCACHE "${_ndk_ccache}" DOC "The path to ccache binary") -else() - unset( NDK_CCACHE CACHE ) -endif() -unset( _ndk_ccache ) - - -# setup the cross-compiler -if( NOT CMAKE_C_COMPILER ) - if( NDK_CCACHE AND NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) - set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" ) - set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" ) - if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_C_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") - set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") - else() - set( CMAKE_C_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") - set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") - endif() - else() - if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_C_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") - set( CMAKE_CXX_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") - else() - set( CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler" ) - set( CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler" ) - endif() - endif() - set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" ) - set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" ) - if( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" ) - # Use gcc-ar if we have it for better LTO support. - set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) - else() - set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) - endif() - set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" ) - set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" ) - set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" ) - set( CMAKE_OBJDUMP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump" ) - set( CMAKE_RANLIB "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib" ) -endif() - -set( _CMAKE_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_MACHINE_NAME}-" ) -if( CMAKE_VERSION VERSION_LESS 2.8.5 ) - set( CMAKE_ASM_COMPILER_ARG1 "-c" ) -endif() -if( APPLE ) - find_program( CMAKE_INSTALL_NAME_TOOL NAMES install_name_tool ) - if( NOT CMAKE_INSTALL_NAME_TOOL ) - message( FATAL_ERROR "Could not find install_name_tool, please check your installation." ) - endif() - mark_as_advanced( CMAKE_INSTALL_NAME_TOOL ) -endif() - -# Force set compilers because standard identification works badly for us -include( CMakeForceCompiler ) -# CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU ) -if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_C_COMPILER_ID Clang ) -endif() -set( CMAKE_C_PLATFORM_ID Linux ) -if( X86_64 OR MIPS64 OR ARM64_V8A ) - set( CMAKE_C_SIZEOF_DATA_PTR 8 ) -else() - set( CMAKE_C_SIZEOF_DATA_PTR 4 ) -endif() -set( CMAKE_C_HAS_ISYSROOT 1 ) -set( CMAKE_C_COMPILER_ABI ELF ) -# CMAKE_FORCE_CXX_COMPILER( "${CMAKE_CXX_COMPILER}" GNU ) -if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_CXX_COMPILER_ID Clang) -endif() -set( CMAKE_CXX_PLATFORM_ID Linux ) -set( CMAKE_CXX_SIZEOF_DATA_PTR ${CMAKE_C_SIZEOF_DATA_PTR} ) -set( CMAKE_CXX_HAS_ISYSROOT 1 ) -set( CMAKE_CXX_COMPILER_ABI ELF ) -set( CMAKE_CXX_SOURCE_FILE_EXTENSIONS cc cp cxx cpp CPP c++ C ) -# force ASM compiler (required for CMake < 2.8.5) -set( CMAKE_ASM_COMPILER_ID_RUN TRUE ) -set( CMAKE_ASM_COMPILER_ID GNU ) -set( CMAKE_ASM_COMPILER_WORKS TRUE ) -set( CMAKE_ASM_COMPILER_FORCED TRUE ) -set( CMAKE_COMPILER_IS_GNUASM 1) -set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm ) - -foreach( lang C CXX ASM ) - if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_CLANG_VERSION} ) - else() - set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_COMPILER_VERSION} ) - endif() -endforeach() - -# flags and definitions -remove_definitions( -DANDROID ) -add_definitions( -DANDROID ) - -if( ANDROID_SYSROOT MATCHES "[ ;\"]" ) - if( CMAKE_HOST_WIN32 ) - # try to convert path to 8.3 form - file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "@echo %~s1" ) - execute_process( COMMAND "$ENV{ComSpec}" /c "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "${ANDROID_SYSROOT}" - OUTPUT_VARIABLE __path OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE __result ERROR_QUIET ) - if( __result EQUAL 0 ) - file( TO_CMAKE_PATH "${__path}" ANDROID_SYSROOT ) - set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) - else() - set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) - endif() - else() - set( ANDROID_CXX_FLAGS "'--sysroot=${ANDROID_SYSROOT}'" ) - endif() - if( NOT _CMAKE_IN_TRY_COMPILE ) - # quotes can break try_compile and compiler identification - message(WARNING "Path to your Android NDK (or toolchain) has non-alphanumeric symbols.\nThe build might be broken.\n") - endif() -else() - set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) -endif() - -# NDK flags -if (ARM64_V8A ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) - set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) - set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) - endif() -elseif( ARMEABI OR ARMEABI_V7A) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) - if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 ) - set( ANDROID_CXX_FLAGS_RELEASE "-mthumb -fomit-frame-pointer -fno-strict-aliasing" ) - set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -finline-limit=64" ) - endif() - else() - # always compile ARMEABI_V6 in arm mode; otherwise there is no difference from ARMEABI - set( ANDROID_CXX_FLAGS_RELEASE "-marm -fomit-frame-pointer -fstrict-aliasing" ) - set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) - endif() - endif() -elseif( X86 OR X86_64 ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) - endif() - set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) - set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) -elseif( MIPS OR MIPS64 ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-strict-aliasing -finline-functions -funwind-tables -fmessage-length=0" ) - set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer" ) - set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers" ) - set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) - endif() -elseif() - set( ANDROID_CXX_FLAGS_RELEASE "" ) - set( ANDROID_CXX_FLAGS_DEBUG "" ) -endif() - -set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" ) # good/necessary when porting desktop libraries - -if( NOT X86 AND NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "-Wno-psabi ${ANDROID_CXX_FLAGS}" ) -endif() - -if( NOT ANDROID_COMPILER_VERSION VERSION_LESS "4.6" ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -no-canonical-prefixes" ) # see https://android-review.googlesource.com/#/c/47564/ -endif() - -# ABI-specific flags -if( ARMEABI_V7A ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv7-a -mfloat-abi=softfp" ) - if( NEON ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=neon" ) - elseif( VFPV3 ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3" ) - else() - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3-d16" ) - endif() -elseif( ARMEABI_V6 ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv6 -mfloat-abi=softfp -mfpu=vfp" ) # vfp == vfpv2 -elseif( ARMEABI ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" ) -endif() - -if( ANDROID_STL MATCHES "gnustl" AND (EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}") ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) -else() - set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) -endif() - -# STL -if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) - if( EXISTS "${__libstl}" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libstl}\"" ) - endif() - if( EXISTS "${__libsupcxx}" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) - # C objects: - set( CMAKE_C_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_C_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_C_LINK_EXECUTABLE " -o " ) - set( CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) - set( CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) - set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) - endif() - if( ANDROID_STL MATCHES "gnustl" ) - if( NOT EXISTS "${ANDROID_LIBM_PATH}" ) - set( ANDROID_LIBM_PATH -lm ) - endif() - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} ${ANDROID_LIBM_PATH}" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} ${ANDROID_LIBM_PATH}" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} ${ANDROID_LIBM_PATH}" ) - endif() -endif() - -# variables controlling optional build flags -if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 - # libGLESv2.so in NDK's prior to r7 refers to missing external symbols. - # So this flag option is required for all projects using OpenGL from native. - __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES ON ) -else() - __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF ) -endif() -__INIT_VARIABLE( ANDROID_NO_UNDEFINED VALUES ON ) -__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON ) -__INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON ) -__INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON ) -__INIT_VARIABLE( ANDROID_RELRO VALUES ON ) - -set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" ) -set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) -set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Put each function in separate section and enable garbage collection of unused input sections at link time" ) -set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker" ) -set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) -set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" ) -mark_as_advanced( ANDROID_NO_UNDEFINED ANDROID_SO_UNDEFINED ANDROID_FUNCTION_LEVEL_LINKING ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO ) - -# linker flags -set( ANDROID_LINKER_FLAGS "" ) - -if( ARMEABI_V7A ) - # this is *required* to use the following linker flags that routes around - # a CPU bug in some Cortex-A8 implementations: - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--fix-cortex-a8" ) -endif() - -if( ANDROID_NO_UNDEFINED ) - if( MIPS ) - # there is some sysroot-related problem in mips linker... - if( NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined -Wl,-rpath-link,${ANDROID_SYSROOT}/usr/lib" ) - endif() - else() - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) - endif() -endif() - -if( ANDROID_SO_UNDEFINED ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-allow-shlib-undefined" ) -endif() - -if( ANDROID_FUNCTION_LEVEL_LINKING ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fdata-sections -ffunction-sections" ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--gc-sections" ) -endif() - -if( ANDROID_COMPILER_VERSION VERSION_EQUAL "4.6" ) - if( ANDROID_GOLD_LINKER AND (CMAKE_HOST_UNIX OR ANDROID_NDK_RELEASE_NUM GREATER 8002) AND (ARMEABI OR ARMEABI_V7A OR X86) ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=gold" ) - elseif( ANDROID_NDK_RELEASE_NUM GREATER 8002 ) # after r8b - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=bfd" ) - elseif( ANDROID_NDK_RELEASE STREQUAL "r8b" AND ARMEABI AND NOT _CMAKE_IN_TRY_COMPILE ) - message( WARNING "The default bfd linker from arm GCC 4.6 toolchain can fail with 'unresolvable R_ARM_THM_CALL relocation' error message. See https://code.google.com/p/android/issues/detail?id=35342 - On Linux and OS X host platform you can workaround this problem using gold linker (default). - Rerun cmake with -DANDROID_GOLD_LINKER=ON option in case of problems. -" ) - endif() -endif() # version 4.6 - -if( ANDROID_NOEXECSTACK ) - if( ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Xclang -mnoexecstack" ) - else() - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Wa,--noexecstack" ) - endif() - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,noexecstack" ) -endif() - -if( ANDROID_RELRO ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now" ) -endif() - -if( ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "-target ${ANDROID_LLVM_TRIPLE} -Qunused-arguments ${ANDROID_CXX_FLAGS}" ) - if( BUILD_WITH_ANDROID_NDK ) - set( ANDROID_CXX_FLAGS "-gcc-toolchain ${ANDROID_TOOLCHAIN_ROOT} ${ANDROID_CXX_FLAGS}" ) - endif() -endif() - -# cache flags -set( CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags" ) -set( CMAKE_C_FLAGS "" CACHE STRING "c flags" ) -set( CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c++ Release flags" ) -set( CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c Release flags" ) -set( CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c++ Debug flags" ) -set( CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c Debug flags" ) -set( CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id" CACHE STRING "shared linker flags" ) -set( CMAKE_MODULE_LINKER_FLAGS "-Wl,--build-id" CACHE STRING "module linker flags" ) -set( CMAKE_EXE_LINKER_FLAGS "-Wl,--build-id -Wl,-z,nocopyreloc" CACHE STRING "executable linker flags" ) - -# put flags to cache (for debug purpose only) -set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS}" CACHE INTERNAL "Android specific c/c++ flags" ) -set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE}" CACHE INTERNAL "Android specific c/c++ Release flags" ) -set( ANDROID_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG}" CACHE INTERNAL "Android specific c/c++ Debug flags" ) -set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}" CACHE INTERNAL "Android specific c/c++ linker flags" ) - -# finish flags -set( CMAKE_CXX_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_CXX_FLAGS}" ) -set( CMAKE_C_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_C_FLAGS}" ) -set( CMAKE_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_CXX_FLAGS_RELEASE}" ) -set( CMAKE_C_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_C_FLAGS_RELEASE}" ) -set( CMAKE_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_DEBUG}" ) -set( CMAKE_C_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_C_FLAGS_DEBUG}" ) -set( CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" ) -set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" ) -set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" ) - -if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) - set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) - set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) - set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) -endif() - -# pie/pic -if( NOT (ANDROID_NATIVE_API_LEVEL LESS 16) AND (NOT DEFINED ANDROID_APP_PIE OR ANDROID_APP_PIE) AND (CMAKE_VERSION VERSION_GREATER 2.8.8) ) - set( CMAKE_POSITION_INDEPENDENT_CODE TRUE ) - set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie") -else() - set( CMAKE_POSITION_INDEPENDENT_CODE FALSE ) - set( CMAKE_CXX_FLAGS "-fpic ${CMAKE_CXX_FLAGS}" ) - set( CMAKE_C_FLAGS "-fpic ${CMAKE_C_FLAGS}" ) -endif() - -# configure rtti -if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES ) - if( ANDROID_RTTI ) - set( CMAKE_CXX_FLAGS "-frtti ${CMAKE_CXX_FLAGS}" ) - else() - set( CMAKE_CXX_FLAGS "-fno-rtti ${CMAKE_CXX_FLAGS}" ) - endif() -endif() - -# configure exceptios -if( DEFINED ANDROID_EXCEPTIONS AND ANDROID_STL_FORCE_FEATURES ) - if( ANDROID_EXCEPTIONS ) - set( CMAKE_CXX_FLAGS "-fexceptions ${CMAKE_CXX_FLAGS}" ) - set( CMAKE_C_FLAGS "-fexceptions ${CMAKE_C_FLAGS}" ) - else() - set( CMAKE_CXX_FLAGS "-fno-exceptions ${CMAKE_CXX_FLAGS}" ) - set( CMAKE_C_FLAGS "-fno-exceptions ${CMAKE_C_FLAGS}" ) - endif() -endif() - -# global includes and link directories -include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} ) -get_filename_component(__android_install_path "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" ABSOLUTE) # avoid CMP0015 policy warning -link_directories( "${__android_install_path}" ) - -# detect if need link crtbegin_so.o explicitly -if( NOT DEFINED ANDROID_EXPLICIT_CRT_LINK ) - set( __cmd "${CMAKE_CXX_CREATE_SHARED_LIBRARY}" ) - string( REPLACE "" "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_CXX_FLAGS}" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_SHARED_LINKER_FLAGS}" __cmd "${__cmd}" ) - string( REPLACE "" "-shared" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain_crtlink_test.so" __cmd "${__cmd}" ) - string( REPLACE "" "\"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - separate_arguments( __cmd ) - foreach( __var ANDROID_NDK ANDROID_NDK_TOOLCHAINS_PATH ANDROID_STANDALONE_TOOLCHAIN ) - if( ${__var} ) - set( __tmp "${${__var}}" ) - separate_arguments( __tmp ) - string( REPLACE "${__tmp}" "${${__var}}" __cmd "${__cmd}") - endif() - endforeach() - string( REPLACE "'" "" __cmd "${__cmd}" ) - string( REPLACE "\"" "" __cmd "${__cmd}" ) - execute_process( COMMAND ${__cmd} RESULT_VARIABLE __cmd_result OUTPUT_QUIET ERROR_QUIET ) - if( __cmd_result EQUAL 0 ) - set( ANDROID_EXPLICIT_CRT_LINK ON ) - else() - set( ANDROID_EXPLICIT_CRT_LINK OFF ) - endif() -endif() - -if( ANDROID_EXPLICIT_CRT_LINK ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) -endif() - -# setup output directories -set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) - -if( DEFINED LIBRARY_OUTPUT_PATH_ROOT - OR EXISTS "${CMAKE_SOURCE_DIR}/AndroidManifest.xml" - OR (EXISTS "${CMAKE_SOURCE_DIR}/../AndroidManifest.xml" AND EXISTS "${CMAKE_SOURCE_DIR}/../jni/") ) - set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "Root for binaries output, set this to change where Android libs are installed to" ) - if( NOT _CMAKE_IN_TRY_COMPILE ) - if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" ) - set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" ) - else() - set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" ) - endif() - set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for Android libs" ) - endif() -endif() - -# copy shaed stl library to build directory -if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" AND DEFINED LIBRARY_OUTPUT_PATH ) - get_filename_component( __libstlname "${__libstl}" NAME ) - execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess ) - if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}") - message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" ) - endif() - unset( __fileCopyProcess ) - unset( __libstlname ) -endif() - - -# set these global flags for cmake client scripts to change behavior -set( ANDROID True ) -set( BUILD_ANDROID True ) - -# where is the target environment -set( CMAKE_FIND_ROOT_PATH "${ANDROID_TOOLCHAIN_ROOT}/bin" "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" "${ANDROID_SYSROOT}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_PREFIX}/share" ) - -# only search for libraries and includes in the ndk toolchain -set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) -set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) -set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) - - -# macro to find packages on the host OS -macro( find_host_package ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) - if( CMAKE_HOST_WIN32 ) - SET( WIN32 1 ) - SET( UNIX ) - elseif( CMAKE_HOST_APPLE ) - SET( APPLE 1 ) - SET( UNIX ) - endif() - find_package( ${ARGN} ) - SET( WIN32 ) - SET( APPLE ) - SET( UNIX 1 ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) -endmacro() - - -# macro to find programs on the host OS -macro( find_host_program ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) - if( CMAKE_HOST_WIN32 ) - SET( WIN32 1 ) - SET( UNIX ) - elseif( CMAKE_HOST_APPLE ) - SET( APPLE 1 ) - SET( UNIX ) - endif() - find_program( ${ARGN} ) - SET( WIN32 ) - SET( APPLE ) - SET( UNIX 1 ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) -endmacro() - - -# export toolchain settings for the try_compile() command -if( NOT _CMAKE_IN_TRY_COMPILE ) - set( __toolchain_config "") - foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN - ANDROID_NDK_HOST_X64 - ANDROID_NDK - ANDROID_NDK_LAYOUT - ANDROID_STANDALONE_TOOLCHAIN - ANDROID_TOOLCHAIN_NAME - ANDROID_ABI - ANDROID_NATIVE_API_LEVEL - ANDROID_STL - ANDROID_STL_FORCE_FEATURES - ANDROID_FORCE_ARM_BUILD - ANDROID_NO_UNDEFINED - ANDROID_SO_UNDEFINED - ANDROID_FUNCTION_LEVEL_LINKING - ANDROID_GOLD_LINKER - ANDROID_NOEXECSTACK - ANDROID_RELRO - ANDROID_LIBM_PATH - ANDROID_EXPLICIT_CRT_LINK - ANDROID_APP_PIE - ) - if( DEFINED ${__var} ) - if( ${__var} MATCHES " ") - set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" ) - else() - set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" ) - endif() - endif() - endforeach() - file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android.toolchain.config.cmake" "${__toolchain_config}" ) - unset( __toolchain_config ) -endif() - - -# force cmake to produce / instead of \ in build commands for Ninja generator -if( CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_HOST_WIN32 ) - # it is a bad hack after all - # CMake generates Ninja makefiles with UNIX paths only if it thinks that we are going to build with MinGW - set( CMAKE_COMPILER_IS_MINGW TRUE ) # tell CMake that we are MinGW - set( CMAKE_CROSSCOMPILING TRUE ) # stop recursion - enable_language( C ) - enable_language( CXX ) - # unset( CMAKE_COMPILER_IS_MINGW ) # can't unset because CMake does not convert back-slashes in response files without it - unset( MINGW ) -endif() - -# Variables need by cmAndroidGradleBuild to generate android_gradle_build.json -set(CMAKE_ANDROID_ARCH_ABI ${ANDROID_ABI}) - - -# Variables controlling behavior or set by cmake toolchain: -# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips", "arm64-v8a", "x86_64", "mips64" -# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14,15,16,17,18,19,21 (depends on NDK version) -# ANDROID_STL : gnustl_static/gnustl_shared/stlport_static/stlport_shared/gabi++_static/gabi++_shared/system_re/system/none -# ANDROID_FORBID_SYGWIN : ON/OFF -# ANDROID_NO_UNDEFINED : ON/OFF -# ANDROID_SO_UNDEFINED : OFF/ON (default depends on NDK version) -# ANDROID_FUNCTION_LEVEL_LINKING : ON/OFF -# ANDROID_GOLD_LINKER : ON/OFF -# ANDROID_NOEXECSTACK : ON/OFF -# ANDROID_RELRO : ON/OFF -# ANDROID_FORCE_ARM_BUILD : ON/OFF -# ANDROID_STL_FORCE_FEATURES : ON/OFF -# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` -# Can be set only at the first run: -# ANDROID_NDK : path to your NDK install -# NDK_CCACHE : path to your ccache executable -# ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain -# ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) -# ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID) -# LIBRARY_OUTPUT_PATH_ROOT : -# ANDROID_STANDALONE_TOOLCHAIN -# -# Primary read-only variables: -# ANDROID : always TRUE -# ARMEABI : TRUE for arm v6 and older devices -# ARMEABI_V6 : TRUE for arm v6 -# ARMEABI_V7A : TRUE for arm v7a -# ARM64_V8A : TRUE for arm64-v8a -# NEON : TRUE if NEON unit is enabled -# VFPV3 : TRUE if VFP version 3 is enabled -# X86 : TRUE if configured for x86 -# X86_64 : TRUE if configured for x86_64 -# MIPS : TRUE if configured for mips -# MIPS64 : TRUE if configured for mips64 -# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used -# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used -# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform -# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86", "mips", "arm64-v8a", "x86_64", "mips64" depending on ANDROID_ABI -# ANDROID_NDK_RELEASE : from r5 to r10d; set only for NDK -# ANDROID_NDK_RELEASE_NUM : numeric ANDROID_NDK_RELEASE version (1000*major+minor) -# ANDROID_ARCH_NAME : "arm", "x86", "mips", "arm64", "x86_64", "mips64" depending on ANDROID_ABI -# ANDROID_SYSROOT : path to the compiler sysroot -# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform -# ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used -# -# Secondary (less stable) read-only variables: -# ANDROID_COMPILER_VERSION : GCC version used (not Clang version) -# ANDROID_CLANG_VERSION : version of clang compiler if clang is used -# ANDROID_CXX_FLAGS : C/C++ compiler flags required by Android platform -# ANDROID_SUPPORTED_ABIS : list of currently allowed values for ANDROID_ABI -# ANDROID_TOOLCHAIN_MACHINE_NAME : "arm-linux-androideabi", "arm-eabi" or "i686-android-linux" -# ANDROID_TOOLCHAIN_ROOT : path to the top level of toolchain (standalone or placed inside NDK) -# ANDROID_CLANG_TOOLCHAIN_ROOT : path to clang tools -# ANDROID_SUPPORTED_NATIVE_API_LEVELS : list of native API levels found inside NDK -# ANDROID_STL_INCLUDE_DIRS : stl include paths -# ANDROID_RTTI : if rtti is enabled by the runtime -# ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime -# ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used -# -# Defaults: -# ANDROID_DEFAULT_NDK_API_LEVEL -# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH} -# ANDROID_NDK_SEARCH_PATHS -# ANDROID_SUPPORTED_ABIS_${ARCH} -# ANDROID_SUPPORTED_NDK_VERSIONS diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 06ef7cca18..40da63f010 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -157,8 +157,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple static JavaClass java_sync_session_class(env, "io/realm/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", "(ILjava/lang/Long;Ljava/lang/String;)V"); - JavaGlobalRef java_session_object_ref(env, session_object); - session->wait_for_download_completion([java_session_object_ref, callback_id](std::error_code error) { + auto obj = env->NewGlobalRef(session_object); + session->wait_for_download_completion([obj, callback_id](std::error_code error) { JNIEnv* env = JniUtils::get_env(true); JavaLocalRef java_error_code; JavaLocalRef java_error_message; @@ -167,8 +167,9 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); } - env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, + env->CallVoidMethod(obj, java_notify_result_method, callback_id, java_error_code.get(), java_error_message.get()); + env->DeleteGlobalRef(obj); }); return to_jbool(JNI_TRUE); } @@ -190,9 +191,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompleti static JavaClass java_sync_session_class(env, "io/realm/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", "(ILjava/lang/Long;Ljava/lang/String;)V"); - JavaGlobalRef java_session_object_ref(env, session_object); - - session->wait_for_upload_completion([java_session_object_ref, callback_id](std::error_code error) { + auto obj = env->NewGlobalRef(session_object); + session->wait_for_upload_completion([obj, callback_id] (std::error_code error) { JNIEnv* env = JniUtils::get_env(true); JavaLocalRef java_error_code; JavaLocalRef java_error_message; @@ -200,8 +200,9 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompleti java_error_code = JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); } - env->CallVoidMethod(java_session_object_ref.get(), java_notify_result_method, + env->CallVoidMethod(obj, java_notify_result_method, callback_id, java_error_code.get(), java_error_message.get()); + env->DeleteGlobalRef(obj); }); return JNI_TRUE; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 2866bb4023..47988fc0ee 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -180,8 +180,7 @@ static inline Obj do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm } else { if (bool(table->find_first_int(col_key, pk_value))) { - THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, - format(PK_EXCEPTION_MSG_FORMAT, reinterpret_cast(pk_value))); + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, pk_value)); } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 48678d7e65..a868a9ac83 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -291,7 +291,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe case ECONNABORTED: error_code = 113; break; default: /* Do nothing */ - error_code = error_code; + (void)0; } } else if (std::strcmp(error_category, "realm.util.misc_ext") == 0) { switch (util::MiscExtErrors(error_code)) { @@ -300,7 +300,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe case util::MiscExtErrors::delim_not_found: error_code = 3; break; default: /* Do nothing */ - error_code = error_code; + (void)0; } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index a8cdcc95fc..1568b789c1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -536,7 +536,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeFreeze(JNIEnv { try { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - return reinterpret_cast(new SharedRealm(std::move(shared_realm->freeze()))); + return reinterpret_cast(new SharedRealm(shared_realm->freeze())); } CATCH_STD() return reinterpret_cast(nullptr); diff --git a/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp b/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp index 53bd485407..92904d6fe5 100644 --- a/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp +++ b/realm/realm-library/src/main/cpp/jni_impl/android_logger.hpp @@ -24,7 +24,7 @@ namespace realm { namespace jni_impl { // Default logger implementation for Android. -class AndroidLogger : public realm::jni_util::JniLogger { +class AndroidLogger final: public realm::jni_util::JniLogger { public: static std::shared_ptr shared(); diff --git a/realm/realm-library/src/main/cpp/jni_util/log.cpp b/realm/realm-library/src/main/cpp/jni_util/log.cpp index 14026e3ccc..a3492f883c 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.cpp @@ -62,6 +62,10 @@ JniLogger::JniLogger() { } +JniLogger::~JniLogger() +{ +} + JniLogger::JniLogger(bool is_java_logger) : m_is_java_logger(is_java_logger) { diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp index ec4c593765..3b694571d6 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -155,6 +155,7 @@ class Log { class JniLogger { protected: JniLogger(); + virtual ~JniLogger(); // Used by JavaLogger. JniLogger(bool is_java_logger); // Indicate if this is a wrapper for Java RealmLogger class. See JavaLogger diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 82b338f500..66199adbff 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 82b338f50089890455a2087c2a0c77a634646e14 +Subproject commit 66199adbfffbe153e696309a53d4ec03e32c44e3 diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java b/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java index 880d28439e..c2041e248c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java +++ b/realm/realm-library/src/main/java/io/realm/internal/sync/PermissionHelper.java @@ -18,6 +18,7 @@ import io.realm.Realm; import io.realm.RealmList; import io.realm.RealmObject; +import io.realm.internal.Util; import io.realm.internal.annotations.ObjectServer; import io.realm.sync.permissions.Permission; import io.realm.sync.permissions.Role; @@ -38,6 +39,9 @@ public class PermissionHelper { * @return */ public static Permission findOrCreatePermissionForRole(RealmObject container, RealmList permissions, String roleName) { + if (Util.isEmptyString(roleName)) { + throw new IllegalArgumentException("Non-empty 'roleName' required."); + } if (!container.isManaged()) { throw new IllegalStateException("'findOrCreate()' can only be called on managed objects."); } @@ -49,7 +53,6 @@ public static Permission findOrCreatePermissionForRole(RealmObject container, Re // Find existing permission object or create new one Permission permission = permissions.where().equalTo("role.name", roleName).findFirst(); if (permission == null) { - // Find existing role or create new one Role role = realm.where(Role.class).equalTo("name", roleName).findFirst(); if (role == null) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index 228a5f08e1..82da398c07 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -18,6 +18,7 @@ import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,16 +42,17 @@ import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.TestHelper; +import io.realm.TestSyncConfigurationFactory; import io.realm.entities.AllTypes; import io.realm.log.RealmLog; import io.realm.objectserver.utils.Constants; import io.realm.objectserver.utils.UserFactory; -import io.realm.TestSyncConfigurationFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +@Ignore("FIXME: Most of these are currently broken. See https://jira.mongodb.org/browse/RSYNC-101") @RunWith(AndroidJUnit4.class) public class ProgressListenerTests extends StandardIntegrationTest { diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 8bb97d2e84..2c3fdeedd9 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -73,7 +73,7 @@ public class TestHelper { public static final int VERY_SHORT_WAIT_SECS = 1; public static final int SHORT_WAIT_SECS = 10; - public static final int STANDARD_WAIT_SECS = 100; + public static final int STANDARD_WAIT_SECS = 200; private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final Random RANDOM = new Random(); diff --git a/tools/analyze_realm_metrics.sh b/tools/analyze_realm_metrics.sh new file mode 100755 index 0000000000..8f09a22ba0 --- /dev/null +++ b/tools/analyze_realm_metrics.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# This script will print metrics for the Realm library being deployed to end users. +# To run it: +# 1. Make sure that $D8 is defined in your environment, e.g. `D8="$ANDROID_SDK_ROOT/build-tools/29.0.2/d8"` +# 2. Make sure that you have built the library artifacts using `./gradlew assembleRelease` from the realm folder. +# 3. Run the script: `> sh ./analyze_realm_metrics.sh` +# +# Note: This script has only been tested on MacOS + +HERE=`pwd` + +cd "$(dirname $0)/.." + +cd realm/realm-library/build/outputs/aar + +# Base variant +echo "Analyzing Base..." +stat -f"AAR size: %z" realm-android-library-base-release.aar +rm -rf unzippedBase +unzip -qq realm-android-library-base-release.aar -d unzippedBase +sh "$D8" --release --output ./unzippedBase unzippedBase/classes.jar > /dev/null 2>&1 +cat ./unzippedBase/classes.dex | head -c 92 | tail -c 4 | hexdump -e '1/4 "Method count: %d\n"' +find ./unzippedBase -name '*.so' -exec stat -f"%z %N" {} \; + +# ObjectServer variant +echo "\nAnalyzing ObjectServer..." +stat -f"AAR size: %z" realm-android-library-objectServer-release.aar +rm -rf unzippedObjectServer +unzip -qq realm-android-library-objectServer-release.aar -d unzippedObjectServer +sh "$D8" --release --output ./unzippedObjectServer unzippedObjectServer/classes.jar > /dev/null 2>&1 +cat ./unzippedObjectServer/classes.dex | head -c 92 | tail -c 4 | hexdump -e '1/4 "Method count: %d\n"' +find ./unzippedObjectServer -name '*.so' -exec stat -f"%z %N" {} \; + +cd $HERE From 5da790f60d0c44498a3d5beba3ffd9151b41f0a8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 27 Feb 2020 12:44:08 +0100 Subject: [PATCH 1471/2110] Remove Query-based Sync API's (#6758) --- CHANGELOG.md | 1 + .../OrderedCollectionChangeSetTests.java | 2 - .../java/io/realm/SessionTests.java | 3 +- .../java/io/realm/SyncConfigurationTests.java | 29 +- .../java/io/realm/SyncedRealmQueryTests.java | 328 --------------- .../java/io/realm/SyncedRealmTests.java | 197 --------- .../realm-library/src/main/cpp/CMakeLists.txt | 3 +- .../src/main/cpp/io_realm_RealmQuery.cpp | 18 - .../cpp/io_realm_internal_OsRealmConfig.cpp | 3 +- .../cpp/io_realm_internal_OsSharedRealm.cpp | 7 - .../src/main/cpp/io_realm_internal_Table.cpp | 8 +- ...realm_internal_core_DescriptorOrdering.cpp | 12 - .../io_realm_internal_sync_OsSubscription.cpp | 116 ------ .../src/main/java/io/realm/BaseRealm.java | 22 +- .../src/main/java/io/realm/DynamicRealm.java | 8 +- .../io/realm/OrderedCollectionChangeSet.java | 43 -- .../src/main/java/io/realm/Realm.java | 141 +------ .../src/main/java/io/realm/RealmCache.java | 107 +---- .../src/main/java/io/realm/RealmQuery.java | 334 +-------------- .../io/realm/internal/EmptyLoadChangeSet.java | 32 +- .../io/realm/internal/ObjectServerFacade.java | 19 - .../realm/internal/OsCollectionChangeSet.java | 31 -- .../java/io/realm/internal/OsRealmConfig.java | 12 +- .../java/io/realm/internal/OsResults.java | 4 +- .../java/io/realm/internal/OsSharedRealm.java | 12 +- .../io/realm/internal/RealmObjectProxy.java | 2 + .../internal/StatefulCollectionChangeSet.java | 8 - .../internal/SubscriptionAwareOsResults.java | 119 ------ .../main/java/io/realm/internal/Table.java | 8 +- .../internal/core/DescriptorOrdering.java | 10 - .../io/realm/internal/sync/BaseModule.java | 25 -- .../realm/internal/sync/OsSubscription.java | 133 ------ .../internal/sync/SubscriptionAction.java | 61 --- .../permissions/ObjectPermissionsModule.java | 13 - .../main/java/io/realm/sync/Subscription.java | 381 ------------------ .../java/io/realm/ClientResyncMode.java | 7 - .../java/io/realm/SyncConfiguration.java | 62 +-- .../objectServer/java/io/realm/SyncUser.java | 5 +- .../internal/SyncObjectServerFacade.java | 95 +---- .../java/io/realm/BaseIntegrationTest.java | 3 +- .../java/io/realm/SSLConfigurationTests.java | 8 - .../java/io/realm/SyncSessionTests.java | 12 - .../io/realm/SyncedRealmIntegrationTests.java | 46 +-- .../EncryptedSynchronizedRealmTests.java | 4 - .../objectserver/ProcessCommitTests.java | 4 - .../objectserver/ProgressListenerTests.java | 8 +- .../objectserver/QueryBasedSyncTests.java | 354 ---------------- .../realm/TestSyncConfigurationFactory.java | 2 - .../realm/objectserver/utils/UserFactory.java | 1 - .../rule/TestRealmConfigurationFactory.java | 4 - 50 files changed, 60 insertions(+), 2807 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java delete mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/BaseModule.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java delete mode 100644 realm/realm-library/src/main/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java delete mode 100644 realm/realm-library/src/main/java/io/realm/sync/Subscription.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ac03c709..76d76582d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Breaking Changes * Removed all references and API's releated to permissions. These are now managed through MongoDB Realm. Read more [here](XXX). +* Removed Query Based Sync API's and Subscriptions. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. `SyncConfiguration.partionKey()` has been added as a replacement. Read more [here](XXX). ### Enhancements * None. diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java index 267860f80a..306de32caa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedCollectionChangeSetTests.java @@ -472,7 +472,6 @@ public void initialChangeSet_findAllAsync() { results.addChangeListener((collection, changeSet) -> { assertSame(collection, results); assertEquals(10, collection.size()); - assertTrue(changeSet.isCompleteResult()); assertEquals(OrderedCollectionChangeSet.State.INITIAL, changeSet.getState()); assertEquals(0, changeSet.getInsertions().length); assertEquals(0, changeSet.getChanges().length); @@ -497,7 +496,6 @@ public void initialChangeSet_findAll() { results.addChangeListener((collection, changeSet) -> { assertSame(collection, results); assertEquals(11, collection.size()); - assertTrue(changeSet.isCompleteResult()); assertEquals(OrderedCollectionChangeSet.State.UPDATE, changeSet.getState()); assertEquals(1, changeSet.getInsertions().length); looperThread.testComplete(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index b1a608aee6..a47e58d0e8 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -36,7 +36,6 @@ import io.realm.objectserver.utils.StringOnlyModule; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; import static io.realm.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; @@ -66,7 +65,7 @@ public class SessionTests { @Before public void setUp() { user = createTestUser(); - configuration = user.createConfiguration(REALM_URI).addModule(new ObjectPermissionsModule()).build(); + configuration = user.createConfiguration(REALM_URI).build(); } @Test diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 4c3fccc2c8..20f40ff0ac 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -477,13 +477,6 @@ public void getDefaultConfiguration_throwsIfNotLoggedIn() { } } - @Test - public void getDefaultConfiguration_isFullySynchronized() { - SyncUser user = createTestUser(); - SyncConfiguration config = user.getDefaultConfiguration(); - assertFalse(config.isFullySynchronizedRealm()); - } - @Test public void automatic_convertsAuthUrl() { Object[][] input = { @@ -521,15 +514,9 @@ public void clientResyncMode() { String url = "realm://objectserver.realm.io/default"; // Default mode for full Realms - SyncConfiguration config = user.createConfiguration(url) - .fullSynchronization() - .build(); + SyncConfiguration config = user.createConfiguration(url).build(); assertEquals(ClientResyncMode.RECOVER_LOCAL_REALM, config.getClientResyncMode()); - // Default mode for query-based Realms - config = user.createConfiguration(url).build(); - assertEquals(ClientResyncMode.MANUAL, config.getClientResyncMode()); - // Manually set the mode config = user.createConfiguration(url) .clientResyncMode(ClientResyncMode.MANUAL) @@ -549,18 +536,4 @@ public void clientResyncMode_throwsOnNull() { } catch (IllegalArgumentException ignore) { } } - - @Test - public void clientResyncMode_throwsIfNotManualForQueryBasedRealms() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration.Builder config = user.createConfiguration(url) - .clientResyncMode(ClientResyncMode.RECOVER_LOCAL_REALM); - try { - //noinspection ConstantConditions - config.build(); - fail(); - } catch (IllegalStateException ignore) { - } - } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java deleted file mode 100644 index 1037ff8200..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmQueryTests.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm; - -import android.os.SystemClock; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -import java.util.Date; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import io.realm.entities.AllJavaTypes; -import io.realm.entities.AllTypes; -import io.realm.entities.Dog; -import io.realm.rule.RunInLooperThread; -import io.realm.sync.Subscription; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/** - * Testing sync specific methods on {@link RealmQuery}. - */ -@RunWith(AndroidJUnit4.class) -public class SyncedRealmQueryTests { - - @Rule - public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - - @Rule - public final RunInLooperThread looperThread = new RunInLooperThread(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - private Realm realm; - private DynamicRealm dynamicRealm; - - @After - public void tearDown() { - if (realm != null && !realm.isClosed()) { - realm.close(); - } - if (dynamicRealm != null && !dynamicRealm.isClosed()) { - dynamicRealm.close(); - } - for (SyncUser user : SyncUser.all().values()) { - user.logOut(); - } - } - - private String randomName() { - return UUID.randomUUID().toString(); - } - - private Realm getPartialRealm() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/partialSync") - .build(); - realm = Realm.getInstance(config); - return realm; - } - - private Realm getFullySyncRealm() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/fullSync") - .fullSynchronization() - .build(); - realm = Realm.getInstance(config); - return realm; - } - - @Test - public void subscribe() { - realm = getPartialRealm(); - realm.beginTransaction(); - RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); - Date now = new Date(); - SystemClock.sleep(2); - Subscription sub = query.subscribe(); - assertTrue(sub.getName().startsWith("[AllTypes] ")); - assertEquals(Subscription.State.PENDING, sub.getState()); - assertEquals("", sub.getErrorMessage()); - assertEquals(query.getDescription(), sub.getQueryDescription()); - assertEquals("AllTypes", sub.getQueryClassName()); - assertTrue(now.getTime() < sub.getCreatedAt().getTime()); - assertTrue(now.getTime() < sub.getUpdatedAt().getTime()); - assertTrue(sub.getCreatedAt().getTime() == sub.getUpdatedAt().getTime()); - assertEquals(Long.MAX_VALUE, sub.getTimeToLive()); - assertEquals(new Date(Long.MAX_VALUE), sub.getExpiresAt()); - } - - @Test - public void subscribe_withName() { - String name = randomName(); - realm = getPartialRealm(); - realm.beginTransaction(); - RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); - Subscription sub = query.subscribe(name); - assertEquals(name, sub.getName()); - assertEquals(Subscription.State.PENDING, sub.getState()); - assertEquals("", sub.getErrorMessage()); - assertEquals(query.getDescription(), sub.getQueryDescription()); - assertEquals("AllTypes", sub.getQueryClassName()); - } - - @Test - public void subscribe_withTimeToLive() { - realm = getPartialRealm(); - realm.beginTransaction(); - RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); - Date now = new Date(); - SystemClock.sleep(2); - Subscription sub = query.subscribe(randomName(), 0, TimeUnit.MILLISECONDS); - assertTrue(now.getTime() < sub.getCreatedAt().getTime()); - assertEquals(sub.getCreatedAt(), sub.getUpdatedAt()); - assertEquals(sub.getUpdatedAt(), sub.getExpiresAt()); - assertEquals(0, sub.getTimeToLive()); - } - - @Test - public void subscribeOrUpdate() { - String name = randomName(); - realm = getPartialRealm(); - realm.beginTransaction(); - RealmQuery query1 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); - Subscription sub1 = query1.subscribe(name); - Date firstUpdate = sub1.getUpdatedAt(); - RealmQuery query2 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_BOOLEAN, false); - SystemClock.sleep(2); - Subscription sub2 = query2.subscribeOrUpdate(name); - assertEquals(sub1, sub2); - assertEquals(query2.getDescription(), sub2.getQueryDescription()); - assertTrue(firstUpdate.getTime() < sub2.getUpdatedAt().getTime()); - } - - @Test - public void subscribeOrUpdate_failsWithDifferentQueryType() { - String name = randomName(); - realm = getPartialRealm(); - realm.beginTransaction(); - realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo").subscribe(name); - try { - realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_BOOLEAN, false).subscribeOrUpdate(name); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - - @Test - public void subscribeOrUpdate_withTimeToLive() { - String name = randomName(); - realm = getPartialRealm(); - realm.beginTransaction(); - realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo").subscribe(name, 10, TimeUnit.MILLISECONDS); - RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_BOOLEAN, false); - Subscription sub = query.subscribeOrUpdate(name, 20, TimeUnit.DAYS); - assertEquals(TimeUnit.MILLISECONDS.convert(20, TimeUnit.DAYS), sub.getTimeToLive()); - assertEquals(query.getDescription(), sub.getQueryDescription()); - } - - @Test - public void subscribe_throwIfNameIsAlreadyUsed() { - realm = getPartialRealm(); - realm.beginTransaction(); - realm.where(Dog.class).subscribe("foo"); - try { - realm.where(AllTypes.class).subscribe("foo"); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - - @Test - public void subscribe_throwOnDynamicRealm() { - getPartialRealm().close(); // Build schema - dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); - dynamicRealm.beginTransaction(); - RealmQuery query = dynamicRealm.where(AllTypes.CLASS_NAME); - try { - query.subscribe("sub"); - fail(); - } catch (IllegalStateException ignore) { - } - } - - @Test - public void subscribe_throwIfOutsideWriteTransaction() { - realm = getPartialRealm(); - RealmQuery query = realm.where(AllTypes.class); - try { - query.subscribe("sub"); - fail(); - } catch (IllegalStateException ignore) { - } - } - - @Test - public void subscribe_throwIfBasedOnList() { - realm = getPartialRealm(); - realm.beginTransaction(); - realm.createObject(AllTypes.class).getColumnRealmList().add(new Dog("fido")); - RealmQuery query = realm.where(AllTypes.class).findFirst().getColumnRealmList().where(); - try { - query.subscribe("sub"); - fail(); - } catch (IllegalStateException ignore) { - } - } - - @Test - public void subscribe_throwIfNonPartialRealm() { - realm = getFullySyncRealm(); - realm.beginTransaction(); - RealmQuery query = realm.where(AllTypes.class); - try { - query.subscribe("sub"); - fail(); - } catch (IllegalStateException ignore) { - } - } - - @Test - public void subscribe_throwIfRealmClosed() { - realm = getPartialRealm(); - realm.beginTransaction(); - RealmQuery query = realm.where(AllTypes.class); - realm.close(); - try { - query.subscribe("sub"); - fail(); - } catch (IllegalStateException ignore) { - } - } - - @Test - public void subscription_setQuery() { - realm = getPartialRealm(); - realm.beginTransaction(); - RealmQuery query1 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); - Date now = new Date(); - SystemClock.sleep(2); - Subscription sub = query1.subscribe("sub3"); - RealmQuery query2 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_BOOLEAN, false); - assertEquals("AllTypes", sub.getQueryClassName()); - assertTrue(now.getTime() < sub.getUpdatedAt().getTime()); - Date query1Updated = sub.getUpdatedAt(); - SystemClock.sleep(2); - sub.setQuery(query2); - assertEquals(query2.getDescription(), sub.getQueryDescription()); - assertEquals("AllTypes", sub.getQueryClassName()); - assertTrue(query1Updated.getTime() < sub.getUpdatedAt().getTime()); - } - - @Test - public void subscription_setQuery_wrongTypeThrows() { - realm = getPartialRealm(); - realm.beginTransaction(); - RealmQuery query1 = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo"); - Subscription sub = query1.subscribe("sub4"); - RealmQuery query2 = realm.where(AllJavaTypes.class).equalTo(AllJavaTypes.FIELD_BOOLEAN, false); - try { - sub.setQuery(query2); - fail(); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains("It is only allowed to replace a query")); - } - } - - @Test - public void subscription_setTimeToLive() { - realm = getPartialRealm(); - realm.beginTransaction(); - - Subscription sub = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo").subscribe(); - assertEquals(Long.MAX_VALUE, sub.getExpiresAt().getTime()); - assertEquals(Long.MAX_VALUE, sub.getTimeToLive()); - - Date now = new Date(); - Date now_plus_1_sec = new Date(now.getTime() + 1000); - Date now_plus_11_sec = new Date(now.getTime() + 11000); - SystemClock.sleep(2); - sub.setTimeToLive(10, TimeUnit.SECONDS); - assertEquals(10000, sub.getTimeToLive()); - assertTrue(now.getTime() < sub.getUpdatedAt().getTime()); - assertTrue(now.getTime() < sub.getExpiresAt().getTime()); - assertTrue(sub.getUpdatedAt().getTime() < now_plus_1_sec.getTime()); - assertTrue(sub.getExpiresAt().getTime() < now_plus_11_sec.getTime()); - } - - @Test - public void subscription_setTimeToLive_illegalValuesThrows() { - realm = getPartialRealm(); - realm.beginTransaction(); - Subscription sub = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "foo").subscribe(); - try { - sub.setTimeToLive(-1, TimeUnit.SECONDS); - fail(); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains("A negative time-to-live is not allowed")); - } - try { - sub.setTimeToLive(0, null); - fail(); - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains("Non-null 'timeUnit' required")); - } - } - -} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index 9fd2ab5da1..fb1795eb38 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -30,16 +30,11 @@ import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; -import io.realm.objectserver.model.PartialSyncObjectA; import io.realm.objectserver.utils.Constants; import io.realm.rule.RunInLooperThread; -import io.realm.rule.RunTestInLooperThread; -import io.realm.sync.Subscription; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -76,16 +71,8 @@ private Realm getNormalRealm() { return realm; } - private Realm getPartialRealm() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/fullsync") - .build(); - realm = Realm.getInstance(config); - return realm; - } - private Realm getFullySyncRealm() { SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/fullsync") - .fullSynchronization() .build(); realm = Realm.getInstance(config); return realm; @@ -122,127 +109,6 @@ public void testUpgradingOptionalSubscriptionFields() throws IOException { } } - @Test - public void unsubscribeAsync_nullOrEmptyArgumentsThrows() { - Realm realm = getPartialRealm(); - Realm.UnsubscribeCallback callback = new Realm.UnsubscribeCallback() { - @Override - public void onSuccess(String subscriptionName) { - } - - @Override - public void onError(String subscriptionName, Throwable error) { - } - }; - - try { - //noinspection ConstantConditions - realm.unsubscribeAsync(null, callback); - fail(); - } catch (IllegalArgumentException ignore) { - } - - try { - realm.unsubscribeAsync("", callback); - fail(); - } catch (IllegalArgumentException ignore) { - } - - try { - //noinspection ConstantConditions - realm.unsubscribeAsync("my-id", null); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - - @Test - public void unsubscribeAsync_nonLooperThreadThrows() { - Realm realm = getPartialRealm(); - Realm.UnsubscribeCallback callback = new Realm.UnsubscribeCallback() { - @Override - public void onSuccess(String subscriptionName) { - } - - @Override - public void onError(String subscriptionName, Throwable error) { - } - }; - - try { - //noinspection ConstantConditions - realm.unsubscribeAsync("my-id", callback); - fail(); - } catch (IllegalStateException ignore) { - } - } - - @Test - @RunTestInLooperThread - public void unsubscribeAsync_nonPartialRealmThrows() { - Realm.UnsubscribeCallback callback = new Realm.UnsubscribeCallback() { - @Override - public void onSuccess(String subscriptionName) { - } - - @Override - public void onError(String subscriptionName, Throwable error) { - } - }; - - Realm realm = getNormalRealm(); - try { - //noinspection ConstantConditions - realm.unsubscribeAsync("my-id", callback); - fail(); - } catch (UnsupportedOperationException ignore) { - } finally { - realm.close(); - } - - realm = getFullySyncRealm(); - try { - //noinspection ConstantConditions - realm.unsubscribeAsync("my-id", callback); - fail(); - } catch (UnsupportedOperationException ignore) { - } finally { - realm.close(); - } - - looperThread.testComplete(); - } - - @Test - public void delete_throws() { - realm = getPartialRealm(); - realm.beginTransaction(); - try { - realm.deleteAll(); - fail(); - } catch (IllegalStateException e) { - } - - try { - realm.delete(PartialSyncObjectA.class); - fail(); - } catch (IllegalStateException e) { - } - realm.cancelTransaction(); - - DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); - try { - dynamicRealm.beginTransaction(); - try { - dynamicRealm.delete(PartialSyncObjectA.class.getSimpleName()); - fail(); - } catch (IllegalStateException e) { - } - } finally { - dynamicRealm.close(); - } - } - @Test public void compactRealm_populatedRealm() { SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), Constants.DEFAULT_REALM).build(); @@ -290,67 +156,4 @@ public boolean shouldCompact(long totalBytes, long usedBytes) { assertTrue(originalSize > compactedSize); } - @Test - public void getSubscriptions() { - realm = getPartialRealm(); - RealmResults subscriptions = realm.getSubscriptions(); - assertEquals(0, subscriptions.size()); - - realm.executeTransaction(r -> { - r.where(AllTypes.class).subscribe("sub1"); - }); - - assertEquals(1, subscriptions.size()); - assertEquals("sub1", subscriptions.first().getName()); - } - - @Test - public void getSubscriptions_withPattern() { - realm = getPartialRealm(); - assertEquals(0, realm.getSubscriptions("sub?").size()); - - realm.executeTransaction(r -> { - r.where(AllTypes.class).subscribe("sub1"); - r.where(AllTypes.class).subscribe("sub2"); - }); - - assertEquals(0, realm.getSubscriptions("sub").size()); - assertEquals(2, realm.getSubscriptions("sub?").size()); - assertEquals(2, realm.getSubscriptions("s*").size()); - } - - @Test - public void getSubscriptions_withPattern_throwsIfNullPattern() { - realm = getPartialRealm(); - try { - //noinspection ConstantConditions - realm.getSubscriptions(null); - fail(); - } catch (IllegalArgumentException ignore) { - } - } - - @Test - public void getSubscription() { - realm = getPartialRealm(); - assertNull(realm.getSubscription("sub")); - - realm.executeTransaction(r -> { - r.where(AllTypes.class).subscribe("sub"); - }); - - Subscription sub = realm.getSubscription("sub"); - assertNotNull(sub); - assertEquals("sub", sub.getName()); - } - - @Test - public void includeLinkingObjects_throwsForNonQueryBasedRealms() { - realm = getFullySyncRealm(); - try { - realm.where(AllJavaTypes.class).includeLinkingObjects(AllJavaTypes.FIELD_STRING); - fail(); - } catch (IllegalStateException ignore) { - } - } } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 677d592023..67e0a3b8bd 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -79,7 +79,7 @@ set(classes_LIST io.realm.internal.OsObjectSchemaInfo io.realm.internal.OsResults io.realm.internal.NativeObjectReference io.realm.internal.OsCollectionChangeSet io.realm.internal.OsObject io.realm.internal.OsRealmConfig io.realm.internal.OsList - io.realm.internal.OsObjectStore io.realm.internal.sync.OsSubscription + io.realm.internal.OsObjectStore io.realm.internal.core.DescriptorOrdering io.realm.internal.core.IncludeDescriptor io.realm.internal.objectstore.OsObjectBuilder ) @@ -174,7 +174,6 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAsyncOpenTask.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_sync_OsSubscription.cpp ) endif() diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp index 267ccff02e..93060c33cb 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp @@ -44,21 +44,3 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmQuery_nativeSerializeQuery(JNIEnv* CATCH_STD() return to_jstring(env, ""); } - -JNIEXPORT jlong JNICALL Java_io_realm_RealmQuery_nativeSubscribe(JNIEnv* env, jclass, jlong shared_realm_ptr, - jstring j_name, jlong table_query_ptr, jlong descriptor_ptr, REALM_UNUSED jlong time_to_live_ms, REALM_UNUSED jboolean update) -{ - try { - auto realm = *reinterpret_cast(shared_realm_ptr); - auto name = util::Optional(JStringAccessor(env, j_name)); - auto query = reinterpret_cast(table_query_ptr); - auto descriptor = reinterpret_cast(descriptor_ptr); - Results r(realm, *query, *descriptor); -#if REALM_ENABLE_SYNC - Obj obj = partial_sync::subscribe_blocking(r, name, util::Optional(time_to_live_ms), update); - return to_jlong_or_not_found(obj.get_key()); -#endif - } - CATCH_STD() - return realm::npos; -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index a868a9ac83..f338037462 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -243,7 +243,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNo #if REALM_ENABLE_SYNC JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( JNIEnv* env, jclass, jlong native_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, - jstring j_refresh_token, jboolean j_is_partial, jbyte j_session_stop_policy, jstring j_url_prefix, + jstring j_refresh_token, jbyte j_session_stop_policy, jstring j_url_prefix, jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array, jbyte j_client_reset_mode) { auto& config = *reinterpret_cast(native_ptr); @@ -356,7 +356,6 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe config.sync_config->stop_policy = session_stop_policy; config.sync_config->bind_session_handler = std::move(bind_handler); config.sync_config->error_handler = std::move(error_handler); - config.sync_config->is_partial = (j_is_partial == JNI_TRUE); switch (j_client_reset_mode) { case io_realm_internal_OsRealmConfig_CLIENT_RESYNC_MODE_RECOVER: config.sync_config->client_resync_mode = realm::ClientResyncMode::Recover; break; case io_realm_internal_OsRealmConfig_CLIENT_RESYNC_MODE_DISCARD: config.sync_config->client_resync_mode = realm::ClientResyncMode::DiscardLocal; break; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 1568b789c1..06d5ab6aed 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -515,13 +515,6 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetObjectPrivi } #endif -JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsPartial(JNIEnv*, jclass, jlong shared_realm_ptr) -{ - // No throws - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - return to_jbool(shared_realm->is_partial()); -} - JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsFrozen(JNIEnv* env, jclass, jlong shared_realm_ptr) { try { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index eeffa27781..ccdf5dc06a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -177,15 +177,11 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeSize(JNIEnv*, jobject return static_cast(table->size()); // noexcept } -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear(JNIEnv* env, jobject, jlong nativeTableRefPtr, jboolean is_partial_realm) +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeClear(JNIEnv* env, jobject, jlong nativeTableRefPtr) { try { TableRef table = TBL_REF(nativeTableRefPtr); - if (is_partial_realm) { - table->where().find_all().clear(); - } else { - table->clear(); - } + table->clear(); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp index 827a60131e..9e19039902 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_core_DescriptorOrdering.cpp @@ -80,18 +80,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppe CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeAppendInclude(JNIEnv* env, jclass, - jlong descriptor_ptr, - jlong include_descriptor_ptr) -{ - try { - auto descriptor = reinterpret_cast(descriptor_ptr); - auto include_descriptor = reinterpret_cast(include_descriptor_ptr); - descriptor->append_include(*include_descriptor); - } - CATCH_STD() -} - JNIEXPORT jboolean JNICALL Java_io_realm_internal_core_DescriptorOrdering_nativeIsEmpty(JNIEnv* env, jclass, jlong descriptor_ptr) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp deleted file mode 100644 index ab4acc0ef6..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_internal_sync_OsSubscription.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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 "io_realm_internal_sync_OsSubscription.h" - -#include "java_class_global_def.hpp" -#include "observable_collection_wrapper.hpp" -#include "util.hpp" -#include "subscription_wrapper.hpp" -#include "jni_util/java_class.hpp" -#include "jni_util/java_method.hpp" -#include "object-store/src/sync/partial_sync.hpp" - -#include -#include - -using namespace realm; -using namespace realm::jni_util; -using namespace realm::_impl; - -typedef ObservableCollectionWrapper ResultsWrapper; - -static void finalize_subscription(jlong ptr); - -static void finalize_subscription(jlong ptr) -{ - delete reinterpret_cast(ptr); -} - -JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeCreateOrUpdate(JNIEnv* env, jclass, jlong results_ptr, jstring j_subscription_name, jlong time_to_live, jboolean update) -{ - try { - const auto results = reinterpret_cast(results_ptr); - JStringAccessor subscription_name(env, j_subscription_name); - auto key = subscription_name.is_null_or_empty() ? util::none : util::Optional(subscription_name); - partial_sync::SubscriptionOptions options; - options.user_provided_name = key; - options.time_to_live_ms = util::Optional(time_to_live); - options.update = update; - auto subscription = partial_sync::subscribe(results->collection(), options); - auto wrapper = new SubscriptionWrapper(std::move(subscription)); - return reinterpret_cast(wrapper); - } - CATCH_STD() - return reinterpret_cast(nullptr); -} - - -JNIEXPORT jlong JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetFinalizerPtr(JNIEnv*, jclass) -{ - return reinterpret_cast(&finalize_subscription); -} - -JNIEXPORT void JNICALL Java_io_realm_internal_sync_OsSubscription_nativeStartListening(JNIEnv* env, jobject object, jlong native_ptr) -{ - try { - auto wrapper = reinterpret_cast(native_ptr); - wrapper->start_listening(env, object); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_internal_sync_OsSubscription_nativeStopListening(JNIEnv* env, jobject, jlong native_ptr) -{ - try { - auto wrapper = reinterpret_cast(native_ptr); - wrapper->stop_listening(); - } - CATCH_STD() -} - -JNIEXPORT jint JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetState(JNIEnv* env, jclass, jlong native_ptr) -{ - try { - auto wrapper = reinterpret_cast(native_ptr); - return static_cast(wrapper->subscription().state()); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jobject JNICALL Java_io_realm_internal_sync_OsSubscription_nativeGetError(JNIEnv* env, jclass, jlong native_ptr) -{ - try { - auto wrapper = reinterpret_cast(native_ptr); - auto err = wrapper->subscription().error(); - if (err) { - std::string error_message = ""; - try { - std::rethrow_exception(err); - } - catch (const std::exception &e) { - error_message = e.what(); - } - - static JavaClass illegal_argument_class(env, "java/lang/IllegalArgumentException"); - static JavaMethod illegal_argument_constructor(env, illegal_argument_class, "", "(Ljava/lang/String;)V"); - return static_cast(env->NewObject(illegal_argument_class, illegal_argument_constructor, to_jstring(env, error_message))); - } - return nullptr; - } - CATCH_STD() - return nullptr; -} diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 07deb09d5a..063b808c63 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -499,17 +499,6 @@ protected void checkIfInTransaction() { } } - protected void checkIfPartialRealm() { - boolean isPartialRealm = false; - if (configuration.isSyncConfiguration()) { - isPartialRealm = ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(configuration); - } - - if (!isPartialRealm) { - throw new IllegalStateException("This method is only available on partially synchronized Realms."); - } - } - /** * Checks if the Realm is valid and in a transaction. */ @@ -668,20 +657,13 @@ E get(@Nullable Class clazz, @Nullable String dynamicC /** * Deletes all objects from this Realm. - *

            - * If the Realm is a partially synchronized Realm, all subscriptions will be cleared as well. * - * @throws IllegalStateException if the corresponding Realm is a partially synchronized Realm, is - * closed or called from an incorrect thread. + * @throws IllegalStateException if the Realm is closed or called from an incorrect thread. */ public void deleteAll() { checkIfValid(); - if (sharedRealm.isPartial()) { - throw new IllegalStateException(DELETE_NOT_SUPPORTED_UNDER_PARTIAL_SYNC); - } - boolean isPartialRealm = sharedRealm.isPartial(); for (RealmObjectSchema objectSchema : getSchema().getAll()) { - getSchema().getTable(objectSchema.getClassName()).clear(isPartialRealm); + getSchema().getTable(objectSchema.getClassName()).clear(); } } diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index ecf3a69b5d..eeef10b648 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -223,16 +223,12 @@ public void removeAllChangeListeners() { * Deletes all objects of the specified class from the Realm. * * @param className the class for which all objects should be removed. - * @throws IllegalStateException if the corresponding Realm is a partially synchronized Realm, is - * closed or called from an incorrect thread. + * @throws IllegalStateException if the Realm is closed or called from an incorrect thread. */ public void delete(String className) { checkIfValid(); checkIfInTransaction(); - if (sharedRealm.isPartial()) { - throw new IllegalStateException(DELETE_NOT_SUPPORTED_UNDER_PARTIAL_SYNC); - } - schema.getTable(className).clear(sharedRealm.isPartial()); + schema.getTable(className).clear(); } /** diff --git a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java index 54abab36e7..8cf1e5ba45 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java @@ -49,12 +49,6 @@ public enum State { *

            * For local and fully synchronized Realms, this state should only be encountered if the * Realm could not be succesfully opened in the background,. - *

            - * For partially synchronized Realms, it is only possible to get into this state if an error - * happened while evaluating the query on the server or some other error prevented data from - * being downloaded. - *

            - * In this state, the content of the {@link RealmResults} is undefined. */ ERROR } @@ -123,43 +117,6 @@ public enum State { @Nullable Throwable getError(); - /** - * Returns {@code true} if the query result is considered "complete". For all local Realms, or - * fully synchronized Realms, this method will always return {@code true}. - *

            - * This method thus only makes sense for query-based synchronized Realms. - *

            - * For those Realms, data is only downloaded when queried which means that until the data is - * downloaded, a local query might return a query result that would not have been possible on a - * fully synchronized Realm. - *

            - * Consider the following case: - *

              - *
            1. An app is online and makes a query for all messages containing the word "Realm".
            2. - *
            3. Partial synchronization downloads all those messages.
            4. - *
            5. The app goes offline.
            6. - *
            7. The app makes an offline query against all messages containing the word "Database".
            8. - *
            - * - * Here there are two situations where the query result might be considered "incomplete". - *

            - * The first is when the "Realm" query runs for the first time. The local query will finish - * faster than the network can download data so the query will initially report an empty - * incomplete query result. - *

            - * The second is when the "Database" query is run. The initial query result will not be - * empty, but contain all messages that contain both "Realm" and "Database", as they are already - * available offline. - *

            - * In both cases, a new notification will be triggered as soon as the device is able to download - * the data required to produce a "complete" query result. - * - * @return {@code true} if the query result is fully consistent with the server at some point in - * time. {@code false} if the query was executed while the device was offline or all data - * has not been downloaded yet. - */ - boolean isCompleteResult(); - /** * */ diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 84b61064c1..02cbae7d42 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -47,18 +47,15 @@ import javax.annotation.Nullable; import io.reactivex.Flowable; -import io.realm.annotations.Beta; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.internal.ColumnIndices; -import io.realm.internal.NativeObject; import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsObject; import io.realm.internal.OsObjectSchemaInfo; import io.realm.internal.OsObjectStore; -import io.realm.internal.OsResults; import io.realm.internal.OsSchemaInfo; import io.realm.internal.OsSharedRealm; import io.realm.internal.RealmCore; @@ -66,12 +63,10 @@ import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Table; -import io.realm.internal.TableQuery; import io.realm.internal.Util; import io.realm.internal.annotations.ObjectServer; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; -import io.realm.sync.Subscription; /** * The Realm class is the storage and transactional manager of your object persistent store. It is in charge of creating @@ -1672,15 +1667,11 @@ public void run() { * Deletes all objects of the specified class from the Realm. * * @param clazz the class which objects should be removed. - * @throws IllegalStateException if the corresponding Realm is a query-based synchronized Realm, is - * closed or called from an incorrect thread. + * @throws IllegalStateException if the Realm is closed or called from an incorrect thread. */ public void delete(Class clazz) { checkIfValid(); - if (sharedRealm.isPartial()) { - throw new IllegalStateException(DELETE_NOT_SUPPORTED_UNDER_PARTIAL_SYNC); - } - schema.getTable(clazz).clear(sharedRealm.isPartial()); + schema.getTable(clazz).clear(); } @@ -1802,112 +1793,6 @@ public static boolean compactRealm(RealmConfiguration configuration) { return BaseRealm.compactRealm(configuration); } - /** - * Cancel a named subscription that was created by calling {@link RealmQuery#findAllAsync(String)}. - * If after this, some objects are no longer part of any active subscription they will be removed - * locally from the device (but not on the server). - * - * The effect of unsubscribing is not immediate. The local Realm must coordinate with the Object - * Server before this can happen. A successful callback just indicate that the request was - * succesfully enqueued and any data will be removed as soon as possible. When the data is - * actually removed locally, a standard change notification will be triggered and from the - * perspective of the device it will look like the data was deleted. - * - * @param subscriptionName name of the subscription to remove - * @param callback callback reporting back if the intent to unsubscribe was enqueued successfully or failed. - * @return a {@link RealmAsyncTask} representing a cancellable task. - * @throws IllegalArgumentException if no {@code subscriptionName} or {@code callback} was provided. - * @throws IllegalStateException if called on a non-looper thread. - * @throws UnsupportedOperationException if the Realm is not a query-based synchronized Realm. - */ - @Beta - public RealmAsyncTask unsubscribeAsync(String subscriptionName, Realm.UnsubscribeCallback callback) { - if (Util.isEmptyString(subscriptionName)) { - throw new IllegalArgumentException("Non-empty 'subscriptionName' required."); - } - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("'callback' required."); - } - sharedRealm.capabilities.checkCanDeliverNotification("This method is only available from a Looper thread."); - if (!ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(configuration)) { - throw new UnsupportedOperationException("Realm is fully synchronized Realm. This method is only available when using query-based synchronization: " + configuration.getPath()); - } - - return executeTransactionAsync(new Transaction() { - @Override - public void execute(Realm realm) { - - // Need to manually run a dynamic query here. - // TODO Add support for DynamicRealm.executeTransactionAsync() - Table table = realm.sharedRealm.getTable("class___ResultSets"); - TableQuery query = table.where() - .equalTo(new long[]{table.getColumnKey("name")}, new long[]{NativeObject.NULLPTR}, subscriptionName); - - OsResults result = OsResults.createFromQuery(realm.sharedRealm, query); - long count = result.size(); - if (count == 0) { - throw new IllegalArgumentException("No active subscription named '"+ subscriptionName +"' exists."); - } - if (count > 1) { - RealmLog.warn("Multiple subscriptions named '" + subscriptionName + "' exists. This should not be possible. They will all be deleted"); - } - result.clear(); - } - }, new Transaction.OnSuccess() { - @Override - public void onSuccess() { - callback.onSuccess(subscriptionName); - } - }, new Transaction.OnError() { - @Override - public void onError(Throwable error) { - callback.onError(subscriptionName, error); - } - }); - } - - /** - * Returns a list of all known subscriptions, regardless of their status. - * - * @return a list of all known subscriptions. - */ - @Beta - @ObjectServer - public RealmResults getSubscriptions() { - return where(Subscription.class).findAll(); - } - - /** - * Returns a list of all subscriptions that match a given pattern. {@code *} can be used to - * indicate any number of unknown characters and {@code ?} represents a single unknown character. - * - * @param pattern which subscriptions to find. - * @return list of subscriptions that match the pattern. - * @throws IllegalArgumentException if an empty or {@code null} pattern is provided. - */ - @Beta - @ObjectServer - public RealmResults getSubscriptions(String pattern) { - if (Util.isEmptyString(pattern)) { - throw new IllegalArgumentException("Non-empty 'pattern' required"); - } - return where(Subscription.class).like("name", pattern).findAll(); - } - - /** - * Returns the first subscription that matches the given name. - * - * @param name the name of the subscription to find. - * @return returns the subscription that matches the name or {@code null} if no subscription matches the name. - */ - @Beta - @ObjectServer - @Nullable - public Subscription getSubscription(String name) { - return where(Subscription.class).equalTo("name", name).findFirst(); - } - /** * {@inheritDoc} */ @@ -2014,28 +1899,6 @@ interface OnError { } } - /** - * Interface used when canceling query-based sync subscriptions. - * - * @see #unsubscribeAsync(String, UnsubscribeCallback) - */ - public interface UnsubscribeCallback { - /** - * Callback invoked when the request to unsubscribe was succesfully enqueued. - * - * @param subscriptionName subscription that was canceled. - */ - void onSuccess(String subscriptionName); - - /** - * Callback invoked if an error happened while trying to unsubscribe. - * - * @param subscriptionName subscription on which the error occurred. - * @param error cause of error. - */ - void onError(String subscriptionName, Throwable error); - } - /** * {@inheritDoc} */ diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 4c972bc953..f3c06c81bf 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -15,8 +15,6 @@ */ package io.realm; -import android.os.SystemClock; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -426,40 +424,17 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo if (firstRealmInstanceInProcess) { copyAssetFileIfNeeded(configuration); - OsSharedRealm sharedRealm = null; - try { - // If waitForInitialRemoteData() was enabled, we need to make sure that all data is downloaded - // before proceeding. We need to open the Realm instance first to start any potential underlying - // SyncSession so this will work. - if (configuration.isSyncConfiguration() && realmFileIsBeingCreated) { - // Manually create the Java session wrapper session as this might otherwise - // not be created - OsRealmConfig osConfig = new OsRealmConfig.Builder(configuration).build(); - ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(osConfig); - - if (ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(configuration)) { - // Partial Realms are not supported by async open yet, so continue to - // use the old way of opening those Realms. - sharedRealm = OsSharedRealm.getInstance(configuration, OsSharedRealm.VersionID.LIVE); - try { - ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialRemoteChanges(configuration); - } catch (Throwable t) { - // If an error happened while downloading initial data, we need to reset the file so we can - // download it again on the next attempt. - sharedRealm.close(); - sharedRealm = null; - deleteRealmFileOnDisk(configuration); - throw t; - } - } else { - // Fully synchronized Realms are supported by AsyncOpen - ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialRemoteChanges(configuration); - } - } - } finally { - if (sharedRealm != null) { - sharedRealm.close(); - } + // If waitForInitialRemoteData() was enabled, we need to make sure that all data is downloaded + // before proceeding. We need to open the Realm instance first to start any potential underlying + // SyncSession so this will work. + if (configuration.isSyncConfiguration() && realmFileIsBeingCreated) { + // Manually create the Java session wrapper session as this might otherwise + // not be created + OsRealmConfig osConfig = new OsRealmConfig.Builder(configuration).build(); + ObjectServerFacade.getSyncFacadeIfPossible().wrapObjectStoreSessionIfRequired(osConfig); + + // Fully synchronized Realms are supported by AsyncOpen + ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialRemoteChanges(configuration); } // We are holding the lock, and we can set the valid configuration since there is no global ref to it. @@ -470,7 +445,7 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo } if (!referenceCounter.hasInstanceAvailableForThread()) { - createInstance(realmClass, referenceCounter, realmFileIsBeingCreated, version); + createInstance(realmClass, referenceCounter, version); } referenceCounter.incrementThreadCount(1); @@ -497,7 +472,6 @@ private ReferenceCounter getRefCounter(Class realmClass private void createInstance(Class realmClass, ReferenceCounter referenceCounter, - boolean realmFileIsBeingCreated, OsSharedRealm.VersionID version) { // Creates a new local Realm instance BaseRealm realm; @@ -506,12 +480,6 @@ private void createInstance(Class realmClass, // RealmMigrationNeededException might be thrown here. realm = Realm.createInstance(this, version); - // If `waitForInitialRemoteData` data is set, we also want to ensure that all subscriptions - // are fully ACTIVE before proceeding. Most of the Realm is initialized during a write - // transaction. So we cannot download subscription data until all other initializers have run. - // At this point we also have access to all normal APIs as the schema is fully initialized. - synchronizeInitialSubscriptionsIfNeeded((Realm) realm, realmFileIsBeingCreated); - } else if (realmClass == DynamicRealm.class) { realm = DynamicRealm.createInstance(this, version); } else { @@ -522,57 +490,6 @@ private void createInstance(Class realmClass, referenceCounter.onRealmCreated(realm); } - /** - * Synchronize all initial subscriptions to disk (if needed). - * - * If activating the subscriptions fails for a new Realm file, the file will be deleted so a new - * attempt can be done later. Old Realm files will be left alone. - * - * This method is not threadsafe. Synchronization should happen outside it. - * - * @param realm Realm instance to synchronize instances for. It is safe to close this Realm if an exception is thrown. - * @param {@code true} if the file existed on disk before trying to open the Realm. - */ - private static void synchronizeInitialSubscriptionsIfNeeded(Realm realm, boolean realmFileIsBeingCreated) { - if (realmFileIsBeingCreated) { - try { - ObjectServerFacade.getSyncFacadeIfPossible().downloadInitialSubscriptions(realm); - } catch (Throwable t) { - realm.close(); - deleteRealmFileOnDisk(realm.getConfiguration()); - } - } - } - - /** - * Attempts to delete the underlying Realm. Any errors happening here will just be - * outputted to logcat instead of thrown as this method is only called from other exception - * handlers which have more important exceptions to show to the user. - * - * This method is not threadsafe. Synchronization should happen outside it. - */ - private static void deleteRealmFileOnDisk(RealmConfiguration configuration) { - // FIXME: We don't have a way to ensure that the Realm instance on client thread has been closed for now. - // https://github.com/realm/realm-java/issues/5416 - int attempts = 5; - boolean success = false; - while (attempts > 0 && !success) { - try { - success = BaseRealm.deleteRealm(configuration); - } catch (IllegalStateException e) { - attempts--; - RealmLog.warn("Sync server still holds a reference to the Realm. It cannot be deleted. Retrying " + attempts + " more times"); - if (attempts > 0) { - SystemClock.sleep(15); - } - } - } - - if (!success) { - RealmLog.error("Failed to delete the underlying Realm file: " + configuration.getPath()); - } - } - /** * Releases a given {@link Realm} or {@link DynamicRealm} from cache. The instance will be closed by this method * if there is no more local reference to this Realm instance in current Thread. diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 9c406bacc3..b70991542d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -17,35 +17,23 @@ package io.realm; -import android.text.TextUtils; - import java.util.Collections; import java.util.Date; import java.util.Locale; -import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; -import io.realm.annotations.Beta; import io.realm.annotations.Required; -import io.realm.internal.CheckedRow; -import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsList; import io.realm.internal.OsResults; import io.realm.internal.PendingRow; -import io.realm.internal.annotations.ObjectServer; -import io.realm.internal.core.IncludeDescriptor; -import io.realm.internal.core.QueryDescriptor; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; -import io.realm.internal.SubscriptionAwareOsResults; import io.realm.internal.Table; import io.realm.internal.TableQuery; -import io.realm.internal.Util; import io.realm.internal.core.DescriptorOrdering; +import io.realm.internal.core.QueryDescriptor; import io.realm.internal.fields.FieldDescriptor; -import io.realm.internal.sync.SubscriptionAction; -import io.realm.sync.Subscription; /** @@ -1775,7 +1763,7 @@ public long count() { @SuppressWarnings("unchecked") public RealmResults findAll() { realm.checkIfValid(); - return createRealmResults(query, queryDescriptors, true, SubscriptionAction.NO_SUBSCRIPTION); + return createRealmResults(query, queryDescriptors, true); } /** @@ -1791,15 +1779,11 @@ private OsResults lazyFindAll() { return createRealmResults( query, queryDescriptors, - false, - SubscriptionAction.NO_SUBSCRIPTION).osResults; + false).osResults; } /** * Finds all objects that fulfill the query conditions. This method is only available from a Looper thread. - *

            - * If the Realm is a Query-based synchronized Realms, this method will also create an anonymous subscription - * that will download all server data matching the query. * * @return immediately an empty {@link RealmResults}. Users need to register a listener * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. @@ -1807,123 +1791,10 @@ private OsResults lazyFindAll() { */ public RealmResults findAllAsync() { realm.checkIfValid(); - realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - SubscriptionAction subscriptionAction; - - // Don't create subscriptions for list queries as they are always part of an object covered by another query. - if (realm.sharedRealm.isPartial() && osList == null) { - subscriptionAction = SubscriptionAction.ANONYMOUS_SUBSCRIPTION; - } else { - subscriptionAction = SubscriptionAction.NO_SUBSCRIPTION; - } - return createRealmResults(query, queryDescriptors, false, subscriptionAction); + return createRealmResults(query, queryDescriptors, false); } - /** - * Finds all objects that fulfill the query condition(s). This method is only available from a Looper thread. - *

            - * This method is only available on query-based synchronized Realms and will also create a named subscription - * that will synchronize all server data matching the query. Named subscriptions can be removed again by - * calling {@code Realm.unsubscribe(subscriptionName}. - * - * @param subscriptionName name of the underlying subscription being created. - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @see io.realm.RealmResults - * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm or the query is on a {@link RealmList}. - */ - @ObjectServer - public RealmResults findAllAsync(String subscriptionName) { - return findAllAsync(subscriptionName, Long.MAX_VALUE, TimeUnit.MILLISECONDS, false); - } - - /** - * Finds all objects that fulfil the query condition(s). This method is only available from a Looper thread. - *

            - * This method is only available on query-based synchronized Realms and will also create a named subscription - * that will synchronize all server data matching the query. Named subscriptions can be removed again by - * calling {@code Realm.unsubscribe(subscriptionName}. - * - * @param subscriptionName name of the underlying subscription being created. - * @param update if an existing subscription exists with a different query. It will be replaced with this - * one instead of an error being reported through {@link OrderedRealmCollectionChangeListener}. - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @see io.realm.RealmResults - * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm or the query is on a {@link RealmList}. - */ - @ObjectServer - @Beta - public RealmResults findAllAsync(String subscriptionName, boolean update) { - return findAllAsync(subscriptionName, Long.MAX_VALUE, TimeUnit.MILLISECONDS, update); - } - - /** - * Finds all objects that fulfil the query condition(s). This method is only available from a Looper thread. - *

            - * This method is only available on query-based synchronized Realms and will also create a named subscription - * that will synchronize all server data matching the query. Named subscriptions can be removed again by - * calling {@code Realm.unsubscribe(subscriptionName}. - * - * @param subscriptionName name of the underlying subscription being created. - * @param timeToLive the amount of time the Subscription must be kept alive after last being used. After this - * period Realm will automatically remove it. - * @param timeUnit the unit for {@code timeToLive}. - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @see io.realm.RealmResults - * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm or the query is on a {@link RealmList}. - */ - @ObjectServer - @Beta - public RealmResults findAllAsync(String subscriptionName, long timeToLive, TimeUnit timeUnit) { - return findAllAsync(subscriptionName, timeToLive, timeUnit, false); - } - - /** - * Finds all objects that fulfil the query condition(s). This method is only available from a Looper thread. - *

            - * This method is only available on query-based synchronized Realms and will also create a named subscription - * that will synchronize all server data matching the query. Named subscriptions can be removed again by - * calling {@code Realm.unsubscribe(subscriptionName}. - * - * @param subscriptionName name of the underlying subscription being created. - * @param timeToLive the amount of time the Subscription must be kept alive after last being used. After this - * period Realm will automatically remove it. - * @param timeUnit the unit for {@code timeToLive}. - * @param update if an existing subscription exists with a different query. It will be replaced with this - * one instead of an error being reported through {@link OrderedRealmCollectionChangeListener}. - * @return immediately an empty {@link RealmResults}. Users need to register a listener - * {@link io.realm.RealmResults#addChangeListener(RealmChangeListener)} to be notified when the query completes. - * @see io.realm.RealmResults - * @throws IllegalStateException If the Realm is a not a query-based synchronized Realm or the query is on a {@link RealmList}. - */ - @ObjectServer - @Beta - public RealmResults findAllAsync(String subscriptionName, long timeToLive, TimeUnit timeUnit, boolean update) { - realm.checkIfValid(); - realm.checkIfPartialRealm(); - if (osList != null) { - throw new IllegalStateException("Cannot create subscriptions for queries based on a 'RealmList'"); - } - if (Util.isEmptyString(subscriptionName)) { - throw new IllegalArgumentException("Non-empty 'subscriptionName' required."); - } - if (timeToLive < 0) { - throw new IllegalArgumentException("Negative values for 'timeToLive' are not allowed: " + timeToLive); - } - //noinspection ConstantConditions - if (timeUnit == null) { - throw new IllegalArgumentException("Non-null 'timeUnit' required."); - } - realm.sharedRealm.capabilities.checkCanDeliverNotification(ASYNC_QUERY_WRONG_THREAD_MESSAGE); - long timeToLiveMs = timeUnit.toMillis(timeToLive); - SubscriptionAction action = (update) ? SubscriptionAction.update(subscriptionName, timeToLiveMs) : SubscriptionAction.create(subscriptionName, timeToLiveMs); - return createRealmResults(query, queryDescriptors, false, action); - } - - /** * Sorts the query result by the specific field name in ascending order. *

            @@ -2054,45 +1925,6 @@ public RealmQuery limit(long limit) { return this; } - /** - * This predicate is only relevant for Query-based Realms. - *

            - * Objects referenced through fields marked with {@link io.realm.annotations.LinkingObjects} are normally not downloaded - * as part of the subscription in Query-based Realms, but by using this predicate, it is possible to specify which linking - * objects relationships should also be included in the subscription as well. - *

            - * Note, that all "forward" object references like object references and lists are always downloaded as part of the - * subscription by default. - *

            - * This predicate can be called multiple times, in which case all fields will be added to the subscription. - *

            - * NOTE: This method is only supported when connecting to Realm Object Server 3.21.0 or later. If you use it with previous - * versions of Realm Object Server, an {@link IllegalArgumentException} will be sent to {@link OrderedCollectionChangeSet#getError()}. - * - * @param firstIncludePath the first {@link io.realm.annotations.LinkingObjects} field to add. - * @param remainingFieldPaths any remaining {@link io.realm.annotations.LinkingObjects} fields to add. - * @throws IllegalStateException if called on a non-query-based Realm. - * @throws IllegalArgumentException if the path does not end with a field marked with {@link io.realm.annotations.LinkingObjects}. - */ - @ObjectServer - public RealmQuery includeLinkingObjects(String firstIncludePath, @Nullable String... remainingFieldPaths) { - realm.checkIfValid(); - if (!ObjectServerFacade.getSyncFacadeIfPossible().isPartialRealm(realm.getConfiguration())) { - throw new IllegalStateException("This method is only available for Query-based Realms."); - } - if (Util.isEmptyString(firstIncludePath)) { - throw new IllegalArgumentException("Non-empty 'firstIncludePath' required."); - } - queryDescriptors.appendIncludes(IncludeDescriptor.createInstance(getSchemaConnector(), table, firstIncludePath)); - if (remainingFieldPaths != null) { - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < remainingFieldPaths.length; i++) { - queryDescriptors.appendIncludes(IncludeDescriptor.createInstance(getSchemaConnector(), table, remainingFieldPaths[i])); - } - } - return this; - } - /** * This predicate will always match. */ @@ -2133,152 +1965,6 @@ public Realm getRealm() { return (Realm) realm; } - /** - * Creates an anonymous subscription from this query or returns the existing Subscription if - * one already existed. - * - * @return the subscription representing this query. - * @throws IllegalStateException if this method is not called inside a write transaction or if - * the query is on a {@link DynamicRealm} - */ - @ObjectServer - @Beta - public Subscription subscribe() { - StringBuilder sb = new StringBuilder("["); - sb.append((table != null) ? table.getClassName() : ""); - sb.append("] "); - sb.append(nativeSerializeQuery(query.getNativePtr(), queryDescriptors.getNativePtr())); - String name = sb.toString(); - return subscribe(name); - } - - /** - * Creates a named subscription from this query or returns the existing Subscription if - * one already existed. Subscriptions created this way will live forever or until the - * subscription is manually deleted. - * - * @return the name of the subscription representing this query. - * @return the subscription representing this query. - * @throws IllegalStateException if this method is not called inside a write transaction, if - * the query is on a {@link DynamicRealm} or a {@link RealmList}. - * @throws IllegalArgumentException if a subscription for a different query with the same name - * already exists. - */ - @ObjectServer - @Beta - public Subscription subscribe(String name) { - return subscribe(name, Long.MAX_VALUE, TimeUnit.MILLISECONDS, false); - } - - /** - * Creates a named subscription from this query or returns the existing Subscription if - * one already exists. - *

            - * {@code timeToLive} indicates for how long Realm must keep the subscription alive after last - * being used. After this period expires Realm are allowed to delete the subscription. - * This happens automatically. The period is reset, whenever someone resubscribes or updates - * the subscription itself. - *

            - * When a subscription is deleted, the data covered by the subscription is removed from the - * device, but not the server. - * - * @param name the name subscription representing this query. - * @param timeToLive the amount of time the Subscription must be kept alive after last being used. After this - * period Realm will automatically remove it. - * @param timeUnit the unit for {@code timeToLive}. - * @return the subscription representing this query. - * @throws IllegalStateException if this method is not called inside a write transaction, if - * the query is on a {@link DynamicRealm} or a {@link RealmList}. - * @throws IllegalArgumentException if a subscription for a different query with the same name - * already exists. - */ - @ObjectServer - @Beta - public Subscription subscribe(String name, long timeToLive, TimeUnit timeUnit) { - return subscribe(name, timeToLive, timeUnit, false); - } - - /** - * Creates a named subscription from this query or returns the existing Subscription if - * one already existed. If an existing subscription already exists and the existing query - * is different, it will be replaced by this query. - *

            - * It is only allowed to update a subscription that queries for objects of the same type. If - * the existing subscription queries for objects of a different type, an {@link IllegalArgumentException} - * is thrown. - * - * @param name the name of the subscription. - * @return the subscription representing this query. - * @throws IllegalStateException if this method is not called inside a write transaction, if - * the query is on a {@link DynamicRealm} or a {@link RealmList}. - * @throws IllegalArgumentException if this query are for other objects than those already being - * returned by an existing subscription. - */ - @ObjectServer - @Beta - public Subscription subscribeOrUpdate(String name) { - return subscribe(name, Long.MAX_VALUE, TimeUnit.MILLISECONDS, true); - } - - /** - * Creates a named subscription from this query or returns the existing Subscription if - * one already existed. If a subscription already exists and the query - * is different, it will be replaced by this query. - *

            - * It is only allowed to update a subscription that queries for objects of the same type. If - * the existing subscription queries for objects of a different type, an {@link IllegalArgumentException} - * is thrown. - *

            - * {@code timeToLive} indicates for how long Realm must keep the subscription alive after last - * being used. After this period expires Realm are allowed to delete the subscription. - * This happens automatically. The period is reset, whenever the subscription is resubscribed or updated - *

            - * When a subscription is deleted, the data covered by the subscription is removed from the - * device, but not the server. - * - * @param name the name of the subscription. - * @param timeToLive the amount of time the Subscription must be kept alive after last being used. - * @param timeUnit the unit for {@code timeToLive}. - * @return the subscription representing this query. - * @throws IllegalStateException if this method is not called inside a write transaction, if - * the query is on a {@link DynamicRealm} or a {@link RealmList}. - * @throws IllegalArgumentException if this query are for other objects than those already being - * returned by an existing subscription. - */ - @ObjectServer - @Beta - public Subscription subscribeOrUpdate(String name, long timeToLive, TimeUnit timeUnit) { - return subscribe(name, timeToLive, timeUnit, true); - } - - - @ObjectServer - private Subscription subscribe(String name, long timeToLive, TimeUnit timeUnit, boolean update) { - realm.checkIfValid(); - if (realm instanceof DynamicRealm) { - throw new IllegalStateException("'subscribe' is not supported for queries on Dynamic Realms."); - } - if (osList != null) { - throw new IllegalStateException("Cannot create subscriptions for queries based on a 'RealmList. Subscribe to the object holding the list instead.'"); - } - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("Non-empty 'name' required."); - } - //noinspection ConstantConditions - if (timeUnit == null) { - throw new IllegalArgumentException("Non-null 'timeUnit' is required."); - } - - // Convert timestamp to milliseconds and clamp at max - long timeToLiveMs = TimeUnit.MILLISECONDS.convert(timeToLive, timeUnit); - - long objKey = nativeSubscribe(realm.getSharedRealm().getNativePtr(), name, query.getNativePtr(), - queryDescriptors.getNativePtr(), timeToLiveMs, update); - CheckedRow row = ((Realm) realm).getTable(Subscription.class).getCheckedRow(objKey); - return realm.get(Subscription.class, null, row); - } - - /** * Returns a textual description of this query. * @@ -2378,15 +2064,10 @@ public E findFirstAsync() { private RealmResults createRealmResults(TableQuery query, DescriptorOrdering queryDescriptors, - boolean loadResults, - SubscriptionAction subscriptionAction) { + boolean loadResults) { RealmResults results; OsResults osResults; - if (subscriptionAction.shouldCreateSubscriptions()) { - osResults = SubscriptionAwareOsResults.createFromQuery(realm.sharedRealm, query, queryDescriptors, subscriptionAction); - } else { - osResults = OsResults.createFromQuery(realm.sharedRealm, query, queryDescriptors); - } + osResults = OsResults.createFromQuery(realm.sharedRealm, query, queryDescriptors); if (isDynamicQuery()) { results = new RealmResults<>(realm, osResults, className); @@ -2418,7 +2099,4 @@ private SchemaConnector getSchemaConnector() { } private static native String nativeSerializeQuery(long tableQueryPtr, long descriptorPtr); - private static native long nativeSubscribe(long sharedRealmPtr, String name, long tableQueryPtr, - long descriptorPtr, long timeToLiveMs, boolean update); - } diff --git a/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java index 3ac1a8ebad..b10b2d352b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/EmptyLoadChangeSet.java @@ -15,10 +15,7 @@ */ package io.realm.internal; -import javax.annotation.Nullable; - import io.realm.RealmResults; -import io.realm.internal.sync.OsSubscription; /** * Empty changeset used if {@link RealmResults#load()} is called manually or if no collection @@ -29,12 +26,12 @@ public class EmptyLoadChangeSet extends OsCollectionChangeSet { private static final int[] NO_INDEX_CHANGES = new int[0]; private static final Range[] NO_RANGE_CHANGES = new Range[0]; - public EmptyLoadChangeSet(@Nullable OsSubscription subscription, boolean firstCallback, boolean isPartialRealm) { - super(0, firstCallback, subscription, isPartialRealm); + public EmptyLoadChangeSet(boolean firstCallback) { + super(0, firstCallback); } - public EmptyLoadChangeSet(@Nullable OsSubscription subscription, boolean isPartialRealm) { - super(0, true, subscription, isPartialRealm); + public EmptyLoadChangeSet() { + super(0, true); } @Override @@ -74,22 +71,9 @@ public Range[] getChangeRanges() { @Override public Throwable getError() { - if (subscription != null && subscription.getState() == OsSubscription.SubscriptionState.ERROR) { - return subscription.getError(); - } return null; } - @Override - public boolean isRemoteDataLoaded() { - return super.isRemoteDataLoaded(); - } - - @Override - public boolean isCompleteResult() { - return isRemoteDataLoaded(); - } - @Override public boolean isFirstAsyncCallback() { return super.isFirstAsyncCallback(); @@ -97,13 +81,7 @@ public boolean isFirstAsyncCallback() { @Override public boolean isEmpty() { - // Since this class represents "No collection" changes, it is only considered empty - // if no partial sync updates are found - if (subscription == null) { - return true; - } else { - return false; - } + return true; } @Override diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index a345f74170..a17800acd3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -116,25 +116,6 @@ public boolean wasDownloadInterrupted(Throwable throwable) { return false; } - public boolean isPartialRealm(RealmConfiguration configuration) { - return false; - } - - public void addSupportForObjectLevelPermissions(RealmConfiguration.Builder builder) { - // Do nothing - } - - /** - * If the Realm is a Query-based Realm, ensure that all subscriptions are ACTIVE before - * proceeding. This should only be called when opening a Realm for the first time. - * - * @throws {@code DownloadingRealmInterruptedException} if the thread was interrupted while blocked waiting for - * this to complete. - */ - public void downloadInitialSubscriptions(Realm realm) { - // Do nothing - } - public void createNativeSyncSession(RealmConfiguration configuration) { // Do nothing } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java index 44902a5e02..67addc40de 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java @@ -18,10 +18,7 @@ import java.util.Arrays; -import javax.annotation.Nullable; - import io.realm.OrderedCollectionChangeSet; -import io.realm.internal.sync.OsSubscription; /** * Implementation of {@link OrderedCollectionChangeSet}. This class holds a pointer to the Object Store's @@ -46,18 +43,10 @@ public class OsCollectionChangeSet implements OrderedCollectionChangeSet, Native private static long finalizerPtr = nativeGetFinalizerPtr(); private final long nativePtr; private final boolean firstAsyncCallback; - protected final OsSubscription subscription; - protected final boolean isPartialRealm; public OsCollectionChangeSet(long nativePtr, boolean firstAsyncCallback) { - this(nativePtr, firstAsyncCallback, null, false); - } - - public OsCollectionChangeSet(long nativePtr, boolean firstAsyncCallback, @Nullable OsSubscription subscription, boolean isPartialRealm) { this.nativePtr = nativePtr; this.firstAsyncCallback = firstAsyncCallback; - this.subscription = subscription; - this.isPartialRealm = isPartialRealm; NativeContext.dummyContext.addReference(this); } @@ -116,29 +105,9 @@ public Range[] getChangeRanges() { @Override public Throwable getError() { - if (subscription != null && subscription.getState() == OsSubscription.SubscriptionState.ERROR) { - return subscription.getError(); - } return null; } - @Override - public boolean isCompleteResult() { - throw new UnsupportedOperationException("This method should be overridden in a subclass"); - } - - public boolean isRemoteDataLoaded() { - if (!isPartialRealm) { - return true; - } else if (subscription == null) { - // This will in some cases return false positives, like adding change listeners - // to synchronous queries. For now this is acceptable. - return false; - } else { - return subscription.getState() == OsSubscription.SubscriptionState.COMPLETE; - } - } - /** * Returns {@code true} if this is the first time an asynchronous query returns a result, i.e. * the query completed. diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index c2e517b863..8ad8576e3f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -214,15 +214,14 @@ private OsRealmConfig(final RealmConfiguration config, boolean syncClientValidateSsl = (Boolean.TRUE.equals(syncConfigurationOptions[4])); String syncSslTrustCertificatePath = (String) syncConfigurationOptions[5]; Byte sessionStopPolicy = (Byte) syncConfigurationOptions[6]; - boolean isPartial = (Boolean.TRUE.equals(syncConfigurationOptions[7])); - String urlPrefix = (String)(syncConfigurationOptions[8]); - String customAuthorizationHeaderName = (String)(syncConfigurationOptions[9]); - Byte clientResyncMode = (Byte) syncConfigurationOptions[11]; + String urlPrefix = (String)(syncConfigurationOptions[7]); + String customAuthorizationHeaderName = (String)(syncConfigurationOptions[8]); + Byte clientResyncMode = (Byte) syncConfigurationOptions[10]; // Convert the headers into a String array to make it easier to send through JNI // [key1, value1, key2, value2, ...] //noinspection unchecked - Map customHeadersMap = (Map) (syncConfigurationOptions[10]); + Map customHeadersMap = (Map) (syncConfigurationOptions[9]); String[] customHeaders = new String[customHeadersMap != null ? customHeadersMap.size() * 2 : 0]; if (customHeadersMap != null) { int i = 0; @@ -282,7 +281,6 @@ private OsRealmConfig(final RealmConfiguration config, syncRealmAuthUrl, syncUserIdentifier, syncRefreshToken, - isPartial, sessionStopPolicy, urlPrefix, customAuthorizationHeaderName, @@ -380,7 +378,7 @@ private native void nativeSetSchemaConfig(long nativePtr, byte schemaMode, long private static native void nativeEnableChangeNotification(long nativePtr, boolean enableNotification); private static native String nativeCreateAndSetSyncConfig(long nativePtr, String syncRealmUrl, String authUrl, - String userId, String refreshToken, boolean isPartial, + String userId, String refreshToken, byte sessionStopPolicy, String urlPrefix, String customAuthorizationHeaderName, String[] customHeaders, byte clientResetMode); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index fe40a175fc..2fbfaff187 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -625,8 +625,8 @@ public void notifyChangeListeners(long nativeChangeSetPtr) { // Object Store compute the change set between the SharedGroup versions when the query created and the latest. // So it is possible it deliver a non-empty change set for the first async query returns. OsCollectionChangeSet changeset = (nativeChangeSetPtr == 0) - ? new EmptyLoadChangeSet(null, sharedRealm.isPartial()) - : new OsCollectionChangeSet(nativeChangeSetPtr, !isLoaded(), null, sharedRealm.isPartial()); + ? new EmptyLoadChangeSet() + : new OsCollectionChangeSet(nativeChangeSetPtr, !isLoaded()); // Happens e.g. if a synchronous query is created, a change listener is added and then // a transaction is started on the same thread. This will trigger all notifications diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 031471df59..2274f1c982 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -459,15 +459,7 @@ public void registerSchemaChangedCallback(SchemaChangedCallback callback) { } /** - * Returns {@code true} if this Realm is a query-based synchronized Realm. - */ - public boolean isPartial() { - return nativeIsPartial(nativePtr); - } - - /** - * Returns {@code true} if this Realm is a synchronized Realm, either query-based or fully - * synchronized. + * Returns {@code true} if this Realm is a synchronized Realm. */ public boolean isSyncRealm() { return osRealmConfig.getResolvedRealmURI() != null; @@ -639,8 +631,6 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare private static native int nativeGetObjectPrivileges(long nativePtr, long rowNativePtr); - private static native boolean nativeIsPartial(long nativePtr); - private static native boolean nativeIsFrozen(long nativePtr); private static native long nativeFreeze(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java b/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java index e3e5845669..cd036788c5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmObjectProxy.java @@ -16,6 +16,7 @@ package io.realm.internal; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.ProxyState; import io.realm.RealmModel; @@ -34,6 +35,7 @@ public interface RealmObjectProxy extends RealmModel { * Tuple class for saving meta data about a cached RealmObject. */ class CacheData { + @SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") public int minDepth; public final E object; diff --git a/realm/realm-library/src/main/java/io/realm/internal/StatefulCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/StatefulCollectionChangeSet.java index dae64a411e..ac73fd645e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/StatefulCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/StatefulCollectionChangeSet.java @@ -3,7 +3,6 @@ import javax.annotation.Nullable; import io.realm.OrderedCollectionChangeSet; -import io.realm.log.RealmLog; /** * A wrapper around {@link OsCollectionChangeSet} that makes it stateful with regard to how many @@ -18,7 +17,6 @@ public class StatefulCollectionChangeSet implements OrderedCollectionChangeSet { private final OrderedCollectionChangeSet changeset; private final Throwable error; private final State state; - private final boolean remoteDataSynchronized; /** * @param backingChangeset Underlying changeset backing this. @@ -28,8 +26,6 @@ public StatefulCollectionChangeSet(OsCollectionChangeSet backingChangeset) { // Calculate the state here since object is immutable boolean isInitial = backingChangeset.isFirstAsyncCallback(); - remoteDataSynchronized = backingChangeset.isRemoteDataLoaded(); - error = backingChangeset.getError(); if (error != null) { state = State.ERROR; @@ -79,9 +75,5 @@ public Throwable getError() { return error; } - @Override - public boolean isCompleteResult() { - return remoteDataSynchronized; - } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java b/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java deleted file mode 100644 index 7ba0cc06d5..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/SubscriptionAwareOsResults.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ - -package io.realm.internal; - -import io.realm.RealmChangeListener; -import io.realm.internal.core.DescriptorOrdering; -import io.realm.internal.sync.OsSubscription; -import io.realm.internal.sync.SubscriptionAction; - -/** - * Wrapper around Object Stores Results class that is capable of combining partial sync Subscription - * state updates and collection change updates. - */ -public class SubscriptionAwareOsResults extends OsResults { - - // The native ptr to a delayed notification. Since Java group all notifications for each - // RealmResults, only one change from OS will ever be sent. - private long delayedNotificationPtr = 0; - // If true, the subscription somehow changed during this round of notifications being sent - private boolean subscriptionChanged; - // Reference to a (potential) underlying subscription - private OsSubscription subscription = null; - private boolean collectionChanged = false; - private boolean firstCallback; - - public static SubscriptionAwareOsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery query, - DescriptorOrdering queryDescriptors, - SubscriptionAction subscriptionInfo) { - query.validateQuery(); - long ptr = nativeCreateResults(sharedRealm.getNativePtr(), query.getNativePtr(), queryDescriptors.getNativePtr()); - return new SubscriptionAwareOsResults(sharedRealm, query.getTable(), ptr, subscriptionInfo); - } - - SubscriptionAwareOsResults(OsSharedRealm sharedRealm, Table table, long nativePtr, SubscriptionAction subscriptionInfo) { - super(sharedRealm, table, nativePtr); - - this.firstCallback = true; - this.subscription = new OsSubscription(this, subscriptionInfo); - this.subscription.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(OsSubscription o) { - subscriptionChanged = true; - } - }); - RealmNotifier notifier = sharedRealm.realmNotifier; - notifier.addBeginSendingNotificationsCallback(new Runnable() { - @Override - public void run() { - subscriptionChanged = false; - collectionChanged = false; - delayedNotificationPtr = 0; - } - }); - notifier.addFinishedSendingNotificationsCallback(new Runnable() { - @Override - public void run() { - if (collectionChanged || subscriptionChanged) { - triggerDelayedChangeListener(); - } - } - }); - } - - private void triggerDelayedChangeListener() { - // Only parse on the subscription if it actually changed - OsSubscription subscription = (subscriptionChanged) ? this.subscription : null; - - // In case no collection listener was triggered, only trigger the listener if non-relevant - // changes happened to the subscription. In our case this means we only care about the - // errors and a completed subscription - if (delayedNotificationPtr == 0 - && subscription != null - && !firstCallback - && subscription.getState() != OsSubscription.SubscriptionState.ERROR - && subscription.getState() != OsSubscription.SubscriptionState.COMPLETE) { - return; - } - - OsCollectionChangeSet changeset; - if (delayedNotificationPtr == 0) { - changeset = new EmptyLoadChangeSet(subscription, firstCallback, true); - } else { - changeset = new OsCollectionChangeSet(delayedNotificationPtr, firstCallback, subscription, true); - } - - // Happens e.g. if a synchronous query is created, a change listener is added and then - // a transaction is started on the same thread. This will trigger all notifications - // and deliver an empty changeset. - if (changeset.isEmpty() && isLoaded()) { - return; - } - loaded = true; - firstCallback = false; - observerPairs.foreach(new Callback(changeset)); - } - - @Override - public void notifyChangeListeners(long nativeChangeSetPtr) { - collectionChanged = true; - delayedNotificationPtr = nativeChangeSetPtr; - } - -} - - diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 253bacaf14..ab6b2f8679 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -254,12 +254,10 @@ public boolean isEmpty() { /** * Clears the table i.e., deleting all rows in the table. - * - * If using partial sync, this method will behave similarly to 'findAll().deleteFromRealm()'. */ - public void clear(boolean partialRealm) { + public void clear() { checkImmutable(); - nativeClear(nativeTableRefPtr, partialRealm); + nativeClear(nativeTableRefPtr); } // Column Information. @@ -711,7 +709,7 @@ public static String getTableNameForClass(String name) { private native long nativeSize(long nativeTableRefPtr); - private native void nativeClear(long nativeTableRefPtr, boolean partialRealm); + private native void nativeClear(long nativeTableRefPtr); private native long nativeGetColumnCount(long nativeTableRefPtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java b/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java index 604b4efdb6..101871c4cd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java +++ b/realm/realm-library/src/main/java/io/realm/internal/core/DescriptorOrdering.java @@ -99,15 +99,6 @@ public void setLimit(long limit) { limitDefined = true; } - /** - * Add a linkingObject reference that should be fetched from the server. - * This only makes sense for Query-based Realms. It is up to callers of this method - * to ensure this. - */ - public void appendIncludes(IncludeDescriptor descriptor) { - nativeAppendInclude(nativePtr, descriptor.getNativePtr()); - } - /** * Returns true if no descriptors or limits have been added. */ @@ -121,7 +112,6 @@ public boolean isEmpty() { private static native void nativeAppendSort(long descriptorPtr, QueryDescriptor includeDescriptor); private static native void nativeAppendDistinct(long descriptorPtr, QueryDescriptor includeDescriptor); private static native void nativeAppendLimit(long descriptorPtr, long limit); - private static native void nativeAppendInclude(long descriptorPtr, long includeDescriptorPtr); private static native boolean nativeIsEmpty(long descriptorPtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/BaseModule.java b/realm/realm-library/src/main/java/io/realm/internal/sync/BaseModule.java deleted file mode 100644 index 91f1e68b7a..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/sync/BaseModule.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ - -package io.realm.internal.sync; - -import io.realm.annotations.RealmModule; - -// Workaround preventing `io.realm.DefaultRealmModuleMediator` being generated in the -// Realm JAR. Related to `https://github.com/realm/realm-java/issues/5799 -@RealmModule(library = true, allClasses = true) -public class BaseModule { -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java b/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java deleted file mode 100644 index 49313701e0..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/sync/OsSubscription.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ - -package io.realm.internal.sync; - -import javax.annotation.Nullable; - -import io.realm.RealmChangeListener; -import io.realm.internal.KeepMember; -import io.realm.internal.NativeObject; -import io.realm.internal.ObserverPairList; -import io.realm.internal.OsResults; - -@KeepMember -public class OsSubscription implements NativeObject { - - private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); - - // Mirrors the values in https://github.com/realm/realm-object-store/blob/master/src/sync/subscription_state.hpp - public enum SubscriptionState { - ERROR(-1), // An error occurred while creating or processing the partial sync subscription. - CREATING(2), // The subscription is being created. - PENDING(0), // The subscription was created, but has not yet been processed by the sync server. - COMPLETE(1), // The subscription has been processed by the sync server and data is being synced to the device. - INVALIDATED(3); // The subscription has been removed. - - private final int val; - - SubscriptionState(int val) { - this.val = val; - } - - public static SubscriptionState fromInternalValue(int val) { - for (SubscriptionState subscriptionState : values()) { - if (subscriptionState.val == val) { - return subscriptionState; - } - } - throw new IllegalArgumentException("Unknown value: " + val); - } - } - - private static class SubscriptionObserverPair - extends ObserverPairList.ObserverPair> { - public SubscriptionObserverPair(OsSubscription observer, RealmChangeListener listener) { - super(observer, listener); - } - - public void onChange(OsSubscription observer) { - listener.onChange(observer); - } - } - - private static class Callback implements ObserverPairList.Callback { - @Override - public void onCalled(SubscriptionObserverPair pair, Object observer) { - pair.onChange((OsSubscription) observer); - } - } - - private final long nativePtr; - protected final ObserverPairList observerPairs = new ObserverPairList<>(); - - public OsSubscription(OsResults results, SubscriptionAction subscriptionInfo) { - this.nativePtr = nativeCreateOrUpdate(results.getNativePtr(), subscriptionInfo.getName(), - subscriptionInfo.getTimeToLiveMs(), subscriptionInfo.isUpdate()); - } - - @Override - public long getNativePtr() { - return nativePtr; - } - - @Override - public long getNativeFinalizerPtr() { - return nativeFinalizerPtr; - } - - public SubscriptionState getState() { - return SubscriptionState.fromInternalValue(nativeGetState(nativePtr)); - } - - @Nullable - public Throwable getError() { - return (Throwable) nativeGetError(nativePtr); - } - - public void addChangeListener(RealmChangeListener listener) { - if (observerPairs.isEmpty()) { - nativeStartListening(nativePtr); - } - observerPairs.add(new SubscriptionObserverPair(this, listener)); - } - - public void removeChangeListener(RealmChangeListener listener) { - observerPairs.remove(this, listener); - if (observerPairs.isEmpty()) { - nativeStopListening(nativePtr); - } - } - - // Called from JNI - @KeepMember - private void notifyChangeListeners() { - observerPairs.foreach(new Callback()); - } - - private static native long nativeCreateOrUpdate(long resultsNativePtr, String subscriptionName, long timeToLiveMs, boolean update); - - private static native long nativeGetFinalizerPtr(); - - private static native int nativeGetState(long nativePtr); - - private static native Object nativeGetError(long nativePtr); - - private native void nativeStartListening(long nativePtr); - - private native void nativeStopListening(long nativePtr); - -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java b/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java deleted file mode 100644 index 4a6b91a988..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/sync/SubscriptionAction.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ - -package io.realm.internal.sync; - -import java.util.concurrent.TimeUnit; - -/** - * Wrapper class describing if and how a subscription should be created when creating a query result. - */ -public class SubscriptionAction { - public static final SubscriptionAction NO_SUBSCRIPTION = new SubscriptionAction(null, 0, false); - public static final SubscriptionAction ANONYMOUS_SUBSCRIPTION = new SubscriptionAction("", Long.MAX_VALUE, false); - - public static SubscriptionAction create(String subscriptionName, long timeToLiveMs) { - return new SubscriptionAction(subscriptionName, timeToLiveMs, false); - } - - public static SubscriptionAction update(String subscriptionName, long timeToLiveMs) { - return new SubscriptionAction(subscriptionName, timeToLiveMs, true); - } - - private final String subscriptionName; - private final long timeToLiveMs; - private final boolean update; - - public SubscriptionAction(String subscriptionName, long timeToLiveMs, boolean update) { - this.subscriptionName = subscriptionName; - this.timeToLiveMs = timeToLiveMs; - this.update = update; - } - - public boolean shouldCreateSubscriptions() { - return subscriptionName != null; - } - - public String getName() { - return subscriptionName; - } - - public long getTimeToLiveMs() { - return timeToLiveMs; - } - - public boolean isUpdate() { - return update; - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java b/realm/realm-library/src/main/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java deleted file mode 100644 index 3c97b1e8af..0000000000 --- a/realm/realm-library/src/main/java/io/realm/internal/sync/permissions/ObjectPermissionsModule.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.realm.internal.sync.permissions; - -import io.realm.annotations.RealmModule; -import io.realm.sync.Subscription; - -/** - * Realm model classes that are always part of Query-based Realms - */ -@RealmModule(library = true, classes = { - Subscription.class -}) -public class ObjectPermissionsModule { -} diff --git a/realm/realm-library/src/main/java/io/realm/sync/Subscription.java b/realm/realm-library/src/main/java/io/realm/sync/Subscription.java deleted file mode 100644 index a33a964fcd..0000000000 --- a/realm/realm-library/src/main/java/io/realm/sync/Subscription.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm.sync; - -import java.util.Date; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nullable; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.RealmObject; -import io.realm.RealmQuery; -import io.realm.annotations.Beta; -import io.realm.annotations.Index; -import io.realm.annotations.RealmClass; -import io.realm.annotations.RealmField; -import io.realm.annotations.Required; -import io.realm.internal.annotations.ObjectServer; - -/** - * Subscriptions represents the data from the server that a device is interested in when using - * Query-based Realms. - *

            - * They are created automatically when using {@link RealmQuery#findAllAsync()} or {@link RealmQuery#findAllAsync(String)} - * on those Realms, but can also be created manually using {@link RealmQuery#subscribe()} and {@link RealmQuery#subscribe(String)}. - *

            - * As long as any subscription exist that include an object, that object will be present on the - * device. If an object is not covered by an active subscription it will be removed from the device, - * but not the server. - *

            - * Subscriptions are Realm objects, so deleting them e.g. by calling {@link RealmObject#deleteFromRealm()}, - * is the same as calling {@link #unsubscribe()}. - *

            - * Warning: Instances of this class should never be created directly through - * {@link io.realm.Realm#createObject(Class)} but only by using {@link RealmQuery#subscribe()} or - * {@link RealmQuery#subscribe(String)}. - */ -@ObjectServer -@RealmClass(name = "__ResultSets") -@Beta -public class Subscription extends RealmObject { - - /** - * The different states a Subscription can be in. - */ - public enum State { - /** - * An error occurred while creating or processing the subscription. - * See {@link #getErrorMessage()} for details on what went wrong. - */ - ERROR((byte) -1), - - /** - * The subscription has been created, but has not yet been processed by the sync - * server. - */ - PENDING((byte) 0), - - /** - * The subscription has been processed by the Realm Object Server and data is being synced - * to the device. - */ - ACTIVE((byte) 1), - - /** - * The subscription has been removed. Data is no longer being synchronized from the Realm - * Object Server, and the objects covered by this subscription might be deleted from the - * device if no other subscriptions include them. - */ - INVALIDATED(null); - - - private final Byte nativeValue; - - State(Byte nativeValue) { - this.nativeValue = nativeValue; - } - - /** - * Returns the native value representing this state. - * - * @return the native value representing this state. - */ - public Byte getValue() { - return nativeValue; - } - } - - public Subscription() { - // Required by Realm. - } - - /** - * Creates a unmanaged named subscription from a {@link RealmQuery}. - * This will not take effect until it has been added to the Realm. - * - * @param name name of the query. - * @param query the query to turn into a subscription. - */ - public Subscription(String name, RealmQuery query) { - this.name = name; - this.query = query.getDescription(); - this.status = 0; - this.errorMessage = ""; - this.matchesProperty = ""; - } - - @Index - @Required - private String name; - - /** - * The underlying representation of the State - */ - private byte status; - - @Required - @RealmField("error_message") - private String errorMessage; - - @Required - @RealmField("matches_property") - private String matchesProperty; - - @Required - private String query; - - @RealmField("query_parse_counter") - private int queryParseCounter; - - /** - * Field indicating when this subscription was created. - */ - @Required - @RealmField("created_at") - private Date createdAt; - - /** - * Field indicating when this subscription was last used or updated. - *

            - * "Used" in this context means that someone resubscribed to the subscription. - *

            - * "Updated" means that someone updated the {@link #query} or some other field part of this class. - *

            - * This field is NOT updated whenever the results of the query changes. - *

            - * This field plus {@link #timeToLive} defines {@link #expiresAt}. - */ - @Required - @RealmField("updated_at") - private Date updatedAt; - - /** - * Field indicating when it is safe to delete this subscription. - *

            - * If {@code null} is returned, this subscription will live until manually deleted. - */ - @Nullable - @RealmField("expires_at") - private Date expiresAt; - - /** - * Field indicating for how long after last being used Realm must keep this subscription. After - * the TTL expires, Realm is allowed to remove the subscription. - *

            - * If {@code null} is returned, the subscription should live forever. - *

            - * This field plus {@link #updatedAt} defines {@link #expiresAt}. - */ - @Nullable - @RealmField("time_to_live") - private Long timeToLive; - - /** - * Returns the name of the subscription. - * - * @return the name of the subscription. - */ - public String getName() { - return name; - } - - /** - * Returns when this subscription was initially created. If {@code new Date(0)} is returned, - * it is unknown when the subscription was created. - * - * @return when this subscription was initially created. - */ - @SuppressFBWarnings({"EI_EXPOSE_REP"}) - public Date getCreatedAt() { - return createdAt; - } - - /** - * Returns when this subscription was last used or updated. - *

            - * "Used" in this context means that someone resubscribed to the subscription. - *

            - * "Updated" means that someone updated the {@link #query} or some other field part of this class. - *

            - * This field is NOT updated whenever the results of the query changes. - *

            - * This field plus {@link #timeToLive} defines {@link #expiresAt}. - * - * @return the point in time this subscription was last used or updated. - */ - @SuppressFBWarnings({"EI_EXPOSE_REP"}) - public Date getUpdatedAt() { - return updatedAt; - } - - /** - * Returns the point in time from which Realm can safely delete this subscription. This will - * happen automatically. - *

            - * Realm will attempt to cleanup expired subscriptions when the app is started or whenever - * any subscription is modified, there is no guarantee it will happen immediately after it - * expires. - * - * @return the point in time after which Realm can safely delete this subscription. - */ - @SuppressFBWarnings({"EI_EXPOSE_REP"}) - public Date getExpiresAt() { - if (expiresAt == null) { - return new Date(Long.MAX_VALUE); - } else { - return expiresAt; - } - } - - /** - * Returns for how long the subscription must be kept alive after last being used. The value - * returned are in milliseconds. - * - * @return in milliseconds, for how long the subscription must be kept alive after last being used. - */ - public long getTimeToLive() { - return (timeToLive != null) ? timeToLive : Long.MAX_VALUE; - } - - /** - * Sets the time-to-live in milliseconds for this subscription. This defines for how long Realm - * must keep the subscription alive after last being used. - * - * @param timeToLive for how long Realm must keep the subscription after last being used. - * @param timeUnit time unit for {@code timeToLive}. - * @throws IllegalArgumentException if a negative time-to-live or null timeUnit is provided. - */ - public void setTimeToLive(long timeToLive, TimeUnit timeUnit) { - if (timeToLive < 0) { - throw new IllegalArgumentException("A negative time-to-live is not allowed: " + timeToLive); - } - if (timeUnit == null) { - throw new IllegalArgumentException("Non-null 'timeUnit' required."); - } - this.updatedAt = new Date(System.currentTimeMillis()); - this.timeToLive = TimeUnit.MILLISECONDS.convert(timeToLive, timeUnit); - long expiryTime = this.updatedAt.getTime(); - if (expiryTime + this.timeToLive < expiryTime) { - expiryTime = Long.MAX_VALUE; // Clamp overflow to max - } else { - expiryTime = expiryTime + this.timeToLive; - } - this.expiresAt = new Date(expiryTime); - } - - /** - * Returns a textual description of the query that created this subscription. - * - * @return a textual description of the query. - */ - public String getQueryDescription() { - return query; - } - - /** - * Replaces the current query controlled by this subscription with a new query. - * - * @param query the query which should replace the current one. - */ - public void setQuery(RealmQuery query) { - if (query == null) { - throw new IllegalArgumentException("Non-null 'query' required"); - } - if (!query.getTypeQueried().equals(getQueryClassName())) { - throw new IllegalArgumentException(String.format("It is only allowed to replace a query with another query on the same type." + - "Existing query: '%s'. New query: '%s'", getQueryClassName(), query.getTypeQueried())); - } - this.query = query.getDescription(); - this.updatedAt = new Date(); - } - - /** - * Returns the internal name of the Class being queried. - * - * @return the internal name of the of the class being queried. - */ - public String getQueryClassName() { - // Strip the __matches suffix to end up with the class being queried. - String classQueried = matchesProperty; - return classQueried.substring(0, classQueried.length() - "_matches".length()); - } - - /** - * Returns the state of the subscription - * - * @return the state of the subscription. - * @see State - */ - public State getState () { - if (!RealmObject.isValid(this)) { - return State.INVALIDATED; - } else { - switch (status) { - case -1: - return State.ERROR; - case 0: - return State.PENDING; - case 1: - return State.ACTIVE; - default: - throw new IllegalArgumentException("Unknown subscription state value: " + status); - } - } - } - - /** - * Returns the error message if {@link #getState()} returned {@link State#ERROR}, otherwise - * the empty string is returned. - * - * @return the error string if the subscription encountered an error. - */ - public String getErrorMessage() { - return errorMessage; - } - - /** - * Cancels the subscription. After this, if the objects covered by the subscription are not - * part of any other subscription, they will be removed locally from the device (but not on the - * server). - *

            - * The effect of unsubscribing is not immediate. The local Realm must coordinate with the Realm - * Object Server before it can happen. When it happens, any objects removed will trigger a standard - * change notification, and from the perspective of the device it will look like they where - * deleted. - *

            - * Calling this method is the equivalent of calling {@link RealmObject#deleteFromRealm()}. - * - * @throws IllegalStateException if the Realm is not in a write transaction. - */ - public void unsubscribe() { - RealmObject.deleteFromRealm(this); - } - - @Override - public String toString() { - return "Subscription{" + - "name='" + name + '\'' + - ", status=" + status + - ", errorMessage='" + errorMessage + '\'' + - ", query='" + query + '\'' + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - ", expiresAt=" + expiresAt + - ", timeToLive=" + timeToLive + - '}'; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java b/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java index d94f7c40f2..75c28c796e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java @@ -32,17 +32,12 @@ public enum ClientResyncMode { /** * Realm will compare the local Realm with the Realm on the server and automatically transfer * any changes from the local Realm that makes sense to the Realm provided by the server. - *

            - * This is the default mode for fully synchronized Realms. It is not yet supported by - * Query-based Realms. */ RECOVER_LOCAL_REALM(OsRealmConfig.CLIENT_RESYNC_MODE_RECOVER), /** * The local Realm will be discarded and replaced with the server side Realm. * All local changes will be lost. - *

            - * This mode is not yet supported by Query-based Realms. */ DISCARD_LOCAL_REALM(OsRealmConfig.CLIENT_RESYNC_MODE_DISCARD), @@ -53,8 +48,6 @@ public enum ClientResyncMode { * {@link io.realm.SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError)}, triggering * a Client Reset. Doing this provides a handle to both the old and new Realm file, enabling * full control of which changes to move, if any. - *

            - * This is the only supported mode for Query-based Realms. * * @see io.realm.SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more * information about when and why Client Reset occurs and how to deal with it. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 48867a013c..c77781e2c3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -42,7 +42,6 @@ import io.realm.log.RealmLog; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; /** * A {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm @@ -59,26 +58,6 @@ * SyncConfiguration config = new SyncConfiguration.Builder(user, url).build(); * } * - * - * Synchronized Realms come in two forms: - *

              - *
            • - * Query-based synchronization: - * This is the default mode. The Realm will only synchronize data you have queried for. - * This means the Realm on the device is initially empty and will gradually fill up as - * you start to query for data. This is useful if the server side Realm is too large - * to fit on the device or contains data from multiple users. Data synchronized this way - * can also be removed from the device again without being deleted on the server. - *
            • - *
            • - * Full synchronization - * Enable this mode by setting {@link Builder#fullSynchronization()}. In this mode - * the entire Realm is synchronized in the background without having to query for - * data first. This means that data generally will be available quicker but should only - * be used if the server side Realm is small and doesn't contain data the device is not - * allowed to see. - *
            • - *
            *

            * Synchronized Realms only support additive migrations which can be detected and performed automatically, so * the following builder options are not accessible compared to a normal Realm: @@ -112,7 +91,6 @@ public class SyncConfiguration extends RealmConfiguration { private final boolean waitForInitialData; private final long initialDataTimeoutMillis; private final OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy; - private final boolean isPartial; @Nullable private final String syncUrlPrefix; private final ClientResyncMode clientResyncMode; @@ -140,7 +118,6 @@ private SyncConfiguration(File directory, boolean waitForInitialData, long initialDataTimeoutMillis, OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy, - boolean isPartial, CompactOnLaunchCallback compactOnLaunch, @Nullable String syncUrlPrefix, ClientResyncMode clientResyncMode) { @@ -172,7 +149,6 @@ private SyncConfiguration(File directory, this.waitForInitialData = waitForInitialData; this.initialDataTimeoutMillis = initialDataTimeoutMillis; this.sessionStopPolicy = sessionStopPolicy; - this.isPartial = isPartial; this.syncUrlPrefix = syncUrlPrefix; this.clientResyncMode = clientResyncMode; } @@ -257,7 +233,6 @@ public boolean equals(Object o) { if (syncClientValidateSsl != that.syncClientValidateSsl) return false; if (waitForInitialData != that.waitForInitialData) return false; if (initialDataTimeoutMillis != that.initialDataTimeoutMillis) return false; - if (isPartial != that.isPartial) return false; if (!serverUrl.equals(that.serverUrl)) return false; if (!user.equals(that.user)) return false; if (!errorHandler.equals(that.errorHandler)) return false; @@ -284,7 +259,6 @@ public int hashCode() { result = 31 * result + (waitForInitialData ? 1 : 0); result = 31 * result + (int) (initialDataTimeoutMillis ^ (initialDataTimeoutMillis >>> 32)); result = 31 * result + sessionStopPolicy.hashCode(); - result = 31 * result + (isPartial ? 1 : 0); result = 31 * result + (syncUrlPrefix != null ? syncUrlPrefix.hashCode() : 0); result = 31 * result + clientResyncMode.hashCode(); return result; @@ -314,8 +288,6 @@ public String toString() { sb.append("\n"); sb.append("sessionStopPolicy: ").append(sessionStopPolicy); sb.append("\n"); - sb.append("isPartial: ").append(isPartial); - sb.append("\n"); sb.append("syncUrlPrefix: ").append(syncUrlPrefix); sb.append("\n"); sb.append("clientResyncMode: ").append(clientResyncMode); @@ -428,15 +400,6 @@ public OsRealmConfig.SyncSessionStopPolicy getSessionStopPolicy() { return sessionStopPolicy; } - /** - * Returns whether this configuration is for a fully synchronized Realm or not. - * - * @see Builder#fullSynchronization() for more details. - */ - public boolean isFullySynchronizedRealm() { - return !isPartial; - } - /** * Returns the url prefix used when establishing a sync connection to the Realm Object Server. */ @@ -488,7 +451,6 @@ public static final class Builder { @Nullable private String serverCertificateFilePath; private OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy = OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED; - private boolean isPartial = true; // Partial Synchronization is enabled by default private CompactOnLaunchCallback compactOnLaunch; private String syncUrlPrefix = null; @Nullable // null means the user hasn't explicitly set one. An appropriate default is chosen when calling build() @@ -955,19 +917,6 @@ public SyncConfiguration.Builder readOnly() { return this; } - /** - * Define this Realm as a fully synchronized Realm. - *

            - * Full synchronization, unlike the default query-based synchronization, will transparently - * synchronize the entire Realm without needing to query for the data. This option is - * useful if the serverside Realm is small and all the data in the Realm should be - * available to the user. - */ - public SyncConfiguration.Builder fullSynchronization() { - this.isPartial = false; - return this; - } - /** * Setting this will cause Realm to compact the Realm file if the Realm file has grown too large and a * significant amount of space can be recovered. See {@link DefaultCompactOnLaunchCallback} for details. @@ -1113,10 +1062,7 @@ public SyncConfiguration build() { // Set the default Client Resync Mode based on the current type of Realm. // Eventually RECOVER_LOCAL_REALM should be the default for all types. if (clientResyncMode == null) { - clientResyncMode = (isPartial) ? ClientResyncMode.MANUAL : ClientResyncMode.RECOVER_LOCAL_REALM; - } - if (isPartial && clientResyncMode != ClientResyncMode.MANUAL) { - throw new IllegalStateException("Query-based sync only supports manual Client Resync. It was: " + clientResyncMode); + clientResyncMode = ClientResyncMode.RECOVER_LOCAL_REALM; } if (rxFactory == null && isRxJavaAvailable()) { @@ -1178,11 +1124,6 @@ public SyncConfiguration build() { } } - // If query based sync is enabled, also add support for Object Level Permissions - if (isPartial) { - addModule(new ObjectPermissionsModule()); - } - return new SyncConfiguration( // Realm Configuration options realmFileDirectory, @@ -1211,7 +1152,6 @@ public SyncConfiguration build() { waitForServerChanges, initialDataTimeoutMillis, sessionStopPolicy, - isPartial, compactOnLaunch, syncUrlPrefix, clientResyncMode diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 10c8b4dee4..6bf75d4da8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -224,9 +224,6 @@ public SyncUser run() throws ObjectServerError { * Opening a synchronized Realm requires a {@link SyncConfiguration}. This method creates a * {@link SyncConfiguration.Builder} that can be used to create it by calling {@link SyncConfiguration.Builder#build()}. *

            - * The default synchronization mode for this Realm is query-based synchronizaton, - * but see the {@link SyncConfiguration.Builder} class for more details on how to configure a Realm. - *

            * A synchronized Realm is identified by an unique URI. In the URI, {@code /~/} can be used as a placeholder for * a user ID in case the Realm should only be available to one user e.g., {@code "realm://objectserver.realm.io/~/default"}. *

            @@ -257,7 +254,7 @@ public SyncConfiguration.Builder createConfiguration(String uri) { /** * Returns the default configuration for this user. The default configuration points to the - * default query-based Realm on the server the user authenticated against. + * default Realm on the server the user authenticated against. * * @return the default configuration for this user. * @throws IllegalStateException if the user isn't valid. See {@link #isValid()}. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 84b6352529..062c843f3d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -23,24 +23,18 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Map; import java.util.concurrent.TimeUnit; -import io.realm.Realm; import io.realm.RealmConfiguration; -import io.realm.RealmResults; import io.realm.SyncConfiguration; import io.realm.SyncManager; -import io.realm.SyncSession; import io.realm.SyncUser; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.objectstore.OsAsyncOpenTask; -import io.realm.sync.Subscription; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; @SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep @@ -113,14 +107,13 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath(), sessionStopPolicy, - !syncConfig.isFullySynchronizedRealm(), urlPrefix, customAuthorizationHeaderName, customHeaders, syncConfig.getClientResyncMode().getNativeValue() }; } else { - return new Object[12]; + return new Object[11]; } } @@ -188,11 +181,7 @@ public void downloadInitialRemoteChanges(RealmConfiguration config) { if (new AndroidCapabilities().isMainThread()) { throw new IllegalStateException("waitForInitialRemoteData() cannot be used synchronously on the main thread. Use Realm.getInstanceAsync() instead."); } - if (syncConfig.isFullySynchronizedRealm()) { - downloadInitialFullRealm(syncConfig); - } else { - downloadInitialQueryBasedRealm(syncConfig); - } + downloadInitialFullRealm(syncConfig); } } } @@ -206,91 +195,11 @@ private void downloadInitialFullRealm(SyncConfiguration syncConfig) { } } - private void downloadInitialQueryBasedRealm(SyncConfiguration syncConfig) { - if (syncConfig.shouldWaitForInitialRemoteData()) { - SyncSession session = SyncManager.getSession(syncConfig); - try { - long timeoutMillis = syncConfig.getInitialRemoteDataTimeout(TimeUnit.MILLISECONDS); - if (!syncConfig.isFullySynchronizedRealm()) { - // For Query-based Realms we want to upload all our local changes - // first since those might include subscriptions the server needs to process. - // This means that once `downloadAllServerChanges` completes, all initial - // subscriptions will also have been downloaded. - // - // Note that we are reusing the same timeout for uploading and downloading. - // This means that in the worst case you end up with 2x the timeout for - // Query-based Realms. This is probably an acceptable trade-of as trying - // to expose this would not only complicate the API surface quite a lot, - // but in most (almost all?) cases the amount of data to upload will be trivial. - if (!session.uploadAllLocalChanges(timeoutMillis, TimeUnit.MILLISECONDS)) { - throw new DownloadingRealmInterruptedException(syncConfig, "Failed to first upload local changes in " + timeoutMillis + " milliseconds"); - }; - } - if (!session.downloadAllServerChanges(timeoutMillis, TimeUnit.MILLISECONDS)) { - throw new DownloadingRealmInterruptedException(syncConfig, "Failed to download remote changes in " + timeoutMillis + " milliseconds"); - } - } catch (InterruptedException e) { - throw new DownloadingRealmInterruptedException(syncConfig, e); - } - } - } - @Override public boolean wasDownloadInterrupted(Throwable throwable) { return (throwable instanceof DownloadingRealmInterruptedException); } - @Override - public boolean isPartialRealm(RealmConfiguration configuration) { - if (configuration instanceof SyncConfiguration) { - SyncConfiguration syncConfig = (SyncConfiguration) configuration; - return !syncConfig.isFullySynchronizedRealm(); - } - - return false; - } - - @Override - public void addSupportForObjectLevelPermissions(RealmConfiguration.Builder builder) { - builder.addModule(new ObjectPermissionsModule()); - } - - @Override - public void downloadInitialSubscriptions(Realm realm) { - if (isPartialRealm(realm.getConfiguration())) { - SyncConfiguration syncConfig = (SyncConfiguration) realm.getConfiguration(); - if (syncConfig.shouldWaitForInitialRemoteData()) { - RealmResults pendingSubscriptions = realm.where(Subscription.class) - .equalTo("status", Subscription.State.PENDING.getValue()) - .findAll(); - SyncSession session = SyncManager.getSession(syncConfig); - - // Continue once all subscriptions are either ACTIVE or ERROR'ed. - while (!pendingSubscriptions.isEmpty()) { - try { - session.uploadAllLocalChanges(); // Uploads subscriptions (if any) - session.downloadAllServerChanges(); // Download subscriptions (if any) - } catch (InterruptedException e) { - throw new DownloadingRealmInterruptedException(syncConfig, e); - } - realm.refresh(); - } - - // If some of the subscriptions failed to become ACTIVE, report them and cancel opening - // the Realm. Note, this should only happen if the client is contacting an older - // version of the server which are lacking query support for features available - // in the client SDK. - RealmResults failedSubscriptions = realm.where(Subscription.class) - .equalTo("status", Subscription.State.ERROR.getValue()) - .findAll(); - if (!failedSubscriptions.isEmpty()) { - String errorMessage = "Some initial subscriptions encountered errors:" + Arrays.toString(failedSubscriptions.toArray()); - throw new DownloadingRealmInterruptedException(syncConfig, errorMessage); - } - } - } - } - @Override public void createNativeSyncSession(RealmConfiguration configuration) { if (configuration instanceof SyncConfiguration) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index d90db96404..0c9227a59a 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -29,7 +29,6 @@ import io.realm.internal.Util; import io.realm.objectserver.utils.HttpUtils; import io.realm.rule.RunInLooperThread; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; /** @@ -94,7 +93,7 @@ protected static class ConfigurationWrapper { public SyncConfiguration.Builder createSyncConfigurationBuilder(SyncUser user, String url) { return user.createConfiguration(url) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .modules(Realm.getDefaultModule(), new ObjectPermissionsModule()) + .modules(Realm.getDefaultModule()) .directory(looperThread.getRoot()); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index 19817ab1b4..d2f8fdeb71 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -60,7 +60,6 @@ public void trustedRootCA() throws InterruptedException { // 1. Copy a valid Realm to the server //noinspection unchecked final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() .schema(StringOnly.class) .build(); Realm realm = Realm.getInstance(syncConfig); @@ -79,7 +78,6 @@ public void trustedRootCA() throws InterruptedException { user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .fullSynchronization() .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() @@ -107,7 +105,6 @@ public void withoutSSLVerification() throws InterruptedException { // 1. Copy a valid Realm to the server //noinspection unchecked final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() .schema(StringOnly.class) .build(); Realm realm = Realm.getInstance(syncConfig); @@ -126,7 +123,6 @@ public void withoutSSLVerification() throws InterruptedException { user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .fullSynchronization() .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() @@ -246,7 +242,6 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt // 1. Copy a valid Realm to the server using ssl_verify_path option //noinspection unchecked final SyncConfiguration syncConfigWithCertificate = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .fullSynchronization() .schema(StringOnly.class) .trustedRootCA("trusted_ca.pem") .build(); @@ -266,7 +261,6 @@ public void combiningTrustedRootCA_and_disableSSLVerification() throws Interrupt user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); //noinspection unchecked SyncConfiguration syncConfigDisableSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .fullSynchronization() .name("useSsl") .schema(StringOnly.class) .waitForInitialRemoteData() @@ -299,7 +293,6 @@ public void sslVerifyCallback_isUsed() throws InterruptedException { // 1. Copy a valid Realm to the server using ssl_verify_path option //noinspection unchecked final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() .schema(StringOnly.class) .build(); Realm realm = Realm.getInstance(syncConfig); @@ -319,7 +312,6 @@ public void sslVerifyCallback_isUsed() throws InterruptedException { //noinspection unchecked SyncConfiguration syncConfigSecure = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) .name("useSsl") - .fullSynchronization() .schema(StringOnly.class) .waitForInitialRemoteData() .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index bebb220ac0..a24fabbcd8 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -122,7 +122,6 @@ public void getState_loggedOut() { SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .fullSynchronization() .build(); Realm realm = Realm.getInstance(syncConfiguration); @@ -142,11 +141,9 @@ public void uploadDownloadAllChanges() throws InterruptedException { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); SyncConfiguration userConfig = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .fullSynchronization() .build(); SyncConfiguration adminConfig = configFactory .createSyncConfigurationBuilder(adminUser, userConfig.getServerUrl().toString()) - .fullSynchronization() .build(); Realm userRealm = Realm.getInstance(userConfig); @@ -169,11 +166,9 @@ public void interruptWaits() throws InterruptedException { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); final SyncConfiguration userConfig = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .fullSynchronization() .build(); final SyncConfiguration adminConfig = configFactory .createSyncConfigurationBuilder(adminUser, userConfig.getServerUrl().toString()) - .fullSynchronization() .build(); Thread t = new Thread(new Runnable() { @@ -282,7 +277,6 @@ public void logBackResumeUpload() throws InterruptedException { final SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .build(); @@ -325,7 +319,6 @@ public void run() { SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) .modules(new StringOnlyModule()) - .fullSynchronization() .waitForInitialRemoteData() .build(); final Realm adminRealm = Realm.getInstance(adminConfig); @@ -376,7 +369,6 @@ public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { final SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .fullSynchronization() .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED) .modules(new StringOnlyModule()) .build(); @@ -403,7 +395,6 @@ public void run() { SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(admin, syncConfiguration.getServerUrl().toString()) - .fullSynchronization() .modules(new StringOnlyModule()) .build(); final Realm adminRealm = Realm.getInstance(adminConfig); @@ -442,7 +433,6 @@ public void downloadChangesWhenRealmOutOfScope() throws InterruptedException { final SyncConfiguration syncConfiguration = configFactory .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .fullSynchronization() .modules(new StringOnlyModule()) .build(); Realm realm = Realm.getInstance(syncConfiguration); @@ -477,7 +467,6 @@ public void run() { SyncUser adminUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL); SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) - .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .build(); @@ -522,7 +511,6 @@ public void clientReset_manualTriggerAllowSessionToRestart() { final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .clientResyncMode(ClientResyncMode.MANUAL) .directory(looperThread.getRoot()) - .fullSynchronization() .errorHandler(new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 5995ae7beb..fad0605944 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -32,6 +32,7 @@ import io.realm.entities.AllTypes; import io.realm.entities.StringOnly; import io.realm.exceptions.DownloadingRealmInterruptedException; +import io.realm.exceptions.RealmMigrationNeededException; import io.realm.internal.OsRealmConfig; import io.realm.log.LogLevel; import io.realm.log.RealmLog; @@ -60,7 +61,6 @@ public void loginLogoutResumeSyncing() throws InterruptedException { SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) .schema(StringOnly.class) - .fullSynchronization() .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); @@ -85,7 +85,6 @@ public void loginLogoutResumeSyncing() throws InterruptedException { user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM) - .fullSynchronization() .schema(StringOnly.class) .build(); @@ -102,7 +101,6 @@ public void loginLogoutResumeSyncing() throws InterruptedException { public void waitForInitialRemoteData_mainThreadThrows() { final SyncUser user = SyncTestUtils.createTestUser(Constants.AUTH_URL); SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .fullSynchronization() .waitForInitialRemoteData() .build(); @@ -126,7 +124,6 @@ public void waitForInitialRemoteData() throws InterruptedException { // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() .schema(StringOnly.class) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); @@ -148,7 +145,6 @@ public void execute(Realm realm) { user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) .name("newRealm") - .fullSynchronization() .schema(StringOnly.class) .waitForInitialRemoteData() .build(); @@ -248,7 +244,6 @@ public void waitForInitialRemoteData_readOnlyTrue() throws InterruptedException // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() .schema(StringOnly.class) .build(); Realm realm = Realm.getInstance(configOld); @@ -269,7 +264,6 @@ public void execute(Realm realm) { user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .name("newRealm") - .fullSynchronization() .waitForInitialRemoteData() .readOnly() .schema(StringOnly.class) @@ -299,7 +293,7 @@ public void waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { // schema. realm = Realm.getInstance(configNew); fail(); - } catch (IllegalStateException ignored) { + } catch (RealmMigrationNeededException ignore) { } finally { if (realm != null) { realm.close(); @@ -328,6 +322,7 @@ public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { } } + @Ignore("FIXME: Re-enable this once we can test againt a proper Stitch server") @Test public void defaultRealm() throws InterruptedException { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); @@ -422,7 +417,7 @@ public void syncAuthHeaderAndUrlPrefix_specificHost() { private void runSyncAuthHeadersAndUrlPrefixTest() { SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM) + SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .urlPrefix("/foo") .errorHandler(new SyncSession.ErrorHandler() { @Override @@ -432,11 +427,10 @@ public void onError(SyncSession session, ObjectServerError error) { }) .build(); - AtomicBoolean headersSet = new AtomicBoolean(false); RealmLog.setLevel(LogLevel.ALL); RealmLogger logger = (level, tag, throwable, message) -> { if (tag.equals("REALM_SYNC") - && message.contains("GET /foo/%2Fdefault%2F__partial%") + && message.contains("GET /foo/") && message.contains("TestAuth: Realm-Access-Token version=1") && message.contains("Test: test")) { looperThread.testComplete(); @@ -450,34 +444,6 @@ public void onError(SyncSession session, ObjectServerError error) { looperThread.closeAfterTest(realm); } - - /** - * Tests https://github.com/realm/realm-java/issues/6235 - * This checks that the INITIAL callback is called for query-based notifications even when - * the device is offline. - */ - @Test - @RunTestInLooperThread - public void listenersTriggerWhenOffline() { - SyncUser user = SyncTestUtils.createTestUser(); // Creating a fake user will make it behave as "offline" - String url = "http://foo.com/offlineListeners"; - SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, url) - .build(); - Realm realm = Realm.getInstance(config); - looperThread.closeAfterTest(realm); - - RealmResults results = realm.where(AllTypes.class).findAllAsync(); - - looperThread.keepStrongReference(results); - results.addChangeListener((objects, changeSet) -> { - if(changeSet.getState() == OrderedCollectionChangeSet.State.INITIAL) { - assertTrue(results.isLoaded()); - assertFalse(changeSet.isCompleteResult()); - looperThread.testComplete(); - } - }); - } - @Test @RunTestInLooperThread public void progressListenersWorkWhenUsingWaitForInitialRemoteData() throws InterruptedException { @@ -487,7 +453,6 @@ public void progressListenersWorkWhenUsingWaitForInitialRemoteData() throws Inte // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() .schema(StringOnly.class) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build(); @@ -510,7 +475,6 @@ public void execute(Realm realm) { user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) .name("newRealm") - .fullSynchronization() .schema(StringOnly.class) .waitForInitialRemoteData() .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index 23c15d1543..cbf0615b2d 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -45,7 +45,6 @@ public void setEncryptionKey_canReOpenRealmWithoutKey() throws InterruptedExcept final byte[] randomKey = TestHelper.getRandomKey(); SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .errorHandler(new SyncSession.ErrorHandler() { @@ -73,7 +72,6 @@ public void onError(SyncSession session, ObjectServerError error) { // fail user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); SyncConfiguration configWithoutEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() .name("newName") .modules(new StringOnlyModule()) .waitForInitialRemoteData() @@ -162,7 +160,6 @@ public void setEncryptionKey_differentClientsWithDifferentKeys() throws Interrup final byte[] randomKey = TestHelper.getRandomKey(); SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .errorHandler(new SyncSession.ErrorHandler() { @@ -193,7 +190,6 @@ public void onError(SyncSession session, ObjectServerError error) { final byte[] adminRandomKey = TestHelper.getRandomKey(); SyncConfiguration adminConfigWithEncryption = configurationFactory.createSyncConfigurationBuilder(adminUser, configWithEncryption.getServerUrl().toString()) - .fullSynchronization() .modules(new StringOnlyModule()) .waitForInitialRemoteData() .errorHandler(new SyncSession.ErrorHandler() { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index e20642aa6c..9651cdf4ce 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -74,7 +74,6 @@ protected void run() { String realmUrl = Constants.SYNC_SERVER_URL; final SyncConfiguration syncConfig = user.createConfiguration(realmUrl) - .fullSynchronization() .modules(new ProcessCommitTestsModule()) .directory(getService().getRoot()) .build(); @@ -126,7 +125,6 @@ public void expectSimpleCommit() { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; final SyncConfiguration syncConfig = user.createConfiguration(realmUrl) - .fullSynchronization() .modules(new ProcessCommitTestsModule()) .directory(looperThread.getRoot()) .build(); @@ -160,7 +158,6 @@ protected void run() { String realmUrl = Constants.SYNC_SERVER_URL; final SyncConfiguration syncConfig = user.createConfiguration(realmUrl) - .fullSynchronization() .modules(new ProcessCommitTestsModule()) .directory(getService().getRoot()) .name(UUID.randomUUID().toString() + ".realm") @@ -208,7 +205,6 @@ public void expectALot() throws Throwable { final SyncUser user = UserFactory.getInstance().createDefaultUser(Constants.AUTH_URL); String realmUrl = Constants.SYNC_SERVER_URL; final SyncConfiguration syncConfig = user.createConfiguration(realmUrl) - .fullSynchronization() .modules(new ProcessCommitTestsModule()) .directory(looperThread.getRoot()) .build(); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index 82da398c07..5f585473ad 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -63,9 +63,7 @@ public class ProgressListenerTests extends StandardIntegrationTest { @Nonnull private SyncConfiguration createSyncConfig() { SyncUser user = UserFactory.createAdminUser(Constants.AUTH_URL); - return configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .fullSynchronization() - .build(); + return configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL).build(); } private void writeSampleData(Realm realm) { @@ -139,13 +137,11 @@ public void downloadProgressListener_changesOnly() { final CountDownLatch allChangesDownloaded = new CountDownLatch(1); SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); SyncConfiguration userWithDataConfig = configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM) - .fullSynchronization() .build(); URI serverUrl = createRemoteData(userWithDataConfig); SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()) - .fullSynchronization() .build(); Realm realm = Realm.getInstance(config); SyncSession session = SyncManager.getSession(config); @@ -171,7 +167,6 @@ public void downloadProgressListener_indefinitely() throws InterruptedException final SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); final SyncConfiguration userWithDataConfig = configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM) .name("remote") - .fullSynchronization() .build(); URI serverUrl = createRemoteData(userWithDataConfig); @@ -190,7 +185,6 @@ public void run() { SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); final SyncConfiguration adminConfig = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()) .name("local") - .fullSynchronization() .build(); Realm adminRealm = Realm.getInstance(adminConfig); SyncSession session = SyncManager.getSession(adminConfig); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java deleted file mode 100644 index 3585330c64..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/QueryBasedSyncTests.java +++ /dev/null @@ -1,354 +0,0 @@ -package io.realm.objectserver; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.realm.DynamicRealm; -import io.realm.OrderedCollectionChangeSet; -import io.realm.Realm; -import io.realm.RealmChangeListener; -import io.realm.RealmList; -import io.realm.RealmResults; -import io.realm.StandardIntegrationTest; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncTestUtils; -import io.realm.SyncUser; -import io.realm.entities.AllJavaTypes; -import io.realm.entities.AllTypes; -import io.realm.entities.Dog; -import io.realm.objectserver.model.PartialSyncModule; -import io.realm.objectserver.model.PartialSyncObjectA; -import io.realm.objectserver.model.PartialSyncObjectB; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.RunTestInLooperThread; - -import static org.hamcrest.number.OrderingComparison.greaterThan; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class QueryBasedSyncTests extends StandardIntegrationTest { - - private static final int TEST_SIZE = 10; - - @Test - @RunTestInLooperThread - public void invalidQuery() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - final Realm realm = Realm.getInstance(partialSyncConfig); - looperThread.closeAfterTest(realm); - - // Backlinks not yet supported: https://github.com/realm/realm-core/pull/2947 - RealmResults query = realm.where(AllJavaTypes.class).equalTo("objectParents.fieldString", "Foo").findAllAsync(); - query.addChangeListener((results, changeSet) -> { - if (changeSet.getState() == OrderedCollectionChangeSet.State.ERROR) { - assertTrue(changeSet.getError() instanceof IllegalArgumentException); - Throwable iae = changeSet.getError(); - assertTrue(iae.getMessage().contains("Querying over backlinks is disabled but backlinks were found")); - looperThread.testComplete(); - } - }); - looperThread.keepStrongReference(query); - } - - // List queries are operating on data that are always up to date as data in a list will - // always be fetched as part of another top-level subscription. Thus `remoteDataLoaded` is - // always true and no queries on them can fail. - @Test - @RunTestInLooperThread - public void listQueries_doNotCreateSubscriptions() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - - final DynamicRealm dRealm = DynamicRealm.getInstance(partialSyncConfig); - final Realm realm = Realm.getInstance(partialSyncConfig); - looperThread.closeAfterTest(dRealm); - looperThread.closeAfterTest(realm); - - realm.beginTransaction(); - RealmList list = realm.createObject(AllTypes.class).getColumnRealmList(); - list.add(new Dog("Fido")); - list.add(new Dog("Eido")); - realm.commitTransaction(); - - RealmResults query = list.where().sort("name").findAllAsync(); - query.addChangeListener((dogs, changeSet) -> { - assertEquals(OrderedCollectionChangeSet.State.INITIAL, changeSet.getState()); - assertEquals(0, dRealm.where("__ResultSets").count()); - looperThread.testComplete(); - }); - looperThread.keepStrongReference(query); - } - - @Test - @RunTestInLooperThread - public void anonymousSubscription() throws InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - createServerData(user, Constants.SYNC_SERVER_URL); - - // Download data in partial Realm - final Realm partialSyncRealm = getPartialRealm(user); - looperThread.closeAfterTest(partialSyncRealm); - assertTrue(partialSyncRealm.isEmpty()); - - RealmResults results = partialSyncRealm.where(PartialSyncObjectA.class) - .greaterThan("number", 5) - .findAllAsync(); - looperThread.keepStrongReference(results); - - results.addChangeListener((partialSyncObjectAS, changeSet) -> { - if (changeSet.isCompleteResult()) { - if (results.size() == 4) { - for (PartialSyncObjectA object : results) { - assertThat(object.getNumber(), greaterThan(5)); - assertEquals("partial", object.getString()); - } - // make sure the Realm contains only PartialSyncObjectA - assertEquals(0, partialSyncRealm.where(PartialSyncObjectB.class).count()); - looperThread.testComplete(); - } - } - }); - } - - @Test - @RunTestInLooperThread - public void namedSubscription() throws InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - createServerData(user, Constants.SYNC_SERVER_URL); - - // Download data in partial Realm - final Realm partialSyncRealm = getPartialRealm(user); - looperThread.closeAfterTest(partialSyncRealm); - assertTrue(partialSyncRealm.isEmpty()); - - RealmResults results = partialSyncRealm.where(PartialSyncObjectA.class) - .greaterThan("number", 5) - .findAllAsync("my-subscription-id"); - looperThread.keepStrongReference(results); - - results.addChangeListener((partialSyncObjectAS, changeSet) -> { - if (changeSet.isCompleteResult()) { - if (results.size() == 4) { - for (PartialSyncObjectA object : results) { - assertThat(object.getNumber(), greaterThan(5)); - assertEquals("partial", object.getString()); - } - // make sure the Realm contains only PartialSyncObjectA - assertEquals(0, partialSyncRealm.where(PartialSyncObjectB.class).count()); - looperThread.testComplete(); - } - } - }); - - } - - @Test - @RunTestInLooperThread - public void partialSync_namedSubscriptionThrowsOnNonPartialRealms() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - final SyncConfiguration fullSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .fullSynchronization() - .name("fullySynchronizedRealm") - .build(); - - Realm realm = Realm.getInstance(fullSyncConfig); - looperThread.closeAfterTest(realm); - - try { - realm.where(PartialSyncObjectA.class).findAllAsync("my-id"); - fail(); - } catch (IllegalStateException ignore) { - looperThread.testComplete(); - } - } - - @Test - @RunTestInLooperThread - public void partialSync_namedSubscription_namedConflictThrows() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - - RealmResults results1 = realm.where(PartialSyncObjectA.class) - .findAllAsync("my-id"); - results1.addChangeListener((results, changeSet) -> { - // Ignore. Just used to trigger partial sync path - }); - - RealmResults results2 = realm.where(PartialSyncObjectB.class) - .findAllAsync("my-id"); - results2.addChangeListener((results, changeSet) -> { - if (changeSet.getState() == OrderedCollectionChangeSet.State.ERROR) { - assertEquals(OrderedCollectionChangeSet.State.ERROR, changeSet.getState()); - assertTrue(changeSet.getError() instanceof IllegalArgumentException); - looperThread.testComplete(); - } - }); - - looperThread.keepStrongReference(results1); - looperThread.keepStrongReference(results2); - } - - @Test - @RunTestInLooperThread - public void unsubscribeAsync() throws InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - createServerData(user, Constants.SYNC_SERVER_URL); - Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - - final String subscriptionName = "my-objects"; - RealmResults r = realm.where(PartialSyncObjectB.class) - .greaterThan("number", 0) - .findAllAsync(subscriptionName); - - r.addChangeListener((results, changeSet) -> { - if (changeSet.isCompleteResult()) { - // 1. Partial sync downloaded all expected objects - assertEquals(TEST_SIZE - 1, results.size()); - r.removeAllChangeListeners(); - - // 2. Attempt to remove them again - realm.unsubscribeAsync(subscriptionName, new Realm.UnsubscribeCallback() { - @Override - public void onSuccess(String subscriptionName) { - assertEquals(subscriptionName, subscriptionName); - - // Use global Realm change listener to avoid re-subscribing - realm.addChangeListener(new RealmChangeListener() { - @Override - public void onChange(Realm realm) { - // Eventually they should be removed - if (realm.where(PartialSyncObjectB.class).count() == 0) { - looperThread.testComplete(); - } - } - }); - } - - @Override - public void onError(String subscriptionName, Throwable error) { - fail(error.toString()); - } - }); - } - }); - } - - @Test - @RunTestInLooperThread - public void unsubscribeAsync_nonExistingIdThrows() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - - realm.unsubscribeAsync("i-dont-exist", new Realm.UnsubscribeCallback() { - @Override - public void onSuccess(String subscriptionName) { - fail(); - } - - @Override - public void onError(String subscriptionName, Throwable error) { - assertEquals("i-dont-exist", subscriptionName); - assertTrue(error instanceof IllegalArgumentException); - assertTrue(error.getMessage().contains("No active subscription named")); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread - public void clearTable() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - Realm realm = getPartialRealm(user); - looperThread.closeAfterTest(realm); - - // Create test data and make sure it is uploaded to the server - RealmResults result = realm.where(PartialSyncObjectA.class).findAllAsync(); - realm.executeTransaction(r -> { - r.createObject(PartialSyncObjectA.class).setString("ObjectA"); - }); - SyncTestUtils.syncRealm(realm); - assertEquals(1, result.size()); - - // Delete data and make sure it is accepted by the server - realm.executeTransaction(r -> { - // TODO the API's that actual use the clearTable instruction have all been disabled for now - // and are throwing IllegalStateException (realm.delete(Class) and realm.deleteAll). - // Keep the test for time being, but use the recommend workaround for deleting objects - // instead. - r.where(PartialSyncObjectA.class).findAll().deleteAllFromRealm(); - }); - SyncTestUtils.syncRealm(realm); - assertTrue(result.isEmpty()); - looperThread.testComplete(); - } - - private Realm getPartialRealm(SyncUser user) { - final SyncConfiguration partialSyncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .name("partialSync") - .modules(new PartialSyncModule()) - .build(); - return Realm.getInstance(partialSyncConfig); - } - - private void createServerData(SyncUser user, String url) throws InterruptedException { - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, url) - .waitForInitialRemoteData() - .modules(new PartialSyncModule()) - .build(); - - // Create server data - Realm realm = Realm.getInstance(syncConfig); - realm.beginTransaction(); - PartialSyncObjectA objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(0); - objectA.setString("realm"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(1); - objectA.setString(""); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(2); - objectA.setString(""); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(3); - objectA.setString(""); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(4); - objectA.setString("realm"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(5); - objectA.setString("sync"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(6); - objectA.setString("partial"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(7); - objectA.setString("partial"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(8); - objectA.setString("partial"); - objectA = realm.createObject(PartialSyncObjectA.class); - objectA.setNumber(9); - objectA.setString("partial"); - - for (int i = 0; i < TEST_SIZE; i++) { - realm.createObject(PartialSyncObjectB.class).setNumber(i); - } - realm.commitTransaction(); - SyncManager.getSession(syncConfig).uploadAllLocalChanges(); - realm.close(); - } -} diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java index 8c42aa22c0..437be1b8ea 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java @@ -18,7 +18,6 @@ import io.realm.internal.OsRealmConfig; import io.realm.rule.TestRealmConfigurationFactory; -import io.realm.internal.sync.permissions.ObjectPermissionsModule; /** * Test rule used for creating SyncConfigurations. Will ensure that any Realm files are deleted when the @@ -29,7 +28,6 @@ public class TestSyncConfigurationFactory extends TestRealmConfigurationFactory public SyncConfiguration.Builder createSyncConfigurationBuilder(SyncUser user, String url) { return user.createConfiguration(url) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .addModule(new ObjectPermissionsModule()) .directory(getRoot()); } } diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java index 6fe64e459c..38bd154f25 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java @@ -51,7 +51,6 @@ public class UserFactory { private static synchronized void initFactory(boolean forceReset) { if (configuration == null || forceReset) { RealmConfiguration.Builder builder = new RealmConfiguration.Builder().name("user-factory.realm"); - ObjectServerFacade.getSyncFacadeIfPossible().addSupportForObjectLevelPermissions(builder); configuration = builder.build(); } } diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java index 94662ecaae..8e27668f00 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -35,7 +35,6 @@ import io.realm.Realm; import io.realm.RealmConfiguration; -import io.realm.internal.ObjectServerFacade; import static org.junit.Assert.assertTrue; @@ -131,7 +130,6 @@ private synchronized boolean isUnitTestFailed() { // You have to delete it yourself. public RealmConfiguration.Builder createConfigurationBuilder() { RealmConfiguration.Builder builder = new RealmConfiguration.Builder().directory(getRoot()); - ObjectServerFacade.getSyncFacadeIfPossible().addSupportForObjectLevelPermissions(builder); return builder; } @@ -171,8 +169,6 @@ public RealmConfiguration createConfiguration(String subDir, String name, Object if (module != null) { builder.modules(module); - } else { - ObjectServerFacade.getSyncFacadeIfPossible().addSupportForObjectLevelPermissions(builder); } if (key != null) { From 9cb7b8617a61717fd557a016ce371cbff9b4134a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 28 Feb 2020 15:08:54 +0100 Subject: [PATCH 1472/2110] Upgrade to Sync 10.0.0-alpha.1. Disable all Sync builds (#6760) --- Jenkinsfile | 2 +- dependencies.list | 4 +- examples/settings.gradle | 4 +- realm/kotlin-extensions/build.gradle | 50 ++++++++--------- .../realm-annotations-processor/build.gradle | 4 +- .../java/io/realm/processor/ClassMetaData.kt | 9 ++-- realm/realm-library/build.gradle | 54 +++++++++---------- .../java/io/realm/RealmAnnotationTests.java | 4 +- .../java/io/realm/RealmMigrationTests.java | 4 +- .../java/io/realm/RealmObjectSchemaTests.java | 19 ++----- .../java/io/realm/RealmQueryTests.java | 8 +++ .../java/io/realm/RealmSchemaTests.java | 12 ++--- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../main/cpp/io_realm_RealmFileUserStore.cpp | 7 +-- .../src/main/cpp/io_realm_RealmQuery.cpp | 3 -- .../cpp/io_realm_internal_OsRealmConfig.cpp | 5 +- .../main/cpp/io_realm_internal_OsResults.cpp | 4 +- .../cpp/io_realm_internal_OsSharedRealm.cpp | 8 ++- .../src/main/cpp/java_object_accessor.hpp | 47 +++++++++++++++- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/cpp/subscription_wrapper.hpp | 1 - realm/realm-library/src/main/cpp/util.cpp | 14 ----- .../src/main/java/io/realm/Realm.java | 8 +-- .../java/io/realm/internal/OsRealmConfig.java | 18 ++++--- .../objectServer/java/io/realm/SyncUser.java | 4 +- .../internal/SyncObjectServerFacade.java | 6 ++- 26 files changed, 169 insertions(+), 134 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a820d8a31f..8539903132 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -31,7 +31,7 @@ try { def instrumentationTestTarget = "connectedAndroidTest" if (!['master', 'next-major'].contains(env.BRANCH_NAME)) { abiFilter = "-PbuildTargetABIs=armeabi-v7a" - instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting + instrumentationTestTarget = "connectedBaseDebugAndroidTest" // Run in debug more for better error reporting } def buildEnv diff --git a/dependencies.list b/dependencies.list index 39286cc1ff..96cb3a5c2e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.0 -REALM_SYNC_SHA256=93825d20e47627eae314d0793380908a65d622c3cdddbc0ca30fce3bb39d21cd +REALM_SYNC_VERSION=10.0.0-alpha.1 +REALM_SYNC_SHA256=c910205387ab4397b65703f8494a1d362652643f7f096c81746b80674fe4aa4d # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/examples/settings.gradle b/examples/settings.gradle index 15f6d6c37d..8a877ae427 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -10,8 +10,8 @@ include 'moduleExample:app' include 'moduleExample:library' include 'newsreaderExample' include 'rxJavaExample' -include 'secureTokenAndroidKeyStore' +// include 'secureTokenAndroidKeyStore' include 'threadExample' include 'unitTestExample' -include 'objectServerExample' +// include 'objectServerExample' include 'multiprocessExample' diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 9025bc552b..29e178c48d 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -45,21 +45,21 @@ android { base { dimension 'api' } - objectServer { - dimension 'api' - } +// objectServer { +// dimension 'api' +// } } sourceSets { main.java.srcDirs += 'src/main/kotlin' androidTest.java.srcDirs += ['src/androidTest/kotlin', '../realm-library/src/testUtils/java'] - objectServer.java.srcDirs += 'src/objectServer/kotlin' - androidTestObjectServer.java.srcDirs += [ - 'src/androidTestObjectServer/kotlin', - '../realm-library/src/testUtils/java', - '../realm-library/src/testUtils/kotlin', - '../realm-library/src/syncTestUtils/java', - ] +// objectServer.java.srcDirs += 'src/objectServer/kotlin' +// androidTestObjectServer.java.srcDirs += [ +// 'src/androidTestObjectServer/kotlin', +// '../realm-library/src/testUtils/java', +// '../realm-library/src/testUtils/kotlin', +// '../realm-library/src/syncTestUtils/java', +// ] } // Required from Kotlin 1.1.2 @@ -77,9 +77,9 @@ dependencies { androidTestImplementation 'com.android.support.test:rules:1.0.2' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' - androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' - androidTestObjectServerImplementation 'com.google.code.findbugs:jsr305:3.0.2' +// androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' +// androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' +// androidTestObjectServerImplementation 'com.google.code.findbugs:jsr305:3.0.2' } repositories { @@ -95,7 +95,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { task sourcesJar(type: Jar) { - from android.sourceSets.objectServer.java.srcDirs +// from android.sourceSets.objectServer.java.srcDirs from android.sourceSets.main.java.srcDirs classifier = 'sources' } @@ -188,16 +188,16 @@ publishing { pom.withXml(createPomDependencies(["baseImplementation", "implementation", "baseApi", "api"])) } - objectServerPublication(MavenPublication) { - groupId 'io.realm' - artifactId 'realm-android-kotlin-extensions-object-server' - version project.version - artifact file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-objectServer-release.aar") - artifact sourcesJar - artifact javadocJar - - pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) - } +// objectServerPublication(MavenPublication) { +// groupId 'io.realm' +// artifactId 'realm-android-kotlin-extensions-object-server' +// version project.version +// artifact file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-objectServer-release.aar") +// artifact sourcesJar +// artifact javadocJar +// +// pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) +// } } repositories { maven { @@ -223,7 +223,7 @@ artifactory { password = project.hasProperty('bintrayKey') ? bintrayKey : 'noKey' } defaults { - publications('basePublication', 'objectServerPublication') + publications('basePublication'/*, 'objectServerPublication'*/) publishPom = true publishIvy = false } diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index ae79c01a89..8c623393d9 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -11,7 +11,9 @@ dependencies { compile "com.squareup:javawriter:2.5.1" compile "io.realm:realm-annotations:${version}" - testCompile files('../realm-library/build/intermediates/intermediate-jars/objectServer/release/classes.jar') // Java projects cannot depend on AAR files +// testCompile files('../realm-library/build/intermediates/intermediate-jars/objectServer/release/classes.jar') // Java projects cannot depend on AAR files + // FIXME: Revert to objectServer once Sync is working + testCompile files('../realm-library/build/intermediates/intermediate-jars/base/release/classes.jar') // Java projects cannot depend on AAR files testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it testCompile group:'junit', name:'junit', version:'4.12' testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt index 6c6c75205b..905ef673ce 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt @@ -698,7 +698,9 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat } // The field has the @PrimaryKey annotation. It is only valid for - // String, short, int, long and must only be present one time + // String, short, int and long and must only be present one time. + // From Core 6 String primary keys no longer needs to be indexed, and from Core 10 + // none of the primary key types do. private fun categorizePrimaryKeyField(fieldElement: RealmFieldElement): Boolean { if (primaryKey != null) { Utils.error(String.format(Locale.US, @@ -719,11 +721,6 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat primaryKey = fieldElement - // Also add as index. All non string types of primary key can be indexed. - if (!isStringPrimaryKeyType(fieldType) && !indexedFields.contains(fieldElement)) { - indexedFields.add(fieldElement) - } - return true } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index bc289641a1..62e3abb21b 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -97,10 +97,10 @@ android { androidTest { java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java', 'src/testUtils/kotlin'] } - androidTestObjectServer { - java.srcDirs += ['src/syncIntegrationTest/java', 'src/syncTestUtils/java'] - assets.srcDirs += ['src/syncIntegrationTest/assets/'] - } +// androidTestObjectServer { +// java.srcDirs += ['src/syncIntegrationTest/java', 'src/syncTestUtils/java'] +// assets.srcDirs += ['src/syncIntegrationTest/assets/'] +// } } compileOptions { @@ -133,16 +133,16 @@ android { consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-base.pro' proguardFiles 'proguard-rules-build-common.pro' } - objectServer { - dimension 'api' - externalNativeBuild { - cmake { - arguments "-DREALM_FLAVOR=objectServer" - } - } - consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-objectServer.pro' - proguardFiles 'proguard-rules-build-common.pro', 'proguard-rules-build-objectServer.pro' - } +// objectServer { +// dimension 'api' +// externalNativeBuild { +// cmake { +// arguments "-DREALM_FLAVOR=objectServer" +// } +// } +// consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-objectServer.pro' +// proguardFiles 'proguard-rules-build-common.pro', 'proguard-rules-build-objectServer.pro' +// } } variantFilter { variant -> @@ -209,7 +209,7 @@ dependencies { } kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 - objectServerImplementation 'com.squareup.okhttp3:okhttp:3.10.0' +// objectServerImplementation 'com.squareup.okhttp3:okhttp:3.10.0' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' @@ -228,7 +228,7 @@ dependencies { } task sourcesJar(type: Jar) { - from android.sourceSets.objectServer.java.srcDirs +// from android.sourceSets.objectServer.java.srcDirs from android.sourceSets.main.java.srcDirs classifier = 'sources' } @@ -239,7 +239,7 @@ def betaTag = 'Beta:a:

            This soft 'considered at production quality, and should be used with care.
            ' task javadoc(type: Javadoc) { - source android.sourceSets.objectServer.java.srcDirs +// source android.sourceSets.objectServer.java.srcDirs source android.sourceSets.main.java.srcDirs source "../../realm-annotations/src/main/java" classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) @@ -409,16 +409,16 @@ publishing { pom.withXml(createPomDependencies(["baseImplementation", "implementation", "baseApi", "api"])) } - objectServerPublication(MavenPublication) { - groupId 'io.realm' - artifactId 'realm-android-library-object-server' - version project.version - artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-objectServer-release.aar") - artifact sourcesJar - artifact javadocJar - - pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) - } +// objectServerPublication(MavenPublication) { +// groupId 'io.realm' +// artifactId 'realm-android-library-object-server' +// version project.version +// artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-objectServer-release.aar") +// artifact sourcesJar +// artifact javadocJar +// +// pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) +// } } repositories { maven { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java index d159f306ba..be3fdb1e13 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java @@ -128,13 +128,15 @@ public void primaryKey_errorOnInsertingSameObject() { @Test public void string_primaryKey_isNotIndexed() { + // Before Core 6 only String primary keys did not have a Index as a default + // With Core 10, primary keys do not need indexes in general. Table table = realm.getTable(PrimaryKeyAsString.class); assertNotNull(OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), PrimaryKeyAsString.CLASS_NAME)); assertFalse(table.hasSearchIndex(table.getColumnKey("name"))); table = realm.getTable(PrimaryKeyAsLong.class); assertNotNull(OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), PrimaryKeyAsLong.CLASS_NAME)); - assertTrue(table.hasSearchIndex(table.getColumnKey("id"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("id"))); } // Annotation processor honors common naming conventions. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index f3de8e9439..75bc6be6e5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -852,7 +852,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm = Realm.getInstance(realmConfig); RealmObjectSchema schema = realm.getSchema().get("AnnotationTypes"); assertTrue(schema.hasPrimaryKey()); - assertTrue(schema.hasIndex("id")); + assertFalse(schema.hasIndex("id")); realm.close(); } @@ -886,7 +886,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { Table table = realm.getTable(AnnotationTypes.class); assertEquals(3, table.getColumnCount()); assertEquals("id", OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), "AnnotationTypes")); - assertTrue(table.hasSearchIndex(table.getColumnKey("id"))); + assertFalse(table.hasSearchIndex(table.getColumnKey("id"))); assertTrue(table.hasSearchIndex(table.getColumnKey("indexString"))); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 5cbb06e2c4..75fd6cf9e1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -928,20 +928,11 @@ private void setRequired_onPrimaryKeyField(boolean isRequired) { ((DynamicRealm)realm).createObject(schema.getClassName(), "1"); ((DynamicRealm)realm).createObject(schema.getClassName(), "2"); assertTrue(schema.hasPrimaryKey()); - if (fieldType.getType().isAssignableFrom(String.class)) { - assertFalse(schema.hasIndex(fieldName)); - } else { - assertTrue(schema.hasIndex(fieldName)); - } - + assertFalse(schema.hasIndex(fieldName)); schema.setRequired(fieldName, isRequired); assertTrue(schema.hasPrimaryKey()); - if (fieldType.getType().isAssignableFrom(String.class)) { - assertFalse(schema.hasIndex(fieldName)); - } else { - assertTrue(schema.hasIndex(fieldName)); - } + assertFalse(schema.hasIndex(fieldName)); RealmResults results = ((DynamicRealm)realm).where(className).sort(fieldName).findAll(); assertEquals(2, results.size()); @@ -1028,11 +1019,7 @@ public void setPrimaryKey_trueAndFalse() { schema.addPrimaryKey(fieldName); assertTrue(schema.hasPrimaryKey()); assertTrue(schema.isPrimaryKey(fieldName)); - if (fieldType.getType().isAssignableFrom(String.class)) { - assertFalse(schema.hasIndex(fieldName)); - } else { - assertTrue(schema.hasIndex(fieldName)); - } + assertFalse(schema.hasIndex(fieldName)); schema.removePrimaryKey(); assertFalse(schema.hasPrimaryKey()); assertFalse(schema.isPrimaryKey(fieldName)); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 742f71ebe0..475e9e24d6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -18,6 +18,7 @@ import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -1531,6 +1532,7 @@ public void isNull_notNullableFields() { } // Queries nullable PrimaryKey. + @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void equalTo_nullPrimaryKeys() { final long SECONDARY_FIELD_NUMBER = 49992417L; @@ -1554,6 +1556,7 @@ public void equalTo_nullPrimaryKeys() { assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedLong.class).equalTo(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, (Long) null).findAll().first().getName()); } + @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void isNull_nullPrimaryKeys() { final long SECONDARY_FIELD_NUMBER = 49992417L; @@ -1635,6 +1638,7 @@ public void like_nullStringPrimaryKey() { .findAll().first().getId()); } + @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void between_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. @@ -1653,6 +1657,7 @@ public void between_nullPrimaryKeysIsNotZero() { assertEquals(3, realm.where(PrimaryKeyAsBoxedLong.class).between(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, -1, 1).count()); } + @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void greaterThan_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. @@ -1671,6 +1676,7 @@ public void greaterThan_nullPrimaryKeysIsNotZero() { assertEquals(4, realm.where(PrimaryKeyAsBoxedLong.class).greaterThan(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, -1).count()); } + @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void greaterThanOrEqualTo_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. @@ -1689,6 +1695,7 @@ public void greaterThanOrEqualTo_nullPrimaryKeysIsNotZero() { assertEquals(5, realm.where(PrimaryKeyAsBoxedLong.class).greaterThanOrEqualTo(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, -1).count()); } + @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void lessThan_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. @@ -1707,6 +1714,7 @@ public void lessThan_nullPrimaryKeysIsNotZero() { assertEquals(6, realm.where(PrimaryKeyAsBoxedLong.class).lessThan(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, 1).count()); } + @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void lessThanOrEqualTo_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index 9e00db42c1..a08741fa6c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -231,7 +231,7 @@ public void createWithPrimaryKeyField_boxedInteger() { assertEquals("pkField", objectSchema.getPrimaryKey()); assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); assertFalse(objectSchema.isNullable("pkField")); - assertTrue(objectSchema.hasIndex("pkField")); + assertFalse(objectSchema.hasIndex("pkField")); realmSchema.remove(validClassName); @@ -241,7 +241,7 @@ public void createWithPrimaryKeyField_boxedInteger() { assertEquals("pkField", objectSchema.getPrimaryKey()); assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); assertTrue(objectSchema.isNullable("pkField")); - assertTrue(objectSchema.hasIndex("pkField")); + assertFalse(objectSchema.hasIndex("pkField")); } } @@ -267,7 +267,7 @@ public void createWithPrimaryKeyField_int() { assertEquals("pkField", objectSchema.getPrimaryKey()); assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); assertFalse(objectSchema.isNullable("pkField")); - assertTrue(objectSchema.hasIndex("pkField")); + assertFalse(objectSchema.hasIndex("pkField")); realmSchema.remove(validClassName); @@ -277,7 +277,7 @@ public void createWithPrimaryKeyField_int() { assertEquals("pkField", objectSchema.getPrimaryKey()); assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); assertFalse(objectSchema.isNullable("pkField")); - assertTrue(objectSchema.hasIndex("pkField")); + assertFalse(objectSchema.hasIndex("pkField")); } } @@ -292,7 +292,7 @@ public void createWithPrimaryKeyField_explicitIndexed() { assertEquals("pkField", objectSchema.getPrimaryKey()); assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); assertFalse(objectSchema.isNullable("pkField")); - assertTrue(objectSchema.hasIndex("pkField")); + assertFalse(objectSchema.hasIndex("pkField")); } @Test @@ -306,7 +306,7 @@ public void createWithPrimaryKeyField_explicitPrimaryKey() { assertEquals("pkField", objectSchema.getPrimaryKey()); assertEquals(RealmFieldType.INTEGER, objectSchema.getFieldType("pkField")); assertFalse(objectSchema.isNullable("pkField")); - assertTrue(objectSchema.hasIndex("pkField")); + assertFalse(objectSchema.hasIndex("pkField")); } @Test diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 67e0a3b8bd..b6a2252011 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -142,7 +142,7 @@ endif() set(WARNING_CXX_FLAGS "-Werror -Wall -Wextra -pedantic -Wmissing-declarations \ -Wempty-body -Wparentheses -Wunknown-pragmas -Wunreachable-code \ -Wno-missing-field-initializers -Wno-unevaluated-expression -Wno-unreachable-code") -set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -fdata-sections -pthread -frtti -fvisibility=hidden -fsigned-char -fno-stack-protector -std=c++14") +set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -fdata-sections -pthread -frtti -fvisibility=hidden -fsigned-char -fno-stack-protector -std=c++17") if (build_SYNC) set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ENABLE_SYNC=1") endif() diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index bcc446bb25..e504cfce59 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -68,12 +68,13 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetUser(JNIEnv* } JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser(JNIEnv* env, jclass, - jstring j_user_id, jstring json_token, + jstring j_user_id, + jstring j_refresh_json_token, jstring j_auth_url) { try { - JStringAccessor user_json_token(env, json_token); // throws - SyncManager::shared().get_user(create_sync_user_identifier(env, j_user_id, j_auth_url), user_json_token); + JStringAccessor refresh_json_token(env, j_refresh_json_token); // throws + SyncManager::shared().get_user(create_sync_user_identifier(env, j_user_id, j_auth_url), refresh_json_token, refresh_json_token); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp index 93060c33cb..b2754775a9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp @@ -18,9 +18,6 @@ #include #include -#if REALM_ENABLE_SYNC -#include -#endif #include "util.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index f338037462..a2d1557621 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -243,7 +243,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNo #if REALM_ENABLE_SYNC JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( JNIEnv* env, jclass, jlong native_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, - jstring j_refresh_token, jbyte j_session_stop_policy, jstring j_url_prefix, + jstring j_refresh_token, jstring j_access_token, jbyte j_session_stop_policy, jstring j_url_prefix, jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array, jbyte j_client_reset_mode) { auto& config = *reinterpret_cast(native_ptr); @@ -346,7 +346,8 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe if (!user) { JStringAccessor realm_auth_url(env, j_auth_url); JStringAccessor refresh_token(env, j_refresh_token); - user = SyncManager::shared().get_user(sync_user_identifier, refresh_token); + JStringAccessor access_token(env, j_access_token); + user = SyncManager::shared().get_user(sync_user_identifier, refresh_token, access_token); } SyncSessionStopPolicy session_stop_policy = static_cast(j_session_stop_policy); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index b73ab5c6fc..b51c828796 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -161,9 +161,9 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_OsResults_nativeAggregate(JNIEn value = wrapper->collection().max(col_key); break; case io_realm_internal_OsResults_AGGREGATE_FUNCTION_AVERAGE: { - Optional value_count(wrapper->collection().average(col_key)); + Optional value_count(wrapper->collection().average(col_key)); if (value_count) { - value = Optional(Mixed(value_count.value())); + value = value_count; } else { value = Optional(0.0); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 06d5ab6aed..be50790e46 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -20,7 +20,6 @@ #include "object-store/src/sync/sync_config.hpp" #include "object-store/src/sync/sync_session.hpp" #include "object-store/src/results.hpp" -#include "object-store/src/sync/partial_sync.hpp" #include "observable_collection_wrapper.hpp" #endif @@ -515,6 +514,13 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetObjectPrivi } #endif +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsPartial(JNIEnv*, jclass, jlong /*shared_realm_ptr*/) +{ + // No throws + // auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return to_jbool(false); +} + JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsFrozen(JNIEnv* env, jclass, jlong shared_realm_ptr) { try { diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp index 948493e8c5..6ea59e0897 100644 --- a/realm/realm-library/src/main/cpp/java_object_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -39,6 +39,8 @@ using namespace realm::_impl; X(Float) \ X(Double) \ X(Date) \ + X(ObjectId) \ + X(Decimal) \ X(Binary) \ X(Object) \ X(List) \ @@ -72,6 +74,8 @@ template <> struct JavaValueTypeRepr { using Type = jboo template <> struct JavaValueTypeRepr { using Type = jfloat; }; template <> struct JavaValueTypeRepr { using Type = jdouble; }; template <> struct JavaValueTypeRepr { using Type = Timestamp; }; +template <> struct JavaValueTypeRepr{ using Type = ObjectId; }; +template <> struct JavaValueTypeRepr { using Type = Decimal128; }; template <> struct JavaValueTypeRepr { using Type = OwnedBinaryData; }; template <> struct JavaValueTypeRepr { using Type = Obj*; }; template <> struct JavaValueTypeRepr { using Type = std::vector; }; @@ -217,6 +221,16 @@ struct JavaValue { return get_as(); } + auto& get_object_id() const noexcept + { + return get_as(); + } + + auto& get_decimal128() const noexcept + { + return get_as(); + } + auto& get_binary() const noexcept { return get_as(); @@ -266,6 +280,10 @@ struct JavaValue { case JavaValueType::Date: ss << get_date(); return std::string(ss.str()); + case JavaValueType::ObjectId: + return get_object_id().to_string(); + case JavaValueType::Decimal: + return get_decimal128().to_string(); case JavaValueType::Binary: ss << "Blob["; ss << get_binary().size(); @@ -318,6 +336,11 @@ class JavaContext { , object_schema(prop.type == PropertyType::Object ? &*realm->schema().find(prop.object_type) : c.object_schema) { } + bool is_embedded() const + { + return object_schema ? bool(object_schema->is_embedded) : false; + } + // The use of util::Optional for the following two functions is not a hard // requirement; only that it be some type which can be evaluated in a // boolean context to determine if it contains a value, and if it does @@ -401,10 +424,13 @@ class JavaContext { // using the provided value. If `update` is true then upsert semantics // should be used for this. template - T unbox(JavaValue const& /*v*/, CreatePolicy = CreatePolicy::Skip, ObjKey /*current_row*/ = ObjKey()) const { + T unbox(JavaValue const& /*v*/, CreatePolicy = CreatePolicy::Skip, ObjKey /*current_row*/ = ObjKey()) const + { throw std::logic_error("Missing template specialization"); // All types should have specialized templates } + Obj unbox_embedded(JavaValue const& v, CreatePolicy policy, Obj& parent, ColKey col, size_t ndx) const; + bool is_null(JavaValue const& v) const noexcept { return !v.has_value(); } JavaValue null_value() const noexcept { return {}; } util::Optional no_value() const noexcept { return {}; } @@ -535,6 +561,25 @@ inline Mixed JavaContext::unbox(JavaValue const&, CreatePolicy, ObjKey) const REALM_TERMINATE("'Mixed' not supported"); } +template <> +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? util::make_optional(v.get_object_id()) : util::none; +} + +template <> +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? util::make_optional(v.get_decimal128()) : util::none; +} + +inline Obj JavaContext::unbox_embedded(JavaValue const& v, CreatePolicy policy, Obj& parent, ColKey col, size_t ndx) const +{ + return Object::create_embedded(const_cast(*this), realm, *object_schema, v, policy, parent, col, ndx).obj(); +} + + + } #endif // REALM_JAVA_OBJECT_ACCESSOR_HPP diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 66199adbff..38fc599923 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 66199adbfffbe153e696309a53d4ec03e32c44e3 +Subproject commit 38fc5999234184fc2ce5ea8b61f5cce65afa71f4 diff --git a/realm/realm-library/src/main/cpp/subscription_wrapper.hpp b/realm/realm-library/src/main/cpp/subscription_wrapper.hpp index 7c58651637..3272fcd827 100644 --- a/realm/realm-library/src/main/cpp/subscription_wrapper.hpp +++ b/realm/realm-library/src/main/cpp/subscription_wrapper.hpp @@ -23,7 +23,6 @@ #include "jni_util/java_method.hpp" #include -#include namespace realm { namespace _impl { diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 78f4d3b17b..d665845357 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -32,9 +32,6 @@ #include "java_exception_def.hpp" #include "java_object_accessor.hpp" #include "object.hpp" -#if REALM_ENABLE_SYNC -#include "sync/partial_sync.hpp" -#endif #include "jni_util/java_exception_thrower.hpp" @@ -139,17 +136,6 @@ void ConvertException(JNIEnv* env, const char* file, int line) catch(realm::RequiredFieldValueNotProvidedException e) { ThrowException(env, IllegalArgument, e.what()); } -#if REALM_ENABLE_SYNC - catch (partial_sync::InvalidRealmStateException& e) { - ThrowException(env, IllegalState, e.what()); - } - catch (partial_sync::ExistingSubscriptionException& e) { - ThrowException(env, IllegalArgument, e.what()); - } - catch (partial_sync::QueryTypeMismatchException& e) { - ThrowException(env, IllegalArgument, e.what()); - } -#endif catch (std::logic_error e) { ThrowException(env, IllegalState, e.what()); } diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 02cbae7d42..1aab74a91e 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -379,11 +379,11 @@ private static void checkFilesDirAvailable(Context context) { * @return an instance of the Realm class. * @throws java.lang.NullPointerException if no default configuration has been defined. * @throws RealmMigrationNeededException if no migration has been provided by the default configuration and the - * RealmObject classes or version has has changed so a migration is required. * @throws RealmFileException if an error happened when accessing the underlying Realm file. - * @throws io.realm.exceptions.DownloadingRealmInterruptedException if {@link SyncConfiguration.Builder#waitForInitialRemoteData()} * was set and the thread opening the Realm was interrupted while the download was in progress. */ +// * @throws io.realm.exceptions.DownloadingRealmInterruptedException if {@link SyncConfiguration.Builder#waitForInitialRemoteData()} +// * RealmObject classes or version has has changed so a migration is required. public static Realm getDefaultInstance() { RealmConfiguration configuration = getDefaultConfiguration(); if (configuration == null) { @@ -405,10 +405,10 @@ public static Realm getDefaultInstance() { * classes or version has has changed so a migration is required. * @throws RealmFileException if an error happened when accessing the underlying Realm file. * @throws IllegalArgumentException if a null {@link RealmConfiguration} is provided. - * @throws io.realm.exceptions.DownloadingRealmInterruptedException if {@link SyncConfiguration.Builder#waitForInitialRemoteData()} - * was set and the thread opening the Realm was interrupted while the download was in progress. * @see RealmConfiguration for details on how to configure a Realm. */ +// * @throws io.realm.exceptions.DownloadingRealmInterruptedException if {@link SyncConfiguration.Builder#waitForInitialRemoteData()} +// * was set and the thread opening the Realm was interrupted while the download was in progress. public static Realm getInstance(RealmConfiguration configuration) { //noinspection ConstantConditions if (configuration == null) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 8ad8576e3f..b9692bdf22 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -211,17 +211,18 @@ private OsRealmConfig(final RealmConfiguration config, String syncRealmUrl = (String) syncConfigurationOptions[1]; String syncRealmAuthUrl = (String) syncConfigurationOptions[2]; String syncRefreshToken = (String) syncConfigurationOptions[3]; - boolean syncClientValidateSsl = (Boolean.TRUE.equals(syncConfigurationOptions[4])); - String syncSslTrustCertificatePath = (String) syncConfigurationOptions[5]; - Byte sessionStopPolicy = (Byte) syncConfigurationOptions[6]; - String urlPrefix = (String)(syncConfigurationOptions[7]); - String customAuthorizationHeaderName = (String)(syncConfigurationOptions[8]); - Byte clientResyncMode = (Byte) syncConfigurationOptions[10]; + String syncAccessToken = (String) syncConfigurationOptions[4]; + boolean syncClientValidateSsl = (Boolean.TRUE.equals(syncConfigurationOptions[5])); + String syncSslTrustCertificatePath = (String) syncConfigurationOptions[6]; + Byte sessionStopPolicy = (Byte) syncConfigurationOptions[7]; + String urlPrefix = (String)(syncConfigurationOptions[8]); + String customAuthorizationHeaderName = (String)(syncConfigurationOptions[9]); + Byte clientResyncMode = (Byte) syncConfigurationOptions[11]; // Convert the headers into a String array to make it easier to send through JNI // [key1, value1, key2, value2, ...] //noinspection unchecked - Map customHeadersMap = (Map) (syncConfigurationOptions[9]); + Map customHeadersMap = (Map) (syncConfigurationOptions[10]); String[] customHeaders = new String[customHeadersMap != null ? customHeadersMap.size() * 2 : 0]; if (customHeadersMap != null) { int i = 0; @@ -281,6 +282,7 @@ private OsRealmConfig(final RealmConfiguration config, syncRealmAuthUrl, syncUserIdentifier, syncRefreshToken, + syncAccessToken, sessionStopPolicy, urlPrefix, customAuthorizationHeaderName, @@ -378,7 +380,7 @@ private native void nativeSetSchemaConfig(long nativePtr, byte schemaMode, long private static native void nativeEnableChangeNotification(long nativePtr, boolean enableNotification); private static native String nativeCreateAndSetSyncConfig(long nativePtr, String syncRealmUrl, String authUrl, - String userId, String refreshToken, + String userId, String refreshToken, String accessToken, byte sessionStopPolicy, String urlPrefix, String customAuthorizationHeaderName, String[] customHeaders, byte clientResetMode); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 6bf75d4da8..1762f170cb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -872,7 +872,7 @@ public String getIdentity() { * * @return the user's refresh token. If this user has logged out or the login has expired {@code null} is returned. */ - Token getRefreshToken() { + public Token getRefreshToken() { return refreshToken; } @@ -900,7 +900,7 @@ boolean isRealmAuthenticated(SyncConfiguration configuration) { return token != null && token.expiresMs() > System.currentTimeMillis(); } - Token getAccessToken(SyncConfiguration configuration) { + public Token getAccessToken(SyncConfiguration configuration) { return realms.get(configuration); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 062c843f3d..fdac84506b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -94,7 +94,8 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { String rosServerUrl = syncConfig.getServerUrl().toString(); String rosUserIdentity = user.getIdentity(); String syncRealmAuthUrl = user.getAuthenticationUrl().toString(); - String rosSerializedUser = user.toJson(); + String syncUserRefreshToken = user.getRefreshToken().toJson().toString(); + String syncUserAccessToken = user.getAccessToken(((SyncConfiguration) config)).toJson().toString(); byte sessionStopPolicy = syncConfig.getSessionStopPolicy().getNativeValue(); String urlPrefix = syncConfig.getUrlPrefix(); String customAuthorizationHeaderName = SyncManager.getAuthorizationHeaderName(syncConfig.getServerUrl()); @@ -103,7 +104,8 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { rosUserIdentity, rosServerUrl, syncRealmAuthUrl, - rosSerializedUser, + syncUserRefreshToken, + syncUserAccessToken, syncConfig.syncClientValidateSsl(), syncConfig.getServerCertificateFilePath(), sessionStopPolicy, From b870bfdafa803d9e43f69d6e0ea05b4668c74821 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 2 Mar 2020 11:11:07 +0100 Subject: [PATCH 1473/2110] Upgrade to Sync 10.0.0-alpha.2 (#6765) --- Jenkinsfile | 2 +- dependencies.list | 4 +- examples/settings.gradle | 4 +- realm/kotlin-extensions/build.gradle | 48 ++++++++--------- .../realm-annotations-processor/build.gradle | 4 +- realm/realm-library/build.gradle | 54 +++++++++---------- .../realm-library/src/main/cpp/CMakeLists.txt | 7 +-- .../src/main/cpp/io_realm_SyncSession.cpp | 2 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 4 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../internal/SyncObjectServerFacade.java | 2 +- version.txt | 2 +- 12 files changed, 67 insertions(+), 68 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8539903132..a820d8a31f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -31,7 +31,7 @@ try { def instrumentationTestTarget = "connectedAndroidTest" if (!['master', 'next-major'].contains(env.BRANCH_NAME)) { abiFilter = "-PbuildTargetABIs=armeabi-v7a" - instrumentationTestTarget = "connectedBaseDebugAndroidTest" // Run in debug more for better error reporting + instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting } def buildEnv diff --git a/dependencies.list b/dependencies.list index 96cb3a5c2e..7ff7ec390b 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.1 -REALM_SYNC_SHA256=c910205387ab4397b65703f8494a1d362652643f7f096c81746b80674fe4aa4d +REALM_SYNC_VERSION=10.0.0-alpha.2 +REALM_SYNC_SHA256=02b78b8961936c61b3f09667ced0c2738de7ed84b299ddeecfbb660ceb227dc9 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/examples/settings.gradle b/examples/settings.gradle index 8a877ae427..15f6d6c37d 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -10,8 +10,8 @@ include 'moduleExample:app' include 'moduleExample:library' include 'newsreaderExample' include 'rxJavaExample' -// include 'secureTokenAndroidKeyStore' +include 'secureTokenAndroidKeyStore' include 'threadExample' include 'unitTestExample' -// include 'objectServerExample' +include 'objectServerExample' include 'multiprocessExample' diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 29e178c48d..91c2b50d8f 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -45,21 +45,21 @@ android { base { dimension 'api' } -// objectServer { -// dimension 'api' -// } + objectServer { + dimension 'api' + } } sourceSets { main.java.srcDirs += 'src/main/kotlin' androidTest.java.srcDirs += ['src/androidTest/kotlin', '../realm-library/src/testUtils/java'] -// objectServer.java.srcDirs += 'src/objectServer/kotlin' -// androidTestObjectServer.java.srcDirs += [ -// 'src/androidTestObjectServer/kotlin', -// '../realm-library/src/testUtils/java', -// '../realm-library/src/testUtils/kotlin', -// '../realm-library/src/syncTestUtils/java', -// ] + objectServer.java.srcDirs += 'src/objectServer/kotlin' + androidTestObjectServer.java.srcDirs += [ + 'src/androidTestObjectServer/kotlin', + '../realm-library/src/testUtils/java', + '../realm-library/src/testUtils/kotlin', + '../realm-library/src/syncTestUtils/java', + ] } // Required from Kotlin 1.1.2 @@ -77,9 +77,9 @@ dependencies { androidTestImplementation 'com.android.support.test:rules:1.0.2' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" -// androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' -// androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' -// androidTestObjectServerImplementation 'com.google.code.findbugs:jsr305:3.0.2' + androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' + androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' + androidTestObjectServerImplementation 'com.google.code.findbugs:jsr305:3.0.2' } repositories { @@ -95,7 +95,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { task sourcesJar(type: Jar) { -// from android.sourceSets.objectServer.java.srcDirs + from android.sourceSets.objectServer.java.srcDirs from android.sourceSets.main.java.srcDirs classifier = 'sources' } @@ -188,16 +188,16 @@ publishing { pom.withXml(createPomDependencies(["baseImplementation", "implementation", "baseApi", "api"])) } -// objectServerPublication(MavenPublication) { -// groupId 'io.realm' -// artifactId 'realm-android-kotlin-extensions-object-server' -// version project.version -// artifact file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-objectServer-release.aar") -// artifact sourcesJar -// artifact javadocJar -// -// pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) -// } + objectServerPublication(MavenPublication) { + groupId 'io.realm' + artifactId 'realm-android-kotlin-extensions-object-server' + version project.version + artifact file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-objectServer-release.aar") + artifact sourcesJar + artifact javadocJar + + pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) + } } repositories { maven { diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 8c623393d9..ae79c01a89 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -11,9 +11,7 @@ dependencies { compile "com.squareup:javawriter:2.5.1" compile "io.realm:realm-annotations:${version}" -// testCompile files('../realm-library/build/intermediates/intermediate-jars/objectServer/release/classes.jar') // Java projects cannot depend on AAR files - // FIXME: Revert to objectServer once Sync is working - testCompile files('../realm-library/build/intermediates/intermediate-jars/base/release/classes.jar') // Java projects cannot depend on AAR files + testCompile files('../realm-library/build/intermediates/intermediate-jars/objectServer/release/classes.jar') // Java projects cannot depend on AAR files testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it testCompile group:'junit', name:'junit', version:'4.12' testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 62e3abb21b..bc289641a1 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -97,10 +97,10 @@ android { androidTest { java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java', 'src/testUtils/kotlin'] } -// androidTestObjectServer { -// java.srcDirs += ['src/syncIntegrationTest/java', 'src/syncTestUtils/java'] -// assets.srcDirs += ['src/syncIntegrationTest/assets/'] -// } + androidTestObjectServer { + java.srcDirs += ['src/syncIntegrationTest/java', 'src/syncTestUtils/java'] + assets.srcDirs += ['src/syncIntegrationTest/assets/'] + } } compileOptions { @@ -133,16 +133,16 @@ android { consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-base.pro' proguardFiles 'proguard-rules-build-common.pro' } -// objectServer { -// dimension 'api' -// externalNativeBuild { -// cmake { -// arguments "-DREALM_FLAVOR=objectServer" -// } -// } -// consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-objectServer.pro' -// proguardFiles 'proguard-rules-build-common.pro', 'proguard-rules-build-objectServer.pro' -// } + objectServer { + dimension 'api' + externalNativeBuild { + cmake { + arguments "-DREALM_FLAVOR=objectServer" + } + } + consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-objectServer.pro' + proguardFiles 'proguard-rules-build-common.pro', 'proguard-rules-build-objectServer.pro' + } } variantFilter { variant -> @@ -209,7 +209,7 @@ dependencies { } kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 -// objectServerImplementation 'com.squareup.okhttp3:okhttp:3.10.0' + objectServerImplementation 'com.squareup.okhttp3:okhttp:3.10.0' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' @@ -228,7 +228,7 @@ dependencies { } task sourcesJar(type: Jar) { -// from android.sourceSets.objectServer.java.srcDirs + from android.sourceSets.objectServer.java.srcDirs from android.sourceSets.main.java.srcDirs classifier = 'sources' } @@ -239,7 +239,7 @@ def betaTag = 'Beta:a:
            This soft 'considered at production quality, and should be used with care.
            ' task javadoc(type: Javadoc) { -// source android.sourceSets.objectServer.java.srcDirs + source android.sourceSets.objectServer.java.srcDirs source android.sourceSets.main.java.srcDirs source "../../realm-annotations/src/main/java" classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) @@ -409,16 +409,16 @@ publishing { pom.withXml(createPomDependencies(["baseImplementation", "implementation", "baseApi", "api"])) } -// objectServerPublication(MavenPublication) { -// groupId 'io.realm' -// artifactId 'realm-android-library-object-server' -// version project.version -// artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-objectServer-release.aar") -// artifact sourcesJar -// artifact javadocJar -// -// pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) -// } + objectServerPublication(MavenPublication) { + groupId 'io.realm' + artifactId 'realm-android-library-object-server' + version project.version + artifact file("${rootDir}/realm-library/build/outputs/aar/realm-android-library-objectServer-release.aar") + artifact sourcesJar + artifact javadocJar + + pom.withXml(createPomDependencies(["objectServerImplementation", "implementation", "objectServerApi", "api"])) + } } repositories { maven { diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index b6a2252011..62eb5ceed6 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -122,9 +122,10 @@ get_target_property(ssl_LIB OpenSSL::SSL IMPORTED_LOCATION) # build application's shared lib include_directories( - ${CMAKE_SOURCE_DIR} - ${jni_headers_PATH} - ${CMAKE_SOURCE_DIR}/object-store/src) + ${CMAKE_SOURCE_DIR} + ${jni_headers_PATH} + ${CMAKE_SOURCE_DIR}/object-store/src + ${CMAKE_SOURCE_DIR}/object-store/external/json) # Hack the memmove bug on Samsung device. if (ARMEABI OR ARMEABI_V7A) diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 40da63f010..006fd94d10 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -70,7 +70,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JN JStringAccessor access_token(env, j_access_token); JStringAccessor realm_url(env, j_sync_realm_url); - session->refresh_access_token(access_token, std::string(session->config().realm_url())); + session->refresh_access_token(access_token, session->config().realm_url); return JNI_TRUE; } else { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index a2d1557621..cf9a1f03ce 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -331,7 +331,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe if (access_token_string) { // reusing cached valid token JStringAccessor access_token(env, access_token_string); - session->refresh_access_token(access_token, realm::util::Optional(syncConfig.realm_url())); + session->refresh_access_token(access_token, realm::util::Optional(syncConfig.realm_url)); env->DeleteLocalRef(access_token_string); } env->DeleteLocalRef(jpath); @@ -388,7 +388,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe std::copy_n(config.encryption_key.begin(), 64, config.sync_config->realm_encryption_key->begin()); } - return to_jstring(env, config.sync_config->realm_url().c_str()); + return to_jstring(env, config.sync_config->realm_url.c_str()); } CATCH_STD() return nullptr; diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 38fc599923..79bffff3a4 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 38fc5999234184fc2ce5ea8b61f5cce65afa71f4 +Subproject commit 79bffff3a433bec1f382422e5cb0f6c878c46835 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index fdac84506b..bc647abee4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -115,7 +115,7 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { syncConfig.getClientResyncMode().getNativeValue() }; } else { - return new Object[11]; + return new Object[12]; } } diff --git a/version.txt b/version.txt index 72d3929c4c..f702ec2902 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -8.0.0-beta-SNAPSHOT +10.0.0-SNAPSHOT From 174be0219e8d8e0c79b4456f2473c6475edcbd11 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 6 Mar 2020 20:02:50 +0100 Subject: [PATCH 1474/2110] Update Android Gradle Plugin and Gradle to latest version. (#6768) --- .gitignore | 1 + CHANGELOG.md | 5 +- Dockerfile | 12 +- Jenkinsfile | 2 +- dependencies.list | 6 +- examples/gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 55616 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/gradlew | 22 +- examples/gradlew.bat | 18 +- examples/settings.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 55190 -> 56177 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle-plugin/gradlew | 2 +- gradle-plugin/gradlew.bat | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 56177 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 56177 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 55190 -> 56177 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-annotations/gradlew | 2 +- realm-annotations/gradlew.bat | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 56177 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/build.gradle | 6 +- realm/gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 55616 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +- realm/gradlew | 22 +- realm/gradlew.bat | 18 +- realm/kotlin-extensions/build.gradle | 14 +- realm/kotlin-extensions/lib/package-list.txt | 229 ++++++++++++++++++ .../realm-annotations-processor/build.gradle | 2 +- realm/realm-library/build.gradle | 75 ++---- .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- 35 files changed, 356 insertions(+), 105 deletions(-) create mode 100644 realm/kotlin-extensions/lib/package-list.txt diff --git a/.gitignore b/.gitignore index 2e810308be..8754b69638 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ realm/realm-library/src/main/cpp/jni_include realm/realm-library/distribution # Cmake output realm/realm-library/.externalNativeBuild +realm/realm-library/.cxx diff --git a/CHANGELOG.md b/CHANGELOG.md index 76d76582d3..3bc76cfebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,10 @@ * TODO. ### Internal -* None. +* Updated Android Gradle Plugin to 3.6.1. +* Updated Gradle to 5.6.4 +* Updated Dokka to 0.10.1 +* Updated Android Build Tools to 29.0.2. ## 7.0.0(YYYY-MM-DD) diff --git a/Dockerfile b/Dockerfile index e41a62a059..4817f479ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,16 +62,8 @@ RUN yes | sdkmanager \ 'build-tools;28.0.3' \ 'extras;android;m2repository' \ 'platforms;android-27' \ - 'cmake;3.6.4111459' - -# Install the NDK -RUN mkdir /opt/android-ndk-tmp && \ - cd /opt/android-ndk-tmp && \ - wget -q https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip -O android-ndk.zip && \ - unzip android-ndk.zip && \ - mv android-ndk-r21 /opt/android-ndk && \ - rm -rf /opt/android-ndk-tmp && \ - chmod -R a+rX /opt/android-ndk + 'cmake;3.6.4111459' \ + 'ndk;21.0.6113669' # Make the SDK universally writable RUN chmod -R a+rwX ${ANDROID_HOME} diff --git a/Jenkinsfile b/Jenkinsfile index a820d8a31f..d98ec696d8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -86,7 +86,7 @@ try { stage('Static code analysis') { try { - gradle('realm', "findbugs pmd checkstyle ${abiFilter}") + gradle('realm', "findbugs checkstyle ${abiFilter}") // FIXME Reenable pmd } finally { publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) diff --git a/dependencies.list b/dependencies.list index 7ff7ec390b..09ccce28b6 100644 --- a/dependencies.list +++ b/dependencies.list @@ -8,12 +8,12 @@ REALM_SYNC_SHA256=02b78b8961936c61b3f09667ced0c2738de7ed84b299ddeecfbb660ceb227d REALM_OBJECT_SERVER_VERSION=3.23.1 # Common Android settings across projects -GRADLE_BUILD_TOOLS=3.3.2 -ANDROID_BUILD_TOOLS=28.0.3 +GRADLE_BUILD_TOOLS=3.6.1 +ANDROID_BUILD_TOOLS=29.0.2 # Common classpath dependencies # Gradle 5 is not supported yet: https://issuetracker.google.com/issues/126433059 -gradleVersion=4.10.1 +gradleVersion=5.6.4 ndkVersion=21.0.6113669 BUILD_INFO_EXTRACTOR_GRADLE=4.7.5 GRADLE_BINTRAY_PLUGIN=1.8.4 diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar index 1948b9074f1016d15d505d185bc3f73deb82d8c8..5c2d1cf016b3885f6930543d57b744ea8c220a1a 100644 GIT binary patch literal 55616 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

            ohmj(wKVBMg2V`Or6Dh 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 diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle-plugin/gradlew b/gradle-plugin/gradlew index af6708ff22..cccdd3d517 100755 --- a/gradle-plugin/gradlew +++ b/gradle-plugin/gradlew @@ -28,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/gradle-plugin/gradlew.bat b/gradle-plugin/gradlew.bat index 0f8d5937c4..e95643d6a2 100755 --- a/gradle-plugin/gradlew.bat +++ b/gradle-plugin/gradlew.bat @@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1948b9074f1016d15d505d185bc3f73deb82d8c8..13536770052936a92b204cc34e72284a03a6903c 100644 GIT binary patch delta 49463 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 Ci1Hf% diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index 3a54a3332e..0ebb3108e2 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.jar b/library-build-transformer/gradle/wrapper/gradle-wrapper.jar index 0d4a9516871afd710a9d84d89e31ba77745607bd..13536770052936a92b204cc34e72284a03a6903c 100644 GIT binary patch delta 49463 zcmY&;Q*hty^L83GwrwYkZQHip*!VQIZQD*7+qUhb$v0M;|1&EJDb4dH>bNT1 zl)g{j{6~-fVJox0gAswQ;>CsC2mL$qJ7jKZ%|3q4>2ZxFTQl2(KO5h@5i;NlwhtEY zoh*b+0?K^YMesW@69Cy55pC*S3ZX;Vxg1D|n{E*U)rea^ z*?~4}*Fh(?6Y(R>;+Qu9`%i~9oGGn`G39r}HFN#jUrcSKBpw3^CajQg)?t=PEU%^@ z320i2Q=XyI7R$RgS({9~vlWY6io%vmwI_*{#bI9snhkp0Owv5gTGJ-$O7C21N8QUy zI-osiOks&>>yfqGR&rF6F=Bt&r-u1BVTFtiOZ(?Lrw_lOUS^@acHPp zt}FqQ%4%GMiJXDG6on}KUxIe6X?`)ITp(}+o_B6TMoTKKCw6$p@M#>Lt4n~DRpL22 zqI&y@$9;vR7*9nZm#MI*UUfgY-NkQZRlb5kUN=#EGJ z{)+~Q)b3DgAiQH}{;#&5#~a>Cl$XXGs$iL{uY8GH^EA{KoZvt~x=SqCZ~EVD42vJ? z16Hs41K6)gU|rs^zTUCal1%9IX+TUphF`2>g%XjJVFgZ`=Hb*2y^o-6hKG82 zT%-50;eiUa-tq&kfWJLy6>c7Py$Wv2Nz@$*;jSK$BF!t-UgUDG70$o>_9T;!5G?Ady4eail97fdDnp1mE0Vm8W^GI$Xp-%XHLr8RnujJfa@c3|AscDF6RNIq8HV;n37--j)A6p(^8lnfVn4_<8d=Cf zQ|LGqUdjI{W9vB1zPA)L_AUtdX97&Eew$~CFfi}7bdR? zM)d~&x*n$a%gG%PkTVGO_b%cPF7TuTbz`mrceM$-OC^RU0uDziC;`$b)mj>bC0N=- zD6fzP=7MYP8^2sd=o}F26<33_(&6Mjm;47R=`mQK$hawHi1oLHJoi^JYuvb3;-i}4 zDgv_##rx#4^;dQi-2B6||D&ap*w$)8Pt^GKdNA!CX{FTW6FKy#PYFa&Dc!p$U)8!d zc3{NU86Vom#e&dFBR&4*OULi`yJscnB;H(c z=*B|7rLzZNWgXyi`6F%aQOf>sOYq5*v@34mhBKTB{|UM!9b~n{*#a?pn`F4-ojn4| zauARhU|`?B|F?8Sz%+@o21wBuY{+%c4WeouQBD=3)nyBxtELMMojyUtr0@uk(Gr9SV{VtU_#7 zQK8o#_5?pG94_`D{uk()i+OLdJd8SEP}Q_tZ~cP`(#%v;KG_z1shn2cc*Y|fAvndz z{4lw9T@oGmB{b;;Q-LO{yA$5&`G@Caw$s%L`1k${(eKaSmMA$87913sit%$(dqfxR zik1QIB`p?lyA3tm%4BJfFzmRQ1n$u_tV3(tLVDR;?FYkhuypqy!fW`au-k9|!}+Rd zV91~jyqOpV{WqZ-?3U)elk&|)TZG&EzAhc~nFa=|2E2DWeS}(_+{(RP6qne+3+Am` z$i2~c0EX z`xI*=7VvsV3NL{?2Zf1J0~spg1NbRQbVk_sI~^km@zw~M$ArlJ4v zU{0gSEz6|iEdxCSb$moJw)jP_;dxlhZ4{7#iJcQQt;Byt)0?A@lL{%;sV*D8-A44e zqK{>`CfB*gENkg=2mSS~+AfcDc~jv5XY1f(dP%XjuL+%5O~OYV)s1Nnxf*IYA^n18 zN_L-+D~^zBa86}ts@zk?`=9Z>f<3iwMFInhCjH&5(l}Wk0Wkhv4?Q2ttkv{#RrMBS8z9EfSE&h14cmSDo zhXRyj)zf7OY0l(BI>G!~?q5Uj6ZpmZsRJhR=?w-1_Nh=*1l z00O8am#00KWgIIqxe&T5L*D9a%Qn8NlULEIDRkQUVMx=-gJ7J*5!B?>=n!Ec8z780 zOQVb$F=SdVSLP*~NPa=6+D#7rf-Id)kqBeZRFjj}v7KavxaFe2NH}R4Own7eq({`T z7`Ige2Fq$#jy2U*q34;ev~Abu9L)aF)`fy77|*6gf@iEH-#BX zya$^{M{!%5rP%GT2ywpGEa7Bsxs7uhwJdysi+G@IYrl}GZ%o9l+ve=3*voNUk*I4j zR9!_fbCK0U*>Z%v)GVm+l-Gs3I>-ETeFAv#K73F&PY)3sT1lO3_;Ij)gps7Oy#@3D zo~MthSvlcLg+FhZy9~l+qgfli#?f`j9DeNLGW{8PCK=Uf>NSkYOKp|&lrfng0ijIi z>^052C3C%&_zLPA5&xD$yj3epc#}O;^%dLMC}DkTUp?CtWtR00b}Ua;un96E?E;S4 zNC`qNDfNZKk&Yq9h%YXf?;JAkk!>Q7ZRg6*ca(QDdV>=x*V(93?%}MbKzGs6C6!~J zu?Q+x8yccBom6a7%k#3WnNw-fQuEZEx^g)r-=*=-jBbn*mt`@TC#Jx>aJBU4m>M%k z@z6olqmtalRWDNgbuT`N&M;B!%6$@B&X2d#IM$($b@Dv&wle!VT{NlIZ8Hv7iv3|V zk0#Ya?9{XR62RcMLFW7WQ@z19ZGmf=lYtSPqa;>z{`JO#m6v;~5nuM*p^kGhIB?)I zqgQM7CI*>0g1r2uq}k$O_q9MVbn8+sM7wghj?lS#nyP&ZB#F*`c@NIJh8vftXJ;Av zm{uI=+9GLrjx{uTuw>OC(`f|E?6|M**mX5l6zS>*Y#y`fJa3~Ev(g(i3Hh(P<1BuRG$$H{VydNYmdE%N|aRCg7&M9n|q z!WlZGb??-Z2YKLchs|2{E6!ruW9avllkl5%k_f#t=UI`}YqNcK65_Afj}7HKCVoox zQQj-7o+bNk8R$jvFX@TtalLW%9)lcx+|*2%3*O(5{`m@dZ#cgb&I_ z1+2yszR4+$%ZL~Vwj-ZGD56a2kno#O4?*GUrRZ-{F@|khP0)7kCBQIvE87qLqAa(V zPa@^9i7bx!i5ZqjIpj5wgt`(li7HoA8DTn*B%Uo=T--q2%ZnOylYM79q`jhG@PnJv zWvy%Rlh&6@8Vms{HGK8PIae3!oFg}EWn^a;df0x6rR1k>rOA)K*&k#4G)mu7N&c2o z92q08qZ!&@HeN!BJVnm#`nBLcgV4}E#TRw%qIV^0oJdk*IoF;;9Nn4|HyW<{yq2q*RzaIW;utaCZ0>vZJL(*L7fTMP8pDUrNYgN@F{+H70f~y zLpS8k#3NQx4lDd|Mg@y=5IpwVeOyrXe3HUjj&ni-541>|cRd51d%wfS=q*o~BUzW` zszcD+RLmgAIF2x8X&EElLat1m37;}R4@S_NO8b8+S zDBIon9R+Ni@Uad&LX_iE*9$B*a(Gynr>eRcs#~%wexKRus4K~w95L)E=~N=w(FRJ0 zJn+&v7M((aL$HQcT{WaIoam13nc zLy3`+$DWWH;n8o>VXYoLSY}rOlI`}e>OX6%wsi@szGvFh5Ui`}JX4I;nQ7ov3k8kj zOK^8k#p*d4-v~SHV666am-slJQ2N?ECDHw@)m3f%-X5zFun)`5>4$VO@XpRju$DZ# zN;muJNLC>j8x4`3rY;l@EtDl@`F1$Tj+dVqkiRST!4XdPa}YtJWMR<{Ku4UhX%P%= zb%2+x$F~(|**`gRo9_u*vuRobG5($Dcr3@&?BCIMBp>m}0$?D_sUdC*d3~^RoA9^h zXZr}xhYcT1iOJUE&A9s+uFXdY%_9g+Y224+1v;jce~VU?J44Pba`cH_GAF%rAnaFj z2i6}^^aiTEQvE5%fOzEu&UX7{4%9^GocgpIT0i!bWNh!?cB5Yw3bXH2KDmQeNyg+} zVe7KI5;6laI}K%i#vf8|z$dJcztUc+OT?Xl*se9ylC?nJ{FFfi`)Qs};Z5_opy6D4YgpA*JNmhF2(6!C)mz z9Y0?9T0VTdawktf>7n{g0K2<9v3t?F3tSH7juYBR&FKU28Yg3LcE88w+Li?2_LM8u z!}1gX%fg98kq3=^g&Xn0lva#l+G$&4I(y(e*BfCYb1WhERf0D|()9c*X>(Ne+l1Z z1l1ogt4Sxru8sAfn!ZB?uxZ*BI0)A&K)Bbsa|^Wqe#hEm`zua>L>nT1AR1*RD1jzX_7-9yPwW@881K~a}TNDVhO zzav+MJ25vRNWuP8ImjJ}yHc6fvGyt&&Wuf?`6~ed2~2)DO+ZBCqe;+qYLNCuMtVST zoFx`R%4=Bf9HZ#O*+Zi$lsCG9?rwR_G|E+*#o$tXCyfbdqDM8bCo)ejlsvIufEvwwQgf(7wW?3*6g;axmAYabxU zhQa>evqGhe;VLE<%ADW@D7rVf9MYT zZXMe&Bk_kCA!pESp2Aeg&4jTx}!P8Y^F z-Jg#3%G#UMuLWwwSvLAe9IEw#Hd?zwM4YpIEfXF)k1RYo z3wJG+nmbcMMn22KI49__HUwDN+AgNe?|ZFy*#3e$h}Efq6vpz|Z>lh?LA6P4)&I05 zgpVOPQw_Fed3qn2GTX>TVHn2lkzt2bJN123NqCR3Xx9a8d0e`6v(k1$cpT&`<`^@c zOD=RS^{FNQ>O||Z7T0klXM(1S7}*~3beQCrT_0Tr>NIQQT-KXTjDbQD`c#%B7ON5| zsUu5a6dd_bXYHSopr`QiD#wk?peu~3JZeHAHuKH4ruBS7)ThXc=%#&oi+h)idn}y8 z?*>gfgE{@r2@8e0gEbf2i*=CDI3qc-Df$|mv{2z=V^nlVnEwRx6NDF#U+q{ zpV1id9Y1K<3j5UUQyKOBj+{_v6rMMrC=D9~F5;{=R#{cN>A`ag4imOrWx!6b#s!zh z?r)Q#kSbNY4uEAj4?H122_1p|Wp(Suz5S8IQW%3& zw0r%p1HlKbMmmMy{DBf1xyReuDL@a^(P2MTn6l~)d^(9^9o*&g<8q?1Ls8>39K#Pu zi8DH^3NyO!Lia{=ym;lPYtn8nHbV8d9m}G-Zf&&&FxH*5UOE2UE}O+R*>g*1dJC{z zbv_iz9(u5fKTH%zo22onUbK=+l23K$9G;}9`xM&{X_4|tP?5F%^XU;Mbho8?`zejK z-fSXNrz-AvRLUbtYyZ1ED9~2Jk4Ud}GD>GvF&w6xzRuw3L}n=;bNzRs*>XC`nF~@E z{=$w0kYdx@jzZ;rM%Z~(+-0V*S~~GLOxKoT!Kq^_uCtdnxx>h(f~4T-^z~z zw#hC*^A4hYn<##TONfFqUh%m$WR#1If8Vlp0pt*3q~Rj{uFgM_@XyBR&-ZfBKSWvh zqm)97Py%sLuTu)YzvK^>k4vy6?vaOa3iG!`oGR6naEom$KInu4xlR^4`g zn`hd;3iZc2_MTkM)JQxJw?D@LdvsjSd&5kD_GB_as5B*7fr`9dTf(d$!!~!byj49N zf2iVs=(jHzL5!PeldkoeJesHoX_zyA;8F%jqy9{r9tExo76K|{sAh^sbJ%<#`k1EK z2XpmWNAt{k^^B6>c>XWDl{uDUF>&7jm~D#gFbN~kNH}*GKo$;|Kz@xnGnlB0JBeCz z%P_}9G93?d#zf?DGd$T z1rlOR+;Jh|h+`Zvk{mJLz6KHn<%V9-x+J#}kP+Mns4x8=z#!COL6Bc>an&#Lq=HyDLaET^27&alAI=hVakJeT-FSEdhLHfgRcW93a8?1qfz7hz5t zE4RwjR-6C3JSQ&*H{;JeAMEV9@e2^Zqo@K1SFdg%b3$m|Mjc(Ue zaYVox0fGw{8OyRr?-)brEYboYu?GP)<(GqWr=UQS`#3&vq<_)&H-vXZd&^ZGh5eA{2}0$!_xt1_fhcQjb8R_W14Md+X_V9njau$_N>0;gdPT4-hFHdBU`9-6 z#J9p#qRiY*#_B^TDJrQ?@eqf%{uL+#I=ZLQSrpYP# z;4^2z>*CZE8UYz^k9~ptHpz z`_E39?`_Xk%)h-{3Oj;*BS8E1kN@q~mH!y}9RDNDuU6COsM-#>5#4S@ruy`$9k@R# zDs%&-Uco_JJ|HUQT?S7_Kn0}PJrmvT3qpdrRcFyNof+*qKIqcej((x(P?Cs+2&j*8 z@X9}a3+VI{W+GO17Mi8=5*`RP^pg$}tTBZZEFvj2<)Kn%7%d5kuhORN+>Q^&koN{P zMDg+$9jfFcJH}6(n7O=lVCUZ*CewAlrfdVc14nd%!E;q!;Q49*$lFh31RF#NWu$qB ziJ5(;foU|f*7F)x@>Yhur+Vk`_D<;iTEhQXXZ~1%y_X!*dpm-R@u|G&lCB?cJaTjY zX2u*)bXfkH8I>IgN|0DvG@1!sxUXz7yo_lU`3+u!_-n*WO zHW${G+$$VkggpVH`d`d$>OzGu#Xj~F>y9JjjftFrlSpi74UZWd6;jDz!UYlC&2|Mn ze9J4DCu+Hp8_o4yb|mlyIWUZ5bX#bV5;e8pluwAk;CV={8H<+TWGuy8JRa*2$mik3 zVd{$rR|_HnM2R2l^JcMR0yzub3GR`a zalqdJ(`1JWAHR^#DuXzvW%jdNAN_*KfryL&hs273r7BRzgc!Xk1%ZuZs|in48(CNt5|RQgY-G zf_Ac){AfU0Z`KKkaN&#sZhcWA&R>Qe4N-BZG#9DfU)>{|N0M)<=yW_n-6K&CL-d*S z!m^yypghKDku|b!5fK0c)7*U5DjO16%5{l}m>vWBo8CXIk8jP5HKbySL5tvzVT0PU z3UWTuOXyt6=9iTk@^s`5Zb;*Vrd!$KZOTwy%y^px#3A-<9>Qbx(>Wn37rpAYx1^kDB5 z**OkAg-A&f;kVPymr6O_zvyyy{#Yvv{_4bh<5LB#9V7vk$?f@J29+Y%c$$@jeq6+I zkUk>TA9e9eDvSe6?eoLlpBvr9Dw?HJCP2A2D;CF-2`AjyaY-2V5vPwz%2^kqSfgIM zuDx9pt5cLcQscoc8wk>yI%9vrsC!Oz+MM5m&;=w2NKZ4NY3pHmcKz z&a69@qN1#-NWAS)Ls!HhLL*w`L^-n4$hV}rGyw}L>_9Rs>p^BLI@3fdtZ2$8ZJ;XB zf~+9oBukr=sc?x&x#RK0t@m1Cv}QabQ6R;(#vIqqG3dQ`$*B^?9DpX8nCqXo%t1uCS454st`;^VK$VadbIk&u|O7-)uFCW z)sV=)SWAu4?5jk=@-u{7ifq)01dR{*3#$!Xp=PA@`haOCEb;0`a>>+-@+h^9H{;^E zdz5y4B%&$#l!|^`ghJMO%ofH1-PMsL??LyBxN5tF%MZfs7~VGlR;7u+1)#%wF_WuG zNA+~VztN0EQ`zWLRaa;4M{;z}w0yQt=hdt8H!S~5{3CF}TIQKfr*jW{vOERW!7+x~g35^9rjWHzXH!pnPCZK3U6`B+|H2u} zpARmgbSX&$u}kd86CE^9h$<_+i(MtLp_dMoZN!4Tus%8TgnWj3M9C z&t`7De$yUye^2?u%DIi{iOCV&z~$o$n*K8}^+D&x6 zM2^RM9*c42uavqd@CG_xXWh3}oqm0<0kPx;Ze1V#4Q68hnV5YhYKI-0%d%R5p5G(! z;1`U)<8A2|&$iE4;VF>5NLS{2V-p~jpYPdwUwTkM`w?&yy2QLU7K*K<5^Ra%txA=8?z`6f(?gJ7j^R)4pTL3Tw3QSO@8nN`gdF_vp zJd5qy{;@2~`6$fknfx1=<4^elC7MGc-)r$SvlK86vcQvj(@QCF-Mwvgq4Wd;i`oM%jD+ zk2GHCMyZet$^pRBrybBh>jvPOWq8V4myPIb&fW!LXNI(61{do9tTbv61nxkE?9{@v3u99-)0Mp~eMmp6G!*y9VtMDH+% zqo1(RfU7RQ*@Y!u({OvR)zy{q zM`>;cGI9sgJ0ab)-HsA|q1;9gc89;Q@W=vbX5gs36TkS!=O$HBlxO|9!)O|x`-gE% zAK#unt&-$$c?gD`hYOW8`J@(Ayl5Q+gf^0nJ;Ho)`(;LtQW&KQ!f!vzU`lLiu2t~_6k}U_L~GcI%u~dJOj50&aH|!B>HV(O5;Al)>ze(7iZo5 zFgHVu1cT%U&o+{-->yy7gU|5ehh+@W&+LY^wN7w$uXfL^uR*l>yn$128&Ag#)iX^; z?Y>z%7zWf&yE?J0v^8=j{&4uJ>m65y>S1LKHb8|9c=nu%WOY4bDC3K9Amn@99e+@* z7-j6={^!>-;cgrC-oYsb-Kx7dMS1KiHcWn9L3&JwuPnK zlQr9uCAky=kG3#U#*L4V9ABHyFen|Z7l@fpXBmQGp+8UITdcDVI-^~t^Lks-uG`0i zGkmK-p!~KiLgTg4{D^$KRgDXRj2?)5O)&T#BU@b0AG+gNJSyY=^SPu!OVSDLFKfK4 zi_1=ttPF!Wi^AEGYS(u{cUclF)tFGeCDZ?+C+Z3&zqaXVZNWd_mL9?gJTz=Pz;Wsu z8x22%{+GhNAL5)QGc$?+16Or)2#_KRzB`laG&dDXZ}vofV8pW5>XsxFM)zlP5AFb% zcEN4o6)DMUWL?TKKdF3Kc8(E`T9J9U0Y(7rl6|fW>TFzyv3sjuSfpB9`D2W_nGi9GtO>0U?Yei{0Y^pb*WoK+WWS<8Q1uqqb zw+J_S!{&z3-S=fnVwj4Ox#Q_|-1U9gdFpx*5cs&I&Vy_~1q?)8`mF1gkp#}NpxdULP*oZdc6;(i!C5Gg;+M0oLQguWo*kQAs9N02G?1OzK1 zSb11TBZxWThJh>vy1@Aj!tDg<(B8NLEWCH#m`2JV2}~n2YoR(lgS6y@+9AsW&LvjJ zI$7eS_$%5anTa=#K+TC{j_BQA7jXIpxt*ut6KsUO)2+7Q_Cs>9u?Y9`tP^R@*Tt4~ z$CpV;1$;Lho$9kI&1s3e2bZBi{{<<68~gH7&yHekB_G~LXY?fH)W8V?S>@UR*Gd)E zbsnM!FYo5{RzqVAHpXC&g(XJ0`r3FM@K!Xf`M-qIH(1eVn;)3)fo+E)*(LX75<>A z5?I*D*vsJ3sIU-9NtOA@(gj%uz~bQg({r5TJ`OrhFpal%Q<*vK?IziyO3mHxn-rB-5OYA|Efjkzk~$D@F_6Xdwye=ACTr>5^2Sj&mzOSHYj)|ykGJt6*jsT@Ov3Hd9QDR*#$(!t4Rmr8@f1MB zK6!q*^S7-n%O2z_9j4acFFk;HEes<*u)!2sy+h|OKKPH(h2x7ZAM1>WybOe9QhH-y;D4bbd7Hzlsx+9-DD% zI!(7M@VStxUzdV$8fA#86)5CVx)%^j}IO8!5DpEVgQ1^Td<$+Kns0MTug36dNd<%!+j89PIOIEu9OZkoWZ4G9q*k zuJvX4UgR+dtB-L%+?nV4&og6Gju{*CUc}#{J^*yCQ`6R_$cE0{^2uJV(=7Yt$hS2q z&!ZXK3E6Xe*VUg1tDH(^E1P9pcAFC?pAEBeVx=mKX<@)CWhl#N+R!zFW7>MKE0tpk z)oq7MGE6nDq+U5j!OC=3ObF=igh?b@oIgmW_Jd=K^^?H5@h=96BFZ z?JHphPA~umjRCAme>A2`o8OMaypxX;| zL2Sa+n$~PWVi+k`%I?R~r1e!tR7p(f*;~`0ZAFa5l6FsBOq@;4eo#X6!01YI$Em$j z(~(KVBrZ*1ZVr93wqUm{UUKxMj>^(f`EV+09bx*S6(|bMPAUgMz(QZDj8zjlySn7H z!v*+OEfzr^*m5;iZ8>uoCJ3abdl+#O_5RX=lQ%2>^hw#C~C92y*Jv_ye6qv_Eo+FcJyaL9K4w_MljA9~K{)MRd$**f)d}21$-z3p zK|?mx=8y6}bdHHAXQ9g*`UV0=dSB<8BrYqby+SfIDlBnd{vCS$$>$mVBjyL?b|~OK z?5%y+5VR2D1?icjv>(A8i5i1eucOlcZIiG_;j16#gnqdV;piCt=m|3i#y7m;>W9`( z(MQS>jqQ~-y>0}h3hsN>v}&*5D6W6#f5+6jFN_cdcFZ-rv>o?`4l}+-LD1HX55lrJ z;LkFt>KsI^aIlo%<_p0O5yz9A-JVdJ9JYQ&CEAgH_I-i>@1cIMc(P~uFMw}O4+ci^ z{}i}CV~9Ym4vew-3E|h5>ybemj3`Mzi;}rH8YdbCBB?YxBn1M5hM$aKQZPQ~42&_}fm%sDnhW-nZfklN z2n|4HPW`uRZ#c?fXgEvXjZ?eqm6~n|fxW7jT&9_|0&Ws3{2CXol%C1PZdeVc(SHR=4BS<*lW!AX&VP-rm7$s$9#5#}WzCMWXYmsI%I%@7#A5w`m zGt?#RIVsifiwQqkbWE`9P3Ll{4&UPUYXeYx6*=>#_At|W&ki0+gp6q3dPlLnCPHu) zJ`0E)58+GS3P-bhTH>_iTodg-6GyS_KFydV%pEfTGME55{eRO_LT7Tv4`{f5MKN3= zN7GqRlMQi^vKQ_PP@^+hsP=-QLM`a4(&`PAb(;NjPChZG=+o8f4RqW5P}LMycmcCu z6*Tri1xgN0c9fr4Q_Fq)XmL7{>$*0OM`hv6W-c(~3JR7fHs%@>o9c@g9C$&N6QdOi zi>8utRm)_GK6qsKLjw^D{p?grqmxjP?gNXa+A|g;Eb7=8<43H7+F89Y;gT7(PXk^I z4Fmc(-?J>9o-7&K*1zLNwI#`;&;dw3VEfx+P=nv>h?f3ag#Bw=t}fP14_d4|Q{;$L zODYi9X~VixJSrd@L6&SrGHv6)_nSTmvK(FRM}tL#)`4rdUfk zF$6VDzBDZ?^BD1Xnj~m>*^cxAD72`V5paY;Gn_xpcaW(<}o*oTbnZ6@Ra*k!SY`^=+p4v3m7Up z{8w;qm%`D0VrdQN%@T1V?7iT&1E&7`-$7-l{7m)KDWCGYN7kHrncTnjWUZT20_Wfc zC?&VH%q;!+@ftQ|v4*frA^>=B11V798W{}=rpQonHd72m&*_5&pE($Iz_2nJ|9k$(YIQRy3IH_+af7anoTd(y z@+0i|F}f|S#PQYlMxc(m&3jx@>);hS`!ggxjVt@<%B*N8)7u|bkV4GKn9E?UPi6VY}Ay+r|e`;9GIfwx_e9FGv$N_R=mZMFb+R8-Tr zTQVNPNTGn%UK>=g0cfH23_PPE$dg;@V%9bY$(eU9$Ap15W~}pf*KxI^263dZQcrJL znsYL+#X|G>jn;%oZ_94fi1z$IL&Qxh<>U_j?)hs;HGDpoYOQo30`l+;Bs?EB%qSoH z@iPIlIb4oG=IvrTg|-4c)ubS1RFCBAkH)|a6rW`4EUkNUm77tq->bygi#Du!L%Eae0;dv;ZGxVG|R8HDLgbb?7fml zMgwhEU7jKW3pgU1828hxt#7F z5p8Pw ze{ht=KMNhD0%wsQ$axK<=5LEXGiEUTlBd%c;k(9uVwN$nn``*v}1ZtK=J z(hJT33q4)u!9^8O}dDw2#nv zg(j42K3itzA`bIU{z~uem^3rsacoj}Tz(StR6g9kzVLpDNY05_J%(vFwKJh8dKF~M z!Aq~g&69k_!W{JfDKaE&oRC?UoN~8)o`2}{%m?M7+qTcvd z6*rhKYw`l4YnPuqj#B&lUj0&_>>N};vXtv)+;Le3LRnT`?Z2*Ltqj0d@BwiZSbYm3 z`yFrfnBc9;GRaP%7P%h$`($QeT=HWuV$h)Kq>qi%_+!R1J%O0xM>0JFGu3N%>;c1Txt>J(_f~Q^f}?HCH`r@lA*Gl}%Gt zsscPyVy3>i)M@I=PQK3*EiboLW}9z~bP^IHO7-_v?GE$b_!@w&vR1xe|LS9^z2v1}Kgl?85=vGSayJOytBE3cpiQ6lo0?(r4vbRG|6u`Etz;(=?9b|p}ZNc>N2bb2|G%3o0893Tc z3x*a&h3C%34ls-H7@_2b&xk@3f4=bgb zoS>2}nuq@?jWkWOIW!r_&u*dR3zP{-^{m6>s1&L!VVnju{uun1DSZvIEyb65&x_&y zKd#;>IJ54H_6|Gl*tTukwr%r?lP9*HsME1Kc849?wr!(>&X@1hxj65?YFF)xeYf_m zwf6jtF~{UcCAB(F847JxD)BXqU9FgUsX7>!$F`e>$tlW;U)hscsDjNRg_6s3=&oC} zqZINxu1zJjL&^AnI{IGyJI^^_^V`hvWGO|?GSRd;Oe2SO01KI#o1;W0=c$cdi9eZJ za^h5hd-Q3?%UI#HY68%pjCCrxqxsC0UQ4iMsTs_EvnuB%3%88`m(r4 zzA<3+l4HsT^6}mlvaD&IwbDOJA>O!ov|qpD>8;5#iv(!qN>k5JENY_+vuZt~+Jsf= zN~K{lqf=2NtAQ-aIa9v?VciR^ne-QL)N(-7>N%g zaxB^wQ3QH{E3vk<*G*Q>ZsKhg&3eXPS;k+Hh;20+ycznTAv%tu@$EZ z!zOv>9h}J5wCYyB@e9LXc`>E+b9V$3@lj^mSpwy&r1>A52`4FBR)vpe$%4G%&7Pv-#5kkpwFl6YM8v&9zvw0DlfGss%!TUb=TCPVX)5(&XUtD zeOeC%{1jzQ&|i{m+{GD|E6!{TFD;_4X4+yOKg~QG3%B2t`L}1V+@$tp-8dm12ec^TLxL7Z}sEOn72k-;(5;jMUi($nmI zz86pS5u9ZodBG($s~emvt`+$PhUv)eVS#- zOK)6wg2*c?%~3K8r=i@n)P2;#vkNs$FctP|ZNQ_-Ow_3c%+dAmF_yLDXvGc|*q3o$ zJ`n0o@Dhez?l59o0Z;>t)f z@w#dbAusSBtjK9=N=)JM+g^5gSn=a(hVue0HSz%LZQ1Jt>uOusMI!+?o-eQi zZ!}fjd1yl__+lYQIp1dV$AIeL7RaQX-?8Bv4v>U{0n{tlv3Z8&oepg65rYXnMX^a` z2}~+nx*WcEaSfr_Df>yhgzH?q1CaFqJ8oIeARqV?AYN2naI7GL;f3^w|99R~+^la% zu+i*6P7t0Sv0LEikUJhJ?gTvS$y+7Q7?6$XzL|>G^n$REAEURJNWhsBNTLtuAdAs$ z3beLYSrTmP_9l?R=v;gYG`DPoO*$fK4@pmy-MPbW-R|%RICsfAR4Bhoj1X>tZCTS+ zwc?gxJ(y;X8C@EEf2AKWuN#CI=)W6U)PU=~40$bH_REJM984f0&O4tcCm2u}`Jt4k zVJgF-pXtHRS?6z;KY@OfPuTUZXh_8ws5!>LE=J)uMn(Il0EyGzV5g2fzA(1$h9bT& z?E%PjLLv&EC`m#hANR>9VnLeNqpbd(43r;QR= z9jQXvLrj3wu4x46_mF=uOcK!FY>j6{;{Pn1^nlaNo>Ver$pQ7U0>IbXAqHaq_0 zUsN&Ktf#GoBtqK#Lcnu>oZF=Xl~42o(U>6 z({yoMS$h;hbR+KrE#^F4`2S;FQzqS4G&pc!CtN<#wX529zxJtaeeOF=fLKmFrXY!R;l#vYC z-hzl;p|H7-r4iqE&{QkFqY3hBGwyShpBZP$7}%M}2l#~|;Y9+j@^w~&W{`a{vpjDYZ`GCU6=6Rk2lE!JTc8BVzv z6v!m2l5sF@EwMPr$f~G9hI?%S97TMMY(;;q07f~bO7&@`0sZoVhyFH4s zlhG9vCP;o09d9e0h_`G~H6U;K)5(0pTL|eUE5ogw01w8LkTA!oMVq<(#)a!}lHVuG z9uY2igxRpCho+bJHpUrDBywb*Dt}82LfI$^mtzj8_9?;rvjJyYNu}X3D?=A;@R&%P zl)*Es`**}&HRH`*%ztO7nPglmr|Z}&m-+UTw6o*FOQNj+M|iQeuqZ z8Q2Af-;kO`>DD0uS$zt5-WKquvP%4=$!pd7xF2w& ztfUg#OG0_(snPYar26b}6RF7s1aj7KO~X3(xKxXT1F1Ps6(kjk*+LvJ3bU0AqqqJj@ajPA0&R zbyCf{_4j3`NZ|&*0+d(IWm>p~c1)(F>z|L-w&;9GuXuB(zNq_GL8r#59hbY=hJx+S zOd)VHo$nvj-TbDKbnYVBFWh6tN{vJQu+6cXThZBGhsVsFD@!EV%&H0vBEYnr+9wJk zMc85$Q;K;(+UT3)G7hD}>Xy*@a>df5W;$9(KS!D`Gb+O*xp9P^{@=u~Z7eR!f6k*d zWg5Bx?Hy#(w_1urqt8{9=Xb1dFcMsdOd>$keQ*c{Xq}&Wln+U9=Sk+@yyKnH|aZrADGtVi=-|D}9@`R~@Fmfr%6{pC(D z{mN>g`oAv~6KE@rzlz~C_$`IWcCAVA*@N1VtgRrv`8Bix9Cq+gH$MT1>RQ_JNgY#Hj z$sRf?K|eM5OKK;7sB+}(4_KktX~`QzA?7)34S&_6b=2&EMmB-6J$br61Vh?nQM;9< z{)(yHO#R#gGu)D|S~%P3fB&-lK_BuWe52bnZE~F zjn+>tHUQ1B7@+mm8W8=+YBS`dWP77y-OWMs>h!Vq>hAOzR_yBZ_XzH%KVsnv=a(OU z`qI_vw=D=D>9narJEB8Z9-O2%79WpCgglMAHzq15GAt-HA3F&C4Y;ikFMq znB==MfzN^JJJY0a91?Q$vXc_hBS9+POg>*~IRQ-N)+LD#pznhFICJhy8W05@uaZkdE2i?bGA5VbuRtAJYlf9 zB8Gn z@D8kA^}`P28tK$)xzBi*a z8LJDowjas>xC{xk^Jj)@1hvYzmg5SE^HfH|N%}gAYBxttB!8=TCuiEPuh498lyVXF z)3qfd>p1mbe3Ox7j|*h=HobVxsYj%4<^g61QNW|^O7a_5o*O#iF*l%TbQY6{b?i_4 z{0aKkZ?4ge4lc`%JcT{>ltucp>N6mXlZ=`pZch}6_{&Kkyu=cmXX6c_Y^U=@!#PTQ zx}NoxZqz%gOp$br9zq$0#;t_xSMUO<_=Jazcz(ipdDZGDC!nQB$Etnqd16(#toK8n`>E;Jviw<_wZH{4&73Tw4Dht5p-8J zFF(BvFk2Hs9aa1BS4{tX1P-6;SOH`4N3&XDy6s;gDNHb&8^|c{kIk!UGIe9ps`0gU zu|@~Bn8F6C_}M#rH+9^f94Ftx13&pY2L%X-1$2srzc+Qe{=VSbN3#FOZpfx6iUvXMKJB8IST52-nnApbB)k`{bag zRWIH|-o|Uo=H0j#=hxHm4Ow1*xD~avFK}b6Lsqt<%wL*(; z_Y1H#-DKKG;X2`48&eh>wT_=$wKr=3_j=t{qpQ7on%lqiFgG}T55&Y#&Uo}c4HB$% zkcO$fsNJYeF?aA5?R3D-qyt)?qmaq-3<66U-DxQTjrX%foy&8bq5W=I5*g_ikL)o^R}T^K4rO6098>LJ&t$zGbI#g zee`Gxeyihh3u_kxAn2E>`;$s8Vm1%ZHqhC~*`#FPI;&x4Ft{&q=lU7W_0N5jIXv+V zQ8{LkXz29M1Td-CYDbK?H?k%26+&6l1xWb8Bls-c4&&|!AzS)m<%j+5Jb{OszN>*T0gRiOt_gUxSoCniQkz!Q9s1 z^YRASBAO>d!fM37|2al4--xJppuoWDK=m5@Ky_`5e_XOWqy!Qo3pyGxT78W5 z*p-wr`j~b!P;xX;T`S@|RMP#tr=!s#&QF20$~jXjk$(Ue3T5n1l;15&g*;1zNBNq! zg`!E}X8C?peeGY{Mt+yh)mhOEuFU4+rV9@Mt zw=vn1Ksp(kO!XT(FviRqbE5&|c?4%57b&raRY`$Q9W+wj6b{2MiL2*_tB7W(^1Io)7tWg^)x)c|Oeg#QrJtcjs zsaXYUqwxbk@DOT0N46@sa=EinkKdYJX$xQu$nxkNXes;qxTz$LDMwe9k-c^`!XP8n zh}!W0>Kip9Kc?*JnwwN&)tVkx<$Cgf@6@cel|wAT@Qk}PC&=n2sYkI#*%F-#iw5Fy z9ofE=RN@n6hR^VbL}?Sl^Is&;@=P;kun8uYmDRn{tVE`XNsgtTgRcYQFKfbnHrsmx z;L`G`Or!c=%uSC9-Ylc`Vrs%dytsad4!WsC-u7zVbi_dE7J^2BteCSp=q&jtfE44` zB1@6zyPj`i9x-wi&Z!JO`#uRB4_6Q-I4IwFPL?b9_syncwDmt0?KFfS%BL?@m<(2{ zsOQs~7As7St_#>)?85mRghIm6J5xUbztja}n%KIHianNG6q|Lv$@SS9$-|hmTAEd+hTk3%V!Up1L!LGLgDMXU2O~!+USNrVjlOP{ zA${;ds<*U4>bJaqRBkE%sNHh@F}>38kG+O^CH~liyQr%6Sd!3`f?klry9OOzWy-HF z+5JLy0Af3kOP%u8#b;Z?+uKXGV96%;Y%!`IcBH`?vzZ@SD5r3F>qJqG_&3<){m|$IrX~CIYd5sTV&y}xBo~1vtk@t&KqYwlzZbB zuWt|YR%3f1{}lJ&WkY6Xa4G!$o=mXgN^syz($l`%8M#kdgNJgUJ>n?}^a;y?mF}Hg z1|sZvVb960p3sHDs#ZvXz0OS6!$IOu#QYpIzCnX~Z;6f=)W1=9!ru8n{S*B2D+pzl zry0&NIrrnXw*VsR1!*IQ?;;J9|NMmHG0a`SsXeNi0S0&eK0i6m04YoYC*u`g-zKV_w(kT@_SgYCJQ%s(eBK`)V*BsK@Qn6B>^H z%=OKwPh7|s)oRC-05D9FV2(4{nb7G^K$!ZrI}F~GGR(z>Lje@DsLsd(pF=zH&TcfF zL!)Fxz%l!cxKD@MB~>fDBl{j=eM6tnMlm&~kIWYhfB#O?9>otFTjjhUZ-{(Y^G^Rv z%(X(qqiVg!sV+`m+b8&V!KJ<-P)8kj7T_Gih)YdTZ^}7vSolah?2hGvp5ERkHG*b# zso0T(E+fIn{sZ|*3vJzgRVdrLB8<~5uDtKGXIvQBc*XEU{NFm_N**63`3qtE{mPF3 zrRwqnt=Hf4ZfLZ&O;18PrKjj9?U9Ch z+W7sk=jQvf>4_s{H+)1r@4~RXvbyCzmULMSto@NYn1t-7S31vers1CDw#`2;Y=(2L zxR^LGHs{vm-k0=lmKOo?#(1L2T}`;GWs#6SG>lmBeM>s~k#(u#%ukIp9^lNS*SmQM z+1PB~&LtdcF(KR_7~xiZ$rWE}Pt?M&-)Ntkt5;Kx{rcE)KkMXa0BS^WDz*KKAHX~f zOdq>jXV2Y8rGI>OSqkBx^ItnzZT-iIDWG|;bGmJ`c;cdc7stX+`uN1)--oXw2RiFt zJ(lt7imXHXj*aPB{+s_Q__6P(BTs|TJGfh_pJvFWmif0fx834LjxWIPb8+csMFgq^ zPQ>&dl+QG8yoM2*5qI?+ss6eM0_W@x;B>8=wjqOSrK929@hMrwvAN+uC=cqusB`c;+p6opXabp(Y8IQW zf*MCS+?d@i$(Ta2b!$lF9$KhC?cn3JOn@M9I!XwF>$`zKyuDbb*e2>$^)w(E*e)Uq zvr1;`^bLKepNTbCTJJ_H;=Hv0EP9NDT`q|%UOPy_GKtOh0i5K$|0dR%R&*4UB?b#443UoNXip?H~cqLgt%~LKZcoh#Q237F`zn`kk%()~p92giM2FS^P1^5>@ ze}wxEP95MmVEg%&`l0dChk|rmisdPE``l;i+FiKzeWN=s5UU64U4Fn` zvXd!HnnGjuk!*{EG-@6Vsxw)63rMjyN!lqnpikL}o%N`F*i_}y^`yl1bnZ!qor5#KuA-lQVG`AUJlpmWPT#sqlk9M?Wa7Ovp z<<;!0ttr2~a@q4XS4Xj)2%^nQn>?0O1uaKmQyQQ4<-nC(e#YFwg3PSkBKgPtc2T#4 z3S0MXtC@2Ad59LN@m}YM6@Z!|_9s#u1p!HvieC zM7XC)vOiRN@RH_YoLzV+z8-4nFc5pQpVYPzi*B&G?i9WUuX^|5XGd$}Rsptz?M^9y za(}cy#Oo3z#CS-pQ&5iL%8Lk zBhsY-X3x{scgkunFm62Z`e^w5uQut_qzJWRR_)^*ju}bgU&O)@AODdp21M9dNQm6< zh{v?b`AD`^<6G5g(V*Cm8qr!(HW+5f)5BYk$D+*Kb<0Or-7M7v*+6%9Il8yt%WoLZ2bv=rS>G_6k@Zl*g>ob&EF!wvsJf>V8vMJFtCx+@rSsw>JS+mP;NhN;Go*R5a* z=vKo%QcdN{03w{^iiSM)LMl(UFGF`!97oJLocLp9hZ0yyh3WfKZIBjMxSo|L>4#EN{wr|g#aY~HRXzfdMwY>F)XcdgdvCEwGpnNsDcn?m249Cc9Ulc)=JoVOE zDTYBx68J7TWPHnw3e31gepm1LBP3{c=zkDqr6iT`Yu@t)!H2Ut-rkO?@X%keJ)J5Q zo4zZiq}09CZEio$H}A>dXuMZeT-dbRfpRMlxo>Z*9kXue`H(AIuwaS~bPRx%mQ3 z3B;ctaMsJ?)mqy)i|hk%y#Qpou`?d%GSdOof3~qPBg{BIymUl9lkH}sx>|pFGUpp& za?J+jS$AhY5$1l#t$F!A{hkf%w%!(dW10?R?AvIuB|MsPoLm^UHIixYn~~2BXgZfb zsty|Rf$hI9$4dyWJVwcscJcns>5gK}0BjA!a~uN5%+ai8+@K+}V?i?QKuN@=M@^DF z@MujGdGato=k|u|p6Q&S9q=-APV|`_vQ;qR0yrR9`=W|)9f@?|hT~EP={e>loXZ$B zS|gdDPAsUnBKaVUBzj_*&14x|QFnFT${h^m)JV9fn~Z? znd18lSr?6IPr8kOmNB7@NuiD$0gNAmc5iiqLI&onEo;{HmyLj>)mL|$v&N0OUQhAt z%^9x9mBagDAXMTWs{|p8hY4%^&yg(72W2<*^ttX+sn<%}Uapu^&%zB^*X>}xI&}4w z@W}d~SJ#wbf;_PuHyDG$wrwmTz&A^tu>N#;>ysfClj@b^kmfzaUkwKBh?<|INp`5@ zWC>BN*db9FbF|%09Bwv9eN}~RY1!Drn zF5_?v1m~>&^#1!+^A$a(oc@-vi8D&fR~2RWR5ttOq7aboBhe2u;0+I39toL#gB{U& zppeQrqT+^=Q8#W;AOb~x?uf?nH;PbKu5>X7kNNl4CX z)Da88b*&5-pIm0SDw{yB(gZI}<21!^&AyT=eu8t;=>dqOo;-8`4{_>-M^{uBn4rpS z=p9G=*jNwb5O={VL2PrO-{-)^Dq51!0%c06qn62U&kma^8d!+kSn`SuXDX=2%;<6_ zJ0RkN>C0^qCn#!2GBb4nSk3i3l#g*LatBy_5Ct+8hh%-o=yz;=2Ep2DY+W%YWdeZ? z0D$fPe|hXK(hHwpz12rMZ7FiOrO1{C5}BeR!`Mfkr=Cwz0CfK>I&e02gebA&e|PyX zw!g#-^cmRSwbIV8lEwzFZFw=iHwKwoW}bYh*3Wf#SlciE4Dpr6_1~ zHXNkYsB;6urn<|HCK-t0MZmu8LS~NH0pguRwk^UWmGq_u)8j2(zw0*lqpeM0An@Ne z6{%Gb2!1|KB#An6k>UFuKU8mBLS8~2Lg)zZ#YY2O^C&C#GQ_py?Rs1-{2FUwWIsp! zChrb;nF`>pey879IQ|87K9z6>WQ18iw$dnS=X|rg)-vPbIwWl?W#YS0qF+wX#mMuN zBdMdR=}POwWAkTTtv`C9U8UwP)SpiAs1f}R`*C^qY8ie?r-b29RcW{v#Q|3O4JioX z#o-8%O;wDA`SvLfYw5j$3?uQ+Z8$3-ruIfX6TQIgN}L!YkFhhvrPRJhf)YkKJlwct zV}=f9!)^SN74z0b55mOpf_!>@oviqBx0oyOW&ncF39wC@TV{qhlgNK|2@ytUD$esHS0Z0xFlT|~bY>N3TGh#A;G zH$vcI(&oh@JBq(8D{dh528EI^qKf*T|(-2z8x;MFJQTOxXr6W>VUQIKJOfKH!_&4S1`Q#!>#AA`d*q z1ft{E2;>7#Qc=J>Ege#XF?o}mC;SSvz-$%%Ul`u!4-vH2LV~CYnvgtn#kY}D-&TZ!+$M|>52 zx#Iov-26x4mOwDzNqDYksqJSHl|wP@@WDagL{8%N#Kw=G+n;|Axxub?_2I-@Da};- zNSy9XW%~&IDR!CW{NGrOH$h3g4Pls7Pe2o-yoViii@z@22zjzMrdFT-+cF{iC)wE96g>E&T?xFMkgAKHxYscvhDK$6+cCt{Y%y2b7k5WiVr z>u?_4AemHD4>I|gUrn2?*y>FZLg_Wha_bhj$8}z+v|mF??xb2B9<-Yd6JN=;O~I*; zAhkLh`^&O~17EnlY$8oT4nkf%DdjeT6_;qOz^(lF&?>PvfHd}FYrx*(Y1@)-4!G8U%7 zps7(Ep+8_g=A*@o6SIHx-l3h~7HP6)?Xl}L9Aq%MxKUHe*XYU|P5NovybP?_JDLj1 zAWv_wT2*W2=bJVtY<$fVA~B21 zJ0bJ5yeObCGmZcUxiJ-m)mrH6aDSE}!dQMA;ptgsqvJW~=l0Ondrw0h#xU$;2hh8{ zVUj6Y9#=Eg#!$^dSE_6+BLd4@6LE)(mVC+|hlqUDv962ZathyD(Z52;0}x~P1aQw~ zT$yE?o3?+fd_v8tQy{wMoIH(iG8kOMf!5%#II1)IZc-@5T9-ZW*YAkLW9jO*f`tL;3P?CK#PghTpP> zp+!)5NFuH_S!RG2H*B+T=lOn%Hw9SR(5 zVx@O=;KTdzfqDZ;YdN!ujOc8X8bv8BdYBs@Ixq|%MuDz5;-iFWdgV;IbX-vk$9I*k|ND#-_2)R$NXOaH|1B#6#Bu^gI z2PIbo@z5}s4l_|T0+?1Du@W=!W>kyQvX~+9kbdn^<;8}3^r45K8&}LAU=m7CAOOj( z499p%%^XOu=_bsm<|>ba1d9z~n1b;eDGCZo{<(?H80}sDWXq)aYjLFCKNw^cO|_Vz zg_8QprVie5G~|5FY+m+t=AtIRVTMcz8%fr^aegwP(3IX?=7^{I z>$<=TpbOD)6c_%M;FeFQxBxhDP00O%utzr^E>}RYyV_~5prz6zo?J-5IB~B(Mz`h>+AFv8piS&W^1y!@ZgTR|G zK|8SsAZ3^DKv}POEsTE$Jn81K6gX(q6XG-piZ1IM3bOqurA@d&#jtRPy=^;zUL}F^fd)eCdu7Le%cZNZEc9Da(TgGb}s%P z)e1?@=Eoen?jl`QR@h~laipBQ_@q^edipPG_R#(Z6%BTEgN25rhe?4PEkC7r9J_)Z za{J0>V8mV@d-f7Mt@`g#nW%+6dDzoc4OgS)))bP-WaFU;%=^anW|D&LEaRyO3ii@0 zt|p7I84!8fpd{x{qWQ+PV#0wVd^vj`b@^ZE>FFpqbB*ns45WDXVZ_ZG>N(s^y<50& z|B?;#&`ZcBBwSsM*vkgFMR_vR;&B%j2G>>*fd|#KzEnf!EN(w} z^rbbs`^cVDAx_QlL!BMY=DSyxGH?Pc4JOxXTKJqsz179c21yD<3gWC3ndt2RRllXw z`756BZs3^9!}}95DG&$jQWrW>LM|(B(e3n;?UfxQhcS?qOE<>e6Te85C-1f#BaA{d zP=zB?*K2Z_Tyj&!{)mJ++fnu2hw&%TGCVYAe*%>`()9G=5Go;DDhkhO>^MpFfq|%- zGUM4;42EfYBoevTrhaIt4s)b?&YNv^^qG(NJ%OqR8jUhhMp5pn)`@SSz#3$8iten7 zw3+4^`r!)t8?jDHLz$Z-dkNw4pb20Jn7ziRf_c%WzYxINC)-@tLj%uc+8Fm6vorJ( zz9m|^cBQRk$FOr)RQ*P1ist1jmBpskDIzWr!#_@WrJd~|(xZ)I_1{C$S4_@ zyf$v`)3`^~WXax`ogWZLRi=*r@3gTjK^FGxs&@)r(7Q(MM|Su~50@+*zr(e(+D@h2*pl~WNK*MQW&aaIw$Y@97Pqxgga z#88be6li=Sg4i~Dyp}tdK=RL6AS?-46ZBIgJ@IpZn9)^xBv2#7v2oL1I_z%a+Vvmh z#fRtBr5N#YN82l;;?4bs+B<6Ti}}lxkk~xr%f4{oUvP*+Lf-nY{IFX?d7ESxm~-1O ziuafsiFfLkxAgoM7Qg4wyyxg6>6B?l*t@pqN}9EqaHT5h>q#7nvi&A0pwFIeZq)q% zNuPJZ`_B zzq&`n`*}_%vJd21xe=CxS71^+0|t-2|5S$SUX3<{;2r4d=h+L&KGMGd?Zc^3fX`d> zhh2OPBN%oppPFOkMq0Ji1M(u(&+oU`4t#e{RYtX9_8Rz+{(={l0r^)T*mGX+ba!J9 zD#fLWX{5rJ#>Yt@>#D9zUf-~4e67@C4|9jPOTkF7<~aD=+FW+hz= zvf3jpJlv}?PwUNvUTx$e$R{(hV2!}tNh;I%PgaQI$RSE;f#XY8}UZJYroNkn!pcrEEO5oTVVS77Ze=~@Lv>KjyLLk^@ zx8JQUg)vTlv~&g}TLtC~&_<4k){?B*%Lz>Fpd&$%mTJk|aZK@;3}+`aHzt5j?Rq7B zc*~3;0RC6#7(;2c0I_&V0Yi-nXkcSPsG}}b^yi>b&UOvF!H_9STRnFDkAOlGo&P6y zUW$MW5f!ztv28W`cbZur1G^$QpuUzPuNdHv(cCHD=TYQtX;3l_`CD5(EEDBuD7~<$ zL50ChGdk~hFa}Jlb3V(lwAlRuRyGnt?o5qE-4S)&8GgKTL&T+~c8Pouj0(>UgZVx#s*&akGGzE@c+_Ky(5Zw$% z#?DtLbGgJo^t%;;97}XPQuJb)Rh0#;k0i3bF8^?APy=%_SvhrIT^Pt72?%I)vK7`KV8@76A)=g+0;pG znNT)eT1JG#;hHMJI+G^gIv;%E8pvQVCPU1Xx?+v8tuD4c#0|$z?vS$vhFMQUI~ln2 zg)@OSDovOg^{7*0C0U%*W{^Zgwqi5u0Hf2vsGa(~U^MUOUo`7b^G+9(Xek(D_Uo&~*XdYGZ=$Z1%sanWPP z=H8-c+BO9b;k0BmThN39NglDT8_5yv&N>}f5VfY_HLMJ$@ZP-Wbs=(RANbJLI`q(w z3D)Y}9yr!I5s(zSrDg+jOmDIccBy}5!0dj?<$Q9KpOg^;sRdCp#|L5XF0)Mb@8gdE z$T>$R8xjkrj{zZ*=l9ozr;s_0Cne3H<0}kVs{{lNL|+4GWTjq#tx@AaZAGIJ0ulBW zlVSmhS}V~*UBsquh#j8@S&lCW=_NhL9@nBe_sPYJHcQIsn6HKg3g9xqi!^P`=z^nHK(}D{@SRYL}9|c`Q;IO(AaIlKCN_b>Vc+P;T z5_y)Rx9VbX$l$f1oT-Wjn5AyfFHgrI_sr0TBc0Hz8`eI3Li%H_%*oQc|m z<5fH@e^dRCD%;&o8V8cv%@NkS$617MuZ1+NWNKP9Lyn709e@9E^qGEX5l`<;(ce}2 z;n%VtZa%kNkB_+O#~d*3cQ^FgkaSU#l)-s$Hy%1dvSO78G~&n1$T~W690Myh$Qcn_ zx)CP_5?ZWV4Bw#C@<%0fX!~@^1P)=^*Sx4J9@QeIcYrATX~FwJO8&UOjCp)sJGr6O zUwc|uv8|IV?uA4anL?va>=SvN{3qXruj%?FhNFo9M&Ufd5y+dqAHp08O_-mYn+dHR zp+3#8CezQ}KlnSAP>F-t;2#DR#9Kb|FKAPI^GAfp<%w+*Wz=mZPA3m8vFEW zucVqFb_W<+D`~YwJ&S7DsqsE(@vVTnnyE=p=hU1x6D=_48`G>#KI&hN4@jg5k+=WJrz`WTrh@t6ZN*=_P2j7r zr2y?r;sXb@F4fkhFa+rlBrK9~u;*ve7Ye9?gjskfdT~-9CbeBzszFUuA1RiDs&yrC z4BS59LSoxwIku6-3ZQ@I3fqOix9^t^H3vb_NNhhQ9uhsDuQvUra|QjHf&bpl5x{V9 z*dmhoMe`_eQ4c5#(n?Qt8RO#(SS8>zt7n$Hj-T2!SdvEGLnZGaYI*< zRFY7KtkHxrR4h)sh;ZUp82Y`p8Y)jKsS?!17T{hZos?{4#6xC5M(h3wgT5%E@CRqMYO-QpQ`J4=2wcT_muxQ^4fwIU{K zegOYio-ZSI>|*>p)Kd6%SZaIrq^Ib};o+u#1C4u*7v+h!@MPGLG~b3DAv(^$S8<6! z#2aBL*V@l=@YrR*ajH^P{p(%)(T~$mebTP|WFupp&bkhV|5=>rtXW)Z7)+*TuQiAIU^cBML)7m$6ouKwZndgpdLl-2Uz{W2V+o z?&gNn-f)U9BY^uZYXvvCfNeyy1k=%`U1f6%=Ornciu3sKD}4-xHTQxW z$F|Jc^Q?BZ74V&61S~2(m1p1k+kl>#xoSq$j@*~zBW_UWT_pLjPeC!C;GV#fp)q|L z!OOG`Z6)zq@`84yxvEMD`4)sJKWX-6?S68@r8|FMwR-yex zT2QmdfpMx1t%4OT7UnOo$9k%tePFNUi(aJZXtFJ#f8t-JQF04}{}QAGZL`+VB*Xws zrr7gdP5zZ{A53$R1 z{z@tw_)_Gl)wkgSE3xNq-vNDx`76!f--;0MLx5$xHF`gH+{g?L`$Qmfz2k&!#4n?6 zCs$=DzCH^1mmwur4Dvf~`Xkeni(W=4A z{PNwVSg#vwu54Dn)qX*4bD>3p8TbJVqaki=(bQ0bMT5DXnEKWUd4E2B7mkt4t9&Z-B6$^cXH#?fxnd#`l~>U+ zyU@?QwSc?y4|+1`bgW=JG2N181077H!6me z!LU-UZxggO@u_5K`u(2D#xwEr`8d}+5sG!fxH!rb@Y;lPv{wmiIB$-uDGRHau5;;a zp3R_ekFe7DA(hA{SdRNTiR5AN=68(f1e?(XgccXxLS7MwtE^6#7Puk)UqshX-`xVp!BdU|J`=e}pt zmp=W1Nf~V+hcH+E(#62p$D@tTC*7=6l|@?Dd5cq4RQC&H5=kvm94Jv+0}iG&UFFsS z=g;ayu$yR{Gv(rG(C^TD!QL6}64+E@P8%=G(or4dD7_Loy@t6alV((?36rT8Ttc5* z8;E_+clMfrwjZR79U-`eUKzxsmH5)2uc|eKm__5rKTxx$V7cr_b+(=Bq**#fZl56T ze@OH9h$R+=p9K$`r1S)e3swXco_r~qF^=jht+Ja>3Pg%r)KR9lQmPOi2qD9t4{$A> zlG8!s-nhq(S65aZz+Pxpa}>N72KKuMydeS0KoN_KaEbCTLSw>$4QQ(m6# zaSb3Xhey!}&{*<5A(2J5Q&Eb*=jPDyp=Nlk^ob^fK^}5brhY<}X{7-MBN>K(!|{_a z#mccn@yv^>$QFzUfm;l_U1&Ia0i?vpc?hT>wNR)(rzpq-B8OA2uSv5F$3R7%h>Ab2 zqJu5Go_V+YyRcYxq!C!^k7Y6RldmGb*uLlcW_8P#Y)?tqA(PkyqP<0zo&y#L{ zAc(rD;Lug4HFwTEE0jy*FA96WRk%F4wHKdH(A*0cUL<|X<0k^X6J9#9$T7pZp~xXQ zUJY2Cp+P%l-w$5Wp1_btv7M{TSd^E}Pn4WUO+Qm*%Ifwr{xXYEs%w(kA9}QyXjwgS z`O#jv?}I5pmIgUjE`ChU;@#@5b*f!C^kQ_^jjh`|mvFY`B1xiKMeY=nJRJqK^&fEz z@9gdxnt8eNcq`O_K6{iQcPK4wVHBgig+sL1ZFG!Exy$g3sfi_gN-(B3bA91**NdBqTlP%nNU^lEb0e5VnUt+8yVdQw}m>I6o-nm$O_P_ z*w<>+in|)+hMaZD3*eLkhUT9Wr%Kd6o22JJkH4HBQl2wx!Bj6SuDQuM4JtLT`>qj+wS5m(@Q_DyY+ znEbt08h!65*7wXV)Dy-o)c2_z8=qt!6JL@Sk4Uhrut0Utym5W3*4&C?35cAdaz6SH z&GrDGP_4ZmlN_S$7Yg0S7=lUk9i-ex%0z4ZF05YG=1Ot?!J9Q&$eU@!zfeH6sXwUhJJfhHMOSMCYiN3Z zmi7+pNibxbZnw;+wyc}(3_T>MbvV?@5LG+kQD!1t8_bEWn(WRdD=5JTOWPOQd zf0p$^rQ1i;g`20)Ga73*YK@a;GnI)zZ|X3H5_m;#JgL|6NmML$tJe8IFwtl@vwt0- z87^mD(=f5H!YU0d+ic@-6Ywaum#Rq^x77PGK0ekO@+|>oL)q$x{%I{lBp*V4cL*ITcn}Yfw!))CUr&6r>-!;C^H5@h8srfW;?@lp$%p0y#j|&hVBvz|C za8g4&>&?W}YRxthdOlX8Ib=uM!`$OT6!-l>kSr+PwR@_kpNAHo&C%85o}NZ`{P~#` zGsCzTl!qYx$sm4bIEaQtC75adjnJeW&JCn{_oZ?S!4v^VBH3i&raXYjAGQV5D&Sex zvqc8Th}QBDB~rBou`9qyD9-atnKWQG@)+z8>Y~}%5{pmn0^hC{7GhLQsUM~kelCiy zk{MSCzf4~?BFleSQ=kdsiFn+T9UFF_E#KZ_z9tGCykDX7&-HkYU19MblDiV)0f{~t1&j= z`x@!WI0GW-mvFz^8i@Fz^8U`){sfWmWp%m`JWRU>e?I!ZUv_lUgdmh?6rdZs5*kSw zv>EEi2eFSIF$cr&XwoMP18bNo)3`R7ws7*VzdGE`KZsz24B+}h2q#`|G*3Xv4kc5c z^R^tlOqr~K<8)qOS);It5U^Y+mE{sRs}?f&R8o~KsHl?mQ@`%x1RdJ?Bvb6An&5fw zjM#3C@hTavP%3UMxaG^ z8RIL2m%)d=a-4RH*;$axDCgs_Jc#oI=8{&X`YGn}CSg*%K9g8TgB+P(dT5>m86p7RZc-6FuY?c&)+X zu*s~%aCL{z>lC3U`Z~Y~)^#5!eCls4K}J8Y`Vk1SYB&0c=3WCF){7BUr|vpfzimfm z71IfX+HzZ%ol7Jrp(JpMEd(E%hBwj_iXW%Z*FuRQ4(5bk#1oLY#CatOw6OJT ziCSP-oY-=*4)Z4L$Rds(^TpPoeZvxI5^*A_b70DbZCYrz15^PAXB{DN{odP2k9bL3 z(?Jy?38QTee{}LDS-UrBRTN?-#A|juK`WumDuf@^WF-!~3`L;EPIZnZ3G=rfXIl-V z{>*Fc#3aV_;2oY1F!=3|Q@yc@OSz&+VgVfr$qbd=XA18nl{}kk1ZU_ooM6?gzOq$& z>c5}l3Z?2V&b!UYp~wY=89wVs&uaXGMxz&gfu%CAqEK-LGi^Zl@1b93Ro!(hxDAiN zg5rZ|KvyxuKs&{GB`p67jio9Ed-4ff-(EIT+a5!iI9!7-;baP&Y1)C?7tMSOpJna& zXLI&guA)dd(YX@-`HVc|BHJ53i4|eseZhOwHtpg)-F7~0unmNuHRcNC8R9%{CDYs| z5CjYK#LdSI7e>;d&?C~Da}Fq}IjIgZg?difH?^t*S<;ew`(eH~+OwGM>`5m_;$Y#fj&Axfy6PpL^_TFG(!7nQ7Ogq*C zF}*bdt>b7mppg5Onr|b!4@0P12tw)^7+}*U}c9RN}WC8FjGr~f4WhvrdIhT&?J=)|EM=!8TC#<(|N-pRoLt^KK1Vd zPxVUHFGd2MYa9(*oRacKwaXNr_7yw#@1BCV!F~~2wuHd68X0c&6u!I6W$Vy=(?j!P z=Gd$q_p!FkHe_ByWvoNVI5;M9;Cn^&>7%eU4^TzKb{8M)L|=5L#e58AVnn z_KO`5GXC6E;di^GVQrldAWt?{%5b0wfEc$ZqrrZewq8v8@H<%SkRAnu6Oq4 zAhHA|AmdI*Ag4LjZImR5x!D9=9BWN(I1GLidomW~piqPvKEt~=v7z#4E_^=G>2FpU zZ?K{QH5cqsR2DfL$ZB#K4%~!38-6Bd+J*S#JY4;2T)L`fqD9}kIRrx|rt%#4+()ed z1l7Cz%%bJC>l;kkp+`<4j2eKJ>P0=1?LD1IAUjvz2V@_EU@yB?Vh@)TURv(9r8VVT z%|{0KcZz}pdC3v;R-@vfqn2Sv5AU*?j1X(`#sZv7Et8Fwo60ZK-Stpyq0`1%@I8LL z_nchj=d#uw;)Bw87^%l{n@wJM_6pr_n?deWZ?GryS6!&OsR>T+va$k3b`tAFq2z0n z0~O}0u2YIF-?Stjct2%{zy{F?42DKqATuo6`YMh8LMJ%2M|!%wgKSbg7VoaiWR;r}5LR<)E z^m9h^j6VCH_6d`XbaPk=no3jvo=^`_E=qOVE?^aeSL{Uu8C~VaDfz>1kO(|<~&1u1$j`6sjWH1N^0uL`s7gR{rpS~L_Qd|p)k1% zIu$#P*$wr_?qZ;FW!RG@4|kr6al;Evzo$UICC6{v+l{N}I_Idam=5Q}kF3jA%0O~5 znQ}WeiX!`6IBjI&(RHh;EUZz;MSy6A*TuO8^%e$)u4y#Grc++qR%^_Lbx5dKTyA<; z(}=FFFWCo=3VzhDSWPSgg{;@)dAP%n?^0^Zlxc`hvLEQrRmwRsq4KgA+rwkAQ-&GRs zUU6z@nWGV|>X2I-KP0MF?zlx(Xvx-D|D+oCTA`LxiH zjYwv6`40h+3#oYMa!SZ=?Y7)zPVMVw%n~GnG*>K??;@y$zFZbMU2Fy#W!A}h41%;Lw;h15&5U3>vw=xa*5_pK6>0#fiZCFTM z@O0tyFPp!xfWExA)1aL=8(hD+1C*qLIR?l3bCuU|(|=f%TKei8dPX-Su);a`M7ka|M6?`7~Jx)A3`i z_<+xRux7bU6Prr^Cuw8Q%ZaZwscJ)kg~E#Cz4}Wo%y5=VYSL$D0?%B2B~;fYka{lN*q` z_UtHTrT-&RU$oS$6AU;G{T8vVv;a=qSf75j)K%bJXLRrHyc7?j7Hc@ZY(wc`_)vT; z>0vkI3ZUnh(l;!(&@$?xD`ta4H-TN+7ZT{MLVnSWr;!^SG&WTm_>m?_6~#3nd54M;8dUo(a$ zpQVfxa8vey`TyfSSP<(h4lqk?`=8O@+l5_=$cZ7joH$${{BuAeq{s+-03(?;`=Dv4 z*fw(k{@`3fGsPRj)%Oe*pKBNl`RJ}EZv6eWb1|pmXw>$j;nykNZC?HsgRais@P_CW zd{6Q7dE=s@`l8BVk&-v5%Z6h%2}lGo1UUE!r_DBDgvc_?-Q&3$zz)ACqf+zsrTL}V z>;qw3pZ8v&Hu|DG?R_mpn@;7s0Yt2(?lGe4E~j}8?URunu8h9u-aMpg+`|ITUh!H#taD1p=dTvT;rq#@62;#-XEnJ#Joyg@RTje<FPaL<)=fwf`Di)a1Yy**65aUC2#_0cQ0*5@YIwhE{3k=$BVo!_-EE z1gU#vZkP$9GycE(;x;(ST2OeV+B>!ThOJCkNa4GVsunw6e~HpJMb6%f5M1k>&YyRC z+~&qQ-}1;Azawb8x-G0Ya50@>*Tp7;;21fi{c3FoLh=2`@d-+GtOZeIw?aolSS5qD zv*m&Es+gb9{2E57jT%0}qg^ARD<@E)!RL~PlNS}KW9fuA?|h*AeEP#YWz9CRdk1(; zs_v`ibp_{nv$xzEh0F4U%jomrGnq&!C2@!A^zE(B{LgO861K-QJ z&d}3L6tb%Bk=7Uza4743-6IIn{nc6`$u^7z1P*N0UsOjx1U>aMyLqhc9n|Zar)oLr zQdqQhSe~>6njI!*sYI*~V$UyCw8ry22#AKa!-_f|-~)SIa9X}@q#va>f0DfJBTF`V zplz5|t=y-PGVN`YcZmO_NMd2DFr$B+<`#NYuUfeGO?=F{sIja)_OZAq3YQngzRG18 zi1kd0_bWS{a;^vSx$V58k;LAs*yJ~r1rk*zbn%e~{x^@DloZ_pl{vfE1$m1Pv}Y@c zyWZfenik6M*>VG>*CZ1&eMd^+aeiSs{i>)g6iZ6tn~NHUT!y8S zdLl3WYBr48;~t}S*E>9|=3=cr=twog8#)UXIhyO5GETbYr!bnZ7|#d2u!`@ch7jQV zPHo_kSJx+x4aUpR)XfOt}1xGF&Am7C7Gun1Z(qr zYwEJJQ6aiQX6J0hVm$UmF*fhTDe+LO4BAgMic=OW^J=R3JTmG))ne#GhBoZ-vcl~5 z+qVK`3Hass<|A^Li|pfZNOBWNtDzHYg=+)8Z8KtDC)CA$BA2n%X?YoSMd&oCFd-pp zTN<}vv`U#jnAY8lVKk|nii(}rt8tCXX*0W7yT+5@9vL<1?t!pFE7kgC7n3V|RcdbS zuSTu!e7=&|$aB&Gds9YPV+!LXb0@45FhrLgzdM^Pdt-h!3nI|l1yLVleJ!?4m@Kx9 zXYi|6x9a=ZI+pmw_NR1%jxE~&&PT&4_^|WidL>ev29vijWzA6ugyQG)>PUB?Ip8TQ_1*YOl~K{do3BaR_eZb zpCbl;rZp_<6vSwHo+QMO;mvjTAhotQYnP2nVM9L*J=wyD*6%dmj9f(dOlW<2+FV_x z*HV2^4;uhn1jh7m1(e5lvZ`S>eLfddw|VDejh)SchwhZ9x0aU3M*j7i(2PR*#*m>6 z57;PmuJ7B#BFxmZ66uD{0eosVJ%(MC^;cLO5qEVLTCJp7J9SoD{;??ov9yABc}k`p zevYx5363crKg_)o%yH7!#n&s$rzi(9p$@%U7x|ENtOrCxhTD&Vu1=V{dv{+i6;*Y6 zq<})y-n=^N7O521O+N>KuR;|I`r%8`ONey1-%9_pOwoaIwP zQ{iY{aK~X+4kt|%UZBoqHdvvV550FIfwLT2fTieRrn#Z94`CY79M#%%_6tu5Rt=H8 zk?l`I4OSpL`KXlY`^o$+8Qgxdbm=WAV+YNT(jy2sY`AmAlI#61nK|iGoFMeMt?gRI zlB7vbRSs%)=ck=w3j)Cp99n$qAV|R0QFoZmUw zt_aamgBXj`B&6iY2&!M*Gm&vkrx@`N(XLh4uj0rwD{Av1uE$LRG>DspU&F1}*V7Ob zexX}bO2Hn+M2LMpvt(b*)GjjcS=g$crsU;dm+w~ZO;uTKn!ljz(8Yvzb5xBbijiEr zu#$Ax|r%k9mo>BKa&=_Yn6kyj4#S z9E!gp^C7Li*qWQR^VB;}Z7B_B#{^Wgoads2q%@Am`GB=sU*~R9CT*gYA7m+!+*Xy)qGx0f~2dRPz#OP|CG6gxF zc#i0&kV5--a|NU$Q%l4iaN;J*5GRJDBpcJXl0`1-peW@vof*%Dg^)aU?U4+W3VeXq zRK^VrF>`~g6yQ34%dTwJ` z)ROqwIc39sbo0hb_9C7|t6uRJrlJfGXIKUk3#lYU`$oJ?6Wwn#;rYA2ak0MUxHY!H zfY6dyMB5nC{CFyqm1T0}-H-fPsf7f%#JPRgVky>z{CY{t<)9lg*)DQ8M&44ub=+CY z_llcZDg)3n(<5K$FvShZUI$fJV&fYe4{0gWW3g{kk`B&;+NT?hA;_AjwGXI3EmoIs zH;wIQF|T{&MFDf^6kmQTi+8I1{O8GH(CmU?v>m@IMiPb(B9K+B3uZr-fPG~spbGM5 z`nvbF86RVucR)9ZRNF~3$dB0}1|##y0`swgFnh@Y735QT0EYJ#(AiMo$pSPjIyW7F zL+Zh&#Bcagb$ud(hNI|7HYN=~(Ht^K>{m7%B&#NjPgPuK#`0@*Q66UH`sOI#Q}ubO zcs^9I;xQlx=ey??HPnW9IIh^IRX2KmYHJ%#z*YaSz=#ywAMa_DTU5lFFhoe|z{5L# zHZ!mnVASe{Y zb3h8VQs7+CUD+7Iv@s7K#LKZ}*|f|0@Tv;6#Oo{SZ#c>z6OTZZfoss+_WErN?O>~x zj;nm*TT$ggg608q7M~?!U8n&&?QHJlwO3Fi&-%PC?REC+E_3SLY7W0>z+QvE(-AU? z0-P@tp0&{ZZw%7X2S$f?ltC97to7 z#kT=CvJ4KShn3hCi3|}0m9B>I=Zgniyu&Gp*;R}`bBbuy?%rC@E*-1S03x024@mY` zxIg4NKfirs(z=9J6(Y$n(qDsGw?rBsI1_Yw%?l1m;jaWn)6q?80Q+M$u#Y#2v!`c0 zZ#p%U5{j5NN`=sLH__n-pU^241ECpUd5&wiw|CChH;Sj&xR>gflEW7R6R@>yR*mR0 z=jB#X2ZOs0{W9)EN3RCV$3~xmMx)6DTA%$qSOa%)d}6)5+O6gg-7*>KGt8~8X02$o z($6i0PPe=w)@j|#fZj3QP<)~hgxY2~Mehsn350)^fzJM%9AP^i0$ zn<`3vZ!l7$&LV7OxtBd$RY}U`757TZ@MgqZ*IYLyLIboJ4MN`Shnt1hRs`7?=Wg*&mQa z7#%@K5*!7p-9f@sB@Hh?h$AC7W~iNB-tr#@w333!WDgz0+x@~_8F!xDKO|!`WBK2R zB?yFySGR@@WOw(%$IT>nm`?*rPT)Jj;(taTcI2EpssJHec7}ALN1S#c-|7Lx0^uwT z+v6?wkSYvbNUKM+(@YG)hy{dQC8~iP>17H?c&BJo(517V1P1PEoX^aEv;Ka_JzB#0 z@x(hhQGn-x>YU+i_#sI!Hq>6A<7A-T%XfwWF*u@G==4R)_`Fom>gjiK9DKU1FRCw++1n5pNnCS9|O-Fz?# zu}<$jRb4?jg9|~lq;BYCsN^+pg90%>4XBYKtUG{Xw%zx5#RoInCP|DA#*$S;{^@ag zCYCZ3pW0gL;i$0G(hZirI*aRWeMMYUEWLj8+bk9M;@iz)#jM`DY3=PDRKnLns@Ycg z1MP5*{!PUldnq-Id@@*ygUZ>v->z+L(I+1%sw}FoB17v^i+;qcipJ=d8{U`{uPZHj9e?+>7<7j1|2|Fn>?k0R(7>}yi zM{hdr#$>CpXCG0r-O2<$JlWnNl(Ojpv9(n48v$HT^cYx!LFkGd!bR zVci@({Tk0ukM}Zhn{M89O~%6=++YgC&>YJis2&eex+U(Lc#=UXxbPIP_S3qMgjQ6d%E+ORJX;REaSE_wn8RB%M8-jq zUlfEtH-xW55qBJi$H}bOJK8shWK#fB)eW|kygJs-%YR{cB5s7GF2H1Kb8SVMy6eY@ zVwxWr_S+Gg_dH_{vr5h=o%Em&sZ-CC>KLhsb2Fd)crC4DDKEL|_}Kk$43ZY>!9T0; zinPI}BJ})0hz}#PJt=T49Zh+pD}UH0E2f0I`wet`_PY+qBMXWD1rH-u@- zyR!DiV9FJ2nsf-ilkfo64*-%uHnqLF^0t$U6eh$(~c#Ekg#^^fwhZl3oFFHxzVVe@sY^UUWv=Qn^o$Y>fHS-nFSh`YQX? z3-%Rcy3~OC7y>GRBe#JCdNx@gokS3GubH;Wh%;gtML1qeqAt*0WXzjOW&^gkuY&%b zB6aWHEU3YxHN5W|Ynwxx;g0jAiNC?yprjLmM;;vP-i5W?7e0Kt-J+OLPAXHW_ z(jS&Wa#Ok@aZ-lACX%3Plafou(gMp93rmAY+^;FjsZfgmw!X1MOM?N(0_Z20tmYPO zAqQ4HQ|T^!S3&w3IAmX0@aZRg!Y^F~RmAX`)1TZt5?<^h85;-@?@cZRqn8=7r3ZgB zMSn!2agM<9&ljU}GR85nK$#`$2}juHJ)_gtikY89FgjvZot<7%3g&s_GipRwwQ8nCBJ$~>i5Tfm+xS>L?Z)9J&`8w+l556 zrma*P^QZU-$8;M32sd7ZI)X^pV}wP}{l&iNUhtt`TE}oL6h&>_a$1U$ z<^yJvBDUNNJ+QsSxY*DsPnYE?9HL4J_bTGwv^l>6#sUqS9}c{LFVO_8tg^&?(-ANB zI=?Xd^L{FuE!+Nz$?ldrd5j&9vZyg|nu>QKr`i|v)-&79!rPJ!$zt@~?t$Y&{~d7x zNA5>75r70Mw6G6c5-X{VOZCM#!U#|_2^>1I*JR1-pO3}nnY5On`M}|uI^=QlHZDD( zd~8=$YVSFqu{8SJAe6`Ww)-6LN{* zVjjVn@A4Dp zTLs*rd^x!=FwoF11a|CJ)=I?;(S1XQ#X}sUN@xZ58wP_CGILZf=Wh}U@XJSM8RS$SCjh;f*?Pl_sWtm>-S z_)b*pGBe5jQTay~U-q|}7@CU;KLNj_?$Py89;loyV1hzkB$?nNWwAfK zpi-x(@u}AMn{^jm!I{a~QA2FQr6CYy@Z7vREHmq@#x0{Y=JhU+8e?1#0$GmDTw9Sr&HYTQ!1kuM<2wD)%_(g$TuoaPn6o zG_9|Abycq)349@IB(B7<4I<~x#+5nu80fnQ#0hc8AJRR6f`Ja<_uL^vEx`vZHdjQt znB$j-UoOiY{Gpc%j(cpl9w7B``Gm1zNg{?%>HRAWOSl{|G(G@v$NL{M>i`bxIRI~F+lm#`uh;h704q(zWIJRF)q-TYn;io}|73M9 zTy3}D4k`KGY?c*79R}nW>7`H{1B-IG7&L;ZZ`udHa7lujN_DpxkhzYuxTQ=w0vqy^T& zfEDDf0)KXV>fnwGCQTr-8ojM@&!G1en`tkznpH=*0dC{QFq!yhmAlo+B7K8%{x?*#4ZXj~y$l#OsYp6AHDVNYv^*>#` z7s2Yss<`;LiAI4{K__vUTWo=y9(eto-kr8JW**BL`vrxcW=Tmh>nT9#lAS=~`b%Jd zqs_`<6oNGUhxck!_7n>`iv&92s%WaRz|%{{s%|e?SYF!SYL>puPKyJ=(36Qe56^zM zqW)+FD_^D00={;7;-l_j4?J)HNn+;qD52_=qa@ja>2*Ki68DEaNSY>u_wz_}DDTDY zIEB8!XD1p!1-=6eBYc!0p^gkxP2}pz%2Jjop=+UNROru*WW(O4r~cw|tmY&!2*lKV zCCvIwl`VP?YTS+91)1(!JyFbf6QozXMJJya1|pX-tZ?xXVHkhK>KBQIzh-LON@NXu zKinu9dd#!9&j&yYnx3?XyQII!(i0_YxZ3fw92s5sD0Ri?;;1EM|Gv*hUzBsT)(79G zI041ypn^-c9`QX>)o+Tn=rDWl2p})1O(`KdC0Y@#(~^0Dx>xuouv~M0Lo2BhEXJWSx}C*UcH`kXw+T4_=boRO2M`L`9(& zDvo<-4=-!IB5C}#0nC)iJY>Ev0o%0d<{WEF*vg^I?|#||!u1xn{+5J63M zd*e)^?PBTrqOODspV1mAZo|35($ReFh=NMUv=2OIv1({G&frp@T+v50kmPYSv3lLy zL1E#a{H~d(L)f;ZSiLrCimQz!NmDv65jGHze`%tB?csCb+TT-DzlY0jmEwfb+paYF z>*6bU&H?vU9y0|CIyY^l7|>(>^%ulu6`|rAQTx&i+vW^@LeVxwxetsUP(HE`QD&LY zbb{_z^`}RS&Fm!mVL%TCF42%RFM^mPT%ip*t;g&j_4kx<&n2^ZMRufMaiHBrv79t_ zh@K}+F>;Q}$Tv2@3v+Eu(r7y3o`ouTglr2fdcJ7EYbdg$+{yL?R8pJtR zp~d;!edOHj+F00_&q!VsOv@C~8@Yq@zvBe2Z5Y=zaF~QRXr-MCsHeE_5le8h=0Y;S z>`@8Y2s_wJxxDlNl04i}N`p;1sJF$)Lj|_|y!aerx8%xpFA6oa6k2-00+@KM@YztC z@;YxmZQW;D@Mr^U5(f^8P3;45*3*`!N>KzFBNDY{7axR1j#A!oVzj39GRaXneeSv} zZDrV=cWx`$>xzT}B4T`=uyxe@)Z94QoVs-tT$U1iI!P;)6k^0yv+D>$!IWbW)G*2@ z%Hk7K8=b~WFFk=M>*<1+A9Ae@QzzaoGe|^bbe-~bjn6~B!3{*-mY`>JNbeFX5Z0U8 zLKZHaXUZRH&r@mb+-Z#XitVDSuh(e9Wv))OAhU_tEPkE?bZOK(tQer?b}Zy9SG^DN_N*Yr>xa2# z#QQp03W!OdrrpIM)B3nWEmO9|lKWOS08~F)8yQAm&RUTxEJt(rsMCKdw)y>2v20oF z$NA}I`o$}}p9DCRd9%L>FXLocEk+}~X$TNUk&DKl~#gybex#?1BT zz+(G@x+=ha*NdHYx_LlaSz$aBxo=XJ%zWZAUwO`fqs}@kN~Z6X*C1E-gcA42$Zej( z{l(83a{S^o9t{HNjn(EQ`I$zBscxSipjrXO=fPU@gAU=B`R8a6O_|?fzfbSs4!n;e z&m-w`e4+&iexwdGnE6%8{=?&pDkfK-{_dFa<b#cKY0Q!cHFD2+>oGibvC8R^v!hT? zXYN27A(U^OWP3m3G?*O=^m>^WRwV0p=Id_1?2BOz>w*?|$XC4(RIVbTfB&t=2BGUd zB;pH}V?zRKhVcD=%S6ygm(06#yaujrS4e2YX&ymPRnKQgr3QrF7f2iq&KVA9@Hb^R z_}TJWWE^l2Ebt79?+1W5`xy`%?5rR}z$-LCmi?-bS)h)7HppJkML!SZ*&kOl!2vV? zIgdQU2e3sW3nByr6PW#XB7p?$4q=1*2j~GB#3h3T;QuUwXNrFb@KP@9A7}yr9ux~U zbvyyrxa@4&g$llXKZ5h`Isbx#;r;`=z~O^r2hjkh2#2;!V4JpBA_xewzu*IeKXCLQ zF|=2JTt29J5C=ptgbVs-Q~QI|06Oo&{tj@kaRf311jk<`NAdrZofvD@DCbpGzbXt|0)6jF~sr*$_7I}&F*ZO!I#C90Rn>bFDQij4|Fk1 z1o&UG%zwFS**}oL2mtVxSK+?|cqvQa5A=W5g|vXb{O|q(iF!-+r0R#l?Uo|2c{sCV`h2H_KKtMAp zkntG(yDckF%orKqADvwR@HJ8ew?ekRilJD8P|P_%Z)0eHzXVkOf>Le45I$H>l@sun zoZ(;4XD3i^JOHe2NCWssnGYX)lUaZ_N%t3Y?feJ2v`783$$v%az`+@Rfy%CbKt)I7 zKga!#BvCOKI0nyuA9tQN7^DF8OyC0kqXh&$-TZ&m_SNSP1e}BcDR^LmSSQf{|EQV# zljKAPZd`x2ZEoQIhsgndX@n?&yv#_AW863iY zV=e!8*XBX9vy|YZn}32YsR93lw84Q-bvyV3vH!K_9(+B7 z|AGL8e<0)>;y=6hS3u8SpiT)GBnSN*!~y*K9^`);mgqOoW;;2kX$}YQpFt4#KpFq4 zDYy>|G5=|K|NFrBmn$6veVUj1zXwMAJkC4cVNlt;**nEi5ZVG8;J=?1;12Jf zx_0eed#%;C%@8r=5WjigUe{85MZmzoG~z|op>^+EZ(YB|_Y%anc*0-%>ZM*of`Ki< zC0>!@C4T=#2VT7p(~1r?RI5HCZBrWYrs8Xh&# zDi-cU83e7*jI*5Xp=7umi7ub6t7)vs6tit7IlDC@4{k;`PfrP-k}a1f)`Fa?CZ`u1;iN?XcVy{wSPnS}=U<&Y00G{4KklVufjC_{)kKn=^Zo|F%pk zNbsQv4V$!snI%K&S8=7Pu4w6aSCJlOz!Il@vn7p3lZJe{Z7dqwg)Q!cvVpPOVtuP? ze1WGdxbL~Y0OeK2eQH{l=@BI2BAgCmZ9`SIGvGtXx02R+sl?**IcYVmIN>sWudmw- zvMW(d$Wx`e%ZOJfx1{LkV}zM-ZlqJ0{5#Znq#@H(RcK*S9a_ur6Su$M2XYLu>l)*K zr*>>Y*RhzVm}q%}RiQXYG2V@S^M8{UlIb(tNX%oP?f-2;zcL5|&)7})?OP*Ol8bR4 z1YnQG>MQNiV(Zcs#v9nzcCUkJzc2ACr!>ce%TlFJ=0!(zn)!9?&9EXQLjmCl0(o{xscuF+|rSZ zU04f0j^Tw^;HXquD1Q)JRl?Q)?S0sv(BTph~Vpk5ZkIZ$<<1 z8tnlmEqjZ3wQ6O>B`4Ghsnjq%K(i@LY=G`>00YU*RfcF2}z0<)Xx)OBf&nbM0=zOclw{1@fTImxx( z?Gv|kbLz|ENWs}!9&c7nS>cuP9R6drgl`_YDiPJUBx{<+l5>gOo)WyL>JDyGCoG$8 zYhIBeyTDdm;`dOHCtbIk=zWQlIG`lDe$aq4Lp1FWQ#)t<${LXSnu`LS>(fYFtRL(d z@Nlp8otP6kgbi#U2Ok-S8feH?k@{)4_Wr5(-5KTYvThnzt>N{@;Zgw$vL+bz;xvPtt6 zq1~iSNm`@kKc2=_*wWGwFM&!k0BX0m+}f>MeeZ|&iFLFhQUr}RG(9dnQQ6K81!Vu1 zG0CrfmVaOOnBnaCrYtBbtZGsR)7kxq3HmudV83Nh%Z{A-{~~oMb35P;Ce9*b4Rl9) zRx0qwOXb||*9BiuNfwczi4Si{Oc+-te-Yrlev$iv`K|x~i3tV<3yc0=pA-YL3AV5* z{BO7T-=avoCB;s3o`6kUg~kWMa{5S|$(RMj>?Q2sTAFe~^zk@w=gbgMo?u*VQ@yFY+!JCLpwe@dXQMIK)IzF#@FKVD`ot z3Rksy_1pCKU6i8#9L~DU9?Fdj-Zhw}INV}Dn%{Ab+qICF)zfjUlL%PS?TR!y9|5u} z_7Zw4$ef4(&Yrr?enX$T*?C_1F}L|VMs%%I~4lW|>-QSmMeSJjbj=Xmx@m4W<%_&w54sS1}^#;~F0rEIg2=#OTG zq)g zOk!>$Dq(N|BCOg!Ah;lzq?tanfh>%MBjR6LMow%zER&r>O@+J*I@&nSlO5tJ9@CTE zkuRWq6dlR+W=ELD@++cE%9m52@&4}|LVy1g8@ch{NKz#`f839Vs78JYa03G}0tx6< z7uVzh=mktWfStuwhX>&1SWe1ILH0j4ti4(0nOcS1vctAV3syN z9tB(lJa_v|cU^Zs9{0SSPJ^$|G2;TVJMBTHof-pM))x1=tDTIQ+6QMWCofq+#)B-N z7fT3eM_g~L98WXh@PnJ#PJ@MOp%#e zCH7Kf%wUuv7>E^`|CfiD69U4-2dpX-dKqK zIqX1w$&B#0f7FIK;je_Jy8A~N_w7@M{O+P(lhq*LB?oVZ#V4oaG(5>Di&qb0w)HTz z^-iTeLsVtKcc1-hIE1eh!UfD0MK!hCEJ;Q&FX7?hoO__n4nenFOWTq=&J9r;+Aj@F zwnv$i#ODc8Q> zkA>zVC~-)GW++c2cqDw(q|i`jZ-z9N`mrLW#q6F7EOYM*0+Xyae_0W8`C8$aPOFoWGwrvG| z4g>C1K~uleqH|czDZi^|>(Y6N(MBf$*ZV2?OcHk%e!6A(=#+T3o|!H=n$v5X6FWnx z>g0^7tW`EL)Dtg^1)$TE_H>0Jy-jXi0Hjp1pu0iGz-8(q$2X+{{jo}^)CJ;7E-&b) zu-cw<**;-&%y7*_IX!r;F`iNgM={9wb9_jiPJqA;L$v)w2rx)lvKv*{FO&UQw4Q5T z7BD|Hm6K7iDk-HBub6sJC86Qqu~OJP_tw%IA^DSy2c&c}m{e>}%9268s;b3zn`mVk zd~;P@e;glVMV_i*!1#VgujGGpUSR?LroV$4(m+X9sdLX~&ns3(f zyYEX&@U7*T)iE5+x#o9a4!~0JA$QV@&y)MFxpp_ZA|3}RgSsnH>l_+{xoxELjO<7Y zgjby{6<<dbq2`}BS5*t~VqKiEKCSzzi-Bt*W z5?bmcXx8o*#~-gA`>8*>-6&}ODV62``s!d0HcC(_6i z0`y{%sn!u?etk~`DH0CzPnd3j?sRaH0X%xLZK~{2C=7rMMyG1$fra#(yOfMN_xw)_ z1sXiTSw&Tos;IQYJp-0kSz(fD1{r^2>U6a}+_7?+Y6<&5#OZW1wH#M@N=4Hs&=JPy zx>S96jc{^2C!6^%-9AMSV=+GU?VmDkV2IB}I4(==$w4ai&7gws^vHcIie8B~LpI$^ z`3AD@HZhzZj#YbtrI4YHTl23txGJAt`T3kdg&q4TWjnH7_aV!~x?-REk9N?~gd2oN zsUAtKq*KtVE2x3ZNOqL3!(zGts#Wf%_Kgz1FWc#K&EC5O|5%wv~+HiYMLan4%JUgpvJ_ZT+hekvF-mo%!HCEuG}>UR}bGw}rE? z?X<>VjdMkx+o4>s*~PEj&yRntU8qa@v!$@e4&K;8_K&=>nNfKie`{kWs41j5F*6m~ z(!TGuT7jyDcBl{hoQb+ZPdp(K`2GHZaCWDo`J?@Z{x9}1c_#{ocbH2ML*ianefPtj-$0Yd7OS`1c(K(4GWNOLutx;r4XSPbq^O}DAU!_ol&cUVw=vvbfSJO>>3f87OR;^G1lgb=7 zC-0(B5>XJSgjGUke2C_%SutNU(tYU{tn*tUoZN}U@+!BfGp(_e!;*!;&lju7fBJjo z)Ab&k_$54#LUbf+VhOA@Af3}xUNy$=t8v!Zj6UqW5M?w+Q^A$t@BAHI>ppw*wLB>^ zg$s=cH#4Kj(r9)?WUu7Yz$poiWxJus*yBmr!am*2a)uDL|`_${3(Y`-!5l!`Bd8>Yj=f7x}pD81tmWkF#+fGAM*~;a3to${-|C zMD~vD|7s_vP>eC=Wwm~`V%ph(qj^(i?Pd4RRye85z&9$S#L#K#ZansTpBh%|@6vv3 zN>;>g1Z4KTd|B2a08}2l8=(M|IgaV8q=^WlG2GetagRU4jot4ja^`x&9UDS1$d_u6 z9S%`;6e>%Z1F>p|rhVZI-Iw2r-8kBw-)+E|S1Uf1Y*`MbX0flDP80gAjnN>6L441K zgQLErrvAI8MvI4$R2~z0>n{`i5zUa+Jf_i2#F$Gh`SsyXpt+E^#f-^O0d~8ctMM7qpvPs_)RY z7=e2M+@^eU`u_Mhwl|^^na5Px;;-&}il}zRGnJ+$FUEp={|>iXkQL zY{LrXwxRkX<57*N8#WJsI>|zgzm+h=v|P_cyww~5T(Yz*a4j1&KP@yZR5&>(TT_^8;Nkike=&~V>wu{*lq1)ew$+r>ZIZMzq*ujL2q-Z#^JwJU&!t9*pP z8z%4vau)>2ut20{3_8m`%q_F{AsO^b2i_jQmGne=m}6}*{P$hL8^xCH7Jd4Oc;S|^ zc2DNU2(P%H28`zE&e@Cwrj%Yas`N%!10nK&Q0DuIw}`OlIAf4fD$A$fpE%X_kb{)- zR-vtwA?3a!b50D4+wU{#br?hiu5XhSH*WM?wQi3XO!CD)-X|ZTf&?_Ny6q zlG+fOd!+|w3AqN9Rx$eKgq0Kaq{d#uZu=3v??tVUbhAW3I85kueOS%PNT!yeh5NR0 zt_Ug}ni44y6caqNNih~Teh#gy8%KA2dZ&bylRt01l*v;-RkygNsB9VFqrKBo4JOn0 ze0?z@BYTCw{NcqEHu%IN95r(;qVNYaV&kBAW3_oqX@^&HL`zBgoUti{`eaGb56+xB z+lBlEqg2tOJA*N_|KcZB(TwX*d{d3=q;V$Nzr6{GzE@{am&^W3xZlHe)H$MY=PgOJ zYVxjC(~WfQQ|4HbL|ot${St-davae%645de$q6cWKYe{Hdf|;XQaY=CQGEiyvvlJ~ z2JYS0_%gV7o_UEr_g~SGT|vk(;6v51eIBe>q7c#>x3_`pYNyW z#@CGW4eI&v&m3#FZf9@oc;bL|Qf=h{i>gmk_g^8ZgsHIWQgMmh2KhFaBG+A*)hEb) zlu;E{`%x8I!PPQ`XVkhN9l>4|R_xML^V{zsLQLqth8~}EI@uQbq|c)b2NsgqSA->+ za*D-C4x2vNw*05jnn{j{Uf?{EDELC}WZ-r&8{!%(<>CM}BC3gqt_q=8^gc0*#Y-Hj z3!GI1*WAt4VWWtoT(ty|7+p1WkQj`dDGQcQCM&QvQU8`#8b8F8votL1cPvm+$Od979L zbW^H^L_%5Lj_#;gke8z=&S}wMi5e-wXF}(NAMibQ0{?&FNMHmjnh`V@*dapVE}j6O z?}l-V7bvVdw|36Ojm(QS!Zjnwz7qT(O5Z^#-KRn7_Qz#EM0{y3yvw_(vPSENf{L8N zT9JdV{92gneC84Pe7Y4i8tQb2K@&%pV`)!bYTK!jwXf=%f5)GAlva%C7lH2Z}F~B3*aU9nqrd#%ka{GO5gD;6K`JEG)QayS(979pZ6+DLaMz zt|I#BK@w1dq3xnH;DLmI-%U58E|@Li1FHHi=n2~USkc*q3fkJkItpP-(|M0PTQRS3Ol-_luzgY3m zFjAA65ivVabu+w^j%xLU~2%2@r48OEY-f(WLglSG>@^LJ`dY?o3 z<1_A(G_tFxuuy7U^H(h|dh}d#-Pk0826psT{vKR_TrAa7!7b%nz7(6^1oNI=^$!*i zMgw~2uZfD;iK~AC6DN2g}M(@y5und%4Ubvd$W_J9Cn3*rF>@FLgghQ##?qSJm2KCrh7F)NrclMhpK?ClOLJw$0l2SA*0`dy_K_g4? zrkof?n=D-CZSS)e1Zuz-beQu%Zy+HnYXk(iw4Dufcc>GBVE0=nQT4jA?i+OX&ujb_ zVk0t@u&lYxAnnCI)NZF8m{!mZ7uN?WkM2b{_tc`{BVlSHvoq`UV)ma?-yq$)Vl3GP z+j7c`Ln`p}RH1E+Ms^mARM3u0eBm-uRYw9JRJbmjo%NOxmRov2M-0-zl&a^4LKe!BSh>z9a{=+%$(7 z6TBcf+PqW;c{!^MFhTSTAZeYMJ}{Z01eQ4V@ib=qBKuiFZj=qjfgQS$I8_xmf-j}4 zi)G{}XT4;XE(aWLGht}@UOwu@P(v*Ypmd(dnq0TDn1_-TC}|z(E=PQ^gc*+4Ky7%g zHKl~jD)km}a7~xP+(lzez}Xw3;9j04O|W=GeHEW@pWq1aV4Bo`1Npd~(}r=4OBcCJ zROwk1c4pYXD(%NxW~4pkTPMa>l*kfqk!g(+ZG{w!hxG&is}r5AXl?| z?7#4pM|w4-Wz$~`B~h#qb(49yB-ghu8&FU5( zk>50!@mE$mqRu}VObA=UH^KRYh9EdNWi|4Z!2M51i~rFrw{CbfKwrth99O z01ys4_~Z4+AOpRuTLZB83wndJLc{~5ys<$351=979}LYcm=xLoD1R4Gl@KR@HsX?( z%(K4j`p_`U3&w@|;4c(=$uGZ$8$?cDhZ&Vv>w`XYr&jzD8MK=2V2XoD;x+#iYX{jV zVsbpw3+yj3L7CA4_OCF(oa5Fu+#)+Q~K!bH>|N1 zA}lzXF^BVxs=r)0-GQV7xY3<5c`GC#F1|;yIt|9_D864{E^Hb+0FM`58f&v(;*?75PU9%tbV3KOFaJ#BX7kaq~^pm*_L%7bpR12p80!xG4i*?(=+*lBM%X zBqUAmEr7>;I-5HDTb5g`w0W`9UX0Nz~6t+?1#S=vh2)4g{p0uv~ox`-Y0f zm;I+x|4jYOFVX)3t(&$SN-l{=t`*^l7$-MH8rF8+kB&`C{X{i?)>D&8r|++>4U5H+ z7XnlM#kX*S8tH}zgfl+%J6=$UyOwBrKC%37mUdocn^*$>pD%<921fs1EjqbcCqDn@ zZS72=0ogh*{us-6|3rQV+`ZcWv}Ywp6a!SQoFIj;zE95qHo@>PB>maxbtl5lo$$V{m-?A-#Q)Xpgh^6PuUlO z->Njj+A1}}o1*ppK7>DdC@fgC^BL$bDn%q*Mji$C%0-uoGJ0q#Qu5&~8A=j2_Gu13 z1ypzwl4#wc`D+Y~+_+%n_F}*bm+z?4H}A+{-O8LAb34SD4 zJS34Ar$l$}jc8-}E3LV<7<6Ba5Zyf!1p2EFwcI#BZQt&9B)s_1cgKpE(+Nm2PKNR|g&g!u4@>(uHG zY%)cq!qXM?edqA!_VFZ0H6*uIxmLD_PR}N8%RN<}uFkLUC&W@GgR#+)9b>4>m$-vo zACV-$yOG^O<|@eRi)J(B2)WgMzU{V>C36<~`pc>e9c)3pRn_b0nHUZ##)QeJ18=e$ zu(`6Q`}@-Loda7}UYqK73bX6QxK6#dH#GC5 zyZ2?`AAw28OU9VY!a}vxsLle^dK5YBL-BTH{Ml$rbRY)j>uXhK?$VH^aJZ3g@$Ii{ zJ_@AP(sb~mJsSX^uKgKQ5Wy#-m0o1?eU zhd<;hh^7YjC_beZB4YIUI5PvfsR_9pb^c45H3C zqxZCsT6V0EWL!8pW<%lSfB}{pPD9NTW$7>gfpG2!ku7s14HG{w(wOe0DQtVEWsr~h zyDK9G$4P_IKN&dN7ox!0U6hx?U=h#@$5($*23=XUuWywM$?}c%htk0IEeaB7qkI=L zr@XJ<5dN!t*Z8HSR}JOaW4xO%u3<+ISzr=lw6#304u+S8hEos+Xi;GKvTj~4p?f-6 z((&VWpj=T^N5AedI00YqAvK=NNoj74zs0L_U)LcU6fDsT^qnyX|6^IgB;S0GwL32v zNxK-*9|13-iDB8A?QozMCeS3ECzvZ)R$wp(vd3{dz5P`-BUxOgHVCf7i1|%Q!>&M@ zQEkvnY2g=@`=dB z?IacwgA6m~{PclWh=AX$M7*Y*Y>P0Tf{p|2*Ij}@RcXxk%{6z!@hZ+s0sn;vy$l^P zE%`_?rK*IzVAGC-Om$_t>AP857dDA?yD>3}t1$NlAqw$3>}sX-PmCJfs9tj76!AQRdM>KA#U6la$TxMD7URXX2y5Jl;e3$Z*w~8+pMWk zEer0CT^nwK053{Z_o=XNPV>SsI}6GD{AKy9V!x~&jvCX5_7tRySz$URf}MlvIup0F zkq_0=0-Ls-;7G6_d&lW5Jw8{}hrY|`LyV>%E$tN{h4UjJ^DjZ>i)aTVC_g?)%PP;+ zNASncR!+~yP#HM-alH*=?*@TQTen$R*3wmrW=p7NK)-&uD|{+6^bcIeS7&q=bLi>t zwGV&g$T~RYKyz>j$-@|m3fffHzR!^d%Ze-W6X7Jh;mk?jpPQV`QH{v)+tLg^?_In) z-k%p6i|SPZG)Ir__4c7j`WeQLZq zB@e_gwZ*Y(Orz2?H^oZIK+-rYBSK@ThI^4Rb`$XL~@o;@K zacK|sVWNPxjsCzl^11^I{(bLI8CGQ98E+_8AReaLP{I>j*Ic>vEh_8|nx)+h@l4p- zMqlmPhq*X;x^YY0|1Kx<+5WIn=!Fe^wv~=0eE9zO>s$sX%!N^yk&GMTy#-Qda0E=ZV{#V%kRhp^X~SU4Zua zQ?y9(z@tJ=%cGz_zNE5Opv6_jt!%pLTxSnI-Uh9uXm$uGuCBvH_@H9)2+5q&kMF9N zIn<8V;YHT#!L->Ko3b=(h*Ty;kx~ekSx+o58{Fl-#v9r0BX`<1_?4&5S-}u5_lkUMBC9`z=Ov!^N0|Fub zaf}ux5hCy5s(q)e%pN$#{YYv~k840|%Puh*aS6WV1&yPf?D7~pIi?jJaK-g_JRD%2 zg%LnJV%VfXZt9;0XWWT$AeR>bW}-9|vr?({Eirl*b^S^j;7`k{>$IBPA%;Pz?LL~y zo=Q_a98jx^*{Ozy&BmhGEeAS{9o|jN{km1h#%_63s8f3haQ3to%ZVd3+#|8I?x~xf z?%4}|+FaaL^>k5_x&|3TmL1m;QWo&~VVTsLZditOMxzYm2T0Ku7 z%24RMl+9Bi`msy!7iTU2N^Zi0dz-pyp7zzf39udwpjtbzJ-tGH$7%g#IWur=_*84Y zD(#E?L0^c3We?S)Se~pn#-(^c?aQYK827!jumyRsd3bR+6gR~r%?w7j{WSW$SwS>C za1UoJcY=^M6`XeX(2H7Ja>PsFXiQ`cC&90U|BuVm-Ih()1VBFv>oIiW4%+M;YFx8|v{?yRTDXuWa zfVKIH0ioebr=lS9Qa37a3|+!0n7;WD~oTE=_4Se1%BEtLs}X|o9}vot(zO=j^4JE3#8G+@b{ z@{^w8o-Y+D{Lb3-pfmHlT)2h{Ddw*oSj5WACA=cAlhRFHM)xJy1y}r4s8?QY(QkLrhiMYUqb~CT z7SRgCYa9tF17X&qt^eiKn6dKmQ23FBR;24~)Lh5OGfy`L1HwVkk+uq+Tt_@tD%e&H zBx7<3`c=xI1Q39f^tLj}8@1Q2uZH%59-p20O>Z}cZo~IUJ^#5I*IR!2ogfgeB~TMR2LK_yb#G+t0I|&w1)uYCC{j${=oTMXE*%rg8j;ujMz=@#8{@43iC`(r$JAmfR%i6 z8qd>}pSAN^T?|Cqy_r~!wnZz3RO3_WIk8TA`uyE7*Q^6VS8_zhmYp!$i?>4l#v>?2 zK57;sgK+?-VDUb9j#k72CgSw+ZM5P)T%6OU6czky)TnvJMr(tr7|S7i?iW9Q6YEZMevHL`U)gG;h@-Vo_18@%D&*TgmMytW=5^0?%S zzq(`RSf^q+?l{i@&F0w{qS5!rU|g=2Z-pM&^J9Cu>KEOWlZXS}Yr91AWxF|jG8-+> zC~*Wr%m0b8JLxaI8EK=RxzPyr?y#8i>`;81tbgiruH|bq{t=40_mBAE@T}v-1C?oJ zrB=ZqElhrsEha_dnNz%6(i?wQgPc7^mL4rzn$rVrZGORCOk|yQ8G?YVSF6^)c0hOo z!Dr((umHQd9*i@JKG@abO z^NNZ~IC}R~7mnmH9U{dVwEM^#%PJ%?G~}vHcCDXWKlR_~8#)``$s_+-Zj`VBp@w&l zzC$>YRV-^e2cGyWp1+5?W2(I9O zShEX8!SZpPwowr!7I%s637448u_fJCI4XZfEI5bow#BNV_*0Q{$j)C3*GnFNC!J{z z*e3PNieoAB>P$A{$ho=+w55{)6mjt}eC#g427)rw!ph7>lo-a8IL2f$UsK8d`hVy} z1=tE*Y0+&|zPaS$4~ywdm9brD$C=3rg+iR+Ay(qB78nhaJ<=mhlXxb#p&~RivVn(a9^yY)4vvgw>lKmq6{)(0 zGQSQVt;!PIqTxe4L6l!xm=r?iljp#K2QZol&L#Z?MX=&zGBdgwkZaO5Sgmw775wAw z?3xpSEoA43WxU$e&eH2D`}oB9-*0VN3YIP$1{hcs9T*sC;w}qOViylA0Ozl9*d#*3 zkuHG8tWVlo7b{EYw-W!uMmAVdLKJ*Z6wA@dF#(}rP{4~+ta2$>=hAFbuaVh9fbgt#eeHPtZR`A;;WO9InmX}-oV3Td`+c${>k*jp>W*9fdU|k^ z{EuuAZsT%yH-db@1IdtSz{-+zpZZSaDIVOoFF$a1jt zN(s`5w%l5PN@xiYXC!mz&r9h9H>Wwk1rx352;pZmgeiI(UKR1uDU|B_t)XnNafgx(ik+KskLd)BwHRS%42)>^qdq!yx)mgWjbe5$&mYwH>>|s5 zy?nma?zn+>CQKvcdFAvseba+CO4FxO%I16&&ljVudb)pQ|0BIC9m;ZZ9~$VG5Y+UI zKQ6Oa7iZ_uKfa#;(=q;P7>w(#1gv^@621l6ruw__0M2`xQlNc&iTG~yH{0E5T7S78 z+@sHPU`=;6B2J0dfG3C^yvn5`cOWn}qR#i5mvd>^!lAW9w}50qHoOc((Ll6~St0Kf z9H`@fk} z7iTd+PG}Uf%1Ottqa3u~{5G5tLx8XCu(r}#iNc!kX7AHkZpXKQP=uHdxu9u`)JOfGC)}` zK&)hgR+8?BKyW^P-(RfX&~8g+RwYEAWZmJQliLBES#l<{YeNsZnA0Xu?cyd}N*ry{ zGib1^^ehBsocobTkM&Ilk?g4Ei>Y$+mTBxh+$R8)c?y zg1=$yW+yX1f3F|%me1IC{)@h44e`v<7NLYF88$rlQh?Z)+!pf|U;Fckq^-vm7%9+< zjm~&CWi_Id2C;TGd`jljsi9)XO`PyA>0Y}=quGHNm%k$(mA>;~(u{OthI4bpOgf!K zR9NQtBv^~*Zq!)a=mSav;py1f3uA857vR=XyvqbuT4iF{nwhWIh5CU5DZPuEh@}OrPO-fW3Snw=9#O<9sapes&|mnwqsW>5Mu=o=1G+g219bCszLm@1QDA0W zv*iE{)9NajGRxSVO|(}jQ9 zsV;f0pt%$wAuG{*4y^ zJGE^2)5!-L@+o$hS0|&~5m&@Zu2&Ay4xMcV@A9KeVbDyEP_@KNrr#j*u_E4{YmYZI zuc@@i9RX93GLvgn7j?F<{Xk`YNDa^_qf~$L`Q~#wPI47&R)TD!vT}z1f<~5<`|Fx5 z?}c$$XUs#go493StBS>5z}$|@Rqf}5Y(g-_A3jlV8VL)-sn!ENGRwcFB7EUG5`tLs z4Uuc+QHL=nu6ne7?2%4)vR9ypSR|TuKQ~uYbX+dX>&)h_+RUc{PYya5ifBKE9(DffFrLY-rvc|m!xI=6qJUs*z z<@2I8Dha#znF>K7J$+~%mXt}H?Ea(q$x0s~1ei8PH0E)Y_06`~1hS@w#-`*+CQp7Z z)Y_6|%~AGbqk>e;H>`lXIW*u*{j~IfBN_SxFIiY-xy&8QuzIfWAx-}M)s|Jb8eW|{ zK)E=;y;?rEvjtj*r+bG`@wQNL8%GycN7{s$ z#C-35`23GXP730=y)GTAy*edX?n-#BB9*P*$*>-zQQ;57gmeG>c>K6%-=bf@{rR!n zblqDw_IwXvRtKXg{1@HMGJr z75Em8PDI9o$Ir=2>niQi$-4y|-Ch$3>dI$!NF}yie)Rr3XSV5T67ET$rLF0*!Tfhp z=kM7ZBd4!Tja{1%&w>MW-Rv1s-&|M!xR{2eILsgWH z%9K6%11;0g*RO0qaZp_EFT1*2eX6>sW~5pEhCD||Mg_G-7GtdSoSE#HSh{Oi12#&j zT?hWUDk(-q6G9^z20frZ^HMezce@WyqV)>r^5OL&N4WSC)Ud?aWHg#hPm)hkxiRud zPm;W)euLM=APXQj65P5&!u#h8pZXVoTf<(kH+coNGNg#2?di6A*N;K?SAX}BKD2)2 z3(eJjh5EKn`Mid}YQn6CcX@li12E0>WPev({0sSy}jJA}rfl?2P1qRZVltoG{n z$Nq>WRX3fGosy~ui=|Sbr~pmCUDc1Al6}a!%&+bZ(F35I$oO3p_21OrJyv`BlQ(=> z#)FBD6n+#-8yuNfiWiTFyVujsMM0b?lncDZo!8!0GKnG5LX$8wbFB8B0KTXEPXD8M zOk{_|%&|p%zx;zDQ0CAJuba`8$zJ2_H&Ae+qWP8H`gQ#AA2p+ZXH5pa!Q8!rN1qM^ zG&{;#asyO6+egZg9f5RP@5%Bcvm}^bqKC%!i}e4(oFw)HUHWBAGx6fRr9mw_54F8` zU@OWKE==&IbfOCQ1cw68m`?Asa4rC$JYXPqdUO7jWE$nlJcFTNP&<*I2pVp@bkE@t zUep)cWv#!o6VvMIW9Rtcv1$yc9O4@h$(w>!h13OZ^o zi}AuOlcC^Fjlw~d2J|yPISzc^USc5*`?@!D+$^T?IZE3}IQ-po6A4S*QbX?$B3*tJ zVI}AA^BYV_x2ztdlU%tV5KWTNdqLBys%190qadp)SIDsC-x}qvXXr@?%TnK9KvbH% z0K^#N1_+k(*4H?We`=9ajHKl^2_&&6Ak{k}q>31aXLnr^jKQAi%e0SZ=92S_9?Y_V zE`s2MoL6DZEjBObStQ_-bD*H{I2g-NwDc#spwd&r#IO1O*@zLMQODJm#4&YA|D!${?|!>RVB;S~ zw(^rsZjD6x6hGD5I0vsAxU5=>pooL*=-0E<)S#;X3@4Q*g-gKLVaB_Gsy!f^5 zvP-bDZ6ir{LhQTYHV{nJCQbDAqISR^^!scs!49Kj3w6=_?*!OHS?iX0F@P+>+Q3%X z8$Csk!qPnNV~vCiZa&DN=uv|NgbA``!vr;yF)*Ir$akb|IJ*= z5OLb@k}RXGmT!3o`WmuoQ)M~-6Fkrm3Es&6IO$PcY!wV@4PGXu zRQp{{NW5@-Ebh0OmP?4gWwMOTglXo$qfKc7%BB4U;;bY;2Kz*Qx<>RCpz97{HAa~6 zAoYeGzlo8)auuoJxt}qfQ^a->U3&}N6`-M$ z$dU`kB^(`x{041frms7LGf_)hdr?{-obdpz6zHFpRXkIVn8ZM8N2OFqFDpEq;ubT& z#BeO%9^VZgc?ZJVl;rsd$p65eWvZ)%@f$zUdgJHJgcd@XxzypxESV=dc|9Rmxg44j zsE0{al_7B))B)pohc6{8-dX#Wv8J`9GM^!&(78V{%kh=*U-V>ale3FGZChqO)jO)w zi|Ah~dD=ByZ~_N1@xQu)IIAVmab1T`%nDUOg%7E(yz4eU(3UqpK>UF9ieu!HG*w0i zi~TTbe=WpGUlyIYEMwgajfIyn-@ZHz6W4>K@mA(N!&nItTvH%@D&|aS{)WH4D;{H| zDll2hiinlpP>ylLdg8w(MqopuC3(vrovIq_%jSud-V7&@mv_+(BTb)&V8a%WctzDi zo6U7D+>RnGK4`KGd_I5`;VN{Ti1EWVVE-Sk&M`QXX#4s}CYacq*qPW)Cbn(ccAoHw zZQC{`&cwED+j{fApWb_`x~uwgch^39uk%~$D8}RWA<&x(pBTJijJKm`69`O&TuE{X zZvitL;om8FlOe~MQ;|zqQ+lgkCX(c4G~l+Rv2)vOg2^_`${Ht>W*ig!E^m(*wfvPG z-8|bJpBs;;%0~-W{CGG)R->Asn4Z8IQ-1KN$>V|9ARSL=$YcO)^lLY~aL;aQo#63` zDgIn>W2x9s4-Y3`jVzq}d?<49#K1tRhEEzQt**?^DYxoK3V8KsnG*yt^tIXn8^Gx7 zD4K}y52m$wNusss%2iV?8vz7hKxbok#mvU{re@i2AK);oOBHO#hYdpt7zZ@?-{1K? zc(zZdT9BpYzzYE0h4cPZ4JuY*RT})W>z$(S!M{9QuPnG_9g}eU0&$CcLu>wN zUVzX-Bu^!8>$>`3>?+M^zpSIwm`V5|=kMsks9+q>m)=15_%yzQl%Wf3DK!$uXg(eO z7$H!Gdo?YVxdxeU$Lnefl9tqnj;aq1LAh!so@f@DJz&&&U4mWgao^RpX64{zeYK+AA(yfMeyWJm>CK>))(Kmf z_jAD?^Q3?@ecH+;?SfE`#ioVln3?@t#nw%K$LXzEG0Y}nJMkVnb3X9|-1=WLcn8kx z>_1eChOnMdX9z2y8A2riP7o$c}M=wjzF`F7t)}yJ8s&2Aezya{@mX zf*=w6zPwuoRUScDJ0X;ab;HguG4HvjvS8 z(5rw=F-;{8122N$yUP;qE2619YjyNh9$iw8iEMCOX;91={dggw39TZ=dsJ@Y+x;}| zi6c;Ps#YycTcb{xoMFwWQEYVaYqP|a>9W*1kmVDK8e9YpN1l*JPf~;g>S+MI2Nb!`0%65cjv64aAivMDEYc{_{5$gm#F$4cu3Fng=be5Z@5%=a}4kuj$vH zAsYmLPDolW_5Pg64QGA9NCE_tuIp@O;A@9)+f#j`K3|n6s_7XLLlLGmjD1jj5N5_fR~t_t&v=Ied!)Wp3R8SytWP$bfGnr zD?FCs{Ml+uS3I6iu^vNq!xbFe@(-w0exZvm(cW83G@CD|ysz|N@K93`8el$@4RXXu zjJ$|!t;S>9IjBCl(1Vwg2<}mc0H18cvL9=Ht)gnQ(HOtF{j+tzoHdZh3>xsEm7XW~ zmE9Z$#&|G1o=r&jnRBmqqF*+srg|Xd=}1w<^$mn9zndL%J2qU+vwb0c*m*k1aN#|l z6ZiYHpk8)Ws`80ZPrtxMUI-3(9kZOgCw=nU96r=Wlj8A@N0u)+Ehoh*r9U?$zUS^| zuJ|sma5jA0x8#|zJ>!}=x`>-)b7#o89$j!=f~%%-PEBB)%f5jR=JH@_($;meW={%CpSlATghNh-h30D&J3Q5 zzT-hbLA2<)LVI(yQ^^1lt4&s({6%&df%1Sdt*NbyHlu z7%-5l-I#;EyE8DS7e+NH*6AD!PMHnEEogTETA*G zpL{!x{FCBlrmhvN2CYjXz7aU)yNsKSxGlI=v`L)ZET%axlpQIvk^%!?1N6oEpvTq#9Dfe#DxPCz=@VT< z8OfU--QUZ%ibTP%shNCivP5u7Dhk>rlqsqv3n=n1b}MfhrA6`8T8?}9BKw=I=yG>> z8O}4L#fnX#*N|sBaz>how8GX301@H&iKNflgKPTZIrX7Vz}*cHb3UjkA-o#K&;GYQ z2^B`AVf2=Tt@18s=Z0VDk2#M$8jjGPGGm%m1UnSOm&~JcZroOHdNC~$`TJk8_ntw5 z_om=ak!J`PG$CmU4UG}EzPO1rDiLV;#-NSNZZdT<3V0&*fUF=! zBgR>1QvKihJT=cYnJa4E+4d)&gvGe^X}LSEuhQnN6&})lK#0wb>_1^$V#((<5vKob$`L!mlhVnb3 zt|GvU-?jtxxLSYZ(cI))ZzG*-&jwBAZA* zyHPSzOom7xIdM#S@`cZXp}%3VpgT0 zQXjzfa(Lbu$f0GdTeUnu-QFM&hWk1zMBCBGKH`D6s|FymXHu*q5Ggv8Mile$8MtDu3=l^d4TLQU z@3b8zr}oiWu?MmkApE!ckNuCcGXp+@gsWC+yQJYC->`3>+b z*lBsr3g5DN{_$D5Blp}8K!>)H+8?K>`|T-aM}-Gz<)4?OF- z(QV-5V1$|h^RJPN=5TOU<7@ti1q(X}~zbUVMb8q~5jLDqGrA>n#bN01bE}C_>LA^Y~tmhu6`b6JRsM%I2ec zbdJ(+WJ>^IjBWO;;+(Cge*riiBxwbKxH>9_m#F5iHOtzK@riXz^sxF762k#MgiKv9EgRXwVmOpteKe>!n=V4kG)qcWY@T;Zkzqdw>ICN0Cx^F zx%Auk(jCx!9&4LhNQlj=>tu$=Y~C`adIUS?rC-oG53D8%`jIImn#5qxMEO7m&WzqUR)X<*sdqW&!3)*A%K``VW^k1A+Wm4qK;59 z`t&%iS7aKm*IS=XY!LA2AP`0vw$QwpUfV8}Pcjx-4=`N^^~wBzq0n zUqOK8%RE?u2cFV-^JwW5;u%z6E6=JpluC)V)zeJ4MU=3k< zdqi`VGtWU4SJCm_iI+Yd(2bN@rr@D#Du~+ql zG2RL3VgOvF*U{a0t}>pUs4Cvy(Xao{t&04ADP+c;<6H6HIbOWbARr9?Ssf{+@c>b( zT1r|fXrF8n97MRIoh;&7g-tye^u$o0eohU*BT*K|4b2e2xbS1E>g2 z8BY3Lq^VPHdP$<}UUKzSWr-F4<^i^CAe>+hea!m`F%>xJ#Oo*rWC)aLB-|^J$J|Sh zsnwHqlh^l)F|GL42mdN|Pz=1Gyb6$9ZVN%A2e|k*FKb$Rcz#aq&h`-x+)M9cL^)-w z`p_oMjtf&BE(6nk!m`L{m=4ZJn~6x7xcI+9_p@YLfmQePg!_4ouI4s*Oad4l%w4T? zT2h)G66pdp9B7;JSAdzAQMMEo#^Ass8g69C2VlA;hOn7K-$Lcu%D^s^4&h|%$CvR^mcFUC2Il5JvX zhPp`-fU$umMRf^YKhufR24Kz&Tf6~vg?7Vvlv7iEYkm(w!DUdyKRW({B-++0nk<1o ziEmWHRz}EU6)Z~RA6-MTv?_jo3LWRe3Ca`WFM|`xJumhTf}?^1P>OF0v`yQfA3P(8 zfu-!CY2=BSlh1ByS87tzo~M6kc8M|%WlDK#C?>dO%v0I|lf-2aA)wmuhxV321hjv3 ziL$0>M+LEeA&k(VZHl3oc>QN;b)2Ml%Ev$BGv(4D$hU}@S-RLaa z@fmNm)|JkoqsjpJr9_-!9fT6OahdoY2}E-^94>?g`cq!T@*o1e=wWDXZh#It!5%7l z&Mq^0&Ypib{dG}54M5pD$_^Y-Zc`DAHYr8@4{cGDjV)RY_1ZhznY;(6@!6OYOeUvm z(qoda`z{qVN#KaHw4oT-!joa{s4^+5wm*IITEn&O(C<`tZm@+v#E@3T@`f6+tBcL& z8Qz}{=bx#U!+R;4?&ZY+ZGIb{_5hmC1L%CI}zjH~OqyfrX20vTU2u79oJxw%! zqnh#Gm^5dNm+z3i02hc;nkwW@Oa;NzM}NL?CFo3{ExADwvuZe!?T68QAijyEu_kpw zYEN1b+7}JpqC)(;9t+UNprB3D6__Eu-6Aw!bx9~ zet`Nkc!8+dR*O88&^B!3b4Eemr!%-ma!Xqkaz*)BEq{ejHv`G~thq5a4JrkT1Y5 zKcD!5C^L7P85&WAQ3VcN+*_#HKAGeodR>S0M#+Mh@g|s{2hk+6Q!BS}?H@aH1M^+Z z$A$LW+tidE$l*0*h*au`K3fECB;2I~|Ih>v`%{}o&x9aOgawcB^rH>`^|>m0txkwR zt%JEQ6Z{ZNWYxq&30&~zD6>MC3lA95&d#}MP(sgvm1FA;srrsQt>OAX0 z8j_0?X6AmE?9o-1omZrXlz%;zi;De?Hxph#E^9H{A@SW(j8-O)Ooy*7(GMM~9oy)w89mVLkn;HSv0QSrNtV0v|Zc443Ik^htfgiWa*h zzgs*W1q6i`+2nZ00q7-FbLs7ho=va9GJt-DfGCFEgrN;QU*ysM)I;kc_2#_)p`V#^ z&XDCwIW-(}#@}-QJB_}v^?3zP12`pWr)CjOOxX9?&y$f~ zi^Fb>+T*dK5jtQSD?&2~?4f@@6^i0?BL4hhc;D5Mj4gy?DgbY*f|M8~bSg}{`k{zK zm>?Nj30I#B-c|rPGD;XDaaV{auHYw4C7{SO>_jH60v{hQkSx(K3sp$^$5Jx35l&Gp z;P_h~fUB3KNMG{#GYV$RHNXA=``^Fl>SqY(&`%JM#BVB$@;@u=_B1*mNiEC^^?UZL zilgz+fE&iDWefxPAsEl-)*6rYOKJU8p7xjJ+@OY(l~-6LdIkD6i-n+P;fc;_BT=WE z=TM{mOe-{Rp3K!7-&d?}mdD>pRl~gdyQ8PgjsBz(lZ z38vF);h%D&KX)_Xpyh4=lUjoAR#d zPU3u_D+WE4_D*&Hy?NQ1!iXi1vUI7U0?f2J)RO8J8M^iN+Ge906nDR<6~*pxSGLTh zPDLr^NELDzZxdbgxhbn3MQPB030zf*6t*Sx1A=neAyRSY%9R9gUNdIR-Wv{mnKQKG$G=V% zCcX18CHiQ~nX2?m>UUP`PcQUGS;x;9i>8a*sVx#Q-fa zRBR3b;Ng-O6lS-hv5hu`C%yxsFH9<)SQn+2>BGy~3&HGg!*oSl)%9H|t+kDvDqQ1q zsP-avX!a`H|8`HEz#B*nAlMEmq&TBmpld3nYK{H{14H=b`!BtmRI!}3YI}(028KDK zbNs$h(oVLWrC^(aO;n;+PD9#5nZEFZm_@4}FqQgt_ZtZR4r}@zk1fOJ6Cbc_ljx{{ zOld1S!_3FX8ikEKSA|=qM}=jk_Z_6s-D`*b9U|$+D~M`t+=EUUWRzQBC;jM9$OGF7n}z87*94NU<*PFlMSKe}707 z84>;+&7YIqS|%483#|3F*{BunTFKIYlEM3cDp@*D=nSJkZrqH}t+plvxr{;VPg!II zbHY?-Q_a20uAr@-uf)Szu7OVc;J@_@C#v07CtKj~a;@MrAqSdJ%%ASU352VeZkZ0> z)*~ve4UkO9&1y@kS^$(IcW}^JyCmPS3LCzN>$_=s^!HxsBh~q2-nLI4k);j*g0tf@ z0OQA~#UEJwoolGoS}T@mM$5M3eXmXNNH<9>Wk-WgtePZHwwTnoG%d|K1f zRTg@p4WDC*5zzd01v%vCcw7&BNDk>IP-J2Oen z>mdBm&2%S(BM|0hbh@G>JG5jSCTg`j*&PAmJPr^t&`i5vlN9l4b;Q1D940JNMn&qP zsy8e#3nq=@1q+*6MpsQ?olTFMypNlmXsO3Miz^vt7ZdP;wHupj=2tcV6t4{We3w7& z-2;z&y_;@{$lb1C=P|n)JRzxjZkWk4tx+8-fA~RLj38X)o!#Q+ZQDnA|Fy+bd(rF0 z_@wswX7+<}eSr|+{)1SL!8~47B6bALS2F}35+*&e&z$^I$>AHX?qE1SL%_Sja$99` zqK!9kftD*wAVTXAs1z^@*kMXPoDtyGqM|*V;Y=xAR}~iS&hYKRhXf#2Qs7w+Nd6j| z!TZ$&P}a`$DuE^S6D@5RtexZn)XQx*L=ee?@gLdJlG)^LO&!(PfE)4A zmPdmkqg3g8{P0iiahY!cKa5{OmY%HY^aHvF+`7n?A8W~}tM_Q!`Xo!fDufRh?f34f z%V;bEDZ;)IDWWf`Y5(tvrtRR0VkVBR3VO~BU99}oiPj{LY>B#Y_?bIrBl<(TgI&v3 z1~>arxN%}(sGCD=sF3LEwo1-3K1DHB3SeZ%g1bH?Hh~3&69R1cRChkugg* zXbE{rC;9sb+8dv->Gw(g_};pBEGo6O8w=NXY!JQvC9_sk)Wf3 z>2mA%L(^Bz)iJqnl4^xE8QTUv3EWWIV490~zI57#kk}CL&)4!S0!k9tVr_MvK zy^DRiZpgmkge#jYB%u0S--0cF6v~<4chz2PxFuG*pmZs$=o|q|Mz^a9L4(?ZjoyMp zUylv&+ntjn4E-jN$|1gskANM{o{9vV?;rEmjSDXXF`qlg$z}+`=OMrEa|r;1 zT%DGt{9b1bd&e0%v^g4IJmLu(n1w{#KFO7T<2KFTTWP#fll~Dr-a;mln#g07_=}?< z%BI>1@+KgQ=4KuRrH@11NInFrE)sseAtjzpQ!rVjke&Y~s!H~AW&tj*ex!cs`@Sey92l5F~}hOEl?f7B-C090kqV9ed|)POa}U? zj$wu9dFZk8L52Tdi<%^65*2^JW6D5SSe=t%_e~pNm<$U0M zBDmy8h@+CvCX#7nIz49lygY82U8V7VeO@4fTon-QpzvYbgat*2-yK8;sjMf}0=+Wd z6LT)5hQCM391`wGfQvPp5vrE=iy@MpA?r2s1dWiX6GfJQ;7ax=Qpo^u4%@>}|V{da`af$)m5 zBs!}?rFochZ@o;Bxm}v546BVylWLTu%8bGyXeWb8J_BX8Nhoaf5VtXV{_^Z@L+6 zj#!%q`3LLMIKz{rM?9Nm^<=y)F^NUpvNUKv9wN??ahVYHinQp);_on*qU6Zz{4z(@ z!0;X><-(Fvz)Z7ZS%p4?I{ zZb(JGiHdZK!@Q}Aq#%sq{4Dg#4@Cjwhc72640ipL83fG^FC8uwOfkq6b$f4)y!s;p zjg4WA?SgxI;01)$xfu;L^sioSJ46Zoz)5?QbQ3R{aO^BUb$uIU8t<^ooaTw*xmBfD zoigSG^goZdCq1E`fn%_qdl%C8lN2tnDy5Kop$g0KGZenoUCdZ{wXWun${={!wC=^< zB1JkkyQ?<1k3~5)6rWUp>7ELqq1?tjGD35LdS8F$(&V@XkPU&Vos;yeuOTTrH zy0#&t)1+{?oEI0xZtNS&Cr%k)C0F;naZCsxe&pW!2uR%d+(+Z{M;q)5wby-V?iE7; zdbNxlS^XK}oD(AdpJx#uk3%k(RgIEUK2*)gAE)Q-qcn#b9C>SAR`( zKqT|*3cX^~H$b9JBpeYsLoa*Wr`~7Gnhy~<7orb#)pm2Rc5`szbo+z-%%i?p_(sQW z$=@kC%qegZ%)xe-gdNZ_IJ-|RL6IFuRzGYb30wCJE6wqfm%E@@aqLbiqbqp<&Gzrx zX!n0l>&GPUw+S{5%7CS$fY1Pj*4m;sXPn}=G+`#@Q>(V$t=FL5l#CKm=(GVE>-*X3 zZyTt$pEs|N+i(*oFw0B^Ya$yWfjyi@0VTWvuXzADf-+16WX}&}2V~b6Ab7A8rzyBf z4yyFb%0; z_otlth*T;0Qrm+x66WS*-P%ZvDeiB@aO9GFX)lZRjJX>55)Vb_;6+{I%01O>H*2qB z%VnU63PDDK!MS%aYRKol6WwFNqejiG!mu76u%-up#=-G)vL2kX=l;%JN(<K`u) zhqrQ=b6w*5A98{MTpSJ?6a>WVo86&Gn2E(p;F(2C;BZ0%)W}eYE1-eFKyS93v|CHC z*KZ_#vLSwg01{he^1u@WF_W`A8m_*NEIN8R`_;ociRX-$iIPdG1X~TCw*^~b-TR(M zwbPANy~1%I0PTe*=V1s|1B1*JGZqBENh@m$WDfWxk(WBcO0m8GjVP&Z+^9^91?hn6 z{)m$8Wl$Uppl8V_T$fWk>1O0TN>E_SU}S|ZT5W4M!z*O#s?Hh=K@(Y1LpKalG(Ya~ zqPT~2=(gmk%u-A|;M$R6YI?;aYn$pG`V5J{l!|Vr@WpnIi!kUul~=9c>m|nfYKbiuREtd4+R@5Rv7EHx~bJD5O zt)byhXY|_x><0r-_Ry81nshnd*JjT`^HRSbOfHJIb)xXZjYaj7N75LOIsiv zepiOe{t(!914w;>+|7H2+!ziL>UI%3j<{FblDKMlfN%o|QMzaz7VN80%74=e51JUs zbd^>L9hiGdNC`=l;f6zKLK1LG!7XIKdN?gnkpSyt>kXP@GG|#`CA-p<@Ty-c8WZ?Q z(|i?};pIAYEYMzOZbRitLbMq54b4TEumwZMd8Mj(_4()*ZEIv)Hn&+g?DLLkV$Ceo zQANRK0G^s8r{v4nxY(b4QlTdU+9ncBoC$=q@p`>X)I|^*y2%Xd!&8kgD*;7^@U$7s z_vfu`_4ycg37!fW4gnnAMjlnh7Mu%dh1+XGhEPhe!tfqjU5y_nggacokbu&&G5G*DUIVN{835R-;t%Vds?9|- zyRsCx2{+*xiQGkXNJ(k-K|v zS$Jq=2q=fZ{2CFClH?R@kzU#Jcn76k0PVR-tr*-7!-M94CkL@XC>Xwfi2*V8YJzG; zjK6=ldk_5x<%JkQrTTpd3fAz-Yt?TpR2_~0jHO+ z!#qn#n@>#S0Ts#|F1%G6hV%9_FY|7?N%7|8ToADZ=@dnm%GZ@hxB7F3<{R}2Yn60$ zPjFT0NGPTlgbZgQRxJ{3a=i5ejm zLmH5lgamarJ8}TZ0Y(k$@8m(ZfQ}EAFQ!RP%q0ZF*gK+0U$I|(?_J5hU{oJyc@c8c zk3VnHW_Yp8WeZ8C!&8)^Av_dIF zbBE;!Q+Un<+U$427VJ9%7s2Itrcf=iu|zVvfsb>{16i1i(!R#QDGXp7fJIqc;=BGv zbl(bUv5vP+=`%`&@|ICY@=0r?d3+A2dRdf#T|NqPbmPEZxiCzFO=|Yz5FUYg0)nz; z!sPhwPvM+nKb&Q`<#`v3@YPnTq@ya-Po7%w-TFF46@oa&(H)9;JF#z?{t)Tq61AK8 zWUg)eL6K#FSXqWMUfx-P1MHIy>cnza20A@bajD-=N|@H1AyQ)B_(aOUS{RU_WaQ{a z7`ak-nw#R2Xu!54X+XlFnn-YI#Ovz1&^vJnS2r2M|J^A-JD|!W_m6ci*Ct-#M9n{6 z^F@^Cd-9D@FSdFzx~DaUgoq2#ZN*mw4f=^nDjN!()1Yf~A zqeh>SzII#!`7i0c)d-W6;5aw(uk#^-@5w#NVO-~{q%rT%4U|4WIK2sWnsacJJ$ltL z>1sIUCLGlF3ADDy*#}14C9xeG_MM-RQT)efZyi&-k?Nrwayfs@Z~5Z*2GM zriJ&=Z)I?l`(BDC%{HPY$7C`^=-It+_6JLP&0kow?YVpxA!f9LNg;j`3|4u+2Bql^ zu%$Oq)CTJ13R~!wXwu|Z#^bjXzSS;e!`8(=W=C*p+Hozu|4*O6q-PRS{7tnUf8Uhf z%Oh#RwkI|~Xi6RpT%;HEpKB?t1(?KxJspo<2#S(+yFK3>KY{7y*@rvWFjB}*A>p87 zYun~+o2t~>VVN~aolC0;3Jbh77m};^ZVfV-* z3HeW`n`XmAl5;d3(Mg=3gihFA(M~=|;g`6?Dd!JbZ?j{FEwD&$NGz|M*Qj^+2ii36 z|Bo2>KVV=Pfoq-T2MEY0^!Gf5o?st_k}&Q?447=Lt)-Pm`n`!v?XSCxA>2c(DNm=U zDV_xWMQc(8Ym#D(d4ui^vh@QM{&(0-PFT#Y5Z2K-1`S4gaP0HZcxD>sR$5wC8t?bS z{{czpZvtHvn_&?qWgbWqXFVKZu#U<|yvw%~DS^aDx9cAY4b3Ml8G9}M&j^_cD0nb03DXdEA0GC3FPYLs&pq>)j#G^hfN zucyx1EoCv26eY+i6x0ZWD0bvCyWaK20H}&ec+f%-c}Fw@jrHG0I35!hW3S?EwuL8R z7<8V?*-_e|wUr)2aGIs1>x!#NL9X-G^pO+rgllYvRrD3mn=L=?D~p(~)K@OHxLL;& zXEXR2+zIk^c)3IW^k1TRd5pyt-1ntd;dJ+FXan94WQdC zBVbNNXFM9>$!5H7H3>9WUx>qeEw(*BrAYkqWbvE7dWXx+I_}#=84#?r9autT=(3oo z#5^yPq;;tZq}Kq(IW?*9GIFvPyP_-c;?G%x=QTV*C;jz8U!VaS3O$t`Ia*MpV!zgyf{k&Fu^!%-O$^m3bZZ}W4l z%w&FGyAsqXsMc-NJHp~=qV7oc@HqXbnHQM~2*-aNYnk!#g8=i+MLzQHIr=qmCc8Q@ zvne!B5bYg+6fOL;^YX_FRHw1o5idjJM@C@G1c`V(;mI(5iUWErXSy`05ujC|g|qZ( z2AqCI`I#hW>RPlrSD?p@#nh00+5yCYRWQAnVNL9A6SPpp)(vFKl2noya4x@6@3t=q z1!WvzZiek#MjT$!6ag&_a3=n2VLW}PBHBb1 zcg=gW6R4uYd9Pt8zEKx^v1Njl6X}-oo0(K?{dcd^Y;CjFR&$4$_Zrcd{z#R5$vCgp zX&&Rr)iMq|@Vqmn>wwo|Z>>7^v>Yk9W%pXNpLl@>Cqdtjo|bK7`Y8OT-~cPozj!@T zGJ$WQcENw0NYxwJ#=7mxHWe_4ZdLuPBy(5;m&~2e;YS6SFjK`y6M@utX^#`3XF-RB zi>SMYztL>H5)PUBb;3qww65$x9U-!An#!qw$RwR({Dyrfw~6N*xaXE+kbfckH%pCZ zGG<4pME4639@S<5Ak#uix1q0F#kYLNt9v+Ey>LJxFY4cwYBBsskjN{dh={ggW_&_& zfouU2g`EY!`!f?oFMgg-o}07zAwE5NbZn3@y^P^#{L8kTBu42Ep#)9A5erc87%Xa5 zO2XT^PBAv+S|lj15er6R5a30R`GZB*E6whIo`(aiMF_Y7X%~r zB=H920}?xuwKj@4{qC_d3;GA(S!lU)oMj9Qe{{_(cv2Gi|NKZXrWe!_K#$ zgxMsrmD;cy$fzagm^GArAHf;)3Clf)pgGvQ#WhF&LGXc`mHqdZ5Q9H_H1mg}-lWeZ z=akRmdc#xAcWs9MgY(T#{F`O8WMb-5-yI%_t)(G}kOI{nM30vHXB=##(gWa2BHH^s z)I_9x)Jn8R&X>R#=@Q)$iNwYMgWG})d2w|w>KtuAY^S-Pyr9@(rCF{xr(|W?S&g&i zu`z91p(@ni7^J%hztJ^r@3Z>rle~}7&TZR~IRkDsy{(Ed6A2h)+F7aLHt!fklh>i4 zV3ko`6yY&O3Ed92qjHgFVF6fjt}wvNI7pU*JSa9WU(}SbVPOfN^@xvrbQY$7SANXp z{dd9#+h1#eX0uQaD`&mI!qKIVn$)3D zlI48In}I5$$#Mrb{RxE zDk^CZeX6jymLXT}rD8q%JraM?d$+kG*;L`uI;4wymD57mkKYY>1?c%Av)!fCMb3j# zN!>J{!9qL867_u+ZYt?sf@1kA;KctRN%INoUFU8H^_gRS*&i~Gk7;EM`okI-)D@o~ z&L(m3XN5nG)fyYx6`IpEcr&5`;X@=vJ);XqC5>vsm=4H`Lj<)Z0)pfDWbR8CVkjb! zYP7qwf>nvnJBiUYmn`Pm_aA04Fk;Rk^fStxGMhDSACrkja9nxEz(&VJa0q?;ws4Ej z?k^cZDe_fBnL41^z5gw%5VMW+&&dORmoamIwlBEsOZ#>Q^g&PR7VzKopt~6sx}5i0 zbXois%|-vyS<)?#0g9B?Z0G(U@t$d~Lxunw;pLSzC3D*p*nI>j6~KiB!_hz$%e(C2 zqgF=isMpG#(LagkJJ7q|4RRQ>oo@ia*Y8ar*y?yYZAP3<>C%=@38EFg>m5ais0La8nvIXla? z0UPjBRZ66+5tKFny@d#SuGhJEQhGIj`u8PwXyO8dh_--@6vR?sEAH3#9R4g;`ZOs$ zOXlC`eLt4*boqDfZNqEJXXNV3`wq`fG!jxC!UPh24eTe$Zz2po#NSHz`$UsM?1(ss zYiVCZ(9ryzq{pB%03fnoq;59vd58u$EE_Y~kJ3K`^lxP6KlSTHyzCR-*) zrRb5#A8R7D9untQ!#QAz425b4k|wQ7QdR&yC|Hof>jsClgz!`UsR}P$4_Uy%Bts7KlY}x>U_uew& zW4MLbJZOeAEQ_-2+F)g?WsOoL#ic8PP<$seAW4DBUQv}$jv6(Un~r|XdO_Jb zkhx;G_OFUlpc>Gz26c6wiDZR|_{twQG_ltcdMMpvsy(_ z)>9J}K(2x)YVxQ$5S?jthDhGWAV)E+(Uq{=b4TygW{KRKe_wYYFWj+$7>!w(y6~6l zuqwWyP&mG%i8tOxj1hLX!I~?pWOirN$N6S%bdp0VmPbG)+YP=maiTy&E9WxTs<3 zDsC~*_0@+UUBq zp~a7TdX{qJ{qvsTucwew`p$(fxq zXSO`sn9h!JNtcBk+Bw#^Y01Y8j<$kjBO7@}SWb^(C-7vUgF(U1Eby}BoM@~_S>2Tu zTTpJuXzgLFs&Yh@=A9ND$L&k}aH@_R*;2dM4tX|zw`u9f8&*m*$v#z|=&oxgNDk9A z=5i)_0g<55BgjcDoS2g*1GaU6+rZjmB9$lSSFychb>fLtPMBJM|EdVVFyHx?1{aK~ zTasKVA-NWB1TQ1zz~@W6`!;b!rr;jOakL`GGqmR|R~>uZ5J zvjWoZK;(SU1#dMy;hZ%bOD=TAjz`{uP|JR?F?>m-;QbxFNyv5NE(j&H67VWG{s{p(HTY zA0}xyr4i7rIJ1Hw8tbH?KTeR(!*E22#nn|mPM_NclB;49d3P+YeVA{0d}MO5N{Vc{ zWVt0bm@L`Xal=ouKNFI`#u(7U#DBVl-W3>Ug)vwcYbj-RpUi4*YU?eYN9qZ`FE`RX zk|n$(kjeH{qIsiX7WbVh8-swouq9(WWoj2cfT#ws$4hJA^{w)VU0&Y42@bX~nLTB;1!6C677V{|#Q6*Y@B0Pwj!1diK&t%*Y(^2_ zQ6d)zZtFN>S=qp<8=C*tee8AHSil?*Al0ugU^EW^nrb$4W2cFAZ4UdiLVn|BD8i zTW0I72(r_)THFqH(Eh1s3fZ=%A+G<{C}XRuT-yI7n~76%V^Swi4$g zaly8}#ZIR`a@NvOsY1I4<+&tQSeOQ!wKMyj>xgOn5P0z%@8QE$yT}yQbUR?&rx@$WeU9yxai~yEKh(}h^f9?)V1ro4a=QXhz@01IOn!k| zFfsC)=UeZNUcP(Ss)VGE=6D;qe#2@9p%#x;ItP6Q}DrU;G8{rO#D|fKyyc7u3;Wl@q|CH><|4Avf`cJch#`!7ohtrBU);GOS zsc>>eG{Jo2qn1-OZ)<*&4YMuN*g7n_eY=uw3$XfDP^F}Ms#06BpH)QT=XBcXbCRO6 z(;QKrNdlfHa(qc*peru;qO2MpNSfT7*$0C&D8BG+wDc0T;}Yrv>?aC=&v7DSa&nT% z#Ho>~(H8q-++bdpGa|une(*Kn?TF;eWx^8j0&-T@E^@tWKk<>6RL3k8nM;lJMc*LP zV5=qJCb~7xfwtufZMlJ>O{b9%UVG18jwrVk8iDN|){mJWCL0vyKk0jW*l>A1j@cq4 z(}EW|-QKOOpCL3sbw?aumXc6T8+6!J`1aW|Ny*HbC(dYaQm%vYNLS2;_W6&df>G(awwFb&IZ`O1*vnwbl*tXPyZ(f?dpdokn0fN!+|W#F=O` z0FtG9Gb-drA-9&nK%dYK7Lo$f*?}>EwC*lqknz|#*(388*g54Kv;11ABF%Run4^lZ z)aWKdR@4?+Iv2p#(Fup4BH>}@EA-;H74CwPj?O8oGqnKM<}dYcyjXiaR!{fcv@Y znBb@>kDi>>Bc`;l9mNt1(%+aPCJ^h8ub-GE#Qyo$lgN(&X%Kii#qv#t7nnDd9)x)% zB8<<9^w?kczMf?tQ!-6ssEOD_X&^DMXpyp(&P42c?vQ43El||rQ0su>juUs=%Pm!y zhWiQFD-^P_?_OVP(dH1%AH^n$Rcfx;9y;1Dpep^sd(Ld?{?6}wk)qVevL*DQ1*NRc zF4KOPg>RC)&$!>|M0<_>mR_Yo3#3bC+go!dmbMUjl5T&_0>1}b(B?(DrUAhUZG{x!aH+>Hpr{zXt~5M2jEJCn&Y|aRXj2@pLiRR4XJjGx72$W&ii(%6sDL} zvgU=v(a9*`*hO^aS`6vIk5B3BxvgAg7T70)D1KbG7XPNc^>(G_2-hy-fp|sDyLJm6 zObE6PVBC=KP)aT_KeFU7SL)VZ3Dd7gSXQ<9?5@R}%mB38zgh}hCsor7T*ReZiU`g39^e1I$7))))N?M?T^&29F5;FKF+tisMj zk=^h2NIeW4O5xcReUvlOA!wWJMfKLVIJ5KV`;qm5seYRf#Lx8Oi`m$$PGzqB6jbM1 zioU*~Y5Q<@{3_Sh_%s2rQ!+$ml-YU-swK|o_$(wS+XX1H~9rgE-TV=#<+RxaNXP{r*h_3?SGsBUO^Mo-=je zXp3m#rSIqVG~x|vo<;t|qA_eU55Y0}5W2po>+3P@NAn|4;EyXT30e4VmhDtzDRrxu zkQ<}56IN^}1{S$Z62b(%U5ZPuFI8^%oR--|8O2J()pWzKk;&w80r&~U3i8l#3BOTa zFRG~7WUdXRki3@?h)qE2obn{`&bz1!2ykqY~@Zkk==ffGs^Gr^8EWZ$|jnDuW8Lu zZ+}TxVwhd%X_@Lw7PD!&j~!ey`W=jD7j<$A-Ziht#xMC#>HX%bdrFx$Ru?-(HeJKP zMxd?R;UUd6r)-2Zmer~1)kb9zPSPCmK&ruU%`o9FUW^}vITIn3w0Et?O9F*$BfJSb zy}p%HKla5@##`Ujkx~1+!K%tGP+(7Ni>dZLq?D^$s2j??V&fN}DBtD*sC>#a)M3K@w6S7Qy7919Z z5lzonsgpvMp5m2Izi24Tdwi6oWP+Cn9T3AE{=7FPm%d3t{-lFg>_tNTuBUv%7?a*1 z-$FXIJrj9_|C%|jt`nb+TXU-!{Kb8WO_aQUO#4SZ$4>xC@N^Rf%b`u3Q9DXVHUH$h za)$S!neJ6sf|i;wpBMC2%3EXAZZhT0=ft9#`4G;CLHG)9SWtPrkAmq_L0Wrq#~@Z+ zDtC;G*ypC|ZS=Re>$;K_4gy%*$a}xCn6QlY@qXfTGoxH;kH72nH8YCcoa>>O^S~;v zdTJLQ_umW8k8@W|!$Kp_FghGEmP{Mufipt;<*e0@K@g>BjLU2A*#C8B{!D04h3=#! z0ufGq-svp{O%8JsqgZzLy#dJ1W)SpM3`M2WLfoO1c)%{pLZYoV9A4>uJ)#LuyiM2s z>TRdCtg1_A3yFjvgXlN4{D>K@;n7Iy*^IR;o{JssfFnVCj%9BVB|i1;tR1M&PGAIA zljR#(x*S2l1uf8DWQ|WmJNS2!MFT+ODfDV5CV=&$4`tTlC;Ad;Lhp9_v7m@jw7UjN zEFbD$Xpdes{zzHYL8v4gI6(`Ngot6*Ddw?xr@0|Jalx5qWR$Fm%y^SR{YhB?m-b|F z!3hHdt7nGd+qZ2g0_GglGPI0oDNUc9yeN>?&i!()2GpjX4g4Z8nxN4Au zvj6Pt-Yo(^*{YKY7?4Jw=I$#Y#UYW3SoEaomeh5aXd=?#MG;b9aaksE?~fxzC|d5r z=PCEq--k2deXia_00-(pNmt`d1uK{ZhONARfYU`9hg20Ug9VI zqAl&Dip%Fb0taB3RC`Q)qKYwt7US<#-p zy5fIzM(`o;@Y6dDK@h>)t)5ghs===n6SF|R5p^O3Xn4w%231~rN3us=?bw=)cfmWh zc7BorA-sPkCI$HX?$vBlw)DAB_MfZg%hP#7`0aB@x>s6Rt8z$%~$uF8>^)}jU;T?zB0^rk1{NBDd z@g=ZJ;>~r>kr;nA6c2{*A&sQf5KF}@K~QVXJFq( zE=mlGMAbCQ`mC;fgC@9{>=c&u>;gL%w!rAYv8{m_isV3}$`I@4=N~3FVi7_>y9x=X z8b(9d$|nscEiP8W)DEdLTp(B3IW!H;kWDhUG|-jAilf2t|4hr-g>ApuGHp=P!Jm$V zE<)k7wZDG8a+lTRcRs%R`=a>*YS&3e7-a{EhiO{4B8v&!v)kXD6lgW}$%#GE2E}=4 zCx@^fAPRa$_ZV$!2fjf$e!DWiW)_t?>YZ)`**nH$ZT&1LK0r!I*KwDou*cDm)v4YV zKi>R>LUN9eGMsGj3_Gc{%HeF2W3voL(2pFZ8M;+CCZJFon@gMkJIm222FLH0Rm&~x z({C}4*E1@g5P5=4U6DLfaxSo+nZlsIi`mjRp$Aeq4KiHxu}+kHt8jYI^lQeyWiLJU zcWM@WlH-T8oDH+Xbw7Q5=NM_JW`1SP?4`UK6P3`Ds^gXQ#CRW;Y^!F4NE4!&b*nu4 zm1t)D9Wv*U^*UE(V@}-fm&bx^HD2!P4s{Lu)Ux_%)J7$E;@Z%&qw`sjI#U0ve%oV5~v&1QO5~hz^ z95|6iY-`r6p&6*qqLhr-7xq331_$aPx4&Zs8lmTEX_-F}^}Ku342{P`%~w-y7`ppG z@e^ILz>lr=cLVJ-gKU?934DA*Eq`#sDPa$2`JQl>fG0klG&4|^^#z^f2`0o(obxGA z;1;X&P`2WOA(RCah=RfNMOv5ofmTU2yuiYVp@m0A-&AUAq0xtww5RK#!}^A8(~K*w z8!8<+zOE^5D3fgS!p*Q1u1jb{wcBO482nw(*Rec9(I+7~>Ln@-_u{j8f^#1hqFpNJ z$Fi6uk&^RIl0s6=MTNel>bArWYU;<5j12I)AB%{OqS8202n3;4xRvMNi3C-~%ha%X zlZ7A~@3+||l21HQY6BP1!OXezQSv`~F(XW{tu>!bCbrqsydwBM7~7gV@mD_oE19H2 z*Fh@-5}~Q4`iJ^#I-U%~U}9_J;*zc9WvDvW`o}MQPu`w^1m;aN(kDnq5-19hMHDo4 z={LAAy`<~poWtLlFf4!~y2ttzjmvG=m2)%J<#Y4wBqcLl^Hl;mPx?Zl(-;WlI(W<@7>WIFzLrS{DeWE~;_wrDS@$c(QCm|8604L&VZxYZ07H1BuNV|k zjxxrkltMk;D)%XY%*zMq`k8h31Es$sN}op@J0;G{?q`B2UCaoH?iInt1a8iCz+Y~3 zVAzZ#OXYHtkgS42eQcT$7P%3(%6yz65s9GXGO)BbXmSek@^`O5^=e_ zRwbc~E4P}wN_}z0k3FNpEW&uR3_6WjZ@1mZ&y}j))ks4i>zgidFso|=bvNpLJge3y z8dd$WX$lY$XwqMr`(o0ZRc3AhyKB3b4b+=RaB*8A9*4Hd2S$h4{xAdEz8mtgu2%!o zzd?vDk?f1CLe9Ku!NM6^U4+!rkX1!!Qq6QSv{f%er$<=If`7sOnooDCp#IQIdZkF} z8o{0-skjMpwD@U%-bsSkX5*flom#%Ft*O-Nrk1vT#+-gvuM~3)lFjTlR-2#hxx@tm zdA$_^R^DE9I|(k;%mzDADr4@EubOh{vty22ZQrcq=ujx-l;&_y#emM4U>tN>RIf^#)qByXdklf_x zUIV>FlKB3Z{BX;XGxO`T26A=U4tn~G4ocT7@v!bHis%m>(vCZ!i)vc)O&Jv{ABc3< z)uEIF2V|%PT6a)%wu&rZOXVz0hgxEoo+xMD>28#tXTGV}m&} z_*P745V`X|%!`PhFW%M&XcVQ^kDG>G$Fh%6Xxk5*aG2DQ+5kEDR}RychG#Z1rE!}r zTZ+C(pQD0HvQ^m*V&;)*Nzh zF4+@YolPPGH7PVnO5{0403~TojA)g5AWErub!8S{=CHGY&dGoE)Eq4+J;Cbm%q)~w zP3)PeKueQ{4F9CTxQx_UJz2YxxnlI4m~}C+XccOH%gm4Zg=0!vUE|Ov&o7gablci!O}t+s9FKf#;5h50A8KG^fI%VT?=ZtJzpd5phAmWFQFR4N_f55 zX47clK@y!U^UxLe?qr{%KB5mA0v9=!;do7TbU`3JHb4{4X?S~a1G45r_zTTYx@1$W z-}?}%)q2jIBgJU`p!{&1qmCu4 zJ*h}FcC1ic1`VnL1w7|sT`}8+ySb+2{sSjupNw7Yo39y2<{&uhV(-QQkR}~I>#lY2 zb>&q*I+90u8~U$`r#M^=~>T%(YNz46n$ail(1JexBPMJGGN|3}sXEf*0|YY}|mX z@q64-TP6a zXxRG?#fPb}|EOjqORy|hJUyk0HNVW*jVV&+0c3dgL{miGg?-@fN3Ot`$8CYmo!!611ht8}9tcN~nD6q_$6@39|-dy~LtL=4z++xmR_FV!KW+4InD!nZR; zK&&00$E@qePp8fwD&3IBDfxLBAJigpE5640&vyN;ebF-`D$OlO6C%y%>Pu-Y&)Kk} zSWJyBujb{KTU7SkIq2`=oi-_@CWyz0Y4}7|28F$tL=9raPxjBTpX1HmPVupi$cDyt zpWWNSNge({5OsTFk6h-0%$hH}_FHY8KC9&?7>@@|OWAFvW& zKYuC|*|;mW7m$g5UHNUWDpeJI%Xdc0{#$QSZplgx>X2rYgsT z)vQELcF~MSPI%_x*~lLzOjMT9v(%Z>|Xbdd5RXwu8;qR`d#%=%sq}_XQ4^KeIz#tNR^6 zlfKGOGF$yDvg22q@?CiE-kYo`}nmJJbL!T>I*(gaI7k-%YH9{WHs^v6*H` zq>&Us^`b3dPX^noqgVPbSNju%)IVHs#>WbAFA$tp#Pgcf_(HObb`e7jQH-R2A^cpS zDK|w>(V+=b`hF6~c}Ot8)-CKMHjV_l(SPl2;5Nk>jt9RtomX9BiN^ZAz!0W>8S~^c z1-dnLv|BjK5sfmQ<&f+uB8aPOo#^_#@jFBY5M{S+W*+d6R+By4%gsNmE*!HzQXSb^ zxXX*N&PxF}t7V$uz?yA4>+L6{9Etk)Y}MwaVAxH9na~#eBP^aXP&srb=^{Sl(~ZBG z`;=gG2>11!EX8Op-}L-DLAjBy3%1hqQeJL12qRQqI%< zRXJM)KhH#5D}QPus1u`qOGIBz*tboStU$<{n9=umj?pxHl;oo}gpFy}dTR26&Z_hl52)IJ!)8ELL=gaKp=|oPG z?Jbg(np8KC((%?P+!E7t9wyT|)~*Jp@rAeW_1u-#8g#|@dqJm;RNNMvo_~y|Iwd6>$&Xfiv=q8eQ*&T6ngMx43kh@cojMyGg<~W^JNZ zd>mgddJkvg(9{tlaYOltv+e#NB# zszN^%+M)Z^h6siIxDWlEqk@}+jPX^sBOwMtr@EYH22eVTAT zD}-0JMSKNi&yFyo%QRs%S(jQ0ntIdXn7Z7-YV>nfz61l-ZN@M*hi$CKopVi`#}P(S zppF7_sjzzjW>o1h?a11Mcr4I21`1RD5VpEHb_r>FCQfRziVO-8Z4F}OlTM;vLC~Dh zcmwH}9G|@kSHyOj1MGkU5%%U zR+n&!J$IA`qKYww%)DuYxMsnVmXSxT$w;Pbu1CvH=-E9oUj_nP%bCqyt&Z!c9ud>{WHcIN4Jec zOgN36#hyRgWKvJ-t4L+jAW!HIY_RUR>~VJl64fCD{>1eEgvzH0BE8#G>#0pm;g{)D zOD`+njwviW5G=ZRmIy3&nOA7pAL7bLxl&0f{^}nicPsd9Taw>#0$~|<>}-5}E}5~% zz;hxr(&%)^R#q{?R`%G>lE>1`bLCH)ojZB z9rm8SNd!Vc%Bkl-kkBuliOmCB`bNvX>NCsA5)L)nX8qo8Q%%GNQd0+|KMjVFeQsSI z)w}z(r5`Bhr>GCyNL}F$SFRl#UGrWvV3weBwm#dw=P#@j&|cDYb8Rf0(Amv)!F{TT z?2tM33L~R`)*K`-``yk&UlhFm zlMj>lAgJZMlkPIWadbZRXV1G%cH1=LdEc*A z;2Of_mCw@`&&nlU1Qvfxa<9J~HX7N}`Uq+GCcZK>^Ql&CFm?^7Hp`V_#vMvN!g-gKc2MzM6IZPqeUOvR>} z`f>-@<2w0@br{6%U|spES#|*B{n;a`x9T+g^`Lx32Tq(i5`^A&rT1le${hO2@O9A> zIt<^(2A`QP&4c6$olU*wn_LTrvdf~56L~ZnZ)P?dvWKh%{e3S1+Ev zuQXTL>mJC43VIh`OrIg6-3>2orC}>W&>T4@w(U~b_zq+tU5#+q`LT(kJ2vJx)ILt? zaBQABtA<^M-(W`kaY8a&HVszJbMn%>JnDe`1o28v#Cs%5L&ic?2FJ#szJD(Z7%T#o zLnQI54!-5N0GKpcU1ch}-@Cp|j$fl$i|)OiI9>7tO7a>r9wtMr0ez20%wukEi9^X< z*=A~Vzk-yUxP(C0=jRa((J*EvG8SS3+f7=u_qVnNiXW0>)Mn=gVtR_okPCvqM6N9A zSMg>VUl7hK+*_?>4y}0{i^@}xG9;@iHDZs{L$qhO6PEHW-_W|cj@>wLZJ?;yy|a+0sSDQK z43m39gwTWscx*-eG*__V4c~~x{&X1Hn)p>s;kW71LPM(*)XEQ$ZehN{l=5ZZb-iw( z%=sPm@d5&fN9gW+?++-rqD8dPyt>qC9wu*gp|4V_alg@|&GIr9c>lY{3O4MD15uCi zs0F00wHZ=i2=$aH;CM;f;qab{EBNh)bFDu-_FBhv#G2g_mr8gVSRFwgX1*HTcb9&! zg5Td$n4a%*vNo}+V?HAYrd(lg^ogIFJ*#$4Ub-N{cRF!p9hL2}#^5BhFL>%ibi#dC zIbf`d#~RyA3dDL2)R+XS1bK)F6mF*8V}*gls{^SajMh*OzuDEzp2fQWCaf6W zTUCAtX0L&C^;XI;kTRNzyZIhJnGN`rjHoG_I*YXN(a^9B91E+)tKte;*OU*-IixQlRk}-qYTmeOATvE z&?kv_Y=ZZQLAoKABwv>fUml4U!Z<2H{~VE!KL|vVjP!nH`^;o|-jUc2OYQM7zf}ie zLnzod$@mm**Ns`eHpF-vF7daGBDw6rLXBLHT2@hf;-!XdZflJCaRaIZoIPo0s1G@c zM0-JwzPMzp@slInodBs`?bjiE_h2XYy@_w?=N%r|6T!?q2hdc$?yS26&sCre;WDt` zxChIw4|Bv5Q^APEWE{F!58?>Y#`#2%!nTxj7Qi=A#5YhqSRT|yc1!6x5#kap z(-o3k?}APOBw(iLYeD<-`76lgT{OZB|MCvkdB*YGDnf+QzB%HbEnWzd{>mY+`A%M9 zK=&n#ERa8JNmS%#TFkjOe1z{~SLUJ{NACJrM_Ykg#pU8i_O}Cv?H3z?wWsWnLs0-X z`Jq?}R;oo6f$3B)xnGu>dlsg=vqc6wkPjQiq82_*C9-Zt9Kb@>qXNnun=R6e$2%CF zl@$p;9K3?DLS8_pYnZr)$t~^^K1KHpwJh3%-=vD}!po$WM!_JFhjdz?J+@)-=H6QU zMxoGm*MG(;2o8z06y<5>{B)pQ`U})M0da83Ttsortui9rVtfBdG^k!~z%UAsD^cna zqQn7EvN9NEi!umOH&t48qV4@kyGz4&O?z8VAjQ%2sq8C70U;>wqga4KjQAQA;Z5Ba z1T^skvm4!w`0p$-Ps)04ur`EY#Ri3$pw>j$I$+N2H_gP8^w!fY{n(gm&K*V&wPmwt zN@o&t=}GV1-Fasvor@3$4=}X}r2Cel?kg>xP$zE{L3ZwsH{hxYqBfSAP~GqqYNck~ z>##nU-v{1@_RK>3y(ABGYhJO<-+yI2G2Wk_ZBzLzQkTH^d{dVd?HU!0EhH7CfOon2 zSKSsky928O5)35`QIut8lm`SYqir|7AM0vGB zZ9OvA=+Qw)lfbU~ya?&|Pky%&c$23L#h6mQ>Dk;**%!VW+^paJ`2K-5M5m=TRa8`x zB?%OH|HzbEuooJcDMeQl|2b!Xo(|}w4VRht&@guN!{pe z+WODnGL7I9dfLM>q+VnF(JdtNHviPX`e8cJYVX*Z=slKVa_OEA`dY$l28*g_I*lhu zs~YIoplNGcmjkR74(i0JMRs$07KtGj+-NV}z@%N;CX>U^-ecf`P05r2%i!LPTKlx2 zx}LHf8^4i`Dg(&M(Ln`YktvNaEAl7?ca|BO=p;bjbPuFT5ABp&SZx19~0&r z3F?u6@)ch*ft+btH)(?l>+>y>V&R|JG@4s8>unjEdv8IWv06{ue^}^-L{w-9tvz89 z`S5$ByuMDe@K3XOeRf6E?lEcOTa#x`y86_sLT!2bXTU4e%)}>k;Y(QRL~Q7JIPE%Y zr%q{dD6K^8I`<=Sva5uZpJF;&*-6fk^%$tr_;jfDVhxuDl&0yvWD3=d|!=!91!(3##i7uLzv%5 z{6DXokT=yGlOzg88KCisyvOq+3RR)pIz*pAe-6AY({oB=3$!j%EzG?iEE6HbAec*C zo*Selys9{HP5iT+c#}l81z*aWxMSoz6%XhKGGl2gd5WW3X@JRpNgT#(x_Qv<#{;>^ zx)g~%{v2LoGxx=FwGbUprlS3Ly_p#J_Dk)1(`S>^froc7OHJL}M9F{t#ef|p%x(*A z>(_pRk5P|3;0g@`hT=^J#}4X1f2uzoe1^uThk=kxz$18)(V9R=vY{YbSt#)42mp*b zLW}sYoZ>AA5vYP?M^vCQzb z7m$R4IY>hbdWht|F90+c8VL)m2#pW+9Yuq&3^X+d$B$yaQHO)b3BdiMxG*CQJTBlZ zh>QpRudEXu56n1*28h2xpLqWdGZO#`isZiXpVwb_kFZyfJ|Wc-5)z z6=+)W@A!x(0kHpRs3ts#^~O{N+^t9oUXjNIm(QR9UbX3Z^=*4O2#^YVKaU1@)ve?e zC`0w{DJZAt0sr&_NPtY{17w`R(R|S%F_4@i>nSraPMFIb%vV?--eRV!x zO9%iDJZ?)4mYG3=`Ab4)FoXSu#14Fw4giB^C}IBLQv-KFWX<+}WqdP4fLEkpuN2}q z{sl?R0sya=W?q2^-N6B~Jb?c)y8L@sNFER^WDtHAeDKB(G{8UH6#vw_P(w_H^_8Yv z@4rBI(1*X~@?TDZ{}jgj7n|e<&i^3~_}27)YLx%`DdcD|;IAfzId;IS8q-%Gui(F+ zf;l?CziV**i>r_N3qqZz1-zl6|GJ^8Dgk5@X83m`yJu-Fnd z;FZ_l73ioQoYzANPKSX0cSi!TXS!FQ?sf=>;qTw}pS9f=8VU*-lJ3m&3N+XSrdXB* z{Ik~o19*l^E%z%x=67(+ve_H^9DWo<_WjQ}7Y{XRz3dwMVU4-Q zn%}IHTCm#|FbG8%FmSZ^JoI=RR8%w&kni6?KtO~*?Cq>Tg#T3<@q%*b#-shs8K>Ad zr`UH+w@%+c{`&~&xBr}5L;ue^5&RoI=KuWw38x$_I|K*_BMb-#ePSFbMxy*Te87D{ z9O*B{gu1cl_hD#GL1RfGHwO0Nq?DkNU&Lg);5Z^>(8W|w>&T-Ht)1=jBnuC$yK|!S;vP}>MV^c_r5GVcLs=nH^?rC-*=KA7NJXV?cuDb zS0XP*_AqA;t}qDGK_~w2gbd*IVL+s@XEB%-ar>e_Id-a96hu9C?RXo?piLW%$X3{o z6thF_ILtq7nlQ%HZieLF;a5y`uYWPL6cf4i#ThY!$5@7#D=@tn--Z8>zpi^Ws~JMrK>!OEzJ*l%TcY-YNr$DuvZ&5SeAQcQ`&2vpVPD^i~w>A3>y#3 zrPkuZ>hzg9iEw`#R+-_3Qs!3{ajlg#qK%nNbTX8(T*x!hvzA@>&khT<#UkpOqnSiz z;kisws8VZ&2jH@6o|>$cp12n3Ug>#?E03JkRqwNcke`G4t`38PU9zR|7?hS{!i;3} zY$eEqVL$O&H70pQ46^-)U;*x#H7N~=l&#8xMtrDfSdx&zk=RUwu;t7hXv^|m9!t5~+VRoN)j*{uEY6gE+W6zvCY z4#_@qY<5c#W_iWMD6M)YpLH%QD~Zvgm&QqmFBty5yi})XlHatyTj=KBRr@Snbo(%05sx7WMs$AydwDW?C

            ohmj(wKVBMg2V`Or6Dh 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 diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-annotations/gradlew b/realm-annotations/gradlew index af6708ff22..cccdd3d517 100755 --- a/realm-annotations/gradlew +++ b/realm-annotations/gradlew @@ -28,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/realm-annotations/gradlew.bat b/realm-annotations/gradlew.bat index 0f8d5937c4..e95643d6a2 100755 --- a/realm-annotations/gradlew.bat +++ b/realm-annotations/gradlew.bat @@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.jar b/realm-transformer/gradle/wrapper/gradle-wrapper.jar index 0d4a9516871afd710a9d84d89e31ba77745607bd..13536770052936a92b204cc34e72284a03a6903c 100644 GIT binary patch delta 49463 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 - variant.externalNativeBuildTasks[0].dependsOn(checkNdk) if (project.hasProperty('buildTargetABIs') && project.getProperty('buildTargetABIs').trim().isEmpty()) { variant.externalNativeBuildTasks[0].enabled = false } @@ -599,34 +600,6 @@ project.afterEvaluate { } } -task checkNdk() { - doLast { - def ndkPathInEnvVariable = System.env.ANDROID_NDK_HOME - if (!ndkPathInEnvVariable) { - throw new GradleException("The environment variable 'ANDROID_NDK_HOME' must be set.") - } - checkNdk(ndkPathInEnvVariable) - - def localPropFile = rootProject.file('local.properties') - if (!localPropFile.exists()) { - // we can skip the checks since 'ANDROID_NDK_HOME' will be used instead. - } else { - def String ndkPathInLocalProperties = getValueFromPropertiesFile(localPropFile, 'ndk.dir') - if (!ndkPathInLocalProperties) { - throw new GradleException("'ndk.dir' must be set in ${localPropFile.getAbsolutePath()}.") - } - checkNdk(ndkPathInLocalProperties) - if (new File(ndkPathInLocalProperties).getCanonicalPath() - != new File(ndkPathInEnvVariable).getCanonicalPath()) { - throw new GradleException( - "The value of environment variable 'ANDROID_NDK_HOME' (${ndkPathInEnvVariable}) and" - + " 'ndk.dir' in 'local.properties' (${ndkPathInLocalProperties}) " - + ' must point the same directory.') - } - } - } -} - android.productFlavors.all { flavor -> def librarySuffix = flavor.name == 'base' ? '' : '-object-server' def userName = project.findProperty('bintrayUser') ?: 'noUser' @@ -777,26 +750,6 @@ task ojoUpload() { group = 'Publishing' } -def checkNdk(String ndkPath) { - def detectedNdkVersion - def releaseFile = new File(ndkPath, 'RELEASE.TXT') - def propertyFile = new File(ndkPath, 'source.properties') - if (releaseFile.isFile()) { - detectedNdkVersion = releaseFile.text.trim().split()[0].split('-')[0] - } else if (propertyFile.isFile()) { - detectedNdkVersion = getValueFromPropertiesFile(propertyFile, 'Pkg.Revision') - if (detectedNdkVersion == null) { - throw new GradleException("Failed to obtain the NDK version information from ${ndkPath}/source.properties") - } - } else { - throw new GradleException("Neither ${releaseFile.getAbsolutePath()} nor ${propertyFile.getAbsolutePath()} is a file.") - } - if (detectedNdkVersion != project.ndkVersion) { - throw new GradleException("Your NDK version: ${detectedNdkVersion}." - + " Realm JNI must be compiled with version ${project.ndkVersion} of the NDK.") - } -} - static def getValueFromPropertiesFile(File propFile, String key) { if (!propFile.isFile() || !propFile.canRead()) { return null diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 62eb5ceed6..5b90396f18 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -69,7 +69,7 @@ capitalizeFirstLetter(buildTypeCap "${CMAKE_BUILD_TYPE}") # Generate JNI header files. Each build has its own JNI header in its build_dir/jni_include. # WARNING: The classes_PATH is not part the public API offered by the Android Gradle Plugin # so it might change without warning when upgrading the plugin. -set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/compile${realmFlavorCap}${buildTypeCap}JavaWithJavac/classes/) +set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/classes/) set(classes_LIST io.realm.RealmQuery io.realm.internal.Table io.realm.internal.CheckedRow From bd59aa72e66ee6585039c0bd3642d2fce6fdb3ed Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 7 Mar 2020 22:27:44 +0100 Subject: [PATCH 1475/2110] Upgrade to AndroidX test artifacts (#6769) --- realm/build.gradle | 4 ++-- realm/gradle.properties | 5 ++++- realm/kotlin-extensions/build.gradle | 6 +++--- .../kotlin/io/realm/KotlinRealmModelTests.kt | 6 +++--- .../kotlin/io/realm/KotlinRealmQueryTests.kt | 6 +++--- .../kotlin/io/realm/KotlinRealmTests.kt | 3 +-- .../io/realm/kotlin/KotlinSyncedRealmTests.kt | 13 ++++++------- realm/realm-library/build.gradle | 15 ++++++++++----- .../java/io/realm/BulkInsertTests.java | 2 +- .../java/io/realm/ColumnInfoTests.java | 2 +- .../java/io/realm/CustomRealmNameTests.java | 2 +- .../java/io/realm/DynamicRealmObjectTests.java | 2 +- .../java/io/realm/DynamicRealmTests.java | 2 +- .../java/io/realm/FrozenObjectsTests.java | 2 +- .../src/androidTest/java/io/realm/GCTests.java | 2 +- .../androidTest/java/io/realm/IOSRealmTests.java | 4 ++-- .../java/io/realm/LinkingObjectsDynamicTests.java | 2 +- .../java/io/realm/LinkingObjectsManagedTests.java | 4 ++-- .../java/io/realm/LinkingObjectsQueryTests.java | 2 +- .../io/realm/LinkingObjectsUnmanagedTests.java | 2 +- .../ManagedRealmListForValue_toArrayTests.java | 1 - .../androidTest/java/io/realm/MediatorTest.java | 2 +- .../java/io/realm/MutableRealmIntegerTests.java | 6 +++--- .../java/io/realm/NotificationsTest.java | 13 +++---------- .../java/io/realm/ObjectChangeSetTests.java | 2 +- .../OrderedRealmCollectionIteratorTests.java | 4 ++-- .../OrderedRealmCollectionSnapshotTests.java | 2 +- .../java/io/realm/RealmAnnotationTests.java | 2 +- .../java/io/realm/RealmAsyncQueryTests.java | 4 ++-- .../java/io/realm/RealmCacheTests.java | 4 ++-- .../java/io/realm/RealmChangeListenerTests.java | 4 ++-- .../java/io/realm/RealmConfigurationTests.java | 6 +++--- .../java/io/realm/RealmInMemoryTest.java | 2 +- .../java/io/realm/RealmInterprocessTest.java | 8 ++++---- .../androidTest/java/io/realm/RealmJsonTests.java | 6 +++--- .../androidTest/java/io/realm/RealmLinkTests.java | 2 +- .../androidTest/java/io/realm/RealmListTests.java | 2 +- .../java/io/realm/RealmMigrationTests.java | 4 ++-- .../java/io/realm/RealmModelTests.java | 4 ++-- .../java/io/realm/RealmObjectTests.java | 4 ++-- .../java/io/realm/RealmProxyMediatorTests.java | 2 +- .../java/io/realm/RealmQueryTests.java | 2 +- .../java/io/realm/RealmResultsTests.java | 4 ++-- .../src/androidTest/java/io/realm/RealmTests.java | 6 +++--- .../realm/RunTestInLooperThreadLifeCycleTest.java | 2 +- .../androidTest/java/io/realm/RxJavaTests.java | 2 +- .../src/androidTest/java/io/realm/SortTest.java | 4 ++-- .../io/realm/TypeBasedNotificationsTests.java | 8 ++++---- .../realm/internal/AndroidCapabilitiesTest.java | 2 +- .../java/io/realm/internal/JNIColumnInfoTest.java | 4 ++-- .../java/io/realm/internal/JNINativeTest.java | 2 +- .../java/io/realm/internal/JNIQueryTest.java | 5 ++--- .../java/io/realm/internal/JNIRowTest.java | 4 ++-- .../io/realm/internal/JNITableInsertTest.java | 2 +- .../java/io/realm/internal/JNITableTest.java | 2 +- .../io/realm/internal/ObserverPairListTests.java | 2 +- .../java/io/realm/internal/OsListTests.java | 2 +- .../io/realm/internal/OsObjectStoreTests.java | 2 +- .../java/io/realm/internal/OsResultsTests.java | 2 +- .../io/realm/internal/OsSharedRealmTests.java | 2 +- .../java/io/realm/internal/PrimaryKeyTests.java | 3 +-- .../io/realm/internal/QueryDescriptorTests.java | 2 +- .../io/realm/internal/RealmNotifierTests.java | 2 +- .../realm/internal/TableIndexAndDistinctTest.java | 4 ++-- .../realm/internal/android/ISO8601UtilsTest.java | 2 +- .../io/realm/internal/android/JsonUtilsTest.java | 2 +- .../java/io/realm/log/RealmLogTests.java | 6 +++--- .../kotlin/io/realm/KotlinSchemaTests.kt | 2 +- .../java/io/realm/AuthenticateRequestTests.java | 6 +++--- .../java/io/realm/CredentialsTests.java | 2 +- .../java/io/realm/ProgressTests.java | 2 +- .../java/io/realm/SchemaTests.java | 2 +- .../java/io/realm/SessionTests.java | 6 +++--- .../java/io/realm/SyncConfigurationTests.java | 8 ++++---- .../java/io/realm/SyncManagerTests.java | 14 ++++++-------- .../java/io/realm/SyncUserTests.java | 12 ++++++------ .../java/io/realm/SyncedRealmMigrationTests.java | 2 +- .../java/io/realm/SyncedRealmTests.java | 6 +++--- .../TrustManagerCertificateValidationTests.java | 6 +++--- .../java/io/realm/BaseIntegrationTest.java | 2 +- .../java/io/realm/SSLConfigurationTests.java | 2 +- .../java/io/realm/SyncSessionTests.java | 2 +- .../io/realm/SyncedRealmIntegrationTests.java | 4 ++-- .../java/io/realm/objectserver/AuthTests.java | 2 +- .../io/realm/objectserver/ProcessCommitTests.java | 3 +-- .../realm/objectserver/ProgressListenerTests.java | 2 +- .../java/io/realm/SyncTestUtils.java | 8 ++++---- .../src/testUtils/java/io/realm/TestHelper.java | 4 ++-- .../java/io/realm/rule/RunWithRemoteService.java | 3 +-- .../realm/rule/TestRealmConfigurationFactory.java | 4 ++-- 90 files changed, 173 insertions(+), 181 deletions(-) diff --git a/realm/build.gradle b/realm/build.gradle index a0d23f19d1..3a314774e9 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get('GRADLE_BUILD_TOOLS')}" - classpath 'de.undercouch:gradle-download-task:3.4.3' + classpath 'de.undercouch:gradle-download-task:4.0.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.4' @@ -35,7 +35,7 @@ allprojects { project.ext.set(key, val) } project.ext.minSdkVersion = 16 - project.ext.compileSdkVersion = 28 + project.ext.compileSdkVersion = 29 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") group = 'io.realm' version = file("${rootDir}/../version.txt").text.trim() diff --git a/realm/gradle.properties b/realm/gradle.properties index 4bc510ffff..d3b9be87a4 100644 --- a/realm/gradle.properties +++ b/realm/gradle.properties @@ -1,7 +1,10 @@ org.gradle.jvmargs=-Xms512m -Xmx2048m org.gradle.caching=true -kotlin.incremental=false; +kotlin.incremental=false +org.gradle.parallel=false # See https://issuetracker.google.com/issues/80464216 # Can be removed when we upgrade to Android Build Tools 3.3.0 org.gradle.workers.max=1 +android.useAndroidX=true +android.enableJetifier=true diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index cec79019e2..d61b115d4b 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -25,7 +25,7 @@ android { versionName version project.archivesBaseName = "realm-kotlin-extensions" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { debug { @@ -73,8 +73,8 @@ dependencies { implementation project(':realm-library') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" androidTestImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test:rules:1.0.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:rules:1.2.0' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt index 4596fa3135..f57d34eaae 100644 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmModelTests.kt @@ -1,7 +1,7 @@ package io.realm -import android.support.test.InstrumentationRegistry -import android.support.test.runner.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.entities.PrimaryKeyClass import io.realm.entities.SimpleClass import io.realm.kotlin.* @@ -29,7 +29,7 @@ class KotlinRealmModelTests { @Before fun setUp() { - Realm.init(InstrumentationRegistry.getTargetContext()) + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) realm = Realm.getInstance(configFactory.createConfiguration()) } diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt index b909c3a4fe..ba6e423c16 100644 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmQueryTests.kt @@ -1,7 +1,7 @@ package io.realm -import android.support.test.InstrumentationRegistry -import android.support.test.runner.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.entities.AllPropTypesClass import io.realm.kotlin.createObject import io.realm.kotlin.oneOf @@ -28,7 +28,7 @@ class KotlinRealmQueryTests { @Before fun setUp() { - Realm.init(InstrumentationRegistry.getTargetContext()) + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) realm = Realm.getInstance(configFactory.createConfiguration()) } diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt index e15e68fe5b..1d51735447 100644 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/KotlinRealmTests.kt @@ -1,7 +1,6 @@ package io.realm -import android.support.test.InstrumentationRegistry -import android.support.test.runner.AndroidJUnit4 +import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.entities.PrimaryKeyClass import io.realm.entities.SimpleClass import io.realm.kotlin.createObject diff --git a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt index 7deb8e8bc5..6ed2c9c9d2 100644 --- a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt +++ b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt @@ -1,15 +1,12 @@ package io.realm.kotlin -import android.support.test.InstrumentationRegistry -import android.support.test.runner.AndroidJUnit4 +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import io.realm.* import io.realm.objectserver.utils.Constants -import org.junit.After +import org.junit.* import org.junit.Assert.assertEquals import org.junit.Assert.fail -import org.junit.Before -import org.junit.Rule -import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @@ -23,7 +20,7 @@ class KotlinSyncedRealmTests { @Before fun setUp() { - Realm.init(InstrumentationRegistry.getTargetContext()) + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) val user = SyncTestUtils.createTestUser() realm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM).build()) } @@ -33,11 +30,13 @@ class KotlinSyncedRealmTests { realm.close() } + @Ignore("FIXME") @Test fun syncSession() { assertEquals(SyncManager.getSession(realm.configuration as SyncConfiguration), realm.syncSession) } + @Ignore("FIXME") @Test fun syncSession_throwsForNonSyncRealm() { realm.close() diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 361b1f2dce..594501a84f 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -46,7 +46,7 @@ android { targetSdkVersion rootProject.compileSdkVersion versionName version project.archivesBaseName = "realm-android-library" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { arguments "-DREALM_CORE_DIST_DIR:STRING=${project.coreDir.getAbsolutePath()}", @@ -216,8 +216,8 @@ dependencies { kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' androidTestImplementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test:rules:1.0.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' @@ -584,13 +584,18 @@ if (project.hasProperty('dontCleanJniFiles')) { project.afterEvaluate { android.libraryVariants.all { variant -> if (project.hasProperty('buildTargetABIs') && project.getProperty('buildTargetABIs').trim().isEmpty()) { - variant.externalNativeBuildTasks[0].enabled = false + variant.externalNativeBuildProviders[0].configure { + enabled = false + } } // all Java files must be compiled before native build + // See https://github.com/android/ndk-samples/issues/284 android.libraryVariants.all { anotherVariant -> if (variant.flavorName == anotherVariant.flavorName) { - variant.externalNativeBuildTasks[0].dependsOn("compile${anotherVariant.name.capitalize()}JavaWithJavac") + variant.externalNativeBuildProviders[0].configure { + dependsOn "compile${anotherVariant.name.capitalize()}JavaWithJavac" + } } } // as of android gradle plugin 3.0.0-alpha5, generateJsonModel* triggers native build. Java files must be compiled before them. diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java index 3b1142cfc0..2f904d56eb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java index 9022d49b87..226e2beede 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ColumnInfoTests.java @@ -15,7 +15,7 @@ */ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java b/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java index 829c3386db..17a1adf5c2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/CustomRealmNameTests.java @@ -15,7 +15,7 @@ */ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 0acf690f3c..8f2f5ad754 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.Matchers; import org.junit.After; diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index 6dba5227a9..e8b7cbb295 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java b/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java index ee2c0786ab..bf71ce5b3b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java @@ -15,7 +15,7 @@ */ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/GCTests.java b/realm/realm-library/src/androidTest/java/io/realm/GCTests.java index 106733e813..09b5f7d0b5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/GCTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/GCTests.java @@ -17,7 +17,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index 67a431475c..fe25eddc86 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -17,8 +17,8 @@ package io.realm; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index 0280be4916..61a49ece18 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 3cd48ae070..13ed88e978 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -17,8 +17,8 @@ package io.realm; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.CoreMatchers; import org.junit.After; diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java index 4b2d657c55..b7f16c586e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java @@ -15,7 +15,7 @@ */ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java index 633184a5d8..5e5ce1e446 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsUnmanagedTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValue_toArrayTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValue_toArrayTests.java index 1d7dc8b403..b59c859466 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValue_toArrayTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedRealmListForValue_toArrayTests.java @@ -53,7 +53,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; - /** * Unit tests specific for RealmList with value elements. */ diff --git a/realm/realm-library/src/androidTest/java/io/realm/MediatorTest.java b/realm/realm-library/src/androidTest/java/io/realm/MediatorTest.java index 29c034cfa7..a246325427 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/MediatorTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/MediatorTest.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/realm/realm-library/src/androidTest/java/io/realm/MutableRealmIntegerTests.java b/realm/realm-library/src/androidTest/java/io/realm/MutableRealmIntegerTests.java index ffb65eb4da..7082382480 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/MutableRealmIntegerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/MutableRealmIntegerTests.java @@ -16,8 +16,8 @@ package io.realm; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.json.JSONException; import org.json.JSONObject; @@ -397,7 +397,7 @@ public void testJSON() throws JSONException { @Test public void testStream() throws IOException { - Context context = InstrumentationRegistry.getTargetContext(); + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); InputStream in = TestHelper.loadJsonFromAssets(context, "empty.json"); realm.beginTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java index 27ef50903e..8fab0a0982 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/NotificationsTest.java @@ -19,22 +19,19 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; -import android.support.test.annotation.UiThreadTest; -import android.support.test.rule.UiThreadTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.annotation.UiThreadTest; +import androidx.test.rule.UiThreadTestRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import android.util.Log; import junit.framework.AssertionFailedError; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -46,18 +43,14 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nullable; - import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.Dog; -import io.realm.log.LogLevel; import io.realm.log.RealmLog; import io.realm.log.RealmLogger; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; -import kotlin.reflect.jvm.internal.impl.descriptors.deserialization.PlatformDependentDeclarationFilter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java index 2755132a69..11d7417fed 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ObjectChangeSetTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java index 1554c9a0ce..40240cfa3d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionIteratorTests.java @@ -16,8 +16,8 @@ package io.realm; -import android.support.test.annotation.UiThreadTest; -import android.support.test.rule.UiThreadTestRule; +import androidx.test.annotation.UiThreadTest; +import androidx.test.rule.UiThreadTestRule; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionSnapshotTests.java b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionSnapshotTests.java index 6d43158ba5..d20fbda071 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionSnapshotTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/OrderedRealmCollectionSnapshotTests.java @@ -17,7 +17,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java index be3fdb1e13..29d100495d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAnnotationTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index b0a58f1085..d3b4754d2c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -17,8 +17,8 @@ package io.realm; import android.os.SystemClock; -import android.support.test.rule.UiThreadTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.rule.UiThreadTestRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java index 9fd28e2e0d..67b6223a90 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmCacheTests.java @@ -17,8 +17,8 @@ package io.realm; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Rule; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java index 740dddad4f..02ab1437c6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmChangeListenerTests.java @@ -16,8 +16,8 @@ package io.realm; -import android.support.test.rule.UiThreadTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.rule.UiThreadTestRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.CoreMatchers; import org.junit.After; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 45cbb1cdd3..ba0fca9266 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -17,8 +17,8 @@ package io.realm; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; @@ -87,7 +87,7 @@ public class RealmConfigurationTests { @Before public void setUp() { - context = InstrumentationRegistry.getTargetContext(); + context = InstrumentationRegistry.getInstrumentation().getTargetContext(); defaultConfig = configFactory.createConfiguration(); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index 0b48697da2..20da46d837 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import junit.framework.AssertionFailedError; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java index 55dba9fe68..946f71c612 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java @@ -29,9 +29,9 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; -import android.support.test.InstrumentationRegistry; -import android.support.test.annotation.UiThreadTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; @@ -234,7 +234,7 @@ private ActivityManager.RunningServiceInfo getServiceInfo() { } private Context getContext() { - return InstrumentationRegistry.getTargetContext(); + return InstrumentationRegistry.getInstrumentation().getTargetContext(); } // Gets the remote process info if it is alive. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index dff06ec723..873b3148ca 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -18,8 +18,8 @@ import android.content.Context; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import android.util.Base64; import org.json.JSONArray; @@ -81,7 +81,7 @@ public class RealmJsonTests { @Before public void setUp() { - context = InstrumentationRegistry.getTargetContext(); + context = InstrumentationRegistry.getInstrumentation().getTargetContext(); RealmConfiguration realmConfig = configFactory.createConfiguration(); realm = Realm.getInstance(realmConfig); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java index e9c94fb610..e59d21a3c6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmLinkTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java index 74f75dddbf..c1d20edcc9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmListTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.CoreMatchers; import org.junit.After; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 75bc6be6e5..4e83ab71ec 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -17,8 +17,8 @@ package io.realm; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.CoreMatchers; import org.junit.After; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java index 2f3c334857..765c55ceec 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmModelTests.java @@ -18,8 +18,8 @@ import android.content.Context; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Assert; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index dee26d5ef6..b22f7fcc37 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -16,8 +16,8 @@ package io.realm; -import android.support.test.rule.UiThreadTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.rule.UiThreadTestRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.CoreMatchers; import org.junit.After; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java index 1b029f415f..dd87166c08 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmProxyMediatorTests.java @@ -15,7 +15,7 @@ */ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 475e9e24d6..c128e8f70d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Ignore; import org.junit.Test; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 30bc219ed6..90d0b13b6c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -16,8 +16,8 @@ package io.realm; -import android.support.test.annotation.UiThreadTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.json.JSONException; import org.junit.After; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 2028a668aa..74023fb904 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -20,9 +20,9 @@ import android.os.Build; import android.os.Looper; import android.os.SystemClock; -import android.support.test.InstrumentationRegistry; -import android.support.test.rule.UiThreadTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.UiThreadTestRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import junit.framework.AssertionFailedError; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java b/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java index f0945fbdc6..f2b394fff3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RunTestInLooperThreadLifeCycleTest.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index d77a9feb0d..e33db3f09d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -17,7 +17,7 @@ package io.realm; import android.os.SystemClock; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Rule; diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 45b23de504..e2e54a4262 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -17,8 +17,8 @@ package io.realm; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java index 86067e4bb5..3eb2d4d1f0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/TypeBasedNotificationsTests.java @@ -17,8 +17,8 @@ import android.content.Context; import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import android.util.Base64; import org.json.JSONException; @@ -67,7 +67,7 @@ public class TypeBasedNotificationsTests { @Before public void setUp() { - context = InstrumentationRegistry.getTargetContext(); + context = InstrumentationRegistry.getInstrumentation().getTargetContext(); globalCommitInvocations = new AtomicInteger(0); typebasedCommitInvocations = new AtomicInteger(0); } @@ -252,7 +252,7 @@ public void callback_should_trigger_for_createObjectFromJson() { final Realm realm = looperThread.getRealm(); try { - InputStream in = TestHelper.loadJsonFromAssets(InstrumentationRegistry.getTargetContext(), "all_simple_types.json"); + InputStream in = TestHelper.loadJsonFromAssets(InstrumentationRegistry.getInstrumentation().getTargetContext(), "all_simple_types.json"); realm.beginTransaction(); final AllTypes objectFromJson = realm.createObjectFromJson(AllTypes.class, in); realm.commitTransaction(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/AndroidCapabilitiesTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/AndroidCapabilitiesTest.java index b3ca055a6d..f2528ce426 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/AndroidCapabilitiesTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/AndroidCapabilitiesTest.java @@ -15,7 +15,7 @@ */ package io.realm.internal; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java index ddbe875804..e5aa9b1807 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIColumnInfoTest.java @@ -16,8 +16,8 @@ package io.realm.internal; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNINativeTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNINativeTest.java index b8dbec776b..137b484fd7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNINativeTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNINativeTest.java @@ -16,7 +16,7 @@ package io.realm.internal; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index 3e3668c074..f3e30ae85d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -16,12 +16,11 @@ package io.realm.internal; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java index 4f50e12414..83966cc5be 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java @@ -16,8 +16,8 @@ package io.realm.internal; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java index c7d411f6f7..c0339e1cd5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java @@ -16,7 +16,7 @@ package io.realm.internal; -import android.support.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index 7e6ecc3580..bae1190be5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -16,7 +16,7 @@ package io.realm.internal; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java index fb77df3b1e..7559b7471a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/ObserverPairListTests.java @@ -17,7 +17,7 @@ package io.realm.internal; import android.annotation.SuppressLint; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java index 98f35e85dc..2b79b9d1b2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java @@ -16,7 +16,7 @@ package io.realm.internal; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java index be314b5baa..856d105599 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsObjectStoreTests.java @@ -15,7 +15,7 @@ */ package io.realm.internal; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java index 1c57bf704e..1537705e89 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsResultsTests.java @@ -17,7 +17,7 @@ package io.realm.internal; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java index 5d5b7aa180..a9ca9a61c8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java @@ -15,7 +15,7 @@ */ package io.realm.internal; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index 29abbe9a82..3ef3916881 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -16,8 +16,7 @@ package io.realm.internal; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java index 5e22340d79..3c6cf31486 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java @@ -16,7 +16,7 @@ package io.realm.internal; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java index bdd33a580b..b993de9ac6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/RealmNotifierTests.java @@ -16,7 +16,7 @@ package io.realm.internal; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java index f7e2e5c75d..55de5c8889 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/TableIndexAndDistinctTest.java @@ -16,8 +16,8 @@ package io.realm.internal; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java index 63042386a8..f7fbcb1002 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/android/ISO8601UtilsTest.java @@ -16,7 +16,7 @@ */ package io.realm.internal.android; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java index e9333fb789..3fb8ead5f6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/android/JsonUtilsTest.java @@ -16,7 +16,7 @@ */ package io.realm.internal.android; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/realm/realm-library/src/androidTest/java/io/realm/log/RealmLogTests.java b/realm/realm-library/src/androidTest/java/io/realm/log/RealmLogTests.java index 8584214176..396a5c1247 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/log/RealmLogTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/log/RealmLogTests.java @@ -1,7 +1,7 @@ package io.realm.log; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; @@ -20,7 +20,7 @@ public class RealmLogTests { @Before public void setUp() { - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); } @Test diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/KotlinSchemaTests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/KotlinSchemaTests.kt index a6a9c34b5a..6f3adea637 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/KotlinSchemaTests.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/KotlinSchemaTests.kt @@ -15,7 +15,7 @@ */ package io.realm -import android.support.test.runner.AndroidJUnit4 +import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.entities.AllKotlinTypes import io.realm.rule.TestRealmConfigurationFactory import org.junit.After diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index cccf23178f..8bd80e18b4 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -1,8 +1,8 @@ package io.realm; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.json.JSONException; import org.json.JSONObject; @@ -31,7 +31,7 @@ public class AuthenticateRequestTests { @Before public void setUp() { - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); } // Tests based on the schemas described here: https://github.com/realm/realm-sync-services/blob/master/doc/index.apib diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java index 745b2a940c..84c342b93f 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java @@ -15,7 +15,7 @@ * limitations under the License. */ -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java index 22c31bc2a5..dbe44b6378 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java index b57db5696a..fe0fa55af0 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java @@ -17,7 +17,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Rule; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index a47e58d0e8..6adddd3b26 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -16,9 +16,9 @@ package io.realm; -import android.support.test.annotation.UiThreadTest; -import android.support.test.rule.UiThreadTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.annotation.UiThreadTest; +import androidx.test.rule.UiThreadTestRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.CoreMatchers; import org.junit.Before; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index 20f40ff0ac..a82bf9aa7a 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -16,8 +16,8 @@ package io.realm; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; @@ -63,7 +63,7 @@ public class SyncConfigurationTests { @Before public void setUp() { - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); } @After @@ -100,7 +100,7 @@ public void serverUrl_setsFolderAndFileName() { SyncConfiguration config = user.createConfiguration(serverUrl).build(); - assertEquals(new File(InstrumentationRegistry.getContext().getFilesDir(), expectedFolder), config.getRealmDirectory()); + assertEquals(new File(InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), expectedFolder), config.getRealmDirectory()); assertEquals(expectedFileName, config.getRealmFileName()); } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index a4f577bd34..969f904ada 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -16,8 +16,8 @@ package io.realm; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; @@ -39,10 +39,8 @@ import io.realm.objectserver.utils.UserFactory; import io.realm.rule.TestRealmConfigurationFactory; -import static io.realm.SyncTestUtils.createTestUser; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static io.realm.SyncTestUtils.*; +import static org.junit.Assert.*; @RunWith(AndroidJUnit4.class) public class SyncManagerTests { @@ -97,7 +95,7 @@ public void tearDown() { } SyncManager.reset(); BaseRealm.applicationContext = null; // Required for Realm.init() to work - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); } @Test @@ -172,7 +170,7 @@ public void loggedOut(SyncUser user) { @Test public void session() throws IOException { BaseRealm.applicationContext = null; - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); SyncUser user = createTestUser(); String url = "realm://objectserver.realm.io/default"; SyncConfiguration config = user.createConfiguration(url) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 4e6a04fa53..e83cd430bc 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -16,9 +16,9 @@ package io.realm; -import android.support.test.InstrumentationRegistry; -import android.support.test.rule.UiThreadTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.UiThreadTestRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; @@ -94,7 +94,7 @@ public class SyncUserTests { @Before public void setUp() { BaseRealm.applicationContext = null; - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); UserStore userStore = SyncManager.getUserStore(); for (SyncUser syncUser : userStore.allUsers()) { userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); @@ -488,7 +488,7 @@ public void fromJson_WorkWithRemovedObjectServerUser() { public void logoutUserShouldDeleteRealmAfterRestart() throws InterruptedException { SyncManager.reset(); BaseRealm.applicationContext = null; // Required for Realm.init() to work - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); SyncUser user = createTestUser(); SyncConfiguration syncConfiguration = user.createConfiguration("realm://127.0.0.1:9080/~/tests") @@ -511,7 +511,7 @@ public void execute(Realm realm) { // simulate an app restart SyncManager.reset(); BaseRealm.applicationContext = null; - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); //now the file should be deleted assertFalse(realmPath.exists()); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index a00d510bf6..49443ce1a1 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.CoreMatchers; import org.junit.BeforeClass; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index fb1795eb38..dcd1eb5cd7 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -15,8 +15,8 @@ */ package io.realm; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Ignore; @@ -93,7 +93,7 @@ public void testUpgradingOptionalSubscriptionFields() throws IOException { File realmDir = config.getRealmDirectory(); File oldRealmFile = new File(realmDir, "optionalsubscriptionfields"); assertFalse(oldRealmFile.exists()); - configFactory.copyFileFromAssets(InstrumentationRegistry.getTargetContext().getApplicationContext(), "optionalsubscriptionfields.realm", oldRealmFile); + configFactory.copyFileFromAssets(InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(), "optionalsubscriptionfields.realm", oldRealmFile); assertTrue(oldRealmFile.exists()); try { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java index b7ef785f6e..fcc41b7e01 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java @@ -1,7 +1,7 @@ package io.realm; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.BeforeClass; import org.junit.Ignore; @@ -19,7 +19,7 @@ public static void setUp() { // mainly to setup logging otherwise // java.lang.UnsatisfiedLinkError: No implementation found for void io.realm.log.RealmLog.nativeSetLogLevel(int) (tried Java_io_realm_log_RealmLog_nativeSetLogLevel and Java_io_realm_log_RealmLog_nativeSetLogLevel__I) // will be thrown - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); } // IMPORTANT: Following test assume the root certificate is installed on the test device diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java index 0c9227a59a..ff1bfaf98c 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/BaseIntegrationTest.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.rule.UiThreadTestRule; +import androidx.test.rule.UiThreadTestRule; import android.util.Log; import org.junit.Rule; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java index d2f8fdeb71..b41e5147ab 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java @@ -17,7 +17,7 @@ package io.realm; import android.os.SystemClock; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Ignore; import org.junit.Rule; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java index a24fabbcd8..a03b2e30eb 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java @@ -4,7 +4,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.SystemClock; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Assert; import org.junit.Ignore; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index fad0605944..a6f04205e5 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -17,8 +17,8 @@ package io.realm; import android.os.SystemClock; -import android.support.test.annotation.UiThreadTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Ignore; import org.junit.Test; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index 8914ac1566..d9ec65f998 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -3,7 +3,7 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Assert; import org.junit.Ignore; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java index 9651cdf4ce..fbcda87b6b 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProcessCommitTests.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package io.realm.objectserver; import android.os.Looper; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index 5f585473ad..39fb68e0bb 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -16,7 +16,7 @@ package io.realm.objectserver; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Ignore; import org.junit.Rule; diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java index 8a7c228a5c..e1a05ab892 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java @@ -16,7 +16,7 @@ package io.realm; -import android.support.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import org.json.JSONException; import org.json.JSONObject; @@ -53,7 +53,7 @@ public class SyncTestUtils { } public static void prepareEnvironmentForTest(){ - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); originalLogLevel = RealmLog.getLevel(); RealmLog.setLevel(LogLevel.DEBUG); } @@ -81,14 +81,14 @@ public static void restoreEnvironmentAfterTest() throws IOException { BaseRealm.applicationContext = null; // Required for Realm.init() to work } deleteRosFiles(); - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); } // Cleanup filesystem to make sure nothing lives for the next test. // Failing to do so might lead to DIVERGENT_HISTORY errors being thrown if Realms from // previous tests are being accessed. private static void deleteRosFiles() throws IOException { - File rosFiles = new File(InstrumentationRegistry.getContext().getFilesDir(),"realm-object-server"); + File rosFiles = new File(InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),"realm-object-server"); deleteFile(rosFiles); } diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 2c3fdeedd9..98940c9662 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -20,7 +20,7 @@ import android.content.res.AssetManager; import android.os.Build; import android.os.Looper; -import android.support.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Assert; @@ -474,7 +474,7 @@ public static RealmConfiguration createConfiguration(Context context, String nam */ @Deprecated public static RealmConfiguration createConfiguration(File dir, String name, byte[] key) { - RealmConfiguration.Builder config = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) + RealmConfiguration.Builder config = new RealmConfiguration.Builder(InstrumentationRegistry.getInstrumentation().getTargetContext()) .directory(dir) .name(name); if (key != null) { diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java index 8aff527678..b5f8a62827 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java @@ -39,8 +39,7 @@ import io.realm.TestHelper; import io.realm.services.RemoteTestService; -import static android.support.test.InstrumentationRegistry.getContext; -import static junit.framework.Assert.assertTrue; +import static androidx.test.InstrumentationRegistry.getContext; import static junit.framework.Assert.fail; /** diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java index 8e27668f00..869bcc1990 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -17,7 +17,7 @@ package io.realm.rule; import android.content.Context; -import android.support.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.rules.TemporaryFolder; import org.junit.runner.Description; @@ -73,7 +73,7 @@ public void evaluate() throws Throwable { @Override protected void before() throws Throwable { - Realm.init(InstrumentationRegistry.getTargetContext()); + Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); super.before(); } From 881489027d47ce48308ef91dca936c2e7095d84c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 8 Mar 2020 21:55:40 +0100 Subject: [PATCH 1476/2110] Temporarely disable broken unit tests (#6770) --- .../groovy/io/realm/gradle/PluginTest.groovy | 24 ++++++++++++------- realm/realm-library/build.gradle | 2 +- .../io/realm/AuthenticateRequestTests.java | 2 ++ .../java/io/realm/ProgressTests.java | 2 ++ .../java/io/realm/SchemaTests.java | 2 ++ .../java/io/realm/SessionTests.java | 2 ++ .../java/io/realm/SyncConfigurationTests.java | 1 + .../java/io/realm/SyncManagerTests.java | 2 ++ .../java/io/realm/SyncUserTests.java | 1 + .../io/realm/SyncedRealmMigrationTests.java | 2 ++ .../java/io/realm/SyncedRealmTests.java | 1 + .../objectserver/utils/StringOnlyModule.java | 0 .../main/cpp/io_realm_RealmFileUserStore.cpp | 8 ++++--- 13 files changed, 36 insertions(+), 13 deletions(-) rename realm/realm-library/src/{syncIntegrationTest => androidTestObjectServer}/java/io/realm/objectserver/utils/StringOnlyModule.java (100%) diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index 3ced4b57ed..54a59ca3f6 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -54,6 +54,7 @@ class PluginTest { project.buildscript { repositories { mavenLocal() + mavenCentral() google() jcenter() } @@ -90,6 +91,7 @@ class PluginTest { project.buildscript { repositories { mavenLocal() + mavenCentral() jcenter() } dependencies { @@ -114,6 +116,7 @@ class PluginTest { maven { url 'https://maven.google.com/' } + mavenCentral() jcenter() } dependencies { @@ -139,8 +142,8 @@ class PluginTest { project.evaluate() - assertEquals(2, project.buildscript.repositories.size()) - assertEquals(4, project.repositories.size()) // The Android plugin adds 3 different local repos + assertEquals(3, project.buildscript.repositories.size()) + assertEquals(1, project.repositories.size()) assertEquals('jcenter.bintray.com', project.repositories.last().url.host) } @@ -148,6 +151,7 @@ class PluginTest { void pluginAddsRightRepositories_withRepositoriesSet() { project.buildscript { repositories { + mavenCentral() jcenter() maven { url 'https://maven.google.com/' @@ -180,10 +184,10 @@ class PluginTest { project.evaluate() - assertEquals(2, project.buildscript.repositories.size()) + assertEquals(3, project.buildscript.repositories.size()) assertEquals('maven.google.com', project.buildscript.repositories.last().url.host) - assertEquals(4, project.repositories.size()) + assertEquals(1, project.repositories.size()) assertEquals('dl.google.com', project.repositories.last().url.host) } @@ -196,6 +200,7 @@ class PluginTest { maven { url 'https://maven.google.com/' } + mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" @@ -227,10 +232,10 @@ class PluginTest { project.evaluate() - assertEquals(2, project.buildscript.repositories.size()) - assertEquals('maven.google.com', project.buildscript.repositories.last().url.host) + assertEquals(3, project.buildscript.repositories.size()) + assertEquals('repo.maven.apache.org', project.buildscript.repositories.last().url.host) - assertEquals(5, project.repositories.size()) + assertEquals(2, project.repositories.size()) assertEquals('dl.google.com', project.repositories.last().url.host) } @@ -242,6 +247,7 @@ class PluginTest { maven { url 'https://maven.google.com/' } + mavenCentral() jcenter() } dependencies { @@ -271,10 +277,10 @@ class PluginTest { project.evaluate() - assertEquals(2, project.buildscript.repositories.size()) + assertEquals(3, project.buildscript.repositories.size()) assertEquals('maven.google.com', project.buildscript.repositories.first().url.host) - assertEquals(4, project.repositories.size()) + assertEquals(1, project.repositories.size()) assertEquals('dl.google.com', project.repositories.last().url.host) } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 594501a84f..77c02f5497 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -100,7 +100,7 @@ android { java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java', 'src/testUtils/kotlin'] } androidTestObjectServer { - java.srcDirs += ['src/syncIntegrationTest/java', 'src/syncTestUtils/java'] + java.srcDirs += [/* FIXME 'src/syncIntegrationTest/java', */'src/syncTestUtils/java'] assets.srcDirs += ['src/syncIntegrationTest/assets/'] } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java index 8bd80e18b4..66a3c7b9ce 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java @@ -7,6 +7,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -26,6 +27,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.when; +@Ignore("FIXME: RealmApp refactor") @RunWith(AndroidJUnit4.class) public class AuthenticateRequestTests { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java index dbe44b6378..61b5415b7d 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java @@ -18,6 +18,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -25,6 +26,7 @@ import static org.junit.Assert.assertEquals; +@Ignore("FIXME: RealmApp refactor") @RunWith(AndroidJUnit4.class) public class ProgressTests { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java index fe0fa55af0..3a8728a2f9 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java @@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,6 +35,7 @@ import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.fail; +@Ignore("FIXME: RealmApp refactor") @RunWith(AndroidJUnit4.class) public class SchemaTests { @Rule diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 6adddd3b26..4fcbf6b56a 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -22,6 +22,7 @@ import org.hamcrest.CoreMatchers; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,6 +46,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +@Ignore("FIXME: RealmApp refactor") @RunWith(AndroidJUnit4.class) public class SessionTests { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index a82bf9aa7a..cb487c1013 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -47,6 +47,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +@Ignore("FIXME: RealmApp Refactor") @RunWith(AndroidJUnit4.class) public class SyncConfigurationTests { @Rule diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 969f904ada..026baad335 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -21,6 +21,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -42,6 +43,7 @@ import static io.realm.SyncTestUtils.*; import static org.junit.Assert.*; +@Ignore("FIXME: RealmApp refactor") @RunWith(AndroidJUnit4.class) public class SyncManagerTests { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index e83cd430bc..4c61cce570 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -65,6 +65,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.when; +@Ignore("FIXME: REalmApp refactor") @RunWith(AndroidJUnit4.class) public class SyncUserTests { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index 49443ce1a1..11aa251bbc 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -20,6 +20,7 @@ import org.hamcrest.CoreMatchers; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -46,6 +47,7 @@ /** * Testing methods around migrations for Realms using a {@link SyncConfiguration}. */ +@Ignore("FIXME: RealmApp refactor") @RunWith(AndroidJUnit4.class) public class SyncedRealmMigrationTests { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index dcd1eb5cd7..ab523aca0c 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -41,6 +41,7 @@ /** * Testing sync specific methods on {@link Realm}. */ +@Ignore("FIXME: RealmApp refactor") @RunWith(AndroidJUnit4.class) public class SyncedRealmTests { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/StringOnlyModule.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/objectserver/utils/StringOnlyModule.java similarity index 100% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/StringOnlyModule.java rename to realm/realm-library/src/androidTestObjectServer/java/io/realm/objectserver/utils/StringOnlyModule.java diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index e504cfce59..537a1a2e40 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -69,12 +69,14 @@ JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetUser(JNIEnv* JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser(JNIEnv* env, jclass, jstring j_user_id, - jstring j_refresh_json_token, + jstring /*j_refresh_json_token*/, jstring j_auth_url) { try { - JStringAccessor refresh_json_token(env, j_refresh_json_token); // throws - SyncManager::shared().get_user(create_sync_user_identifier(env, j_user_id, j_auth_url), refresh_json_token, refresh_json_token); +// FIXME +// JStringAccessor refresh_json_token(env, j_refresh_json_token); // throws + std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODE1MDc3OTYsImlhdCI6MTU4MTUwNTk5NiwiaXNzIjoiNWU0M2RkY2M2MzZlZTEwNmVhYTEyYmRjIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWUxNDk5MTNjOTBiNGFmMGViZTkzNTI3Iiwic3ViIjoiNWU0M2RkY2M2MzZlZTEwNmVhYTEyYmRhIiwidHlwIjoiYWNjZXNzIn0.0q3y9KpFxEnbmRwahvjWU1v9y1T1s3r2eozu93vMc3s"; + SyncManager::shared().get_user(create_sync_user_identifier(env, j_user_id, j_auth_url), token, token); } CATCH_STD() } From b0658e707a7f2d2b071a3760a62a63e236381268 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 11 Mar 2020 07:47:25 +0100 Subject: [PATCH 1477/2110] Upgrade to latest Sync alpha.4 release + ObjectStore (#6772) --- dependencies.list | 4 +-- realm/kotlin-extensions/build.gradle | 16 ++++----- .../main/cpp/io_realm_RealmFileUserStore.cpp | 30 +++++++++------- .../cpp/io_realm_internal_OsRealmConfig.cpp | 6 ++-- .../cpp/io_realm_internal_OsSharedRealm.cpp | 34 ------------------- .../src/main/cpp/io_realm_internal_Table.cpp | 2 +- .../cpp/io_realm_internal_UncheckedRow.cpp | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/internal/OsSharedRealm.java | 14 +++----- 9 files changed, 37 insertions(+), 73 deletions(-) diff --git a/dependencies.list b/dependencies.list index 09ccce28b6..d1a946c56d 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.2 -REALM_SYNC_SHA256=02b78b8961936c61b3f09667ced0c2738de7ed84b299ddeecfbb660ceb227dc9 +REALM_SYNC_VERSION=10.0.0-alpha.4 +REALM_SYNC_SHA256=7048eff89f00554aa4014239a25d419fc98d0b22074ff2deadd3e9717a5c4dee # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index d61b115d4b..1f0ad6cd62 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -104,18 +104,16 @@ dokka { outputFormat = 'html' outputDirectory = "$buildDir/docs" configuration { - externalDocumentationLink { - noJdkLink = true - noStdlibLink = true - noAndroidSdkLink = true - // Workaround until https://github.com/Kotlin/dokka/issues/709 is fixed - url = new URL("https://kotlinlang.org/api/latest/jvm/stdlib/") - packageListUrl = new URL("file://${rootDir}/kotlin-extensions/lib/package-list.txt") - } +// FIXME: +// externalDocumentationLink { +// noJdkLink = true +// noStdlibLink = true +// noAndroidSdkLink = true +// } } } -task javadocJar(type: Jar, dependsOn: dokka) { +task javadocJar(type: Jar/* FIXME: , dependsOn: dokka*/) { classifier = 'javadoc' from "$buildDir/dokka" } diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp index 537a1a2e40..7beed1a127 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp @@ -22,6 +22,7 @@ #include "java_class_global_def.hpp" #include "util.hpp" #include "jni_util/log.hpp" +#include "object-store/src/sync/sync_user.hpp" using namespace realm; using namespace realm::_impl; @@ -38,7 +39,7 @@ static jstring to_user_string_or_null(JNIEnv* env, const std::shared_ptr user = SyncManager::shared().get_existing_logged_in_user(user_identifier.id); return to_user_string_or_null(env, user); } CATCH_STD() @@ -73,20 +74,22 @@ JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser jstring j_auth_url) { try { -// FIXME -// JStringAccessor refresh_json_token(env, j_refresh_json_token); // throws + // FIXME Replace in RealmApp refactor + JStringAccessor id(env, j_user_id); + JStringAccessor auth_url(env, j_auth_url); std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODE1MDc3OTYsImlhdCI6MTU4MTUwNTk5NiwiaXNzIjoiNWU0M2RkY2M2MzZlZTEwNmVhYTEyYmRjIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWUxNDk5MTNjOTBiNGFmMGViZTkzNTI3Iiwic3ViIjoiNWU0M2RkY2M2MzZlZTEwNmVhYTEyYmRhIiwidHlwIjoiYWNjZXNzIn0.0q3y9KpFxEnbmRwahvjWU1v9y1T1s3r2eozu93vMc3s"; - SyncManager::shared().get_user(create_sync_user_identifier(env, j_user_id, j_auth_url), token, token); + SyncManager::shared().get_user(id, auth_url, token, token); } CATCH_STD() } JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeLogoutUser(JNIEnv* env, jclass, jstring j_user_id, - jstring j_auth_url) + jstring /*j_auth_url*/) { try { - auto user = SyncManager::shared().get_existing_logged_in_user( - create_sync_user_identifier(env, j_user_id, j_auth_url)); + // FIXME Replace in RealmApp refactor + JStringAccessor id(env, j_user_id); + auto user = SyncManager::shared().get_existing_logged_in_user(id); if (user) { user->log_out(); } @@ -95,11 +98,12 @@ JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeLogoutUser(JNIEnv* } JNIEXPORT jboolean JNICALL Java_io_realm_RealmFileUserStore_nativeIsActive(JNIEnv* env, jclass, jstring j_user_id, - jstring j_auth_url) + jstring /*j_auth_url*/) { try { - auto user = SyncManager::shared().get_existing_logged_in_user( - create_sync_user_identifier(env, j_user_id, j_auth_url)); + // FIXME Replace in RealmApp refactor + JStringAccessor id(env, j_user_id); + auto user = SyncManager::shared().get_existing_logged_in_user(id); if (user) { return to_jbool(user->state() == SyncUser::State::Active); } @@ -110,7 +114,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_RealmFileUserStore_nativeIsActive(JNIEn JNIEXPORT jobjectArray JNICALL Java_io_realm_RealmFileUserStore_nativeGetAllUsers(JNIEnv* env, jclass) { - auto all_users = SyncManager::shared().all_logged_in_users(); + auto all_users = SyncManager::shared().all_users(); if (!all_users.empty()) { size_t len = all_users.size(); jobjectArray users_token = env->NewObjectArray(len, JavaClassGlobalDef::java_lang_string(), 0); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index cf9a1f03ce..9b1a1a3909 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -341,13 +341,13 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe // Get logged in user JStringAccessor user_id(env, j_user_id); JStringAccessor auth_url(env, j_auth_url); - SyncUserIdentifier sync_user_identifier = {user_id, auth_url}; - std::shared_ptr user = SyncManager::shared().get_existing_logged_in_user(sync_user_identifier); + std::shared_ptr user = SyncManager::shared().get_existing_logged_in_user(user_id); if (!user) { JStringAccessor realm_auth_url(env, j_auth_url); JStringAccessor refresh_token(env, j_refresh_token); JStringAccessor access_token(env, j_access_token); - user = SyncManager::shared().get_user(sync_user_identifier, refresh_token, access_token); + // FIXME RealmApp refactor + user = SyncManager::shared().get_user(user_id, auth_url, refresh_token, access_token); } SyncSessionStopPolicy session_stop_policy = static_cast(j_session_stop_policy); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index be50790e46..9781e9f170 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -480,40 +480,6 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterSchema } } -#if REALM_ENABLE_SYNC -JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetRealmPrivileges( - JNIEnv*, jclass, jlong shared_realm_ptr) -{ - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - return static_cast(shared_realm->get_privileges()); -} - -JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetClassPrivileges( - JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_class_name) -{ - try { - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - JStringAccessor class_name(env, j_class_name); - return static_cast(shared_realm->get_privileges(StringData(class_name))); - } - CATCH_STD() - return 0; -} - -JNIEXPORT jint JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetObjectPrivileges( - JNIEnv* env, jclass, jlong shared_realm_ptr, jlong row_ptr) -{ - try { - auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto r = reinterpret_cast(row_ptr); - auto obj = r->get_table()->get_object(r->get_key()); - return static_cast(shared_realm->get_privileges(obj)); - } - CATCH_STD() - return 0; -} -#endif - JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsPartial(JNIEnv*, jclass, jlong /*shared_realm_ptr*/) { // No throws diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index ccdf5dc06a..9f5ff027f6 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -859,6 +859,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFreeze(JNIEnv*, jclas { auto& shared_realm = *(reinterpret_cast(j_frozen_shared_realm_ptr)); TableRef table = TableRef(TBL_REF(j_table_ptr)); - TableRef* frozen_table = new TableRef(shared_realm->transaction().import_copy_of(table)); + TableRef* frozen_table = new TableRef(shared_realm->import_copy_of(table)); return reinterpret_cast(frozen_table); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index aa4b80a96d..2583dea5f0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -395,7 +395,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeFreeze(JNIEnv* try { Obj* obj = reinterpret_cast(j_native_row_ptr); auto frozen_realm = *(reinterpret_cast(j_frozen_realm_native_ptr)); - auto frozen_obj = new Obj(frozen_realm->transaction().import_copy_of(*obj)); + auto frozen_obj = new Obj(frozen_realm->import_copy_of(*obj)); return reinterpret_cast(frozen_obj); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 79bffff3a4..24b903d140 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 79bffff3a433bec1f382422e5cb0f6c878c46835 +Subproject commit 24b903d140e716663d2922853999de1ac3bf1ed3 diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 2274f1c982..7b4c897f1e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -370,17 +370,19 @@ public OsSharedRealm.VersionID getVersionID() { @ObjectServer public int getPrivileges() { - return nativeGetRealmPrivileges(nativePtr); + // FIXME: Remove + return 0; } @ObjectServer public int getClassPrivileges(String className) { - return nativeGetClassPrivileges(nativePtr, className); + // FIXME: Remove + return 0; } @ObjectServer public int getObjectPrivileges(UncheckedRow row) { - return nativeGetObjectPrivileges(nativePtr, ((UncheckedRow) row).getNativePtr()); + return 0; } public boolean isClosed() { @@ -625,12 +627,6 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare private static native void nativeRegisterSchemaChangedCallback(long nativePtr, SchemaChangedCallback callback); - private static native int nativeGetRealmPrivileges(long nativePtr); - - private static native int nativeGetClassPrivileges(long nativePtr, String className); - - private static native int nativeGetObjectPrivileges(long nativePtr, long rowNativePtr); - private static native boolean nativeIsFrozen(long nativePtr); private static native long nativeFreeze(long nativePtr); From 6b7b85cf4d2c8d88afdb9f7bc85e63e05b2f55d1 Mon Sep 17 00:00:00 2001 From: Junxian Date: Mon, 16 Mar 2020 12:50:59 -0700 Subject: [PATCH 1478/2110] Add support for RealmProxy#toString() to print length information for binary field (#6767) --- .../main/java/io/realm/processor/RealmProxyClassGenerator.kt | 3 +++ .../test/resources/io/realm/some_test_AllTypesRealmProxy.java | 2 +- .../test/resources/io/realm/some_test_NullTypesRealmProxy.java | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index d19f6dcf60..f82eeb61e9 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -1632,6 +1632,9 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi Utils.isMutableRealmInteger(field) -> { emitStatement("stringBuilder.append(%s().get())", metadata.getInternalGetter(fieldName)) } + field is ByteArray -> { + emitStatement("stringBuilder.append(\"binary(\" + field.length + \")\")") + } else -> { if (metadata.isNullable(field)) { emitStatement("stringBuilder.append(%s() != null ? %s() : \"null\")", metadata.getInternalGetter(fieldName), metadata.getInternalGetter(fieldName)) diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index a9899a51d0..823590397b 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -2270,7 +2270,7 @@ public String toString() { stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{columnBinary:"); - stringBuilder.append(realmGet$columnBinary()); + stringBuilder.append("binary(" + realmGet$columnBinary().length + ")"); stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{columnMutableRealmInteger:"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index 5d37725d00..f91a7af319 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -3959,7 +3959,7 @@ public String toString() { stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{fieldBytesNotNull:"); - stringBuilder.append(realmGet$fieldBytesNotNull()); + stringBuilder.append("binary(" + realmGet$fieldBytesNotNull().length + ")"); stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{fieldBytesNull:"); From fb352e7025700f001e77cd2fcfd61b4accfbe898 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 16 Mar 2020 20:55:59 +0100 Subject: [PATCH 1479/2110] Add credits to changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6265a3edd..dfabb59c1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## 6.2.0(YYYY-MM-DD) + +### Enhancements +* The default `toString()` for proxy objects now print the length of binary fields. (Issue [#6767](https://github.com/realm/realm-java/pull/6767)) + +### Fixed +* None. + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. + +### Internal +* None. + +### Credits +* Thanks to @joxon for better support for binary fields in proxy objects. + + ## 6.1.0(2020-01-17) ### Enhancements From b6d8033d542392bd9ea74353316a183417f0579c Mon Sep 17 00:00:00 2001 From: Junxian Date: Tue, 17 Mar 2020 02:04:42 -0700 Subject: [PATCH 1480/2110] Add new test cases (#6777) --- .../assets/ios/0.98.0-alltypes-mix.realm | Bin 0 -> 4096 bytes .../java/io/realm/IOSRealmTests.java | 20 ++++++++++ .../java/io/realm/RealmInMemoryTest.java | 35 +++++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-mix.realm diff --git a/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-mix.realm b/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-mix.realm new file mode 100644 index 0000000000000000000000000000000000000000..0bb0cb607aad6bbad4ca2aee153ccaeb233f5839 GIT binary patch literal 4096 zcmeHHJ#5oJ6n?%tH*O&%Obr842Zk){83Qenk&uW)hit`d42kN-Q4%PfIEkfW$BZ4r z)-fC1=*XC{VyeVIB$jUBdv~^zAS$urr|9m!_jm8U_ne7KM(WD$JCC=YN{O>Wv`(bD z2UdT}`tUFu1kGR&++yF@kAi-G_xa0LPoik&xEuDD9oIAaR;wK~myJO8i8Ki!v6h)M z6@j}4yMtga3KMk!xZ8`nVQ+BED!fMXj!;d{h~yca(}Xp^hf=tvWTQZXz_{k|dU0OkGZE!Or;`&WFTD z6WUNL5q*C2l`96o5A`idaW9UetvE_`UcVLh26IKB-C00Ht5Mu}J2#WZ_{ChO-b8V* z5K(Bx@4#{vv9fnOm?I*uKj^g=GOl12mhAh>ZCx|C7cx;S1?B;~6jNeKCA`>(rY-Ps zl%Rw!HFQRf7DLn5)Ndv655dv}EUErqck~@%`e=3ofo&x^X_}p*k}S(j{yt#$jHBii z<(nNxO*O2%qo3y)lfJB*EQy~OiAIKTkl*p~S+Vog2@df* z9tp#jOj%;Qjd^!B+y`l0xL2LZGkGpAjDK2Z%_Ll^PhQDuIZgZ!C%(y>tPajoaZVg9 zJ;nF9cxMYID;5tec~EIq8y*l>C{tNEn-x`6zCs+t=LL43(66-JP-zNrZ&#(NxgXzW z({gAQry2bXW0=w;n(h2O&t@NI#vPcy)0vT<(9Q3`pA5w3;4~iC_WMSW>zN1mw7O*X zd3eRAAHTjV1!Z{+&oJ3JXeq5u)liMpSY4`#x>jYk;#S?d+pzRlKg_}FMQr##ahd-* z^DoDutft1p;tBL6^XKRjMql1DZ{elE^M^hRU1xLTj@?Ta_2!Z@kTZ}okTZ}okTZ}o KkTdX~Gw>TrA-qcf literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index e7ab4d0c69..bf006da8fb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -222,4 +222,24 @@ private byte[] getIOSKey() { } return keyData; } + + @Test + public void iOSDataTypesMixValues() throws IOException { + for (String iosVersion : IOS_VERSIONS) { + configFactory.copyRealmFromAssets(context, + "ios/" + iosVersion + "-alltypes-mix.realm", REALM_NAME); + realm = Realm.getDefaultInstance(); + + IOSAllTypes obj = realm.where(IOSAllTypes.class).findFirst(); + assertEquals(null, obj.getByteCol()); + assertEquals(null, obj.getStringCol()); + assertFalse(obj.isBoolCol()); + assertEquals(11125, obj.getShortCol()); + assertEquals(15350, obj.getIntCol()); + assertEquals(773863123, obj.getLongCol()); + assertEquals((float) 0.8914557, obj.getFloatCol(), 0F); + assertEquals(0.8702290174167451, obj.getDoubleCol(), 0D); + assertEquals(Long.MIN_VALUE, obj.getDateCol().getTime()); + } + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index 9f7f26123d..6435ada015 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -182,6 +182,33 @@ public void writeCopyTo() { } } + // Tests writeCopyTo result when called in a transaction. + @Test + public void writeCopyToInTransaction() { + String fileName = IDENTIFIER + ".realm"; + RealmConfiguration conf = configFactory.createConfigurationBuilder() + .name(fileName) + .build(); + + Realm.deleteRealm(conf); + + testRealm.beginTransaction(); + Dog dog = testRealm.createObject(Dog.class); + dog.setName("DinoDog"); + + // Write copy to destination file in transaction. + // Check if the new data would be written into the file. + testRealm.writeCopyTo(new File(configFactory.getRoot(), fileName)); + Realm onDiskRealm = Realm.getInstance(conf); + assertEquals(1, onDiskRealm.where(Dog.class).count()); + + testRealm.commitTransaction(); + + assertEquals(1, testRealm.where(Dog.class).count()); + onDiskRealm.close(); + } + + // Test below scenario: // 1. Creates a in-memory Realm instance in the main thread. // 2. Creates a in-memory Realm with same name in another thread. @@ -233,7 +260,9 @@ public void run() { // Waits until the worker thread started. workerCommittedLatch.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS); - if (threadError[0] != null) { throw threadError[0]; } + if (threadError[0] != null) { + throw threadError[0]; + } // Refreshes will be ran in the next loop, manually refreshes it here. testRealm.waitForChange(); @@ -254,7 +283,9 @@ public void run() { // Waits until the worker thread finished. workerClosedLatch.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS); - if (threadError[0] != null) { throw threadError[0]; } + if (threadError[0] != null) { + throw threadError[0]; + } // Since all previous Realm instances has been closed before, below will create a fresh new in-mem-realm instance. testRealm = Realm.getInstance(inMemConf); From 15424125c8c7ce0c092b7c87ee49c9d50ece2723 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 19 Mar 2020 14:25:08 +0100 Subject: [PATCH 1481/2110] Fix ProgressListeners for Core 6 (#6776) --- CHANGELOG.md | 7 ++++--- Dockerfile | 4 ++-- dependencies.list | 6 +++--- .../androidTest/java/io/realm/RxJavaTests.java | 15 +++++++++++++-- .../java/io/realm/rx/RealmObservableFactory.java | 5 ++--- .../ObjectLevelPermissionIntegrationTests.java | 2 ++ .../realm/objectserver/ProgressListenerTests.java | 1 - .../io/realm/objectserver/utils/HttpUtils.java | 4 ++-- tools/sync_test_server/ros/tsconfig.json | 1 + 9 files changed, 29 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38ad95ce45..e0d09628bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ ## 7.0.0(YYYY-MM-DD) -Based on v6.0.2. - NOTE: This version bumps the Realm file format to version 10. It is not possible to downgrade version 9 or earlier. Files created with older versions of Realm will be automatically upgraded. ### Breaking Changes @@ -22,7 +20,7 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) * Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. -* `RealmResults.asJSON()` is no longer `@Beta` +* `RealmResults.asJSON()` is no longer `@Beta`. ### Compatibility * Realm Object Server: 3.23.1 or later. @@ -35,6 +33,9 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * The NDK has been upgraded from r10e to r21. * The compiler used for C++ code has changed from GCC to Clang. * OpenSSL used by Realms encryption layer has been upgraded from 1.0.2k to 1.1.1b. +* Updated to Object Store commit: 66199adbfffbe153e696309a53d4ec03e32c44e3. +* Updated to Realm Sync 5.0.3. +* Updated to Realm Core 6.0.4. ## 6.1.0(2020-01-17) diff --git a/Dockerfile b/Dockerfile index e41a62a059..fe1790d66a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,9 +59,9 @@ RUN yes | sdkmanager --licenses # Please keep all sections in descending order! RUN yes | sdkmanager \ 'platform-tools' \ - 'build-tools;28.0.3' \ + 'build-tools;29.0.2' \ 'extras;android;m2repository' \ - 'platforms;android-27' \ + 'platforms;android-29' \ 'cmake;3.6.4111459' # Install the NDK diff --git a/dependencies.list b/dependencies.list index 39286cc1ff..f80a113157 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,11 +1,11 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.0 -REALM_SYNC_SHA256=93825d20e47627eae314d0793380908a65d622c3cdddbc0ca30fce3bb39d21cd +REALM_SYNC_VERSION=5.0.3 +REALM_SYNC_SHA256=bccf1fb89e32950a78dca4e93074f6479e0d016320897ab8bd1135d8568a18d3 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.23.1 +REALM_OBJECT_SERVER_VERSION=3.28.2 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.3.2 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java index d77a9feb0d..2cb808ddb2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java @@ -247,7 +247,16 @@ public void findFirstAsync_emittedOnSubscribe() { final AllTypes asyncObj = realm.where(AllTypes.class).findFirstAsync(); subscription = asyncObj.asFlowable().subscribe(rxObject -> { assertTrue(rxObject.isFrozen()); - assertEquals(42, rxObject.getColumnLong()); + // Because the subscription is run asynchronously. There is a chance + // the query resolved before the subscription triggers. + // This means it is not deterministic what state is first emitted here. + // It can either be a fully loaded object or one that is still loading. + if (rxObject.isLoaded()) { + assertTrue(rxObject.isValid()); + assertEquals(42, rxObject.getColumnLong()); + } else { + assertFalse(rxObject.isValid()); + } disposeSuccessfulTest(realm); }); } @@ -261,8 +270,10 @@ public void findFirstAsync_emittedOnUpdate() { subscription = realm.where(AllTypes.class).findFirstAsync().asFlowable().subscribe(rxObject -> { assertTrue(rxObject.isFrozen()); + if (!rxObject.isLoaded()) return; + if (rxObject.getColumnLong() == 1) { - realm.executeTransaction(r -> realm.where(AllTypes.class).findFirst().setColumnLong(42)); + realm.executeTransactionAsync(r -> r.where(AllTypes.class).findFirst().setColumnLong(42)); } else if (rxObject.getColumnLong() == 42) { disposeSuccessfulTest(realm); } diff --git a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java index d856351336..8d86f79f53 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java +++ b/realm/realm-library/src/main/java/io/realm/rx/RealmObservableFactory.java @@ -555,7 +555,7 @@ public Flowable from(final Realm realm, final E object @Override public void subscribe(final FlowableEmitter emitter) { // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. - if (!RealmObject.isValid(object)) return; + if (realm.isClosed()) return; // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. @@ -585,7 +585,6 @@ public void run() { // Emit current value immediately emitter.onNext(returnFrozenObjects ? RealmObject.freeze(object) : object); - } }, BACK_PRESSURE_STRATEGY).subscribeOn(scheduler).unsubscribeOn(scheduler); } @@ -646,7 +645,7 @@ public Flowable from(DynamicRealm realm, final DynamicRealmO @Override public void subscribe(final FlowableEmitter emitter) { // If the Realm has been closed, just create an empty Observable because we assume it is going to be disposed shortly. - if (!RealmObject.isValid(object)) return; + if (realm.isClosed()) return; // Gets instance to make sure that the Realm is open for as long as the // Observable is subscribed to it. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java index 4ba09b1c28..79f982db56 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ObjectLevelPermissionIntegrationTests.java @@ -17,6 +17,7 @@ import android.support.test.runner.AndroidJUnit4; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -53,6 +54,7 @@ * It is currently not possible to manually create a world readable Realm as * {@link io.realm.PermissionManager} is unstable on CI. */ +@Ignore("Runs locally, but fail on CI due to some network issues. These tests are going away shortly, so they are disabled instead of being fixed.") @RunWith(AndroidJUnit4.class) public class ObjectLevelPermissionIntegrationTests extends IsolatedIntegrationTests { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java index 82da398c07..c101119587 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java @@ -52,7 +52,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -@Ignore("FIXME: Most of these are currently broken. See https://jira.mongodb.org/browse/RSYNC-101") @RunWith(AndroidJUnit4.class) public class ProgressListenerTests extends StandardIntegrationTest { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java index e456364cf0..dcc18b8ddf 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/utils/HttpUtils.java @@ -36,8 +36,8 @@ public class HttpUtils { // "Realm could not be deleted errors". // FIXME re-adjust timeout after https://github.com/realm/realm-object-server-private/issues/697 is fixed private final static OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(40, TimeUnit.SECONDS) - .readTimeout(40, TimeUnit.SECONDS)// since ROS startup timeout is 30s + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS)// since ROS startup timeout is 30s .build(); // adb reverse tcp:8888 tcp:8888 diff --git a/tools/sync_test_server/ros/tsconfig.json b/tools/sync_test_server/ros/tsconfig.json index a5aca56049..87c9f42aeb 100644 --- a/tools/sync_test_server/ros/tsconfig.json +++ b/tools/sync_test_server/ros/tsconfig.json @@ -12,6 +12,7 @@ "declaration": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, + "skipLibCheck": true, "lib": [ "dom", "es6", From a0b6c25b2c56f7a3697d24878062c306610f0d4f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 19 Mar 2020 17:15:19 +0100 Subject: [PATCH 1482/2110] Add initial support for RealmApp and the Network Transport(#6757) * Initial support for ReamApp and associated classes. * SyncUser has been replaced by RealmUser. * SyncCredentials has been replaced by RealmCredentials. * Networking has been replaced by a Network Transport exposed by Object Store. --- CHANGELOG.md | 2 + Dockerfile | 1 + Jenkinsfile | 253 ++-- dependencies.list | 3 + examples/settings.gradle | 6 +- realm/realm-library/build.gradle | 62 +- .../io/realm/entities}/StringOnlyModule.java | 2 +- .../io/realm/AuthenticateRequestTests.java | 91 -- .../java/io/realm/SessionTests.java | 2 +- .../java/io/realm/SyncConfigurationTests.java | 17 +- .../java/io/realm/SyncManagerTests.java | 48 +- .../java/io/realm/SyncUserTests.java | 1038 ++++++++--------- .../java/io/realm/SyncedRealmTests.java | 8 +- .../kotlin/io/realm/AppUserTests.java | 517 ++++++++ .../kotlin/io/realm/RealmAppTests.kt | 41 + .../kotlin/io/realm/RealmCredentialsTests.kt | 139 +++ .../kotlin/io/realm/TestRealmApp.kt | 68 ++ .../transport/OkHttpNetworkTransportTests.kt | 146 +++ .../transport/OsJavaNetworkTransportTests.kt | 197 ++++ .../realm-library/src/main/cpp/CMakeLists.txt | 12 +- .../src/main/cpp/io_realm_RealmApp.cpp | 100 ++ .../main/cpp/io_realm_RealmFileUserStore.cpp | 132 --- ..._internal_objectstore_OsAppCredentials.cpp | 93 ++ ..._realm_internal_objectstore_OsSyncUser.cpp | 183 +++ .../src/main/cpp/java_network_transport.hpp | 128 ++ .../src/main/cpp/jni_util/java_method.cpp | 7 + .../src/main/cpp/jni_util/java_method.hpp | 4 + .../objectServer/java/io/realm/ErrorCode.java | 102 +- .../java/io/realm/ObjectServer.java | 5 +- .../objectServer/java/io/realm/RealmApp.java | 411 +++++++ .../java/io/realm/RealmAppConfiguration.java | 256 ++++ .../java/io/realm/RealmCredentials.java | 257 ++++ .../java/io/realm/RealmFileUserStore.java | 115 -- .../java/io/realm/RealmFunctions.java | 19 + .../java/io/realm/RealmPushNotifications.java | 19 + .../objectServer/java/io/realm/RealmUser.java | 177 +++ .../java/io/realm/RealmUserIdentity.java | 81 ++ .../java/io/realm/SyncManager.java | 84 +- .../java/io/realm/SyncSession.java | 349 +++--- .../objectServer/java/io/realm/SyncUser.java | 651 +++++------ .../java/io/realm/SyncUserInfo.java | 8 +- .../objectServer/java/io/realm/UserStore.java | 89 -- .../internal/network/AuthServerResponse.java | 80 -- .../internal/network/AuthenticateRequest.java | 108 -- .../network/AuthenticateResponse.java | 168 --- .../network/ChangePasswordRequest.java | 70 -- .../network/ChangePasswordResponse.java | 71 -- .../network/ExponentialBackoffTask.java | 118 -- .../realm/internal/network/LogoutRequest.java | 52 - .../internal/network/LogoutResponse.java | 94 -- .../network/LookupUserIdResponse.java | 154 --- .../network/OkHttpNetworkTransport.java | 132 +++ .../network/OkHttpRealmObjectServer.java | 350 ------ .../internal/network/RealmObjectServer.java | 117 -- .../network/UpdateAccountRequest.java | 80 -- .../network/UpdateAccountResponse.java | 60 - .../objectstore/OsAppCredentials.java | 95 ++ .../objectstore/OsJavaNetworkTransport.java | 147 +++ .../internal/objectstore/OsSyncUser.java | 119 ++ .../internal/objectstore/package-info.java | 18 + .../realm/mongodb/RealmMongoDBCollection.java | 19 + .../realm/mongodb/RealmMongoDBDatabase.java | 19 + .../io/realm/mongodb/RealmMongoDBService.java | 19 + .../java/io/realm/SyncTestUtils.java | 55 +- .../realm/objectserver/utils/Constants.java | 1 + .../realm/objectserver/utils/UserFactory.java | 63 +- .../io/realm/rule/RunWithRemoteService.java | 16 +- tools/sync_test_server/Dockerfile | 31 +- .../app_config/auth_providers/anon-user.json | 6 + .../app_config/auth_providers/api-key.json | 6 + .../auth_providers/custom-function.json | 9 + .../auth_providers/local-userpass.json | 12 + .../app_config/functions/authFunc/config.json | 6 + .../app_config/functions/authFunc/source.js | 17 + .../functions/resetFunc/config.json | 6 + .../app_config/functions/resetFunc/source.js | 47 + .../services/integration_tests/config.json | 10 + tools/sync_test_server/app_config/stitch.json | 14 + .../integration-test-command-server.js | 213 ---- .../mongodb-realm-command-server.js | 75 ++ tools/sync_test_server/ros/package.json | 17 - tools/sync_test_server/ros/src/index.ts | 101 -- tools/sync_test_server/ros/tsconfig.json | 32 - tools/sync_test_server/setup_mongodb_realm.sh | 80 ++ tools/sync_test_server/start_server.sh | 31 +- tools/sync_test_server/stop_server.sh | 6 +- 86 files changed, 5106 insertions(+), 3761 deletions(-) rename realm/realm-library/src/{androidTestObjectServer/java/io/realm/objectserver/utils => androidTest/java/io/realm/entities}/StringOnlyModule.java (81%) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppUserTests.java create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt create mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp delete mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp create mode 100644 realm/realm-library/src/main/cpp/java_network_transport.hpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmApp.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmPushNotifications.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmUser.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmUserIdentity.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/UserStore.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountRequest.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountResponse.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/package-info.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBCollection.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBDatabase.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBService.java create mode 100755 tools/sync_test_server/app_config/auth_providers/anon-user.json create mode 100755 tools/sync_test_server/app_config/auth_providers/api-key.json create mode 100755 tools/sync_test_server/app_config/auth_providers/custom-function.json create mode 100755 tools/sync_test_server/app_config/auth_providers/local-userpass.json create mode 100755 tools/sync_test_server/app_config/functions/authFunc/config.json create mode 100755 tools/sync_test_server/app_config/functions/authFunc/source.js create mode 100755 tools/sync_test_server/app_config/functions/resetFunc/config.json create mode 100755 tools/sync_test_server/app_config/functions/resetFunc/source.js create mode 100755 tools/sync_test_server/app_config/services/integration_tests/config.json create mode 100755 tools/sync_test_server/app_config/stitch.json delete mode 100755 tools/sync_test_server/integration-test-command-server.js create mode 100755 tools/sync_test_server/mongodb-realm-command-server.js delete mode 100644 tools/sync_test_server/ros/package.json delete mode 100644 tools/sync_test_server/ros/src/index.ts delete mode 100644 tools/sync_test_server/ros/tsconfig.json create mode 100755 tools/sync_test_server/setup_mongodb_realm.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 317469169d..a043991519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,12 @@ * TODO. ### Internal +* OKHttp was upgraded to 3.12.0 from 3.10.0. * Updated Android Gradle Plugin to 3.6.1. * Updated Gradle to 5.6.4 * Updated Dokka to 0.10.1 * Updated Android Build Tools to 29.0.2. +* Updated compileSdkVersion to 29. ## 7.0.0(YYYY-MM-DD) diff --git a/Dockerfile b/Dockerfile index 6738d5b10f..4d7888da18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,7 @@ RUN DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 \ curl \ file \ git \ + jq \ libc6:i386 \ libgcc1:i386 \ libncurses5:i386 \ diff --git a/Jenkinsfile b/Jenkinsfile index d98ec696d8..fc71f32cb5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,7 +3,10 @@ import groovy.json.JsonOutput def buildSuccess = false -def rosContainer +def mongoDbRealmContainer = null +def mongoDbRealmCLIContainer = null +def mongoDbRealmCommandServerContainer = null +def dockerNetworkId = UUID.randomUUID().toString() try { node('android') { timeout(time: 90, unit: 'MINUTES') { @@ -11,15 +14,15 @@ try { ws('/tmp/realm-java') { stage('SCM') { checkout([ - $class: 'GitSCM', - branches: scm.branches, - gitTool: 'native git', - extensions: scm.extensions + [ - [$class: 'CleanCheckout'], - [$class: 'SubmoduleOption', recursiveSubmodules: true] - ], - userRemoteConfigs: scm.userRemoteConfigs - ]) + $class : 'GitSCM', + branches : scm.branches, + gitTool : 'native git', + extensions : scm.extensions + [ + [$class: 'CleanCheckout'], + [$class: 'SubmoduleOption', recursiveSubmodules: true] + ], + userRemoteConfigs: scm.userRemoteConfigs + ]) } // Toggles for PR vs. Master builds. @@ -30,27 +33,37 @@ try { def abiFilter = "" def instrumentationTestTarget = "connectedAndroidTest" if (!['master', 'next-major'].contains(env.BRANCH_NAME)) { - abiFilter = "-PbuildTargetABIs=armeabi-v7a" - instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting + abiFilter = "-PbuildTargetABIs=armeabi-v7a" + instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" + // Run in debug more for better error reporting } - def buildEnv - def rosEnv - stage('Docker build') { - // Docker image for build - buildEnv = docker.build 'realm-java:snapshot' - // Docker image for testing Realm Object Server - def dependProperties = readProperties file: 'dependencies.list' - def rosVersion = dependProperties["REALM_OBJECT_SERVER_VERSION"] - withCredentials([string(credentialsId: 'realm-sync-feature-token-enterprise', variable: 'realmFeatureToken')]) { - rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_VERSION=${rosVersion} --build-arg REALM_FEATURE_TOKEN=${realmFeatureToken} tools/sync_test_server" - } + // Prepare Docker images + // FIXME: Had issues moving these into a seperate Stage step. Is this needed? + buildEnv = docker.build 'realm-java:snapshot' + // `aws ecr describe-images --repository-name ci/mongodb-realm-images --query 'sort_by(imageDetails,& imagePushedAt)[-1].imageTags[0]'` + def version = "test_server-26e6463b98d8e3f0f4522a70e37f105d34b688a9-race" + def mdbRealmImage = docker.image("${env.DOCKER_REGISTRY}/ci/mongodb-realm-images:${version}") + def stitchCliImage = docker.image("${env.DOCKER_REGISTRY}/ci/stitch-cli:190") + docker.withRegistry("https://${env.DOCKER_REGISTRY}", "ecr:eu-west-1:aws-ci-user") { + mdbRealmImage.pull() + stitchCliImage.pull() } - - rosContainer = rosEnv.run() + def commandServerEnv = docker.build 'mongodb-realm-command-server', "tools/sync_test_server" try { - buildEnv.inside("-e HOME=/tmp " + + // Prepare Docker containers used by Instrumentation tests + // TODO: How much of this logic can be moved to start_server.sh for shared logic with local testing. + + sh "docker network create ${dockerNetworkId}" + mongoDbRealmContainer = mdbRealmImage.run("--network ${dockerNetworkId}") + mongoDbRealmCLIContainer = stitchCliImage.run("-t --network container:${mongoDbRealmContainer.id}") + mongoDbRealmCommandServerContainer = commandServerEnv.run("--network container:${mongoDbRealmContainer.id}") + sh "docker cp tools/sync_test_server/app_config ${mongoDbRealmCLIContainer.id}:/tmp/app_config" + sh "docker cp tools/sync_test_server/setup_mongodb_realm.sh ${mongoDbRealmCLIContainer.id}:/tmp/" + sh "docker exec -i ${mongoDbRealmCLIContainer.id} sh /tmp/setup_mongodb_realm.sh" + + buildEnv.inside("-e HOME=/tmp " + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + "--privileged " + "-v /dev/bus/usb:/dev/bus/usb " + @@ -58,92 +71,98 @@ try { "-v ${env.HOME}/.android:/tmp/.android " + "-v ${env.HOME}/ccache:/tmp/.ccache " + "-e REALM_CORE_DOWNLOAD_DIR=/tmp/.gradle " + - "--network container:${rosContainer.id}") { + "--network container:${mongoDbRealmContainer.id} ") { - // Lock required around all usages of Gradle as it isn't - // able to share its cache between builds. - lock("${env.NODE_NAME}-android") { + // Lock required around all usages of Gradle as it isn't + // able to share its cache between builds. + lock("${env.NODE_NAME}-android") { - stage('JVM tests') { - try { - withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" - } - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) - } + stage('JVM tests') { + try { + withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { + sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" } + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) + } + } - stage('Realm Transformer tests') { - try { - gradle('realm-transformer', 'check') - } finally { - storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' - } - } + stage('Realm Transformer tests') { + try { + gradle('realm-transformer', 'check') + } finally { + storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + } + } - stage('Static code analysis') { - try { - gradle('realm', "findbugs checkstyle ${abiFilter}") // FIXME Reenable pmd - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) - step([$class: 'CheckStylePublisher', - canComputeNew: false, - defaultEncoding: '', - healthy: '', - pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - unHealthy: '' - ]) - } - } - stage('Run instrumented tests') { - String backgroundPid - try { - backgroundPid = startLogCatCollector() - forwardAdbPorts() - gradle('realm', "${instrumentationTestTarget}") - } finally { - stopLogCatCollector(backgroundPid) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' - storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' - } - } + stage('Static code analysis') { + try { + gradle('realm', "findbugs ${abiFilter}") // FIXME Renable pmd and checkstyle + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) +// publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) +// step([$class: 'CheckStylePublisher', +// canComputeNew: false, +// defaultEncoding: '', +// healthy: '', +// pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', +// unHealthy: '' +// ]) + } + } - // Gradle plugin tests require that artifacts are available, so this - // step needs to be after the instrumentation tests - stage('Gradle plugin tests') { - try { - gradle('gradle-plugin', 'check --debug') - } finally { - storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' - } - } + stage('Run instrumented tests') { + String backgroundPid + try { + backgroundPid = startLogCatCollector() + forwardAdbPorts() + gradle('realm', "${instrumentationTestTarget}") + } finally { + stopLogCatCollector(backgroundPid) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } + } + + // Gradle plugin tests require that artifacts are available, so this + // step needs to be after the instrumentation tests + stage('Gradle plugin tests') { + try { + gradle('gradle-plugin', 'check --debug') + } finally { + storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + } + } - // TODO: add support for running monkey on the example apps + // TODO: add support for running monkey on the example apps - if (['master'].contains(env.BRANCH_NAME)) { - stage('Collect metrics') { - collectAarMetrics() - } - } + if (['master'].contains(env.BRANCH_NAME)) { + stage('Collect metrics') { + collectAarMetrics() + } + } - if (['master', 'next-major'].contains(env.BRANCH_NAME)) { - stage('Publish to OJO') { - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" - } - } + if (['master', 'next-major'].contains(env.BRANCH_NAME)) { + stage('Publish to OJO') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" } } } + } + } } finally { - archiveRosLog(rosContainer.id) - sh "docker logs ${rosContainer.id}" - rosContainer.stop() + // FIXME: Figure out which logs we need to safe, if any? + // archiveRosLog(rosContainer.id) + // sh "docker logs ${rosContainer.id}" + // rosContainer.stop() + mongoDbRealmContainer.stop() + mongoDbRealmCLIContainer.stop() + mongoDbRealmCommandServerContainer.stop() + sh "docker network rm ${dockerNetworkId}" } } } @@ -159,14 +178,14 @@ try { node { withCredentials([[$class: 'StringBinding', credentialsId: 'slack-java-url', variable: 'SLACK_URL']]) { def payload = JsonOutput.toJson([ - username: 'Mr. Jenkins', - icon_emoji: ':jenkins:', - attachments: [[ - 'title': "The ${env.BRANCH_NAME} branch is broken!", - 'text': "<${env.BUILD_URL}|Click here> to check the build.", - 'color': "danger" - ]] - ]) + username: 'Mr. Jenkins', + icon_emoji: ':jenkins:', + attachments: [[ + 'title': "The ${env.BRANCH_NAME} branch is broken!", + 'text': "<${env.BUILD_URL}|Click here> to check the build.", + 'color': "danger" + ]] + ]) sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" } } @@ -175,7 +194,7 @@ try { def forwardAdbPorts() { sh ''' adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && - adb reverse tcp:8888 tcp:8888 + adb reverse tcp:8888 tcp:8888 && adb reverse tcp:9090 tcp:9090 ''' } @@ -190,9 +209,9 @@ def String startLogCatCollector() { def stopLogCatCollector(String backgroundPid) { sh "kill ${backgroundPid}" zip([ - 'zipFile': 'logcat.zip', - 'archive': true, - 'glob' : 'logcat.txt' + 'zipFile': 'logcat.zip', + 'archive': true, + 'glob' : 'logcat.txt' ]) sh 'rm logcat.txt' } @@ -200,9 +219,9 @@ def stopLogCatCollector(String backgroundPid) { def archiveRosLog(String id) { sh "docker cp ${id}:/tmp/integration-test-command-server.log ./ros.log" zip([ - 'zipFile': 'roslog.zip', - 'archive': true, - 'glob' : 'ros.log' + 'zipFile': 'roslog.zip', + 'archive': true, + 'glob' : 'ros.log' ]) sh 'rm ros.log' } @@ -221,10 +240,10 @@ def getTagsString(Map tags) { def storeJunitResults(String path) { step([ - $class: 'JUnitResultArchiver', - allowEmptyResults: true, - testResults: path - ]) + $class: 'JUnitResultArchiver', + allowEmptyResults: true, + testResults: path + ]) } def collectAarMetrics() { diff --git a/dependencies.list b/dependencies.list index 96d322ff91..0452506e20 100644 --- a/dependencies.list +++ b/dependencies.list @@ -7,6 +7,9 @@ REALM_SYNC_SHA256=7048eff89f00554aa4014239a25d419fc98d0b22074ff2deadd3e9717a5c4d # Use `npm view realm-object-server versions` to get a list of available versions. REALM_OBJECT_SERVER_VERSION=3.28.2 +# Version of MongoDB Realm used by integration tests +MONGODB_REALM_SERVER_VERSION=test_server-0ed2349a36352666402d0fb2e8763ac67731768c-race + # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 ANDROID_BUILD_TOOLS=29.0.2 diff --git a/examples/settings.gradle b/examples/settings.gradle index ee1023049c..2fa201b4d5 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -10,8 +10,8 @@ include 'moduleExample:app' include 'moduleExample:library' include 'newsreaderExample' include 'rxJavaExample' -include 'secureTokenAndroidKeyStore' +// FIXME include 'secureTokenAndroidKeyStore' include 'threadExample' -// include 'unitTestExample' Disable project because fixing it requires AndroidX -include 'objectServerExample' +// FIXME include 'unitTestExample' Disable project because fixing it requires AndroidX +// FIXME include 'objectServerExample' include 'multiprocessExample' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 77c02f5497..2d363aa38b 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -100,7 +100,7 @@ android { java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java', 'src/testUtils/kotlin'] } androidTestObjectServer { - java.srcDirs += [/* FIXME 'src/syncIntegrationTest/java', */'src/syncTestUtils/java'] + java.srcDirs += [/* FIXME 'src/syncIntegrationTest/java', */ 'src/androidTestObjectServer/kotlin', 'src/syncTestUtils/java'] assets.srcDirs += ['src/syncIntegrationTest/assets/'] } } @@ -211,7 +211,7 @@ dependencies { } kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 - objectServerImplementation 'com.squareup.okhttp3:okhttp:3.10.0' + objectServerImplementation 'com.squareup.okhttp3:okhttp:3.12.0' // Going above this requires minSDK 21 kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' @@ -241,34 +241,35 @@ def betaTag = 'Beta:a:

            ' task javadoc(type: Javadoc) { - source android.sourceSets.objectServer.java.srcDirs - source android.sourceSets.main.java.srcDirs - source "../../realm-annotations/src/main/java" - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - options { - title = "Realm ${project.version}" - memberLevel = JavadocMemberLevel.PUBLIC - docEncoding = 'UTF-8' - encoding = 'UTF-8' - charSet = 'UTF-8' - locale = 'en_US' - overview = 'src/overview.html' - - links "https://docs.oracle.com/javase/7/docs/api/" - links "http://reactivex.io/RxJava/javadoc/" - linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" - - tags = [betaTag] - } - exclude '**/internal/**' - exclude '**/BuildConfig.java' - exclude '**/R.java' - doLast { - copy { - from "src/realm-java-overview.png" - into "$buildDir/docs/javadoc" - } - } +// FIXME: Disable JavaDoc until API Stabilizes a bit more +// source android.sourceSets.objectServer.java.srcDirs +// source android.sourceSets.main.java.srcDirs +// source "../../realm-annotations/src/main/java" +// classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +// options { +// title = "Realm ${project.version}" +// memberLevel = JavadocMemberLevel.PUBLIC +// docEncoding = 'UTF-8' +// encoding = 'UTF-8' +// charSet = 'UTF-8' +// locale = 'en_US' +// overview = 'src/overview.html' +// +// links "https://docs.oracle.com/javase/7/docs/api/" +// links "http://reactivex.io/RxJava/javadoc/" +// linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" +// +// tags = [betaTag] +// } +// exclude '**/internal/**' +// exclude '**/BuildConfig.java' +// exclude '**/R.java' +// doLast { +// copy { +// from "src/realm-java-overview.png" +// into "$buildDir/docs/javadoc" +// } +// } } task javadocJar(type: Jar, dependsOn: javadoc) { @@ -575,6 +576,7 @@ if (project.hasProperty('dontCleanJniFiles')) { } else { task cleanExternalBuildFiles(type: Delete) { delete project.file('.externalNativeBuild') + delete project.file('.cxx') // Clean .so files that were created by old build script (realm/realm-jni/build.gradle). delete project.file('src/main/jniLibs') } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/objectserver/utils/StringOnlyModule.java b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnlyModule.java similarity index 81% rename from realm/realm-library/src/androidTestObjectServer/java/io/realm/objectserver/utils/StringOnlyModule.java rename to realm/realm-library/src/androidTest/java/io/realm/entities/StringOnlyModule.java index e935a0b1b9..72c0703851 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/objectserver/utils/StringOnlyModule.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnlyModule.java @@ -1,4 +1,4 @@ -package io.realm.objectserver.utils; +package io.realm.entities; import io.realm.annotations.RealmModule; import io.realm.entities.StringOnly; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java deleted file mode 100644 index 66a3c7b9ce..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/AuthenticateRequestTests.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.realm; - - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; - -import io.realm.internal.network.AuthenticateRequest; -import io.realm.internal.network.RealmObjectServer; -import io.realm.internal.objectserver.Token; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.when; - -@Ignore("FIXME: RealmApp refactor") -@RunWith(AndroidJUnit4.class) -public class AuthenticateRequestTests { - - @Before - public void setUp() { - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - } - - // Tests based on the schemas described here: https://github.com/realm/realm-sync-services/blob/master/doc/index.apib - - @Test - public void realmLogin() throws URISyntaxException, JSONException { - Token t = SyncTestUtils.createTestUser().getRefreshToken(); - AuthenticateRequest request = AuthenticateRequest.realmLogin(t, new URI("realm://objectserver/" + t.identity() + "/default").getPath()); - - JSONObject obj = new JSONObject(request.toJson()); - assertEquals("/" + t.identity() + "/default", obj.get("path")); - assertEquals(t.value(), obj.get("data")); - assertEquals("realm", obj.get("provider")); - } - - @Test - public void userLogin() throws URISyntaxException, JSONException { - AuthenticateRequest request = AuthenticateRequest.userLogin(SyncCredentials.facebook("foo")); - - JSONObject obj = new JSONObject(request.toJson()); - assertFalse(obj.has("path")); - assertEquals("foo", obj.get("data")); - assertEquals("facebook", obj.get("provider")); - } - - @Test - public void userRefresh() throws URISyntaxException, JSONException { - Token t = SyncTestUtils.createTestUser().getRefreshToken(); - AuthenticateRequest request = AuthenticateRequest.userRefresh(t, new URI("realm://objectserver/" + t.identity() + "/default").getPath()); - - JSONObject obj = new JSONObject(request.toJson()); - assertTrue(obj.has("path")); - assertEquals(t.value(), obj.get("data")); - assertEquals("realm", obj.get("provider")); - } - - - @Test - public void errorsNotWrapped() { - RealmObjectServer originalAuthServer = SyncManager.getAuthServer(); - RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); - when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createErrorResponse(ErrorCode.ACCESS_DENIED)); - SyncManager.setAuthServerImpl(authServer); - - try { - SyncUser.logIn(SyncCredentials.facebook("foo"), "http://foo.bar/auth"); - fail(); - } catch (ObjectServerError e) { - assertEquals(ErrorCode.ACCESS_DENIED, e.getErrorCode()); - } finally { - // Reset the auth server implementation for other tests. - SyncManager.setAuthServerImpl(originalAuthServer); - } - } -} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index 4fcbf6b56a..babba3ddd0 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -34,7 +34,7 @@ import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.log.RealmLog; -import io.realm.objectserver.utils.StringOnlyModule; +import io.realm.entities.StringOnlyModule; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java index cb487c1013..8ec9b2e4af 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java @@ -35,7 +35,7 @@ import java.util.Map; import io.realm.entities.StringOnly; -import io.realm.objectserver.utils.StringOnlyModule; +import io.realm.entities.StringOnlyModule; import io.realm.rule.RunInLooperThread; import static io.realm.SyncTestUtils.createNamedTestUser; @@ -69,10 +69,11 @@ public void setUp() { @After public void tearDown() { - UserStore userStore = SyncManager.getUserStore(); - for (SyncUser syncUser : userStore.allUsers()) { - userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); - } +// FIXME +// UserStore userStore = SyncManager.getUserStore(); +// for (SyncUser syncUser : userStore.allUsers()) { +// userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); +// } } @Test @@ -466,10 +467,11 @@ public void multipleUsersReferenceSameRealm() { assertNotEquals(config1.getPath(), config2.getPath()); } + @Ignore("FIXME") @Test public void getDefaultConfiguration_throwsIfNotLoggedIn() { SyncUser user = createTestUser(); - user.logOut(); +// user.logOut(); try { user.getDefaultConfiguration(); fail(); @@ -478,6 +480,7 @@ public void getDefaultConfiguration_throwsIfNotLoggedIn() { } } + @Ignore("FIXME") @Test public void automatic_convertsAuthUrl() { Object[][] input = { @@ -505,7 +508,7 @@ public void automatic_convertsAuthUrl() { SyncConfiguration config = user.getDefaultConfiguration(); URI url = config.getServerUrl(); assertEquals(realmUrl, url.toString()); - user.logOut(); +// user.logOut(); } } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java index 026baad335..98522879f8 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java @@ -30,13 +30,12 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; -import io.realm.objectserver.utils.StringOnlyModule; +import io.realm.entities.StringOnlyModule; import io.realm.objectserver.utils.UserFactory; import io.realm.rule.TestRealmConfigurationFactory; @@ -47,8 +46,6 @@ @RunWith(AndroidJUnit4.class) public class SyncManagerTests { - private UserStore userStore; - @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @@ -57,60 +54,17 @@ public class SyncManagerTests { @Before public void setUp() { - userStore = new UserStore() { - @Override - public void put(SyncUser user) {} - - @Override - public SyncUser getCurrent() { - return null; - } - - @Override - public SyncUser get(String identity, String authenticationUrl) { - return null; - } - - @Override - public void remove(String identity, String authenticationUrl) { - } - - @Override - public Collection allUsers() { - return Collections.emptySet(); - } - - @Override - public boolean isActive(String identity, String authenticationUrl) { - return true; - } - }; SyncManager.reset(); } @After public void tearDown() { UserFactory.logoutAllUsers(); - UserStore userStore = SyncManager.getUserStore(); - for (SyncUser syncUser : userStore.allUsers()) { - userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); - } SyncManager.reset(); BaseRealm.applicationContext = null; // Required for Realm.init() to work Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); } - @Test - public void set_userStore() { - SyncManager.setUserStore(userStore); - assertTrue(userStore.equals(SyncManager.getUserStore())); - } - - @Test(expected = IllegalArgumentException.class) - public void set_userStore_null() { - SyncManager.setUserStore(null); - } - @Test public void authListener() { SyncUser user = createTestUser(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java index 4c61cce570..d179db7bee 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java @@ -1,520 +1,518 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.rule.UiThreadTestRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.io.File; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Calendar; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import io.realm.entities.AllTypesModelModule; -import io.realm.entities.StringOnly; -import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.network.RealmObjectServer; -import io.realm.internal.objectserver.Token; -import io.realm.log.RealmLog; -import io.realm.objectserver.utils.StringOnlyModule; -import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.RunInLooperThread; -import io.realm.rule.RunTestInLooperThread; - -import static io.realm.SyncTestUtils.createTestAdminUser; -import static io.realm.SyncTestUtils.createTestUser; -import static junit.framework.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.when; - -@Ignore("FIXME: REalmApp refactor") -@RunWith(AndroidJUnit4.class) -public class SyncUserTests { - - private static final URL authUrl; - private static final Constructor SYNC_USER_CONSTRUCTOR; - static { - try { - authUrl = new URL("http://localhost/auth"); - SYNC_USER_CONSTRUCTOR = SyncUser.class.getDeclaredConstructor(Token.class, URL.class); - SYNC_USER_CONSTRUCTOR.setAccessible(true); - } catch (MalformedURLException e) { - throw new ExceptionInInitializerError(e); - } catch (NoSuchMethodException e) { - throw new ExceptionInInitializerError(e); - } - } - - @Rule - public final RunInLooperThread looperThread = new RunInLooperThread(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Rule - public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); - - @Before - public void setUp() { - BaseRealm.applicationContext = null; - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - UserStore userStore = SyncManager.getUserStore(); - for (SyncUser syncUser : userStore.allUsers()) { - userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); - } - } - - @After - public void after() { - if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { - UserFactory.logoutAllUsers(); - } else { - looperThread.runAfterTest(new Runnable() { - @Override - public void run() { - UserFactory.logoutAllUsers(); - } - }); - } - } - - private static SyncUser createFakeUser(String id) { - final Token token = new Token("token_value", id, "path_value", Long.MAX_VALUE, null); - try { - return SYNC_USER_CONSTRUCTOR.newInstance(token, authUrl); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - fail(e.getMessage()); - } - return null; - } - - @Test - public void equals_validUser() { - final SyncUser user1 = createFakeUser("id_value"); - final SyncUser user2 = createFakeUser("id_value"); - assertTrue(user1.equals(user2)); - } - - @Test - public void equals_loggedOutUser() { - final SyncUser user1 = createFakeUser("id_value"); - final SyncUser user2 = createFakeUser("id_value"); - user1.logOut(); - user2.logOut(); - assertTrue(user1.equals(user2)); - } - - @Test - public void hashCode_validUser() { - final SyncUser user = createFakeUser("id_value"); - assertNotEquals(0, user.hashCode()); - } - - @Test - public void hashCode_loggedOutUser() { - final SyncUser user = createFakeUser("id_value"); - user.logOut(); - assertNotEquals(0, user.hashCode()); - } - - @Test - public void toAndFromJson() { - SyncUser user1 = createTestUser(); - SyncUser user2 = SyncUser.fromJson(user1.toJson()); - assertEquals(user1, user2); - } - - // Tests that the UserStore does not return users that have expired - @Test - public void currentUser_returnsNullIfUserExpired() { - // Add an expired user to the user store - UserStore userStore = SyncManager.getUserStore(); - userStore.put(createTestUser(Long.MIN_VALUE)); - - // Invalid users should not be returned when asking the for the current user - assertNull(SyncUser.current()); - } - - @Test - public void currentUser_throwsIfMultipleUsersLoggedIn() { - RealmObjectServer originalAuthServer = SyncManager.getAuthServer(); - RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); - SyncManager.setAuthServerImpl(authServer); - - try { - // 1. Login two random users - when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenAnswer(new Answer() { - @Override - public AuthenticateResponse answer(InvocationOnMock invocationOnMock) throws Throwable { - return getNewRandomUser(); - } - }); - SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); - SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); - - // 2. Verify current() now throws - try { - SyncUser.current(); - fail(); - } catch (IllegalStateException ignore) { - } - } finally { - SyncManager.setAuthServerImpl(originalAuthServer); - } - - } - - private AuthenticateResponse getNewRandomUser() { - String identity = UUID.randomUUID().toString(); - String userTokenValue = UUID.randomUUID().toString(); - return SyncTestUtils.createLoginResponse(userTokenValue, identity, Long.MAX_VALUE, false); - } - - // Test that current user is cleared if it is logged out - @Test - public void currentUser_clearedOnLogout() { - // Add 1 valid user to the user store - SyncUser user = createTestUser(Long.MAX_VALUE); - UserStore userStore = SyncManager.getUserStore(); - userStore.put(user); - - SyncUser savedUser = SyncUser.current(); - assertEquals(user, savedUser); - assertNotNull(savedUser); - savedUser.logOut(); - assertNull(SyncUser.current()); - } - - // `all()` returns an empty list if no users are logged in - @Test - public void all_empty() { - Map users = SyncUser.all(); - assertTrue(users.isEmpty()); - } - - // `all()` returns only valid users. Invalid users are filtered. - @Test - public void all_validUsers() { - // Add 1 expired user and 1 valid user to the user store - UserStore userStore = SyncManager.getUserStore(); - userStore.put(createTestUser(Long.MIN_VALUE)); - userStore.put(createTestUser(Long.MAX_VALUE)); - - Map users = SyncUser.all(); - assertEquals(1, users.size()); - assertTrue(users.entrySet().iterator().next().getValue().isValid()); - } - - @Test - public void isAdmin() { - SyncUser user1 = createTestUser(); - assertFalse(user1.isAdmin()); - - SyncUser user2 = createTestAdminUser(); - assertTrue(user2.isAdmin()); - } - - @Test - public void isAdmin_allUsers() { - UserStore userStore = SyncManager.getUserStore(); - SyncUser user = createTestAdminUser(); - assertTrue(user.isAdmin()); - userStore.put(user); - - Map users = SyncUser.all(); - assertEquals(1, users.size()); - assertTrue(users.entrySet().iterator().next().getValue().isAdmin()); - } - - // Tests that the user store returns the last user to login - @Ignore("This test fails because of wrong JSON string.") - @Test - public void currentUser_returnsUserAfterLogin() { - RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); - when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(Long.MAX_VALUE)); - - SyncUser user = SyncUser.logIn(SyncCredentials.facebook("foo"), "http://bar.com/auth"); - assertEquals(user, SyncUser.current()); - } - - @Test - public void toString_returnDescription() { - SyncUser user = createTestUser("http://objectserver.realm.io/auth"); - String str = user.toString(); - assertTrue(str != null && !str.isEmpty()); - } - - // Test that a login with an access token logs the user in directly without touching the network - @Test - public void login_withAccessToken() { - RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); - when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenThrow(new AssertionError("Server contacted.")); - RealmObjectServer originalServer = SyncManager.getAuthServer(); - SyncManager.setAuthServerImpl(authServer); - try { - SyncCredentials credentials = SyncCredentials.accessToken("foo", "bar"); - SyncUser user = SyncUser.logIn(credentials, "http://ros.realm.io/auth"); - assertTrue(user.isValid()); - } finally { - SyncManager.setAuthServerImpl(originalServer); - } - } - - // Checks that `/auth` is correctly added to any URL without a path - @Test - public void login_appendAuthSegment() { - RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); - RealmObjectServer originalServer = SyncManager.getAuthServer(); - SyncManager.setAuthServerImpl(authServer); - String[][] urls = { - {"http://ros.realm.io", "http://ros.realm.io/auth"}, - {"http://ros.realm.io:8080", "http://ros.realm.io:8080/auth"}, - {"http://ros.realm.io/", "http://ros.realm.io/"}, - {"http://ros.realm.io/?foo=bar", "http://ros.realm.io/?foo=bar"}, - {"http://ros.realm.io/auth", "http://ros.realm.io/auth"}, - {"http://ros.realm.io/auth/", "http://ros.realm.io/auth/"}, - {"http://ros.realm.io/custom-path/", "http://ros.realm.io/custom-path/"} - }; - - try { - for (String[] url : urls) { - RealmLog.error(url[0]); - String input = url[0]; - String normalizedInput = url[1]; - SyncCredentials credentials = SyncCredentials.accessToken("token", UUID.randomUUID().toString()); - SyncUser user = SyncUser.logIn(credentials, input); - assertEquals(normalizedInput, user.getAuthenticationUrl().toString()); - user.logOut(); - } - } finally { - SyncManager.setAuthServerImpl(originalServer); - } - } - - @Test - public void changePassword_nullThrows() { - SyncUser user = createTestUser(); - - thrown.expect(IllegalArgumentException.class); - //noinspection ConstantConditions - user.changePassword(null); - } - - @Test - public void changePassword_admin_nullThrows() { - SyncUser user = createTestUser(); - - thrown.expect(IllegalArgumentException.class); - //noinspection ConstantConditions - user.changePassword(null, "new-password"); - } - - @Test - public void changePasswordAsync_nonLooperThreadThrows() { - SyncUser user = createTestUser(); - - thrown.expect(IllegalStateException.class); - user.changePasswordAsync("password", new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - fail(); - } - }); - } - - @Test - public void changePassword_admin_Async_nonLooperThreadThrows() { - SyncUser user = createTestUser(); - - thrown.expect(IllegalStateException.class); - user.changePasswordAsync("user-id", "new", new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - fail(); - } - }); - } - - @Test - @RunTestInLooperThread - public void changePasswordAsync_nullCallbackThrows() { - SyncUser user = createTestUser(); - - thrown.expect(IllegalArgumentException.class); - //noinspection ConstantConditions - user.changePasswordAsync("new-password", null); - } - - @Test - @RunTestInLooperThread - public void changePassword_admin_Async_nullCallbackThrows() { - SyncUser user = createTestUser(); - - thrown.expect(IllegalArgumentException.class); - //noinspection ConstantConditions - user.changePasswordAsync("user-id", "new-password", null); - } - - @Test - @RunTestInLooperThread - public void changePassword_noneAdminThrows() { - SyncUser user = createTestUser(); - - thrown.expect(IllegalStateException.class); - user.changePassword("user-id", "new-password"); - } - - @Test - public void allSessions() { - String url1 = "realm://objectserver.realm.io/default"; - String url2 = "realm://objectserver.realm.io/~/default"; - - SyncUser user = createTestUser(); - assertEquals(0, user.allSessions().size()); - - SyncConfiguration configuration1 = user.createConfiguration(url1).modules(new AllTypesModelModule()).build(); - Realm realm1 = Realm.getInstance(configuration1); - List allSessions = user.allSessions(); - assertEquals(1, allSessions.size()); - Iterator iter = allSessions.iterator(); - SyncSession session = iter.next(); - assertEquals(user, session.getUser()); - assertEquals(url1, session.getServerUrl().toString()); - - SyncConfiguration configuration2 = user.createConfiguration(url2).modules(new AllTypesModelModule()).build(); - Realm realm2 = Realm.getInstance(configuration2); - allSessions = user.allSessions(); - assertEquals(2, allSessions.size()); - iter = allSessions.iterator(); - String individualUrl = url2.replace("~", user.getIdentity()); - int foundCount = 0; - while (iter.hasNext()) { - session = iter.next(); - assertEquals(user, session.getUser()); - if (individualUrl.equals(session.getServerUrl().toString())) { - foundCount++; - } - } - assertEquals(1, foundCount); - realm1.close(); - - allSessions = user.allSessions(); - assertEquals(1, allSessions.size()); - iter = allSessions.iterator(); - session = iter.next(); - assertEquals(user, session.getUser()); - assertEquals(individualUrl, session.getServerUrl().toString()); - - realm2.close(); - assertEquals(0, user.allSessions().size()); - } - - // JSON format changed in 3.6.0 (removed unnecessary fields), this regression test - // makes sure we can still deserialize a valid SyncUser from the old format. - @Test - public void fromJson_WorkWithRemovedObjectServerUser() { - String oldSyncUserJSON = "{\"authUrl\":\"http:\\/\\/192.168.1.151:9080\\/auth\",\"userToken\":{\"token\":\"eyJpZGVudGl0eSI6IjY4OWQ5MGMxNDIyYTIwMmZkNTljNDYwM2M0ZTRmNmNjIiwiZXhwaXJlcyI6MTgxNjM1ODE4NCwiYXBwX2lkIjoiaW8ucmVhbG0ucmVhbG10YXNrcyIsImFjY2VzcyI6WyJyZWZyZXNoIl0sImlzX2FkbWluIjpmYWxzZSwic2FsdCI6MC4yMTEwMjQyNDgwOTEyMzg1NH0=:lEDa83o1zu8rkwdZVpTyunLHh1wmjxPPSGmZQNxdEM7xDmpbiU7V+8dgDWGevJNHMFluNDAOmrcAOI9TLfhI4rMDl70NI1K9rv\\/Aeq5uIOzq\\/Gf7JTeTUKY5Z7yRoppd8NArlNBKesLFxzdLRlfm1hflF9wH23xQXA19yUZ67JIlkhDPL5e3bau8O3Pr\\/St0unW3KzPOiZUk1l9KRrs2iMCCiXCfq4rf6rp7B2M7rBUMQm68GnB1Ot7l1CblxEWcREcbpyhBKTWIOFRGMwg2TW\\/zRR3cRNglx+ZC4FOeO0mfkX+nf+slyFODAnQkOzPZcGO8xc3I1emafX58Wl\\/Guw==\",\"token_data\":{\"identity\":\"689d90c1422a202fd59c4603c4e4f6cc\",\"path\":\"\",\"expires\":1816358184,\"access\":[\"unknown\"],\"is_admin\":false}},\"realms\":[]}"; - SyncUser syncUser = SyncUser.fromJson(oldSyncUserJSON); - - // Note: we can't call isValid() and expect it to be true - // since the user is not persisted in the UserStore - // isValid() requires SyncManager.getUserStore().isActive(identity) - // to return true as well. - Token refreshToken = syncUser.getRefreshToken(); - assertNotNull(refreshToken); - // refresh token should expire in 10 years (July 23, 2027) - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(refreshToken.expiresMs()); - int day = calendar.get(Calendar.DAY_OF_MONTH); - int month = calendar.get(Calendar.MONTH); - int year = calendar.get(Calendar.YEAR); - - assertEquals(23, day); - assertEquals(Calendar.JULY, month); - assertEquals(2027, year); - - assertEquals("http://192.168.1.151:9080/auth", syncUser.getAuthenticationUrl().toString()); - } - - @Test - @Ignore("until https://github.com/realm/realm-java/issues/5097 is fixed") - public void logoutUserShouldDeleteRealmAfterRestart() throws InterruptedException { - SyncManager.reset(); - BaseRealm.applicationContext = null; // Required for Realm.init() to work - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - - SyncUser user = createTestUser(); - SyncConfiguration syncConfiguration = user.createConfiguration("realm://127.0.0.1:9080/~/tests") - .modules(new StringOnlyModule()) - .build(); - - Realm realm = Realm.getInstance(syncConfiguration); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObject(StringOnly.class).setChars("1"); - } - }); - user.logOut(); - realm.close(); - - final File realmPath = new File (syncConfiguration.getPath()); - assertTrue(realmPath.exists()); - - // simulate an app restart - SyncManager.reset(); - BaseRealm.applicationContext = null; - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - - //now the file should be deleted - assertFalse(realmPath.exists()); - } -} +///* +// * Copyright 2016 Realm Inc. +// * +// * 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. +// */ +// +//package io.realm; +// +//import androidx.test.platform.app.InstrumentationRegistry; +//import androidx.test.rule.UiThreadTestRule; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +// +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Ignore; +//import org.junit.Rule; +//import org.junit.Test; +//import org.junit.rules.ExpectedException; +//import org.junit.runner.RunWith; +//import org.mockito.Mockito; +//import org.mockito.invocation.InvocationOnMock; +//import org.mockito.stubbing.Answer; +// +//import java.io.File; +//import java.lang.reflect.Constructor; +//import java.lang.reflect.InvocationTargetException; +//import java.net.MalformedURLException; +//import java.net.URL; +//import java.util.Calendar; +//import java.util.Iterator; +//import java.util.List; +//import java.util.Map; +//import java.util.UUID; +// +//import io.realm.entities.AllTypesModelModule; +//import io.realm.entities.StringOnly; +//import io.realm.internal.objectserver.Token; +//import io.realm.log.RealmLog; +//import io.realm.entities.StringOnlyModule; +//import io.realm.objectserver.utils.UserFactory; +//import io.realm.rule.RunInLooperThread; +//import io.realm.rule.RunTestInLooperThread; +// +//import static io.realm.SyncTestUtils.createTestAdminUser; +//import static io.realm.SyncTestUtils.createTestUser; +//import static junit.framework.Assert.assertEquals; +//import static org.junit.Assert.assertFalse; +//import static org.junit.Assert.assertNotEquals; +//import static org.junit.Assert.assertNotNull; +//import static org.junit.Assert.assertNull; +//import static org.junit.Assert.assertTrue; +//import static org.junit.Assert.fail; +//import static org.mockito.Matchers.any; +//import static org.mockito.Mockito.when; +// +//@Ignore("FIXME: REalmApp refactor") +//@RunWith(AndroidJUnit4.class) +//public class SyncUserTests { +// +// private static final URL authUrl; +// private static final Constructor SYNC_USER_CONSTRUCTOR; +// static { +// try { +// authUrl = new URL("http://localhost/auth"); +// SYNC_USER_CONSTRUCTOR = SyncUser.class.getDeclaredConstructor(Token.class, URL.class); +// SYNC_USER_CONSTRUCTOR.setAccessible(true); +// } catch (MalformedURLException e) { +// throw new ExceptionInInitializerError(e); +// } catch (NoSuchMethodException e) { +// throw new ExceptionInInitializerError(e); +// } +// } +// +// @Rule +// public final RunInLooperThread looperThread = new RunInLooperThread(); +// +// @Rule +// public final ExpectedException thrown = ExpectedException.none(); +// +// @Rule +// public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); +// +// @Before +// public void setUp() { +// BaseRealm.applicationContext = null; +// Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); +// UserStore userStore = SyncManager.getUserStore(); +// for (SyncUser syncUser : userStore.allUsers()) { +// userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); +// } +// } +// +// @After +// public void after() { +// if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { +// UserFactory.logoutAllUsers(); +// } else { +// looperThread.runAfterTest(new Runnable() { +// @Override +// public void run() { +// UserFactory.logoutAllUsers(); +// } +// }); +// } +// } +// +// private static SyncUser createFakeUser(String id) { +// final Token token = new Token("token_value", id, "path_value", Long.MAX_VALUE, null); +// try { +// return SYNC_USER_CONSTRUCTOR.newInstance(token, authUrl); +// } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { +// fail(e.getMessage()); +// } +// return null; +// } +// +// @Test +// public void equals_validUser() { +// final SyncUser user1 = createFakeUser("id_value"); +// final SyncUser user2 = createFakeUser("id_value"); +// assertTrue(user1.equals(user2)); +// } +// +// @Test +// public void equals_loggedOutUser() { +// final SyncUser user1 = createFakeUser("id_value"); +// final SyncUser user2 = createFakeUser("id_value"); +// user1.logOut(); +// user2.logOut(); +// assertTrue(user1.equals(user2)); +// } +// +// @Test +// public void hashCode_validUser() { +// final SyncUser user = createFakeUser("id_value"); +// assertNotEquals(0, user.hashCode()); +// } +// +// @Test +// public void hashCode_loggedOutUser() { +// final SyncUser user = createFakeUser("id_value"); +// user.logOut(); +// assertNotEquals(0, user.hashCode()); +// } +// +// @Test +// public void toAndFromJson() { +// SyncUser user1 = createTestUser(); +// SyncUser user2 = SyncUser.fromJson(user1.toJson()); +// assertEquals(user1, user2); +// } +// +// // Tests that the UserStore does not return users that have expired +// @Test +// public void currentUser_returnsNullIfUserExpired() { +// // Add an expired user to the user store +// UserStore userStore = SyncManager.getUserStore(); +// userStore.put(createTestUser(Long.MIN_VALUE)); +// +// // Invalid users should not be returned when asking the for the current user +// assertNull(SyncUser.current()); +// } +// +// @Test +// public void currentUser_throwsIfMultipleUsersLoggedIn() { +// RealmObjectServer originalAuthServer = SyncManager.getAuthServer(); +// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); +// SyncManager.setAuthServerImpl(authServer); +// +// try { +// // 1. Login two random users +// when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenAnswer(new Answer() { +// @Override +// public AuthenticateResponse answer(InvocationOnMock invocationOnMock) throws Throwable { +// return getNewRandomUser(); +// } +// }); +// SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); +// SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); +// +// // 2. Verify current() now throws +// try { +// SyncUser.current(); +// fail(); +// } catch (IllegalStateException ignore) { +// } +// } finally { +// SyncManager.setAuthServerImpl(originalAuthServer); +// } +// +// } +// +// private AuthenticateResponse getNewRandomUser() { +// String identity = UUID.randomUUID().toString(); +// String userTokenValue = UUID.randomUUID().toString(); +// return SyncTestUtils.createLoginResponse(userTokenValue, identity, Long.MAX_VALUE, false); +// } +// +// // Test that current user is cleared if it is logged out +// @Test +// public void currentUser_clearedOnLogout() { +// // Add 1 valid user to the user store +// SyncUser user = createTestUser(Long.MAX_VALUE); +// UserStore userStore = SyncManager.getUserStore(); +// userStore.put(user); +// +// SyncUser savedUser = SyncUser.current(); +// assertEquals(user, savedUser); +// assertNotNull(savedUser); +// savedUser.logOut(); +// assertNull(SyncUser.current()); +// } +// +// // `all()` returns an empty list if no users are logged in +// @Test +// public void all_empty() { +// Map users = SyncUser.all(); +// assertTrue(users.isEmpty()); +// } +// +// // `all()` returns only valid users. Invalid users are filtered. +// @Test +// public void all_validUsers() { +// // Add 1 expired user and 1 valid user to the user store +// UserStore userStore = SyncManager.getUserStore(); +// userStore.put(createTestUser(Long.MIN_VALUE)); +// userStore.put(createTestUser(Long.MAX_VALUE)); +// +// Map users = SyncUser.all(); +// assertEquals(1, users.size()); +// assertTrue(users.entrySet().iterator().next().getValue().isValid()); +// } +// +// @Test +// public void isAdmin() { +// SyncUser user1 = createTestUser(); +// assertFalse(user1.isAdmin()); +// +// SyncUser user2 = createTestAdminUser(); +// assertTrue(user2.isAdmin()); +// } +// +// @Test +// public void isAdmin_allUsers() { +// UserStore userStore = SyncManager.getUserStore(); +// SyncUser user = createTestAdminUser(); +// assertTrue(user.isAdmin()); +// userStore.put(user); +// +// Map users = SyncUser.all(); +// assertEquals(1, users.size()); +// assertTrue(users.entrySet().iterator().next().getValue().isAdmin()); +// } +// +// // Tests that the user store returns the last user to login +// @Ignore("This test fails because of wrong JSON string.") +// @Test +// public void currentUser_returnsUserAfterLogin() { +// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); +// when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(Long.MAX_VALUE)); +// +// SyncUser user = SyncUser.logIn(SyncCredentials.facebook("foo"), "http://bar.com/auth"); +// assertEquals(user, SyncUser.current()); +// } +// +// @Test +// public void toString_returnDescription() { +// SyncUser user = createTestUser("http://objectserver.realm.io/auth"); +// String str = user.toString(); +// assertTrue(str != null && !str.isEmpty()); +// } +// +// // Test that a login with an access token logs the user in directly without touching the network +// @Test +// public void login_withAccessToken() { +// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); +// when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenThrow(new AssertionError("Server contacted.")); +// RealmObjectServer originalServer = SyncManager.getAuthServer(); +// SyncManager.setAuthServerImpl(authServer); +// try { +// SyncCredentials credentials = SyncCredentials.accessToken("foo", "bar"); +// SyncUser user = SyncUser.logIn(credentials, "http://ros.realm.io/auth"); +// assertTrue(user.isValid()); +// } finally { +// SyncManager.setAuthServerImpl(originalServer); +// } +// } +// +// // Checks that `/auth` is correctly added to any URL without a path +// @Test +// public void login_appendAuthSegment() { +// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); +// RealmObjectServer originalServer = SyncManager.getAuthServer(); +// SyncManager.setAuthServerImpl(authServer); +// String[][] urls = { +// {"http://ros.realm.io", "http://ros.realm.io/auth"}, +// {"http://ros.realm.io:8080", "http://ros.realm.io:8080/auth"}, +// {"http://ros.realm.io/", "http://ros.realm.io/"}, +// {"http://ros.realm.io/?foo=bar", "http://ros.realm.io/?foo=bar"}, +// {"http://ros.realm.io/auth", "http://ros.realm.io/auth"}, +// {"http://ros.realm.io/auth/", "http://ros.realm.io/auth/"}, +// {"http://ros.realm.io/custom-path/", "http://ros.realm.io/custom-path/"} +// }; +// +// try { +// for (String[] url : urls) { +// RealmLog.error(url[0]); +// String input = url[0]; +// String normalizedInput = url[1]; +// SyncCredentials credentials = SyncCredentials.accessToken("token", UUID.randomUUID().toString()); +// SyncUser user = SyncUser.logIn(credentials, input); +// assertEquals(normalizedInput, user.getAuthenticationUrl().toString()); +// user.logOut(); +// } +// } finally { +// SyncManager.setAuthServerImpl(originalServer); +// } +// } +// +// @Test +// public void changePassword_nullThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalArgumentException.class); +// //noinspection ConstantConditions +// user.changePassword(null); +// } +// +// @Test +// public void changePassword_admin_nullThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalArgumentException.class); +// //noinspection ConstantConditions +// user.changePassword(null, "new-password"); +// } +// +// @Test +// public void changePasswordAsync_nonLooperThreadThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalStateException.class); +// user.changePasswordAsync("password", new SyncUser.Callback() { +// @Override +// public void onSuccess(SyncUser user) { +// fail(); +// } +// +// @Override +// public void onError(ObjectServerError error) { +// fail(); +// } +// }); +// } +// +// @Test +// public void changePassword_admin_Async_nonLooperThreadThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalStateException.class); +// user.changePasswordAsync("user-id", "new", new SyncUser.Callback() { +// @Override +// public void onSuccess(SyncUser user) { +// fail(); +// } +// +// @Override +// public void onError(ObjectServerError error) { +// fail(); +// } +// }); +// } +// +// @Test +// @RunTestInLooperThread +// public void changePasswordAsync_nullCallbackThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalArgumentException.class); +// //noinspection ConstantConditions +// user.changePasswordAsync("new-password", null); +// } +// +// @Test +// @RunTestInLooperThread +// public void changePassword_admin_Async_nullCallbackThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalArgumentException.class); +// //noinspection ConstantConditions +// user.changePasswordAsync("user-id", "new-password", null); +// } +// +// @Test +// @RunTestInLooperThread +// public void changePassword_noneAdminThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalStateException.class); +// user.changePassword("user-id", "new-password"); +// } +// +// @Test +// public void allSessions() { +// String url1 = "realm://objectserver.realm.io/default"; +// String url2 = "realm://objectserver.realm.io/~/default"; +// +// SyncUser user = createTestUser(); +// assertEquals(0, user.allSessions().size()); +// +// SyncConfiguration configuration1 = user.createConfiguration(url1).modules(new AllTypesModelModule()).build(); +// Realm realm1 = Realm.getInstance(configuration1); +// List allSessions = user.allSessions(); +// assertEquals(1, allSessions.size()); +// Iterator iter = allSessions.iterator(); +// SyncSession session = iter.next(); +// assertEquals(user, session.getUser()); +// assertEquals(url1, session.getServerUrl().toString()); +// +// SyncConfiguration configuration2 = user.createConfiguration(url2).modules(new AllTypesModelModule()).build(); +// Realm realm2 = Realm.getInstance(configuration2); +// allSessions = user.allSessions(); +// assertEquals(2, allSessions.size()); +// iter = allSessions.iterator(); +// String individualUrl = url2.replace("~", user.getIdentity()); +// int foundCount = 0; +// while (iter.hasNext()) { +// session = iter.next(); +// assertEquals(user, session.getUser()); +// if (individualUrl.equals(session.getServerUrl().toString())) { +// foundCount++; +// } +// } +// assertEquals(1, foundCount); +// realm1.close(); +// +// allSessions = user.allSessions(); +// assertEquals(1, allSessions.size()); +// iter = allSessions.iterator(); +// session = iter.next(); +// assertEquals(user, session.getUser()); +// assertEquals(individualUrl, session.getServerUrl().toString()); +// +// realm2.close(); +// assertEquals(0, user.allSessions().size()); +// } +// +// // JSON format changed in 3.6.0 (removed unnecessary fields), this regression test +// // makes sure we can still deserialize a valid SyncUser from the old format. +// @Test +// public void fromJson_WorkWithRemovedObjectServerUser() { +// String oldSyncUserJSON = "{\"authUrl\":\"http:\\/\\/192.168.1.151:9080\\/auth\",\"userToken\":{\"token\":\"eyJpZGVudGl0eSI6IjY4OWQ5MGMxNDIyYTIwMmZkNTljNDYwM2M0ZTRmNmNjIiwiZXhwaXJlcyI6MTgxNjM1ODE4NCwiYXBwX2lkIjoiaW8ucmVhbG0ucmVhbG10YXNrcyIsImFjY2VzcyI6WyJyZWZyZXNoIl0sImlzX2FkbWluIjpmYWxzZSwic2FsdCI6MC4yMTEwMjQyNDgwOTEyMzg1NH0=:lEDa83o1zu8rkwdZVpTyunLHh1wmjxPPSGmZQNxdEM7xDmpbiU7V+8dgDWGevJNHMFluNDAOmrcAOI9TLfhI4rMDl70NI1K9rv\\/Aeq5uIOzq\\/Gf7JTeTUKY5Z7yRoppd8NArlNBKesLFxzdLRlfm1hflF9wH23xQXA19yUZ67JIlkhDPL5e3bau8O3Pr\\/St0unW3KzPOiZUk1l9KRrs2iMCCiXCfq4rf6rp7B2M7rBUMQm68GnB1Ot7l1CblxEWcREcbpyhBKTWIOFRGMwg2TW\\/zRR3cRNglx+ZC4FOeO0mfkX+nf+slyFODAnQkOzPZcGO8xc3I1emafX58Wl\\/Guw==\",\"token_data\":{\"identity\":\"689d90c1422a202fd59c4603c4e4f6cc\",\"path\":\"\",\"expires\":1816358184,\"access\":[\"unknown\"],\"is_admin\":false}},\"realms\":[]}"; +// SyncUser syncUser = SyncUser.fromJson(oldSyncUserJSON); +// +// // Note: we can't call isValid() and expect it to be true +// // since the user is not persisted in the UserStore +// // isValid() requires SyncManager.getUserStore().isActive(identity) +// // to return true as well. +// Token refreshToken = syncUser.getRefreshToken(); +// assertNotNull(refreshToken); +// // refresh token should expire in 10 years (July 23, 2027) +// Calendar calendar = Calendar.getInstance(); +// calendar.setTimeInMillis(refreshToken.expiresMs()); +// int day = calendar.get(Calendar.DAY_OF_MONTH); +// int month = calendar.get(Calendar.MONTH); +// int year = calendar.get(Calendar.YEAR); +// +// assertEquals(23, day); +// assertEquals(Calendar.JULY, month); +// assertEquals(2027, year); +// +// assertEquals("http://192.168.1.151:9080/auth", syncUser.getAuthenticationUrl().toString()); +// } +// +// @Test +// @Ignore("until https://github.com/realm/realm-java/issues/5097 is fixed") +// public void logoutUserShouldDeleteRealmAfterRestart() throws InterruptedException { +// SyncManager.reset(); +// BaseRealm.applicationContext = null; // Required for Realm.init() to work +// Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); +// +// SyncUser user = createTestUser(); +// SyncConfiguration syncConfiguration = user.createConfiguration("realm://127.0.0.1:9080/~/tests") +// .modules(new StringOnlyModule()) +// .build(); +// +// Realm realm = Realm.getInstance(syncConfiguration); +// realm.executeTransaction(new Realm.Transaction() { +// @Override +// public void execute(Realm realm) { +// realm.createObject(StringOnly.class).setChars("1"); +// } +// }); +// user.logOut(); +// realm.close(); +// +// final File realmPath = new File (syncConfiguration.getPath()); +// assertTrue(realmPath.exists()); +// +// // simulate an app restart +// SyncManager.reset(); +// BaseRealm.applicationContext = null; +// Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); +// +// //now the file should be deleted +// assertFalse(realmPath.exists()); +// } +//} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index ab523aca0c..ca5ed81e63 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -61,9 +61,11 @@ public void tearDown() { if (realm != null && !realm.isClosed()) { realm.close(); } - for (SyncUser user : SyncUser.all().values()) { - user.logOut(); - } + +// FIXME +// for (RealmUser user : RealmApp.allUsers().values()) { +// RealmApp.logout(user); +// } } private Realm getNormalRealm() { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppUserTests.java b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppUserTests.java new file mode 100644 index 0000000000..f9a47d6b3c --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppUserTests.java @@ -0,0 +1,517 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +// +//package io.realm; +// +//import androidx.test.ext.junit.runners.AndroidJUnit4; +//import androidx.test.platform.app.InstrumentationRegistry; +//import androidx.test.rule.UiThreadTestRule; +// +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Ignore; +//import org.junit.Rule; +//import org.junit.Test; +//import org.junit.rules.ExpectedException; +//import org.junit.runner.RunWith; +//import org.mockito.Mockito; +//import org.mockito.invocation.InvocationOnMock; +//import org.mockito.stubbing.Answer; +// +//import java.io.File; +//import java.lang.reflect.Constructor; +//import java.lang.reflect.InvocationTargetException; +//import java.net.MalformedURLException; +//import java.net.URL; +//import java.util.Calendar; +//import java.util.Iterator; +//import java.util.List; +//import java.util.Map; +//import java.util.UUID; +// +//import io.realm.entities.AllTypesModelModule; +//import io.realm.entities.StringOnly; +//import io.realm.entities.StringOnlyModule; +//import io.realm.internal.objectserver.Token; +//import io.realm.log.RealmLog; +//import io.realm.objectserver.utils.UserFactory; +//import io.realm.rule.RunInLooperThread; +//import io.realm.rule.RunTestInLooperThread; +// +//import static io.realm.SyncTestUtils.createTestAdminUser; +//import static io.realm.SyncTestUtils.createTestUser; +//import static junit.framework.Assert.assertEquals; +//import static org.junit.Assert.assertFalse; +//import static org.junit.Assert.assertNotEquals; +//import static org.junit.Assert.assertNotNull; +//import static org.junit.Assert.assertNull; +//import static org.junit.Assert.assertTrue; +//import static org.junit.Assert.fail; +//import static org.mockito.Matchers.any; +//import static org.mockito.Mockito.when; +// +//@RunWith(AndroidJUnit4.class) +//public class AppUserTests { +// +// private static final URL authUrl; +// private static final Constructor SYNC_USER_CONSTRUCTOR; +// static { +// try { +// authUrl = new URL("http://localhost/auth"); +// SYNC_USER_CONSTRUCTOR = SyncUser.class.getDeclaredConstructor(Token.class, URL.class); +// SYNC_USER_CONSTRUCTOR.setAccessible(true); +// } catch (MalformedURLException e) { +// throw new ExceptionInInitializerError(e); +// } catch (NoSuchMethodException e) { +// throw new ExceptionInInitializerError(e); +// } +// } +// +// @Rule +// public final RunInLooperThread looperThread = new RunInLooperThread(); +// +// @Rule +// public final ExpectedException thrown = ExpectedException.none(); +// +// @Rule +// public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); +// +// @Before +// public void setUp() { +// BaseRealm.applicationContext = null; +// Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); +// UserStore userStore = SyncManager.getUserStore(); +// for (SyncUser syncUser : userStore.allUsers()) { +// userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); +// } +// } +// +// @After +// public void after() { +// if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { +// UserFactory.logoutAllUsers(); +// } else { +// looperThread.runAfterTest(new Runnable() { +// @Override +// public void run() { +// UserFactory.logoutAllUsers(); +// } +// }); +// } +// } +// +// private static SyncUser createFakeUser(String id) { +// final Token token = new Token("token_value", id, "path_value", Long.MAX_VALUE, null); +// try { +// return SYNC_USER_CONSTRUCTOR.newInstance(token, authUrl); +// } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { +// fail(e.getMessage()); +// } +// return null; +// } +// +// @Test +// public void equals_validUser() { +// final SyncUser user1 = createFakeUser("id_value"); +// final SyncUser user2 = createFakeUser("id_value"); +// assertTrue(user1.equals(user2)); +// } +// +// @Test +// public void equals_loggedOutUser() { +// final SyncUser user1 = createFakeUser("id_value"); +// final SyncUser user2 = createFakeUser("id_value"); +// user1.logOut(); +// user2.logOut(); +// assertTrue(user1.equals(user2)); +// } +// +// @Test +// public void hashCode_validUser() { +// final SyncUser user = createFakeUser("id_value"); +// assertNotEquals(0, user.hashCode()); +// } +// +// @Test +// public void hashCode_loggedOutUser() { +// final SyncUser user = createFakeUser("id_value"); +// user.logOut(); +// assertNotEquals(0, user.hashCode()); +// } +// +// @Test +// public void toAndFromJson() { +// SyncUser user1 = createTestUser(); +// SyncUser user2 = SyncUser.fromJson(user1.toJson()); +// assertEquals(user1, user2); +// } +// +// // Tests that the UserStore does not return users that have expired +// @Test +// public void currentUser_returnsNullIfUserExpired() { +// // Add an expired user to the user store +// UserStore userStore = SyncManager.getUserStore(); +// userStore.put(createTestUser(Long.MIN_VALUE)); +// +// // Invalid users should not be returned when asking the for the current user +// assertNull(SyncUser.current()); +// } +// +// @Test +// public void currentUser_throwsIfMultipleUsersLoggedIn() { +// RealmObjectServer originalAuthServer = SyncManager.getAuthServer(); +// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); +// SyncManager.setAuthServerImpl(authServer); +// +// try { +// // 1. Login two random users +// when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenAnswer(new Answer() { +// @Override +// public AuthenticateResponse answer(InvocationOnMock invocationOnMock) throws Throwable { +// return getNewRandomUser(); +// } +// }); +// SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); +// SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); +// +// // 2. Verify current() now throws +// try { +// SyncUser.current(); +// fail(); +// } catch (IllegalStateException ignore) { +// } +// } finally { +// SyncManager.setAuthServerImpl(originalAuthServer); +// } +// +// } +// +// private AuthenticateResponse getNewRandomUser() { +// String identity = UUID.randomUUID().toString(); +// String userTokenValue = UUID.randomUUID().toString(); +// return SyncTestUtils.createLoginResponse(userTokenValue, identity, Long.MAX_VALUE, false); +// } +// +// // Test that current user is cleared if it is logged out +// @Test +// public void currentUser_clearedOnLogout() { +// // Add 1 valid user to the user store +// SyncUser user = createTestUser(Long.MAX_VALUE); +// UserStore userStore = SyncManager.getUserStore(); +// userStore.put(user); +// +// SyncUser savedUser = SyncUser.current(); +// assertEquals(user, savedUser); +// assertNotNull(savedUser); +// savedUser.logOut(); +// assertNull(SyncUser.current()); +// } +// +// // `all()` returns an empty list if no users are logged in +// @Test +// public void all_empty() { +// Map users = SyncUser.all(); +// assertTrue(users.isEmpty()); +// } +// +// // `all()` returns only valid users. Invalid users are filtered. +// @Test +// public void all_validUsers() { +// // Add 1 expired user and 1 valid user to the user store +// UserStore userStore = SyncManager.getUserStore(); +// userStore.put(createTestUser(Long.MIN_VALUE)); +// userStore.put(createTestUser(Long.MAX_VALUE)); +// +// Map users = SyncUser.all(); +// assertEquals(1, users.size()); +// assertTrue(users.entrySet().iterator().next().getValue().isValid()); +// } +// +// @Test +// public void isAdmin() { +// SyncUser user1 = createTestUser(); +// assertFalse(user1.isAdmin()); +// +// SyncUser user2 = createTestAdminUser(); +// assertTrue(user2.isAdmin()); +// } +// +// @Test +// public void isAdmin_allUsers() { +// UserStore userStore = SyncManager.getUserStore(); +// SyncUser user = createTestAdminUser(); +// assertTrue(user.isAdmin()); +// userStore.put(user); +// +// Map users = SyncUser.all(); +// assertEquals(1, users.size()); +// assertTrue(users.entrySet().iterator().next().getValue().isAdmin()); +// } +// +// // Tests that the user store returns the last user to login +// @Ignore("This test fails because of wrong JSON string.") +// @Test +// public void currentUser_returnsUserAfterLogin() { +// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); +// when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(Long.MAX_VALUE)); +// +// SyncUser user = SyncUser.logIn(SyncCredentials.facebook("foo"), "http://bar.com/auth"); +// assertEquals(user, SyncUser.current()); +// } +// +// @Test +// public void toString_returnDescription() { +// SyncUser user = createTestUser("http://objectserver.realm.io/auth"); +// String str = user.toString(); +// assertTrue(str != null && !str.isEmpty()); +// } +// +// // Test that a login with an access token logs the user in directly without touching the network +// @Test +// public void login_withAccessToken() { +// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); +// when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenThrow(new AssertionError("Server contacted.")); +// RealmObjectServer originalServer = SyncManager.getAuthServer(); +// SyncManager.setAuthServerImpl(authServer); +// try { +// SyncCredentials credentials = SyncCredentials.accessToken("foo", "bar"); +// SyncUser user = SyncUser.logIn(credentials, "http://ros.realm.io/auth"); +// assertTrue(user.isValid()); +// } finally { +// SyncManager.setAuthServerImpl(originalServer); +// } +// } +// +// // Checks that `/auth` is correctly added to any URL without a path +// @Test +// public void login_appendAuthSegment() { +// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); +// RealmObjectServer originalServer = SyncManager.getAuthServer(); +// SyncManager.setAuthServerImpl(authServer); +// String[][] urls = { +// {"http://ros.realm.io", "http://ros.realm.io/auth"}, +// {"http://ros.realm.io:8080", "http://ros.realm.io:8080/auth"}, +// {"http://ros.realm.io/", "http://ros.realm.io/"}, +// {"http://ros.realm.io/?foo=bar", "http://ros.realm.io/?foo=bar"}, +// {"http://ros.realm.io/auth", "http://ros.realm.io/auth"}, +// {"http://ros.realm.io/auth/", "http://ros.realm.io/auth/"}, +// {"http://ros.realm.io/custom-path/", "http://ros.realm.io/custom-path/"} +// }; +// +// try { +// for (String[] url : urls) { +// RealmLog.error(url[0]); +// String input = url[0]; +// String normalizedInput = url[1]; +// SyncCredentials credentials = SyncCredentials.accessToken("token", UUID.randomUUID().toString()); +// SyncUser user = SyncUser.logIn(credentials, input); +// assertEquals(normalizedInput, user.getAuthenticationUrl().toString()); +// user.logOut(); +// } +// } finally { +// SyncManager.setAuthServerImpl(originalServer); +// } +// } +// +// @Test +// public void changePassword_nullThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalArgumentException.class); +// //noinspection ConstantConditions +// user.changePassword(null); +// } +// +// @Test +// public void changePassword_admin_nullThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalArgumentException.class); +// //noinspection ConstantConditions +// user.changePassword(null, "new-password"); +// } +// +// @Test +// public void changePasswordAsync_nonLooperThreadThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalStateException.class); +// user.changePasswordAsync("password", new SyncUser.Callback() { +// @Override +// public void onSuccess(SyncUser user) { +// fail(); +// } +// +// @Override +// public void onError(ObjectServerError error) { +// fail(); +// } +// }); +// } +// +// @Test +// public void changePassword_admin_Async_nonLooperThreadThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalStateException.class); +// user.changePasswordAsync("user-id", "new", new SyncUser.Callback() { +// @Override +// public void onSuccess(SyncUser user) { +// fail(); +// } +// +// @Override +// public void onError(ObjectServerError error) { +// fail(); +// } +// }); +// } +// +// @Test +// @RunTestInLooperThread +// public void changePasswordAsync_nullCallbackThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalArgumentException.class); +// //noinspection ConstantConditions +// user.changePasswordAsync("new-password", null); +// } +// +// @Test +// @RunTestInLooperThread +// public void changePassword_admin_Async_nullCallbackThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalArgumentException.class); +// //noinspection ConstantConditions +// user.changePasswordAsync("user-id", "new-password", null); +// } +// +// @Test +// @RunTestInLooperThread +// public void changePassword_noneAdminThrows() { +// SyncUser user = createTestUser(); +// +// thrown.expect(IllegalStateException.class); +// user.changePassword("user-id", "new-password"); +// } +// +// @Test +// public void allSessions() { +// String url1 = "realm://objectserver.realm.io/default"; +// String url2 = "realm://objectserver.realm.io/~/default"; +// +// SyncUser user = createTestUser(); +// assertEquals(0, user.allSessions().size()); +// +// SyncConfiguration configuration1 = user.createConfiguration(url1).modules(new AllTypesModelModule()).build(); +// Realm realm1 = Realm.getInstance(configuration1); +// List allSessions = user.allSessions(); +// assertEquals(1, allSessions.size()); +// Iterator iter = allSessions.iterator(); +// SyncSession session = iter.next(); +// assertEquals(user, session.getUser()); +// assertEquals(url1, session.getServerUrl().toString()); +// +// SyncConfiguration configuration2 = user.createConfiguration(url2).modules(new AllTypesModelModule()).build(); +// Realm realm2 = Realm.getInstance(configuration2); +// allSessions = user.allSessions(); +// assertEquals(2, allSessions.size()); +// iter = allSessions.iterator(); +// String individualUrl = url2.replace("~", user.getIdentity()); +// int foundCount = 0; +// while (iter.hasNext()) { +// session = iter.next(); +// assertEquals(user, session.getUser()); +// if (individualUrl.equals(session.getServerUrl().toString())) { +// foundCount++; +// } +// } +// assertEquals(1, foundCount); +// realm1.close(); +// +// allSessions = user.allSessions(); +// assertEquals(1, allSessions.size()); +// iter = allSessions.iterator(); +// session = iter.next(); +// assertEquals(user, session.getUser()); +// assertEquals(individualUrl, session.getServerUrl().toString()); +// +// realm2.close(); +// assertEquals(0, user.allSessions().size()); +// } +// +// // JSON format changed in 3.6.0 (removed unnecessary fields), this regression test +// // makes sure we can still deserialize a valid SyncUser from the old format. +// @Test +// public void fromJson_WorkWithRemovedObjectServerUser() { +// String oldSyncUserJSON = "{\"authUrl\":\"http:\\/\\/192.168.1.151:9080\\/auth\",\"userToken\":{\"token\":\"eyJpZGVudGl0eSI6IjY4OWQ5MGMxNDIyYTIwMmZkNTljNDYwM2M0ZTRmNmNjIiwiZXhwaXJlcyI6MTgxNjM1ODE4NCwiYXBwX2lkIjoiaW8ucmVhbG0ucmVhbG10YXNrcyIsImFjY2VzcyI6WyJyZWZyZXNoIl0sImlzX2FkbWluIjpmYWxzZSwic2FsdCI6MC4yMTEwMjQyNDgwOTEyMzg1NH0=:lEDa83o1zu8rkwdZVpTyunLHh1wmjxPPSGmZQNxdEM7xDmpbiU7V+8dgDWGevJNHMFluNDAOmrcAOI9TLfhI4rMDl70NI1K9rv\\/Aeq5uIOzq\\/Gf7JTeTUKY5Z7yRoppd8NArlNBKesLFxzdLRlfm1hflF9wH23xQXA19yUZ67JIlkhDPL5e3bau8O3Pr\\/St0unW3KzPOiZUk1l9KRrs2iMCCiXCfq4rf6rp7B2M7rBUMQm68GnB1Ot7l1CblxEWcREcbpyhBKTWIOFRGMwg2TW\\/zRR3cRNglx+ZC4FOeO0mfkX+nf+slyFODAnQkOzPZcGO8xc3I1emafX58Wl\\/Guw==\",\"token_data\":{\"identity\":\"689d90c1422a202fd59c4603c4e4f6cc\",\"path\":\"\",\"expires\":1816358184,\"access\":[\"unknown\"],\"is_admin\":false}},\"realms\":[]}"; +// SyncUser syncUser = SyncUser.fromJson(oldSyncUserJSON); +// +// // Note: we can't call isValid() and expect it to be true +// // since the user is not persisted in the UserStore +// // isValid() requires SyncManager.getUserStore().isActive(identity) +// // to return true as well. +// Token refreshToken = syncUser.getRefreshToken(); +// assertNotNull(refreshToken); +// // refresh token should expire in 10 years (July 23, 2027) +// Calendar calendar = Calendar.getInstance(); +// calendar.setTimeInMillis(refreshToken.expiresMs()); +// int day = calendar.get(Calendar.DAY_OF_MONTH); +// int month = calendar.get(Calendar.MONTH); +// int year = calendar.get(Calendar.YEAR); +// +// assertEquals(23, day); +// assertEquals(Calendar.JULY, month); +// assertEquals(2027, year); +// +// assertEquals("http://192.168.1.151:9080/auth", syncUser.getAuthenticationUrl().toString()); +// } +// +// @Test +// @Ignore("until https://github.com/realm/realm-java/issues/5097 is fixed") +// public void logoutUserShouldDeleteRealmAfterRestart() throws InterruptedException { +// SyncManager.reset(); +// BaseRealm.applicationContext = null; // Required for Realm.init() to work +// Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); +// +// SyncUser user = createTestUser(); +// SyncConfiguration syncConfiguration = user.createConfiguration("realm://127.0.0.1:9080/~/tests") +// .modules(new StringOnlyModule()) +// .build(); +// +// Realm realm = Realm.getInstance(syncConfiguration); +// realm.executeTransaction(new Realm.Transaction() { +// @Override +// public void execute(Realm realm) { +// realm.createObject(StringOnly.class).setChars("1"); +// } +// }); +// user.logOut(); +// realm.close(); +// +// final File realmPath = new File (syncConfiguration.getPath()); +// assertTrue(realmPath.exists()); +// +// // simulate an app restart +// SyncManager.reset(); +// BaseRealm.applicationContext = null; +// Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); +// +// //now the file should be deleted +// assertFalse(realmPath.exists()); +// } +//} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt new file mode 100644 index 0000000000..088d3b0b60 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RealmAppTests { + + private lateinit var app: RealmApp + + @Before + fun setUp() { + app = TestRealmApp.getInstance() + } + + // FIXME: Smoke test for the network protocol and associated classes. + @Test + fun login() { + val creds = RealmCredentials.anonymous() + var user = app.login(creds) + assertNotNull(user) + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt new file mode 100644 index 0000000000..58c0cc532a --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.* +import org.junit.BeforeClass +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith + +@Ignore("FIXME: Reenable these when adding full suppport for a Credentials") +@RunWith(AndroidJUnit4::class) +class RealmCredentialsTests { + + companion object { + @BeforeClass + @JvmStatic + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + } + + @Test + fun anonymous() { + val creds = RealmCredentials.anonymous() + assertEquals("anon-user", creds.identityProvider) + assertNotNull(creds.asJson()) // Treat the JSON as an opaque value. + } + + @Test + fun apiKey() { + TODO() + } + + @Test + fun apiKey_invalidInput() { + TODO() + } + + @Test + fun apple() { + val creds = RealmCredentials.apple("apple-token") + assertEquals("oauth2-apple", creds.identityProvider) + assertTrue(creds.asJson().contains("apple-token")) // Treat the JSON as a largely opaque value. + } + + @Test + fun apple_invalidInput() { + try { + RealmCredentials.apple("") + } catch (ignored: IllegalArgumentException) { + } + } + + @Test + fun customFunction() { + TODO() + } + + @Test + fun customFunction_invalidInput() { + TODO() + } + + @Test + fun emailPassword() { + val creds = RealmCredentials.emailPassword("foo@bar.com", "secret") + assertEquals("local-userpass", creds.identityProvider) + // Treat the JSON as a largely opaque value. + assertTrue(creds.asJson().contains("foo@bar.com")) + assertTrue(creds.asJson().contains("secret")) + } + + @Test + fun emailPassword_invalidInput() { + TODO() + } + + @Test + fun facebook() { + val creds = RealmCredentials.facebook("fb-token") + assertEquals("oauth2-facebook", creds.identityProvider) + assertTrue(creds.asJson().contains("fb-token")) + } + + @Test + fun facebook_invalidInput() { + try { + RealmCredentials.facebook("") + } catch (ignored: IllegalArgumentException) { + } + } + + @Test + fun google() { + val creds = RealmCredentials.google("google-token") + assertEquals("google", creds.identityProvider) + assertTrue(creds.asJson().contains("google-token")) + } + + @Test + fun google_invalidInput() { + try { + RealmCredentials.google("") + } catch (ignored: IllegalArgumentException) { + } + } + + @Test + fun jwt() { + val creds = RealmCredentials.google("jwt-token") + assertEquals("jwt", creds.identityProvider) + assertTrue(creds.asJson().contains("jwt-token")) + } + + @Test + fun jwt_invalidInput() { + try { + RealmCredentials.google("") + } catch (ignored: IllegalArgumentException) { + } + } + +} \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt new file mode 100644 index 0000000000..4c397140f8 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.internal.network.OkHttpNetworkTransport +import io.realm.internal.objectstore.OsJavaNetworkTransport +import io.realm.log.LogLevel +import java.lang.IllegalStateException + +/** + * This class wraps various methods making it easier to create an RealmApp that can be used + * for testing. + * + * NOTE: This class must remain in the [io.realm] package in order to work. + */ +class TestRealmApp private constructor() { + companion object { + private val applicationId = fetchApplicationId() + val config = RealmAppConfiguration.Builder(applicationId) + .logLevel(LogLevel.DEBUG) + .baseUrl("http://127.0.0.1:9090") + .appName("MongoDB Realm Integration Tests") + .appVersion("1.0.") + .build() + + private fun init() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + + private fun fetchApplicationId(): String { + init() + val transport = OkHttpNetworkTransport() + val response = transport.sendRequest( + "get", + "http://127.0.0.1:8888/application-id", + 5000, + mapOf(), + "" + ) + return when(response.httpResponseCode) { + 200 -> response.body + else -> throw IllegalStateException(response.toString()) + } + } + + fun getInstance(networkTransport: OsJavaNetworkTransport? = null): RealmApp { + val app = RealmApp(config) + if (networkTransport != null) { + app.networkTransport = networkTransport + } + return app + } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt new file mode 100644 index 0000000000..6ee4c68931 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.transport + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import io.realm.internal.network.OkHttpNetworkTransport +import io.realm.internal.objectstore.OsJavaNetworkTransport +import junit.framework.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** + * This class is responsible for testing the OkHttp implementation of the network layer. + * Any behavior happening after the network request has executed are not covered by this class, + * but instead in [OsJavaNetworkTransportTests]. + * + * This class uses a simple custom webserver written in Node that must be running when + * executing these tests. + */ +@RunWith(AndroidJUnit4::class) +class OkHttpNetworkTransportTests { + + private lateinit var transport: OkHttpNetworkTransport + private val baseUrl = "http://127.0.0.1:8888" // URL to command server + + enum class HTTPMethod(val nativeKey: String) { + GET("get"), POST("post"), PATCH("patch"), PUT("put"), DELETE("delete"); + } + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + transport = OkHttpNetworkTransport() + } + + @Test + fun requestSuccessful() { + val url = "$baseUrl/okhttp?success=true" + for (method in HTTPMethod.values()) { + val body = if (method == HTTPMethod.GET) "" else "{ \"body\" : \"some content\" }" + val headers = mapOf( + Pair("Content-Type", "application/json;charset=utf-8"), + Pair("Accept", "application/json") + ) + + val response: OsJavaNetworkTransport.Response = transport.sendRequest(method.nativeKey, + url, + 5000, + headers, + body) + assertEquals(200, response.httpResponseCode) + assertEquals(0, response.customResponseCode) + assertEquals("${method.name}-success", response.body) + } + } + + @Test + fun requestFailedOnServer() { + val url = "$baseUrl/okhttp?success=false" + for (method in HTTPMethod.values()) { + val body = if (method == HTTPMethod.GET) "" else "{ \"body\" : \"some content\" }" + val headers = mapOf( + Pair("Content-Type", "application/json;charset=utf-8"), + Pair("Accept", "application/json") + ) + + val response: OsJavaNetworkTransport.Response = transport.sendRequest(method.nativeKey, + url, + 5000, + headers, + body) + assertEquals(500, response.httpResponseCode) + assertEquals(0, response.customResponseCode) + assertEquals("${method.name}-failure", response.body) + } + } + + // Make sure that the client doesn't crash if attempting to send invalid JSON + // This is mostly a guard against Java crashing if ObjectStore serializes the wrong + // way by accident. + @Test + fun requestSendsIllegalJson() { + val url = "$baseUrl/okhttp?success=true" + for (method in HTTPMethod.values()) { + val body = if (method == HTTPMethod.GET) "" else "Boom!" + val headers = mapOf( + Pair("Content-Type", "application/json;charset=utf-8"), + Pair("Accept", "application/json") + ) + + val response: OsJavaNetworkTransport.Response = transport.sendRequest(method.nativeKey, + url, + 5000, + headers, + body) + assertEquals(200, response.httpResponseCode) + assertEquals(0, response.customResponseCode) + assertEquals("${method.name}-success", response.body) + } + } + + @Test + fun requestInterrupted() { + val url = "$baseUrl/okhttp?success=true" + for (method in HTTPMethod.values()) { + val body = if (method == HTTPMethod.GET) "" else "{ \"body\" : \"some content\" }" + val headers = mapOf( + Pair("Content-Type", "application/json;charset=utf-8"), + Pair("Accept", "application/json") + ) + + val t = Thread(Runnable { + val response: OsJavaNetworkTransport.Response = transport.sendRequest(method.nativeKey, + url, + 5000, + headers, + body) + assertEquals(0, response.httpResponseCode) + assertEquals(OsJavaNetworkTransport.ERROR_IO, response.customResponseCode) + assertTrue(response.body.contains("interrupted")) + }) + t.start() + // There is a very small chance that the network request already completed when getting + // to here, which would cause the test to fail. Ignore this possibility for now. + t.interrupt() + t.join() + } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt new file mode 100644 index 0000000000..920f1020e9 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -0,0 +1,197 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.transport + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.* +import io.realm.internal.objectstore.OsJavaNetworkTransport +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +/** + * This class is responsible for testing the general network transport layer, i.e. that + * requests can round trip correctly through all layers and that exceptions/errors are reported + * correctly. + * + * This class should _NOT_ test any real network logic. See [OkHttpNetworkTransportTests] for + * tests using the actual network implementation. + */ +@RunWith(AndroidJUnit4::class) +class OsJavaNetworkTransportTests { + + private lateinit var app: RealmApp + private val successHeaders: Map = mapOf(Pair("Content-Type", "application/json")) + + // Test that the round trip works in case of a successful HTTP request. + @Test + fun requestSuccess() { + app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { + var result = "" + if (url.endsWith("/providers/${RealmCredentials.IdentityProvider.ANONYMOUS.id}/login")) { + result = """ + { + "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODM5NjcyMDgsImlhdCI6MTU4Mzk2NTQwOCwiaXNzIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWEzIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWU2OTYzZGVhZmVhNjMyNTQ1ODFjMDI1Iiwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoiYWNjZXNzIn0.J4mp8LnlsxTQRV_7W2Er4qY0tptR76PJGG1k6HSMmUYqgfpJC2Fnbcf1VCoebzoNolH2-sr8AHDVBBCyjxRjqoY9OudFHmWZKmhDV1ysxPP4XmID0nUuN45qJSO8QEAqoOmP1crXjrUZWedFw8aaCZE-bxYfvcDHyjBcbNKZqzawwUw2PyTOlrNjgs01k2J4o5a5XzYkEsJuzr4_8UqKW6zXvYj24UtqnqoYatW5EzpX63m2qig8AcBwPK4ZHb5wEEUdf4QZxkRY5QmTgRHP8SSqVUB_mkHgKaizC_tSB3E0BekaDfLyWVC1taAstXJNfzgFtLI86AzuXS2dCiCfqQ", + "refresh_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODkxNDk0MDgsImlhdCI6MTU4Mzk2NTQwOCwic3RpdGNoX2RhdGEiOm51bGwsInN0aXRjaF9kZXZJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInN0aXRjaF9kb21haW5JZCI6IjVlNjk2M2RlYWZlYTYzMjU0NTgxYzAyNSIsInN0aXRjaF9pZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMyIsInN0aXRjaF9pZGVudCI6eyJpZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMC1oaWF2b3ZkbmJxbGNsYXBwYnl1cmJpaW8iLCJwcm92aWRlcl90eXBlIjoiYW5vbi11c2VyIiwicHJvdmlkZXJfaWQiOiI1ZTY5NjNlMGFmZWE2MzI1NDU4MWMwNGEifSwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoicmVmcmVzaCJ9.FhLdpmL48Mw0SyUKWuaplz3wfeS8TCO8S7I9pIJenQww9nPqQ7lIvykQxjCCtinGvsZIJKt_7R31xYCq4Jp53Nw81By79IwkXtO7VXHPsXXZG5_2xV-s0u44e85sYD5su_H-xnx03sU2piJbWJLSB8dKu3rMD4mO-S0HNXCCAty-JkYKSaM2-d_nS8MNb6k7Vfm7y69iz_uwHc-bb_1rPg7r827K6DEeEMF41Hy3Nx1kCdAUOM9-6nYv3pZSU1PFrGYi2uyTXPJ7R7HigY5IGHWd0hwONb_NUr4An2omqfvlkLEd77ut4V9m6mExFkoKzRz7shzn-IGkh3e4h7ECGA", + "user_id": "5e6964e0afea63254581c1a1", + "device_id": "000000000000000000000000" + } + """.trimIndent() + } else if (url.endsWith("/auth/profile")) { + result = """ + { + "user_id": "5e6964e0afea63254581c1a1", + "domain_id": "000000000000000000000000", + "identities": [ + { + "id": "5e68f51ade5ba998bb17500d", + "provider_type": "local-userpass", + "provider_id": "000000000000000000000003", + "provider_data": { + "email": "unique_user@domain.com" + } + } + ], + "data": { + "email": "unique_user@domain.com" + }, + "type": "normal", + "roles": [ + { + "role_name": "GROUP_OWNER", + "group_id": "5e68f51e087b1b33a53f56d5" + } + ] + } + """.trimIndent() + } else { + fail("Unexpected request url: $url") + } + return Response.httpResponse(200, successHeaders, result) + } + }) + + val creds = RealmCredentials.anonymous() + val user: RealmUser = app.login(creds) + assertNotNull(user) + } + + // Test that the server accepting the result but returns an error is succesfully reported back + // to the user as an exception. + @Test + fun requestFailWithServerError() { + app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { + val result = """ + { + "error": "invalid username/password", + "error_code": "AuthError", + "link": "http://localhost:9090/some_link" + } + """.trimIndent() + return Response.httpResponse(200, successHeaders, result) + } + }) + + val creds = RealmCredentials.emailPassword("foo", "bar") + try { + app.login(creds) + fail() + } catch (ex: ObjectServerError) { + assertEquals(ErrorCode.AUTH_ERROR, ex.errorCode) + assertEquals(ErrorCode.Type.SERVICE, ex.errorType) + } + } + + // Test that the server failing to respond with a non-200 status code returns a proper exception + // to the user. + @Test + fun requestFailWithHttpError() { + app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { + return Response.httpResponse(500, mapOf(), "Boom!") + } + }) + + val creds = RealmCredentials.anonymous() + try { + app.login(creds) + fail() + } catch (ex: ObjectServerError) { + assertEquals(ErrorCode.INTERNAL_SERVER_ERROR, ex.errorCode) + assertEquals(ErrorCode.Type.HTTP, ex.errorType) + } + } + + // Test that custom error codes thrown from the Java transport are correctly reported back to the user. + @Test + fun requestFailWithCustomError() { + app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { + return Response.ioError("Boom!") + } + }) + + val creds = RealmCredentials.anonymous() + try { + app.login(creds) + fail() + } catch (ex: ObjectServerError) { + assertEquals(ErrorCode.JAVA_IO_EXCEPTION, ex.errorCode) + assertEquals(ErrorCode.Type.JAVA, ex.errorType) + } + } + + + // Test that if the Java transport throws an uncaught exception it is correctly returned + // to the user. + @Test + fun requestFailWithTransportException() { + app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { + throw IllegalStateException("Boom!") + } + }) + + val creds = RealmCredentials.anonymous() + try { + app.login(creds) + fail() + } catch (ex: IllegalStateException) { + assertEquals("Boom!", ex.message) + } + } + + // Test that if the Java transport throws a fatal error it is correctly returned to the user. + @Test + fun requestFailWithTransportError() { + app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { + throw Error("Boom!") + } + }) + + val creds = RealmCredentials.anonymous() + try { + app.login(creds) + fail() + } catch (ex: Error) { + assertEquals("Boom!", ex.message) + } + } + +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 5b90396f18..9c0325a98d 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -88,14 +88,17 @@ set(classes_LIST set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST - io.realm.ClientResetRequiredError io.realm.RealmFileUserStore + io.realm.RealmApp io.realm.ClientResetRequiredError io.realm.SyncManager io.realm.SyncSession io.realm.SyncUser io.realm.internal.objectstore.OsAsyncOpenTask + io.realm.internal.objectstore.OsJavaNetworkTransport + io.realm.internal.objectstore.OsAppCredentials + io.realm.internal.objectstore.OsSyncUser ) endif() create_javah(TARGET jni_headers CLASSES ${classes_LIST} - CLASSPATH ${classes_PATH} + CLASSPATH ${classes_PATH} $ENV{ANDROID_HOME}/platforms/android-29/android.jar OUTPUT_DIR ${jni_headers_PATH} DEPENDS ${classes_PATH} ) @@ -170,11 +173,14 @@ file(GLOB jni_SRC # Those source file are only needed for sync. if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmApp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetRequiredError.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFileUserStore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAsyncOpenTask.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAppCredentials.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsSyncUser.cpp ) endif() diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp new file mode 100644 index 0000000000..4e1d6e360e --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_RealmApp.h" + +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" + +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::jni_util; +using namespace realm::_impl; + +JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject obj, + jstring j_app_id, + jstring j_base_url, + jstring j_app_name, + jstring j_app_version, + jlong j_request_timeout_ms) +{ + try { + JavaVM* jvm; + jint ret = env->GetJavaVM(&jvm); + if (ret != 0) { + throw std::runtime_error(util::format("Failed to get Java VM. Error: %d", ret)); + } + jobject java_app_obj = env->NewGlobalRef(obj); // FIXME: Leaking the app object + std::function()> transport_generator = [jvm, java_app_obj] { + JNIEnv* env; + if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { + jvm->AttachCurrentThread(&env, nullptr); // Should never fail + } + static JavaMethod get_network_transport_method(env, java_app_obj, "getNetworkTransport", "()Lio/realm/internal/objectstore/OsJavaNetworkTransport;"); + jobject network_transport_impl = env->CallObjectMethod(java_app_obj, get_network_transport_method); + return std::unique_ptr(new JavaNetworkTransport(jvm, network_transport_impl)); + }; + + JStringAccessor app_id(env, j_app_id); + JStringAccessor base_url(env, j_base_url); + JStringAccessor app_name(env, j_app_name); + JStringAccessor app_version(env, j_app_version); + return reinterpret_cast(new App(App::Config{ + app_id, + transport_generator, + util::Optional(base_url), + util::Optional(app_name), + util::Optional(app_version), + util::Optional(j_request_timeout_ms) + })); + } + CATCH_STD() + return 0; +} + +JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogin(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_credentials_ptr, jobject j_callback) +{ + try { + // Caching callback method ID's in static fields to prevent looking them up more than once + static JavaClass java_callback_class(env, "io/realm/internal/objectstore/OsJavaNetworkTransport$NetworkTransportJNIResultCallback"); + static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); + static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); + + App* app = reinterpret_cast(j_app_ptr); + auto credentials = reinterpret_cast(j_credentials_ptr); + jobject callback = env->NewGlobalRef(j_callback); + app->log_in_with_credentials(*credentials, [&](std::shared_ptr user, Optional error) { + if (error) { + auto err = error.value(); + std::string error_category = err.error_code.category().name(); + env->CallVoidMethod(callback, + java_notify_onerror, + to_jstring(env, error_category), + err.error_code.value(), + to_jstring(env, err.message)); + } else { + auto* java_user = new std::shared_ptr(std::move(user)); + jobject ptr_value = JavaClassGlobalDef::new_long(env, reinterpret_cast(java_user)); + env->CallVoidMethod(callback, java_notify_onsuccess, ptr_value); + } + env->DeleteGlobalRef(callback); + }); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp deleted file mode 100644 index 7beed1a127..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFileUserStore.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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 "io_realm_RealmFileUserStore.h" - -#include -#include - -#include "java_class_global_def.hpp" -#include "util.hpp" -#include "jni_util/log.hpp" -#include "object-store/src/sync/sync_user.hpp" - -using namespace realm; -using namespace realm::_impl; - -static const char* ERR_COULD_NOT_ALLOCATE_MEMORY = "Could not allocate memory to return all users."; - -static jstring to_user_string_or_null(JNIEnv* env, const std::shared_ptr& user) -{ - if (user) { - return to_jstring(env, user->refresh_token().data()); - } - else { - return nullptr; - } -} - -static SyncUserIdentity create_sync_user_identifier(JNIEnv* env, jstring j_user_id, jstring j_auth_url) -{ - JStringAccessor user_id(env, j_user_id); // throws - JStringAccessor auth_url(env, j_auth_url); // throws - return {user_id, auth_url}; -} - -JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetCurrentUser(JNIEnv* env, jclass) -{ - try { - auto user = SyncManager::shared().get_current_user(); - return to_user_string_or_null(env, user); - } - CATCH_STD() - return nullptr; -} - -JNIEXPORT jstring JNICALL Java_io_realm_RealmFileUserStore_nativeGetUser(JNIEnv* env, jclass, jstring j_user_id, - jstring j_auth_url) -{ - try { - SyncUserIdentity user_identifier = create_sync_user_identifier(env, j_user_id, j_auth_url); - std::shared_ptr user = SyncManager::shared().get_existing_logged_in_user(user_identifier.id); - return to_user_string_or_null(env, user); - } - CATCH_STD() - return nullptr; -} - -JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeUpdateOrCreateUser(JNIEnv* env, jclass, - jstring j_user_id, - jstring /*j_refresh_json_token*/, - jstring j_auth_url) -{ - try { - // FIXME Replace in RealmApp refactor - JStringAccessor id(env, j_user_id); - JStringAccessor auth_url(env, j_auth_url); - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODE1MDc3OTYsImlhdCI6MTU4MTUwNTk5NiwiaXNzIjoiNWU0M2RkY2M2MzZlZTEwNmVhYTEyYmRjIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWUxNDk5MTNjOTBiNGFmMGViZTkzNTI3Iiwic3ViIjoiNWU0M2RkY2M2MzZlZTEwNmVhYTEyYmRhIiwidHlwIjoiYWNjZXNzIn0.0q3y9KpFxEnbmRwahvjWU1v9y1T1s3r2eozu93vMc3s"; - SyncManager::shared().get_user(id, auth_url, token, token); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_RealmFileUserStore_nativeLogoutUser(JNIEnv* env, jclass, jstring j_user_id, - jstring /*j_auth_url*/) -{ - try { - // FIXME Replace in RealmApp refactor - JStringAccessor id(env, j_user_id); - auto user = SyncManager::shared().get_existing_logged_in_user(id); - if (user) { - user->log_out(); - } - } - CATCH_STD() -} - -JNIEXPORT jboolean JNICALL Java_io_realm_RealmFileUserStore_nativeIsActive(JNIEnv* env, jclass, jstring j_user_id, - jstring /*j_auth_url*/) -{ - try { - // FIXME Replace in RealmApp refactor - JStringAccessor id(env, j_user_id); - auto user = SyncManager::shared().get_existing_logged_in_user(id); - if (user) { - return to_jbool(user->state() == SyncUser::State::Active); - } - } - CATCH_STD() - return JNI_FALSE; -} - -JNIEXPORT jobjectArray JNICALL Java_io_realm_RealmFileUserStore_nativeGetAllUsers(JNIEnv* env, jclass) -{ - auto all_users = SyncManager::shared().all_users(); - if (!all_users.empty()) { - size_t len = all_users.size(); - jobjectArray users_token = env->NewObjectArray(len, JavaClassGlobalDef::java_lang_string(), 0); - if (users_token == nullptr) { - ThrowException(env, OutOfMemory, ERR_COULD_NOT_ALLOCATE_MEMORY); - return nullptr; - } - for (size_t i = 0; i < len; ++i) { - env->SetObjectArrayElement(users_token, i, to_jstring(env, all_users[i]->refresh_token().data())); - } - - return users_token; - } - return nullptr; -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp new file mode 100644 index 0000000000..a4f0cd456c --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_internal_objectstore_OsAppCredentials.h" + +#include "util.hpp" + +#include + +using namespace realm; +using namespace realm::app; + +static void finalize_credentials(jlong ptr) +{ + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) +{ + return reinterpret_cast(&finalize_credentials); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nativeCreate(JNIEnv* env, jclass, jint j_type, jobjectArray j_args) +{ + try { + AppCredentials creds = AppCredentials::anonymous(); // Is there a way to avoid setting this to a specific value? + switch(j_type) { + case io_realm_internal_objectstore_OsAppCredentials_TYPE_ANONYMOUS: + /* Default, do nothing */; + break; + case io_realm_internal_objectstore_OsAppCredentials_TYPE_FACEBOOK: { + JStringAccessor access_token(env, (jstring) env->GetObjectArrayElement(j_args, 0)); + creds = AppCredentials::facebook(access_token); + break; + } + case io_realm_internal_objectstore_OsAppCredentials_TYPE_EMAIL_PASSWORD: { + JStringAccessor email(env, (jstring) env->GetObjectArrayElement(j_args, 0)); + JStringAccessor password(env, (jstring) env->GetObjectArrayElement(j_args, 1)); + creds = AppCredentials::username_password(email, password); + break; + } + case io_realm_internal_objectstore_OsAppCredentials_TYPE_APPLE: { + JStringAccessor id_token(env, (jstring) env->GetObjectArrayElement(j_args, 0)); + creds = AppCredentials::apple(id_token); + break; + } + case io_realm_internal_objectstore_OsAppCredentials_TYPE_API_KEY: + case io_realm_internal_objectstore_OsAppCredentials_TYPE_CUSTOM_FUNCTION: + case io_realm_internal_objectstore_OsAppCredentials_TYPE_GOOGLE: + case io_realm_internal_objectstore_OsAppCredentials_TYPE_JWT: + default: + throw std::runtime_error(util::format("Unknown credentials type: %1", j_type)); + } + return reinterpret_cast(new AppCredentials(std::move(creds))); + } + CATCH_STD() + return 0; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nativeGetProvider(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto credentials = reinterpret_cast(j_native_ptr); + std::string provider = credentials->provider_as_string(); + return to_jstring(env, provider); + } + CATCH_STD() + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nativeAsJson(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto credentials = reinterpret_cast(j_native_ptr); + std::string json = credentials->serialize_as_json(); + return to_jstring(env, json); + } + CATCH_STD() + return 0; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp new file mode 100644 index 0000000000..d2a6b6a7fb --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_internal_objectstore_OsSyncUser.h" + +#include "java_class_global_def.hpp" +#include "util.hpp" +#include "jni_util/java_class.hpp" + +#include + +using namespace realm; +using namespace realm::_impl; +using namespace realm::jni_util; + +static void finalize_user(jlong ptr) +{ + delete reinterpret_cast*>(ptr); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) +{ + return reinterpret_cast(&finalize_user); +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetName(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->user_profile().name); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetEmail(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->user_profile().email); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetPictureUrl(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->user_profile().picture_url); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetFirstName(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->user_profile().first_name); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetLastName(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->user_profile().last_name); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetGender(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->user_profile().gender); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetBirthDay(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->user_profile().birthday); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetMinAge(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->user_profile().min_age); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetMaxAge(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->user_profile().max_age); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetAccessToken(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + std::string token = user->access_token(); + return to_jstring(env, token); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetRefreshToken(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + std::string token = user->refresh_token(); + return to_jstring(env, token); + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jobjectArray JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetIdentities(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + std::vector ids = user->identities(); + jobjectArray arr = env->NewObjectArray(ids.size()*2, JavaClassGlobalDef::java_lang_string(), 0); + if (arr == NULL) { + ThrowException(env, OutOfMemory, "Could not allocate memory to return identites"); + return NULL; + } + int j = 0; + for(size_t i = 0; i < ids.size(); ++i) { + SyncUserIdentity id = ids[i]; + env->SetObjectArrayElement( arr, j, to_jstring(env, id.id)); + env->SetObjectArrayElement( arr, j+1, to_jstring(env, id.provider_type)); + j = j+2; + } + return arr; + } + CATCH_STD(); + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetIdentity(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->identity()); + } + CATCH_STD(); + return nullptr; +} + diff --git a/realm/realm-library/src/main/cpp/java_network_transport.hpp b/realm/realm-library/src/main/cpp/java_network_transport.hpp new file mode 100644 index 0000000000..6d0a32205d --- /dev/null +++ b/realm/realm-library/src/main/cpp/java_network_transport.hpp @@ -0,0 +1,128 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JAVA_NETWORK_TRANSPORT +#define REALM_JAVA_NETWORK_TRANSPORT + +#include "java_accessor.hpp" +#include "util.hpp" +#include "sync/generic_network_transport.hpp" +#include "jni_util/java_class.hpp" +#include "jni_util/java_method.hpp" + +using namespace realm::app; +using namespace realm::jni_util; +using namespace realm::_impl; + +namespace realm { + +struct JavaNetworkTransport : public app::GenericNetworkTransport { + + JavaNetworkTransport(JavaVM* vm, jobject java_network_transport_impl) { + m_jvm = vm; + JNIEnv* env = get_current_env(); + m_java_network_transport_impl = env->NewGlobalRef(java_network_transport_impl); + jclass cls = env->GetObjectClass(m_java_network_transport_impl); + auto method_name = "sendRequest"; + auto signature = "(Ljava/lang/String;Ljava/lang/String;JLjava/util/Map;Ljava/lang/String;)Lio/realm/internal/objectstore/OsJavaNetworkTransport$Response;"; + m_send_request_method = env->GetMethodID(cls, method_name, signature); + REALM_ASSERT_RELEASE_EX(m_send_request_method != nullptr, method_name, signature); + } + + void send_request_to_server(const app::Request request, std::function completionBlock) + { + JNIEnv* env = get_current_env(); + + // Setup method + std::string method; + switch(request.method) { + case app::HttpMethod::get: method = "get"; break; + case app::HttpMethod::post: method = "post"; break; + case app::HttpMethod::patch: method = "patch"; break; + case app::HttpMethod::put: method = "put"; break; + case app::HttpMethod::del: method = "delete"; break; + } + + // Create headers + static JavaClass mapClass(env, "java/util/HashMap"); + static JavaMethod init(env, mapClass, "", "(I)V"); + static JavaMethod put_method(env, mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + size_t map_size = request.headers.size(); + jobject request_headers = env->NewObject(mapClass, init, (jsize) map_size); + for (auto header : request.headers) { + env->CallObjectMethod(request_headers, put_method, to_jstring(env, header.first), to_jstring(env, header.second)); + } + + // Execute network request on the Java side + jobject response = env->CallObjectMethod(m_java_network_transport_impl, + m_send_request_method, + to_jstring(env, method), + to_jstring(env, request.url), + static_cast(request.timeout_ms), + request_headers, + to_jstring(env, request.body) + ); + env->DeleteLocalRef(request_headers); + + if (env->ExceptionCheck()) { + // This should not happen. All exceptions should ideally have been caught by Java + // and turned into a realm::app::Response object. If this happened just + // let the Java exception bubble up. + return; + } else { + // Read response + static JavaClass responseClass(env, "io/realm/internal/objectstore/OsJavaNetworkTransport$Response"); + static JavaMethod get_http_code_method(env, responseClass, "getHttpResponseCode", "()I"); + static JavaMethod get_custom_code_method(env, responseClass, "getCustomResponseCode", "()I"); + static JavaMethod get_headers_method(env, responseClass, "getJNIFriendlyHeaders", "()[Ljava/lang/String;"); + static JavaMethod get_body_method(env, responseClass, "getBody", "()Ljava/lang/String;"); + + jint http_code = env->CallIntMethod(response, get_http_code_method); + jint custom_code = env->CallIntMethod(response, get_custom_code_method); + JStringAccessor java_body(env, (jstring) env->CallObjectMethod(response, get_body_method)); + JObjectArrayAccessor java_headers(env, static_cast(env->CallObjectMethod(response, get_headers_method))); + auto response_headers = std::map(); + for (int i = 0; i < java_headers.size(); i = i + 2) { + JStringAccessor key = java_headers[i]; + JStringAccessor value = java_headers[i+1]; + response_headers.insert(std::pair(key,value)); + } + std::string body = java_body; + completionBlock(Response{(int) http_code, (int) custom_code, response_headers, body}); + } + } + + ~JavaNetworkTransport() { + get_current_env()->DeleteGlobalRef(m_java_network_transport_impl); + } + +private: + JavaVM* m_jvm; + jobject m_java_network_transport_impl; // Global ref of Java implementation of the network transport. + jmethodID m_send_request_method; + inline JNIEnv* get_current_env() noexcept + { + JNIEnv* env; + if (m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { + m_jvm->AttachCurrentThread(&env, nullptr); // Should never fail + } + return env; + } +}; + +} // realm namespace + +#endif diff --git a/realm/realm-library/src/main/cpp/jni_util/java_method.cpp b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp index 2cb559750e..30c65be4cf 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_method.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_method.cpp @@ -32,3 +32,10 @@ JavaMethod::JavaMethod(JNIEnv* env, JavaClass const& cls, const char* method_nam REALM_ASSERT_RELEASE_EX(m_method_id != nullptr, method_name, signature); } + +JavaMethod::JavaMethod(JNIEnv* env, jobject const& obj, const char* method_name, const char* signature) +{ + jclass cls = env->GetObjectClass(obj); + m_method_id = env->GetMethodID(cls, method_name, signature); + REALM_ASSERT_RELEASE_EX(m_method_id != nullptr, method_name, signature); +} diff --git a/realm/realm-library/src/main/cpp/jni_util/java_method.hpp b/realm/realm-library/src/main/cpp/jni_util/java_method.hpp index 0bcc80172a..73625b31da 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_method.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_method.hpp @@ -31,9 +31,13 @@ class JavaMethod { : m_method_id(nullptr) { } + // Lookup a method on a named class JavaMethod(JNIEnv* env, JavaClass const& cls, const char* method_name, const char* signature, bool static_method = false); + // Lookup a method on a object from Java + JavaMethod(JNIEnv* env, jobject const& cls, const char* method_name, const char* signature); + // From https://developer.android.com/training/articles/perf-jni.html // The class references, field IDs, and method IDs are guaranteed valid until the class is unloaded. Classes are // only unloaded if all classes associated with a ClassLoader can be garbage collected, which is rare but will not diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index 4220636b41..a8329ba7b4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -17,9 +17,9 @@ package io.realm; -import java.io.IOException; import java.util.Locale; +import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.log.RealmLog; /** @@ -29,14 +29,18 @@ public enum ErrorCode { // See Client::Error in https://github.com/realm/realm-sync/blob/master/src/realm/sync/client.hpp#L1230 // See https://github.com/realm/realm-sync/blob/develop/src/realm/sync/protocol.hpp + // See https://github.com/realm/realm-object-store/blob/v10/src/sync/generic_network_transport.hpp#L47 // Catch-all // The underlying type and error code should be part of the error message UNKNOWN(Type.UNKNOWN, -1), // Realm Java errors - IO_EXCEPTION(Type.JAVA, 0, Category.RECOVERABLE), // Some IO error while either contacting the server or reading the response - JSON_EXCEPTION(Type.AUTH, 1), // JSON input could not be parsed correctly + JAVA_IO_EXCEPTION(Type.JAVA, OsJavaNetworkTransport.ERROR_IO), + JAVA_INTERRUPTED(Type.JAVA, OsJavaNetworkTransport.ERROR_INTERRUPTED), + JAVA_UNKNOWN(Type.JAVA, OsJavaNetworkTransport.ERROR_UNKNOWN), + + // Custom Object Store errors CLIENT_RESET(Type.PROTOCOL, 7), // Client Reset required. Don't change this value without modifying io_realm_internal_OsSharedRealm.cpp // Connection level and protocol errors from the native Sync Client @@ -128,7 +132,7 @@ public enum ErrorCode { USE_PROXY(Type.HTTP, 305), TEMPORARY_REDIRECT(Type.HTTP, 307), PERMANENT_REDIRECT(Type.HTTP, 308), - BAD_REQUEST(Type.HTTP, 400), + HTTP_BAD_REQUEST(Type.HTTP, 400), UNAUTHORIZED(Type.HTTP, 401), PAYMENT_REQUIRED(Type.HTTP, 402), FORBIDDEN(Type.HTTP, 403), @@ -167,24 +171,58 @@ public enum ErrorCode { NOT_EXTENDED(Type.HTTP, 510), NETWORK_AUTHENTICATION_REQUIRED(Type.HTTP, 511), - // Realm Authentication Server response errors (600 - 699) - INVALID_PARAMETERS(Type.AUTH, 601), - MISSING_PARAMETERS(Type.AUTH, 602), - INVALID_CREDENTIALS(Type.AUTH, 611), - UNKNOWN_ACCOUNT(Type.AUTH, 612), - EXISTING_ACCOUNT(Type.AUTH, 613), - ACCESS_DENIED(Type.AUTH, 614), - EXPIRED_REFRESH_TOKEN(Type.AUTH, 615), - INVALID_HOST(Type.AUTH, 616), - REALM_NOT_FOUND(Type.AUTH, 617), - UNKNOWN_USER(Type.AUTH, 618), - WRONG_REALM_TYPE(Type.AUTH, 619), // The Realm found on the server is of different type than the one requested. - - // Other Realm Object Server response errors - EXPIRED_PERMISSION_OFFER(Type.AUTH, 701), - AMBIGUOUS_PERMISSION_OFFER_TOKEN(Type.AUTH, 702), - FILE_MAY_NOT_BE_SHARED(Type.AUTH, 703), - SERVER_MISCONFIGURATION(Type.AUTH, 801), + // MongoDB Realm Service Response codes + INVALID_SESSION(Type.SERVICE, 2), + USER_APP_DOMAIN_MISMATCH(Type.SERVICE, 3), + DOMAIN_NOT_ALLOWED(Type.SERVICE, 4), + READ_SIZE_LIMIT_EXCEEDED(Type.SERVICE, 5), + INVALID_PARAMETER(Type.SERVICE, 6), + MISSING_PARAMETER(Type.SERVICE, 7), + TWILIO_ERROR(Type.SERVICE, 8), + GCM_ERROR(Type.SERVICE, 9), + HTTP_ERROR(Type.SERVICE, 10), + AWS_ERROR(Type.SERVICE, 11), + MONGODB_ERROR(Type.SERVICE, 12), + ARGUMENTS_NOT_ALLOWED(Type.SERVICE, 13), + FUNCTION_EXECUTION_ERROR(Type.SERVICE, 14), + NO_MATCHING_RULE_FOUND(Type.SERVICE, 15), + SERVICE_INTERNAL_SERVER_ERROR(Type.SERVICE, 16), + AUTH_PROVIDER_NOT_FOUND(Type.SERVICE, 17), + AUTH_PROVIDER_ALREADY_EXISTS(Type.SERVICE, 18), + SERVICE_NOT_FOUND(Type.SERVICE, 19), + SERVICE_TYPE_NOT_FOUND(Type.SERVICE, 20), + SERVICE_ALREADY_EXISTS(Type.SERVICE, 21), + SERVICE_COMMAND_NOT_FOUND(Type.SERVICE, 22), + VALUE_NOT_FOUND(Type.SERVICE, 23), + VALUE_ALREADY_EXISTS(Type.SERVICE, 24), + VALUE_DUPLICATE_NAME(Type.SERVICE, 25), + FUNCTION_NOT_FOUND(Type.SERVICE, 26), + FUNCTION_ALREADY_EXISTS(Type.SERVICE, 27), + FUNCTION_DUPLICATE_NAME(Type.SERVICE, 28), + FUNCTION_SYNTAX_ERROR(Type.SERVICE, 29), + FUNCTION_INVALID(Type.SERVICE, 30), + INCOMING_WEBHOOK_NOT_FOUND(Type.SERVICE, 31), + INCOMING_WEBHOOK_ALREADY_EXISTS(Type.SERVICE, 32), + INCOMING_WEBHOOK_DUPLICATE_NAME(Type.SERVICE, 33), + RULE_NOT_FOUND(Type.SERVICE, 34), + API_KEY_NOT_FOUND(Type.SERVICE, 35), + RULE_ALREADY_EXISTS(Type.SERVICE, 36), + RULE_DUPLICATE_NAME(Type.SERVICE, 37), + AUTH_PROVIDER_DUPLICATE_NAME(Type.SERVICE, 38), + RESTRICTED_HOST(Type.SERVICE, 39), + API_KEY_ALREADY_EXISTS(Type.SERVICE, 40), + INCOMING_WEBHOOK_AUTH_FAILED(Type.SERVICE, 41), + EXECUTION_TIME_LIMIT_EXCEEDED(Type.SERVICE, 42), + NOT_CALLABLE(Type.SERVICE, 43), + USER_ALREADY_CONFIRMED(Type.SERVICE, 44), + USER_NOT_FOUND(Type.SERVICE, 45), + USER_DISABLED(Type.SERVICE, 46), + AUTH_ERROR(Type.SERVICE, 47), + BAD_REQUEST(Type.SERVICE, 48), + ACCOUNT_NAME_IN_USE(Type.SERVICE, 49), + + SERVICE_UNKNOWN(Type.SERVICE, -1), + SERVICE_NONE(Type.SERVICE, 0), // Generic system errors we want to enumerate specifically CONNECTION_RESET_BY_PEER(Type.CONNECTION, 104, Category.RECOVERABLE), // ECONNRESET: Connection reset by peer @@ -276,27 +314,15 @@ public static ErrorCode fromNativeError(String type, int errorCode) { return UNKNOWN; } - /** - * Helper method for mapping between {@link Exception} and {@link ErrorCode}. - * @param exception to be mapped as an {@link ErrorCode}. - * @return mapped {@link ErrorCode}. - */ - public static ErrorCode fromException(Exception exception) { - // IOException are recoverable (with exponential backoff) - if (exception instanceof IOException) { - return ErrorCode.IO_EXCEPTION; - } else { - return ErrorCode.UNKNOWN; - } - } - public static class Type { public static final String AUTH = "auth"; // Errors from the Realm Object Server public static final String CONNECTION = "realm.basic_system"; // Connection/System errors from the native Sync Client public static final String DEPRECATED = "deprecated"; // Deprecated errors - public static final String HTTP = "http"; // Errors from the HTTP layer - public static final String JAVA = "java"; // Errors from the Java layer + public static final String HTTP = "realm::app::HttpError"; // Errors from the HTTP layer + public static final String JAVA = "realm::app::CustomError"; // Errors from the Java layer public static final String MISC = "realm.util.misc_ext"; // Misc errors from the native Sync Client + public static final String SERVICE = "realm::app::ServiceError"; // MongoDB Realm Response errors + public static final String JSON = "realm::app::JSONError"; // Errors when parsing JSON public static final String PROTOCOL = "realm::sync::ProtocolError"; // Protocol level errors from the native Sync Client public static final String SESSION = "realm::sync::Client::Error"; // Session level errors from the native Sync Client public static final String UNKNOWN = "unknown"; // Catch-all category diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java index 49b1317a8e..07d6fc980d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java @@ -89,9 +89,6 @@ public static void init(Context context, String appDefinedUserAgent) { SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath(), userAgentBindingInfo, appDefinedUserAgent); } - // Configure default UserStore - UserStore userStore = new RealmFileUserStore(); - - SyncManager.init(appId, userStore); + SyncManager.init(appId); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java new file mode 100644 index 0000000000..476e338c16 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -0,0 +1,411 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.Nullable; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.internal.RealmNotifier; +import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.android.AndroidRealmNotifier; +import io.realm.internal.async.RealmAsyncTaskImpl; +import io.realm.internal.async.RealmThreadPoolExecutor; +import io.realm.internal.network.OkHttpNetworkTransport; +import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.log.RealmLog; +import io.realm.mongodb.RealmMongoDBService; + +/** + * FIXME + */ +public class RealmApp { + + // Implementation notes: + // The public API's currently only allow for one RealmApp, however this is a restriction + // we might want to lift in the future. So any implementation details so ideally be made + // with that in mind, i.e. keep static state to minimum. + + // Default session error handler that just output errors to LogCat + private static final SyncSession.ErrorHandler SESSION_NO_OP_ERROR_HANDLER = new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { + RealmLog.error("Client Reset required for: " + session.getConfiguration().getServerUrl()); + return; + } + + String errorMsg = String.format(Locale.US, "Session Error[%s]: %s", + session.getConfiguration().getServerUrl(), + error.toString()); + switch (error.getErrorCode().getCategory()) { + case FATAL: + RealmLog.error(errorMsg); + break; + case RECOVERABLE: + RealmLog.info(errorMsg); + break; + default: + throw new IllegalArgumentException("Unsupported error category: " + error.getErrorCode().getCategory()); + } + } + }; + + /** + * Thread pool used when doing network requests against MongoDB Realm. + *

            + * This pool is only exposed for testing purposes and replacing it while the queue is not + * empty will result in undefined behaviour. + */ + @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + public static ThreadPoolExecutor NETWORK_POOL_EXECUTOR = RealmThreadPoolExecutor.newDefaultExecutor(); + + private final RealmAppConfiguration config; + private OsJavaNetworkTransport networkTransport; + private final long nativePtr; + private CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList<>(); + + public RealmApp(String appId) { + this(new RealmAppConfiguration.Builder(appId).build()); + } + + /** + * FIXME + * @param config + */ + public RealmApp(RealmAppConfiguration config) { + this.config = config; + this.networkTransport = new OkHttpNetworkTransport(); + this.nativePtr = nativeCreate( + config.getAppId(), + config.getBaseUrl(), + config.getAppName(), + config.getAppVersion(), + config.getRequestTimeoutMs()); + } + + /** + * Returns the current user that is logged in and still valid. + * A user is invalidated when he/she logs out or the user's refresh token expires or is revoked. + *

            + * If two or more users are logged in, it is the last valid user that is returned by this method. + * + * @return current {@link RealmUser} that has logged in and is still valid. {@code null} if no + * user is logged in or the user has expired. + */ + @Nullable + public RealmUser currentUser() { + Long userPtr = nativeCurrentUser(nativePtr); + return (userPtr != null) ? new RealmUser(userPtr) : null; + } + + /** + * FIXME + * Returns all currently logged in users + * @return + */ + public Map allUsers() { + long[] nativeUsers = nativeAllUsers(nativePtr); + HashMap users = new HashMap<>(nativeUsers.length); + for (int i = 0; i < nativeUsers.length; i++) { + RealmUser user = new RealmUser(nativeUsers[i]); + users.put(user.getId(), user); + } + return users; + } + + /** + * TODO: Manually set the user returned by {@link #currentUser()} + * + * @param user + */ + public static void setCurrentUser(SyncUser user) { + // FIXME + } + + /** + * FIXME + * + * @param credentials + * @return + * @throws ObjectServerError + */ + public RealmUser login(RealmCredentials credentials) throws ObjectServerError { + checkNull(credentials, "credentials"); + AtomicReference user = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + nativeLogin(nativePtr, credentials.osCredentials.getNativePtr(), new OsJavaNetworkTransport.NetworkTransportJNIResultCallback() { + @Override + public void onSuccess(Object result) { + Long nativePtr = (Long) result; + user.set(new RealmUser(nativePtr)); + } + @Override + public void onError(String nativeErrorCategory, int nativeErrorCode, String errorMessage) { + ErrorCode code = ErrorCode.fromNativeError(nativeErrorCategory, nativeErrorCode); + if (code == ErrorCode.UNKNOWN) { + // In case of UNKNOWN errors parse as much error information on as possible. + String detailedErrorMessage = String.format("{%s::%s} %s", nativeErrorCategory, nativeErrorCode, errorMessage); + error.set(new ObjectServerError(code, detailedErrorMessage)); + } else { + error.set(new ObjectServerError(code, errorMessage)); + } + } + }); + + // ObjectStore runs all code in the same thread even though it is using a callback. + // So results should be available here. + if (user.get() == null && error.get() == null) { + throw new IllegalStateException("Network result callback did not trigger correctly"); + } + if (user.get() != null) { + return user.get(); + } else { + throw error.get(); + } + } + + /** + * FIXME + * @param credentials + * @param callback + * @return + */ + public RealmAsyncTask loginAsync(RealmCredentials credentials, Callback callback) { + checkLooperThread("Asynchronous login is only possible from looper threads."); + return new Request(NETWORK_POOL_EXECUTOR, callback) { + @Override + public RealmUser run() throws ObjectServerError { + return login(credentials); + } + }.start(); + } + + public static void logout(RealmUser user) { + + } + public RealmAsyncTask logoutAsync(RealmUser user, Callback callback) { + return null; + } + + public RealmUser registerWithEmail(String email, String password) { + return null; + } + public RealmAsyncTask registerWithEmailAsync(String email, String password, Callback callback) { + return null; + } + public RealmUser confirmUser(String token, String tokenId) { + return null; + } + public RealmAsyncTask confirmUserAsync(String token, String tokenId, Callback callback) { + return null; + } + public void resendConfirmationEmail(String email) { + } + public RealmAsyncTask resendConfirmationEmailAsync(String email, Callback callback) { + return null; + } + public RealmUser resetPassword(String token, String tokenId, String password) { + return null; + } + public RealmAsyncTask resetPasswordAsync(String token, String tokenId, String password, Callback callback) { + return null; + } + public RealmUser sendResetPasswordEmail(String email) { + return null; + } + public RealmAsyncTask sendResetPasswordEmailAsync(String email, Callback callback) { + return null; + } + + public SyncSession getSyncSession(SyncConfiguration config) { + return null; + } + + public void refreshConnections() { + + } + + /** + * Sets a global authentication listener that will be notified about User events like + * login and logout. + * + * @param listener listener to register. + * @throws IllegalArgumentException if {@code listener} is {@code null}. + */ + public void addAuthenticationListener(AuthenticationListener listener) { + //noinspection ConstantConditions + if (listener == null) { + throw new IllegalArgumentException("Non-null 'listener' required."); + } + authListeners.add(listener); + } + + + /** + * Removes the provided global authentication listener. + * + * @param listener listener to remove. + */ + public void removeAuthenticationListener(AuthenticationListener listener) { + //noinspection ConstantConditions + if (listener == null) { + return; + } + authListeners.remove(listener); + } + + // Services entry point + public RealmFunctions getFunctions() { + // FIXME + return null; + } + + public RealmPushNotifications getFSMPushNotifications() { + // FIXME + return null; + + } + + public RealmMongoDBService getMongoDBService() { + // FIXME + return null; + } + + // Private API's for now. + + /** + * Exposed for testing. + * + * Swap the currently configured network transport with the provided one. + * This should only be done if no network requests are currently running. + */ + void setNetworkTransport(OsJavaNetworkTransport transport) { + networkTransport = transport; + } + + OsJavaNetworkTransport getNetworkTransport() { + return networkTransport; + } + + private static void checkLooperThread(String errorMessage) { + AndroidCapabilities capabilities = new AndroidCapabilities(); + capabilities.checkCanDeliverNotification(errorMessage); + } + + private void checkNull(@Nullable Object argValue, String argName) { + if (argValue == null) { + throw new IllegalArgumentException("Nonnull '" + argName + "' required."); + } + } + + // Class wrapping requests made against MongoDB Realm. Is also responsible for calling with success/error on the + // correct thread. + private static abstract class Request { + @Nullable + private final RealmApp.Callback callback; + private final RealmNotifier handler; + private final ThreadPoolExecutor networkPoolExecutor; + + Request(ThreadPoolExecutor networkPoolExecutor, @Nullable RealmApp.Callback callback) { + this.callback = callback; + this.handler = new AndroidRealmNotifier(null, new AndroidCapabilities()); + this.networkPoolExecutor = networkPoolExecutor; + } + + // Implements the request. Return the current sync user if the request succeeded. Otherwise throw an error. + public abstract T run() throws ObjectServerError; + + // Start the request + public RealmAsyncTask start() { + Future authenticateRequest = networkPoolExecutor.submit(new Runnable() { + @Override + public void run() { + try { + postSuccess(Request.this.run()); + } catch (ObjectServerError e) { + postError(e); + } catch (Throwable e) { + postError(new ObjectServerError(ErrorCode.UNKNOWN, "Unexpected error", e)); + } + } + }); + return new RealmAsyncTaskImpl(authenticateRequest, networkPoolExecutor); + } + + private void postError(final ObjectServerError error) { + boolean errorHandled = false; + if (callback != null) { + Runnable action = new Runnable() { + @Override + public void run() { + callback.onError(error); + } + }; + errorHandled = handler.post(action); + } + + if (!errorHandled) { + RealmLog.error(error, "An error was thrown, but could not be posted: \n" + error.toString()); + } + } + + private void postSuccess(final T result) { + if (callback != null) { + handler.post(new Runnable() { + @Override + public void run() { + callback.onSuccess(result); + } + }); + } + } + } + + /** + * Callback for async methods available to the {@link RealmApp}. + * + * @param Type returned if the request was a success. + */ + public interface Callback { + /** + * The request was a success. + * @param t The object representing the successful request. See each method for details. + */ + void onSuccess(T t); + + /** + * The request failed for some reason, either because there was a network error or the Realm + * Object Server returned an error. + * + * @param error the error that was detected. + */ + void onError(ObjectServerError error); + } + + private native long nativeCreate(String appId, String baseUrl, String appName, String appVersion, long requestTimeoutMs); + private static native void nativeLogin(long nativeAppPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + @Nullable + private static native Long nativeCurrentUser(long nativePtr); + private static native long[] nativeAllUsers(long nativePtr); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java new file mode 100644 index 0000000000..70a3ad1e56 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java @@ -0,0 +1,256 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.content.Context; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +import io.realm.log.LogLevel; + +/** + * FIXME + */ +public class RealmAppConfiguration { + + private final String appId; + private final String appName; + private final String appVersion; + private final String baseUrl; + private final Context context; + private final SyncSession.ErrorHandler defaultErrorHandler; + @Nullable private final byte[] encryptionKey; + private final long logLevel; + private final long requestTimeoutMs; + + private RealmAppConfiguration(String appId, + String appName, + String appVersion, + String baseUrl, + Context context, + SyncSession.ErrorHandler defaultErrorHandler, + @Nullable byte[] encryptionKey, + long logLevel, + long requestTimeoutMs) { + + this.appId = appId; + this.appName = appName; + this.appVersion = appVersion; + this.baseUrl = baseUrl; + this.context = context; + this.defaultErrorHandler = defaultErrorHandler; + this.encryptionKey = (encryptionKey == null) ? null : Arrays.copyOf(encryptionKey, encryptionKey.length); + this.logLevel = logLevel; + this.requestTimeoutMs = requestTimeoutMs; + } + + /** + * FIXME + * @return + */ + public String getAppId() { + return appId; + } + + /** + * FIXME + * @return + */ + public String getAppName() { + return appName; + } + + /** + * FIXME + * @return + */ + public String getAppVersion() { + return appVersion; + } + + /** + * FIXME + * @return + */ + public String getBaseUrl() { + return baseUrl; + } + + /** + * FIXME + * @return + */ + public Context getContext() { + return context; + } + + /** + * FIXME + * @return + */ + public SyncSession.ErrorHandler getDefaultErrorHandler() { + return defaultErrorHandler; + } + + /** + * FIXME + * @return + */ + public byte[] getEncryptionKey() { + return encryptionKey == null ? null : Arrays.copyOf(encryptionKey, encryptionKey.length); + } + + /** + * FIXME + * @return + */ + public long getLogLevel() { + return logLevel; + } + + /** + * FIXME + * @return + */ + public long getRequestTimeoutMs() { + return requestTimeoutMs; + } + + /** + * FIXME + */ + public static class Builder { + private String appId; + private String appName; + private String appVersion; + private String baseUrl; + private Context context; + private SyncSession.ErrorHandler defaultErrorHandler; + private byte[] encryptionKey; + private long logLevel = LogLevel.WARN; // FIXME: Consider what this should be set at + private long requestTimeoutMs = 60000; + + /** + * FIXME + * + * @param appId + */ + public Builder(String appId) { + // FIXME: Null checks + this.context = Realm.applicationContext; + this.appId = appId; + } + + /** + * FIXME + * + * @param level + * @return + */ + public Builder logLevel(int level) { + // FIXME: Boundary checks + this.logLevel = level; + return this; + } + + /** + * FIXME + * + * @param key + * @return + */ + public Builder encryptionKey(byte[] key) { + this.encryptionKey = Arrays.copyOf(key, key.length); + return this; + } + + /** + * FIXME + * + * @param baseUrl + * @return + */ + public Builder baseUrl(String baseUrl) { + // FIXME Check input + this.baseUrl = baseUrl; + return this; + } + + /** + * FIXME + * + * @param appName + * @return + */ + public Builder appName(String appName) { + // FIXME CHecks + this.appName = appName; + return this; + } + + /** + * FIXME + * + * @param appVersion + * @return + */ + public Builder appVersion(String appVersion) { + // FIXME checks + this.appVersion = appVersion; + return this; + } + + /** + * FIXME + * + * @param errorHandler + * @return + */ + public Builder defaultSessionErrorHandler(@Nullable SyncSession.ErrorHandler errorHandler) { + // FIXME checks + this.defaultErrorHandler = errorHandler; + return this; + } + + /** + * FIXME + * + * @param time + * @param unit + * @return + */ + public Builder requestTimeout(long time, TimeUnit unit) { + // FIXME checks + this.requestTimeoutMs = TimeUnit.MICROSECONDS.convert(time, unit); + return this; + } + + public RealmAppConfiguration build() { + return new RealmAppConfiguration(appId, + appName, + appVersion, + baseUrl, + context, + defaultErrorHandler, + encryptionKey, + logLevel, + requestTimeoutMs); + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java new file mode 100644 index 0000000000..a71970c5a6 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java @@ -0,0 +1,257 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm; + +import io.realm.internal.Util; +import io.realm.internal.objectstore.OsAppCredentials; + + +/** + * FIXME: Revisit this description when all providers are implemented. + * + * Credentials represent a login with a 3rd party login provider in an OAuth2 login flow, and are used by the Realm + * Object Server to verify the user and grant access. + *

            + * Logging into the Realm Object Server consists of the following steps: + *

              + *
            1. + * Log in to 3rd party provider (Facebook or Google). The result is usually an Authorization Grant that must be + * saved in a {@link RealmCredentials} object of the proper type e.g., {@link RealmCredentials#facebook(String)} for a + * Facebook login. + *
            2. + *
            3. + * Authenticate a {@link RealmUser} through the Object Server using these credentials. Once authenticated, + * an Object Server user is returned. Then this user can be attached to a {@link io.realm.SyncConfiguration}, which + * will make it possible to synchronize data between the local and remote Realm. + *

              + * It is possible to persist the user object e.g., using the {@link UserStore}. That means, logging + * into an OAuth2 provider is only required the first time the app is used. + *

            4. + *
            + * + *
            + * {@code
            + * // Example
            + *
            + * Credentials credentials = Credentials.facebook(getFacebookToken());
            + * User.login(credentials, "http://objectserver.realm.io/auth", new User.Callback() {
            + *     \@Override
            + *     public void onSuccess(User user) {
            + *          // User is now authenticated and be be used to open Realms.
            + *     }
            + *
            + *     \@Override
            + *     public void onError(ObjectServerError error) {
            + *
            + *     }
            + * });
            + * }
            + * 
            + */ +public class RealmCredentials { + + OsAppCredentials osCredentials; + + /** + * FIXME + * Creates credentials anonymously. + * + * Note: logging the user out again means that data is lost with no means of recovery + * and it isn't possible to share the user details across devices. + * + * @return a set of credentials that can be used to log into the Object Server using + * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + */ + public static RealmCredentials anonymous() { + return new RealmCredentials(OsAppCredentials.anonymous()); + } + + /** + * FIXME + */ + public static RealmCredentials apiKey(String key) { + assertStringNotEmpty(key, "id"); + return new RealmCredentials(OsAppCredentials.apiKey(key)); + } + + /** + * FIXME + */ + public static RealmCredentials apple(String idToken) { + assertStringNotEmpty(idToken, "idToken"); + return new RealmCredentials(OsAppCredentials.apple(idToken)); + } + + /** + * FIXME + */ + public static RealmCredentials customFunction(String functionName, Object... arguments) { +// assertStringNotEmpty(idToken, "idToken"); + return new RealmCredentials(OsAppCredentials.customFunction(functionName, arguments)); + } + + /** + * FIXME + */ + public static RealmCredentials emailPassword(String email, String password) { + assertStringNotEmpty(email, "email"); + assertStringNotEmpty(password, "password"); + return new RealmCredentials(OsAppCredentials.emailPassword(email, password)); + } + + /** + * FIXME + * Creates credentials based on a Facebook login. + * + * @param accessToken a facebook userIdentifier acquired by logging into Facebook. + * @return a set of credentials that can be used to log into the Object Server using + * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * @throws IllegalArgumentException if user name is either {@code null} or empty. + */ + public static RealmCredentials facebook(String accessToken) { + assertStringNotEmpty(accessToken, "accessToken"); + return new RealmCredentials(OsAppCredentials.facebook(accessToken)); + } + + /** + * FIXME + * Creates credentials based on a Google login. + * + * @param googleToken a google userIdentifier acquired by logging into Google. + * @return a set of credentials that can be used to log into the Object Server using + * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * @throws IllegalArgumentException if user name is either {@code null} or empty. + */ + public static RealmCredentials google(String googleToken) { + assertStringNotEmpty(googleToken, "googleToken"); + return new RealmCredentials(OsAppCredentials.google(googleToken)); + } + + /** + * FIXME + * Creates credentials based on a JSON Web Token (JWT). + * + * @param jwtToken a JWT token that identifies the user. + * @return a set of credentials that can be used to log into the Object Server using + * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * @throws IllegalArgumentException if the token is either {@code null} or empty. + */ + public static RealmCredentials jwt(String jwtToken) { + assertStringNotEmpty(jwtToken, "jwtToken"); + return new RealmCredentials(OsAppCredentials.jwt(jwtToken)); + } + + /** + * Returns the id for the provider used to authenticate with. + * + * @return the id identifying the chosen authentication provider. + */ + public IdentityProvider getIdentityProvider() { + return IdentityProvider.fromId(osCredentials.getProvider()); + } + + /** + * Returns the credentials object serialized as a json string. + * + * @return a json serialized string of the credentials object. + */ + public String asJson() { + return osCredentials.asJson(); + } + + private static void assertStringNotEmpty(String string, String message) { + //noinspection ConstantConditions + if (Util.isEmptyString(string)) { + throw new IllegalArgumentException("Non-null '" + message + "' required."); + } + } + + private RealmCredentials(OsAppCredentials credentials) { + this.osCredentials = credentials; + } + + /** + * FIXME + */ + public enum IdentityProvider { + /** + * FIXME + */ + ANONYMOUS("anon-user"), + /** + * FIXME + */ + API_KEY(""), // FIXME + /** + * FIXME + */ + APPLE("oauth2-apple"), + /** + * FIXME + */ + CUSTOM_FUNCTION(""), // FIXME + /** + * FIXME + */ + EMAIL_PASSWORD("local-userpass"), + + /** + * FIXME + */ + FACEBOOK("oauth2-facebook"), + /** + * FIXME + */ + GOOGLE("oauth2-google"), + /** + * FIXME + */ + JWT("jwt"), + /** + * FIXME + */ + UNKNOWN(""); + + /** + * FIXME + * + * @param id the string identifier for the provider + * @return + */ + public static IdentityProvider fromId(String id) { + for (IdentityProvider value : values()) { + if (value.getId().equals(id)) { + return value; + } + } + return UNKNOWN; + } + + private final String id; + + IdentityProvider(String id) { + this.id = id; + } + + /** + * FIXME + */ + public String getId() { + return id; + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java deleted file mode 100644 index 255acb0218..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFileUserStore.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - -import javax.annotation.Nullable; - - -/** - * A User Store backed by a Realm file to store users. - */ -public class RealmFileUserStore implements UserStore { - - /** - * {@inheritDoc} - */ - @Override - public void put(SyncUser user) { - String userJson = user.toJson(); - // create or update token (userJson) using identity - nativeUpdateOrCreateUser(user.getIdentity(), userJson, user.getAuthenticationUrl().toString()); - } - - /** - * {@inheritDoc} - */ - @Override - @Nullable - public SyncUser getCurrent() { - String userJson = nativeGetCurrentUser(); - return toSyncUserOrNull(userJson); - } - - /** - * {@inheritDoc} - */ - @Override - @Nullable - public SyncUser get(String identity, String authUrl) { - String userJson = nativeGetUser(identity, authUrl); - return toSyncUserOrNull(userJson); - } - - /** - * {@inheritDoc} - */ - @Override - public void remove(String identity, String authUrl) { - nativeLogoutUser(identity, authUrl); - } - - /** - * {@inheritDoc} - */ - @Override - public Collection allUsers() { - String[] allUsers = nativeGetAllUsers(); - if (allUsers != null && allUsers.length > 0) { - ArrayList users = new ArrayList(allUsers.length); - for (String userJson : allUsers) { - users.add(SyncUser.fromJson(userJson)); - } - return users; - } - return Collections.emptyList(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isActive(String identity, String authenticationUrl) { - return nativeIsActive(identity, authenticationUrl); - } - - @Nullable - private static SyncUser toSyncUserOrNull(@Nullable String userJson) { - if (userJson == null) { - return null; - } - return SyncUser.fromJson(userJson); - } - - // returns json data (token) of the current logged in user - protected static native String nativeGetCurrentUser(); - - // returns json data (token) of the specified user - @Nullable - protected static native String nativeGetUser(String identity, String authUrl); - - protected static native String[] nativeGetAllUsers(); - - protected static native void nativeUpdateOrCreateUser(String identity, String jsonToken, String url); - - protected static native void nativeLogoutUser(String identity, String authUrl); - - protected static native boolean nativeIsActive(String identity, String authUrl); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java new file mode 100644 index 0000000000..4752b76d7a --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +class RealmFunctions { +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmPushNotifications.java b/realm/realm-library/src/objectServer/java/io/realm/RealmPushNotifications.java new file mode 100644 index 0000000000..64553c2029 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmPushNotifications.java @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +class RealmPushNotifications { +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java new file mode 100644 index 0000000000..742e2dc063 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -0,0 +1,177 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +import io.realm.internal.objectstore.OsSyncUser; +import io.realm.internal.util.Pair; + +/** + * FIXME + */ +public class RealmUser { + + private final OsSyncUser osUser; + + /** + * FIXME + */ + enum UserType { + NORMAL("normal"), + SERVER("server"), + UNKNOWN("unknown"); + + private final String key; + + UserType(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + } + + RealmUser(long nativePtr) { + this.osUser = new OsSyncUser(nativePtr); + } + + /** + * FIXME + * @return + */ + public String getId() { + return osUser.getIdentity(); + } + + /** + * FIXME + * @return + */ + public String getName() { + return osUser.nativeGetName(); + } + + /** + * FIXME + * @return + */ + @Nullable + public String getEmail() { + return osUser.getEmail(); + } + + /** + * FIXME + * @return + */ + @Nullable + public String getPictureUrl() { + return osUser.getPictureUrl(); + } + + /** + * FIXME + * @return + */ + @Nullable + public String getFirstName() { + return osUser.getFirstName(); + } + + /** + * FIXME + * @return + */ + @Nullable + public String getLastName() { + return osUser.getLastName(); + } + + /** + * FIXME + * @return + */ + @Nullable + public String getGender() { + return osUser.getGender(); + } + + /** + * FIXME + * @return + */ + @Nullable + public String getBirthday() { + return osUser.getBirthday(); + } + + /** + * FIXME + * @return + */ + @Nullable + public Long getMinAge() { + String minAge = osUser.getMinAge(); + return (minAge == null) ? null : Long.parseLong(minAge); + } + + /** + * FIXME + * @return + */ + @Nullable + public Long getMaxAge() { + String maxAge = osUser.getMaxAge(); + return (maxAge == null) ? null : Long.parseLong(maxAge); + } + + /** + * FIXME + * @return + */ + public List getIdentities() { + Pair[] osIdentities = osUser.getIdentities(); + List identities = new ArrayList<>(osIdentities.length); + for (int i = 0; i < osIdentities.length; i++) { + Pair data = osIdentities[i]; + identities.add(new RealmUserIdentity(data.first, data.second)); + } + return identities; + } + + /** + * FIXME + * @return + */ + + public String getAccessToken() { + return osUser.getAccessToken(); + } + + /** + * FIXME + * @return + */ + public String getRefreshToken() { + return osUser.getRefreshToken(); + } + +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUserIdentity.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUserIdentity.java new file mode 100644 index 0000000000..70a47f9d11 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUserIdentity.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +/** + * Each RealmUser is represented by 1 or more identities each defined by an + * {@link RealmCredentials.IdentityProvider}. + * + * This class represents the identity defined by a specific provider. + */ +public class RealmUserIdentity { + + private final String userId; + private final String providerId; + private final RealmCredentials.IdentityProvider provider; + + RealmUserIdentity(String id, String providerId) { + this.userId = id; + this.providerId = providerId; + this.provider = RealmCredentials.IdentityProvider.fromId(providerId); + } + + /** + * Returns a unique identifier for this identity. + * + * @return a unique identifier for this identifier. + */ + public String getId() { + return userId; + } + + /** + * Returns the provider defining this identity. + * + * @return + */ + public RealmCredentials.IdentityProvider getProvider() { + return provider; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RealmUserIdentity that = (RealmUserIdentity) o; + + if (!userId.equals(that.userId)) return false; + if (!providerId.equals(that.providerId)) return false; + return provider == that.provider; + } + + @Override + public int hashCode() { + int result = userId.hashCode(); + result = 31 * result + providerId.hashCode(); + result = 31 * result + provider.hashCode(); + return result; + } + + @Override + public String toString() { + return "RealmUserIdentity{" + + "userId='" + userId + '\'' + + ", providerId='" + providerId + '\'' + + '}'; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index d28319f723..3f167ccb05 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -47,9 +47,7 @@ import io.realm.internal.Keep; import io.realm.internal.OsRealmConfig; import io.realm.internal.Util; -import io.realm.internal.network.RealmObjectServer; import io.realm.internal.network.NetworkStateReceiver; -import io.realm.internal.network.OkHttpRealmObjectServer; import io.realm.log.RealmLog; import okhttp3.internal.tls.OkHostnameVerifier; @@ -129,8 +127,7 @@ public void onError(SyncSession session, ObjectServerError error) { // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. // Right now it just lives and dies together with the process. - private static volatile RealmObjectServer authServer = new OkHttpRealmObjectServer(); - private static volatile UserStore userStore; +// private static volatile RealmObjectServer authServer = new OkHttpRealmObjectServer(); // Header configuration private static String globalAuthorizationHeaderName = "Authorization"; // authorization header name if no host-defined header is available @@ -154,24 +151,8 @@ public void onChange(boolean connectionAvailable) { static volatile SyncSession.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; // Initialize the SyncManager - static void init(String appId, UserStore userStore) { + static void init(String appId) { SyncManager.APP_ID = appId; - SyncManager.userStore = userStore; - } - - /** - * Set the {@link UserStore} used by the Realm Object Server to save user information. - * If no Userstore is specified {@link SyncUser#current()} will always return {@code null}. - * - * @param userStore {@link UserStore} to use. - * @throws IllegalArgumentException if {@code userStore} is {@code null}. - */ - public static void setUserStore(UserStore userStore) { - //noinspection ConstantConditions - if (userStore == null) { - throw new IllegalArgumentException("Non-null 'userStore' required."); - } - SyncManager.userStore = userStore; } /** @@ -278,7 +259,7 @@ public static synchronized SyncSession getOrCreateSession(SyncConfiguration sync // access token, however since the Realm might not be open yet, the wrapObjectStoreSessionIfRequired // will not be invoked to wrap the OS store session with the Java session, the Sync client to not resume // syncing. - session.getAccessToken(authServer, ""); +// session.getAccessToken(authServer, ""); } // The underlying session will be created as part of opening the Realm, but this approach @@ -305,7 +286,7 @@ public static synchronized SyncSession getOrCreateSession(SyncConfiguration sync */ public static synchronized void setAuthorizationHeaderName(String headerName) { checkNotEmpty(headerName, "headerName"); - authServer.setAuthorizationHeaderName(headerName, null); +// authServer.setAuthorizationHeaderName(headerName, null); globalAuthorizationHeaderName = headerName; } @@ -327,7 +308,7 @@ public static synchronized void setAuthorizationHeaderName(String headerName, St checkNotEmpty(headerName, "headerName"); checkNotEmpty(host, "host"); host = host.toLowerCase(Locale.US); - authServer.setAuthorizationHeaderName(headerName, host); +// authServer.setAuthorizationHeaderName(headerName, host); hostRestrictedAuthorizationHeaderName.put(host, headerName); } @@ -341,7 +322,7 @@ public static synchronized void setAuthorizationHeaderName(String headerName, St public static synchronized void addCustomRequestHeader(String headerName, String headerValue) { checkNotEmpty(headerName, "headerName"); checkNotNull(headerValue, "headerValue"); - authServer.addHeader(headerName, headerValue, null); +// authServer.addHeader(headerName, headerValue, null); globalCustomHeaders.put(headerName, headerValue); } @@ -361,7 +342,7 @@ public static synchronized void addCustomRequestHeader(String headerName, String // Headers host = host.toLowerCase(Locale.US); - authServer.addHeader(headerName, headerValue, host); +// authServer.addHeader(headerName, headerValue, host); Map headers = hostRestrictedCustomHeaders.get(host); if (headers == null) { headers = new LinkedHashMap<>(); @@ -474,21 +455,16 @@ static List getAllSessions(SyncUser syncUser) { return allSessions; } - static RealmObjectServer getAuthServer() { - return authServer; - } - - /** - * Sets the auth server implementation used when validating credentials. - */ - static void setAuthServerImpl(RealmObjectServer authServerImpl) { - authServer = authServerImpl; - } +// static RealmObjectServer getAuthServer() { +// return authServer; +// } - // Return the currently configured User store. - public static UserStore getUserStore() { - return userStore; - } +// /** +// * Sets the auth server implementation used when validating credentials. +// */ +// static void setAuthServerImpl(RealmObjectServer authServerImpl) { +// authServer = authServerImpl; +// } // Notify listeners that a user logged in static void notifyUserLoggedIn(SyncUser user) { @@ -587,19 +563,19 @@ private static synchronized void notifyConnectionListeners(String localRealmPath * @return a valid cached {@code access_token} if available or null. */ @SuppressWarnings("unused") - private synchronized static String bindSessionWithConfig(String sessionPath, String refreshToken) { - final SyncSession syncSession = sessions.get(sessionPath); - if (syncSession == null) { - RealmLog.error("Matching Java SyncSession could not be found for: " + sessionPath); - } else { - try { - return syncSession.getAccessToken(authServer, refreshToken); - } catch (Exception exception) { - RealmLog.error(exception); - } - } - return null; - } +// private synchronized static String bindSessionWithConfig(String sessionPath, String refreshToken) { +// final SyncSession syncSession = sessions.get(sessionPath); +// if (syncSession == null) { +// RealmLog.error("Matching Java SyncSession could not be found for: " + sessionPath); +// } else { +// try { +// return syncSession.getAccessToken(authServer, refreshToken); +// } catch (Exception exception) { +// RealmLog.error(exception); +// } +// } +// return null; +// } /** * Realm will automatically detect when a device gets connectivity after being offline and @@ -752,7 +728,7 @@ static synchronized void reset() { globalAuthorizationHeaderName = "Authorization"; hostRestrictedCustomHeaders.clear(); globalCustomHeaders.clear(); - authServer.clearCustomHeaderSettings(); +// authServer.clearCustomHeaderSettings(); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 43a77d53b0..717c5aeca7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -16,10 +16,6 @@ package io.realm; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.InterruptedIOException; import java.net.URI; import java.util.HashMap; import java.util.IdentityHashMap; @@ -28,8 +24,6 @@ import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -40,16 +34,7 @@ import javax.annotation.Nullable; import io.realm.internal.Keep; -import io.realm.internal.SyncObjectServerFacade; -import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; -import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.network.RealmObjectServer; -import io.realm.internal.network.ExponentialBackoffTask; -import io.realm.internal.network.NetworkStateReceiver; -import io.realm.internal.objectserver.Token; -import io.realm.internal.objectserver.SyncWorker; import io.realm.internal.util.Pair; import io.realm.log.RealmLog; @@ -734,173 +719,173 @@ public interface ErrorHandler { void onError(SyncSession session, ObjectServerError error); } - // Return the access token for the Realm this Session is connected to. - String getAccessToken(final RealmObjectServer authServer, String refreshToken) { - // check first if there's a valid access_token we can return immediately - if (getUser().isRealmAuthenticated(configuration)) { - Token accessToken = getUser().getAccessToken(configuration); - // start refreshing this token if a refresh is not going on - if (!onGoingAccessTokenQuery.getAndSet(true)) { - scheduleRefreshAccessToken(authServer, accessToken.expiresMs()); - } - return accessToken.value(); - - } else { - // check and update if we received a new refresh_token - if (!Util.isEmptyString(refreshToken)) { - try { - JSONObject refreshTokenJSON = new JSONObject(refreshToken); - Token newRefreshToken = Token.from(refreshTokenJSON.getJSONObject("userToken")); - if (newRefreshToken.hashCode() != getUser().getRefreshToken().hashCode()) { - RealmLog.debug("Session[%s]: Access token updated", configuration.getPath()); - getUser().setRefreshToken(newRefreshToken); - } - } catch (JSONException e) { - RealmLog.error(e, "Session[%s]: Can not parse the refresh_token into a valid JSONObject: ", configuration.getPath()); - } - } - if (!onGoingAccessTokenQuery.get() && NetworkStateReceiver.isOnline(SyncObjectServerFacade.getApplicationContext())) { - authenticateRealm(authServer); - } - } - return null; - } - - // Authenticate by getting access tokens for the specific Realm - private void authenticateRealm(final RealmObjectServer authServer) { - if (networkRequest != null) { - networkRequest.cancel(); - } - clearScheduledAccessTokenRefresh(); - - onGoingAccessTokenQuery.set(true); - // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { - @Override - protected AuthenticateResponse execute() { - if (!isClosed && !Thread.currentThread().isInterrupted()) { - return authServer.loginToRealm( - getUser().getRefreshToken(), //refresh token in fact - resolvedRealmURI, - getUser().getAuthenticationUrl() - ); - } - return null; - } - - @Override - protected void onSuccess(AuthenticateResponse response) { - RealmLog.debug("Session[%s]: Access token acquired", configuration.getPath()); - if (!isClosed && !Thread.currentThread().isInterrupted()) { - URI realmUrl = configuration.getServerUrl(); - getUser().addRealm(configuration, response.getAccessToken()); - if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), realmUrl.toString())) { - scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); - - } else { - // token not applied, no refresh will be scheduled - onGoingAccessTokenQuery.set(false); - } - } - } - - @Override - protected void onError(AuthenticateResponse response) { - onGoingAccessTokenQuery.set(false); - RealmLog.debug("Session[%s]: Failed to get access token (%s)", configuration.getPath(), - response.getError().getErrorCode()); - if (!isClosed - && !Thread.currentThread().isInterrupted() - // We might be interrupted while negotiating an access token with the Realm Object Server - // This will result in a InterruptedIOException from OkHttp. We should ignore this as - // well. - && !(response.getError().getException() instanceof InterruptedIOException)) { - errorHandler.onError(SyncSession.this, response.getError()); - } - } - }); - networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); - } - - private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long expireDateInMs) { - onGoingAccessTokenQuery.set(true); - // calculate the delay time before which we should refresh the access_token, - // we adjust to 10 second to proactively refresh the access_token before the session - // hit the expire date on the token - long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; - if (refreshAfter < 0) { - // Token already expired - RealmLog.debug("Expires time already reached for the access token, refresh as soon as possible"); - // we avoid refreshing directly to avoid an edge case where the client clock is ahead - // of the server, causing all access_token received from the server to be always - // expired, we will flood the server with refresh token requests then, so adding - // a bit of delay is the best effort in this case. - refreshAfter = REFRESH_MARGIN_DELAY; - } - - RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); - - if (refreshTokenTask != null) { - refreshTokenTask.cancel(); - } - - ScheduledFuture task = REFRESH_TOKENS_EXECUTOR.schedule(new Runnable() { - @Override - public void run() { - if (!isClosed && !Thread.currentThread().isInterrupted() && !refreshTokenTask.isCancelled()) { - refreshAccessToken(authServer); - } - } - }, refreshAfter, TimeUnit.MILLISECONDS); - refreshTokenTask = new RealmAsyncTaskImpl(task, REFRESH_TOKENS_EXECUTOR); - } - - // Authenticate by getting access tokens for the specific Realm - private void refreshAccessToken(final RealmObjectServer authServer) { - // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. - clearScheduledAccessTokenRefresh(); - - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { - @Override - protected AuthenticateResponse execute() { - if (!isClosed && !Thread.currentThread().isInterrupted()) { - return authServer.refreshUser(getUser().getRefreshToken(), resolvedRealmURI, getUser().getAuthenticationUrl()); - } - return null; - } - - @Override - protected void onSuccess(AuthenticateResponse response) { - synchronized (SyncSession.this) { - if (!isClosed && !Thread.currentThread().isInterrupted() && !refreshTokenNetworkRequest.isCancelled()) { - RealmLog.debug("Access Token refreshed successfully, Sync URL: " + configuration.getServerUrl()); - - SyncWorker syncWorker = response.getSyncWorker(); - if (syncWorker != null) { - nativeSetUrlPrefix(configuration.getPath(), syncWorker.path()); - } - - URI realmUrl = configuration.getServerUrl(); - if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), realmUrl.toString())) { - // replace the user old access_token - getUser().addRealm(configuration, response.getAccessToken()); - // schedule the next refresh - scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); - } - } - } - } - - @Override - protected void onError(AuthenticateResponse response) { - if (!isClosed && !Thread.currentThread().isInterrupted()) { - onGoingAccessTokenQuery.set(false); - RealmLog.error("Unrecoverable error, while refreshing the access Token (" + response.getError().toString() + ") reschedule will not happen"); - } - } - }); - refreshTokenNetworkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); - } +// // Return the access token for the Realm this Session is connected to. +// String getAccessToken(final RealmObjectServer authServer, String refreshToken) { +// // check first if there's a valid access_token we can return immediately +// if (getUser().isRealmAuthenticated(configuration)) { +// Token accessToken = getUser().getAccessToken(configuration); +// // start refreshing this token if a refresh is not going on +// if (!onGoingAccessTokenQuery.getAndSet(true)) { +// scheduleRefreshAccessToken(authServer, accessToken.expiresMs()); +// } +// return accessToken.value(); +// +// } else { +// // check and update if we received a new refresh_token +// if (!Util.isEmptyString(refreshToken)) { +// try { +// JSONObject refreshTokenJSON = new JSONObject(refreshToken); +// Token newRefreshToken = Token.from(refreshTokenJSON.getJSONObject("userToken")); +// if (newRefreshToken.hashCode() != getUser().getRefreshToken().hashCode()) { +// RealmLog.debug("Session[%s]: Access token updated", configuration.getPath()); +// getUser().setRefreshToken(newRefreshToken); +// } +// } catch (JSONException e) { +// RealmLog.error(e, "Session[%s]: Can not parse the refresh_token into a valid JSONObject: ", configuration.getPath()); +// } +// } +// if (!onGoingAccessTokenQuery.get() && NetworkStateReceiver.isOnline(SyncObjectServerFacade.getApplicationContext())) { +// authenticateRealm(authServer); +// } +// } +// return null; +// } +// +// // Authenticate by getting access tokens for the specific Realm +// private void authenticateRealm(final RealmObjectServer authServer) { +// if (networkRequest != null) { +// networkRequest.cancel(); +// } +// clearScheduledAccessTokenRefresh(); +// +// onGoingAccessTokenQuery.set(true); +// // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. +// Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { +// @Override +// protected AuthenticateResponse execute() { +// if (!isClosed && !Thread.currentThread().isInterrupted()) { +// return authServer.loginToRealm( +// getUser().getRefreshToken(), //refresh token in fact +// resolvedRealmURI, +// getUser().getAuthenticationUrl() +// ); +// } +// return null; +// } +// +// @Override +// protected void onSuccess(AuthenticateResponse response) { +// RealmLog.debug("Session[%s]: Access token acquired", configuration.getPath()); +// if (!isClosed && !Thread.currentThread().isInterrupted()) { +// URI realmUrl = configuration.getServerUrl(); +// getUser().addRealm(configuration, response.getAccessToken()); +// if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), realmUrl.toString())) { +// scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); +// +// } else { +// // token not applied, no refresh will be scheduled +// onGoingAccessTokenQuery.set(false); +// } +// } +// } +// +// @Override +// protected void onError(AuthenticateResponse response) { +// onGoingAccessTokenQuery.set(false); +// RealmLog.debug("Session[%s]: Failed to get access token (%s)", configuration.getPath(), +// response.getError().getErrorCode()); +// if (!isClosed +// && !Thread.currentThread().isInterrupted() +// // We might be interrupted while negotiating an access token with the Realm Object Server +// // This will result in a InterruptedIOException from OkHttp. We should ignore this as +// // well. +// && !(response.getError().getException() instanceof InterruptedIOException)) { +// errorHandler.onError(SyncSession.this, response.getError()); +// } +// } +// }); +// networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); +// } +// +// private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long expireDateInMs) { +// onGoingAccessTokenQuery.set(true); +// // calculate the delay time before which we should refresh the access_token, +// // we adjust to 10 second to proactively refresh the access_token before the session +// // hit the expire date on the token +// long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; +// if (refreshAfter < 0) { +// // Token already expired +// RealmLog.debug("Expires time already reached for the access token, refresh as soon as possible"); +// // we avoid refreshing directly to avoid an edge case where the client clock is ahead +// // of the server, causing all access_token received from the server to be always +// // expired, we will flood the server with refresh token requests then, so adding +// // a bit of delay is the best effort in this case. +// refreshAfter = REFRESH_MARGIN_DELAY; +// } +// +// RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); +// +// if (refreshTokenTask != null) { +// refreshTokenTask.cancel(); +// } +// +// ScheduledFuture task = REFRESH_TOKENS_EXECUTOR.schedule(new Runnable() { +// @Override +// public void run() { +// if (!isClosed && !Thread.currentThread().isInterrupted() && !refreshTokenTask.isCancelled()) { +// refreshAccessToken(authServer); +// } +// } +// }, refreshAfter, TimeUnit.MILLISECONDS); +// refreshTokenTask = new RealmAsyncTaskImpl(task, REFRESH_TOKENS_EXECUTOR); +// } +// +// // Authenticate by getting access tokens for the specific Realm +// private void refreshAccessToken(final RealmObjectServer authServer) { +// // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. +// clearScheduledAccessTokenRefresh(); +// +// Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { +// @Override +// protected AuthenticateResponse execute() { +// if (!isClosed && !Thread.currentThread().isInterrupted()) { +// return authServer.refreshUser(getUser().getRefreshToken(), resolvedRealmURI, getUser().getAuthenticationUrl()); +// } +// return null; +// } +// +// @Override +// protected void onSuccess(AuthenticateResponse response) { +// synchronized (SyncSession.this) { +// if (!isClosed && !Thread.currentThread().isInterrupted() && !refreshTokenNetworkRequest.isCancelled()) { +// RealmLog.debug("Access Token refreshed successfully, Sync URL: " + configuration.getServerUrl()); +// +// SyncWorker syncWorker = response.getSyncWorker(); +// if (syncWorker != null) { +// nativeSetUrlPrefix(configuration.getPath(), syncWorker.path()); +// } +// +// URI realmUrl = configuration.getServerUrl(); +// if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), realmUrl.toString())) { +// // replace the user old access_token +// getUser().addRealm(configuration, response.getAccessToken()); +// // schedule the next refresh +// scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); +// } +// } +// } +// } +// +// @Override +// protected void onError(AuthenticateResponse response) { +// if (!isClosed && !Thread.currentThread().isInterrupted()) { +// onGoingAccessTokenQuery.set(false); +// RealmLog.error("Unrecoverable error, while refreshing the access Token (" + response.getError().toString() + ") reschedule will not happen"); +// } +// } +// }); +// refreshTokenNetworkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); +// } void clearScheduledAccessTokenRefresh() { if (refreshTokenTask != null) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index 1762f170cb..b4cebcb24a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -24,6 +24,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -35,17 +36,9 @@ import javax.annotation.Nullable; import io.realm.internal.RealmNotifier; -import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.internal.network.AuthenticateResponse; -import io.realm.internal.network.ChangePasswordResponse; -import io.realm.internal.network.ExponentialBackoffTask; -import io.realm.internal.network.LogoutResponse; -import io.realm.internal.network.LookupUserIdResponse; -import io.realm.internal.network.RealmObjectServer; -import io.realm.internal.network.UpdateAccountResponse; import io.realm.internal.objectserver.Token; import io.realm.log.RealmLog; @@ -90,10 +83,11 @@ public class SyncUser { * @throws IllegalStateException if multiple users are logged in. */ public static SyncUser current() { - SyncUser user = SyncManager.getUserStore().getCurrent(); - if (user != null && user.isValid()) { - return user; - } + // FIXME +// SyncUser user = null; //SyncManager.getUserStore().getCurrent(); +// if (user != null && user.isValid()) { +// return user; +// } return null; } @@ -104,8 +98,7 @@ public static SyncUser current() { * @return a map from user identifier to user. It includes all known valid users. */ public static Map all() { - UserStore userStore = SyncManager.getUserStore(); - Collection storedUsers = userStore.allUsers(); + Collection storedUsers = new ArrayList<>(); // FIXME Map map = new HashMap<>(); for (SyncUser user : storedUsers) { if (user.isValid()) { @@ -144,39 +137,39 @@ public static SyncUser fromJson(String user) { * @throws ObjectServerError if the login failed. * @throws IllegalArgumentException if the URL is malformed. */ - public static SyncUser logIn(final SyncCredentials credentials, final String authenticationUrl) throws ObjectServerError { - URL authUrl = getUrl(authenticationUrl); - - ObjectServerError error; - try { - AuthenticateResponse result; - if (credentials.getIdentityProvider().equals(SyncCredentials.IdentityProvider.ACCESS_TOKEN)) { - // Credentials using ACCESS_TOKEN as IdentityProvider are optimistically assumed to be valid already. - // So log them in directly without contacting the authentication server. This is done by mirroring - // the JSON response expected from the server. - String userIdentifier = credentials.getUserIdentifier(); - String token = (String) credentials.getUserInfo().get("_token"); - boolean isAdmin = (Boolean) credentials.getUserInfo().get("_isAdmin"); - result = AuthenticateResponse.createValidResponseWithUser(userIdentifier, token, isAdmin); - } else { - final RealmObjectServer server = SyncManager.getAuthServer(); - result = server.loginUser(credentials, authUrl); - } - if (result.isValid()) { - SyncUser user = new SyncUser(result.getRefreshToken(), authUrl); - RealmLog.info("Succeeded authenticating user.\n%s", user); - SyncManager.getUserStore().put(user); - SyncManager.notifyUserLoggedIn(user); - return user; - } else { - RealmLog.info("Failed authenticating user.\n%s", result.getError()); - error = result.getError(); - } - } catch (Throwable e) { - throw new ObjectServerError(ErrorCode.UNKNOWN, e); - } - throw error; - } +// public static SyncUser logIn(final SyncCredentials credentials, final String authenticationUrl) throws ObjectServerError { +// URL authUrl = getUrl(authenticationUrl); +// +// ObjectServerError error; +// try { +// AuthenticateResponse result; +// if (credentials.getIdentityProvider().equals(SyncCredentials.IdentityProvider.ACCESS_TOKEN)) { +// // Credentials using ACCESS_TOKEN as IdentityProvider are optimistically assumed to be valid already. +// // So log them in directly without contacting the authentication server. This is done by mirroring +// // the JSON response expected from the server. +// String userIdentifier = credentials.getUserIdentifier(); +// String token = (String) credentials.getUserInfo().get("_token"); +// boolean isAdmin = (Boolean) credentials.getUserInfo().get("_isAdmin"); +// result = AuthenticateResponse.createValidResponseWithUser(userIdentifier, token, isAdmin); +// } else { +// final RealmObjectServer server = SyncManager.getAuthServer(); +// result = server.loginUser(credentials, authUrl); +// } +// if (result.isValid()) { +// SyncUser user = new SyncUser(result.getRefreshToken(), authUrl); +// RealmLog.info("Succeeded authenticating user.\n%s", user); +// SyncManager.getUserStore().put(user); +// SyncManager.notifyUserLoggedIn(user); +// return user; +// } else { +// RealmLog.info("Failed authenticating user.\n%s", result.getError()); +// error = result.getError(); +// } +// } catch (Throwable e) { +// throw new ObjectServerError(ErrorCode.UNKNOWN, e); +// } +// throw error; +// } /** * Converts the input URL to a Realm Authentication URL @@ -210,15 +203,15 @@ private static URL getUrl(String authenticationUrl) { * @return representation of the async task that can be used to cancel it if needed. * @throws IllegalArgumentException if not on a Looper thread. */ - public static RealmAsyncTask logInAsync(final SyncCredentials credentials, final String authenticationUrl, final Callback callback) { - checkLooperThread("Asynchronous login is only possible from looper threads."); - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public SyncUser run() throws ObjectServerError { - return logIn(credentials, authenticationUrl); - } - }.start(); - } +// public static RealmAsyncTask logInAsync(final SyncCredentials credentials, final String authenticationUrl, final Callback callback) { +// checkLooperThread("Asynchronous login is only possible from looper threads."); +// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { +// @Override +// public SyncUser run() throws ObjectServerError { +// return logIn(credentials, authenticationUrl); +// } +// }.start(); +// } /** * Opening a synchronized Realm requires a {@link SyncConfiguration}. This method creates a @@ -308,61 +301,61 @@ private static String createUrl(SyncUser user) { // */ // this is a fire and forget, end user should not worry about the state of the async query @SuppressWarnings("FutureReturnValueIgnored") - public void logOut() { - // Acquire lock to prevent users creating new instances - synchronized (Realm.class) { - if (!SyncManager.getUserStore().isActive(identity, authenticationUrl.toString())) { - return; // Already logged out status - } - - // Mark the user as logged out in the ObjectStore - SyncManager.getUserStore().remove(identity, authenticationUrl.toString()); - - // invalidate all pending refresh_token queries - for (SyncConfiguration syncConfiguration : realms.keySet()) { - try { - SyncSession session = SyncManager.getSession(syncConfiguration); - session.clearScheduledAccessTokenRefresh(); - } catch (IllegalStateException e) { - if (!e.getMessage().contains("No SyncSession found")) { - throw e; - }// else no session, either the Realm was not opened or session was removed. - } - } - - // Remove all local tokens, preventing further connections. - // don't remove identity as this SyncUser might be re-activated and we need - // to avoid throwing a mismatch SyncConfiguration in RealmCache if we have - // the similar SyncConfiguration using the same identity, but with different (new) - // refresh-token. - realms.clear(); - - // Finally revoke server token. The local user is logged out in any case. - final RealmObjectServer server = SyncManager.getAuthServer(); - // don't reference directly the refreshToken inside the revoke request - // as it may revoke the newly acquired refresh_token - final Token refreshTokenToBeRevoked = refreshToken; - - ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; - networkPoolExecutor.submit(new ExponentialBackoffTask(3) { - - @Override - protected LogoutResponse execute() { - return server.logout(refreshTokenToBeRevoked, getAuthenticationUrl()); - } - - @Override - protected void onSuccess(LogoutResponse response) { - SyncManager.notifyUserLoggedOut(SyncUser.this); - } - - @Override - protected void onError(LogoutResponse response) { - RealmLog.error("Failed to log user out.\n" + response.getError().toString()); - } - }); - } - } +// public void logOut() { +// // Acquire lock to prevent users creating new instances +// synchronized (Realm.class) { +// if (!SyncManager.getUserStore().isActive(identity, authenticationUrl.toString())) { +// return; // Already logged out status +// } +// +// // Mark the user as logged out in the ObjectStore +// SyncManager.getUserStore().remove(identity, authenticationUrl.toString()); +// +// // invalidate all pending refresh_token queries +// for (SyncConfiguration syncConfiguration : realms.keySet()) { +// try { +// SyncSession session = SyncManager.getSession(syncConfiguration); +// session.clearScheduledAccessTokenRefresh(); +// } catch (IllegalStateException e) { +// if (!e.getMessage().contains("No SyncSession found")) { +// throw e; +// }// else no session, either the Realm was not opened or session was removed. +// } +// } +// +// // Remove all local tokens, preventing further connections. +// // don't remove identity as this SyncUser might be re-activated and we need +// // to avoid throwing a mismatch SyncConfiguration in RealmCache if we have +// // the similar SyncConfiguration using the same identity, but with different (new) +// // refresh-token. +// realms.clear(); +// +// // Finally revoke server token. The local user is logged out in any case. +// final RealmObjectServer server = SyncManager.getAuthServer(); +// // don't reference directly the refreshToken inside the revoke request +// // as it may revoke the newly acquired refresh_token +// final Token refreshTokenToBeRevoked = refreshToken; +// +// ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; +// networkPoolExecutor.submit(new ExponentialBackoffTask(3) { +// +// @Override +// protected LogoutResponse execute() { +// return server.logout(refreshTokenToBeRevoked, getAuthenticationUrl()); +// } +// +// @Override +// protected void onSuccess(LogoutResponse response) { +// SyncManager.notifyUserLoggedOut(SyncUser.this); +// } +// +// @Override +// protected void onError(LogoutResponse response) { +// RealmLog.error("Failed to log user out.\n" + response.getError().toString()); +// } +// }); +// } +// } /** * Changes this user's password. This is done synchronously and involves the network, so calling this method on the @@ -374,17 +367,17 @@ protected void onError(LogoutResponse response) { * @param newPassword the user's new password. * @throws ObjectServerError if the password could not be changed. */ - public void changePassword(final String newPassword) throws ObjectServerError { - //noinspection ConstantConditions - if (newPassword == null) { - throw new IllegalArgumentException("Not-null 'newPassword' required."); - } - RealmObjectServer authServer = SyncManager.getAuthServer(); - ChangePasswordResponse response = authServer.changePassword(refreshToken, newPassword, getAuthenticationUrl()); - if (!response.isValid()) { - throw response.getError(); - } - } +// public void changePassword(final String newPassword) throws ObjectServerError { +// //noinspection ConstantConditions +// if (newPassword == null) { +// throw new IllegalArgumentException("Not-null 'newPassword' required."); +// } +// RealmObjectServer authServer = SyncManager.getAuthServer(); +// ChangePasswordResponse response = authServer.changePassword(refreshToken, newPassword, getAuthenticationUrl()); +// if (!response.isValid()) { +// throw response.getError(); +// } +// } /** * Changes another user's password. This is done synchronously and involves the network, so calling this method on the @@ -399,31 +392,31 @@ public void changePassword(final String newPassword) throws ObjectServerError { * @param newPassword the user's new password. * @throws ObjectServerError if the password could not be changed. */ - public void changePassword(final String userId, final String newPassword) throws ObjectServerError { - //noinspection ConstantConditions - if (newPassword == null) { - throw new IllegalArgumentException("Not-null 'newPassword' required."); - } - - if (Util.isEmptyString(userId)) { - throw new IllegalArgumentException("None empty 'userId' required."); - } - - if (userId.equals(getIdentity())) { // user want's to change his/her own password - changePassword(newPassword); - - } else { - if (!isAdmin()) { - throw new IllegalStateException("User need to be admin in order to change another user's password."); - } - - RealmObjectServer authServer = SyncManager.getAuthServer(); - ChangePasswordResponse response = authServer.changePassword(refreshToken, userId, newPassword, getAuthenticationUrl()); - if (!response.isValid()) { - throw response.getError(); - } - } - } +// public void changePassword(final String userId, final String newPassword) throws ObjectServerError { +// //noinspection ConstantConditions +// if (newPassword == null) { +// throw new IllegalArgumentException("Not-null 'newPassword' required."); +// } +// +// if (Util.isEmptyString(userId)) { +// throw new IllegalArgumentException("None empty 'userId' required."); +// } +// +// if (userId.equals(getIdentity())) { // user want's to change his/her own password +// changePassword(newPassword); +// +// } else { +// if (!isAdmin()) { +// throw new IllegalStateException("User need to be admin in order to change another user's password."); +// } +// +// RealmObjectServer authServer = SyncManager.getAuthServer(); +// ChangePasswordResponse response = authServer.changePassword(refreshToken, userId, newPassword, getAuthenticationUrl()); +// if (!response.isValid()) { +// throw response.getError(); +// } +// } +// } /** * Changes this user's password asynchronously. @@ -437,20 +430,20 @@ public void changePassword(final String userId, final String newPassword) throws * @return representation of the async task that can be used to cancel it if needed. * @throws IllegalArgumentException if not on a Looper thread. */ - public RealmAsyncTask changePasswordAsync(final String newPassword, final Callback callback) { - checkLooperThread("Asynchronous changing password is only possible from looper threads."); - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public SyncUser run() { - changePassword(newPassword); - return SyncUser.this; - } - }.start(); - } +// public RealmAsyncTask changePasswordAsync(final String newPassword, final Callback callback) { +// checkLooperThread("Asynchronous changing password is only possible from looper threads."); +// //noinspection ConstantConditions +// if (callback == null) { +// throw new IllegalArgumentException("Non-null 'callback' required."); +// } +// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { +// @Override +// public SyncUser run() { +// changePassword(newPassword); +// return SyncUser.this; +// } +// }.start(); +// } /** * Changes another user's password asynchronously. @@ -467,21 +460,21 @@ public SyncUser run() { * @return representation of the async task that can be used to cancel it if needed. * @throws IllegalArgumentException if not on a Looper thread. */ - public RealmAsyncTask changePasswordAsync(final String userId, final String newPassword, final Callback callback) { - checkLooperThread("Asynchronous changing password is only possible from looper threads."); - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public SyncUser run() { - changePassword(userId, newPassword); - return SyncUser.this; - } - }.start(); - } +// public RealmAsyncTask changePasswordAsync(final String userId, final String newPassword, final Callback callback) { +// checkLooperThread("Asynchronous changing password is only possible from looper threads."); +// //noinspection ConstantConditions +// if (callback == null) { +// throw new IllegalArgumentException("Non-null 'callback' required."); +// } +// +// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { +// @Override +// public SyncUser run() { +// changePassword(userId, newPassword); +// return SyncUser.this; +// } +// }.start(); +// } /** @@ -497,17 +490,17 @@ public SyncUser run() { * @throws IllegalArgumentException if no email or authenticationUrl was provided. * @throws ObjectServerError if an error happened on the server. */ - public static void requestPasswordReset(String email, String authenticationUrl) throws ObjectServerError { - if (Util.isEmptyString(email)) { - throw new IllegalArgumentException("Not-null 'email' required."); - } - URL authUrl = getUrl(authenticationUrl); - RealmObjectServer authServer = SyncManager.getAuthServer(); - UpdateAccountResponse response = authServer.requestPasswordReset(email, authUrl); - if (!response.isValid()) { - throw response.getError(); - } - } +// public static void requestPasswordReset(String email, String authenticationUrl) throws ObjectServerError { +// if (Util.isEmptyString(email)) { +// throw new IllegalArgumentException("Not-null 'email' required."); +// } +// URL authUrl = getUrl(authenticationUrl); +// RealmObjectServer authServer = SyncManager.getAuthServer(); +// UpdateAccountResponse response = authServer.requestPasswordReset(email, authUrl); +// if (!response.isValid()) { +// throw response.getError(); +// } +// } /** * Request a password reset email to be sent to a user's email. @@ -524,21 +517,21 @@ public static void requestPasswordReset(String email, String authenticationUrl) * @throws IllegalStateException if this method is called on a non-looper thread. * @throws IllegalArgumentException if no email or authenticationUrl was provided. */ - public static RealmAsyncTask requestPasswordResetAsync(final String email, final String authenticationUrl, final Callback callback) { - checkLooperThread("Asynchronous requesting a password reset is only possible from looper threads."); - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public Void run() { - requestPasswordReset(email, authenticationUrl); - return null; - } - }.start(); - } +// public static RealmAsyncTask requestPasswordResetAsync(final String email, final String authenticationUrl, final Callback callback) { +// checkLooperThread("Asynchronous requesting a password reset is only possible from looper threads."); +// //noinspection ConstantConditions +// if (callback == null) { +// throw new IllegalArgumentException("Non-null 'callback' required."); +// } +// +// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { +// @Override +// public Void run() { +// requestPasswordReset(email, authenticationUrl); +// return null; +// } +// }.start(); +// } /** * Complete the password reset flow by using the reset token sent to the user's email as a one-time authorization @@ -559,20 +552,20 @@ public Void run() { * @throws IllegalArgumentException if no {@code token} or {@code newPassword} was provided. * @throws ObjectServerError if an error happened on the server. */ - public static void completePasswordReset(String resetToken, String newPassword, String authenticationUrl) { - if (Util.isEmptyString(resetToken)) { - throw new IllegalArgumentException("Not-null 'token' required."); - } - if (Util.isEmptyString(newPassword)) { - throw new IllegalArgumentException("Not-null 'newPassword' required."); - } - URL authUrl = getUrl(authenticationUrl); - RealmObjectServer authServer = SyncManager.getAuthServer(); - UpdateAccountResponse response = authServer.completePasswordReset(resetToken, newPassword, authUrl); - if (!response.isValid()) { - throw response.getError(); - } - } +// public static void completePasswordReset(String resetToken, String newPassword, String authenticationUrl) { +// if (Util.isEmptyString(resetToken)) { +// throw new IllegalArgumentException("Not-null 'token' required."); +// } +// if (Util.isEmptyString(newPassword)) { +// throw new IllegalArgumentException("Not-null 'newPassword' required."); +// } +// URL authUrl = getUrl(authenticationUrl); +// RealmObjectServer authServer = SyncManager.getAuthServer(); +// UpdateAccountResponse response = authServer.completePasswordReset(resetToken, newPassword, authUrl); +// if (!response.isValid()) { +// throw response.getError(); +// } +// } /** * Complete the password reset flow by using the reset token sent to the user's email as a one-time authorization @@ -595,24 +588,24 @@ public static void completePasswordReset(String resetToken, String newPassword, * @throws IllegalStateException if this method is called on a non-looper thread. * @throws IllegalArgumentException if no {@code token} or {@code newPassword} was provided. */ - public static RealmAsyncTask completePasswordResetAsync(final String resetToken, - final String newPassword, - final String authenticationUrl, - final Callback callback) throws ObjectServerError { - checkLooperThread("Asynchronously completing a password reset is only possible from looper threads."); - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public Void run() { - completePasswordReset(resetToken, newPassword, authenticationUrl); - return null; - } - }.start(); - } +// public static RealmAsyncTask completePasswordResetAsync(final String resetToken, +// final String newPassword, +// final String authenticationUrl, +// final Callback callback) throws ObjectServerError { +// checkLooperThread("Asynchronously completing a password reset is only possible from looper threads."); +// //noinspection ConstantConditions +// if (callback == null) { +// throw new IllegalArgumentException("Non-null 'callback' required."); +// } +// +// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { +// @Override +// public Void run() { +// completePasswordReset(resetToken, newPassword, authenticationUrl); +// return null; +// } +// }.start(); +// } /** * Request an email confirmation email to be sent to a user's email. @@ -627,17 +620,17 @@ public Void run() { * @throws IllegalArgumentException if no {@code email} was provided. * @throws ObjectServerError if an error happened on the server. */ - public static void requestEmailConfirmation(String email, String authenticationUrl) throws ObjectServerError { - if (Util.isEmptyString(email)) { - throw new IllegalArgumentException("Not-null 'email' required."); - } - URL authUrl = getUrl(authenticationUrl); - RealmObjectServer authServer = SyncManager.getAuthServer(); - UpdateAccountResponse response = authServer.requestEmailConfirmation(email, authUrl); - if (!response.isValid()) { - throw response.getError(); - } - } +// public static void requestEmailConfirmation(String email, String authenticationUrl) throws ObjectServerError { +// if (Util.isEmptyString(email)) { +// throw new IllegalArgumentException("Not-null 'email' required."); +// } +// URL authUrl = getUrl(authenticationUrl); +// RealmObjectServer authServer = SyncManager.getAuthServer(); +// UpdateAccountResponse response = authServer.requestEmailConfirmation(email, authUrl); +// if (!response.isValid()) { +// throw response.getError(); +// } +// } /** * Request an email confirmation email to be sent to a user's email. @@ -654,21 +647,21 @@ public static void requestEmailConfirmation(String email, String authenticationU * @throws IllegalStateException if this method is called on a non-looper thread. * @throws IllegalArgumentException if no {@code email} was provided. */ - public static RealmAsyncTask requestEmailConfirmationAsync(final String email, final String authenticationUrl, final Callback callback) { - checkLooperThread("Asynchronously requesting an email confirmation is only possible from looper threads."); - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public Void run() { - requestEmailConfirmation(email, authenticationUrl); - return null; - } - }.start(); - } +// public static RealmAsyncTask requestEmailConfirmationAsync(final String email, final String authenticationUrl, final Callback callback) { +// checkLooperThread("Asynchronously requesting an email confirmation is only possible from looper threads."); +// //noinspection ConstantConditions +// if (callback == null) { +// throw new IllegalArgumentException("Non-null 'callback' required."); +// } +// +// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { +// @Override +// public Void run() { +// requestEmailConfirmation(email, authenticationUrl); +// return null; +// } +// }.start(); +// } /** * Complete the email confirmation flow by using the confirmation token sent to the user's email as a one-time @@ -688,17 +681,17 @@ public Void run() { * @throws IllegalArgumentException if no {@code confirmationToken} was provided. * @throws ObjectServerError if an error happened on the server. */ - public static void confirmEmail(String confirmationToken, String authenticationUrl) throws ObjectServerError { - if (Util.isEmptyString(confirmationToken)) { - throw new IllegalArgumentException("Not-null 'confirmationToken' required."); - } - URL authUrl = getUrl(authenticationUrl); - RealmObjectServer authServer = SyncManager.getAuthServer(); - UpdateAccountResponse response = authServer.confirmEmail(confirmationToken, authUrl); - if (!response.isValid()) { - throw response.getError(); - } - } +// public static void confirmEmail(String confirmationToken, String authenticationUrl) throws ObjectServerError { +// if (Util.isEmptyString(confirmationToken)) { +// throw new IllegalArgumentException("Not-null 'confirmationToken' required."); +// } +// URL authUrl = getUrl(authenticationUrl); +// RealmObjectServer authServer = SyncManager.getAuthServer(); +// UpdateAccountResponse response = authServer.confirmEmail(confirmationToken, authUrl); +// if (!response.isValid()) { +// throw response.getError(); +// } +// } /** * Complete the email confirmation flow by using the confirmation token sent to the user's email as a one-time @@ -720,23 +713,23 @@ public static void confirmEmail(String confirmationToken, String authenticationU * @throws IllegalStateException if this method is called on a non-looper thread. * @throws IllegalArgumentException if no {@code confirmationToken} was provided. */ - public static RealmAsyncTask confirmEmailAsync(final String confirmationToken, - final String authenticationUrl, - final Callback callback) { - checkLooperThread("Asynchronously confirming an email is only possible from looper threads."); - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public Void run() { - confirmEmail(confirmationToken, authenticationUrl); - return null; - } - }.start(); - } +// public static RealmAsyncTask confirmEmailAsync(final String confirmationToken, +// final String authenticationUrl, +// final Callback callback) { +// checkLooperThread("Asynchronously confirming an email is only possible from looper threads."); +// //noinspection ConstantConditions +// if (callback == null) { +// throw new IllegalArgumentException("Non-null 'callback' required."); +// } +// +// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { +// @Override +// public Void run() { +// confirmEmail(confirmationToken, authenticationUrl); +// return null; +// } +// }.start(); +// } /** * Given a Realm Object Server authentication provider and a provider identifier for a user (for example, a username), look up and return user information for that user. @@ -751,31 +744,31 @@ public Void run() { * @throws IllegalArgumentException if no {@code providerUserIdentity} or {@code provider} string was provided. * @throws ObjectServerError if an error happened on the server. */ - public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final String provider) throws ObjectServerError { - if (Util.isEmptyString(providerUserIdentity)) { - throw new IllegalArgumentException("'providerUserIdentity' cannot be empty."); - } - - if (Util.isEmptyString(provider)) { - throw new IllegalArgumentException("'provider' cannot be empty."); - } - - if (!isAdmin()) { - throw new IllegalArgumentException("SyncUser needs to be admin in order to lookup other users ID."); - } - - RealmObjectServer authServer = SyncManager.getAuthServer(); - LookupUserIdResponse response = authServer.retrieveUser(refreshToken, provider, providerUserIdentity, getAuthenticationUrl()); - if (!response.isValid()) { - if (response.getError().getErrorCode() == ErrorCode.UNKNOWN_ACCOUNT) { - return null; - } else { - throw response.getError(); - } - } else { - return SyncUserInfo.fromLookupUserIdResponse(response); - } - } +// public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final String provider) throws ObjectServerError { +// if (Util.isEmptyString(providerUserIdentity)) { +// throw new IllegalArgumentException("'providerUserIdentity' cannot be empty."); +// } +// +// if (Util.isEmptyString(provider)) { +// throw new IllegalArgumentException("'provider' cannot be empty."); +// } +// +// if (!isAdmin()) { +// throw new IllegalArgumentException("SyncUser needs to be admin in order to lookup other users ID."); +// } +// +// RealmObjectServer authServer = SyncManager.getAuthServer(); +// LookupUserIdResponse response = authServer.retrieveUser(refreshToken, provider, providerUserIdentity, getAuthenticationUrl()); +// if (!response.isValid()) { +// if (response.getError().getErrorCode() == ErrorCode.UNKNOWN_ACCOUNT) { +// return null; +// } else { +// throw response.getError(); +// } +// } else { +// return SyncUserInfo.fromLookupUserIdResponse(response); +// } +// } /** * Given a Realm Object Server authentication provider and a provider identifier for a user (for example, a username), asynchronously look up and return user information for that user. @@ -788,20 +781,20 @@ public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final * as this method is called on. * @return representation of the async task that can be used to cancel it if needed. */ - public RealmAsyncTask retrieveInfoForUserAsync(final String providerUserIdentity, final String provider, final Callback callback) { - checkLooperThread("Asynchronously retrieving user is only possible from looper threads."); - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public SyncUserInfo run() throws ObjectServerError { - return retrieveInfoForUser(providerUserIdentity, provider); - } - }.start(); - } +// public RealmAsyncTask retrieveInfoForUserAsync(final String providerUserIdentity, final String provider, final Callback callback) { +// checkLooperThread("Asynchronously retrieving user is only possible from looper threads."); +// //noinspection ConstantConditions +// if (callback == null) { +// throw new IllegalArgumentException("Non-null 'callback' required."); +// } +// +// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { +// @Override +// public SyncUserInfo run() throws ObjectServerError { +// return retrieveInfoForUser(providerUserIdentity, provider); +// } +// }.start(); +// } private static void checkLooperThread(String errorMessage) { AndroidCapabilities capabilities = new AndroidCapabilities(); @@ -841,7 +834,9 @@ public String toJson() { * @return {@code true} if the User is logged into the Realm Object Server, {@code false} otherwise. */ public boolean isValid() { - return refreshToken != null && refreshToken.expiresMs() > System.currentTimeMillis() && SyncManager.getUserStore().isActive(identity, authenticationUrl.toString()); + // FIXME + /* && SyncManager.getUserStore().isActive(identity, authenticationUrl.toString()*/ + return refreshToken != null && refreshToken.expiresMs() > System.currentTimeMillis(); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java index a9a1369966..6706a196b6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java @@ -19,8 +19,6 @@ import java.util.Collections; import java.util.Map; -import io.realm.internal.network.LookupUserIdResponse; - /** * POJO representing information about a user that was retrieved from a user lookup call. * @see SyncUser#retrieveInfoForUser(String, String) @@ -39,9 +37,9 @@ private SyncUserInfo(String identity, boolean isAdmin, Map metad this.accounts = Collections.unmodifiableMap(accounts); } - static SyncUserInfo fromLookupUserIdResponse(LookupUserIdResponse response) { - return new SyncUserInfo(response.getUserId(), response.isAdmin(), response.getMetadata(), response.getAccounts()); - } +// static SyncUserInfo fromLookupUserIdResponse(LookupUserIdResponse response) { +// return new SyncUserInfo(response.getUserId(), response.isAdmin(), response.getMetadata(), response.getAccounts()); +// } /** * @return the identity issued to this user by the Realm Object Server. diff --git a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java b/realm/realm-library/src/objectServer/java/io/realm/UserStore.java deleted file mode 100644 index 584f8e7ae4..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/UserStore.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import java.util.Collection; - -import javax.annotation.Nullable; - - -/** - * Interface for classes responsible for saving and retrieving Object Server users again. - *

            - * Any implementation of a User Store is expected to not perform lengthy blocking operations as it might - * be called on the Main Thread. All implementations of this interface should be thread safe. - * - * @see SyncManager#setUserStore(UserStore) - * @see RealmFileUserStore - */ -public interface UserStore { - - /** - * Saves a {@link SyncUser} object. If another user already exists, it will be replaced. - * {@link SyncUser#getIdentity()} is used as a unique identifier of a given {@link SyncUser}. - * - * @param user {@link SyncUser} object to store. - */ - void put(SyncUser user); - - /** - * Retrieves the current {@link SyncUser}. - *

            - * This method will throw an exception if more than one valid, logged in users exist. - * @return {@link SyncUser} object or {@code null} if not found. - */ - @Nullable - SyncUser getCurrent(); - - /** - * Retrieves specified {@link SyncUser}. - * - * @param identity identity of the user. - * @param authenticationUrl the URL of the authentication. - * @return {@link SyncUser} object or {@code null} if not found. - */ - @Nullable - SyncUser get(String identity, String authenticationUrl); - - /** - * Removes the user from the store. - *

            - * If the user is not found, this method does nothing. - * - * @param identity identity of the user. - * @param authenticationUrl the URL of the authentication. - */ - void remove(String identity, String authenticationUrl); - - /** - * Returns a collection of all users saved in the User store. - * - * @return Collection of all users. If no users exist, an empty collection is returned. - */ - Collection allUsers(); - - /** - * Returns the state of the specified user: {@code true} if active (not logged out), {@code false} otherwise. - * This method checks if the user was marked as logged out. If the user has expired but not actively logged out - * this method will return {@code true}. - * - * @param identity identity of the user. - * @param authenticationUrl the URL of the authentication. - * @return {@code true} if the user is not logged out, {@code false} otherwise. - */ - boolean isActive(String identity, String authenticationUrl); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java deleted file mode 100644 index 1f2786c241..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthServerResponse.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.realm.internal.network; -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -import org.json.JSONException; -import org.json.JSONObject; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; - -/** - * Base class for all response types from the Realm Authentication Server. - */ -public abstract class AuthServerResponse { - - protected ObjectServerError error; - - /** - * Checks if this response was valid. - * - * @return {@code true} if valid, {@code false} otherwise. - */ - public boolean isValid() { - return (error == null); - } - - /** - * If {@link #isValid()} returns {@code false}, this method will return the error causing this. - * - * @return the error. - */ - public ObjectServerError getError() { - return error; - } - - protected void setError(ObjectServerError error) { - this.error = error; - } - - /** - * Parse an HTTP error from a Realm Authentication Server. The server returns errors following - * https://tools.ietf.org/html/rfc7807 with an extra "code" field for Realm specific error codes. - * - * @param response the server response. - * @param httpErrorCode the HTTP error code. - * @return an server error. - */ - public static ObjectServerError createError(String response, int httpErrorCode) { - try { - JSONObject obj = new JSONObject(response); - String title = obj.optString("title", null); - String hint = obj.optString("hint", null); - ErrorCode errorCode; - if (obj.has("code")) { - errorCode = ErrorCode.fromNativeError(ErrorCode.Type.AUTH, obj.getInt("code")); - } else if (obj.has("status")) { - errorCode = ErrorCode.fromNativeError(ErrorCode.Type.AUTH, obj.getInt("status")); - } else { - errorCode = ErrorCode.UNKNOWN; - } - return new ObjectServerError(errorCode, title, hint); - } catch (JSONException e) { - return new ObjectServerError(ErrorCode.JSON_EXCEPTION, "Server failed with " + - httpErrorCode + ", but could not parse error.", e); - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java deleted file mode 100644 index bc333b9ab0..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateRequest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.net.URI; -import java.util.Collections; -import java.util.Map; - -import io.realm.internal.objectserver.Token; -import io.realm.SyncCredentials; -import io.realm.SyncManager; - -/** - * This class encapsulates a request to authenticate a user on the Realm Authentication Server. It is responsible for - * constructing the JSON understood by the Realm Authentication Server. - */ -public class AuthenticateRequest { - - private final String provider; - private final String data; - private final String appId; - private final Map userInfo; - private final String path; - - /** - * Generates a proper login request for a new user. - */ - public static AuthenticateRequest userLogin(SyncCredentials credentials) { - if (credentials == null) { - throw new IllegalArgumentException("Non-null credentials required."); - } - String provider = credentials.getIdentityProvider(); - String data = credentials.getUserIdentifier(); - Map userInfo = credentials.getUserInfo(); - String appId = SyncManager.APP_ID; - return new AuthenticateRequest(provider, data, appId, null, userInfo); - } - - /** - * Generates a request for refreshing a user token. - */ - public static AuthenticateRequest userRefresh(Token userToken, String serverUrl) { - return new AuthenticateRequest("realm", - userToken.value(), - SyncManager.APP_ID, - serverUrl, - Collections.emptyMap() - ); - } - - /** - * Generates a request for accessing a Realm - */ - public static AuthenticateRequest realmLogin(Token userToken, String serverUrl) { - // Authenticate a given Realm path using an already logged in user. - return new AuthenticateRequest("realm", - userToken.value(), - SyncManager.APP_ID, - serverUrl, - Collections.emptyMap() - ); - } - - private AuthenticateRequest(String provider, String data, String appId, String path, Map userInfo) { - this.provider = provider; - this.data = data; - this.appId = appId; - this.path = path; - this.userInfo = userInfo; - } - - /** - * Converts the request into a JSON payload. - */ - public String toJson() { - JSONObject request = new JSONObject(); - try { - request.put("provider", provider); - request.put("data", data); - request.put("app_id", appId); - if (path != null) { - request.put("path", path); - } - request.put("user_info", new JSONObject(userInfo)); - } catch (JSONException e) { - throw new RuntimeException(e); - } - - return request.toString(); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java deleted file mode 100644 index 14b205cc06..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.util.Locale; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.internal.objectserver.Token; -import io.realm.internal.objectserver.SyncWorker; -import io.realm.log.RealmLog; -import okhttp3.Response; - -/** - * This class represents the response for an authenticate request. - */ -public class AuthenticateResponse extends AuthServerResponse { - - private static final String JSON_FIELD_ACCESS_TOKEN = "access_token"; - private static final String JSON_FIELD_REFRESH_TOKEN = "refresh_token"; - private static final String JSON_FIELD_SYNC_WORKER = "sync_worker"; - - private final Token accessToken; - private final Token refreshToken; - private final SyncWorker syncWorker; - - /** - * Helper method for creating the proper Authenticate response. This method will set the appropriate error - * depending on any HTTP response codes or IO errors. - * - * @param response the HTTP response. - * @return an authenticate response. - */ - public static AuthenticateResponse from(Response response) { - String serverResponse; - try { - serverResponse = response.body().string(); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new AuthenticateResponse(error); - } - if (!response.isSuccessful()) { - return new AuthenticateResponse(AuthServerResponse.createError(serverResponse, response.code())); - } else { - return new AuthenticateResponse(serverResponse); - } - } - - /** - * Helper method for creating the response from a JSON string. - */ - public static AuthenticateResponse from(String json) { - return new AuthenticateResponse(json); - } - - /** - * Helper method for creating a failed response. - */ - public static AuthenticateResponse from(ObjectServerError error) { - return new AuthenticateResponse(error); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static AuthenticateResponse from(Exception exception) { - return AuthenticateResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - /** - * Helper method for creating a valid user login response. The user returned will be assumed to have all permissions - * and doesn't expire. - * - * @param identifier user identifier. - * @param refreshToken user's refresh token. - */ - public static AuthenticateResponse createValidResponseWithUser(String identifier, String refreshToken, boolean isAdmin) { - try { - JSONObject response = new JSONObject(); - response.put(JSON_FIELD_REFRESH_TOKEN, new Token(refreshToken, identifier, null, Long.MAX_VALUE, Token.Permission.ALL, isAdmin).toJson()); - return new AuthenticateResponse(response.toString()); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - /** - * Creates an unsuccessful authentication response. This should only happen in case of network or I/O related - * issues. - * - * @param error the network or I/O error. - */ - private AuthenticateResponse(ObjectServerError error) { - setError(error); - this.accessToken = null; - this.refreshToken = null; - this.syncWorker = null; - } - - /** - * Parses a valid (200) server response. It might still result in an unsuccessful authentication attempt, if the - * JSON response could not be parsed correctly. - * - * @param serverResponse the server response. - */ - private AuthenticateResponse(String serverResponse) { - ObjectServerError error; - Token accessToken; - Token refreshToken; - SyncWorker syncWorker; - String debugMessage; - try { - JSONObject obj = new JSONObject(serverResponse); - accessToken = obj.has(JSON_FIELD_ACCESS_TOKEN) ? Token.from(obj.getJSONObject(JSON_FIELD_ACCESS_TOKEN)) : null; - refreshToken = obj.has(JSON_FIELD_REFRESH_TOKEN) ? Token.from(obj.getJSONObject(JSON_FIELD_REFRESH_TOKEN)) : null; - syncWorker = obj.has(JSON_FIELD_SYNC_WORKER) ? SyncWorker.from(obj.getJSONObject(JSON_FIELD_SYNC_WORKER)) : null; - error = null; - if (accessToken == null) { - debugMessage = "accessToken = null"; - } else { - debugMessage = String.format(Locale.US, "Identity %s; Path %s", accessToken.identity(), accessToken.path()); - } - } catch (JSONException ex) { - accessToken = null; - refreshToken = null; - syncWorker = null; - String exceptionMessage = String.format(Locale.US, "Server response could not be parsed as JSON:%n%s", serverResponse); - //noinspection ThrowableInstanceNeverThrown - error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, exceptionMessage, ex); - debugMessage = String.format(Locale.US, "Error %s", error.getErrorMessage()); - } - RealmLog.debug("AuthenticateResponse. " + debugMessage); - setError(error); - this.accessToken = accessToken; - this.refreshToken = refreshToken; - this.syncWorker = syncWorker; - } - - public Token getAccessToken() { - return accessToken; - } - - public Token getRefreshToken() { - return refreshToken; - } - - public SyncWorker getSyncWorker() { - return syncWorker; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java deleted file mode 100644 index b3ee747053..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordRequest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; - -import io.realm.internal.objectserver.Token; - -/** - * This class encapsulates a request to change the password for a user on the Realm Authentication Server. It is - * responsible for constructing the JSON understood by the Realm Authentication Server. - */ -public class ChangePasswordRequest { - - private final String token; - private final String newPassword; - private String userID; //optional, used to change the password when using the admin account. - - public static ChangePasswordRequest create(Token userToken, String newPassword) { - return new ChangePasswordRequest(userToken.value(), newPassword); - } - - public static ChangePasswordRequest create(Token adminToken, String userID, String newPassword) { - return new ChangePasswordRequest(adminToken.value(), newPassword, userID); - } - - private ChangePasswordRequest(String token, String newPassword) { - this.token = token; - this.newPassword = newPassword; - } - - private ChangePasswordRequest(String token, String newPassword, String userID) { - this.token = token; - this.newPassword = newPassword; - this.userID = userID; - } - - /** - * Converts the request into a JSON payload. - */ - public String toJson() { - try { - JSONObject request = new JSONObject(); - if (userID != null) { - request.put("user_id", userID); - } - JSONObject data = new JSONObject(); - data.put("new_password", newPassword); - request.put("data", data); - return request.toString(); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java deleted file mode 100644 index 777869ab6b..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ChangePasswordResponse.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import java.io.IOException; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import okhttp3.Response; - -/** - * Class wrapping the response from `/auth/password` - */ -public class ChangePasswordResponse extends AuthServerResponse { - - /** - * Helper method for creating the proper change password response. This method will set the appropriate error - * depending on any HTTP response codes or I/O errors. - * - * @param response the server response. - * @return the change password response. - */ - static ChangePasswordResponse from(Response response) { - if (response.isSuccessful()) { - return new ChangePasswordResponse(); - } - try { - String serverResponse = response.body().string(); - return new ChangePasswordResponse(AuthServerResponse.createError(serverResponse, response.code())); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new ChangePasswordResponse(error); - } - } - - /** - * Helper method for creating a failed response. - */ - public static ChangePasswordResponse from(ObjectServerError objectServerError) { - return new ChangePasswordResponse(objectServerError); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static ChangePasswordResponse from(Exception exception) { - return ChangePasswordResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - private ChangePasswordResponse() { - this.error = null; - } - - private ChangePasswordResponse(ObjectServerError error) { - this.error = error; - } - -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java deleted file mode 100644 index f23dc5022b..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import java.util.concurrent.TimeUnit; - -import io.realm.ErrorCode; -import io.realm.log.RealmLog; - -/** - * Abstracts the concept of running an network task with incremental backoff. It will run forever until interrupted. - */ -public abstract class ExponentialBackoffTask implements Runnable { - private final int maxRetries; - - public ExponentialBackoffTask(int maxRetries) { - this.maxRetries = maxRetries; - } - - public ExponentialBackoffTask() { - this(Integer.MAX_VALUE - 1); - } - - // Task to perform - protected abstract T execute(); - - // Check if the task was successful - protected boolean isSuccess(T result) { - return result != null && result.isValid(); - } - - // Return true if based on the task result that this task will never complete - protected boolean shouldAbortTask(T response) { - // Only retry in case of IO exceptions, since that might be network timeouts etc. - // All other errors indicate a bigger problem, so just stop the task. - if (Thread.interrupted()) { - return true; - } else if (!response.isValid()) { - return response.getError().getErrorCode() != ErrorCode.IO_EXCEPTION; - } else { - return false; - } - } - - // Callback when task have succeeded - protected abstract void onSuccess(T response); - - // Callback when task has failed - protected abstract void onError(T response); - - @Override - public void run() { - int attempt = 0; - while (!Thread.interrupted()) { - attempt++; - long sleep = calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); - if (sleep > 0) { - try { - Thread.sleep(sleep); - } catch (InterruptedException e) { - RealmLog.debug("Incremental backoff was interrupted."); - return; // Abort if interrupted - } - } - T response = execute(); - - if (isSuccess(response)) { - onSuccess(response); - break; - } else { - if (shouldAbortTask(response) || attempt == maxRetries + 1) { - onError(response); - break; - } - } - } - } - - private static long calculateExponentialDelay(int failedAttempts, long maxDelayInMs) { - // https://en.wikipedia.org/wiki/Exponential_backoff - //Attempt = FailedAttempts + 1 - //Attempt 1 0s 0s - //Attempt 2 2s 2s - //Attempt 3 4s 4s - //Attempt 4 8s 8s - //Attempt 5 16s 16s - //Attempt 6 32s 32s - //Attempt 7 64s 1m 4s - //Attempt 8 128s 2m 8s - //Attempt 9 256s 4m 16s - //Attempt 10 512 8m 32s - //Attempt 11 1024 17m 4s - //Attempt 12 2048 34m 8s - //Attempt 13 4096 1h 8m 16s - //Attempt 14 8192 2h 16m 32s - //Attempt 15 16384 4h 33m 4s - double SCALE = 1.0D; // Scale the exponential backoff - double delayInMs = ((Math.pow(2.0D, failedAttempts) - 1d) / 2.0D) * 1000 * SCALE; - - // Just use maximum back-off value. We are not afraid of many threads using this value - // to trigger at once. - return maxDelayInMs < delayInMs ? maxDelayInMs : (long) delayInMs; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java deleted file mode 100644 index 49a30ade75..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutRequest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; - -import io.realm.internal.objectserver.Token; - -/** - * This class encapsulates a request to log out a user on the Realm Authentication Server. It is responsible for - * constructing the JSON understood by the Realm Authentication Server. - */ -public class LogoutRequest { - - private final String token; - - public static LogoutRequest create(Token userToken) { - return new LogoutRequest(userToken.value()); - } - - private LogoutRequest(String token) { - this.token = token; - } - - /** - * Converts the request into a JSON payload. - */ - public String toJson() { - try { - JSONObject request = new JSONObject(); - request.put("token", token); - return request.toString(); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java deleted file mode 100644 index f13fbf6b2e..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LogoutResponse.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import java.io.IOException; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.log.RealmLog; -import okhttp3.Response; - -/** - * This class represents the response for a log out request. - */ -public class LogoutResponse extends AuthServerResponse { - - /** - * Helper method for creating the proper Logout response. This method will set the appropriate error - * depending on any HTTP response codes or I/O errors. - * - * @param response the server response. - * @return the log out response. - */ - static LogoutResponse from(Response response) { - if (response.isSuccessful()) { - // success - return new LogoutResponse(); - } - try { - String serverResponse = response.body().string(); - return new LogoutResponse(AuthServerResponse.createError(serverResponse, response.code())); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new LogoutResponse(error); - } - } - - /** - * Helper method for creating a failed response. - */ - public static LogoutResponse from(ObjectServerError error) { - return new LogoutResponse(error); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static LogoutResponse from(Exception exception) { - return LogoutResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - /** - * Creates an unsuccessful authentication response. This should only happen in case of network or I/O - * related issues. - * - * @param error an authentication response error. - */ - private LogoutResponse(ObjectServerError error) { - RealmLog.debug("Logout response - Error: " + error.getErrorMessage()); - setError(error); - } - - /** - * Parses a valid (204) server response. - */ - private LogoutResponse() { - RealmLog.debug("Logout response - Success"); - setError(null); - } - - /** - * Checks if response was valid. - * - * @return {@code true} if valid. - */ - @Override - public boolean isValid() { - return (error == null) || (error.getErrorCode() == ErrorCode.EXPIRED_REFRESH_TOKEN); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java deleted file mode 100644 index 810b4ee87f..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LookupUserIdResponse.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ -package io.realm.internal.network; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.log.RealmLog; -import okhttp3.Response; - -/** - * Class wrapping the response from `GET /auth/users/:userId` - */ -public class LookupUserIdResponse extends AuthServerResponse { - - private static final String JSON_FIELD_USER_ID = "user_id"; - private static final String JSON_FIELD_USER_IS_ADMIN = "is_admin"; - private static final String JSON_FIELD_METADATA = "metadata"; - private static final String JSON_FIELD_ACCOUNTS = "accounts"; - - private final String userId; - private final Boolean isAdmin; - private final Map metadata; - private final Map accounts; - /** - * Helper method for creating the proper lookup user response. This method will set the appropriate error - * depending on any HTTP response codes or I/O errors. - * - * @param response the server response. - * @return the user lookup response. - */ - static LookupUserIdResponse from(Response response) { - String serverResponse; - try { - serverResponse = response.body().string(); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new LookupUserIdResponse(error); - } - if (!response.isSuccessful()) { - return new LookupUserIdResponse(AuthServerResponse.createError(serverResponse, response.code())); - } else { - return new LookupUserIdResponse(serverResponse); - } - } - - /** - * Helper method for creating a failed response. - */ - public static LookupUserIdResponse from(ObjectServerError objectServerError) { - return new LookupUserIdResponse(objectServerError); - } - - /** - * Helper method for creating a failed response from an {@link Exception}. - */ - public static LookupUserIdResponse from(Exception exception) { - return LookupUserIdResponse.from(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - private LookupUserIdResponse(ObjectServerError error) { - RealmLog.debug("LookupUserIdResponse - Error: " + error); - setError(error); - this.error = error; - this.userId = null; - this.isAdmin = null; - this.metadata = new HashMap<>(); - this.accounts = new HashMap<>(); - } - - private LookupUserIdResponse(String serverResponse) { - ObjectServerError error; - String userId; - Boolean isAdmin; - String message; - Map metadata; - Map accounts; - try { - JSONObject obj = new JSONObject(serverResponse); - userId = obj.getString(JSON_FIELD_USER_ID); - isAdmin = obj.getBoolean(JSON_FIELD_USER_IS_ADMIN); - metadata = jsonToMap(obj.getJSONArray(JSON_FIELD_METADATA), "key", "value"); - accounts = jsonToMap(obj.getJSONArray(JSON_FIELD_ACCOUNTS), "provider", "provider_id"); - error = null; - - message = String.format(Locale.US, "Identity %s; Path %b", userId, isAdmin); - - } catch (JSONException e) { - userId = null; - isAdmin = null; - metadata = new HashMap<>(); - accounts = new HashMap<>(); - error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, e); - message = String.format(Locale.US, "Error %s", error.getErrorMessage()); - } - - RealmLog.debug("LookupUserIdResponse. " + message); - setError(error); - this.userId = userId; - this.isAdmin = isAdmin; - this.metadata = metadata; - this.accounts = accounts; - } - - public String getUserId() { - return userId; - } - - public boolean isAdmin() { - return isAdmin; - } - - public Map getMetadata() { return metadata; } - - public Map getAccounts() { return accounts; } - - // Assume arrays of key/value irrespectively of what they are named. - // Throws if this is not the case - private static Map jsonToMap(JSONArray array, String keyName, String valueName) throws JSONException { - Map map = new HashMap<>(); - if (array == null) { - return map; - } - for (int i = 0; i < array.length(); i++) { - JSONObject obj = array.getJSONObject(i); - if (obj.length() != 2) { - throw new IllegalStateException("Array object not a key/value object. Has " + obj.length() + " fields"); - } - map.put(obj.getString(keyName), obj.getString(valueName)); - } - return map; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java new file mode 100644 index 0000000000..f195a65d44 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -0,0 +1,132 @@ +package io.realm.internal.network; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.RealmApp; +import io.realm.RealmAsyncTask; +import io.realm.SyncManager; +import io.realm.internal.RealmNotifier; +import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.android.AndroidRealmNotifier; +import io.realm.internal.async.RealmAsyncTaskImpl; +import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; +import okhttp3.Call; +import okhttp3.ConnectionPool; +import okhttp3.Headers; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import okio.Buffer; + +public class OkHttpNetworkTransport extends OsJavaNetworkTransport { + + + public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static final Charset UTF8 = Charset.forName("UTF-8"); + private volatile OkHttpClient client = null; + + @Override + public Response sendRequest(String method, String url, long timeoutMs, Map headers, String body) { + try { + OkHttpClient client = getClient(timeoutMs); + okhttp3.Response response = null; + try { + Request.Builder builder = new Request.Builder().url(url); + switch(method) { + case "get": builder.get(); break; + case "delete": builder.delete(RequestBody.create(JSON, body)); break; + case "patch": builder.patch(RequestBody.create(JSON, body)); break; + case "post": builder.post(RequestBody.create(JSON, body)); break; + case "put": builder.put(RequestBody.create(JSON, body)); break; + default: throw new IllegalArgumentException("Unknown method type: "+ method); + } + + for (Map.Entry entry : headers.entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + Call call = client.newCall(builder.build()); + response = call.execute(); + ResponseBody responseBody = response.body(); + String result = ""; + if (responseBody != null) { + result = responseBody.string(); + } + return Response.httpResponse(response.code(), parseHeaders(response.headers()), result); + } catch (IOException ex) { + return Response.ioError(ex.toString()); + } catch (Exception ex) { + return Response.unknownError(ex.toString()); + } finally { + if (response != null) { + response.close(); + } + } + } catch (Exception e) { + return Response.unknownError(e.toString()); + } + } + + // Lazily creates the client if not already created + // TODO: timeOuts are not expected to change between requests. So for now just use the timeout first send. + private synchronized OkHttpClient getClient(long timeoutMs) { + if (client == null) { + client = new OkHttpClient.Builder() + .callTimeout(timeoutMs, TimeUnit.MILLISECONDS) + .followRedirects(true) + .addInterceptor(new Interceptor() { + @Override + public okhttp3.Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + if (RealmLog.getLevel() <= LogLevel.TRACE) { + StringBuilder sb = new StringBuilder(request.method()); + sb.append(' '); + sb.append(request.url()); + sb.append('\n'); + sb.append(request.headers()); + if (request.body() != null) { + // Stripped down version of https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java + // We only expect request context to be JSON. + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + sb.append(buffer.readString(UTF8)); + } + RealmLog.trace("HTTP Request = \n%s", sb); + } + return chain.proceed(request); + } + }) + // using custom Connection Pool to evict idle connection after 5 seconds rather than 5 minutes (which is the default) + // keeping idle connection on the pool will prevent the ROS to be stopped, since the HttpUtils#stopSyncServer query + // will not return before the tests timeout (ex 10 seconds for AuthTests) + .connectionPool(new ConnectionPool(5, 5, TimeUnit.SECONDS)) + .build(); + } + + return client; + } + + // Parse Headers outputtet from OKHttp to the format expected by ObjectStore + private Map parseHeaders(Headers headers) { + HashMap osHeaders = new HashMap<>(headers.size()/2); + for (String key : headers.names()) { + osHeaders.put(key, headers.get(key)); + } + return osHeaders; + } + +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java deleted file mode 100644 index 2b97a62a6e..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpRealmObjectServer.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nullable; - -import io.realm.SyncCredentials; -import io.realm.internal.Util; -import io.realm.internal.objectserver.Token; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; -import okhttp3.Call; -import okhttp3.ConnectionPool; -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okio.Buffer; - -public class OkHttpRealmObjectServer implements RealmObjectServer { - - public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static final String ACTION_LOGOUT = "revoke"; // Auth end point for logging out users - private static final String ACTION_CHANGE_PASSWORD = "password"; // Auth end point for changing passwords - private static final String ACTION_LOOKUP_USER_ID = "users/:provider:/:providerId:"; // Auth end point for looking up user id - private static final String ACTION_UPDATE_ACCOUNT = "password/updateAccount"; // Password reset and email confirmation - private static final String ACTION_GET_PERMISSIONS = "permissions"; - private static final String ACTION_UPDATE_PERMISSIONS = "permissions/apply"; - private static final String ACTION_OFFER_PERMISSIONS = "permissions/offers"; - private static final String ACTION_ACCEPT_PERMISSIONS_OFFER = "permissions/offers/:token:/accept"; - private static final String ACTION_DELETE_PERMISSIONS_OFFER = "permissions/offers/:token:"; - private static final String ACTION_GET_PERMISSION_OFFERS = "permissions/offers"; - - private static final Charset UTF8 = Charset.forName("UTF-8"); - - private final OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(15, TimeUnit.SECONDS) - .writeTimeout(15, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .followRedirects(true) - .addInterceptor(new Interceptor() { - @Override - public Response intercept(Chain chain) throws IOException { - Request request = chain.request(); - if (RealmLog.getLevel() <= LogLevel.TRACE) { - StringBuilder sb = new StringBuilder(request.method()); - sb.append(' '); - sb.append(request.url()); - sb.append('\n'); - sb.append(request.headers()); - if (request.body() != null) { - // Stripped down version of https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java - // We only expect request context to be JSON. - Buffer buffer = new Buffer(); - request.body().writeTo(buffer); - sb.append(buffer.readString(UTF8)); - } - RealmLog.trace("HTTP Request = \n%s", sb); - } - return chain.proceed(request); - } - }) - // using custom Connection Pool to evict idle connection after 5 seconds rather than 5 minutes (which is the default) - // keeping idle connection on the pool will prevent the ROS to be stopped, since the HttpUtils#stopSyncServer query - // will not return before the tests timeout (ex 10 seconds for AuthTests) - .connectionPool(new ConnectionPool(5, 5, TimeUnit.SECONDS)) - .build(); - - private Map> customHeaders = new LinkedHashMap<>(); - private Map customAuthorizationHeaders = new HashMap<>(); - - public OkHttpRealmObjectServer() { - initHeaders(); - } - - private void initHeaders() { - customAuthorizationHeaders.put("", "Authorization"); // Default value for authorization header - customHeaders.put("", new LinkedHashMap<>()); // Add holder for headers used across all hosts - } - - @Override - public void setAuthorizationHeaderName(String headerName, @Nullable String host) { - if (Util.isEmptyString(host)) { - customAuthorizationHeaders.put("", headerName); - } else { - customAuthorizationHeaders.put(host, headerName); - } - } - - @Override - public void addHeader(String headerName, String headerValue, @Nullable String host) { - if (Util.isEmptyString(host)) { - customHeaders.get("").put(headerName, headerValue); - } else { - Map headers = customHeaders.get(host); - if (headers == null) { - headers = new LinkedHashMap<>(); - customHeaders.put(host, headers); - } - headers.put(headerName, headerValue); - } - } - - @Override - public void clearCustomHeaderSettings() { - customAuthorizationHeaders.clear(); - customHeaders.clear(); - initHeaders(); - } - - /** - * Authenticate the given credentials on the specified Realm Authentication Server. - */ - @Override - public AuthenticateResponse loginUser(SyncCredentials credentials, URL authenticationUrl) { - try { - String requestBody = AuthenticateRequest.userLogin(credentials).toJson(); - return authenticate(authenticationUrl, requestBody); - } catch (Exception e) { - return AuthenticateResponse.from(e); - } - } - - @Override - public AuthenticateResponse loginToRealm(Token refreshToken, URI serverUrl, URL authenticationUrl) { - try { - String requestBody = AuthenticateRequest.realmLogin(refreshToken, serverUrl.getPath()).toJson(); - return authenticate(authenticationUrl, requestBody); - } catch (Exception e) { - return AuthenticateResponse.from(e); - } - } - - @Override - public AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL authenticationUrl) { - try { - String requestBody = AuthenticateRequest.userRefresh(userToken, serverUrl.getPath()).toJson(); - return authenticate(authenticationUrl, requestBody); - } catch (Exception e) { - return AuthenticateResponse.from(e); - } - } - - @Override - public LogoutResponse logout(Token userToken, URL authenticationUrl) { - try { - String requestBody = LogoutRequest.create(userToken).toJson(); - return logout(buildActionUrl(authenticationUrl, ACTION_LOGOUT), userToken.value(), requestBody); - } catch (Exception e) { - return LogoutResponse.from(e); - } - } - - @Override - public ChangePasswordResponse changePassword(Token userToken, String newPassword, URL authenticationUrl) { - try { - String requestBody = ChangePasswordRequest.create(userToken, newPassword).toJson(); - return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD), userToken.value(), requestBody); - } catch (Exception e) { - return ChangePasswordResponse.from(e); - } - } - - @Override - public ChangePasswordResponse changePassword(Token adminToken, String userId, String newPassword, URL authenticationUrl) { - try { - String requestBody = ChangePasswordRequest.create(adminToken, userId, newPassword).toJson(); - return changePassword(buildActionUrl(authenticationUrl, ACTION_CHANGE_PASSWORD), adminToken.value(), requestBody); - } catch (Exception e) { - return ChangePasswordResponse.from(e); - } - } - - @Override - public LookupUserIdResponse retrieveUser(Token adminToken, String provider, String providerId, URL authenticationUrl) { - try { - String action = ACTION_LOOKUP_USER_ID - .replace(":provider:", provider) - .replace(":providerId:", providerId); - return lookupUserId(buildActionUrl(authenticationUrl, action), adminToken.value()); - } catch (Exception e) { - return LookupUserIdResponse.from(e); - } - } - - @Override - public UpdateAccountResponse requestPasswordReset(String email, URL authenticationUrl) { - try { - String requestBody = UpdateAccountRequest.requestPasswordReset(email).toJson(); - return updateAccount(buildActionUrl(authenticationUrl, ACTION_UPDATE_ACCOUNT), requestBody); - } catch (Exception e) { - return UpdateAccountResponse.from(e); - } - } - - @Override - public UpdateAccountResponse completePasswordReset(String token, String newPassword, URL authenticationUrl) { - try { - String requestBody = UpdateAccountRequest.completePasswordReset(token, newPassword).toJson(); - return updateAccount(buildActionUrl(authenticationUrl, ACTION_UPDATE_ACCOUNT), requestBody); - } catch (Exception e) { - return UpdateAccountResponse.from(e); - } - } - - @Override - public UpdateAccountResponse requestEmailConfirmation(String email, URL authenticationUrl) { - try { - String requestBody = UpdateAccountRequest.requestEmailConfirmation(email).toJson(); - return updateAccount(buildActionUrl(authenticationUrl, ACTION_UPDATE_ACCOUNT), requestBody); - } catch (Exception e) { - return UpdateAccountResponse.from(e); - } - } - - @Override - public UpdateAccountResponse confirmEmail(String confirmationToken, URL authenticationUrl) { - try { - String requestBody = UpdateAccountRequest.completeEmailConfirmation(confirmationToken).toJson(); - return updateAccount(buildActionUrl(authenticationUrl, ACTION_UPDATE_ACCOUNT), requestBody); - } catch (Exception e) { - return UpdateAccountResponse.from(e); - } - } - - // Builds the URL for a specific auth endpoint - private static URL buildActionUrl(URL authenticationUrl, String action) { - final String baseUrlString = authenticationUrl.toExternalForm(); - try { - String separator = baseUrlString.endsWith("/") ? "" : "/"; - return new URL(baseUrlString + separator + action); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - private AuthenticateResponse authenticate(URL authenticationUrl, String requestBody) throws Exception { - RealmLog.debug("Network request (authenticate): " + authenticationUrl); - Request request = newAuthRequest(authenticationUrl) - .post(RequestBody.create(JSON, requestBody)) - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return AuthenticateResponse.from(response); - } - - private LogoutResponse logout(URL logoutUrl, String authToken, String requestBody) throws Exception { - RealmLog.debug("Network request (logout): " + logoutUrl); - Request request = newAuthRequest(logoutUrl, authToken) - .post(RequestBody.create(JSON, requestBody)) - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return LogoutResponse.from(response); - } - - private ChangePasswordResponse changePassword(URL changePasswordUrl, String authToken, String requestBody) throws Exception { - RealmLog.debug("Network request (changePassword): " + changePasswordUrl); - Request request = newAuthRequest(changePasswordUrl, authToken) - .put(RequestBody.create(JSON, requestBody)) - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return ChangePasswordResponse.from(response); - } - - private LookupUserIdResponse lookupUserId(URL lookupUserIdUrl, String authToken) throws Exception { - RealmLog.debug("Network request (lookupUserId): " + lookupUserIdUrl); - Request request = newAuthRequest(lookupUserIdUrl, authToken) - .get() - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return LookupUserIdResponse.from(response); - } - - private UpdateAccountResponse updateAccount(URL updateAccountUrl, String requestBody) throws Exception { - RealmLog.debug("Network request (updateAccount): " + updateAccountUrl); - Request request = newAuthRequest(updateAccountUrl) - .post(RequestBody.create(JSON, requestBody)) - .build(); - Call call = client.newCall(request); - Response response = call.execute(); - return UpdateAccountResponse.from(response); - } - - private Request.Builder newAuthRequest(URL url) { - return newAuthRequest(url, null); - } - - private Request.Builder newAuthRequest(URL url, String authToken) { - Request.Builder builder = new Request.Builder() - .url(url) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json"); - - // Add custom headers used by all hosts - for (Map.Entry entry : customHeaders.get("").entrySet()) { - builder.addHeader(entry.getKey(), entry.getValue()); - } - - // add custom headers used by specific host (may override global headers) - Map customHeaders = this.customHeaders.get(url.getHost()); - if (customHeaders != null) { - for (Map.Entry entry : customHeaders.entrySet()) { - builder.addHeader(entry.getKey(), entry.getValue()); - } - } - - // Only add Authorization header for those API's that require it. - // Use the defined custom authorization name if one is available for this host. - if (!Util.isEmptyString(authToken)) { - String headerName = customAuthorizationHeaders.get(url.getHost()); - if (headerName != null) { - builder.addHeader(headerName, authToken); - } else { - builder.addHeader(customAuthorizationHeaders.get(""), authToken); - } - } - - return builder; - } - -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java deleted file mode 100644 index f6695d91ae..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/RealmObjectServer.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import java.net.URI; -import java.net.URL; - -import javax.annotation.Nullable; - -import io.realm.SyncCredentials; -import io.realm.SyncUser; -import io.realm.internal.objectserver.Token; - -/** - * Interface for handling communication with Realm Object Servers. - *

            - * Note, no implementation of this class is responsible for handling retries or error handling. It is - * only responsible for executing a given network request. - */ -public interface RealmObjectServer { - - /** - * Overrides the default header name used to send Realm Object Server credentials. - * The Realm Object Server must be setup to handle this specifically. - */ - void setAuthorizationHeaderName(String headerName, @Nullable String host); - - /** - * Add a custom header that should be applied to all HTTP requests made by the authentication - * server. - */ - void addHeader(String headerName, String headerValue, @Nullable String host); - - /** - * Clear any custom header settings (Authorization and others). - */ - void clearCustomHeaderSettings(); - - /** - * Login a User on the Object Server. This will create a "UserToken" (Currently called RefreshToken) that acts as - * the users credentials. - */ - AuthenticateResponse loginUser(SyncCredentials credentials, URL authenticationUrl); - - /** - * Requests access to a specific Realm. Only users with a valid user token can ask for permission to a remote Realm. - * Permission to a Realm is granted through an "AccessToken". Each Realm have their own access token, and all - * tokens should be managed by {@link SyncUser}. - */ - AuthenticateResponse loginToRealm(Token userToken, URI serverUrl, URL authenticationUrl); - - /** - * When the Object Server returns the user token, it also sends a timestamp for when the token expires. - * Before it expires, the client should try to refresh the token, effectively keeping the user logged in on the - * Object Server. Failing to do so will cause a "soft logout", where the User will have limited access rights. - */ - AuthenticateResponse refreshUser(Token userToken, URI serverUrl, URL authenticationUrl); - - /** - * Logs out the user on the Object Server by invalidating the refresh token. Each device should be given their - * own refresh token, but if the refresh token for some reason was shared or stolen all these devices will be - * logged out as well. - */ - LogoutResponse logout(Token userToken, URL authenticationUrl); - - /** - * Changes a user's password. - */ - ChangePasswordResponse changePassword(Token userToken, String newPassword, URL authenticationUrl); - - /** - * Changes a user's password using admin account. - */ - ChangePasswordResponse changePassword(Token adminToken, String userID, String newPassword, URL authenticationUrl); - - /** - * Looks up a {@code SyncUser} using the identity provider {@link io.realm.SyncCredentials.IdentityProvider} - * used when the account was created and the username or email used to create the account for the first time - * what is needed will depend on what type of {@link SyncCredentials} was used. - */ - LookupUserIdResponse retrieveUser(Token adminToken, String provider, String providerId, URL authenticationUrl); - - /** - * Request a password reset for the user identified by the provided email. - */ - UpdateAccountResponse requestPasswordReset(String email, URL authenticationUrl); - - /** - * Complete a password reset by sending the one-time token and the new password. - */ - UpdateAccountResponse completePasswordReset(String token, String newPassword, URL authenticationUrl); - - /** - * Request an email confirmation. - */ - UpdateAccountResponse requestEmailConfirmation(String email, URL authenticationUrl); - - /** - * Complete an email confirmation by sending the token contained in the email. - */ - UpdateAccountResponse confirmEmail(String confirmationToken, URL authenticationUrl); - -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountRequest.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountRequest.java deleted file mode 100644 index 5669e6cf0f..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountRequest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.Map; - -import io.realm.internal.Util; - -/** - * This class encapsulates the JSON request body when either doing a password reset or an email confirmation - * flow. - */ -public class UpdateAccountRequest { - - private static final Map NO_DATA = new HashMap<>(); - - private final String action; - private final Map data; - private final String providerId; // Should be an email address, but let server validate that. - - public static UpdateAccountRequest requestPasswordReset(String email) { - return new UpdateAccountRequest("reset_password", NO_DATA, email); - } - - public static UpdateAccountRequest completePasswordReset(String resetPasswordToken, String newPassword) { - Map data = new HashMap<>(); - data.put("token", resetPasswordToken); - data.put("new_password", newPassword); - return new UpdateAccountRequest("complete_reset", data, null); - } - - public static UpdateAccountRequest requestEmailConfirmation(String email) { - return new UpdateAccountRequest("request_email_confirmation", NO_DATA, email); - } - - public static UpdateAccountRequest completeEmailConfirmation(String confirmEmailToken) { - Map data = new HashMap<>(); - data.put("token", confirmEmailToken); - return new UpdateAccountRequest("confirm_email", data, null); - } - - private UpdateAccountRequest(String action, Map data, String providerId) { - this.action = action; - this.data = data; - this.providerId = providerId; - } - - /** - * Converts the request into a JSON payload. - */ - public String toJson() { - Map payload = new HashMap() {{ - if (!Util.isEmptyString(providerId)) { - put("provider_id", providerId); - } - data.put("action", action); - put("data", data); - }}; - - return new JSONObject(payload).toString(); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountResponse.java deleted file mode 100644 index cdb528bd86..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/UpdateAccountResponse.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ - -package io.realm.internal.network; - -import java.io.IOException; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import okhttp3.Response; - -/** - * This class represents the response from an {@link UpdateAccountRequest} network call. - */ -public class UpdateAccountResponse extends AuthServerResponse { - - public static UpdateAccountResponse from(Exception exception) { - return new UpdateAccountResponse(new ObjectServerError(ErrorCode.fromException(exception), exception)); - } - - public static UpdateAccountResponse from(Response response) { - if (response.isSuccessful()) { - return new UpdateAccountResponse(); - } else { - try { - String serverResponse = response.body().string(); - return new UpdateAccountResponse(AuthServerResponse.createError(serverResponse, response.code())); - } catch (IOException e) { - ObjectServerError error = new ObjectServerError(ErrorCode.IO_EXCEPTION, e); - return new UpdateAccountResponse(error); - } - } - } - - /** - * Create a failure response object. - */ - public UpdateAccountResponse(ObjectServerError error) { - this.error = error; - } - - /** - * Create a successful response object. - */ - public UpdateAccountResponse() { - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java new file mode 100644 index 0000000000..3387badb69 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.internal.objectstore; + +import io.realm.internal.NativeObject; + +/** + * Class wrapping ObjectStores {@code realm::app::AppCredentials}. + */ +public class OsAppCredentials implements NativeObject { + + private static final int TYPE_ANONYMOUS = 1; + private static final int TYPE_API_KEY = 2; + private static final int TYPE_APPLE = 3; + private static final int TYPE_CUSTOM_FUNCTION = 4; + private static final int TYPE_EMAIL_PASSWORD = 5; + private static final int TYPE_FACEBOOK = 6; + private static final int TYPE_GOOGLE = 7; + private static final int TYPE_JWT = 8; + private static final long finalizerPtr = nativeGetFinalizerMethodPtr(); + + public static OsAppCredentials anonymous() { + return new OsAppCredentials(nativeCreate(TYPE_ANONYMOUS)); + } + + public static OsAppCredentials apiKey(String key) { + return new OsAppCredentials(nativeCreate(TYPE_API_KEY, key)); + } + + public static OsAppCredentials apple(String idToken) { + return new OsAppCredentials(nativeCreate(TYPE_APPLE, idToken)); + } + + public static OsAppCredentials customFunction(String functionName, Object... args) { + return new OsAppCredentials(nativeCreate(TYPE_CUSTOM_FUNCTION, functionName, args)); + } + + public static OsAppCredentials emailPassword(String email, String password) { + return new OsAppCredentials(nativeCreate(TYPE_EMAIL_PASSWORD, email, password)); + } + + public static OsAppCredentials facebook(String accessToken) { + return new OsAppCredentials(nativeCreate(TYPE_FACEBOOK, accessToken)); + } + + public static OsAppCredentials google(String whatToCallThisToken) { + return new OsAppCredentials(nativeCreate(TYPE_GOOGLE, whatToCallThisToken)); + } + + public static OsAppCredentials jwt(String jwtToken) { + return new OsAppCredentials(nativeCreate(TYPE_JWT, jwtToken)); + } + + private final long nativePtr; + + private OsAppCredentials(long nativePtr) { + this.nativePtr = nativePtr; + } + + public String getProvider() { + return nativeGetProvider(nativePtr); + } + + public String asJson() { + return nativeAsJson(nativePtr); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return finalizerPtr; + } + + private static native long nativeCreate(int type, Object... args); + private static native String nativeGetProvider(long nativePtr); + private static native String nativeAsJson(long nativePtr); + private static native long nativeGetFinalizerMethodPtr(); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java new file mode 100644 index 0000000000..ecc338070a --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java @@ -0,0 +1,147 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.internal.objectstore; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; + +import javax.annotation.Nullable; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.RealmApp; +import io.realm.RealmAsyncTask; +import io.realm.internal.Keep; +import io.realm.internal.KeepMember; +import io.realm.internal.NativeObject; +import io.realm.internal.RealmNotifier; +import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.android.AndroidRealmNotifier; +import io.realm.internal.async.RealmAsyncTaskImpl; +import io.realm.log.RealmLog; + +/** + * Java implementation of the transport layer exposed by ObjectStore when communicating with + * the MongoDB Realm Server. + */ +@Keep +public abstract class OsJavaNetworkTransport { + + // Custom error codes. These must not match any HTTP response error codes + public static final int ERROR_IO = 1000; + public static final int ERROR_INTERRUPTED = 1001; + public static final int ERROR_UNKNOWN = 1002; + + /** + * This method is being called from JNI in order to execute the network transport itself. + * All logic around retry and parsing of results should be done by ObjectStore. + * + * Warning: This method is not allowed to throw. Any exception should be wrapped in a {@link Response} + * and be returned as a result. + * + * @param method Which kind of HTTP method in lowercase. + * @param url Url to connect to. + * @param timeoutMs How long does the request has to complete? + * @param headers Which headers to send? + * @param body Which body to include? + * @return Result of the request. All exceptions should also be wrapped in this. + */ + protected abstract Response sendRequest(String method, String url, long timeoutMs, Map headers, String body); + + public static class Response { + private final int httpResponseCode; + private final int customResponseCode; + private final Map headers; + private final String body; + + public static Response unknownError(String stacktrace) { + return new Response(0, ERROR_UNKNOWN, new HashMap<>(), stacktrace); + } + + public static Response ioError(String stackTrace) { + return new Response(0, ERROR_IO, new HashMap<>(), stackTrace); + } + + public static Response interruptedError(String stackTrace) { + return new Response(0, ERROR_INTERRUPTED, new HashMap<>(), stackTrace); + } + + public static Response httpResponse(int statusCode, Map responseHeaders, String body) { + return new Response(statusCode, 0, responseHeaders, body); + } + + private Response(int httpResponseCode, int customResponseCode, Map headers, String body) { + this.httpResponseCode = httpResponseCode; + this.customResponseCode = customResponseCode; + this.headers = headers; + this.body = body; + } + + public int getHttpResponseCode() { + return httpResponseCode; + } + + public int getCustomResponseCode() { + return customResponseCode; + } + + public Map getHeaders() { + return headers; + } + + // Returns the HTTP headers in a JNI friendly way where it is being serialized to a + // String array consisting of pairs of { key , value } pairs. + public String[] getJNIFriendlyHeaders() { + String[] jniHeaders = new String[headers.size() * 2]; + int i = 0; + for (Map.Entry entry : headers.entrySet()) { + jniHeaders[i] = entry.getKey(); + jniHeaders[i + 1] = entry.getValue(); + i = i + 2; + } + return jniHeaders; + } + + public String getBody() { + return body; + } + + @Override + public String toString() { + return "Response{" + + "httpResponseCode=" + httpResponseCode + + ", customResponseCode=" + customResponseCode + + ", headers=" + headers + + ", body='" + body + '\'' + + '}'; + } + } + + /** + * Callback used when sending back results from network requests performed by ObjectStores + * {@code realm::app::NetworkTransport}. + * + * The callback will happen on the thread running the network request, not the intended receiver thread. + */ + // Abstract because these methods needs to be called from JNI and we cannot look up interface methods. + @Keep + public static abstract class NetworkTransportJNIResultCallback { + public void onSuccess(Object result) {} + public void onError(String nativeErrorCategory, int nativeErrorCode, String errorMessage) {} + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java new file mode 100644 index 0000000000..6176c1ccf6 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java @@ -0,0 +1,119 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.internal.objectstore; + +import io.realm.internal.NativeObject; +import io.realm.internal.util.Pair; + +public class OsSyncUser implements NativeObject { + + private final long nativePtr; + private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); + + public OsSyncUser(long nativePtr) { + this.nativePtr = nativePtr; + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + public String nativeGetName() { + return nativeGetName(nativePtr); + } + + public String getEmail() { + return nativeGetEmail(nativePtr); + } + + public String getPictureUrl() { + return nativeGetPictureUrl(nativePtr); + } + + public String getFirstName() { + return nativeGetFirstName(nativePtr); + } + + public String getLastName() { + return nativeGetLastName(nativePtr); + } + + public String getGender() { + return nativeGetGender(nativePtr); + } + + public String getBirthday() { + return nativeGetBirthday(nativePtr); + } + + public String getMinAge() { + return nativeGetMinAge(nativePtr); + } + + public String getMaxAge() { + return nativeGetMaxAge(nativePtr); + } + + public String getIdentity() { + return nativeGetIdentity(nativePtr); + } + + public String getAccessToken() { + return nativeGetAccessToken(nativePtr); + } + + public String getRefreshToken() { + return nativeGetRefreshToken(nativePtr); + } + + public Pair[] getIdentities() { + String[] identityData = nativeGetIdentities(nativePtr); + @SuppressWarnings("unchecked") + Pair[] identities = new Pair[identityData.length/2]; + for (int i = 0; i < identityData.length; i = i + 2) { + identities[i] = new Pair<>(identityData[i], identityData[i+1]); + } + return identities; + } + + + private static native long nativeGetFinalizerMethodPtr(); + + // Profile data + private static native String nativeGetName(long nativePtr); + private static native String nativeGetEmail(long nativePtr); + private static native String nativeGetPictureUrl(long nativePtr); + private static native String nativeGetFirstName(long nativePtr); + private static native String nativeGetLastName(long nativePtr); + private static native String nativeGetGender(long nativePtr); + private static native String nativeGetBirthday(long nativePtr); + private static native String nativeGetMinAge(long nativePtr); + private static native String nativeGetMaxAge(long nativePtr); + + private static native String nativeGetIdentity(long nativePtr); + private static native String nativeGetAccessToken(long nativePtr); + private static native String nativeGetRefreshToken(long nativePtr); + private static native String[] nativeGetIdentities(long nativePtr); // Returns pairs of {id, provider} +} + + + diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/package-info.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/package-info.java new file mode 100644 index 0000000000..e9cd54b62c --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +@javax.annotation.ParametersAreNonnullByDefault +package io.realm.internal.objectstore; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBCollection.java new file mode 100644 index 0000000000..2a509db842 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBCollection.java @@ -0,0 +1,19 @@ +/** + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.mongodb; + +public class RealmMongoDBCollection { +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBDatabase.java new file mode 100644 index 0000000000..8c420fadef --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBDatabase.java @@ -0,0 +1,19 @@ +/** + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.mongodb; + +public class RealmMongoDBDatabase { +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBService.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBService.java new file mode 100644 index 0000000000..dbc97c5d22 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBService.java @@ -0,0 +1,19 @@ +/** + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.mongodb; + +public class RealmMongoDBService { +} diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java index e1a05ab892..75db7302f5 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java @@ -27,7 +27,6 @@ import java.lang.reflect.Method; import java.util.UUID; -import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.objectserver.Token; import io.realm.log.LogLevel; import io.realm.log.RealmLog; @@ -137,31 +136,31 @@ public static SyncUser createTestUser(String userTokenValue, String userIdentifi // persist the user to the ObjectStore sync metadata, to simulate real login, otherwise SyncUser.isValid will // "throw IllegalArgumentException: User not authenticated or authentication expired." since // the call to SyncManager.getUserStore().isActive(syncUser.getIdentity()) will return false - addToUserStore(syncUser); +// addToUserStore(syncUser); return syncUser; } catch (JSONException e) { throw new RuntimeException(e); } } - public static AuthenticateResponse createLoginResponse(long expires) { - return createLoginResponse(USER_TOKEN, "JohnDoe", expires, false); - } - - public static AuthenticateResponse createLoginResponse(String userTokenValue, String userIdentity, long expires, boolean isAdmin) { - try { - Token userToken = new Token(userTokenValue, userIdentity, null, expires, null, isAdmin); - JSONObject response = new JSONObject(); - response.put("refresh_token", userToken.toJson()); - return AuthenticateResponse.from(response.toString()); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - public static AuthenticateResponse createErrorResponse(ErrorCode code) { - return AuthenticateResponse.from(new ObjectServerError(code, "dummy")); - } +// public static AuthenticateResponse createLoginResponse(long expires) { +// return createLoginResponse(USER_TOKEN, "JohnDoe", expires, false); +// } +// +// public static AuthenticateResponse createLoginResponse(String userTokenValue, String userIdentity, long expires, boolean isAdmin) { +// try { +// Token userToken = new Token(userTokenValue, userIdentity, null, expires, null, isAdmin); +// JSONObject response = new JSONObject(); +// response.put("refresh_token", userToken.toJson()); +// return AuthenticateResponse.from(response.toString()); +// } catch (JSONException e) { +// throw new RuntimeException(e); +// } +// } +// +// public static AuthenticateResponse createErrorResponse(ErrorCode code) { +// return AuthenticateResponse.from(new ObjectServerError(code, "dummy")); +// } public static Token getRefreshToken(SyncUser user) { try { @@ -171,14 +170,14 @@ public static Token getRefreshToken(SyncUser user) { } } - private static void addToUserStore(SyncUser user) { - try { - UserStore userStore = (UserStore) SYNC_MANAGER_GET_USER_STORE_METHOD.invoke(null); - userStore.put(user); - } catch (InvocationTargetException | IllegalAccessException e) { - throw new AssertionError(e); - } - } +// private static void addToUserStore(SyncUser user) { +// try { +// UserStore userStore = (UserStore) SYNC_MANAGER_GET_USER_STORE_METHOD.invoke(null); +// userStore.put(user); +// } catch (InvocationTargetException | IllegalAccessException e) { +// throw new AssertionError(e); +// } +// } // Fully synchronize a Realm with the server by making sure that all changes are uploaded // and downloaded again. diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java index d39ae9dd24..31f7d51e69 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java @@ -28,4 +28,5 @@ public class Constants { public static final String DEFAULT_REALM = "realm://" + HOST + ":9080/default"; public static final String AUTH_SERVER_URL = "http://" + HOST + ":9080/"; public static final String AUTH_URL = AUTH_SERVER_URL + "auth"; + public static final String APP_ID = "mongdodb-realm-integrationtest-app"; // FIXME: This doesn't work because the name changes } diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java index 38bd154f25..064748f282 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java @@ -25,15 +25,13 @@ import java.util.concurrent.TimeUnit; import io.realm.Realm; +import io.realm.RealmApp; +import io.realm.RealmCredentials; +import io.realm.RealmUser; import io.realm.RealmConfiguration; -import io.realm.SyncCredentials; -import io.realm.SyncUser; import io.realm.TestHelper; -import io.realm.internal.ObjectServerFacade; import io.realm.log.RealmLog; -import static org.junit.Assert.fail; - // Helper class to retrieve users with same IDs even in multi-processes. // Must be in `io.realm.objectserver` to work around package protected methods. @@ -45,6 +43,7 @@ public class UserFactory { private String userName; private static UserFactory instance; private static RealmConfiguration configuration; + private static RealmApp app; // Run initializer here to make it possible to ensure that Realm.init has been called. // It is unpredictable when the static initializer is running @@ -52,6 +51,7 @@ private static synchronized void initFactory(boolean forceReset) { if (configuration == null || forceReset) { RealmConfiguration.Builder builder = new RealmConfiguration.Builder().name("user-factory.realm"); configuration = builder.build(); + app = new RealmApp(Constants.APP_ID); } } @@ -59,43 +59,34 @@ private UserFactory(String userName) { this.userName = userName; } - public SyncUser loginWithDefaultUser(String authUrl) { - SyncCredentials credentials = SyncCredentials.usernamePassword(userName, PASSWORD, false); - return SyncUser.logIn(credentials, authUrl); - } - - /** - * Create a unique user, using the standard authentification URL used by the test server. - */ - public static SyncUser createUniqueUser() { - return createUniqueUser(Constants.AUTH_URL); - } - - public static SyncUser createUser(String username) { - return createUser(username, Constants.AUTH_URL); + public RealmUser loginWithDefaultUser() { + RealmCredentials credentials = RealmCredentials.emailPassword(userName, PASSWORD); + return app.login(credentials); } - public static SyncUser createUniqueUser(String authUrl) { + public static RealmUser createUniqueUser() { String uniqueName = UUID.randomUUID().toString(); return createUser(uniqueName); } - private static SyncUser createUser(String username, String authUrl) { - SyncCredentials credentials = SyncCredentials.usernamePassword(username, PASSWORD, true); - return SyncUser.logIn(credentials, authUrl); + private static RealmUser createUser(String username) { + return null; // FIXME +// RealmCredentials credentials = RealmCredentials.emailPassword(username, PASSWORD, true); +// return app.login(credentials); } - - public SyncUser createDefaultUser(String authUrl) { - SyncCredentials credentials = SyncCredentials.usernamePassword(userName, PASSWORD, true); - return SyncUser.logIn(credentials, authUrl); + public RealmUser createDefaultUser() { + return null; // FIXME +// RealmCredentials credentials = RealmCredentials.emailPassword(userName, PASSWORD, true); +// return app.login(credentials); } - public static SyncUser createAdminUser(String authUrl) { - // `admin` required as user identifier to be granted admin rights. - // ROS 2.0 comes with a default admin user named "realm-admin" with password "". - SyncCredentials credentials = SyncCredentials.usernamePassword("realm-admin", "", false); - return SyncUser.logIn(credentials, authUrl); + public static RealmUser createAdminUser() { + return null; //FIXME +// // `admin` required as user identifier to be granted admin rights. +// // ROS 2.0 comes with a default admin user named "realm-admin" with password "". +// RealmCredentials credentials = RealmCredentials.emailPassword("realm-admin", "", false); +// return app.login(credentials); } // Since we don't have a reliable way to reset the sync server and client, just use a new user factory for every @@ -150,10 +141,10 @@ public static void logoutAllUsers() { handler.post(new Runnable() { @Override public void run() { - Map users = SyncUser.all(); - for (SyncUser user : users.values()) { - user.logOut(); - } +// Map users = RealmApp.allUsers(); +// for (RealmUser user : users.values()) { +// RealmApp.logout(user); +// } TestHelper.waitForNetworkThreadExecutorToFinish(); allUsersLoggedOut.countDown(); } diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java index b5f8a62827..2f09f0eb00 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/RunWithRemoteService.java @@ -17,6 +17,7 @@ package io.realm.rule; import android.app.ActivityManager; +import android.app.Instrumentation; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -29,6 +30,8 @@ import android.os.Messenger; import android.os.RemoteException; +import androidx.test.platform.app.InstrumentationRegistry; + import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -39,7 +42,6 @@ import io.realm.TestHelper; import io.realm.services.RemoteTestService; -import static androidx.test.InstrumentationRegistry.getContext; import static junit.framework.Assert.fail; /** @@ -117,13 +119,14 @@ public void onServiceDisconnected(ComponentName componentName) { private void before(Class serviceClass) throws Throwable { // Start the testing remote process. serviceStartLatch = new CountDownLatch(1); - Intent intent = new Intent(getContext(), serviceClass); - getContext().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + Intent intent = new Intent(instrumentation.getContext(), serviceClass); + instrumentation.getContext().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); TestHelper.awaitOrFail(serviceStartLatch); } private void after() { - getContext().unbindService(serviceConnection); + InstrumentationRegistry.getInstrumentation().getContext().unbindService(serviceConnection); // Kill the remote process. ActivityManager.RunningAppProcessInfo info = getRemoteProcessInfo(); @@ -183,10 +186,11 @@ public void triggerServiceStep(RemoteTestService.Step step) { // Get the remote process info if it is alive. private ActivityManager.RunningAppProcessInfo getRemoteProcessInfo() { - ActivityManager manager = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + ActivityManager manager = (ActivityManager) instrumentation.getContext().getSystemService(Context.ACTIVITY_SERVICE); List processInfoList = manager.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo info : processInfoList) { - if (info.processName.equals(getContext().getPackageName() + REMOTE_PROCESS_POSTFIX)) { + if (info.processName.equals(instrumentation.getContext().getPackageName() + REMOTE_PROCESS_POSTFIX)) { return info; } } diff --git a/tools/sync_test_server/Dockerfile b/tools/sync_test_server/Dockerfile index 24fde4982f..1fda76b43c 100644 --- a/tools/sync_test_server/Dockerfile +++ b/tools/sync_test_server/Dockerfile @@ -1,14 +1,14 @@ FROM node:10 +# This Docker image is only responsible for running the Integration Command Server which can be +# used to instrument other parts of the Integration tests. +# +# It exposes a webserver on port 8888. + # set timezone to Copenhagen (by default it's using UTC) to match Android's device time. RUN cp /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime RUN echo "Europe/Copenhagen" > /etc/timezone -ARG ROS_VERSION -ARG REALM_FEATURE_TOKEN -RUN if [ "x$ROS_VERSION" = "x" ] ; then echo Non-empty ROS_VERSION required ; exit 1; fi -RUN if [ "x$REALM_FEATURE_TOKEN" = "x" ] ; then echo Non-empty REALM_FEATURE_TOKEN required ; exit 1; fi - # Install netstat (used for debugging) # Fix https://superuser.com/questions/1420231/how-to-solve-404-error-in-aws-apg-get-for-debian-jessie-fetch RUN rm etc/apt/sources.list @@ -24,23 +24,10 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -## Copy ROS node template project to image. Then configure and prepare it for usage. -COPY ros /ros -WORKDIR "/ros" -RUN sed -i -e "s/%ROS_VERSION%/$ROS_VERSION/g" package.json -RUN sed -i -e "s/%REALM_FEATURE_TOKEN%/$REALM_FEATURE_TOKEN/g" src/index.ts -RUN npm install -WORKDIR "/" - -# Install test server dependencies +# Copy webserver script and install dependencies +WORKDIR "/tmp" +COPY mongodb-realm-command-server.js /tmp RUN npm install winston@2.4.0 temp httpdispatcher@1.0.0 fs-extra moment is-port-available@0.1.5 -COPY keys/public.pem keys/private.pem keys/127_0_0_1-server.key.pem keys/127_0_0_1-chain.crt.pem / -COPY integration-test-command-server.js /usr/bin/ - -# Bypass the ROS license check -ENV DOCKER_DATA_PATH / -ENV ROS_TOS_EMAIL_ADDRESS 'ci@realm.io' - # Run integration test server -CMD /usr/bin/integration-test-command-server.js /tmp/integration-test-command-server.log +CMD /tmp/mongodb-realm-command-server.js diff --git a/tools/sync_test_server/app_config/auth_providers/anon-user.json b/tools/sync_test_server/app_config/auth_providers/anon-user.json new file mode 100755 index 0000000000..00e4641703 --- /dev/null +++ b/tools/sync_test_server/app_config/auth_providers/anon-user.json @@ -0,0 +1,6 @@ +{ + "id": "5e688e10535956d2ec4046e3", + "name": "anon-user", + "type": "anon-user", + "disabled": false +} diff --git a/tools/sync_test_server/app_config/auth_providers/api-key.json b/tools/sync_test_server/app_config/auth_providers/api-key.json new file mode 100755 index 0000000000..a0ef800747 --- /dev/null +++ b/tools/sync_test_server/app_config/auth_providers/api-key.json @@ -0,0 +1,6 @@ +{ + "id": "5e688e10535956d2ec4046e4", + "name": "api-key", + "type": "api-key", + "disabled": false +} diff --git a/tools/sync_test_server/app_config/auth_providers/custom-function.json b/tools/sync_test_server/app_config/auth_providers/custom-function.json new file mode 100755 index 0000000000..34ccf8fbae --- /dev/null +++ b/tools/sync_test_server/app_config/auth_providers/custom-function.json @@ -0,0 +1,9 @@ +{ + "id": "5e689dc8535956d2ec40527a", + "name": "custom-function", + "type": "custom-function", + "config": { + "authFunctionName": "authFunc" + }, + "disabled": false +} diff --git a/tools/sync_test_server/app_config/auth_providers/local-userpass.json b/tools/sync_test_server/app_config/auth_providers/local-userpass.json new file mode 100755 index 0000000000..4927e58d16 --- /dev/null +++ b/tools/sync_test_server/app_config/auth_providers/local-userpass.json @@ -0,0 +1,12 @@ +{ + "id": "5e689d78535956d2ec405220", + "name": "local-userpass", + "type": "local-userpass", + "config": { + "autoConfirm": true, + "resetFunctionName": "resetFunc", + "runConfirmationFunction": false, + "runResetFunction": true + }, + "disabled": false +} diff --git a/tools/sync_test_server/app_config/functions/authFunc/config.json b/tools/sync_test_server/app_config/functions/authFunc/config.json new file mode 100755 index 0000000000..d99d62ff41 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/authFunc/config.json @@ -0,0 +1,6 @@ +{ + "id": "5e689dc8535956d2ec405275", + "name": "authFunc", + "private": false, + "can_evaluate": {} +} diff --git a/tools/sync_test_server/app_config/functions/authFunc/source.js b/tools/sync_test_server/app_config/functions/authFunc/source.js new file mode 100755 index 0000000000..58d4bd3ede --- /dev/null +++ b/tools/sync_test_server/app_config/functions/authFunc/source.js @@ -0,0 +1,17 @@ + + /* + + This function will be run when a user logs in with this provider. + + The return object must contain a string id, this string id will be used to login with an existing + or create a new user. This is NOT the Stitch user id, but it is the id used to identify which user has + been created or logged in with. + + If an error is thrown within the function the login will fail. + + The default function provided below will always result in failure. + */ + + exports = (loginPayload) => { + return; + }; diff --git a/tools/sync_test_server/app_config/functions/resetFunc/config.json b/tools/sync_test_server/app_config/functions/resetFunc/config.json new file mode 100755 index 0000000000..0afe98c70d --- /dev/null +++ b/tools/sync_test_server/app_config/functions/resetFunc/config.json @@ -0,0 +1,6 @@ +{ + "id": "5e689d78535956d2ec405217", + "name": "resetFunc", + "private": false, + "can_evaluate": {} +} diff --git a/tools/sync_test_server/app_config/functions/resetFunc/source.js b/tools/sync_test_server/app_config/functions/resetFunc/source.js new file mode 100755 index 0000000000..b31a1971d9 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/resetFunc/source.js @@ -0,0 +1,47 @@ + + /* + This function will be run when the client SDK 'callResetPasswordFunction' and is called with an object parameter + which contains four keys: 'token', 'tokenId', 'username', and 'password', and additional parameters + for each parameter passed in as part of the argument list from the SDK. + + The return object must contain a 'status' key which can be empty or one of three string values: + 'success', 'pending', or 'fail' + + 'success': the user's password is set to the passed in 'password' parameter. + + 'pending': the user's password is not reset and the UserPasswordAuthProviderClient 'resetPassword' function would + need to be called with the token, tokenId, and new password via an SDK. (see below) + + const emailPassClient = Stitch.defaultAppClient.auth + .getProviderClient(UserPasswordAuthProviderClient.factory); + + emailPassClient.resetPassword(token, tokenId, newPassword) + + 'fail': the user's password is not reset and will not be able to log in with that password. + + If an error is thrown within the function the result is the same as 'fail'. + + Example below: + + exports = ({ token, tokenId, username, password }, sendEmail, securityQuestionAnswer) => { + // process the reset token, tokenId, username and password + if (sendEmail) { + context.functions.execute('sendResetPasswordEmail', username, token, tokenId); + // will wait for SDK resetPassword to be called with the token and tokenId + return { status: 'pending' }; + } else if (context.functions.execute('validateSecurityQuestionAnswer', username, securityQuestionAnswer)) { + // will set the users password to the password parameter + return { status: 'success' }; + } + + // will not reset the password + return { status: 'fail' }; + }; + + The uncommented function below is just a placeholder and will result in failure. + */ + + exports = ({ token, tokenId, username, password }) => { + // will not reset the password + return { status: 'fail' }; + }; diff --git a/tools/sync_test_server/app_config/services/integration_tests/config.json b/tools/sync_test_server/app_config/services/integration_tests/config.json new file mode 100755 index 0000000000..0bd351441b --- /dev/null +++ b/tools/sync_test_server/app_config/services/integration_tests/config.json @@ -0,0 +1,10 @@ +{ + "id": "5e688e10535956d2ec4046e2", + "name": "integration_tests", + "type": "mongodb", + "config": {}, + "secret_config": { + "uri": "integration_tests_uri" + }, + "version": 1 +} diff --git a/tools/sync_test_server/app_config/stitch.json b/tools/sync_test_server/app_config/stitch.json new file mode 100755 index 0000000000..118b56a159 --- /dev/null +++ b/tools/sync_test_server/app_config/stitch.json @@ -0,0 +1,14 @@ +{ + "app_id": "realm-sdk-integration-tests-pwjzl", + "config_version": 20180301, + "name": "realm-sdk-integration-tests", + "location": "US-VA", + "deployment_model": "GLOBAL", + "security": {}, + "custom_user_data_config": { + "enabled": false + }, + "sync": { + "development_mode_enabled": false + } +} diff --git a/tools/sync_test_server/integration-test-command-server.js b/tools/sync_test_server/integration-test-command-server.js deleted file mode 100755 index 826635747f..0000000000 --- a/tools/sync_test_server/integration-test-command-server.js +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env nodejs - -/** - * This script controls the Command Server responsible for starting and stopping - * ROS instances. The integration tests running on the device will communicate - * with it using a predefined port in order to say when the ROS instance - * should be started and stopped. - * - * This script is responsible for cleaning up any server state after it has been - * stopped, so a new integration test will start from a clean slate. - */ - -var winston = require('winston'); //logging -const spawn = require('child_process').spawn; -const exec = require('child_process').exec; -const isPortAvailable = require('is-port-available'); -var http = require('http'); -var dispatcher = require('httpdispatcher'); -var fs = require('fs-extra'); -var moment = require('moment') - -if (process. argv. length <= 2) { - console.log("Usage: " + __filename + " somefile.log"); - process.exit(-1); -} - -const logFile = process.argv[2]; -winston.level = 'debug'; -winston.add(winston.transports.File, { - filename: logFile, - json: false, - formatter: function(options) { - return moment().format('YYYY-MM-DD HH:mm:ss.SSSS') + ' ' + (undefined !== options.message ? options.message : ''); - } -}); - -const PORT = 8888; -var syncServerChildProcess = null; - -// When starting ROS, it isn't ready immediately. This method will wait until /health/ -// returns OK indicating that ROS is now fully initialized and ready. -function waitForRosToInitialize(attempts, onSuccess, onError, startSequence) { - if (attempts == 0) { - onError("Could not get ROS to start. See Docker log."); - return; - } - - http.get("http://0.0.0.0:9080/health", function(res) { - if (res.statusCode != 200) { - winston.warn("command-server: ROS /health/ returned: " + res.statusCode) - setTimeout(function() { - waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); - }, 500); - } else { - onSuccess(startSequence); - } - }).on('error', function(err) { - winston.warn("command-server: ROS /health/ returned an error: " + err) - // ROS not accepting any connections yet. - // Errors like ECONNREFUSED 0.0.0.0:9080 will be reported here. - // Wait a little before trying again (common startup is ~1 second). - setTimeout(function() { - waitForRosToInitialize(attempts - 1, onSuccess, onError, startSequence); - }, 500); - }); -} - -// When starting a new ROS instance, an old one might still be in the process of being -// torn down. This can sometimes cause the new server to fail to start due to the -// port still being used. To prevent that, we wait for the port to be ready -// before trying to start the server. -function waitForPortToBeReady(attempts, onSuccess, onError) { - if (attempts == 0) { - // Log as much info as possible in order to help debugging - exec('ps auxw', (error, stdout, stderr) => { - winston.info(`command-server:\n ${stdout}`); - }); - exec('netstat -tulpn', (error, stdout, stderr) => { - winston.info(`command-server:\n ${stdout}`); - }); - onError("Port failed to become ready in time"); - return; - } - - // Port 9080 and 9443 are being used by ROS - isPortAvailable("9443").then( status => { - if (status) { - onSuccess(); - } else { - winston.info("command-server: Port still in use. Retrying.") - setTimeout(function() { - waitForPortToBeReady(attempts - 1, onSuccess, onError); - }, 500); - } - }); -} - -function startRealmObjectServer(onSuccess, onError) { - stopRealmObjectServer(() => { - waitForPortToBeReady(20, function() { - winston.info("command-server: Starting ROS in /ros"); - var env = Object.create( process.env ); - winston.info(env.NODE_ENV); - env.NODE_ENV = 'development'; - - // Cleanup any previous server state - winston.info("command-server: Cleaning old server state"); - fs.removeSync('/ros/data'); - fs.removeSync('/ros/realm-object-server'); - fs.removeSync('/ros/log.txt'); - if (fs.existsSync('/ros/data')) { - onError("Could not delete data directory: " + globalNotifierDir); - return; - } - if (fs.existsSync('/ros/realm-object-server')) { - onError("Could not delete global notifier directory: " + globalNotifierDir); - return; - } - - // Start ROS - syncServerChildProcess = spawn('npm', ['start'], { env: env, cwd: '/ros' }); - - // Route logs from ROS to the Command Server log so we can save it - syncServerChildProcess.stdout.on('data', (data) => { - winston.info(`ros: ${data}`); - }); - - syncServerChildProcess.stderr.on('data', (data) => { - winston.info(`ros: ${data}`); - }); - - // The interval between every health check is 0.5 second. Give the ROS 30 seconds to get fully initialized. - waitForRosToInitialize(60, onSuccess, onError, Date.now()); - - }, onError); - }, onError) -} - -// FIXME: This method seems broken in Node 10 and/or latest version of ROS -function stopRealmObjectServer(onSuccess, onError) { - if(syncServerChildProcess == null || syncServerChildProcess.killed) { - onSuccess("No ROS process found or the process has been killed before"); - } - if (syncServerChildProcess) { - - // Work-around for https://github.com/realm/realm-java/issues/6137 - // Pull the log file before removing it and output all of it to this process - // so we can capture it. This means the logs won't show up until ROS is stopped - exec('cat /ros/log.txt', (error, stdout, stderr) => { - winston.info(`Realm Object Server Logs:\n${stdout}`); - syncServerChildProcess.on('exit', function(code) { - // Manually kill sub process started by node that actually runs ROS. - // It is not killed when killing the process running NPM - exec('fuser -k 9443/tcp', (error, stdout, stderr) => { - if (error) { - onError(error) - return; - } - winston.info(`command-server: Stopping process: '${stdout}'`) - syncServerChildProcess.removeAllListeners('exit'); - syncServerChildProcess = null; - onSuccess(); - }); - }); - syncServerChildProcess.kill('SIGTERM'); - }); - - } -} - -// Command Server endpoint: Start a new instance of ROS -dispatcher.onGet("/start", function(req, res) { - winston.info("command-server: Attempting to start ROS"); - startRealmObjectServer((startSequence) => { - res.writeHead(200, {'Content-Type': 'text/plain'}); - let response = `ROS started after ${Date.now() - startSequence} ms`; - res.end(response); - winston.info("command-server: " + response); - }, function (err) { - res.writeHead(500, {'Content-Type': 'text/plain'}); - res.end('Starting ROS failed: ' + err); - winston.error('command-server: Starting ROS failed: ' + err); - }); -}); - -// Command Server endpoint: Stop a running instance of ROS. -dispatcher.onGet("/stop", function(req, res) { - winston.info("command-server: Attempting to stop ROS"); - stopRealmObjectServer(function() { - winston.info("command-server: ROS stopped"); - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end('ROS stopped'); - }, function(err) { - winston.error('command-server: Stopping ROS failed: ' + err); - res.writeHead(500, {'Content-Type': 'text/plain'}); - res.end('Stopping ROS failed: ' + err); - }); -}); - -function handleRequest(request, response) { - try { - winston.info('command-server: ' + request.url); - dispatcher.dispatch(request, response); - } catch(err) { - winston.error('command-server: ' + err); - } -} - -//Create and start the Http server -var server = http.createServer(handleRequest); -server.listen(PORT, function() { - winston.info("command-server: Integration test server listening on: 127.0.0.1:%s", PORT); -}); diff --git a/tools/sync_test_server/mongodb-realm-command-server.js b/tools/sync_test_server/mongodb-realm-command-server.js new file mode 100755 index 0000000000..e26f4ee323 --- /dev/null +++ b/tools/sync_test_server/mongodb-realm-command-server.js @@ -0,0 +1,75 @@ +#!/usr/bin/env nodejs + +/** + * This script controls the Command Server responsible for starting and stopping + * ROS instances. The integration tests running on the device will communicate + * with it using a predefined port in order to say when the ROS instance + * should be started and stopped. + * + * This script is responsible for cleaning up any server state after it has been + * stopped, so a new integration test will start from a clean slate. + */ + +var winston = require('winston'); //logging +var http = require('http'); + +const isPortAvailable = require('is-port-available'); + +function handleUnknownEndPoint(req, resp) { + resp.writeHead(404, {'Content-Type': 'text/plain'}); + resp.end(); +} + +function handleOkHttp(req, resp) { + var emitSuccess = req.url.endsWith("?success=true"); + if (emitSuccess) { + resp.writeHead(200, {'Content-Type': 'text/plain'}); + resp.end(req.method + "-success"); + } else { + resp.writeHead(500, {'Content-Type': 'text/plain'}); + resp.end(req.method + "-failure"); + } +} + +function handleApplicationId(req, resp) { + switch(req.method) { + case "GET": + resp.writeHead(200, {'Content-Type': 'text/plain'}); + resp.end(applicationId); + break; + case "PUT": + var body = []; + req.on('data', (chunk) => { + body.push(chunk); + }).on('end', () => { + body = Buffer.concat(body).toString(); + applicationId = body.split("=")[1]; + resp.writeHead(201, {'Content-Location': '/application-id'}); + resp.end(); + }); + break; + default: + handleUnknownEndPoint(req, resp); + } +} + +//Create and start the Http server +const PORT = 8888; +var applicationId = "unknown" // Should be updated by the Docker setup script before any tests are run. +var server = http.createServer(function(req, resp) { + try { + winston.info('command-server: ' + req.method + " " + req.url); + if (req.url.includes("/okhttp")) { + handleOkHttp(req, resp); + } else if (req.url.includes('/application-id')) { + handleApplicationId(req, resp); + } else { + handleUnknownEndPoint(req, resp); + } + } catch(err) { + winston.error('command-server: ' + err); + } +}); +server.listen(PORT, function() { + winston.info("command-server: MongoDB Realm Integration Test Server listening on: 127.0.0.1:%s", PORT); +}); diff --git a/tools/sync_test_server/ros/package.json b/tools/sync_test_server/ros/package.json deleted file mode 100644 index a661d3f5de..0000000000 --- a/tools/sync_test_server/ros/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "ros-integration-test-server", - "version": "1.0.0", - "description": "ROS instance used by integration tests", - "main": "src/index.js", - "scripts": { - "build": "rm -rf dist; ./node_modules/.bin/tsc", - "clean": "rm -rf dist", - "start": "npm run build && NODE_TLS_REJECT_UNAUTHORIZED=0 node dist/index.js" - }, - "devDependencies": { - "typescript": "3.4.3" - }, - "dependencies": { - "realm-object-server": "%ROS_VERSION%" - } -} diff --git a/tools/sync_test_server/ros/src/index.ts b/tools/sync_test_server/ros/src/index.ts deleted file mode 100644 index 9ae228133e..0000000000 --- a/tools/sync_test_server/ros/src/index.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { BasicServer, FileConsoleLogger } from 'realm-object-server' -import * as path from 'path' - -const server = new BasicServer() - -server.start({ - // For all the full list of configuration parameters see: - // https://realm.io/docs/realm-object-server/latest/api/ros/interfaces/serverconfig.html - - featureToken: '%REALM_FEATURE_TOKEN%', - - // This is the location where ROS will store its runtime data - dataPath: path.join(__dirname, '../data'), - - // A logger to pipe ROS information. You can also specify the log level. - // The log level can be one of: all, trace, debug, detail, info, warn, error, fatal, off. - logger: new FileConsoleLogger(path.join(__dirname, '../log.txt'), 'all', { - file: { - timestamp: true, - level: 'detail' - }, - console: { - level: 'info' - } - }), - - // The address on which to listen for connections - // address?: string = '0.0.0.0' - // address: '0.0.0.0', - - // The port on which to listen for connections - // port?: number = 9080 - // port: 9080, - - // Override the default list of authentication providers - // the default has PasswordAuthProvider, AnonymousAuthProvider, and NicknameAuthProvider - // you will need to add `import { auth, BasicServer } from 'realm-object-server' - // authProviders?: IAuthProvider[] - // authProviders: [new auth.PasswordAuthProvider({ autoCreateAdminUser: true }), new auth.NicknameAuthProvider(), new auth.AnonymousAuthProvider()] - - // Autogenerate public and private keys on startup - // autoKeyGen?: boolean = true - autoKeyGen: false, - - // Specify an alternative path to the private key. Otherwise, it is expected to be under the data path. - // privateKeyPath?: string - privateKeyPath: '/private.pem', - - // Specify an alternative path to the public key. Otherwise, it is expected to be under the data path. - // publicKeyPath?: string - publicKeyPath: '/public.pem', - - // The desired logging threshold. Can be one of: all, trace, debug, detail, info, warn, error, fatal, off) - // logLevel?: string = 'info' - logLevel: 'detail', - - // Enable the HTTPS Server. - // https?: boolean = false - https: true, - - // The port on which to listen for HTTPS connections. - // httpsAddress?: string = '0.0.0.0', - // httpsAddress: '0.0.0.0', - - // The address on which to listen for HTTPS connections. - // httpsPort?: number = 9443 - httpsPort: 9443, - - // The path to your HTTPS private key in PEM format. Required if HTTPS is enabled. - // httpsKeyPath?: string - httpsKeyPath: '/127_0_0_1-server.key.pem', - - // The path to your HTTPS certificate chain in PEM format. Required if HTTPS is enabled. - // httpsCertChainPath?: string - httpsCertChainPath: '/127_0_0_1-chain.crt.pem', - - // Specify the length of time (in seconds) in which access tokens are valid. - // accessTokenTtl?: number = 600 (ten minutes) - accessTokenTtl: 20, - - // Specify the length of time (in seconds) in which refresh tokens are valid. - // refreshTokenTtl?: number = 3153600000 (ten years) - // refreshTokenTtl: 3153600000, - - // Enable Log Compaction to save on bandwidth - // read more at https://docs.realm.io/platform/learn/advanced/log-compaction - // enableLogCompaction?: boolean = true - // enableLogCompaction: true - - // Increase or decrease the max download - // This affects how the Log Compaction works - // read more at https://docs.realm.io/platform/learn/advanced/log-compaction - // maxDownloadSize?: number 16000000 (16 megabytes) - // maxDownloadSize: 16000000 - }) - .then(() => { - console.log(`Realm Object Server was started on ${server.address}`) - }) - .catch(err => { - console.error(`Error starting Realm Object Server: ${err.message}`) - }) diff --git a/tools/sync_test_server/ros/tsconfig.json b/tools/sync_test_server/ros/tsconfig.json deleted file mode 100644 index 87c9f42aeb..0000000000 --- a/tools/sync_test_server/ros/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "moduleResolution": "node", - "noImplicitAny": false, - "removeComments": true, - "preserveConstEnums": true, - "sourceMap": true, - "outDir": "dist", - "sourceRoot": "src", - "declaration": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "skipLibCheck": true, - "lib": [ - "dom", - "es6", - "dom.iterable", - "scripthost", - "esnext.asynciterable" - ] - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "node_modules", - "dist" - ] - } - \ No newline at end of file diff --git a/tools/sync_test_server/setup_mongodb_realm.sh b/tools/sync_test_server/setup_mongodb_realm.sh new file mode 100755 index 0000000000..f7f4e61a4b --- /dev/null +++ b/tools/sync_test_server/setup_mongodb_realm.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +# +# This script is inteded to run within the mongodb-realm-cli Docker image and will setup and +# configure Stitch so it is ready for running integration tests against. +# +# If you need to re-configure Stitch, do the following +# +# 1) Run this script to start a Stitch instance +# 2) Start a browser and go to: http://127.0.0.1:9090 +# 3) Log in with "unique_user@domain.com" and "password" +# 4) Select the only Group ID there is and choose the App starting with "realm-sdk-integration-tests" +# 5) Make modifications as required +# 6) Export the app again in "Manage > Deploy > Import/Export App" +# 7) Unpack the zip file and replace the "app_config" folder. +# 8) Rerun this script +# + +cd /tmp + +echo "Waiting for Stitch to start" +while ! curl --output /dev/null --silent --head --fail http://localhost:9090; do + sleep 1 && echo -n .; +done; + +ACCESS_TOKEN=$(curl --request POST --header "Content-Type: application/json" --data '{ "username":"unique_user@domain.com", "password":"password" }' http://localhost:9090/api/admin/v3.0/auth/providers/local-userpass/login -s | jq ".access_token" -r) +GROUP_ID=$(curl --header "Authorization: Bearer $ACCESS_TOKEN" http://localhost:9090/api/admin/v3.0/auth/profile -s | jq '.roles[0].group_id' -r) + +# Enable for debug +# echo "Access token: $ACCESS_TOKEN" +echo "Group Id: $GROUP_ID" + +# 1. Log in to enable Stitch CLI commands +stitch-cli login --config-path=/tmp/stitch-config \ + --base-url=http://localhost:9090 \ + --auth-provider=local-userpass \ + --username=unique_user@domain.com \ + --password=password + +# 2. Attempt to import project. It will fail because of lacking secret, but create the App ID +# which we need to extract from the commandline output +IMPORT_RESPONSE=$(stitch-cli import \ + --config-path=/tmp/stitch-config \ + --base-url=http://localhost:9090 \ + --path=/tmp/app_config \ + --app-name realm-sdk-integration-tests \ + --project-id "$GROUP_ID" \ + --strategy replace \ + -y) +APP_ID_SUFFIX=$(echo "$IMPORT_RESPONSE" | grep "New app created:" | cut -d ':' -f 2 | cut -d '-' -f 5) +echo "App ID Suffix: $APP_ID_SUFFIX" + +# 3. Create the secret(s) needed to start the Stitch app: +# - a) MongoDB Service: Requires an URI. +stitch-cli secrets add \ + --name="integration_tests_uri" \ + --value="mongodb://localhost:26000" \ + --app-id="realm-sdk-integration-tests-$APP_ID_SUFFIX" \ + --base-url=http://localhost:9090 \ + --config-path=/tmp/stitch-config + +# 4. Due to how Stitch works internally, it is currently not possible to create a secret starting +# with '__'. This is a problem as the Stitch UI has a builtin requirement on a secret named +# "__integration_test_url". In order to fix this, we hack the JSON output from Stitch before +# importing it again. Doing it this way, makes it possible to use the output from a Stitch +# export directly. +sed -i 's/\"uri\": \"__integration_tests_uri\"/\"uri\": \"integration_tests_uri\"/g' /tmp/app_config/services/integration_tests/config.json + +# 5. Now we can correctly import the Stitch app +stitch-cli import \ + --config-path=/tmp/stitch-config \ + --base-url=http://localhost:9090 \ + --path=/tmp/app_config \ + --app-name realm-sdk-integration-tests \ + --project-id "$GROUP_ID" \ + --strategy replace \ + -y + +# 7. Store the application id in the Command Server so it can be accessed by Integration Tests on the device +curl -X PUT -d id="realm-sdk-integration-tests-$APP_ID_SUFFIX" http://localhost:8888/application-id \ No newline at end of file diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 4d4eb01889..12392e4d2d 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -1,23 +1,30 @@ #!/bin/sh -if [ -z "$REALM_FEATURE_TOKEN" ] -then - echo 'The environment variable $REALM_FEATURE_TOKEN was not set' - exit 1 -fi - # Get the script dir which contains the Dockerfile DOCKERFILE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -ROS_VERSION=$(grep REALM_OBJECT_SERVER_VERSION $DOCKERFILE_DIR/../../dependencies.list | cut -d'=' -f2) - -TMP_DIR=$(mktemp -d /tmp/sync-test.XXXX) || { echo "Failed to mktemp $TEST_TEMP_DIR" ; exit 1 ; } +MONGODB_REALM_VERSION=$(grep MONGODB_REALM_SERVER_VERSION $DOCKERFILE_DIR/../../dependencies.list | cut -d'=' -f2) adb reverse tcp:9443 tcp:9443 && \ adb reverse tcp:9080 tcp:9080 && \ +adb reverse tcp:9090 tcp:9090 && \ adb reverse tcp:8888 tcp:8888 || { echo "Failed to reverse adb port." ; exit 1 ; } -docker build $DOCKERFILE_DIR --build-arg ROS_VERSION=$ROS_VERSION --build-arg REALM_FEATURE_TOKEN=$REALM_FEATURE_TOKEN -t sync-test-server || { echo "Failed to build Docker image." ; exit 1 ; } +# Make sure that Docker works correctly with AWS by logging in +DOCKER_LOGIN=$(aws ecr get-login --no-include-email) +eval $DOCKER_LOGIN + +# Work-around for getting latest Stich image +LATEST_MONGODB_REALM_VERSION=$(aws ecr describe-images --repository-name ci/mongodb-realm-images --query 'sort_by(imageDetails,& imagePushedAt)[-1].imageTags[0]' | cut -d '"' -f 2) +LATEST_CLI_VERSION="190" + +# Run Stitch and Stitch CLI Docker images +docker network create mongodb-realm-network +docker build $DOCKERFILE_DIR -t mongodb-realm-command-server || { echo "Failed to build Docker image." ; exit 1 ; } +ID=$(docker run --rm -i -t -d --network mongodb-realm-network -p 8888:8888 -p 9090:9090 --name mongodb-realm 012067661104.dkr.ecr.eu-west-1.amazonaws.com/ci/mongodb-realm-images:"$LATEST_MONGODB_REALM_VERSION") +docker run --rm -i -t -d --network container:$ID -v$TMP_DIR:/tmp --name mongodb-realm-command-server mongodb-realm-command-server +docker run --rm -i -t -d --network container:$ID --name mongodb-realm-cli 012067661104.dkr.ecr.eu-west-1.amazonaws.com/ci/stitch-cli:"$LATEST_CLI_VERSION" -echo "See log files in $TMP_DIR" -docker run -p 9080:9080 -p 9443:9443 -p 8888:8888 -v$TMP_DIR:/tmp --name sync-test-server sync-test-server +docker cp "$DOCKERFILE_DIR"/app_config mongodb-realm-cli:/tmp/app_config +docker cp "$DOCKERFILE_DIR"/setup_mongodb_realm.sh mongodb-realm-cli:/tmp/ +docker exec -it mongodb-realm-cli sh /tmp/setup_mongodb_realm.sh diff --git a/tools/sync_test_server/stop_server.sh b/tools/sync_test_server/stop_server.sh index 6dd95f1fb4..0ae83c2ad3 100755 --- a/tools/sync_test_server/stop_server.sh +++ b/tools/sync_test_server/stop_server.sh @@ -1,4 +1,6 @@ #!/bin/sh -docker stop sync-test-server -t0 -docker rm sync-test-server +docker stop mongodb-realm -t0 +docker stop mongodb-realm-cli -t0 +docker stop mongodb-realm-command-server -t0 +docker network rm mongodb-realm-network From 63cf202e753812f8d4bdec658795522b9238fd79 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 20 Mar 2020 16:40:03 +0100 Subject: [PATCH 1483/2110] Add support for RealmApp.logOut + associated helper methods (#6779) * Add support for `RealmApp.logOut()`, `RealmApp.logOutAsync()`, `RealmUser.logOut()` and `RealmUser.logOutAsync()`. * Add support for `RealmApp.currentUser()` * Generalized Network Transport * Added new BlockingLooperThread test class. --- .../kotlin/io/realm/RealmAppExt.kt | 15 + .../kotlin/io/realm/RealmAppTests.kt | 72 +++- .../kotlin/io/realm/RealmUserTests.kt | 85 +++++ .../kotlin/io/realm/TestRealmApp.kt | 41 ++- .../io/realm/rule/BlockingLooperThread.kt | 317 ++++++++++++++++++ .../transport/OkHttpNetworkTransportTests.kt | 2 +- .../transport/OsJavaNetworkTransportTests.kt | 13 +- .../src/main/cpp/io_realm_RealmApp.cpp | 136 +++++--- ..._realm_internal_objectstore_OsSyncUser.cpp | 40 ++- .../src/main/cpp/java_network_transport.hpp | 20 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../objectServer/java/io/realm/RealmApp.java | 165 +++++++-- .../objectServer/java/io/realm/RealmUser.java | 106 +++++- .../network/OkHttpNetworkTransport.java | 5 +- .../internal/objectstore/OsSyncUser.java | 36 +- 15 files changed, 926 insertions(+), 129 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt new file mode 100644 index 0000000000..04fcbb1941 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt @@ -0,0 +1,15 @@ +package io.realm + +import androidx.test.platform.app.InstrumentationRegistry + +/** + * Resets the Realm Application and delete all local state. + * + * Trying to access any Sync or Realm App API's after this has been called has undefined + * behavior. + */ +fun RealmApp.close() { + // TODO Do we need to log out users? + SyncManager.reset() + BaseRealm.applicationContext = null // Required for Realm.init() to work +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt index 088d3b0b60..ddbdad1f10 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -16,7 +16,11 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Assert.assertNotNull +import io.realm.rule.BlockingLooperThread +import io.realm.rule.RunInLooperThread +import io.realm.rule.RunTestInLooperThread +import org.junit.After +import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -24,11 +28,17 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RealmAppTests { - private lateinit var app: RealmApp + private val looperThread = BlockingLooperThread() + private lateinit var app: TestRealmApp @Before fun setUp() { - app = TestRealmApp.getInstance() + app = TestRealmApp() + } + + @After + fun tearDown() { + app.close() } // FIXME: Smoke test for the network protocol and associated classes. @@ -38,4 +48,60 @@ class RealmAppTests { var user = app.login(creds) assertNotNull(user) } + + @Test + fun currentUser() { + assertNull(app.currentUser()) + val user: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user, app.currentUser()) + user.logOut() + assertNull(app.currentUser()) + } + + @Test + fun logOut() { + val user: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user, app.currentUser()) + app.logOut() + assertEquals(RealmUser.State.ERROR, user.state) // Should be LOGGED_OUT in a future update of OS + assertNull(app.currentUser()) + } + + @Test + fun logOutAsync() = looperThread.runBlocking { + val user: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user, app.currentUser()) + app.logOutAsync(object: RealmApp.Callback { + override fun onSuccess(callbackUser: RealmUser) { + assertNull(app.currentUser()) + assertEquals(user, callbackUser) + assertEquals(RealmUser.State.ERROR, user.state) // Should be LOGGED_OUT in a future update of OS + assertEquals(RealmUser.State.ERROR, callbackUser.state) // Should be LOGGED_OUT in a future update of OS + looperThread.testComplete() + } + + override fun onError(error: ObjectServerError) { + fail(error.toString()) + } + }) + } + + @Test + fun logOutAsync_throwsOnNonLooperThread() { + val user: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user, app.currentUser()) + val callback = object: RealmApp.Callback { + override fun onSuccess(t: RealmUser) { + fail("Method should throw") + } + override fun onError(error: ObjectServerError) { + fail("Method should throw") + } + } + try { + app.logOutAsync(callback) + fail() + } catch (ignore: IllegalStateException) { + } + } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt new file mode 100644 index 0000000000..bf14f7bcd3 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.rule.BlockingLooperThread +import io.realm.rule.RunInLooperThread +import io.realm.rule.RunTestInLooperThread +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RealmUserTests { + + val looperThread = BlockingLooperThread() + + private lateinit var app: RealmApp + private lateinit var anonUser: RealmUser + + @Before + fun setUp() { + app = TestRealmApp() + anonUser = app.login(RealmCredentials.anonymous()) + } + + @After + fun tearDown() { + app.close() + } + + @Test + fun getApp() { + assertEquals(app, anonUser.app) + } + + @Test + fun getState_anonymousUser() { + assertEquals(RealmUser.State.ACTIVE, anonUser.state) + anonUser.logOut() + assertEquals(RealmUser.State.ERROR, anonUser.state) + } + + @Ignore("Add test when registerUser works") + @Test + fun getState_emailUser() { + TODO("Implement when we implement registerUser") + } + + @Test + fun logOut() { + anonUser.logOut() + assertEquals(RealmUser.State.ERROR, anonUser.state) + } + + @Test + fun logOutAsync() = looperThread.runBlocking { + anonUser.logOutAsync(object: RealmApp.Callback { + override fun onSuccess(t: RealmUser) { + looperThread.testComplete() + } + + override fun onError(error: ObjectServerError) { + fail(error.toString()) + } + }) + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt index 4c397140f8..5e61a18bf8 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt @@ -19,7 +19,6 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.internal.network.OkHttpNetworkTransport import io.realm.internal.objectstore.OsJavaNetworkTransport import io.realm.log.LogLevel -import java.lang.IllegalStateException /** * This class wraps various methods making it easier to create an RealmApp that can be used @@ -27,22 +26,27 @@ import java.lang.IllegalStateException * * NOTE: This class must remain in the [io.realm] package in order to work. */ -class TestRealmApp private constructor() { - companion object { - private val applicationId = fetchApplicationId() - val config = RealmAppConfiguration.Builder(applicationId) - .logLevel(LogLevel.DEBUG) - .baseUrl("http://127.0.0.1:9090") - .appName("MongoDB Realm Integration Tests") - .appVersion("1.0.") - .build() +class TestRealmApp(networkTransport: OsJavaNetworkTransport? = null) : RealmApp(createConfiguration()) { - private fun init() { - Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + init { + if (networkTransport != null) { + this.networkTransport = networkTransport; + } + } + + companion object { + fun createConfiguration(): RealmAppConfiguration { + return RealmAppConfiguration.Builder(initializeMongoDbRealm()) + .logLevel(LogLevel.DEBUG) + .baseUrl("http://127.0.0.1:9090") + .appName("MongoDB Realm Integration Tests") + .appVersion("1.0.") + .build() } - private fun fetchApplicationId(): String { - init() + // Initializes MongoDB Realm. Clears all local state and fetches the application ID. + private fun initializeMongoDbRealm(): String { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) val transport = OkHttpNetworkTransport() val response = transport.sendRequest( "get", @@ -56,13 +60,6 @@ class TestRealmApp private constructor() { else -> throw IllegalStateException(response.toString()) } } - - fun getInstance(networkTransport: OsJavaNetworkTransport? = null): RealmApp { - val app = RealmApp(config) - if (networkTransport != null) { - app.networkTransport = networkTransport - } - return app - } } } + diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt new file mode 100644 index 0000000000..c429d6643b --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt @@ -0,0 +1,317 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.rule + +import android.os.Handler +import android.os.Looper +import io.realm.TestHelper +import io.realm.TestHelper.LooperTest +import io.realm.internal.android.AndroidCapabilities +import io.realm.rule.RunTestInLooperThread +import org.junit.runners.model.MultipleFailureException +import java.io.Closeable +import java.io.PrintStream +import java.util.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock +import kotlin.collections.ArrayList + +/** + * Helper class that makes it easier to run a piece of code inside a Looper Thread. This is done by + * calling the `run` method. This method will block until the Looper either completes or throws + * an exception. + * + * Usage: + * ``` + * @get:Rule + * val lopperThread = LooperThreadTest() + * + * @Before + * fun setUp() { + * // Runs before test + * } + * + * @After + * fun tearDown() { + * // Runs after test completed or failed + * } + * + * @Test + * fun myTest() = looperThread.run { + * // test code + * } + * ``` + */ +class BlockingLooperThread { + // lock protecting objects shared with the test thread + private val lock = ReentrantLock() + private var condition: CountDownLatch = CountDownLatch(1) + + // Thread safe + private val signalTestCompleted = CountDownLatch(1) + + // Access guarded by 'lock' + private var backgroundHandler: Handler? = null + + // the variables created inside the test are local and eligible for GC. + // but sometimes we need the variables to survive across different Looper + // events (Callbacks happening in the future), so we add a strong reference + // to them for the duration of the test. + // Access guarded by 'lock' + private var keepStrongReference = ArrayList() + + // List of closable resources that will be automatically closed when the test finishes. + // These will run before any methods marked with `@After`. + // Access guarded by 'lock' + private var closableResources = ArrayList() + + // Runnable guaranteed to trigger after the test either succeeded or failed. + // These will run before any methods marked with `@After`. + // Access guarded by 'lock' + private val runAfterTestIsComplete = ArrayList() + + /** + * Runs the test on a Looper thread + */ + fun runBlocking(threadName: String = "TestLooperThread", emulateMainThread: Boolean = false, test: () -> Unit) { + RunInLooperThreadStatement(threadName, emulateMainThread, test).evaluate() + } + + /** + * Hold a reference to an object, to prevent it from being GCed, + * until after the test completes. + * + * Accessed only from the main thread, here, but synchronized in case it is called from within a test. + */ + fun keepStrongReference(obj: Any) { + synchronized(lock) { keepStrongReference.add(obj) } + } + + /** + * Add a closable resource which this test will guarantee to call [Closeable.close] on + * when the tests is done. + * + * @param closeable [Closeable] to close. + */ + fun closeAfterTest(closeable: Closeable) { + synchronized(lock) { closableResources.add(closeable) } + } + + /** + * Posts a runnable to the currently running looper. + */ + fun postRunnable(runnable: Runnable) { + getBackgroundHandler().post(runnable) + } + + /** + * Posts a runnable to this worker threads looper with a delay in milli second. + */ + fun postRunnableDelayed(runnable: Runnable, delayMillis: Long) { + getBackgroundHandler().postDelayed(runnable, delayMillis) + } + + /** + * Signal that the test has completed. + */ + fun testComplete() { + // Close all resources and run any after test tasks + // Post as runnable to ensure that this code runs on the correct thread. + postRunnable(Runnable { closeTestResources() }) + } + + /** + * Internal logic for shutting down a test. + */ + private fun closeTestResources() { + try { + closeResources() + for (task in runAfterTestIsComplete) { + task.run() + } + } catch (t: Throwable) { + throw AssertionError("Failed to close test resources correctly", t) + } finally { + signalTestCompleted.countDown() + } + } + + /** + * Signal that the test has completed, after waiting for any additional latches. + * + * @param latches additional latches to wait on, before setting the test completed flag. + */ + fun testComplete(vararg latches: CountDownLatch) { + for (latch in latches) { + TestHelper.awaitOrFail(latch) + } + testComplete() + } + + private fun getBackgroundHandler(): Handler { + synchronized(lock) { + while (backgroundHandler == null) { + try { + condition.await(5*1000, TimeUnit.MILLISECONDS) + } catch (e: InterruptedException) { + throw AssertionError("Could not acquire the test handler.", e) + } + } + return backgroundHandler!! + } + } + + // Accessed from both test and main threads + // Storing the handler is the gate that indicates that the test thread has started. + private fun setBackgroundHandler(backgroundHandler: Handler?) { + synchronized(lock) { + this.backgroundHandler = backgroundHandler + condition.countDown() + } + } + + private fun before() { + synchronized(lock) { + backgroundHandler = null + keepStrongReference.clear() + closableResources.clear() + } + } + + private fun after() { + // Wait for all async tasks to have completed to ensure a successful deleteRealm call. + // If it times out, it will throw. + TestHelper.waitRealmThreadExecutorFinish() + TestHelper.waitForNetworkThreadExecutorToFinish() + AndroidCapabilities.EMULATE_MAIN_THREAD = false + + // probably belt *and* suspenders... + synchronized(lock) { + backgroundHandler = null + keepStrongReference.clear() + condition = CountDownLatch(1) + } + } + + private fun closeResources() { + synchronized(lock) { + for (cr in closableResources!!) { + cr.close() + } + } + } + + private inner class RunInLooperThreadStatement(private val threadName: String, + private val emulateMainThread: Boolean, + private val test: () -> Unit) { + + fun evaluate() { + before() + AndroidCapabilities.EMULATE_MAIN_THREAD = emulateMainThread + runTest(threadName) + after() + } + + private fun runTest(threadName: String) { + var failure: Throwable? = null + try { + val executorService = Executors.newSingleThreadExecutor { runnable -> Thread(runnable, threadName) } + val test = TestThread(test) + val ignored = executorService.submit(test) + TestHelper.exitOrThrow(executorService, signalTestCompleted, test) + } catch (testFailure: Throwable) { + // These exceptions should only come from TestHelper.awaitOrFail() + failure = testFailure + } finally { + // Tries as hard as possible to close down gracefully, while still keeping all exceptions intact. + failure = cleanUp(failure) + } + if (failure != null) { + throw failure + } + } + + private fun cleanUp(testfailure: Throwable?): Throwable? { + return try { + after() + testfailure + } catch (afterFailure: Throwable) { + if (testfailure == null) { + // Only after() threw an exception + afterFailure + } else object : MultipleFailureException(Arrays.asList(testfailure, afterFailure)) { + override fun printStackTrace(out: PrintStream) { + var i = 0 + for (t in failures) { + out.println("Error " + i + ": " + t.message) + t.printStackTrace(out) + out.println() + i++ + } + } + } + } + } + } + + private inner class TestThread internal constructor(private val test: () -> Unit) : Runnable, TestHelper.LooperTest { + private var threadAssertionError: Throwable? = null + private var looper: Looper? = null + + @Synchronized + override fun getLooper(): Looper? { + return looper + } + + @Synchronized + private fun setLooper(looper: Looper) { + this.looper = looper + setBackgroundHandler(Handler(looper)) + } + + @Synchronized + override fun getAssertionError(): Throwable? { + return threadAssertionError + } + + // Only record the first error + @Synchronized + private fun setAssertionError(threadAssertionError: Throwable) { + if (this.threadAssertionError == null) { + this.threadAssertionError = threadAssertionError + } + } + + override fun run() { + Looper.prepare() + try { + setLooper(Looper.myLooper()!!) + test() + Looper.loop() + } catch (t: Throwable) { + setAssertionError(t) + // If an exception occurred, `looperThread.testComplete()` was probably no called. + // Rerun it here, but ignore any failures as the first failure is more important. + try { + closeTestResources() + } catch (ignore: Throwable) { + } + } + } + } +} \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt index 6ee4c68931..a8c0de802b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt @@ -20,7 +20,7 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm import io.realm.internal.network.OkHttpNetworkTransport import io.realm.internal.objectstore.OsJavaNetworkTransport -import junit.framework.Assert.assertEquals +import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt index 920f1020e9..175643e41a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -39,7 +39,7 @@ class OsJavaNetworkTransportTests { // Test that the round trip works in case of a successful HTTP request. @Test fun requestSuccess() { - app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + app = TestRealmApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { var result = "" if (url.endsWith("/providers/${RealmCredentials.IdentityProvider.ANONYMOUS.id}/login")) { @@ -94,7 +94,7 @@ class OsJavaNetworkTransportTests { // to the user as an exception. @Test fun requestFailWithServerError() { - app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + app = TestRealmApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { val result = """ { @@ -121,7 +121,7 @@ class OsJavaNetworkTransportTests { // to the user. @Test fun requestFailWithHttpError() { - app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + app = TestRealmApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { return Response.httpResponse(500, mapOf(), "Boom!") } @@ -140,7 +140,7 @@ class OsJavaNetworkTransportTests { // Test that custom error codes thrown from the Java transport are correctly reported back to the user. @Test fun requestFailWithCustomError() { - app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + app = TestRealmApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { return Response.ioError("Boom!") } @@ -161,7 +161,7 @@ class OsJavaNetworkTransportTests { // to the user. @Test fun requestFailWithTransportException() { - app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + app = TestRealmApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { throw IllegalStateException("Boom!") } @@ -179,7 +179,7 @@ class OsJavaNetworkTransportTests { // Test that if the Java transport throws a fatal error it is correctly returned to the user. @Test fun requestFailWithTransportError() { - app = TestRealmApp.getInstance(object: OsJavaNetworkTransport() { + app = TestRealmApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { throw Error("Boom!") } @@ -195,3 +195,4 @@ class OsJavaNetworkTransportTests { } } + diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp index 4e1d6e360e..3bc7014076 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp @@ -19,6 +19,7 @@ #include "java_network_transport.hpp" #include "util.hpp" #include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" #include @@ -27,6 +28,58 @@ using namespace realm::app; using namespace realm::jni_util; using namespace realm::_impl; +// Helper method for constructing callbacks for REST calls that must return an actual result to Java +template +std::function)> create_result_callback(JNIEnv* env, jobject j_callback, const std::function& success_mapper) { + jobject callback = env->NewGlobalRef(j_callback); + return [callback, success_mapper](T result, Optional error) { + JNIEnv* env = JniUtils::get_env(true); + + static JavaClass java_callback_class(env, "io/realm/RealmApp$OsJNIResultCallback"); + static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); + static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); + + if (error) { + auto err = error.value(); + std::string error_category = err.error_code.category().name(); + env->CallVoidMethod(callback, + java_notify_onerror, + to_jstring(env, error_category), + err.error_code.value(), + to_jstring(env, err.message)); + } else { + jobject success_obj = success_mapper(env, result); + env->CallVoidMethod(callback, java_notify_onsuccess, success_obj); + } + env->DeleteGlobalRef(callback); + }; +} + +// Helper method for constructing callbacks for REST calls that doesn't return any results to Java. +std::function)> create_void_callback(JNIEnv* env, jobject j_callback) { + jobject callback = env->NewGlobalRef(j_callback); + return [&](Optional error) { + JNIEnv* env = JniUtils::get_env(true); + + static JavaClass java_callback_class(env, "io/realm/RealmApp$OsJNIVoidResultCallback"); + static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); + static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); + + if (error) { + auto err = error.value(); + std::string error_category = err.error_code.category().name(); + env->CallVoidMethod(callback, + java_notify_onerror, + to_jstring(env, error_category), + err.error_code.value(), + to_jstring(env, err.message)); + } else { + env->CallVoidMethod(callback, java_notify_onsuccess, NULL); + } + env->DeleteGlobalRef(callback); + }; +} + JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject obj, jstring j_app_id, jstring j_base_url, @@ -35,20 +88,12 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject jlong j_request_timeout_ms) { try { - JavaVM* jvm; - jint ret = env->GetJavaVM(&jvm); - if (ret != 0) { - throw std::runtime_error(util::format("Failed to get Java VM. Error: %d", ret)); - } jobject java_app_obj = env->NewGlobalRef(obj); // FIXME: Leaking the app object - std::function()> transport_generator = [jvm, java_app_obj] { - JNIEnv* env; - if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { - jvm->AttachCurrentThread(&env, nullptr); // Should never fail - } + std::function()> transport_generator = [java_app_obj] { + JNIEnv* env = JniUtils::get_env(true); static JavaMethod get_network_transport_method(env, java_app_obj, "getNetworkTransport", "()Lio/realm/internal/objectstore/OsJavaNetworkTransport;"); jobject network_transport_impl = env->CallObjectMethod(java_app_obj, get_network_transport_method); - return std::unique_ptr(new JavaNetworkTransport(jvm, network_transport_impl)); + return std::unique_ptr(new JavaNetworkTransport(network_transport_impl)); }; JStringAccessor app_id(env, j_app_id); @@ -56,45 +101,58 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject JStringAccessor app_name(env, j_app_name); JStringAccessor app_version(env, j_app_version); return reinterpret_cast(new App(App::Config{ - app_id, - transport_generator, - util::Optional(base_url), - util::Optional(app_name), - util::Optional(app_version), - util::Optional(j_request_timeout_ms) + app_id, + transport_generator, + util::Optional(base_url), + util::Optional(app_name), + util::Optional(app_version), + util::Optional(j_request_timeout_ms) })); } CATCH_STD() return 0; } + JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogin(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_credentials_ptr, jobject j_callback) { try { - // Caching callback method ID's in static fields to prevent looking them up more than once - static JavaClass java_callback_class(env, "io/realm/internal/objectstore/OsJavaNetworkTransport$NetworkTransportJNIResultCallback"); - static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); - static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); + App *app = reinterpret_cast(j_app_ptr); + auto credentials = reinterpret_cast(j_credentials_ptr); + std::function)> mapper = [](JNIEnv* env, std::shared_ptr user) { + auto* java_user = new std::shared_ptr(std::move(user)); + return JavaClassGlobalDef::new_long(env, reinterpret_cast(java_user)); + }; + auto callback = create_result_callback(env, j_callback, mapper); + app->log_in_with_credentials(*credentials, callback); + } + CATCH_STD() +} +JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) +{ + try { App* app = reinterpret_cast(j_app_ptr); - auto credentials = reinterpret_cast(j_credentials_ptr); - jobject callback = env->NewGlobalRef(j_callback); - app->log_in_with_credentials(*credentials, [&](std::shared_ptr user, Optional error) { - if (error) { - auto err = error.value(); - std::string error_category = err.error_code.category().name(); - env->CallVoidMethod(callback, - java_notify_onerror, - to_jstring(env, error_category), - err.error_code.value(), - to_jstring(env, err.message)); - } else { - auto* java_user = new std::shared_ptr(std::move(user)); - jobject ptr_value = JavaClassGlobalDef::new_long(env, reinterpret_cast(java_user)); - env->CallVoidMethod(callback, java_notify_onsuccess, ptr_value); - } - env->DeleteGlobalRef(callback); - }); + auto user = *reinterpret_cast*>(j_user_ptr); + app->log_out(user, create_void_callback(env, j_callback)); } CATCH_STD() } + +JNIEXPORT jobject JNICALL Java_io_realm_RealmApp_nativeCurrentUser(JNIEnv* env, jclass, jlong j_app_ptr) +{ + try { + App* app = reinterpret_cast(j_app_ptr); + std::shared_ptr user = app->current_user(); + if (user) { + auto* java_user = new std::shared_ptr(std::move(user)); + return JavaClassGlobalDef::new_long(env, reinterpret_cast(java_user)); + } + else { + return NULL; + } + } + CATCH_STD() + return NULL; +} + diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp index d2a6b6a7fb..6e9a056a37 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp @@ -25,6 +25,7 @@ using namespace realm; using namespace realm::_impl; using namespace realm::jni_util; +using namespace realm::util; static void finalize_user(jlong ptr) { @@ -96,7 +97,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGe return nullptr; } -JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetBirthDay(JNIEnv* env, jclass, jlong j_native_ptr) +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetBirthday(JNIEnv* env, jclass, jlong j_native_ptr) { try { auto user = *reinterpret_cast*>(j_native_ptr); @@ -181,3 +182,40 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGe return nullptr; } +JNIEXPORT jbyte JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetState(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + switch(user->state()) { + case SyncUser::State::LoggedOut: return static_cast(io_realm_internal_objectstore_OsSyncUser_STATE_LOGGED_OUT); + case SyncUser::State::Active: return static_cast(io_realm_internal_objectstore_OsSyncUser_STATE_ACTIVE); + case SyncUser::State::Error: return static_cast(io_realm_internal_objectstore_OsSyncUser_STATE_ERROR); + default: + throw std::logic_error(util::format("Unknown state: %1", static_cast(user->state()))); + } + } + CATCH_STD(); + return static_cast(-1); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeSetState(JNIEnv* env, jclass, jlong j_native_ptr, jbyte j_state) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + switch(j_state) { + case io_realm_internal_objectstore_OsSyncUser_STATE_LOGGED_OUT: + user->set_state(SyncUser::State::LoggedOut); + break; + case io_realm_internal_objectstore_OsSyncUser_STATE_ACTIVE: + user->set_state(SyncUser::State::Active); + break; + case io_realm_internal_objectstore_OsSyncUser_STATE_ERROR: + user->set_state(SyncUser::State::Error); + break; + default: + throw std::logic_error(util::format("Unknown state: %1", j_state)); + } + } + CATCH_STD(); +} + diff --git a/realm/realm-library/src/main/cpp/java_network_transport.hpp b/realm/realm-library/src/main/cpp/java_network_transport.hpp index 6d0a32205d..3c1971b005 100644 --- a/realm/realm-library/src/main/cpp/java_network_transport.hpp +++ b/realm/realm-library/src/main/cpp/java_network_transport.hpp @@ -22,6 +22,7 @@ #include "sync/generic_network_transport.hpp" #include "jni_util/java_class.hpp" #include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" using namespace realm::app; using namespace realm::jni_util; @@ -31,9 +32,8 @@ namespace realm { struct JavaNetworkTransport : public app::GenericNetworkTransport { - JavaNetworkTransport(JavaVM* vm, jobject java_network_transport_impl) { - m_jvm = vm; - JNIEnv* env = get_current_env(); + JavaNetworkTransport(jobject java_network_transport_impl) { + JNIEnv* env = JniUtils::get_env(true); m_java_network_transport_impl = env->NewGlobalRef(java_network_transport_impl); jclass cls = env->GetObjectClass(m_java_network_transport_impl); auto method_name = "sendRequest"; @@ -44,7 +44,7 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { void send_request_to_server(const app::Request request, std::function completionBlock) { - JNIEnv* env = get_current_env(); + JNIEnv* env = JniUtils::get_env(true); // Setup method std::string method; @@ -106,23 +106,15 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { } ~JavaNetworkTransport() { - get_current_env()->DeleteGlobalRef(m_java_network_transport_impl); + JniUtils::get_env(true)->DeleteGlobalRef(m_java_network_transport_impl); } private: - JavaVM* m_jvm; jobject m_java_network_transport_impl; // Global ref of Java implementation of the network transport. jmethodID m_send_request_method; - inline JNIEnv* get_current_env() noexcept - { - JNIEnv* env; - if (m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { - m_jvm->AttachCurrentThread(&env, nullptr); // Should never fail - } - return env; - } }; } // realm namespace #endif + diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 24b903d140..2b94e623c5 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 24b903d140e716663d2922853999de1ac3bf1ed3 +Subproject commit 2b94e623c506d0d6fc96a8fd087429fc715830d3 diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index 476e338c16..91524f5497 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -26,6 +26,7 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.internal.Keep; import io.realm.internal.RealmNotifier; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; @@ -116,7 +117,7 @@ public RealmApp(RealmAppConfiguration config) { @Nullable public RealmUser currentUser() { Long userPtr = nativeCurrentUser(nativePtr); - return (userPtr != null) ? new RealmUser(userPtr) : null; + return (userPtr != null) ? new RealmUser(userPtr, this) : null; } /** @@ -128,7 +129,7 @@ public Map allUsers() { long[] nativeUsers = nativeAllUsers(nativePtr); HashMap users = new HashMap<>(nativeUsers.length); for (int i = 0; i < nativeUsers.length; i++) { - RealmUser user = new RealmUser(nativeUsers[i]); + RealmUser user = new RealmUser(nativeUsers[i], this); users.put(user.getId(), user); } return users; @@ -152,37 +153,16 @@ public static void setCurrentUser(SyncUser user) { */ public RealmUser login(RealmCredentials credentials) throws ObjectServerError { checkNull(credentials, "credentials"); - AtomicReference user = new AtomicReference<>(null); + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeLogin(nativePtr, credentials.osCredentials.getNativePtr(), new OsJavaNetworkTransport.NetworkTransportJNIResultCallback() { + nativeLogin(nativePtr, credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { @Override - public void onSuccess(Object result) { + protected void mapSuccess(Object result, AtomicReference success) { Long nativePtr = (Long) result; - user.set(new RealmUser(nativePtr)); - } - @Override - public void onError(String nativeErrorCategory, int nativeErrorCode, String errorMessage) { - ErrorCode code = ErrorCode.fromNativeError(nativeErrorCategory, nativeErrorCode); - if (code == ErrorCode.UNKNOWN) { - // In case of UNKNOWN errors parse as much error information on as possible. - String detailedErrorMessage = String.format("{%s::%s} %s", nativeErrorCategory, nativeErrorCode, errorMessage); - error.set(new ObjectServerError(code, detailedErrorMessage)); - } else { - error.set(new ObjectServerError(code, errorMessage)); - } + success.set(new RealmUser(nativePtr, RealmApp.this)); } }); - - // ObjectStore runs all code in the same thread even though it is using a callback. - // So results should be available here. - if (user.get() == null && error.get() == null) { - throw new IllegalStateException("Network result callback did not trigger correctly"); - } - if (user.get() != null) { - return user.get(); - } else { - throw error.get(); - } + return handleResult(success, error); } /** @@ -201,11 +181,66 @@ public RealmUser run() throws ObjectServerError { }.start(); } - public static void logout(RealmUser user) { + /** + * Log the current user out of the Realm App, destroying their server state, unregistering them from the + * SDK, and removing any synced Realms associated with them from on-disk storage on next app + * launch. + *

            + * This method should be called whenever the application is committed to not using a user again. + * Failing to call this method may result in unused files and metadata needlessly taking up space. + *

            + * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} + * will be notified and user credentials will be deleted from this device. + * + * @throws IllegalStateException if no current user could be found. + * @throws ObjectServerError if an error occurred while trying to log the user out of the Realm + * App. + */ + public void logOut() { + RealmUser user = currentUser(); + if (user == null) { + throw new IllegalStateException("No current user was found."); + } + logOut(user); + } + /** + * Log the current user out of the Realm App asynchronously, destroying their server state, unregistering them from the + * SDK, and removing any synced Realms associated with them from on-disk storage on next app + * launch. + *

            + * This method should be called whenever the application is committed to not using a user again. + * Failing to call this method may result in unused files and metadata needlessly taking up space. + *

            + * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} + * will be notified and user credentials will be deleted from this device. + * + * @throws IllegalStateException if not called on a looper thread or no current user could be found. + */ + public RealmAsyncTask logOutAsync(Callback callback) { + RealmUser user = currentUser(); + if (user == null) { + throw new IllegalStateException("No current user was found."); + } + return logOutAsync(user, callback); + } + + void logOut(RealmUser user) { + checkNull(user, "user"); + AtomicReference error = new AtomicReference<>(null); + nativeLogOut(nativePtr, user.osUser.getNativePtr(), new OsJNIVoidResultCallback(error)); + handleResult(null, error); } - public RealmAsyncTask logoutAsync(RealmUser user, Callback callback) { - return null; + + RealmAsyncTask logOutAsync(RealmUser user, Callback callback) { + checkLooperThread("Asynchronous log out is only possible from looper threads."); + return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public RealmUser run() throws ObjectServerError { + logOut(user); + return user; + } + }.start(); } public RealmUser registerWithEmail(String email, String password) { @@ -319,6 +354,72 @@ private void checkNull(@Nullable Object argValue, String argName) { } } + // Common callback for handling callbacks from the ObjectStore layer. + // NOTE: This class is called from JNI. If renamed, adjust callbacks in RealmApp.cpp + @Keep + private static class OsJNIVoidResultCallback extends OsJNIResultCallback { + + public OsJNIVoidResultCallback(AtomicReference error) { + super(null, error); + } + + @Override + protected void mapSuccess(Object result, AtomicReference success) { + // Do nothing + } + } + + // Common callback for handling results from the ObjectStore layer. + // NOTE: This class is called from JNI. If renamed, adjust callbacks in RealmApp.cpp + @Keep + private static abstract class OsJNIResultCallback extends OsJavaNetworkTransport.NetworkTransportJNIResultCallback { + + private final AtomicReference success; + private final AtomicReference error; + + public OsJNIResultCallback(@Nullable AtomicReference success, AtomicReference error) { + this.success = success; + this.error = error; + } + + @Override + public void onSuccess(Object result) { + mapSuccess(result, success); + } + + // Must map the underlying success Object to the appropriate type in Java + protected abstract void mapSuccess(Object result, @Nullable AtomicReference success); + + @Override + public void onError(String nativeErrorCategory, int nativeErrorCode, String errorMessage) { + ErrorCode code = ErrorCode.fromNativeError(nativeErrorCategory, nativeErrorCode); + if (code == ErrorCode.UNKNOWN) { + // In case of UNKNOWN errors parse as much error information on as possible. + String detailedErrorMessage = String.format("{%s::%s} %s", nativeErrorCategory, nativeErrorCode, errorMessage); + error.set(new ObjectServerError(code, detailedErrorMessage)); + } else { + error.set(new ObjectServerError(code, errorMessage)); + } + } + } + + // Handle returning the correct result or throw an exception. Must be separated from + // OsJNIResultCallback due to how + private T handleResult(@Nullable AtomicReference success, AtomicReference error) { + if (success != null && success.get() == null && error.get() == null) { + throw new IllegalStateException("Network result callback did not trigger correctly"); + } + if (error.get() != null) { + throw error.get(); + } else { + if (success != null) { + return success.get(); + } else { + return null; + } + } + } + // Class wrapping requests made against MongoDB Realm. Is also responsible for calling with success/error on the // correct thread. private static abstract class Request { @@ -408,4 +509,6 @@ public interface Callback { @Nullable private static native Long nativeCurrentUser(long nativePtr); private static native long[] nativeAllUsers(long nativePtr); + private static native void nativeLogOut(long appNativePtr, long userNativePtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } + diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java index 742e2dc063..883010e23c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -28,7 +28,8 @@ */ public class RealmUser { - private final OsSyncUser osUser; + final OsSyncUser osUser; + private final RealmApp app; /** * FIXME @@ -49,8 +50,26 @@ public String getKey() { } } - RealmUser(long nativePtr) { + public enum State { + ACTIVE(OsSyncUser.STATE_ACTIVE), + ERROR(OsSyncUser.STATE_ERROR), + LOGGED_OUT(OsSyncUser.STATE_LOGGED_OUT); + + private final byte nativeValue; + + State(byte nativeValue) { + this.nativeValue = nativeValue; + } + + byte getKey() { + return nativeValue; + } + } + + + RealmUser(long nativePtr, RealmApp app) { this.osUser = new OsSyncUser(nativePtr); + this.app = app; } /** @@ -161,7 +180,6 @@ public List getIdentities() { * FIXME * @return */ - public String getAccessToken() { return osUser.getAccessToken(); } @@ -174,4 +192,86 @@ public String getRefreshToken() { return osUser.getRefreshToken(); } + + /** + * Returns the {@link RealmApp} this user is associated with. + * + * @return the {@link RealmApp} this user is associated with. + */ + public RealmApp getApp() { + return app; + } + + /** + * Returns the {@link State} the user is in. + * + * @return the {@link State} of the user. + */ + public State getState() { + byte nativeState = osUser.getState(); + for (State state : State.values()) { + if (state.nativeValue == nativeState) { + return state; + } + } + throw new IllegalStateException("Unknown state: " + nativeState); + } + + /** + * Log the user out of the Realm App, destroying their server state, unregistering them from the + * SDK, and removing any synced Realms associated with them from on-disk storage on next app + * launch. + *

            + * If the user is already logged out, this method does nothing. + *

            + * This method should be called whenever the application is committed to not using a user again. + * Failing to call this method may result in unused files and metadata needlessly taking up space. + *

            + * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} + * will be notified and user credentials will be deleted from this device. + * + * @throws ObjectServerError if an error occurred while trying to log the user out of the Realm + * App. + */ + public void logOut() { + app.logOut(this); + } + + /** + * Log the user out of the Realm App, destroying their server state, unregistering them from the + * SDK, and removing any synced Realms associated with them from on-disk storage on next app + * launch. If the user is already logged out or in an error state, this method does nothing. + *

            + * If the user is already logged out, this method does nothing. + *

            + * This method should be called whenever the application is committed to not using a user again. + * Failing to call this method may result in unused files and metadata needlessly taking up space. + *

            + * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} + * will be notified and user credentials will be deleted from this device. + * + * @throws IllegalStateException if not called on a looper thread. + */ + public RealmAsyncTask logOutAsync(RealmApp.Callback callback) { + return app.logOutAsync(this, callback); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RealmUser realmUser = (RealmUser) o; + + if (!osUser.equals(realmUser.osUser)) return false; + return app.equals(realmUser.app); + } + + @Override + public int hashCode() { + int result = osUser.hashCode(); + result = 31 * result + app.hashCode(); + return result; + } } + diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index f195a65d44..1c85930ad4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -92,7 +92,7 @@ private synchronized OkHttpClient getClient(long timeoutMs) { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request request = chain.request(); - if (RealmLog.getLevel() <= LogLevel.TRACE) { + if (RealmLog.getLevel() <= LogLevel.DEBUG) { StringBuilder sb = new StringBuilder(request.method()); sb.append(' '); sb.append(request.url()); @@ -105,7 +105,7 @@ public okhttp3.Response intercept(Chain chain) throws IOException { request.body().writeTo(buffer); sb.append(buffer.readString(UTF8)); } - RealmLog.trace("HTTP Request = \n%s", sb); + RealmLog.debug("HTTP Request = \n%s", sb); } return chain.proceed(request); } @@ -130,3 +130,4 @@ private Map parseHeaders(Headers headers) { } } + diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java index 6176c1ccf6..69fb05eccb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java @@ -23,6 +23,10 @@ public class OsSyncUser implements NativeObject { private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); + public static final byte STATE_ACTIVE = 1; + public static final byte STATE_ERROR = 2; + public static final byte STATE_LOGGED_OUT = 3; + public OsSyncUser(long nativePtr) { this.nativePtr = nativePtr; } @@ -95,10 +99,32 @@ public Pair[] getIdentities() { return identities; } + /** + * @return {@link #STATE_ACTIVE}, {@link #STATE_LOGGED_OUT} or {@link #STATE_ERROR} + */ + public byte getState() { + return nativeGetState(nativePtr); + } - private static native long nativeGetFinalizerMethodPtr(); + public void invalidate() { + nativeSetState(nativePtr, STATE_ERROR); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + OsSyncUser that = (OsSyncUser) o; + return getIdentity().equals(that.getIdentity()); + } - // Profile data + @Override + public int hashCode() { + return getIdentity().hashCode(); + } + + private static native long nativeGetFinalizerMethodPtr(); private static native String nativeGetName(long nativePtr); private static native String nativeGetEmail(long nativePtr); private static native String nativeGetPictureUrl(long nativePtr); @@ -108,12 +134,10 @@ public Pair[] getIdentities() { private static native String nativeGetBirthday(long nativePtr); private static native String nativeGetMinAge(long nativePtr); private static native String nativeGetMaxAge(long nativePtr); - private static native String nativeGetIdentity(long nativePtr); private static native String nativeGetAccessToken(long nativePtr); private static native String nativeGetRefreshToken(long nativePtr); private static native String[] nativeGetIdentities(long nativePtr); // Returns pairs of {id, provider} + private static native byte nativeGetState(long nativePtr); + private static native void nativeSetState(long nativePtr, byte state); } - - - From c4c93a48c59dbb69a20eb9df7d5cc7ec4ee336b9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 28 Mar 2020 00:18:24 +0100 Subject: [PATCH 1484/2110] Switch to using Github Docker Registry (#6781) --- Jenkinsfile | 53 +++++++++++-------- dependencies.list | 3 +- tools/sync_test_server/setup_mongodb_realm.sh | 2 +- tools/sync_test_server/start_server.sh | 40 +++++++++----- tools/sync_test_server/stop_server.sh | 1 - 5 files changed, 61 insertions(+), 38 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index fc71f32cb5..acc93e005b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,7 +4,6 @@ import groovy.json.JsonOutput def buildSuccess = false def mongoDbRealmContainer = null -def mongoDbRealmCLIContainer = null def mongoDbRealmCommandServerContainer = null def dockerNetworkId = UUID.randomUUID().toString() try { @@ -41,27 +40,23 @@ try { // Prepare Docker images // FIXME: Had issues moving these into a seperate Stage step. Is this needed? buildEnv = docker.build 'realm-java:snapshot' - // `aws ecr describe-images --repository-name ci/mongodb-realm-images --query 'sort_by(imageDetails,& imagePushedAt)[-1].imageTags[0]'` - def version = "test_server-26e6463b98d8e3f0f4522a70e37f105d34b688a9-race" - def mdbRealmImage = docker.image("${env.DOCKER_REGISTRY}/ci/mongodb-realm-images:${version}") - def stitchCliImage = docker.image("${env.DOCKER_REGISTRY}/ci/stitch-cli:190") - docker.withRegistry("https://${env.DOCKER_REGISTRY}", "ecr:eu-west-1:aws-ci-user") { + def props = readProperties file: 'dependencies.list' + echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" + def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") + docker.withRegistry('https://docker.pkg.github.com', 'github-packages-token') { mdbRealmImage.pull() - stitchCliImage.pull() } def commandServerEnv = docker.build 'mongodb-realm-command-server', "tools/sync_test_server" try { // Prepare Docker containers used by Instrumentation tests // TODO: How much of this logic can be moved to start_server.sh for shared logic with local testing. - sh "docker network create ${dockerNetworkId}" mongoDbRealmContainer = mdbRealmImage.run("--network ${dockerNetworkId}") - mongoDbRealmCLIContainer = stitchCliImage.run("-t --network container:${mongoDbRealmContainer.id}") mongoDbRealmCommandServerContainer = commandServerEnv.run("--network container:${mongoDbRealmContainer.id}") - sh "docker cp tools/sync_test_server/app_config ${mongoDbRealmCLIContainer.id}:/tmp/app_config" - sh "docker cp tools/sync_test_server/setup_mongodb_realm.sh ${mongoDbRealmCLIContainer.id}:/tmp/" - sh "docker exec -i ${mongoDbRealmCLIContainer.id} sh /tmp/setup_mongodb_realm.sh" + sh "docker cp tools/sync_test_server/app_config ${mongoDbRealmContainer.id}:/tmp/app_config" + sh "docker cp tools/sync_test_server/setup_mongodb_realm.sh ${mongoDbRealmContainer.id}:/tmp/" + sh "docker exec -i ${mongoDbRealmContainer.id} sh /tmp/setup_mongodb_realm.sh" buildEnv.inside("-e HOME=/tmp " + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + @@ -155,12 +150,8 @@ try { } } } finally { - // FIXME: Figure out which logs we need to safe, if any? - // archiveRosLog(rosContainer.id) - // sh "docker logs ${rosContainer.id}" - // rosContainer.stop() + archiveServerLogs(mongoDbRealmContainer.id, mongoDbRealmCommandServerContainer.id) mongoDbRealmContainer.stop() - mongoDbRealmCLIContainer.stop() mongoDbRealmCommandServerContainer.stop() sh "docker network rm ${dockerNetworkId}" } @@ -216,14 +207,30 @@ def stopLogCatCollector(String backgroundPid) { sh 'rm logcat.txt' } -def archiveRosLog(String id) { - sh "docker cp ${id}:/tmp/integration-test-command-server.log ./ros.log" +def archiveServerLogs(String mongoDbRealmContainerId, String commandServerContainerId) { + sh "docker logs ${commandServerContainerId} > ./command-server.log" zip([ - 'zipFile': 'roslog.zip', - 'archive': true, - 'glob' : 'ros.log' + 'zipFile': 'command-server-log.zip', + 'archive': true, + 'glob' : 'command-server.log' + ]) + sh 'rm command-server.log' + + sh "docker cp ${mongoDbRealmContainerId}:/var/log/stitch.log ./stitch.log" + zip([ + 'zipFile': 'stitchlog.zip', + 'archive': true, + 'glob' : 'stitch.log' + ]) + sh 'rm stitch.log' + + sh "docker cp ${mongoDbRealmContainerId}:/var/log/mongodb.log ./mongodb.log" + zip([ + 'zipFile': 'mongodb.zip', + 'archive': true, + 'glob' : 'mongodb.log' ]) - sh 'rm ros.log' + sh 'rm mongodb.log' } def sendMetrics(String metricName, String metricValue, Map tags) { diff --git a/dependencies.list b/dependencies.list index 0452506e20..3fb3f9d7bf 100644 --- a/dependencies.list +++ b/dependencies.list @@ -8,7 +8,8 @@ REALM_SYNC_SHA256=7048eff89f00554aa4014239a25d419fc98d0b22074ff2deadd3e9717a5c4d REALM_OBJECT_SERVER_VERSION=3.28.2 # Version of MongoDB Realm used by integration tests -MONGODB_REALM_SERVER_VERSION=test_server-0ed2349a36352666402d0fb2e8763ac67731768c-race +# See https://github.com/realm/ci/packages/147854 for available versions +MONGODB_REALM_SERVER_VERSION=2020-03-25 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/tools/sync_test_server/setup_mongodb_realm.sh b/tools/sync_test_server/setup_mongodb_realm.sh index f7f4e61a4b..2720bc8821 100755 --- a/tools/sync_test_server/setup_mongodb_realm.sh +++ b/tools/sync_test_server/setup_mongodb_realm.sh @@ -31,7 +31,7 @@ GROUP_ID=$(curl --header "Authorization: Bearer $ACCESS_TOKEN" http://localhost: echo "Group Id: $GROUP_ID" # 1. Log in to enable Stitch CLI commands -stitch-cli login --config-path=/tmp/stitch-config \ +yes | stitch-cli login --config-path=/tmp/stitch-config \ --base-url=http://localhost:9090 \ --auth-provider=local-userpass \ --username=unique_user@domain.com \ diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 12392e4d2d..53547ed32c 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -1,5 +1,26 @@ #!/bin/sh +# How to use this script: +# +# 1. Logging into GitHub +# 2. Goto "Settings > Developer Settings > Personal access tokens" +# 3. Press "Generate new Token" +# 4. Select "read:packages" as Scope. Give it a name and create the token. +# 5. Store the token in a environment variable called GITHUB_DOCKER_TOKEN. +# 6. Store the GitHub username in an environment variable called GITHUB_DOCKER_USER. +# 7. Run this script. + +# Verify that Github username and tokens are available as environment vars +if [[ -z "${GITHUB_DOCKER_USER}" ]]; then + echo "Could not find \$GITHUB_DOCKER_USER as an environment variabel" + exit 1 +fi + +if [[ -z "${GITHUB_DOCKER_TOKEN}" ]]; then + echo "Could not find \$GITHUB_DOCKER_TOKEN as an environment variabel. This is used to download Docker Registry packages." + exit 1 +fi + # Get the script dir which contains the Dockerfile DOCKERFILE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -10,21 +31,16 @@ adb reverse tcp:9080 tcp:9080 && \ adb reverse tcp:9090 tcp:9090 && \ adb reverse tcp:8888 tcp:8888 || { echo "Failed to reverse adb port." ; exit 1 ; } -# Make sure that Docker works correctly with AWS by logging in -DOCKER_LOGIN=$(aws ecr get-login --no-include-email) -eval $DOCKER_LOGIN - -# Work-around for getting latest Stich image -LATEST_MONGODB_REALM_VERSION=$(aws ecr describe-images --repository-name ci/mongodb-realm-images --query 'sort_by(imageDetails,& imagePushedAt)[-1].imageTags[0]' | cut -d '"' -f 2) -LATEST_CLI_VERSION="190" +# Make sure that Docker works correctly with Github Docker Registry by logging in +docker login docker.pkg.github.com -u $GITHUB_DOCKER_USER -p $GITHUB_DOCKER_TOKEN # Run Stitch and Stitch CLI Docker images docker network create mongodb-realm-network docker build $DOCKERFILE_DIR -t mongodb-realm-command-server || { echo "Failed to build Docker image." ; exit 1 ; } -ID=$(docker run --rm -i -t -d --network mongodb-realm-network -p 8888:8888 -p 9090:9090 --name mongodb-realm 012067661104.dkr.ecr.eu-west-1.amazonaws.com/ci/mongodb-realm-images:"$LATEST_MONGODB_REALM_VERSION") +ID=$(docker run --rm -i -t -d --network mongodb-realm-network -p 9090:9090 -p 8888:8888 --name mongodb-realm docker.pkg.github.com/realm/ci/mongodb-realm-test-server:$MONGODB_REALM_VERSION) docker run --rm -i -t -d --network container:$ID -v$TMP_DIR:/tmp --name mongodb-realm-command-server mongodb-realm-command-server -docker run --rm -i -t -d --network container:$ID --name mongodb-realm-cli 012067661104.dkr.ecr.eu-west-1.amazonaws.com/ci/stitch-cli:"$LATEST_CLI_VERSION" -docker cp "$DOCKERFILE_DIR"/app_config mongodb-realm-cli:/tmp/app_config -docker cp "$DOCKERFILE_DIR"/setup_mongodb_realm.sh mongodb-realm-cli:/tmp/ -docker exec -it mongodb-realm-cli sh /tmp/setup_mongodb_realm.sh +docker cp "$DOCKERFILE_DIR"/app_config mongodb-realm:/tmp/app_config +docker cp "$DOCKERFILE_DIR"/setup_mongodb_realm.sh mongodb-realm:/tmp/ +docker exec -it mongodb-realm sh /tmp/setup_mongodb_realm.sh + diff --git a/tools/sync_test_server/stop_server.sh b/tools/sync_test_server/stop_server.sh index 0ae83c2ad3..9caef2c680 100755 --- a/tools/sync_test_server/stop_server.sh +++ b/tools/sync_test_server/stop_server.sh @@ -1,6 +1,5 @@ #!/bin/sh docker stop mongodb-realm -t0 -docker stop mongodb-realm-cli -t0 docker stop mongodb-realm-command-server -t0 docker network rm mongodb-realm-network From 8878265d992449fb127db8fddfb1960e57835766 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 29 Mar 2020 22:52:36 +0200 Subject: [PATCH 1485/2110] Add support for EmailPasswordAuthProvider (#6783) --- .../realm/EmailPasswordAuthProviderTests.kt | 535 ++++++++++++++++++ .../kotlin/io/realm/RealmAppExt.kt | 11 +- .../kotlin/io/realm/RealmAppTests.kt | 30 +- .../kotlin/io/realm/RealmUserTests.kt | 13 +- .../kotlin/io/realm/admin/ServerAdmin.kt | 191 +++++++ .../transport/OsJavaNetworkTransportTests.kt | 2 +- .../realm-library/src/main/cpp/CMakeLists.txt | 11 +- .../io_realm_EmailPasswordAuthProvider.cpp | 67 +++ .../src/main/cpp/io_realm_RealmApp.cpp | 56 +- ..._realm_internal_objectstore_OsSyncUser.cpp | 9 + .../src/main/cpp/java_network_transport.hpp | 52 ++ .../src/main/java/io/realm/internal/Util.java | 18 + .../io/realm/EmailPasswordAuthProvider.java | 294 ++++++++++ .../objectServer/java/io/realm/ErrorCode.java | 8 +- .../objectServer/java/io/realm/RealmApp.java | 209 ++++--- .../internal/objectstore/OsSyncUser.java | 5 + .../testUtils/java/io/realm/TestHelper.java | 6 + .../auth_providers/local-userpass.json | 5 +- .../app_config/functions/resetFunc/source.js | 9 +- 19 files changed, 1359 insertions(+), 172 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt create mode 100644 realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuthProvider.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuthProvider.java diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt new file mode 100644 index 0000000000..df1d9a4636 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt @@ -0,0 +1,535 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.annotation.UiThreadTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.admin.ServerAdmin +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.rule.BlockingLooperThread +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.IllegalStateException + +@RunWith(AndroidJUnit4::class) +class EmailPasswordAuthProviderTests { + + private val looperThread = BlockingLooperThread() + private lateinit var app: TestRealmApp + private lateinit var admin: ServerAdmin + + // Callback use to verify that an Illegal Argument was thrown from async methods + private val checkNullArgCallback = RealmApp.Callback { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.UNKNOWN, result.error.errorCode) + looperThread.testComplete() + } + } + + // Methods exposed by the EmailPasswordAuthProvider + enum class Method { + REGISTER_USER, + CONFIRM_USER, + RESEND_CONFIRMATION_EMAIL, + SEND_RESET_PASSWORD_EMAIL, + CALL_RESET_PASSWORD_FUNCTION, + RESET_PASSWORD + } + + @Before + fun setUp() { + app = TestRealmApp() + RealmLog.setLevel(LogLevel.DEBUG) + admin = ServerAdmin() + } + + @After + fun tearDown() { + app.close() + admin.deleteAllUsers() + RealmLog.setLevel(LogLevel.WARN) + } + + inline fun expectException(method: () -> Unit) { + try { + method() + fail() + } catch (e: Throwable) { + if (e !is T) { + fail("Unexpected exception: $e") + } + } + } + + @Test + fun registerUser() { + val email = TestHelper.getRandomEmail() + val password = "password1234" + app.emailPasswordAuthProvider.registerUser(email, password) + val user = app.login(RealmCredentials.emailPassword(email, password)) + assertEquals(RealmUser.State.ACTIVE, user.state) + } + + @Test + fun registerUserAsync() { + val email = TestHelper.getRandomEmail() + val password = "password1234" + looperThread.runBlocking { + app.emailPasswordAuthProvider.registerUserAsync(email, password) { result -> + if (result.isSuccess) { + val user2 = app.login(RealmCredentials.emailPassword(email, password)) + assertEquals(RealmUser.State.ACTIVE, user2.state) + looperThread.testComplete() + } else { + fail(result.error.toString()) + } + } + } + } + + @Test + fun registerUser_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + try { + provider.registerUser("invalid-email", "1234") + fail() + } catch (ex: ObjectServerError) { + assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) + } + } + + @Test + fun registerUserAsync_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + looperThread.runBlocking { + provider.registerUserAsync("invalid-email", "1234") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.BAD_REQUEST, result.error.errorCode) + looperThread.testComplete() + } + } + } + } + + @Test + fun registerUser_invalidArgumentsThrows() { + val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider + expectException { provider.registerUser(TestHelper.getNullString(), "123456") } + expectException { provider.registerUser("foo@bar.baz", TestHelper.getNullString()) } + looperThread.runBlocking { + provider.registerUserAsync(TestHelper.getNullString(), "123456", checkNullArgCallback) + } + looperThread.runBlocking { + provider.registerUserAsync("foo@bar.baz", TestHelper.getNullString(), checkNullArgCallback) + } + } + + @Ignore("Find a way to automate this") + @Test + fun confirmUser() { + TODO("Figure out how to manually test this") + } + + @Ignore("Find a way to automate this") + @Test + fun confirmUserAsync() { + TODO("Figure out how to manually test this") + } + + @Test + fun confirmUser_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + try { + provider.confirmUser("invalid-token", "invalid-token-id") + fail() + } catch (ex: ObjectServerError) { + assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) + } + } + + @Test + fun confirmUserAsync_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + looperThread.runBlocking { + provider.confirmUserAsync("invalid-email", "1234") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.BAD_REQUEST, result.error.errorCode) + looperThread.testComplete() + } + } + } + } + + @Test + fun confirmUser_invalidArgumentsThrows() { + val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider + expectException { provider.confirmUser(TestHelper.getNullString(), "token-id") } + expectException { provider.confirmUser("token", TestHelper.getNullString()) } + looperThread.runBlocking { + provider.confirmUserAsync(TestHelper.getNullString(), "token-id", checkNullArgCallback) + } + looperThread.runBlocking { + provider.confirmUserAsync("token", TestHelper.getNullString(), checkNullArgCallback) + } + } + + @Test + fun resendConfirmationEmail() { + // We only test that the server successfully accepts the request. We have no way of knowing + // if the Email was actually sent. + // FIXME: Figure out a way to check if this actually happened. Perhaps a custom SMTP server? + val email = "test@10gen.com" + admin.setAutomaticConfirmation(false) + try { + val provider = app.emailPasswordAuthProvider + provider.registerUser(email, "123456") + provider.resendConfirmationEmail(email) + } finally { + admin.setAutomaticConfirmation(true) + } + } + + @Test + fun resendConfirmationEmailAsync() { + // We only test that the server successfully accepts the request. We have no way of knowing + // if the Email was actually sent. + val email = "test@10gen.com" + admin.setAutomaticConfirmation(false) + try { + looperThread.runBlocking { + val provider = app.emailPasswordAuthProvider + provider.registerUser(email, "123456") + provider.resendConfirmationEmailAsync(email) { result -> + when(result.isSuccess) { + true -> looperThread.testComplete() + false -> fail(result.error.toString()) + } + } + } + } finally { + admin.setAutomaticConfirmation(true) + } + } + + @Test + fun resendConfirmationEmail_invalidServerArgsThrows() { + val email = "test@10gen.com" + admin.setAutomaticConfirmation(false) + val provider = app.emailPasswordAuthProvider + provider.registerUser(email, "123456") + try { + provider.resendConfirmationEmail("foo") + fail() + } catch (error: ObjectServerError) { + assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) + } finally { + admin.setAutomaticConfirmation(true) + } + } + + @Test + fun resendConfirmationEmailAsync_invalidServerArgsThrows() { + val email = "test@10gen.com" + admin.setAutomaticConfirmation(false) + val provider = app.emailPasswordAuthProvider + provider.registerUser(email, "123456") + try { + looperThread.runBlocking { + provider.resendConfirmationEmailAsync("foo") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + } finally { + admin.setAutomaticConfirmation(true) + } + } + + @Test + fun resendConfirmationEmail_invalidArgumentsThrows() { + val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider + expectException { provider.resendConfirmationEmail(TestHelper.getNullString()) } + looperThread.runBlocking { + provider.resendConfirmationEmailAsync(TestHelper.getNullString(), checkNullArgCallback) + } + } + + @Test + fun sendResetPasswordEmail() { + val provider = app.emailPasswordAuthProvider + val email: String = "test@10gen.com" // Must be a valid email, otherwise the server will fail + provider.registerUser(email, "123456") + provider.sendResetPasswordEmail(email) + } + + @Test + fun sendResetPasswordEmailAsync() { + val provider = app.emailPasswordAuthProvider + val email: String = "test@10gen.com" // Must be a valid email, otherwise the server will fail + provider.registerUser(email, "123456") + looperThread.runBlocking { + provider.sendResetPasswordEmailAsync(email) { result -> + when(result.isSuccess) { + true -> looperThread.testComplete() + false -> fail(result.error.toString()) + } + + } + } + } + + @Test + fun sendResetPasswordEmail_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + try { + provider.sendResetPasswordEmail("unknown@10gen.com") + fail() + } catch (error: ObjectServerError) { + assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) + } + } + + @Test + fun sendResetPasswordEmailAsync_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + looperThread.runBlocking { + provider.sendResetPasswordEmailAsync("unknown@10gen.com") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + } + + @Test + fun sendResetPasswordEmail_invalidArgumentsThrows() { + val provider = app.emailPasswordAuthProvider + expectException { provider.sendResetPasswordEmail(TestHelper.getNullString()) } + looperThread.runBlocking { + provider.sendResetPasswordEmailAsync(TestHelper.getNullString(), checkNullArgCallback) + } + } + + @Test + fun callResetPasswordFunction() { + val provider = app.emailPasswordAuthProvider + admin.setResetFunction(enabled = true) + val email = TestHelper.getRandomEmail() + provider.registerUser(email, "123456") + try { + provider.callResetPasswordFunction(email, "new-password", "say-the-magic-word", 42) + app.login(RealmCredentials.emailPassword(email, "new-password")) + app.logOut() + } finally { + admin.setResetFunction(enabled = false) + } + } + + @Test + fun callResetPasswordFunctionAsync() { + val provider = app.emailPasswordAuthProvider + admin.setResetFunction(enabled = true) + val email = TestHelper.getRandomEmail() + provider.registerUser(email, "123456") + try { + looperThread.runBlocking { + provider.callResetPasswordFunctionAsync(email, + "new-password", + arrayOf("say-the-magic-word", 42)) { result -> + if (result.isSuccess) { + app.login(RealmCredentials.emailPassword(email, "new-password")) + app.logOut() + looperThread.testComplete() + } else { + fail(result.error.toString()) + } + } + } + } finally { + admin.setResetFunction(enabled = false) + } + } + + @Test + fun callResetPasswordFunction_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + admin.setResetFunction(enabled = true) + val email = TestHelper.getRandomEmail() + provider.registerUser(email, "123456") + try { + provider.callResetPasswordFunction(email, "new-password", "wrong-magic-word") + } catch (error: ObjectServerError) { + assertEquals(ErrorCode.SERVICE_UNKNOWN, error.errorCode) + } finally { + admin.setResetFunction(enabled = false) + } + } + + @Test + fun callResetPasswordFunctionAsync_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + admin.setResetFunction(enabled = true) + val email = TestHelper.getRandomEmail() + provider.registerUser(email, "123456") + try { + looperThread.runBlocking { + provider.callResetPasswordFunctionAsync( + email, + "new-password", + arrayOf("wrong-magic-word")) { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.SERVICE_UNKNOWN, result.error.errorCode) + looperThread.testComplete() + } + } + } + } finally { + admin.setResetFunction(enabled = false) + } + } + + @Test + fun callResetPasswordFunction_invalidArgumentsThrows() { + val provider = app.emailPasswordAuthProvider + expectException { provider.callResetPasswordFunction(TestHelper.getNullString(), "password") } + expectException { provider.callResetPasswordFunction("foo@bar.baz", TestHelper.getNullString()) } + looperThread.runBlocking { + provider.callResetPasswordFunctionAsync(TestHelper.getNullString(), "new-password", arrayOf(), checkNullArgCallback) + } + looperThread.runBlocking { + provider.callResetPasswordFunctionAsync("foo@bar.baz", io.realm.TestHelper.getNullString(), arrayOf(), checkNullArgCallback) + } + } + + @Ignore("Find a way to automate this") + @Test + fun resetPassword() { + TODO("How to test this manually?") + } + + @Ignore("Find a way to automate this") + @Test + fun resetPasswordAsync() { + TODO("How to test this manually?") + } + + @Test + fun resetPassword_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + try { + provider.resetPassword("invalid-token", "invalid-token-id", "new-password") + } catch (error: ObjectServerError) { + assertEquals(ErrorCode.BAD_REQUEST, error.errorCode) + } + } + + @Test + fun resetPasswordASync_invalidServerArgsThrows() { + val provider = app.emailPasswordAuthProvider + looperThread.runBlocking { + provider.resetPasswordAsync("invalid-token", "invalid-token-id", "new-password") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.BAD_REQUEST, result.error.errorCode) + looperThread.testComplete() + } + } + } + } + + @Test + fun resetPassword_invalidArgumentsThrows() { + val provider = app.emailPasswordAuthProvider + expectException { provider.resetPassword(TestHelper.getNullString(), "token-id", "password") } + expectException { provider.resetPassword("token", TestHelper.getNullString(), "password") } + expectException { provider.resetPassword("token", "token-id", TestHelper.getNullString()) } + looperThread.runBlocking { + provider.resetPasswordAsync(TestHelper.getNullString(), "token-id", "password", checkNullArgCallback) + } + looperThread.runBlocking { + provider.resetPasswordAsync("token", TestHelper.getNullString(), "password", checkNullArgCallback) + } + looperThread.runBlocking { + provider.resetPasswordAsync("token","token-id", TestHelper.getNullString(), checkNullArgCallback) + } + } + + @Test + @UiThreadTest + fun callMethodsOnMainThreadThrows() { + val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider + val email: String = TestHelper.getRandomEmail() + for (method in Method.values()) { + try { + when(method) { + Method.REGISTER_USER -> provider.registerUser(email, "123456") + Method.CONFIRM_USER -> provider.confirmUser("token", "tokenId") + Method.RESEND_CONFIRMATION_EMAIL -> provider.resendConfirmationEmail(email) + Method.SEND_RESET_PASSWORD_EMAIL -> provider.sendResetPasswordEmail(email) + Method.CALL_RESET_PASSWORD_FUNCTION -> provider.callResetPasswordFunction(email, "123456") + Method.RESET_PASSWORD -> provider.resetPassword("token", "token-id", "password") + } + fail("$method should have thrown an exception") + } catch (error: ObjectServerError) { + assertEquals(ErrorCode.NETWORK_UNKNOWN, error.errorCode) + } + } + } + + @Test + fun callAsyncMethodsOnNonLooperThreadThrows() { + val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider + val email: String = TestHelper.getRandomEmail() + val callback = RealmApp.Callback { fail() } + for (method in Method.values()) { + try { + when(method) { + Method.REGISTER_USER -> provider.registerUserAsync(email, "123456", callback) + Method.CONFIRM_USER -> provider.confirmUserAsync("token", "tokenId", callback) + Method.RESEND_CONFIRMATION_EMAIL -> provider.resendConfirmationEmailAsync(email, callback) + Method.SEND_RESET_PASSWORD_EMAIL -> provider.sendResetPasswordEmailAsync(email, callback) + Method.CALL_RESET_PASSWORD_FUNCTION -> provider.callResetPasswordFunctionAsync(email, "123456", arrayOf(), callback) + Method.RESET_PASSWORD -> provider.resetPasswordAsync("token", "token-id", "password", callback) + } + fail("$method should have thrown an exception") + } catch (ignore: IllegalStateException) { + } + } + } +} + diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt index 04fcbb1941..80660f4580 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt @@ -1,7 +1,5 @@ package io.realm -import androidx.test.platform.app.InstrumentationRegistry - /** * Resets the Realm Application and delete all local state. * @@ -13,3 +11,12 @@ fun RealmApp.close() { SyncManager.reset() BaseRealm.applicationContext = null // Required for Realm.init() to work } + +/** + * Helper function for quickly logging in test users. + * This only works if users in the Realm Application are configured to be automatically confirmed. + */ +fun RealmApp.registerUserAndLogin(email: String, password: String): RealmUser { + emailPasswordAuthProvider.registerUser(email, password) + return login(RealmCredentials.emailPassword(email, password)) +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt index ddbdad1f10..f096c324ba 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -71,33 +71,21 @@ class RealmAppTests { fun logOutAsync() = looperThread.runBlocking { val user: RealmUser = app.login(RealmCredentials.anonymous()) assertEquals(user, app.currentUser()) - app.logOutAsync(object: RealmApp.Callback { - override fun onSuccess(callbackUser: RealmUser) { - assertNull(app.currentUser()) - assertEquals(user, callbackUser) - assertEquals(RealmUser.State.ERROR, user.state) // Should be LOGGED_OUT in a future update of OS - assertEquals(RealmUser.State.ERROR, callbackUser.state) // Should be LOGGED_OUT in a future update of OS - looperThread.testComplete() - } - - override fun onError(error: ObjectServerError) { - fail(error.toString()) - } - }) + app.logOutAsync() { result -> + val callbackUser: RealmUser = result.orThrow + assertNull(app.currentUser()) + assertEquals(user, callbackUser) + assertEquals(RealmUser.State.ERROR, user.state) // Should be LOGGED_OUT in a future update of OS + assertEquals(RealmUser.State.ERROR, callbackUser.state) // Should be LOGGED_OUT in a future update of OS + looperThread.testComplete() + } } @Test fun logOutAsync_throwsOnNonLooperThread() { val user: RealmUser = app.login(RealmCredentials.anonymous()) assertEquals(user, app.currentUser()) - val callback = object: RealmApp.Callback { - override fun onSuccess(t: RealmUser) { - fail("Method should throw") - } - override fun onError(error: ObjectServerError) { - fail("Method should throw") - } - } + val callback = RealmApp.Callback { fail("Method should throw") } try { app.logOutAsync(callback) fail() diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt index bf14f7bcd3..fce0997294 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt @@ -72,14 +72,11 @@ class RealmUserTests { @Test fun logOutAsync() = looperThread.runBlocking { - anonUser.logOutAsync(object: RealmApp.Callback { - override fun onSuccess(t: RealmUser) { - looperThread.testComplete() + anonUser.logOutAsync { + when(it.isSuccess) { + true -> looperThread.testComplete() + false -> fail(it.error.toString()) } - - override fun onError(error: ObjectServerError) { - fail(error.toString()) - } - }) + } } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt new file mode 100644 index 0000000000..898e767ecf --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt @@ -0,0 +1,191 @@ +package io.realm.admin + +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import okhttp3.* +import okio.Buffer +import org.json.JSONArray +import org.json.JSONObject +import java.nio.charset.Charset +import java.util.concurrent.TimeUnit + +/** + * Wrapper around MongoDB Realm Server Admin functions needed for tests. + */ +class ServerAdmin { + + private lateinit var accessToken: String + private lateinit var groupId: String + private lateinit var appId: String + + private val json = MediaType.parse("application/json; charset=utf-8") + private val baseUrl = "http://127.0.0.1:9090/api/admin/v3.0" + private val client: OkHttpClient = OkHttpClient.Builder() + .callTimeout(10, TimeUnit.SECONDS) + .followRedirects(true) + .addInterceptor { chain -> + val request: Request = chain.request() + if (RealmLog.getLevel() <= LogLevel.DEBUG) { + val sb = StringBuilder(request.method()) + sb.append(' ') + sb.append(request.url()) + sb.append('\n') + sb.append(request.headers()) + if (request.body() != null) { + // Stripped down version of https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java + // We only expect request context to be JSON. + val buffer = Buffer() + request.body()?.writeTo(buffer) + sb.append(buffer.readString(Charset.forName("UTF-8"))) + } + RealmLog.debug("Admin HTTP Request = \n%s", sb) + } + chain.proceed(request) + } + .connectionPool(ConnectionPool(5, 5, TimeUnit.SECONDS)) + .build() + + init { + logIn() + } + + private fun executeRequest(builder: Request.Builder, authenticate: Boolean = true): String { + if (authenticate) { + builder.header("Authorization", "Bearer $accessToken") + } + val call = client.newCall(builder.build()) + val response = call.execute() + val body: String = response.body()?.string() ?: "" + val code = response.code() + if (code < 200 || code > 299) { + throw IllegalArgumentException("HTTP error $code : $body") + } + + return body + } + + // Logs the admin user so we can call other endpoints + private fun logIn() { + // Login + val body = mapOf(Pair("username", "unique_user@domain.com"), Pair("password", "password")) + var builder: Request.Builder = Request.Builder() + .url("$baseUrl/auth/providers/local-userpass/login") + .post(RequestBody.create(json, JSONObject(body).toString())) + var result = JSONObject(executeRequest(builder, authenticate = false)) + accessToken = result.getString("access_token") + + // Get GroupId + builder = Request.Builder().url("$baseUrl/auth/profile").get() + result = JSONObject(executeRequest(builder)) + groupId = (result.getJSONArray("roles")[0] as JSONObject).getString("group_id") + + // Get Internal App Id + builder = Request.Builder().url("$baseUrl/groups/$groupId/apps").get() + result = JSONArray(executeRequest(builder))[0] as JSONObject + appId = result.getString("_id") + } + + /** + * Toggle whether or not automatic confirmation of new users are enabled. + */ + fun setAutomaticConfirmation(enabled: Boolean) { + val providerId: String = getLocalUserPassProviderId() + var request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId") + .get() + val authProviderConfig = JSONObject(executeRequest(request, true)) + authProviderConfig.getJSONObject("config").apply { + put("autoConfirm", enabled) + put("emailConfirmationUrl", "http://realm.io/confirm-user") + } + // Change autoConfirm and update the provider + request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId") + .patch(RequestBody.create(json, authProviderConfig.toString())) + executeRequest(request) + } + + /** + * Deletes all currently registered and pending users on MongoDB Realm. + */ + fun deleteAllUsers() { + deleteAllRegisteredUsers() + deleteAllPendingUsers() + } + + private fun deleteAllPendingUsers() { + var request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/user_registrations/pending_users") + .get() + val pendingUsers = JSONArray(executeRequest(request)) + for (i in 0 until pendingUsers.length()) { + val user = pendingUsers[i] as JSONObject + val loginTypes = user.getJSONArray("login_ids") + for (j in 0 until loginTypes.length()) { + val login = loginTypes[j] as JSONObject + if (login.getString("id_type") == "email") { + deletePendingUser(login.getString("id")) + } + } + } + } + + private fun deleteAllRegisteredUsers() { + var request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/users") + .get() + val list = JSONArray(executeRequest(request)) + for (i in 0 until list.length()) { + val o = list[i] as JSONObject + request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/users/${o.getString("_id")}") + .delete() + executeRequest(request) + } + } + + private fun deletePendingUser(email: String) { + val request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/user_registrations/by_email/$email") + .delete() + executeRequest(request) + } + + /** + * Determines whether or not the preconfigured reset password function is used instead + * of sending an email. + */ + fun setResetFunction(enabled: Boolean) { + val providerId: String = getLocalUserPassProviderId() + + // Read current config + var request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId") + .get() + val authProviderConfig = JSONObject(executeRequest(request, true)) + authProviderConfig.getJSONObject("config").apply { + put("runResetFunction", enabled) + } + // Change autoConfirm and update the provider + request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId") + .patch(RequestBody.create(json, authProviderConfig.toString())) + executeRequest(request) + } + + private fun getLocalUserPassProviderId(): String { + val request: Request.Builder = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/auth_providers") + .get() + val authProvidersListResult = JSONArray(executeRequest(request, true)) + var providerId: String? = null + for (i in 0 until authProvidersListResult.length()) { + val o = authProvidersListResult[i] as JSONObject + if (o.getString("name") == "local-userpass") { + providerId = o.getString("_id") + break + } + } + return providerId!! + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt index 175643e41a..b3e67be3c3 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -151,7 +151,7 @@ class OsJavaNetworkTransportTests { app.login(creds) fail() } catch (ex: ObjectServerError) { - assertEquals(ErrorCode.JAVA_IO_EXCEPTION, ex.errorCode) + assertEquals(ErrorCode.NETWORK_IO_EXCEPTION, ex.errorCode) assertEquals(ErrorCode.Type.JAVA, ex.errorType) } } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 9c0325a98d..40a78e1455 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -88,11 +88,15 @@ set(classes_LIST set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST - io.realm.RealmApp io.realm.ClientResetRequiredError - io.realm.SyncManager io.realm.SyncSession io.realm.SyncUser + io.realm.ClientResetRequiredError + io.realm.EmailPasswordAuthProvider + io.realm.RealmApp + io.realm.SyncManager + io.realm.SyncSession + io.realm.SyncUser + io.realm.internal.objectstore.OsAppCredentials io.realm.internal.objectstore.OsAsyncOpenTask io.realm.internal.objectstore.OsJavaNetworkTransport - io.realm.internal.objectstore.OsAppCredentials io.realm.internal.objectstore.OsSyncUser ) endif() @@ -174,6 +178,7 @@ file(GLOB jni_SRC if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmApp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_EmailPasswordAuthProvider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetRequiredError.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuthProvider.cpp b/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuthProvider.cpp new file mode 100644 index 0000000000..8c3bb697c5 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuthProvider.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_EmailPasswordAuthProvider.h" + +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" + +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::jni_util; +using namespace realm::_impl; + +JNIEXPORT void JNICALL Java_io_realm_EmailPasswordAuthProvider_nativeCallFunction(JNIEnv* env, + jclass, + jint j_function_type, + jlong j_app_ptr, + jobject j_callback, + jobjectArray j_args) +{ + try { + App* app = reinterpret_cast(j_app_ptr); + JObjectArrayAccessor args(env, j_args); + auto client = app->provider_client(); + switch(j_function_type) { + case io_realm_EmailPasswordAuthProvider_TYPE_REGISTER_USER: + client.register_email(args[0], args[1], JavaNetworkTransport::create_void_callback(env, j_callback)); + break; + case io_realm_EmailPasswordAuthProvider_TYPE_CONFIRM_USER: + client.confirm_user(args[0], args[1], JavaNetworkTransport::create_void_callback(env, j_callback)); + break; + case io_realm_EmailPasswordAuthProvider_TYPE_RESEND_CONFIRMATION_EMAIL: + client.resend_confirmation_email(args[0], JavaNetworkTransport::create_void_callback(env, j_callback)); + break; + case io_realm_EmailPasswordAuthProvider_TYPE_SEND_RESET_PASSWORD_EMAIL: + client.send_reset_password_email(args[0], JavaNetworkTransport::create_void_callback(env, j_callback)); + break; + case io_realm_EmailPasswordAuthProvider_TYPE_CALL_RESET_PASSWORD_FUNCTION: + client.call_reset_password_function(args[0], args[1], args[2], JavaNetworkTransport::create_void_callback(env, j_callback)); + break; + case io_realm_EmailPasswordAuthProvider_TYPE_RESET_PASSWORD: + client.reset_password(args[0], args[1], args[2], JavaNetworkTransport::create_void_callback(env, j_callback)); + break; + default: + throw std::logic_error(util::format("Unknown function: %1", j_function_type)); + } + } + CATCH_STD() +} + diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp index 3bc7014076..1368b17aae 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp @@ -28,58 +28,6 @@ using namespace realm::app; using namespace realm::jni_util; using namespace realm::_impl; -// Helper method for constructing callbacks for REST calls that must return an actual result to Java -template -std::function)> create_result_callback(JNIEnv* env, jobject j_callback, const std::function& success_mapper) { - jobject callback = env->NewGlobalRef(j_callback); - return [callback, success_mapper](T result, Optional error) { - JNIEnv* env = JniUtils::get_env(true); - - static JavaClass java_callback_class(env, "io/realm/RealmApp$OsJNIResultCallback"); - static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); - static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); - - if (error) { - auto err = error.value(); - std::string error_category = err.error_code.category().name(); - env->CallVoidMethod(callback, - java_notify_onerror, - to_jstring(env, error_category), - err.error_code.value(), - to_jstring(env, err.message)); - } else { - jobject success_obj = success_mapper(env, result); - env->CallVoidMethod(callback, java_notify_onsuccess, success_obj); - } - env->DeleteGlobalRef(callback); - }; -} - -// Helper method for constructing callbacks for REST calls that doesn't return any results to Java. -std::function)> create_void_callback(JNIEnv* env, jobject j_callback) { - jobject callback = env->NewGlobalRef(j_callback); - return [&](Optional error) { - JNIEnv* env = JniUtils::get_env(true); - - static JavaClass java_callback_class(env, "io/realm/RealmApp$OsJNIVoidResultCallback"); - static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); - static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); - - if (error) { - auto err = error.value(); - std::string error_category = err.error_code.category().name(); - env->CallVoidMethod(callback, - java_notify_onerror, - to_jstring(env, error_category), - err.error_code.value(), - to_jstring(env, err.message)); - } else { - env->CallVoidMethod(callback, java_notify_onsuccess, NULL); - } - env->DeleteGlobalRef(callback); - }; -} - JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject obj, jstring j_app_id, jstring j_base_url, @@ -123,7 +71,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogin(JNIEnv* env, jclass, j auto* java_user = new std::shared_ptr(std::move(user)); return JavaClassGlobalDef::new_long(env, reinterpret_cast(java_user)); }; - auto callback = create_result_callback(env, j_callback, mapper); + auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, mapper); app->log_in_with_credentials(*credentials, callback); } CATCH_STD() @@ -134,7 +82,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogOut(JNIEnv* env, jclass, try { App* app = reinterpret_cast(j_app_ptr); auto user = *reinterpret_cast*>(j_user_ptr); - app->log_out(user, create_void_callback(env, j_callback)); + app->log_out(user, JavaNetworkTransport::create_void_callback(env, j_callback)); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp index 6e9a056a37..15278b1dc5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp @@ -219,3 +219,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeSetSt CATCH_STD(); } +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetProviderType(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->provider_type()); + } + CATCH_STD(); + return nullptr; +} diff --git a/realm/realm-library/src/main/cpp/java_network_transport.hpp b/realm/realm-library/src/main/cpp/java_network_transport.hpp index 3c1971b005..a60845cbd8 100644 --- a/realm/realm-library/src/main/cpp/java_network_transport.hpp +++ b/realm/realm-library/src/main/cpp/java_network_transport.hpp @@ -105,6 +105,58 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { } } + // Helper method for constructing callbacks for REST calls that must return an actual result to Java + template + static std::function)> create_result_callback(JNIEnv* env, jobject j_callback, const std::function& success_mapper) { + jobject callback = env->NewGlobalRef(j_callback); + return [callback, success_mapper](T result, Optional error) { + JNIEnv* env = JniUtils::get_env(true); + + static JavaClass java_callback_class(env, "io/realm/RealmApp$OsJNIResultCallback"); + static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); + static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); + + if (error) { + auto err = error.value(); + std::string error_category = err.error_code.category().name(); + env->CallVoidMethod(callback, + java_notify_onerror, + to_jstring(env, error_category), + err.error_code.value(), + to_jstring(env, err.message)); + } else { + jobject success_obj = success_mapper(env, result); + env->CallVoidMethod(callback, java_notify_onsuccess, success_obj); + } + env->DeleteGlobalRef(callback); + }; + } + + // Helper method for constructing callbacks for REST calls that doesn't return any results to Java. + static std::function)> create_void_callback(JNIEnv* env, jobject j_callback) { + jobject callback = env->NewGlobalRef(j_callback); + return [callback](Optional error) { + JNIEnv* env = JniUtils::get_env(true); + + static JavaClass java_callback_class(env, "io/realm/RealmApp$OsJNIVoidResultCallback"); + static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); + static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); + + if (error) { + auto err = error.value(); + std::string error_category = err.error_code.category().name(); + env->CallVoidMethod(callback, + java_notify_onerror, + to_jstring(env, error_category), + err.error_code.value(), + to_jstring(env, err.message)); + } else { + env->CallVoidMethod(callback, java_notify_onsuccess, NULL); + } + env->DeleteGlobalRef(callback); + }; + } + ~JavaNetworkTransport() { JniUtils::get_env(true)->DeleteGlobalRef(m_java_network_transport_impl); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 6850af8c8e..31dfb24a43 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -36,6 +36,7 @@ import io.realm.RealmConfiguration; import io.realm.RealmModel; import io.realm.RealmObject; +import io.realm.internal.android.AndroidCapabilities; import io.realm.log.RealmLog; @@ -180,4 +181,21 @@ public static Set toSet(T... items) { return set; } } + + public static void checkEmpty(String argValue, String argName) { + if (isEmptyString(argValue)) { + throw new IllegalArgumentException("Non-empty '" + argName + "' required."); + } + } + + public static void checkNull(@Nullable Object argValue, String argName) { + if (argValue == null) { + throw new IllegalArgumentException("Nonnull '" + argName + "' required."); + } + } + + public static void checkLooperThread(String errorMessage) { + AndroidCapabilities capabilities = new AndroidCapabilities(); + capabilities.checkCanDeliverNotification(errorMessage); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuthProvider.java b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuthProvider.java new file mode 100644 index 0000000000..6bfa8d7360 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuthProvider.java @@ -0,0 +1,294 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +import org.json.JSONArray; + +import java.util.concurrent.atomic.AtomicReference; + +import io.realm.internal.Util; +import io.realm.internal.objectstore.OsJavaNetworkTransport; + +/** + * Class encapsulating functionality provided when {@link RealmUser}'s are logged in through the + * {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} provider. + */ +public class EmailPasswordAuthProvider { + + private static final int TYPE_REGISTER_USER = 1; + private static final int TYPE_CONFIRM_USER = 2; + private static final int TYPE_RESEND_CONFIRMATION_EMAIL = 3; + private static final int TYPE_SEND_RESET_PASSWORD_EMAIL = 4; + private static final int TYPE_CALL_RESET_PASSWORD_FUNCTION = 5; + private static final int TYPE_RESET_PASSWORD = 6; + + private final RealmApp app; + + /** + * Creates an authentication provider exposing functionality to using an email and password + * for login into a Realm Application. + */ + public EmailPasswordAuthProvider(RealmApp app) { + this.app = app; + } + + /** + * Registers a new user with the given email and password. + * + * @param email the email to register with. This will be the username used during log in. + * @param password the password to associate with the email. The password must be between + * 6 and 128 characters long. + * + * @throws ObjectServerError if the server failed to register the user. + */ + public void registerUser(String email, String password) throws ObjectServerError { + Util.checkEmpty(email, "email"); + Util.checkEmpty(password, "password"); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_REGISTER_USER, + app.nativePtr, + new RealmApp.OsJNIVoidResultCallback(error), + email, password); + RealmApp.handleResult(null, error); + } + + /** + * Registers a new user with the given email and password. + * + * @param email the email to register with. This will be the username used during log in. + * @param password the password to associated with the email. The password must be between + * 6 and 128 characters long. + * @param callback callback when registration has completed or failed. The callback will always + * happen on the same thread as this method is called on. + * + * @throws IllegalStateException if called from a non-looper thread. + * @throws ObjectServerError if the server failed to register the user. + */ + public RealmAsyncTask registerUserAsync(String email, String password, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous registration of a user is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + registerUser(email, password); + return null; + } + }.start(); + } + + /** + * Confirms a user with the given token and token id. + * + * @param token the confirmation token. + * @param tokenId the id of the confirmation token. + * @throws ObjectServerError if the server failed to confirm the user. + */ + public void confirmUser(String token, String tokenId) throws ObjectServerError { + Util.checkEmpty(token, "token"); + Util.checkEmpty(tokenId, "tokenId"); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_CONFIRM_USER, + app.nativePtr, + new RealmApp.OsJNIVoidResultCallback(error), + token, tokenId); + RealmApp.handleResult(null, error); + } + + /** + * Confirms a user with the given token and token id. + * + * @param token the confirmation token. + * @param tokenId the id of the confirmation token. + * @param callback callback when confirmation has completed or failed. The callback will always + * happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask confirmUserAsync(String token, String tokenId, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous confirmation of a user is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + confirmUser(token, tokenId); + return null; + } + }.start(); + } + + /** + * Resend the confirmation for a user to the given email. + * + * @param email the email of the user. + * @throws ObjectServerError if the server failed to confirm the user. + */ + public void resendConfirmationEmail(String email) throws ObjectServerError { + Util.checkEmpty(email, "email"); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_RESEND_CONFIRMATION_EMAIL, + app.nativePtr, + new RealmApp.OsJNIVoidResultCallback(error), + email); + RealmApp.handleResult(null, error); + } + + /** + * Resend the confirmation for a user to the given email. + * + * @param email the email of the user. + * @param callback callback when resending the email has completed or failed. The callback will + * always happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask resendConfirmationEmailAsync(String email, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous resending the confirmation email is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + resendConfirmationEmail(email); + return null; + } + }.start(); + } + + /** + * Sends a user a password reset email for the given email. + * + * @param email the email of the user. + * @throws ObjectServerError if the server failed to confirm the user. + */ + public void sendResetPasswordEmail(String email) throws ObjectServerError { + Util.checkEmpty(email, "email"); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_SEND_RESET_PASSWORD_EMAIL, + app.nativePtr, + new RealmApp.OsJNIVoidResultCallback(error), + email); + RealmApp.handleResult(null, error); + } + + /** + * Sends a user a password reset email for the given email. + * + * @param email the email of the user. + * @param callback callback when sending the email has completed or failed. The callback will + * always happen on the same thread as this method is called on. + * @throws ObjectServerError if the server failed to confirm the user. + */ + public RealmAsyncTask sendResetPasswordEmailAsync(String email, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous sending the reset password email is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + sendResetPasswordEmail(email); + return null; + } + }.start(); + } + + /** + * Call the reset password function configured to the + * {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} provider. + * + * @param email the email of the user. + * @param newPassword the new password of the user. + * @param args any additional arguments provided to the reset function. All arguments must + * be able to be converted to JSON compatible values using {@code toString()}. + * @throws ObjectServerError if the server failed to confirm the user. + */ + public void callResetPasswordFunction(String email, String newPassword, Object... args) throws ObjectServerError { + Util.checkEmpty(email, "email"); + Util.checkEmpty(newPassword, "newPassword"); + JSONArray array = new JSONArray(); + for (Object arg : args) { + array.put((arg != null) ? arg.toString() : null); + } + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_CALL_RESET_PASSWORD_FUNCTION, + app.nativePtr, + new RealmApp.OsJNIVoidResultCallback(error), + email, newPassword, array.toString()); + RealmApp.handleResult(null, error); + } + + /** + * Call the reset password function configured to the + * {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} provider. + * + * @param email the email of the user. + * @param newPassword the new password of the user. + * @param args any additional arguments provided to the reset function. All arguments must + * be able to be converted to JSON compatible values using {@code toString()}. + * @param callback callback when the reset has completed or failed. The callback will always + * happen on the same thread as this this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask callResetPasswordFunctionAsync(String email, String newPassword, Object[] args, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous calling the password reset function is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + callResetPasswordFunction(email, newPassword, args); + return null; + } + }.start(); + } + + /** + * Resets the password of a user with the given token, token id, and new password. + * + * @param token the reset password token. + * @param tokenId the id of the reset password token. + * @param newPassword the new password for the user identified by the {@code token}. The password + * must be between 6 and 128 characters long. + * @throws ObjectServerError if the server failed to confirm the user. + */ + public void resetPassword(String token, String tokenId, String newPassword) throws ObjectServerError { + Util.checkEmpty(token, "token"); + Util.checkEmpty(tokenId, "tokenId"); + Util.checkEmpty(newPassword, "newPassword"); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_RESET_PASSWORD, + app.nativePtr, + new RealmApp.OsJNIVoidResultCallback(error), + token, tokenId, newPassword); + RealmApp.handleResult(null, error); + } + + /** + * Resets the newPassword of a user with the given token, token id, and new password. + * + * @param token the reset password token. + * @param tokenId the id of the reset password token. + * @param newPassword the new password for the user identified by the {@code token}. The password + * must be between 6 and 128 characters long. + * @param callback callback when the reset has completed or failed. The callback will always + * happen on the same thread as this this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask resetPasswordAsync(String token, String tokenId, String newPassword, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous reset of a password is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + resetPassword(token, tokenId, newPassword); + return null; + } + }.start(); + } + + private static native void nativeCallFunction(int functionType, + long appNativePtr, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback, + String... args); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index a8329ba7b4..d3df2b51ea 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -35,10 +35,10 @@ public enum ErrorCode { // The underlying type and error code should be part of the error message UNKNOWN(Type.UNKNOWN, -1), - // Realm Java errors - JAVA_IO_EXCEPTION(Type.JAVA, OsJavaNetworkTransport.ERROR_IO), - JAVA_INTERRUPTED(Type.JAVA, OsJavaNetworkTransport.ERROR_INTERRUPTED), - JAVA_UNKNOWN(Type.JAVA, OsJavaNetworkTransport.ERROR_UNKNOWN), + // Network Transport related errors originating from Java + NETWORK_IO_EXCEPTION(Type.JAVA, OsJavaNetworkTransport.ERROR_IO), + NETWORK_INTERRUPTED(Type.JAVA, OsJavaNetworkTransport.ERROR_INTERRUPTED), + NETWORK_UNKNOWN(Type.JAVA, OsJavaNetworkTransport.ERROR_UNKNOWN), // Custom Object Store errors CLIENT_RESET(Type.PROTOCOL, 7), // Client Reset required. Don't change this value without modifying io_realm_internal_OsSharedRealm.cpp diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index 91524f5497..2621220b6c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -15,6 +15,7 @@ */ package io.realm; +import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -28,6 +29,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.Keep; import io.realm.internal.RealmNotifier; +import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmAsyncTaskImpl; @@ -83,7 +85,8 @@ public void onError(SyncSession session, ObjectServerError error) { private final RealmAppConfiguration config; private OsJavaNetworkTransport networkTransport; - private final long nativePtr; + final long nativePtr; + private final EmailPasswordAuthProvider emailAuthProvider = new EmailPasswordAuthProvider(this); private CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList<>(); public RealmApp(String appId) { @@ -152,7 +155,7 @@ public static void setCurrentUser(SyncUser user) { * @throws ObjectServerError */ public RealmUser login(RealmCredentials credentials) throws ObjectServerError { - checkNull(credentials, "credentials"); + Util.checkNull(credentials, "credentials"); AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); nativeLogin(nativePtr, credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { @@ -172,7 +175,7 @@ protected void mapSuccess(Object result, AtomicReference success) { * @return */ public RealmAsyncTask loginAsync(RealmCredentials credentials, Callback callback) { - checkLooperThread("Asynchronous login is only possible from looper threads."); + Util.checkLooperThread("Asynchronous login is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public RealmUser run() throws ObjectServerError { @@ -225,15 +228,25 @@ public RealmAsyncTask logOutAsync(Callback callback) { return logOutAsync(user, callback); } + /** + * Returns a wrapper for interacting with functionality related to users either being created or + * login using the {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} identity provider. + * + * @return wrapper for interacting with the {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} identity provider. + */ + public EmailPasswordAuthProvider getEmailPasswordAuthProvider() { + return emailAuthProvider; + } + void logOut(RealmUser user) { - checkNull(user, "user"); + Util.checkNull(user, "user"); AtomicReference error = new AtomicReference<>(null); nativeLogOut(nativePtr, user.osUser.getNativePtr(), new OsJNIVoidResultCallback(error)); handleResult(null, error); } RealmAsyncTask logOutAsync(RealmUser user, Callback callback) { - checkLooperThread("Asynchronous log out is only possible from looper threads."); + Util.checkLooperThread("Asynchronous log out is only possible from looper threads."); return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { @Override public RealmUser run() throws ObjectServerError { @@ -243,36 +256,6 @@ public RealmUser run() throws ObjectServerError { }.start(); } - public RealmUser registerWithEmail(String email, String password) { - return null; - } - public RealmAsyncTask registerWithEmailAsync(String email, String password, Callback callback) { - return null; - } - public RealmUser confirmUser(String token, String tokenId) { - return null; - } - public RealmAsyncTask confirmUserAsync(String token, String tokenId, Callback callback) { - return null; - } - public void resendConfirmationEmail(String email) { - } - public RealmAsyncTask resendConfirmationEmailAsync(String email, Callback callback) { - return null; - } - public RealmUser resetPassword(String token, String tokenId, String password) { - return null; - } - public RealmAsyncTask resetPasswordAsync(String token, String tokenId, String password, Callback callback) { - return null; - } - public RealmUser sendResetPasswordEmail(String email) { - return null; - } - public RealmAsyncTask sendResetPasswordEmailAsync(String email, Callback callback) { - return null; - } - public SyncSession getSyncSession(SyncConfiguration config) { return null; } @@ -343,21 +326,27 @@ OsJavaNetworkTransport getNetworkTransport() { return networkTransport; } - private static void checkLooperThread(String errorMessage) { - AndroidCapabilities capabilities = new AndroidCapabilities(); - capabilities.checkCanDeliverNotification(errorMessage); - } - - private void checkNull(@Nullable Object argValue, String argName) { - if (argValue == null) { - throw new IllegalArgumentException("Nonnull '" + argName + "' required."); + // Handle returning the correct result or throw an exception. Must be separated from + // OsJNIResultCallback due to how the Object Store callbacks work. + static T handleResult(@Nullable AtomicReference success, AtomicReference error) { + if (success != null && success.get() == null && error.get() == null) { + throw new IllegalStateException("Network result callback did not trigger correctly"); + } + if (error.get() != null) { + throw error.get(); + } else { + if (success != null) { + return success.get(); + } else { + return null; + } } } // Common callback for handling callbacks from the ObjectStore layer. // NOTE: This class is called from JNI. If renamed, adjust callbacks in RealmApp.cpp @Keep - private static class OsJNIVoidResultCallback extends OsJNIResultCallback { + static class OsJNIVoidResultCallback extends OsJNIResultCallback { public OsJNIVoidResultCallback(AtomicReference error) { super(null, error); @@ -372,7 +361,7 @@ protected void mapSuccess(Object result, AtomicReference success) { // Common callback for handling results from the ObjectStore layer. // NOTE: This class is called from JNI. If renamed, adjust callbacks in RealmApp.cpp @Keep - private static abstract class OsJNIResultCallback extends OsJavaNetworkTransport.NetworkTransportJNIResultCallback { + static abstract class OsJNIResultCallback extends OsJavaNetworkTransport.NetworkTransportJNIResultCallback { private final AtomicReference success; private final AtomicReference error; @@ -403,26 +392,9 @@ public void onError(String nativeErrorCategory, int nativeErrorCode, String erro } } - // Handle returning the correct result or throw an exception. Must be separated from - // OsJNIResultCallback due to how - private T handleResult(@Nullable AtomicReference success, AtomicReference error) { - if (success != null && success.get() == null && error.get() == null) { - throw new IllegalStateException("Network result callback did not trigger correctly"); - } - if (error.get() != null) { - throw error.get(); - } else { - if (success != null) { - return success.get(); - } else { - return null; - } - } - } - // Class wrapping requests made against MongoDB Realm. Is also responsible for calling with success/error on the // correct thread. - private static abstract class Request { + static abstract class Request { @Nullable private final RealmApp.Callback callback; private final RealmNotifier handler; @@ -460,7 +432,7 @@ private void postError(final ObjectServerError error) { Runnable action = new Runnable() { @Override public void run() { - callback.onError(error); + callback.onResult(Result.withError(error)); } }; errorHandled = handler.post(action); @@ -476,7 +448,7 @@ private void postSuccess(final T result) { handler.post(new Runnable() { @Override public void run() { - callback.onSuccess(result); + callback.onResult((result == null) ? Result.success() : Result.withResult(result)); } }); } @@ -484,24 +456,112 @@ public void run() { } /** - * Callback for async methods available to the {@link RealmApp}. + * Result class representing the result of an async request from this app towards MongoDB Realm. * * @param Type returned if the request was a success. + * @see Callback */ - public interface Callback { + public static class Result { + private T result; + private ObjectServerError error; + + private Result(@Nullable T result, @Nullable ObjectServerError exception) { + this.result = result; + this.error = exception; + } + + /** + * Creates a successful request result with no return value. + */ + public static Result success() { + return new Result(null, null); + } + + /** + * Creates a successful request result with a return value. + * + * @param result the result value. + */ + public static Result withResult(T result) { + return new Result<>(result, null); + } + + /** + * Creates a failed request result. The request failed for some reason, either because there + * was a network error or the Realm Object Server returned an error. + * + * @param exception error that occurred. + */ + public static Result withError(ObjectServerError exception) { + return new Result<>(null, exception); + } + + /** + * Returns whether or not request was successful + * + * @return {@code true} if the request was a success, {@code false} if not. + */ + public boolean isSuccess() { + return error == null; + } + + /** + * Returns the response in case the request was a success. + * + * @return the response value in case of a successful request. + */ + public T get() { + return result; + } + + /** + * Returns the response if the request was a success. If it failed, the default value is + * returned instead. + * + * @return the response value in case of a successful request. If the request failed, the + * default value is returned instead. + */ + public T getOrDefault(T defaultValue) { + return isSuccess() ? result : defaultValue; + } + + /** + * If the request was successful the response is returned, otherwise the provided error + * is thrown. + * + * @return the response object in case the request was a success. + * @throws ObjectServer provided error in case the request failed. + */ + public T getOrThrow() { + if (isSuccess()) { + return result; + } else { + throw error; + } + } + /** - * The request was a success. - * @param t The object representing the successful request. See each method for details. + * Returns the error in case of a failed request. + * + * @return the {@link ObjectServerError} in case of a failed request. */ - void onSuccess(T t); + public ObjectServerError getError() { + return error; + } + } + /** + * Callback for async methods available to the {@link RealmApp}. + * + * @param Type returned if the request was a success. + */ + public interface Callback { /** - * The request failed for some reason, either because there was a network error or the Realm - * Object Server returned an error. + * Returns the result of the request when available. * - * @param error the error that was detected. + * @param result the request response. */ - void onError(ObjectServerError error); + void onResult(Result result); } private native long nativeCreate(String appId, String baseUrl, String appName, String appVersion, long requestTimeoutMs); @@ -511,4 +571,3 @@ public interface Callback { private static native long[] nativeAllUsers(long nativePtr); private static native void nativeLogOut(long appNativePtr, long userNativePtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } - diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java index 69fb05eccb..4f1a3aa9cb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java @@ -110,6 +110,10 @@ public void invalidate() { nativeSetState(nativePtr, STATE_ERROR); } + public String getProviderType() { + return nativeGetProviderType(nativePtr); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -140,4 +144,5 @@ public int hashCode() { private static native String[] nativeGetIdentities(long nativePtr); // Returns pairs of {id, provider} private static native byte nativeGetState(long nativePtr); private static native void nativeSetState(long nativePtr, byte state); + private static native String nativeGetProviderType(long nativePtr); } diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 98940c9662..4fdb0222d7 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -92,6 +92,12 @@ public void onResult(int count) { } } + + // Used by Kotlin tests to cheat the type system + public static String getNullString() { + return null; + } + public static RealmFieldType getColumnType(Object o) { if (o instanceof Boolean) { return RealmFieldType.BOOLEAN; diff --git a/tools/sync_test_server/app_config/auth_providers/local-userpass.json b/tools/sync_test_server/app_config/auth_providers/local-userpass.json index 4927e58d16..412cbee3c6 100755 --- a/tools/sync_test_server/app_config/auth_providers/local-userpass.json +++ b/tools/sync_test_server/app_config/auth_providers/local-userpass.json @@ -6,7 +6,10 @@ "autoConfirm": true, "resetFunctionName": "resetFunc", "runConfirmationFunction": false, - "runResetFunction": true + "emailConfirmationUrl": "http://realm.io/confirm-user", + "runResetFunction": false, + "resetPasswordSubject": "Reset Password", + "resetPasswordUrl": "http://realm.io/reset-password" }, "disabled": false } diff --git a/tools/sync_test_server/app_config/functions/resetFunc/source.js b/tools/sync_test_server/app_config/functions/resetFunc/source.js index b31a1971d9..7dac30ae01 100755 --- a/tools/sync_test_server/app_config/functions/resetFunc/source.js +++ b/tools/sync_test_server/app_config/functions/resetFunc/source.js @@ -41,7 +41,10 @@ The uncommented function below is just a placeholder and will result in failure. */ - exports = ({ token, tokenId, username, password }) => { - // will not reset the password - return { status: 'fail' }; + exports = ({ token, tokenId, username, password }, customParam1, customParam2) => { + if (customParam1 != "say-the-magic-word" || customParam2 != 42) { + return { status: 'fail' }; + } else { + return { status: 'success' }; + } }; From df9fbc96a4d84982f5a7f1598e2277c4fc5b25fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Lo=CC=81pez?= Date: Tue, 31 Mar 2020 23:14:44 +0200 Subject: [PATCH 1486/2110] Updated readme file --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index df53f892bf..781c81f1af 100644 --- a/README.md +++ b/README.md @@ -67,17 +67,17 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * The latest stable version of Android Studio. Currently [3.5.2](https://developer.android.com/studio/). - * Download & install the Android SDK **Build-Tools 27.0.2**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). + * The latest stable version of Android Studio. Currently [3.6.2](https://developer.android.com/studio/). + * Download & install the Android SDK **Build-Tools 28.0.3**, **Android Pie (API 28)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * Realm currently requires version r10e of the NDK. Download the one appropriate for your development platform, from the NDK [archive](https://developer.android.com/ndk/downloads/older_releases.html). You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library/Android`. The download will unzip as the directory `android-ndk-r10e`. - * If you will be building with Android Studio, you will need to tell it to use the correct NDK. To do this, define the variable `ndk.dir` in `realm/local.properties` and assign it the full pathname of the directory that you unzipped above. Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited. + * If you will be building with Android Studio, you will need to tell it to use the correct NDK. To do this, define the variable `ndk.dir` in `realm/realm-library/local.properties` and assign it the full pathname of the directory that you unzipped above. Note that there is a `local.properites` in the root directory that is *not* the one that needs to be edited. ``` - ndk.dir=/Users/brian/Library/Android/android-ndk-r10e + ndk.dir=/Users//Library/Android/android-ndk-r10e ``` @@ -202,7 +202,7 @@ The repository is organized into six Gradle projects: * `realm-transformer`: it contains the bytecode transformer. * `gradle-plugin`: it contains the Gradle plugin. * `examples`: it contains the example projects. This project directly depends on `gradle-plugin` which adds a dependency to the artifacts produced by `realm`. - * The root folder is another Gradle project. All it does is orchestrate the other jobs + * The root folder is another Gradle project. All it does is orchestrate the other jobs. This means that `./gradlew clean` and `./gradlew cleanExamples` will fail if `assembleExamples` has not been executed first. Note that IntelliJ [does not support multiple projects in the same window](https://youtrack.jetbrains.com/issue/IDEABKL-6118#) @@ -247,7 +247,7 @@ A docker image can be built from `tools/sync_test_server/Dockerfile` to run the To run a testing server locally: -1. Install [docker](https://www.docker.com/products/overview). +1. Install [docker](https://www.docker.com/products/overview) and run it. 2. Run `tools/sync_test_server/start_server.sh`: From e266b1e252ff570239e07f84324be3069652d2d7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 Apr 2020 09:31:11 +0200 Subject: [PATCH 1487/2110] Add support for allUsers(), switchUser(), removeUser() (#6785) --- dependencies.list | 4 +- .../java/io/realm/IOSRealmTests.java | 5 +- .../java/io/realm/RealmInMemoryTest.java | 2 + .../androidTest/java/io/realm/RealmTests.java | 2 + .../realm/EmailPasswordAuthProviderTests.kt | 4 +- .../kotlin/io/realm/RealmAppTests.kt | 185 +++++++++++++++++- .../kotlin/io/realm/RealmUserTests.kt | 6 +- .../io/realm/rule/BlockingLooperThread.kt | 1 - .../src/main/cpp/io_realm_RealmApp.cpp | 53 +++++ ..._realm_internal_objectstore_OsSyncUser.cpp | 12 +- realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/util.cpp | 13 ++ .../objectServer/java/io/realm/RealmApp.java | 81 +++++++- .../objectServer/java/io/realm/RealmUser.java | 4 +- .../internal/objectstore/OsSyncUser.java | 8 +- .../testUtils/java/io/realm/TestHelper.java | 5 + 16 files changed, 350 insertions(+), 37 deletions(-) diff --git a/dependencies.list b/dependencies.list index 3fb3f9d7bf..7a86581a12 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.4 -REALM_SYNC_SHA256=7048eff89f00554aa4014239a25d419fc98d0b22074ff2deadd3e9717a5c4dee +REALM_SYNC_VERSION=10.0.0-alpha.5 +REALM_SYNC_SHA256=aa490300e06dca385622bc83430f9d8e3141fcccfd7bf6ae2f68af8a29af5dc1 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index fe25eddc86..bbbd8c5036 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -17,11 +17,13 @@ package io.realm; import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; + import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,6 +46,7 @@ /** * This class test interoperability with Realms created on iOS. */ +@Ignore("FIXME: See https://github.com/realm/realm-java/issues/6789") @RunWith(AndroidJUnit4.class) public class IOSRealmTests { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index 20da46d837..8ca8360eae 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -22,6 +22,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,6 +40,7 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +@Ignore("FIXME: See https://github.com/realm/realm-java/issues/6790") @RunWith(AndroidJUnit4.class) public class RealmInMemoryTest { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 74023fb904..016dffcc00 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -33,6 +33,7 @@ import org.junit.After; import org.junit.Assume; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -4561,6 +4562,7 @@ public void hittingMaxNumberOfVersionsThrows() { } // Test for https://github.com/realm/realm-java/issues/6152 + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6792") @Test @RunTestInLooperThread public void encryption_stressTest() { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt index df1d9a4636..51ab1b4c7e 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt @@ -88,7 +88,7 @@ class EmailPasswordAuthProviderTests { val password = "password1234" app.emailPasswordAuthProvider.registerUser(email, password) val user = app.login(RealmCredentials.emailPassword(email, password)) - assertEquals(RealmUser.State.ACTIVE, user.state) + assertEquals(RealmUser.State.LOGGED_IN, user.state) } @Test @@ -99,7 +99,7 @@ class EmailPasswordAuthProviderTests { app.emailPasswordAuthProvider.registerUserAsync(email, password) { result -> if (result.isSuccess) { val user2 = app.login(RealmCredentials.emailPassword(email, password)) - assertEquals(RealmUser.State.ACTIVE, user2.state) + assertEquals(RealmUser.State.LOGGED_IN, user2.state) looperThread.testComplete() } else { fail(result.error.toString()) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt index f096c324ba..0b5c7114ec 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -22,8 +22,10 @@ import io.realm.rule.RunTestInLooperThread import org.junit.After import org.junit.Assert.* import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import java.lang.IllegalArgumentException @RunWith(AndroidJUnit4::class) class RealmAppTests { @@ -58,12 +60,185 @@ class RealmAppTests { assertNull(app.currentUser()) } + @Test + fun allUsers() { + assertEquals(0, app.allUsers().size) + val user1 = app.login(RealmCredentials.anonymous()) + var allUsers = app.allUsers() + assertEquals(1, allUsers.size) + assertTrue(allUsers.containsKey(user1.id)) + assertEquals(user1, allUsers[user1.id]) + + val user2 = app.login(RealmCredentials.anonymous()) + allUsers = app.allUsers() + assertEquals(2, allUsers.size) + assertTrue(allUsers.containsKey(user2.id)) + + val user3: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + allUsers = app.allUsers() + assertEquals(3, allUsers.size) + assertTrue(allUsers.containsKey(user3.id)) + + // Logging out users that registered with email/password will just put them in LOGGED_OUT state + user3.logOut(); + allUsers = app.allUsers() + assertEquals(3, allUsers.size) + assertTrue(allUsers.containsKey(user3.id)) + assertEquals(RealmUser.State.LOGGED_OUT, allUsers[user3.id]!!.state) + + // Logging out anonymous users will remove them completely + user1.logOut() + allUsers = app.allUsers() + assertEquals(2, allUsers.size) + assertFalse(allUsers.containsKey(user1.id)) + } + + @Test + fun allUsers_retrieveRemovedUser() { + val user1: RealmUser = app.login(RealmCredentials.anonymous()) + val allUsers: Map = app.allUsers() + assertEquals(1, allUsers.size) + user1.logOut() + assertEquals(1, allUsers.size) + val userCopy: RealmUser = allUsers[user1.id] ?: error("Could not find user") + assertEquals(user1, userCopy) + assertEquals(RealmUser.State.REMOVED, userCopy.state) + assertTrue(app.allUsers().isEmpty()) + } + + @Test + fun switchUser() { + val user1: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user1, app.currentUser()) + val user2: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user2, app.currentUser()) + + assertEquals(user1, app.switchUser(user1)) + assertEquals(user1, app.currentUser()) + } + + @Test + fun switchUser_throwIfUserNotLoggedIn() { + val user1: RealmUser = app.login(RealmCredentials.anonymous()) + val user2: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user2, app.currentUser()) + + user1.logOut() + try { + app.switchUser(user1) + fail() + } catch (ignore: IllegalArgumentException) { + } + } + + @Test + fun currentUser_FallbackToNextValidUser() { + val user1: RealmUser = app.login(RealmCredentials.anonymous()) + val user2: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user2, app.currentUser()) + user2.logOut() + assertEquals(user1, app.currentUser()) + user1.logOut() + assertNull(app.currentUser()) + } + + @Test + fun switchUser_nullThrows() { + try { + app.switchUser(TestHelper.getNull()) + fail() + } catch (ignore: IllegalArgumentException) { + } + } + + @Ignore("Add this test once we have support for both EmailPassword and ApiKey Auth Providers") + @Test + fun switchUser_authProvidersLockUsers() { + TODO() + } + + @Test + fun removeUser() { + // Removing logged in user + val user1 = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(user1, app.currentUser()) + assertEquals(1, app.allUsers().size) + app.removeUser(user1) + assertEquals(RealmUser.State.REMOVED, user1.state) + assertNull(app.currentUser()) + assertEquals(0, app.allUsers().size) + + // Remove logged out user + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + user2.logOut() + assertNull(app.currentUser()) + assertEquals(1, app.allUsers().size) + app.removeUser(user2) + assertEquals(RealmUser.State.REMOVED, user2.state) + assertEquals(0, app.allUsers().size) + } + + @Test + fun removeUser_nullThrows() { + try { + app.removeUser(TestHelper.getNull()) + fail() + } catch (ignore: IllegalArgumentException) { + } + } + + @Test + fun removeUserAsync() { + // Removing logged in user + looperThread.runBlocking { + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(user, app.currentUser()) + assertEquals(1, app.allUsers().size) + app.removeUserAsync(user) { result -> + assertEquals(RealmUser.State.REMOVED, result.orThrow.state) + assertNull(app.currentUser()) + assertEquals(0, app.allUsers().size) + looperThread.testComplete() + } + } + + // Removing logged out user + looperThread.runBlocking { + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + user.logOut() + assertNull(app.currentUser()) + assertEquals(1, app.allUsers().size) + app.removeUserAsync(user) { result -> + assertEquals(RealmUser.State.REMOVED, result.orThrow.state) + assertEquals(0, app.allUsers().size) + looperThread.testComplete() + } + } + } + + @Test + fun removeUserAsync_nonLooperThreadThrows() { + val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "1234567") + try { + app.removeUserAsync(user) { fail() } + } catch (ignore: IllegalStateException) { + } + } + @Test fun logOut() { - val user: RealmUser = app.login(RealmCredentials.anonymous()) - assertEquals(user, app.currentUser()) + // Anonymous users are removed upon log out + val user1: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user1, app.currentUser()) + app.logOut() + assertEquals(RealmUser.State.REMOVED, user1.state) + assertNull(app.currentUser()) + + // Users registered with Email/Password will register as Logged Out + val user2: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(user2, app.currentUser()) app.logOut() - assertEquals(RealmUser.State.ERROR, user.state) // Should be LOGGED_OUT in a future update of OS + assertEquals(RealmUser.State.LOGGED_OUT, user2.state) assertNull(app.currentUser()) } @@ -75,8 +250,8 @@ class RealmAppTests { val callbackUser: RealmUser = result.orThrow assertNull(app.currentUser()) assertEquals(user, callbackUser) - assertEquals(RealmUser.State.ERROR, user.state) // Should be LOGGED_OUT in a future update of OS - assertEquals(RealmUser.State.ERROR, callbackUser.state) // Should be LOGGED_OUT in a future update of OS + assertEquals(RealmUser.State.REMOVED, user.state) + assertEquals(RealmUser.State.REMOVED, callbackUser.state) looperThread.testComplete() } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt index fce0997294..ba42476e15 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt @@ -53,9 +53,9 @@ class RealmUserTests { @Test fun getState_anonymousUser() { - assertEquals(RealmUser.State.ACTIVE, anonUser.state) + assertEquals(RealmUser.State.LOGGED_IN, anonUser.state) anonUser.logOut() - assertEquals(RealmUser.State.ERROR, anonUser.state) + assertEquals(RealmUser.State.REMOVED, anonUser.state) } @Ignore("Add test when registerUser works") @@ -67,7 +67,7 @@ class RealmUserTests { @Test fun logOut() { anonUser.logOut() - assertEquals(RealmUser.State.ERROR, anonUser.state) + assertEquals(RealmUser.State.REMOVED, anonUser.state) } @Test diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt index c429d6643b..e1877a0d94 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt @@ -38,7 +38,6 @@ import kotlin.collections.ArrayList * * Usage: * ``` - * @get:Rule * val lopperThread = LooperThreadTest() * * @Before diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp index 1368b17aae..b53a6f67b3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp @@ -104,3 +104,56 @@ JNIEXPORT jobject JNICALL Java_io_realm_RealmApp_nativeCurrentUser(JNIEnv* env, return NULL; } +JNIEXPORT jlongArray JNICALL Java_io_realm_RealmApp_nativeGetAllUsers(JNIEnv* env, jclass, jlong j_app_ptr) +{ + try { + App *app = reinterpret_cast(j_app_ptr); + std::vector> users = app->all_users(); + auto size = users.size(); + + jlongArray java_users = env->NewLongArray(size); + if (!java_users) { + ThrowException(env, OutOfMemory, "Could not allocate memory to create array of users."); + return nullptr; + } + + jlong* user_ptrs = new jlong[size]; + for(size_t i = 0; i < size; ++i) { + auto *java_user = new std::shared_ptr(std::move(users[i])); + user_ptrs[i] = reinterpret_cast(java_user); + } + + env->SetLongArrayRegion(java_users, 0, size, user_ptrs); + delete[] user_ptrs; + return java_users; + } + CATCH_STD() + return nullptr; +} + +JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeSwitchUser(JNIEnv* env, + jclass, + jlong j_app_ptr, + jlong j_user_ptr) +{ + try { + App* app = reinterpret_cast(j_app_ptr); + auto user = *reinterpret_cast*>(j_user_ptr); + app->switch_user(user); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeRemoveUser(JNIEnv* env, + jclass, + jlong j_app_ptr, + jlong j_user_ptr, + jobject j_callback) +{ + try { + App* app = reinterpret_cast(j_app_ptr); + auto user = *reinterpret_cast*>(j_user_ptr); + app->remove_user(user, JavaNetworkTransport::create_void_callback(env, j_callback)); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp index 15278b1dc5..8afed94bea 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp @@ -188,8 +188,8 @@ JNIEXPORT jbyte JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetS auto user = *reinterpret_cast*>(j_native_ptr); switch(user->state()) { case SyncUser::State::LoggedOut: return static_cast(io_realm_internal_objectstore_OsSyncUser_STATE_LOGGED_OUT); - case SyncUser::State::Active: return static_cast(io_realm_internal_objectstore_OsSyncUser_STATE_ACTIVE); - case SyncUser::State::Error: return static_cast(io_realm_internal_objectstore_OsSyncUser_STATE_ERROR); + case SyncUser::State::LoggedIn: return static_cast(io_realm_internal_objectstore_OsSyncUser_STATE_LOGGED_IN); + case SyncUser::State::Removed: return static_cast(io_realm_internal_objectstore_OsSyncUser_STATE_REMOVED); default: throw std::logic_error(util::format("Unknown state: %1", static_cast(user->state()))); } @@ -206,11 +206,11 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeSetSt case io_realm_internal_objectstore_OsSyncUser_STATE_LOGGED_OUT: user->set_state(SyncUser::State::LoggedOut); break; - case io_realm_internal_objectstore_OsSyncUser_STATE_ACTIVE: - user->set_state(SyncUser::State::Active); + case io_realm_internal_objectstore_OsSyncUser_STATE_LOGGED_IN: + user->set_state(SyncUser::State::LoggedIn); break; - case io_realm_internal_objectstore_OsSyncUser_STATE_ERROR: - user->set_state(SyncUser::State::Error); + case io_realm_internal_objectstore_OsSyncUser_STATE_REMOVED: + user->set_state(SyncUser::State::Removed); break; default: throw std::logic_error(util::format("Unknown state: %1", j_state)); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 2b94e623c5..3f16f2287f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 2b94e623c506d0d6fc96a8fd087429fc715830d3 +Subproject commit 3f16f2287fdd20acc7c890c0aa77ba055ea54597 diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index d665845357..cf9e56f786 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -32,6 +32,9 @@ #include "java_exception_def.hpp" #include "java_object_accessor.hpp" #include "object.hpp" +#if REALM_ENABLE_SYNC +#include "sync/app.hpp" +#endif #include "jni_util/java_exception_thrower.hpp" @@ -136,6 +139,16 @@ void ConvertException(JNIEnv* env, const char* file, int line) catch(realm::RequiredFieldValueNotProvidedException e) { ThrowException(env, IllegalArgument, e.what()); } +#if REALM_ENABLE_SYNC + catch (realm::app::AppError& e) { + // TODO Figure out exactly what kind of mapping is needed here + if (e.error_code.category() == realm::app::custom_error_category()) { + ThrowException(env, IllegalArgument, e.message); + } else { + ThrowException(env, IllegalState, e.message); + } + } +#endif catch (std::logic_error e) { ThrowException(env, IllegalState, e.what()); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index 2621220b6c..df524b6ef9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -124,12 +124,15 @@ public RealmUser currentUser() { } /** - * FIXME - * Returns all currently logged in users - * @return + * Returns all known users that are either {@link RealmUser.State#LOGGED_IN} or + * {@link RealmUser.State#LOGGED_OUT}. + *

            + * Only users that at some point logged into this device will be returned. + * + * @return a map of user identifiers and users known locally. */ public Map allUsers() { - long[] nativeUsers = nativeAllUsers(nativePtr); + long[] nativeUsers = nativeGetAllUsers(nativePtr); HashMap users = new HashMap<>(nativeUsers.length); for (int i = 0; i < nativeUsers.length; i++) { RealmUser user = new RealmUser(nativeUsers[i], this); @@ -139,12 +142,58 @@ public Map allUsers() { } /** - * TODO: Manually set the user returned by {@link #currentUser()} + * Switch current user. The current user is the user returned by {@link #currentUser()}. * - * @param user + * @param user the new current user. + * @throws IllegalArgumentException if the user is is not {@link RealmUser.State#LOGGED_IN}. */ - public static void setCurrentUser(SyncUser user) { - // FIXME + public RealmUser switchUser(RealmUser user) { + Util.checkNull(user, "user"); + nativeSwitchUser(nativePtr, user.osUser.getNativePtr()); + return user; + } + + /** + * Removes a users credentials from this device. If the user was currently logged in, they + * will be logged out as part of the process. This is only a local change and does not + * affect the user state on the server. + * + * @param user user to remove. + * @return user that was removed. + * @throws ObjectServerError if called from the UI thread or if the user was logged in, but + * could not be logged out. + */ + public RealmUser removeUser(RealmUser user) throws ObjectServerError { + Util.checkNull(user, "user"); + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + nativeRemoveUser(nativePtr, user.osUser.getNativePtr(), new OsJNIResultCallback(success, error) { + @Override + protected void mapSuccess(Object result, AtomicReference success) { + success.set(user); + } + }); + return handleResult(success, error); + } + + /** + * Removes a users credentials from this device. If the user was currently logged in, they + * will be logged out as part of the process. This is only a local change and does not + * affect the user state on the server. + * + * @param user user to remove. + * @param callback callback when removing the user has completed or failed. The callback will always + * happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask removeUserAsync(RealmUser user, Callback callback) { + Util.checkLooperThread("Asynchronous removal of users is only possible from looper threads."); + return new Request(NETWORK_POOL_EXECUTOR, callback) { + @Override + public RealmUser run() throws ObjectServerError { + return removeUser(user); + } + }.start(); } /** @@ -194,12 +243,16 @@ public RealmUser run() throws ObjectServerError { *

            * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} * will be notified and user credentials will be deleted from this device. + *

            + * Logging out anonymous users will remove them immediately instead of marking them as + * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} + * and will still be returned by {@link #allUsers()}. * * @throws IllegalStateException if no current user could be found. * @throws ObjectServerError if an error occurred while trying to log the user out of the Realm * App. */ - public void logOut() { + public void logOut() throws ObjectServerError { RealmUser user = currentUser(); if (user == null) { throw new IllegalStateException("No current user was found."); @@ -217,7 +270,13 @@ public void logOut() { *

            * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} * will be notified and user credentials will be deleted from this device. + *

            + * Logging out anonymous users will remove them immediately instead of marking them as + * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} + * and will still be returned by {@link #allUsers()}. * + * @param callback callback when logging out has completed or failed. The callback will always + * happen on the same thread as this method is called on. * @throws IllegalStateException if not called on a looper thread or no current user could be found. */ public RealmAsyncTask logOutAsync(Callback callback) { @@ -568,6 +627,8 @@ public interface Callback { private static native void nativeLogin(long nativeAppPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); @Nullable private static native Long nativeCurrentUser(long nativePtr); - private static native long[] nativeAllUsers(long nativePtr); + private static native long[] nativeGetAllUsers(long nativePtr); private static native void nativeLogOut(long appNativePtr, long userNativePtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeSwitchUser(long nativeAppPtr, long nativeUserPtr); + private static native void nativeRemoveUser(long nativeAppPtr, long nativeUserPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java index 883010e23c..eb59d76ef3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -51,8 +51,8 @@ public String getKey() { } public enum State { - ACTIVE(OsSyncUser.STATE_ACTIVE), - ERROR(OsSyncUser.STATE_ERROR), + LOGGED_IN(OsSyncUser.STATE_LOGGED_IN), + REMOVED(OsSyncUser.STATE_REMOVED), LOGGED_OUT(OsSyncUser.STATE_LOGGED_OUT); private final byte nativeValue; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java index 4f1a3aa9cb..6b8e9e3412 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java @@ -23,8 +23,8 @@ public class OsSyncUser implements NativeObject { private final long nativePtr; private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); - public static final byte STATE_ACTIVE = 1; - public static final byte STATE_ERROR = 2; + public static final byte STATE_LOGGED_IN = 1; + public static final byte STATE_REMOVED = 2; public static final byte STATE_LOGGED_OUT = 3; public OsSyncUser(long nativePtr) { @@ -100,14 +100,14 @@ public Pair[] getIdentities() { } /** - * @return {@link #STATE_ACTIVE}, {@link #STATE_LOGGED_OUT} or {@link #STATE_ERROR} + * @return {@link #STATE_LOGGED_IN}, {@link #STATE_LOGGED_OUT} or {@link #STATE_REMOVED} */ public byte getState() { return nativeGetState(nativePtr); } public void invalidate() { - nativeSetState(nativePtr, STATE_ERROR); + nativeSetState(nativePtr, STATE_REMOVED); } public String getProviderType() { diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 4fdb0222d7..2468ef74e8 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -1305,4 +1305,9 @@ public static void waitForNetworkThreadExecutorToFinish() { } } + // Workaround to cheat Kotlins type system when testing interop with Java + @SuppressWarnings("TypeParameterUnusedInFormals") + public static T getNull() { + return null; + } } From e6380b7d9cbc0a594f978b569ee6c265af269ed4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 Apr 2020 21:43:30 +0200 Subject: [PATCH 1488/2110] Fix annotation processor not compiling (#6791) --- .../groovy/io/realm/gradle/PluginTest.groovy | 3 ++- .../processor/RealmProxyClassGenerator.kt | 4 ++-- .../realm/some_test_NullTypesRealmProxy.java | 4 ++-- .../java/io/realm/IOSRealmTests.java | 21 +------------------ tools/sync_test_server/ros/tsconfig.json | 1 + 5 files changed, 8 insertions(+), 25 deletions(-) diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index 3ced4b57ed..7b2daea1aa 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -148,6 +148,7 @@ class PluginTest { void pluginAddsRightRepositories_withRepositoriesSet() { project.buildscript { repositories { + mavenCentral() jcenter() maven { url 'https://maven.google.com/' @@ -180,7 +181,7 @@ class PluginTest { project.evaluate() - assertEquals(2, project.buildscript.repositories.size()) + assertEquals(3, project.buildscript.repositories.size()) assertEquals('maven.google.com', project.buildscript.repositories.last().url.host) assertEquals(4, project.repositories.size()) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index f82eeb61e9..ca9e11a5f2 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -1632,8 +1632,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi Utils.isMutableRealmInteger(field) -> { emitStatement("stringBuilder.append(%s().get())", metadata.getInternalGetter(fieldName)) } - field is ByteArray -> { - emitStatement("stringBuilder.append(\"binary(\" + field.length + \")\")") + Utils.isByteArray(field) -> { + emitStatement("stringBuilder.append(\"binary(\" + %s().length + \")\")", metadata.getInternalGetter(fieldName)) } else -> { if (metadata.isNullable(field)) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index f91a7af319..b256a85459 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -2185,7 +2185,7 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader } private static some_test_NullTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { - // Ignore default values to avoid creating uexpected objects from RealmModel/RealmList fields + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NullTypes.class), false, Collections.emptyList()); io.realm.some_test_NullTypesRealmProxy obj = new io.realm.some_test_NullTypesRealmProxy(); @@ -3963,7 +3963,7 @@ public String toString() { stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{fieldBytesNull:"); - stringBuilder.append(realmGet$fieldBytesNull() != null ? realmGet$fieldBytesNull() : "null"); + stringBuilder.append("binary(" + realmGet$fieldBytesNull().length + ")"); stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{fieldByteNotNull:"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index bf006da8fb..38954692c4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -222,24 +222,5 @@ private byte[] getIOSKey() { } return keyData; } - - @Test - public void iOSDataTypesMixValues() throws IOException { - for (String iosVersion : IOS_VERSIONS) { - configFactory.copyRealmFromAssets(context, - "ios/" + iosVersion + "-alltypes-mix.realm", REALM_NAME); - realm = Realm.getDefaultInstance(); - - IOSAllTypes obj = realm.where(IOSAllTypes.class).findFirst(); - assertEquals(null, obj.getByteCol()); - assertEquals(null, obj.getStringCol()); - assertFalse(obj.isBoolCol()); - assertEquals(11125, obj.getShortCol()); - assertEquals(15350, obj.getIntCol()); - assertEquals(773863123, obj.getLongCol()); - assertEquals((float) 0.8914557, obj.getFloatCol(), 0F); - assertEquals(0.8702290174167451, obj.getDoubleCol(), 0D); - assertEquals(Long.MIN_VALUE, obj.getDateCol().getTime()); - } - } + } diff --git a/tools/sync_test_server/ros/tsconfig.json b/tools/sync_test_server/ros/tsconfig.json index a5aca56049..87c9f42aeb 100644 --- a/tools/sync_test_server/ros/tsconfig.json +++ b/tools/sync_test_server/ros/tsconfig.json @@ -12,6 +12,7 @@ "declaration": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, + "skipLibCheck": true, "lib": [ "dom", "es6", From 0d0bc5a2e5f785495db4a692f8e929eaf4ae4ea4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 3 Apr 2020 15:28:21 +0200 Subject: [PATCH 1489/2110] Add support for ApiKeyAuthProvider (#6786) --- examples/build.gradle | 2 +- realm/build.gradle | 2 +- realm/realm-library/build.gradle | 2 +- .../io/realm/ApiKeyAuthProviderTests.kt | 478 ++++++++++++++++++ .../realm/EmailPasswordAuthProviderTests.kt | 44 +- .../kotlin/io/realm/RealmAppTests.kt | 18 + .../io/realm/rule/BlockingLooperThread.kt | 4 +- .../realm-library/src/main/cpp/CMakeLists.txt | 6 +- .../main/cpp/io_realm_ApiKeyAuthProvider.cpp | 120 +++++ .../src/main/cpp/java_class_global_def.hpp | 8 + realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/ApiKeyAuthProvider.java | 310 ++++++++++++ .../objectServer/java/io/realm/RealmApp.java | 37 +- .../java/io/realm/RealmUserApiKey.java | 114 +++++ .../testUtils/java/io/realm/TestHelper.java | 6 - 15 files changed, 1110 insertions(+), 43 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt create mode 100644 realm/realm-library/src/main/cpp/io_realm_ApiKeyAuthProvider.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuthProvider.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmUserApiKey.java diff --git a/examples/build.gradle b/examples/build.gradle index 0436277664..ae31565c21 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,7 +1,7 @@ def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) project.ext.sdkVersion = 27 -project.ext.minSdkVersion = 16 +project.ext.minSdkVersion = 21 // FIXME: Should be 16. Figure out how to enable MultiDex for ObjectServer tests project.ext.buildTools = projectDependencies.get("ANDROID_BUILD_TOOLS") // Don't cache SNAPSHOT (changing) dependencies. diff --git a/realm/build.gradle b/realm/build.gradle index 3a314774e9..920d352cb7 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -34,7 +34,7 @@ allprojects { projectDependencies.each { key, val -> project.ext.set(key, val) } - project.ext.minSdkVersion = 16 + project.ext.minSdkVersion = 21 // FIXME: Should be 16. Figure out how to enable MultiDex for ObjectServer tests project.ext.compileSdkVersion = 29 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") group = 'io.realm' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 2d363aa38b..a875afa2f4 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -212,7 +212,7 @@ dependencies { kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 objectServerImplementation 'com.squareup.okhttp3:okhttp:3.12.0' // Going above this requires minSDK 21 - + objectServerImplementation "org.mongodb:bson:3.12.0" kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' androidTestImplementation 'io.reactivex.rxjava2:rxandroid:2.1.1' diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt new file mode 100644 index 0000000000..de05c86f90 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt @@ -0,0 +1,478 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.annotation.UiThreadTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.admin.ServerAdmin +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.rule.BlockingLooperThread +import org.bson.types.ObjectId +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ApiKeyAuthProviderTests { + + private val looperThread = BlockingLooperThread() + private lateinit var app: TestRealmApp + private lateinit var admin: ServerAdmin + private lateinit var user: RealmUser + private lateinit var provider: ApiKeyAuthProvider + + // Callback use to verify that an Illegal Argument was thrown from async methods + private val checkNullInVoidCallback = RealmApp.Callback { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.UNKNOWN, result.error.errorCode) + looperThread.testComplete() + } + } + + private val checkNullInApiKeyCallback = RealmApp.Callback { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.UNKNOWN, result.error.errorCode) + looperThread.testComplete() + } + } + + // Methods exposed by the EmailPasswordAuthProvider + enum class Method { + CREATE, + FETCH_SINGLE, + FETCH_ALL, + DELETE, + ENABLE, + DISABLE + } + + @Before + fun setUp() { + app = TestRealmApp() + RealmLog.setLevel(LogLevel.DEBUG) + admin = ServerAdmin() + user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + provider = app.apiKeyAuthProvider + } + + @After + fun tearDown() { + app.close() + admin.deleteAllUsers() + RealmLog.setLevel(LogLevel.WARN) + } + + inline fun testNullArg(method: () -> Unit) { + try { + method() + fail() + } catch (ignore: IllegalArgumentException) { + } + } + + @Test + fun createApiKey() { + val key: RealmUserApiKey = provider.createApiKey("my-key") + assertEquals("my-key", key.name) + assertNotNull("my-key", key.value) + assertNotNull("my-key", key.id) + assertTrue("my-key", key.isEnabled) + } + + @Test + fun createApiKey_invalidServerArgsThrows() { + try { + provider.createApiKey("%s") + fail() + } catch (e: ObjectServerError) { + assertEquals(ErrorCode.INVALID_PARAMETER, e.errorCode) + } + } + + @Test + fun createApiKey_invalidArgumentThrows() { + testNullArg { provider.createApiKey(TestHelper.getNull()) } + testNullArg { provider.createApiKey("") } + looperThread.runBlocking { + provider.createApiKeyAsync(TestHelper.getNull(), checkNullInApiKeyCallback) + } + looperThread.runBlocking { + provider.createApiKeyAsync("", checkNullInApiKeyCallback) + } + } + + @Test + fun createApiKeyAsync() = looperThread.runBlocking { + provider.createApiKeyAsync("my-key") { result -> + val key = result.orThrow + assertEquals("my-key", key.name) + assertNotNull("my-key", key.value) + assertNotNull("my-key", key.id) + assertTrue("my-key", key.isEnabled) + looperThread.testComplete() + } + } + + @Test + fun createApiKeyAsync_invalidServerArgsThrows() = looperThread.runBlocking { + provider.createApiKeyAsync("%s") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.INVALID_PARAMETER, result.error.errorCode) + looperThread.testComplete() + } + } + } + + @Test + fun fetchApiKey() { + val key1: RealmUserApiKey = provider.createApiKey("my-key") + val key2: RealmUserApiKey = provider.fetchApiKey(key1.id) + + assertEquals(key1.id, key2.id) + assertEquals(key1.name, key2.name) + assertNull(key2.value) + assertEquals(key1.isEnabled, key2.isEnabled) + } + + @Test + fun fetchApiKey_nonExistingKey() { + try { + provider.fetchApiKey(ObjectId()) + fail() + } catch (e: ObjectServerError) { + assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) + } + } + + @Test + fun fetchApiKey_invalidArgumentThrows() { + testNullArg { provider.fetchApiKey(TestHelper.getNull()) } + looperThread.runBlocking { + provider.fetchApiKeyAsync(TestHelper.getNull(), checkNullInApiKeyCallback) + } + } + + @Test + fun fetchApiKeyAsync() { + val key1: RealmUserApiKey = provider.createApiKey("my-key") + looperThread.runBlocking { + provider.fetchApiKeyAsync(key1.id) { result -> + val key2 = result.orThrow + assertEquals(key1.id, key2.id) + assertEquals(key1.name, key2.name) + assertNull(key2.value) + assertEquals(key1.isEnabled, key2.isEnabled) + looperThread.testComplete() + } + } + } + + @Test + fun fetchAllApiKeys() { + val key1: RealmUserApiKey = provider.createApiKey("my-key") + val key2: RealmUserApiKey = provider.createApiKey("other-key") + val allKeys: List = provider.fetchAllApiKeys() + assertEquals(2, allKeys.size) + assertTrue(allKeys.any { it.id == key1.id }) + assertTrue(allKeys.any { it.id == key2.id }) + } + + @Test + fun fetchAllApiKeysAsync() { + val key1: RealmUserApiKey = provider.createApiKey("my-key") + val key2: RealmUserApiKey = provider.createApiKey("other-key") + looperThread.runBlocking { + provider.fetchAllApiKeys() { result -> + val keys: List = result.orThrow + assertEquals(2, keys.size) + assertEquals(key1.id, keys[0].id) + assertEquals(key2.id, keys[1].id) + looperThread.testComplete() + } + } + } + + @Test + fun deleteApiKey() { + val key1: RealmUserApiKey = provider.createApiKey("my-key") + assertNotNull(provider.fetchApiKey(key1.id)) + provider.deleteApiKey(key1.id) + try { + provider.fetchApiKey(key1.id) + fail() + } catch (e: ObjectServerError) { + assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) + } + } + + @Test + fun deleteApiKey_invalidServerArgsThrows() { + try { + provider.deleteApiKey(ObjectId()) + fail() + } catch (e: ObjectServerError) { + assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) + } + } + + @Test + fun deleteApiKey_invalidArgumentThrows() { + testNullArg { provider.deleteApiKey(TestHelper.getNull()) } + looperThread.runBlocking { + provider.deleteApiKeyAsync(TestHelper.getNull(), checkNullInVoidCallback) + } + } + + @Test + fun deleteApiKeyAsync() { + val key: RealmUserApiKey = provider.createApiKey("my-key") + assertNotNull(provider.fetchApiKey(key.id)) + looperThread.runBlocking { + provider.deleteApiKeyAsync(key.id) { result -> + if (result.isSuccess) { + try { + provider.fetchApiKey(key.id) + fail() + } catch (e: ObjectServerError) { + assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) + } + looperThread.testComplete() + } else { + fail(result.error.toString()) + } + } + } + } + + @Test + fun deleteApiKeyAsync_invalidServerArgsThrows() = looperThread.runBlocking { + provider.deleteApiKeyAsync(ObjectId()) { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.API_KEY_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + + @Test + fun enableApiKey() { + val key: RealmUserApiKey = provider.createApiKey("my-key") + provider.disableApiKey(key.id) + assertFalse(provider.fetchApiKey(key.id).isEnabled) + provider.enableApiKey(key.id) + assertTrue(provider.fetchApiKey(key.id).isEnabled) + } + + @Test + fun enableApiKey_alreadyEnabled() { + val key: RealmUserApiKey = provider.createApiKey("my-key") + provider.disableApiKey(key.id) + assertFalse(provider.fetchApiKey(key.id).isEnabled) + provider.enableApiKey(key.id) + assertTrue(provider.fetchApiKey(key.id).isEnabled) + provider.enableApiKey(key.id) + assertTrue(provider.fetchApiKey(key.id).isEnabled) + } + + @Test + fun enableApiKey_invalidServerArgsThrows() { + try { + provider.enableApiKey(ObjectId()) + fail() + } catch (e: ObjectServerError) { + assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) + } + } + + @Test + fun enableApiKey_invalidArgumentThrows() { + testNullArg { provider.enableApiKey(TestHelper.getNull()) } + looperThread.runBlocking { + provider.enableApiKeyAsync(TestHelper.getNull(), checkNullInVoidCallback) + } + } + + @Test + fun enableApiKeyAsync() { + val key: RealmUserApiKey = provider.createApiKey("my-key") + provider.disableApiKey(key.id) + assertFalse(provider.fetchApiKey(key.id).isEnabled) + looperThread.runBlocking { + provider.enableApiKeyAsync(key.id) { result -> + if (result.isSuccess) { + assertTrue(provider.fetchApiKey(key.id).isEnabled) + looperThread.testComplete() + } else { + fail(result.error.toString()) + } + } + } + } + + @Test + fun enableApiKeyAsync_invalidServerArgsThrows() = looperThread.runBlocking { + provider.disableApiKeyAsync(ObjectId()) { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.API_KEY_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + + @Test + fun disableApiKey() { + val key: RealmUserApiKey = provider.createApiKey("my-key") + provider.disableApiKey(key.id) + assertFalse(provider.fetchApiKey(key.id).isEnabled) + } + + @Test + fun disableApiKey_alreadyDisabled() { + val key: RealmUserApiKey = provider.createApiKey("my-key") + provider.disableApiKey(key.id) + assertFalse(provider.fetchApiKey(key.id).isEnabled) + provider.disableApiKey(key.id) + assertFalse(provider.fetchApiKey(key.id).isEnabled) + } + + @Test + fun disableApiKey_invalidServerArgsThrows() { + try { + provider.disableApiKey(ObjectId()) + fail() + } catch (e: ObjectServerError) { + assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) + } + } + + @Test + fun disableApiKey_invalidArgumentThrows() { + testNullArg { provider.disableApiKey(TestHelper.getNull()) } + looperThread.runBlocking { + provider.disableApiKeyAsync(TestHelper.getNull(), checkNullInVoidCallback) + } + } + + @Test + fun disableApiKeyAsync() { + val key: RealmUserApiKey = provider.createApiKey("my-key") + assertTrue(key.isEnabled) + looperThread.runBlocking { + provider.disableApiKeyAsync(key.id) { result -> + if (result.isSuccess) { + assertFalse(provider.fetchApiKey(key.id).isEnabled) + looperThread.testComplete() + } else { + fail(result.error.toString()) + } + } + } + } + + @Test + fun disableApiKeyAsync_invalidServerArgsThrows() = looperThread.runBlocking { + provider.disableApiKeyAsync(ObjectId()) { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.API_KEY_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + + @Test + @UiThreadTest + fun callMethodsOnMainThreadThrows() { + for (method in Method.values()) { + try { + when(method) { + Method.CREATE -> provider.createApiKey("name") + Method.FETCH_SINGLE -> provider.fetchApiKey(ObjectId()) + Method.FETCH_ALL -> provider.fetchAllApiKeys() + Method.DELETE -> provider.deleteApiKey(ObjectId()) + Method.ENABLE -> provider.enableApiKey(ObjectId()) + Method.DISABLE -> provider.disableApiKey(ObjectId()) + } + fail("$method should have thrown an exception") + } catch (error: ObjectServerError) { + assertEquals(ErrorCode.NETWORK_UNKNOWN, error.errorCode) + } + } + } + + @Test + fun callAsyncMethodsOnNonLooperThreadThrows() { + for (method in Method.values()) { + try { + when(method) { + Method.CREATE -> provider.createApiKeyAsync("key") { fail() } + Method.FETCH_SINGLE -> provider.fetchApiKeyAsync(ObjectId()) { fail() } + Method.FETCH_ALL -> provider.fetchAllApiKeys { fail() } + Method.DELETE -> provider.deleteApiKeyAsync(ObjectId()) { fail() } + Method.ENABLE -> provider.enableApiKeyAsync(ObjectId()) { fail() } + Method.DISABLE -> provider.disableApiKeyAsync(ObjectId()) { fail() } + } + fail("$method should have thrown an exception") + } catch (ignore: IllegalStateException) { + } + } + } + + @Test + fun callMethodWithLoggedOutUser() { + user.logOut() + for (method in Method.values()) { + try { + when(method) { + Method.CREATE -> provider.createApiKey("name") + Method.FETCH_SINGLE -> provider.fetchApiKey(ObjectId()) + Method.FETCH_ALL -> provider.fetchAllApiKeys() + Method.DELETE -> provider.deleteApiKey(ObjectId()) + Method.ENABLE -> provider.enableApiKey(ObjectId()) + Method.DISABLE -> provider.disableApiKey(ObjectId()) + } + fail("$method should have thrown an exception") + } catch (error: ObjectServerError) { + assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) + } + } + } + + @Test + fun getUser() { + assertEquals(app.currentUser(), provider.user) + } + + @Test + fun getApp() { + assertEquals(app, provider.app) + } +} + diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt index 51ab1b4c7e..53749d76d8 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt @@ -137,13 +137,13 @@ class EmailPasswordAuthProviderTests { @Test fun registerUser_invalidArgumentsThrows() { val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider - expectException { provider.registerUser(TestHelper.getNullString(), "123456") } - expectException { provider.registerUser("foo@bar.baz", TestHelper.getNullString()) } + expectException { provider.registerUser(TestHelper.getNull(), "123456") } + expectException { provider.registerUser("foo@bar.baz", TestHelper.getNull()) } looperThread.runBlocking { - provider.registerUserAsync(TestHelper.getNullString(), "123456", checkNullArgCallback) + provider.registerUserAsync(TestHelper.getNull(), "123456", checkNullArgCallback) } looperThread.runBlocking { - provider.registerUserAsync("foo@bar.baz", TestHelper.getNullString(), checkNullArgCallback) + provider.registerUserAsync("foo@bar.baz", TestHelper.getNull(), checkNullArgCallback) } } @@ -188,13 +188,13 @@ class EmailPasswordAuthProviderTests { @Test fun confirmUser_invalidArgumentsThrows() { val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider - expectException { provider.confirmUser(TestHelper.getNullString(), "token-id") } - expectException { provider.confirmUser("token", TestHelper.getNullString()) } + expectException { provider.confirmUser(TestHelper.getNull(), "token-id") } + expectException { provider.confirmUser("token", TestHelper.getNull()) } looperThread.runBlocking { - provider.confirmUserAsync(TestHelper.getNullString(), "token-id", checkNullArgCallback) + provider.confirmUserAsync(TestHelper.getNull(), "token-id", checkNullArgCallback) } looperThread.runBlocking { - provider.confirmUserAsync("token", TestHelper.getNullString(), checkNullArgCallback) + provider.confirmUserAsync("token", TestHelper.getNull(), checkNullArgCallback) } } @@ -277,9 +277,9 @@ class EmailPasswordAuthProviderTests { @Test fun resendConfirmationEmail_invalidArgumentsThrows() { val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider - expectException { provider.resendConfirmationEmail(TestHelper.getNullString()) } + expectException { provider.resendConfirmationEmail(TestHelper.getNull()) } looperThread.runBlocking { - provider.resendConfirmationEmailAsync(TestHelper.getNullString(), checkNullArgCallback) + provider.resendConfirmationEmailAsync(TestHelper.getNull(), checkNullArgCallback) } } @@ -336,9 +336,9 @@ class EmailPasswordAuthProviderTests { @Test fun sendResetPasswordEmail_invalidArgumentsThrows() { val provider = app.emailPasswordAuthProvider - expectException { provider.sendResetPasswordEmail(TestHelper.getNullString()) } + expectException { provider.sendResetPasswordEmail(TestHelper.getNull()) } looperThread.runBlocking { - provider.sendResetPasswordEmailAsync(TestHelper.getNullString(), checkNullArgCallback) + provider.sendResetPasswordEmailAsync(TestHelper.getNull(), checkNullArgCallback) } } @@ -425,13 +425,13 @@ class EmailPasswordAuthProviderTests { @Test fun callResetPasswordFunction_invalidArgumentsThrows() { val provider = app.emailPasswordAuthProvider - expectException { provider.callResetPasswordFunction(TestHelper.getNullString(), "password") } - expectException { provider.callResetPasswordFunction("foo@bar.baz", TestHelper.getNullString()) } + expectException { provider.callResetPasswordFunction(TestHelper.getNull(), "password") } + expectException { provider.callResetPasswordFunction("foo@bar.baz", TestHelper.getNull()) } looperThread.runBlocking { - provider.callResetPasswordFunctionAsync(TestHelper.getNullString(), "new-password", arrayOf(), checkNullArgCallback) + provider.callResetPasswordFunctionAsync(TestHelper.getNull(), "new-password", arrayOf(), checkNullArgCallback) } looperThread.runBlocking { - provider.callResetPasswordFunctionAsync("foo@bar.baz", io.realm.TestHelper.getNullString(), arrayOf(), checkNullArgCallback) + provider.callResetPasswordFunctionAsync("foo@bar.baz", io.realm.TestHelper.getNull(), arrayOf(), checkNullArgCallback) } } @@ -475,17 +475,17 @@ class EmailPasswordAuthProviderTests { @Test fun resetPassword_invalidArgumentsThrows() { val provider = app.emailPasswordAuthProvider - expectException { provider.resetPassword(TestHelper.getNullString(), "token-id", "password") } - expectException { provider.resetPassword("token", TestHelper.getNullString(), "password") } - expectException { provider.resetPassword("token", "token-id", TestHelper.getNullString()) } + expectException { provider.resetPassword(TestHelper.getNull(), "token-id", "password") } + expectException { provider.resetPassword("token", TestHelper.getNull(), "password") } + expectException { provider.resetPassword("token", "token-id", TestHelper.getNull()) } looperThread.runBlocking { - provider.resetPasswordAsync(TestHelper.getNullString(), "token-id", "password", checkNullArgCallback) + provider.resetPasswordAsync(TestHelper.getNull(), "token-id", "password", checkNullArgCallback) } looperThread.runBlocking { - provider.resetPasswordAsync("token", TestHelper.getNullString(), "password", checkNullArgCallback) + provider.resetPasswordAsync("token", TestHelper.getNull(), "password", checkNullArgCallback) } looperThread.runBlocking { - provider.resetPasswordAsync("token","token-id", TestHelper.getNullString(), checkNullArgCallback) + provider.resetPasswordAsync("token","token-id", TestHelper.getNull(), checkNullArgCallback) } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt index 0b5c7114ec..63255e947e 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -267,4 +267,22 @@ class RealmAppTests { } catch (ignore: IllegalStateException) { } } + + @Test + fun getApiKeyAuthProvider() { + val user1: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val provider1: ApiKeyAuthProvider = app.apiKeyAuthProvider + val user2: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val provider2: ApiKeyAuthProvider = app.apiKeyAuthProvider + + assertNotEquals(provider1, provider2) + user2.logOut() + assertEquals(provider1, app.apiKeyAuthProvider) + user1.logOut() + try { + app.apiKeyAuthProvider + fail() + } catch (ignore: IllegalStateException) { + } + } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt index e1877a0d94..a95991a25e 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt @@ -209,7 +209,7 @@ class BlockingLooperThread { private fun closeResources() { synchronized(lock) { - for (cr in closableResources!!) { + for (cr in closableResources) { cr.close() } } @@ -231,7 +231,7 @@ class BlockingLooperThread { try { val executorService = Executors.newSingleThreadExecutor { runnable -> Thread(runnable, threadName) } val test = TestThread(test) - val ignored = executorService.submit(test) + executorService.submit(test) TestHelper.exitOrThrow(executorService, signalTestCompleted, test) } catch (testFailure: Throwable) { // These exceptions should only come from TestHelper.awaitOrFail() diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 40a78e1455..f719090f71 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -69,6 +69,8 @@ capitalizeFirstLetter(buildTypeCap "${CMAKE_BUILD_TYPE}") # Generate JNI header files. Each build has its own JNI header in its build_dir/jni_include. # WARNING: The classes_PATH is not part the public API offered by the Android Gradle Plugin # so it might change without warning when upgrading the plugin. +file(DOWNLOAD "https://repo1.maven.org/maven2/org/mongodb/bson/3.12.1/bson-3.12.1.jar" "${PROJECT_BINARY_DIR}/bson-3.12.1.jar") +set(bsonlib_PATH ${PROJECT_BINARY_DIR}/bson-3.12.1.jar) set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/classes/) set(classes_LIST io.realm.RealmQuery @@ -90,6 +92,7 @@ if (build_SYNC) list(APPEND classes_LIST io.realm.ClientResetRequiredError io.realm.EmailPasswordAuthProvider + io.realm.ApiKeyAuthProvider io.realm.RealmApp io.realm.SyncManager io.realm.SyncSession @@ -102,7 +105,7 @@ if (build_SYNC) endif() create_javah(TARGET jni_headers CLASSES ${classes_LIST} - CLASSPATH ${classes_PATH} $ENV{ANDROID_HOME}/platforms/android-29/android.jar + CLASSPATH ${classes_PATH} $ENV{ANDROID_HOME}/platforms/android-29/android.jar ${bsonlib_PATH} OUTPUT_DIR ${jni_headers_PATH} DEPENDS ${classes_PATH} ) @@ -179,6 +182,7 @@ if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmApp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_EmailPasswordAuthProvider.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ApiKeyAuthProvider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetRequiredError.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_ApiKeyAuthProvider.cpp b/realm/realm-library/src/main/cpp/io_realm_ApiKeyAuthProvider.cpp new file mode 100644 index 0000000000..23d6c215ae --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_ApiKeyAuthProvider.cpp @@ -0,0 +1,120 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_ApiKeyAuthProvider.h" + +#include "java_class_global_def.hpp" +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" +#include "object-store/src/sync/app.hpp" + +#include +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::jni_util; +using namespace realm::_impl; + +static jobjectArray map_key(JNIEnv* env, App::UserAPIKey& key) +{ + jobjectArray arr = (jobjectArray)env->NewObjectArray(4, JavaClassGlobalDef::java_lang_object(), NULL); + if (arr == NULL) { + ThrowException(env, OutOfMemory, "Could not allocate memory to return API key."); + return NULL; + } + std::string api_key_id = key.id.to_string(); + env->SetObjectArrayElement(arr, 0, to_jstring(env, api_key_id)); + env->SetObjectArrayElement(arr, 1, (key.key) ? to_jstring(env, key.key) : NULL); + env->SetObjectArrayElement(arr, 2, to_jstring(env, key.name)); + env->SetObjectArrayElement(arr, 3, JavaClassGlobalDef::new_boolean(env, key.disabled)); + return arr; +} + +// Shared mapper function for mapping UserApiKey to Java Object[] +static std::function single_key_mapper = [](JNIEnv* env, App::UserAPIKey key) { + return map_key(env, key); +}; + +// Shared mapper function for mapping Vector to Java Object[][] +static std::function)> multi_key_mapper = [](JNIEnv* env, std::vector keys) { + jobjectArray arr = (jobjectArray)env->NewObjectArray(static_cast(keys.size()), JavaClassGlobalDef::java_lang_object(), NULL); + if (arr == NULL) { + ThrowException(env, OutOfMemory, "Could not allocate memory to return list of API keys."); + return arr; + } + for (size_t i = 0; i < keys.size(); ++i) { + env->SetObjectArrayElement(arr, i, map_key(env, keys[i])); + } + return arr; +}; + +JNIEXPORT void JNICALL Java_io_realm_ApiKeyAuthProvider_nativeCallFunction(JNIEnv* env, + jclass, + jint j_function_type, + jlong j_app_ptr, + jlong j_user_ptr, + jstring j_arg, + jobject j_callback) +{ + try { + App* app = reinterpret_cast(j_app_ptr); + auto user = *reinterpret_cast*>(j_user_ptr); + auto client = app->provider_client(); + switch(j_function_type) { + case io_realm_ApiKeyAuthProvider_TYPE_CREATE: { + JStringAccessor name(env, j_arg); + auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, single_key_mapper); + client.create_api_key(name, user, callback); + break; + } + case io_realm_ApiKeyAuthProvider_TYPE_FETCH_SINGLE: { + auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, single_key_mapper); + std::string str_id = JStringAccessor(env, static_cast(j_arg)); + client.fetch_api_key(ObjectId(str_id.c_str()), user, callback); + break; + } + case io_realm_ApiKeyAuthProvider_TYPE_FETCH_ALL: { + auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, multi_key_mapper); + client.fetch_api_keys(user, callback); + break; + } + case io_realm_ApiKeyAuthProvider_TYPE_DELETE: { + auto callback = JavaNetworkTransport::create_void_callback(env, j_callback); + std::string str_id = JStringAccessor(env, static_cast(j_arg)); + client.delete_api_key(ObjectId(str_id.c_str()), user, callback); + break; + } + case io_realm_ApiKeyAuthProvider_TYPE_ENABLE: { + auto callback = JavaNetworkTransport::create_void_callback(env, j_callback); + std::string str_id = JStringAccessor(env, static_cast(j_arg)); + client.enable_api_key(ObjectId(str_id.c_str()), user, callback); + break; + } + case io_realm_ApiKeyAuthProvider_TYPE_DISABLE: { + auto callback = JavaNetworkTransport::create_void_callback(env, j_callback); + std::string str_id = JStringAccessor(env, static_cast(j_arg)); + client.disable_api_key(ObjectId(str_id.c_str()), user, callback); + break; + } + default: + throw std::logic_error(util::format("Unknown function: %1", j_function_type)); + } + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/java_class_global_def.hpp b/realm/realm-library/src/main/cpp/java_class_global_def.hpp index f1baf81348..f272340454 100644 --- a/realm/realm-library/src/main/cpp/java_class_global_def.hpp +++ b/realm/realm-library/src/main/cpp/java_class_global_def.hpp @@ -50,6 +50,7 @@ class JavaClassGlobalDef { , m_java_util_date(env, "java/util/Date", false) , m_java_lang_string(env, "java/lang/String", false) , m_java_lang_boolean(env, "java/lang/Boolean", false) + , m_java_lang_object(env, "java/lang/Object", false) , m_shared_realm_schema_change_callback(env, "io/realm/internal/OsSharedRealm$SchemaChangedCallback", false) , m_realm_notifier(env, "io/realm/internal/RealmNotifier", false) { @@ -61,6 +62,7 @@ class JavaClassGlobalDef { jni_util::JavaClass m_java_util_date; jni_util::JavaClass m_java_lang_string; jni_util::JavaClass m_java_lang_boolean; + jni_util::JavaClass m_java_lang_object; jni_util::JavaClass m_shared_realm_schema_change_callback; jni_util::JavaClass m_realm_notifier; @@ -165,6 +167,12 @@ class JavaClassGlobalDef { { return instance()->m_realm_notifier; } + + // java.lang.Object + inline static const jni_util::JavaClass& java_lang_object() + { + return instance()->m_java_lang_object; + } }; } // namespace realm diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 3f16f2287f..f18b240b8b 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 3f16f2287fdd20acc7c890c0aa77ba055ea54597 +Subproject commit f18b240b8bf592e5d5ae8045c5444f423b6d8e9d diff --git a/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuthProvider.java b/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuthProvider.java new file mode 100644 index 0000000000..2e37808eaf --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuthProvider.java @@ -0,0 +1,310 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +import org.bson.types.ObjectId; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.Nullable; + +import io.realm.internal.Util; +import io.realm.internal.objectstore.OsJavaNetworkTransport; + +/** + * This class exposes functionality for a user to manage API keys under their control. + */ +public class ApiKeyAuthProvider { + + private static final int TYPE_CREATE = 1; + private static final int TYPE_FETCH_SINGLE = 2; + private static final int TYPE_FETCH_ALL = 3; + private static final int TYPE_DELETE = 4; + private static final int TYPE_DISABLE = 5; + private static final int TYPE_ENABLE = 6; + + private final RealmUser user; + + /** + * Create an instance of this class for a specific user. + * + * @param user user that is controlling the API keys. + */ + public ApiKeyAuthProvider(RealmUser user) { + this.user = user; + } + + public RealmUser getUser() { + return user; + } + + public RealmApp getApp() { + return user.getApp(); + } + + /** + * Creates a user API key that can be used to authenticate as the user. + *

            + * The value of the key must be persisted at this time as this is the only time it is visible. + *

            + * The key is enabled when created. It can be disabled by calling {@link #disableApiKey(ObjectId)}. + * + * @param name the name of the key + * @throws ObjectServer if the server failed to create the API key. + * @return the new API key for the user. + */ + public RealmUserApiKey createApiKey(String name) throws ObjectServerError { + Util.checkEmpty(name, "name"); + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + RealmApp.OsJNIResultCallback callback = new RealmApp.OsJNIResultCallback(success, error) { + @Override + protected RealmUserApiKey mapSuccess(Object result) { + return createKeyFromNative((Object[]) result); + } + }; + nativeCallFunction(TYPE_CREATE, user.getApp().nativePtr, user.osUser.getNativePtr(), name, callback); + return RealmApp.handleResult(success, error); + } + + /** + * Asynchronously creates a user API key that can be used to authenticate as the user. + *

            + * The value of the key must be persisted at this time as this is the only time it is visible. + *

            + * The key is enabled when created. It can be disabled by calling {@link #disableApiKey(ObjectId)}. + * + * @param name the name of the key + * @param callback callback when key creation has completed or failed. The callback will always + * happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask createApiKeyAsync(String name, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous creation of api keys are only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public RealmUserApiKey run() throws ObjectServerError { + return createApiKey(name); + } + }.start(); + } + + /** + * Fetches a specific user API key associated with the user. + * + * @param id the id of the key to fetch. + * @throws ObjectServer if the server failed to fetch the API key. + */ + public RealmUserApiKey fetchApiKey(ObjectId id) throws ObjectServerError { + Util.checkNull(id, "id"); + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_FETCH_SINGLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new RealmApp.OsJNIResultCallback(success, error) { + @Override + protected RealmUserApiKey mapSuccess(Object result) { + return createKeyFromNative((Object[]) result); + } + }); + return RealmApp.handleResult(success, error); + } + + /** + * Fetches a specific user API key associated with the user. + * + * @param id the id of the key to fetch. + * @param callback callback used when the key was fetched or the call failed. The callback + * will always happen on the same thread as this method was called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask fetchApiKeyAsync(ObjectId id, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public RealmUserApiKey run() throws ObjectServerError { + return fetchApiKey(id); + } + }.start(); + } + + /** + * Fetches all API keys associated with the user. + * + * @throws ObjectServer if the server failed to fetch the API keys. + */ + public List fetchAllApiKeys() throws ObjectServerError { + AtomicReference> success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_FETCH_ALL, user.getApp().nativePtr, user.osUser.getNativePtr(), null, new RealmApp.OsJNIResultCallback>(success, error) { + @Override + protected List mapSuccess(Object result) { + Object[] keyData = (Object[]) result; + List list = new ArrayList<>(); + for (int i = 0; i < keyData.length; i++) { + list.add(createKeyFromNative((Object[]) keyData[i])); + } + return list; + } + }); + return RealmApp.handleResult(success, error); + } + + + /** + * Fetches all API keys associated with the user. + * + * @param callback callback used when the keys were fetched or the call failed. The callback + * will always happen on the same thread as this method was called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask fetchAllApiKeys(RealmApp.Callback> callback) { + Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); + return new RealmApp.Request>(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public List run() throws ObjectServerError { + return fetchAllApiKeys(); + } + }.start(); + } + + /** + * Deletes a specific API key created by the user. + * + * @param id the id of the key to delete. + * @throws ObjectServer if the server failed to delete the API key. + */ + public void deleteApiKey(ObjectId id) throws ObjectServerError { + Util.checkNull(id, "id"); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_DELETE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new RealmApp.OsJNIVoidResultCallback(error)); + RealmApp.handleResult(null, error); + } + + /** + * Deletes a specific API key created by the user. + * + * @param id the id of the key to delete. + * @param callback callback used when the was deleted or the call failed. The callback + * will always happen on the same thread as this method was called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask deleteApiKeyAsync(ObjectId id, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous deleting an api key is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + deleteApiKey(id); + return null; + } + }.start(); + } + + /** + * Disables a specific API key created by the user. + * + * @param id the id of the key to disable. + * @throws ObjectServer if the server failed to disable the API key. + */ + public void disableApiKey(ObjectId id) throws ObjectServerError { + Util.checkNull(id, "id"); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_DISABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new RealmApp.OsJNIVoidResultCallback(error)); + RealmApp.handleResult(null, error); + } + + /** + * Disables a specific API key created by the user. + * + * @param id the id of the key to disable. + * @param callback callback used when the key was disabled or the call failed. The callback + * will always happen on the same thread as this method was called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask disableApiKeyAsync(ObjectId id, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous disabling an api key is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + disableApiKey(id); + return null; + } + }.start(); + } + + /** + * Enables a specific API key created by the user. + * + * @param id the id of the key to enable. + * @throws ObjectServer if the server failed to enable the API key. + */ + public void enableApiKey(ObjectId id) throws ObjectServerError { + Util.checkNull(id, "id"); + AtomicReference error = new AtomicReference<>(null); + nativeCallFunction(TYPE_ENABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new RealmApp.OsJNIVoidResultCallback(error)); + RealmApp.handleResult(null, error); + } + + /** + * Enables a specific API key created by the user. + * + * @param id the id of the key to enable. + * @param callback callback used when the key was enabled or the call failed. The callback + * will always happen on the same thread as this method was called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask enableApiKeyAsync(ObjectId id, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous enabling an api key is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws ObjectServerError { + enableApiKey(id); + return null; + } + }.start(); + } + + private RealmUserApiKey createKeyFromNative(Object[] keyData) { + return new RealmUserApiKey(new ObjectId((String) keyData[0]), + (String) keyData[1], + (String) keyData[2], + !(Boolean) keyData[3]); // Server returns disabled state instead of enabled + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ApiKeyAuthProvider that = (ApiKeyAuthProvider) o; + + return user.equals(that.user); + } + + @Override + public int hashCode() { + return user.hashCode(); + } + + @Override + public String toString() { + return "ApiKeyAuthProvider{" + + "user=" + user.getId() + + '}'; + } + + private static native void nativeCallFunction(int functionType, long nativeAppPtr, long nativeUserPtr, @Nullable String arg, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index df524b6ef9..948a0313e4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -87,6 +87,7 @@ public void onError(SyncSession session, ObjectServerError error) { private OsJavaNetworkTransport networkTransport; final long nativePtr; private final EmailPasswordAuthProvider emailAuthProvider = new EmailPasswordAuthProvider(this); + private ApiKeyAuthProvider apiKeyAuthProvider = null; private CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList<>(); public RealmApp(String appId) { @@ -169,8 +170,8 @@ public RealmUser removeUser(RealmUser user) throws ObjectServerError { AtomicReference error = new AtomicReference<>(null); nativeRemoveUser(nativePtr, user.osUser.getNativePtr(), new OsJNIResultCallback(success, error) { @Override - protected void mapSuccess(Object result, AtomicReference success) { - success.set(user); + protected RealmUser mapSuccess(Object result) { + return user; } }); return handleResult(success, error); @@ -209,9 +210,9 @@ public RealmUser login(RealmCredentials credentials) throws ObjectServerError { AtomicReference error = new AtomicReference<>(null); nativeLogin(nativePtr, credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { @Override - protected void mapSuccess(Object result, AtomicReference success) { + protected RealmUser mapSuccess(Object result) { Long nativePtr = (Long) result; - success.set(new RealmUser(nativePtr, RealmApp.this)); + return new RealmUser(nativePtr, RealmApp.this); } }); return handleResult(success, error); @@ -287,6 +288,23 @@ public RealmAsyncTask logOutAsync(Callback callback) { return logOutAsync(user, callback); } + /** + * Returns a wrapper for managing API keys controlled by the current user. + * + * @return wrapper for managing API keys controlled by the current user. + * @throws IllegalStateException if no user is currently logged in. + */ + public synchronized ApiKeyAuthProvider getApiKeyAuthProvider() { + RealmUser user = currentUser(); + if (user == null) { + throw new IllegalStateException("No user is currently logged in."); + } + if (apiKeyAuthProvider == null || !user.equals(apiKeyAuthProvider.getUser())) { + apiKeyAuthProvider = new ApiKeyAuthProvider(user); + } + return apiKeyAuthProvider; + } + /** * Returns a wrapper for interacting with functionality related to users either being created or * login using the {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} identity provider. @@ -412,8 +430,8 @@ public OsJNIVoidResultCallback(AtomicReference error) { } @Override - protected void mapSuccess(Object result, AtomicReference success) { - // Do nothing + protected Void mapSuccess(Object result) { + return null; } } @@ -432,11 +450,14 @@ public OsJNIResultCallback(@Nullable AtomicReference success, AtomicReference @Override public void onSuccess(Object result) { - mapSuccess(result, success); + T mappedResult = mapSuccess(result); + if (success != null) { + success.set(mappedResult); + } } // Must map the underlying success Object to the appropriate type in Java - protected abstract void mapSuccess(Object result, @Nullable AtomicReference success); + protected abstract T mapSuccess(Object result); @Override public void onError(String nativeErrorCategory, int nativeErrorCode, String errorMessage) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUserApiKey.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUserApiKey.java new file mode 100644 index 0000000000..05c09a734d --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUserApiKey.java @@ -0,0 +1,114 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +import org.bson.types.ObjectId; + +import javax.annotation.Nullable; + +/** + * Class representing an API key for a {@link RealmUser}. An API can be used to represent the + * user when logging instead of using email and password. + *

            + * These keys are created and managed through {@link RealmApp#getApiKeyAuthProvider()}. + *

            + * Note that a keys {@link #value} is only available when the key is created, after that it is not + * visible. So anyone creating an API key is responsible for storing it safely after that. + */ +public class RealmUserApiKey { + private final ObjectId id; + private final String value; + private final String name; + private final boolean enabled; + + RealmUserApiKey(ObjectId id, @Nullable String value, String name, boolean enabled) { + this.id = id; + this.value = value; + this.name = name; + this.enabled = enabled; + } + + /** + * Returns the unique identifier for this key. + * + * @return the id, uniquely identifying the key. + */ + public ObjectId getId() { + return id; + } + + /** + * Returns this keys value. This value is only returned when the key is created. After that + * the value is no longer visible. + * + * @return the value of this key. Is only returned when the key is created. + */ + @Nullable + public String getValue() { + return value; + } + + /** + * Returns the name of this key. + * + * @return the name of the key. + */ + public String getName() { + return name; + } + + + /** + * Returns whether or not this key is currently enabled. + * + * @return if the key is enabled or not. + */ + public boolean isEnabled() { + return enabled; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RealmUserApiKey that = (RealmUserApiKey) o; + + if (enabled != that.enabled) return false; + if (!id.equals(that.id)) return false; + if (!value.equals(that.value)) return false; + return name.equals(that.name); + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + value.hashCode(); + result = 31 * result + name.hashCode(); + result = 31 * result + (enabled ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "RealmUserApiKey{" + + "id=" + id + + ", value='" + value + '\'' + + ", name='" + name + '\'' + + ", enabled=" + enabled + + '}'; + } +} diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 2468ef74e8..52cee80d9b 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -92,12 +92,6 @@ public void onResult(int count) { } } - - // Used by Kotlin tests to cheat the type system - public static String getNullString() { - return null; - } - public static RealmFieldType getColumnType(Object o) { if (o instanceof Boolean) { return RealmFieldType.BOOLEAN; From 0b18dfeec209c3587ca5c508a668168f38a74c14 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 3 Apr 2020 16:13:17 +0200 Subject: [PATCH 1490/2110] Add support for Login, Credentials and LinkUser (#6795) --- .../java/io/realm/CredentialsTests.java | 206 ------------------ .../kotlin/io/realm/RealmAppExt.kt | 4 +- .../kotlin/io/realm/RealmAppTests.kt | 139 +++++++++++- .../kotlin/io/realm/RealmCredentialsTests.kt | 125 ++++++++--- .../realm-library/src/main/cpp/CMakeLists.txt | 3 +- .../src/main/cpp/io_realm_RealmApp.cpp | 21 ++ ..._internal_objectstore_OsAppCredentials.cpp | 14 +- .../src/main/cpp/java_accessor.hpp | 28 +-- .../src/main/cpp/java_object_accessor.hpp | 26 +-- realm/realm-library/src/main/cpp/object-store | 2 +- .../objectServer/java/io/realm/RealmApp.java | 116 +++++++++- .../java/io/realm/RealmCredentials.java | 190 ++++++++-------- .../objectServer/java/io/realm/RealmUser.java | 2 +- 13 files changed, 493 insertions(+), 383 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java deleted file mode 100644 index 84c342b93f..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/CredentialsTests.java +++ /dev/null @@ -1,206 +0,0 @@ -package io.realm; -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class CredentialsTests { - - // See https://github.com/realm/realm-sync-services/blob/master/doc/index.apib for a description of the fields - // needed by each identity provider. - - @Test - public void getUserInfo_isUnmodifiable() { - SyncCredentials creds = SyncCredentials.custom("foo", "customProvider", null); - Map userInfo = creds.getUserInfo(); - try { - userInfo.put("boom", null); - fail(); - } catch (UnsupportedOperationException ignored) { - } - } - - @Test - public void facebook() { - SyncCredentials creds = SyncCredentials.facebook("foo"); - - assertEquals(SyncCredentials.IdentityProvider.FACEBOOK, creds.getIdentityProvider()); - assertEquals("foo", creds.getUserIdentifier()); - assertTrue(creds.getUserInfo().isEmpty()); - } - - @Test - public void facebook_invalidInput() { - String[] invalidInput = {null, ""}; - for (String input : invalidInput) { - try { - SyncCredentials.facebook(input); - fail(input + " should have failed"); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void google() { - SyncCredentials creds = SyncCredentials.google("foo"); - - assertEquals(SyncCredentials.IdentityProvider.GOOGLE, creds.getIdentityProvider()); - assertEquals("foo", creds.getUserIdentifier()); - assertTrue(creds.getUserInfo().isEmpty()); - } - - @Test - public void google_invalidInput() { - String[] invalidInput = {null, ""}; - for (String input : invalidInput) { - try { - SyncCredentials.google(input); - fail(input + " should have failed"); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void jwt() { - SyncCredentials creds = SyncCredentials.jwt("foo"); - - assertEquals(SyncCredentials.IdentityProvider.JWT, creds.getIdentityProvider()); - assertEquals("foo", creds.getUserIdentifier()); - assertTrue(creds.getUserInfo().isEmpty()); - } - - @Test - public void jwt_invalidInput() { - String[] invalidInput = {null, ""}; - for (String input : invalidInput) { - try { - SyncCredentials.jwt(input); - fail(input + " should have failed"); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void anonymous() { - SyncCredentials creds = SyncCredentials.anonymous(); - assertEquals(SyncCredentials.IdentityProvider.ANONYMOUS, creds.getIdentityProvider()); - assertTrue(creds.getUserInfo().isEmpty()); - } - - @Test - public void usernamePassword_register() { - SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar", true); - assertUsernamePassword(creds, "foo", "bar", true); - } - - @Test - public void usernamePassword_noRegister() { - SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar", false); - assertUsernamePassword(creds, "foo", "bar", false); - } - - @Test - public void usernamePassword_defaultRegister() { - SyncCredentials creds = SyncCredentials.usernamePassword("foo", "bar"); - assertUsernamePassword(creds, "foo", "bar", false); - } - - // Only validate username. All passwords are allowed - @Test - public void usernamePassword_invalidUserName() { - String[] invalidInput = {null, ""}; - for (String input : invalidInput) { - try { - SyncCredentials.usernamePassword(input, "bar", true); - fail(input + " should have failed"); - } catch (IllegalArgumentException ignored) { - } - } - } - - // Null passwords are allowed - @Test - public void usernamePassword_nullPassword() { - SyncCredentials creds = SyncCredentials.usernamePassword("foo", null, true); - assertUsernamePassword(creds, "foo", null, true); - } - - @Test - public void custom() { - Map userInfo = new HashMap(); - userInfo.put("custom", "property"); - SyncCredentials creds = SyncCredentials.custom("foo", "customProvider", userInfo); - - assertEquals("foo", creds.getUserIdentifier()); - assertEquals("customProvider", creds.getIdentityProvider()); - assertEquals(1, creds.getUserInfo().size()); - assertEquals("property", creds.getUserInfo().get("custom")); - } - - @Test - public void custom_invalidUserName() { - Map userInfo = new HashMap(); - - String[] invalidInput = {null, ""}; - for (String username : invalidInput) { - try { - SyncCredentials.custom(username, SyncCredentials.IdentityProvider.FACEBOOK, userInfo); - fail(); - } catch (IllegalArgumentException ignored) { - } - } - } - - @Test - public void custom_invalidProvider() { - Map userInfo = new HashMap(); - - try { - SyncCredentials.custom("foo", null, userInfo); - fail(); - } catch (IllegalArgumentException ignored) { - } - } - - private void assertUsernamePassword(SyncCredentials creds, String username, String password, boolean register) { - assertEquals(username, creds.getUserIdentifier()); - - Map userInfo = creds.getUserInfo(); - assertEquals(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, creds.getIdentityProvider()); - - assertEquals(password, userInfo.get("password")); - - Boolean registerActual = (Boolean) userInfo.get("register"); - if (registerActual == null) { - registerActual = Boolean.FALSE; - } - assertEquals(register, registerActual); - } -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt index 80660f4580..6d29c6053a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt @@ -1,5 +1,7 @@ package io.realm +import io.realm.admin.ServerAdmin + /** * Resets the Realm Application and delete all local state. * @@ -7,7 +9,7 @@ package io.realm * behavior. */ fun RealmApp.close() { - // TODO Do we need to log out users? + ServerAdmin().deleteAllUsers() SyncManager.reset() BaseRealm.applicationContext = null // Required for Realm.init() to work } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt index 63255e947e..fda4d17ca6 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -16,6 +16,9 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.admin.ServerAdmin +import io.realm.log.LogLevel +import io.realm.log.RealmLog import io.realm.rule.BlockingLooperThread import io.realm.rule.RunInLooperThread import io.realm.rule.RunTestInLooperThread @@ -32,10 +35,12 @@ class RealmAppTests { private val looperThread = BlockingLooperThread() private lateinit var app: TestRealmApp + private lateinit var admin: ServerAdmin @Before fun setUp() { app = TestRealmApp() + admin = ServerAdmin() } @After @@ -43,7 +48,6 @@ class RealmAppTests { app.close() } - // FIXME: Smoke test for the network protocol and associated classes. @Test fun login() { val creds = RealmCredentials.anonymous() @@ -51,6 +55,52 @@ class RealmAppTests { assertNotNull(user) } + @Test + fun login_invalidUserThrows() { + val credentials = RealmCredentials.emailPassword("foo", "bar") + try { + app.login(credentials) + fail() + } catch(ex: ObjectServerError) { + assertEquals(ErrorCode.AUTH_ERROR, ex.errorCode) + } + } + + @Test + fun login_invalidArgsThrows() { + try { + app.login(TestHelper.getNull()) + fail() + } catch(ignore: IllegalArgumentException) { + } + } + + @Test + fun loginAsync() = looperThread.runBlocking { + app.loginAsync(RealmCredentials.anonymous()) { result -> + assertNotNull(result.orThrow) + looperThread.testComplete() + } + } + + @Test + fun loginAsync_invalidUserThrows() = looperThread.runBlocking { + app.loginAsync(RealmCredentials.emailPassword("foo", "bar")) { result -> + assertFalse(result.isSuccess) + assertEquals(ErrorCode.AUTH_ERROR, result.error.errorCode) + looperThread.testComplete() + } + } + + @Test + fun loginAsync_throwsOnNonLooperThread() { + try { + app.loginAsync(RealmCredentials.anonymous()) { fail() } + fail() + } catch (ignore: IllegalStateException) { + } + } + @Test fun currentUser() { assertNull(app.currentUser()) @@ -154,7 +204,7 @@ class RealmAppTests { @Ignore("Add this test once we have support for both EmailPassword and ApiKey Auth Providers") @Test fun switchUser_authProvidersLockUsers() { - TODO() + TODO("FIXME") } @Test @@ -268,6 +318,90 @@ class RealmAppTests { } } + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUser() { + admin.setAutomaticConfirmation(enabled = false) + val user: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(1, user.identities.size) + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuthProvider.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled + val linkedUser: RealmUser = app.linkUser(RealmCredentials.emailPassword(email, password)) + assertTrue(user === linkedUser) + assertEquals(2, linkedUser.identities.size) + assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) + admin.setAutomaticConfirmation(enabled = true) + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUser_existingCredentialsThrows() { + admin.setAutomaticConfirmation(enabled = false) + val email = TestHelper.getRandomEmail() + val password = "123456" + val emailUser: RealmUser = app.registerUserAndLogin(email, password) + val anonymousUser: RealmUser = app.login(RealmCredentials.anonymous()) + try { + app.linkUser(RealmCredentials.emailPassword(email, password)) + fail() + } catch (ex: ObjectServerError) { + assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) + } + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUser_noCurrentUserThrows() { + try { + app.linkUser(RealmCredentials.emailPassword(TestHelper.getRandomEmail(), "123456")) + fail() + } catch (ignore: IllegalStateException) { + } + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUser_invalidArgsThrows() { + try { + app.linkUser(TestHelper.getNull()) + fail() + } catch (ignore: IllegalArgumentException) { + } + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUserAsync() { + admin.setAutomaticConfirmation(enabled = false) + val user: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(1, user.identities.size) + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuthProvider.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled + looperThread.runBlocking { + app.linkUserAsync(RealmCredentials.emailPassword(email, password)) { result -> + val linkedUser: RealmUser = result.orThrow + assertTrue(user === linkedUser) + assertEquals(2, linkedUser.identities.size) + assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) + admin.setAutomaticConfirmation(enabled = true) + } + } + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUserAsync_throwsOnNonLooperThread() { + val user: RealmUser = app.login(RealmCredentials.anonymous()) + try { + app.linkUserAsync(RealmCredentials.emailPassword(TestHelper.getRandomEmail(), "123456")) { fail() } + fail() + } catch (ignore: java.lang.IllegalStateException) { + } + + } + @Test fun getApiKeyAuthProvider() { val user1: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") @@ -285,4 +419,5 @@ class RealmAppTests { } catch (ignore: IllegalStateException) { } } + } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt index 58c0cc532a..34ed2aed00 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt @@ -15,6 +15,7 @@ */ package io.realm +import io.realm.ErrorCode import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.* @@ -23,7 +24,6 @@ import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith -@Ignore("FIXME: Reenable these when adding full suppport for a Credentials") @RunWith(AndroidJUnit4::class) class RealmCredentialsTests { @@ -35,43 +35,58 @@ class RealmCredentialsTests { } } + inline fun expectException(method: () -> Unit) { + try { + method() + fail() + } catch (e: Throwable) { + if (e !is T) { + fail("Unexpected exception: $e") + } + } + } + @Test fun anonymous() { val creds = RealmCredentials.anonymous() - assertEquals("anon-user", creds.identityProvider) - assertNotNull(creds.asJson()) // Treat the JSON as an opaque value. + assertEquals("anon-user", creds.identityProvider.id) + assertTrue(creds.asJson().contains("anon-user")) // Treat the JSON as an opaque value. } + @Ignore("FIXME: Awaiting ObjectStore support") @Test fun apiKey() { - TODO() + val creds = RealmCredentials.apiKey("token") + assertEquals("anon-user", creds.identityProvider.id) + assertTrue(creds.asJson().contains("token")) // Treat the JSON as an opaque value. } @Test fun apiKey_invalidInput() { - TODO() + expectException { RealmCredentials.apiKey("") } + expectException { RealmCredentials.apiKey(TestHelper.getNull()) } } @Test fun apple() { val creds = RealmCredentials.apple("apple-token") - assertEquals("oauth2-apple", creds.identityProvider) + assertEquals("oauth2-apple", creds.identityProvider.id) assertTrue(creds.asJson().contains("apple-token")) // Treat the JSON as a largely opaque value. } @Test fun apple_invalidInput() { - try { - RealmCredentials.apple("") - } catch (ignored: IllegalArgumentException) { - } + expectException { RealmCredentials.apple("") } + expectException { RealmCredentials.apple(TestHelper.getNull()) } } + @Ignore("FIXME: Awaiting ObjectStore support") @Test fun customFunction() { TODO() } + @Ignore("FIXME: Awaiting ObjectStore support") @Test fun customFunction_invalidInput() { TODO() @@ -80,7 +95,7 @@ class RealmCredentialsTests { @Test fun emailPassword() { val creds = RealmCredentials.emailPassword("foo@bar.com", "secret") - assertEquals("local-userpass", creds.identityProvider) + assertEquals("local-userpass", creds.identityProvider.id) // Treat the JSON as a largely opaque value. assertTrue(creds.asJson().contains("foo@bar.com")) assertTrue(creds.asJson().contains("secret")) @@ -88,52 +103,112 @@ class RealmCredentialsTests { @Test fun emailPassword_invalidInput() { - TODO() + expectException { RealmCredentials.emailPassword("", "password") } + expectException { RealmCredentials.emailPassword("email", "") } + expectException { RealmCredentials.emailPassword(TestHelper.getNull(), "password") } + expectException { RealmCredentials.emailPassword("email", TestHelper.getNull()) } } @Test fun facebook() { val creds = RealmCredentials.facebook("fb-token") - assertEquals("oauth2-facebook", creds.identityProvider) + assertEquals("oauth2-facebook", creds.identityProvider.id) assertTrue(creds.asJson().contains("fb-token")) } @Test fun facebook_invalidInput() { - try { - RealmCredentials.facebook("") - } catch (ignored: IllegalArgumentException) { - } + expectException { RealmCredentials.facebook("") } + expectException { RealmCredentials.facebook(TestHelper.getNull()) } } @Test fun google() { val creds = RealmCredentials.google("google-token") - assertEquals("google", creds.identityProvider) + assertEquals("oauth2-google", creds.identityProvider.id) assertTrue(creds.asJson().contains("google-token")) } @Test fun google_invalidInput() { - try { - RealmCredentials.google("") - } catch (ignored: IllegalArgumentException) { - } + expectException { RealmCredentials.google("") } + expectException { RealmCredentials.google(TestHelper.getNull()) } } + @Ignore("FIXME: Awaiting ObjectStore support") @Test fun jwt() { val creds = RealmCredentials.google("jwt-token") - assertEquals("jwt", creds.identityProvider) + assertEquals("jwt", creds.identityProvider.id) assertTrue(creds.asJson().contains("jwt-token")) } @Test fun jwt_invalidInput() { + expectException { RealmCredentials.jwt("") } + expectException { RealmCredentials.jwt(TestHelper.getNull()) } + } + + fun expectErrorCode(app: RealmApp, expectedCode: ErrorCode, credentials: RealmCredentials) { try { - RealmCredentials.google("") - } catch (ignored: IllegalArgumentException) { + app.login(credentials) + fail() + } catch (error: ObjectServerError) { + assertEquals(expectedCode, error.errorCode) } } + @Test + fun loginUsingCredentials() { + val app = TestRealmApp() + try { + RealmCredentials.IdentityProvider.values().forEach { provider -> + when(provider) { + RealmCredentials.IdentityProvider.ANONYMOUS -> { + val user = app.login(RealmCredentials.anonymous()) + assertNotNull(user) + } + RealmCredentials.IdentityProvider.API_KEY -> { + // FIXME: Wait for API Key support in OS +// val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") +// val key: RealmUserApiKey = app.apiKeyAuthProvider.createApiKey("my-key"); +// val apiKeyUser = app.login(RealmCredentials.apiKey(key.value!!)) +// assertNotNull(apiKeyUser) + } + RealmCredentials.IdentityProvider.CUSTOM_FUNCTION -> { + // FIXME Wait for Custom Function support + } + RealmCredentials.IdentityProvider.EMAIL_PASSWORD -> { + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuthProvider.registerUser(email, password) + val user = app.login(RealmCredentials.emailPassword(email, password)) + assertNotNull(user) + } + + // These providers are hard to test for real since they depend on a 3rd party + // login service. Instead we attempt to login and verify that a proper exception + // is thrown. At least that should verify that correctly formatted JSON is being + // sent across the wire. + RealmCredentials.IdentityProvider.FACEBOOK -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.facebook("facebook-token")) + } + RealmCredentials.IdentityProvider.APPLE -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.apple("apple-token")) + } + RealmCredentials.IdentityProvider.GOOGLE -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.google("google-token")) + } + RealmCredentials.IdentityProvider.JWT -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.jwt("jwt-token")) + } + RealmCredentials.IdentityProvider.UNKNOWN -> { + // Ignore + } + } + } + } finally { + app.close() + } + } } \ No newline at end of file diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index f719090f71..2d7fd3ba6e 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -152,7 +152,8 @@ endif() # -Wno-missing-field-initializers disable in object store as well. set(WARNING_CXX_FLAGS "-Werror -Wall -Wextra -pedantic -Wmissing-declarations \ -Wempty-body -Wparentheses -Wunknown-pragmas -Wunreachable-code \ - -Wno-missing-field-initializers -Wno-unevaluated-expression -Wno-unreachable-code") + -Wno-missing-field-initializers -Wno-unevaluated-expression -Wno-unreachable-code \ + -Wno-c99-extensions") set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -fdata-sections -pthread -frtti -fvisibility=hidden -fsigned-char -fno-stack-protector -std=c++17") if (build_SYNC) set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ENABLE_SYNC=1") diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp index b53a6f67b3..9bda5f4030 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp @@ -157,3 +157,24 @@ JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeRemoveUser(JNIEnv* env, } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLinkUser(JNIEnv* env, + jclass, + jlong j_app_ptr, + jlong j_user_ptr, + jlong j_credentials_ptr, + jobject j_callback) +{ + try { + App* app = reinterpret_cast(j_app_ptr); + auto user = *reinterpret_cast*>(j_user_ptr); + auto credentials = reinterpret_cast(j_credentials_ptr); + std::function)> mapper = [](JNIEnv* env, std::shared_ptr user) { + auto* java_user = new std::shared_ptr(std::move(user)); + return JavaClassGlobalDef::new_long(env, reinterpret_cast(java_user)); + }; + auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, mapper); + app->link_user(user, *credentials, callback); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp index a4f0cd456c..29a8d81e7c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp @@ -57,10 +57,18 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nati creds = AppCredentials::apple(id_token); break; } - case io_realm_internal_objectstore_OsAppCredentials_TYPE_API_KEY: + case io_realm_internal_objectstore_OsAppCredentials_TYPE_GOOGLE: { + JStringAccessor id_token(env, (jstring) env->GetObjectArrayElement(j_args, 0)); + creds = AppCredentials::google(id_token); + break; + } + case io_realm_internal_objectstore_OsAppCredentials_TYPE_JWT: { + JStringAccessor token(env, (jstring) env->GetObjectArrayElement(j_args, 0)); + creds = AppCredentials::custom(token); + break; + } case io_realm_internal_objectstore_OsAppCredentials_TYPE_CUSTOM_FUNCTION: - case io_realm_internal_objectstore_OsAppCredentials_TYPE_GOOGLE: - case io_realm_internal_objectstore_OsAppCredentials_TYPE_JWT: + case io_realm_internal_objectstore_OsAppCredentials_TYPE_API_KEY: default: throw std::runtime_error(util::format("Unknown credentials type: %1", j_type)); } diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index c2730fa706..7cecea181d 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -244,7 +244,7 @@ class JavaAccessorContext { // using the provided value. If `update` is true then upsert semantics // should be used for this. template - T unbox(util::Any& v, CreatePolicy=CreatePolicy::ForceCreate) const + T unbox(util::Any& v, CreatePolicy = CreatePolicy::Skip, ObjKey /*current_row*/ = ObjKey()) const { return any_cast(v); } @@ -344,35 +344,35 @@ inline JPrimitiveArrayAccessor::ElementsHolder::~ElementsHold } template <> -inline bool JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline bool JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Boolean"); return any_cast(v) == JNI_TRUE; } template <> -inline int64_t JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline int64_t JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Long"); return static_cast(any_cast(v)); } template <> -inline double JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline double JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Double"); return static_cast(any_cast(v)); } template <> -inline float JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline float JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Float"); return static_cast(any_cast(v)); } template <> -inline StringData JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline StringData JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { if (!v.has_value()) { return StringData(); @@ -382,7 +382,7 @@ inline StringData JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const } template <> -inline BinaryData JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline BinaryData JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { if (!v.has_value()) return BinaryData(); @@ -391,43 +391,43 @@ inline BinaryData JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const } template <> -inline Timestamp JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline Timestamp JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { return v.has_value() ? from_milliseconds(any_cast(v)) : Timestamp(); } template <> -inline Obj JavaAccessorContext::unbox(util::Any&, CreatePolicy) const +inline Obj JavaAccessorContext::unbox(util::Any&, CreatePolicy, ObjKey) const { REALM_TERMINATE("not supported"); } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { return v.has_value() ? util::make_optional(any_cast(v) == JNI_TRUE) : util::none; } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { return v.has_value() ? util::make_optional(static_cast(any_cast(v))) : util::none; } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { return v.has_value() ? util::make_optional(any_cast(v)) : util::none; } template <> -inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy) const +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { return v.has_value() ? util::make_optional(any_cast(v)) : util::none; } template <> -inline Mixed JavaAccessorContext::unbox(util::Any&, CreatePolicy) const +inline Mixed JavaAccessorContext::unbox(util::Any&, CreatePolicy, ObjKey) const { REALM_TERMINATE("not supported"); } diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp index 6ea59e0897..197c94199b 100644 --- a/realm/realm-library/src/main/cpp/java_object_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -330,17 +330,14 @@ class JavaContext { // This constructor is the only one used by the object accessor code, and is // used when recurring into a link or array property during object creation // (i.e. prop.type will always be Object or Array). - JavaContext(JavaContext& c, Property const& prop) - : m_env(c.m_env), - realm(c.realm) + JavaContext(JavaContext& c, Obj parent, Property const& prop) + : m_env(c.m_env) + , realm(c.realm) + , m_parent(std::move(parent)) + , m_property(&prop) , object_schema(prop.type == PropertyType::Object ? &*realm->schema().find(prop.object_type) : c.object_schema) { } - bool is_embedded() const - { - return object_schema ? bool(object_schema->is_embedded) : false; - } - // The use of util::Optional for the following two functions is not a hard // requirement; only that it be some type which can be evaluated in a // boolean context to determine if it contains a value, and if it does @@ -453,9 +450,13 @@ class JavaContext { // mimick this behavior so just return false here. bool allow_missing(JavaValue const&) const { return false; } + Obj create_embedded_object(); + private: JNIEnv* m_env; std::shared_ptr realm; + Obj m_parent; + const Property* m_property = nullptr; const ObjectSchema* object_schema = nullptr; inline void check_value_not_null(JavaValue const& v, const char* expected_type) const @@ -524,7 +525,7 @@ inline Obj JavaContext::unbox(JavaValue const& v, CreatePolicy policy, ObjKey cu { if (v.get_type() == JavaValueType::Object) { return *v.get_object(); - } else if (policy == CreatePolicy::Skip) { + } else if (!policy.create) { return Obj(); } REALM_ASSERT(object_schema); @@ -573,13 +574,10 @@ inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePoli return v.has_value() ? util::make_optional(v.get_decimal128()) : util::none; } -inline Obj JavaContext::unbox_embedded(JavaValue const& v, CreatePolicy policy, Obj& parent, ColKey col, size_t ndx) const -{ - return Object::create_embedded(const_cast(*this), realm, *object_schema, v, policy, parent, col, ndx).obj(); +inline Obj JavaContext::create_embedded_object() { + return m_parent.create_and_set_linked_object(m_property->column_key); } - - } #endif // REALM_JAVA_OBJECT_ACCESSOR_HPP diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index f18b240b8b..1577211c19 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit f18b240b8bf592e5d5ae8045c5444f423b6d8e9d +Subproject commit 1577211c1959b60b10a967579dd2aaef2f571149 diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index 948a0313e4..50f40415fa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -36,6 +36,7 @@ import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.internal.network.OkHttpNetworkTransport; import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.internal.objectstore.OsSyncUser; import io.realm.log.RealmLog; import io.realm.mongodb.RealmMongoDBService; @@ -154,6 +155,80 @@ public RealmUser switchUser(RealmUser user) { return user; } + /** + * Links the current user with a new user identity represented by the given credentials. + *

            + * Linking a user with more credentials, mean the user can login either of these credentials. + * It also makes it possible to "upgrade" an anonymous user by linking it with e.g. + * Email/Password credentials. + *

            +     * {@code
            +     * // Example
            +     * RealmApp app = new RealmApp("app-id")
            +     * RealmUser user = app.login(RealmCredentials.anonymous());
            +     * app.linkUser(RealmCredentials.emailPassword("email", "password"));
            +     * }
            +     * 
            + *

            + * Note: It is not possible to link two existing users of MongoDB Realm. The provided credentials + * must not have been used by another user. + * + * @param credentials the credentials to link with the current user. + * @throws IllegalStateException if no user is currently logged in. + * @return the {@link io.realm.RealmUser} the credentials were linked to. + */ + public RealmUser linkUser(RealmCredentials credentials) { + Util.checkNull(credentials, "credentials"); + final RealmUser user = currentUser(); + if (user == null) { + throw new IllegalStateException("No user is logged in"); + } + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + nativeLinkUser(nativePtr, user.osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { + @Override + protected RealmUser mapSuccess(Object result) { + user.osUser = new OsSyncUser((long) result); // OS returns the updated user as a new one. + return user; + } + }); + return handleResult(success, error); + } + + + /** + * Links the current user with a new user identity represented by the given credentials. + *

            + * Linking a user with more credentials, mean the user can login either of these credentials. + * It also makes it possible to "upgrade" an anonymous user by linking it with e.g. + * Email/Password credentials. + *

            +     * {@code
            +     * // Example
            +     * RealmApp app = new RealmApp("app-id")
            +     * RealmUser user = app.login(RealmCredentials.anonymous());
            +     * app.linkUser(RealmCredentials.emailPassword("email", "password"));
            +     * }
            +     * 
            + *

            + * Note: It is not possible to link two existing users of MongoDB Realm. The provided credentials + * must not have been used by another user. + * + * @param credentials the credentials to link with the current user. + * @param callback callback when user identities has been linked or it failed. The callback will + * always happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask linkUserAsync(RealmCredentials credentials, Callback callback) { + Util.checkLooperThread("Asynchronous linking identities is only possible from looper threads."); + return new Request(NETWORK_POOL_EXECUTOR, callback) { + @Override + public RealmUser run() throws ObjectServerError { + return linkUser(credentials); + } + }.start(); + } + /** * Removes a users credentials from this device. If the user was currently logged in, they * will be logged out as part of the process. This is only a local change and does not @@ -198,11 +273,20 @@ public RealmUser run() throws ObjectServerError { } /** - * FIXME + * Logs in as a user with the given credentials associated with an authentication provider. + *

            + * The user who logs in becomes the current user. Other RealmApp functionality acts on behalf of + * the current user. + *

            + * If there was already a current user, that user is still logged in and can be found in the + * list returned by {@link #allUsers()}. + *

            + * It is also possible to switch between which user is considered the current user by using + * {@link #switchUser(RealmUser)}. * - * @param credentials - * @return - * @throws ObjectServerError + * @param credentials the credentials representing the type of login. + * @return a {@link RealmUser} representing the logged in user. + * @throws ObjectServerError if the user could not be logged in. */ public RealmUser login(RealmCredentials credentials) throws ObjectServerError { Util.checkNull(credentials, "credentials"); @@ -219,13 +303,24 @@ protected RealmUser mapSuccess(Object result) { } /** - * FIXME - * @param credentials - * @param callback - * @return + * Logs in as a user with the given credentials associated with an authentication provider. + *

            + * The user who logs in becomes the current user. Other RealmApp functionality acts on behalf of + * the current user. + *

            + * If there was already a current user, that user is still logged in and can be found in the + * list returned by {@link #allUsers()}. + *

            + * It is also possible to switch between which user is considered the current user by using + * {@link #switchUser(RealmUser)}. + * + * @param credentials the credentials representing the type of login. + * @param callback callback when logging in has completed or failed. The callback will always + * happen on the same thread as this method is called on. + * @throws IllegalStateException if not called on a looper thread. */ - public RealmAsyncTask loginAsync(RealmCredentials credentials, Callback callback) { - Util.checkLooperThread("Asynchronous login is only possible from looper threads."); + public RealmAsyncTask loginAsync(RealmCredentials credentials, Callback callback) { + Util.checkLooperThread("Asynchronous log in is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public RealmUser run() throws ObjectServerError { @@ -652,4 +747,5 @@ public interface Callback { private static native void nativeLogOut(long appNativePtr, long userNativePtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); private static native void nativeSwitchUser(long nativeAppPtr, long nativeUserPtr); private static native void nativeRemoveUser(long nativeAppPtr, long nativeUserPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeLinkUser(long nativeAppPtr, long nativeUserPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java index a71970c5a6..8d5c310774 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java @@ -19,61 +19,47 @@ import io.realm.internal.Util; import io.realm.internal.objectstore.OsAppCredentials; - /** - * FIXME: Revisit this description when all providers are implemented. - * - * Credentials represent a login with a 3rd party login provider in an OAuth2 login flow, and are used by the Realm - * Object Server to verify the user and grant access. + * Credentials represent a login with a given login provider, and are used by the MongoDB Realm to + * verify the user and grant access. The {@link IdentityProvider#EMAIL_PASSWORD} provider is enabled + * by default. All other providers must be enabled on MongoDB Realm to work. *

            - * Logging into the Realm Object Server consists of the following steps: - *

              - *
            1. - * Log in to 3rd party provider (Facebook or Google). The result is usually an Authorization Grant that must be - * saved in a {@link RealmCredentials} object of the proper type e.g., {@link RealmCredentials#facebook(String)} for a - * Facebook login. - *
            2. - *
            3. - * Authenticate a {@link RealmUser} through the Object Server using these credentials. Once authenticated, - * an Object Server user is returned. Then this user can be attached to a {@link io.realm.SyncConfiguration}, which - * will make it possible to synchronize data between the local and remote Realm. - *

              - * It is possible to persist the user object e.g., using the {@link UserStore}. That means, logging - * into an OAuth2 provider is only required the first time the app is used. - *

            4. - *
            - * + * Note that users wanting to login using Email/Password must register first using + * {@link io.realm.EmailPasswordAuthProvider#registerUser(String, String)}. + *

            + * Credentials are used the following way: *
              * {@code
              * // Example
            - *
            - * Credentials credentials = Credentials.facebook(getFacebookToken());
            - * User.login(credentials, "http://objectserver.realm.io/auth", new User.Callback() {
            - *     \@Override
            - *     public void onSuccess(User user) {
            - *          // User is now authenticated and be be used to open Realms.
            - *     }
            - *
            - *     \@Override
            - *     public void onError(ObjectServerError error) {
            - *
            + * RealmApp app = new RealmApp("app-id");
            + * RealmCredentials credentials = RealmCredentials.emailPassword("email", "password");
            + * RealmUser user = app.loginAsync(credentials, new RealmApp.Callback() {
            + *   \@Override
            + *   public void onResult(Result result) {
            + *     if (result.isSuccess() {
            + *       handleLogin(result.get());
            + *     } else {
            + *       handleError(result.getError());
              *     }
            - * });
            + *   }
            + * ));
              * }
              * 
            + * @see
            Authentication Providers */ public class RealmCredentials { OsAppCredentials osCredentials; /** - * FIXME - * Creates credentials anonymously. - * - * Note: logging the user out again means that data is lost with no means of recovery - * and it isn't possible to share the user details across devices. + * Creates credentials representing an anonymous user. + *

            + * Logging the user out again means that data is lost with no means of recovery + * and it isn't possible to share the user details across devices. + *

            + * The anonymous user must be linked to another real user to preserve data after a log out. * - * @return a set of credentials that can be used to log into the Object Server using + * @return a set of credentials that can be used to log into MongoDB Realm using * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. */ public static RealmCredentials anonymous() { @@ -81,77 +67,101 @@ public static RealmCredentials anonymous() { } /** - * FIXME + * Creates credentials representing a login using an API key. + *

            + * This provider must be enabled on MongoDB Realm to work. + * + * @param key the API key to use for login. + * @return a set of credentials that can be used to log into MongoDB Realm using + * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. */ public static RealmCredentials apiKey(String key) { - assertStringNotEmpty(key, "id"); + Util.checkEmpty(key, "id"); return new RealmCredentials(OsAppCredentials.apiKey(key)); } /** - * FIXME + * Creates credentials representing a login using an Apple ID token. + *

            + * This provider must be enabled on MongoDB Realm to work. + * + * @param idToken the ID token generated when using your Apple login. + * @return a set of credentials that can be used to log into MongoDB Realm using + * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. */ public static RealmCredentials apple(String idToken) { - assertStringNotEmpty(idToken, "idToken"); + Util.checkEmpty(idToken, "idToken"); return new RealmCredentials(OsAppCredentials.apple(idToken)); } /** * FIXME + *

            + * This provider must be enabled on MongoDB Realm to work. + * + * @return a set of credentials that can be used to log into MongoDB Realm using + * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. */ public static RealmCredentials customFunction(String functionName, Object... arguments) { -// assertStringNotEmpty(idToken, "idToken"); + // FIXME: How to check arguments? + Util.checkEmpty(functionName, "functionName"); return new RealmCredentials(OsAppCredentials.customFunction(functionName, arguments)); } /** - * FIXME + * Creates credentials representing a login using email and password. + * + * @param email email of the user logging in. + * @param password password of the user logging in. + * @return a set of credentials that can be used to log into MongoDB Realm using + * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. */ public static RealmCredentials emailPassword(String email, String password) { - assertStringNotEmpty(email, "email"); - assertStringNotEmpty(password, "password"); + Util.checkEmpty(email, "email"); + Util.checkEmpty(password, "password"); return new RealmCredentials(OsAppCredentials.emailPassword(email, password)); } /** - * FIXME - * Creates credentials based on a Facebook login. + * Creates credentials representing a login using an Facebook access token. + *

            + * This provider must be enabled on MongoDB Realm to work. * - * @param accessToken a facebook userIdentifier acquired by logging into Facebook. - * @return a set of credentials that can be used to log into the Object Server using + * @param accessToken the access token returned when logging in to Facebook. + * @return a set of credentials that can be used to log into MongoDB Realm using * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. - * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static RealmCredentials facebook(String accessToken) { - assertStringNotEmpty(accessToken, "accessToken"); + Util.checkEmpty(accessToken, "accessToken"); return new RealmCredentials(OsAppCredentials.facebook(accessToken)); } /** - * FIXME - * Creates credentials based on a Google login. + * Creates credentials representing a login using an Google access token. + *

            + * This provider must be enabled on MongoDB Realm to work. * - * @param googleToken a google userIdentifier acquired by logging into Google. - * @return a set of credentials that can be used to log into the Object Server using + * @param googleToken the access token returned when logging in to Google. + * @return a set of credentials that can be used to log into MongoDB Realm using * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. - * @throws IllegalArgumentException if user name is either {@code null} or empty. */ public static RealmCredentials google(String googleToken) { - assertStringNotEmpty(googleToken, "googleToken"); + Util.checkEmpty(googleToken, "googleToken"); return new RealmCredentials(OsAppCredentials.google(googleToken)); } /** - * FIXME - * Creates credentials based on a JSON Web Token (JWT). + * Creates credentials representing a login using an JWT Token. This token is normally generated + * after a custom OAuth2 login flow. + *

            + * This provider must be enabled on MongoDB Realm to work. * - * @param jwtToken a JWT token that identifies the user. - * @return a set of credentials that can be used to log into the Object Server using + * @param jwtToken the jwt token returned after a custom login to a another service. + * @return a set of credentials that can be used to log into MongoDB Realm using * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. - * @throws IllegalArgumentException if the token is either {@code null} or empty. */ public static RealmCredentials jwt(String jwtToken) { - assertStringNotEmpty(jwtToken, "jwtToken"); + Util.checkEmpty(jwtToken, "jwtToken"); return new RealmCredentials(OsAppCredentials.jwt(jwtToken)); } @@ -173,64 +183,34 @@ public String asJson() { return osCredentials.asJson(); } - private static void assertStringNotEmpty(String string, String message) { - //noinspection ConstantConditions - if (Util.isEmptyString(string)) { - throw new IllegalArgumentException("Non-null '" + message + "' required."); - } - } - private RealmCredentials(OsAppCredentials credentials) { this.osCredentials = credentials; } /** - * FIXME + * This enum contains the list of identity providers supported by MongoDB Realm. + * All of these except {@link #EMAIL_PASSWORD} must be enabled manually on MongoDB Realm to + * work. + * + * @see Authentication Providers */ public enum IdentityProvider { - /** - * FIXME - */ ANONYMOUS("anon-user"), - /** - * FIXME - */ API_KEY(""), // FIXME - /** - * FIXME - */ APPLE("oauth2-apple"), - /** - * FIXME - */ CUSTOM_FUNCTION(""), // FIXME - /** - * FIXME - */ EMAIL_PASSWORD("local-userpass"), - - /** - * FIXME - */ FACEBOOK("oauth2-facebook"), - /** - * FIXME - */ GOOGLE("oauth2-google"), - /** - * FIXME - */ JWT("jwt"), - /** - * FIXME - */ UNKNOWN(""); /** - * FIXME + * Create the identity provider from the ID string returned by MongoDB Realm. * * @param id the string identifier for the provider - * @return + * @return the enum representing the provider or {@link #UNKNOWN} if no matching provider + * was found. */ public static IdentityProvider fromId(String id) { for (IdentityProvider value : values()) { @@ -248,7 +228,7 @@ public static IdentityProvider fromId(String id) { } /** - * FIXME + * Return the string presentation of this identity provider. */ public String getId() { return id; diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java index eb59d76ef3..236e8bb6d7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -28,7 +28,7 @@ */ public class RealmUser { - final OsSyncUser osUser; + OsSyncUser osUser; private final RealmApp app; /** From 5a8a7182096791ba676ac7a25beefa1b5c983eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Wed, 15 Apr 2020 09:24:41 +0200 Subject: [PATCH 1491/2110] Reverted to minSdkVersion 16 and added support for multidexing. Can't use latest version though, TODO added with explanation in that regard (#6800) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eduardo López --- realm/build.gradle | 2 +- realm/realm-library/build.gradle | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/realm/build.gradle b/realm/build.gradle index 920d352cb7..3a314774e9 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -34,7 +34,7 @@ allprojects { projectDependencies.each { key, val -> project.ext.set(key, val) } - project.ext.minSdkVersion = 21 // FIXME: Should be 16. Figure out how to enable MultiDex for ObjectServer tests + project.ext.minSdkVersion = 16 project.ext.compileSdkVersion = 29 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") group = 'io.realm' diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index a875afa2f4..2ff5c31131 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -47,6 +47,7 @@ android { versionName version project.archivesBaseName = "realm-android-library" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true externalNativeBuild { cmake { arguments "-DREALM_CORE_DIST_DIR:STRING=${project.coreDir.getAbsolutePath()}", @@ -210,6 +211,11 @@ dependencies { exclude group: 'io.reactivex.rxjava2', module: 'rxjava' } + // TODO: investigate why we can't use the latest multidex version + // check baseDebugAndroidTestRuntimeClasspath and objectServerDebugAndroidTestRuntimeClasspath + // tasks as they introduce version 2.0.0 strictly, even when specifying 2.0.1 from here + androidTestImplementation "androidx.multidex:multidex:2.0.0" + kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 objectServerImplementation 'com.squareup.okhttp3:okhttp:3.12.0' // Going above this requires minSDK 21 objectServerImplementation "org.mongodb:bson:3.12.0" From 547e9e5b0cc297c4cec0b991e5bf5ccf818d02a2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 17 Apr 2020 23:35:51 +0200 Subject: [PATCH 1492/2110] Release SNAPSHOT builds from v10 (#6806) --- Jenkinsfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index acc93e005b..06cdcd0f95 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,6 +6,7 @@ def buildSuccess = false def mongoDbRealmContainer = null def mongoDbRealmCommandServerContainer = null def dockerNetworkId = UUID.randomUUID().toString() +def releaseBranches = ['master', 'next-major', 'v10'] // Branches from which we release SNAPSHOT's try { node('android') { timeout(time: 90, unit: 'MINUTES') { @@ -31,7 +32,7 @@ try { // on PR's for even more throughput. def abiFilter = "" def instrumentationTestTarget = "connectedAndroidTest" - if (!['master', 'next-major'].contains(env.BRANCH_NAME)) { + if (!releaseBranches.contains(env.BRANCH_NAME)) { abiFilter = "-PbuildTargetABIs=armeabi-v7a" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting @@ -140,7 +141,7 @@ try { } } - if (['master', 'next-major'].contains(env.BRANCH_NAME)) { + if (releaseBranches.contains(env.BRANCH_NAME)) { stage('Publish to OJO') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" From c121e970b2c1dd4c2c7044388bbf90ffd2a2f49c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 20 Apr 2020 10:47:17 +0200 Subject: [PATCH 1493/2110] RealmUser/RealmApp API reorganisation (#6796) --- .../io/realm/ApiKeyAuthProviderTests.kt | 2 +- .../realm/EmailPasswordAuthProviderTests.kt | 8 +- .../kotlin/io/realm/RealmAppTests.kt | 213 ---------------- .../kotlin/io/realm/RealmUserTests.kt | 213 +++++++++++++++- .../realm-library/src/main/cpp/CMakeLists.txt | 2 + .../src/main/cpp/io_realm_RealmApp.cpp | 35 --- .../src/main/cpp/io_realm_RealmUser.cpp | 74 ++++++ realm/realm-library/src/main/cpp/object-store | 2 +- realm/realm-library/src/main/cpp/util.cpp | 8 +- .../objectServer/java/io/realm/RealmApp.java | 233 +----------------- .../objectServer/java/io/realm/RealmUser.java | 226 +++++++++++++++-- 11 files changed, 499 insertions(+), 517 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt index de05c86f90..a18429a044 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt @@ -72,7 +72,7 @@ class ApiKeyAuthProviderTests { RealmLog.setLevel(LogLevel.DEBUG) admin = ServerAdmin() user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - provider = app.apiKeyAuthProvider + provider = user.apiKeyAuthProvider } @After diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt index 53749d76d8..e5494620b0 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt @@ -350,8 +350,8 @@ class EmailPasswordAuthProviderTests { provider.registerUser(email, "123456") try { provider.callResetPasswordFunction(email, "new-password", "say-the-magic-word", 42) - app.login(RealmCredentials.emailPassword(email, "new-password")) - app.logOut() + val user = app.login(RealmCredentials.emailPassword(email, "new-password")) + user.logOut() } finally { admin.setResetFunction(enabled = false) } @@ -369,8 +369,8 @@ class EmailPasswordAuthProviderTests { "new-password", arrayOf("say-the-magic-word", 42)) { result -> if (result.isSuccess) { - app.login(RealmCredentials.emailPassword(email, "new-password")) - app.logOut() + val user = app.login(RealmCredentials.emailPassword(email, "new-password")) + user.logOut() looperThread.testComplete() } else { fail(result.error.toString()) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt index fda4d17ca6..5347fabd75 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -207,217 +207,4 @@ class RealmAppTests { TODO("FIXME") } - @Test - fun removeUser() { - // Removing logged in user - val user1 = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(user1, app.currentUser()) - assertEquals(1, app.allUsers().size) - app.removeUser(user1) - assertEquals(RealmUser.State.REMOVED, user1.state) - assertNull(app.currentUser()) - assertEquals(0, app.allUsers().size) - - // Remove logged out user - val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - user2.logOut() - assertNull(app.currentUser()) - assertEquals(1, app.allUsers().size) - app.removeUser(user2) - assertEquals(RealmUser.State.REMOVED, user2.state) - assertEquals(0, app.allUsers().size) - } - - @Test - fun removeUser_nullThrows() { - try { - app.removeUser(TestHelper.getNull()) - fail() - } catch (ignore: IllegalArgumentException) { - } - } - - @Test - fun removeUserAsync() { - // Removing logged in user - looperThread.runBlocking { - val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(user, app.currentUser()) - assertEquals(1, app.allUsers().size) - app.removeUserAsync(user) { result -> - assertEquals(RealmUser.State.REMOVED, result.orThrow.state) - assertNull(app.currentUser()) - assertEquals(0, app.allUsers().size) - looperThread.testComplete() - } - } - - // Removing logged out user - looperThread.runBlocking { - val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - user.logOut() - assertNull(app.currentUser()) - assertEquals(1, app.allUsers().size) - app.removeUserAsync(user) { result -> - assertEquals(RealmUser.State.REMOVED, result.orThrow.state) - assertEquals(0, app.allUsers().size) - looperThread.testComplete() - } - } - } - - @Test - fun removeUserAsync_nonLooperThreadThrows() { - val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "1234567") - try { - app.removeUserAsync(user) { fail() } - } catch (ignore: IllegalStateException) { - } - } - - @Test - fun logOut() { - // Anonymous users are removed upon log out - val user1: RealmUser = app.login(RealmCredentials.anonymous()) - assertEquals(user1, app.currentUser()) - app.logOut() - assertEquals(RealmUser.State.REMOVED, user1.state) - assertNull(app.currentUser()) - - // Users registered with Email/Password will register as Logged Out - val user2: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(user2, app.currentUser()) - app.logOut() - assertEquals(RealmUser.State.LOGGED_OUT, user2.state) - assertNull(app.currentUser()) - } - - @Test - fun logOutAsync() = looperThread.runBlocking { - val user: RealmUser = app.login(RealmCredentials.anonymous()) - assertEquals(user, app.currentUser()) - app.logOutAsync() { result -> - val callbackUser: RealmUser = result.orThrow - assertNull(app.currentUser()) - assertEquals(user, callbackUser) - assertEquals(RealmUser.State.REMOVED, user.state) - assertEquals(RealmUser.State.REMOVED, callbackUser.state) - looperThread.testComplete() - } - } - - @Test - fun logOutAsync_throwsOnNonLooperThread() { - val user: RealmUser = app.login(RealmCredentials.anonymous()) - assertEquals(user, app.currentUser()) - val callback = RealmApp.Callback { fail("Method should throw") } - try { - app.logOutAsync(callback) - fail() - } catch (ignore: IllegalStateException) { - } - } - - @Ignore("FIXME: Wait for linkUser support in ObjectStore") - @Test - fun linkUser() { - admin.setAutomaticConfirmation(enabled = false) - val user: RealmUser = app.login(RealmCredentials.anonymous()) - assertEquals(1, user.identities.size) - val email = TestHelper.getRandomEmail() - val password = "123456" - app.emailPasswordAuthProvider.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled - val linkedUser: RealmUser = app.linkUser(RealmCredentials.emailPassword(email, password)) - assertTrue(user === linkedUser) - assertEquals(2, linkedUser.identities.size) - assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) - admin.setAutomaticConfirmation(enabled = true) - } - - @Ignore("FIXME: Wait for linkUser support in ObjectStore") - @Test - fun linkUser_existingCredentialsThrows() { - admin.setAutomaticConfirmation(enabled = false) - val email = TestHelper.getRandomEmail() - val password = "123456" - val emailUser: RealmUser = app.registerUserAndLogin(email, password) - val anonymousUser: RealmUser = app.login(RealmCredentials.anonymous()) - try { - app.linkUser(RealmCredentials.emailPassword(email, password)) - fail() - } catch (ex: ObjectServerError) { - assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) - } - } - - @Ignore("FIXME: Wait for linkUser support in ObjectStore") - @Test - fun linkUser_noCurrentUserThrows() { - try { - app.linkUser(RealmCredentials.emailPassword(TestHelper.getRandomEmail(), "123456")) - fail() - } catch (ignore: IllegalStateException) { - } - } - - @Ignore("FIXME: Wait for linkUser support in ObjectStore") - @Test - fun linkUser_invalidArgsThrows() { - try { - app.linkUser(TestHelper.getNull()) - fail() - } catch (ignore: IllegalArgumentException) { - } - } - - @Ignore("FIXME: Wait for linkUser support in ObjectStore") - @Test - fun linkUserAsync() { - admin.setAutomaticConfirmation(enabled = false) - val user: RealmUser = app.login(RealmCredentials.anonymous()) - assertEquals(1, user.identities.size) - val email = TestHelper.getRandomEmail() - val password = "123456" - app.emailPasswordAuthProvider.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled - looperThread.runBlocking { - app.linkUserAsync(RealmCredentials.emailPassword(email, password)) { result -> - val linkedUser: RealmUser = result.orThrow - assertTrue(user === linkedUser) - assertEquals(2, linkedUser.identities.size) - assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) - admin.setAutomaticConfirmation(enabled = true) - } - } - } - - @Ignore("FIXME: Wait for linkUser support in ObjectStore") - @Test - fun linkUserAsync_throwsOnNonLooperThread() { - val user: RealmUser = app.login(RealmCredentials.anonymous()) - try { - app.linkUserAsync(RealmCredentials.emailPassword(TestHelper.getRandomEmail(), "123456")) { fail() } - fail() - } catch (ignore: java.lang.IllegalStateException) { - } - - } - - @Test - fun getApiKeyAuthProvider() { - val user1: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - val provider1: ApiKeyAuthProvider = app.apiKeyAuthProvider - val user2: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - val provider2: ApiKeyAuthProvider = app.apiKeyAuthProvider - - assertNotEquals(provider1, provider2) - user2.logOut() - assertEquals(provider1, app.apiKeyAuthProvider) - user1.logOut() - try { - app.apiKeyAuthProvider - fail() - } catch (ignore: IllegalStateException) { - } - } - } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt index ba42476e15..cbe0b000f7 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt @@ -16,16 +16,17 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.admin.ServerAdmin import io.realm.rule.BlockingLooperThread import io.realm.rule.RunInLooperThread import io.realm.rule.RunTestInLooperThread import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.fail +import org.junit.Assert.* import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import java.lang.IllegalArgumentException @RunWith(AndroidJUnit4::class) class RealmUserTests { @@ -34,10 +35,12 @@ class RealmUserTests { private lateinit var app: RealmApp private lateinit var anonUser: RealmUser + private lateinit var admin: ServerAdmin @Before fun setUp() { app = TestRealmApp() + admin = ServerAdmin() anonUser = app.login(RealmCredentials.anonymous()) } @@ -58,25 +61,215 @@ class RealmUserTests { assertEquals(RealmUser.State.REMOVED, anonUser.state) } - @Ignore("Add test when registerUser works") @Test fun getState_emailUser() { - TODO("Implement when we implement registerUser") + val emailUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(RealmUser.State.LOGGED_IN, emailUser.state) + emailUser.logOut() + assertEquals(RealmUser.State.LOGGED_OUT, emailUser.state) + emailUser.removeUser() + assertEquals(RealmUser.State.REMOVED, emailUser.state) } @Test fun logOut() { - anonUser.logOut() - assertEquals(RealmUser.State.REMOVED, anonUser.state) + anonUser.logOut(); // Remove user created for other tests + + // Anonymous users are removed upon log out + val user1: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(user1, app.currentUser()) + user1.logOut() + assertEquals(RealmUser.State.REMOVED, user1.state) + assertNull(app.currentUser()) + + // Users registered with Email/Password will register as Logged Out + val user2: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(user2, app.currentUser()) + user2.logOut() + assertEquals(RealmUser.State.LOGGED_OUT, user2.state) + assertNull(app.currentUser()) } @Test fun logOutAsync() = looperThread.runBlocking { - anonUser.logOutAsync { - when(it.isSuccess) { - true -> looperThread.testComplete() - false -> fail(it.error.toString()) + assertEquals(anonUser, app.currentUser()) + anonUser.logOutAsync() { result -> + val callbackUser: RealmUser = result.orThrow + assertNull(app.currentUser()) + assertEquals(anonUser, callbackUser) + assertEquals(RealmUser.State.REMOVED, anonUser.state) + assertEquals(RealmUser.State.REMOVED, callbackUser.state) + looperThread.testComplete() + } + } + + @Test + fun logOutAsync_throwsOnNonLooperThread() { + val user: RealmUser = app.login(RealmCredentials.anonymous()) + try { + user.logOutAsync { fail() } + fail() + } catch (ignore: IllegalStateException) { + } + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUser() { + admin.setAutomaticConfirmation(enabled = false) + val anonUser: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(1, anonUser.identities.size) + + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuthProvider.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled + var linkedUser: RealmUser = anonUser.linkUser(RealmCredentials.emailPassword(email, password)) + assertTrue(anonUser === linkedUser) + assertEquals(2, linkedUser.identities.size) + assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) + admin.setAutomaticConfirmation(enabled = true) + + val otherEmail = TestHelper.getRandomEmail() + val otherPassword = "123456" + app.emailPasswordAuthProvider.registerUser(otherEmail, otherPassword) + linkedUser = anonUser.linkUser(RealmCredentials.emailPassword(email, password)) + assertTrue(anonUser === linkedUser) + assertEquals(3, linkedUser.identities.size) + assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[2].provider) + admin.setAutomaticConfirmation(enabled = true) + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUser_existingCredentialsThrows() { + val email = TestHelper.getRandomEmail() + val password = "123456" + val emailUser: RealmUser = app.registerUserAndLogin(email, password) + val anonymousUser: RealmUser = app.login(RealmCredentials.anonymous()) + try { + anonymousUser.linkUser(RealmCredentials.emailPassword(email, password)) + fail() + } catch (ex: ObjectServerError) { + assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) + } + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUser_invalidArgsThrows() { + try { + anonUser.linkUser(TestHelper.getNull()) + fail() + } catch (ignore: IllegalArgumentException) { + } + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUserAsync() { + admin.setAutomaticConfirmation(enabled = false) + val user: RealmUser = app.login(RealmCredentials.anonymous()) + assertEquals(1, user.identities.size) + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuthProvider.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled + looperThread.runBlocking { + anonUser.linkUserAsync(RealmCredentials.emailPassword(email, password)) { result -> + val linkedUser: RealmUser = result.orThrow + assertTrue(user === linkedUser) + assertEquals(2, linkedUser.identities.size) + assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) + admin.setAutomaticConfirmation(enabled = true) + } + } + } + + @Ignore("FIXME: Wait for linkUser support in ObjectStore") + @Test + fun linkUserAsync_throwsOnNonLooperThread() { + try { + anonUser.linkUserAsync(RealmCredentials.emailPassword(TestHelper.getRandomEmail(), "123456")) { fail() } + fail() + } catch (ignore: java.lang.IllegalStateException) { + } + } + + @Test + fun removeUser() { + anonUser.logOut() // Remove user used by other tests + + // Removing logged in user + val user1 = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(user1, app.currentUser()) + assertEquals(1, app.allUsers().size) + user1.removeUser() + assertEquals(RealmUser.State.REMOVED, user1.state) + assertNull(app.currentUser()) + assertEquals(0, app.allUsers().size) + + // Remove logged out user + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + user2.logOut() + assertNull(app.currentUser()) + assertEquals(1, app.allUsers().size) + user2.removeUser() + assertEquals(RealmUser.State.REMOVED, user2.state) + assertEquals(0, app.allUsers().size) + } + + @Test + fun removeUserAsync() { + anonUser.logOut() // Remove user used by other tests + + // Removing logged in user + looperThread.runBlocking { + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(user, app.currentUser()) + assertEquals(1, app.allUsers().size) + user.removeUserAsync { result -> + assertEquals(RealmUser.State.REMOVED, result.orThrow.state) + assertNull(app.currentUser()) + assertEquals(0, app.allUsers().size) + looperThread.testComplete() + } + } + + // Removing logged out user + looperThread.runBlocking { + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + user.logOut() + assertNull(app.currentUser()) + assertEquals(1, app.allUsers().size) + user.removeUserAsync { result -> + assertEquals(RealmUser.State.REMOVED, result.orThrow.state) + assertEquals(0, app.allUsers().size) + looperThread.testComplete() } } } + + @Test + fun removeUserAsync_nonLooperThreadThrows() { + val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "1234567") + try { + user.removeUserAsync { fail() } + } catch (ignore: IllegalStateException) { + } + } + + @Test + fun getApiKeyAuthProvider() { + val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val provider1: ApiKeyAuthProvider = user.apiKeyAuthProvider + assertEquals(user, provider1.user) + + user.logOut() + + try { + user.apiKeyAuthProvider + fail() + } catch (ex: IllegalStateException) { + } + } + } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 2d7fd3ba6e..018defd85e 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -94,6 +94,7 @@ if (build_SYNC) io.realm.EmailPasswordAuthProvider io.realm.ApiKeyAuthProvider io.realm.RealmApp + io.realm.RealmUser io.realm.SyncManager io.realm.SyncSession io.realm.SyncUser @@ -182,6 +183,7 @@ file(GLOB jni_SRC if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmApp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmUser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_EmailPasswordAuthProvider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ApiKeyAuthProvider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp index 9bda5f4030..85eb37b684 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp @@ -143,38 +143,3 @@ JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeSwitchUser(JNIEnv* env, } CATCH_STD() } - -JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeRemoveUser(JNIEnv* env, - jclass, - jlong j_app_ptr, - jlong j_user_ptr, - jobject j_callback) -{ - try { - App* app = reinterpret_cast(j_app_ptr); - auto user = *reinterpret_cast*>(j_user_ptr); - app->remove_user(user, JavaNetworkTransport::create_void_callback(env, j_callback)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLinkUser(JNIEnv* env, - jclass, - jlong j_app_ptr, - jlong j_user_ptr, - jlong j_credentials_ptr, - jobject j_callback) -{ - try { - App* app = reinterpret_cast(j_app_ptr); - auto user = *reinterpret_cast*>(j_user_ptr); - auto credentials = reinterpret_cast(j_credentials_ptr); - std::function)> mapper = [](JNIEnv* env, std::shared_ptr user) { - auto* java_user = new std::shared_ptr(std::move(user)); - return JavaClassGlobalDef::new_long(env, reinterpret_cast(java_user)); - }; - auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, mapper); - app->link_user(user, *credentials, callback); - } - CATCH_STD() -} diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp new file mode 100644 index 0000000000..7af12209d8 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_RealmUser.h" + +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" + +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::jni_util; +using namespace realm::_impl; + +JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeLinkUser(JNIEnv* env, + jclass, + jlong j_app_ptr, + jlong j_user_ptr, + jlong j_credentials_ptr, + jobject j_callback) +{ + try { + App* app = reinterpret_cast(j_app_ptr); + auto user = *reinterpret_cast*>(j_user_ptr); + auto credentials = reinterpret_cast(j_credentials_ptr); + std::function)> mapper = [](JNIEnv* env, std::shared_ptr user) { + auto* java_user = new std::shared_ptr(std::move(user)); + return JavaClassGlobalDef::new_long(env, reinterpret_cast(java_user)); + }; + auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, mapper); + app->link_user(user, *credentials, callback); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeRemoveUser(JNIEnv* env, + jclass, + jlong j_app_ptr, + jlong j_user_ptr, + jobject j_callback) +{ + try { + App* app = reinterpret_cast(j_app_ptr); + auto user = *reinterpret_cast*>(j_user_ptr); + app->remove_user(user, JavaNetworkTransport::create_void_callback(env, j_callback)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) +{ + try { + App* app = reinterpret_cast(j_app_ptr); + auto user = *reinterpret_cast*>(j_user_ptr); + app->log_out(user, JavaNetworkTransport::create_void_callback(env, j_callback)); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 1577211c19..88138d8109 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 1577211c1959b60b10a967579dd2aaef2f571149 +Subproject commit 88138d8109e4411aff63db67253064715ec3ec33 diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index cf9e56f786..da7e62e425 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -142,9 +142,13 @@ void ConvertException(JNIEnv* env, const char* file, int line) #if REALM_ENABLE_SYNC catch (realm::app::AppError& e) { // TODO Figure out exactly what kind of mapping is needed here - if (e.error_code.category() == realm::app::custom_error_category()) { + if (e.is_custom_error()) { ThrowException(env, IllegalArgument, e.message); - } else { + } + else if (e.error_code.value() == static_cast(realm::app::ClientErrorCode::user_not_logged_in)) { + ThrowException(env, IllegalArgument, e.message); + } + else { ThrowException(env, IllegalState, e.message); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index 50f40415fa..5c5bde06e6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -38,7 +38,6 @@ import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.internal.objectstore.OsSyncUser; import io.realm.log.RealmLog; -import io.realm.mongodb.RealmMongoDBService; /** * FIXME @@ -88,7 +87,6 @@ public void onError(SyncSession session, ObjectServerError error) { private OsJavaNetworkTransport networkTransport; final long nativePtr; private final EmailPasswordAuthProvider emailAuthProvider = new EmailPasswordAuthProvider(this); - private ApiKeyAuthProvider apiKeyAuthProvider = null; private CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList<>(); public RealmApp(String appId) { @@ -155,123 +153,6 @@ public RealmUser switchUser(RealmUser user) { return user; } - /** - * Links the current user with a new user identity represented by the given credentials. - *

            - * Linking a user with more credentials, mean the user can login either of these credentials. - * It also makes it possible to "upgrade" an anonymous user by linking it with e.g. - * Email/Password credentials. - *

            -     * {@code
            -     * // Example
            -     * RealmApp app = new RealmApp("app-id")
            -     * RealmUser user = app.login(RealmCredentials.anonymous());
            -     * app.linkUser(RealmCredentials.emailPassword("email", "password"));
            -     * }
            -     * 
            - *

            - * Note: It is not possible to link two existing users of MongoDB Realm. The provided credentials - * must not have been used by another user. - * - * @param credentials the credentials to link with the current user. - * @throws IllegalStateException if no user is currently logged in. - * @return the {@link io.realm.RealmUser} the credentials were linked to. - */ - public RealmUser linkUser(RealmCredentials credentials) { - Util.checkNull(credentials, "credentials"); - final RealmUser user = currentUser(); - if (user == null) { - throw new IllegalStateException("No user is logged in"); - } - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - nativeLinkUser(nativePtr, user.osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { - @Override - protected RealmUser mapSuccess(Object result) { - user.osUser = new OsSyncUser((long) result); // OS returns the updated user as a new one. - return user; - } - }); - return handleResult(success, error); - } - - - /** - * Links the current user with a new user identity represented by the given credentials. - *

            - * Linking a user with more credentials, mean the user can login either of these credentials. - * It also makes it possible to "upgrade" an anonymous user by linking it with e.g. - * Email/Password credentials. - *

            -     * {@code
            -     * // Example
            -     * RealmApp app = new RealmApp("app-id")
            -     * RealmUser user = app.login(RealmCredentials.anonymous());
            -     * app.linkUser(RealmCredentials.emailPassword("email", "password"));
            -     * }
            -     * 
            - *

            - * Note: It is not possible to link two existing users of MongoDB Realm. The provided credentials - * must not have been used by another user. - * - * @param credentials the credentials to link with the current user. - * @param callback callback when user identities has been linked or it failed. The callback will - * always happen on the same thread as this method is called on. - * @throws IllegalStateException if called from a non-looper thread. - */ - public RealmAsyncTask linkUserAsync(RealmCredentials credentials, Callback callback) { - Util.checkLooperThread("Asynchronous linking identities is only possible from looper threads."); - return new Request(NETWORK_POOL_EXECUTOR, callback) { - @Override - public RealmUser run() throws ObjectServerError { - return linkUser(credentials); - } - }.start(); - } - - /** - * Removes a users credentials from this device. If the user was currently logged in, they - * will be logged out as part of the process. This is only a local change and does not - * affect the user state on the server. - * - * @param user user to remove. - * @return user that was removed. - * @throws ObjectServerError if called from the UI thread or if the user was logged in, but - * could not be logged out. - */ - public RealmUser removeUser(RealmUser user) throws ObjectServerError { - Util.checkNull(user, "user"); - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - nativeRemoveUser(nativePtr, user.osUser.getNativePtr(), new OsJNIResultCallback(success, error) { - @Override - protected RealmUser mapSuccess(Object result) { - return user; - } - }); - return handleResult(success, error); - } - - /** - * Removes a users credentials from this device. If the user was currently logged in, they - * will be logged out as part of the process. This is only a local change and does not - * affect the user state on the server. - * - * @param user user to remove. - * @param callback callback when removing the user has completed or failed. The callback will always - * happen on the same thread as this method is called on. - * @throws IllegalStateException if called from a non-looper thread. - */ - public RealmAsyncTask removeUserAsync(RealmUser user, Callback callback) { - Util.checkLooperThread("Asynchronous removal of users is only possible from looper threads."); - return new Request(NETWORK_POOL_EXECUTOR, callback) { - @Override - public RealmUser run() throws ObjectServerError { - return removeUser(user); - } - }.start(); - } - /** * Logs in as a user with the given credentials associated with an authentication provider. *

            @@ -329,105 +210,16 @@ public RealmUser run() throws ObjectServerError { }.start(); } - /** - * Log the current user out of the Realm App, destroying their server state, unregistering them from the - * SDK, and removing any synced Realms associated with them from on-disk storage on next app - * launch. - *

            - * This method should be called whenever the application is committed to not using a user again. - * Failing to call this method may result in unused files and metadata needlessly taking up space. - *

            - * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} - * will be notified and user credentials will be deleted from this device. - *

            - * Logging out anonymous users will remove them immediately instead of marking them as - * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} - * and will still be returned by {@link #allUsers()}. - * - * @throws IllegalStateException if no current user could be found. - * @throws ObjectServerError if an error occurred while trying to log the user out of the Realm - * App. - */ - public void logOut() throws ObjectServerError { - RealmUser user = currentUser(); - if (user == null) { - throw new IllegalStateException("No current user was found."); - } - logOut(user); - } - - /** - * Log the current user out of the Realm App asynchronously, destroying their server state, unregistering them from the - * SDK, and removing any synced Realms associated with them from on-disk storage on next app - * launch. - *

            - * This method should be called whenever the application is committed to not using a user again. - * Failing to call this method may result in unused files and metadata needlessly taking up space. - *

            - * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} - * will be notified and user credentials will be deleted from this device. - *

            - * Logging out anonymous users will remove them immediately instead of marking them as - * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} - * and will still be returned by {@link #allUsers()}. - * - * @param callback callback when logging out has completed or failed. The callback will always - * happen on the same thread as this method is called on. - * @throws IllegalStateException if not called on a looper thread or no current user could be found. - */ - public RealmAsyncTask logOutAsync(Callback callback) { - RealmUser user = currentUser(); - if (user == null) { - throw new IllegalStateException("No current user was found."); - } - return logOutAsync(user, callback); - } - - /** - * Returns a wrapper for managing API keys controlled by the current user. - * - * @return wrapper for managing API keys controlled by the current user. - * @throws IllegalStateException if no user is currently logged in. - */ - public synchronized ApiKeyAuthProvider getApiKeyAuthProvider() { - RealmUser user = currentUser(); - if (user == null) { - throw new IllegalStateException("No user is currently logged in."); - } - if (apiKeyAuthProvider == null || !user.equals(apiKeyAuthProvider.getUser())) { - apiKeyAuthProvider = new ApiKeyAuthProvider(user); - } - return apiKeyAuthProvider; - } - /** * Returns a wrapper for interacting with functionality related to users either being created or - * login using the {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} identity provider. + * logged in using the {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} identity provider. * * @return wrapper for interacting with the {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} identity provider. */ - public EmailPasswordAuthProvider getEmailPasswordAuthProvider() { + public EmailPasswordAuthProvider getEmailPasswordAuthProvider() { return emailAuthProvider; } - void logOut(RealmUser user) { - Util.checkNull(user, "user"); - AtomicReference error = new AtomicReference<>(null); - nativeLogOut(nativePtr, user.osUser.getNativePtr(), new OsJNIVoidResultCallback(error)); - handleResult(null, error); - } - - RealmAsyncTask logOutAsync(RealmUser user, Callback callback) { - Util.checkLooperThread("Asynchronous log out is only possible from looper threads."); - return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { - @Override - public RealmUser run() throws ObjectServerError { - logOut(user); - return user; - } - }.start(); - } - public SyncSession getSyncSession(SyncConfiguration config) { return null; } @@ -451,7 +243,6 @@ public void addAuthenticationListener(AuthenticationListener listener) { authListeners.add(listener); } - /** * Removes the provided global authentication listener. * @@ -465,23 +256,6 @@ public void removeAuthenticationListener(AuthenticationListener listener) { authListeners.remove(listener); } - // Services entry point - public RealmFunctions getFunctions() { - // FIXME - return null; - } - - public RealmPushNotifications getFSMPushNotifications() { - // FIXME - return null; - - } - - public RealmMongoDBService getMongoDBService() { - // FIXME - return null; - } - // Private API's for now. /** @@ -744,8 +518,5 @@ public interface Callback { @Nullable private static native Long nativeCurrentUser(long nativePtr); private static native long[] nativeGetAllUsers(long nativePtr); - private static native void nativeLogOut(long appNativePtr, long userNativePtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); private static native void nativeSwitchUser(long nativeAppPtr, long nativeUserPtr); - private static native void nativeRemoveUser(long nativeAppPtr, long nativeUserPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeLinkUser(long nativeAppPtr, long nativeUserPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java index 236e8bb6d7..eccd51e681 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -17,11 +17,17 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; +import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.internal.objectstore.OsSyncUser; import io.realm.internal.util.Pair; +import io.realm.internal.Util; +import io.realm.mongodb.RealmMongoDBService; + +import static io.realm.RealmApp.handleResult; /** * FIXME @@ -30,6 +36,7 @@ public class RealmUser { OsSyncUser osUser; private final RealmApp app; + private ApiKeyAuthProvider apiKeyAuthProvider = null; /** * FIXME @@ -218,42 +225,211 @@ public State getState() { } /** - * Log the user out of the Realm App, destroying their server state, unregistering them from the - * SDK, and removing any synced Realms associated with them from on-disk storage on next app - * launch. + * Returns whether or not this user is still logged into the MongoDB Realm App. + * + * @return {@code true} if still logged in, {@code false} if not. + */ + public boolean isLoggedIn() { + return getState() == State.LOGGED_IN; + } + + /** + * Links the current user with a new user identity represented by the given credentials. *

            - * If the user is already logged out, this method does nothing. + * Linking a user with more credentials, mean the user can login either of these credentials. + * It also makes it possible to "upgrade" an anonymous user by linking it with e.g. + * Email/Password credentials. + *

            +     * {@code
            +     * // Example
            +     * RealmApp app = new RealmApp("app-id")
            +     * RealmUser user = app.login(RealmCredentials.anonymous());
            +     * user.linkUser(RealmCredentials.emailPassword("email", "password"));
            +     * }
            +     * 
            + *

            + * Note: It is not possible to link two existing users of MongoDB Realm. The provided credentials + * must not have been used by another user. + * + * @param credentials the credentials to link with the current user. + * @throws IllegalStateException if no user is currently logged in. + * @return the {@link io.realm.RealmUser} the credentials were linked to. + */ + public RealmUser linkUser(RealmCredentials credentials) { + Util.checkNull(credentials, "credentials"); + checkLoggedIn(); + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + nativeLinkUser(app.nativePtr, osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new RealmApp.OsJNIResultCallback(success, error) { + @Override + protected RealmUser mapSuccess(Object result) { + osUser = new OsSyncUser((long) result); // OS returns the updated user as a new one. + return RealmUser.this; + } + }); + return handleResult(success, error); + } + + /** + * Links the current user with a new user identity represented by the given credentials. *

            - * This method should be called whenever the application is committed to not using a user again. - * Failing to call this method may result in unused files and metadata needlessly taking up space. + * Linking a user with more credentials, mean the user can login either of these credentials. + * It also makes it possible to "upgrade" an anonymous user by linking it with e.g. + * Email/Password credentials. + *

            +     * {@code
            +     * // Example
            +     * RealmApp app = new RealmApp("app-id")
            +     * RealmUser user = app.login(RealmCredentials.anonymous());
            +     * user.linkUser(RealmCredentials.emailPassword("email", "password"));
            +     * }
            +     * 
            + *

            + * Note: It is not possible to link two existing users of MongoDB Realm. The provided credentials + * must not have been used by another user. + * + * @param credentials the credentials to link with the current user. + * @param callback callback when user identities has been linked or it failed. The callback will + * always happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask linkUserAsync(RealmCredentials credentials, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous linking identities is only possible from looper threads."); + return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { + @Override + public RealmUser run() throws ObjectServerError { + return linkUser(credentials); + } + }.start(); + } + + /** + * Removes a users credentials from this device. If the user was currently logged in, they + * will be logged out as part of the process. This is only a local change and does not + * affect the user state on the server. + * + * @return user that was removed. + * @throws ObjectServerError if called from the UI thread or if the user was logged in, but + * could not be logged out. + */ + public RealmUser removeUser() throws ObjectServerError { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + nativeRemoveUser(app.nativePtr, osUser.getNativePtr(), new RealmApp.OsJNIResultCallback(success, error) { + @Override + protected RealmUser mapSuccess(Object result) { + return RealmUser.this; + } + }); + return handleResult(success, error); + } + + /** + * Removes a user's credentials from this device. If the user was currently logged in, they + * will be logged out as part of the process. This is only a local change and does not + * affect the user state on the server. + * + * @param user user to remove. + * @param callback callback when removing the user has completed or failed. The callback will always + * happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask removeUserAsync(RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous removal of users is only possible from looper threads."); + return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { + @Override + public RealmUser run() throws ObjectServerError { + return removeUser(); + } + }.start(); + } + + /** + * Log the user out of the Realm App. This will unregister them on the device, stop any + * synchronization to and from the users' Realms, and those Realms will be deleted next time + * the app restarts. Therefor logging out should not be done until all changes to Realms have + * been uploaded to the server. *

            * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} * will be notified and user credentials will be deleted from this device. + *

            + * Logging out anonymous users will remove them immediately instead of marking them as + * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} + * and will still be returned by {@link #allUsers()}. They can be removed completely by calling + * {@link #removeUser()}. * * @throws ObjectServerError if an error occurred while trying to log the user out of the Realm * App. */ - public void logOut() { - app.logOut(this); + public void logOut() throws ObjectServerError { + AtomicReference error = new AtomicReference<>(null); + nativeLogOut(app.nativePtr, osUser.getNativePtr(), new RealmApp.OsJNIVoidResultCallback(error)); + handleResult(null, error); } /** - * Log the user out of the Realm App, destroying their server state, unregistering them from the - * SDK, and removing any synced Realms associated with them from on-disk storage on next app - * launch. If the user is already logged out or in an error state, this method does nothing. - *

            - * If the user is already logged out, this method does nothing. - *

            - * This method should be called whenever the application is committed to not using a user again. - * Failing to call this method may result in unused files and metadata needlessly taking up space. + * Log the user out of the Realm App asynchronously. This will unregister them on the device, stop any + * synchronization to and from the users' Realms, and those Realms will be deleted next time + * the app restarts. Therefor logging out should not be done until all changes to Realms have + * been uploaded to the server. *

            * Once the Realm App has confirmed the logout any registered {@link AuthenticationListener} * will be notified and user credentials will be deleted from this device. + *

            + * Logging out anonymous users will remove them immediately instead of marking them as + * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} + * and will still be returned by {@link #allUsers()}. They can be removed completely by calling + * {@link #removeUser()}. * - * @throws IllegalStateException if not called on a looper thread. + * @param callback callback when logging out has completed or failed. The callback will always + * happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask logOutAsync(RealmApp.Callback callback) { + final RealmUser user = this; + Util.checkLooperThread("Asynchronous log out is only possible from looper threads."); + return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + @Override + public RealmUser run() throws ObjectServerError { + logOut(); + return user; + } + }.start(); + } + + /** + * Returns a wrapper for managing API keys controlled by the current user. + * + * @return wrapper for managing API keys controlled by the current user. + * @throws IllegalStateException if no user is currently logged in. + */ + public synchronized ApiKeyAuthProvider getApiKeyAuthProvider() { + checkLoggedIn(); + if (apiKeyAuthProvider == null) { + apiKeyAuthProvider = new ApiKeyAuthProvider(this); + } + return apiKeyAuthProvider; + } + + /** + * FIXME Add support for functions. Name of Class and method still TBD. + */ + public RealmFunctions getFunctions() { + return null; + } + + /** + * FIXME Add support for push notifications. Name of Class and method still TBD. */ - public RealmAsyncTask logOutAsync(RealmApp.Callback callback) { - return app.logOutAsync(this, callback); + public RealmPushNotifications getPushNotifications() { + return null; + } + + /** + * FIXME Add support for the MongoDB wrapper. Name of Class and method still TBD. + */ + public RealmMongoDBService getMongoDBService() { + return null; } @Override @@ -273,5 +449,15 @@ public int hashCode() { result = 31 * result + app.hashCode(); return result; } -} + private void checkLoggedIn() { + if (!isLoggedIn()) { + throw new IllegalStateException("User is not logged in."); + } + } + + private static native void nativeRemoveUser(long nativeAppPtr, long nativeUserPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeLinkUser(long nativeAppPtr, long nativeUserPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeLogOut(long appNativePtr, long userNativePtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + +} From 0803a12761797dfae23f3bd0e039dca80ecca7bd Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 21 Apr 2020 10:36:47 +0200 Subject: [PATCH 1494/2110] Fix detecting current branch (#6808) --- Jenkinsfile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 06cdcd0f95..70981cb561 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,6 +7,7 @@ def mongoDbRealmContainer = null def mongoDbRealmCommandServerContainer = null def dockerNetworkId = UUID.randomUUID().toString() def releaseBranches = ['master', 'next-major', 'v10'] // Branches from which we release SNAPSHOT's +def currentBranch = env.CHANGE_BRANCH try { node('android') { timeout(time: 90, unit: 'MINUTES') { @@ -32,7 +33,7 @@ try { // on PR's for even more throughput. def abiFilter = "" def instrumentationTestTarget = "connectedAndroidTest" - if (!releaseBranches.contains(env.BRANCH_NAME)) { + if (!releaseBranches.contains(currentBranch)) { abiFilter = "-PbuildTargetABIs=armeabi-v7a" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting @@ -135,13 +136,13 @@ try { // TODO: add support for running monkey on the example apps - if (['master'].contains(env.BRANCH_NAME)) { + if (['master'].contains(currentBranch)) { stage('Collect metrics') { collectAarMetrics() } } - if (releaseBranches.contains(env.BRANCH_NAME)) { + if (releaseBranches.contains(currentBranch)) { stage('Publish to OJO') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" @@ -166,14 +167,14 @@ try { buildSuccess = false throw e } finally { - if (['master', 'releases', 'next-major'].contains(env.BRANCH_NAME) && !buildSuccess) { + if (['master', 'releases', 'next-major'].contains(currentBranch) && !buildSuccess) { node { withCredentials([[$class: 'StringBinding', credentialsId: 'slack-java-url', variable: 'SLACK_URL']]) { def payload = JsonOutput.toJson([ username: 'Mr. Jenkins', icon_emoji: ':jenkins:', attachments: [[ - 'title': "The ${env.BRANCH_NAME} branch is broken!", + 'title': "The ${currentBranch} branch is broken!", 'text': "<${env.BUILD_URL}|Click here> to check the build.", 'color': "danger" ]] From 93e1b43ed721744b5f198016cf67f72f2fc6787b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 22 Apr 2020 12:45:14 +0200 Subject: [PATCH 1495/2110] Add support for MongoDB datatypes: Decimal128 and ObjectId (#6722) * Support for ObjectId and Decimal128 --- CHANGELOG.md | 2 + dependencies.list | 8 +- realm/kotlin-extensions/build.gradle | 4 + .../realm-annotations-processor/build.gradle | 19 +- .../java/io/realm/processor/ClassMetaData.kt | 12 +- .../main/java/io/realm/processor/Constants.kt | 74 +- .../processor/OsObjectBuilderTypeHelper.kt | 67 +- .../io/realm/processor/RealmJsonTypeHelper.kt | 102 +- .../processor/RealmProxyClassGenerator.kt | 229 ++-- .../java/io/realm/processor/TypeMirrors.kt | 8 +- .../src/main/java/io/realm/processor/Utils.kt | 11 + .../realm/some_test_AllTypesRealmProxy.java | 636 +++++++-- .../realm/some_test_BooleansRealmProxy.java | 56 +- ...amePolicyMixedClassSettingsRealmProxy.java | 48 +- ...st_NamePolicyModuleDefaultsRealmProxy.java | 48 +- .../realm/some_test_NullTypesRealmProxy.java | 1216 ++++++++++++++--- .../io/realm/some_test_SimpleRealmProxy.java | 40 +- .../test/resources/some/test/AllTypes.java | 10 +- .../test/resources/some/test/NullTypes.java | 19 + realm/realm-library/build.gradle | 3 +- .../src/androidTest/AndroidManifest.xml | 2 +- .../assets/decimal128_as_double.json | 3 + .../androidTest/assets/decimal128_as_int.json | 3 + .../assets/decimal128_as_long.json | 3 + .../assets/decimal128_as_string.json | 3 + .../androidTest/assets/nulltypes_invalid.json | 8 + .../assets/objectid_as_string.json | 3 + .../java/io/realm/BulkInsertTests.java | 19 + .../io/realm/DynamicRealmObjectTests.java | 114 +- .../java/io/realm/FrozenObjectsTests.java | 7 +- .../io/realm/LinkingObjectsDynamicTests.java | 16 + .../io/realm/LinkingObjectsQueryTests.java | 36 + .../androidTest/java/io/realm/QueryTests.java | 2 + .../java/io/realm/RealmJsonTests.java | 224 ++- .../java/io/realm/RealmObjectTests.java | 3 +- .../java/io/realm/RealmQueryTests.java | 56 + .../java/io/realm/RealmResultsTests.java | 328 ++++- .../java/io/realm/RealmSchemaTests.java | 4 + .../androidTest/java/io/realm/RealmTests.java | 71 +- .../java/io/realm/entities/AllJavaTypes.java | 43 + .../io/realm/entities/MappedAllJavaTypes.java | 7 + .../realm/entities/NoPrimaryKeyNullTypes.java | 43 + .../io/realm/entities/PrimitiveListTypes.java | 26 + .../entities/pojo/AllTypesRealmModel.java | 6 + .../realm/internal/QueryDescriptorTests.java | 2 + .../kotlin/io/realm/Decimal128Tests.kt | 491 +++++++ .../kotlin/io/realm/ObjectIdTests.kt | 394 ++++++ .../realm-library/src/main/cpp/CMakeLists.txt | 16 +- .../main/cpp/io_realm_internal_CheckedRow.cpp | 41 + .../src/main/cpp/io_realm_internal_OsList.cpp | 61 + .../main/cpp/io_realm_internal_OsObject.cpp | 57 +- .../main/cpp/io_realm_internal_OsResults.cpp | 17 + .../main/cpp/io_realm_internal_Property.cpp | 2 +- .../src/main/cpp/io_realm_internal_Table.cpp | 79 +- .../main/cpp/io_realm_internal_TableQuery.cpp | 381 +++++- .../cpp/io_realm_internal_UncheckedRow.cpp | 68 +- ...m_internal_objectstore_OsObjectBuilder.cpp | 49 +- .../src/main/cpp/java_accessor.hpp | 28 + .../src/main/cpp/java_class_global_def.cpp | 17 + .../src/main/cpp/java_class_global_def.hpp | 8 + .../src/main/cpp/java_object_accessor.hpp | 15 + realm/realm-library/src/main/cpp/util.hpp | 15 + .../java/io/realm/DynamicRealmObject.java | 123 ++ .../main/java/io/realm/FrozenPendingRow.java | 23 + .../main/java/io/realm/RealmFieldType.java | 35 +- .../src/main/java/io/realm/RealmList.java | 111 ++ .../src/main/java/io/realm/RealmQuery.java | 249 +++- .../src/main/java/io/realm/RealmResults.java | 65 +- .../java/io/realm/internal/CheckedRow.java | 12 + .../java/io/realm/internal/InvalidRow.java | 23 + .../main/java/io/realm/internal/OsList.java | 63 + .../main/java/io/realm/internal/OsObject.java | 26 +- .../java/io/realm/internal/OsResults.java | 41 + .../java/io/realm/internal/PendingRow.java | 23 + .../main/java/io/realm/internal/Property.java | 26 + .../src/main/java/io/realm/internal/Row.java | 11 + .../main/java/io/realm/internal/Table.java | 38 + .../java/io/realm/internal/TableQuery.java | 161 +++ .../java/io/realm/internal/UncheckedRow.java | 47 + .../src/main/java/io/realm/internal/Util.java | 5 - .../realm/internal/core/QueryDescriptor.java | 4 +- .../internal/objectstore/OsObjectBuilder.java | 205 +-- .../testUtils/java/io/realm/TestHelper.java | 38 +- .../java/io/realm/entities/AllTypes.java | 45 + .../java/io/realm/entities/NullTypes.java | 93 ++ 85 files changed, 6017 insertions(+), 833 deletions(-) create mode 100644 realm/realm-library/src/androidTest/assets/decimal128_as_double.json create mode 100644 realm/realm-library/src/androidTest/assets/decimal128_as_int.json create mode 100644 realm/realm-library/src/androidTest/assets/decimal128_as_long.json create mode 100644 realm/realm-library/src/androidTest/assets/decimal128_as_string.json create mode 100644 realm/realm-library/src/androidTest/assets/objectid_as_string.json create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index a043991519..4e0628556b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * [ObjectServer] `IncompatibleSyncedFileException` is removed and no longer thrown. ### Enhancements +* Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. +* Add support for `org.bson.types.ObjectId` as a primary key. * Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) * Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. diff --git a/dependencies.list b/dependencies.list index 7a86581a12..9af82988d4 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.5 -REALM_SYNC_SHA256=aa490300e06dca385622bc83430f9d8e3141fcccfd7bf6ae2f68af8a29af5dc1 +REALM_SYNC_VERSION=10.0.0-alpha.7 +REALM_SYNC_SHA256=f6350407097c9f95eba579420ba387b41d9def6095d2c54e1032597c83403e50 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. @@ -21,3 +21,7 @@ gradleVersion=5.6.4 ndkVersion=21.0.6113669 BUILD_INFO_EXTRACTOR_GRADLE=4.7.5 GRADLE_BINTRAY_PLUGIN=1.8.4 + +# Bson dependency version +BSON_DEPENDENCY_VERSION=3.12.1 + diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 1f0ad6cd62..c5676e58e6 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -14,6 +14,9 @@ apply plugin: 'org.jetbrains.dokka' //apply plugin: 'com.github.kt3k.coveralls' //apply plugin: 'net.ltgt.errorprone' +def properties = new Properties() +properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) + android.registerTransform(new io.realm.transformer.RealmTransformer(project)) android { @@ -76,6 +79,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:rules:1.2.0' kaptAndroidTest project(':realm-annotations-processor') + androidTestObjectServerImplementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY_VERSION')}" androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 3e5b74a924..53c696aa10 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -7,16 +7,19 @@ apply plugin: 'com.jfrog.bintray' sourceCompatibility = '1.8' targetCompatibility = '1.8' +def properties = new Properties() +properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) + dependencies { - compile "com.squareup:javawriter:2.5.1" - compile "io.realm:realm-annotations:${version}" - - testCompile files('../realm-library/build/intermediates/aar_main_jar/objectServerRelease/classes.jar') // Java projects cannot depend on AAR files - testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it - testCompile group:'junit', name:'junit', version:'4.12' - testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' - testCompile files(file("${System.env.ANDROID_HOME}/platforms/android-27/android.jar")) + implementation "com.squareup:javawriter:2.5.1" + implementation "io.realm:realm-annotations:${version}" + implementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY_VERSION')}" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + testImplementation files('../realm-library/build/intermediates/aar_main_jar/baseRelease/classes.jar') // Java projects cannot depend on AAR files + testImplementation files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it + testImplementation group:'junit', name:'junit', version:'4.12' + testImplementation group:'com.google.testing.compile', name:'compile-testing', version:'0.6' + testImplementation files(file("${System.env.ANDROID_HOME}/platforms/android-27/android.jar")) } // for Ant filter diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt index 905ef673ce..f8590ae51b 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt @@ -83,7 +83,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat typeMirrors.PRIMITIVE_LONG_MIRROR, typeMirrors.PRIMITIVE_INT_MIRROR, typeMirrors.PRIMITIVE_SHORT_MIRROR, - typeMirrors.PRIMITIVE_BYTE_MIRROR + typeMirrors.PRIMITIVE_BYTE_MIRROR, + typeMirrors.OBJECT_ID_MIRROR ) private val validListValueTypes: List = Arrays.asList( typeMirrors.STRING_MIRROR, @@ -95,7 +96,9 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat typeMirrors.BYTE_MIRROR, typeMirrors.DOUBLE_MIRROR, typeMirrors.FLOAT_MIRROR, - typeMirrors.DATE_MIRROR + typeMirrors.DATE_MIRROR, + typeMirrors.DECIMAL128_MIRROR, + typeMirrors.OBJECT_ID_MIRROR ) private val stringType = typeMirrors.STRING_MIRROR @@ -644,7 +647,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat } // The field has the @Index annotation. It's only valid for column types: - // STRING, DATE, INTEGER, BOOLEAN, and RealmMutableInteger + // STRING, DATE, INTEGER, BOOLEAN, RealmMutableInteger and OBJECT_ID private fun categorizeIndexField(element: Element, fieldElement: RealmFieldElement): Boolean { var indexable = false @@ -655,7 +658,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat Constants.RealmFieldType.STRING, Constants.RealmFieldType.DATE, Constants.RealmFieldType.INTEGER, - Constants.RealmFieldType.BOOLEAN -> { indexable = true } + Constants.RealmFieldType.BOOLEAN, + Constants.RealmFieldType.OBJECT_ID -> { indexable = true } else -> { /* Ignore */ } } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt index 053a4de5ef..f537c55725 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt @@ -31,8 +31,40 @@ object Constants { "throw new io.realm.exceptions.RealmException(\"Primary key field '%s' cannot be changed after object was created.\")" const val STATEMENT_EXCEPTION_ILLEGAL_JSON_LOAD = "throw new io.realm.exceptions.RealmException(\"\\\"%s\\\" field \\\"%s\\\" cannot be loaded from json\")" - val JAVA_TO_REALM_TYPES = hashMapOf() - val LIST_ELEMENT_TYPE_TO_REALM_TYPES = hashMapOf() + val JAVA_TO_REALM_TYPES = mapOf("byte" to RealmFieldType.INTEGER, + "short" to RealmFieldType.INTEGER, + "int" to RealmFieldType.INTEGER, + "long" to RealmFieldType.INTEGER, + "float" to RealmFieldType.FLOAT, + "double" to RealmFieldType.DOUBLE, + "boolean" to RealmFieldType.BOOLEAN, + "java.lang.Byte" to RealmFieldType.INTEGER, + "java.lang.Short" to RealmFieldType.INTEGER, + "java.lang.Integer" to RealmFieldType.INTEGER, + "java.lang.Long" to RealmFieldType.INTEGER, + "java.lang.Float" to RealmFieldType.FLOAT, + "java.lang.Double" to RealmFieldType.DOUBLE, + "java.lang.Boolean" to RealmFieldType.BOOLEAN, + "java.lang.String" to RealmFieldType.STRING, + "java.util.Date" to RealmFieldType.DATE, + "byte[]" to RealmFieldType.BINARY, + "org.bson.types.Decimal128" to RealmFieldType.DECIMAL128, + "org.bson.types.ObjectId" to RealmFieldType.OBJECT_ID) + + val LIST_ELEMENT_TYPE_TO_REALM_TYPES = mapOf( + "java.lang.Byte" to RealmFieldType.INTEGER_LIST, + "java.lang.Short" to RealmFieldType.INTEGER_LIST, + "java.lang.Integer" to RealmFieldType.INTEGER_LIST, + "java.lang.Long" to RealmFieldType.INTEGER_LIST, + "java.lang.Float" to RealmFieldType.FLOAT_LIST, + "java.lang.Double" to RealmFieldType.DOUBLE_LIST, + "java.lang.Boolean" to RealmFieldType.BOOLEAN_LIST, + "java.lang.String" to RealmFieldType.STRING_LIST, + "java.util.Date" to RealmFieldType.DATE_LIST, + "byte[]" to RealmFieldType.BINARY_LIST, + "org.bson.types.Decimal128" to RealmFieldType.DECIMAL128_LIST, + "org.bson.types.ObjectId" to RealmFieldType.OBJECT_ID_LIST + ) /** * Realm types and their corresponding Java types. @@ -52,6 +84,8 @@ object Constants { REALM_INTEGER("INTEGER", "Long"), OBJECT("OBJECT", "Object"), LIST("LIST", "List"), + DECIMAL128("DECIMAL128", "Decimal128"), + OBJECT_ID("OBJECT_ID", "ObjectId"), BACKLINK("LINKING_OBJECTS", null), @@ -61,43 +95,13 @@ object Constants { BINARY_LIST("BINARY_LIST", "List"), DATE_LIST("DATE_LIST", "List"), FLOAT_LIST("FLOAT_LIST", "List"), - DOUBLE_LIST("DOUBLE_LIST", "List"); + DOUBLE_LIST("DOUBLE_LIST", "List"), + DECIMAL128_LIST("DECIMAL128_LIST", "List"), + OBJECT_ID_LIST("OBJECT_ID_LIST", "List"); /** * The name of the enum, used in the Java bindings, used to represent the corresponding type. */ val realmType: String = "RealmFieldType.$realmType" } - - init { - JAVA_TO_REALM_TYPES["byte"] = RealmFieldType.INTEGER - JAVA_TO_REALM_TYPES["short"] = RealmFieldType.INTEGER - JAVA_TO_REALM_TYPES["int"] = RealmFieldType.INTEGER - JAVA_TO_REALM_TYPES["long"] = RealmFieldType.INTEGER - JAVA_TO_REALM_TYPES["float"] = RealmFieldType.FLOAT - JAVA_TO_REALM_TYPES["double"] = RealmFieldType.DOUBLE - JAVA_TO_REALM_TYPES["boolean"] = RealmFieldType.BOOLEAN - JAVA_TO_REALM_TYPES["java.lang.Byte"] = RealmFieldType.INTEGER - JAVA_TO_REALM_TYPES["java.lang.Short"] = RealmFieldType.INTEGER - JAVA_TO_REALM_TYPES["java.lang.Integer"] = RealmFieldType.INTEGER - JAVA_TO_REALM_TYPES["java.lang.Long"] = RealmFieldType.INTEGER - JAVA_TO_REALM_TYPES["java.lang.Float"] = RealmFieldType.FLOAT - JAVA_TO_REALM_TYPES["java.lang.Double"] = RealmFieldType.DOUBLE - JAVA_TO_REALM_TYPES["java.lang.Boolean"] = RealmFieldType.BOOLEAN - JAVA_TO_REALM_TYPES["java.lang.String"] = RealmFieldType.STRING - JAVA_TO_REALM_TYPES["java.util.Date"] = RealmFieldType.DATE - JAVA_TO_REALM_TYPES["byte[]"] = RealmFieldType.BINARY - // TODO: add support for char and Char - - LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Byte"] = RealmFieldType.INTEGER_LIST - LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Short"] = RealmFieldType.INTEGER_LIST - LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Integer"] = RealmFieldType.INTEGER_LIST - LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Long"] = RealmFieldType.INTEGER_LIST - LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Float"] = RealmFieldType.FLOAT_LIST - LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Double"] = RealmFieldType.DOUBLE_LIST - LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.Boolean"] = RealmFieldType.BOOLEAN_LIST - LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.lang.String"] = RealmFieldType.STRING_LIST - LIST_ELEMENT_TYPE_TO_REALM_TYPES["java.util.Date"] = RealmFieldType.DATE_LIST - LIST_ELEMENT_TYPE_TO_REALM_TYPES["byte[]"] = RealmFieldType.BINARY_LIST - } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt index 6470087c91..4e74bfda84 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt @@ -31,39 +31,48 @@ object OsObjectBuilderTypeHelper { init { // Map of qualified types to their OsObjectBuilder Type val fieldTypes = HashMap() - fieldTypes[QualifiedClassName("byte")] = "Integer" - fieldTypes[QualifiedClassName("short")] = "Integer" - fieldTypes[QualifiedClassName("int")] = "Integer" - fieldTypes[QualifiedClassName("long")] = "Integer" - fieldTypes[QualifiedClassName("float")] = "Float" - fieldTypes[QualifiedClassName("double")] = "Double" - fieldTypes[QualifiedClassName("boolean")] = "Boolean" - fieldTypes[QualifiedClassName("byte[]")] = "ByteArray" - fieldTypes[QualifiedClassName("java.lang.Byte")] = "Integer" - fieldTypes[QualifiedClassName("java.lang.Short")] = "Integer" - fieldTypes[QualifiedClassName("java.lang.Integer")] = "Integer" - fieldTypes[QualifiedClassName("java.lang.Long")] = "Integer" - fieldTypes[QualifiedClassName("java.lang.Float")] = "Float" - fieldTypes[QualifiedClassName("java.lang.Double")] = "Double" - fieldTypes[QualifiedClassName("java.lang.Boolean")] = "Boolean" - fieldTypes[QualifiedClassName("java.lang.String")] = "String" - fieldTypes[QualifiedClassName("java.util.Date")] = "Date" - fieldTypes[QualifiedClassName("io.realm.MutableRealmInteger")] = "MutableRealmInteger" + fieldTypes.apply { + this[QualifiedClassName("byte")] = "Integer" + this[QualifiedClassName("byte")] = "Integer" + this[QualifiedClassName("short")] = "Integer" + this[QualifiedClassName("int")] = "Integer" + this[QualifiedClassName("long")] = "Integer" + this[QualifiedClassName("float")] = "Float" + this[QualifiedClassName("double")] = "Double" + this[QualifiedClassName("boolean")] = "Boolean" + this[QualifiedClassName("byte[]")] = "ByteArray" + this[QualifiedClassName("java.lang.Byte")] = "Integer" + this[QualifiedClassName("java.lang.Short")] = "Integer" + this[QualifiedClassName("java.lang.Integer")] = "Integer" + this[QualifiedClassName("java.lang.Long")] = "Integer" + this[QualifiedClassName("java.lang.Float")] = "Float" + this[QualifiedClassName("java.lang.Double")] = "Double" + this[QualifiedClassName("java.lang.Boolean")] = "Boolean" + this[QualifiedClassName("java.lang.String")] = "String" + this[QualifiedClassName("java.util.Date")] = "Date" + this[QualifiedClassName("org.bson.types.Decimal128")] = "Decimal128" + this[QualifiedClassName("org.bson.types.ObjectId")] = "ObjectId" + this[QualifiedClassName("io.realm.MutableRealmInteger")] = "MutableRealmInteger" + } QUALIFIED_TYPE_TO_BUILDER = Collections.unmodifiableMap(fieldTypes) // Map of qualified types to their OsObjectBuilder Type val listTypes = HashMap() - listTypes[QualifiedClassName("byte[]")] = "ByteArrayList" - listTypes[QualifiedClassName("java.lang.Byte")] = "ByteList" - listTypes[QualifiedClassName("java.lang.Short")] = "ShortList" - listTypes[QualifiedClassName("java.lang.Integer")] = "IntegerList" - listTypes[QualifiedClassName("java.lang.Long")] = "LongList" - listTypes[QualifiedClassName("java.lang.Float")] = "FloatList" - listTypes[QualifiedClassName("java.lang.Double")] = "DoubleList" - listTypes[QualifiedClassName("java.lang.Boolean")] = "BooleanList" - listTypes[QualifiedClassName("java.lang.String")] = "StringList" - listTypes[QualifiedClassName("java.util.Date")] = "DateList" - listTypes[QualifiedClassName("io.realm.MutableRealmInteger")] = "MutableRealmIntegerList" + listTypes.apply { + this[QualifiedClassName("byte[]")] = "ByteArrayList" + this[QualifiedClassName("java.lang.Byte")] = "ByteList" + this[QualifiedClassName("java.lang.Short")] = "ShortList" + this[QualifiedClassName("java.lang.Integer")] = "IntegerList" + this[QualifiedClassName("java.lang.Long")] = "LongList" + this[QualifiedClassName("java.lang.Float")] = "FloatList" + this[QualifiedClassName("java.lang.Double")] = "DoubleList" + this[QualifiedClassName("java.lang.Boolean")] = "BooleanList" + this[QualifiedClassName("java.lang.String")] = "StringList" + this[QualifiedClassName("java.util.Date")] = "DateList" + this[QualifiedClassName("io.realm.MutableRealmInteger")] = "MutableRealmIntegerList" + this[QualifiedClassName("org.bson.types.Decimal128")] = "Decimal128List" + this[QualifiedClassName("org.bson.types.ObjectId")] = "ObjectIdList" + } QUALIFIED_LIST_TYPE_TO_BUILDER = Collections.unmodifiableMap(listTypes) } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt index 3bdebfa9a9..0eaff76dad 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt @@ -17,11 +17,8 @@ package io.realm.processor import com.squareup.javawriter.JavaWriter - import java.io.IOException -import java.util.Collections -import java.util.HashMap -import java.util.Locale +import java.util.* /** @@ -49,6 +46,8 @@ object RealmJsonTypeHelper { m[QualifiedClassName("java.lang.Boolean")] = m[QualifiedClassName("boolean")] as JsonToRealmFieldTypeConverter m[QualifiedClassName("java.lang.String")] = SimpleTypeConverter("String", "String") m[QualifiedClassName("java.util.Date")] = DateTypeConverter() + m[QualifiedClassName("org.bson.types.Decimal128")] = Decimal128TypeConverter() + m[QualifiedClassName("org.bson.types.ObjectId")] = ObjectIdTypeConverter() m[QualifiedClassName("io.realm.MutableRealmInteger")] = MutableRealmIntegerTypeConverter() JAVA_TO_JSON_TYPES = Collections.unmodifiableMap(m) } @@ -318,6 +317,101 @@ object RealmJsonTypeHelper { } } + private class Decimal128TypeConverter : JsonToRealmFieldTypeConverter { + @Throws(IOException::class) + override fun emitTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("%s.%s(null)", varName, accessor) + nextControlFlow("else") + emitStatement("Object decimal = json.get(\"%s\")", fieldName) + beginControlFlow("if (decimal instanceof org.bson.types.Decimal128)") + emitStatement("%s.%s((org.bson.types.Decimal128) decimal)", varName, accessor) + nextControlFlow("else if (decimal instanceof String)") + emitStatement("%s.%s(org.bson.types.Decimal128.parse((String)decimal))", varName, accessor) + nextControlFlow("else if (decimal instanceof Integer)") + emitStatement("%s.%s(new org.bson.types.Decimal128((Integer)(decimal)))", varName, accessor, fieldName) + nextControlFlow("else if (decimal instanceof Long)") + emitStatement("%s.%s(new org.bson.types.Decimal128((Long)(decimal)))", varName, accessor, fieldName) + nextControlFlow("else if (decimal instanceof Double)") + emitStatement("%s.%s(new org.bson.types.Decimal128(new java.math.BigDecimal((Double)(decimal))))", varName, accessor, fieldName) + nextControlFlow("else") + emitStatement("throw new UnsupportedOperationException(decimal.getClass() + \" is not supported as a Decimal128 value\")") + endControlFlow() + endControlFlow() + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitStreamTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter, isPrimaryKey: Boolean) { + writer.apply { + beginControlFlow("if (reader.peek() == JsonToken.NULL)") + emitStatement("reader.skipValue()") + emitStatement("%s.%s(null)", varName, accessor) + nextControlFlow("else") + emitStatement("%s.%s(org.bson.types.Decimal128.parse(reader.nextString()))", varName, accessor) + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitGetObjectWithPrimaryKeyValue(realmObjectClass: QualifiedClassName, realmObjectProxyClass: QualifiedClassName, fieldName: String, writer: JavaWriter) { + throw IllegalArgumentException("'Decimal128' is not allowed as a primary key value.") + } + } + + private class ObjectIdTypeConverter() : JsonToRealmFieldTypeConverter { + @Throws(IOException::class) + override fun emitTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("%s.%s(null)", varName, accessor) + nextControlFlow("else") + emitStatement("Object id = json.get(\"%s\")", fieldName) + beginControlFlow("if (id instanceof org.bson.types.ObjectId)") + emitStatement("%s.%s((org.bson.types.ObjectId) id)", varName, accessor) + nextControlFlow("else") + emitStatement("%s.%s(new org.bson.types.ObjectId((String)id))", varName, accessor) + endControlFlow() + endControlFlow() + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitStreamTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter, isPrimaryKey: Boolean) { + writer.apply { + beginControlFlow("if (reader.peek() == JsonToken.NULL)") + emitStatement("reader.skipValue()") + emitStatement("%s.%s(null)", varName, accessor) + nextControlFlow("else") + emitStatement("%s.%s(new org.bson.types.ObjectId(reader.nextString()))", varName, accessor) + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitGetObjectWithPrimaryKeyValue(realmObjectClass: QualifiedClassName, realmObjectProxyClass: QualifiedClassName, fieldName: String, writer: JavaWriter) { + // No error checking is done here for valid primary key types. + // This should be done by the annotation processor. + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, null, true, excludeFields)", realmObjectProxyClass, realmObjectClass) + nextControlFlow("else") + emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, json.get(\"%3\$s\"), true, excludeFields)", realmObjectProxyClass, realmObjectClass, fieldName) + endControlFlow() + nextControlFlow("else") + emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) + endControlFlow() + } + } + } + + private class MutableRealmIntegerTypeConverter : JsonToRealmFieldTypeConverter { @Throws(IOException::class) override fun emitTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index 757dd3094b..c597137d5c 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -17,23 +17,17 @@ package io.realm.processor import com.squareup.javawriter.JavaWriter - +import io.realm.processor.ext.beginMethod +import io.realm.processor.ext.beginType import java.io.BufferedWriter import java.io.IOException -import java.util.ArrayList -import java.util.Arrays -import java.util.Collections -import java.util.EnumSet -import java.util.Locale - +import java.util.* import javax.annotation.processing.ProcessingEnvironment import javax.lang.model.element.Modifier import javax.lang.model.element.VariableElement import javax.lang.model.type.DeclaredType import javax.lang.model.type.TypeMirror - -import io.realm.processor.ext.beginMethod -import io.realm.processor.ext.beginType +import javax.tools.JavaFileObject /** * This class is responsible for generating the Realm Proxy classes for each model class defined @@ -64,9 +58,10 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi // in the realm-library project, for an example of how to set this flag. private val suppressWarnings: Boolean = !"false".equals(processingEnvironment.options[OPTION_SUPPRESS_WARNINGS], ignoreCase = true) + lateinit var sourceFile: JavaFileObject @Throws(IOException::class, UnsupportedOperationException::class) fun generate() { - val sourceFile = processingEnvironment.filer.createSourceFile(generatedClassName.toString()) + sourceFile = processingEnvironment.filer.createSourceFile(generatedClassName.toString()) val imports = ArrayList(IMPORTS) if (metadata.backlinkFields.isNotEmpty()) { @@ -549,6 +544,12 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi if (typeUtils.isSameType(elementTypeMirror, typeMirrors.FLOAT_MIRROR)) { return "$osListVariableName.addFloat($valueVariableName.floatValue())" } + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DECIMAL128_MIRROR)) { + return "$osListVariableName.addDecimal128($valueVariableName)" + } + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.OBJECT_ID_MIRROR)) { + return "$osListVariableName.addObjectId($valueVariableName)" + } throw RuntimeException("unexpected element type: $elementTypeMirror") } @@ -660,6 +661,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi Constants.RealmFieldType.BINARY_LIST, Constants.RealmFieldType.DATE_LIST, Constants.RealmFieldType.FLOAT_LIST, + Constants.RealmFieldType.DECIMAL128_LIST, + Constants.RealmFieldType.OBJECT_ID_LIST, Constants.RealmFieldType.DOUBLE_LIST -> { val requiredFlag = if (metadata.isElementNullable(field)) "!Property.REQUIRED" else "Property.REQUIRED" emitStatement("builder.addPersistedValueListProperty(\"%s\", %s, %s)", fieldName, fieldType.realmType, requiredFlag) @@ -674,6 +677,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi Constants.RealmFieldType.STRING, Constants.RealmFieldType.DATE, Constants.RealmFieldType.BINARY, + Constants.RealmFieldType.DECIMAL128, + Constants.RealmFieldType.OBJECT_ID, Constants.RealmFieldType.REALM_INTEGER -> { val nullableFlag = (if (metadata.isNullable(field)) "!" else "") + "Property.REQUIRED" val indexedFlag = (if (metadata.isIndexed(field)) "" else "!") + "Property.INDEXED" @@ -792,31 +797,46 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi if (metadata.isNullable(primaryKeyElement!!)) { if (Utils.isString(primaryKeyElement)) { emitStatement("String value = ((%s) object).%s()", interfaceName, primaryKeyGetter) - emitStatement("long colKey = Table.NO_MATCH") + emitStatement("long objKey = Table.NO_MATCH") beginControlFlow("if (value == null)") - emitStatement("colKey = table.findFirstNull(pkColumnKey)") + emitStatement("objKey = table.findFirstNull(pkColumnKey)") nextControlFlow("else") - emitStatement("colKey = table.findFirstString(pkColumnKey, value)") + emitStatement("objKey = table.findFirstString(pkColumnKey, value)") + endControlFlow() + } else if (Utils.isObjectId(primaryKeyElement)) { + emitStatement("org.bson.types.ObjectId value = ((%s) object).%s()", interfaceName, primaryKeyGetter) + emitStatement("long objKey = Table.NO_MATCH") + beginControlFlow("if (value == null)") + emitStatement("objKey = table.findFirstNull(pkColumnKey)") + nextControlFlow("else") + emitStatement("objKey = table.findFirstObjectId(pkColumnKey, value)") endControlFlow() } else { emitStatement("Number value = ((%s) object).%s()", interfaceName, primaryKeyGetter) - emitStatement("long colKey = Table.NO_MATCH") + emitStatement("long objKey = Table.NO_MATCH") beginControlFlow("if (value == null)") - emitStatement("colKey = table.findFirstNull(pkColumnKey)") + emitStatement("objKey = table.findFirstNull(pkColumnKey)") nextControlFlow("else") - emitStatement("colKey = table.findFirstLong(pkColumnKey, value.longValue())") + emitStatement("objKey = table.findFirstLong(pkColumnKey, value.longValue())") endControlFlow() } } else { - val pkType = if (Utils.isString(metadata.primaryKey)) "String" else "Long" - emitStatement("long colKey = table.findFirst%s(pkColumnKey, ((%s) object).%s())", pkType, interfaceName, primaryKeyGetter) + if (Utils.isString(primaryKeyElement)) { + emitStatement("long objKey = table.findFirstString(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) + + } else if (Utils.isObjectId(primaryKeyElement)) { + emitStatement("long objKey = table.findFirstObjectId(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) + + } else { + emitStatement("long objKey = table.findFirstLong(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) + } } - beginControlFlow("if (colKey == Table.NO_MATCH)") + beginControlFlow("if (objKey == Table.NO_MATCH)") emitStatement("canUpdate = false") nextControlFlow("else") beginControlFlow("try") - emitStatement("objectContext.set(realm, table.getUncheckedRow(colKey), columnInfo, false, Collections. emptyList())") + emitStatement("objectContext.set(realm, table.getUncheckedRow(objKey), columnInfo, false, Collections. emptyList())") emitStatement("realmObject = new %s()", generatedClassName) emitStatement("cache.put(object, (RealmObjectProxy) realmObject)") nextControlFlow("finally") @@ -841,7 +861,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi "int", "short", "byte" -> { - emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, colKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, objKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) } "java.lang.Long", "java.lang.Integer", @@ -849,92 +869,112 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi "java.lang.Byte" -> { emitStatement("Number %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, colKey, %s.longValue(), false)", fieldName, getter) + emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, objKey, %s.longValue(), false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) } endControlFlow() } "io.realm.MutableRealmInteger" -> { emitStatement("Long %s = ((%s) object).%s().get()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, colKey, %s.longValue(), false)", fieldName, getter) + emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, objKey, %s.longValue(), false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) } endControlFlow() } "double" -> { - emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sColKey, colKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sColKey, objKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) } "java.lang.Double" -> { emitStatement("Double %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) } endControlFlow() } "float" -> { - emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sColKey, colKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sColKey, objKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) } "java.lang.Float" -> { emitStatement("Float %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) } endControlFlow() } "boolean" -> { - emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sColKey, colKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) + emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sColKey, objKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter) } "java.lang.Boolean" -> { emitStatement("Boolean %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) } endControlFlow() } "byte[]" -> { emitStatement("byte[] %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) } endControlFlow() } "java.util.Date" -> { emitStatement("java.util.Date %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sColKey, colKey, %s.getTime(), false)", fieldName, getter) + emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sColKey, objKey, %s.getTime(), false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) } endControlFlow() } "java.lang.String" -> { emitStatement("String %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sColKey, colKey, %s, false)", fieldName, getter) + emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter) if (isUpdate) { nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, colKey, false)", fieldName) + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) } endControlFlow() } + "org.bson.types.Decimal128" -> { + emitStatement("org.bson.types.Decimal128 %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetDecimal128(tableNativePtr, columnInfo.%1\$sColKey, objKey, %2\$s.getLow(), %2\$s.getHigh(), false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) + } + endControlFlow() + } + "org.bson.types.ObjectId" -> { + emitStatement("org.bson.types.ObjectId %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetObjectId(tableNativePtr, columnInfo.%sColKey, objKey, %s.toString(), false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) + } + endControlFlow() + } else -> { throw IllegalStateException("Unsupported type $fieldType") } @@ -975,7 +1015,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache%s == null)", fieldName) emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, colKey, cache%1\$s, false)", fieldName) + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) endControlFlow() } Utils.isRealmModelList(field) -> { @@ -983,7 +1023,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) beginControlFlow("if (cacheItemIndex%s == null)", fieldName) @@ -999,7 +1039,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) beginControlFlow("if (%1\$sItem == null)", fieldName) emitStatement(fieldName + "OsList.addNull()") @@ -1017,7 +1057,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } } - emitStatement("return colKey") + emitStatement("return objKey") endMethod() emitEmptyLine() } @@ -1060,14 +1100,14 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache%s == null)", fieldName) emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) endControlFlow() - emitStatement("table.setLink(columnInfo.%1\$sColKey, colKey, cache%1\$s, false)", fieldName) + emitStatement("table.setLink(columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) endControlFlow() } else if (Utils.isRealmModelList(field)) { val genericType = Utils.getGenericTypeQualifiedName(field) emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) beginControlFlow("if (cacheItemIndex%s == null)", fieldName) @@ -1082,7 +1122,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) beginControlFlow("if (%1\$sItem == null)", fieldName) emitStatement("%1\$sOsList.addNull()", fieldName) @@ -1134,15 +1174,15 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache%s == null)", fieldName) emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, colKey, cache%1\$s, false)", fieldName) + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, colKey)", fieldName) + emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, objKey)", fieldName) endControlFlow() } else if (Utils.isRealmModelList(field)) { val genericType = Utils.getGenericTypeQualifiedName(field) emitEmptyLine() - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%1\$sList != null && %1\$sList.size() == %1\$sOsList.size())", fieldName) emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") @@ -1172,7 +1212,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val genericType = Utils.getGenericTypeQualifiedName(field) val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) emitEmptyLine() - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("%1\$sOsList.removeAll()", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) @@ -1192,7 +1232,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } } - emitStatement("return colKey") + emitStatement("return objKey") endMethod() emitEmptyLine() } @@ -1235,16 +1275,16 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache%s == null)", fieldName) emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, colKey, cache%1\$s, false)", fieldName) + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. - emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, colKey)", fieldName) + emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, objKey)", fieldName) endControlFlow() } Utils.isRealmModelList(field) -> { val genericType = Utils.getGenericTypeQualifiedName(field) emitEmptyLine() - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%1\$sList != null && %1\$sList.size() == %1\$sOsList.size())", fieldName) emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.") @@ -1275,7 +1315,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val genericType = Utils.getGenericTypeQualifiedName(field) val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field) emitEmptyLine() - emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.%1\$sColKey)", fieldName) + emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("%1\$sOsList.removeAll()", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) @@ -1311,38 +1351,48 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi if (metadata.isNullable(primaryKeyElement!!)) { if (Utils.isString(primaryKeyElement)) { emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - emitStatement("long colKey = Table.NO_MATCH") + emitStatement("long objKey = Table.NO_MATCH") + beginControlFlow("if (primaryKeyValue == null)") + emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") + nextControlFlow("else") + emitStatement("objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue)") + endControlFlow() + } else if (Utils.isObjectId(primaryKeyElement)) { + emitStatement("org.bson.types.ObjectId primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) + emitStatement("long objKey = Table.NO_MATCH") beginControlFlow("if (primaryKeyValue == null)") - emitStatement("colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") + emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") nextControlFlow("else") - emitStatement("colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue)") + emitStatement("objKey = Table.nativeFindFirstObjectId(tableNativePtr, pkColumnKey, primaryKeyValue.toString())") endControlFlow() } else { emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) - emitStatement("long colKey = Table.NO_MATCH") + emitStatement("long objKey = Table.NO_MATCH") beginControlFlow("if (primaryKeyValue == null)") - emitStatement("colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") + emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") nextControlFlow("else") - emitStatement("colKey = Table.nativeFindFirstInt(tableNativePtr, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) + emitStatement("objKey = Table.nativeFindFirstInt(tableNativePtr, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) endControlFlow() } } else { - emitStatement("long colKey = Table.NO_MATCH") + emitStatement("long objKey = Table.NO_MATCH") emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) beginControlFlow("if (primaryKeyValue != null)") if (Utils.isString(metadata.primaryKey)) { - emitStatement("colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, (String)primaryKeyValue)") + emitStatement("objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, (String)primaryKeyValue)") + } else if (Utils.isObjectId(metadata.primaryKey)) { + emitStatement("objKey = Table.nativeFindFirstObjectId(tableNativePtr, pkColumnKey, ((org.bson.types.ObjectId)primaryKeyValue).toString())") } else { - emitStatement("colKey = Table.nativeFindFirstInt(tableNativePtr, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) + emitStatement("objKey = Table.nativeFindFirstInt(tableNativePtr, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) } endControlFlow() } - beginControlFlow("if (colKey == Table.NO_MATCH)") - if (Utils.isString(metadata.primaryKey)) { - emitStatement("colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue)") + beginControlFlow("if (objKey == Table.NO_MATCH)") + if (Utils.isString(metadata.primaryKey) || Utils.isObjectId(metadata.primaryKey)) { + emitStatement("objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue)") } else { - emitStatement("colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) + emitStatement("objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) } if (throwIfPrimaryKeyDuplicate) { @@ -1350,10 +1400,10 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("Table.throwDuplicatePrimaryKeyException(primaryKeyValue)") } endControlFlow() - emitStatement("cache.put(object, colKey)") + emitStatement("cache.put(object, objKey)") } else { - emitStatement("long colKey = OsObject.createRow(table)") - emitStatement("cache.put(object, colKey)") + emitStatement("long objKey = OsObject.createRow(table)") + emitStatement("cache.put(object, objKey)") } } } @@ -1666,12 +1716,12 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginMethod("int", "hashCode", EnumSet.of(Modifier.PUBLIC)) emitStatement("String realmName = proxyState.getRealm\$realm().getPath()") emitStatement("String tableName = proxyState.getRow\$realm().getTable().getName()") - emitStatement("long colKey = proxyState.getRow\$realm().getObjectKey()") + emitStatement("long objKey = proxyState.getRow\$realm().getObjectKey()") emitEmptyLine() emitStatement("int result = 17") emitStatement("result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0)") emitStatement("result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0)") - emitStatement("result = 31 * result + (int) (colKey ^ (colKey >>> 32))") + emitStatement("result = 31 * result + (int) (objKey ^ (objKey >>> 32))") emitStatement("return result") endMethod() emitEmptyLine() @@ -1731,28 +1781,38 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi buildExcludeFieldsList(writer, metadata.fields) emitStatement("%s obj = realm.createObjectInternal(%s.class, true, excludeFields)", qualifiedJavaClassName, qualifiedJavaClassName) } else { - val pkType = if (Utils.isString(metadata.primaryKey)) "String" else "Long" + var pkType = "Long" + var jsonAccessorMethodSuffix = "Long" + var findFirstCast = "" + if (Utils.isString(metadata.primaryKey)) { + pkType = "String" + jsonAccessorMethodSuffix= "String" + } else if (Utils.isObjectId(metadata.primaryKey)) { + pkType = "ObjectId" + findFirstCast = "(org.bson.types.ObjectId)" + jsonAccessorMethodSuffix = "" + } emitStatement("%s obj = null", qualifiedJavaClassName) beginControlFlow("if (update)") emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey)) - emitStatement("long colKey = Table.NO_MATCH") + emitStatement("long objKey = Table.NO_MATCH") if (metadata.isNullable(metadata.primaryKey!!)) { beginControlFlow("if (json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) - emitStatement("colKey = table.findFirstNull(pkColumnKey)") + emitStatement("objKey = table.findFirstNull(pkColumnKey)") nextControlFlow("else") - emitStatement("colKey = table.findFirst%s(pkColumnKey, json.get%s(\"%s\"))", pkType, pkType, metadata.primaryKey!!.simpleName) + emitStatement("objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))", pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) endControlFlow() } else { beginControlFlow("if (!json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) - emitStatement("colKey = table.findFirst%s(pkColumnKey, json.get%s(\"%s\"))", pkType, pkType, metadata.primaryKey!!.simpleName) + emitStatement("objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))", pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) endControlFlow() } - beginControlFlow("if (colKey != Table.NO_MATCH)") + beginControlFlow("if (objKey != Table.NO_MATCH)") emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") beginControlFlow("try") - emitStatement("objectContext.set(realm, table.getUncheckedRow(colKey), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", qualifiedJavaClassName) + emitStatement("objectContext.set(realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", qualifiedJavaClassName) emitStatement("obj = new %s()", generatedClassName) nextControlFlow("finally") emitStatement("objectContext.clear()") @@ -1803,13 +1863,12 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi metadata.getInternalSetter(fieldName), fieldName, qualifiedFieldType, - writer - ) + writer) } } emitStatement("return obj") endMethod() - emitEmptyLine() + emitEmptyLine() } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt index e917487f0a..ab27ca71f1 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt @@ -16,6 +16,8 @@ @file:JvmName("TypeMirrors") package io.realm.processor +import org.bson.types.Decimal128 +import org.bson.types.ObjectId import java.util.Date import javax.annotation.processing.ProcessingEnvironment @@ -42,6 +44,8 @@ class TypeMirrors(env: ProcessingEnvironment) { @JvmField val DOUBLE_MIRROR: TypeMirror @JvmField val FLOAT_MIRROR: TypeMirror @JvmField val DATE_MIRROR: TypeMirror + @JvmField val DECIMAL128_MIRROR: TypeMirror + @JvmField val OBJECT_ID_MIRROR: TypeMirror @JvmField val PRIMITIVE_LONG_MIRROR: TypeMirror @JvmField val PRIMITIVE_INT_MIRROR: TypeMirror @@ -62,6 +66,8 @@ class TypeMirrors(env: ProcessingEnvironment) { DOUBLE_MIRROR = elementUtils.getTypeElement(Double::class.javaObjectType.name).asType() FLOAT_MIRROR = elementUtils.getTypeElement(Float::class.javaObjectType.name).asType() DATE_MIRROR = elementUtils.getTypeElement(Date::class.javaObjectType.name).asType() + DECIMAL128_MIRROR = elementUtils.getTypeElement(Decimal128::class.javaObjectType.name).asType() + OBJECT_ID_MIRROR = elementUtils.getTypeElement(ObjectId::class.javaObjectType.name).asType() PRIMITIVE_LONG_MIRROR = typeUtils.getPrimitiveType(TypeKind.LONG) PRIMITIVE_INT_MIRROR = typeUtils.getPrimitiveType(TypeKind.INT) @@ -79,7 +85,7 @@ class TypeMirrors(env: ProcessingEnvironment) { return null } val typeArguments = (field.asType() as DeclaredType).typeArguments - return if (!typeArguments.isEmpty()) typeArguments[0] else null + return if (typeArguments.isNotEmpty()) typeArguments[0] else null } } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt index a84b674d8e..dd2264784c 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt @@ -104,6 +104,17 @@ object Utils { return getFieldTypeQualifiedName(field).toString() == "java.lang.String" } + /** + * @return `true` if a field is of type "org.bson.types.ObjectId", `false` otherwise. + * @throws IllegalArgumentException if the field is `null`. + */ + fun isObjectId(field: VariableElement?): Boolean { + if (field == null) { + throw IllegalArgumentException("Argument 'field' cannot be null.") + } + return getFieldTypeQualifiedName(field).toString() == "org.bson.types.ObjectId" + } + /** * @return `true` if a field is a primitive type, `false` otherwise. * @throws IllegalArgumentException if the typeString is `null`. diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index 2da49cede1..359a33e03e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -44,6 +44,8 @@ static final class AllTypesColumnInfo extends ColumnInfo { long columnFloatColKey; long columnDoubleColKey; long columnBooleanColKey; + long columnDecimal128ColKey; + long columnObjectIdColKey; long columnDateColKey; long columnBinaryColKey; long columnMutableRealmIntegerColKey; @@ -59,15 +61,19 @@ static final class AllTypesColumnInfo extends ColumnInfo { long columnDoubleListColKey; long columnFloatListColKey; long columnDateListColKey; + long columnDecimal128ListColKey; + long columnObjectIdListColKey; AllTypesColumnInfo(OsSchemaInfo schemaInfo) { - super(20); + super(24); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("AllTypes"); this.columnStringColKey = addColumnDetails("columnString", "columnString", objectSchemaInfo); this.columnLongColKey = addColumnDetails("columnLong", "columnLong", objectSchemaInfo); this.columnFloatColKey = addColumnDetails("columnFloat", "columnFloat", objectSchemaInfo); this.columnDoubleColKey = addColumnDetails("columnDouble", "columnDouble", objectSchemaInfo); this.columnBooleanColKey = addColumnDetails("columnBoolean", "columnBoolean", objectSchemaInfo); + this.columnDecimal128ColKey = addColumnDetails("columnDecimal128", "columnDecimal128", objectSchemaInfo); + this.columnObjectIdColKey = addColumnDetails("columnObjectId", "columnObjectId", objectSchemaInfo); this.columnDateColKey = addColumnDetails("columnDate", "columnDate", objectSchemaInfo); this.columnBinaryColKey = addColumnDetails("columnBinary", "columnBinary", objectSchemaInfo); this.columnMutableRealmIntegerColKey = addColumnDetails("columnMutableRealmInteger", "columnMutableRealmInteger", objectSchemaInfo); @@ -83,6 +89,8 @@ static final class AllTypesColumnInfo extends ColumnInfo { this.columnDoubleListColKey = addColumnDetails("columnDoubleList", "columnDoubleList", objectSchemaInfo); this.columnFloatListColKey = addColumnDetails("columnFloatList", "columnFloatList", objectSchemaInfo); this.columnDateListColKey = addColumnDetails("columnDateList", "columnDateList", objectSchemaInfo); + this.columnDecimal128ListColKey = addColumnDetails("columnDecimal128List", "columnDecimal128List", objectSchemaInfo); + this.columnObjectIdListColKey = addColumnDetails("columnObjectIdList", "columnObjectIdList", objectSchemaInfo); addBacklinkDetails(schemaInfo, "parentObjects", "AllTypes", "columnObject"); } @@ -105,6 +113,8 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.columnFloatColKey = src.columnFloatColKey; dst.columnDoubleColKey = src.columnDoubleColKey; dst.columnBooleanColKey = src.columnBooleanColKey; + dst.columnDecimal128ColKey = src.columnDecimal128ColKey; + dst.columnObjectIdColKey = src.columnObjectIdColKey; dst.columnDateColKey = src.columnDateColKey; dst.columnBinaryColKey = src.columnBinaryColKey; dst.columnMutableRealmIntegerColKey = src.columnMutableRealmIntegerColKey; @@ -120,6 +130,8 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.columnDoubleListColKey = src.columnDoubleListColKey; dst.columnFloatListColKey = src.columnFloatListColKey; dst.columnDateListColKey = src.columnDateListColKey; + dst.columnDecimal128ListColKey = src.columnDecimal128ListColKey; + dst.columnObjectIdListColKey = src.columnObjectIdListColKey; } } @@ -142,6 +154,8 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private RealmList columnDoubleListRealmList; private RealmList columnFloatListRealmList; private RealmList columnDateListRealmList; + private RealmList columnDecimal128ListRealmList; + private RealmList columnObjectIdListRealmList; private RealmResults parentObjectsBacklinks; some_test_AllTypesRealmProxy() { @@ -268,6 +282,62 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { proxyState.getRow$realm().setBoolean(columnInfo.columnBooleanColKey, value); } + @Override + @SuppressWarnings("cast") + public org.bson.types.Decimal128 realmGet$columnDecimal128() { + proxyState.getRealm$realm().checkIfValid(); + return (org.bson.types.Decimal128) proxyState.getRow$realm().getDecimal128(columnInfo.columnDecimal128ColKey); + } + + @Override + public void realmSet$columnDecimal128(org.bson.types.Decimal128 value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'columnDecimal128' to null."); + } + row.getTable().setDecimal128(columnInfo.columnDecimal128ColKey, row.getObjectKey(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'columnDecimal128' to null."); + } + proxyState.getRow$realm().setDecimal128(columnInfo.columnDecimal128ColKey, value); + } + + @Override + @SuppressWarnings("cast") + public org.bson.types.ObjectId realmGet$columnObjectId() { + proxyState.getRealm$realm().checkIfValid(); + return (org.bson.types.ObjectId) proxyState.getRow$realm().getObjectId(columnInfo.columnObjectIdColKey); + } + + @Override + public void realmSet$columnObjectId(org.bson.types.ObjectId value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'columnObjectId' to null."); + } + row.getTable().setObjectId(columnInfo.columnObjectIdColKey, row.getObjectKey(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'columnObjectId' to null."); + } + proxyState.getRow$realm().setObjectId(columnInfo.columnObjectIdColKey, value); + } + @Override @SuppressWarnings("cast") public Date realmGet$columnDate() { @@ -822,6 +892,84 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } } + @Override + public RealmList realmGet$columnDecimal128List() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnDecimal128ListRealmList != null) { + return columnDecimal128ListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDecimal128ListColKey, RealmFieldType.DECIMAL128_LIST); + columnDecimal128ListRealmList = new RealmList(org.bson.types.Decimal128.class, osList, proxyState.getRealm$realm()); + return columnDecimal128ListRealmList; + } + } + + @Override + public void realmSet$columnDecimal128List(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnDecimal128List")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnDecimal128ListColKey, RealmFieldType.DECIMAL128_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (org.bson.types.Decimal128 item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addDecimal128(item); + } + } + } + + @Override + public RealmList realmGet$columnObjectIdList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnObjectIdListRealmList != null) { + return columnObjectIdListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnObjectIdListColKey, RealmFieldType.OBJECT_ID_LIST); + columnObjectIdListRealmList = new RealmList(org.bson.types.ObjectId.class, osList, proxyState.getRealm$realm()); + return columnObjectIdListRealmList; + } + } + + @Override + public void realmSet$columnObjectIdList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnObjectIdList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnObjectIdListColKey, RealmFieldType.OBJECT_ID_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (org.bson.types.ObjectId item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addObjectId(item); + } + } + } + @Override public RealmResults realmGet$parentObjects() { BaseRealm realm = proxyState.getRealm$realm(); @@ -834,12 +982,14 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", 20, 1); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", 24, 1); builder.addPersistedProperty("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnDouble", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addPersistedProperty("columnDecimal128", RealmFieldType.DECIMAL128, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addPersistedProperty("columnObjectId", RealmFieldType.OBJECT_ID, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnMutableRealmInteger", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); @@ -855,6 +1005,8 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { builder.addPersistedValueListProperty("columnDoubleList", RealmFieldType.DOUBLE_LIST, !Property.REQUIRED); builder.addPersistedValueListProperty("columnFloatList", RealmFieldType.FLOAT_LIST, !Property.REQUIRED); builder.addPersistedValueListProperty("columnDateList", RealmFieldType.DATE_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnDecimal128List", RealmFieldType.DECIMAL128_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnObjectIdList", RealmFieldType.OBJECT_ID_LIST, !Property.REQUIRED); builder.addComputedLinkProperty("parentObjects", "AllTypes", "columnObject"); return builder.build(); } @@ -878,22 +1030,22 @@ public static final class ClassNameHelper { @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { - final List excludeFields = new ArrayList(12); + final List excludeFields = new ArrayList(14); some.test.AllTypes obj = null; if (update) { Table table = realm.getTable(some.test.AllTypes.class); AllTypesColumnInfo columnInfo = (AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class); long pkColumnKey = columnInfo.columnStringColKey; - long colKey = Table.NO_MATCH; + long objKey = Table.NO_MATCH; if (json.isNull("columnString")) { - colKey = table.findFirstNull(pkColumnKey); + objKey = table.findFirstNull(pkColumnKey); } else { - colKey = table.findFirstString(pkColumnKey, json.getString("columnString")); + objKey = table.findFirstString(pkColumnKey, json.getString("columnString")); } - if (colKey != Table.NO_MATCH) { + if (objKey != Table.NO_MATCH) { final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); try { - objectContext.set(realm, table.getUncheckedRow(colKey), realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); + objectContext.set(realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections. emptyList()); obj = new io.realm.some_test_AllTypesRealmProxy(); } finally { objectContext.clear(); @@ -937,6 +1089,12 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON if (json.has("columnDateList")) { excludeFields.add("columnDateList"); } + if (json.has("columnDecimal128List")) { + excludeFields.add("columnDecimal128List"); + } + if (json.has("columnObjectIdList")) { + excludeFields.add("columnObjectIdList"); + } if (json.has("columnString")) { if (json.isNull("columnString")) { obj = (io.realm.some_test_AllTypesRealmProxy) realm.createObjectInternal(some.test.AllTypes.class, null, true, excludeFields); @@ -977,6 +1135,38 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON objProxy.realmSet$columnBoolean((boolean) json.getBoolean("columnBoolean")); } } + if (json.has("columnDecimal128")) { + if (json.isNull("columnDecimal128")) { + objProxy.realmSet$columnDecimal128(null); + } else { + Object decimal = json.get("columnDecimal128"); + if (decimal instanceof org.bson.types.Decimal128) { + objProxy.realmSet$columnDecimal128((org.bson.types.Decimal128) decimal); + } else if (decimal instanceof String) { + objProxy.realmSet$columnDecimal128(org.bson.types.Decimal128.parse((String)decimal)); + } else if (decimal instanceof Integer) { + objProxy.realmSet$columnDecimal128(new org.bson.types.Decimal128((Integer)(decimal))); + } else if (decimal instanceof Long) { + objProxy.realmSet$columnDecimal128(new org.bson.types.Decimal128((Long)(decimal))); + } else if (decimal instanceof Double) { + objProxy.realmSet$columnDecimal128(new org.bson.types.Decimal128(new java.math.BigDecimal((Double)(decimal)))); + } else { + throw new UnsupportedOperationException(decimal.getClass() + " is not supported as a Decimal128 value"); + } + } + } + if (json.has("columnObjectId")) { + if (json.isNull("columnObjectId")) { + objProxy.realmSet$columnObjectId(null); + } else { + Object id = json.get("columnObjectId"); + if (id instanceof org.bson.types.ObjectId) { + objProxy.realmSet$columnObjectId((org.bson.types.ObjectId) id); + } else { + objProxy.realmSet$columnObjectId(new org.bson.types.ObjectId((String)id)); + } + } + } if (json.has("columnDate")) { if (json.isNull("columnDate")) { objProxy.realmSet$columnDate(null); @@ -1029,6 +1219,8 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnDoubleList(), json, "columnDoubleList"); ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnFloatList(), json, "columnFloatList"); ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnDateList(), json, "columnDateList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnDecimal128List(), json, "columnDecimal128List"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnObjectIdList(), json, "columnObjectIdList"); return obj; } @@ -1079,6 +1271,20 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r reader.skipValue(); throw new IllegalArgumentException("Trying to set non-nullable field 'columnBoolean' to null."); } + } else if (name.equals("columnDecimal128")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$columnDecimal128(null); + } else { + objProxy.realmSet$columnDecimal128(org.bson.types.Decimal128.parse(reader.nextString())); + } + } else if (name.equals("columnObjectId")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$columnObjectId(null); + } else { + objProxy.realmSet$columnObjectId(new org.bson.types.ObjectId(reader.nextString())); + } } else if (name.equals("columnDate")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); @@ -1147,6 +1353,10 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r objProxy.realmSet$columnFloatList(ProxyUtils.createRealmListWithJsonStream(java.lang.Float.class, reader)); } else if (name.equals("columnDateList")) { objProxy.realmSet$columnDateList(ProxyUtils.createRealmListWithJsonStream(java.util.Date.class, reader)); + } else if (name.equals("columnDecimal128List")) { + objProxy.realmSet$columnDecimal128List(ProxyUtils.createRealmListWithJsonStream(org.bson.types.Decimal128.class, reader)); + } else if (name.equals("columnObjectIdList")) { + objProxy.realmSet$columnObjectIdList(ProxyUtils.createRealmListWithJsonStream(org.bson.types.ObjectId.class, reader)); } else { reader.skipValue(); } @@ -1189,17 +1399,17 @@ public static some.test.AllTypes copyOrUpdate(Realm realm, AllTypesColumnInfo co Table table = realm.getTable(some.test.AllTypes.class); long pkColumnKey = columnInfo.columnStringColKey; String value = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); - long colKey = Table.NO_MATCH; + long objKey = Table.NO_MATCH; if (value == null) { - colKey = table.findFirstNull(pkColumnKey); + objKey = table.findFirstNull(pkColumnKey); } else { - colKey = table.findFirstString(pkColumnKey, value); + objKey = table.findFirstString(pkColumnKey, value); } - if (colKey == Table.NO_MATCH) { + if (objKey == Table.NO_MATCH) { canUpdate = false; } else { try { - objectContext.set(realm, table.getUncheckedRow(colKey), columnInfo, false, Collections. emptyList()); + objectContext.set(realm, table.getUncheckedRow(objKey), columnInfo, false, Collections. emptyList()); realmObject = new io.realm.some_test_AllTypesRealmProxy(); cache.put(object, (RealmObjectProxy) realmObject); } finally { @@ -1228,6 +1438,8 @@ public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo builder.addFloat(columnInfo.columnFloatColKey, realmObjectSource.realmGet$columnFloat()); builder.addDouble(columnInfo.columnDoubleColKey, realmObjectSource.realmGet$columnDouble()); builder.addBoolean(columnInfo.columnBooleanColKey, realmObjectSource.realmGet$columnBoolean()); + builder.addDecimal128(columnInfo.columnDecimal128ColKey, realmObjectSource.realmGet$columnDecimal128()); + builder.addObjectId(columnInfo.columnObjectIdColKey, realmObjectSource.realmGet$columnObjectId()); builder.addDate(columnInfo.columnDateColKey, realmObjectSource.realmGet$columnDate()); builder.addByteArray(columnInfo.columnBinaryColKey, realmObjectSource.realmGet$columnBinary()); builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, realmObjectSource.realmGet$columnMutableRealmInteger()); @@ -1241,6 +1453,8 @@ public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo builder.addDoubleList(columnInfo.columnDoubleListColKey, realmObjectSource.realmGet$columnDoubleList()); builder.addFloatList(columnInfo.columnFloatListColKey, realmObjectSource.realmGet$columnFloatList()); builder.addDateList(columnInfo.columnDateListColKey, realmObjectSource.realmGet$columnDateList()); + builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, realmObjectSource.realmGet$columnDecimal128List()); + builder.addObjectIdList(columnInfo.columnObjectIdListColKey, realmObjectSource.realmGet$columnObjectIdList()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. @@ -1288,33 +1502,41 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null) { - OsList columnRealmListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnRealmListColKey); + OsList columnRealmListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnRealmListColKey); for (some.test.AllTypes columnRealmListItem : columnRealmListList) { Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { @@ -1340,7 +1562,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { - OsList columnStringListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnStringListColKey); + OsList columnStringListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnStringListColKey); for (java.lang.String columnStringListItem : columnStringListList) { if (columnStringListItem == null) { columnStringListOsList.addNull(); @@ -1352,7 +1574,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { - OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBinaryListColKey); + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnBinaryListColKey); for (byte[] columnBinaryListItem : columnBinaryListList) { if (columnBinaryListItem == null) { columnBinaryListOsList.addNull(); @@ -1364,7 +1586,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { - OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBooleanListColKey); + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnBooleanListColKey); for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { if (columnBooleanListItem == null) { columnBooleanListOsList.addNull(); @@ -1376,7 +1598,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { - OsList columnLongListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnLongListColKey); + OsList columnLongListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnLongListColKey); for (java.lang.Long columnLongListItem : columnLongListList) { if (columnLongListItem == null) { columnLongListOsList.addNull(); @@ -1388,7 +1610,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { - OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnIntegerListColKey); + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnIntegerListColKey); for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { if (columnIntegerListItem == null) { columnIntegerListOsList.addNull(); @@ -1400,7 +1622,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { - OsList columnShortListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnShortListColKey); + OsList columnShortListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnShortListColKey); for (java.lang.Short columnShortListItem : columnShortListList) { if (columnShortListItem == null) { columnShortListOsList.addNull(); @@ -1412,7 +1634,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { - OsList columnByteListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnByteListColKey); + OsList columnByteListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnByteListColKey); for (java.lang.Byte columnByteListItem : columnByteListList) { if (columnByteListItem == null) { columnByteListOsList.addNull(); @@ -1424,7 +1646,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { - OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDoubleListColKey); + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnDoubleListColKey); for (java.lang.Double columnDoubleListItem : columnDoubleListList) { if (columnDoubleListItem == null) { columnDoubleListOsList.addNull(); @@ -1436,7 +1658,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { - OsList columnFloatListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnFloatListColKey); + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnFloatListColKey); for (java.lang.Float columnFloatListItem : columnFloatListList) { if (columnFloatListItem == null) { columnFloatListOsList.addNull(); @@ -1448,7 +1670,7 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { - OsList columnDateListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDateListColKey); + OsList columnDateListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnDateListColKey); for (java.util.Date columnDateListItem : columnDateListList) { if (columnDateListItem == null) { columnDateListOsList.addNull(); @@ -1457,7 +1679,31 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnDecimal128ListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDecimal128List(); + if (columnDecimal128ListList != null) { + OsList columnDecimal128ListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnDecimal128ListColKey); + for (org.bson.types.Decimal128 columnDecimal128ListItem : columnDecimal128ListList) { + if (columnDecimal128ListItem == null) { + columnDecimal128ListOsList.addNull(); + } else { + columnDecimal128ListOsList.addDecimal128(columnDecimal128ListItem); + } + } + } + + RealmList columnObjectIdListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObjectIdList(); + if (columnObjectIdListList != null) { + OsList columnObjectIdListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnObjectIdListColKey); + for (org.bson.types.ObjectId columnObjectIdListItem : columnObjectIdListList) { + if (columnObjectIdListItem == null) { + columnObjectIdListOsList.addNull(); + } else { + columnObjectIdListOsList.addObjectId(columnObjectIdListItem); + } + } + } + return objKey; } public static void insert(Realm realm, Iterator objects, Map cache) { @@ -1476,33 +1722,41 @@ public static void insert(Realm realm, Iterator objects, M continue; } String primaryKeyValue = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); - long colKey = Table.NO_MATCH; + long objKey = Table.NO_MATCH; if (primaryKeyValue == null) { - colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); } else { - colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); } - if (colKey == Table.NO_MATCH) { - colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); } else { Table.throwDuplicatePrimaryKeyException(primaryKeyValue); } - cache.put(object, colKey); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + cache.put(object, objKey); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongColKey, objKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatColKey, objKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleColKey, objKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanColKey, objKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + org.bson.types.Decimal128 realmGet$columnDecimal128 = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDecimal128(); + if (realmGet$columnDecimal128 != null) { + Table.nativeSetDecimal128(tableNativePtr, columnInfo.columnDecimal128ColKey, objKey, realmGet$columnDecimal128.getLow(), realmGet$columnDecimal128.getHigh(), false); + } + org.bson.types.ObjectId realmGet$columnObjectId = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObjectId(); + if (realmGet$columnObjectId != null) { + Table.nativeSetObjectId(tableNativePtr, columnInfo.columnObjectIdColKey, objKey, realmGet$columnObjectId.toString(), false); + } java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, colKey, realmGet$columnDate.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, objKey, realmGet$columnDate.getTime(), false); } byte[] realmGet$columnBinary = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryColKey, colKey, realmGet$columnBinary, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryColKey, objKey, realmGet$columnBinary, false); } Long realmGet$columnMutableRealmInteger = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); if (realmGet$columnMutableRealmInteger != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, colKey, realmGet$columnMutableRealmInteger.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, objKey, realmGet$columnMutableRealmInteger.longValue(), false); } some.test.AllTypes columnObjectObj = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObject(); @@ -1511,12 +1765,12 @@ public static void insert(Realm realm, Iterator objects, M if (cachecolumnObject == null) { cachecolumnObject = some_test_AllTypesRealmProxy.insert(realm, columnObjectObj, cache); } - table.setLink(columnInfo.columnObjectColKey, colKey, cachecolumnObject, false); + table.setLink(columnInfo.columnObjectColKey, objKey, cachecolumnObject, false); } RealmList columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null) { - OsList columnRealmListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnRealmListColKey); + OsList columnRealmListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnRealmListColKey); for (some.test.AllTypes columnRealmListItem : columnRealmListList) { Long cacheItemIndexcolumnRealmList = cache.get(columnRealmListItem); if (cacheItemIndexcolumnRealmList == null) { @@ -1528,7 +1782,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { - OsList columnStringListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnStringListColKey); + OsList columnStringListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnStringListColKey); for (java.lang.String columnStringListItem : columnStringListList) { if (columnStringListItem == null) { columnStringListOsList.addNull(); @@ -1540,7 +1794,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { - OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBinaryListColKey); + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnBinaryListColKey); for (byte[] columnBinaryListItem : columnBinaryListList) { if (columnBinaryListItem == null) { columnBinaryListOsList.addNull(); @@ -1552,7 +1806,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { - OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBooleanListColKey); + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnBooleanListColKey); for (java.lang.Boolean columnBooleanListItem : columnBooleanListList) { if (columnBooleanListItem == null) { columnBooleanListOsList.addNull(); @@ -1564,7 +1818,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { - OsList columnLongListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnLongListColKey); + OsList columnLongListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnLongListColKey); for (java.lang.Long columnLongListItem : columnLongListList) { if (columnLongListItem == null) { columnLongListOsList.addNull(); @@ -1576,7 +1830,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { - OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnIntegerListColKey); + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnIntegerListColKey); for (java.lang.Integer columnIntegerListItem : columnIntegerListList) { if (columnIntegerListItem == null) { columnIntegerListOsList.addNull(); @@ -1588,7 +1842,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { - OsList columnShortListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnShortListColKey); + OsList columnShortListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnShortListColKey); for (java.lang.Short columnShortListItem : columnShortListList) { if (columnShortListItem == null) { columnShortListOsList.addNull(); @@ -1600,7 +1854,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { - OsList columnByteListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnByteListColKey); + OsList columnByteListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnByteListColKey); for (java.lang.Byte columnByteListItem : columnByteListList) { if (columnByteListItem == null) { columnByteListOsList.addNull(); @@ -1612,7 +1866,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { - OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDoubleListColKey); + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnDoubleListColKey); for (java.lang.Double columnDoubleListItem : columnDoubleListList) { if (columnDoubleListItem == null) { columnDoubleListOsList.addNull(); @@ -1624,7 +1878,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { - OsList columnFloatListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnFloatListColKey); + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnFloatListColKey); for (java.lang.Float columnFloatListItem : columnFloatListList) { if (columnFloatListItem == null) { columnFloatListOsList.addNull(); @@ -1636,7 +1890,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { - OsList columnDateListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDateListColKey); + OsList columnDateListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnDateListColKey); for (java.util.Date columnDateListItem : columnDateListList) { if (columnDateListItem == null) { columnDateListOsList.addNull(); @@ -1645,6 +1899,30 @@ public static void insert(Realm realm, Iterator objects, M } } } + + RealmList columnDecimal128ListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDecimal128List(); + if (columnDecimal128ListList != null) { + OsList columnDecimal128ListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnDecimal128ListColKey); + for (org.bson.types.Decimal128 columnDecimal128ListItem : columnDecimal128ListList) { + if (columnDecimal128ListItem == null) { + columnDecimal128ListOsList.addNull(); + } else { + columnDecimal128ListOsList.addDecimal128(columnDecimal128ListItem); + } + } + } + + RealmList columnObjectIdListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObjectIdList(); + if (columnObjectIdListList != null) { + OsList columnObjectIdListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnObjectIdListColKey); + for (org.bson.types.ObjectId columnObjectIdListItem : columnObjectIdListList) { + if (columnObjectIdListItem == null) { + columnObjectIdListOsList.addNull(); + } else { + columnObjectIdListOsList.addObjectId(columnObjectIdListItem); + } + } + } } } @@ -1657,37 +1935,49 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null && columnRealmListList.size() == columnRealmListOsList.size()) { // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. @@ -1728,7 +2018,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { @@ -1742,7 +2032,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { @@ -1756,7 +2046,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { @@ -1770,7 +2060,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { @@ -1784,7 +2074,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { @@ -1798,7 +2088,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { @@ -1812,7 +2102,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { @@ -1826,7 +2116,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { @@ -1840,7 +2130,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { @@ -1854,7 +2144,7 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { @@ -1867,7 +2157,35 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnDecimal128ListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDecimal128List(); + if (columnDecimal128ListList != null) { + for (org.bson.types.Decimal128 columnDecimal128ListItem : columnDecimal128ListList) { + if (columnDecimal128ListItem == null) { + columnDecimal128ListOsList.addNull(); + } else { + columnDecimal128ListOsList.addDecimal128(columnDecimal128ListItem); + } + } + } + + + OsList columnObjectIdListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnObjectIdListColKey); + columnObjectIdListOsList.removeAll(); + RealmList columnObjectIdListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObjectIdList(); + if (columnObjectIdListList != null) { + for (org.bson.types.ObjectId columnObjectIdListItem : columnObjectIdListList) { + if (columnObjectIdListItem == null) { + columnObjectIdListOsList.addNull(); + } else { + columnObjectIdListOsList.addObjectId(columnObjectIdListItem); + } + } + } + + return objKey; } public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { @@ -1886,37 +2204,49 @@ public static void insertOrUpdate(Realm realm, Iterator ob continue; } String primaryKeyValue = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnString(); - long colKey = Table.NO_MATCH; + long objKey = Table.NO_MATCH; if (primaryKeyValue == null) { - colKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); } else { - colKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + } + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + } + cache.put(object, objKey); + Table.nativeSetLong(tableNativePtr, columnInfo.columnLongColKey, objKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); + Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatColKey, objKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); + Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleColKey, objKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanColKey, objKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); + org.bson.types.Decimal128 realmGet$columnDecimal128 = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDecimal128(); + if (realmGet$columnDecimal128 != null) { + Table.nativeSetDecimal128(tableNativePtr, columnInfo.columnDecimal128ColKey, objKey, realmGet$columnDecimal128.getLow(), realmGet$columnDecimal128.getHigh(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.columnDecimal128ColKey, objKey, false); } - if (colKey == Table.NO_MATCH) { - colKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + org.bson.types.ObjectId realmGet$columnObjectId = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObjectId(); + if (realmGet$columnObjectId != null) { + Table.nativeSetObjectId(tableNativePtr, columnInfo.columnObjectIdColKey, objKey, realmGet$columnObjectId.toString(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.columnObjectIdColKey, objKey, false); } - cache.put(object, colKey); - Table.nativeSetLong(tableNativePtr, columnInfo.columnLongColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLong(), false); - Table.nativeSetFloat(tableNativePtr, columnInfo.columnFloatColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloat(), false); - Table.nativeSetDouble(tableNativePtr, columnInfo.columnDoubleColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDouble(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.columnBooleanColKey, colKey, ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBoolean(), false); java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, colKey, realmGet$columnDate.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, objKey, realmGet$columnDate.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnDateColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.columnDateColKey, objKey, false); } byte[] realmGet$columnBinary = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinary(); if (realmGet$columnBinary != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryColKey, colKey, realmGet$columnBinary, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.columnBinaryColKey, objKey, realmGet$columnBinary, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.columnBinaryColKey, objKey, false); } Long realmGet$columnMutableRealmInteger = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnMutableRealmInteger().get(); if (realmGet$columnMutableRealmInteger != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, colKey, realmGet$columnMutableRealmInteger.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, objKey, realmGet$columnMutableRealmInteger.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.columnMutableRealmIntegerColKey, objKey, false); } some.test.AllTypes columnObjectObj = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObject(); @@ -1925,12 +2255,12 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cachecolumnObject == null) { cachecolumnObject = some_test_AllTypesRealmProxy.insertOrUpdate(realm, columnObjectObj, cache); } - Table.nativeSetLink(tableNativePtr, columnInfo.columnObjectColKey, colKey, cachecolumnObject, false); + Table.nativeSetLink(tableNativePtr, columnInfo.columnObjectColKey, objKey, cachecolumnObject, false); } else { - Table.nativeNullifyLink(tableNativePtr, columnInfo.columnObjectColKey, colKey); + Table.nativeNullifyLink(tableNativePtr, columnInfo.columnObjectColKey, objKey); } - OsList columnRealmListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnRealmListColKey); + OsList columnRealmListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnRealmListColKey); RealmList columnRealmListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmList(); if (columnRealmListList != null && columnRealmListList.size() == columnRealmListOsList.size()) { // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. @@ -1957,7 +2287,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnStringListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnStringListColKey); + OsList columnStringListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnStringListColKey); columnStringListOsList.removeAll(); RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { @@ -1971,7 +2301,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBinaryListColKey); + OsList columnBinaryListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnBinaryListColKey); columnBinaryListOsList.removeAll(); RealmList columnBinaryListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBinaryList(); if (columnBinaryListList != null) { @@ -1985,7 +2315,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnBooleanListColKey); + OsList columnBooleanListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnBooleanListColKey); columnBooleanListOsList.removeAll(); RealmList columnBooleanListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnBooleanList(); if (columnBooleanListList != null) { @@ -1999,7 +2329,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnLongListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnLongListColKey); + OsList columnLongListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnLongListColKey); columnLongListOsList.removeAll(); RealmList columnLongListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnLongList(); if (columnLongListList != null) { @@ -2013,7 +2343,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnIntegerListColKey); + OsList columnIntegerListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnIntegerListColKey); columnIntegerListOsList.removeAll(); RealmList columnIntegerListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnIntegerList(); if (columnIntegerListList != null) { @@ -2027,7 +2357,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnShortListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnShortListColKey); + OsList columnShortListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnShortListColKey); columnShortListOsList.removeAll(); RealmList columnShortListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnShortList(); if (columnShortListList != null) { @@ -2041,7 +2371,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnByteListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnByteListColKey); + OsList columnByteListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnByteListColKey); columnByteListOsList.removeAll(); RealmList columnByteListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnByteList(); if (columnByteListList != null) { @@ -2055,7 +2385,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDoubleListColKey); + OsList columnDoubleListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnDoubleListColKey); columnDoubleListOsList.removeAll(); RealmList columnDoubleListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDoubleList(); if (columnDoubleListList != null) { @@ -2069,7 +2399,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnFloatListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnFloatListColKey); + OsList columnFloatListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnFloatListColKey); columnFloatListOsList.removeAll(); RealmList columnFloatListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnFloatList(); if (columnFloatListList != null) { @@ -2083,7 +2413,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList columnDateListOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.columnDateListColKey); + OsList columnDateListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnDateListColKey); columnDateListOsList.removeAll(); RealmList columnDateListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDateList(); if (columnDateListList != null) { @@ -2096,6 +2426,34 @@ public static void insertOrUpdate(Realm realm, Iterator ob } } + + OsList columnDecimal128ListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnDecimal128ListColKey); + columnDecimal128ListOsList.removeAll(); + RealmList columnDecimal128ListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDecimal128List(); + if (columnDecimal128ListList != null) { + for (org.bson.types.Decimal128 columnDecimal128ListItem : columnDecimal128ListList) { + if (columnDecimal128ListItem == null) { + columnDecimal128ListOsList.addNull(); + } else { + columnDecimal128ListOsList.addDecimal128(columnDecimal128ListItem); + } + } + } + + + OsList columnObjectIdListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnObjectIdListColKey); + columnObjectIdListOsList.removeAll(); + RealmList columnObjectIdListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnObjectIdList(); + if (columnObjectIdListList != null) { + for (org.bson.types.ObjectId columnObjectIdListItem : columnObjectIdListList) { + if (columnObjectIdListItem == null) { + columnObjectIdListOsList.addNull(); + } else { + columnObjectIdListOsList.addObjectId(columnObjectIdListItem); + } + } + } + } } @@ -2123,6 +2481,8 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje unmanagedCopy.realmSet$columnFloat(realmSource.realmGet$columnFloat()); unmanagedCopy.realmSet$columnDouble(realmSource.realmGet$columnDouble()); unmanagedCopy.realmSet$columnBoolean(realmSource.realmGet$columnBoolean()); + unmanagedCopy.realmSet$columnDecimal128(realmSource.realmGet$columnDecimal128()); + unmanagedCopy.realmSet$columnObjectId(realmSource.realmGet$columnObjectId()); unmanagedCopy.realmSet$columnDate(realmSource.realmGet$columnDate()); unmanagedCopy.realmSet$columnBinary(realmSource.realmGet$columnBinary()); unmanagedCopy.realmGet$columnMutableRealmInteger().set(realmSource.realmGet$columnMutableRealmInteger().get()); @@ -2175,6 +2535,12 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje unmanagedCopy.realmSet$columnDateList(new RealmList()); unmanagedCopy.realmGet$columnDateList().addAll(realmSource.realmGet$columnDateList()); + unmanagedCopy.realmSet$columnDecimal128List(new RealmList()); + unmanagedCopy.realmGet$columnDecimal128List().addAll(realmSource.realmGet$columnDecimal128List()); + + unmanagedCopy.realmSet$columnObjectIdList(new RealmList()); + unmanagedCopy.realmGet$columnObjectIdList().addAll(realmSource.realmGet$columnObjectIdList()); + return unmanagedObject; } @@ -2188,6 +2554,8 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som builder.addFloat(columnInfo.columnFloatColKey, realmObjectSource.realmGet$columnFloat()); builder.addDouble(columnInfo.columnDoubleColKey, realmObjectSource.realmGet$columnDouble()); builder.addBoolean(columnInfo.columnBooleanColKey, realmObjectSource.realmGet$columnBoolean()); + builder.addDecimal128(columnInfo.columnDecimal128ColKey, realmObjectSource.realmGet$columnDecimal128()); + builder.addObjectId(columnInfo.columnObjectIdColKey, realmObjectSource.realmGet$columnObjectId()); builder.addDate(columnInfo.columnDateColKey, realmObjectSource.realmGet$columnDate()); builder.addByteArray(columnInfo.columnBinaryColKey, realmObjectSource.realmGet$columnBinary()); builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, realmObjectSource.realmGet$columnMutableRealmInteger()); @@ -2230,6 +2598,8 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som builder.addDoubleList(columnInfo.columnDoubleListColKey, realmObjectSource.realmGet$columnDoubleList()); builder.addFloatList(columnInfo.columnFloatListColKey, realmObjectSource.realmGet$columnFloatList()); builder.addDateList(columnInfo.columnDateListColKey, realmObjectSource.realmGet$columnDateList()); + builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, realmObjectSource.realmGet$columnDecimal128List()); + builder.addObjectIdList(columnInfo.columnObjectIdListColKey, realmObjectSource.realmGet$columnObjectIdList()); builder.updateExistingObject(); return realmObject; @@ -2262,6 +2632,14 @@ public String toString() { stringBuilder.append(realmGet$columnBoolean()); stringBuilder.append("}"); stringBuilder.append(","); + stringBuilder.append("{columnDecimal128:"); + stringBuilder.append(realmGet$columnDecimal128()); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnObjectId:"); + stringBuilder.append(realmGet$columnObjectId()); + stringBuilder.append("}"); + stringBuilder.append(","); stringBuilder.append("{columnDate:"); stringBuilder.append(realmGet$columnDate()); stringBuilder.append("}"); @@ -2321,6 +2699,14 @@ public String toString() { stringBuilder.append("{columnDateList:"); stringBuilder.append("RealmList[").append(realmGet$columnDateList().size()).append("]"); stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnDecimal128List:"); + stringBuilder.append("RealmList[").append(realmGet$columnDecimal128List().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnObjectIdList:"); + stringBuilder.append("RealmList[").append(realmGet$columnObjectIdList().size()).append("]"); + stringBuilder.append("}"); stringBuilder.append("]"); return stringBuilder.toString(); } @@ -2334,12 +2720,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long colKey = proxyState.getRow$realm().getObjectKey(); + long objKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (colKey ^ (colKey >>> 32)); + result = 31 * result + (int) (objKey ^ (objKey >>> 32)); return result; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java index 96d244c47e..6906c18754 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java @@ -354,13 +354,13 @@ public static long insert(Realm realm, some.test.Booleans object, Map objects, Map cache) { @@ -377,12 +377,12 @@ public static void insert(Realm realm, Iterator objects, M cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneColKey, objKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyColKey, objKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedColKey, objKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanColKey, objKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); } } @@ -393,13 +393,13 @@ public static long insertOrUpdate(Realm realm, some.test.Booleans object, Map objects, Map cache) { @@ -416,12 +416,12 @@ public static void insertOrUpdate(Realm realm, Iterator ob cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); - Table.nativeSetBoolean(tableNativePtr, columnInfo.doneColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); - Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanColKey, colKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); + Table.nativeSetBoolean(tableNativePtr, columnInfo.doneColKey, objKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$done(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.isReadyColKey, objKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$isReady(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.mCompletedColKey, objKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$mCompleted(), false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.anotherBooleanColKey, objKey, ((some_test_BooleansRealmProxyInterface) object).realmGet$anotherBoolean(), false); } } @@ -487,12 +487,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long colKey = proxyState.getRow$realm().getObjectKey(); + long objKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (colKey ^ (colKey >>> 32)); + result = 31 * result + (int) (objKey ^ (objKey >>> 32)); return result; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java index fcab04337d..b879ae03d8 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java @@ -288,17 +288,17 @@ public static long insert(Realm realm, some.test.NamePolicyMixedClassSettings ob Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); long tableNativePtr = table.getNativePtr(); NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, objKey, realmGet$firstName, false); } String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, objKey, realmGet$lastName, false); } - return colKey; + return objKey; } public static void insert(Realm realm, Iterator objects, Map cache) { @@ -315,15 +315,15 @@ public static void insert(Realm realm, Iterator objects, M cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, objKey, realmGet$firstName, false); } String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, objKey, realmGet$lastName, false); } } } @@ -335,21 +335,21 @@ public static long insertOrUpdate(Realm realm, some.test.NamePolicyMixedClassSet Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); long tableNativePtr = table.getNativePtr(); NamePolicyMixedClassSettingsColumnInfo columnInfo = (NamePolicyMixedClassSettingsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class); - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, objKey, realmGet$firstName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, objKey, false); } String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, objKey, realmGet$lastName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, objKey, false); } - return colKey; + return objKey; } public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { @@ -366,19 +366,19 @@ public static void insertOrUpdate(Realm realm, Iterator ob cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$firstName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, objKey, realmGet$firstName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, objKey, false); } String realmGet$lastName = ((some_test_NamePolicyMixedClassSettingsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, objKey, realmGet$lastName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, objKey, false); } } } @@ -435,12 +435,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long colKey = proxyState.getRow$realm().getObjectKey(); + long objKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (colKey ^ (colKey >>> 32)); + result = 31 * result + (int) (objKey ^ (objKey >>> 32)); return result; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java index c4880c7813..83bc876281 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java @@ -288,17 +288,17 @@ public static long insert(Realm realm, some.test.NamePolicyModuleDefaults object Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); long tableNativePtr = table.getNativePtr(); NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, objKey, realmGet$firstName, false); } String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, objKey, realmGet$lastName, false); } - return colKey; + return objKey; } public static void insert(Realm realm, Iterator objects, Map cache) { @@ -315,15 +315,15 @@ public static void insert(Realm realm, Iterator objects, M cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, objKey, realmGet$firstName, false); } String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, objKey, realmGet$lastName, false); } } } @@ -335,21 +335,21 @@ public static long insertOrUpdate(Realm realm, some.test.NamePolicyModuleDefault Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); long tableNativePtr = table.getNativePtr(); NamePolicyModuleDefaultsColumnInfo columnInfo = (NamePolicyModuleDefaultsColumnInfo) realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class); - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, objKey, realmGet$firstName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, objKey, false); } String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, objKey, realmGet$lastName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, objKey, false); } - return colKey; + return objKey; } public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { @@ -366,19 +366,19 @@ public static void insertOrUpdate(Realm realm, Iterator ob cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$firstName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$firstName(); if (realmGet$firstName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, colKey, realmGet$firstName, false); + Table.nativeSetString(tableNativePtr, columnInfo.firstNameColKey, objKey, realmGet$firstName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.firstNameColKey, objKey, false); } String realmGet$lastName = ((some_test_NamePolicyModuleDefaultsRealmProxyInterface) object).realmGet$lastName(); if (realmGet$lastName != null) { - Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, colKey, realmGet$lastName, false); + Table.nativeSetString(tableNativePtr, columnInfo.lastNameColKey, objKey, realmGet$lastName, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.lastNameColKey, objKey, false); } } } @@ -435,12 +435,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long colKey = proxyState.getRow$realm().getObjectKey(); + long objKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (colKey ^ (colKey >>> 32)); + result = 31 * result + (int) (objKey ^ (objKey >>> 32)); return result; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index 86080c0cef..fff9196d84 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -58,6 +58,10 @@ static final class NullTypesColumnInfo extends ColumnInfo { long fieldDoubleNullColKey; long fieldDateNotNullColKey; long fieldDateNullColKey; + long fieldDecimal128NotNullColKey; + long fieldDecimal128NullColKey; + long fieldObjectIdNotNullColKey; + long fieldObjectIdNullColKey; long fieldObjectNullColKey; long fieldStringListNotNullColKey; long fieldStringListNullColKey; @@ -79,9 +83,13 @@ static final class NullTypesColumnInfo extends ColumnInfo { long fieldFloatListNullColKey; long fieldDateListNotNullColKey; long fieldDateListNullColKey; + long fieldDecimal128ListNotNullColKey; + long fieldDecimal128ListNullColKey; + long fieldObjectIdListNotNullColKey; + long fieldObjectIdListNullColKey; NullTypesColumnInfo(OsSchemaInfo schemaInfo) { - super(41); + super(49); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("NullTypes"); this.fieldStringNotNullColKey = addColumnDetails("fieldStringNotNull", "fieldStringNotNull", objectSchemaInfo); this.fieldStringNullColKey = addColumnDetails("fieldStringNull", "fieldStringNull", objectSchemaInfo); @@ -103,6 +111,10 @@ static final class NullTypesColumnInfo extends ColumnInfo { this.fieldDoubleNullColKey = addColumnDetails("fieldDoubleNull", "fieldDoubleNull", objectSchemaInfo); this.fieldDateNotNullColKey = addColumnDetails("fieldDateNotNull", "fieldDateNotNull", objectSchemaInfo); this.fieldDateNullColKey = addColumnDetails("fieldDateNull", "fieldDateNull", objectSchemaInfo); + this.fieldDecimal128NotNullColKey = addColumnDetails("fieldDecimal128NotNull", "fieldDecimal128NotNull", objectSchemaInfo); + this.fieldDecimal128NullColKey = addColumnDetails("fieldDecimal128Null", "fieldDecimal128Null", objectSchemaInfo); + this.fieldObjectIdNotNullColKey = addColumnDetails("fieldObjectIdNotNull", "fieldObjectIdNotNull", objectSchemaInfo); + this.fieldObjectIdNullColKey = addColumnDetails("fieldObjectIdNull", "fieldObjectIdNull", objectSchemaInfo); this.fieldObjectNullColKey = addColumnDetails("fieldObjectNull", "fieldObjectNull", objectSchemaInfo); this.fieldStringListNotNullColKey = addColumnDetails("fieldStringListNotNull", "fieldStringListNotNull", objectSchemaInfo); this.fieldStringListNullColKey = addColumnDetails("fieldStringListNull", "fieldStringListNull", objectSchemaInfo); @@ -124,6 +136,10 @@ static final class NullTypesColumnInfo extends ColumnInfo { this.fieldFloatListNullColKey = addColumnDetails("fieldFloatListNull", "fieldFloatListNull", objectSchemaInfo); this.fieldDateListNotNullColKey = addColumnDetails("fieldDateListNotNull", "fieldDateListNotNull", objectSchemaInfo); this.fieldDateListNullColKey = addColumnDetails("fieldDateListNull", "fieldDateListNull", objectSchemaInfo); + this.fieldDecimal128ListNotNullColKey = addColumnDetails("fieldDecimal128ListNotNull", "fieldDecimal128ListNotNull", objectSchemaInfo); + this.fieldDecimal128ListNullColKey = addColumnDetails("fieldDecimal128ListNull", "fieldDecimal128ListNull", objectSchemaInfo); + this.fieldObjectIdListNotNullColKey = addColumnDetails("fieldObjectIdListNotNull", "fieldObjectIdListNotNull", objectSchemaInfo); + this.fieldObjectIdListNullColKey = addColumnDetails("fieldObjectIdListNull", "fieldObjectIdListNull", objectSchemaInfo); } NullTypesColumnInfo(ColumnInfo src, boolean mutable) { @@ -160,6 +176,10 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.fieldDoubleNullColKey = src.fieldDoubleNullColKey; dst.fieldDateNotNullColKey = src.fieldDateNotNullColKey; dst.fieldDateNullColKey = src.fieldDateNullColKey; + dst.fieldDecimal128NotNullColKey = src.fieldDecimal128NotNullColKey; + dst.fieldDecimal128NullColKey = src.fieldDecimal128NullColKey; + dst.fieldObjectIdNotNullColKey = src.fieldObjectIdNotNullColKey; + dst.fieldObjectIdNullColKey = src.fieldObjectIdNullColKey; dst.fieldObjectNullColKey = src.fieldObjectNullColKey; dst.fieldStringListNotNullColKey = src.fieldStringListNotNullColKey; dst.fieldStringListNullColKey = src.fieldStringListNullColKey; @@ -181,6 +201,10 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.fieldFloatListNullColKey = src.fieldFloatListNullColKey; dst.fieldDateListNotNullColKey = src.fieldDateListNotNullColKey; dst.fieldDateListNullColKey = src.fieldDateListNullColKey; + dst.fieldDecimal128ListNotNullColKey = src.fieldDecimal128ListNotNullColKey; + dst.fieldDecimal128ListNullColKey = src.fieldDecimal128ListNullColKey; + dst.fieldObjectIdListNotNullColKey = src.fieldObjectIdListNotNullColKey; + dst.fieldObjectIdListNullColKey = src.fieldObjectIdListNullColKey; } } @@ -208,6 +232,10 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private RealmList fieldFloatListNullRealmList; private RealmList fieldDateListNotNullRealmList; private RealmList fieldDateListNullRealmList; + private RealmList fieldDecimal128ListNotNullRealmList; + private RealmList fieldDecimal128ListNullRealmList; + private RealmList fieldObjectIdListNotNullRealmList; + private RealmList fieldObjectIdListNullRealmList; some_test_NullTypesRealmProxy() { proxyState.setConstructionFinished(); @@ -831,6 +859,128 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { proxyState.getRow$realm().setDate(columnInfo.fieldDateNullColKey, value); } + @Override + @SuppressWarnings("cast") + public org.bson.types.Decimal128 realmGet$fieldDecimal128NotNull() { + proxyState.getRealm$realm().checkIfValid(); + return (org.bson.types.Decimal128) proxyState.getRow$realm().getDecimal128(columnInfo.fieldDecimal128NotNullColKey); + } + + @Override + public void realmSet$fieldDecimal128NotNull(org.bson.types.Decimal128 value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDecimal128NotNull' to null."); + } + row.getTable().setDecimal128(columnInfo.fieldDecimal128NotNullColKey, row.getObjectKey(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldDecimal128NotNull' to null."); + } + proxyState.getRow$realm().setDecimal128(columnInfo.fieldDecimal128NotNullColKey, value); + } + + @Override + @SuppressWarnings("cast") + public org.bson.types.Decimal128 realmGet$fieldDecimal128Null() { + proxyState.getRealm$realm().checkIfValid(); + if (proxyState.getRow$realm().isNull(columnInfo.fieldDecimal128NullColKey)) { + return null; + } + return (org.bson.types.Decimal128) proxyState.getRow$realm().getDecimal128(columnInfo.fieldDecimal128NullColKey); + } + + @Override + public void realmSet$fieldDecimal128Null(org.bson.types.Decimal128 value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldDecimal128NullColKey, row.getObjectKey(), true); + return; + } + row.getTable().setDecimal128(columnInfo.fieldDecimal128NullColKey, row.getObjectKey(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + proxyState.getRow$realm().setNull(columnInfo.fieldDecimal128NullColKey); + return; + } + proxyState.getRow$realm().setDecimal128(columnInfo.fieldDecimal128NullColKey, value); + } + + @Override + @SuppressWarnings("cast") + public org.bson.types.ObjectId realmGet$fieldObjectIdNotNull() { + proxyState.getRealm$realm().checkIfValid(); + return (org.bson.types.ObjectId) proxyState.getRow$realm().getObjectId(columnInfo.fieldObjectIdNotNullColKey); + } + + @Override + public void realmSet$fieldObjectIdNotNull(org.bson.types.ObjectId value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldObjectIdNotNull' to null."); + } + row.getTable().setObjectId(columnInfo.fieldObjectIdNotNullColKey, row.getObjectKey(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'fieldObjectIdNotNull' to null."); + } + proxyState.getRow$realm().setObjectId(columnInfo.fieldObjectIdNotNullColKey, value); + } + + @Override + @SuppressWarnings("cast") + public org.bson.types.ObjectId realmGet$fieldObjectIdNull() { + proxyState.getRealm$realm().checkIfValid(); + if (proxyState.getRow$realm().isNull(columnInfo.fieldObjectIdNullColKey)) { + return null; + } + return (org.bson.types.ObjectId) proxyState.getRow$realm().getObjectId(columnInfo.fieldObjectIdNullColKey); + } + + @Override + public void realmSet$fieldObjectIdNull(org.bson.types.ObjectId value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.fieldObjectIdNullColKey, row.getObjectKey(), true); + return; + } + row.getTable().setObjectId(columnInfo.fieldObjectIdNullColKey, row.getObjectKey(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + proxyState.getRow$realm().setNull(columnInfo.fieldObjectIdNullColKey); + return; + } + proxyState.getRow$realm().setObjectId(columnInfo.fieldObjectIdNullColKey, value); + } + @Override public some.test.NullTypes realmGet$fieldObjectNull() { proxyState.getRealm$realm().checkIfValid(); @@ -1652,8 +1802,164 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } } + @Override + public RealmList realmGet$fieldDecimal128ListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldDecimal128ListNotNullRealmList != null) { + return fieldDecimal128ListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDecimal128ListNotNullColKey, RealmFieldType.DECIMAL128_LIST); + fieldDecimal128ListNotNullRealmList = new RealmList(org.bson.types.Decimal128.class, osList, proxyState.getRealm$realm()); + return fieldDecimal128ListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldDecimal128ListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldDecimal128ListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDecimal128ListNotNullColKey, RealmFieldType.DECIMAL128_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (org.bson.types.Decimal128 item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldDecimal128ListNotNull' is not allowed by the schema."); + } else { + osList.addDecimal128(item); + } + } + } + + @Override + public RealmList realmGet$fieldDecimal128ListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldDecimal128ListNullRealmList != null) { + return fieldDecimal128ListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDecimal128ListNullColKey, RealmFieldType.DECIMAL128_LIST); + fieldDecimal128ListNullRealmList = new RealmList(org.bson.types.Decimal128.class, osList, proxyState.getRealm$realm()); + return fieldDecimal128ListNullRealmList; + } + } + + @Override + public void realmSet$fieldDecimal128ListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldDecimal128ListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldDecimal128ListNullColKey, RealmFieldType.DECIMAL128_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (org.bson.types.Decimal128 item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addDecimal128(item); + } + } + } + + @Override + public RealmList realmGet$fieldObjectIdListNotNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldObjectIdListNotNullRealmList != null) { + return fieldObjectIdListNotNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldObjectIdListNotNullColKey, RealmFieldType.OBJECT_ID_LIST); + fieldObjectIdListNotNullRealmList = new RealmList(org.bson.types.ObjectId.class, osList, proxyState.getRealm$realm()); + return fieldObjectIdListNotNullRealmList; + } + } + + @Override + public void realmSet$fieldObjectIdListNotNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldObjectIdListNotNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldObjectIdListNotNullColKey, RealmFieldType.OBJECT_ID_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (org.bson.types.ObjectId item : value) { + if (item == null) { + throw new IllegalArgumentException("Storing 'null' into fieldObjectIdListNotNull' is not allowed by the schema."); + } else { + osList.addObjectId(item); + } + } + } + + @Override + public RealmList realmGet$fieldObjectIdListNull() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (fieldObjectIdListNullRealmList != null) { + return fieldObjectIdListNullRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldObjectIdListNullColKey, RealmFieldType.OBJECT_ID_LIST); + fieldObjectIdListNullRealmList = new RealmList(org.bson.types.ObjectId.class, osList, proxyState.getRealm$realm()); + return fieldObjectIdListNullRealmList; + } + } + + @Override + public void realmSet$fieldObjectIdListNull(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("fieldObjectIdListNull")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.fieldObjectIdListNullColKey, RealmFieldType.OBJECT_ID_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (org.bson.types.ObjectId item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addObjectId(item); + } + } + } + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NullTypes", 41, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NullTypes", 49, 0); builder.addPersistedProperty("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -1674,6 +1980,10 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { builder.addPersistedProperty("fieldDoubleNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addPersistedProperty("fieldDecimal128NotNull", RealmFieldType.DECIMAL128, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addPersistedProperty("fieldDecimal128Null", RealmFieldType.DECIMAL128, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addPersistedProperty("fieldObjectIdNotNull", RealmFieldType.OBJECT_ID, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addPersistedProperty("fieldObjectIdNull", RealmFieldType.OBJECT_ID, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedLinkProperty("fieldObjectNull", RealmFieldType.OBJECT, "NullTypes"); builder.addPersistedValueListProperty("fieldStringListNotNull", RealmFieldType.STRING_LIST, Property.REQUIRED); builder.addPersistedValueListProperty("fieldStringListNull", RealmFieldType.STRING_LIST, !Property.REQUIRED); @@ -1695,6 +2005,10 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { builder.addPersistedValueListProperty("fieldFloatListNull", RealmFieldType.FLOAT_LIST, !Property.REQUIRED); builder.addPersistedValueListProperty("fieldDateListNotNull", RealmFieldType.DATE_LIST, Property.REQUIRED); builder.addPersistedValueListProperty("fieldDateListNull", RealmFieldType.DATE_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldDecimal128ListNotNull", RealmFieldType.DECIMAL128_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldDecimal128ListNull", RealmFieldType.DECIMAL128_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("fieldObjectIdListNotNull", RealmFieldType.OBJECT_ID_LIST, Property.REQUIRED); + builder.addPersistedValueListProperty("fieldObjectIdListNull", RealmFieldType.OBJECT_ID_LIST, !Property.REQUIRED); return builder.build(); } @@ -1717,7 +2031,7 @@ public static final class ClassNameHelper { @SuppressWarnings("cast") public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { - final List excludeFields = new ArrayList(21); + final List excludeFields = new ArrayList(25); if (json.has("fieldObjectNull")) { excludeFields.add("fieldObjectNull"); } @@ -1781,6 +2095,18 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO if (json.has("fieldDateListNull")) { excludeFields.add("fieldDateListNull"); } + if (json.has("fieldDecimal128ListNotNull")) { + excludeFields.add("fieldDecimal128ListNotNull"); + } + if (json.has("fieldDecimal128ListNull")) { + excludeFields.add("fieldDecimal128ListNull"); + } + if (json.has("fieldObjectIdListNotNull")) { + excludeFields.add("fieldObjectIdListNotNull"); + } + if (json.has("fieldObjectIdListNull")) { + excludeFields.add("fieldObjectIdListNull"); + } some.test.NullTypes obj = realm.createObjectInternal(some.test.NullTypes.class, true, excludeFields); final some_test_NullTypesRealmProxyInterface objProxy = (some_test_NullTypesRealmProxyInterface) obj; @@ -1934,6 +2260,70 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO } } } + if (json.has("fieldDecimal128NotNull")) { + if (json.isNull("fieldDecimal128NotNull")) { + objProxy.realmSet$fieldDecimal128NotNull(null); + } else { + Object decimal = json.get("fieldDecimal128NotNull"); + if (decimal instanceof org.bson.types.Decimal128) { + objProxy.realmSet$fieldDecimal128NotNull((org.bson.types.Decimal128) decimal); + } else if (decimal instanceof String) { + objProxy.realmSet$fieldDecimal128NotNull(org.bson.types.Decimal128.parse((String)decimal)); + } else if (decimal instanceof Integer) { + objProxy.realmSet$fieldDecimal128NotNull(new org.bson.types.Decimal128((Integer)(decimal))); + } else if (decimal instanceof Long) { + objProxy.realmSet$fieldDecimal128NotNull(new org.bson.types.Decimal128((Long)(decimal))); + } else if (decimal instanceof Double) { + objProxy.realmSet$fieldDecimal128NotNull(new org.bson.types.Decimal128(new java.math.BigDecimal((Double)(decimal)))); + } else { + throw new UnsupportedOperationException(decimal.getClass() + " is not supported as a Decimal128 value"); + } + } + } + if (json.has("fieldDecimal128Null")) { + if (json.isNull("fieldDecimal128Null")) { + objProxy.realmSet$fieldDecimal128Null(null); + } else { + Object decimal = json.get("fieldDecimal128Null"); + if (decimal instanceof org.bson.types.Decimal128) { + objProxy.realmSet$fieldDecimal128Null((org.bson.types.Decimal128) decimal); + } else if (decimal instanceof String) { + objProxy.realmSet$fieldDecimal128Null(org.bson.types.Decimal128.parse((String)decimal)); + } else if (decimal instanceof Integer) { + objProxy.realmSet$fieldDecimal128Null(new org.bson.types.Decimal128((Integer)(decimal))); + } else if (decimal instanceof Long) { + objProxy.realmSet$fieldDecimal128Null(new org.bson.types.Decimal128((Long)(decimal))); + } else if (decimal instanceof Double) { + objProxy.realmSet$fieldDecimal128Null(new org.bson.types.Decimal128(new java.math.BigDecimal((Double)(decimal)))); + } else { + throw new UnsupportedOperationException(decimal.getClass() + " is not supported as a Decimal128 value"); + } + } + } + if (json.has("fieldObjectIdNotNull")) { + if (json.isNull("fieldObjectIdNotNull")) { + objProxy.realmSet$fieldObjectIdNotNull(null); + } else { + Object id = json.get("fieldObjectIdNotNull"); + if (id instanceof org.bson.types.ObjectId) { + objProxy.realmSet$fieldObjectIdNotNull((org.bson.types.ObjectId) id); + } else { + objProxy.realmSet$fieldObjectIdNotNull(new org.bson.types.ObjectId((String)id)); + } + } + } + if (json.has("fieldObjectIdNull")) { + if (json.isNull("fieldObjectIdNull")) { + objProxy.realmSet$fieldObjectIdNull(null); + } else { + Object id = json.get("fieldObjectIdNull"); + if (id instanceof org.bson.types.ObjectId) { + objProxy.realmSet$fieldObjectIdNull((org.bson.types.ObjectId) id); + } else { + objProxy.realmSet$fieldObjectIdNull(new org.bson.types.ObjectId((String)id)); + } + } + } if (json.has("fieldObjectNull")) { if (json.isNull("fieldObjectNull")) { objProxy.realmSet$fieldObjectNull(null); @@ -1962,6 +2352,10 @@ public static some.test.NullTypes createOrUpdateUsingJsonObject(Realm realm, JSO ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldFloatListNull(), json, "fieldFloatListNull"); ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldDateListNotNull(), json, "fieldDateListNotNull"); ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldDateListNull(), json, "fieldDateListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldDecimal128ListNotNull(), json, "fieldDecimal128ListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldDecimal128ListNull(), json, "fieldDecimal128ListNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldObjectIdListNotNull(), json, "fieldObjectIdListNotNull"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$fieldObjectIdListNull(), json, "fieldObjectIdListNull"); return obj; } @@ -2125,6 +2519,34 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader } else { objProxy.realmSet$fieldDateNull(JsonUtils.stringToDate(reader.nextString())); } + } else if (name.equals("fieldDecimal128NotNull")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$fieldDecimal128NotNull(null); + } else { + objProxy.realmSet$fieldDecimal128NotNull(org.bson.types.Decimal128.parse(reader.nextString())); + } + } else if (name.equals("fieldDecimal128Null")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$fieldDecimal128Null(null); + } else { + objProxy.realmSet$fieldDecimal128Null(org.bson.types.Decimal128.parse(reader.nextString())); + } + } else if (name.equals("fieldObjectIdNotNull")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$fieldObjectIdNotNull(null); + } else { + objProxy.realmSet$fieldObjectIdNotNull(new org.bson.types.ObjectId(reader.nextString())); + } + } else if (name.equals("fieldObjectIdNull")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$fieldObjectIdNull(null); + } else { + objProxy.realmSet$fieldObjectIdNull(new org.bson.types.ObjectId(reader.nextString())); + } } else if (name.equals("fieldObjectNull")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); @@ -2173,6 +2595,14 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader objProxy.realmSet$fieldDateListNotNull(ProxyUtils.createRealmListWithJsonStream(java.util.Date.class, reader)); } else if (name.equals("fieldDateListNull")) { objProxy.realmSet$fieldDateListNull(ProxyUtils.createRealmListWithJsonStream(java.util.Date.class, reader)); + } else if (name.equals("fieldDecimal128ListNotNull")) { + objProxy.realmSet$fieldDecimal128ListNotNull(ProxyUtils.createRealmListWithJsonStream(org.bson.types.Decimal128.class, reader)); + } else if (name.equals("fieldDecimal128ListNull")) { + objProxy.realmSet$fieldDecimal128ListNull(ProxyUtils.createRealmListWithJsonStream(org.bson.types.Decimal128.class, reader)); + } else if (name.equals("fieldObjectIdListNotNull")) { + objProxy.realmSet$fieldObjectIdListNotNull(ProxyUtils.createRealmListWithJsonStream(org.bson.types.ObjectId.class, reader)); + } else if (name.equals("fieldObjectIdListNull")) { + objProxy.realmSet$fieldObjectIdListNull(ProxyUtils.createRealmListWithJsonStream(org.bson.types.ObjectId.class, reader)); } else { reader.skipValue(); } @@ -2241,6 +2671,10 @@ public static some.test.NullTypes copy(Realm realm, NullTypesColumnInfo columnIn builder.addDouble(columnInfo.fieldDoubleNullColKey, realmObjectSource.realmGet$fieldDoubleNull()); builder.addDate(columnInfo.fieldDateNotNullColKey, realmObjectSource.realmGet$fieldDateNotNull()); builder.addDate(columnInfo.fieldDateNullColKey, realmObjectSource.realmGet$fieldDateNull()); + builder.addDecimal128(columnInfo.fieldDecimal128NotNullColKey, realmObjectSource.realmGet$fieldDecimal128NotNull()); + builder.addDecimal128(columnInfo.fieldDecimal128NullColKey, realmObjectSource.realmGet$fieldDecimal128Null()); + builder.addObjectId(columnInfo.fieldObjectIdNotNullColKey, realmObjectSource.realmGet$fieldObjectIdNotNull()); + builder.addObjectId(columnInfo.fieldObjectIdNullColKey, realmObjectSource.realmGet$fieldObjectIdNull()); builder.addStringList(columnInfo.fieldStringListNotNullColKey, realmObjectSource.realmGet$fieldStringListNotNull()); builder.addStringList(columnInfo.fieldStringListNullColKey, realmObjectSource.realmGet$fieldStringListNull()); builder.addByteArrayList(columnInfo.fieldBinaryListNotNullColKey, realmObjectSource.realmGet$fieldBinaryListNotNull()); @@ -2261,6 +2695,10 @@ public static some.test.NullTypes copy(Realm realm, NullTypesColumnInfo columnIn builder.addFloatList(columnInfo.fieldFloatListNullColKey, realmObjectSource.realmGet$fieldFloatListNull()); builder.addDateList(columnInfo.fieldDateListNotNullColKey, realmObjectSource.realmGet$fieldDateListNotNull()); builder.addDateList(columnInfo.fieldDateListNullColKey, realmObjectSource.realmGet$fieldDateListNull()); + builder.addDecimal128List(columnInfo.fieldDecimal128ListNotNullColKey, realmObjectSource.realmGet$fieldDecimal128ListNotNull()); + builder.addDecimal128List(columnInfo.fieldDecimal128ListNullColKey, realmObjectSource.realmGet$fieldDecimal128ListNull()); + builder.addObjectIdList(columnInfo.fieldObjectIdListNotNullColKey, realmObjectSource.realmGet$fieldObjectIdListNotNull()); + builder.addObjectIdList(columnInfo.fieldObjectIdListNullColKey, realmObjectSource.realmGet$fieldObjectIdListNull()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. @@ -2291,87 +2729,103 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { - OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNotNullColKey); + OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldStringListNotNullColKey); for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { if (fieldStringListNotNullItem == null) { fieldStringListNotNullOsList.addNull(); @@ -2397,7 +2851,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { - OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNullColKey); + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldStringListNullColKey); for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { if (fieldStringListNullItem == null) { fieldStringListNullOsList.addNull(); @@ -2409,7 +2863,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { - OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNotNullColKey); + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBinaryListNotNullColKey); for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { if (fieldBinaryListNotNullItem == null) { fieldBinaryListNotNullOsList.addNull(); @@ -2421,7 +2875,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { - OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNullColKey); + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBinaryListNullColKey); for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { if (fieldBinaryListNullItem == null) { fieldBinaryListNullOsList.addNull(); @@ -2433,7 +2887,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { - OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNotNullColKey); + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBooleanListNotNullColKey); for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { if (fieldBooleanListNotNullItem == null) { fieldBooleanListNotNullOsList.addNull(); @@ -2445,7 +2899,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { - OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNullColKey); + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBooleanListNullColKey); for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { if (fieldBooleanListNullItem == null) { fieldBooleanListNullOsList.addNull(); @@ -2457,7 +2911,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { - OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNotNullColKey); + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldLongListNotNullColKey); for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { if (fieldLongListNotNullItem == null) { fieldLongListNotNullOsList.addNull(); @@ -2469,7 +2923,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { - OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNullColKey); + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldLongListNullColKey); for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { if (fieldLongListNullItem == null) { fieldLongListNullOsList.addNull(); @@ -2481,7 +2935,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { - OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNotNullColKey); + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldIntegerListNotNullColKey); for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { if (fieldIntegerListNotNullItem == null) { fieldIntegerListNotNullOsList.addNull(); @@ -2493,7 +2947,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { - OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNullColKey); + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldIntegerListNullColKey); for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { if (fieldIntegerListNullItem == null) { fieldIntegerListNullOsList.addNull(); @@ -2505,7 +2959,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { - OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNotNullColKey); + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldShortListNotNullColKey); for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { if (fieldShortListNotNullItem == null) { fieldShortListNotNullOsList.addNull(); @@ -2517,7 +2971,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { - OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNullColKey); + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldShortListNullColKey); for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { if (fieldShortListNullItem == null) { fieldShortListNullOsList.addNull(); @@ -2529,7 +2983,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { - OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNotNullColKey); + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldByteListNotNullColKey); for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { if (fieldByteListNotNullItem == null) { fieldByteListNotNullOsList.addNull(); @@ -2541,7 +2995,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { - OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNullColKey); + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldByteListNullColKey); for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { if (fieldByteListNullItem == null) { fieldByteListNullOsList.addNull(); @@ -2553,7 +3007,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { - OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNotNullColKey); + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDoubleListNotNullColKey); for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { if (fieldDoubleListNotNullItem == null) { fieldDoubleListNotNullOsList.addNull(); @@ -2565,7 +3019,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { - OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNullColKey); + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDoubleListNullColKey); for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { if (fieldDoubleListNullItem == null) { fieldDoubleListNullOsList.addNull(); @@ -2577,7 +3031,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { - OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNotNullColKey); + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldFloatListNotNullColKey); for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { if (fieldFloatListNotNullItem == null) { fieldFloatListNotNullOsList.addNull(); @@ -2589,7 +3043,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { - OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNullColKey); + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldFloatListNullColKey); for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { if (fieldFloatListNullItem == null) { fieldFloatListNullOsList.addNull(); @@ -2601,7 +3055,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { - OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNotNullColKey); + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDateListNotNullColKey); for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { if (fieldDateListNotNullItem == null) { fieldDateListNotNullOsList.addNull(); @@ -2613,7 +3067,7 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { - OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNullColKey); + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDateListNullColKey); for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { if (fieldDateListNullItem == null) { fieldDateListNullOsList.addNull(); @@ -2622,7 +3076,55 @@ public static long insert(Realm realm, some.test.NullTypes object, Map fieldDecimal128ListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128ListNotNull(); + if (fieldDecimal128ListNotNullList != null) { + OsList fieldDecimal128ListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDecimal128ListNotNullColKey); + for (org.bson.types.Decimal128 fieldDecimal128ListNotNullItem : fieldDecimal128ListNotNullList) { + if (fieldDecimal128ListNotNullItem == null) { + fieldDecimal128ListNotNullOsList.addNull(); + } else { + fieldDecimal128ListNotNullOsList.addDecimal128(fieldDecimal128ListNotNullItem); + } + } + } + + RealmList fieldDecimal128ListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128ListNull(); + if (fieldDecimal128ListNullList != null) { + OsList fieldDecimal128ListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDecimal128ListNullColKey); + for (org.bson.types.Decimal128 fieldDecimal128ListNullItem : fieldDecimal128ListNullList) { + if (fieldDecimal128ListNullItem == null) { + fieldDecimal128ListNullOsList.addNull(); + } else { + fieldDecimal128ListNullOsList.addDecimal128(fieldDecimal128ListNullItem); + } + } + } + + RealmList fieldObjectIdListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdListNotNull(); + if (fieldObjectIdListNotNullList != null) { + OsList fieldObjectIdListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldObjectIdListNotNullColKey); + for (org.bson.types.ObjectId fieldObjectIdListNotNullItem : fieldObjectIdListNotNullList) { + if (fieldObjectIdListNotNullItem == null) { + fieldObjectIdListNotNullOsList.addNull(); + } else { + fieldObjectIdListNotNullOsList.addObjectId(fieldObjectIdListNotNullItem); + } + } + } + + RealmList fieldObjectIdListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdListNull(); + if (fieldObjectIdListNullList != null) { + OsList fieldObjectIdListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldObjectIdListNullColKey); + for (org.bson.types.ObjectId fieldObjectIdListNullItem : fieldObjectIdListNullList) { + if (fieldObjectIdListNullItem == null) { + fieldObjectIdListNullOsList.addNull(); + } else { + fieldObjectIdListNullOsList.addObjectId(fieldObjectIdListNullItem); + } + } + } + return objKey; } public static void insert(Realm realm, Iterator objects, Map cache) { @@ -2639,87 +3141,103 @@ public static void insert(Realm realm, Iterator objects, M cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$fieldStringNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullColKey, colKey, realmGet$fieldStringNotNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullColKey, objKey, realmGet$fieldStringNotNull, false); } String realmGet$fieldStringNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullColKey, colKey, realmGet$fieldStringNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullColKey, objKey, realmGet$fieldStringNull, false); } Boolean realmGet$fieldBooleanNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, colKey, realmGet$fieldBooleanNotNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, objKey, realmGet$fieldBooleanNotNull, false); } Boolean realmGet$fieldBooleanNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullColKey, colKey, realmGet$fieldBooleanNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullColKey, objKey, realmGet$fieldBooleanNull, false); } byte[] realmGet$fieldBytesNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullColKey, colKey, realmGet$fieldBytesNotNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullColKey, objKey, realmGet$fieldBytesNotNull, false); } byte[] realmGet$fieldBytesNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullColKey, colKey, realmGet$fieldBytesNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullColKey, objKey, realmGet$fieldBytesNull, false); } Number realmGet$fieldByteNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullColKey, colKey, realmGet$fieldByteNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullColKey, objKey, realmGet$fieldByteNotNull.longValue(), false); } Number realmGet$fieldByteNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullColKey, colKey, realmGet$fieldByteNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullColKey, objKey, realmGet$fieldByteNull.longValue(), false); } Number realmGet$fieldShortNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullColKey, colKey, realmGet$fieldShortNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullColKey, objKey, realmGet$fieldShortNotNull.longValue(), false); } Number realmGet$fieldShortNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullColKey, colKey, realmGet$fieldShortNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullColKey, objKey, realmGet$fieldShortNull.longValue(), false); } Number realmGet$fieldIntegerNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, colKey, realmGet$fieldIntegerNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, objKey, realmGet$fieldIntegerNotNull.longValue(), false); } Number realmGet$fieldIntegerNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullColKey, colKey, realmGet$fieldIntegerNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullColKey, objKey, realmGet$fieldIntegerNull.longValue(), false); } Number realmGet$fieldLongNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullColKey, colKey, realmGet$fieldLongNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullColKey, objKey, realmGet$fieldLongNotNull.longValue(), false); } Number realmGet$fieldLongNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullColKey, colKey, realmGet$fieldLongNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullColKey, objKey, realmGet$fieldLongNull.longValue(), false); } Float realmGet$fieldFloatNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullColKey, colKey, realmGet$fieldFloatNotNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullColKey, objKey, realmGet$fieldFloatNotNull, false); } Float realmGet$fieldFloatNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullColKey, colKey, realmGet$fieldFloatNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullColKey, objKey, realmGet$fieldFloatNull, false); } Double realmGet$fieldDoubleNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, colKey, realmGet$fieldDoubleNotNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, objKey, realmGet$fieldDoubleNotNull, false); } Double realmGet$fieldDoubleNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullColKey, colKey, realmGet$fieldDoubleNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullColKey, objKey, realmGet$fieldDoubleNull, false); } java.util.Date realmGet$fieldDateNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullColKey, colKey, realmGet$fieldDateNotNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullColKey, objKey, realmGet$fieldDateNotNull.getTime(), false); } java.util.Date realmGet$fieldDateNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullColKey, colKey, realmGet$fieldDateNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullColKey, objKey, realmGet$fieldDateNull.getTime(), false); + } + org.bson.types.Decimal128 realmGet$fieldDecimal128NotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128NotNull(); + if (realmGet$fieldDecimal128NotNull != null) { + Table.nativeSetDecimal128(tableNativePtr, columnInfo.fieldDecimal128NotNullColKey, objKey, realmGet$fieldDecimal128NotNull.getLow(), realmGet$fieldDecimal128NotNull.getHigh(), false); + } + org.bson.types.Decimal128 realmGet$fieldDecimal128Null = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128Null(); + if (realmGet$fieldDecimal128Null != null) { + Table.nativeSetDecimal128(tableNativePtr, columnInfo.fieldDecimal128NullColKey, objKey, realmGet$fieldDecimal128Null.getLow(), realmGet$fieldDecimal128Null.getHigh(), false); + } + org.bson.types.ObjectId realmGet$fieldObjectIdNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdNotNull(); + if (realmGet$fieldObjectIdNotNull != null) { + Table.nativeSetObjectId(tableNativePtr, columnInfo.fieldObjectIdNotNullColKey, objKey, realmGet$fieldObjectIdNotNull.toString(), false); + } + org.bson.types.ObjectId realmGet$fieldObjectIdNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdNull(); + if (realmGet$fieldObjectIdNull != null) { + Table.nativeSetObjectId(tableNativePtr, columnInfo.fieldObjectIdNullColKey, objKey, realmGet$fieldObjectIdNull.toString(), false); } some.test.NullTypes fieldObjectNullObj = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); @@ -2728,12 +3246,12 @@ public static void insert(Realm realm, Iterator objects, M if (cachefieldObjectNull == null) { cachefieldObjectNull = some_test_NullTypesRealmProxy.insert(realm, fieldObjectNullObj, cache); } - table.setLink(columnInfo.fieldObjectNullColKey, colKey, cachefieldObjectNull, false); + table.setLink(columnInfo.fieldObjectNullColKey, objKey, cachefieldObjectNull, false); } RealmList fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { - OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNotNullColKey); + OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldStringListNotNullColKey); for (java.lang.String fieldStringListNotNullItem : fieldStringListNotNullList) { if (fieldStringListNotNullItem == null) { fieldStringListNotNullOsList.addNull(); @@ -2745,7 +3263,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { - OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNullColKey); + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldStringListNullColKey); for (java.lang.String fieldStringListNullItem : fieldStringListNullList) { if (fieldStringListNullItem == null) { fieldStringListNullOsList.addNull(); @@ -2757,7 +3275,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { - OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNotNullColKey); + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBinaryListNotNullColKey); for (byte[] fieldBinaryListNotNullItem : fieldBinaryListNotNullList) { if (fieldBinaryListNotNullItem == null) { fieldBinaryListNotNullOsList.addNull(); @@ -2769,7 +3287,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { - OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNullColKey); + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBinaryListNullColKey); for (byte[] fieldBinaryListNullItem : fieldBinaryListNullList) { if (fieldBinaryListNullItem == null) { fieldBinaryListNullOsList.addNull(); @@ -2781,7 +3299,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { - OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNotNullColKey); + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBooleanListNotNullColKey); for (java.lang.Boolean fieldBooleanListNotNullItem : fieldBooleanListNotNullList) { if (fieldBooleanListNotNullItem == null) { fieldBooleanListNotNullOsList.addNull(); @@ -2793,7 +3311,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { - OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNullColKey); + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBooleanListNullColKey); for (java.lang.Boolean fieldBooleanListNullItem : fieldBooleanListNullList) { if (fieldBooleanListNullItem == null) { fieldBooleanListNullOsList.addNull(); @@ -2805,7 +3323,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { - OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNotNullColKey); + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldLongListNotNullColKey); for (java.lang.Long fieldLongListNotNullItem : fieldLongListNotNullList) { if (fieldLongListNotNullItem == null) { fieldLongListNotNullOsList.addNull(); @@ -2817,7 +3335,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { - OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNullColKey); + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldLongListNullColKey); for (java.lang.Long fieldLongListNullItem : fieldLongListNullList) { if (fieldLongListNullItem == null) { fieldLongListNullOsList.addNull(); @@ -2829,7 +3347,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { - OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNotNullColKey); + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldIntegerListNotNullColKey); for (java.lang.Integer fieldIntegerListNotNullItem : fieldIntegerListNotNullList) { if (fieldIntegerListNotNullItem == null) { fieldIntegerListNotNullOsList.addNull(); @@ -2841,7 +3359,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { - OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNullColKey); + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldIntegerListNullColKey); for (java.lang.Integer fieldIntegerListNullItem : fieldIntegerListNullList) { if (fieldIntegerListNullItem == null) { fieldIntegerListNullOsList.addNull(); @@ -2853,7 +3371,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { - OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNotNullColKey); + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldShortListNotNullColKey); for (java.lang.Short fieldShortListNotNullItem : fieldShortListNotNullList) { if (fieldShortListNotNullItem == null) { fieldShortListNotNullOsList.addNull(); @@ -2865,7 +3383,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { - OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNullColKey); + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldShortListNullColKey); for (java.lang.Short fieldShortListNullItem : fieldShortListNullList) { if (fieldShortListNullItem == null) { fieldShortListNullOsList.addNull(); @@ -2877,7 +3395,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { - OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNotNullColKey); + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldByteListNotNullColKey); for (java.lang.Byte fieldByteListNotNullItem : fieldByteListNotNullList) { if (fieldByteListNotNullItem == null) { fieldByteListNotNullOsList.addNull(); @@ -2889,7 +3407,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { - OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNullColKey); + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldByteListNullColKey); for (java.lang.Byte fieldByteListNullItem : fieldByteListNullList) { if (fieldByteListNullItem == null) { fieldByteListNullOsList.addNull(); @@ -2901,7 +3419,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { - OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNotNullColKey); + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDoubleListNotNullColKey); for (java.lang.Double fieldDoubleListNotNullItem : fieldDoubleListNotNullList) { if (fieldDoubleListNotNullItem == null) { fieldDoubleListNotNullOsList.addNull(); @@ -2913,7 +3431,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { - OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNullColKey); + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDoubleListNullColKey); for (java.lang.Double fieldDoubleListNullItem : fieldDoubleListNullList) { if (fieldDoubleListNullItem == null) { fieldDoubleListNullOsList.addNull(); @@ -2925,7 +3443,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { - OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNotNullColKey); + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldFloatListNotNullColKey); for (java.lang.Float fieldFloatListNotNullItem : fieldFloatListNotNullList) { if (fieldFloatListNotNullItem == null) { fieldFloatListNotNullOsList.addNull(); @@ -2937,7 +3455,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { - OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNullColKey); + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldFloatListNullColKey); for (java.lang.Float fieldFloatListNullItem : fieldFloatListNullList) { if (fieldFloatListNullItem == null) { fieldFloatListNullOsList.addNull(); @@ -2949,7 +3467,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { - OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNotNullColKey); + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDateListNotNullColKey); for (java.util.Date fieldDateListNotNullItem : fieldDateListNotNullList) { if (fieldDateListNotNullItem == null) { fieldDateListNotNullOsList.addNull(); @@ -2961,7 +3479,7 @@ public static void insert(Realm realm, Iterator objects, M RealmList fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { - OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNullColKey); + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDateListNullColKey); for (java.util.Date fieldDateListNullItem : fieldDateListNullList) { if (fieldDateListNullItem == null) { fieldDateListNullOsList.addNull(); @@ -2970,6 +3488,54 @@ public static void insert(Realm realm, Iterator objects, M } } } + + RealmList fieldDecimal128ListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128ListNotNull(); + if (fieldDecimal128ListNotNullList != null) { + OsList fieldDecimal128ListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDecimal128ListNotNullColKey); + for (org.bson.types.Decimal128 fieldDecimal128ListNotNullItem : fieldDecimal128ListNotNullList) { + if (fieldDecimal128ListNotNullItem == null) { + fieldDecimal128ListNotNullOsList.addNull(); + } else { + fieldDecimal128ListNotNullOsList.addDecimal128(fieldDecimal128ListNotNullItem); + } + } + } + + RealmList fieldDecimal128ListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128ListNull(); + if (fieldDecimal128ListNullList != null) { + OsList fieldDecimal128ListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDecimal128ListNullColKey); + for (org.bson.types.Decimal128 fieldDecimal128ListNullItem : fieldDecimal128ListNullList) { + if (fieldDecimal128ListNullItem == null) { + fieldDecimal128ListNullOsList.addNull(); + } else { + fieldDecimal128ListNullOsList.addDecimal128(fieldDecimal128ListNullItem); + } + } + } + + RealmList fieldObjectIdListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdListNotNull(); + if (fieldObjectIdListNotNullList != null) { + OsList fieldObjectIdListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldObjectIdListNotNullColKey); + for (org.bson.types.ObjectId fieldObjectIdListNotNullItem : fieldObjectIdListNotNullList) { + if (fieldObjectIdListNotNullItem == null) { + fieldObjectIdListNotNullOsList.addNull(); + } else { + fieldObjectIdListNotNullOsList.addObjectId(fieldObjectIdListNotNullItem); + } + } + } + + RealmList fieldObjectIdListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdListNull(); + if (fieldObjectIdListNullList != null) { + OsList fieldObjectIdListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldObjectIdListNullColKey); + for (org.bson.types.ObjectId fieldObjectIdListNullItem : fieldObjectIdListNullList) { + if (fieldObjectIdListNullItem == null) { + fieldObjectIdListNullOsList.addNull(); + } else { + fieldObjectIdListNullOsList.addObjectId(fieldObjectIdListNullItem); + } + } + } } } @@ -2980,127 +3546,151 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { @@ -3128,7 +3718,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { @@ -3142,7 +3732,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { @@ -3156,7 +3746,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { @@ -3170,7 +3760,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { @@ -3184,7 +3774,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { @@ -3198,7 +3788,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { @@ -3212,7 +3802,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { @@ -3226,7 +3816,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { @@ -3240,7 +3830,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { @@ -3254,7 +3844,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { @@ -3268,7 +3858,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { @@ -3282,7 +3872,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { @@ -3296,7 +3886,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { @@ -3310,7 +3900,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { @@ -3324,7 +3914,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { @@ -3338,7 +3928,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { @@ -3352,7 +3942,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { @@ -3366,7 +3956,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { @@ -3380,7 +3970,7 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { @@ -3393,7 +3983,63 @@ public static long insertOrUpdate(Realm realm, some.test.NullTypes object, Map fieldDecimal128ListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128ListNotNull(); + if (fieldDecimal128ListNotNullList != null) { + for (org.bson.types.Decimal128 fieldDecimal128ListNotNullItem : fieldDecimal128ListNotNullList) { + if (fieldDecimal128ListNotNullItem == null) { + fieldDecimal128ListNotNullOsList.addNull(); + } else { + fieldDecimal128ListNotNullOsList.addDecimal128(fieldDecimal128ListNotNullItem); + } + } + } + + + OsList fieldDecimal128ListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDecimal128ListNullColKey); + fieldDecimal128ListNullOsList.removeAll(); + RealmList fieldDecimal128ListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128ListNull(); + if (fieldDecimal128ListNullList != null) { + for (org.bson.types.Decimal128 fieldDecimal128ListNullItem : fieldDecimal128ListNullList) { + if (fieldDecimal128ListNullItem == null) { + fieldDecimal128ListNullOsList.addNull(); + } else { + fieldDecimal128ListNullOsList.addDecimal128(fieldDecimal128ListNullItem); + } + } + } + + + OsList fieldObjectIdListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldObjectIdListNotNullColKey); + fieldObjectIdListNotNullOsList.removeAll(); + RealmList fieldObjectIdListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdListNotNull(); + if (fieldObjectIdListNotNullList != null) { + for (org.bson.types.ObjectId fieldObjectIdListNotNullItem : fieldObjectIdListNotNullList) { + if (fieldObjectIdListNotNullItem == null) { + fieldObjectIdListNotNullOsList.addNull(); + } else { + fieldObjectIdListNotNullOsList.addObjectId(fieldObjectIdListNotNullItem); + } + } + } + + + OsList fieldObjectIdListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldObjectIdListNullColKey); + fieldObjectIdListNullOsList.removeAll(); + RealmList fieldObjectIdListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdListNull(); + if (fieldObjectIdListNullList != null) { + for (org.bson.types.ObjectId fieldObjectIdListNullItem : fieldObjectIdListNullList) { + if (fieldObjectIdListNullItem == null) { + fieldObjectIdListNullOsList.addNull(); + } else { + fieldObjectIdListNullOsList.addObjectId(fieldObjectIdListNullItem); + } + } + } + + return objKey; } public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { @@ -3410,127 +4056,151 @@ public static void insertOrUpdate(Realm realm, Iterator ob cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$fieldStringNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNotNull(); if (realmGet$fieldStringNotNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullColKey, colKey, realmGet$fieldStringNotNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNotNullColKey, objKey, realmGet$fieldStringNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNotNullColKey, objKey, false); } String realmGet$fieldStringNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringNull(); if (realmGet$fieldStringNull != null) { - Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullColKey, colKey, realmGet$fieldStringNull, false); + Table.nativeSetString(tableNativePtr, columnInfo.fieldStringNullColKey, objKey, realmGet$fieldStringNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldStringNullColKey, objKey, false); } Boolean realmGet$fieldBooleanNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNotNull(); if (realmGet$fieldBooleanNotNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, colKey, realmGet$fieldBooleanNotNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, objKey, realmGet$fieldBooleanNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNotNullColKey, objKey, false); } Boolean realmGet$fieldBooleanNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanNull(); if (realmGet$fieldBooleanNull != null) { - Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullColKey, colKey, realmGet$fieldBooleanNull, false); + Table.nativeSetBoolean(tableNativePtr, columnInfo.fieldBooleanNullColKey, objKey, realmGet$fieldBooleanNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBooleanNullColKey, objKey, false); } byte[] realmGet$fieldBytesNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNotNull(); if (realmGet$fieldBytesNotNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullColKey, colKey, realmGet$fieldBytesNotNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNotNullColKey, objKey, realmGet$fieldBytesNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNotNullColKey, objKey, false); } byte[] realmGet$fieldBytesNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBytesNull(); if (realmGet$fieldBytesNull != null) { - Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullColKey, colKey, realmGet$fieldBytesNull, false); + Table.nativeSetByteArray(tableNativePtr, columnInfo.fieldBytesNullColKey, objKey, realmGet$fieldBytesNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldBytesNullColKey, objKey, false); } Number realmGet$fieldByteNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNotNull(); if (realmGet$fieldByteNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullColKey, colKey, realmGet$fieldByteNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNotNullColKey, objKey, realmGet$fieldByteNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNotNullColKey, objKey, false); } Number realmGet$fieldByteNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteNull(); if (realmGet$fieldByteNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullColKey, colKey, realmGet$fieldByteNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldByteNullColKey, objKey, realmGet$fieldByteNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldByteNullColKey, objKey, false); } Number realmGet$fieldShortNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNotNull(); if (realmGet$fieldShortNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullColKey, colKey, realmGet$fieldShortNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNotNullColKey, objKey, realmGet$fieldShortNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNotNullColKey, objKey, false); } Number realmGet$fieldShortNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortNull(); if (realmGet$fieldShortNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullColKey, colKey, realmGet$fieldShortNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldShortNullColKey, objKey, realmGet$fieldShortNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldShortNullColKey, objKey, false); } Number realmGet$fieldIntegerNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNotNull(); if (realmGet$fieldIntegerNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, colKey, realmGet$fieldIntegerNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, objKey, realmGet$fieldIntegerNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNotNullColKey, objKey, false); } Number realmGet$fieldIntegerNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerNull(); if (realmGet$fieldIntegerNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullColKey, colKey, realmGet$fieldIntegerNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldIntegerNullColKey, objKey, realmGet$fieldIntegerNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldIntegerNullColKey, objKey, false); } Number realmGet$fieldLongNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNotNull(); if (realmGet$fieldLongNotNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullColKey, colKey, realmGet$fieldLongNotNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNotNullColKey, objKey, realmGet$fieldLongNotNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNotNullColKey, objKey, false); } Number realmGet$fieldLongNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongNull(); if (realmGet$fieldLongNull != null) { - Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullColKey, colKey, realmGet$fieldLongNull.longValue(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.fieldLongNullColKey, objKey, realmGet$fieldLongNull.longValue(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldLongNullColKey, objKey, false); } Float realmGet$fieldFloatNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNotNull(); if (realmGet$fieldFloatNotNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullColKey, colKey, realmGet$fieldFloatNotNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNotNullColKey, objKey, realmGet$fieldFloatNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNotNullColKey, objKey, false); } Float realmGet$fieldFloatNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatNull(); if (realmGet$fieldFloatNull != null) { - Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullColKey, colKey, realmGet$fieldFloatNull, false); + Table.nativeSetFloat(tableNativePtr, columnInfo.fieldFloatNullColKey, objKey, realmGet$fieldFloatNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldFloatNullColKey, objKey, false); } Double realmGet$fieldDoubleNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNotNull(); if (realmGet$fieldDoubleNotNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, colKey, realmGet$fieldDoubleNotNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, objKey, realmGet$fieldDoubleNotNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNotNullColKey, objKey, false); } Double realmGet$fieldDoubleNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleNull(); if (realmGet$fieldDoubleNull != null) { - Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullColKey, colKey, realmGet$fieldDoubleNull, false); + Table.nativeSetDouble(tableNativePtr, columnInfo.fieldDoubleNullColKey, objKey, realmGet$fieldDoubleNull, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDoubleNullColKey, objKey, false); } java.util.Date realmGet$fieldDateNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNotNull(); if (realmGet$fieldDateNotNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullColKey, colKey, realmGet$fieldDateNotNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNotNullColKey, objKey, realmGet$fieldDateNotNull.getTime(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNotNullColKey, objKey, false); } java.util.Date realmGet$fieldDateNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateNull(); if (realmGet$fieldDateNull != null) { - Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullColKey, colKey, realmGet$fieldDateNull.getTime(), false); + Table.nativeSetTimestamp(tableNativePtr, columnInfo.fieldDateNullColKey, objKey, realmGet$fieldDateNull.getTime(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullColKey, objKey, false); + } + org.bson.types.Decimal128 realmGet$fieldDecimal128NotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128NotNull(); + if (realmGet$fieldDecimal128NotNull != null) { + Table.nativeSetDecimal128(tableNativePtr, columnInfo.fieldDecimal128NotNullColKey, objKey, realmGet$fieldDecimal128NotNull.getLow(), realmGet$fieldDecimal128NotNull.getHigh(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDecimal128NotNullColKey, objKey, false); + } + org.bson.types.Decimal128 realmGet$fieldDecimal128Null = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128Null(); + if (realmGet$fieldDecimal128Null != null) { + Table.nativeSetDecimal128(tableNativePtr, columnInfo.fieldDecimal128NullColKey, objKey, realmGet$fieldDecimal128Null.getLow(), realmGet$fieldDecimal128Null.getHigh(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldDecimal128NullColKey, objKey, false); + } + org.bson.types.ObjectId realmGet$fieldObjectIdNotNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdNotNull(); + if (realmGet$fieldObjectIdNotNull != null) { + Table.nativeSetObjectId(tableNativePtr, columnInfo.fieldObjectIdNotNullColKey, objKey, realmGet$fieldObjectIdNotNull.toString(), false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.fieldDateNullColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.fieldObjectIdNotNullColKey, objKey, false); + } + org.bson.types.ObjectId realmGet$fieldObjectIdNull = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdNull(); + if (realmGet$fieldObjectIdNull != null) { + Table.nativeSetObjectId(tableNativePtr, columnInfo.fieldObjectIdNullColKey, objKey, realmGet$fieldObjectIdNull.toString(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.fieldObjectIdNullColKey, objKey, false); } some.test.NullTypes fieldObjectNullObj = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectNull(); @@ -3539,12 +4209,12 @@ public static void insertOrUpdate(Realm realm, Iterator ob if (cachefieldObjectNull == null) { cachefieldObjectNull = some_test_NullTypesRealmProxy.insertOrUpdate(realm, fieldObjectNullObj, cache); } - Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullColKey, colKey, cachefieldObjectNull, false); + Table.nativeSetLink(tableNativePtr, columnInfo.fieldObjectNullColKey, objKey, cachefieldObjectNull, false); } else { - Table.nativeNullifyLink(tableNativePtr, columnInfo.fieldObjectNullColKey, colKey); + Table.nativeNullifyLink(tableNativePtr, columnInfo.fieldObjectNullColKey, objKey); } - OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNotNullColKey); + OsList fieldStringListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldStringListNotNullColKey); fieldStringListNotNullOsList.removeAll(); RealmList fieldStringListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNotNull(); if (fieldStringListNotNullList != null) { @@ -3558,7 +4228,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldStringListNullColKey); + OsList fieldStringListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldStringListNullColKey); fieldStringListNullOsList.removeAll(); RealmList fieldStringListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldStringListNull(); if (fieldStringListNullList != null) { @@ -3572,7 +4242,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNotNullColKey); + OsList fieldBinaryListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBinaryListNotNullColKey); fieldBinaryListNotNullOsList.removeAll(); RealmList fieldBinaryListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNotNull(); if (fieldBinaryListNotNullList != null) { @@ -3586,7 +4256,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBinaryListNullColKey); + OsList fieldBinaryListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBinaryListNullColKey); fieldBinaryListNullOsList.removeAll(); RealmList fieldBinaryListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBinaryListNull(); if (fieldBinaryListNullList != null) { @@ -3600,7 +4270,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNotNullColKey); + OsList fieldBooleanListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBooleanListNotNullColKey); fieldBooleanListNotNullOsList.removeAll(); RealmList fieldBooleanListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNotNull(); if (fieldBooleanListNotNullList != null) { @@ -3614,7 +4284,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldBooleanListNullColKey); + OsList fieldBooleanListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldBooleanListNullColKey); fieldBooleanListNullOsList.removeAll(); RealmList fieldBooleanListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldBooleanListNull(); if (fieldBooleanListNullList != null) { @@ -3628,7 +4298,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNotNullColKey); + OsList fieldLongListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldLongListNotNullColKey); fieldLongListNotNullOsList.removeAll(); RealmList fieldLongListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNotNull(); if (fieldLongListNotNullList != null) { @@ -3642,7 +4312,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldLongListNullColKey); + OsList fieldLongListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldLongListNullColKey); fieldLongListNullOsList.removeAll(); RealmList fieldLongListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldLongListNull(); if (fieldLongListNullList != null) { @@ -3656,7 +4326,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNotNullColKey); + OsList fieldIntegerListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldIntegerListNotNullColKey); fieldIntegerListNotNullOsList.removeAll(); RealmList fieldIntegerListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNotNull(); if (fieldIntegerListNotNullList != null) { @@ -3670,7 +4340,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldIntegerListNullColKey); + OsList fieldIntegerListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldIntegerListNullColKey); fieldIntegerListNullOsList.removeAll(); RealmList fieldIntegerListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldIntegerListNull(); if (fieldIntegerListNullList != null) { @@ -3684,7 +4354,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNotNullColKey); + OsList fieldShortListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldShortListNotNullColKey); fieldShortListNotNullOsList.removeAll(); RealmList fieldShortListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNotNull(); if (fieldShortListNotNullList != null) { @@ -3698,7 +4368,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldShortListNullColKey); + OsList fieldShortListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldShortListNullColKey); fieldShortListNullOsList.removeAll(); RealmList fieldShortListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldShortListNull(); if (fieldShortListNullList != null) { @@ -3712,7 +4382,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNotNullColKey); + OsList fieldByteListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldByteListNotNullColKey); fieldByteListNotNullOsList.removeAll(); RealmList fieldByteListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNotNull(); if (fieldByteListNotNullList != null) { @@ -3726,7 +4396,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldByteListNullColKey); + OsList fieldByteListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldByteListNullColKey); fieldByteListNullOsList.removeAll(); RealmList fieldByteListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldByteListNull(); if (fieldByteListNullList != null) { @@ -3740,7 +4410,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNotNullColKey); + OsList fieldDoubleListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDoubleListNotNullColKey); fieldDoubleListNotNullOsList.removeAll(); RealmList fieldDoubleListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNotNull(); if (fieldDoubleListNotNullList != null) { @@ -3754,7 +4424,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDoubleListNullColKey); + OsList fieldDoubleListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDoubleListNullColKey); fieldDoubleListNullOsList.removeAll(); RealmList fieldDoubleListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDoubleListNull(); if (fieldDoubleListNullList != null) { @@ -3768,7 +4438,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNotNullColKey); + OsList fieldFloatListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldFloatListNotNullColKey); fieldFloatListNotNullOsList.removeAll(); RealmList fieldFloatListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNotNull(); if (fieldFloatListNotNullList != null) { @@ -3782,7 +4452,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldFloatListNullColKey); + OsList fieldFloatListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldFloatListNullColKey); fieldFloatListNullOsList.removeAll(); RealmList fieldFloatListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldFloatListNull(); if (fieldFloatListNullList != null) { @@ -3796,7 +4466,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNotNullColKey); + OsList fieldDateListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDateListNotNullColKey); fieldDateListNotNullOsList.removeAll(); RealmList fieldDateListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNotNull(); if (fieldDateListNotNullList != null) { @@ -3810,7 +4480,7 @@ public static void insertOrUpdate(Realm realm, Iterator ob } - OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(colKey), columnInfo.fieldDateListNullColKey); + OsList fieldDateListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDateListNullColKey); fieldDateListNullOsList.removeAll(); RealmList fieldDateListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDateListNull(); if (fieldDateListNullList != null) { @@ -3823,6 +4493,62 @@ public static void insertOrUpdate(Realm realm, Iterator ob } } + + OsList fieldDecimal128ListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDecimal128ListNotNullColKey); + fieldDecimal128ListNotNullOsList.removeAll(); + RealmList fieldDecimal128ListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128ListNotNull(); + if (fieldDecimal128ListNotNullList != null) { + for (org.bson.types.Decimal128 fieldDecimal128ListNotNullItem : fieldDecimal128ListNotNullList) { + if (fieldDecimal128ListNotNullItem == null) { + fieldDecimal128ListNotNullOsList.addNull(); + } else { + fieldDecimal128ListNotNullOsList.addDecimal128(fieldDecimal128ListNotNullItem); + } + } + } + + + OsList fieldDecimal128ListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldDecimal128ListNullColKey); + fieldDecimal128ListNullOsList.removeAll(); + RealmList fieldDecimal128ListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldDecimal128ListNull(); + if (fieldDecimal128ListNullList != null) { + for (org.bson.types.Decimal128 fieldDecimal128ListNullItem : fieldDecimal128ListNullList) { + if (fieldDecimal128ListNullItem == null) { + fieldDecimal128ListNullOsList.addNull(); + } else { + fieldDecimal128ListNullOsList.addDecimal128(fieldDecimal128ListNullItem); + } + } + } + + + OsList fieldObjectIdListNotNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldObjectIdListNotNullColKey); + fieldObjectIdListNotNullOsList.removeAll(); + RealmList fieldObjectIdListNotNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdListNotNull(); + if (fieldObjectIdListNotNullList != null) { + for (org.bson.types.ObjectId fieldObjectIdListNotNullItem : fieldObjectIdListNotNullList) { + if (fieldObjectIdListNotNullItem == null) { + fieldObjectIdListNotNullOsList.addNull(); + } else { + fieldObjectIdListNotNullOsList.addObjectId(fieldObjectIdListNotNullItem); + } + } + } + + + OsList fieldObjectIdListNullOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.fieldObjectIdListNullColKey); + fieldObjectIdListNullOsList.removeAll(); + RealmList fieldObjectIdListNullList = ((some_test_NullTypesRealmProxyInterface) object).realmGet$fieldObjectIdListNull(); + if (fieldObjectIdListNullList != null) { + for (org.bson.types.ObjectId fieldObjectIdListNullItem : fieldObjectIdListNullList) { + if (fieldObjectIdListNullItem == null) { + fieldObjectIdListNullOsList.addNull(); + } else { + fieldObjectIdListNullOsList.addObjectId(fieldObjectIdListNullItem); + } + } + } + } } @@ -3865,6 +4591,10 @@ public static some.test.NullTypes createDetachedCopy(some.test.NullTypes realmOb unmanagedCopy.realmSet$fieldDoubleNull(realmSource.realmGet$fieldDoubleNull()); unmanagedCopy.realmSet$fieldDateNotNull(realmSource.realmGet$fieldDateNotNull()); unmanagedCopy.realmSet$fieldDateNull(realmSource.realmGet$fieldDateNull()); + unmanagedCopy.realmSet$fieldDecimal128NotNull(realmSource.realmGet$fieldDecimal128NotNull()); + unmanagedCopy.realmSet$fieldDecimal128Null(realmSource.realmGet$fieldDecimal128Null()); + unmanagedCopy.realmSet$fieldObjectIdNotNull(realmSource.realmGet$fieldObjectIdNotNull()); + unmanagedCopy.realmSet$fieldObjectIdNull(realmSource.realmGet$fieldObjectIdNull()); // Deep copy of fieldObjectNull unmanagedCopy.realmSet$fieldObjectNull(some_test_NullTypesRealmProxy.createDetachedCopy(realmSource.realmGet$fieldObjectNull(), currentDepth + 1, maxDepth, cache)); @@ -3929,6 +4659,18 @@ public static some.test.NullTypes createDetachedCopy(some.test.NullTypes realmOb unmanagedCopy.realmSet$fieldDateListNull(new RealmList()); unmanagedCopy.realmGet$fieldDateListNull().addAll(realmSource.realmGet$fieldDateListNull()); + unmanagedCopy.realmSet$fieldDecimal128ListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldDecimal128ListNotNull().addAll(realmSource.realmGet$fieldDecimal128ListNotNull()); + + unmanagedCopy.realmSet$fieldDecimal128ListNull(new RealmList()); + unmanagedCopy.realmGet$fieldDecimal128ListNull().addAll(realmSource.realmGet$fieldDecimal128ListNull()); + + unmanagedCopy.realmSet$fieldObjectIdListNotNull(new RealmList()); + unmanagedCopy.realmGet$fieldObjectIdListNotNull().addAll(realmSource.realmGet$fieldObjectIdListNotNull()); + + unmanagedCopy.realmSet$fieldObjectIdListNull(new RealmList()); + unmanagedCopy.realmGet$fieldObjectIdListNull().addAll(realmSource.realmGet$fieldObjectIdListNull()); + return unmanagedObject; } @@ -4019,6 +4761,22 @@ public String toString() { stringBuilder.append(realmGet$fieldDateNull() != null ? realmGet$fieldDateNull() : "null"); stringBuilder.append("}"); stringBuilder.append(","); + stringBuilder.append("{fieldDecimal128NotNull:"); + stringBuilder.append(realmGet$fieldDecimal128NotNull()); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldDecimal128Null:"); + stringBuilder.append(realmGet$fieldDecimal128Null() != null ? realmGet$fieldDecimal128Null() : "null"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldObjectIdNotNull:"); + stringBuilder.append(realmGet$fieldObjectIdNotNull()); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldObjectIdNull:"); + stringBuilder.append(realmGet$fieldObjectIdNull() != null ? realmGet$fieldObjectIdNull() : "null"); + stringBuilder.append("}"); + stringBuilder.append(","); stringBuilder.append("{fieldObjectNull:"); stringBuilder.append(realmGet$fieldObjectNull() != null ? "NullTypes" : "null"); stringBuilder.append("}"); @@ -4102,6 +4860,22 @@ public String toString() { stringBuilder.append("{fieldDateListNull:"); stringBuilder.append("RealmList[").append(realmGet$fieldDateListNull().size()).append("]"); stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldDecimal128ListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldDecimal128ListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldDecimal128ListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldDecimal128ListNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldObjectIdListNotNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldObjectIdListNotNull().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{fieldObjectIdListNull:"); + stringBuilder.append("RealmList[").append(realmGet$fieldObjectIdListNull().size()).append("]"); + stringBuilder.append("}"); stringBuilder.append("]"); return stringBuilder.toString(); } @@ -4115,12 +4889,12 @@ public String toString() { public int hashCode() { String realmName = proxyState.getRealm$realm().getPath(); String tableName = proxyState.getRow$realm().getTable().getName(); - long colKey = proxyState.getRow$realm().getObjectKey(); + long objKey = proxyState.getRow$realm().getObjectKey(); int result = 17; result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); - result = 31 * result + (int) (colKey ^ (colKey >>> 32)); + result = 31 * result + (int) (objKey ^ (objKey >>> 32)); return result; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java index c86513d900..dcec635152 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java @@ -280,14 +280,14 @@ public static long insert(Realm realm, some.test.Simple object, Map objects, Map cache) { @@ -304,13 +304,13 @@ public static void insert(Realm realm, Iterator objects, M cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$name = ((some_test_SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, colKey, realmGet$name, false); + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, objKey, realmGet$name, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, colKey, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, objKey, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); } } @@ -321,16 +321,16 @@ public static long insertOrUpdate(Realm realm, some.test.Simple object, Map objects, Map cache) { @@ -347,15 +347,15 @@ public static void insertOrUpdate(Realm realm, Iterator ob cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); continue; } - long colKey = OsObject.createRow(table); - cache.put(object, colKey); + long objKey = OsObject.createRow(table); + cache.put(object, objKey); String realmGet$name = ((some_test_SimpleRealmProxyInterface) object).realmGet$name(); if (realmGet$name != null) { - Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, colKey, realmGet$name, false); + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, objKey, realmGet$name, false); } else { - Table.nativeSetNull(tableNativePtr, columnInfo.nameColKey, colKey, false); + Table.nativeSetNull(tableNativePtr, columnInfo.nameColKey, objKey, false); } - Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, colKey, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, objKey, ((some_test_SimpleRealmProxyInterface) object).realmGet$age(), false); } } diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java index 4539db1355..e44e6cffce 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java @@ -16,6 +16,9 @@ package some.test; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.MutableRealmInteger; @@ -38,6 +41,10 @@ public class AllTypes extends RealmObject { private float columnFloat; private double columnDouble; private boolean columnBoolean; + @Required + private Decimal128 columnDecimal128; + @Required + private ObjectId columnObjectId; @Required private Date columnDate; @@ -61,7 +68,8 @@ public class AllTypes extends RealmObject { private RealmList columnDoubleList; private RealmList columnFloatList; private RealmList columnDateList; - + private RealmList columnDecimal128List; + private RealmList columnObjectIdList; @LinkingObjects(FIELD_PARENTS) private final RealmResults parentObjects = null; diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/NullTypes.java b/realm/realm-annotations-processor/src/test/resources/some/test/NullTypes.java index 45bc47aae6..9a2cec460d 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/NullTypes.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/NullTypes.java @@ -16,6 +16,9 @@ package some.test; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.lang.String; import java.util.Date; @@ -64,6 +67,14 @@ public class NullTypes extends RealmObject { private Date fieldDateNotNull; private Date fieldDateNull; + @Required + private Decimal128 fieldDecimal128NotNull; + private Decimal128 fieldDecimal128Null; + + @Required + private ObjectId fieldObjectIdNotNull; + private ObjectId fieldObjectIdNull; + private NullTypes fieldObjectNull; @Required @@ -106,6 +117,14 @@ public class NullTypes extends RealmObject { private RealmList fieldDateListNotNull; private RealmList fieldDateListNull; + @Required + private RealmList fieldDecimal128ListNotNull; + private RealmList fieldDecimal128ListNull; + + @Required + private RealmList fieldObjectIdListNotNull; + private RealmList fieldObjectIdListNull; + public String getFieldStringNotNull() { return realmGet$fieldStringNotNull(); } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 2ff5c31131..b83fff4241 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -207,6 +207,7 @@ dependencies { api "io.realm:realm-annotations:${version}" implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.getkeepsafe.relinker:relinker:1.4.0' + implementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY_VERSION')}" implementation('io.reactivex.rxjava2:rxandroid:2.1.1') { exclude group: 'io.reactivex.rxjava2', module: 'rxjava' } @@ -218,7 +219,6 @@ dependencies { kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 objectServerImplementation 'com.squareup.okhttp3:okhttp:3.12.0' // Going above this requires minSDK 21 - objectServerImplementation "org.mongodb:bson:3.12.0" kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' androidTestImplementation 'io.reactivex.rxjava2:rxandroid:2.1.1' @@ -230,6 +230,7 @@ dependencies { androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" androidTestImplementation "org.skyscreamer:jsonassert:1.5.0" + androidTestImplementation project(':kotlin-extensions') // specify error prone version to prevent sudden failure errorprone 'com.google.errorprone:error_prone_core:2.1.2' diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index f7c35d6a71..eaafa2d7d1 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -11,7 +11,7 @@ + android:targetSdkVersion="29"/> ()); nullTypes1.getFieldListNull().add(nullTypes2); nullTypes1.getFieldListNull().add(nullTypes3); @@ -240,6 +257,8 @@ public void insertOrUpdate_nullTypes() { assertNull(first.getFieldBooleanNull()); assertNull(first.getFieldStringNull()); assertNull(first.getFieldDateNull()); + assertNull(first.getFieldDecimal128Null()); + assertNull(first.getFieldObjectIdNull()); assertEquals(2, first.getFieldListNull().size()); assertEquals(2, first.getFieldListNull().get(0).getId()); assertEquals(3, first.getFieldListNull().get(1).getId()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 8f2f5ad754..39501d4b13 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -18,6 +18,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; @@ -27,6 +29,7 @@ import org.junit.runner.RunWith; import java.lang.reflect.Field; +import java.math.BigDecimal; import java.text.ParseException; import java.util.Arrays; import java.util.Date; @@ -93,6 +96,8 @@ public void setUp() { typedObj.setFieldBinary(new byte[]{1, 2, 3}); typedObj.setFieldBoolean(true); typedObj.setFieldDate(new Date(1000)); + typedObj.setFieldDecimal128(new Decimal128(BigDecimal.TEN)); + typedObj.setFieldObjectId(new ObjectId(TestHelper.generateObjectIdHexString(7))); typedObj.setFieldObject(typedObj); typedObj.getFieldList().add(typedObj); typedObj.getFieldIntegerList().add(1); @@ -121,16 +126,16 @@ public void tearDown() { // Types supported by the DynamicRealmObject. private enum SupportedType { - BOOLEAN, SHORT, INT, LONG, BYTE, FLOAT, DOUBLE, STRING, BINARY, DATE, OBJECT, LIST, - LIST_INTEGER, LIST_STRING, LIST_BOOLEAN, LIST_FLOAT, LIST_DOUBLE, LIST_BINARY, LIST_DATE + BOOLEAN, SHORT, INT, LONG, BYTE, FLOAT, DOUBLE, STRING, BINARY, DATE, OBJECT, DECIMAL128, OBJECT_ID, LIST, + LIST_INTEGER, LIST_STRING, LIST_BOOLEAN, LIST_FLOAT, LIST_DOUBLE, LIST_BINARY, LIST_DATE, LIST_DECIMAL128, LIST_OBJECT_ID } private enum ThreadConfinedMethods { GET_BOOLEAN, GET_BYTE, GET_SHORT, GET_INT, GET_LONG, GET_FLOAT, GET_DOUBLE, - GET_BLOB, GET_STRING, GET_DATE, GET_OBJECT, GET_LIST, GET_PRIMITIVE_LIST, GET, + GET_BLOB, GET_STRING, GET_DATE, GET_DECIMAL128, GET_OBJECT_ID, GET_OBJECT, GET_LIST, GET_PRIMITIVE_LIST, GET, SET_BOOLEAN, SET_BYTE, SET_SHORT, SET_INT, SET_LONG, SET_FLOAT, SET_DOUBLE, - SET_BLOB, SET_STRING, SET_DATE, SET_OBJECT, SET_LIST, SET_PRIMITIVE_LIST, SET, + SET_BLOB, SET_STRING, SET_DATE, SET_DECIMAL128, SET_OBJECT_ID, SET_OBJECT, SET_LIST, SET_PRIMITIVE_LIST, SET, IS_NULL, SET_NULL, @@ -152,6 +157,8 @@ private static void callThreadConfinedMethod(DynamicRealmObject obj, ThreadConfi case GET_BLOB: obj.getBlob(AllJavaTypes.FIELD_BINARY); break; case GET_STRING: obj.getString(AllJavaTypes.FIELD_STRING); break; case GET_DATE: obj.getDate(AllJavaTypes.FIELD_DATE); break; + case GET_DECIMAL128: obj.getDate(AllJavaTypes.FIELD_DECIMAL128); break; + case GET_OBJECT_ID: obj.getDate(AllJavaTypes.FIELD_OBJECT_ID); break; case GET_OBJECT: obj.getObject(AllJavaTypes.FIELD_OBJECT); break; case GET_LIST: obj.getList(AllJavaTypes.FIELD_LIST); break; case GET_PRIMITIVE_LIST: obj.getList(AllJavaTypes.FIELD_STRING_LIST, String.class); break; @@ -167,6 +174,8 @@ private static void callThreadConfinedMethod(DynamicRealmObject obj, ThreadConfi case SET_BLOB: obj.setBlob(AllJavaTypes.FIELD_BINARY, new byte[] {1, 2, 3}); break; case SET_STRING: obj.setString(AllJavaTypes.FIELD_STRING, "12345"); break; case SET_DATE: obj.setDate(AllJavaTypes.FIELD_DATE, new Date(1L)); break; + case SET_DECIMAL128: obj.setDecimal128(AllJavaTypes.FIELD_DECIMAL128, new Decimal128(BigDecimal.ONE)); break; + case SET_OBJECT_ID: obj.setObjectId(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(5))); break; case SET_OBJECT: obj.setObject(AllJavaTypes.FIELD_OBJECT, obj); break; case SET_LIST: obj.setList(AllJavaTypes.FIELD_LIST, new RealmList<>(obj)); break; case SET_PRIMITIVE_LIST: obj.setList(AllJavaTypes.FIELD_STRING_LIST,new RealmList("foo")); break; @@ -335,6 +344,8 @@ private static void callGetter(DynamicRealmObject target, SupportedType type, Li case STRING: target.getString(fieldName); break; case BINARY: target.getBlob(fieldName); break; case DATE: target.getDate(fieldName); break; + case DECIMAL128: target.getDecimal128(fieldName); break; + case OBJECT_ID: target.getObjectId(fieldName); break; case OBJECT: target.getObject(fieldName); break; case LIST: case LIST_INTEGER: @@ -344,6 +355,8 @@ private static void callGetter(DynamicRealmObject target, SupportedType type, Li case LIST_DOUBLE: case LIST_BINARY: case LIST_DATE: + case LIST_DECIMAL128: + case LIST_OBJECT_ID: target.getList(fieldName); break; default: @@ -467,6 +480,8 @@ private static void callSetter(DynamicRealmObject target, SupportedType type, Li case STRING: target.setString(fieldName, "foo"); break; case BINARY: target.setBlob(fieldName, new byte[]{}); break; case DATE: target.getDate(fieldName); break; + case DECIMAL128: target.getDecimal128(fieldName); break; + case OBJECT_ID: target.getObjectId(fieldName); break; case OBJECT: target.setObject(fieldName, null); target.setObject(fieldName, target); break; case LIST: target.setList(fieldName, new RealmList()); break; case LIST_INTEGER: target.setList(fieldName, new RealmList(1)); break; @@ -476,6 +491,8 @@ private static void callSetter(DynamicRealmObject target, SupportedType type, Li case LIST_DOUBLE: target.setList(fieldName, new RealmList(1.234D)); break; case LIST_BINARY: target.setList(fieldName, new RealmList(new byte[]{})); break; case LIST_DATE: target.setList(fieldName, new RealmList(new Date())); break; + case LIST_DECIMAL128: target.setList(fieldName, new RealmList<>(new Decimal128(BigDecimal.ONE))); break; + case LIST_OBJECT_ID: target.setList(fieldName, new RealmList<>(new ObjectId(TestHelper.generateObjectIdHexString(7)))); break; default: fail(); } @@ -531,6 +548,14 @@ public void typedGettersAndSetters() { dObj.setDate(AllJavaTypes.FIELD_DATE, new Date(1000)); assertEquals(new Date(1000), dObj.getDate(AllJavaTypes.FIELD_DATE)); break; + case DECIMAL128: + dObj.setDecimal128(AllJavaTypes.FIELD_DECIMAL128, new Decimal128(BigDecimal.ONE)); + assertEquals(new Decimal128(BigDecimal.ONE), dObj.getDecimal128(AllJavaTypes.FIELD_DECIMAL128)); + break; + case OBJECT_ID: + dObj.setObjectId(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(0))); + assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(0)), dObj.getObjectId(AllJavaTypes.FIELD_OBJECT_ID)); + break; case OBJECT: dObj.setObject(AllJavaTypes.FIELD_OBJECT, dObj); assertEquals(dObj, dObj.getObject(AllJavaTypes.FIELD_OBJECT)); @@ -556,6 +581,12 @@ public void typedGettersAndSetters() { case LIST_DATE: checkSetGetValueList(dObj, AllJavaTypes.FIELD_DATE_LIST, Date.class, new RealmList<>(null, new Date(1000))); break; + case LIST_DECIMAL128: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_DECIMAL128_LIST, Decimal128.class, new RealmList<>(null, new Decimal128(BigDecimal.ONE))); + break; + case LIST_OBJECT_ID: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_OBJECT_ID_LIST, ObjectId.class, new RealmList<>(null, new ObjectId(TestHelper.generateObjectIdHexString(0)))); + break; case LIST: // Ignores. See testGetList/testSetList. break; @@ -646,6 +677,20 @@ public void setter_null() { } catch (IllegalArgumentException ignored) { } break; + case LIST_DECIMAL128: + try { + dObj.setNull(NullTypes.FIELD_DECIMAL128_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; + case LIST_OBJECT_ID: + try { + dObj.setNull(NullTypes.FIELD_OBJECT_ID_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; case BOOLEAN: dObj.setNull(NullTypes.FIELD_BOOLEAN_NULL); assertTrue(dObj.isNull(NullTypes.FIELD_BOOLEAN_NULL)); @@ -686,6 +731,14 @@ public void setter_null() { dObj.setNull(NullTypes.FIELD_DATE_NULL); assertTrue(dObj.isNull(NullTypes.FIELD_DATE_NULL)); break; + case DECIMAL128: + dObj.setNull(NullTypes.FIELD_DECIMAL128_NULL); + assertTrue(dObj.isNull(NullTypes.FIELD_DECIMAL128_NULL)); + break; + case OBJECT_ID: + dObj.setNull(NullTypes.FIELD_OBJECT_ID_NULL); + assertTrue(dObj.isNull(NullTypes.FIELD_OBJECT_ID_NULL)); + break; default: fail("Unknown type: " + type); } @@ -711,9 +764,11 @@ public void setter_nullOnRequiredFieldsThrows() { case LIST_STRING: fieldName = NullTypes.FIELD_STRING_LIST_NULL; break; case LIST_BOOLEAN: fieldName = NullTypes.FIELD_BOOLEAN_LIST_NULL; break; case LIST_FLOAT: fieldName = NullTypes.FIELD_FLOAT_LIST_NULL; break; - case LIST_DOUBLE: fieldName = NullTypes.FIELD_DATE_LIST_NULL; break; + case LIST_DOUBLE: fieldName = NullTypes.FIELD_DOUBLE_LIST_NULL; break; case LIST_BINARY: fieldName = NullTypes.FIELD_BINARY_LIST_NULL; break; case LIST_DATE: fieldName = NullTypes.FIELD_DATE_LIST_NULL; break; + case LIST_DECIMAL128: fieldName = NullTypes.FIELD_DECIMAL128_LIST_NULL; break; + case LIST_OBJECT_ID: fieldName = NullTypes.FIELD_OBJECT_ID_LIST_NULL; break; case BOOLEAN: fieldName = NullTypes.FIELD_BOOLEAN_NOT_NULL; break; case BYTE: fieldName = NullTypes.FIELD_BYTE_NOT_NULL; break; case SHORT: fieldName = NullTypes.FIELD_SHORT_NOT_NULL; break; @@ -724,6 +779,8 @@ public void setter_nullOnRequiredFieldsThrows() { case STRING: fieldName = NullTypes.FIELD_STRING_NOT_NULL; break; case BINARY: fieldName = NullTypes.FIELD_BYTES_NOT_NULL; break; case DATE: fieldName = NullTypes.FIELD_DATE_NOT_NULL; break; + case DECIMAL128: fieldName = NullTypes.FIELD_DECIMAL128_NOT_NULL; break; + case OBJECT_ID: fieldName = NullTypes.FIELD_OBJECT_ID_NOT_NULL; break; default: fail("Unknown type: " + type); } @@ -1157,6 +1214,14 @@ public void untypedGetterSetter() { dObj.set(AllJavaTypes.FIELD_DATE, new Date(1000)); assertEquals(new Date(1000), dObj.get(AllJavaTypes.FIELD_DATE)); break; + case DECIMAL128: + dObj.set(AllJavaTypes.FIELD_DECIMAL128, new Decimal128(BigDecimal.ONE)); + assertEquals(new Decimal128(BigDecimal.ONE), dObj.get(AllJavaTypes.FIELD_DECIMAL128)); + break; + case OBJECT_ID: + dObj.set(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(7))); + assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(7)), dObj.get(AllJavaTypes.FIELD_OBJECT_ID)); + break; case OBJECT: dObj.set(AllJavaTypes.FIELD_OBJECT, dObj); assertEquals(dObj, dObj.get(AllJavaTypes.FIELD_OBJECT)); @@ -1226,6 +1291,22 @@ public void untypedGetterSetter() { assertArrayEquals(newList.toArray(), list.toArray()); break; } + case LIST_DECIMAL128: { + RealmList newList = new RealmList<>(null, new Decimal128(BigDecimal.ONE)); + dObj.set(AllJavaTypes.FIELD_DECIMAL128_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_DECIMAL128_LIST, Decimal128.class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } + case LIST_OBJECT_ID: { + RealmList newList = new RealmList<>(null, new ObjectId(TestHelper.generateObjectIdHexString(0))); + dObj.set(AllJavaTypes.FIELD_OBJECT_ID_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_OBJECT_ID_LIST, ObjectId.class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } default: fail(); } @@ -1271,6 +1352,14 @@ public void untypedSetter_usingStringConversion() { dObj.set(AllJavaTypes.FIELD_DATE, "1000"); assertEquals(new Date(1000), dObj.getDate(AllJavaTypes.FIELD_DATE)); break; + case DECIMAL128: + dObj.set(AllJavaTypes.FIELD_DECIMAL128, "1"); + assertEquals(new Decimal128(BigDecimal.ONE), dObj.get(AllJavaTypes.FIELD_DECIMAL128)); + break; + case OBJECT_ID: + dObj.set(AllJavaTypes.FIELD_OBJECT_ID, TestHelper.generateObjectIdHexString(7)); + assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(7)), dObj.get(AllJavaTypes.FIELD_OBJECT_ID)); + break; // These types don't have a string representation that can be parsed. case OBJECT: case LIST: @@ -1281,6 +1370,8 @@ public void untypedSetter_usingStringConversion() { case LIST_DOUBLE: case LIST_BINARY: case LIST_DATE: + case LIST_DECIMAL128: + case LIST_OBJECT_ID: case STRING: case BINARY: case BYTE: @@ -1322,7 +1413,12 @@ public void untypedSetter_illegalImplicitConversionThrows() { case DATE: dObj.set(AllJavaTypes.FIELD_DATE, "foo"); break; - + case DECIMAL128: + dObj.set(AllJavaTypes.FIELD_DECIMAL128, "foo"); + break; + case OBJECT_ID: + dObj.set(AllJavaTypes.FIELD_OBJECT_ID, "foo"); + break; // These types don't have a string representation that can be parsed. case BOOLEAN: // Boolean is special as it returns false for all strings != "true" case BYTE: @@ -1335,6 +1431,8 @@ public void untypedSetter_illegalImplicitConversionThrows() { case LIST_DOUBLE: case LIST_BINARY: case LIST_DATE: + case LIST_DECIMAL128: + case LIST_OBJECT_ID: case STRING: case BINARY: continue; @@ -1413,11 +1511,11 @@ public void getFieldNames() { String[] expectedKeys = {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID, AllJavaTypes.FIELD_LONG, AllJavaTypes.FIELD_SHORT, AllJavaTypes.FIELD_INT, AllJavaTypes.FIELD_BYTE, AllJavaTypes.FIELD_FLOAT, AllJavaTypes.FIELD_DOUBLE, AllJavaTypes.FIELD_BOOLEAN, AllJavaTypes.FIELD_DATE, - AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_OBJECT, AllJavaTypes.FIELD_LIST, + AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_DECIMAL128, AllJavaTypes.FIELD_OBJECT_ID, AllJavaTypes.FIELD_OBJECT, AllJavaTypes.FIELD_LIST, AllJavaTypes.FIELD_STRING_LIST, AllJavaTypes.FIELD_BINARY_LIST, AllJavaTypes.FIELD_BOOLEAN_LIST, AllJavaTypes.FIELD_LONG_LIST, AllJavaTypes.FIELD_INTEGER_LIST, AllJavaTypes.FIELD_SHORT_LIST, AllJavaTypes.FIELD_BYTE_LIST, AllJavaTypes.FIELD_DOUBLE_LIST, AllJavaTypes.FIELD_FLOAT_LIST, - AllJavaTypes.FIELD_DATE_LIST}; + AllJavaTypes.FIELD_DATE_LIST, AllJavaTypes.FIELD_DECIMAL128_LIST, AllJavaTypes.FIELD_OBJECT_ID_LIST}; String[] keys = dObjTyped.getFieldNames(); // After the stable ID support, primary key field will be inserted first before others. So even FIELD_STRING is // the first defined field in the class, it will be inserted after FIELD_ID. diff --git a/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java b/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java index bf71ce5b3b..002c8a9bf5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/FrozenObjectsTests.java @@ -17,12 +17,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.math.BigDecimal; import java.util.Arrays; import javax.annotation.Nullable; @@ -744,8 +747,10 @@ private Realm createDataForLiveRealm(int dataSize) { obj.setColumnString("String " + i); obj.setColumnLong(i); obj.setColumnRealmList(list); - obj.setColumnStringList(new RealmList("Foo", "Bar", "Baz")); + obj.setColumnStringList(new RealmList<>("Foo", "Bar", "Baz")); obj.setColumnRealmObject(r.copyToRealm(new Dog("Dog 42"))); + obj.setColumnObjectId(new ObjectId(TestHelper.randomObjectIdHexString())); + obj.setColumnDecimal128(new Decimal128(new BigDecimal(i + ".23456789"))); r.insert(obj); } }); diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index 61a49ece18..ed2b7b4fc9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -202,6 +202,12 @@ public void linkingObjects_invalidFieldType() { case DOUBLE: object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE); break; + case DECIMAL128: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DECIMAL128); + break; + case OBJECT_ID: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_OBJECT_ID); + break; case INTEGER_LIST: // FIXME zaki50 enable this once Primitive List is implemented //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_INT_LIST); @@ -237,6 +243,16 @@ public void linkingObjects_invalidFieldType() { //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE_LIST); //break; throw new IllegalArgumentException("Unexpected field type"); + case DECIMAL128_LIST: + // FIXME enable this once Primitive List is implemented + //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE_LIST); + //break; + throw new IllegalArgumentException("Unexpected field type"); + case OBJECT_ID_LIST: + // FIXME enable this once Primitive List is implemented + //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE_LIST); + //break; + throw new IllegalArgumentException("Unexpected field type"); default: fail("unknown type: " + fieldType); break; diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java index b7f16c586e..dfe6a85864 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsQueryTests.java @@ -17,9 +17,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.junit.Test; import org.junit.runner.RunWith; +import java.math.BigDecimal; import java.util.Date; import io.realm.entities.AllJavaTypes; @@ -152,6 +155,12 @@ public void isNull_object() { // 10 Date assertEquals(1, realm.where(NullTypes.class).isNull( NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_DATE_NULL).count()); + // Decimal128 + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_DECIMAL128_NULL).count()); + // ObjectId + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_OBJECT_ID_NULL).count()); } // Tests isNull on link's nullable field. @@ -189,6 +198,12 @@ public void isNull_list() { // 10 Date assertEquals(1, realm.where(NullTypes.class).isNull( NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_DATE_NULL).count()); + // 10 Decimal128 + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_DECIMAL128_NULL).count()); + // 10 ObjectId + assertEquals(1, realm.where(NullTypes.class).isNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_OBJECT_ID_NULL).count()); } @Test @@ -266,6 +281,12 @@ public void isNotNull_object() { // 10 Date assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_DATE_NULL).count()); + // 11 Decimal128 + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_DECIMAL128_NULL).count()); + // 12 ObjectId + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_OBJECT + "." + NullTypes.FIELD_OBJECT_ID_NULL).count()); } // Tests isNotNull on link's nullable field. @@ -303,6 +324,13 @@ public void isNotNull_list() { // 10 Date assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_DATE_NULL).count()); + // 11 Decimal128 + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_DECIMAL128_NULL).count()); + // 12 ObjectId + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_LO_LIST + "." + NullTypes.FIELD_OBJECT_ID_NULL).count()); + } @Test @@ -584,6 +612,10 @@ private void populateTestRealmForNullTests(Realm testRealm) { Date[] dates = {new Date(0), null, new Date(10000)}; NullTypes[] nullTypesArray = new NullTypes[3]; + Decimal128[] decimals = {new Decimal128(BigDecimal.TEN), null, new Decimal128(BigDecimal.ONE)}; + + ObjectId[] ids = {new ObjectId(TestHelper.generateObjectIdHexString(10)), null, new ObjectId(TestHelper.generateObjectIdHexString(1))}; + testRealm.beginTransaction(); for (int i = 0; i < 3; i++) { NullTypes nullTypes = new NullTypes(); @@ -630,6 +662,10 @@ private void populateTestRealmForNullTests(Realm testRealm) { nullTypes.setFieldDateNotNull(dates[i]); } + nullTypes.setFieldDecimal128Null(decimals[i]); + + nullTypes.setFieldObjectIdNull(ids[i]); + nullTypesArray[i] = testRealm.copyToRealm(nullTypes); } nullTypesArray[0].setFieldObjectNull(nullTypesArray[0]); diff --git a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java index 5ef1233760..2fd36f16f3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java @@ -71,6 +71,8 @@ public abstract class QueryTests { list.remove(RealmFieldType.DOUBLE_LIST); list.remove(RealmFieldType.FLOAT_LIST); list.remove(RealmFieldType.DATE_LIST); + list.remove(RealmFieldType.DECIMAL128_LIST); + list.remove(RealmFieldType.OBJECT_ID_LIST); NOT_SUPPORTED_IS_EMPTY_TYPES = Collections.unmodifiableList(list); NOT_SUPPORTED_IS_NOT_EMPTY_TYPES = Collections.unmodifiableList(list); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index 873b3148ca..130a23561e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -18,10 +18,13 @@ import android.content.Context; import android.os.Build; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; import android.util.Base64; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -34,6 +37,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.math.BigDecimal; import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -247,6 +251,112 @@ public void createObjectFromJson_dateAsString() throws JSONException { assertEquals(new Date(1000), obj.getColumnDate()); } + @Test + public void createObjectFromJson_decimal128() throws JSONException { + JSONObject json = new JSONObject(); + json.put("columnDecimal128", new Decimal128(BigDecimal.TEN)); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new Decimal128(BigDecimal.TEN), obj.getColumnDecimal128()); + } + + @Test + public void createUsingJsonStream_decimal128() throws JSONException { + JSONObject json = new JSONObject(); + json.put("columnDecimal128", new Decimal128(BigDecimal.TEN)); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new Decimal128(BigDecimal.TEN), obj.getColumnDecimal128()); + } + + @Test + public void createObjectFromJson_decimal128AsInt() throws JSONException { + JSONObject json = new JSONObject(); + json.put("columnDecimal128", -42); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new Decimal128(-42), obj.getColumnDecimal128()); + } + + @Test + public void createObjectFromJson_decimal128AsLong() throws JSONException { + JSONObject json = new JSONObject(); + json.put("columnDecimal128", -32361122672259149L); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new Decimal128(-32361122672259149L), obj.getColumnDecimal128()); + } + + @Test + public void createObjectFromJson_decimal128AsDouble() throws JSONException { + JSONObject json = new JSONObject(); + json.put("columnDecimal128", 0.30000001192092896D); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new Decimal128(new BigDecimal(0.30000001192092896D)), obj.getColumnDecimal128()); + } + + @Test + public void createObjectFromJson_decimal128AsString() throws JSONException { + JSONObject json = new JSONObject(); + json.put("columnDecimal128", "32361122672259149"); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(Decimal128.parse("32361122672259149"), obj.getColumnDecimal128()); + } + + @Test + public void createObjectFromJson_objectId() throws JSONException { + JSONObject json = new JSONObject(); + String idHex = TestHelper.generateObjectIdHexString(7); + json.put("columnObjectId", new ObjectId(idHex)); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new ObjectId(idHex), obj.getColumnObjectId()); + } + + @Test + public void createObjectFromJson_objectIdAsString() throws JSONException { + JSONObject json = new JSONObject(); + String idHex = TestHelper.generateObjectIdHexString(7); + json.put("columnObjectId", idHex); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new ObjectId(idHex), obj.getColumnObjectId()); + } + @Test public void createObjectFromJson_dateAsStringTimeZone() throws JSONException { // Oct 03 2015 14:45.33 @@ -759,6 +869,76 @@ public void createObjectFromJson_streamDateAsLong() throws IOException { assertEquals(new Date(1000), obj.getColumnDate()); } + @Test + public void createObjectFromJson_streamDecimal128AsInt() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + + InputStream in = TestHelper.loadJsonFromAssets(context, "decimal128_as_int.json"); + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, in); + realm.commitTransaction(); + in.close(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new Decimal128(-42), obj.getColumnDecimal128()); + } + + @Test + public void createObjectFromJson_streamDecimal128AsLong() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + + InputStream in = TestHelper.loadJsonFromAssets(context, "decimal128_as_long.json"); + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, in); + realm.commitTransaction(); + in.close(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new Decimal128(-32361122672259149L), obj.getColumnDecimal128()); + } + + @Test + public void createObjectFromJson_streamDecimal128AsDouble() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + + InputStream in = TestHelper.loadJsonFromAssets(context, "decimal128_as_double.json"); + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, in); + realm.commitTransaction(); + in.close(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(Decimal128.parse("0.30000001192092896"), obj.getColumnDecimal128()); + } + + @Test + public void createObjectFromJson_streamDecimal128AsString() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + + InputStream in = TestHelper.loadJsonFromAssets(context, "decimal128_as_string.json"); + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, in); + realm.commitTransaction(); + in.close(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(Decimal128.parse("32361122672259149"), obj.getColumnDecimal128()); + } + + @Test + public void createObjectFromJson_streamObjectIdAsString() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + + InputStream in = TestHelper.loadJsonFromAssets(context, "objectid_as_string.json"); + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, in); + realm.commitTransaction(); + in.close(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(new ObjectId("789ABCDEF0123456789ABCDE"), obj.getColumnObjectId()); + } + @Test public void createObjectFromJson_streamDateAsString() throws IOException { assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); @@ -1560,6 +1740,26 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti fail("Unexpected exception: " + e); } + // 11 Decimal128 + try { + realm.createObjectFromJson(NullTypes.class, array.getJSONObject(10)); + fail(); + } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_DECIMAL128_NOT_NULL)); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } + + // 12 ObjectId + try { + realm.createObjectFromJson(NullTypes.class, array.getJSONObject(11)); + fail(); + } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_OBJECT_ID_NOT_NULL)); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } + realm.cancelTransaction(); } @@ -1674,6 +1874,26 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE } finally { realm.cancelTransaction(); } + // 11 Decimal128 + try { + realm.beginTransaction(); + realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(10))); + fail(); + } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_DECIMAL128_NOT_NULL)); + } finally { + realm.cancelTransaction(); + } + // 12 ObjectId + try { + realm.beginTransaction(); + realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(11))); + fail(); + } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_OBJECT_ID_NOT_NULL)); + } finally { + realm.cancelTransaction(); + } } /** diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index b22f7fcc37..16b1180e1a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -111,7 +111,6 @@ public void tearDown() { } } - // FIXME remove? @Test public void row_isValid() { realm.beginTransaction(); @@ -120,7 +119,7 @@ public void row_isValid() { realm.commitTransaction(); assertNotNull("RealmObject.realmGetRow returns zero ", row); - assertEquals(17, row.getColumnCount()); + assertEquals(21, row.getColumnCount()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index c128e8f70d..f6c050a48e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -18,11 +18,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import java.lang.reflect.Field; +import java.math.BigDecimal; import java.util.Date; import java.util.Locale; import java.util.concurrent.CountDownLatch; @@ -68,6 +71,8 @@ private void populateTestRealm(Realm testRealm, int dataSize) { allTypes.setColumnFloat(1.2345f + i); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); + allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(i))); + allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(i + ".23456789"))); NonLatinFieldNames nonLatinFieldNames = testRealm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); @@ -652,6 +657,23 @@ public void equalTo() { assertEquals(0, resultList.size()); } + @Test + public void equalTo_decimal128() { + populateTestRealm(realm, 10); + RealmResults resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_DECIMAL128, new Decimal128(new BigDecimal( "7.23456789"))).findAll(); + assertEquals(1, resultList.size()); + assertEquals(new Decimal128(new BigDecimal( "7.23456789")), resultList.get(0).getColumnDecimal128()); + } + + @Test + public void equalTo_objectId() { + populateTestRealm(realm, 10); + RealmResults resultList = realm.where(AllTypes.class).sort(AllTypes.FIELD_OBJECT_ID, Sort.ASCENDING).findAll(); + for (int i = 0; i < 10; i++) { + assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(i)), resultList.get(i).getColumnObjectId()); + } + } + @Test public void equalTo_date() { final int TEST_OBJECTS_COUNT = 200; @@ -2501,6 +2523,12 @@ public void isNotNull_linkField() { fail(); } catch (IllegalArgumentException ignored) { } + + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_OBJECT_NULL + "." + NullTypes.FIELD_DECIMAL128_NULL).count()); + + assertEquals(1, realm.where(NullTypes.class).isNotNull( + NullTypes.FIELD_OBJECT_NULL + "." + NullTypes.FIELD_OBJECT_ID_NULL).count()); } // Tests isNotNull on link's not-nullable field. Should throw. @@ -2579,6 +2607,22 @@ public void isNotNull_linkFieldNotNullable() { } catch (IllegalArgumentException ignored) { } // 11 Object skipped, RealmObject is always nullable. + + // 10 Decimal128 + try { + realm.where(NullTypes.class) + .isNotNull(NullTypes.FIELD_OBJECT_NULL + "." + NullTypes.FIELD_DECIMAL128_NOT_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + + // 10 ObjectId + try { + realm.where(NullTypes.class) + .isNotNull(NullTypes.FIELD_OBJECT_NULL + "." + NullTypes.FIELD_OBJECT_ID_NOT_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } } // Calling isNull on fields with the RealmList type will trigger an exception. @@ -2775,6 +2819,12 @@ public void isEmpty_illegalFieldTypeThrows() { case DATE: realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_DATE).findAll(); break; + case DECIMAL128: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_DECIMAL128).findAll(); + break; + case OBJECT_ID: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT_ID).findAll(); + break; default: fail("Unknown type: " + type); } @@ -2888,6 +2938,12 @@ public void isNotEmpty_illegalFieldTypeThrows() { case DATE: realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_DATE).findAll(); break; + case DECIMAL128: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_DECIMAL128).findAll(); + break; + case OBJECT_ID: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_OBJECT_ID).findAll(); + break; default: fail("Unknown type: " + type); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 90d0b13b6c..0c29521fd0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -19,6 +19,8 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.json.JSONException; import org.junit.After; import org.junit.Before; @@ -29,6 +31,7 @@ import org.mockito.Mockito; import org.skyscreamer.jsonassert.JSONAssert; +import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; @@ -736,6 +739,8 @@ private void populateMappedAllJavaTypes(int objects) { obj.fieldString = ("test data " + i); obj.fieldLong = i; obj.fieldObject = obj; + obj.fieldDecimal128 = new Decimal128( i); + obj.fieldObjectId = new ObjectId(TestHelper.generateObjectIdHexString(i)); obj.fieldList.add(obj); } realm.commitTransaction(); @@ -754,6 +759,8 @@ private void populateAllJavaTypes(int objects) { obj.setFieldString("test data " + i); obj.setFieldLong(i); obj.setFieldObject(obj); + obj.setFieldDecimal128(new Decimal128(new BigDecimal(i + ".23456789"))); + obj.setFieldObjectId(new ObjectId(TestHelper.generateObjectIdHexString(i))); obj.getFieldList().add(obj); } realm.commitTransaction(); @@ -770,6 +777,8 @@ enum BulkSetMethods { DOUBLE, BINARY, DATE, + DECIMAL128, + OBJECT_ID, OBJECT, MODEL_LIST, STRING_VALUE_LIST, @@ -781,7 +790,9 @@ enum BulkSetMethods { FLOAT_VALUE_LIST, DOUBLE_VALUE_LIST, BINARY_VALUE_LIST, - DATE_VALUE_LIST + DATE_VALUE_LIST, + DECIMAL128_VALUE_LIST, + OBJECT_ID_VALUE_LIST } interface ElementValidator { @@ -845,7 +856,20 @@ public void setValue() { collection.setValue(AllJavaTypes.FIELD_DATE, new Date(1000)); assertElements(collection, obj -> assertEquals(new Date(1000), obj.getFieldDate())); collection.setValue(AllJavaTypes.FIELD_DATE, null); - assertElements(collection, obj -> assertEquals(null, obj.getFieldDate())); + assertElements(collection, obj -> assertNull(obj.getFieldDate())); + break; + case DECIMAL128: + collection.setValue(AllJavaTypes.FIELD_DECIMAL128, new Decimal128(1000)); + assertElements(collection, obj -> assertEquals(new Decimal128(1000), obj.getFieldDecimal128())); + collection.setValue(AllJavaTypes.FIELD_DECIMAL128, null); + assertElements(collection, obj -> assertNull(obj.getFieldDecimal128())); + break; + case OBJECT_ID: + String hex = TestHelper.randomObjectIdHexString(); + collection.setValue(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(hex)); + assertElements(collection, obj -> assertEquals(new ObjectId(hex), obj.getFieldObjectId())); + collection.setValue(AllJavaTypes.FIELD_OBJECT_ID, null); + assertElements(collection, obj -> assertNull(obj.getFieldObjectId())); break; case OBJECT: { AllJavaTypes childObj = realm.createObject(AllJavaTypes.class, 42); @@ -954,6 +978,26 @@ public void setValue() { }); break; } + case DECIMAL128_VALUE_LIST: { + RealmList list = new RealmList<>(new Decimal128(1000), new Decimal128(2000)); + collection.setValue(AllJavaTypes.FIELD_DECIMAL128_LIST, list); + assertElements(collection, obj -> { + assertEquals(new Decimal128(1000), obj.getFieldDecimal128List().first()); + assertEquals(new Decimal128(2000), obj.getFieldDecimal128List().last()); + }); + break; + } + case OBJECT_ID_VALUE_LIST: { + String hex1 = TestHelper.randomObjectIdHexString(); + String hex2 = TestHelper.randomObjectIdHexString(); + RealmList list = new RealmList<>(new ObjectId(hex1), new ObjectId(hex2)); + collection.setValue(AllJavaTypes.FIELD_OBJECT_ID_LIST, list); + assertElements(collection, obj -> { + assertEquals(new ObjectId(hex1), obj.getFieldObjectIdList().first()); + assertEquals(new ObjectId(hex2), obj.getFieldObjectIdList().last()); + }); + break; + } default: fail("Unknown type: " + type); } @@ -1009,6 +1053,15 @@ public void setValue_implicitConversions() { collection.setValue(AllJavaTypes.FIELD_DATE, "/Date(2000+0000)/"); assertElements(collection, obj -> assertEquals(new Date(2000), obj.getFieldDate())); break; + case DECIMAL128: + collection.setValue(AllJavaTypes.FIELD_DECIMAL128, "1.234"); + assertElements(collection, obj -> assertEquals(Decimal128.parse("1.234"), obj.getFieldDecimal128())); + break; + case OBJECT_ID: + String hex = TestHelper.randomObjectIdHexString(); + collection.setValue(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(hex)); + assertElements(collection, obj -> assertEquals(new ObjectId(hex), obj.getFieldObjectId())); + break; // These types do not offer any implicit conversion case STRING: @@ -1025,6 +1078,8 @@ public void setValue_implicitConversions() { case DOUBLE_VALUE_LIST: case BINARY_VALUE_LIST: case DATE_VALUE_LIST: + case DECIMAL128_VALUE_LIST: + case OBJECT_ID_VALUE_LIST: continue; default: @@ -1107,7 +1162,19 @@ public void setValue_specificType() { collection.setDate(AllJavaTypes.FIELD_DATE, new Date(1000)); assertElements(collection, obj -> assertEquals(new Date(1000), obj.getFieldDate())); collection.setDate(AllJavaTypes.FIELD_DATE, null); - assertElements(collection, obj -> assertEquals(null, obj.getFieldDate())); + assertElements(collection, obj -> assertNull(obj.getFieldDate())); + break; + case DECIMAL128: + collection.setDecimal128(AllJavaTypes.FIELD_DECIMAL128, new Decimal128(1000)); + assertElements(collection, obj -> assertEquals(new Decimal128(1000), obj.getFieldDecimal128())); + collection.setDecimal128(AllJavaTypes.FIELD_DECIMAL128, null); + assertElements(collection, obj -> assertNull(obj.getFieldDecimal128())); + break; + case OBJECT_ID: + collection.setObjectId(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(1))); + assertElements(collection, obj -> assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(1)), obj.getFieldObjectId())); + collection.setObjectId(AllJavaTypes.FIELD_OBJECT_ID, null); + assertElements(collection, obj -> assertNull(obj.getFieldObjectId())); break; case OBJECT: { AllJavaTypes childObj = realm.createObject(AllJavaTypes.class, 42); @@ -1216,6 +1283,26 @@ public void setValue_specificType() { }); break; } + case DECIMAL128_VALUE_LIST: { + RealmList list = new RealmList<>(new Decimal128(1000), new Decimal128(2000)); + collection.setList(AllJavaTypes.FIELD_DECIMAL128_LIST, list); + assertElements(collection, obj -> { + assertEquals(new Decimal128(1000), obj.getFieldDecimal128List().first()); + assertEquals(new Decimal128(2000), obj.getFieldDecimal128List().last()); + }); + break; + } + case OBJECT_ID_VALUE_LIST: { + String hex1 = TestHelper.randomObjectIdHexString(); + String hex2 = TestHelper.randomObjectIdHexString(); + RealmList list = new RealmList<>(new ObjectId(hex1), new ObjectId(hex2)); + collection.setList(AllJavaTypes.FIELD_OBJECT_ID_LIST, list); + assertElements(collection, obj -> { + assertEquals(new ObjectId(hex1), obj.getFieldObjectIdList().first()); + assertEquals(new ObjectId(hex2), obj.getFieldObjectIdList().last()); + }); + break; + } default: fail("Unknown type: " + type); } @@ -1316,6 +1403,8 @@ public void setValue_specificType_wrongFieldNameThrows() { case DOUBLE: collection.setDouble("foo", 1.234); break; case BINARY: collection.setBlob("foo", new byte[]{1,2,3}); break; case DATE: collection.setDate("foo", new Date(1000)); break; + case DECIMAL128: collection.setDecimal128("foo", new Decimal128(1000)); break; + case OBJECT_ID: collection.setObjectId("foo", new ObjectId(TestHelper.randomObjectIdHexString())); break; case OBJECT: collection.setObject("foo", realm.createObject(AllTypes.class)); break; case MODEL_LIST: collection.setList("foo", new RealmList<>()); break; case STRING_VALUE_LIST: collection.setList("foo", new RealmList<>("Foo")); break; @@ -1328,6 +1417,8 @@ public void setValue_specificType_wrongFieldNameThrows() { case DOUBLE_VALUE_LIST: collection.setList("foo", new RealmList<>(1.1D)); break; case BINARY_VALUE_LIST: collection.setList("foo", new RealmList<>(new byte[] {})); break; case DATE_VALUE_LIST: collection.setList("foo", new RealmList<>(new Date())); break; + case DECIMAL128_VALUE_LIST: collection.setList("foo", new RealmList<>(new Decimal128(1000))); break; + case OBJECT_ID_VALUE_LIST: collection.setList("foo", new RealmList<>(new ObjectId(TestHelper.randomObjectIdHexString()))); break; default: fail("Unknown type: " + type); } @@ -1356,6 +1447,8 @@ public void setValue_specificType_wrongTypeThrows() { case DOUBLE: collection.setDouble(AllJavaTypes.FIELD_STRING, 1.234); break; case BINARY: collection.setBlob(AllJavaTypes.FIELD_STRING, new byte[]{1,2,3}); break; case DATE: collection.setDate(AllJavaTypes.FIELD_STRING, new Date(1000)); break; + case DECIMAL128: collection.setDecimal128(AllJavaTypes.FIELD_STRING, new Decimal128(1000)); break; + case OBJECT_ID: collection.setObjectId(AllJavaTypes.FIELD_STRING, new ObjectId(TestHelper.randomObjectIdHexString())); break; case OBJECT: collection.setObject(AllJavaTypes.FIELD_STRING, realm.createObject(AllJavaTypes.class, 42)); break; case MODEL_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(realm.createObject(AllJavaTypes.class, 43))); break; case STRING_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>("Foo")); break; @@ -1368,6 +1461,8 @@ public void setValue_specificType_wrongTypeThrows() { case DOUBLE_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(2.2D)); break; case BINARY_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(new byte[]{})); break; case DATE_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(new Date())); break; + case DECIMAL128_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(new Decimal128(1000))); break; + case OBJECT_ID_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(new ObjectId(TestHelper.randomObjectIdHexString()))); break; default: fail("Unknown type: " + type); } @@ -1398,6 +1493,15 @@ public void setValue_specificType_primaryKeyFieldThrows() { fail(); } catch (IllegalStateException ignore) { } + + try { + RealmResults collection = realm.where(ObjectIdPrimaryKeyRequired.class).findAll(); + collection.setObjectId("id", new ObjectId(TestHelper.randomObjectIdHexString())); + fail(); + } catch (IllegalStateException ignore) { + } + + } @Test @@ -1447,6 +1551,15 @@ public void setValue_specificType_modelClassNameOnTypedRealms() { collection.setDate("fieldDate", new Date(1000)); assertElements(collection, obj -> assertEquals(new Date(1000), obj.fieldDate)); break; + case DECIMAL128: + collection.setDecimal128("fieldDecimal128", new Decimal128(1000)); + assertElements(collection, obj -> assertEquals(new Decimal128(1000), obj.fieldDecimal128)); + break; + case OBJECT_ID: +// String hex = TestHelper.randomObjectIdHexString(); +// collection.setObjectId("fieldObjectId", new ObjectId(hex)); +// assertElements(collection, obj -> assertEquals(new ObjectId(hex), obj.fieldObjectId)); + break; case OBJECT: { MappedAllJavaTypes childObj = realm.createObject(MappedAllJavaTypes.class, 42); collection.setObject("fieldObject", childObj); @@ -1532,6 +1645,21 @@ public void setValue_specificType_modelClassNameOnTypedRealms() { assertEquals(new Date(1000), obj.fieldDateList.first()); }); break; + case DECIMAL128_VALUE_LIST: + collection.setList("fieldDecimalList", new RealmList<>(new Decimal128(1000))); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldDecimalList.size()); + assertEquals(new Decimal128(1000), obj.fieldDecimalList.first()); + }); + break; + case OBJECT_ID_VALUE_LIST: + String hex = TestHelper.randomObjectIdHexString(); + collection.setList("fieldObjectIdList", new RealmList<>(new ObjectId(hex))); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldObjectIdList.size()); + assertEquals(new ObjectId(hex), obj.fieldObjectIdList.first()); + }); + break; default: fail("Unknown type: " + type); } @@ -1587,6 +1715,15 @@ public void setValue_specificType_internalNameOnDynamicRealms() { collection.setDate("field_date", new Date(1000)); assertElements(collection, obj -> assertEquals(new Date(1000), obj.getDate("field_date"))); break; + case DECIMAL128: + collection.setDecimal128("field_decimal128", new Decimal128(1000)); + assertElements(collection, obj -> assertEquals(new Decimal128(1000), obj.getDecimal128("field_decimal128"))); + break; + case OBJECT_ID: + String hex = TestHelper.randomObjectIdHexString(); + collection.setObjectId("field_object_id", new ObjectId(hex)); + assertElements(collection, obj -> assertEquals(new ObjectId(hex), obj.getObjectId("field_object_id"))); + break; case OBJECT: { DynamicRealmObject childObj = dynamicRealm.createObject("MappedAllJavaTypes", 42); collection.setObject("field_object", childObj); @@ -1683,6 +1820,23 @@ public void setValue_specificType_internalNameOnDynamicRealms() { assertEquals(new Date(1000), list.first()); }); break; + case DECIMAL128_VALUE_LIST: + collection.setList("field_decimal_list", new RealmList<>(new Decimal128(1000))); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_decimal_list", Decimal128.class); + assertEquals(1, list.size()); + assertEquals(new Decimal128(1000), list.first()); + }); + break; + case OBJECT_ID_VALUE_LIST: + hex = TestHelper.randomObjectIdHexString(); + collection.setList("field_object_id_list", new RealmList<>(new ObjectId(hex))); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_object_id_list", ObjectId.class); + assertEquals(1, list.size()); + assertEquals(new ObjectId(hex), list.first()); + }); + break; default: fail("Unknown type: " + type); } @@ -1737,6 +1891,8 @@ public void asJSON() throws JSONException { allTypes.setColumnDouble(0.89123); allTypes.setColumnBoolean(false); allTypes.setColumnDate(date); + allTypes.setColumnDecimal128(new Decimal128(new BigDecimal("0.123456789"))); + allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(7))); allTypes.setColumnBinary(new byte[]{1, 2, 3}); allTypes.setColumnMutableRealmInteger(0); allTypes.setColumnRealmObject(dog1); @@ -1754,6 +1910,14 @@ public void asJSON() throws JSONException { allTypes.getColumnFloatList().add(0.13f); allTypes.getColumnDateList().add(date); allTypes.getColumnDateList().add(date); + allTypes.getColumnDecimal128List().add(new Decimal128(-42)); + allTypes.getColumnDecimal128List().add(Decimal128.NaN); + allTypes.getColumnDecimal128List().add(Decimal128.NEGATIVE_ZERO); + allTypes.getColumnDecimal128List().add(Decimal128.POSITIVE_ZERO); + allTypes.getColumnDecimal128List().add(Decimal128.NEGATIVE_INFINITY); + allTypes.getColumnDecimal128List().add(Decimal128.POSITIVE_INFINITY); + allTypes.getColumnObjectIdList().add(new ObjectId(TestHelper.generateObjectIdHexString(1))); + allTypes.getColumnObjectIdList().add(new ObjectId(TestHelper.generateObjectIdHexString(2))); AllTypes allTypes2 = realm.createObject(AllTypes.class); allTypes2.setColumnString("alltypes2"); @@ -1764,81 +1928,99 @@ public void asJSON() throws JSONException { assertEquals(1, all.size()); String json = all.asJSON(); final String expectedJSON = "[\n" + - " {\n" + - " \"_key\": 100,\n" + - " \"columnString\": \"alltypes1\",\n" + - " \"columnLong\": 1337,\n" + - " \"columnFloat\": 3.1400001,\n" + - " \"columnDouble\": 0.89122999999999997,\n" + - " \"columnBoolean\": false,\n" + - " \"columnDate\": \"" + now + "\",\n" + - " \"columnBinary\": \"AQID\",\n" + - " \"columnMutableRealmInteger\": 0,\n" + - " \"columnRealmObject\": {\n" + - " \"_key\": 100,\n" + - " \"name\": \"dog1\",\n" + - " \"age\": 1,\n" + - " \"height\": 1.1,\n" + - " \"weight\": 10.100000381469727,\n" + - " \"hasTail\": true,\n" + + " {\n" + + " \"_key\":100,\n" + + " \"columnString\":\"alltypes1\",\n" + + " \"columnLong\":1337,\n" + + " \"columnFloat\":3.1400001e+00,\n" + + " \"columnDouble\":8.9122999999999997e-01,\n" + + " \"columnBoolean\":false,\n" + + " \"columnDate\": \"" + now + "\",\n" + + " \"columnBinary\":\"AQID\",\n" + + " \"columnDecimal128\":\"1.23456789E-1\",\n" + + " \"columnObjectId\":\"789abcdef0123456789abcde\",\n" + + " \"columnMutableRealmInteger\":0,\n" + + " \"columnRealmObject\":{\n" + + " \"_key\":100,\n" + + " \"name\":\"dog1\",\n" + + " \"age\":1,\n" + + " \"height\":1.1000000e+00,\n" + + " \"weight\":1.0100000381469727e+01,\n" + + " \"hasTail\":true,\n" + + " \"birthday\": \"" + now + "\",\n" + + " \"owner\":null\n" + + " },\n" + + " \"columnRealmList\":[\n" + + " {\n" + + " \"_key\":101,\n" + + " \"name\":\"dog2\",\n" + + " \"age\":2,\n" + + " \"height\":2.0999999e+00,\n" + + " \"weight\":2.0100000381469727e+01,\n" + + " \"hasTail\":false,\n" + " \"birthday\": \"" + now + "\",\n" + - " \"owner\": null\n" + - " },\n" + - " \"columnRealmList\": [\n" + - " {\n" + - " \"_key\": 101,\n" + - " \"name\": \"dog2\",\n" + - " \"age\": 2,\n" + - " \"height\": 2.0999999,\n" + - " \"weight\": 20.100000381469727,\n" + - " \"hasTail\": false,\n" + - " \"birthday\": \"" + now + "\",\n" + - " \"owner\": null\n" + - " },\n" + - " {\n" + - " \"_key\": 102,\n" + - " \"name\": \"dog3\",\n" + - " \"age\": 3,\n" + - " \"height\": 3.0999999,\n" + - " \"weight\": 30.100000381469727,\n" + - " \"hasTail\": true,\n" + - " \"birthday\": \"" + now + "\",\n" + - " \"owner\": {\n" + - " \"_key\": 0,\n" + - " \"name\": \"Dog owner 1\",\n" + - " \"dogs\": [],\n" + - " \"cat\": null\n" + - " }\n" + + " \"owner\":null\n" + + " },\n" + + " {\n" + + " \"_key\":102,\n" + + " \"name\":\"dog3\",\n" + + " \"age\":3,\n" + + " \"height\":3.0999999e+00,\n" + + " \"weight\":3.0100000381469727e+01,\n" + + " \"hasTail\":true,\n" + + " \"birthday\": \"" + now + "\",\n" + + " \"owner\":{\n" + + " \"_key\":0,\n" + + " \"name\":\"Dog owner 1\",\n" + + " \"dogs\":[\n" + + "\n" + + " ],\n" + + " \"cat\":null\n" + " }\n" + - " ],\n" + - " \"columnStringList\": [\n" + - " \"Foo\",\n" + - " \"Bar\"\n" + - " ],\n" + - " \"columnBinaryList\": [],\n" + - " \"columnBooleanList\": [\n" + - " false,\n" + - " true\n" + - " ],\n" + - " \"columnLongList\": [\n" + - " 1000,\n" + - " 2000\n" + - " ],\n" + - " \"columnDoubleList\": [\n" + - " 1.123,\n" + - " 5.3209999999999997\n" + - " ],\n" + - " \"columnFloatList\": [\n" + - " 0.12,\n" + - " 0.13\n" + - " ],\n" + - " \"columnDateList\": [\n" + + " }\n" + + " ],\n" + + " \"columnStringList\":[\n" + + " \"Foo\",\n" + + " \"Bar\"\n" + + " ],\n" + + " \"columnBinaryList\":[\n" + + "\n" + + " ],\n" + + " \"columnBooleanList\":[\n" + + " false,\n" + + " true\n" + + " ],\n" + + " \"columnLongList\":[\n" + + " 1000,\n" + + " 2000\n" + + " ],\n" + + " \"columnDoubleList\":[\n" + + " 1.1230000000000000e+00,\n" + + " 5.3209999999999997e+00\n" + + " ],\n" + + " \"columnFloatList\":[\n" + + " 1.2000000e-01,\n" + + " 1.3000000e-01\n" + + " ],\n" + + " \"columnDateList\":[\n" + " \"" + now + "\",\n" + " \"" + now + "\"\n" + - " ]\n" + - " }\n" + + " ],\n" + + " \"columnDecimal128List\":[\n" + + " \"-42\",\n" + + " \"NaN\",\n" + + " \"-0\",\n" + + " \"0\",\n" + + " \"-Inf\",\n" + + " \"Inf\"\n" + + " ],\n" + + " \"columnObjectIdList\":[\n" + + " \"123456789abcdef012345678\",\n" + + " \"23456789abcdef0123456789\"\n" + + " ]\n" + + " }\n" + "]"; - JSONAssert.assertEquals(expectedJSON, json, false); + JSONAssert.assertEquals(expectedJSON, json, true); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java index a08741fa6c..e9c4df84a6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmSchemaTests.java @@ -580,6 +580,10 @@ public void schemaInformationOfPrimitiveLists() { fieldNameToType.put(NullTypes.FIELD_BOOLEAN_LIST_NOT_NULL, RealmFieldType.BOOLEAN_LIST); fieldNameToType.put(NullTypes.FIELD_DATE_LIST_NULL, RealmFieldType.DATE_LIST); fieldNameToType.put(NullTypes.FIELD_DATE_LIST_NOT_NULL, RealmFieldType.DATE_LIST); + fieldNameToType.put(NullTypes.FIELD_DECIMAL128_LIST_NULL, RealmFieldType.DECIMAL128_LIST); + fieldNameToType.put(NullTypes.FIELD_DECIMAL128_LIST_NOT_NULL, RealmFieldType.DECIMAL128_LIST); + fieldNameToType.put(NullTypes.FIELD_OBJECT_ID_LIST_NULL, RealmFieldType.OBJECT_ID_LIST); + fieldNameToType.put(NullTypes.FIELD_OBJECT_ID_LIST_NOT_NULL, RealmFieldType.OBJECT_ID_LIST); fieldNameToType.put(NullTypes.FIELD_DOUBLE_LIST_NULL, RealmFieldType.DOUBLE_LIST); fieldNameToType.put(NullTypes.FIELD_DOUBLE_LIST_NOT_NULL, RealmFieldType.DOUBLE_LIST); fieldNameToType.put(NullTypes.FIELD_FLOAT_LIST_NULL, RealmFieldType.FLOAT_LIST); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 016dffcc00..af3e3508cf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -20,12 +20,15 @@ import android.os.Build; import android.os.Looper; import android.os.SystemClock; + +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.UiThreadTestRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; import junit.framework.AssertionFailedError; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.hamcrest.CoreMatchers; import org.json.JSONArray; import org.json.JSONException; @@ -47,6 +50,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -146,18 +150,19 @@ public class RealmTests { private Context context; private Realm realm; - private List columnData = new ArrayList(); + private List columnData = new ArrayList() {{ + add(AllTypes.FIELD_BOOLEAN); + add(AllTypes.FIELD_DATE); + add(AllTypes.FIELD_DOUBLE); + add(AllTypes.FIELD_FLOAT); + add(AllTypes.FIELD_STRING); + add(AllTypes.FIELD_LONG); + add(AllTypes.FIELD_BINARY); + add(AllTypes.FIELD_DECIMAL128); + add(AllTypes.FIELD_OBJECT_ID); + }}; private RealmConfiguration realmConfig; - private void setColumnData() { - columnData.add(0, AllTypes.FIELD_BOOLEAN); - columnData.add(1, AllTypes.FIELD_DATE); - columnData.add(2, AllTypes.FIELD_DOUBLE); - columnData.add(3, AllTypes.FIELD_FLOAT); - columnData.add(4, AllTypes.FIELD_STRING); - columnData.add(5, AllTypes.FIELD_LONG); - } - @Before public void setUp() { // Injecting the Instrumentation instance is required @@ -184,6 +189,8 @@ private void populateTestRealm(Realm realm, int objects) { allTypes.setColumnDate(new Date()); allTypes.setColumnDouble(Math.PI); allTypes.setColumnFloat(1.234567F + i); + allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(i))); + allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(i + "12345"))); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); @@ -324,7 +331,6 @@ public void where_queryResults() throws IOException { @Test public void where_equalTo_wrongFieldTypeAsInput() throws IOException { populateTestRealm(); - setColumnData(); for (int i = 0; i < columnData.size(); i++) { try { @@ -374,6 +380,30 @@ public void where_equalTo_wrongFieldTypeAsInput() throws IOException { } } catch (IllegalArgumentException ignored) { } + + try { + realm.where(AllTypes.class).equalTo(columnData.get(i), new byte[] {1, 2, 3}).findAll(); + if (i != 6) { + fail("Realm.where should fail with illegal argument"); + } + } catch (IllegalArgumentException ignored) { + } + + try { + realm.where(AllTypes.class).equalTo(columnData.get(i), new Decimal128(new BigDecimal(i + "12345"))).findAll(); + if (i != 7) { + fail("Realm.where should fail with illegal argument"); + } + } catch (IllegalArgumentException ignored) { + } + + try { + realm.where(AllTypes.class).equalTo(columnData.get(i), new ObjectId(TestHelper.generateObjectIdHexString(i))).findAll(); + if (i != 8) { + fail("Realm.where should fail with illegal argument"); + } + } catch (IllegalArgumentException ignored) { + } } } @@ -1283,6 +1313,8 @@ public void copyToRealm_fromOtherRealm() { realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnString("Test"); + allTypes.setColumnDecimal128(new Decimal128(new BigDecimal("12345"))); + allTypes.setColumnObjectId(new ObjectId(TestHelper.randomObjectIdHexString())); realm.commitTransaction(); RealmConfiguration realmConfig = configFactory.createConfiguration("other-realm"); @@ -1312,6 +1344,8 @@ public void copyToRealm() { allTypes.setColumnBoolean(true); allTypes.setColumnDate(date); allTypes.setColumnBinary(new byte[] {1, 2, 3}); + allTypes.setColumnDecimal128(new Decimal128(new BigDecimal("12345"))); + allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(7))); allTypes.setColumnRealmObject(dog); allTypes.setColumnRealmList(list); @@ -1322,6 +1356,8 @@ public void copyToRealm() { allTypes.setColumnDoubleList(new RealmList(1D)); allTypes.setColumnFloatList(new RealmList(1F)); allTypes.setColumnDateList(new RealmList(new Date(1L))); + allTypes.setColumnDecimal128List(new RealmList(new Decimal128(new BigDecimal("54321")))); + allTypes.setColumnObjectIdList(new RealmList(new ObjectId(TestHelper.generateObjectIdHexString(5)))); realm.beginTransaction(); AllTypes realmTypes = realm.copyToRealm(allTypes); @@ -1335,6 +1371,8 @@ public void copyToRealm() { assertEquals(allTypes.isColumnBoolean(), realmTypes.isColumnBoolean()); assertEquals(allTypes.getColumnDate(), realmTypes.getColumnDate()); assertArrayEquals(allTypes.getColumnBinary(), realmTypes.getColumnBinary()); + assertEquals(allTypes.getColumnDecimal128(), realmTypes.getColumnDecimal128()); + assertEquals(allTypes.getColumnObjectId(), realmTypes.getColumnObjectId()); assertEquals(allTypes.getColumnRealmObject().getName(), dog.getName()); assertEquals(list.size(), realmTypes.getColumnRealmList().size()); //noinspection ConstantConditions @@ -1353,6 +1391,13 @@ public void copyToRealm() { assertEquals((Float) 1F, realmTypes.getColumnFloatList().get(0)); assertEquals(1, realmTypes.getColumnDateList().size()); assertEquals(new Date(1), realmTypes.getColumnDateList().get(0)); + + assertEquals(1, realmTypes.getColumnDecimal128List().size()); + assertEquals(new Decimal128(new BigDecimal("54321")), realmTypes.getColumnDecimal128List().get(0)); + + assertEquals(1, realmTypes.getColumnObjectIdList().size()); + assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(5)), realmTypes.getColumnObjectIdList().get(0)); + } @Test @@ -3363,6 +3408,8 @@ public void copyFromRealm() { assertEquals(realmObject.getColumnDouble(), unmanagedObject.getColumnDouble(), 0.00000000001); assertEquals(realmObject.isColumnBoolean(), unmanagedObject.isColumnBoolean()); assertEquals(realmObject.getColumnDate(), unmanagedObject.getColumnDate()); + assertEquals(realmObject.getColumnObjectId(), unmanagedObject.getColumnObjectId()); + assertEquals(realmObject.getColumnDecimal128(), unmanagedObject.getColumnDecimal128()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java index 8a85897b55..44f943df69 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java @@ -16,6 +16,9 @@ package io.realm.entities; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.RealmList; @@ -43,6 +46,8 @@ public class AllJavaTypes extends RealmObject { public static final String FIELD_BOOLEAN = "fieldBoolean"; public static final String FIELD_DATE = "fieldDate"; public static final String FIELD_BINARY = "fieldBinary"; + public static final String FIELD_DECIMAL128 = "fieldDecimal128"; + public static final String FIELD_OBJECT_ID = "fieldObjectId"; public static final String FIELD_OBJECT = "fieldObject"; public static final String FIELD_LIST = "fieldList"; @@ -56,6 +61,8 @@ public class AllJavaTypes extends RealmObject { public static final String FIELD_DOUBLE_LIST = "fieldDoubleList"; public static final String FIELD_FLOAT_LIST = "fieldFloatList"; public static final String FIELD_DATE_LIST = "fieldDateList"; + public static final String FIELD_DECIMAL128_LIST = "fieldDecimal128List"; + public static final String FIELD_OBJECT_ID_LIST = "fieldObjectIdList"; public static final String FIELD_LO_OBJECT = "objectParents"; public static final String FIELD_LO_LIST = "listParents"; @@ -86,6 +93,8 @@ public class AllJavaTypes extends RealmObject { private boolean fieldBoolean; private Date fieldDate; private byte[] fieldBinary; + private Decimal128 fieldDecimal128; + private ObjectId fieldObjectId; private AllJavaTypes fieldObject; private RealmList fieldList; @@ -99,6 +108,8 @@ public class AllJavaTypes extends RealmObject { private RealmList fieldDoubleList; private RealmList fieldFloatList; private RealmList fieldDateList; + private RealmList fieldDecimal128List; + private RealmList fieldObjectIdList; @LinkingObjects(FIELD_OBJECT) private final RealmResults objectParents = null; @@ -306,6 +317,38 @@ public void setFieldDateList(RealmList fieldDateList) { this.fieldDateList = fieldDateList; } + public Decimal128 getFieldDecimal128() { + return fieldDecimal128; + } + + public void setFieldDecimal128(Decimal128 fieldDecimal128) { + this.fieldDecimal128 = fieldDecimal128; + } + + public ObjectId getFieldObjectId() { + return fieldObjectId; + } + + public void setFieldObjectId(ObjectId fieldObjectId) { + this.fieldObjectId = fieldObjectId; + } + + public RealmList getFieldDecimal128List() { + return fieldDecimal128List; + } + + public void setFieldDecimal128List(RealmList fieldDecimal128List) { + this.fieldDecimal128List = fieldDecimal128List; + } + + public RealmList getFieldObjectIdList() { + return fieldObjectIdList; + } + + public void setFieldObjectIdList(RealmList fieldObjectIdList) { + this.fieldObjectIdList = fieldObjectIdList; + } + public RealmResults getObjectParents() { return objectParents; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java index 96c9843ad7..1e3da43cf7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java @@ -16,6 +16,9 @@ package io.realm.entities; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.RealmList; @@ -49,6 +52,8 @@ public class MappedAllJavaTypes extends RealmObject { public boolean fieldBoolean; public Date fieldDate; public byte[] fieldBinary; + public Decimal128 fieldDecimal128; + public ObjectId fieldObjectId; public MappedAllJavaTypes fieldObject; public RealmList fieldList; @@ -62,6 +67,8 @@ public class MappedAllJavaTypes extends RealmObject { public RealmList fieldDoubleList; public RealmList fieldFloatList; public RealmList fieldDateList; + public RealmList fieldDecimalList; // FIXME using fieldDecimal128List causes issues investigate + public RealmList fieldObjectIdList; public MappedAllJavaTypes() { } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/NoPrimaryKeyNullTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/NoPrimaryKeyNullTypes.java index d8ee4e85aa..c120399fa8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/NoPrimaryKeyNullTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/NoPrimaryKeyNullTypes.java @@ -16,6 +16,9 @@ package io.realm.entities; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.RealmObject; @@ -94,6 +97,14 @@ public class NoPrimaryKeyNullTypes extends RealmObject { private Date fieldDateNotNull = new Date(0); private Date fieldDateNull; + @Required + private Decimal128 fieldDecimal128NotNull = new Decimal128(0); + private Decimal128 fieldDecimal128Null; + + @Required + private ObjectId fieldObjectIdNotNull = new ObjectId(); + private ObjectId fieldObjectIdNull; + private NoPrimaryKeyNullTypes fieldObjectNull; public String getFieldStringNotNull() { @@ -263,4 +274,36 @@ public NoPrimaryKeyNullTypes getFieldObjectNull() { public void setFieldObjectNull(NoPrimaryKeyNullTypes fieldObjectNull) { this.fieldObjectNull = fieldObjectNull; } + + public Decimal128 getFieldDecimal128NotNull() { + return fieldDecimal128NotNull; + } + + public void setFieldDecimal128NotNull(Decimal128 fieldDecimal128NotNull) { + this.fieldDecimal128NotNull = fieldDecimal128NotNull; + } + + public Decimal128 getFieldDecimal128Null() { + return fieldDecimal128Null; + } + + public void setFieldDecimal128Null(Decimal128 fieldDecimal128Null) { + this.fieldDecimal128Null = fieldDecimal128Null; + } + + public ObjectId getFieldObjectIdNotNull() { + return fieldObjectIdNotNull; + } + + public void setFieldObjectIdNotNull(ObjectId fieldObjectIdNotNull) { + this.fieldObjectIdNotNull = fieldObjectIdNotNull; + } + + public ObjectId getFieldObjectIdNull() { + return fieldObjectIdNull; + } + + public void setFieldObjectIdNull(ObjectId fieldObjectIdNull) { + this.fieldObjectIdNull = fieldObjectIdNull; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimitiveListTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimitiveListTypes.java index 9e80a1b946..44319c3f90 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimitiveListTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimitiveListTypes.java @@ -16,6 +16,10 @@ package io.realm.entities; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + +import java.text.DecimalFormat; import java.util.Date; import io.realm.RealmList; @@ -33,6 +37,8 @@ public class PrimitiveListTypes extends RealmObject { public static final String FIELD_SHORT_LIST = "shortList"; public static final String FIELD_INT_LIST = "intList"; public static final String FIELD_LONG_LIST = "longList"; + public static final String FIELD_DECIMAL128_LIST = "decimal128List"; + public static final String FIELD_OBJECT_ID_LIST = "objectIdList"; public static final String FIELD_REQUIRED_STRING_LIST = "requiredStringList"; public static final String FIELD_REQUIRED_BINARY_LIST = "requiredBinaryList"; public static final String FIELD_REQUIRED_BOOLEAN_LIST = "requiredBooleanList"; @@ -43,6 +49,8 @@ public class PrimitiveListTypes extends RealmObject { public static final String FIELD_REQUIRED_SHORT_LIST = "requiredShortList"; public static final String FIELD_REQUIRED_INT_LIST = "requiredIntList"; public static final String FIELD_REQUIRED_LONG_LIST = "requiredLongList"; + public static final String FIELD_REQUIRED_DECIMAL128_LIST = "requiredDecimal128List"; + public static final String FIELD_REQUIRED_OBJECT_ID_LIST = "requiredObjectIdList"; @SuppressWarnings("unused") private RealmList stringList; @@ -64,6 +72,10 @@ public class PrimitiveListTypes extends RealmObject { private RealmList intList; @SuppressWarnings("unused") private RealmList longList; + @SuppressWarnings("unused") + private RealmList decimal128List; + @SuppressWarnings("unused") + private RealmList objectIdList; @SuppressWarnings("unused") @Required @@ -95,6 +107,12 @@ public class PrimitiveListTypes extends RealmObject { @SuppressWarnings("unused") @Required private RealmList requiredLongList; + @SuppressWarnings("unused") + @Required + private RealmList requiredDecimal128List; + @SuppressWarnings("unused") + @Required + private RealmList requiredObjectIdList; public RealmList getList(String fieldName) { switch (fieldName) { @@ -118,6 +136,10 @@ public RealmList getList(String fieldName) { return intList; case FIELD_LONG_LIST: return longList; + case FIELD_DECIMAL128_LIST: + return decimal128List; + case FIELD_OBJECT_ID_LIST: + return objectIdList; case FIELD_REQUIRED_STRING_LIST: return requiredStringList; case FIELD_REQUIRED_BINARY_LIST: @@ -138,6 +160,10 @@ public RealmList getList(String fieldName) { return requiredIntList; case FIELD_REQUIRED_LONG_LIST: return requiredLongList; + case FIELD_REQUIRED_DECIMAL128_LIST: + return requiredDecimal128List; + case FIELD_REQUIRED_OBJECT_ID_LIST: + return requiredObjectIdList; default: throw new IllegalArgumentException("Unknown field name: '" + fieldName + "'."); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java b/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java index 4998ef8bb7..e408f50e61 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java @@ -16,6 +16,9 @@ package io.realm.entities.pojo; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.RealmList; @@ -47,6 +50,9 @@ public class AllTypesRealmModel implements RealmModel { public byte[] columnBinary; public Dog columnRealmObject; public RealmList columnRealmList; + public Decimal128 columnDecimal128; + public ObjectId columnObjectId; + @Override public int hashCode() { diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java index 3c6cf31486..594c2e2462 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java @@ -267,6 +267,8 @@ private Set getValidFieldTypes(Set filter) { case DATE_LIST: case FLOAT_LIST: case DOUBLE_LIST: + case DECIMAL128_LIST: + case OBJECT_ID_LIST: break; case LIST: case OBJECT: diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt new file mode 100644 index 0000000000..c6b3094613 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt @@ -0,0 +1,491 @@ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.annotations.PrimaryKey +import io.realm.annotations.Required +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import org.bson.types.Decimal128 +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import java.math.BigDecimal + +open class Decimal128Required : RealmObject() { + @field:PrimaryKey + var id: Long = 0 + @field:Required + var decimal: Decimal128? = null + var name: String = "" +} + +open class Decimal128NotRequired : RealmObject() { + @field:PrimaryKey + var id: Long = 0 + var decimal: Decimal128? = null + var name: String = "" +} + +open class Decimal128RequiredRealmList : RealmObject() { + var id: Long = 0 + @field:Required + var decimals: RealmList = RealmList() + var name: String = "" +} + +open class Decimal128OptionalRealmList : RealmObject() { + var id: Long = 0 + var decimals: RealmList = RealmList() + var name: String = "" +} + +@RunWith(AndroidJUnit4::class) +class Decimal128Tests { + private lateinit var realmConfiguration: RealmConfiguration + private lateinit var realm: Realm + + @Rule + @JvmField + val folder = TemporaryFolder() + + init { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + + @Before + fun setUp() { + realmConfiguration = RealmConfiguration + .Builder(InstrumentationRegistry.getInstrumentation().targetContext) + .directory(folder.newFolder()) + .schema(Decimal128Required::class.java, + Decimal128NotRequired::class.java, + Decimal128RequiredRealmList::class.java, + Decimal128OptionalRealmList::class.java) + .build() + realm = Realm.getInstance(realmConfiguration) + } + + @After + fun tearDown() { + realm.close() + } + + @Test + fun copyToAndFromRealm() { + val value = Decimal128NotRequired() + value.decimal = Decimal128(BigDecimal.TEN) + value.id = 42 + value.name = "Foo" + + // copyToRealm + realm.beginTransaction() + val obj = realm.copyToRealm(value) + realm.commitTransaction() + assertEquals(Decimal128(BigDecimal.TEN), obj.decimal) + assertEquals(42L, obj.id) + assertEquals("Foo", obj.name) + + // copyToRealmOrUpdate + value.id = 42 + value.decimal = Decimal128(BigDecimal.ONE) + value.name = "Bar" + realm.beginTransaction() + realm.copyToRealmOrUpdate(value) + realm.commitTransaction() + + // copyFromRealm + val copy = realm.copyFromRealm(obj) + assertEquals(Decimal128(BigDecimal.ONE), copy.decimal) + assertEquals(42L, copy.id) + assertEquals("Bar", copy.name) + } + + @Test + fun insert() { + val value = Decimal128Required() + value.id = 7 + value.name = "Foo" + value.decimal = Decimal128(10) + + // insert + realm.beginTransaction() + realm.insert(value) + realm.commitTransaction() + + val obj = realm.where().findFirst() + assertNotNull(obj) + assertEquals(7, obj!!.id) + assertEquals(Decimal128(10), obj.decimal) + assertEquals("Foo", obj.name) + + // insertOrUpdate + realm.beginTransaction() + obj.decimal = Decimal128(20) + obj.name = "Bar" + realm.insertOrUpdate(obj) + realm.commitTransaction() + + val all = realm.where().findAll() + assertEquals(1, all.size) + assertEquals(7, all[0]!!.id) + assertEquals(Decimal128(20), all[0]!!.decimal) + assertEquals("Bar", all[0]!!.name) + } + + @Test + fun frozen() { + realm.beginTransaction() + val obj = realm.createObject(42) + obj.name = "foo" + obj.decimal = (Decimal128(BigDecimal.TEN)) + realm.commitTransaction() + + val frozen = obj.freeze() + assertEquals(Decimal128(BigDecimal.TEN), frozen.decimal) + assertEquals("foo", frozen.name) + assertEquals(42L, frozen.id) + } + + @Test + fun requiredField() { + realm.beginTransaction() + val obj = realm.createObject(42) + obj.name = "foo" + obj.decimal = (Decimal128(BigDecimal.TEN)) + realm.commitTransaction() + + val result = realm.where().equalTo("decimal", Decimal128(BigDecimal.TEN)).findFirst() + assertNotNull(result) + assertEquals(42L, result!!.id) + assertEquals("foo", result.name) + + realm.beginTransaction() + try { + result.decimal = null + fail("It should not be possible to set null value for the required decimal field") + } catch (expected: IllegalArgumentException) { + } + realm.commitTransaction() + } + + @Test + fun nullableFiled() { + realm.beginTransaction() + val obj = realm.createObject(42) + obj.name = "foo" + obj.decimal = null + realm.commitTransaction() + + val result = realm.where().isNull("decimal").findFirst() + assertNotNull(result) + assertEquals(42L, result!!.id) + assertEquals("foo", result.name) + + realm.beginTransaction() + result.decimal = Decimal128(BigDecimal.TEN) + realm.commitTransaction() + + val result2 = realm.where().equalTo("decimal", Decimal128(BigDecimal.TEN)).findFirst() + assertEquals(42L, result2!!.id) + assertEquals("foo", result2.name) + } + + @Test + fun requiredRealmList() { + realm.beginTransaction() + val obj = realm.createObject() + try { + obj.decimals.add(null) + fail("It should not be possible to add nullable elements to a required RealmList") + } catch (expected: Exception) { + } + } + + @Test + fun optionalRealmList() { + realm.beginTransaction() + val obj = realm.createObject() + obj.decimals.add(null) + obj.decimals.add(Decimal128(BigDecimal.ZERO)) + realm.commitTransaction() + + assertEquals(2, realm.where().findFirst()?.decimals?.size) + } + + @Test + fun linkQueryNotSupported() { + try { + realm.where().greaterThan("decimals", Decimal128(BigDecimal.ZERO)).findAll() + fail("It should not be possible to perform link query on Decimal128") + } catch (expected: IllegalArgumentException) { + } + + realm.beginTransaction() + val obj = realm.createObject() + realm.cancelTransaction() + + try { + obj.decimals.where().equalTo("decimals", Decimal128(BigDecimal.ZERO)).findAll() + } catch (expected: UnsupportedOperationException) { + } + } + + @Test + fun NaN() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal(Float.NaN.toLong())) + realm.createObject(2).decimal = Decimal128(Float.NaN.toLong()) + realm.createObject(3).decimal = Decimal128(Double.NaN.toLong()) + realm.commitTransaction() + + val all = realm.where().equalTo("decimal", Decimal128(Float.NaN.toLong())).findAll() + assertEquals(3, all.size) + } + + @Test + fun minValue() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal(Float.MIN_VALUE.toLong())) + realm.createObject(2).decimal = Decimal128(Float.MIN_VALUE.toLong()) + realm.createObject(3).decimal = Decimal128(Double.MIN_VALUE.toLong()) + realm.createObject(4).decimal = Decimal128.NEGATIVE_INFINITY + realm.createObject(5).decimal = Decimal128.NEGATIVE_NaN + realm.createObject(6).decimal = Decimal128.NEGATIVE_ZERO + realm.commitTransaction() + + var all = realm.where().equalTo("decimal", Decimal128(Float.MIN_VALUE.toLong())).findAll() + assertEquals(4, all.size) + assertEquals(Decimal128(BigDecimal(Float.MIN_VALUE.toLong())), all[0]!!.decimal) + assertEquals(Decimal128(Float.MIN_VALUE.toLong()), all[1]!!.decimal) + assertEquals(Decimal128(Double.MIN_VALUE.toLong()), all[2]!!.decimal) + assertEquals(Decimal128.NEGATIVE_ZERO, all[3]!!.decimal) + + all = realm.where().notEqualTo("decimal", Decimal128(Float.MIN_VALUE.toLong())).findAll() + assertEquals(2, all.size) + assertEquals(Decimal128.NEGATIVE_INFINITY, all[0]!!.decimal) + assertEquals(Decimal128.NEGATIVE_NaN, all[1]!!.decimal) + } + + @Test + fun minQuery() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(2).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(3).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() + + val min: Number? = realm.where().min("decimal") + assertNotNull(min) + assertTrue(min is Decimal128) + assertEquals(Decimal128(BigDecimal.ZERO), min) + } + + + @Test + fun maxValue() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal(Float.MAX_VALUE.toLong())) + realm.createObject(2).decimal = Decimal128(Float.MAX_VALUE.toLong()) + realm.createObject(3).decimal = Decimal128(Double.MAX_VALUE.toLong()) + realm.createObject(4).decimal = Decimal128.POSITIVE_INFINITY + realm.createObject(5).decimal = Decimal128.NaN + realm.createObject(6).decimal = Decimal128.POSITIVE_ZERO + realm.commitTransaction() + + var all = realm.where().equalTo("decimal", Decimal128(Float.MAX_VALUE.toLong())).findAll() + assertEquals(3, all.size) + assertEquals(Decimal128(BigDecimal(Float.MAX_VALUE.toLong())), all[0]!!.decimal) + assertEquals(Decimal128(Float.MAX_VALUE.toLong()), all[1]!!.decimal) + assertEquals(Decimal128(Double.MAX_VALUE.toLong()), all[2]!!.decimal) + + all = realm.where().notEqualTo("decimal", Decimal128(Float.MAX_VALUE.toLong())).findAll() + assertEquals(3, all.size) + assertEquals(Decimal128.POSITIVE_INFINITY, all[0]!!.decimal) + assertEquals(Decimal128.NaN, all[1]!!.decimal) + assertEquals(Decimal128.POSITIVE_ZERO, all[2]!!.decimal) + } + + @Test + fun maxQuery() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(2).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(3).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() + + val max: Number? = realm.where().max("decimal") + assertNotNull(max) + assertTrue(max is Decimal128) + assertEquals(Decimal128(BigDecimal.TEN), max) + } + + @Test + fun betweenQuery() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(2).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(3).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() + + val between = realm.where().between("decimal", Decimal128(-1L), Decimal128(11L)).findAll() + assertEquals(3, between.size) + assertEquals(Decimal128(BigDecimal.TEN), between[0]!!.decimal) + assertEquals(Decimal128(BigDecimal.ONE), between[1]!!.decimal) + assertEquals(Decimal128(BigDecimal.ZERO), between[2]!!.decimal) + } + + @Test + fun averageQuery() { + var average = realm.where().averageDecimal128("decimal") + assertEquals(Decimal128(0), average) + + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(3) + realm.createObject(2).decimal = Decimal128(7) + realm.createObject(3).decimal = Decimal128(5) + realm.commitTransaction() + + average = realm.where().averageDecimal128("decimal") + assertEquals(Decimal128(5), average) + } + + @Test + fun sort() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = Decimal128(BigDecimal.ZERO) + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.commitTransaction() + + var all = realm.where().sort("decimal", Sort.ASCENDING).findAll() + assertEquals(3, all.size) + assertEquals(Decimal128(BigDecimal.ZERO), all[0]!!.decimal) + assertEquals(Decimal128(BigDecimal.ONE), all[1]!!.decimal) + assertEquals(Decimal128(BigDecimal.TEN), all[2]!!.decimal) + + all = realm.where().sort("decimal", Sort.DESCENDING).findAll() + assertEquals(3, all.size) + assertEquals(Decimal128(BigDecimal.TEN), all[0]!!.decimal) + assertEquals(Decimal128(BigDecimal.ONE), all[1]!!.decimal) + assertEquals(Decimal128(BigDecimal.ZERO), all[2]!!.decimal) + } + + @Test + fun distinct() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(3).decimal = null + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.createObject(5).decimal = Decimal128(BigDecimal.ZERO) + realm.createObject(6).decimal = null + realm.createObject(7).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(8).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(9).decimal = null + realm.commitTransaction() + + val all = realm.where().distinct("decimal").sort("decimal", Sort.ASCENDING).findAll() + assertEquals(4, all.size) + assertNull(all[0]!!.decimal) + assertEquals(Decimal128(BigDecimal.ZERO), all[1]!!.decimal) + assertEquals(Decimal128(BigDecimal.ONE), all[2]!!.decimal) + assertEquals(Decimal128(BigDecimal.TEN), all[3]!!.decimal) + + } + + @Test + fun queries() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() + + // count + assertEquals(4, realm.where().count()) + + // notEqualTo + var all = realm.where() + .notEqualTo("decimal", Decimal128(BigDecimal.ONE)) + .sort("decimal", Sort.ASCENDING) + .findAll() + assertEquals(3, all.size) + assertNull(all[0]!!.decimal) + assertEquals(Decimal128(BigDecimal.ZERO), all[1]!!.decimal) + assertEquals(Decimal128(BigDecimal.TEN), all[2]!!.decimal) + + // greaterThanOrEqualTo + all = realm.where() + .greaterThanOrEqualTo("decimal", Decimal128(BigDecimal.ONE)) + .sort("decimal", Sort.ASCENDING) + .findAll() + assertEquals(2, all.size) + assertEquals(Decimal128(BigDecimal.ONE), all[0]!!.decimal) + assertEquals(Decimal128(BigDecimal.TEN), all[1]!!.decimal) + + // greaterThan + all = realm.where() + .greaterThan("decimal", Decimal128(BigDecimal.ONE)) + .sort("decimal", Sort.ASCENDING) + .findAll() + assertEquals(1, all.size) + assertEquals(Decimal128(BigDecimal.TEN), all[0]!!.decimal) + + + // lessThanOrEqualTo + all = realm.where() + .lessThanOrEqualTo("decimal", Decimal128(BigDecimal.ONE)) + .sort("decimal", Sort.ASCENDING) + .findAll() + assertEquals(2, all.size) + assertEquals(Decimal128(BigDecimal.ZERO), all[0]!!.decimal) + assertEquals(Decimal128(BigDecimal.ONE), all[1]!!.decimal) + + // lessThan + all = realm.where() + .lessThan("decimal", Decimal128(BigDecimal.ONE)) + .sort("decimal", Sort.ASCENDING) + .findAll() + assertEquals(1, all.size) + assertEquals(Decimal128(BigDecimal.ZERO), all[0]!!.decimal) + + // isNull + all = realm.where() + .isNull("decimal") + .findAll() + assertEquals(1, all.size) + assertNull(all[0]!!.decimal) + assertEquals(2L, all[0]!!.id) + + // isNotNull + all = realm.where() + .isNotNull("decimal") + .sort("decimal", Sort.ASCENDING) + .findAll() + assertEquals(3, all.size) + assertEquals(Decimal128(BigDecimal.ZERO), all[0]!!.decimal) + assertEquals(Decimal128(BigDecimal.ONE), all[1]!!.decimal) + assertEquals(Decimal128(BigDecimal.TEN), all[2]!!.decimal) + + // average + try { + realm.where().average("decimal") // FIXME should we support avergae queries in Core? + fail("Average is not supported for Decimal128") + } catch (expected: IllegalArgumentException) { + } + + // isEmpty + try { + realm.where().isEmpty("decimal") + fail("isEmpty is not supported for Decimal128") + } catch (expected: IllegalArgumentException) { + } + } + +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt new file mode 100644 index 0000000000..f1e755a5e1 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt @@ -0,0 +1,394 @@ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.TestHelper.generateObjectIdHexString +import io.realm.annotations.PrimaryKey +import io.realm.annotations.Required +import io.realm.exceptions.RealmException +import io.realm.exceptions.RealmPrimaryKeyConstraintException +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import org.bson.types.ObjectId +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith + +open class ObjectIdPrimaryKeyRequired + : RealmObject() { + @field:PrimaryKey + @field:Required + var id : ObjectId? = null + var name : String = "" + var anotherId: ObjectId? = null + +} + +open class ObjectIdPrimaryKeyNotRequired + : RealmObject() { + @field:PrimaryKey + var id : ObjectId? = null + var name : String = "" + +} + +open class ObjectIdAndString + : RealmObject() { + var id : ObjectId? = null + var name : String = "" +} + +open class ObjectIdRequiredRealmList + : RealmObject() { + var id: Long = 0 + + @field:Required + var ids : RealmList = RealmList() + var name : String = "" +} + +open class ObjectIdOptionalRealmList + : RealmObject() { + var id: Long = 0 + + var ids : RealmList = RealmList() + var name : String = "" +} + +@RunWith(AndroidJUnit4::class) +class ObjectIdTests { + private lateinit var realmConfiguration: RealmConfiguration + private lateinit var realm: Realm + + @Rule + @JvmField val folder = TemporaryFolder() + + init { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + + @Before + fun setUp() { + realmConfiguration = RealmConfiguration + .Builder(InstrumentationRegistry.getInstrumentation().targetContext) + .directory(folder.newFolder()) + .schema(ObjectIdPrimaryKeyRequired::class.java, + ObjectIdPrimaryKeyNotRequired::class.java, + ObjectIdAndString::class.java, + ObjectIdRequiredRealmList::class.java, + ObjectIdOptionalRealmList::class.java) + .build() + realm = Realm.getInstance(realmConfiguration) + } + + @After + fun tearDown() { + realm.close() + } + + @Test + fun copyToAndFromRealm() { + val objectIdHex1 = generateObjectIdHexString(1) + val objectIdHex2 = generateObjectIdHexString(2) + val objectIdHex3 = generateObjectIdHexString(3) + + val value = ObjectIdPrimaryKeyRequired() + value.id = ObjectId(objectIdHex1) + value.anotherId = ObjectId(objectIdHex2) + value.name = "Foo" + + // copyToRealm + realm.beginTransaction() + val obj = realm.copyToRealm(value) + realm.commitTransaction() + assertEquals(ObjectId(objectIdHex1), obj.id) + assertEquals(ObjectId(objectIdHex2), obj.anotherId) + assertEquals("Foo", obj.name) + + // copyToRealmOrUpdate + value.name = "Bar" + value.anotherId = ObjectId(objectIdHex3) + realm.beginTransaction() + realm.copyToRealmOrUpdate(value) + realm.commitTransaction() + + // copyFromRealm + val copy = realm.copyFromRealm(obj) + assertEquals(ObjectId(objectIdHex1), copy.id) + assertEquals(ObjectId(objectIdHex3), copy.anotherId) + assertEquals("Bar", copy.name) + } + + @Test + fun insert() { + val value = ObjectIdPrimaryKeyRequired() + val objectIdHex1 = generateObjectIdHexString(0) + val objectIdHex2 = generateObjectIdHexString(7) + value.id = ObjectId(objectIdHex1) + value.name = "Foo" + value.anotherId = ObjectId(generateObjectIdHexString(7)) + + // insert + realm.beginTransaction() + realm.insert(value) + realm.commitTransaction() + + val obj = realm.where().findFirst() + assertNotNull(obj) + assertEquals(ObjectId(objectIdHex1), obj!!.id) + assertEquals(ObjectId(objectIdHex2), obj.anotherId) + assertEquals("Foo", obj.name) + + // insertOrUpdate + realm.beginTransaction() + val objectIdHex3 = generateObjectIdHexString(1) + obj.anotherId = ObjectId(objectIdHex3) + obj.name = "Bar" + realm.insertOrUpdate(obj) + realm.commitTransaction() + + val all = realm.where().findAll() + assertEquals(1, all.size) + assertEquals(ObjectId(objectIdHex1), all[0]!!.id) + assertEquals(ObjectId(objectIdHex3), all[0]!!.anotherId) + assertEquals("Bar", all[0]!!.name) + } + + @Test + fun frozen() { + realm.beginTransaction() + val hex = generateObjectIdHexString(7) + val obj = realm.createObject(ObjectId(hex)) + obj.name = "foo" + realm.commitTransaction() + + val frozen = obj.freeze() + assertEquals(ObjectId(hex), frozen.id) + assertEquals("foo", frozen.name) + } + + + @Test + fun requiredPK() { + realm.beginTransaction() + try { + realm.createObject() + fail() + } catch (ignore: RealmException) { + } + + val obj = realm.createObject(ObjectId(generateObjectIdHexString(42))) + obj.name = "foo" + + realm.commitTransaction() + + val result = realm.where().equalTo("id", ObjectId(generateObjectIdHexString(42))).findFirst() + assertNotNull(result) + assertEquals("foo", result?.name) + } + + @Test + fun nullablePK() { + try { + realm.createObject() + fail() + } catch (ignore: RealmException) { + } + + realm.beginTransaction() + val obj = realm.createObject(null) + obj.name = "foo" + realm.commitTransaction() + + val result = realm.where().equalTo("id", null as ObjectId?).findFirst() + assertNotNull(result) + assertEquals("foo", result!!.name) + } + + + @Test + fun requiredRealmList() { + realm.beginTransaction() + val obj = realm.createObject() + try { + obj.ids.add(null) + fail("It should not be possible to add nullable elements to a required RealmList") + } catch (expected: Exception) { + } + } + + @Test + fun optionalRealmList() { + realm.beginTransaction() + val obj = realm.createObject() + obj.ids.add(null) + obj.ids.add(ObjectId(generateObjectIdHexString(0))) + realm.commitTransaction() + + assertEquals(2, realm.where().findFirst()?.ids?.size) + } + + @Test + fun linkQueryNotSupported() { + try { + realm.where().greaterThan("ids", ObjectId(generateObjectIdHexString(0))).findAll() + fail("It should not be possible to perform link query on ObjectId") + } catch (expected: IllegalArgumentException) {} + + realm.beginTransaction() + val obj = realm.createObject() + realm.cancelTransaction() + + try { + obj.ids.where().equalTo("ids", ObjectId(generateObjectIdHexString(0))).findAll() + } catch (expected: UnsupportedOperationException) {} + } + + @Test + fun duplicatePK() { + realm.beginTransaction() + realm.createObject(ObjectId(generateObjectIdHexString(0))) + try { + realm.createObject(ObjectId(generateObjectIdHexString(0))) + fail("It should throw for duplicate PK usage") + } catch (expected: RealmPrimaryKeyConstraintException) {} + + realm.cancelTransaction() + } + + @Test + fun sort() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.commitTransaction() + + var all = realm.where().sort("id", Sort.ASCENDING).findAll() + assertEquals(3, all.size) + assertEquals(ObjectId(generateObjectIdHexString(0)), all[0]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(1)), all[1]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(10)), all[2]!!.id) + + all = realm.where().sort("id", Sort.DESCENDING).findAll() + assertEquals(3, all.size) + assertEquals(ObjectId(generateObjectIdHexString(10)), all[0]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(1)), all[1]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(0)), all[2]!!.id) + } + + @Test + fun distinct() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = null + realm.commitTransaction() + + val all = realm.where().distinct("id").sort("id", Sort.ASCENDING).findAll() + assertEquals(4, all.size) + assertNull(all[0]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(0)), all[1]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(1)), all[2]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(10)), all[3]!!.id) + + } + + @Test + fun queries() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() + + // count + assertEquals(4, realm.where().count()) + + // notEqualTo + var all = realm.where() + .notEqualTo("id", ObjectId(generateObjectIdHexString(1))) + .sort("id", Sort.ASCENDING) + .findAll() + assertEquals(3, all.size) + assertNull(all[0]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(0)), all[1]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(10)), all[2]!!.id) + + // greaterThanOrEqualTo + all = realm.where() + .greaterThanOrEqualTo("id", ObjectId(generateObjectIdHexString(1))) + .sort("id", Sort.ASCENDING) + .findAll() + assertEquals(2, all.size) + assertEquals(ObjectId(generateObjectIdHexString(1)), all[0]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(10)), all[1]!!.id) + + // greaterThan + all = realm.where() + .greaterThan("id", ObjectId(generateObjectIdHexString(1))) + .sort("id", Sort.ASCENDING) + .findAll() + assertEquals(1, all.size) + assertEquals(ObjectId(generateObjectIdHexString(10)), all[0]!!.id) + + + // lessThanOrEqualTo + all = realm.where() + .lessThanOrEqualTo("id", ObjectId(generateObjectIdHexString(1))) + .sort("id", Sort.ASCENDING) + .findAll() + assertEquals(2, all.size) + assertEquals(ObjectId(generateObjectIdHexString(0)), all[0]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(1)), all[1]!!.id) + + // lessThan + all = realm.where() + .lessThan("id", ObjectId(generateObjectIdHexString(1))) + .sort("id", Sort.ASCENDING) + .findAll() + assertEquals(1, all.size) + assertEquals(ObjectId(generateObjectIdHexString(0)), all[0]!!.id) + + // isNull + all = realm.where() + .isNull("id") + .findAll() + assertEquals(1, all.size) + assertNull(all[0]!!.id) + + // isNotNull + all = realm.where() + .isNotNull("id") + .sort("id", Sort.ASCENDING) + .findAll() + assertEquals(3, all.size) + assertEquals(ObjectId(generateObjectIdHexString(0)), all[0]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(1)), all[1]!!.id) + assertEquals(ObjectId(generateObjectIdHexString(10)), all[2]!!.id) + + // average + try { + realm.where().average("id") // FIXME should we support avergae queries in Core? + fail("Average is not supported for ObjectId") + } catch (expected: IllegalArgumentException) {} + + // isEmpty + try { + realm.where().isEmpty("id") + fail("isEmpty is not supported for ObjectId") + } catch (expected: IllegalArgumentException) {} + } + +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 018defd85e..cf46b45a7a 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -17,6 +17,18 @@ ########################################################################### cmake_minimum_required(VERSION 3.6.0) +# loading dependencies properties +file(STRINGS "${CMAKE_SOURCE_DIR}/../../../../../dependencies.list" DEPENDENCIES) +foreach(LINE IN LISTS DEPENDENCIES) + string(REGEX MATCHALL "([^=]+)" KEY_VALUE "${LINE}") + list(LENGTH KEY_VALUE matches_count) + if(matches_count STREQUAL 2) + list(GET KEY_VALUE 0 KEY) + list(GET KEY_VALUE 1 VALUE) + set(DEP_${KEY} ${VALUE}) + endif() +endforeach() + FUNCTION(capitalizeFirstLetter var value) string(SUBSTRING ${value} 0 1 firstLetter) string(TOUPPER ${firstLetter} firstLetter) @@ -69,8 +81,8 @@ capitalizeFirstLetter(buildTypeCap "${CMAKE_BUILD_TYPE}") # Generate JNI header files. Each build has its own JNI header in its build_dir/jni_include. # WARNING: The classes_PATH is not part the public API offered by the Android Gradle Plugin # so it might change without warning when upgrading the plugin. -file(DOWNLOAD "https://repo1.maven.org/maven2/org/mongodb/bson/3.12.1/bson-3.12.1.jar" "${PROJECT_BINARY_DIR}/bson-3.12.1.jar") -set(bsonlib_PATH ${PROJECT_BINARY_DIR}/bson-3.12.1.jar) +file(DOWNLOAD "https://repo1.maven.org/maven2/org/mongodb/bson/${DEP_BSON_DEPENDENCY_VERSION}/bson-${DEP_BSON_DEPENDENCY_VERSION}.jar" "${PROJECT_BINARY_DIR}/bson-${DEP_BSON_DEPENDENCY_VERSION}.jar") +set(bsonlib_PATH ${PROJECT_BINARY_DIR}/bson-${DEP_BSON_DEPENDENCY_VERSION}.jar) set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/classes/) set(classes_LIST io.realm.RealmQuery diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp index 34bf82c550..577277fa4e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_CheckedRow.cpp @@ -235,3 +235,44 @@ JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeNullifyLink(JNIEn Java_io_realm_internal_UncheckedRow_nativeNullifyLink(env, obj, nativeRowPtr, columnKey); } + + +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_CheckedRow_nativeGetDecimal128(JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnKey) +{ + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Decimal)) { + return nullptr; + } + + return Java_io_realm_internal_UncheckedRow_nativeGetDecimal128(env, obj, nativeRowPtr, columnKey); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetDecimal128(JNIEnv* env, jobject obj, jlong nativeRowPtr, jlong columnKey, jlong low, jlong high) +{ + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_Decimal)) { + return; + } + + Java_io_realm_internal_UncheckedRow_nativeSetDecimal128(env, obj, nativeRowPtr, columnKey, low, high); +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_CheckedRow_nativeGetObjectId(JNIEnv* env, jobject obj, + jlong nativeRowPtr, + jlong columnKey) +{ + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_ObjectId)) { + return nullptr; + } + + return Java_io_realm_internal_UncheckedRow_nativeGetObjectId(env, obj, nativeRowPtr, columnKey); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_CheckedRow_nativeSetObjectId(JNIEnv* env, jobject obj, + jlong nativeRowPtr, jlong columnKey, + jstring j_value) +{ + if (!TYPE_VALID(env, OBJ(nativeRowPtr)->get_table(), columnKey, type_ObjectId)) { + return; + } + + Java_io_realm_internal_UncheckedRow_nativeSetObjectId(env, obj, nativeRowPtr, columnKey, j_value); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp index b750764291..c5f6544108 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp @@ -477,6 +477,67 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetString(JNIEnv* env CATCH_STD() } +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddDecimal128(JNIEnv* env, jclass, jlong list_ptr, + jlong j_low_value, jlong j_high_value) +{ + try { + Decimal128::Bid128 raw {static_cast(j_low_value), static_cast(j_high_value)}; + add_value(env, list_ptr, Any(Decimal128(raw))); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertDecimal128(JNIEnv* env, jclass, jlong list_ptr, + jlong pos, jlong j_low_value, jlong j_high_value) +{ + try { + Decimal128::Bid128 raw {static_cast(j_low_value), static_cast(j_high_value)}; + insert_value(env, list_ptr, pos, Any(Decimal128(raw))); + } + CATCH_STD(); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetDecimal128(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jlong j_high_value, jlong j_low_value) +{ + try { + Decimal128::Bid128 raw {static_cast(j_low_value), static_cast(j_high_value)}; + set_value(env, list_ptr, pos, Any(Decimal128(raw))); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddObjectId(JNIEnv* env, jclass, jlong list_ptr, + jstring j_value) +{ + + try { + JStringAccessor value(env, j_value); + add_value(env, list_ptr, Any(ObjectId(StringData(value).data()))); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertObjectId(JNIEnv* env, jclass, jlong list_ptr, + jlong pos, jstring j_value) +{ + try { + JStringAccessor value(env, j_value); + insert_value(env, list_ptr, pos, Any(ObjectId(StringData(value).data()))); + } + CATCH_STD(); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetObjectId(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jstring j_value) +{ + try { + JStringAccessor value(env, j_value); + set_value(env, list_ptr, pos, Any(ObjectId(StringData(value).data()))); + } + CATCH_STD() +} + JNIEXPORT jobject JNICALL Java_io_realm_internal_OsList_nativeGetValue(JNIEnv* env, jclass, jlong list_ptr, jlong pos) { try { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 47988fc0ee..84080d0697 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -214,6 +214,36 @@ static inline Obj do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm return table->create_object_with_primary_key(StringData(str_accessor)); } +static inline Obj do_create_row_with_object_id_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ref_ptr, + jlong pk_column_key, jstring pk_value) +{ + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + TableRef table = TBL_REF(table_ref_ptr); + ColKey col_key(pk_column_key); + shared_realm->verify_in_write(); // throws + JStringAccessor str_accessor(env, pk_value); // throws + if (!pk_value && !COL_NULLABLE(env, table, pk_column_key)) { + return Obj(); + } + + if (pk_value) { + auto objectId = ObjectId(StringData(str_accessor).data()); + if (bool(table->find_first_object_id(col_key, objectId))) { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, + format(PK_EXCEPTION_MSG_FORMAT, str_accessor.operator std::string())); + } + + return table->create_object_with_primary_key(objectId); + } + else { + if (bool(table->find_first_null(col_key))) { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, "'null'")); + } + return table->create_object_with_primary_key(realm::util::Optional()); + } +} + + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeGetFinalizerPtr(JNIEnv*, jclass) { return reinterpret_cast(&finalize_object); @@ -320,7 +350,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWit } } CATCH_STD() - return 0; } @@ -334,3 +363,29 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithStrin CATCH_STD() return realm::npos; } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithObjectIdPrimaryKey( + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) +{ + try { + Obj obj = do_create_row_with_object_id_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value); + return (jlong)(obj.get_key().value); + } + CATCH_STD() + return realm::npos; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWithObjectIdPrimaryKey( + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) +{ + try { + Obj obj = do_create_row_with_object_id_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value); + if (bool(obj)) { + return reinterpret_cast(new Obj(obj)); + } else { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, "Invalid Object returned from 'do_create_row_with_object_id_primary_key'"); + } + } + CATCH_STD() + return 0; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index b51c828796..cb62c52fc1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -378,6 +378,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetTimestamp(JNIEn update_objects(env, native_ptr, j_field_name, value); } +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetDecimal128(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong low, jlong high) +{ + + Decimal128::Bid128 raw = {static_cast(low), static_cast(high)}; + Decimal128 decimal128 = Decimal128(raw); + JavaValue value(decimal128); + update_objects(env, native_ptr, j_field_name, value); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetObjectId(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jstring j_value) +{ + JStringAccessor data(env, j_value); + ObjectId objectId = ObjectId(StringData(data).data()); + JavaValue value(objectId); + update_objects(env, native_ptr, j_field_name, value); +} + JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetObject(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong row_ptr) { JavaValue value(reinterpret_cast(row_ptr)); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp index efd57bce9b..ada73fef66 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp @@ -55,7 +55,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeCreatePersistedPro throw std::invalid_argument( "This field cannot be indexed - Only String/byte/short/int/long/boolean/Date fields are supported."); } - if (to_bool(is_primary) && p_type != PropertyType::Int && p_type != PropertyType::String) { + if (to_bool(is_primary) && p_type != PropertyType::Int && p_type != PropertyType::String && p_type != PropertyType::ObjectId) { std::string typ = property->type_string(); throw std::invalid_argument("Invalid primary key type: " + typ); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 9f5ff027f6..60f3231742 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -252,7 +252,7 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_Table_nativeGetColumnType(JNIEnv*, ColKey column_key (columnKey); TableRef table = TBL_REF(nativeTableRefPtr); jint column_type = table->get_column_type(column_key); - if (table->is_list(column_key) && column_type < type_LinkList) { + if (column_type != type_LinkList && table->is_list(column_key)) { // add the offset so it can be mapped correctly in Java (RealmFieldType#fromNativeValue) column_type += 128; } @@ -349,6 +349,34 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetString(JNIEnv* e return nullptr; } +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_Table_nativeGetDecimal128(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) +{ + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Decimal)) { + return nullptr; + } + try { + Decimal128 decimal128 = table->get_object(ObjKey(rowKey)).get(ColKey(columnKey)); + RETURN_DECIMAL128_AS_JLONG_ARRAY__OR_NULL(decimal128) + } + CATCH_STD() + return nullptr; +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_Table_nativeGetObjectId(JNIEnv* env, jobject, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey) +{ + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_ObjectId)) { + return nullptr; + } + try { + return to_jstring(env, table->get_object(ObjKey(rowKey)).get(ColKey(columnKey)).to_string().data()); + } + CATCH_STD() + return nullptr; +} JNIEXPORT jbyteArray JNICALL Java_io_realm_internal_Table_nativeGetByteArray(JNIEnv* env, jobject, jlong nativeTableRefPtr, jlong columnKey, @@ -540,6 +568,37 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetByteArray(JNIEnv* e CATCH_STD() } +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetDecimal128(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jlong low, + jlong high, jboolean isDefault) +{ + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Decimal)) { + return; + } + try { + Decimal128::Bid128 raw {static_cast(low), static_cast(high)}; + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), Decimal128(raw), B(isDefault)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetObjectId(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jstring j_value, + jboolean isDefault) +{ + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_ObjectId)) { + return; + } + try { + JStringAccessor value(env, j_value); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), ObjectId(StringData(value).data()), B(isDefault)); + } + CATCH_STD() +} + + JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull(JNIEnv* env, jclass, jlong nativeTableRefPtr, jlong columnKey, jlong rowKey, jboolean isDefault) @@ -795,6 +854,24 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstString(JNIEn return -1; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstObjectId(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jstring j_value) +{ + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_ObjectId)) { + return -1; + } + + try { + JStringAccessor value(env, j_value); // throws + ObjectId id = ObjectId(StringData(value).data()); + return to_jlong_or_not_found(table->find_first_object_id(ColKey(columnKey), id)); + } + CATCH_STD() + return -1; +} + + JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull(JNIEnv* env, jclass, jlong nativeTableRefPtr, jlong columnKey) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index cd8d3a23cb..f315099bb2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -814,8 +814,37 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenTimestamp( return; } Q(nativeQueryPtr) - ->greater_equal(ColKey(col_key_arr[0]), from_milliseconds(value1)) - .less_equal(ColKey(col_key_arr[0]), from_milliseconds(value2)); + ->greater_equal(ColKey(col_key_arr[0]), from_milliseconds(value1)) + .less_equal(ColKey(col_key_arr[0]), from_milliseconds(value2)); + } + else { + ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); + } + } + CATCH_STD() +} + + +// Decimal128 +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeBetweenDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlong value1Low, jlong value1High, + jlong value2Low, jlong value2High) +{ + Decimal128::Bid128 raw1 = {static_cast(value1Low), static_cast(value1High)}; + Decimal128::Bid128 raw2 = {static_cast(value2Low), static_cast(value2High)}; + Decimal128 value1 = Decimal128(raw1); + Decimal128 value2 = Decimal128(raw2); + + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); + try { + if (arr_len == 1) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Decimal)) { + return; + } + Q(nativeQueryPtr)->between(ColKey(col_key_arr[0]), value1, value2); } else { ThrowException(env, IllegalArgument, "between() does not support queries using child object fields."); @@ -849,6 +878,270 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JZ(J CATCH_STD() } +// Decimal128 +enum Decimal128Predicate { Decimal128Equal, Decimal128NotEqual, Decimal128Less, Decimal128LessEqual, Decimal128Greater, Decimal128GreaterEqual }; +static void TableQuery_Decimal128Predicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnKeys, + jlongArray tablePointers, jlong low, jlong high, Decimal128Predicate predicate) +{ + try { + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); + + Decimal128::Bid128 raw = {static_cast(low), static_cast(high)}; + Decimal128 decimal128 = Decimal128(raw); + if (arr_len == 1) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_Decimal)) { + return; + } + switch (predicate) { + case Decimal128Equal: + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), decimal128); + break; + case Decimal128NotEqual: + Q(nativeQueryPtr)->not_equal(ColKey(col_key_arr[0]), decimal128); + break; + case Decimal128Less: + Q(nativeQueryPtr)->less(ColKey(col_key_arr[0]), decimal128); + break; + case Decimal128LessEqual: + Q(nativeQueryPtr)->less_equal(ColKey(col_key_arr[0]), decimal128); + break; + case Decimal128Greater: + Q(nativeQueryPtr)->greater(ColKey(col_key_arr[0]), decimal128); + break; + case Decimal128GreaterEqual: + Q(nativeQueryPtr)->greater_equal(ColKey(col_key_arr[0]), decimal128); + break; + + } + } + else { + switch (predicate) { + case Decimal128Equal: + Q(nativeQueryPtr) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) == + decimal128); + break; + case Decimal128NotEqual: + Q(nativeQueryPtr) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) != + decimal128); + break; + case Decimal128Less: + Q(nativeQueryPtr) + ->and_query(numeric_link_less(linkChain, col_key_arr[arr_len - 1], decimal128)); + break; + case Decimal128LessEqual: + Q(nativeQueryPtr) + ->and_query(numeric_link_lessequal(linkChain, col_key_arr[arr_len - 1], decimal128)); + break; + case Decimal128Greater: + Q(nativeQueryPtr) + ->and_query(numeric_link_greater(linkChain, col_key_arr[arr_len - 1], decimal128)); + break; + case Decimal128GreaterEqual: + Q(nativeQueryPtr) + ->and_query(numeric_link_greaterequal(linkChain, col_key_arr[arr_len - 1], decimal128)); + break; + + } + } + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jlong low, + jlong high) +{ + TableQuery_Decimal128Predicate(env, nativeQueryPtr, columnKeys, tablePointers, low, high, Decimal128GreaterEqual); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jlong low, + jlong high) +{ + TableQuery_Decimal128Predicate(env, nativeQueryPtr, columnKeys, tablePointers, low, high, Decimal128Greater); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jlong low, + jlong high) +{ + TableQuery_Decimal128Predicate(env, nativeQueryPtr, columnKeys, tablePointers, low, high, Decimal128LessEqual); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jlong low, + jlong high) +{ + TableQuery_Decimal128Predicate(env, nativeQueryPtr, columnKeys, tablePointers, low, high, Decimal128Less); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jlong low, + jlong high) +{ + TableQuery_Decimal128Predicate(env, nativeQueryPtr, columnKeys, tablePointers, low, high, Decimal128NotEqual); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jlong low, + jlong high) +{ + TableQuery_Decimal128Predicate(env, nativeQueryPtr, columnKeys, tablePointers, low, high, Decimal128Equal); +} + + +// ObjectID +enum ObjectIdPredicate { ObjectIdEqual, ObjectIdNotEqual, ObjectIdLess, ObjectIdLessEqual, ObjectIdGreater, ObjectIdGreaterEqual }; +static void TableQuery_ObjectIdPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnKeys, + jlongArray tablePointers, jstring j_data, ObjectIdPredicate predicate) +{ + try { + JStringAccessor data(env, j_data); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); + + ObjectId objectId = ObjectId(StringData(data).data()); + if (arr_len == 1) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_ObjectId)) { + return; + } + + switch (predicate) { + case ObjectIdEqual: + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), objectId); + break; + case ObjectIdNotEqual: + Q(nativeQueryPtr)->not_equal(ColKey(col_key_arr[0]), objectId); + break; + case ObjectIdLess: + Q(nativeQueryPtr)->less(ColKey(col_key_arr[0]), objectId); + break; + case ObjectIdLessEqual: + Q(nativeQueryPtr)->less_equal(ColKey(col_key_arr[0]), objectId); + break; + case ObjectIdGreater: + Q(nativeQueryPtr)->greater(ColKey(col_key_arr[0]), objectId); + break; + case ObjectIdGreaterEqual: + Q(nativeQueryPtr)->greater_equal(ColKey(col_key_arr[0]), objectId); + break; + } + } + else { + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); + switch (predicate) { + case ObjectIdEqual: + Q(nativeQueryPtr) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) == + objectId); + break; + case ObjectIdNotEqual: + Q(nativeQueryPtr) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) != + objectId); + break; + case ObjectIdLess: + Q(nativeQueryPtr) + ->and_query(numeric_link_less(linkChain, col_key_arr[arr_len - 1], objectId)); + break; + case ObjectIdLessEqual: + Q(nativeQueryPtr) + ->and_query(numeric_link_lessequal(linkChain, col_key_arr[arr_len - 1], objectId)); + break; + case ObjectIdGreater: + Q(nativeQueryPtr) + ->and_query(numeric_link_greater(linkChain, col_key_arr[arr_len - 1], objectId)); + break; + case ObjectIdGreaterEqual: + Q(nativeQueryPtr) + ->and_query(numeric_link_greaterequal(linkChain, col_key_arr[arr_len - 1], objectId)); + break; + } + } + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualObjectId(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring j_data) +{ + TableQuery_ObjectIdPredicate(env, nativeQueryPtr, columnKeys, tablePointers, j_data, ObjectIdEqual); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualObjectId(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring data) +{ + TableQuery_ObjectIdPredicate(env, nativeQueryPtr, columnKeys, tablePointers, data, ObjectIdNotEqual); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessObjectId(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring data) +{ + TableQuery_ObjectIdPredicate(env, nativeQueryPtr, columnKeys, tablePointers, data, ObjectIdLess); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualObjectId(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring data) +{ + TableQuery_ObjectIdPredicate(env, nativeQueryPtr, columnKeys, tablePointers, data, ObjectIdLessEqual); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterObjectId(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring data) +{ + TableQuery_ObjectIdPredicate(env, nativeQueryPtr, columnKeys, tablePointers, data, ObjectIdGreater); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualObjectId(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring data) +{ + TableQuery_ObjectIdPredicate(env, nativeQueryPtr, columnKeys, tablePointers, data, ObjectIdGreaterEqual); +} + + // String enum StringPredicate { StringEqual, StringNotEqual, StringContains, StringBeginsWith, StringEndsWith, StringLike }; @@ -1284,6 +1577,41 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMaximumDouble( return nullptr; } +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeMaximumDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnKey) +{ + Query* pQuery = Q(nativeQueryPtr); + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Decimal)) { + return nullptr; + } + try { + Decimal128 decimal128 = pQuery->maximum_decimal128(ColKey(columnKey)); + RETURN_DECIMAL128_AS_JLONG_ARRAY__OR_NULL(decimal128) + } + CATCH_STD() + return nullptr; +} + +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeSumDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, jlong columnKey) +{ + Query* pQuery = Q(nativeQueryPtr); + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Decimal)) { + return 0; + } + try { + +// Decimal128 decimal128 = pQuery->sum_decimal128(ColKey(columnKey)); //FIXME waiting for Core to add sum_decimal into query.hpp + Decimal128 decimal128 = pQuery->get_table()->sum_decimal(ColKey(columnKey)); + RETURN_DECIMAL128_AS_JLONG_ARRAY__OR_NULL(decimal128) + } + CATCH_STD() + return 0; +} + JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumDouble(JNIEnv* env, jobject, jlong nativeQueryPtr, jlong columnKey) @@ -1304,6 +1632,23 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_TableQuery_nativeMinimumDouble( return nullptr; } +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeMinimumDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnKey) +{ + Query* pQuery = Q(nativeQueryPtr); + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Decimal)) { + return nullptr; + } + try { + Decimal128 decimal128 = pQuery->minimum_decimal128(ColKey(columnKey)); + RETURN_DECIMAL128_AS_JLONG_ARRAY__OR_NULL(decimal128) + } + CATCH_STD() + return nullptr; +} + JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageDouble(JNIEnv* env, jobject, jlong nativeQueryPtr, jlong columnKey) @@ -1320,6 +1665,22 @@ JNIEXPORT jdouble JNICALL Java_io_realm_internal_TableQuery_nativeAverageDouble( return 0; } +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_TableQuery_nativeAverageDecimal128(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlong columnKey) +{ + Query* pQuery = Q(nativeQueryPtr); + ConstTableRef pTable = pQuery->get_table(); + if (!TYPE_VALID(env, pTable, columnKey, type_Decimal)) { + return nullptr; + } + try { + Decimal128 decimal128 = pQuery->average_decimal128(ColKey(columnKey)); + RETURN_DECIMAL128_AS_JLONG_ARRAY__OR_NULL(decimal128) + } + CATCH_STD() + return nullptr; +} // date aggregates // FIXME: This is a rough workaround while waiting for https://github.com/realm/realm-core/issues/1745 to be solved @@ -1423,6 +1784,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en case type_Float: case type_Double: case type_Timestamp: + case type_Decimal: + case type_ObjectId: Q(nativeQueryPtr)->equal(ColKey(column_idx), realm::null()); break; default: @@ -1459,6 +1822,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en case type_Timestamp: pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); break; + case type_Decimal: + pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); + break; + case type_ObjectId: + pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); + break; default: REALM_UNREACHABLE(); } @@ -1503,6 +1872,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* case type_Float: case type_Double: case type_Timestamp: + case type_Decimal: + case type_ObjectId: pQuery->not_equal(ColKey(column_idx), realm::null()); break; default: @@ -1540,6 +1911,12 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* case type_Timestamp: pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); break; + case type_Decimal: + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); + break; + case type_ObjectId: + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); + break; default: REALM_UNREACHABLE(); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 2583dea5f0..fb00218b93 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -87,7 +87,7 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_UncheckedRow_nativeGetColumnType(J ColKey column_key (columnKey); auto table = OBJ(nativeRowPtr)->get_table(); jint column_type = table->get_column_type(column_key); - if (table->is_list(column_key) && column_type < type_LinkList) { + if (column_type != type_LinkList && table->is_list(column_key)/* && column_type < type_LinkList because type_ObjectId is = 15*/) { // add the offset so it can be mapped correctly in Java (RealmFieldType#fromNativeValue) column_type += 128; } @@ -368,7 +368,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_UncheckedRow_nativeIsNull(JNIE } try { - return to_jbool(OBJ(nativeRowPtr)->is_null(ColKey(columnKey))); + bool is_bool = to_jbool(OBJ(nativeRowPtr)->is_null(ColKey(columnKey))); + return is_bool; } CATCH_STD() return JNI_FALSE; @@ -412,3 +413,66 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeGetFinalizerPt { return reinterpret_cast(&finalize_unchecked_row); } + +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_UncheckedRow_nativeGetDecimal128(JNIEnv* env, jobject, + jlong nativeRowPtr, + jlong columnKey) +{ + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { + return nullptr; + } + + try { + Decimal128 decimal128 = OBJ(nativeRowPtr)->get(ColKey(columnKey)); + RETURN_DECIMAL128_AS_JLONG_ARRAY__OR_NULL(decimal128) + } + CATCH_STD() + return nullptr; +} + +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDecimal128(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnKey, + jlong low, jlong high) +{ + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { + return; + } + + try { + ColKey col_key(columnKey); + Decimal128::Bid128 raw {static_cast(low), static_cast(high)}; + OBJ(nativeRowPtr)->set(col_key, Decimal128(raw)); + } + CATCH_STD() +} + +JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetObjectId(JNIEnv* env, jobject, + jlong nativeRowPtr, + jlong columnKey) +{ + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { + return nullptr; + } + + try { + ObjectId objectId = OBJ(nativeRowPtr)->get(ColKey(columnKey)); + return to_jstring(env, objectId.to_string().data()); + } + CATCH_STD() + return nullptr; +} + +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetObjectId(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnKey, + jstring j_value) +{ + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { + return; + } + + try { + JStringAccessor value(env, j_value); + OBJ(nativeRowPtr)->set(ColKey(columnKey), ObjectId(StringData(value).data())); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp index 0b20ee8b79..e34fb401e9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp @@ -131,6 +131,29 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_native CATCH_STD() } +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddDecimal128 + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jlong j_low_value, jlong j_high_value) +{ + try { + Decimal128::Bid128 raw {static_cast(j_low_value), static_cast(j_high_value)}; + Decimal128 decimal128 = Decimal128(raw); + const JavaValue value(decimal128); + add_property(data_ptr, column_key, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddObjectId + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jstring j_data) +{ + try { + JStringAccessor data(env, j_data); + ObjectId objectId = ObjectId(StringData(data).data()); + const JavaValue value(objectId); + add_property(data_ptr, column_key, value); + } + CATCH_STD() +} JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddObject (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jlong row_ptr) @@ -318,4 +341,28 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_native add_list_element(list_ptr, value); } CATCH_STD() -} \ No newline at end of file +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddDecimal128ListItem + (JNIEnv* env, jclass, jlong list_ptr, jlong j_low_value, jlong j_high_value) +{ + try { + Decimal128::Bid128 raw {static_cast(j_low_value), static_cast(j_high_value)}; + Decimal128 decimal128 = Decimal128(raw); + const JavaValue value(decimal128); + add_list_element(list_ptr, value); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddObjectIdListItem + (JNIEnv* env, jclass, jlong list_ptr, jstring j_data) +{ + try { + JStringAccessor data(env, j_data); + ObjectId objectId = ObjectId(StringData(data).data()); + const JavaValue value(objectId); + add_list_element(list_ptr, value); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index 7cecea181d..4ceb2dd7e3 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -188,6 +188,14 @@ class JavaAccessorContext { { return JavaClassGlobalDef::new_date(m_env, v); } + util::Any box(Decimal v) const + { + return JavaClassGlobalDef::new_decimal128(m_env, v); + } + util::Any box(ObjectId v) const + { + return JavaClassGlobalDef::new_object_id(m_env, v); + } util::Any box(bool v) const { return _impl::JavaClassGlobalDef::new_boolean(m_env, v); @@ -220,6 +228,14 @@ class JavaAccessorContext { { return v ? _impl::JavaClassGlobalDef::new_long(m_env, v.value()) : nullptr; } + util::Any box(util::Optional v) const + { + return v ? _impl::JavaClassGlobalDef::new_decimal128(m_env, v.value()) : nullptr; + } + util::Any box(util::Optional v) const + { + return v ? _impl::JavaClassGlobalDef::new_object_id(m_env, v.value()) : nullptr; + } util::Any box(Obj) const { REALM_TERMINATE("not supported"); @@ -396,6 +412,18 @@ inline Timestamp JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) return v.has_value() ? from_milliseconds(any_cast(v)) : Timestamp(); } +template <> +inline Decimal128 JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? any_cast(v) : Decimal128(realm::null()); +} + +template <> +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? util::make_optional(any_cast(v)) : util::none; +} + template <> inline Obj JavaAccessorContext::unbox(util::Any&, CreatePolicy, ObjKey) const { diff --git a/realm/realm-library/src/main/cpp/java_class_global_def.cpp b/realm/realm-library/src/main/cpp/java_class_global_def.cpp index d1ecd443ca..1fd6c45ffd 100644 --- a/realm/realm-library/src/main/cpp/java_class_global_def.cpp +++ b/realm/realm-library/src/main/cpp/java_class_global_def.cpp @@ -41,3 +41,20 @@ jbyteArray JavaClassGlobalDef::new_byte_array(JNIEnv* env, const BinaryData& bin env->SetByteArrayRegion(ret, 0, size, reinterpret_cast(binary_data.data())); return ret; } + + +jobject JavaClassGlobalDef::new_decimal128(JNIEnv* env, const Decimal128& decimal128) +{ + if (decimal128.is_null()) { + return nullptr; + } + static jni_util::JavaMethod fromIEEE754BIDEncoding(env, instance()->m_bson_decimal128, "fromIEEE754BIDEncoding", "(JJ)Lorg/bson/types/Decimal128;", true); + const Decimal128::Bid128* raw = decimal128.raw(); + return env->CallStaticObjectMethod(instance()->m_bson_decimal128, fromIEEE754BIDEncoding, static_cast(raw->w[1]), static_cast(raw->w[0])); +} + +jobject JavaClassGlobalDef::new_object_id(JNIEnv* env, const ObjectId& objectId) +{ + static jni_util::JavaMethod init(env, instance()->m_bson_object_id, "", "(Ljava/lang/String;)V"); + return env->NewObject(instance()->m_bson_object_id, init, to_jstring(env, objectId.to_string().data())); +} diff --git a/realm/realm-library/src/main/cpp/java_class_global_def.hpp b/realm/realm-library/src/main/cpp/java_class_global_def.hpp index f272340454..167d0fdc4e 100644 --- a/realm/realm-library/src/main/cpp/java_class_global_def.hpp +++ b/realm/realm-library/src/main/cpp/java_class_global_def.hpp @@ -53,6 +53,8 @@ class JavaClassGlobalDef { , m_java_lang_object(env, "java/lang/Object", false) , m_shared_realm_schema_change_callback(env, "io/realm/internal/OsSharedRealm$SchemaChangedCallback", false) , m_realm_notifier(env, "io/realm/internal/RealmNotifier", false) + , m_bson_decimal128(env, "org/bson/types/Decimal128", false) + , m_bson_object_id(env, "org/bson/types/ObjectId", false) { } @@ -66,6 +68,8 @@ class JavaClassGlobalDef { jni_util::JavaClass m_shared_realm_schema_change_callback; jni_util::JavaClass m_realm_notifier; + jni_util::JavaClass m_bson_decimal128; + jni_util::JavaClass m_bson_object_id; inline static std::unique_ptr& instance() { @@ -156,6 +160,10 @@ class JavaClassGlobalDef { // return nullptr if binary_data is null static jbyteArray new_byte_array(JNIEnv* env, const BinaryData& binary_data); + static jobject new_decimal128(JNIEnv* env, const Decimal128& decimal128); + + static jobject new_object_id(JNIEnv* env, const ObjectId& objectId); + // io.realm.internal.OsSharedRealm.SchemaChangedCallback inline static const jni_util::JavaClass& shared_realm_schema_change_callback() { diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp index 197c94199b..cd761f607a 100644 --- a/realm/realm-library/src/main/cpp/java_object_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -28,6 +28,8 @@ #include "object_accessor.hpp" #include "object-store/src/property.hpp" +#include +#include #include using namespace realm::_impl; @@ -520,6 +522,19 @@ inline Timestamp JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) co return v.has_value() ? v.get_date() : Timestamp(); } +template <> +inline Decimal128 JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? v.get_decimal128() : Decimal128(); +} + + +template <> +inline ObjectId JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? v.get_object_id() : ObjectId(); +} + template <> inline Obj JavaContext::unbox(JavaValue const& v, CreatePolicy policy, ObjKey current_row) const { diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index f33d5fbd35..0b2cf5dfee 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -55,6 +55,21 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved); ConvertException(env, __FILE__, __LINE__); \ } +// Return a Decimal128 value as a jlongArray two value (low, high) or nullptrif the Decimal128 is null +#define RETURN_DECIMAL128_AS_JLONG_ARRAY__OR_NULL(decimal128) \ + if (!decimal128.is_null()) { \ + uint64_t* raw = decimal128.raw()->w; \ + jlongArray ret_array = env->NewLongArray(2); \ + if (!ret_array) { \ + ThrowException(env, OutOfMemory, "Could not allocate memory to return decimal128 value."); \ + return nullptr; \ + } \ + jlong ret[2] = { jlong(raw[0])/*low*/, jlong(raw[1]) /*high*/}; \ + env->SetLongArrayRegion(ret_array, 0, 2, ret); \ + return ret_array; \ + } else { \ + return nullptr; \ + } #define MAX_JINT 0x7FFFFFFFL #define MAX_JSIZE MAX_JINT diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 24367b165d..c376b5e5c1 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -15,6 +15,9 @@ */ package io.realm; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Arrays; import java.util.Date; import java.util.Iterator; @@ -110,6 +113,10 @@ public E get(String fieldName) { return (E) proxyState.getRow$realm().getBinaryByteArray(columnKey); case DATE: return (E) proxyState.getRow$realm().getDate(columnKey); + case DECIMAL128: + return (E) proxyState.getRow$realm().getDecimal128(columnKey); + case OBJECT_ID: + return (E) proxyState.getRow$realm().getObjectId(columnKey); case OBJECT: return (E) getObject(fieldName); case LIST: @@ -313,6 +320,44 @@ public Date getDate(String fieldName) { } } + /** + * Returns the {@code Decimal128} value for a given field. + * + * @param fieldName the name of the field. + * @return the Decimal128 value. + * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain Decimal128. + */ + public Decimal128 getDecimal128(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + checkFieldType(fieldName, columnKey, RealmFieldType.DECIMAL128); + if (proxyState.getRow$realm().isNull(columnKey)) { + return null; + } else { + return proxyState.getRow$realm().getDecimal128(columnKey); + } + } + + /** + * Returns the {@code ObjectId} value for a given field. + * + * @param fieldName the name of the field. + * @return the ObjectId value. + * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain ObjectId. + */ + public ObjectId getObjectId(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + checkFieldType(fieldName, columnKey, RealmFieldType.OBJECT_ID); + if (proxyState.getRow$realm().isNull(columnKey)) { + return null; + } else { + return proxyState.getRow$realm().getObjectId(columnKey); + } + } + /** * Returns the object being linked to from this field. * @@ -406,6 +451,10 @@ private RealmFieldType classToRealmType(Class primitiveType) { return RealmFieldType.FLOAT_LIST; } else if (primitiveType.equals(Double.class)) { return RealmFieldType.DOUBLE_LIST; + } else if (primitiveType.equals(Decimal128.class)) { + return RealmFieldType.DECIMAL128_LIST; + } else if (primitiveType.equals(ObjectId.class)) { + return RealmFieldType.OBJECT_ID_LIST; } else { throw new IllegalArgumentException("Unsupported element type. Only primitive types supported. Yours was: " + primitiveType); } @@ -433,6 +482,8 @@ public boolean isNull(String fieldName) { case STRING: case BINARY: case DATE: + case DECIMAL128: + case OBJECT_ID: return proxyState.getRow$realm().isNull(columnKey); case LIST: case LINKING_OBJECTS: @@ -443,6 +494,8 @@ public boolean isNull(String fieldName) { case DATE_LIST: case FLOAT_LIST: case DOUBLE_LIST: + case DECIMAL128_LIST: + case OBJECT_ID_LIST: // fall through default: return false; @@ -514,6 +567,12 @@ public void set(String fieldName, Object value) { case DATE: value = JsonUtils.stringToDate(strValue); break; + case DECIMAL128: + value = Decimal128.parse(strValue); + break; + case OBJECT_ID: + value = new ObjectId(strValue); + break; default: throw new IllegalArgumentException(String.format(Locale.US, "Field %s is not a String field, " + @@ -557,6 +616,10 @@ private void setValue(String fieldName, Object value) { } else if (valueClass == RealmList.class) { RealmList list = (RealmList) value; setList(fieldName, list); + } else if (valueClass == Decimal128.class) { + setDecimal128(fieldName, (Decimal128) value); + } else if (valueClass == ObjectId.class) { + setObjectId(fieldName, (ObjectId) value); } else { throw new IllegalArgumentException("Value is of an type not supported: " + value.getClass()); } @@ -716,6 +779,42 @@ public void setDate(String fieldName, @Nullable Date value) { } } + /** + * Sets the {@code Decimal128} value of the given field. + * + * @param fieldName field name. + * @param value value to insert. + * @throws IllegalArgumentException if field name doesn't exist or field isn't a Decimal128 field. + */ + public void setDecimal128(String fieldName, @Nullable Decimal128 value) { + proxyState.getRealm$realm().checkIfValid(); + + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + if (value == null) { + proxyState.getRow$realm().setNull(columnKey); + } else { + proxyState.getRow$realm().setDecimal128(columnKey, value); + } + } + + /** + * Sets the {@code ObjectId} value of the given field. + * + * @param fieldName field name. + * @param value value to insert. + * @throws IllegalArgumentException if field name doesn't exist or field isn't a ObjectId field. + */ + public void setObjectId(String fieldName, @Nullable ObjectId value) { + proxyState.getRealm$realm().checkIfValid(); + + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + if (value == null) { + proxyState.getRow$realm().setNull(columnKey); + } else { + proxyState.getRow$realm().setObjectId(columnKey, value); + } + } + /** * Sets a reference to another object on the given field. * @@ -794,6 +893,8 @@ public void setList(String fieldName, RealmList list) { case DATE_LIST: case FLOAT_LIST: case DOUBLE_LIST: + case DECIMAL128_LIST: + case OBJECT_ID_LIST: setValueList(fieldName, list, columnType); break; default: @@ -864,6 +965,8 @@ private void setValueList(String fieldName, RealmList list, RealmFieldTyp case DATE_LIST: elementClass = (Class) Date.class; break; case FLOAT_LIST: elementClass = (Class) Float.class; break; case DOUBLE_LIST: elementClass = (Class) Double.class; break; + case DECIMAL128_LIST: elementClass = (Class) Decimal128.class; break; + case OBJECT_ID_LIST: elementClass = (Class) ObjectId.class; break; default: throw new IllegalArgumentException("Unsupported type: " + primitiveType); } @@ -915,6 +1018,14 @@ private ManagedListOperator getOperator(BaseRealm realm, OsList osList, R //noinspection unchecked return (ManagedListOperator) new DateListOperator(realm, osList, (Class) valueClass); } + if (valueListType == RealmFieldType.DECIMAL128_LIST) { + //noinspection unchecked + return (ManagedListOperator) new Decimal128ListOperator(realm, osList, (Class) valueClass); + } + if (valueListType == RealmFieldType.OBJECT_ID_LIST) { + //noinspection unchecked + return (ManagedListOperator) new ObjectIdListOperator(realm, osList, (Class) valueClass); + } throw new IllegalArgumentException("Unexpected list type: " + valueListType.name()); } @@ -1074,6 +1185,12 @@ public String toString() { case DATE: sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getDate(columnKey)); break; + case DECIMAL128: + sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getDecimal128(columnKey)); + break; + case OBJECT_ID: + sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getObjectId(columnKey)); + break; case OBJECT: sb.append(proxyState.getRow$realm().isNullLink(columnKey) ? "null" @@ -1104,6 +1221,12 @@ public String toString() { case DOUBLE_LIST: sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); break; + case DECIMAL128_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); + break; + case OBJECT_ID_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); + break; default: sb.append("?"); break; diff --git a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java index d6bacd2a75..693fe41909 100644 --- a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java @@ -15,6 +15,9 @@ */ package io.realm; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.internal.InvalidRow; @@ -99,6 +102,16 @@ public byte[] getBinaryByteArray(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public Decimal128 getDecimal128(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public ObjectId getObjectId(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public long getLink(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); @@ -174,6 +187,16 @@ public void setNull(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public void setDecimal128(long columnKey, Decimal128 value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setObjectId(long columnKey, ObjectId value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public boolean isValid() { return false; diff --git a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java index 2514255c69..b723b4a061 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java +++ b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java @@ -16,6 +16,9 @@ package io.realm; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.nio.ByteBuffer; import io.realm.internal.Keep; @@ -24,16 +27,15 @@ import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_BINARY; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_BOOLEAN; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_DATE; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_DECIMAL128; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_DOUBLE; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_FLOAT; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_INTEGER; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_LINKING_OBJECTS; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_LIST; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_OBJECT; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_OBJECTID; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_STRING; -import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_UNSUPPORTED_DATE; -import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_UNSUPPORTED_MIXED; -import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_UNSUPPORTED_TABLE; import static io.realm.RealmFieldTypeConstants.LIST_OFFSET; import static io.realm.RealmFieldTypeConstants.MAX_CORE_TYPE_VALUE; @@ -45,17 +47,16 @@ interface RealmFieldTypeConstants { int CORE_TYPE_VALUE_BOOLEAN = 1; int CORE_TYPE_VALUE_STRING = 2; int CORE_TYPE_VALUE_BINARY = 4; - int CORE_TYPE_VALUE_UNSUPPORTED_TABLE = 5; - int CORE_TYPE_VALUE_UNSUPPORTED_MIXED = 6; - int CORE_TYPE_VALUE_UNSUPPORTED_DATE = 7; int CORE_TYPE_VALUE_DATE = 8; int CORE_TYPE_VALUE_FLOAT = 9; int CORE_TYPE_VALUE_DOUBLE = 10; int CORE_TYPE_VALUE_OBJECT = 12; int CORE_TYPE_VALUE_LIST = 13; int CORE_TYPE_VALUE_LINKING_OBJECTS = 14; + int CORE_TYPE_VALUE_DECIMAL128 = 11; + int CORE_TYPE_VALUE_OBJECTID = 15; - int MAX_CORE_TYPE_VALUE = CORE_TYPE_VALUE_LINKING_OBJECTS; + int MAX_CORE_TYPE_VALUE = CORE_TYPE_VALUE_OBJECTID; } /** @@ -76,6 +77,8 @@ public enum RealmFieldType { FLOAT(CORE_TYPE_VALUE_FLOAT), DOUBLE(CORE_TYPE_VALUE_DOUBLE), OBJECT(CORE_TYPE_VALUE_OBJECT), + DECIMAL128(CORE_TYPE_VALUE_DECIMAL128), + OBJECT_ID(CORE_TYPE_VALUE_OBJECTID), LIST(CORE_TYPE_VALUE_LIST), LINKING_OBJECTS(CORE_TYPE_VALUE_LINKING_OBJECTS), @@ -86,7 +89,9 @@ public enum RealmFieldType { BINARY_LIST(CORE_TYPE_VALUE_BINARY + LIST_OFFSET), DATE_LIST(CORE_TYPE_VALUE_DATE + LIST_OFFSET), FLOAT_LIST(CORE_TYPE_VALUE_FLOAT + LIST_OFFSET), - DOUBLE_LIST(CORE_TYPE_VALUE_DOUBLE + LIST_OFFSET); + DOUBLE_LIST(CORE_TYPE_VALUE_DOUBLE + LIST_OFFSET), + DECIMAL128_LIST(CORE_TYPE_VALUE_DECIMAL128 + LIST_OFFSET), + OBJECT_ID_LIST(CORE_TYPE_VALUE_OBJECTID + LIST_OFFSET); // Primitive array for fast mapping between between native values and their Realm type. private static final RealmFieldType[] basicTypes = new RealmFieldType[MAX_CORE_TYPE_VALUE + 1]; @@ -140,25 +145,23 @@ public boolean isValid(Object obj) { return (obj instanceof Float); case CORE_TYPE_VALUE_DOUBLE: return (obj instanceof Double); + case CORE_TYPE_VALUE_DECIMAL128: + return (obj instanceof Decimal128); + case CORE_TYPE_VALUE_OBJECTID: + return (obj instanceof ObjectId); case CORE_TYPE_VALUE_OBJECT: return false; case CORE_TYPE_VALUE_LIST: - return false; case CORE_TYPE_VALUE_LINKING_OBJECTS: - return false; case CORE_TYPE_VALUE_INTEGER + LIST_OFFSET: - return false; case CORE_TYPE_VALUE_BOOLEAN + LIST_OFFSET: - return false; case CORE_TYPE_VALUE_STRING + LIST_OFFSET: - return false; case CORE_TYPE_VALUE_BINARY + LIST_OFFSET: - return false; case CORE_TYPE_VALUE_DATE + LIST_OFFSET: - return false; case CORE_TYPE_VALUE_FLOAT + LIST_OFFSET: - return false; case CORE_TYPE_VALUE_DOUBLE + LIST_OFFSET: + case CORE_TYPE_VALUE_DECIMAL128 + LIST_OFFSET: + case CORE_TYPE_VALUE_OBJECTID + LIST_OFFSET: return false; default: throw new RuntimeException("Unsupported Realm type: " + this); diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index e7a1a0884f..6f79eabcfa 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -16,6 +16,9 @@ package io.realm; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; @@ -1328,6 +1331,14 @@ private ManagedListOperator getOperator(BaseRealm realm, OsList osList, @Null //noinspection unchecked return (ManagedListOperator) new DateListOperator(realm, osList, (Class) clazz); } + if (clazz == Decimal128.class) { + //noinspection unchecked + return (ManagedListOperator) new Decimal128ListOperator(realm, osList, (Class) clazz); + } + if (clazz == ObjectId.class) { + //noinspection unchecked + return (ManagedListOperator) new ObjectIdListOperator(realm, osList, (Class) clazz); + } throw new IllegalArgumentException("Unexpected value class: " + clazz.getName()); } } @@ -1955,3 +1966,103 @@ protected void setValue(int index, Object value) { osList.setDate(index, (Date) value); } } + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link Decimal128} list field. + */ +final class Decimal128ListOperator extends ManagedListOperator { + + Decimal128ListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Decimal128 get(int index) { + return (Decimal128) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Decimal128)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "org.bson.types.Decimal128", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addDecimal128((Decimal128)value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertDecimal128(index, (Decimal128) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setDecimal128(index, (Decimal128) value); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link ObjectId} list field. + */ +final class ObjectIdListOperator extends ManagedListOperator { + + ObjectIdListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public ObjectId get(int index) { + return (ObjectId) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof ObjectId)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "org.bson.types.ObjectId", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addObjectId((ObjectId)value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertObjectId(index, (ObjectId) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setObjectId(index, (ObjectId) value); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index b70991542d..0f29dc4ac3 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -17,6 +17,9 @@ package io.realm; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Collections; import java.util.Date; import java.util.Locale; @@ -299,6 +302,32 @@ public RealmQuery equalTo(String fieldName, @Nullable String value, Case casi return equalToWithoutThreadValidation(fieldName, value, casing); } + /** + * Equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery equalTo(String fieldName, @Nullable Decimal128 value) { + realm.checkIfValid(); + return equalToWithoutThreadValidation(fieldName, value); + } + + /** + * Equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery equalTo(String fieldName, @Nullable ObjectId value) { + realm.checkIfValid(); + return equalToWithoutThreadValidation(fieldName, value); + } + private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable String value, Case casing) { FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING); this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value, casing); @@ -359,7 +388,6 @@ public RealmQuery equalTo(String fieldName, @Nullable byte[] value) { */ public RealmQuery equalTo(String fieldName, @Nullable Short value) { realm.checkIfValid(); - return equalToWithoutThreadValidation(fieldName, value); } @@ -513,6 +541,27 @@ private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable return this; } + private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable Decimal128 value) { + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DECIMAL128); + if (value == null) { + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); + } else { + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + } + return this; + } + + private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable ObjectId value) { + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.OBJECT_ID); + if (value == null) { + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); + } else { + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + } + return this; + } + + /** * In comparison. This allows you to test if objects match any value in an array of values. * @@ -778,6 +827,44 @@ public RealmQuery notEqualTo(String fieldName, @Nullable String value, Case c return this; } + /** + * Not-equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery notEqualTo(String fieldName, Decimal128 value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DECIMAL128); + if (value == null) { + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); + } else { + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + } + return this; + } + + /** + * Not-equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery notEqualTo(String fieldName, ObjectId value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.OBJECT_ID); + if (value == null) { + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); + } else { + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + } + return this; + } + /** * Not-equal-to comparison. * @@ -1038,6 +1125,36 @@ public RealmQuery greaterThan(String fieldName, Date value) { return this; } + /** + * Greater-than comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery greaterThan(String fieldName, Decimal128 value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DECIMAL128); + this.query.greaterThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + + /** + * Greater-than comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery greaterThan(String fieldName, ObjectId value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.OBJECT_ID); + this.query.greaterThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + /** * Greater-than-or-equal-to comparison. * @@ -1118,6 +1235,36 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, Date value) { return this; } + /** + * Greater-than-or-equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery greaterThanOrEqualTo(String fieldName, Decimal128 value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DECIMAL128); + this.query.greaterThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + + /** + * Greater-than-or-equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery greaterThanOrEqualTo(String fieldName, ObjectId value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.OBJECT_ID); + this.query.greaterThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + /** * Less-than comparison. * @@ -1150,6 +1297,36 @@ public RealmQuery lessThan(String fieldName, long value) { return this; } + /** + * Less-than comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery lessThan(String fieldName, Decimal128 value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DECIMAL128); + this.query.lessThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + + /** + * Less-than comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery lessThan(String fieldName, ObjectId value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.OBJECT_ID); + this.query.lessThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + /** * Less-than comparison. * @@ -1192,7 +1369,6 @@ public RealmQuery lessThan(String fieldName, float value) { */ public RealmQuery lessThan(String fieldName, Date value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DATE); this.query.lessThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; @@ -1224,12 +1400,41 @@ public RealmQuery lessThanOrEqualTo(String fieldName, int value) { */ public RealmQuery lessThanOrEqualTo(String fieldName, long value) { realm.checkIfValid(); - FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.INTEGER); this.query.lessThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); return this; } + /** + * Less-than-or-equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery lessThanOrEqualTo(String fieldName, Decimal128 value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DECIMAL128); + this.query.lessThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + + /** + * Less-than-or-equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery lessThanOrEqualTo(String fieldName, ObjectId value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.OBJECT_ID); + this.query.lessThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + /** * Less-than-or-equal-to comparison. * @@ -1363,6 +1568,22 @@ public RealmQuery between(String fieldName, Date from, Date to) { return this; } + /** + * Between condition. + * + * @param fieldName the field to compare. + * @param from lowest value (inclusive). + * @param to highest value (inclusive). + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery between(String fieldName, Decimal128 from, Decimal128 to) { + realm.checkIfValid(); + + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.DECIMAL128); + this.query.between(fd.getColumnKeys(), from, to); + return this; + } /** * Condition that value of field contains the specified substring. @@ -1616,6 +1837,8 @@ public Number sum(String fieldName) { return query.sumFloat(columnKey); case DOUBLE: return query.sumDouble(columnKey); + case DECIMAL128: + return query.sumDecimal128(columnKey); default: throw new IllegalArgumentException(String.format(Locale.US, TYPE_MISMATCH, fieldName, "int, float or double")); @@ -1645,10 +1868,24 @@ public double average(String fieldName) { return query.averageFloat(columnIndex); default: throw new IllegalArgumentException(String.format(Locale.US, - TYPE_MISMATCH, fieldName, "int, float or double")); + TYPE_MISMATCH, fieldName, "int, float or double. For Decimal128 use `averageDecimal128` method.")); } } + /** + * Returns the average of a given field. + * Does not support dotted field notation. + * + * @param fieldName the field to calculate average on. Only Decimal128 fields is supported. For other number types consider using {@link #average(String)}. + * @return the average for the given field amongst objects in query results. This will be of type Decimal128. If no objects exist or they all have {@code null} + * as the value for the given field {@code 0} will be returned. When computing the average, objects with {@code null} values are ignored. + * @throws java.lang.IllegalArgumentException if the field is not a Decimal128 type. + */ + public @Nullable Decimal128 averageDecimal128(String fieldName) { + realm.checkIfValid(); + long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); + return query.averageDecimal128(columnIndex); + } /** * Finds the minimum value of a field. * @@ -1670,6 +1907,8 @@ public Number min(String fieldName) { return this.query.minimumFloat(columnIndex); case DOUBLE: return this.query.minimumDouble(columnIndex); + case DECIMAL128: + return this.query.minimumDecimal128(columnIndex); default: throw new IllegalArgumentException(String.format(Locale.US, TYPE_MISMATCH, fieldName, "int, float or double")); @@ -1714,6 +1953,8 @@ public Number max(String fieldName) { return this.query.maximumFloat(columnIndex); case DOUBLE: return this.query.maximumDouble(columnIndex); + case DECIMAL128: + return this.query.maximumDecimal128(columnIndex); default: throw new IllegalArgumentException(String.format(Locale.US, TYPE_MISMATCH, fieldName, "int, float or double")); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 18979d112b..f3b871d82e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -19,6 +19,9 @@ import android.annotation.SuppressLint; import android.os.Looper; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import java.util.List; import java.util.Locale; @@ -198,6 +201,12 @@ public void setValue(String fieldName, @Nullable Object value) { case DATE: value = JsonUtils.stringToDate(strValue); break; + case DECIMAL128: + value = Decimal128.parse(strValue); + break; + case OBJECT_ID: + value = new ObjectId(strValue); + break; default: throw new IllegalArgumentException(String.format(Locale.US, "Field %s is not a String field, " + @@ -227,6 +236,10 @@ public void setValue(String fieldName, @Nullable Object value) { setString(fieldName, (String) value); } else if (value instanceof Date) { setDate(fieldName, (Date) value); + } else if (value instanceof Decimal128) { + setDecimal128(fieldName, (Decimal128) value); + } else if (value instanceof ObjectId) { + setObjectId(fieldName, (ObjectId) value); } else if (value instanceof byte[]) { setBlob(fieldName, (byte[]) value); } else if (value instanceof RealmModel) { @@ -392,7 +405,7 @@ public void setBlob(String fieldName, @Nullable byte[] value) { * * @param fieldName name of the field to update. * @param value new value for the field. - * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a date field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a {@code Date} field. */ public void setDate(String fieldName, @Nullable Date value) { checkNonEmptyFieldName(fieldName); @@ -418,6 +431,36 @@ public void setObject(String fieldName, @Nullable RealmModel value) { osResults.setObject(fieldName, row); } + /** + * Sets the {@code Decimal128} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a {@code Decimal128} field. + */ + public void setDecimal128(String fieldName, @Nullable Decimal128 value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.DECIMAL128); + osResults.setDecimal128(fieldName, value); + } + + /** + * Sets the {@code ObjectId} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a {@code ObjectId field. + */ + public void setObjectId(String fieldName, @Nullable ObjectId value) { + checkNonEmptyFieldName(fieldName); + realm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.OBJECT_ID); + osResults.setObjectId(fieldName, value); + } + private Row checkRealmObjectConstraints(String fieldName, @Nullable RealmModel value) { if (value != null) { if (!(RealmObject.isManaged(value) && RealmObject.isValid(value))) { @@ -508,6 +551,14 @@ public void setList(String fieldName, RealmList list) { checkTypeOfListElements(list, Date.class); osResults.setDateList(fieldName, (RealmList) list); break; + case DECIMAL128_LIST: + checkTypeOfListElements(list, Decimal128.class); + osResults.setDecimal128List(fieldName, (RealmList) list); + break; + case OBJECT_ID_LIST: + checkTypeOfListElements(list, ObjectId.class); + osResults.setObjectIdList(fieldName, (RealmList) list); + break; case FLOAT_LIST: checkTypeOfListElements(list, Float.class); osResults.setFloatList(fieldName, (RealmList) list); @@ -516,8 +567,18 @@ public void setList(String fieldName, RealmList list) { checkTypeOfListElements(list, Double.class); osResults.setDoubleList(fieldName, (RealmList) list); break; - default: + default: { +// // Handle Decimal128 and ObjectId in a special way since they might not be on the +// // classpath +// if (columnType == RealmFieldType.DECIMAL128_LIST) { +// checkTypeOfListElements(list, Decimal128.class); +// osResults.setDecimal128List(fieldName, (RealmList) list); +// } else if (columnType == RealmFieldType.OBJECT_ID_LIST) { +// checkTypeOfListElements(list, ObjectId.class); +// osResults.setObjectIdList(fieldName, (RealmList) list); +// } throw new IllegalArgumentException(String.format("Field '%s' is not a list but a %s", fieldName, columnType)); + } } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java index ba7d8b1d94..24595f6eb9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java @@ -189,4 +189,16 @@ public Row freeze(OsSharedRealm frozenRealm) { @Override protected native void nativeNullifyLink(long nativeRowPtr, long columnIndex); + + @Override + protected native long[] nativeGetDecimal128(long nativePtr, long columnKey); + + @Override + protected native String nativeGetObjectId(long nativePtr, long columnKey); + + @Override + protected native void nativeSetDecimal128(long nativePtr, long columnKey, long low, long high); + + @Override + protected native void nativeSetObjectId(long nativePtr, long columnKey, String value); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index 2d0681b11a..afd7f74aa6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -16,6 +16,9 @@ package io.realm.internal; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.RealmFieldType; @@ -94,6 +97,16 @@ public byte[] getBinaryByteArray(long columnKey) { throw getStubException(); } + @Override + public Decimal128 getDecimal128(long columnKey) { + throw getStubException(); + } + + @Override + public ObjectId getObjectId(long columnKey) { + throw getStubException(); + } + @Override public long getLink(long columnKey) { throw getStubException(); @@ -169,6 +182,16 @@ public void setNull(long columnKey) { throw getStubException(); } + @Override + public void setDecimal128(long columnKey, Decimal128 value) { + throw getStubException(); + } + + @Override + public void setObjectId(long columnKey, ObjectId value) { + throw getStubException(); + } + @Override public boolean isValid() { return false; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsList.java b/realm/realm-library/src/main/java/io/realm/internal/OsList.java index 71b60650b7..a95145d955 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsList.java @@ -1,5 +1,8 @@ package io.realm.internal; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import javax.annotation.Nullable; @@ -176,6 +179,54 @@ public void setDate(long pos, @Nullable Date value) { } } + public void addDecimal128(@Nullable Decimal128 value) { + if (value == null) { + nativeAddNull(nativePtr); + } else { + nativeAddDecimal128(nativePtr, value.getLow(), value.getHigh()); + } + } + + public void insertDecimal128(long pos, @Nullable Decimal128 value) { + if (value == null) { + nativeInsertNull(nativePtr, pos); + } else { + nativeInsertDecimal128(nativePtr, pos, value.getLow(), value.getHigh()); + } + } + + public void setDecimal128(long pos, @Nullable Decimal128 value) { + if (value == null) { + nativeSetNull(nativePtr, pos); + } else { + nativeSetDecimal128(nativePtr, pos, value.getLow(), value.getHigh()); + } + } + + public void addObjectId(@Nullable ObjectId value) { + if (value == null) { + nativeAddNull(nativePtr); + } else { + nativeAddObjectId(nativePtr, value.toString()); + } + } + + public void insertObjectId(long pos, @Nullable ObjectId value) { + if (value == null) { + nativeInsertNull(nativePtr, pos); + } else { + nativeInsertObjectId(nativePtr, pos, value.toString()); + } + } + + public void setObjectId(long pos, @Nullable ObjectId value) { + if (value == null) { + nativeSetNull(nativePtr, pos); + } else { + nativeSetObjectId(nativePtr, pos, value.toString()); + } + } + @Nullable public Object getValue(long pos) { return nativeGetValue(nativePtr, pos); @@ -348,6 +399,18 @@ public OsList freeze(OsSharedRealm frozenRealm) { private static native void nativeSetString(long nativePtr, long pos, @Nullable String value); + private static native void nativeAddDecimal128(long nativePtr, long low, long high); + + private static native void nativeInsertDecimal128(long nativePtr, long pos, long low, long high); + + private static native void nativeSetDecimal128(long nativePtr, long pos, long low, long high); + + private static native void nativeAddObjectId(long nativePtr, String data); + + private static native void nativeInsertObjectId(long nativePtr, long pos, String data); + + private static native void nativeSetObjectId(long nativePtr, long pos, String data); + private static native Object nativeGetValue(long nativePtr, long pos); private native void nativeStartListening(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index defa6baabb..ab139b299b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -16,6 +16,8 @@ package io.realm.internal; +import org.bson.types.ObjectId; + import javax.annotation.Nullable; import io.realm.ObjectChangeSet; @@ -204,6 +206,11 @@ public static UncheckedRow createWithPrimaryKey(Table table, @Nullable Object pr return new UncheckedRow(sharedRealm.context, table, nativeCreateNewObjectWithLongPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), primaryKeyColumnKey, value, primaryKeyValue == null)); + } else if (type == RealmFieldType.OBJECT_ID) { + String objectIdValue = primaryKeyValue == null ? null : primaryKeyValue.toString(); + return new UncheckedRow(sharedRealm.context, table, + nativeCreateNewObjectWithObjectIdPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), + primaryKeyColumnKey, objectIdValue)); } else { throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); } @@ -220,7 +227,7 @@ public static UncheckedRow createWithPrimaryKey(Table table, @Nullable Object pr * @return a newly created {@code UncheckedRow}. */ // FIXME: Proxy could just pass the pk index here which is much faster. - public static long createRowWithPrimaryKey(Table table, long primaryKeyColumnIndex, Object primaryKeyValue) { + public static long createRowWithPrimaryKey(Table table, long primaryKeyColumnIndex, @Nullable Object primaryKeyValue) { RealmFieldType type = table.getColumnType(primaryKeyColumnIndex); final OsSharedRealm sharedRealm = table.getSharedRealm(); @@ -235,6 +242,13 @@ public static long createRowWithPrimaryKey(Table table, long primaryKeyColumnInd long value = primaryKeyValue == null ? 0 : Long.parseLong(primaryKeyValue.toString()); return nativeCreateRowWithLongPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), primaryKeyColumnIndex, value, primaryKeyValue == null); + } else if (type == RealmFieldType.OBJECT_ID) { + if (primaryKeyValue != null && !(primaryKeyValue instanceof ObjectId)) { + throw new IllegalArgumentException("Primary key value is not an ObjectId: " + primaryKeyValue); + } + String objectIdValue = primaryKeyValue == null ? null : primaryKeyValue.toString(); + return nativeCreateRowWithObjectIdPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), + primaryKeyColumnIndex, objectIdValue); } else { throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); } @@ -277,6 +291,14 @@ private static native long nativeCreateNewObjectWithStringPrimaryKey(long shared // Return a index of newly created Row. private static native long nativeCreateRowWithStringPrimaryKey(long sharedRealmPtr, long tableRefPtr, long pk_column_index, - String primaryKeyValue); + @Nullable String primaryKeyValue); + + private static native long nativeCreateRowWithObjectIdPrimaryKey(long sharedRealmPtr, + long tableRefPtr, long pk_column_index, + @Nullable String primaryKeyValue); + + private static native long nativeCreateNewObjectWithObjectIdPrimaryKey(long sharedRealmPtr, + long tableRefPtr, long pk_column_index, + @Nullable String data); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 2fbfaff187..6ea7bd9a66 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -16,6 +16,9 @@ package io.realm.internal; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Date; @@ -452,6 +455,22 @@ public void setDate(String fieldName, @Nullable Date timestamp) { } } + public void setDecimal128(String fieldName, @Nullable Decimal128 value) { + if (value == null) { + nativeSetNull(nativePtr, fieldName); + } else { + nativeSetDecimal128(nativePtr, fieldName, value.getLow(), value.getHigh()); + } + } + + public void setObjectId(String fieldName, @Nullable ObjectId value) { + if (value == null) { + nativeSetNull(nativePtr, fieldName); + } else { + nativeSetObjectId(nativePtr, fieldName, value.toString()); + } + } + public void setObject(String fieldName, @Nullable Row row) { if (row == null) { setNull(fieldName); @@ -587,6 +606,24 @@ public void addList(OsObjectBuilder builder, RealmList list) { }); } + public void setDecimal128List(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addDecimal128List(0, list); + } + }); + } + + public void setObjectIdList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addObjectIdList(0, list); + } + }); + } + public void addListener(T observer, OrderedRealmCollectionChangeListener listener) { if (observerPairs.isEmpty()) { nativeStartListening(nativePtr); @@ -713,6 +750,10 @@ public void load() { private static native void nativeSetTimestamp(long nativePtr, String fieldName, long value); + private static native void nativeSetDecimal128(long nativePtr, String fieldName, long low, long high); + + private static native void nativeSetObjectId(long nativePtr, String fieldName, String data); + private static native void nativeSetObject(long nativePtr, String fieldName, long rowNativePtr); private static native void nativeSetList(long nativePtr, String fieldName, long builderNativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index e96a01dc2e..f95bf2a138 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -1,5 +1,8 @@ package io.realm.internal; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.lang.ref.WeakReference; import java.util.Date; @@ -123,6 +126,16 @@ public byte[] getBinaryByteArray(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public Decimal128 getDecimal128(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public ObjectId getObjectId(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public long getLink(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); @@ -198,6 +211,16 @@ public void setNull(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public void setDecimal128(long columnKey, Decimal128 value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + + @Override + public void setObjectId(long columnKey, ObjectId value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public boolean isValid() { return false; diff --git a/realm/realm-library/src/main/java/io/realm/internal/Property.java b/realm/realm-library/src/main/java/io/realm/internal/Property.java index c5d4648ebb..d3ea639b84 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Property.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Property.java @@ -24,9 +24,11 @@ import static io.realm.RealmFieldType.BINARY_LIST; import static io.realm.RealmFieldType.BOOLEAN_LIST; import static io.realm.RealmFieldType.DATE_LIST; +import static io.realm.RealmFieldType.DECIMAL128_LIST; import static io.realm.RealmFieldType.DOUBLE_LIST; import static io.realm.RealmFieldType.FLOAT_LIST; import static io.realm.RealmFieldType.INTEGER_LIST; +import static io.realm.RealmFieldType.OBJECT_ID_LIST; import static io.realm.RealmFieldType.STRING_LIST; @@ -58,6 +60,10 @@ public class Property implements NativeObject { @SuppressWarnings("WeakerAccess") public static final int TYPE_LINKING_OBJECTS = 8; @SuppressWarnings("WeakerAccess") + public static final int TYPE_DECIMAL128 = 11; + @SuppressWarnings("WeakerAccess") + public static final int TYPE_OBJECT_ID = 10; + @SuppressWarnings("WeakerAccess") public static final int TYPE_REQUIRED = 0; @SuppressWarnings("WeakerAccess") public static final int TYPE_NULLABLE = 64; @@ -102,6 +108,12 @@ static int convertFromRealmFieldType(RealmFieldType fieldType, boolean isRequire case FLOAT: type = TYPE_FLOAT; break; + case DECIMAL128: + type = TYPE_DECIMAL128; + break; + case OBJECT_ID: + type = TYPE_OBJECT_ID; + break; case DOUBLE: type = TYPE_DOUBLE; break; @@ -124,6 +136,12 @@ static int convertFromRealmFieldType(RealmFieldType fieldType, boolean isRequire case FLOAT_LIST: type = TYPE_FLOAT | TYPE_ARRAY; break; + case DECIMAL128_LIST: + type = TYPE_DECIMAL128 | TYPE_ARRAY; + break; + case OBJECT_ID_LIST: + type = TYPE_OBJECT_ID | TYPE_ARRAY; + break; case DOUBLE_LIST: type = TYPE_DOUBLE | TYPE_ARRAY; break; @@ -159,6 +177,10 @@ private static RealmFieldType convertToRealmFieldType(int propertyType) { return RealmFieldType.FLOAT; case TYPE_DOUBLE: return RealmFieldType.DOUBLE; + case TYPE_DECIMAL128: + return RealmFieldType.DECIMAL128; + case TYPE_OBJECT_ID: + return RealmFieldType.OBJECT_ID; //noinspection PointlessBitwiseExpression case TYPE_INT | TYPE_ARRAY: return INTEGER_LIST; @@ -174,6 +196,10 @@ private static RealmFieldType convertToRealmFieldType(int propertyType) { return FLOAT_LIST; case TYPE_DOUBLE | TYPE_ARRAY: return DOUBLE_LIST; + case TYPE_DECIMAL128 | TYPE_ARRAY: + return DECIMAL128_LIST; + case TYPE_OBJECT_ID | TYPE_ARRAY: + return OBJECT_ID_LIST; default: throw new IllegalArgumentException( String.format(Locale.US, "Unsupported property type: '%d'", propertyType)); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index a58ad9ce51..08374fb427 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -16,6 +16,9 @@ package io.realm.internal; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import javax.annotation.Nullable; @@ -79,6 +82,10 @@ public interface Row { byte[] getBinaryByteArray(long columnKey); + Decimal128 getDecimal128(long columnKey); + + ObjectId getObjectId(long columnKey); + long getLink(long columnKey); boolean isNullLink(long columnKey); @@ -109,6 +116,10 @@ public interface Row { void setNull(long columnKey); + void setDecimal128(long columnKey, Decimal128 value); + + void setObjectId(long columnKey, ObjectId value); + /** * Checks if the row is still valid. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index ab6b2f8679..d388ca1bcc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -16,6 +16,9 @@ package io.realm.internal; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import javax.annotation.Nullable; @@ -478,6 +481,24 @@ public void setBinaryByteArray(long columnKey, long rowKey, byte[] data, boolean nativeSetByteArray(nativeTableRefPtr, columnKey, rowKey, data, isDefault); } + public void setDecimal128(long columnKey, long rowKey, @Nullable Decimal128 value, boolean isDefault) { + checkImmutable(); + if (value == null) { + nativeSetNull(nativeTableRefPtr, columnKey, rowKey, isDefault); + } else { + nativeSetDecimal128(nativeTableRefPtr, columnKey, rowKey, value.getLow(), value.getHigh(), isDefault); + } + } + + public void setObjectId(long columnKey, long rowKey, @Nullable ObjectId value, boolean isDefault) { + checkImmutable(); + if (value == null) { + nativeSetNull(nativeTableRefPtr, columnKey, rowKey, isDefault); + } else { + nativeSetObjectId(nativeTableRefPtr, columnKey, rowKey, value.toString(), isDefault); + } + } + public void setLink(long columnKey, long rowKey, long value, boolean isDefault) { checkImmutable(); nativeSetLink(nativeTableRefPtr, columnKey, rowKey, value, isDefault); @@ -581,6 +602,13 @@ public long findFirstString(long columnKey, String value) { return nativeFindFirstString(nativeTableRefPtr, columnKey, value); } + public long findFirstObjectId(long columnKey, ObjectId value) { + if (value == null) { + throw new IllegalArgumentException("null is not supported"); + } + return nativeFindFirstObjectId(nativeTableRefPtr, columnKey, value.toString()); + } + /** * Searches for first occurrence of null. Beware that the order in the column is undefined. * @@ -741,6 +769,10 @@ public static String getTableNameForClass(String name) { private native long nativeGetLinkTarget(long nativePtr, long columnKey); + private native long[] nativeGetDecimal128(long nativePtr, long columnKey, long rowKey); + + private native String nativeGetObjectId(long nativePtr, long columnKey, long rowKey); + private native boolean nativeIsNull(long nativePtr, long columnKey, long rowKey); native long nativeGetRowPtr(long nativePtr, long objKey); @@ -763,6 +795,10 @@ public static String getTableNameForClass(String name) { public static native void nativeSetByteArray(long nativePtr, long columnKey, long rowKey, byte[] data, boolean isDefault); + public static native void nativeSetDecimal128(long nativeTableRefPtr, long columnKey, long rowKey, long low, long high, boolean isDefault); + + public static native void nativeSetObjectId(long nativeTableRefPtr, long columnKey, long rowKey, String data, boolean isDefault); + public static native void nativeSetLink(long nativeTableRefPtr, long columnKey, long rowKey, long value, boolean isDefault); private native void nativeAddSearchIndex(long nativePtr, long columnKey); @@ -797,6 +833,8 @@ public static String getTableNameForClass(String name) { public static native long nativeFindFirstString(long nativeTableRefPtr, long columnKey, String value); + public static native long nativeFindFirstObjectId(long nativeTableRefPtr, long columnKey, String value); + public static native long nativeFindFirstNull(long nativeTableRefPtr, long columnKey); private native String nativeGetName(long nativeTableRefPtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index c1a87a7d6b..4c0e12f52b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -16,6 +16,9 @@ package io.realm.internal; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import javax.annotation.Nullable; @@ -309,6 +312,16 @@ public TableQuery between(long[] columnKey, Date value1, Date value2) { return this; } + public TableQuery between(long[] columnKey, Decimal128 value1, Decimal128 value2) { + //noinspection ConstantConditions + if (value1 == null || value2 == null) { + throw new IllegalArgumentException("Decimal128 values in query criteria must not be null."); + } + nativeBetweenDecimal128(nativePtr, columnKey, value1.getLow(), value1.getHigh(), value2.getLow(), value2.getHigh()); + queryValidated = false; + return this; + } + // Queries for Binary values. public TableQuery equalTo(long[] columnKeys, long[] tablePtrs, byte[] value) { @@ -409,6 +422,83 @@ public TableQuery isNotEmpty(long[] columnKeys, long[] tablePtrs) { return this; } + // Queries for Decimal128 + + public TableQuery equalTo(long[] columnKeys, long[] tablePtrs, Decimal128 value) { + nativeEqualDecimal128(nativePtr, columnKeys, tablePtrs, value.getLow(), value.getHigh()); + queryValidated = false; + return this; + } + + public TableQuery notEqualTo(long[] columnKeys, long[] tablePtrs, Decimal128 value) { + nativeNotEqualDecimal128(nativePtr, columnKeys, tablePtrs, value.getLow(), value.getHigh()); + queryValidated = false; + return this; + } + + public TableQuery lessThan(long[] columnKeys, long[] tablePtrs, Decimal128 value) { + nativeLessDecimal128(nativePtr, columnKeys, tablePtrs, value.getLow(), value.getHigh()); + queryValidated = false; + return this; + } + + public TableQuery lessThanOrEqual(long[] columnKeys, long[] tablePtrs, Decimal128 value) { + nativeLessEqualDecimal128(nativePtr, columnKeys, tablePtrs, value.getLow(), value.getHigh()); + queryValidated = false; + return this; + } + + public TableQuery greaterThan(long[] columnKeys, long[] tablePtrs, Decimal128 value) { + nativeGreaterDecimal128(nativePtr, columnKeys, tablePtrs, value.getLow(), value.getHigh()); + queryValidated = false; + return this; + } + + public TableQuery greaterThanOrEqual(long[] columnKeys, long[] tablePtrs, Decimal128 value) { + nativeGreaterEqualDecimal128(nativePtr, columnKeys, tablePtrs, value.getLow(), value.getHigh()); + queryValidated = false; + return this; + } + + + // Queries for ObjectId + + public TableQuery equalTo(long[] columnKeys, long[] tablePtrs, ObjectId value) { + nativeEqualObjectId(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery notEqualTo(long[] columnKeys, long[] tablePtrs, ObjectId value) { + nativeNotEqualObjectId(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery lessThan(long[] columnKeys, long[] tablePtrs, ObjectId value) { + nativeLessObjectId(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery lessThanOrEqual(long[] columnKeys, long[] tablePtrs, ObjectId value) { + nativeLessEqualObjectId(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery greaterThan(long[] columnKeys, long[] tablePtrs, ObjectId value) { + nativeGreaterObjectId(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery greaterThanOrEqual(long[] columnKeys, long[] tablePtrs, ObjectId value) { + nativeGreaterEqualObjectId(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + // Searching methods. /** @@ -474,6 +564,16 @@ public double sumDouble(long columnKey) { return nativeSumDouble(nativePtr, columnKey); } + public Decimal128 sumDecimal128(long columnKey) { + validateQuery(); + long[] data = nativeSumDecimal128(nativePtr, columnKey); + if (data != null) { + return Decimal128.fromIEEE754BIDEncoding(data[1]/*high*/, data[0]/*low*/); + } else { + return null; + } + } + public Double maximumDouble(long columnKey) { validateQuery(); return nativeMaximumDouble(nativePtr, columnKey); @@ -489,6 +589,24 @@ public double averageDouble(long columnKey) { return nativeAverageDouble(nativePtr, columnKey); } + public Decimal128 averageDecimal128(long columnKey) { + validateQuery(); + long[] result = nativeAverageDecimal128(nativePtr, columnKey); + if (result != null) { + return Decimal128.fromIEEE754BIDEncoding(result[1]/*high*/, result[0]/*low*/); + } + return null; + } + + public Decimal128 maximumDecimal128(long columnKey) { + validateQuery(); + long[] result = nativeMaximumDecimal128(nativePtr, columnKey); + if (result != null) { + return Decimal128.fromIEEE754BIDEncoding(result[1]/*high*/, result[0]/*low*/); + } + return null; + } + // Date aggregation public Date maximumDate(long columnKey) { @@ -509,6 +627,15 @@ public Date minimumDate(long columnKey) { return null; } + public Decimal128 minimumDecimal128(long columnKey) { + validateQuery(); + long[] result = nativeMinimumDecimal128(nativePtr, columnKey); + if (result != null) { + return Decimal128.fromIEEE754BIDEncoding(result[1]/*high*/, result[0]/*low*/); + } + return null; + } + // isNull and isNotNull public TableQuery isNull(long[] columnKeys, long[] tablePtrs) { nativeIsNull(nativePtr, columnKeys, tablePtrs); @@ -622,6 +749,8 @@ public void alwaysFalse() { private native void nativeBetweenTimestamp(long nativeQueryPtr, long[] columnIndex, long value1, long value2); + private native void nativeBetweenDecimal128(long nativeQueryPtr, long[] columnIndex, long value1Low, long value1How, long value2Low, long value2High); + private native void nativeEqual(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, byte[] value); private native void nativeNotEqual(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, byte[] value); @@ -638,6 +767,30 @@ public void alwaysFalse() { private native void nativeContains(long nativeQueryPtr, long[] columnKeys, long[] tablePtrs, String value, boolean caseSensitive); + private native void nativeEqualDecimal128(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long low, long high); + + private native void nativeNotEqualDecimal128(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long low, long high); + + private native void nativeGreaterDecimal128(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long low, long high); + + private native void nativeGreaterEqualDecimal128(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long low, long high); + + private native void nativeLessDecimal128(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long low, long high); + + private native void nativeLessEqualDecimal128(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, long low, long high); + + private native void nativeEqualObjectId(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeNotEqualObjectId(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeGreaterObjectId(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeGreaterEqualObjectId(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeLessObjectId(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeLessEqualObjectId(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + private native void nativeIsEmpty(long nativePtr, long[] columnKeys, long[] tablePtrs); private native void nativeIsNotEmpty(long nativePtr, long[] columnKeys, long[] tablePtrs); @@ -666,12 +819,20 @@ public void alwaysFalse() { private native double nativeSumDouble(long nativeQueryPtr, long columnKey); + private native long[] nativeSumDecimal128(long nativeQueryPtr, long columnKey); + private native Double nativeMaximumDouble(long nativeQueryPtr, long columnKey); + private native long[] nativeMaximumDecimal128(long nativeQueryPtr, long columnKey); + private native Double nativeMinimumDouble(long nativeQueryPtr, long columnKey); + private native long[] nativeMinimumDecimal128(long nativeQueryPtr, long columnKey); + private native double nativeAverageDouble(long nativeQueryPtr, long columnKey); + private native long[] nativeAverageDecimal128(long nativeQueryPtr, long columnKey); + private native Long nativeMaximumTimestamp(long nativeQueryPtr, long columnKey); private native Long nativeMinimumTimestamp(long nativeQueryPtr, long columnKey); diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index dad433f875..095fb4f9a5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -16,6 +16,9 @@ package io.realm.internal; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import javax.annotation.Nullable; @@ -161,6 +164,21 @@ public byte[] getBinaryByteArray(long columnKey) { return nativeGetByteArray(nativePtr, columnKey); } + @Override + public Decimal128 getDecimal128(long columnKey) { + long[] data = nativeGetDecimal128(nativePtr, columnKey); + if (data != null) { + return Decimal128.fromIEEE754BIDEncoding(data[1]/*high*/, data[0]/*low*/); + } else { + return null; + } + } + + @Override + public ObjectId getObjectId(long columnKey) { + return new ObjectId(nativeGetObjectId(nativePtr, columnKey)); + } + @Override public long getLink(long columnKey) { return nativeGetLink(nativePtr, columnKey); @@ -268,6 +286,26 @@ public void setNull(long columnKey) { nativeSetNull(nativePtr, columnKey); } + @Override + public void setDecimal128(long columnKey, @Nullable Decimal128 value) { + parent.checkImmutable(); + if (value == null) { + nativeSetNull(nativePtr, columnKey); + } else { + nativeSetDecimal128(nativePtr, columnKey, value.getLow(), value.getHigh()); + } + } + + @Override + public void setObjectId(long columnKey, @Nullable ObjectId value) { + parent.checkImmutable(); + if (value == null) { + nativeSetNull(nativePtr, columnKey); + } else { + nativeSetObjectId(nativePtr, columnKey, value.toString()); + } + } + /** * Converts the unchecked Row to a checked variant. * @@ -333,6 +371,11 @@ public boolean isLoaded() { protected native byte[] nativeGetByteArray(long nativePtr, long columnKey); + // Returns String representation for Decimal128() + protected native long[] nativeGetDecimal128(long nativePtr, long columnKey); + + protected native String nativeGetObjectId(long nativePtr, long columnKey); + protected native void nativeSetLong(long nativeRowPtr, long columnKey, long value); protected native void nativeSetBoolean(long nativeRowPtr, long columnKey, boolean value); @@ -349,6 +392,10 @@ public boolean isLoaded() { protected native void nativeSetByteArray(long nativePtr, long columnKey, @Nullable byte[] data); + protected native void nativeSetDecimal128(long nativePtr, long columnKey, long low, long high); + + protected native void nativeSetObjectId(long nativePtr, long columnKey, String value); + protected native void nativeSetLink(long nativeRowPtr, long columnKey, long value); protected native void nativeNullifyLink(long nativeRowPtr, long columnKey); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 31dfb24a43..150b24a311 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -21,18 +21,13 @@ import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; -import java.util.List; import java.util.Locale; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; -import io.realm.ImportFlag; import io.realm.RealmConfiguration; import io.realm.RealmModel; import io.realm.RealmObject; diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java index 65ee0467ab..8f3408f1f9 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java @@ -47,11 +47,11 @@ public class QueryDescriptor { //@VisibleForTesting public final static Set SORT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, - RealmFieldType.STRING, RealmFieldType.DATE))); + RealmFieldType.STRING, RealmFieldType.DATE, RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID))); //@VisibleForTesting public final static Set DISTINCT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( - RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE))); + RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE, RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID))); public static QueryDescriptor getInstanceForSort(FieldDescriptor.SchemaProxy proxy, Table table, String fieldDescription, Sort sortOrder) { return getInstanceForSort(proxy, table, new String[] {fieldDescription}, new Sort[] {sortOrder}); diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java index 9256c3814a..201efe62e5 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java @@ -15,11 +15,16 @@ */ package io.realm.internal.objectstore; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.io.Closeable; import java.util.Date; import java.util.List; import java.util.Set; +import javax.annotation.Nullable; + import io.realm.ImportFlag; import io.realm.MutableRealmInteger; import io.realm.RealmList; @@ -157,6 +162,20 @@ public void handleItem(long listPtr, MutableRealmInteger item) { } }; + private static ItemCallback decimal128ItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, Decimal128 item) { + nativeAddDecimal128ListItem(listPtr, item.getLow(), item.getHigh()); + } + }; + + private static ItemCallback objectIdItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, ObjectId item) { + nativeAddObjectIdListItem(listPtr, item.toString()); + } + }; + // If true, fields will not be updated if the same value would be written to it. private final boolean ignoreFieldsWithSameValue; @@ -171,109 +190,125 @@ public OsObjectBuilder(Table table, Set flags) { this.ignoreFieldsWithSameValue = flags.contains(ImportFlag.CHECK_SAME_VALUES_BEFORE_SET); } - public void addInteger(long columnIndex, Byte val) { + public void addInteger(long columnKey, @Nullable Byte val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddInteger(builderPtr, columnIndex, val); + nativeAddInteger(builderPtr, columnKey, val); } } - public void addInteger(long columnIndex, Short val) { + public void addInteger(long columnKey, @Nullable Short val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddInteger(builderPtr, columnIndex, val); + nativeAddInteger(builderPtr, columnKey, val); } } - public void addInteger(long columnIndex, Integer val) { + public void addInteger(long columnKey, @Nullable Integer val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddInteger(builderPtr, columnIndex, val); + nativeAddInteger(builderPtr, columnKey, val); } } - public void addInteger(long columnIndex, Long val) { + public void addInteger(long columnKey, @Nullable Long val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddInteger(builderPtr, columnIndex, val); + nativeAddInteger(builderPtr, columnKey, val); } } - public void addMutableRealmInteger(long columnIndex, MutableRealmInteger val) { + public void addMutableRealmInteger(long columnKey, @Nullable MutableRealmInteger val) { if (val == null || val.get() == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); + } else { + nativeAddInteger(builderPtr, columnKey, val.get()); + } + } + + public void addString(long columnKey, @Nullable String val) { + if (val == null) { + nativeAddNull(builderPtr, columnKey); + } else { + nativeAddString(builderPtr, columnKey, val); + } + } + + public void addFloat(long columnKey, @Nullable Float val) { + if (val == null) { + nativeAddNull(builderPtr, columnKey); } else { - nativeAddInteger(builderPtr, columnIndex, val.get()); + nativeAddFloat(builderPtr, columnKey, val); } } - public void addString(long columnIndex, String val) { + public void addDouble(long columnKey, @Nullable Double val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddString(builderPtr, columnIndex, val); + nativeAddDouble(builderPtr, columnKey, val); } } - public void addFloat(long columnIndex, Float val) { + public void addBoolean(long columnKey, @Nullable Boolean val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddFloat(builderPtr, columnIndex, val); + nativeAddBoolean(builderPtr, columnKey, val); } } - public void addDouble(long columnIndex, Double val) { + public void addDate(long columnKey, @Nullable Date val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddDouble(builderPtr, columnIndex, val); + nativeAddDate(builderPtr, columnKey, val.getTime()); } } - public void addBoolean(long columnIndex, Boolean val) { + public void addByteArray(long columnKey, @Nullable byte[] val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddBoolean(builderPtr, columnIndex, val); + nativeAddByteArray(builderPtr, columnKey, val); } } - public void addDate(long columnIndex, Date val) { + public void addDecimal128(long columnKey, @Nullable Decimal128 val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddDate(builderPtr, columnIndex, val.getTime()); + nativeAddDecimal128(builderPtr, columnKey, val.getLow(), val.getHigh()); } } - public void addByteArray(long columnIndex, byte[] val) { + public void addObjectId(long columnKey, @Nullable ObjectId val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { - nativeAddByteArray(builderPtr, columnIndex, val); + nativeAddObjectId(builderPtr, columnKey, val.toString()); } } - public void addNull(long columnIndex) { - nativeAddNull(builderPtr, columnIndex); + public void addNull(long columnKey) { + nativeAddNull(builderPtr, columnKey); } - public void addObject(long columnIndex, RealmModel val) { + public void addObject(long columnKey, @Nullable RealmModel val) { if (val == null) { - nativeAddNull(builderPtr, columnIndex); + nativeAddNull(builderPtr, columnKey); } else { RealmObjectProxy proxy = (RealmObjectProxy) val; UncheckedRow row = (UncheckedRow) proxy.realmGet$proxyState().getRow$realm(); - nativeAddObject(builderPtr, columnIndex, row.getNativePtr()); + nativeAddObject(builderPtr, columnKey, row.getNativePtr()); } } - private void addListItem(long builderPtr, long columnIndex, List list, ItemCallback itemCallback) { + private void addListItem(long builderPtr, long columnKey, @Nullable List list, ItemCallback itemCallback) { if (list != null) { long listPtr = nativeStartList(list.size()); for (int i = 0; i < list.size(); i++) { @@ -284,13 +319,13 @@ private void addListItem(long builderPtr, long columnIndex, List list, It itemCallback.handleItem(listPtr, item); } } - nativeStopList(builderPtr, columnIndex, listPtr); + nativeStopList(builderPtr, columnKey, listPtr); } else { - addEmptyList(columnIndex); + addEmptyList(columnKey); } } - public void addObjectList(long columnIndex, RealmList list) { + public void addObjectList(long columnKey, @Nullable RealmList list) { // Null objects references are not allowed. So we can optimize the JNI boundary by // sending all object references in one long[] array. if (list != null) { @@ -303,59 +338,67 @@ public void addObjectList(long columnIndex, RealmList rowPointers[i] = ((UncheckedRow) item.realmGet$proxyState().getRow$realm()).getNativePtr(); } } - nativeAddObjectList(builderPtr, columnIndex, rowPointers); + nativeAddObjectList(builderPtr, columnKey, rowPointers); } else { - nativeAddObjectList(builderPtr, columnIndex, new long[0]); + nativeAddObjectList(builderPtr, columnKey, new long[0]); } } - public void addStringList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, stringItemCallback); + public void addStringList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, stringItemCallback); + } + + public void addByteList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, byteItemCallback); + } + + public void addShortList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, shortItemCallback); } - public void addByteList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, byteItemCallback); + public void addIntegerList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, integerItemCallback); } - public void addShortList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, shortItemCallback); + public void addLongList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, longItemCallback); } - public void addIntegerList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, integerItemCallback); + public void addBooleanList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, booleanItemCallback); } - public void addLongList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, longItemCallback); + public void addFloatList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, floatItemCallback); } - public void addBooleanList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, booleanItemCallback); + public void addDoubleList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, doubleItemCallback); } - public void addFloatList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, floatItemCallback); + public void addDateList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, dateItemCallback); } - public void addDoubleList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, doubleItemCallback); + public void addByteArrayList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, byteArrayItemCallback); } - public void addDateList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, dateItemCallback); + public void addMutableRealmIntegerList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, mutableRealmIntegerItemCallback); } - public void addByteArrayList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, byteArrayItemCallback); + public void addDecimal128List(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, decimal128ItemCallback); } - public void addMutableRealmIntegerList(long columnIndex, RealmList list) { - addListItem(builderPtr, columnIndex, list, mutableRealmIntegerItemCallback); + public void addObjectIdList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, objectIdItemCallback); } - private void addEmptyList(long columnIndex) { + private void addEmptyList(long columnKey) { long listPtr = nativeStartList(0); - nativeStopList(builderPtr, columnIndex, listPtr); + nativeStopList(builderPtr, columnKey, listPtr); } /** @@ -415,20 +458,22 @@ private static native long nativeCreateOrUpdate(long sharedRealmPtr, boolean ignoreFieldsWithSameValue); // Add simple properties - private static native void nativeAddNull(long builderPtr, long columnIndex); - private static native void nativeAddInteger(long builderPtr, long columnIndex, long val); - private static native void nativeAddString(long builderPtr, long columnIndex, String val); - private static native void nativeAddFloat(long builderPtr, long columnIndex, float val); - private static native void nativeAddDouble(long builderPtr, long columnIndex, double val); - private static native void nativeAddBoolean(long builderPtr, long columnIndex, boolean val); - private static native void nativeAddByteArray(long builderPtr, long columnIndex, byte[] val); - private static native void nativeAddDate(long builderPtr, long columnIndex, long val); - private static native void nativeAddObject(long builderPtr, long columnIndex, long rowPtr); + private static native void nativeAddNull(long builderPtr, long columnKey); + private static native void nativeAddInteger(long builderPtr, long columnKey, long val); + private static native void nativeAddString(long builderPtr, long columnKey, String val); + private static native void nativeAddFloat(long builderPtr, long columnKey, float val); + private static native void nativeAddDouble(long builderPtr, long columnKey, double val); + private static native void nativeAddBoolean(long builderPtr, long columnKey, boolean val); + private static native void nativeAddByteArray(long builderPtr, long columnKey, byte[] val); + private static native void nativeAddDate(long builderPtr, long columnKey, long val); + private static native void nativeAddObject(long builderPtr, long columnKey, long rowPtr); + private static native void nativeAddDecimal128(long builderPtr, long columnKey, long low, long high); + private static native void nativeAddObjectId(long builderPtr, long columnKey, String data); // Methods for adding lists // Lists sent across JNI one element at a time private static native long nativeStartList(long size); - private static native void nativeStopList(long builderPtr, long columnIndex, long listPtr); + private static native void nativeStopList(long builderPtr, long columnKey, long listPtr); private static native void nativeAddNullListItem(long listPtr); private static native void nativeAddIntegerListItem(long listPtr, long value); private static native void nativeAddStringListItem(long listPtr, String val); @@ -437,6 +482,8 @@ private static native long nativeCreateOrUpdate(long sharedRealmPtr, private static native void nativeAddBooleanListItem(long listPtr, boolean val); private static native void nativeAddByteArrayListItem(long listPtr, byte[] val); private static native void nativeAddDateListItem(long listPtr, long val); + private static native void nativeAddDecimal128ListItem(long listPtr, long low, long high); + private static native void nativeAddObjectIdListItem(long listPtr, String data); private static native void nativeAddObjectListItem(long listPtr, long rowPtr); - private static native void nativeAddObjectList(long builderPtr, long columnIndex, long[] rowPtrs); + private static native void nativeAddObjectList(long builderPtr, long columnKey, long[] rowPtrs); } diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 52cee80d9b..49f00424df 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -20,6 +20,7 @@ import android.content.res.AssetManager; import android.os.Build; import android.os.Looper; + import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Assert; @@ -33,9 +34,11 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.math.BigDecimal; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.Date; import java.util.Locale; import java.util.Random; @@ -57,8 +60,8 @@ import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; -import io.realm.internal.OsResults; import io.realm.internal.OsObject; +import io.realm.internal.OsResults; import io.realm.internal.OsSharedRealm; import io.realm.internal.Table; import io.realm.internal.Util; @@ -70,13 +73,16 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + public class TestHelper { public static final int VERY_SHORT_WAIT_SECS = 1; public static final int SHORT_WAIT_SECS = 10; public static final int STANDARD_WAIT_SECS = 200; private static final Charset UTF_8 = Charset.forName("UTF-8"); - private static final Random RANDOM = new Random(); + private static final SecureRandom RANDOM = new SecureRandom(); public static class ExpectedCountCallback implements RealmCache.Callback { @@ -673,6 +679,9 @@ public static void populateTestRealmForNullTests(Realm testRealm) { Date[] dates = {new Date(0), null, new Date(10000)}; NullTypes[] nullTypesArray = new NullTypes[3]; + Decimal128[] decimals = {new Decimal128(BigDecimal.TEN), null, new Decimal128(BigDecimal.ONE)}; + ObjectId[] ids = {new ObjectId(TestHelper.generateObjectIdHexString(10)), null, new ObjectId(TestHelper.generateObjectIdHexString(1))}; + testRealm.beginTransaction(); for (int i = 0; i < 3; i++) { NullTypes nullTypes = new NullTypes(); @@ -719,6 +728,10 @@ public static void populateTestRealmForNullTests(Realm testRealm) { nullTypes.setFieldDateNotNull(dates[i]); } + nullTypes.setFieldDecimal128Null(decimals[i]); + + nullTypes.setFieldObjectIdNull(ids[i]); + nullTypesArray[i] = testRealm.copyToRealm(nullTypes); } nullTypesArray[0].setFieldObjectNull(nullTypesArray[0]); @@ -1304,4 +1317,25 @@ public static void waitForNetworkThreadExecutorToFinish() { public static T getNull() { return null; } + + public static String randomObjectIdHexString() { + char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E' , 'F'}; + + StringBuilder randomId = new StringBuilder(24); + for (int i = 0; i < 24; i++) { + randomId.append(hex[RANDOM.nextInt(16)]); + } + return randomId.toString(); + } + + public static String generateObjectIdHexString(int i) { + char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E' , 'F'}; + + StringBuilder randomId = new StringBuilder(24); + for (int j = 0; j < 24; j++) { + randomId.append(hex[(i + j) % 16]); + } + return randomId.toString(); + } + } diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java index c91fdbe2b3..e7a0779283 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java @@ -16,13 +16,18 @@ package io.realm.entities; +import java.math.BigDecimal; import java.util.Date; import io.realm.MutableRealmInteger; import io.realm.RealmList; import io.realm.RealmObject; +import io.realm.TestHelper; import io.realm.annotations.Required; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + public class AllTypes extends RealmObject { public static final String CLASS_NAME = "AllTypes"; @@ -34,6 +39,8 @@ public class AllTypes extends RealmObject { public static final String FIELD_DATE = "columnDate"; public static final String FIELD_BINARY = "columnBinary"; public static final String FIELD_MUTABLEREALMINTEGER = "columnMutableRealmInteger"; + public static final String FIELD_DECIMAL128 = "columnDecimal128"; + public static final String FIELD_OBJECT_ID = "columnObjectId"; public static final String FIELD_REALMOBJECT = "columnRealmObject"; public static final String FIELD_REALMLIST = "columnRealmList"; @@ -60,6 +67,10 @@ public class AllTypes extends RealmObject { private Date columnDate = new Date(0); @Required private byte[] columnBinary = new byte[0]; + @Required + private Decimal128 columnDecimal128 = new Decimal128(BigDecimal.ZERO); + @Required + private ObjectId columnObjectId = new ObjectId(TestHelper.randomObjectIdHexString()); private final MutableRealmInteger columnMutableRealmInteger = MutableRealmInteger.ofNull(); private Dog columnRealmObject; @@ -72,6 +83,8 @@ public class AllTypes extends RealmObject { private RealmList columnDoubleList; private RealmList columnFloatList; private RealmList columnDateList; + private RealmList columnDecimal128List; + private RealmList columnObjectIdList; public String getColumnString() { return columnString; @@ -208,4 +221,36 @@ public RealmList getColumnDateList() { public void setColumnDateList(RealmList columnDateList) { this.columnDateList = columnDateList; } + + public Decimal128 getColumnDecimal128() { + return columnDecimal128; + } + + public void setColumnDecimal128(Decimal128 columnDecimal128) { + this.columnDecimal128 = columnDecimal128; + } + + public ObjectId getColumnObjectId() { + return columnObjectId; + } + + public void setColumnObjectId(ObjectId columnObjectId) { + this.columnObjectId = columnObjectId; + } + + public RealmList getColumnDecimal128List() { + return columnDecimal128List; + } + + public void setColumnDecimal128List(RealmList columnDecimal128List) { + this.columnDecimal128List = columnDecimal128List; + } + + public RealmList getColumnObjectIdList() { + return columnObjectIdList; + } + + public void setColumnObjectIdList(RealmList columnObjectIdList) { + this.columnObjectIdList = columnObjectIdList; + } } diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java index 47842e8796..62daabede4 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java @@ -16,15 +16,20 @@ package io.realm.entities; +import java.math.BigDecimal; import java.util.Date; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.RealmResults; +import io.realm.TestHelper; import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + // Always follow below order and put comments like below to make NullTypes Related cases // 1 String // 2 Bytes @@ -61,6 +66,10 @@ public class NullTypes extends RealmObject { public static final String FIELD_DOUBLE_NULL = "fieldDoubleNull"; public static final String FIELD_DATE_NOT_NULL = "fieldDateNotNull"; public static final String FIELD_DATE_NULL = "fieldDateNull"; + public static final String FIELD_DECIMAL128_NULL = "fieldDecimal128Null"; + public static final String FIELD_DECIMAL128_NOT_NULL = "fieldDecimal128NotNull"; + public static final String FIELD_OBJECT_ID_NULL = "fieldObjectIdNull"; + public static final String FIELD_OBJECT_ID_NOT_NULL = "fieldObjectIdNotNull"; public static final String FIELD_OBJECT_NULL = "fieldObjectNull"; public static final String FIELD_LIST_NULL = "fieldListNull"; public static final String FIELD_LO_OBJECT = "objectParents"; @@ -86,6 +95,10 @@ public class NullTypes extends RealmObject { public static final String FIELD_FLOAT_LIST_NULL = "fieldFloatListNull"; public static final String FIELD_DATE_LIST_NOT_NULL = "fieldDateListNotNull"; public static final String FIELD_DATE_LIST_NULL = "fieldDateListNull"; + public static final String FIELD_DECIMAL128_LIST_NULL = "fieldDecimal128ListNull"; + public static final String FIELD_DECIMAL128_LIST_NOT_NULL = "fieldDecimal128ListNotNull"; + public static final String FIELD_OBJECT_ID_LIST_NULL = "fieldObjectIdListNull"; + public static final String FIELD_OBJECT_ID_LIST_NOT_NULL = "fieldObjectIdListNotNull"; @PrimaryKey private int id; @@ -130,6 +143,14 @@ public class NullTypes extends RealmObject { private Date fieldDateNotNull = new Date(0); private Date fieldDateNull; + @Required + private Decimal128 fieldDecimal128NotNull = new Decimal128(BigDecimal.ZERO); + private Decimal128 fieldDecimal128Null; + + @Required + private ObjectId fieldObjectIdNotNull = new ObjectId(TestHelper.generateObjectIdHexString(0)); + private ObjectId fieldObjectIdNull; + private NullTypes fieldObjectNull; // never nullable @@ -175,6 +196,14 @@ public class NullTypes extends RealmObject { private RealmList fieldDateListNotNull; private RealmList fieldDateListNull; + @Required + private RealmList fieldDecimal128ListNotNull; + private RealmList fieldDecimal128ListNull; + + @Required + private RealmList fieldObjectIdListNotNull; + private RealmList fieldObjectIdListNull; + // never nullable @LinkingObjects(FIELD_OBJECT_NULL) private final RealmResults objectParents = null; @@ -534,4 +563,68 @@ public RealmList getFieldDateListNull() { public void setFieldDateListNull(RealmList fieldDateListNull) { this.fieldDateListNull = fieldDateListNull; } + + public Decimal128 getFieldDecimal128NotNull() { + return fieldDecimal128NotNull; + } + + public void setFieldDecimal128NotNull(Decimal128 fieldDecimal128NotNull) { + this.fieldDecimal128NotNull = fieldDecimal128NotNull; + } + + public Decimal128 getFieldDecimal128Null() { + return fieldDecimal128Null; + } + + public void setFieldDecimal128Null(Decimal128 fieldDecimal128Null) { + this.fieldDecimal128Null = fieldDecimal128Null; + } + + public ObjectId getFieldObjectIdNotNull() { + return fieldObjectIdNotNull; + } + + public void setFieldObjectIdNotNull(ObjectId fieldObjectIdNotNull) { + this.fieldObjectIdNotNull = fieldObjectIdNotNull; + } + + public ObjectId getFieldObjectIdNull() { + return fieldObjectIdNull; + } + + public void setFieldObjectIdNull(ObjectId fieldObjectIdNull) { + this.fieldObjectIdNull = fieldObjectIdNull; + } + + public RealmList getFieldDecimal128ListNotNull() { + return fieldDecimal128ListNotNull; + } + + public void setFieldDecimal128ListNotNull(RealmList fieldDecimal128ListNotNull) { + this.fieldDecimal128ListNotNull = fieldDecimal128ListNotNull; + } + + public RealmList getFieldDecimal128ListNull() { + return fieldDecimal128ListNull; + } + + public void setFieldDecimal128ListNull(RealmList fieldDecimal128ListNull) { + this.fieldDecimal128ListNull = fieldDecimal128ListNull; + } + + public RealmList getFieldObjectIdListNotNull() { + return fieldObjectIdListNotNull; + } + + public void setFieldObjectIdListNotNull(RealmList fieldObjectIdListNotNull) { + this.fieldObjectIdListNotNull = fieldObjectIdListNotNull; + } + + public RealmList getFieldObjectIdListNull() { + return fieldObjectIdListNull; + } + + public void setFieldObjectIdListNull(RealmList fieldObjectIdListNull) { + this.fieldObjectIdListNull = fieldObjectIdListNull; + } } From 4cfc517478e6b357c190f3cb8b4d6c9bf449d191 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 22 Apr 2020 18:03:51 +0200 Subject: [PATCH 1496/2110] Fix kotlin extensions tests (#6811) --- realm/kotlin-extensions/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index c5676e58e6..626efd3300 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -78,8 +78,8 @@ dependencies { androidTestImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY_VERSION')}" kaptAndroidTest project(':realm-annotations-processor') - androidTestObjectServerImplementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY_VERSION')}" androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' From 2822e37841ebd2c4b346fb8c46afd7499c892620 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 26 Apr 2020 17:32:25 +0200 Subject: [PATCH 1497/2110] Refactor support for Sync to work with MongoDB Realm (#6788) --- dependencies.list | 6 +- .../io/realm/kotlin/KotlinSyncedRealmTests.kt | 21 +- .../io/realm/kotlin/SyncedRealmExtensions.kt | 5 +- realm/realm-library/build.gradle | 1 + .../java/io/realm/SchemaTests.java | 14 +- .../java/io/realm/SessionTests.java | 100 +- .../java/io/realm/SyncConfigurationTests.java | 543 --------- .../java/io/realm/SyncConfigurationTests.kt | 264 +++++ .../java/io/realm/SyncManagerTests.java | 316 ----- .../io/realm/SyncedRealmMigrationTests.java | 33 +- .../java/io/realm/SyncedRealmTests.java | 30 +- ...rustManagerCertificateValidationTests.java | 28 +- ...uthProviderTests.kt => ApiKeyAuthTests.kt} | 19 +- ...iderTests.kt => EmailPasswordAuthTests.kt} | 97 +- .../kotlin/io/realm/KotlinSyncedRealmTests.kt | 188 +++ .../kotlin/io/realm/ProgressListenerTests.kt | 359 ++++++ .../io/realm/RealmAppConfigurationTests.kt | 155 +++ .../kotlin/io/realm/RealmAppExt.kt | 20 +- .../kotlin/io/realm/RealmAppTests.kt | 65 +- .../kotlin/io/realm/RealmCredentialsTests.kt | 129 +- .../kotlin/io/realm/RealmUserTests.kt | 40 +- .../io/realm/entities/DefaultSyncSchema.kt | 27 + .../kotlin/io/realm/entities/SyncColor.kt | 31 + .../kotlin/io/realm/entities/SyncDog.kt | 37 + .../kotlin/io/realm/entities/SyncPerson.kt | 40 + .../transport/OkHttpNetworkTransportTests.kt | 14 + .../transport/OsJavaNetworkTransportTests.kt | 10 +- .../kotlin/io/realm/util/KotlinTestUtils.kt | 20 + .../realm-library/src/main/cpp/CMakeLists.txt | 13 +- ...thProvider.cpp => io_realm_ApiKeyAuth.cpp} | 30 +- ...der.cpp => io_realm_EmailPasswordAuth.cpp} | 28 +- .../src/main/cpp/io_realm_RealmApp.cpp | 99 +- .../src/main/cpp/io_realm_RealmSync.cpp | 75 ++ .../src/main/cpp/io_realm_RealmUser.cpp | 6 +- .../src/main/cpp/io_realm_SyncManager.cpp | 154 --- .../src/main/cpp/io_realm_SyncSession.cpp | 64 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 53 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../io/realm/internal/ObjectServerFacade.java | 6 +- .../java/io/realm/internal/OsRealmConfig.java | 14 +- .../src/main/java/io/realm/internal/Util.java | 7 + ...piKeyAuthProvider.java => ApiKeyAuth.java} | 20 +- .../java/io/realm/AuthenticationListener.java | 8 +- .../java/io/realm/ClientResyncMode.java | 2 +- ...thProvider.java => EmailPasswordAuth.java} | 18 +- .../objectServer/java/io/realm/ErrorCode.java | 1 + .../java/io/realm/ObjectServer.java | 94 -- .../objectServer/java/io/realm/RealmApp.java | 215 +++- .../java/io/realm/RealmAppConfiguration.java | 217 +++- .../java/io/realm/RealmCredentials.java | 2 +- .../objectServer/java/io/realm/RealmSync.java | 432 +++++++ .../objectServer/java/io/realm/RealmUser.java | 54 +- .../java/io/realm/SyncConfiguration.java | 394 ++++--- .../java/io/realm/SyncManager.java | 754 ------------ .../java/io/realm/SyncSession.java | 273 +---- .../objectServer/java/io/realm/SyncUser.java | 1050 ----------------- .../java/io/realm/SyncUserInfo.java | 100 -- .../internal/SyncObjectServerFacade.java | 101 +- .../network/NetworkStateReceiver.java | 4 +- .../network/OkHttpNetworkTransport.java | 13 - .../objectstore/OsJavaNetworkTransport.java | 44 +- .../java/io/realm/SyncTestUtils.java | 195 --- .../java/io/realm/SyncTestUtils.kt | 145 +++ .../java}/io/realm/TestRealmApp.kt | 2 +- .../realm/TestSyncConfigurationFactory.java | 7 +- .../realm/objectserver/utils/UserFactory.java | 48 - .../testUtils/java/io/realm/TestHelper.java | 6 +- .../java/io/realm/rule/RunInLooperThread.java | 2 - .../app_config/auth_providers/anon-user.json | 2 +- .../app_config/auth_providers/api-key.json | 2 +- .../auth_providers/custom-function.json | 2 +- .../auth_providers/local-userpass.json | 10 +- .../app_config/functions/authFunc/config.json | 2 +- .../app_config/functions/authFunc/source.js | 2 +- .../functions/confirmFunc/config.json | 6 + .../functions/confirmFunc/source.js | 49 + .../functions/resetFunc/config.json | 2 +- .../app_config/functions/resetFunc/source.js | 8 +- .../sync_test_server/app_config/secrets.json | 3 + .../app_config/services/BackingDB/config.json | 22 + .../BackingDB/rules/test_data.SyncDog.json | 34 + .../BackingDB/rules/test_data.SyncPerson.json | 46 + .../services/integration_tests/config.json | 10 - tools/sync_test_server/app_config/stitch.json | 4 +- tools/sync_test_server/setup_mongodb_realm.sh | 13 +- tools/sync_test_server/start_server.sh | 2 +- 86 files changed, 3231 insertions(+), 4352 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java create mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.kt delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{ApiKeyAuthProviderTests.kt => ApiKeyAuthTests.kt} (97%) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{EmailPasswordAuthProviderTests.kt => EmailPasswordAuthTests.kt} (86%) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncColor.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncDog.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncPerson.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt rename realm/realm-library/src/main/cpp/{io_realm_ApiKeyAuthProvider.cpp => io_realm_ApiKeyAuth.cpp} (87%) rename realm/realm-library/src/main/cpp/{io_realm_EmailPasswordAuthProvider.cpp => io_realm_EmailPasswordAuth.cpp} (74%) create mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmSync.cpp delete mode 100644 realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp rename realm/realm-library/src/objectServer/java/io/realm/{ApiKeyAuthProvider.java => ApiKeyAuth.java} (94%) rename realm/realm-library/src/objectServer/java/io/realm/{EmailPasswordAuthProvider.java => EmailPasswordAuth.java} (95%) delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmSync.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/SyncManager.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/SyncUser.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java delete mode 100644 realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java create mode 100644 realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt rename realm/realm-library/src/{androidTestObjectServer/kotlin => syncTestUtils/java}/io/realm/TestRealmApp.kt (95%) mode change 100755 => 100644 tools/sync_test_server/app_config/auth_providers/anon-user.json mode change 100755 => 100644 tools/sync_test_server/app_config/auth_providers/api-key.json mode change 100755 => 100644 tools/sync_test_server/app_config/auth_providers/custom-function.json mode change 100755 => 100644 tools/sync_test_server/app_config/auth_providers/local-userpass.json mode change 100755 => 100644 tools/sync_test_server/app_config/functions/authFunc/config.json mode change 100755 => 100644 tools/sync_test_server/app_config/functions/authFunc/source.js create mode 100644 tools/sync_test_server/app_config/functions/confirmFunc/config.json create mode 100644 tools/sync_test_server/app_config/functions/confirmFunc/source.js mode change 100755 => 100644 tools/sync_test_server/app_config/functions/resetFunc/config.json mode change 100755 => 100644 tools/sync_test_server/app_config/functions/resetFunc/source.js create mode 100644 tools/sync_test_server/app_config/secrets.json create mode 100644 tools/sync_test_server/app_config/services/BackingDB/config.json create mode 100644 tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json create mode 100644 tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json delete mode 100755 tools/sync_test_server/app_config/services/integration_tests/config.json mode change 100755 => 100644 tools/sync_test_server/app_config/stitch.json diff --git a/dependencies.list b/dependencies.list index 9af82988d4..d93bff39f7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.7 -REALM_SYNC_SHA256=f6350407097c9f95eba579420ba387b41d9def6095d2c54e1032597c83403e50 +REALM_SYNC_VERSION=10.0.0-alpha.8 +REALM_SYNC_SHA256=2cdccdd43f1cb0c0d7297e9616824efa34be62deefd311d722ecee88d8a6e44e # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. @@ -9,7 +9,7 @@ REALM_OBJECT_SERVER_VERSION=3.28.2 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-03-25 +MONGODB_REALM_SERVER_VERSION=2020-04-26 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt index 6ed2c9c9d2..e0dd96dee5 100644 --- a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt +++ b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt @@ -3,7 +3,6 @@ package io.realm.kotlin import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.* -import io.realm.objectserver.utils.Constants import org.junit.* import org.junit.Assert.assertEquals import org.junit.Assert.fail @@ -15,25 +14,33 @@ class KotlinSyncedRealmTests { @get:Rule val configFactory = TestSyncConfigurationFactory() - + private lateinit var app: RealmApp private lateinit var realm: Realm @Before fun setUp() { - Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - val user = SyncTestUtils.createTestUser() - realm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM).build()) + // FIXME +// Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) +// app = RealmApp("foo") +// val user = SyncTestUtils.createTestUser(app) +// realm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(user).build()) } @After fun tearDown() { - realm.close() + // FIXME +// if (this::realm.isInitialized) { +// realm.close() +// } +// if (this::app.isInitialized) { +// RealmApp.CREATED = false +// } } @Ignore("FIXME") @Test fun syncSession() { - assertEquals(SyncManager.getSession(realm.configuration as SyncConfiguration), realm.syncSession) + assertEquals(app.sync.getSession(realm.configuration as SyncConfiguration), realm.syncSession) } @Ignore("FIXME") diff --git a/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt b/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt index ad3d753760..b50a3967ab 100644 --- a/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt +++ b/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt @@ -17,7 +17,6 @@ package io.realm.kotlin import io.realm.Realm import io.realm.SyncConfiguration -import io.realm.SyncManager import io.realm.SyncSession @@ -32,5 +31,7 @@ val Realm.syncSession: SyncSession if (!(this.configuration is SyncConfiguration)) { throw IllegalStateException("This method is only available on synchronized Realms") } - return SyncManager.getSession(this.configuration as SyncConfiguration) + + val syncConfig = this.configuration as SyncConfiguration + return syncConfig.user.app.sync.getSession(syncConfig) } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index b83fff4241..c23c4858dc 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -220,6 +220,7 @@ dependencies { kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 objectServerImplementation 'com.squareup.okhttp3:okhttp:3.12.0' // Going above this requires minSDK 21 kaptAndroidTest project(':realm-annotations-processor') + androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' androidTestImplementation 'io.reactivex.rxjava2:rxandroid:2.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.1' diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java index 3a8728a2f9..78be86829e 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java @@ -19,6 +19,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -42,11 +43,20 @@ public class SchemaTests { public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); private SyncConfiguration config; + private TestRealmApp app; @Before public void setUp() { - SyncUser user = SyncTestUtils.createTestUser(); - config = configFactory.createSyncConfigurationBuilder(user, "realm://objectserver.realm.io/~/default").build(); + app = new TestRealmApp(); + RealmUser user = SyncTestUtils.createTestUser(app); + config = configFactory.createSyncConfigurationBuilder(user).build(); + } + + @After + public void tearDown() { + if (app != null) { + RealmAppExtKt.close(app); + } } @Test diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java index babba3ddd0..eaeb1ea0f7 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java @@ -17,10 +17,10 @@ package io.realm; import androidx.test.annotation.UiThreadTest; -import androidx.test.rule.UiThreadTestRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.CoreMatchers; +import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -38,7 +38,6 @@ import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; -import static io.realm.SyncTestUtils.createTestUser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -46,14 +45,12 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -@Ignore("FIXME: RealmApp refactor") @RunWith(AndroidJUnit4.class) public class SessionTests { - private static String REALM_URI = "realm://objectserver.realm.io/~/default"; - private SyncConfiguration configuration; - private SyncUser user; + private TestRealmApp app; + private RealmUser user; @Rule public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); @@ -61,26 +58,31 @@ public class SessionTests { @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); - @Rule - public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); - @Before public void setUp() { - user = createTestUser(); - configuration = user.createConfiguration(REALM_URI).build(); + app = new TestRealmApp(); + user = SyncTestUtils.createTestUser(app); + configuration = SyncConfiguration.defaultConfig(user, "default"); + } + + @After + public void tearDown() { + if (app != null) { + RealmAppExtKt.close(app); + } } @Test public void get_syncValues() { SyncSession session = new SyncSession(configuration); - assertEquals("realm://objectserver.realm.io/" + user.getIdentity() + "/default", session.getServerUrl().toString()); + assertEquals("ws://127.0.0.1:9090/", session.getServerUrl().toString()); assertEquals(user, session.getUser()); assertEquals(configuration, session.getConfiguration()); } @Test public void addDownloadProgressListener_nullThrows() { - SyncSession session = SyncManager.getOrCreateSession(configuration, null); + SyncSession session = app.getSync().getOrCreateSession(configuration); try { session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, null); fail(); @@ -90,7 +92,7 @@ public void addDownloadProgressListener_nullThrows() { @Test public void addUploadProgressListener_nullThrows() { - SyncSession session = SyncManager.getOrCreateSession(configuration, null); + SyncSession session = app.getSync().getOrCreateSession(configuration); try { session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, null); fail(); @@ -101,7 +103,7 @@ public void addUploadProgressListener_nullThrows() { @Test public void removeProgressListener() { Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getOrCreateSession(configuration, null); + SyncSession session = app.getSync().getOrCreateSession(configuration); ProgressListener[] listeners = new ProgressListener[] { null, progress -> { @@ -123,10 +125,11 @@ public void removeProgressListener() { // Check that a Client Reset is correctly reported. @Test @RunTestInLooperThread + @Ignore("FIXME: Figure out how to fix this") public void errorHandler_clientResetReported() { - SyncUser user = createTestUser(); + RealmUser user = SyncTestUtils.createTestUser(app); String url = "realm://objectserver.realm.io/default"; - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) .errorHandler((session, error) -> { if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { @@ -149,16 +152,17 @@ public void errorHandler_clientResetReported() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); + RealmSync syncService = user.getApp().getSync(); + syncService.simulateClientReset(syncService.getSession((config))); } // Check that we can manually execute the Client Reset. @Test @RunTestInLooperThread + @Ignore("FIXME: Figure out how to fix this") public void errorHandler_manualExecuteClientReset() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + RealmUser user = SyncTestUtils.createTestUser(app); + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) .errorHandler((session, error) -> { if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { @@ -188,16 +192,16 @@ public void errorHandler_manualExecuteClientReset() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); + user.getApp().getSync().simulateClientReset(app.getSync().getSession(configuration)); } // Check that we can use the backup SyncConfiguration to open the Realm. @Test @RunTestInLooperThread + @Ignore("FIXME: Figure out how to fix this") public void errorHandler_useBackupSyncConfigurationForClientReset() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + RealmUser user = SyncTestUtils.createTestUser(app); + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) .schema(StringOnly.class) .errorHandler((session, error) -> { @@ -246,7 +250,7 @@ public void errorHandler_useBackupSyncConfigurationForClientReset() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); + user.getApp().getSync().simulateClientReset(app.getSync().getSession(configuration)); } // Check that we can open the backup file without using the provided SyncConfiguration, @@ -254,10 +258,10 @@ public void errorHandler_useBackupSyncConfigurationForClientReset() { // persisted the location of the file) @Test @RunTestInLooperThread + @Ignore("FIXME: Figure out how to fix this") public void errorHandler_useBackupSyncConfigurationAfterClientReset() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + RealmUser user = SyncTestUtils.createTestUser(app); + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) .errorHandler((session, error) -> { if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { @@ -330,17 +334,17 @@ public void errorHandler_useBackupSyncConfigurationAfterClientReset() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); + user.getApp().getSync().simulateClientReset(app.getSync().getSession(configuration)); } // make sure the backup file Realm is encrypted with the same key as the original synced Realm. @Test @RunTestInLooperThread + @Ignore("FIXME: Figure out how to fix this") public void errorHandler_useClientResetEncrypted() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; + RealmUser user = SyncTestUtils.createTestUser(app); final byte[] randomKey = TestHelper.getRandomKey(); - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) + final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) .encryptionKey(randomKey) .modules(new StringOnlyModule()) @@ -392,7 +396,7 @@ public void errorHandler_useClientResetEncrypted() { looperThread.addTestRealm(realm); // Trigger error - SyncManager.simulateClientReset(SyncManager.getOrCreateSession(config, null)); + user.getApp().getSync().simulateClientReset(app.getSync().getSession(configuration)); } @Test @@ -400,7 +404,7 @@ public void errorHandler_useClientResetEncrypted() { public void uploadAllLocalChanges_throwsOnUiThread() throws InterruptedException { Realm realm = Realm.getInstance(configuration); try { - SyncManager.getOrCreateSession(configuration, null).uploadAllLocalChanges(); + app.getSync().getOrCreateSession(configuration).uploadAllLocalChanges(); fail("Should throw an IllegalStateException on Ui Thread"); } catch (IllegalStateException ignored) { } finally { @@ -413,7 +417,7 @@ public void uploadAllLocalChanges_throwsOnUiThread() throws InterruptedException public void uploadAllLocalChanges_withTimeout_throwsOnUiThread() throws InterruptedException { Realm realm = Realm.getInstance(configuration); try { - SyncManager.getOrCreateSession(configuration, null).uploadAllLocalChanges(30, TimeUnit.SECONDS); + app.getSync().getOrCreateSession(configuration).uploadAllLocalChanges(30, TimeUnit.SECONDS); fail("Should throw an IllegalStateException on Ui Thread"); } catch (IllegalStateException ignored) { } finally { @@ -424,7 +428,7 @@ public void uploadAllLocalChanges_withTimeout_throwsOnUiThread() throws Interrup @Test public void uploadAllLocalChanges_withTimeout_invalidParametersThrows() throws InterruptedException { Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getOrCreateSession(configuration, null); + SyncSession session = app.getSync().getOrCreateSession(configuration); try { try { session.uploadAllLocalChanges(-1, TimeUnit.SECONDS); @@ -446,7 +450,7 @@ public void uploadAllLocalChanges_withTimeout_invalidParametersThrows() throws I @Test public void uploadAllLocalChanges_returnFalseWhenTimedOut() throws InterruptedException { Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getOrCreateSession(configuration, null); + SyncSession session = app.getSync().getSession(configuration); try { assertFalse(session.uploadAllLocalChanges(100, TimeUnit.MILLISECONDS)); } finally { @@ -459,7 +463,7 @@ public void uploadAllLocalChanges_returnFalseWhenTimedOut() throws InterruptedEx public void downloadAllServerChanges_throwsOnUiThread() throws InterruptedException { Realm realm = Realm.getInstance(configuration); try { - SyncManager.getOrCreateSession(configuration, null).downloadAllServerChanges(); + app.getSync().getSession(configuration).downloadAllServerChanges(); fail("Should throw an IllegalStateException on Ui Thread"); } catch (IllegalStateException ignored) { } finally { @@ -472,7 +476,7 @@ public void downloadAllServerChanges_throwsOnUiThread() throws InterruptedExcept public void downloadAllServerChanges_withTimeout_throwsOnUiThread() throws InterruptedException { Realm realm = Realm.getInstance(configuration); try { - SyncManager.getOrCreateSession(configuration, null).downloadAllServerChanges(30, TimeUnit.SECONDS); + app.getSync().getSession(configuration).downloadAllServerChanges(30, TimeUnit.SECONDS); fail("Should throw an IllegalStateException on Ui Thread"); } catch (IllegalStateException ignored) { } finally { @@ -484,7 +488,7 @@ public void downloadAllServerChanges_withTimeout_throwsOnUiThread() throws Inter @Test public void downloadAllServerChanges_withTimeout_invalidParametersThrows() throws InterruptedException { Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getOrCreateSession(configuration, null); + SyncSession session = app.getSync().getSession(configuration); try { try { session.downloadAllServerChanges(-1, TimeUnit.SECONDS); @@ -506,7 +510,7 @@ public void downloadAllServerChanges_withTimeout_invalidParametersThrows() throw @Test public void downloadAllServerChanges_returnFalseWhenTimedOut() throws InterruptedException { Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getOrCreateSession(configuration, null); + SyncSession session = app.getSync().getSession(configuration); try { assertFalse(session.downloadAllServerChanges(100, TimeUnit.MILLISECONDS)); } finally { @@ -518,7 +522,7 @@ public void downloadAllServerChanges_returnFalseWhenTimedOut() throws Interrupte @UiThreadTest public void unrecognizedErrorCode_errorHandler() { AtomicBoolean errorHandlerCalled = new AtomicBoolean(false); - configuration = configFactory.createSyncConfigurationBuilder(user, REALM_URI) + configuration = configFactory.createSyncConfigurationBuilder(user) .errorHandler((session, error) -> { errorHandlerCalled.set(true); assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); @@ -527,7 +531,7 @@ public void unrecognizedErrorCode_errorHandler() { }) .build(); Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getOrCreateSession(configuration, null); + SyncSession session = app.getSync().getSession(configuration); TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); RealmLog.add(testLogger); @@ -544,13 +548,13 @@ public void unrecognizedErrorCode_errorHandler() { @Test public void getSessionThrowsOnNonExistingSession() { Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getSession(configuration); + SyncSession session = app.getSync().getSession(configuration); assertEquals(configuration, session.getConfiguration()); // Closing the Realm should remove the session realm.close(); try { - SyncManager.getSession(configuration); + app.getSync().getSession(configuration); fail("getSession should throw an ISE"); } catch (IllegalStateException expected) { assertThat(expected.getMessage(), CoreMatchers.containsString( @@ -561,7 +565,7 @@ public void getSessionThrowsOnNonExistingSession() { @Test public void isConnected_falseForInvalidUser() { Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getSession(configuration); + SyncSession session = app.getSync().getSession(configuration); try { assertFalse(session.isConnected()); } finally { @@ -572,7 +576,7 @@ public void isConnected_falseForInvalidUser() { @Test public void stop_doesNotThrowIfCalledWhenRealmIsClosed() { Realm realm = Realm.getInstance(configuration); - SyncSession session = SyncManager.getSession(configuration); + SyncSession session = app.getSync().getSession(configuration); realm.close(); session.stop(); } diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java deleted file mode 100644 index 8ec9b2e4af..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.util.HashMap; -import java.util.Map; - -import io.realm.entities.StringOnly; -import io.realm.entities.StringOnlyModule; -import io.realm.rule.RunInLooperThread; - -import static io.realm.SyncTestUtils.createNamedTestUser; -import static io.realm.SyncTestUtils.createTestUser; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@Ignore("FIXME: RealmApp Refactor") -@RunWith(AndroidJUnit4.class) -public class SyncConfigurationTests { - @Rule - public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - - @Rule - public final RunInLooperThread looperThread = new RunInLooperThread(); - - @Rule - public final TemporaryFolder tempFolder = new TemporaryFolder(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Before - public void setUp() { - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - } - - @After - public void tearDown() { -// FIXME -// UserStore userStore = SyncManager.getUserStore(); -// for (SyncUser syncUser : userStore.allUsers()) { -// userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); -// } - } - - @Test - public void user_invalidUserThrows() { - SyncUser user = createTestUser(0); // Create user that has expired credentials - try { - user.createConfiguration("realm://ros.realm.io/default"); - } catch (IllegalStateException ignore) { - } - } - - @Test - public void serverUrl_setsFolderAndFileName() { - SyncUser user = createTestUser(); - String identity = user.getIdentity(); - String[][] validUrls = { - // , , - { "realm://objectserver.realm.io/~/default", "realm-object-server/" + identity + "/" + identity, "default" }, - { "realm://objectserver.realm.io/~/sub/default", "realm-object-server/" + identity + "/" + identity + "/sub", "default" } - }; - - for (String[] validUrl : validUrls) { - String serverUrl = validUrl[0]; - String expectedFolder = validUrl[1]; - String expectedFileName = validUrl[2]; - - SyncConfiguration config = user.createConfiguration(serverUrl).build(); - - assertEquals(new File(InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), expectedFolder), config.getRealmDirectory()); - assertEquals(expectedFileName, config.getRealmFileName()); - } - } - - @Test - public void serverUrl_flexibleInput() { - // Check that the serverUrl accept a wide range of input - Object[][] fuzzyInput = { - // Only path -> Use auth server as basis for server url, but ignore port if set - { createTestUser("http://ros.realm.io/auth"), "/~/default", "realm://ros.realm.io/~/default" }, - { createTestUser("http://ros.realm.io:7777/auth"), "/~/default", "realm://ros.realm.io/~/default" }, - { createTestUser("https://ros.realm.io/auth"), "/~/default", "realms://ros.realm.io/~/default" }, - { createTestUser("https://127.0.0.1/auth"), "/~/default", "realms://127.0.0.1/~/default" }, - - { createTestUser("http://ros.realm.io/auth"), "~/default", "realm://ros.realm.io/~/default" }, - { createTestUser("http://ros.realm.io:7777/auth"), "~/default", "realm://ros.realm.io/~/default" }, - { createTestUser("https://ros.realm.io/auth"), "~/default", "realms://ros.realm.io/~/default" }, - { createTestUser("https://127.0.0.1/auth"), "~/default", "realms://127.0.0.1/~/default" }, - - // Check that the same name used for server and name doesn't crash - { createTestUser("http://ros.realm.io/auth"), "~/ros.realm.io", "realm://ros.realm.io/~/ros.realm.io" }, - - // Forgot schema -> Use the one from the auth url - { createTestUser("http://ros.realm.io/auth"), "ros.realm.io/~/default", "realm://ros.realm.io/~/default" }, - { createTestUser("http://ros.realm.io/auth"), "//ros.realm.io/~/default", "realm://ros.realm.io/~/default" }, - { createTestUser("https://ros.realm.io/auth"), "ros.realm.io/~/default", "realms://ros.realm.io/~/default" }, - { createTestUser("https://ros.realm.io/auth"), "//ros.realm.io/~/default", "realms://ros.realm.io/~/default" }, - - // Automatically replace http|https with realm|realms - { createTestUser(), "http://ros.realm.io/~/default", "realm://ros.realm.io/~/default" }, - { createTestUser(), "https://ros.realm.io/~/default", "realms://ros.realm.io/~/default" } - }; - - for (Object[] test : fuzzyInput) { - SyncUser user = (SyncUser) test[0]; - String serverUrlInput = (String) test[1]; - String resolvedServerUrl = ((String) test[2]).replace("~", user.getIdentity()); - - SyncConfiguration config = user.createConfiguration(serverUrlInput).build(); - - assertEquals(String.format("Input '%s' did not resolve correctly.", serverUrlInput), - resolvedServerUrl, config.getServerUrl().toString()); - } - } - - @Test - public void serverUrl_invalidUrlThrows() { - String[] invalidUrls = { - null, -// TODO Should these two fail? -// "objectserver.realm.io/~/default", // Missing protocol. TODO Should we just default to one? -// "/~/default", // Missing server - "realm://objectserver.realm.io/~/default.realm", // Ending with .realm - "realm://objectserver.realm.io/~/default.realm.lock", // Ending with .realm.lock - "realm://objectserver.realm.io/~/default.realm.management", // Ending with .realm.management - "realm://objectserver.realm.io/<~>/default.realm", // Invalid chars <> - "realm://objectserver.realm.io/~/default.realm/", // Ending with / - "realm://objectserver.realm.io/~/Αθήνα", // Non-ascii - "realm://objectserver.realm.io/~/foo/../bar", // .. is not allowed - "realm://objectserver.realm.io/~/foo/./bar", // . is not allowed - }; - - for (String invalidUrl : invalidUrls) { - try { - createTestUser().createConfiguration(invalidUrl); - fail(invalidUrl + " should have failed."); - } catch (IllegalArgumentException ignore) { - } - } - } - - private String makeServerUrl(int len) { - StringBuilder builder = new StringBuilder("realm://objectserver.realm.io/~/"); - for (int i = 0; i < len; i++) { - builder.append('A'); - } - return builder.toString(); - } - - @Test - public void serverUrl_length() { - int[] lengths = {1, SyncConfiguration.MAX_FILE_NAME_LENGTH - 1, - SyncConfiguration.MAX_FILE_NAME_LENGTH, SyncConfiguration.MAX_FILE_NAME_LENGTH + 1, 1000}; - - for (int len : lengths) { - SyncConfiguration config = createTestUser().createConfiguration(makeServerUrl(len)).build(); - assertTrue("Length: " + len, config.getRealmFileName().length() <= SyncConfiguration.MAX_FILE_NAME_LENGTH); - assertTrue("Length: " + len, config.getPath().length() <= SyncConfiguration.MAX_FULL_PATH_LENGTH); - } - } - - @Test - public void serverUrl_invalidChars() { - SyncConfiguration.Builder builder = createTestUser().createConfiguration("realm://objectserver.realm.io/~/?"); - SyncConfiguration config = builder.build(); - assertFalse(config.getRealmFileName().contains("?")); - } - - @Test - public void serverUrl_port() { - Map urlPort = new HashMap(); - urlPort.put("realm://objectserver.realm.io/~/default", -1); // default port - handled by sync client - urlPort.put("realms://objectserver.realm.io/~/default", -1); // default port - handled by sync client - urlPort.put("realm://objectserver.realm.io:8080/~/default", 8080); - urlPort.put("realms://objectserver.realm.io:2443/~/default", 2443); - - for (String url : urlPort.keySet()) { - SyncConfiguration config = createTestUser().createConfiguration(url).build(); - assertEquals(urlPort.get(url).intValue(), config.getServerUrl().getPort()); - } - } - - @Test - public void errorHandler() { - SyncConfiguration.Builder builder = createTestUser().createConfiguration("realm://objectserver.realm.io/default"); - SyncSession.ErrorHandler errorHandler = new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - - } - }; - SyncConfiguration config = builder.errorHandler(errorHandler).build(); - assertEquals(errorHandler, config.getErrorHandler()); - } - - @Test - public void errorHandler_fromSyncManager() { - // Set default error handler - SyncSession.ErrorHandler errorHandler = new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - - } - }; - SyncManager.setDefaultSessionErrorHandler(errorHandler); - - // Create configuration using the default handler - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = user.createConfiguration(url).build(); - assertEquals(errorHandler, config.getErrorHandler()); - SyncManager.setDefaultSessionErrorHandler(null); - } - - - @Test - public void errorHandler_nullThrows() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration.Builder builder = user.createConfiguration(url); - - try { - builder.errorHandler(null); - } catch (IllegalArgumentException ignore) { - } - } - - @Test - public void equals() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = user.createConfiguration(url) - .build(); - assertTrue(config.equals(config)); - } - - @Test - public void equals_same() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config1 = user.createConfiguration(url).build(); - SyncConfiguration config2 = user.createConfiguration(url).build(); - - assertTrue(config1.equals(config2)); - } - - @Test - public void equals_not() { - SyncUser user = createTestUser(); - String url1 = "realm://objectserver.realm.io/default1"; - String url2 = "realm://objectserver.realm.io/default2"; - SyncConfiguration config1 = user.createConfiguration(url1).build(); - SyncConfiguration config2 = user.createConfiguration(url2).build(); - assertFalse(config1.equals(config2)); - } - - @Test - public void hashCode_equal() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = user.createConfiguration(url) - .build(); - - assertEquals(config.hashCode(), config.hashCode()); - } - - @Test - public void hashCode_notEquals() { - SyncUser user = createTestUser(); - String url1 = "realm://objectserver.realm.io/default1"; - String url2 = "realm://objectserver.realm.io/default2"; - SyncConfiguration config1 = user.createConfiguration(url1).build(); - SyncConfiguration config2 = user.createConfiguration(url2).build(); - assertNotEquals(config1.hashCode(), config2.hashCode()); - } - - @Test - public void get_syncSpecificValues() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = user.createConfiguration(url).build(); - assertTrue(user.equals(config.getUser())); - assertEquals("realm://objectserver.realm.io/default", config.getServerUrl().toString()); - assertFalse(config.shouldDeleteRealmOnLogout()); - assertTrue(config.isSyncConfiguration()); - } - - @Test - public void encryption() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = user.createConfiguration(url) - .encryptionKey(TestHelper.getRandomKey()) - .build(); - assertNotNull(config.getEncryptionKey()); - } - - @Test(expected = IllegalArgumentException.class) - public void encryption_invalid_null() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - - user.createConfiguration(url).encryptionKey(null); - } - - @Test(expected = IllegalArgumentException.class) - public void encryption_invalid_wrong_length() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - - user.createConfiguration(url).encryptionKey(new byte[]{1, 2, 3}); - } - - @Test(expected = IllegalArgumentException.class) - public void directory_null() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - user.createConfiguration(url).directory(null); - } - - @Test(expected = IllegalArgumentException.class) - public void directory_writeProtectedDir() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - - File dir = new File("/"); - user.createConfiguration(url).directory(dir); - } - - @Test - public void directory_dirIsAFile() throws IOException { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - - File dir = configFactory.getRoot(); - File file = new File(dir, "dummyfile"); - assertTrue(file.createNewFile()); - thrown.expect(IllegalArgumentException.class); - user.createConfiguration(url).directory(file); - file.delete(); // clean up - } - - @Ignore("deleteRealmOnLogout is not supported yet") - @Test - public void deleteOnLogout() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - - SyncConfiguration config = user.createConfiguration(url) - //.deleteRealmOnLogout() - .build(); - assertTrue(config.shouldDeleteRealmOnLogout()); - } - - @Test - public void initialData() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, url) - .schema(StringOnly.class) - .initialData(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - StringOnly stringOnly = realm.createObject(StringOnly.class); - stringOnly.setChars("TEST 42"); - } - }) - .build(); - - assertNotNull(config.getInitialDataTransaction()); - - // open the first time - initialData must be triggered - Realm realm1 = Realm.getInstance(config); - RealmResults results = realm1.where(StringOnly.class).findAll(); - assertEquals(1, results.size()); - assertEquals("TEST 42", results.first().getChars()); - realm1.close(); - - // open the second time - initialData must not be triggered - Realm realm2 = Realm.getInstance(config); - assertEquals(1, realm2.where(StringOnly.class).count()); - realm2.close(); - } - - @Test - public void defaultRxFactory() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = user.createConfiguration(url).build(); - - assertNotNull(config.getRxFactory()); - } - - @Test - public void toString_nonEmpty() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = user.createConfiguration(url).build(); - - String configStr = config.toString(); - assertTrue(configStr != null && !configStr.isEmpty()); - } - - // Check that it is possible for multiple users to reference the same Realm URL while each user still use their - // own copy on the filesystem. This is e.g. what happens if a Realm is shared using a PermissionOffer. - @Test - public void multipleUsersReferenceSameRealm() { - SyncUser user1 = createNamedTestUser("user1"); - SyncUser user2 = createNamedTestUser("user2"); - String sharedUrl = "realm://ros.realm.io/42/default"; - SyncConfiguration config1 = user1.createConfiguration(sharedUrl) - .modules(new StringOnlyModule()) - .build(); - Realm realm1 = Realm.getInstance(config1); - SyncConfiguration config2 = user2.createConfiguration(sharedUrl) - .modules(new StringOnlyModule()) - .build(); - Realm realm2 = null; - - // Verify that two different configurations can be used for the same URL - try { - realm2 = Realm.getInstance(config1); - } finally { - realm1.close(); - if (realm2 != null) { - realm2.close(); - } - } - - // Verify that we actually save two different files - assertNotEquals(config1.getPath(), config2.getPath()); - } - - @Ignore("FIXME") - @Test - public void getDefaultConfiguration_throwsIfNotLoggedIn() { - SyncUser user = createTestUser(); -// user.logOut(); - try { - user.getDefaultConfiguration(); - fail(); - } catch (IllegalStateException e) { - assertTrue(e.getMessage().startsWith("The default configuration can only be created for users that are logged in.")); - } - } - - @Ignore("FIXME") - @Test - public void automatic_convertsAuthUrl() { - Object[][] input = { - // AuthUrl -> Expected Realm URL - { "http://ros.realm.io/auth", "realm://ros.realm.io/default" }, - { "http://ros.realm.io:7777", "realm://ros.realm.io:7777/default" }, - { "http://127.0.0.1/auth", "realm://127.0.0.1/default" }, - { "HTTP://ros.realm.io" , "realm://ros.realm.io/default" }, - - { "https://ros.realm.io/auth", "realms://ros.realm.io/default" }, - { "https://ros.realm.io:7777", "realms://ros.realm.io:7777/default" }, - { "https://127.0.0.1/auth", "realms://127.0.0.1/default" }, - { "HTTPS://ros.realm.io" , "realms://ros.realm.io/default" }, - // with port - { "http://192.168.1.65:9080" , "realm://192.168.1.65:9080/default" }, - { "http://192.168.1.65:9080/auth" , "realm://192.168.1.65:9080/default" }, - { "https://192.168.1.65:9080/auth" , "realms://192.168.1.65:9080/default" }, - }; - - for (Object[] test : input) { - String authUrl = (String) test[0]; - String realmUrl = (String) test[1]; - - SyncUser user = createTestUser(authUrl); - SyncConfiguration config = user.getDefaultConfiguration(); - URI url = config.getServerUrl(); - assertEquals(realmUrl, url.toString()); -// user.logOut(); - } - } - - @Test - public void clientResyncMode() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - - // Default mode for full Realms - SyncConfiguration config = user.createConfiguration(url).build(); - assertEquals(ClientResyncMode.RECOVER_LOCAL_REALM, config.getClientResyncMode()); - - // Manually set the mode - config = user.createConfiguration(url) - .clientResyncMode(ClientResyncMode.MANUAL) - .build(); - assertEquals(ClientResyncMode.MANUAL, config.getClientResyncMode()); - } - - @Test - public void clientResyncMode_throwsOnNull() { - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration.Builder config = user.createConfiguration(url); - try { - //noinspection ConstantConditions - config.clientResyncMode(null); - fail(); - } catch (IllegalArgumentException ignore) { - } - } -} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.kt new file mode 100644 index 0000000000..b73de533ea --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.kt @@ -0,0 +1,264 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.SyncTestUtils.Companion.createTestUser +import io.realm.entities.StringOnly +import io.realm.entities.StringOnlyModule +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import io.realm.rule.RunInLooperThread +import org.junit.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import java.lang.IllegalArgumentException +import kotlin.test.assertFailsWith + +@RunWith(AndroidJUnit4::class) +class SyncConfigurationTests { + + companion object { + private const val DEFAULT_PARTITION = "default" + } + + @get:Rule + val configFactory = TestSyncConfigurationFactory() + + @get:Rule + val looperThread = RunInLooperThread() + + @get:Rule + val tempFolder = TemporaryFolder() + + private lateinit var app: TestRealmApp + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestRealmApp() + } + + @After + fun tearDown() { + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun errorHandler() { + val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + val errorHandler: SyncSession.ErrorHandler = object : SyncSession.ErrorHandler { + override fun onError(session: SyncSession, error: ObjectServerError) {} + } + val config = builder.errorHandler(errorHandler).build() + Assert.assertEquals(errorHandler, config.errorHandler) + } + + @Test + fun errorHandler_fromSyncManager() { + val user: RealmUser = createTestUser(app) + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) + Assert.assertEquals(app.configuration.defaultErrorHandler, config.errorHandler) + } + + @Test + fun errorHandler_nullThrows() { + val user: RealmUser = createTestUser(app) + val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) + assertFailsWith { builder.errorHandler(TestHelper.getNull()) } + } + + @Test + fun equals() { + val user: RealmUser = createTestUser(app) + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) + Assert.assertTrue(config == config) + } + + @Test + fun equals_same() { + val user: RealmUser = createTestUser(app) + val config1: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION).build() + val config2: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION).build() + Assert.assertTrue(config1 == config2) + } + + @Test + fun equals_not() { + val user1: RealmUser = createTestUser(app) + val user2: RealmUser = createTestUser(app) + val config1: SyncConfiguration = SyncConfiguration.Builder(user1, DEFAULT_PARTITION).build() + val config2: SyncConfiguration = SyncConfiguration.Builder(user2, DEFAULT_PARTITION).build() + Assert.assertFalse(config1 == config2) + } + + @Test + fun hashCode_equal() { + val user: RealmUser = createTestUser(app) + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) + Assert.assertEquals(config.hashCode(), config.hashCode()) + } + + @Test + fun hashCode_notEquals() { + val user1: RealmUser = createTestUser(app) + val user2: RealmUser = createTestUser(app) + val config1: SyncConfiguration = SyncConfiguration.defaultConfig(user1, DEFAULT_PARTITION) + val config2: SyncConfiguration = SyncConfiguration.defaultConfig(user2, DEFAULT_PARTITION) + Assert.assertNotEquals(config1.hashCode(), config2.hashCode()) + } + + @Test + fun get_syncSpecificValues() { + val user: RealmUser = createTestUser(app) + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) + Assert.assertTrue(user == config.user) + Assert.assertEquals("ws://127.0.0.1:9090/", config.serverUrl.toString()) // FIXME: Figure out exactly what to return here + Assert.assertFalse(config.shouldDeleteRealmOnLogout()) + Assert.assertTrue(config.isSyncConfiguration) + } + + @Test + fun encryption() { + val user: RealmUser = createTestUser(app) + val config: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION) + .encryptionKey(TestHelper.getRandomKey()) + .build() + Assert.assertNotNull(config.encryptionKey) + } + + @Test + fun encryption_invalid_null() { + val user: RealmUser = createTestUser(app) + val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) + assertFailsWith { builder.encryptionKey(TestHelper.getNull()) } + } + + fun encryption_invalid_wrong_length() { + val user: RealmUser = createTestUser(app) + val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) + assertFailsWith { builder.encryptionKey(byteArrayOf(1, 2, 3)) } + } + + @Test + fun initialData() { + val user: RealmUser = createTestUser(app) + val config = configFactory.createSyncConfigurationBuilder(user) + .schema(StringOnly::class.java) + .initialData(object : Realm.Transaction { + override fun execute(realm: Realm) { + val stringOnly: StringOnly = realm.createObject() + stringOnly.setChars("TEST 42") + } + }) + .build() + Assert.assertNotNull(config.initialDataTransaction) + + // open the first time - initialData must be triggered + val realm1: Realm = Realm.getInstance(config) + val results: RealmResults = realm1.where().findAll() + assertEquals(1, results.size) + assertEquals("TEST 42", results.first()!!.getChars()) + realm1.close() + + // open the second time - initialData must not be triggered + val realm2: Realm = Realm.getInstance(config) + assertEquals(1, realm2.where().count()) + realm2.close() + } + + @Test + fun defaultRxFactory() { + val user: RealmUser = createTestUser(app) + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) + Assert.assertNotNull(config.rxFactory) + } + + @Test + fun toString_nonEmpty() { + val user: RealmUser = createTestUser(app) + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) + val configStr = config.toString() + assertTrue(configStr.isNotEmpty()) + } + + // Check that it is possible for multiple users to reference the same Realm URL while each user still use their + // own copy on the filesystem. This is e.g. what happens if a Realm is shared using a PermissionOffer. + @Test + @Ignore("FIXME: Enable this once Sync is working.") + fun multipleUsersReferenceSameRealm() { + val user1: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val user2: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val config1: SyncConfiguration = SyncConfiguration.Builder(user1, DEFAULT_PARTITION) + .modules(StringOnlyModule()) + .build() + val realm1: Realm = Realm.getInstance(config1) + val config2: SyncConfiguration = SyncConfiguration.Builder(user2, DEFAULT_PARTITION) + .modules(StringOnlyModule()) + .build() + var realm2: Realm? = null + + // Verify that two different configurations can be used for the same URL + realm2 = try { + Realm.getInstance(config1) + } finally { + realm1.close() + if (realm2 != null) { + realm2.close() + } + } + + // Verify that we actually save two different files + Assert.assertNotEquals(config1.path, config2.path) + } + @Test + fun defaultConfiguration_throwsIfNotLoggedIn() { + val user: RealmUser = createTestUser(app) + user.osUser.invalidate() + assertFailsWith { SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) } + } + + @Test + fun clientResyncMode() { + val user: RealmUser = createTestUser(app) + + // Default mode for full Realms + var config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) + assertEquals(ClientResyncMode.MANUAL, config.clientResyncMode) + + // Manually set the mode + config = SyncConfiguration.Builder(user, DEFAULT_PARTITION) + .clientResyncMode(ClientResyncMode.RECOVER_LOCAL_REALM) + .build() + assertEquals(ClientResyncMode.RECOVER_LOCAL_REALM, config.clientResyncMode) + } + + @Test + fun clientResyncMode_throwsOnNull() { + val user: RealmUser = createTestUser(app) + val config: SyncConfiguration.Builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) + try { + config.clientResyncMode(TestHelper.getNull()) + Assert.fail() + } catch (ignore: IllegalArgumentException) { + } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java deleted file mode 100644 index 98522879f8..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncManagerTests.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; - -import io.realm.entities.StringOnlyModule; -import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.TestRealmConfigurationFactory; - -import static io.realm.SyncTestUtils.*; -import static org.junit.Assert.*; - -@Ignore("FIXME: RealmApp refactor") -@RunWith(AndroidJUnit4.class) -public class SyncManagerTests { - - @Rule - public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Before - public void setUp() { - SyncManager.reset(); - } - - @After - public void tearDown() { - UserFactory.logoutAllUsers(); - SyncManager.reset(); - BaseRealm.applicationContext = null; // Required for Realm.init() to work - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - } - - @Test - public void authListener() { - SyncUser user = createTestUser(); - final int[] counter = {0, 0}; - - AuthenticationListener authenticationListener = new AuthenticationListener() { - @Override - public void loggedIn(SyncUser user) { - counter[0]++; - } - - @Override - public void loggedOut(SyncUser user) { - counter[1]++; - } - }; - - SyncManager.addAuthenticationListener(authenticationListener); - SyncManager.notifyUserLoggedIn(user); - SyncManager.notifyUserLoggedOut(user); - assertEquals(1, counter[0]); - assertEquals(1, counter[1]); - } - - @Test(expected = IllegalArgumentException.class) - public void authListener_null() { - SyncManager.addAuthenticationListener(null); - } - - @Test - public void authListener_remove() { - SyncUser user = createTestUser(); - final int[] counter = {0, 0}; - - AuthenticationListener authenticationListener = new AuthenticationListener() { - @Override - public void loggedIn(SyncUser user) { - counter[0]++; - } - - @Override - public void loggedOut(SyncUser user) { - counter[1]++; - } - }; - - SyncManager.addAuthenticationListener(authenticationListener); - - SyncManager.removeAuthenticationListener(authenticationListener); - - SyncManager.notifyUserLoggedIn(user); - SyncManager.notifyUserLoggedOut(user); - - // no listener to update counters - assertEquals(0, counter[0]); - assertEquals(0, counter[1]); - } - - @Test - public void session() throws IOException { - BaseRealm.applicationContext = null; - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - SyncUser user = createTestUser(); - String url = "realm://objectserver.realm.io/default"; - SyncConfiguration config = user.createConfiguration(url) - .modules(new StringOnlyModule()) - .build(); - // This will trigger the creation of the session - Realm realm = Realm.getInstance(config); - SyncSession session = SyncManager.getSession(config); - assertEquals(user, session.getUser()); // see also SessionTests - - realm.close(); - } - - private void tryCase(Runnable runnable) { - try { - runnable.run(); - fail(); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void setAuthorizationHeaderName_illegalArgumentsThrows() { - //noinspection ConstantConditions - tryCase(() -> SyncManager.setAuthorizationHeaderName(null)); - tryCase(() -> SyncManager.setAuthorizationHeaderName("")); - //noinspection ConstantConditions - tryCase(() -> SyncManager.setAuthorizationHeaderName(null, "myhost")); - tryCase(() -> SyncManager.setAuthorizationHeaderName("", "myhost")); - //noinspection ConstantConditions - tryCase(() -> SyncManager.setAuthorizationHeaderName("myheader", null)); - tryCase(() -> SyncManager.setAuthorizationHeaderName("myheader", "")); - } - - @Test - public void setAuthorizationHeaderName() throws URISyntaxException { - SyncManager.setAuthorizationHeaderName("foo"); - assertEquals("foo", SyncManager.getAuthorizationHeaderName(new URI("http://localhost"))); - } - - @Test - public void setAuthorizationHeaderName_hostOverrideGlobal() throws URISyntaxException { - SyncManager.setAuthorizationHeaderName("foo"); - SyncManager.setAuthorizationHeaderName("bar", "localhost"); - assertEquals("bar", SyncManager.getAuthorizationHeaderName(new URI("http://localhost"))); - } - - @Test - public void getAuthorizationHeaderName_ignoreHostCasing() throws URISyntaxException { - SyncManager.setAuthorizationHeaderName("foo", "lOcAlHoSt"); - assertEquals("foo", SyncManager.getAuthorizationHeaderName(new URI("http://localhost"))); - assertEquals("foo", SyncManager.getAuthorizationHeaderName(new URI("http://LOCALHOST"))); - } - - @Test - public void addCustomRequestHeader_illegalArgumentThrows() { - //noinspection ConstantConditions - tryCase(() -> SyncManager.addCustomRequestHeader(null, "val")); - tryCase(() -> SyncManager.addCustomRequestHeader("", "val")); - //noinspection ConstantConditions - tryCase(() -> SyncManager.addCustomRequestHeader("header", null)); - - //noinspection ConstantConditions - tryCase(() -> SyncManager.addCustomRequestHeader(null, "val", "localhost")); - tryCase(() -> SyncManager.addCustomRequestHeader("", "val", "localhost")); - //noinspection ConstantConditions - tryCase(() -> SyncManager.addCustomRequestHeader("header", "value", null)); - tryCase(() -> SyncManager.addCustomRequestHeader("header", "value", "")); - } - - @Test - public void addCustomRequestHeaders_illegalArgumentThrows() { - tryCase(() -> SyncManager.addCustomRequestHeaders(Collections.emptyMap(), null)); - tryCase(() -> SyncManager.addCustomRequestHeaders(Collections.emptyMap(), "")); - } - - @Test - public void addCustomRequestHeader() throws URISyntaxException { - SyncManager.addCustomRequestHeader("header1", "val1"); - SyncManager.addCustomRequestHeader("header2", "val2"); - Map headers = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); - assertEquals(2, headers.size()); - Map.Entry header = headers.entrySet().iterator().next(); - String expected = header.getKey().equals("header1") ? "val1" : "val2"; - assertEquals(expected, header.getValue()); - } - - @Test - public void addCustomRequestHeader_hostOverrideGlobal() throws URISyntaxException { - SyncManager.addCustomRequestHeader("header1", "val1"); - SyncManager.addCustomRequestHeader("header1", "val2", "localhost"); - Map headers = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); - assertEquals(1, headers.size()); - Map.Entry header = headers.entrySet().iterator().next(); - assertEquals("header1", header.getKey()); - assertEquals("val2", header.getValue()); - } - - @Test - public void addCustomRequestHeader_ignoreCasingForHost() throws URISyntaxException { - SyncManager.addCustomRequestHeader("header1", "val1", "lOcAlHoSt"); - SyncManager.addCustomRequestHeader("header2", "val2", "LOCALHOST"); - Map headers = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); - assertEquals(2, headers.size()); - } - - @Test - public void addCustomHeaders() throws URISyntaxException { - Map inputHeaders = new LinkedHashMap<>(); - inputHeaders.put("header1", "value1"); - inputHeaders.put("header2", "value2"); - SyncManager.addCustomRequestHeaders(null); - SyncManager.addCustomRequestHeaders(inputHeaders); - Map outputHeaders = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); - assertEquals(2, outputHeaders.size()); - Iterator> it = outputHeaders.entrySet().iterator(); - Map.Entry header1 = it.next(); - - if (header1.getKey().equals("header1")) { - assertEquals("header1", header1.getKey()); - assertEquals("value1", header1.getValue()); - Map.Entry header2 = it.next(); - assertEquals("header2", header2.getKey()); - assertEquals("value2", header2.getValue()); - - } else { - assertEquals("header2", header1.getKey()); - assertEquals("value2", header1.getValue()); - Map.Entry header2 = it.next(); - assertEquals("header1", header2.getKey()); - assertEquals("value1", header2.getValue()); - } - } - - @Test - public void addCustomHeaders_hostOverrideGlobal() throws URISyntaxException { - Map inputHeaders = new LinkedHashMap<>(); - inputHeaders.put("header1", "val1"); - SyncManager.addCustomRequestHeaders(inputHeaders); - inputHeaders.put("header1", "val2"); - SyncManager.addCustomRequestHeaders(inputHeaders, "localhost"); - Map outputHeaders = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); - assertEquals(1, outputHeaders.size()); - Map.Entry header = outputHeaders.entrySet().iterator().next(); - assertEquals("header1", header.getKey()); - assertEquals("val2", header.getValue()); - } - - @Test - public void addCustomHeader_combinesSingleAndMultiple() throws URISyntaxException { - Map inputHeaders1 = new LinkedHashMap<>(); - inputHeaders1.put("header1", "val1"); - Map inputHeaders2 = new LinkedHashMap<>(); - inputHeaders2.put("header2", "val2"); - - SyncManager.addCustomRequestHeader("header3", "val3"); - SyncManager.addCustomRequestHeaders(inputHeaders1); - SyncManager.addCustomRequestHeader("header4", "val4", "realm.io"); - SyncManager.addCustomRequestHeaders(inputHeaders2, "realm.io"); - - Map localhostHeaders = SyncManager.getCustomRequestHeaders(new URI("http://localhost")); - assertEquals(2, localhostHeaders.size()); - Iterator> it = localhostHeaders.entrySet().iterator(); - Map.Entry item = it.next(); - assertEquals("header3", item.getKey()); - assertEquals("val3", item.getValue()); - item = it.next(); - assertEquals("header1", item.getKey()); - assertEquals("val1", item.getValue()); - - Map realmioHeaders = SyncManager.getCustomRequestHeaders(new URI("http://realm.io")); - it = realmioHeaders.entrySet().iterator(); - assertEquals(4, realmioHeaders.size()); - item = it.next(); - assertEquals("header3", item.getKey()); - assertEquals("val3", item.getValue()); - item = it.next(); - assertEquals("header1", item.getKey()); - assertEquals("val1", item.getValue()); - item = it.next(); - assertEquals("header4", item.getKey()); - assertEquals("val4", item.getValue()); - item = it.next(); - assertEquals("header2", item.getKey()); - assertEquals("val2", item.getValue()); - } -} diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java index 11aa251bbc..cf7b120837 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java @@ -19,6 +19,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.hamcrest.CoreMatchers; +import org.junit.After; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Rule; @@ -55,6 +57,7 @@ public class SyncedRealmMigrationTests { public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); @Rule public final ExpectedException thrown = ExpectedException.none(); + private TestRealmApp app; @BeforeClass public static void beforeClass () { @@ -64,9 +67,21 @@ public static void beforeClass () { BaseRealm.applicationContext = null; } + @Before + public void setUp() { + app = new TestRealmApp(); + } + + @After + public void tearDown() { + if (app != null) { + RealmAppExtKt.close(app); + } + } + @Test public void migrateRealm_syncConfigurationThrows() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth").build(); + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)).build(); try { Realm.migrateRealm(config); fail(); @@ -80,7 +95,7 @@ public void migrateRealm_syncConfigurationThrows() { // automatically. @Test public void addField_worksWithMigrationError() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) .schema(StringOnly.class) .build(); @@ -107,7 +122,7 @@ public void addField_worksWithMigrationError() { // The underlying field should not be deleted, just hidden. @Test public void missingFields_hiddenSilently() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) .schema(StringOnly.class) .build(); @@ -140,7 +155,7 @@ public void missingFields_hiddenSilently() { // Check that a Realm cannot be opened if it contain breaking schema changes, like changing a primary key @Test public void breakingSchemaChange_throws() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) .schema(PrimaryKeyAsString.class) .build(); @@ -165,7 +180,7 @@ public void breakingSchemaChange_throws() { @Test public void sameSchemaVersion_doNotRebuildIndexes() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) .schema(IndexedFields.class) .schemaVersion(42) .build(); @@ -197,7 +212,7 @@ public void sameSchemaVersion_doNotRebuildIndexes() { @Test public void differentSchemaVersions_rebuildIndexes() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) .schema(IndexedFields.class) .schemaVersion(42) .build(); @@ -229,7 +244,7 @@ public void differentSchemaVersions_rebuildIndexes() { @Test public void addingFields_rebuildIndexes() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) .schema(IndexedFields.class) .schemaVersion(42) .build(); @@ -258,7 +273,7 @@ public void addingFields_rebuildIndexes() { @Test public void schemaVersionUpgradedWhenMigrating() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) .schemaVersion(42) .build(); @@ -285,7 +300,7 @@ public void schemaVersionUpgradedWhenMigrating() { @Test public void moreFieldsThanExpectedIsAllowed() { SyncConfiguration config = configFactory - .createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/auth") + .createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) .schema(StringOnly.class) .build(); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java index ca5ed81e63..8499a47f07 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java @@ -19,6 +19,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; +import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -30,7 +31,6 @@ import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; -import io.realm.objectserver.utils.Constants; import io.realm.rule.RunInLooperThread; import static org.junit.Assert.assertEquals; @@ -55,17 +55,21 @@ public class SyncedRealmTests { public final ExpectedException thrown = ExpectedException.none(); private Realm realm; + private RealmApp app; + + @Before + public void setUp() { + app = new TestRealmApp(); + } @After public void tearDown() { if (realm != null && !realm.isClosed()) { realm.close(); } - -// FIXME -// for (RealmUser user : RealmApp.allUsers().values()) { -// RealmApp.logout(user); -// } + if (app != null) { + RealmAppExtKt.close(app); + } } private Realm getNormalRealm() { @@ -75,7 +79,7 @@ private Realm getNormalRealm() { } private Realm getFullySyncRealm() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), "http://foo.com/fullsync") + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) .build(); realm = Realm.getInstance(config); return realm; @@ -85,14 +89,14 @@ private Realm getFullySyncRealm() { @Test @Ignore("Going to be removed anyway") public void testUpgradingOptionalSubscriptionFields() throws IOException { - SyncUser user = SyncTestUtils.createTestUser(); + RealmUser user = SyncTestUtils.createTestUser(app); // Put an older Realm at the location where Realm would otherwise create a new empty one. // This way, Realm will upgrade this file instead. // We don't need to synchronize data with the server, so any errors due to missing // server side files are ignored. // The file was created using Realm Java 5.10.0 - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, "realm://127.0.0.1:9080/optionalsubscriptionfields").build(); + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user).build(); File realmDir = config.getRealmDirectory(); File oldRealmFile = new File(realmDir, "optionalsubscriptionfields"); assertFalse(oldRealmFile.exists()); @@ -114,7 +118,7 @@ public void testUpgradingOptionalSubscriptionFields() throws IOException { @Test public void compactRealm_populatedRealm() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(), Constants.DEFAULT_REALM).build(); + SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)).build(); realm = Realm.getInstance(config); realm.executeTransaction(r -> { for (int i = 0; i < 10; i++) { @@ -129,10 +133,10 @@ public void compactRealm_populatedRealm() { @Test public void compactOnLaunch_shouldCompact() throws IOException { - SyncUser user = SyncTestUtils.createTestUser(); + RealmUser user = SyncTestUtils.createTestUser(app); // Fill Realm with data and record size - SyncConfiguration config1 = configFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM).build(); + SyncConfiguration config1 = configFactory.createSyncConfigurationBuilder(user).build(); realm = Realm.getInstance(config1); byte[] oneMBData = new byte[1024 * 1024]; realm.beginTransaction(); @@ -144,7 +148,7 @@ public void compactOnLaunch_shouldCompact() throws IOException { long originalSize = new File(realm.getPath()).length(); // Open Realm with CompactOnLaunch - SyncConfiguration config2 = configFactory.createSyncConfigurationBuilder(user, Constants.DEFAULT_REALM) + SyncConfiguration config2 = configFactory.createSyncConfigurationBuilder(user) .compactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java index fcc41b7e01..162efffa2b 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java @@ -106,8 +106,8 @@ public void sslVerifyCallback_certificateChainWithRootCAInstalledShouldValidate( String serverAddress = "127.0.0.1"; - assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth1, 1)); - assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth0, 0)); + assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth1, 1)); + assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth0, 0)); } @Test @@ -238,10 +238,10 @@ public void sslVerifyCallback_shouldFailOnExpiredCert() { String serverAddress = "nabil-test.ie1.realmlab.net"; - assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth3, 3)); - assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth2, 2)); - assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth1, 1)); - assertFalse(SyncManager.sslVerifyCallback(serverAddress, pem_depth0, 0)); + assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth3, 3)); + assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth2, 2)); + assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth1, 1)); + assertFalse(RealmSync.sslVerifyCallback(serverAddress, pem_depth0, 0)); } @Ignore("FIXME: Certificate expired") @@ -368,19 +368,19 @@ public void sslVerifyCallback_shouldVerifyHostname() { String serverAddress = "foo.us1a.cloud.realm.io"; - assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth3, 3)); - assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth2, 2)); - assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth1, 1)); - assertTrue(SyncManager.sslVerifyCallback(serverAddress, pem_depth0, 0)); + assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth3, 3)); + assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth2, 2)); + assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth1, 1)); + assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth0, 0)); // reaching depth0 will validate (or not) the entire chain, then removing the PEMs from memory // make sure the hostname verify works String wrongServerAddress = "hax0r-us1a.cloud2.realm.io"; - assertTrue(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth3, 3)); - assertTrue(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth2, 2)); - assertTrue(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth1, 1)); + assertTrue(RealmSync.sslVerifyCallback(wrongServerAddress, pem_depth3, 3)); + assertTrue(RealmSync.sslVerifyCallback(wrongServerAddress, pem_depth2, 2)); + assertTrue(RealmSync.sslVerifyCallback(wrongServerAddress, pem_depth1, 1)); // the method fails because of the hostname verification - assertFalse(SyncManager.sslVerifyCallback(wrongServerAddress, pem_depth0, 0)); + assertFalse(RealmSync.sslVerifyCallback(wrongServerAddress, pem_depth0, 0)); } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt similarity index 97% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt index a18429a044..5af23bb84d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthProviderTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt @@ -29,13 +29,12 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class ApiKeyAuthProviderTests { - +class ApiKeyAuthTests { private val looperThread = BlockingLooperThread() private lateinit var app: TestRealmApp private lateinit var admin: ServerAdmin private lateinit var user: RealmUser - private lateinit var provider: ApiKeyAuthProvider + private lateinit var provider: ApiKeyAuth // Callback use to verify that an Illegal Argument was thrown from async methods private val checkNullInVoidCallback = RealmApp.Callback { result -> @@ -68,18 +67,18 @@ class ApiKeyAuthProviderTests { @Before fun setUp() { - app = TestRealmApp() - RealmLog.setLevel(LogLevel.DEBUG) admin = ServerAdmin() + app = TestRealmApp() user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - provider = user.apiKeyAuthProvider + provider = user.apiKeyAuth } @After fun tearDown() { - app.close() + if (this::app.isInitialized) { + app.close() + } admin.deleteAllUsers() - RealmLog.setLevel(LogLevel.WARN) } inline fun testNullArg(method: () -> Unit) { @@ -207,8 +206,8 @@ class ApiKeyAuthProviderTests { provider.fetchAllApiKeys() { result -> val keys: List = result.orThrow assertEquals(2, keys.size) - assertEquals(key1.id, keys[0].id) - assertEquals(key2.id, keys[1].id) + assertTrue(keys.any { it.id == key1.id }) + assertTrue(keys.any { it.id == key2.id }) looperThread.testComplete() } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt similarity index 86% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index e5494620b0..879ea815d6 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthProviderTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -29,9 +29,10 @@ import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import java.lang.IllegalStateException +import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) -class EmailPasswordAuthProviderTests { +class EmailPasswordAuthTests { private val looperThread = BlockingLooperThread() private lateinit var app: TestRealmApp @@ -62,31 +63,23 @@ class EmailPasswordAuthProviderTests { app = TestRealmApp() RealmLog.setLevel(LogLevel.DEBUG) admin = ServerAdmin() + admin.deleteAllUsers() } @After fun tearDown() { - app.close() + if (this::app.isInitialized) { + app.close() + } admin.deleteAllUsers() RealmLog.setLevel(LogLevel.WARN) } - inline fun expectException(method: () -> Unit) { - try { - method() - fail() - } catch (e: Throwable) { - if (e !is T) { - fail("Unexpected exception: $e") - } - } - } - @Test fun registerUser() { val email = TestHelper.getRandomEmail() val password = "password1234" - app.emailPasswordAuthProvider.registerUser(email, password) + app.emailPasswordAuth.registerUser(email, password) val user = app.login(RealmCredentials.emailPassword(email, password)) assertEquals(RealmUser.State.LOGGED_IN, user.state) } @@ -96,7 +89,7 @@ class EmailPasswordAuthProviderTests { val email = TestHelper.getRandomEmail() val password = "password1234" looperThread.runBlocking { - app.emailPasswordAuthProvider.registerUserAsync(email, password) { result -> + app.emailPasswordAuth.registerUserAsync(email, password) { result -> if (result.isSuccess) { val user2 = app.login(RealmCredentials.emailPassword(email, password)) assertEquals(RealmUser.State.LOGGED_IN, user2.state) @@ -110,7 +103,7 @@ class EmailPasswordAuthProviderTests { @Test fun registerUser_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth try { provider.registerUser("invalid-email", "1234") fail() @@ -121,7 +114,7 @@ class EmailPasswordAuthProviderTests { @Test fun registerUserAsync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth looperThread.runBlocking { provider.registerUserAsync("invalid-email", "1234") { result -> if (result.isSuccess) { @@ -136,9 +129,9 @@ class EmailPasswordAuthProviderTests { @Test fun registerUser_invalidArgumentsThrows() { - val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider - expectException { provider.registerUser(TestHelper.getNull(), "123456") } - expectException { provider.registerUser("foo@bar.baz", TestHelper.getNull()) } + val provider: EmailPasswordAuth = app.emailPasswordAuth + assertFailsWith { provider.registerUser(TestHelper.getNull(), "123456") } + assertFailsWith { provider.registerUser("foo@bar.baz", TestHelper.getNull()) } looperThread.runBlocking { provider.registerUserAsync(TestHelper.getNull(), "123456", checkNullArgCallback) } @@ -161,7 +154,7 @@ class EmailPasswordAuthProviderTests { @Test fun confirmUser_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth try { provider.confirmUser("invalid-token", "invalid-token-id") fail() @@ -172,7 +165,7 @@ class EmailPasswordAuthProviderTests { @Test fun confirmUserAsync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth looperThread.runBlocking { provider.confirmUserAsync("invalid-email", "1234") { result -> if (result.isSuccess) { @@ -187,9 +180,9 @@ class EmailPasswordAuthProviderTests { @Test fun confirmUser_invalidArgumentsThrows() { - val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider - expectException { provider.confirmUser(TestHelper.getNull(), "token-id") } - expectException { provider.confirmUser("token", TestHelper.getNull()) } + val provider: EmailPasswordAuth = app.emailPasswordAuth + assertFailsWith { provider.confirmUser(TestHelper.getNull(), "token-id") } + assertFailsWith { provider.confirmUser("token", TestHelper.getNull()) } looperThread.runBlocking { provider.confirmUserAsync(TestHelper.getNull(), "token-id", checkNullArgCallback) } @@ -206,7 +199,7 @@ class EmailPasswordAuthProviderTests { val email = "test@10gen.com" admin.setAutomaticConfirmation(false) try { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth provider.registerUser(email, "123456") provider.resendConfirmationEmail(email) } finally { @@ -222,7 +215,7 @@ class EmailPasswordAuthProviderTests { admin.setAutomaticConfirmation(false) try { looperThread.runBlocking { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth provider.registerUser(email, "123456") provider.resendConfirmationEmailAsync(email) { result -> when(result.isSuccess) { @@ -240,7 +233,7 @@ class EmailPasswordAuthProviderTests { fun resendConfirmationEmail_invalidServerArgsThrows() { val email = "test@10gen.com" admin.setAutomaticConfirmation(false) - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth provider.registerUser(email, "123456") try { provider.resendConfirmationEmail("foo") @@ -256,7 +249,7 @@ class EmailPasswordAuthProviderTests { fun resendConfirmationEmailAsync_invalidServerArgsThrows() { val email = "test@10gen.com" admin.setAutomaticConfirmation(false) - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth provider.registerUser(email, "123456") try { looperThread.runBlocking { @@ -276,8 +269,8 @@ class EmailPasswordAuthProviderTests { @Test fun resendConfirmationEmail_invalidArgumentsThrows() { - val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider - expectException { provider.resendConfirmationEmail(TestHelper.getNull()) } + val provider: EmailPasswordAuth = app.emailPasswordAuth + assertFailsWith { provider.resendConfirmationEmail(TestHelper.getNull()) } looperThread.runBlocking { provider.resendConfirmationEmailAsync(TestHelper.getNull(), checkNullArgCallback) } @@ -285,7 +278,7 @@ class EmailPasswordAuthProviderTests { @Test fun sendResetPasswordEmail() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth val email: String = "test@10gen.com" // Must be a valid email, otherwise the server will fail provider.registerUser(email, "123456") provider.sendResetPasswordEmail(email) @@ -293,7 +286,7 @@ class EmailPasswordAuthProviderTests { @Test fun sendResetPasswordEmailAsync() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth val email: String = "test@10gen.com" // Must be a valid email, otherwise the server will fail provider.registerUser(email, "123456") looperThread.runBlocking { @@ -309,7 +302,7 @@ class EmailPasswordAuthProviderTests { @Test fun sendResetPasswordEmail_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth try { provider.sendResetPasswordEmail("unknown@10gen.com") fail() @@ -320,7 +313,7 @@ class EmailPasswordAuthProviderTests { @Test fun sendResetPasswordEmailAsync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth looperThread.runBlocking { provider.sendResetPasswordEmailAsync("unknown@10gen.com") { result -> if (result.isSuccess) { @@ -335,8 +328,8 @@ class EmailPasswordAuthProviderTests { @Test fun sendResetPasswordEmail_invalidArgumentsThrows() { - val provider = app.emailPasswordAuthProvider - expectException { provider.sendResetPasswordEmail(TestHelper.getNull()) } + val provider = app.emailPasswordAuth + assertFailsWith { provider.sendResetPasswordEmail(TestHelper.getNull()) } looperThread.runBlocking { provider.sendResetPasswordEmailAsync(TestHelper.getNull(), checkNullArgCallback) } @@ -344,7 +337,7 @@ class EmailPasswordAuthProviderTests { @Test fun callResetPasswordFunction() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth admin.setResetFunction(enabled = true) val email = TestHelper.getRandomEmail() provider.registerUser(email, "123456") @@ -359,7 +352,7 @@ class EmailPasswordAuthProviderTests { @Test fun callResetPasswordFunctionAsync() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth admin.setResetFunction(enabled = true) val email = TestHelper.getRandomEmail() provider.registerUser(email, "123456") @@ -384,7 +377,7 @@ class EmailPasswordAuthProviderTests { @Test fun callResetPasswordFunction_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth admin.setResetFunction(enabled = true) val email = TestHelper.getRandomEmail() provider.registerUser(email, "123456") @@ -399,7 +392,7 @@ class EmailPasswordAuthProviderTests { @Test fun callResetPasswordFunctionAsync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth admin.setResetFunction(enabled = true) val email = TestHelper.getRandomEmail() provider.registerUser(email, "123456") @@ -424,9 +417,9 @@ class EmailPasswordAuthProviderTests { @Test fun callResetPasswordFunction_invalidArgumentsThrows() { - val provider = app.emailPasswordAuthProvider - expectException { provider.callResetPasswordFunction(TestHelper.getNull(), "password") } - expectException { provider.callResetPasswordFunction("foo@bar.baz", TestHelper.getNull()) } + val provider = app.emailPasswordAuth + assertFailsWith { provider.callResetPasswordFunction(TestHelper.getNull(), "password") } + assertFailsWith { provider.callResetPasswordFunction("foo@bar.baz", TestHelper.getNull()) } looperThread.runBlocking { provider.callResetPasswordFunctionAsync(TestHelper.getNull(), "new-password", arrayOf(), checkNullArgCallback) } @@ -449,7 +442,7 @@ class EmailPasswordAuthProviderTests { @Test fun resetPassword_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth try { provider.resetPassword("invalid-token", "invalid-token-id", "new-password") } catch (error: ObjectServerError) { @@ -459,7 +452,7 @@ class EmailPasswordAuthProviderTests { @Test fun resetPasswordASync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuthProvider + val provider = app.emailPasswordAuth looperThread.runBlocking { provider.resetPasswordAsync("invalid-token", "invalid-token-id", "new-password") { result -> if (result.isSuccess) { @@ -474,10 +467,10 @@ class EmailPasswordAuthProviderTests { @Test fun resetPassword_invalidArgumentsThrows() { - val provider = app.emailPasswordAuthProvider - expectException { provider.resetPassword(TestHelper.getNull(), "token-id", "password") } - expectException { provider.resetPassword("token", TestHelper.getNull(), "password") } - expectException { provider.resetPassword("token", "token-id", TestHelper.getNull()) } + val provider = app.emailPasswordAuth + assertFailsWith { provider.resetPassword(TestHelper.getNull(), "token-id", "password") } + assertFailsWith { provider.resetPassword("token", TestHelper.getNull(), "password") } + assertFailsWith { provider.resetPassword("token", "token-id", TestHelper.getNull()) } looperThread.runBlocking { provider.resetPasswordAsync(TestHelper.getNull(), "token-id", "password", checkNullArgCallback) } @@ -492,7 +485,7 @@ class EmailPasswordAuthProviderTests { @Test @UiThreadTest fun callMethodsOnMainThreadThrows() { - val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider + val provider: EmailPasswordAuth = app.emailPasswordAuth val email: String = TestHelper.getRandomEmail() for (method in Method.values()) { try { @@ -513,7 +506,7 @@ class EmailPasswordAuthProviderTests { @Test fun callAsyncMethodsOnNonLooperThreadThrows() { - val provider: EmailPasswordAuthProvider = app.emailPasswordAuthProvider + val provider: EmailPasswordAuth = app.emailPasswordAuth val email: String = TestHelper.getRandomEmail() val callback = RealmApp.Callback { fail() } for (method in Method.values()) { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt new file mode 100644 index 0000000000..912df9a38d --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt @@ -0,0 +1,188 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.entities.* +import io.realm.kotlin.syncSession +import io.realm.kotlin.where +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* + +@RunWith(AndroidJUnit4::class) +class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaining Java tests have been moved + + private lateinit var app: TestRealmApp + private lateinit var realm: Realm + private lateinit var partitionValue: String + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + RealmLog.setLevel(LogLevel.TRACE) + app = TestRealmApp() + partitionValue = UUID.randomUUID().toString() + } + + @After + fun tearDown() { + if (this::realm.isInitialized) { + realm.close() + } + if (this::app.isInitialized) { + app.close() + } + RealmLog.setLevel(LogLevel.WARN) + } + + // Smoke test for Sync. Waiting for working Sync support. + @Test + fun connectWithInitialSchema() { + val user: RealmUser = createNewUser() + val config = createDefaultConfig(user) + realm = Realm.getInstance(config) + app.syncManager.getSession(config).uploadAllLocalChanges() + app.syncManager.getSession(config).downloadAllServerChanges() + assertTrue(realm.isEmpty) + } + + // Smoke test for Sync + @Ignore("Dev Mode doesn't work fully yet on the server") + @Test + fun roundTripObjectsNotInServerSchemaObject() { + // User 1 creates an object an uploads it to MongoDB Realm + val user1: RealmUser = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + realm = Realm.getInstance(config1) + realm.executeTransaction { + for (i in 1..10) { + it.insert(SyncColor()) + } + } + app.syncManager.getSession(config1).uploadAllLocalChanges() + assertEquals(10, realm.where().count()) + realm.close() + + // User 2 logs and using the same partition key should see the object + val user2: RealmUser = createNewUser() + val config2 = createDefaultConfig(user2, partitionValue) + realm = Realm.getInstance(config2) + app.syncManager.getSession(config2).downloadAllServerChanges() + assertEquals(10, realm.where().count()) + } + + // Smoke test for sync + // Insert different types with no links between them + @Test + fun roundTripSimpleObjectsInServerSchema() { + // User 1 creates an object an uploads it to MongoDB Realm + val user1: RealmUser = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + realm = Realm.getInstance(config1) + realm.executeTransaction { + val person = SyncPerson() + person.firstName = "Jane" + person.lastName = "Doe" + person.age = 42 + realm.insert(person); + for (i in 0..9) { + val dog = SyncDog() + dog.name = "Fido $i" + it.insert(dog) + } + } + realm.syncSession.uploadAllLocalChanges() + assertEquals(10, realm.where().count()) + assertEquals(1, realm.where().count()) + realm.close() + + // User 2 logs and using the same partition key should see the object + val user2: RealmUser = createNewUser() + val config2 = createDefaultConfig(user2, partitionValue) + realm = Realm.getInstance(config2) + realm.syncSession.downloadAllServerChanges() + assertEquals(10, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + + // Smoke test for sync + // Insert objects with links between them + @Ignore("Crashes server currently") + @Test + fun roundTripObjectsWithLists() { + // User 1 creates an object an uploads it to MongoDB Realm + val user1: RealmUser = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + realm = Realm.getInstance(config1) + realm.executeTransaction { + val person = SyncPerson() + person.firstName = "Jane" + person.lastName = "Doe" + person.age = 42 + for (i in 0..9) { + val dog = SyncDog() + dog.name = "Fido $i" + it.insert(dog) + person.dogs.add(dog.id) + } + realm.insert(person) + } + realm.syncSession.uploadAllLocalChanges() + assertEquals(10, realm.where().count()) + assertEquals(1, realm.where().count()) + realm.close() + + // User 2 logs and using the same partition key should see the object + val user2: RealmUser = createNewUser() + val config2 = createDefaultConfig(user2, partitionValue) + realm = Realm.getInstance(config2) + realm.syncSession.downloadAllServerChanges() + assertEquals(10, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + @Test + fun session() { + val user: RealmUser = app.login(RealmCredentials.anonymous()) + realm = Realm.getInstance(createDefaultConfig(user)) + assertNotNull(realm.syncSession) + assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) + assertEquals(user, realm.syncSession.user) + } + + private fun createDefaultConfig(user: RealmUser, partitionValue: String = defaultPartitionValue): SyncConfiguration { + return SyncConfiguration.Builder(user, partitionValue) + .waitForInitialRemoteData() // FIXME: This should not be required + .modules(DefaultSyncSchema()) + .build() + } + + private fun createNewUser(): RealmUser { + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuth.registerUser(email, password) + return app.login(RealmCredentials.emailPassword(email, password)) + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt new file mode 100644 index 0000000000..2ec24191ed --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt @@ -0,0 +1,359 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.entities.DefaultSyncSchema +import io.realm.entities.SyncDog +import io.realm.kotlin.where +import io.realm.kotlin.syncSession +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.rule.BlockingLooperThread +import org.junit.* +import org.junit.Assert.* +import org.junit.runner.RunWith +import java.util.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +@Ignore("These are generally flaky. We need to investigate further.") +@RunWith(AndroidJUnit4::class) +class ProgressListenerTests { + + companion object { + private const val TEST_SIZE: Long = 10 + } + + private val looperThread = BlockingLooperThread() + private lateinit var app: TestRealmApp + private lateinit var realm: Realm + private lateinit var partitionValue: String + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + RealmLog.setLevel(LogLevel.TRACE) + partitionValue = UUID.randomUUID().toString() + app = TestRealmApp() + } + + @After + fun tearDown() { + if (this::realm.isInitialized) { + realm.close() + } + if (this::app.isInitialized) { + app.close() + } + RealmLog.setLevel(LogLevel.WARN) + } + + @Ignore("See https://mongodb.slack.com/archives/CQLDYRJ3V/p1587563930459100") + @Test + fun downloadProgressListener_changesOnly() { + val allChangesDownloaded = CountDownLatch(1) + val user1: RealmUser = app.login(RealmCredentials.anonymous()) + val user1Config = createSyncConfig(user1) + createRemoteData(user1Config) + val user2: RealmUser = app.login(RealmCredentials.anonymous()) + val user2Config = createSyncConfig(user2) + val realm = Realm.getInstance(user2Config) + val session: SyncSession = realm.syncSession + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + RealmLog.error(progress.toString()) + if (progress.isTransferComplete) { + assertTransferComplete(progress, true) + assertEquals(TEST_SIZE, getStoreTestDataSize(user2Config)) + allChangesDownloaded.countDown() + } + } + TestHelper.awaitOrFail(allChangesDownloaded) + realm.close() + } + + @Test + fun downloadProgressListener_indefinitely() { + val transferCompleted = AtomicInteger(0) + val allChangesDownloaded = CountDownLatch(1) + val startWorker = CountDownLatch(1) + val user1: RealmUser = app.login(RealmCredentials.anonymous()) + val user1Config: SyncConfiguration = createSyncConfig(user1) + + // Create worker thread that puts data into another Realm. + // This is to avoid blocking one progress listener while waiting for another to complete. + val worker = Thread(Runnable { + TestHelper.awaitOrFail(startWorker) + createRemoteData(user1Config) + }) + worker.start() + val user2: RealmUser = app.login(RealmCredentials.anonymous()) + val user2Config: SyncConfiguration = createSyncConfig(user2) + val user2Realm = Realm.getInstance(user2Config) + val session: SyncSession = user2Realm.syncSession + session.addDownloadProgressListener(ProgressMode.INDEFINITELY) { progress -> + val objectCounts = getStoreTestDataSize(user2Config) + // The downloading progress listener could be triggered at the db version where only contains the meta + // data. So we start checking from when the first 10 objects downloaded. + RealmLog.warn(String.format( + Locale.ENGLISH, "downloadProgressListener_indefinitely download %d/%d objects count:%d", + progress.transferredBytes, progress.transferableBytes, objectCounts)) + if (objectCounts != 0L && progress.isTransferComplete) { + when (transferCompleted.incrementAndGet()) { + 1 -> { + assertEquals(TEST_SIZE, objectCounts) + assertTransferComplete(progress, true) + startWorker.countDown() + } + 2 -> { + assertTransferComplete(progress, true) + assertEquals(TEST_SIZE * 2, objectCounts) + allChangesDownloaded.countDown() + } + else -> fail("Transfer complete called too many times:" + transferCompleted.get()) + } + } + } + TestHelper.awaitOrFail(allChangesDownloaded) + user2Realm.close() + // worker thread will hang if logout happens before listener triggered. + worker.join() + user1.logOut() + user2.logOut() + } + + // Make sure that a ProgressListener continues to report the correct thing, even if it crashed + @Test + fun uploadListener_worksEvenIfCrashed() { + val transferCompleted = AtomicInteger(0) + val testDone = CountDownLatch(1) + val config = createSyncConfig() + val realm = Realm.getInstance(config) + writeSampleData(realm) // Write first batch of sample data + val session: SyncSession = realm.syncSession + session.addUploadProgressListener(ProgressMode.INDEFINITELY) { progress -> + if (progress.isTransferComplete) { + when (transferCompleted.incrementAndGet()) { + 1 -> { + val realm = Realm.getInstance(config) + writeSampleData(realm) + realm.close() + throw RuntimeException("Crashing the changelistener") + } + 2 -> { + assertTransferComplete(progress, true) + testDone.countDown() + } + else -> fail("Unsupported number of transfers completed: " + transferCompleted.get()) + } + } + } + TestHelper.awaitOrFail(testDone) + realm.close() + } + + @Test + fun uploadProgressListener_changesOnly() { + val allChangeUploaded = CountDownLatch(1) + val config = createSyncConfig() + val realm = Realm.getInstance(config) + writeSampleData(realm) + val session: SyncSession = realm.syncSession + assertEquals(SyncSession.State.ACTIVE, session.state) + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + RealmLog.error(progress.toString()); + if (progress.isTransferComplete) { + assertTransferComplete(progress, true) + allChangeUploaded.countDown() + } + } + TestHelper.awaitOrFail(allChangeUploaded) + realm.close() + } + + @Test + fun uploadProgressListener_indefinitely() { + val transferCompleted = AtomicInteger(0) + val testDone = CountDownLatch(1) + val config = createSyncConfig() + val realm = Realm.getInstance(config) + writeSampleData(realm) // Write first batch of sample data + val session: SyncSession = realm.syncSession + session.addUploadProgressListener(ProgressMode.INDEFINITELY) { progress -> + if (progress.isTransferComplete) { + when (transferCompleted.incrementAndGet()) { + 1 -> { + val realm = Realm.getInstance(config) + writeSampleData(realm) + realm.close() + } + 2 -> { + assertTransferComplete(progress, true) + testDone.countDown() + } + else -> fail("Unsupported number of transfers completed: " + transferCompleted.get()) + } + } + } + TestHelper.awaitOrFail(testDone) + realm.close() + } + + @Test + fun addListenerInsideCallback() { + val allChangeUploaded = CountDownLatch(1) + val config = createSyncConfig() + val realm = Realm.getInstance(config) + writeSampleData(realm) + val session: SyncSession = realm.syncSession + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + if (progress.isTransferComplete) { + val realm = Realm.getInstance(config) + writeSampleData(realm) + realm.close() + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + if (progress.isTransferComplete) { + allChangeUploaded.countDown() + } + } + } + } + TestHelper.awaitOrFail(allChangeUploaded) + realm.close() + } + + @Test + fun addListenerInsideCallback_mixProgressModes() { + val allChangeUploaded = CountDownLatch(3) + val progressCompletedReported = AtomicBoolean(false) + val config = createSyncConfig() + val realm = Realm.getInstance(config) + writeSampleData(realm) + val session: SyncSession = realm.syncSession + session.addUploadProgressListener(ProgressMode.INDEFINITELY) { progress -> + if (progress.isTransferComplete) { + allChangeUploaded.countDown() + if (progressCompletedReported.compareAndSet(false, true)) { + val realm = Realm.getInstance(config) + writeSampleData(realm) + realm.close() + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + if (progress.isTransferComplete) { + allChangeUploaded.countDown() + } + } + } + } + } + TestHelper.awaitOrFail(allChangeUploaded) + realm.close() + } + + @Test + fun addProgressListener_triggerImmediatelyWhenRegistered() { + val config = createSyncConfig() + val realm = Realm.getInstance(config) + val session: SyncSession = realm.syncSession + checkListener(session, ProgressMode.INDEFINITELY) + checkListener(session, ProgressMode.CURRENT_CHANGES) + realm.close() + } + + @Test + fun uploadListener_keepIncreasingInSize() { + val config = createSyncConfig() + val realm = Realm.getInstance(config) + val session: SyncSession = realm.syncSession + for (i in 0..9) { + val changesUploaded = CountDownLatch(1) + writeSampleData(realm) + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + RealmLog.info("Test %s -> %s", Integer.toString(i), progress.toString()) + if (progress.isTransferComplete) { + assertTransferComplete(progress, true) + changesUploaded.countDown() + } + } + TestHelper.awaitOrFail(changesUploaded) + } + realm.close() + } + + private fun checkListener(session: SyncSession, progressMode: ProgressMode) { + val listenerCalled = CountDownLatch(1) + session.addDownloadProgressListener(progressMode) { listenerCalled.countDown() } + TestHelper.awaitOrFail(listenerCalled) + } + + private fun writeSampleData(realm: Realm, partitionValue: String = getTestPartitionValue()) { + realm.beginTransaction() + for (i in 0 until TEST_SIZE) { + val obj = SyncDog() + obj.name = "Object $i" + realm.insert(obj) + } + realm.commitTransaction() + } + + private fun assertTransferComplete(progress: Progress, nonZeroChange: Boolean) { + assertTrue(progress.isTransferComplete) + assertEquals(1.0, progress.fractionTransferred, 0.0) + assertEquals(progress.transferableBytes, progress.transferredBytes) + if (nonZeroChange) { + assertTrue(progress.transferredBytes > 0) + } + } + + // Create remote data for a given user. + private fun createRemoteData(config: SyncConfiguration) { + val realm = Realm.getInstance(config) + val changesUploaded = CountDownLatch(1) + writeSampleData(realm) + val session: SyncSession = realm.syncSession + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, object : ProgressListener { + override fun onChange(progress: Progress) { + if (progress.isTransferComplete) { + session.removeProgressListener(this) + changesUploaded.countDown() + } + } + }) + TestHelper.awaitOrFail(changesUploaded) + realm.close() + } + + private fun getStoreTestDataSize(config: RealmConfiguration): Long { + val realm: Realm = Realm.getInstance(config) + val objectCounts: Long = realm.where().count() + realm.close() + return objectCounts + } + + private fun createSyncConfig(user: RealmUser = app.login(RealmCredentials.anonymous()), partitionValue: String = getTestPartitionValue()): SyncConfiguration { + return SyncConfiguration.Builder(user, partitionValue) + .modules(DefaultSyncSchema()) + .build() + } + + private fun getTestPartitionValue(): String { + if (!this::partitionValue.isInitialized) { + fail("Test not setup correctly. Partition value is missing"); + } + return partitionValue + } +} \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt new file mode 100644 index 0000000000..b92304137b --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt @@ -0,0 +1,155 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import java.io.File +import java.lang.IllegalArgumentException +import kotlin.test.assertFailsWith + +@RunWith(AndroidJUnit4::class) +class RealmAppConfigurationTests { + + // FIXME: Add tests for remaining builder methods + // builder.appName() + // builder.appVersion() + // builder.baseUrl() + // builder.defaultSyncErrorHandler() + // builder.encryptionKey() + // builder.logLevel() + // builder.requestTimeout() + // builder.syncRootDir() + + @get:Rule + val tempFolder = TemporaryFolder() + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + + @Test + fun authorizationHeaderName_illegalArgumentsThrows() { + val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + assertFailsWith { builder.authorizationHeaderName(TestHelper.getNull()) } + assertFailsWith { builder.authorizationHeaderName("") } + } + + @Test + fun authorizationHeaderName() { + val config1 = RealmAppConfiguration.Builder("app-id").build() + assertEquals("Authorization", config1.authorizationHeaderName) + + val config2 = RealmAppConfiguration.Builder("app-id") + .authorizationHeaderName("CustomAuth") + .build() + assertEquals("CustomAuth", config2.authorizationHeaderName) + } + + @Test + fun addCustomRequestHeader_illegalArgumentThrows() { + val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + assertFailsWith { builder.addCustomRequestHeader("", "val") } + assertFailsWith { builder.addCustomRequestHeader(TestHelper.getNull(), "val") } + assertFailsWith { builder.addCustomRequestHeader("header", TestHelper.getNull()) } + // FIXME: Add tests for illegally formatted headers. Figure out what legal headers look like. + } + + @Test + fun addCustomRequestHeader() { + val config = RealmAppConfiguration.Builder("app-id") + .addCustomRequestHeader("header1", "val1") + .addCustomRequestHeader("header2", "val2") + .build() + val headers: Map = config.customRequestHeaders + assertEquals(2, headers.size.toLong()) + assertTrue(headers.any { it.key == "header1" && it.value == "val1" }) + assertTrue(headers.any { it.key == "header2" && it.value == "val2" }) + } + + @Test + fun addCustomRequestHeaders() { + val inputHeaders: MutableMap = LinkedHashMap() + inputHeaders["header1"] = "value1" + inputHeaders["header2"] = "value2" + val config = RealmAppConfiguration.Builder("app-id") + .addCustomRequestHeaders(TestHelper.getNull()) + .addCustomRequestHeaders(inputHeaders) + .build() + val outputHeaders: Map = config.customRequestHeaders + assertEquals(2, outputHeaders.size.toLong()) + assertTrue(outputHeaders.any { it.key == "header1" && it.value == "value1" }) + assertTrue(outputHeaders.any { it.key == "header2" && it.value == "value2" }) + } + + @Test + fun addCustomHeader_combinesSingleAndMultiple() { + val config = RealmAppConfiguration.Builder("app-id") + .addCustomRequestHeader("header3", "val3") + .addCustomRequestHeaders(mapOf(Pair("header1", "val1"))) + .build() + val headers: Map = config.customRequestHeaders + assertEquals(2, headers.size) + assertTrue(headers.any { it.key == "header3" && it.value == "val3" }) + assertTrue(headers.any { it.key == "header1" && it.value == "val1" }) + } + + @Test + fun syncRootDirectory_default() { + val config = RealmAppConfiguration.Builder("app-id").build() + val expectedDefaultRoot = File(InstrumentationRegistry.getInstrumentation().targetContext.filesDir, "mongodb-realm") + assertEquals(expectedDefaultRoot, config.syncRootDirectory) + } + + @Test + fun syncRootDirectory() { + val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val expectedRoot = tempFolder.newFolder() + val config = builder + .syncRootDirectory(expectedRoot) + .build() + assertEquals(expectedRoot, config.syncRootDirectory) + } + + @Test + fun syncRootDirectory_null() { + val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + assertFailsWith { builder.syncRootDirectory(TestHelper.getNull()) } + } + + @Test + fun syncRootDirectory_writeProtectedDir() { + val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val dir = File("/") + assertFailsWith { builder.syncRootDirectory(dir) } + } + + @Test + fun syncRootDirectory_dirIsAFile() { + val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val file = File(tempFolder.newFolder(), "dummyfile") + assertTrue(file.createNewFile()) + assertFailsWith { builder.syncRootDirectory(file) } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt index 6d29c6053a..ab8b670921 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ package io.realm import io.realm.admin.ServerAdmin @@ -10,7 +25,8 @@ import io.realm.admin.ServerAdmin */ fun RealmApp.close() { ServerAdmin().deleteAllUsers() - SyncManager.reset() + this.syncManager.reset() + RealmApp.CREATED = false BaseRealm.applicationContext = null // Required for Realm.init() to work } @@ -19,6 +35,6 @@ fun RealmApp.close() { * This only works if users in the Realm Application are configured to be automatically confirmed. */ fun RealmApp.registerUserAndLogin(email: String, password: String): RealmUser { - emailPasswordAuthProvider.registerUser(email, password) + emailPasswordAuth.registerUser(email, password) return login(RealmCredentials.emailPassword(email, password)) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt index 5347fabd75..8051deac61 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -17,18 +17,15 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.admin.ServerAdmin -import io.realm.log.LogLevel -import io.realm.log.RealmLog import io.realm.rule.BlockingLooperThread -import io.realm.rule.RunInLooperThread -import io.realm.rule.RunTestInLooperThread import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith -import java.lang.IllegalArgumentException +import java.util.concurrent.atomic.AtomicReference +import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) class RealmAppTests { @@ -45,7 +42,9 @@ class RealmAppTests { @After fun tearDown() { - app.close() + if (this::app.isInitialized) { + app.close() + } } @Test @@ -62,17 +61,13 @@ class RealmAppTests { app.login(credentials) fail() } catch(ex: ObjectServerError) { - assertEquals(ErrorCode.AUTH_ERROR, ex.errorCode) + assertEquals(ErrorCode.SERVICE_UNKNOWN, ex.errorCode) } } @Test fun login_invalidArgsThrows() { - try { - app.login(TestHelper.getNull()) - fail() - } catch(ignore: IllegalArgumentException) { - } + assertFailsWith { app.login(TestHelper.getNull()) } } @Test @@ -87,7 +82,7 @@ class RealmAppTests { fun loginAsync_invalidUserThrows() = looperThread.runBlocking { app.loginAsync(RealmCredentials.emailPassword("foo", "bar")) { result -> assertFalse(result.isSuccess) - assertEquals(ErrorCode.AUTH_ERROR, result.error.errorCode) + assertEquals(ErrorCode.SERVICE_UNKNOWN, result.error.errorCode) looperThread.testComplete() } } @@ -207,4 +202,48 @@ class RealmAppTests { TODO("FIXME") } + @Test + fun authListener() { + val userRef = AtomicReference(null) + looperThread.runBlocking { + val authenticationListener = object : AuthenticationListener { + override fun loggedIn(user: RealmUser) { + userRef.set(user) + user.logOutAsync { /* Ignore */ } + } + + override fun loggedOut(user: RealmUser) { + assertEquals(userRef.get(), user) + looperThread.testComplete() + } + } + app.addAuthenticationListener(authenticationListener) + app.login(RealmCredentials.anonymous()) + } + } + + @Test + fun authListener_nullThrows() { + assertFailsWith { app.addAuthenticationListener(TestHelper.getNull()) } + } + + @Test + fun authListener_remove() = looperThread.runBlocking { + val failListener = object : AuthenticationListener { + override fun loggedIn(user: RealmUser) { fail() } + override fun loggedOut(user: RealmUser) { fail() } + } + val successListener = object : AuthenticationListener { + override fun loggedOut(user: RealmUser) { fail() } + override fun loggedIn(user: RealmUser) { looperThread.testComplete() } + } + // This test depends on listeners being executed in order which is an + // implementation detail, but there isn't a sure fire way to do this + // without depending on implementation details or assume a specific timing. + app.addAuthenticationListener(failListener) + app.addAuthenticationListener(successListener) + app.removeAuthenticationListener(failListener) + app.login(RealmCredentials.anonymous()) + } + } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt index 34ed2aed00..6a6aff2ddb 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt @@ -15,18 +15,22 @@ */ package io.realm -import io.realm.ErrorCode import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.After import org.junit.Assert.* import org.junit.BeforeClass import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) class RealmCredentialsTests { + private lateinit var app: RealmApp + + companion object { @BeforeClass @JvmStatic @@ -35,17 +39,14 @@ class RealmCredentialsTests { } } - inline fun expectException(method: () -> Unit) { - try { - method() - fail() - } catch (e: Throwable) { - if (e !is T) { - fail("Unexpected exception: $e") - } + @After + fun tearDown() { + if (this::app.isInitialized) { + app.close() } } + @Test fun anonymous() { val creds = RealmCredentials.anonymous() @@ -63,8 +64,8 @@ class RealmCredentialsTests { @Test fun apiKey_invalidInput() { - expectException { RealmCredentials.apiKey("") } - expectException { RealmCredentials.apiKey(TestHelper.getNull()) } + assertFailsWith { RealmCredentials.apiKey("") } + assertFailsWith { RealmCredentials.apiKey(TestHelper.getNull()) } } @Test @@ -76,8 +77,8 @@ class RealmCredentialsTests { @Test fun apple_invalidInput() { - expectException { RealmCredentials.apple("") } - expectException { RealmCredentials.apple(TestHelper.getNull()) } + assertFailsWith { RealmCredentials.apple("") } + assertFailsWith { RealmCredentials.apple(TestHelper.getNull()) } } @Ignore("FIXME: Awaiting ObjectStore support") @@ -103,10 +104,10 @@ class RealmCredentialsTests { @Test fun emailPassword_invalidInput() { - expectException { RealmCredentials.emailPassword("", "password") } - expectException { RealmCredentials.emailPassword("email", "") } - expectException { RealmCredentials.emailPassword(TestHelper.getNull(), "password") } - expectException { RealmCredentials.emailPassword("email", TestHelper.getNull()) } + assertFailsWith { RealmCredentials.emailPassword("", "password") } + assertFailsWith { RealmCredentials.emailPassword("email", "") } + assertFailsWith { RealmCredentials.emailPassword(TestHelper.getNull(), "password") } + assertFailsWith { RealmCredentials.emailPassword("email", TestHelper.getNull()) } } @Test @@ -118,8 +119,8 @@ class RealmCredentialsTests { @Test fun facebook_invalidInput() { - expectException { RealmCredentials.facebook("") } - expectException { RealmCredentials.facebook(TestHelper.getNull()) } + assertFailsWith { RealmCredentials.facebook("") } + assertFailsWith { RealmCredentials.facebook(TestHelper.getNull()) } } @Test @@ -131,8 +132,8 @@ class RealmCredentialsTests { @Test fun google_invalidInput() { - expectException { RealmCredentials.google("") } - expectException { RealmCredentials.google(TestHelper.getNull()) } + assertFailsWith { RealmCredentials.google("") } + assertFailsWith { RealmCredentials.google(TestHelper.getNull()) } } @Ignore("FIXME: Awaiting ObjectStore support") @@ -145,8 +146,8 @@ class RealmCredentialsTests { @Test fun jwt_invalidInput() { - expectException { RealmCredentials.jwt("") } - expectException { RealmCredentials.jwt(TestHelper.getNull()) } + assertFailsWith { RealmCredentials.jwt("") } + assertFailsWith { RealmCredentials.jwt(TestHelper.getNull()) } } fun expectErrorCode(app: RealmApp, expectedCode: ErrorCode, credentials: RealmCredentials) { @@ -160,55 +161,51 @@ class RealmCredentialsTests { @Test fun loginUsingCredentials() { - val app = TestRealmApp() - try { - RealmCredentials.IdentityProvider.values().forEach { provider -> - when(provider) { - RealmCredentials.IdentityProvider.ANONYMOUS -> { - val user = app.login(RealmCredentials.anonymous()) - assertNotNull(user) - } - RealmCredentials.IdentityProvider.API_KEY -> { - // FIXME: Wait for API Key support in OS + app = TestRealmApp() + RealmCredentials.IdentityProvider.values().forEach { provider -> + when(provider) { + RealmCredentials.IdentityProvider.ANONYMOUS -> { + val user = app.login(RealmCredentials.anonymous()) + assertNotNull(user) + } + RealmCredentials.IdentityProvider.API_KEY -> { + // FIXME: Wait for API Key support in OS // val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") // val key: RealmUserApiKey = app.apiKeyAuthProvider.createApiKey("my-key"); // val apiKeyUser = app.login(RealmCredentials.apiKey(key.value!!)) // assertNotNull(apiKeyUser) - } - RealmCredentials.IdentityProvider.CUSTOM_FUNCTION -> { - // FIXME Wait for Custom Function support - } - RealmCredentials.IdentityProvider.EMAIL_PASSWORD -> { - val email = TestHelper.getRandomEmail() - val password = "123456" - app.emailPasswordAuthProvider.registerUser(email, password) - val user = app.login(RealmCredentials.emailPassword(email, password)) - assertNotNull(user) - } - - // These providers are hard to test for real since they depend on a 3rd party - // login service. Instead we attempt to login and verify that a proper exception - // is thrown. At least that should verify that correctly formatted JSON is being - // sent across the wire. - RealmCredentials.IdentityProvider.FACEBOOK -> { - expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.facebook("facebook-token")) - } - RealmCredentials.IdentityProvider.APPLE -> { - expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.apple("apple-token")) - } - RealmCredentials.IdentityProvider.GOOGLE -> { - expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.google("google-token")) - } - RealmCredentials.IdentityProvider.JWT -> { - expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.jwt("jwt-token")) - } - RealmCredentials.IdentityProvider.UNKNOWN -> { - // Ignore - } + } + RealmCredentials.IdentityProvider.CUSTOM_FUNCTION -> { + // FIXME Wait for Custom Function support + } + RealmCredentials.IdentityProvider.EMAIL_PASSWORD -> { + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuth.registerUser(email, password) + val user = app.login(RealmCredentials.emailPassword(email, password)) + assertNotNull(user) + } + + // These providers are hard to test for real since they depend on a 3rd party + // login service. Instead we attempt to login and verify that a proper exception + // is thrown. At least that should verify that correctly formatted JSON is being + // sent across the wire. + RealmCredentials.IdentityProvider.FACEBOOK -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.facebook("facebook-token")) + } + RealmCredentials.IdentityProvider.APPLE -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.apple("apple-token")) + } + RealmCredentials.IdentityProvider.GOOGLE -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.google("google-token")) + } + RealmCredentials.IdentityProvider.JWT -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.jwt("jwt-token")) + } + RealmCredentials.IdentityProvider.UNKNOWN -> { + // Ignore } } - } finally { - app.close() } } } \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt index cbe0b000f7..3eb18f936c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt @@ -18,8 +18,6 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.admin.ServerAdmin import io.realm.rule.BlockingLooperThread -import io.realm.rule.RunInLooperThread -import io.realm.rule.RunTestInLooperThread import org.junit.After import org.junit.Assert.* import org.junit.Before @@ -46,7 +44,9 @@ class RealmUserTests { @After fun tearDown() { - app.close() + if (this::app.isInitialized) { + app.close() + } } @Test @@ -67,7 +67,7 @@ class RealmUserTests { assertEquals(RealmUser.State.LOGGED_IN, emailUser.state) emailUser.logOut() assertEquals(RealmUser.State.LOGGED_OUT, emailUser.state) - emailUser.removeUser() + emailUser.remove() assertEquals(RealmUser.State.REMOVED, emailUser.state) } @@ -122,8 +122,8 @@ class RealmUserTests { val email = TestHelper.getRandomEmail() val password = "123456" - app.emailPasswordAuthProvider.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled - var linkedUser: RealmUser = anonUser.linkUser(RealmCredentials.emailPassword(email, password)) + app.emailPasswordAuth.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled + var linkedUser: RealmUser = anonUser.linkCredentials(RealmCredentials.emailPassword(email, password)) assertTrue(anonUser === linkedUser) assertEquals(2, linkedUser.identities.size) assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) @@ -131,8 +131,8 @@ class RealmUserTests { val otherEmail = TestHelper.getRandomEmail() val otherPassword = "123456" - app.emailPasswordAuthProvider.registerUser(otherEmail, otherPassword) - linkedUser = anonUser.linkUser(RealmCredentials.emailPassword(email, password)) + app.emailPasswordAuth.registerUser(otherEmail, otherPassword) + linkedUser = anonUser.linkCredentials(RealmCredentials.emailPassword(email, password)) assertTrue(anonUser === linkedUser) assertEquals(3, linkedUser.identities.size) assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[2].provider) @@ -147,7 +147,7 @@ class RealmUserTests { val emailUser: RealmUser = app.registerUserAndLogin(email, password) val anonymousUser: RealmUser = app.login(RealmCredentials.anonymous()) try { - anonymousUser.linkUser(RealmCredentials.emailPassword(email, password)) + anonymousUser.linkCredentials(RealmCredentials.emailPassword(email, password)) fail() } catch (ex: ObjectServerError) { assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) @@ -158,7 +158,7 @@ class RealmUserTests { @Test fun linkUser_invalidArgsThrows() { try { - anonUser.linkUser(TestHelper.getNull()) + anonUser.linkCredentials(TestHelper.getNull()) fail() } catch (ignore: IllegalArgumentException) { } @@ -172,9 +172,9 @@ class RealmUserTests { assertEquals(1, user.identities.size) val email = TestHelper.getRandomEmail() val password = "123456" - app.emailPasswordAuthProvider.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled + app.emailPasswordAuth.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled looperThread.runBlocking { - anonUser.linkUserAsync(RealmCredentials.emailPassword(email, password)) { result -> + anonUser.linkCredentialsAsync(RealmCredentials.emailPassword(email, password)) { result -> val linkedUser: RealmUser = result.orThrow assertTrue(user === linkedUser) assertEquals(2, linkedUser.identities.size) @@ -188,7 +188,7 @@ class RealmUserTests { @Test fun linkUserAsync_throwsOnNonLooperThread() { try { - anonUser.linkUserAsync(RealmCredentials.emailPassword(TestHelper.getRandomEmail(), "123456")) { fail() } + anonUser.linkCredentialsAsync(RealmCredentials.emailPassword(TestHelper.getRandomEmail(), "123456")) { fail() } fail() } catch (ignore: java.lang.IllegalStateException) { } @@ -202,7 +202,7 @@ class RealmUserTests { val user1 = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertEquals(user1, app.currentUser()) assertEquals(1, app.allUsers().size) - user1.removeUser() + user1.remove() assertEquals(RealmUser.State.REMOVED, user1.state) assertNull(app.currentUser()) assertEquals(0, app.allUsers().size) @@ -212,7 +212,7 @@ class RealmUserTests { user2.logOut() assertNull(app.currentUser()) assertEquals(1, app.allUsers().size) - user2.removeUser() + user2.remove() assertEquals(RealmUser.State.REMOVED, user2.state) assertEquals(0, app.allUsers().size) } @@ -226,7 +226,7 @@ class RealmUserTests { val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertEquals(user, app.currentUser()) assertEquals(1, app.allUsers().size) - user.removeUserAsync { result -> + user.removeAsync { result -> assertEquals(RealmUser.State.REMOVED, result.orThrow.state) assertNull(app.currentUser()) assertEquals(0, app.allUsers().size) @@ -240,7 +240,7 @@ class RealmUserTests { user.logOut() assertNull(app.currentUser()) assertEquals(1, app.allUsers().size) - user.removeUserAsync { result -> + user.removeAsync { result -> assertEquals(RealmUser.State.REMOVED, result.orThrow.state) assertEquals(0, app.allUsers().size) looperThread.testComplete() @@ -252,7 +252,7 @@ class RealmUserTests { fun removeUserAsync_nonLooperThreadThrows() { val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "1234567") try { - user.removeUserAsync { fail() } + user.removeAsync { fail() } } catch (ignore: IllegalStateException) { } } @@ -260,13 +260,13 @@ class RealmUserTests { @Test fun getApiKeyAuthProvider() { val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - val provider1: ApiKeyAuthProvider = user.apiKeyAuthProvider + val provider1: ApiKeyAuth = user.apiKeyAuth assertEquals(user, provider1.user) user.logOut() try { - user.apiKeyAuthProvider + user.apiKeyAuth fail() } catch (ex: IllegalStateException) { } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt new file mode 100644 index 0000000000..246e85a22a --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt @@ -0,0 +1,27 @@ +/** + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities + +import io.realm.annotations.RealmModule + +const val defaultPartitionValue = "default" + +/** + * The set of classes initially supported by MongoDB Realm. + */ +@RealmModule(classes = [SyncDog::class, SyncPerson::class]) +class DefaultSyncSchema { +} \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncColor.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncColor.kt new file mode 100644 index 0000000000..4e2108806b --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncColor.kt @@ -0,0 +1,31 @@ +/** + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities + +import android.graphics.Color +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmField +import org.bson.types.ObjectId + +// FIXME: This class is just temporary as a smoke test for Sync. Should be removed once all Sync tests have been migrated. +open class SyncColor: RealmObject() { + @PrimaryKey + var _id: ObjectId = ObjectId.get() + @RealmField(name = "realm_id") + var realmId: String? = null + var color: String = Color.RED.toString() +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncDog.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncDog.kt new file mode 100644 index 0000000000..329fcde61f --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncDog.kt @@ -0,0 +1,37 @@ +/** + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmClass +import io.realm.annotations.RealmField +import org.bson.types.ObjectId + +open class SyncDog: RealmObject() { + @PrimaryKey + @RealmField(name = "_id") + var id: ObjectId? = ObjectId() + // This field is not required by clients + // But if added, it must always have the + // same value as the partition value + // used to open the Realm + // @RealmField(name = "realm_id") + // var realmId: String? = null + var breed: String? = null + var name: String = "" +} + diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncPerson.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncPerson.kt new file mode 100644 index 0000000000..5aec2a34d2 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncPerson.kt @@ -0,0 +1,40 @@ +/** + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmClass +import io.realm.annotations.RealmField +import io.realm.annotations.Required +import org.bson.types.ObjectId + +open class SyncPerson( + @PrimaryKey + @RealmField(name = "_id") + var id: ObjectId? = ObjectId(), + var age: Long = 0, + var dogs: RealmList = RealmList(), + var firstName: String = "", + var lastName: String = "" + // This field is not required by clients + // But if added, it must always have the + // same value as the partition value + // used to open the Realm + // @RealmField(name = "realm_id") + // var realmId: String? = null +): RealmObject() \ No newline at end of file diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt index a8c0de802b..482b36cc31 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt @@ -23,6 +23,7 @@ import io.realm.internal.objectstore.OsJavaNetworkTransport import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @@ -143,4 +144,17 @@ class OkHttpNetworkTransportTests { t.join() } } + + @Ignore("Add test for this") + @Test + fun customAuthorizationHeader() { + TODO("FIXME") + } + + @Ignore("Add test for this") + @Test + fun customHeaders() { + TODO("FIXME") + } + } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt index b3e67be3c3..003f6169ec 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -18,9 +18,11 @@ package io.realm.transport import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.* import io.realm.internal.objectstore.OsJavaNetworkTransport +import org.junit.After import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith +import java.util.* /** * This class is responsible for testing the general network transport layer, i.e. that @@ -36,6 +38,13 @@ class OsJavaNetworkTransportTests { private lateinit var app: RealmApp private val successHeaders: Map = mapOf(Pair("Content-Type", "application/json")) + @After + fun tearDown() { + if (this::app.isInitialized) { + app.close() + } + } + // Test that the round trip works in case of a successful HTTP request. @Test fun requestSuccess() { @@ -193,6 +202,5 @@ class OsJavaNetworkTransportTests { assertEquals("Boom!", ex.message) } } - } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt new file mode 100644 index 0000000000..7654efd98f --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt @@ -0,0 +1,20 @@ +package io.realm.util + +import io.realm.ErrorCode +import io.realm.ObjectServerError +import org.junit.Assert.assertEquals +import org.junit.Assert.fail + +// Helper methods for improving Kotlin unit tests. + +/** + * Verify that an [ObjectServerError] exception is thrown with a specific [ErrorCode] + */ +inline fun expectErrorCode(expectedCode: ErrorCode, method: () -> Unit) { + try { + method() + fail() + } catch (e: ObjectServerError) { + assertEquals("Unexpected error code", expectedCode, e.errorCode) + } +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index cf46b45a7a..01553e4ac0 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -103,13 +103,12 @@ set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST io.realm.ClientResetRequiredError - io.realm.EmailPasswordAuthProvider - io.realm.ApiKeyAuthProvider + io.realm.EmailPasswordAuth + io.realm.ApiKeyAuth io.realm.RealmApp io.realm.RealmUser - io.realm.SyncManager + io.realm.RealmSync io.realm.SyncSession - io.realm.SyncUser io.realm.internal.objectstore.OsAppCredentials io.realm.internal.objectstore.OsAsyncOpenTask io.realm.internal.objectstore.OsJavaNetworkTransport @@ -196,11 +195,11 @@ if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmApp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmUser.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_EmailPasswordAuthProvider.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ApiKeyAuthProvider.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_EmailPasswordAuth.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ApiKeyAuth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetRequiredError.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmSync.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAsyncOpenTask.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAppCredentials.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_ApiKeyAuthProvider.cpp b/realm/realm-library/src/main/cpp/io_realm_ApiKeyAuth.cpp similarity index 87% rename from realm/realm-library/src/main/cpp/io_realm_ApiKeyAuthProvider.cpp rename to realm/realm-library/src/main/cpp/io_realm_ApiKeyAuth.cpp index 23d6c215ae..c0182f7a82 100644 --- a/realm/realm-library/src/main/cpp/io_realm_ApiKeyAuthProvider.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_ApiKeyAuth.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_ApiKeyAuthProvider.h" +#include "io_realm_ApiKeyAuth.h" #include "java_class_global_def.hpp" #include "java_network_transport.hpp" @@ -64,49 +64,49 @@ static std::function)> multi_key_m return arr; }; -JNIEXPORT void JNICALL Java_io_realm_ApiKeyAuthProvider_nativeCallFunction(JNIEnv* env, - jclass, - jint j_function_type, - jlong j_app_ptr, - jlong j_user_ptr, - jstring j_arg, - jobject j_callback) +JNIEXPORT void JNICALL Java_io_realm_ApiKeyAuth_nativeCallFunction(JNIEnv* env, + jclass, + jint j_function_type, + jlong j_app_ptr, + jlong j_user_ptr, + jstring j_arg, + jobject j_callback) { try { - App* app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); auto user = *reinterpret_cast*>(j_user_ptr); auto client = app->provider_client(); switch(j_function_type) { - case io_realm_ApiKeyAuthProvider_TYPE_CREATE: { + case io_realm_ApiKeyAuth_TYPE_CREATE: { JStringAccessor name(env, j_arg); auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, single_key_mapper); client.create_api_key(name, user, callback); break; } - case io_realm_ApiKeyAuthProvider_TYPE_FETCH_SINGLE: { + case io_realm_ApiKeyAuth_TYPE_FETCH_SINGLE: { auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, single_key_mapper); std::string str_id = JStringAccessor(env, static_cast(j_arg)); client.fetch_api_key(ObjectId(str_id.c_str()), user, callback); break; } - case io_realm_ApiKeyAuthProvider_TYPE_FETCH_ALL: { + case io_realm_ApiKeyAuth_TYPE_FETCH_ALL: { auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, multi_key_mapper); client.fetch_api_keys(user, callback); break; } - case io_realm_ApiKeyAuthProvider_TYPE_DELETE: { + case io_realm_ApiKeyAuth_TYPE_DELETE: { auto callback = JavaNetworkTransport::create_void_callback(env, j_callback); std::string str_id = JStringAccessor(env, static_cast(j_arg)); client.delete_api_key(ObjectId(str_id.c_str()), user, callback); break; } - case io_realm_ApiKeyAuthProvider_TYPE_ENABLE: { + case io_realm_ApiKeyAuth_TYPE_ENABLE: { auto callback = JavaNetworkTransport::create_void_callback(env, j_callback); std::string str_id = JStringAccessor(env, static_cast(j_arg)); client.enable_api_key(ObjectId(str_id.c_str()), user, callback); break; } - case io_realm_ApiKeyAuthProvider_TYPE_DISABLE: { + case io_realm_ApiKeyAuth_TYPE_DISABLE: { auto callback = JavaNetworkTransport::create_void_callback(env, j_callback); std::string str_id = JStringAccessor(env, static_cast(j_arg)); client.disable_api_key(ObjectId(str_id.c_str()), user, callback); diff --git a/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuthProvider.cpp b/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp similarity index 74% rename from realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuthProvider.cpp rename to realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp index 8c3bb697c5..1f73a7c95a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuthProvider.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_EmailPasswordAuthProvider.h" +#include "io_realm_EmailPasswordAuth.h" #include "java_network_transport.hpp" #include "util.hpp" @@ -28,34 +28,34 @@ using namespace realm::app; using namespace realm::jni_util; using namespace realm::_impl; -JNIEXPORT void JNICALL Java_io_realm_EmailPasswordAuthProvider_nativeCallFunction(JNIEnv* env, - jclass, - jint j_function_type, - jlong j_app_ptr, - jobject j_callback, - jobjectArray j_args) +JNIEXPORT void JNICALL Java_io_realm_EmailPasswordAuth_nativeCallFunction(JNIEnv* env, + jclass, + jint j_function_type, + jlong j_app_ptr, + jobject j_callback, + jobjectArray j_args) { try { - App* app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); JObjectArrayAccessor args(env, j_args); auto client = app->provider_client(); switch(j_function_type) { - case io_realm_EmailPasswordAuthProvider_TYPE_REGISTER_USER: + case io_realm_EmailPasswordAuth_TYPE_REGISTER_USER: client.register_email(args[0], args[1], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuthProvider_TYPE_CONFIRM_USER: + case io_realm_EmailPasswordAuth_TYPE_CONFIRM_USER: client.confirm_user(args[0], args[1], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuthProvider_TYPE_RESEND_CONFIRMATION_EMAIL: + case io_realm_EmailPasswordAuth_TYPE_RESEND_CONFIRMATION_EMAIL: client.resend_confirmation_email(args[0], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuthProvider_TYPE_SEND_RESET_PASSWORD_EMAIL: + case io_realm_EmailPasswordAuth_TYPE_SEND_RESET_PASSWORD_EMAIL: client.send_reset_password_email(args[0], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuthProvider_TYPE_CALL_RESET_PASSWORD_FUNCTION: + case io_realm_EmailPasswordAuth_TYPE_CALL_RESET_PASSWORD_FUNCTION: client.call_reset_password_function(args[0], args[1], args[2], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuthProvider_TYPE_RESET_PASSWORD: + case io_realm_EmailPasswordAuth_TYPE_RESET_PASSWORD: client.reset_password(args[0], args[1], args[2], JavaNetworkTransport::create_void_callback(env, j_callback)); break; default: diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp index 85eb37b684..346c904920 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp @@ -21,21 +21,81 @@ #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" +#include #include +#include using namespace realm; using namespace realm::app; using namespace realm::jni_util; using namespace realm::_impl; +struct AndroidClientListener : public realm::BindingCallbackThreadObserver { + AndroidClientListener(JNIEnv* env) + : m_realm_exception_class(env, "io/realm/exceptions/RealmError") + { + } + + void did_create_thread() override + { + Log::d("SyncClient thread created"); + // Attach the sync client thread to the JVM so errors can be returned properly + JniUtils::get_env(true); + } + + void will_destroy_thread() override + { + // avoid allocating any NewString if we have a pending exception + // otherwise a "JNI called with pending exception" will be called + if (JniUtils::get_env(true)->ExceptionCheck() == JNI_FALSE) { + Log::d("SyncClient thread destroyed"); + } + + // Failing to detach the JVM before closing the thread will crash on ART + JniUtils::detach_current_thread(); + } + + void handle_error(std::exception const& e) override + { + JNIEnv* env = JniUtils::get_env(true); + std::string msg = format("An exception has been thrown on the sync client thread:\n%1", e.what()); + Log::f(msg.c_str()); + // Since user has no way to handle exceptions thrown on the sync client thread, we just convert it to a Java + // exception to get more debug information for ourself. + // FIXME: We really need to find a universal and clever way to get the native backtrace when exception thrown + env->ThrowNew(m_realm_exception_class, msg.c_str()); + } + +private: + // FindClass() doesn't work in the native thread even when the JVM is attached before, due to it + // using another ClassLoader. So we get the RealmError class on a normal JVM thread and throw it + // later on the sync client thread. + JavaClass m_realm_exception_class; +}; + +struct AndroidSyncLoggerFactory : public realm::SyncLoggerFactory { + // The level param is ignored. Use the global RealmLog.setLevel() to control all log levels. + std::unique_ptr make_logger(Logger::Level) override + { + auto logger = std::make_unique(std::string("REALM_SYNC")); + // Cast to std::unique_ptr + return std::move(logger); + } +} s_sync_logger_factory; + JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject obj, jstring j_app_id, jstring j_base_url, jstring j_app_name, jstring j_app_version, - jlong j_request_timeout_ms) + jlong j_request_timeout_ms, + jstring j_sync_base_dir, + jstring j_user_agent_binding_info, + jstring j_user_agent_application_info) { try { + + // App Config jobject java_app_obj = env->NewGlobalRef(obj); // FIXME: Leaking the app object std::function()> transport_generator = [java_app_obj] { JNIEnv* env = JniUtils::get_env(true); @@ -48,14 +108,36 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject JStringAccessor base_url(env, j_base_url); JStringAccessor app_name(env, j_app_name); JStringAccessor app_version(env, j_app_version); - return reinterpret_cast(new App(App::Config{ + + auto app_config = App::Config{ app_id, transport_generator, util::Optional(base_url), util::Optional(app_name), util::Optional(app_version), util::Optional(j_request_timeout_ms) - })); + }; + + // Sync Config + JStringAccessor base_file_path(env, j_sync_base_dir); // throws + JStringAccessor user_agent_binding_info(env, j_user_agent_binding_info); // throws + JStringAccessor user_agent_application_info(env, j_user_agent_application_info); // throws + + SyncClientConfig client_config; + client_config.base_file_path = base_file_path; + client_config.metadata_mode = SyncManager::MetadataMode::NoEncryption; + client_config.user_agent_binding_info = user_agent_binding_info; + client_config.user_agent_application_info = user_agent_application_info; + + // FIXME: SyncManager is still a singleton. Should be refactored to allow multiple + SyncManager::shared().configure(client_config, app_config); + // Init logger. Must be called after .configure() + SyncManager::shared().set_logger_factory(s_sync_logger_factory); + // Register Sync Client thread start/stop callback. Must be called after .configure() + static AndroidClientListener client_thread_listener(env); + g_binding_callback_thread_observer = &client_thread_listener; + + return reinterpret_cast(new std::shared_ptr(SyncManager::shared().app())); } CATCH_STD() return 0; @@ -65,7 +147,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogin(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_credentials_ptr, jobject j_callback) { try { - App *app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); auto credentials = reinterpret_cast(j_credentials_ptr); std::function)> mapper = [](JNIEnv* env, std::shared_ptr user) { auto* java_user = new std::shared_ptr(std::move(user)); @@ -80,7 +162,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogin(JNIEnv* env, jclass, j JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) { try { - App* app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); auto user = *reinterpret_cast*>(j_user_ptr); app->log_out(user, JavaNetworkTransport::create_void_callback(env, j_callback)); } @@ -90,7 +172,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogOut(JNIEnv* env, jclass, JNIEXPORT jobject JNICALL Java_io_realm_RealmApp_nativeCurrentUser(JNIEnv* env, jclass, jlong j_app_ptr) { try { - App* app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); std::shared_ptr user = app->current_user(); if (user) { auto* java_user = new std::shared_ptr(std::move(user)); @@ -107,7 +189,7 @@ JNIEXPORT jobject JNICALL Java_io_realm_RealmApp_nativeCurrentUser(JNIEnv* env, JNIEXPORT jlongArray JNICALL Java_io_realm_RealmApp_nativeGetAllUsers(JNIEnv* env, jclass, jlong j_app_ptr) { try { - App *app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); std::vector> users = app->all_users(); auto size = users.size(); @@ -137,9 +219,10 @@ JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeSwitchUser(JNIEnv* env, jlong j_user_ptr) { try { - App* app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); auto user = *reinterpret_cast*>(j_user_ptr); app->switch_user(user); } CATCH_STD() } + diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmSync.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmSync.cpp new file mode 100644 index 0000000000..1b3787f32e --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_RealmSync.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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 "io_realm_RealmSync.h" + +#include +#include +#include +#include + +#include "util.hpp" +#include "jni_util/java_class.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" + +using namespace realm; +using namespace realm::jni_util; +using namespace realm::util; + +JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeReset(JNIEnv* env, jclass) +{ + try { + SyncManager::shared().reset_for_testing(); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeSimulateSyncError(JNIEnv* env, jclass, jstring local_realm_path, + jint err_code, jstring err_message, + jboolean is_fatal) +{ + try { + JStringAccessor path(env, local_realm_path); + JStringAccessor message(env, err_message); + + auto session = SyncManager::shared().get_existing_active_session(path); + if (!session) { + ThrowException(env, IllegalArgument, concat_stringdata("Session not found: ", path)); + return; + } + std::error_code code = std::error_code{static_cast(err_code), realm::sync::protocol_error_category()}; + SyncSession::OnlyForTesting::handle_error(*session, {code, std::string(message), to_bool(is_fatal)}); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeReconnect(JNIEnv* env, jclass) +{ + try { + SyncManager::shared().reconnect(); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeCreateSession(JNIEnv* env, jclass, jlong j_native_config_ptr) +{ + try { + auto& config = *reinterpret_cast(j_native_config_ptr); + _impl::RealmCoordinator::get_coordinator(config)->create_session(config); + } + CATCH_STD() +} \ No newline at end of file diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp index 7af12209d8..0a2822f0a4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp @@ -36,7 +36,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeLinkUser(JNIEnv* env, jobject j_callback) { try { - App* app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); auto user = *reinterpret_cast*>(j_user_ptr); auto credentials = reinterpret_cast(j_credentials_ptr); std::function)> mapper = [](JNIEnv* env, std::shared_ptr user) { @@ -56,7 +56,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeRemoveUser(JNIEnv* env, jobject j_callback) { try { - App* app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); auto user = *reinterpret_cast*>(j_user_ptr); app->remove_user(user, JavaNetworkTransport::create_void_callback(env, j_callback)); } @@ -66,7 +66,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeRemoveUser(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) { try { - App* app = reinterpret_cast(j_app_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); auto user = *reinterpret_cast*>(j_user_ptr); app->log_out(user, JavaNetworkTransport::create_void_callback(env, j_callback)); } diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp deleted file mode 100644 index f87c968aad..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_SyncManager.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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 "io_realm_SyncManager.h" - -#include -#include -#include -#include - -#include "util.hpp" -#include "jni_util/java_class.hpp" -#include "jni_util/java_method.hpp" -#include "jni_util/jni_utils.hpp" - -using namespace realm; -using namespace realm::jni_util; -using namespace realm::util; - -struct AndroidClientListener : public realm::BindingCallbackThreadObserver { - AndroidClientListener(JNIEnv* env) - : m_realm_exception_class(env, "io/realm/exceptions/RealmError") - { - } - - void did_create_thread() override - { - Log::d("SyncClient thread created"); - // Attach the sync client thread to the JVM so errors can be returned properly - JniUtils::get_env(true); - } - - void will_destroy_thread() override - { - // avoid allocating any NewString if we have a pending exception - // otherwise a "JNI called with pending exception" will be called - if (JniUtils::get_env(true)->ExceptionCheck() == JNI_FALSE) { - Log::d("SyncClient thread destroyed"); - } - - // Failing to detach the JVM before closing the thread will crash on ART - JniUtils::detach_current_thread(); - } - - void handle_error(std::exception const& e) override - { - JNIEnv* env = JniUtils::get_env(true); - std::string msg = format("An exception has been thrown on the sync client thread:\n%1", e.what()); - Log::f(msg.c_str()); - // Since user has no way to handle exceptions thrown on the sync client thread, we just convert it to a Java - // exception to get more debug information for ourself. - // FIXME: We really need to find a universal and clever way to get the native backtrace when exception thrown - env->ThrowNew(m_realm_exception_class, msg.c_str()); - } - -private: - // For some reasons, FindClass() doesn't work in the native thread even when the JVM is attached before. Get the - // RealmError class on a normal JVM thread and throw it later on the sync client thread. - JavaClass m_realm_exception_class; -}; - -struct AndroidSyncLoggerFactory : public realm::SyncLoggerFactory { - // The level param is ignored. Use the global RealmLog.setLevel() to control all log levels. - std::unique_ptr make_logger(Logger::Level) override - { - auto logger = std::make_unique(std::string("REALM_SYNC")); - // Cast to std::unique_ptr - return std::move(logger); - } -} s_sync_logger_factory; - -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReset(JNIEnv* env, jclass) -{ - try { - SyncManager::shared().reset_for_testing(); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeInitializeSyncManager(JNIEnv* env, jclass, - jstring j_sync_base_dir, - jstring j_user_agent_binding_info, - jstring j_user_agent_application_info) -{ - try { - JStringAccessor base_file_path(env, j_sync_base_dir); // throws - JStringAccessor user_agent_binding_info(env, j_user_agent_binding_info); // throws - JStringAccessor user_agent_application_info(env, j_user_agent_application_info); // throws - - SyncClientConfig client_config; - client_config.base_file_path = base_file_path; - client_config.metadata_mode = SyncManager::MetadataMode::NoEncryption; - client_config.user_agent_binding_info = user_agent_binding_info; - client_config.user_agent_application_info = user_agent_application_info; - SyncManager::shared().configure(client_config); - - static AndroidClientListener client_thread_listener(env); - // Register Sync Client thread start/stop callback - g_binding_callback_thread_observer = &client_thread_listener; - - // init logger - SyncManager::shared().set_logger_factory(s_sync_logger_factory); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeSimulateSyncError(JNIEnv* env, jclass, jstring local_realm_path, - jint err_code, jstring err_message, - jboolean is_fatal) -{ - try { - JStringAccessor path(env, local_realm_path); - JStringAccessor message(env, err_message); - - auto session = SyncManager::shared().get_existing_active_session(path); - if (!session) { - ThrowException(env, IllegalArgument, concat_stringdata("Session not found: ", path)); - return; - } - std::error_code code = std::error_code{static_cast(err_code), realm::sync::protocol_error_category()}; - SyncSession::OnlyForTesting::handle_error(*session, {code, std::string(message), to_bool(is_fatal)}); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeReconnect(JNIEnv* env, jclass) -{ - try { - SyncManager::shared().reconnect(); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL Java_io_realm_SyncManager_nativeCreateSession(JNIEnv* env, jclass, jlong j_native_config_ptr) -{ - try { - auto& config = *reinterpret_cast(j_native_config_ptr); - _impl::RealmCoordinator::get_coordinator(config)->create_session(config); - } - CATCH_STD() -} \ No newline at end of file diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp index 006fd94d10..de94e52651 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp @@ -35,9 +35,6 @@ using namespace realm::jni_util; using namespace realm::sync; using namespace realm::_impl; -static_assert(SyncSession::PublicState::WaitingForAccessToken == - static_cast(io_realm_SyncSession_STATE_VALUE_WAITING_FOR_ACCESS_TOKEN), - ""); static_assert(SyncSession::PublicState::Active == static_cast(io_realm_SyncSession_STATE_VALUE_ACTIVE), ""); @@ -58,30 +55,7 @@ static_assert(SyncSession::ConnectionState::Connected == static_cast(io_realm_SyncSession_CONNECTION_VALUE_CONNECTED), ""); -JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeRefreshAccessToken(JNIEnv* env, jclass, - jstring j_local_realm_path, - jstring j_access_token, - jstring j_sync_realm_url) -{ - try { - JStringAccessor local_realm_path(env, j_local_realm_path); - auto session = SyncManager::shared().get_existing_session(local_realm_path); - if (session) { - JStringAccessor access_token(env, j_access_token); - JStringAccessor realm_url(env, j_sync_realm_url); - - session->refresh_access_token(access_token, session->config().realm_url); - return JNI_TRUE; - } - else { - Log::d("no active/inactive session found"); - } - } - CATCH_STD() - return JNI_FALSE; -} - -JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIEnv* env, jclass, +JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIEnv* env, jobject j_session_object, jstring j_local_realm_path, jlong listener_id, jint direction, jboolean is_streaming) @@ -98,20 +72,21 @@ JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIE return 0; } - SyncSession::NotifierType type = - (direction == 1) ? SyncSession::NotifierType::download : SyncSession::NotifierType::upload; + SyncSession::NotifierType type = (direction == 1) ? SyncSession::NotifierType::download : SyncSession::NotifierType::upload; - static JavaClass java_syncmanager_class(env, "io/realm/SyncManager"); - static JavaMethod java_notify_progress_listener(env, java_syncmanager_class, "notifyProgressListener", "(Ljava/lang/String;JJJ)V", true); + static JavaClass java_syncsession_class(env, "io/realm/SyncSession"); + static JavaMethod java_notify_progress_listener(env, java_syncsession_class, "notifyProgressListener", "(JJJ)V"); - std::function callback = [local_realm_path, listener_id]( - uint64_t transferred, uint64_t transferrable) { + auto session_ref = env->NewGlobalRef(j_session_object); // This leaks. FIXME + std::function callback = [session_ref, local_realm_path, listener_id](uint64_t transferred, uint64_t transferrable) { JNIEnv* local_env = jni_util::JniUtils::get_env(true); JavaLocalRef path(local_env, to_jstring(local_env, local_realm_path)); - local_env->CallStaticVoidMethod(java_syncmanager_class, java_notify_progress_listener, path.get(), - listener_id, static_cast(transferred), - static_cast(transferrable)); + local_env->CallVoidMethod(session_ref, + java_notify_progress_listener, + listener_id, + static_cast(transferred), + static_cast(transferrable)); // All exceptions will be caught on the Java side of handlers, but Errors will still end // up here, so we need to do something sensible with them. @@ -220,8 +195,6 @@ JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetState(JNIEnv* env, jc if (session) { switch (session->state()) { - case SyncSession::PublicState::WaitingForAccessToken: - return io_realm_SyncSession_STATE_VALUE_WAITING_FOR_ACCESS_TOKEN; case SyncSession::PublicState::Active: return io_realm_SyncSession_STATE_VALUE_ACTIVE; case SyncSession::PublicState::Dying: @@ -279,7 +252,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddConnectionListener(JN return 0; } - static JavaClass java_syncmanager_class(env, "io/realm/SyncManager"); + static JavaClass java_syncmanager_class(env, "io/realm/RealmSync"); static JavaMethod java_notify_connection_listener(env, java_syncmanager_class, "notifyConnectionListeners", "(Ljava/lang/String;JJ)V", true); std::function callback = [local_realm_path](SyncSession::ConnectionState old_state, SyncSession::ConnectionState new_state) { @@ -350,16 +323,3 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStop(JNIEnv* env, jclass, } CATCH_STD() } - -JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeSetUrlPrefix(JNIEnv* env, jclass, jstring j_local_realm_path, jstring j_url_prefix) -{ - try { - JStringAccessor local_realm_path(env, j_local_realm_path); - auto session = SyncManager::shared().get_existing_session(local_realm_path); - if (session) { - JStringAccessor url_prefix(env, j_url_prefix); - session->set_url_prefix(url_prefix); - } - } - CATCH_STD() -} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 9b1a1a3909..d88755db73 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -18,6 +18,7 @@ #include #if REALM_ENABLE_SYNC +#include #include #include #include @@ -38,6 +39,7 @@ using namespace realm; using namespace realm::jni_util; using namespace realm::_impl; + static_assert(SchemaMode::Automatic == static_cast(io_realm_internal_OsRealmConfig_SCHEMA_MODE_VALUE_AUTOMATIC), ""); @@ -244,23 +246,23 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNo JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( JNIEnv* env, jclass, jlong native_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, jstring j_refresh_token, jstring j_access_token, jbyte j_session_stop_policy, jstring j_url_prefix, - jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array, jbyte j_client_reset_mode) + jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array, jbyte j_client_reset_mode, + jstring j_partion_key_value, jobject j_java_sync_service) { auto& config = *reinterpret_cast(native_ptr); // sync_config should only be initialized once! REALM_ASSERT(!config.sync_config); try { - static JavaClass sync_manager_class(env, "io/realm/SyncManager"); + static JavaClass sync_manager_class(env, "io/realm/RealmSync"); // Doing the methods lookup from the thread that loaded the lib, to avoid // https://developer.android.com/training/articles/perf-jni.html#faq_FindClass static JavaMethod java_error_callback_method(env, sync_manager_class, "notifyErrorHandler", - "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V", true); - static JavaMethod java_bind_session_method(env, sync_manager_class, "bindSessionWithConfig", - "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", true); + "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); // error handler will be called form the sync client thread - auto error_handler = [](std::shared_ptr session, SyncError error) { + auto sync_service_object = env->NewGlobalRef(j_java_sync_service); // FIXME: This object is leaking + auto error_handler = [sync_service_object](std::shared_ptr session, SyncError error) { auto error_category = error.error_code.category().name(); auto error_message = error.message; auto error_code = error.error_code.value(); @@ -308,36 +310,13 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe jstring jerror_category = to_jstring(env, error_category); jstring jerror_message = to_jstring(env, error_message); jstring jsession_path = to_jstring(env, session.get()->path()); - env->CallStaticVoidMethod(sync_manager_class, java_error_callback_method, jerror_category, error_code, jerror_message, + env->CallVoidMethod(sync_service_object, java_error_callback_method, jerror_category, error_code, jerror_message, jsession_path); env->DeleteLocalRef(jerror_category); env->DeleteLocalRef(jerror_message); env->DeleteLocalRef(jsession_path); }; - // path on disk of the Realm file. - // the sync configuration object. - // the session which should be bound. - auto bind_handler = [](const std::string& path, const SyncConfig& syncConfig, - std::shared_ptr session) { - realm::jni_util::Log::d("Callback to Java requesting token for path: %1", path.c_str()); - - JNIEnv* env = realm::jni_util::JniUtils::get_env(true); - - jstring jpath = to_jstring(env, path.c_str()); - jstring jrefresh_token = to_jstring(env, session->user()->refresh_token().c_str()); - jstring access_token_string = (jstring)env->CallStaticObjectMethod( - sync_manager_class, java_bind_session_method, jpath, jrefresh_token); - if (access_token_string) { - // reusing cached valid token - JStringAccessor access_token(env, access_token_string); - session->refresh_access_token(access_token, realm::util::Optional(syncConfig.realm_url)); - env->DeleteLocalRef(access_token_string); - } - env->DeleteLocalRef(jpath); - env->DeleteLocalRef(jrefresh_token); - }; - // Get logged in user JStringAccessor user_id(env, j_user_id); JStringAccessor auth_url(env, j_auth_url); @@ -346,16 +325,15 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe JStringAccessor realm_auth_url(env, j_auth_url); JStringAccessor refresh_token(env, j_refresh_token); JStringAccessor access_token(env, j_access_token); - // FIXME RealmApp refactor user = SyncManager::shared().get_user(user_id, auth_url, refresh_token, access_token); } SyncSessionStopPolicy session_stop_policy = static_cast(j_session_stop_policy); JStringAccessor realm_url(env, j_sync_realm_url); - config.sync_config = std::make_shared(SyncConfig{user, realm_url}); + JStringAccessor partion_key_value(env, j_partion_key_value); + config.sync_config = std::make_shared(SyncConfig{user, partion_key_value}); config.sync_config->stop_policy = session_stop_policy; - config.sync_config->bind_session_handler = std::move(bind_handler); config.sync_config->error_handler = std::move(error_handler); switch (j_client_reset_mode) { case io_realm_internal_OsRealmConfig_CLIENT_RESYNC_MODE_RECOVER: config.sync_config->client_resync_mode = realm::ClientResyncMode::Recover; break; @@ -366,7 +344,8 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe if (j_url_prefix) { JStringAccessor url_prefix(env, j_url_prefix); - config.sync_config->url_prefix = realm::util::Optional(url_prefix); + (void) url_prefix; + // config.sync_config->url_prefix = realm::util::Optional(url_prefix); } if (j_custom_auth_header_name) { @@ -388,7 +367,9 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe std::copy_n(config.encryption_key.begin(), 64, config.sync_config->realm_encryption_key->begin()); } - return to_jstring(env, config.sync_config->realm_url.c_str()); + // return to_jstring(env, config.sync_config->realm_url.c_str()); + // FIXME: We must return the realm url here for proxy support to work + return to_jstring(env, ""); } CATCH_STD() return nullptr; @@ -412,7 +393,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigS } else if (config.sync_config->client_validate_ssl) { // set default callback to allow Android to check the certificate - static JavaClass sync_manager_class(env, "io/realm/SyncManager"); + static JavaClass sync_manager_class(env, "io/realm/RealmSync"); static JavaMethod java_ssl_verify_callback(env, sync_manager_class, "sslVerifyCallback", "(Ljava/lang/String;Ljava/lang/String;I)Z", true); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 88138d8109..2d3a3c3630 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 88138d8109e4411aff63db67253064715ec3ec33 +Subproject commit 2d3a3c3630c08f37ea85fe8b2c089e756f84dfa8 diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index a17800acd3..11e87af2cd 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -20,7 +20,6 @@ import java.lang.reflect.InvocationTargetException; -import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.exceptions.RealmException; @@ -31,6 +30,8 @@ */ public class ObjectServerFacade { + public final static int SYNC_CONFIG_OPTIONS = 14; + private final static ObjectServerFacade nonSyncFacade = new ObjectServerFacade(); private static ObjectServerFacade syncFacade = null; @@ -66,7 +67,7 @@ public void realmClosed(RealmConfiguration configuration) { } public Object[] getSyncConfigurationOptions(RealmConfiguration config) { - return new Object[12]; + return new Object[SYNC_CONFIG_OPTIONS]; } public static ObjectServerFacade getFacade(boolean needSyncFacade) { @@ -119,4 +120,5 @@ public boolean wasDownloadInterrupted(Throwable throwable) { public void createNativeSyncSession(RealmConfiguration configuration) { // Do nothing } + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index b9692bdf22..1991b2a0af 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -218,6 +218,8 @@ private OsRealmConfig(final RealmConfiguration config, String urlPrefix = (String)(syncConfigurationOptions[8]); String customAuthorizationHeaderName = (String)(syncConfigurationOptions[9]); Byte clientResyncMode = (Byte) syncConfigurationOptions[11]; + String partitionValue = (String) syncConfigurationOptions[12]; + Object syncService = syncConfigurationOptions[13]; // Convert the headers into a String array to make it easier to send through JNI // [key1, value1, key2, value2, ...] @@ -287,8 +289,11 @@ private OsRealmConfig(final RealmConfiguration config, urlPrefix, customAuthorizationHeaderName, customHeaders, - clientResyncMode); + clientResyncMode, + partitionValue, + syncService); try { + resolvedSyncRealmUrl = syncRealmAuthUrl + urlPrefix.substring(1); // FIXME resolvedRealmURI = new URI(resolvedSyncRealmUrl); } catch (URISyntaxException e) { RealmLog.error(e, "Cannot create a URI from the Realm URL address"); @@ -300,8 +305,8 @@ private OsRealmConfig(final RealmConfiguration config, if (resolvedRealmURI != null && proxySelector != null) { URI websocketUrl = null; try { - // replace scheme in URI so that a proxy selector won't be confused by 'realm://' - websocketUrl = new URI(resolvedSyncRealmUrl.replaceFirst("realm", "http")); + // replace scheme in URI so that a proxy selector won't be confused by 'ws://' or 'wss://' + websocketUrl = new URI(resolvedSyncRealmUrl.replaceFirst("ws", "http")); } catch (URISyntaxException e) { // we shouldn't ever get here if parsing the resolved url above worked RealmLog.error(e, "Cannot create a URI from the Realm URL address"); @@ -383,7 +388,8 @@ private static native String nativeCreateAndSetSyncConfig(long nativePtr, String String userId, String refreshToken, String accessToken, byte sessionStopPolicy, String urlPrefix, String customAuthorizationHeaderName, - String[] customHeaders, byte clientResetMode); + String[] customHeaders, byte clientResetMode, + String partionKeyValue, Object syncService); private static native void nativeSetSyncConfigSslSettings(long nativePtr, boolean validateSsl, String trustCertificatePath); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 150b24a311..691a0b2ae7 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -193,4 +193,11 @@ public static void checkLooperThread(String errorMessage) { AndroidCapabilities capabilities = new AndroidCapabilities(); capabilities.checkCanDeliverNotification(errorMessage); } + + public static void checkNotOnMainThread(String errorMessage) { + if (new AndroidCapabilities().isMainThread()) { + throw new IllegalStateException(errorMessage); + } + } + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuthProvider.java b/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java similarity index 94% rename from realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuthProvider.java rename to realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java index 2e37808eaf..9f0b3b1893 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuthProvider.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java @@ -26,10 +26,12 @@ import io.realm.internal.Util; import io.realm.internal.objectstore.OsJavaNetworkTransport; +import static io.realm.RealmApp.NETWORK_POOL_EXECUTOR; + /** * This class exposes functionality for a user to manage API keys under their control. */ -public class ApiKeyAuthProvider { +public class ApiKeyAuth { private static final int TYPE_CREATE = 1; private static final int TYPE_FETCH_SINGLE = 2; @@ -45,7 +47,7 @@ public class ApiKeyAuthProvider { * * @param user user that is controlling the API keys. */ - public ApiKeyAuthProvider(RealmUser user) { + public ApiKeyAuth(RealmUser user) { this.user = user; } @@ -96,7 +98,7 @@ protected RealmUserApiKey mapSuccess(Object result) { */ public RealmAsyncTask createApiKeyAsync(String name, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous creation of api keys are only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public RealmUserApiKey run() throws ObjectServerError { return createApiKey(name); @@ -133,7 +135,7 @@ protected RealmUserApiKey mapSuccess(Object result) { */ public RealmAsyncTask fetchApiKeyAsync(ObjectId id, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public RealmUserApiKey run() throws ObjectServerError { return fetchApiKey(id); @@ -173,7 +175,7 @@ protected List mapSuccess(Object result) { */ public RealmAsyncTask fetchAllApiKeys(RealmApp.Callback> callback) { Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); - return new RealmApp.Request>(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request>(NETWORK_POOL_EXECUTOR, callback) { @Override public List run() throws ObjectServerError { return fetchAllApiKeys(); @@ -204,7 +206,7 @@ public void deleteApiKey(ObjectId id) throws ObjectServerError { */ public RealmAsyncTask deleteApiKeyAsync(ObjectId id, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous deleting an api key is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { deleteApiKey(id); @@ -236,7 +238,7 @@ public void disableApiKey(ObjectId id) throws ObjectServerError { */ public RealmAsyncTask disableApiKeyAsync(ObjectId id, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous disabling an api key is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { disableApiKey(id); @@ -268,7 +270,7 @@ public void enableApiKey(ObjectId id) throws ObjectServerError { */ public RealmAsyncTask enableApiKeyAsync(ObjectId id, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous enabling an api key is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { enableApiKey(id); @@ -289,7 +291,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ApiKeyAuthProvider that = (ApiKeyAuthProvider) o; + ApiKeyAuth that = (ApiKeyAuth) o; return user.equals(that.user); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java b/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java index 3602509d82..ccdef93280 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java @@ -23,14 +23,14 @@ public interface AuthenticationListener { /** * A user was logged into the Object Server * - * @param user {@link SyncUser} that is now logged in. + * @param user {@link RealmUser} that is now logged in. */ - void loggedIn(SyncUser user); + void loggedIn(RealmUser user); /** * A user was successfully logged out from the Object Server. * - * @param user {@link SyncUser} that was successfully logged out. + * @param user {@link RealmUser} that was successfully logged out. */ - void loggedOut(SyncUser user); + void loggedOut(RealmUser user); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java b/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java index 75c28c796e..83d3f5d9ef 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java @@ -27,7 +27,7 @@ *

            * IMPORTANT: Just having the device offline will not trigger a Client Resync. */ -public enum ClientResyncMode { +enum ClientResyncMode { /** * Realm will compare the local Realm with the Realm on the server and automatically transfer diff --git a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuthProvider.java b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java similarity index 95% rename from realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuthProvider.java rename to realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java index 6bfa8d7360..8fb25f1322 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuthProvider.java +++ b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java @@ -22,11 +22,13 @@ import io.realm.internal.Util; import io.realm.internal.objectstore.OsJavaNetworkTransport; +import static io.realm.RealmApp.NETWORK_POOL_EXECUTOR; + /** * Class encapsulating functionality provided when {@link RealmUser}'s are logged in through the * {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} provider. */ -public class EmailPasswordAuthProvider { +public class EmailPasswordAuth { private static final int TYPE_REGISTER_USER = 1; private static final int TYPE_CONFIRM_USER = 2; @@ -41,7 +43,7 @@ public class EmailPasswordAuthProvider { * Creates an authentication provider exposing functionality to using an email and password * for login into a Realm Application. */ - public EmailPasswordAuthProvider(RealmApp app) { + public EmailPasswordAuth(RealmApp app) { this.app = app; } @@ -79,7 +81,7 @@ public void registerUser(String email, String password) throws ObjectServerError */ public RealmAsyncTask registerUserAsync(String email, String password, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous registration of a user is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { registerUser(email, password); @@ -117,7 +119,7 @@ public void confirmUser(String token, String tokenId) throws ObjectServerError { */ public RealmAsyncTask confirmUserAsync(String token, String tokenId, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous confirmation of a user is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { confirmUser(token, tokenId); @@ -152,7 +154,7 @@ public void resendConfirmationEmail(String email) throws ObjectServerError { */ public RealmAsyncTask resendConfirmationEmailAsync(String email, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous resending the confirmation email is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { resendConfirmationEmail(email); @@ -187,7 +189,7 @@ public void sendResetPasswordEmail(String email) throws ObjectServerError { */ public RealmAsyncTask sendResetPasswordEmailAsync(String email, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous sending the reset password email is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { sendResetPasswordEmail(email); @@ -235,7 +237,7 @@ public void callResetPasswordFunction(String email, String newPassword, Object.. */ public RealmAsyncTask callResetPasswordFunctionAsync(String email, String newPassword, Object[] args, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous calling the password reset function is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { callResetPasswordFunction(email, newPassword, args); @@ -278,7 +280,7 @@ public void resetPassword(String token, String tokenId, String newPassword) thro */ public RealmAsyncTask resetPasswordAsync(String token, String tokenId, String newPassword, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous reset of a password is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { resetPassword(token, tokenId, newPassword); diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index d3df2b51ea..fdb4a0f19c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -315,6 +315,7 @@ public static ErrorCode fromNativeError(String type, int errorCode) { } public static class Type { + // FIXME Figure out where errors like 'realm::util::websocket::Error:7' are coming from public static final String AUTH = "auth"; // Errors from the Realm Object Server public static final String CONNECTION = "realm.basic_system"; // Connection/System errors from the native Sync Client public static final String DEPRECATED = "deprecated"; // Deprecated errors diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java b/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java deleted file mode 100644 index 07d6fc980d..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServer.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.os.Build; - -import java.io.File; -import java.io.IOException; -import java.util.Locale; - -import io.realm.internal.Keep; -import io.realm.internal.Util; -import io.realm.log.RealmLog; - -/** - * Internal initializer class for the Object Server. - * Use to keep the `SyncManager` free from Android dependencies - */ -@SuppressWarnings("unused") -@Keep -class ObjectServer { - - public static void init(Context context, String appDefinedUserAgent) { - // Setup AppID - String appId = "unknown"; - try { - PackageInfo pi = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - appId = pi.packageName; - } catch (Exception ignore) { - } - - // Setup Realm part of User-Agent string - String userAgentBindingInfo = "Unknown"; // Fallback in case of anything going wrong - try { - StringBuilder sb = new StringBuilder(); - sb.append("RealmJava/"); - sb.append(BuildConfig.VERSION_NAME); - sb.append(" ("); - sb.append(Util.isEmptyString(Build.DEVICE) ? "unknown-device" : Build.DEVICE); - sb.append(", "); - sb.append(Util.isEmptyString(Build.MODEL) ? "unknown-model" : Build.MODEL); - sb.append(", v"); - sb.append(Build.VERSION.SDK_INT); - sb.append(")"); - userAgentBindingInfo = sb.toString(); - } catch (Exception e) { - // Failures to construct the user agent should never cause the system itself to crash. - RealmLog.warn("Constructing User-Agent description failed.", e); - } - - // init the "sync_manager.cpp" metadata Realm, this is also needed later, when re try - // to schedule a client reset. in realm-java#master this is already done, when initialising - // the RealmFileUserStore (not available now on releases) - if (SyncManager.Debug.separatedDirForSyncManager) { - try { - // Files.createTempDirectory is not available on JDK 6. - File dir = File.createTempFile("remote_sync_", "_" + android.os.Process.myPid(), - context.getFilesDir()); - if (!dir.delete()) { - throw new IllegalStateException(String.format(Locale.US, - "Temp file '%s' cannot be deleted.", dir.getPath())); - } - if (!dir.mkdir()) { - throw new IllegalStateException(String.format(Locale.US, - "Directory '%s' for SyncManager cannot be created. ", - dir.getPath())); - } - SyncManager.nativeInitializeSyncManager(dir.getPath(), userAgentBindingInfo, appDefinedUserAgent); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } else { - SyncManager.nativeInitializeSyncManager(context.getFilesDir().getPath(), userAgentBindingInfo, appDefinedUserAgent); - } - - SyncManager.init(appId); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index 5c5bde06e6..248b2fac6e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -15,7 +15,13 @@ */ package io.realm; -import java.lang.reflect.Constructor; +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; + +import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -28,6 +34,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.Keep; +import io.realm.internal.KeepMember; import io.realm.internal.RealmNotifier; import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; @@ -36,7 +43,6 @@ import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.internal.network.OkHttpNetworkTransport; import io.realm.internal.objectstore.OsJavaNetworkTransport; -import io.realm.internal.objectstore.OsSyncUser; import io.realm.log.RealmLog; /** @@ -49,30 +55,10 @@ public class RealmApp { // we might want to lift in the future. So any implementation details so ideally be made // with that in mind, i.e. keep static state to minimum. - // Default session error handler that just output errors to LogCat - private static final SyncSession.ErrorHandler SESSION_NO_OP_ERROR_HANDLER = new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { - RealmLog.error("Client Reset required for: " + session.getConfiguration().getServerUrl()); - return; - } - - String errorMsg = String.format(Locale.US, "Session Error[%s]: %s", - session.getConfiguration().getServerUrl(), - error.toString()); - switch (error.getErrorCode().getCategory()) { - case FATAL: - RealmLog.error(errorMsg); - break; - case RECOVERABLE: - RealmLog.info(errorMsg); - break; - default: - throw new IllegalArgumentException("Unsupported error category: " + error.getErrorCode().getCategory()); - } - } - }; + // Currently we only allow one instance of RealmApp (due to restrictions in ObjectStore that + // only allows one underlying SyncClient). + // FIXME: Lift this restriction so it is possible to create multiple app instances. + public volatile static boolean CREATED = false; /** * Thread pool used when doing network requests against MongoDB Realm. @@ -84,10 +70,12 @@ public void onError(SyncSession session, ObjectServerError error) { public static ThreadPoolExecutor NETWORK_POOL_EXECUTOR = RealmThreadPoolExecutor.newDefaultExecutor(); private final RealmAppConfiguration config; - private OsJavaNetworkTransport networkTransport; - final long nativePtr; - private final EmailPasswordAuthProvider emailAuthProvider = new EmailPasswordAuthProvider(this); + OsJavaNetworkTransport networkTransport; + final RealmSync syncManager; + public final long nativePtr; //FIXME Find a way to make this package protected + private final EmailPasswordAuth emailAuthProvider = new EmailPasswordAuth(this); private CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList<>(); + private Handler mainHandler = new Handler(Looper.getMainLooper()); public RealmApp(String appId) { this(new RealmAppConfiguration.Builder(appId).build()); @@ -100,12 +88,110 @@ public RealmApp(String appId) { public RealmApp(RealmAppConfiguration config) { this.config = config; this.networkTransport = new OkHttpNetworkTransport(); - this.nativePtr = nativeCreate( + networkTransport.setAuthorizationHeaderName(config.getAuthorizationHeaderName()); + for (Map.Entry entry : config.getCustomRequestHeaders().entrySet()) { + networkTransport.addCustomRequestHeader(entry.getKey(), entry.getValue()); + } + this.syncManager = new RealmSync(this); + this.nativePtr = init(config); + + // FIXME: Right now we only support one RealmApp. This class will throw a + // exception if you try to create it twice. This is a really hacky way to do this + // Figure out a better API that is always forward compatible + synchronized (RealmSync.class) { + if (CREATED) { + throw new IllegalStateException("Only one RealmApp is currently supported. " + + "This restriction will be lifted soon. Instead, store the RealmApp" + + "instance in a shared global variable."); + } + CREATED = true; + } + } + + private long init(RealmAppConfiguration config) { + String userAgentBindingInfo = getBindingInfo(); + String appDefinedUserAgent = getAppInfo(config); + String syncDir = getSyncBaseDirectory(); + return nativeCreate( config.getAppId(), - config.getBaseUrl(), + config.getBaseUrl().toString(), config.getAppName(), config.getAppVersion(), - config.getRequestTimeoutMs()); + config.getRequestTimeoutMs(), + syncDir, + userAgentBindingInfo, + appDefinedUserAgent); + } + + private String getSyncBaseDirectory() { + if (BaseRealm.applicationContext == null) { + throw new IllegalStateException("Call Realm.init() first."); + } + Context context = BaseRealm.applicationContext; + String syncDir; + if (RealmSync.Debug.separatedDirForSyncManager) { + try { + // Files.createTempDirectory is not available on JDK 6. + File dir = File.createTempFile("remote_sync_", "_" + android.os.Process.myPid(), context.getFilesDir()); + if (!dir.delete()) { + throw new IllegalStateException(String.format(Locale.US, + "Temp file '%s' cannot be deleted.", dir.getPath())); + } + if (!dir.mkdir()) { + throw new IllegalStateException(String.format(Locale.US, + "Directory '%s' for SyncManager cannot be created. ", + dir.getPath())); + } + syncDir = dir.getPath(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } else { + syncDir = context.getFilesDir().getPath(); + } + return syncDir; + } + + private String getAppInfo(RealmAppConfiguration config) { + // Create app UserAgent string + String appDefinedUserAgent = "Unknown"; + try { + String appName = config.getAppName(); + String appVersion = config.getAppVersion(); + if (!Util.isEmptyString(appName) || !Util.isEmptyString(appVersion)) { + StringBuilder sb = new StringBuilder(); + sb.append(Util.isEmptyString(appName) ? "Undefined" : appName); + sb.append('/'); + sb.append(Util.isEmptyString(appName) ? "Undefined" : appVersion); + appDefinedUserAgent = sb.toString(); + } + } catch (Exception e) { + // Failures to construct the user agent should never cause the system itself to crash. + RealmLog.warn("Constructing Binding User-Agent description failed.", e); + } + return appDefinedUserAgent; + } + + private String getBindingInfo() { + // Setup Realm part of User-Agent string + String userAgentBindingInfo = "Unknown"; // Fallback in case of anything going wrong + try { + StringBuilder sb = new StringBuilder(); + sb.append("RealmJava/"); + sb.append(BuildConfig.VERSION_NAME); + sb.append(" ("); + sb.append(Util.isEmptyString(Build.DEVICE) ? "unknown-device" : Build.DEVICE); + sb.append(", "); + sb.append(Util.isEmptyString(Build.MODEL) ? "unknown-model" : Build.MODEL); + sb.append(", v"); + sb.append(Build.VERSION.SDK_INT); + sb.append(")"); + userAgentBindingInfo = sb.toString(); + } catch (Exception e) { + // Failures to construct the user agent should never cause the system itself to crash. + RealmLog.warn("Constructing User-Agent description failed.", e); + } + return userAgentBindingInfo; } /** @@ -180,7 +266,31 @@ protected RealmUser mapSuccess(Object result) { return new RealmUser(nativePtr, RealmApp.this); } }); - return handleResult(success, error); + RealmUser user = handleResult(success, error); + notifyUserLoggedIn(user); + return user; + } + + private void notifyUserLoggedIn(RealmUser user) { + mainHandler.post(new Runnable() { + @Override + public void run() { + for (AuthenticationListener listener : authListeners) { + listener.loggedIn(user); + } + } + }); + } + + void notifyUserLoggedOut(RealmUser user) { + mainHandler.post(new Runnable() { + @Override + public void run() { + for (AuthenticationListener listener : authListeners) { + listener.loggedOut(user); + } + } + }); } /** @@ -216,21 +326,15 @@ public RealmUser run() throws ObjectServerError { * * @return wrapper for interacting with the {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} identity provider. */ - public EmailPasswordAuthProvider getEmailPasswordAuthProvider() { + public EmailPasswordAuth getEmailPasswordAuth() { return emailAuthProvider; } - public SyncSession getSyncSession(SyncConfiguration config) { - return null; - } - - public void refreshConnections() { - - } - /** * Sets a global authentication listener that will be notified about User events like * login and logout. + *

            + * Callbacks to authentication listeners will happen on the UI thread. * * @param listener listener to register. * @throws IllegalArgumentException if {@code listener} is {@code null}. @@ -256,7 +360,22 @@ public void removeAuthenticationListener(AuthenticationListener listener) { authListeners.remove(listener); } - // Private API's for now. + /** + * FIXME: Figure out naming of this method and class. + * @return + */ + public RealmSync getSync() { + return syncManager; + } + + /** + * Returns the configuration object for this app. + * + * @return the configuration for this app. + */ + public RealmAppConfiguration getConfiguration() { + return config; + } /** * Exposed for testing. @@ -268,6 +387,7 @@ void setNetworkTransport(OsJavaNetworkTransport transport) { networkTransport = transport; } + @KeepMember // Called from JNI OsJavaNetworkTransport getNetworkTransport() { return networkTransport; } @@ -513,7 +633,14 @@ public interface Callback { void onResult(Result result); } - private native long nativeCreate(String appId, String baseUrl, String appName, String appVersion, long requestTimeoutMs); + private native long nativeCreate(String appId, + String baseUrl, + String appName, + String appVersion, + long requestTimeoutMs, + String syncDirPath, + String bindingUserInfo, + String appUserInfo); private static native void nativeLogin(long nativeAppPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); @Nullable private static native Long nativeCurrentUser(long nativePtr); diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java index 70a3ad1e56..a5f9341c76 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java @@ -17,12 +17,21 @@ import android.content.Context; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; +import io.realm.internal.Util; import io.realm.log.LogLevel; +import io.realm.log.RealmLog; /** * FIXME @@ -32,32 +41,46 @@ public class RealmAppConfiguration { private final String appId; private final String appName; private final String appVersion; - private final String baseUrl; - private final Context context; + private final URL baseUrl; private final SyncSession.ErrorHandler defaultErrorHandler; @Nullable private final byte[] encryptionKey; private final long logLevel; private final long requestTimeoutMs; + private final String authorizationHeaderName; + private final Map customHeaders; + private final File syncRootDir; // Root directory for storing Sync related files private RealmAppConfiguration(String appId, String appName, String appVersion, String baseUrl, - Context context, SyncSession.ErrorHandler defaultErrorHandler, @Nullable byte[] encryptionKey, long logLevel, - long requestTimeoutMs) { + long requestTimeoutMs, + String authorizationHeaderName, + Map customHeaders, + File syncRootdir) { this.appId = appId; this.appName = appName; this.appVersion = appVersion; - this.baseUrl = baseUrl; - this.context = context; + this.baseUrl = createUrl(baseUrl); this.defaultErrorHandler = defaultErrorHandler; this.encryptionKey = (encryptionKey == null) ? null : Arrays.copyOf(encryptionKey, encryptionKey.length); this.logLevel = logLevel; this.requestTimeoutMs = requestTimeoutMs; + this.authorizationHeaderName = (!Util.isEmptyString(authorizationHeaderName)) ? authorizationHeaderName : "Authorization"; + this.customHeaders = Collections.unmodifiableMap(customHeaders); + this.syncRootDir = syncRootdir; + } + + private URL createUrl(String baseUrl) { + try { + return new URL(baseUrl); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(baseUrl); + } } /** @@ -88,7 +111,7 @@ public String getAppVersion() { * FIXME * @return */ - public String getBaseUrl() { + public URL getBaseUrl() { return baseUrl; } @@ -96,40 +119,60 @@ public String getBaseUrl() { * FIXME * @return */ - public Context getContext() { - return context; + public byte[] getEncryptionKey() { + return encryptionKey == null ? null : Arrays.copyOf(encryptionKey, encryptionKey.length); } /** * FIXME * @return */ - public SyncSession.ErrorHandler getDefaultErrorHandler() { - return defaultErrorHandler; + public long getLogLevel() { + return logLevel; } /** * FIXME * @return */ - public byte[] getEncryptionKey() { - return encryptionKey == null ? null : Arrays.copyOf(encryptionKey, encryptionKey.length); + public long getRequestTimeoutMs() { + return requestTimeoutMs; } + /** * FIXME + * * @return */ - public long getLogLevel() { - return logLevel; + public String getAuthorizationHeaderName() { + return authorizationHeaderName; } /** * FIXME + * * @return */ - public long getRequestTimeoutMs() { - return requestTimeoutMs; + public Map getCustomRequestHeaders() { + return customHeaders; + } + + /** + * FIXME + * + * @return + */ + public SyncSession.ErrorHandler getDefaultErrorHandler() { + return defaultErrorHandler; + } + + /** + * Returns the root folder containing all files and Realms used when synchronizing data + * between the device and MongoDB Realm. + */ + public File getSyncRootDirectory() { + return syncRootDir; } /** @@ -139,12 +182,36 @@ public static class Builder { private String appId; private String appName; private String appVersion; - private String baseUrl; - private Context context; - private SyncSession.ErrorHandler defaultErrorHandler; + private String baseUrl = "https://stitch.mongodb.com"; // FIXME Find the correct base url for release + private SyncSession.ErrorHandler defaultErrorHandler = new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { + RealmLog.error("Client Reset required for: " + session.getConfiguration().getServerUrl()); + return; + } + + String errorMsg = String.format(Locale.US, "Session Error[%s]: %s", + session.getConfiguration().getServerUrl(), + error.toString()); + switch (error.getErrorCode().getCategory()) { + case FATAL: + RealmLog.error(errorMsg); + break; + case RECOVERABLE: + RealmLog.info(errorMsg); + break; + default: + throw new IllegalArgumentException("Unsupported error category: " + error.getErrorCode().getCategory()); + } + } + }; private byte[] encryptionKey; private long logLevel = LogLevel.WARN; // FIXME: Consider what this should be set at private long requestTimeoutMs = 60000; + private String autorizationHeaderName; + private Map customHeaders = new HashMap<>(); + private File syncRootDir; /** * FIXME @@ -152,9 +219,17 @@ public static class Builder { * @param appId */ public Builder(String appId) { - // FIXME: Null checks - this.context = Realm.applicationContext; + Util.checkEmpty(appId, "appId"); this.appId = appId; + Context context = BaseRealm.applicationContext; + if (context == null) { + throw new IllegalStateException("Call `Realm.init(Context)` before calling this method."); + } + File rootDir = new File(context.getFilesDir(), "mongodb-realm"); + if (!rootDir.exists() && !rootDir.mkdir()) { + throw new IllegalStateException("Could not create Sync root dir: " + rootDir.getAbsolutePath()); + } + syncRootDir = rootDir; } /** @@ -219,25 +294,97 @@ public Builder appVersion(String appVersion) { /** * FIXME * - * @param errorHandler + * @param time + * @param unit * @return */ - public Builder defaultSessionErrorHandler(@Nullable SyncSession.ErrorHandler errorHandler) { - // FIXME checks - this.defaultErrorHandler = errorHandler; + public Builder requestTimeout(long time, TimeUnit unit) { + if (time < 1) { + throw new IllegalStateException("A timeout above 0 is required: " + time); + } + Util.checkNull(unit, "unit"); + this.requestTimeoutMs = TimeUnit.MICROSECONDS.convert(time, unit); return this; } /** - * FIXME + * Sets the name of the HTTP header used to send authorization data in when making requests to + * MongoDB Realm. The MongoDB server or firewall must have been configured to expect a + * custom authorization header. + *

            + * The default authorization header is named "Authorization". * - * @param time - * @param unit + * @param headerName name of the header. + * @throws IllegalArgumentException if a null or empty header is provided. + * @see Adding a custom proxy + */ + public Builder authorizationHeaderName(String headerName) { + Util.checkEmpty(headerName, "headerName"); + this.autorizationHeaderName = headerName; + return this; + } + + /** + * Adds an extra HTTP header to append to every request to a Realm Object Server. + * + * @param headerName the name of the header. + * @param headerValue the value of header. + * @throws IllegalArgumentException if a non-empty {@code headerName} is provided or a null {@code headerValue}. + */ + public Builder addCustomRequestHeader(String headerName, String headerValue) { + Util.checkEmpty(headerName, "headerName"); + Util.checkNull(headerValue, "headerValue"); + customHeaders.put(headerName, headerValue); + return this; + } + + /** + * Adds extra HTTP headers to append to every request to a Realm Object Server. + * + * @param headers map of (headerName, headerValue) pairs. + * @throws IllegalArgumentException If any of the headers provided are illegal. + */ + public Builder addCustomRequestHeaders(@Nullable Map headers) { + if (headers != null) { + customHeaders.putAll(headers); + } + return this; + } + + /** + * + * @param errorHandler * @return */ - public Builder requestTimeout(long time, TimeUnit unit) { - // FIXME checks - this.requestTimeoutMs = TimeUnit.MICROSECONDS.convert(time, unit); + public Builder defaultSyncErrorHandler(SyncSession.ErrorHandler errorHandler) { + Util.checkNull(errorHandler, "errorHandler"); + defaultErrorHandler = errorHandler; + return this; + } + + /** + * Configures the root folder containing all files and Realms used when synchronizing data + * between the device and MongoDB Realm. + *

            + * The default root dir is {@code Context.getFilesDir()/mongodb-realm}. + *

            + * @param rootDir where to store sync related files. + */ + public Builder syncRootDirectory(File rootDir) { + Util.checkNull(rootDir, "rootDir"); + if (rootDir.isFile()) { + throw new IllegalArgumentException("'rootDir' is a file, not a directory: " + + rootDir.getAbsolutePath() + "."); + } + if (!rootDir.exists() && !rootDir.mkdirs()) { + throw new IllegalArgumentException("Could not create the specified directory: " + + rootDir.getAbsolutePath() + "."); + } + if (!rootDir.canWrite()) { + throw new IllegalArgumentException("Realm directory is not writable: " + + rootDir.getAbsolutePath() + "."); + } + syncRootDir = rootDir; return this; } @@ -246,11 +393,13 @@ public RealmAppConfiguration build() { appName, appVersion, baseUrl, - context, defaultErrorHandler, encryptionKey, logLevel, - requestTimeoutMs); + requestTimeoutMs, + autorizationHeaderName, + customHeaders, + syncRootDir); } } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java index 8d5c310774..5bfa854fba 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java @@ -25,7 +25,7 @@ * by default. All other providers must be enabled on MongoDB Realm to work. *

            * Note that users wanting to login using Email/Password must register first using - * {@link io.realm.EmailPasswordAuthProvider#registerUser(String, String)}. + * {@link EmailPasswordAuth#registerUser(String, String)}. *

            * Credentials are used the following way: *
            diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmSync.java b/realm/realm-library/src/objectServer/java/io/realm/RealmSync.java
            new file mode 100644
            index 0000000000..eecb38575a
            --- /dev/null
            +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmSync.java
            @@ -0,0 +1,432 @@
            +/*
            + * Copyright 2016 Realm Inc.
            + *
            + * 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.
            + */
            +
            +package io.realm;
            +
            +import android.content.Context;
            +import android.os.Build;
            +
            +import java.io.ByteArrayInputStream;
            +import java.io.File;
            +import java.io.IOException;
            +import java.io.InputStream;
            +import java.net.URI;
            +import java.security.GeneralSecurityException;
            +import java.security.KeyStore;
            +import java.security.cert.CertificateException;
            +import java.security.cert.CertificateFactory;
            +import java.security.cert.X509Certificate;
            +import java.util.ArrayList;
            +import java.util.Arrays;
            +import java.util.HashMap;
            +import java.util.List;
            +import java.util.Locale;
            +import java.util.Map;
            +import java.util.concurrent.ConcurrentHashMap;
            +import java.util.concurrent.CopyOnWriteArrayList;
            +
            +import javax.annotation.Nullable;
            +import javax.net.ssl.TrustManager;
            +import javax.net.ssl.TrustManagerFactory;
            +import javax.net.ssl.X509TrustManager;
            +
            +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
            +import io.realm.internal.Keep;
            +import io.realm.internal.OsRealmConfig;
            +import io.realm.internal.Util;
            +import io.realm.internal.network.NetworkStateReceiver;
            +import io.realm.log.RealmLog;
            +import okhttp3.internal.tls.OkHostnameVerifier;
            +
            +/**
            + * Class wrapping Sync responsibilities for a {@link io.realm.RealmApp}.
            + *
            + * FIXME: Better description that makes sense for end users.
            + */
            +@Keep
            +@SuppressFBWarnings("MS_CANNOT_BE_FINAL")
            +public class RealmSync {
            +
            +    private final RealmApp app;
            +    // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path'
            +    private Map sessions = new ConcurrentHashMap<>();
            +
            +    RealmSync(RealmApp app) {
            +        this.app = app;
            +    }
            +
            +    /**
            +     * Debugging related options.
            +     */
            +    @SuppressFBWarnings("MS_SHOULD_BE_FINAL")
            +    public static class Debug {
            +        /**
            +         * Set this to true to bypass checking if the device is offline before making HTTP requests.
            +         */
            +        public static boolean skipOnlineChecking = false;
            +
            +        /**
            +         * Set this to true to init a SyncManager with a directory named by the process ID. This is useful for
            +         * integration tests which are emulating multiple sync client by using multiple processes.
            +         */
            +        public static boolean separatedDirForSyncManager = false;
            +    }
            +
            +    private static NetworkStateReceiver.ConnectionListener networkListener = new NetworkStateReceiver.ConnectionListener() {
            +        @Override
            +        public void onChange(boolean connectionAvailable) {
            +            if (connectionAvailable) {
            +                RealmLog.debug("NetworkListener: Connection available");
            +                // notify all sessions
            +                notifyNetworkIsBack();
            +            } else {
            +                RealmLog.debug("NetworkListener: Connection lost");
            +            }
            +        }
            +    };
            +
            +    /**
            +     * Gets a cached {@link SyncSession} for the given {@link SyncConfiguration} or throw if no one exists yet.
            +     *
            +     * A session should exist after you open a Realm with a {@link SyncConfiguration}.
            +     *
            +     * @param syncConfiguration configuration object for the synchronized Realm.
            +     * @return the {@link SyncSession} for the specified Realm.
            +     * @throws IllegalArgumentException if syncConfiguration is {@code null}.
            +     * @throws IllegalStateException if the session could not be found using the provided {@code SyncConfiguration}.
            +     */
            +    public synchronized SyncSession getSession(SyncConfiguration syncConfiguration) throws IllegalStateException {
            +        //noinspection ConstantConditions
            +        if (syncConfiguration == null) {
            +            throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required.");
            +        }
            +
            +        SyncSession session = sessions.get(syncConfiguration.getPath());
            +        if (session == null) {
            +            throw new IllegalStateException("No SyncSession found using the path : " + syncConfiguration.getPath()
            +                    + "\nplease ensure to call this method after you've open the Realm");
            +        }
            +
            +        return session;
            +    }
            +
            +    /**
            +     * Gets any cached {@link SyncSession} for the given {@link SyncConfiguration} or create a new one if
            +     * no one exists.
            +     *
            +     * Note: This is mainly for internal usage, consider using {@link #getSession(SyncConfiguration)} instead.
            +     *
            +     * @param syncConfiguration configuration object for the synchronized Realm.
            +     * @return the {@link SyncSession} for the specified Realm.
            +     * @throws IllegalArgumentException if syncConfiguration is {@code null}.
            +     */
            +    public synchronized SyncSession getOrCreateSession(SyncConfiguration syncConfiguration) {
            +        // This will not create a new native (Object Store) session, this will only associate a Realm's path
            +        // with a SyncSession. Object Store's SyncManager is responsible of the life cycle (including creation)
            +        // of the native session. The provided Java wrap, helps interact with the native session, when reporting error
            +        // or requesting an access_token for example.
            +
            +        //noinspection ConstantConditions
            +        if (syncConfiguration == null) {
            +            throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required.");
            +        }
            +
            +        SyncSession session = sessions.get(syncConfiguration.getPath());
            +        if (session == null) {
            +            RealmLog.debug("Creating session for: %s", syncConfiguration.getPath());
            +            session = new SyncSession(syncConfiguration);
            +            sessions.put(syncConfiguration.getPath(), session);
            +            if (sessions.size() == 1) {
            +                RealmLog.debug("First session created. Adding network listener.");
            +                NetworkStateReceiver.addListener(networkListener);
            +            }
            +            // The underlying session will be created as part of opening the Realm, but this approach
            +            // does not work when using `Realm.getInstanceAsync()` in combination with AsyncOpen.
            +            //
            +            // So instead we manually create the underlying native session.
            +            OsRealmConfig config = new OsRealmConfig.Builder(syncConfiguration).build();
            +            nativeCreateSession(config.getNativePtr());
            +        }
            +
            +        return session;
            +    }
            +
            +    List getAllSyncSessions(RealmUser user) {
            +        //noinspection ConstantConditions
            +        if (user == null) {
            +            throw new IllegalArgumentException("A non-empty 'syncUser' is required.");
            +        }
            +        ArrayList allSessions = new ArrayList();
            +        for (SyncSession syncSession : sessions.values()) {
            +            if (syncSession.getUser().equals(user)) {
            +                allSessions.add(syncSession);
            +            }
            +        }
            +        return allSessions;
            +    }
            +
            +
            +
            +    /**
            +     * Remove the wrapped Java session.
            +     * @param syncConfiguration configuration object for the synchronized Realm.
            +     */
            +    @SuppressWarnings("unused")
            +    private synchronized void removeSession(SyncConfiguration syncConfiguration) {
            +        //noinspection ConstantConditions
            +        if (syncConfiguration == null) {
            +            throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required.");
            +        }
            +        RealmLog.debug("Removing session for: %s", syncConfiguration.getPath());
            +        SyncSession syncSession = sessions.remove(syncConfiguration.getPath());
            +        if (syncSession != null) {
            +            syncSession.close();
            +        }
            +        if (sessions.isEmpty()) {
            +            RealmLog.debug("Last session dropped. Remove network listener.");
            +            NetworkStateReceiver.removeListener(networkListener);
            +        }
            +    }
            +
            +    /**
            +     * All errors from native Sync is reported to this method. From the path we can determine which
            +     * session to contact. If {@code path == null} all sessions are effected.
            +     */
            +    @SuppressWarnings("unused")
            +    private synchronized void notifyErrorHandler(String nativeErrorCategory, int nativeErrorCode, String errorMessage, @Nullable String path) {
            +        if (Util.isEmptyString(path)) {
            +            // notify all sessions
            +            for (SyncSession syncSession : sessions.values()) {
            +                try {
            +                    syncSession.notifySessionError(nativeErrorCategory, nativeErrorCode, errorMessage);
            +                } catch (Exception exception) {
            +                    RealmLog.error(exception);
            +                }
            +            }
            +        } else {
            +            SyncSession syncSession = sessions.get(path);
            +            if (syncSession != null) {
            +                try {
            +                    syncSession.notifySessionError(nativeErrorCategory, nativeErrorCode, errorMessage);
            +                } catch (Exception exception) {
            +                    RealmLog.error(exception);
            +                }
            +            } else {
            +                RealmLog.warn("Cannot find the SyncSession corresponding to the path: " + path);
            +            }
            +        }
            +    }
            +
            +    private static synchronized void notifyNetworkIsBack() {
            +        try {
            +            nativeReconnect();
            +        } catch (Exception exception) {
            +            RealmLog.error(exception);
            +        }
            +    }
            +
            +    /**
            +     * All progress listener events from native Sync are reported to this method.
            +     * It costs 2 HashMap lookups for each listener triggered (one to find the session, one to
            +     * find the progress listener), but it means we don't have to cache anything on the C++ side which
            +     * can leak since we don't have control over the session lifecycle.
            +     */
            +    @SuppressWarnings("unused")
            +    private synchronized void notifyProgressListener(String localRealmPath, long listenerId, long transferedBytes, long transferableBytes) {
            +        SyncSession session = sessions.get(localRealmPath);
            +        if (session != null) {
            +            try {
            +                session.notifyProgressListener(listenerId, transferedBytes, transferableBytes);
            +            } catch (Exception exception) {
            +                RealmLog.error(exception);
            +            }
            +        }
            +    }
            +
            +    /**
            +     * Called from native code. This method is not allowed to throw as it would be swallowed
            +     * by the native Sync Client thread. Instead log all exceptions to logcat.
            +     */
            +    @SuppressWarnings("unused")
            +    private synchronized void notifyConnectionListeners(String localRealmPath, long oldState, long newState) {
            +        SyncSession session = sessions.get(localRealmPath);
            +        if (session != null) {
            +            try {
            +                session.notifyConnectionListeners(ConnectionState.fromNativeValue(oldState), ConnectionState.fromNativeValue(newState));
            +            } catch (Exception exception) {
            +                RealmLog.error(exception);
            +            }
            +        }
            +    }
            +
            +    /**
            +     * Realm will automatically detect when a device gets connectivity after being offline and
            +     * resume syncing.
            +     * 

            + * However, as some of these checks are performed using incremental backoff, this will in some + * cases not happen immediately. + *

            + * In those cases it can be beneficial to call this method manually, which will force all + * sessions to attempt to reconnect immediately and reset any timers they are using for + * incremental backoff. + */ + public static void refreshConnections() { + notifyNetworkIsBack(); + } + + // Holds the certificate chain (per hostname). We need to keep the order of each certificate + // according to it's depth in the chain. The depth of the last + // certificate is 0. The depth of the first certificate is chain + // length - 1. + private static HashMap> ROS_CERTIFICATES_CHAIN; + + // The default Android Trust Manager which uses the default KeyStore to + // validate the certificate chain. + private static X509TrustManager TRUST_MANAGER; + + // Help transform a String PEM representation of the certificate, into + // X509Certificate format. + private static CertificateFactory CERTIFICATE_FACTORY; + + // From Sync implementation: + // A recommended way of using the callback function is to return true + // if preverify_ok = 1 and depth > 0, + // always check the host name if depth = 0, + // and use an independent verification step if preverify_ok = 0. + // + // Another possible way of using the callback is to collect all the + // ROS_CERTIFICATES_CHAIN until depth = 0, and present the entire chain for + // independent verification. + // + // In this implementation we use the second method, since it's more suitable for + // the underlying Java API we need to call to validate the certificate chain. + @SuppressWarnings("unused") + synchronized static boolean sslVerifyCallback(String serverAddress, String pemData, int depth) { + try { + if (ROS_CERTIFICATES_CHAIN == null) { + ROS_CERTIFICATES_CHAIN = new HashMap<>(); + TRUST_MANAGER = systemDefaultTrustManager(); + CERTIFICATE_FACTORY = CertificateFactory.getInstance("X.509"); + } + + if (!ROS_CERTIFICATES_CHAIN.containsKey(serverAddress)) { + ROS_CERTIFICATES_CHAIN.put(serverAddress, new ArrayList()); + } + + ROS_CERTIFICATES_CHAIN.get(serverAddress).add(pemData); + + if (depth == 0) { + // transform all PEM ROS_CERTIFICATES_CHAIN into Java X509 + // with respecting the order/depth provided from Sync. + List pemChain = ROS_CERTIFICATES_CHAIN.get(serverAddress); + int n = pemChain.size(); + X509Certificate[] chain = new X509Certificate[n]; + for (String pem : pemChain) { + // The depth of the last certificate is 0. + // The depth of the first certificate is chain length - 1. + chain[--n] = buildCertificateFromPEM(pem); + } + + // verify the entire chain + try { + TRUST_MANAGER.checkClientTrusted(chain, "RSA"); + // verify the hostname + boolean isValid = OkHostnameVerifier.INSTANCE.verify(serverAddress, chain[0]); + if (isValid) { + return true; + } else { + RealmLog.error("Can not verify the hostname for the host: " + serverAddress); + return false; + } + } catch (CertificateException e) { + RealmLog.error(e, "Can not validate SSL chain certificate for the host: " + serverAddress); + return false; + } finally { + // don't keep the certificate chain in memory + ROS_CERTIFICATES_CHAIN.remove(serverAddress); + } + } else { + // return true, since the verification will happen for the entire chain + // when receiving the depth == 0 (host certificate) + return true; + } + } catch (Exception e) { + RealmLog.error(e, "Error during certificate validation for host: " + serverAddress); + return false; + } + } + + // Credit OkHttp https://github.com/square/okhttp/blob/e5c84e1aef9572adb493197c1b6c4e882aca085b/okhttp/src/main/java/okhttp3/OkHttpClient.java#L270 + private static X509TrustManager systemDefaultTrustManager() { + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init((KeyStore) null); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + + Arrays.toString(trustManagers)); + } + return (X509TrustManager) trustManagers[0]; + } catch (GeneralSecurityException e) { + throw new IllegalStateException("No System TLS", e); // The system has no TLS. Just give up. + } + } + + private static X509Certificate buildCertificateFromPEM(String pem) throws IOException, CertificateException { + InputStream stream = null; + try { + stream = new ByteArrayInputStream(pem.getBytes("UTF-8")); + return (X509Certificate) CERTIFICATE_FACTORY.generateCertificate(stream); + } finally { + if (stream != null) { + stream.close(); + } + } + } + + /** + * Resets the SyncManger and clear all existing users. + * This will also terminate all sessions. + * + * Only call this method when testing. + */ + synchronized void reset() { + nativeReset(); + sessions.clear(); + app.networkTransport.resetHeaders(); + } + + /** + * Simulate a Client Reset by triggering the Object Store error handler with Sync Error Code that will be + * converted to a Client Reset (211 - Diverging Histories). + * + * Only call this method when testing. + * + * @param session Session to trigger Client Reset for. + */ + void simulateClientReset(SyncSession session) { + nativeSimulateSyncError(session.getConfiguration().getPath(), + ErrorCode.DIVERGING_HISTORIES.intValue(), + "Simulate Client Reset", + true); + } + + private static native void nativeReset(); + private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); + private static native void nativeReconnect(); + private static native void nativeCreateSession(long nativeConfigPtr); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java index eccd51e681..3263c678c7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -21,6 +21,7 @@ import javax.annotation.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.internal.objectstore.OsSyncUser; import io.realm.internal.util.Pair; @@ -36,7 +37,7 @@ public class RealmUser { OsSyncUser osUser; private final RealmApp app; - private ApiKeyAuthProvider apiKeyAuthProvider = null; + private ApiKeyAuth apiKeyAuthProvider = null; /** * FIXME @@ -225,8 +226,10 @@ public State getState() { } /** + * Returns true if the user is currently logged in. * Returns whether or not this user is still logged into the MongoDB Realm App. * + * @return {@code true} if the user is logged in. {@code false} otherwise. * @return {@code true} if still logged in, {@code false} if not. */ public boolean isLoggedIn() { @@ -244,7 +247,7 @@ public boolean isLoggedIn() { * // Example * RealmApp app = new RealmApp("app-id") * RealmUser user = app.login(RealmCredentials.anonymous()); - * user.linkUser(RealmCredentials.emailPassword("email", "password")); + * user.linkCredentials(RealmCredentials.emailPassword("email", "password")); * } *

            *

            @@ -255,7 +258,7 @@ public boolean isLoggedIn() { * @throws IllegalStateException if no user is currently logged in. * @return the {@link io.realm.RealmUser} the credentials were linked to. */ - public RealmUser linkUser(RealmCredentials credentials) { + public RealmUser linkCredentials(RealmCredentials credentials) { Util.checkNull(credentials, "credentials"); checkLoggedIn(); AtomicReference success = new AtomicReference<>(null); @@ -281,7 +284,7 @@ protected RealmUser mapSuccess(Object result) { * // Example * RealmApp app = new RealmApp("app-id") * RealmUser user = app.login(RealmCredentials.anonymous()); - * user.linkUser(RealmCredentials.emailPassword("email", "password")); + * user.linkCredentials(RealmCredentials.emailPassword("email", "password")); * } * *

            @@ -293,12 +296,12 @@ protected RealmUser mapSuccess(Object result) { * always happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask linkUserAsync(RealmCredentials credentials, RealmApp.Callback callback) { + public RealmAsyncTask linkCredentialsAsync(RealmCredentials credentials, RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous linking identities is only possible from looper threads."); return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { @Override public RealmUser run() throws ObjectServerError { - return linkUser(credentials); + return linkCredentials(credentials); } }.start(); } @@ -312,7 +315,8 @@ public RealmUser run() throws ObjectServerError { * @throws ObjectServerError if called from the UI thread or if the user was logged in, but * could not be logged out. */ - public RealmUser removeUser() throws ObjectServerError { + public RealmUser remove() throws ObjectServerError { + boolean loggedIn = isLoggedIn(); AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); nativeRemoveUser(app.nativePtr, osUser.getNativePtr(), new RealmApp.OsJNIResultCallback(success, error) { @@ -321,7 +325,11 @@ protected RealmUser mapSuccess(Object result) { return RealmUser.this; } }); - return handleResult(success, error); + handleResult(success, error); + if (loggedIn) { + app.notifyUserLoggedOut(this); + } + return this; } /** @@ -329,17 +337,16 @@ protected RealmUser mapSuccess(Object result) { * will be logged out as part of the process. This is only a local change and does not * affect the user state on the server. * - * @param user user to remove. * @param callback callback when removing the user has completed or failed. The callback will always * happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask removeUserAsync(RealmApp.Callback callback) { + public RealmAsyncTask removeAsync(RealmApp.Callback callback) { Util.checkLooperThread("Asynchronous removal of users is only possible from looper threads."); return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { @Override public RealmUser run() throws ObjectServerError { - return removeUser(); + return remove(); } }.start(); } @@ -355,16 +362,20 @@ public RealmUser run() throws ObjectServerError { *

            * Logging out anonymous users will remove them immediately instead of marking them as * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} - * and will still be returned by {@link #allUsers()}. They can be removed completely by calling - * {@link #removeUser()}. + * and will still be returned by {@link RealmApp#allUsers()}. They can be removed completely by calling + * {@link #remove()}. * * @throws ObjectServerError if an error occurred while trying to log the user out of the Realm * App. */ public void logOut() throws ObjectServerError { + boolean loggedIn = isLoggedIn(); AtomicReference error = new AtomicReference<>(null); nativeLogOut(app.nativePtr, osUser.getNativePtr(), new RealmApp.OsJNIVoidResultCallback(error)); handleResult(null, error); + if (loggedIn) { + app.notifyUserLoggedOut(this); + } } /** @@ -378,21 +389,20 @@ public void logOut() throws ObjectServerError { *

            * Logging out anonymous users will remove them immediately instead of marking them as * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} - * and will still be returned by {@link #allUsers()}. They can be removed completely by calling - * {@link #removeUser()}. + * and will still be returned by {@link RealmApp#allUsers()}. They can be removed completely by calling + * {@link #remove()}. * * @param callback callback when logging out has completed or failed. The callback will always * happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ public RealmAsyncTask logOutAsync(RealmApp.Callback callback) { - final RealmUser user = this; Util.checkLooperThread("Asynchronous log out is only possible from looper threads."); - return new RealmApp.Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { + return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { @Override public RealmUser run() throws ObjectServerError { logOut(); - return user; + return RealmUser.this; } }.start(); } @@ -403,10 +413,10 @@ public RealmUser run() throws ObjectServerError { * @return wrapper for managing API keys controlled by the current user. * @throws IllegalStateException if no user is currently logged in. */ - public synchronized ApiKeyAuthProvider getApiKeyAuthProvider() { + public synchronized ApiKeyAuth getApiKeyAuth() { checkLoggedIn(); if (apiKeyAuthProvider == null) { - apiKeyAuthProvider = new ApiKeyAuthProvider(this); + apiKeyAuthProvider = new ApiKeyAuth(this); } return apiKeyAuthProvider; } @@ -432,8 +442,9 @@ public RealmMongoDBService getMongoDBService() { return null; } + @SuppressFBWarnings("NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION") @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -459,5 +470,4 @@ private void checkLoggedIn() { private static native void nativeRemoveUser(long nativeAppPtr, long nativeUserPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); private static native void nativeLinkUser(long nativeAppPtr, long nativeUserPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); private static native void nativeLogOut(long appNativePtr, long userNativePtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index c77781e2c3..ffd66501d3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -18,10 +18,18 @@ import android.content.Context; +import org.bson.BsonInt32; +import org.bson.BsonInt64; +import org.bson.BsonObjectId; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.bson.types.ObjectId; + import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -29,11 +37,12 @@ import java.util.HashSet; import java.util.Locale; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.OsRealmConfig; @@ -44,23 +53,25 @@ import io.realm.rx.RxObservableFactory; /** - * A {@link SyncConfiguration} is used to setup a Realm that can be synchronized between devices using the Realm - * Object Server. + * A {@link SyncConfiguration} is used to setup a Realm Database that can be synchronized between + * devices using MongoDB Realm. *

            - * A valid {@link SyncUser} is required to create a {@link SyncConfiguration}. See {@link SyncCredentials} and - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)} for more information on how to get a user object. + * A valid {@link RealmUser} is required to create a {@link SyncConfiguration}. See + * {@link RealmCredentials} and {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)} for + * more information on how to get a user object. *

            * A minimal {@link SyncConfiguration} can be found below. *

              * {@code
            - * SyncUser user = SyncUser.current();
            - * String url = "realm://myinstance.cloud.realm.io/default";
            - * SyncConfiguration config = new SyncConfiguration.Builder(user, url).build();
            + * RealmApp app = new RealmApp("app-id");
            + * RealmUser user = app.login(RealmCredentials.anonymous());
            + * SyncConfiguration config = SyncConfiguration.defaultConfiguration(user, "partition-value");
            + * Realm realm = Realm.getInstance(config);
              * }
              * 
            *

            - * Synchronized Realms only support additive migrations which can be detected and performed automatically, so - * the following builder options are not accessible compared to a normal Realm: + * Synchronized Realms only support additive migrations which can be detected and performed + * automatically, so the following builder options are not accessible compared to a normal Realm: * *

              *
            • {@code deleteRealmIfMigrationNeeded()}
            • @@ -70,8 +81,8 @@ * Synchronized Realms are created by using {@link Realm#getInstance(RealmConfiguration)} and * {@link Realm#getDefaultInstance()} like ordinary unsynchronized Realms. * - * @see The docs for more - * information about the two types of synchronization. + * @see The docs for + * more information about the two types of synchronization. */ public class SyncConfiguration extends RealmConfiguration { @@ -81,7 +92,7 @@ public class SyncConfiguration extends RealmConfiguration { static final int MAX_FILE_NAME_LENGTH = 255; private static final char[] INVALID_CHARS = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'}; private final URI serverUrl; - private final SyncUser user; + private final RealmUser user; private final SyncSession.ErrorHandler errorHandler; private final boolean deleteRealmOnLogout; private final boolean syncClientValidateSsl; @@ -93,6 +104,7 @@ public class SyncConfiguration extends RealmConfiguration { private final OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy; @Nullable private final String syncUrlPrefix; private final ClientResyncMode clientResyncMode; + private final BsonValue partitionValue; private SyncConfiguration(File directory, String filename, @@ -108,7 +120,7 @@ private SyncConfiguration(File directory, @Nullable Realm.Transaction initialDataTransaction, boolean readOnly, long maxNumberOfActiveVersions, - SyncUser user, + RealmUser user, URI serverUrl, SyncSession.ErrorHandler errorHandler, boolean deleteRealmOnLogout, @@ -120,7 +132,8 @@ private SyncConfiguration(File directory, OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy, CompactOnLaunchCallback compactOnLaunch, @Nullable String syncUrlPrefix, - ClientResyncMode clientResyncMode) { + ClientResyncMode clientResyncMode, + BsonValue partitionValue) { super(directory, filename, canonicalPath, @@ -151,6 +164,7 @@ private SyncConfiguration(File directory, this.sessionStopPolicy = sessionStopPolicy; this.syncUrlPrefix = syncUrlPrefix; this.clientResyncMode = clientResyncMode; + this.partitionValue = partitionValue; } /** @@ -182,6 +196,55 @@ public static RealmConfiguration forRecovery(String canonicalPath, @Nullable byt return forRecovery(canonicalPath, encryptionKey, schemaMediator); } + /** + * FIXME + * + * @param user + * @param partitionValue + * @return + */ + @Beta + public static SyncConfiguration defaultConfig(RealmUser user, String partitionValue) { + return new SyncConfiguration.Builder(user, partitionValue).build(); + } + + /** + * FIXME + * + * @param user + * @param partitionValue + * @return + */ + @Beta + public static SyncConfiguration defaultConfig(RealmUser user, long partitionValue) { + return new SyncConfiguration.Builder(user, partitionValue).build(); + } + + /** + * FIXME + * + * @param user + * @param partitionValue + * @return + */ + @Beta + public static SyncConfiguration defaultConfig(RealmUser user, int partitionValue) { + return new SyncConfiguration.Builder(user, partitionValue).build(); + } + + /** + * FIXME + * + * @param user + * @param partitionValue + * @return + */ + @Beta + public static SyncConfiguration defaultConfig(RealmUser user, ObjectId partitionValue) { + return new SyncConfiguration.Builder(user, partitionValue).build(); + } + + /** * Returns a {@link RealmConfiguration} appropriate to open a read-only, non-synced Realm to recover any pending changes. * This is useful when trying to open a backup/recovery Realm (after a client reset). @@ -200,29 +263,17 @@ static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] enc return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true, Long.MAX_VALUE); } - static URI resolveServerUrl(URI serverUrl, String userIdentifier) { - try { - return new URI(serverUrl.toString().replace("/~/", "/" + userIdentifier + "/")); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Could not replace '/~/' with a valid user ID.", e); - } - } - // Extract the full server path, minus the file name - private static String getServerPath(URI serverUrl) { - String path = serverUrl.getPath(); - int endIndex = path.lastIndexOf("/"); - if (endIndex == -1 ) { - return path; - } else if (endIndex == 0) { - return path.substring(1); - } else { - return path.substring(1, endIndex); // Also strip leading / - } + private static String getServerPath(RealmUser user, URI serverUrl) { + // FIXME Add support for partion key + // Current scheme is ///default.realm or + // Current scheme is ////default.realm + return user.getApp().getConfiguration().getAppId() + "/" + user.getId(); // TODO Check that it doesn't contain invalid filesystem chars } + @SuppressFBWarnings("NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION") @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; @@ -299,15 +350,14 @@ public String toString() { * * @return the user. */ - public SyncUser getUser() { + public RealmUser getUser() { return user; } /** - * Returns the fully disambiguated URI for the remote Realm i.e., the {@code /~/} placeholder has been replaced - * by the proper user ID. + * Returns the server URI for the remote MongoDB Realm the local Realm is synchronizing with. * - * @return {@link URI} identifying the remote Realm this local Realm is synchronized with. + * @return {@link URI} identifying the MongoDB Realm this local Realm is synchronized with. */ public URI getServerUrl() { return serverUrl; @@ -411,19 +461,27 @@ public String getUrlPrefix() { /** * Returns what happens in case of a Client Resync. */ - public ClientResyncMode getClientResyncMode() { + ClientResyncMode getClientResyncMode() { return clientResyncMode; } + /** + * Returns the value this Realm is partitioned on. The partition key is a property defined in + * MongoDB Realm. All classes with a property with this value will be synchronized to the + * Realm. + * + * @return the value being used by MongoDB Realm to partition the server side MongoDB Database + * into Realms that can be synchronized independently. + */ + public BsonValue getPartitionValue() { + return partitionValue; + } + /** * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ public static final class Builder { - private File directory; - private boolean overrideDefaultFolder = false; - private String fileName; - private boolean overrideDefaultLocalFileName = false; @Nullable private byte[] key; private long schemaVersion = 0; @@ -443,8 +501,8 @@ public static final class Builder { // sync specific private boolean deleteRealmOnLogout = false; private URI serverUrl; - private SyncUser user = null; - private SyncSession.ErrorHandler errorHandler = SyncManager.defaultSessionErrorHandler; + private RealmUser user = null; + private SyncSession.ErrorHandler errorHandler; private boolean syncClientValidateSsl = true; @Nullable private String serverCertificateAssetName; @@ -456,65 +514,103 @@ public static final class Builder { @Nullable // null means the user hasn't explicitly set one. An appropriate default is chosen when calling build() private ClientResyncMode clientResyncMode = null; private long maxNumberOfActiveVersions = Long.MAX_VALUE; + private final BsonValue partitionValue; - Builder(Context context, SyncUser user, String url) { - //noinspection ConstantConditions + /** + * FIXME + * + * @param user + * @param partitionValue + */ + public Builder(RealmUser user, String partitionValue) { + this(user, new BsonString(partitionValue)); + } + + /** + * FIXME + * + * @param user + * @param partitionValue + */ + public Builder(RealmUser user, ObjectId partitionValue) { + this(user, new BsonObjectId(partitionValue)); + } + + /** + * FIXME + * + * @param user + * @param partitionValue + */ + public Builder(RealmUser user, int partitionValue) { + this(user, new BsonInt32(partitionValue)); + } + + /** + * FIXME + * + * @param user + * @param partitionValue + */ + public Builder(RealmUser user, long partitionValue) { + this(user, new BsonInt64(partitionValue)); + } + + /** + * Builder used to construct instances of a SyncConfiguration in a fluent manner. + * + * @param user the user opening the Realm on the server. + * @param partitionValue te value this Realm is partitioned on. The partition key is a + * property defined in MongoDB Realm. All classes with a property with this value will be + * synchronized to the Realm. + * @see Link to docs about partions + */ + private Builder(RealmUser user, BsonValue partitionValue) { + Context context = BaseRealm.applicationContext; if (context == null) { throw new IllegalStateException("Call `Realm.init(Context)` before creating a SyncConfiguration"); } - this.defaultFolder = new File(context.getFilesDir(), "realm-object-server"); + Util.checkNull(user, "user"); + Util.checkNull(partitionValue, "partitionValue"); + validateAndSet(user); + validateAndSet(user.getApp().getConfiguration().getBaseUrl()); + this.partitionValue = partitionValue; + this.defaultFolder = user.getApp().getConfiguration().getSyncRootDirectory(); if (Realm.getDefaultModule() != null) { this.modules.add(Realm.getDefaultModule()); } - - validateAndSet(user); - validateAndSet(url); + this.errorHandler = user.getApp().getConfiguration().getDefaultErrorHandler(); } - private void validateAndSet(SyncUser user) { + private void validateAndSet(RealmUser user) { //noinspection ConstantConditions if (user == null) { throw new IllegalArgumentException("Non-null `user` required."); } - if (!user.isValid()) { + if (!user.isLoggedIn()) { throw new IllegalArgumentException("User not authenticated or authentication expired."); } this.user = user; } - private void validateAndSet(String uri) { - //noinspection ConstantConditions - if (uri == null) { - throw new IllegalArgumentException("Non-null 'uri' required."); - } - + private void validateAndSet(URL baseUrl ) { try { - serverUrl = new URI(uri); + serverUrl = new URI(baseUrl.toString()); } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid URI: " + uri, e); + throw new IllegalArgumentException("Invalid URI: " + baseUrl.toString(), e); } try { // Automatically set scheme based on auth server if not set or wrongly set String serverScheme = serverUrl.getScheme(); - if (serverScheme == null) { - String authProtocol = user.getAuthenticationUrl().getProtocol(); - if (authProtocol.equalsIgnoreCase("https")) { - serverScheme = "realms"; - } else { - serverScheme = "realm"; - } - } else if (serverScheme.equalsIgnoreCase("http")) { - serverScheme = "realm"; + if (serverScheme == null || serverScheme.equalsIgnoreCase("http")) { + serverScheme = "ws"; } else if (serverScheme.equalsIgnoreCase("https")) { - serverScheme = "realms"; + serverScheme = "wss"; } // Automatically set host if one wasn't defined String host = serverUrl.getHost(); - if (host == null) { - host = user.getAuthenticationUrl().getHost(); - } // Convert relative paths to absolute if required String path = serverUrl.getPath(); @@ -531,91 +627,10 @@ private void validateAndSet(String uri) { serverUrl.getRawFragment()); } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid URI: " + uri, e); - } - - // Detect last path segment as it is the default file name - String path = serverUrl.getPath(); - if (path == null) { - throw new IllegalArgumentException("Invalid URI: " + uri); + throw new IllegalArgumentException("Invalid URI: " + baseUrl, e); } - String[] pathSegments = path.split("/"); - for (int i = 1; i < pathSegments.length; i++) { - String segment = pathSegments[i]; - if (segment.equals("~")) { - continue; - } - if (segment.equals("..") || segment.equals(".")) { - throw new IllegalArgumentException("The URI has an invalid segment: " + segment); - } - Matcher m = pattern.matcher(segment); - if (!m.matches()) { - throw new IllegalArgumentException("The URI must only contain characters 0-9, a-z, A-Z, ., _, and -: " + segment); - } - } - - this.defaultLocalFileName = pathSegments[pathSegments.length - 1]; - - // Validate filename - // TODO Lift this restriction on the Object Server - if (defaultLocalFileName.endsWith(".realm") - || defaultLocalFileName.endsWith(".realm.lock") - || defaultLocalFileName.endsWith(".realm.management")) { - throw new IllegalArgumentException("The URI must not end with '.realm', '.realm.lock' or '.realm.management: " + uri); - } - } - - /** - * Sets the local file name for the Realm. - * This will override the default name defined by the Realm URL. - * - * @param filename name of the local file on disk. - * @throws IllegalArgumentException if file name is {@code null} or empty. - */ - public Builder name(String filename) { - //noinspection ConstantConditions - if (filename == null || filename.isEmpty()) { - throw new IllegalArgumentException("A non-empty filename must be provided"); - } - this.fileName = filename; - this.overrideDefaultLocalFileName = true; - return this; - } - - /** - * Sets the local root directory where synchronized Realm files can be saved. - *

              - * Synchronized Realms will not be saved directly in the provided directory, but instead in a - * subfolder that matches the path defined by Realm URI. As Realm server URIs are unique - * this means that multiple users can save their Realms on disk without the risk of them overwriting - * each other files. - *

              - * The default location is {@code context.getFilesDir()}. - * - * @param directory directory on disk where the Realm file can be saved. - * @throws IllegalArgumentException if the directory is not valid. - */ - public Builder directory(File directory) { - //noinspection ConstantConditions - if (directory == null) { - throw new IllegalArgumentException("Non-null 'directory' required."); - } - if (directory.isFile()) { - throw new IllegalArgumentException("'directory' is a file, not a directory: " + - directory.getAbsolutePath() + "."); - } - if (!directory.exists() && !directory.mkdirs()) { - throw new IllegalArgumentException("Could not create the specified directory: " + - directory.getAbsolutePath() + "."); - } - if (!directory.canWrite()) { - throw new IllegalArgumentException("Realm directory is not writable: " + - directory.getAbsolutePath() + "."); - } - this.directory = directory; - overrideDefaultFolder = true; - return this; + this.defaultLocalFileName = "default.realm"; } /** @@ -803,7 +818,7 @@ public Builder inMemory() { /** * Sets the error handler used by this configuration. This will override any handler set by calling - * {@link SyncManager#setDefaultSessionErrorHandler(SyncSession.ErrorHandler)}. + * {@link RealmSync#setDefaultSessionErrorHandler(SyncSession.ErrorHandler)}. *

              * Only errors not handled by the defined {@code SyncPolicy} will be reported to this error handler. * @@ -943,10 +958,10 @@ public SyncConfiguration.Builder compactOnLaunch(CompactOnLaunchCallback compact } /** - * The prefix that is prepended to the path in the HTTP request that initiates a sync - * connection to the Realm Object Server. The value specified must match the server’s - * configuration otherwise the device will not be able to create a connection. If no value - * is specified then the default {@code /realm-sync} path is used. + * The prefix that is prepended to the path in the WebSocket request that initiates a sync + * connection to MongoDB Realm. The value specified must match the server’s configuration + * otherwise the device will not be able to create a connection. This value is optional + * and should only be set if a specific firewall rule requires it. * * @param urlPrefix The prefix to append to the sync connection url. * @see Adding a custom proxy @@ -955,6 +970,9 @@ public SyncConfiguration.Builder urlPrefix(String urlPrefix) { if (Util.isEmptyString(urlPrefix)) { throw new IllegalArgumentException("Non-empty 'urlPrefix' required"); } + if (urlPrefix.endsWith("/")) { + urlPrefix = urlPrefix.substring(0, Math.min(0, urlPrefix.length() - 2)); + } this.syncUrlPrefix = urlPrefix; return this; } @@ -990,6 +1008,8 @@ public Builder deleteRealmOnLogout() { */ /** + * TODO: Removed from the public API until MongoDB Realm correctly supports anything byt MANUAL mode again. + * * Configure the behavior in case of a Client Resync. *

              * The default mode is {@link ClientResyncMode#RECOVER_LOCAL_REALM}. @@ -997,7 +1017,7 @@ public Builder deleteRealmOnLogout() { * @param mode what should happen when a Client Resync happens * @see ClientResyncMode for more information about what a Client Resync is. */ - public Builder clientResyncMode(ClientResyncMode mode) { + Builder clientResyncMode(ClientResyncMode mode) { //noinspection ConstantConditions if (mode == null) { throw new IllegalArgumentException("Non-null 'mode' required."); @@ -1052,48 +1072,35 @@ public SyncConfiguration build() { } } - // Check if the user has an identifier, if not, it cannot use /~/. - if (serverUrl.toString().contains("/~/") && user.getIdentity() == null) { - throw new IllegalStateException("The serverUrl contains a /~/, but the user does not have an identity." + - " Most likely it hasn't been authenticated yet or has been created directly from an" + - " access token. Use a path without /~/."); - } - // Set the default Client Resync Mode based on the current type of Realm. // Eventually RECOVER_LOCAL_REALM should be the default for all types. + // FIXME: We should add support back for this. if (clientResyncMode == null) { - clientResyncMode = ClientResyncMode.RECOVER_LOCAL_REALM; + clientResyncMode = ClientResyncMode.MANUAL; } if (rxFactory == null && isRxJavaAvailable()) { rxFactory = new RealmObservableFactory(true); } + // FIXME: Figure out how to map to on-disk path. Partition key can be up to 16MB in size. // Determine location on disk - // Use the serverUrl + user to create a unique filepath unless it has been explicitly overridden. - // /// - URI resolvedServerUrl = resolveServerUrl(serverUrl, user.getIdentity()); - File rootDir = overrideDefaultFolder ? directory : defaultFolder; - String realmPathFromRootDir = user.getIdentity() + "/" + getServerPath(resolvedServerUrl); - File realmFileDirectory = new File(rootDir, realmPathFromRootDir); - - String realmFileName = overrideDefaultLocalFileName ? fileName : defaultLocalFileName; + // Use the serverUrl + user to create a unique filepath. + // The following types of paths can be generated + // //default.realm + // ///default.realm + URI resolvedServerUrl = serverUrl; // resolveServerUrl(serverUrl, user); + syncUrlPrefix = String.format("/api/client/v2.0/app/%s/realm-sync", user.getApp().getConfiguration().getAppId()); + String realmPathFromRootDir = user.getId() + "/" + getServerPath(user, resolvedServerUrl); + File realmFileDirectory = new File(defaultFolder, realmPathFromRootDir); + String realmFileName = defaultLocalFileName; String fullPathName = realmFileDirectory.getAbsolutePath() + File.pathSeparator + realmFileName; + // full path must not exceed 256 characters (on FAT) if (fullPathName.length() > MAX_FULL_PATH_LENGTH) { - // path is too long, so we make the file name shorter - realmFileName = MD5(realmFileName); - fullPathName = realmFileDirectory.getAbsolutePath() + File.pathSeparator + realmFileName; - if (fullPathName.length() > MAX_FULL_PATH_LENGTH) { - // use rootDir/userIdentify as directory instead as it is shorter - realmFileDirectory = new File(rootDir, user.getIdentity()); - fullPathName = realmFileDirectory.getAbsolutePath() + File.pathSeparator + realmFileName; - if (fullPathName.length() > MAX_FULL_PATH_LENGTH) { // we are out of ideas - throw new IllegalStateException(String.format(Locale.US, - "Full path name must not exceed %d characters: %s", - MAX_FULL_PATH_LENGTH, fullPathName)); - } - } + throw new IllegalStateException(String.format(Locale.US, + "Full path name must not exceed %d characters: %s", + MAX_FULL_PATH_LENGTH, fullPathName)); } if (realmFileName.length() > MAX_FILE_NAME_LENGTH) { @@ -1154,7 +1161,8 @@ public SyncConfiguration build() { sessionStopPolicy, compactOnLaunch, syncUrlPrefix, - clientResyncMode + clientResyncMode, + partitionValue ); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java deleted file mode 100644 index 3f167ccb05..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ /dev/null @@ -1,754 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nullable; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.internal.Keep; -import io.realm.internal.OsRealmConfig; -import io.realm.internal.Util; -import io.realm.internal.network.NetworkStateReceiver; -import io.realm.log.RealmLog; -import okhttp3.internal.tls.OkHostnameVerifier; - -/** - * The SyncManager is the central controller for interacting with the Realm Object Server. - * It handles the creation of {@link SyncSession}s and it is possible to configure session defaults and the underlying - * network client using this class. - *

              - * Through the SyncManager, it is possible to add authentication listeners. An authentication listener will - * response to events like user logging in or out. - *

              - * Default error handling for any {@link SyncConfiguration} can be added using the SyncManager. - * - */ -@Keep -@SuppressFBWarnings("MS_CANNOT_BE_FINAL") -public class SyncManager { - - /** - * Debugging related options. - */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") - public static class Debug { - /** - * Set this to true to bypass checking if the device is offline before making HTTP requests. - */ - public static boolean skipOnlineChecking = false; - - /** - * Set this to true to init a SyncManager with a directory named by the process ID. This is useful for - * integration tests which are emulating multiple sync client by using multiple processes. - */ - public static boolean separatedDirForSyncManager = false; - } - - /** - * APP ID sent to the Realm Object Server. Is automatically initialized to the package name for the app. - */ - public static String APP_ID = null; - - /** - * Thread pool used when doing network requests against the Realm Object Server. - *

              - * This pool is only exposed for testing purposes and replacing it while the queue is not - * empty will result in undefined behaviour. - */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") - public static ThreadPoolExecutor NETWORK_POOL_EXECUTOR = new ThreadPoolExecutor( - 10, 10, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100)); - - private static final SyncSession.ErrorHandler SESSION_NO_OP_ERROR_HANDLER = new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { - RealmLog.error("Client Reset required for: " + session.getConfiguration().getServerUrl()); - return; - } - - String errorMsg = String.format(Locale.US, "Session Error[%s]: %s", - session.getConfiguration().getServerUrl(), - error.toString()); - switch (error.getErrorCode().getCategory()) { - case FATAL: - RealmLog.error(errorMsg); - break; - case RECOVERABLE: - RealmLog.info(errorMsg); - break; - default: - throw new IllegalArgumentException("Unsupported error category: " + error.getErrorCode().getCategory()); - } - } - }; - // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path' - private static Map sessions = new ConcurrentHashMap<>(); - private static CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList(); - - // The Sync Client is lightweight, but consider creating/removing it when there is no sessions. - // Right now it just lives and dies together with the process. -// private static volatile RealmObjectServer authServer = new OkHttpRealmObjectServer(); - - // Header configuration - private static String globalAuthorizationHeaderName = "Authorization"; // authorization header name if no host-defined header is available - private static Map hostRestrictedAuthorizationHeaderName = new HashMap<>(); // authorization header name for the given host - private static Map globalCustomHeaders = new HashMap<>(); - private static Map> hostRestrictedCustomHeaders = new HashMap<>(); - - private static NetworkStateReceiver.ConnectionListener networkListener = new NetworkStateReceiver.ConnectionListener() { - @Override - public void onChange(boolean connectionAvailable) { - if (connectionAvailable) { - RealmLog.debug("NetworkListener: Connection available"); - // notify all sessions - notifyNetworkIsBack(); - } else { - RealmLog.debug("NetworkListener: Connection lost"); - } - } - }; - - static volatile SyncSession.ErrorHandler defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; - - // Initialize the SyncManager - static void init(String appId) { - SyncManager.APP_ID = appId; - } - - /** - * Sets a global authentication listener that will be notified about User events like - * login and logout. - * - * @param listener listener to register. - * @throws IllegalArgumentException if {@code listener} is {@code null}. - */ - public static void addAuthenticationListener(AuthenticationListener listener) { - //noinspection ConstantConditions - if (listener == null) { - throw new IllegalArgumentException("Non-null 'listener' required."); - } - authListeners.add(listener); - } - - /** - * Removes the provided global authentication listener. - * - * @param listener listener to remove. - */ - public static void removeAuthenticationListener(AuthenticationListener listener) { - //noinspection ConstantConditions - if (listener == null) { - return; - } - authListeners.remove(listener); - } - - /** - * Sets the default error handler used by all {@link SyncConfiguration} objects when they are created. - * - * @param errorHandler the default error handler used when interacting with a Realm managed by a Realm Object Server. - */ - public static void setDefaultSessionErrorHandler(@Nullable SyncSession.ErrorHandler errorHandler) { - if (errorHandler == null) { - defaultSessionErrorHandler = SESSION_NO_OP_ERROR_HANDLER; - } else { - defaultSessionErrorHandler = errorHandler; - } - } - - /** - * Gets a cached {@link SyncSession} for the given {@link SyncConfiguration} or throw if no one exists yet. - * - * A session should exist after you open a Realm with a {@link SyncConfiguration}. - * - * @param syncConfiguration configuration object for the synchronized Realm. - * @return the {@link SyncSession} for the specified Realm. - * @throws IllegalArgumentException if syncConfiguration is {@code null}. - * @throws IllegalStateException if the session could not be found using the provided {@code SyncConfiguration}. - */ - public static synchronized SyncSession getSession(SyncConfiguration syncConfiguration) throws IllegalStateException { - //noinspection ConstantConditions - if (syncConfiguration == null) { - throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); - } - - SyncSession session = sessions.get(syncConfiguration.getPath()); - if (session == null) { - throw new IllegalStateException("No SyncSession found using the path : " + syncConfiguration.getPath() - + "\nplease ensure to call this method after you've open the Realm"); - } - - return session; - } - - /** - * Gets any cached {@link SyncSession} for the given {@link SyncConfiguration} or create a new one if - * no one exists. - * - * Note: This is mainly for internal usage, consider using {@link #getSession(SyncConfiguration)} instead. - * - * @param syncConfiguration configuration object for the synchronized Realm. - * @param resolvedRealmURL resolved Realm URL with the user specific part if not a global Realm. - * @return the {@link SyncSession} for the specified Realm. - * @throws IllegalArgumentException if syncConfiguration is {@code null}. - */ - public static synchronized SyncSession getOrCreateSession(SyncConfiguration syncConfiguration, @Nullable URI resolvedRealmURL) { - // This will not create a new native (Object Store) session, this will only associate a Realm's path - // with a SyncSession. Object Store's SyncManager is responsible of the life cycle (including creation) - // of the native session. The provided Java wrap, helps interact with the native session, when reporting error - // or requesting an access_token for example. - - //noinspection ConstantConditions - if (syncConfiguration == null) { - throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); - } - - SyncSession session = sessions.get(syncConfiguration.getPath()); - if (session == null) { - RealmLog.debug("Creating session for: %s", syncConfiguration.getPath()); - session = new SyncSession(syncConfiguration); - sessions.put(syncConfiguration.getPath(), session); - if (sessions.size() == 1) { - RealmLog.debug("First session created. Adding network listener."); - NetworkStateReceiver.addListener(networkListener); - } - if (resolvedRealmURL != null) { - session.setResolvedRealmURI(resolvedRealmURL); - // Currently when the user login, the Object Store will try to revive it's inactive sessions - // (stored previously after a logout). this will cause the OS to call bindSession to obtain an - // access token, however since the Realm might not be open yet, the wrapObjectStoreSessionIfRequired - // will not be invoked to wrap the OS store session with the Java session, the Sync client to not resume - // syncing. -// session.getAccessToken(authServer, ""); - } - - // The underlying session will be created as part of opening the Realm, but this approach - // does not work when using `Realm.getInstanceAsync()` in combination with AsyncOpen. - // - // So instead we manually create the underlying native session. - OsRealmConfig config = new OsRealmConfig.Builder(syncConfiguration).build(); - nativeCreateSession(config.getNativePtr()); - } - - return session; - } - - /** - * Sets the name of the HTTP header used to send authorization data in when making requests to - * all Realm Object Servers used by the app. These servers must have been configured to expect a - * custom authorization header. - *

              - * The default authorization header is named "Authorization". - * - * @param headerName name of the header. - * @throws IllegalArgumentException if a null or empty header is provided. - * @see Adding a custom proxy - */ - public static synchronized void setAuthorizationHeaderName(String headerName) { - checkNotEmpty(headerName, "headerName"); -// authServer.setAuthorizationHeaderName(headerName, null); - globalAuthorizationHeaderName = headerName; - } - - /** - * Sets the name of the HTTP header used to send authorization data in when making requests to - * the Realm Object Server running on the defined {@code host}. This server must have been - * configured to expect a custom authorization header. - *

              - * The default authorization header is named "Authorization". - * - * @param headerName name of the header. - * @param host if this is provided, the authorization header name will only be used on this particular host. - * Example of valid values: "localhost", "127.0.0.1" and "myinstance.us1.cloud.realm.io". - * @throws IllegalArgumentException if a {@code null} or empty header and/or host is provided. - * @see Adding a custom proxy - */ - - public static synchronized void setAuthorizationHeaderName(String headerName, String host) { - checkNotEmpty(headerName, "headerName"); - checkNotEmpty(host, "host"); - host = host.toLowerCase(Locale.US); -// authServer.setAuthorizationHeaderName(headerName, host); - hostRestrictedAuthorizationHeaderName.put(host, headerName); - } - - /** - * Adds an extra HTTP header to append to every request to a Realm Object Server. - * - * @param headerName the name of the header. - * @param headerValue the value of header. - * @throws IllegalArgumentException if a non-empty {@code headerName} is provided or a null {@code headerValue}. - */ - public static synchronized void addCustomRequestHeader(String headerName, String headerValue) { - checkNotEmpty(headerName, "headerName"); - checkNotNull(headerValue, "headerValue"); -// authServer.addHeader(headerName, headerValue, null); - globalCustomHeaders.put(headerName, headerValue); - } - - /** - * Adds an extra HTTP header to append to every request to a Realm Object Server. - * - * @param headerName the name of the header. - * @param headerValue the value of header. - * @param host if this is provided, this header will only be used on this particular host. - * Example of valid values: "localhost", "127.0.0.1" and "myinstance.us1.cloud.realm.io". - * @throws IllegalArgumentException If an non-empty {@code headerName}, {@code headerValue} or {@code host} is provided. - */ - public static synchronized void addCustomRequestHeader(String headerName, String headerValue, String host) { - checkNotEmpty(headerName, "headerName"); - checkNotNull(headerValue, "headerValue"); - checkNotEmpty(host, "host"); - - // Headers - host = host.toLowerCase(Locale.US); -// authServer.addHeader(headerName, headerValue, host); - Map headers = hostRestrictedCustomHeaders.get(host); - if (headers == null) { - headers = new LinkedHashMap<>(); - hostRestrictedCustomHeaders.put(host, headers); - } - headers.put(headerName, headerValue); - } - - /** - * Adds extra HTTP headers to append to every request to a Realm Object Server. - * - * @param headers map of (headerName, headerValue) pairs. - * @throws IllegalArgumentException If any of the headers provided are illegal. - */ - public static synchronized void addCustomRequestHeaders(@Nullable Map headers) { - if (headers != null) { - for (Map.Entry entry : headers.entrySet()) { - addCustomRequestHeader(entry.getKey(), entry.getValue()); - } - } - } - - /** - * Adds extra HTTP headers to append to every request to a Realm Object Server. - * - * @param headers map of (headerName, headerValue) pairs. - * @param host if this is provided, the this header will only be used on this particular host. - * Example of valid values: "localhost", "127.0.0.1" and "myinstance.us1.cloud.realm.io". - * @throws IllegalArgumentException If any of the headers provided are illegal. - */ - public static synchronized void addCustomRequestHeaders(@Nullable Map headers, String host) { - if (Util.isEmptyString(host)) { - throw new IllegalArgumentException("Non-empty 'host' required"); - } - host = host.toLowerCase(Locale.US); - if (headers != null) { - for (Map.Entry entry : headers.entrySet()) { - addCustomRequestHeader(entry.getKey(), entry.getValue(), host); - } - } - } - - /** - * Returns the authentication header name used for the http request to the given url. - * - * @param objectServerUrl Url to get header for. - * @return the authorization header name used by http requests to this url. - */ - public static synchronized String getAuthorizationHeaderName(URI objectServerUrl) { - String host = objectServerUrl.getHost().toLowerCase(Locale.US); - String hostRestrictedHeader = hostRestrictedAuthorizationHeaderName.get(host); - return (hostRestrictedHeader != null) ? hostRestrictedHeader : globalAuthorizationHeaderName; - } - - /** - * Returns all the custom headers added to requests to the given url. - * - * @return all defined custom headers used when making http requests to the given url. - */ - public static synchronized Map getCustomRequestHeaders(URI serverSyncUrl) { - Map headers = new LinkedHashMap<>(globalCustomHeaders); - String host = serverSyncUrl.getHost().toLowerCase(Locale.US); - Map hostHeaders = hostRestrictedCustomHeaders.get(host); - if (hostHeaders != null) { - for (Map.Entry entry : hostHeaders.entrySet()) { - headers.put(entry.getKey(), entry.getValue()); - } - } - return headers; - } - - /** - * Remove the wrapped Java session. - * @param syncConfiguration configuration object for the synchronized Realm. - */ - @SuppressWarnings("unused") - private static synchronized void removeSession(SyncConfiguration syncConfiguration) { - //noinspection ConstantConditions - if (syncConfiguration == null) { - throw new IllegalArgumentException("A non-empty 'syncConfiguration' is required."); - } - RealmLog.debug("Removing session for: %s", syncConfiguration.getPath()); - SyncSession syncSession = sessions.remove(syncConfiguration.getPath()); - if (syncSession != null) { - syncSession.close(); - } - if (sessions.isEmpty()) { - RealmLog.debug("Last session dropped. Remove network listener."); - NetworkStateReceiver.removeListener(networkListener); - } - } - - /** - * Returns the all valid sessions belonging to the user. - * - * @param syncUser the user to use. - * @return the all valid sessions belonging to the user. - */ - static List getAllSessions(SyncUser syncUser) { - //noinspection ConstantConditions - if (syncUser == null) { - throw new IllegalArgumentException("A non-empty 'syncUser' is required."); - } - ArrayList allSessions = new ArrayList(); - for (SyncSession syncSession : sessions.values()) { - if (syncSession.getUser().equals(syncUser)) { - allSessions.add(syncSession); - } - } - return allSessions; - } - -// static RealmObjectServer getAuthServer() { -// return authServer; -// } - -// /** -// * Sets the auth server implementation used when validating credentials. -// */ -// static void setAuthServerImpl(RealmObjectServer authServerImpl) { -// authServer = authServerImpl; -// } - - // Notify listeners that a user logged in - static void notifyUserLoggedIn(SyncUser user) { - for (AuthenticationListener authListener : authListeners) { - authListener.loggedIn(user); - } - } - - // Notify listeners that a user logged out successfully - static void notifyUserLoggedOut(SyncUser user) { - for (AuthenticationListener authListener : authListeners) { - authListener.loggedOut(user); - } - } - - /** - * All errors from native Sync is reported to this method. From the path we can determine which - * session to contact. If {@code path == null} all sessions are effected. - */ - @SuppressWarnings("unused") - private static synchronized void notifyErrorHandler(String nativeErrorCategory, int nativeErrorCode, String errorMessage, @Nullable String path) { - if (Util.isEmptyString(path)) { - // notify all sessions - for (SyncSession syncSession : sessions.values()) { - try { - syncSession.notifySessionError(nativeErrorCategory, nativeErrorCode, errorMessage); - } catch (Exception exception) { - RealmLog.error(exception); - } - } - } else { - SyncSession syncSession = sessions.get(path); - if (syncSession != null) { - try { - syncSession.notifySessionError(nativeErrorCategory, nativeErrorCode, errorMessage); - } catch (Exception exception) { - RealmLog.error(exception); - } - } else { - RealmLog.warn("Cannot find the SyncSession corresponding to the path: " + path); - } - } - } - - private static synchronized void notifyNetworkIsBack() { - try { - nativeReconnect(); - } catch (Exception exception) { - RealmLog.error(exception); - } - } - - /** - * All progress listener events from native Sync are reported to this method. - * It costs 2 HashMap lookups for each listener triggered (one to find the session, one to - * find the progress listener), but it means we don't have to cache anything on the C++ side which - * can leak since we don't have control over the session lifecycle. - */ - @SuppressWarnings("unused") - private static synchronized void notifyProgressListener(String localRealmPath, long listenerId, long transferedBytes, long transferableBytes) { - SyncSession session = sessions.get(localRealmPath); - if (session != null) { - try { - session.notifyProgressListener(listenerId, transferedBytes, transferableBytes); - } catch (Exception exception) { - RealmLog.error(exception); - } - } - } - - /** - * Called from native code. This method is not allowed to throw as it would be swallowed - * by the native Sync Client thread. Instead log all exceptions to logcat. - */ - @SuppressWarnings("unused") - private static synchronized void notifyConnectionListeners(String localRealmPath, long oldState, long newState) { - SyncSession session = sessions.get(localRealmPath); - if (session != null) { - try { - session.notifyConnectionListeners(ConnectionState.fromNativeValue(oldState), ConnectionState.fromNativeValue(newState)); - } catch (Exception exception) { - RealmLog.error(exception); - } - } - } - - /** - * This is called from the Object Store (through JNI) to request an {@code access_token} for - * the session specified by sessionPath. - * - * This will also schedule a timer to proactively refresh the {@code access_token} regularly, before - * the {@code access_token} expires. - * - * @throws IllegalStateException if the wrapped Java session is not found. - * @param sessionPath The path to the previously Java wraped session. - * @return a valid cached {@code access_token} if available or null. - */ - @SuppressWarnings("unused") -// private synchronized static String bindSessionWithConfig(String sessionPath, String refreshToken) { -// final SyncSession syncSession = sessions.get(sessionPath); -// if (syncSession == null) { -// RealmLog.error("Matching Java SyncSession could not be found for: " + sessionPath); -// } else { -// try { -// return syncSession.getAccessToken(authServer, refreshToken); -// } catch (Exception exception) { -// RealmLog.error(exception); -// } -// } -// return null; -// } - - /** - * Realm will automatically detect when a device gets connectivity after being offline and - * resume syncing. - *

              - * However, as some of these checks are performed using incremental backoff, this will in some - * cases not happen immediately. - *

              - * In those cases it can be beneficial to call this method manually, which will force all - * sessions to attempt to reconnect immediately and reset any timers they are using for - * incremental backoff. - */ - public static void refreshConnections() { - notifyNetworkIsBack(); - } - - // Holds the certificate chain (per hostname). We need to keep the order of each certificate - // according to it's depth in the chain. The depth of the last - // certificate is 0. The depth of the first certificate is chain - // length - 1. - private static HashMap> ROS_CERTIFICATES_CHAIN; - - // The default Android Trust Manager which uses the default KeyStore to - // validate the certificate chain. - private static X509TrustManager TRUST_MANAGER; - - // Help transform a String PEM representation of the certificate, into - // X509Certificate format. - private static CertificateFactory CERTIFICATE_FACTORY; - - // From Sync implementation: - // A recommended way of using the callback function is to return true - // if preverify_ok = 1 and depth > 0, - // always check the host name if depth = 0, - // and use an independent verification step if preverify_ok = 0. - // - // Another possible way of using the callback is to collect all the - // ROS_CERTIFICATES_CHAIN until depth = 0, and present the entire chain for - // independent verification. - // - // In this implementation we use the second method, since it's more suitable for - // the underlying Java API we need to call to validate the certificate chain. - @SuppressWarnings("unused") - synchronized static boolean sslVerifyCallback(String serverAddress, String pemData, int depth) { - try { - if (ROS_CERTIFICATES_CHAIN == null) { - ROS_CERTIFICATES_CHAIN = new HashMap<>(); - TRUST_MANAGER = systemDefaultTrustManager(); - CERTIFICATE_FACTORY = CertificateFactory.getInstance("X.509"); - } - - if (!ROS_CERTIFICATES_CHAIN.containsKey(serverAddress)) { - ROS_CERTIFICATES_CHAIN.put(serverAddress, new ArrayList()); - } - - ROS_CERTIFICATES_CHAIN.get(serverAddress).add(pemData); - - if (depth == 0) { - // transform all PEM ROS_CERTIFICATES_CHAIN into Java X509 - // with respecting the order/depth provided from Sync. - List pemChain = ROS_CERTIFICATES_CHAIN.get(serverAddress); - int n = pemChain.size(); - X509Certificate[] chain = new X509Certificate[n]; - for (String pem : pemChain) { - // The depth of the last certificate is 0. - // The depth of the first certificate is chain length - 1. - chain[--n] = buildCertificateFromPEM(pem); - } - - // verify the entire chain - try { - TRUST_MANAGER.checkClientTrusted(chain, "RSA"); - // verify the hostname - boolean isValid = OkHostnameVerifier.INSTANCE.verify(serverAddress, chain[0]); - if (isValid) { - return true; - } else { - RealmLog.error("Can not verify the hostname for the host: " + serverAddress); - return false; - } - } catch (CertificateException e) { - RealmLog.error(e, "Can not validate SSL chain certificate for the host: " + serverAddress); - return false; - } finally { - // don't keep the certificate chain in memory - ROS_CERTIFICATES_CHAIN.remove(serverAddress); - } - } else { - // return true, since the verification will happen for the entire chain - // when receiving the depth == 0 (host certificate) - return true; - } - } catch (Exception e) { - RealmLog.error(e, "Error during certificate validation for host: " + serverAddress); - return false; - } - } - - // Credit OkHttp https://github.com/square/okhttp/blob/e5c84e1aef9572adb493197c1b6c4e882aca085b/okhttp/src/main/java/okhttp3/OkHttpClient.java#L270 - private static X509TrustManager systemDefaultTrustManager() { - try { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException("Unexpected default trust managers:" - + Arrays.toString(trustManagers)); - } - return (X509TrustManager) trustManagers[0]; - } catch (GeneralSecurityException e) { - throw new IllegalStateException("No System TLS", e); // The system has no TLS. Just give up. - } - } - - private static X509Certificate buildCertificateFromPEM(String pem) throws IOException, CertificateException { - InputStream stream = null; - try { - stream = new ByteArrayInputStream(pem.getBytes("UTF-8")); - return (X509Certificate) CERTIFICATE_FACTORY.generateCertificate(stream); - } finally { - if (stream != null) { - stream.close(); - } - } - } - - private static void checkNotEmpty(String headerName, String varName) { - if (Util.isEmptyString(headerName)) { - throw new IllegalArgumentException("Non-empty '" + varName +"' required."); - } - } - - private static void checkNotNull(@Nullable String val, String varName) { - if (val == null) { - throw new IllegalArgumentException("Non-null'" + varName +"' required."); - } - } - - /** - * Resets the SyncManger and clear all existing users. - * This will also terminate all sessions. - * - * Only call this method when testing. - */ - static synchronized void reset() { - nativeReset(); - sessions.clear(); - hostRestrictedAuthorizationHeaderName.clear(); - globalAuthorizationHeaderName = "Authorization"; - hostRestrictedCustomHeaders.clear(); - globalCustomHeaders.clear(); -// authServer.clearCustomHeaderSettings(); - } - - /** - * Simulate a Client Reset by triggering the Object Store error handler with Sync Error Code that will be - * converted to a Client Reset (211 - Diverging Histories). - * - * Only call this method when testing. - * - * @param session Session to trigger Client Reset for. - */ - static void simulateClientReset(SyncSession session) { - nativeSimulateSyncError(session.getConfiguration().getPath(), - ErrorCode.DIVERGING_HISTORIES.intValue(), - "Simulate Client Reset", - true); - } - - protected static native void nativeInitializeSyncManager(String syncBaseDir, String bindingUserAgentInfo, String appUserAgentInfo); - private static native void nativeReset(); - private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); - private static native void nativeReconnect(); - private static native void nativeCreateSession(long nativeConfigPtr); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 717c5aeca7..916f5074c8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -24,17 +24,13 @@ import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.Nullable; - import io.realm.internal.Keep; -import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.Util; import io.realm.internal.util.Pair; import io.realm.log.RealmLog; @@ -59,17 +55,11 @@ */ @Keep public class SyncSession { - private final static ScheduledThreadPoolExecutor REFRESH_TOKENS_EXECUTOR = new ScheduledThreadPoolExecutor(1); - private final static long REFRESH_MARGIN_DELAY = TimeUnit.SECONDS.toMillis(10); private final static int DIRECTION_DOWNLOAD = 1; private final static int DIRECTION_UPLOAD = 2; private final SyncConfiguration configuration; private final ErrorHandler errorHandler; - private RealmAsyncTask networkRequest; - private RealmAsyncTask refreshTokenTask; - private RealmAsyncTask refreshTokenNetworkRequest; - private AtomicBoolean onGoingAccessTokenQuery = new AtomicBoolean(false); private volatile boolean isClosed = false; private final AtomicReference waitingForServerChanges = new AtomicReference<>(null); @@ -92,10 +82,9 @@ public class SyncSession { private final AtomicLong progressListenerId = new AtomicLong(-1); // represent different states as defined in SyncSession::PublicState 'sync_session.hpp' - private static final byte STATE_VALUE_WAITING_FOR_ACCESS_TOKEN = 0; - private static final byte STATE_VALUE_ACTIVE = 1; - private static final byte STATE_VALUE_DYING = 2; - private static final byte STATE_VALUE_INACTIVE = 3; + private static final byte STATE_VALUE_ACTIVE = 0; + private static final byte STATE_VALUE_DYING = 1; + private static final byte STATE_VALUE_INACTIVE = 2; // List of Java connection change listeners private final CopyOnWriteArrayList connectionListeners = new CopyOnWriteArrayList<>(); @@ -110,8 +99,6 @@ public class SyncSession { static final byte CONNECTION_VALUE_CONNECTING = 1; static final byte CONNECTION_VALUE_CONNECTED = 2; - private URI resolvedRealmURI; - /** * Enum describing the states a SyncSession can be in. The initial state is * {@link State#INACTIVE}. @@ -123,28 +110,16 @@ public enum State { /** * This is the initial state. The session is closed. No data is being synchronized. The session - * will automatically transition to {@link #WAITING_FOR_ACCESS_TOKEN} when a Realm is opened. + * will automatically transition to {@link #ACTIVE} when a Realm is opened. */ INACTIVE(STATE_VALUE_INACTIVE), - /** - * The user is attempting to synchronize data but needs a valid access token to do so. Realm - * will either use a cached token or automatically try to acquire one based on the current - * users login. This requires a network connection. - *

              - * Data cannot be synchronized in this state. - *

              - * Once a valid token is acquired, the session will transition to {@link #ACTIVE}. - */ - WAITING_FOR_ACCESS_TOKEN(STATE_VALUE_WAITING_FOR_ACCESS_TOKEN), - /** * The Realm is open and data will be synchronized between the device and the server * if the underlying connection is {@link ConnectionState#CONNECTED}. *

              - * The session will remain in this state until either the current login expires or the Realm - * is closed. In the first case, the session will transition to {@link #WAITING_FOR_ACCESS_TOKEN}, - * in the second case, it will become {@link #DYING}. + * The session will remain in this state until the Realm + * is closed. In which case it will become {@link #DYING}. */ ACTIVE(STATE_VALUE_ACTIVE), @@ -187,12 +162,12 @@ public SyncConfiguration getConfiguration() { } /** - * Returns the {@link SyncUser} defined by the {@link SyncConfiguration} that is used to connect to the - * Realm Object Server. + * Returns the {@link RealmUser} defined by the {@link SyncConfiguration} that is used to connect to + * MongoDB Realm. * - * @return {@link SyncUser} used to authenticate the session on the Realm Object Server. + * @return {@link RealmUser} used to authenticate the session on MongoDB Realm. */ - public SyncUser getUser() { + public RealmUser getUser() { return configuration.getUser(); } @@ -374,14 +349,8 @@ private void addProgressListener(ProgressMode mode, int direction, ProgressListe } private void checkProgressListenerArguments(ProgressMode mode, ProgressListener listener) { - //noinspection ConstantConditions - if (listener == null) { - throw new IllegalArgumentException("Non-null 'listener' required."); - } - //noinspection ConstantConditions - if (mode == null) { - throw new IllegalArgumentException("Non-null 'mode' required."); - } + Util.checkNull(listener, "listener"); + Util.checkNull(mode, "mode"); } /** @@ -393,7 +362,7 @@ private void checkProgressListenerArguments(ProgressMode mode, ProgressListener * @see ConnectionState */ public synchronized void addConnectionChangeListener(ConnectionListener listener) { - checkNonNullListener(listener); + Util.checkNull(listener, "listener"); if (connectionListeners.isEmpty()) { nativeConnectionListenerToken = nativeAddConnectionListener(configuration.getPath()); } @@ -407,7 +376,7 @@ public synchronized void addConnectionChangeListener(ConnectionListener listener * @throws IllegalArgumentException if the listener is {@code null}. */ public synchronized void removeConnectionChangeListener(ConnectionListener listener) { - checkNonNullListener(listener); + Util.checkNull(listener, "listener"); connectionListeners.remove(listener); if (connectionListeners.isEmpty()) { nativeRemoveConnectionListener(nativeConnectionListenerToken, configuration.getPath()); @@ -416,10 +385,6 @@ public synchronized void removeConnectionChangeListener(ConnectionListener liste void close() { isClosed = true; - if (networkRequest != null) { - networkRequest.cancel(); - } - clearScheduledAccessTokenRefresh(); } // This method will be called once all changes have been downloaded or uploaded. @@ -457,7 +422,7 @@ private void notifyAllChangesSent(int callbackId, Long errorcode, String errorMe * @throws InterruptedException if the thread was interrupted while downloading was in progress. */ public void downloadAllServerChanges() throws InterruptedException { - checkIfNotOnMainThread("downloadAllServerChanges() cannot be called from the main thread."); + Util.checkNotOnMainThread("downloadAllServerChanges() cannot be called from the main thread."); // Blocking only happens at the Java layer. To prevent deadlocking the underlying SyncSession we register // an async listener there and let it callback to the Java Session when done. This feels icky at best, but @@ -484,7 +449,7 @@ public void downloadAllServerChanges() throws InterruptedException { * @return {@code true} if the data was downloaded before the timeout. {@code false} if the operation timed out or otherwise failed. */ public boolean downloadAllServerChanges(long timeout, TimeUnit unit) throws InterruptedException { - checkIfNotOnMainThread("downloadAllServerChanges() cannot be called from the main thread."); + Util.checkNotOnMainThread("downloadAllServerChanges() cannot be called from the main thread."); checkTimeout(timeout, unit); // Blocking only happens at the Java layer. To prevent deadlocking the underlying SyncSession we register @@ -510,7 +475,7 @@ public boolean downloadAllServerChanges(long timeout, TimeUnit unit) throws Inte * @throws InterruptedException if the thread was interrupted while downloading was in progress. */ public void uploadAllLocalChanges() throws InterruptedException { - checkIfNotOnMainThread("uploadAllLocalChanges() cannot be called from the main thread."); + Util.checkNotOnMainThread("uploadAllLocalChanges() cannot be called from the main thread."); // Blocking only happens at the Java layer. To prevent deadlocking the underlying SyncSession we register // an async listener there and let it callback to the Java Session when done. This feels icky at best, but @@ -537,7 +502,7 @@ public void uploadAllLocalChanges() throws InterruptedException { * @return {@code true} if the data was uploaded before the timeout. {@code false} if the operation timed out or otherwise failed. */ public boolean uploadAllLocalChanges(long timeout, TimeUnit unit) throws InterruptedException { - checkIfNotOnMainThread("uploadAllLocalChanges() cannot be called from the main thread."); + Util.checkNotOnMainThread("uploadAllLocalChanges() cannot be called from the main thread."); checkTimeout(timeout, unit); // Blocking only happens at the Java layer. To prevent deadlocking the underlying SyncSession we register @@ -583,10 +548,6 @@ public synchronized void stop() { nativeStop(configuration.getPath()); } - void setResolvedRealmURI(URI resolvedRealmURI) { - this.resolvedRealmURI = resolvedRealmURI; - } - /** * This method should only be called when guarded by the {@link #waitForChangesMutex}. * It will block into all changes have been either uploaded or downloaded depending on the chosen direction. @@ -642,12 +603,6 @@ private boolean waitForChanges(int direction, long timeout, TimeUnit unit) throw return result; } - private void checkIfNotOnMainThread(String errorMessage) { - if (new AndroidCapabilities().isMainThread()) { - throw new IllegalStateException(errorMessage); - } - } - private void checkTimeout(long timeout, TimeUnit unit) { if (timeout <= 0) { throw new IllegalArgumentException("'timeout' must be > 0. It was: " + timeout); @@ -658,16 +613,10 @@ private void checkTimeout(long timeout, TimeUnit unit) { } } - private void checkNonNullListener(@Nullable Object listener) { - if (listener == null) { - throw new IllegalArgumentException("Non-null 'listener' required."); - } - } - /** * Interface used to report any session errors. * - * @see SyncManager#setDefaultSessionErrorHandler(ErrorHandler) + * @see RealmSync#setDefaultSessionErrorHandler(ErrorHandler) * @see SyncConfiguration.Builder#errorHandler(ErrorHandler) */ public interface ErrorHandler { @@ -719,184 +668,6 @@ public interface ErrorHandler { void onError(SyncSession session, ObjectServerError error); } -// // Return the access token for the Realm this Session is connected to. -// String getAccessToken(final RealmObjectServer authServer, String refreshToken) { -// // check first if there's a valid access_token we can return immediately -// if (getUser().isRealmAuthenticated(configuration)) { -// Token accessToken = getUser().getAccessToken(configuration); -// // start refreshing this token if a refresh is not going on -// if (!onGoingAccessTokenQuery.getAndSet(true)) { -// scheduleRefreshAccessToken(authServer, accessToken.expiresMs()); -// } -// return accessToken.value(); -// -// } else { -// // check and update if we received a new refresh_token -// if (!Util.isEmptyString(refreshToken)) { -// try { -// JSONObject refreshTokenJSON = new JSONObject(refreshToken); -// Token newRefreshToken = Token.from(refreshTokenJSON.getJSONObject("userToken")); -// if (newRefreshToken.hashCode() != getUser().getRefreshToken().hashCode()) { -// RealmLog.debug("Session[%s]: Access token updated", configuration.getPath()); -// getUser().setRefreshToken(newRefreshToken); -// } -// } catch (JSONException e) { -// RealmLog.error(e, "Session[%s]: Can not parse the refresh_token into a valid JSONObject: ", configuration.getPath()); -// } -// } -// if (!onGoingAccessTokenQuery.get() && NetworkStateReceiver.isOnline(SyncObjectServerFacade.getApplicationContext())) { -// authenticateRealm(authServer); -// } -// } -// return null; -// } -// -// // Authenticate by getting access tokens for the specific Realm -// private void authenticateRealm(final RealmObjectServer authServer) { -// if (networkRequest != null) { -// networkRequest.cancel(); -// } -// clearScheduledAccessTokenRefresh(); -// -// onGoingAccessTokenQuery.set(true); -// // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. -// Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { -// @Override -// protected AuthenticateResponse execute() { -// if (!isClosed && !Thread.currentThread().isInterrupted()) { -// return authServer.loginToRealm( -// getUser().getRefreshToken(), //refresh token in fact -// resolvedRealmURI, -// getUser().getAuthenticationUrl() -// ); -// } -// return null; -// } -// -// @Override -// protected void onSuccess(AuthenticateResponse response) { -// RealmLog.debug("Session[%s]: Access token acquired", configuration.getPath()); -// if (!isClosed && !Thread.currentThread().isInterrupted()) { -// URI realmUrl = configuration.getServerUrl(); -// getUser().addRealm(configuration, response.getAccessToken()); -// if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), realmUrl.toString())) { -// scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); -// -// } else { -// // token not applied, no refresh will be scheduled -// onGoingAccessTokenQuery.set(false); -// } -// } -// } -// -// @Override -// protected void onError(AuthenticateResponse response) { -// onGoingAccessTokenQuery.set(false); -// RealmLog.debug("Session[%s]: Failed to get access token (%s)", configuration.getPath(), -// response.getError().getErrorCode()); -// if (!isClosed -// && !Thread.currentThread().isInterrupted() -// // We might be interrupted while negotiating an access token with the Realm Object Server -// // This will result in a InterruptedIOException from OkHttp. We should ignore this as -// // well. -// && !(response.getError().getException() instanceof InterruptedIOException)) { -// errorHandler.onError(SyncSession.this, response.getError()); -// } -// } -// }); -// networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); -// } -// -// private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long expireDateInMs) { -// onGoingAccessTokenQuery.set(true); -// // calculate the delay time before which we should refresh the access_token, -// // we adjust to 10 second to proactively refresh the access_token before the session -// // hit the expire date on the token -// long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; -// if (refreshAfter < 0) { -// // Token already expired -// RealmLog.debug("Expires time already reached for the access token, refresh as soon as possible"); -// // we avoid refreshing directly to avoid an edge case where the client clock is ahead -// // of the server, causing all access_token received from the server to be always -// // expired, we will flood the server with refresh token requests then, so adding -// // a bit of delay is the best effort in this case. -// refreshAfter = REFRESH_MARGIN_DELAY; -// } -// -// RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); -// -// if (refreshTokenTask != null) { -// refreshTokenTask.cancel(); -// } -// -// ScheduledFuture task = REFRESH_TOKENS_EXECUTOR.schedule(new Runnable() { -// @Override -// public void run() { -// if (!isClosed && !Thread.currentThread().isInterrupted() && !refreshTokenTask.isCancelled()) { -// refreshAccessToken(authServer); -// } -// } -// }, refreshAfter, TimeUnit.MILLISECONDS); -// refreshTokenTask = new RealmAsyncTaskImpl(task, REFRESH_TOKENS_EXECUTOR); -// } -// -// // Authenticate by getting access tokens for the specific Realm -// private void refreshAccessToken(final RealmObjectServer authServer) { -// // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. -// clearScheduledAccessTokenRefresh(); -// -// Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { -// @Override -// protected AuthenticateResponse execute() { -// if (!isClosed && !Thread.currentThread().isInterrupted()) { -// return authServer.refreshUser(getUser().getRefreshToken(), resolvedRealmURI, getUser().getAuthenticationUrl()); -// } -// return null; -// } -// -// @Override -// protected void onSuccess(AuthenticateResponse response) { -// synchronized (SyncSession.this) { -// if (!isClosed && !Thread.currentThread().isInterrupted() && !refreshTokenNetworkRequest.isCancelled()) { -// RealmLog.debug("Access Token refreshed successfully, Sync URL: " + configuration.getServerUrl()); -// -// SyncWorker syncWorker = response.getSyncWorker(); -// if (syncWorker != null) { -// nativeSetUrlPrefix(configuration.getPath(), syncWorker.path()); -// } -// -// URI realmUrl = configuration.getServerUrl(); -// if (nativeRefreshAccessToken(configuration.getPath(), response.getAccessToken().value(), realmUrl.toString())) { -// // replace the user old access_token -// getUser().addRealm(configuration, response.getAccessToken()); -// // schedule the next refresh -// scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); -// } -// } -// } -// } -// -// @Override -// protected void onError(AuthenticateResponse response) { -// if (!isClosed && !Thread.currentThread().isInterrupted()) { -// onGoingAccessTokenQuery.set(false); -// RealmLog.error("Unrecoverable error, while refreshing the access Token (" + response.getError().toString() + ") reschedule will not happen"); -// } -// } -// }); -// refreshTokenNetworkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); -// } - - void clearScheduledAccessTokenRefresh() { - if (refreshTokenTask != null) { - refreshTokenTask.cancel(); - } - if (refreshTokenNetworkRequest != null) { - refreshTokenNetworkRequest.cancel(); - } - onGoingAccessTokenQuery.set(false); - } - // Wrapper class for handling the async operations of the underlying SyncSession calling // `async_wait_for_download_completion` or `async_wait_for_upload_completion` private static class WaitForSessionWrapper { @@ -948,14 +719,12 @@ public void throwExceptionIfNeeded() { private static native long nativeAddConnectionListener(String localRealmPath); private static native void nativeRemoveConnectionListener(long listenerId, String localRealmPath); - private static native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); + private native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); private static native void nativeRemoveProgressListener(String localRealmPath, long listenerToken); - private static native boolean nativeRefreshAccessToken(String localRealmPath, String accessToken, String realmUrl); private native boolean nativeWaitForDownloadCompletion(int callbackId, String localRealmPath); private native boolean nativeWaitForUploadCompletion(int callbackId, String localRealmPath); private static native byte nativeGetState(String localRealmPath); private static native byte nativeGetConnectionState(String localRealmPath); private static native void nativeStart(String localRealmPath); private static native void nativeStop(String localRealmPath); - private static native void nativeSetUrlPrefix(String localRealmPath, String urlPrefix); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java deleted file mode 100644 index b4cebcb24a..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ /dev/null @@ -1,1050 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; - -import javax.annotation.Nullable; - -import io.realm.internal.RealmNotifier; -import io.realm.internal.android.AndroidCapabilities; -import io.realm.internal.android.AndroidRealmNotifier; -import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.internal.objectserver.Token; -import io.realm.log.RealmLog; - -/** - * This class represents a user on the Realm Object Server. The credentials are provided by various 3rd party - * providers (Facebook, Google, etc.). - *

              - * A user can log in to the Realm Object Server, and if access is granted, it is possible to synchronize the local - * and the remote Realm. Moreover, synchronization is halted when the user is logged out. - *

              - * It is possible to persist a user. By retrieving a user, there is no need to log in to the 3rd party provider again. - * Persisting a user between sessions, the user's credentials are stored locally on the device, and should be treated - * as sensitive data. - */ -public class SyncUser { - private final String identity; - private Token refreshToken; - private final URL baseUrl; - private final URL authenticationUrl; - // maps all RealmConfiguration and accessToken, using this SyncUser. - private final Map realms = new HashMap(); - private SyncConfiguration defaultConfiguration; - - SyncUser(Token refreshToken, URL authenticationUrl) { - this.identity = refreshToken.identity(); - this.authenticationUrl = authenticationUrl; - try { - this.baseUrl = new URL(authenticationUrl.getProtocol(), authenticationUrl.getHost(), authenticationUrl.getPort(), ""); - } catch (MalformedURLException e) { - // Should never happen - throw new RuntimeException(e); - } - this.refreshToken = refreshToken; - } - - /** - * Returns the current user that is logged in and still valid. - * A user is invalidated when he/she logs out or the user's access token expires. - * - * @return current {@link SyncUser} that has logged in and is still valid. {@code null} if no user is logged in or the user has - * expired. - * @throws IllegalStateException if multiple users are logged in. - */ - public static SyncUser current() { - // FIXME -// SyncUser user = null; //SyncManager.getUserStore().getCurrent(); -// if (user != null && user.isValid()) { -// return user; -// } - return null; - } - - /** - * Returns all valid users known by this device. - * A user is invalidated when he/she logs out or the user's access token expires. - * - * @return a map from user identifier to user. It includes all known valid users. - */ - public static Map all() { - Collection storedUsers = new ArrayList<>(); // FIXME - Map map = new HashMap<>(); - for (SyncUser user : storedUsers) { - if (user.isValid()) { - map.put(user.getIdentity(), user); - } - } - return Collections.unmodifiableMap(map); - } - - /** - * Loads a user that has previously been serialized using {@link #toJson()}. - * - * @param user JSON string representing the user. - * @return the user object. - * @throws IllegalArgumentException if the JSON couldn't be converted to a valid {@link SyncUser} object. - */ - public static SyncUser fromJson(String user) { - try { - JSONObject obj = new JSONObject(user); - URL authUrl = new URL(obj.getString("authUrl")); - Token userToken = Token.from(obj.getJSONObject("userToken"));//TODO rename to refresh_token - return new SyncUser(userToken, authUrl); - } catch (JSONException e) { - throw new IllegalArgumentException("Could not parse user json: " + user, e); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("URL in JSON not valid: " + user, e); - } - } - - /** - * Logs in the user to the Realm Object Server. This is done synchronously, so calling this method on the Android - * UI thread will always crash. A logged in user is required to be able to create a {@link SyncConfiguration}. - * - * @param credentials credentials to use. - * @param authenticationUrl server that can authenticate against. - * @throws ObjectServerError if the login failed. - * @throws IllegalArgumentException if the URL is malformed. - */ -// public static SyncUser logIn(final SyncCredentials credentials, final String authenticationUrl) throws ObjectServerError { -// URL authUrl = getUrl(authenticationUrl); -// -// ObjectServerError error; -// try { -// AuthenticateResponse result; -// if (credentials.getIdentityProvider().equals(SyncCredentials.IdentityProvider.ACCESS_TOKEN)) { -// // Credentials using ACCESS_TOKEN as IdentityProvider are optimistically assumed to be valid already. -// // So log them in directly without contacting the authentication server. This is done by mirroring -// // the JSON response expected from the server. -// String userIdentifier = credentials.getUserIdentifier(); -// String token = (String) credentials.getUserInfo().get("_token"); -// boolean isAdmin = (Boolean) credentials.getUserInfo().get("_isAdmin"); -// result = AuthenticateResponse.createValidResponseWithUser(userIdentifier, token, isAdmin); -// } else { -// final RealmObjectServer server = SyncManager.getAuthServer(); -// result = server.loginUser(credentials, authUrl); -// } -// if (result.isValid()) { -// SyncUser user = new SyncUser(result.getRefreshToken(), authUrl); -// RealmLog.info("Succeeded authenticating user.\n%s", user); -// SyncManager.getUserStore().put(user); -// SyncManager.notifyUserLoggedIn(user); -// return user; -// } else { -// RealmLog.info("Failed authenticating user.\n%s", result.getError()); -// error = result.getError(); -// } -// } catch (Throwable e) { -// throw new ObjectServerError(ErrorCode.UNKNOWN, e); -// } -// throw error; -// } - - /** - * Converts the input URL to a Realm Authentication URL - * - * @param authenticationUrl user provided url string. - * - * @return normalized authentication url. - * @throws IllegalArgumentException if something was wrong with the URL. - */ - private static URL getUrl(String authenticationUrl) { - try { - URL authUrl = new URL(authenticationUrl); - // If no path segment is provided append `/auth` which is the standard location. - if (authUrl.getPath().equals("")) { - authUrl = new URL(authUrl.toString() + "/auth"); - } - return authUrl; - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Invalid URL " + authenticationUrl + ".", e); - } - } - - /** - * Logs in the user to the Realm Object Server. A logged in user is required to be able to create a - * {@link SyncConfiguration}. - * - * @param credentials credentials to use. - * @param authenticationUrl server that the user is authenticated against. - * @param callback callback when login has completed or failed. The callback will always happen on the same thread - * as this this method is called on. - * @return representation of the async task that can be used to cancel it if needed. - * @throws IllegalArgumentException if not on a Looper thread. - */ -// public static RealmAsyncTask logInAsync(final SyncCredentials credentials, final String authenticationUrl, final Callback callback) { -// checkLooperThread("Asynchronous login is only possible from looper threads."); -// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { -// @Override -// public SyncUser run() throws ObjectServerError { -// return logIn(credentials, authenticationUrl); -// } -// }.start(); -// } - - /** - * Opening a synchronized Realm requires a {@link SyncConfiguration}. This method creates a - * {@link SyncConfiguration.Builder} that can be used to create it by calling {@link SyncConfiguration.Builder#build()}. - *

              - * A synchronized Realm is identified by an unique URI. In the URI, {@code /~/} can be used as a placeholder for - * a user ID in case the Realm should only be available to one user e.g., {@code "realm://objectserver.realm.io/~/default"}. - *

              - * The URL cannot end with {@code .realm}, {@code .realm.lock} or {@code .realm.management}. - *

              - * The {@code /~/} will automatically be replaced with the user ID when creating the {@link SyncConfiguration}. - *

              - * Moreover, the URI defines the local location on disk. The location of a synchronized Realm file is - * {@code /data/data//files/realm-object-server//}, but this behavior - * can be overwritten using {@link SyncConfiguration.Builder#name(String)} and {@link SyncConfiguration.Builder#directory(File)}. - *

              - * Many Android devices are using FAT32 file systems. FAT32 file systems have a limitation that - * file names cannot be longer than 255 characters. Moreover, the entire URI should not exceed 256 characters. - * If the file name and underlying path are too long to handle for FAT32, a shorter unique name will be generated. - * See also @{link https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx}. - * - * @param uri URI identifying the Realm. If only a path like {@code /~/default} is given, the configuration will - * assume the file is located on the same server returned by {@link #getAuthenticationUrl()}. - * - * @throws IllegalStateException if the user isn't valid. See {@link #isValid()}. - */ - public SyncConfiguration.Builder createConfiguration(String uri) { - if (!isValid()) { - throw new IllegalStateException("Configurations can only be created from valid users"); - } - return new SyncConfiguration.Builder(Realm.applicationContext, this, uri); - } - - /** - * Returns the default configuration for this user. The default configuration points to the - * default Realm on the server the user authenticated against. - * - * @return the default configuration for this user. - * @throws IllegalStateException if the user isn't valid. See {@link #isValid()}. - */ - public SyncConfiguration getDefaultConfiguration() { - if (!isValid()) { - throw new IllegalStateException("The default configuration can only be created for users that are logged in."); - } - if (defaultConfiguration == null) { - defaultConfiguration = new SyncConfiguration.Builder(Realm.applicationContext, this, createUrl(this)) - .build(); - } - return defaultConfiguration; - } - - // Infer the URL to the default Realm based on the server used to login the user - private static String createUrl(SyncUser user) { - URL url = user.getAuthenticationUrl(); - String protocol = url.getProtocol(); - String host = url.getHost(); - int port = url.getPort(); - if (port != -1) { // port set - host += ":" + port; - } - - if (protocol.equalsIgnoreCase("https")) { - protocol = "realms"; - } else { - protocol = "realm"; - } - - return protocol + "://" + host + "/default"; - } - - /** - * Log a user out, destroying their server state, unregistering them from the SDK, and removing - * any synced Realms associated with them, from on-disk storage on next app launch (or directly - * if all instances are closed). - * If the user is already logged out or in an error state, this method does nothing. - * - * This method should be called whenever the application is committed to not using a user again - * unless they are recreated. Failing to call this method may result in unused files and metadata - * needlessly taking up space. - * - * Once the Object Server has confirmed the logout any registered {@link AuthenticationListener} - * will be notified and user credentials will be deleted from this device. - */ -// /* FIXME: Add this back to the javadoc when enable SyncConfiguration.Builder#deleteRealmOnLogout() -//

              -// Any Realms owned by the user will be deleted, when the application restart. -// */ - // this is a fire and forget, end user should not worry about the state of the async query - @SuppressWarnings("FutureReturnValueIgnored") -// public void logOut() { -// // Acquire lock to prevent users creating new instances -// synchronized (Realm.class) { -// if (!SyncManager.getUserStore().isActive(identity, authenticationUrl.toString())) { -// return; // Already logged out status -// } -// -// // Mark the user as logged out in the ObjectStore -// SyncManager.getUserStore().remove(identity, authenticationUrl.toString()); -// -// // invalidate all pending refresh_token queries -// for (SyncConfiguration syncConfiguration : realms.keySet()) { -// try { -// SyncSession session = SyncManager.getSession(syncConfiguration); -// session.clearScheduledAccessTokenRefresh(); -// } catch (IllegalStateException e) { -// if (!e.getMessage().contains("No SyncSession found")) { -// throw e; -// }// else no session, either the Realm was not opened or session was removed. -// } -// } -// -// // Remove all local tokens, preventing further connections. -// // don't remove identity as this SyncUser might be re-activated and we need -// // to avoid throwing a mismatch SyncConfiguration in RealmCache if we have -// // the similar SyncConfiguration using the same identity, but with different (new) -// // refresh-token. -// realms.clear(); -// -// // Finally revoke server token. The local user is logged out in any case. -// final RealmObjectServer server = SyncManager.getAuthServer(); -// // don't reference directly the refreshToken inside the revoke request -// // as it may revoke the newly acquired refresh_token -// final Token refreshTokenToBeRevoked = refreshToken; -// -// ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; -// networkPoolExecutor.submit(new ExponentialBackoffTask(3) { -// -// @Override -// protected LogoutResponse execute() { -// return server.logout(refreshTokenToBeRevoked, getAuthenticationUrl()); -// } -// -// @Override -// protected void onSuccess(LogoutResponse response) { -// SyncManager.notifyUserLoggedOut(SyncUser.this); -// } -// -// @Override -// protected void onError(LogoutResponse response) { -// RealmLog.error("Failed to log user out.\n" + response.getError().toString()); -// } -// }); -// } -// } - - /** - * Changes this user's password. This is done synchronously and involves the network, so calling this method on the - * Android UI thread will always crash. - *

              - * WARNING: Changing a user's password through an authentication server that doesn't use HTTPS is a major - * security flaw, and should only be done while testing. - * - * @param newPassword the user's new password. - * @throws ObjectServerError if the password could not be changed. - */ -// public void changePassword(final String newPassword) throws ObjectServerError { -// //noinspection ConstantConditions -// if (newPassword == null) { -// throw new IllegalArgumentException("Not-null 'newPassword' required."); -// } -// RealmObjectServer authServer = SyncManager.getAuthServer(); -// ChangePasswordResponse response = authServer.changePassword(refreshToken, newPassword, getAuthenticationUrl()); -// if (!response.isValid()) { -// throw response.getError(); -// } -// } - - /** - * Changes another user's password. This is done synchronously and involves the network, so calling this method on the - * Android UI thread will always crash. - *

              - * This user needs admin privilege in order to change someone else's password. - *

              - * WARNING: Changing a user's password through an authentication server that doesn't use HTTPS is a major - * security flaw, and should only be done while testing. - * - * @param userId identity ({@link #getIdentity()}) of the user we want to change the password for. - * @param newPassword the user's new password. - * @throws ObjectServerError if the password could not be changed. - */ -// public void changePassword(final String userId, final String newPassword) throws ObjectServerError { -// //noinspection ConstantConditions -// if (newPassword == null) { -// throw new IllegalArgumentException("Not-null 'newPassword' required."); -// } -// -// if (Util.isEmptyString(userId)) { -// throw new IllegalArgumentException("None empty 'userId' required."); -// } -// -// if (userId.equals(getIdentity())) { // user want's to change his/her own password -// changePassword(newPassword); -// -// } else { -// if (!isAdmin()) { -// throw new IllegalStateException("User need to be admin in order to change another user's password."); -// } -// -// RealmObjectServer authServer = SyncManager.getAuthServer(); -// ChangePasswordResponse response = authServer.changePassword(refreshToken, userId, newPassword, getAuthenticationUrl()); -// if (!response.isValid()) { -// throw response.getError(); -// } -// } -// } - - /** - * Changes this user's password asynchronously. - *

              - * WARNING: Changing a users password using an authentication server that doesn't use HTTPS is a major - * security flaw, and should only be done while testing. - * - * @param newPassword the user's new password. - * @param callback callback when login has completed or failed. The callback will always happen on the same thread - * as this method is called on. - * @return representation of the async task that can be used to cancel it if needed. - * @throws IllegalArgumentException if not on a Looper thread. - */ -// public RealmAsyncTask changePasswordAsync(final String newPassword, final Callback callback) { -// checkLooperThread("Asynchronous changing password is only possible from looper threads."); -// //noinspection ConstantConditions -// if (callback == null) { -// throw new IllegalArgumentException("Non-null 'callback' required."); -// } -// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { -// @Override -// public SyncUser run() { -// changePassword(newPassword); -// return SyncUser.this; -// } -// }.start(); -// } - - /** - * Changes another user's password asynchronously. - *

              - * This user needs admin privilege in order to change someone else's password. - * - * WARNING: Changing a users password using an authentication server that doesn't use HTTPS is a major - * security flaw, and should only be done while testing. - * - * @param userId identity ({@link #getIdentity()}) of the user we want to change the password for. - * @param newPassword the user's new password. - * @param callback callback when login has completed or failed. The callback will always happen on the same thread - * as this method is called on. - * @return representation of the async task that can be used to cancel it if needed. - * @throws IllegalArgumentException if not on a Looper thread. - */ -// public RealmAsyncTask changePasswordAsync(final String userId, final String newPassword, final Callback callback) { -// checkLooperThread("Asynchronous changing password is only possible from looper threads."); -// //noinspection ConstantConditions -// if (callback == null) { -// throw new IllegalArgumentException("Non-null 'callback' required."); -// } -// -// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { -// @Override -// public SyncUser run() { -// changePassword(userId, newPassword); -// return SyncUser.this; -// } -// }.start(); -// } - - - /** - * Request a password reset email to be sent to a user's email. - * This will not fail, even if the email doesn't belong to a Realm Object Server user. - *

              - * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} - * provider, and passed a valid email address as a username. - * - * @param email email that corresponds to the user's username. - * @param authenticationUrl the url used to authenticate the user. - * @throws IllegalStateException if this method is called on the UI thread. - * @throws IllegalArgumentException if no email or authenticationUrl was provided. - * @throws ObjectServerError if an error happened on the server. - */ -// public static void requestPasswordReset(String email, String authenticationUrl) throws ObjectServerError { -// if (Util.isEmptyString(email)) { -// throw new IllegalArgumentException("Not-null 'email' required."); -// } -// URL authUrl = getUrl(authenticationUrl); -// RealmObjectServer authServer = SyncManager.getAuthServer(); -// UpdateAccountResponse response = authServer.requestPasswordReset(email, authUrl); -// if (!response.isValid()) { -// throw response.getError(); -// } -// } - - /** - * Request a password reset email to be sent to a user's email. - * This will not fail, even if the email doesn't belong to a Realm Object Server user. - *

              - * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} - * provider, and passed a valid email address as a username. - * - * @param email email that corresponds to the user's username. - * @param authenticationUrl the url used to authenticate the user. - * @param callback callback when the request has completed or failed. The callback will always happen on the same thread - * as this method is called on. - * @return representation of the async task that can be used to cancel it if needed. - * @throws IllegalStateException if this method is called on a non-looper thread. - * @throws IllegalArgumentException if no email or authenticationUrl was provided. - */ -// public static RealmAsyncTask requestPasswordResetAsync(final String email, final String authenticationUrl, final Callback callback) { -// checkLooperThread("Asynchronous requesting a password reset is only possible from looper threads."); -// //noinspection ConstantConditions -// if (callback == null) { -// throw new IllegalArgumentException("Non-null 'callback' required."); -// } -// -// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { -// @Override -// public Void run() { -// requestPasswordReset(email, authenticationUrl); -// return null; -// } -// }.start(); -// } - - /** - * Complete the password reset flow by using the reset token sent to the user's email as a one-time authorization - * token to change the password. - *

              - * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} - * provider, and passed a valid email address as a username. - *

              - * By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where - * they can enter their new password. If you wish to provide a native UX, you may wish to modify the password - * authentication provider to use a custom URL with deep linking, so you can open the app, extract the token, and - * navigate to a view that allows to change the password within the app. - * - * @param resetToken the token that was sent to the user's email address. - * @param newPassword the user's new password. - * @param authenticationUrl the url used to authenticate the user. - * @throws IllegalStateException if this method is called on the UI thread. - * @throws IllegalArgumentException if no {@code token} or {@code newPassword} was provided. - * @throws ObjectServerError if an error happened on the server. - */ -// public static void completePasswordReset(String resetToken, String newPassword, String authenticationUrl) { -// if (Util.isEmptyString(resetToken)) { -// throw new IllegalArgumentException("Not-null 'token' required."); -// } -// if (Util.isEmptyString(newPassword)) { -// throw new IllegalArgumentException("Not-null 'newPassword' required."); -// } -// URL authUrl = getUrl(authenticationUrl); -// RealmObjectServer authServer = SyncManager.getAuthServer(); -// UpdateAccountResponse response = authServer.completePasswordReset(resetToken, newPassword, authUrl); -// if (!response.isValid()) { -// throw response.getError(); -// } -// } - - /** - * Complete the password reset flow by using the reset token sent to the user's email as a one-time authorization - * token to change the password. - *

              - * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} - * provider, and passed a valid email address as a username. - *

              - * By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where - * they can enter their new password. If you wish to provide a native UX, you may wish to modify the password - * authentication provider to use a custom URL with deep linking, so you can open the app, extract the token, and - * navigate to a view that allows to change the password within the app. - * - * @param resetToken the token that was sent to the user's email address. - * @param newPassword the user's new password. - * @param authenticationUrl the url used to authenticate the user. - * @param callback callback when the server has accepted the new password or failed. The callback will always happen on the same thread - * as this method is called on. - * @return representation of the async task that can be used to cancel it if needed. - * @throws IllegalStateException if this method is called on a non-looper thread. - * @throws IllegalArgumentException if no {@code token} or {@code newPassword} was provided. - */ -// public static RealmAsyncTask completePasswordResetAsync(final String resetToken, -// final String newPassword, -// final String authenticationUrl, -// final Callback callback) throws ObjectServerError { -// checkLooperThread("Asynchronously completing a password reset is only possible from looper threads."); -// //noinspection ConstantConditions -// if (callback == null) { -// throw new IllegalArgumentException("Non-null 'callback' required."); -// } -// -// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { -// @Override -// public Void run() { -// completePasswordReset(resetToken, newPassword, authenticationUrl); -// return null; -// } -// }.start(); -// } - - /** - * Request an email confirmation email to be sent to a user's email. - * This will not fail, even if the email doesn't belong to a Realm Object Server user. - *

              - * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} - * provider, and passed a valid email address as a username. - * - * @param email the email that corresponds to the user's username. - * @param authenticationUrl the url used to authenticate the user. - * @throws IllegalStateException if this method is called on the UI thread. - * @throws IllegalArgumentException if no {@code email} was provided. - * @throws ObjectServerError if an error happened on the server. - */ -// public static void requestEmailConfirmation(String email, String authenticationUrl) throws ObjectServerError { -// if (Util.isEmptyString(email)) { -// throw new IllegalArgumentException("Not-null 'email' required."); -// } -// URL authUrl = getUrl(authenticationUrl); -// RealmObjectServer authServer = SyncManager.getAuthServer(); -// UpdateAccountResponse response = authServer.requestEmailConfirmation(email, authUrl); -// if (!response.isValid()) { -// throw response.getError(); -// } -// } - - /** - * Request an email confirmation email to be sent to a user's email. - * This will not fail, even if the email doesn't belong to a Realm Object Server user. - *

              - * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} - * provider, and passed a valid email address as a username. - * - * @param email the email that corresponds to the user's username. - * @param authenticationUrl the url used to authenticate the user. - * @param callback callback when the request has completed or failed. The callback will always happen on the same thread - * as this method is called on. - * @return representation of the async task that can be used to cancel it if needed. - * @throws IllegalStateException if this method is called on a non-looper thread. - * @throws IllegalArgumentException if no {@code email} was provided. - */ -// public static RealmAsyncTask requestEmailConfirmationAsync(final String email, final String authenticationUrl, final Callback callback) { -// checkLooperThread("Asynchronously requesting an email confirmation is only possible from looper threads."); -// //noinspection ConstantConditions -// if (callback == null) { -// throw new IllegalArgumentException("Non-null 'callback' required."); -// } -// -// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { -// @Override -// public Void run() { -// requestEmailConfirmation(email, authenticationUrl); -// return null; -// } -// }.start(); -// } - - /** - * Complete the email confirmation flow by using the confirmation token sent to the user's email as a one-time - * authorization token to confirm their email. - *

              - * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} - * provider, and passed a valid email address as a username. - *

              - * By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where - * they can enter their new password. If you wish to provide a native UX, you may wish to modify the password - * authentication provider to use a custom URL with deep linking, so you can open the app, extract the token, - * and navigate to a view that allows to confirm the email within the app. - * - * @param confirmationToken the token that was sent to the user's email address. - * @param authenticationUrl the url used to authenticate the user. - * @throws IllegalStateException if this method is called on the UI thread. - * @throws IllegalArgumentException if no {@code confirmationToken} was provided. - * @throws ObjectServerError if an error happened on the server. - */ -// public static void confirmEmail(String confirmationToken, String authenticationUrl) throws ObjectServerError { -// if (Util.isEmptyString(confirmationToken)) { -// throw new IllegalArgumentException("Not-null 'confirmationToken' required."); -// } -// URL authUrl = getUrl(authenticationUrl); -// RealmObjectServer authServer = SyncManager.getAuthServer(); -// UpdateAccountResponse response = authServer.confirmEmail(confirmationToken, authUrl); -// if (!response.isValid()) { -// throw response.getError(); -// } -// } - - /** - * Complete the email confirmation flow by using the confirmation token sent to the user's email as a one-time - * authorization token to confirm their email. This functionalit - *

              - * This can only be used for users who authenticated with the {@link SyncCredentials.IdentityProvider#USERNAME_PASSWORD} - * provider, and passed a valid email address as a username. - *

              - * By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where - * they can enter their new password. If you wish to provide a native UX, you may wish to modify the password - * authentication provider to use a custom URL with deep linking, so you can open the app, extract the token, - * and navigate to a view that allows to confirm the email within the app. - * - * @param confirmationToken the token that was sent to the user's email address. - * @param authenticationUrl the url used to authenticate the user. - * @param callback callback when the server has confirmed the email or failed. The callback will always happen on the same thread - * as this method is called on. - * @return representation of the async task that can be used to cancel it if needed. - * @throws IllegalStateException if this method is called on a non-looper thread. - * @throws IllegalArgumentException if no {@code confirmationToken} was provided. - */ -// public static RealmAsyncTask confirmEmailAsync(final String confirmationToken, -// final String authenticationUrl, -// final Callback callback) { -// checkLooperThread("Asynchronously confirming an email is only possible from looper threads."); -// //noinspection ConstantConditions -// if (callback == null) { -// throw new IllegalArgumentException("Non-null 'callback' required."); -// } -// -// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { -// @Override -// public Void run() { -// confirmEmail(confirmationToken, authenticationUrl); -// return null; -// } -// }.start(); -// } - - /** - * Given a Realm Object Server authentication provider and a provider identifier for a user (for example, a username), look up and return user information for that user. - * - * @param providerUserIdentity The username or identity of the user as issued by the authentication provider. - * In most cases this is different from the Realm Object Server-issued identity. - * @param provider The authentication provider {@link io.realm.SyncCredentials.IdentityProvider} that manages the user whose information is desired. - * - * @return {@code SyncUser} associated with the given identity provider and providerId, or {@code null} in case - * of an {@code invalid} provider or {@code providerId}. - * @throws IllegalStateException if this method is called on the UI thread. - * @throws IllegalArgumentException if no {@code providerUserIdentity} or {@code provider} string was provided. - * @throws ObjectServerError if an error happened on the server. - */ -// public SyncUserInfo retrieveInfoForUser(final String providerUserIdentity, final String provider) throws ObjectServerError { -// if (Util.isEmptyString(providerUserIdentity)) { -// throw new IllegalArgumentException("'providerUserIdentity' cannot be empty."); -// } -// -// if (Util.isEmptyString(provider)) { -// throw new IllegalArgumentException("'provider' cannot be empty."); -// } -// -// if (!isAdmin()) { -// throw new IllegalArgumentException("SyncUser needs to be admin in order to lookup other users ID."); -// } -// -// RealmObjectServer authServer = SyncManager.getAuthServer(); -// LookupUserIdResponse response = authServer.retrieveUser(refreshToken, provider, providerUserIdentity, getAuthenticationUrl()); -// if (!response.isValid()) { -// if (response.getError().getErrorCode() == ErrorCode.UNKNOWN_ACCOUNT) { -// return null; -// } else { -// throw response.getError(); -// } -// } else { -// return SyncUserInfo.fromLookupUserIdResponse(response); -// } -// } - - /** - * Given a Realm Object Server authentication provider and a provider identifier for a user (for example, a username), asynchronously look up and return user information for that user. - * - * @param providerUserIdentity The username or identity of the user as issued by the authentication provider. - * In most cases this is different from the Realm Object Server-issued identity. - * @param provider The authentication provider {@link io.realm.SyncCredentials.IdentityProvider} that manages the user whose information is desired. - * @return representation of the async task that can be used to cancel it if needed. - * @param callback callback when the lookup has completed or failed. The callback will always happen on the same thread - * as this method is called on. - * @return representation of the async task that can be used to cancel it if needed. - */ -// public RealmAsyncTask retrieveInfoForUserAsync(final String providerUserIdentity, final String provider, final Callback callback) { -// checkLooperThread("Asynchronously retrieving user is only possible from looper threads."); -// //noinspection ConstantConditions -// if (callback == null) { -// throw new IllegalArgumentException("Non-null 'callback' required."); -// } -// -// return new Request(SyncManager.NETWORK_POOL_EXECUTOR, callback) { -// @Override -// public SyncUserInfo run() throws ObjectServerError { -// return retrieveInfoForUser(providerUserIdentity, provider); -// } -// }.start(); -// } - - private static void checkLooperThread(String errorMessage) { - AndroidCapabilities capabilities = new AndroidCapabilities(); - capabilities.checkCanDeliverNotification(errorMessage); - } - - /** - * Returns a JSON token representing this user. - *

              - * Possession of this JSON token can potentially grant access to data stored on the Realm Object Server, so it - * should be treated as sensitive data. - * - * @return JSON string representing this user. It can be converted back into a real user object using - * {@link #fromJson(String)}. - * @see #fromJson(String) - */ - public String toJson() { - JSONObject obj = new JSONObject(); - try { - obj.put("authUrl", authenticationUrl); - obj.put("userToken", refreshToken.toJson()); - return obj.toString(); - } catch (JSONException e) { - throw new RuntimeException("Could not convert SyncUser to JSON", e); - } - } - - /** - * Returns {@code true} if the user is logged into the Realm Object Server. If this method returns {@code true} it - * implies that the user has valid credentials that have not expired. - *

              - * The user might still have been logged out by the Realm Object Server which will not be detected before the - * user tries to actively synchronize a Realm. If a logged out user tries to synchronize a Realm, an error will be - * reported to the {@link SyncSession.ErrorHandler} defined by - * {@link SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler)}. - * - * @return {@code true} if the User is logged into the Realm Object Server, {@code false} otherwise. - */ - public boolean isValid() { - // FIXME - /* && SyncManager.getUserStore().isActive(identity, authenticationUrl.toString()*/ - return refreshToken != null && refreshToken.expiresMs() > System.currentTimeMillis(); - } - - /** - * Returns {@code true} if this user is an administrator on the Realm Object Server, {@code false} otherwise. - *

              - * Administrators can access all Realms on the server as well as change the permissions of the Realms. - * - * @return {@code true} if the user is an administrator on the Realm Object Server, {@code false} otherwise. - */ - public boolean isAdmin() { - return refreshToken.isAdmin(); - } - - /** - * Returns the identity of this user on the Realm Object Server. The identity is a guaranteed to be unique - * among all users on the Realm Object Server. - * - * @return identity of the user on the Realm Object Server. If the user has logged out or the login has expired - * {@code null} is returned. - */ - public String getIdentity() { - return identity; - } - - /** - * Returns this user's refresh token. This is the users credential for accessing the Realm Object Server and should - * be treated as sensitive data. - * - * @return the user's refresh token. If this user has logged out or the login has expired {@code null} is returned. - */ - public Token getRefreshToken() { - return refreshToken; - } - - void setRefreshToken(Token refreshToken) { - this.refreshToken = refreshToken; - } - - /** - * Returns all the valid sessions belonging to the user. - * - * @return the all valid sessions belong to the user. - */ - public List allSessions() { - return SyncManager.getAllSessions(this); - } - - /** - * Checks if the user has access to the given Realm. Being authenticated means that the - * user is known by the Realm Object Server and have been granted access to the given Realm. - * - * Authenticating will happen automatically as part of opening a Realm. - */ - boolean isRealmAuthenticated(SyncConfiguration configuration) { - Token token = realms.get(configuration); - return token != null && token.expiresMs() > System.currentTimeMillis(); - } - - public Token getAccessToken(SyncConfiguration configuration) { - return realms.get(configuration); - } - - void addRealm(SyncConfiguration syncConfiguration, Token accessToken) { - realms.put(syncConfiguration, accessToken); - } - /** - * Returns the {@link URL} where this user was authenticated. - * - * @return {@link URL} where the user was authenticated. - */ - public URL getAuthenticationUrl() { - return authenticationUrl; - } - - // Creates the URL to the permission Realm based on the authentication URL. - private static String getManagementRealmUrl(URL authUrl) { - String scheme = "realm"; - if (authUrl.getProtocol().equalsIgnoreCase("https")) { - scheme = "realms"; - } - try { - return new URI(scheme, authUrl.getUserInfo(), authUrl.getHost(), authUrl.getPort(), - "/~/__management", null, null).toString(); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Could not create URL to the management Realm", e); - } - } - - // what defines a user is it's identity(Token) and authURL (as required by the constructor) - // - // not the list of Realms it's managing, furthermore, trying to include the `realms` in the `hashCode` will - // end in a StackOverFlow, since we need to calculate the `hashCode` of the SyncConfiguration which itself - // contains a reference to the SyncUser. - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - SyncUser syncUser = (SyncUser) o; - - if (!identity.equals(syncUser.identity)) return false; - return authenticationUrl.toExternalForm().equals(syncUser.authenticationUrl.toExternalForm()); - } - - @Override - public int hashCode() { - int result = identity.hashCode(); - result = 31 * result + authenticationUrl.toExternalForm().hashCode(); - return result; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("{"); - sb.append("UserId: ").append(identity); - sb.append(", AuthUrl: ").append(getAuthenticationUrl()); - sb.append("}"); - return sb.toString(); - } - - private void checkCallbackNotNull(Callback callback) { - //noinspection ConstantConditions - if (callback == null) { - throw new IllegalArgumentException("Non-null 'callback' required."); - } - } - - // Class wrapping requests made against the auth server. Is also responsible for calling with success/error on the - // correct thread. - private static abstract class Request { - @Nullable - private final Callback callback; - private final RealmNotifier handler; - private final ThreadPoolExecutor networkPoolExecutor; - - Request(ThreadPoolExecutor networkPoolExecutor, @Nullable Callback callback) { - this.callback = callback; - this.handler = new AndroidRealmNotifier(null, new AndroidCapabilities()); - this.networkPoolExecutor = networkPoolExecutor; - } - - // Implements the request. Return the current sync user if the request succeeded. Otherwise throw an error. - public abstract T run() throws ObjectServerError; - - // Start the request - public RealmAsyncTask start() { - Future authenticateRequest = networkPoolExecutor.submit(new Runnable() { - @Override - public void run() { - try { - postSuccess(Request.this.run()); - } catch (ObjectServerError e) { - postError(e); - } catch (Throwable e) { - postError(new ObjectServerError(ErrorCode.UNKNOWN, "Unexpected error", e)); - } - } - }); - return new RealmAsyncTaskImpl(authenticateRequest, networkPoolExecutor); - } - - private void postError(final ObjectServerError error) { - boolean errorHandled = false; - if (callback != null) { - Runnable action = new Runnable() { - @Override - public void run() { - callback.onError(error); - } - }; - errorHandled = handler.post(action); - } - - if (!errorHandled) { - RealmLog.error(error, "An error was thrown, but could not be handled."); - } - } - - private void postSuccess(final T result) { - if (callback != null) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onSuccess(result); - } - }); - } - } - } - - /** - * Callback for async methods available to the {@link SyncUser}. - * - * @param Type returned if the request was a success. - */ - public interface Callback { - /** - * The request was a success. - * @param t The object representing the successful request. See each method for details. - */ - void onSuccess(T t); - - /** - * The request failed for some reason, either because there was a network error or the Realm - * Object Server returned an error. - * - * @param error the error that was detected. - */ - void onError(ObjectServerError error); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java deleted file mode 100644 index 6706a196b6..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUserInfo.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import java.util.Collections; -import java.util.Map; - -/** - * POJO representing information about a user that was retrieved from a user lookup call. - * @see SyncUser#retrieveInfoForUser(String, String) - */ - -public class SyncUserInfo { - private final String identity; - private final boolean isAdmin; - private final Map metadata; - private final Map accounts; - - private SyncUserInfo(String identity, boolean isAdmin, Map metadata, Map accounts) { - this.identity = identity; - this.isAdmin = isAdmin; - this.metadata = Collections.unmodifiableMap(metadata); - this.accounts = Collections.unmodifiableMap(accounts); - } - -// static SyncUserInfo fromLookupUserIdResponse(LookupUserIdResponse response) { -// return new SyncUserInfo(response.getUserId(), response.isAdmin(), response.getMetadata(), response.getAccounts()); -// } - - /** - * @return the identity issued to this user by the Realm Object Server. - */ - public String getIdentity() { - return identity; - } - - /** - * @return whether the user is flagged on the Realm Object Server as an administrator. - */ - public boolean isAdmin() { - return isAdmin; - } - - /** - * Returns the metadata associated with the user. The metadata is a generic key/value map with - * the only restriction that a key must be non-empty. - * - * @return the metadata associated with this user. - */ - public Map getMetadata() { - return metadata; - } - - /** - * Returns the accounts associated with this user. The map returned is a map of {@link SyncCredentials.IdentityProvider} - * and the providerId used in that provider. - *

              - * Example being {@code ("password", "my@email.com") }, if the user created an account using the standard account creation - * supported by the Realm Object Server. - *

              - * A user can have multiple accounts associated with it. - * - * @return the accounts associated with the user. - */ - public Map getAccounts() { return accounts; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - SyncUserInfo that = (SyncUserInfo) o; - - if (isAdmin != that.isAdmin) return false; - if (!identity.equals(that.identity)) return false; - return metadata.equals(that.metadata); - } - - @Override - public int hashCode() { - int result = identity.hashCode(); - result = 31 * result + (isAdmin ? 1 : 0); - result = 31 * result + metadata.hashCode(); - return result; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index bc647abee4..d67fb08296 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -21,15 +21,18 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; +import org.bson.BsonValue; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.TimeUnit; +import io.realm.RealmApp; import io.realm.RealmConfiguration; +import io.realm.RealmUser; import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncUser; +import io.realm.RealmSync; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; import io.realm.internal.android.AndroidCapabilities; @@ -48,27 +51,8 @@ public class SyncObjectServerFacade extends ObjectServerFacade { @Override public void initialize(Context context, String userAgent) { - // Trying to keep things out the public API is no fun :/ - // Just use reflection on init. It is a one-time method call so should be acceptable. - //noinspection TryWithIdenticalCatches - try { - // FIXME: Reflection can be avoided by moving some functions of SyncManager and ObjectServer out of public - Class syncManager = Class.forName("io.realm.ObjectServer"); - Method method = syncManager.getDeclaredMethod("init", Context.class, String.class); - method.setAccessible(true); - method.invoke(null, context, userAgent); - } catch (NoSuchMethodException e) { - throw new RealmException("Could not initialize the Realm Object Server", e); - } catch (InvocationTargetException e) { - throw new RealmException("Could not initialize the Realm Object Server", e); - } catch (IllegalAccessException e) { - throw new RealmException("Could not initialize the Realm Object Server", e); - } catch (ClassNotFoundException e) { - throw new RealmException("Could not initialize the Realm Object Server", e); - } if (applicationContext == null) { applicationContext = context; - applicationContext.registerReceiver(new NetworkStateReceiver(), new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); } @@ -90,32 +74,50 @@ public void realmClosed(RealmConfiguration configuration) { public Object[] getSyncConfigurationOptions(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; - SyncUser user = syncConfig.getUser(); + RealmUser user = syncConfig.getUser(); + RealmApp app = user.getApp(); String rosServerUrl = syncConfig.getServerUrl().toString(); - String rosUserIdentity = user.getIdentity(); - String syncRealmAuthUrl = user.getAuthenticationUrl().toString(); - String syncUserRefreshToken = user.getRefreshToken().toJson().toString(); - String syncUserAccessToken = user.getAccessToken(((SyncConfiguration) config)).toJson().toString(); + String rosUserIdentity = user.getId(); + String syncRealmAuthUrl = user.getApp().getConfiguration().getBaseUrl().toString(); + String syncUserRefreshToken = user.getRefreshToken(); + String syncUserAccessToken = user.getAccessToken(); byte sessionStopPolicy = syncConfig.getSessionStopPolicy().getNativeValue(); String urlPrefix = syncConfig.getUrlPrefix(); - String customAuthorizationHeaderName = SyncManager.getAuthorizationHeaderName(syncConfig.getServerUrl()); - Map customHeaders = SyncManager.getCustomRequestHeaders(syncConfig.getServerUrl()); - return new Object[]{ - rosUserIdentity, - rosServerUrl, - syncRealmAuthUrl, - syncUserRefreshToken, - syncUserAccessToken, - syncConfig.syncClientValidateSsl(), - syncConfig.getServerCertificateFilePath(), - sessionStopPolicy, - urlPrefix, - customAuthorizationHeaderName, - customHeaders, - syncConfig.getClientResyncMode().getNativeValue() - }; + String customAuthorizationHeaderName = app.getConfiguration().getAuthorizationHeaderName(); + Map customHeaders = app.getConfiguration().getCustomRequestHeaders(); + + // Temporary work-around for serializing supported bson values + BsonValue val = syncConfig.getPartitionValue(); + String partitionValue = null; + if (val.isString()) { + partitionValue = "\"" + val.asString().getValue() + "\""; + } else if (val.isInt32()) { + partitionValue = "{ \"$bsonInt\" : " + val.asInt32().intValue() + " }"; + } else if (val.isInt64()) { + partitionValue = "{ \"$bsonLong\" : " + val.asInt64().longValue() + " }"; + } else if (val.isObjectId()) { + partitionValue = "{ \"$oid\" : " + val.asObjectId().toString() + " }"; + } else { + throw new IllegalArgumentException("Unsupported type: " + val); + } + Object[] configObj = new Object[SYNC_CONFIG_OPTIONS]; + configObj[0] = rosUserIdentity; + configObj[1] = rosServerUrl; + configObj[2] = syncRealmAuthUrl; + configObj[3] = syncUserRefreshToken; + configObj[4] = syncUserAccessToken; + configObj[5] = syncConfig.syncClientValidateSsl(); + configObj[6] = syncConfig.getServerCertificateFilePath(); + configObj[7] = sessionStopPolicy; + configObj[8] = urlPrefix; + configObj[9] = customAuthorizationHeaderName; + configObj[10] = customHeaders; + configObj[11] = OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL; + configObj[12] = partitionValue; + configObj[13] = app.getSync(); + return configObj; } else { - return new Object[12]; + return new Object[SYNC_CONFIG_OPTIONS]; } } @@ -126,7 +128,9 @@ public static Context getApplicationContext() { @Override public void wrapObjectStoreSessionIfRequired(OsRealmConfig config) { if (config.getRealmConfiguration() instanceof SyncConfiguration) { - SyncManager.getOrCreateSession((SyncConfiguration) config.getRealmConfiguration(), config.getResolvedRealmURI()); + SyncConfiguration syncConfig = (SyncConfiguration) config.getRealmConfiguration(); + RealmApp app = syncConfig.getUser().getApp(); + app.getSync().getOrCreateSession(syncConfig); } } @@ -159,13 +163,13 @@ private void invokeRemoveSession(SyncConfiguration syncConfig) { if (removeSessionMethod == null) { synchronized (SyncObjectServerFacade.class) { if (removeSessionMethod == null) { - Method removeSession = SyncManager.class.getDeclaredMethod("removeSession", SyncConfiguration.class); + Method removeSession = RealmSync.class.getDeclaredMethod("removeSession", SyncConfiguration.class); removeSession.setAccessible(true); removeSessionMethod = removeSession; } } } - removeSessionMethod.invoke(null, syncConfig); + removeSessionMethod.invoke(syncConfig.getUser().getApp().getSync(), syncConfig); } catch (NoSuchMethodException e) { throw new RealmException("Could not lookup method to remove session: " + syncConfig.toString(), e); } catch (InvocationTargetException e) { @@ -206,8 +210,9 @@ public boolean wasDownloadInterrupted(Throwable throwable) { public void createNativeSyncSession(RealmConfiguration configuration) { if (configuration instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) configuration; - OsRealmConfig config = new OsRealmConfig.Builder(syncConfig).build(); - SyncManager.getOrCreateSession(syncConfig, config.getResolvedRealmURI()); + RealmApp app = syncConfig.getUser().getApp(); + app.getSync().getOrCreateSession(syncConfig); } } + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java index 5f337b1504..a15da2ddac 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import io.realm.SyncManager; +import io.realm.RealmSync; import io.realm.internal.Util; /** @@ -68,7 +68,7 @@ public static synchronized void removeListener(ConnectionListener listener) { * @return {@code true} if device is online, otherwise {@code false}. */ public static boolean isOnline(Context context) { - if (SyncManager.Debug.skipOnlineChecking) { + if (RealmSync.Debug.skipOnlineChecking) { return true; } ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index 1c85930ad4..896bbed2d2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -4,21 +4,8 @@ import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.RealmApp; -import io.realm.RealmAsyncTask; -import io.realm.SyncManager; -import io.realm.internal.RealmNotifier; -import io.realm.internal.android.AndroidCapabilities; -import io.realm.internal.android.AndroidRealmNotifier; -import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.log.LogLevel; import io.realm.log.RealmLog; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java index ecc338070a..59027f2e7b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java @@ -17,23 +17,8 @@ import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; -import javax.annotation.Nullable; - -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.RealmApp; -import io.realm.RealmAsyncTask; import io.realm.internal.Keep; -import io.realm.internal.KeepMember; -import io.realm.internal.NativeObject; -import io.realm.internal.RealmNotifier; -import io.realm.internal.android.AndroidCapabilities; -import io.realm.internal.android.AndroidRealmNotifier; -import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.log.RealmLog; /** * Java implementation of the transport layer exposed by ObjectStore when communicating with @@ -47,6 +32,10 @@ public abstract class OsJavaNetworkTransport { public static final int ERROR_INTERRUPTED = 1001; public static final int ERROR_UNKNOWN = 1002; + // Header configuration + private String authorizationHeaderName; + private Map customHeaders = new HashMap<>(); + /** * This method is being called from JNI in order to execute the network transport itself. * All logic around retry and parsing of results should be done by ObjectStore. @@ -63,6 +52,31 @@ public abstract class OsJavaNetworkTransport { */ protected abstract Response sendRequest(String method, String url, long timeoutMs, Map headers, String body); + public void setAuthorizationHeaderName(String headerName) { + authorizationHeaderName = headerName; + } + + public void addCustomRequestHeader(String headerName, String headerValue) { + customHeaders.put(headerName, headerValue); + } + + public String getAuthorizationHeaderName() { + return authorizationHeaderName; + } + + public Map getCustomRequestHeaders() { + return customHeaders; + } + + /** + * Reset all configured headers to their default. + * Used for testing. + */ + public void resetHeaders() { + authorizationHeaderName = "Authorization"; + customHeaders.clear(); + } + public static class Response { private final int httpResponseCode; private final int customResponseCode; diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java deleted file mode 100644 index 75db7302f5..0000000000 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import androidx.test.platform.app.InstrumentationRegistry; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.UUID; - -import io.realm.internal.objectserver.Token; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; -import io.realm.objectserver.utils.UserFactory; - -public class SyncTestUtils { - - public static final String USER_TOKEN = UUID.randomUUID().toString(); - public static final String DEFAULT_AUTH_URL = "http://objectserver.realm.io/auth"; - - private final static Method SYNC_MANAGER_GET_USER_STORE_METHOD; - private final static Method SYNC_USER_GET_ACCESS_TOKEN_METHOD; - private static int originalLogLevel; // Should only be modified by prepareEnvironmentForTest and restoreEnvironmentAfterTest - static { - try { - SYNC_MANAGER_GET_USER_STORE_METHOD = SyncManager.class.getDeclaredMethod("getUserStore"); - SYNC_USER_GET_ACCESS_TOKEN_METHOD = SyncUser.class.getDeclaredMethod("getRefreshToken"); - SYNC_MANAGER_GET_USER_STORE_METHOD.setAccessible(true); - SYNC_USER_GET_ACCESS_TOKEN_METHOD.setAccessible(true); - } catch (NoSuchMethodException e) { - throw new AssertionError(e); - } - } - - public static void prepareEnvironmentForTest(){ - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - originalLogLevel = RealmLog.getLevel(); - RealmLog.setLevel(LogLevel.DEBUG); - } - - /** - * Tries to restore the environment as best as possible after a test. - */ - public static void restoreEnvironmentAfterTest() throws IOException { - // Block until all users are logged out - UserFactory.logoutAllUsers(); - - // Reset log level - RealmLog.setLevel(originalLogLevel); - - if (BaseRealm.applicationContext != null) { - // Realm was already initialized. Reset all internal state - // in order to be able fully re-initialize. - - // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' - // causing the SyncUser to remain in memory. - // They're actually not persisted into disk. - // move this call to 'tearDown' to clean in-memory & on-disk users - // once https://github.com/realm/realm-object-store/issues/207 is resolved - SyncManager.reset(); - BaseRealm.applicationContext = null; // Required for Realm.init() to work - } - deleteRosFiles(); - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - } - - // Cleanup filesystem to make sure nothing lives for the next test. - // Failing to do so might lead to DIVERGENT_HISTORY errors being thrown if Realms from - // previous tests are being accessed. - private static void deleteRosFiles() throws IOException { - File rosFiles = new File(InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),"realm-object-server"); - deleteFile(rosFiles); - } - - private static void deleteFile(File file) throws IOException { - if (file.isDirectory()) { - for (File c : file.listFiles()) { - deleteFile(c); - } - } - if (!file.delete()) { - throw new IllegalStateException("Failed to delete file or directory: " + file.getAbsolutePath()); - } - } - - public static SyncUser createTestAdminUser() { - return createTestUser(USER_TOKEN, UUID.randomUUID().toString(), DEFAULT_AUTH_URL, Long.MAX_VALUE, true); - } - - public static SyncUser createTestUser() { - return createTestUser(USER_TOKEN, UUID.randomUUID().toString(), DEFAULT_AUTH_URL, Long.MAX_VALUE, false); - } - - public static SyncUser createTestUser(long expires) { - return createTestUser(USER_TOKEN, UUID.randomUUID().toString(), DEFAULT_AUTH_URL, expires, false); - } - - public static SyncUser createTestUser(String authUrl) { - return createTestUser(USER_TOKEN, UUID.randomUUID().toString(), authUrl, Long.MAX_VALUE, false); - } - - public static SyncUser createNamedTestUser(String userIdentifier) { - return createTestUser(USER_TOKEN, userIdentifier, DEFAULT_AUTH_URL, Long.MAX_VALUE, false); - } - - public static SyncUser createTestUser(String userTokenValue, String userIdentifier, String authUrl, long expires, boolean isAdmin) { - Token userToken = new Token(userTokenValue, userIdentifier, null, expires, null, isAdmin); - - JSONObject obj = new JSONObject(); - try { - JSONObject realmDesc = new JSONObject(); - realmDesc.put("uri", "realm://objectserver.realm.io/default"); - - obj.put("authUrl", authUrl); - obj.put("userToken", userToken.toJson()); - SyncUser syncUser = SyncUser.fromJson(obj.toString()); - // persist the user to the ObjectStore sync metadata, to simulate real login, otherwise SyncUser.isValid will - // "throw IllegalArgumentException: User not authenticated or authentication expired." since - // the call to SyncManager.getUserStore().isActive(syncUser.getIdentity()) will return false -// addToUserStore(syncUser); - return syncUser; - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - -// public static AuthenticateResponse createLoginResponse(long expires) { -// return createLoginResponse(USER_TOKEN, "JohnDoe", expires, false); -// } -// -// public static AuthenticateResponse createLoginResponse(String userTokenValue, String userIdentity, long expires, boolean isAdmin) { -// try { -// Token userToken = new Token(userTokenValue, userIdentity, null, expires, null, isAdmin); -// JSONObject response = new JSONObject(); -// response.put("refresh_token", userToken.toJson()); -// return AuthenticateResponse.from(response.toString()); -// } catch (JSONException e) { -// throw new RuntimeException(e); -// } -// } -// -// public static AuthenticateResponse createErrorResponse(ErrorCode code) { -// return AuthenticateResponse.from(new ObjectServerError(code, "dummy")); -// } - - public static Token getRefreshToken(SyncUser user) { - try { - return (Token) SYNC_USER_GET_ACCESS_TOKEN_METHOD.invoke(user); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new AssertionError(e); - } - } - -// private static void addToUserStore(SyncUser user) { -// try { -// UserStore userStore = (UserStore) SYNC_MANAGER_GET_USER_STORE_METHOD.invoke(null); -// userStore.put(user); -// } catch (InvocationTargetException | IllegalAccessException e) { -// throw new AssertionError(e); -// } -// } - - // Fully synchronize a Realm with the server by making sure that all changes are uploaded - // and downloaded again. - public static void syncRealm(Realm realm) { - SyncConfiguration config = (SyncConfiguration) realm.getConfiguration(); - SyncSession session = SyncManager.getSession(config); - try { - session.uploadAllLocalChanges(); - session.downloadAllServerChanges(); - } catch (InterruptedException e) { - throw new AssertionError(e); - } - realm.refresh(); - } -} diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt new file mode 100644 index 0000000000..d9aac2c845 --- /dev/null +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.internal.objectstore.OsJavaNetworkTransport +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.objectserver.utils.UserFactory +import java.io.File +import java.lang.IllegalStateException +import java.util.* + +class SyncTestUtils { + companion object { + private var originalLogLevel = RealmLog.getLevel() // Should only be modified by prepareEnvironmentForTest and restoreEnvironmentAfterTest = 0 + fun prepareEnvironmentForTest() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + originalLogLevel = RealmLog.getLevel() + RealmLog.setLevel(LogLevel.DEBUG) + } + + /** + * Tries to restore the environment as best as possible after a test. + */ + fun restoreEnvironmentAfterTest() { + // Block until all users are logged out + UserFactory.logoutAllUsers() + + // Reset log level + RealmLog.setLevel(originalLogLevel) + if (BaseRealm.applicationContext != null) { + // Realm was already initialized. Reset all internal state + // in order to be able to fully re-initialize. + + // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' + // causing the RealmUser to remain in memory. + // They're actually not persisted into disk. + // move this call to 'tearDown' to clean in-memory & on-disk users + // once https://github.com/realm/realm-object-store/issues/207 is resolved + // SyncManager.reset(); // FIXME + BaseRealm.applicationContext = null // Required for Realm.init() to work + } + deleteRosFiles() + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + + // Cleanup filesystem to make sure nothing lives for the next test. + // Failing to do so might lead to DIVERGENT_HISTORY errors being thrown if Realms from + // previous tests are being accessed. + private fun deleteRosFiles() { + val rosFiles = File(InstrumentationRegistry.getInstrumentation().context.filesDir, "realm-object-server") + deleteFile(rosFiles) + } + + private fun deleteFile(file: File) { + if (file.isDirectory) { + for (c in file.listFiles()) { + deleteFile(c) + } + } + check(file.delete()) { "Failed to delete file or directory: " + file.absolutePath } + } + + @JvmStatic + @JvmOverloads + fun createTestUser(app: RealmApp, userIdentifier: String = UUID.randomUUID().toString()): RealmUser { + val transportBackup = app.networkTransport + app.networkTransport = object : OsJavaNetworkTransport() { + override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: Map, body: String): Response { + if (url.endsWith("/login")) { + return Response.httpResponse(200, mapOf(), """ + { + "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODM5NjcyMDgsImlhdCI6MTU4Mzk2NTQwOCwiaXNzIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWEzIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWU2OTYzZGVhZmVhNjMyNTQ1ODFjMDI1Iiwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoiYWNjZXNzIn0.J4mp8LnlsxTQRV_7W2Er4qY0tptR76PJGG1k6HSMmUYqgfpJC2Fnbcf1VCoebzoNolH2-sr8AHDVBBCyjxRjqoY9OudFHmWZKmhDV1ysxPP4XmID0nUuN45qJSO8QEAqoOmP1crXjrUZWedFw8aaCZE-bxYfvcDHyjBcbNKZqzawwUw2PyTOlrNjgs01k2J4o5a5XzYkEsJuzr4_8UqKW6zXvYj24UtqnqoYatW5EzpX63m2qig8AcBwPK4ZHb5wEEUdf4QZxkRY5QmTgRHP8SSqVUB_mkHgKaizC_tSB3E0BekaDfLyWVC1taAstXJNfzgFtLI86AzuXS2dCiCfqQ", + "refresh_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODkxNDk0MDgsImlhdCI6MTU4Mzk2NTQwOCwic3RpdGNoX2RhdGEiOm51bGwsInN0aXRjaF9kZXZJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInN0aXRjaF9kb21haW5JZCI6IjVlNjk2M2RlYWZlYTYzMjU0NTgxYzAyNSIsInN0aXRjaF9pZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMyIsInN0aXRjaF9pZGVudCI6eyJpZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMC1oaWF2b3ZkbmJxbGNsYXBwYnl1cmJpaW8iLCJwcm92aWRlcl90eXBlIjoiYW5vbi11c2VyIiwicHJvdmlkZXJfaWQiOiI1ZTY5NjNlMGFmZWE2MzI1NDU4MWMwNGEifSwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoicmVmcmVzaCJ9.FhLdpmL48Mw0SyUKWuaplz3wfeS8TCO8S7I9pIJenQww9nPqQ7lIvykQxjCCtinGvsZIJKt_7R31xYCq4Jp53Nw81By79IwkXtO7VXHPsXXZG5_2xV-s0u44e85sYD5su_H-xnx03sU2piJbWJLSB8dKu3rMD4mO-S0HNXCCAty-JkYKSaM2-d_nS8MNb6k7Vfm7y69iz_uwHc-bb_1rPg7r827K6DEeEMF41Hy3Nx1kCdAUOM9-6nYv3pZSU1PFrGYi2uyTXPJ7R7HigY5IGHWd0hwONb_NUr4An2omqfvlkLEd77ut4V9m6mExFkoKzRz7shzn-IGkh3e4h7ECGA", + "user_id": "$userIdentifier", + "device_id": "000000000000000000000000" + } + """.trimIndent()) + + } else if (url.endsWith("/auth/profile")) { + return Response.httpResponse(200, mapOf(), """ + { + "user_id": "$userIdentifier", + "domain_id": "000000000000000000000000", + "identities": [ + { + "id": "5e68f51ade5ba998bb17500d", + "provider_type": "local-userpass", + "provider_id": "000000000000000000000003", + "provider_data": { + "email": "unique_user@domain.com" + } + } + ], + "data": { + "email": "unique_user@domain.com" + }, + "type": "normal", + "roles": [ + { + "role_name": "GROUP_OWNER", + "group_id": "5e68f51e087b1b33a53f56d5" + } + ] + } + """.trimIndent()) + } else { + throw IllegalStateException("Unsupported URL: $url") + } + } + } + val user = app.login(RealmCredentials.anonymous()) + app.networkTransport = transportBackup + return user + } + + // Fully synchronize a Realm with the server by making sure that all changes are uploaded + // and downloaded again. + fun syncRealm(realm: Realm) { + val config = realm.getConfiguration() as SyncConfiguration + val session = config.user.app.sync.getSession(config) + try { + session.uploadAllLocalChanges() + session.downloadAllServerChanges() + } catch (e: InterruptedException) { + throw AssertionError(e) + } + realm.refresh() + } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt b/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt similarity index 95% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt rename to realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt index 5e61a18bf8..229e83f4e0 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/TestRealmApp.kt +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt @@ -26,7 +26,7 @@ import io.realm.log.LogLevel * * NOTE: This class must remain in the [io.realm] package in order to work. */ -class TestRealmApp(networkTransport: OsJavaNetworkTransport? = null) : RealmApp(createConfiguration()) { +class TestRealmApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: (RealmAppConfiguration.Builder) -> Unit = {}) : RealmApp(createConfiguration()) { init { if (networkTransport != null) { diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java index 437be1b8ea..7b5d62b5a9 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java @@ -25,9 +25,8 @@ */ public class TestSyncConfigurationFactory extends TestRealmConfigurationFactory { - public SyncConfiguration.Builder createSyncConfigurationBuilder(SyncUser user, String url) { - return user.createConfiguration(url) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .directory(getRoot()); + public SyncConfiguration.Builder createSyncConfigurationBuilder(RealmUser user) { + return new SyncConfiguration.Builder(user, "default") + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY); } } diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java index 064748f282..cf8c4dde08 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java @@ -43,7 +43,6 @@ public class UserFactory { private String userName; private static UserFactory instance; private static RealmConfiguration configuration; - private static RealmApp app; // Run initializer here to make it possible to ensure that Realm.init has been called. // It is unpredictable when the static initializer is running @@ -51,7 +50,6 @@ private static synchronized void initFactory(boolean forceReset) { if (configuration == null || forceReset) { RealmConfiguration.Builder builder = new RealmConfiguration.Builder().name("user-factory.realm"); configuration = builder.build(); - app = new RealmApp(Constants.APP_ID); } } @@ -59,52 +57,6 @@ private UserFactory(String userName) { this.userName = userName; } - public RealmUser loginWithDefaultUser() { - RealmCredentials credentials = RealmCredentials.emailPassword(userName, PASSWORD); - return app.login(credentials); - } - - public static RealmUser createUniqueUser() { - String uniqueName = UUID.randomUUID().toString(); - return createUser(uniqueName); - } - - private static RealmUser createUser(String username) { - return null; // FIXME -// RealmCredentials credentials = RealmCredentials.emailPassword(username, PASSWORD, true); -// return app.login(credentials); - } - - public RealmUser createDefaultUser() { - return null; // FIXME -// RealmCredentials credentials = RealmCredentials.emailPassword(userName, PASSWORD, true); -// return app.login(credentials); - } - - public static RealmUser createAdminUser() { - return null; //FIXME -// // `admin` required as user identifier to be granted admin rights. -// // ROS 2.0 comes with a default admin user named "realm-admin" with password "". -// RealmCredentials credentials = RealmCredentials.emailPassword("realm-admin", "", false); -// return app.login(credentials); - } - - // Since we don't have a reliable way to reset the sync server and client, just use a new user factory for every - // test case. - public static void resetInstance() { - initFactory(true); - Realm realm = Realm.getInstance(configuration); - UserFactoryStore store = realm.where(UserFactoryStore.class).findFirst(); - realm.beginTransaction(); - if (store == null) { - store = realm.createObject(UserFactoryStore.class); - } - store.setUserName(UUID.randomUUID().toString()); - realm.commitTransaction(); - realm.close(); - instance = null; - } - // The @Before method will be called before the looper tests finished. We need to find a better place to call this. public static void clearInstance() { Realm realm = Realm.getInstance(configuration); diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 49f00424df..d11a976a8c 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -1272,15 +1272,15 @@ public static void populateLinkedDataSet(Realm realm) { */ private static final Field networkPoolExecutorField; static { - Class syncManager = null; + Class app = null; try { - syncManager = Class.forName("io.realm.SyncManager"); + app = Class.forName("io.realm.RealmApp"); } catch (ClassNotFoundException e) { // Ignore } try { - networkPoolExecutorField = (syncManager != null) ? syncManager.getDeclaredField("NETWORK_POOL_EXECUTOR") : null; + networkPoolExecutorField = (app != null) ? app.getDeclaredField("NETWORK_POOL_EXECUTOR") : null; } catch (NoSuchFieldException e) { throw new AssertionError("Could not find field: NETWORK_POOL_EXECUTOR\n" + Util.getStackTrace(e)); } diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java b/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java index 5c1b9b4107..25bcaa552d 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/RunInLooperThread.java @@ -28,7 +28,6 @@ import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedList; import java.util.List; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -40,7 +39,6 @@ import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.TestHelper; -import io.realm.internal.ObjectServerFacade; import io.realm.internal.android.AndroidCapabilities; diff --git a/tools/sync_test_server/app_config/auth_providers/anon-user.json b/tools/sync_test_server/app_config/auth_providers/anon-user.json old mode 100755 new mode 100644 index 00e4641703..a57bb6eae2 --- a/tools/sync_test_server/app_config/auth_providers/anon-user.json +++ b/tools/sync_test_server/app_config/auth_providers/anon-user.json @@ -1,5 +1,5 @@ { - "id": "5e688e10535956d2ec4046e3", + "id": "5e9578c1a06f8d660afdaa34", "name": "anon-user", "type": "anon-user", "disabled": false diff --git a/tools/sync_test_server/app_config/auth_providers/api-key.json b/tools/sync_test_server/app_config/auth_providers/api-key.json old mode 100755 new mode 100644 index a0ef800747..c9a0167893 --- a/tools/sync_test_server/app_config/auth_providers/api-key.json +++ b/tools/sync_test_server/app_config/auth_providers/api-key.json @@ -1,5 +1,5 @@ { - "id": "5e688e10535956d2ec4046e4", + "id": "5e9578c1a06f8d660afdaa35", "name": "api-key", "type": "api-key", "disabled": false diff --git a/tools/sync_test_server/app_config/auth_providers/custom-function.json b/tools/sync_test_server/app_config/auth_providers/custom-function.json old mode 100755 new mode 100644 index 34ccf8fbae..340315f6c6 --- a/tools/sync_test_server/app_config/auth_providers/custom-function.json +++ b/tools/sync_test_server/app_config/auth_providers/custom-function.json @@ -1,5 +1,5 @@ { - "id": "5e689dc8535956d2ec40527a", + "id": "5e9578c1a06f8d660afdaa36", "name": "custom-function", "type": "custom-function", "config": { diff --git a/tools/sync_test_server/app_config/auth_providers/local-userpass.json b/tools/sync_test_server/app_config/auth_providers/local-userpass.json old mode 100755 new mode 100644 index 412cbee3c6..77056f028f --- a/tools/sync_test_server/app_config/auth_providers/local-userpass.json +++ b/tools/sync_test_server/app_config/auth_providers/local-userpass.json @@ -1,15 +1,15 @@ { - "id": "5e689d78535956d2ec405220", + "id": "5e9578c1a06f8d660afdaa37", "name": "local-userpass", "type": "local-userpass", "config": { "autoConfirm": true, - "resetFunctionName": "resetFunc", - "runConfirmationFunction": false, "emailConfirmationUrl": "http://realm.io/confirm-user", - "runResetFunction": false, + "resetFunctionName": "resetFunc", "resetPasswordSubject": "Reset Password", - "resetPasswordUrl": "http://realm.io/reset-password" + "resetPasswordUrl": "http://realm.io/reset-password", + "runConfirmationFunction": false, + "runResetFunction": false }, "disabled": false } diff --git a/tools/sync_test_server/app_config/functions/authFunc/config.json b/tools/sync_test_server/app_config/functions/authFunc/config.json old mode 100755 new mode 100644 index d99d62ff41..8bb99cb6ab --- a/tools/sync_test_server/app_config/functions/authFunc/config.json +++ b/tools/sync_test_server/app_config/functions/authFunc/config.json @@ -1,5 +1,5 @@ { - "id": "5e689dc8535956d2ec405275", + "id": "5e9578c1a06f8d660afdaa31", "name": "authFunc", "private": false, "can_evaluate": {} diff --git a/tools/sync_test_server/app_config/functions/authFunc/source.js b/tools/sync_test_server/app_config/functions/authFunc/source.js old mode 100755 new mode 100644 index 58d4bd3ede..590f3177eb --- a/tools/sync_test_server/app_config/functions/authFunc/source.js +++ b/tools/sync_test_server/app_config/functions/authFunc/source.js @@ -13,5 +13,5 @@ */ exports = (loginPayload) => { - return; + return loginPayload["realmCustomAuthFuncUserId"]; }; diff --git a/tools/sync_test_server/app_config/functions/confirmFunc/config.json b/tools/sync_test_server/app_config/functions/confirmFunc/config.json new file mode 100644 index 0000000000..e1a93ddfbe --- /dev/null +++ b/tools/sync_test_server/app_config/functions/confirmFunc/config.json @@ -0,0 +1,6 @@ +{ + "id": "5e9578c1a06f8d660afdaa32", + "name": "confirmFunc", + "private": false, + "can_evaluate": {} +} diff --git a/tools/sync_test_server/app_config/functions/confirmFunc/source.js b/tools/sync_test_server/app_config/functions/confirmFunc/source.js new file mode 100644 index 0000000000..6a3d024046 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/confirmFunc/source.js @@ -0,0 +1,49 @@ + + /* + + This function will be run AFTER a user registers their username and password and is called with an object parameter + which contains three keys: 'token', 'tokenId', and 'username'. + + The return object must contain a 'status' key which can be empty or one of three string values: + 'success', 'pending', or 'fail'. + + 'success': the user is confirmed and is able to log in. + + 'pending': the user is not confirmed and the UserPasswordAuthProviderClient 'confirmUser' function would + need to be called with the token and tokenId via an SDK. (see below) + + const emailPassClient = Stitch.defaultAppClient.auth + .getProviderClient(UserPasswordAuthProviderClient.factory); + + return emailPassClient.confirmUser(token, tokenId); + + 'fail': the user is not confirmed and will not be able to log in. + + If an error is thrown within the function the result is the same as 'fail'. + + Example below: + + exports = ({ token, tokenId, username }) => { + // process the confirm token, tokenId and username + if (context.functions.execute('isValidUser', username)) { + // will confirm the user + return { status: 'success' }; + } else { + context.functions.execute('sendConfirmationEmail', username, token, tokenId); + return { status: 'pending' }; + } + + return { status: 'fail' }; + }; + + The uncommented function below is just a placeholder and will result in failure. + */ + + exports = ({ token, tokenId, username }) => { + // process the confirm token, tokenId and username + if (username.includes("realm_tests_do_autoverify")) { + return { status: 'success' } + } + // do not confirm the user + return { status: 'fail' }; + }; diff --git a/tools/sync_test_server/app_config/functions/resetFunc/config.json b/tools/sync_test_server/app_config/functions/resetFunc/config.json old mode 100755 new mode 100644 index 0afe98c70d..33dcc7dd91 --- a/tools/sync_test_server/app_config/functions/resetFunc/config.json +++ b/tools/sync_test_server/app_config/functions/resetFunc/config.json @@ -1,5 +1,5 @@ { - "id": "5e689d78535956d2ec405217", + "id": "5e9578c1a06f8d660afdaa33", "name": "resetFunc", "private": false, "can_evaluate": {} diff --git a/tools/sync_test_server/app_config/functions/resetFunc/source.js b/tools/sync_test_server/app_config/functions/resetFunc/source.js old mode 100755 new mode 100644 index 7dac30ae01..7482a7b07c --- a/tools/sync_test_server/app_config/functions/resetFunc/source.js +++ b/tools/sync_test_server/app_config/functions/resetFunc/source.js @@ -41,10 +41,10 @@ The uncommented function below is just a placeholder and will result in failure. */ - exports = ({ token, tokenId, username, password }, customParam1, customParam2) => { +exports = ({ token, tokenId, username, password }, customParam1, customParam2) => { if (customParam1 != "say-the-magic-word" || customParam2 != 42) { - return { status: 'fail' }; + return { status: 'fail' }; } else { - return { status: 'success' }; + return { status: 'success' }; } - }; +} diff --git a/tools/sync_test_server/app_config/secrets.json b/tools/sync_test_server/app_config/secrets.json new file mode 100644 index 0000000000..533fe384b0 --- /dev/null +++ b/tools/sync_test_server/app_config/secrets.json @@ -0,0 +1,3 @@ +{ + "BackingDB_uri": "mongodb://localhost:26000" +} \ No newline at end of file diff --git a/tools/sync_test_server/app_config/services/BackingDB/config.json b/tools/sync_test_server/app_config/services/BackingDB/config.json new file mode 100644 index 0000000000..674e448d9e --- /dev/null +++ b/tools/sync_test_server/app_config/services/BackingDB/config.json @@ -0,0 +1,22 @@ +{ + "id": "5e9578c1a06f8d660afdaa2b", + "name": "BackingDB", + "type": "mongodb", + "config": { + "sync": { + "state": "enabled", + "database_name": "test_data", + "partition": { + "key": "realm_id", + "permissions": { + "read": true, + "write": true + } + } + } + }, + "secret_config": { + "uri": "BackingDB_uri" + }, + "version": 1 +} diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json new file mode 100644 index 0000000000..c1980fbde9 --- /dev/null +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json @@ -0,0 +1,34 @@ +{ + "id": "5e9578c1a06f8d660afdaa2c", + "database": "test_data", + "collection": "SyncDog", + "roles": [ + { + "name": "default", + "apply_when": {}, + "insert": true, + "delete": true, + "additional_fields": {} + } + ], + "schema": { + "properties": { + "_id": { + "bsonType": "objectId" + }, + "breed": { + "bsonType": "string" + }, + "name": { + "bsonType": "string" + }, + "realm_id": { + "bsonType": "string" + } + }, + "required": [ + "name" + ], + "title": "SyncDog" + } +} diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json new file mode 100644 index 0000000000..5909aeacdf --- /dev/null +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json @@ -0,0 +1,46 @@ +{ + "id": "5e9578c1a06f8d660afdaa2d", + "database": "test_data", + "collection": "SyncPerson", + "roles": [ + { + "name": "default", + "apply_when": {}, + "insert": true, + "delete": true, + "additional_fields": {} + } + ], + "schema": { + "properties": { + "_id": { + "bsonType": "objectId" + }, + "age": { + "bsonType": "int" + }, + "dogs": { + "bsonType": "array", + "items": { + "bsonType": "objectId" + } + }, + "firstName": { + "bsonType": "string" + }, + "lastName": { + "bsonType": "string" + }, + "realm_id": { + "bsonType": "string" + } + }, + "required": [ + "firstName", + "lastName", + "age", + "dogs" + ], + "title": "SyncPerson" + } +} diff --git a/tools/sync_test_server/app_config/services/integration_tests/config.json b/tools/sync_test_server/app_config/services/integration_tests/config.json deleted file mode 100755 index 0bd351441b..0000000000 --- a/tools/sync_test_server/app_config/services/integration_tests/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "5e688e10535956d2ec4046e2", - "name": "integration_tests", - "type": "mongodb", - "config": {}, - "secret_config": { - "uri": "integration_tests_uri" - }, - "version": 1 -} diff --git a/tools/sync_test_server/app_config/stitch.json b/tools/sync_test_server/app_config/stitch.json old mode 100755 new mode 100644 index 118b56a159..ccbd792c4e --- a/tools/sync_test_server/app_config/stitch.json +++ b/tools/sync_test_server/app_config/stitch.json @@ -1,5 +1,5 @@ { - "app_id": "realm-sdk-integration-tests-pwjzl", + "app_id": "realm-sdk-integration-tests-ibecp", "config_version": 20180301, "name": "realm-sdk-integration-tests", "location": "US-VA", @@ -9,6 +9,6 @@ "enabled": false }, "sync": { - "development_mode_enabled": false + "development_mode_enabled": true } } diff --git a/tools/sync_test_server/setup_mongodb_realm.sh b/tools/sync_test_server/setup_mongodb_realm.sh index 2720bc8821..3771a43d25 100755 --- a/tools/sync_test_server/setup_mongodb_realm.sh +++ b/tools/sync_test_server/setup_mongodb_realm.sh @@ -53,20 +53,13 @@ echo "App ID Suffix: $APP_ID_SUFFIX" # 3. Create the secret(s) needed to start the Stitch app: # - a) MongoDB Service: Requires an URI. stitch-cli secrets add \ - --name="integration_tests_uri" \ + --name="BackingDB_uri" \ --value="mongodb://localhost:26000" \ --app-id="realm-sdk-integration-tests-$APP_ID_SUFFIX" \ --base-url=http://localhost:9090 \ --config-path=/tmp/stitch-config -# 4. Due to how Stitch works internally, it is currently not possible to create a secret starting -# with '__'. This is a problem as the Stitch UI has a builtin requirement on a secret named -# "__integration_test_url". In order to fix this, we hack the JSON output from Stitch before -# importing it again. Doing it this way, makes it possible to use the output from a Stitch -# export directly. -sed -i 's/\"uri\": \"__integration_tests_uri\"/\"uri\": \"integration_tests_uri\"/g' /tmp/app_config/services/integration_tests/config.json - -# 5. Now we can correctly import the Stitch app +# 4. Now we can correctly import the Stitch app stitch-cli import \ --config-path=/tmp/stitch-config \ --base-url=http://localhost:9090 \ @@ -76,5 +69,5 @@ stitch-cli import \ --strategy replace \ -y -# 7. Store the application id in the Command Server so it can be accessed by Integration Tests on the device +# 5. Store the application id in the Command Server so it can be accessed by Integration Tests on the device curl -X PUT -d id="realm-sdk-integration-tests-$APP_ID_SUFFIX" http://localhost:8888/application-id \ No newline at end of file diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 53547ed32c..2dabbda6b4 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -37,7 +37,7 @@ docker login docker.pkg.github.com -u $GITHUB_DOCKER_USER -p $GITHUB_DOCKER_TOKE # Run Stitch and Stitch CLI Docker images docker network create mongodb-realm-network docker build $DOCKERFILE_DIR -t mongodb-realm-command-server || { echo "Failed to build Docker image." ; exit 1 ; } -ID=$(docker run --rm -i -t -d --network mongodb-realm-network -p 9090:9090 -p 8888:8888 --name mongodb-realm docker.pkg.github.com/realm/ci/mongodb-realm-test-server:$MONGODB_REALM_VERSION) +ID=$(docker run --rm -i -t -d --network mongodb-realm-network -p9090:9090 -p8888:8888 -p26000:26000 --name mongodb-realm docker.pkg.github.com/realm/ci/mongodb-realm-test-server:$MONGODB_REALM_VERSION) docker run --rm -i -t -d --network container:$ID -v$TMP_DIR:/tmp --name mongodb-realm-command-server mongodb-realm-command-server docker cp "$DOCKERFILE_DIR"/app_config mongodb-realm:/tmp/app_config From c69459a32e6094348ad5901e77eeb48d7b638bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 1 May 2020 17:13:32 +0200 Subject: [PATCH 1498/2110] Merge Stitch and Realm SDKs - 1: scaffolding (#6804) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First iteration: added GMS library (possibly temporarily) to avoid introducing immediate breaking changes in how we process asynchronous operations with AsyncRealmTask. All original Stitch interfaces and proxies have been discarded in favour of Java classes (although this approach might be changed). Some interfaces connected to the collection's iterables have been omitted as it is unclear whether they will be needed or not for the time being. * Added licences to class headers plus a bit of cleanup * Added latest API methods and necessary classes * Added remote mongo client and remote database, their respective Os files and part of the native logic * Moved JNI callbacks outside RealmApp and added more remote collection classes * Updated object store branch to v10 and fixed wrong use of count call * Cleanup * Fixed wrong finalizer methods and cleanup to interop files * Apply suggestions from code review Accepted review suggestions Co-authored-by: Christian Melchior * Cleanup after second round of code review * Final cleanup * FIXMEs instead of TODOs Co-authored-by: Eduardo López Co-authored-by: Christian Melchior --- realm/realm-library/build.gradle | 2 + .../realm-library/src/main/cpp/CMakeLists.txt | 11 +- ...ternal_objectstore_OsRemoteMongoClient.cpp | 73 ++ ...al_objectstore_OsRemoteMongoCollection.cpp | 64 ++ ...rnal_objectstore_OsRemoteMongoDatabase.cpp | 58 ++ .../src/main/cpp/java_network_transport.hpp | 4 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/ApiKeyAuth.java | 14 +- .../java/io/realm/EmailPasswordAuth.java | 13 +- .../objectServer/java/io/realm/RealmApp.java | 53 +- .../objectServer/java/io/realm/RealmUser.java | 21 +- .../internal/jni/OsJNIResultCallback.java | 63 ++ .../jni/OsJNIVoidResultCallback.java} | 18 +- .../objectstore/OsRemoteMongoClient.java | 50 ++ .../objectstore/OsRemoteMongoCollection.java | 49 ++ .../objectstore/OsRemoteMongoDatabase.java | 56 ++ .../java/io/realm/mongodb/MongoNamespace.java | 186 +++++ .../io/realm/mongodb/RemoteMongoClient.java | 45 ++ .../realm/mongodb/RemoteMongoCollection.java | 651 ++++++++++++++++++ .../io/realm/mongodb/RemoteMongoDatabase.java | 77 +++ .../mongodb/remote/RemoteCountOptions.java | 51 ++ .../RemoteDeleteResult.java} | 30 +- .../remote/RemoteFindOneAndModifyOptions.java | 134 ++++ .../mongodb/remote/RemoteFindOptions.java | 108 +++ .../remote/RemoteInsertManyResult.java | 50 ++ .../mongodb/remote/RemoteInsertOneResult.java | 45 ++ .../mongodb/remote/RemoteUpdateOptions.java | 53 ++ .../mongodb/remote/RemoteUpdateResult.java | 79 +++ .../aggregate/RemoteAggregateIterable.java} | 15 +- .../remote/find/RemoteFindIterable.java | 71 ++ 30 files changed, 2061 insertions(+), 85 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoClient.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java rename realm/realm-library/src/objectServer/java/io/realm/{mongodb/RealmMongoDBDatabase.java => internal/jni/OsJNIVoidResultCallback.java} (63%) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoClient.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoCollection.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoDatabase.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/MongoNamespace.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoClient.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoCollection.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoDatabase.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteCountOptions.java rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{RealmMongoDBService.java => remote/RemoteDeleteResult.java} (51%) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOneAndModifyOptions.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOptions.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertManyResult.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertOneResult.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateOptions.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateResult.java rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{RealmMongoDBCollection.java => remote/aggregate/RemoteAggregateIterable.java} (64%) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/find/RemoteFindIterable.java diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index c23c4858dc..55609f16fa 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -211,6 +211,8 @@ dependencies { implementation('io.reactivex.rxjava2:rxandroid:2.1.1') { exclude group: 'io.reactivex.rxjava2', module: 'rxjava' } + // FIXME: Attempt to find a way to remove this dependency + implementation "com.google.android.gms:play-services-tasks:17.0.2" // added to support mongo client's asynchronous nature without breaking Stitch's API // TODO: investigate why we can't use the latest multidex version // check baseDebugAndroidTestRuntimeClasspath and objectServerDebugAndroidTestRuntimeClasspath diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 01553e4ac0..f7e22281ab 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -112,6 +112,9 @@ if (build_SYNC) io.realm.internal.objectstore.OsAppCredentials io.realm.internal.objectstore.OsAsyncOpenTask io.realm.internal.objectstore.OsJavaNetworkTransport + io.realm.internal.objectstore.OsRemoteMongoClient + io.realm.internal.objectstore.OsRemoteMongoCollection + io.realm.internal.objectstore.OsRemoteMongoDatabase io.realm.internal.objectstore.OsSyncUser ) endif() @@ -197,12 +200,15 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmUser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_EmailPasswordAuth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ApiKeyAuth.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetRequiredError.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmSync.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAsyncOpenTask.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAppCredentials.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoClient.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsSyncUser.cpp ) endif() @@ -222,7 +228,8 @@ if (build_SYNC) "object-store/src/results.cpp" "object-store/src/impl/results_notifier.cpp" "object-store/src/sync/*.cpp" - "object-store/src/sync/impl/*.cpp") + "object-store/src/sync/impl/*.cpp" + "object-store/src/util/bson/*.cpp") endif() add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoClient.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoClient.cpp new file mode 100644 index 0000000000..2def4d1ff8 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoClient.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_internal_objectstore_OsRemoteMongoClient.h" + +#include "java_class_global_def.hpp" +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" + +#include +#include +#include +#include +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::jni_util; +using namespace realm::_impl; + +static void finalize_client(jlong ptr) { + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsRemoteMongoClient_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { + return reinterpret_cast(&finalize_client); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsRemoteMongoClient_nativeCreate(JNIEnv* env, + jclass, + jlong j_app_ptr, + jstring j_service_name) { + try { + App* app = reinterpret_cast(j_app_ptr); + JStringAccessor name(env, j_service_name); + RemoteMongoClient client(app->remote_mongo_client(name)); + return reinterpret_cast(new RemoteMongoClient(std::move(client))); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsRemoteMongoClient_nativeCreateDatabase(JNIEnv* env, + jclass, + jlong j_client_ptr, + jstring j_database_name) { + try { + RemoteMongoClient* client = reinterpret_cast(j_client_ptr); + JStringAccessor name(env, j_database_name); + RemoteMongoDatabase database(client->db(name)); + return reinterpret_cast(new RemoteMongoDatabase(std::move(database))); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp new file mode 100644 index 0000000000..a8ce6a91b0 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_internal_objectstore_OsRemoteMongoCollection.h" + +#include "java_class_global_def.hpp" +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" +#include "object-store/src/util/bson/bson.hpp" + +#include +#include +#include +#include +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::jni_util; +using namespace realm::_impl; + +static std::function collection_mapper = [](JNIEnv* env, uint64_t result) { + return JavaClassGlobalDef::new_long(env, result); +}; + +static void finalize_collection(jlong ptr) { + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsRemoteMongoCollection_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { + return reinterpret_cast(&finalize_collection); +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsRemoteMongoCollection_nativeCount(JNIEnv* env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jlong j_limit, + jobject j_callback) { + try { + RemoteMongoCollection* collection = reinterpret_cast(j_collection_ptr); + JStringAccessor filter(env, j_filter); + uint64_t limit = std::uint64_t(j_limit); + collection->count(filter, limit, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper)); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp new file mode 100644 index 0000000000..7dc060c7de --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_internal_objectstore_OsRemoteMongoDatabase.h" + +#include "java_class_global_def.hpp" +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" + +#include +#include +#include +#include +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::jni_util; +using namespace realm::_impl; + +static void finalize_database(jlong ptr) { + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsRemoteMongoDatabase_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { + return reinterpret_cast(&finalize_database); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsRemoteMongoDatabase_nativeGetCollection(JNIEnv* env, + jclass, + jlong j_database_ptr, + jstring j_collection_name) { + try { + RemoteMongoDatabase* database = reinterpret_cast(j_database_ptr); + JStringAccessor name(env, j_collection_name); + RemoteMongoCollection collection(database->collection(name)); + return reinterpret_cast(new RemoteMongoCollection(std::move(collection))); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} diff --git a/realm/realm-library/src/main/cpp/java_network_transport.hpp b/realm/realm-library/src/main/cpp/java_network_transport.hpp index a60845cbd8..2fe55a796d 100644 --- a/realm/realm-library/src/main/cpp/java_network_transport.hpp +++ b/realm/realm-library/src/main/cpp/java_network_transport.hpp @@ -112,7 +112,7 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { return [callback, success_mapper](T result, Optional error) { JNIEnv* env = JniUtils::get_env(true); - static JavaClass java_callback_class(env, "io/realm/RealmApp$OsJNIResultCallback"); + static JavaClass java_callback_class(env, "io/realm/internal/jni/OsJNIResultCallback"); static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); @@ -138,7 +138,7 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { return [callback](Optional error) { JNIEnv* env = JniUtils::get_env(true); - static JavaClass java_callback_class(env, "io/realm/RealmApp$OsJNIVoidResultCallback"); + static JavaClass java_callback_class(env, "io/realm/internal/jni/OsJNIVoidResultCallback"); static JavaMethod java_notify_onerror(env, java_callback_class, "onError", "(Ljava/lang/String;ILjava/lang/String;)V"); static JavaMethod java_notify_onsuccess(env, java_callback_class, "onSuccess", "(Ljava/lang/Object;)V"); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 2d3a3c3630..d2a4126bd3 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 2d3a3c3630c08f37ea85fe8b2c089e756f84dfa8 +Subproject commit d2a4126bd317415edb9ee0addcae67aa0b667d6d diff --git a/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java b/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java index 9f0b3b1893..884066b47f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java @@ -24,6 +24,8 @@ import javax.annotation.Nullable; import io.realm.internal.Util; +import io.realm.internal.jni.OsJNIResultCallback; +import io.realm.internal.jni.OsJNIVoidResultCallback; import io.realm.internal.objectstore.OsJavaNetworkTransport; import static io.realm.RealmApp.NETWORK_POOL_EXECUTOR; @@ -74,7 +76,7 @@ public RealmUserApiKey createApiKey(String name) throws ObjectServerError { Util.checkEmpty(name, "name"); AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - RealmApp.OsJNIResultCallback callback = new RealmApp.OsJNIResultCallback(success, error) { + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected RealmUserApiKey mapSuccess(Object result) { return createKeyFromNative((Object[]) result); @@ -116,7 +118,7 @@ public RealmUserApiKey fetchApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_FETCH_SINGLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new RealmApp.OsJNIResultCallback(success, error) { + nativeCallFunction(TYPE_FETCH_SINGLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIResultCallback(success, error) { @Override protected RealmUserApiKey mapSuccess(Object result) { return createKeyFromNative((Object[]) result); @@ -151,7 +153,7 @@ public RealmUserApiKey run() throws ObjectServerError { public List fetchAllApiKeys() throws ObjectServerError { AtomicReference> success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_FETCH_ALL, user.getApp().nativePtr, user.osUser.getNativePtr(), null, new RealmApp.OsJNIResultCallback>(success, error) { + nativeCallFunction(TYPE_FETCH_ALL, user.getApp().nativePtr, user.osUser.getNativePtr(), null, new OsJNIResultCallback>(success, error) { @Override protected List mapSuccess(Object result) { Object[] keyData = (Object[]) result; @@ -192,7 +194,7 @@ public List run() throws ObjectServerError { public void deleteApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_DELETE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new RealmApp.OsJNIVoidResultCallback(error)); + nativeCallFunction(TYPE_DELETE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIVoidResultCallback(error)); RealmApp.handleResult(null, error); } @@ -224,7 +226,7 @@ public Void run() throws ObjectServerError { public void disableApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_DISABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new RealmApp.OsJNIVoidResultCallback(error)); + nativeCallFunction(TYPE_DISABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIVoidResultCallback(error)); RealmApp.handleResult(null, error); } @@ -256,7 +258,7 @@ public Void run() throws ObjectServerError { public void enableApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_ENABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new RealmApp.OsJNIVoidResultCallback(error)); + nativeCallFunction(TYPE_ENABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIVoidResultCallback(error)); RealmApp.handleResult(null, error); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java index 8fb25f1322..e42c788bfa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java @@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicReference; import io.realm.internal.Util; +import io.realm.internal.jni.OsJNIVoidResultCallback; import io.realm.internal.objectstore.OsJavaNetworkTransport; import static io.realm.RealmApp.NETWORK_POOL_EXECUTOR; @@ -62,7 +63,7 @@ public void registerUser(String email, String password) throws ObjectServerError AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_REGISTER_USER, app.nativePtr, - new RealmApp.OsJNIVoidResultCallback(error), + new OsJNIVoidResultCallback(error), email, password); RealmApp.handleResult(null, error); } @@ -103,7 +104,7 @@ public void confirmUser(String token, String tokenId) throws ObjectServerError { AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_CONFIRM_USER, app.nativePtr, - new RealmApp.OsJNIVoidResultCallback(error), + new OsJNIVoidResultCallback(error), token, tokenId); RealmApp.handleResult(null, error); } @@ -139,7 +140,7 @@ public void resendConfirmationEmail(String email) throws ObjectServerError { AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_RESEND_CONFIRMATION_EMAIL, app.nativePtr, - new RealmApp.OsJNIVoidResultCallback(error), + new OsJNIVoidResultCallback(error), email); RealmApp.handleResult(null, error); } @@ -174,7 +175,7 @@ public void sendResetPasswordEmail(String email) throws ObjectServerError { AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_SEND_RESET_PASSWORD_EMAIL, app.nativePtr, - new RealmApp.OsJNIVoidResultCallback(error), + new OsJNIVoidResultCallback(error), email); RealmApp.handleResult(null, error); } @@ -218,7 +219,7 @@ public void callResetPasswordFunction(String email, String newPassword, Object.. AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_CALL_RESET_PASSWORD_FUNCTION, app.nativePtr, - new RealmApp.OsJNIVoidResultCallback(error), + new OsJNIVoidResultCallback(error), email, newPassword, array.toString()); RealmApp.handleResult(null, error); } @@ -262,7 +263,7 @@ public void resetPassword(String token, String tokenId, String newPassword) thro AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_RESET_PASSWORD, app.nativePtr, - new RealmApp.OsJNIVoidResultCallback(error), + new OsJNIVoidResultCallback(error), token, tokenId, newPassword); RealmApp.handleResult(null, error); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index 248b2fac6e..ffee3fc231 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -41,6 +41,7 @@ import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.async.RealmThreadPoolExecutor; +import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.network.OkHttpNetworkTransport; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.log.RealmLog; @@ -409,58 +410,6 @@ static T handleResult(@Nullable AtomicReference success, AtomicReference< } } - // Common callback for handling callbacks from the ObjectStore layer. - // NOTE: This class is called from JNI. If renamed, adjust callbacks in RealmApp.cpp - @Keep - static class OsJNIVoidResultCallback extends OsJNIResultCallback { - - public OsJNIVoidResultCallback(AtomicReference error) { - super(null, error); - } - - @Override - protected Void mapSuccess(Object result) { - return null; - } - } - - // Common callback for handling results from the ObjectStore layer. - // NOTE: This class is called from JNI. If renamed, adjust callbacks in RealmApp.cpp - @Keep - static abstract class OsJNIResultCallback extends OsJavaNetworkTransport.NetworkTransportJNIResultCallback { - - private final AtomicReference success; - private final AtomicReference error; - - public OsJNIResultCallback(@Nullable AtomicReference success, AtomicReference error) { - this.success = success; - this.error = error; - } - - @Override - public void onSuccess(Object result) { - T mappedResult = mapSuccess(result); - if (success != null) { - success.set(mappedResult); - } - } - - // Must map the underlying success Object to the appropriate type in Java - protected abstract T mapSuccess(Object result); - - @Override - public void onError(String nativeErrorCategory, int nativeErrorCode, String errorMessage) { - ErrorCode code = ErrorCode.fromNativeError(nativeErrorCategory, nativeErrorCode); - if (code == ErrorCode.UNKNOWN) { - // In case of UNKNOWN errors parse as much error information on as possible. - String detailedErrorMessage = String.format("{%s::%s} %s", nativeErrorCategory, nativeErrorCode, errorMessage); - error.set(new ObjectServerError(code, detailedErrorMessage)); - } else { - error.set(new ObjectServerError(code, errorMessage)); - } - } - } - // Class wrapping requests made against MongoDB Realm. Is also responsible for calling with success/error on the // correct thread. static abstract class Request { diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java index 3263c678c7..183073a1b0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -22,11 +22,13 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.internal.Util; +import io.realm.internal.jni.OsJNIResultCallback; +import io.realm.internal.jni.OsJNIVoidResultCallback; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.internal.objectstore.OsSyncUser; import io.realm.internal.util.Pair; -import io.realm.internal.Util; -import io.realm.mongodb.RealmMongoDBService; +import io.realm.mongodb.RemoteMongoClient; import static io.realm.RealmApp.handleResult; @@ -38,6 +40,7 @@ public class RealmUser { OsSyncUser osUser; private final RealmApp app; private ApiKeyAuth apiKeyAuthProvider = null; + private RemoteMongoClient remoteMongoClient = null; /** * FIXME @@ -263,7 +266,7 @@ public RealmUser linkCredentials(RealmCredentials credentials) { checkLoggedIn(); AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeLinkUser(app.nativePtr, osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new RealmApp.OsJNIResultCallback(success, error) { + nativeLinkUser(app.nativePtr, osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { @Override protected RealmUser mapSuccess(Object result) { osUser = new OsSyncUser((long) result); // OS returns the updated user as a new one. @@ -319,7 +322,7 @@ public RealmUser remove() throws ObjectServerError { boolean loggedIn = isLoggedIn(); AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeRemoveUser(app.nativePtr, osUser.getNativePtr(), new RealmApp.OsJNIResultCallback(success, error) { + nativeRemoveUser(app.nativePtr, osUser.getNativePtr(), new OsJNIResultCallback(success, error) { @Override protected RealmUser mapSuccess(Object result) { return RealmUser.this; @@ -371,7 +374,7 @@ public RealmUser run() throws ObjectServerError { public void logOut() throws ObjectServerError { boolean loggedIn = isLoggedIn(); AtomicReference error = new AtomicReference<>(null); - nativeLogOut(app.nativePtr, osUser.getNativePtr(), new RealmApp.OsJNIVoidResultCallback(error)); + nativeLogOut(app.nativePtr, osUser.getNativePtr(), new OsJNIVoidResultCallback(error)); handleResult(null, error); if (loggedIn) { app.notifyUserLoggedOut(this); @@ -438,8 +441,12 @@ public RealmPushNotifications getPushNotifications() { /** * FIXME Add support for the MongoDB wrapper. Name of Class and method still TBD. */ - public RealmMongoDBService getMongoDBService() { - return null; + public RemoteMongoClient getRemoteMongoClient() { + if (remoteMongoClient == null) { + // FIXME: serviceName? + remoteMongoClient = new RemoteMongoClient(this, "serviceName"); + } + return remoteMongoClient; } @SuppressFBWarnings("NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION") diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java new file mode 100644 index 0000000000..176a8fe12c --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.jni; + +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.Nullable; + +import io.realm.ErrorCode; +import io.realm.ObjectServerError; +import io.realm.internal.Keep; +import io.realm.internal.objectstore.OsJavaNetworkTransport; + +// Common callback for handling results from the ObjectStore layer. +// NOTE: This class is called from JNI. If renamed, adjust callbacks in RealmApp.cpp +@Keep +public abstract class OsJNIResultCallback extends OsJavaNetworkTransport.NetworkTransportJNIResultCallback { + + private final AtomicReference success; + private final AtomicReference error; + + public OsJNIResultCallback(@Nullable AtomicReference success, AtomicReference error) { + this.success = success; + this.error = error; + } + + @Override + public void onSuccess(Object result) { + T mappedResult = mapSuccess(result); + if (success != null) { + success.set(mappedResult); + } + } + + // Must map the underlying success Object to the appropriate type in Java + protected abstract T mapSuccess(Object result); + + @Override + public void onError(String nativeErrorCategory, int nativeErrorCode, String errorMessage) { + ErrorCode code = ErrorCode.fromNativeError(nativeErrorCategory, nativeErrorCode); + if (code == ErrorCode.UNKNOWN) { + // In case of UNKNOWN errors parse as much error information on as possible. + String detailedErrorMessage = String.format("{%s::%s} %s", nativeErrorCategory, nativeErrorCode, errorMessage); + error.set(new ObjectServerError(code, detailedErrorMessage)); + } else { + error.set(new ObjectServerError(code, errorMessage)); + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIVoidResultCallback.java similarity index 63% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBDatabase.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIVoidResultCallback.java index 8c420fadef..f7c11fb195 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIVoidResultCallback.java @@ -1,4 +1,4 @@ -/** +/* * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm.mongodb; -public class RealmMongoDBDatabase { +package io.realm.internal.jni; + +import java.util.concurrent.atomic.AtomicReference; + +public class OsJNIVoidResultCallback extends OsJNIResultCallback { + + public OsJNIVoidResultCallback(AtomicReference error) { + super(null, error); + } + + @Override + protected Void mapSuccess(Object result) { + return null; + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoClient.java new file mode 100644 index 0000000000..35afc92305 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoClient.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.objectstore; + +import io.realm.RealmUser; +import io.realm.internal.NativeObject; + +public class OsRemoteMongoClient implements NativeObject { + + private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); + + private final long nativePtr; + + public OsRemoteMongoClient(RealmUser realmUser, String serviceName) { + this.nativePtr = nativeCreate(realmUser.getApp().nativePtr, serviceName); + } + + public OsRemoteMongoDatabase getRemoteDatabase(String databaseName) { + long nativeDatabasePtr = nativeCreateDatabase(nativePtr, databaseName); + return new OsRemoteMongoDatabase(nativeDatabasePtr); + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + private static native long nativeCreate(long nativeAppPtr, String serviceName); + private static native long nativeCreateDatabase(long nativeAppPtr, String databaseName); + private static native long nativeGetFinalizerMethodPtr(); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoCollection.java new file mode 100644 index 0000000000..d8984b835c --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoCollection.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.objectstore; + +import io.realm.internal.NativeObject; + +public class OsRemoteMongoCollection implements NativeObject { + + private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); + + private final long nativePtr; + + OsRemoteMongoCollection(long nativeCollectionPtr) { + this.nativePtr = nativeCollectionPtr; + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + public void count(String filter) { + throw new UnsupportedOperationException("Not Implemented"); + } + + private static native long nativeGetFinalizerMethodPtr(); + private static native void nativeCount(long remoteMongoCollectionPtr, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback, + String filter); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoDatabase.java new file mode 100644 index 0000000000..fa1fe8d020 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoDatabase.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.objectstore; + +import io.realm.internal.NativeObject; + +public class OsRemoteMongoDatabase implements NativeObject { + + private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); + + private final long nativePtr; + + public OsRemoteMongoDatabase(long nativeDatabasePtr) { + this.nativePtr = nativeDatabasePtr; + } + + public OsRemoteMongoCollection getCollection(String collectionName) { + long nativeCollectionPtr = nativeGetCollection(nativePtr, collectionName); + return new OsRemoteMongoCollection(nativeCollectionPtr); + } + + // FIXME: what about this one? +// public RemoteMongoCollection getCollection( +// final String collectionName, +// final Class documentClass +// ) { +// throw new RuntimeException("Not implemented"); +// } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + private static native long nativeGetCollection(long nativeDatabasePtr, String collectionName); + private static native long nativeGetFinalizerMethodPtr(); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/MongoNamespace.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/MongoNamespace.java new file mode 100644 index 0000000000..2c7139b5d6 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/MongoNamespace.java @@ -0,0 +1,186 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.util.HashSet; +import java.util.Set; + +import static java.util.Arrays.asList; +import static org.bson.assertions.Assertions.isTrueArgument; +import static org.bson.assertions.Assertions.notNull; + +/** + * A MongoDB namespace, which includes a database name and collection name. + */ +public final class MongoNamespace { + public static final String COMMAND_COLLECTION_NAME = "$cmd"; + + private static final Set PROHIBITED_CHARACTERS_IN_DATABASE_NAME = + new HashSet(asList('\0', '/', '\\', ' ', '"', '.')); + + private final String databaseName; + private final String collectionName; + @BsonIgnore + private final String fullName; // cache to avoid repeated string building + + /** + * Check the validity of the given database name. A valid database name is non-null, non-empty, and does not contain any of the + * following characters: {@code '\0', '/', '\\', ' ', '"', '.'}. The server may impose additional restrictions on database names. + * + * @param databaseName the database name + * @throws IllegalArgumentException if the database name is invalid + */ + public static void checkDatabaseNameValidity(final String databaseName) { + notNull("databaseName", databaseName); + isTrueArgument("databaseName is not empty", !databaseName.isEmpty()); + for (int i = 0; i < databaseName.length(); i++) { + isTrueArgument("databaseName does not contain '" + databaseName.charAt(i) + "'", + !PROHIBITED_CHARACTERS_IN_DATABASE_NAME.contains(databaseName.charAt(i))); + } + } + + /** + * Check the validity of the given collection name. A valid collection name is non-null and non-empty. The server may impose + * additional restrictions on collection names. + * + * @param collectionName the collection name + * @throws IllegalArgumentException if the collection name is invalid + */ + public static void checkCollectionNameValidity(final String collectionName) { + notNull("collectionName", collectionName); + isTrueArgument("collectionName is not empty", !collectionName.isEmpty()); + } + + /** + * Construct an instance for the given full name. The database name is the string preceding the first {@code "."} character. + * + * @param fullName the non-null full namespace + * @see #checkDatabaseNameValidity(String) + * @see #checkCollectionNameValidity(String) + */ + public MongoNamespace(final String fullName) { + notNull("fullName", fullName); + this.fullName = fullName; + this.databaseName = getDatatabaseNameFromFullName(fullName); + this.collectionName = getCollectionNameFullName(fullName); + checkDatabaseNameValidity(databaseName); + checkCollectionNameValidity(collectionName); + } + + /** + * Construct an instance from the given database name and collection name. + * + * @param databaseName the valid database name + * @param collectionName the valid collection name + * @see #checkDatabaseNameValidity(String) + * @see #checkCollectionNameValidity(String) + */ + @BsonCreator + public MongoNamespace(@BsonProperty("db") final String databaseName, + @BsonProperty("coll") final String collectionName) { + checkDatabaseNameValidity(databaseName); + checkCollectionNameValidity(collectionName); + this.databaseName = databaseName; + this.collectionName = collectionName; + this.fullName = databaseName + '.' + collectionName; + } + + /** + * Gets the database name. + * + * @return the database name + */ + public String getDatabaseName() { + return databaseName; + } + + /** + * Gets the collection name. + * + * @return the collection name + */ + public String getCollectionName() { + return collectionName; + } + + /** + * Gets the full name, which is the database name and the collection name, separated by a period. + * + * @return the full name + */ + public String getFullName() { + return fullName; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MongoNamespace that = (MongoNamespace) o; + + if (!collectionName.equals(that.collectionName)) { + return false; + } + if (!databaseName.equals(that.databaseName)) { + return false; + } + + return true; + } + + /** + * Returns the standard MongoDB representation of a namespace, which is {@code <database>.<collection>}. + * + * @return string representation of the namespace. + */ + @Override + public String toString() { + return fullName; + } + + @Override + public int hashCode() { + int result = databaseName.hashCode(); + result = 31 * result + (collectionName.hashCode()); + return result; + } + + private static String getCollectionNameFullName(final String namespace) { + int firstDot = namespace.indexOf('.'); + if (firstDot == -1) { + return namespace; + } + return namespace.substring(firstDot + 1); + } + + private static String getDatatabaseNameFromFullName(final String namespace) { + int firstDot = namespace.indexOf('.'); + if (firstDot == -1) { + return ""; + } + return namespace.substring(0, firstDot); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoClient.java new file mode 100644 index 0000000000..5b6a7d0bb4 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoClient.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import io.realm.RealmUser; +import io.realm.internal.Util; +import io.realm.internal.objectstore.OsRemoteMongoClient; + +/** + * The remote MongoClient used for working with data in MongoDB remotely via Realm. + */ +public class RemoteMongoClient { + + private OsRemoteMongoClient osRemoteMongoClient; + + public RemoteMongoClient(RealmUser realmUser, String serviceName) { + Util.checkEmpty(serviceName, "serviceName"); + osRemoteMongoClient = new OsRemoteMongoClient(realmUser, serviceName); + } + + /** + * Gets a {@link RemoteMongoDatabase} instance for the given database name. + * + * @param databaseName the name of the database to retrieve + * @return a {@code RemoteMongoDatabase} representing the specified database + */ + public RemoteMongoDatabase getDatabase(final String databaseName) { + Util.checkEmpty(databaseName, "databaseName"); + return new RemoteMongoDatabase(osRemoteMongoClient.getRemoteDatabase(databaseName), databaseName); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoCollection.java new file mode 100644 index 0000000000..427bf99727 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoCollection.java @@ -0,0 +1,651 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import com.google.android.gms.tasks.Task; + +import org.bson.BsonDocument; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.conversions.Bson; + +import java.util.List; + +import io.realm.internal.objectstore.OsRemoteMongoCollection; +import io.realm.mongodb.remote.RemoteCountOptions; +import io.realm.mongodb.remote.RemoteFindOneAndModifyOptions; +import io.realm.mongodb.remote.RemoteUpdateOptions; +import io.realm.mongodb.remote.RemoteDeleteResult; +import io.realm.mongodb.remote.RemoteFindOptions; +import io.realm.mongodb.remote.RemoteInsertManyResult; +import io.realm.mongodb.remote.RemoteInsertOneResult; +import io.realm.mongodb.remote.RemoteUpdateResult; +import io.realm.mongodb.remote.aggregate.RemoteAggregateIterable; +import io.realm.mongodb.remote.find.RemoteFindIterable; + +/** + * The RemoteMongoCollection interface provides read and write access to documents. + *

              + * Use {@link RemoteMongoDatabase#getCollection} to get a collection instance. + *

              + * Before any access is possible, there must be an active, logged-in user. + *

              + * Create, read, update and delete (CRUD) functionality is available depending + * on the privileges of the active logged-in user. You can set up + * Roles + * in the Stitch console. Stitch checks any given request against the Roles for the + * active user and determines whether the request is permitted for each requested + * document. + *

              + * + * @param The type that this collection will encode documents from and decode documents + * to. + * @see RemoteMongoDatabase + * @see + * MongoDB Atlas Overview with Stitch + */ +public class RemoteMongoCollection { + + private OsRemoteMongoCollection osRemoteMongoCollection; + + public RemoteMongoCollection(OsRemoteMongoCollection osRemoteMongoCollection) { + this.osRemoteMongoCollection = osRemoteMongoCollection; + } + + /** + * Gets the namespace of this collection, i.e. the database and collection names together. + * + * @return the namespace + */ + MongoNamespace getNamespace() { + throw new RuntimeException("Not Implemented"); + } + + /** + * Get the class of documents stored in this collection. + *

              + * If you used the simple {@link RemoteMongoDatabase#getCollection(String)} to get + * this collection, + * this is {@link org.bson.Document}. + *

              + * + * @return the class + */ + Class getDocumentClass() { + throw new RuntimeException("Not Implemented"); + } + + /** + * Get the codec registry for the RemoteMongoCollection. + * + * @return the {@link CodecRegistry} + */ + CodecRegistry getCodecRegistry() { + throw new RuntimeException("Not Implemented"); + } + + /** + * Create a new RemoteMongoCollection instance with a different default class to cast any + * documents returned from the database into. + * + * @param clazz the default class to cast any documents returned from the database into. + * @param The type that the new collection will encode documents from and decode + * documents to. + * @return a new RemoteMongoCollection instance with the different default class + */ + RemoteMongoCollection withDocumentClass( + final Class clazz) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Create a new RemoteMongoCollection instance with a different codec registry. + * + * @param codecRegistry the new {@link CodecRegistry} for the + * collection. + * @return a new RemoteMongoCollection instance with the different codec registry + */ + RemoteMongoCollection withCodecRegistry(final CodecRegistry codecRegistry) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Counts the number of documents in the collection. + * + * @return a task containing the number of documents in the collection + */ + Task count() { + throw new RuntimeException("Not Implemented"); + } + + /** + * Counts the number of documents in the collection according to the given options. + * + * @param filter the query filter + * @return a task containing the number of documents in the collection + */ + Task count(final Bson filter) { + BsonDocument bsonDocument = filter.toBsonDocument(null, null); + osRemoteMongoCollection.count(bsonDocument.toJson()); + throw new RuntimeException("Not Implemented"); + } + + /** + * Counts the number of documents in the collection according to the given options. + * + * @param filter the query filter + * @param options the options describing the count + * @return a task containing the number of documents in the collection + */ + Task count(final Bson filter, final RemoteCountOptions options) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection. + * + * @return a task containing the result of the find one operation + */ + Task findOne() { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection. + * + * @param resultClass the class to decode each document into + * @param the target document type + * @return a task containing the result of the find one operation + */ + Task findOne(final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection. + * + * @param filter the query filter + * @return a task containing the result of the find one operation + */ + Task findOne(final Bson filter) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection. + * + * @param filter the query filter + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return a task containing the result of the find one operation + */ + Task findOne(final Bson filter, final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection. + * + * @param filter the query filter + * @param options A RemoteFindOptions struct + * @return a task containing the result of the find one operation + */ + Task findOne(final Bson filter, final RemoteFindOptions options) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection. + * + * @param filter the query filter + * @param options A RemoteFindOptions struct + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return a task containing the result of the find one operation + */ + Task findOne( + final Bson filter, + final RemoteFindOptions options, + final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds all documents in the collection. + * + * @return the find iterable interface + */ + RemoteFindIterable find() { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds all documents in the collection. + * + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return the find iterable interface + */ + RemoteFindIterable find(final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds all documents in the collection that match the given filter. + * + * @param filter the query filter + * @return the find iterable interface + */ + RemoteFindIterable find(final Bson filter) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds all documents in the collection that match the given filter. + * + * @param filter the query filter + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return the find iterable interface + */ + RemoteFindIterable find(final Bson filter, final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + + /** + * Aggregates documents according to the specified aggregation pipeline. + * + * @param pipeline the aggregation pipeline + * @return an iterable containing the result of the aggregation operation + */ + RemoteAggregateIterable aggregate(final List pipeline) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Aggregates documents according to the specified aggregation pipeline. + * + * @param pipeline the aggregation pipeline + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return an iterable containing the result of the aggregation operation + */ + RemoteAggregateIterable aggregate( + final List pipeline, + final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Inserts the provided document. If the document is missing an identifier, the client should + * generate one. + * + * @param document the document to insert + * @return a task containing the result of the insert one operation + */ + Task insertOne(final DocumentT document) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Inserts one or more documents. + * + * @param documents the documents to insert + * @return a task containing the result of the insert many operation + */ + Task insertMany(final List documents) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Removes at most one document from the collection that matches the given filter. If no + * documents match, the collection is not + * modified. + * + * @param filter the query filter to apply the the delete operation + * @return a task containing the result of the remove one operation + */ + Task deleteOne(final Bson filter) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Removes all documents from the collection that match the given query filter. If no documents + * match, the collection is not modified. + * + * @param filter the query filter to apply the the delete operation + * @return a task containing the result of the remove many operation + */ + Task deleteMany(final Bson filter) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Update a single document in the collection according to the specified arguments. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to + * apply must include only update operators. + * @return a task containing the result of the update one operation + */ + Task updateOne(final Bson filter, final Bson update) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Update a single document in the collection according to the specified arguments. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to + * apply must include only update operators. + * @param updateOptions the options to apply to the update operation + * @return a task containing the result of the update one operation + */ + Task updateOne( + final Bson filter, + final Bson update, + final RemoteUpdateOptions updateOptions) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Update all documents in the collection according to the specified arguments. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to + * apply must include only update operators. + * @return a task containing the result of the update many operation + */ + Task updateMany(final Bson filter, final Bson update) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Update all documents in the collection according to the specified arguments. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a document describing the update, which may not be null. The update to + * apply must include only update operators. + * @param updateOptions the options to apply to the update operation + * @return a task containing the result of the update many operation + */ + Task updateMany( + final Bson filter, + final Bson update, + final RemoteUpdateOptions updateOptions) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and performs the given update. + * + * @param filter the query filter + * @param update the update document + * @return a task containing the resulting document + */ + Task findOneAndUpdate(final Bson filter, final Bson update) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and performs the given update. + * + * @param filter the query filter + * @param update the update document + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return a task containing the resulting document + */ + Task findOneAndUpdate(final Bson filter, + final Bson update, + final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and performs the given update. + * + * @param filter the query filter + * @param update the update document + * @param options A RemoteFindOneAndModifyOptions struct + * @return a task containing the resulting document + */ + Task findOneAndUpdate(final Bson filter, + final Bson update, + final RemoteFindOneAndModifyOptions options) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and performs the given update. + * + * @param filter the query filter + * @param update the update document + * @param options A RemoteFindOneAndModifyOptions struct + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return a task containing the resulting document + */ + Task findOneAndUpdate( + final Bson filter, + final Bson update, + final RemoteFindOneAndModifyOptions options, + final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and replaces it with the given document. + * + * @param filter the query filter + * @param replacement the document to replace the matched document with + * @return a task containing the resulting document + */ + Task findOneAndReplace(final Bson filter, final Bson replacement) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and replaces it with the given document. + * + * @param filter the query filter + * @param replacement the document to replace the matched document with + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return a task containing the resulting document + */ + Task findOneAndReplace(final Bson filter, + final Bson replacement, + final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and replaces it with the given document. + * + * @param filter the query filter + * @param replacement the document to replace the matched document with + * @param options A RemoteFindOneAndModifyOptions struct + * @return a task containing the resulting document + */ + Task findOneAndReplace(final Bson filter, + final Bson replacement, + final RemoteFindOneAndModifyOptions options) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and replaces it with the given document. + * + * @param filter the query filter + * @param replacement the document to replace the matched document with + * @param options A RemoteFindOneAndModifyOptions struct + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return a task containing the resulting document + */ + Task findOneAndReplace( + final Bson filter, + final Bson replacement, + final RemoteFindOneAndModifyOptions options, + final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and delete it. + * + * @param filter the query filter + * @return a task containing the resulting document + */ + Task findOneAndDelete(final Bson filter) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and delete it. + * + * @param filter the query filter + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return a task containing the resulting document + */ + Task findOneAndDelete(final Bson filter, + final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and delete it. + * + * @param filter the query filter + * @param options A RemoteFindOneAndModifyOptions struct + * @return a task containing the resulting document + */ + Task findOneAndDelete(final Bson filter, + final RemoteFindOneAndModifyOptions options) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Finds a document in the collection and delete it. + * + * @param filter the query filter + * @param options A RemoteFindOneAndModifyOptions struct + * @param resultClass the class to decode each document into + * @param the target document type of the iterable. + * @return a task containing the resulting document + */ + Task findOneAndDelete( + final Bson filter, + final RemoteFindOneAndModifyOptions options, + final Class resultClass) { + throw new RuntimeException("Not Implemented"); + } + + // FIXME: what about these? +// /** +// * Watches a collection. The resulting stream will be notified of all events on this collection +// * that the active user is authorized to see based on the configured MongoDB rules. +// * +// * @return the stream of change events. +// */ +// Task>> watch(); +// +// /** +// * Watches specified IDs in a collection. This convenience overload supports the use case +// * of non-{@link BsonValue} instances of {@link ObjectId}. +// * +// * @param ids unique object identifiers of the IDs to watch. +// * @return the stream of change events. +// */ +// Task>> watch(final ObjectId... ids); +// +// /** +// * Watches specified IDs in a collection. +// * +// * @param ids the ids to watch. +// * @return the stream of change events. +// */ +// Task>> watch(final BsonValue... ids); +// +// /** +// * Watches a collection. The provided BSON document will be used as a match expression filter on +// * the change events coming from the stream. +// * See https://docs.mongodb.com/manual/reference/operator/aggregation/match/ for documentation +// * around how to define a match filter. Defining the match expression to filter ChangeEvents is +// * similar to defining the match expression for triggers: +// * https://docs.mongodb.com/stitch/triggers/database-triggers/ +// * +// * @param matchFilter the $match filter to apply to incoming change events +// * @return the stream of change events. +// */ +// Task>> watchWithFilter( +// final BsonDocument matchFilter); +// +// /** +// * Watches a collection. The provided BSON document will be used as a match expression filter on +// * the change events coming from the stream. +// * See https://docs.mongodb.com/manual/reference/operator/aggregation/match/ for documentation +// * around how to define a match filter. Defining the match expression to filter ChangeEvents is +// * similar to defining the match expression for triggers: +// * https://docs.mongodb.com/stitch/triggers/database-triggers/ +// * +// * @param matchFilter the $match filter to apply to incoming change events +// * @return the stream of change events. +// */ +// Task>> watchWithFilter( +// final Document matchFilter); +// +// /** +// * Watches specified IDs in a collection. This convenience overload supports the use case +// * of non-{@link BsonValue} instances of {@link ObjectId}. This convenience overload supports the +// * use case of non-{@link BsonValue} instances of {@link ObjectId}. Requests a stream where the +// * full document of update events, and several other unnecessary fields are omitted from the +// * change event objects returned by the server. This can save on network usage when watching +// * large documents. +// * +// * @param ids unique object identifiers of the IDs to watch. +// * @return the stream of change events. +// */ +// Task>> watchCompact( +// final ObjectId... ids); +// +// /** +// * Watches specified IDs in a collection. This convenience overload supports the use case of +// * non-{@link BsonValue} instances of {@link ObjectId}. Requests a stream where the full document +// * of update events, and several other unnecessary fields are omitted from the change event +// * objects returned by the server. This can save on network usage when watching large documents. +// * +// * @param ids the ids to watch. +// * @return the stream of change events. +// */ +// Task>> watchCompact( +// final BsonValue... ids); + + // FIXME: what about this one? +// /** +// * A set of synchronization related operations on this collection. +// * +// *

              +// * WARNING: This is a BETA feature and the API and on-device storage format +// * are subject to change. +// *

              +// * @return set of sync operations for this collection +// */ +// Sync sync(); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoDatabase.java new file mode 100644 index 0000000000..ad922e8cc0 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoDatabase.java @@ -0,0 +1,77 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import org.bson.Document; + +import io.realm.internal.Util; +import io.realm.internal.objectstore.OsRemoteMongoDatabase; + +/** + * The RemoteMongoDatabase provides access to its {@link Document} {@link RemoteMongoCollection}s. + */ +public class RemoteMongoDatabase { + + private String databaseName; + private OsRemoteMongoDatabase osRemoteMongoDatabase; + + RemoteMongoDatabase(OsRemoteMongoDatabase osRemoteMongoDatabase, String databaseName) { + // we deliver the database name because we don't want to modify the C++ code right now, + // although ideally it should be done there, i.e. remote_mongo_database.hpp should + // include the public (Java) API's methods that aren't there yet. + this.databaseName = databaseName; + this.osRemoteMongoDatabase = osRemoteMongoDatabase; + } + + /** + * Gets the name of the database. + * + * @return the database name + */ + String getName() { + return databaseName; + } + + /** + * Gets a collection. + * + * @param collectionName the name of the collection to return + * @return the collection + */ + RemoteMongoCollection getCollection(final String collectionName) { + Util.checkEmpty(collectionName, "collectionName"); + return new RemoteMongoCollection<>(osRemoteMongoDatabase.getCollection(collectionName)); + } + + // FIXME: what about this one? +// /** +// * Gets a collection, with a specific default document class. +// * +// * @param collectionName the name of the collection to return +// * @param documentClass the default class to cast any documents returned from the database into. +// * @param the type of the class to use instead of {@code Document}. +// * @return the collection +// */ +// RemoteMongoCollection getCollection( +// final String collectionName, +// final Class documentClass +// ) { +// Util.checkEmpty(collectionName, "collectionName"); +// Util.checkNull(documentClass, "documentClass"); +// return osRemoteMongoDatabase.getCollection(collectionName, documentClass); +// } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteCountOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteCountOptions.java new file mode 100644 index 0000000000..1a751811a1 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteCountOptions.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.remote; + +/** + * The options for a count operation. + */ +public class RemoteCountOptions { + private int limit; + + /** + * Gets the limit to apply. The default is 0, which means there is no limit. + * + * @return the limit + */ + public int getLimit() { + return limit; + } + + /** + * Sets the limit to apply. + * + * @param limit the limit + * @return this + */ + public RemoteCountOptions limit(final int limit) { + this.limit = limit; + return this; + } + + @Override + public String toString() { + return "RemoteCountOptions{" + + "limit=" + limit + + '}'; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBService.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteDeleteResult.java similarity index 51% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBService.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteDeleteResult.java index dbc97c5d22..f394ad1208 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBService.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteDeleteResult.java @@ -1,4 +1,4 @@ -/** +/* * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm.mongodb; -public class RealmMongoDBService { +package io.realm.mongodb.remote; + +/** + * The result of a delete operation. + */ +public class RemoteDeleteResult { + + private final long deletedCount; + + /** + * Constructs a result. + * + * @param deletedCount the number of documents deleted. + */ + public RemoteDeleteResult(final long deletedCount) { + this.deletedCount = deletedCount; + } + + /** + * Gets the number of documents deleted. + * + * @return the number of documents deleted + */ + public long getDeletedCount() { + return deletedCount; + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOneAndModifyOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOneAndModifyOptions.java new file mode 100644 index 0000000000..dcf12db4fc --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOneAndModifyOptions.java @@ -0,0 +1,134 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.remote; + +import javax.annotation.Nullable; + +import org.bson.conversions.Bson; + +/** + * The options to apply to a findOneAndUpdate, findOneAndReplace, or findOneAndDelete operation + * (also commonly referred to as findOneAndModify operations). + */ +public class RemoteFindOneAndModifyOptions { + private Bson projection; + private Bson sort; + private boolean upsert; + private boolean returnNewDocument; + + /** + * Gets a document describing the fields to return for all matching documents. + * + * @return the project document, which may be null + */ + @Nullable + public Bson getProjection() { + return projection; + } + + /** + * Sets a document describing the fields to return for all matching documents. + * + * @param projection the project document, which may be null. + * @return this + */ + public RemoteFindOneAndModifyOptions projection(@Nullable final Bson projection) { + this.projection = projection; + return this; + } + + /** + * Gets the sort criteria to apply to the query. The default is null, which means that the + * documents will be returned in an undefined order. + * + * @return a document describing the sort criteria + */ + @Nullable + public Bson getSort() { + return sort; + } + + /** + * Sets the sort criteria to apply to the query. + * + * @param sort the sort criteria, which may be null. + * @return this + */ + public RemoteFindOneAndModifyOptions sort(@Nullable final Bson sort) { + this.sort = sort; + return this; + } + + /** + * Returns true if a new document should be inserted if there are no matches to the query filter. + * The default is false. + * Note: Only findOneAndUpdate and findOneAndReplace take this option + * + * @return true if a new document should be inserted if there are no matches to the query filter + */ + public boolean isUpsert() { + return upsert; + } + + /** + * Set to true if a new document should be inserted if there are no matches to the query filter. + * + * @param upsert true if a new document should be inserted if there are no matches to the query + * filter. + * @return this + */ + public RemoteFindOneAndModifyOptions upsert(final boolean upsert) { + this.upsert = upsert; + return this; + } + + /** + * Returns true if the findOneAndModify operation should return the new document. + * The default is false + * Note: Only findOneAndUpdate and findOneAndReplace take this options + * findOneAndDelete will always return the old document + * + * @return true if findOneAndModify operation should return the new document + */ + public boolean isReturnNewDocument() { + return returnNewDocument; + } + + /** + * Set to true if findOneAndModify operations should return the new updated document. + * Set to false / leave blank to have these operation return the document before the update. + * Note: Only findOneAndUpdate and findOneAndReplace take this options + * findOneAndDelete will always return the old document + * + * @param returnNewDocument true if findOneAndModify operations should return the updated document + * @return this + */ + public RemoteFindOneAndModifyOptions returnNewDocument(final boolean returnNewDocument) { + this.returnNewDocument = returnNewDocument; + return this; + } + + @Override + public String toString() { + return "RemoteFindOneAndModifyOptions{" + + "projection=" + projection + + ", sort=" + sort + + ", upsert=" + upsert + + ", returnNewDocument=" + returnNewDocument + + "}"; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOptions.java new file mode 100644 index 0000000000..87d9dbba17 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOptions.java @@ -0,0 +1,108 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.remote; + +import javax.annotation.Nullable; + +import org.bson.conversions.Bson; + +/** + * The options to apply to a find operation (also commonly referred to as a query). + */ +public class RemoteFindOptions { + private int limit; + private Bson projection; + private Bson sort; + + /** + * Construct a new instance. + */ + public RemoteFindOptions() { + } + + /** + * Gets the limit to apply. The default is null. + * + * @return the limit + */ + public int getLimit() { + return limit; + } + + /** + * Sets the limit to apply. + * + * @param limit the limit, which may be null + * @return this + */ + public RemoteFindOptions limit(final int limit) { + this.limit = limit; + return this; + } + + /** + * Gets a document describing the fields to return for all matching documents. + * + * @return the project document, which may be null + */ + @Nullable + public Bson getProjection() { + return projection; + } + + /** + * Sets a document describing the fields to return for all matching documents. + * + * @param projection the project document, which may be null. + * @return this + */ + public RemoteFindOptions projection(@Nullable final Bson projection) { + this.projection = projection; + return this; + } + + /** + * Gets the sort criteria to apply to the query. The default is null, which means that the + * documents will be returned in an undefined order. + * + * @return a document describing the sort criteria + */ + @Nullable + public Bson getSort() { + return sort; + } + + /** + * Sets the sort criteria to apply to the query. + * + * @param sort the sort criteria, which may be null. + * @return this + */ + public RemoteFindOptions sort(@Nullable final Bson sort) { + this.sort = sort; + return this; + } + + @Override + public String toString() { + return "RemoteFindOptions{" + + "limit=" + limit + + ", projection=" + projection + + ", sort=" + sort + + "}"; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertManyResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertManyResult.java new file mode 100644 index 0000000000..44d7a85362 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertManyResult.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.remote; + +import java.util.Map; + +import org.bson.BsonValue; + +/** + * The result of an insert many operation. + */ +public class RemoteInsertManyResult { + + private final Map insertedIds; + + /** + * Constructs a result. + * + * @param insertedIds the _ids of the inserted documents arranged by the index of the document + * from the operation and its corresponding id. + */ + public RemoteInsertManyResult(final Map insertedIds) { + this.insertedIds = insertedIds; + } + + /** + * Returns the _ids of the inserted documents arranged by the index of the document from the + * operation and its corresponding id. + * + * @return the _ids of the inserted documents arranged by the index of the document from the + * operation and its corresponding id. + */ + public Map getInsertedIds() { + return insertedIds; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertOneResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertOneResult.java new file mode 100644 index 0000000000..d24e013b9f --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertOneResult.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.remote; + +import org.bson.BsonValue; + +/** + * The result of an insert one operation. + */ +public class RemoteInsertOneResult { + + private final BsonValue insertedId; + + /** + * Constructs a result. + * + * @param insertedId the _id of the inserted document. + */ + public RemoteInsertOneResult(final BsonValue insertedId) { + this.insertedId = insertedId; + } + + /** + * Returns the _id of the inserted document. + * + * @return the _id of the inserted document. + */ + public BsonValue getInsertedId() { + return insertedId; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateOptions.java new file mode 100644 index 0000000000..5bc2834043 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateOptions.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.remote; + +/** + * The options to apply when updating documents. + */ +public class RemoteUpdateOptions { + private boolean upsert; + + /** + * Returns true if a new document should be inserted if there are no matches to the query filter. + * The default is false. + * + * @return true if a new document should be inserted if there are no matches to the query filter + */ + public boolean isUpsert() { + return upsert; + } + + /** + * Set to true if a new document should be inserted if there are no matches to the query filter. + * + * @param upsert true if a new document should be inserted if there are no matches to the query + * filter. + * @return this + */ + public RemoteUpdateOptions upsert(final boolean upsert) { + this.upsert = upsert; + return this; + } + + @Override + public String toString() { + return "RemoteUpdateOptions{" + + "upsert=" + upsert + + '}'; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateResult.java new file mode 100644 index 0000000000..48840d88c7 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateResult.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.remote; + +import javax.annotation.Nullable; + +import org.bson.BsonValue; + +/** + * The result of an update operation. + */ +public class RemoteUpdateResult { + + private final long matchedCount; + private final long modifiedCount; + private final BsonValue upsertedId; + + /** + * Constructs a result. + * + * @param matchedCount the number of documents matched by the query. + * @param modifiedCount the number of documents modified. + * @param upsertedId the _id of the inserted document if the replace resulted in an inserted + * document, otherwise null. + */ + public RemoteUpdateResult( + final long matchedCount, + final long modifiedCount, + final BsonValue upsertedId + ) { + this.matchedCount = matchedCount; + this.modifiedCount = modifiedCount; + this.upsertedId = upsertedId; + } + + /** + * Returns the number of documents matched by the query. + * + * @return the number of documents matched. + */ + public long getMatchedCount() { + return matchedCount; + } + + /** + * Returns the number of documents modified. + * + * @return the number of documents modified. + */ + public long getModifiedCount() { + return modifiedCount; + } + + /** + * If the replace resulted in an inserted document, gets the _id of the inserted document, + * otherwise null. + * + * @return if the replace resulted in an inserted document, the _id of the inserted document, + * otherwise null. + */ + @Nullable + public BsonValue getUpsertedId() { + return upsertedId; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/aggregate/RemoteAggregateIterable.java similarity index 64% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBCollection.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/aggregate/RemoteAggregateIterable.java index 2a509db842..c08e0da636 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmMongoDBCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/aggregate/RemoteAggregateIterable.java @@ -1,4 +1,4 @@ -/** +/* * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm.mongodb; -public class RealmMongoDBCollection { +package io.realm.mongodb.remote.aggregate; + +/** + * Iterable for aggregate. + * + * @param The type of the result. + */ +// TODO: figure out whether or not we need the parent interface +//public interface RemoteAggregateIterable extends RemoteMongoIterable { +public interface RemoteAggregateIterable { + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/find/RemoteFindIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/find/RemoteFindIterable.java new file mode 100644 index 0000000000..5b6e448d78 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/find/RemoteFindIterable.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.remote.find; + +import org.bson.conversions.Bson; + +import javax.annotation.Nullable; + +/** + * Iterable for find. + * + * @param The type of the result. + */ +// TODO: figure out whether or not we need the parent interface +//public interface RemoteFindIterable extends RemoteMongoIterable { +public class RemoteFindIterable { + + /** + * Sets the query filter to apply to the query. + * + * @param filter the filter, which may be null. + * @return this + */ + RemoteFindIterable filter(@Nullable final Bson filter) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Sets the limit to apply. + * + * @param limit the limit, which may be 0 + * @return this + */ + RemoteFindIterable limit(final int limit) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Sets a document describing the fields to return for all matching documents. + * + * @param projection the project document, which may be null. + * @return this + */ + RemoteFindIterable projection(@Nullable final Bson projection) { + throw new RuntimeException("Not Implemented"); + } + + /** + * Sets the sort criteria to apply to the query. + * + * @param sort the sort criteria, which may be null. + * @return this + */ + RemoteFindIterable sort(@Nullable final Bson sort) { + throw new RuntimeException("Not Implemented"); + } +} From a377ead6029285e3a050e428c0ddfbc946909e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 1 May 2020 18:08:43 +0200 Subject: [PATCH 1499/2110] Updated OS pointer to v10 and sync to alpha 11 (#6825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eduardo López --- dependencies.list | 5 ++--- realm/realm-library/src/main/cpp/object-store | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dependencies.list b/dependencies.list index d93bff39f7..a1bf8fe044 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.8 -REALM_SYNC_SHA256=2cdccdd43f1cb0c0d7297e9616824efa34be62deefd311d722ecee88d8a6e44e +REALM_SYNC_VERSION=10.0.0-alpha.11 +REALM_SYNC_SHA256=e86b07dc4854e4eddb85589c2a6c4852e3b06146957ec9ecfc343db573a92b4b # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. @@ -24,4 +24,3 @@ GRADLE_BINTRAY_PLUGIN=1.8.4 # Bson dependency version BSON_DEPENDENCY_VERSION=3.12.1 - diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index d2a4126bd3..8e6923c09e 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit d2a4126bd317415edb9ee0addcae67aa0b667d6d +Subproject commit 8e6923c09efd6b8d94e6ccc8e07c9ab4382473ee From 0adc56b13955c4f923c803acff28dcba0653cce2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 4 May 2020 09:17:39 +0200 Subject: [PATCH 1500/2110] Upgrade to Sync 10.0.0-alpha.12 (#6820) --- Dockerfile | 1 + Jenkinsfile | 2 +- dependencies.list | 6 +++--- examples/build.gradle | 4 ++-- realm/realm-annotations-processor/build.gradle | 2 +- realm/realm-library/build.gradle | 2 +- .../kotlin/io/realm/EmailPasswordAuthTests.kt | 1 - .../kotlin/io/realm/KotlinSyncedRealmTests.kt | 17 ++++++++++++----- .../kotlin/io/realm/entities/SyncColor.kt | 2 +- .../transport/OsJavaNetworkTransportTests.kt | 8 ++++++++ realm/realm-library/src/main/cpp/CMakeLists.txt | 4 ++-- .../java/io/realm/SyncTestUtils.kt | 13 ++++++++++--- 12 files changed, 42 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4d7888da18..c14bc38961 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ ENV ANDROID_NDK /opt/android-ndk ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} ENV NDK_CCACHE /usr/bin/ccache +ENV CCACHE_CPP2 yes # The 32 bit binaries because aapt requires it # `file` is need by the script that creates NDK toolchains diff --git a/Jenkinsfile b/Jenkinsfile index 70981cb561..913cc53e50 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,7 +9,7 @@ def dockerNetworkId = UUID.randomUUID().toString() def releaseBranches = ['master', 'next-major', 'v10'] // Branches from which we release SNAPSHOT's def currentBranch = env.CHANGE_BRANCH try { - node('android') { + node('docker-cph-01') { // FIXME: Only working Slave timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { diff --git a/dependencies.list b/dependencies.list index a1bf8fe044..2367d7701f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.11 -REALM_SYNC_SHA256=e86b07dc4854e4eddb85589c2a6c4852e3b06146957ec9ecfc343db573a92b4b +REALM_SYNC_VERSION=10.0.0-alpha.12 +REALM_SYNC_SHA256=d9ab40f7b73b4438c811adfbbb22ed8da1461d922e77bb46f555f49ae1921748 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. @@ -9,7 +9,7 @@ REALM_OBJECT_SERVER_VERSION=3.28.2 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-04-26 +MONGODB_REALM_SERVER_VERSION=2020-04-30 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/examples/build.gradle b/examples/build.gradle index ae31565c21..04c40a67c5 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,7 +1,7 @@ def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) -project.ext.sdkVersion = 27 -project.ext.minSdkVersion = 21 // FIXME: Should be 16. Figure out how to enable MultiDex for ObjectServer tests +project.ext.sdkVersion = 29 +project.ext.minSdkVersion = 16 project.ext.buildTools = projectDependencies.get("ANDROID_BUILD_TOOLS") // Don't cache SNAPSHOT (changing) dependencies. diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 53c696aa10..21734dbcd4 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -19,7 +19,7 @@ dependencies { testImplementation files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it testImplementation group:'junit', name:'junit', version:'4.12' testImplementation group:'com.google.testing.compile', name:'compile-testing', version:'0.6' - testImplementation files(file("${System.env.ANDROID_HOME}/platforms/android-27/android.jar")) + testImplementation files(file("${System.env.ANDROID_HOME}/platforms/android-29/android.jar")) } // for Ant filter diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 55609f16fa..ec477f4204 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -212,7 +212,7 @@ dependencies { exclude group: 'io.reactivex.rxjava2', module: 'rxjava' } // FIXME: Attempt to find a way to remove this dependency - implementation "com.google.android.gms:play-services-tasks:17.0.2" // added to support mongo client's asynchronous nature without breaking Stitch's API + objectServerImplementation "com.google.android.gms:play-services-tasks:17.0.2" // added to support mongo client's asynchronous nature without breaking Stitch's API // TODO: investigate why we can't use the latest multidex version // check baseDebugAndroidTestRuntimeClasspath and objectServerDebugAndroidTestRuntimeClasspath diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index 879ea815d6..80a383a5ec 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -71,7 +71,6 @@ class EmailPasswordAuthTests { if (this::app.isInitialized) { app.close() } - admin.deleteAllUsers() RealmLog.setLevel(LogLevel.WARN) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt index 912df9a38d..d2528710fe 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt @@ -68,12 +68,11 @@ class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaini } // Smoke test for Sync - @Ignore("Dev Mode doesn't work fully yet on the server") @Test fun roundTripObjectsNotInServerSchemaObject() { // User 1 creates an object an uploads it to MongoDB Realm val user1: RealmUser = createNewUser() - val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + val config1: SyncConfiguration = createCustomConfig(user1, partitionValue) realm = Realm.getInstance(config1) realm.executeTransaction { for (i in 1..10) { @@ -86,9 +85,10 @@ class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaini // User 2 logs and using the same partition key should see the object val user2: RealmUser = createNewUser() - val config2 = createDefaultConfig(user2, partitionValue) + val config2 = createCustomConfig(user2, partitionValue) realm = Realm.getInstance(config2) - app.syncManager.getSession(config2).downloadAllServerChanges() + realm.syncSession.downloadAllServerChanges() + realm.refresh() assertEquals(10, realm.where().count()) } @@ -122,6 +122,7 @@ class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaini val config2 = createDefaultConfig(user2, partitionValue) realm = Realm.getInstance(config2) realm.syncSession.downloadAllServerChanges() + realm.refresh() assertEquals(10, realm.where().count()) assertEquals(1, realm.where().count()) } @@ -159,6 +160,7 @@ class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaini val config2 = createDefaultConfig(user2, partitionValue) realm = Realm.getInstance(config2) realm.syncSession.downloadAllServerChanges() + realm.refresh() assertEquals(10, realm.where().count()) assertEquals(1, realm.where().count()) } @@ -174,11 +176,16 @@ class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaini private fun createDefaultConfig(user: RealmUser, partitionValue: String = defaultPartitionValue): SyncConfiguration { return SyncConfiguration.Builder(user, partitionValue) - .waitForInitialRemoteData() // FIXME: This should not be required .modules(DefaultSyncSchema()) .build() } + private fun createCustomConfig(user: RealmUser, partitionValue: String = defaultPartitionValue): SyncConfiguration { + return SyncConfiguration.Builder(user, partitionValue) + .schema(SyncColor::class.java) + .build() + } + private fun createNewUser(): RealmUser { val email = TestHelper.getRandomEmail() val password = "123456" diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncColor.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncColor.kt index 4e2108806b..61ea5a1f71 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncColor.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncColor.kt @@ -24,7 +24,7 @@ import org.bson.types.ObjectId // FIXME: This class is just temporary as a smoke test for Sync. Should be removed once all Sync tests have been migrated. open class SyncColor: RealmObject() { @PrimaryKey - var _id: ObjectId = ObjectId.get() + var _id: ObjectId? = ObjectId.get() @RealmField(name = "realm_id") var realmId: String? = null var color: String = Color.RED.toString() diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt index 003f6169ec..0a7623e257 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -87,6 +87,14 @@ class OsJavaNetworkTransportTests { ] } """.trimIndent() + } else if (url.endsWith("/location")) { + return Response.httpResponse(200, mapOf(), """ + { "deployment_model" : "GLOBAL", + "location": "US-VA", + "hostname": "http://localhost:9090", + "ws_hostname": "ws://localhost:9090" + } + """.trimIndent()) } else { fail("Unexpected request url: $url") } diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index f7e22281ab..6e6a44cec7 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -228,8 +228,8 @@ if (build_SYNC) "object-store/src/results.cpp" "object-store/src/impl/results_notifier.cpp" "object-store/src/sync/*.cpp" - "object-store/src/sync/impl/*.cpp" - "object-store/src/util/bson/*.cpp") + "object-store/src/util/bson/*.cpp" + "object-store/src/sync/impl/*.cpp") endif() add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt index d9aac2c845..574a75df7c 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt @@ -89,8 +89,7 @@ class SyncTestUtils { "user_id": "$userIdentifier", "device_id": "000000000000000000000000" } - """.trimIndent()) - + """.trimIndent()) } else if (url.endsWith("/auth/profile")) { return Response.httpResponse(200, mapOf(), """ { @@ -117,7 +116,15 @@ class SyncTestUtils { } ] } - """.trimIndent()) + """.trimIndent()) + } else if (url.endsWith("/location")) { + return Response.httpResponse(200, mapOf(), """ + { "deployment_model" : "GLOBAL", + "location": "US-VA", + "hostname": "http://localhost:9090", + "ws_hostname": "ws://localhost:9090" + } + """.trimIndent()) } else { throw IllegalStateException("Unsupported URL: $url") } From 4cdd2f2a0a620ad0b1aa9919d5f08a6699947de0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 4 May 2020 11:27:09 +0200 Subject: [PATCH 1501/2110] Fix SchemaChangedCallback tests (#6830) --- .../io/realm/internal/OsSharedRealmTests.java | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java index a9ca9a61c8..4fbd0ffbe7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java @@ -137,7 +137,7 @@ public void renameTable_tableNotExist() { private void changeSchemaByAnotherRealm() { OsSharedRealm sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); sharedRealm.beginTransaction(); - sharedRealm.createTable("NewTable"); + sharedRealm.createTable("class_NewTable"); sharedRealm.commitTransaction(); sharedRealm.close(); } @@ -146,12 +146,12 @@ private void changeSchemaByAnotherRealm() { public void registerSchemaChangedCallback_beginTransaction() { final AtomicBoolean listenerCalled = new AtomicBoolean(false); - assertFalse(sharedRealm.hasTable("NewTable")); + assertFalse(sharedRealm.hasTable("class_NewTable")); sharedRealm.registerSchemaChangedCallback(new OsSharedRealm.SchemaChangedCallback() { @Override public void onSchemaChanged() { - assertTrue(sharedRealm.hasTable("NewTable")); + assertTrue(sharedRealm.hasTable("class_NewTable")); listenerCalled.set(true); } }); @@ -164,12 +164,12 @@ public void onSchemaChanged() { public void registerSchemaChangedCallback_refresh() { final AtomicBoolean listenerCalled = new AtomicBoolean(false); - assertFalse(sharedRealm.hasTable("NewTable")); + assertFalse(sharedRealm.hasTable("class_NewTable")); sharedRealm.registerSchemaChangedCallback(new OsSharedRealm.SchemaChangedCallback() { @Override public void onSchemaChanged() { - assertTrue(sharedRealm.hasTable("NewTable")); + assertTrue(sharedRealm.hasTable("class_NewTable")); listenerCalled.set(true); } }); @@ -178,6 +178,33 @@ public void onSchemaChanged() { assertTrue(listenerCalled.get()); } + // Test for https://github.com/realm/realm-core/issues/3707 + @Test + public void emitTableInstructionsForCustomClasses() { + final AtomicBoolean listenerCalled = new AtomicBoolean(false); + assertFalse(sharedRealm.hasTable("NewTable")); + sharedRealm.registerSchemaChangedCallback(new OsSharedRealm.SchemaChangedCallback() { + @Override + public void onSchemaChanged() { + assertTrue(sharedRealm.hasTable("NewTable")); + listenerCalled.set(true); + } + }); + + // Change schema using another Realm + // Classes not starting with class_ were treated differently by Sync + OsSharedRealm bgRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); + bgRealm.beginTransaction(); + bgRealm.createTable("NewTable"); + bgRealm.commitTransaction(); + bgRealm.close(); + + // Refresh existing instance + sharedRealm.refresh(); + assertTrue(sharedRealm.hasTable("NewTable")); + assertFalse(listenerCalled.get()); // TODO: Change to assertTrue once bug is fixed + } + @Test public void isClosed() { sharedRealm.close(); From 69030c6202bdd9681a712b683b14ed9ca59c9f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 5 May 2020 11:23:22 +0200 Subject: [PATCH 1502/2110] JNI BSON Protocol (#6831) --- Jenkinsfile | 1 + realm/realm-library/build.gradle | 7 + .../kotlin/io/realm/RealmFunctionsTest.kt | 50 +++++ .../realm-library/src/main/cpp/CMakeLists.txt | 3 + .../src/main/cpp/io_realm_RealmFunctions.cpp | 35 +++ .../realm-library/src/main/cpp/util_sync.cpp | 37 ++++ .../realm-library/src/main/cpp/util_sync.hpp | 26 +++ .../java/io/realm/RealmFunctions.java | 15 +- .../realm/internal/jni/JniBsonProtocol.java | 48 +++++ .../io/realm/internal/util/BsonConverter.java | 204 ++++++++++++++++++ .../kotlin/io/realm/BsonTest.kt | 175 +++++++++++++++ 11 files changed, 600 insertions(+), 1 deletion(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTest.kt create mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp create mode 100644 realm/realm-library/src/main/cpp/util_sync.cpp create mode 100644 realm/realm-library/src/main/cpp/util_sync.hpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java create mode 100644 realm/realm-library/src/testObjectServer/kotlin/io/realm/BsonTest.kt diff --git a/Jenkinsfile b/Jenkinsfile index 913cc53e50..e10d317dcb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -82,6 +82,7 @@ try { } finally { storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + storeJunitResults 'realm/realm-library/build/test-results/**/TEST-*.xml' step([$class: 'LintPublisher']) } } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index ec477f4204..4ac805103d 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -97,6 +97,9 @@ android { } sourceSets { + testObjectServer { + java.srcDirs += ['src/testObjectServer/kotlin'] + } androidTest { java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java', 'src/testUtils/kotlin'] } @@ -214,6 +217,10 @@ dependencies { // FIXME: Attempt to find a way to remove this dependency objectServerImplementation "com.google.android.gms:play-services-tasks:17.0.2" // added to support mongo client's asynchronous nature without breaking Stitch's API + testImplementation 'junit:junit:4.12' + testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + // TODO: investigate why we can't use the latest multidex version // check baseDebugAndroidTestRuntimeClasspath and objectServerDebugAndroidTestRuntimeClasspath // tasks as they introduce version 2.0.0 strictly, even when specifying 2.0.1 from here diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTest.kt new file mode 100644 index 0000000000..1d03aa0557 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm + +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.internal.util.BsonConverter +import org.bson.* +import org.junit.Before +import org.junit.Test +import java.util.stream.Collectors +import kotlin.test.assertEquals + +class RealmFunctionsTest { + + @Before + fun setup() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + + @Test + fun jniBsonOnlyRoundtrip() { + val functions = RealmFunctions() + val i32 = 42 + val i64 = 42L + val s = "Realm" + + assertEquals(i32, functions.invoke(BsonInt32(i32)).asInt32().value) + assertEquals(i64, functions.invoke(BsonInt64(i64)).asInt64().value) + assertEquals(s, functions.invoke(BsonString(s)).asString().value) + + val values = listOf(BsonInt32(i32), BsonInt64(i64), BsonString(s)) + val invoke: BsonValue = functions.invoke(BsonConverter.to(values)) + assertEquals(values, invoke.asArray().values) + } + +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 6e6a44cec7..977d5deb19 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -108,6 +108,7 @@ if (build_SYNC) io.realm.RealmApp io.realm.RealmUser io.realm.RealmSync + io.realm.RealmFunctions io.realm.SyncSession io.realm.internal.objectstore.OsAppCredentials io.realm.internal.objectstore.OsAsyncOpenTask @@ -198,6 +199,7 @@ if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmApp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmUser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFunctions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_EmailPasswordAuth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ApiKeyAuth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetRequiredError.cpp @@ -210,6 +212,7 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsSyncUser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/util_sync.cpp ) endif() diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp new file mode 100644 index 0000000000..90d272741d --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_RealmFunctions.h" + +#include "util.hpp" +#include "util_sync.hpp" + +using namespace realm; + +// FIXME This is just a basic round trip test for passing bson back and forth. Proper implementation +// will come with actual Function implementation. +JNIEXPORT jstring JNICALL Java_io_realm_RealmFunctions_nativeCallFunction + (JNIEnv* env, jclass, jstring j_args) { + try { + bson::Bson bson = jstring_to_bson(env, j_args); + return bson_to_jstring(env, bson); + } + CATCH_STD() + return NULL; +} + diff --git a/realm/realm-library/src/main/cpp/util_sync.cpp b/realm/realm-library/src/main/cpp/util_sync.cpp new file mode 100644 index 0000000000..1e73a82b84 --- /dev/null +++ b/realm/realm-library/src/main/cpp/util_sync.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "util.hpp" +#include "util_sync.hpp" + +// Must match OSJNIBsonProtocol.VALUE +static const std::string VALUE("value"); + +using namespace realm::bson; + +Bson jstring_to_bson(JNIEnv* env, jstring arg) { + JStringAccessor args_json(env, arg); + BsonDocument document(parse(args_json)); + return document[VALUE]; +} + +jstring bson_to_jstring(JNIEnv* env, Bson bson) { + BsonDocument document{{VALUE, bson}}; + std::stringstream buffer; + buffer << document; + std::string r = buffer.str(); + return to_jstring(env, r); +}; diff --git a/realm/realm-library/src/main/cpp/util_sync.hpp b/realm/realm-library/src/main/cpp/util_sync.hpp new file mode 100644 index 0000000000..759f001dab --- /dev/null +++ b/realm/realm-library/src/main/cpp/util_sync.hpp @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +#ifndef REALM_UTIL_SYNC_HPP +#define REALM_UTIL_SYNC_HPP + +#include +#include + +realm::bson::Bson jstring_to_bson(JNIEnv* env, jstring arg); +jstring bson_to_jstring(JNIEnv* env, realm::bson::Bson bson); + +#endif //REALM_UTIL_SYNC_HPP diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java index 4752b76d7a..0c8d043540 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java @@ -15,5 +15,18 @@ */ package io.realm; -class RealmFunctions { +import org.bson.BsonValue; + +import io.realm.internal.jni.JniBsonProtocol; + +public class RealmFunctions { + + // FIXME Prelimiry implementation to be able to test passing BsonValues through JNI + BsonValue invoke(BsonValue arg) { + String response = nativeCallFunction(JniBsonProtocol.encode(arg)); + return JniBsonProtocol.decode(response); + } + + private static native String nativeCallFunction(String arg); + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java new file mode 100644 index 0000000000..7c294be8b7 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.jni; + +import org.bson.BsonDocument; +import org.bson.BsonValue; +import org.bson.json.JsonMode; +import org.bson.json.JsonWriterSettings; + +/** + * Protocol for passing {@link BsonValue}s to JNI. + * + * For now this just encapsulated the BSON value in a document with key {@value VALUE}. This + * overcomes the shortcoming of {@code org.bson.JsonWrite} not being able to serialize single values. + */ +public class JniBsonProtocol { + + private static final String VALUE = "value"; + + private static JsonWriterSettings writerSettings = JsonWriterSettings.builder() + .outputMode(JsonMode.EXTENDED) + .build(); + + public static String encode(BsonValue bsonValue) { + BsonDocument document = new BsonDocument(VALUE, bsonValue); + return document.toJson(writerSettings); + } + + public static BsonValue decode(String string) { + BsonDocument document = BsonDocument.parse(string); + return document.get(VALUE); + } + +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java new file mode 100644 index 0000000000..e5140638f1 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java @@ -0,0 +1,204 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.util; + +import org.bson.BsonArray; +import org.bson.BsonBinary; +import org.bson.BsonBoolean; +import org.bson.BsonDateTime; +import org.bson.BsonDecimal128; +import org.bson.BsonDouble; +import org.bson.BsonInt32; +import org.bson.BsonInt64; +import org.bson.BsonObjectId; +import org.bson.BsonString; +import org.bson.BsonType; +import org.bson.BsonValue; +import org.bson.conversions.Bson; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * A BSON converter to handle conversion between native Java types and BSON values. + */ +public class BsonConverter { + + /** + * Converts value object to BSON value based on type. + * + * Converts primitive boxed types to the equivalent BSON equivalent value object and {@link List} + * of values into {@link BsonArray} of converted values. + * + * {@link BsonValue} objects are left as is. + * + * @param value The object to convert. + * @return BSON value representation of the origin value object. + * + * @throws IllegalArgumentException If the object could not be mapped to a BSON type. + */ + // FIXME Review supported types...any obvious types missing? + public static BsonValue to(Object value) { + if (value instanceof BsonValue) { + return (BsonValue) value; + } + // Convert list to BsonArray + else if (value instanceof List) { + return BsonConverter.to(((List) value).toArray()); + } + // Native types + else if (value instanceof Integer) { + return new BsonInt32((Integer) value); + } else if (value instanceof Long) { + return new BsonInt64((Long) value); + } else if (value instanceof Float) { + return new BsonDouble((Float) value); + } else if (value instanceof Double) { + return new BsonDouble((Double) value); + } else if (value instanceof Boolean) { + return new BsonBoolean((Boolean) value); + } else if (value instanceof String){ + return new BsonString((String) value); + } else if (value instanceof byte[]) { + return new BsonBinary((byte[]) value); + } + // Bson values + else if (value instanceof ObjectId) { + return new BsonObjectId((ObjectId) value); + } + else if (value instanceof Decimal128) { + return new BsonDecimal128((Decimal128) value); + } + // FIXME Missing Realm types + // Date + // Object + // List + // LinkingObject + // FIXME Missing Bson value + throw new IllegalArgumentException("Conversion to BSON value not supported for " + value.getClass().getName()); + } + + /** + * Converts a list of objects to BSON values. + * + * @param value List of value objects to convert. + * @return A list of BSON values of the converted input arguments. + * + * @throws IllegalArgumentException If any of the value objects could not be converted to a + * BSON type. + * + * @see #to(Object) + */ + public static BsonArray to(Object... value) { + ArrayList result = new ArrayList(); + for (Object o1 : value) { + result.add(to(o1)); + } + return new BsonArray(result); + } + + /** + * Unwrap BSON values for types that just wraps another similar Java type. + * + * @param value The BSON value to convert. + * @param The requested result type of the conversion. + * @return The converted value object corresponding to the given {@code value}. + * + * @throws IllegalArgumentException if not able to convert the value to the requested type. + * @throws ClassCastException if the BsonValue cannot be converted to the requested type + * parameters. + */ + public static T from(Class clz, BsonValue value) { + Object result = null; + + if (BsonValue.class.isAssignableFrom(clz)) { + if (clz.isInstance(value)) { + return (T) value; + } else { + throw new ClassCastException("Cannot convert " + value + " to " + clz.getName()); + } + } + BsonType bsonType = value.getBsonType(); + switch (bsonType) { +// case END_OF_DOCUMENT: +// break; + case DOUBLE: + result = value.asDouble().getValue(); + break; + case STRING: + result = value.asString().getValue(); + break; +// case DOCUMENT: +// break; + case ARRAY: + result = value.asArray().getValues(); + break; + case BINARY: + result = value.asBinary().getData(); + break; +// case UNDEFINED: +// break; + case OBJECT_ID: + result = value.asObjectId().getValue(); + break; + case BOOLEAN: + result = value.asBoolean().getValue(); + break; +// case DATE_TIME: +// break; +// case NULL: +// break; +// case REGULAR_EXPRESSION: +// break; +// case DB_POINTER: +// break; +// case JAVASCRIPT: +// break; +// case SYMBOL: +// break; +// case JAVASCRIPT_WITH_SCOPE: +// break; + case INT32: + result = value.asInt32().getValue(); + break; +// case TIMESTAMP: +// break; + case INT64: + result = value.asInt64().getValue(); + break; + case DECIMAL128: + result = value.asDecimal128().getValue(); + break; +// case MIN_KEY: +// break; +// case MAX_KEY: +// break; + default: + // FIXME + throw new IllegalArgumentException("Not able to convert " + value + " to " + clz.getName()); + } + if (clz.isInstance(result)) { + return (T) result; + } else { + throw new IllegalArgumentException("Not able to convert " + value + " to " + clz.getName()); + } + } + +} diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/BsonTest.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/BsonTest.kt new file mode 100644 index 0000000000..5833489279 --- /dev/null +++ b/realm/realm-library/src/testObjectServer/kotlin/io/realm/BsonTest.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import io.realm.internal.util.BsonConverter +import org.bson.* +import org.bson.types.Decimal128 +import org.bson.types.ObjectId +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Ignore +import org.junit.Test +import kotlin.test.assertFailsWith + +class BsonTest { + + /** + * Simple test to verify semantics of org.bson JSON encoding and decoding. + */ + // Only for Bson API evaluation, not testing Realm functionality + @Test + fun bsonRoundtrip() { + val valueInt32 = 42 + val valueInt64 = 42L + val valueString = "Realm" + val valueBoolean = true + val valueOid = ObjectId() + + val document = BsonDocument.parse("{}") + + document.append("arg1", BsonInt32(valueInt32)) + document.append("arg2", BsonInt64(valueInt64)) + document.append("arg3", BsonString(valueString)) + document.append("arg4", BsonBoolean(valueBoolean)) + document.append("arg5", BsonObjectId(valueOid)) + + val roundtrip = BsonDocument.parse(document.toJson()) + assertEquals(valueInt32, roundtrip.get("arg1")?.asInt32()?.value) + assertEquals(valueInt64, roundtrip.get("arg2")?.asInt64()?.value) + assertEquals(valueString, roundtrip.get("arg3")?.asString()?.value) + assertEquals(valueBoolean, roundtrip.get("arg4")?.asBoolean()?.value) + assertEquals(valueOid, roundtrip.get("arg5")?.asObjectId()?.value) + + // We cannot retrieve bson values differently type, not even if it could fit in the type + assertFailsWith { + roundtrip.getInt32("arg2"); + } + assertFailsWith { + roundtrip.getInt64("arg1"); + } + } + + /** + * Simple test of type conversion between native Java object types and BSON types. + */ + @Test + fun bsonConversion() { + val b = true + val i32 = 32 + val i64 = 32L + val f = 1.24f + val d = 2.34.toDouble() + val s = "Realm" + val oid = ObjectId() + val d128 = Decimal128(i64) + val bin = byteArrayOf(0, 1, 2, 3) + + val bi32 = BsonInt32(15) + val bOid = BsonObjectId(oid) + val bDoc = BsonDocument() + + for (type in BsonType.values()) { + when (type) { + BsonType.DOUBLE -> { + assertEquals(BsonDouble(f.toDouble()), BsonConverter.to(f)) + assertEquals(BsonDouble(d), BsonConverter.to(d)) + assertEquals(d, BsonConverter.from(java.lang.Double::class.java, BsonDouble(d))) + } + BsonType.STRING -> { + assertEquals(BsonString(s), BsonConverter.to(s)) + assertEquals(s, BsonConverter.from(String::class.java, BsonString(s))) + + } + BsonType.ARRAY -> { + assertTrue(BsonConverter.to(b, i32, i64) is BsonArray) + val listValues = listOf(BsonInt32(i32), BsonInt64(i64)) + assertEquals(listValues, BsonConverter.from(List::class.java, BsonArray(listValues))) + } + BsonType.BINARY -> { + assertEquals(BsonBinary(bin), BsonConverter.to(bin)) + assertEquals(bin, BsonConverter.from(ByteArray::class.java, BsonBinary(bin))) + } + BsonType.OBJECT_ID -> { + assertEquals(BsonObjectId(oid), BsonConverter.to(oid)) + assertEquals(oid, BsonConverter.from(ObjectId::class.java, BsonObjectId(oid))) + } + BsonType.BOOLEAN -> { + assertEquals(BsonBoolean(b), BsonConverter.to(b)) + assertEquals(b, BsonConverter.from(java.lang.Boolean::class.java, BsonBoolean(b))) + } + BsonType.INT32 -> { + assertEquals(BsonInt32(i32), BsonConverter.to(i32)) + assertEquals(i32, BsonConverter.from(Integer::class.java, BsonInt32(i32))) + } + BsonType.INT64 -> { + assertEquals(BsonInt64(i64), BsonConverter.to(i64)) + assertEquals(i64, BsonConverter.from(java.lang.Long::class.java, BsonInt64(i64))) + } + BsonType.DECIMAL128 -> { + assertEquals(BsonDecimal128(d128), BsonConverter.to(d128)) + assertEquals(oid, BsonConverter.from(ObjectId::class.java, BsonObjectId(oid))) + } + BsonType.DOCUMENT, + BsonType.UNDEFINED, + BsonType.DATE_TIME, + BsonType.NULL, + BsonType.REGULAR_EXPRESSION, + BsonType.SYMBOL, + BsonType.DB_POINTER, + BsonType.JAVASCRIPT, + BsonType.JAVASCRIPT_WITH_SCOPE, + BsonType.TIMESTAMP, + BsonType.END_OF_DOCUMENT, + BsonType.MIN_KEY, + BsonType.MAX_KEY -> { + // No conversion is implemented for these types yet + } + } + } + + // To BSONValue + assertEquals(bi32, BsonConverter.to(bi32)) + assertEquals(bOid, BsonConverter.to(bOid)) + assertEquals(bDoc, BsonConverter.to(bDoc)) + + assertEquals(listOf(BsonBoolean(b), BsonInt32(i32), BsonInt64(i64)), BsonConverter.to(b, i32, i64)) + val list = listOf(BsonInt32(i32), BsonInt64(i64), BsonString(s)) + assertEquals(list, BsonConverter.to(list)) + + // From BSONValue + // BsonValue types are just passed as is + assertEquals(BsonInt32(i32), BsonConverter.from(BsonInt32::class.java, BsonInt32(i32))) + assertEquals(BsonInt64(i64), BsonConverter.from(BsonInt64::class.java, BsonInt64(i64))) + assertEquals(BsonString(s), BsonConverter.from(BsonString::class.java, BsonString(s))) + + // FIXME Howto auto box/wrap as Kotlin's primitive types are not assignable + // (isAssignablefrom) Java's auto boxed types + // assertEquals(i32, BsonConverter.from(Int::class.java, BsonInt32(i32))) + assertEquals(i32, BsonConverter.from(Integer::class.java, BsonInt32(i32))) + + // Not trying to fit wider types event though possible + // FIXME Would we like to support this + assertFailsWith { + BsonConverter.from(java.lang.Long::class.java, BsonInt32(i32)) + } + assertFailsWith { + BsonConverter.from(Int::class.java, BsonInt64(i64)) + } + + } + +} From 6b0979e54ff5ce9d2d4d47d67a762bcbf85b0976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Wed, 6 May 2020 09:57:24 +0200 Subject: [PATCH 1503/2110] Merge Stitch and Realm SDKs - 2: add task framework from Stitch (#6826) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First iteration: added GMS library (possibly temporarily) to avoid introducing immediate breaking changes in how we process asynchronous operations with AsyncRealmTask. All original Stitch interfaces and proxies have been discarded in favour of Java classes (although this approach might be changed). Some interfaces connected to the collection's iterables have been omitted as it is unclear whether they will be needed or not for the time being. * Added licences to class headers plus a bit of cleanup * Added latest API methods and necessary classes * Added remote mongo client and remote database, their respective Os files and part of the native logic * Moved JNI callbacks outside RealmApp and added more remote collection classes * Updated object store branch to v10 and fixed wrong use of count call * Cleanup * Added task-related classes from Stitch * Fixed wrong finalizer methods and cleanup to interop files * Moved classes * Updated OS pointer * Removed duplicate entries in CMakeLists * added suppresswarnings for ignored futures - issue inherited from Stitch's task framework - test to see if Jenkins swallows it Co-authored-by: Eduardo López --- realm/config/findbugs/findbugs-filter.xml | 4 + .../realm/internal/common/AsyncAdapter.java | 21 ++++++ .../io/realm/internal/common/Callback.java | 23 ++++++ .../internal/common/CallbackAsyncAdapter.java | 20 +++++ .../io/realm/internal/common/Dispatcher.java | 25 +++++++ .../internal/common/OperationResult.java | 66 +++++++++++++++++ .../internal/common/TaskCallbackAdapter.java | 42 +++++++++++ .../realm/internal/common/TaskDispatcher.java | 27 +++++++ .../internal/common/ThreadDispatcher.java | 74 +++++++++++++++++++ 9 files changed, 302 insertions(+) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/AsyncAdapter.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/Callback.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/CallbackAsyncAdapter.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/Dispatcher.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/OperationResult.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskCallbackAdapter.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskDispatcher.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index 5a5d451546..4f5774800d 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -42,5 +42,9 @@ + + + + diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/AsyncAdapter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/AsyncAdapter.java new file mode 100644 index 0000000000..7685528a9c --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/AsyncAdapter.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.common; + +public interface AsyncAdapter { + T getAdapter(); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/Callback.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/Callback.java new file mode 100644 index 0000000000..3af9773892 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/Callback.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.common; + +import javax.annotation.Nonnull; + +public interface Callback { + void onComplete(@Nonnull OperationResult result); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/CallbackAsyncAdapter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/CallbackAsyncAdapter.java new file mode 100644 index 0000000000..c90aee9762 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/CallbackAsyncAdapter.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.common; + +public interface CallbackAsyncAdapter extends Callback, AsyncAdapter { +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/Dispatcher.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/Dispatcher.java new file mode 100644 index 0000000000..c9e34a0be8 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/Dispatcher.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.common; + +import java.util.concurrent.Callable; + +public interface Dispatcher { + void dispatch(final Callable callable); + + void close(); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/OperationResult.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/OperationResult.java new file mode 100644 index 0000000000..8a26fc9382 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/OperationResult.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.common; + +public final class OperationResult { + private final SuccessTypeT result; + private final FailureTypeT failureResult; + private final boolean isSuccessful; + + private OperationResult( + final SuccessTypeT result, final FailureTypeT failureResult, final boolean isSuccessful) { + this.result = result; + this.failureResult = failureResult; + this.isSuccessful = isSuccessful; + } + + public static OperationResult successfulResultOf(final T value) { + return new OperationResult<>(value, null, true); + } + + public static OperationResult failedResultOf(final U value) { + return new OperationResult<>(null, value, false); + } + + public boolean isSuccessful() { + return isSuccessful; + } + + /** + * Gets the result of the operation, if successful. + * + * @return The result of the operation. + */ + public SuccessTypeT geResult() { + if (!isSuccessful) { + throw new IllegalStateException("operation was failed, not successful"); + } + return result; + } + + /** + * Gets the failure reason for the operation, if it failed. + * + * @return The failure reason of the operation. + */ + public FailureTypeT getFailure() { + if (isSuccessful) { + throw new IllegalStateException("operation was successful, not failed"); + } + return failureResult; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskCallbackAdapter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskCallbackAdapter.java new file mode 100644 index 0000000000..885bff4e0d --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskCallbackAdapter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.common; + +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; + +public final class TaskCallbackAdapter implements CallbackAsyncAdapter> { + private final TaskCompletionSource taskCompletionSource; + + TaskCallbackAdapter() { + this.taskCompletionSource = new TaskCompletionSource<>(); + } + + @Override + public Task getAdapter() { + return taskCompletionSource.getTask(); + } + + @Override + public void onComplete(final OperationResult result) { + if (result.isSuccessful()) { + taskCompletionSource.setResult(result.geResult()); + } else { + taskCompletionSource.setException(result.getFailure()); + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskDispatcher.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskDispatcher.java new file mode 100644 index 0000000000..ded1820577 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskDispatcher.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.common; + +import com.google.android.gms.tasks.Task; + +import java.util.concurrent.Callable; + +public final class TaskDispatcher extends ThreadDispatcher { + public Task dispatchTask(final Callable callable) { + return dispatch(callable, new TaskCallbackAdapter()); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java new file mode 100644 index 0000000000..d96c15c192 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.common; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class ThreadDispatcher implements Dispatcher { + private final ExecutorService executorService; + + public ThreadDispatcher() { + executorService = + new ThreadPoolExecutor( + 8, + 32, + 60, + TimeUnit.SECONDS, + new LinkedBlockingDeque(), + Executors.defaultThreadFactory() + ); + } + + + @SuppressWarnings("FutureReturnValueIgnored") + @Override + public void dispatch(final Callable callable) { + // ignoring the output of this future messes with Findbugs, thus the suppress + executorService.submit(callable); + } + + protected U dispatch( + final Callable callable, + final CallbackAsyncAdapter callbackAdapter + ) { + dispatch(callable, (Callback) callbackAdapter); + return callbackAdapter.getAdapter(); + } + + @SuppressWarnings("FutureReturnValueIgnored") + private void dispatch(final Callable callable, final Callback callback) { + // ignoring the output of this future messes with Findbugs, thus the suppress + executorService.submit(() -> { + try { + callback.onComplete(OperationResult.successfulResultOf(callable.call())); + } catch (final Exception e) { + callback.onComplete(OperationResult.failedResultOf(e)); + } + }); + } + + @Override + public void close() { + executorService.shutdownNow(); + } +} From 5c55ba23ca9f308fb4e9d842c2d14b6d56e08009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 7 May 2020 22:38:03 +0200 Subject: [PATCH 1504/2110] Add ability to use CodecRegistry for passing objects as BSON to JNI (#6837) --- .../kotlin/io/realm/RealmFunctionsTest.kt | 50 ----- .../kotlin/io/realm/RealmFunctionsTests.kt | 151 +++++++++++++ .../realm-library/src/main/cpp/CMakeLists.txt | 2 +- .../main/cpp/io_realm_EmailPasswordAuth.cpp | 7 +- .../src/main/cpp/io_realm_RealmFunctions.cpp | 7 +- ...al_objectstore_OsRemoteMongoCollection.cpp | 4 +- .../{util_sync.cpp => jni_util/bson_util.cpp} | 22 +- .../{util_sync.hpp => jni_util/bson_util.hpp} | 22 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/EmailPasswordAuth.java | 10 +- .../java/io/realm/RealmAppConfiguration.java | 37 +++- .../java/io/realm/RealmFunctions.java | 24 ++- .../realm/internal/jni/JniBsonProtocol.java | 41 +++- .../io/realm/internal/util/BsonConverter.java | 204 ------------------ .../kotlin/io/realm/BsonTest.kt | 175 --------------- 15 files changed, 290 insertions(+), 468 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTest.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTests.kt rename realm/realm-library/src/main/cpp/{util_sync.cpp => jni_util/bson_util.cpp} (61%) rename realm/realm-library/src/main/cpp/{util_sync.hpp => jni_util/bson_util.hpp} (53%) delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java delete mode 100644 realm/realm-library/src/testObjectServer/kotlin/io/realm/BsonTest.kt diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTest.kt deleted file mode 100644 index 1d03aa0557..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm - -import androidx.test.platform.app.InstrumentationRegistry -import io.realm.internal.util.BsonConverter -import org.bson.* -import org.junit.Before -import org.junit.Test -import java.util.stream.Collectors -import kotlin.test.assertEquals - -class RealmFunctionsTest { - - @Before - fun setup() { - Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - } - - @Test - fun jniBsonOnlyRoundtrip() { - val functions = RealmFunctions() - val i32 = 42 - val i64 = 42L - val s = "Realm" - - assertEquals(i32, functions.invoke(BsonInt32(i32)).asInt32().value) - assertEquals(i64, functions.invoke(BsonInt64(i64)).asInt64().value) - assertEquals(s, functions.invoke(BsonString(s)).asString().value) - - val values = listOf(BsonInt32(i32), BsonInt64(i64), BsonString(s)) - val invoke: BsonValue = functions.invoke(BsonConverter.to(values)) - assertEquals(values, invoke.asArray().values) - } - -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTests.kt new file mode 100644 index 0000000000..5a52e8537a --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTests.kt @@ -0,0 +1,151 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm + +import androidx.test.platform.app.InstrumentationRegistry +import org.bson.* +import org.bson.codecs.StringCodec +import org.bson.codecs.configuration.CodecRegistries +import org.bson.codecs.pojo.PojoCodecProvider +import org.bson.types.Decimal128 +import org.bson.types.ObjectId +import org.junit.After +import org.junit.Before +import org.junit.Test +import kotlin.test.assertEquals + +class RealmFunctionsTests { + + private lateinit var app: TestRealmApp + private lateinit var functions : RealmFunctions + + @Before + fun setup() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestRealmApp() + functions = RealmFunctions(app.configuration.defaultCodecRegistry) + } + + @After + fun teardown() { + if (this::app.isInitialized) { + app.close() + } + } + + // Test of BSON JNI round trip until superseded with actual public api tests are added. + @Test + fun jniRoundTripForDefaultCodecRegistry() { + val i32 = 42 + val i64 = 42L + + for (type in BsonType.values()) { + when (type) { + BsonType.DOUBLE -> { + assertEquals(1.4f, functions.invoke(1.4f, java.lang.Float::class.java).toFloat()) + assertEquals(1.4, functions.invoke(1.4, java.lang.Double::class.java).toDouble()) + assertTypedEcho(BsonDouble(1.4), BsonDouble::class.java) + } + BsonType.STRING -> { + assertTypedEcho("Realm", String::class.java) + assertTypedEcho(BsonString("Realm"), BsonString::class.java) + } + BsonType.ARRAY -> { + val listValues = listOf(true, i32, i64) + assertTypedEcho(listValues, List::class.java) + } + BsonType.BINARY -> { + val value = byteArrayOf(1, 2, 3) + val actual = functions.invoke(value, ByteArray::class.java) + assertEquals(value.toList(), actual.toList()) + // FIXME C++ Does not seem to preserve subtype + // arg = "{"value": {"$binary": {"base64": "JmS8oQitTny4IPS2tyjmdA==", "subType": "04"}}}" + // response = "{"value":{"$binary":{"base64":"JmS8oQitTny4IPS2tyjmdA==","subType":"00"}}}" + // assertTypedEcho(BsonBinary(UUID.randomUUID()), BsonBinary::class.java) + assertTypedEcho(BsonBinary(byteArrayOf(1,2,3)), BsonBinary::class.java) + } + BsonType.OBJECT_ID -> { + assertTypedEcho(ObjectId(), ObjectId::class.java) + assertTypedEcho(BsonObjectId(ObjectId()), BsonObjectId::class.java) + } + BsonType.BOOLEAN -> { + val value: Boolean = true + val actual: java.lang.Boolean = functions.invoke(value, java.lang.Boolean::class.java) + assertEquals(value, actual.booleanValue()) + assertTypedEcho(BsonBoolean(true), BsonBoolean::class.java) + } + BsonType.INT32 -> { + assertEquals(32, functions.invoke(32, Integer::class.java).toInt()) + assertEquals(32, functions.invoke(32L, Integer::class.java).toInt()) + assertTypedEcho(BsonInt32(32), BsonInt32::class.java) + } + BsonType.INT64 -> { + assertEquals(32L, functions.invoke(32, java.lang.Long::class.java).toLong()) + assertEquals(32L, functions.invoke(32L, java.lang.Long::class.java).toLong()) + assertTypedEcho(BsonInt64(32), BsonInt64::class.java) + } + BsonType.DECIMAL128 -> { + assertTypedEcho(Decimal128(32L), Decimal128::class.java) + assertTypedEcho(BsonDecimal128(Decimal128(32L)), BsonDecimal128::class.java) + } + // TODO + BsonType.DOCUMENT, + BsonType.UNDEFINED, + BsonType.DATE_TIME, + BsonType.NULL, + BsonType.REGULAR_EXPRESSION, + BsonType.SYMBOL, + BsonType.DB_POINTER, + BsonType.JAVASCRIPT, + BsonType.JAVASCRIPT_WITH_SCOPE, + BsonType.TIMESTAMP, + BsonType.END_OF_DOCUMENT, + BsonType.MIN_KEY, + BsonType.MAX_KEY -> { + // No conversion is implemented for these types yet + } + } + } + } + + private fun assertTypedEcho(value: T, returnClass: Class) : T { + val actual = functions.invoke(value, returnClass) + assertEquals(value, actual) + return actual + } + + // Test of BSON JNI round trip until superseded with actual public api tests are added. + data class Dog(var name: String? = null) + @Test + fun pojoCodecRegistry() { + val pojoRegistry = CodecRegistries.fromRegistries( + CodecRegistries.fromCodecs(StringCodec()), + CodecRegistries.fromProviders( + PojoCodecProvider.builder() + .register(Dog::class.java) + .build() + ) + ) + + val input = Dog("PojoFido") + + val actual: Dog = functions.invoke(input, Dog::class.java, pojoRegistry) + + assertEquals(input, actual) + } + +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 977d5deb19..e95692f547 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -212,7 +212,7 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsSyncUser.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/util_sync.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/jni_util/bson_util.cpp ) endif() diff --git a/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp b/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp index 1f73a7c95a..26c244dc8e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp @@ -20,6 +20,7 @@ #include "util.hpp" #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" +#include "jni_util/bson_util.hpp" #include @@ -52,9 +53,11 @@ JNIEXPORT void JNICALL Java_io_realm_EmailPasswordAuth_nativeCallFunction(JNIEnv case io_realm_EmailPasswordAuth_TYPE_SEND_RESET_PASSWORD_EMAIL: client.send_reset_password_email(args[0], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuth_TYPE_CALL_RESET_PASSWORD_FUNCTION: - client.call_reset_password_function(args[0], args[1], args[2], JavaNetworkTransport::create_void_callback(env, j_callback)); + case io_realm_EmailPasswordAuth_TYPE_CALL_RESET_PASSWORD_FUNCTION: { + bson::BsonArray reset_arg(JniBsonProtocol::string_to_bson(args[2])); + client.call_reset_password_function(args[0], args[1], reset_arg, JavaNetworkTransport::create_void_callback(env, j_callback)); break; + } case io_realm_EmailPasswordAuth_TYPE_RESET_PASSWORD: client.reset_password(args[0], args[1], args[2], JavaNetworkTransport::create_void_callback(env, j_callback)); break; diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp index 90d272741d..82a44a86c8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp @@ -17,17 +17,18 @@ #include "io_realm_RealmFunctions.h" #include "util.hpp" -#include "util_sync.hpp" +#include "jni_util/bson_util.hpp" using namespace realm; +using namespace realm::jni_util; // FIXME This is just a basic round trip test for passing bson back and forth. Proper implementation // will come with actual Function implementation. JNIEXPORT jstring JNICALL Java_io_realm_RealmFunctions_nativeCallFunction (JNIEnv* env, jclass, jstring j_args) { try { - bson::Bson bson = jstring_to_bson(env, j_args); - return bson_to_jstring(env, bson); + bson::Bson bson = JniBsonProtocol::jstring_to_bson(env, j_args); + return JniBsonProtocol::bson_to_jstring(env, bson); } CATCH_STD() return NULL; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp index a8ce6a91b0..6e5d61f2dd 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp @@ -21,6 +21,7 @@ #include "util.hpp" #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" +#include "jni_util/bson_util.hpp" #include "object-store/src/util/bson/bson.hpp" #include @@ -28,6 +29,7 @@ #include #include #include +#include using namespace realm; using namespace realm::app; @@ -56,7 +58,7 @@ Java_io_realm_internal_objectstore_OsRemoteMongoCollection_nativeCount(JNIEnv* e jobject j_callback) { try { RemoteMongoCollection* collection = reinterpret_cast(j_collection_ptr); - JStringAccessor filter(env, j_filter); + bson::BsonDocument filter(JniBsonProtocol::jstring_to_bson(env, j_filter)); uint64_t limit = std::uint64_t(j_limit); collection->count(filter, limit, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper)); } diff --git a/realm/realm-library/src/main/cpp/util_sync.cpp b/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp similarity index 61% rename from realm/realm-library/src/main/cpp/util_sync.cpp rename to realm/realm-library/src/main/cpp/jni_util/bson_util.cpp index 1e73a82b84..888b5a19d8 100644 --- a/realm/realm-library/src/main/cpp/util_sync.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp @@ -14,24 +14,32 @@ * limitations under the License. */ +#include #include "util.hpp" -#include "util_sync.hpp" +#include "bson_util.hpp" -// Must match OSJNIBsonProtocol.VALUE +// Must match JniBsonProtocol.VALUE from Java static const std::string VALUE("value"); using namespace realm::bson; +using namespace realm::jni_util; -Bson jstring_to_bson(JNIEnv* env, jstring arg) { - JStringAccessor args_json(env, arg); - BsonDocument document(parse(args_json)); +Bson JniBsonProtocol::string_to_bson(std::string arg) { + BsonDocument document(parse(arg)); return document[VALUE]; } +Bson JniBsonProtocol::jstring_to_bson(JNIEnv* env, jstring arg) { + return string_to_bson(JStringAccessor(env, arg)); +} -jstring bson_to_jstring(JNIEnv* env, Bson bson) { +std::string JniBsonProtocol::bson_to_string(Bson bson) { BsonDocument document{{VALUE, bson}}; std::stringstream buffer; buffer << document; - std::string r = buffer.str(); + return buffer.str(); +} + +jstring JniBsonProtocol::bson_to_jstring(JNIEnv* env, Bson bson) { + std::string r = bson_to_string(bson); return to_jstring(env, r); }; diff --git a/realm/realm-library/src/main/cpp/util_sync.hpp b/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp similarity index 53% rename from realm/realm-library/src/main/cpp/util_sync.hpp rename to realm/realm-library/src/main/cpp/jni_util/bson_util.hpp index 759f001dab..c4a3931495 100644 --- a/realm/realm-library/src/main/cpp/util_sync.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp @@ -14,13 +14,25 @@ * limitations under the License. */ -#ifndef REALM_UTIL_SYNC_HPP -#define REALM_UTIL_SYNC_HPP +#ifndef REALM_BSON_UTIL_HPP +#define REALM_BSON_UTIL_HPP #include #include -realm::bson::Bson jstring_to_bson(JNIEnv* env, jstring arg); -jstring bson_to_jstring(JNIEnv* env, realm::bson::Bson bson); +namespace realm { +namespace jni_util { -#endif //REALM_UTIL_SYNC_HPP +// Serializes and wraps bson values passed between java and JNI according to JniBsonProtocol.java +class JniBsonProtocol { +public: + static realm::bson::Bson string_to_bson(std::string arg); + static realm::bson::Bson jstring_to_bson(JNIEnv* env, jstring arg); + static std::string bson_to_string(realm::bson::Bson bson); + static jstring bson_to_jstring(JNIEnv* env, realm::bson::Bson bson); +}; + +} // jni_util +} // realm + +#endif //REALM_BSON_UTIL_HPP diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 8e6923c09e..9a1d0f5804 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 8e6923c09efd6b8d94e6ccc8e07c9ab4382473ee +Subproject commit 9a1d0f5804ab265a54ff8659bf6ca828a5520fc1 diff --git a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java index e42c788bfa..f0b417ed08 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java @@ -17,9 +17,12 @@ import org.json.JSONArray; +import java.util.ArrayList; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; import io.realm.internal.Util; +import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIVoidResultCallback; import io.realm.internal.objectstore.OsJavaNetworkTransport; @@ -212,15 +215,12 @@ public Void run() throws ObjectServerError { public void callResetPasswordFunction(String email, String newPassword, Object... args) throws ObjectServerError { Util.checkEmpty(email, "email"); Util.checkEmpty(newPassword, "newPassword"); - JSONArray array = new JSONArray(); - for (Object arg : args) { - array.put((arg != null) ? arg.toString() : null); - } + String encodedArgs = JniBsonProtocol.encode(Arrays.asList(args), app.getConfiguration().getDefaultCodecRegistry()); AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_CALL_RESET_PASSWORD_FUNCTION, app.nativePtr, new OsJNIVoidResultCallback(error), - email, newPassword, array.toString()); + email, newPassword, encodedArgs); RealmApp.handleResult(null, error); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java index a5f9341c76..c3eb08f8c6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java @@ -17,6 +17,12 @@ import android.content.Context; +import org.bson.codecs.BsonValueCodecProvider; +import org.bson.codecs.IterableCodecProvider; +import org.bson.codecs.ValueCodecProvider; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; + import java.io.File; import java.net.MalformedURLException; import java.net.URL; @@ -49,6 +55,7 @@ public class RealmAppConfiguration { private final String authorizationHeaderName; private final Map customHeaders; private final File syncRootDir; // Root directory for storing Sync related files + private final CodecRegistry codecRegistry; private RealmAppConfiguration(String appId, String appName, @@ -60,7 +67,8 @@ private RealmAppConfiguration(String appId, long requestTimeoutMs, String authorizationHeaderName, Map customHeaders, - File syncRootdir) { + File syncRootdir, + CodecRegistry codecRegistry) { this.appId = appId; this.appName = appName; @@ -73,6 +81,7 @@ private RealmAppConfiguration(String appId, this.authorizationHeaderName = (!Util.isEmptyString(authorizationHeaderName)) ? authorizationHeaderName : "Authorization"; this.customHeaders = Collections.unmodifiableMap(customHeaders); this.syncRootDir = syncRootdir; + this.codecRegistry = codecRegistry; } private URL createUrl(String baseUrl) { @@ -175,10 +184,25 @@ public File getSyncRootDirectory() { return syncRootDir; } + // FIXME Doc + public CodecRegistry getDefaultCodecRegistry() { return codecRegistry; } + /** * FIXME */ public static class Builder { + // Default BSON codec for passing BSON to/from JNI + static CodecRegistry DEFAULT_BSON_CODEC_REGISTRY = CodecRegistries.fromRegistries( + CodecRegistries.fromProviders( + // For primitive support + new ValueCodecProvider(), + // For BSONValue support + new BsonValueCodecProvider(), + // For list support + new IterableCodecProvider() + ) + ); + private String appId; private String appName; private String appVersion; @@ -212,6 +236,7 @@ public void onError(SyncSession session, ObjectServerError error) { private String autorizationHeaderName; private Map customHeaders = new HashMap<>(); private File syncRootDir; + private CodecRegistry codecRegistry = DEFAULT_BSON_CODEC_REGISTRY; /** * FIXME @@ -388,6 +413,13 @@ public Builder syncRootDirectory(File rootDir) { return this; } + // FIXME Doc + public Builder codecRegistry(CodecRegistry codecRegistry) { + Util.checkNull(codecRegistry, "codecRegistry"); + this.codecRegistry = codecRegistry; + return this; + } + public RealmAppConfiguration build() { return new RealmAppConfiguration(appId, appName, @@ -399,7 +431,8 @@ public RealmAppConfiguration build() { requestTimeoutMs, autorizationHeaderName, customHeaders, - syncRootDir); + syncRootDir, + codecRegistry); } } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java index 0c8d043540..99c7ddb279 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java @@ -15,16 +15,30 @@ */ package io.realm; -import org.bson.BsonValue; +import org.bson.codecs.configuration.CodecRegistry; import io.realm.internal.jni.JniBsonProtocol; -public class RealmFunctions { +// FIXME This class is only a placeholder for JNI round trip as until actual RealmFunctions +// implementation supersedes it. +class RealmFunctions { + + private CodecRegistry codecRegistry; + + RealmFunctions(CodecRegistry codecRegistry) { + this.codecRegistry = codecRegistry; + } + + // FIXME Prelimiry implementation to be able to test passing BsonValues through JNI + T invoke(Object arg, Class resultClass) { + return invoke(arg, resultClass, codecRegistry); + } // FIXME Prelimiry implementation to be able to test passing BsonValues through JNI - BsonValue invoke(BsonValue arg) { - String response = nativeCallFunction(JniBsonProtocol.encode(arg)); - return JniBsonProtocol.decode(response); + T invoke(Object arg, Class resultClass, CodecRegistry registry) { + String a = JniBsonProtocol.encode(arg, registry); + String s = nativeCallFunction(a); + return JniBsonProtocol.decode(s, resultClass, registry); } private static native String nativeCallFunction(String arg); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java index 7c294be8b7..ecdb840994 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java @@ -16,11 +16,20 @@ package io.realm.internal.jni; -import org.bson.BsonDocument; import org.bson.BsonValue; +import org.bson.codecs.Decoder; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.Encoder; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecRegistry; import org.bson.json.JsonMode; +import org.bson.json.JsonReader; +import org.bson.json.JsonWriter; import org.bson.json.JsonWriterSettings; +import java.io.StringReader; +import java.io.StringWriter; + /** * Protocol for passing {@link BsonValue}s to JNI. * @@ -35,14 +44,32 @@ public class JniBsonProtocol { .outputMode(JsonMode.EXTENDED) .build(); - public static String encode(BsonValue bsonValue) { - BsonDocument document = new BsonDocument(VALUE, bsonValue); - return document.toJson(writerSettings); + public static String encode(T value, CodecRegistry registry) { + return encode(value, (Encoder)registry.get(value.getClass())); + } + + public static String encode(T value, Encoder encoder) { + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter, writerSettings); + jsonWriter.writeStartDocument(); + jsonWriter.writeName(VALUE); + encoder.encode(jsonWriter, value, EncoderContext.builder().build()); + jsonWriter.writeEndDocument(); + return stringWriter.toString(); + } + + public static T decode(String string, Class clz, CodecRegistry registry) { + return decode(string, registry.get(clz)); } - public static BsonValue decode(String string) { - BsonDocument document = BsonDocument.parse(string); - return document.get(VALUE); + public static T decode(String string, Decoder decoder) { + StringReader stringReader = new StringReader(string); + JsonReader jsonReader = new JsonReader(stringReader); + jsonReader.readStartDocument(); + jsonReader.readName(VALUE); + T value = decoder.decode(jsonReader, DecoderContext.builder().build()); + jsonReader.readEndDocument(); + return value; } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java deleted file mode 100644 index e5140638f1..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.util; - -import org.bson.BsonArray; -import org.bson.BsonBinary; -import org.bson.BsonBoolean; -import org.bson.BsonDateTime; -import org.bson.BsonDecimal128; -import org.bson.BsonDouble; -import org.bson.BsonInt32; -import org.bson.BsonInt64; -import org.bson.BsonObjectId; -import org.bson.BsonString; -import org.bson.BsonType; -import org.bson.BsonValue; -import org.bson.conversions.Bson; -import org.bson.types.Decimal128; -import org.bson.types.ObjectId; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * A BSON converter to handle conversion between native Java types and BSON values. - */ -public class BsonConverter { - - /** - * Converts value object to BSON value based on type. - * - * Converts primitive boxed types to the equivalent BSON equivalent value object and {@link List} - * of values into {@link BsonArray} of converted values. - * - * {@link BsonValue} objects are left as is. - * - * @param value The object to convert. - * @return BSON value representation of the origin value object. - * - * @throws IllegalArgumentException If the object could not be mapped to a BSON type. - */ - // FIXME Review supported types...any obvious types missing? - public static BsonValue to(Object value) { - if (value instanceof BsonValue) { - return (BsonValue) value; - } - // Convert list to BsonArray - else if (value instanceof List) { - return BsonConverter.to(((List) value).toArray()); - } - // Native types - else if (value instanceof Integer) { - return new BsonInt32((Integer) value); - } else if (value instanceof Long) { - return new BsonInt64((Long) value); - } else if (value instanceof Float) { - return new BsonDouble((Float) value); - } else if (value instanceof Double) { - return new BsonDouble((Double) value); - } else if (value instanceof Boolean) { - return new BsonBoolean((Boolean) value); - } else if (value instanceof String){ - return new BsonString((String) value); - } else if (value instanceof byte[]) { - return new BsonBinary((byte[]) value); - } - // Bson values - else if (value instanceof ObjectId) { - return new BsonObjectId((ObjectId) value); - } - else if (value instanceof Decimal128) { - return new BsonDecimal128((Decimal128) value); - } - // FIXME Missing Realm types - // Date - // Object - // List - // LinkingObject - // FIXME Missing Bson value - throw new IllegalArgumentException("Conversion to BSON value not supported for " + value.getClass().getName()); - } - - /** - * Converts a list of objects to BSON values. - * - * @param value List of value objects to convert. - * @return A list of BSON values of the converted input arguments. - * - * @throws IllegalArgumentException If any of the value objects could not be converted to a - * BSON type. - * - * @see #to(Object) - */ - public static BsonArray to(Object... value) { - ArrayList result = new ArrayList(); - for (Object o1 : value) { - result.add(to(o1)); - } - return new BsonArray(result); - } - - /** - * Unwrap BSON values for types that just wraps another similar Java type. - * - * @param value The BSON value to convert. - * @param The requested result type of the conversion. - * @return The converted value object corresponding to the given {@code value}. - * - * @throws IllegalArgumentException if not able to convert the value to the requested type. - * @throws ClassCastException if the BsonValue cannot be converted to the requested type - * parameters. - */ - public static T from(Class clz, BsonValue value) { - Object result = null; - - if (BsonValue.class.isAssignableFrom(clz)) { - if (clz.isInstance(value)) { - return (T) value; - } else { - throw new ClassCastException("Cannot convert " + value + " to " + clz.getName()); - } - } - BsonType bsonType = value.getBsonType(); - switch (bsonType) { -// case END_OF_DOCUMENT: -// break; - case DOUBLE: - result = value.asDouble().getValue(); - break; - case STRING: - result = value.asString().getValue(); - break; -// case DOCUMENT: -// break; - case ARRAY: - result = value.asArray().getValues(); - break; - case BINARY: - result = value.asBinary().getData(); - break; -// case UNDEFINED: -// break; - case OBJECT_ID: - result = value.asObjectId().getValue(); - break; - case BOOLEAN: - result = value.asBoolean().getValue(); - break; -// case DATE_TIME: -// break; -// case NULL: -// break; -// case REGULAR_EXPRESSION: -// break; -// case DB_POINTER: -// break; -// case JAVASCRIPT: -// break; -// case SYMBOL: -// break; -// case JAVASCRIPT_WITH_SCOPE: -// break; - case INT32: - result = value.asInt32().getValue(); - break; -// case TIMESTAMP: -// break; - case INT64: - result = value.asInt64().getValue(); - break; - case DECIMAL128: - result = value.asDecimal128().getValue(); - break; -// case MIN_KEY: -// break; -// case MAX_KEY: -// break; - default: - // FIXME - throw new IllegalArgumentException("Not able to convert " + value + " to " + clz.getName()); - } - if (clz.isInstance(result)) { - return (T) result; - } else { - throw new IllegalArgumentException("Not able to convert " + value + " to " + clz.getName()); - } - } - -} diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/BsonTest.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/BsonTest.kt deleted file mode 100644 index 5833489279..0000000000 --- a/realm/realm-library/src/testObjectServer/kotlin/io/realm/BsonTest.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ -package io.realm - -import io.realm.internal.util.BsonConverter -import org.bson.* -import org.bson.types.Decimal128 -import org.bson.types.ObjectId -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Ignore -import org.junit.Test -import kotlin.test.assertFailsWith - -class BsonTest { - - /** - * Simple test to verify semantics of org.bson JSON encoding and decoding. - */ - // Only for Bson API evaluation, not testing Realm functionality - @Test - fun bsonRoundtrip() { - val valueInt32 = 42 - val valueInt64 = 42L - val valueString = "Realm" - val valueBoolean = true - val valueOid = ObjectId() - - val document = BsonDocument.parse("{}") - - document.append("arg1", BsonInt32(valueInt32)) - document.append("arg2", BsonInt64(valueInt64)) - document.append("arg3", BsonString(valueString)) - document.append("arg4", BsonBoolean(valueBoolean)) - document.append("arg5", BsonObjectId(valueOid)) - - val roundtrip = BsonDocument.parse(document.toJson()) - assertEquals(valueInt32, roundtrip.get("arg1")?.asInt32()?.value) - assertEquals(valueInt64, roundtrip.get("arg2")?.asInt64()?.value) - assertEquals(valueString, roundtrip.get("arg3")?.asString()?.value) - assertEquals(valueBoolean, roundtrip.get("arg4")?.asBoolean()?.value) - assertEquals(valueOid, roundtrip.get("arg5")?.asObjectId()?.value) - - // We cannot retrieve bson values differently type, not even if it could fit in the type - assertFailsWith { - roundtrip.getInt32("arg2"); - } - assertFailsWith { - roundtrip.getInt64("arg1"); - } - } - - /** - * Simple test of type conversion between native Java object types and BSON types. - */ - @Test - fun bsonConversion() { - val b = true - val i32 = 32 - val i64 = 32L - val f = 1.24f - val d = 2.34.toDouble() - val s = "Realm" - val oid = ObjectId() - val d128 = Decimal128(i64) - val bin = byteArrayOf(0, 1, 2, 3) - - val bi32 = BsonInt32(15) - val bOid = BsonObjectId(oid) - val bDoc = BsonDocument() - - for (type in BsonType.values()) { - when (type) { - BsonType.DOUBLE -> { - assertEquals(BsonDouble(f.toDouble()), BsonConverter.to(f)) - assertEquals(BsonDouble(d), BsonConverter.to(d)) - assertEquals(d, BsonConverter.from(java.lang.Double::class.java, BsonDouble(d))) - } - BsonType.STRING -> { - assertEquals(BsonString(s), BsonConverter.to(s)) - assertEquals(s, BsonConverter.from(String::class.java, BsonString(s))) - - } - BsonType.ARRAY -> { - assertTrue(BsonConverter.to(b, i32, i64) is BsonArray) - val listValues = listOf(BsonInt32(i32), BsonInt64(i64)) - assertEquals(listValues, BsonConverter.from(List::class.java, BsonArray(listValues))) - } - BsonType.BINARY -> { - assertEquals(BsonBinary(bin), BsonConverter.to(bin)) - assertEquals(bin, BsonConverter.from(ByteArray::class.java, BsonBinary(bin))) - } - BsonType.OBJECT_ID -> { - assertEquals(BsonObjectId(oid), BsonConverter.to(oid)) - assertEquals(oid, BsonConverter.from(ObjectId::class.java, BsonObjectId(oid))) - } - BsonType.BOOLEAN -> { - assertEquals(BsonBoolean(b), BsonConverter.to(b)) - assertEquals(b, BsonConverter.from(java.lang.Boolean::class.java, BsonBoolean(b))) - } - BsonType.INT32 -> { - assertEquals(BsonInt32(i32), BsonConverter.to(i32)) - assertEquals(i32, BsonConverter.from(Integer::class.java, BsonInt32(i32))) - } - BsonType.INT64 -> { - assertEquals(BsonInt64(i64), BsonConverter.to(i64)) - assertEquals(i64, BsonConverter.from(java.lang.Long::class.java, BsonInt64(i64))) - } - BsonType.DECIMAL128 -> { - assertEquals(BsonDecimal128(d128), BsonConverter.to(d128)) - assertEquals(oid, BsonConverter.from(ObjectId::class.java, BsonObjectId(oid))) - } - BsonType.DOCUMENT, - BsonType.UNDEFINED, - BsonType.DATE_TIME, - BsonType.NULL, - BsonType.REGULAR_EXPRESSION, - BsonType.SYMBOL, - BsonType.DB_POINTER, - BsonType.JAVASCRIPT, - BsonType.JAVASCRIPT_WITH_SCOPE, - BsonType.TIMESTAMP, - BsonType.END_OF_DOCUMENT, - BsonType.MIN_KEY, - BsonType.MAX_KEY -> { - // No conversion is implemented for these types yet - } - } - } - - // To BSONValue - assertEquals(bi32, BsonConverter.to(bi32)) - assertEquals(bOid, BsonConverter.to(bOid)) - assertEquals(bDoc, BsonConverter.to(bDoc)) - - assertEquals(listOf(BsonBoolean(b), BsonInt32(i32), BsonInt64(i64)), BsonConverter.to(b, i32, i64)) - val list = listOf(BsonInt32(i32), BsonInt64(i64), BsonString(s)) - assertEquals(list, BsonConverter.to(list)) - - // From BSONValue - // BsonValue types are just passed as is - assertEquals(BsonInt32(i32), BsonConverter.from(BsonInt32::class.java, BsonInt32(i32))) - assertEquals(BsonInt64(i64), BsonConverter.from(BsonInt64::class.java, BsonInt64(i64))) - assertEquals(BsonString(s), BsonConverter.from(BsonString::class.java, BsonString(s))) - - // FIXME Howto auto box/wrap as Kotlin's primitive types are not assignable - // (isAssignablefrom) Java's auto boxed types - // assertEquals(i32, BsonConverter.from(Int::class.java, BsonInt32(i32))) - assertEquals(i32, BsonConverter.from(Integer::class.java, BsonInt32(i32))) - - // Not trying to fit wider types event though possible - // FIXME Would we like to support this - assertFailsWith { - BsonConverter.from(java.lang.Long::class.java, BsonInt32(i32)) - } - assertFailsWith { - BsonConverter.from(Int::class.java, BsonInt64(i64)) - } - - } - -} From d8f188e7bb4cc6d4a5cdac58530433559ed7b915 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 8 May 2020 10:13:34 +0200 Subject: [PATCH 1505/2110] Improve Jenkins builds (#6846) --- Jenkinsfile | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e10d317dcb..1fb0353fdc 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -117,7 +117,7 @@ try { try { backgroundPid = startLogCatCollector() forwardAdbPorts() - gradle('realm', "${instrumentationTestTarget}") + gradle('realm', "${instrumentationTestTarget} ${abiFilter}") } finally { stopLogCatCollector(backgroundPid) storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' @@ -192,22 +192,31 @@ def forwardAdbPorts() { ''' } -def String startLogCatCollector() { - sh '''adb logcat -c - adb logcat -v time > "logcat.txt" & - echo $! > pid - ''' - return readFile("pid").trim() +String startLogCatCollector() { + // Cancel build quickly if no device is available. The lock acquired already should + // ensure we have access to a device. If not, it is most likely a more severe problem. + timeout(time: 1, unit: 'MINUTES') { + sh 'adb devices' + sh """adb logcat -c + adb logcat -v time > 'logcat.txt' & + echo \$! > pid + """ + return readFile("pid").trim() + } } def stopLogCatCollector(String backgroundPid) { - sh "kill ${backgroundPid}" - zip([ - 'zipFile': 'logcat.zip', - 'archive': true, - 'glob' : 'logcat.txt' - ]) - sh 'rm logcat.txt' + // The pid might not be available if the build was terminated early or stopped due to + // a build error. + if (backgroundPid != null) { + sh "kill ${backgroundPid}" + zip([ + 'zipFile': 'logcat.zip', + 'archive': true, + 'glob' : 'logcat.txt' + ]) + sh 'rm logcat.txt' + } } def archiveServerLogs(String mongoDbRealmContainerId, String commandServerContainerId) { From 4f08a3a7ccde08f627137ec1e8905d866bb9a1e4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 11 May 2020 11:38:44 +0200 Subject: [PATCH 1506/2110] Update ObjectServerExample (#6815) --- .../README.md | 0 .../build.gradle | 31 +++--- .../mongoDbRealmExample/gradle.properties | 2 + .../lint.xml | 0 .../src/main/AndroidManifest.xml | 8 +- .../mongodb/realm/example}/CounterActivity.kt | 94 ++++++++++-------- .../mongodb/realm/example}/LoginActivity.kt | 43 ++++---- .../mongodb/realm/example}/MyApplication.kt | 19 +++- .../realm/example}/model/CRDTCounter.kt | 10 +- .../ic_exit_to_app_white_24dp.png | Bin .../ic_exit_to_app_white_24dp.png | Bin .../src/main/res/drawable/button_counter.xml | 0 .../src/main/res/drawable/logo.png | Bin .../src/main/res/layout/activity_counter.xml | 0 .../src/main/res/layout/activity_login.xml | 12 +-- .../src/main/res/menu/menu_counter.xml | 2 +- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../src/main/res/values-w820dp/dimens.xml | 0 .../src/main/res/values/dimens.xml | 0 .../src/main/res/values/realm_colors.xml | 0 .../src/main/res/values/strings.xml | 2 +- .../src/main/res/values/styles.xml | 0 examples/settings.gradle | 2 +- 26 files changed, 125 insertions(+), 100 deletions(-) rename examples/{objectServerExample => mongoDbRealmExample}/README.md (100%) rename examples/{objectServerExample => mongoDbRealmExample}/build.gradle (53%) create mode 100644 examples/mongoDbRealmExample/gradle.properties rename examples/{objectServerExample => mongoDbRealmExample}/lint.xml (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/AndroidManifest.xml (71%) rename examples/{objectServerExample/src/main/java/io/realm/examples/objectserver => mongoDbRealmExample/src/main/java/com/mongodb/realm/example}/CounterActivity.kt (64%) rename examples/{objectServerExample/src/main/java/io/realm/examples/objectserver => mongoDbRealmExample/src/main/java/com/mongodb/realm/example}/LoginActivity.kt (74%) rename examples/{objectServerExample/src/main/java/io/realm/examples/objectserver => mongoDbRealmExample/src/main/java/com/mongodb/realm/example}/MyApplication.kt (59%) rename examples/{objectServerExample/src/main/java/io/realm/examples/objectserver => mongoDbRealmExample/src/main/java/com/mongodb/realm/example}/model/CRDTCounter.kt (79%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/drawable/button_counter.xml (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/drawable/logo.png (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/layout/activity_counter.xml (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/layout/activity_login.xml (87%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/menu/menu_counter.xml (86%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/values-w820dp/dimens.xml (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/values/dimens.xml (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/values/realm_colors.xml (100%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/values/strings.xml (90%) rename examples/{objectServerExample => mongoDbRealmExample}/src/main/res/values/styles.xml (100%) diff --git a/examples/objectServerExample/README.md b/examples/mongoDbRealmExample/README.md similarity index 100% rename from examples/objectServerExample/README.md rename to examples/mongoDbRealmExample/README.md diff --git a/examples/objectServerExample/build.gradle b/examples/mongoDbRealmExample/build.gradle similarity index 53% rename from examples/objectServerExample/build.gradle rename to examples/mongoDbRealmExample/build.gradle index f0055aba23..5ca62095ce 100644 --- a/examples/objectServerExample/build.gradle +++ b/examples/mongoDbRealmExample/build.gradle @@ -21,7 +21,7 @@ android { buildToolsVersion rootProject.buildTools defaultConfig { - applicationId 'io.realm.examples.objectserver' + applicationId 'com.mongodb.realm.example' targetSdkVersion rootProject.sdkVersion minSdkVersion rootProject.minSdkVersion versionCode 1 @@ -33,22 +33,21 @@ android { } buildTypes { - // Go to https://cloud.realm.io and copy the URL to your instance. Insert it below. - // It will look something like "https://test.us1.cloud.realm.io" + // Configure server and App Id. + // The default server is https://realm-dev.mongodb.com/ . Go to that and copy the MongoDB + // Realm App Id. // - // If you're running a self-hosted version, use the hostname/IP address of the Realm Object - // Server, e.g "http://127.0.0.1:9080". - def rosUrl = "" - def realmAuthUrl = "\"${rosUrl}/auth\"" - def realmUrl = "\"${rosUrl.replace("http", "realm")}/default\"" - + // If you are running a local version of MongoDB Realm, modify endpoint accordingly. Most + // likely it is "http://localhost:9090" + def mongodbRealmUrl = "https://realm-dev.mongodb.com" + def appId = "my-app-id" debug { - buildConfigField "String", "REALM_AUTH_URL", "${realmAuthUrl}" - buildConfigField "String", "REALM_URL", "${realmUrl}" + buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" + buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\"" } release { - buildConfigField "String", "REALM_AUTH_URL", "${realmAuthUrl}" - buildConfigField "String", "REALM_URL", "${realmUrl}" + buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" + buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\"" minifyEnabled true signingConfig signingConfigs.debug } @@ -60,8 +59,8 @@ realm { } dependencies { - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support:design:27.1.1' - implementation 'me.zhanghai.android.materialprogressbar:library:1.3.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'com.google.android.material:material:1.1.0' + implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/examples/mongoDbRealmExample/gradle.properties b/examples/mongoDbRealmExample/gradle.properties new file mode 100644 index 0000000000..3ab3c32e63 --- /dev/null +++ b/examples/mongoDbRealmExample/gradle.properties @@ -0,0 +1,2 @@ +# FIXME: Required as long as we depend on PlayServices for RemoteMongDB API's +android.useAndroidX=true diff --git a/examples/objectServerExample/lint.xml b/examples/mongoDbRealmExample/lint.xml similarity index 100% rename from examples/objectServerExample/lint.xml rename to examples/mongoDbRealmExample/lint.xml diff --git a/examples/objectServerExample/src/main/AndroidManifest.xml b/examples/mongoDbRealmExample/src/main/AndroidManifest.xml similarity index 71% rename from examples/objectServerExample/src/main/AndroidManifest.xml rename to examples/mongoDbRealmExample/src/main/AndroidManifest.xml index 45e5d52c1c..909f62618c 100644 --- a/examples/objectServerExample/src/main/AndroidManifest.xml +++ b/examples/mongoDbRealmExample/src/main/AndroidManifest.xml @@ -1,14 +1,14 @@ + package="com.mongodb.realm.example" > @@ -16,7 +16,7 @@ diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt similarity index 64% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt rename to examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt index bc069f88c6..7078951577 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/CounterActivity.kt +++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt @@ -14,22 +14,20 @@ * limitations under the License. */ -package io.realm.examples.objectserver +package com.mongodb.realm.example import android.content.Intent -import android.databinding.DataBindingUtil import android.graphics.PorterDuff import android.os.Bundle -import android.support.annotation.ColorRes -import android.support.v7.app.AppCompatActivity import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil import io.realm.* -import io.realm.examples.objectserver.databinding.ActivityCounterBinding -import io.realm.examples.objectserver.model.CRDTCounter -import io.realm.kotlin.createObject +import com.mongodb.realm.example.model.CRDTCounter +import com.mongodb.realm.example.databinding.ActivityCounterBinding import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.RealmLog @@ -54,20 +52,20 @@ class CounterActivity : AppCompatActivity() { private val downloadingChanges = AtomicBoolean(false) private val uploadingChanges = AtomicBoolean(false) - private lateinit var realm: Realm + private var realm: Realm? = null private lateinit var session: SyncSession - private var user: SyncUser? = null + private var user: RealmUser? = null private lateinit var counterView: TextView private lateinit var progressBar: MaterialProgressBar - private lateinit var counters: RealmResults // Keep strong reference to counter to keep change listeners alive. + private lateinit var counter: CRDTCounter // Keep strong reference to counter to keep change listeners alive. - private val loggedInUser: SyncUser? + private val loggedInUser: RealmUser? get() { - var user: SyncUser? = null + var user: RealmUser? = null try { - user = SyncUser.current() + user = APP.currentUser() } catch (e: IllegalStateException) { RealmLog.warn(e); } @@ -95,30 +93,42 @@ class CounterActivity : AppCompatActivity() { val user = user if (user != null) { // Create a RealmConfiguration for our user - val config = user.createConfiguration(BuildConfig.REALM_URL) - .initialData { realm -> realm.createObject(user.identity) } + // Use user id as partition value, so each user gets an unique view. + // FIXME Right now we are using waitForInitialRemoteData and a more advanced + // initialData block due to Sync only supporting ObjectId keys. This should + // be changed once natural keys are supported. + val config = SyncConfiguration.Builder(user, user.id) + .initialData { + if (it.isEmpty) { + it.insert(CRDTCounter()) + } + } + .waitForInitialRemoteData() .build() // This will automatically sync all changes in the background for as long as the Realm is open - realm = Realm.getInstance(config) - - counterView.text = "-" - counters = realm.where().equalTo("name", user.identity).findAllAsync() - counters.addChangeListener { counters, _ -> - if (counters.isValid && !counters.isEmpty()) { - val counter = counters.first() - counterView.text = String.format(Locale.US, "%d", counter!!.count) - } else { - counterView.text = "-" + Realm.getInstanceAsync(config, object: Realm.Callback() { + override fun onSuccess(realm: Realm) { + this@CounterActivity.realm = realm + + counter = realm.where().findFirstAsync() + counter.addChangeListener { obj, _ -> + if (obj.isValid) { + counterView.text = String.format(Locale.US, "%d", counter.count) + } else { + counterView.text = "-" + } + } + + // Setup progress listeners for indeterminate progress bars + session = realm.syncSession + session.run { + addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener) + addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener) + } } - } - - // Setup progress listeners for indeterminate progress bars - session = realm.syncSession - session.run { - addDownloadProgressListener(ProgressMode.INDEFINITELY, downloadListener) - addUploadProgressListener(ProgressMode.INDEFINITELY, uploadListener) - } + }) + counterView.text = "-" } } @@ -129,7 +139,7 @@ class CounterActivity : AppCompatActivity() { removeProgressListener(downloadListener) removeProgressListener(uploadListener) } - realm.close() + realm?.close() } } @@ -141,21 +151,23 @@ class CounterActivity : AppCompatActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_logout -> { - realm.close() val user = user - if (user != null) { - user.logOut() - this.user = loggedInUser + user?.logOutAsync { + if (it.isSuccess) { + realm?.close() + this.user = loggedInUser + } else { + RealmLog.error(it.error.toString()) + } } true } - else -> super.onOptionsItemSelected(item) } } private fun updateProgressBar(downloading: Boolean, uploading: Boolean) { - @ColorRes val color = when { + val color = when { downloading && uploading -> R.color.progress_both downloading -> R.color.progress_download uploading -> R.color.progress_upload @@ -168,7 +180,7 @@ class CounterActivity : AppCompatActivity() { private fun adjustCounter(adjustment: Int) { // A synchronized Realm can get written to at any point in time, so doing synchronous writes on the UI // thread is HIGHLY discouraged as it might block longer than intended. Use only async transactions. - realm.executeTransactionAsync { realm -> + realm?.executeTransactionAsync { realm -> val counter = realm.where().findFirst() counter?.incrementCounter(adjustment.toLong()) } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt similarity index 74% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt rename to examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt index 1038e795a8..8e76283804 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/LoginActivity.kt +++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt @@ -14,20 +14,18 @@ * limitations under the License. */ -package io.realm.examples.objectserver +package com.mongodb.realm.example import android.app.ProgressDialog -import android.databinding.DataBindingUtil import android.os.Bundle -import android.support.v7.app.AppCompatActivity import android.widget.Button import android.widget.EditText import android.widget.Toast -import io.realm.ErrorCode -import io.realm.ObjectServerError -import io.realm.SyncCredentials -import io.realm.SyncUser -import io.realm.examples.objectserver.databinding.ActivityLoginBinding +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import com.mongodb.realm.example.databinding.ActivityLoginBinding +import io.realm.* +import io.realm.log.RealmLog class LoginActivity : AppCompatActivity() { @@ -67,25 +65,28 @@ class LoginActivity : AppCompatActivity() { val username = this.username.text.toString() val password = this.password.text.toString() - val creds = SyncCredentials.usernamePassword(username, password, createUser) - val callback = object : SyncUser.Callback { - override fun onSuccess(user: SyncUser) { + + if (createUser) { + APP.emailPasswordAuth.registerUserAsync(username, password) { progressDialog.dismiss() - onLoginSuccess() + binding.buttonCreate.isEnabled = true + binding.buttonLogin.isEnabled = true + if (!it.isSuccess) { + onLoginFailed("Could not register user. Check Logcat") + } } - - override fun onError(error: ObjectServerError) { + } else { + val creds = RealmCredentials.emailPassword(username, password) + APP.loginAsync(creds) { progressDialog.dismiss() - val errorMsg: String = when (error.errorCode) { - ErrorCode.UNKNOWN_ACCOUNT -> getString(R.string.login_error_unknown_account) - ErrorCode.INVALID_CREDENTIALS -> getString(R.string.login_error_invalid_credentials) - else -> error.toString() + if (!it.isSuccess) { + RealmLog.error(it.error.toString()) + onLoginFailed(it.error.message ?: "An error occurred. Check Logcat") + } else { + onLoginSuccess() } - onLoginFailed(errorMsg) } } - - SyncUser.logInAsync(creds, BuildConfig.REALM_AUTH_URL, callback) } override fun onBackPressed() { diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/MyApplication.kt similarity index 59% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt rename to examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/MyApplication.kt index dc7360fb48..ced3a2c79a 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/MyApplication.kt +++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/MyApplication.kt @@ -14,23 +14,32 @@ * limitations under the License. */ -package io.realm.examples.objectserver +package com.mongodb.realm.example import android.app.Application -import android.util.Log import io.realm.Realm +import io.realm.RealmApp +import io.realm.RealmAppConfiguration +import io.realm.log.LogLevel import io.realm.log.RealmLog +lateinit var APP: RealmApp + class MyApplication : Application() { override fun onCreate() { super.onCreate() - Realm.init(this, "ObjectServerExample/" + BuildConfig.VERSION_NAME) + Realm.init(this) + APP = RealmApp(RealmAppConfiguration.Builder(BuildConfig.MONGODB_REALM_APP_ID) + .baseUrl(BuildConfig.MONGODB_REALM_URL) + .appName(BuildConfig.VERSION_NAME) + .appVersion(BuildConfig.VERSION_CODE.toString()) + .build()) - // Enable more + // Enable more logging in debug mode if (BuildConfig.DEBUG) { - RealmLog.setLevel(Log.DEBUG) + RealmLog.setLevel(LogLevel.DEBUG) } } } diff --git a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/model/CRDTCounter.kt similarity index 79% rename from examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt rename to examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/model/CRDTCounter.kt index 6078bb0512..54d9b97ad1 100644 --- a/examples/objectServerExample/src/main/java/io/realm/examples/objectserver/model/CRDTCounter.kt +++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/model/CRDTCounter.kt @@ -13,20 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm.examples.objectserver.model +package com.mongodb.realm.example.model import io.realm.MutableRealmInteger import io.realm.RealmObject import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmField import io.realm.annotations.Required +import org.bson.types.ObjectId open class CRDTCounter : RealmObject() { @PrimaryKey - var name: String = "" - + @RealmField("_id") + var id: ObjectId = ObjectId.get() @Required - private val counter = MutableRealmInteger.valueOf(0L) + private val counter: MutableRealmInteger = MutableRealmInteger.valueOf(0L) val count: Long get() = this.counter.get()!!.toLong() diff --git a/examples/objectServerExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png b/examples/mongoDbRealmExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png similarity index 100% rename from examples/objectServerExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png rename to examples/mongoDbRealmExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png diff --git a/examples/objectServerExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png b/examples/mongoDbRealmExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png similarity index 100% rename from examples/objectServerExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png rename to examples/mongoDbRealmExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png diff --git a/examples/objectServerExample/src/main/res/drawable/button_counter.xml b/examples/mongoDbRealmExample/src/main/res/drawable/button_counter.xml similarity index 100% rename from examples/objectServerExample/src/main/res/drawable/button_counter.xml rename to examples/mongoDbRealmExample/src/main/res/drawable/button_counter.xml diff --git a/examples/objectServerExample/src/main/res/drawable/logo.png b/examples/mongoDbRealmExample/src/main/res/drawable/logo.png similarity index 100% rename from examples/objectServerExample/src/main/res/drawable/logo.png rename to examples/mongoDbRealmExample/src/main/res/drawable/logo.png diff --git a/examples/objectServerExample/src/main/res/layout/activity_counter.xml b/examples/mongoDbRealmExample/src/main/res/layout/activity_counter.xml similarity index 100% rename from examples/objectServerExample/src/main/res/layout/activity_counter.xml rename to examples/mongoDbRealmExample/src/main/res/layout/activity_counter.xml diff --git a/examples/objectServerExample/src/main/res/layout/activity_login.xml b/examples/mongoDbRealmExample/src/main/res/layout/activity_login.xml similarity index 87% rename from examples/objectServerExample/src/main/res/layout/activity_login.xml rename to examples/mongoDbRealmExample/src/main/res/layout/activity_login.xml index 29f078ebc2..7dd1524b30 100644 --- a/examples/objectServerExample/src/main/res/layout/activity_login.xml +++ b/examples/mongoDbRealmExample/src/main/res/layout/activity_login.xml @@ -24,7 +24,7 @@ android:contentDescription="@string/realm_logo" android:src="@drawable/logo" /> - - + - - + - - + tools:context="com.mongodb.realm.example.CounterActivity"> - Object Server Example + MongoDB Realm Example Realm Logo Username Password diff --git a/examples/objectServerExample/src/main/res/values/styles.xml b/examples/mongoDbRealmExample/src/main/res/values/styles.xml similarity index 100% rename from examples/objectServerExample/src/main/res/values/styles.xml rename to examples/mongoDbRealmExample/src/main/res/values/styles.xml diff --git a/examples/settings.gradle b/examples/settings.gradle index 2fa201b4d5..6df1628f5e 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -13,5 +13,5 @@ include 'rxJavaExample' // FIXME include 'secureTokenAndroidKeyStore' include 'threadExample' // FIXME include 'unitTestExample' Disable project because fixing it requires AndroidX -// FIXME include 'objectServerExample' +include 'mongoDbRealmExample' include 'multiprocessExample' From 48c66f66faedd268f9139d87048fcc2e60c01a56 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 11 May 2020 14:32:04 +0200 Subject: [PATCH 1507/2110] Fix MongoDB Realm example. --- examples/mongoDbRealmExample/build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/mongoDbRealmExample/build.gradle b/examples/mongoDbRealmExample/build.gradle index 5ca62095ce..989a65a9bb 100644 --- a/examples/mongoDbRealmExample/build.gradle +++ b/examples/mongoDbRealmExample/build.gradle @@ -52,6 +52,11 @@ android { signingConfig signingConfigs.debug } } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } } realm { From 3c4e29c3da2306435f1b2d1feeca7d36747c203d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 11 May 2020 16:20:16 +0200 Subject: [PATCH 1508/2110] Cache docker file and configure/create all docker images as a single step (#6847) --- Jenkinsfile | 55 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1fb0353fdc..7b874d2041 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,5 +1,7 @@ #!groovy +@Library('realm-ci') _ + import groovy.json.JsonOutput def buildSuccess = false @@ -39,26 +41,29 @@ try { // Run in debug more for better error reporting } - // Prepare Docker images - // FIXME: Had issues moving these into a seperate Stage step. Is this needed? - buildEnv = docker.build 'realm-java:snapshot' - def props = readProperties file: 'dependencies.list' - echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" - def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") - docker.withRegistry('https://docker.pkg.github.com', 'github-packages-token') { - mdbRealmImage.pull() - } - def commandServerEnv = docker.build 'mongodb-realm-command-server', "tools/sync_test_server" - try { - // Prepare Docker containers used by Instrumentation tests - // TODO: How much of this logic can be moved to start_server.sh for shared logic with local testing. - sh "docker network create ${dockerNetworkId}" - mongoDbRealmContainer = mdbRealmImage.run("--network ${dockerNetworkId}") - mongoDbRealmCommandServerContainer = commandServerEnv.run("--network container:${mongoDbRealmContainer.id}") - sh "docker cp tools/sync_test_server/app_config ${mongoDbRealmContainer.id}:/tmp/app_config" - sh "docker cp tools/sync_test_server/setup_mongodb_realm.sh ${mongoDbRealmContainer.id}:/tmp/" - sh "docker exec -i ${mongoDbRealmContainer.id} sh /tmp/setup_mongodb_realm.sh" + + def buildEnv = null + stage('Prepare Docker Images') { + buildEnv = buildDockerEnv("ci/realm-java:v10", push: env.BRANCH_NAME == 'v10') // TODO Should be renamed to 'master' when merged there. + def props = readProperties file: 'dependencies.list' + echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" + def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") + docker.withRegistry('https://docker.pkg.github.com', 'github-packages-token') { + mdbRealmImage.pull() + } + def commandServerEnv = docker.build 'mongodb-realm-command-server', "tools/sync_test_server" + + // Prepare Docker containers used by Instrumentation tests + // TODO: How much of this logic can be moved to start_server.sh for shared logic with local testing. + sh "docker network create ${dockerNetworkId}" + mongoDbRealmContainer = mdbRealmImage.run("--network ${dockerNetworkId}") + mongoDbRealmCommandServerContainer = commandServerEnv.run("--network container:${mongoDbRealmContainer.id}") + sh "docker cp tools/sync_test_server/app_config ${mongoDbRealmContainer.id}:/tmp/app_config" + sh "docker cp tools/sync_test_server/setup_mongodb_realm.sh ${mongoDbRealmContainer.id}:/tmp/" + sh "docker exec -i ${mongoDbRealmContainer.id} sh /tmp/setup_mongodb_realm.sh" + } + buildEnv.inside("-e HOME=/tmp " + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + @@ -95,7 +100,6 @@ try { } } - stage('Static code analysis') { try { gradle('realm', "findbugs ${abiFilter}") // FIXME Renable pmd and checkstyle @@ -153,10 +157,13 @@ try { } } } finally { - archiveServerLogs(mongoDbRealmContainer.id, mongoDbRealmCommandServerContainer.id) - mongoDbRealmContainer.stop() - mongoDbRealmCommandServerContainer.stop() - sh "docker network rm ${dockerNetworkId}" + // We assume that creating these containers and the docker network can be considered an atomic operation. + if (mongoDbRealmContainer != null && mongoDbRealmCommandServerContainer != null) { + archiveServerLogs(mongoDbRealmContainer.id, mongoDbRealmCommandServerContainer.id) + mongoDbRealmContainer.stop() + mongoDbRealmCommandServerContainer.stop() + sh "docker network rm ${dockerNetworkId}" + } } } } From cd380c2f46b33f16b8272038f0f4bd805aa630b0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 13 May 2020 21:50:47 +0200 Subject: [PATCH 1509/2110] Update server used by testing to 2020-05-13 (#6851) --- dependencies.list | 2 +- .../services/BackingDB/rules/test_data.SyncPerson.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dependencies.list b/dependencies.list index 2367d7701f..f01fe25715 100644 --- a/dependencies.list +++ b/dependencies.list @@ -9,7 +9,7 @@ REALM_OBJECT_SERVER_VERSION=3.28.2 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-04-30 +MONGODB_REALM_SERVER_VERSION=2020-05-13 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json index 5909aeacdf..54fe0237e7 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json @@ -38,8 +38,7 @@ "required": [ "firstName", "lastName", - "age", - "dogs" + "age" ], "title": "SyncPerson" } From f30daddb30bbce9ccdb440bef13304ed4b633f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 14 May 2020 09:41:40 +0200 Subject: [PATCH 1510/2110] Migrate schema tests for sync (#6850) --- CHANGELOG.md | 1 + .../java/io/realm/SchemaTests.java | 228 ------------------ .../kotlin/io/realm/SchemaTests.kt | 188 +++++++++++++++ .../kotlin/io/realm/util/KotlinTestUtils.kt | 11 + .../src/main/java/io/realm/BaseRealm.java | 3 +- .../main/java/io/realm/RealmObjectSchema.java | 15 +- .../src/main/java/io/realm/RealmSchema.java | 4 +- 7 files changed, 211 insertions(+), 239 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0628556b..a3ed9045ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Breaking Changes * Removed all references and API's releated to permissions. These are now managed through MongoDB Realm. Read more [here](XXX). * Removed Query Based Sync API's and Subscriptions. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. `SyncConfiguration.partionKey()` has been added as a replacement. Read more [here](XXX). +* Destructive updates of a schema of a synced Realm will now consistently throw an `UnsupportedOperationException` instead of some methods throwing `IllegalArgumentException`. The affected methods are `RealmSchema.remove(String)`, `RealmSchema.rename(String, String)`, `RealmObjectSchema.setClassName(String)`, `RealmObjectSchema.removeField(String)`, `RealmObjectSchema.renameField(String, String)`, `RealmObjectSchema.removeIndex(String)`, `RealmObjectSchema.removePrimaryKey()`, `RealmObjectSchema.addPrimaryKey(String)` and `RealmObjectSchema.addField(String, Class, FieldAttribute)` ### Enhancements * None. diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java deleted file mode 100644 index 78be86829e..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SchemaTests.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Set; - -import io.realm.entities.StringOnly; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; -import static junit.framework.TestCase.assertFalse; -import static org.junit.Assert.fail; - -@Ignore("FIXME: RealmApp refactor") -@RunWith(AndroidJUnit4.class) -public class SchemaTests { - @Rule - public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - - private SyncConfiguration config; - private TestRealmApp app; - - @Before - public void setUp() { - app = new TestRealmApp(); - RealmUser user = SyncTestUtils.createTestUser(app); - config = configFactory.createSyncConfigurationBuilder(user).build(); - } - - @After - public void tearDown() { - if (app != null) { - RealmAppExtKt.close(app); - } - } - - @Test - public void getInstance() { - Realm realm = Realm.getInstance(config); - assertFalse(realm.isClosed()); - realm.close(); - assertTrue(realm.isClosed()); - } - - @Test - public void createObject() { - Realm realm = Realm.getInstance(config); - realm.beginTransaction(); - assertTrue(realm.getSchema().contains("StringOnly")); - StringOnly stringOnly= realm.createObject(StringOnly.class); - stringOnly.setChars("TEST"); - realm.commitTransaction(); - assertEquals(1, realm.where(StringOnly.class).count()); - realm.close(); - } - - @Test - public void disallow_removeClass() { - // Init schema - Realm.getInstance(config).close(); - - DynamicRealm realm = DynamicRealm.getInstance(config); - String className = "StringOnly"; - realm.beginTransaction(); - assertTrue(realm.getSchema().contains(className)); - try { - realm.getSchema().remove(className); - fail(); - } catch (IllegalArgumentException ignored) { - } finally { - realm.cancelTransaction(); - realm.close(); - } - } - - @Test - public void allow_createClass() { - DynamicRealm realm = DynamicRealm.getInstance(config); - String className = "Dogplace"; - realm.beginTransaction(); - realm.getSchema().create("Dogplace"); - realm.commitTransaction(); - assertTrue(realm.getSchema().contains(className)); - realm.close(); - } - - @Test - public void disallow_renameClass() { - // Init schema - Realm.getInstance(config).close(); - - DynamicRealm realm = DynamicRealm.getInstance(config); - String className = "StringOnly"; - realm.beginTransaction(); - try { - realm.getSchema().rename(className, "Dogplace"); - fail(); - } catch (IllegalArgumentException ignored) { - } finally { - realm.cancelTransaction(); - assertTrue(realm.getSchema().contains(className)); - realm.close(); - } - } - - @Test - public void disallow_removeField() { - // Init schema - Realm.getInstance(config).close(); - - DynamicRealm realm = DynamicRealm.getInstance(config); - String className = "StringOnly"; - String fieldName = "chars"; - final RealmObjectSchema objectSchema = realm.getSchema().get(className); - assertNotNull(objectSchema); - assertTrue(objectSchema.hasField(fieldName)); - realm.beginTransaction(); - try { - objectSchema.removeField(fieldName); - fail(); - } catch (IllegalArgumentException ignored) { - } finally { - realm.cancelTransaction(); - realm.close(); - } - } - - @Test - public void allow_addField() { - // Init schema - Realm.getInstance(config).close(); - String className = "StringOnly"; - - DynamicRealm realm = DynamicRealm.getInstance(config); - final RealmObjectSchema objectSchema = realm.getSchema().get(className); - assertNotNull(objectSchema); - realm.beginTransaction(); - objectSchema.addField("foo", String.class); - realm.commitTransaction(); - - assertTrue(objectSchema.hasField("foo")); - - realm.close(); - } - - @Test - public void addPrimaryKey_notAllowed() { - // Init schema - Realm.getInstance(config).close(); - String className = "StringOnly"; - String fieldName = "chars"; - DynamicRealm realm = DynamicRealm.getInstance(config); - - RealmObjectSchema objectSchema = realm.getSchema().get(className); - assertNotNull(objectSchema); - assertTrue(objectSchema.hasField(fieldName)); - - realm.beginTransaction(); - try { - objectSchema.addPrimaryKey(fieldName); - fail(); - } catch (UnsupportedOperationException ignored) { - } finally { - realm.cancelTransaction(); - realm.close(); - } - } - - @Test - public void addField_withPrimaryKeyModifier_notAllowed() { - // Init schema - Realm.getInstance(config).close(); - String className = "StringOnly"; - DynamicRealm realm = DynamicRealm.getInstance(config); - - realm.beginTransaction(); - RealmObjectSchema objectSchema = realm.getSchema().get(className); - assertNotNull(objectSchema); - - try { - objectSchema.addField("bar", String.class, FieldAttribute.PRIMARY_KEY); - fail(); - } catch (UnsupportedOperationException ignored) { - } finally { - realm.cancelTransaction(); - realm.close(); - } - } - - // Special column "__OID" should be hidden from users. - @Test - public void getFieldNames_stableIdColumnShouldBeHidden() { - String className = "StringOnly"; - Realm realm = Realm.getInstance(config); - - RealmObjectSchema objectSchema = realm.getSchema().get(className); - assertNotNull(objectSchema); - Set names = objectSchema.getFieldNames(); - assertEquals(1, names.size()); - assertEquals(StringOnly.FIELD_CHARS, names.iterator().next()); - realm.close(); - } -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt new file mode 100644 index 0000000000..47f7299bd1 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt @@ -0,0 +1,188 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.SyncTestUtils.Companion.createTestUser +import io.realm.entities.StringOnly +import io.realm.util.assertFailsWith +import junit.framework.Assert.* +import junit.framework.TestCase +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ErrorCollector +import org.junit.runner.RunWith +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +class SchemaTests { + @get:Rule + val configFactory = TestSyncConfigurationFactory() + + @get:Rule + val errorCollector = ErrorCollector() + + private lateinit var config: SyncConfiguration + private lateinit var app: TestRealmApp + + @Before + fun setUp() { + app = TestRealmApp() + val user = createTestUser(app) + config = configFactory.createSyncConfigurationBuilder(user).build() + } + + @After + fun tearDown() { + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun instance() { + val realm = Realm.getInstance(config) + realm.use { + TestCase.assertFalse(realm.isClosed) + } + assertTrue(realm.isClosed) + } + + @Test + fun createObject() { + Realm.getInstance(config).use { realm -> + realm.executeTransaction { + assertTrue(realm.schema.contains("StringOnly")) + val stringOnly = realm.createObject(StringOnly::class.java) + stringOnly.chars = "TEST" + } + assertEquals(1, realm.where(StringOnly::class.java).count()) + } + } + + @Test + fun allow_createClass() { + DynamicRealm.getInstance(config).use { realm -> + val className = "Dogplace" + realm.executeTransaction { + realm.schema.create(className) + } + assertTrue(realm.schema.contains(className)) + } + } + + @Test + fun allow_addField() { + // Init schema + Realm.getInstance(config).close() + val className = "StringOnly" + DynamicRealm.getInstance(config).use { realm -> + val objectSchema = realm.schema[className]!! + assertNotNull(objectSchema) + realm.executeTransaction { + objectSchema.addField("foo", String::class.java) + assertTrue(objectSchema.hasField("foo")) + } + assertTrue(objectSchema.hasField("foo")) + } + } + + // Special column "__OID" should be hidden from users. + @Test + fun fieldNames_stableIdColumnShouldBeHidden() { + val className = "StringOnly" + Realm.getInstance(config).use { realm -> + val objectSchema = realm.schema[className]!! + assertNotNull(objectSchema) + val names = objectSchema.fieldNames + assertEquals(1, names.size) + assertEquals(StringOnly.FIELD_CHARS, names.iterator().next()) + } + } + + enum class DestructiveSchemaOperation { + REMOVE_CLASS, + RENAME_CLASS, + SET_CLASS_NAME, + REMOVE_FIELD, + RENAME_FIELD, + REMOVE_INDEX, + REMOVE_PRIMARY_KEY, + ADD_PRIMARY_KEY, + ADD_FIELD_PRIMARY_KEY, + } + + @Test + fun disallowDestructiveUpdateOfSyncedDynamicRealm() { + for (operation in DestructiveSchemaOperation.values()) { + // Init schema + Realm.getInstance(config).close() + val className = "StringOnly" + val newClassName = "Dogplace" + val fieldName = "chars" + val newFieldName = "newchars" + + DynamicRealm.getInstance(config).use { realm -> + assertTrue(realm.schema.contains(className)) + val objectSchema = realm.schema[className]!! + assertNotNull(objectSchema) + assertTrue(objectSchema.hasField(fieldName)) + + realm.beginTransaction() + errorCollector.assertFailsWith { + when (operation) { + DestructiveSchemaOperation.REMOVE_CLASS -> + realm.schema.remove(className) + DestructiveSchemaOperation.RENAME_CLASS -> + realm.schema.rename(className, newClassName) + DestructiveSchemaOperation.SET_CLASS_NAME -> + objectSchema.setClassName(newClassName) + DestructiveSchemaOperation.REMOVE_FIELD -> + objectSchema.removeField(fieldName) + DestructiveSchemaOperation.RENAME_FIELD -> + objectSchema.renameField(fieldName, newFieldName) + DestructiveSchemaOperation.REMOVE_INDEX -> + objectSchema.removeIndex(fieldName) + DestructiveSchemaOperation.REMOVE_PRIMARY_KEY -> + objectSchema.removePrimaryKey() + DestructiveSchemaOperation.ADD_PRIMARY_KEY -> + objectSchema.addPrimaryKey(fieldName) + DestructiveSchemaOperation.ADD_FIELD_PRIMARY_KEY -> { + objectSchema.addField(newFieldName, String::class.java, FieldAttribute.PRIMARY_KEY) + } + else -> fail() + } + } + // Verify that operation is actually not performed in the transaction + assertTrue(realm.schema.contains(className)) + assertFalse(realm.schema.contains(newClassName)) + assertTrue(objectSchema.hasField(fieldName)) + + realm.cancelTransaction() + + // Verify that operation is actually not performed after cancelling + assertTrue(realm.schema.contains(className)) + assertFalse(realm.schema.contains(newClassName)) + assertTrue(objectSchema.hasField(fieldName)) + assertFalse(objectSchema.hasField(newFieldName)) + assertNotNull(objectSchema) + } + } + } + +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt index 7654efd98f..20e7db88ac 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt @@ -4,6 +4,7 @@ import io.realm.ErrorCode import io.realm.ObjectServerError import org.junit.Assert.assertEquals import org.junit.Assert.fail +import org.junit.rules.ErrorCollector // Helper methods for improving Kotlin unit tests. @@ -18,3 +19,13 @@ inline fun expectErrorCode(expectedCode: ErrorCode, method: () -> Unit) { assertEquals("Unexpected error code", expectedCode, e.errorCode) } } + +inline fun ErrorCollector.assertFailsWith(block : () -> Unit){ + try { + block() + } catch (e : Exception) { + if (e !is T) { + addError(e) + } + } +} diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 063b808c63..6d68fa0c9e 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -513,8 +513,7 @@ protected void checkIfValidAndInTransaction() { */ void checkNotInSync() { if (configuration.isSyncConfiguration()) { - throw new IllegalArgumentException("You cannot perform changes to a schema. " + - "Please update app and restart."); + throw new UnsupportedOperationException("You cannot perform destructive changes to a schema of a synced Realm"); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 6aabd33260..85bd1edb88 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -119,7 +119,7 @@ public String getClassName() { * @param className the new name for this class. * @throws IllegalArgumentException if className is {@code null} or an empty string, or its length exceeds 56 * characters. - * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable. + * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable or from a synced Realm. * @see RealmSchema#rename(String, String) */ public abstract RealmObjectSchema setClassName(String className); @@ -139,7 +139,8 @@ public String getClassName() { * @return the updated schema. * @throws IllegalArgumentException if the type isn't supported, field name is illegal or a field with that name * already exists. - * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable. + * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable or if adding a + * a field with {@link FieldAttribute#PRIMARY_KEY} attribute to a schema of a synced Realm. */ public abstract RealmObjectSchema addField(String fieldName, Class fieldType, FieldAttribute... attributes); @@ -201,7 +202,7 @@ public String getClassName() { * @param fieldName field name to remove. * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist. - * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable. + * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable or for a synced Realm. */ public abstract RealmObjectSchema removeField(String fieldName); @@ -212,7 +213,7 @@ public String getClassName() { * @param newFieldName the new field name. * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist or if the new field name already exists. - * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable. + * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable or for a synced Realm. */ public abstract RealmObjectSchema renameField(String currentFieldName, String newFieldName); @@ -258,7 +259,7 @@ public boolean hasIndex(String fieldName) { * @param fieldName field to remove index from. * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist or the field doesn't have an index. - * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable. + * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable or of a synced Realm. */ public abstract RealmObjectSchema removeIndex(String fieldName); @@ -271,7 +272,7 @@ public boolean hasIndex(String fieldName) { * @return the updated schema. * @throws IllegalArgumentException if field name doesn't exist, the field cannot be a primary key or it already * has a primary key defined. - * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable or this method is called on a synced Realm. + * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable or of a synced Realm. */ public abstract RealmObjectSchema addPrimaryKey(String fieldName); @@ -282,7 +283,7 @@ public boolean hasIndex(String fieldName) { * * @return the updated schema. * @throws IllegalArgumentException if the class doesn't have a primary key defined. - * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable. + * @throws UnsupportedOperationException if this {@link RealmObjectSchema} is immutable or of a synced Realm. */ public abstract RealmObjectSchema removePrimaryKey(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmSchema.java b/realm/realm-library/src/main/java/io/realm/RealmSchema.java index afdb0f0ca0..3843430edf 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmSchema.java @@ -111,7 +111,7 @@ public abstract RealmObjectSchema createWithPrimaryKeyField(String className, St * to it will throw an {@link IllegalStateException}. Removes those classes or fields first. * * @param className name of the class to remove. - * @throws UnsupportedOperationException if this {@link RealmSchema} is immutable. + * @throws UnsupportedOperationException if this {@link RealmSchema} is immutable or of a synced Realm. */ public abstract void remove(String className); @@ -121,7 +121,7 @@ public abstract RealmObjectSchema createWithPrimaryKeyField(String className, St * @param oldClassName old class name. * @param newClassName new class name. * @return a schema object for renamed class. - * @throws UnsupportedOperationException if this {@link RealmSchema} is immutable. + * @throws UnsupportedOperationException if this {@link RealmSchema} is immutable or of a synced Realm. */ public abstract RealmObjectSchema rename(String oldClassName, String newClassName); From e5eea79d13de0161ad81a833b39d5f15f83bc7e3 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 14 May 2020 16:35:13 +0100 Subject: [PATCH 1511/2110] Using the new ObjectStore Scheduler (#6838) --- CHANGELOG.md | 5 +- .../groovy/io/realm/gradle/PluginTest.groovy | 54 +++++++++++-------- .../io/realm/LinkingObjectsDynamicTests.java | 6 +++ .../io/realm/internal/OsSharedRealmTests.java | 6 --- .../io/realm/ObjectLevelPermissionsTest.java | 2 + .../cpp/io_realm_internal_OsSharedRealm.cpp | 1 + .../src/main/cpp/io_realm_internal_Table.cpp | 2 +- .../cpp/io_realm_internal_UncheckedRow.cpp | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- 9 files changed, 47 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0d09628bb..3dfe8f90e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. * `RealmResults.asJSON()` is no longer `@Beta`. +### Fixes +* If a DynamicRealm and Realm was opened for the same file they would share transaction state by accident. The implication was that writes to a `Realm` would immediately show up in the `DynamicRealm`. This has been fixed, so now it is required to call `refresh()` on the other Realm or wait for normal change listeners to detect the change. + ### Compatibility * Realm Object Server: 3.23.1 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). @@ -33,7 +36,7 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * The NDK has been upgraded from r10e to r21. * The compiler used for C++ code has changed from GCC to Clang. * OpenSSL used by Realms encryption layer has been upgraded from 1.0.2k to 1.1.1b. -* Updated to Object Store commit: 66199adbfffbe153e696309a53d4ec03e32c44e3. +* Updated to Object Store commit: 820b74e2378f111991877d43068a95d2b7a2e404. * Updated to Realm Sync 5.0.3. * Updated to Realm Core 6.0.4. diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index 3ced4b57ed..def91cc266 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -35,6 +35,14 @@ import static org.junit.Assert.assertEquals import static org.junit.Assert.assertTrue import static org.junit.Assert.fail +/** + * Comment about the order of repositories. + * The order of repositories do matter, and might need to change depending on the + * version of the Build Tools being used. See e.g.: + * + * https://stackoverflow.com/questions/55278227/android-gradle-build-error-artifacts-for-configuration-classpath/55278968#55278968 + * https://stackoverflow.com/questions/52968576/could-not-find-aapt2-proto-jar-com-android-tools-buildaapt2-proto0-3-1 + */ class PluginTest { private Project project @@ -54,6 +62,7 @@ class PluginTest { project.buildscript { repositories { mavenLocal() + mavenCentral() google() jcenter() } @@ -90,6 +99,7 @@ class PluginTest { project.buildscript { repositories { mavenLocal() + mavenCentral() jcenter() } dependencies { @@ -111,9 +121,8 @@ class PluginTest { void pluginAddsRightRepositories_noRepositorySet() { project.buildscript { repositories { - maven { - url 'https://maven.google.com/' - } + google() + mavenCentral() jcenter() } dependencies { @@ -139,8 +148,8 @@ class PluginTest { project.evaluate() - assertEquals(2, project.buildscript.repositories.size()) - assertEquals(4, project.repositories.size()) // The Android plugin adds 3 different local repos + assertEquals(3, project.buildscript.repositories.size()) + assertEquals(4, project.repositories.size()) assertEquals('jcenter.bintray.com', project.repositories.last().url.host) } @@ -148,10 +157,9 @@ class PluginTest { void pluginAddsRightRepositories_withRepositoriesSet() { project.buildscript { repositories { + google() + mavenCentral() jcenter() - maven { - url 'https://maven.google.com/' - } } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" @@ -160,6 +168,7 @@ class PluginTest { project.repositories { google() + mavenCentral() } def manifest = project.file("src/main/AndroidManifest.xml") @@ -180,11 +189,11 @@ class PluginTest { project.evaluate() - assertEquals(2, project.buildscript.repositories.size()) - assertEquals('maven.google.com', project.buildscript.repositories.last().url.host) + assertEquals(3, project.buildscript.repositories.size()) + assertEquals('jcenter.bintray.com', project.buildscript.repositories.last().url.host) - assertEquals(4, project.repositories.size()) - assertEquals('dl.google.com', project.repositories.last().url.host) + assertEquals(5, project.repositories.size()) + assertEquals('repo.maven.apache.org', project.repositories.last().url.host) } // Test for https://github.com/realm/realm-java/issues/6610 @@ -192,10 +201,9 @@ class PluginTest { void pluginAddsRightRepositories_withFlatDirs() { project.buildscript { repositories { + mavenCentral() + google() jcenter() - maven { - url 'https://maven.google.com/' - } } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" @@ -206,6 +214,7 @@ class PluginTest { flatDir { dirs 'libs' } + mavenCentral() google() } @@ -227,10 +236,10 @@ class PluginTest { project.evaluate() - assertEquals(2, project.buildscript.repositories.size()) - assertEquals('maven.google.com', project.buildscript.repositories.last().url.host) + assertEquals(3, project.buildscript.repositories.size()) + assertEquals('jcenter.bintray.com', project.buildscript.repositories.last().url.host) - assertEquals(5, project.repositories.size()) + assertEquals(6, project.repositories.size()) assertEquals('dl.google.com', project.repositories.last().url.host) } @@ -239,9 +248,8 @@ class PluginTest { void pluginAddsRightRepositories_withRepositoriesSetAfterPluginIsApplied() { project.buildscript { repositories { - maven { - url 'https://maven.google.com/' - } + google() + mavenCentral() jcenter() } dependencies { @@ -271,8 +279,8 @@ class PluginTest { project.evaluate() - assertEquals(2, project.buildscript.repositories.size()) - assertEquals('maven.google.com', project.buildscript.repositories.first().url.host) + assertEquals(3, project.buildscript.repositories.size()) + assertEquals('dl.google.com', project.buildscript.repositories.first().url.host) assertEquals(4, project.repositories.size()) assertEquals('dl.google.com', project.repositories.last().url.host) diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index 0280be4916..7add5a96e3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -291,6 +291,8 @@ public void execute(Realm realm) { } }); + dynamicRealm.refresh(); + final DynamicRealmObject target1 = dynamicRealm.where(BacklinksTarget.CLASS_NAME).equalTo(BacklinksTarget.FIELD_ID, 1).findFirst(); final RealmResults target1Sources = target1.linkingObjects(BacklinksSource.CLASS_NAME, BacklinksSource.FIELD_CHILD); assertNotNull(target1Sources); @@ -350,6 +352,8 @@ public void execute(Realm realm) { } }); + dynamicRealm.refresh(); + final DynamicRealmObject cat1 = dynamicRealm.where(Cat.CLASS_NAME).equalTo(Cat.FIELD_NAME, "cat1").findFirst(); final RealmResults cat1Owners = cat1.linkingObjects(Owner.CLASS_NAME, Owner.FIELD_CAT); assertNotNull(cat1Owners); @@ -402,6 +406,8 @@ public void execute(Realm realm) { } }); + dynamicRealm.refresh(); + final DynamicRealmObject target1 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 1L).findFirst(); final DynamicRealmObject target2 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 2L).findFirst(); final DynamicRealmObject target3 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 3L).findFirst(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java index 5d5b7aa180..c849019941 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java @@ -56,12 +56,6 @@ public void tearDown() { } } - @Test - public void getVersionID_without_read_or_write_transaction_throws() { - thrown.expectMessage("Cannot get versionId, this could be related to a non existing read/write transaction"); - sharedRealm.getVersionID(); - } - @Test public void hasTable() { assertFalse(sharedRealm.hasTable("MyTable")); diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java index 13183c24a0..fe1d717c10 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java +++ b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ObjectLevelPermissionsTest.java @@ -104,6 +104,7 @@ public void getPrivileges_realm_revokeLocally() { RealmPrivileges privileges = realm.getPrivileges(); assertNoAccess(privileges); + dynamicRealm.refresh(); privileges = dynamicRealm.getPrivileges(); assertNoAccess(privileges); } @@ -127,6 +128,7 @@ public void getPrivileges_class_revokeLocally() { ClassPrivileges privileges = realm.getPrivileges(AllJavaTypes.class); assertNoAccess(privileges); + dynamicRealm.refresh(); privileges = dynamicRealm.getPrivileges(AllJavaTypes.CLASS_NAME); assertNoAccess(privileges); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 1568b789c1..498fd40e0b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -68,6 +68,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSharedReal SharedRealm shared_realm; if (j_version_no == -1 && j_version_index == -1) { shared_realm = Realm::get_shared_realm(config); + shared_realm->read_group(); // Required to start the ObjectStore Scheduler. } else { VersionID version(static_cast(j_version_no), static_cast(j_version_index)); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index eeffa27781..23d04510c7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -863,6 +863,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFreeze(JNIEnv*, jclas { auto& shared_realm = *(reinterpret_cast(j_frozen_shared_realm_ptr)); TableRef table = TableRef(TBL_REF(j_table_ptr)); - TableRef* frozen_table = new TableRef(shared_realm->transaction().import_copy_of(table)); + TableRef* frozen_table = new TableRef(shared_realm->import_copy_of(table)); return reinterpret_cast(frozen_table); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index aa4b80a96d..2583dea5f0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -395,7 +395,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeFreeze(JNIEnv* try { Obj* obj = reinterpret_cast(j_native_row_ptr); auto frozen_realm = *(reinterpret_cast(j_frozen_realm_native_ptr)); - auto frozen_obj = new Obj(frozen_realm->transaction().import_copy_of(*obj)); + auto frozen_obj = new Obj(frozen_realm->import_copy_of(*obj)); return reinterpret_cast(frozen_obj); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 66199adbff..820b74e237 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 66199adbfffbe153e696309a53d4ec03e32c44e3 +Subproject commit 820b74e2378f111991877d43068a95d2b7a2e404 From eab31d04834b35d33019a17799899ec40ec8edd3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 14 May 2020 22:03:00 +0200 Subject: [PATCH 1512/2110] Fix merge mistake. --- .../src/main/java/io/realm/MutableRealmObjectSchema.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index 00f5f755b2..d1a43e37bd 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -303,9 +303,9 @@ public RealmObjectSchema transform(Function function) { if (original_size > Integer.MAX_VALUE) { throw new UnsupportedOperationException("Too many results to iterate: " + original_size); } - int size = (int) results.size(); + int size = (int) result.size(); for (int i = 0; i < size; i++) { - DynamicRealmObject obj = new DynamicRealmObject(realm, new CheckedRow(results.getUncheckedRow(i))); + DynamicRealmObject obj = new DynamicRealmObject(realm, new CheckedRow(result.getUncheckedRow(i))); if (obj.isValid()) { function.apply(obj); } From 34378700c7ec6c600dae600f8a1ec2928efeba23 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 14 May 2020 22:03:00 +0200 Subject: [PATCH 1513/2110] Fix merge mistake. --- .../io_realm_internal_objectstore_OsObjectBuilder.cpp | 11 ----------- .../main/java/io/realm/MutableRealmObjectSchema.java | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp index ca477d165a..0b20ee8b79 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp @@ -172,17 +172,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativ JavaContext ctx(env, shared_realm, object_schema); auto list = *reinterpret_cast(builder_ptr); JavaValue values = JavaValue(list); - CreatePolicy policy; - if (ignore_same_values) { - policy = CreatePolicy::UpdateModified; - } - else if (update_existing) { - policy = CreatePolicy::UpdateAll; - } - else { - policy = CreatePolicy::ForceCreate; - } - Object obj = Object::create(ctx, shared_realm, object_schema, values, policy); return reinterpret_cast(new Obj(obj.obj())); } diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index 00f5f755b2..d1a43e37bd 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -303,9 +303,9 @@ public RealmObjectSchema transform(Function function) { if (original_size > Integer.MAX_VALUE) { throw new UnsupportedOperationException("Too many results to iterate: " + original_size); } - int size = (int) results.size(); + int size = (int) result.size(); for (int i = 0; i < size; i++) { - DynamicRealmObject obj = new DynamicRealmObject(realm, new CheckedRow(results.getUncheckedRow(i))); + DynamicRealmObject obj = new DynamicRealmObject(realm, new CheckedRow(result.getUncheckedRow(i))); if (obj.isValid()) { function.apply(obj); } From 34211b1b930131cf294696af339b8d157a497122 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 15 May 2020 08:20:04 +0200 Subject: [PATCH 1514/2110] Fix compilation issues --- .../src/main/cpp/io_realm_internal_OsResults.cpp | 16 ---------------- .../src/main/cpp/java_object_accessor.hpp | 2 +- .../main/java/io/realm/internal/OsResults.java | 7 ------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index 57238ada5f..b73ab5c6fc 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -62,22 +62,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResults(JNI return reinterpret_cast(nullptr); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateResultsFromTable(JNIEnv* env, jclass, - jlong shared_realm_ptr, - jlong table_ptr) -{ - TR_ENTER() - try { - auto shared_realm = *(reinterpret_cast(shared_realm_ptr)); - auto table = reinterpret_cast(table_ptr); - Results results(shared_realm, *table); - auto wrapper = new ResultsWrapper(results); - return reinterpret_cast(wrapper); - } - CATCH_STD() - return reinterpret_cast(nullptr); -} - JNIEXPORT jlong JNICALL Java_io_realm_internal_OsResults_nativeCreateSnapshot(JNIEnv* env, jclass, jlong native_ptr) { try { diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp index 5bc7130640..948493e8c5 100644 --- a/realm/realm-library/src/main/cpp/java_object_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -498,7 +498,7 @@ inline Obj JavaContext::unbox(JavaValue const& v, CreatePolicy policy, ObjKey cu { if (v.get_type() == JavaValueType::Object) { return *v.get_object(); - } else if (!policy.create) { + } else if (policy == CreatePolicy::Skip) { return Obj(); } REALM_ASSERT(object_schema); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index f984a80906..fe40a175fc 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -302,11 +302,6 @@ public static OsResults createFromQuery(OsSharedRealm sharedRealm, TableQuery qu return createFromQuery(sharedRealm, query, new DescriptorOrdering()); } - public static OsResults createFromTable(OsSharedRealm sharedRealm, Table table) { - long ptr = nativeCreateResultsFromTable(sharedRealm.getNativePtr(), table.getNativePtr()); - return new OsResults(sharedRealm, table, ptr); - } - OsResults(OsSharedRealm sharedRealm, Table table, long nativePtr) { this.sharedRealm = sharedRealm; this.context = sharedRealm.context; @@ -674,8 +669,6 @@ public void load() { protected static native long nativeCreateResults(long sharedRealmNativePtr, long queryNativePtr, long descriptorOrderingPtr); - private static native long nativeCreateResultsFromTable(long sharedRealmNativePtr, long tablePtr); - private static native long nativeCreateSnapshot(long nativePtr); private static native long nativeFreeze(long nativePtr, long frozenRealmNativePtr); From 04cec39348494514755a7ce36eb48cb2979e648f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 15 May 2020 09:33:40 +0200 Subject: [PATCH 1515/2110] Use only working CI device --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index a820d8a31f..6fa2761da5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,7 @@ import groovy.json.JsonOutput def buildSuccess = false def rosContainer try { - node('android') { + node('docker-cph-01') { timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { From ed768981bb22f8cbf312c24ff02649acaedf4212 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 15 May 2020 11:49:05 +0200 Subject: [PATCH 1516/2110] Fix JSON tests --- .../java/io/realm/RealmResultsTests.java | 156 +++++++++--------- 1 file changed, 76 insertions(+), 80 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 5baa8bc51d..4e42bd42b9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1764,81 +1764,87 @@ public void asJSON() throws JSONException { assertEquals(1, all.size()); String json = all.asJSON(); final String expectedJSON = "[\n" + - " {\n" + - " \"_key\": 100,\n" + - " \"_key\":100," + - " \"columnString\": \"alltypes1\",\n" + - " \"columnLong\": 1337,\n" + - " \"columnFloat\": 3.1400001,\n" + - " \"columnDouble\": 0.89122999999999997,\n" + - " \"columnBoolean\": false,\n" + - " \"columnDate\": \"" + now + "\",\n" + - " \"columnBinary\": \"AQID\",\n" + - " \"columnMutableRealmInteger\": 0,\n" + - " \"columnRealmObject\": {\n" + - " \"_key\": 100,\n" + - " \"name\": \"dog1\",\n" + - " \"age\": 1,\n" + - " \"height\": 1.1,\n" + - " \"weight\": 10.100000381469727,\n" + - " \"hasTail\": true,\n" + + " {\n" + + " \"_key\":100,\n" + + " \"columnString\":\"alltypes1\",\n" + + " \"columnLong\":1337,\n" + + " \"columnFloat\":3.1400001e+00,\n" + + " \"columnDouble\":8.9122999999999997e-01,\n" + + " \"columnBoolean\":false,\n" + + " \"columnDate\": \"" + now + "\",\n" + + " \"columnBinary\":\"AQID\",\n" + + " \"columnDecimal128\":\"1.23456789E-1\",\n" + + " \"columnObjectId\":\"789abcdef0123456789abcde\",\n" + + " \"columnMutableRealmInteger\":0,\n" + + " \"columnRealmObject\":{\n" + + " \"_key\":100,\n" + + " \"name\":\"dog1\",\n" + + " \"age\":1,\n" + + " \"height\":1.1000000e+00,\n" + + " \"weight\":1.0100000381469727e+01,\n" + + " \"hasTail\":true,\n" + + " \"birthday\": \"" + now + "\",\n" + + " \"owner\":null\n" + + " },\n" + + " \"columnRealmList\":[\n" + + " {\n" + + " \"_key\":101,\n" + + " \"name\":\"dog2\",\n" + + " \"age\":2,\n" + + " \"height\":2.0999999e+00,\n" + + " \"weight\":2.0100000381469727e+01,\n" + + " \"hasTail\":false,\n" + " \"birthday\": \"" + now + "\",\n" + - " \"owner\": null\n" + - " },\n" + - " \"columnRealmList\": [\n" + - " {\n" + - " \"_key\": 101,\n" + - " \"name\": \"dog2\",\n" + - " \"age\": 2,\n" + - " \"height\": 2.0999999,\n" + - " \"weight\": 20.100000381469727,\n" + - " \"hasTail\": false,\n" + - " \"birthday\": \"" + now + "\",\n" + - " \"owner\": null\n" + - " },\n" + - " {\n" + - " \"_key\": 102,\n" + - " \"name\": \"dog3\",\n" + - " \"age\": 3,\n" + - " \"height\": 3.0999999,\n" + - " \"weight\": 30.100000381469727,\n" + - " \"hasTail\": true,\n" + - " \"birthday\": \"" + now + "\",\n" + - " \"owner\": {\n" + - " \"_key\": 0,\n" + - " \"name\": \"Dog owner 1\",\n" + - " \"dogs\": [],\n" + - " \"cat\": null\n" + - " }\n" + + " \"owner\":null\n" + + " },\n" + + " {\n" + + " \"_key\":102,\n" + + " \"name\":\"dog3\",\n" + + " \"age\":3,\n" + + " \"height\":3.0999999e+00,\n" + + " \"weight\":3.0100000381469727e+01,\n" + + " \"hasTail\":true,\n" + + " \"birthday\": \"" + now + "\",\n" + + " \"owner\":{\n" + + " \"_key\":0,\n" + + " \"name\":\"Dog owner 1\",\n" + + " \"dogs\":[\n" + + "\n" + + " ],\n" + + " \"cat\":null\n" + " }\n" + - " ],\n" + - " \"columnStringList\": [\n" + - " \"Foo\",\n" + - " \"Bar\"\n" + - " ],\n" + - " \"columnBinaryList\": [],\n" + - " \"columnBooleanList\": [\n" + - " false,\n" + - " true\n" + - " ],\n" + - " \"columnLongList\": [\n" + - " 1000,\n" + - " 2000\n" + - " ],\n" + - " \"columnDoubleList\": [\n" + - " 1.123,\n" + - " 5.3209999999999997\n" + - " ],\n" + - " \"columnFloatList\": [\n" + - " 0.12,\n" + - " 0.13\n" + - " ],\n" + - " \"columnDateList\": [\n" + + " }\n" + + " ],\n" + + " \"columnStringList\":[\n" + + " \"Foo\",\n" + + " \"Bar\"\n" + + " ],\n" + + " \"columnBinaryList\":[\n" + + "\n" + + " ],\n" + + " \"columnBooleanList\":[\n" + + " false,\n" + + " true\n" + + " ],\n" + + " \"columnLongList\":[\n" + + " 1000,\n" + + " 2000\n" + + " ],\n" + + " \"columnDoubleList\":[\n" + + " 1.1230000000000000e+00,\n" + + " 5.3209999999999997e+00\n" + + " ],\n" + + " \"columnFloatList\":[\n" + + " 1.2000000e-01,\n" + + " 1.3000000e-01\n" + + " ],\n" + + " \"columnDateList\":[\n" + " \"" + now + "\",\n" + " \"" + now + "\"\n" + - " ]\n" + - " }\n" + + " ]\n" + + " }\n" + "]"; + JSONAssert.assertEquals(expectedJSON, json, false); } @@ -1885,11 +1891,6 @@ public void asJSON_cycles() throws JSONException { " \"otherObject\": null,\n" + " \"objects\": []\n" + " },\n" + - " \"object\": {\n" + - " \"table\": \"class_CyclicType\",\n" + - " \"key\": 0\n" + - " },\n" + - " \"otherObject\": null,\n" + " \"otherObject\": null,\n" + " \"objects\": []\n" + " },\n" + @@ -1910,11 +1911,6 @@ public void asJSON_cycles() throws JSONException { " \"otherObject\": null,\n" + " \"objects\": []\n" + " },\n" + - " \"object\": {\n" + - " \"table\": \"class_CyclicType\",\n" + - " \"key\": 1\n" + - " },\n" + - " \"otherObject\": null,\n" + " \"otherObject\": null,\n" + " \"objects\": []\n" + " }\n" + From 856b274c539d3e151250db9ed2dc0e37715c8edb Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 15 May 2020 15:04:00 +0200 Subject: [PATCH 1517/2110] Merge changes from Core 6 + upgrade to latest ObjectStore (#6855) --- .../workflows/gradle-wrapper-validation.yml | 10 +++++ CHANGELOG.md | 24 +++++++++++- README.md | 8 ++-- .../processor/RealmProxyClassGenerator.kt | 3 ++ .../realm/some_test_AllTypesRealmProxy.java | 2 +- .../realm/some_test_NullTypesRealmProxy.java | 4 +- .../assets/ios/0.98.0-alltypes-mix.realm | Bin 0 -> 4096 bytes .../java/io/realm/IOSRealmTests.java | 1 + .../io/realm/LinkingObjectsDynamicTests.java | 6 +++ .../java/io/realm/RealmInMemoryTest.java | 35 +++++++++++++++++- .../java/io/realm/RealmResultsTests.java | 3 +- .../io/realm/internal/OsSharedRealmTests.java | 6 --- .../cpp/io_realm_internal_OsSharedRealm.cpp | 1 + realm/realm-library/src/main/cpp/object-store | 2 +- version.txt | 2 +- 15 files changed, 88 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/gradle-wrapper-validation.yml create mode 100644 realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-mix.realm diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 0000000000..405a2b3065 --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,10 @@ +name: "Validate Gradle Wrapper" +on: [push, pull_request] + +jobs: + validation: + name: "Validation" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index a3ed9045ef..e9eb46c364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,10 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. * `RealmResults.asJSON()` is no longer `@Beta`. +* The default `toString()` for proxy objects now print the length of binary fields. (Issue [#6767](https://github.com/realm/realm-java/pull/6767)) + +### Fixes +* If a DynamicRealm and Realm was opened for the same file they would share transaction state by accident. The implication was that writes to a `Realm` would immediately show up in the `DynamicRealm`. This has been fixed, so now it is required to call `refresh()` on the other Realm or wait for normal change listeners to detect the change. ### Compatibility * Realm Object Server: 3.23.1 or later. @@ -59,10 +63,28 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible * The NDK has been upgraded from r10e to r21. * The compiler used for C++ code has changed from GCC to Clang. * OpenSSL used by Realms encryption layer has been upgraded from 1.0.2k to 1.1.1b. -* Updated to Object Store commit: 66199adbfffbe153e696309a53d4ec03e32c44e3. +* Updated to Object Store commit: 820b74e2378f111991877d43068a95d2b7a2e404. * Updated to Realm Sync 5.0.3. * Updated to Realm Core 6.0.4. +### Credits +* Thanks to @joxon for better support for binary fields in proxy objects. + + +## 6.1.0(2020-01-17) + +### Fixed +* None. + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v9 (Reads and upgrades all previous formats) +* APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. + +### Internal +* None. + + ## 6.1.0(2020-01-17) diff --git a/README.md b/README.md index 055be13bf0..60085b34ec 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,8 @@ In case you don't want to use the precompiled version, you can build Realm yours ### Prerequisites * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. - * The latest stable version of Android Studio. Currently [3.5.3](https://developer.android.com/studio/). - * Download & install the Android SDK **Build-Tools 27.0.2**, **Android Oreo (API 27)** (for example through Android Studio’s **Android SDK Manager**). + * The latest stable version of Android Studio. Currently [3.6.2](https://developer.android.com/studio/). + * Download & install the Android SDK **Build-Tools 28.0.3**, **Android Pie (API 28)** (for example through Android Studio’s **Android SDK Manager**). * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). * Install the NDK (currently r21) from the SDK Manager in Android Studio or using the [website](https://developer.android.com/ndk/downloads). If downloaded You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library`. The download will unzip as the directory `android-ndk-r21`. @@ -194,7 +194,7 @@ The repository is organized into six Gradle projects: * `realm-transformer`: it contains the bytecode transformer. * `gradle-plugin`: it contains the Gradle plugin. * `examples`: it contains the example projects. This project directly depends on `gradle-plugin` which adds a dependency to the artifacts produced by `realm`. - * The root folder is another Gradle project. All it does is orchestrate the other jobs + * The root folder is another Gradle project. All it does is orchestrate the other jobs. This means that `./gradlew clean` and `./gradlew cleanExamples` will fail if `assembleExamples` has not been executed first. Note that IntelliJ [does not support multiple projects in the same window](https://youtrack.jetbrains.com/issue/IDEABKL-6118#) @@ -239,7 +239,7 @@ A docker image can be built from `tools/sync_test_server/Dockerfile` to run the To run a testing server locally: -1. Install [docker](https://www.docker.com/products/overview). +1. Install [docker](https://www.docker.com/products/overview) and run it. 2. Run `tools/sync_test_server/start_server.sh`: diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index c597137d5c..26b97deab7 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -1679,6 +1679,9 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi Utils.isMutableRealmInteger(field) -> { emitStatement("stringBuilder.append(%s().get())", metadata.getInternalGetter(fieldName)) } + Utils.isByteArray(field) -> { + emitStatement("stringBuilder.append(\"binary(\" + %s().length + \")\")", metadata.getInternalGetter(fieldName)) + } else -> { if (metadata.isNullable(field)) { emitStatement("stringBuilder.append(%s() != null ? %s() : \"null\")", metadata.getInternalGetter(fieldName), metadata.getInternalGetter(fieldName)) diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index 359a33e03e..2a61aac1bf 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -2645,7 +2645,7 @@ public String toString() { stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{columnBinary:"); - stringBuilder.append(realmGet$columnBinary()); + stringBuilder.append("binary(" + realmGet$columnBinary().length + ")"); stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{columnMutableRealmInteger:"); diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index fff9196d84..20327c69ca 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -4698,11 +4698,11 @@ public String toString() { stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{fieldBytesNotNull:"); - stringBuilder.append(realmGet$fieldBytesNotNull()); + stringBuilder.append("binary(" + realmGet$fieldBytesNotNull().length + ")"); stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{fieldBytesNull:"); - stringBuilder.append(realmGet$fieldBytesNull() != null ? realmGet$fieldBytesNull() : "null"); + stringBuilder.append("binary(" + realmGet$fieldBytesNull().length + ")"); stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{fieldByteNotNull:"); diff --git a/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-mix.realm b/realm/realm-library/src/androidTest/assets/ios/0.98.0-alltypes-mix.realm new file mode 100644 index 0000000000000000000000000000000000000000..0bb0cb607aad6bbad4ca2aee153ccaeb233f5839 GIT binary patch literal 4096 zcmeHHJ#5oJ6n?%tH*O&%Obr842Zk){83Qenk&uW)hit`d42kN-Q4%PfIEkfW$BZ4r z)-fC1=*XC{VyeVIB$jUBdv~^zAS$urr|9m!_jm8U_ne7KM(WD$JCC=YN{O>Wv`(bD z2UdT}`tUFu1kGR&++yF@kAi-G_xa0LPoik&xEuDD9oIAaR;wK~myJO8i8Ki!v6h)M z6@j}4yMtga3KMk!xZ8`nVQ+BED!fMXj!;d{h~yca(}Xp^hf=tvWTQZXz_{k|dU0OkGZE!Or;`&WFTD z6WUNL5q*C2l`96o5A`idaW9UetvE_`UcVLh26IKB-C00Ht5Mu}J2#WZ_{ChO-b8V* z5K(Bx@4#{vv9fnOm?I*uKj^g=GOl12mhAh>ZCx|C7cx;S1?B;~6jNeKCA`>(rY-Ps zl%Rw!HFQRf7DLn5)Ndv655dv}EUErqck~@%`e=3ofo&x^X_}p*k}S(j{yt#$jHBii z<(nNxO*O2%qo3y)lfJB*EQy~OiAIKTkl*p~S+Vog2@df* z9tp#jOj%;Qjd^!B+y`l0xL2LZGkGpAjDK2Z%_Ll^PhQDuIZgZ!C%(y>tPajoaZVg9 zJ;nF9cxMYID;5tec~EIq8y*l>C{tNEn-x`6zCs+t=LL43(66-JP-zNrZ&#(NxgXzW z({gAQry2bXW0=w;n(h2O&t@NI#vPcy)0vT<(9Q3`pA5w3;4~iC_WMSW>zN1mw7O*X zd3eRAAHTjV1!Z{+&oJ3JXeq5u)liMpSY4`#x>jYk;#S?d+pzRlKg_}FMQr##ahd-* z^DoDutft1p;tBL6^XKRjMql1DZ{elE^M^hRU1xLTj@?Ta_2!Z@kTZ}okTZ}okTZ}o KkTdX~Gw>TrA-qcf literal 0 HcmV?d00001 diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index bbbd8c5036..7fa55d6614 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -224,4 +224,5 @@ private byte[] getIOSKey() { } return keyData; } + } diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index ed2b7b4fc9..7e3bbdef1f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -307,6 +307,8 @@ public void execute(Realm realm) { } }); + dynamicRealm.refresh(); + final DynamicRealmObject target1 = dynamicRealm.where(BacklinksTarget.CLASS_NAME).equalTo(BacklinksTarget.FIELD_ID, 1).findFirst(); final RealmResults target1Sources = target1.linkingObjects(BacklinksSource.CLASS_NAME, BacklinksSource.FIELD_CHILD); assertNotNull(target1Sources); @@ -366,6 +368,8 @@ public void execute(Realm realm) { } }); + dynamicRealm.refresh(); + final DynamicRealmObject cat1 = dynamicRealm.where(Cat.CLASS_NAME).equalTo(Cat.FIELD_NAME, "cat1").findFirst(); final RealmResults cat1Owners = cat1.linkingObjects(Owner.CLASS_NAME, Owner.FIELD_CAT); assertNotNull(cat1Owners); @@ -418,6 +422,8 @@ public void execute(Realm realm) { } }); + dynamicRealm.refresh(); + final DynamicRealmObject target1 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 1L).findFirst(); final DynamicRealmObject target2 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 2L).findFirst(); final DynamicRealmObject target3 = dynamicRealm.where(AllJavaTypes.CLASS_NAME).equalTo(AllJavaTypes.FIELD_ID, 3L).findFirst(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java index 8ca8360eae..7aefc6841a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInMemoryTest.java @@ -184,6 +184,33 @@ public void writeCopyTo() { } } + // Tests writeCopyTo result when called in a transaction. + @Test + public void writeCopyToInTransaction() { + String fileName = IDENTIFIER + ".realm"; + RealmConfiguration conf = configFactory.createConfigurationBuilder() + .name(fileName) + .build(); + + Realm.deleteRealm(conf); + + testRealm.beginTransaction(); + Dog dog = testRealm.createObject(Dog.class); + dog.setName("DinoDog"); + + // Write copy to destination file in transaction. + // Check if the new data would be written into the file. + testRealm.writeCopyTo(new File(configFactory.getRoot(), fileName)); + Realm onDiskRealm = Realm.getInstance(conf); + assertEquals(1, onDiskRealm.where(Dog.class).count()); + + testRealm.commitTransaction(); + + assertEquals(1, testRealm.where(Dog.class).count()); + onDiskRealm.close(); + } + + // Test below scenario: // 1. Creates a in-memory Realm instance in the main thread. // 2. Creates a in-memory Realm with same name in another thread. @@ -235,7 +262,9 @@ public void run() { // Waits until the worker thread started. workerCommittedLatch.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS); - if (threadError[0] != null) { throw threadError[0]; } + if (threadError[0] != null) { + throw threadError[0]; + } // Refreshes will be ran in the next loop, manually refreshes it here. testRealm.refresh(); @@ -256,7 +285,9 @@ public void run() { // Waits until the worker thread finished. workerClosedLatch.await(TestHelper.SHORT_WAIT_SECS, TimeUnit.SECONDS); - if (threadError[0] != null) { throw threadError[0]; } + if (threadError[0] != null) { + throw threadError[0]; + } // Since all previous Realm instances has been closed before, below will create a fresh new in-mem-realm instance. testRealm = Realm.getInstance(inMemConf); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 0c29521fd0..b892f38fe9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1927,6 +1927,7 @@ public void asJSON() throws JSONException { .equalTo("columnString", "alltypes1").findAll(); assertEquals(1, all.size()); String json = all.asJSON(); + final String expectedJSON = "[\n" + " {\n" + " \"_key\":100,\n" + @@ -2020,7 +2021,7 @@ public void asJSON() throws JSONException { " ]\n" + " }\n" + "]"; - JSONAssert.assertEquals(expectedJSON, json, true); + JSONAssert.assertEquals(expectedJSON, json, false); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java index 4fbd0ffbe7..e5c5b7cbaa 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java @@ -56,12 +56,6 @@ public void tearDown() { } } - @Test - public void getVersionID_without_read_or_write_transaction_throws() { - thrown.expectMessage("Cannot get versionId, this could be related to a non existing read/write transaction"); - sharedRealm.getVersionID(); - } - @Test public void hasTable() { assertFalse(sharedRealm.hasTable("MyTable")); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 9781e9f170..a51b69d79b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -67,6 +67,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSharedReal SharedRealm shared_realm; if (j_version_no == -1 && j_version_index == -1) { shared_realm = Realm::get_shared_realm(config); + shared_realm->read_group(); // Required to start the ObjectStore Scheduler. } else { VersionID version(static_cast(j_version_no), static_cast(j_version_index)); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 9a1d0f5804..73bfe58c21 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 9a1d0f5804ab265a54ff8659bf6ca828a5520fc1 +Subproject commit 73bfe58c21f647bfabfae1631850f362ff59e885 diff --git a/version.txt b/version.txt index f702ec2902..9dc23dfda1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-SNAPSHOT +10.0.0-SNAPSHOT \ No newline at end of file From 7fd8b86539b8aa910d1bd84e7cb47b8e21333813 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 15 May 2020 18:18:04 +0200 Subject: [PATCH 1518/2110] Fix JSON tests --- .../src/androidTest/java/io/realm/RealmResultsTests.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index 4e42bd42b9..c129106c82 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -1773,8 +1773,6 @@ public void asJSON() throws JSONException { " \"columnBoolean\":false,\n" + " \"columnDate\": \"" + now + "\",\n" + " \"columnBinary\":\"AQID\",\n" + - " \"columnDecimal128\":\"1.23456789E-1\",\n" + - " \"columnObjectId\":\"789abcdef0123456789abcde\",\n" + " \"columnMutableRealmInteger\":0,\n" + " \"columnRealmObject\":{\n" + " \"_key\":100,\n" + From 6f5737c9cdd0f80c6d0851307c9d0332f16b6a66 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 15 May 2020 19:28:53 +0200 Subject: [PATCH 1519/2110] Re-enable both CI devices + fix caching of Docker image (#6858) --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7b874d2041..998bdfbe99 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,7 +11,7 @@ def dockerNetworkId = UUID.randomUUID().toString() def releaseBranches = ['master', 'next-major', 'v10'] // Branches from which we release SNAPSHOT's def currentBranch = env.CHANGE_BRANCH try { - node('docker-cph-01') { // FIXME: Only working Slave + node('android') { timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { @@ -45,7 +45,7 @@ try { def buildEnv = null stage('Prepare Docker Images') { - buildEnv = buildDockerEnv("ci/realm-java:v10", push: env.BRANCH_NAME == 'v10') // TODO Should be renamed to 'master' when merged there. + buildEnv = buildDockerEnv("ci/realm-java:v10", push: currentBranch == 'v10') // TODO Should be renamed to 'master' when merged there. def props = readProperties file: 'dependencies.list' echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") From ce7df4c52e5854362b5a2d549532385527847079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 15 May 2020 21:32:43 +0200 Subject: [PATCH 1520/2110] Merge Stitch and Realm SDKs - 3: remote collection count, insert, delete and findOne (#6839) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First iteration: added GMS library (possibly temporarily) to avoid introducing immediate breaking changes in how we process asynchronous operations with AsyncRealmTask. All original Stitch interfaces and proxies have been discarded in favour of Java classes (although this approach might be changed). Some interfaces connected to the collection's iterables have been omitted as it is unclear whether they will be needed or not for the time being. * Added licences to class headers plus a bit of cleanup * Added latest API methods and necessary classes * Added remote mongo client and remote database, their respective Os files and part of the native logic * Moved JNI callbacks outside RealmApp and added more remote collection classes * Updated object store branch to v10 and fixed wrong use of count call * Cleanup * Added task-related classes from Stitch * First steps towards using tasks for the count operation * Added preliminary collection test, only with scaffolding for "count", but still not working as the OS code isn't fully ready yet. Moved Realm initialisation in test cases outside TestRealmApp to setUp method as agreed internally, plus fixed some wrong implementation in the interop layer. Also updated dependencies list to fetch sync version 10 alpha 9 instead of 8. * Fixed wrong finalizer methods and cleanup to interop files * Moved TaskUtils to tests * test * cleanup * Moved classes * Updated OS pointer * Moved classes * Removed duplicate entries in CMakeLists * Added documentClass property to internal collection class * updated OS pointer * updated OS pointer * added suppresswarnings for ignored futures - issue inherited from Stitch's task framework - test to see if Jenkins swallows it * wip * Added test for Task.blockingGet and wip on insertOne * Added insertmany * Added more meaningful tests for count and insert. Temporarily commented out some code in EmailPasswordAuth.cpp after updating OS to v10. Now the remoteMongoClient is fetched as a shared_ptr in our interop layer. Added codec handling for RemoteMongoDatabase and document class for RemoteMongoCollection * Work in progress - insertMany and interop * Added deleteOne * Added deleteMany and adjusted visibility of OS constructors * wip * Updated pointer to OS * Restored curly braces * Removed unnecessary codec parameter in getDatabase * Added findOne and proper use of the BSON parsing protocol for handling, delivering and decoding results from the JNI * Updated OS pointer to branch that contains parsing fixes - update to OS v10 as soon as it is merged * Added missing findOne implementations and updated OS pointer * fixed unboxing that caused findbugs to complain * First batch of cleanup * Addressed error handling in interop layer plus more cleanup * Moved classes to new packages and removed "remote" prefix from class names * Restored wrongly removed public modifier to method * Renamed OS interop classes * Cleanup * Final round of cleanup and updated pointer to OS that allegedly fixes failing stress test * Fixed broken tests Co-authored-by: Eduardo López --- .../kotlin/io/realm/ApiKeyAuthTests.kt | 2 + .../kotlin/io/realm/EmailPasswordAuthTests.kt | 2 + .../kotlin/io/realm/RealmAppTests.kt | 2 + .../kotlin/io/realm/RealmUserTests.kt | 2 + .../io/realm/mongodb/MongoCollectionTest.kt | 279 +++++++++++++++++ .../transport/OsJavaNetworkTransportTests.kt | 8 +- .../kotlin/io/realm/util/TaskExt.kt | 49 +++ .../kotlin/io/realm/util/TaskExtKtTest.kt | 54 ++++ .../realm-library/src/main/cpp/CMakeLists.txt | 12 +- ...lm_internal_objectstore_OsMongoClient.cpp} | 22 +- ...internal_objectstore_OsMongoCollection.cpp | 205 ++++++++++++ ..._internal_objectstore_OsMongoDatabase.cpp} | 12 +- ...al_objectstore_OsRemoteMongoCollection.cpp | 66 ---- .../src/main/cpp/jni_util/bson_util.cpp | 1 + .../java/io/realm/ApiKeyAuth.java | 13 +- .../java/io/realm/EmailPasswordAuth.java | 16 +- .../objectServer/java/io/realm/RealmApp.java | 21 +- .../java/io/realm/RealmAppConfiguration.java | 6 +- .../objectServer/java/io/realm/RealmUser.java | 22 +- .../internal/common/ThreadDispatcher.java | 1 - .../realm/internal/network/ResultHandler.java | 40 +++ ...oteMongoClient.java => OsMongoClient.java} | 10 +- .../objectstore/OsMongoCollection.java | 293 ++++++++++++++++++ ...ongoDatabase.java => OsMongoDatabase.java} | 28 +- .../objectstore/OsRemoteMongoCollection.java | 49 --- .../MongoClient.java} | 22 +- .../MongoCollection.java} | 268 ++++++++-------- .../MongoDatabase.java} | 53 ++-- .../mongodb/{ => mongo}/MongoNamespace.java | 2 +- .../iterable}/RemoteAggregateIterable.java | 2 +- .../iterable}/RemoteFindIterable.java | 2 +- .../options}/RemoteCountOptions.java | 2 +- .../RemoteFindOneAndModifyOptions.java | 2 +- .../options}/RemoteFindOptions.java | 2 +- .../options}/RemoteInsertManyResult.java | 2 +- .../options}/RemoteUpdateOptions.java | 2 +- .../result}/RemoteDeleteResult.java | 2 +- .../result}/RemoteInsertOneResult.java | 2 +- .../result}/RemoteUpdateResult.java | 2 +- .../java/io/realm/TestRealmApp.kt | 1 - 40 files changed, 1193 insertions(+), 388 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExt.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExtKtTest.kt rename realm/realm-library/src/main/cpp/{io_realm_internal_objectstore_OsRemoteMongoClient.cpp => io_realm_internal_objectstore_OsMongoClient.cpp} (74%) create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp rename realm/realm-library/src/main/cpp/{io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp => io_realm_internal_objectstore_OsMongoDatabase.cpp} (83%) delete mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java rename realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/{OsRemoteMongoClient.java => OsMongoClient.java} (80%) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java rename realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/{OsRemoteMongoDatabase.java => OsMongoDatabase.java} (64%) delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoCollection.java rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{RemoteMongoClient.java => mongo/MongoClient.java} (58%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{RemoteMongoCollection.java => mongo/MongoCollection.java} (71%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{RemoteMongoDatabase.java => mongo/MongoDatabase.java} (50%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{ => mongo}/MongoNamespace.java (99%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote/aggregate => mongo/iterable}/RemoteAggregateIterable.java (95%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote/find => mongo/iterable}/RemoteFindIterable.java (97%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote => mongo/options}/RemoteCountOptions.java (96%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote => mongo/options}/RemoteFindOneAndModifyOptions.java (99%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote => mongo/options}/RemoteFindOptions.java (98%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote => mongo/options}/RemoteInsertManyResult.java (97%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote => mongo/options}/RemoteUpdateOptions.java (97%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote => mongo/result}/RemoteDeleteResult.java (96%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote => mongo/result}/RemoteInsertOneResult.java (96%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{remote => mongo/result}/RemoteUpdateResult.java (98%) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt index 5af23bb84d..43616060b2 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt @@ -17,6 +17,7 @@ package io.realm import androidx.test.annotation.UiThreadTest import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.log.LogLevel import io.realm.log.RealmLog @@ -67,6 +68,7 @@ class ApiKeyAuthTests { @Before fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) admin = ServerAdmin() app = TestRealmApp() user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index 80a383a5ec..e15b7ff2e7 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -17,6 +17,7 @@ package io.realm import androidx.test.annotation.UiThreadTest import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.log.LogLevel import io.realm.log.RealmLog @@ -60,6 +61,7 @@ class EmailPasswordAuthTests { @Before fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) app = TestRealmApp() RealmLog.setLevel(LogLevel.DEBUG) admin = ServerAdmin() diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt index 8051deac61..1440bf57b7 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -16,6 +16,7 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.rule.BlockingLooperThread import org.junit.After @@ -36,6 +37,7 @@ class RealmAppTests { @Before fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) app = TestRealmApp() admin = ServerAdmin() } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt index 3eb18f936c..939a68d73a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt @@ -16,6 +16,7 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.rule.BlockingLooperThread import org.junit.After @@ -37,6 +38,7 @@ class RealmUserTests { @Before fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) app = TestRealmApp() admin = ServerAdmin() anonUser = app.login(RealmCredentials.anonymous()) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt new file mode 100644 index 0000000000..d3433dfae1 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt @@ -0,0 +1,279 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.mongodb + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.* +import io.realm.mongodb.mongo.MongoClient +import io.realm.mongodb.mongo.MongoCollection +import io.realm.mongodb.mongo.MongoDatabase +import io.realm.mongodb.mongo.options.RemoteCountOptions +import io.realm.util.blockingGetResult +import org.bson.Document +import org.bson.types.ObjectId +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertNull + +private const val SERVICE_NAME = "BackingDB" // it comes from the test server's BackingDB/config.json +private const val DATABASE_NAME = "test_data" // same as above +private const val COLLECTION_NAME = "COLLECTION_NAME" +private const val KEY_1 = "KEY" +private const val VALUE_1 = "666" + +@RunWith(AndroidJUnit4::class) +class MongoCollectionTest { + + private lateinit var app: TestRealmApp + private lateinit var user: RealmUser + private lateinit var client: MongoClient + private lateinit var database: MongoDatabase + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestRealmApp() + user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + client = user.getMongoClient(SERVICE_NAME) + database = client.getDatabase(DATABASE_NAME) + } + + @After + fun tearDown() { + // FIXME: probably not the best way to "reset" the state + with(getCollectionInternal(COLLECTION_NAME)) { + deleteMany(Document()).blockingGetResult() + } + + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun insertOne() { + with(getCollectionInternal(COLLECTION_NAME)) { + assertEquals(0, count().blockingGetResult()) + val doc = Document(mapOf("KEY_1" to "WORLD_1", "KEY_2" to "WORLD_2")) + insertOne(doc).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // FIXME: revisit this later +// val doc = Document("hello", "world") +// doc["_id"] = ObjectId() +// +// assertEquals(doc.getObjectId("_id"), insertOne(doc).blockingGetResult()!!.insertedId.asObjectId().value) +// assertFailsWith(ObjectServerError::class) { insertOne(doc).blockingGetResult() } +// +// val doc2 = Document("hello", "world") +// assertNotEquals(doc.getObjectId("_id"), insertOne(doc2).blockingGetResult()!!.insertedId.asObjectId().value) + } + } + + @Test + fun insertMany() { + with(getCollectionInternal(COLLECTION_NAME)) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document(KEY_1, VALUE_1) + val doc1 = Document(rawDoc) + val doc2 = Document(rawDoc) + val doc3 = Document(rawDoc) + val doc4 = Document("foo", "bar") + val manyDocuments = listOf(doc1, doc2, doc3, doc4) + + insertMany(manyDocuments) + .blockingGetResult() + .let { assertEquals(manyDocuments.size, it!!.insertedIds.size) } + + assertEquals(manyDocuments.size.toLong(), count().blockingGetResult()) + assertEquals(3, count(rawDoc).blockingGetResult()) + assertEquals(1, count(Document("foo", "bar")).blockingGetResult()) + assertEquals(0, count(Document("bar", "foo")).blockingGetResult()) + } + } + + @Test + fun count() { + with(getCollectionInternal(COLLECTION_NAME)) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document("hello", "world") + val doc1 = Document(rawDoc) + val doc2 = Document(rawDoc) + insertOne(doc1).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + insertOne(doc2).blockingGetResult() + assertEquals(2, count().blockingGetResult()) + + assertEquals(2, count(rawDoc).blockingGetResult()) + assertEquals(0, count(Document("hello", "Friend")).blockingGetResult()) + assertEquals(1,count(rawDoc, RemoteCountOptions().limit(1)).blockingGetResult()) + + // FIXME: investigate error handling for malformed payloads +// try { +// count(Document("\$who", 1)).blockingGetResult() +// Assert.fail() +// } catch (ex: ExecutionException) { +// // FIXME: add assertion +// val a = 0 +// } + } + } + + @Test + fun deleteOne_singleDocument() { + with(getCollectionInternal(COLLECTION_NAME)) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document(KEY_1, VALUE_1) + val doc1 = Document(rawDoc) + + insertOne(doc1).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + assertEquals(1, deleteOne(doc1).blockingGetResult()!!.deletedCount) + assertEquals(0, count().blockingGetResult()) + } + } + + @Test + fun deleteOne_listOfDocuments() { + with(getCollectionInternal(COLLECTION_NAME)) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document(KEY_1, VALUE_1) + val doc1 = Document(rawDoc) + val doc1b = Document(rawDoc) + val doc2 = Document("foo", "bar") + val doc3 = Document("42", "666") + insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() + assertEquals(1, deleteOne(rawDoc).blockingGetResult()!!.deletedCount) + assertEquals(1, deleteOne(Document()).blockingGetResult()!!.deletedCount) + } + } + + @Test + fun deleteMany_singleDocument() { + with(getCollectionInternal(COLLECTION_NAME)) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document(KEY_1, VALUE_1) + val doc1 = Document(rawDoc) + + insertOne(doc1).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + assertEquals(1, deleteMany(doc1).blockingGetResult()!!.deletedCount) + assertEquals(0, count().blockingGetResult()) + } + } + + @Test + fun deleteMany_listOfDocuments() { + with(getCollectionInternal(COLLECTION_NAME)) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document(KEY_1, VALUE_1) + val doc1 = Document(rawDoc) + val doc1b = Document(rawDoc) + val doc2 = Document("foo", "bar") + val doc3 = Document("42", "666") + insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() + assertEquals(2, deleteMany(rawDoc).blockingGetResult()!!.deletedCount) // two docs will be deleted + assertEquals(2, count().blockingGetResult()) // two docs still present + assertEquals(2, deleteMany(Document()).blockingGetResult()!!.deletedCount) // delete all + assertEquals(0, count().blockingGetResult()) + + insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() + assertEquals(4, deleteMany(Document()).blockingGetResult()!!.deletedCount) // delete all + assertEquals(0, count().blockingGetResult()) + } + } + + @Test + fun findOne() { + with(getCollectionInternal(COLLECTION_NAME)) { + val doc1 = Document("hello", "world1") + val doc2 = Document("hello", "world2") + val doc3 = Document("hello", "world3") + + // Test findOne() on empty collection with no filter and no options + assertNull(findOne().blockingGetResult()) + + // Insert a document into the collection + insertOne(doc1).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // Test findOne() with no filter and no options + assertEquals(doc1, findOne().blockingGetResult()!!.withoutId()) + + // Test findOne() with filter that does not match any documents and no options + assertNull(findOne(Document("hello", "worldDNE")).blockingGetResult()) + + // FIXME: revisit this later +// // Insert 2 more documents into the collection +//// insertMany(listOf(doc2, doc3)).blockingGetResult() // use insertOne for now +// insertOne(doc2).blockingGetResult() +// insertOne(doc3).blockingGetResult() +// assertEquals(3, count().blockingGetResult()) +// +// // test findOne() with projection and sort options +// val projection = Document("hello", 1) +// projection["_id"] = 0 +// val options1 = RemoteFindOptions() +// .limit(2) +// .projection(projection) +// .sort(Document("hello", 1)) +// assertEquals(doc1, findOne(Document(), options1).blockingGetResult()!!.withoutId()) +// +// val options2 = RemoteFindOptions() +// .limit(2) +// .projection(projection) +// .sort(Document("hello", -1)) +// assertEquals(doc3.withoutId(), findOne(Document(), options2).blockingGetResult()!!.withoutId()) +// +// // test findOne() properly fails +// try { +// Tasks.await(coll.findOne(Document("\$who", 1))) +// Assert.fail() +// } catch (ex: ExecutionException) { +// Assert.assertTrue(ex.cause is StitchServiceException) +// val svcEx = ex.cause as StitchServiceException +// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) +// } + } + } + + // FIXME: more to come + + private fun getCollectionInternal(collectionName: String, javaClass: Class? = null): MongoCollection { + return when (javaClass) { + null -> database.getCollection(collectionName) + else -> database.getCollection(collectionName, javaClass) + } + } + + private fun Document.withId(objectId: ObjectId? = null): Document { + return apply { this["_id"] = objectId ?: ObjectId() } + } + + private fun Document.withoutId(): Document { + return apply { remove("_id") } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt index 0a7623e257..22c18336c4 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -16,13 +16,14 @@ package io.realm.transport import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import io.realm.* import io.realm.internal.objectstore.OsJavaNetworkTransport import org.junit.After import org.junit.Assert.* +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import java.util.* /** * This class is responsible for testing the general network transport layer, i.e. that @@ -38,6 +39,11 @@ class OsJavaNetworkTransportTests { private lateinit var app: RealmApp private val successHeaders: Map = mapOf(Pair("Content-Type", "application/json")) + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + @After fun tearDown() { if (this::app.isInitialized) { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExt.kt new file mode 100644 index 0000000000..0be79878c8 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExt.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.util + +import com.google.android.gms.tasks.Task +import java.util.concurrent.CountDownLatch + +/** + * Returns the result of a [Task] in a synchronous way or will throw an exception if a failure is + * detected. This operation blocks the thread on which it is called. + * + * @return the [T] result emitted by the task + */ +fun Task.blockingGetResult(): T? { + val countDownLatch = CountDownLatch(1) + var error: Exception? = null + var result: T? = null + + addOnSuccessListener { successResult -> + result = successResult + countDownLatch.countDown() + } + addOnFailureListener { exception -> + error = exception + countDownLatch.countDown() + } + + countDownLatch.await() + + if (error != null) { + throw error!! + } + + return result +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExtKtTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExtKtTest.kt new file mode 100644 index 0000000000..e48c735fc8 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExtKtTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.util + +import io.realm.internal.common.TaskDispatcher +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull +import kotlin.test.fail + +class TaskUtilsKtTest { + + @Test + fun blockingGetResult() { + TaskDispatcher() + .dispatchTask { RESULT } + .blockingGetResult() + .let { assertEquals(RESULT, it) } + + TaskDispatcher() + .dispatchTask { null } + .blockingGetResult() + .let { assertNull(it) } + } + + @Test + fun blockingGetResultThrows() { + assertFailsWith(RuntimeException::class) { + TaskDispatcher() + .dispatchTask { throw RuntimeException("BOOM!") } + .blockingGetResult() + fail() + } + } + + private companion object { + const val RESULT = 666 + } +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index e95692f547..8f9e1d608a 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -113,9 +113,9 @@ if (build_SYNC) io.realm.internal.objectstore.OsAppCredentials io.realm.internal.objectstore.OsAsyncOpenTask io.realm.internal.objectstore.OsJavaNetworkTransport - io.realm.internal.objectstore.OsRemoteMongoClient - io.realm.internal.objectstore.OsRemoteMongoCollection - io.realm.internal.objectstore.OsRemoteMongoDatabase + io.realm.internal.objectstore.OsMongoClient + io.realm.internal.objectstore.OsMongoCollection + io.realm.internal.objectstore.OsMongoDatabase io.realm.internal.objectstore.OsSyncUser ) endif() @@ -208,9 +208,9 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAsyncOpenTask.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAppCredentials.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoClient.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoClient.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoCollection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoDatabase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsSyncUser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/jni_util/bson_util.cpp ) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoClient.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp similarity index 74% rename from realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoClient.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp index 2def4d1ff8..745757da20 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoClient.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_internal_objectstore_OsRemoteMongoClient.h" +#include "io_realm_internal_objectstore_OsMongoClient.h" #include "java_class_global_def.hpp" #include "java_network_transport.hpp" @@ -38,17 +38,17 @@ static void finalize_client(jlong ptr) { } JNIEXPORT jlong JNICALL -Java_io_realm_internal_objectstore_OsRemoteMongoClient_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { +Java_io_realm_internal_objectstore_OsMongoClient_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { return reinterpret_cast(&finalize_client); } JNIEXPORT jlong JNICALL -Java_io_realm_internal_objectstore_OsRemoteMongoClient_nativeCreate(JNIEnv* env, - jclass, - jlong j_app_ptr, - jstring j_service_name) { +Java_io_realm_internal_objectstore_OsMongoClient_nativeCreate(JNIEnv* env, + jclass, + jlong j_app_ptr, + jstring j_service_name) { try { - App* app = reinterpret_cast(j_app_ptr); + std::shared_ptr &app = *reinterpret_cast *>(j_app_ptr); JStringAccessor name(env, j_service_name); RemoteMongoClient client(app->remote_mongo_client(name)); return reinterpret_cast(new RemoteMongoClient(std::move(client))); @@ -58,10 +58,10 @@ Java_io_realm_internal_objectstore_OsRemoteMongoClient_nativeCreate(JNIEnv* env, } JNIEXPORT jlong JNICALL -Java_io_realm_internal_objectstore_OsRemoteMongoClient_nativeCreateDatabase(JNIEnv* env, - jclass, - jlong j_client_ptr, - jstring j_database_name) { +Java_io_realm_internal_objectstore_OsMongoClient_nativeCreateDatabase(JNIEnv* env, + jclass, + jlong j_client_ptr, + jstring j_database_name) { try { RemoteMongoClient* client = reinterpret_cast(j_client_ptr); JStringAccessor name(env, j_database_name); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp new file mode 100644 index 0000000000..4b560d8bc2 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp @@ -0,0 +1,205 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_internal_objectstore_OsMongoCollection.h" + +#include "java_class_global_def.hpp" +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" +#include "jni_util/bson_util.hpp" +#include "object-store/src/util/bson/bson.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::bson; +using namespace realm::jni_util; +using namespace realm::_impl; + +// This mapper works for both count and delete operations +static std::function collection_mapper_count = [](JNIEnv* env, uint64_t result) { + return JavaClassGlobalDef::new_long(env, result); +}; + +static std::function)> collection_mapper_find_one = [](JNIEnv* env, util::Optional document) { + return document ? JniBsonProtocol::bson_to_jstring(env, *document) : NULL; +}; + +static std::function)> collection_mapper_insert_one = [](JNIEnv* env, util::Optional object_id) { + if (object_id) { + return JavaClassGlobalDef::new_object_id(env, object_id.value()); + } + throw std::logic_error("Error in 'insert_one', parameter 'object_id' has no value."); +}; + +static std::function)> collection_mapper_insert_many = [](JNIEnv* env, std::vector object_ids) { + if (object_ids.size() == 0) { + throw std::logic_error("Error in 'insert_many', parameter 'object_ids' is empty."); + } + jobjectArray arr = (jobjectArray)env->NewObjectArray(static_cast(object_ids.size()), JavaClassGlobalDef::java_lang_object(), NULL); + if (arr == NULL) { + ThrowException(env, OutOfMemory, "Could not allocate memory to return list of ObjectIds of inserted documents."); + return arr; + } + for (size_t i = 0; i < object_ids.size(); ++i) { + jobject j_object_id = JavaClassGlobalDef::new_object_id(env, object_ids[i]); + env->SetObjectArrayElement(arr, i, j_object_id); + } + return arr; +}; + +static void finalize_collection(jlong ptr) { + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { + return reinterpret_cast(&finalize_collection); +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeCount(JNIEnv* env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jlong j_limit, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) + bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_filter)); + uint64_t limit = std::uint64_t(j_limit); + collection->count(bson_filter, limit, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOne(JNIEnv* env, + jclass, + jlong j_collection_ptr, + jstring j_document, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) + bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_document)); + collection->find_one(bson_filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneWithOptions(JNIEnv* env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_projection, + jstring j_sort, + jlong j_limit, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + uint64_t limit = std::uint64_t(j_limit); + + // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) + bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_filter)); + bson::BsonDocument projection(JniBsonProtocol::jstring_to_bson(env, j_projection)); + bson::BsonDocument sort(JniBsonProtocol::jstring_to_bson(env, j_sort)); + RemoteMongoCollection::RemoteFindOptions options = { + limit, + projection, + sort + }; + + collection->find_one(bson_filter, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeInsertOne(JNIEnv* env, + jclass, + jlong j_collection_ptr, + jstring j_document, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) + bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_document)); + collection->insert_one(bson_filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_insert_one)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeInsertMany(JNIEnv* env, + jclass, + jlong j_collection_ptr, + jstring j_documents, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) + BsonArray bson_array(JniBsonProtocol::jstring_to_bson(env, j_documents)); + collection->insert_many(bson_array, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_insert_many)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeDeleteOne(JNIEnv* env, + jclass, + jlong j_collection_ptr, + jstring j_document, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) + bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_document)); + collection->delete_one(bson_filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeDeleteMany(JNIEnv* env, + jclass, + jlong j_collection_ptr, + jstring j_document, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) + bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_document)); + collection->delete_many(bson_filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp similarity index 83% rename from realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp index 7dc060c7de..d71c3697b5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoDatabase.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_internal_objectstore_OsRemoteMongoDatabase.h" +#include "io_realm_internal_objectstore_OsMongoDatabase.h" #include "java_class_global_def.hpp" #include "java_network_transport.hpp" @@ -38,15 +38,15 @@ static void finalize_database(jlong ptr) { } JNIEXPORT jlong JNICALL -Java_io_realm_internal_objectstore_OsRemoteMongoDatabase_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { +Java_io_realm_internal_objectstore_OsMongoDatabase_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { return reinterpret_cast(&finalize_database); } JNIEXPORT jlong JNICALL -Java_io_realm_internal_objectstore_OsRemoteMongoDatabase_nativeGetCollection(JNIEnv* env, - jclass, - jlong j_database_ptr, - jstring j_collection_name) { +Java_io_realm_internal_objectstore_OsMongoDatabase_nativeGetCollection(JNIEnv* env, + jclass, + jlong j_database_ptr, + jstring j_collection_name) { try { RemoteMongoDatabase* database = reinterpret_cast(j_database_ptr); JStringAccessor name(env, j_collection_name); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp deleted file mode 100644 index 6e5d61f2dd..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsRemoteMongoCollection.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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 "io_realm_internal_objectstore_OsRemoteMongoCollection.h" - -#include "java_class_global_def.hpp" -#include "java_network_transport.hpp" -#include "util.hpp" -#include "jni_util/java_method.hpp" -#include "jni_util/jni_utils.hpp" -#include "jni_util/bson_util.hpp" -#include "object-store/src/util/bson/bson.hpp" - -#include -#include -#include -#include -#include -#include - -using namespace realm; -using namespace realm::app; -using namespace realm::jni_util; -using namespace realm::_impl; - -static std::function collection_mapper = [](JNIEnv* env, uint64_t result) { - return JavaClassGlobalDef::new_long(env, result); -}; - -static void finalize_collection(jlong ptr) { - delete reinterpret_cast(ptr); -} - -JNIEXPORT jlong JNICALL -Java_io_realm_internal_objectstore_OsRemoteMongoCollection_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { - return reinterpret_cast(&finalize_collection); -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsRemoteMongoCollection_nativeCount(JNIEnv* env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jlong j_limit, - jobject j_callback) { - try { - RemoteMongoCollection* collection = reinterpret_cast(j_collection_ptr); - bson::BsonDocument filter(JniBsonProtocol::jstring_to_bson(env, j_filter)); - uint64_t limit = std::uint64_t(j_limit); - collection->count(filter, limit, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper)); - } - CATCH_STD() -} diff --git a/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp b/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp index 888b5a19d8..737bb41a45 100644 --- a/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp @@ -28,6 +28,7 @@ Bson JniBsonProtocol::string_to_bson(std::string arg) { BsonDocument document(parse(arg)); return document[VALUE]; } + Bson JniBsonProtocol::jstring_to_bson(JNIEnv* env, jstring arg) { return string_to_bson(JStringAccessor(env, arg)); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java b/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java index 884066b47f..5eb4ccfcf5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java @@ -23,6 +23,7 @@ import javax.annotation.Nullable; +import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.jni.OsJNIVoidResultCallback; @@ -83,7 +84,7 @@ protected RealmUserApiKey mapSuccess(Object result) { } }; nativeCallFunction(TYPE_CREATE, user.getApp().nativePtr, user.osUser.getNativePtr(), name, callback); - return RealmApp.handleResult(success, error); + return ResultHandler.handleResult(success, error); } /** @@ -124,7 +125,7 @@ protected RealmUserApiKey mapSuccess(Object result) { return createKeyFromNative((Object[]) result); } }); - return RealmApp.handleResult(success, error); + return ResultHandler.handleResult(success, error); } /** @@ -164,7 +165,7 @@ protected List mapSuccess(Object result) { return list; } }); - return RealmApp.handleResult(success, error); + return ResultHandler.handleResult(success, error); } @@ -195,7 +196,7 @@ public void deleteApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_DELETE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIVoidResultCallback(error)); - RealmApp.handleResult(null, error); + ResultHandler.handleResult(null, error); } /** @@ -227,7 +228,7 @@ public void disableApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_DISABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIVoidResultCallback(error)); - RealmApp.handleResult(null, error); + ResultHandler.handleResult(null, error); } /** @@ -259,7 +260,7 @@ public void enableApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); nativeCallFunction(TYPE_ENABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIVoidResultCallback(error)); - RealmApp.handleResult(null, error); + ResultHandler.handleResult(null, error); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java index f0b417ed08..23ec061eb9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java @@ -15,12 +15,10 @@ */ package io.realm; -import org.json.JSONArray; - -import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; +import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIVoidResultCallback; @@ -68,7 +66,7 @@ public void registerUser(String email, String password) throws ObjectServerError app.nativePtr, new OsJNIVoidResultCallback(error), email, password); - RealmApp.handleResult(null, error); + ResultHandler.handleResult(null, error); } /** @@ -109,7 +107,7 @@ public void confirmUser(String token, String tokenId) throws ObjectServerError { app.nativePtr, new OsJNIVoidResultCallback(error), token, tokenId); - RealmApp.handleResult(null, error); + ResultHandler.handleResult(null, error); } /** @@ -145,7 +143,7 @@ public void resendConfirmationEmail(String email) throws ObjectServerError { app.nativePtr, new OsJNIVoidResultCallback(error), email); - RealmApp.handleResult(null, error); + ResultHandler.handleResult(null, error); } /** @@ -180,7 +178,7 @@ public void sendResetPasswordEmail(String email) throws ObjectServerError { app.nativePtr, new OsJNIVoidResultCallback(error), email); - RealmApp.handleResult(null, error); + ResultHandler.handleResult(null, error); } /** @@ -221,7 +219,7 @@ public void callResetPasswordFunction(String email, String newPassword, Object.. app.nativePtr, new OsJNIVoidResultCallback(error), email, newPassword, encodedArgs); - RealmApp.handleResult(null, error); + ResultHandler.handleResult(null, error); } /** @@ -265,7 +263,7 @@ public void resetPassword(String token, String tokenId, String newPassword) thro app.nativePtr, new OsJNIVoidResultCallback(error), token, tokenId, newPassword); - RealmApp.handleResult(null, error); + ResultHandler.handleResult(null, error); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index ffee3fc231..09bda6881f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -33,9 +33,9 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.internal.Keep; import io.realm.internal.KeepMember; import io.realm.internal.RealmNotifier; +import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; @@ -267,7 +267,7 @@ protected RealmUser mapSuccess(Object result) { return new RealmUser(nativePtr, RealmApp.this); } }); - RealmUser user = handleResult(success, error); + RealmUser user = ResultHandler.handleResult(success, error); notifyUserLoggedIn(user); return user; } @@ -393,23 +393,6 @@ OsJavaNetworkTransport getNetworkTransport() { return networkTransport; } - // Handle returning the correct result or throw an exception. Must be separated from - // OsJNIResultCallback due to how the Object Store callbacks work. - static T handleResult(@Nullable AtomicReference success, AtomicReference error) { - if (success != null && success.get() == null && error.get() == null) { - throw new IllegalStateException("Network result callback did not trigger correctly"); - } - if (error.get() != null) { - throw error.get(); - } else { - if (success != null) { - return success.get(); - } else { - return null; - } - } - } - // Class wrapping requests made against MongoDB Realm. Is also responsible for calling with success/error on the // correct thread. static abstract class Request { diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java index c3eb08f8c6..5f14764617 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java @@ -18,7 +18,9 @@ import android.content.Context; import org.bson.codecs.BsonValueCodecProvider; +import org.bson.codecs.DocumentCodecProvider; import org.bson.codecs.IterableCodecProvider; +import org.bson.codecs.MapCodecProvider; import org.bson.codecs.ValueCodecProvider; import org.bson.codecs.configuration.CodecRegistries; import org.bson.codecs.configuration.CodecRegistry; @@ -198,8 +200,10 @@ public static class Builder { new ValueCodecProvider(), // For BSONValue support new BsonValueCodecProvider(), + new DocumentCodecProvider(), // For list support - new IterableCodecProvider() + new IterableCodecProvider(), + new MapCodecProvider() ) ); diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java index 183073a1b0..22393cda82 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -22,15 +22,14 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.jni.OsJNIVoidResultCallback; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.internal.objectstore.OsSyncUser; import io.realm.internal.util.Pair; -import io.realm.mongodb.RemoteMongoClient; - -import static io.realm.RealmApp.handleResult; +import io.realm.mongodb.mongo.MongoClient; /** * FIXME @@ -40,7 +39,7 @@ public class RealmUser { OsSyncUser osUser; private final RealmApp app; private ApiKeyAuth apiKeyAuthProvider = null; - private RemoteMongoClient remoteMongoClient = null; + private MongoClient mongoClient = null; /** * FIXME @@ -273,7 +272,7 @@ protected RealmUser mapSuccess(Object result) { return RealmUser.this; } }); - return handleResult(success, error); + return ResultHandler.handleResult(success, error); } /** @@ -328,7 +327,7 @@ protected RealmUser mapSuccess(Object result) { return RealmUser.this; } }); - handleResult(success, error); + ResultHandler.handleResult(success, error); if (loggedIn) { app.notifyUserLoggedOut(this); } @@ -375,7 +374,7 @@ public void logOut() throws ObjectServerError { boolean loggedIn = isLoggedIn(); AtomicReference error = new AtomicReference<>(null); nativeLogOut(app.nativePtr, osUser.getNativePtr(), new OsJNIVoidResultCallback(error)); - handleResult(null, error); + ResultHandler.handleResult(null, error); if (loggedIn) { app.notifyUserLoggedOut(this); } @@ -441,12 +440,11 @@ public RealmPushNotifications getPushNotifications() { /** * FIXME Add support for the MongoDB wrapper. Name of Class and method still TBD. */ - public RemoteMongoClient getRemoteMongoClient() { - if (remoteMongoClient == null) { - // FIXME: serviceName? - remoteMongoClient = new RemoteMongoClient(this, "serviceName"); + public MongoClient getMongoClient(String serviceName) { + if (mongoClient == null) { + mongoClient = new MongoClient(this, serviceName, app.getConfiguration().getDefaultCodecRegistry()); } - return remoteMongoClient; + return mongoClient; } @SuppressFBWarnings("NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION") diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java index d96c15c192..eb2aec075f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java @@ -19,7 +19,6 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java new file mode 100644 index 0000000000..316d99294d --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.network; + +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.Nullable; + +import io.realm.ObjectServerError; + +public class ResultHandler { + + // Handle returning the correct result or throw an exception. Must be separated from + // OsJNIResultCallback due to how the Object Store callbacks work. + public static T handleResult(@Nullable AtomicReference success, AtomicReference error) { + if (error.get() != null) { + throw error.get(); + } else { + if (success != null) { + return success.get(); + } else { + return null; + } + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java similarity index 80% rename from realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoClient.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java index 35afc92305..837423b6e8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java @@ -16,22 +16,24 @@ package io.realm.internal.objectstore; +import org.bson.codecs.configuration.CodecRegistry; + import io.realm.RealmUser; import io.realm.internal.NativeObject; -public class OsRemoteMongoClient implements NativeObject { +public class OsMongoClient implements NativeObject { private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); private final long nativePtr; - public OsRemoteMongoClient(RealmUser realmUser, String serviceName) { + public OsMongoClient(RealmUser realmUser, String serviceName) { this.nativePtr = nativeCreate(realmUser.getApp().nativePtr, serviceName); } - public OsRemoteMongoDatabase getRemoteDatabase(String databaseName) { + public OsMongoDatabase getRemoteDatabase(final String databaseName, final CodecRegistry codecRegistry) { long nativeDatabasePtr = nativeCreateDatabase(nativePtr, databaseName); - return new OsRemoteMongoDatabase(nativeDatabasePtr); + return new OsMongoDatabase(nativeDatabasePtr, codecRegistry); } @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java new file mode 100644 index 0000000000..72853cb80d --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java @@ -0,0 +1,293 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.objectstore; + +import org.bson.BsonObjectId; +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.Nullable; + +import io.realm.ObjectServerError; +import io.realm.internal.NativeObject; +import io.realm.internal.network.ResultHandler; +import io.realm.internal.jni.JniBsonProtocol; +import io.realm.internal.jni.OsJNIResultCallback; +import io.realm.mongodb.mongo.options.RemoteCountOptions; +import io.realm.mongodb.mongo.result.RemoteDeleteResult; +import io.realm.mongodb.mongo.options.RemoteFindOptions; +import io.realm.mongodb.mongo.options.RemoteInsertManyResult; +import io.realm.mongodb.mongo.result.RemoteInsertOneResult; + +public class OsMongoCollection implements NativeObject { + + private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); + + private final long nativePtr; + private final Class documentClass; + private final CodecRegistry codecRegistry; + + OsMongoCollection(final long nativeCollectionPtr, final Class documentClass, final CodecRegistry codecRegistry) { + this.nativePtr = nativeCollectionPtr; + this.documentClass = documentClass; + this.codecRegistry = codecRegistry; + } + + @Override + public long getNativePtr() { + return nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + public Long count() { + return count(null); + } + + public Long count(@Nullable final Bson filter) { + return count(filter, null); + } + + public Long count(@Nullable final Bson filter, @Nullable final RemoteCountOptions options) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected Long mapSuccess(Object result) { + return (Long) result; + } + }; + + // no filter means count all + String filterString = (filter == null) ? + JniBsonProtocol.encode(new Document(), codecRegistry) : + JniBsonProtocol.encode(filter, codecRegistry); + int limit = (options == null) ? 0 : options.getLimit(); + + nativeCount(nativePtr, filterString, limit, callback); + + return ResultHandler.handleResult(success, error); + } + + public DocumentT findOne() { + return findOne(new Document()); + } + + public ResultT findOne(final Class resultClass) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected ResultT mapSuccess(Object result) { + return findOneSuccessMapper(result, resultClass); + } + }; + + nativeFindOne(nativePtr, JniBsonProtocol.encode(new Document(), codecRegistry), callback); + + return ResultHandler.handleResult(success, error); + } + + public DocumentT findOne(final Bson filter) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected DocumentT mapSuccess(Object result) { + return findOneSuccessMapper(result, documentClass); + } + }; + + String encodedFilter = JniBsonProtocol.encode(filter, codecRegistry); + nativeFindOne(nativePtr, encodedFilter, callback); + + return ResultHandler.handleResult(success, error); + } + + public ResultT findOne(final @Nullable Bson filter, final Class resultClass) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected ResultT mapSuccess(Object result) { + return findOneSuccessMapper(result, resultClass); + } + }; + + String encodedFilter = filter == null ? + JniBsonProtocol.encode(new Document(), codecRegistry) : + JniBsonProtocol.encode(filter, codecRegistry); + nativeFindOne(nativePtr, encodedFilter, callback); + + return ResultHandler.handleResult(success, error); + } + + public DocumentT findOne(@Nullable final Bson filter, final RemoteFindOptions options) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected DocumentT mapSuccess(Object result) { + return findOneSuccessMapper(result, documentClass); + } + }; + + String encodedFilter = filter == null ? + JniBsonProtocol.encode(new Document(), codecRegistry) : + JniBsonProtocol.encode(filter, codecRegistry); + String projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); + String sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); + nativeFindOneWithOptions(nativePtr, encodedFilter, projectionString, sortString, options.getLimit(), callback); + + return ResultHandler.handleResult(success, error); + } + + public ResultT findOne( + final Bson filter, + final RemoteFindOptions options, + final Class resultClass) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected ResultT mapSuccess(Object result) { + return findOneSuccessMapper(result, resultClass); + } + }; + + String encodedFilter = JniBsonProtocol.encode(filter, codecRegistry); + String projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); + String sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); + nativeFindOneWithOptions(nativePtr, encodedFilter, projectionString, sortString, options.getLimit(), callback); + + return ResultHandler.handleResult(success, error); + } + + public RemoteInsertOneResult insertOne(final DocumentT document) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected RemoteInsertOneResult mapSuccess(Object result) { + BsonValue bsonObjectId = new BsonObjectId((ObjectId) result); + return new RemoteInsertOneResult(bsonObjectId); + } + }; + + String encodedDocument = JniBsonProtocol.encode(document, codecRegistry); + nativeInsertOne(nativePtr, encodedDocument, callback); + return ResultHandler.handleResult(success, error); + } + + public RemoteInsertManyResult insertMany(final List documents) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected RemoteInsertManyResult mapSuccess(Object result) { + Object[] objects = (Object[]) result; + Map insertedIdsMap = new HashMap<>(); + for (int i = 0; i < objects.length; i++) { + ObjectId objectId = (ObjectId) objects[i]; + BsonValue bsonObjectId = new BsonObjectId(objectId); + insertedIdsMap.put((long) i, bsonObjectId); + } + return new RemoteInsertManyResult(insertedIdsMap); + } + }; + + String encodedDocumentArray = JniBsonProtocol.encode(documents, codecRegistry); + nativeInsertMany(nativePtr, encodedDocumentArray, callback); + return ResultHandler.handleResult(success, error); + } + + public RemoteDeleteResult deleteOne(final Bson filter) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected RemoteDeleteResult mapSuccess(Object result) { + return new RemoteDeleteResult((Long) result); + } + }; + + String jsonDocument = JniBsonProtocol.encode(filter, codecRegistry); + nativeDeleteOne(nativePtr, jsonDocument, callback); + return ResultHandler.handleResult(success, error); + } + + public RemoteDeleteResult deleteMany(final Bson filter) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected RemoteDeleteResult mapSuccess(Object result) { + return new RemoteDeleteResult((Long) result); + } + }; + + String jsonDocument = JniBsonProtocol.encode(filter, codecRegistry); + nativeDeleteMany(nativePtr, jsonDocument, callback); + return ResultHandler.handleResult(success, error); + } + + private T findOneSuccessMapper(@Nullable Object result, Class resultClass) { + if (result == null) { + return null; + } else { + return JniBsonProtocol.decode((String) result, resultClass, codecRegistry); + } + } + + private static native long nativeGetFinalizerMethodPtr(); + private static native void nativeCount(long remoteMongoCollectionPtr, + String filter, + long limit, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOne(long nativePtr, + String filterString, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneWithOptions(long nativePtr, + String filterString, + String projectionString, + String sortString, + long limit, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeInsertOne(long remoteMongoCollectionPtr, + String document, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeInsertMany(long remoteMongoCollectionPtr, + String documents, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeDeleteOne(long remoteMongoCollectionPtr, + String document, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeDeleteMany(long remoteMongoCollectionPtr, + String document, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java similarity index 64% rename from realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoDatabase.java rename to realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java index fa1fe8d020..f4965bba60 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java @@ -16,30 +16,34 @@ package io.realm.internal.objectstore; +import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistry; + import io.realm.internal.NativeObject; -public class OsRemoteMongoDatabase implements NativeObject { +public class OsMongoDatabase implements NativeObject { private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); private final long nativePtr; + private final CodecRegistry codecRegistry; - public OsRemoteMongoDatabase(long nativeDatabasePtr) { + OsMongoDatabase(long nativeDatabasePtr, CodecRegistry codecRegistry) { this.nativePtr = nativeDatabasePtr; + this.codecRegistry = codecRegistry; } - public OsRemoteMongoCollection getCollection(String collectionName) { - long nativeCollectionPtr = nativeGetCollection(nativePtr, collectionName); - return new OsRemoteMongoCollection(nativeCollectionPtr); + public OsMongoCollection getCollection(final String collectionName) { + return getCollection(collectionName, Document.class); } - // FIXME: what about this one? -// public RemoteMongoCollection getCollection( -// final String collectionName, -// final Class documentClass -// ) { -// throw new RuntimeException("Not implemented"); -// } + public OsMongoCollection getCollection( + final String collectionName, + final Class documentClass + ) { + long nativeCollectionPtr = nativeGetCollection(nativePtr, collectionName); + return new OsMongoCollection<>(nativeCollectionPtr, documentClass, codecRegistry); + } @Override public long getNativePtr() { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoCollection.java deleted file mode 100644 index d8984b835c..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsRemoteMongoCollection.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.objectstore; - -import io.realm.internal.NativeObject; - -public class OsRemoteMongoCollection implements NativeObject { - - private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); - - private final long nativePtr; - - OsRemoteMongoCollection(long nativeCollectionPtr) { - this.nativePtr = nativeCollectionPtr; - } - - @Override - public long getNativePtr() { - return nativePtr; - } - - @Override - public long getNativeFinalizerPtr() { - return nativeFinalizerPtr; - } - - public void count(String filter) { - throw new UnsupportedOperationException("Not Implemented"); - } - - private static native long nativeGetFinalizerMethodPtr(); - private static native void nativeCount(long remoteMongoCollectionPtr, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback, - String filter); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java similarity index 58% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoClient.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java index 5b6a7d0bb4..1faa079c09 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java @@ -14,32 +14,36 @@ * limitations under the License. */ -package io.realm.mongodb; +package io.realm.mongodb.mongo; + +import org.bson.codecs.configuration.CodecRegistry; import io.realm.RealmUser; import io.realm.internal.Util; -import io.realm.internal.objectstore.OsRemoteMongoClient; +import io.realm.internal.objectstore.OsMongoClient; /** * The remote MongoClient used for working with data in MongoDB remotely via Realm. */ -public class RemoteMongoClient { +public class MongoClient { - private OsRemoteMongoClient osRemoteMongoClient; + private OsMongoClient osMongoClient; + private CodecRegistry codecRegistry; - public RemoteMongoClient(RealmUser realmUser, String serviceName) { + public MongoClient(final RealmUser realmUser, final String serviceName, final CodecRegistry codecRegistry) { + this.codecRegistry = codecRegistry; Util.checkEmpty(serviceName, "serviceName"); - osRemoteMongoClient = new OsRemoteMongoClient(realmUser, serviceName); + osMongoClient = new OsMongoClient(realmUser, serviceName); } /** - * Gets a {@link RemoteMongoDatabase} instance for the given database name. + * Gets a {@link MongoDatabase} instance for the given database name. * * @param databaseName the name of the database to retrieve * @return a {@code RemoteMongoDatabase} representing the specified database */ - public RemoteMongoDatabase getDatabase(final String databaseName) { + public MongoDatabase getDatabase(final String databaseName) { Util.checkEmpty(databaseName, "databaseName"); - return new RemoteMongoDatabase(osRemoteMongoClient.getRemoteDatabase(databaseName), databaseName); + return new MongoDatabase(osMongoClient.getRemoteDatabase(databaseName, codecRegistry), databaseName); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java similarity index 71% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoCollection.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java index 427bf99727..df1e54cd55 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java @@ -14,55 +14,48 @@ * limitations under the License. */ -package io.realm.mongodb; +package io.realm.mongodb.mongo; import com.google.android.gms.tasks.Task; -import org.bson.BsonDocument; import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; import java.util.List; -import io.realm.internal.objectstore.OsRemoteMongoCollection; -import io.realm.mongodb.remote.RemoteCountOptions; -import io.realm.mongodb.remote.RemoteFindOneAndModifyOptions; -import io.realm.mongodb.remote.RemoteUpdateOptions; -import io.realm.mongodb.remote.RemoteDeleteResult; -import io.realm.mongodb.remote.RemoteFindOptions; -import io.realm.mongodb.remote.RemoteInsertManyResult; -import io.realm.mongodb.remote.RemoteInsertOneResult; -import io.realm.mongodb.remote.RemoteUpdateResult; -import io.realm.mongodb.remote.aggregate.RemoteAggregateIterable; -import io.realm.mongodb.remote.find.RemoteFindIterable; +import io.realm.internal.common.TaskDispatcher; +import io.realm.internal.objectstore.OsMongoCollection; +import io.realm.mongodb.mongo.iterable.RemoteAggregateIterable; +import io.realm.mongodb.mongo.iterable.RemoteFindIterable; +import io.realm.mongodb.mongo.options.RemoteCountOptions; +import io.realm.mongodb.mongo.options.RemoteFindOneAndModifyOptions; +import io.realm.mongodb.mongo.options.RemoteFindOptions; +import io.realm.mongodb.mongo.options.RemoteInsertManyResult; +import io.realm.mongodb.mongo.options.RemoteUpdateOptions; +import io.realm.mongodb.mongo.result.RemoteDeleteResult; +import io.realm.mongodb.mongo.result.RemoteInsertOneResult; +import io.realm.mongodb.mongo.result.RemoteUpdateResult; /** * The RemoteMongoCollection interface provides read and write access to documents. *

              - * Use {@link RemoteMongoDatabase#getCollection} to get a collection instance. + * Use {@link MongoDatabase#getCollection} to get a collection instance. *

              * Before any access is possible, there must be an active, logged-in user. - *

              - * Create, read, update and delete (CRUD) functionality is available depending - * on the privileges of the active logged-in user. You can set up - * Roles - * in the Stitch console. Stitch checks any given request against the Roles for the - * active user and determines whether the request is permitted for each requested - * document. - *

              * * @param The type that this collection will encode documents from and decode documents * to. - * @see RemoteMongoDatabase - * @see - * MongoDB Atlas Overview with Stitch + * @see MongoDatabase */ -public class RemoteMongoCollection { +public class MongoCollection { + + private OsMongoCollection osMongoCollection; - private OsRemoteMongoCollection osRemoteMongoCollection; + private TaskDispatcher dispatcher; - public RemoteMongoCollection(OsRemoteMongoCollection osRemoteMongoCollection) { - this.osRemoteMongoCollection = osRemoteMongoCollection; + MongoCollection(OsMongoCollection osMongoCollection) { + this.dispatcher = new TaskDispatcher(); + this.osMongoCollection = osMongoCollection; } /** @@ -70,30 +63,7 @@ public RemoteMongoCollection(OsRemoteMongoCollection osRemoteMongoCollection) { * * @return the namespace */ - MongoNamespace getNamespace() { - throw new RuntimeException("Not Implemented"); - } - - /** - * Get the class of documents stored in this collection. - *

              - * If you used the simple {@link RemoteMongoDatabase#getCollection(String)} to get - * this collection, - * this is {@link org.bson.Document}. - *

              - * - * @return the class - */ - Class getDocumentClass() { - throw new RuntimeException("Not Implemented"); - } - - /** - * Get the codec registry for the RemoteMongoCollection. - * - * @return the {@link CodecRegistry} - */ - CodecRegistry getCodecRegistry() { + public MongoNamespace getNamespace() { throw new RuntimeException("Not Implemented"); } @@ -106,9 +76,9 @@ CodecRegistry getCodecRegistry() { * documents to. * @return a new RemoteMongoCollection instance with the different default class */ - RemoteMongoCollection withDocumentClass( + public MongoCollection withDocumentClass( final Class clazz) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -118,8 +88,8 @@ RemoteMongoCollection withDocumentClass( * collection. * @return a new RemoteMongoCollection instance with the different codec registry */ - RemoteMongoCollection withCodecRegistry(final CodecRegistry codecRegistry) { - throw new RuntimeException("Not Implemented"); + public MongoCollection withCodecRegistry(final CodecRegistry codecRegistry) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -127,8 +97,10 @@ RemoteMongoCollection withCodecRegistry(final CodecRegistry codecRegi * * @return a task containing the number of documents in the collection */ - Task count() { - throw new RuntimeException("Not Implemented"); + public Task count() { + return dispatcher.dispatchTask(() -> + osMongoCollection.count() + ); } /** @@ -137,10 +109,10 @@ Task count() { * @param filter the query filter * @return a task containing the number of documents in the collection */ - Task count(final Bson filter) { - BsonDocument bsonDocument = filter.toBsonDocument(null, null); - osRemoteMongoCollection.count(bsonDocument.toJson()); - throw new RuntimeException("Not Implemented"); + public Task count(final Bson filter) { + return dispatcher.dispatchTask(() -> + osMongoCollection.count(filter) + ); } /** @@ -150,8 +122,10 @@ Task count(final Bson filter) { * @param options the options describing the count * @return a task containing the number of documents in the collection */ - Task count(final Bson filter, final RemoteCountOptions options) { - throw new RuntimeException("Not Implemented"); + public Task count(final Bson filter, final RemoteCountOptions options) { + return dispatcher.dispatchTask(() -> + osMongoCollection.count(filter, options) + ); } /** @@ -159,8 +133,10 @@ Task count(final Bson filter, final RemoteCountOptions options) { * * @return a task containing the result of the find one operation */ - Task findOne() { - throw new RuntimeException("Not Implemented"); + public Task findOne() { + return dispatcher.dispatchTask(() -> + osMongoCollection.findOne() + ); } /** @@ -170,8 +146,10 @@ Task findOne() { * @param the target document type * @return a task containing the result of the find one operation */ - Task findOne(final Class resultClass) { - throw new RuntimeException("Not Implemented"); + public Task findOne(final Class resultClass) { + return dispatcher.dispatchTask(() -> + osMongoCollection.findOne(resultClass) + ); } /** @@ -180,8 +158,10 @@ Task findOne(final Class resultClass) { * @param filter the query filter * @return a task containing the result of the find one operation */ - Task findOne(final Bson filter) { - throw new RuntimeException("Not Implemented"); + public Task findOne(final Bson filter) { + return dispatcher.dispatchTask(() -> + osMongoCollection.findOne(filter) + ); } /** @@ -192,8 +172,10 @@ Task findOne(final Bson filter) { * @param the target document type of the iterable. * @return a task containing the result of the find one operation */ - Task findOne(final Bson filter, final Class resultClass) { - throw new RuntimeException("Not Implemented"); + public Task findOne(final Bson filter, final Class resultClass) { + return dispatcher.dispatchTask(() -> + osMongoCollection.findOne(filter, resultClass) + ); } /** @@ -203,8 +185,10 @@ Task findOne(final Bson filter, final Class resultCl * @param options A RemoteFindOptions struct * @return a task containing the result of the find one operation */ - Task findOne(final Bson filter, final RemoteFindOptions options) { - throw new RuntimeException("Not Implemented"); + public Task findOne(final Bson filter, final RemoteFindOptions options) { + return dispatcher.dispatchTask(() -> + osMongoCollection.findOne(filter, options) + ); } /** @@ -216,11 +200,13 @@ Task findOne(final Bson filter, final RemoteFindOptions options) { * @param the target document type of the iterable. * @return a task containing the result of the find one operation */ - Task findOne( + public Task findOne( final Bson filter, final RemoteFindOptions options, final Class resultClass) { - throw new RuntimeException("Not Implemented"); + return dispatcher.dispatchTask(() -> + osMongoCollection.findOne(filter, options, resultClass) + ); } /** @@ -229,7 +215,7 @@ Task findOne( * @return the find iterable interface */ RemoteFindIterable find() { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -240,7 +226,7 @@ RemoteFindIterable find() { * @return the find iterable interface */ RemoteFindIterable find(final Class resultClass) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -249,8 +235,8 @@ RemoteFindIterable find(final Class resultClass) { * @param filter the query filter * @return the find iterable interface */ - RemoteFindIterable find(final Bson filter) { - throw new RuntimeException("Not Implemented"); + public RemoteFindIterable find(final Bson filter) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -261,8 +247,8 @@ RemoteFindIterable find(final Bson filter) { * @param the target document type of the iterable. * @return the find iterable interface */ - RemoteFindIterable find(final Bson filter, final Class resultClass) { - throw new RuntimeException("Not Implemented"); + public RemoteFindIterable find(final Bson filter, final Class resultClass) { + throw new UnsupportedOperationException("Not Implemented"); } @@ -272,8 +258,8 @@ RemoteFindIterable find(final Bson filter, final Class aggregate(final List pipeline) { - throw new RuntimeException("Not Implemented"); + public RemoteAggregateIterable aggregate(final List pipeline) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -284,10 +270,10 @@ RemoteAggregateIterable aggregate(final List pipeline * @param the target document type of the iterable. * @return an iterable containing the result of the aggregation operation */ - RemoteAggregateIterable aggregate( + public RemoteAggregateIterable aggregate( final List pipeline, final Class resultClass) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -297,8 +283,10 @@ RemoteAggregateIterable aggregate( * @param document the document to insert * @return a task containing the result of the insert one operation */ - Task insertOne(final DocumentT document) { - throw new RuntimeException("Not Implemented"); + public Task insertOne(final DocumentT document) { + return dispatcher.dispatchTask(() -> + osMongoCollection.insertOne(document) + ); } /** @@ -307,8 +295,10 @@ Task insertOne(final DocumentT document) { * @param documents the documents to insert * @return a task containing the result of the insert many operation */ - Task insertMany(final List documents) { - throw new RuntimeException("Not Implemented"); + public Task insertMany(final List documents) { + return dispatcher.dispatchTask(() -> + osMongoCollection.insertMany(documents) + ); } /** @@ -319,8 +309,10 @@ Task insertMany(final List document * @param filter the query filter to apply the the delete operation * @return a task containing the result of the remove one operation */ - Task deleteOne(final Bson filter) { - throw new RuntimeException("Not Implemented"); + public Task deleteOne(final Bson filter) { + return dispatcher.dispatchTask(() -> + osMongoCollection.deleteOne(filter) + ); } /** @@ -330,8 +322,10 @@ Task deleteOne(final Bson filter) { * @param filter the query filter to apply the the delete operation * @return a task containing the result of the remove many operation */ - Task deleteMany(final Bson filter) { - throw new RuntimeException("Not Implemented"); + public Task deleteMany(final Bson filter) { + return dispatcher.dispatchTask(() -> + osMongoCollection.deleteMany(filter) + ); } /** @@ -342,8 +336,8 @@ Task deleteMany(final Bson filter) { * apply must include only update operators. * @return a task containing the result of the update one operation */ - Task updateOne(final Bson filter, final Bson update) { - throw new RuntimeException("Not Implemented"); + public Task updateOne(final Bson filter, final Bson update) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -355,11 +349,11 @@ Task updateOne(final Bson filter, final Bson update) { * @param updateOptions the options to apply to the update operation * @return a task containing the result of the update one operation */ - Task updateOne( + public Task updateOne( final Bson filter, final Bson update, final RemoteUpdateOptions updateOptions) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -370,8 +364,8 @@ Task updateOne( * apply must include only update operators. * @return a task containing the result of the update many operation */ - Task updateMany(final Bson filter, final Bson update) { - throw new RuntimeException("Not Implemented"); + public Task updateMany(final Bson filter, final Bson update) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -383,11 +377,11 @@ Task updateMany(final Bson filter, final Bson update) { * @param updateOptions the options to apply to the update operation * @return a task containing the result of the update many operation */ - Task updateMany( + public Task updateMany( final Bson filter, final Bson update, final RemoteUpdateOptions updateOptions) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -397,8 +391,8 @@ Task updateMany( * @param update the update document * @return a task containing the resulting document */ - Task findOneAndUpdate(final Bson filter, final Bson update) { - throw new RuntimeException("Not Implemented"); + public Task findOneAndUpdate(final Bson filter, final Bson update) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -410,10 +404,10 @@ Task findOneAndUpdate(final Bson filter, final Bson update) { * @param the target document type of the iterable. * @return a task containing the resulting document */ - Task findOneAndUpdate(final Bson filter, - final Bson update, - final Class resultClass) { - throw new RuntimeException("Not Implemented"); + public Task findOneAndUpdate(final Bson filter, + final Bson update, + final Class resultClass) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -424,10 +418,10 @@ Task findOneAndUpdate(final Bson filter, * @param options A RemoteFindOneAndModifyOptions struct * @return a task containing the resulting document */ - Task findOneAndUpdate(final Bson filter, - final Bson update, - final RemoteFindOneAndModifyOptions options) { - throw new RuntimeException("Not Implemented"); + public Task findOneAndUpdate(final Bson filter, + final Bson update, + final RemoteFindOneAndModifyOptions options) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -440,12 +434,12 @@ Task findOneAndUpdate(final Bson filter, * @param the target document type of the iterable. * @return a task containing the resulting document */ - Task findOneAndUpdate( + public Task findOneAndUpdate( final Bson filter, final Bson update, final RemoteFindOneAndModifyOptions options, final Class resultClass) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -455,8 +449,8 @@ Task findOneAndUpdate( * @param replacement the document to replace the matched document with * @return a task containing the resulting document */ - Task findOneAndReplace(final Bson filter, final Bson replacement) { - throw new RuntimeException("Not Implemented"); + public Task findOneAndReplace(final Bson filter, final Bson replacement) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -468,10 +462,10 @@ Task findOneAndReplace(final Bson filter, final Bson replacement) { * @param the target document type of the iterable. * @return a task containing the resulting document */ - Task findOneAndReplace(final Bson filter, - final Bson replacement, - final Class resultClass) { - throw new RuntimeException("Not Implemented"); + public Task findOneAndReplace(final Bson filter, + final Bson replacement, + final Class resultClass) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -482,10 +476,10 @@ Task findOneAndReplace(final Bson filter, * @param options A RemoteFindOneAndModifyOptions struct * @return a task containing the resulting document */ - Task findOneAndReplace(final Bson filter, - final Bson replacement, - final RemoteFindOneAndModifyOptions options) { - throw new RuntimeException("Not Implemented"); + public Task findOneAndReplace(final Bson filter, + final Bson replacement, + final RemoteFindOneAndModifyOptions options) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -498,12 +492,12 @@ Task findOneAndReplace(final Bson filter, * @param the target document type of the iterable. * @return a task containing the resulting document */ - Task findOneAndReplace( + public Task findOneAndReplace( final Bson filter, final Bson replacement, final RemoteFindOneAndModifyOptions options, final Class resultClass) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -512,8 +506,8 @@ Task findOneAndReplace( * @param filter the query filter * @return a task containing the resulting document */ - Task findOneAndDelete(final Bson filter) { - throw new RuntimeException("Not Implemented"); + public Task findOneAndDelete(final Bson filter) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -524,9 +518,9 @@ Task findOneAndDelete(final Bson filter) { * @param the target document type of the iterable. * @return a task containing the resulting document */ - Task findOneAndDelete(final Bson filter, - final Class resultClass) { - throw new RuntimeException("Not Implemented"); + public Task findOneAndDelete(final Bson filter, + final Class resultClass) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -536,9 +530,9 @@ Task findOneAndDelete(final Bson filter, * @param options A RemoteFindOneAndModifyOptions struct * @return a task containing the resulting document */ - Task findOneAndDelete(final Bson filter, - final RemoteFindOneAndModifyOptions options) { - throw new RuntimeException("Not Implemented"); + public Task findOneAndDelete(final Bson filter, + final RemoteFindOneAndModifyOptions options) { + throw new UnsupportedOperationException("Not Implemented"); } /** @@ -550,11 +544,11 @@ Task findOneAndDelete(final Bson filter, * @param the target document type of the iterable. * @return a task containing the resulting document */ - Task findOneAndDelete( + public Task findOneAndDelete( final Bson filter, final RemoteFindOneAndModifyOptions options, final Class resultClass) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not Implemented"); } // FIXME: what about these? diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java similarity index 50% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoDatabase.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java index ad922e8cc0..bb791f2c8a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RemoteMongoDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java @@ -14,27 +14,27 @@ * limitations under the License. */ -package io.realm.mongodb; +package io.realm.mongodb.mongo; import org.bson.Document; import io.realm.internal.Util; -import io.realm.internal.objectstore.OsRemoteMongoDatabase; +import io.realm.internal.objectstore.OsMongoDatabase; /** - * The RemoteMongoDatabase provides access to its {@link Document} {@link RemoteMongoCollection}s. + * The RemoteMongoDatabase provides access to its {@link Document} {@link MongoCollection}s. */ -public class RemoteMongoDatabase { +public class MongoDatabase { private String databaseName; - private OsRemoteMongoDatabase osRemoteMongoDatabase; + private OsMongoDatabase osMongoDatabase; - RemoteMongoDatabase(OsRemoteMongoDatabase osRemoteMongoDatabase, String databaseName) { + MongoDatabase(OsMongoDatabase osMongoDatabase, String databaseName) { // we deliver the database name because we don't want to modify the C++ code right now, // although ideally it should be done there, i.e. remote_mongo_database.hpp should // include the public (Java) API's methods that aren't there yet. this.databaseName = databaseName; - this.osRemoteMongoDatabase = osRemoteMongoDatabase; + this.osMongoDatabase = osMongoDatabase; } /** @@ -42,7 +42,7 @@ public class RemoteMongoDatabase { * * @return the database name */ - String getName() { + public String getName() { return databaseName; } @@ -52,26 +52,25 @@ String getName() { * @param collectionName the name of the collection to return * @return the collection */ - RemoteMongoCollection getCollection(final String collectionName) { + public MongoCollection getCollection(final String collectionName) { Util.checkEmpty(collectionName, "collectionName"); - return new RemoteMongoCollection<>(osRemoteMongoDatabase.getCollection(collectionName)); + return new MongoCollection<>(osMongoDatabase.getCollection(collectionName)); } - // FIXME: what about this one? -// /** -// * Gets a collection, with a specific default document class. -// * -// * @param collectionName the name of the collection to return -// * @param documentClass the default class to cast any documents returned from the database into. -// * @param the type of the class to use instead of {@code Document}. -// * @return the collection -// */ -// RemoteMongoCollection getCollection( -// final String collectionName, -// final Class documentClass -// ) { -// Util.checkEmpty(collectionName, "collectionName"); -// Util.checkNull(documentClass, "documentClass"); -// return osRemoteMongoDatabase.getCollection(collectionName, documentClass); -// } + /** + * Gets a collection, with a specific default document class. + * + * @param collectionName the name of the collection to return + * @param documentClass the default class to cast any documents returned from the database into. + * @param the type of the class to use instead of {@code Document}. + * @return the collection + */ + public MongoCollection getCollection( + final String collectionName, + final Class documentClass + ) { + Util.checkEmpty(collectionName, "collectionName"); + Util.checkNull(documentClass, "documentClass"); + return new MongoCollection<>(osMongoDatabase.getCollection(collectionName, documentClass)); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/MongoNamespace.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoNamespace.java similarity index 99% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/MongoNamespace.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoNamespace.java index 2c7139b5d6..4a7457b1bd 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/MongoNamespace.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoNamespace.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb; +package io.realm.mongodb.mongo; import org.bson.codecs.pojo.annotations.BsonCreator; import org.bson.codecs.pojo.annotations.BsonIgnore; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/aggregate/RemoteAggregateIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteAggregateIterable.java similarity index 95% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/aggregate/RemoteAggregateIterable.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteAggregateIterable.java index c08e0da636..f4417445a1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/aggregate/RemoteAggregateIterable.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteAggregateIterable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote.aggregate; +package io.realm.mongodb.mongo.iterable; /** * Iterable for aggregate. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/find/RemoteFindIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteFindIterable.java similarity index 97% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/find/RemoteFindIterable.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteFindIterable.java index 5b6e448d78..13ea09ceba 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/find/RemoteFindIterable.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteFindIterable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote.find; +package io.realm.mongodb.mongo.iterable; import org.bson.conversions.Bson; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteCountOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteCountOptions.java similarity index 96% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteCountOptions.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteCountOptions.java index 1a751811a1..9cc3bd6c4d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteCountOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteCountOptions.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote; +package io.realm.mongodb.mongo.options; /** * The options for a count operation. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOneAndModifyOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOneAndModifyOptions.java similarity index 99% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOneAndModifyOptions.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOneAndModifyOptions.java index dcf12db4fc..e3f8542fef 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOneAndModifyOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOneAndModifyOptions.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote; +package io.realm.mongodb.mongo.options; import javax.annotation.Nullable; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOptions.java similarity index 98% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOptions.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOptions.java index 87d9dbba17..8d0509898a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteFindOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOptions.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote; +package io.realm.mongodb.mongo.options; import javax.annotation.Nullable; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertManyResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteInsertManyResult.java similarity index 97% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertManyResult.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteInsertManyResult.java index 44d7a85362..f32c3639dc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertManyResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteInsertManyResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote; +package io.realm.mongodb.mongo.options; import java.util.Map; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteUpdateOptions.java similarity index 97% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateOptions.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteUpdateOptions.java index 5bc2834043..64a85a4a79 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteUpdateOptions.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote; +package io.realm.mongodb.mongo.options; /** * The options to apply when updating documents. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteDeleteResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteDeleteResult.java similarity index 96% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteDeleteResult.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteDeleteResult.java index f394ad1208..e101029438 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteDeleteResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteDeleteResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote; +package io.realm.mongodb.mongo.result; /** * The result of a delete operation. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertOneResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteInsertOneResult.java similarity index 96% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertOneResult.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteInsertOneResult.java index d24e013b9f..e66be192ce 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteInsertOneResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteInsertOneResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote; +package io.realm.mongodb.mongo.result; import org.bson.BsonValue; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteUpdateResult.java similarity index 98% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateResult.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteUpdateResult.java index 48840d88c7..18c708d520 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/remote/RemoteUpdateResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteUpdateResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm.mongodb.remote; +package io.realm.mongodb.mongo.result; import javax.annotation.Nullable; diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt b/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt index 229e83f4e0..df12fa0c72 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt @@ -46,7 +46,6 @@ class TestRealmApp(networkTransport: OsJavaNetworkTransport? = null, customizeCo // Initializes MongoDB Realm. Clears all local state and fetches the application ID. private fun initializeMongoDbRealm(): String { - Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) val transport = OkHttpNetworkTransport() val response = transport.sendRequest( "get", From 12951e6e347eb71475bc5a8ff2082cf453b8d917 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 16 May 2020 09:21:59 +0200 Subject: [PATCH 1521/2110] Apply suggestions from code review Co-authored-by: Brian Munkholm --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e59288c20..e391b2862e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 7.0.0(YYYY-MM-DD) -NOTE: This version bumps the Realm file format to version 10. It is not possible to downgrade version 9 or earlier. Files created with older versions of Realm will be automatically upgraded. +NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. ### Breaking Changes * [ObjectServer] Removed deprecated method `SyncConfiguration.Builder.partialRealm()`. Use `SyncConfiguration.Builder.fullSynchronization()` instead. @@ -29,7 +29,7 @@ NOTE: This version bumps the Realm file format to version 10. It is not possible ### Compatibility * Realm Object Server: 3.23.1 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). -* APIs are backwards compatible with all previous release of realm-java in the 6.x.y series. +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * `OsSharedRealm.VersionID.hashCode()` was not implemented correctly and included the memory location in the hashcode. From 17fa266325789b0c0ec6682bdce5366dc8a895e2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 16 May 2020 09:55:51 +0200 Subject: [PATCH 1522/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e391b2862e..a8417ed52a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 7.0.0(YYYY-MM-DD) +## 7.0.0(2020-05-16) NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. From 5e1cb707bf37a4c5fa03b45cd5a4fde39fed146d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 16 May 2020 09:56:46 +0200 Subject: [PATCH 1523/2110] Release v7.0.0 --- version.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/version.txt b/version.txt index 1eed1dbd63..4122521804 100644 --- a/version.txt +++ b/version.txt @@ -1,2 +1 @@ -7.0.0-SNAPSHOT - +7.0.0 \ No newline at end of file From b908f0fd0fe1a45c3d58e4d32397fbc80e53de81 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 16 May 2020 09:56:46 +0200 Subject: [PATCH 1524/2110] Prepare next release v7.0.1-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 4122521804..44bad91b17 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.0 \ No newline at end of file +7.0.1-SNAPSHOT \ No newline at end of file From 3ce8f2e221d34207597849fb9822dbdaf954df1f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 16 May 2020 19:27:28 +0200 Subject: [PATCH 1525/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 44bad91b17..40e36364ab 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.1-SNAPSHOT \ No newline at end of file +7.1.0-SNAPSHOT \ No newline at end of file From 3c0f7e4c2be698edcc94acfb7204d917277bceaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 18 May 2020 12:33:43 +0200 Subject: [PATCH 1526/2110] Migrate and reenable ProgressTest (#6860) Including: * Disabling KotlinSyncedRealmTests until proper dev mode support in Stitch * Constraining CI to run on the only slave with a physical device --- Jenkinsfile | 2 +- .../java/io/realm/ProgressTests.java | 74 ------------------- .../kotlin/io/realm/KotlinSyncedRealmTests.kt | 1 + .../kotlin/io/realm/ProgressTests.kt | 65 ++++++++++++++++ 4 files changed, 67 insertions(+), 75 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressTests.kt diff --git a/Jenkinsfile b/Jenkinsfile index 998bdfbe99..23a73db10e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,7 +11,7 @@ def dockerNetworkId = UUID.randomUUID().toString() def releaseBranches = ['master', 'next-major', 'v10'] // Branches from which we release SNAPSHOT's def currentBranch = env.CHANGE_BRANCH try { - node('android') { + node('docker-cph-01') { // FIXME: Only working Slave timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java deleted file mode 100644 index 61b5415b7d..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/ProgressTests.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Locale; - -import static org.junit.Assert.assertEquals; - -@Ignore("FIXME: RealmApp refactor") -@RunWith(AndroidJUnit4.class) -public class ProgressTests { - - @Test - public void getFractionTransferred() { - Object[][] testData = { - { 0L, 0L, 1.0D }, - { 0L, 1L, 0.0D }, - { 1L, 1L, 1.0D }, - { 1L, 2L, 0.5D } - }; - - for (Object[] test : testData) { - long transferredBytes = (long) test[0]; - long transferableBytes = (long) test[1]; - double fraction = (double) test[2]; - Progress progress = new Progress(transferredBytes, transferableBytes); - String errorMessage = String.format(Locale.US, "Failed with: (%d, %d)", transferredBytes, transferableBytes); - assertEquals(errorMessage, fraction, progress.getFractionTransferred(), 0.0D); - } - } - - @Test - public void getTransferredBytes () { - long[] testData = { 0, Long.MAX_VALUE }; - - for (long transferredBytes : testData) { - String errorMessage = String.format(Locale.US, "Failed with: %d", transferredBytes); - Progress progress = new Progress(transferredBytes, Long.MAX_VALUE); - assertEquals(errorMessage, transferredBytes, progress.getTransferredBytes()); - } - } - - @Test - public void getTransferableBytes () { - long[] testData = { 0, Long.MAX_VALUE }; - - for (long transferableBytes : testData) { - String errorMessage = String.format(Locale.US, "Failed with: %d", transferableBytes); - Progress progress = new Progress(0, transferableBytes); - assertEquals(errorMessage, transferableBytes, progress.getTransferableBytes()); - } - } - -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt index d2528710fe..21353aa2dd 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt @@ -31,6 +31,7 @@ import org.junit.runner.RunWith import java.util.* @RunWith(AndroidJUnit4::class) +@Ignore("FIXME This stalls Trying to bypass sync test to see if this is blocking CI") class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaining Java tests have been moved private lateinit var app: TestRealmApp diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressTests.kt new file mode 100644 index 0000000000..a5d0fa0e86 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressTests.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* + +@RunWith(AndroidJUnit4::class) +class ProgressTests { + + @Test + fun getFractionTransferred () { + val testData = arrayOf( + arrayOf(0L, 0L, 1.0), + arrayOf(0L, 1L, 0.0), + arrayOf(1L, 1L, 1.0), + arrayOf(1L, 2L, 0.5) + ) + for (test in testData) { + val transferredBytes = test[0] as Long + val transferableBytes = test[1] as Long + val fraction = test[2] as Double + val progress = Progress(transferredBytes, transferableBytes) + val errorMessage = String.format(Locale.US, "Failed with: (%d, %d)", transferredBytes, transferableBytes) + assertEquals(errorMessage, fraction, progress.fractionTransferred, 0.0) + } + } + + @Test + fun getTransferredBytes() { + val testData = longArrayOf(0, Long.MAX_VALUE) + for (transferredBytes in testData) { + val errorMessage = String.format(Locale.US, "Failed with: %d", transferredBytes) + val progress = Progress(transferredBytes, Long.MAX_VALUE) + assertEquals(errorMessage, transferredBytes, progress.transferredBytes) + } + } + + @Test + fun getTransferableBytes() { + val testData = longArrayOf(0, Long.MAX_VALUE) + for (transferableBytes in testData) { + val errorMessage = String.format(Locale.US, "Failed with: %d", transferableBytes) + val progress = Progress(0, transferableBytes) + assertEquals(errorMessage, transferableBytes, progress.transferableBytes) + } + } + +} From 9531264391f66e73bde607b887db51eba93f36f5 Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Mon, 18 May 2020 13:38:11 +0200 Subject: [PATCH 1527/2110] Update CHANGELOG.md with required Studio version --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8417ed52a..fc3a4e1a24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 7.0.0(2020-05-16) -NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. +NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only Studio 3.11 or later will be able to open the new file format. ### Breaking Changes * [ObjectServer] Removed deprecated method `SyncConfiguration.Builder.partialRealm()`. Use `SyncConfiguration.Builder.fullSynchronization()` instead. @@ -28,6 +28,7 @@ NOTE: This version bumps the Realm file format to version 10. Files created with ### Compatibility * Realm Object Server: 3.23.1 or later. +* Realm Studio: 3.11 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. From 9ecbd4665e8f60633f7cec9a5384a2c2a8b34397 Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Mon, 18 May 2020 14:29:31 +0200 Subject: [PATCH 1528/2110] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc3a4e1a24..0c154ae534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 7.0.0(2020-05-16) -NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only Studio 3.11 or later will be able to open the new file format. +NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Studio 3.11](https://github.com/realm/realm-studio/releases/tag/v3.11.0) or later will be able to open the new file format. ### Breaking Changes * [ObjectServer] Removed deprecated method `SyncConfiguration.Builder.partialRealm()`. Use `SyncConfiguration.Builder.fullSynchronization()` instead. From 980823b8b398aa3fd4b4c29189c197a326002950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 19 May 2020 15:34:13 +0200 Subject: [PATCH 1529/2110] Add support for Realm Functions (#6810) --- realm/realm-library/build.gradle | 4 - .../kotlin/io/realm/KotlinSyncedRealmTests.kt | 2 +- .../io/realm/RealmAppConfigurationTests.kt | 27 ++ .../kotlin/io/realm/RealmAppTests.kt | 20 + .../kotlin/io/realm/RealmFunctionsTests.kt | 151 ------ .../realm/mongodb/functions/FunctionsTests.kt | 433 ++++++++++++++++++ .../kotlin/io/realm/util/KotlinTestUtils.kt | 2 +- .../realm-library/src/main/cpp/CMakeLists.txt | 4 +- .../src/main/cpp/io_realm_RealmFunctions.cpp | 36 -- .../cpp/io_realm_mongodb_FunctionsImpl.cpp | 58 +++ .../src/main/cpp/jni_util/bson_util.cpp | 19 +- .../src/main/cpp/jni_util/bson_util.hpp | 12 +- .../java/io/realm/FunctionsImpl.java | 67 +++ .../objectServer/java/io/realm/RealmApp.java | 30 +- .../java/io/realm/RealmAppConfiguration.java | 37 +- .../java/io/realm/RealmFunctions.java | 46 -- .../objectServer/java/io/realm/RealmUser.java | 25 +- .../io/realm/internal/util/BsonConverter.java | 0 .../io/realm/mongodb/functions/Functions.java | 195 ++++++++ .../app_config/functions/authFunc/config.json | 2 +- .../functions/authorizedOnly/config.json | 12 + .../functions/authorizedOnly/source.js | 16 + .../functions/confirmFunc/config.json | 2 +- .../app_config/functions/error/config.json | 5 + .../app_config/functions/error/source.js | 3 + .../app_config/functions/firstArg/config.json | 5 + .../app_config/functions/firstArg/source.js | 4 + .../app_config/functions/null/config.json | 5 + .../app_config/functions/null/source.js | 3 + .../functions/resetFunc/config.json | 2 +- .../app_config/functions/sum/config.json | 5 + .../app_config/functions/sum/source.js | 3 + .../app_config/functions/void/config.json | 5 + .../app_config/functions/void/source.js | 3 + 34 files changed, 973 insertions(+), 270 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt delete mode 100644 realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java create mode 100644 tools/sync_test_server/app_config/functions/authorizedOnly/config.json create mode 100644 tools/sync_test_server/app_config/functions/authorizedOnly/source.js create mode 100644 tools/sync_test_server/app_config/functions/error/config.json create mode 100644 tools/sync_test_server/app_config/functions/error/source.js create mode 100644 tools/sync_test_server/app_config/functions/firstArg/config.json create mode 100644 tools/sync_test_server/app_config/functions/firstArg/source.js create mode 100644 tools/sync_test_server/app_config/functions/null/config.json create mode 100644 tools/sync_test_server/app_config/functions/null/source.js create mode 100644 tools/sync_test_server/app_config/functions/sum/config.json create mode 100644 tools/sync_test_server/app_config/functions/sum/source.js create mode 100644 tools/sync_test_server/app_config/functions/void/config.json create mode 100644 tools/sync_test_server/app_config/functions/void/source.js diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 4ac805103d..d4e694f7ea 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -217,10 +217,6 @@ dependencies { // FIXME: Attempt to find a way to remove this dependency objectServerImplementation "com.google.android.gms:play-services-tasks:17.0.2" // added to support mongo client's asynchronous nature without breaking Stitch's API - testImplementation 'junit:junit:4.12' - testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - // TODO: investigate why we can't use the latest multidex version // check baseDebugAndroidTestRuntimeClasspath and objectServerDebugAndroidTestRuntimeClasspath // tasks as they introduce version 2.0.0 strictly, even when specifying 2.0.1 from here diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt index 21353aa2dd..c865d3f603 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt @@ -30,8 +30,8 @@ import org.junit.Test import org.junit.runner.RunWith import java.util.* +@Ignore("FIXME Disabled until dev mode is not causing this to hang") @RunWith(AndroidJUnit4::class) -@Ignore("FIXME This stalls Trying to bypass sync test to see if this is blocking CI") class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaining Java tests have been moved private lateinit var app: TestRealmApp diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt index b92304137b..b346508e9d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt @@ -17,6 +17,9 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import org.bson.codecs.StringCodec +import org.bson.codecs.configuration.CodecRegistries +import org.bson.codecs.configuration.CodecRegistry import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -152,4 +155,28 @@ class RealmAppConfigurationTests { assertTrue(file.createNewFile()) assertFailsWith { builder.syncRootDirectory(file) } } + + @Test + fun codecRegistry_null() { + val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + assertFailsWith { + builder.codecRegistry(TestHelper.getNull()) + } + } + + @Test + fun defaultFunctionsCodecRegistry() { + val config: RealmAppConfiguration = RealmAppConfiguration.Builder("app-id").build() + assertEquals(RealmAppConfiguration.DEFAULT_BSON_CODEC_REGISTRY, config.defaultCodecRegistry) + } + + @Test + fun customCodecRegistry() { + val configCodecRegistry = CodecRegistries.fromCodecs(StringCodec()) + val config: RealmAppConfiguration = RealmAppConfiguration.Builder("app-id") + .codecRegistry(configCodecRegistry) + .build() + assertEquals(configCodecRegistry, config.defaultCodecRegistry) + } + } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt index 1440bf57b7..303cfb34c7 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt @@ -19,6 +19,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.rule.BlockingLooperThread +import org.bson.BsonReader +import org.bson.BsonWriter +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.StringCodec +import org.bson.codecs.configuration.CodecRegistries import org.junit.After import org.junit.Assert.* import org.junit.Before @@ -248,4 +255,17 @@ class RealmAppTests { app.login(RealmCredentials.anonymous()) } + @Test + fun functions_defaultCodecRegistry() { + var user = app.login(RealmCredentials.anonymous()) + assertEquals(app.configuration.defaultCodecRegistry, app.getFunctions(user).defaultCodecRegistry) + } + + @Test + fun functions_customCodecRegistry() { + var user = app.login(RealmCredentials.anonymous()) + val registry = CodecRegistries.fromCodecs(StringCodec()) + assertEquals(registry, app.getFunctions(user, registry).defaultCodecRegistry) + } + } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTests.kt deleted file mode 100644 index 5a52e8537a..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmFunctionsTests.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm - -import androidx.test.platform.app.InstrumentationRegistry -import org.bson.* -import org.bson.codecs.StringCodec -import org.bson.codecs.configuration.CodecRegistries -import org.bson.codecs.pojo.PojoCodecProvider -import org.bson.types.Decimal128 -import org.bson.types.ObjectId -import org.junit.After -import org.junit.Before -import org.junit.Test -import kotlin.test.assertEquals - -class RealmFunctionsTests { - - private lateinit var app: TestRealmApp - private lateinit var functions : RealmFunctions - - @Before - fun setup() { - Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestRealmApp() - functions = RealmFunctions(app.configuration.defaultCodecRegistry) - } - - @After - fun teardown() { - if (this::app.isInitialized) { - app.close() - } - } - - // Test of BSON JNI round trip until superseded with actual public api tests are added. - @Test - fun jniRoundTripForDefaultCodecRegistry() { - val i32 = 42 - val i64 = 42L - - for (type in BsonType.values()) { - when (type) { - BsonType.DOUBLE -> { - assertEquals(1.4f, functions.invoke(1.4f, java.lang.Float::class.java).toFloat()) - assertEquals(1.4, functions.invoke(1.4, java.lang.Double::class.java).toDouble()) - assertTypedEcho(BsonDouble(1.4), BsonDouble::class.java) - } - BsonType.STRING -> { - assertTypedEcho("Realm", String::class.java) - assertTypedEcho(BsonString("Realm"), BsonString::class.java) - } - BsonType.ARRAY -> { - val listValues = listOf(true, i32, i64) - assertTypedEcho(listValues, List::class.java) - } - BsonType.BINARY -> { - val value = byteArrayOf(1, 2, 3) - val actual = functions.invoke(value, ByteArray::class.java) - assertEquals(value.toList(), actual.toList()) - // FIXME C++ Does not seem to preserve subtype - // arg = "{"value": {"$binary": {"base64": "JmS8oQitTny4IPS2tyjmdA==", "subType": "04"}}}" - // response = "{"value":{"$binary":{"base64":"JmS8oQitTny4IPS2tyjmdA==","subType":"00"}}}" - // assertTypedEcho(BsonBinary(UUID.randomUUID()), BsonBinary::class.java) - assertTypedEcho(BsonBinary(byteArrayOf(1,2,3)), BsonBinary::class.java) - } - BsonType.OBJECT_ID -> { - assertTypedEcho(ObjectId(), ObjectId::class.java) - assertTypedEcho(BsonObjectId(ObjectId()), BsonObjectId::class.java) - } - BsonType.BOOLEAN -> { - val value: Boolean = true - val actual: java.lang.Boolean = functions.invoke(value, java.lang.Boolean::class.java) - assertEquals(value, actual.booleanValue()) - assertTypedEcho(BsonBoolean(true), BsonBoolean::class.java) - } - BsonType.INT32 -> { - assertEquals(32, functions.invoke(32, Integer::class.java).toInt()) - assertEquals(32, functions.invoke(32L, Integer::class.java).toInt()) - assertTypedEcho(BsonInt32(32), BsonInt32::class.java) - } - BsonType.INT64 -> { - assertEquals(32L, functions.invoke(32, java.lang.Long::class.java).toLong()) - assertEquals(32L, functions.invoke(32L, java.lang.Long::class.java).toLong()) - assertTypedEcho(BsonInt64(32), BsonInt64::class.java) - } - BsonType.DECIMAL128 -> { - assertTypedEcho(Decimal128(32L), Decimal128::class.java) - assertTypedEcho(BsonDecimal128(Decimal128(32L)), BsonDecimal128::class.java) - } - // TODO - BsonType.DOCUMENT, - BsonType.UNDEFINED, - BsonType.DATE_TIME, - BsonType.NULL, - BsonType.REGULAR_EXPRESSION, - BsonType.SYMBOL, - BsonType.DB_POINTER, - BsonType.JAVASCRIPT, - BsonType.JAVASCRIPT_WITH_SCOPE, - BsonType.TIMESTAMP, - BsonType.END_OF_DOCUMENT, - BsonType.MIN_KEY, - BsonType.MAX_KEY -> { - // No conversion is implemented for these types yet - } - } - } - } - - private fun assertTypedEcho(value: T, returnClass: Class) : T { - val actual = functions.invoke(value, returnClass) - assertEquals(value, actual) - return actual - } - - // Test of BSON JNI round trip until superseded with actual public api tests are added. - data class Dog(var name: String? = null) - @Test - fun pojoCodecRegistry() { - val pojoRegistry = CodecRegistries.fromRegistries( - CodecRegistries.fromCodecs(StringCodec()), - CodecRegistries.fromProviders( - PojoCodecProvider.builder() - .register(Dog::class.java) - .build() - ) - ) - - val input = Dog("PojoFido") - - val actual: Dog = functions.invoke(input, Dog::class.java, pojoRegistry) - - assertEquals(input, actual) - } - -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt new file mode 100644 index 0000000000..cc55d2e776 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt @@ -0,0 +1,433 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.functions + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.* +import io.realm.admin.ServerAdmin +import io.realm.rule.BlockingLooperThread +import io.realm.util.assertFailsWithErrorCode +import org.bson.* +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.StringCodec +import org.bson.codecs.configuration.CodecConfigurationException +import org.bson.codecs.configuration.CodecProvider +import org.bson.codecs.configuration.CodecRegistries +import org.bson.codecs.configuration.CodecRegistry +import org.bson.codecs.pojo.PojoCodecProvider +import org.bson.types.Decimal128 +import org.bson.types.ObjectId +import org.junit.After +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +class FunctionsTests { + + companion object { + const val FIRST_ARG_FUNCTION = "firstArg" + } + + // Pojo class for testing custom encoder/decoder + data class Dog(var name: String? = null) + + private val looperThread = BlockingLooperThread() + + private lateinit var app: TestRealmApp + private lateinit var functions: Functions + + private lateinit var anonUser: RealmUser + private lateinit var admin: ServerAdmin + + // Custom registry with support for encoding/decoding Dogs + val pojoRegistry by lazy { + CodecRegistries.fromRegistries( + app.configuration.defaultCodecRegistry, + CodecRegistries.fromProviders( + PojoCodecProvider.builder() + .register(Dog::class.java) + .build() + ) + ) + } + + @Before + fun setup() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestRealmApp() + admin = ServerAdmin() + anonUser = app.login(RealmCredentials.anonymous()) + functions = anonUser.functions + } + + @After + fun teardown() { + if (this::app.isInitialized) { + app.close() + } + } + + // Tests + // - Default codec factory + @Test + fun jniRoundTripForDefaultCodecRegistry() { + val i32 = 42 + val i64 = 42L + + for (type in BsonType.values()) { + when (type) { + BsonType.DOUBLE -> { + assertEquals(1.4f, functions.callFunction(FIRST_ARG_FUNCTION, listOf(1.4f), java.lang.Float::class.java).toFloat()) + assertEquals(1.4, functions.callFunction(FIRST_ARG_FUNCTION, listOf(1.4f), java.lang.Double::class.java).toDouble()) + assertTypeOfFirstArgFunction(BsonDouble(1.4), BsonDouble::class.java) + } + BsonType.STRING -> { + assertTypeOfFirstArgFunction("Realm", String::class.java) + assertTypeOfFirstArgFunction(BsonString("Realm"), BsonString::class.java) + } + BsonType.ARRAY -> { + val values1 = listOf(true, i32, i64) + assertEquals(values1[0], functions.callFunction(FIRST_ARG_FUNCTION, values1, java.lang.Boolean::class.java)) + + // Previously failing in C++ parsing + val values2 = listOf(1, true, 3) + assertEquals(values2, functions.callFunction(FIRST_ARG_FUNCTION, listOf(values2), List::class.java)) + val values3 = listOf(2, "Realm", 3) + assertEquals(values3, functions.callFunction(FIRST_ARG_FUNCTION, listOf(values3), List::class.java)) + } + // FIXME Does not seem to work, typically this has indicated an issue with C++ + // parser. Probably because of embedding an array in an array, added explicit test +// BsonType.BINARY -> { +// val value = byteArrayOf(1, 2, 3) +// val actual = functions.callFunction(FIRST_ARG_FUNCTION, listOf(value), ByteArray::class.java) +// assertEquals(value.toList(), actual.toList()) +// // FIXME C++ Does not seem to preserve subtype +// // arg = "{"value": {"$binary": {"base64": "JmS8oQitTny4IPS2tyjmdA==", "subType": "04"}}}" +// // response = "{"value":{"$binary":{"base64":"JmS8oQitTny4IPS2tyjmdA==","subType":"00"}}}" +// // assertTypedEcho(BsonBinary(UUID.randomUUID()), BsonBinary::class.java) +// assertTypedEcho(BsonBinary(byteArrayOf(1,2,3)), BsonBinary::class.java) +// } + BsonType.OBJECT_ID -> { + assertTypeOfFirstArgFunction(ObjectId(), ObjectId::class.java) + assertTypeOfFirstArgFunction(BsonObjectId(ObjectId()), BsonObjectId::class.java) + } + BsonType.BOOLEAN -> { + assertTrue(functions.callFunction(FIRST_ARG_FUNCTION, listOf(true), java.lang.Boolean::class.java).booleanValue()) + assertTypeOfFirstArgFunction(BsonBoolean(true), BsonBoolean::class.java) + } + BsonType.INT32 -> { + assertEquals(32, functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), Integer::class.java).toInt()) + assertEquals(32, functions.callFunction(FIRST_ARG_FUNCTION, listOf(32L), Integer::class.java).toInt()) + assertTypeOfFirstArgFunction(BsonInt32(32), BsonInt32::class.java) + } + BsonType.INT64 -> { + assertEquals(32L, functions.callFunction(FIRST_ARG_FUNCTION, listOf(32L), java.lang.Long::class.java).toLong()) + assertEquals(32L, functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), java.lang.Long::class.java).toLong()) + assertTypeOfFirstArgFunction(BsonInt64(32), BsonInt64::class.java) + } + BsonType.DECIMAL128 -> { + assertTypeOfFirstArgFunction(Decimal128(32L), Decimal128::class.java) + assertTypeOfFirstArgFunction(BsonDecimal128(Decimal128(32L)), BsonDecimal128::class.java) + } + BsonType.DOCUMENT -> { + val map = mapOf("foo" to 5) + val document = Document(map) + assertEquals(map, functions.callFunction(FIRST_ARG_FUNCTION, listOf(map), Map::class.java)) + assertEquals(map, functions.callFunction(FIRST_ARG_FUNCTION, listOf(document), Map::class.java)) + assertEquals(document, functions.callFunction(FIRST_ARG_FUNCTION, listOf(map), Document::class.java)) + assertEquals(document, functions.callFunction(FIRST_ARG_FUNCTION, listOf(document), Document::class.java)) + + // Previously failing in C++ parser + var documents = listOf(Document(), Document()) + assertEquals(documents[0], functions.callFunction(FIRST_ARG_FUNCTION, documents, Document::class.java)) + documents = listOf(Document("KEY", "VALUE"), Document("KEY", "VALUE"), Document("KEY", "VALUE")) + assertEquals(documents[0], functions.callFunction(FIRST_ARG_FUNCTION, documents, Document::class.java)) + } + BsonType.DATE_TIME -> { + // FIXME See jniParseError_date + } + BsonType.UNDEFINED, + BsonType.NULL, + BsonType.REGULAR_EXPRESSION, + BsonType.SYMBOL, + BsonType.DB_POINTER, + BsonType.JAVASCRIPT, + BsonType.JAVASCRIPT_WITH_SCOPE, + BsonType.TIMESTAMP, + BsonType.END_OF_DOCUMENT, + BsonType.MIN_KEY, + BsonType.MAX_KEY -> { + // Relying on org.bson codec providers for conversion, so skipping explicit + // tests for these more exotic types + } + } + } + } + + private fun assertTypeOfFirstArgFunction(value: T, returnClass: Class) : T { + val actual = functions.callFunction(FIRST_ARG_FUNCTION, listOf(value), returnClass) + assertEquals(value, actual) + return actual + } + + @Test + fun asyncCallFunction() = looperThread.runBlocking { + functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(32), Integer::class.java) { result -> + try { + assertEquals(32, result.orThrow.toInt()) + } finally { + looperThread.testComplete() + } + } + } + + + @Test + fun codecArgumentFailure() { + assertFailsWith { + functions.callFunction(FIRST_ARG_FUNCTION, listOf(Dog("PojoFido")), Dog::class.java) + } + } + + @Test + fun asyncCodecArgumentFailure() = looperThread.runBlocking { + functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(Dog("PojoFido")), Integer::class.java) { result -> + try { + assertTrue(result.error.exception is CodecConfigurationException) + } finally { + looperThread.testComplete() + } + } + } + + @Test + fun codecResponseFailure() { + assertFailsWith { + functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), Dog::class.java) + } + } + + @Test + fun asyncCodecResponseFailure() = looperThread.runBlocking { + functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(Dog("PojoFido")), Integer::class.java) { result -> + try { + assertTrue(result.error.exception is CodecConfigurationException) + } finally { + looperThread.testComplete() + } + } + } + + @Test + fun codecBsonFailure() { + assertFailsWith { + functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), String::class.java) + } + } + + @Test + fun asyncCodecBsonFailure() = looperThread.runBlocking { + functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(32), String::class.java) { result -> + try { + assertTrue(result.error.exception is BSONException) + } finally { + looperThread.testComplete() + } + } + } + + @Test + fun localCodecRegistry() { + val input = Dog("PojoFido") + assertEquals(input, functions.callFunction(FIRST_ARG_FUNCTION, listOf(input), Dog::class.java, pojoRegistry)) + } + + @Test + fun asyncLocalCodecRegistry() = looperThread.runBlocking { + val input = Dog("PojoFido") + functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(input), Dog::class.java, pojoRegistry) { result -> + try { + assertEquals(input, result.orThrow) + } finally { + looperThread.testComplete() + } + } + } + + @Test + fun instanceCodecRegistry() { + val input = Dog("PojoFido") + val functionsWithCodecRegistry = anonUser.getFunctions(pojoRegistry) + assertEquals(input, functionsWithCodecRegistry.callFunction(FIRST_ARG_FUNCTION, listOf(input), Dog::class.java)) + } + + + @Test + fun unknownFunction() { + assertFailsWithErrorCode(ErrorCode.FUNCTION_NOT_FOUND) { + functions.callFunction("unknown", listOf(32), Dog::class.java) + } + } + + @Test + fun asyncUnknownFunction() = looperThread.runBlocking { + val input = Dog("PojoFido") + functions.callFunctionAsync("unknown", listOf(input), Dog::class.java, pojoRegistry) { result -> + try { + assertEquals(ErrorCode.FUNCTION_NOT_FOUND, result.error.errorCode) + } finally { + looperThread.testComplete() + } + } + } + + @Test + fun asyncNonLoopers() { + assertFailsWith { + functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(32), Integer::class.java, pojoRegistry) { result -> + fail() + } + } + } + + @Test + fun callFunction_sum() { + val numbers = listOf(1, 2, 3, 4) + assertEquals(10, functions.callFunction("sum", numbers, Integer::class.java).toInt()) + } + + @Test + fun callFunction_remoteError() { + assertFailsWithErrorCode(ErrorCode.FUNCTION_EXECUTION_ERROR) { + functions.callFunction("error", emptyList(), String::class.java) + } + } + + @Test + fun callFunction_null() { + assertTrue(functions.callFunction("null", emptyList(), BsonNull::class.java).isNull) + } + + @Test + fun callFunction_void() { + assertEquals(BsonType.UNDEFINED, functions.callFunction("void", emptyList(), BsonUndefined::class.java).bsonType) + } + + @Test + fun callFunction_afterLogout() { + anonUser.logOut() + assertFailsWithErrorCode(ErrorCode.SERVICE_UNKNOWN) { + functions.callFunction(FIRST_ARG_FUNCTION, listOf(1, 2, 3), Integer::class.java) + } + } + + // Tests that functions that should not execute based on "canevalute"-expression fails. + @Test + fun callFunction_authorizedOnly() { + // Not allow for anonymous user + assertFailsWithErrorCode(ErrorCode.FUNCTION_EXECUTION_ERROR) { + functions.callFunction("authorizedOnly", listOf(1, 2, 3), Document::class.java) + } + // User email must match "canevaluate" section of servers "functions/authorizedOnly/config.json" + val authorizedUser = app.registerUserAndLogin("authorizeduser@example.org", "asdfasdf") + assertNotNull(authorizedUser.functions.callFunction("authorizedOnly", listOf(1,2,3), Document::class.java)) + } + + @Test + fun getApp() { + assertEquals(app, functions.app) + } + + @Test + fun getUser() { + assertEquals(anonUser, functions.user) + } + + @Test + fun defaultCodecRegistry() { + // TODO Maybe we should test that setting configuration specific would propagate all the way + // to here, but we do not have infrastructure to easily override TestRealmApp configuration, + // and actual configuration is verified in RealmAppConfigurationTests + assertEquals(app.configuration.defaultCodecRegistry, functions.defaultCodecRegistry) + } + + @Test + fun customCodecRegistry() { + val configCodecRegistry = CodecRegistries.fromCodecs(StringCodec()) + val customCodecRegistryFunctions = anonUser.getFunctions(configCodecRegistry) + assertEquals(configCodecRegistry, customCodecRegistryFunctions.defaultCodecRegistry) + } + + @Test + fun illegalBsonArgument() { + // Coded that will generate non-BsonArray from list + val faultyListCodec = object : Codec> { + override fun getEncoderClass(): Class> { return Iterable::class.java } + override fun encode(writer: BsonWriter, value: Iterable<*>, encoderContext: EncoderContext) { + writer.writeString("Not an array") + } + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): ArrayList<*> { + TODO("Not yet implemented") + } + } + // Codec registry that will use the above faulty codec for lists + val faultyCodecRegistry = CodecRegistries.fromProviders( + object: CodecProvider { + override fun get(clazz: Class?, registry: CodecRegistry?): Codec { + return faultyListCodec as Codec + } + } + ) + assertFailsWith { + functions.callFunction(FIRST_ARG_FUNCTION, listOf("Realm"), String::class.java, faultyCodecRegistry) + } + } + + @Test + @Ignore("JNI parsing crashes tests") + fun jniParseError_arrayOfBinary() { + val value = byteArrayOf(1, 2, 3) + val listOf = listOf(value) + val actual = functions.callFunction(FIRST_ARG_FUNCTION, listOf, ByteArray::class.java) + assertEquals(value.toList(), actual.toList()) + } + + @Test + @Ignore("JNI parsing fails to parse into a bson array") + fun jniParseError_arrayOfDocuments() { + val map = mapOf("foo" to 5, "bar" to 7) + assertEquals(map, functions.callFunction(FIRST_ARG_FUNCTION, listOf(map), Map::class.java)) + } + + @Test + @Ignore("JNI parsing seems to truncate value to 32-bit") + fun jniParseError_date() { + val now = Date(System.currentTimeMillis()) + assertEquals(now, functions.callFunction(FIRST_ARG_FUNCTION, listOf(now), Date::class.java)) + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt index 20e7db88ac..411d8e5e1a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt @@ -11,7 +11,7 @@ import org.junit.rules.ErrorCollector /** * Verify that an [ObjectServerError] exception is thrown with a specific [ErrorCode] */ -inline fun expectErrorCode(expectedCode: ErrorCode, method: () -> Unit) { +inline fun assertFailsWithErrorCode(expectedCode: ErrorCode, method: () -> Unit) { try { method() fail() diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 8f9e1d608a..13f75d6d44 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -108,7 +108,7 @@ if (build_SYNC) io.realm.RealmApp io.realm.RealmUser io.realm.RealmSync - io.realm.RealmFunctions + io.realm.FunctionsImpl io.realm.SyncSession io.realm.internal.objectstore.OsAppCredentials io.realm.internal.objectstore.OsAsyncOpenTask @@ -199,7 +199,7 @@ if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmApp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmUser.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmFunctions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_FunctionsImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_EmailPasswordAuth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ApiKeyAuth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetRequiredError.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp deleted file mode 100644 index 82a44a86c8..0000000000 --- a/realm/realm-library/src/main/cpp/io_realm_RealmFunctions.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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 "io_realm_RealmFunctions.h" - -#include "util.hpp" -#include "jni_util/bson_util.hpp" - -using namespace realm; -using namespace realm::jni_util; - -// FIXME This is just a basic round trip test for passing bson back and forth. Proper implementation -// will come with actual Function implementation. -JNIEXPORT jstring JNICALL Java_io_realm_RealmFunctions_nativeCallFunction - (JNIEnv* env, jclass, jstring j_args) { - try { - bson::Bson bson = JniBsonProtocol::jstring_to_bson(env, j_args); - return JniBsonProtocol::bson_to_jstring(env, bson); - } - CATCH_STD() - return NULL; -} - diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp new file mode 100644 index 0000000000..2830ff8338 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_FunctionsImpl.h" + +#include "util.hpp" +#include "jni_util/bson_util.hpp" +#include "java_network_transport.hpp" +#include "object-store/src/sync/app.hpp" + + +using namespace realm; +using namespace realm::app; +using namespace realm::bson; +using namespace realm::jni_util; + +static std::function )> success_mapper = [](JNIEnv* env, Optional response) { + if (response) { + return JniBsonProtocol::bson_to_jstring(env, *response); + } else { + // We should never reach here, as this is the success mapper and we would not end up here + // if we did not received a parsable BSON response + throw std::logic_error("Function did not return a result"); + } +}; + +JNIEXPORT void JNICALL +Java_io_realm_FunctionsImpl_nativeCallFunction(JNIEnv* env, jclass , jlong j_app_ptr, jlong j_user_ptr, jstring j_name, + jstring j_args_json , jobject j_callback) { + try { + auto app = *reinterpret_cast*>(j_app_ptr); + auto user = *reinterpret_cast*>(j_user_ptr); + + std::function, Optional)> callback = JavaNetworkTransport::create_result_callback(env, j_callback, success_mapper); + + auto handler = [callback](Optional error, Optional response) { + callback(response, error); + }; + + JStringAccessor name(env, j_name); + BsonArray args(JniBsonProtocol::parse_checked(env, j_args_json, Bson::Type::Array, "BSON argument must be an BsonArray")); + app->call_function(user, name, args, handler); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp b/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp index 737bb41a45..ff45442518 100644 --- a/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/bson_util.cpp @@ -24,23 +24,34 @@ static const std::string VALUE("value"); using namespace realm::bson; using namespace realm::jni_util; -Bson JniBsonProtocol::string_to_bson(std::string arg) { +Bson JniBsonProtocol::string_to_bson(const std::string arg) { BsonDocument document(parse(arg)); return document[VALUE]; } -Bson JniBsonProtocol::jstring_to_bson(JNIEnv* env, jstring arg) { +Bson JniBsonProtocol::jstring_to_bson(JNIEnv* env, const jstring arg) { return string_to_bson(JStringAccessor(env, arg)); } -std::string JniBsonProtocol::bson_to_string(Bson bson) { +const Bson& JniBsonProtocol::check(const realm::bson::Bson& bson, const realm::bson::Bson::Type type, const std::string message) { + if (bson.type() != type) { + throw realm::util::invalid_argument(message); + } + return bson; +} + +Bson JniBsonProtocol::parse_checked(JNIEnv* env, const jstring arg, const Bson::Type type, const std::string message) { + return JniBsonProtocol::check(JniBsonProtocol::jstring_to_bson(env, arg), type, message); +} + +std::string JniBsonProtocol::bson_to_string(const Bson& bson) { BsonDocument document{{VALUE, bson}}; std::stringstream buffer; buffer << document; return buffer.str(); } -jstring JniBsonProtocol::bson_to_jstring(JNIEnv* env, Bson bson) { +jstring JniBsonProtocol::bson_to_jstring(JNIEnv* env, const Bson& bson) { std::string r = bson_to_string(bson); return to_jstring(env, r); }; diff --git a/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp b/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp index c4a3931495..eb5377d7a7 100644 --- a/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp @@ -23,13 +23,17 @@ namespace realm { namespace jni_util { +using namespace realm::bson; + // Serializes and wraps bson values passed between java and JNI according to JniBsonProtocol.java class JniBsonProtocol { public: - static realm::bson::Bson string_to_bson(std::string arg); - static realm::bson::Bson jstring_to_bson(JNIEnv* env, jstring arg); - static std::string bson_to_string(realm::bson::Bson bson); - static jstring bson_to_jstring(JNIEnv* env, realm::bson::Bson bson); + static Bson string_to_bson(const std::string arg); + static Bson jstring_to_bson(JNIEnv* env, const jstring arg); + static const Bson& check(const Bson& bson, const Bson::Type type, const std::string message); + static Bson parse_checked(JNIEnv* env, const jstring arg, const Bson::Type type, const std::string message); + static std::string bson_to_string(const Bson& bson); + static jstring bson_to_jstring(JNIEnv* env, const Bson& bson); }; } // jni_util diff --git a/realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java b/realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java new file mode 100644 index 0000000000..fc734657fd --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import io.realm.internal.Util; +import io.realm.internal.jni.JniBsonProtocol; +import io.realm.internal.jni.OsJNIResultCallback; +import io.realm.internal.network.ResultHandler; +import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.mongodb.functions.Functions; + +/** + * Internal implementation of Functions invoking the actual OS function in the context of the + * {@link RealmUser}/{@link RealmApp}. + */ +class FunctionsImpl extends Functions { + + FunctionsImpl(RealmUser user) { + this(user, user.getApp().getConfiguration().getDefaultCodecRegistry()); + } + + FunctionsImpl(RealmUser user, CodecRegistry codecRegistry) { + super(user, codecRegistry); + } + + // Invokes actual MongoDB Realm Function in the context of the associated user/app. + @Override + public T invoke(String name, List args, Class resultClass, CodecRegistry codecRegistry) { + Util.checkEmpty(name, "name"); + + String encodedArgs = JniBsonProtocol.encode(args, codecRegistry); + + // NativePO calling scheme is actually synchronous + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected String mapSuccess(Object result) { + return (String) result; + } + }; + nativeCallFunction(user.getApp().nativePtr, user.osUser.getNativePtr(), name, encodedArgs, callback); + String encodedResponse = ResultHandler.handleResult(success, error); + return JniBsonProtocol.decode(encodedResponse, resultClass, codecRegistry); + } + + private static native void nativeCallFunction(long nativeAppPtr, long nativeUserPtr, String name, String args_json, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index 09bda6881f..5cea0add2c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -20,6 +20,8 @@ import android.os.Handler; import android.os.Looper; +import org.bson.codecs.configuration.CodecRegistry; + import java.io.File; import java.io.IOException; import java.util.HashMap; @@ -45,6 +47,7 @@ import io.realm.internal.network.OkHttpNetworkTransport; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.log.RealmLog; +import io.realm.mongodb.functions.Functions; /** * FIXME @@ -369,6 +372,25 @@ public RealmSync getSync() { return syncManager; } + /** + * Returns a Functions manager for invoking MongoDB Realm Functions. + *

              + * This will use the associated app's default codec registry to encode and decode arguments and + * results. + */ + public Functions getFunctions(RealmUser user) { + return new FunctionsImpl(user); + } + + /** + * Returns a Functions manager for invoking MongoDB Realm Functions with custom + * codec registry for encoding and decoding arguments and results. + */ + public Functions getFunctions(RealmUser user, CodecRegistry codecRegistry) { + return new FunctionsImpl(user, codecRegistry); + } + + /** * Returns the configuration object for this app. * @@ -395,13 +417,17 @@ OsJavaNetworkTransport getNetworkTransport() { // Class wrapping requests made against MongoDB Realm. Is also responsible for calling with success/error on the // correct thread. - static abstract class Request { + // FIXME Made public to use in Functions. Consider reworking when RealmApp, RealmUser is moved + // to mongodb package and async MongoDB API's are settled + public static abstract class Request { @Nullable private final RealmApp.Callback callback; private final RealmNotifier handler; private final ThreadPoolExecutor networkPoolExecutor; - Request(ThreadPoolExecutor networkPoolExecutor, @Nullable RealmApp.Callback callback) { + // FIXME Made public to use in Functions. Consider reworking when RealmApp, RealmUser is moved + // to mongodb package and async MongoDB API's are settled + public Request(ThreadPoolExecutor networkPoolExecutor, @Nullable RealmApp.Callback callback) { this.callback = callback; this.handler = new AndroidRealmNotifier(null, new AndroidCapabilities()); this.networkPoolExecutor = networkPoolExecutor; diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java index 5f14764617..e0ca95e8c3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java @@ -46,6 +46,30 @@ */ public class RealmAppConfiguration { + /** + * Default BSON codec registry for encoding/decoding arguments and results to/from MongoDB Realm backend. + * + * @see RealmAppConfiguration#getDefaultCodecRegistry() + * @see RealmAppConfiguration.Builder#codecRegistry(CodecRegistry) + * @see ValueCodecProvider + * @see BsonValueCodecProvider + * @see IterableCodecProvider + * @see MapCodecProvider + * @see DocumentCodecProvider + */ + public static final CodecRegistry DEFAULT_BSON_CODEC_REGISTRY = CodecRegistries.fromRegistries( + CodecRegistries.fromProviders( + // For primitive support + new ValueCodecProvider(), + // For BSONValue support + new BsonValueCodecProvider(), + new DocumentCodecProvider(), + // For list support + new IterableCodecProvider(), + new MapCodecProvider() + ) + ); + private final String appId; private final String appName; private final String appVersion; @@ -193,19 +217,6 @@ public File getSyncRootDirectory() { * FIXME */ public static class Builder { - // Default BSON codec for passing BSON to/from JNI - static CodecRegistry DEFAULT_BSON_CODEC_REGISTRY = CodecRegistries.fromRegistries( - CodecRegistries.fromProviders( - // For primitive support - new ValueCodecProvider(), - // For BSONValue support - new BsonValueCodecProvider(), - new DocumentCodecProvider(), - // For list support - new IterableCodecProvider(), - new MapCodecProvider() - ) - ); private String appId; private String appName; diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java b/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java deleted file mode 100644 index 99c7ddb279..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmFunctions.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ -package io.realm; - -import org.bson.codecs.configuration.CodecRegistry; - -import io.realm.internal.jni.JniBsonProtocol; - -// FIXME This class is only a placeholder for JNI round trip as until actual RealmFunctions -// implementation supersedes it. -class RealmFunctions { - - private CodecRegistry codecRegistry; - - RealmFunctions(CodecRegistry codecRegistry) { - this.codecRegistry = codecRegistry; - } - - // FIXME Prelimiry implementation to be able to test passing BsonValues through JNI - T invoke(Object arg, Class resultClass) { - return invoke(arg, resultClass, codecRegistry); - } - - // FIXME Prelimiry implementation to be able to test passing BsonValues through JNI - T invoke(Object arg, Class resultClass, CodecRegistry registry) { - String a = JniBsonProtocol.encode(arg, registry); - String s = nativeCallFunction(a); - return JniBsonProtocol.decode(s, resultClass, registry); - } - - private static native String nativeCallFunction(String arg); - -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java index 22393cda82..00f3cd9cf8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -15,6 +15,8 @@ */ package io.realm; +import org.bson.codecs.configuration.CodecRegistry; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -29,6 +31,7 @@ import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.internal.objectstore.OsSyncUser; import io.realm.internal.util.Pair; +import io.realm.mongodb.functions.Functions; import io.realm.mongodb.mongo.MongoClient; /** @@ -40,6 +43,7 @@ public class RealmUser { private final RealmApp app; private ApiKeyAuth apiKeyAuthProvider = null; private MongoClient mongoClient = null; + private Functions functions = null; /** * FIXME @@ -424,10 +428,25 @@ public synchronized ApiKeyAuth getApiKeyAuth() { } /** - * FIXME Add support for functions. Name of Class and method still TBD. + * Returns a Realm Functions manager for invoking MongoDB Realm Functions. + *

              + * This will use the associated app's default codec registry to encode and decode arguments and + * results. */ - public RealmFunctions getFunctions() { - return null; + public synchronized Functions getFunctions() { + checkLoggedIn(); + if (functions == null) { + functions = new FunctionsImpl(this); + } + return functions; + } + + /** + * Returns a Realm Functions manager for invoking MongoDB Realm Functions with custom + * codec registry for encoding and decoding arguments and results. + */ + public Functions getFunctions(CodecRegistry codecRegistry) { + return new FunctionsImpl(this, codecRegistry); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java new file mode 100644 index 0000000000..e69de29bb2 diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java new file mode 100644 index 0000000000..c19b5b5cea --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java @@ -0,0 +1,195 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.functions; + +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.List; + +import io.realm.ObjectServerError; +import io.realm.RealmApp; +import io.realm.RealmAppConfiguration; +import io.realm.RealmAsyncTask; +import io.realm.RealmUser; +import io.realm.internal.Util; +import io.realm.internal.jni.JniBsonProtocol; + +/** + * A Functions manager to call MongoDB Realm functions. + *

              + * Arguments and results are encoded/decoded with the Functions' codec registry either + * inherited from the {@link RealmAppConfiguration#getDefaultCodecRegistry()} or set explicitly + * when creating the Functions-instance through {@link RealmUser#getFunctions(CodecRegistry)} + * or through the individual calls to {@link #callFunction(String, List, Class, CodecRegistry)}. + * + * @see RealmUser#getFunctions() + * @see RealmUser#getFunctions(CodecRegistry) + * @see RealmApp#getFunctions(RealmUser) + * @see RealmApp#getFunctions(RealmUser, CodecRegistry) + * @see RealmAppConfiguration + * @see CodecRegistry + */ +public abstract class Functions { + + protected RealmUser user; + + private CodecRegistry defaultCodecRegistry; + + protected Functions(RealmUser user, CodecRegistry codecRegistry) { + this.user = user; + this.defaultCodecRegistry = codecRegistry; + } + + /** + * Call a MongoDB Realm function synchronously with custom codec registry encoding/decoding + * arguments/results. + * + * @param name Name of the Stitch function to call. + * @param args Arguments to the Stitch function. + * @param resultClass The type that the functions result should be converted to. + * @param codecRegistry Codec registry to use for argument encoding and result decoding. + * @param The type that the response will be decoded as using the {@code codecRegistry}. + * @return Result of the Stitch function. + * + * @throws ObjectServerError if the request failed in some way. + * @throws org.bson.codecs.configuration.CodecConfigurationException if the {@code codecRegistry} + * does not provide codecs for the argument or {@code resultClass}. + * @throws org.bson.BSONException is an error occurred during BSON processing. + * + * @see #callFunctionAsync(String, List, Class, CodecRegistry, RealmApp.Callback) + * @see RealmAppConfiguration#getDefaultCodecRegistry() + */ + public T callFunction(String name, List args, Class resultClass, CodecRegistry codecRegistry) { + return invoke(name, args, resultClass, codecRegistry); + } + + /** + * Call a MongoDB Realm function synchronously with default codec registry encoding/decoding + * arguments/results. + * + * @param name Name of the Stitch function to call. + * @param args Arguments to the Stitch function. + * @param resultClass The type that the functions result should be converted to. + * @param The type that the response will be decoded as using the default codec registry. + * @return Result of the Stitch function. + * + * @throws ObjectServerError if the request failed in some way. + * @throws org.bson.codecs.configuration.CodecConfigurationException if the {@code codecRegistry} + * does not provide codecs for the argument or {@code resultClass}. + * @throws org.bson.BSONException is an error occurred during BSON processing. + * + * @see #callFunction(String, List, Class, CodecRegistry) + * @see RealmAppConfiguration#getDefaultCodecRegistry() + */ + public T callFunction(String name, List args, Class resultClass) { + return callFunction(name, args, resultClass, defaultCodecRegistry); + } + + /** + * Call a MongoDB Realm function asynchronously with custom codec registry for encoding/decoding + * arguments/results. + *

              + * This is the asynchronous equivalent of {@link #callFunction(String, List, Class, CodecRegistry)}. + * + * @param name Name of the Stitch function to call. + * @param args Arguments to the Stitch function. + * @param resultClass The type that the functions result should be converted to. + * @param codecRegistry Codec registry to use for argument encoding and result decoding. + * @param callback The callback that will receive the result of the request. If the request + * failed in some way, the codec registry failed to provide codecs for the + * arguments or {@code resultClass}, or an error occurres during BSON processing + * the result will indicate the error as a {@link ObjectServerError}, + * {@link org.bson.codecs.configuration.CodecConfigurationException} + * or {@link ObjectServerError} respectively. + * @param The type that the response will be decoded as using the default codec registry. + * @return Result of the Stitch function. + * + * @throws IllegalStateException if not called on a looper thread. + * + * @see #callFunction(String, List, Class, CodecRegistry) + * @see #callFunctionAsync(String, List, Class, CodecRegistry, RealmApp.Callback) + * @see RealmAppConfiguration#getDefaultCodecRegistry() + */ + public RealmAsyncTask callFunctionAsync(String name, List args, Class resultClass, CodecRegistry codecRegistry, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous functions is only possible from looper threads."); + return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { + @Override + public T run() throws ObjectServerError { + return callFunction(name, args, resultClass, codecRegistry); + } + }.start(); + } + + /** + * Call a MongoDB Realm function asynchronously with custom codec registry for encoding/decoding + * arguments/results. + *

              + * This is the asynchronous equivalent of {@link #callFunction(String, List, Class)}. + * + * @param name Name of the Stitch function to call. + * @param args Arguments to the Stitch function. + * @param resultClass The type that the functions result should be converted to. + * @param callback The callback that will receive the result of the request. If the request + * failed in some way, the codec registry failed to provide codecs for the + * arguments or {@code resultClass}, or an error occurres during BSON processing + * the result will indicate the error as a {@link ObjectServerError}, + * {@link org.bson.codecs.configuration.CodecConfigurationException} + * or {@link ObjectServerError} respectively. + * @param The type that the response will be decoded as using the default codec registry. + * @return Result of the Stitch function. + * + * @throws IllegalStateException if not called on a looper thread. + * + * @see #callFunction(String, List, Class) + * @see #callFunctionAsync(String, List, Class, CodecRegistry, RealmApp.Callback) + * @see RealmAppConfiguration#getDefaultCodecRegistry() + */ + public RealmAsyncTask callFunctionAsync(String name, List args, Class resultClass, RealmApp.Callback callback) { + return callFunctionAsync(name, args, resultClass, defaultCodecRegistry, callback); + } + + /** + * Returns the default codec registry used for encoding arguments and decoding results for this + * Realm functions instance. + * + * @return The default codec registry. + */ + public CodecRegistry getDefaultCodecRegistry() { + return defaultCodecRegistry; + } + + /** + * Returns the {@link RealmApp} that this instance in associated with. + * + * @return The {@link RealmApp} that this instance in associated with. + */ + public RealmApp getApp() { + return user.getApp(); + } + + /** + * Returns the {@link RealmUser} that this instance in associated with. + * + * @return The {@link RealmUser} that this instance in associated with. + */ + public RealmUser getUser() { + return user; + } + + protected abstract T invoke(String name, List args, Class resultClass, CodecRegistry codecRegistry); + +} diff --git a/tools/sync_test_server/app_config/functions/authFunc/config.json b/tools/sync_test_server/app_config/functions/authFunc/config.json index 8bb99cb6ab..43e28c813c 100644 --- a/tools/sync_test_server/app_config/functions/authFunc/config.json +++ b/tools/sync_test_server/app_config/functions/authFunc/config.json @@ -1,5 +1,5 @@ { - "id": "5e9578c1a06f8d660afdaa31", + "id": "5eba4fbc21bb6f152f45a84d", "name": "authFunc", "private": false, "can_evaluate": {} diff --git a/tools/sync_test_server/app_config/functions/authorizedOnly/config.json b/tools/sync_test_server/app_config/functions/authorizedOnly/config.json new file mode 100644 index 0000000000..b400765f4f --- /dev/null +++ b/tools/sync_test_server/app_config/functions/authorizedOnly/config.json @@ -0,0 +1,12 @@ +{ + "id": "5ebd96e125e549dd987715a0", + "name": "authorizedOnly", + "private": false, + "can_evaluate": { + "%%user.data.email": { + "%in": [ + "authorizeduser@example.org" + ] + } + } +} diff --git a/tools/sync_test_server/app_config/functions/authorizedOnly/source.js b/tools/sync_test_server/app_config/functions/authorizedOnly/source.js new file mode 100644 index 0000000000..6d96642d2c --- /dev/null +++ b/tools/sync_test_server/app_config/functions/authorizedOnly/source.js @@ -0,0 +1,16 @@ +exports = function(arg){ + /* + Accessing application's values: + var x = context.values.get("value_name"); + + Accessing a mongodb service: + var collection = context.services.get("mongodb-atlas").db("dbname").collection("coll_name"); + var doc = collection.findOne({owner_id: context.user.id}); + + To call other named functions: + var result = context.functions.execute("function_name", arg1, arg2); + + Try running in the console below. + */ + return {arg: context.user}; +}; \ No newline at end of file diff --git a/tools/sync_test_server/app_config/functions/confirmFunc/config.json b/tools/sync_test_server/app_config/functions/confirmFunc/config.json index e1a93ddfbe..117b601c20 100644 --- a/tools/sync_test_server/app_config/functions/confirmFunc/config.json +++ b/tools/sync_test_server/app_config/functions/confirmFunc/config.json @@ -1,5 +1,5 @@ { - "id": "5e9578c1a06f8d660afdaa32", + "id": "5eba4fbc21bb6f152f45a84e", "name": "confirmFunc", "private": false, "can_evaluate": {} diff --git a/tools/sync_test_server/app_config/functions/error/config.json b/tools/sync_test_server/app_config/functions/error/config.json new file mode 100644 index 0000000000..2625f03553 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/error/config.json @@ -0,0 +1,5 @@ +{ + "id": "5eba4fbc21bb6f152f45a850", + "name": "error", + "private": false +} diff --git a/tools/sync_test_server/app_config/functions/error/source.js b/tools/sync_test_server/app_config/functions/error/source.js new file mode 100644 index 0000000000..5f58441042 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/error/source.js @@ -0,0 +1,3 @@ +exports = function(arg){ + return unknown; +}; \ No newline at end of file diff --git a/tools/sync_test_server/app_config/functions/firstArg/config.json b/tools/sync_test_server/app_config/functions/firstArg/config.json new file mode 100644 index 0000000000..fc1fc81ca8 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/firstArg/config.json @@ -0,0 +1,5 @@ +{ + "id": "5eba4fbc21bb6f152f45a851", + "name": "firstArg", + "private": false +} diff --git a/tools/sync_test_server/app_config/functions/firstArg/source.js b/tools/sync_test_server/app_config/functions/firstArg/source.js new file mode 100644 index 0000000000..0c45d88599 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/firstArg/source.js @@ -0,0 +1,4 @@ +exports = function(arg){ + // Returns first argument + return arg +}; \ No newline at end of file diff --git a/tools/sync_test_server/app_config/functions/null/config.json b/tools/sync_test_server/app_config/functions/null/config.json new file mode 100644 index 0000000000..cefb66ac08 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/null/config.json @@ -0,0 +1,5 @@ +{ + "id": "5eba4fbc21bb6f152f45a852", + "name": "null", + "private": false +} diff --git a/tools/sync_test_server/app_config/functions/null/source.js b/tools/sync_test_server/app_config/functions/null/source.js new file mode 100644 index 0000000000..f9227c9111 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/null/source.js @@ -0,0 +1,3 @@ +exports = function(arg){ + return null; +}; \ No newline at end of file diff --git a/tools/sync_test_server/app_config/functions/resetFunc/config.json b/tools/sync_test_server/app_config/functions/resetFunc/config.json index 33dcc7dd91..0d014d4c02 100644 --- a/tools/sync_test_server/app_config/functions/resetFunc/config.json +++ b/tools/sync_test_server/app_config/functions/resetFunc/config.json @@ -1,5 +1,5 @@ { - "id": "5e9578c1a06f8d660afdaa33", + "id": "5eba4fbc21bb6f152f45a853", "name": "resetFunc", "private": false, "can_evaluate": {} diff --git a/tools/sync_test_server/app_config/functions/sum/config.json b/tools/sync_test_server/app_config/functions/sum/config.json new file mode 100644 index 0000000000..6461497828 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/sum/config.json @@ -0,0 +1,5 @@ +{ + "id": "5eba4fbc21bb6f152f45a854", + "name": "sum", + "private": false +} diff --git a/tools/sync_test_server/app_config/functions/sum/source.js b/tools/sync_test_server/app_config/functions/sum/source.js new file mode 100644 index 0000000000..7e76cbbaba --- /dev/null +++ b/tools/sync_test_server/app_config/functions/sum/source.js @@ -0,0 +1,3 @@ +exports = function(...args) { + return parseInt(args.reduce((a,b) => a + b, 0)); +}; diff --git a/tools/sync_test_server/app_config/functions/void/config.json b/tools/sync_test_server/app_config/functions/void/config.json new file mode 100644 index 0000000000..3fbcfb56f6 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/void/config.json @@ -0,0 +1,5 @@ +{ + "id": "5eba4fbc21bb6f152f45a84f", + "name": "void", + "private": false +} diff --git a/tools/sync_test_server/app_config/functions/void/source.js b/tools/sync_test_server/app_config/functions/void/source.js new file mode 100644 index 0000000000..5cac9b2977 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/void/source.js @@ -0,0 +1,3 @@ +exports = function(arg){ + return void(0); +}; \ No newline at end of file From 408050d21bdf1954d9548b257bd812731ee0e2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 19 May 2020 16:03:25 +0200 Subject: [PATCH 1530/2110] Migrate and fix ignored SyncConfiguration test (#6857) --- .../io/realm/SyncConfigurationTests.kt | 87 +++++++++---------- 1 file changed, 39 insertions(+), 48 deletions(-) rename realm/realm-library/src/androidTestObjectServer/{java => kotlin}/io/realm/SyncConfigurationTests.kt (80%) diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt similarity index 80% rename from realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt index b73de533ea..5f2d69e4d2 100644 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt @@ -22,14 +22,12 @@ import io.realm.entities.StringOnly import io.realm.entities.StringOnlyModule import io.realm.kotlin.createObject import io.realm.kotlin.where -import io.realm.rule.RunInLooperThread -import org.junit.* -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.rules.TemporaryFolder +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test import org.junit.runner.RunWith -import java.lang.IllegalArgumentException -import kotlin.test.assertFailsWith +import kotlin.test.* @RunWith(AndroidJUnit4::class) class SyncConfigurationTests { @@ -41,12 +39,6 @@ class SyncConfigurationTests { @get:Rule val configFactory = TestSyncConfigurationFactory() - @get:Rule - val looperThread = RunInLooperThread() - - @get:Rule - val tempFolder = TemporaryFolder() - private lateinit var app: TestRealmApp @Before @@ -69,14 +61,14 @@ class SyncConfigurationTests { override fun onError(session: SyncSession, error: ObjectServerError) {} } val config = builder.errorHandler(errorHandler).build() - Assert.assertEquals(errorHandler, config.errorHandler) + assertEquals(errorHandler, config.errorHandler) } @Test fun errorHandler_fromSyncManager() { val user: RealmUser = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) - Assert.assertEquals(app.configuration.defaultErrorHandler, config.errorHandler) + assertEquals(app.configuration.defaultErrorHandler, config.errorHandler) } @Test @@ -90,7 +82,7 @@ class SyncConfigurationTests { fun equals() { val user: RealmUser = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) - Assert.assertTrue(config == config) + assertTrue(config == config) } @Test @@ -98,7 +90,7 @@ class SyncConfigurationTests { val user: RealmUser = createTestUser(app) val config1: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION).build() val config2: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION).build() - Assert.assertTrue(config1 == config2) + assertTrue(config1 == config2) } @Test @@ -107,14 +99,14 @@ class SyncConfigurationTests { val user2: RealmUser = createTestUser(app) val config1: SyncConfiguration = SyncConfiguration.Builder(user1, DEFAULT_PARTITION).build() val config2: SyncConfiguration = SyncConfiguration.Builder(user2, DEFAULT_PARTITION).build() - Assert.assertFalse(config1 == config2) + assertFalse(config1 == config2) } @Test fun hashCode_equal() { val user: RealmUser = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) - Assert.assertEquals(config.hashCode(), config.hashCode()) + assertEquals(config.hashCode(), config.hashCode()) } @Test @@ -123,17 +115,17 @@ class SyncConfigurationTests { val user2: RealmUser = createTestUser(app) val config1: SyncConfiguration = SyncConfiguration.defaultConfig(user1, DEFAULT_PARTITION) val config2: SyncConfiguration = SyncConfiguration.defaultConfig(user2, DEFAULT_PARTITION) - Assert.assertNotEquals(config1.hashCode(), config2.hashCode()) + assertNotEquals(config1.hashCode(), config2.hashCode()) } @Test fun get_syncSpecificValues() { val user: RealmUser = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) - Assert.assertTrue(user == config.user) - Assert.assertEquals("ws://127.0.0.1:9090/", config.serverUrl.toString()) // FIXME: Figure out exactly what to return here - Assert.assertFalse(config.shouldDeleteRealmOnLogout()) - Assert.assertTrue(config.isSyncConfiguration) + assertTrue(user == config.user) + assertEquals("ws://127.0.0.1:9090/", config.serverUrl.toString()) // FIXME: Figure out exactly what to return here + assertFalse(config.shouldDeleteRealmOnLogout()) + assertTrue(config.isSyncConfiguration) } @Test @@ -142,7 +134,7 @@ class SyncConfigurationTests { val config: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION) .encryptionKey(TestHelper.getRandomKey()) .build() - Assert.assertNotNull(config.encryptionKey) + assertNotNull(config.encryptionKey) } @Test @@ -152,6 +144,7 @@ class SyncConfigurationTests { assertFailsWith { builder.encryptionKey(TestHelper.getNull()) } } + @Test fun encryption_invalid_wrong_length() { val user: RealmUser = createTestUser(app) val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) @@ -170,26 +163,26 @@ class SyncConfigurationTests { } }) .build() - Assert.assertNotNull(config.initialDataTransaction) + assertNotNull(config.initialDataTransaction) // open the first time - initialData must be triggered - val realm1: Realm = Realm.getInstance(config) - val results: RealmResults = realm1.where().findAll() - assertEquals(1, results.size) - assertEquals("TEST 42", results.first()!!.getChars()) - realm1.close() + Realm.getInstance(config).use { realm -> + val results: RealmResults = realm.where().findAll() + assertEquals(1, results.size) + assertEquals("TEST 42", results.first()!!.getChars()) + } // open the second time - initialData must not be triggered - val realm2: Realm = Realm.getInstance(config) - assertEquals(1, realm2.where().count()) - realm2.close() + Realm.getInstance(config).use { realm -> + assertEquals(1, realm.where().count()) + } } @Test fun defaultRxFactory() { val user: RealmUser = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) - Assert.assertNotNull(config.rxFactory) + assertNotNull(config.rxFactory) } @Test @@ -203,32 +196,29 @@ class SyncConfigurationTests { // Check that it is possible for multiple users to reference the same Realm URL while each user still use their // own copy on the filesystem. This is e.g. what happens if a Realm is shared using a PermissionOffer. @Test - @Ignore("FIXME: Enable this once Sync is working.") fun multipleUsersReferenceSameRealm() { val user1: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") val user2: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val config1: SyncConfiguration = SyncConfiguration.Builder(user1, DEFAULT_PARTITION) .modules(StringOnlyModule()) .build() - val realm1: Realm = Realm.getInstance(config1) val config2: SyncConfiguration = SyncConfiguration.Builder(user2, DEFAULT_PARTITION) .modules(StringOnlyModule()) .build() - var realm2: Realm? = null // Verify that two different configurations can be used for the same URL - realm2 = try { - Realm.getInstance(config1) - } finally { - realm1.close() - if (realm2 != null) { - realm2.close() - } - } + val realm1: Realm = Realm.getInstance(config1) + val realm2: Realm = Realm.getInstance(config2) + assertNotEquals(realm1, realm2) + + realm1.close() + realm2.close() // Verify that we actually save two different files - Assert.assertNotEquals(config1.path, config2.path) + assertNotEquals(config1.path, config2.path) } + @Test fun defaultConfiguration_throwsIfNotLoggedIn() { val user: RealmUser = createTestUser(app) @@ -257,8 +247,9 @@ class SyncConfigurationTests { val config: SyncConfiguration.Builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) try { config.clientResyncMode(TestHelper.getNull()) - Assert.fail() + fail() } catch (ignore: IllegalArgumentException) { } } + } From 227e5967e1cef2ecb32a9937300583e118d55890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 20 May 2020 15:47:32 +0200 Subject: [PATCH 1531/2110] Re-enabling all android CI build nodes (#6865) --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 23a73db10e..998bdfbe99 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,7 +11,7 @@ def dockerNetworkId = UUID.randomUUID().toString() def releaseBranches = ['master', 'next-major', 'v10'] // Branches from which we release SNAPSHOT's def currentBranch = env.CHANGE_BRANCH try { - node('docker-cph-01') { // FIXME: Only working Slave + node('android') { timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { From a537a69b18327850da2b2f563fecde71358987bf Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 25 May 2020 09:43:13 +0200 Subject: [PATCH 1532/2110] Add support for Device Id and bump Sync/OS to latest version (#6873) --- dependencies.list | 6 ++--- .../kotlin/io/realm/KotlinSyncedRealmTests.kt | 3 +-- .../kotlin/io/realm/RealmUserTests.kt | 6 +++++ .../kotlin/io/realm/entities/SyncPerson.kt | 2 +- .../src/main/cpp/CMake/RealmCore.cmake | 4 +++- .../src/main/cpp/io_realm_RealmApp.cpp | 14 +++++++++--- .../cpp/io_realm_internal_OsRealmConfig.cpp | 5 +++-- ..._realm_internal_objectstore_OsSyncUser.cpp | 11 ++++++++++ realm/realm-library/src/main/cpp/object-store | 2 +- .../io/realm/internal/ObjectServerFacade.java | 2 +- .../java/io/realm/internal/OsRealmConfig.java | 22 ++++++++++--------- .../objectServer/java/io/realm/RealmApp.java | 10 +++++++-- .../objectServer/java/io/realm/RealmUser.java | 9 ++++++++ .../internal/SyncObjectServerFacade.java | 20 +++++++++-------- .../internal/objectstore/OsSyncUser.java | 5 +++++ .../BackingDB/rules/test_data.SyncPerson.json | 8 +++++++ 16 files changed, 94 insertions(+), 35 deletions(-) diff --git a/dependencies.list b/dependencies.list index f01fe25715..c42be704dc 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.12 -REALM_SYNC_SHA256=d9ab40f7b73b4438c811adfbbb22ed8da1461d922e77bb46f555f49ae1921748 +REALM_SYNC_VERSION=10.0.0-alpha.14 +REALM_SYNC_SHA256=1d1e9478210f7b26cea882f8e8c8fd5968b7938415ef1120485671a1a4ed2e67 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. @@ -9,7 +9,7 @@ REALM_OBJECT_SERVER_VERSION=3.28.2 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-05-13 +MONGODB_REALM_SERVER_VERSION=2020-05-22 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt index c865d3f603..79f5a35141 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt @@ -30,7 +30,6 @@ import org.junit.Test import org.junit.runner.RunWith import java.util.* -@Ignore("FIXME Disabled until dev mode is not causing this to hang") @RunWith(AndroidJUnit4::class) class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaining Java tests have been moved @@ -147,7 +146,7 @@ class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaini val dog = SyncDog() dog.name = "Fido $i" it.insert(dog) - person.dogs.add(dog.id) + person.dogs.add(dog) } realm.insert(person) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt index 939a68d73a..3dca9415d9 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt @@ -274,4 +274,10 @@ class RealmUserTests { } } + @Test + fun getDeviceId() { + // TODO No reason to integration test this. Use a stubbed response instead. + val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertTrue(user.deviceId.isNotEmpty() && user.deviceId.length == 24) // Server returns a UUID + } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncPerson.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncPerson.kt index 5aec2a34d2..02ab1f48dd 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncPerson.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncPerson.kt @@ -28,7 +28,7 @@ open class SyncPerson( @RealmField(name = "_id") var id: ObjectId? = ObjectId(), var age: Long = 0, - var dogs: RealmList = RealmList(), + var dogs: RealmList = RealmList(), var firstName: String = "", var lastName: String = "" // This field is not required by clients diff --git a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake index ee22a08fc9..6014163b4f 100644 --- a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake +++ b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake @@ -89,9 +89,11 @@ function(use_sync_release enable_sync sync_dist_path) # -latomic is not set by default for mips and armv5. # See https://code.google.com/p/android/issues/detail?id=182094 + list(APPEND LIB_INCLUDE_DIRS "${sync_dist_path}/include") + list(APPEND LIB_INCLUDE_DIRS "${sync_dist_path}/include/realm") set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_path} IMPORTED_LINK_INTERFACE_LIBRARIES atomic - INTERFACE_INCLUDE_DIRECTORIES "${sync_dist_path}/include") + INTERFACE_INCLUDE_DIRECTORIES "${LIB_INCLUDE_DIRS}") if (enable_sync) # Sync static library diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp index 346c904920..d50d9c2619 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp @@ -91,7 +91,10 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject jlong j_request_timeout_ms, jstring j_sync_base_dir, jstring j_user_agent_binding_info, - jstring j_user_agent_application_info) + jstring j_user_agent_application_info, + jstring j_platform, + jstring j_platform_version, + jstring j_sdk_version) { try { @@ -108,14 +111,19 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject JStringAccessor base_url(env, j_base_url); JStringAccessor app_name(env, j_app_name); JStringAccessor app_version(env, j_app_version); - + JStringAccessor platform(env, j_platform); + JStringAccessor platform_version(env, j_platform_version); + JStringAccessor sdk_version(env, j_sdk_version); auto app_config = App::Config{ app_id, transport_generator, util::Optional(base_url), util::Optional(app_name), util::Optional(app_version), - util::Optional(j_request_timeout_ms) + util::Optional(j_request_timeout_ms), + platform, + platform_version, + sdk_version }; // Sync Config diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index d88755db73..e21faafc51 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -245,7 +245,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNo #if REALM_ENABLE_SYNC JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( JNIEnv* env, jclass, jlong native_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, - jstring j_refresh_token, jstring j_access_token, jbyte j_session_stop_policy, jstring j_url_prefix, + jstring j_refresh_token, jstring j_access_token, jstring j_device_id, jbyte j_session_stop_policy, jstring j_url_prefix, jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array, jbyte j_client_reset_mode, jstring j_partion_key_value, jobject j_java_sync_service) { @@ -325,7 +325,8 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe JStringAccessor realm_auth_url(env, j_auth_url); JStringAccessor refresh_token(env, j_refresh_token); JStringAccessor access_token(env, j_access_token); - user = SyncManager::shared().get_user(user_id, auth_url, refresh_token, access_token); + JStringAccessor device_id(env, j_device_id); + user = SyncManager::shared().get_user(user_id, auth_url, refresh_token, access_token, device_id); } SyncSessionStopPolicy session_stop_policy = static_cast(j_session_stop_policy); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp index 8afed94bea..59075c47d9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp @@ -228,3 +228,14 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGe CATCH_STD(); return nullptr; } + +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetDeviceId(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + std::string device_id = user->device_id(); + return to_jstring(env, device_id); + } + CATCH_STD(); + return nullptr; +} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 73bfe58c21..ccd8b7f9a1 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 73bfe58c21f647bfabfae1631850f362ff59e885 +Subproject commit ccd8b7f9a1ac20396aaa38ff9493360cfc40ed96 diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 11e87af2cd..6b986cb85e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -30,7 +30,7 @@ */ public class ObjectServerFacade { - public final static int SYNC_CONFIG_OPTIONS = 14; + public final static int SYNC_CONFIG_OPTIONS = 15; private final static ObjectServerFacade nonSyncFacade = new ObjectServerFacade(); private static ObjectServerFacade syncFacade = null; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 1991b2a0af..590eb1f2fa 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -212,19 +212,20 @@ private OsRealmConfig(final RealmConfiguration config, String syncRealmAuthUrl = (String) syncConfigurationOptions[2]; String syncRefreshToken = (String) syncConfigurationOptions[3]; String syncAccessToken = (String) syncConfigurationOptions[4]; - boolean syncClientValidateSsl = (Boolean.TRUE.equals(syncConfigurationOptions[5])); - String syncSslTrustCertificatePath = (String) syncConfigurationOptions[6]; - Byte sessionStopPolicy = (Byte) syncConfigurationOptions[7]; - String urlPrefix = (String)(syncConfigurationOptions[8]); - String customAuthorizationHeaderName = (String)(syncConfigurationOptions[9]); - Byte clientResyncMode = (Byte) syncConfigurationOptions[11]; - String partitionValue = (String) syncConfigurationOptions[12]; - Object syncService = syncConfigurationOptions[13]; + String deviceId = (String) syncConfigurationOptions[5]; + boolean syncClientValidateSsl = (Boolean.TRUE.equals(syncConfigurationOptions[6])); + String syncSslTrustCertificatePath = (String) syncConfigurationOptions[7]; + Byte sessionStopPolicy = (Byte) syncConfigurationOptions[8]; + String urlPrefix = (String)(syncConfigurationOptions[9]); + String customAuthorizationHeaderName = (String)(syncConfigurationOptions[10]); + Byte clientResyncMode = (Byte) syncConfigurationOptions[12]; + String partitionValue = (String) syncConfigurationOptions[13]; + Object syncService = syncConfigurationOptions[14]; // Convert the headers into a String array to make it easier to send through JNI // [key1, value1, key2, value2, ...] //noinspection unchecked - Map customHeadersMap = (Map) (syncConfigurationOptions[10]); + Map customHeadersMap = (Map) (syncConfigurationOptions[11]); String[] customHeaders = new String[customHeadersMap != null ? customHeadersMap.size() * 2 : 0]; if (customHeadersMap != null) { int i = 0; @@ -285,6 +286,7 @@ private OsRealmConfig(final RealmConfiguration config, syncUserIdentifier, syncRefreshToken, syncAccessToken, + deviceId, sessionStopPolicy, urlPrefix, customAuthorizationHeaderName, @@ -386,7 +388,7 @@ private native void nativeSetSchemaConfig(long nativePtr, byte schemaMode, long private static native String nativeCreateAndSetSyncConfig(long nativePtr, String syncRealmUrl, String authUrl, String userId, String refreshToken, String accessToken, - byte sessionStopPolicy, String urlPrefix, + String deviceId, byte sessionStopPolicy, String urlPrefix, String customAuthorizationHeaderName, String[] customHeaders, byte clientResetMode, String partionKeyValue, Object syncService); diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java index 5cea0add2c..eb12ccf1af 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java @@ -124,7 +124,10 @@ private long init(RealmAppConfiguration config) { config.getRequestTimeoutMs(), syncDir, userAgentBindingInfo, - appDefinedUserAgent); + appDefinedUserAgent, + "android", + android.os.Build.VERSION.RELEASE, + io.realm.BuildConfig.VERSION_NAME); } private String getSyncBaseDirectory() { @@ -598,7 +601,10 @@ private native long nativeCreate(String appId, long requestTimeoutMs, String syncDirPath, String bindingUserInfo, - String appUserInfo); + String appUserInfo, + String platform, + String platformVersion, + String sdkVersion); private static native void nativeLogin(long nativeAppPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); @Nullable private static native Long nativeCurrentUser(long nativePtr); diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java index 00f3cd9cf8..490becf24e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java @@ -207,6 +207,15 @@ public String getRefreshToken() { } + /** + * Returns a unique identifier for the device the user logged in to. + * + * @return a unique device identifier for the user. + */ + public String getDeviceId() { + return osUser.getDeviceId(); + } + /** * Returns the {@link RealmApp} this user is associated with. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index d67fb08296..4c78346d4f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -81,6 +81,7 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { String syncRealmAuthUrl = user.getApp().getConfiguration().getBaseUrl().toString(); String syncUserRefreshToken = user.getRefreshToken(); String syncUserAccessToken = user.getAccessToken(); + String deviceId = user.getDeviceId(); byte sessionStopPolicy = syncConfig.getSessionStopPolicy().getNativeValue(); String urlPrefix = syncConfig.getUrlPrefix(); String customAuthorizationHeaderName = app.getConfiguration().getAuthorizationHeaderName(); @@ -106,15 +107,16 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { configObj[2] = syncRealmAuthUrl; configObj[3] = syncUserRefreshToken; configObj[4] = syncUserAccessToken; - configObj[5] = syncConfig.syncClientValidateSsl(); - configObj[6] = syncConfig.getServerCertificateFilePath(); - configObj[7] = sessionStopPolicy; - configObj[8] = urlPrefix; - configObj[9] = customAuthorizationHeaderName; - configObj[10] = customHeaders; - configObj[11] = OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL; - configObj[12] = partitionValue; - configObj[13] = app.getSync(); + configObj[5] = deviceId; + configObj[6] = syncConfig.syncClientValidateSsl(); + configObj[7] = syncConfig.getServerCertificateFilePath(); + configObj[8] = sessionStopPolicy; + configObj[9] = urlPrefix; + configObj[10] = customAuthorizationHeaderName; + configObj[11] = customHeaders; + configObj[12] = OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL; + configObj[13] = partitionValue; + configObj[14] = app.getSync(); return configObj; } else { return new Object[SYNC_CONFIG_OPTIONS]; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java index 6b8e9e3412..65af02cd51 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java @@ -99,6 +99,10 @@ public Pair[] getIdentities() { return identities; } + public String getDeviceId() { + return nativeGetDeviceId(nativePtr); + } + /** * @return {@link #STATE_LOGGED_IN}, {@link #STATE_LOGGED_OUT} or {@link #STATE_REMOVED} */ @@ -145,4 +149,5 @@ public int hashCode() { private static native byte nativeGetState(long nativePtr); private static native void nativeSetState(long nativePtr, byte state); private static native String nativeGetProviderType(long nativePtr); + private static native String nativeGetDeviceId(long nativePtr); } diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json index 54fe0237e7..c7e8e69360 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json @@ -2,6 +2,14 @@ "id": "5e9578c1a06f8d660afdaa2d", "database": "test_data", "collection": "SyncPerson", + "relationships": { + "dogs": { + "ref": "#/stitch/BackingDB/test_data/SyncDog", + "source_key": "dogs", + "foreign_key": "_id", + "is_list": true + } + }, "roles": [ { "name": "default", From 550a337a5ce6b24f17d00576e6bf27c8af4a8104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 25 May 2020 13:51:19 +0200 Subject: [PATCH 1533/2110] Migrated leftovers of SyncUsersTest to Kotlin (#6868) --- .../java/io/realm/SyncUserTests.java | 518 ------------------ .../kotlin/io/realm/RealmUserTests.kt | 28 + 2 files changed, 28 insertions(+), 518 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java deleted file mode 100644 index d179db7bee..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncUserTests.java +++ /dev/null @@ -1,518 +0,0 @@ -///* -// * Copyright 2016 Realm Inc. -// * -// * 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. -// */ -// -//package io.realm; -// -//import androidx.test.platform.app.InstrumentationRegistry; -//import androidx.test.rule.UiThreadTestRule; -//import androidx.test.ext.junit.runners.AndroidJUnit4; -// -//import org.junit.After; -//import org.junit.Before; -//import org.junit.Ignore; -//import org.junit.Rule; -//import org.junit.Test; -//import org.junit.rules.ExpectedException; -//import org.junit.runner.RunWith; -//import org.mockito.Mockito; -//import org.mockito.invocation.InvocationOnMock; -//import org.mockito.stubbing.Answer; -// -//import java.io.File; -//import java.lang.reflect.Constructor; -//import java.lang.reflect.InvocationTargetException; -//import java.net.MalformedURLException; -//import java.net.URL; -//import java.util.Calendar; -//import java.util.Iterator; -//import java.util.List; -//import java.util.Map; -//import java.util.UUID; -// -//import io.realm.entities.AllTypesModelModule; -//import io.realm.entities.StringOnly; -//import io.realm.internal.objectserver.Token; -//import io.realm.log.RealmLog; -//import io.realm.entities.StringOnlyModule; -//import io.realm.objectserver.utils.UserFactory; -//import io.realm.rule.RunInLooperThread; -//import io.realm.rule.RunTestInLooperThread; -// -//import static io.realm.SyncTestUtils.createTestAdminUser; -//import static io.realm.SyncTestUtils.createTestUser; -//import static junit.framework.Assert.assertEquals; -//import static org.junit.Assert.assertFalse; -//import static org.junit.Assert.assertNotEquals; -//import static org.junit.Assert.assertNotNull; -//import static org.junit.Assert.assertNull; -//import static org.junit.Assert.assertTrue; -//import static org.junit.Assert.fail; -//import static org.mockito.Matchers.any; -//import static org.mockito.Mockito.when; -// -//@Ignore("FIXME: REalmApp refactor") -//@RunWith(AndroidJUnit4.class) -//public class SyncUserTests { -// -// private static final URL authUrl; -// private static final Constructor SYNC_USER_CONSTRUCTOR; -// static { -// try { -// authUrl = new URL("http://localhost/auth"); -// SYNC_USER_CONSTRUCTOR = SyncUser.class.getDeclaredConstructor(Token.class, URL.class); -// SYNC_USER_CONSTRUCTOR.setAccessible(true); -// } catch (MalformedURLException e) { -// throw new ExceptionInInitializerError(e); -// } catch (NoSuchMethodException e) { -// throw new ExceptionInInitializerError(e); -// } -// } -// -// @Rule -// public final RunInLooperThread looperThread = new RunInLooperThread(); -// -// @Rule -// public final ExpectedException thrown = ExpectedException.none(); -// -// @Rule -// public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); -// -// @Before -// public void setUp() { -// BaseRealm.applicationContext = null; -// Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); -// UserStore userStore = SyncManager.getUserStore(); -// for (SyncUser syncUser : userStore.allUsers()) { -// userStore.remove(syncUser.getIdentity(), syncUser.getAuthenticationUrl().toString()); -// } -// } -// -// @After -// public void after() { -// if (!looperThread.isRuleUsed() || looperThread.isTestComplete()) { -// UserFactory.logoutAllUsers(); -// } else { -// looperThread.runAfterTest(new Runnable() { -// @Override -// public void run() { -// UserFactory.logoutAllUsers(); -// } -// }); -// } -// } -// -// private static SyncUser createFakeUser(String id) { -// final Token token = new Token("token_value", id, "path_value", Long.MAX_VALUE, null); -// try { -// return SYNC_USER_CONSTRUCTOR.newInstance(token, authUrl); -// } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { -// fail(e.getMessage()); -// } -// return null; -// } -// -// @Test -// public void equals_validUser() { -// final SyncUser user1 = createFakeUser("id_value"); -// final SyncUser user2 = createFakeUser("id_value"); -// assertTrue(user1.equals(user2)); -// } -// -// @Test -// public void equals_loggedOutUser() { -// final SyncUser user1 = createFakeUser("id_value"); -// final SyncUser user2 = createFakeUser("id_value"); -// user1.logOut(); -// user2.logOut(); -// assertTrue(user1.equals(user2)); -// } -// -// @Test -// public void hashCode_validUser() { -// final SyncUser user = createFakeUser("id_value"); -// assertNotEquals(0, user.hashCode()); -// } -// -// @Test -// public void hashCode_loggedOutUser() { -// final SyncUser user = createFakeUser("id_value"); -// user.logOut(); -// assertNotEquals(0, user.hashCode()); -// } -// -// @Test -// public void toAndFromJson() { -// SyncUser user1 = createTestUser(); -// SyncUser user2 = SyncUser.fromJson(user1.toJson()); -// assertEquals(user1, user2); -// } -// -// // Tests that the UserStore does not return users that have expired -// @Test -// public void currentUser_returnsNullIfUserExpired() { -// // Add an expired user to the user store -// UserStore userStore = SyncManager.getUserStore(); -// userStore.put(createTestUser(Long.MIN_VALUE)); -// -// // Invalid users should not be returned when asking the for the current user -// assertNull(SyncUser.current()); -// } -// -// @Test -// public void currentUser_throwsIfMultipleUsersLoggedIn() { -// RealmObjectServer originalAuthServer = SyncManager.getAuthServer(); -// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); -// SyncManager.setAuthServerImpl(authServer); -// -// try { -// // 1. Login two random users -// when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenAnswer(new Answer() { -// @Override -// public AuthenticateResponse answer(InvocationOnMock invocationOnMock) throws Throwable { -// return getNewRandomUser(); -// } -// }); -// SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); -// SyncUser.logIn(SyncCredentials.facebook("foo"), "http:/test.realm.io/auth"); -// -// // 2. Verify current() now throws -// try { -// SyncUser.current(); -// fail(); -// } catch (IllegalStateException ignore) { -// } -// } finally { -// SyncManager.setAuthServerImpl(originalAuthServer); -// } -// -// } -// -// private AuthenticateResponse getNewRandomUser() { -// String identity = UUID.randomUUID().toString(); -// String userTokenValue = UUID.randomUUID().toString(); -// return SyncTestUtils.createLoginResponse(userTokenValue, identity, Long.MAX_VALUE, false); -// } -// -// // Test that current user is cleared if it is logged out -// @Test -// public void currentUser_clearedOnLogout() { -// // Add 1 valid user to the user store -// SyncUser user = createTestUser(Long.MAX_VALUE); -// UserStore userStore = SyncManager.getUserStore(); -// userStore.put(user); -// -// SyncUser savedUser = SyncUser.current(); -// assertEquals(user, savedUser); -// assertNotNull(savedUser); -// savedUser.logOut(); -// assertNull(SyncUser.current()); -// } -// -// // `all()` returns an empty list if no users are logged in -// @Test -// public void all_empty() { -// Map users = SyncUser.all(); -// assertTrue(users.isEmpty()); -// } -// -// // `all()` returns only valid users. Invalid users are filtered. -// @Test -// public void all_validUsers() { -// // Add 1 expired user and 1 valid user to the user store -// UserStore userStore = SyncManager.getUserStore(); -// userStore.put(createTestUser(Long.MIN_VALUE)); -// userStore.put(createTestUser(Long.MAX_VALUE)); -// -// Map users = SyncUser.all(); -// assertEquals(1, users.size()); -// assertTrue(users.entrySet().iterator().next().getValue().isValid()); -// } -// -// @Test -// public void isAdmin() { -// SyncUser user1 = createTestUser(); -// assertFalse(user1.isAdmin()); -// -// SyncUser user2 = createTestAdminUser(); -// assertTrue(user2.isAdmin()); -// } -// -// @Test -// public void isAdmin_allUsers() { -// UserStore userStore = SyncManager.getUserStore(); -// SyncUser user = createTestAdminUser(); -// assertTrue(user.isAdmin()); -// userStore.put(user); -// -// Map users = SyncUser.all(); -// assertEquals(1, users.size()); -// assertTrue(users.entrySet().iterator().next().getValue().isAdmin()); -// } -// -// // Tests that the user store returns the last user to login -// @Ignore("This test fails because of wrong JSON string.") -// @Test -// public void currentUser_returnsUserAfterLogin() { -// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); -// when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenReturn(SyncTestUtils.createLoginResponse(Long.MAX_VALUE)); -// -// SyncUser user = SyncUser.logIn(SyncCredentials.facebook("foo"), "http://bar.com/auth"); -// assertEquals(user, SyncUser.current()); -// } -// -// @Test -// public void toString_returnDescription() { -// SyncUser user = createTestUser("http://objectserver.realm.io/auth"); -// String str = user.toString(); -// assertTrue(str != null && !str.isEmpty()); -// } -// -// // Test that a login with an access token logs the user in directly without touching the network -// @Test -// public void login_withAccessToken() { -// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); -// when(authServer.loginUser(any(SyncCredentials.class), any(URL.class))).thenThrow(new AssertionError("Server contacted.")); -// RealmObjectServer originalServer = SyncManager.getAuthServer(); -// SyncManager.setAuthServerImpl(authServer); -// try { -// SyncCredentials credentials = SyncCredentials.accessToken("foo", "bar"); -// SyncUser user = SyncUser.logIn(credentials, "http://ros.realm.io/auth"); -// assertTrue(user.isValid()); -// } finally { -// SyncManager.setAuthServerImpl(originalServer); -// } -// } -// -// // Checks that `/auth` is correctly added to any URL without a path -// @Test -// public void login_appendAuthSegment() { -// RealmObjectServer authServer = Mockito.mock(RealmObjectServer.class); -// RealmObjectServer originalServer = SyncManager.getAuthServer(); -// SyncManager.setAuthServerImpl(authServer); -// String[][] urls = { -// {"http://ros.realm.io", "http://ros.realm.io/auth"}, -// {"http://ros.realm.io:8080", "http://ros.realm.io:8080/auth"}, -// {"http://ros.realm.io/", "http://ros.realm.io/"}, -// {"http://ros.realm.io/?foo=bar", "http://ros.realm.io/?foo=bar"}, -// {"http://ros.realm.io/auth", "http://ros.realm.io/auth"}, -// {"http://ros.realm.io/auth/", "http://ros.realm.io/auth/"}, -// {"http://ros.realm.io/custom-path/", "http://ros.realm.io/custom-path/"} -// }; -// -// try { -// for (String[] url : urls) { -// RealmLog.error(url[0]); -// String input = url[0]; -// String normalizedInput = url[1]; -// SyncCredentials credentials = SyncCredentials.accessToken("token", UUID.randomUUID().toString()); -// SyncUser user = SyncUser.logIn(credentials, input); -// assertEquals(normalizedInput, user.getAuthenticationUrl().toString()); -// user.logOut(); -// } -// } finally { -// SyncManager.setAuthServerImpl(originalServer); -// } -// } -// -// @Test -// public void changePassword_nullThrows() { -// SyncUser user = createTestUser(); -// -// thrown.expect(IllegalArgumentException.class); -// //noinspection ConstantConditions -// user.changePassword(null); -// } -// -// @Test -// public void changePassword_admin_nullThrows() { -// SyncUser user = createTestUser(); -// -// thrown.expect(IllegalArgumentException.class); -// //noinspection ConstantConditions -// user.changePassword(null, "new-password"); -// } -// -// @Test -// public void changePasswordAsync_nonLooperThreadThrows() { -// SyncUser user = createTestUser(); -// -// thrown.expect(IllegalStateException.class); -// user.changePasswordAsync("password", new SyncUser.Callback() { -// @Override -// public void onSuccess(SyncUser user) { -// fail(); -// } -// -// @Override -// public void onError(ObjectServerError error) { -// fail(); -// } -// }); -// } -// -// @Test -// public void changePassword_admin_Async_nonLooperThreadThrows() { -// SyncUser user = createTestUser(); -// -// thrown.expect(IllegalStateException.class); -// user.changePasswordAsync("user-id", "new", new SyncUser.Callback() { -// @Override -// public void onSuccess(SyncUser user) { -// fail(); -// } -// -// @Override -// public void onError(ObjectServerError error) { -// fail(); -// } -// }); -// } -// -// @Test -// @RunTestInLooperThread -// public void changePasswordAsync_nullCallbackThrows() { -// SyncUser user = createTestUser(); -// -// thrown.expect(IllegalArgumentException.class); -// //noinspection ConstantConditions -// user.changePasswordAsync("new-password", null); -// } -// -// @Test -// @RunTestInLooperThread -// public void changePassword_admin_Async_nullCallbackThrows() { -// SyncUser user = createTestUser(); -// -// thrown.expect(IllegalArgumentException.class); -// //noinspection ConstantConditions -// user.changePasswordAsync("user-id", "new-password", null); -// } -// -// @Test -// @RunTestInLooperThread -// public void changePassword_noneAdminThrows() { -// SyncUser user = createTestUser(); -// -// thrown.expect(IllegalStateException.class); -// user.changePassword("user-id", "new-password"); -// } -// -// @Test -// public void allSessions() { -// String url1 = "realm://objectserver.realm.io/default"; -// String url2 = "realm://objectserver.realm.io/~/default"; -// -// SyncUser user = createTestUser(); -// assertEquals(0, user.allSessions().size()); -// -// SyncConfiguration configuration1 = user.createConfiguration(url1).modules(new AllTypesModelModule()).build(); -// Realm realm1 = Realm.getInstance(configuration1); -// List allSessions = user.allSessions(); -// assertEquals(1, allSessions.size()); -// Iterator iter = allSessions.iterator(); -// SyncSession session = iter.next(); -// assertEquals(user, session.getUser()); -// assertEquals(url1, session.getServerUrl().toString()); -// -// SyncConfiguration configuration2 = user.createConfiguration(url2).modules(new AllTypesModelModule()).build(); -// Realm realm2 = Realm.getInstance(configuration2); -// allSessions = user.allSessions(); -// assertEquals(2, allSessions.size()); -// iter = allSessions.iterator(); -// String individualUrl = url2.replace("~", user.getIdentity()); -// int foundCount = 0; -// while (iter.hasNext()) { -// session = iter.next(); -// assertEquals(user, session.getUser()); -// if (individualUrl.equals(session.getServerUrl().toString())) { -// foundCount++; -// } -// } -// assertEquals(1, foundCount); -// realm1.close(); -// -// allSessions = user.allSessions(); -// assertEquals(1, allSessions.size()); -// iter = allSessions.iterator(); -// session = iter.next(); -// assertEquals(user, session.getUser()); -// assertEquals(individualUrl, session.getServerUrl().toString()); -// -// realm2.close(); -// assertEquals(0, user.allSessions().size()); -// } -// -// // JSON format changed in 3.6.0 (removed unnecessary fields), this regression test -// // makes sure we can still deserialize a valid SyncUser from the old format. -// @Test -// public void fromJson_WorkWithRemovedObjectServerUser() { -// String oldSyncUserJSON = "{\"authUrl\":\"http:\\/\\/192.168.1.151:9080\\/auth\",\"userToken\":{\"token\":\"eyJpZGVudGl0eSI6IjY4OWQ5MGMxNDIyYTIwMmZkNTljNDYwM2M0ZTRmNmNjIiwiZXhwaXJlcyI6MTgxNjM1ODE4NCwiYXBwX2lkIjoiaW8ucmVhbG0ucmVhbG10YXNrcyIsImFjY2VzcyI6WyJyZWZyZXNoIl0sImlzX2FkbWluIjpmYWxzZSwic2FsdCI6MC4yMTEwMjQyNDgwOTEyMzg1NH0=:lEDa83o1zu8rkwdZVpTyunLHh1wmjxPPSGmZQNxdEM7xDmpbiU7V+8dgDWGevJNHMFluNDAOmrcAOI9TLfhI4rMDl70NI1K9rv\\/Aeq5uIOzq\\/Gf7JTeTUKY5Z7yRoppd8NArlNBKesLFxzdLRlfm1hflF9wH23xQXA19yUZ67JIlkhDPL5e3bau8O3Pr\\/St0unW3KzPOiZUk1l9KRrs2iMCCiXCfq4rf6rp7B2M7rBUMQm68GnB1Ot7l1CblxEWcREcbpyhBKTWIOFRGMwg2TW\\/zRR3cRNglx+ZC4FOeO0mfkX+nf+slyFODAnQkOzPZcGO8xc3I1emafX58Wl\\/Guw==\",\"token_data\":{\"identity\":\"689d90c1422a202fd59c4603c4e4f6cc\",\"path\":\"\",\"expires\":1816358184,\"access\":[\"unknown\"],\"is_admin\":false}},\"realms\":[]}"; -// SyncUser syncUser = SyncUser.fromJson(oldSyncUserJSON); -// -// // Note: we can't call isValid() and expect it to be true -// // since the user is not persisted in the UserStore -// // isValid() requires SyncManager.getUserStore().isActive(identity) -// // to return true as well. -// Token refreshToken = syncUser.getRefreshToken(); -// assertNotNull(refreshToken); -// // refresh token should expire in 10 years (July 23, 2027) -// Calendar calendar = Calendar.getInstance(); -// calendar.setTimeInMillis(refreshToken.expiresMs()); -// int day = calendar.get(Calendar.DAY_OF_MONTH); -// int month = calendar.get(Calendar.MONTH); -// int year = calendar.get(Calendar.YEAR); -// -// assertEquals(23, day); -// assertEquals(Calendar.JULY, month); -// assertEquals(2027, year); -// -// assertEquals("http://192.168.1.151:9080/auth", syncUser.getAuthenticationUrl().toString()); -// } -// -// @Test -// @Ignore("until https://github.com/realm/realm-java/issues/5097 is fixed") -// public void logoutUserShouldDeleteRealmAfterRestart() throws InterruptedException { -// SyncManager.reset(); -// BaseRealm.applicationContext = null; // Required for Realm.init() to work -// Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); -// -// SyncUser user = createTestUser(); -// SyncConfiguration syncConfiguration = user.createConfiguration("realm://127.0.0.1:9080/~/tests") -// .modules(new StringOnlyModule()) -// .build(); -// -// Realm realm = Realm.getInstance(syncConfiguration); -// realm.executeTransaction(new Realm.Transaction() { -// @Override -// public void execute(Realm realm) { -// realm.createObject(StringOnly.class).setChars("1"); -// } -// }); -// user.logOut(); -// realm.close(); -// -// final File realmPath = new File (syncConfiguration.getPath()); -// assertTrue(realmPath.exists()); -// -// // simulate an app restart -// SyncManager.reset(); -// BaseRealm.applicationContext = null; -// Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); -// -// //now the file should be deleted -// assertFalse(realmPath.exists()); -// } -//} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt index 3dca9415d9..670e960733 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt @@ -280,4 +280,32 @@ class RealmUserTests { val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertTrue(user.deviceId.isNotEmpty() && user.deviceId.length == 24) // Server returns a UUID } + @Test + fun equals() { + // TODO Could be that we could use a fake user + val user: RealmUser = app.registerUserAndLogin("user1@example.com", "123456") + assertEquals(user, user) + assertNotEquals(user, app) + user.logOut() + + val sameUserNewLogin = app.login(RealmCredentials.emailPassword(user.email!!, "123456")) + // Verify that it is not same object but uses underlying OSSyncUser equality on identity + assertFalse(user === sameUserNewLogin) + assertEquals(user, sameUserNewLogin) + + val differentUser: RealmUser = app.registerUserAndLogin("user2@example.com", "123456") + assertNotEquals(user, differentUser) + } + + @Test + fun hashCode_user() { + val user: RealmUser = app.registerUserAndLogin("user1@example.com", "123456") + user.logOut() + + val sameUserNewLogin = app.login(RealmCredentials.emailPassword(user.email!!, "123456")) + // Verify that two equal users also returns same hashCode + assertFalse(user === sameUserNewLogin) + assertEquals(user.hashCode(), sameUserNewLogin.hashCode()) + } + } From 409c5eb580a322e9b46f815d8c52a5a121f48bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 25 May 2020 14:15:17 +0200 Subject: [PATCH 1534/2110] Remove unused SSL configuration options and tests (#6870) --- .../res/raw/android_test_certificate | Bin 1484 -> 0 bytes .../res/xml/network_security_config.xml | 1 - ...rustManagerCertificateValidationTests.java | 386 ------------------ .../io/realm/internal/ObjectServerFacade.java | 2 +- .../java/io/realm/internal/OsRealmConfig.java | 32 +- .../objectServer/java/io/realm/RealmSync.java | 111 ----- .../java/io/realm/SyncConfiguration.java | 115 ------ .../internal/SyncObjectServerFacade.java | 49 +-- .../java/io/realm/SSLConfigurationTests.java | 329 --------------- .../keys/127_0_0_1-chain.crt.pem | 229 ----------- .../keys/127_0_0_1-server.key.pem | 27 -- .../keys/HowToGenerateKey.txt | 18 - tools/sync_test_server/keys/private.pem | 27 -- tools/sync_test_server/keys/public.pem | 9 - tools/sync_test_server/keys/test_token.json | 11 - 15 files changed, 30 insertions(+), 1316 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/res/raw/android_test_certificate delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java delete mode 100644 tools/sync_test_server/keys/127_0_0_1-chain.crt.pem delete mode 100644 tools/sync_test_server/keys/127_0_0_1-server.key.pem delete mode 100644 tools/sync_test_server/keys/HowToGenerateKey.txt delete mode 100644 tools/sync_test_server/keys/private.pem delete mode 100644 tools/sync_test_server/keys/public.pem delete mode 100644 tools/sync_test_server/keys/test_token.json diff --git a/realm/realm-library/src/androidTest/res/raw/android_test_certificate b/realm/realm-library/src/androidTest/res/raw/android_test_certificate deleted file mode 100644 index 53a5f087efe810f3e3e44318e494d2038f45aaa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1484 zcmXqLVm)Ed#JphvGZP~d6CkJHZIOdYMqlm>7+19 ziZNy88;TkTqlmEL#wKYXC(dhVW?*SxZeVF*WMmvA&TE9s9VlltF)1Mj5F;xCa}yIkgFzD$7gG}x zBf}l%O_Mh6`+v*%^Ny?DPV-pn{q}R}GZ{;%xcnEYGo5pGb7BS8hUSK*H;ZD@KGa^h z|8vg1t$xq46C$6#~9&5iRpW^n%(j*bZ8ZvD97-#+_Yui^sqU&*%0A8OYs5Mi|~ z$a#Exd1JPnVEXa78?*OKGy8T#w)u6(-C64V_tWLh8ZazONwW<=TyozQuF<&Y@Ch+?*gLSs>p0EF!r#MI~obJ{W7roBw=;V^6 z^W`q32yeL7oZy%FU5u6crR$rWK@~#53D-NmMwBW%S?%@s^v2kxa36bjmx2jw%L_zo zvYUTuunRSQD_~O54Ju%qCR47No)gz_Bk=E2=h%d`I`LCimFRxzb2fOH{`z<-m$&r> z`*p3i4AiuCCE5)Jr(3160M({&};l7x2G5-R3OI zeH;Bl$21QVNN$dr_m97}d1~|0+l)7kOw50$ntirUZ&PN6{LAq7K1U?ZUUKp~D7m-y zq~G(+tiSS(8gU)|u=mYnmn#h%YPv$FrI*a*JjKoQI%-=;;F<}^FMSR*X)O0ox-a1`(>=NBvr#9P-NC(Gu9`pUC43f^KYX<1b%0@Z`PB-csVAAP zCo&7??fZAP*;L!-j%#DSSiRcwY%BQW%HoeWob4%U&_zCM>R~)TY#k?)#7U*D|r+9ScVXH@r?*06F z?A$D|eG-l;BJbSGKHc6p;}hRsiFM0Ad<+e%n)RtAp!^fZ@h|pYrJ1LDIzC>gefZti ZJ$vSATUZK|L`zK4`gOqD?ZAJxFaV;QXXpR` diff --git a/realm/realm-library/src/androidTest/res/xml/network_security_config.xml b/realm/realm-library/src/androidTest/res/xml/network_security_config.xml index 9f14d34442..6407747c60 100644 --- a/realm/realm-library/src/androidTest/res/xml/network_security_config.xml +++ b/realm/realm-library/src/androidTest/res/xml/network_security_config.xml @@ -2,7 +2,6 @@ - diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java deleted file mode 100644 index 162efffa2b..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/TrustManagerCertificateValidationTests.java +++ /dev/null @@ -1,386 +0,0 @@ -package io.realm; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -@RunWith(AndroidJUnit4.class) -public class TrustManagerCertificateValidationTests { - - @BeforeClass - public static void setUp() { - // mainly to setup logging otherwise - // java.lang.UnsatisfiedLinkError: No implementation found for void io.realm.log.RealmLog.nativeSetLogLevel(int) (tried Java_io_realm_log_RealmLog_nativeSetLogLevel and Java_io_realm_log_RealmLog_nativeSetLogLevel__I) - // will be thrown - Realm.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - } - - // IMPORTANT: Following test assume the root certificate is installed on the test device - // certificate is located in /tools/sync_test_server/keys/android_test_certificate.crt - // adb push /tools/sync_test_server/keys/android_test_certificate.crt /sdcard/ - // then import the certificate from the device (Settings/Security/Install from storage) - @Test - @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") - public void sslVerifyCallback_certificateChainWithRootCAInstalledShouldValidate() { - // simulating the following certificate chain - // --- - // Certificate chain - // 0 s:/DC=127.0.0.1/O=Realm/OU=Realm/CN=127.0.0.1 - // i:/DC=io/DC=realm/O=Realm/OU=Realm Test Signing CA/CN=Realm Test Signing CA - // 1 s:/DC=io/DC=realm/O=Realm/OU=Realm Test Signing CA/CN=Realm Test Signing CA - // i:/DC=io/DC=realm/O=Realm/OU=Realm Test Root CA/CN=Realm Test Root CA - // --- - - // s:/DC=127.0.0.1/O=Realm/OU=Realm/CN=127.0.0.1 - String pem_depth0 = "-----BEGIN CERTIFICATE-----\n" + - "MIIE1DCCArygAwIBAgIBBzANBgkqhkiG9w0BAQUFADB7MRIwEAYKCZImiZPyLGQB\n" + - "GRYCaW8xFTATBgoJkiaJk/IsZAEZFgVyZWFsbTEOMAwGA1UECgwFUmVhbG0xHjAc\n" + - "BgNVBAsMFVJlYWxtIFRlc3QgU2lnbmluZyBDQTEeMBwGA1UEAwwVUmVhbG0gVGVz\n" + - "dCBTaWduaW5nIENBMB4XDTE3MDUxNzIzMjg0OFoXDTE5MDUxNzIzMjg0OFowTzEZ\n" + - "MBcGCgmSJomT8ixkARkWCTEyNy4wLjAuMTEOMAwGA1UECgwFUmVhbG0xDjAMBgNV\n" + - "BAsMBVJlYWxtMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUA\n" + - "A4IBDwAwggEKAoIBAQC3jJl7a1spgJyZt/64HgZsTVi9OLbME2r//fYmoHHSipTq\n" + - "Br7huFsDXpaOYRkPgF+4UUOXADhnRw4JuKuA0ZyBuIHbC7TF3no89ZzLvysS/rGd\n" + - "TqBKq67EERlUxRftWMNy8OVG3CFBTGMdMYXzuvataT7Yhp3EVjtSR10k3UCv+foD\n" + - "TE4tW9I03PCkGRMU9mx8HEe9fXmiCWGtP41OWcWupys5AOk0aGxv2GCiqSQzHJ+A\n" + - "tMaOujeYcT3dgmbY4MKBzEvRXVgmz4UKrP0IpUBQ//lz6CcYe3B1cyojx9cVvsrO\n" + - "V8nuu2202P3HIkcomwBeS6+CY8PXanROYBeUavuDAgMBAAGjgY4wgYswDgYDVR0P\n" + - "AQH/BAQDAgWgMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF\n" + - "BwMCMB0GA1UdDgQWBBTGvfRJ9S52UkTx4s4ubPlZsVYUrTAfBgNVHSMEGDAWgBQn\n" + - "eeHa8RXQ6eWGMIfnH1/PJzpwtDAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEB\n" + - "BQUAA4ICAQCbP3T0aXJrW3WItxBf4HOygr7ccRuj1qRurqZfUXhcgGQgISATFgjQ\n" + - "rhX2UiTZI1wk7WI7DuZfAEu/oZQ0KvsqRl9U5jt/voFb3+h4ph7O4oe5i+TYBB8Y\n" + - "xCmAeiGpVsUp7k4oM/qNkkaiMTHF+TEZ7R32x3WCZbYarbw0SvMYBaCj1JpQ8u+7\n" + - "xC+JEJVoF2qFds6IjBnP16pww9BZm5rA0KjQ08318I5eGauhrlTcB6xtbtjw7mVH\n" + - "3ikedhsdDmL13R32bq0nLo2+xKhBC7FEIj0ps1d0PjtBKBmNSO1lBVuOF6erRSTZ\n" + - "lQDkBOds2GtrKoleH/u08hwgVer1QJlYot7Dg+UBcPhT6Y2Vugsg0JnmtDEFVQCc\n" + - "9/OWfHRbfcdqruyQ+A/y8FjsgAx5BLDzac3lQfL1/ES62U8/Mv5p824fMpRieBd2\n" + - "3NUMGaaLl3DpGTmo+rEAphhvSy04Lx2WC4eYhsEsdUQ8DuHr9MROAsef98wwinIj\n" + - "v0R8fD/3fLGx16pL5B7dyv1ajS6q/0mvpWNviDEmfbOk401NRdZEexKobga7gcCA\n" + - "pF+VO9SlSgEdAA57XSApl9DWiHPxicEBVIWbnO9Bbfm2g8xlrDTKv4j8NE9/YjDi\n" + - "2QLrx1iGkG/kfl8gRfLEoH6tklqFjwiQPehlvlR54mI8XY5XNioXuw==\n" + - "-----END CERTIFICATE-----"; - - // s:/DC=io/DC=realm/O=Realm/OU=Realm Test Signing CA/CN=Realm Test Signing CA - String pem_depth1 = "-----BEGIN CERTIFICATE-----\n" + - "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\n" + - "pf7lM/4=\n" + - "-----END CERTIFICATE-----\n"; - - String serverAddress = "127.0.0.1"; - - assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth1, 1)); - assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth0, 0)); - } - - @Test - public void sslVerifyCallback_shouldFailOnExpiredCert() { - // simulating the following certificate chain (one of the - // --- - // Certificate chain - // 0 s:/CN=*.ie1.realmlab.net - // i:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon - // 1 s:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon - // i:/C=US/O=Amazon/CN=Amazon Root CA 1 - // 2 s:/C=US/O=Amazon/CN=Amazon Root CA 1 - // i:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 - // 3 s:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 - // i:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority - // --- - - // ie1.realmlab.net (!!!! EXPIRED on May 3, 2018) - String pem_depth0 = "-----BEGIN CERTIFICATE-----\n" + - "MIIEWDCCA0CgAwIBAgIQBE6+74j1z/Z88OEsSc3VIzANBgkqhkiG9w0BAQsFADBG\n" + - "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRUwEwYDVQQLEwxTZXJ2ZXIg\n" + - "Q0EgMUIxDzANBgNVBAMTBkFtYXpvbjAeFw0xNzA0MDMwMDAwMDBaFw0xODA1MDMx\n" + - "MjAwMDBaMB0xGzAZBgNVBAMMEiouaWUxLnJlYWxtbGFiLm5ldDCCASIwDQYJKoZI\n" + - "hvcNAQEBBQADggEPADCCAQoCggEBAKfV/38WJ47qvr4Onopu+XKYlTyTsvouX2VQ\n" + - "jRopM0gdXehp9BfwnFme8KUVZLSYh0vdmY7Wm5A7oxcL4ZuUpDSs9+xuERNg1YMD\n" + - "gI46ehj08+KUSfuqsVuw3gpNM6VPtpKY2I4//fJFmJKTWXA/fl35By0Xbuv4I180\n" + - "FFWu7CV0N4b/QQsjT0+CVvAjHRMMTpw0qtcZGQ4lWNNiqcqUql+Eklm/90S+lyBD\n" + - "q8YQUwcxhMgxKt6M5zwJpWuIbjov9kygDzlw/YU8P5wqvgocfnnXaKw+rr7EdiTS\n" + - "U2ZT99JO0F0CPzPZnphNrRtjkJ4Chtp0FVRqAdthpGH4i1VIKP0CAwEAAaOCAWkw\n" + - "ggFlMB8GA1UdIwQYMBaAFFmkZgZSoHuVkjyjlAcnlnRb+T3QMB0GA1UdDgQWBBRP\n" + - "5MQbQpMCFJgjiFgEtZUIiKdNeDAdBgNVHREEFjAUghIqLmllMS5yZWFsbWxhYi5u\n" + - "ZXQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD\n" + - "AjA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vY3JsLnNjYTFiLmFtYXpvbnRydXN0\n" + - "LmNvbS9zY2ExYi5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwdQYIKwYBBQUHAQEE\n" + - "aTBnMC0GCCsGAQUFBzABhiFodHRwOi8vb2NzcC5zY2ExYi5hbWF6b250cnVzdC5j\n" + - "b20wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuc2NhMWIuYW1hem9udHJ1c3QuY29t\n" + - "L3NjYTFiLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQAObbVL\n" + - "zDqqFO4iDjR4VRTYQbb3gSDxySqFqMm4iBJBmqgNRDsNDb75EmlbB0udbZ6+LHDK\n" + - "pmPh81ocdJECHZctidDh1zCkVf3uOYyPJqxNpt0ZCurGMTi4i5kaIbAwR50lZU2V\n" + - "eSkR5rYFoBIVcUNbXzzOMLTcJrRqbVYz7z9zCN71l12dKNMXdu9tLcec+WCGi0R+\n" + - "MNBOQ/XVlAzymsmQM6nWb0DEQ86ya9AAAMVQBVgyeEPZNPidxc82kU8pML9mO0Yl\n" + - "MtbgZWXH1kTppsi+/WbOwy+kalpiMJ7TXIvHmQat81FWiJNTnKwfVEsz79Op8EAW\n" + - "p9RkpzfSQpZQ30/u\n" + - "-----END CERTIFICATE-----\n"; - - // OU=Server CA 1B/CN=Amazon - String pem_depth1 = "-----BEGIN CERTIFICATE-----\n" + - "MIIESTCCAzGgAwIBAgITBn+UV4WH6Kx33rJTMlu8mYtWDTANBgkqhkiG9w0BAQsF\n" + - "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" + - "b24gUm9vdCBDQSAxMB4XDTE1MTAyMjAwMDAwMFoXDTI1MTAxOTAwMDAwMFowRjEL\n" + - "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEVMBMGA1UECxMMU2VydmVyIENB\n" + - "IDFCMQ8wDQYDVQQDEwZBbWF6b24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" + - "AoIBAQDCThZn3c68asg3Wuw6MLAd5tES6BIoSMzoKcG5blPVo+sDORrMd4f2AbnZ\n" + - "cMzPa43j4wNxhplty6aUKk4T1qe9BOwKFjwK6zmxxLVYo7bHViXsPlJ6qOMpFge5\n" + - "blDP+18x+B26A0piiQOuPkfyDyeR4xQghfj66Yo19V+emU3nazfvpFA+ROz6WoVm\n" + - "B5x+F2pV8xeKNR7u6azDdU5YVX1TawprmxRC1+WsAYmz6qP+z8ArDITC2FMVy2fw\n" + - "0IjKOtEXc/VfmtTFch5+AfGYMGMqqvJ6LcXiAhqG5TI+Dr0RtM88k+8XUBCeQ8IG\n" + - "KuANaL7TiItKZYxK1MMuTJtV9IblAgMBAAGjggE7MIIBNzASBgNVHRMBAf8ECDAG\n" + - "AQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUWaRmBlKge5WSPKOUByeW\n" + - "dFv5PdAwHwYDVR0jBBgwFoAUhBjMhTTsvAyUlC4IWZzHshBOCggwewYIKwYBBQUH\n" + - "AQEEbzBtMC8GCCsGAQUFBzABhiNodHRwOi8vb2NzcC5yb290Y2ExLmFtYXpvbnRy\n" + - "dXN0LmNvbTA6BggrBgEFBQcwAoYuaHR0cDovL2NydC5yb290Y2ExLmFtYXpvbnRy\n" + - "dXN0LmNvbS9yb290Y2ExLmNlcjA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3Js\n" + - "LnJvb3RjYTEuYW1hem9udHJ1c3QuY29tL3Jvb3RjYTEuY3JsMBMGA1UdIAQMMAow\n" + - "CAYGZ4EMAQIBMA0GCSqGSIb3DQEBCwUAA4IBAQCFkr41u3nPo4FCHOTjY3NTOVI1\n" + - "59Gt/a6ZiqyJEi+752+a1U5y6iAwYfmXss2lJwJFqMp2PphKg5625kXg8kP2CN5t\n" + - "6G7bMQcT8C8xDZNtYTd7WPD8UZiRKAJPBXa30/AbwuZe0GaFEQ8ugcYQgSn+IGBI\n" + - "8/LwhBNTZTUVEWuCUUBVV18YtbAiPq3yXqMB48Oz+ctBWuZSkbvkNodPLamkB2g1\n" + - "upRyzQ7qDn1X8nn8N8V7YJ6y68AtkHcNSRAnpTitxBKjtKPISLMVCx7i4hncxHZS\n" + - "yLyKQXhw2W2Xs0qLeC1etA+jTGDK4UfLeC0SF7FSi8o5LL21L8IzApar2pR/\n" + - "-----END CERTIFICATE-----\n"; - // Amazon Root CA 1 - String pem_depth2 = "-----BEGIN CERTIFICATE-----\n" + - "MIIEkjCCA3qgAwIBAgITBn+USionzfP6wq4rAfkI7rnExjANBgkqhkiG9w0BAQsF\n" + - "ADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNj\n" + - "b3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4x\n" + - "OzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1\n" + - "dGhvcml0eSAtIEcyMB4XDTE1MDUyNTEyMDAwMFoXDTM3MTIzMTAxMDAwMFowOTEL\n" + - "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" + - "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" + - "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" + - "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" + - "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" + - "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" + - "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" + - "jgSubJrIqg0CAwEAAaOCATEwggEtMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\n" + - "BAQDAgGGMB0GA1UdDgQWBBSEGMyFNOy8DJSULghZnMeyEE4KCDAfBgNVHSMEGDAW\n" + - "gBScXwDfqgHXMCs4iKK4bUqc8hGRgzB4BggrBgEFBQcBAQRsMGowLgYIKwYBBQUH\n" + - "MAGGImh0dHA6Ly9vY3NwLnJvb3RnMi5hbWF6b250cnVzdC5jb20wOAYIKwYBBQUH\n" + - "MAKGLGh0dHA6Ly9jcnQucm9vdGcyLmFtYXpvbnRydXN0LmNvbS9yb290ZzIuY2Vy\n" + - "MD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwucm9vdGcyLmFtYXpvbnRydXN0\n" + - "LmNvbS9yb290ZzIuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQsF\n" + - "AAOCAQEAYjdCXLwQtT6LLOkMm2xF4gcAevnFWAu5CIw+7bMlPLVvUOTNNWqnkzSW\n" + - "MiGpSESrnO09tKpzbeR/FoCJbM8oAxiDR3mjEH4wW6w7sGDgd9QIpuEdfF7Au/ma\n" + - "eyKdpwAJfqxGF4PcnCZXmTA5YpaP7dreqsXMGz7KQ2hsVxa81Q4gLv7/wmpdLqBK\n" + - "bRRYh5TmOTFffHPLkIhqhBGWJ6bt2YFGpn6jcgAKUj6DiAdjd4lpFw85hdKrCEVN\n" + - "0FE6/V1dN2RMfjCyVSRCnTawXZwXgWHxyvkQAiSr6w10kY17RSlQOYiypok1JR4U\n" + - "akcjMS9cmvqtmg5iUaQqqcT5NJ0hGA==\n" + - "-----END CERTIFICATE-----\n"; - - // O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 - String pem_depth3 = "-----BEGIN CERTIFICATE-----\n" + - "MIIEdTCCA12gAwIBAgIJAKcOSkw0grd/MA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNV\n" + - "BAYTAlVTMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTIw\n" + - "MAYDVQQLEylTdGFyZmllbGQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0\n" + - "eTAeFw0wOTA5MDIwMDAwMDBaFw0zNDA2MjgxNzM5MTZaMIGYMQswCQYDVQQGEwJV\n" + - "UzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UE\n" + - "ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZp\n" + - "ZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEi\n" + - "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVDDrEKvlO4vW+GZdfjohTsR8/\n" + - "y8+fIBNtKTrID30892t2OGPZNmCom15cAICyL1l/9of5JUOG52kbUpqQ4XHj2C0N\n" + - "Tm/2yEnZtvMaVq4rtnQU68/7JuMauh2WLmo7WJSJR1b/JaCTcFOD2oR0FMNnngRo\n" + - "Ot+OQFodSk7PQ5E751bWAHDLUu57fa4657wx+UX2wmDPE1kCK4DMNEffud6QZW0C\n" + - "zyyRpqbn3oUYSXxmTqM6bam17jQuug0DuDPfR+uxa40l2ZvOgdFFRjKWcIfeAg5J\n" + - "Q4W2bHO7ZOphQazJ1FTfhy/HIrImzJ9ZVGif/L4qL8RVHHVAYBeFAlU5i38FAgMB\n" + - "AAGjgfAwge0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0O\n" + - "BBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtV\n" + - "rNzXEMIOqYjnME8GCCsGAQUFBwEBBEMwQTAcBggrBgEFBQcwAYYQaHR0cDovL28u\n" + - "c3MyLnVzLzAhBggrBgEFBQcwAoYVaHR0cDovL3guc3MyLnVzL3guY2VyMCYGA1Ud\n" + - "HwQfMB0wG6AZoBeGFWh0dHA6Ly9zLnNzMi51cy9yLmNybDARBgNVHSAECjAIMAYG\n" + - "BFUdIAAwDQYJKoZIhvcNAQELBQADggEBACMd44pXyn3pF3lM8R5V/cxTbj5HD9/G\n" + - "VfKyBDbtgB9TxF00KGu+x1X8Z+rLP3+QsjPNG1gQggL4+C/1E2DUBc7xgQjB3ad1\n" + - "l08YuW3e95ORCLp+QCztweq7dp4zBncdDQh/U90bZKuCJ/Fp1U1ervShw3WnWEQt\n" + - "8jxwmKy6abaVd38PMV4s/KCHOkdp8Hlf9BRUpJVeEXgSYCfOn8J3/yNTd126/+pZ\n" + - "59vPr5KW7ySaNRB6nJHGDn2Z9j8Z3/VyVOEVqQdZe4O/Ui5GjLIAZHYcSNPYeehu\n" + - "VsyuLAOQ1xk4meTKCRlb/weWsKh/NEnfVqn3sF/tM+2MR7cwA130A4w=\n" + - "-----END CERTIFICATE-----\n"; - - String serverAddress = "nabil-test.ie1.realmlab.net"; - - assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth3, 3)); - assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth2, 2)); - assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth1, 1)); - assertFalse(RealmSync.sslVerifyCallback(serverAddress, pem_depth0, 0)); - } - - @Ignore("FIXME: Certificate expired") - @Test - public void sslVerifyCallback_shouldVerifyHostname() { - // simulating the following certificate chain - - // 0 s:/CN=us1a.cloud.realm.io - // i:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon - String pem_depth0 = "-----BEGIN CERTIFICATE-----\n" + - "MIIEfjCCA2agAwIBAgIQAuZyKHDOzYP160MtNtRBEjANBgkqhkiG9w0BAQsFADBG\n" + - "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRUwEwYDVQQLEwxTZXJ2ZXIg\n" + - "Q0EgMUIxDzANBgNVBAMTBkFtYXpvbjAeFw0xODAyMTkwMDAwMDBaFw0xOTAzMTkx\n" + - "MjAwMDBaMB4xHDAaBgNVBAMTE3VzMWEuY2xvdWQucmVhbG0uaW8wggEiMA0GCSqG\n" + - "SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6XER+3bFiK4TCc5lQv/O3xTc9oC/bcPVr\n" + - "zs52mzcGW/wNH6dxW3i3T3gz3Pit8TDkDf0tzoZNdfr7PYs+BPtinM3ZbKSSnF6G\n" + - "5F8HNpe/1p1blko22wJDa9OyZD4tZ3f6hBlUU+8tHFC2B7BGEzuVKf3Aacap0wdh\n" + - "KsAAaF/mbtLQaelRFtHcIOz2B28e7Fub/iwJGCW79Keq+lDRLG+xayEsBqO3+FJ3\n" + - "h4FxbhsKW/O5tb/5B4dZfgJopWZfcmTUZ89ZX2IYaukfwkrV+/09ZAr87jMi9E7+\n" + - "zU37qHtrWVWQV48BxdWiMmmvJb0ytYM0rxal2YuXi6NOBTP0sbxVAgMBAAGjggGO\n" + - "MIIBijAfBgNVHSMEGDAWgBRZpGYGUqB7lZI8o5QHJ5Z0W/k90DAdBgNVHQ4EFgQU\n" + - "ZNEE3UPcZg2ZOJd4eMZryxUTvKswNQYDVR0RBC4wLIITdXMxYS5jbG91ZC5yZWFs\n" + - "bS5pb4IVKi51czFhLmNsb3VkLnJlYWxtLmlvMA4GA1UdDwEB/wQEAwIFoDAdBgNV\n" + - "HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwOwYDVR0fBDQwMjAwoC6gLIYqaHR0\n" + - "cDovL2NybC5zY2ExYi5hbWF6b250cnVzdC5jb20vc2NhMWIuY3JsMCAGA1UdIAQZ\n" + - "MBcwCwYJYIZIAYb9bAECMAgGBmeBDAECATB1BggrBgEFBQcBAQRpMGcwLQYIKwYB\n" + - "BQUHMAGGIWh0dHA6Ly9vY3NwLnNjYTFiLmFtYXpvbnRydXN0LmNvbTA2BggrBgEF\n" + - "BQcwAoYqaHR0cDovL2NydC5zY2ExYi5hbWF6b250cnVzdC5jb20vc2NhMWIuY3J0\n" + - "MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAAserhwXWohdFjImCcCh\n" + - "0XGW7s47vygasV4kE7vg59dz5RQrVuu+U0HFKTuPw6d4xSaQrUq1wo76RJtZalpG\n" + - "ek9vOvS0GWxjSsts2D0oWZXq772bhlXRfj21NsgwzfWMXIrUaV32l5qDhin1wx7x\n" + - "oZL7mNQ75qFB56jv5zzsX2woFv1GN0a03nFgy9Jk6aWCM5Q3oujrxJJWsgXIMloj\n" + - "uqg+I4MfhTEC1ZnGOEoO4Rq3i1rSLa59mv4lhcO/+yrEENKESgx8/8DnIjQoEuRp\n" + - "QtbxCVxPYfnjBuRuvyTfSo1GMK6SuhvkqVbDhBbRDDCh2T8Nmea3BcFi1kcpImOr\n" + - "MI4=\n" + - "-----END CERTIFICATE-----"; - - // 1 s:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon - // i:/C=US/O=Amazon/CN=Amazon Root CA 1 - String pem_depth1 = "-----BEGIN CERTIFICATE-----\n" + - "MIIESTCCAzGgAwIBAgITBn+UV4WH6Kx33rJTMlu8mYtWDTANBgkqhkiG9w0BAQsF\n" + - "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" + - "b24gUm9vdCBDQSAxMB4XDTE1MTAyMjAwMDAwMFoXDTI1MTAxOTAwMDAwMFowRjEL\n" + - "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEVMBMGA1UECxMMU2VydmVyIENB\n" + - "IDFCMQ8wDQYDVQQDEwZBbWF6b24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" + - "AoIBAQDCThZn3c68asg3Wuw6MLAd5tES6BIoSMzoKcG5blPVo+sDORrMd4f2AbnZ\n" + - "cMzPa43j4wNxhplty6aUKk4T1qe9BOwKFjwK6zmxxLVYo7bHViXsPlJ6qOMpFge5\n" + - "blDP+18x+B26A0piiQOuPkfyDyeR4xQghfj66Yo19V+emU3nazfvpFA+ROz6WoVm\n" + - "B5x+F2pV8xeKNR7u6azDdU5YVX1TawprmxRC1+WsAYmz6qP+z8ArDITC2FMVy2fw\n" + - "0IjKOtEXc/VfmtTFch5+AfGYMGMqqvJ6LcXiAhqG5TI+Dr0RtM88k+8XUBCeQ8IG\n" + - "KuANaL7TiItKZYxK1MMuTJtV9IblAgMBAAGjggE7MIIBNzASBgNVHRMBAf8ECDAG\n" + - "AQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUWaRmBlKge5WSPKOUByeW\n" + - "dFv5PdAwHwYDVR0jBBgwFoAUhBjMhTTsvAyUlC4IWZzHshBOCggwewYIKwYBBQUH\n" + - "AQEEbzBtMC8GCCsGAQUFBzABhiNodHRwOi8vb2NzcC5yb290Y2ExLmFtYXpvbnRy\n" + - "dXN0LmNvbTA6BggrBgEFBQcwAoYuaHR0cDovL2NydC5yb290Y2ExLmFtYXpvbnRy\n" + - "dXN0LmNvbS9yb290Y2ExLmNlcjA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3Js\n" + - "LnJvb3RjYTEuYW1hem9udHJ1c3QuY29tL3Jvb3RjYTEuY3JsMBMGA1UdIAQMMAow\n" + - "CAYGZ4EMAQIBMA0GCSqGSIb3DQEBCwUAA4IBAQCFkr41u3nPo4FCHOTjY3NTOVI1\n" + - "59Gt/a6ZiqyJEi+752+a1U5y6iAwYfmXss2lJwJFqMp2PphKg5625kXg8kP2CN5t\n" + - "6G7bMQcT8C8xDZNtYTd7WPD8UZiRKAJPBXa30/AbwuZe0GaFEQ8ugcYQgSn+IGBI\n" + - "8/LwhBNTZTUVEWuCUUBVV18YtbAiPq3yXqMB48Oz+ctBWuZSkbvkNodPLamkB2g1\n" + - "upRyzQ7qDn1X8nn8N8V7YJ6y68AtkHcNSRAnpTitxBKjtKPISLMVCx7i4hncxHZS\n" + - "yLyKQXhw2W2Xs0qLeC1etA+jTGDK4UfLeC0SF7FSi8o5LL21L8IzApar2pR/\n" + - "-----END CERTIFICATE-----"; - - // 2 s:/C=US/O=Amazon/CN=Amazon Root CA 1 - // i:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 - String pem_depth2 = "-----BEGIN CERTIFICATE-----\n" + - "MIIEkjCCA3qgAwIBAgITBn+USionzfP6wq4rAfkI7rnExjANBgkqhkiG9w0BAQsF\n" + - "ADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNj\n" + - "b3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4x\n" + - "OzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1\n" + - "dGhvcml0eSAtIEcyMB4XDTE1MDUyNTEyMDAwMFoXDTM3MTIzMTAxMDAwMFowOTEL\n" + - "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" + - "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" + - "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" + - "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" + - "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" + - "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" + - "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" + - "jgSubJrIqg0CAwEAAaOCATEwggEtMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\n" + - "BAQDAgGGMB0GA1UdDgQWBBSEGMyFNOy8DJSULghZnMeyEE4KCDAfBgNVHSMEGDAW\n" + - "gBScXwDfqgHXMCs4iKK4bUqc8hGRgzB4BggrBgEFBQcBAQRsMGowLgYIKwYBBQUH\n" + - "MAGGImh0dHA6Ly9vY3NwLnJvb3RnMi5hbWF6b250cnVzdC5jb20wOAYIKwYBBQUH\n" + - "MAKGLGh0dHA6Ly9jcnQucm9vdGcyLmFtYXpvbnRydXN0LmNvbS9yb290ZzIuY2Vy\n" + - "MD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwucm9vdGcyLmFtYXpvbnRydXN0\n" + - "LmNvbS9yb290ZzIuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQsF\n" + - "AAOCAQEAYjdCXLwQtT6LLOkMm2xF4gcAevnFWAu5CIw+7bMlPLVvUOTNNWqnkzSW\n" + - "MiGpSESrnO09tKpzbeR/FoCJbM8oAxiDR3mjEH4wW6w7sGDgd9QIpuEdfF7Au/ma\n" + - "eyKdpwAJfqxGF4PcnCZXmTA5YpaP7dreqsXMGz7KQ2hsVxa81Q4gLv7/wmpdLqBK\n" + - "bRRYh5TmOTFffHPLkIhqhBGWJ6bt2YFGpn6jcgAKUj6DiAdjd4lpFw85hdKrCEVN\n" + - "0FE6/V1dN2RMfjCyVSRCnTawXZwXgWHxyvkQAiSr6w10kY17RSlQOYiypok1JR4U\n" + - "akcjMS9cmvqtmg5iUaQqqcT5NJ0hGA==\n" + - "-----END CERTIFICATE-----"; - - // 3 s:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 - // i:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority - String pem_depth3 = "-----BEGIN CERTIFICATE-----\n" + - "MIIEdTCCA12gAwIBAgIJAKcOSkw0grd/MA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNV\n" + - "BAYTAlVTMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTIw\n" + - "MAYDVQQLEylTdGFyZmllbGQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0\n" + - "eTAeFw0wOTA5MDIwMDAwMDBaFw0zNDA2MjgxNzM5MTZaMIGYMQswCQYDVQQGEwJV\n" + - "UzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UE\n" + - "ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZp\n" + - "ZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEi\n" + - "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVDDrEKvlO4vW+GZdfjohTsR8/\n" + - "y8+fIBNtKTrID30892t2OGPZNmCom15cAICyL1l/9of5JUOG52kbUpqQ4XHj2C0N\n" + - "Tm/2yEnZtvMaVq4rtnQU68/7JuMauh2WLmo7WJSJR1b/JaCTcFOD2oR0FMNnngRo\n" + - "Ot+OQFodSk7PQ5E751bWAHDLUu57fa4657wx+UX2wmDPE1kCK4DMNEffud6QZW0C\n" + - "zyyRpqbn3oUYSXxmTqM6bam17jQuug0DuDPfR+uxa40l2ZvOgdFFRjKWcIfeAg5J\n" + - "Q4W2bHO7ZOphQazJ1FTfhy/HIrImzJ9ZVGif/L4qL8RVHHVAYBeFAlU5i38FAgMB\n" + - "AAGjgfAwge0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0O\n" + - "BBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtV\n" + - "rNzXEMIOqYjnME8GCCsGAQUFBwEBBEMwQTAcBggrBgEFBQcwAYYQaHR0cDovL28u\n" + - "c3MyLnVzLzAhBggrBgEFBQcwAoYVaHR0cDovL3guc3MyLnVzL3guY2VyMCYGA1Ud\n" + - "HwQfMB0wG6AZoBeGFWh0dHA6Ly9zLnNzMi51cy9yLmNybDARBgNVHSAECjAIMAYG\n" + - "BFUdIAAwDQYJKoZIhvcNAQELBQADggEBACMd44pXyn3pF3lM8R5V/cxTbj5HD9/G\n" + - "VfKyBDbtgB9TxF00KGu+x1X8Z+rLP3+QsjPNG1gQggL4+C/1E2DUBc7xgQjB3ad1\n" + - "l08YuW3e95ORCLp+QCztweq7dp4zBncdDQh/U90bZKuCJ/Fp1U1ervShw3WnWEQt\n" + - "8jxwmKy6abaVd38PMV4s/KCHOkdp8Hlf9BRUpJVeEXgSYCfOn8J3/yNTd126/+pZ\n" + - "59vPr5KW7ySaNRB6nJHGDn2Z9j8Z3/VyVOEVqQdZe4O/Ui5GjLIAZHYcSNPYeehu\n" + - "VsyuLAOQ1xk4meTKCRlb/weWsKh/NEnfVqn3sF/tM+2MR7cwA130A4w=\n" + - "-----END CERTIFICATE-----"; - - String serverAddress = "foo.us1a.cloud.realm.io"; - - assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth3, 3)); - assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth2, 2)); - assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth1, 1)); - assertTrue(RealmSync.sslVerifyCallback(serverAddress, pem_depth0, 0)); - - // reaching depth0 will validate (or not) the entire chain, then removing the PEMs from memory - // make sure the hostname verify works - - String wrongServerAddress = "hax0r-us1a.cloud2.realm.io"; - assertTrue(RealmSync.sslVerifyCallback(wrongServerAddress, pem_depth3, 3)); - assertTrue(RealmSync.sslVerifyCallback(wrongServerAddress, pem_depth2, 2)); - assertTrue(RealmSync.sslVerifyCallback(wrongServerAddress, pem_depth1, 1)); - // the method fails because of the hostname verification - assertFalse(RealmSync.sslVerifyCallback(wrongServerAddress, pem_depth0, 0)); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index 6b986cb85e..d5b15f9126 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -30,7 +30,7 @@ */ public class ObjectServerFacade { - public final static int SYNC_CONFIG_OPTIONS = 15; + public final static int SYNC_CONFIG_OPTIONS = 13; private final static ObjectServerFacade nonSyncFacade = new ObjectServerFacade(); private static ObjectServerFacade syncFacade = null; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 590eb1f2fa..b0750fd4e7 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -206,26 +206,25 @@ private OsRealmConfig(final RealmConfiguration config, NativeContext.dummyContext.addReference(this); // Retrieve Sync settings first. We need syncRealmUrl to identify if this is a SyncConfig + int j = 0; Object[] syncConfigurationOptions = ObjectServerFacade.getSyncFacadeIfPossible().getSyncConfigurationOptions(realmConfiguration); - String syncUserIdentifier = (String) syncConfigurationOptions[0]; - String syncRealmUrl = (String) syncConfigurationOptions[1]; - String syncRealmAuthUrl = (String) syncConfigurationOptions[2]; - String syncRefreshToken = (String) syncConfigurationOptions[3]; - String syncAccessToken = (String) syncConfigurationOptions[4]; - String deviceId = (String) syncConfigurationOptions[5]; - boolean syncClientValidateSsl = (Boolean.TRUE.equals(syncConfigurationOptions[6])); - String syncSslTrustCertificatePath = (String) syncConfigurationOptions[7]; - Byte sessionStopPolicy = (Byte) syncConfigurationOptions[8]; - String urlPrefix = (String)(syncConfigurationOptions[9]); - String customAuthorizationHeaderName = (String)(syncConfigurationOptions[10]); - Byte clientResyncMode = (Byte) syncConfigurationOptions[12]; - String partitionValue = (String) syncConfigurationOptions[13]; - Object syncService = syncConfigurationOptions[14]; + String syncUserIdentifier = (String) syncConfigurationOptions[j++]; + String syncRealmUrl = (String) syncConfigurationOptions[j++]; + String syncRealmAuthUrl = (String) syncConfigurationOptions[j++]; + String syncRefreshToken = (String) syncConfigurationOptions[j++]; + String syncAccessToken = (String) syncConfigurationOptions[j++]; + String deviceId = (String) syncConfigurationOptions[j++]; + Byte sessionStopPolicy = (Byte) syncConfigurationOptions[j++]; + String urlPrefix = (String)(syncConfigurationOptions[j++]); + String customAuthorizationHeaderName = (String)(syncConfigurationOptions[j++]); + //noinspection unchecked + Map customHeadersMap = (Map) (syncConfigurationOptions[j++]); + Byte clientResyncMode = (Byte) syncConfigurationOptions[j++]; + String partitionValue = (String) syncConfigurationOptions[j++]; + Object syncService = syncConfigurationOptions[j++]; // Convert the headers into a String array to make it easier to send through JNI // [key1, value1, key2, value2, ...] - //noinspection unchecked - Map customHeadersMap = (Map) (syncConfigurationOptions[11]); String[] customHeaders = new String[customHeadersMap != null ? customHeadersMap.size() * 2 : 0]; if (customHeadersMap != null) { int i = 0; @@ -300,7 +299,6 @@ private OsRealmConfig(final RealmConfiguration config, } catch (URISyntaxException e) { RealmLog.error(e, "Cannot create a URI from the Realm URL address"); } - nativeSetSyncConfigSslSettings(nativePtr, syncClientValidateSsl, syncSslTrustCertificatePath); // TODO: maybe expose the option for a custom Proxy or ProxySelector in the config? ProxySelector proxySelector = ProxySelector.getDefault(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmSync.java b/realm/realm-library/src/objectServer/java/io/realm/RealmSync.java index eecb38575a..abd6888e15 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmSync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmSync.java @@ -287,117 +287,6 @@ public static void refreshConnections() { notifyNetworkIsBack(); } - // Holds the certificate chain (per hostname). We need to keep the order of each certificate - // according to it's depth in the chain. The depth of the last - // certificate is 0. The depth of the first certificate is chain - // length - 1. - private static HashMap> ROS_CERTIFICATES_CHAIN; - - // The default Android Trust Manager which uses the default KeyStore to - // validate the certificate chain. - private static X509TrustManager TRUST_MANAGER; - - // Help transform a String PEM representation of the certificate, into - // X509Certificate format. - private static CertificateFactory CERTIFICATE_FACTORY; - - // From Sync implementation: - // A recommended way of using the callback function is to return true - // if preverify_ok = 1 and depth > 0, - // always check the host name if depth = 0, - // and use an independent verification step if preverify_ok = 0. - // - // Another possible way of using the callback is to collect all the - // ROS_CERTIFICATES_CHAIN until depth = 0, and present the entire chain for - // independent verification. - // - // In this implementation we use the second method, since it's more suitable for - // the underlying Java API we need to call to validate the certificate chain. - @SuppressWarnings("unused") - synchronized static boolean sslVerifyCallback(String serverAddress, String pemData, int depth) { - try { - if (ROS_CERTIFICATES_CHAIN == null) { - ROS_CERTIFICATES_CHAIN = new HashMap<>(); - TRUST_MANAGER = systemDefaultTrustManager(); - CERTIFICATE_FACTORY = CertificateFactory.getInstance("X.509"); - } - - if (!ROS_CERTIFICATES_CHAIN.containsKey(serverAddress)) { - ROS_CERTIFICATES_CHAIN.put(serverAddress, new ArrayList()); - } - - ROS_CERTIFICATES_CHAIN.get(serverAddress).add(pemData); - - if (depth == 0) { - // transform all PEM ROS_CERTIFICATES_CHAIN into Java X509 - // with respecting the order/depth provided from Sync. - List pemChain = ROS_CERTIFICATES_CHAIN.get(serverAddress); - int n = pemChain.size(); - X509Certificate[] chain = new X509Certificate[n]; - for (String pem : pemChain) { - // The depth of the last certificate is 0. - // The depth of the first certificate is chain length - 1. - chain[--n] = buildCertificateFromPEM(pem); - } - - // verify the entire chain - try { - TRUST_MANAGER.checkClientTrusted(chain, "RSA"); - // verify the hostname - boolean isValid = OkHostnameVerifier.INSTANCE.verify(serverAddress, chain[0]); - if (isValid) { - return true; - } else { - RealmLog.error("Can not verify the hostname for the host: " + serverAddress); - return false; - } - } catch (CertificateException e) { - RealmLog.error(e, "Can not validate SSL chain certificate for the host: " + serverAddress); - return false; - } finally { - // don't keep the certificate chain in memory - ROS_CERTIFICATES_CHAIN.remove(serverAddress); - } - } else { - // return true, since the verification will happen for the entire chain - // when receiving the depth == 0 (host certificate) - return true; - } - } catch (Exception e) { - RealmLog.error(e, "Error during certificate validation for host: " + serverAddress); - return false; - } - } - - // Credit OkHttp https://github.com/square/okhttp/blob/e5c84e1aef9572adb493197c1b6c4e882aca085b/okhttp/src/main/java/okhttp3/OkHttpClient.java#L270 - private static X509TrustManager systemDefaultTrustManager() { - try { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException("Unexpected default trust managers:" - + Arrays.toString(trustManagers)); - } - return (X509TrustManager) trustManagers[0]; - } catch (GeneralSecurityException e) { - throw new IllegalStateException("No System TLS", e); // The system has no TLS. Just give up. - } - } - - private static X509Certificate buildCertificateFromPEM(String pem) throws IOException, CertificateException { - InputStream stream = null; - try { - stream = new ByteArrayInputStream(pem.getBytes("UTF-8")); - return (X509Certificate) CERTIFICATE_FACTORY.generateCertificate(stream); - } finally { - if (stream != null) { - stream.close(); - } - } - } - /** * Resets the SyncManger and clear all existing users. * This will also terminate all sessions. diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index ffd66501d3..be1c02724f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -95,10 +95,6 @@ public class SyncConfiguration extends RealmConfiguration { private final RealmUser user; private final SyncSession.ErrorHandler errorHandler; private final boolean deleteRealmOnLogout; - private final boolean syncClientValidateSsl; - @Nullable - private final String serverCertificateAssetName; - @Nullable private final String serverCertificateFilePath; private final boolean waitForInitialData; private final long initialDataTimeoutMillis; private final OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy; @@ -124,9 +120,6 @@ private SyncConfiguration(File directory, URI serverUrl, SyncSession.ErrorHandler errorHandler, boolean deleteRealmOnLogout, - boolean syncClientValidateSsl, - @Nullable String serverCertificateAssetName, - @Nullable String serverCertificateFilePath, boolean waitForInitialData, long initialDataTimeoutMillis, OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy, @@ -156,9 +149,6 @@ private SyncConfiguration(File directory, this.serverUrl = serverUrl; this.errorHandler = errorHandler; this.deleteRealmOnLogout = deleteRealmOnLogout; - this.syncClientValidateSsl = syncClientValidateSsl; - this.serverCertificateAssetName = serverCertificateAssetName; - this.serverCertificateFilePath = serverCertificateFilePath; this.waitForInitialData = waitForInitialData; this.initialDataTimeoutMillis = initialDataTimeoutMillis; this.sessionStopPolicy = sessionStopPolicy; @@ -281,16 +271,11 @@ public boolean equals(@Nullable Object o) { SyncConfiguration that = (SyncConfiguration) o; if (deleteRealmOnLogout != that.deleteRealmOnLogout) return false; - if (syncClientValidateSsl != that.syncClientValidateSsl) return false; if (waitForInitialData != that.waitForInitialData) return false; if (initialDataTimeoutMillis != that.initialDataTimeoutMillis) return false; if (!serverUrl.equals(that.serverUrl)) return false; if (!user.equals(that.user)) return false; if (!errorHandler.equals(that.errorHandler)) return false; - if (serverCertificateAssetName != null ? !serverCertificateAssetName.equals(that.serverCertificateAssetName) : that.serverCertificateAssetName != null) - return false; - if (serverCertificateFilePath != null ? !serverCertificateFilePath.equals(that.serverCertificateFilePath) : that.serverCertificateFilePath != null) - return false; if (sessionStopPolicy != that.sessionStopPolicy) return false; if (syncUrlPrefix != null ? !syncUrlPrefix.equals(that.syncUrlPrefix) : that.syncUrlPrefix != null) return false; @@ -304,9 +289,6 @@ public int hashCode() { result = 31 * result + user.hashCode(); result = 31 * result + errorHandler.hashCode(); result = 31 * result + (deleteRealmOnLogout ? 1 : 0); - result = 31 * result + (syncClientValidateSsl ? 1 : 0); - result = 31 * result + (serverCertificateAssetName != null ? serverCertificateAssetName.hashCode() : 0); - result = 31 * result + (serverCertificateFilePath != null ? serverCertificateFilePath.hashCode() : 0); result = 31 * result + (waitForInitialData ? 1 : 0); result = 31 * result + (int) (initialDataTimeoutMillis ^ (initialDataTimeoutMillis >>> 32)); result = 31 * result + sessionStopPolicy.hashCode(); @@ -327,12 +309,6 @@ public String toString() { sb.append("\n"); sb.append("deleteRealmOnLogout: ").append(deleteRealmOnLogout); sb.append("\n"); - sb.append("syncClientValidateSsl: ").append(syncClientValidateSsl); - sb.append("\n"); - sb.append("serverCertificateAssetName: ").append(serverCertificateAssetName); - sb.append("\n"); - sb.append("serverCertificateFilePath: ").append(serverCertificateFilePath); - sb.append("\n"); sb.append("waitForInitialData: ").append(waitForInitialData); sb.append("\n"); sb.append("initialDataTimeoutMillis: ").append(initialDataTimeoutMillis); @@ -377,40 +353,6 @@ public boolean shouldDeleteRealmOnLogout() { return deleteRealmOnLogout; } - /** - * Returns the name of certificate stored under the {@code assets}, to be used to validate - * the TLS connection to the Realm Object Server. - * - * @return name of the certificate to be copied from the {@code assets}. - * @see #getServerCertificateFilePath() - */ - @Nullable - public String getServerCertificateAssetName() { - return serverCertificateAssetName; - } - - /** - * Returns the name of the certificate copied from {@code assets} into internal storage, so it - * can be used to validate the TLS connection to the Realm Object Server. - * - * @return absolute path to the certificate. - * @see #getServerCertificateAssetName() - */ - @Nullable - public String getServerCertificateFilePath() { - return serverCertificateFilePath; - } - - /** - * Whether the Realm Object Server certificate should be validated in order - * to establish a valid TLS connection. - * - * @return {@code true} to validate the remote certificate, or {@code false} to bypass certificate validation. - */ - public boolean syncClientValidateSsl() { - return syncClientValidateSsl; - } - /** * Returns {@code true} if the Realm will download all known changes from the remote server before being opened the * first time. @@ -503,11 +445,6 @@ public static final class Builder { private URI serverUrl; private RealmUser user = null; private SyncSession.ErrorHandler errorHandler; - private boolean syncClientValidateSsl = true; - @Nullable - private String serverCertificateAssetName; - @Nullable - private String serverCertificateFilePath; private OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy = OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED; private CompactOnLaunchCallback compactOnLaunch; private String syncUrlPrefix = null; @@ -834,43 +771,6 @@ public Builder errorHandler(SyncSession.ErrorHandler errorHandler) { return this; } - /** - * Provides the trusted root certificate(s) authority (CA) in {@code PEM} format, that should be used to - * validate the TLS connections to the Realm Object Server. - *

              - * The file should be stored under {@code assets}, it will be copied at runtime into the internal storage. - *

              - * Note: This is similar to passing the parameter {@code CAfile} to {@code SSL_CTX_load_verify_locations}, - * Therefore it is recommended to include only the root CA you trust, and not the entire list of root CA - * as this file will be loaded at runtime. - * - * It is your responsibility to download and verify the correct {@code PEM} for the root CA you trust. - * An existing list by Mozilla exist that could be used https://mozillacaprogram.secure.force.com/CA/IncludedCACertificateReportPEMCSV - * - * @param filename the path under {@code assets} to the root CA. - * @see SSL_CTX_load_verify_locations - */ - public Builder trustedRootCA(String filename) { - //noinspection ConstantConditions - if (filename == null || filename.isEmpty()) { - throw new IllegalArgumentException("A non-empty filename must be provided"); - } - this.serverCertificateAssetName = filename; - return this; - } - - /** - * This will disable TLS certificate verification for the remote Realm Object Server. - * It is not recommended to use this in production. - *

              - * This might be useful in non-production environments where you use a self-signed certificate - * for testing. - */ - public Builder disableSSLVerification() { - this.syncClientValidateSsl = false; - return this; - } - /** * Setting this will cause the Realm to download all known changes from the server the first time a Realm is * opened. The Realm will not open until all the data has been downloaded. This means that if a device is @@ -1119,18 +1019,6 @@ public SyncConfiguration build() { throw new IllegalStateException("Could not create directory for saving the Realm: " + realmFileDirectory); } - if (!Util.isEmptyString(serverCertificateAssetName)) { - if (syncClientValidateSsl) { - // Create the path where the serverCertificateAssetName will be copied - // so we can supply it to the Sync client. - // using getRealmDirectory avoid file collision between same filename from different users (Realms) - String fileName = serverCertificateAssetName.substring(serverCertificateAssetName.lastIndexOf(File.separatorChar) + 1); - serverCertificateFilePath = new File(realmFileDirectory, fileName).getAbsolutePath(); - } else { - RealmLog.warn("SSL Verification is disabled, the provided server certificate will not be used."); - } - } - return new SyncConfiguration( // Realm Configuration options realmFileDirectory, @@ -1153,9 +1041,6 @@ public SyncConfiguration build() { resolvedServerUrl, errorHandler, deleteRealmOnLogout, - syncClientValidateSsl, - serverCertificateAssetName, - serverCertificateFilePath, waitForServerChanges, initialDataTimeoutMillis, sessionStopPolicy, diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 4c78346d4f..24fdb1dd23 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -101,22 +101,21 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { } else { throw new IllegalArgumentException("Unsupported type: " + val); } + int i = 0; Object[] configObj = new Object[SYNC_CONFIG_OPTIONS]; - configObj[0] = rosUserIdentity; - configObj[1] = rosServerUrl; - configObj[2] = syncRealmAuthUrl; - configObj[3] = syncUserRefreshToken; - configObj[4] = syncUserAccessToken; - configObj[5] = deviceId; - configObj[6] = syncConfig.syncClientValidateSsl(); - configObj[7] = syncConfig.getServerCertificateFilePath(); - configObj[8] = sessionStopPolicy; - configObj[9] = urlPrefix; - configObj[10] = customAuthorizationHeaderName; - configObj[11] = customHeaders; - configObj[12] = OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL; - configObj[13] = partitionValue; - configObj[14] = app.getSync(); + configObj[i++] = rosUserIdentity; + configObj[i++] = rosServerUrl; + configObj[i++] = syncRealmAuthUrl; + configObj[i++] = syncUserRefreshToken; + configObj[i++] = syncUserAccessToken; + configObj[i++] = deviceId; + configObj[i++] = sessionStopPolicy; + configObj[i++] = urlPrefix; + configObj[i++] = customAuthorizationHeaderName; + configObj[i++] = customHeaders; + configObj[i++] = OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL; + configObj[i++] = partitionValue; + configObj[i++] = app.getSync(); return configObj; } else { return new Object[SYNC_CONFIG_OPTIONS]; @@ -136,26 +135,6 @@ public void wrapObjectStoreSessionIfRequired(OsRealmConfig config) { } } - @Override - public String getSyncServerCertificateAssetName(RealmConfiguration configuration) { - if (configuration instanceof SyncConfiguration) { - SyncConfiguration syncConfig = (SyncConfiguration) configuration; - return syncConfig.getServerCertificateAssetName(); - } else { - throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); - } - } - - @Override - public String getSyncServerCertificateFilePath(RealmConfiguration configuration) { - if (configuration instanceof SyncConfiguration) { - SyncConfiguration syncConfig = (SyncConfiguration) configuration; - return syncConfig.getServerCertificateFilePath(); - } else { - throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); - } - } - //FIXME remove this reflection call once we redesign the SyncManager to separate interface // from implementation to avoid issue like exposing internal method like SyncManager#removeSession // or SyncSession#close. This happens because SyncObjectServerFacade is internal, whereas diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java deleted file mode 100644 index b41e5147ab..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SSLConfigurationTests.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.os.SystemClock; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.Timeout; -import org.junit.runner.RunWith; - -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import io.realm.entities.StringOnly; -import io.realm.exceptions.RealmFileException; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; -import io.realm.objectserver.utils.Constants; -import io.realm.rule.RunTestInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class SSLConfigurationTests extends StandardIntegrationTest { - - // TODO: All tests in this class are currently marked @RunTestInLooperThread, - // this is strictly not necessary, but currently needed to avoid other issues with setting - // up tests. - - @Rule - public Timeout globalTimeout = Timeout.seconds(120); - - @Test - @RunTestInLooperThread - @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") - public void trustedRootCA() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server - //noinspection unchecked - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - Realm realm = Realm.getInstance(syncConfig); - - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - - // make sure the changes gets to the server - SyncManager.getSession(syncConfig).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes. - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - //noinspection unchecked - SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .name("useSsl") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .trustedRootCA("trusted_ca.pem") - .build(); - realm = Realm.getInstance(syncConfigSSL); - - RealmResults all = realm.where(StringOnly.class).findAll(); - try { - assertEquals(1, all.size()); - assertEquals("Foo", all.get(0).getChars()); - } finally { - realm.close(); - } - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - public void withoutSSLVerification() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server - //noinspection unchecked - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - Realm realm = Realm.getInstance(syncConfig); - - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - - // make sure the changes gets to the server - SyncManager.getSession(syncConfig).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes. - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - //noinspection unchecked - SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .name("useSsl") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .disableSSLVerification() - .build(); - realm = Realm.getInstance(syncConfigSSL); - - RealmResults all = realm.where(StringOnly.class).findAll(); - try { - assertEquals(1, all.size()); - assertEquals("Foo", all.get(0).getChars()); - } finally { - realm.close(); - } - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - public void trustedRootCA_syncShouldFailWithoutTrustedCA() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server - //noinspection unchecked - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - Realm realm = Realm.getInstance(syncConfig); - - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - - // make sure the changes gets to the server - SyncManager.getSession(syncConfig).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes. - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - //noinspection unchecked - SyncConfiguration syncConfigSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .name("useSsl") - .schema(StringOnly.class) - .trustedRootCA("untrusted_ca.pem") - .build(); - // waitForInitialRemoteData will throw an Internal error (125): Operation Canceled - SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); - realm = Realm.getInstance(syncConfigSSL); - try { - assertTrue(realm.isEmpty()); - } finally { - realm.close(); - } - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - public void combining_trustedRootCA_and_withoutSSLVerification_willThrow() { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); - int originalLevel = RealmLog.getLevel(); - RealmLog.add(testLogger); - RealmLog.setLevel(LogLevel.WARN); - - //noinspection unchecked - configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .name("useSsl") - .schema(StringOnly.class) - .trustedRootCA("trusted_ca.pem") - .disableSSLVerification() - .build(); - - assertEquals("SSL Verification is disabled, the provided server certificate will not be used.", - testLogger.message); - RealmLog.remove(testLogger); - RealmLog.setLevel(originalLevel); - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") - public void trustedRootCA_notExisting_certificate_willThrow() { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - //noinspection unchecked - SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .schema(StringOnly.class) - .trustedRootCA("none_existing_file.pem") - .build(); - - try { - Realm.getInstance(syncConfig); - fail(); - } catch (RealmFileException ignored) { - } - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") - public void combiningTrustedRootCA_and_disableSSLVerification() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server using ssl_verify_path option - //noinspection unchecked - final SyncConfiguration syncConfigWithCertificate = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .schema(StringOnly.class) - .trustedRootCA("trusted_ca.pem") - .build(); - Realm realm = Realm.getInstance(syncConfigWithCertificate); - - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - - // make sure the changes gets to the server - SyncManager.getSession(syncConfigWithCertificate).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes. - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - //noinspection unchecked - SyncConfiguration syncConfigDisableSSL = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .name("useSsl") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .disableSSLVerification() - .build(); - realm = Realm.getInstance(syncConfigDisableSSL); - - RealmResults all = realm.where(StringOnly.class).findAll(); - try { - assertEquals(1, all.size()); - assertEquals("Foo", all.get(0).getChars()); - } finally { - realm.close(); - } - looperThread.testComplete(); - } - - // IMPORTANT: Following test assume the root certificate is installed on the test device - // certificate is located in /tools/sync_test_server/keys/android_test_certificate.crt - // adb push /tools/sync_test_server/keys/android_test_certificate.crt /sdcard/ - // then import the certificate from the device (Settings/Security/Install from storage) - @Test - @RunTestInLooperThread - @Ignore("FIXME: https://github.com/realm/realm-java/issues/6472") - public void sslVerifyCallback_isUsed() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server using ssl_verify_path option - //noinspection unchecked - final SyncConfiguration syncConfig = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - Realm realm = Realm.getInstance(syncConfig); - - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - - // make sure the changes gets to the server - SyncManager.getSession(syncConfig).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes. - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - //noinspection unchecked - SyncConfiguration syncConfigSecure = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM_SECURE) - .name("useSsl") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .build(); - realm = Realm.getInstance(syncConfigSecure); - - RealmResults all = realm.where(StringOnly.class).findAll(); - try { - assertEquals(1, all.size()); - assertEquals("Foo", all.get(0).getChars()); - } finally { - realm.close(); - } - looperThread.testComplete(); - } -} diff --git a/tools/sync_test_server/keys/127_0_0_1-chain.crt.pem b/tools/sync_test_server/keys/127_0_0_1-chain.crt.pem deleted file mode 100644 index 7c55402c55..0000000000 --- a/tools/sync_test_server/keys/127_0_0_1-chain.crt.pem +++ /dev/null @@ -1,229 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 7 (0x7) - Signature Algorithm: sha1WithRSAEncryption - Issuer: DC=io, DC=realm, O=Realm, OU=Realm Test Signing CA, CN=Realm Test Signing CA - Validity - Not Before: May 17 23:28:48 2017 GMT - Not After : May 17 23:28:48 2019 GMT - Subject: DC=127.0.0.1, O=Realm, OU=Realm, CN=127.0.0.1 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (2048 bit) - Modulus (2048 bit): - 00:b7:8c:99:7b:6b:5b:29:80:9c:99:b7:fe:b8:1e: - 06:6c:4d:58:bd:38:b6:cc:13:6a:ff:fd:f6:26:a0: - 71:d2:8a:94:ea:06:be:e1:b8:5b:03:5e:96:8e:61: - 19:0f:80:5f:b8:51:43:97:00:38:67:47:0e:09:b8: - ab:80:d1:9c:81:b8:81:db:0b:b4:c5:de:7a:3c:f5: - 9c:cb:bf:2b:12:fe:b1:9d:4e:a0:4a:ab:ae:c4:11: - 19:54:c5:17:ed:58:c3:72:f0:e5:46:dc:21:41:4c: - 63:1d:31:85:f3:ba:f6:ad:69:3e:d8:86:9d:c4:56: - 3b:52:47:5d:24:dd:40:af:f9:fa:03:4c:4e:2d:5b: - d2:34:dc:f0:a4:19:13:14:f6:6c:7c:1c:47:bd:7d: - 79:a2:09:61:ad:3f:8d:4e:59:c5:ae:a7:2b:39:00: - e9:34:68:6c:6f:d8:60:a2:a9:24:33:1c:9f:80:b4: - c6:8e:ba:37:98:71:3d:dd:82:66:d8:e0:c2:81:cc: - 4b:d1:5d:58:26:cf:85:0a:ac:fd:08:a5:40:50:ff: - f9:73:e8:27:18:7b:70:75:73:2a:23:c7:d7:15:be: - ca:ce:57:c9:ee:bb:6d:b4:d8:fd:c7:22:47:28:9b: - 00:5e:4b:af:82:63:c3:d7:6a:74:4e:60:17:94:6a: - fb:83 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Digital Signature, Key Encipherment - X509v3 Basic Constraints: - CA:FALSE - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Subject Key Identifier: - C6:BD:F4:49:F5:2E:76:52:44:F1:E2:CE:2E:6C:F9:59:B1:56:14:AD - X509v3 Authority Key Identifier: - keyid:27:79:E1:DA:F1:15:D0:E9:E5:86:30:87:E7:1F:5F:CF:27:3A:70:B4 - - X509v3 Subject Alternative Name: - IP Address:127.0.0.1 - Signature Algorithm: sha1WithRSAEncryption - 9b:3f:74:f4:69:72:6b:5b:75:88:b7:10:5f:e0:73:b2:82:be: - dc:71:1b:a3:d6:a4:6e:ae:a6:5f:51:78:5c:80:64:20:21:20: - 13:16:08:d0:ae:15:f6:52:24:d9:23:5c:24:ed:62:3b:0e:e6: - 5f:00:4b:bf:a1:94:34:2a:fb:2a:46:5f:54:e6:3b:7f:be:81: - 5b:df:e8:78:a6:1e:ce:e2:87:b9:8b:e4:d8:04:1f:18:c4:29: - 80:7a:21:a9:56:c5:29:ee:4e:28:33:fa:8d:92:46:a2:31:31: - c5:f9:31:19:ed:1d:f6:c7:75:82:65:b6:1a:ad:bc:34:4a:f3: - 18:05:a0:a3:d4:9a:50:f2:ef:bb:c4:2f:89:10:95:68:17:6a: - 85:76:ce:88:8c:19:cf:d7:aa:70:c3:d0:59:9b:9a:c0:d0:a8: - d0:d3:cd:f5:f0:8e:5e:19:ab:a1:ae:54:dc:07:ac:6d:6e:d8: - f0:ee:65:47:de:29:1e:76:1b:1d:0e:62:f5:dd:1d:f6:6e:ad: - 27:2e:8d:be:c4:a8:41:0b:b1:44:22:3d:29:b3:57:74:3e:3b: - 41:28:19:8d:48:ed:65:05:5b:8e:17:a7:ab:45:24:d9:95:00: - e4:04:e7:6c:d8:6b:6b:2a:89:5e:1f:fb:b4:f2:1c:20:55:ea: - f5:40:99:58:a2:de:c3:83:e5:01:70:f8:53:e9:8d:95:ba:0b: - 20:d0:99:e6:b4:31:05:55:00:9c:f7:f3:96:7c:74:5b:7d:c7: - 6a:ae:ec:90:f8:0f:f2:f0:58:ec:80:0c:79:04:b0:f3:69:cd: - e5:41:f2:f5:fc:44:ba:d9:4f:3f:32:fe:69:f3:6e:1f:32:94: - 62:78:17:76:dc:d5:0c:19:a6:8b:97:70:e9:19:39:a8:fa:b1: - 00:a6:18:6f:4b:2d:38:2f:1d:96:0b:87:98:86:c1:2c:75:44: - 3c:0e:e1:eb:f4:c4:4e:02:c7:9f:f7:cc:30:8a:72:23:bf:44: - 7c:7c:3f:f7:7c:b1:b1:d7:aa:4b:e4:1e:dd:ca:fd:5a:8d:2e: - aa:ff:49:af:a5:63:6f:88:31:26:7d:b3:a4:e3:4d:4d:45:d6: - 44:7b:12:a8:6e:06:bb:81:c0:80:a4:5f:95:3b:d4:a5:4a:01: - 1d:00:0e:7b:5d:20:29:97:d0:d6:88:73:f1:89:c1:01:54:85: - 9b:9c:ef:41:6d:f9:b6:83:cc:65:ac:34:ca:bf:88:fc:34:4f: - 7f:62:30:e2:d9:02:eb:c7:58:86:90:6f:e4:7e:5f:20:45:f2: - c4:a0:7e:ad:92:5a:85:8f:08:90:3d:e8:65:be:54:79:e2:62: - 3c:5d:8e:57:36:2a:17:bb ------BEGIN CERTIFICATE----- -MIIE1DCCArygAwIBAgIBBzANBgkqhkiG9w0BAQUFADB7MRIwEAYKCZImiZPyLGQB -GRYCaW8xFTATBgoJkiaJk/IsZAEZFgVyZWFsbTEOMAwGA1UECgwFUmVhbG0xHjAc -BgNVBAsMFVJlYWxtIFRlc3QgU2lnbmluZyBDQTEeMBwGA1UEAwwVUmVhbG0gVGVz -dCBTaWduaW5nIENBMB4XDTE3MDUxNzIzMjg0OFoXDTE5MDUxNzIzMjg0OFowTzEZ -MBcGCgmSJomT8ixkARkWCTEyNy4wLjAuMTEOMAwGA1UECgwFUmVhbG0xDjAMBgNV -BAsMBVJlYWxtMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQC3jJl7a1spgJyZt/64HgZsTVi9OLbME2r//fYmoHHSipTq -Br7huFsDXpaOYRkPgF+4UUOXADhnRw4JuKuA0ZyBuIHbC7TF3no89ZzLvysS/rGd -TqBKq67EERlUxRftWMNy8OVG3CFBTGMdMYXzuvataT7Yhp3EVjtSR10k3UCv+foD -TE4tW9I03PCkGRMU9mx8HEe9fXmiCWGtP41OWcWupys5AOk0aGxv2GCiqSQzHJ+A -tMaOujeYcT3dgmbY4MKBzEvRXVgmz4UKrP0IpUBQ//lz6CcYe3B1cyojx9cVvsrO -V8nuu2202P3HIkcomwBeS6+CY8PXanROYBeUavuDAgMBAAGjgY4wgYswDgYDVR0P -AQH/BAQDAgWgMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF -BwMCMB0GA1UdDgQWBBTGvfRJ9S52UkTx4s4ubPlZsVYUrTAfBgNVHSMEGDAWgBQn -eeHa8RXQ6eWGMIfnH1/PJzpwtDAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEB -BQUAA4ICAQCbP3T0aXJrW3WItxBf4HOygr7ccRuj1qRurqZfUXhcgGQgISATFgjQ -rhX2UiTZI1wk7WI7DuZfAEu/oZQ0KvsqRl9U5jt/voFb3+h4ph7O4oe5i+TYBB8Y -xCmAeiGpVsUp7k4oM/qNkkaiMTHF+TEZ7R32x3WCZbYarbw0SvMYBaCj1JpQ8u+7 -xC+JEJVoF2qFds6IjBnP16pww9BZm5rA0KjQ08318I5eGauhrlTcB6xtbtjw7mVH -3ikedhsdDmL13R32bq0nLo2+xKhBC7FEIj0ps1d0PjtBKBmNSO1lBVuOF6erRSTZ -lQDkBOds2GtrKoleH/u08hwgVer1QJlYot7Dg+UBcPhT6Y2Vugsg0JnmtDEFVQCc -9/OWfHRbfcdqruyQ+A/y8FjsgAx5BLDzac3lQfL1/ES62U8/Mv5p824fMpRieBd2 -3NUMGaaLl3DpGTmo+rEAphhvSy04Lx2WC4eYhsEsdUQ8DuHr9MROAsef98wwinIj -v0R8fD/3fLGx16pL5B7dyv1ajS6q/0mvpWNviDEmfbOk401NRdZEexKobga7gcCA -pF+VO9SlSgEdAA57XSApl9DWiHPxicEBVIWbnO9Bbfm2g8xlrDTKv4j8NE9/YjDi -2QLrx1iGkG/kfl8gRfLEoH6tklqFjwiQPehlvlR54mI8XY5XNioXuw== ------END CERTIFICATE----- -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 2 (0x2) - Signature Algorithm: sha1WithRSAEncryption - Issuer: DC=io, DC=realm, O=Realm, OU=Realm Test Root CA, CN=Realm Test Root CA - Validity - Not Before: Sep 7 10:17:28 2016 GMT - Not After : Sep 7 10:17:28 2026 GMT - Subject: DC=io, DC=realm, O=Realm, OU=Realm Test Signing CA, CN=Realm Test Signing CA - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (4096 bit) - Modulus: - 00:bf:5b:5a:92:de:53:af:73:80:e1:3f:22:55:75: - 38:7d:9e:3b:65:49:aa:d3:a7:ac:04:be:4d:a5:c2: - 7d:03:30:c9:d8:41:d2:00:0a:cd:df:a3:68:a5:13: - 92:0a:71:22:c5:09:d4:75:97:73:a6:fa:37:64:a7: - 61:55:84:88:c1:be:eb:92:e0:a6:1b:00:04:c4:31: - fd:4a:e9:14:56:c8:ad:0b:5c:73:c8:55:1c:e6:60: - b1:3a:e3:c2:1d:41:1a:6d:57:12:df:da:c2:fe:40: - e7:d1:a5:71:29:71:cb:0d:12:d5:c6:be:e8:ab:62: - 9c:11:70:b5:de:f1:20:b6:bc:05:af:7a:3f:e4:df: - 74:33:d6:bb:a3:33:59:f3:3a:a0:af:2d:ea:e7:16: - c8:8f:25:f1:fb:27:73:80:46:e3:44:5b:b8:f5:4c: - ba:8e:61:6d:5f:2c:c0:8f:e8:d2:bd:3a:e8:0d:fa: - 16:de:32:19:84:c8:6c:ad:11:14:5c:ce:44:be:46: - be:f0:7b:83:27:21:f1:49:f5:ba:e5:bc:59:07:b0: - c2:fb:4f:7e:56:76:eb:cb:14:f1:50:d6:b3:83:10: - 2b:b1:d3:80:68:98:45:67:70:9e:1b:ef:ff:28:d0: - ef:1a:38:28:64:16:84:5a:d1:9f:05:7f:15:50:6a: - ce:ac:08:25:e4:3f:d6:df:09:d9:59:b2:05:d9:b7: - e3:94:ae:f6:c6:04:69:2e:d5:47:79:54:92:fb:72: - f2:4c:09:2b:64:3a:3e:d1:30:76:0c:33:65:0a:08: - 55:34:44:83:05:e9:1e:51:c2:58:70:44:30:6c:ef: - 0a:e0:b0:e2:10:2b:e1:55:29:24:03:68:61:bb:44: - 58:ea:ad:03:b3:a9:0f:13:44:ff:ea:24:d3:7b:bd: - 25:78:57:79:7b:e4:4b:9e:bc:32:33:63:d5:f7:25: - 39:f7:c5:31:8d:9f:f2:76:a3:6b:bb:5b:a4:dd:cc: - 96:44:b4:44:9d:50:ef:2e:64:29:02:a3:c7:52:f7: - 36:92:c5:fa:fb:75:dd:25:67:3e:46:37:e3:9d:dc: - de:f6:e5:6a:0a:95:7e:e9:90:3e:c9:b1:f8:74:07: - b9:ff:f2:24:c9:84:f0:9f:3c:a7:ce:ca:64:be:77: - 1e:7c:93:29:6f:c8:8e:8d:74:d3:a8:1d:e1:db:9b: - 8b:c3:27:d8:0d:03:fc:7e:3e:25:19:6a:b8:a5:97: - d0:7a:c3:13:33:bc:3a:8c:c7:25:e9:f9:cf:4a:c5: - c3:26:63:42:ef:58:d7:42:06:30:8a:20:c8:cd:6a: - 40:8b:fa:88:e0:54:ed:60:09:69:26:12:43:b5:f2: - 93:6c:5b - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - X509v3 Basic Constraints: critical - CA:TRUE, pathlen:0 - X509v3 Subject Key Identifier: - 27:79:E1:DA:F1:15:D0:E9:E5:86:30:87:E7:1F:5F:CF:27:3A:70:B4 - X509v3 Authority Key Identifier: - keyid:84:70:71:2C:04:3B:D0:92:83:B5:FB:7C:7F:B0:61:0C:62:16:71:74 - - Signature Algorithm: sha1WithRSAEncryption - 36:05:84:8c:88:21:08:a7:e2:bd:41:a8:27:7f:b7:c2:9a:86: - d7:21:fe:ed:4c:51:d1:29:df:35:4a:0e:ea:a0:b5:6c:cc:28: - 2f:5e:bd:9f:97:68:be:aa:2a:ff:54:91:9e:ef:04:5d:0d:ec: - e7:98:35:10:78:50:b6:1f:17:96:a9:5e:9a:60:fb:68:e9:06: - 7d:53:8c:58:b7:9a:47:e0:9f:c8:d1:43:1a:74:41:3f:ab:03: - 21:35:88:8e:34:3a:25:b7:98:67:24:8b:d4:14:88:57:1c:99: - df:08:0c:bd:57:9f:53:db:3a:47:a4:e5:4c:29:38:e2:82:39: - 45:ea:62:ca:1b:d8:95:cb:0a:e3:65:10:97:c7:10:d7:8b:2d: - db:fa:16:c6:c4:0c:81:26:e6:6f:f1:da:fb:79:c0:12:27:58: - 9a:2b:95:a0:bd:73:88:ad:f5:ad:9b:cd:49:3d:ad:2c:02:84: - f8:88:be:3c:bf:d7:a2:28:e8:09:1e:7c:0d:b0:56:ad:e6:e3: - a7:11:56:58:66:83:dd:80:31:56:a6:15:45:e1:e3:52:49:f2: - f8:4c:3c:60:fd:d6:1b:45:61:ec:52:c1:d9:b9:da:b0:5d:5c: - 3b:7f:ef:34:dd:48:26:19:5a:66:ad:b5:a7:87:6b:73:e8:ea: - d3:5d:cd:d1:3f:ac:77:e4:59:8d:4d:95:38:2f:e8:17:ff:8f: - 67:c8:f8:5d:8e:86:b7:78:50:25:62:35:b9:07:15:f6:eb:65: - 98:80:96:0b:d2:14:cb:54:1a:75:0c:ab:d9:c2:99:1b:20:da: - e3:a4:77:68:ef:75:cc:44:7f:66:f4:47:8c:7a:03:21:b6:6c: - c7:00:b2:50:15:84:5c:87:2a:fb:03:3a:d7:2d:df:52:96:80: - b6:c0:3d:a0:4b:65:67:5e:f3:bd:41:fe:f4:62:3a:de:0f:30: - 8d:47:bf:8a:0b:f8:0e:d8:7b:84:93:60:e6:73:a9:60:11:f3: - 5a:16:2c:2a:58:c0:dc:78:8f:66:c8:10:90:d5:da:03:35:e9: - a7:22:8a:04:14:cf:e1:fb:c9:46:5f:cc:29:ef:c7:22:bd:65: - a4:8a:47:e0:d5:10:0d:12:4c:a3:72:11:e6:5f:9e:5b:87:38: - eb:48:74:dd:86:26:ab:45:61:63:45:52:21:a9:35:65:84:30: - 49:85:68:90:b6:23:0c:f0:10:7e:de:e1:e8:3d:94:6d:ff:44: - a0:2e:86:1c:4c:bb:ec:72:85:3b:d0:6d:49:69:47:a9:61:1f: - c5:39:cb:d6:7a:06:e9:41:52:df:00:00:c0:08:3f:21:bd:44: - 52:c4:78:a5:fe:e5:33:fe ------BEGIN CERTIFICATE----- -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 -pf7lM/4= ------END CERTIFICATE----- diff --git a/tools/sync_test_server/keys/127_0_0_1-server.key.pem b/tools/sync_test_server/keys/127_0_0_1-server.key.pem deleted file mode 100644 index 8f46018e95..0000000000 --- a/tools/sync_test_server/keys/127_0_0_1-server.key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAt4yZe2tbKYCcmbf+uB4GbE1YvTi2zBNq//32JqBx0oqU6ga+ -4bhbA16WjmEZD4BfuFFDlwA4Z0cOCbirgNGcgbiB2wu0xd56PPWcy78rEv6xnU6g -SquuxBEZVMUX7VjDcvDlRtwhQUxjHTGF87r2rWk+2IadxFY7UkddJN1Ar/n6A0xO -LVvSNNzwpBkTFPZsfBxHvX15oglhrT+NTlnFrqcrOQDpNGhsb9hgoqkkMxyfgLTG -jro3mHE93YJm2ODCgcxL0V1YJs+FCqz9CKVAUP/5c+gnGHtwdXMqI8fXFb7KzlfJ -7rtttNj9xyJHKJsAXkuvgmPD12p0TmAXlGr7gwIDAQABAoIBADZl3gr86zymmELa -jAxHIcIxPi5+Q3bB/oE15CDYhkNOvQmKzEGbYKXj/5zc3A+DDVtUAkMbVpwNK/Tn -nTSFauvrIdkoZAAMio/MfxbHZl2vzDYB2nGm5hnHs4kzDH9UQkCrclgI33Y5zFoX -lkqAy6DjQzPq2ZEZuWUOL2XAiO5eF313OwfTfPGfswN3OWyUshdE7pxkD1JAwkU6 -f5oeOistu964OwJdCDdf2xF3q3Ix2Ll3JA2sccgvuh9If1Rqn9qOZ6yfElr/IClU -Y73RBALvjc931cKtba1Syo7Brp/UysfH77DxSifsLp+MleYMVK2+EqYP1VaDnmjs -5I2r/0kCgYEA78JKabi68+gTSk/4FlUw+nCQ4EV9p6JiZJZoSUAbfXYxy8dXGYk8 -rNsRFaoM8Fl3br3xpRSV2OyJyX3H9Y33jOWwXUU0E6Ao+K26FvHXNmmGZCDjWTF6 -LleMisYaFvU5L6E5PKgibiTf/Z8oT7fk0dqTJbWT+klxX9DHknSMv9UCgYEAw/uQ -eEV2blUOcTXFh+9j0t4gBrixOvLn67siwFm3NQZV1PQAYfYqfRTZm4mpisaVMNKU -juAp8GWvq/R1kVJfISoztLmzytuyRtFtuX9hUSuiv6HyYBPoVGoM2Mj52SZKE3pf -XbkYQJdIgoaKvubem++eHrIQBcijio1Xmdl90fcCgYBiBcM6mgYFNjq8xRkeuFG/ -8kmpB4AqCx/DFCMq34TdtHcDY0pe7FbcLOw9OTr1AP7tTcb/wPzKpVpoAH7CC/rL -phSG7YYvB+n4Ub6lJtbgLiB9y1xn2OylCbIyAnAkNrncmUO3Yt5Avd696FYo0XxB -t+U1I5mOWHx7ufX+EJyCyQKBgQCYO6G1+ucKvyEvyT/93nMhCg/AiNiKXMLP9pYA -6e+IzboAZ+SgM5I/hOGfkuhSdvzOZtSkwvVw2dwCayqjzmM8pMZzPMiu68bogaeE -rrCOV6Hcz1QxU2VlpNcD0eFZzwc9aBIKAEwZaCoX0aCWt0j1wcSGPXR6uaZnanFA -fZPhcwKBgD3HayHdVtdCxdeFF01pDf1wQFRNgGrOPFPB3PIYC8Dpc+VpHYZNrkQ0 -AJqs4elBq1KeW8Et23KuVjYkMLA7NeefnOZA1XkAS9SISOwDB083j9BbsM/Uc+ug -qFl9DVG+S56WuyjfNDiVm95jcalD8UctQgqIxSj2u8QfO3LOyjLC ------END RSA PRIVATE KEY----- diff --git a/tools/sync_test_server/keys/HowToGenerateKey.txt b/tools/sync_test_server/keys/HowToGenerateKey.txt deleted file mode 100644 index c3db0e9188..0000000000 --- a/tools/sync_test_server/keys/HowToGenerateKey.txt +++ /dev/null @@ -1,18 +0,0 @@ -// The Base64-encoded user token is generated by the following command: -// cat test_token.json | base64 -// The Base64-encoded signature is generated by the following command: -// cat test_token.json | openssl dgst -sha256 -binary -sign private.pem | base64 -// The two are concatenated with a ':'. -// This token does not contain a "path" field, and therefore grants access to -// all Realms. - -// Example: -g_signed_test_user_token = - // cat test_token.json | base64 -"ewogICJpZGVudGl0eSI6ICJ0ZXN0IiwKICAiYWNjZXNzIjogWwogICAgImRvd25sb2FkIiwKICAgICJ1cGxvYWQiCiAgXSwKICAidGltZXN0YW1wIjogMTQ1NTUzMDYxNCwKICAiZXhwaXJlcyI6IG51bGwsCiAgImFwcF9pZCI6ICJpby5yZWFsbS50ZXN0cy5zeW5jIgp9" -+ ":" -// cat test_token.json | openssl dgst -sha256 -binary -sign private.pem | base64 -"Y5+K3Y+wd+McaZx6rte1MQvKpHgy7NoTqTzgF3CnGKcosMT7PkG1M71rLsq9/Fcldn6G26Bn3kb0vnw93TS2Ox4wa0FMiObK+N7VNdI6p/+dG5bDjBhtW2AFd2P0nOUCvx39EIdLVnGr3JUidJZEZGzFyFOdZVpnmIAnHNDaOIPOXt4vnASJ/dBjUTkOlexOwSRKIK1hvkA1GO9zpvnG5EbnVG6LuVSRM93Hp0tzuFdesns19P827/FsdZATDA9TFlwVTIa7vHz0KbzolSXKvIiOr5XWC2NXyDFEowxwFHyCuXN52jk9kylagFDTBvXu1ddmDZjWxg9SinJzS4lsYA==" - - -Reference https://github.com/realm/realm-sync/blob/master/test/test_sync.cpp#L65 \ No newline at end of file diff --git a/tools/sync_test_server/keys/private.pem b/tools/sync_test_server/keys/private.pem deleted file mode 100644 index e8f1a123c3..0000000000 --- a/tools/sync_test_server/keys/private.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAo65ZQ8mFIVk0ZB22bHdNuBr4G3K7SwfFlUhmBmjQFb2EdopA -nmu/XdXn+Zw3pmYlzIxe+3RX9M4eh8luIil0J2Nlb7tHOPZQkQAiuuud8JQ9RTND -ixUQTGS4YmhyQv+7LA9cjdaczD3Bf3Nw/yZQTQQqw7vsbTUJeAMz+6EFLeMj2Lxn -ZVLP7ePezxaSpKiQ8mp7eurQrZIqmvEC1xp8a7XvkgtqnMBepauIBiw6Wlpin0lP -D3P5uMtrP+z5MmQXpP/GOp6XjBlULMQAMH/V03WYMwnevzMWmKhF2apepnjd65nB -h29iaFuiE8tYvKpJxmrsmoU+aOvMt3ZORxj7LQIDAQABAoIBAGfSlWh8EOgAT00Z -07alTjTzVmECu25yNY/lZmG2ZhcEKVuPgkF6kt4Qap5XyqzPqjY+65iQSaJMg+0Z -hbRBmx3I3HSs1BZ7lssCzQTHo10QinS0ealk6Pur/5DcM23wDGd8LvcBJGAg4/XV -4dzWDqVreTzCnMsAk0r+rSB1GHXr0/jMiivPvUzvxpVRZ+dyGPdxUFBQivPGC7h8 -3VJRLj8zBFlf0az7xeVCGgZMAtiaJhhGtG2QCBKhk3mqlDmhIB6jTai+b+vnL1KK -tTOOhMsYXIhJXYeE6H2aXNn7z53sKoiRq2Zptzfl9csbQ5yelbtZ05CRC+nzAscr -XOl2BjkCgYEA0QYexhXsd9OA8vU+kfm1WyLZmGc1biK+RLqV+nhXzy1lRy1DdaOq -6raNgaZ9xgX5zHLqxk+2s5+dWMfyvbUxDPhl7C6R0yLNuXzW2tjsy3T5AI8ApWrA -STUOaamLyaqRt0VB8AVSco2bvHCVEjyY+Bc7RD0LHnDfBUSwobr+FrMCgYEAyHd+ -nGsWhqGEabtfZCS8f1f1PnS4jge5VY9PFjgSLT9K6KJv00tmG9PGRhKXgGiLs+DW -0EHiwWIYpAGHVOvnndWIUsxo14Mg8cRfJlA/a87RrXMNu6I/4rGPRwHqeRauNWmu -wuNSJZTul09UYo7iHqtiEFOxpSEufC4965QUlp8CgYA/9qZ+KYFWXdPNBX1jQE3e -GLkLqTGxhVJCR/LTVfZRAOxILrLBEheggcKl1SQR8Aw0I0py6zvWldaZr345zXO4 -K19NOicHvFPGGkzJZa54yE/WeuxQsm0rOeAyN17+lILI2ZnG8Gn9ghYRQUZs8TxC -VyGczS1U4Gdu/kkrBMTyfwKBgQCRYh//fqZ6gx7Ns2bt8LqHvBmO7wV9c9qUU3du -zMFZ8UH5Tvy8hz0JR1/PJ+KZ7LgMfy4rIO07hFIMd1NXYjK6w8a3DamnSmEVFW5Q -Efi8zeRA32UBRB0C4fTf8WLD6I/1Cq0Eh+nmeYlDUPQI+kjBJ1faMWhvMo5M3xhn -BiCcTwKBgQC/oQ5R6avo15UK7Tituj9TqduLf4leGJwn3ht6GsAPNDENDJZJc30A -wL+ghnvUieG1fz3OelZPx3Ber5QdNzhM8+24klevCLaCdF8alhg9nIEtWFrGpXEv -RLZ4jP2FUo1XJDNqXK4l17slzdWzEs1jiB7ePLvpoiA+GVcL3Anmkg== ------END RSA PRIVATE KEY----- diff --git a/tools/sync_test_server/keys/public.pem b/tools/sync_test_server/keys/public.pem deleted file mode 100644 index 8f81325947..0000000000 --- a/tools/sync_test_server/keys/public.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo65ZQ8mFIVk0ZB22bHdN -uBr4G3K7SwfFlUhmBmjQFb2EdopAnmu/XdXn+Zw3pmYlzIxe+3RX9M4eh8luIil0 -J2Nlb7tHOPZQkQAiuuud8JQ9RTNDixUQTGS4YmhyQv+7LA9cjdaczD3Bf3Nw/yZQ -TQQqw7vsbTUJeAMz+6EFLeMj2LxnZVLP7ePezxaSpKiQ8mp7eurQrZIqmvEC1xp8 -a7XvkgtqnMBepauIBiw6Wlpin0lPD3P5uMtrP+z5MmQXpP/GOp6XjBlULMQAMH/V -03WYMwnevzMWmKhF2apepnjd65nBh29iaFuiE8tYvKpJxmrsmoU+aOvMt3ZORxj7 -LQIDAQAB ------END PUBLIC KEY----- diff --git a/tools/sync_test_server/keys/test_token.json b/tools/sync_test_server/keys/test_token.json deleted file mode 100644 index 8d043f2fb7..0000000000 --- a/tools/sync_test_server/keys/test_token.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "identity": "test2", - "access": [ - "download", - "upload" - ], - "timestamp": 1455530614, - "expires": null, - "app_id": "io.realm.tests.sync" -} - From ddf75c9a1a1b662243dc336f16bb8d48e8d093e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 25 May 2020 14:37:21 +0200 Subject: [PATCH 1535/2110] Migrate and reenable SyncedRealmMigrationTests in Kotlin (#6861) --- .../io/realm/SyncedRealmMigrationTests.java | 322 ------------------ .../io/realm/SyncedRealmMigrationTests.kt | 281 +++++++++++++++ .../kotlin/io/realm/util/KotlinTestUtils.kt | 17 +- .../io/realm/util/KotlinTestUtilsTests.kt | 62 ++++ 4 files changed, 358 insertions(+), 324 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtilsTests.kt diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java deleted file mode 100644 index cf7b120837..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmMigrationTests.java +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.hamcrest.CoreMatchers; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.List; - -import io.realm.entities.IndexedFields; -import io.realm.entities.PrimaryKeyAsString; -import io.realm.entities.StringOnly; -import io.realm.internal.OsObjectSchemaInfo; -import io.realm.internal.OsRealmConfig; -import io.realm.internal.OsSchemaInfo; -import io.realm.internal.OsSharedRealm; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/** - * Testing methods around migrations for Realms using a {@link SyncConfiguration}. - */ -@Ignore("FIXME: RealmApp refactor") -@RunWith(AndroidJUnit4.class) -public class SyncedRealmMigrationTests { - - @Rule - public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - @Rule - public final ExpectedException thrown = ExpectedException.none(); - private TestRealmApp app; - - @BeforeClass - public static void beforeClass () { - // another Test class may have the BaseRealm.applicationContext set but - // the SyncManager reset. This will make assertion to fail, we need to re-initialise - // the sync_manager.cpp#m_file_manager (configFactory rule do this) - BaseRealm.applicationContext = null; - } - - @Before - public void setUp() { - app = new TestRealmApp(); - } - - @After - public void tearDown() { - if (app != null) { - RealmAppExtKt.close(app); - } - } - - @Test - public void migrateRealm_syncConfigurationThrows() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)).build(); - try { - Realm.migrateRealm(config); - fail(); - } catch (FileNotFoundException e) { - fail(e.toString()); - } catch (IllegalArgumentException ignored) { - } - } - - // Check that the Realm can still be opened even if the ondisk schema are missing fields. These will be added - // automatically. - @Test - public void addField_worksWithMigrationError() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) - .schema(StringOnly.class) - .build(); - - // Setup initial Realm schema (with missing fields) - String className = StringOnly.class.getSimpleName(); - DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); - RealmSchema schema = dynamicRealm.getSchema(); - dynamicRealm.beginTransaction(); - schema.create(className); // Create empty class - dynamicRealm.commitTransaction(); - dynamicRealm.close(); - - // Open typed Realm, which will validate the schema - Realm realm = Realm.getInstance(config); - RealmObjectSchema stringOnlySchema = realm.getSchema().get(className); - try { - assertTrue(stringOnlySchema.hasField(StringOnly.FIELD_CHARS)); // Field has been added - } finally { - realm.close(); - } - } - - // Check that the Realm can still be opened even if the ondisk schema has more fields than in the model class. - // The underlying field should not be deleted, just hidden. - @Test - public void missingFields_hiddenSilently() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) - .schema(StringOnly.class) - .build(); - - // Setup initial Realm schema (with too many fields) - String className = StringOnly.class.getSimpleName(); - DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); - RealmSchema schema = dynamicRealm.getSchema(); - dynamicRealm.beginTransaction(); - schema.create(className) - .addField(StringOnly.FIELD_CHARS, String.class) - .addField("newField", String.class); - // A schema version has to be set otherwise Object Store will try to initialize the schema again and reach an - // error branch. That is not a real case. - dynamicRealm.setVersion(0); - dynamicRealm.commitTransaction(); - dynamicRealm.close(); - - // Open typed Realm, which will validate the schema - Realm realm = Realm.getInstance(config); - RealmObjectSchema stringOnlySchema = realm.getSchema().get(className); - try { - assertTrue(stringOnlySchema.hasField(StringOnly.FIELD_CHARS)); - assertTrue(stringOnlySchema.hasField("newField")); - assertEquals(2, stringOnlySchema.getFieldNames().size()); - } finally { - realm.close(); - } - } - - // Check that a Realm cannot be opened if it contain breaking schema changes, like changing a primary key - @Test - public void breakingSchemaChange_throws() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) - .schema(PrimaryKeyAsString.class) - .build(); - - // Setup initial Realm schema (with a different primary key) - OsObjectSchemaInfo expectedObjectSchema = new OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME, 2, 0) - .addPersistedProperty(PrimaryKeyAsString.FIELD_PRIMARY_KEY, RealmFieldType.STRING, false, true, false) - .addPersistedProperty(PrimaryKeyAsString.FIELD_ID, RealmFieldType.INTEGER, true, true, true) - .build(); - List list = new ArrayList(); - list.add(expectedObjectSchema); - OsSchemaInfo schemaInfo = new OsSchemaInfo(list); - OsRealmConfig.Builder configBuilder = new OsRealmConfig.Builder(config).schemaInfo(schemaInfo); - OsSharedRealm.getInstance(configBuilder, OsSharedRealm.VersionID.LIVE).close(); - - thrown.expectMessage( - CoreMatchers.containsString("The following changes cannot be made in additive-only schema mode:")); - thrown.expect(IllegalStateException.class); - Realm.getInstance(config); - } - - // Check that indexes are not being added if the schema version is the same - @Test - public void sameSchemaVersion_doNotRebuildIndexes() { - - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) - .schema(IndexedFields.class) - .schemaVersion(42) - .build(); - - // Setup initial Realm schema (with no indexes) - String className = IndexedFields.class.getSimpleName(); - DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); - RealmSchema schema = dynamicRealm.getSchema(); - dynamicRealm.beginTransaction(); - schema.create(className) - .addField(IndexedFields.FIELD_INDEXED_STRING, String.class) // No index - .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String.class); - dynamicRealm.setVersion(42); - dynamicRealm.commitTransaction(); - dynamicRealm.close(); - - Realm realm = Realm.getInstance(config); // Opening at same schema version (42) will not rebuild indexes - - RealmObjectSchema indexedFieldsSchema = realm.getSchema().get(className); - try { - assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_INDEXED_STRING)); - assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_NON_INDEXED_STRING)); - } finally { - realm.close(); - } - } - - // Check that indexes are being added if the schema version is different - @Test - public void differentSchemaVersions_rebuildIndexes() { - - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) - .schema(IndexedFields.class) - .schemaVersion(42) - .build(); - - // Setup initial Realm schema (with no indexes) - String className = IndexedFields.class.getSimpleName(); - DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); - RealmSchema schema = dynamicRealm.getSchema(); - dynamicRealm.beginTransaction(); - schema.create(className) - .addField(IndexedFields.FIELD_INDEXED_STRING, String.class) // No index - .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String.class); - dynamicRealm.setVersion(43); - dynamicRealm.commitTransaction(); - dynamicRealm.close(); - - Realm realm = Realm.getInstance(config); // Opening at different schema version (42) should rebuild indexes - try { - RealmObjectSchema indexedFieldsSchema = realm.getSchema().get(className); - assertNotNull(indexedFieldsSchema); - assertTrue(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_INDEXED_STRING)); - assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_NON_INDEXED_STRING)); - } finally { - realm.close(); - } - } - - // Check that indexes are being added if other fields are being added as well - @Test - public void addingFields_rebuildIndexes() { - - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) - .schema(IndexedFields.class) - .schemaVersion(42) - .build(); - - // Setup initial Realm schema (with no indexes) - String className = IndexedFields.class.getSimpleName(); - DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); - RealmSchema schema = dynamicRealm.getSchema(); - dynamicRealm.beginTransaction(); - schema.create(className) - .addField(IndexedFields.FIELD_INDEXED_STRING, String.class); // No index - // .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String.class); // Missing field - dynamicRealm.setVersion(41); - dynamicRealm.commitTransaction(); - dynamicRealm.close(); - - // Opening at different schema version (42) should add field and rebuild indexes - Realm realm = Realm.getInstance(config); - try { - assertTrue(realm.getSchema().get(className).hasField(IndexedFields.FIELD_NON_INDEXED_STRING)); - assertTrue(realm.getSchema().get(className).hasIndex(IndexedFields.FIELD_INDEXED_STRING)); - } finally { - realm.close(); - } - } - - @Test - public void schemaVersionUpgradedWhenMigrating() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) - .schemaVersion(42) - .build(); - - // Setup initial Realm schema (with missing fields) - String className = StringOnly.class.getSimpleName(); - DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); - RealmSchema schema = dynamicRealm.getSchema(); - dynamicRealm.beginTransaction(); - schema.create(className); // Create empty class - dynamicRealm.setVersion(1); - dynamicRealm.commitTransaction(); - dynamicRealm.close(); - - // Open typed Realm, which will validate the schema - Realm realm = Realm.getInstance(config); - try { - assertEquals(42, realm.getVersion()); - } finally { - realm.close(); - } - } - - // The remote Realm containing more field than the local typed Realm defined is allowed. - @Test - public void moreFieldsThanExpectedIsAllowed() { - SyncConfiguration config = configFactory - .createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) - .schema(StringOnly.class) - .build(); - - // Initialize schema - Realm.getInstance(config).close(); - DynamicRealm dynamicRealm = DynamicRealm.getInstance(config); - dynamicRealm.beginTransaction(); - RealmObjectSchema objectSchema = dynamicRealm.getSchema().get(StringOnly.CLASS_NAME); - // Add one extra field which doesn't exist in the typed Realm. - objectSchema.addField("oneMoreField", int.class); - dynamicRealm.commitTransaction(); - // Clear column keys cache. - dynamicRealm.close(); - - // Verify schema again. - Realm realm = Realm.getInstance(config); - realm.close(); - } -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt new file mode 100644 index 0000000000..4d1b63393a --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt @@ -0,0 +1,281 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.SyncTestUtils.Companion.createTestUser +import io.realm.entities.IndexedFields +import io.realm.entities.PrimaryKeyAsString +import io.realm.entities.StringOnly +import io.realm.internal.OsObjectSchemaInfo +import io.realm.internal.OsRealmConfig +import io.realm.internal.OsSchemaInfo +import io.realm.internal.OsSharedRealm +import io.realm.util.assertFailsWithMessage +import org.hamcrest.CoreMatchers +import org.junit.* +import org.junit.Assert.* +import org.junit.runner.RunWith +import kotlin.test.assertFailsWith + +/** + * Testing methods around migrations for Realms using a [SyncConfiguration]. + */ +@RunWith(AndroidJUnit4::class) +class SyncedRealmMigrationTests { + + @get:Rule + val configFactory = TestSyncConfigurationFactory() + + private lateinit var app: TestRealmApp + + @Before + fun setUp() { + app = TestRealmApp() + } + + @After + fun tearDown() { + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun migrateRealm_syncConfigurationThrows() { + val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)).build() + assertFailsWith { + Realm.migrateRealm(config) + } + } + + // Check that the Realm can still be opened even if the ondisk schema are missing fields. These will be added + // automatically. + @Test + fun addField_worksWithMigrationError() { + val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) + .schema(StringOnly::class.java) + .build() + + // Setup initial Realm schema (with missing fields) + val className = StringOnly::class.java.simpleName + DynamicRealm.getInstance(config).use { dynamicRealm -> + val schema = dynamicRealm.schema + dynamicRealm.executeTransaction { + schema.create(className) // Create empty class + } + } + + // Open typed Realm, which will validate the schema + Realm.getInstance(config).use { realm -> + assertTrue(realm.schema[className]!!.hasField(StringOnly.FIELD_CHARS)) // Field has been added + } + } + + // Check that the Realm can still be opened even if the ondisk schema has more fields than in the model class. + // The underlying field should not be deleted, just hidden. + @Test + fun missingFields_hiddenSilently() { + val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) + .schema(StringOnly::class.java) + .build() + + // Setup initial Realm schema (with too many fields) + val className = StringOnly::class.java.simpleName + DynamicRealm.getInstance(config).use { dynamicRealm -> + val schema = dynamicRealm.schema + dynamicRealm.executeTransaction { + schema.create(className) + .addField(StringOnly.FIELD_CHARS, String::class.java) + .addField("newField", String::class.java) + // A schema version has to be set otherwise Object Store will try to initialize the schema again and reach an + // error branch. That is not a real case. + dynamicRealm.version = 0 + } + } + + // Open typed Realm, which will validate the schema + Realm.getInstance(config).use { realm -> + val stringOnlySchema = realm.schema[className]!! + assertTrue(stringOnlySchema.hasField(StringOnly.FIELD_CHARS)) + assertTrue(stringOnlySchema.hasField("newField")) + assertEquals(2, stringOnlySchema.fieldNames.size.toLong()) + } + } + + // Check that a Realm cannot be opened if it contain breaking schema changes, like changing a primary key + @Test + fun breakingSchemaChange_throws() { + val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) + .schema(PrimaryKeyAsString::class.java) + .build() + + // Setup initial Realm schema (with a different primary key) + val expectedObjectSchema = OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME, 2, 0) + .addPersistedProperty(PrimaryKeyAsString.FIELD_PRIMARY_KEY, RealmFieldType.STRING, false, true, false) + .addPersistedProperty(PrimaryKeyAsString.FIELD_ID, RealmFieldType.INTEGER, true, true, true) + .build() + val schemaInfo = OsSchemaInfo(listOf(expectedObjectSchema)) + val configBuilder = OsRealmConfig.Builder(config).schemaInfo(schemaInfo) + OsSharedRealm.getInstance(configBuilder, OsSharedRealm.VersionID.LIVE).close() + assertFailsWithMessage( + CoreMatchers.containsString("The following changes cannot be made in additive-only schema mode:") + ) { + Realm.getInstance(config).close() + } + } + + // Check that indexes are not being added if the schema version is the same + @Test + fun sameSchemaVersion_doNotRebuildIndexes() { + val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) + .schema(IndexedFields::class.java) + .schemaVersion(42) + .build() + + // Setup initial Realm schema (with no indexes) + val className = IndexedFields::class.java.simpleName + + DynamicRealm.getInstance(config).use { dynamicRealm -> + val schema = dynamicRealm.schema + dynamicRealm.executeTransaction { + schema.create(className) + .addField(IndexedFields.FIELD_INDEXED_STRING, String::class.java) // No index + .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String::class.java) + dynamicRealm.version = 42 + } + } + + Realm.getInstance(config).use { realm -> + // Opening at same schema version (42) will not rebuild indexes + val indexedFieldsSchema = realm.schema[className]!! + assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_INDEXED_STRING)) + assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_NON_INDEXED_STRING)) + } + } + + // Check that indexes are being added if the schema version is different + @Test + fun differentSchemaVersions_rebuildIndexes() { + val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) + .schema(IndexedFields::class.java) + .schemaVersion(42) + .build() + + // Setup initial Realm schema (with no indexes) + val className = IndexedFields::class.java.simpleName + DynamicRealm.getInstance(config).use { dynamicRealm -> + val schema = dynamicRealm.schema + dynamicRealm.executeTransaction { + schema.create(className) + .addField(IndexedFields.FIELD_INDEXED_STRING, String::class.java) // No index + .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String::class.java) + dynamicRealm.version = 43 + } + } + + Realm.getInstance(config).use {realm -> + // Opening at different schema version (42) should rebuild indexes + val indexedFieldsSchema = realm.schema[className]!! + assertNotNull(indexedFieldsSchema) + assertTrue(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_INDEXED_STRING)) + assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_NON_INDEXED_STRING)) + } + } + + // Check that indexes are being added if other fields are being added as well + @Test + fun addingFields_rebuildIndexes() { + val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) + .schema(IndexedFields::class.java) + .schemaVersion(42) + .build() + + // Setup initial Realm schema (with no indexes) + val className = IndexedFields::class.java.simpleName + DynamicRealm.getInstance(config).use { dynamicRealm -> + val schema = dynamicRealm.schema + dynamicRealm.executeTransaction { + schema.create(className) + .addField(IndexedFields.FIELD_INDEXED_STRING, String::class.java) // No index + // .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String.class); // Missing field + dynamicRealm.version = 41 + } + } + + // Opening at different schema version (42) should add field and rebuild indexes + Realm.getInstance(config).use { realm -> + val realmObjectSchema = realm.schema[className]!! + assertTrue(realmObjectSchema.hasField(IndexedFields.FIELD_NON_INDEXED_STRING)) + assertTrue(realmObjectSchema.hasIndex(IndexedFields.FIELD_INDEXED_STRING)) + } + } + + @Test + fun schemaVersionUpgradedWhenMigrating() { + val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) + .schemaVersion(42) + .build() + + // Setup initial Realm schema (with missing fields) + DynamicRealm.getInstance(config).use { dynamicRealm -> + val className = StringOnly::class.java.simpleName + val schema = dynamicRealm.schema + dynamicRealm.executeTransaction { + schema.create(className) // Create empty class + dynamicRealm.version = 1 + } + } + + // Open typed Realm, which will validate the schema + Realm.getInstance(config).use { realm -> + assertEquals(42, realm.version) + } + } + + // The remote Realm containing more field than the local typed Realm defined is allowed. + @Test + fun moreFieldsThanExpectedIsAllowed() { + val config = configFactory + .createSyncConfigurationBuilder(createTestUser(app)) + .schema(StringOnly::class.java) + .build() + + // Initialize schema + Realm.getInstance(config).close() + DynamicRealm.getInstance(config).use { dynamicRealm -> + dynamicRealm.executeTransaction { + val objectSchema = dynamicRealm.schema[StringOnly.CLASS_NAME]!! + // Add one extra field which doesn't exist in the typed Realm. + objectSchema.addField("oneMoreField", Integer::class.java) + } + // Column keys cache are cleared when closing + } + + // Verify schema again. + Realm.getInstance(config).close() + } + + companion object { + @BeforeClass + fun beforeClass() { + // another Test class may have the BaseRealm.applicationContext set but + // the SyncManager reset. This will make assertion to fail, we need to re-initialise + // the sync_manager.cpp#m_file_manager (configFactory rule do this) + BaseRealm.applicationContext = null + } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt index 411d8e5e1a..6f5df18a3b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt @@ -2,9 +2,10 @@ package io.realm.util import io.realm.ErrorCode import io.realm.ObjectServerError -import org.junit.Assert.assertEquals -import org.junit.Assert.fail +import org.hamcrest.Matcher +import org.junit.Assert.* import org.junit.rules.ErrorCollector +import kotlin.test.assertFailsWith // Helper methods for improving Kotlin unit tests. @@ -29,3 +30,15 @@ inline fun ErrorCollector.assertFailsWith(block : () -> Unit){ } } } + +inline fun assertFailsWithMessage(matcher: Matcher, block : () -> Unit){ + try { + block() + fail("assertFailsWithMessage completed without expected exception") + } catch (e : Exception) { + if (e !is T) { + throw AssertionError("assertFailsWithMessage did not throw expected exception: " + T::class.java.name) + } + assertThat(e.message, matcher) + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtilsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtilsTests.kt new file mode 100644 index 0000000000..47aee7c8a8 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtilsTests.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.util + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.hamcrest.CoreMatchers +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.RuntimeException + +@RunWith(AndroidJUnit4::class) +class KotlinTestUtilsTests { + + @Test + fun assertFailsWithMessage_noException() { + kotlin.test.assertFailsWith { + assertFailsWithMessage(CoreMatchers.anything()) { } + } + } + + @Test + fun assertFailsWithMessage_wrongException() { + kotlin.test.assertFailsWith { + assertFailsWithMessage(CoreMatchers.anything()) { + throw RuntimeException() + } + } + } + + @Test + fun assertFailsWithMessage_nonMatchingMessage() { + val message= "Exception error messages" + kotlin.test.assertFailsWith { + assertFailsWithMessage(CoreMatchers.equalTo("")) { + RuntimeException("Exception error messages") + } + } + } + + @Test + fun assertFailsWithMessage_matchingMessage() { + val message= "Exception error messages" + assertFailsWithMessage(CoreMatchers.equalTo(message)) { + throw RuntimeException(message) + } + } + +} From a864f091e3ed92e9a172b1393bb594521a13c440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Mon, 25 May 2020 16:55:38 +0200 Subject: [PATCH 1536/2110] Merge Stitch and Realm SDKs - 4: remote collection update- and findOneAnd- (#6869) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First iteration: added GMS library (possibly temporarily) to avoid introducing immediate breaking changes in how we process asynchronous operations with AsyncRealmTask. All original Stitch interfaces and proxies have been discarded in favour of Java classes (although this approach might be changed). Some interfaces connected to the collection's iterables have been omitted as it is unclear whether they will be needed or not for the time being. * Added licences to class headers plus a bit of cleanup * Added latest API methods and necessary classes * Added remote mongo client and remote database, their respective Os files and part of the native logic * Moved JNI callbacks outside RealmApp and added more remote collection classes * Updated object store branch to v10 and fixed wrong use of count call * Cleanup * Added task-related classes from Stitch * First steps towards using tasks for the count operation * Added preliminary collection test, only with scaffolding for "count", but still not working as the OS code isn't fully ready yet. Moved Realm initialisation in test cases outside TestRealmApp to setUp method as agreed internally, plus fixed some wrong implementation in the interop layer. Also updated dependencies list to fetch sync version 10 alpha 9 instead of 8. * Fixed wrong finalizer methods and cleanup to interop files * Moved TaskUtils to tests * test * cleanup * Moved classes * Updated OS pointer * Moved classes * Removed duplicate entries in CMakeLists * Added documentClass property to internal collection class * updated OS pointer * updated OS pointer * added suppresswarnings for ignored futures - issue inherited from Stitch's task framework - test to see if Jenkins swallows it * wip * Added test for Task.blockingGet and wip on insertOne * Added insertmany * Added more meaningful tests for count and insert. Temporarily commented out some code in EmailPasswordAuth.cpp after updating OS to v10. Now the remoteMongoClient is fetched as a shared_ptr in our interop layer. Added codec handling for RemoteMongoDatabase and document class for RemoteMongoCollection * Work in progress - insertMany and interop * Added deleteOne * Added deleteMany and adjusted visibility of OS constructors * wip * Updated pointer to OS * Restored curly braces * Removed unnecessary codec parameter in getDatabase * Added findOne and proper use of the BSON parsing protocol for handling, delivering and decoding results from the JNI * Updated OS pointer to branch that contains parsing fixes - update to OS v10 as soon as it is merged * Added missing findOne implementations and updated OS pointer * fixed unboxing that caused findbugs to complain * First batch of cleanup * Addressed error handling in interop layer plus more cleanup * Moved classes to new packages and removed "remote" prefix from class names * Restored wrongly removed public modifier to method * Renamed OS interop classes * Cleanup * Final round of cleanup and updated pointer to OS that allegedly fixes failing stress test * Fixed broken tests * Added updateOne * Renamed missing "remote" options and remote classes and added updateMany * Added findOneAndUpdate * Added another findOneAndUpdate variant * Removed duplicated code and added findOneAndReplace and Delete variants * Updated OS pointer * Reverted OS pointer * Added find * Removed unnecessary classes plus added options to find * A bit more cleanup * Removed unnecessary roundtrip to parser to decode a collection, added parsing guards in native code, added more visible fixmes in test file Co-authored-by: Eduardo López --- .../io/realm/mongodb/MongoCollectionTest.kt | 236 ++++++++- .../realm-library/src/main/cpp/CMakeLists.txt | 6 +- ...internal_objectstore_OsMongoCollection.cpp | 302 ++++++++++- realm/realm-library/src/main/cpp/object-store | 2 +- .../realm/internal/jni/JniBsonProtocol.java | 1 - .../objectstore/OsMongoCollection.java | 483 ++++++++++++++---- .../realm/mongodb/mongo/MongoCollection.java | 186 ++++--- .../iterable/RemoteAggregateIterable.java | 28 - .../mongo/iterable/RemoteFindIterable.java | 71 --- ...oteCountOptions.java => CountOptions.java} | 4 +- ...ions.java => FindOneAndModifyOptions.java} | 10 +- ...emoteFindOptions.java => FindOptions.java} | 10 +- ...tManyResult.java => InsertManyResult.java} | 4 +- ...eUpdateOptions.java => UpdateOptions.java} | 4 +- ...oteDeleteResult.java => DeleteResult.java} | 4 +- ...ertOneResult.java => InsertOneResult.java} | 4 +- ...oteUpdateResult.java => UpdateResult.java} | 4 +- 17 files changed, 1028 insertions(+), 331 deletions(-) delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteAggregateIterable.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteFindIterable.java rename realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/{RemoteCountOptions.java => CountOptions.java} (93%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/{RemoteFindOneAndModifyOptions.java => FindOneAndModifyOptions.java} (91%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/{RemoteFindOptions.java => FindOptions.java} (90%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/{RemoteInsertManyResult.java => InsertManyResult.java} (92%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/{RemoteUpdateOptions.java => UpdateOptions.java} (93%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/{RemoteDeleteResult.java => DeleteResult.java} (92%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/{RemoteInsertOneResult.java => InsertOneResult.java} (91%) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/{RemoteUpdateResult.java => UpdateResult.java} (97%) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt index d3433dfae1..3bb7c81483 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt @@ -21,16 +21,18 @@ import io.realm.* import io.realm.mongodb.mongo.MongoClient import io.realm.mongodb.mongo.MongoCollection import io.realm.mongodb.mongo.MongoDatabase -import io.realm.mongodb.mongo.options.RemoteCountOptions +import io.realm.mongodb.mongo.options.CountOptions +import io.realm.mongodb.mongo.options.FindOneAndModifyOptions +import io.realm.mongodb.mongo.options.UpdateOptions import io.realm.util.blockingGetResult import org.bson.Document import org.bson.types.ObjectId import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith -import kotlin.test.assertEquals -import kotlin.test.assertNull +import kotlin.test.* private const val SERVICE_NAME = "BackingDB" // it comes from the test server's BackingDB/config.json private const val DATABASE_NAME = "test_data" // same as above @@ -71,19 +73,19 @@ class MongoCollectionTest { fun insertOne() { with(getCollectionInternal(COLLECTION_NAME)) { assertEquals(0, count().blockingGetResult()) - val doc = Document(mapOf("KEY_1" to "WORLD_1", "KEY_2" to "WORLD_2")) - insertOne(doc).blockingGetResult() + val doc1 = Document(mapOf("hello_1" to "1", "hello_2" to "2")) + insertOne(doc1).blockingGetResult() assertEquals(1, count().blockingGetResult()) - // FIXME: revisit this later -// val doc = Document("hello", "world") -// doc["_id"] = ObjectId() + // FIXME: revisit when parser is fully operational +// val doc2 = Document("hello", "world") +// doc2["_id"] = ObjectId() // -// assertEquals(doc.getObjectId("_id"), insertOne(doc).blockingGetResult()!!.insertedId.asObjectId().value) -// assertFailsWith(ObjectServerError::class) { insertOne(doc).blockingGetResult() } +// assertEquals(doc2.getObjectId("_id"), insertOne(doc2).blockingGetResult()!!.insertedId.asObjectId().value) +// assertFailsWith(ObjectServerError::class) { insertOne(doc2).blockingGetResult() } // -// val doc2 = Document("hello", "world") -// assertNotEquals(doc.getObjectId("_id"), insertOne(doc2).blockingGetResult()!!.insertedId.asObjectId().value) +// val doc3 = Document("hello", "world") +// assertNotEquals(doc2.getObjectId("_id"), insertOne(doc3).blockingGetResult()!!.insertedId.asObjectId().value) } } @@ -125,7 +127,7 @@ class MongoCollectionTest { assertEquals(2, count(rawDoc).blockingGetResult()) assertEquals(0, count(Document("hello", "Friend")).blockingGetResult()) - assertEquals(1,count(rawDoc, RemoteCountOptions().limit(1)).blockingGetResult()) + assertEquals(1,count(rawDoc, CountOptions().limit(1)).blockingGetResult()) // FIXME: investigate error handling for malformed payloads // try { @@ -226,23 +228,23 @@ class MongoCollectionTest { // Test findOne() with filter that does not match any documents and no options assertNull(findOne(Document("hello", "worldDNE")).blockingGetResult()) - // FIXME: revisit this later -// // Insert 2 more documents into the collection -//// insertMany(listOf(doc2, doc3)).blockingGetResult() // use insertOne for now -// insertOne(doc2).blockingGetResult() -// insertOne(doc3).blockingGetResult() -// assertEquals(3, count().blockingGetResult()) -// + // FIXME: revisit when parser is fully operational + // Insert 2 more documents into the collection +// insertMany(listOf(doc2, doc3)).blockingGetResult() // use insertOne for now + insertOne(doc2).blockingGetResult() + insertOne(doc3).blockingGetResult() + assertEquals(3, count().blockingGetResult()) + // // test findOne() with projection and sort options // val projection = Document("hello", 1) // projection["_id"] = 0 -// val options1 = RemoteFindOptions() +// val options1 = FindOptions() // .limit(2) // .projection(projection) // .sort(Document("hello", 1)) // assertEquals(doc1, findOne(Document(), options1).blockingGetResult()!!.withoutId()) // -// val options2 = RemoteFindOptions() +// val options2 = FindOptions() // .limit(2) // .projection(projection) // .sort(Document("hello", -1)) @@ -260,6 +262,196 @@ class MongoCollectionTest { } } + @Test + fun updateOne() { + with(getCollectionInternal(COLLECTION_NAME)) { + val doc1 = Document("hello", "world") + val result1 = updateOne(Document(), doc1).blockingGetResult()!! + assertEquals(0, result1.matchedCount) + assertEquals(0, result1.modifiedCount) + assertNull(result1.upsertedId) + + val options2 = UpdateOptions().upsert(true) + val result2 = updateOne(Document(), doc1, options2).blockingGetResult()!! + assertEquals(0, result2.matchedCount) + assertEquals(0, result2.modifiedCount) + assertFalse(result2.upsertedId!!.isNull) + + val result3 = updateOne(Document(), Document("\$set", Document("woof", "meow"))).blockingGetResult()!! + assertEquals(1, result3.matchedCount) + assertEquals(1, result3.modifiedCount) + assertNull(result3.upsertedId) + + // FIXME: revisit when parser is fully operational +// val expectedDoc = Document("hello", "world") +// expectedDoc["woof"] = "meow" +// assertEquals(expectedDoc, withoutId(Tasks.await(coll.find(Document()).first()))) +// +// try { +// Tasks.await(coll.updateOne(Document("\$who", 1), Document())) +// fail() +// } catch (ex: ExecutionException) { +// assertTrue(ex.cause is StitchServiceException) +// val svcEx = ex.cause as StitchServiceException +// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) +// } + } + } + + @Test + fun updateMany() { + with(getCollectionInternal(COLLECTION_NAME)) { + val doc1 = Document("hello", "world") + val result1 = updateMany(Document(), doc1).blockingGetResult()!! + assertEquals(0, result1.matchedCount) + assertEquals(0, result1.modifiedCount) + assertNull(result1.upsertedId) + + val options2 = UpdateOptions().upsert(true) + val result2 = updateMany(Document(), doc1, options2).blockingGetResult()!! + assertEquals(0, result2.matchedCount) + assertEquals(0, result2.modifiedCount) + assertNotNull(result2.upsertedId) + + val result3 = updateMany(Document(), Document("\$set", Document("woof", "meow"))).blockingGetResult()!! + assertEquals(1, result3.matchedCount) + assertEquals(1, result3.modifiedCount) + assertNull(result3.upsertedId) + + insertOne(Document()).blockingGetResult() + val result4 = updateMany(Document(), Document("\$set", Document("woof", "meow"))).blockingGetResult()!! + assertEquals(2, result4.matchedCount) + assertEquals(2, result4.modifiedCount) + + // FIXME: revisit when parser is fully operational +// val expectedDoc1 = Document("hello", "world") +// expectedDoc1["woof"] = "meow" +// val expectedDoc2 = Document("woof", "meow") +// assertEquals(listOf(expectedDoc1, expectedDoc2), withoutIds(Tasks.await>(coll.find(Document()).into(mutableListOf())))) +// +// try { +// Tasks.await(coll.updateMany(Document("\$who", 1), Document())) +// fail() +// } catch (ex: ExecutionException) { +// assertTrue(ex.cause is StitchServiceException) +// val svcEx = ex.cause as StitchServiceException +// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) +// } + } + } + + @Test + @Ignore + // FIXME: revisit when parser is fully operational + fun findOneAndUpdate() { + with(getCollectionInternal(COLLECTION_NAME)) { + val sampleDoc = Document("hello", "world1") + sampleDoc["num"] = 2 + + // Collection should start out empty + // This also tests the null return format + assertNull(findOneAndUpdate(Document(), Document()).blockingGetResult()) + + // Insert a sample Document + insertOne(sampleDoc).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // Sample call to findOneAndUpdate() where we get the previous document back + val sampleUpdate = Document("\$set", Document("hello", "hellothere")) + sampleUpdate["\$inc"] = Document("num", 1) + assertEquals(sampleDoc.withoutId(), findOneAndUpdate(Document("hello", "world1"), sampleUpdate).blockingGetResult()) + assertEquals(1, count().blockingGetResult()) + + // Make sure the update took place + val expectedDoc = Document("hello", "hellothere") + expectedDoc["num"] = 3 +// assertEquals(expectedDoc.withoutId(), withoutId(Tasks.await(coll.find().first()))) + assertEquals(1, count().blockingGetResult()) + + // Call findOneAndUpdate() again but get the new document + sampleUpdate.remove("\$set") + expectedDoc["num"] = 4 + val result = findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, FindOneAndModifyOptions().returnNewDocument(true)).blockingGetResult() + assertEquals(expectedDoc.withoutId(), result!!.withoutId()) + assertEquals(1, count().blockingGetResult()) + + // Test null behaviour again with a filter that should not match any documents + assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).blockingGetResult()) + assertEquals(1, count().blockingGetResult()) + + val doc1 = Document("hello", "world1") + doc1["num"] = 1 + + val doc2 = Document("hello", "world2") + doc2["num"] = 2 + + val doc3 = Document("hello", "world3") + doc3["num"] = 3 + + // Test the upsert option where it should not actually be invoked + val result2 = findOneAndUpdate(Document("hello", "hellothere"), Document("\$set", doc1), FindOneAndModifyOptions().returnNewDocument(true).upsert(true)).blockingGetResult() + assertEquals(doc1, result2!!.withoutId()) + assertEquals(1, count().blockingGetResult()) +// assertEquals(doc1.withoutId(), withoutId(Tasks.await(coll.find().first()))) + + // Test the upsert option where the server should perform upsert and return new document + val result3 = findOneAndUpdate(Document("hello", "hellothere"), Document("\$set", doc2), FindOneAndModifyOptions().returnNewDocument(true).upsert(true)).blockingGetResult() + assertEquals(doc2, result3!!.withoutId()) + assertEquals(2, count().blockingGetResult()) + + // Test the upsert option where the server should perform upsert and return old document + // The old document should be empty + val result4 = findOneAndUpdate(Document("hello", "hellothere"), Document("\$set", doc3), FindOneAndModifyOptions().upsert(true)).blockingGetResult() + assertNull(result4) + assertEquals(3, count().blockingGetResult()) + + // Test sort and project +// assertEquals(listOf(doc1, doc2, doc3), +// withoutIds(Tasks.await>(coll.find().into(mutableListOf())))) + + val sampleProject = Document("hello", 1) + sampleProject["_id"] = 0 + + val result5 = findOneAndUpdate(Document(), sampleUpdate, FindOneAndModifyOptions().projection(sampleProject).sort(Document("num", 1))).blockingGetResult() + assertEquals(Document("hello", "world1"), result5!!.withoutId()) + assertEquals(3, count().blockingGetResult()) + + val result6 = findOneAndUpdate(Document(), sampleUpdate, FindOneAndModifyOptions().projection(sampleProject).sort(Document("num", -1))).blockingGetResult() + assertEquals(Document("hello", "world3"), result6!!.withoutId()) + assertEquals(3, count().blockingGetResult()) + + // Test proper failure +// try { +// Tasks.await(coll.findOneAndUpdate(Document(), Document("\$who", 1))) +// fail() +// } catch (ex: ExecutionException) { +// assertTrue(ex.cause is StitchServiceException) +// val svcEx = ex.cause as StitchServiceException +// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) +// } +// +// try { +// Tasks.await(coll.findOneAndUpdate(Document(), Document("\$who", 1), +// FindOneAndModifyOptions().upsert(true))) +// fail() +// } catch (ex: ExecutionException) { +// assertTrue(ex.cause is StitchServiceException) +// val svcEx = ex.cause as StitchServiceException +// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) +// } + } + } + + @Test + fun find() { + with(getCollectionInternal(COLLECTION_NAME)) { + // FIXME: fix find implementation - ignore this code for code review + val iter = find().blockingGetResult()!! + assertFalse(iter.iterator().hasNext()) + assertFailsWith { iter.first() } + } + } + // FIXME: more to come private fun getCollectionInternal(collectionName: String, javaClass: Class? = null): MongoCollection { diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 13f75d6d44..56bc9f1fc5 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -208,9 +208,9 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAsyncOpenTask.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAppCredentials.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoClient.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoCollection.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoDatabase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoClient.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoCollection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoDatabase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsSyncUser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/jni_util/bson_util.cpp ) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp index 4b560d8bc2..e1e7377fce 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp @@ -42,6 +42,7 @@ static std::function collection_mapper_count = [](JN return JavaClassGlobalDef::new_long(env, result); }; +// This mapper works for both findOne and findOneAndUpdate/Replace functions static std::function)> collection_mapper_find_one = [](JNIEnv* env, util::Optional document) { return document ? JniBsonProtocol::bson_to_jstring(env, *document) : NULL; }; @@ -69,6 +70,23 @@ static std::function)> collection_mapper_ return arr; }; +static std::function collection_mapper_update = [](JNIEnv* env, RemoteMongoCollection::RemoteUpdateResult result) { + Bson matched_count(result.matched_count); + Bson modified_count(result.modified_count); + Bson upserted_value; + if (result.upserted_id) { + upserted_value = new Bson(result.upserted_id.value()); + } + // FIXME: maybe not the most efficient way. Suggestions? + std::vector bson_vector = { matched_count, modified_count, upserted_value }; + Bson output(bson_vector); + return JniBsonProtocol::bson_to_jstring(env, output); +}; + +static std::function)> collection_mapper_find = [](JNIEnv* env, util::Optional array) { + return array ? JniBsonProtocol::bson_to_jstring(env, *array) : NULL; +}; + static void finalize_collection(jlong ptr) { delete reinterpret_cast(ptr); } @@ -88,10 +106,9 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeCount(JNIEnv* env, try { auto collection = reinterpret_cast(j_collection_ptr); - // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) - bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_filter)); + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); uint64_t limit = std::uint64_t(j_limit); - collection->count(bson_filter, limit, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); + collection->count(filter, limit, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); } CATCH_STD() } @@ -100,14 +117,13 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOne(JNIEnv* env, jclass, jlong j_collection_ptr, - jstring j_document, + jstring j_filter, jobject j_callback) { try { auto collection = reinterpret_cast(j_collection_ptr); - // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) - bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_document)); - collection->find_one(bson_filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + collection->find_one(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); } CATCH_STD() } @@ -125,17 +141,16 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneWithOptions(JN auto collection = reinterpret_cast(j_collection_ptr); uint64_t limit = std::uint64_t(j_limit); - // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) - bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_filter)); - bson::BsonDocument projection(JniBsonProtocol::jstring_to_bson(env, j_projection)); - bson::BsonDocument sort(JniBsonProtocol::jstring_to_bson(env, j_sort)); + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); RemoteMongoCollection::RemoteFindOptions options = { limit, projection, sort }; - collection->find_one(bson_filter, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + collection->find_one(filter, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); } CATCH_STD() } @@ -149,9 +164,8 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeInsertOne(JNIEnv* env try { auto collection = reinterpret_cast(j_collection_ptr); - // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) - bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_document)); - collection->insert_one(bson_filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_insert_one)); + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_document, Bson::Type::Document, "BSON document must be a Document")); + collection->insert_one(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_insert_one)); } CATCH_STD() } @@ -165,8 +179,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeInsertMany(JNIEnv* en try { auto collection = reinterpret_cast(j_collection_ptr); - // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) - BsonArray bson_array(JniBsonProtocol::jstring_to_bson(env, j_documents)); + BsonArray bson_array(JniBsonProtocol::parse_checked(env, j_documents, Bson::Type::Array, "BSON documents must be a BsonArray")); collection->insert_many(bson_array, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_insert_many)); } CATCH_STD() @@ -181,9 +194,8 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeDeleteOne(JNIEnv* env try { auto collection = reinterpret_cast(j_collection_ptr); - // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) - bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_document)); - collection->delete_one(bson_filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_document, Bson::Type::Document, "BSON document must be a Document")); + collection->delete_one(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); } CATCH_STD() } @@ -197,9 +209,253 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeDeleteMany(JNIEnv* en try { auto collection = reinterpret_cast(j_collection_ptr); - // FIXME: add guard agains wrongly encoded strings (e.g. due to using a bogus codec from Java) - bson::BsonDocument bson_filter(JniBsonProtocol::jstring_to_bson(env, j_document)); - collection->delete_many(bson_filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_document, Bson::Type::Document, "BSON document must be a Document")); + collection->delete_many(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdateOne(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_update, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); + collection->update_one(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdateOneWithOptions(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_update, + jboolean j_upsert, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); + collection->update_one(filter, update, to_bool(j_upsert), JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdateMany(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_update, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); + collection->update_many(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdateManyWithOptions(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_update, + jboolean j_upsert, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); + collection->update_many(filter, update, to_bool(j_upsert), JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndUpdate(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_update, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); + collection->find_one_and_update(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndUpdateWithOptions(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_update, + jstring j_projection, + jstring j_sort, + jboolean j_upsert, + jboolean j_return_new_document, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); + RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { + projection, + sort, + to_bool(j_upsert), + to_bool(j_return_new_document) + }; + collection->find_one_and_update(filter, update, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndReplace(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_update, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); + collection->find_one_and_update(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndReplaceWithOptions(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_update, + jstring j_projection, + jstring j_sort, + jboolean j_upsert, + jboolean j_return_new_document, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); + RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { + projection, + sort, + to_bool(j_upsert), + to_bool(j_return_new_document) + }; + collection->find_one_and_replace(filter, update, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndDelete(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + collection->find_one_and_delete(filter, JavaNetworkTransport::create_void_callback(env, j_callback)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndDeleteWithOptions(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_projection, + jstring j_sort, + jboolean j_upsert, + jboolean j_return_new_document, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); + RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { + projection, + sort, + to_bool(j_upsert), + to_bool(j_return_new_document) + }; + collection->find_one_and_delete(filter, options, JavaNetworkTransport::create_void_callback(env, j_callback)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFind(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + collection->find(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find)); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindWithOptions(JNIEnv *env, + jclass, + jlong j_collection_ptr, + jstring j_filter, + jstring j_projection, + jstring j_sort, + jlong j_limit, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + uint64_t limit = std::uint64_t(j_limit); + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); + RemoteMongoCollection::RemoteFindOptions options = { + limit, + projection, + sort + }; + collection->find(filter, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find)); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index ccd8b7f9a1..dee13524d2 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit ccd8b7f9a1ac20396aaa38ff9493360cfc40ed96 +Subproject commit dee13524d2863402238bdacbb0beb99ef74d970b diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java index ecdb840994..70ff2b8187 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java @@ -71,5 +71,4 @@ public static T decode(String string, Decoder decoder) { jsonReader.readEndDocument(); return value; } - } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java index 72853cb80d..fcaee9bed0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java @@ -16,6 +16,8 @@ package io.realm.internal.objectstore; +import org.bson.BsonArray; +import org.bson.BsonNull; import org.bson.BsonObjectId; import org.bson.BsonValue; import org.bson.Document; @@ -23,6 +25,8 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,14 +36,17 @@ import io.realm.ObjectServerError; import io.realm.internal.NativeObject; -import io.realm.internal.network.ResultHandler; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; -import io.realm.mongodb.mongo.options.RemoteCountOptions; -import io.realm.mongodb.mongo.result.RemoteDeleteResult; -import io.realm.mongodb.mongo.options.RemoteFindOptions; -import io.realm.mongodb.mongo.options.RemoteInsertManyResult; -import io.realm.mongodb.mongo.result.RemoteInsertOneResult; +import io.realm.internal.network.ResultHandler; +import io.realm.mongodb.mongo.options.CountOptions; +import io.realm.mongodb.mongo.options.FindOneAndModifyOptions; +import io.realm.mongodb.mongo.options.FindOptions; +import io.realm.mongodb.mongo.options.InsertManyResult; +import io.realm.mongodb.mongo.options.UpdateOptions; +import io.realm.mongodb.mongo.result.DeleteResult; +import io.realm.mongodb.mongo.result.InsertOneResult; +import io.realm.mongodb.mongo.result.UpdateResult; public class OsMongoCollection implements NativeObject { @@ -73,7 +80,7 @@ public Long count(@Nullable final Bson filter) { return count(filter, null); } - public Long count(@Nullable final Bson filter, @Nullable final RemoteCountOptions options) { + public Long count(@Nullable final Bson filter, @Nullable final CountOptions options) { AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @@ -94,108 +101,96 @@ protected Long mapSuccess(Object result) { return ResultHandler.handleResult(success, error); } - public DocumentT findOne() { - return findOne(new Document()); + public Collection find() { + return find(new Document()); } - public ResultT findOne(final Class resultClass) { - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { - @Override - protected ResultT mapSuccess(Object result) { - return findOneSuccessMapper(result, resultClass); - } - }; + public Collection find(final FindOptions options) { + return find(new Document(), options); + } - nativeFindOne(nativePtr, JniBsonProtocol.encode(new Document(), codecRegistry), callback); + public Collection find(final Class resultClass) { + return find(new Document(), resultClass); + } - return ResultHandler.handleResult(success, error); + public Collection find(final Class resultClass, final FindOptions options) { + return find(new Document(), resultClass, options); } - public DocumentT findOne(final Bson filter) { - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { - @Override - protected DocumentT mapSuccess(Object result) { - return findOneSuccessMapper(result, documentClass); - } - }; + public Collection find(final Bson filter) { + return find(filter, documentClass); + } - String encodedFilter = JniBsonProtocol.encode(filter, codecRegistry); - nativeFindOne(nativePtr, encodedFilter, callback); + public Collection find(final Bson filter, final FindOptions options) { + return find(filter, documentClass, options); + } - return ResultHandler.handleResult(success, error); + public Collection find(final Bson filter, final Class resultClass) { + return find(filter, resultClass, null); } - public ResultT findOne(final @Nullable Bson filter, final Class resultClass) { - AtomicReference success = new AtomicReference<>(null); + // FIXME: fix find implementation - ignore this code for code review + public Collection find(final Bson filter, + final Class resultClass, + @Nullable final FindOptions options) { + AtomicReference> success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + OsJNIResultCallback> callback = new OsJNIResultCallback>(success, error) { @Override - protected ResultT mapSuccess(Object result) { - return findOneSuccessMapper(result, resultClass); + @SuppressWarnings("unchecked") + protected Collection mapSuccess(Object result) { + return JniBsonProtocol.decode((String) result, Collection.class, codecRegistry); } }; - String encodedFilter = filter == null ? - JniBsonProtocol.encode(new Document(), codecRegistry) : - JniBsonProtocol.encode(filter, codecRegistry); - nativeFindOne(nativePtr, encodedFilter, callback); + String filterString = JniBsonProtocol.encode(filter, codecRegistry); + + if (options == null) { + nativeFind(nativePtr, filterString, callback); + } else { + String projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); + String sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); + + nativeFindWithOptions(nativePtr, filterString, projectionString, sortString, options.getLimit(), callback); + } return ResultHandler.handleResult(success, error); } - public DocumentT findOne(@Nullable final Bson filter, final RemoteFindOptions options) { - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { - @Override - protected DocumentT mapSuccess(Object result) { - return findOneSuccessMapper(result, documentClass); - } - }; + public DocumentT findOne() { + return findOne(new Document()); + } - String encodedFilter = filter == null ? - JniBsonProtocol.encode(new Document(), codecRegistry) : - JniBsonProtocol.encode(filter, codecRegistry); - String projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); - String sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); - nativeFindOneWithOptions(nativePtr, encodedFilter, projectionString, sortString, options.getLimit(), callback); + public ResultT findOne(final Class resultClass) { + return findOne(null, resultClass); + } - return ResultHandler.handleResult(success, error); + public DocumentT findOne(final Bson filter) { + return findOne(filter, documentClass); } - public ResultT findOne( - final Bson filter, - final RemoteFindOptions options, - final Class resultClass) { - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { - @Override - protected ResultT mapSuccess(Object result) { - return findOneSuccessMapper(result, resultClass); - } - }; + public ResultT findOne(final @Nullable Bson filter, final Class resultClass) { + return findOneInternal(filter, null, resultClass); + } - String encodedFilter = JniBsonProtocol.encode(filter, codecRegistry); - String projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); - String sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); - nativeFindOneWithOptions(nativePtr, encodedFilter, projectionString, sortString, options.getLimit(), callback); + public DocumentT findOne(@Nullable final Bson filter, final FindOptions options) { + return findOne(filter, options, documentClass); + } - return ResultHandler.handleResult(success, error); + public ResultT findOne(@Nullable final Bson filter, + final FindOptions options, + final Class resultClass) { + return findOneInternal(filter, options, resultClass); } - public RemoteInsertOneResult insertOne(final DocumentT document) { - AtomicReference success = new AtomicReference<>(null); + public InsertOneResult insertOne(final DocumentT document) { + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override - protected RemoteInsertOneResult mapSuccess(Object result) { + protected InsertOneResult mapSuccess(Object result) { BsonValue bsonObjectId = new BsonObjectId((ObjectId) result); - return new RemoteInsertOneResult(bsonObjectId); + return new InsertOneResult(bsonObjectId); } }; @@ -204,12 +199,12 @@ protected RemoteInsertOneResult mapSuccess(Object result) { return ResultHandler.handleResult(success, error); } - public RemoteInsertManyResult insertMany(final List documents) { - AtomicReference success = new AtomicReference<>(null); + public InsertManyResult insertMany(final List documents) { + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override - protected RemoteInsertManyResult mapSuccess(Object result) { + protected InsertManyResult mapSuccess(Object result) { Object[] objects = (Object[]) result; Map insertedIdsMap = new HashMap<>(); for (int i = 0; i < objects.length; i++) { @@ -217,7 +212,7 @@ protected RemoteInsertManyResult mapSuccess(Object result) { BsonValue bsonObjectId = new BsonObjectId(objectId); insertedIdsMap.put((long) i, bsonObjectId); } - return new RemoteInsertManyResult(insertedIdsMap); + return new InsertManyResult(insertedIdsMap); } }; @@ -226,37 +221,248 @@ protected RemoteInsertManyResult mapSuccess(Object result) { return ResultHandler.handleResult(success, error); } - public RemoteDeleteResult deleteOne(final Bson filter) { - AtomicReference success = new AtomicReference<>(null); + public DeleteResult deleteOne(final Bson filter) { + return deleteInternal(DeleteType.ONE, filter); + } + + public DeleteResult deleteMany(final Bson filter) { + return deleteInternal(DeleteType.MANY, filter); + } + + public UpdateResult updateOne(final Bson filter, final Bson update) { + return updateOne(filter, update, null); + } + + public UpdateResult updateOne(final Bson filter, + final Bson update, + @Nullable final UpdateOptions options) { + return updateInternal(UpdateType.ONE, filter, update, options); + } + + public UpdateResult updateMany(final Bson filter, final Bson update) { + return updateMany(filter, update, null); + } + + public UpdateResult updateMany(final Bson filter, + final Bson update, + @Nullable final UpdateOptions options) { + return updateInternal(UpdateType.MANY, filter, update, options); + } + + public DocumentT findOneAndUpdate(final Bson filter, final Bson update) { + return findOneAndUpdate(filter, update, documentClass); + } + + public ResultT findOneAndUpdate(final Bson filter, + final Bson update, + final Class resultClass) { + return findOneAndInternal(FindOneAndType.UPDATE, filter, update, null, resultClass); + } + + public DocumentT findOneAndUpdate(final Bson filter, + final Bson update, + final FindOneAndModifyOptions options) { + return findOneAndUpdate(filter, update, options, documentClass); + } + + public ResultT findOneAndUpdate(final Bson filter, + final Bson update, + final FindOneAndModifyOptions options, + final Class resultClass) { + return findOneAndInternal(FindOneAndType.UPDATE, filter, update, options, resultClass); + } + + public DocumentT findOneAndReplace(final Bson filter, final Bson replacement) { + return findOneAndReplace(filter, replacement, documentClass); + } + + public ResultT findOneAndReplace(final Bson filter, + final Bson update, + final Class resultClass) { + return findOneAndInternal(FindOneAndType.REPLACE, filter, update, null, resultClass); + } + + public DocumentT findOneAndReplace(final Bson filter, + final Bson update, + final FindOneAndModifyOptions options) { + return findOneAndReplace(filter, update, options, documentClass); + } + + public ResultT findOneAndReplace(final Bson filter, + final Bson update, + final FindOneAndModifyOptions options, + final Class resultClass) { + return findOneAndInternal(FindOneAndType.REPLACE, filter, update, options, resultClass); + } + + public DocumentT findOneAndDelete(final Bson filter) { + return findOneAndDelete(filter, documentClass); + } + + public ResultT findOneAndDelete(final Bson filter, + final Class resultClass) { + return findOneAndDeleteInternal(filter, null, resultClass); + } + + public DocumentT findOneAndDelete(final Bson filter, + final FindOneAndModifyOptions options) { + return findOneAndDeleteInternal(filter, options, documentClass); + } + + public ResultT findOneAndDelete(final Bson filter, + final FindOneAndModifyOptions options, + final Class resultClass) { + return findOneAndDeleteInternal(filter, options, resultClass); + } + + private UpdateResult updateInternal(UpdateType type, final Bson filter, final Bson update, @Nullable final UpdateOptions options) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected UpdateResult mapSuccess(Object result) { + // FIXME: see OsMongoCollection.cpp - collection_mapper_update. There surely is a better way to do this + BsonArray array = JniBsonProtocol.decode((String) result, BsonArray.class, codecRegistry); + long matchedCount = array.get(0).asInt32().getValue(); + long modifiedCount = array.get(1).asInt32().getValue(); + + // FIXME: this seems ugly, but Stitch allows retuning null for upsertedId + BsonValue upsertedId = array.get(2); + if (upsertedId instanceof BsonNull) { + upsertedId = null; + } + return new UpdateResult(matchedCount, modifiedCount, upsertedId); + } + }; + + String jsonFilter = JniBsonProtocol.encode(filter, codecRegistry); + String jsonUpdate = JniBsonProtocol.encode(update, codecRegistry); + + switch (type) { + case ONE: + if (options == null) { + nativeUpdateOne(nativePtr, jsonFilter, jsonUpdate, callback); + } else { + nativeUpdateOneWithOptions(nativePtr, jsonFilter, jsonUpdate, options.isUpsert(), callback); + } + break; + case MANY: + if (options == null) { + nativeUpdateMany(nativePtr, jsonFilter, jsonUpdate, callback); + } else { + nativeUpdateManyWithOptions(nativePtr, jsonFilter, jsonUpdate, options.isUpsert(), callback); + } + break; + } + return ResultHandler.handleResult(success, error); + } + + private ResultT findOneInternal(@Nullable final Bson filter, + @Nullable final FindOptions options, + final Class resultClass) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected ResultT mapSuccess(Object result) { + return findSuccessMapper(result, resultClass); + } + }; + + String encodedFilter = (filter == null) ? + JniBsonProtocol.encode(new Document(), codecRegistry) : + JniBsonProtocol.encode(filter, codecRegistry); + if (options == null) { + nativeFindOne(nativePtr, encodedFilter, callback); + } else { + String projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); + String sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); + + nativeFindOneWithOptions(nativePtr, encodedFilter, projectionString, sortString, options.getLimit(), callback); + } + + return ResultHandler.handleResult(success, error); + } + + private ResultT findOneAndDeleteInternal(final Bson filter, + @Nullable final FindOneAndModifyOptions options, + final Class resultClass) { + return findOneAndInternal(FindOneAndType.DELETE, filter, new Document(), options, resultClass); + } + + private ResultT findOneAndInternal(final FindOneAndType type, + final Bson filter, + final Bson update, + @Nullable final FindOneAndModifyOptions options, + final Class resultClass) { + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override - protected RemoteDeleteResult mapSuccess(Object result) { - return new RemoteDeleteResult((Long) result); + protected ResultT mapSuccess(Object result) { + return findSuccessMapper(result, resultClass); } }; - String jsonDocument = JniBsonProtocol.encode(filter, codecRegistry); - nativeDeleteOne(nativePtr, jsonDocument, callback); + String encodedFilter = JniBsonProtocol.encode(filter, codecRegistry); + String encodedUpdate = JniBsonProtocol.encode(update, codecRegistry); + String encodedProjection = null; + String encodedSort = null; + if (options != null) { + encodedProjection = JniBsonProtocol.encode(options.getProjection(), codecRegistry); + encodedSort = JniBsonProtocol.encode(options.getSort(), codecRegistry); + } + + switch (type) { + case UPDATE: + if (options == null) { + nativeFindOneAndUpdate(nativePtr, encodedFilter, encodedUpdate, callback); + } else { + nativeFindOneAndUpdateWithOptions(nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); + } + break; + case REPLACE: + if (options == null) { + nativeFindOneAndReplace(nativePtr, encodedFilter, encodedUpdate, callback); + } else { + nativeFindOneAndReplaceWithOptions(nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); + } + break; + case DELETE: + if (options == null) { + nativeFindOneAndDelete(nativePtr, encodedFilter, callback); + } else { + nativeFindOneAndDeleteWithOptions(nativePtr, encodedFilter, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); + } + break; + } + return ResultHandler.handleResult(success, error); } - public RemoteDeleteResult deleteMany(final Bson filter) { - AtomicReference success = new AtomicReference<>(null); + private DeleteResult deleteInternal(final DeleteType type, final Bson filter) { + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override - protected RemoteDeleteResult mapSuccess(Object result) { - return new RemoteDeleteResult((Long) result); + protected DeleteResult mapSuccess(Object result) { + return new DeleteResult((Long) result); } }; String jsonDocument = JniBsonProtocol.encode(filter, codecRegistry); - nativeDeleteMany(nativePtr, jsonDocument, callback); + switch (type) { + case ONE: + nativeDeleteOne(nativePtr, jsonDocument, callback); + break; + case MANY: + nativeDeleteMany(nativePtr, jsonDocument, callback); + break; + } return ResultHandler.handleResult(success, error); } - private T findOneSuccessMapper(@Nullable Object result, Class resultClass) { + private T findSuccessMapper(@Nullable Object result, Class resultClass) { if (result == null) { return null; } else { @@ -264,18 +470,30 @@ private T findOneSuccessMapper(@Nullable Object result, Class resultClass } } + private enum UpdateType { + ONE, MANY + } + + private enum DeleteType { + ONE, MANY + } + + private enum FindOneAndType { + UPDATE, REPLACE, DELETE + } + private static native long nativeGetFinalizerMethodPtr(); private static native void nativeCount(long remoteMongoCollectionPtr, String filter, long limit, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); private static native void nativeFindOne(long nativePtr, - String filterString, + String filter, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); private static native void nativeFindOneWithOptions(long nativePtr, - String filterString, - String projectionString, - String sortString, + String filter, + String projection, + String sort, long limit, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); private static native void nativeInsertOne(long remoteMongoCollectionPtr, @@ -290,4 +508,65 @@ private static native void nativeDeleteOne(long remoteMongoCollectionPtr, private static native void nativeDeleteMany(long remoteMongoCollectionPtr, String document, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeUpdateOne(long remoteMongoCollectionPtr, + String filter, + String update, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeUpdateOneWithOptions(long remoteMongoCollectionPtr, + String filter, + String update, + boolean upsert, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeUpdateMany(long remoteMongoCollectionPtr, + String filter, + String update, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeUpdateManyWithOptions(long remoteMongoCollectionPtr, + String filter, + String update, + boolean upsert, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndUpdate(long remoteMongoCollectionPtr, + String filter, + String update, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndUpdateWithOptions(long remoteMongoCollectionPtr, + String filter, + String update, + String projection, + String sort, + boolean upsert, + boolean returnNewDocument, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndReplace(long remoteMongoCollectionPtr, + String filter, + String update, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndReplaceWithOptions(long remoteMongoCollectionPtr, + String filter, + String update, + String projection, + String sort, + boolean upsert, + boolean returnNewDocument, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndDelete(long remoteMongoCollectionPtr, + String filter, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndDeleteWithOptions(long remoteMongoCollectionPtr, + String filter, + String projection, + String sort, + boolean upsert, + boolean returnNewDocument, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFind(long remoteMongoCollectionPtr, + String filter, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindWithOptions(long remoteMongoCollectionPtr, + String filter, + String projection, + String sort, + long limit, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java index df1e54cd55..1b48bb5282 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java @@ -25,16 +25,14 @@ import io.realm.internal.common.TaskDispatcher; import io.realm.internal.objectstore.OsMongoCollection; -import io.realm.mongodb.mongo.iterable.RemoteAggregateIterable; -import io.realm.mongodb.mongo.iterable.RemoteFindIterable; -import io.realm.mongodb.mongo.options.RemoteCountOptions; -import io.realm.mongodb.mongo.options.RemoteFindOneAndModifyOptions; -import io.realm.mongodb.mongo.options.RemoteFindOptions; -import io.realm.mongodb.mongo.options.RemoteInsertManyResult; -import io.realm.mongodb.mongo.options.RemoteUpdateOptions; -import io.realm.mongodb.mongo.result.RemoteDeleteResult; -import io.realm.mongodb.mongo.result.RemoteInsertOneResult; -import io.realm.mongodb.mongo.result.RemoteUpdateResult; +import io.realm.mongodb.mongo.options.CountOptions; +import io.realm.mongodb.mongo.options.FindOneAndModifyOptions; +import io.realm.mongodb.mongo.options.FindOptions; +import io.realm.mongodb.mongo.options.InsertManyResult; +import io.realm.mongodb.mongo.options.UpdateOptions; +import io.realm.mongodb.mongo.result.DeleteResult; +import io.realm.mongodb.mongo.result.InsertOneResult; +import io.realm.mongodb.mongo.result.UpdateResult; /** * The RemoteMongoCollection interface provides read and write access to documents. @@ -122,7 +120,7 @@ public Task count(final Bson filter) { * @param options the options describing the count * @return a task containing the number of documents in the collection */ - public Task count(final Bson filter, final RemoteCountOptions options) { + public Task count(final Bson filter, final CountOptions options) { return dispatcher.dispatchTask(() -> osMongoCollection.count(filter, options) ); @@ -185,7 +183,7 @@ public Task findOne(final Bson filter, final Class r * @param options A RemoteFindOptions struct * @return a task containing the result of the find one operation */ - public Task findOne(final Bson filter, final RemoteFindOptions options) { + public Task findOne(final Bson filter, final FindOptions options) { return dispatcher.dispatchTask(() -> osMongoCollection.findOne(filter, options) ); @@ -202,7 +200,7 @@ public Task findOne(final Bson filter, final RemoteFindOptions option */ public Task findOne( final Bson filter, - final RemoteFindOptions options, + final FindOptions options, final Class resultClass) { return dispatcher.dispatchTask(() -> osMongoCollection.findOne(filter, options, resultClass) @@ -214,8 +212,18 @@ public Task findOne( * * @return the find iterable interface */ - RemoteFindIterable find() { - throw new UnsupportedOperationException("Not Implemented"); + // FIXME: fix find implementation - ignore this code for code review + public Task> find() { + return dispatcher.dispatchTask(() -> + osMongoCollection.find() + ); + } + + // FIXME: fix find implementation - ignore this code for code review + public Task> find(final FindOptions options) { + return dispatcher.dispatchTask(() -> + osMongoCollection.find(options) + ); } /** @@ -225,8 +233,18 @@ RemoteFindIterable find() { * @param the target document type of the iterable. * @return the find iterable interface */ - RemoteFindIterable find(final Class resultClass) { - throw new UnsupportedOperationException("Not Implemented"); + // FIXME: fix find implementation - ignore this code for code review + public Task> find(final Class resultClass) { + return dispatcher.dispatchTask(() -> + osMongoCollection.find(resultClass) + ); + } + + // FIXME: fix find implementation - ignore this code for code review + public Task> find(final Class resultClass, final FindOptions options) { + return dispatcher.dispatchTask(() -> + osMongoCollection.find(resultClass, options) + ); } /** @@ -235,8 +253,18 @@ RemoteFindIterable find(final Class resultClass) { * @param filter the query filter * @return the find iterable interface */ - public RemoteFindIterable find(final Bson filter) { - throw new UnsupportedOperationException("Not Implemented"); + // FIXME: fix find implementation - ignore this code for code review + public Task> find(final Bson filter) { + return dispatcher.dispatchTask(() -> + osMongoCollection.find(filter) + ); + } + + // FIXME: fix find implementation - ignore this code for code review + public Task> find(final Bson filter, final FindOptions options) { + return dispatcher.dispatchTask(() -> + osMongoCollection.find(filter, options) + ); } /** @@ -247,10 +275,21 @@ public RemoteFindIterable find(final Bson filter) { * @param the target document type of the iterable. * @return the find iterable interface */ - public RemoteFindIterable find(final Bson filter, final Class resultClass) { - throw new UnsupportedOperationException("Not Implemented"); + // FIXME: fix find implementation - ignore this code for code review + public Task> find(final Bson filter, final Class resultClass) { + return dispatcher.dispatchTask(() -> + osMongoCollection.find(filter, resultClass) + ); } + // FIXME: fix find implementation - ignore this code for code review + public Task> find(final Bson filter, + final Class resultClass, + final FindOptions options) { + return dispatcher.dispatchTask(() -> + osMongoCollection.find(filter, resultClass, options) + ); + } /** * Aggregates documents according to the specified aggregation pipeline. @@ -258,7 +297,7 @@ public RemoteFindIterable find(final Bson filter, final Class * @param pipeline the aggregation pipeline * @return an iterable containing the result of the aggregation operation */ - public RemoteAggregateIterable aggregate(final List pipeline) { + public Task aggregate(final List pipeline) { throw new UnsupportedOperationException("Not Implemented"); } @@ -270,7 +309,7 @@ public RemoteAggregateIterable aggregate(final List p * @param the target document type of the iterable. * @return an iterable containing the result of the aggregation operation */ - public RemoteAggregateIterable aggregate( + public Task aggregate( final List pipeline, final Class resultClass) { throw new UnsupportedOperationException("Not Implemented"); @@ -283,7 +322,7 @@ public RemoteAggregateIterable aggregate( * @param document the document to insert * @return a task containing the result of the insert one operation */ - public Task insertOne(final DocumentT document) { + public Task insertOne(final DocumentT document) { return dispatcher.dispatchTask(() -> osMongoCollection.insertOne(document) ); @@ -295,7 +334,7 @@ public Task insertOne(final DocumentT document) { * @param documents the documents to insert * @return a task containing the result of the insert many operation */ - public Task insertMany(final List documents) { + public Task insertMany(final List documents) { return dispatcher.dispatchTask(() -> osMongoCollection.insertMany(documents) ); @@ -309,7 +348,7 @@ public Task insertMany(final List d * @param filter the query filter to apply the the delete operation * @return a task containing the result of the remove one operation */ - public Task deleteOne(final Bson filter) { + public Task deleteOne(final Bson filter) { return dispatcher.dispatchTask(() -> osMongoCollection.deleteOne(filter) ); @@ -322,7 +361,7 @@ public Task deleteOne(final Bson filter) { * @param filter the query filter to apply the the delete operation * @return a task containing the result of the remove many operation */ - public Task deleteMany(final Bson filter) { + public Task deleteMany(final Bson filter) { return dispatcher.dispatchTask(() -> osMongoCollection.deleteMany(filter) ); @@ -336,8 +375,10 @@ public Task deleteMany(final Bson filter) { * apply must include only update operators. * @return a task containing the result of the update one operation */ - public Task updateOne(final Bson filter, final Bson update) { - throw new UnsupportedOperationException("Not Implemented"); + public Task updateOne(final Bson filter, final Bson update) { + return dispatcher.dispatchTask(() -> + osMongoCollection.updateOne(filter, update) + ); } /** @@ -349,11 +390,13 @@ public Task updateOne(final Bson filter, final Bson update) * @param updateOptions the options to apply to the update operation * @return a task containing the result of the update one operation */ - public Task updateOne( + public Task updateOne( final Bson filter, final Bson update, - final RemoteUpdateOptions updateOptions) { - throw new UnsupportedOperationException("Not Implemented"); + final UpdateOptions updateOptions) { + return dispatcher.dispatchTask(() -> + osMongoCollection.updateOne(filter, update, updateOptions) + ); } /** @@ -364,8 +407,10 @@ public Task updateOne( * apply must include only update operators. * @return a task containing the result of the update many operation */ - public Task updateMany(final Bson filter, final Bson update) { - throw new UnsupportedOperationException("Not Implemented"); + public Task updateMany(final Bson filter, final Bson update) { + return dispatcher.dispatchTask(() -> + osMongoCollection.updateMany(filter, update) + ); } /** @@ -377,11 +422,13 @@ public Task updateMany(final Bson filter, final Bson update) * @param updateOptions the options to apply to the update operation * @return a task containing the result of the update many operation */ - public Task updateMany( + public Task updateMany( final Bson filter, final Bson update, - final RemoteUpdateOptions updateOptions) { - throw new UnsupportedOperationException("Not Implemented"); + final UpdateOptions updateOptions) { + return dispatcher.dispatchTask(() -> + osMongoCollection.updateMany(filter, update, updateOptions) + ); } /** @@ -392,7 +439,9 @@ public Task updateMany( * @return a task containing the resulting document */ public Task findOneAndUpdate(final Bson filter, final Bson update) { - throw new UnsupportedOperationException("Not Implemented"); + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndUpdate(filter, update) + ); } /** @@ -407,7 +456,9 @@ public Task findOneAndUpdate(final Bson filter, final Bson update) { public Task findOneAndUpdate(final Bson filter, final Bson update, final Class resultClass) { - throw new UnsupportedOperationException("Not Implemented"); + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndUpdate(filter, update, resultClass) + ); } /** @@ -420,8 +471,10 @@ public Task findOneAndUpdate(final Bson filter, */ public Task findOneAndUpdate(final Bson filter, final Bson update, - final RemoteFindOneAndModifyOptions options) { - throw new UnsupportedOperationException("Not Implemented"); + final FindOneAndModifyOptions options) { + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndUpdate(filter, update, options) + ); } /** @@ -437,9 +490,11 @@ public Task findOneAndUpdate(final Bson filter, public Task findOneAndUpdate( final Bson filter, final Bson update, - final RemoteFindOneAndModifyOptions options, + final FindOneAndModifyOptions options, final Class resultClass) { - throw new UnsupportedOperationException("Not Implemented"); + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndUpdate(filter, update, options, resultClass) + ); } /** @@ -450,7 +505,9 @@ public Task findOneAndUpdate( * @return a task containing the resulting document */ public Task findOneAndReplace(final Bson filter, final Bson replacement) { - throw new UnsupportedOperationException("Not Implemented"); + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndReplace(filter, replacement) + ); } /** @@ -465,7 +522,9 @@ public Task findOneAndReplace(final Bson filter, final Bson replaceme public Task findOneAndReplace(final Bson filter, final Bson replacement, final Class resultClass) { - throw new UnsupportedOperationException("Not Implemented"); + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndReplace(filter, replacement, resultClass) + ); } /** @@ -478,8 +537,10 @@ public Task findOneAndReplace(final Bson filter, */ public Task findOneAndReplace(final Bson filter, final Bson replacement, - final RemoteFindOneAndModifyOptions options) { - throw new UnsupportedOperationException("Not Implemented"); + final FindOneAndModifyOptions options) { + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndReplace(filter, replacement, options) + ); } /** @@ -495,9 +556,11 @@ public Task findOneAndReplace(final Bson filter, public Task findOneAndReplace( final Bson filter, final Bson replacement, - final RemoteFindOneAndModifyOptions options, + final FindOneAndModifyOptions options, final Class resultClass) { - throw new UnsupportedOperationException("Not Implemented"); + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndReplace(filter, replacement, options, resultClass) + ); } /** @@ -507,7 +570,9 @@ public Task findOneAndReplace( * @return a task containing the resulting document */ public Task findOneAndDelete(final Bson filter) { - throw new UnsupportedOperationException("Not Implemented"); + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndDelete(filter) + ); } /** @@ -520,7 +585,9 @@ public Task findOneAndDelete(final Bson filter) { */ public Task findOneAndDelete(final Bson filter, final Class resultClass) { - throw new UnsupportedOperationException("Not Implemented"); + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndDelete(filter, resultClass) + ); } /** @@ -531,8 +598,10 @@ public Task findOneAndDelete(final Bson filter, * @return a task containing the resulting document */ public Task findOneAndDelete(final Bson filter, - final RemoteFindOneAndModifyOptions options) { - throw new UnsupportedOperationException("Not Implemented"); + final FindOneAndModifyOptions options) { + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndDelete(filter, options) + ); } /** @@ -544,11 +613,12 @@ public Task findOneAndDelete(final Bson filter, * @param the target document type of the iterable. * @return a task containing the resulting document */ - public Task findOneAndDelete( - final Bson filter, - final RemoteFindOneAndModifyOptions options, - final Class resultClass) { - throw new UnsupportedOperationException("Not Implemented"); + public Task findOneAndDelete(final Bson filter, + final FindOneAndModifyOptions options, + final Class resultClass) { + return dispatcher.dispatchTask(() -> + osMongoCollection.findOneAndDelete(filter, options, resultClass) + ); } // FIXME: what about these? diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteAggregateIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteAggregateIterable.java deleted file mode 100644 index f4417445a1..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteAggregateIterable.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.mongodb.mongo.iterable; - -/** - * Iterable for aggregate. - * - * @param The type of the result. - */ -// TODO: figure out whether or not we need the parent interface -//public interface RemoteAggregateIterable extends RemoteMongoIterable { -public interface RemoteAggregateIterable { - -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteFindIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteFindIterable.java deleted file mode 100644 index 13ea09ceba..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/RemoteFindIterable.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.mongodb.mongo.iterable; - -import org.bson.conversions.Bson; - -import javax.annotation.Nullable; - -/** - * Iterable for find. - * - * @param The type of the result. - */ -// TODO: figure out whether or not we need the parent interface -//public interface RemoteFindIterable extends RemoteMongoIterable { -public class RemoteFindIterable { - - /** - * Sets the query filter to apply to the query. - * - * @param filter the filter, which may be null. - * @return this - */ - RemoteFindIterable filter(@Nullable final Bson filter) { - throw new RuntimeException("Not Implemented"); - } - - /** - * Sets the limit to apply. - * - * @param limit the limit, which may be 0 - * @return this - */ - RemoteFindIterable limit(final int limit) { - throw new RuntimeException("Not Implemented"); - } - - /** - * Sets a document describing the fields to return for all matching documents. - * - * @param projection the project document, which may be null. - * @return this - */ - RemoteFindIterable projection(@Nullable final Bson projection) { - throw new RuntimeException("Not Implemented"); - } - - /** - * Sets the sort criteria to apply to the query. - * - * @param sort the sort criteria, which may be null. - * @return this - */ - RemoteFindIterable sort(@Nullable final Bson sort) { - throw new RuntimeException("Not Implemented"); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteCountOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/CountOptions.java similarity index 93% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteCountOptions.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/CountOptions.java index 9cc3bd6c4d..1ad8e51985 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteCountOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/CountOptions.java @@ -19,7 +19,7 @@ /** * The options for a count operation. */ -public class RemoteCountOptions { +public class CountOptions { private int limit; /** @@ -37,7 +37,7 @@ public int getLimit() { * @param limit the limit * @return this */ - public RemoteCountOptions limit(final int limit) { + public CountOptions limit(final int limit) { this.limit = limit; return this; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOneAndModifyOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java similarity index 91% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOneAndModifyOptions.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java index e3f8542fef..59234e3edf 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOneAndModifyOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java @@ -24,7 +24,7 @@ * The options to apply to a findOneAndUpdate, findOneAndReplace, or findOneAndDelete operation * (also commonly referred to as findOneAndModify operations). */ -public class RemoteFindOneAndModifyOptions { +public class FindOneAndModifyOptions { private Bson projection; private Bson sort; private boolean upsert; @@ -46,7 +46,7 @@ public Bson getProjection() { * @param projection the project document, which may be null. * @return this */ - public RemoteFindOneAndModifyOptions projection(@Nullable final Bson projection) { + public FindOneAndModifyOptions projection(@Nullable final Bson projection) { this.projection = projection; return this; } @@ -68,7 +68,7 @@ public Bson getSort() { * @param sort the sort criteria, which may be null. * @return this */ - public RemoteFindOneAndModifyOptions sort(@Nullable final Bson sort) { + public FindOneAndModifyOptions sort(@Nullable final Bson sort) { this.sort = sort; return this; } @@ -91,7 +91,7 @@ public boolean isUpsert() { * filter. * @return this */ - public RemoteFindOneAndModifyOptions upsert(final boolean upsert) { + public FindOneAndModifyOptions upsert(final boolean upsert) { this.upsert = upsert; return this; } @@ -117,7 +117,7 @@ public boolean isReturnNewDocument() { * @param returnNewDocument true if findOneAndModify operations should return the updated document * @return this */ - public RemoteFindOneAndModifyOptions returnNewDocument(final boolean returnNewDocument) { + public FindOneAndModifyOptions returnNewDocument(final boolean returnNewDocument) { this.returnNewDocument = returnNewDocument; return this; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java similarity index 90% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOptions.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java index 8d0509898a..c849e96d5e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteFindOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java @@ -23,7 +23,7 @@ /** * The options to apply to a find operation (also commonly referred to as a query). */ -public class RemoteFindOptions { +public class FindOptions { private int limit; private Bson projection; private Bson sort; @@ -31,7 +31,7 @@ public class RemoteFindOptions { /** * Construct a new instance. */ - public RemoteFindOptions() { + public FindOptions() { } /** @@ -49,7 +49,7 @@ public int getLimit() { * @param limit the limit, which may be null * @return this */ - public RemoteFindOptions limit(final int limit) { + public FindOptions limit(final int limit) { this.limit = limit; return this; } @@ -70,7 +70,7 @@ public Bson getProjection() { * @param projection the project document, which may be null. * @return this */ - public RemoteFindOptions projection(@Nullable final Bson projection) { + public FindOptions projection(@Nullable final Bson projection) { this.projection = projection; return this; } @@ -92,7 +92,7 @@ public Bson getSort() { * @param sort the sort criteria, which may be null. * @return this */ - public RemoteFindOptions sort(@Nullable final Bson sort) { + public FindOptions sort(@Nullable final Bson sort) { this.sort = sort; return this; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteInsertManyResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/InsertManyResult.java similarity index 92% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteInsertManyResult.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/InsertManyResult.java index f32c3639dc..a7d931664a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteInsertManyResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/InsertManyResult.java @@ -23,7 +23,7 @@ /** * The result of an insert many operation. */ -public class RemoteInsertManyResult { +public class InsertManyResult { private final Map insertedIds; @@ -33,7 +33,7 @@ public class RemoteInsertManyResult { * @param insertedIds the _ids of the inserted documents arranged by the index of the document * from the operation and its corresponding id. */ - public RemoteInsertManyResult(final Map insertedIds) { + public InsertManyResult(final Map insertedIds) { this.insertedIds = insertedIds; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteUpdateOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/UpdateOptions.java similarity index 93% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteUpdateOptions.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/UpdateOptions.java index 64a85a4a79..154957dca1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/RemoteUpdateOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/UpdateOptions.java @@ -19,7 +19,7 @@ /** * The options to apply when updating documents. */ -public class RemoteUpdateOptions { +public class UpdateOptions { private boolean upsert; /** @@ -39,7 +39,7 @@ public boolean isUpsert() { * filter. * @return this */ - public RemoteUpdateOptions upsert(final boolean upsert) { + public UpdateOptions upsert(final boolean upsert) { this.upsert = upsert; return this; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteDeleteResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/DeleteResult.java similarity index 92% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteDeleteResult.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/DeleteResult.java index e101029438..d7e7986e09 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteDeleteResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/DeleteResult.java @@ -19,7 +19,7 @@ /** * The result of a delete operation. */ -public class RemoteDeleteResult { +public class DeleteResult { private final long deletedCount; @@ -28,7 +28,7 @@ public class RemoteDeleteResult { * * @param deletedCount the number of documents deleted. */ - public RemoteDeleteResult(final long deletedCount) { + public DeleteResult(final long deletedCount) { this.deletedCount = deletedCount; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteInsertOneResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/InsertOneResult.java similarity index 91% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteInsertOneResult.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/InsertOneResult.java index e66be192ce..2a7a03056f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteInsertOneResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/InsertOneResult.java @@ -21,7 +21,7 @@ /** * The result of an insert one operation. */ -public class RemoteInsertOneResult { +public class InsertOneResult { private final BsonValue insertedId; @@ -30,7 +30,7 @@ public class RemoteInsertOneResult { * * @param insertedId the _id of the inserted document. */ - public RemoteInsertOneResult(final BsonValue insertedId) { + public InsertOneResult(final BsonValue insertedId) { this.insertedId = insertedId; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteUpdateResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/UpdateResult.java similarity index 97% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteUpdateResult.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/UpdateResult.java index 18c708d520..e998648848 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/RemoteUpdateResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/UpdateResult.java @@ -23,7 +23,7 @@ /** * The result of an update operation. */ -public class RemoteUpdateResult { +public class UpdateResult { private final long matchedCount; private final long modifiedCount; @@ -37,7 +37,7 @@ public class RemoteUpdateResult { * @param upsertedId the _id of the inserted document if the replace resulted in an inserted * document, otherwise null. */ - public RemoteUpdateResult( + public UpdateResult( final long matchedCount, final long modifiedCount, final BsonValue upsertedId From 3e83886fc0edb79847b67c6e0dc8cfb50f046824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 26 May 2020 09:07:45 +0200 Subject: [PATCH 1537/2110] Migrate and fix SessionTests (#6856) --- .../java/io/realm/SessionTests.java | 583 ------------------ .../kotlin/io/realm/SessionTests.kt | 492 +++++++++++++++ .../kotlin/io/realm/util/KotlinTestUtils.kt | 21 +- .../testUtils/java/io/realm/TestHelper.java | 9 + 4 files changed, 521 insertions(+), 584 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SessionTests.kt diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java deleted file mode 100644 index eaeb1ea0f7..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SessionTests.java +++ /dev/null @@ -1,583 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.hamcrest.CoreMatchers; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.realm.entities.StringOnly; -import io.realm.exceptions.RealmFileException; -import io.realm.exceptions.RealmMigrationNeededException; -import io.realm.log.RealmLog; -import io.realm.entities.StringOnlyModule; -import io.realm.rule.RunInLooperThread; -import io.realm.rule.RunTestInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class SessionTests { - - private SyncConfiguration configuration; - private TestRealmApp app; - private RealmUser user; - - @Rule - public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - - @Rule - public final RunInLooperThread looperThread = new RunInLooperThread(); - - @Before - public void setUp() { - app = new TestRealmApp(); - user = SyncTestUtils.createTestUser(app); - configuration = SyncConfiguration.defaultConfig(user, "default"); - } - - @After - public void tearDown() { - if (app != null) { - RealmAppExtKt.close(app); - } - } - - @Test - public void get_syncValues() { - SyncSession session = new SyncSession(configuration); - assertEquals("ws://127.0.0.1:9090/", session.getServerUrl().toString()); - assertEquals(user, session.getUser()); - assertEquals(configuration, session.getConfiguration()); - } - - @Test - public void addDownloadProgressListener_nullThrows() { - SyncSession session = app.getSync().getOrCreateSession(configuration); - try { - session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, null); - fail(); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void addUploadProgressListener_nullThrows() { - SyncSession session = app.getSync().getOrCreateSession(configuration); - try { - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, null); - fail(); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void removeProgressListener() { - Realm realm = Realm.getInstance(configuration); - SyncSession session = app.getSync().getOrCreateSession(configuration); - ProgressListener[] listeners = new ProgressListener[] { - null, - progress -> { - // Listener 1, not present - }, - progress -> { - // Listener 2, present - } - }; - session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, listeners[2]); - - // Check that remove works unconditionally for all input - for (ProgressListener listener : listeners) { - session.removeProgressListener(listener); - } - realm.close(); - } - - // Check that a Client Reset is correctly reported. - @Test - @RunTestInLooperThread - @Ignore("FIXME: Figure out how to fix this") - public void errorHandler_clientResetReported() { - RealmUser user = SyncTestUtils.createTestUser(app); - String url = "realm://objectserver.realm.io/default"; - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) - .clientResyncMode(ClientResyncMode.MANUAL) - .errorHandler((session, error) -> { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - String filePathFromError = handler.getOriginalFile().getAbsolutePath(); - String filePathFromConfig = session.getConfiguration().getPath(); - assertEquals(filePathFromError, filePathFromConfig); - assertFalse(handler.getBackupFile().exists()); - assertTrue(handler.getOriginalFile().exists()); - - looperThread.testComplete(); - }) - .build(); - - Realm realm = Realm.getInstance(config); - looperThread.addTestRealm(realm); - - // Trigger error - RealmSync syncService = user.getApp().getSync(); - syncService.simulateClientReset(syncService.getSession((config))); - } - - // Check that we can manually execute the Client Reset. - @Test - @RunTestInLooperThread - @Ignore("FIXME: Figure out how to fix this") - public void errorHandler_manualExecuteClientReset() { - RealmUser user = SyncTestUtils.createTestUser(app); - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) - .clientResyncMode(ClientResyncMode.MANUAL) - .errorHandler((session, error) -> { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - try { - handler.executeClientReset(); - fail("All Realms should be closed before executing Client Reset can be allowed"); - } catch(IllegalStateException ignored) { - } - - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); - - // Validate that files have been moved - assertFalse(handler.getOriginalFile().exists()); - assertTrue(handler.getBackupFile().exists()); - looperThread.testComplete(); - }) - .build(); - - Realm realm = Realm.getInstance(config); - looperThread.addTestRealm(realm); - - // Trigger error - user.getApp().getSync().simulateClientReset(app.getSync().getSession(configuration)); - } - - // Check that we can use the backup SyncConfiguration to open the Realm. - @Test - @RunTestInLooperThread - @Ignore("FIXME: Figure out how to fix this") - public void errorHandler_useBackupSyncConfigurationForClientReset() { - RealmUser user = SyncTestUtils.createTestUser(app); - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) - .clientResyncMode(ClientResyncMode.MANUAL) - .schema(StringOnly.class) - .errorHandler((session, error) -> { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); - - // Validate that files have been moved - assertFalse(handler.getOriginalFile().exists()); - assertTrue(handler.getBackupFile().exists()); - - RealmConfiguration backupRealmConfiguration = handler.getBackupRealmConfiguration(); - assertNotNull(backupRealmConfiguration); - assertFalse(backupRealmConfiguration.isSyncConfiguration()); - assertTrue(backupRealmConfiguration.isRecoveryConfiguration()); - - Realm backupRealm = Realm.getInstance(backupRealmConfiguration); - assertFalse(backupRealm.isEmpty()); - assertEquals(1, backupRealm.where(StringOnly.class).count()); - assertEquals("Foo", backupRealm.where(StringOnly.class).findAll().first().getChars()); - backupRealm.close(); - - // opening a Dynamic Realm should also work - DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); - dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); - RealmResults all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); - assertEquals(1, all.size()); - assertEquals("Foo", all.first().getString(StringOnly.FIELD_CHARS)); - dynamicRealm.close(); - looperThread.testComplete(); - }) - .modules(new StringOnlyModule()) - .build(); - - Realm realm = Realm.getInstance(config); - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - - looperThread.addTestRealm(realm); - - // Trigger error - user.getApp().getSync().simulateClientReset(app.getSync().getSession(configuration)); - } - - // Check that we can open the backup file without using the provided SyncConfiguration, - // this might be the case if the user decide to act upon the client reset later (providing s/he - // persisted the location of the file) - @Test - @RunTestInLooperThread - @Ignore("FIXME: Figure out how to fix this") - public void errorHandler_useBackupSyncConfigurationAfterClientReset() { - RealmUser user = SyncTestUtils.createTestUser(app); - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) - .clientResyncMode(ClientResyncMode.MANUAL) - .errorHandler((session, error) -> { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); - - // Validate that files have been moved - assertFalse(handler.getOriginalFile().exists()); - assertTrue(handler.getBackupFile().exists()); - - String backupFile = handler.getBackupFile().getAbsolutePath(); - - // this SyncConf doesn't specify any module, it will throw a migration required - // exception since the backup Realm contain only StringOnly table - RealmConfiguration backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile); - - try { - Realm.getInstance(backupRealmConfiguration); - fail("Expected to throw a Migration required"); - } catch (RealmMigrationNeededException expected) { - } - - // opening a DynamicRealm will work though - DynamicRealm dynamicRealm = DynamicRealm.getInstance(backupRealmConfiguration); - - dynamicRealm.getSchema().checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME); - RealmResults all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll(); - assertEquals(1, all.size()); - assertEquals("Foo", all.first().getString(StringOnly.FIELD_CHARS)); - - // make sure we can't write to it (read-only Realm) - try { - dynamicRealm.beginTransaction(); - fail("Can't perform transactions on read-only Realms"); - } catch (IllegalStateException expected) { - } - dynamicRealm.close(); - - try { - SyncConfiguration.forRecovery(backupFile, null, StringOnly.class); - fail("Expected to throw java.lang.Class is not a RealmModule"); - } catch (IllegalArgumentException expected) { - } - - // specifying the module will allow to open the typed Realm - backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, null, new StringOnlyModule()); - Realm backupRealm = Realm.getInstance(backupRealmConfiguration); - assertFalse(backupRealm.isEmpty()); - assertEquals(1, backupRealm.where(StringOnly.class).count()); - RealmResults allSorted = backupRealm.where(StringOnly.class).findAll(); - assertEquals("Foo", allSorted.get(0).getChars()); - backupRealm.close(); - - looperThread.testComplete(); - }) - .modules(new StringOnlyModule()) - .build(); - - Realm realm = Realm.getInstance(config); - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - - looperThread.addTestRealm(realm); - - // Trigger error - user.getApp().getSync().simulateClientReset(app.getSync().getSession(configuration)); - } - - // make sure the backup file Realm is encrypted with the same key as the original synced Realm. - @Test - @RunTestInLooperThread - @Ignore("FIXME: Figure out how to fix this") - public void errorHandler_useClientResetEncrypted() { - RealmUser user = SyncTestUtils.createTestUser(app); - final byte[] randomKey = TestHelper.getRandomKey(); - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user) - .clientResyncMode(ClientResyncMode.MANUAL) - .encryptionKey(randomKey) - .modules(new StringOnlyModule()) - .errorHandler((session, error) -> { - if (error.getErrorCode() != ErrorCode.CLIENT_RESET) { - fail("Wrong error " + error.toString()); - return; - } - - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); - - RealmConfiguration backupRealmConfiguration = handler.getBackupRealmConfiguration(); - - // can open encrypted backup Realm - Realm backupEncryptedRealm = Realm.getInstance(backupRealmConfiguration); - assertEquals(1, backupEncryptedRealm.where(StringOnly.class).count()); - RealmResults allSorted = backupEncryptedRealm.where(StringOnly.class).findAll(); - assertEquals("Foo", allSorted.get(0).getChars()); - backupEncryptedRealm.close(); - - String backupFile = handler.getBackupFile().getAbsolutePath(); - // build a conf to open a DynamicRealm - backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, randomKey, new StringOnlyModule()); - backupEncryptedRealm = Realm.getInstance(backupRealmConfiguration); - assertEquals(1, backupEncryptedRealm.where(StringOnly.class).count()); - allSorted = backupEncryptedRealm.where(StringOnly.class).findAll(); - assertEquals("Foo", allSorted.get(0).getChars()); - backupEncryptedRealm.close(); - - // using wrong key throw - try { - Realm.getInstance(SyncConfiguration.forRecovery(backupFile, TestHelper.getRandomKey(), new StringOnlyModule())); - fail("Expected to throw when using wrong encryption key"); - } catch (RealmFileException expected) { - } - - looperThread.testComplete(); - }) - .build(); - - Realm realm = Realm.getInstance(config); - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - - looperThread.addTestRealm(realm); - - // Trigger error - user.getApp().getSync().simulateClientReset(app.getSync().getSession(configuration)); - } - - @Test - @UiThreadTest - public void uploadAllLocalChanges_throwsOnUiThread() throws InterruptedException { - Realm realm = Realm.getInstance(configuration); - try { - app.getSync().getOrCreateSession(configuration).uploadAllLocalChanges(); - fail("Should throw an IllegalStateException on Ui Thread"); - } catch (IllegalStateException ignored) { - } finally { - realm.close(); - } - } - - @Test - @UiThreadTest - public void uploadAllLocalChanges_withTimeout_throwsOnUiThread() throws InterruptedException { - Realm realm = Realm.getInstance(configuration); - try { - app.getSync().getOrCreateSession(configuration).uploadAllLocalChanges(30, TimeUnit.SECONDS); - fail("Should throw an IllegalStateException on Ui Thread"); - } catch (IllegalStateException ignored) { - } finally { - realm.close(); - } - } - - @Test - public void uploadAllLocalChanges_withTimeout_invalidParametersThrows() throws InterruptedException { - Realm realm = Realm.getInstance(configuration); - SyncSession session = app.getSync().getOrCreateSession(configuration); - try { - try { - session.uploadAllLocalChanges(-1, TimeUnit.SECONDS); - fail(); - } catch (IllegalArgumentException ignored) { - } - - try { - //noinspection ConstantConditions - session.uploadAllLocalChanges(1, null); - fail(); - } catch (IllegalArgumentException ignored) { - } - } finally { - realm.close(); - } - } - - @Test - public void uploadAllLocalChanges_returnFalseWhenTimedOut() throws InterruptedException { - Realm realm = Realm.getInstance(configuration); - SyncSession session = app.getSync().getSession(configuration); - try { - assertFalse(session.uploadAllLocalChanges(100, TimeUnit.MILLISECONDS)); - } finally { - realm.close(); - } - } - - @Test - @UiThreadTest - public void downloadAllServerChanges_throwsOnUiThread() throws InterruptedException { - Realm realm = Realm.getInstance(configuration); - try { - app.getSync().getSession(configuration).downloadAllServerChanges(); - fail("Should throw an IllegalStateException on Ui Thread"); - } catch (IllegalStateException ignored) { - } finally { - realm.close(); - } - } - - @Test - @UiThreadTest - public void downloadAllServerChanges_withTimeout_throwsOnUiThread() throws InterruptedException { - Realm realm = Realm.getInstance(configuration); - try { - app.getSync().getSession(configuration).downloadAllServerChanges(30, TimeUnit.SECONDS); - fail("Should throw an IllegalStateException on Ui Thread"); - } catch (IllegalStateException ignored) { - } finally { - realm.close(); - } - } - - - @Test - public void downloadAllServerChanges_withTimeout_invalidParametersThrows() throws InterruptedException { - Realm realm = Realm.getInstance(configuration); - SyncSession session = app.getSync().getSession(configuration); - try { - try { - session.downloadAllServerChanges(-1, TimeUnit.SECONDS); - fail(); - } catch (IllegalArgumentException ignored) { - } - - try { - //noinspection ConstantConditions - session.downloadAllServerChanges(1, null); - fail(); - } catch (IllegalArgumentException ignored) { - } - } finally { - realm.close(); - } - } - - @Test - public void downloadAllServerChanges_returnFalseWhenTimedOut() throws InterruptedException { - Realm realm = Realm.getInstance(configuration); - SyncSession session = app.getSync().getSession(configuration); - try { - assertFalse(session.downloadAllServerChanges(100, TimeUnit.MILLISECONDS)); - } finally { - realm.close(); - } - } - - @Test - @UiThreadTest - public void unrecognizedErrorCode_errorHandler() { - AtomicBoolean errorHandlerCalled = new AtomicBoolean(false); - configuration = configFactory.createSyncConfigurationBuilder(user) - .errorHandler((session, error) -> { - errorHandlerCalled.set(true); - assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals(ErrorCode.Category.FATAL, error.getCategory()); - - }) - .build(); - Realm realm = Realm.getInstance(configuration); - SyncSession session = app.getSync().getSession(configuration); - - TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); - RealmLog.add(testLogger); - - session.notifySessionError("unknown", 3, "Unknown Error"); - RealmLog.remove(testLogger); - - assertTrue(errorHandlerCalled.get()); - assertEquals("Unknown error code: 'unknown:3'", testLogger.message); - - realm.close(); - } - - @Test - public void getSessionThrowsOnNonExistingSession() { - Realm realm = Realm.getInstance(configuration); - SyncSession session = app.getSync().getSession(configuration); - assertEquals(configuration, session.getConfiguration()); - - // Closing the Realm should remove the session - realm.close(); - try { - app.getSync().getSession(configuration); - fail("getSession should throw an ISE"); - } catch (IllegalStateException expected) { - assertThat(expected.getMessage(), CoreMatchers.containsString( - "No SyncSession found using the path : ")); - } - } - - @Test - public void isConnected_falseForInvalidUser() { - Realm realm = Realm.getInstance(configuration); - SyncSession session = app.getSync().getSession(configuration); - try { - assertFalse(session.isConnected()); - } finally { - realm.close(); - } - } - - @Test - public void stop_doesNotThrowIfCalledWhenRealmIsClosed() { - Realm realm = Realm.getInstance(configuration); - SyncSession session = app.getSync().getSession(configuration); - realm.close(); - session.stop(); - } -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SessionTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SessionTests.kt new file mode 100644 index 0000000000..6daa597eb1 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SessionTests.kt @@ -0,0 +1,492 @@ +/* + * Copyright 2016 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.annotation.UiThreadTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.TestHelper.TestLogger +import io.realm.entities.StringOnly +import io.realm.entities.StringOnlyModule +import io.realm.exceptions.RealmFileException +import io.realm.exceptions.RealmMigrationNeededException +import io.realm.kotlin.syncSession +import io.realm.log.RealmLog +import io.realm.rule.BlockingLooperThread +import io.realm.util.ResourceContainer +import io.realm.util.assertFailsWithMessage +import org.hamcrest.CoreMatchers +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.* + +@RunWith(AndroidJUnit4::class) +class SessionTests { + private lateinit var configuration: SyncConfiguration + private lateinit var app: TestRealmApp + private lateinit var user: RealmUser + + @get:Rule + val configFactory = TestSyncConfigurationFactory() + + private val looperThread = BlockingLooperThread() + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestRealmApp() + // TODO We could potentially work without a fully functioning user to speed up tests, but + // seems like the old way of "faking" it, does now work for now, so using a real user. + // user = SyncTestUtils.createTestUser(app) + user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + configuration = SyncConfiguration.defaultConfig(user, "default") + } + + @After + fun tearDown() { + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun get_syncValues() { + Realm.getInstance(configuration).use { realm -> + val session = realm.syncSession + assertEquals("ws://127.0.0.1:9090/", session.serverUrl.toString()) + assertEquals(user, session.user) + assertEquals(configuration, session.configuration) + } + } + + @Test + fun addDownloadProgressListener_nullThrows() { + Realm.getInstance(configuration).use { realm -> + val session = realm.syncSession + assertFailsWith { + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, TestHelper.getNull()) + } + } + } + + @Test + fun addUploadProgressListener_nullThrows() { + Realm.getInstance(configuration).use { realm -> + val session = realm.syncSession + assertFailsWith { + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, TestHelper.getNull()) + } + } + } + + @Test + fun removeProgressListener() { + Realm.getInstance(configuration).use { realm -> + val session = realm.syncSession + val listeners = arrayOf( + null, + ProgressListener { progress: Progress? -> }, + ProgressListener { progress: Progress? -> } + ) + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, TestHelper.allowNull(listeners[2])) + session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, TestHelper.allowNull(listeners[2])) + + // Check that remove works unconditionally for all input + for (listener in listeners) { + session.removeProgressListener(TestHelper.allowNull(listener)) + } + } + } + + // Check that a Client Reset is correctly reported. + @Test + fun errorHandler_clientResetReported() = looperThread.runBlocking { + val config = configFactory.createSyncConfigurationBuilder(user) + .clientResyncMode(ClientResyncMode.MANUAL) + .errorHandler { session: SyncSession, error: ObjectServerError -> + if (error.errorCode != ErrorCode.CLIENT_RESET) { + fail("Wrong error $error") + return@errorHandler + } + val handler = error as ClientResetRequiredError + val filePathFromError = handler.originalFile.absolutePath + val filePathFromConfig = session.configuration.path + assertEquals(filePathFromError, filePathFromConfig) + assertFalse(handler.backupFile.exists()) + assertTrue(handler.originalFile.exists()) + looperThread.testComplete() + } + .build() + + val realm = Realm.getInstance(config) + looperThread.closeAfterTest(realm) + + // Trigger error + user.app.sync.simulateClientReset(realm.syncSession) + } + + // Check that we can manually execute the Client Reset. + @Test + fun errorHandler_manualExecuteClientReset() = looperThread.runBlocking { + val resources = ResourceContainer() + + val config = configFactory.createSyncConfigurationBuilder(user) + .clientResyncMode(ClientResyncMode.MANUAL) + .errorHandler { session: SyncSession?, error: ObjectServerError -> + if (error.errorCode != ErrorCode.CLIENT_RESET) { + fail("Wrong error $error") + return@errorHandler + } + val handler = error as ClientResetRequiredError + try { + handler.executeClientReset() + fail("All Realms should be closed before executing Client Reset can be allowed") + } catch (ignored: IllegalStateException) { + } + + // Execute Client Reset + resources.close() + handler.executeClientReset() + + // Validate that files have been moved + assertFalse(handler.originalFile.exists()) + assertTrue(handler.backupFile.exists()) + looperThread.testComplete() + } + .build() + val realm = Realm.getInstance(config) + resources.add(realm) + + // Trigger error + user.app.sync.simulateClientReset(realm.syncSession) + } + + // Check that we can use the backup SyncConfiguration to open the Realm. + @Test + fun errorHandler_useBackupSyncConfigurationForClientReset() = looperThread.runBlocking { + val resources = ResourceContainer() + val config = configFactory.createSyncConfigurationBuilder(user) + .clientResyncMode(ClientResyncMode.MANUAL) + .schema(StringOnly::class.java) + .errorHandler { session: SyncSession?, error: ObjectServerError -> + if (error.errorCode != ErrorCode.CLIENT_RESET) { + fail("Wrong error $error") + return@errorHandler + } + val handler = error as ClientResetRequiredError + // Execute Client Reset + resources.close() + handler.executeClientReset() + + // Validate that files have been moved + assertFalse(handler.originalFile.exists()) + assertTrue(handler.backupFile.exists()) + val backupRealmConfiguration = handler.backupRealmConfiguration + assertNotNull(backupRealmConfiguration) + assertFalse(backupRealmConfiguration.isSyncConfiguration) + assertTrue(backupRealmConfiguration.isRecoveryConfiguration) + Realm.getInstance(backupRealmConfiguration).use { backupRealm -> + assertFalse(backupRealm.isEmpty) + assertEquals(1, backupRealm.where(StringOnly::class.java).count()) + assertEquals("Foo", backupRealm.where(StringOnly::class.java).findAll().first()!!.chars) + } + + // opening a Dynamic Realm should also work + DynamicRealm.getInstance(backupRealmConfiguration).use { dynamicRealm -> + dynamicRealm.schema.checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME) + val all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll() + assertEquals(1, all.size.toLong()) + assertEquals("Foo", all.first()!!.getString(StringOnly.FIELD_CHARS)) + } + looperThread.testComplete() + } + .modules(StringOnlyModule()) + .build() + val realm = Realm.getInstance(config) + realm.executeTransaction { + realm.createObject(StringOnly::class.java).chars = "Foo" + } + resources.add(realm) + + // Trigger error + user.app.sync.simulateClientReset(realm.syncSession) + } + + // Check that we can open the backup file without using the provided SyncConfiguration, + // this might be the case if the user decide to act upon the client reset later (providing s/he + // persisted the location of the file) + @Test + fun errorHandler_useBackupSyncConfigurationAfterClientReset() = looperThread.runBlocking { + val resources = ResourceContainer() + val config = configFactory.createSyncConfigurationBuilder(user) + .clientResyncMode(ClientResyncMode.MANUAL) + .errorHandler { session: SyncSession?, error: ObjectServerError -> + if (error.errorCode != ErrorCode.CLIENT_RESET) { + fail("Wrong error $error") + return@errorHandler + } + val handler = error as ClientResetRequiredError + // Execute Client Reset + resources.close() + handler.executeClientReset() + + // Validate that files have been moved + assertFalse(handler.originalFile.exists()) + assertTrue(handler.backupFile.exists()) + val backupFile = handler.backupFile.absolutePath + + // this SyncConf doesn't specify any module, it will throw a migration required + // exception since the backup Realm contain only StringOnly table + var backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile) + assertFailsWith { + Realm.getInstance(backupRealmConfiguration) + } + + // opening a DynamicRealm will work though + DynamicRealm.getInstance(backupRealmConfiguration).use { dynamicRealm -> + dynamicRealm.schema.checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME) + val all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll() + assertEquals(1, all.size.toLong()) + assertEquals("Foo", all.first()!!.getString(StringOnly.FIELD_CHARS)) + // make sure we can't write to it (read-only Realm) + assertFailsWith { + dynamicRealm.beginTransaction() + } + } + + assertFailsWith { + SyncConfiguration.forRecovery(backupFile, null, StringOnly::class.java) + } + + // specifying the module will allow to open the typed Realm + backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, null, StringOnlyModule()) + Realm.getInstance(backupRealmConfiguration).use { backupRealm -> + assertFalse(backupRealm.isEmpty) + assertEquals(1, backupRealm.where(StringOnly::class.java).count()) + val allSorted = backupRealm.where(StringOnly::class.java).findAll() + assertEquals("Foo", allSorted[0]!!.chars) + } + looperThread.testComplete() + } + .modules(StringOnlyModule()) + .build() + + val realm = Realm.getInstance(config) + realm.executeTransaction { + realm.createObject(StringOnly::class.java).chars = "Foo" + } + resources.add(realm) + + // Trigger error + user.app.sync.simulateClientReset(realm.syncSession) + } + + // make sure the backup file Realm is encrypted with the same key as the original synced Realm. + @Test + fun errorHandler_useClientResetEncrypted() = looperThread.runBlocking { + val resources = ResourceContainer() + + val randomKey = TestHelper.getRandomKey() + val config = configFactory.createSyncConfigurationBuilder(user) + .clientResyncMode(ClientResyncMode.MANUAL) + .encryptionKey(randomKey) + .modules(StringOnlyModule()) + .errorHandler { session: SyncSession?, error: ObjectServerError -> + if (error.errorCode != ErrorCode.CLIENT_RESET) { + fail("Wrong error $error") + return@errorHandler + } + val handler = error as ClientResetRequiredError + // Execute Client Reset + resources.close() + handler.executeClientReset() + var backupRealmConfiguration = handler.backupRealmConfiguration + + // can open encrypted backup Realm + Realm.getInstance(backupRealmConfiguration).use { backupEncryptedRealm -> + assertEquals(1, backupEncryptedRealm.where(StringOnly::class.java).count()) + val allSorted = backupEncryptedRealm.where(StringOnly::class.java).findAll() + assertEquals("Foo", allSorted[0]!!.chars) + } + val backupFile = handler.backupFile.absolutePath + + // build a conf to open a DynamicRealm + backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, randomKey, StringOnlyModule()) + Realm.getInstance(backupRealmConfiguration).use { backupEncryptedRealm -> + assertEquals(1, backupEncryptedRealm.where(StringOnly::class.java).count()) + val allSorted = backupEncryptedRealm.where(StringOnly::class.java).findAll() + assertEquals("Foo", allSorted[0]!!.chars) + } + + // using wrong key throw + assertFailsWith { + Realm.getInstance(SyncConfiguration.forRecovery(backupFile, TestHelper.getRandomKey(), StringOnlyModule())) + } + looperThread.testComplete() + } + .build() + + val realm = Realm.getInstance(config) + realm.executeTransaction { + realm.createObject(StringOnly::class.java).chars = "Foo" + } + resources.add(realm) + + // Trigger error + user.app.sync.simulateClientReset(realm.syncSession) + } + + @Test + @UiThreadTest + fun uploadAllLocalChanges_throwsOnUiThread() { + Realm.getInstance(configuration).use { realm -> + assertFailsWith { + realm.syncSession.uploadAllLocalChanges() + } + } + } + + @Test + @UiThreadTest + fun uploadAllLocalChanges_withTimeout_throwsOnUiThread() { + Realm.getInstance(configuration).use { realm -> + assertFailsWith { + realm.syncSession.uploadAllLocalChanges(30, TimeUnit.SECONDS) + } + } + } + + @Test + fun uploadAllLocalChanges_withTimeout_invalidParametersThrows() { + Realm.getInstance(configuration). use { realm -> + val session = realm.syncSession + assertFailsWith { + session.uploadAllLocalChanges(-1, TimeUnit.SECONDS) + } + assertFailsWith { + session.uploadAllLocalChanges(1, TestHelper.getNull()) + } + } + } + + @Test + fun uploadAllLocalChanges_returnFalseWhenTimedOut() { + Realm.getInstance(configuration).use { realm -> + val session = realm.syncSession + assertFalse(session.uploadAllLocalChanges(100, TimeUnit.MILLISECONDS)) + } + } + + @Test + @UiThreadTest + fun downloadAllServerChanges_throwsOnUiThread() { + Realm.getInstance(configuration).use {realm -> + assertFailsWith { + realm.syncSession.downloadAllServerChanges() + } + } + } + + @Test + @UiThreadTest + fun downloadAllServerChanges_withTimeout_throwsOnUiThread() { + Realm.getInstance(configuration).use { realm -> + assertFailsWith { + realm.syncSession.downloadAllServerChanges(30, TimeUnit.SECONDS) + } + } + } + + @Test + fun downloadAllServerChanges_withTimeout_invalidParametersThrows() { + Realm.getInstance(configuration).use { realm -> + val session = realm.syncSession + assertFailsWith { + session.downloadAllServerChanges(-1, TimeUnit.SECONDS) + } + assertFailsWith { + session.downloadAllServerChanges(1, TestHelper.getNull()) + } + } + } + + @Test + fun downloadAllServerChanges_returnFalseWhenTimedOut() { + Realm.getInstance(configuration).use { realm -> + val session = realm.syncSession + // We never assume to be able to download changes with one 1ms + assertFalse(session.downloadAllServerChanges(1, TimeUnit.MILLISECONDS)) + } + } + + @Test + @UiThreadTest + fun unrecognizedErrorCode_errorHandler() { + val errorHandlerCalled = AtomicBoolean(false) + configuration = configFactory.createSyncConfigurationBuilder(user) + .errorHandler { session: SyncSession?, error: ObjectServerError -> + errorHandlerCalled.set(true) + assertEquals(ErrorCode.UNKNOWN, error.errorCode) + assertEquals(ErrorCode.Category.FATAL, error.category) + } + .build() + + Realm.getInstance(configuration).use { realm -> + val session = realm.syncSession + val testLogger = TestLogger() + RealmLog.add(testLogger) + session.notifySessionError("unknown", 3, "Unknown Error") + RealmLog.remove(testLogger) + assertTrue(errorHandlerCalled.get()) + assertEquals("Unknown error code: 'unknown:3'", testLogger.message) + } + } + + // Closing the Realm should remove the session + @Test + fun getSessionThrowsOnNonExistingSession() { + Realm.getInstance(configuration).use { realm -> + val session = realm.syncSession + assertEquals(configuration, session.configuration) + // Exiting the scope closes the Realm and should remove the session + } + assertFailsWithMessage( + CoreMatchers.containsString( "No SyncSession found using the path : ") + ) { + app.sync.getSession(configuration) + } + } + + @Test + fun stop_doesNotThrowIfCalledWhenRealmIsClosed() { + val realm = Realm.getInstance(configuration) + val session = realm.syncSession + realm.close() + session.stop() + } + + // Smoke test of discouraged method of retrieving session + @Test + fun getOrCreateSession() { + assertNotNull(app.sync.getOrCreateSession(configuration)) + } + +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt index 6f5df18a3b..cd6710b806 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt @@ -1,11 +1,12 @@ package io.realm.util +import android.util.ArraySet import io.realm.ErrorCode import io.realm.ObjectServerError import org.hamcrest.Matcher import org.junit.Assert.* import org.junit.rules.ErrorCollector -import kotlin.test.assertFailsWith +import java.io.Closeable // Helper methods for improving Kotlin unit tests. @@ -42,3 +43,21 @@ inline fun assertFailsWithMessage(matcher: Matcher, bloc assertThat(e.message, matcher) } } + +/** + * A **resource container** to keep references for objects that should later be closed. + */ +class ResourceContainer : Closeable { + val resources = ArraySet() + + @Synchronized + override fun close() { + resources.map { it.close() } + } + + @Synchronized + fun add(resource: Closeable) { + resources.add(resource) + } +} + diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index d11a976a8c..b5e283f428 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -76,6 +76,9 @@ import org.bson.types.Decimal128; import org.bson.types.ObjectId; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + public class TestHelper { public static final int VERY_SHORT_WAIT_SECS = 1; public static final int SHORT_WAIT_SECS = 10; @@ -1318,6 +1321,12 @@ public static T getNull() { return null; } + // Workaround to cheat Kotlins type system when testing interop with Java + @Nonnull + public static T allowNull(@Nullable T value) { + return value; + } + public static String randomObjectIdHexString() { char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E' , 'F'}; From f6c4d3af98c41ea5515b590d2871bdac14da24a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 26 May 2020 16:56:40 +0200 Subject: [PATCH 1538/2110] Move integration tests to Kotlin source set --- .../{java => kotlin}/io/realm/SyncSessionTests.java | 0 .../{java => kotlin}/io/realm/SyncedRealmIntegrationTests.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename realm/realm-library/src/syncIntegrationTest/{java => kotlin}/io/realm/SyncSessionTests.java (100%) rename realm/realm-library/src/syncIntegrationTest/{java => kotlin}/io/realm/SyncedRealmIntegrationTests.java (100%) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.java similarity index 100% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncSessionTests.java rename to realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.java diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java similarity index 100% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java rename to realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java From 1a254cf82a57246fb212b5227b4451f2c359c3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 26 May 2020 17:10:10 +0200 Subject: [PATCH 1539/2110] Automatic conversion of integration test to Kotlin --- realm/realm-library/build.gradle | 5 +- .../kotlin/io/realm/SyncSessionTests.java | 661 ------------------ .../kotlin/io/realm/SyncSessionTests.kt | 575 +++++++++++++++ .../io/realm/SyncedRealmIntegrationTests.java | 521 -------------- .../io/realm/SyncedRealmIntegrationTests.kt | 459 ++++++++++++ 5 files changed, 1038 insertions(+), 1183 deletions(-) delete mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt delete mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index d4e694f7ea..1140d96420 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -104,7 +104,10 @@ android { java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java', 'src/testUtils/kotlin'] } androidTestObjectServer { - java.srcDirs += [/* FIXME 'src/syncIntegrationTest/java', */ 'src/androidTestObjectServer/kotlin', 'src/syncTestUtils/java'] + java.srcDirs += [/* FIXME 'src/syncIntegrationTest/java', */ + 'src/syncIntegrationTest/kotlin', + 'src/androidTestObjectServer/kotlin', 'src/syncTestUtils/java' + ] assets.srcDirs += ['src/syncIntegrationTest/assets/'] } } diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.java b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.java deleted file mode 100644 index a03b2e30eb..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.java +++ /dev/null @@ -1,661 +0,0 @@ -package io.realm; - -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.SystemClock; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import io.realm.entities.AllTypes; -import io.realm.entities.StringOnly; -import io.realm.exceptions.DownloadingRealmInterruptedException; -import io.realm.internal.OsRealmConfig; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.StringOnlyModule; -import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.RunTestInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class SyncSessionTests extends StandardIntegrationTest { - - @Rule - public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - - private interface SessionCallback { - void onReady(SyncSession session); - } - - private void getSession(SessionCallback callback) { - // Work-around for a race condition happening when shutting down a Looper test and - // Resetting the SyncManager - // The problem is the `@After` block which runs as soon as the test method has completed. - // For integration tests this will attempt to reset the SyncManager which will fail - // if Realms are still open as they hold a reference to a session object. - // By moving this into a Looper callback we ensure that a looper test can shutdown as - // intended. - // Generally it seems that using calling `RunInLooperThread.testComplete()` in a synchronous - looperThread.postRunnable((Runnable) () -> { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - looperThread.closeAfterTest(Realm.getInstance(syncConfiguration)); - callback.onReady(SyncManager.getSession(syncConfiguration)); - }); - } - - private void getActiveSession(SessionCallback callback) { - getSession(session -> { - if (session.isConnected()) { - callback.onReady(session); - } else { - session.addConnectionChangeListener(new ConnectionListener() { - @Override - public void onChange(ConnectionState oldState, ConnectionState newState) { - if (newState == ConnectionState.CONNECTED) { - session.removeConnectionChangeListener(this); - callback.onReady(session); - } - } - }); - } - }); - } - - @Test(timeout=3000) - public void getState_active() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - Realm realm = Realm.getInstance(syncConfiguration); - - SyncSession session = SyncManager.getSession(syncConfiguration); - - // make sure the `access_token` is acquired. otherwise we can still be - // in WAITING_FOR_ACCESS_TOKEN state - while(session.getState() != SyncSession.State.ACTIVE) { - SystemClock.sleep(200); - } - - realm.close(); - } - - @Test - public void getState_throwOnClosedSession() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - Realm realm = Realm.getInstance(syncConfiguration); - - SyncSession session = SyncManager.getSession(syncConfiguration); - realm.close(); - user.logOut(); - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Could not find session, Realm was probably closed"); - session.getState(); - } - - @Test - public void getState_loggedOut() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - Realm realm = Realm.getInstance(syncConfiguration); - - SyncSession session = SyncManager.getSession(syncConfiguration); - - user.logOut(); - - SyncSession.State state = session.getState(); - assertEquals(SyncSession.State.INACTIVE, state); - - realm.close(); - } - - @Test - public void uploadDownloadAllChanges() throws InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncConfiguration userConfig = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - SyncConfiguration adminConfig = configFactory - .createSyncConfigurationBuilder(adminUser, userConfig.getServerUrl().toString()) - .build(); - - Realm userRealm = Realm.getInstance(userConfig); - userRealm.beginTransaction(); - userRealm.createObject(AllTypes.class); - userRealm.commitTransaction(); - SyncManager.getSession(userConfig).uploadAllLocalChanges(); - userRealm.close(); - - Realm adminRealm = Realm.getInstance(adminConfig); - SyncManager.getSession(adminConfig).downloadAllServerChanges(); - adminRealm.refresh(); - assertEquals(1, adminRealm.where(AllTypes.class).count()); - adminRealm.close(); - } - - @Test - public void interruptWaits() throws InterruptedException { - final SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - final SyncConfiguration userConfig = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - final SyncConfiguration adminConfig = configFactory - .createSyncConfigurationBuilder(adminUser, userConfig.getServerUrl().toString()) - .build(); - - Thread t = new Thread(new Runnable() { - @Override - public void run() { - Realm userRealm = Realm.getInstance(userConfig); - userRealm.beginTransaction(); - userRealm.createObject(AllTypes.class); - userRealm.commitTransaction(); - SyncSession userSession = SyncManager.getSession(userConfig); - try { - // 1. Start download (which will be interrupted) - Thread.currentThread().interrupt(); - userSession.downloadAllServerChanges(); - } catch (InterruptedException ignored) { - assertFalse(Thread.currentThread().isInterrupted()); - } - try { - // 2. Upload all changes - userSession.uploadAllLocalChanges(); - } catch (InterruptedException e) { - fail("Upload interrupted"); - } - userRealm.close(); - - Realm adminRealm = Realm.getInstance(adminConfig); - SyncSession adminSession = SyncManager.getSession(adminConfig); - try { - // 3. Start upload (which will be interrupted) - Thread.currentThread().interrupt(); - adminSession.uploadAllLocalChanges(); - } catch (InterruptedException ignored) { - assertFalse(Thread.currentThread().isInterrupted()); // clear interrupted flag - } - try { - // 4. Download all changes - adminSession.downloadAllServerChanges(); - } catch (InterruptedException e) { - fail("Download interrupted"); - } - adminRealm.refresh(); - assertEquals(1, adminRealm.where(AllTypes.class).count()); - adminRealm.close(); - } - }); - t.start(); - t.join(); - } - - // check that logging out a SyncUser used by different Realm will - // affect all associated sessions. - @Test(timeout=5000) - public void logout_sameSyncUserMultipleSessions() { - String uniqueName = UUID.randomUUID().toString(); - SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - - SyncConfiguration syncConfiguration1 = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - Realm realm1 = Realm.getInstance(syncConfiguration1); - - SyncConfiguration syncConfiguration2 = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL_2) - .build(); - Realm realm2 = Realm.getInstance(syncConfiguration2); - - SyncSession session1 = SyncManager.getSession(syncConfiguration1); - SyncSession session2 = SyncManager.getSession(syncConfiguration2); - - // make sure the `access_token` is acquired. otherwise we can still be - // in WAITING_FOR_ACCESS_TOKEN state - while(session1.getState() != SyncSession.State.ACTIVE || session2.getState() != SyncSession.State.ACTIVE) { - SystemClock.sleep(200); - } - assertEquals(SyncSession.State.ACTIVE, session1.getState()); - assertEquals(SyncSession.State.ACTIVE, session2.getState()); - assertNotEquals(session1, session2); - - assertEquals(session1.getUser(), session2.getUser()); - - user.logOut(); - - assertEquals(SyncSession.State.INACTIVE, session1.getState()); - assertEquals(SyncSession.State.INACTIVE, session2.getState()); - - credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); - SyncUser.logIn(credentials, Constants.AUTH_URL); - - // reviving the sessions. The state could be changed concurrently. - assertTrue(session1.getState() == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || - session1.getState() == SyncSession.State.ACTIVE); - assertTrue(session2.getState() == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || - session2.getState() == SyncSession.State.ACTIVE); - - realm1.close(); - realm2.close(); - } - - // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. - @Test - public void logBackResumeUpload() throws InterruptedException { - final String uniqueName = UUID.randomUUID().toString(); - SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - - final SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .modules(new StringOnlyModule()) - .waitForInitialRemoteData() - .build(); - final Realm realm = Realm.getInstance(syncConfiguration); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObject(StringOnly.class).setChars("1"); - } - }); - - final SyncSession session = SyncManager.getSession(syncConfiguration); - session.uploadAllLocalChanges(); - - user.logOut(); - - // add a commit while we're still offline - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObject(StringOnly.class).setChars("2"); - } - }); - - final CountDownLatch testCompleted = new CountDownLatch(1); - - final HandlerThread handlerThread = new HandlerThread("HandlerThread"); - handlerThread.start(); - Looper looper = handlerThread.getLooper(); - Handler handler = new Handler(looper); - AtomicReference> allResults = new AtomicReference<>();// notifier could be GC'ed before it get a chance to trigger the second commit, so declaring it outside the Runnable - handler.post(new Runnable() { - @Override - public void run() { - // access the Realm from an different path on the device (using admin user), then monitor - // when the offline commits get synchronized - SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); - SyncUser adminUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL); - - SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) - .modules(new StringOnlyModule()) - .waitForInitialRemoteData() - .build(); - final Realm adminRealm = Realm.getInstance(adminConfig); - allResults.set(adminRealm.where(StringOnly.class).sort(StringOnly.FIELD_CHARS).findAll()); - RealmChangeListener> realmChangeListener = new RealmChangeListener>() { - @Override - public void onChange(RealmResults stringOnlies) { - if (stringOnlies.size() == 2) { - Assert.assertEquals("1", stringOnlies.get(0).getChars()); - Assert.assertEquals("2", stringOnlies.get(1).getChars()); - handler.post(() -> { - // Closing a Realm from inside a listener doesn't seem to remove the - // active session reference in Object Store - adminRealm.close(); - testCompleted.countDown(); - handlerThread.quitSafely(); - }); - } - } - }; - allResults.get().addChangeListener(realmChangeListener); - - // login again to re-activate the user - SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); - // this login will re-activate the logged out user, and resume all it's pending sessions - // the OS will trigger bindSessionWithConfig with the new refresh_token, in order to obtain - // a new access_token. - SyncUser.logIn(credentials, Constants.AUTH_URL); - } - }); - - TestHelper.awaitOrFail(testCompleted); - realm.close(); - } - - // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. - // this test validate the behaviour of SyncSessionStopPolicy::AfterChangesUploaded - @Test - public void uploadChangesWhenRealmOutOfScope() throws InterruptedException { - final List strongRefs = new ArrayList<>(); - final String uniqueName = UUID.randomUUID().toString(); - SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - - final char[] chars = new char[1_000_000];// 2MB - Arrays.fill(chars, '.'); - final String twoMBString = new String(chars); - - final SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED) - .modules(new StringOnlyModule()) - .build(); - Realm realm = Realm.getInstance(syncConfiguration); - - realm.beginTransaction(); - // upload 10MB - for (int i = 0; i < 5; i++) { - realm.createObject(StringOnly.class).setChars(twoMBString); - } - realm.commitTransaction(); - realm.close(); - - final CountDownLatch testCompleted = new CountDownLatch(1); - - final HandlerThread handlerThread = new HandlerThread("HandlerThread"); - handlerThread.start(); - Looper looper = handlerThread.getLooper(); - Handler handler = new Handler(looper); - handler.post(new Runnable() { - @Override - public void run() { - // using an admin user to open the Realm on different path on the device to monitor when all the uploads are done - SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - - SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(admin, syncConfiguration.getServerUrl().toString()) - .modules(new StringOnlyModule()) - .build(); - final Realm adminRealm = Realm.getInstance(adminConfig); - RealmResults all = adminRealm.where(StringOnly.class).findAll(); - - if (all.size() == 5) { - adminRealm.close(); - testCompleted.countDown(); - handlerThread.quit(); - } else { - strongRefs.add(all); - OrderedRealmCollectionChangeListener> realmChangeListener = (results, changeSet) -> { - if (results.size() == 5) { - adminRealm.close(); - testCompleted.countDown(); - handlerThread.quit(); - } - }; - all.addChangeListener(realmChangeListener); - } - } - }); - - TestHelper.awaitOrFail(testCompleted, TestHelper.STANDARD_WAIT_SECS); - handlerThread.join(); - - user.logOut(); - } - - // A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. - @Test - public void downloadChangesWhenRealmOutOfScope() throws InterruptedException { - final String uniqueName = UUID.randomUUID().toString(); - SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - - final SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .modules(new StringOnlyModule()) - .build(); - Realm realm = Realm.getInstance(syncConfiguration); - - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("1"); - realm.commitTransaction(); - - SyncSession session = SyncManager.getSession(syncConfiguration); - session.uploadAllLocalChanges(); - - // Log out the user. - user.logOut(); - - // Log the user back in. - credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); - SyncUser.logIn(credentials, Constants.AUTH_URL); - - // now let the admin upload some commits - final CountDownLatch backgroundUpload = new CountDownLatch(1); - - final HandlerThread handlerThread = new HandlerThread("HandlerThread"); - handlerThread.start(); - Looper looper = handlerThread.getLooper(); - Handler handler = new Handler(looper); - handler.post(new Runnable() { - @Override - public void run() { - // using an admin user to open the Realm on different path on the device then some commits - SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); - SyncUser adminUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL); - - SyncConfiguration adminConfig = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.getServerUrl().toString()) - .modules(new StringOnlyModule()) - .waitForInitialRemoteData() - .build(); - - final Realm adminRealm = Realm.getInstance(adminConfig); - adminRealm.beginTransaction(); - adminRealm.createObject(StringOnly.class).setChars("2"); - adminRealm.createObject(StringOnly.class).setChars("3"); - adminRealm.commitTransaction(); - - try { - SyncManager.getSession(adminConfig).uploadAllLocalChanges(); - } catch (InterruptedException e) { - e.printStackTrace(); - fail(e.getMessage()); - } - adminRealm.close(); - - backgroundUpload.countDown(); - handlerThread.quit(); - } - }); - - TestHelper.awaitOrFail(backgroundUpload, 60); - // Resume downloading - session.downloadAllServerChanges(); - realm.refresh();//FIXME not calling refresh will still point to the previous version of the Realm count == 1 - assertEquals(3, realm.where(StringOnly.class).count()); - realm.close(); - } - - // Check that if we manually trigger a Client Reset, then it should be possible to start - // downloading the Realm immediately after. - @Test - @RunTestInLooperThread - public void clientReset_manualTriggerAllowSessionToRestart() { - final String uniqueName = UUID.randomUUID().toString(); - SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - - final AtomicReference configRef = new AtomicReference<>(null); - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .clientResyncMode(ClientResyncMode.MANUAL) - .directory(looperThread.getRoot()) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - final ClientResetRequiredError handler = (ClientResetRequiredError) error; - // Execute Client Reset - looperThread.closeTestRealms(); - handler.executeClientReset(); - - // Try to re-open Realm and download it again - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - // Validate that files have been moved - assertFalse(handler.getOriginalFile().exists()); - assertTrue(handler.getBackupFile().exists()); - - SyncConfiguration config = configRef.get(); - Realm instance = Realm.getInstance(config); - looperThread.addTestRealm(instance); - try { - SyncManager.getSession(config).downloadAllServerChanges(); - looperThread.testComplete(); - } catch (InterruptedException e) { - fail(e.toString()); - } - } - }); - } - }) - .build(); - configRef.set(config); - - Realm realm = Realm.getInstance(config); - looperThread.addTestRealm(realm); - // Trigger error - SyncManager.simulateClientReset(SyncManager.getSession(config)); - } - - @Test - @RunTestInLooperThread - public void registerConnectionListener() { - getSession(session -> { - session.addConnectionChangeListener((oldState, newState) -> { - if (newState == ConnectionState.DISCONNECTED) { - // Closing a Realm inside a connection listener doesn't work: https://github.com/realm/realm-java/issues/6249 - looperThread.postRunnable(() -> looperThread.testComplete()); - } - }); - session.stop(); - }); - } - - @Test - @RunTestInLooperThread - public void removeConnectionListener() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build(); - Realm realm = Realm.getInstance(syncConfiguration); - SyncSession session = SyncManager.getSession(syncConfiguration); - ConnectionListener listener1 = (oldState, newState) -> { - if (newState == ConnectionState.DISCONNECTED) { - fail("Listener should have been removed"); - } - }; - ConnectionListener listener2 = (oldState, newState) -> { - if (newState == ConnectionState.DISCONNECTED) { - looperThread.testComplete(); - } - }; - - session.addConnectionChangeListener(listener1); - session.addConnectionChangeListener(listener2); - session.removeConnectionChangeListener(listener1); - realm.close(); - } - - @Test - @RunTestInLooperThread - public void isConnected() { - getActiveSession(session -> { - assertEquals(session.getConnectionState(), ConnectionState.CONNECTED); - assertTrue(session.isConnected()); - looperThread.testComplete(); - }); - } - - @Test - @RunTestInLooperThread - public void stopStartSession() { - getActiveSession(session -> { - assertEquals(SyncSession.State.ACTIVE, session.getState()); - session.stop(); - assertEquals(SyncSession.State.INACTIVE, session.getState()); - session.start(); - assertNotEquals(SyncSession.State.INACTIVE, session.getState()); - looperThread.testComplete(); - }); - } - - @Test - @RunTestInLooperThread - public void start_multipleTimes() { - getActiveSession(session -> { - session.start(); - assertEquals(SyncSession.State.ACTIVE, session.getState()); - session.start(); - assertEquals(SyncSession.State.ACTIVE, session.getState()); - looperThread.testComplete(); - }); - } - - - @Test - @RunTestInLooperThread - public void stop_multipleTimes() { - getSession(session -> { - session.stop(); - assertEquals(SyncSession.State.INACTIVE, session.getState()); - session.stop(); - assertEquals(SyncSession.State.INACTIVE, session.getState()); - looperThread.testComplete(); - }); - } - - @Test - @RunTestInLooperThread - public void waitForInitialRemoteData_throwsOnTimeout() { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .initialData(bgRealm -> { - for (int i = 0; i < 100; i++) { - bgRealm.createObject(AllTypes.class); - } - }) - .waitForInitialRemoteData(1, TimeUnit.MILLISECONDS) - .build(); - - try { - Realm.getInstance(syncConfiguration); - fail("This should have timed out"); - } catch (DownloadingRealmInterruptedException ignore) { - } - looperThread.testComplete(); - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt new file mode 100644 index 0000000000..9213848c1f --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -0,0 +1,575 @@ +package io.realm + +import android.os.Handler +import android.os.HandlerThread +import android.os.SystemClock +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.SyncTestUtils +import io.realm.entities.AllTypes +import io.realm.entities.StringOnly +import io.realm.exceptions.DownloadingRealmInterruptedException +import io.realm.internal.OsRealmConfig +import io.realm.objectserver.utils.Constants +import io.realm.objectserver.utils.StringOnlyModule +import io.realm.objectserver.utils.UserFactory +import io.realm.rule.RunTestInLooperThread +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference + +@RunWith(AndroidJUnit4::class) +class SyncSessionTests : StandardIntegrationTest() { + @Rule + var configFactory = TestSyncConfigurationFactory() + + private interface SessionCallback { + fun onReady(session: SyncSession?) + } + + private fun getSession(callback: SessionCallback) { + // Work-around for a race condition happening when shutting down a Looper test and + // Resetting the SyncManager + // The problem is the `@After` block which runs as soon as the test method has completed. + // For integration tests this will attempt to reset the SyncManager which will fail + // if Realms are still open as they hold a reference to a session object. + // By moving this into a Looper callback we ensure that a looper test can shutdown as + // intended. + // Generally it seems that using calling `RunInLooperThread.testComplete()` in a synchronous + looperThread.postRunnable(Runnable { + val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build() + looperThread.closeAfterTest(Realm.getInstance(syncConfiguration)) + callback.onReady(SyncManager.getSession(syncConfiguration)) + }) + } + + private fun getActiveSession(callback: SessionCallback) { + getSession(SessionCallback { session: SyncSession -> + if (session.isConnected) { + callback.onReady(session) + } else { + session.addConnectionChangeListener(object : ConnectionListener { + override fun onChange(oldState: ConnectionState, newState: ConnectionState) { + if (newState == ConnectionState.CONNECTED) { + session.removeConnectionChangeListener(this) + callback.onReady(session) + } + } + }) + } + }) + } + + // make sure the `access_token` is acquired. otherwise we can still be + // in WAITING_FOR_ACCESS_TOKEN state + @get:Test(timeout = 3000) + val state_active: Unit + get() { + val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build() + val realm = Realm.getInstance(syncConfiguration) + val session: SyncSession = SyncManager.getSession(syncConfiguration) + + // make sure the `access_token` is acquired. otherwise we can still be + // in WAITING_FOR_ACCESS_TOKEN state + while (session.state != SyncSession.State.ACTIVE) { + SystemClock.sleep(200) + } + realm.close() + } + + @get:Test + val state_throwOnClosedSession: Unit + get() { + val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build() + val realm = Realm.getInstance(syncConfiguration) + val session: SyncSession = SyncManager.getSession(syncConfiguration) + realm.close() + user.logOut() + thrown.expect(IllegalStateException::class.java) + thrown.expectMessage("Could not find session, Realm was probably closed") + session.state + } + + @get:Test + val state_loggedOut: Unit + get() { + val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build() + val realm = Realm.getInstance(syncConfiguration) + val session: SyncSession = SyncManager.getSession(syncConfiguration) + user.logOut() + val state = session.state + Assert.assertEquals(SyncSession.State.INACTIVE, state) + realm.close() + } + + @Test + @Throws(InterruptedException::class) + fun uploadDownloadAllChanges() { + val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + val adminUser: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) + val userConfig = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build() + val adminConfig = configFactory + .createSyncConfigurationBuilder(adminUser, userConfig.serverUrl.toString()) + .build() + val userRealm = Realm.getInstance(userConfig) + userRealm.beginTransaction() + userRealm.createObject(AllTypes::class.java) + userRealm.commitTransaction() + SyncManager.getSession(userConfig).uploadAllLocalChanges() + userRealm.close() + val adminRealm = Realm.getInstance(adminConfig) + SyncManager.getSession(adminConfig).downloadAllServerChanges() + adminRealm.refresh() + Assert.assertEquals(1, adminRealm.where(AllTypes::class.java).count()) + adminRealm.close() + } + + @Test + @Throws(InterruptedException::class) + fun interruptWaits() { + val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + val adminUser: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) + val userConfig = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build() + val adminConfig = configFactory + .createSyncConfigurationBuilder(adminUser, userConfig.serverUrl.toString()) + .build() + val t = Thread(Runnable { + val userRealm = Realm.getInstance(userConfig) + userRealm.beginTransaction() + userRealm.createObject(AllTypes::class.java) + userRealm.commitTransaction() + val userSession: SyncSession = SyncManager.getSession(userConfig) + try { + // 1. Start download (which will be interrupted) + Thread.currentThread().interrupt() + userSession.downloadAllServerChanges() + } catch (ignored: InterruptedException) { + Assert.assertFalse(Thread.currentThread().isInterrupted) + } + try { + // 2. Upload all changes + userSession.uploadAllLocalChanges() + } catch (e: InterruptedException) { + Assert.fail("Upload interrupted") + } + userRealm.close() + val adminRealm = Realm.getInstance(adminConfig) + val adminSession: SyncSession = SyncManager.getSession(adminConfig) + try { + // 3. Start upload (which will be interrupted) + Thread.currentThread().interrupt() + adminSession.uploadAllLocalChanges() + } catch (ignored: InterruptedException) { + Assert.assertFalse(Thread.currentThread().isInterrupted) // clear interrupted flag + } + try { + // 4. Download all changes + adminSession.downloadAllServerChanges() + } catch (e: InterruptedException) { + Assert.fail("Download interrupted") + } + adminRealm.refresh() + Assert.assertEquals(1, adminRealm.where(AllTypes::class.java).count()) + adminRealm.close() + }) + t.start() + t.join() + } + + // check that logging out a SyncUser used by different Realm will + // affect all associated sessions. + @Test(timeout = 5000) + fun logout_sameSyncUserMultipleSessions() { + val uniqueName = UUID.randomUUID().toString() + var credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val syncConfiguration1 = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build() + val realm1 = Realm.getInstance(syncConfiguration1) + val syncConfiguration2 = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL_2) + .build() + val realm2 = Realm.getInstance(syncConfiguration2) + val session1: SyncSession = SyncManager.getSession(syncConfiguration1) + val session2: SyncSession = SyncManager.getSession(syncConfiguration2) + + // make sure the `access_token` is acquired. otherwise we can still be + // in WAITING_FOR_ACCESS_TOKEN state + while (session1.state != SyncSession.State.ACTIVE || session2.state != SyncSession.State.ACTIVE) { + SystemClock.sleep(200) + } + Assert.assertEquals(SyncSession.State.ACTIVE, session1.state) + Assert.assertEquals(SyncSession.State.ACTIVE, session2.state) + Assert.assertNotEquals(session1, session2) + Assert.assertEquals(session1.user, session2.user) + user.logOut() + Assert.assertEquals(SyncSession.State.INACTIVE, session1.state) + Assert.assertEquals(SyncSession.State.INACTIVE, session2.state) + credentials = SyncCredentials.usernamePassword(uniqueName, "password", false) + SyncUser.logIn(credentials, Constants.AUTH_URL) + + // reviving the sessions. The state could be changed concurrently. + Assert.assertTrue(session1.state == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || + session1.state == SyncSession.State.ACTIVE) + Assert.assertTrue(session2.state == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || + session2.state == SyncSession.State.ACTIVE) + realm1.close() + realm2.close() + } + + // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. + @Test + @Throws(InterruptedException::class) + fun logBackResumeUpload() { + val uniqueName = UUID.randomUUID().toString() + val credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .modules(StringOnlyModule()) + .waitForInitialRemoteData() + .build() + val realm = Realm.getInstance(syncConfiguration) + realm.executeTransaction { realm -> realm.createObject(StringOnly::class.java).chars = "1" } + val session: SyncSession = SyncManager.getSession(syncConfiguration) + session.uploadAllLocalChanges() + user.logOut() + + // add a commit while we're still offline + realm.executeTransaction { realm -> realm.createObject(StringOnly::class.java).chars = "2" } + val testCompleted = CountDownLatch(1) + val handlerThread = HandlerThread("HandlerThread") + handlerThread.start() + val looper = handlerThread.looper + val handler = Handler(looper) + val allResults = AtomicReference>() // notifier could be GC'ed before it get a chance to trigger the second commit, so declaring it outside the Runnable + handler.post { // access the Realm from an different path on the device (using admin user), then monitor + // when the offline commits get synchronized + val admin: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) + val credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user") + val adminUser: SyncUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL) + val adminConfig: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.serverUrl.toString()) + .modules(StringOnlyModule()) + .waitForInitialRemoteData() + .build() + val adminRealm = Realm.getInstance(adminConfig) + allResults.set(adminRealm.where(StringOnly::class.java).sort(StringOnly.FIELD_CHARS).findAll()) + val realmChangeListener: RealmChangeListener> = object : RealmChangeListener?> { + override fun onChange(stringOnlies: RealmResults) { + if (stringOnlies.size == 2) { + Assert.assertEquals("1", stringOnlies[0]!!.chars) + Assert.assertEquals("2", stringOnlies[1]!!.chars) + handler.post { + + // Closing a Realm from inside a listener doesn't seem to remove the + // active session reference in Object Store + adminRealm.close() + testCompleted.countDown() + handlerThread.quitSafely() + } + } + } + } + allResults.get().addChangeListener(realmChangeListener) + + // login again to re-activate the user + val credentials = SyncCredentials.usernamePassword(uniqueName, "password", false) + // this login will re-activate the logged out user, and resume all it's pending sessions + // the OS will trigger bindSessionWithConfig with the new refresh_token, in order to obtain + // a new access_token. + SyncUser.logIn(credentials, Constants.AUTH_URL) + } + TestHelper.awaitOrFail(testCompleted) + realm.close() + } + + // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. + // this test validate the behaviour of SyncSessionStopPolicy::AfterChangesUploaded + @Test + @Throws(InterruptedException::class) + fun uploadChangesWhenRealmOutOfScope() { + val strongRefs: MutableList = ArrayList() + val uniqueName = UUID.randomUUID().toString() + val credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val chars = CharArray(1000000) // 2MB + Arrays.fill(chars, '.') + val twoMBString = String(chars) + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED) + .modules(StringOnlyModule()) + .build() + val realm = Realm.getInstance(syncConfiguration) + realm.beginTransaction() + // upload 10MB + for (i in 0..4) { + realm.createObject(StringOnly::class.java).chars = twoMBString + } + realm.commitTransaction() + realm.close() + val testCompleted = CountDownLatch(1) + val handlerThread = HandlerThread("HandlerThread") + handlerThread.start() + val looper = handlerThread.looper + val handler = Handler(looper) + handler.post { // using an admin user to open the Realm on different path on the device to monitor when all the uploads are done + val admin: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) + val adminConfig: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(admin, syncConfiguration.serverUrl.toString()) + .modules(StringOnlyModule()) + .build() + val adminRealm = Realm.getInstance(adminConfig) + val all = adminRealm.where(StringOnly::class.java).findAll() + if (all.size == 5) { + adminRealm.close() + testCompleted.countDown() + handlerThread.quit() + } else { + strongRefs.add(all) + val realmChangeListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet? -> + if (results.size == 5) { + adminRealm.close() + testCompleted.countDown() + handlerThread.quit() + } + } + all.addChangeListener(realmChangeListener) + } + } + TestHelper.awaitOrFail(testCompleted, TestHelper.STANDARD_WAIT_SECS) + handlerThread.join() + user.logOut() + } + + // A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. + @Test + @Throws(InterruptedException::class) + fun downloadChangesWhenRealmOutOfScope() { + val uniqueName = UUID.randomUUID().toString() + var credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .modules(StringOnlyModule()) + .build() + val realm = Realm.getInstance(syncConfiguration) + realm.beginTransaction() + realm.createObject(StringOnly::class.java).chars = "1" + realm.commitTransaction() + val session: SyncSession = SyncManager.getSession(syncConfiguration) + session.uploadAllLocalChanges() + + // Log out the user. + user.logOut() + + // Log the user back in. + credentials = SyncCredentials.usernamePassword(uniqueName, "password", false) + SyncUser.logIn(credentials, Constants.AUTH_URL) + + // now let the admin upload some commits + val backgroundUpload = CountDownLatch(1) + val handlerThread = HandlerThread("HandlerThread") + handlerThread.start() + val looper = handlerThread.looper + val handler = Handler(looper) + handler.post { // using an admin user to open the Realm on different path on the device then some commits + val admin: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) + val credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user") + val adminUser: SyncUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL) + val adminConfig: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.serverUrl.toString()) + .modules(StringOnlyModule()) + .waitForInitialRemoteData() + .build() + val adminRealm = Realm.getInstance(adminConfig) + adminRealm.beginTransaction() + adminRealm.createObject(StringOnly::class.java).chars = "2" + adminRealm.createObject(StringOnly::class.java).chars = "3" + adminRealm.commitTransaction() + try { + SyncManager.getSession(adminConfig).uploadAllLocalChanges() + } catch (e: InterruptedException) { + e.printStackTrace() + Assert.fail(e.message) + } + adminRealm.close() + backgroundUpload.countDown() + handlerThread.quit() + } + TestHelper.awaitOrFail(backgroundUpload, 60) + // Resume downloading + session.downloadAllServerChanges() + realm.refresh() //FIXME not calling refresh will still point to the previous version of the Realm count == 1 + Assert.assertEquals(3, realm.where(StringOnly::class.java).count()) + realm.close() + } + + // Check that if we manually trigger a Client Reset, then it should be possible to start + // downloading the Realm immediately after. + @Test + @RunTestInLooperThread + fun clientReset_manualTriggerAllowSessionToRestart() { + val uniqueName = UUID.randomUUID().toString() + val credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val configRef = AtomicReference(null) + val config: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .clientResyncMode(ClientResyncMode.MANUAL) + .directory(looperThread.getRoot()) + .errorHandler(SyncSession.ErrorHandler { session, error -> + val handler = error as ClientResetRequiredError + // Execute Client Reset + looperThread.closeTestRealms() + handler.executeClientReset() + + // Try to re-open Realm and download it again + looperThread.postRunnable(Runnable { // Validate that files have been moved + Assert.assertFalse(handler.originalFile.exists()) + Assert.assertTrue(handler.backupFile.exists()) + val config = configRef.get() + val instance = Realm.getInstance(config!!) + looperThread.addTestRealm(instance) + try { + SyncManager.getSession(config).downloadAllServerChanges() + looperThread.testComplete() + } catch (e: InterruptedException) { + Assert.fail(e.toString()) + } + }) + }) + .build() + configRef.set(config) + val realm = Realm.getInstance(config) + looperThread.addTestRealm(realm) + // Trigger error + SyncManager.simulateClientReset(SyncManager.getSession(config)) + } + + @Test + @RunTestInLooperThread + fun registerConnectionListener() { + getSession(SessionCallback { session: SyncSession -> + session.addConnectionChangeListener { oldState: ConnectionState?, newState: ConnectionState -> + if (newState == ConnectionState.DISCONNECTED) { + // Closing a Realm inside a connection listener doesn't work: https://github.com/realm/realm-java/issues/6249 + looperThread.postRunnable({ looperThread.testComplete() }) + } + } + session.stop() + }) + } + + @Test + @RunTestInLooperThread + fun removeConnectionListener() { + val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .build() + val realm = Realm.getInstance(syncConfiguration) + val session: SyncSession = SyncManager.getSession(syncConfiguration) + val listener1 = ConnectionListener { oldState: ConnectionState?, newState: ConnectionState -> + if (newState == ConnectionState.DISCONNECTED) { + Assert.fail("Listener should have been removed") + } + } + val listener2 = ConnectionListener { oldState: ConnectionState?, newState: ConnectionState -> + if (newState == ConnectionState.DISCONNECTED) { + looperThread.testComplete() + } + } + session.addConnectionChangeListener(listener1) + session.addConnectionChangeListener(listener2) + session.removeConnectionChangeListener(listener1) + realm.close() + } + + @get:RunTestInLooperThread + @get:Test + val isConnected: Unit + get() { + getActiveSession(SessionCallback { session: SyncSession -> + Assert.assertEquals(session.connectionState, ConnectionState.CONNECTED) + Assert.assertTrue(session.isConnected) + looperThread.testComplete() + }) + } + + @Test + @RunTestInLooperThread + fun stopStartSession() { + getActiveSession(SessionCallback { session: SyncSession -> + Assert.assertEquals(SyncSession.State.ACTIVE, session.state) + session.stop() + Assert.assertEquals(SyncSession.State.INACTIVE, session.state) + session.start() + Assert.assertNotEquals(SyncSession.State.INACTIVE, session.state) + looperThread.testComplete() + }) + } + + @Test + @RunTestInLooperThread + fun start_multipleTimes() { + getActiveSession(SessionCallback { session: SyncSession -> + session.start() + Assert.assertEquals(SyncSession.State.ACTIVE, session.state) + session.start() + Assert.assertEquals(SyncSession.State.ACTIVE, session.state) + looperThread.testComplete() + }) + } + + @Test + @RunTestInLooperThread + fun stop_multipleTimes() { + getSession(SessionCallback { session: SyncSession -> + session.stop() + Assert.assertEquals(SyncSession.State.INACTIVE, session.state) + session.stop() + Assert.assertEquals(SyncSession.State.INACTIVE, session.state) + looperThread.testComplete() + }) + } + + @Test + @RunTestInLooperThread + fun waitForInitialRemoteData_throwsOnTimeout() { + val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .initialData { bgRealm: Realm -> + for (i in 0..99) { + bgRealm.createObject(AllTypes::class.java) + } + } + .waitForInitialRemoteData(1, TimeUnit.MILLISECONDS) + .build() + try { + Realm.getInstance(syncConfiguration) + Assert.fail("This should have timed out") + } catch (ignore: DownloadingRealmInterruptedException) { + } + looperThread.testComplete() + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java deleted file mode 100644 index a6f04205e5..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java +++ /dev/null @@ -1,521 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.os.SystemClock; -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.realm.entities.AllTypes; -import io.realm.entities.StringOnly; -import io.realm.exceptions.DownloadingRealmInterruptedException; -import io.realm.exceptions.RealmMigrationNeededException; -import io.realm.internal.OsRealmConfig; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; -import io.realm.log.RealmLogger; -import io.realm.objectserver.utils.Constants; -import io.realm.rule.RunTestInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - - -/** - * Catch all class for tests that not naturally fit anywhere else. - */ -@RunWith(AndroidJUnit4.class) -public class SyncedRealmIntegrationTests extends StandardIntegrationTest { - - @Test - @RunTestInLooperThread - public void loginLogoutResumeSyncing() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - - Realm realm = Realm.getInstance(config); - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - SyncManager.getSession(config).uploadAllLocalChanges(); - user.logOut(); - realm.close(); - try { - assertTrue(Realm.deleteRealm(config)); - } catch (IllegalStateException e) { - // FIXME: We don't have a way to ensure that the Realm instance on client thread has been - // closed for now https://github.com/realm/realm-java/issues/5416 - if (e.getMessage().contains("It's not allowed to delete the file")) { - // retry after 1 second - SystemClock.sleep(1000); - assertTrue(Realm.deleteRealm(config)); - } - } - - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); - SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - - Realm realm2 = Realm.getInstance(config2); - SyncManager.getSession(config2).downloadAllServerChanges(); - realm2.refresh(); - assertEquals(1, realm2.where(StringOnly.class).count()); - realm2.close(); - looperThread.testComplete(); - } - - @Test - @UiThreadTest - public void waitForInitialRemoteData_mainThreadThrows() { - final SyncUser user = SyncTestUtils.createTestUser(Constants.AUTH_URL); - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .waitForInitialRemoteData() - .build(); - - Realm realm = null; - try { - realm = Realm.getInstance(config); - fail(); - } catch (IllegalStateException ignore) { - } finally { - if (realm != null) { - realm.close(); - } - } - } - - @Test - public void waitForInitialRemoteData() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - Realm realm = Realm.getInstance(configOld); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo" + i); - } - } - }); - SyncManager.getSession(configOld).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with - // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .build(); - - realm = Realm.getInstance(config); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo 1" + i); - } - } - }); - try { - assertEquals(20, realm.where(StringOnly.class).count()); - } finally { - realm.close(); - } - } - - // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that - // We cannot do much better since we cannot control the order of events internally in Realm which would be - // needed to correctly test all error paths. - @Test - @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + - " https://github.com/realm/realm-java/issues/5416") - public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .waitForInitialRemoteData() - .build(); - - for (int i = 0; i < 10; i++) { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - Realm realm = null; - try { - // This will cause the download latch called later to immediately throw an InterruptedException. - Thread.currentThread().interrupt(); - realm = Realm.getInstance(config); - } catch (DownloadingRealmInterruptedException ignored) { - assertFalse(new File(config.getPath()).exists()); - } finally { - if (realm != null) { - realm.close(); - Realm.deleteRealm(config); - } - } - } - }); - t.start(); - t.join(); - } - } - - // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that - // We cannot do much better since we cannot control the order of events internally in Realm which would be - // needed to correctly test all error paths. - @Test - @RunTestInLooperThread - @Ignore("See https://github.com/realm/realm-java/issues/5373") - public void waitForInitialData_resilientInCaseOfRetriesAsync() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .directory(configurationFactory.getRoot()) - .waitForInitialRemoteData() - .build(); - Random randomizer = new Random(); - - for (int i = 0; i < 10; i++) { - RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { - @Override - public void onSuccess(Realm realm) { - fail(); - } - - @Override - public void onError(Throwable exception) { - fail(exception.toString()); - } - }); - SystemClock.sleep(randomizer.nextInt(5)); - task.cancel(); - } - looperThread.testComplete(); - } - - @Test - public void waitForInitialRemoteData_readOnlyTrue() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - Realm realm = Realm.getInstance(configOld); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo" + i); - } - } - }); - SyncManager.getSession(configOld).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); - final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .name("newRealm") - .waitForInitialRemoteData() - .readOnly() - .schema(StringOnly.class) - .build(); - assertFalse(configNew.realmExists()); - - realm = Realm.getInstance(configNew); - assertEquals(10, realm.where(StringOnly.class).count()); - realm.close(); - user.logOut(); - } - - @Test - public void waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .waitForInitialRemoteData() - .readOnly() - .schema(StringOnly.class) - .build(); - assertFalse(configNew.realmExists()); - - Realm realm = null; - try { - // This will fail, because the server Realm is completely empty and the Client is not allowed to write the - // schema. - realm = Realm.getInstance(configNew); - fail(); - } catch (RealmMigrationNeededException ignore) { - } finally { - if (realm != null) { - realm.close(); - } - user.logOut(); - } - } - - @Test - public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema - .schema(StringOnly.class) // This schema should be written when opening the empty Realm. - .schemaVersion(2) - .build(); - assertFalse(config.realmExists()); - - Realm realm = Realm.getInstance(config); - try { - assertEquals(0, realm.where(StringOnly.class).count()); - } finally { - realm.close(); - user.logOut(); - } - } - - @Ignore("FIXME: Re-enable this once we can test againt a proper Stitch server") - @Test - public void defaultRealm() throws InterruptedException { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncConfiguration config = user.getDefaultConfiguration(); - Realm realm = Realm.getInstance(config); - SyncManager.getSession(config).downloadAllServerChanges(); - realm.refresh(); - - try { - assertTrue(realm.isEmpty()); - } finally { - realm.close(); - user.logOut(); - } - } - - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - @Test - @RunTestInLooperThread - public void javaRequestCustomHeaders() { - SyncManager.addCustomRequestHeader("Foo", "bar"); - SyncManager.setAuthorizationHeaderName("RealmAuth"); - runJavaRequestCustomHeadersTest(); - } - - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - @Test - @RunTestInLooperThread - public void javaRequestCustomHeaders_specificHost() { - SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST); - SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST); - runJavaRequestCustomHeadersTest(); - } - - private void runJavaRequestCustomHeadersTest() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); - - AtomicBoolean headerSet = new AtomicBoolean(false); - RealmLog.setLevel(LogLevel.ALL); - RealmLogger logger = (level, tag, throwable, message) -> { - if (level == LogLevel.TRACE - && message.contains("Foo: bar") - && message.contains("RealmAuth: ")) { - headerSet.set(true); - } - }; - looperThread.runAfterTest(() -> { - RealmLog.remove(logger); - }); - RealmLog.add(logger); - - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - try { - user.changePassword("foo"); - } catch (ObjectServerError e) { - if (e.getErrorCode() != ErrorCode.INVALID_CREDENTIALS) { - throw e; - } - } - - assertTrue(headerSet.get()); - looperThread.testComplete(); - } - - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. - @Test - @RunTestInLooperThread - public void syncAuthHeaderAndUrlPrefix() { - SyncManager.setAuthorizationHeaderName("TestAuth"); - SyncManager.addCustomRequestHeader("Test", "test"); - runSyncAuthHeadersAndUrlPrefixTest(); - } - - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. - @Test - @RunTestInLooperThread - public void syncAuthHeaderAndUrlPrefix_specificHost() { - SyncManager.setAuthorizationHeaderName("TestAuth", Constants.HOST); - SyncManager.addCustomRequestHeader("Test", "test", Constants.HOST); - runSyncAuthHeadersAndUrlPrefixTest(); - } - - private void runSyncAuthHeadersAndUrlPrefixTest() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .urlPrefix("/foo") - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - RealmLog.error(error.toString()); - } - }) - .build(); - - RealmLog.setLevel(LogLevel.ALL); - RealmLogger logger = (level, tag, throwable, message) -> { - if (tag.equals("REALM_SYNC") - && message.contains("GET /foo/") - && message.contains("TestAuth: Realm-Access-Token version=1") - && message.contains("Test: test")) { - looperThread.testComplete(); - } - }; - looperThread.runAfterTest(() -> { - RealmLog.remove(logger); - }); - RealmLog.add(logger); - Realm realm = Realm.getInstance(config); - looperThread.closeAfterTest(realm); - } - - @Test - @RunTestInLooperThread - public void progressListenersWorkWhenUsingWaitForInitialRemoteData() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - Realm realm = Realm.getInstance(configOld); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo" + i); - } - } - }); - SyncManager.getSession(configOld).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - assertTrue(SyncManager.getAllSessions(user).isEmpty()); - - // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with - // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .build(); - assertFalse(config.realmExists()); - AtomicBoolean indefineteListenerComplete = new AtomicBoolean(false); - AtomicBoolean currentChangesListenerComplete = new AtomicBoolean(false); - RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { - - @Override - public void onSuccess(Realm realm) { - realm.close(); - if (!indefineteListenerComplete.get()) { - fail("Indefinete progress listener did not report complete."); - } - if (!currentChangesListenerComplete.get()) { - fail("Current changes progress listener did not report complete."); - } - looperThread.testComplete(); - } - - @Override - public void onError(Throwable exception) { - fail(exception.toString()); - } - }); - looperThread.keepStrongReference(task); - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - indefineteListenerComplete.set(true); - } - } - }); - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - currentChangesListenerComplete.set(true); - } - } - }); - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt new file mode 100644 index 0000000000..ecf5d12c11 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -0,0 +1,459 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import android.os.SystemClock +import androidx.test.annotation.UiThreadTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.SyncTestUtils.Companion.createTestUser +import io.realm.entities.StringOnly +import io.realm.exceptions.DownloadingRealmInterruptedException +import io.realm.exceptions.RealmMigrationNeededException +import io.realm.internal.OsRealmConfig +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.log.RealmLogger +import io.realm.objectserver.utils.Constants +import io.realm.rule.RunTestInLooperThread +import org.junit.Assert +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Catch all class for tests that not naturally fit anywhere else. + */ +@RunWith(AndroidJUnit4::class) +class SyncedRealmIntegrationTests : StandardIntegrationTest() { + @Test + @RunTestInLooperThread + @Throws(InterruptedException::class) + fun loginLogoutResumeSyncing() { + val username = UUID.randomUUID().toString() + val password = "password" + var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .schema(StringOnly::class.java) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + val realm = Realm.getInstance(config) + realm.beginTransaction() + realm.createObject(StringOnly::class.java).chars = "Foo" + realm.commitTransaction() + SyncManager.getSession(config).uploadAllLocalChanges() + user.logOut() + realm.close() + try { + Assert.assertTrue(Realm.deleteRealm(config)) + } catch (e: IllegalStateException) { + // FIXME: We don't have a way to ensure that the Realm instance on client thread has been + // closed for now https://github.com/realm/realm-java/issues/5416 + if (e.message!!.contains("It's not allowed to delete the file")) { + // retry after 1 second + SystemClock.sleep(1000) + Assert.assertTrue(Realm.deleteRealm(config)) + } + } + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL) + val config2: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .schema(StringOnly::class.java) + .build() + val realm2 = Realm.getInstance(config2) + SyncManager.getSession(config2).downloadAllServerChanges() + realm2.refresh() + Assert.assertEquals(1, realm2.where(StringOnly::class.java).count()) + realm2.close() + looperThread.testComplete() + } + + @Test + @UiThreadTest + fun waitForInitialRemoteData_mainThreadThrows() { + val user: SyncUser = createTestUser(Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .waitForInitialRemoteData() + .build() + var realm: Realm? = null + try { + realm = Realm.getInstance(config) + Assert.fail() + } catch (ignore: IllegalStateException) { + } finally { + realm?.close() + } + } + + @Test + @Throws(InterruptedException::class) + fun waitForInitialRemoteData() { + val username = UUID.randomUUID().toString() + val password = "password" + var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly::class.java) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + var realm = Realm.getInstance(configOld) + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(StringOnly::class.java).chars = "Foo$i" + } + } + SyncManager.getSession(configOld).uploadAllLocalChanges() + realm.close() + user.logOut() + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .name("newRealm") + .schema(StringOnly::class.java) + .waitForInitialRemoteData() + .build() + realm = Realm.getInstance(config) + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(StringOnly::class.java).chars = "Foo 1$i" + } + } + try { + Assert.assertEquals(20, realm.where(StringOnly::class.java).count()) + } finally { + realm.close() + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + + " https://github.com/realm/realm-java/issues/5416") + @Throws(InterruptedException::class) + fun waitForInitialData_resilientInCaseOfRetries() { + val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .waitForInitialRemoteData() + .build() + for (i in 0..9) { + val t = Thread(Runnable { + var realm: Realm? = null + try { + // This will cause the download latch called later to immediately throw an InterruptedException. + Thread.currentThread().interrupt() + realm = Realm.getInstance(config) + } catch (ignored: DownloadingRealmInterruptedException) { + Assert.assertFalse(File(config.path).exists()) + } finally { + if (realm != null) { + realm.close() + Realm.deleteRealm(config) + } + } + }) + t.start() + t.join() + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + @RunTestInLooperThread + @Ignore("See https://github.com/realm/realm-java/issues/5373") + fun waitForInitialData_resilientInCaseOfRetriesAsync() { + val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .directory(configurationFactory.getRoot()) + .waitForInitialRemoteData() + .build() + val randomizer = Random() + for (i in 0..9) { + val task = Realm.getInstanceAsync(config, object : Realm.Callback() { + override fun onSuccess(realm: Realm) { + Assert.fail() + } + + override fun onError(exception: Throwable) { + Assert.fail(exception.toString()) + } + }) + SystemClock.sleep(randomizer.nextInt(5).toLong()) + task.cancel() + } + looperThread.testComplete() + } + + @Test + @Throws(InterruptedException::class) + fun waitForInitialRemoteData_readOnlyTrue() { + val username = UUID.randomUUID().toString() + val password = "password" + var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly::class.java) + .build() + var realm = Realm.getInstance(configOld) + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(StringOnly::class.java).chars = "Foo$i" + } + } + SyncManager.getSession(configOld).uploadAllLocalChanges() + realm.close() + user.logOut() + + // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should + // download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL) + val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .name("newRealm") + .waitForInitialRemoteData() + .readOnly() + .schema(StringOnly::class.java) + .build() + Assert.assertFalse(configNew.realmExists()) + realm = Realm.getInstance(configNew) + Assert.assertEquals(10, realm.where(StringOnly::class.java).count()) + realm.close() + user.logOut() + } + + @Test + fun waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { + val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .readOnly() + .schema(StringOnly::class.java) + .build() + Assert.assertFalse(configNew.realmExists()) + var realm: Realm? = null + try { + // This will fail, because the server Realm is completely empty and the Client is not allowed to write the + // schema. + realm = Realm.getInstance(configNew) + Assert.fail() + } catch (ignore: RealmMigrationNeededException) { + } finally { + realm?.close() + user.logOut() + } + } + + @Test + fun waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { + val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema + .schema(StringOnly::class.java) // This schema should be written when opening the empty Realm. + .schemaVersion(2) + .build() + Assert.assertFalse(config.realmExists()) + val realm = Realm.getInstance(config) + try { + Assert.assertEquals(0, realm.where(StringOnly::class.java).count()) + } finally { + realm.close() + user.logOut() + } + } + + @Ignore("FIXME: Re-enable this once we can test againt a proper Stitch server") + @Test + @Throws(InterruptedException::class) + fun defaultRealm() { + val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = user.getDefaultConfiguration() + val realm = Realm.getInstance(config) + SyncManager.getSession(config).downloadAllServerChanges() + realm.refresh() + try { + Assert.assertTrue(realm.isEmpty) + } finally { + realm.close() + user.logOut() + } + } + + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + @RunTestInLooperThread + fun javaRequestCustomHeaders() { + SyncManager.addCustomRequestHeader("Foo", "bar") + SyncManager.setAuthorizationHeaderName("RealmAuth") + runJavaRequestCustomHeadersTest() + } + + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + @RunTestInLooperThread + fun javaRequestCustomHeaders_specificHost() { + SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST) + SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST) + runJavaRequestCustomHeadersTest() + } + + private fun runJavaRequestCustomHeadersTest() { + val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) + val headerSet = AtomicBoolean(false) + RealmLog.setLevel(LogLevel.ALL) + val logger = RealmLogger { level: Int, tag: String?, throwable: Throwable?, message: String? -> + if (level == LogLevel.TRACE && message!!.contains("Foo: bar") + && message.contains("RealmAuth: ")) { + headerSet.set(true) + } + } + looperThread.runAfterTest({ RealmLog.remove(logger) }) + RealmLog.add(logger) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + try { + user.changePassword("foo") + } catch (e: ObjectServerError) { + if (e.errorCode != ErrorCode.INVALID_CREDENTIALS) { + throw e + } + } + Assert.assertTrue(headerSet.get()) + looperThread.testComplete() + } + + // Test that auth header renaming, custom headers and url prefix are all propagated correctly + // to Sync. There really isn't a way to create a proper integration test since ROS used for testing + // isn't configured to accept such requests. Instead we inspect the log from Sync which will + // output the headers in TRACE mode. + @Test + @RunTestInLooperThread + fun syncAuthHeaderAndUrlPrefix() { + SyncManager.setAuthorizationHeaderName("TestAuth") + SyncManager.addCustomRequestHeader("Test", "test") + runSyncAuthHeadersAndUrlPrefixTest() + } + + // Test that auth header renaming, custom headers and url prefix are all propagated correctly + // to Sync. There really isn't a way to create a proper integration test since ROS used for testing + // isn't configured to accept such requests. Instead we inspect the log from Sync which will + // output the headers in TRACE mode. + @Test + @RunTestInLooperThread + fun syncAuthHeaderAndUrlPrefix_specificHost() { + SyncManager.setAuthorizationHeaderName("TestAuth", Constants.HOST) + SyncManager.addCustomRequestHeader("Test", "test", Constants.HOST) + runSyncAuthHeadersAndUrlPrefixTest() + } + + private fun runSyncAuthHeadersAndUrlPrefixTest() { + val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .urlPrefix("/foo") + .errorHandler(SyncSession.ErrorHandler { session, error -> RealmLog.error(error.toString()) }) + .build() + RealmLog.setLevel(LogLevel.ALL) + val logger = RealmLogger { level: Int, tag: String, throwable: Throwable?, message: String? -> + if (tag == "REALM_SYNC" && message!!.contains("GET /foo/") + && message.contains("TestAuth: Realm-Access-Token version=1") + && message.contains("Test: test")) { + looperThread.testComplete() + } + } + looperThread.runAfterTest({ RealmLog.remove(logger) }) + RealmLog.add(logger) + val realm = Realm.getInstance(config) + looperThread.closeAfterTest(realm) + } + + @Test + @RunTestInLooperThread + @Throws(InterruptedException::class) + fun progressListenersWorkWhenUsingWaitForInitialRemoteData() { + val username = UUID.randomUUID().toString() + val password = "password" + var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly::class.java) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + val realm = Realm.getInstance(configOld) + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(StringOnly::class.java).chars = "Foo$i" + } + } + SyncManager.getSession(configOld).uploadAllLocalChanges() + realm.close() + user.logOut() + Assert.assertTrue(SyncManager.getAllSessions(user).isEmpty()) + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .name("newRealm") + .schema(StringOnly::class.java) + .waitForInitialRemoteData() + .build() + Assert.assertFalse(config.realmExists()) + val indefineteListenerComplete = AtomicBoolean(false) + val currentChangesListenerComplete = AtomicBoolean(false) + val task = Realm.getInstanceAsync(config, object : Realm.Callback() { + override fun onSuccess(realm: Realm) { + realm.close() + if (!indefineteListenerComplete.get()) { + Assert.fail("Indefinete progress listener did not report complete.") + } + if (!currentChangesListenerComplete.get()) { + Assert.fail("Current changes progress listener did not report complete.") + } + looperThread.testComplete() + } + + override fun onError(exception: Throwable) { + Assert.fail(exception.toString()) + } + }) + looperThread.keepStrongReference(task) + SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.INDEFINITELY, ProgressListener { progress -> + if (progress.isTransferComplete) { + indefineteListenerComplete.set(true) + } + }) + SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, ProgressListener { progress -> + if (progress.isTransferComplete) { + currentChangesListenerComplete.set(true) + } + }) + } +} From b25841a8066a08cb0d8287830626cac072bc6fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 28 May 2020 12:21:46 +0200 Subject: [PATCH 1540/2110] Fix serialization of partition values --- .../cpp/io_realm_internal_OsRealmConfig.cpp | 10 ++++++-- .../java/io/realm/internal/OsRealmConfig.java | 24 ++++++++++++++++--- .../internal/SyncObjectServerFacade.java | 20 ++-------------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index e21faafc51..d8ee560159 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -26,6 +26,7 @@ #endif #include +#include #include "java_accessor.hpp" #include "util.hpp" @@ -332,8 +333,13 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe SyncSessionStopPolicy session_stop_policy = static_cast(j_session_stop_policy); JStringAccessor realm_url(env, j_sync_realm_url); - JStringAccessor partion_key_value(env, j_partion_key_value); - config.sync_config = std::make_shared(SyncConfig{user, partion_key_value}); + // TODO Simplify. Java serialization only allows writing full documents, so the partition + // key is embedded in a document with key 'value'. To get is as string were we parse it + // and reformat with C++ bson serialization as it supports serializing single values. + Bson bson(JniBsonProtocol::jstring_to_bson(env, j_partion_key_value)); + std::stringstream buffer; + buffer << bson; + config.sync_config = std::make_shared(SyncConfig{user, buffer.str()}); config.sync_config->stop_policy = session_stop_policy; config.sync_config->error_handler = std::move(error_handler); switch (j_client_reset_mode) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index b0750fd4e7..a608465778 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -16,17 +16,21 @@ package io.realm.internal; +import org.bson.BsonValue; + import java.io.File; import java.net.ProxySelector; import java.net.URI; import java.net.URISyntaxException; -import java.util.Map; import java.util.List; +import java.util.Map; import javax.annotation.Nullable; import io.realm.CompactOnLaunchCallback; +import io.realm.RealmAppConfiguration; import io.realm.RealmConfiguration; +import io.realm.internal.jni.JniBsonProtocol; import io.realm.log.RealmLog; /** @@ -220,7 +224,7 @@ private OsRealmConfig(final RealmConfiguration config, //noinspection unchecked Map customHeadersMap = (Map) (syncConfigurationOptions[j++]); Byte clientResyncMode = (Byte) syncConfigurationOptions[j++]; - String partitionValue = (String) syncConfigurationOptions[j++]; + BsonValue partitionValue = (BsonValue) syncConfigurationOptions[j++]; Object syncService = syncConfigurationOptions[j++]; // Convert the headers into a String array to make it easier to send through JNI @@ -235,6 +239,20 @@ private OsRealmConfig(final RealmConfiguration config, } } + // TODO Simplify. org.bson serialization only allows writing full documents, so the partition + // key is embedded in a document with key 'value' and unwrapped in JNI. + String encodedPartitionValue; + switch (partitionValue.getBsonType()) { + case STRING: + case OBJECT_ID: + case INT32: + case INT64: + encodedPartitionValue = JniBsonProtocol.encode(partitionValue, RealmAppConfiguration.DEFAULT_BSON_CODEC_REGISTRY); + break; + default: + throw new IllegalArgumentException("Unsupported type: " + partitionValue); + } + // Set encryption key byte[] key = config.getEncryptionKey(); if (key != null) { @@ -291,7 +309,7 @@ private OsRealmConfig(final RealmConfiguration config, customAuthorizationHeaderName, customHeaders, clientResyncMode, - partitionValue, + encodedPartitionValue, syncService); try { resolvedSyncRealmUrl = syncRealmAuthUrl + urlPrefix.substring(1); // FIXME diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 24fdb1dd23..fb894b5219 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -21,8 +21,6 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; -import org.bson.BsonValue; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; @@ -30,9 +28,9 @@ import io.realm.RealmApp; import io.realm.RealmConfiguration; +import io.realm.RealmSync; import io.realm.RealmUser; import io.realm.SyncConfiguration; -import io.realm.RealmSync; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; import io.realm.internal.android.AndroidCapabilities; @@ -87,20 +85,6 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { String customAuthorizationHeaderName = app.getConfiguration().getAuthorizationHeaderName(); Map customHeaders = app.getConfiguration().getCustomRequestHeaders(); - // Temporary work-around for serializing supported bson values - BsonValue val = syncConfig.getPartitionValue(); - String partitionValue = null; - if (val.isString()) { - partitionValue = "\"" + val.asString().getValue() + "\""; - } else if (val.isInt32()) { - partitionValue = "{ \"$bsonInt\" : " + val.asInt32().intValue() + " }"; - } else if (val.isInt64()) { - partitionValue = "{ \"$bsonLong\" : " + val.asInt64().longValue() + " }"; - } else if (val.isObjectId()) { - partitionValue = "{ \"$oid\" : " + val.asObjectId().toString() + " }"; - } else { - throw new IllegalArgumentException("Unsupported type: " + val); - } int i = 0; Object[] configObj = new Object[SYNC_CONFIG_OPTIONS]; configObj[i++] = rosUserIdentity; @@ -114,7 +98,7 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { configObj[i++] = customAuthorizationHeaderName; configObj[i++] = customHeaders; configObj[i++] = OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL; - configObj[i++] = partitionValue; + configObj[i++] = syncConfig.getPartitionValue(); configObj[i++] = app.getSync(); return configObj; } else { From 914fa1a79bd59f1ed07bc360e1c8c8d5bda6de4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 28 May 2020 12:26:40 +0200 Subject: [PATCH 1541/2110] Revert conversion of SyncedRealmIntegrationTests to keep PR smaller --- .../io/realm/SyncedRealmIntegrationTests.java | 521 ++++++++++++++++++ .../io/realm/SyncedRealmIntegrationTests.kt | 459 --------------- 2 files changed, 521 insertions(+), 459 deletions(-) create mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java delete mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java new file mode 100644 index 0000000000..a6f04205e5 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -0,0 +1,521 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +package io.realm; + +import android.os.SystemClock; +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.realm.entities.AllTypes; +import io.realm.entities.StringOnly; +import io.realm.exceptions.DownloadingRealmInterruptedException; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.OsRealmConfig; +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; +import io.realm.log.RealmLogger; +import io.realm.objectserver.utils.Constants; +import io.realm.rule.RunTestInLooperThread; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + + +/** + * Catch all class for tests that not naturally fit anywhere else. + */ +@RunWith(AndroidJUnit4.class) +public class SyncedRealmIntegrationTests extends StandardIntegrationTest { + + @Test + @RunTestInLooperThread + public void loginLogoutResumeSyncing() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) + .schema(StringOnly.class) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build(); + + Realm realm = Realm.getInstance(config); + realm.beginTransaction(); + realm.createObject(StringOnly.class).setChars("Foo"); + realm.commitTransaction(); + SyncManager.getSession(config).uploadAllLocalChanges(); + user.logOut(); + realm.close(); + try { + assertTrue(Realm.deleteRealm(config)); + } catch (IllegalStateException e) { + // FIXME: We don't have a way to ensure that the Realm instance on client thread has been + // closed for now https://github.com/realm/realm-java/issues/5416 + if (e.getMessage().contains("It's not allowed to delete the file")) { + // retry after 1 second + SystemClock.sleep(1000); + assertTrue(Realm.deleteRealm(config)); + } + } + + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM) + .schema(StringOnly.class) + .build(); + + Realm realm2 = Realm.getInstance(config2); + SyncManager.getSession(config2).downloadAllServerChanges(); + realm2.refresh(); + assertEquals(1, realm2.where(StringOnly.class).count()); + realm2.close(); + looperThread.testComplete(); + } + + @Test + @UiThreadTest + public void waitForInitialRemoteData_mainThreadThrows() { + final SyncUser user = SyncTestUtils.createTestUser(Constants.AUTH_URL); + SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) + .waitForInitialRemoteData() + .build(); + + Realm realm = null; + try { + realm = Realm.getInstance(config); + fail(); + } catch (IllegalStateException ignore) { + } finally { + if (realm != null) { + realm.close(); + } + } + } + + @Test + public void waitForInitialRemoteData() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly.class) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build(); + Realm realm = Realm.getInstance(configOld); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + for (int i = 0; i < 10; i++) { + realm.createObject(StringOnly.class).setChars("Foo" + i); + } + } + }); + SyncManager.getSession(configOld).uploadAllLocalChanges(); + realm.close(); + user.logOut(); + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) + .name("newRealm") + .schema(StringOnly.class) + .waitForInitialRemoteData() + .build(); + + realm = Realm.getInstance(config); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + for (int i = 0; i < 10; i++) { + realm.createObject(StringOnly.class).setChars("Foo 1" + i); + } + } + }); + try { + assertEquals(20, realm.where(StringOnly.class).count()); + } finally { + realm.close(); + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + + " https://github.com/realm/realm-java/issues/5416") + public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) + .waitForInitialRemoteData() + .build(); + + for (int i = 0; i < 10; i++) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + Realm realm = null; + try { + // This will cause the download latch called later to immediately throw an InterruptedException. + Thread.currentThread().interrupt(); + realm = Realm.getInstance(config); + } catch (DownloadingRealmInterruptedException ignored) { + assertFalse(new File(config.getPath()).exists()); + } finally { + if (realm != null) { + realm.close(); + Realm.deleteRealm(config); + } + } + } + }); + t.start(); + t.join(); + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + @RunTestInLooperThread + @Ignore("See https://github.com/realm/realm-java/issues/5373") + public void waitForInitialData_resilientInCaseOfRetriesAsync() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .directory(configurationFactory.getRoot()) + .waitForInitialRemoteData() + .build(); + Random randomizer = new Random(); + + for (int i = 0; i < 10; i++) { + RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { + @Override + public void onSuccess(Realm realm) { + fail(); + } + + @Override + public void onError(Throwable exception) { + fail(exception.toString()); + } + }); + SystemClock.sleep(randomizer.nextInt(5)); + task.cancel(); + } + looperThread.testComplete(); + } + + @Test + public void waitForInitialRemoteData_readOnlyTrue() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly.class) + .build(); + Realm realm = Realm.getInstance(configOld); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + for (int i = 0; i < 10; i++) { + realm.createObject(StringOnly.class).setChars("Foo" + i); + } + } + }); + SyncManager.getSession(configOld).uploadAllLocalChanges(); + realm.close(); + user.logOut(); + + // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should + // download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); + final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .name("newRealm") + .waitForInitialRemoteData() + .readOnly() + .schema(StringOnly.class) + .build(); + assertFalse(configNew.realmExists()); + + realm = Realm.getInstance(configNew); + assertEquals(10, realm.where(StringOnly.class).count()); + realm.close(); + user.logOut(); + } + + @Test + public void waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .readOnly() + .schema(StringOnly.class) + .build(); + assertFalse(configNew.realmExists()); + + Realm realm = null; + try { + // This will fail, because the server Realm is completely empty and the Client is not allowed to write the + // schema. + realm = Realm.getInstance(configNew); + fail(); + } catch (RealmMigrationNeededException ignore) { + } finally { + if (realm != null) { + realm.close(); + } + user.logOut(); + } + } + + @Test + public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema + .schema(StringOnly.class) // This schema should be written when opening the empty Realm. + .schemaVersion(2) + .build(); + assertFalse(config.realmExists()); + + Realm realm = Realm.getInstance(config); + try { + assertEquals(0, realm.where(StringOnly.class).count()); + } finally { + realm.close(); + user.logOut(); + } + } + + @Ignore("FIXME: Re-enable this once we can test againt a proper Stitch server") + @Test + public void defaultRealm() throws InterruptedException { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + SyncConfiguration config = user.getDefaultConfiguration(); + Realm realm = Realm.getInstance(config); + SyncManager.getSession(config).downloadAllServerChanges(); + realm.refresh(); + + try { + assertTrue(realm.isEmpty()); + } finally { + realm.close(); + user.logOut(); + } + } + + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + @RunTestInLooperThread + public void javaRequestCustomHeaders() { + SyncManager.addCustomRequestHeader("Foo", "bar"); + SyncManager.setAuthorizationHeaderName("RealmAuth"); + runJavaRequestCustomHeadersTest(); + } + + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + @RunTestInLooperThread + public void javaRequestCustomHeaders_specificHost() { + SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST); + SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST); + runJavaRequestCustomHeadersTest(); + } + + private void runJavaRequestCustomHeadersTest() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); + + AtomicBoolean headerSet = new AtomicBoolean(false); + RealmLog.setLevel(LogLevel.ALL); + RealmLogger logger = (level, tag, throwable, message) -> { + if (level == LogLevel.TRACE + && message.contains("Foo: bar") + && message.contains("RealmAuth: ")) { + headerSet.set(true); + } + }; + looperThread.runAfterTest(() -> { + RealmLog.remove(logger); + }); + RealmLog.add(logger); + + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + try { + user.changePassword("foo"); + } catch (ObjectServerError e) { + if (e.getErrorCode() != ErrorCode.INVALID_CREDENTIALS) { + throw e; + } + } + + assertTrue(headerSet.get()); + looperThread.testComplete(); + } + + // Test that auth header renaming, custom headers and url prefix are all propagated correctly + // to Sync. There really isn't a way to create a proper integration test since ROS used for testing + // isn't configured to accept such requests. Instead we inspect the log from Sync which will + // output the headers in TRACE mode. + @Test + @RunTestInLooperThread + public void syncAuthHeaderAndUrlPrefix() { + SyncManager.setAuthorizationHeaderName("TestAuth"); + SyncManager.addCustomRequestHeader("Test", "test"); + runSyncAuthHeadersAndUrlPrefixTest(); + } + + // Test that auth header renaming, custom headers and url prefix are all propagated correctly + // to Sync. There really isn't a way to create a proper integration test since ROS used for testing + // isn't configured to accept such requests. Instead we inspect the log from Sync which will + // output the headers in TRACE mode. + @Test + @RunTestInLooperThread + public void syncAuthHeaderAndUrlPrefix_specificHost() { + SyncManager.setAuthorizationHeaderName("TestAuth", Constants.HOST); + SyncManager.addCustomRequestHeader("Test", "test", Constants.HOST); + runSyncAuthHeadersAndUrlPrefixTest(); + } + + private void runSyncAuthHeadersAndUrlPrefixTest() { + SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); + SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); + SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .urlPrefix("/foo") + .errorHandler(new SyncSession.ErrorHandler() { + @Override + public void onError(SyncSession session, ObjectServerError error) { + RealmLog.error(error.toString()); + } + }) + .build(); + + RealmLog.setLevel(LogLevel.ALL); + RealmLogger logger = (level, tag, throwable, message) -> { + if (tag.equals("REALM_SYNC") + && message.contains("GET /foo/") + && message.contains("TestAuth: Realm-Access-Token version=1") + && message.contains("Test: test")) { + looperThread.testComplete(); + } + }; + looperThread.runAfterTest(() -> { + RealmLog.remove(logger); + }); + RealmLog.add(logger); + Realm realm = Realm.getInstance(config); + looperThread.closeAfterTest(realm); + } + + @Test + @RunTestInLooperThread + public void progressListenersWorkWhenUsingWaitForInitialRemoteData() throws InterruptedException { + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly.class) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build(); + Realm realm = Realm.getInstance(configOld); + realm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + for (int i = 0; i < 10; i++) { + realm.createObject(StringOnly.class).setChars("Foo" + i); + } + } + }); + SyncManager.getSession(configOld).uploadAllLocalChanges(); + realm.close(); + user.logOut(); + assertTrue(SyncManager.getAllSessions(user).isEmpty()); + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); + SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) + .name("newRealm") + .schema(StringOnly.class) + .waitForInitialRemoteData() + .build(); + assertFalse(config.realmExists()); + AtomicBoolean indefineteListenerComplete = new AtomicBoolean(false); + AtomicBoolean currentChangesListenerComplete = new AtomicBoolean(false); + RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { + + @Override + public void onSuccess(Realm realm) { + realm.close(); + if (!indefineteListenerComplete.get()) { + fail("Indefinete progress listener did not report complete."); + } + if (!currentChangesListenerComplete.get()) { + fail("Current changes progress listener did not report complete."); + } + looperThread.testComplete(); + } + + @Override + public void onError(Throwable exception) { + fail(exception.toString()); + } + }); + looperThread.keepStrongReference(task); + SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + indefineteListenerComplete.set(true); + } + } + }); + SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { + @Override + public void onChange(Progress progress) { + if (progress.isTransferComplete()) { + currentChangesListenerComplete.set(true); + } + } + }); + } +} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt deleted file mode 100644 index ecf5d12c11..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ -package io.realm - -import android.os.SystemClock -import androidx.test.annotation.UiThreadTest -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.realm.SyncTestUtils.Companion.createTestUser -import io.realm.entities.StringOnly -import io.realm.exceptions.DownloadingRealmInterruptedException -import io.realm.exceptions.RealmMigrationNeededException -import io.realm.internal.OsRealmConfig -import io.realm.log.LogLevel -import io.realm.log.RealmLog -import io.realm.log.RealmLogger -import io.realm.objectserver.utils.Constants -import io.realm.rule.RunTestInLooperThread -import org.junit.Assert -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import java.io.File -import java.util.* -import java.util.concurrent.atomic.AtomicBoolean - -/** - * Catch all class for tests that not naturally fit anywhere else. - */ -@RunWith(AndroidJUnit4::class) -class SyncedRealmIntegrationTests : StandardIntegrationTest() { - @Test - @RunTestInLooperThread - @Throws(InterruptedException::class) - fun loginLogoutResumeSyncing() { - val username = UUID.randomUUID().toString() - val password = "password" - var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly::class.java) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build() - val realm = Realm.getInstance(config) - realm.beginTransaction() - realm.createObject(StringOnly::class.java).chars = "Foo" - realm.commitTransaction() - SyncManager.getSession(config).uploadAllLocalChanges() - user.logOut() - realm.close() - try { - Assert.assertTrue(Realm.deleteRealm(config)) - } catch (e: IllegalStateException) { - // FIXME: We don't have a way to ensure that the Realm instance on client thread has been - // closed for now https://github.com/realm/realm-java/issues/5416 - if (e.message!!.contains("It's not allowed to delete the file")) { - // retry after 1 second - SystemClock.sleep(1000) - Assert.assertTrue(Realm.deleteRealm(config)) - } - } - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL) - val config2: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly::class.java) - .build() - val realm2 = Realm.getInstance(config2) - SyncManager.getSession(config2).downloadAllServerChanges() - realm2.refresh() - Assert.assertEquals(1, realm2.where(StringOnly::class.java).count()) - realm2.close() - looperThread.testComplete() - } - - @Test - @UiThreadTest - fun waitForInitialRemoteData_mainThreadThrows() { - val user: SyncUser = createTestUser(Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .waitForInitialRemoteData() - .build() - var realm: Realm? = null - try { - realm = Realm.getInstance(config) - Assert.fail() - } catch (ignore: IllegalStateException) { - } finally { - realm?.close() - } - } - - @Test - @Throws(InterruptedException::class) - fun waitForInitialRemoteData() { - val username = UUID.randomUUID().toString() - val password = "password" - var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly::class.java) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build() - var realm = Realm.getInstance(configOld) - realm.executeTransaction { realm -> - for (i in 0..9) { - realm.createObject(StringOnly::class.java).chars = "Foo$i" - } - } - SyncManager.getSession(configOld).uploadAllLocalChanges() - realm.close() - user.logOut() - - // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with - // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly::class.java) - .waitForInitialRemoteData() - .build() - realm = Realm.getInstance(config) - realm.executeTransaction { realm -> - for (i in 0..9) { - realm.createObject(StringOnly::class.java).chars = "Foo 1$i" - } - } - try { - Assert.assertEquals(20, realm.where(StringOnly::class.java).count()) - } finally { - realm.close() - } - } - - // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that - // We cannot do much better since we cannot control the order of events internally in Realm which would be - // needed to correctly test all error paths. - @Test - @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + - " https://github.com/realm/realm-java/issues/5416") - @Throws(InterruptedException::class) - fun waitForInitialData_resilientInCaseOfRetries() { - val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .waitForInitialRemoteData() - .build() - for (i in 0..9) { - val t = Thread(Runnable { - var realm: Realm? = null - try { - // This will cause the download latch called later to immediately throw an InterruptedException. - Thread.currentThread().interrupt() - realm = Realm.getInstance(config) - } catch (ignored: DownloadingRealmInterruptedException) { - Assert.assertFalse(File(config.path).exists()) - } finally { - if (realm != null) { - realm.close() - Realm.deleteRealm(config) - } - } - }) - t.start() - t.join() - } - } - - // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that - // We cannot do much better since we cannot control the order of events internally in Realm which would be - // needed to correctly test all error paths. - @Test - @RunTestInLooperThread - @Ignore("See https://github.com/realm/realm-java/issues/5373") - fun waitForInitialData_resilientInCaseOfRetriesAsync() { - val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .directory(configurationFactory.getRoot()) - .waitForInitialRemoteData() - .build() - val randomizer = Random() - for (i in 0..9) { - val task = Realm.getInstanceAsync(config, object : Realm.Callback() { - override fun onSuccess(realm: Realm) { - Assert.fail() - } - - override fun onError(exception: Throwable) { - Assert.fail(exception.toString()) - } - }) - SystemClock.sleep(randomizer.nextInt(5).toLong()) - task.cancel() - } - looperThread.testComplete() - } - - @Test - @Throws(InterruptedException::class) - fun waitForInitialRemoteData_readOnlyTrue() { - val username = UUID.randomUUID().toString() - val password = "password" - var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly::class.java) - .build() - var realm = Realm.getInstance(configOld) - realm.executeTransaction { realm -> - for (i in 0..9) { - realm.createObject(StringOnly::class.java).chars = "Foo$i" - } - } - SyncManager.getSession(configOld).uploadAllLocalChanges() - realm.close() - user.logOut() - - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL) - val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .name("newRealm") - .waitForInitialRemoteData() - .readOnly() - .schema(StringOnly::class.java) - .build() - Assert.assertFalse(configNew.realmExists()) - realm = Realm.getInstance(configNew) - Assert.assertEquals(10, realm.where(StringOnly::class.java).count()) - realm.close() - user.logOut() - } - - @Test - fun waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { - val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .waitForInitialRemoteData() - .readOnly() - .schema(StringOnly::class.java) - .build() - Assert.assertFalse(configNew.realmExists()) - var realm: Realm? = null - try { - // This will fail, because the server Realm is completely empty and the Client is not allowed to write the - // schema. - realm = Realm.getInstance(configNew) - Assert.fail() - } catch (ignore: RealmMigrationNeededException) { - } finally { - realm?.close() - user.logOut() - } - } - - @Test - fun waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { - val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema - .schema(StringOnly::class.java) // This schema should be written when opening the empty Realm. - .schemaVersion(2) - .build() - Assert.assertFalse(config.realmExists()) - val realm = Realm.getInstance(config) - try { - Assert.assertEquals(0, realm.where(StringOnly::class.java).count()) - } finally { - realm.close() - user.logOut() - } - } - - @Ignore("FIXME: Re-enable this once we can test againt a proper Stitch server") - @Test - @Throws(InterruptedException::class) - fun defaultRealm() { - val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = user.getDefaultConfiguration() - val realm = Realm.getInstance(config) - SyncManager.getSession(config).downloadAllServerChanges() - realm.refresh() - try { - Assert.assertTrue(realm.isEmpty) - } finally { - realm.close() - user.logOut() - } - } - - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - @Test - @RunTestInLooperThread - fun javaRequestCustomHeaders() { - SyncManager.addCustomRequestHeader("Foo", "bar") - SyncManager.setAuthorizationHeaderName("RealmAuth") - runJavaRequestCustomHeadersTest() - } - - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - @Test - @RunTestInLooperThread - fun javaRequestCustomHeaders_specificHost() { - SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST) - SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST) - runJavaRequestCustomHeadersTest() - } - - private fun runJavaRequestCustomHeadersTest() { - val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) - val headerSet = AtomicBoolean(false) - RealmLog.setLevel(LogLevel.ALL) - val logger = RealmLogger { level: Int, tag: String?, throwable: Throwable?, message: String? -> - if (level == LogLevel.TRACE && message!!.contains("Foo: bar") - && message.contains("RealmAuth: ")) { - headerSet.set(true) - } - } - looperThread.runAfterTest({ RealmLog.remove(logger) }) - RealmLog.add(logger) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - try { - user.changePassword("foo") - } catch (e: ObjectServerError) { - if (e.errorCode != ErrorCode.INVALID_CREDENTIALS) { - throw e - } - } - Assert.assertTrue(headerSet.get()) - looperThread.testComplete() - } - - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. - @Test - @RunTestInLooperThread - fun syncAuthHeaderAndUrlPrefix() { - SyncManager.setAuthorizationHeaderName("TestAuth") - SyncManager.addCustomRequestHeader("Test", "test") - runSyncAuthHeadersAndUrlPrefixTest() - } - - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. - @Test - @RunTestInLooperThread - fun syncAuthHeaderAndUrlPrefix_specificHost() { - SyncManager.setAuthorizationHeaderName("TestAuth", Constants.HOST) - SyncManager.addCustomRequestHeader("Test", "test", Constants.HOST) - runSyncAuthHeadersAndUrlPrefixTest() - } - - private fun runSyncAuthHeadersAndUrlPrefixTest() { - val credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .urlPrefix("/foo") - .errorHandler(SyncSession.ErrorHandler { session, error -> RealmLog.error(error.toString()) }) - .build() - RealmLog.setLevel(LogLevel.ALL) - val logger = RealmLogger { level: Int, tag: String, throwable: Throwable?, message: String? -> - if (tag == "REALM_SYNC" && message!!.contains("GET /foo/") - && message.contains("TestAuth: Realm-Access-Token version=1") - && message.contains("Test: test")) { - looperThread.testComplete() - } - } - looperThread.runAfterTest({ RealmLog.remove(logger) }) - RealmLog.add(logger) - val realm = Realm.getInstance(config) - looperThread.closeAfterTest(realm) - } - - @Test - @RunTestInLooperThread - @Throws(InterruptedException::class) - fun progressListenersWorkWhenUsingWaitForInitialRemoteData() { - val username = UUID.randomUUID().toString() - val password = "password" - var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly::class.java) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build() - val realm = Realm.getInstance(configOld) - realm.executeTransaction { realm -> - for (i in 0..9) { - realm.createObject(StringOnly::class.java).chars = "Foo$i" - } - } - SyncManager.getSession(configOld).uploadAllLocalChanges() - realm.close() - user.logOut() - Assert.assertTrue(SyncManager.getAllSessions(user).isEmpty()) - - // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with - // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly::class.java) - .waitForInitialRemoteData() - .build() - Assert.assertFalse(config.realmExists()) - val indefineteListenerComplete = AtomicBoolean(false) - val currentChangesListenerComplete = AtomicBoolean(false) - val task = Realm.getInstanceAsync(config, object : Realm.Callback() { - override fun onSuccess(realm: Realm) { - realm.close() - if (!indefineteListenerComplete.get()) { - Assert.fail("Indefinete progress listener did not report complete.") - } - if (!currentChangesListenerComplete.get()) { - Assert.fail("Current changes progress listener did not report complete.") - } - looperThread.testComplete() - } - - override fun onError(exception: Throwable) { - Assert.fail(exception.toString()) - } - }) - looperThread.keepStrongReference(task) - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.INDEFINITELY, ProgressListener { progress -> - if (progress.isTransferComplete) { - indefineteListenerComplete.set(true) - } - }) - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, ProgressListener { progress -> - if (progress.isTransferComplete) { - currentChangesListenerComplete.set(true) - } - }) - } -} From 8e3a5b980c15c8d4e59d444ffd37342c618e1468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 28 May 2020 12:28:20 +0200 Subject: [PATCH 1542/2110] Updated SyncSessionTests --- .../kotlin/io/realm/util/KotlinTestUtils.kt | 1 + .../java/io/realm/SyncConfiguration.java | 2 +- .../kotlin/io/realm/SyncSessionTests.kt | 902 ++++++++++-------- .../realm/TestSyncConfigurationFactory.java | 7 + 4 files changed, 538 insertions(+), 374 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt index cd6710b806..17ebbcd1f1 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt @@ -53,6 +53,7 @@ class ResourceContainer : Closeable { @Synchronized override fun close() { resources.map { it.close() } + resources.clear() } @Synchronized diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index be1c02724f..f12fc18981 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -502,7 +502,7 @@ public Builder(RealmUser user, long partitionValue) { * synchronized to the Realm. * @see Link to docs about partions */ - private Builder(RealmUser user, BsonValue partitionValue) { + Builder(RealmUser user, BsonValue partitionValue) { Context context = BaseRealm.applicationContext; if (context == null) { throw new IllegalStateException("Call `Realm.init(Context)` before creating a SyncConfiguration"); diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 9213848c1f..89ab1fc73b 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -4,32 +4,49 @@ import android.os.Handler import android.os.HandlerThread import android.os.SystemClock import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.realm.SyncTestUtils -import io.realm.entities.AllTypes -import io.realm.entities.StringOnly +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.entities.* import io.realm.exceptions.DownloadingRealmInterruptedException import io.realm.internal.OsRealmConfig -import io.realm.objectserver.utils.Constants -import io.realm.objectserver.utils.StringOnlyModule -import io.realm.objectserver.utils.UserFactory -import io.realm.rule.RunTestInLooperThread -import org.junit.Assert -import org.junit.Rule -import org.junit.Test +import io.realm.kotlin.syncSession +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.rule.BlockingLooperThread +import io.realm.util.ResourceContainer +import io.realm.util.assertFailsWithMessage +import org.bson.BsonInt32 +import org.bson.BsonInt64 +import org.bson.BsonObjectId +import org.bson.BsonString +import org.bson.types.ObjectId +import org.hamcrest.CoreMatchers +import org.junit.* +import org.junit.Assert.* import org.junit.runner.RunWith +import java.io.Closeable +import java.lang.Thread import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +typealias SessionCallback = (SyncSession) -> Unit + +private val SECRET_PASSWORD = "123456" @RunWith(AndroidJUnit4::class) -class SyncSessionTests : StandardIntegrationTest() { - @Rule - var configFactory = TestSyncConfigurationFactory() +class SyncSessionTests { - private interface SessionCallback { - fun onReady(session: SyncSession?) - } + @get:Rule + private val looperThread = BlockingLooperThread() + + private lateinit var app: TestRealmApp + private lateinit var user: RealmUser + private lateinit var syncConfiguration: SyncConfiguration + + private val configFactory: TestSyncConfigurationFactory = TestSyncConfigurationFactory() private fun getSession(callback: SessionCallback) { // Work-around for a race condition happening when shutting down a Looper test and @@ -41,156 +58,292 @@ class SyncSessionTests : StandardIntegrationTest() { // intended. // Generally it seems that using calling `RunInLooperThread.testComplete()` in a synchronous looperThread.postRunnable(Runnable { - val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .createSyncConfigurationBuilder(user) .build() - looperThread.closeAfterTest(Realm.getInstance(syncConfiguration)) - callback.onReady(SyncManager.getSession(syncConfiguration)) + val realm = Realm.getInstance(syncConfiguration) + looperThread.closeAfterTest(realm) + callback(realm.syncSession) }) } private fun getActiveSession(callback: SessionCallback) { - getSession(SessionCallback { session: SyncSession -> + getSession { session -> if (session.isConnected) { - callback.onReady(session) + callback(session) } else { session.addConnectionChangeListener(object : ConnectionListener { override fun onChange(oldState: ConnectionState, newState: ConnectionState) { if (newState == ConnectionState.CONNECTED) { session.removeConnectionChangeListener(this) - callback.onReady(session) + callback(session) } } }) } - }) + } } - // make sure the `access_token` is acquired. otherwise we can still be - // in WAITING_FOR_ACCESS_TOKEN state - @get:Test(timeout = 3000) - val state_active: Unit - get() { - val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) - val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build() - val realm = Realm.getInstance(syncConfiguration) - val session: SyncSession = SyncManager.getSession(syncConfiguration) + @Before + fun setup() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + RealmLog.setLevel(LogLevel.ALL) + app = TestRealmApp() + user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + syncConfiguration = configFactory + .createSyncConfigurationBuilder(user) + .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .build() + } + + @After + fun teardown() { + if (this::app.isInitialized) { + app.close() + } + RealmLog.setLevel(LogLevel.WARN) + } + + @Test + // FIXME Investigate further + @Ignore("Works on first run, but generates Bad changeset on subsequent runs. Even after " + + "just running one of the other partitionValue test...even if registering a new user") + fun partitionValue_string() { + // FIXME See comment for partitionValue_int32 + val partitionValue = "123464652" + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, BsonString(partitionValue)) + .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .build() + Realm.getInstance(syncConfiguration).use { realm -> + realm.executeTransaction { + realm.createObject(AllTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() + } + } + + @Test + // FIXME Investigate further + @Ignore("Works on first run, but generates Bad changeset on subsequent runs. Even after " + + "just running one of the other partitionValue test...even if registering a new user") + fun partitionValue_int32() { + // FIXME Seems like we cannot repeatedly connect if we change the partitionValue, subsequent + // runs will fail with a + // Connection[1]: Session[1]: Failed to transform received changeset: Schema mismatch: Property 'columnStringList' in class 'AllTypes' is nullable on one side and not on the other. + // Connection[1]: Connection closed due to error + // Session Error[ws://127.0.0.1:9090/]: CLIENT_BAD_CHANGESET(realm::sync::Client::Error:112): Bad changeset (DOWNLOAD) + val int = 123536462 + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, BsonInt32(int)) + .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .build() + Realm.getInstance(syncConfiguration).use { realm -> + realm.executeTransaction { + realm.createObject(AllTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() + } + } + + @Test + // FIXME Investigate further + @Ignore("Works on first run, but generates Bad changeset on subsequent runs. Even after " + + "just running one of the other partitionValue test...even if registering a new user") + fun partitionValue_int64() { + // FIXME See comment for partitionValue_int32 + val long = 1243513244L + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, BsonInt64(long)) + .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .build() + Realm.getInstance(syncConfiguration).use { realm -> + realm.executeTransaction { + realm.createObject(AllTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() + } + } + + @Test + // FIXME Investigate further + @Ignore("Works on first run, but generates Bad changeset on subsequent runs. Even after " + + "just running one of the other partitionValue test...even if registering a new user") + fun partitionValue_objectId() { + // FIXME See comment for partitionValue_int32 + val objectId = ObjectId("5ecf72df02aa3c32ab6b4ce0") + val syncConfiguration = configFactory + .createSyncConfigurationBuilder(user, BsonObjectId(objectId)) + .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .build() + Realm.getInstance(syncConfiguration).use { realm -> + realm.executeTransaction { + realm.createObject(AllTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() + } + } + + @Test + // FIXME Differentiate path for Realms with different partition values + @Ignore("Partition value does not generate different paths") + fun differentPathsForDifferentPartitionValues() { + val syncConfiguration1 = configFactory + .createSyncConfigurationBuilder(user, BsonString("partitionvalue1")) + .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .build() + val syncConfiguration2 = configFactory + .createSyncConfigurationBuilder(user, BsonString("partitionvalue2")) + .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .build() + Realm.getInstance(syncConfiguration1).use { realm1 -> + Realm.getInstance(syncConfiguration2).use { realm2 -> + assertNotEquals(realm1, realm2) + assertNotEquals(realm1.path, realm2.path) + } + } + } + + @Test(timeout = 3000) + fun getState_active() { + Realm.getInstance(syncConfiguration).use { realm -> + val session: SyncSession = realm.syncSession // make sure the `access_token` is acquired. otherwise we can still be // in WAITING_FOR_ACCESS_TOKEN state while (session.state != SyncSession.State.ACTIVE) { SystemClock.sleep(200) } - realm.close() } + } - @get:Test - val state_throwOnClosedSession: Unit - get() { - val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) - val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build() - val realm = Realm.getInstance(syncConfiguration) - val session: SyncSession = SyncManager.getSession(syncConfiguration) - realm.close() - user.logOut() - thrown.expect(IllegalStateException::class.java) - thrown.expectMessage("Could not find session, Realm was probably closed") - session.state + @Test + fun getState_throwOnClosedSession() { + var session: SyncSession? = null + Realm.getInstance(syncConfiguration).use { realm -> + session = realm.syncSession } + user.logOut() - @get:Test - val state_loggedOut: Unit - get() { - val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) - val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build() - val realm = Realm.getInstance(syncConfiguration) - val session: SyncSession = SyncManager.getSession(syncConfiguration) - user.logOut() - val state = session.state - Assert.assertEquals(SyncSession.State.INACTIVE, state) - realm.close() + assertFailsWithMessage(CoreMatchers.equalTo("Could not find session, Realm was probably closed")) { + session!!.state } + } @Test - @Throws(InterruptedException::class) - fun uploadDownloadAllChanges() { - val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) - val adminUser: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) - val userConfig = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + fun getState_loggedOut() { + Realm.getInstance(syncConfiguration).use { realm -> + val session = realm.syncSession + user.logOut(); + assertEquals(SyncSession.State.INACTIVE, session.state); + } + } + + @Test + // FIXME Find a way to flush data from server between each run + @Ignore("Needs clean server as asserting on number of rows of a specific class") + fun uploadDownloadAllChangesWorking() { + Realm.getInstance(syncConfiguration).use { realm -> + realm.executeTransaction { + realm.createObject(AllTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() + } + + // New user but same Realm as configuration has the same partition value + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config2 = configFactory + .createSyncConfigurationBuilder(user2) .build() - val adminConfig = configFactory - .createSyncConfigurationBuilder(adminUser, userConfig.serverUrl.toString()) + + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges() + realm.refresh() + // FIXME Requires server to flush data between each run + assertEquals(1, realm.where(AllTypes::class.java).count()) + } + } + + @Test + // FIXME Find a way to flush data from server between each run + @Ignore("Needs clean server as asserting on number of rows of a specific class") + fun uploadDownloadAllChanges() { + Realm.getInstance(syncConfiguration).use { realm -> + realm.executeTransaction { + realm.createObject(AllTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() + } + + // New user but same Realm as configuration has the same partition value + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config2 = configFactory + .createSyncConfigurationBuilder(user2, syncConfiguration.partitionValue) .build() - val userRealm = Realm.getInstance(userConfig) - userRealm.beginTransaction() - userRealm.createObject(AllTypes::class.java) - userRealm.commitTransaction() - SyncManager.getSession(userConfig).uploadAllLocalChanges() - userRealm.close() - val adminRealm = Realm.getInstance(adminConfig) - SyncManager.getSession(adminConfig).downloadAllServerChanges() - adminRealm.refresh() - Assert.assertEquals(1, adminRealm.where(AllTypes::class.java).count()) - adminRealm.close() + + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges() + realm.refresh() + // FIXME Requires server to flush data between each run + assertEquals(1, realm.where(AllTypes::class.java).count()) + } } @Test - @Throws(InterruptedException::class) + // FIXME Find a way to flush data from server between each run + @Ignore("Needs clean server as asserting on number of rows of a specific class") fun interruptWaits() { - val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) - val adminUser: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) - val userConfig = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build() - val adminConfig = configFactory - .createSyncConfigurationBuilder(adminUser, userConfig.serverUrl.toString()) - .build() + // FIXME Convert to BackgroundLooperThread? Is it doable with all the interruptions val t = Thread(Runnable { - val userRealm = Realm.getInstance(userConfig) - userRealm.beginTransaction() - userRealm.createObject(AllTypes::class.java) - userRealm.commitTransaction() - val userSession: SyncSession = SyncManager.getSession(userConfig) - try { - // 1. Start download (which will be interrupted) - Thread.currentThread().interrupt() - userSession.downloadAllServerChanges() - } catch (ignored: InterruptedException) { - Assert.assertFalse(Thread.currentThread().isInterrupted) - } - try { - // 2. Upload all changes - userSession.uploadAllLocalChanges() - } catch (e: InterruptedException) { - Assert.fail("Upload interrupted") - } - userRealm.close() - val adminRealm = Realm.getInstance(adminConfig) - val adminSession: SyncSession = SyncManager.getSession(adminConfig) - try { - // 3. Start upload (which will be interrupted) - Thread.currentThread().interrupt() - adminSession.uploadAllLocalChanges() - } catch (ignored: InterruptedException) { - Assert.assertFalse(Thread.currentThread().isInterrupted) // clear interrupted flag + Realm.getInstance(syncConfiguration).use { userRealm -> + userRealm.executeTransaction { + userRealm.createObject(AllTypes::class.java, ObjectId()) + } + val userSession = userRealm.syncSession + try { + // 1. Start download (which will be interrupted) + Thread.currentThread().interrupt() + userSession.downloadAllServerChanges() + fail() + } catch (ignored: InterruptedException) { + assertFalse(Thread.currentThread().isInterrupted) + } + try { + // 2. Upload all changes + userSession.uploadAllLocalChanges() + } catch (e: InterruptedException) { + fail("Upload interrupted") + } } - try { - // 4. Download all changes - adminSession.downloadAllServerChanges() - } catch (e: InterruptedException) { - Assert.fail("Download interrupted") + + // New user but same Realm as configuration has the same partition value + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config2 = configFactory + .createSyncConfigurationBuilder(user2) + .build() + + Realm.getInstance(config2).use { adminRealm -> + val adminSession: SyncSession = adminRealm.syncSession + try { + // 3. Start upload (which will be interrupted) + Thread.currentThread().interrupt() + adminSession.uploadAllLocalChanges() + fail() + } catch (ignored: InterruptedException) { + assertFalse(Thread.currentThread().isInterrupted) // clear interrupted flag + } + try { + // 4. Download all changes + adminSession.downloadAllServerChanges() + } catch (e: InterruptedException) { + fail("Download interrupted") + } + adminRealm.refresh() + + // FIXME Requires server to flush data + assertEquals(1, adminRealm.where(AllTypes::class.java).count()) } - adminRealm.refresh() - Assert.assertEquals(1, adminRealm.where(AllTypes::class.java).count()) - adminRealm.close() }) t.start() t.join() @@ -199,163 +352,169 @@ class SyncSessionTests : StandardIntegrationTest() { // check that logging out a SyncUser used by different Realm will // affect all associated sessions. @Test(timeout = 5000) + // FIXME Differentiate path for Realms with different partition values, see differentPathsForDifferentPartitionValues + @Ignore("Partition value does not generate different paths") fun logout_sameSyncUserMultipleSessions() { - val uniqueName = UUID.randomUUID().toString() - var credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val syncConfiguration1 = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build() - val realm1 = Realm.getInstance(syncConfiguration1) - val syncConfiguration2 = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL_2) - .build() - val realm2 = Realm.getInstance(syncConfiguration2) - val session1: SyncSession = SyncManager.getSession(syncConfiguration1) - val session2: SyncSession = SyncManager.getSession(syncConfiguration2) - - // make sure the `access_token` is acquired. otherwise we can still be - // in WAITING_FOR_ACCESS_TOKEN state - while (session1.state != SyncSession.State.ACTIVE || session2.state != SyncSession.State.ACTIVE) { - SystemClock.sleep(200) + Realm.getInstance(syncConfiguration).use { realm1 -> + // New partitionValue to differentiate sync session + val syncConfiguration2 = configFactory + .createSyncConfigurationBuilder(user, BsonObjectId(ObjectId())) + .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .build() + + Realm.getInstance(syncConfiguration2).use { realm2 -> + val session1: SyncSession = realm1.syncSession + val session2: SyncSession = realm2.syncSession + + // make sure the `access_token` is acquired. otherwise we can still be + // in WAITING_FOR_ACCESS_TOKEN state + // FIXME Reavaluate with new sync states + while (session1.state != SyncSession.State.ACTIVE || session2.state != SyncSession.State.ACTIVE) { + SystemClock.sleep(200) + } + + assertEquals(SyncSession.State.ACTIVE, session1.state) + assertEquals(SyncSession.State.ACTIVE, session2.state) + assertNotEquals(realm1, realm2) + assertNotEquals(session1, session2) + assertEquals(session1.user, session2.user) + user.logOut() + assertEquals(SyncSession.State.INACTIVE, session1.state) + assertEquals(SyncSession.State.INACTIVE, session2.state) + + // Login again + app.login(RealmCredentials.emailPassword(user.email!!, SECRET_PASSWORD)) + + // reviving the sessions. The state could be changed concurrently. + // FIXME Reavaluate with new sync states + assertTrue( + //session1.state == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || + session1.state == SyncSession.State.ACTIVE) + assertTrue( + //session2.state == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || + session2.state == SyncSession.State.ACTIVE) + } } - Assert.assertEquals(SyncSession.State.ACTIVE, session1.state) - Assert.assertEquals(SyncSession.State.ACTIVE, session2.state) - Assert.assertNotEquals(session1, session2) - Assert.assertEquals(session1.user, session2.user) - user.logOut() - Assert.assertEquals(SyncSession.State.INACTIVE, session1.state) - Assert.assertEquals(SyncSession.State.INACTIVE, session2.state) - credentials = SyncCredentials.usernamePassword(uniqueName, "password", false) - SyncUser.logIn(credentials, Constants.AUTH_URL) - - // reviving the sessions. The state could be changed concurrently. - Assert.assertTrue(session1.state == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || - session1.state == SyncSession.State.ACTIVE) - Assert.assertTrue(session2.state == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || - session2.state == SyncSession.State.ACTIVE) - realm1.close() - realm2.close() } // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. @Test - @Throws(InterruptedException::class) + // FIXME Investigate further + // FIXME Rewrite to use BlockingLooperThread + @Ignore("Re-logging in does not authorize") fun logBackResumeUpload() { - val uniqueName = UUID.randomUUID().toString() - val credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + val config1 = configFactory + .createSyncConfigurationBuilder(user) .modules(StringOnlyModule()) .waitForInitialRemoteData() .build() - val realm = Realm.getInstance(syncConfiguration) - realm.executeTransaction { realm -> realm.createObject(StringOnly::class.java).chars = "1" } - val session: SyncSession = SyncManager.getSession(syncConfiguration) - session.uploadAllLocalChanges() - user.logOut() + Realm.getInstance(config1).use { realm1 -> + realm1.executeTransaction { realm -> realm.createObject(StringOnly::class.java, ObjectId()).chars = "1" } + val session1: SyncSession = realm1.syncSession + session1.uploadAllLocalChanges() + user.logOut() - // add a commit while we're still offline - realm.executeTransaction { realm -> realm.createObject(StringOnly::class.java).chars = "2" } - val testCompleted = CountDownLatch(1) - val handlerThread = HandlerThread("HandlerThread") - handlerThread.start() - val looper = handlerThread.looper - val handler = Handler(looper) - val allResults = AtomicReference>() // notifier could be GC'ed before it get a chance to trigger the second commit, so declaring it outside the Runnable - handler.post { // access the Realm from an different path on the device (using admin user), then monitor - // when the offline commits get synchronized - val admin: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) - val credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user") - val adminUser: SyncUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL) - val adminConfig: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.serverUrl.toString()) - .modules(StringOnlyModule()) - .waitForInitialRemoteData() - .build() - val adminRealm = Realm.getInstance(adminConfig) - allResults.set(adminRealm.where(StringOnly::class.java).sort(StringOnly.FIELD_CHARS).findAll()) - val realmChangeListener: RealmChangeListener> = object : RealmChangeListener?> { - override fun onChange(stringOnlies: RealmResults) { - if (stringOnlies.size == 2) { - Assert.assertEquals("1", stringOnlies[0]!!.chars) - Assert.assertEquals("2", stringOnlies[1]!!.chars) - handler.post { - - // Closing a Realm from inside a listener doesn't seem to remove the - // active session reference in Object Store - adminRealm.close() - testCompleted.countDown() - handlerThread.quitSafely() + // add a commit while we're still offline + realm1.executeTransaction { realm -> realm.createObject(StringOnly::class.java, ObjectId()).chars = "2" } + val testCompleted = CountDownLatch(1) + val handlerThread = HandlerThread("HandlerThread") + handlerThread.start() + val looper = handlerThread.looper + val handler = Handler(looper) + val allResults = AtomicReference>() // notifier could be GC'ed before it get a chance to trigger the second commit, so declaring it outside the Runnable + handler.post { // access the Realm from an different path on the device (using admin user), then monitor + // when the offline commits get synchronized + // FIXME Do we somehow need to extract the refreshtoken...and could it be the reason for app.login not working later on + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) + .modules(StringOnlyModule()) + .waitForInitialRemoteData() + .build() + val realm2 = Realm.getInstance(config2) + + allResults.set(realm2.where(StringOnly::class.java).sort(StringOnly.FIELD_CHARS).findAll()) + val realmChangeListener: RealmChangeListener> = object : RealmChangeListener> { + override fun onChange(stringOnlies: RealmResults) { + if (stringOnlies.size == 2) { + assertEquals("1", stringOnlies[0]!!.chars) + assertEquals("2", stringOnlies[1]!!.chars) + handler.post { + + // Closing a Realm from inside a listener doesn't seem to remove the + // active session reference in Object Store + realm2.close() + testCompleted.countDown() + handlerThread.quitSafely() + } } } } + allResults.get().addChangeListener(realmChangeListener) + + // login again to re-activate the user + val credentials = RealmCredentials.emailPassword(user.email!!, SECRET_PASSWORD) + // this login will re-activate the logged out user, and resume all it's pending sessions + // the OS will trigger bindSessionWithConfig with the new refresh_token, in order to obtain + // a new access_token. + app.login(credentials) } - allResults.get().addChangeListener(realmChangeListener) - - // login again to re-activate the user - val credentials = SyncCredentials.usernamePassword(uniqueName, "password", false) - // this login will re-activate the logged out user, and resume all it's pending sessions - // the OS will trigger bindSessionWithConfig with the new refresh_token, in order to obtain - // a new access_token. - SyncUser.logIn(credentials, Constants.AUTH_URL) + TestHelper.awaitOrFail(testCompleted) } - TestHelper.awaitOrFail(testCompleted) - realm.close() } // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. // this test validate the behaviour of SyncSessionStopPolicy::AfterChangesUploaded @Test - @Throws(InterruptedException::class) + // FIXME Investigate why it does not terminate...probably rewrite to BlockingLooperThread + @Ignore("Does not terminate") fun uploadChangesWhenRealmOutOfScope() { val strongRefs: MutableList = ArrayList() - val uniqueName = UUID.randomUUID().toString() - val credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) val chars = CharArray(1000000) // 2MB Arrays.fill(chars, '.') val twoMBString = String(chars) - val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + val config1 = configFactory + .createSyncConfigurationBuilder(user) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED) .modules(StringOnlyModule()) .build() - val realm = Realm.getInstance(syncConfiguration) - realm.beginTransaction() - // upload 10MB - for (i in 0..4) { - realm.createObject(StringOnly::class.java).chars = twoMBString + Realm.getInstance(config1).use { realm -> + realm.executeTransaction { + // upload 10MB + for (i in 0..4) { + realm.createObject(StringOnly::class.java, ObjectId()).chars = twoMBString + } + } } - realm.commitTransaction() - realm.close() + val testCompleted = CountDownLatch(1) val handlerThread = HandlerThread("HandlerThread") handlerThread.start() val looper = handlerThread.looper val handler = Handler(looper) - handler.post { // using an admin user to open the Realm on different path on the device to monitor when all the uploads are done - val admin: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) - val adminConfig: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(admin, syncConfiguration.serverUrl.toString()) + handler.post { // using an other user to open the Realm on different path on the device to monitor when all the uploads are done + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) .modules(StringOnlyModule()) .build() - val adminRealm = Realm.getInstance(adminConfig) - val all = adminRealm.where(StringOnly::class.java).findAll() - if (all.size == 5) { - adminRealm.close() - testCompleted.countDown() - handlerThread.quit() - } else { - strongRefs.add(all) - val realmChangeListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet? -> - if (results.size == 5) { - adminRealm.close() - testCompleted.countDown() - handlerThread.quit() + Realm.getInstance(config2).use { realm2 -> + val all = realm2.where(StringOnly::class.java).findAll() + if (all.size == 5) { + realm2.close() + testCompleted.countDown() + handlerThread.quit() + } else { + strongRefs.add(all) + val realmChangeListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet? -> + if (results.size == 5) { + realm2.close() + testCompleted.countDown() + handlerThread.quit() + } } + all.addChangeListener(realmChangeListener) } - all.addChangeListener(realmChangeListener) } + handlerThread.quit() } TestHelper.awaitOrFail(testCompleted, TestHelper.STANDARD_WAIT_SECS) handlerThread.join() @@ -364,212 +523,209 @@ class SyncSessionTests : StandardIntegrationTest() { // A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. @Test - @Throws(InterruptedException::class) + // FIXME Investigate why it does not terminate...probably rewrite to BlockingLooperThread + @Ignore("Does not terminate") fun downloadChangesWhenRealmOutOfScope() { val uniqueName = UUID.randomUUID().toString() var credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + val config1 = configFactory + .createSyncConfigurationBuilder(user) .modules(StringOnlyModule()) .build() - val realm = Realm.getInstance(syncConfiguration) - realm.beginTransaction() - realm.createObject(StringOnly::class.java).chars = "1" - realm.commitTransaction() - val session: SyncSession = SyncManager.getSession(syncConfiguration) - session.uploadAllLocalChanges() - - // Log out the user. - user.logOut() + Realm.getInstance(config1).use { realm -> + realm.executeTransaction { + realm.createObject(StringOnly::class.java, ObjectId()).chars = "1" + } + val session: SyncSession = realm.syncSession + session.uploadAllLocalChanges() - // Log the user back in. - credentials = SyncCredentials.usernamePassword(uniqueName, "password", false) - SyncUser.logIn(credentials, Constants.AUTH_URL) + // Log out the user. + user.logOut() - // now let the admin upload some commits - val backgroundUpload = CountDownLatch(1) - val handlerThread = HandlerThread("HandlerThread") - handlerThread.start() - val looper = handlerThread.looper - val handler = Handler(looper) - handler.post { // using an admin user to open the Realm on different path on the device then some commits - val admin: SyncUser = UserFactory.createAdminUser(Constants.AUTH_URL) - val credentialsAdmin = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user") - val adminUser: SyncUser = SyncUser.logIn(credentialsAdmin, Constants.AUTH_URL) - val adminConfig: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(adminUser, syncConfiguration.serverUrl.toString()) - .modules(StringOnlyModule()) - .waitForInitialRemoteData() - .build() - val adminRealm = Realm.getInstance(adminConfig) - adminRealm.beginTransaction() - adminRealm.createObject(StringOnly::class.java).chars = "2" - adminRealm.createObject(StringOnly::class.java).chars = "3" - adminRealm.commitTransaction() - try { - SyncManager.getSession(adminConfig).uploadAllLocalChanges() - } catch (e: InterruptedException) { - e.printStackTrace() - Assert.fail(e.message) + // Log the user back in. + val credentials = RealmCredentials.emailPassword(user.email!!, SECRET_PASSWORD) + app.login(credentials) + + // now let the admin upload some commits + val backgroundUpload = CountDownLatch(1) + val handlerThread = HandlerThread("HandlerThread") + handlerThread.start() + val looper = handlerThread.looper + val handler = Handler(looper) + handler.post { // using an admin user to open the Realm on different path on the device then some commits + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) + .modules(StringOnlyModule()) + .waitForInitialRemoteData() + .build() + Realm.getInstance(config2).use { realm2 -> + realm2.executeTransaction { + realm2.createObject(StringOnly::class.java, ObjectId()).chars = "2" + realm2.createObject(StringOnly::class.java, ObjectId()).chars = "3" + } + realm2.syncSession.uploadAllLocalChanges() + } + backgroundUpload.countDown() + handlerThread.quit() } - adminRealm.close() - backgroundUpload.countDown() - handlerThread.quit() + TestHelper.awaitOrFail(backgroundUpload, 60) + // Resume downloading + session.downloadAllServerChanges() + realm.refresh() //FIXME not calling refresh will still point to the previous version of the Realm count == 1 + assertEquals(3, realm.where(StringOnly::class.java).count()) } - TestHelper.awaitOrFail(backgroundUpload, 60) - // Resume downloading - session.downloadAllServerChanges() - realm.refresh() //FIXME not calling refresh will still point to the previous version of the Realm count == 1 - Assert.assertEquals(3, realm.where(StringOnly::class.java).count()) - realm.close() } // Check that if we manually trigger a Client Reset, then it should be possible to start // downloading the Realm immediately after. @Test - @RunTestInLooperThread - fun clientReset_manualTriggerAllowSessionToRestart() { - val uniqueName = UUID.randomUUID().toString() - val credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + // TODO Seems to align with tests in SessionTests, should we move them to same location + fun clientReset_manualTriggerAllowSessionToRestart() = looperThread.runBlocking { + val resources = ResourceContainer() + val configRef = AtomicReference(null) - val config: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + val config: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) - .directory(looperThread.getRoot()) - .errorHandler(SyncSession.ErrorHandler { session, error -> + // FIXME Is this critical for the test + // .directory(looperThread.getRoot()) + .errorHandler { session, error -> val handler = error as ClientResetRequiredError // Execute Client Reset - looperThread.closeTestRealms() + resources.close() handler.executeClientReset() // Try to re-open Realm and download it again looperThread.postRunnable(Runnable { // Validate that files have been moved - Assert.assertFalse(handler.originalFile.exists()) - Assert.assertTrue(handler.backupFile.exists()) + assertFalse(handler.originalFile.exists()) + assertTrue(handler.backupFile.exists()) val config = configRef.get() - val instance = Realm.getInstance(config!!) - looperThread.addTestRealm(instance) - try { - SyncManager.getSession(config).downloadAllServerChanges() + Realm.getInstance(config!!).use { realm -> + realm.syncSession.downloadAllServerChanges() looperThread.testComplete() - } catch (e: InterruptedException) { - Assert.fail(e.toString()) } }) - }) + } .build() configRef.set(config) val realm = Realm.getInstance(config) - looperThread.addTestRealm(realm) + resources.add(realm) // Trigger error - SyncManager.simulateClientReset(SyncManager.getSession(config)) + user.app.sync.simulateClientReset(realm.syncSession) } @Test - @RunTestInLooperThread - fun registerConnectionListener() { - getSession(SessionCallback { session: SyncSession -> + // FIXME Implement connection listeners + @Ignore("Connection listener callback is not implemented yet") + fun registerConnectionListener() = looperThread.runBlocking { + getSession { session: SyncSession -> session.addConnectionChangeListener { oldState: ConnectionState?, newState: ConnectionState -> if (newState == ConnectionState.DISCONNECTED) { // Closing a Realm inside a connection listener doesn't work: https://github.com/realm/realm-java/issues/6249 - looperThread.postRunnable({ looperThread.testComplete() }) + looperThread.postRunnable(Runnable { looperThread.testComplete() }) } } session.stop() - }) + } } @Test - @RunTestInLooperThread - fun removeConnectionListener() { - val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) - val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .build() - val realm = Realm.getInstance(syncConfiguration) - val session: SyncSession = SyncManager.getSession(syncConfiguration) - val listener1 = ConnectionListener { oldState: ConnectionState?, newState: ConnectionState -> - if (newState == ConnectionState.DISCONNECTED) { - Assert.fail("Listener should have been removed") + // FIXME Implement connection listeners + @Ignore("Connection listener callback is not implemented yet") + fun removeConnectionListener() = looperThread.runBlocking { + Realm.getInstance(syncConfiguration).use { realm -> + val session: SyncSession = realm.syncSession + val listener1 = ConnectionListener { oldState: ConnectionState?, newState: ConnectionState -> + if (newState == ConnectionState.DISCONNECTED) { + fail("Listener should have been removed") + } } - } - val listener2 = ConnectionListener { oldState: ConnectionState?, newState: ConnectionState -> - if (newState == ConnectionState.DISCONNECTED) { - looperThread.testComplete() + var listener2 = object : ConnectionListener { + override fun onChange(oldState: ConnectionState, newState: ConnectionState) { + if (newState == ConnectionState.DISCONNECTED) { + looperThread.testComplete() + } + } } + session.addConnectionChangeListener(listener1) + session.addConnectionChangeListener(listener2) + session.removeConnectionChangeListener(listener1) } - session.addConnectionChangeListener(listener1) - session.addConnectionChangeListener(listener2) - session.removeConnectionChangeListener(listener1) - realm.close() } - @get:RunTestInLooperThread - @get:Test - val isConnected: Unit - get() { - getActiveSession(SessionCallback { session: SyncSession -> - Assert.assertEquals(session.connectionState, ConnectionState.CONNECTED) - Assert.assertTrue(session.isConnected) - looperThread.testComplete() - }) + @Test + // FIXME Implement connection listeners + @Ignore("Connection listener callback is not implemented yet") + fun getIsConnected() = looperThread.runBlocking { + getActiveSession { session: SyncSession -> + assertEquals(session.connectionState, ConnectionState.CONNECTED) + assertTrue(session.isConnected) + looperThread.testComplete() } + } @Test - @RunTestInLooperThread - fun stopStartSession() { - getActiveSession(SessionCallback { session: SyncSession -> - Assert.assertEquals(SyncSession.State.ACTIVE, session.state) + // FIXME Implement connection listeners + @Ignore("Connection listener callback is not implemented yet") + fun stopStartSession() = looperThread.runBlocking { + getActiveSession { session: SyncSession -> + assertEquals(SyncSession.State.ACTIVE, session.state) session.stop() - Assert.assertEquals(SyncSession.State.INACTIVE, session.state) + assertEquals(SyncSession.State.INACTIVE, session.state) session.start() - Assert.assertNotEquals(SyncSession.State.INACTIVE, session.state) + assertNotEquals(SyncSession.State.INACTIVE, session.state) looperThread.testComplete() - }) + } } @Test - @RunTestInLooperThread - fun start_multipleTimes() { - getActiveSession(SessionCallback { session: SyncSession -> + // FIXME Implement connection listeners + @Ignore("Connection listener callback is not implemented yet") + fun start_multipleTimes() = looperThread.runBlocking { + getActiveSession { session -> session.start() - Assert.assertEquals(SyncSession.State.ACTIVE, session.state) + assertEquals(SyncSession.State.ACTIVE, session.state) session.start() - Assert.assertEquals(SyncSession.State.ACTIVE, session.state) + assertEquals(SyncSession.State.ACTIVE, session.state) looperThread.testComplete() - }) + } } @Test - @RunTestInLooperThread - fun stop_multipleTimes() { - getSession(SessionCallback { session: SyncSession -> + // FIXME Implement connection listeners + @Ignore("Connection listener callback is not implemented yet") + fun stop_multipleTimes() = looperThread.runBlocking { + getActiveSession { session -> session.stop() - Assert.assertEquals(SyncSession.State.INACTIVE, session.state) + assertEquals(SyncSession.State.INACTIVE, session.state) session.stop() - Assert.assertEquals(SyncSession.State.INACTIVE, session.state) + assertEquals(SyncSession.State.INACTIVE, session.state) looperThread.testComplete() - }) + } } @Test - @RunTestInLooperThread - fun waitForInitialRemoteData_throwsOnTimeout() { - val user: SyncUser = UserFactory.createUniqueUser(Constants.AUTH_URL) + // FIXME Investigate + @Ignore("Asserts with no_session when tearing down, meaning that all session are not " + + "closed, but realm seems to be closed, so further investigation is needed") + fun waitForInitialRemoteData_throwsOnTimeout() = looperThread.runBlocking { val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) + .createSyncConfigurationBuilder(user) + .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) .initialData { bgRealm: Realm -> for (i in 0..99) { - bgRealm.createObject(AllTypes::class.java) + bgRealm.createObject(AllTypes::class.java, ObjectId()) } } .waitForInitialRemoteData(1, TimeUnit.MILLISECONDS) .build() - try { - Realm.getInstance(syncConfiguration) - Assert.fail("This should have timed out") - } catch (ignore: DownloadingRealmInterruptedException) { + assertFailsWith { + val instance = Realm.getInstance(syncConfiguration) + looperThread.closeAfterTest(Closeable { + instance.syncSession.close() + instance.close() + }) } looperThread.testComplete() } + } diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java index 7b5d62b5a9..2b4292da18 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java @@ -16,6 +16,8 @@ package io.realm; +import org.bson.BsonValue; + import io.realm.internal.OsRealmConfig; import io.realm.rule.TestRealmConfigurationFactory; @@ -29,4 +31,9 @@ public SyncConfiguration.Builder createSyncConfigurationBuilder(RealmUser user) return new SyncConfiguration.Builder(user, "default") .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY); } + + public SyncConfiguration.Builder createSyncConfigurationBuilder(RealmUser user, BsonValue partitionValue) { + return new SyncConfiguration.Builder(user, partitionValue) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY); + } } From 5b7b4c117ba4e736a6ecb8992dcbd39aa912605d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 28 May 2020 12:44:41 +0200 Subject: [PATCH 1543/2110] Add primary keys for synced classes --- .../androidTest/java/io/realm/entities/StringOnly.java | 9 +++++++++ .../src/testUtils/java/io/realm/entities/AllTypes.java | 7 +++++++ .../src/testUtils/java/io/realm/entities/Cat.java | 9 +++++++++ .../src/testUtils/java/io/realm/entities/Dog.java | 9 +++++++++ .../testUtils/java/io/realm/entities/DogPrimaryKey.java | 4 +++- .../src/testUtils/java/io/realm/entities/Owner.java | 9 +++++++++ 6 files changed, 46 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java index 88bd0419cc..a665ec53d6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java @@ -16,13 +16,22 @@ package io.realm.entities; +import org.bson.types.ObjectId; + import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmField; public class StringOnly extends RealmObject { public static final String CLASS_NAME = "StringOnly"; public static final String FIELD_CHARS = "chars"; + // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value + @PrimaryKey + @RealmField(name = "_id") + private ObjectId id = new ObjectId(); + private String chars; public String getChars() { diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java index e7a0779283..3849ff3388 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java @@ -23,6 +23,8 @@ import io.realm.RealmList; import io.realm.RealmObject; import io.realm.TestHelper; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmField; import io.realm.annotations.Required; import org.bson.types.Decimal128; @@ -57,6 +59,11 @@ public class AllTypes extends RealmObject { FIELD_STRING_LIST, FIELD_BINARY_LIST, FIELD_BOOLEAN_LIST, FIELD_LONG_LIST, FIELD_DOUBLE_LIST, FIELD_FLOAT_LIST, FIELD_DATE_LIST}; + // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value + @PrimaryKey + @RealmField(name = "_id") + private ObjectId id = new ObjectId(); + @Required private String columnString = ""; private long columnLong; diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java b/realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java index b9e218f3e2..619e25622e 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java @@ -16,9 +16,13 @@ package io.realm.entities; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmField; public class Cat extends RealmObject { @@ -32,6 +36,11 @@ public class Cat extends RealmObject { public static final String FIELD_OWNER = "owner"; public static final String FIELD_SCARED_OF_DOG = "scaredOfDog"; + // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value + @PrimaryKey + @RealmField(name = "_id") + private ObjectId id = new ObjectId(); + private String name; private long age; private float height; diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java b/realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java index 4064e2b395..2a05bfdcfd 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java @@ -17,10 +17,14 @@ package io.realm.entities; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.RealmObject; import io.realm.annotations.Index; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmField; public class Dog extends RealmObject { @@ -32,6 +36,11 @@ public class Dog extends RealmObject { public static final String FIELD_BIRTHDAY = "birthday"; public static final String FIELD_HAS_TAIL = "hasTail"; + // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value + @PrimaryKey + @RealmField(name = "_id") + private ObjectId id = new ObjectId(); + @Index private String name; private long age; diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java b/realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java index 21004f036d..0f3e905266 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java @@ -21,11 +21,14 @@ import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmField; public class DogPrimaryKey extends RealmObject { public static final String CLASS_NAME = "DogPrimaryKey"; + // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value + @RealmField(name = "_id") @PrimaryKey private long id; private String name; @@ -49,7 +52,6 @@ public DogPrimaryKey(String name) { this.name = name; } - public long getId() { return id; } diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java b/realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java index 605d5ae79a..25e96a60fe 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java @@ -16,8 +16,12 @@ package io.realm.entities; +import org.bson.types.ObjectId; + import io.realm.RealmList; import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmField; public class Owner extends RealmObject { @@ -26,6 +30,11 @@ public class Owner extends RealmObject { public static String FIELD_DOGS = "dogs"; public static String FIELD_CAT = "cat"; + // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value + @PrimaryKey + @RealmField(name = "_id") + private ObjectId id = new ObjectId(); + private String name; private RealmList dogs; private Cat cat; From de035639e369cb46a4ed9981ea64ff1a359b2a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 28 May 2020 12:51:35 +0200 Subject: [PATCH 1544/2110] Make SyncConfigurations with diffferent partitionValue differ --- .../kotlin/io/realm/SyncConfigurationTests.kt | 1 + .../src/objectServer/java/io/realm/SyncConfiguration.java | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt index 5f2d69e4d2..8196bf7d91 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt @@ -94,6 +94,7 @@ class SyncConfigurationTests { } @Test + // FIXME Tests are not exhaustive fun equals_not() { val user1: RealmUser = createTestUser(app) val user2: RealmUser = createTestUser(app) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index f12fc18981..3bc7dfff04 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -279,7 +279,8 @@ public boolean equals(@Nullable Object o) { if (sessionStopPolicy != that.sessionStopPolicy) return false; if (syncUrlPrefix != null ? !syncUrlPrefix.equals(that.syncUrlPrefix) : that.syncUrlPrefix != null) return false; - return clientResyncMode == that.clientResyncMode; + if (clientResyncMode != that.clientResyncMode) return false; + return partitionValue == that.partitionValue; } @Override @@ -294,6 +295,7 @@ public int hashCode() { result = 31 * result + sessionStopPolicy.hashCode(); result = 31 * result + (syncUrlPrefix != null ? syncUrlPrefix.hashCode() : 0); result = 31 * result + clientResyncMode.hashCode(); + result = 31 * result + partitionValue.hashCode(); return result; } @@ -318,6 +320,8 @@ public String toString() { sb.append("syncUrlPrefix: ").append(syncUrlPrefix); sb.append("\n"); sb.append("clientResyncMode: ").append(clientResyncMode); + sb.append("\n"); + sb.append("partitionValue: ").append(partitionValue); return sb.toString(); } From 6d434c07b9f2695b8a119cc5c203a2af52e1917e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 28 May 2020 15:09:17 +0200 Subject: [PATCH 1545/2110] Revert "Add primary keys for synced classes" This reverts commit 5b7b4c117ba4e736a6ecb8992dcbd39aa912605d. --- .../androidTest/java/io/realm/entities/StringOnly.java | 9 --------- .../src/testUtils/java/io/realm/entities/AllTypes.java | 7 ------- .../src/testUtils/java/io/realm/entities/Cat.java | 9 --------- .../src/testUtils/java/io/realm/entities/Dog.java | 9 --------- .../testUtils/java/io/realm/entities/DogPrimaryKey.java | 4 +--- .../src/testUtils/java/io/realm/entities/Owner.java | 9 --------- 6 files changed, 1 insertion(+), 46 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java index a665ec53d6..88bd0419cc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/StringOnly.java @@ -16,22 +16,13 @@ package io.realm.entities; -import org.bson.types.ObjectId; - import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmField; public class StringOnly extends RealmObject { public static final String CLASS_NAME = "StringOnly"; public static final String FIELD_CHARS = "chars"; - // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value - @PrimaryKey - @RealmField(name = "_id") - private ObjectId id = new ObjectId(); - private String chars; public String getChars() { diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java index 3849ff3388..e7a0779283 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java @@ -23,8 +23,6 @@ import io.realm.RealmList; import io.realm.RealmObject; import io.realm.TestHelper; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmField; import io.realm.annotations.Required; import org.bson.types.Decimal128; @@ -59,11 +57,6 @@ public class AllTypes extends RealmObject { FIELD_STRING_LIST, FIELD_BINARY_LIST, FIELD_BOOLEAN_LIST, FIELD_LONG_LIST, FIELD_DOUBLE_LIST, FIELD_FLOAT_LIST, FIELD_DATE_LIST}; - // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value - @PrimaryKey - @RealmField(name = "_id") - private ObjectId id = new ObjectId(); - @Required private String columnString = ""; private long columnLong; diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java b/realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java index 619e25622e..b9e218f3e2 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/Cat.java @@ -16,13 +16,9 @@ package io.realm.entities; -import org.bson.types.ObjectId; - import java.util.Date; import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmField; public class Cat extends RealmObject { @@ -36,11 +32,6 @@ public class Cat extends RealmObject { public static final String FIELD_OWNER = "owner"; public static final String FIELD_SCARED_OF_DOG = "scaredOfDog"; - // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value - @PrimaryKey - @RealmField(name = "_id") - private ObjectId id = new ObjectId(); - private String name; private long age; private float height; diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java b/realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java index 2a05bfdcfd..4064e2b395 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/Dog.java @@ -17,14 +17,10 @@ package io.realm.entities; -import org.bson.types.ObjectId; - import java.util.Date; import io.realm.RealmObject; import io.realm.annotations.Index; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmField; public class Dog extends RealmObject { @@ -36,11 +32,6 @@ public class Dog extends RealmObject { public static final String FIELD_BIRTHDAY = "birthday"; public static final String FIELD_HAS_TAIL = "hasTail"; - // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value - @PrimaryKey - @RealmField(name = "_id") - private ObjectId id = new ObjectId(); - @Index private String name; private long age; diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java b/realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java index 0f3e905266..21004f036d 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/DogPrimaryKey.java @@ -21,14 +21,11 @@ import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmField; public class DogPrimaryKey extends RealmObject { public static final String CLASS_NAME = "DogPrimaryKey"; - // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value - @RealmField(name = "_id") @PrimaryKey private long id; private String name; @@ -52,6 +49,7 @@ public DogPrimaryKey(String name) { this.name = name; } + public long getId() { return id; } diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java b/realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java index 25e96a60fe..605d5ae79a 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/Owner.java @@ -16,12 +16,8 @@ package io.realm.entities; -import org.bson.types.ObjectId; - import io.realm.RealmList; import io.realm.RealmObject; -import io.realm.annotations.PrimaryKey; -import io.realm.annotations.RealmField; public class Owner extends RealmObject { @@ -30,11 +26,6 @@ public class Owner extends RealmObject { public static String FIELD_DOGS = "dogs"; public static String FIELD_CAT = "cat"; - // FIXME Needed for sync. Does it break usage other places as it now requires createObject with primary key value - @PrimaryKey - @RealmField(name = "_id") - private ObjectId id = new ObjectId(); - private String name; private RealmList dogs; private Cat cat; From fbcef54eb60ae1cf41703d4b0d457de182abaf20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 28 May 2020 16:47:40 +0200 Subject: [PATCH 1546/2110] Add separate sync entities with primary keys --- .../io/realm/entities/DefaultSyncSchema.kt | 4 +- .../kotlin/io/realm/entities/SyncAllTypes.kt | 99 +++++++++++++++ .../io/realm/entities/SyncStringOnly.kt | 36 ++++++ .../io/realm/entities/SyncStringOnlyModule.kt | 22 ++++ .../kotlin/io/realm/SyncSessionTests.kt | 113 ++++++++++-------- 5 files changed, 225 insertions(+), 49 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypes.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnly.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnlyModule.kt diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt index 246e85a22a..3861aa01ce 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt @@ -22,6 +22,6 @@ const val defaultPartitionValue = "default" /** * The set of classes initially supported by MongoDB Realm. */ -@RealmModule(classes = [SyncDog::class, SyncPerson::class]) +@RealmModule(classes = [SyncDog::class, SyncPerson::class, SyncAllTypes::class]) class DefaultSyncSchema { -} \ No newline at end of file +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypes.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypes.kt new file mode 100644 index 0000000000..58713f55e1 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypes.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.entities + +import io.realm.MutableRealmInteger +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.TestHelper +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmField +import io.realm.annotations.Required +import org.bson.types.Decimal128 +import org.bson.types.ObjectId +import java.math.BigDecimal +import java.util.* + +open class SyncAllTypes : RealmObject() { + + companion object { + const val CLASS_NAME = "AllTypes" + const val FIELD_STRING = "columnString" + const val FIELD_LONG = "columnLong" + const val FIELD_FLOAT = "columnFloat" + const val FIELD_DOUBLE = "columnDouble" + const val FIELD_BOOLEAN = "columnBoolean" + const val FIELD_DATE = "columnDate" + const val FIELD_BINARY = "columnBinary" + const val FIELD_MUTABLEREALMINTEGER = "columnMutableRealmInteger" + const val FIELD_DECIMAL128 = "columnDecimal128" + const val FIELD_OBJECT_ID = "columnObjectId" + const val FIELD_REALMOBJECT = "columnRealmObject" + const val FIELD_REALMLIST = "columnRealmList" + const val FIELD_STRING_LIST = "columnStringList" + const val FIELD_BINARY_LIST = "columnBinaryList" + const val FIELD_BOOLEAN_LIST = "columnBooleanList" + const val FIELD_LONG_LIST = "columnLongList" + const val FIELD_DOUBLE_LIST = "columnDoubleList" + const val FIELD_FLOAT_LIST = "columnFloatList" + const val FIELD_DATE_LIST = "columnDateList" + val INVALID_TYPES_FIELDS_FOR_DISTINCT = arrayOf(FIELD_REALMOBJECT, FIELD_REALMLIST, FIELD_DOUBLE, FIELD_FLOAT, + FIELD_STRING_LIST, FIELD_BINARY_LIST, FIELD_BOOLEAN_LIST, FIELD_LONG_LIST, + FIELD_DOUBLE_LIST, FIELD_FLOAT_LIST, FIELD_DATE_LIST) + } + + @PrimaryKey + @RealmField(name = "_id") + var id = ObjectId() + + @Required + var columnString = "" + var columnLong: Long = 0 + var columnFloat = 0f + var columnDouble = 0.0 + var isColumnBoolean = false + + @Required + var columnDate = Date(0) + + @Required + var columnBinary = ByteArray(0) + + @Required + var columnDecimal128 = Decimal128(BigDecimal.ZERO) + + @Required + var columnObjectId = ObjectId(TestHelper.randomObjectIdHexString()) + val columnRealmInteger = MutableRealmInteger.ofNull() + var columnRealmObject: SyncDog? = null + var columnRealmList: RealmList? = null + var columnStringList: RealmList? = null + var columnBinaryList: RealmList? = null + var columnBooleanList: RealmList? = null + var columnLongList: RealmList? = null + var columnDoubleList: RealmList? = null + var columnFloatList: RealmList? = null + var columnDateList: RealmList? = null + var columnDecimal128List: RealmList? = null + + var columnObjectIdList: RealmList? = null + + fun setColumnMutableRealmInteger(value: Int) { + columnRealmInteger.set(value.toLong()) + } + +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnly.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnly.kt new file mode 100644 index 0000000000..c36c0084bc --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnly.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmField +import org.bson.types.ObjectId + +open class SyncStringOnly : RealmObject() { + + companion object { + const val CLASS_NAME = "StringOnly" + const val FIELD_CHARS = "chars" + } + + @PrimaryKey + @RealmField(name = "_id") + var id = ObjectId() + + var chars: String? = null + +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnlyModule.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnlyModule.kt new file mode 100644 index 0000000000..0dced1ff20 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnlyModule.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.entities + +import io.realm.annotations.RealmModule + +@RealmModule(classes = [SyncStringOnly::class]) +class SyncStringOnlyModule diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 89ab1fc73b..67b3e630d0 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -5,7 +5,10 @@ import android.os.HandlerThread import android.os.SystemClock import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import io.realm.entities.* +import io.realm.entities.DefaultSyncSchema +import io.realm.entities.SyncAllTypes +import io.realm.entities.SyncStringOnly +import io.realm.entities.SyncStringOnlyModule import io.realm.exceptions.DownloadingRealmInterruptedException import io.realm.internal.OsRealmConfig import io.realm.kotlin.syncSession @@ -93,7 +96,7 @@ class SyncSessionTests { user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) syncConfiguration = configFactory .createSyncConfigurationBuilder(user) - .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .modules(DefaultSyncSchema()) .build() } @@ -114,11 +117,11 @@ class SyncSessionTests { val partitionValue = "123464652" val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonString(partitionValue)) - .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .modules(DefaultSyncSchema()) .build() Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(AllTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -137,11 +140,11 @@ class SyncSessionTests { val int = 123536462 val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonInt32(int)) - .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .modules(DefaultSyncSchema()) .build() Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(AllTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -156,11 +159,11 @@ class SyncSessionTests { val long = 1243513244L val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonInt64(long)) - .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .modules(DefaultSyncSchema()) .build() Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(AllTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -175,11 +178,11 @@ class SyncSessionTests { val objectId = ObjectId("5ecf72df02aa3c32ab6b4ce0") val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonObjectId(objectId)) - .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .modules(DefaultSyncSchema()) .build() Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(AllTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -191,11 +194,13 @@ class SyncSessionTests { fun differentPathsForDifferentPartitionValues() { val syncConfiguration1 = configFactory .createSyncConfigurationBuilder(user, BsonString("partitionvalue1")) - .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .modules(DefaultSyncSchema()) + .build() val syncConfiguration2 = configFactory .createSyncConfigurationBuilder(user, BsonString("partitionvalue2")) - .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .modules(DefaultSyncSchema()) + .build() Realm.getInstance(syncConfiguration1).use { realm1 -> Realm.getInstance(syncConfiguration2).use { realm2 -> @@ -243,10 +248,10 @@ class SyncSessionTests { @Test // FIXME Find a way to flush data from server between each run @Ignore("Needs clean server as asserting on number of rows of a specific class") - fun uploadDownloadAllChangesWorking() { + fun uploadDownloadAllChanges() { Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(AllTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -255,38 +260,52 @@ class SyncSessionTests { val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2 = configFactory .createSyncConfigurationBuilder(user2) + .modules(DefaultSyncSchema()) .build() Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() realm.refresh() // FIXME Requires server to flush data between each run - assertEquals(1, realm.where(AllTypes::class.java).count()) + assertEquals(1, realm.where(SyncAllTypes::class.java).count()) } } @Test - // FIXME Find a way to flush data from server between each run - @Ignore("Needs clean server as asserting on number of rows of a specific class") - fun uploadDownloadAllChanges() { + // FIXME Investigate further + @Ignore("Bad changeset for session with different partitionValue") + fun sameSchemeWithDifferentPartitionValue() { Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(AllTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } + // Not relevant for test but just to verify that we actually download the correct schema // New user but same Realm as configuration has the same partition value val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2 = configFactory - .createSyncConfigurationBuilder(user2, syncConfiguration.partitionValue) + .createSyncConfigurationBuilder(user2) + .modules(DefaultSyncSchema()) .build() Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() - realm.refresh() - // FIXME Requires server to flush data between each run - assertEquals(1, realm.where(AllTypes::class.java).count()) + } + + // New user and different partition value + val user3 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config3 = configFactory + .createSyncConfigurationBuilder(user3, BsonObjectId(ObjectId())) + .modules(DefaultSyncSchema()) + .build() + + Realm.getInstance(config3).use { realm -> + realm.executeTransaction { + realm.createObject(SyncAllTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() } } @@ -298,7 +317,7 @@ class SyncSessionTests { val t = Thread(Runnable { Realm.getInstance(syncConfiguration).use { userRealm -> userRealm.executeTransaction { - userRealm.createObject(AllTypes::class.java, ObjectId()) + userRealm.createObject(SyncAllTypes::class.java, ObjectId()) } val userSession = userRealm.syncSession try { @@ -342,7 +361,7 @@ class SyncSessionTests { adminRealm.refresh() // FIXME Requires server to flush data - assertEquals(1, adminRealm.where(AllTypes::class.java).count()) + assertEquals(1, adminRealm.where(SyncAllTypes::class.java).count()) } }) t.start() @@ -359,7 +378,7 @@ class SyncSessionTests { // New partitionValue to differentiate sync session val syncConfiguration2 = configFactory .createSyncConfigurationBuilder(user, BsonObjectId(ObjectId())) - .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .modules(DefaultSyncSchema()) .build() Realm.getInstance(syncConfiguration2).use { realm2 -> @@ -405,36 +424,36 @@ class SyncSessionTests { fun logBackResumeUpload() { val config1 = configFactory .createSyncConfigurationBuilder(user) - .modules(StringOnlyModule()) + .modules(SyncStringOnlyModule()) .waitForInitialRemoteData() .build() Realm.getInstance(config1).use { realm1 -> - realm1.executeTransaction { realm -> realm.createObject(StringOnly::class.java, ObjectId()).chars = "1" } + realm1.executeTransaction { realm -> realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "1" } val session1: SyncSession = realm1.syncSession session1.uploadAllLocalChanges() user.logOut() // add a commit while we're still offline - realm1.executeTransaction { realm -> realm.createObject(StringOnly::class.java, ObjectId()).chars = "2" } + realm1.executeTransaction { realm -> realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "2" } val testCompleted = CountDownLatch(1) val handlerThread = HandlerThread("HandlerThread") handlerThread.start() val looper = handlerThread.looper val handler = Handler(looper) - val allResults = AtomicReference>() // notifier could be GC'ed before it get a chance to trigger the second commit, so declaring it outside the Runnable + val allResults = AtomicReference>() // notifier could be GC'ed before it get a chance to trigger the second commit, so declaring it outside the Runnable handler.post { // access the Realm from an different path on the device (using admin user), then monitor // when the offline commits get synchronized // FIXME Do we somehow need to extract the refreshtoken...and could it be the reason for app.login not working later on val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) - .modules(StringOnlyModule()) + .modules(SyncStringOnlyModule()) .waitForInitialRemoteData() .build() val realm2 = Realm.getInstance(config2) - allResults.set(realm2.where(StringOnly::class.java).sort(StringOnly.FIELD_CHARS).findAll()) - val realmChangeListener: RealmChangeListener> = object : RealmChangeListener> { - override fun onChange(stringOnlies: RealmResults) { + allResults.set(realm2.where(SyncStringOnly::class.java).sort(SyncStringOnly.FIELD_CHARS).findAll()) + val realmChangeListener: RealmChangeListener> = object : RealmChangeListener> { + override fun onChange(stringOnlies: RealmResults) { if (stringOnlies.size == 2) { assertEquals("1", stringOnlies[0]!!.chars) assertEquals("2", stringOnlies[1]!!.chars) @@ -475,13 +494,13 @@ class SyncSessionTests { val config1 = configFactory .createSyncConfigurationBuilder(user) .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED) - .modules(StringOnlyModule()) + .modules(SyncStringOnlyModule()) .build() Realm.getInstance(config1).use { realm -> realm.executeTransaction { // upload 10MB for (i in 0..4) { - realm.createObject(StringOnly::class.java, ObjectId()).chars = twoMBString + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = twoMBString } } } @@ -494,17 +513,17 @@ class SyncSessionTests { handler.post { // using an other user to open the Realm on different path on the device to monitor when all the uploads are done val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) - .modules(StringOnlyModule()) + .modules(SyncStringOnlyModule()) .build() Realm.getInstance(config2).use { realm2 -> - val all = realm2.where(StringOnly::class.java).findAll() + val all = realm2.where(SyncStringOnly::class.java).findAll() if (all.size == 5) { realm2.close() testCompleted.countDown() handlerThread.quit() } else { strongRefs.add(all) - val realmChangeListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet? -> + val realmChangeListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet? -> if (results.size == 5) { realm2.close() testCompleted.countDown() @@ -530,11 +549,11 @@ class SyncSessionTests { var credentials = SyncCredentials.usernamePassword(uniqueName, "password", true) val config1 = configFactory .createSyncConfigurationBuilder(user) - .modules(StringOnlyModule()) + .modules(SyncStringOnlyModule()) .build() Realm.getInstance(config1).use { realm -> realm.executeTransaction { - realm.createObject(StringOnly::class.java, ObjectId()).chars = "1" + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "1" } val session: SyncSession = realm.syncSession session.uploadAllLocalChanges() @@ -555,13 +574,13 @@ class SyncSessionTests { handler.post { // using an admin user to open the Realm on different path on the device then some commits val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) - .modules(StringOnlyModule()) + .modules(SyncStringOnlyModule()) .waitForInitialRemoteData() .build() Realm.getInstance(config2).use { realm2 -> realm2.executeTransaction { - realm2.createObject(StringOnly::class.java, ObjectId()).chars = "2" - realm2.createObject(StringOnly::class.java, ObjectId()).chars = "3" + realm2.createObject(SyncStringOnly::class.java, ObjectId()).chars = "2" + realm2.createObject(SyncStringOnly::class.java, ObjectId()).chars = "3" } realm2.syncSession.uploadAllLocalChanges() } @@ -572,7 +591,7 @@ class SyncSessionTests { // Resume downloading session.downloadAllServerChanges() realm.refresh() //FIXME not calling refresh will still point to the previous version of the Realm count == 1 - assertEquals(3, realm.where(StringOnly::class.java).count()) + assertEquals(3, realm.where(SyncStringOnly::class.java).count()) } } @@ -710,10 +729,10 @@ class SyncSessionTests { fun waitForInitialRemoteData_throwsOnTimeout() = looperThread.runBlocking { val syncConfiguration = configFactory .createSyncConfigurationBuilder(user) - .schema(AllTypes::class.java, Dog::class.java, Owner::class.java, Cat::class.java, DogPrimaryKey::class.java) + .modules(DefaultSyncSchema()) .initialData { bgRealm: Realm -> for (i in 0..99) { - bgRealm.createObject(AllTypes::class.java, ObjectId()) + bgRealm.createObject(SyncAllTypes::class.java, ObjectId()) } } .waitForInitialRemoteData(1, TimeUnit.MILLISECONDS) From 28c046a911a3bb29153cb53de3534b965b0f8d93 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 28 May 2020 17:22:00 +0200 Subject: [PATCH 1547/2110] Add JavaDoc to RealmAppConfiguration + test stubs (#6885) --- .../java/io/realm/IOSRealmTests.java | 2 +- .../io/realm/RealmConfigurationTests.java | 4 +- .../io/realm/RealmAppConfigurationTests.kt | 95 ++++++++- .../src/main/java/io/realm/Realm.java | 5 + .../java/io/realm/RealmConfiguration.java | 9 +- .../java/io/realm/RealmAppConfiguration.java | 193 ++++++++++-------- .../java/io/realm/SyncConfiguration.java | 4 +- .../objectstore/OsJavaNetworkTransport.java | 3 +- .../java/io/realm/TestRealmApp.kt | 1 - .../testUtils/java/io/realm/TestHelper.java | 4 +- 10 files changed, 206 insertions(+), 114 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java index 7fa55d6614..9b04a33bd7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/IOSRealmTests.java @@ -218,7 +218,7 @@ public void iOSEncryptedRealm() throws IOException { } private byte[] getIOSKey() { - byte[] keyData = new byte[64]; + byte[] keyData = new byte[Realm.ENCRYPTION_KEY_LENGTH]; for (int i = 0; i < keyData.length; i++) { keyData[i] = 1; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index ba0fca9266..2f437475ac 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -201,8 +201,8 @@ public void constructBuilder_nullKeyThrows() { public void constructBuilder_wrongKeyLengthThrows() { byte[][] wrongKeys = new byte[][] { new byte[0], - new byte[RealmConfiguration.KEY_LENGTH - 1], - new byte[RealmConfiguration.KEY_LENGTH + 1] + new byte[Realm.ENCRYPTION_KEY_LENGTH - 1], + new byte[Realm.ENCRYPTION_KEY_LENGTH + 1] }; for (byte[] key : wrongKeys) { try { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt index b346508e9d..1a2fdba9f9 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt @@ -23,6 +23,7 @@ import org.bson.codecs.configuration.CodecRegistry import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -34,16 +35,6 @@ import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) class RealmAppConfigurationTests { - // FIXME: Add tests for remaining builder methods - // builder.appName() - // builder.appVersion() - // builder.baseUrl() - // builder.defaultSyncErrorHandler() - // builder.encryptionKey() - // builder.logLevel() - // builder.requestTimeout() - // builder.syncRootDir() - @get:Rule val tempFolder = TemporaryFolder() @@ -68,6 +59,10 @@ class RealmAppConfigurationTests { .authorizationHeaderName("CustomAuth") .build() assertEquals("CustomAuth", config2.authorizationHeaderName) + + // FIXME Add network check + + // FIXME Add sync session check } @Test @@ -89,6 +84,10 @@ class RealmAppConfigurationTests { assertEquals(2, headers.size.toLong()) assertTrue(headers.any { it.key == "header1" && it.value == "val1" }) assertTrue(headers.any { it.key == "header2" && it.value == "val2" }) + + // FIXME Add network check + + // FIXME Add sync session check } @Test @@ -123,6 +122,8 @@ class RealmAppConfigurationTests { val config = RealmAppConfiguration.Builder("app-id").build() val expectedDefaultRoot = File(InstrumentationRegistry.getInstrumentation().targetContext.filesDir, "mongodb-realm") assertEquals(expectedDefaultRoot, config.syncRootDirectory) + + // FIXME Add check when opening Realm } @Test @@ -133,6 +134,8 @@ class RealmAppConfigurationTests { .syncRootDirectory(expectedRoot) .build() assertEquals(expectedRoot, config.syncRootDirectory) + + // FIXME Add check when opening Realm } @Test @@ -156,6 +159,78 @@ class RealmAppConfigurationTests { assertFailsWith { builder.syncRootDirectory(file) } } + @Test + @Ignore("FIXME") + fun appName() { + TODO("FIXME: When support has been added in ObjectStore") + } + + @Test + @Ignore("FIXME") + fun appName_invalidValuesThrows() { + TODO() + } + + @Test + @Ignore("FIXME") + fun appVersion() { + TODO("FIXME: When support has been added in ObjectStore") + } + + @Test + @Ignore("FIXME") + fun appVersion_invalidValuesThrows() { + TODO() + } + + @Test + @Ignore("FIXME") + fun baseUrl() { + TODO() + } + + @Test + @Ignore("FIXME") + fun baseUrl_invalidValuesThrows() { + TODO() + } + + @Test + @Ignore("FIXME") + fun defaultSyncErrorHandler() { + TODO() + } + + @Test + @Ignore("FIXME") + fun defaultSyncErrorHandler_invalidValuesThrows() { + TODO() + } + + @Test + @Ignore("FIXME") + fun encryptionKey() { + TODO() + } + + @Test + @Ignore("FIXME") + fun encryptionKey_invalidValuesThrows() { + TODO() + } + + @Test + @Ignore("FIXME") + fun requestTimeout() { + TODO() + } + + @Test + @Ignore("FIXME") + fun requestTimeout_invalidValuesThrows() { + TODO() + } + @Test fun codecRegistry_null() { val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 1aab74a91e..364f8d6d2b 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -137,6 +137,11 @@ public class Realm extends BaseRealm { public static final String DEFAULT_REALM_NAME = RealmConfiguration.DEFAULT_REALM_NAME; + /** + * The required length for encryption keys used to encrypt Realm data. + */ + public static final int ENCRYPTION_KEY_LENGTH = 64; + private static final Object defaultConfigurationLock = new Object(); // guarded by `defaultConfigurationLock` private static RealmConfiguration defaultConfiguration; diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index f0466edf59..60218b895d 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -66,7 +66,6 @@ public class RealmConfiguration { public static final String DEFAULT_REALM_NAME = "default.realm"; - public static final int KEY_LENGTH = 64; private static final Object DEFAULT_MODULE; protected static final RealmProxyMediator DEFAULT_MODULE_MEDIATOR; @@ -409,7 +408,7 @@ public String toString() { stringBuilder.append("\n"); stringBuilder.append("canonicalPath: ").append(canonicalPath); stringBuilder.append("\n"); - stringBuilder.append("key: ").append("[length: ").append(key == null ? 0 : KEY_LENGTH).append("]"); + stringBuilder.append("key: ").append("[length: ").append(key == null ? 0 : Realm.ENCRYPTION_KEY_LENGTH).append("]"); stringBuilder.append("\n"); stringBuilder.append("schemaVersion: ").append(Long.toString(schemaVersion)); stringBuilder.append("\n"); @@ -561,17 +560,17 @@ public Builder directory(File directory) { /** * Sets the 64 byte key used to encrypt and decrypt the Realm file. - * Sets the {@value io.realm.RealmConfiguration#KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file. + * Sets the {@value io.realm.Realm#ENCRYPTION_KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file. */ public Builder encryptionKey(byte[] key) { //noinspection ConstantConditions if (key == null) { throw new IllegalArgumentException("A non-null key must be provided"); } - if (key.length != KEY_LENGTH) { + if (key.length != Realm.ENCRYPTION_KEY_LENGTH) { throw new IllegalArgumentException(String.format(Locale.US, "The provided key must be %s bytes. Yours was: %s", - KEY_LENGTH, key.length)); + Realm.ENCRYPTION_KEY_LENGTH, key.length)); } this.key = Arrays.copyOf(key, key.length); return this; diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java index e0ca95e8c3..5e6abbacf1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java @@ -38,14 +38,40 @@ import javax.annotation.Nullable; import io.realm.internal.Util; -import io.realm.log.LogLevel; import io.realm.log.RealmLog; /** - * FIXME + * A RealmAppConfiguration is used to setup a MongoDB Realm application. + *

              + * Instances of a RealmAppConfiguration can only created by using the + * {@link io.realm.RealmAppConfiguration.Builder} and calling its + * {@link io.realm.RealmAppConfiguration.Builder#build()} method. + *

              + * Configuring a RealmApp is only required if the default settings are not enough. Otherwise calling + * {@code new RealmApp("app-id")} is sufficient. */ public class RealmAppConfiguration { + /** + * The default url for MongoDB Realm applications. + * + * @see Builder#baseUrl(String) + */ + public final static String DEFAULT_BASE_URL = "https://realm-dev.mongodb.com"; //FIXME change to production url before beta release + + /** + * The default request timeout for network requests towards MongoDB Realm in seconds. + * + * @see Builder#requestTimeout(long, TimeUnit) + */ + public final static long DEFAULT_REQUEST_TIMEOUT = 60; + + /** + * The default header name used to carry authorization data when making network requests + * towards MongoDB Realm. + */ + public static final String DEFAULT_AUTHORIZATION_HEADER_NAME = "Authorization"; + /** * Default BSON codec registry for encoding/decoding arguments and results to/from MongoDB Realm backend. * @@ -76,7 +102,6 @@ public class RealmAppConfiguration { private final URL baseUrl; private final SyncSession.ErrorHandler defaultErrorHandler; @Nullable private final byte[] encryptionKey; - private final long logLevel; private final long requestTimeoutMs; private final String authorizationHeaderName; private final Map customHeaders; @@ -86,10 +111,9 @@ public class RealmAppConfiguration { private RealmAppConfiguration(String appId, String appName, String appVersion, - String baseUrl, + URL baseUrl, SyncSession.ErrorHandler defaultErrorHandler, @Nullable byte[] encryptionKey, - long logLevel, long requestTimeoutMs, String authorizationHeaderName, Map customHeaders, @@ -99,10 +123,9 @@ private RealmAppConfiguration(String appId, this.appId = appId; this.appName = appName; this.appVersion = appVersion; - this.baseUrl = createUrl(baseUrl); + this.baseUrl = baseUrl; this.defaultErrorHandler = defaultErrorHandler; this.encryptionKey = (encryptionKey == null) ? null : Arrays.copyOf(encryptionKey, encryptionKey.length); - this.logLevel = logLevel; this.requestTimeoutMs = requestTimeoutMs; this.authorizationHeaderName = (!Util.isEmptyString(authorizationHeaderName)) ? authorizationHeaderName : "Authorization"; this.customHeaders = Collections.unmodifiableMap(customHeaders); @@ -110,93 +133,71 @@ private RealmAppConfiguration(String appId, this.codecRegistry = codecRegistry; } - private URL createUrl(String baseUrl) { - try { - return new URL(baseUrl); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(baseUrl); - } - } - /** - * FIXME - * @return + * Returns the unique app id that identities the Realm application. */ public String getAppId() { return appId; } /** - * FIXME - * @return + * Returns the name used to describe the Realm application. This is only used as debug + * information. */ public String getAppName() { return appName; } /** - * FIXME - * @return + * Returns the version of this Realm application. This is only used as debug information. */ public String getAppVersion() { return appVersion; } /** - * FIXME - * @return + * Returns the base url for this Realm application. */ public URL getBaseUrl() { return baseUrl; } /** - * FIXME - * @return + * Returns the encryption key, if any, that is used to encrypt Realm users meta data on this + * device. If no key is returned, the data is not encrypted. */ + @Nullable public byte[] getEncryptionKey() { return encryptionKey == null ? null : Arrays.copyOf(encryptionKey, encryptionKey.length); } /** - * FIXME - * @return - */ - public long getLogLevel() { - return logLevel; - } - - /** - * FIXME - * @return + * Returns the default timeout for network requests against the Realm application in + * milliseconds. */ public long getRequestTimeoutMs() { return requestTimeoutMs; } - /** - * FIXME - * - * @return + * Returns the name of the header used to carry authentication data when making network + * requests towards MongoDB Realm. */ public String getAuthorizationHeaderName() { return authorizationHeaderName; } /** - * FIXME - * - * @return + * Returns any custom configured headers that will be sent alongside other headers when + * making network requests towards MongoDB Realm. */ public Map getCustomRequestHeaders() { return customHeaders; } /** - * FIXME - * - * @return + * Returns the default error handler used by synced Realms if there are problems with their + * {@link SyncSession}. */ public SyncSession.ErrorHandler getDefaultErrorHandler() { return defaultErrorHandler; @@ -214,14 +215,14 @@ public File getSyncRootDirectory() { public CodecRegistry getDefaultCodecRegistry() { return codecRegistry; } /** - * FIXME + * Builder used to construct instances of a {@link RealmAppConfiguration} in a fluent manner. */ public static class Builder { private String appId; private String appName; private String appVersion; - private String baseUrl = "https://stitch.mongodb.com"; // FIXME Find the correct base url for release + private URL baseUrl = createUrl(DEFAULT_BASE_URL); private SyncSession.ErrorHandler defaultErrorHandler = new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, ObjectServerError error) { @@ -246,17 +247,16 @@ public void onError(SyncSession session, ObjectServerError error) { } }; private byte[] encryptionKey; - private long logLevel = LogLevel.WARN; // FIXME: Consider what this should be set at - private long requestTimeoutMs = 60000; - private String autorizationHeaderName; + private long requestTimeoutMs = TimeUnit.MILLISECONDS.convert(DEFAULT_REQUEST_TIMEOUT, TimeUnit.SECONDS); + private String authorizationHeaderName; private Map customHeaders = new HashMap<>(); private File syncRootDir; private CodecRegistry codecRegistry = DEFAULT_BSON_CODEC_REGISTRY; /** - * FIXME + * Creates an instance of the Builder for the RealmAppConfiguration. * - * @param appId + * @param appId the application id of the MongoDB Realm Application. */ public Builder(String appId) { Util.checkEmpty(appId, "appId"); @@ -273,70 +273,66 @@ public Builder(String appId) { } /** - * FIXME - * - * @param level - * @return - */ - public Builder logLevel(int level) { - // FIXME: Boundary checks - this.logLevel = level; - return this; - } - - /** - * FIXME + * Sets the encryption key used to encrypt user meta data only. Individual Realms needs to + * use {@link SyncConfiguration.Builder#encryptionKey(byte[])} to make them encrypted. * - * @param key - * @return + * @param key a 64 byte encryption key. + * @throws IllegalArgumentException if the key is not 64 bytes long. */ public Builder encryptionKey(byte[] key) { + Util.checkNull(key, "key"); + if (key.length != Realm.ENCRYPTION_KEY_LENGTH) { + throw new IllegalArgumentException(String.format(Locale.US, + "The provided key must be %s bytes. Yours was: %s", + Realm.ENCRYPTION_KEY_LENGTH, key.length)); + } this.encryptionKey = Arrays.copyOf(key, key.length); return this; } /** - * FIXME + * Sets the base url for the MongoDB Realm Application. The default value is + * {@link #DEFAULT_BASE_URL}. * - * @param baseUrl - * @return + * @param baseUrl the base url for the MongoDB Realm application. */ public Builder baseUrl(String baseUrl) { - // FIXME Check input - this.baseUrl = baseUrl; + Util.checkNull(baseUrl, "baseUrl"); + this.baseUrl = createUrl(baseUrl); return this; } /** - * FIXME + * Sets the apps name. This is only used as part of debug headers sent when making + * network requests at the MongoDB Realm application. * - * @param appName - * @return + * @param appName app name used to identify the application. */ public Builder appName(String appName) { - // FIXME CHecks + Util.checkEmpty(appName, "appName"); this.appName = appName; return this; } /** - * FIXME + * Sets the apps version. This is only used as part of debug headers sent when making + * network requests at the MongoDB Realm application. * - * @param appVersion - * @return + * @param appVersion app version used to identify the application. */ public Builder appVersion(String appVersion) { - // FIXME checks + Util.checkEmpty(appVersion, "appVersion"); this.appVersion = appVersion; return this; } /** - * FIXME + * Sets the default timeout used by network requests against the MongoDB Realm application. + * Requests will terminate with a failure if they exceed this limit. The default value is + * {@link RealmAppConfiguration#DEFAULT_REQUEST_TIMEOUT} seconds. * - * @param time - * @param unit - * @return + * @param time the timeout value for network requests. + * @param unit the unit of time used to define the timeout. */ public Builder requestTimeout(long time, TimeUnit unit) { if (time < 1) { @@ -352,7 +348,7 @@ public Builder requestTimeout(long time, TimeUnit unit) { * MongoDB Realm. The MongoDB server or firewall must have been configured to expect a * custom authorization header. *

              - * The default authorization header is named "Authorization". + * The default authorization header is named {@link #DEFAULT_AUTHORIZATION_HEADER_NAME}. * * @param headerName name of the header. * @throws IllegalArgumentException if a null or empty header is provided. @@ -360,7 +356,7 @@ public Builder requestTimeout(long time, TimeUnit unit) { */ public Builder authorizationHeaderName(String headerName) { Util.checkEmpty(headerName, "headerName"); - this.autorizationHeaderName = headerName; + this.authorizationHeaderName = headerName; return this; } @@ -392,9 +388,14 @@ public Builder addCustomRequestHeaders(@Nullable Map headers) { } /** + * Sets the default error handler used by Synced Realms when reporting errors with their + * session. + *

              + * This default can be overridden by calling + * {@link SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler)} when creating + * the {@link SyncConfiguration}. * - * @param errorHandler - * @return + * @param errorHandler the default error handler. */ public Builder defaultSyncErrorHandler(SyncSession.ErrorHandler errorHandler) { Util.checkNull(errorHandler, "errorHandler"); @@ -428,6 +429,14 @@ public Builder syncRootDirectory(File rootDir) { return this; } + private URL createUrl(String baseUrl) { + try { + return new URL(baseUrl); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(baseUrl); + } + } + // FIXME Doc public Builder codecRegistry(CodecRegistry codecRegistry) { Util.checkNull(codecRegistry, "codecRegistry"); @@ -435,6 +444,11 @@ public Builder codecRegistry(CodecRegistry codecRegistry) { return this; } + /** + * Creates the RealmAppConfiguration. + * + * @return the RealmAppConfiguration that can be used to create a {@link RealmApp}. + */ public RealmAppConfiguration build() { return new RealmAppConfiguration(appId, appName, @@ -442,9 +456,8 @@ public RealmAppConfiguration build() { baseUrl, defaultErrorHandler, encryptionKey, - logLevel, requestTimeoutMs, - autorizationHeaderName, + authorizationHeaderName, customHeaders, syncRootDir, codecRegistry); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index be1c02724f..2b3abd84aa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -581,10 +581,10 @@ public Builder encryptionKey(byte[] key) { if (key == null) { throw new IllegalArgumentException("A non-null key must be provided"); } - if (key.length != KEY_LENGTH) { + if (key.length != Realm.ENCRYPTION_KEY_LENGTH) { throw new IllegalArgumentException(String.format(Locale.US, "The provided key must be %s bytes. Yours was: %s", - KEY_LENGTH, key.length)); + Realm.ENCRYPTION_KEY_LENGTH, key.length)); } this.key = Arrays.copyOf(key, key.length); return this; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java index 59027f2e7b..1107de0875 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.Map; +import io.realm.RealmAppConfiguration; import io.realm.internal.Keep; /** @@ -73,7 +74,7 @@ public Map getCustomRequestHeaders() { * Used for testing. */ public void resetHeaders() { - authorizationHeaderName = "Authorization"; + authorizationHeaderName = RealmAppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME; customHeaders.clear(); } diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt b/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt index df12fa0c72..660e20c9fc 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt @@ -37,7 +37,6 @@ class TestRealmApp(networkTransport: OsJavaNetworkTransport? = null, customizeCo companion object { fun createConfiguration(): RealmAppConfiguration { return RealmAppConfiguration.Builder(initializeMongoDbRealm()) - .logLevel(LogLevel.DEBUG) .baseUrl("http://127.0.0.1:9090") .appName("MongoDB Realm Integration Tests") .appVersion("1.0.") diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index b5e283f428..33a8802153 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -331,7 +331,7 @@ public static InputStream stringToStream(String str) { // Returns a random key used by encrypted Realms. public static byte[] getRandomKey() { - byte[] key = new byte[64]; + byte[] key = new byte[Realm.ENCRYPTION_KEY_LENGTH]; RANDOM.nextBytes(key); return key; } @@ -345,7 +345,7 @@ public static String getRandomEmail() { // Returns a random key from the given seed. Used by encrypted Realms. public static byte[] getRandomKey(long seed) { - byte[] key = new byte[64]; + byte[] key = new byte[Realm.ENCRYPTION_KEY_LENGTH]; new Random(seed).nextBytes(key); return key; } From e5010964214cf5be6b98c9ddf4bb437b67fdb00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 29 May 2020 10:35:31 +0200 Subject: [PATCH 1548/2110] Move partition value serialization out of basic flavor --- .../java/io/realm/internal/OsRealmConfig.java | 20 +----------------- .../internal/SyncObjectServerFacade.java | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index a608465778..98a2f39902 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -16,8 +16,6 @@ package io.realm.internal; -import org.bson.BsonValue; - import java.io.File; import java.net.ProxySelector; import java.net.URI; @@ -28,9 +26,7 @@ import javax.annotation.Nullable; import io.realm.CompactOnLaunchCallback; -import io.realm.RealmAppConfiguration; import io.realm.RealmConfiguration; -import io.realm.internal.jni.JniBsonProtocol; import io.realm.log.RealmLog; /** @@ -224,7 +220,7 @@ private OsRealmConfig(final RealmConfiguration config, //noinspection unchecked Map customHeadersMap = (Map) (syncConfigurationOptions[j++]); Byte clientResyncMode = (Byte) syncConfigurationOptions[j++]; - BsonValue partitionValue = (BsonValue) syncConfigurationOptions[j++]; + String encodedPartitionValue = (String) syncConfigurationOptions[j++]; Object syncService = syncConfigurationOptions[j++]; // Convert the headers into a String array to make it easier to send through JNI @@ -239,20 +235,6 @@ private OsRealmConfig(final RealmConfiguration config, } } - // TODO Simplify. org.bson serialization only allows writing full documents, so the partition - // key is embedded in a document with key 'value' and unwrapped in JNI. - String encodedPartitionValue; - switch (partitionValue.getBsonType()) { - case STRING: - case OBJECT_ID: - case INT32: - case INT64: - encodedPartitionValue = JniBsonProtocol.encode(partitionValue, RealmAppConfiguration.DEFAULT_BSON_CODEC_REGISTRY); - break; - default: - throw new IllegalArgumentException("Unsupported type: " + partitionValue); - } - // Set encryption key byte[] key = config.getEncryptionKey(); if (key != null) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index fb894b5219..5b8c1e85f5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -21,12 +21,15 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; +import org.bson.BsonValue; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.TimeUnit; import io.realm.RealmApp; +import io.realm.RealmAppConfiguration; import io.realm.RealmConfiguration; import io.realm.RealmSync; import io.realm.RealmUser; @@ -34,6 +37,7 @@ import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.objectstore.OsAsyncOpenTask; @@ -85,6 +89,21 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { String customAuthorizationHeaderName = app.getConfiguration().getAuthorizationHeaderName(); Map customHeaders = app.getConfiguration().getCustomRequestHeaders(); + // TODO Simplify. org.bson serialization only allows writing full documents, so the partition + // key is embedded in a document with key 'value' and unwrapped in JNI. + BsonValue partitionValue = syncConfig.getPartitionValue(); + String encodedPartitionValue; + switch (partitionValue.getBsonType()) { + case STRING: + case OBJECT_ID: + case INT32: + case INT64: + encodedPartitionValue = JniBsonProtocol.encode(partitionValue, RealmAppConfiguration.DEFAULT_BSON_CODEC_REGISTRY); + break; + default: + throw new IllegalArgumentException("Unsupported type: " + partitionValue); + } + int i = 0; Object[] configObj = new Object[SYNC_CONFIG_OPTIONS]; configObj[i++] = rosUserIdentity; @@ -98,7 +117,7 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { configObj[i++] = customAuthorizationHeaderName; configObj[i++] = customHeaders; configObj[i++] = OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL; - configObj[i++] = syncConfig.getPartitionValue(); + configObj[i++] = encodedPartitionValue; configObj[i++] = app.getSync(); return configObj; } else { From 1a1fe407cadce4a499b805f96f48d8206ef5fabe Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 29 May 2020 17:38:44 +0200 Subject: [PATCH 1549/2110] Update to Sync-alpha.15 (#6886) --- dependencies.list | 6 ++-- .../io/realm/internal/OsSharedRealmTests.java | 2 +- .../src/main/cpp/io_realm_RealmApp.cpp | 4 +-- .../main/cpp/io_realm_internal_OsObject.cpp | 12 ++++---- .../io_realm_internal_OsObjectSchemaInfo.cpp | 2 +- .../main/cpp/io_realm_internal_OsResults.cpp | 6 ++-- .../cpp/io_realm_internal_OsSharedRealm.cpp | 10 +++---- ...m_internal_objectstore_OsObjectBuilder.cpp | 2 +- .../cpp/io_realm_mongodb_FunctionsImpl.cpp | 6 ++-- .../src/main/cpp/java_accessor.hpp | 28 +++++++++---------- .../src/main/cpp/java_network_transport.hpp | 8 +++--- realm/realm-library/src/main/cpp/object-store | 2 +- 12 files changed, 44 insertions(+), 44 deletions(-) diff --git a/dependencies.list b/dependencies.list index c42be704dc..c87abde02f 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.14 -REALM_SYNC_SHA256=1d1e9478210f7b26cea882f8e8c8fd5968b7938415ef1120485671a1a4ed2e67 +REALM_SYNC_VERSION=10.0.0-alpha.15 +REALM_SYNC_SHA256=f7a21bd6945ee5623a0cf93e9d044da28c5b4c76b2e0efa3a12a0d51637a6dcc # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. @@ -9,7 +9,7 @@ REALM_OBJECT_SERVER_VERSION=3.28.2 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-05-22 +MONGODB_REALM_SERVER_VERSION=2020-05-29 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java index e5c5b7cbaa..2144a4c814 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsSharedRealmTests.java @@ -196,7 +196,7 @@ public void onSchemaChanged() { // Refresh existing instance sharedRealm.refresh(); assertTrue(sharedRealm.hasTable("NewTable")); - assertFalse(listenerCalled.get()); // TODO: Change to assertTrue once bug is fixed + assertTrue(listenerCalled.get()); } @Test diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp index d50d9c2619..8eeb39e12e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp @@ -58,7 +58,7 @@ struct AndroidClientListener : public realm::BindingCallbackThreadObserver { void handle_error(std::exception const& e) override { JNIEnv* env = JniUtils::get_env(true); - std::string msg = format("An exception has been thrown on the sync client thread:\n%1", e.what()); + std::string msg = util::format("An exception has been thrown on the sync client thread:\n%1", e.what()); Log::f(msg.c_str()); // Since user has no way to handle exceptions thrown on the sync client thread, we just convert it to a Java // exception to get more debug information for ourself. @@ -75,7 +75,7 @@ struct AndroidClientListener : public realm::BindingCallbackThreadObserver { struct AndroidSyncLoggerFactory : public realm::SyncLoggerFactory { // The level param is ignored. Use the global RealmLog.setLevel() to control all log levels. - std::unique_ptr make_logger(Logger::Level) override + std::unique_ptr make_logger(util::Logger::Level) override { auto logger = std::make_unique(std::string("REALM_SYNC")); // Cast to std::unique_ptr diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 84080d0697..5bfcdf1439 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -175,12 +175,12 @@ static inline Obj do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm if (is_pk_null) { if (bool(table->find_first_null(col_key))) { - THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, "'null'")); + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, util::format(PK_EXCEPTION_MSG_FORMAT, "'null'")); } } else { if (bool(table->find_first_int(col_key, pk_value))) { - THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, pk_value)); + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, util::format(PK_EXCEPTION_MSG_FORMAT, pk_value)); } } @@ -203,12 +203,12 @@ static inline Obj do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm if (pk_value) { if (bool(table->find_first_string(col_key, str_accessor))) { THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, - format(PK_EXCEPTION_MSG_FORMAT, str_accessor.operator std::string())); + util::format(PK_EXCEPTION_MSG_FORMAT, str_accessor.operator std::string())); } } else { if (bool(table->find_first_null(col_key))) { - THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, "'null'")); + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, util::format(PK_EXCEPTION_MSG_FORMAT, "'null'")); } } return table->create_object_with_primary_key(StringData(str_accessor)); @@ -230,14 +230,14 @@ static inline Obj do_create_row_with_object_id_primary_key(JNIEnv* env, jlong sh auto objectId = ObjectId(StringData(str_accessor).data()); if (bool(table->find_first_object_id(col_key, objectId))) { THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, - format(PK_EXCEPTION_MSG_FORMAT, str_accessor.operator std::string())); + util::format(PK_EXCEPTION_MSG_FORMAT, str_accessor.operator std::string())); } return table->create_object_with_primary_key(objectId); } else { if (bool(table->find_first_null(col_key))) { - THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, format(PK_EXCEPTION_MSG_FORMAT, "'null'")); + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, util::format(PK_EXCEPTION_MSG_FORMAT, "'null'")); } return table->create_object_with_primary_key(realm::util::Optional()); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp index b5ea26d925..8e7841e674 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp @@ -110,7 +110,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrope return reinterpret_cast(new Property(*property)); } THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalState, - format("Property '%1' cannot be found.", property_name.data())); + util::format("Property '%1' cannot be found.", property_name.data())); } CATCH_STD() return reinterpret_cast(nullptr); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index cb62c52fc1..8873d832e3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -152,7 +152,7 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_OsResults_nativeAggregate(JNIEn auto wrapper = reinterpret_cast(native_ptr); ColKey col_key(column_key); - Optional value; + util::Optional value; switch (agg_func) { case io_realm_internal_OsResults_AGGREGATE_FUNCTION_MINIMUM: value = wrapper->collection().min(col_key); @@ -161,12 +161,12 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_OsResults_nativeAggregate(JNIEn value = wrapper->collection().max(col_key); break; case io_realm_internal_OsResults_AGGREGATE_FUNCTION_AVERAGE: { - Optional value_count(wrapper->collection().average(col_key)); + util::Optional value_count(wrapper->collection().average(col_key)); if (value_count) { value = value_count; } else { - value = Optional(0.0); + value = util::Optional(0.0); } break; } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index a51b69d79b..d0e0cac1a0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -230,7 +230,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTableRef(J name_str = name_str.substr(TABLE_PREFIX.length()); } THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, - format("The class '%1' doesn't exist in this Realm.", name_str)); + util::format("The class '%1' doesn't exist in this Realm.", name_str)); } TableRef* tableRef = new TableRef(group.get_table(name)); @@ -256,7 +256,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTable(J // Sync doesn't throw when table exists. if (group.has_table(table_name)) { THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, - format(c_table_name_exists_exception_msg, table_name.substr(TABLE_PREFIX.length()))); + util::format(c_table_name_exists_exception_msg, table_name.substr(TABLE_PREFIX.length()))); } table = sync::create_table(static_cast(group), table_name); // throws #else @@ -267,7 +267,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTable(J catch (TableNameInUse& e) { // We need to print the table name, so catch the exception here. std::string class_name_str(table_name.substr(TABLE_PREFIX.length())); - ThrowException(env, IllegalArgument, format(c_table_name_exists_exception_msg, class_name_str)); + ThrowException(env, IllegalArgument, util::format(c_table_name_exists_exception_msg, class_name_str)); } CATCH_STD() @@ -292,7 +292,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi // Sync doesn't throw when table exists. if (group.has_table(table_name)) { THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument, - format(c_table_name_exists_exception_msg, class_name_str)); + util::format(c_table_name_exists_exception_msg, class_name_str)); } table = sync::create_table_with_primary_key(static_cast(group), table_name, pkType, field_name, is_nullable); @@ -304,7 +304,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi } catch (TableNameInUse& e) { // We need to print the table name, so catch the exception here. - ThrowException(env, IllegalArgument, format(c_table_name_exists_exception_msg, class_name_str)); + ThrowException(env, IllegalArgument, util::format(c_table_name_exists_exception_msg, class_name_str)); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp index e34fb401e9..2427b6c3f3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp @@ -171,7 +171,7 @@ static inline const ObjectSchema& get_schema(const Schema& schema, TableRef tabl std::string class_name = std::string(table_name.substr(TABLE_PREFIX.length())); auto it = schema.find(class_name); if (it == schema.end()) { - throw std::runtime_error(format("Class '%1' cannot be found in the schema.", class_name.data())); + throw std::runtime_error(util::format("Class '%1' cannot be found in the schema.", class_name.data())); } return *it; } diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp index 2830ff8338..31effdfac3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp @@ -27,7 +27,7 @@ using namespace realm::app; using namespace realm::bson; using namespace realm::jni_util; -static std::function )> success_mapper = [](JNIEnv* env, Optional response) { +static std::function )> success_mapper = [](JNIEnv* env, util::Optional response) { if (response) { return JniBsonProtocol::bson_to_jstring(env, *response); } else { @@ -44,9 +44,9 @@ Java_io_realm_FunctionsImpl_nativeCallFunction(JNIEnv* env, jclass , jlong j_app auto app = *reinterpret_cast*>(j_app_ptr); auto user = *reinterpret_cast*>(j_user_ptr); - std::function, Optional)> callback = JavaNetworkTransport::create_result_callback(env, j_callback, success_mapper); + std::function, util::Optional)> callback = JavaNetworkTransport::create_result_callback(env, j_callback, success_mapper); - auto handler = [callback](Optional error, Optional response) { + auto handler = [callback](util::Optional error, util::Optional response) { callback(response, error); }; diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index 4ceb2dd7e3..83eed70bd7 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -262,7 +262,7 @@ class JavaAccessorContext { template T unbox(util::Any& v, CreatePolicy = CreatePolicy::Skip, ObjKey /*current_row*/ = ObjKey()) const { - return any_cast(v); + return util::any_cast(v); } private: @@ -363,28 +363,28 @@ template <> inline bool JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Boolean"); - return any_cast(v) == JNI_TRUE; + return util::any_cast(v) == JNI_TRUE; } template <> inline int64_t JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Long"); - return static_cast(any_cast(v)); + return static_cast(util::any_cast(v)); } template <> inline double JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Double"); - return static_cast(any_cast(v)); + return static_cast(util::any_cast(v)); } template <> inline float JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { check_value_not_null(v, "Float"); - return static_cast(any_cast(v)); + return static_cast(util::any_cast(v)); } template <> @@ -393,7 +393,7 @@ inline StringData JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) if (!v.has_value()) { return StringData(); } - auto& value = any_cast(v); + auto& value = util::any_cast(v); return value; } @@ -402,26 +402,26 @@ inline BinaryData JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) { if (!v.has_value()) return BinaryData(); - auto& value = any_cast(v); + auto& value = util::any_cast(v); return value.transform(); } template <> inline Timestamp JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { - return v.has_value() ? from_milliseconds(any_cast(v)) : Timestamp(); + return v.has_value() ? from_milliseconds(util::any_cast(v)) : Timestamp(); } template <> inline Decimal128 JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { - return v.has_value() ? any_cast(v) : Decimal128(realm::null()); + return v.has_value() ? util::any_cast(v) : Decimal128(realm::null()); } template <> inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { - return v.has_value() ? util::make_optional(any_cast(v)) : util::none; + return v.has_value() ? util::make_optional(util::any_cast(v)) : util::none; } template <> @@ -433,25 +433,25 @@ inline Obj JavaAccessorContext::unbox(util::Any&, CreatePolicy, ObjKey) const template <> inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { - return v.has_value() ? util::make_optional(any_cast(v) == JNI_TRUE) : util::none; + return v.has_value() ? util::make_optional(util::any_cast(v) == JNI_TRUE) : util::none; } template <> inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { - return v.has_value() ? util::make_optional(static_cast(any_cast(v))) : util::none; + return v.has_value() ? util::make_optional(static_cast(util::any_cast(v))) : util::none; } template <> inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { - return v.has_value() ? util::make_optional(any_cast(v)) : util::none; + return v.has_value() ? util::make_optional(util::any_cast(v)) : util::none; } template <> inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const { - return v.has_value() ? util::make_optional(any_cast(v)) : util::none; + return v.has_value() ? util::make_optional(util::any_cast(v)) : util::none; } template <> diff --git a/realm/realm-library/src/main/cpp/java_network_transport.hpp b/realm/realm-library/src/main/cpp/java_network_transport.hpp index 2fe55a796d..7a0f015b42 100644 --- a/realm/realm-library/src/main/cpp/java_network_transport.hpp +++ b/realm/realm-library/src/main/cpp/java_network_transport.hpp @@ -107,9 +107,9 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { // Helper method for constructing callbacks for REST calls that must return an actual result to Java template - static std::function)> create_result_callback(JNIEnv* env, jobject j_callback, const std::function& success_mapper) { + static std::function)> create_result_callback(JNIEnv* env, jobject j_callback, const std::function& success_mapper) { jobject callback = env->NewGlobalRef(j_callback); - return [callback, success_mapper](T result, Optional error) { + return [callback, success_mapper](T result, util::Optional error) { JNIEnv* env = JniUtils::get_env(true); static JavaClass java_callback_class(env, "io/realm/internal/jni/OsJNIResultCallback"); @@ -133,9 +133,9 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { } // Helper method for constructing callbacks for REST calls that doesn't return any results to Java. - static std::function)> create_void_callback(JNIEnv* env, jobject j_callback) { + static std::function)> create_void_callback(JNIEnv* env, jobject j_callback) { jobject callback = env->NewGlobalRef(j_callback); - return [callback](Optional error) { + return [callback](util::Optional error) { JNIEnv* env = JniUtils::get_env(true); static JavaClass java_callback_class(env, "io/realm/internal/jni/OsJNIVoidResultCallback"); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index dee13524d2..8ca4a6b01d 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit dee13524d2863402238bdacbb0beb99ef74d970b +Subproject commit 8ca4a6b01dbaf0df5179378dc2421af47653f330 From 38bdee258a725ef0dc1f13df69374b699d76963f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Sat, 30 May 2020 11:26:53 +0200 Subject: [PATCH 1550/2110] Migrate and reenable SyncedRealmTests (#6859) --- .../java/io/realm/SyncedRealmTests.java | 166 ------------ .../kotlin/io/realm/KotlinSyncedRealmTests.kt | 195 -------------- .../kotlin/io/realm/SyncedRealmTests.kt | 246 ++++++++++++++++++ 3 files changed, 246 insertions(+), 361 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java delete mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmTests.kt diff --git a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java b/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java deleted file mode 100644 index 8499a47f07..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/java/io/realm/SyncedRealmTests.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * 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. - */ -package io.realm; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -import java.io.File; -import java.io.IOException; - -import io.realm.entities.AllJavaTypes; -import io.realm.entities.AllTypes; -import io.realm.rule.RunInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/** - * Testing sync specific methods on {@link Realm}. - */ -@Ignore("FIXME: RealmApp refactor") -@RunWith(AndroidJUnit4.class) -public class SyncedRealmTests { - - @Rule - public final TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - - @Rule - public final RunInLooperThread looperThread = new RunInLooperThread(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - private Realm realm; - private RealmApp app; - - @Before - public void setUp() { - app = new TestRealmApp(); - } - - @After - public void tearDown() { - if (realm != null && !realm.isClosed()) { - realm.close(); - } - if (app != null) { - RealmAppExtKt.close(app); - } - } - - private Realm getNormalRealm() { - RealmConfiguration config = configFactory.createConfiguration(); - realm = Realm.getInstance(config); - return realm; - } - - private Realm getFullySyncRealm() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)) - .build(); - realm = Realm.getInstance(config); - return realm; - } - - // Test for https://github.com/realm/realm-java/issues/6619 - @Test - @Ignore("Going to be removed anyway") - public void testUpgradingOptionalSubscriptionFields() throws IOException { - RealmUser user = SyncTestUtils.createTestUser(app); - - // Put an older Realm at the location where Realm would otherwise create a new empty one. - // This way, Realm will upgrade this file instead. - // We don't need to synchronize data with the server, so any errors due to missing - // server side files are ignored. - // The file was created using Realm Java 5.10.0 - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(user).build(); - File realmDir = config.getRealmDirectory(); - File oldRealmFile = new File(realmDir, "optionalsubscriptionfields"); - assertFalse(oldRealmFile.exists()); - configFactory.copyFileFromAssets(InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(), "optionalsubscriptionfields.realm", oldRealmFile); - assertTrue(oldRealmFile.exists()); - - try { - // Opening the Realm should not throw a schema mismatch - realm = Realm.getInstance(config); - - // Verify that createdAt/updatedAt are still optional even though the Java model class - // says they should be required. - assertTrue(realm.getSchema().get("__ResultSets").isNullable("created_at")); - assertTrue(realm.getSchema().get("__ResultSets").isNullable("updated_at")); - } catch (Exception e) { - fail(e.toString()); - } - } - - @Test - public void compactRealm_populatedRealm() { - SyncConfiguration config = configFactory.createSyncConfigurationBuilder(SyncTestUtils.createTestUser(app)).build(); - realm = Realm.getInstance(config); - realm.executeTransaction(r -> { - for (int i = 0; i < 10; i++) { - r.insert(new AllJavaTypes(i)); - } - }); - realm.close(); - assertTrue(Realm.compactRealm(config)); - realm = Realm.getInstance(config); - assertEquals(10, realm.where(AllJavaTypes.class).count()); - } - - @Test - public void compactOnLaunch_shouldCompact() throws IOException { - RealmUser user = SyncTestUtils.createTestUser(app); - - // Fill Realm with data and record size - SyncConfiguration config1 = configFactory.createSyncConfigurationBuilder(user).build(); - realm = Realm.getInstance(config1); - byte[] oneMBData = new byte[1024 * 1024]; - realm.beginTransaction(); - for (int i = 0; i < 10; i++) { - realm.createObject(AllTypes.class).setColumnBinary(oneMBData); - } - realm.commitTransaction(); - realm.close(); - long originalSize = new File(realm.getPath()).length(); - - // Open Realm with CompactOnLaunch - SyncConfiguration config2 = configFactory.createSyncConfigurationBuilder(user) - .compactOnLaunch(new CompactOnLaunchCallback() { - @Override - public boolean shouldCompact(long totalBytes, long usedBytes) { - return true; - } - }) - .build(); - realm = Realm.getInstance(config2); - realm.close(); - long compactedSize = new File(realm.getPath()).length(); - - assertTrue(originalSize > compactedSize); - } - -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt deleted file mode 100644 index 79f5a35141..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/KotlinSyncedRealmTests.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ -package io.realm - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import io.realm.entities.* -import io.realm.kotlin.syncSession -import io.realm.kotlin.where -import io.realm.log.LogLevel -import io.realm.log.RealmLog -import org.junit.After -import org.junit.Assert.* -import org.junit.Before -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import java.util.* - -@RunWith(AndroidJUnit4::class) -class KotlinSyncedRealmTests { // FIXME: Rename to SyncedRealmTests once remaining Java tests have been moved - - private lateinit var app: TestRealmApp - private lateinit var realm: Realm - private lateinit var partitionValue: String - - @Before - fun setUp() { - Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - RealmLog.setLevel(LogLevel.TRACE) - app = TestRealmApp() - partitionValue = UUID.randomUUID().toString() - } - - @After - fun tearDown() { - if (this::realm.isInitialized) { - realm.close() - } - if (this::app.isInitialized) { - app.close() - } - RealmLog.setLevel(LogLevel.WARN) - } - - // Smoke test for Sync. Waiting for working Sync support. - @Test - fun connectWithInitialSchema() { - val user: RealmUser = createNewUser() - val config = createDefaultConfig(user) - realm = Realm.getInstance(config) - app.syncManager.getSession(config).uploadAllLocalChanges() - app.syncManager.getSession(config).downloadAllServerChanges() - assertTrue(realm.isEmpty) - } - - // Smoke test for Sync - @Test - fun roundTripObjectsNotInServerSchemaObject() { - // User 1 creates an object an uploads it to MongoDB Realm - val user1: RealmUser = createNewUser() - val config1: SyncConfiguration = createCustomConfig(user1, partitionValue) - realm = Realm.getInstance(config1) - realm.executeTransaction { - for (i in 1..10) { - it.insert(SyncColor()) - } - } - app.syncManager.getSession(config1).uploadAllLocalChanges() - assertEquals(10, realm.where().count()) - realm.close() - - // User 2 logs and using the same partition key should see the object - val user2: RealmUser = createNewUser() - val config2 = createCustomConfig(user2, partitionValue) - realm = Realm.getInstance(config2) - realm.syncSession.downloadAllServerChanges() - realm.refresh() - assertEquals(10, realm.where().count()) - } - - // Smoke test for sync - // Insert different types with no links between them - @Test - fun roundTripSimpleObjectsInServerSchema() { - // User 1 creates an object an uploads it to MongoDB Realm - val user1: RealmUser = createNewUser() - val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) - realm = Realm.getInstance(config1) - realm.executeTransaction { - val person = SyncPerson() - person.firstName = "Jane" - person.lastName = "Doe" - person.age = 42 - realm.insert(person); - for (i in 0..9) { - val dog = SyncDog() - dog.name = "Fido $i" - it.insert(dog) - } - } - realm.syncSession.uploadAllLocalChanges() - assertEquals(10, realm.where().count()) - assertEquals(1, realm.where().count()) - realm.close() - - // User 2 logs and using the same partition key should see the object - val user2: RealmUser = createNewUser() - val config2 = createDefaultConfig(user2, partitionValue) - realm = Realm.getInstance(config2) - realm.syncSession.downloadAllServerChanges() - realm.refresh() - assertEquals(10, realm.where().count()) - assertEquals(1, realm.where().count()) - } - - - // Smoke test for sync - // Insert objects with links between them - @Ignore("Crashes server currently") - @Test - fun roundTripObjectsWithLists() { - // User 1 creates an object an uploads it to MongoDB Realm - val user1: RealmUser = createNewUser() - val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) - realm = Realm.getInstance(config1) - realm.executeTransaction { - val person = SyncPerson() - person.firstName = "Jane" - person.lastName = "Doe" - person.age = 42 - for (i in 0..9) { - val dog = SyncDog() - dog.name = "Fido $i" - it.insert(dog) - person.dogs.add(dog) - } - realm.insert(person) - } - realm.syncSession.uploadAllLocalChanges() - assertEquals(10, realm.where().count()) - assertEquals(1, realm.where().count()) - realm.close() - - // User 2 logs and using the same partition key should see the object - val user2: RealmUser = createNewUser() - val config2 = createDefaultConfig(user2, partitionValue) - realm = Realm.getInstance(config2) - realm.syncSession.downloadAllServerChanges() - realm.refresh() - assertEquals(10, realm.where().count()) - assertEquals(1, realm.where().count()) - } - - @Test - fun session() { - val user: RealmUser = app.login(RealmCredentials.anonymous()) - realm = Realm.getInstance(createDefaultConfig(user)) - assertNotNull(realm.syncSession) - assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) - assertEquals(user, realm.syncSession.user) - } - - private fun createDefaultConfig(user: RealmUser, partitionValue: String = defaultPartitionValue): SyncConfiguration { - return SyncConfiguration.Builder(user, partitionValue) - .modules(DefaultSyncSchema()) - .build() - } - - private fun createCustomConfig(user: RealmUser, partitionValue: String = defaultPartitionValue): SyncConfiguration { - return SyncConfiguration.Builder(user, partitionValue) - .schema(SyncColor::class.java) - .build() - } - - private fun createNewUser(): RealmUser { - val email = TestHelper.getRandomEmail() - val password = "123456" - app.emailPasswordAuth.registerUser(email, password) - return app.login(RealmCredentials.emailPassword(email, password)) - } -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmTests.kt new file mode 100644 index 0000000000..8daa69c7b5 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmTests.kt @@ -0,0 +1,246 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.SyncTestUtils.Companion.createTestUser +import io.realm.entities.* +import io.realm.kotlin.syncSession +import io.realm.kotlin.where +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import org.junit.* +import org.junit.Assert.* +import org.junit.runner.RunWith +import java.io.File +import java.util.* + +/** + * Testing sync specific methods on [Realm]. + */ +@RunWith(AndroidJUnit4::class) +class SyncedRealmTests { + + @get:Rule + val configFactory = TestSyncConfigurationFactory() + + private lateinit var app: RealmApp + private lateinit var partitionValue: String + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + RealmLog.setLevel(LogLevel.TRACE) + app = TestRealmApp() + partitionValue = UUID.randomUUID().toString() + } + + @After + fun tearDown() { + if (this::app.isInitialized) { + app.close() + } + RealmLog.setLevel(LogLevel.WARN) + } + + // Smoke test for Sync. Waiting for working Sync support. + @Test + fun connectWithInitialSchema() { + val user: RealmUser = createNewUser() + val config = createDefaultConfig(user) + Realm.getInstance(config).use { realm -> + with(realm.syncSession) { + uploadAllLocalChanges() + downloadAllServerChanges() + } + assertTrue(realm.isEmpty) + } + } + + // Smoke test for Sync + @Test + fun roundTripObjectsNotInServerSchemaObject() { + // User 1 creates an object an uploads it to MongoDB Realm + val user1: RealmUser = createNewUser() + val config1: SyncConfiguration = createCustomConfig(user1, partitionValue) + Realm.getInstance(config1).use { realm -> + realm.executeTransaction { + for (i in 1..10) { + it.insert(SyncColor()) + } + } + realm.syncSession.uploadAllLocalChanges() + assertEquals(10, realm.where().count()) + } + + // User 2 logs and using the same partition key should see the object + val user2: RealmUser = createNewUser() + val config2 = createCustomConfig(user2, partitionValue) + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges() + realm.refresh() + assertEquals(10, realm.where().count()) + } + } + + // Smoke test for sync + // Insert different types with no links between them + @Test + fun roundTripSimpleObjectsInServerSchema() { + // User 1 creates an object an uploads it to MongoDB Realm + val user1: RealmUser = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + Realm.getInstance(config1).use { realm -> + realm.executeTransaction { + val person = SyncPerson() + person.firstName = "Jane" + person.lastName = "Doe" + person.age = 42 + realm.insert(person); + for (i in 0..9) { + val dog = SyncDog() + dog.name = "Fido $i" + it.insert(dog) + } + } + realm.syncSession.uploadAllLocalChanges() + assertEquals(10, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + // User 2 logs and using the same partition key should see the object + val user2: RealmUser = createNewUser() + val config2 = createDefaultConfig(user2, partitionValue) + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges() + realm.refresh() + assertEquals(10, realm.where().count()) + assertEquals(1, realm.where().count()) + } + } + + // Smoke test for sync + // Insert objects with links between them + @Test + fun roundTripObjectsWithLists() { + // User 1 creates an object an uploads it to MongoDB Realm + val user1: RealmUser = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + Realm.getInstance(config1).use { realm -> + realm.executeTransaction { + val person = SyncPerson() + person.firstName = "Jane" + person.lastName = "Doe" + person.age = 42 + for (i in 0..9) { + val dog = SyncDog() + dog.name = "Fido $i" + person.dogs.add(dog) + } + realm.insert(person) + } + realm.syncSession.uploadAllLocalChanges() + assertEquals(10, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + // User 2 logs and using the same partition key should see the object + val user2: RealmUser = createNewUser() + val config2 = createDefaultConfig(user2, partitionValue) + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges() + realm.refresh() + assertEquals(10, realm.where().count()) + assertEquals(1, realm.where().count()) + } + } + + @Test + fun session() { + val user: RealmUser = app.login(RealmCredentials.anonymous()) + Realm.getInstance(createDefaultConfig(user)).use { realm -> + assertNotNull(realm.syncSession) + assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) + assertEquals(user, realm.syncSession.user) + } + } + + @Test + @Ignore("FIXME Flaky, seems like Realm.compactRealm(config) sometimes returns false") + fun compactRealm_populatedRealm() { + val config = configFactory.createSyncConfigurationBuilder(createNewUser()).build() + Realm.getInstance(config).use { realm -> + realm.executeTransaction { r: Realm -> + for (i in 0..9) { + r.insert(AllJavaTypes(i.toLong())) + } + } + } + assertTrue(Realm.compactRealm(config)) + + Realm.getInstance(config).use { realm -> + assertEquals(10, realm.where(AllJavaTypes::class.java).count()) + } + } + + @Test + fun compactOnLaunch_shouldCompact() { + val user = createTestUser(app) + + // Fill Realm with data and record size + val config1 = configFactory.createSyncConfigurationBuilder(user).build() + var originalSize : Long? = null + Realm.getInstance(config1).use { realm -> + val oneMBData = ByteArray(1024 * 1024) + realm.executeTransaction { + for (i in 0..9) { + realm.createObject(AllTypes::class.java).columnBinary = oneMBData + } + } + originalSize = File(realm.path).length() + } + + // Open Realm with CompactOnLaunch + val config2 = configFactory.createSyncConfigurationBuilder(user) + .compactOnLaunch { totalBytes, usedBytes -> true } + .build() + Realm.getInstance(config2).use { realm -> + val compactedSize = File(realm.path).length() + assertTrue(originalSize!! > compactedSize) + } + } + + private fun createDefaultConfig(user: RealmUser, partitionValue: String = defaultPartitionValue): SyncConfiguration { + return SyncConfiguration.Builder(user, partitionValue) + .modules(DefaultSyncSchema()) + .build() + } + + private fun createCustomConfig(user: RealmUser, partitionValue: String = defaultPartitionValue): SyncConfiguration { + return SyncConfiguration.Builder(user, partitionValue) + .schema(SyncColor::class.java) + .build() + } + + private fun createNewUser(): RealmUser { + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuth.registerUser(email, password) + return app.login(RealmCredentials.emailPassword(email, password)) + } + +} From 8c1aa7f562f4d12dce501c54d8a15ee5e75f6a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Sun, 31 May 2020 00:10:54 +0200 Subject: [PATCH 1551/2110] Fix SyncConfigruation equals --- .../src/objectServer/java/io/realm/SyncConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java index 9b0746c07e..c273d81a30 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java @@ -280,7 +280,7 @@ public boolean equals(@Nullable Object o) { if (syncUrlPrefix != null ? !syncUrlPrefix.equals(that.syncUrlPrefix) : that.syncUrlPrefix != null) return false; if (clientResyncMode != that.clientResyncMode) return false; - return partitionValue == that.partitionValue; + return partitionValue.equals(that.partitionValue); } @Override From d4d797efb0d4e4ac1b1970bcb6c5d6eac213bd37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 2 Jun 2020 10:31:42 +0200 Subject: [PATCH 1552/2110] Add custom decoder variant of callFunctions (#6874) --- .../realm/mongodb/functions/FunctionsTests.kt | 147 +++++++++++++----- .../objectServer/java/io/realm/ErrorCode.java | 5 + .../java/io/realm/FunctionsImpl.java | 25 ++- .../io/realm/mongodb/functions/Functions.java | 94 +++++++---- 4 files changed, 203 insertions(+), 68 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt index cc55d2e776..00f6da18b2 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt @@ -23,10 +23,7 @@ import io.realm.admin.ServerAdmin import io.realm.rule.BlockingLooperThread import io.realm.util.assertFailsWithErrorCode import org.bson.* -import org.bson.codecs.Codec -import org.bson.codecs.DecoderContext -import org.bson.codecs.EncoderContext -import org.bson.codecs.StringCodec +import org.bson.codecs.* import org.bson.codecs.configuration.CodecConfigurationException import org.bson.codecs.configuration.CodecProvider import org.bson.codecs.configuration.CodecRegistries @@ -40,6 +37,7 @@ import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import java.lang.RuntimeException import java.util.* import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -54,7 +52,7 @@ class FunctionsTests { } // Pojo class for testing custom encoder/decoder - data class Dog(var name: String? = null) + private data class Dog(var name: String? = null) private val looperThread = BlockingLooperThread() @@ -65,7 +63,7 @@ class FunctionsTests { private lateinit var admin: ServerAdmin // Custom registry with support for encoding/decoding Dogs - val pojoRegistry by lazy { + private val pojoRegistry by lazy { CodecRegistries.fromRegistries( app.configuration.defaultCodecRegistry, CodecRegistries.fromProviders( @@ -76,6 +74,35 @@ class FunctionsTests { ) } + // Custom string decoder returning hardcoded value + private class CustomStringDecoder(val value: String) : Decoder { + override fun decode(reader: BsonReader, decoderContext: DecoderContext): String { + reader.readString() + return value + } + } + + // Custom codec that throws an exception when encoding/decoding integers + private val faultyIntegerCodec = object : Codec { + override fun decode(reader: BsonReader, decoderContext: DecoderContext): Integer { + throw RuntimeException("Simulated error") + } + + override fun getEncoderClass(): Class { + return Integer::class.java + } + + override fun encode(writer: BsonWriter?, value: Integer?, encoderContext: EncoderContext?) { + throw RuntimeException("Simulated error") + } + } + + // Custom registry that throws an exception when encoding/decoding integers + private val faultyIntegerRegistry = CodecRegistries.fromRegistries( + CodecRegistries.fromProviders(IterableCodecProvider()), + CodecRegistries.fromCodecs(StringCodec(), faultyIntegerCodec) + ) + @Before fun setup() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) @@ -120,18 +147,12 @@ class FunctionsTests { val values3 = listOf(2, "Realm", 3) assertEquals(values3, functions.callFunction(FIRST_ARG_FUNCTION, listOf(values3), List::class.java)) } - // FIXME Does not seem to work, typically this has indicated an issue with C++ - // parser. Probably because of embedding an array in an array, added explicit test -// BsonType.BINARY -> { -// val value = byteArrayOf(1, 2, 3) -// val actual = functions.callFunction(FIRST_ARG_FUNCTION, listOf(value), ByteArray::class.java) -// assertEquals(value.toList(), actual.toList()) -// // FIXME C++ Does not seem to preserve subtype -// // arg = "{"value": {"$binary": {"base64": "JmS8oQitTny4IPS2tyjmdA==", "subType": "04"}}}" -// // response = "{"value":{"$binary":{"base64":"JmS8oQitTny4IPS2tyjmdA==","subType":"00"}}}" -// // assertTypedEcho(BsonBinary(UUID.randomUUID()), BsonBinary::class.java) -// assertTypedEcho(BsonBinary(byteArrayOf(1,2,3)), BsonBinary::class.java) -// } + BsonType.BINARY -> { + val value = byteArrayOf(1, 2, 3) + val actual = functions.callFunction(FIRST_ARG_FUNCTION, listOf(value), ByteArray::class.java) + assertEquals(value.toList(), actual.toList()) + assertTypeOfFirstArgFunction(BsonBinary(byteArrayOf(1, 2, 3)), BsonBinary::class.java) + } BsonType.OBJECT_ID -> { assertTypeOfFirstArgFunction(ObjectId(), ObjectId::class.java) assertTypeOfFirstArgFunction(BsonObjectId(ObjectId()), BsonObjectId::class.java) @@ -169,7 +190,8 @@ class FunctionsTests { assertEquals(documents[0], functions.callFunction(FIRST_ARG_FUNCTION, documents, Document::class.java)) } BsonType.DATE_TIME -> { - // FIXME See jniParseError_date + val now = Date(System.currentTimeMillis()) + assertEquals(now, functions.callFunction(FIRST_ARG_FUNCTION, listOf(now), Date::class.java)) } BsonType.UNDEFINED, BsonType.NULL, @@ -185,11 +207,14 @@ class FunctionsTests { // Relying on org.bson codec providers for conversion, so skipping explicit // tests for these more exotic types } + else -> { + fail() + } } } } - private fun assertTypeOfFirstArgFunction(value: T, returnClass: Class) : T { + private fun assertTypeOfFirstArgFunction(value: T, returnClass: Class): T { val actual = functions.callFunction(FIRST_ARG_FUNCTION, listOf(value), returnClass) assertEquals(value, actual) return actual @@ -209,7 +234,7 @@ class FunctionsTests { @Test fun codecArgumentFailure() { - assertFailsWith { + assertFailsWithErrorCode(ErrorCode.BSON_CODEC_NOT_FOUND) { functions.callFunction(FIRST_ARG_FUNCTION, listOf(Dog("PojoFido")), Dog::class.java) } } @@ -218,6 +243,7 @@ class FunctionsTests { fun asyncCodecArgumentFailure() = looperThread.runBlocking { functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(Dog("PojoFido")), Integer::class.java) { result -> try { + assertEquals(ErrorCode.BSON_CODEC_NOT_FOUND, result.error.errorCode) assertTrue(result.error.exception is CodecConfigurationException) } finally { looperThread.testComplete() @@ -227,7 +253,7 @@ class FunctionsTests { @Test fun codecResponseFailure() { - assertFailsWith { + assertFailsWithErrorCode(ErrorCode.BSON_CODEC_NOT_FOUND) { functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), Dog::class.java) } } @@ -236,6 +262,7 @@ class FunctionsTests { fun asyncCodecResponseFailure() = looperThread.runBlocking { functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(Dog("PojoFido")), Integer::class.java) { result -> try { + assertEquals(ErrorCode.BSON_CODEC_NOT_FOUND, result.error.errorCode) assertTrue(result.error.exception is CodecConfigurationException) } finally { looperThread.testComplete() @@ -244,16 +271,35 @@ class FunctionsTests { } @Test - fun codecBsonFailure() { - assertFailsWith { + fun codecBsonEncodingFailure() { + assertFailsWithErrorCode(ErrorCode.BSON_ENCODING) { + functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), String::class.java, faultyIntegerRegistry) + } + } + + @Test + fun asyncCodecBsonEncodingFailure() = looperThread.runBlocking { + functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(32), String::class.java, faultyIntegerRegistry) { result -> + try { + assertEquals(ErrorCode.BSON_ENCODING, result.error.errorCode) + } finally { + looperThread.testComplete() + } + } + } + + @Test + fun codecBsonDecodingFailure() { + assertFailsWithErrorCode(ErrorCode.BSON_DECODING) { functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), String::class.java) } } @Test - fun asyncCodecBsonFailure() = looperThread.runBlocking { + fun asyncCodecBsonDecodingFailure() = looperThread.runBlocking { functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(32), String::class.java) { result -> try { + assertEquals(ErrorCode.BSON_DECODING, result.error.errorCode) assertTrue(result.error.exception is BSONException) } finally { looperThread.testComplete() @@ -286,11 +332,30 @@ class FunctionsTests { assertEquals(input, functionsWithCodecRegistry.callFunction(FIRST_ARG_FUNCTION, listOf(input), Dog::class.java)) } + @Test + fun resultDecoder() { + val input = "Realm" + val output = "Custom Realm" + assertEquals(output, functions.callFunction(FIRST_ARG_FUNCTION, listOf(input), CustomStringDecoder(output))) + } + + @Test + fun asyncResultDecoder() = looperThread.runBlocking { + val input = "Realm" + val output = "Custom Realm" + functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(input), CustomStringDecoder(output), RealmApp.Callback { result -> + try { + assertEquals(output, result.orThrow) + } finally { + looperThread.testComplete() + } + }) + } @Test fun unknownFunction() { assertFailsWithErrorCode(ErrorCode.FUNCTION_NOT_FOUND) { - functions.callFunction("unknown", listOf(32), Dog::class.java) + functions.callFunction("unknown", listOf(32), String::class.java) } } @@ -355,7 +420,7 @@ class FunctionsTests { } // User email must match "canevaluate" section of servers "functions/authorizedOnly/config.json" val authorizedUser = app.registerUserAndLogin("authorizeduser@example.org", "asdfasdf") - assertNotNull(authorizedUser.functions.callFunction("authorizedOnly", listOf(1,2,3), Document::class.java)) + assertNotNull(authorizedUser.functions.callFunction("authorizedOnly", listOf(1, 2, 3), Document::class.java)) } @Test @@ -387,18 +452,23 @@ class FunctionsTests { fun illegalBsonArgument() { // Coded that will generate non-BsonArray from list val faultyListCodec = object : Codec> { - override fun getEncoderClass(): Class> { return Iterable::class.java } + override fun getEncoderClass(): Class> { + return Iterable::class.java + } + override fun encode(writer: BsonWriter, value: Iterable<*>, encoderContext: EncoderContext) { writer.writeString("Not an array") } + override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): ArrayList<*> { TODO("Not yet implemented") } } // Codec registry that will use the above faulty codec for lists val faultyCodecRegistry = CodecRegistries.fromProviders( - object: CodecProvider { + object : CodecProvider { override fun get(clazz: Class?, registry: CodecRegistry?): Codec { + @Suppress("UNCHECKED_CAST") return faultyListCodec as Codec } } @@ -408,9 +478,9 @@ class FunctionsTests { } } + // Test cases previously failing due to C++ parsing @Test - @Ignore("JNI parsing crashes tests") - fun jniParseError_arrayOfBinary() { + fun roundtrip_arrayOfBinary() { val value = byteArrayOf(1, 2, 3) val listOf = listOf(value) val actual = functions.callFunction(FIRST_ARG_FUNCTION, listOf, ByteArray::class.java) @@ -418,16 +488,17 @@ class FunctionsTests { } @Test - @Ignore("JNI parsing fails to parse into a bson array") - fun jniParseError_arrayOfDocuments() { - val map = mapOf("foo" to 5, "bar" to 7) + fun roundtrip_arrayOfDocuments() { + val map = mapOf("foo" to 5, "bar" to 7) assertEquals(map, functions.callFunction(FIRST_ARG_FUNCTION, listOf(map), Map::class.java)) } @Test - @Ignore("JNI parsing seems to truncate value to 32-bit") - fun jniParseError_date() { - val now = Date(System.currentTimeMillis()) - assertEquals(now, functions.callFunction(FIRST_ARG_FUNCTION, listOf(now), Date::class.java)) + @Ignore("C++ parser does not support binary subtypes yet") + fun roundtrip_binaryUuid() { + // arg = "{"value": {"$binary": {"base64": "JmS8oQitTny4IPS2tyjmdA==", "subType": "04"}}}" + // response = "{"value":{"$binary":{"base64":"JmS8oQitTny4IPS2tyjmdA==","subType":"00"}}}" + assertTypeOfFirstArgFunction(BsonBinary(UUID.randomUUID()), BsonBinary::class.java) } + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java index fdb4a0f19c..fbd4e14b79 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java @@ -35,10 +35,15 @@ public enum ErrorCode { // The underlying type and error code should be part of the error message UNKNOWN(Type.UNKNOWN, -1), + // Errors originating from Java // Network Transport related errors originating from Java NETWORK_IO_EXCEPTION(Type.JAVA, OsJavaNetworkTransport.ERROR_IO), NETWORK_INTERRUPTED(Type.JAVA, OsJavaNetworkTransport.ERROR_INTERRUPTED), NETWORK_UNKNOWN(Type.JAVA, OsJavaNetworkTransport.ERROR_UNKNOWN), + // BSON encoding/decoding errors originalting from java + BSON_CODEC_NOT_FOUND(Type.JAVA, 1100), + BSON_ENCODING(Type.JAVA, 1101), + BSON_DECODING(Type.JAVA, 1102), // Custom Object Store errors CLIENT_RESET(Type.PROTOCOL, 7), // Client Reset required. Don't change this value without modifying io_realm_internal_OsSharedRealm.cpp diff --git a/realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java b/realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java index fc734657fd..4cc31ff803 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java @@ -15,6 +15,10 @@ */ package io.realm; +import org.bson.BSONException; +import org.bson.BsonElement; +import org.bson.codecs.Decoder; +import org.bson.codecs.configuration.CodecConfigurationException; import org.bson.codecs.configuration.CodecRegistry; import java.util.List; @@ -43,10 +47,17 @@ class FunctionsImpl extends Functions { // Invokes actual MongoDB Realm Function in the context of the associated user/app. @Override - public T invoke(String name, List args, Class resultClass, CodecRegistry codecRegistry) { + public T invoke(String name, List args, CodecRegistry codecRegistry, Decoder resultDecoder) { Util.checkEmpty(name, "name"); - String encodedArgs = JniBsonProtocol.encode(args, codecRegistry); + String encodedArgs; + try { + encodedArgs = JniBsonProtocol.encode(args, codecRegistry); + } catch (CodecConfigurationException e) { + throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve encoder for arguments", e); + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.BSON_ENCODING, "Error encoding function arguments", e); + } // NativePO calling scheme is actually synchronous AtomicReference success = new AtomicReference<>(null); @@ -59,8 +70,14 @@ protected String mapSuccess(Object result) { }; nativeCallFunction(user.getApp().nativePtr, user.osUser.getNativePtr(), name, encodedArgs, callback); String encodedResponse = ResultHandler.handleResult(success, error); - return JniBsonProtocol.decode(encodedResponse, resultClass, codecRegistry); - } + T result; + try { + result = JniBsonProtocol.decode(encodedResponse, resultDecoder); + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.BSON_DECODING, "Error decoding function result", e); + } + return result; + } private static native void nativeCallFunction(long nativeAppPtr, long nativeUserPtr, String name, String args_json, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java index c19b5b5cea..2774b38adb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java @@ -16,17 +16,19 @@ package io.realm.mongodb.functions; +import org.bson.codecs.Decoder; +import org.bson.codecs.configuration.CodecConfigurationException; import org.bson.codecs.configuration.CodecRegistry; import java.util.List; +import io.realm.ErrorCode; import io.realm.ObjectServerError; import io.realm.RealmApp; import io.realm.RealmAppConfiguration; import io.realm.RealmAsyncTask; import io.realm.RealmUser; import io.realm.internal.Util; -import io.realm.internal.jni.JniBsonProtocol; /** * A Functions manager to call MongoDB Realm functions. @@ -62,19 +64,16 @@ protected Functions(RealmUser user, CodecRegistry codecRegistry) { * @param args Arguments to the Stitch function. * @param resultClass The type that the functions result should be converted to. * @param codecRegistry Codec registry to use for argument encoding and result decoding. - * @param The type that the response will be decoded as using the {@code codecRegistry}. + * @param The type that the response will be decoded as using the {@code codecRegistry}. * @return Result of the Stitch function. * * @throws ObjectServerError if the request failed in some way. - * @throws org.bson.codecs.configuration.CodecConfigurationException if the {@code codecRegistry} - * does not provide codecs for the argument or {@code resultClass}. - * @throws org.bson.BSONException is an error occurred during BSON processing. * * @see #callFunctionAsync(String, List, Class, CodecRegistry, RealmApp.Callback) * @see RealmAppConfiguration#getDefaultCodecRegistry() */ - public T callFunction(String name, List args, Class resultClass, CodecRegistry codecRegistry) { - return invoke(name, args, resultClass, codecRegistry); + public ResultT callFunction(String name, List args, Class resultClass, CodecRegistry codecRegistry) { + return invoke(name, args, codecRegistry, decoder(codecRegistry, resultClass)); } /** @@ -84,21 +83,38 @@ public T callFunction(String name, List args, Class resultClass, Codec * @param name Name of the Stitch function to call. * @param args Arguments to the Stitch function. * @param resultClass The type that the functions result should be converted to. - * @param The type that the response will be decoded as using the default codec registry. + * @param The type that the response will be decoded as using the default codec registry. * @return Result of the Stitch function. * * @throws ObjectServerError if the request failed in some way. - * @throws org.bson.codecs.configuration.CodecConfigurationException if the {@code codecRegistry} - * does not provide codecs for the argument or {@code resultClass}. - * @throws org.bson.BSONException is an error occurred during BSON processing. * * @see #callFunction(String, List, Class, CodecRegistry) * @see RealmAppConfiguration#getDefaultCodecRegistry() */ - public T callFunction(String name, List args, Class resultClass) { + public ResultT callFunction(String name, List args, Class resultClass) { return callFunction(name, args, resultClass, defaultCodecRegistry); } + /** + * Call a MongoDB Realm function synchronously with custom result decoder. + *

              + * The arguments will be encoded with the default codec registry encoding. + * + * @param name Name of the Stitch function to call. + * @param args Arguments to the Stitch function. + * @param resultDecoder The decoder used to decode the result. + * @param The type that the response will be decoded as using the {@code resultDecoder} + * @return Result of the Stitch function. + * + * @throws ObjectServerError if the request failed in some way. + * + * @see #callFunction(String, List, Class, CodecRegistry) + * @see RealmAppConfiguration#getDefaultCodecRegistry() + */ + public ResultT callFunction(String name, List args, Decoder resultDecoder) { + return invoke(name, args, defaultCodecRegistry, resultDecoder); + } + /** * Call a MongoDB Realm function asynchronously with custom codec registry for encoding/decoding * arguments/results. @@ -109,12 +125,7 @@ public T callFunction(String name, List args, Class resultClass) { * @param args Arguments to the Stitch function. * @param resultClass The type that the functions result should be converted to. * @param codecRegistry Codec registry to use for argument encoding and result decoding. - * @param callback The callback that will receive the result of the request. If the request - * failed in some way, the codec registry failed to provide codecs for the - * arguments or {@code resultClass}, or an error occurres during BSON processing - * the result will indicate the error as a {@link ObjectServerError}, - * {@link org.bson.codecs.configuration.CodecConfigurationException} - * or {@link ObjectServerError} respectively. + * @param callback The callback that will receive the result or any errors from the request. * @param The type that the response will be decoded as using the default codec registry. * @return Result of the Stitch function. * @@ -129,7 +140,7 @@ public RealmAsyncTask callFunctionAsync(String name, List args, Class return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { @Override public T run() throws ObjectServerError { - return callFunction(name, args, resultClass, codecRegistry); + return invoke(name, args, codecRegistry, decoder(codecRegistry, resultClass)); } }.start(); } @@ -143,12 +154,7 @@ public T run() throws ObjectServerError { * @param name Name of the Stitch function to call. * @param args Arguments to the Stitch function. * @param resultClass The type that the functions result should be converted to. - * @param callback The callback that will receive the result of the request. If the request - * failed in some way, the codec registry failed to provide codecs for the - * arguments or {@code resultClass}, or an error occurres during BSON processing - * the result will indicate the error as a {@link ObjectServerError}, - * {@link org.bson.codecs.configuration.CodecConfigurationException} - * or {@link ObjectServerError} respectively. + * @param callback The callback that will receive the result or any errors from the request. * @param The type that the response will be decoded as using the default codec registry. * @return Result of the Stitch function. * @@ -162,6 +168,34 @@ public RealmAsyncTask callFunctionAsync(String name, List args, Class return callFunctionAsync(name, args, resultClass, defaultCodecRegistry, callback); } + /** + * Call a MongoDB Realm function asynchronously with custom result decoder. + *

              + * This is the asynchronous equivalent of {@link #callFunction(String, List, Decoder)}. + * + * @param name Name of the Stitch function to call. + * @param args Arguments to the Stitch function. + * @param resultDecoder The decoder used to decode the result. + * @param callback The callback that will receive the result or any errors from the request. + * @param The type that the response will be decoded as using the {@code resultDecoder} + * @return Result of the Stitch function. + * + * @throws IllegalStateException if not called on a looper thread. + * + * @see #callFunction(String, List, Class) + * @see #callFunctionAsync(String, List, Class, CodecRegistry, RealmApp.Callback) + * @see RealmAppConfiguration#getDefaultCodecRegistry() + */ + public RealmAsyncTask callFunctionAsync(String name, List args, Decoder resultDecoder, RealmApp.Callback callback) { + Util.checkLooperThread("Asynchronous functions is only possible from looper threads."); + return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { + @Override + public T run() throws ObjectServerError { + return invoke(name, args, defaultCodecRegistry, resultDecoder); + } + }.start(); + } + /** * Returns the default codec registry used for encoding arguments and decoding results for this * Realm functions instance. @@ -190,6 +224,14 @@ public RealmUser getUser() { return user; } - protected abstract T invoke(String name, List args, Class resultClass, CodecRegistry codecRegistry); + protected abstract T invoke(String name, List args, CodecRegistry codecRegistry, Decoder resultDecoder); + + private static Decoder decoder(CodecRegistry codecRegistry, Class clz) { + try { + return codecRegistry.get(clz); + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve decoder for " + clz.getName(), e); + } + } } From ec7162dbb67e1bb6bd623576c5cb242bc7145630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 2 Jun 2020 22:33:00 +0200 Subject: [PATCH 1553/2110] Add scheme with supported types only to allow testing without bad change sets --- .../io/realm/entities/DefaultSyncSchema.kt | 2 +- .../io/realm/entities/SyncAllTypesSchema.kt | 41 +++++++ .../io/realm/entities/SyncSupportedTypes.kt | 102 +++++++++++++++++ .../kotlin/io/realm/SyncSessionTests.kt | 105 ++++++++++-------- 4 files changed, 204 insertions(+), 46 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypesSchema.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSupportedTypes.kt diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt index 3861aa01ce..b448797167 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt @@ -22,6 +22,6 @@ const val defaultPartitionValue = "default" /** * The set of classes initially supported by MongoDB Realm. */ -@RealmModule(classes = [SyncDog::class, SyncPerson::class, SyncAllTypes::class]) +@RealmModule(classes = [SyncDog::class, SyncPerson::class, SyncSupportedTypes::class]) class DefaultSyncSchema { } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypesSchema.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypesSchema.kt new file mode 100644 index 0000000000..4480d0985e --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypesSchema.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +/** + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities + +import io.realm.annotations.RealmModule + +/** + * The set of classes initially supported by MongoDB Realm. + */ +@RealmModule(classes = [SyncDog::class, SyncPerson::class, SyncAllTypes::class]) +class SyncAllTypesSchema { +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSupportedTypes.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSupportedTypes.kt new file mode 100644 index 0000000000..73bbe548e0 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSupportedTypes.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.entities + +import io.realm.MutableRealmInteger +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.TestHelper +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmField +import io.realm.annotations.Required +import org.bson.types.Decimal128 +import org.bson.types.ObjectId +import java.math.BigDecimal +import java.util.* + +open class SyncSupportedTypes : RealmObject() { + + companion object { + const val CLASS_NAME = "AllTypes" + const val FIELD_STRING = "columnString" + const val FIELD_LONG = "columnLong" + const val FIELD_FLOAT = "columnFloat" + const val FIELD_DOUBLE = "columnDouble" + const val FIELD_BOOLEAN = "columnBoolean" + const val FIELD_DATE = "columnDate" + const val FIELD_BINARY = "columnBinary" + const val FIELD_MUTABLEREALMINTEGER = "columnMutableRealmInteger" + const val FIELD_DECIMAL128 = "columnDecimal128" + const val FIELD_OBJECT_ID = "columnObjectId" + const val FIELD_REALMOBJECT = "columnRealmObject" + const val FIELD_REALMLIST = "columnRealmList" + const val FIELD_STRING_LIST = "columnStringList" + const val FIELD_BINARY_LIST = "columnBinaryList" + const val FIELD_BOOLEAN_LIST = "columnBooleanList" + const val FIELD_LONG_LIST = "columnLongList" + const val FIELD_DOUBLE_LIST = "columnDoubleList" + const val FIELD_FLOAT_LIST = "columnFloatList" + const val FIELD_DATE_LIST = "columnDateList" + val INVALID_TYPES_FIELDS_FOR_DISTINCT = arrayOf(FIELD_REALMOBJECT, FIELD_REALMLIST, FIELD_DOUBLE, FIELD_FLOAT, + FIELD_STRING_LIST, FIELD_BINARY_LIST, FIELD_BOOLEAN_LIST, FIELD_LONG_LIST, + FIELD_DOUBLE_LIST, FIELD_FLOAT_LIST, FIELD_DATE_LIST) + } + + @PrimaryKey + @RealmField(name = "_id") + var id = ObjectId() + + @Required + var columnString = "" + var columnLong: Long = 0 + var columnFloat = 0f + var isColumnBoolean = false + + @Required + var columnDate = Date(0) + + @Required + var columnBinary = ByteArray(0) + + @Required + var columnDecimal128 = Decimal128(BigDecimal.ZERO) + + @Required + var columnObjectId = ObjectId(TestHelper.randomObjectIdHexString()) + val columnRealmInteger = MutableRealmInteger.ofNull() + var columnRealmObject: SyncDog? = null + var columnRealmList: RealmList? = null + + // FIXME These are the fields needed to be removed from SyncAllTypes for sync to work when + // updating the partitionValue +// var columnDouble = 0.0 +// var columnStringList: RealmList? = null +// var columnBinaryList: RealmList? = null +// var columnBooleanList: RealmList? = null +// var columnLongList: RealmList? = null +// var columnDoubleList: RealmList? = null +// var columnFloatList: RealmList = RealmList() +// var columnDateList: RealmList? = null +// var columnDecimal128List: RealmList? = null + +// var columnObjectIdList: RealmList? = null + + fun setColumnMutableRealmInteger(value: Int) { + columnRealmInteger.set(value.toLong()) + } + +} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 67b3e630d0..bcef7bc593 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -5,10 +5,7 @@ import android.os.HandlerThread import android.os.SystemClock import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import io.realm.entities.DefaultSyncSchema -import io.realm.entities.SyncAllTypes -import io.realm.entities.SyncStringOnly -import io.realm.entities.SyncStringOnlyModule +import io.realm.entities.* import io.realm.exceptions.DownloadingRealmInterruptedException import io.realm.internal.OsRealmConfig import io.realm.kotlin.syncSession @@ -95,7 +92,10 @@ class SyncSessionTests { app = TestRealmApp() user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) syncConfiguration = configFactory - .createSyncConfigurationBuilder(user) + // TODO We generate new partition value for each test to avoid overlaps in data. We + // could make test booting with a cleaner state by somehow flushing data between + // tests. + .createSyncConfigurationBuilder(user, BsonObjectId(ObjectId())) .modules(DefaultSyncSchema()) .build() } @@ -109,11 +109,7 @@ class SyncSessionTests { } @Test - // FIXME Investigate further - @Ignore("Works on first run, but generates Bad changeset on subsequent runs. Even after " + - "just running one of the other partitionValue test...even if registering a new user") fun partitionValue_string() { - // FIXME See comment for partitionValue_int32 val partitionValue = "123464652" val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonString(partitionValue)) @@ -121,22 +117,14 @@ class SyncSessionTests { .build() Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(SyncAllTypes::class.java, ObjectId()) + realm.createObject(SyncDog::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } } @Test - // FIXME Investigate further - @Ignore("Works on first run, but generates Bad changeset on subsequent runs. Even after " + - "just running one of the other partitionValue test...even if registering a new user") fun partitionValue_int32() { - // FIXME Seems like we cannot repeatedly connect if we change the partitionValue, subsequent - // runs will fail with a - // Connection[1]: Session[1]: Failed to transform received changeset: Schema mismatch: Property 'columnStringList' in class 'AllTypes' is nullable on one side and not on the other. - // Connection[1]: Connection closed due to error - // Session Error[ws://127.0.0.1:9090/]: CLIENT_BAD_CHANGESET(realm::sync::Client::Error:112): Bad changeset (DOWNLOAD) val int = 123536462 val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonInt32(int)) @@ -144,18 +132,14 @@ class SyncSessionTests { .build() Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(SyncAllTypes::class.java, ObjectId()) + realm.createObject(SyncDog::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } } @Test - // FIXME Investigate further - @Ignore("Works on first run, but generates Bad changeset on subsequent runs. Even after " + - "just running one of the other partitionValue test...even if registering a new user") fun partitionValue_int64() { - // FIXME See comment for partitionValue_int32 val long = 1243513244L val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonInt64(long)) @@ -163,18 +147,14 @@ class SyncSessionTests { .build() Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(SyncAllTypes::class.java, ObjectId()) + realm.createObject(SyncDog::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } } @Test - // FIXME Investigate further - @Ignore("Works on first run, but generates Bad changeset on subsequent runs. Even after " + - "just running one of the other partitionValue test...even if registering a new user") fun partitionValue_objectId() { - // FIXME See comment for partitionValue_int32 val objectId = ObjectId("5ecf72df02aa3c32ab6b4ce0") val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonObjectId(objectId)) @@ -182,7 +162,7 @@ class SyncSessionTests { .build() Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(SyncAllTypes::class.java, ObjectId()) + realm.createObject(SyncDog::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -246,12 +226,10 @@ class SyncSessionTests { } @Test - // FIXME Find a way to flush data from server between each run - @Ignore("Needs clean server as asserting on number of rows of a specific class") fun uploadDownloadAllChanges() { Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(SyncAllTypes::class.java, ObjectId()) + realm.createObject(SyncSupportedTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -259,25 +237,22 @@ class SyncSessionTests { // New user but same Realm as configuration has the same partition value val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2 = configFactory - .createSyncConfigurationBuilder(user2) + .createSyncConfigurationBuilder(user2, syncConfiguration.partitionValue) .modules(DefaultSyncSchema()) .build() Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() realm.refresh() - // FIXME Requires server to flush data between each run - assertEquals(1, realm.where(SyncAllTypes::class.java).count()) + assertEquals(1, realm.where(SyncSupportedTypes::class.java).count()) } } @Test - // FIXME Investigate further - @Ignore("Bad changeset for session with different partitionValue") - fun sameSchemeWithDifferentPartitionValue() { + fun differentPartitionValue_supportedTypes() { Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(SyncAllTypes::class.java, ObjectId()) + realm.createObject(SyncSupportedTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -301,6 +276,48 @@ class SyncSessionTests { .modules(DefaultSyncSchema()) .build() + Realm.getInstance(config3).use { realm -> + realm.executeTransaction { + realm.createObject(SyncSupportedTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() + } + } + + @Test + // FIXME Investigate further + @Ignore("Bad changeset for session with different partitionValue") + fun differentPartitionValue_allTypes() { + val config = configFactory + .createSyncConfigurationBuilder(user) + .modules(SyncAllTypesSchema()) + .build() + Realm.getInstance(config).use { realm -> + realm.executeTransaction { + realm.createObject(SyncAllTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() + } + + // Not relevant for test but just to verify that we actually download the correct schema + // New user but same Realm as configuration has the same partition value + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config2 = configFactory + .createSyncConfigurationBuilder(user2) + .modules(SyncAllTypesSchema()) + .build() + + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges() + } + + // New user and different partition value + val user3 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config3 = configFactory + .createSyncConfigurationBuilder(user3, BsonObjectId(ObjectId())) + .modules(SyncAllTypesSchema()) + .build() + Realm.getInstance(config3).use { realm -> realm.executeTransaction { realm.createObject(SyncAllTypes::class.java, ObjectId()) @@ -310,14 +327,12 @@ class SyncSessionTests { } @Test - // FIXME Find a way to flush data from server between each run - @Ignore("Needs clean server as asserting on number of rows of a specific class") fun interruptWaits() { // FIXME Convert to BackgroundLooperThread? Is it doable with all the interruptions val t = Thread(Runnable { Realm.getInstance(syncConfiguration).use { userRealm -> userRealm.executeTransaction { - userRealm.createObject(SyncAllTypes::class.java, ObjectId()) + userRealm.createObject(SyncSupportedTypes::class.java, ObjectId()) } val userSession = userRealm.syncSession try { @@ -339,7 +354,8 @@ class SyncSessionTests { // New user but same Realm as configuration has the same partition value val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2 = configFactory - .createSyncConfigurationBuilder(user2) + .createSyncConfigurationBuilder(user2, syncConfiguration.partitionValue) + .modules(DefaultSyncSchema()) .build() Realm.getInstance(config2).use { adminRealm -> @@ -360,8 +376,7 @@ class SyncSessionTests { } adminRealm.refresh() - // FIXME Requires server to flush data - assertEquals(1, adminRealm.where(SyncAllTypes::class.java).count()) + assertEquals(1, adminRealm.where(SyncSupportedTypes::class.java).count()) } }) t.start() From ecf475144699be5ab276332ae69b00f9bd38df87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 2 Jun 2020 23:06:58 +0200 Subject: [PATCH 1554/2110] Clean up test cases for sync with different partition values --- .../kotlin/io/realm/SyncSessionTests.kt | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index bcef7bc593..dc413d4c98 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -257,31 +257,42 @@ class SyncSessionTests { realm.syncSession.uploadAllLocalChanges() } - // Not relevant for test but just to verify that we actually download the correct schema - // New user but same Realm as configuration has the same partition value + // New user and different partition value val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2 = configFactory - .createSyncConfigurationBuilder(user2) + .createSyncConfigurationBuilder(user2, BsonObjectId(ObjectId())) .modules(DefaultSyncSchema()) .build() Realm.getInstance(config2).use { realm -> - realm.syncSession.downloadAllServerChanges() + realm.executeTransaction { + realm.createObject(SyncSupportedTypes::class.java, ObjectId()) + } + realm.syncSession.uploadAllLocalChanges() } + } - // New user and different partition value - val user3 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) - val config3 = configFactory - .createSyncConfigurationBuilder(user3, BsonObjectId(ObjectId())) - .modules(DefaultSyncSchema()) - .build() - - Realm.getInstance(config3).use { realm -> + @Test + fun differentPartitionValue_noCrosstalk() { + Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { realm.createObject(SyncSupportedTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } + + // New user and different partition value + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config2 = configFactory + .createSyncConfigurationBuilder(user2, BsonObjectId(ObjectId())) + .modules(DefaultSyncSchema()) + .build() + + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges() + // We should not have any data here + assertEquals(0, realm.where(SyncSupportedTypes::class.java).count()) + } } @Test @@ -299,26 +310,14 @@ class SyncSessionTests { realm.syncSession.uploadAllLocalChanges() } - // Not relevant for test but just to verify that we actually download the correct schema - // New user but same Realm as configuration has the same partition value + // New user and different partition value val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2 = configFactory - .createSyncConfigurationBuilder(user2) + .createSyncConfigurationBuilder(user2, BsonObjectId(ObjectId())) .modules(SyncAllTypesSchema()) .build() Realm.getInstance(config2).use { realm -> - realm.syncSession.downloadAllServerChanges() - } - - // New user and different partition value - val user3 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) - val config3 = configFactory - .createSyncConfigurationBuilder(user3, BsonObjectId(ObjectId())) - .modules(SyncAllTypesSchema()) - .build() - - Realm.getInstance(config3).use { realm -> realm.executeTransaction { realm.createObject(SyncAllTypes::class.java, ObjectId()) } From edfe6000b9348dfefe240d7c6362bcc3ae337f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 3 Jun 2020 12:23:49 +0200 Subject: [PATCH 1555/2110] Rework MongoDB package layout (#6888) --- .../mongodb/realm/example/CounterActivity.kt | 11 +- .../mongodb/realm/example/LoginActivity.kt | 3 +- .../mongodb/realm/example/MyApplication.kt | 8 +- realm/kotlin-extensions/build.gradle | 1 + .../io/realm/kotlin/KotlinSyncedRealmTests.kt | 9 +- .../io/realm/kotlin/SyncedRealmExtensions.kt | 4 +- realm/realm-library/build.gradle | 2 +- .../kotlin/io/realm/ApiKeyAuthTests.kt | 51 +-- ...ationTests.kt => AppConfigurationTests.kt} | 36 +- .../realm/{RealmAppTests.kt => AppTests.kt} | 74 ++--- .../kotlin/io/realm/AppUserTests.java | 4 +- ...redentialsTests.kt => CredentialsTests.kt} | 91 ++--- .../kotlin/io/realm/EmailPasswordAuthTests.kt | 24 +- .../{mongodb/functions => }/FunctionsTests.kt | 19 +- .../kotlin/io/realm/ProgressListenerTests.kt | 20 +- .../kotlin/io/realm/SchemaTests.kt | 8 +- .../io/realm/SyncedRealmMigrationTests.kt | 33 +- .../realm/{RealmUserTests.kt => UserTests.kt} | 88 ++--- .../{RealmAppExt.kt => mongodb/AppExt.kt} | 18 +- .../io/realm/mongodb/MongoCollectionTest.kt | 6 +- .../realm/{ => mongodb/sync}/ProgressTests.kt | 2 +- .../realm/{ => mongodb/sync}/SessionTests.kt | 38 ++- .../sync}/SyncConfigurationTests.kt | 59 ++-- .../kotlin/io/realm/mongodb/sync/SyncExt.kt | 22 ++ .../{ => mongodb/sync}/SyncedRealmTests.kt | 40 ++- .../transport/OsJavaNetworkTransportTests.kt | 31 +- .../kotlin/io/realm/util/KotlinTestUtils.kt | 4 +- .../realm-library/src/main/cpp/CMakeLists.txt | 30 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 4 +- ...pp => io_realm_mongodb_ApiKeyAuthImpl.cpp} | 16 +- ..._RealmApp.cpp => io_realm_mongodb_App.cpp} | 14 +- ...o_realm_mongodb_EmailPasswordAuthImpl.cpp} | 16 +- .../cpp/io_realm_mongodb_FunctionsImpl.cpp | 4 +- ...ealmUser.cpp => io_realm_mongodb_User.cpp} | 8 +- ...mongodb_sync_ClientResetRequiredError.cpp} | 4 +- ...ync.cpp => io_realm_mongodb_sync_Sync.cpp} | 12 +- ... => io_realm_mongodb_sync_SyncSession.cpp} | 60 ++-- .../src/main/java/io/realm/BaseRealm.java | 2 +- .../src/main/java/io/realm/Realm.java | 12 + .../java/io/realm/RealmConfiguration.java | 29 +- .../src/main/java/io/realm/internal/Util.java | 20 ++ .../java/io/realm/SyncCredentials.java | 312 ------------------ .../DownloadingRealmInterruptedException.java | 2 +- .../internal/SyncObjectServerFacade.java | 18 +- .../internal/jni/OsJNIResultCallback.java | 6 +- .../io/realm/internal/mongodb/Request.java | 95 ++++++ .../network/NetworkStateReceiver.java | 4 +- .../realm/internal/network/ResultHandler.java | 2 +- .../internal/objectstore/OsAsyncOpenTask.java | 5 +- .../objectstore/OsJavaNetworkTransport.java | 4 +- .../internal/objectstore/OsMongoClient.java | 6 +- .../objectstore/OsMongoCollection.java | 3 +- .../java/io/realm/mongodb/ApiKeyAuthImpl.java | 38 +++ .../realm/{RealmApp.java => mongodb/App.java} | 194 ++++------- .../AppConfiguration.java} | 62 ++-- .../{ => mongodb}/AuthenticationListener.java | 12 +- .../Credentials.java} | 63 ++-- .../realm/mongodb/EmailPasswordAuthImpl.java | 38 +++ .../io/realm/{ => mongodb}/ErrorCode.java | 5 +- .../io/realm/{ => mongodb}/FunctionsImpl.java | 8 +- .../{ => mongodb}/ObjectServerError.java | 5 +- .../{RealmUser.java => mongodb/User.java} | 106 +++--- .../UserIdentity.java} | 20 +- .../realm/{ => mongodb/auth}/ApiKeyAuth.java | 112 ++++--- .../{ => mongodb/auth}/EmailPasswordAuth.java | 84 ++--- .../auth/UserApiKey.java} | 18 +- .../io/realm/mongodb/functions/Functions.java | 72 ++-- .../io/realm/mongodb/mongo/MongoClient.java | 9 +- .../push/Push.java} | 5 +- .../sync}/ClientResetRequiredError.java | 11 +- .../{ => mongodb/sync}/ClientResyncMode.java | 11 +- .../sync}/ConnectionListener.java | 6 +- .../{ => mongodb/sync}/ConnectionState.java | 4 +- .../io/realm/{ => mongodb/sync}/Progress.java | 4 +- .../{ => mongodb/sync}/ProgressListener.java | 4 +- .../{ => mongodb/sync}/ProgressMode.java | 4 +- .../sync/Sync.java} | 39 +-- .../{ => mongodb/sync}/SyncConfiguration.java | 82 +++-- .../realm/{ => mongodb/sync}/SyncSession.java | 28 +- .../java/io/realm/objectserver/AuthTests.java | 1 - .../EncryptedSynchronizedRealmTests.java | 1 - .../kotlin/io/realm/SyncSessionTests.kt | 0 .../realm/objectserver/utils/UserFactory.java | 11 +- .../syncTestUtils/kotlin/io/realm/RealmExt.kt | 25 ++ .../io/realm/TestApp.kt} | 12 +- .../io/realm/TestSyncConfigurationFactory.kt} | 19 +- .../io/realm/mongodb}/SyncTestUtils.kt | 18 +- .../mongodb/sync/SyncConfigurationExt.kt | 28 ++ .../testUtils/java/io/realm/TestHelper.java | 2 +- 89 files changed, 1256 insertions(+), 1299 deletions(-) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{RealmAppConfigurationTests.kt => AppConfigurationTests.kt} (83%) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{RealmAppTests.kt => AppTests.kt} (74%) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{RealmCredentialsTests.kt => CredentialsTests.kt} (59%) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{mongodb/functions => }/FunctionsTests.kt (97%) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{RealmUserTests.kt => UserTests.kt} (71%) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{RealmAppExt.kt => mongodb/AppExt.kt} (71%) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{ => mongodb/sync}/ProgressTests.kt (98%) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{ => mongodb/sync}/SessionTests.kt (93%) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{ => mongodb/sync}/SyncConfigurationTests.kt (84%) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncExt.kt rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{ => mongodb/sync}/SyncedRealmTests.kt (88%) rename realm/realm-library/src/main/cpp/{io_realm_ApiKeyAuth.cpp => io_realm_mongodb_ApiKeyAuthImpl.cpp} (90%) rename realm/realm-library/src/main/cpp/{io_realm_RealmApp.cpp => io_realm_mongodb_App.cpp} (93%) rename realm/realm-library/src/main/cpp/{io_realm_EmailPasswordAuth.cpp => io_realm_mongodb_EmailPasswordAuthImpl.cpp} (80%) rename realm/realm-library/src/main/cpp/{io_realm_RealmUser.cpp => io_realm_mongodb_User.cpp} (89%) rename realm/realm-library/src/main/cpp/{io_realm_ClientResetRequiredError.cpp => io_realm_mongodb_sync_ClientResetRequiredError.cpp} (87%) rename realm/realm-library/src/main/cpp/{io_realm_RealmSync.cpp => io_realm_mongodb_sync_Sync.cpp} (81%) rename realm/realm-library/src/main/cpp/{io_realm_SyncSession.cpp => io_realm_mongodb_sync_SyncSession.cpp} (83%) delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/mongodb/Request.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java rename realm/realm-library/src/objectServer/java/io/realm/{RealmApp.java => mongodb/App.java} (72%) rename realm/realm-library/src/objectServer/java/io/realm/{RealmAppConfiguration.java => mongodb/AppConfiguration.java} (90%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb}/AuthenticationListener.java (77%) rename realm/realm-library/src/objectServer/java/io/realm/{RealmCredentials.java => mongodb/Credentials.java} (76%) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb}/ErrorCode.java (99%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb}/FunctionsImpl.java (95%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb}/ObjectServerError.java (98%) rename realm/realm-library/src/objectServer/java/io/realm/{RealmUser.java => mongodb/User.java} (80%) rename realm/realm-library/src/objectServer/java/io/realm/{RealmUserIdentity.java => mongodb/UserIdentity.java} (77%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/auth}/ApiKeyAuth.java (68%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/auth}/EmailPasswordAuth.java (80%) rename realm/realm-library/src/objectServer/java/io/realm/{RealmUserApiKey.java => mongodb/auth/UserApiKey.java} (85%) rename realm/realm-library/src/objectServer/java/io/realm/{RealmPushNotifications.java => mongodb/push/Push.java} (83%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/sync}/ClientResetRequiredError.java (92%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/sync}/ClientResyncMode.java (86%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/sync}/ConnectionListener.java (93%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/sync}/ConnectionState.java (97%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/sync}/Progress.java (98%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/sync}/ProgressListener.java (97%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/sync}/ProgressMode.java (96%) rename realm/realm-library/src/objectServer/java/io/realm/{RealmSync.java => mongodb/sync/Sync.java} (92%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/sync}/SyncConfiguration.java (94%) rename realm/realm-library/src/objectServer/java/io/realm/{ => mongodb/sync}/SyncSession.java (97%) create mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt create mode 100644 realm/realm-library/src/syncTestUtils/kotlin/io/realm/RealmExt.kt rename realm/realm-library/src/syncTestUtils/{java/io/realm/TestRealmApp.kt => kotlin/io/realm/TestApp.kt} (81%) rename realm/realm-library/src/syncTestUtils/{java/io/realm/TestSyncConfigurationFactory.java => kotlin/io/realm/TestSyncConfigurationFactory.kt} (57%) rename realm/realm-library/src/syncTestUtils/{java/io/realm => kotlin/io/realm/mongodb}/SyncTestUtils.kt (93%) create mode 100644 realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt diff --git a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt index 7078951577..e5279180e3 100644 --- a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt +++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/CounterActivity.kt @@ -31,6 +31,11 @@ import com.mongodb.realm.example.databinding.ActivityCounterBinding import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.RealmLog +import io.realm.mongodb.User +import io.realm.mongodb.sync.ProgressListener +import io.realm.mongodb.sync.ProgressMode +import io.realm.mongodb.sync.SyncConfiguration +import io.realm.mongodb.sync.SyncSession import me.zhanghai.android.materialprogressbar.MaterialProgressBar import java.util.* import java.util.concurrent.atomic.AtomicBoolean @@ -54,15 +59,15 @@ class CounterActivity : AppCompatActivity() { private var realm: Realm? = null private lateinit var session: SyncSession - private var user: RealmUser? = null + private var user: User? = null private lateinit var counterView: TextView private lateinit var progressBar: MaterialProgressBar private lateinit var counter: CRDTCounter // Keep strong reference to counter to keep change listeners alive. - private val loggedInUser: RealmUser? + private val loggedInUser: User? get() { - var user: RealmUser? = null + var user: User? = null try { user = APP.currentUser() diff --git a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt index 8e76283804..bfa09d78bd 100644 --- a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt +++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt @@ -26,6 +26,7 @@ import androidx.databinding.DataBindingUtil import com.mongodb.realm.example.databinding.ActivityLoginBinding import io.realm.* import io.realm.log.RealmLog +import io.realm.mongodb.Credentials class LoginActivity : AppCompatActivity() { @@ -76,7 +77,7 @@ class LoginActivity : AppCompatActivity() { } } } else { - val creds = RealmCredentials.emailPassword(username, password) + val creds = Credentials.emailPassword(username, password) APP.loginAsync(creds) { progressDialog.dismiss() if (!it.isSuccess) { diff --git a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/MyApplication.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/MyApplication.kt index ced3a2c79a..a725160578 100644 --- a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/MyApplication.kt +++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/MyApplication.kt @@ -19,19 +19,19 @@ package com.mongodb.realm.example import android.app.Application import io.realm.Realm -import io.realm.RealmApp -import io.realm.RealmAppConfiguration import io.realm.log.LogLevel import io.realm.log.RealmLog +import io.realm.mongodb.App +import io.realm.mongodb.AppConfiguration -lateinit var APP: RealmApp +lateinit var APP: App class MyApplication : Application() { override fun onCreate() { super.onCreate() Realm.init(this) - APP = RealmApp(RealmAppConfiguration.Builder(BuildConfig.MONGODB_REALM_APP_ID) + APP = App(AppConfiguration.Builder(BuildConfig.MONGODB_REALM_APP_ID) .baseUrl(BuildConfig.MONGODB_REALM_URL) .appName(BuildConfig.VERSION_NAME) .appVersion(BuildConfig.VERSION_CODE.toString()) diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 626efd3300..c5cdd1aceb 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -62,6 +62,7 @@ android { '../realm-library/src/testUtils/java', '../realm-library/src/testUtils/kotlin', '../realm-library/src/syncTestUtils/java', + '../realm-library/src/syncTestUtils/kotlin', ] } diff --git a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt index e0dd96dee5..0f18a0e530 100644 --- a/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt +++ b/realm/kotlin-extensions/src/androidTestObjectServer/kotlin/io/realm/kotlin/KotlinSyncedRealmTests.kt @@ -1,8 +1,9 @@ package io.realm.kotlin import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry import io.realm.* +import io.realm.mongodb.App +import io.realm.mongodb.sync.SyncConfiguration import org.junit.* import org.junit.Assert.assertEquals import org.junit.Assert.fail @@ -14,14 +15,14 @@ class KotlinSyncedRealmTests { @get:Rule val configFactory = TestSyncConfigurationFactory() - private lateinit var app: RealmApp + private lateinit var app: App private lateinit var realm: Realm @Before fun setUp() { // FIXME // Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) -// app = RealmApp("foo") +// app = App("foo") // val user = SyncTestUtils.createTestUser(app) // realm = Realm.getInstance(configFactory.createSyncConfigurationBuilder(user).build()) } @@ -33,7 +34,7 @@ class KotlinSyncedRealmTests { // realm.close() // } // if (this::app.isInitialized) { -// RealmApp.CREATED = false +// App.CREATED = false // } } diff --git a/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt b/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt index b50a3967ab..a9b8dc4769 100644 --- a/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt +++ b/realm/kotlin-extensions/src/objectServer/kotlin/io/realm/kotlin/SyncedRealmExtensions.kt @@ -16,8 +16,8 @@ package io.realm.kotlin import io.realm.Realm -import io.realm.SyncConfiguration -import io.realm.SyncSession +import io.realm.mongodb.sync.SyncConfiguration +import io.realm.mongodb.sync.SyncSession /** diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index d4e694f7ea..bff4d2fb2f 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -104,7 +104,7 @@ android { java.srcDirs += ['src/androidTest/kotlin', 'src/testUtils/java', 'src/testUtils/kotlin'] } androidTestObjectServer { - java.srcDirs += [/* FIXME 'src/syncIntegrationTest/java', */ 'src/androidTestObjectServer/kotlin', 'src/syncTestUtils/java'] + java.srcDirs += [/* FIXME 'src/syncIntegrationTest/java', */ 'src/androidTestObjectServer/kotlin', 'src/syncTestUtils/java', 'src/syncTestUtils/kotlin'] assets.srcDirs += ['src/syncIntegrationTest/assets/'] } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt index 43616060b2..86b0ba6c84 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt @@ -19,8 +19,9 @@ import androidx.test.annotation.UiThreadTest import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin -import io.realm.log.LogLevel -import io.realm.log.RealmLog +import io.realm.mongodb.* +import io.realm.mongodb.auth.ApiKeyAuth +import io.realm.mongodb.auth.UserApiKey import io.realm.rule.BlockingLooperThread import org.bson.types.ObjectId import org.junit.After @@ -32,13 +33,13 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ApiKeyAuthTests { private val looperThread = BlockingLooperThread() - private lateinit var app: TestRealmApp + private lateinit var app: TestApp private lateinit var admin: ServerAdmin - private lateinit var user: RealmUser + private lateinit var user: User private lateinit var provider: ApiKeyAuth // Callback use to verify that an Illegal Argument was thrown from async methods - private val checkNullInVoidCallback = RealmApp.Callback { result -> + private val checkNullInVoidCallback = App.Callback { result -> if (result.isSuccess) { fail() } else { @@ -47,7 +48,7 @@ class ApiKeyAuthTests { } } - private val checkNullInApiKeyCallback = RealmApp.Callback { result -> + private val checkNullInApiKeyCallback = App.Callback { result -> if (result.isSuccess) { fail() } else { @@ -70,7 +71,7 @@ class ApiKeyAuthTests { fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) admin = ServerAdmin() - app = TestRealmApp() + app = TestApp() user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") provider = user.apiKeyAuth } @@ -93,7 +94,7 @@ class ApiKeyAuthTests { @Test fun createApiKey() { - val key: RealmUserApiKey = provider.createApiKey("my-key") + val key: UserApiKey = provider.createApiKey("my-key") assertEquals("my-key", key.name) assertNotNull("my-key", key.value) assertNotNull("my-key", key.id) @@ -148,8 +149,8 @@ class ApiKeyAuthTests { @Test fun fetchApiKey() { - val key1: RealmUserApiKey = provider.createApiKey("my-key") - val key2: RealmUserApiKey = provider.fetchApiKey(key1.id) + val key1: UserApiKey = provider.createApiKey("my-key") + val key2: UserApiKey = provider.fetchApiKey(key1.id) assertEquals(key1.id, key2.id) assertEquals(key1.name, key2.name) @@ -177,7 +178,7 @@ class ApiKeyAuthTests { @Test fun fetchApiKeyAsync() { - val key1: RealmUserApiKey = provider.createApiKey("my-key") + val key1: UserApiKey = provider.createApiKey("my-key") looperThread.runBlocking { provider.fetchApiKeyAsync(key1.id) { result -> val key2 = result.orThrow @@ -192,9 +193,9 @@ class ApiKeyAuthTests { @Test fun fetchAllApiKeys() { - val key1: RealmUserApiKey = provider.createApiKey("my-key") - val key2: RealmUserApiKey = provider.createApiKey("other-key") - val allKeys: List = provider.fetchAllApiKeys() + val key1: UserApiKey = provider.createApiKey("my-key") + val key2: UserApiKey = provider.createApiKey("other-key") + val allKeys: List = provider.fetchAllApiKeys() assertEquals(2, allKeys.size) assertTrue(allKeys.any { it.id == key1.id }) assertTrue(allKeys.any { it.id == key2.id }) @@ -202,11 +203,11 @@ class ApiKeyAuthTests { @Test fun fetchAllApiKeysAsync() { - val key1: RealmUserApiKey = provider.createApiKey("my-key") - val key2: RealmUserApiKey = provider.createApiKey("other-key") + val key1: UserApiKey = provider.createApiKey("my-key") + val key2: UserApiKey = provider.createApiKey("other-key") looperThread.runBlocking { provider.fetchAllApiKeys() { result -> - val keys: List = result.orThrow + val keys: List = result.orThrow assertEquals(2, keys.size) assertTrue(keys.any { it.id == key1.id }) assertTrue(keys.any { it.id == key2.id }) @@ -217,7 +218,7 @@ class ApiKeyAuthTests { @Test fun deleteApiKey() { - val key1: RealmUserApiKey = provider.createApiKey("my-key") + val key1: UserApiKey = provider.createApiKey("my-key") assertNotNull(provider.fetchApiKey(key1.id)) provider.deleteApiKey(key1.id) try { @@ -248,7 +249,7 @@ class ApiKeyAuthTests { @Test fun deleteApiKeyAsync() { - val key: RealmUserApiKey = provider.createApiKey("my-key") + val key: UserApiKey = provider.createApiKey("my-key") assertNotNull(provider.fetchApiKey(key.id)) looperThread.runBlocking { provider.deleteApiKeyAsync(key.id) { result -> @@ -281,7 +282,7 @@ class ApiKeyAuthTests { @Test fun enableApiKey() { - val key: RealmUserApiKey = provider.createApiKey("my-key") + val key: UserApiKey = provider.createApiKey("my-key") provider.disableApiKey(key.id) assertFalse(provider.fetchApiKey(key.id).isEnabled) provider.enableApiKey(key.id) @@ -290,7 +291,7 @@ class ApiKeyAuthTests { @Test fun enableApiKey_alreadyEnabled() { - val key: RealmUserApiKey = provider.createApiKey("my-key") + val key: UserApiKey = provider.createApiKey("my-key") provider.disableApiKey(key.id) assertFalse(provider.fetchApiKey(key.id).isEnabled) provider.enableApiKey(key.id) @@ -319,7 +320,7 @@ class ApiKeyAuthTests { @Test fun enableApiKeyAsync() { - val key: RealmUserApiKey = provider.createApiKey("my-key") + val key: UserApiKey = provider.createApiKey("my-key") provider.disableApiKey(key.id) assertFalse(provider.fetchApiKey(key.id).isEnabled) looperThread.runBlocking { @@ -348,14 +349,14 @@ class ApiKeyAuthTests { @Test fun disableApiKey() { - val key: RealmUserApiKey = provider.createApiKey("my-key") + val key: UserApiKey = provider.createApiKey("my-key") provider.disableApiKey(key.id) assertFalse(provider.fetchApiKey(key.id).isEnabled) } @Test fun disableApiKey_alreadyDisabled() { - val key: RealmUserApiKey = provider.createApiKey("my-key") + val key: UserApiKey = provider.createApiKey("my-key") provider.disableApiKey(key.id) assertFalse(provider.fetchApiKey(key.id).isEnabled) provider.disableApiKey(key.id) @@ -382,7 +383,7 @@ class ApiKeyAuthTests { @Test fun disableApiKeyAsync() { - val key: RealmUserApiKey = provider.createApiKey("my-key") + val key: UserApiKey = provider.createApiKey("my-key") assertTrue(key.isEnabled) looperThread.runBlocking { provider.disableApiKeyAsync(key.id) { result -> diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt similarity index 83% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index 1a2fdba9f9..e5d5d0007a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -17,9 +17,9 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import io.realm.mongodb.AppConfiguration import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries -import org.bson.codecs.configuration.CodecRegistry import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -33,7 +33,7 @@ import java.lang.IllegalArgumentException import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) -class RealmAppConfigurationTests { +class AppConfigurationTests { @get:Rule val tempFolder = TemporaryFolder() @@ -45,17 +45,17 @@ class RealmAppConfigurationTests { @Test fun authorizationHeaderName_illegalArgumentsThrows() { - val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val builder: AppConfiguration.Builder = AppConfiguration.Builder("app-id") assertFailsWith { builder.authorizationHeaderName(TestHelper.getNull()) } assertFailsWith { builder.authorizationHeaderName("") } } @Test fun authorizationHeaderName() { - val config1 = RealmAppConfiguration.Builder("app-id").build() + val config1 = AppConfiguration.Builder("app-id").build() assertEquals("Authorization", config1.authorizationHeaderName) - val config2 = RealmAppConfiguration.Builder("app-id") + val config2 = AppConfiguration.Builder("app-id") .authorizationHeaderName("CustomAuth") .build() assertEquals("CustomAuth", config2.authorizationHeaderName) @@ -67,7 +67,7 @@ class RealmAppConfigurationTests { @Test fun addCustomRequestHeader_illegalArgumentThrows() { - val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val builder: AppConfiguration.Builder = AppConfiguration.Builder("app-id") assertFailsWith { builder.addCustomRequestHeader("", "val") } assertFailsWith { builder.addCustomRequestHeader(TestHelper.getNull(), "val") } assertFailsWith { builder.addCustomRequestHeader("header", TestHelper.getNull()) } @@ -76,7 +76,7 @@ class RealmAppConfigurationTests { @Test fun addCustomRequestHeader() { - val config = RealmAppConfiguration.Builder("app-id") + val config = AppConfiguration.Builder("app-id") .addCustomRequestHeader("header1", "val1") .addCustomRequestHeader("header2", "val2") .build() @@ -95,7 +95,7 @@ class RealmAppConfigurationTests { val inputHeaders: MutableMap = LinkedHashMap() inputHeaders["header1"] = "value1" inputHeaders["header2"] = "value2" - val config = RealmAppConfiguration.Builder("app-id") + val config = AppConfiguration.Builder("app-id") .addCustomRequestHeaders(TestHelper.getNull()) .addCustomRequestHeaders(inputHeaders) .build() @@ -107,7 +107,7 @@ class RealmAppConfigurationTests { @Test fun addCustomHeader_combinesSingleAndMultiple() { - val config = RealmAppConfiguration.Builder("app-id") + val config = AppConfiguration.Builder("app-id") .addCustomRequestHeader("header3", "val3") .addCustomRequestHeaders(mapOf(Pair("header1", "val1"))) .build() @@ -119,7 +119,7 @@ class RealmAppConfigurationTests { @Test fun syncRootDirectory_default() { - val config = RealmAppConfiguration.Builder("app-id").build() + val config = AppConfiguration.Builder("app-id").build() val expectedDefaultRoot = File(InstrumentationRegistry.getInstrumentation().targetContext.filesDir, "mongodb-realm") assertEquals(expectedDefaultRoot, config.syncRootDirectory) @@ -128,7 +128,7 @@ class RealmAppConfigurationTests { @Test fun syncRootDirectory() { - val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val builder: AppConfiguration.Builder = AppConfiguration.Builder("app-id") val expectedRoot = tempFolder.newFolder() val config = builder .syncRootDirectory(expectedRoot) @@ -140,20 +140,20 @@ class RealmAppConfigurationTests { @Test fun syncRootDirectory_null() { - val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val builder: AppConfiguration.Builder = AppConfiguration.Builder("app-id") assertFailsWith { builder.syncRootDirectory(TestHelper.getNull()) } } @Test fun syncRootDirectory_writeProtectedDir() { - val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val builder: AppConfiguration.Builder = AppConfiguration.Builder("app-id") val dir = File("/") assertFailsWith { builder.syncRootDirectory(dir) } } @Test fun syncRootDirectory_dirIsAFile() { - val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val builder: AppConfiguration.Builder = AppConfiguration.Builder("app-id") val file = File(tempFolder.newFolder(), "dummyfile") assertTrue(file.createNewFile()) assertFailsWith { builder.syncRootDirectory(file) } @@ -233,7 +233,7 @@ class RealmAppConfigurationTests { @Test fun codecRegistry_null() { - val builder: RealmAppConfiguration.Builder = RealmAppConfiguration.Builder("app-id") + val builder: AppConfiguration.Builder = AppConfiguration.Builder("app-id") assertFailsWith { builder.codecRegistry(TestHelper.getNull()) } @@ -241,14 +241,14 @@ class RealmAppConfigurationTests { @Test fun defaultFunctionsCodecRegistry() { - val config: RealmAppConfiguration = RealmAppConfiguration.Builder("app-id").build() - assertEquals(RealmAppConfiguration.DEFAULT_BSON_CODEC_REGISTRY, config.defaultCodecRegistry) + val config: AppConfiguration = AppConfiguration.Builder("app-id").build() + assertEquals(AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY, config.defaultCodecRegistry) } @Test fun customCodecRegistry() { val configCodecRegistry = CodecRegistries.fromCodecs(StringCodec()) - val config: RealmAppConfiguration = RealmAppConfiguration.Builder("app-id") + val config: AppConfiguration = AppConfiguration.Builder("app-id") .codecRegistry(configCodecRegistry) .build() assertEquals(configCodecRegistry, config.defaultCodecRegistry) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt similarity index 74% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index 303cfb34c7..ec2083aaac 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -18,12 +18,8 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin +import io.realm.mongodb.* import io.realm.rule.BlockingLooperThread -import org.bson.BsonReader -import org.bson.BsonWriter -import org.bson.codecs.Codec -import org.bson.codecs.DecoderContext -import org.bson.codecs.EncoderContext import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries import org.junit.After @@ -36,16 +32,16 @@ import java.util.concurrent.atomic.AtomicReference import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) -class RealmAppTests { +class AppTests { private val looperThread = BlockingLooperThread() - private lateinit var app: TestRealmApp + private lateinit var app: TestApp private lateinit var admin: ServerAdmin @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestRealmApp() + app = TestApp() admin = ServerAdmin() } @@ -58,14 +54,14 @@ class RealmAppTests { @Test fun login() { - val creds = RealmCredentials.anonymous() + val creds = Credentials.anonymous() var user = app.login(creds) assertNotNull(user) } @Test fun login_invalidUserThrows() { - val credentials = RealmCredentials.emailPassword("foo", "bar") + val credentials = Credentials.emailPassword("foo", "bar") try { app.login(credentials) fail() @@ -81,7 +77,7 @@ class RealmAppTests { @Test fun loginAsync() = looperThread.runBlocking { - app.loginAsync(RealmCredentials.anonymous()) { result -> + app.loginAsync(Credentials.anonymous()) { result -> assertNotNull(result.orThrow) looperThread.testComplete() } @@ -89,7 +85,7 @@ class RealmAppTests { @Test fun loginAsync_invalidUserThrows() = looperThread.runBlocking { - app.loginAsync(RealmCredentials.emailPassword("foo", "bar")) { result -> + app.loginAsync(Credentials.emailPassword("foo", "bar")) { result -> assertFalse(result.isSuccess) assertEquals(ErrorCode.SERVICE_UNKNOWN, result.error.errorCode) looperThread.testComplete() @@ -99,7 +95,7 @@ class RealmAppTests { @Test fun loginAsync_throwsOnNonLooperThread() { try { - app.loginAsync(RealmCredentials.anonymous()) { fail() } + app.loginAsync(Credentials.anonymous()) { fail() } fail() } catch (ignore: IllegalStateException) { } @@ -108,7 +104,7 @@ class RealmAppTests { @Test fun currentUser() { assertNull(app.currentUser()) - val user: RealmUser = app.login(RealmCredentials.anonymous()) + val user: User = app.login(Credentials.anonymous()) assertEquals(user, app.currentUser()) user.logOut() assertNull(app.currentUser()) @@ -117,18 +113,18 @@ class RealmAppTests { @Test fun allUsers() { assertEquals(0, app.allUsers().size) - val user1 = app.login(RealmCredentials.anonymous()) + val user1 = app.login(Credentials.anonymous()) var allUsers = app.allUsers() assertEquals(1, allUsers.size) assertTrue(allUsers.containsKey(user1.id)) assertEquals(user1, allUsers[user1.id]) - val user2 = app.login(RealmCredentials.anonymous()) + val user2 = app.login(Credentials.anonymous()) allUsers = app.allUsers() assertEquals(2, allUsers.size) assertTrue(allUsers.containsKey(user2.id)) - val user3: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val user3: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") allUsers = app.allUsers() assertEquals(3, allUsers.size) assertTrue(allUsers.containsKey(user3.id)) @@ -138,7 +134,7 @@ class RealmAppTests { allUsers = app.allUsers() assertEquals(3, allUsers.size) assertTrue(allUsers.containsKey(user3.id)) - assertEquals(RealmUser.State.LOGGED_OUT, allUsers[user3.id]!!.state) + assertEquals(User.State.LOGGED_OUT, allUsers[user3.id]!!.state) // Logging out anonymous users will remove them completely user1.logOut() @@ -149,22 +145,22 @@ class RealmAppTests { @Test fun allUsers_retrieveRemovedUser() { - val user1: RealmUser = app.login(RealmCredentials.anonymous()) - val allUsers: Map = app.allUsers() + val user1: User = app.login(Credentials.anonymous()) + val allUsers: Map = app.allUsers() assertEquals(1, allUsers.size) user1.logOut() assertEquals(1, allUsers.size) - val userCopy: RealmUser = allUsers[user1.id] ?: error("Could not find user") + val userCopy: User = allUsers[user1.id] ?: error("Could not find user") assertEquals(user1, userCopy) - assertEquals(RealmUser.State.REMOVED, userCopy.state) + assertEquals(User.State.REMOVED, userCopy.state) assertTrue(app.allUsers().isEmpty()) } @Test fun switchUser() { - val user1: RealmUser = app.login(RealmCredentials.anonymous()) + val user1: User = app.login(Credentials.anonymous()) assertEquals(user1, app.currentUser()) - val user2: RealmUser = app.login(RealmCredentials.anonymous()) + val user2: User = app.login(Credentials.anonymous()) assertEquals(user2, app.currentUser()) assertEquals(user1, app.switchUser(user1)) @@ -173,8 +169,8 @@ class RealmAppTests { @Test fun switchUser_throwIfUserNotLoggedIn() { - val user1: RealmUser = app.login(RealmCredentials.anonymous()) - val user2: RealmUser = app.login(RealmCredentials.anonymous()) + val user1: User = app.login(Credentials.anonymous()) + val user2: User = app.login(Credentials.anonymous()) assertEquals(user2, app.currentUser()) user1.logOut() @@ -187,8 +183,8 @@ class RealmAppTests { @Test fun currentUser_FallbackToNextValidUser() { - val user1: RealmUser = app.login(RealmCredentials.anonymous()) - val user2: RealmUser = app.login(RealmCredentials.anonymous()) + val user1: User = app.login(Credentials.anonymous()) + val user2: User = app.login(Credentials.anonymous()) assertEquals(user2, app.currentUser()) user2.logOut() assertEquals(user1, app.currentUser()) @@ -213,21 +209,21 @@ class RealmAppTests { @Test fun authListener() { - val userRef = AtomicReference(null) + val userRef = AtomicReference(null) looperThread.runBlocking { val authenticationListener = object : AuthenticationListener { - override fun loggedIn(user: RealmUser) { + override fun loggedIn(user: User) { userRef.set(user) user.logOutAsync { /* Ignore */ } } - override fun loggedOut(user: RealmUser) { + override fun loggedOut(user: User) { assertEquals(userRef.get(), user) looperThread.testComplete() } } app.addAuthenticationListener(authenticationListener) - app.login(RealmCredentials.anonymous()) + app.login(Credentials.anonymous()) } } @@ -239,12 +235,12 @@ class RealmAppTests { @Test fun authListener_remove() = looperThread.runBlocking { val failListener = object : AuthenticationListener { - override fun loggedIn(user: RealmUser) { fail() } - override fun loggedOut(user: RealmUser) { fail() } + override fun loggedIn(user: User) { fail() } + override fun loggedOut(user: User) { fail() } } val successListener = object : AuthenticationListener { - override fun loggedOut(user: RealmUser) { fail() } - override fun loggedIn(user: RealmUser) { looperThread.testComplete() } + override fun loggedOut(user: User) { fail() } + override fun loggedIn(user: User) { looperThread.testComplete() } } // This test depends on listeners being executed in order which is an // implementation detail, but there isn't a sure fire way to do this @@ -252,18 +248,18 @@ class RealmAppTests { app.addAuthenticationListener(failListener) app.addAuthenticationListener(successListener) app.removeAuthenticationListener(failListener) - app.login(RealmCredentials.anonymous()) + app.login(Credentials.anonymous()) } @Test fun functions_defaultCodecRegistry() { - var user = app.login(RealmCredentials.anonymous()) + var user = app.login(Credentials.anonymous()) assertEquals(app.configuration.defaultCodecRegistry, app.getFunctions(user).defaultCodecRegistry) } @Test fun functions_customCodecRegistry() { - var user = app.login(RealmCredentials.anonymous()) + var user = app.login(Credentials.anonymous()) val registry = CodecRegistries.fromCodecs(StringCodec()) assertEquals(registry, app.getFunctions(user, registry).defaultCodecRegistry) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppUserTests.java b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppUserTests.java index f9a47d6b3c..5c2f099799 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppUserTests.java +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppUserTests.java @@ -51,8 +51,8 @@ //import io.realm.rule.RunInLooperThread; //import io.realm.rule.RunTestInLooperThread; // -//import static io.realm.SyncTestUtils.createTestAdminUser; -//import static io.realm.SyncTestUtils.createTestUser; +//import static io.realm.mongodb.SyncTestUtils.createTestAdminUser; +//import static io.realm.mongodb.SyncTestUtils.createTestUser; //import static junit.framework.Assert.assertEquals; //import static org.junit.Assert.assertFalse; //import static org.junit.Assert.assertNotEquals; diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt similarity index 59% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index 6a6aff2ddb..50a06ba783 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmCredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -17,6 +17,7 @@ package io.realm import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.mongodb.* import org.junit.After import org.junit.Assert.* import org.junit.BeforeClass @@ -26,9 +27,9 @@ import org.junit.runner.RunWith import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) -class RealmCredentialsTests { +class CredentialsTests { - private lateinit var app: RealmApp + private lateinit var app: App companion object { @@ -49,7 +50,7 @@ class RealmCredentialsTests { @Test fun anonymous() { - val creds = RealmCredentials.anonymous() + val creds = Credentials.anonymous() assertEquals("anon-user", creds.identityProvider.id) assertTrue(creds.asJson().contains("anon-user")) // Treat the JSON as an opaque value. } @@ -57,28 +58,28 @@ class RealmCredentialsTests { @Ignore("FIXME: Awaiting ObjectStore support") @Test fun apiKey() { - val creds = RealmCredentials.apiKey("token") + val creds = Credentials.apiKey("token") assertEquals("anon-user", creds.identityProvider.id) assertTrue(creds.asJson().contains("token")) // Treat the JSON as an opaque value. } @Test fun apiKey_invalidInput() { - assertFailsWith { RealmCredentials.apiKey("") } - assertFailsWith { RealmCredentials.apiKey(TestHelper.getNull()) } + assertFailsWith { Credentials.apiKey("") } + assertFailsWith { Credentials.apiKey(TestHelper.getNull()) } } @Test fun apple() { - val creds = RealmCredentials.apple("apple-token") + val creds = Credentials.apple("apple-token") assertEquals("oauth2-apple", creds.identityProvider.id) assertTrue(creds.asJson().contains("apple-token")) // Treat the JSON as a largely opaque value. } @Test fun apple_invalidInput() { - assertFailsWith { RealmCredentials.apple("") } - assertFailsWith { RealmCredentials.apple(TestHelper.getNull()) } + assertFailsWith { Credentials.apple("") } + assertFailsWith { Credentials.apple(TestHelper.getNull()) } } @Ignore("FIXME: Awaiting ObjectStore support") @@ -95,7 +96,7 @@ class RealmCredentialsTests { @Test fun emailPassword() { - val creds = RealmCredentials.emailPassword("foo@bar.com", "secret") + val creds = Credentials.emailPassword("foo@bar.com", "secret") assertEquals("local-userpass", creds.identityProvider.id) // Treat the JSON as a largely opaque value. assertTrue(creds.asJson().contains("foo@bar.com")) @@ -104,53 +105,53 @@ class RealmCredentialsTests { @Test fun emailPassword_invalidInput() { - assertFailsWith { RealmCredentials.emailPassword("", "password") } - assertFailsWith { RealmCredentials.emailPassword("email", "") } - assertFailsWith { RealmCredentials.emailPassword(TestHelper.getNull(), "password") } - assertFailsWith { RealmCredentials.emailPassword("email", TestHelper.getNull()) } + assertFailsWith { Credentials.emailPassword("", "password") } + assertFailsWith { Credentials.emailPassword("email", "") } + assertFailsWith { Credentials.emailPassword(TestHelper.getNull(), "password") } + assertFailsWith { Credentials.emailPassword("email", TestHelper.getNull()) } } @Test fun facebook() { - val creds = RealmCredentials.facebook("fb-token") + val creds = Credentials.facebook("fb-token") assertEquals("oauth2-facebook", creds.identityProvider.id) assertTrue(creds.asJson().contains("fb-token")) } @Test fun facebook_invalidInput() { - assertFailsWith { RealmCredentials.facebook("") } - assertFailsWith { RealmCredentials.facebook(TestHelper.getNull()) } + assertFailsWith { Credentials.facebook("") } + assertFailsWith { Credentials.facebook(TestHelper.getNull()) } } @Test fun google() { - val creds = RealmCredentials.google("google-token") + val creds = Credentials.google("google-token") assertEquals("oauth2-google", creds.identityProvider.id) assertTrue(creds.asJson().contains("google-token")) } @Test fun google_invalidInput() { - assertFailsWith { RealmCredentials.google("") } - assertFailsWith { RealmCredentials.google(TestHelper.getNull()) } + assertFailsWith { Credentials.google("") } + assertFailsWith { Credentials.google(TestHelper.getNull()) } } @Ignore("FIXME: Awaiting ObjectStore support") @Test fun jwt() { - val creds = RealmCredentials.google("jwt-token") + val creds = Credentials.google("jwt-token") assertEquals("jwt", creds.identityProvider.id) assertTrue(creds.asJson().contains("jwt-token")) } @Test fun jwt_invalidInput() { - assertFailsWith { RealmCredentials.jwt("") } - assertFailsWith { RealmCredentials.jwt(TestHelper.getNull()) } + assertFailsWith { Credentials.jwt("") } + assertFailsWith { Credentials.jwt(TestHelper.getNull()) } } - fun expectErrorCode(app: RealmApp, expectedCode: ErrorCode, credentials: RealmCredentials) { + fun expectErrorCode(app: App, expectedCode: ErrorCode, credentials: Credentials) { try { app.login(credentials) fail() @@ -161,28 +162,28 @@ class RealmCredentialsTests { @Test fun loginUsingCredentials() { - app = TestRealmApp() - RealmCredentials.IdentityProvider.values().forEach { provider -> + app = TestApp() + Credentials.IdentityProvider.values().forEach { provider -> when(provider) { - RealmCredentials.IdentityProvider.ANONYMOUS -> { - val user = app.login(RealmCredentials.anonymous()) + Credentials.IdentityProvider.ANONYMOUS -> { + val user = app.login(Credentials.anonymous()) assertNotNull(user) } - RealmCredentials.IdentityProvider.API_KEY -> { + Credentials.IdentityProvider.API_KEY -> { // FIXME: Wait for API Key support in OS -// val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") -// val key: RealmUserApiKey = app.apiKeyAuthProvider.createApiKey("my-key"); -// val apiKeyUser = app.login(RealmCredentials.apiKey(key.value!!)) +// val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") +// val key: UserApiKey = app.apiKeyAuthProvider.createApiKey("my-key"); +// val apiKeyUser = app.login(Credentials.apiKey(key.value!!)) // assertNotNull(apiKeyUser) } - RealmCredentials.IdentityProvider.CUSTOM_FUNCTION -> { + Credentials.IdentityProvider.CUSTOM_FUNCTION -> { // FIXME Wait for Custom Function support } - RealmCredentials.IdentityProvider.EMAIL_PASSWORD -> { + Credentials.IdentityProvider.EMAIL_PASSWORD -> { val email = TestHelper.getRandomEmail() val password = "123456" app.emailPasswordAuth.registerUser(email, password) - val user = app.login(RealmCredentials.emailPassword(email, password)) + val user = app.login(Credentials.emailPassword(email, password)) assertNotNull(user) } @@ -190,22 +191,22 @@ class RealmCredentialsTests { // login service. Instead we attempt to login and verify that a proper exception // is thrown. At least that should verify that correctly formatted JSON is being // sent across the wire. - RealmCredentials.IdentityProvider.FACEBOOK -> { - expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.facebook("facebook-token")) + Credentials.IdentityProvider.FACEBOOK -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.facebook("facebook-token")) } - RealmCredentials.IdentityProvider.APPLE -> { - expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.apple("apple-token")) + Credentials.IdentityProvider.APPLE -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.apple("apple-token")) } - RealmCredentials.IdentityProvider.GOOGLE -> { - expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.google("google-token")) + Credentials.IdentityProvider.GOOGLE -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.google("google-token")) } - RealmCredentials.IdentityProvider.JWT -> { - expectErrorCode(app, ErrorCode.INVALID_SESSION, RealmCredentials.jwt("jwt-token")) + Credentials.IdentityProvider.JWT -> { + expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.jwt("jwt-token")) } - RealmCredentials.IdentityProvider.UNKNOWN -> { + Credentials.IdentityProvider.UNKNOWN -> { // Ignore } } } } -} \ No newline at end of file +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index e15b7ff2e7..439520d9cf 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -21,6 +21,8 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.log.LogLevel import io.realm.log.RealmLog +import io.realm.mongodb.* +import io.realm.mongodb.auth.EmailPasswordAuth import io.realm.rule.BlockingLooperThread import org.junit.After import org.junit.Assert.assertEquals @@ -36,11 +38,11 @@ import kotlin.test.assertFailsWith class EmailPasswordAuthTests { private val looperThread = BlockingLooperThread() - private lateinit var app: TestRealmApp + private lateinit var app: TestApp private lateinit var admin: ServerAdmin // Callback use to verify that an Illegal Argument was thrown from async methods - private val checkNullArgCallback = RealmApp.Callback { result -> + private val checkNullArgCallback = App.Callback { result -> if (result.isSuccess) { fail() } else { @@ -62,7 +64,7 @@ class EmailPasswordAuthTests { @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestRealmApp() + app = TestApp() RealmLog.setLevel(LogLevel.DEBUG) admin = ServerAdmin() admin.deleteAllUsers() @@ -81,8 +83,8 @@ class EmailPasswordAuthTests { val email = TestHelper.getRandomEmail() val password = "password1234" app.emailPasswordAuth.registerUser(email, password) - val user = app.login(RealmCredentials.emailPassword(email, password)) - assertEquals(RealmUser.State.LOGGED_IN, user.state) + val user = app.login(Credentials.emailPassword(email, password)) + assertEquals(User.State.LOGGED_IN, user.state) } @Test @@ -92,8 +94,8 @@ class EmailPasswordAuthTests { looperThread.runBlocking { app.emailPasswordAuth.registerUserAsync(email, password) { result -> if (result.isSuccess) { - val user2 = app.login(RealmCredentials.emailPassword(email, password)) - assertEquals(RealmUser.State.LOGGED_IN, user2.state) + val user2 = app.login(Credentials.emailPassword(email, password)) + assertEquals(User.State.LOGGED_IN, user2.state) looperThread.testComplete() } else { fail(result.error.toString()) @@ -344,7 +346,7 @@ class EmailPasswordAuthTests { provider.registerUser(email, "123456") try { provider.callResetPasswordFunction(email, "new-password", "say-the-magic-word", 42) - val user = app.login(RealmCredentials.emailPassword(email, "new-password")) + val user = app.login(Credentials.emailPassword(email, "new-password")) user.logOut() } finally { admin.setResetFunction(enabled = false) @@ -363,7 +365,7 @@ class EmailPasswordAuthTests { "new-password", arrayOf("say-the-magic-word", 42)) { result -> if (result.isSuccess) { - val user = app.login(RealmCredentials.emailPassword(email, "new-password")) + val user = app.login(Credentials.emailPassword(email, "new-password")) user.logOut() looperThread.testComplete() } else { @@ -425,7 +427,7 @@ class EmailPasswordAuthTests { provider.callResetPasswordFunctionAsync(TestHelper.getNull(), "new-password", arrayOf(), checkNullArgCallback) } looperThread.runBlocking { - provider.callResetPasswordFunctionAsync("foo@bar.baz", io.realm.TestHelper.getNull(), arrayOf(), checkNullArgCallback) + provider.callResetPasswordFunctionAsync("foo@bar.baz", TestHelper.getNull(), arrayOf(), checkNullArgCallback) } } @@ -509,7 +511,7 @@ class EmailPasswordAuthTests { fun callAsyncMethodsOnNonLooperThreadThrows() { val provider: EmailPasswordAuth = app.emailPasswordAuth val email: String = TestHelper.getRandomEmail() - val callback = RealmApp.Callback { fail() } + val callback = App.Callback { fail() } for (method in Method.values()) { try { when(method) { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt similarity index 97% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt index 00f6da18b2..1b2ce4e44d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/functions/FunctionsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt @@ -14,12 +14,13 @@ * limitations under the License. */ -package io.realm.mongodb.functions +package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import io.realm.* import io.realm.admin.ServerAdmin +import io.realm.mongodb.* +import io.realm.mongodb.functions.Functions import io.realm.rule.BlockingLooperThread import io.realm.util.assertFailsWithErrorCode import org.bson.* @@ -56,10 +57,10 @@ class FunctionsTests { private val looperThread = BlockingLooperThread() - private lateinit var app: TestRealmApp + private lateinit var app: TestApp private lateinit var functions: Functions - private lateinit var anonUser: RealmUser + private lateinit var anonUser: User private lateinit var admin: ServerAdmin // Custom registry with support for encoding/decoding Dogs @@ -106,9 +107,9 @@ class FunctionsTests { @Before fun setup() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestRealmApp() + app = TestApp() admin = ServerAdmin() - anonUser = app.login(RealmCredentials.anonymous()) + anonUser = app.login(Credentials.anonymous()) functions = anonUser.functions } @@ -343,7 +344,7 @@ class FunctionsTests { fun asyncResultDecoder() = looperThread.runBlocking { val input = "Realm" val output = "Custom Realm" - functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(input), CustomStringDecoder(output), RealmApp.Callback { result -> + functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(input), CustomStringDecoder(output), App.Callback { result -> try { assertEquals(output, result.orThrow) } finally { @@ -436,8 +437,8 @@ class FunctionsTests { @Test fun defaultCodecRegistry() { // TODO Maybe we should test that setting configuration specific would propagate all the way - // to here, but we do not have infrastructure to easily override TestRealmApp configuration, - // and actual configuration is verified in RealmAppConfigurationTests + // to here, but we do not have infrastructure to easily override TestApp configuration, + // and actual configuration is verified in AppConfigurationTests assertEquals(app.configuration.defaultCodecRegistry, functions.defaultCodecRegistry) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt index 2ec24191ed..e9fd56bfee 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt @@ -23,6 +23,10 @@ import io.realm.kotlin.where import io.realm.kotlin.syncSession import io.realm.log.LogLevel import io.realm.log.RealmLog +import io.realm.mongodb.Credentials +import io.realm.mongodb.User +import io.realm.mongodb.close +import io.realm.mongodb.sync.* import io.realm.rule.BlockingLooperThread import org.junit.* import org.junit.Assert.* @@ -41,7 +45,7 @@ class ProgressListenerTests { } private val looperThread = BlockingLooperThread() - private lateinit var app: TestRealmApp + private lateinit var app: TestApp private lateinit var realm: Realm private lateinit var partitionValue: String @@ -50,7 +54,7 @@ class ProgressListenerTests { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) RealmLog.setLevel(LogLevel.TRACE) partitionValue = UUID.randomUUID().toString() - app = TestRealmApp() + app = TestApp() } @After @@ -68,10 +72,10 @@ class ProgressListenerTests { @Test fun downloadProgressListener_changesOnly() { val allChangesDownloaded = CountDownLatch(1) - val user1: RealmUser = app.login(RealmCredentials.anonymous()) + val user1: User = app.login(Credentials.anonymous()) val user1Config = createSyncConfig(user1) createRemoteData(user1Config) - val user2: RealmUser = app.login(RealmCredentials.anonymous()) + val user2: User = app.login(Credentials.anonymous()) val user2Config = createSyncConfig(user2) val realm = Realm.getInstance(user2Config) val session: SyncSession = realm.syncSession @@ -92,7 +96,7 @@ class ProgressListenerTests { val transferCompleted = AtomicInteger(0) val allChangesDownloaded = CountDownLatch(1) val startWorker = CountDownLatch(1) - val user1: RealmUser = app.login(RealmCredentials.anonymous()) + val user1: User = app.login(Credentials.anonymous()) val user1Config: SyncConfiguration = createSyncConfig(user1) // Create worker thread that puts data into another Realm. @@ -102,7 +106,7 @@ class ProgressListenerTests { createRemoteData(user1Config) }) worker.start() - val user2: RealmUser = app.login(RealmCredentials.anonymous()) + val user2: User = app.login(Credentials.anonymous()) val user2Config: SyncConfiguration = createSyncConfig(user2) val user2Realm = Realm.getInstance(user2Config) val session: SyncSession = user2Realm.syncSession @@ -344,7 +348,7 @@ class ProgressListenerTests { return objectCounts } - private fun createSyncConfig(user: RealmUser = app.login(RealmCredentials.anonymous()), partitionValue: String = getTestPartitionValue()): SyncConfiguration { + private fun createSyncConfig(user: User = app.login(Credentials.anonymous()), partitionValue: String = getTestPartitionValue()): SyncConfiguration { return SyncConfiguration.Builder(user, partitionValue) .modules(DefaultSyncSchema()) .build() @@ -356,4 +360,4 @@ class ProgressListenerTests { } return partitionValue } -} \ No newline at end of file +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt index 47f7299bd1..ace5426cab 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt @@ -16,8 +16,10 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.realm.SyncTestUtils.Companion.createTestUser +import io.realm.mongodb.SyncTestUtils.Companion.createTestUser import io.realm.entities.StringOnly +import io.realm.mongodb.close +import io.realm.mongodb.sync.SyncConfiguration import io.realm.util.assertFailsWith import junit.framework.Assert.* import junit.framework.TestCase @@ -38,11 +40,11 @@ class SchemaTests { val errorCollector = ErrorCollector() private lateinit var config: SyncConfiguration - private lateinit var app: TestRealmApp + private lateinit var app: TestApp @Before fun setUp() { - app = TestRealmApp() + app = TestApp() val user = createTestUser(app) config = configFactory.createSyncConfigurationBuilder(user).build() } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt index 4d1b63393a..276744468b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt @@ -16,7 +16,7 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.realm.SyncTestUtils.Companion.createTestUser +import io.realm.mongodb.SyncTestUtils.Companion.createTestUser import io.realm.entities.IndexedFields import io.realm.entities.PrimaryKeyAsString import io.realm.entities.StringOnly @@ -24,6 +24,8 @@ import io.realm.internal.OsObjectSchemaInfo import io.realm.internal.OsRealmConfig import io.realm.internal.OsSchemaInfo import io.realm.internal.OsSharedRealm +import io.realm.mongodb.close +import io.realm.mongodb.sync.testSchema import io.realm.util.assertFailsWithMessage import org.hamcrest.CoreMatchers import org.junit.* @@ -40,11 +42,11 @@ class SyncedRealmMigrationTests { @get:Rule val configFactory = TestSyncConfigurationFactory() - private lateinit var app: TestRealmApp + private lateinit var app: TestApp @Before fun setUp() { - app = TestRealmApp() + app = TestApp() } @After @@ -67,7 +69,7 @@ class SyncedRealmMigrationTests { @Test fun addField_worksWithMigrationError() { val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) - .schema(StringOnly::class.java) + .testSchema(StringOnly::class.java) .build() // Setup initial Realm schema (with missing fields) @@ -90,7 +92,7 @@ class SyncedRealmMigrationTests { @Test fun missingFields_hiddenSilently() { val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) - .schema(StringOnly::class.java) + .testSchema(StringOnly::class.java) .build() // Setup initial Realm schema (with too many fields) @@ -120,7 +122,7 @@ class SyncedRealmMigrationTests { @Test fun breakingSchemaChange_throws() { val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) - .schema(PrimaryKeyAsString::class.java) + .testSchema(PrimaryKeyAsString::class.java) .build() // Setup initial Realm schema (with a different primary key) @@ -142,7 +144,7 @@ class SyncedRealmMigrationTests { @Test fun sameSchemaVersion_doNotRebuildIndexes() { val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) - .schema(IndexedFields::class.java) + .testSchema(IndexedFields::class.java) .schemaVersion(42) .build() @@ -171,7 +173,7 @@ class SyncedRealmMigrationTests { @Test fun differentSchemaVersions_rebuildIndexes() { val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) - .schema(IndexedFields::class.java) + .testSchema(IndexedFields::class.java) .schemaVersion(42) .build() @@ -187,7 +189,7 @@ class SyncedRealmMigrationTests { } } - Realm.getInstance(config).use {realm -> + Realm.getInstance(config).use { realm -> // Opening at different schema version (42) should rebuild indexes val indexedFieldsSchema = realm.schema[className]!! assertNotNull(indexedFieldsSchema) @@ -200,7 +202,7 @@ class SyncedRealmMigrationTests { @Test fun addingFields_rebuildIndexes() { val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) - .schema(IndexedFields::class.java) + .testSchema(IndexedFields::class.java) .schemaVersion(42) .build() @@ -251,7 +253,7 @@ class SyncedRealmMigrationTests { fun moreFieldsThanExpectedIsAllowed() { val config = configFactory .createSyncConfigurationBuilder(createTestUser(app)) - .schema(StringOnly::class.java) + .testSchema(StringOnly::class.java) .build() // Initialize schema @@ -269,13 +271,4 @@ class SyncedRealmMigrationTests { Realm.getInstance(config).close() } - companion object { - @BeforeClass - fun beforeClass() { - // another Test class may have the BaseRealm.applicationContext set but - // the SyncManager reset. This will make assertion to fail, we need to re-initialise - // the sync_manager.cpp#m_file_manager (configFactory rule do this) - BaseRealm.applicationContext = null - } - } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt similarity index 71% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt index 670e960733..833cd5870b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmUserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt @@ -18,6 +18,8 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin +import io.realm.mongodb.* +import io.realm.mongodb.auth.ApiKeyAuth import io.realm.rule.BlockingLooperThread import org.junit.After import org.junit.Assert.* @@ -28,20 +30,20 @@ import org.junit.runner.RunWith import java.lang.IllegalArgumentException @RunWith(AndroidJUnit4::class) -class RealmUserTests { +class UserTests { val looperThread = BlockingLooperThread() - private lateinit var app: RealmApp - private lateinit var anonUser: RealmUser + private lateinit var app: App + private lateinit var anonUser: User private lateinit var admin: ServerAdmin @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestRealmApp() + app = TestApp() admin = ServerAdmin() - anonUser = app.login(RealmCredentials.anonymous()) + anonUser = app.login(Credentials.anonymous()) } @After @@ -58,19 +60,19 @@ class RealmUserTests { @Test fun getState_anonymousUser() { - assertEquals(RealmUser.State.LOGGED_IN, anonUser.state) + assertEquals(User.State.LOGGED_IN, anonUser.state) anonUser.logOut() - assertEquals(RealmUser.State.REMOVED, anonUser.state) + assertEquals(User.State.REMOVED, anonUser.state) } @Test fun getState_emailUser() { val emailUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(RealmUser.State.LOGGED_IN, emailUser.state) + assertEquals(User.State.LOGGED_IN, emailUser.state) emailUser.logOut() - assertEquals(RealmUser.State.LOGGED_OUT, emailUser.state) + assertEquals(User.State.LOGGED_OUT, emailUser.state) emailUser.remove() - assertEquals(RealmUser.State.REMOVED, emailUser.state) + assertEquals(User.State.REMOVED, emailUser.state) } @Test @@ -78,17 +80,17 @@ class RealmUserTests { anonUser.logOut(); // Remove user created for other tests // Anonymous users are removed upon log out - val user1: RealmUser = app.login(RealmCredentials.anonymous()) + val user1: User = app.login(Credentials.anonymous()) assertEquals(user1, app.currentUser()) user1.logOut() - assertEquals(RealmUser.State.REMOVED, user1.state) + assertEquals(User.State.REMOVED, user1.state) assertNull(app.currentUser()) // Users registered with Email/Password will register as Logged Out - val user2: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val user2: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertEquals(user2, app.currentUser()) user2.logOut() - assertEquals(RealmUser.State.LOGGED_OUT, user2.state) + assertEquals(User.State.LOGGED_OUT, user2.state) assertNull(app.currentUser()) } @@ -96,18 +98,18 @@ class RealmUserTests { fun logOutAsync() = looperThread.runBlocking { assertEquals(anonUser, app.currentUser()) anonUser.logOutAsync() { result -> - val callbackUser: RealmUser = result.orThrow + val callbackUser: User = result.orThrow assertNull(app.currentUser()) assertEquals(anonUser, callbackUser) - assertEquals(RealmUser.State.REMOVED, anonUser.state) - assertEquals(RealmUser.State.REMOVED, callbackUser.state) + assertEquals(User.State.REMOVED, anonUser.state) + assertEquals(User.State.REMOVED, callbackUser.state) looperThread.testComplete() } } @Test fun logOutAsync_throwsOnNonLooperThread() { - val user: RealmUser = app.login(RealmCredentials.anonymous()) + val user: User = app.login(Credentials.anonymous()) try { user.logOutAsync { fail() } fail() @@ -119,25 +121,25 @@ class RealmUserTests { @Test fun linkUser() { admin.setAutomaticConfirmation(enabled = false) - val anonUser: RealmUser = app.login(RealmCredentials.anonymous()) + val anonUser: User = app.login(Credentials.anonymous()) assertEquals(1, anonUser.identities.size) val email = TestHelper.getRandomEmail() val password = "123456" app.emailPasswordAuth.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled - var linkedUser: RealmUser = anonUser.linkCredentials(RealmCredentials.emailPassword(email, password)) + var linkedUser: User = anonUser.linkCredentials(Credentials.emailPassword(email, password)) assertTrue(anonUser === linkedUser) assertEquals(2, linkedUser.identities.size) - assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) + assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) admin.setAutomaticConfirmation(enabled = true) val otherEmail = TestHelper.getRandomEmail() val otherPassword = "123456" app.emailPasswordAuth.registerUser(otherEmail, otherPassword) - linkedUser = anonUser.linkCredentials(RealmCredentials.emailPassword(email, password)) + linkedUser = anonUser.linkCredentials(Credentials.emailPassword(email, password)) assertTrue(anonUser === linkedUser) assertEquals(3, linkedUser.identities.size) - assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[2].provider) + assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[2].provider) admin.setAutomaticConfirmation(enabled = true) } @@ -146,10 +148,10 @@ class RealmUserTests { fun linkUser_existingCredentialsThrows() { val email = TestHelper.getRandomEmail() val password = "123456" - val emailUser: RealmUser = app.registerUserAndLogin(email, password) - val anonymousUser: RealmUser = app.login(RealmCredentials.anonymous()) + val emailUser: User = app.registerUserAndLogin(email, password) + val anonymousUser: User = app.login(Credentials.anonymous()) try { - anonymousUser.linkCredentials(RealmCredentials.emailPassword(email, password)) + anonymousUser.linkCredentials(Credentials.emailPassword(email, password)) fail() } catch (ex: ObjectServerError) { assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) @@ -170,17 +172,17 @@ class RealmUserTests { @Test fun linkUserAsync() { admin.setAutomaticConfirmation(enabled = false) - val user: RealmUser = app.login(RealmCredentials.anonymous()) + val user: User = app.login(Credentials.anonymous()) assertEquals(1, user.identities.size) val email = TestHelper.getRandomEmail() val password = "123456" app.emailPasswordAuth.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled looperThread.runBlocking { - anonUser.linkCredentialsAsync(RealmCredentials.emailPassword(email, password)) { result -> - val linkedUser: RealmUser = result.orThrow + anonUser.linkCredentialsAsync(Credentials.emailPassword(email, password)) { result -> + val linkedUser: User = result.orThrow assertTrue(user === linkedUser) assertEquals(2, linkedUser.identities.size) - assertEquals(RealmCredentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) + assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) admin.setAutomaticConfirmation(enabled = true) } } @@ -190,7 +192,7 @@ class RealmUserTests { @Test fun linkUserAsync_throwsOnNonLooperThread() { try { - anonUser.linkCredentialsAsync(RealmCredentials.emailPassword(TestHelper.getRandomEmail(), "123456")) { fail() } + anonUser.linkCredentialsAsync(Credentials.emailPassword(TestHelper.getRandomEmail(), "123456")) { fail() } fail() } catch (ignore: java.lang.IllegalStateException) { } @@ -205,7 +207,7 @@ class RealmUserTests { assertEquals(user1, app.currentUser()) assertEquals(1, app.allUsers().size) user1.remove() - assertEquals(RealmUser.State.REMOVED, user1.state) + assertEquals(User.State.REMOVED, user1.state) assertNull(app.currentUser()) assertEquals(0, app.allUsers().size) @@ -215,7 +217,7 @@ class RealmUserTests { assertNull(app.currentUser()) assertEquals(1, app.allUsers().size) user2.remove() - assertEquals(RealmUser.State.REMOVED, user2.state) + assertEquals(User.State.REMOVED, user2.state) assertEquals(0, app.allUsers().size) } @@ -229,7 +231,7 @@ class RealmUserTests { assertEquals(user, app.currentUser()) assertEquals(1, app.allUsers().size) user.removeAsync { result -> - assertEquals(RealmUser.State.REMOVED, result.orThrow.state) + assertEquals(User.State.REMOVED, result.orThrow.state) assertNull(app.currentUser()) assertEquals(0, app.allUsers().size) looperThread.testComplete() @@ -243,7 +245,7 @@ class RealmUserTests { assertNull(app.currentUser()) assertEquals(1, app.allUsers().size) user.removeAsync { result -> - assertEquals(RealmUser.State.REMOVED, result.orThrow.state) + assertEquals(User.State.REMOVED, result.orThrow.state) assertEquals(0, app.allUsers().size) looperThread.testComplete() } @@ -252,7 +254,7 @@ class RealmUserTests { @Test fun removeUserAsync_nonLooperThreadThrows() { - val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "1234567") + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "1234567") try { user.removeAsync { fail() } } catch (ignore: IllegalStateException) { @@ -261,7 +263,7 @@ class RealmUserTests { @Test fun getApiKeyAuthProvider() { - val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") val provider1: ApiKeyAuth = user.apiKeyAuth assertEquals(user, provider1.user) @@ -277,32 +279,32 @@ class RealmUserTests { @Test fun getDeviceId() { // TODO No reason to integration test this. Use a stubbed response instead. - val user: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertTrue(user.deviceId.isNotEmpty() && user.deviceId.length == 24) // Server returns a UUID } @Test fun equals() { // TODO Could be that we could use a fake user - val user: RealmUser = app.registerUserAndLogin("user1@example.com", "123456") + val user: User = app.registerUserAndLogin("user1@example.com", "123456") assertEquals(user, user) assertNotEquals(user, app) user.logOut() - val sameUserNewLogin = app.login(RealmCredentials.emailPassword(user.email!!, "123456")) + val sameUserNewLogin = app.login(Credentials.emailPassword(user.email!!, "123456")) // Verify that it is not same object but uses underlying OSSyncUser equality on identity assertFalse(user === sameUserNewLogin) assertEquals(user, sameUserNewLogin) - val differentUser: RealmUser = app.registerUserAndLogin("user2@example.com", "123456") + val differentUser: User = app.registerUserAndLogin("user2@example.com", "123456") assertNotEquals(user, differentUser) } @Test fun hashCode_user() { - val user: RealmUser = app.registerUserAndLogin("user1@example.com", "123456") + val user: User = app.registerUserAndLogin("user1@example.com", "123456") user.logOut() - val sameUserNewLogin = app.login(RealmCredentials.emailPassword(user.email!!, "123456")) + val sameUserNewLogin = app.login(Credentials.emailPassword(user.email!!, "123456")) // Verify that two equal users also returns same hashCode assertFalse(user === sameUserNewLogin) assertEquals(user.hashCode(), sameUserNewLogin.hashCode()) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt similarity index 71% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt index ab8b670921..f73cef3e9c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/RealmAppExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt @@ -13,9 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm +package io.realm.mongodb +import io.realm.RealmExt import io.realm.admin.ServerAdmin +import io.realm.mongodb.sync.testReset +import io.realm.testClearApplicationContext /** * Resets the Realm Application and delete all local state. @@ -23,18 +26,19 @@ import io.realm.admin.ServerAdmin * Trying to access any Sync or Realm App API's after this has been called has undefined * behavior. */ -fun RealmApp.close() { +fun App.close() { ServerAdmin().deleteAllUsers() - this.syncManager.reset() - RealmApp.CREATED = false - BaseRealm.applicationContext = null // Required for Realm.init() to work + this.syncManager.testReset() + this.networkTransport.resetHeaders() + App.CREATED = false + RealmExt.testClearApplicationContext() } /** * Helper function for quickly logging in test users. * This only works if users in the Realm Application are configured to be automatically confirmed. */ -fun RealmApp.registerUserAndLogin(email: String, password: String): RealmUser { +fun App.registerUserAndLogin(email: String, password: String): User { emailPasswordAuth.registerUser(email, password) - return login(RealmCredentials.emailPassword(email, password)) + return login(Credentials.emailPassword(email, password)) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt index 3bb7c81483..d2b0d55c9f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt @@ -43,15 +43,15 @@ private const val VALUE_1 = "666" @RunWith(AndroidJUnit4::class) class MongoCollectionTest { - private lateinit var app: TestRealmApp - private lateinit var user: RealmUser + private lateinit var app: TestApp + private lateinit var user: User private lateinit var client: MongoClient private lateinit var database: MongoDatabase @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestRealmApp() + app = TestApp() user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") client = user.getMongoClient(SERVICE_NAME) database = client.getDatabase(DATABASE_NAME) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/ProgressTests.kt similarity index 98% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/ProgressTests.kt index a5d0fa0e86..dd6cd2d30e 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/ProgressTests.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm +package io.realm.mongodb.sync import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.assertEquals diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SessionTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt similarity index 93% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SessionTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt index 6daa597eb1..07c9d8c1fb 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SessionTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm +package io.realm.mongodb.sync import androidx.test.annotation.UiThreadTest import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import io.realm.* import io.realm.TestHelper.TestLogger +import io.realm.entities.DefaultSyncSchema import io.realm.entities.StringOnly import io.realm.entities.StringOnlyModule import io.realm.exceptions.RealmFileException import io.realm.exceptions.RealmMigrationNeededException import io.realm.kotlin.syncSession +import io.realm.log.LogLevel import io.realm.log.RealmLog +import io.realm.mongodb.* import io.realm.rule.BlockingLooperThread import io.realm.util.ResourceContainer import io.realm.util.assertFailsWithMessage @@ -41,8 +45,8 @@ import kotlin.test.* @RunWith(AndroidJUnit4::class) class SessionTests { private lateinit var configuration: SyncConfiguration - private lateinit var app: TestRealmApp - private lateinit var user: RealmUser + private lateinit var app: TestApp + private lateinit var user: User @get:Rule val configFactory = TestSyncConfigurationFactory() @@ -52,12 +56,14 @@ class SessionTests { @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestRealmApp() + app = TestApp() // TODO We could potentially work without a fully functioning user to speed up tests, but // seems like the old way of "faking" it, does now work for now, so using a real user. // user = SyncTestUtils.createTestUser(app) user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - configuration = SyncConfiguration.defaultConfig(user, "default") + configuration = SyncConfiguration.Builder(user, "default") + .modules(DefaultSyncSchema()) + .build() } @After @@ -201,7 +207,7 @@ class SessionTests { assertTrue(handler.backupFile.exists()) val backupRealmConfiguration = handler.backupRealmConfiguration assertNotNull(backupRealmConfiguration) - assertFalse(backupRealmConfiguration.isSyncConfiguration) + assertFalse(backupRealmConfiguration is SyncConfiguration) assertTrue(backupRealmConfiguration.isRecoveryConfiguration) Realm.getInstance(backupRealmConfiguration).use { backupRealm -> assertFalse(backupRealm.isEmpty) @@ -211,7 +217,7 @@ class SessionTests { // opening a Dynamic Realm should also work DynamicRealm.getInstance(backupRealmConfiguration).use { dynamicRealm -> - dynamicRealm.schema.checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME) + assertNotNull(dynamicRealm.schema.get(StringOnly.CLASS_NAME)) val all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll() assertEquals(1, all.size.toLong()) assertEquals("Foo", all.first()!!.getString(StringOnly.FIELD_CHARS)) @@ -262,7 +268,7 @@ class SessionTests { // opening a DynamicRealm will work though DynamicRealm.getInstance(backupRealmConfiguration).use { dynamicRealm -> - dynamicRealm.schema.checkHasTable(StringOnly.CLASS_NAME, "Dynamic Realm should contains " + StringOnly.CLASS_NAME) + assertNotNull(dynamicRealm.schema.get(StringOnly.CLASS_NAME)) val all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll() assertEquals(1, all.size.toLong()) assertEquals("Foo", all.first()!!.getString(StringOnly.FIELD_CHARS)) @@ -391,14 +397,15 @@ class SessionTests { fun uploadAllLocalChanges_returnFalseWhenTimedOut() { Realm.getInstance(configuration).use { realm -> val session = realm.syncSession - assertFalse(session.uploadAllLocalChanges(100, TimeUnit.MILLISECONDS)) + // We never assume to be able to download changes with one 1ms + assertFalse(session.uploadAllLocalChanges(1, TimeUnit.MILLISECONDS)) } } @Test @UiThreadTest fun downloadAllServerChanges_throwsOnUiThread() { - Realm.getInstance(configuration).use {realm -> + Realm.getInstance(configuration).use { realm -> assertFailsWith { realm.syncSession.downloadAllServerChanges() } @@ -432,7 +439,7 @@ class SessionTests { fun downloadAllServerChanges_returnFalseWhenTimedOut() { Realm.getInstance(configuration).use { realm -> val session = realm.syncSession - // We never assume to be able to download changes with one 1ms + // We never assume to be able to download changes within one 1ms assertFalse(session.downloadAllServerChanges(1, TimeUnit.MILLISECONDS)) } } @@ -451,10 +458,17 @@ class SessionTests { Realm.getInstance(configuration).use { realm -> val session = realm.syncSession + // TODO This test requires errors to be reported, when running full test suite + // some test running before leaves it at FATAL. Do we have conventions about it? For + // now just lowering while triggering the actual test + val level = RealmLog.getLevel() + RealmLog.setLevel(LogLevel.WARN) val testLogger = TestLogger() RealmLog.add(testLogger) session.notifySessionError("unknown", 3, "Unknown Error") RealmLog.remove(testLogger) + // TODO See comment above + RealmLog.setLevel(level) assertTrue(errorHandlerCalled.get()) assertEquals("Unknown error code: 'unknown:3'", testLogger.message) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt similarity index 84% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index 5f2d69e4d2..a90965b22a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -13,15 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm +package io.realm.mongodb.sync import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import io.realm.SyncTestUtils.Companion.createTestUser +import io.realm.* +import io.realm.mongodb.SyncTestUtils.Companion.createTestUser import io.realm.entities.StringOnly import io.realm.entities.StringOnlyModule import io.realm.kotlin.createObject import io.realm.kotlin.where +import io.realm.mongodb.ObjectServerError +import io.realm.mongodb.User +import io.realm.mongodb.close +import io.realm.mongodb.registerUserAndLogin import org.junit.After import org.junit.Before import org.junit.Rule @@ -39,12 +44,12 @@ class SyncConfigurationTests { @get:Rule val configFactory = TestSyncConfigurationFactory() - private lateinit var app: TestRealmApp + private lateinit var app: TestApp @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestRealmApp() + app = TestApp() } @After @@ -66,28 +71,28 @@ class SyncConfigurationTests { @Test fun errorHandler_fromSyncManager() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) assertEquals(app.configuration.defaultErrorHandler, config.errorHandler) } @Test fun errorHandler_nullThrows() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) assertFailsWith { builder.errorHandler(TestHelper.getNull()) } } @Test fun equals() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) assertTrue(config == config) } @Test fun equals_same() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config1: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION).build() val config2: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION).build() assertTrue(config1 == config2) @@ -95,8 +100,8 @@ class SyncConfigurationTests { @Test fun equals_not() { - val user1: RealmUser = createTestUser(app) - val user2: RealmUser = createTestUser(app) + val user1: User = createTestUser(app) + val user2: User = createTestUser(app) val config1: SyncConfiguration = SyncConfiguration.Builder(user1, DEFAULT_PARTITION).build() val config2: SyncConfiguration = SyncConfiguration.Builder(user2, DEFAULT_PARTITION).build() assertFalse(config1 == config2) @@ -104,15 +109,15 @@ class SyncConfigurationTests { @Test fun hashCode_equal() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) assertEquals(config.hashCode(), config.hashCode()) } @Test fun hashCode_notEquals() { - val user1: RealmUser = createTestUser(app) - val user2: RealmUser = createTestUser(app) + val user1: User = createTestUser(app) + val user2: User = createTestUser(app) val config1: SyncConfiguration = SyncConfiguration.defaultConfig(user1, DEFAULT_PARTITION) val config2: SyncConfiguration = SyncConfiguration.defaultConfig(user2, DEFAULT_PARTITION) assertNotEquals(config1.hashCode(), config2.hashCode()) @@ -120,7 +125,7 @@ class SyncConfigurationTests { @Test fun get_syncSpecificValues() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) assertTrue(user == config.user) assertEquals("ws://127.0.0.1:9090/", config.serverUrl.toString()) // FIXME: Figure out exactly what to return here @@ -130,7 +135,7 @@ class SyncConfigurationTests { @Test fun encryption() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION) .encryptionKey(TestHelper.getRandomKey()) .build() @@ -139,21 +144,21 @@ class SyncConfigurationTests { @Test fun encryption_invalid_null() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) assertFailsWith { builder.encryptionKey(TestHelper.getNull()) } } @Test fun encryption_invalid_wrong_length() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) assertFailsWith { builder.encryptionKey(byteArrayOf(1, 2, 3)) } } @Test fun initialData() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config = configFactory.createSyncConfigurationBuilder(user) .schema(StringOnly::class.java) .initialData(object : Realm.Transaction { @@ -163,6 +168,7 @@ class SyncConfigurationTests { } }) .build() + config assertNotNull(config.initialDataTransaction) // open the first time - initialData must be triggered @@ -180,14 +186,14 @@ class SyncConfigurationTests { @Test fun defaultRxFactory() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) assertNotNull(config.rxFactory) } @Test fun toString_nonEmpty() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) val configStr = config.toString() assertTrue(configStr.isNotEmpty()) @@ -197,8 +203,8 @@ class SyncConfigurationTests { // own copy on the filesystem. This is e.g. what happens if a Realm is shared using a PermissionOffer. @Test fun multipleUsersReferenceSameRealm() { - val user1: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - val user2: RealmUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val user1: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val user2: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") val config1: SyncConfiguration = SyncConfiguration.Builder(user1, DEFAULT_PARTITION) .modules(StringOnlyModule()) @@ -221,14 +227,15 @@ class SyncConfigurationTests { @Test fun defaultConfiguration_throwsIfNotLoggedIn() { - val user: RealmUser = createTestUser(app) - user.osUser.invalidate() + // TODO Maybe we could avoid registering a real user + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + user.logOut() assertFailsWith { SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) } } @Test fun clientResyncMode() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) // Default mode for full Realms var config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) @@ -243,7 +250,7 @@ class SyncConfigurationTests { @Test fun clientResyncMode_throwsOnNull() { - val user: RealmUser = createTestUser(app) + val user: User = createTestUser(app) val config: SyncConfiguration.Builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) try { config.clientResyncMode(TestHelper.getNull()) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncExt.kt new file mode 100644 index 0000000000..41e46490c8 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncExt.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.sync + +// Helper to expose package protected methods for testing purpose +fun Sync.testReset() { + this.reset() +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt similarity index 88% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index 8daa69c7b5..a9d9ba69d3 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -13,16 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm +package io.realm.mongodb.sync import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import io.realm.SyncTestUtils.Companion.createTestUser +import io.realm.Realm +import io.realm.mongodb.SyncTestUtils.Companion.createTestUser +import io.realm.TestApp +import io.realm.TestHelper +import io.realm.TestSyncConfigurationFactory import io.realm.entities.* import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.LogLevel import io.realm.log.RealmLog +import io.realm.mongodb.App +import io.realm.mongodb.Credentials +import io.realm.mongodb.User +import io.realm.mongodb.close import org.junit.* import org.junit.Assert.* import org.junit.runner.RunWith @@ -38,14 +46,14 @@ class SyncedRealmTests { @get:Rule val configFactory = TestSyncConfigurationFactory() - private lateinit var app: RealmApp + private lateinit var app: App private lateinit var partitionValue: String @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) RealmLog.setLevel(LogLevel.TRACE) - app = TestRealmApp() + app = TestApp() partitionValue = UUID.randomUUID().toString() } @@ -60,7 +68,7 @@ class SyncedRealmTests { // Smoke test for Sync. Waiting for working Sync support. @Test fun connectWithInitialSchema() { - val user: RealmUser = createNewUser() + val user: User = createNewUser() val config = createDefaultConfig(user) Realm.getInstance(config).use { realm -> with(realm.syncSession) { @@ -75,7 +83,7 @@ class SyncedRealmTests { @Test fun roundTripObjectsNotInServerSchemaObject() { // User 1 creates an object an uploads it to MongoDB Realm - val user1: RealmUser = createNewUser() + val user1: User = createNewUser() val config1: SyncConfiguration = createCustomConfig(user1, partitionValue) Realm.getInstance(config1).use { realm -> realm.executeTransaction { @@ -88,7 +96,7 @@ class SyncedRealmTests { } // User 2 logs and using the same partition key should see the object - val user2: RealmUser = createNewUser() + val user2: User = createNewUser() val config2 = createCustomConfig(user2, partitionValue) Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() @@ -102,7 +110,7 @@ class SyncedRealmTests { @Test fun roundTripSimpleObjectsInServerSchema() { // User 1 creates an object an uploads it to MongoDB Realm - val user1: RealmUser = createNewUser() + val user1: User = createNewUser() val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) Realm.getInstance(config1).use { realm -> realm.executeTransaction { @@ -123,7 +131,7 @@ class SyncedRealmTests { } // User 2 logs and using the same partition key should see the object - val user2: RealmUser = createNewUser() + val user2: User = createNewUser() val config2 = createDefaultConfig(user2, partitionValue) Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() @@ -138,7 +146,7 @@ class SyncedRealmTests { @Test fun roundTripObjectsWithLists() { // User 1 creates an object an uploads it to MongoDB Realm - val user1: RealmUser = createNewUser() + val user1: User = createNewUser() val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) Realm.getInstance(config1).use { realm -> realm.executeTransaction { @@ -159,7 +167,7 @@ class SyncedRealmTests { } // User 2 logs and using the same partition key should see the object - val user2: RealmUser = createNewUser() + val user2: User = createNewUser() val config2 = createDefaultConfig(user2, partitionValue) Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() @@ -171,7 +179,7 @@ class SyncedRealmTests { @Test fun session() { - val user: RealmUser = app.login(RealmCredentials.anonymous()) + val user: User = app.login(Credentials.anonymous()) Realm.getInstance(createDefaultConfig(user)).use { realm -> assertNotNull(realm.syncSession) assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) @@ -224,23 +232,23 @@ class SyncedRealmTests { } } - private fun createDefaultConfig(user: RealmUser, partitionValue: String = defaultPartitionValue): SyncConfiguration { + private fun createDefaultConfig(user: User, partitionValue: String = defaultPartitionValue): SyncConfiguration { return SyncConfiguration.Builder(user, partitionValue) .modules(DefaultSyncSchema()) .build() } - private fun createCustomConfig(user: RealmUser, partitionValue: String = defaultPartitionValue): SyncConfiguration { + private fun createCustomConfig(user: User, partitionValue: String = defaultPartitionValue): SyncConfiguration { return SyncConfiguration.Builder(user, partitionValue) .schema(SyncColor::class.java) .build() } - private fun createNewUser(): RealmUser { + private fun createNewUser(): User { val email = TestHelper.getRandomEmail() val password = "123456" app.emailPasswordAuth.registerUser(email, password) - return app.login(RealmCredentials.emailPassword(email, password)) + return app.login(Credentials.emailPassword(email, password)) } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt index 22c18336c4..1ca87d2ca4 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -19,6 +19,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.* import io.realm.internal.objectstore.OsJavaNetworkTransport +import io.realm.mongodb.* import org.junit.After import org.junit.Assert.* import org.junit.Before @@ -36,7 +37,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class OsJavaNetworkTransportTests { - private lateinit var app: RealmApp + private lateinit var app: App private val successHeaders: Map = mapOf(Pair("Content-Type", "application/json")) @Before @@ -54,10 +55,10 @@ class OsJavaNetworkTransportTests { // Test that the round trip works in case of a successful HTTP request. @Test fun requestSuccess() { - app = TestRealmApp(object: OsJavaNetworkTransport() { + app = TestApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { var result = "" - if (url.endsWith("/providers/${RealmCredentials.IdentityProvider.ANONYMOUS.id}/login")) { + if (url.endsWith("/providers/${Credentials.IdentityProvider.ANONYMOUS.id}/login")) { result = """ { "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODM5NjcyMDgsImlhdCI6MTU4Mzk2NTQwOCwiaXNzIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWEzIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWU2OTYzZGVhZmVhNjMyNTQ1ODFjMDI1Iiwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoiYWNjZXNzIn0.J4mp8LnlsxTQRV_7W2Er4qY0tptR76PJGG1k6HSMmUYqgfpJC2Fnbcf1VCoebzoNolH2-sr8AHDVBBCyjxRjqoY9OudFHmWZKmhDV1ysxPP4XmID0nUuN45qJSO8QEAqoOmP1crXjrUZWedFw8aaCZE-bxYfvcDHyjBcbNKZqzawwUw2PyTOlrNjgs01k2J4o5a5XzYkEsJuzr4_8UqKW6zXvYj24UtqnqoYatW5EzpX63m2qig8AcBwPK4ZHb5wEEUdf4QZxkRY5QmTgRHP8SSqVUB_mkHgKaizC_tSB3E0BekaDfLyWVC1taAstXJNfzgFtLI86AzuXS2dCiCfqQ", @@ -108,8 +109,8 @@ class OsJavaNetworkTransportTests { } }) - val creds = RealmCredentials.anonymous() - val user: RealmUser = app.login(creds) + val creds = Credentials.anonymous() + val user: User = app.login(creds) assertNotNull(user) } @@ -117,7 +118,7 @@ class OsJavaNetworkTransportTests { // to the user as an exception. @Test fun requestFailWithServerError() { - app = TestRealmApp(object: OsJavaNetworkTransport() { + app = TestApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { val result = """ { @@ -130,7 +131,7 @@ class OsJavaNetworkTransportTests { } }) - val creds = RealmCredentials.emailPassword("foo", "bar") + val creds = Credentials.emailPassword("foo", "bar") try { app.login(creds) fail() @@ -144,13 +145,13 @@ class OsJavaNetworkTransportTests { // to the user. @Test fun requestFailWithHttpError() { - app = TestRealmApp(object: OsJavaNetworkTransport() { + app = TestApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { return Response.httpResponse(500, mapOf(), "Boom!") } }) - val creds = RealmCredentials.anonymous() + val creds = Credentials.anonymous() try { app.login(creds) fail() @@ -163,13 +164,13 @@ class OsJavaNetworkTransportTests { // Test that custom error codes thrown from the Java transport are correctly reported back to the user. @Test fun requestFailWithCustomError() { - app = TestRealmApp(object: OsJavaNetworkTransport() { + app = TestApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { return Response.ioError("Boom!") } }) - val creds = RealmCredentials.anonymous() + val creds = Credentials.anonymous() try { app.login(creds) fail() @@ -184,13 +185,13 @@ class OsJavaNetworkTransportTests { // to the user. @Test fun requestFailWithTransportException() { - app = TestRealmApp(object: OsJavaNetworkTransport() { + app = TestApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { throw IllegalStateException("Boom!") } }) - val creds = RealmCredentials.anonymous() + val creds = Credentials.anonymous() try { app.login(creds) fail() @@ -202,13 +203,13 @@ class OsJavaNetworkTransportTests { // Test that if the Java transport throws a fatal error it is correctly returned to the user. @Test fun requestFailWithTransportError() { - app = TestRealmApp(object: OsJavaNetworkTransport() { + app = TestApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { throw Error("Boom!") } }) - val creds = RealmCredentials.anonymous() + val creds = Credentials.anonymous() try { app.login(creds) fail() diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt index cd6710b806..ede4c5d1c9 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt @@ -1,8 +1,8 @@ package io.realm.util import android.util.ArraySet -import io.realm.ErrorCode -import io.realm.ObjectServerError +import io.realm.mongodb.ErrorCode +import io.realm.mongodb.ObjectServerError import org.hamcrest.Matcher import org.junit.Assert.* import org.junit.rules.ErrorCollector diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 56bc9f1fc5..c7164a6730 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -102,14 +102,14 @@ set(classes_LIST set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) if (build_SYNC) list(APPEND classes_LIST - io.realm.ClientResetRequiredError - io.realm.EmailPasswordAuth - io.realm.ApiKeyAuth - io.realm.RealmApp - io.realm.RealmUser - io.realm.RealmSync - io.realm.FunctionsImpl - io.realm.SyncSession + io.realm.mongodb.App + io.realm.mongodb.ApiKeyAuthImpl + io.realm.mongodb.EmailPasswordAuthImpl + io.realm.mongodb.FunctionsImpl + io.realm.mongodb.sync.ClientResetRequiredError + io.realm.mongodb.sync.Sync + io.realm.mongodb.sync.SyncSession + io.realm.mongodb.User io.realm.internal.objectstore.OsAppCredentials io.realm.internal.objectstore.OsAsyncOpenTask io.realm.internal.objectstore.OsJavaNetworkTransport @@ -197,14 +197,14 @@ file(GLOB jni_SRC # Those source file are only needed for sync. if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmApp.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmUser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_App.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_User.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_FunctionsImpl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_EmailPasswordAuth.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ApiKeyAuth.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_ClientResetRequiredError.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_RealmSync.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_SyncSession.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_EmailPasswordAuthImpl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_ApiKeyAuthImpl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_sync_ClientResetRequiredError.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_sync_Sync.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_sync_SyncSession.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAsyncOpenTask.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAppCredentials.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index e21faafc51..38d3b9046a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -254,7 +254,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe REALM_ASSERT(!config.sync_config); try { - static JavaClass sync_manager_class(env, "io/realm/RealmSync"); + static JavaClass sync_manager_class(env, "io/realm/mongodb/sync/Sync"); // Doing the methods lookup from the thread that loaded the lib, to avoid // https://developer.android.com/training/articles/perf-jni.html#faq_FindClass static JavaMethod java_error_callback_method(env, sync_manager_class, "notifyErrorHandler", @@ -394,7 +394,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSyncConfigS } else if (config.sync_config->client_validate_ssl) { // set default callback to allow Android to check the certificate - static JavaClass sync_manager_class(env, "io/realm/RealmSync"); + static JavaClass sync_manager_class(env, "io/realm/mongodb/sync/Sync"); static JavaMethod java_ssl_verify_callback(env, sync_manager_class, "sslVerifyCallback", "(Ljava/lang/String;Ljava/lang/String;I)Z", true); diff --git a/realm/realm-library/src/main/cpp/io_realm_ApiKeyAuth.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_ApiKeyAuthImpl.cpp similarity index 90% rename from realm/realm-library/src/main/cpp/io_realm_ApiKeyAuth.cpp rename to realm/realm-library/src/main/cpp/io_realm_mongodb_ApiKeyAuthImpl.cpp index c0182f7a82..4122558592 100644 --- a/realm/realm-library/src/main/cpp/io_realm_ApiKeyAuth.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_ApiKeyAuthImpl.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_ApiKeyAuth.h" +#include "io_realm_mongodb_ApiKeyAuthImpl.h" #include "java_class_global_def.hpp" #include "java_network_transport.hpp" @@ -64,7 +64,7 @@ static std::function)> multi_key_m return arr; }; -JNIEXPORT void JNICALL Java_io_realm_ApiKeyAuth_nativeCallFunction(JNIEnv* env, +JNIEXPORT void JNICALL Java_io_realm_mongodb_ApiKeyAuthImpl_nativeCallFunction(JNIEnv* env, jclass, jint j_function_type, jlong j_app_ptr, @@ -77,36 +77,36 @@ JNIEXPORT void JNICALL Java_io_realm_ApiKeyAuth_nativeCallFunction(JNIEnv* env, auto user = *reinterpret_cast*>(j_user_ptr); auto client = app->provider_client(); switch(j_function_type) { - case io_realm_ApiKeyAuth_TYPE_CREATE: { + case io_realm_mongodb_ApiKeyAuthImpl_TYPE_CREATE: { JStringAccessor name(env, j_arg); auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, single_key_mapper); client.create_api_key(name, user, callback); break; } - case io_realm_ApiKeyAuth_TYPE_FETCH_SINGLE: { + case io_realm_mongodb_ApiKeyAuthImpl_TYPE_FETCH_SINGLE: { auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, single_key_mapper); std::string str_id = JStringAccessor(env, static_cast(j_arg)); client.fetch_api_key(ObjectId(str_id.c_str()), user, callback); break; } - case io_realm_ApiKeyAuth_TYPE_FETCH_ALL: { + case io_realm_mongodb_ApiKeyAuthImpl_TYPE_FETCH_ALL: { auto callback = JavaNetworkTransport::create_result_callback(env, j_callback, multi_key_mapper); client.fetch_api_keys(user, callback); break; } - case io_realm_ApiKeyAuth_TYPE_DELETE: { + case io_realm_mongodb_ApiKeyAuthImpl_TYPE_DELETE: { auto callback = JavaNetworkTransport::create_void_callback(env, j_callback); std::string str_id = JStringAccessor(env, static_cast(j_arg)); client.delete_api_key(ObjectId(str_id.c_str()), user, callback); break; } - case io_realm_ApiKeyAuth_TYPE_ENABLE: { + case io_realm_mongodb_ApiKeyAuthImpl_TYPE_ENABLE: { auto callback = JavaNetworkTransport::create_void_callback(env, j_callback); std::string str_id = JStringAccessor(env, static_cast(j_arg)); client.enable_api_key(ObjectId(str_id.c_str()), user, callback); break; } - case io_realm_ApiKeyAuth_TYPE_DISABLE: { + case io_realm_mongodb_ApiKeyAuthImpl_TYPE_DISABLE: { auto callback = JavaNetworkTransport::create_void_callback(env, j_callback); std::string str_id = JStringAccessor(env, static_cast(j_arg)); client.disable_api_key(ObjectId(str_id.c_str()), user, callback); diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp similarity index 93% rename from realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp rename to realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp index 8eeb39e12e..93a5fcf31e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_RealmApp.h" +#include "io_realm_mongodb_App.h" #include "java_network_transport.hpp" #include "util.hpp" @@ -83,7 +83,7 @@ struct AndroidSyncLoggerFactory : public realm::SyncLoggerFactory { } } s_sync_logger_factory; -JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject obj, +JNIEXPORT jlong JNICALL Java_io_realm_mongodb_App_nativeCreate(JNIEnv* env, jobject obj, jstring j_app_id, jstring j_base_url, jstring j_app_name, @@ -152,7 +152,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_RealmApp_nativeCreate(JNIEnv* env, jobject } -JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogin(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_credentials_ptr, jobject j_callback) +JNIEXPORT void JNICALL Java_io_realm_mongodb_App_nativeLogin(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_credentials_ptr, jobject j_callback) { try { auto app = *reinterpret_cast*>(j_app_ptr); @@ -167,7 +167,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogin(JNIEnv* env, jclass, j CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) +JNIEXPORT void JNICALL Java_io_realm_mongodb_App_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) { try { auto app = *reinterpret_cast*>(j_app_ptr); @@ -177,7 +177,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeLogOut(JNIEnv* env, jclass, CATCH_STD() } -JNIEXPORT jobject JNICALL Java_io_realm_RealmApp_nativeCurrentUser(JNIEnv* env, jclass, jlong j_app_ptr) +JNIEXPORT jobject JNICALL Java_io_realm_mongodb_App_nativeCurrentUser(JNIEnv* env, jclass, jlong j_app_ptr) { try { auto app = *reinterpret_cast*>(j_app_ptr); @@ -194,7 +194,7 @@ JNIEXPORT jobject JNICALL Java_io_realm_RealmApp_nativeCurrentUser(JNIEnv* env, return NULL; } -JNIEXPORT jlongArray JNICALL Java_io_realm_RealmApp_nativeGetAllUsers(JNIEnv* env, jclass, jlong j_app_ptr) +JNIEXPORT jlongArray JNICALL Java_io_realm_mongodb_App_nativeGetAllUsers(JNIEnv* env, jclass, jlong j_app_ptr) { try { auto app = *reinterpret_cast*>(j_app_ptr); @@ -221,7 +221,7 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_RealmApp_nativeGetAllUsers(JNIEnv* en return nullptr; } -JNIEXPORT void JNICALL Java_io_realm_RealmApp_nativeSwitchUser(JNIEnv* env, +JNIEXPORT void JNICALL Java_io_realm_mongodb_App_nativeSwitchUser(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr) diff --git a/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp similarity index 80% rename from realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp rename to realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp index 26c244dc8e..ef7fe7df2a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_EmailPasswordAuth.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_EmailPasswordAuth.h" +#include "io_realm_mongodb_EmailPasswordAuthImpl.h" #include "java_network_transport.hpp" #include "util.hpp" @@ -29,7 +29,7 @@ using namespace realm::app; using namespace realm::jni_util; using namespace realm::_impl; -JNIEXPORT void JNICALL Java_io_realm_EmailPasswordAuth_nativeCallFunction(JNIEnv* env, +JNIEXPORT void JNICALL Java_io_realm_mongodb_EmailPasswordAuthImpl_nativeCallFunction(JNIEnv* env, jclass, jint j_function_type, jlong j_app_ptr, @@ -41,24 +41,24 @@ JNIEXPORT void JNICALL Java_io_realm_EmailPasswordAuth_nativeCallFunction(JNIEnv JObjectArrayAccessor args(env, j_args); auto client = app->provider_client(); switch(j_function_type) { - case io_realm_EmailPasswordAuth_TYPE_REGISTER_USER: + case io_realm_mongodb_EmailPasswordAuthImpl_TYPE_REGISTER_USER: client.register_email(args[0], args[1], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuth_TYPE_CONFIRM_USER: + case io_realm_mongodb_EmailPasswordAuthImpl_TYPE_CONFIRM_USER: client.confirm_user(args[0], args[1], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuth_TYPE_RESEND_CONFIRMATION_EMAIL: + case io_realm_mongodb_EmailPasswordAuthImpl_TYPE_RESEND_CONFIRMATION_EMAIL: client.resend_confirmation_email(args[0], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuth_TYPE_SEND_RESET_PASSWORD_EMAIL: + case io_realm_mongodb_EmailPasswordAuthImpl_TYPE_SEND_RESET_PASSWORD_EMAIL: client.send_reset_password_email(args[0], JavaNetworkTransport::create_void_callback(env, j_callback)); break; - case io_realm_EmailPasswordAuth_TYPE_CALL_RESET_PASSWORD_FUNCTION: { + case io_realm_mongodb_EmailPasswordAuthImpl_TYPE_CALL_RESET_PASSWORD_FUNCTION: { bson::BsonArray reset_arg(JniBsonProtocol::string_to_bson(args[2])); client.call_reset_password_function(args[0], args[1], reset_arg, JavaNetworkTransport::create_void_callback(env, j_callback)); break; } - case io_realm_EmailPasswordAuth_TYPE_RESET_PASSWORD: + case io_realm_mongodb_EmailPasswordAuthImpl_TYPE_RESET_PASSWORD: client.reset_password(args[0], args[1], args[2], JavaNetworkTransport::create_void_callback(env, j_callback)); break; default: diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp index 31effdfac3..7d4ba9a834 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_FunctionsImpl.h" +#include "io_realm_mongodb_FunctionsImpl.h" #include "util.hpp" #include "jni_util/bson_util.hpp" @@ -38,7 +38,7 @@ static std::function )> success_mapper = [ }; JNIEXPORT void JNICALL -Java_io_realm_FunctionsImpl_nativeCallFunction(JNIEnv* env, jclass , jlong j_app_ptr, jlong j_user_ptr, jstring j_name, +Java_io_realm_mongodb_FunctionsImpl_nativeCallFunction(JNIEnv* env, jclass , jlong j_app_ptr, jlong j_user_ptr, jstring j_name, jstring j_args_json , jobject j_callback) { try { auto app = *reinterpret_cast*>(j_app_ptr); diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp similarity index 89% rename from realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp rename to realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp index 0a2822f0a4..d9f8698cc0 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmUser.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_RealmUser.h" +#include "io_realm_mongodb_User.h" #include "java_network_transport.hpp" #include "util.hpp" @@ -28,7 +28,7 @@ using namespace realm::app; using namespace realm::jni_util; using namespace realm::_impl; -JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeLinkUser(JNIEnv* env, +JNIEXPORT void JNICALL Java_io_realm_mongodb_User_nativeLinkUser(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, @@ -49,7 +49,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeLinkUser(JNIEnv* env, CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeRemoveUser(JNIEnv* env, +JNIEXPORT void JNICALL Java_io_realm_mongodb_User_nativeRemoveUser(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, @@ -63,7 +63,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeRemoveUser(JNIEnv* env, CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_RealmUser_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) +JNIEXPORT void JNICALL Java_io_realm_mongodb_User_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) { try { auto app = *reinterpret_cast*>(j_app_ptr); diff --git a/realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp similarity index 87% rename from realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp rename to realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp index 7cedf00bc2..aa618745cf 100644 --- a/realm/realm-library/src/main/cpp/io_realm_ClientResetRequiredError.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp @@ -19,11 +19,11 @@ #include #include "util.hpp" -#include "io_realm_ClientResetRequiredError.h" +#include "io_realm_mongodb_sync_ClientResetRequiredError.h" using namespace realm; -JNIEXPORT void JNICALL Java_io_realm_ClientResetRequiredError_nativeExecuteClientReset(JNIEnv* env, jobject, +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_ClientResetRequiredError_nativeExecuteClientReset(JNIEnv* env, jobject, jstring localRealmPath) { try { diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmSync.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp similarity index 81% rename from realm/realm-library/src/main/cpp/io_realm_RealmSync.cpp rename to realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp index 1b3787f32e..5c5535e8a8 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmSync.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_RealmSync.h" +#include "io_realm_mongodb_sync_Sync.h" #include #include @@ -30,7 +30,7 @@ using namespace realm; using namespace realm::jni_util; using namespace realm::util; -JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeReset(JNIEnv* env, jclass) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeReset(JNIEnv* env, jclass) { try { SyncManager::shared().reset_for_testing(); @@ -38,7 +38,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeReset(JNIEnv* env, jclass) CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeSimulateSyncError(JNIEnv* env, jclass, jstring local_realm_path, +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeSimulateSyncError(JNIEnv* env, jclass, jstring local_realm_path, jint err_code, jstring err_message, jboolean is_fatal) { @@ -57,7 +57,7 @@ JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeSimulateSyncError(JNIEnv* e CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeReconnect(JNIEnv* env, jclass) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeReconnect(JNIEnv* env, jclass) { try { SyncManager::shared().reconnect(); @@ -65,11 +65,11 @@ JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeReconnect(JNIEnv* env, jcla CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_RealmSync_nativeCreateSession(JNIEnv* env, jclass, jlong j_native_config_ptr) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeCreateSession(JNIEnv* env, jclass, jlong j_native_config_ptr) { try { auto& config = *reinterpret_cast(j_native_config_ptr); _impl::RealmCoordinator::get_coordinator(config)->create_session(config); } CATCH_STD() -} \ No newline at end of file +} diff --git a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp similarity index 83% rename from realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp rename to realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp index de94e52651..e16c432689 100644 --- a/realm/realm-library/src/main/cpp/io_realm_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp @@ -17,7 +17,7 @@ #include #include -#include "io_realm_SyncSession.h" +#include "io_realm_mongodb_sync_SyncSession.h" #include "object-store/src/sync/sync_manager.hpp" #include "object-store/src/sync/sync_session.hpp" @@ -36,26 +36,26 @@ using namespace realm::sync; using namespace realm::_impl; static_assert(SyncSession::PublicState::Active == - static_cast(io_realm_SyncSession_STATE_VALUE_ACTIVE), + static_cast(io_realm_mongodb_sync_SyncSession_STATE_VALUE_ACTIVE), ""); static_assert(SyncSession::PublicState::Dying == - static_cast(io_realm_SyncSession_STATE_VALUE_DYING), + static_cast(io_realm_mongodb_sync_SyncSession_STATE_VALUE_DYING), ""); static_assert(SyncSession::PublicState::Inactive == - static_cast(io_realm_SyncSession_STATE_VALUE_INACTIVE), + static_cast(io_realm_mongodb_sync_SyncSession_STATE_VALUE_INACTIVE), ""); static_assert(SyncSession::ConnectionState::Disconnected == - static_cast(io_realm_SyncSession_CONNECTION_VALUE_DISCONNECTED), + static_cast(io_realm_mongodb_sync_SyncSession_CONNECTION_VALUE_DISCONNECTED), ""); static_assert(SyncSession::ConnectionState::Connecting == - static_cast(io_realm_SyncSession_CONNECTION_VALUE_CONNECTING), + static_cast(io_realm_mongodb_sync_SyncSession_CONNECTION_VALUE_CONNECTING), ""); static_assert(SyncSession::ConnectionState::Connected == - static_cast(io_realm_SyncSession_CONNECTION_VALUE_CONNECTED), + static_cast(io_realm_mongodb_sync_SyncSession_CONNECTION_VALUE_CONNECTED), ""); -JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIEnv* env, jobject j_session_object, +JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddProgressListener(JNIEnv* env, jobject j_session_object, jstring j_local_realm_path, jlong listener_id, jint direction, jboolean is_streaming) @@ -74,7 +74,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIE SyncSession::NotifierType type = (direction == 1) ? SyncSession::NotifierType::download : SyncSession::NotifierType::upload; - static JavaClass java_syncsession_class(env, "io/realm/SyncSession"); + static JavaClass java_syncsession_class(env, "io/realm/mongodb/sync/SyncSession"); static JavaMethod java_notify_progress_listener(env, java_syncsession_class, "notifyProgressListener", "(JJJ)V"); auto session_ref = env->NewGlobalRef(j_session_object); // This leaks. FIXME @@ -105,7 +105,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddProgressListener(JNIE return 0; } -JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveProgressListener(JNIEnv* env, jclass, +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeRemoveProgressListener(JNIEnv* env, jclass, jstring j_local_realm_path, jlong listener_token) { @@ -119,7 +119,7 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveProgressListener(JN CATCH_STD() } -JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadCompletion(JNIEnv* env, +JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForDownloadCompletion(JNIEnv* env, jobject session_object, jint callback_id, jstring j_local_realm_path) @@ -129,7 +129,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple auto session = SyncManager::shared().get_existing_session(local_realm_path); if (session) { - static JavaClass java_sync_session_class(env, "io/realm/SyncSession"); + static JavaClass java_sync_session_class(env, "io/realm/mongodb/sync/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", "(ILjava/lang/Long;Ljava/lang/String;)V"); auto obj = env->NewGlobalRef(session_object); @@ -153,7 +153,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForDownloadComple return JNI_FALSE; } -JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompletion(JNIEnv* env, +JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForUploadCompletion(JNIEnv* env, jobject session_object, jint callback_id, jstring j_local_realm_path) @@ -163,7 +163,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompleti auto session = SyncManager::shared().get_existing_session(local_realm_path); if (session) { - static JavaClass java_sync_session_class(env, "io/realm/SyncSession"); + static JavaClass java_sync_session_class(env, "io/realm/mongodb/sync/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", "(ILjava/lang/Long;Ljava/lang/String;)V"); auto obj = env->NewGlobalRef(session_object); @@ -187,7 +187,7 @@ JNIEXPORT jboolean JNICALL Java_io_realm_SyncSession_nativeWaitForUploadCompleti } -JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetState(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT jbyte JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeGetState(JNIEnv* env, jclass, jstring j_local_realm_path) { try { JStringAccessor local_realm_path(env, j_local_realm_path); @@ -196,11 +196,11 @@ JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetState(JNIEnv* env, jc if (session) { switch (session->state()) { case SyncSession::PublicState::Active: - return io_realm_SyncSession_STATE_VALUE_ACTIVE; + return io_realm_mongodb_sync_SyncSession_STATE_VALUE_ACTIVE; case SyncSession::PublicState::Dying: - return io_realm_SyncSession_STATE_VALUE_DYING; + return io_realm_mongodb_sync_SyncSession_STATE_VALUE_DYING; case SyncSession::PublicState::Inactive: - return io_realm_SyncSession_STATE_VALUE_INACTIVE; + return io_realm_mongodb_sync_SyncSession_STATE_VALUE_INACTIVE; } } } @@ -208,7 +208,7 @@ JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetState(JNIEnv* env, jc return -1; } -JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetConnectionState(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT jbyte JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeGetConnectionState(JNIEnv* env, jclass, jstring j_local_realm_path) { try { JStringAccessor local_realm_path(env, j_local_realm_path); @@ -217,11 +217,11 @@ JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetConnectionState(JNIEn if (session) { switch (session->connection_state()) { case SyncSession::ConnectionState::Disconnected: - return io_realm_SyncSession_CONNECTION_VALUE_DISCONNECTED; + return io_realm_mongodb_sync_SyncSession_CONNECTION_VALUE_DISCONNECTED; case SyncSession::ConnectionState::Connecting: - return io_realm_SyncSession_CONNECTION_VALUE_CONNECTING; + return io_realm_mongodb_sync_SyncSession_CONNECTION_VALUE_CONNECTING; case SyncSession::ConnectionState::Connected: - return io_realm_SyncSession_CONNECTION_VALUE_CONNECTED; + return io_realm_mongodb_sync_SyncSession_CONNECTION_VALUE_CONNECTED; } } } @@ -231,14 +231,14 @@ JNIEXPORT jbyte JNICALL Java_io_realm_SyncSession_nativeGetConnectionState(JNIEn static jlong get_connection_value(SyncSession::ConnectionState state) { switch (state) { - case SyncSession::ConnectionState::Disconnected: return static_cast(io_realm_SyncSession_CONNECTION_VALUE_DISCONNECTED); - case SyncSession::ConnectionState::Connecting: return static_cast(io_realm_SyncSession_CONNECTION_VALUE_CONNECTING); - case SyncSession::ConnectionState::Connected: return static_cast(io_realm_SyncSession_CONNECTION_VALUE_CONNECTED); + case SyncSession::ConnectionState::Disconnected: return static_cast(io_realm_mongodb_sync_SyncSession_CONNECTION_VALUE_DISCONNECTED); + case SyncSession::ConnectionState::Connecting: return static_cast(io_realm_mongodb_sync_SyncSession_CONNECTION_VALUE_CONNECTING); + case SyncSession::ConnectionState::Connected: return static_cast(io_realm_mongodb_sync_SyncSession_CONNECTION_VALUE_CONNECTED); } return static_cast(-1); } -JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddConnectionListener(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddConnectionListener(JNIEnv* env, jclass, jstring j_local_realm_path) { try { // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda @@ -252,7 +252,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddConnectionListener(JN return 0; } - static JavaClass java_syncmanager_class(env, "io/realm/RealmSync"); + static JavaClass java_syncmanager_class(env, "io/realm/mongodb/sync/Sync"); static JavaMethod java_notify_connection_listener(env, java_syncmanager_class, "notifyConnectionListeners", "(Ljava/lang/String;JJ)V", true); std::function callback = [local_realm_path](SyncSession::ConnectionState old_state, SyncSession::ConnectionState new_state) { @@ -282,7 +282,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_SyncSession_nativeAddConnectionListener(JN return 0; } -JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveConnectionListener(JNIEnv* env, jclass, jlong listener_id, jstring j_local_realm_path) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeRemoveConnectionListener(JNIEnv* env, jclass, jlong listener_id, jstring j_local_realm_path) { try { // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda @@ -295,7 +295,7 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeRemoveConnectionListener( CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStart(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStart(JNIEnv* env, jclass, jstring j_local_realm_path) { try { JStringAccessor local_realm_path(env, j_local_realm_path); @@ -312,7 +312,7 @@ JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStart(JNIEnv* env, jclass CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_SyncSession_nativeStop(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStop(JNIEnv* env, jclass, jstring j_local_realm_path) { try { JStringAccessor local_realm_path(env, j_local_realm_path); diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 6d68fa0c9e..f058222f8e 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -147,7 +147,7 @@ public void onInit(OsSharedRealm sharedRealm) { this.shouldCloseSharedRealm = false; } - /** + /** * Sets the auto-refresh status of the Realm instance. *

              * Auto-refresh is a feature that enables automatic update of the current Realm instance and all its derived objects diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 364f8d6d2b..23bcd5f527 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1868,6 +1868,18 @@ public static int getLocalInstanceCount(RealmConfiguration configuration) { return RealmCache.getLocalThreadCount(configuration); } + /** + * Get the application context used when initializing Realm with {@link Realm#init(Context)} or + * {@link Realm#init(Context, String)}. + * + * @return the application context used when initializing Realm with {@link Realm#init(Context)} or + * {@link Realm#init(Context, String)}, or null if Realm has not been initialized yet. + */ + @Nullable + public static Context getApplicationContext() { + return applicationContext; + } + /** * Encapsulates a Realm transaction. *

              diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 60218b895d..cd81d56373 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -69,7 +69,6 @@ public class RealmConfiguration { private static final Object DEFAULT_MODULE; protected static final RealmProxyMediator DEFAULT_MODULE_MEDIATOR; - private static Boolean rxJavaAvailable; static { DEFAULT_MODULE = Realm.getDefaultModule(); @@ -187,7 +186,7 @@ protected RealmProxyMediator getSchemaMediator() { * * @return the initial data transaction. */ - Realm.Transaction getInitialDataTransaction() { + protected Realm.Transaction getInitialDataTransaction() { return initialDataTransaction; } @@ -429,24 +428,6 @@ public String toString() { return stringBuilder.toString(); } - /** - * Checks if RxJava is can be loaded. - * - * @return {@code true} if RxJava dependency exist, {@code false} otherwise. - */ - @SuppressWarnings("LiteralClassName") - static synchronized boolean isRxJavaAvailable() { - if (rxJavaAvailable == null) { - try { - Class.forName("io.reactivex.Flowable"); - rxJavaAvailable = true; - } catch (ClassNotFoundException ignore) { - rxJavaAvailable = false; - } - } - return rxJavaAvailable; - } - // Gets the canonical path for a given file. protected static String getCanonicalPath(File realmFile) { try { @@ -459,10 +440,14 @@ protected static String getCanonicalPath(File realmFile) { } // Checks if this configuration is a SyncConfiguration instance. - boolean isSyncConfiguration() { + protected boolean isSyncConfiguration() { return false; } + protected static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) { + return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true, Long.MAX_VALUE); + } + /** * RealmConfiguration.Builder used to construct instances of a RealmConfiguration in a fluent manner. */ @@ -848,7 +833,7 @@ public RealmConfiguration build() { } } - if (rxFactory == null && isRxJavaAvailable()) { + if (rxFactory == null && Util.isRxJavaAvailable()) { rxFactory = new RealmObservableFactory(true); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 691a0b2ae7..8fe0faff42 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -37,6 +37,8 @@ public class Util { + private static Boolean rxJavaAvailable; + public static String getTablePrefix() { return nativeGetTablePrefix(); } @@ -200,4 +202,22 @@ public static void checkNotOnMainThread(String errorMessage) { } } + /** + * Checks if RxJava is can be loaded. + * + * @return {@code true} if RxJava dependency exist, {@code false} otherwise. + */ + @SuppressWarnings("LiteralClassName") + public static synchronized boolean isRxJavaAvailable() { + if (rxJavaAvailable == null) { + try { + Class.forName("io.reactivex.Flowable"); + rxJavaAvailable = true; + } catch (ClassNotFoundException ignore) { + rxJavaAvailable = false; + } + } + return rxJavaAvailable; + } + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java deleted file mode 100644 index dcc13d3912..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncCredentials.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.annotation.Nullable; - -import io.realm.internal.Util; - - -/** - * Credentials represent a login with a 3rd party login provider in an OAuth2 login flow, and are used by the Realm - * Object Server to verify the user and grant access. - *

              - * Logging into the Realm Object Server consists of the following steps: - *

                - *
              1. - * Log in to 3rd party provider (Facebook or Google). The result is usually an Authorization Grant that must be - * saved in a {@link SyncCredentials} object of the proper type e.g., {@link SyncCredentials#facebook(String)} for a - * Facebook login. - *
              2. - *
              3. - * Authenticate a {@link SyncUser} through the Object Server using these credentials. Once authenticated, - * an Object Server user is returned. Then this user can be attached to a {@link SyncConfiguration}, which - * will make it possible to synchronize data between the local and remote Realm. - *

                - * It is possible to persist the user object e.g., using the {@link UserStore}. That means, logging - * into an OAuth2 provider is only required the first time the app is used. - *

              4. - *
              - * - *
              - * {@code
              - * // Example
              - *
              - * Credentials credentials = Credentials.facebook(getFacebookToken());
              - * User.login(credentials, "http://objectserver.realm.io/auth", new User.Callback() {
              - *     \@Override
              - *     public void onSuccess(User user) {
              - *          // User is now authenticated and be be used to open Realms.
              - *     }
              - *
              - *     \@Override
              - *     public void onError(ObjectServerError error) {
              - *
              - *     }
              - * });
              - * }
              - * 
              - */ -public class SyncCredentials { - - private final String userIdentifier; - private final String identityProvider; - private final Map userInfo; - - // Factory constructors - - /** - * Creates credentials based on a Facebook login. - * - * @param facebookToken a facebook userIdentifier acquired by logging into Facebook. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. - * @throws IllegalArgumentException if user name is either {@code null} or empty. - */ - public static SyncCredentials facebook(String facebookToken) { - assertStringNotEmpty(facebookToken, "facebookToken"); - return new SyncCredentials(facebookToken, IdentityProvider.FACEBOOK, null); - } - - /** - * Creates credentials based on a Google login. - * - * @param googleToken a google userIdentifier acquired by logging into Google. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. - * @throws IllegalArgumentException if user name is either {@code null} or empty. - */ - public static SyncCredentials google(String googleToken) { - assertStringNotEmpty(googleToken, "googleToken"); - return new SyncCredentials(googleToken, IdentityProvider.GOOGLE, null); - } - - /** - * Creates credentials based on a JSON Web Token (JWT). - * - * @param jwtToken a JWT token that identifies the user. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. - * @throws IllegalArgumentException if the token is either {@code null} or empty. - */ - public static SyncCredentials jwt(String jwtToken) { - assertStringNotEmpty(jwtToken, "jwtToken"); - return new SyncCredentials(jwtToken, IdentityProvider.JWT, null); - } - - /** - * Creates credentials anonymously. - * - * Note: logging the user out again means that data is lost with no means of recovery - * and it isn't possible to share the user details across devices. - * - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. - */ - public static SyncCredentials anonymous() { - return new SyncCredentials("", IdentityProvider.ANONYMOUS, null); - } - - /** - * Creates credentials based on a login with username and password. These credentials will only be verified - * by the Object Server. - * - * @param username username of the user. - * @param password the users password. - * @param createUser {@code true} if the user should be created, {@code false} otherwise. It is not possible to - * create a user twice when logging in, so this flag should only be set to {@code true} the first - * time a users log in. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. - * @throws IllegalArgumentException if user name is either {@code null} or empty. - */ - public static SyncCredentials usernamePassword(String username, String password, boolean createUser) { - assertStringNotEmpty(username, "username"); - Map userInfo = new HashMap(); - userInfo.put("register", createUser); - userInfo.put("password", password); - return new SyncCredentials(username, IdentityProvider.USERNAME_PASSWORD, userInfo); - } - - /** - * Creates credentials based on a login with username and password. These credentials will only be verified - * by the Object Server. The user is not created if she does not exist. - * - * @param username username of the user. - * @param password the users password. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. - * @throws IllegalArgumentException if user name is either {@code null} or empty. - */ - public static SyncCredentials usernamePassword(String username, String password) { - return usernamePassword(username, password, false); - } - - /** - * Creates a custom set of credentials. The behaviour will depend on the type of {@code identityProvider} and - * {@code userInfo} used. - * - * @param userIdentifier String identifying the user. Usually a username or user token. - * @param identityProvider provider used to verify the credentials. - * @param userInfo data describing the user further or {@code null} if the user does not have any extra data. The - * data will be serialized to JSON, so all values must be mappable to a valid JSON data type. Custom - * classes will be converted using {@code toString()}. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)}. - * @throws IllegalArgumentException if any parameter is either {@code null} or empty. - */ - public static SyncCredentials custom(String userIdentifier, String identityProvider, @Nullable Map userInfo) { - assertStringNotEmpty(userIdentifier, "userIdentifier"); - assertStringNotEmpty(identityProvider, "identityProvider"); - if (userInfo == null) { - userInfo = new HashMap(); - } - return new SyncCredentials(userIdentifier, identityProvider, userInfo); - } - - /** - * Creates credentials from an existing access token. Since an access token is the proof that a user already - * has logged in. Credentials created this way are automatically assumed to have successfully logged in. - * This means that providing these credentials to {@link SyncUser#logIn(SyncCredentials, String)} will always - * succeed, but accessing any Realm after might fail if the token is no longer valid. - *

              - * It is assumed that this user is not an administrator. Otherwise use {@link #accessToken(String, String, boolean)}. - * - * @param accessToken user's access token. - * @param identifier user identifier. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)} - */ - public static SyncCredentials accessToken(String accessToken, String identifier) { - return accessToken(accessToken, identifier, false); - } - - /** - * Creates credentials from an existing access token. Since an access token is the proof that a user already - * has logged in. Credentials created this way are automatically assumed to have successfully logged in. - * This means that providing these credentials to {@link SyncUser#logIn(SyncCredentials, String)} will always - * succeed, but accessing any Realm after might fail if the token is no longer valid. - * - * @param accessToken user's access token. - * @param identifier user identifier. - * @param isAdmin {@code true} if the access token is an administrator's token, {@code false} if it is a - * non-privileged users. It is to not possible to upgrade a non-admin token to an admin token by setting this - * value. It is purely informational. - * @return a set of credentials that can be used to log into the Object Server using - * {@link SyncUser#logInAsync(SyncCredentials, String, SyncUser.Callback)} - */ - public static SyncCredentials accessToken(String accessToken, String identifier, boolean isAdmin) { - HashMap userInfo = new HashMap(); - userInfo.put("_token", accessToken); - userInfo.put("_isAdmin", isAdmin); - return new SyncCredentials(identifier, IdentityProvider.ACCESS_TOKEN, userInfo); - } - - private static void assertStringNotEmpty(String string, String message) { - //noinspection ConstantConditions - if (Util.isEmptyString(string)) { - throw new IllegalArgumentException("Non-null '" + message + "' required."); - } - } - - private SyncCredentials(String token, String identityProvider, @Nullable Map userInfo) { - this.identityProvider = identityProvider; - this.userIdentifier = token; - this.userInfo = (userInfo == null) ? new HashMap() : userInfo; - } - - /** - * Returns the provider used by the Object Server to validate these credentials. - * - * @return the login type. - */ - public String getIdentityProvider() { - return identityProvider; - } - - /** - * Returns a String that identifies the user. The value will depend on the type of {@link IdentityProvider} used. - * - * @return a String identifying the user. - */ - public String getUserIdentifier() { - return userIdentifier; - } - - /** - * Returns any custom user information associated with this credential. - * The type of information will depend on the type of {@link SyncCredentials.IdentityProvider} - * used. - * - * @return a map of additional information about the user. - */ - public Map getUserInfo() { - return Collections.unmodifiableMap(userInfo); - } - - /** - * Enumeration of the different types of identity providers. An identity provider is the entity responsible for - * verifying that a given credential is valid. - */ - public static final class IdentityProvider { - - /** - * The provided identity is an already registered user (represented by the access token). Logging in with this - * type of identity will happen purely on the device without contacting the Realm Object Server. Acquiring - * access to individual Realms will still require talking to the Object Server. - */ - public static final String ACCESS_TOKEN = "_access_token"; - - /** - * Any credentials verified by the debug identity provider will always be considered valid. - * It is only available if configured on the Object Server, and it is disabled by default. - */ - public static final String DEBUG = "debug"; - - /** - * Credentials will be verified by Facebook. - */ - public static final String FACEBOOK = "facebook"; - - /** - * Credentials will be verified by Google. - */ - public static final String GOOGLE = "google"; - - /** - * Credentials are given in the form of a standard JSON Web Token that will be verified - * by the Realm Object Server. - */ - public static final String JWT = "jwt"; - - /** - * Credentials do not require user/password (anonymous user). - */ - public static final String ANONYMOUS = "anonymous"; - - /** - * Credentials will be verified by the Object Server. - * - * @see #usernamePassword(String, String, boolean) - */ - public static final String USERNAME_PASSWORD = "password"; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java b/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java index a7326edbab..55b4a12bf6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java +++ b/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java @@ -16,7 +16,7 @@ package io.realm.exceptions; -import io.realm.SyncConfiguration; +import io.realm.mongodb.sync.SyncConfiguration; /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 24fdb1dd23..e38b35675c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -28,11 +28,11 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import io.realm.RealmApp; +import io.realm.mongodb.App; import io.realm.RealmConfiguration; -import io.realm.RealmUser; -import io.realm.SyncConfiguration; -import io.realm.RealmSync; +import io.realm.mongodb.sync.Sync; +import io.realm.mongodb.User; +import io.realm.mongodb.sync.SyncConfiguration; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; import io.realm.internal.android.AndroidCapabilities; @@ -74,8 +74,8 @@ public void realmClosed(RealmConfiguration configuration) { public Object[] getSyncConfigurationOptions(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; - RealmUser user = syncConfig.getUser(); - RealmApp app = user.getApp(); + User user = syncConfig.getUser(); + App app = user.getApp(); String rosServerUrl = syncConfig.getServerUrl().toString(); String rosUserIdentity = user.getId(); String syncRealmAuthUrl = user.getApp().getConfiguration().getBaseUrl().toString(); @@ -130,7 +130,7 @@ public static Context getApplicationContext() { public void wrapObjectStoreSessionIfRequired(OsRealmConfig config) { if (config.getRealmConfiguration() instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config.getRealmConfiguration(); - RealmApp app = syncConfig.getUser().getApp(); + App app = syncConfig.getUser().getApp(); app.getSync().getOrCreateSession(syncConfig); } } @@ -144,7 +144,7 @@ private void invokeRemoveSession(SyncConfiguration syncConfig) { if (removeSessionMethod == null) { synchronized (SyncObjectServerFacade.class) { if (removeSessionMethod == null) { - Method removeSession = RealmSync.class.getDeclaredMethod("removeSession", SyncConfiguration.class); + Method removeSession = Sync.class.getDeclaredMethod("removeSession", SyncConfiguration.class); removeSession.setAccessible(true); removeSessionMethod = removeSession; } @@ -191,7 +191,7 @@ public boolean wasDownloadInterrupted(Throwable throwable) { public void createNativeSyncSession(RealmConfiguration configuration) { if (configuration instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) configuration; - RealmApp app = syncConfig.getUser().getApp(); + App app = syncConfig.getUser().getApp(); app.getSync().getOrCreateSession(syncConfig); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java index 176a8fe12c..7ed3286e4f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java @@ -20,13 +20,13 @@ import javax.annotation.Nullable; -import io.realm.ErrorCode; -import io.realm.ObjectServerError; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.ObjectServerError; import io.realm.internal.Keep; import io.realm.internal.objectstore.OsJavaNetworkTransport; // Common callback for handling results from the ObjectStore layer. -// NOTE: This class is called from JNI. If renamed, adjust callbacks in RealmApp.cpp +// NOTE: This class is called from JNI. If renamed, adjust callbacks in App.cpp @Keep public abstract class OsJNIResultCallback extends OsJavaNetworkTransport.NetworkTransportJNIResultCallback { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/mongodb/Request.java b/realm/realm-library/src/objectServer/java/io/realm/internal/mongodb/Request.java new file mode 100644 index 0000000000..11c908adfe --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/mongodb/Request.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.mongodb; + +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; + +import javax.annotation.Nullable; + +import io.realm.RealmAsyncTask; +import io.realm.internal.RealmNotifier; +import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.android.AndroidRealmNotifier; +import io.realm.internal.async.RealmAsyncTaskImpl; +import io.realm.log.RealmLog; +import io.realm.mongodb.App; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.ObjectServerError; + +// Class wrapping requests made against MongoDB Realm. Is also responsible for calling with success/error on the +// correct thread. +public abstract class Request { + @Nullable + private final App.Callback callback; + private final RealmNotifier handler; + private final ThreadPoolExecutor networkPoolExecutor; + + public Request(ThreadPoolExecutor networkPoolExecutor, @Nullable App.Callback callback) { + this.callback = callback; + this.handler = new AndroidRealmNotifier(null, new AndroidCapabilities()); + this.networkPoolExecutor = networkPoolExecutor; + } + + // Implements the request. Return the current sync user if the request succeeded. Otherwise throw an error. + public abstract T run() throws ObjectServerError; + + // Start the request + public RealmAsyncTask start() { + Future authenticateRequest = networkPoolExecutor.submit(new Runnable() { + @Override + public void run() { + try { + postSuccess(Request.this.run()); + } catch (ObjectServerError e) { + postError(e); + } catch (Throwable e) { + postError(new ObjectServerError(ErrorCode.UNKNOWN, "Unexpected error", e)); + } + } + }); + return new RealmAsyncTaskImpl(authenticateRequest, networkPoolExecutor); + } + + private void postError(final ObjectServerError error) { + boolean errorHandled = false; + if (callback != null) { + Runnable action = new Runnable() { + @Override + public void run() { + callback.onResult(App.Result.withError(error)); + } + }; + errorHandled = handler.post(action); + } + + if (!errorHandled) { + RealmLog.error(error, "An error was thrown, but could not be posted: \n" + error.toString()); + } + } + + private void postSuccess(final T result) { + if (callback != null) { + handler.post(new Runnable() { + @Override + public void run() { + callback.onResult((result == null) ? App.Result.success() : App.Result.withResult(result)); + } + }); + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java index a15da2ddac..3c990490c7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/NetworkStateReceiver.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import io.realm.RealmSync; +import io.realm.mongodb.sync.Sync; import io.realm.internal.Util; /** @@ -68,7 +68,7 @@ public static synchronized void removeListener(ConnectionListener listener) { * @return {@code true} if device is online, otherwise {@code false}. */ public static boolean isOnline(Context context) { - if (RealmSync.Debug.skipOnlineChecking) { + if (Sync.Debug.skipOnlineChecking) { return true; } ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java index 316d99294d..b81d7b75b2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java @@ -20,7 +20,7 @@ import javax.annotation.Nullable; -import io.realm.ObjectServerError; +import io.realm.mongodb.ObjectServerError; public class ResultHandler { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java index 115022d3a0..de115ea771 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java @@ -5,9 +5,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.internal.Keep; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.ObjectServerError; import io.realm.internal.KeepMember; import io.realm.internal.OsRealmConfig; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java index 1107de0875..d97c9db45f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java @@ -18,8 +18,8 @@ import java.util.HashMap; import java.util.Map; -import io.realm.RealmAppConfiguration; import io.realm.internal.Keep; +import io.realm.mongodb.AppConfiguration; /** * Java implementation of the transport layer exposed by ObjectStore when communicating with @@ -74,7 +74,7 @@ public Map getCustomRequestHeaders() { * Used for testing. */ public void resetHeaders() { - authorizationHeaderName = RealmAppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME; + authorizationHeaderName = AppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME; customHeaders.clear(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java index 837423b6e8..5602b1e3a1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java @@ -18,7 +18,7 @@ import org.bson.codecs.configuration.CodecRegistry; -import io.realm.RealmUser; +import io.realm.mongodb.User; import io.realm.internal.NativeObject; public class OsMongoClient implements NativeObject { @@ -27,8 +27,8 @@ public class OsMongoClient implements NativeObject { private final long nativePtr; - public OsMongoClient(RealmUser realmUser, String serviceName) { - this.nativePtr = nativeCreate(realmUser.getApp().nativePtr, serviceName); + public OsMongoClient(long appNativePtr, String serviceName) { + this.nativePtr = nativeCreate(appNativePtr, serviceName); } public OsMongoDatabase getRemoteDatabase(final String databaseName, final CodecRegistry codecRegistry) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java index fcaee9bed0..f155addd22 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java @@ -25,7 +25,6 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -34,7 +33,7 @@ import javax.annotation.Nullable; -import io.realm.ObjectServerError; +import io.realm.mongodb.ObjectServerError; import io.realm.internal.NativeObject; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java new file mode 100644 index 0000000000..2a42fb684b --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import javax.annotation.Nullable; + +import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.mongodb.auth.ApiKeyAuth; + + +class ApiKeyAuthImpl extends ApiKeyAuth { + + ApiKeyAuthImpl(User user) { + super(user); + } + + @Override + protected void call(int functionType, @Nullable String arg, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback) { + nativeCallFunction(functionType, getApp().nativePtr, getUser().osUser.getNativePtr(), arg, callback); + } + + private static native void nativeCallFunction(int functionType, long nativeAppPtr, long nativeUserPtr, @Nullable String arg, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java similarity index 72% rename from realm/realm-library/src/objectServer/java/io/realm/RealmApp.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index eb12ccf1af..b49de85ce1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb; import android.content.Context; import android.os.Build; @@ -28,20 +28,21 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.BuildConfig; +import io.realm.Realm; +import io.realm.internal.mongodb.Request; +import io.realm.mongodb.auth.EmailPasswordAuth; +import io.realm.RealmAsyncTask; +import io.realm.mongodb.sync.Sync; import io.realm.internal.KeepMember; -import io.realm.internal.RealmNotifier; import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; -import io.realm.internal.android.AndroidCapabilities; -import io.realm.internal.android.AndroidRealmNotifier; -import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.network.OkHttpNetworkTransport; @@ -52,14 +53,20 @@ /** * FIXME */ -public class RealmApp { +public class App { + + static final class SyncImpl extends Sync { + protected SyncImpl(App app) { + super(app); + } + } // Implementation notes: - // The public API's currently only allow for one RealmApp, however this is a restriction + // The public API's currently only allow for one App, however this is a restriction // we might want to lift in the future. So any implementation details so ideally be made // with that in mind, i.e. keep static state to minimum. - // Currently we only allow one instance of RealmApp (due to restrictions in ObjectStore that + // Currently we only allow one instance of App (due to restrictions in ObjectStore that // only allows one underlying SyncClient). // FIXME: Lift this restriction so it is possible to create multiple app instances. public volatile static boolean CREATED = false; @@ -73,46 +80,46 @@ public class RealmApp { @SuppressFBWarnings("MS_SHOULD_BE_FINAL") public static ThreadPoolExecutor NETWORK_POOL_EXECUTOR = RealmThreadPoolExecutor.newDefaultExecutor(); - private final RealmAppConfiguration config; - OsJavaNetworkTransport networkTransport; - final RealmSync syncManager; - public final long nativePtr; //FIXME Find a way to make this package protected - private final EmailPasswordAuth emailAuthProvider = new EmailPasswordAuth(this); + private final AppConfiguration config; + protected OsJavaNetworkTransport networkTransport; + final Sync syncManager; + final long nativePtr; + private final EmailPasswordAuth emailAuthProvider = new EmailPasswordAuthImpl(this); private CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList<>(); private Handler mainHandler = new Handler(Looper.getMainLooper()); - public RealmApp(String appId) { - this(new RealmAppConfiguration.Builder(appId).build()); + public App(String appId) { + this(new AppConfiguration.Builder(appId).build()); } /** * FIXME * @param config */ - public RealmApp(RealmAppConfiguration config) { + public App(AppConfiguration config) { this.config = config; this.networkTransport = new OkHttpNetworkTransport(); networkTransport.setAuthorizationHeaderName(config.getAuthorizationHeaderName()); for (Map.Entry entry : config.getCustomRequestHeaders().entrySet()) { networkTransport.addCustomRequestHeader(entry.getKey(), entry.getValue()); } - this.syncManager = new RealmSync(this); + this.syncManager = new SyncImpl(this); this.nativePtr = init(config); - // FIXME: Right now we only support one RealmApp. This class will throw a + // FIXME: Right now we only support one App. This class will throw a // exception if you try to create it twice. This is a really hacky way to do this // Figure out a better API that is always forward compatible - synchronized (RealmSync.class) { + synchronized (Sync.class) { if (CREATED) { - throw new IllegalStateException("Only one RealmApp is currently supported. " + - "This restriction will be lifted soon. Instead, store the RealmApp" + + throw new IllegalStateException("Only one App is currently supported. " + + "This restriction will be lifted soon. Instead, store the App" + "instance in a shared global variable."); } CREATED = true; } } - private long init(RealmAppConfiguration config) { + private long init(AppConfiguration config) { String userAgentBindingInfo = getBindingInfo(); String appDefinedUserAgent = getAppInfo(config); String syncDir = getSyncBaseDirectory(); @@ -131,12 +138,12 @@ private long init(RealmAppConfiguration config) { } private String getSyncBaseDirectory() { - if (BaseRealm.applicationContext == null) { + Context context = Realm.getApplicationContext(); + if (context == null) { throw new IllegalStateException("Call Realm.init() first."); } - Context context = BaseRealm.applicationContext; String syncDir; - if (RealmSync.Debug.separatedDirForSyncManager) { + if (Sync.Debug.separatedDirForSyncManager) { try { // Files.createTempDirectory is not available on JDK 6. File dir = File.createTempFile("remote_sync_", "_" + android.os.Process.myPid(), context.getFilesDir()); @@ -159,7 +166,7 @@ private String getSyncBaseDirectory() { return syncDir; } - private String getAppInfo(RealmAppConfiguration config) { + private String getAppInfo(AppConfiguration config) { // Create app UserAgent string String appDefinedUserAgent = "Unknown"; try { @@ -207,28 +214,28 @@ private String getBindingInfo() { *

              * If two or more users are logged in, it is the last valid user that is returned by this method. * - * @return current {@link RealmUser} that has logged in and is still valid. {@code null} if no + * @return current {@link User} that has logged in and is still valid. {@code null} if no * user is logged in or the user has expired. */ @Nullable - public RealmUser currentUser() { + public User currentUser() { Long userPtr = nativeCurrentUser(nativePtr); - return (userPtr != null) ? new RealmUser(userPtr, this) : null; + return (userPtr != null) ? new User(userPtr, this) : null; } /** - * Returns all known users that are either {@link RealmUser.State#LOGGED_IN} or - * {@link RealmUser.State#LOGGED_OUT}. + * Returns all known users that are either {@link User.State#LOGGED_IN} or + * {@link User.State#LOGGED_OUT}. *

              * Only users that at some point logged into this device will be returned. * * @return a map of user identifiers and users known locally. */ - public Map allUsers() { + public Map allUsers() { long[] nativeUsers = nativeGetAllUsers(nativePtr); - HashMap users = new HashMap<>(nativeUsers.length); + HashMap users = new HashMap<>(nativeUsers.length); for (int i = 0; i < nativeUsers.length; i++) { - RealmUser user = new RealmUser(nativeUsers[i], this); + User user = new User(nativeUsers[i], this); users.put(user.getId(), user); } return users; @@ -238,9 +245,9 @@ public Map allUsers() { * Switch current user. The current user is the user returned by {@link #currentUser()}. * * @param user the new current user. - * @throws IllegalArgumentException if the user is is not {@link RealmUser.State#LOGGED_IN}. + * @throws IllegalArgumentException if the user is is not {@link User.State#LOGGED_IN}. */ - public RealmUser switchUser(RealmUser user) { + public User switchUser(User user) { Util.checkNull(user, "user"); nativeSwitchUser(nativePtr, user.osUser.getNativePtr()); return user; @@ -249,36 +256,36 @@ public RealmUser switchUser(RealmUser user) { /** * Logs in as a user with the given credentials associated with an authentication provider. *

              - * The user who logs in becomes the current user. Other RealmApp functionality acts on behalf of + * The user who logs in becomes the current user. Other App functionality acts on behalf of * the current user. *

              * If there was already a current user, that user is still logged in and can be found in the * list returned by {@link #allUsers()}. *

              * It is also possible to switch between which user is considered the current user by using - * {@link #switchUser(RealmUser)}. + * {@link #switchUser(User)}. * * @param credentials the credentials representing the type of login. - * @return a {@link RealmUser} representing the logged in user. + * @return a {@link User} representing the logged in user. * @throws ObjectServerError if the user could not be logged in. */ - public RealmUser login(RealmCredentials credentials) throws ObjectServerError { + public User login(Credentials credentials) throws ObjectServerError { Util.checkNull(credentials, "credentials"); - AtomicReference success = new AtomicReference<>(null); + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeLogin(nativePtr, credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { + nativeLogin(nativePtr, credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { @Override - protected RealmUser mapSuccess(Object result) { + protected User mapSuccess(Object result) { Long nativePtr = (Long) result; - return new RealmUser(nativePtr, RealmApp.this); + return new User(nativePtr, App.this); } }); - RealmUser user = ResultHandler.handleResult(success, error); + User user = ResultHandler.handleResult(success, error); notifyUserLoggedIn(user); return user; } - private void notifyUserLoggedIn(RealmUser user) { + private void notifyUserLoggedIn(User user) { mainHandler.post(new Runnable() { @Override public void run() { @@ -289,7 +296,7 @@ public void run() { }); } - void notifyUserLoggedOut(RealmUser user) { + void notifyUserLoggedOut(User user) { mainHandler.post(new Runnable() { @Override public void run() { @@ -303,25 +310,25 @@ public void run() { /** * Logs in as a user with the given credentials associated with an authentication provider. *

              - * The user who logs in becomes the current user. Other RealmApp functionality acts on behalf of + * The user who logs in becomes the current user. Other App functionality acts on behalf of * the current user. *

              * If there was already a current user, that user is still logged in and can be found in the * list returned by {@link #allUsers()}. *

              * It is also possible to switch between which user is considered the current user by using - * {@link #switchUser(RealmUser)}. + * {@link #switchUser(User)}. * * @param credentials the credentials representing the type of login. * @param callback callback when logging in has completed or failed. The callback will always * happen on the same thread as this method is called on. * @throws IllegalStateException if not called on a looper thread. */ - public RealmAsyncTask loginAsync(RealmCredentials credentials, Callback callback) { + public RealmAsyncTask loginAsync(Credentials credentials, Callback callback) { Util.checkLooperThread("Asynchronous log in is only possible from looper threads."); - return new Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public RealmUser run() throws ObjectServerError { + public User run() throws ObjectServerError { return login(credentials); } }.start(); @@ -329,9 +336,9 @@ public RealmUser run() throws ObjectServerError { /** * Returns a wrapper for interacting with functionality related to users either being created or - * logged in using the {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} identity provider. + * logged in using the {@link Credentials.IdentityProvider#EMAIL_PASSWORD} identity provider. * - * @return wrapper for interacting with the {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} identity provider. + * @return wrapper for interacting with the {@link Credentials.IdentityProvider#EMAIL_PASSWORD} identity provider. */ public EmailPasswordAuth getEmailPasswordAuth() { return emailAuthProvider; @@ -371,7 +378,7 @@ public void removeAuthenticationListener(AuthenticationListener listener) { * FIXME: Figure out naming of this method and class. * @return */ - public RealmSync getSync() { + public Sync getSync() { return syncManager; } @@ -381,7 +388,7 @@ public RealmSync getSync() { * This will use the associated app's default codec registry to encode and decode arguments and * results. */ - public Functions getFunctions(RealmUser user) { + public Functions getFunctions(User user) { return new FunctionsImpl(user); } @@ -389,7 +396,7 @@ public Functions getFunctions(RealmUser user) { * Returns a Functions manager for invoking MongoDB Realm Functions with custom * codec registry for encoding and decoding arguments and results. */ - public Functions getFunctions(RealmUser user, CodecRegistry codecRegistry) { + public Functions getFunctions(User user, CodecRegistry codecRegistry) { return new FunctionsImpl(user, codecRegistry); } @@ -399,7 +406,7 @@ public Functions getFunctions(RealmUser user, CodecRegistry codecRegistry) { * * @return the configuration for this app. */ - public RealmAppConfiguration getConfiguration() { + public AppConfiguration getConfiguration() { return config; } @@ -418,73 +425,6 @@ OsJavaNetworkTransport getNetworkTransport() { return networkTransport; } - // Class wrapping requests made against MongoDB Realm. Is also responsible for calling with success/error on the - // correct thread. - // FIXME Made public to use in Functions. Consider reworking when RealmApp, RealmUser is moved - // to mongodb package and async MongoDB API's are settled - public static abstract class Request { - @Nullable - private final RealmApp.Callback callback; - private final RealmNotifier handler; - private final ThreadPoolExecutor networkPoolExecutor; - - // FIXME Made public to use in Functions. Consider reworking when RealmApp, RealmUser is moved - // to mongodb package and async MongoDB API's are settled - public Request(ThreadPoolExecutor networkPoolExecutor, @Nullable RealmApp.Callback callback) { - this.callback = callback; - this.handler = new AndroidRealmNotifier(null, new AndroidCapabilities()); - this.networkPoolExecutor = networkPoolExecutor; - } - - // Implements the request. Return the current sync user if the request succeeded. Otherwise throw an error. - public abstract T run() throws ObjectServerError; - - // Start the request - public RealmAsyncTask start() { - Future authenticateRequest = networkPoolExecutor.submit(new Runnable() { - @Override - public void run() { - try { - postSuccess(Request.this.run()); - } catch (ObjectServerError e) { - postError(e); - } catch (Throwable e) { - postError(new ObjectServerError(ErrorCode.UNKNOWN, "Unexpected error", e)); - } - } - }); - return new RealmAsyncTaskImpl(authenticateRequest, networkPoolExecutor); - } - - private void postError(final ObjectServerError error) { - boolean errorHandled = false; - if (callback != null) { - Runnable action = new Runnable() { - @Override - public void run() { - callback.onResult(Result.withError(error)); - } - }; - errorHandled = handler.post(action); - } - - if (!errorHandled) { - RealmLog.error(error, "An error was thrown, but could not be posted: \n" + error.toString()); - } - } - - private void postSuccess(final T result) { - if (callback != null) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onResult((result == null) ? Result.success() : Result.withResult(result)); - } - }); - } - } - } - /** * Result class representing the result of an async request from this app towards MongoDB Realm. * @@ -581,7 +521,7 @@ public ObjectServerError getError() { } /** - * Callback for async methods available to the {@link RealmApp}. + * Callback for async methods available to the {@link App}. * * @param Type returned if the request was a success. */ diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java similarity index 90% rename from realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index 5e6abbacf1..084e3079bb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmAppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb; import android.content.Context; @@ -37,31 +37,33 @@ import javax.annotation.Nullable; +import io.realm.Realm; +import io.realm.mongodb.sync.SyncSession; import io.realm.internal.Util; import io.realm.log.RealmLog; /** - * A RealmAppConfiguration is used to setup a MongoDB Realm application. + * A AppConfiguration is used to setup a MongoDB Realm application. *

              - * Instances of a RealmAppConfiguration can only created by using the - * {@link io.realm.RealmAppConfiguration.Builder} and calling its - * {@link io.realm.RealmAppConfiguration.Builder#build()} method. + * Instances of a AppConfiguration can only created by using the + * {@link AppConfiguration.Builder} and calling its + * {@link AppConfiguration.Builder#build()} method. *

              - * Configuring a RealmApp is only required if the default settings are not enough. Otherwise calling - * {@code new RealmApp("app-id")} is sufficient. + * Configuring a App is only required if the default settings are not enough. Otherwise calling + * {@code new App("app-id")} is sufficient. */ -public class RealmAppConfiguration { +public class AppConfiguration { /** * The default url for MongoDB Realm applications. - * + * * @see Builder#baseUrl(String) */ public final static String DEFAULT_BASE_URL = "https://realm-dev.mongodb.com"; //FIXME change to production url before beta release /** * The default request timeout for network requests towards MongoDB Realm in seconds. - * + * * @see Builder#requestTimeout(long, TimeUnit) */ public final static long DEFAULT_REQUEST_TIMEOUT = 60; @@ -75,8 +77,8 @@ public class RealmAppConfiguration { /** * Default BSON codec registry for encoding/decoding arguments and results to/from MongoDB Realm backend. * - * @see RealmAppConfiguration#getDefaultCodecRegistry() - * @see RealmAppConfiguration.Builder#codecRegistry(CodecRegistry) + * @see AppConfiguration#getDefaultCodecRegistry() + * @see AppConfiguration.Builder#codecRegistry(CodecRegistry) * @see ValueCodecProvider * @see BsonValueCodecProvider * @see IterableCodecProvider @@ -108,17 +110,17 @@ public class RealmAppConfiguration { private final File syncRootDir; // Root directory for storing Sync related files private final CodecRegistry codecRegistry; - private RealmAppConfiguration(String appId, - String appName, - String appVersion, - URL baseUrl, + private AppConfiguration(String appId, + String appName, + String appVersion, + URL baseUrl, SyncSession.ErrorHandler defaultErrorHandler, @Nullable byte[] encryptionKey, - long requestTimeoutMs, - String authorizationHeaderName, - Map customHeaders, - File syncRootdir, - CodecRegistry codecRegistry) { + long requestTimeoutMs, + String authorizationHeaderName, + Map customHeaders, + File syncRootdir, + CodecRegistry codecRegistry) { this.appId = appId; this.appName = appName; @@ -215,7 +217,7 @@ public File getSyncRootDirectory() { public CodecRegistry getDefaultCodecRegistry() { return codecRegistry; } /** - * Builder used to construct instances of a {@link RealmAppConfiguration} in a fluent manner. + * Builder used to construct instances of a {@link AppConfiguration} in a fluent manner. */ public static class Builder { @@ -254,14 +256,14 @@ public void onError(SyncSession session, ObjectServerError error) { private CodecRegistry codecRegistry = DEFAULT_BSON_CODEC_REGISTRY; /** - * Creates an instance of the Builder for the RealmAppConfiguration. + * Creates an instance of the Builder for the AppConfiguration. * * @param appId the application id of the MongoDB Realm Application. */ public Builder(String appId) { Util.checkEmpty(appId, "appId"); this.appId = appId; - Context context = BaseRealm.applicationContext; + Context context = Realm.getApplicationContext(); if (context == null) { throw new IllegalStateException("Call `Realm.init(Context)` before calling this method."); } @@ -329,7 +331,7 @@ public Builder appVersion(String appVersion) { /** * Sets the default timeout used by network requests against the MongoDB Realm application. * Requests will terminate with a failure if they exceed this limit. The default value is - * {@link RealmAppConfiguration#DEFAULT_REQUEST_TIMEOUT} seconds. + * {@link AppConfiguration#DEFAULT_REQUEST_TIMEOUT} seconds. * * @param time the timeout value for network requests. * @param unit the unit of time used to define the timeout. @@ -436,7 +438,7 @@ private URL createUrl(String baseUrl) { throw new IllegalArgumentException(baseUrl); } } - + // FIXME Doc public Builder codecRegistry(CodecRegistry codecRegistry) { Util.checkNull(codecRegistry, "codecRegistry"); @@ -445,12 +447,12 @@ public Builder codecRegistry(CodecRegistry codecRegistry) { } /** - * Creates the RealmAppConfiguration. + * Creates the AppConfiguration. * - * @return the RealmAppConfiguration that can be used to create a {@link RealmApp}. + * @return the AppConfiguration that can be used to create a {@link App}. */ - public RealmAppConfiguration build() { - return new RealmAppConfiguration(appId, + public AppConfiguration build() { + return new AppConfiguration(appId, appName, appVersion, baseUrl, diff --git a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AuthenticationListener.java similarity index 77% rename from realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/AuthenticationListener.java index ccdef93280..00cffc9b42 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/AuthenticationListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AuthenticationListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb; /** * Interface describing events related to Users and their authentication @@ -23,14 +23,14 @@ public interface AuthenticationListener { /** * A user was logged into the Object Server * - * @param user {@link RealmUser} that is now logged in. + * @param user {@link User} that is now logged in. */ - void loggedIn(RealmUser user); + void loggedIn(User user); /** * A user was successfully logged out from the Object Server. * - * @param user {@link RealmUser} that was successfully logged out. + * @param user {@link User} that was successfully logged out. */ - void loggedOut(RealmUser user); + void loggedOut(User user); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java similarity index 76% rename from realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java index 5bfa854fba..414e48e37d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb; import io.realm.internal.Util; import io.realm.internal.objectstore.OsAppCredentials; +import io.realm.mongodb.auth.EmailPasswordAuth; /** * Credentials represent a login with a given login provider, and are used by the MongoDB Realm to @@ -31,11 +32,11 @@ *

                * {@code
                * // Example
              - * RealmApp app = new RealmApp("app-id");
              - * RealmCredentials credentials = RealmCredentials.emailPassword("email", "password");
              - * RealmUser user = app.loginAsync(credentials, new RealmApp.Callback() {
              + * App app = new App("app-id");
              + * Credentials credentials = Credentials.emailPassword("email", "password");
              + * User user = app.loginAsync(credentials, new App.Callback() {
                *   \@Override
              - *   public void onResult(Result result) {
              + *   public void onResult(Result result) {
                *     if (result.isSuccess() {
                *       handleLogin(result.get());
                *     } else {
              @@ -47,7 +48,7 @@
                * 
              * @see Authentication Providers */ -public class RealmCredentials { +public class Credentials { OsAppCredentials osCredentials; @@ -60,10 +61,10 @@ public class RealmCredentials { * The anonymous user must be linked to another real user to preserve data after a log out. * * @return a set of credentials that can be used to log into MongoDB Realm using - * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static RealmCredentials anonymous() { - return new RealmCredentials(OsAppCredentials.anonymous()); + public static Credentials anonymous() { + return new Credentials(OsAppCredentials.anonymous()); } /** @@ -73,11 +74,11 @@ public static RealmCredentials anonymous() { * * @param key the API key to use for login. * @return a set of credentials that can be used to log into MongoDB Realm using - * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static RealmCredentials apiKey(String key) { + public static Credentials apiKey(String key) { Util.checkEmpty(key, "id"); - return new RealmCredentials(OsAppCredentials.apiKey(key)); + return new Credentials(OsAppCredentials.apiKey(key)); } /** @@ -87,11 +88,11 @@ public static RealmCredentials apiKey(String key) { * * @param idToken the ID token generated when using your Apple login. * @return a set of credentials that can be used to log into MongoDB Realm using - * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static RealmCredentials apple(String idToken) { + public static Credentials apple(String idToken) { Util.checkEmpty(idToken, "idToken"); - return new RealmCredentials(OsAppCredentials.apple(idToken)); + return new Credentials(OsAppCredentials.apple(idToken)); } /** @@ -100,12 +101,12 @@ public static RealmCredentials apple(String idToken) { * This provider must be enabled on MongoDB Realm to work. * * @return a set of credentials that can be used to log into MongoDB Realm using - * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static RealmCredentials customFunction(String functionName, Object... arguments) { + public static Credentials customFunction(String functionName, Object... arguments) { // FIXME: How to check arguments? Util.checkEmpty(functionName, "functionName"); - return new RealmCredentials(OsAppCredentials.customFunction(functionName, arguments)); + return new Credentials(OsAppCredentials.customFunction(functionName, arguments)); } /** @@ -114,12 +115,12 @@ public static RealmCredentials customFunction(String functionName, Object... arg * @param email email of the user logging in. * @param password password of the user logging in. * @return a set of credentials that can be used to log into MongoDB Realm using - * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static RealmCredentials emailPassword(String email, String password) { + public static Credentials emailPassword(String email, String password) { Util.checkEmpty(email, "email"); Util.checkEmpty(password, "password"); - return new RealmCredentials(OsAppCredentials.emailPassword(email, password)); + return new Credentials(OsAppCredentials.emailPassword(email, password)); } /** @@ -129,11 +130,11 @@ public static RealmCredentials emailPassword(String email, String password) { * * @param accessToken the access token returned when logging in to Facebook. * @return a set of credentials that can be used to log into MongoDB Realm using - * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static RealmCredentials facebook(String accessToken) { + public static Credentials facebook(String accessToken) { Util.checkEmpty(accessToken, "accessToken"); - return new RealmCredentials(OsAppCredentials.facebook(accessToken)); + return new Credentials(OsAppCredentials.facebook(accessToken)); } /** @@ -143,11 +144,11 @@ public static RealmCredentials facebook(String accessToken) { * * @param googleToken the access token returned when logging in to Google. * @return a set of credentials that can be used to log into MongoDB Realm using - * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static RealmCredentials google(String googleToken) { + public static Credentials google(String googleToken) { Util.checkEmpty(googleToken, "googleToken"); - return new RealmCredentials(OsAppCredentials.google(googleToken)); + return new Credentials(OsAppCredentials.google(googleToken)); } /** @@ -158,11 +159,11 @@ public static RealmCredentials google(String googleToken) { * * @param jwtToken the jwt token returned after a custom login to a another service. * @return a set of credentials that can be used to log into MongoDB Realm using - * {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)}. + * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static RealmCredentials jwt(String jwtToken) { + public static Credentials jwt(String jwtToken) { Util.checkEmpty(jwtToken, "jwtToken"); - return new RealmCredentials(OsAppCredentials.jwt(jwtToken)); + return new Credentials(OsAppCredentials.jwt(jwtToken)); } /** @@ -183,7 +184,7 @@ public String asJson() { return osCredentials.asJson(); } - private RealmCredentials(OsAppCredentials credentials) { + private Credentials(OsAppCredentials credentials) { this.osCredentials = credentials; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java new file mode 100644 index 0000000000..a6f33deb60 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.mongodb.auth.EmailPasswordAuth; + +class EmailPasswordAuthImpl extends EmailPasswordAuth { + + EmailPasswordAuthImpl(App app) { + super(app); + } + + @Override + protected void call(int functionType, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback, String... args) { + nativeCallFunction(functionType, app.nativePtr, callback, args); + } + + private static native void nativeCallFunction(int functionType, + long appNativePtr, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback, + String... args); + +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java similarity index 99% rename from realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java index fbd4e14b79..8cdfe9db2a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,14 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb; import java.util.Locale; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.log.RealmLog; +import io.realm.mongodb.sync.SyncConfiguration; /** * This class enumerate all potential errors related to using the Object Server or synchronizing data. diff --git a/realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java similarity index 95% rename from realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java index 4cc31ff803..f4b8d4bbdb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/FunctionsImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb; import org.bson.BSONException; import org.bson.BsonElement; @@ -33,15 +33,15 @@ /** * Internal implementation of Functions invoking the actual OS function in the context of the - * {@link RealmUser}/{@link RealmApp}. + * {@link User}/{@link App}. */ class FunctionsImpl extends Functions { - FunctionsImpl(RealmUser user) { + FunctionsImpl(User user) { this(user, user.getApp().getConfiguration().getDefaultCodecRegistry()); } - FunctionsImpl(RealmUser user, CodecRegistry codecRegistry) { + FunctionsImpl(User user, CodecRegistry codecRegistry) { super(user, codecRegistry); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java similarity index 98% rename from realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java index 4cb2fa961b..83fb9ef247 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ObjectServerError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,12 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb; import javax.annotation.Nullable; import io.realm.internal.Util; +import io.realm.mongodb.sync.SyncSession; /** * This class is a wrapper for all errors happening when communicating with the Realm Object Server. diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java similarity index 80% rename from realm/realm-library/src/objectServer/java/io/realm/RealmUser.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index 490becf24e..75eaa6351c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb; import org.bson.codecs.configuration.CodecRegistry; @@ -24,6 +24,10 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.internal.mongodb.Request; +import io.realm.internal.objectstore.OsMongoClient; +import io.realm.mongodb.auth.ApiKeyAuth; +import io.realm.RealmAsyncTask; import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; import io.realm.internal.jni.OsJNIResultCallback; @@ -33,14 +37,15 @@ import io.realm.internal.util.Pair; import io.realm.mongodb.functions.Functions; import io.realm.mongodb.mongo.MongoClient; +import io.realm.mongodb.push.Push; /** * FIXME */ -public class RealmUser { +public class User { OsSyncUser osUser; - private final RealmApp app; + private final App app; private ApiKeyAuth apiKeyAuthProvider = null; private MongoClient mongoClient = null; private Functions functions = null; @@ -80,8 +85,13 @@ byte getKey() { } } + private static class MongoClientImpl extends MongoClient { + protected MongoClientImpl(OsMongoClient osMongoClient, CodecRegistry codecRegistry) { + super(osMongoClient, codecRegistry); + } + } - RealmUser(long nativePtr, RealmApp app) { + User(long nativePtr, App app) { this.osUser = new OsSyncUser(nativePtr); this.app = app; } @@ -180,12 +190,12 @@ public Long getMaxAge() { * FIXME * @return */ - public List getIdentities() { + public List getIdentities() { Pair[] osIdentities = osUser.getIdentities(); - List identities = new ArrayList<>(osIdentities.length); + List identities = new ArrayList<>(osIdentities.length); for (int i = 0; i < osIdentities.length; i++) { Pair data = osIdentities[i]; - identities.add(new RealmUserIdentity(data.first, data.second)); + identities.add(new UserIdentity(data.first, data.second)); } return identities; } @@ -217,11 +227,11 @@ public String getDeviceId() { } /** - * Returns the {@link RealmApp} this user is associated with. + * Returns the {@link App} this user is associated with. * - * @return the {@link RealmApp} this user is associated with. + * @return the {@link App} this user is associated with. */ - public RealmApp getApp() { + public App getApp() { return app; } @@ -260,9 +270,9 @@ public boolean isLoggedIn() { *
                    * {@code
                    * // Example
              -     * RealmApp app = new RealmApp("app-id")
              -     * RealmUser user = app.login(RealmCredentials.anonymous());
              -     * user.linkCredentials(RealmCredentials.emailPassword("email", "password"));
              +     * App app = new App("app-id")
              +     * User user = app.login(Credentials.anonymous());
              +     * user.linkCredentials(Credentials.emailPassword("email", "password"));
                    * }
                    * 
              *

              @@ -271,18 +281,18 @@ public boolean isLoggedIn() { * * @param credentials the credentials to link with the current user. * @throws IllegalStateException if no user is currently logged in. - * @return the {@link io.realm.RealmUser} the credentials were linked to. + * @return the {@link User} the credentials were linked to. */ - public RealmUser linkCredentials(RealmCredentials credentials) { + public User linkCredentials(Credentials credentials) { Util.checkNull(credentials, "credentials"); checkLoggedIn(); - AtomicReference success = new AtomicReference<>(null); + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeLinkUser(app.nativePtr, osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { + nativeLinkUser(app.nativePtr, osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { @Override - protected RealmUser mapSuccess(Object result) { + protected User mapSuccess(Object result) { osUser = new OsSyncUser((long) result); // OS returns the updated user as a new one. - return RealmUser.this; + return User.this; } }); return ResultHandler.handleResult(success, error); @@ -297,9 +307,9 @@ protected RealmUser mapSuccess(Object result) { *

                    * {@code
                    * // Example
              -     * RealmApp app = new RealmApp("app-id")
              -     * RealmUser user = app.login(RealmCredentials.anonymous());
              -     * user.linkCredentials(RealmCredentials.emailPassword("email", "password"));
              +     * App app = new App("app-id")
              +     * User user = app.login(Credentials.anonymous());
              +     * user.linkCredentials(Credentials.emailPassword("email", "password"));
                    * }
                    * 
              *

              @@ -311,11 +321,11 @@ protected RealmUser mapSuccess(Object result) { * always happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask linkCredentialsAsync(RealmCredentials credentials, RealmApp.Callback callback) { + public RealmAsyncTask linkCredentialsAsync(Credentials credentials, App.Callback callback) { Util.checkLooperThread("Asynchronous linking identities is only possible from looper threads."); - return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { + return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override - public RealmUser run() throws ObjectServerError { + public User run() throws ObjectServerError { return linkCredentials(credentials); } }.start(); @@ -330,14 +340,14 @@ public RealmUser run() throws ObjectServerError { * @throws ObjectServerError if called from the UI thread or if the user was logged in, but * could not be logged out. */ - public RealmUser remove() throws ObjectServerError { + public User remove() throws ObjectServerError { boolean loggedIn = isLoggedIn(); - AtomicReference success = new AtomicReference<>(null); + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeRemoveUser(app.nativePtr, osUser.getNativePtr(), new OsJNIResultCallback(success, error) { + nativeRemoveUser(app.nativePtr, osUser.getNativePtr(), new OsJNIResultCallback(success, error) { @Override - protected RealmUser mapSuccess(Object result) { - return RealmUser.this; + protected User mapSuccess(Object result) { + return User.this; } }); ResultHandler.handleResult(success, error); @@ -356,11 +366,11 @@ protected RealmUser mapSuccess(Object result) { * happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask removeAsync(RealmApp.Callback callback) { + public RealmAsyncTask removeAsync(App.Callback callback) { Util.checkLooperThread("Asynchronous removal of users is only possible from looper threads."); - return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { + return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override - public RealmUser run() throws ObjectServerError { + public User run() throws ObjectServerError { return remove(); } }.start(); @@ -376,8 +386,8 @@ public RealmUser run() throws ObjectServerError { * will be notified and user credentials will be deleted from this device. *

              * Logging out anonymous users will remove them immediately instead of marking them as - * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} - * and will still be returned by {@link RealmApp#allUsers()}. They can be removed completely by calling + * {@link User.State#LOGGED_OUT}. All other users will be marked as {@link User.State#LOGGED_OUT} + * and will still be returned by {@link App#allUsers()}. They can be removed completely by calling * {@link #remove()}. * * @throws ObjectServerError if an error occurred while trying to log the user out of the Realm @@ -403,21 +413,21 @@ public void logOut() throws ObjectServerError { * will be notified and user credentials will be deleted from this device. *

              * Logging out anonymous users will remove them immediately instead of marking them as - * {@link RealmUser.State#LOGGED_OUT}. All other users will be marked as {@link RealmUser.State#LOGGED_OUT} - * and will still be returned by {@link RealmApp#allUsers()}. They can be removed completely by calling + * {@link User.State#LOGGED_OUT}. All other users will be marked as {@link User.State#LOGGED_OUT} + * and will still be returned by {@link App#allUsers()}. They can be removed completely by calling * {@link #remove()}. * * @param callback callback when logging out has completed or failed. The callback will always * happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask logOutAsync(RealmApp.Callback callback) { + public RealmAsyncTask logOutAsync(App.Callback callback) { Util.checkLooperThread("Asynchronous log out is only possible from looper threads."); - return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { + return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override - public RealmUser run() throws ObjectServerError { + public User run() throws ObjectServerError { logOut(); - return RealmUser.this; + return User.this; } }.start(); } @@ -431,7 +441,7 @@ public RealmUser run() throws ObjectServerError { public synchronized ApiKeyAuth getApiKeyAuth() { checkLoggedIn(); if (apiKeyAuthProvider == null) { - apiKeyAuthProvider = new ApiKeyAuth(this); + apiKeyAuthProvider = new ApiKeyAuthImpl(this); } return apiKeyAuthProvider; } @@ -461,7 +471,7 @@ public Functions getFunctions(CodecRegistry codecRegistry) { /** * FIXME Add support for push notifications. Name of Class and method still TBD. */ - public RealmPushNotifications getPushNotifications() { + public Push getPushNotifications() { return null; } @@ -469,8 +479,10 @@ public RealmPushNotifications getPushNotifications() { * FIXME Add support for the MongoDB wrapper. Name of Class and method still TBD. */ public MongoClient getMongoClient(String serviceName) { + Util.checkEmpty(serviceName, "serviceName"); if (mongoClient == null) { - mongoClient = new MongoClient(this, serviceName, app.getConfiguration().getDefaultCodecRegistry()); + OsMongoClient osMongoClient = new OsMongoClient(app.nativePtr, serviceName); + mongoClient = new MongoClientImpl(osMongoClient, app.getConfiguration().getDefaultCodecRegistry()); } return mongoClient; } @@ -481,10 +493,10 @@ public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - RealmUser realmUser = (RealmUser) o; + User user = (User) o; - if (!osUser.equals(realmUser.osUser)) return false; - return app.equals(realmUser.app); + if (!osUser.equals(user.osUser)) return false; + return app.equals(user.app); } @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUserIdentity.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java similarity index 77% rename from realm/realm-library/src/objectServer/java/io/realm/RealmUserIdentity.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java index 70a47f9d11..f133d65325 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUserIdentity.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java @@ -13,24 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb; /** - * Each RealmUser is represented by 1 or more identities each defined by an - * {@link RealmCredentials.IdentityProvider}. + * Each User is represented by 1 or more identities each defined by an + * {@link Credentials.IdentityProvider}. * * This class represents the identity defined by a specific provider. */ -public class RealmUserIdentity { +public class UserIdentity { private final String userId; private final String providerId; - private final RealmCredentials.IdentityProvider provider; + private final Credentials.IdentityProvider provider; - RealmUserIdentity(String id, String providerId) { + UserIdentity(String id, String providerId) { this.userId = id; this.providerId = providerId; - this.provider = RealmCredentials.IdentityProvider.fromId(providerId); + this.provider = Credentials.IdentityProvider.fromId(providerId); } /** @@ -47,7 +47,7 @@ public String getId() { * * @return */ - public RealmCredentials.IdentityProvider getProvider() { + public Credentials.IdentityProvider getProvider() { return provider; } @@ -56,7 +56,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - RealmUserIdentity that = (RealmUserIdentity) o; + UserIdentity that = (UserIdentity) o; if (!userId.equals(that.userId)) return false; if (!providerId.equals(that.providerId)) return false; @@ -73,7 +73,7 @@ public int hashCode() { @Override public String toString() { - return "RealmUserIdentity{" + + return "UserIdentity{" + "userId='" + userId + '\'' + ", providerId='" + providerId + '\'' + '}'; diff --git a/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java similarity index 68% rename from realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java index 5eb4ccfcf5..5fc121f50c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ApiKeyAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb.auth; import org.bson.types.ObjectId; @@ -23,18 +23,23 @@ import javax.annotation.Nullable; +import io.realm.internal.mongodb.Request; +import io.realm.mongodb.ObjectServerError; +import io.realm.RealmAsyncTask; import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.jni.OsJNIVoidResultCallback; import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.mongodb.App; +import io.realm.mongodb.User; -import static io.realm.RealmApp.NETWORK_POOL_EXECUTOR; +import static io.realm.mongodb.App.NETWORK_POOL_EXECUTOR; /** * This class exposes functionality for a user to manage API keys under their control. */ -public class ApiKeyAuth { +public abstract class ApiKeyAuth { private static final int TYPE_CREATE = 1; private static final int TYPE_FETCH_SINGLE = 2; @@ -43,22 +48,32 @@ public class ApiKeyAuth { private static final int TYPE_DISABLE = 5; private static final int TYPE_ENABLE = 6; - private final RealmUser user; + private final User user; /** * Create an instance of this class for a specific user. * * @param user user that is controlling the API keys. */ - public ApiKeyAuth(RealmUser user) { + protected ApiKeyAuth(User user) { this.user = user; } - public RealmUser getUser() { + /** + * Returns the {@link User} that this instance in associated with. + * + * @return The {@link User} that this instance in associated with. + */ + public User getUser() { return user; } - public RealmApp getApp() { + /** + * Returns the {@link App} that this instance in associated with. + * + * @return The {@link App} that this instance in associated with. + */ + public App getApp() { return user.getApp(); } @@ -70,20 +85,20 @@ public RealmApp getApp() { * The key is enabled when created. It can be disabled by calling {@link #disableApiKey(ObjectId)}. * * @param name the name of the key - * @throws ObjectServer if the server failed to create the API key. + * @throws ObjectServerError if the server failed to create the API key. * @return the new API key for the user. */ - public RealmUserApiKey createApiKey(String name) throws ObjectServerError { + public UserApiKey createApiKey(String name) throws ObjectServerError { Util.checkEmpty(name, "name"); - AtomicReference success = new AtomicReference<>(null); + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override - protected RealmUserApiKey mapSuccess(Object result) { + protected UserApiKey mapSuccess(Object result) { return createKeyFromNative((Object[]) result); } }; - nativeCallFunction(TYPE_CREATE, user.getApp().nativePtr, user.osUser.getNativePtr(), name, callback); + call(TYPE_CREATE, name, callback); return ResultHandler.handleResult(success, error); } @@ -99,11 +114,11 @@ protected RealmUserApiKey mapSuccess(Object result) { * happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask createApiKeyAsync(String name, RealmApp.Callback callback) { + public RealmAsyncTask createApiKeyAsync(String name, App.Callback callback) { Util.checkLooperThread("Asynchronous creation of api keys are only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public RealmUserApiKey run() throws ObjectServerError { + public UserApiKey run() throws ObjectServerError { return createApiKey(name); } }.start(); @@ -113,15 +128,15 @@ public RealmUserApiKey run() throws ObjectServerError { * Fetches a specific user API key associated with the user. * * @param id the id of the key to fetch. - * @throws ObjectServer if the server failed to fetch the API key. + * @throws ObjectServerError if the server failed to fetch the API key. */ - public RealmUserApiKey fetchApiKey(ObjectId id) throws ObjectServerError { + public UserApiKey fetchApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); - AtomicReference success = new AtomicReference<>(null); + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_FETCH_SINGLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIResultCallback(success, error) { + call(TYPE_FETCH_SINGLE, id.toHexString(), new OsJNIResultCallback(success, error) { @Override - protected RealmUserApiKey mapSuccess(Object result) { + protected UserApiKey mapSuccess(Object result) { return createKeyFromNative((Object[]) result); } }); @@ -136,11 +151,11 @@ protected RealmUserApiKey mapSuccess(Object result) { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask fetchApiKeyAsync(ObjectId id, RealmApp.Callback callback) { + public RealmAsyncTask fetchApiKeyAsync(ObjectId id, App.Callback callback) { Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public RealmUserApiKey run() throws ObjectServerError { + public UserApiKey run() throws ObjectServerError { return fetchApiKey(id); } }.start(); @@ -149,16 +164,16 @@ public RealmUserApiKey run() throws ObjectServerError { /** * Fetches all API keys associated with the user. * - * @throws ObjectServer if the server failed to fetch the API keys. + * @throws ObjectServerError if the server failed to fetch the API keys. */ - public List fetchAllApiKeys() throws ObjectServerError { - AtomicReference> success = new AtomicReference<>(null); + public List fetchAllApiKeys() throws ObjectServerError { + AtomicReference> success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_FETCH_ALL, user.getApp().nativePtr, user.osUser.getNativePtr(), null, new OsJNIResultCallback>(success, error) { + call(TYPE_FETCH_ALL, null, new OsJNIResultCallback>(success, error) { @Override - protected List mapSuccess(Object result) { + protected List mapSuccess(Object result) { Object[] keyData = (Object[]) result; - List list = new ArrayList<>(); + List list = new ArrayList<>(); for (int i = 0; i < keyData.length; i++) { list.add(createKeyFromNative((Object[]) keyData[i])); } @@ -176,11 +191,11 @@ protected List mapSuccess(Object result) { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask fetchAllApiKeys(RealmApp.Callback> callback) { + public RealmAsyncTask fetchAllApiKeys(App.Callback> callback) { Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); - return new RealmApp.Request>(NETWORK_POOL_EXECUTOR, callback) { + return new Request>(NETWORK_POOL_EXECUTOR, callback) { @Override - public List run() throws ObjectServerError { + public List run() throws ObjectServerError { return fetchAllApiKeys(); } }.start(); @@ -190,12 +205,12 @@ public List run() throws ObjectServerError { * Deletes a specific API key created by the user. * * @param id the id of the key to delete. - * @throws ObjectServer if the server failed to delete the API key. + * @throws ObjectServerError if the server failed to delete the API key. */ public void deleteApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_DELETE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIVoidResultCallback(error)); + call(TYPE_DELETE, id.toHexString(), new OsJNIVoidResultCallback(error)); ResultHandler.handleResult(null, error); } @@ -207,9 +222,9 @@ public void deleteApiKey(ObjectId id) throws ObjectServerError { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask deleteApiKeyAsync(ObjectId id, RealmApp.Callback callback) { + public RealmAsyncTask deleteApiKeyAsync(ObjectId id, App.Callback callback) { Util.checkLooperThread("Asynchronous deleting an api key is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { deleteApiKey(id); @@ -222,12 +237,12 @@ public Void run() throws ObjectServerError { * Disables a specific API key created by the user. * * @param id the id of the key to disable. - * @throws ObjectServer if the server failed to disable the API key. + * @throws ObjectServerError if the server failed to disable the API key. */ public void disableApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_DISABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIVoidResultCallback(error)); + call(TYPE_DISABLE, id.toHexString(), new OsJNIVoidResultCallback(error)); ResultHandler.handleResult(null, error); } @@ -239,9 +254,9 @@ public void disableApiKey(ObjectId id) throws ObjectServerError { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask disableApiKeyAsync(ObjectId id, RealmApp.Callback callback) { + public RealmAsyncTask disableApiKeyAsync(ObjectId id, App.Callback callback) { Util.checkLooperThread("Asynchronous disabling an api key is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { disableApiKey(id); @@ -254,12 +269,12 @@ public Void run() throws ObjectServerError { * Enables a specific API key created by the user. * * @param id the id of the key to enable. - * @throws ObjectServer if the server failed to enable the API key. + * @throws ObjectServerError if the server failed to enable the API key. */ public void enableApiKey(ObjectId id) throws ObjectServerError { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_ENABLE, user.getApp().nativePtr, user.osUser.getNativePtr(), id.toHexString(), new OsJNIVoidResultCallback(error)); + call(TYPE_ENABLE, id.toHexString(), new OsJNIVoidResultCallback(error)); ResultHandler.handleResult(null, error); } @@ -271,9 +286,9 @@ public void enableApiKey(ObjectId id) throws ObjectServerError { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask enableApiKeyAsync(ObjectId id, RealmApp.Callback callback) { + public RealmAsyncTask enableApiKeyAsync(ObjectId id, App.Callback callback) { Util.checkLooperThread("Asynchronous enabling an api key is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { enableApiKey(id); @@ -282,8 +297,8 @@ public Void run() throws ObjectServerError { }.start(); } - private RealmUserApiKey createKeyFromNative(Object[] keyData) { - return new RealmUserApiKey(new ObjectId((String) keyData[0]), + private UserApiKey createKeyFromNative(Object[] keyData) { + return new UserApiKey(new ObjectId((String) keyData[0]), (String) keyData[1], (String) keyData[2], !(Boolean) keyData[3]); // Server returns disabled state instead of enabled @@ -311,5 +326,6 @@ public String toString() { '}'; } - private static native void nativeCallFunction(int functionType, long nativeAppPtr, long nativeUserPtr, @Nullable String arg, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + abstract protected void call(int functionType, @Nullable String arg, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java similarity index 80% rename from realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java index 23ec061eb9..4a0aa6f83e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/EmailPasswordAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java @@ -13,24 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb.auth; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; +import io.realm.internal.mongodb.Request; +import io.realm.mongodb.ObjectServerError; +import io.realm.RealmAsyncTask; import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIVoidResultCallback; import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.mongodb.App; +import io.realm.mongodb.Credentials; +import io.realm.mongodb.User; -import static io.realm.RealmApp.NETWORK_POOL_EXECUTOR; +import static io.realm.mongodb.App.NETWORK_POOL_EXECUTOR; /** - * Class encapsulating functionality provided when {@link RealmUser}'s are logged in through the - * {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} provider. + * Class encapsulating functionality provided when {@link User}'s are logged in through the + * {@link Credentials.IdentityProvider#EMAIL_PASSWORD} provider. */ -public class EmailPasswordAuth { +public abstract class EmailPasswordAuth { private static final int TYPE_REGISTER_USER = 1; private static final int TYPE_CONFIRM_USER = 2; @@ -39,13 +45,13 @@ public class EmailPasswordAuth { private static final int TYPE_CALL_RESET_PASSWORD_FUNCTION = 5; private static final int TYPE_RESET_PASSWORD = 6; - private final RealmApp app; + protected final App app; /** * Creates an authentication provider exposing functionality to using an email and password * for login into a Realm Application. */ - public EmailPasswordAuth(RealmApp app) { + protected EmailPasswordAuth(App app) { this.app = app; } @@ -62,10 +68,7 @@ public void registerUser(String email, String password) throws ObjectServerError Util.checkEmpty(email, "email"); Util.checkEmpty(password, "password"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_REGISTER_USER, - app.nativePtr, - new OsJNIVoidResultCallback(error), - email, password); + call(TYPE_REGISTER_USER, new OsJNIVoidResultCallback(error), email, password); ResultHandler.handleResult(null, error); } @@ -81,9 +84,9 @@ public void registerUser(String email, String password) throws ObjectServerError * @throws IllegalStateException if called from a non-looper thread. * @throws ObjectServerError if the server failed to register the user. */ - public RealmAsyncTask registerUserAsync(String email, String password, RealmApp.Callback callback) { + public RealmAsyncTask registerUserAsync(String email, String password, App.Callback callback) { Util.checkLooperThread("Asynchronous registration of a user is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { registerUser(email, password); @@ -103,10 +106,7 @@ public void confirmUser(String token, String tokenId) throws ObjectServerError { Util.checkEmpty(token, "token"); Util.checkEmpty(tokenId, "tokenId"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_CONFIRM_USER, - app.nativePtr, - new OsJNIVoidResultCallback(error), - token, tokenId); + call(TYPE_CONFIRM_USER, new OsJNIVoidResultCallback(error), token, tokenId); ResultHandler.handleResult(null, error); } @@ -119,9 +119,9 @@ public void confirmUser(String token, String tokenId) throws ObjectServerError { * happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask confirmUserAsync(String token, String tokenId, RealmApp.Callback callback) { + public RealmAsyncTask confirmUserAsync(String token, String tokenId, App.Callback callback) { Util.checkLooperThread("Asynchronous confirmation of a user is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { confirmUser(token, tokenId); @@ -139,10 +139,7 @@ public Void run() throws ObjectServerError { public void resendConfirmationEmail(String email) throws ObjectServerError { Util.checkEmpty(email, "email"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_RESEND_CONFIRMATION_EMAIL, - app.nativePtr, - new OsJNIVoidResultCallback(error), - email); + call(TYPE_RESEND_CONFIRMATION_EMAIL, new OsJNIVoidResultCallback(error), email); ResultHandler.handleResult(null, error); } @@ -154,9 +151,9 @@ public void resendConfirmationEmail(String email) throws ObjectServerError { * always happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask resendConfirmationEmailAsync(String email, RealmApp.Callback callback) { + public RealmAsyncTask resendConfirmationEmailAsync(String email, App.Callback callback) { Util.checkLooperThread("Asynchronous resending the confirmation email is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { resendConfirmationEmail(email); @@ -174,10 +171,7 @@ public Void run() throws ObjectServerError { public void sendResetPasswordEmail(String email) throws ObjectServerError { Util.checkEmpty(email, "email"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_SEND_RESET_PASSWORD_EMAIL, - app.nativePtr, - new OsJNIVoidResultCallback(error), - email); + call(TYPE_SEND_RESET_PASSWORD_EMAIL, new OsJNIVoidResultCallback(error), email); ResultHandler.handleResult(null, error); } @@ -189,9 +183,9 @@ public void sendResetPasswordEmail(String email) throws ObjectServerError { * always happen on the same thread as this method is called on. * @throws ObjectServerError if the server failed to confirm the user. */ - public RealmAsyncTask sendResetPasswordEmailAsync(String email, RealmApp.Callback callback) { + public RealmAsyncTask sendResetPasswordEmailAsync(String email, App.Callback callback) { Util.checkLooperThread("Asynchronous sending the reset password email is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { sendResetPasswordEmail(email); @@ -202,7 +196,7 @@ public Void run() throws ObjectServerError { /** * Call the reset password function configured to the - * {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} provider. + * {@link Credentials.IdentityProvider#EMAIL_PASSWORD} provider. * * @param email the email of the user. * @param newPassword the new password of the user. @@ -215,16 +209,13 @@ public void callResetPasswordFunction(String email, String newPassword, Object.. Util.checkEmpty(newPassword, "newPassword"); String encodedArgs = JniBsonProtocol.encode(Arrays.asList(args), app.getConfiguration().getDefaultCodecRegistry()); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_CALL_RESET_PASSWORD_FUNCTION, - app.nativePtr, - new OsJNIVoidResultCallback(error), - email, newPassword, encodedArgs); + call(TYPE_CALL_RESET_PASSWORD_FUNCTION, new OsJNIVoidResultCallback(error), email, newPassword, encodedArgs); ResultHandler.handleResult(null, error); } /** * Call the reset password function configured to the - * {@link RealmCredentials.IdentityProvider#EMAIL_PASSWORD} provider. + * {@link Credentials.IdentityProvider#EMAIL_PASSWORD} provider. * * @param email the email of the user. * @param newPassword the new password of the user. @@ -234,9 +225,9 @@ public void callResetPasswordFunction(String email, String newPassword, Object.. * happen on the same thread as this this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask callResetPasswordFunctionAsync(String email, String newPassword, Object[] args, RealmApp.Callback callback) { + public RealmAsyncTask callResetPasswordFunctionAsync(String email, String newPassword, Object[] args, App.Callback callback) { Util.checkLooperThread("Asynchronous calling the password reset function is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { callResetPasswordFunction(email, newPassword, args); @@ -259,10 +250,7 @@ public void resetPassword(String token, String tokenId, String newPassword) thro Util.checkEmpty(tokenId, "tokenId"); Util.checkEmpty(newPassword, "newPassword"); AtomicReference error = new AtomicReference<>(null); - nativeCallFunction(TYPE_RESET_PASSWORD, - app.nativePtr, - new OsJNIVoidResultCallback(error), - token, tokenId, newPassword); + call(TYPE_RESET_PASSWORD, new OsJNIVoidResultCallback(error), token, tokenId, newPassword); ResultHandler.handleResult(null, error); } @@ -277,9 +265,9 @@ public void resetPassword(String token, String tokenId, String newPassword) thro * happen on the same thread as this this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask resetPasswordAsync(String token, String tokenId, String newPassword, RealmApp.Callback callback) { + public RealmAsyncTask resetPasswordAsync(String token, String tokenId, String newPassword, App.Callback callback) { Util.checkLooperThread("Asynchronous reset of a password is only possible from looper threads."); - return new RealmApp.Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws ObjectServerError { resetPassword(token, tokenId, newPassword); @@ -288,8 +276,6 @@ public Void run() throws ObjectServerError { }.start(); } - private static native void nativeCallFunction(int functionType, - long appNativePtr, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback, - String... args); + protected abstract void call(int functionType, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback, String... args); + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmUserApiKey.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java similarity index 85% rename from realm/realm-library/src/objectServer/java/io/realm/RealmUserApiKey.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java index 05c09a734d..76dbf2bd1b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmUserApiKey.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java @@ -13,28 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb.auth; import org.bson.types.ObjectId; import javax.annotation.Nullable; +import io.realm.mongodb.App; +import io.realm.mongodb.User; + /** - * Class representing an API key for a {@link RealmUser}. An API can be used to represent the + * Class representing an API key for a {@link User}. An API can be used to represent the * user when logging instead of using email and password. *

              - * These keys are created and managed through {@link RealmApp#getApiKeyAuthProvider()}. + * These keys are created or fetched through {@link ApiKeyAuth#createApiKey(String)} or the various + * {@code fetch}-methods. *

              * Note that a keys {@link #value} is only available when the key is created, after that it is not * visible. So anyone creating an API key is responsible for storing it safely after that. */ -public class RealmUserApiKey { +public class UserApiKey { private final ObjectId id; private final String value; private final String name; private final boolean enabled; - RealmUserApiKey(ObjectId id, @Nullable String value, String name, boolean enabled) { + UserApiKey(ObjectId id, @Nullable String value, String name, boolean enabled) { this.id = id; this.value = value; this.name = name; @@ -85,7 +89,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - RealmUserApiKey that = (RealmUserApiKey) o; + UserApiKey that = (UserApiKey) o; if (enabled != that.enabled) return false; if (!id.equals(that.id)) return false; @@ -104,7 +108,7 @@ public int hashCode() { @Override public String toString() { - return "RealmUserApiKey{" + + return "UserApiKey{" + "id=" + id + ", value='" + value + '\'' + ", name='" + name + '\'' + diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java index 2774b38adb..7d30b32b07 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java @@ -17,41 +17,41 @@ package io.realm.mongodb.functions; import org.bson.codecs.Decoder; -import org.bson.codecs.configuration.CodecConfigurationException; import org.bson.codecs.configuration.CodecRegistry; import java.util.List; -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.RealmApp; -import io.realm.RealmAppConfiguration; import io.realm.RealmAsyncTask; -import io.realm.RealmUser; import io.realm.internal.Util; +import io.realm.internal.mongodb.Request; +import io.realm.mongodb.App; +import io.realm.mongodb.AppConfiguration; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.User; /** * A Functions manager to call MongoDB Realm functions. *

              * Arguments and results are encoded/decoded with the Functions' codec registry either - * inherited from the {@link RealmAppConfiguration#getDefaultCodecRegistry()} or set explicitly - * when creating the Functions-instance through {@link RealmUser#getFunctions(CodecRegistry)} + * inherited from the {@link AppConfiguration#getDefaultCodecRegistry()} or set explicitly + * when creating the Functions-instance through {@link User#getFunctions(CodecRegistry)} * or through the individual calls to {@link #callFunction(String, List, Class, CodecRegistry)}. * - * @see RealmUser#getFunctions() - * @see RealmUser#getFunctions(CodecRegistry) - * @see RealmApp#getFunctions(RealmUser) - * @see RealmApp#getFunctions(RealmUser, CodecRegistry) - * @see RealmAppConfiguration + * @see User#getFunctions() + * @see User#getFunctions(CodecRegistry) + * @see App#getFunctions(User) + * @see App#getFunctions(User, CodecRegistry) + * @see AppConfiguration * @see CodecRegistry */ public abstract class Functions { - protected RealmUser user; + protected User user; private CodecRegistry defaultCodecRegistry; - protected Functions(RealmUser user, CodecRegistry codecRegistry) { + protected Functions(User user, CodecRegistry codecRegistry) { this.user = user; this.defaultCodecRegistry = codecRegistry; } @@ -69,8 +69,8 @@ protected Functions(RealmUser user, CodecRegistry codecRegistry) { * * @throws ObjectServerError if the request failed in some way. * - * @see #callFunctionAsync(String, List, Class, CodecRegistry, RealmApp.Callback) - * @see RealmAppConfiguration#getDefaultCodecRegistry() + * @see #callFunctionAsync(String, List, Class, CodecRegistry, App.Callback) + * @see AppConfiguration#getDefaultCodecRegistry() */ public ResultT callFunction(String name, List args, Class resultClass, CodecRegistry codecRegistry) { return invoke(name, args, codecRegistry, decoder(codecRegistry, resultClass)); @@ -89,7 +89,7 @@ public ResultT callFunction(String name, List args, Class * @throws ObjectServerError if the request failed in some way. * * @see #callFunction(String, List, Class, CodecRegistry) - * @see RealmAppConfiguration#getDefaultCodecRegistry() + * @see AppConfiguration#getDefaultCodecRegistry() */ public ResultT callFunction(String name, List args, Class resultClass) { return callFunction(name, args, resultClass, defaultCodecRegistry); @@ -109,7 +109,7 @@ public ResultT callFunction(String name, List args, Class * @throws ObjectServerError if the request failed in some way. * * @see #callFunction(String, List, Class, CodecRegistry) - * @see RealmAppConfiguration#getDefaultCodecRegistry() + * @see AppConfiguration#getDefaultCodecRegistry() */ public ResultT callFunction(String name, List args, Decoder resultDecoder) { return invoke(name, args, defaultCodecRegistry, resultDecoder); @@ -132,12 +132,12 @@ public ResultT callFunction(String name, List args, Decoder RealmAsyncTask callFunctionAsync(String name, List args, Class resultClass, CodecRegistry codecRegistry, RealmApp.Callback callback) { + public RealmAsyncTask callFunctionAsync(String name, List args, Class resultClass, CodecRegistry codecRegistry, App.Callback callback) { Util.checkLooperThread("Asynchronous functions is only possible from looper threads."); - return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { + return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override public T run() throws ObjectServerError { return invoke(name, args, codecRegistry, decoder(codecRegistry, resultClass)); @@ -161,10 +161,10 @@ public T run() throws ObjectServerError { * @throws IllegalStateException if not called on a looper thread. * * @see #callFunction(String, List, Class) - * @see #callFunctionAsync(String, List, Class, CodecRegistry, RealmApp.Callback) - * @see RealmAppConfiguration#getDefaultCodecRegistry() + * @see #callFunctionAsync(String, List, Class, CodecRegistry, App.Callback) + * @see AppConfiguration#getDefaultCodecRegistry() */ - public RealmAsyncTask callFunctionAsync(String name, List args, Class resultClass, RealmApp.Callback callback) { + public RealmAsyncTask callFunctionAsync(String name, List args, Class resultClass, App.Callback callback) { return callFunctionAsync(name, args, resultClass, defaultCodecRegistry, callback); } @@ -183,12 +183,12 @@ public RealmAsyncTask callFunctionAsync(String name, List args, Class * @throws IllegalStateException if not called on a looper thread. * * @see #callFunction(String, List, Class) - * @see #callFunctionAsync(String, List, Class, CodecRegistry, RealmApp.Callback) - * @see RealmAppConfiguration#getDefaultCodecRegistry() + * @see #callFunctionAsync(String, List, Class, CodecRegistry, App.Callback) + * @see AppConfiguration#getDefaultCodecRegistry() */ - public RealmAsyncTask callFunctionAsync(String name, List args, Decoder resultDecoder, RealmApp.Callback callback) { + public RealmAsyncTask callFunctionAsync(String name, List args, Decoder resultDecoder, App.Callback callback) { Util.checkLooperThread("Asynchronous functions is only possible from looper threads."); - return new RealmApp.Request(RealmApp.NETWORK_POOL_EXECUTOR, callback) { + return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override public T run() throws ObjectServerError { return invoke(name, args, defaultCodecRegistry, resultDecoder); @@ -207,20 +207,20 @@ public CodecRegistry getDefaultCodecRegistry() { } /** - * Returns the {@link RealmApp} that this instance in associated with. + * Returns the {@link App} that this instance in associated with. * - * @return The {@link RealmApp} that this instance in associated with. + * @return The {@link App} that this instance in associated with. */ - public RealmApp getApp() { + public App getApp() { return user.getApp(); } /** - * Returns the {@link RealmUser} that this instance in associated with. + * Returns the {@link User} that this instance in associated with. * - * @return The {@link RealmUser} that this instance in associated with. + * @return The {@link User} that this instance in associated with. */ - public RealmUser getUser() { + public User getUser() { return user; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java index 1faa079c09..17f8110a3d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java @@ -18,22 +18,21 @@ import org.bson.codecs.configuration.CodecRegistry; -import io.realm.RealmUser; +import io.realm.mongodb.User; import io.realm.internal.Util; import io.realm.internal.objectstore.OsMongoClient; /** * The remote MongoClient used for working with data in MongoDB remotely via Realm. */ -public class MongoClient { +abstract public class MongoClient { private OsMongoClient osMongoClient; private CodecRegistry codecRegistry; - public MongoClient(final RealmUser realmUser, final String serviceName, final CodecRegistry codecRegistry) { + protected MongoClient(OsMongoClient osMongoClient, final CodecRegistry codecRegistry) { + this.osMongoClient = osMongoClient; this.codecRegistry = codecRegistry; - Util.checkEmpty(serviceName, "serviceName"); - osMongoClient = new OsMongoClient(realmUser, serviceName); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmPushNotifications.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java similarity index 83% rename from realm/realm-library/src/objectServer/java/io/realm/RealmPushNotifications.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java index 64553c2029..2be4523db5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmPushNotifications.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb.push; -class RealmPushNotifications { +// FIXME Javadoc?? Has to be public to live in separate package. +public class Push { } diff --git a/realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java similarity index 92% rename from realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java index 1ffc75acc7..466ec3aa13 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ClientResetRequiredError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,19 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; import java.io.File; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.ObjectServerError; +import io.realm.Realm; +import io.realm.RealmConfiguration; + /** * Class encapsulating information needed for handling a Client Reset event. * - * @see io.realm.SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more information + * @see SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more information * about when and why Client Reset occurs and how to deal with it. */ public class ClientResetRequiredError extends ObjectServerError { diff --git a/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java similarity index 86% rename from realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java index 83d3f5d9ef..4c0eecde3f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ClientResyncMode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,9 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; +import io.realm.mongodb.ObjectServerError; import io.realm.internal.OsRealmConfig; /** @@ -44,12 +45,12 @@ enum ClientResyncMode { /** * A manual Client Resync is also known as a Client Reset. *

              - * A {@link io.realm.ClientResetRequiredError} will be sent to - * {@link io.realm.SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError)}, triggering + * A {@link ClientResetRequiredError} will be sent to + * {@link SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError)}, triggering * a Client Reset. Doing this provides a handle to both the old and new Realm file, enabling * full control of which changes to move, if any. * - * @see io.realm.SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more + * @see SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more * information about when and why Client Reset occurs and how to deal with it. */ MANUAL(OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL); diff --git a/realm/realm-library/src/objectServer/java/io/realm/ConnectionListener.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionListener.java similarity index 93% rename from realm/realm-library/src/objectServer/java/io/realm/ConnectionListener.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionListener.java index 43bed189a7..2728b9eba9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ConnectionListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; /** * Interface used when reporting changes that happened to the connection used by the session. @@ -22,7 +22,7 @@ * change will be reported to all sessions. *

              * If a disconnect happened due to an error, that error will be reported to the sessions - * {@link io.realm.SyncSession.ErrorHandler}. + * {@link SyncSession.ErrorHandler}. * * @see SyncSession#isConnected() * @see SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler) diff --git a/realm/realm-library/src/objectServer/java/io/realm/ConnectionState.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionState.java similarity index 97% rename from realm/realm-library/src/objectServer/java/io/realm/ConnectionState.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionState.java index e603160f93..08eec29920 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ConnectionState.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionState.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; /** * Enum describing the states of the underlying connection used by a {@link SyncSession}. diff --git a/realm/realm-library/src/objectServer/java/io/realm/Progress.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Progress.java similarity index 98% rename from realm/realm-library/src/objectServer/java/io/realm/Progress.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Progress.java index 77a6c01f78..f9bebacaeb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/Progress.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Progress.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; import io.realm.log.RealmLog; diff --git a/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressListener.java similarity index 97% rename from realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressListener.java index efda3478da..bc6c917041 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ProgressListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressMode.java similarity index 96% rename from realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressMode.java index f80f63150d..ceb7341714 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/ProgressMode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressMode.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; /** * Enum describing how to listen to progress changes. diff --git a/realm/realm-library/src/objectServer/java/io/realm/RealmSync.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java similarity index 92% rename from realm/realm-library/src/objectServer/java/io/realm/RealmSync.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java index abd6888e15..a371cb95e9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/RealmSync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,57 +14,39 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; -import android.content.Context; -import android.os.Build; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nullable; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.mongodb.ErrorCode; import io.realm.internal.Keep; import io.realm.internal.OsRealmConfig; import io.realm.internal.Util; import io.realm.internal.network.NetworkStateReceiver; import io.realm.log.RealmLog; -import okhttp3.internal.tls.OkHostnameVerifier; +import io.realm.mongodb.App; +import io.realm.mongodb.User; /** - * Class wrapping Sync responsibilities for a {@link io.realm.RealmApp}. + * Class wrapping Sync responsibilities for a {@link App}. * * FIXME: Better description that makes sense for end users. */ @Keep @SuppressFBWarnings("MS_CANNOT_BE_FINAL") -public class RealmSync { +public abstract class Sync { - private final RealmApp app; + private final App app; // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path' private Map sessions = new ConcurrentHashMap<>(); - RealmSync(RealmApp app) { + protected Sync(App app) { this.app = app; } @@ -164,7 +146,7 @@ public synchronized SyncSession getOrCreateSession(SyncConfiguration syncConfigu return session; } - List getAllSyncSessions(RealmUser user) { + List getAllSyncSessions(User user) { //noinspection ConstantConditions if (user == null) { throw new IllegalArgumentException("A non-empty 'syncUser' is required."); @@ -296,7 +278,6 @@ public static void refreshConnections() { synchronized void reset() { nativeReset(); sessions.clear(); - app.networkTransport.resetHeaders(); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java similarity index 94% rename from realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index 2b3abd84aa..339362ed09 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; import android.content.Context; @@ -42,13 +42,21 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.CompactOnLaunchCallback; +import io.realm.DefaultCompactOnLaunchCallback; +import io.realm.Realm; +import io.realm.RealmConfiguration; +import io.realm.RealmMigration; +import io.realm.RealmModel; import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; import io.realm.internal.OsRealmConfig; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Util; -import io.realm.log.RealmLog; +import io.realm.mongodb.App; +import io.realm.mongodb.User; +import io.realm.mongodb.Credentials; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; @@ -56,15 +64,15 @@ * A {@link SyncConfiguration} is used to setup a Realm Database that can be synchronized between * devices using MongoDB Realm. *

              - * A valid {@link RealmUser} is required to create a {@link SyncConfiguration}. See - * {@link RealmCredentials} and {@link RealmApp#loginAsync(RealmCredentials, RealmApp.Callback)} for + * A valid {@link User} is required to create a {@link SyncConfiguration}. See + * {@link Credentials} and {@link App#loginAsync(Credentials, App.Callback)} for * more information on how to get a user object. *

              * A minimal {@link SyncConfiguration} can be found below. *

                * {@code
              - * RealmApp app = new RealmApp("app-id");
              - * RealmUser user = app.login(RealmCredentials.anonymous());
              + * App app = new App("app-id");
              + * User user = app.login(Credentials.anonymous());
                * SyncConfiguration config = SyncConfiguration.defaultConfiguration(user, "partition-value");
                * Realm realm = Realm.getInstance(config);
                * }
              @@ -92,7 +100,7 @@ public class SyncConfiguration extends RealmConfiguration {
                   static final int MAX_FILE_NAME_LENGTH = 255;
                   private static final char[] INVALID_CHARS = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'};
                   private final URI serverUrl;
              -    private final RealmUser user;
              +    private final User user;
                   private final SyncSession.ErrorHandler errorHandler;
                   private final boolean deleteRealmOnLogout;
                   private final boolean waitForInitialData;
              @@ -116,7 +124,7 @@ private SyncConfiguration(File directory,
                                             @Nullable Realm.Transaction initialDataTransaction,
                                             boolean readOnly,
                                             long maxNumberOfActiveVersions,
              -                              RealmUser user,
              +                              User user,
                                             URI serverUrl,
                                             SyncSession.ErrorHandler errorHandler,
                                             boolean deleteRealmOnLogout,
              @@ -183,7 +191,11 @@ public static RealmConfiguration forRecovery(String canonicalPath, @Nullable byt
                       }
               
                       RealmProxyMediator schemaMediator = createSchemaMediator(validatedModules, Collections.>emptySet());
              -        return forRecovery(canonicalPath, encryptionKey, schemaMediator);
              +        return RealmConfiguration.forRecovery(canonicalPath, encryptionKey, schemaMediator);
              +    }
              +
              +    RealmConfiguration forErrorRecovery(String canonicalPath) {
              +        return RealmConfiguration.forRecovery(canonicalPath, getEncryptionKey(), getSchemaMediator());
                   }
               
                   /**
              @@ -194,10 +206,15 @@ public static RealmConfiguration forRecovery(String canonicalPath, @Nullable byt
                    * @return
                    */
                   @Beta
              -    public static SyncConfiguration defaultConfig(RealmUser user, String partitionValue) {
              +    public static SyncConfiguration defaultConfig(User user, String partitionValue) {
                       return new SyncConfiguration.Builder(user, partitionValue).build();
                   }
               
              +    @Override
              +    protected Realm.Transaction getInitialDataTransaction() {
              +        return super.getInitialDataTransaction();
              +    }
              +
                   /**
                    * FIXME
                    *
              @@ -206,7 +223,7 @@ public static SyncConfiguration defaultConfig(RealmUser user, String partitionVa
                    * @return
                    */
                   @Beta
              -    public static SyncConfiguration defaultConfig(RealmUser user, long partitionValue) {
              +    public static SyncConfiguration defaultConfig(User user, long partitionValue) {
                       return new SyncConfiguration.Builder(user, partitionValue).build();
                   }
               
              @@ -218,7 +235,7 @@ public static SyncConfiguration defaultConfig(RealmUser user, long partitionValu
                    * @return
                    */
                   @Beta
              -    public static SyncConfiguration defaultConfig(RealmUser user, int partitionValue) {
              +    public static SyncConfiguration defaultConfig(User user, int partitionValue) {
                       return new SyncConfiguration.Builder(user, partitionValue).build();
                   }
               
              @@ -230,7 +247,7 @@ public static SyncConfiguration defaultConfig(RealmUser user, int partitionValue
                    * @return
                    */
                   @Beta
              -    public static SyncConfiguration defaultConfig(RealmUser user, ObjectId partitionValue) {
              +    public static SyncConfiguration defaultConfig(User user, ObjectId partitionValue) {
                       return new SyncConfiguration.Builder(user, partitionValue).build();
                   }
               
              @@ -249,12 +266,9 @@ public static RealmConfiguration forRecovery(String canonicalPath) {
                       return forRecovery(canonicalPath, null);
                   }
               
              -    static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) {
              -        return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true, Long.MAX_VALUE);
              -    }
               
                   // Extract the full server path, minus the file name
              -    private static String getServerPath(RealmUser user, URI serverUrl) {
              +    private static String getServerPath(User user, URI serverUrl) {
                       // FIXME Add support for partion key
                       // Current scheme is ///default.realm or
                       // Current scheme is ////default.realm
              @@ -326,7 +340,7 @@ public String toString() {
                    *
                    * @return the user.
                    */
              -    public RealmUser getUser() {
              +    public User getUser() {
                       return user;
                   }
               
              @@ -344,9 +358,9 @@ public SyncSession.ErrorHandler getErrorHandler() {
                   }
               
                   /**
              -     * Returns {@code true} if the Realm file must be deleted once the {@link SyncUser} owning it logs out.
              +     * Returns {@code true} if the Realm file must be deleted once the {@link User} owning it logs out.
                    *
              -     * @return {@code true} if the Realm file must be deleted if the {@link SyncUser} logs out. {@code false} if the file
              +     * @return {@code true} if the Realm file must be deleted if the {@link User} logs out. {@code false} if the file
                    *         is allowed to remain behind.
                    */
                   public boolean shouldDeleteRealmOnLogout() {
              @@ -377,7 +391,7 @@ public long getInitialRemoteDataTimeout(TimeUnit unit) {
                   }
               
                   @Override
              -    boolean isSyncConfiguration() {
              +    protected boolean isSyncConfiguration() {
                       return true;
                   }
               
              @@ -443,7 +457,7 @@ public static final class Builder  {
                       // sync specific
                       private boolean deleteRealmOnLogout = false;
                       private URI serverUrl;
              -        private RealmUser user = null;
              +        private User user = null;
                       private SyncSession.ErrorHandler errorHandler;
                       private OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy = OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED;
                       private CompactOnLaunchCallback compactOnLaunch;
              @@ -459,7 +473,7 @@ public static final class Builder  {
                        * @param user
                        * @param partitionValue
                        */
              -        public Builder(RealmUser user, String partitionValue) {
              +        public Builder(User user, String partitionValue) {
                           this(user, new BsonString(partitionValue));
                       }
               
              @@ -469,7 +483,7 @@ public Builder(RealmUser user, String partitionValue) {
                        * @param user
                        * @param partitionValue
                        */
              -        public Builder(RealmUser user, ObjectId partitionValue) {
              +        public Builder(User user, ObjectId partitionValue) {
                           this(user, new BsonObjectId(partitionValue));
                       }
               
              @@ -479,7 +493,7 @@ public Builder(RealmUser user, ObjectId partitionValue) {
                        * @param user
                        * @param partitionValue
                        */
              -        public Builder(RealmUser user, int partitionValue) {
              +        public Builder(User user, int partitionValue) {
                           this(user, new BsonInt32(partitionValue));
                       }
               
              @@ -489,7 +503,7 @@ public Builder(RealmUser user, int partitionValue) {
                        * @param user
                        * @param partitionValue
                        */
              -        public Builder(RealmUser user, long partitionValue) {
              +        public Builder(User user, long partitionValue) {
                           this(user, new BsonInt64(partitionValue));
                       }
               
              @@ -502,8 +516,8 @@ public Builder(RealmUser user, long partitionValue) {
                        * synchronized to the Realm.
                        * @see Link to docs about partions
                        */
              -        private Builder(RealmUser user, BsonValue partitionValue) {
              -            Context context = BaseRealm.applicationContext;
              +        private Builder(User user, BsonValue partitionValue) {
              +            Context context = Realm.getApplicationContext();
                           if (context == null) {
                               throw new IllegalStateException("Call `Realm.init(Context)` before creating a SyncConfiguration");
                           }
              @@ -519,7 +533,7 @@ private Builder(RealmUser user, BsonValue partitionValue) {
                           this.errorHandler = user.getApp().getConfiguration().getDefaultErrorHandler();
                       }
               
              -        private void validateAndSet(RealmUser user) {
              +        private void validateAndSet(User user) {
                           //noinspection ConstantConditions
                           if (user == null) {
                               throw new IllegalArgumentException("Non-null `user` required.");
              @@ -571,7 +585,7 @@ private void validateAndSet(URL baseUrl ) {
                       }
               
                       /**
              -         * Sets the {@value io.realm.RealmConfiguration#KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file.
              +         * Sets the {@value io.realm.Realm#ENCRYPTION_KEY_LENGTH} bytes key used to encrypt and decrypt the Realm file.
                        *
                        * @param key the encryption key.
                        * @throws IllegalArgumentException if key is invalid.
              @@ -754,8 +768,7 @@ public Builder inMemory() {
                       }
               
                       /**
              -         * Sets the error handler used by this configuration. This will override any handler set by calling
              -         * {@link RealmSync#setDefaultSessionErrorHandler(SyncSession.ErrorHandler)}.
              +         * Sets the error handler used by this configuration.
                        * 

              * Only errors not handled by the defined {@code SyncPolicy} will be reported to this error handler. * @@ -979,7 +992,8 @@ public SyncConfiguration build() { clientResyncMode = ClientResyncMode.MANUAL; } - if (rxFactory == null && isRxJavaAvailable()) { + // FIXME How to get access to this + if (rxFactory == null && Util.isRxJavaAvailable()) { rxFactory = new RealmObservableFactory(true); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java similarity index 97% rename from realm/realm-library/src/objectServer/java/io/realm/SyncSession.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 916f5074c8..1c21371a7c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.realm; +package io.realm.mongodb.sync; import java.net.URI; import java.util.HashMap; @@ -29,10 +29,15 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.ObjectServerError; +import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.internal.Keep; import io.realm.internal.Util; import io.realm.internal.util.Pair; import io.realm.log.RealmLog; +import io.realm.mongodb.User; /** * A session controls how data is synchronized between a single Realm on the device and the server @@ -162,12 +167,12 @@ public SyncConfiguration getConfiguration() { } /** - * Returns the {@link RealmUser} defined by the {@link SyncConfiguration} that is used to connect to + * Returns the {@link User} defined by the {@link SyncConfiguration} that is used to connect to * MongoDB Realm. * - * @return {@link RealmUser} used to authenticate the session on MongoDB Realm. + * @return {@link User} used to authenticate the session on MongoDB Realm. */ - public RealmUser getUser() { + public User getUser() { return configuration.getUser(); } @@ -188,7 +193,7 @@ void notifySessionError(String nativeErrorCategory, int nativeErrorCode, String ErrorCode errCode = ErrorCode.fromNativeError(nativeErrorCategory, nativeErrorCode); if (errCode == ErrorCode.CLIENT_RESET) { // errorMessage contains the path to the backed up file - RealmConfiguration backupRealmConfiguration = SyncConfiguration.forRecovery(errorMessage, configuration.getEncryptionKey(), configuration.getSchemaMediator()); + RealmConfiguration backupRealmConfiguration = configuration.forErrorRecovery(errorMessage); errorHandler.onError(this, new ClientResetRequiredError(errCode, "A Client Reset is required. " + "Read more here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup.", configuration, backupRealmConfiguration)); @@ -205,10 +210,8 @@ void notifySessionError(String nativeErrorCategory, int nativeErrorCode, String /** * Get the current session's state, as defined in {@link SyncSession.State}. - * - * Note that the state may change after this method returns, example: the authentication - * token will expire, causing the session to move to {@link State#WAITING_FOR_ACCESS_TOKEN} - * after it was in {@link State#ACTIVE}. + *

              + * Note that the state may change after this method returns. * * @return the state of the session. * @see SyncSession.State @@ -524,8 +527,8 @@ public boolean uploadAllLocalChanges(long timeout, TimeUnit unit) throws Interru *

              * If the session was already started, calling this method will do nothing. *

              - * A session is considered started if {@link #getState()} returns either {@link State#ACTIVE} or - * {@link State#WAITING_FOR_ACCESS_TOKEN}. If the session is {@link State#DYING}, the session + * A session is considered started if {@link #getState()} returns {@link State#ACTIVE}. + * If the session is {@link State#DYING}, the session * will be moved back to {@link State#ACTIVE}. * * @see #getState() @@ -616,7 +619,6 @@ private void checkTimeout(long timeout, TimeUnit unit) { /** * Interface used to report any session errors. * - * @see RealmSync#setDefaultSessionErrorHandler(ErrorHandler) * @see SyncConfiguration.Builder#errorHandler(ErrorHandler) */ public interface ErrorHandler { diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java index d9ec65f998..c240f25994 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java @@ -27,7 +27,6 @@ import io.realm.RealmConfiguration; import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; -import io.realm.SyncCredentials; import io.realm.SyncManager; import io.realm.SyncSession; import io.realm.SyncTestUtils; diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java index cbf0615b2d..080b02bdfd 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java @@ -11,7 +11,6 @@ import io.realm.RealmResults; import io.realm.StandardIntegrationTest; import io.realm.SyncConfiguration; -import io.realm.SyncCredentials; import io.realm.SyncManager; import io.realm.SyncSession; import io.realm.SyncTestUtils; diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java index cf8c4dde08..5b45a84e23 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/UserFactory.java @@ -19,15 +19,10 @@ import android.os.Handler; import android.os.HandlerThread; -import java.util.Map; -import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import io.realm.Realm; -import io.realm.RealmApp; -import io.realm.RealmCredentials; -import io.realm.RealmUser; import io.realm.RealmConfiguration; import io.realm.TestHelper; import io.realm.log.RealmLog; @@ -93,9 +88,9 @@ public static void logoutAllUsers() { handler.post(new Runnable() { @Override public void run() { -// Map users = RealmApp.allUsers(); -// for (RealmUser user : users.values()) { -// RealmApp.logout(user); +// Map users = App.allUsers(); +// for (User user : users.values()) { +// App.logout(user); // } TestHelper.waitForNetworkThreadExecutorToFinish(); allUsersLoggedOut.countDown(); diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/RealmExt.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/RealmExt.kt new file mode 100644 index 0000000000..ee2ec9d0eb --- /dev/null +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/RealmExt.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm + +class RealmExt { + companion object {} +} + +fun RealmExt.Companion.testClearApplicationContext() { + BaseRealm.applicationContext = null; +} diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt similarity index 81% rename from realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt rename to realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt index 660e20c9fc..30e686d0dc 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/TestRealmApp.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt @@ -15,18 +15,18 @@ */ package io.realm -import androidx.test.platform.app.InstrumentationRegistry import io.realm.internal.network.OkHttpNetworkTransport import io.realm.internal.objectstore.OsJavaNetworkTransport -import io.realm.log.LogLevel +import io.realm.mongodb.App +import io.realm.mongodb.AppConfiguration /** - * This class wraps various methods making it easier to create an RealmApp that can be used + * This class wraps various methods making it easier to create an App that can be used * for testing. * * NOTE: This class must remain in the [io.realm] package in order to work. */ -class TestRealmApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: (RealmAppConfiguration.Builder) -> Unit = {}) : RealmApp(createConfiguration()) { +class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: (AppConfiguration.Builder) -> Unit = {}) : App(createConfiguration()) { init { if (networkTransport != null) { @@ -35,8 +35,8 @@ class TestRealmApp(networkTransport: OsJavaNetworkTransport? = null, customizeCo } companion object { - fun createConfiguration(): RealmAppConfiguration { - return RealmAppConfiguration.Builder(initializeMongoDbRealm()) + fun createConfiguration(): AppConfiguration { + return AppConfiguration.Builder(initializeMongoDbRealm()) .baseUrl("http://127.0.0.1:9090") .appName("MongoDB Realm Integration Tests") .appVersion("1.0.") diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt similarity index 57% rename from realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java rename to realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt index 7b5d62b5a9..b51883fe4b 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/TestSyncConfigurationFactory.java +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package io.realm -package io.realm; - -import io.realm.internal.OsRealmConfig; -import io.realm.rule.TestRealmConfigurationFactory; +import io.realm.internal.OsRealmConfig +import io.realm.mongodb.User +import io.realm.mongodb.sync.SyncConfiguration +import io.realm.mongodb.sync.testSessionStopPolicy +import io.realm.rule.TestRealmConfigurationFactory /** * Test rule used for creating SyncConfigurations. Will ensure that any Realm files are deleted when the * test ends. */ -public class TestSyncConfigurationFactory extends TestRealmConfigurationFactory { - - public SyncConfiguration.Builder createSyncConfigurationBuilder(RealmUser user) { - return new SyncConfiguration.Builder(user, "default") - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY); +class TestSyncConfigurationFactory : TestRealmConfigurationFactory() { + fun createSyncConfigurationBuilder(user: User?): SyncConfiguration.Builder { + return SyncConfiguration.Builder(user, "default") + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) } } diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt similarity index 93% rename from realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt rename to realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt index 574a75df7c..b6ec982085 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm +package io.realm.mongodb import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import io.realm.RealmExt import io.realm.internal.objectstore.OsJavaNetworkTransport import io.realm.log.LogLevel import io.realm.log.RealmLog +import io.realm.mongodb.sync.SyncConfiguration import io.realm.objectserver.utils.UserFactory +import io.realm.testClearApplicationContext import java.io.File import java.lang.IllegalStateException import java.util.* @@ -42,17 +46,17 @@ class SyncTestUtils { // Reset log level RealmLog.setLevel(originalLogLevel) - if (BaseRealm.applicationContext != null) { + if (Realm.getApplicationContext() != null) { // Realm was already initialized. Reset all internal state // in order to be able to fully re-initialize. // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' - // causing the RealmUser to remain in memory. + // causing the User to remain in memory. // They're actually not persisted into disk. // move this call to 'tearDown' to clean in-memory & on-disk users // once https://github.com/realm/realm-object-store/issues/207 is resolved // SyncManager.reset(); // FIXME - BaseRealm.applicationContext = null // Required for Realm.init() to work + RealmExt.testClearApplicationContext() // Required for Realm.init() to work } deleteRosFiles() Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) @@ -77,7 +81,7 @@ class SyncTestUtils { @JvmStatic @JvmOverloads - fun createTestUser(app: RealmApp, userIdentifier: String = UUID.randomUUID().toString()): RealmUser { + fun createTestUser(app: App, userIdentifier: String = UUID.randomUUID().toString()): User { val transportBackup = app.networkTransport app.networkTransport = object : OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: Map, body: String): Response { @@ -130,7 +134,7 @@ class SyncTestUtils { } } } - val user = app.login(RealmCredentials.anonymous()) + val user = app.login(Credentials.anonymous()) app.networkTransport = transportBackup return user } diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt new file mode 100644 index 0000000000..1a4898c74b --- /dev/null +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.sync + +import io.realm.RealmModel +import io.realm.internal.OsRealmConfig + +fun SyncConfiguration.Builder.testSchema(firstClass: Class, vararg x: Class ) : SyncConfiguration.Builder { + return this.schema(firstClass, *x) +} + +fun SyncConfiguration.Builder.testSessionStopPolicy(policy: OsRealmConfig.SyncSessionStopPolicy): SyncConfiguration.Builder { + return this.sessionStopPolicy(policy) +} diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 33a8802153..31826e8d75 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -1277,7 +1277,7 @@ public static void populateLinkedDataSet(Realm realm) { static { Class app = null; try { - app = Class.forName("io.realm.RealmApp"); + app = Class.forName("io.realm.mongodb.App"); } catch (ClassNotFoundException e) { // Ignore } From dec806675fedfa3d2961812d6baa44be5d205e12 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 3 Jun 2020 13:13:29 +0200 Subject: [PATCH 1556/2110] Add support for Embedded Objects (#6730) --- CHANGELOG.md | 10 +- .../java/io/realm/annotations/PrimaryKey.java | 3 + .../java/io/realm/annotations/RealmClass.java | 29 + .../kotlin/io/realm/kotlin/RealmExtensions.kt | 19 +- .../main/java/io/realm/processor/Backlink.kt | 88 +- .../java/io/realm/processor/ClassMetaData.kt | 44 +- .../java/io/realm/processor/RealmProcessor.kt | 2 + .../processor/RealmProxyClassGenerator.kt | 556 ++++++++--- .../processor/RealmProxyMediatorGenerator.kt | 218 +++-- .../processor/RealmEmbeddedObjectsTest.java | 115 +++ .../io/realm/RealmDefaultModuleMediator.java | 23 +- .../realm/some_test_AllTypesRealmProxy.java | 95 +- .../realm/some_test_BooleansRealmProxy.java | 20 +- ...t_EmbeddedClassSimpleParentRealmProxy.java | 867 ++++++++++++++++++ ...amePolicyMixedClassSettingsRealmProxy.java | 16 +- ...st_NamePolicyModuleDefaultsRealmProxy.java | 16 +- .../realm/some_test_NullTypesRealmProxy.java | 119 +-- .../io/realm/some_test_SimpleRealmProxy.java | 16 +- .../resources/some/test/EmbeddedClass.java | 26 + .../EmbeddedClassMissingFieldDescription.java | 31 + ...ddedClassMissingFinalOnLinkingObjects.java | 31 + .../EmbeddedClassMultipleRequiredParents.java | 38 + .../test/EmbeddedClassOptionalParents.java | 37 + .../some/test/EmbeddedClassParent.java | 34 + .../some/test/EmbeddedClassPrimaryKey.java | 29 + .../test/EmbeddedClassRequiredParent.java | 32 + .../some/test/EmbeddedClassSimpleParent.java | 32 + .../io/realm/LinkingObjectsManagedTests.java | 8 +- .../java/io/realm/internal/OsListTests.java | 2 +- .../kotlin/io/realm/Decimal128Tests.kt | 15 + .../kotlin/io/realm/EmbeddedObjectsTest.kt | 592 ++++++++++++ .../kotlin/io/realm/ObjectIdTests.kt | 15 + .../embedded/EmbeddedCircularChild.kt | 29 + .../embedded/EmbeddedCircularParent.kt | 26 + .../entities/embedded/EmbeddedSimpleChild.kt | 37 + .../embedded/EmbeddedSimpleListParent.kt | 27 + .../entities/embedded/EmbeddedSimpleParent.kt | 25 + .../entities/embedded/EmbeddedTreeLeaf.kt | 37 + .../entities/embedded/EmbeddedTreeNode.kt | 35 + .../entities/embedded/EmbeddedTreeParent.kt | 31 + .../embedded/EmbeddedWithConstructorArgs.kt | 30 + .../io/realm/rule/BlockingLooperThread.kt | 0 .../io/realm/SyncedRealmMigrationTests.kt | 2 +- .../src/main/cpp/io_realm_internal_OsList.cpp | 38 + .../main/cpp/io_realm_internal_OsObject.cpp | 21 + .../io_realm_internal_OsObjectSchemaInfo.cpp | 14 +- .../src/main/cpp/io_realm_internal_Table.cpp | 20 + .../cpp/io_realm_internal_UncheckedRow.cpp | 15 + ...m_internal_objectstore_OsObjectBuilder.cpp | 34 +- .../main/java/io/realm/FrozenPendingRow.java | 5 + .../src/main/java/io/realm/Realm.java | 71 +- .../src/main/java/io/realm/RealmList.java | 78 +- .../main/java/io/realm/RealmObjectSchema.java | 43 + .../java/io/realm/internal/InvalidRow.java | 5 + .../main/java/io/realm/internal/OsList.java | 20 + .../main/java/io/realm/internal/OsObject.java | 6 + .../io/realm/internal/OsObjectSchemaInfo.java | 18 +- .../java/io/realm/internal/PendingRow.java | 5 + .../io/realm/internal/RealmProxyMediator.java | 21 + .../src/main/java/io/realm/internal/Row.java | 5 + .../main/java/io/realm/internal/Table.java | 16 + .../java/io/realm/internal/UncheckedRow.java | 8 + .../internal/modules/CompositeMediator.java | 12 + .../internal/modules/FilterableMediator.java | 12 + .../internal/objectstore/OsObjectBuilder.java | 33 +- 65 files changed, 3567 insertions(+), 390 deletions(-) create mode 100644 realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java create mode 100644 realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClass.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFieldDescription.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFinalOnLinkingObjects.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMultipleRequiredParents.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassOptionalParents.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassParent.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassPrimaryKey.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassRequiredParent.java create mode 100644 realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassSimpleParent.java create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedWithConstructorArgs.kt rename realm/realm-library/src/{androidTestObjectServer => androidTest}/kotlin/io/realm/rule/BlockingLooperThread.kt (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f74bbe824..15707564a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ * Destructive updates of a schema of a synced Realm will now consistently throw an `UnsupportedOperationException` instead of some methods throwing `IllegalArgumentException`. The affected methods are `RealmSchema.remove(String)`, `RealmSchema.rename(String, String)`, `RealmObjectSchema.setClassName(String)`, `RealmObjectSchema.removeField(String)`, `RealmObjectSchema.renameField(String, String)`, `RealmObjectSchema.removeIndex(String)`, `RealmObjectSchema.removePrimaryKey()`, `RealmObjectSchema.addPrimaryKey(String)` and `RealmObjectSchema.addField(String, Class, FieldAttribute)` ### Enhancements -* None. +* Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. +* Added support for `org.bson.types.ObjectId` as a primary key. +* Added support for "Embedded Objects". They are enabled using `@RealmClass(embedded = true)`. An embedded object must have exactly one parent object linking to it and it will be deleted when the the parent is. Embedded objects can also be the parent of other embedded classes. Read more [here](https://realm.io/docs/java/latest/#embedded-objects). (Issue [#6713](https://github.com/realm/realm-java/issues/6713)) ### Fixed * None. @@ -41,12 +43,12 @@ NOTE: This version bumps the Realm file format to version 10. Files created with * [ObjectServer] `IncompatibleSyncedFileException` is removed and no longer thrown. ### Enhancements -* Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. -* Add support for `org.bson.types.ObjectId` as a primary key. * Added `Realm.freeze()`, `RealmObject.freeze()`, `RealmResults.freeze()` and `RealmList.freeze()`. These methods will return a frozen version of the current Realm data. This data can be read from any thread without throwing an `IllegalStateException`, but will never change. All frozen Realms and data can be closed by calling `Realm.close()` on the frozen Realm, but fully closing all live Realms will also close the frozen ones. Frozen data can be queried as normal, but trying to mutate it in any way will throw an `IllegalStateException`. This includes all methods that attempt to refresh or add change listeners. (Issue [#6590](https://github.com/realm/realm-java/pull/6590)) * Added `Realm.isFrozen()`, `RealmObject.isFrozen()`, `RealmObject.isFrozen(RealmModel)`, `RealmResults.isFrozen()` and `RealmList.isFrozen()`, which returns whether or not the data is frozen. * Added `RealmConfiguration.Builder.maxNumberOfActiveVersions(long number)`. Setting this will cause Realm to throw an `IllegalStateException` if too many versions of the Realm data are live at the same time. Having too many versions can dramatically increase the filesize of the Realm. -* `RealmResults.asJSON()` is no longer `@Beta`. +* Storing large binary blobs in Realm files no longer forces the file to be at least 8x the size of the largest blob. +* Reduce the size of transaction logs stored inside the Realm file, reducing file size growth from large transactions. +* `RealmResults.asJSON()` is no longer `@Beta` * The default `toString()` for proxy objects now print the length of binary fields. (Issue [#6767](https://github.com/realm/realm-java/pull/6767)) ### Fixes diff --git a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java index daac8c110f..b654e69451 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java +++ b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java @@ -33,6 +33,9 @@ * It is allowed to apply this annotation on the following primitive types: byte, short, int, and long. * String, Byte, Short, Integer, and Long are also allowed, and further permitted to have {@code null} * as a primary key value. + *

              + * This annotation is not allowed inside Realm classes marked as {@code \@RealmClass(embedded = true)}. + *

              */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) diff --git a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java index 6f284714e8..396c5f9f95 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java +++ b/realm-annotations/src/main/java/io/realm/annotations/RealmClass.java @@ -50,6 +50,35 @@ */ String name() default ""; + /** + * Define objects of this type as "Embedded". Embedded objects have a slightly different behavior than + * normal objects: + *
                + *
              • + * They must have exactly 1 parent linking to them when the embedded object is added to + * the Realm. Embedded objects can be the parent of other embedded objects. The parent + * cannot be changed later, except by copying the object. + *
              • + *
              • + * They cannot have fields annotated with {@code \@PrimaryKey}. + *
              • + *
              • + * When a parent object is deleted, all embedded objects are also deleted. + *
              • + *
              • + * It is possible to define an easy reference to the parent object using the + * {@code \@LinkingObjects} annotation: + *
                +     *         {@code
                +     *              \@LinkingObjects
                +     *              public Parent parent;
                +     *         }
                +     *         
                + *
              • + *
              + */ + boolean embedded() default false; + /** * The naming policy applied to all fields in this class. The default policy is {@link RealmNamingPolicy#NO_POLICY}. *

              diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt index 1307e48a38..15382339c0 100644 --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt @@ -74,6 +74,23 @@ inline fun Realm.createObject(primaryKeyValue: Any?): T return this.createObject(T::class.java, primaryKeyValue) } +/** + * Instantiates and adds a new embedded object to the Realm. + *

              + * This method should only be used to create objects of types marked as embedded. + * + * @param T the Class of the object to create. It must be marked with {@code \@RealmClass(embedded = true)}. + * @param parentObject The parent object which should hold a reference to the embedded object. If the parent property is a list + * the embedded object will be added to the end of that list. + * @param parentProperty the property in the parent class which holds the reference. + * @return the newly created embedded object. + * @throws IllegalArgumentException if {@code clazz} is not an embedded class or if the property + * in the parent class cannot hold objects of the appropriate type. + */ +inline fun Realm.createEmbeddedObject(parentObject: RealmModel, parentProperty: String): T { + return this.createEmbeddedObject(T::class.java, parentObject, parentProperty) +} + /** TODO: Figure out if we should include this is or not. Using this makes it possible to do @@ -99,4 +116,4 @@ Missing functions. Consider these for inclusion later: - createOrUpdateObjectFromJson(Class clazz, org.json.JSONObject json) - createOrUpdateObjectFromJson(Class clazz, String json) - createOrUpdateObjectFromJson(Class clazz, String json) -*/ \ No newline at end of file +*/ diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt index 862db60de5..415c52355a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Backlink.kt @@ -36,11 +36,21 @@ import io.realm.annotations.Required * To expose backlinks for use, create a declaration as follows: * * ``` + * // For Normal top-level objects * class TargetClass { * // ... * @LinkingObjects("sourceField") * final RealmResults targetField = null; * } + * + * // If the class is an embedded object, we know there is always one parent, so + * // backlinks in this case can also be defined this way: + * class TargetClass { + * // ... + * @LinkingObjects("sourceField") + * final SourceClass targetField; + * } + * *``` * * The `targetField`, the field annotated with the @LinkingObjects annotation must be final. @@ -57,7 +67,7 @@ import io.realm.annotations.Required * An unmanaged Model object will have, as the value of its backlink field, the value with which * the field is initialized (typically null). */ -class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) { +class Backlink(private val clazz: ClassMetaData, private val backlinkField: VariableElement) { /** * The fully-qualified name of the class containing the `targetField`, which is the field @@ -74,7 +84,7 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) /** * The fully-qualified name of the class to which the backlinks, from `targetField`, point. */ - val sourceClass: QualifiedClassName? = Utils.getRealmResultsType(backlinkField) + val sourceClass: QualifiedClassName? = if (Utils.isRealmResults(backlinkField)) Utils.getRealmResultsType(backlinkField) else Utils.getModelClassQualifiedName(backlinkField) /** * The name of the field, in `SourceClass` that has a normal link to `targetClass`. @@ -83,8 +93,13 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) */ val sourceField: String? = backlinkField.getAnnotation(LinkingObjects::class.java)?.value - val targetFieldType: String - get() = backlinkField.asType().toString() + /** + * {@code true} if the parent link should be modeled as a RealmResults instead of a single link. + * Single links are only supported in classes that are embedded. + */ + val exposeAsRealmResults: Boolean = Utils.isRealmResults(backlinkField) + + val targetFieldType: String = backlinkField.asType().toString() /** * Validate the source side of the backlink. @@ -92,17 +107,7 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) * @return true if the backlink source looks good. */ fun validateSource(): Boolean { - // A @LinkingObjects cannot be @Required - if (backlinkField.getAnnotation(Required::class.java) != null) { - Utils.error(String.format( - Locale.US, - "The @LinkingObjects field \"%s.%s\" cannot be @Required.", - targetClass, - targetField)) - return false - } - - // The annotation must have an argument, identifying the linked field + // The annotation must have an argument, identifying the linked field. if (sourceField == null || sourceField == "") { Utils.error(String.format( Locale.US, @@ -122,8 +127,17 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) return false } - // The annotated element must be a RealmResult - if (!Utils.isRealmResults(backlinkField)) { + if (Utils.isRealmResults(backlinkField)) { + return validateBacklinksAsRealmResults(backlinkField) + } else { + return validateBacklinkAsObjectReference(backlinkField) + } + } + + private fun validateBacklinkAsObjectReference(field: VariableElement): Boolean { + + // Using @LinkingObjects as a single parent reference is only allowed in embedded classes + if (!clazz.embedded && !Utils.isRealmResults(backlinkField)) { Utils.error(String.format( Locale.US, "The field \"%s.%s\" is a \"%s\". Fields annotated with @LinkingObjects must be RealmResults.", @@ -133,6 +147,44 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) return false } + // A @LinkingObjects can only be required if for the class being embedded there is + // only one @LinkingField field defined. And even in that case, it requires runtime + // schema validation since we need to know if only one other type is pointing to it. + // If multiple types point to it, we cannot keep the contract of @Required. + if (field.getAnnotation(Required::class.java) != null && clazz.backlinkFields.isNotEmpty()) { + Utils.error(String.format( + Locale.US, + "@Required cannot be used on @LinkingObjects field if multiple @LinkingParents are defined: \"%s.%s\".", + targetClass, + targetField)) + return false + } + + // A @LinkingObjects field must be final. + if (!field.modifiers.contains(Modifier.FINAL)) { + Utils.error(String.format( + Locale.US, + "The @LinkingObjects field \"%s.%s\" must be final.", + targetClass, + targetField)) + return false + } + + return true + } + + private fun validateBacklinksAsRealmResults(field: VariableElement): Boolean { + // A @LinkingObjects on a RealmResults cannot be @Required as doesn't have any + // meaning. + if (field.getAnnotation(Required::class.java) != null) { + Utils.error(String.format( + Locale.US, + "The @LinkingObjects field \"%s.%s\" cannot be @Required.", + targetClass, + targetField)) + return false + } + if (sourceClass == null) { Utils.error(String.format( Locale.US, @@ -142,7 +194,7 @@ class Backlink(clazz: ClassMetaData, private val backlinkField: VariableElement) return false } - // A @LinkingObjects field must be final + // A @LinkingObjects field must be final. if (!backlinkField.modifiers.contains(Modifier.FINAL)) { Utils.error(String.format( Locale.US, diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt index f8590ae51b..7c16b9177a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt @@ -128,12 +128,13 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat return type != "io.realm.DynamicRealmObject" && !type.endsWith(".RealmObject") && !type.endsWith("RealmProxy") } + var embedded: Boolean = false + private set + val classElement: Element get() = classType init { - - for (element in classType.enclosedElements) { if (element is ExecutableElement) { val name = element.getSimpleName() @@ -309,6 +310,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat defaultFieldNameFormatter = Utils.getNameFormatter(realmClassAnnotation.fieldNamingPolicy) } + embedded = realmClassAnnotation.embedded + // Categorize and check the rest of the file if (!categorizeClassElements()) { return false @@ -548,7 +551,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat } } } else if (isRequiredField(field)) { - if (!checkBasicRequiredAnnotationUsage(element, field)) { + if (!checkBasicRequiredAnnotationUsage(field)) { return false } } else { @@ -675,25 +678,30 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat // The field has the @Required annotation // Returns `true` if the field could be correctly validated, `false` if an error was reported. - private fun checkBasicRequiredAnnotationUsage(element: Element, variableElement: VariableElement): Boolean { - if (Utils.isPrimitiveType(variableElement)) { + private fun checkBasicRequiredAnnotationUsage(field: VariableElement): Boolean { + if (Utils.isPrimitiveType(field)) { Utils.error(String.format(Locale.US, - "@Required or @NotNull annotation is unnecessary for primitive field \"%s\".", element)) + "@Required or @NotNull annotation is unnecessary for primitive field \"%s\".", field)) return false } - if (Utils.isRealmModel(variableElement)) { - Utils.error(String.format(Locale.US, - "Field \"%s\" with type \"%s\" cannot be @Required or @NotNull.", element, element.asType())) - return false + if (Utils.isRealmModel(field)) { + /** + * Defer checking if @Required usage is valid when checking backlinks. See [categorizeBacklinkField] + */ + if (!embedded || field.getAnnotation(LinkingObjects::class.java) == null) { + Utils.error(String.format(Locale.US, + "Field \"%s\" with type \"%s\" cannot be @Required or @NotNull.", field, field.asType())) + return false + } } // Should never get here - user should remove @Required - if (nullableFields.contains(variableElement)) { + if (nullableFields.contains(field)) { Utils.error(String.format(Locale.US, "Field \"%s\" with type \"%s\" appears to be nullable. Consider removing @Required.", - element, - element.asType())) + field, + field.asType())) return false } @@ -706,6 +714,15 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat // From Core 6 String primary keys no longer needs to be indexed, and from Core 10 // none of the primary key types do. private fun categorizePrimaryKeyField(fieldElement: RealmFieldElement): Boolean { + // Embedded Objects do not support primary keys at all + if (embedded) { + Utils.error(String.format(Locale.US, + "A model class marked as embedded cannot contain a @PrimaryKey. One was defined for: %s", + fieldElement.simpleName.toString())) + return false + } + + // Only one primary key pr. class is allowed if (primaryKey != null) { Utils.error(String.format(Locale.US, "A class cannot have more than one @PrimaryKey. Both \"%s\" and \"%s\" are annotated as @PrimaryKey.", @@ -714,6 +731,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat return false } + // Check that the primary key is defined on a supported field val fieldType = fieldElement.asType() if (!isValidPrimaryKeyType(fieldType)) { Utils.error(String.format(Locale.US, diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt index 47123ba632..2d68ebee44 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProcessor.kt @@ -30,6 +30,7 @@ import javax.lang.model.element.TypeElement import io.realm.annotations.RealmClass import io.realm.annotations.RealmModule import javax.lang.model.element.Name +import javax.lang.model.type.TypeMirror /** @@ -115,6 +116,7 @@ import javax.lang.model.element.Name inline class QualifiedClassName(val name: String) { constructor(name: Name): this(name.toString()) + constructor(name: TypeMirror) : this(name.toString()) fun getSimpleName(): SimpleClassName { return SimpleClassName(Utils.stripPackage(name)) } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index 26b97deab7..be04a300e0 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -106,6 +106,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitInsertOrUpdateListMethod(writer) emitCreateDetachedCopyMethod(writer) emitUpdateMethod(writer) + emitUpdateEmbeddedObjectMethod(writer) emitToStringMethod(writer) emitRealmObjectProxyImplementation(writer) emitHashcodeMethod(writer) @@ -368,15 +369,26 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi // Getter - End // Setter - Start + val fieldType = QualifiedClassName(field.asType()) + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + val linkedQualifiedClassName: QualifiedClassName = Utils.getFieldTypeQualifiedName(field) + val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) emitAnnotation("Override") beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value") + emitStatement("Realm realm = (Realm) proxyState.getRealm\$realm()") emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) { // check excludeFields beginControlFlow("if (proxyState.getExcludeFields\$realm().contains(\"%1\$s\"))", field.simpleName.toString()) emitStatement("return") endControlFlow() beginControlFlow("if (value != null && !RealmObject.isManaged(value))") - emitStatement("value = ((Realm) proxyState.getRealm\$realm()).copyToRealm(value)") + if (fieldTypeMetaData.embedded) { + emitStatement("%1\$s proxyObject = realm.createEmbeddedObject(%1\$s.class, this, \"%2\$s\")", linkedQualifiedClassName, fieldName) + emitStatement("%s.updateEmbeddedObject(realm, value, proxyObject, new HashMap(), Collections.EMPTY_SET)", linkedProxyClass) + emitStatement("value = proxyObject") + } else { + emitStatement("value = realm.copyToRealm(value)") + } endControlFlow() // set value as default value @@ -395,8 +407,17 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("proxyState.getRow\$realm().nullifyLink(%s)", fieldColKeyVariableReference(field)) emitStatement("return") endControlFlow() - emitStatement("proxyState.checkValidObject(value)") - emitStatement("proxyState.getRow\$realm().setLink(%s, ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getObjectKey())", fieldColKeyVariableReference(field)) + + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (RealmObject.isManaged(value))") + emitStatement("proxyState.checkValidObject(value)") + endControlFlow() + emitStatement("%1\$s proxyObject = realm.createEmbeddedObject(%1\$s.class, this, \"%2\$s\")", linkedQualifiedClassName, fieldName) + emitStatement("%s.updateEmbeddedObject(realm, value, proxyObject, new HashMap(), Collections.EMPTY_SET)", linkedProxyClass) + } else { + emitStatement("proxyState.checkValidObject(value)") + emitStatement("proxyState.getRow\$realm().setLink(%s, ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getObjectKey())", fieldColKeyVariableReference(field)) + } endMethod() // Setter - End } @@ -598,19 +619,39 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (backlink in metadata.backlinkFields) { val cacheFieldName = backlink.targetField + BACKLINKS_FIELD_EXTENSION val realmResultsType = "RealmResults<" + backlink.sourceClass + ">" - // Getter, no setter - writer.apply { - emitAnnotation("Override") - beginMethod(realmResultsType, metadata.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC)) - emitStatement("BaseRealm realm = proxyState.getRealm\$realm()") - emitStatement("realm.checkIfValid()") - emitStatement("proxyState.getRow\$realm().checkIfAttached()") - beginControlFlow("if ($cacheFieldName == null)") + when (backlink.exposeAsRealmResults) { + true -> { + // Getter, no setter + writer.apply { + emitAnnotation("Override") + beginMethod(realmResultsType, metadata.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC)) + emitStatement("BaseRealm realm = proxyState.getRealm\$realm()") + emitStatement("realm.checkIfValid()") + emitStatement("proxyState.getRow\$realm().checkIfAttached()") + beginControlFlow("if ($cacheFieldName == null)") emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\")", backlink.sourceClass, backlink.sourceField) - endControlFlow() - emitStatement("return $cacheFieldName") - endMethod() - emitEmptyLine() + endControlFlow() + emitStatement("return $cacheFieldName") + endMethod() + emitEmptyLine() + } + } + false -> { + // Getter, no setter + writer.apply { + emitAnnotation("Override") + beginMethod(backlink.sourceClass.toString(), metadata.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC)) + emitStatement("BaseRealm realm = proxyState.getRealm\$realm()") + emitStatement("realm.checkIfValid()") + emitStatement("proxyState.getRow\$realm().checkIfAttached()") + beginControlFlow("if ($cacheFieldName == null)") + emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\").first()", backlink.sourceClass, backlink.sourceField) + endControlFlow() + emitStatement("return $cacheFieldName") // TODO: Figure out the exact API for this + endMethod() + emitEmptyLine() + } + } } } } @@ -634,8 +675,9 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi // Used to prevent array resizing at runtime val persistedFields = metadata.fields.size val computedFields = metadata.backlinkFields.size + val embeddedClass = if (metadata.embedded) "true" else "false" - emitStatement("OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\", %s, %s)", internalClassName, persistedFields, computedFields) + emitStatement("OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder(\"%s\", %s, %s, %s)", internalClassName, embeddedClass, persistedFields, computedFields) // For each field generate corresponding table index constant for (field in metadata.fields) { @@ -742,7 +784,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitNewProxyInstance(writer: JavaWriter) { writer.apply { - beginMethod(generatedClassName, "newProxyInstance", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC), "BaseRealm", "realm", "Row", "row") + beginMethod(generatedClassName, "newProxyInstance", EnumSet.of(Modifier.STATIC), "BaseRealm", "realm", "Row", "row") emitSingleLineComment("Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields") emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") emitStatement("objectContext.set(realm, row, realm.getSchema().getColumnInfo(%s.class), false, Collections.emptyList())", qualifiedJavaClassName) @@ -985,12 +1027,25 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitInsertMethod(writer: JavaWriter) { writer.apply { - beginMethod("long","insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", qualifiedJavaClassName.toString(), "object", "Map", "cache") - - // If object is already in the Realm there is nothing to update - beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") - emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey()") - endControlFlow() + val topLevelArgs = arrayOf("Realm", "realm", + qualifiedJavaClassName.toString(), "object", + "Map", "cache") + val embeddedArgs = arrayOf("Realm", "realm", + "Table", "parentObjectTable", + "long", "parentColumnKey", + "long", "parentObjectKey", + qualifiedJavaClassName.toString(), "object", + "Map", "cache") + val args = if (metadata.embedded) embeddedArgs else topLevelArgs + beginMethod("long","insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args) + + // If object is already in the Realm there is nothing to update, unless it is an embedded + // object. In which case we always update the underlying object. + if (!metadata.embedded) { + beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") + emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey()") + endControlFlow() + } emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("long tableNativePtr = table.getNativePtr()") @@ -1003,33 +1058,55 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (field in metadata.fields) { val fieldName = field.simpleName.toString() - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType().toString()) val getter = metadata.getInternalGetter(fieldName) when { Utils.isRealmModel(field) -> { + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - beginControlFlow("if (cache%s == null)", fieldName) - emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) - endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cache%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + } endControlFlow() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field) + val genericType = Utils.getGenericTypeQualifiedName(field)!! + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + } endControlFlow() endControlFlow() } @@ -1051,7 +1128,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } else -> { if (metadata.primaryKey !== field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, false) + setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, false) } } } @@ -1066,7 +1143,18 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitInsertListMethod(writer: JavaWriter) { writer.apply { - beginMethod("void", "insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", "Iterator", "objects", "Map", "cache") + val topLevelArgs = arrayOf("Realm", "realm", + "Iterator", "objects", + "Map", "cache") + val embeddedArgs = arrayOf("Realm", "realm", + "Table", "parentObjectTable", + "long", "parentColumnKey", + "long", "parentObjectKey", + "Iterator", "objects", + "Map", "cache") + val args = if (metadata.embedded) embeddedArgs else topLevelArgs + + beginMethod("void", "insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args) emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("long tableNativePtr = table.getNativePtr()") emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) @@ -1089,31 +1177,53 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (field in metadata.fields) { val fieldName = field.simpleName.toString() - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType().toString()) val getter = metadata.getInternalGetter(fieldName) if (Utils.isRealmModel(field)) { + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - beginControlFlow("if (cache%s == null)", fieldName) - emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) - endControlFlow() - emitStatement("table.setLink(columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cache%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + emitStatement("table.setLink(columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + } endControlFlow() } else if (Utils.isRealmModelList(field)) { - val genericType = Utils.getGenericTypeQualifiedName(field) + val genericType = Utils.getGenericTypeQualifiedName(field)!! + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + } endControlFlow() endControlFlow() } else if (Utils.isRealmValueList(field)) { @@ -1133,7 +1243,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endControlFlow() } else { if (metadata.primaryKey !== field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, false) + setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, false) } } } @@ -1146,7 +1256,17 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitInsertOrUpdateMethod(writer: JavaWriter) { writer.apply { - beginMethod("long", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", qualifiedJavaClassName.toString(), "object", "Map", "cache") + val topLevelArgs = arrayOf("Realm", "realm", + qualifiedJavaClassName.toString(), "object", + "Map", "cache") + val embeddedArgs = arrayOf("Realm", "realm", + "Table", "parentObjectTable", + "long", "parentColumnKey", + "long", "parentObjectKey", + qualifiedJavaClassName.toString(), "object", + "Map", "cache") + val args = if (metadata.embedded) embeddedArgs else topLevelArgs + beginMethod("long", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args) // If object is already in the Realm there is nothing to update beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))") @@ -1163,24 +1283,38 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (field in metadata.fields) { val fieldName = field.simpleName.toString() - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType().toString()) val getter = metadata.getInternalGetter(fieldName) if (Utils.isRealmModel(field)) { + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - beginControlFlow("if (cache%s == null)", fieldName) - emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + } nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, objKey)", fieldName) endControlFlow() } else if (Utils.isRealmModelList(field)) { - val genericType = Utils.getGenericTypeQualifiedName(field) + val genericType = Utils.getGenericTypeQualifiedName(field)!! + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + emitEmptyLine() emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) @@ -1190,20 +1324,36 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("for (int i = 0; i < objects; i++)") emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + } endControlFlow() nextControlFlow("else") emitStatement("%1\$sOsList.removeAll()", fieldName) beginControlFlow("if (%sList != null)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + } endControlFlow() endControlFlow() endControlFlow() @@ -1227,7 +1377,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() } else { if (metadata.primaryKey !== field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, true) + setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, true) } } } @@ -1241,7 +1391,18 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitInsertOrUpdateListMethod(writer: JavaWriter) { writer.apply { - beginMethod("void", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "Realm", "realm", "Iterator", "objects", "Map", "cache") + val topLevelArgs = arrayOf("Realm", "realm", + "Iterator", "objects", + "Map", "cache") + val embeddedArgs = arrayOf("Realm", "realm", + "Table", "parentObjectTable", + "long", "parentColumnKey", + "long", "parentObjectKey", + "Iterator", "objects", + "Map", "cache") + val args = if (metadata.embedded) embeddedArgs else topLevelArgs + + beginMethod("void", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args) emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("long tableNativePtr = table.getNativePtr()") emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) @@ -1263,26 +1424,40 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi for (field in metadata.fields) { val fieldName = field.simpleName.toString() - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType().toString()) val getter = metadata.getInternalGetter(fieldName) when { Utils.isRealmModel(field) -> { + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - beginControlFlow("if (cache%s == null)", fieldName) - emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s == null)", fieldName) + emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName) + } nextControlFlow("else") // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, objKey)", fieldName) endControlFlow() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field) + val genericType = Utils.getGenericTypeQualifiedName(field)!! + // FIXME: How to support types from other compilation units? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + emitEmptyLine() emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) @@ -1292,20 +1467,36 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("for (int i = 0; i < objectCount; i++)") emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName) + } endControlFlow() nextControlFlow("else") emitStatement("%1\$sOsList.removeAll()", fieldName) beginControlFlow("if (%sList != null)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - beginControlFlow("if (cacheItemIndex%s == null)", fieldName) - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) - endControlFlow() - emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cacheItemIndex%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) + nextControlFlow("else") + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + endControlFlow() + } else { + beginControlFlow("if (cacheItemIndex%s == null)", fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field)) + endControlFlow() + emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName) + } endControlFlow() endControlFlow() endControlFlow() @@ -1331,7 +1522,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } else -> { if (metadata.primaryKey !== field) { - setTableValues(writer, fieldType, fieldName, interfaceName, getter, true) + setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, true) } } } @@ -1402,8 +1593,13 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endControlFlow() emitStatement("cache.put(object, objKey)") } else { - emitStatement("long objKey = OsObject.createRow(table)") - emitStatement("cache.put(object, objKey)") + if (metadata.embedded) { + emitStatement("long objKey = OsObject.createEmbeddedObject(parentObjectTable, parentObjectKey, parentColumnKey)") + emitStatement("cache.put(object, objKey)") + } else { + emitStatement("long objKey = OsObject.createRow(table)") + emitStatement("cache.put(object, objKey)") + } } } } @@ -1424,7 +1620,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName) endControlFlow() emitEmptyLine() - emitStatement("%1\$s realmObjectSource = (%1\$s) newObject", interfaceName) + emitStatement("%1\$s unmanagedSource = (%1\$s) newObject", interfaceName) emitEmptyLine() emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, flags)") @@ -1436,7 +1632,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val fieldColKey = fieldColKeyVariableReference(field) val fieldName = field.simpleName.toString() val getter = metadata.getInternalGetter(fieldName) - emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldColKey, getter) + emitStatement("builder.%s(%s, unmanagedSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldColKey, getter) } // Create the underlying object @@ -1444,8 +1640,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitSingleLineComment("Create the underlying object and cache it before setting any object/objectlist references") emitSingleLineComment("This will allow us to break any circular dependencies by using the object cache.") emitStatement("Row row = builder.createNewObject()") - emitStatement("%s realmObjectCopy = newProxyInstance(realm, row)", generatedClassName) - emitStatement("cache.put(newObject, realmObjectCopy)") + emitStatement("%s managedCopy = newProxyInstance(realm, row)", generatedClassName) + emitStatement("cache.put(newObject, managedCopy)") // Copy all object references or lists-of-objects emitEmptyLine() @@ -1453,42 +1649,82 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitSingleLineComment("Finally add all fields that reference other Realm Objects, either directly or through a list") } for (field in metadata.objectReferenceFields) { - val fieldType = field.asType().toString() - val fieldName = field.simpleName.toString() - val getter = metadata.getInternalGetter(fieldName) - val setter = metadata.getInternalSetter(fieldName) + val fieldType = QualifiedClassName(field.asType()) + val fieldName: String = field.simpleName.toString() + val getter: String = metadata.getInternalGetter(fieldName) + val setter: String = metadata.getInternalSetter(fieldName) when { Utils.isRealmModel(field) -> { - emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) + // FIXME: How to support Embedded objects defined in another compilation unit? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + val fieldColKey: String = fieldColKeyVariableReference(field) + val linkedQualifiedClassName: QualifiedClassName = Utils.getFieldTypeQualifiedName(field) + val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) + + emitStatement("%s %sObj = unmanagedSource.%s()", fieldType, fieldName, getter) beginControlFlow("if (%sObj == null)", fieldName) - emitStatement("realmObjectCopy.%s(null)", setter) + emitStatement("managedCopy.%s(null)", setter) nextControlFlow("else") emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) - beginControlFlow("if (cache%s != null)", fieldName) - emitStatement("realmObjectCopy.%s(cache%s)", setter, fieldName) - nextControlFlow("else") - emitStatement("realmObjectCopy.%s(%s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, update, cache, flags))", setter, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) - endControlFlow() + + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) + nextControlFlow("else") + emitStatement("long objKey = ((RealmObjectProxy) managedCopy).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s)", fieldColKey) + emitStatement("Row linkedObjectRow = realm.getTable(%s.class).getUncheckedRow(objKey)", linkedQualifiedClassName) + emitStatement("%s linkedObject = %s.newProxyInstance(realm, linkedObjectRow)", linkedQualifiedClassName, linkedProxyClass) + emitStatement("cache.put(%sObj, (RealmObjectProxy) linkedObject)", fieldName) + emitStatement("%s.updateEmbeddedObject(realm, %sObj, linkedObject, cache, flags)", linkedProxyClass, fieldName) + endControlFlow() + } else { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("managedCopy.%s(cache%s)", setter, fieldName) + nextControlFlow("else") + emitStatement("managedCopy.%s(%s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, update, cache, flags))", setter, linkedProxyClass, columnInfoClassName(field), linkedQualifiedClassName, fieldName) + endControlFlow() + } + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. endControlFlow() emitEmptyLine() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field) - emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) - beginControlFlow("if (%sList != null)", fieldName) - emitStatement("RealmList<%s> %sRealmList = realmObjectCopy.%s()", genericType, fieldName, getter) + // FIXME: How to support Embedded objects defined in another compilation unit? + val listElementType: QualifiedClassName = Utils.getRealmListType(field)!! + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(listElementType) + val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!! + val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) + + emitStatement("RealmList<%s> %sUnmanagedList = unmanagedSource.%s()", genericType, fieldName, getter) + beginControlFlow("if (%sUnmanagedList != null)", fieldName) + emitStatement("RealmList<%s> %sManagedList = managedCopy.%s()", genericType, fieldName, getter) // Clear is needed. See bug https://github.com/realm/realm-java/issues/4957 - emitStatement("%sRealmList.clear()", fieldName) - beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) - emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sItem)", genericType, fieldName) - beginControlFlow("if (cache%s != null)", fieldName) - emitStatement("%1\$sRealmList.add(cache%1\$s)", fieldName) - nextControlFlow("else") - emitStatement("%1\$sRealmList.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, update, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) - endControlFlow() + emitStatement("%sManagedList.clear()", fieldName) + beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName) + emitStatement("%1\$s %2\$sUnmanagedItem = %2\$sUnmanagedList.get(i)", genericType, fieldName) + emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sUnmanagedItem)", genericType, fieldName) + + if (fieldTypeMetaData.embedded) { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) + nextControlFlow("else") + emitStatement("long objKey = %sManagedList.getOsList().createAndAddEmbeddedObject()", fieldName) + emitStatement("Row linkedObjectRow = realm.getTable(%s.class).getUncheckedRow(objKey)", genericType) + emitStatement("%s linkedObject = %s.newProxyInstance(realm, linkedObjectRow)", genericType, linkedProxyClass) + emitStatement("cache.put(%sUnmanagedItem, (RealmObjectProxy) linkedObject)", fieldName) + emitStatement("%s.updateEmbeddedObject(realm, %sUnmanagedItem, linkedObject, new HashMap(), Collections.EMPTY_SET)", linkedProxyClass, fieldName) + endControlFlow() + + } else { + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("%1\$sManagedList.add(cache%1\$s)", fieldName) + nextControlFlow("else") + emitStatement("%1\$sManagedList.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sUnmanagedItem, update, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) + endControlFlow() + } + endControlFlow() endControlFlow() emitEmptyLine() @@ -1498,7 +1734,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } } } - emitStatement("return realmObjectCopy") + emitStatement("return managedCopy") endMethod() emitEmptyLine() } @@ -1577,7 +1813,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitUpdateMethod(writer: JavaWriter) { - if (!metadata.hasPrimaryKey()) { + if (!metadata.hasPrimaryKey() && !metadata.embedded) { return } writer.apply { @@ -1594,43 +1830,91 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, flags)") for (field in metadata.fields) { - val fieldType = field.asType().toString() + val fieldType = QualifiedClassName(field.asType()) val fieldName = field.simpleName.toString() val getter = metadata.getInternalGetter(fieldName) val fieldColKey = fieldColKeyVariableReference(field) when { Utils.isRealmModel(field) -> { + // FIXME: How to support Embedded objects defined in another compilation unit? + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + emitEmptyLine() emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) beginControlFlow("if (%sObj == null)", fieldName) emitStatement("builder.addNull(%s)", fieldColKeyVariableReference(field)) nextControlFlow("else") + + if (fieldTypeMetaData.embedded) { + // Embedded objects are created in-place as we need to know the + // parent object + the property containing it. + // After this we know that changing values will always be considered + // an "update + emitSingleLineComment("Embedded objects are created directly instead of using the builder.") + emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) + endControlFlow() + emitEmptyLine() + emitStatement("long objKey = ((RealmObjectProxy) realmObject).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s)", fieldColKey) + emitStatement("Row row = realm.getTable(%s.class).getUncheckedRow(objKey)", Utils.getFieldTypeQualifiedName(field)) + emitStatement("%s proxyObject = %s.newProxyInstance(realm, row)", fieldType, Utils.getProxyClassSimpleName(field)) + emitStatement("cache.put(%sObj, (RealmObjectProxy) proxyObject)", fieldName) + emitStatement("%s.updateEmbeddedObject(realm, %sObj, proxyObject, cache, flags)", Utils.getProxyClassSimpleName(field), fieldName) + } else { + // Non-embedded classes are updating using normal recursive bottom-up approach emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) beginControlFlow("if (cache%s != null)", fieldName) emitStatement("builder.addObject(%s, cache%s)", fieldColKey, fieldName) nextControlFlow("else") emitStatement("builder.addObject(%s, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, true, cache, flags))", fieldColKey, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName) endControlFlow() + } + // No need to throw exception here if the field is not nullable. A exception will be thrown in setter. endControlFlow() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field) + // FIXME: How to support Embedded objects defined in another compilation unit? + val genericType: QualifiedClassName = Utils.getRealmListType(field)!! + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + val proxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) + emitEmptyLine() - emitStatement("RealmList<%s> %sList = realmObjectSource.%s()", genericType, fieldName, getter) - beginControlFlow("if (%sList != null)", fieldName) + emitStatement("RealmList<%s> %sUnmanagedList = realmObjectSource.%s()", genericType, fieldName, getter) + beginControlFlow("if (%sUnmanagedList != null)", fieldName) emitStatement("RealmList<%s> %sManagedCopy = new RealmList<%s>()", genericType, fieldName, genericType) - beginControlFlow("for (int i = 0; i < %sList.size(); i++)", fieldName) - emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) - emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sItem)", genericType, fieldName) - beginControlFlow("if (cache%s != null)", fieldName) - emitStatement("%1\$sManagedCopy.add(cache%1\$s)", fieldName) - nextControlFlow("else") - emitStatement("%1\$sManagedCopy.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, true, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field)) + + if (fieldTypeMetaData.embedded) { + beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName) + emitStatement("%1\$s %2\$sUnmanagedItem = %2\$sUnmanagedList.get(i)", genericType, fieldName) + emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sUnmanagedItem)", genericType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) + nextControlFlow("else") + emitStatement("long objKey = realmObjectTarget.%s().getOsList().createAndAddEmbeddedObject()", getter) + emitStatement("Row row = realm.getTable(%s.class).getUncheckedRow(objKey)", genericType) + emitStatement("%s proxyObject = %s.newProxyInstance(realm, row)", genericType, proxyClass) + emitStatement("cache.put(%sUnmanagedItem, (RealmObjectProxy) proxyObject)", fieldName) + emitStatement("%sManagedCopy.add(proxyObject)", fieldName) + emitStatement("%s.updateEmbeddedObject(realm, %sUnmanagedItem, proxyObject, new HashMap(), Collections.EMPTY_SET)", Utils.getProxyClassSimpleName(field), fieldName) + endControlFlow() endControlFlow() - endControlFlow() - emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldColKey, fieldName) + emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldColKey, fieldName) + } else { + beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName) + emitStatement("%1\$s %2\$sItem = %2\$sUnmanagedList.get(i)", genericType, fieldName) + emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sItem)", genericType, fieldName) + beginControlFlow("if (cache%s != null)", fieldName) + emitStatement("%1\$sManagedCopy.add(cache%1\$s)", fieldName) + nextControlFlow("else") + emitStatement("%1\$sManagedCopy.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, true, cache, flags))", fieldName, proxyClass, columnInfoClassName(field), genericType) + endControlFlow() + endControlFlow() + emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldColKey, fieldName) + } + nextControlFlow("else") emitStatement("builder.addObjectList(%s, new RealmList<%s>())", fieldColKey, genericType) endControlFlow() @@ -1641,13 +1925,37 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } } emitEmptyLine() - emitStatement("builder.updateExistingObject()") + if (metadata.embedded) { + emitStatement("builder.updateExistingEmbeddedObject((RealmObjectProxy) realmObject)") + } else { + emitStatement("builder.updateExistingTopLevelObject()") + } emitStatement("return realmObject") endMethod() emitEmptyLine() } } + @Throws(IOException::class) + private fun emitUpdateEmbeddedObjectMethod(writer: JavaWriter) { + if (!metadata.embedded) { + return + } + + writer.apply { + beginMethod("void", "updateEmbeddedObject", EnumSet.of(Modifier.STATIC, Modifier.PUBLIC), + "Realm", "realm", // Argument type & argument name + qualifiedJavaClassName.toString(), "unmanagedObject", + qualifiedJavaClassName.toString(), "managedObject", + "Map", "cache", + "Set", "flags" + ) + emitStatement("update(realm, (%s) realm.getSchema().getColumnInfo(%s.class), managedObject, unmanagedObject, cache, flags)", Utils.getSimpleColumnInfoClassName(metadata.qualifiedClassName), metadata.qualifiedClassName) + endMethod() + emitEmptyLine() + } + } + @Throws(IOException::class) private fun emitToStringMethod(writer: JavaWriter) { if (metadata.containsToString()) { diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt index 18fd5dea62..c6e3d17793 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt @@ -39,6 +39,7 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE private val qualifiedProxyClasses = ArrayList() private val simpleModelClassNames = ArrayList() private val internalClassNames = ArrayList() + private val embeddedClass = ArrayList() init { for (metadata in classesToValidate) { @@ -47,6 +48,7 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE qualifiedProxyClasses.add(qualifiedProxyClassName) simpleModelClassNames.add(metadata.simpleJavaClassName) internalClassNames.add(metadata.internalClassName) + embeddedClass.add(metadata.embedded) } } @@ -101,6 +103,8 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE emitCreteOrUpdateUsingJsonObject(this) emitCreateUsingJsonStream(this) emitCreateDetachedCopyMethod(this) + emitIsEmbeddedMethod(this) + emitUpdateEmbeddedObjectMethod(this) endType() close() } @@ -147,9 +151,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE "Class", "clazz", // Argument type & argument name "OsSchemaInfo", "schemaInfo" ) - emitMediatorShortCircuitSwitch({ i: Int -> + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> emitStatement("return %s.createColumnInfo(schemaInfo)", qualifiedProxyClasses[i]) - }, writer) + }) endMethod() emitEmptyLine() } @@ -165,9 +169,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE EnumSet.of(Modifier.PUBLIC), "Class", "clazz" ) - emitMediatorShortCircuitSwitch({ i: Int -> - emitStatement("return \"%s\"", internalClassNames[i]) - }, writer) + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> + emitStatement("return \"%s\"", internalClassNames[i]) + }) endMethod() emitEmptyLine() } @@ -191,9 +195,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") beginControlFlow("try") emitStatement("objectContext.set((BaseRealm) baseRealm, row, columnInfo, acceptDefaultValue, excludeFields)") - emitMediatorShortCircuitSwitch({ i: Int -> + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> emitStatement("return clazz.cast(new %s())", qualifiedProxyClasses[i]) - }, writer) + }) nextControlFlow("finally") emitStatement("objectContext.clear()") endControlFlow() @@ -231,10 +235,10 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass())") emitEmptyLine() - emitMediatorShortCircuitSwitch({i: Int -> + emitMediatorShortCircuitSwitch(writer, false) { i: Int -> emitStatement("%1\$s columnInfo = (%1\$s) realm.getSchema().getColumnInfo(%2\$s.class)", Utils.getSimpleColumnInfoClassName(qualifiedModelClasses[i]), qualifiedModelClasses[i]) emitStatement("return clazz.cast(%s.copyOrUpdate(realm, columnInfo, (%s) obj, update, cache, flags))", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + } endMethod() emitEmptyLine() } @@ -249,13 +253,23 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE "insert", EnumSet.of(Modifier.PUBLIC), "Realm", "realm", "RealmModel", "object", "Map", "cache") - emitSingleLineComment("This cast is correct because obj is either") - emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") - emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") - emitEmptyLine() - emitMediatorSwitch({ i: Int -> - emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + + if (embeddedClass.contains(false)) { + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") + emitEmptyLine() + emitMediatorSwitch(writer, false, { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { + emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + } + }) + } else { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } + endMethod() emitEmptyLine() } @@ -270,13 +284,22 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE "insertOrUpdate", EnumSet.of(Modifier.PUBLIC), "Realm", "realm", "RealmModel", "obj", "Map", "cache") + + if (embeddedClass.contains(false)) { emitSingleLineComment("This cast is correct because obj is either") emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass())") emitEmptyLine() - emitMediatorSwitch({ i: Int -> - emitStatement("%s.insertOrUpdate(realm, (%s) obj, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + emitMediatorSwitch(writer, false, { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { + emitStatement("%s.insertOrUpdate(realm, (%s) obj, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + } + }) + } else { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } endMethod() emitEmptyLine() } @@ -292,33 +315,52 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE EnumSet.of(Modifier.PUBLIC), "Realm", "realm", "Collection", "objects") + if (embeddedClass.contains(false)) { emitStatement("Iterator iterator = objects.iterator()") emitStatement("RealmModel object = null") emitStatement("Map cache = new HashMap(objects.size())") beginControlFlow("if (iterator.hasNext())") - emitSingleLineComment(" access the first element to figure out the clazz for the routing below") - emitStatement("object = iterator.next()") - emitSingleLineComment("This cast is correct because obj is either") - emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") - emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") - emitEmptyLine() + emitSingleLineComment(" access the first element to figure out the clazz for the routing below") + emitStatement("object = iterator.next()") + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") + emitEmptyLine() - emitMediatorSwitch({ i: Int -> + emitMediatorSwitch(writer, false) { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { emitStatement("%s.insertOrUpdate(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + } + } - beginControlFlow("if (iterator.hasNext())") - emitMediatorSwitch({ i: Int -> - emitStatement("%s.insertOrUpdate(realm, iterator, cache)", qualifiedProxyClasses[i]) - }, writer, false) - endControlFlow() + beginControlFlow("if (iterator.hasNext())") + emitMediatorSwitch(writer, false) { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { + emitStatement("%s.insertOrUpdate(realm, iterator, cache)", qualifiedProxyClasses[i]) + } + } endControlFlow() + endControlFlow() + } else { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } + endMethod() emitEmptyLine() } } + private fun emitEmbeddedObjectsCannotBeCopiedException(writer: JavaWriter) { + writer.apply { + emitStatement("throw new IllegalArgumentException(\"Embedded objects cannot be copied into Realm by themselves. They need to be attached to a parent object\")") + } + } + @Throws(IOException::class) private fun emitInsertListToRealmMethod(writer: JavaWriter) { writer.apply { @@ -329,29 +371,41 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE EnumSet.of(Modifier.PUBLIC), "Realm", "realm", "Collection", "objects") - emitStatement("Iterator iterator = objects.iterator()") - emitStatement("RealmModel object = null") - emitStatement("Map cache = new HashMap(objects.size())") + if (embeddedClass.contains(false)) { + emitStatement("Iterator iterator = objects.iterator()") + emitStatement("RealmModel object = null") + emitStatement("Map cache = new HashMap(objects.size())") - beginControlFlow("if (iterator.hasNext())") - .emitSingleLineComment(" access the first element to figure out the clazz for the routing below") - .emitStatement("object = iterator.next()") - .emitSingleLineComment("This cast is correct because obj is either") - .emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") - .emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") - .emitEmptyLine() - - emitMediatorSwitch({ i: Int -> + beginControlFlow("if (iterator.hasNext())") + .emitSingleLineComment(" access the first element to figure out the clazz for the routing below") + .emitStatement("object = iterator.next()") + .emitSingleLineComment("This cast is correct because obj is either") + .emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + .emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) ((object instanceof RealmObjectProxy) ? object.getClass().getSuperclass() : object.getClass())") + .emitEmptyLine() + + emitMediatorSwitch(writer, false, { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { emitStatement("%s.insert(realm, (%s) object, cache)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + } + }) beginControlFlow("if (iterator.hasNext())") - emitMediatorSwitch({ i: Int -> + emitMediatorSwitch(writer, false, { i: Int -> + if (embeddedClass[i]) { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } else { emitStatement("%s.insert(realm, iterator, cache)", qualifiedProxyClasses[i]) - }, writer, false) + } + }) + endControlFlow() endControlFlow() - endControlFlow() + } else { + emitEmbeddedObjectsCannotBeCopiedException(writer) + } endMethod() emitEmptyLine() } @@ -368,9 +422,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE Arrays.asList("Class", "clazz", "Realm", "realm", "JSONObject", "json", "boolean", "update"), Arrays.asList("JSONException") ) - emitMediatorShortCircuitSwitch({ i: Int -> - emitStatement("return clazz.cast(%s.createOrUpdateUsingJsonObject(realm, json, update))", qualifiedProxyClasses[i]) - }, writer) + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> + emitStatement("return clazz.cast(%s.createOrUpdateUsingJsonObject(realm, json, update))", qualifiedProxyClasses[i]) + }) endMethod() emitEmptyLine() } @@ -387,9 +441,9 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE Arrays.asList("Class", "clazz", "Realm", "realm", "JsonReader", "reader"), Arrays.asList("java.io.IOException") ) - emitMediatorShortCircuitSwitch({ i: Int -> + emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> emitStatement("return clazz.cast(%s.createUsingJsonStream(realm, reader))", qualifiedProxyClasses[i]) - }, writer) + }) endMethod() emitEmptyLine() } @@ -409,20 +463,70 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) realmObject.getClass().getSuperclass()") emitEmptyLine() - emitMediatorShortCircuitSwitch({ i: Int -> + emitMediatorShortCircuitSwitch(writer, false, { i: Int -> emitStatement("return clazz.cast(%s.createDetachedCopy((%s) realmObject, 0, maxDepth, cache))", qualifiedProxyClasses[i], qualifiedModelClasses[i]) - }, writer, false) + }) endMethod() emitEmptyLine() } } + @Throws(IOException::class) + private fun emitIsEmbeddedMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + " boolean", + "isEmbedded", + EnumSet.of(Modifier.PUBLIC), + "Class", "clazz" + ) + emitMediatorShortCircuitSwitch(writer, false, { i: Int -> + emitStatement("return %s", if (embeddedClass[i]) "true" else "false") + }) + endMethod() + emitEmptyLine() + } + } + + @Throws(IOException::class) + private fun emitUpdateEmbeddedObjectMethod(writer: JavaWriter) { + writer.apply { + emitAnnotation("Override") + beginMethod( + " void", + "updateEmbeddedObject", + EnumSet.of(Modifier.PUBLIC), + "Realm", "realm", + "E", "unmanagedObject", + "E", "managedObject", + "Map", "cache", + "Set", "flags" + ) + + emitSingleLineComment("This cast is correct because obj is either") + emitSingleLineComment("generated by RealmProxy or the original type extending directly from RealmObject") + emitStatement("@SuppressWarnings(\"unchecked\") Class clazz = (Class) managedObject.getClass().getSuperclass()") + emitEmptyLine() + emitMediatorSwitch(writer, false) { i: Int -> + if (embeddedClass[i]) { + emitStatement("%1\$s.updateEmbeddedObject(realm, (%2\$s) unmanagedObject, (%2\$s) managedObject, cache, flags)", qualifiedProxyClasses[i], qualifiedModelClasses[i]) + } else { + emitStatement("throw getNotEmbeddedClassException(\"%s\")", qualifiedModelClasses[i]) + } + } + endMethod() + emitEmptyLine() + } + } + + // Emits the control flow for selecting the appropriate proxy class based on the model class // Currently it is just if..else, which is inefficient for large amounts amounts of model classes. // Consider switching to HashMap or similar. @Throws(IOException::class) - private fun emitMediatorSwitch(emitStatement: (index: Int) -> Unit, writer: JavaWriter, nullPointerCheck: Boolean) { + private fun emitMediatorSwitch(writer: JavaWriter, nullPointerCheck: Boolean, emitStatement: (index: Int) -> Unit) { writer.apply { if (nullPointerCheck) { emitStatement("checkClass(clazz)") @@ -445,7 +549,7 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE } @Throws(IOException::class) - private fun emitMediatorShortCircuitSwitch(emitStatement: (index: Int) -> Unit, writer: JavaWriter, nullPointerCheck: Boolean = true) { + private fun emitMediatorShortCircuitSwitch(writer: JavaWriter, nullPointerCheck: Boolean = true, emitStatement: (index: Int) -> Unit) { writer.apply { if (nullPointerCheck) { emitStatement("checkClass(clazz)") diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java new file mode 100644 index 0000000000..4aae15f466 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java @@ -0,0 +1,115 @@ +package io.realm.processor; + +import com.google.testing.compile.JavaFileObjects; + +import org.junit.Test; + +import java.util.Arrays; + +import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; +import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; +import static org.truth0.Truth.ASSERT; + +public class RealmEmbeddedObjectsTest { + + @Test + public void compileEmbeddedObjectFile() { + ASSERT.about(javaSource()) + .that(JavaFileObjects.forResource("some/test/EmbeddedClass.java")) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + @Test + public void compileParentToEmbeddedObjectFile() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassSimpleParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClass.java") + )) + .processedWith(new RealmProcessor()) + .compilesWithoutError() + .and() + .generatesSources(JavaFileObjects.forResource("io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java")); + } + + @Test + public void compileWithSingleRequiredParent() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClass.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassOptionalParents.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassRequiredParent.java") + )) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + + @Test + public void compileWithMultipleOptionalParents() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClass.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassRequiredParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassOptionalParents.java") + )) + .processedWith(new RealmProcessor()) + .compilesWithoutError(); + } + + @Test + public void failToCompileIfSingleParentIsMissingFinal() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassMissingFinalOnLinkingObjects.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("The @LinkingObjects field \"some.test.EmbeddedClassMissingFinalOnLinkingObjects.parent\" must be final."); + } + + // If a single parent type has multiple potential fields that can act as parent. Any + // @LinkingObject field in the child must designate the field name in the parent. + @Test + public void failToCompileIfMissingFieldDescriptor() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassMissingFieldDescription.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("The @LinkingObjects annotation for the field \"some.test.EmbeddedClassMissingFieldDescription.parent1\" must have a parameter identifying the link target."); + } + + + // @PrimaryKey is not allowed inside embedded classes + @Test + public void failToCompileWithPrimaryKey() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassPrimaryKey.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("A model class marked as embedded cannot contain a @PrimaryKey."); + } + + // If a child has multiple potential parents, none of them are allowed to be marked + // @Required. + @Test + public void failToCompileWithMultipleRequiredParents() { + ASSERT.about(javaSources()) + .that(Arrays.asList( + JavaFileObjects.forResource("some/test/EmbeddedClassParent.java"), + JavaFileObjects.forResource("some/test/EmbeddedClassMultipleRequiredParents.java") + )) + .processedWith(new RealmProcessor()) + .failsToCompile() + .withErrorContaining("@Required cannot be used on @LinkingObjects field if multiple @LinkingParents are defined"); + } +} diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java index 9c5b47c9da..082303afba 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/RealmDefaultModuleMediator.java @@ -206,4 +206,25 @@ public E createDetachedCopy(E realmObject, int maxDepth, throw getMissingProxyClassException(clazz); } -} + @Override + public boolean isEmbedded(Class clazz) { + if (clazz.equals(some.test.AllTypes.class)) { + return false; + } + throw getMissingProxyClassException(clazz); + } + + @Override + public void updateEmbeddedObject(Realm realm, E unmanagedObject, E managedObject, Map cache, Set flags) { + // This cast is correct because obj is either + // generated by RealmProxy or the original type extending directly from RealmObject + @SuppressWarnings("unchecked") Class clazz = (Class) managedObject.getClass().getSuperclass(); + + if (clazz.equals(some.test.AllTypes.class)) { + throw getNotEmbeddedClassException("some.test.AllTypes"); + } else { + throw getMissingProxyClassException(clazz); + } + } + +} \ No newline at end of file diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index 2a61aac1bf..918f1b8bb8 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -411,6 +411,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @Override public void realmSet$columnObject(some.test.AllTypes value) { + Realm realm = (Realm) proxyState.getRealm$realm(); if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -419,7 +420,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } if (value != null && !RealmObject.isManaged(value)) { - value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value); + value = realm.copyToRealm(value); } final Row row = proxyState.getRow$realm(); if (value == null) { @@ -982,7 +983,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", 24, 1); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", false, 24, 1); builder.addPersistedProperty("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -1368,7 +1369,7 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r return realm.copyToRealm(obj); } - private static some_test_AllTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_AllTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.AllTypes.class), false, Collections.emptyList()); @@ -1427,70 +1428,70 @@ public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo return (some.test.AllTypes) cachedRealmObject; } - some_test_AllTypesRealmProxyInterface realmObjectSource = (some_test_AllTypesRealmProxyInterface) newObject; + some_test_AllTypesRealmProxyInterface unmanagedSource = (some_test_AllTypesRealmProxyInterface) newObject; Table table = realm.getTable(some.test.AllTypes.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.columnStringColKey, realmObjectSource.realmGet$columnString()); - builder.addInteger(columnInfo.columnLongColKey, realmObjectSource.realmGet$columnLong()); - builder.addFloat(columnInfo.columnFloatColKey, realmObjectSource.realmGet$columnFloat()); - builder.addDouble(columnInfo.columnDoubleColKey, realmObjectSource.realmGet$columnDouble()); - builder.addBoolean(columnInfo.columnBooleanColKey, realmObjectSource.realmGet$columnBoolean()); - builder.addDecimal128(columnInfo.columnDecimal128ColKey, realmObjectSource.realmGet$columnDecimal128()); - builder.addObjectId(columnInfo.columnObjectIdColKey, realmObjectSource.realmGet$columnObjectId()); - builder.addDate(columnInfo.columnDateColKey, realmObjectSource.realmGet$columnDate()); - builder.addByteArray(columnInfo.columnBinaryColKey, realmObjectSource.realmGet$columnBinary()); - builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, realmObjectSource.realmGet$columnMutableRealmInteger()); - builder.addStringList(columnInfo.columnStringListColKey, realmObjectSource.realmGet$columnStringList()); - builder.addByteArrayList(columnInfo.columnBinaryListColKey, realmObjectSource.realmGet$columnBinaryList()); - builder.addBooleanList(columnInfo.columnBooleanListColKey, realmObjectSource.realmGet$columnBooleanList()); - builder.addLongList(columnInfo.columnLongListColKey, realmObjectSource.realmGet$columnLongList()); - builder.addIntegerList(columnInfo.columnIntegerListColKey, realmObjectSource.realmGet$columnIntegerList()); - builder.addShortList(columnInfo.columnShortListColKey, realmObjectSource.realmGet$columnShortList()); - builder.addByteList(columnInfo.columnByteListColKey, realmObjectSource.realmGet$columnByteList()); - builder.addDoubleList(columnInfo.columnDoubleListColKey, realmObjectSource.realmGet$columnDoubleList()); - builder.addFloatList(columnInfo.columnFloatListColKey, realmObjectSource.realmGet$columnFloatList()); - builder.addDateList(columnInfo.columnDateListColKey, realmObjectSource.realmGet$columnDateList()); - builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, realmObjectSource.realmGet$columnDecimal128List()); - builder.addObjectIdList(columnInfo.columnObjectIdListColKey, realmObjectSource.realmGet$columnObjectIdList()); + builder.addString(columnInfo.columnStringColKey, unmanagedSource.realmGet$columnString()); + builder.addInteger(columnInfo.columnLongColKey, unmanagedSource.realmGet$columnLong()); + builder.addFloat(columnInfo.columnFloatColKey, unmanagedSource.realmGet$columnFloat()); + builder.addDouble(columnInfo.columnDoubleColKey, unmanagedSource.realmGet$columnDouble()); + builder.addBoolean(columnInfo.columnBooleanColKey, unmanagedSource.realmGet$columnBoolean()); + builder.addDecimal128(columnInfo.columnDecimal128ColKey, unmanagedSource.realmGet$columnDecimal128()); + builder.addObjectId(columnInfo.columnObjectIdColKey, unmanagedSource.realmGet$columnObjectId()); + builder.addDate(columnInfo.columnDateColKey, unmanagedSource.realmGet$columnDate()); + builder.addByteArray(columnInfo.columnBinaryColKey, unmanagedSource.realmGet$columnBinary()); + builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, unmanagedSource.realmGet$columnMutableRealmInteger()); + builder.addStringList(columnInfo.columnStringListColKey, unmanagedSource.realmGet$columnStringList()); + builder.addByteArrayList(columnInfo.columnBinaryListColKey, unmanagedSource.realmGet$columnBinaryList()); + builder.addBooleanList(columnInfo.columnBooleanListColKey, unmanagedSource.realmGet$columnBooleanList()); + builder.addLongList(columnInfo.columnLongListColKey, unmanagedSource.realmGet$columnLongList()); + builder.addIntegerList(columnInfo.columnIntegerListColKey, unmanagedSource.realmGet$columnIntegerList()); + builder.addShortList(columnInfo.columnShortListColKey, unmanagedSource.realmGet$columnShortList()); + builder.addByteList(columnInfo.columnByteListColKey, unmanagedSource.realmGet$columnByteList()); + builder.addDoubleList(columnInfo.columnDoubleListColKey, unmanagedSource.realmGet$columnDoubleList()); + builder.addFloatList(columnInfo.columnFloatListColKey, unmanagedSource.realmGet$columnFloatList()); + builder.addDateList(columnInfo.columnDateListColKey, unmanagedSource.realmGet$columnDateList()); + builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, unmanagedSource.realmGet$columnDecimal128List()); + builder.addObjectIdList(columnInfo.columnObjectIdListColKey, unmanagedSource.realmGet$columnObjectIdList()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_AllTypesRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_AllTypesRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); // Finally add all fields that reference other Realm Objects, either directly or through a list - some.test.AllTypes columnObjectObj = realmObjectSource.realmGet$columnObject(); + some.test.AllTypes columnObjectObj = unmanagedSource.realmGet$columnObject(); if (columnObjectObj == null) { - realmObjectCopy.realmSet$columnObject(null); + managedCopy.realmSet$columnObject(null); } else { some.test.AllTypes cachecolumnObject = (some.test.AllTypes) cache.get(columnObjectObj); if (cachecolumnObject != null) { - realmObjectCopy.realmSet$columnObject(cachecolumnObject); + managedCopy.realmSet$columnObject(cachecolumnObject); } else { - realmObjectCopy.realmSet$columnObject(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnObjectObj, update, cache, flags)); + managedCopy.realmSet$columnObject(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnObjectObj, update, cache, flags)); } } - RealmList columnRealmListList = realmObjectSource.realmGet$columnRealmList(); - if (columnRealmListList != null) { - RealmList columnRealmListRealmList = realmObjectCopy.realmGet$columnRealmList(); - columnRealmListRealmList.clear(); - for (int i = 0; i < columnRealmListList.size(); i++) { - some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); - some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); + RealmList columnRealmListUnmanagedList = unmanagedSource.realmGet$columnRealmList(); + if (columnRealmListUnmanagedList != null) { + RealmList columnRealmListManagedList = managedCopy.realmGet$columnRealmList(); + columnRealmListManagedList.clear(); + for (int i = 0; i < columnRealmListUnmanagedList.size(); i++) { + some.test.AllTypes columnRealmListUnmanagedItem = columnRealmListUnmanagedList.get(i); + some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListUnmanagedItem); if (cachecolumnRealmList != null) { - columnRealmListRealmList.add(cachecolumnRealmList); + columnRealmListManagedList.add(cachecolumnRealmList); } else { - columnRealmListRealmList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnRealmListItem, update, cache, flags)); + columnRealmListManagedList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnRealmListUnmanagedItem, update, cache, flags)); } } } - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.AllTypes object, Map cache) { @@ -2572,11 +2573,11 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som } } - RealmList columnRealmListList = realmObjectSource.realmGet$columnRealmList(); - if (columnRealmListList != null) { + RealmList columnRealmListUnmanagedList = realmObjectSource.realmGet$columnRealmList(); + if (columnRealmListUnmanagedList != null) { RealmList columnRealmListManagedCopy = new RealmList(); - for (int i = 0; i < columnRealmListList.size(); i++) { - some.test.AllTypes columnRealmListItem = columnRealmListList.get(i); + for (int i = 0; i < columnRealmListUnmanagedList.size(); i++) { + some.test.AllTypes columnRealmListItem = columnRealmListUnmanagedList.get(i); some.test.AllTypes cachecolumnRealmList = (some.test.AllTypes) cache.get(columnRealmListItem); if (cachecolumnRealmList != null) { columnRealmListManagedCopy.add(cachecolumnRealmList); @@ -2601,7 +2602,7 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, realmObjectSource.realmGet$columnDecimal128List()); builder.addObjectIdList(columnInfo.columnObjectIdListColKey, realmObjectSource.realmGet$columnObjectIdList()); - builder.updateExistingObject(); + builder.updateExistingTopLevelObject(); return realmObject; } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java index 6906c18754..719b35e008 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_BooleansRealmProxy.java @@ -185,7 +185,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Booleans", 4, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Booleans", false, 4, 0); builder.addPersistedProperty("done", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("isReady", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("mCompleted", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -293,7 +293,7 @@ public static some.test.Booleans createUsingJsonStream(Realm realm, JsonReader r return realm.copyToRealm(obj); } - private static some_test_BooleansRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_BooleansRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.Booleans.class), false, Collections.emptyList()); @@ -327,24 +327,24 @@ public static some.test.Booleans copy(Realm realm, BooleansColumnInfo columnInfo return (some.test.Booleans) cachedRealmObject; } - some_test_BooleansRealmProxyInterface realmObjectSource = (some_test_BooleansRealmProxyInterface) newObject; + some_test_BooleansRealmProxyInterface unmanagedSource = (some_test_BooleansRealmProxyInterface) newObject; Table table = realm.getTable(some.test.Booleans.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addBoolean(columnInfo.doneColKey, realmObjectSource.realmGet$done()); - builder.addBoolean(columnInfo.isReadyColKey, realmObjectSource.realmGet$isReady()); - builder.addBoolean(columnInfo.mCompletedColKey, realmObjectSource.realmGet$mCompleted()); - builder.addBoolean(columnInfo.anotherBooleanColKey, realmObjectSource.realmGet$anotherBoolean()); + builder.addBoolean(columnInfo.doneColKey, unmanagedSource.realmGet$done()); + builder.addBoolean(columnInfo.isReadyColKey, unmanagedSource.realmGet$isReady()); + builder.addBoolean(columnInfo.mCompletedColKey, unmanagedSource.realmGet$mCompleted()); + builder.addBoolean(columnInfo.anotherBooleanColKey, unmanagedSource.realmGet$anotherBoolean()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_BooleansRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_BooleansRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.Booleans object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java new file mode 100644 index 0000000000..44e2dc1f99 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java @@ -0,0 +1,867 @@ +package io.realm; + + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.JsonReader; +import android.util.JsonToken; +import io.realm.ImportFlag; +import io.realm.ProxyUtils; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.ColumnInfo; +import io.realm.internal.OsList; +import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.OsSchemaInfo; +import io.realm.internal.Property; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; +import io.realm.internal.Table; +import io.realm.internal.android.JsonUtils; +import io.realm.internal.objectstore.OsObjectBuilder; +import io.realm.log.RealmLog; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +@SuppressWarnings("all") +public class some_test_EmbeddedClassSimpleParentRealmProxy extends some.test.EmbeddedClassSimpleParent + implements RealmObjectProxy, some_test_EmbeddedClassSimpleParentRealmProxyInterface { + + static final class EmbeddedClassSimpleParentColumnInfo extends ColumnInfo { + long idColKey; + long childColKey; + long childrenColKey; + + EmbeddedClassSimpleParentColumnInfo(OsSchemaInfo schemaInfo) { + super(3); + OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("EmbeddedClassSimpleParent"); + this.idColKey = addColumnDetails("id", "id", objectSchemaInfo); + this.childColKey = addColumnDetails("child", "child", objectSchemaInfo); + this.childrenColKey = addColumnDetails("children", "children", objectSchemaInfo); + } + + EmbeddedClassSimpleParentColumnInfo(ColumnInfo src, boolean mutable) { + super(src, mutable); + copy(src, this); + } + + @Override + protected final ColumnInfo copy(boolean mutable) { + return new EmbeddedClassSimpleParentColumnInfo(this, mutable); + } + + @Override + protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { + final EmbeddedClassSimpleParentColumnInfo src = (EmbeddedClassSimpleParentColumnInfo) rawSrc; + final EmbeddedClassSimpleParentColumnInfo dst = (EmbeddedClassSimpleParentColumnInfo) rawDst; + dst.idColKey = src.idColKey; + dst.childColKey = src.childColKey; + dst.childrenColKey = src.childrenColKey; + } + } + + private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); + + private EmbeddedClassSimpleParentColumnInfo columnInfo; + private ProxyState proxyState; + private RealmList childrenRealmList; + + some_test_EmbeddedClassSimpleParentRealmProxy() { + proxyState.setConstructionFinished(); + } + + @Override + public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } + final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); + this.columnInfo = (EmbeddedClassSimpleParentColumnInfo) context.getColumnInfo(); + this.proxyState = new ProxyState(this); + proxyState.setRealm$realm(context.getRealm()); + proxyState.setRow$realm(context.getRow()); + proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); + proxyState.setExcludeFields$realm(context.getExcludeFields()); + } + + @Override + @SuppressWarnings("cast") + public String realmGet$id() { + proxyState.getRealm$realm().checkIfValid(); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.idColKey); + } + + @Override + public void realmSet$id(String value) { + if (proxyState.isUnderConstruction()) { + // default value of the primary key is always ignored. + return; + } + + proxyState.getRealm$realm().checkIfValid(); + throw new io.realm.exceptions.RealmException("Primary key field 'id' cannot be changed after object was created."); + } + + @Override + public some.test.EmbeddedClass realmGet$child() { + proxyState.getRealm$realm().checkIfValid(); + if (proxyState.getRow$realm().isNullLink(columnInfo.childColKey)) { + return null; + } + return proxyState.getRealm$realm().get(some.test.EmbeddedClass.class, proxyState.getRow$realm().getLink(columnInfo.childColKey), false, Collections.emptyList()); + } + + @Override + public void realmSet$child(some.test.EmbeddedClass value) { + Realm realm = (Realm) proxyState.getRealm$realm(); + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("child")) { + return; + } + if (value != null && !RealmObject.isManaged(value)) { + some.test.EmbeddedClass proxyObject = realm.createEmbeddedObject(some.test.EmbeddedClass.class, this, "child"); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, value, proxyObject, new HashMap(), Collections.EMPTY_SET); + value = proxyObject; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + // Table#nullifyLink() does not support default value. Just using Row. + row.nullifyLink(columnInfo.childColKey); + return; + } + proxyState.checkValidObject(value); + row.getTable().setLink(columnInfo.childColKey, row.getObjectKey(), ((RealmObjectProxy) value).realmGet$proxyState().getRow$realm().getObjectKey(), true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + proxyState.getRow$realm().nullifyLink(columnInfo.childColKey); + return; + } + if (RealmObject.isManaged(value)) { + proxyState.checkValidObject(value); + } + some.test.EmbeddedClass proxyObject = realm.createEmbeddedObject(some.test.EmbeddedClass.class, this, "child"); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, value, proxyObject, new HashMap(), Collections.EMPTY_SET); + } + + @Override + public RealmList realmGet$children() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (childrenRealmList != null) { + return childrenRealmList; + } else { + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.childrenColKey); + childrenRealmList = new RealmList(some.test.EmbeddedClass.class, osList, proxyState.getRealm$realm()); + return childrenRealmList; + } + } + + @Override + public void realmSet$children(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("children")) { + return; + } + // if the list contains unmanaged RealmObjects, convert them to managed. + if (value != null && !value.isManaged()) { + final Realm realm = (Realm) proxyState.getRealm$realm(); + final RealmList original = value; + value = new RealmList(); + for (some.test.EmbeddedClass item : original) { + if (item == null || RealmObject.isManaged(item)) { + value.add(item); + } else { + value.add(realm.copyToRealm(item)); + } + } + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.childrenColKey); + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + if (value != null && value.size() == osList.size()) { + int objects = value.size(); + for (int i = 0; i < objects; i++) { + some.test.EmbeddedClass linkedObject = value.get(i); + proxyState.checkValidObject(linkedObject); + osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getObjectKey()); + } + } else { + osList.removeAll(); + if (value == null) { + return; + } + int objects = value.size(); + for (int i = 0; i < objects; i++) { + some.test.EmbeddedClass linkedObject = value.get(i); + proxyState.checkValidObject(linkedObject); + osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getObjectKey()); + } + } + } + + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("EmbeddedClassSimpleParent", false, 3, 0); + builder.addPersistedProperty("id", RealmFieldType.STRING, Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addPersistedLinkProperty("child", RealmFieldType.OBJECT, "EmbeddedClass"); + builder.addPersistedLinkProperty("children", RealmFieldType.LIST, "EmbeddedClass"); + return builder.build(); + } + + public static OsObjectSchemaInfo getExpectedObjectSchemaInfo() { + return expectedObjectSchemaInfo; + } + + public static EmbeddedClassSimpleParentColumnInfo createColumnInfo(OsSchemaInfo schemaInfo) { + return new EmbeddedClassSimpleParentColumnInfo(schemaInfo); + } + + public static String getSimpleClassName() { + return "EmbeddedClassSimpleParent"; + } + + public static final class ClassNameHelper { + public static final String INTERNAL_CLASS_NAME = "EmbeddedClassSimpleParent"; + } + + @SuppressWarnings("cast") + public static some.test.EmbeddedClassSimpleParent createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) + throws JSONException { + final List excludeFields = new ArrayList(2); + some.test.EmbeddedClassSimpleParent obj = null; + if (update) { + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + long objKey = Table.NO_MATCH; + if (json.isNull("id")) { + objKey = table.findFirstNull(pkColumnKey); + } else { + objKey = table.findFirstString(pkColumnKey, json.getString("id")); + } + if (objKey != Table.NO_MATCH) { + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + try { + objectContext.set(realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class), false, Collections. emptyList()); + obj = new io.realm.some_test_EmbeddedClassSimpleParentRealmProxy(); + } finally { + objectContext.clear(); + } + } + } + if (obj == null) { + if (json.has("child")) { + excludeFields.add("child"); + } + if (json.has("children")) { + excludeFields.add("children"); + } + if (json.has("id")) { + if (json.isNull("id")) { + obj = (io.realm.some_test_EmbeddedClassSimpleParentRealmProxy) realm.createObjectInternal(some.test.EmbeddedClassSimpleParent.class, null, true, excludeFields); + } else { + obj = (io.realm.some_test_EmbeddedClassSimpleParentRealmProxy) realm.createObjectInternal(some.test.EmbeddedClassSimpleParent.class, json.getString("id"), true, excludeFields); + } + } else { + throw new IllegalArgumentException("JSON object doesn't have the primary key field 'id'."); + } + } + + final some_test_EmbeddedClassSimpleParentRealmProxyInterface objProxy = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) obj; + if (json.has("child")) { + if (json.isNull("child")) { + objProxy.realmSet$child(null); + } else { + some.test.EmbeddedClass childObj = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, json.getJSONObject("child"), update); + objProxy.realmSet$child(childObj); + } + } + if (json.has("children")) { + if (json.isNull("children")) { + objProxy.realmSet$children(null); + } else { + objProxy.realmGet$children().clear(); + JSONArray array = json.getJSONArray("children"); + for (int i = 0; i < array.length(); i++) { + some.test.EmbeddedClass item = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update); + objProxy.realmGet$children().add(item); + } + } + } + return obj; + } + + @SuppressWarnings("cast") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static some.test.EmbeddedClassSimpleParent createUsingJsonStream(Realm realm, JsonReader reader) + throws IOException { + boolean jsonHasPrimaryKey = false; + final some.test.EmbeddedClassSimpleParent obj = new some.test.EmbeddedClassSimpleParent(); + final some_test_EmbeddedClassSimpleParentRealmProxyInterface objProxy = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) obj; + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (false) { + } else if (name.equals("id")) { + if (reader.peek() != JsonToken.NULL) { + objProxy.realmSet$id((String) reader.nextString()); + } else { + reader.skipValue(); + objProxy.realmSet$id(null); + } + jsonHasPrimaryKey = true; + } else if (name.equals("child")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$child(null); + } else { + some.test.EmbeddedClass childObj = some_test_EmbeddedClassRealmProxy.createUsingJsonStream(realm, reader); + objProxy.realmSet$child(childObj); + } + } else if (name.equals("children")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$children(null); + } else { + objProxy.realmSet$children(new RealmList()); + reader.beginArray(); + while (reader.hasNext()) { + some.test.EmbeddedClass item = some_test_EmbeddedClassRealmProxy.createUsingJsonStream(realm, reader); + objProxy.realmGet$children().add(item); + } + reader.endArray(); + } + } else { + reader.skipValue(); + } + } + reader.endObject(); + if (!jsonHasPrimaryKey) { + throw new IllegalArgumentException("JSON object doesn't have the primary key field 'id'."); + } + return realm.copyToRealm(obj); + } + + static some_test_EmbeddedClassSimpleParentRealmProxy newProxyInstance(BaseRealm realm, Row row) { + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class), false, Collections.emptyList()); + io.realm.some_test_EmbeddedClassSimpleParentRealmProxy obj = new io.realm.some_test_EmbeddedClassSimpleParentRealmProxy(); + objectContext.clear(); + return obj; + } + + public static some.test.EmbeddedClassSimpleParent copyOrUpdate(Realm realm, EmbeddedClassSimpleParentColumnInfo columnInfo, some.test.EmbeddedClassSimpleParent object, boolean update, Map cache, Set flags) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); + if (otherRealm.threadId != realm.threadId) { + throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); + } + if (otherRealm.getPath().equals(realm.getPath())) { + return object; + } + } + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + RealmObjectProxy cachedRealmObject = cache.get(object); + if (cachedRealmObject != null) { + return (some.test.EmbeddedClassSimpleParent) cachedRealmObject; + } + + some.test.EmbeddedClassSimpleParent realmObject = null; + boolean canUpdate = update; + if (canUpdate) { + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + String value = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (value == null) { + objKey = table.findFirstNull(pkColumnKey); + } else { + objKey = table.findFirstString(pkColumnKey, value); + } + if (objKey == Table.NO_MATCH) { + canUpdate = false; + } else { + try { + objectContext.set(realm, table.getUncheckedRow(objKey), columnInfo, false, Collections. emptyList()); + realmObject = new io.realm.some_test_EmbeddedClassSimpleParentRealmProxy(); + cache.put(object, (RealmObjectProxy) realmObject); + } finally { + objectContext.clear(); + } + } + } + + return (canUpdate) ? update(realm, columnInfo, realmObject, object, cache, flags) : copy(realm, columnInfo, object, update, cache, flags); + } + + public static some.test.EmbeddedClassSimpleParent copy(Realm realm, EmbeddedClassSimpleParentColumnInfo columnInfo, some.test.EmbeddedClassSimpleParent newObject, boolean update, Map cache, Set flags) { + RealmObjectProxy cachedRealmObject = cache.get(newObject); + if (cachedRealmObject != null) { + return (some.test.EmbeddedClassSimpleParent) cachedRealmObject; + } + + some_test_EmbeddedClassSimpleParentRealmProxyInterface unmanagedSource = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) newObject; + + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); + + // Add all non-"object reference" fields + builder.addString(columnInfo.idColKey, unmanagedSource.realmGet$id()); + + // Create the underlying object and cache it before setting any object/objectlist references + // This will allow us to break any circular dependencies by using the object cache. + Row row = builder.createNewObject(); + io.realm.some_test_EmbeddedClassSimpleParentRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); + + // Finally add all fields that reference other Realm Objects, either directly or through a list + some.test.EmbeddedClass childObj = unmanagedSource.realmGet$child(); + if (childObj == null) { + managedCopy.realmSet$child(null); + } else { + some.test.EmbeddedClass cachechild = (some.test.EmbeddedClass) cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechild.toString()"); + } else { + long objKey = ((RealmObjectProxy) managedCopy).realmGet$proxyState().getRow$realm().createEmbeddedObject(columnInfo.childColKey); + Row linkedObjectRow = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); + some.test.EmbeddedClass linkedObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, linkedObjectRow); + cache.put(childObj, (RealmObjectProxy) linkedObject); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, childObj, linkedObject, cache, flags); + } + } + + RealmList childrenUnmanagedList = unmanagedSource.realmGet$children(); + if (childrenUnmanagedList != null) { + RealmList childrenManagedList = managedCopy.realmGet$children(); + childrenManagedList.clear(); + for (int i = 0; i < childrenUnmanagedList.size(); i++) { + some.test.EmbeddedClass childrenUnmanagedItem = childrenUnmanagedList.get(i); + some.test.EmbeddedClass cachechildren = (some.test.EmbeddedClass) cache.get(childrenUnmanagedItem); + if (cachechildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechildren.toString()"); + } else { + long objKey = childrenManagedList.getOsList().createAndAddEmbeddedObject(); + Row linkedObjectRow = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); + some.test.EmbeddedClass linkedObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, linkedObjectRow); + cache.put(childrenUnmanagedItem, (RealmObjectProxy) linkedObject); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, childrenUnmanagedItem, linkedObject, new HashMap(), Collections.EMPTY_SET); + } + } + } + + return managedCopy; + } + + public static long insert(Realm realm, some.test.EmbeddedClassSimpleParent object, Map cache) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); + } + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + String primaryKeyValue = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (primaryKeyValue == null) { + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + } else { + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + } + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + } else { + Table.throwDuplicatePrimaryKeyException(primaryKeyValue); + } + cache.put(object, objKey); + + some.test.EmbeddedClass childObj = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$child(); + if (childObj != null) { + Long cachechild = cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cachechild.toString()); + } else { + cachechild = some_test_EmbeddedClassRealmProxy.insert(realm, table, columnInfo.childColKey, objKey, childObj, cache); + } + } + + RealmList childrenList = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$children(); + if (childrenList != null) { + OsList childrenOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.childrenColKey); + for (some.test.EmbeddedClass childrenItem : childrenList) { + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insert(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } + return objKey; + } + + public static void insert(Realm realm, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + some.test.EmbeddedClassSimpleParent object = null; + while (objects.hasNext()) { + object = (some.test.EmbeddedClassSimpleParent) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); + continue; + } + String primaryKeyValue = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (primaryKeyValue == null) { + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + } else { + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + } + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + } else { + Table.throwDuplicatePrimaryKeyException(primaryKeyValue); + } + cache.put(object, objKey); + + some.test.EmbeddedClass childObj = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$child(); + if (childObj != null) { + Long cachechild = cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cachechild.toString()); + } else { + cachechild = some_test_EmbeddedClassRealmProxy.insert(realm, table, columnInfo.childColKey, objKey, childObj, cache); + } + } + + RealmList childrenList = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$children(); + if (childrenList != null) { + OsList childrenOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.childrenColKey); + for (some.test.EmbeddedClass childrenItem : childrenList) { + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insert(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } + } + } + + public static long insertOrUpdate(Realm realm, some.test.EmbeddedClassSimpleParent object, Map cache) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); + } + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + String primaryKeyValue = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (primaryKeyValue == null) { + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + } else { + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + } + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + } + cache.put(object, objKey); + + some.test.EmbeddedClass childObj = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$child(); + if (childObj != null) { + Long cachechild = cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cachechild.toString()); + } else { + cachechild = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childColKey, objKey, childObj, cache); + } + } else { + Table.nativeNullifyLink(tableNativePtr, columnInfo.childColKey, objKey); + } + + OsList childrenOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.childrenColKey); + RealmList childrenList = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$children(); + if (childrenList != null && childrenList.size() == childrenOsList.size()) { + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + int objects = childrenList.size(); + for (int i = 0; i < objects; i++) { + some.test.EmbeddedClass childrenItem = childrenList.get(i); + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } else { + childrenOsList.removeAll(); + if (childrenList != null) { + for (some.test.EmbeddedClass childrenItem : childrenList) { + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } + } + + return objKey; + } + + public static void insertOrUpdate(Realm realm, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassSimpleParentColumnInfo columnInfo = (EmbeddedClassSimpleParentColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClassSimpleParent.class); + long pkColumnKey = columnInfo.idColKey; + some.test.EmbeddedClassSimpleParent object = null; + while (objects.hasNext()) { + object = (some.test.EmbeddedClassSimpleParent) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); + continue; + } + String primaryKeyValue = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$id(); + long objKey = Table.NO_MATCH; + if (primaryKeyValue == null) { + objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey); + } else { + objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue); + } + if (objKey == Table.NO_MATCH) { + objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue); + } + cache.put(object, objKey); + + some.test.EmbeddedClass childObj = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$child(); + if (childObj != null) { + Long cachechild = cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cachechild.toString()); + } else { + cachechild = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childColKey, objKey, childObj, cache); + } + } else { + Table.nativeNullifyLink(tableNativePtr, columnInfo.childColKey, objKey); + } + + OsList childrenOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.childrenColKey); + RealmList childrenList = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$children(); + if (childrenList != null && childrenList.size() == childrenOsList.size()) { + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + int objectCount = childrenList.size(); + for (int i = 0; i < objectCount; i++) { + some.test.EmbeddedClass childrenItem = childrenList.get(i); + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } else { + childrenOsList.removeAll(); + if (childrenList != null) { + for (some.test.EmbeddedClass childrenItem : childrenList) { + Long cacheItemIndexchildren = cache.get(childrenItem); + if (cacheItemIndexchildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString()); + } else { + cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache); + } + } + } + } + + } + } + + public static some.test.EmbeddedClassSimpleParent createDetachedCopy(some.test.EmbeddedClassSimpleParent realmObject, int currentDepth, int maxDepth, Map> cache) { + if (currentDepth > maxDepth || realmObject == null) { + return null; + } + CacheData cachedObject = cache.get(realmObject); + some.test.EmbeddedClassSimpleParent unmanagedObject; + if (cachedObject == null) { + unmanagedObject = new some.test.EmbeddedClassSimpleParent(); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + } else { + // Reuse cached object or recreate it because it was encountered at a lower depth. + if (currentDepth >= cachedObject.minDepth) { + return (some.test.EmbeddedClassSimpleParent) cachedObject.object; + } + unmanagedObject = (some.test.EmbeddedClassSimpleParent) cachedObject.object; + cachedObject.minDepth = currentDepth; + } + some_test_EmbeddedClassSimpleParentRealmProxyInterface unmanagedCopy = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) unmanagedObject; + some_test_EmbeddedClassSimpleParentRealmProxyInterface realmSource = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) realmObject; + unmanagedCopy.realmSet$id(realmSource.realmGet$id()); + + // Deep copy of child + unmanagedCopy.realmSet$child(some_test_EmbeddedClassRealmProxy.createDetachedCopy(realmSource.realmGet$child(), currentDepth + 1, maxDepth, cache)); + + // Deep copy of children + if (currentDepth == maxDepth) { + unmanagedCopy.realmSet$children(null); + } else { + RealmList managedchildrenList = realmSource.realmGet$children(); + RealmList unmanagedchildrenList = new RealmList(); + unmanagedCopy.realmSet$children(unmanagedchildrenList); + int nextDepth = currentDepth + 1; + int size = managedchildrenList.size(); + for (int i = 0; i < size; i++) { + some.test.EmbeddedClass item = some_test_EmbeddedClassRealmProxy.createDetachedCopy(managedchildrenList.get(i), nextDepth, maxDepth, cache); + unmanagedchildrenList.add(item); + } + } + + return unmanagedObject; + } + + static some.test.EmbeddedClassSimpleParent update(Realm realm, EmbeddedClassSimpleParentColumnInfo columnInfo, some.test.EmbeddedClassSimpleParent realmObject, some.test.EmbeddedClassSimpleParent newObject, Map cache, Set flags) { + some_test_EmbeddedClassSimpleParentRealmProxyInterface realmObjectTarget = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) realmObject; + some_test_EmbeddedClassSimpleParentRealmProxyInterface realmObjectSource = (some_test_EmbeddedClassSimpleParentRealmProxyInterface) newObject; + Table table = realm.getTable(some.test.EmbeddedClassSimpleParent.class); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); + builder.addString(columnInfo.idColKey, realmObjectSource.realmGet$id()); + + some.test.EmbeddedClass childObj = realmObjectSource.realmGet$child(); + if (childObj == null) { + builder.addNull(columnInfo.childColKey); + } else { + // Embedded objects are created directly instead of using the builder. + some.test.EmbeddedClass cachechild = (some.test.EmbeddedClass) cache.get(childObj); + if (cachechild != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechild.toString()"); + } + + long objKey = ((RealmObjectProxy) realmObject).realmGet$proxyState().getRow$realm().createEmbeddedObject(columnInfo.childColKey); + Row row = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); + some.test.EmbeddedClass proxyObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, row); + cache.put(childObj, (RealmObjectProxy) proxyObject); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, childObj, proxyObject, cache, flags); + } + + RealmList childrenUnmanagedList = realmObjectSource.realmGet$children(); + if (childrenUnmanagedList != null) { + RealmList childrenManagedCopy = new RealmList(); + for (int i = 0; i < childrenUnmanagedList.size(); i++) { + some.test.EmbeddedClass childrenUnmanagedItem = childrenUnmanagedList.get(i); + some.test.EmbeddedClass cachechildren = (some.test.EmbeddedClass) cache.get(childrenUnmanagedItem); + if (cachechildren != null) { + throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechildren.toString()"); + } else { + long objKey = realmObjectTarget.realmGet$children().getOsList().createAndAddEmbeddedObject(); + Row row = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); + some.test.EmbeddedClass proxyObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, row); + cache.put(childrenUnmanagedItem, (RealmObjectProxy) proxyObject); + childrenManagedCopy.add(proxyObject); + some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, childrenUnmanagedItem, proxyObject, new HashMap(), Collections.EMPTY_SET); + } + } + builder.addObjectList(columnInfo.childrenColKey, childrenManagedCopy); + } else { + builder.addObjectList(columnInfo.childrenColKey, new RealmList()); + } + + builder.updateExistingTopLevelObject(); + return realmObject; + } + + @Override + @SuppressWarnings("ArrayToString") + public String toString() { + if (!RealmObject.isValid(this)) { + return "Invalid object"; + } + StringBuilder stringBuilder = new StringBuilder("EmbeddedClassSimpleParent = proxy["); + stringBuilder.append("{id:"); + stringBuilder.append(realmGet$id() != null ? realmGet$id() : "null"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{child:"); + stringBuilder.append(realmGet$child() != null ? "EmbeddedClass" : "null"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{children:"); + stringBuilder.append("RealmList[").append(realmGet$children().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append("]"); + return stringBuilder.toString(); + } + + @Override + public ProxyState realmGet$proxyState() { + return proxyState; + } + + @Override + public int hashCode() { + String realmName = proxyState.getRealm$realm().getPath(); + String tableName = proxyState.getRow$realm().getTable().getName(); + long objKey = proxyState.getRow$realm().getObjectKey(); + + int result = 17; + result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); + result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); + result = 31 * result + (int) (objKey ^ (objKey >>> 32)); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + some_test_EmbeddedClassSimpleParentRealmProxy aEmbeddedClassSimpleParent = (some_test_EmbeddedClassSimpleParentRealmProxy)o; + + BaseRealm realm = proxyState.getRealm$realm(); + BaseRealm otherRealm = aEmbeddedClassSimpleParent.proxyState.getRealm$realm(); + String path = realm.getPath(); + String otherPath = otherRealm.getPath(); + if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + if (realm.isFrozen() != otherRealm.isFrozen()) return false; + if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID())) { + return false; + } + + String tableName = proxyState.getRow$realm().getTable().getName(); + String otherTableName = aEmbeddedClassSimpleParent.proxyState.getRow$realm().getTable().getName(); + if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; + + if (proxyState.getRow$realm().getObjectKey() != aEmbeddedClassSimpleParent.proxyState.getRow$realm().getObjectKey()) return false; + + return true; + } +} diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java index b879ae03d8..f7cbbd3b2e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyMixedClassSettingsRealmProxy.java @@ -151,7 +151,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("customName", 2, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("customName", false, 2, 0); builder.addPersistedProperty("first_name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("LastName", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); return builder.build(); @@ -229,7 +229,7 @@ public static some.test.NamePolicyMixedClassSettings createUsingJsonStream(Realm return realm.copyToRealm(obj); } - private static some_test_NamePolicyMixedClassSettingsRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_NamePolicyMixedClassSettingsRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NamePolicyMixedClassSettings.class), false, Collections.emptyList()); @@ -263,22 +263,22 @@ public static some.test.NamePolicyMixedClassSettings copy(Realm realm, NamePolic return (some.test.NamePolicyMixedClassSettings) cachedRealmObject; } - some_test_NamePolicyMixedClassSettingsRealmProxyInterface realmObjectSource = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) newObject; + some_test_NamePolicyMixedClassSettingsRealmProxyInterface unmanagedSource = (some_test_NamePolicyMixedClassSettingsRealmProxyInterface) newObject; Table table = realm.getTable(some.test.NamePolicyMixedClassSettings.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.firstNameColKey, realmObjectSource.realmGet$firstName()); - builder.addString(columnInfo.lastNameColKey, realmObjectSource.realmGet$lastName()); + builder.addString(columnInfo.firstNameColKey, unmanagedSource.realmGet$firstName()); + builder.addString(columnInfo.lastNameColKey, unmanagedSource.realmGet$lastName()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_NamePolicyMixedClassSettingsRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_NamePolicyMixedClassSettingsRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.NamePolicyMixedClassSettings object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java index 83bc876281..aef2c2fd5a 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NamePolicyModuleDefaultsRealmProxy.java @@ -151,7 +151,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NamePolicyModuleDefaults", 2, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NamePolicyModuleDefaults", false, 2, 0); builder.addPersistedProperty("FirstName", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("LastName", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); return builder.build(); @@ -229,7 +229,7 @@ public static some.test.NamePolicyModuleDefaults createUsingJsonStream(Realm rea return realm.copyToRealm(obj); } - private static some_test_NamePolicyModuleDefaultsRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_NamePolicyModuleDefaultsRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NamePolicyModuleDefaults.class), false, Collections.emptyList()); @@ -263,22 +263,22 @@ public static some.test.NamePolicyModuleDefaults copy(Realm realm, NamePolicyMod return (some.test.NamePolicyModuleDefaults) cachedRealmObject; } - some_test_NamePolicyModuleDefaultsRealmProxyInterface realmObjectSource = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) newObject; + some_test_NamePolicyModuleDefaultsRealmProxyInterface unmanagedSource = (some_test_NamePolicyModuleDefaultsRealmProxyInterface) newObject; Table table = realm.getTable(some.test.NamePolicyModuleDefaults.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.firstNameColKey, realmObjectSource.realmGet$firstName()); - builder.addString(columnInfo.lastNameColKey, realmObjectSource.realmGet$lastName()); + builder.addString(columnInfo.firstNameColKey, unmanagedSource.realmGet$firstName()); + builder.addString(columnInfo.lastNameColKey, unmanagedSource.realmGet$lastName()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_NamePolicyModuleDefaultsRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_NamePolicyModuleDefaultsRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.NamePolicyModuleDefaults object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index 20327c69ca..396c74efbe 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -992,6 +992,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @Override public void realmSet$fieldObjectNull(some.test.NullTypes value) { + Realm realm = (Realm) proxyState.getRealm$realm(); if (proxyState.isUnderConstruction()) { if (!proxyState.getAcceptDefaultValue$realm()) { return; @@ -1000,7 +1001,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { return; } if (value != null && !RealmObject.isManaged(value)) { - value = ((Realm) proxyState.getRealm$realm()).copyToRealm(value); + value = realm.copyToRealm(value); } final Row row = proxyState.getRow$realm(); if (value == null) { @@ -1959,7 +1960,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NullTypes", 49, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("NullTypes", false, 49, 0); builder.addPersistedProperty("fieldStringNotNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("fieldStringNull", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("fieldBooleanNotNull", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -2611,7 +2612,7 @@ public static some.test.NullTypes createUsingJsonStream(Realm realm, JsonReader return realm.copyToRealm(obj); } - private static some_test_NullTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_NullTypesRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.NullTypes.class), false, Collections.emptyList()); @@ -2645,81 +2646,81 @@ public static some.test.NullTypes copy(Realm realm, NullTypesColumnInfo columnIn return (some.test.NullTypes) cachedRealmObject; } - some_test_NullTypesRealmProxyInterface realmObjectSource = (some_test_NullTypesRealmProxyInterface) newObject; + some_test_NullTypesRealmProxyInterface unmanagedSource = (some_test_NullTypesRealmProxyInterface) newObject; Table table = realm.getTable(some.test.NullTypes.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.fieldStringNotNullColKey, realmObjectSource.realmGet$fieldStringNotNull()); - builder.addString(columnInfo.fieldStringNullColKey, realmObjectSource.realmGet$fieldStringNull()); - builder.addBoolean(columnInfo.fieldBooleanNotNullColKey, realmObjectSource.realmGet$fieldBooleanNotNull()); - builder.addBoolean(columnInfo.fieldBooleanNullColKey, realmObjectSource.realmGet$fieldBooleanNull()); - builder.addByteArray(columnInfo.fieldBytesNotNullColKey, realmObjectSource.realmGet$fieldBytesNotNull()); - builder.addByteArray(columnInfo.fieldBytesNullColKey, realmObjectSource.realmGet$fieldBytesNull()); - builder.addInteger(columnInfo.fieldByteNotNullColKey, realmObjectSource.realmGet$fieldByteNotNull()); - builder.addInteger(columnInfo.fieldByteNullColKey, realmObjectSource.realmGet$fieldByteNull()); - builder.addInteger(columnInfo.fieldShortNotNullColKey, realmObjectSource.realmGet$fieldShortNotNull()); - builder.addInteger(columnInfo.fieldShortNullColKey, realmObjectSource.realmGet$fieldShortNull()); - builder.addInteger(columnInfo.fieldIntegerNotNullColKey, realmObjectSource.realmGet$fieldIntegerNotNull()); - builder.addInteger(columnInfo.fieldIntegerNullColKey, realmObjectSource.realmGet$fieldIntegerNull()); - builder.addInteger(columnInfo.fieldLongNotNullColKey, realmObjectSource.realmGet$fieldLongNotNull()); - builder.addInteger(columnInfo.fieldLongNullColKey, realmObjectSource.realmGet$fieldLongNull()); - builder.addFloat(columnInfo.fieldFloatNotNullColKey, realmObjectSource.realmGet$fieldFloatNotNull()); - builder.addFloat(columnInfo.fieldFloatNullColKey, realmObjectSource.realmGet$fieldFloatNull()); - builder.addDouble(columnInfo.fieldDoubleNotNullColKey, realmObjectSource.realmGet$fieldDoubleNotNull()); - builder.addDouble(columnInfo.fieldDoubleNullColKey, realmObjectSource.realmGet$fieldDoubleNull()); - builder.addDate(columnInfo.fieldDateNotNullColKey, realmObjectSource.realmGet$fieldDateNotNull()); - builder.addDate(columnInfo.fieldDateNullColKey, realmObjectSource.realmGet$fieldDateNull()); - builder.addDecimal128(columnInfo.fieldDecimal128NotNullColKey, realmObjectSource.realmGet$fieldDecimal128NotNull()); - builder.addDecimal128(columnInfo.fieldDecimal128NullColKey, realmObjectSource.realmGet$fieldDecimal128Null()); - builder.addObjectId(columnInfo.fieldObjectIdNotNullColKey, realmObjectSource.realmGet$fieldObjectIdNotNull()); - builder.addObjectId(columnInfo.fieldObjectIdNullColKey, realmObjectSource.realmGet$fieldObjectIdNull()); - builder.addStringList(columnInfo.fieldStringListNotNullColKey, realmObjectSource.realmGet$fieldStringListNotNull()); - builder.addStringList(columnInfo.fieldStringListNullColKey, realmObjectSource.realmGet$fieldStringListNull()); - builder.addByteArrayList(columnInfo.fieldBinaryListNotNullColKey, realmObjectSource.realmGet$fieldBinaryListNotNull()); - builder.addByteArrayList(columnInfo.fieldBinaryListNullColKey, realmObjectSource.realmGet$fieldBinaryListNull()); - builder.addBooleanList(columnInfo.fieldBooleanListNotNullColKey, realmObjectSource.realmGet$fieldBooleanListNotNull()); - builder.addBooleanList(columnInfo.fieldBooleanListNullColKey, realmObjectSource.realmGet$fieldBooleanListNull()); - builder.addLongList(columnInfo.fieldLongListNotNullColKey, realmObjectSource.realmGet$fieldLongListNotNull()); - builder.addLongList(columnInfo.fieldLongListNullColKey, realmObjectSource.realmGet$fieldLongListNull()); - builder.addIntegerList(columnInfo.fieldIntegerListNotNullColKey, realmObjectSource.realmGet$fieldIntegerListNotNull()); - builder.addIntegerList(columnInfo.fieldIntegerListNullColKey, realmObjectSource.realmGet$fieldIntegerListNull()); - builder.addShortList(columnInfo.fieldShortListNotNullColKey, realmObjectSource.realmGet$fieldShortListNotNull()); - builder.addShortList(columnInfo.fieldShortListNullColKey, realmObjectSource.realmGet$fieldShortListNull()); - builder.addByteList(columnInfo.fieldByteListNotNullColKey, realmObjectSource.realmGet$fieldByteListNotNull()); - builder.addByteList(columnInfo.fieldByteListNullColKey, realmObjectSource.realmGet$fieldByteListNull()); - builder.addDoubleList(columnInfo.fieldDoubleListNotNullColKey, realmObjectSource.realmGet$fieldDoubleListNotNull()); - builder.addDoubleList(columnInfo.fieldDoubleListNullColKey, realmObjectSource.realmGet$fieldDoubleListNull()); - builder.addFloatList(columnInfo.fieldFloatListNotNullColKey, realmObjectSource.realmGet$fieldFloatListNotNull()); - builder.addFloatList(columnInfo.fieldFloatListNullColKey, realmObjectSource.realmGet$fieldFloatListNull()); - builder.addDateList(columnInfo.fieldDateListNotNullColKey, realmObjectSource.realmGet$fieldDateListNotNull()); - builder.addDateList(columnInfo.fieldDateListNullColKey, realmObjectSource.realmGet$fieldDateListNull()); - builder.addDecimal128List(columnInfo.fieldDecimal128ListNotNullColKey, realmObjectSource.realmGet$fieldDecimal128ListNotNull()); - builder.addDecimal128List(columnInfo.fieldDecimal128ListNullColKey, realmObjectSource.realmGet$fieldDecimal128ListNull()); - builder.addObjectIdList(columnInfo.fieldObjectIdListNotNullColKey, realmObjectSource.realmGet$fieldObjectIdListNotNull()); - builder.addObjectIdList(columnInfo.fieldObjectIdListNullColKey, realmObjectSource.realmGet$fieldObjectIdListNull()); + builder.addString(columnInfo.fieldStringNotNullColKey, unmanagedSource.realmGet$fieldStringNotNull()); + builder.addString(columnInfo.fieldStringNullColKey, unmanagedSource.realmGet$fieldStringNull()); + builder.addBoolean(columnInfo.fieldBooleanNotNullColKey, unmanagedSource.realmGet$fieldBooleanNotNull()); + builder.addBoolean(columnInfo.fieldBooleanNullColKey, unmanagedSource.realmGet$fieldBooleanNull()); + builder.addByteArray(columnInfo.fieldBytesNotNullColKey, unmanagedSource.realmGet$fieldBytesNotNull()); + builder.addByteArray(columnInfo.fieldBytesNullColKey, unmanagedSource.realmGet$fieldBytesNull()); + builder.addInteger(columnInfo.fieldByteNotNullColKey, unmanagedSource.realmGet$fieldByteNotNull()); + builder.addInteger(columnInfo.fieldByteNullColKey, unmanagedSource.realmGet$fieldByteNull()); + builder.addInteger(columnInfo.fieldShortNotNullColKey, unmanagedSource.realmGet$fieldShortNotNull()); + builder.addInteger(columnInfo.fieldShortNullColKey, unmanagedSource.realmGet$fieldShortNull()); + builder.addInteger(columnInfo.fieldIntegerNotNullColKey, unmanagedSource.realmGet$fieldIntegerNotNull()); + builder.addInteger(columnInfo.fieldIntegerNullColKey, unmanagedSource.realmGet$fieldIntegerNull()); + builder.addInteger(columnInfo.fieldLongNotNullColKey, unmanagedSource.realmGet$fieldLongNotNull()); + builder.addInteger(columnInfo.fieldLongNullColKey, unmanagedSource.realmGet$fieldLongNull()); + builder.addFloat(columnInfo.fieldFloatNotNullColKey, unmanagedSource.realmGet$fieldFloatNotNull()); + builder.addFloat(columnInfo.fieldFloatNullColKey, unmanagedSource.realmGet$fieldFloatNull()); + builder.addDouble(columnInfo.fieldDoubleNotNullColKey, unmanagedSource.realmGet$fieldDoubleNotNull()); + builder.addDouble(columnInfo.fieldDoubleNullColKey, unmanagedSource.realmGet$fieldDoubleNull()); + builder.addDate(columnInfo.fieldDateNotNullColKey, unmanagedSource.realmGet$fieldDateNotNull()); + builder.addDate(columnInfo.fieldDateNullColKey, unmanagedSource.realmGet$fieldDateNull()); + builder.addDecimal128(columnInfo.fieldDecimal128NotNullColKey, unmanagedSource.realmGet$fieldDecimal128NotNull()); + builder.addDecimal128(columnInfo.fieldDecimal128NullColKey, unmanagedSource.realmGet$fieldDecimal128Null()); + builder.addObjectId(columnInfo.fieldObjectIdNotNullColKey, unmanagedSource.realmGet$fieldObjectIdNotNull()); + builder.addObjectId(columnInfo.fieldObjectIdNullColKey, unmanagedSource.realmGet$fieldObjectIdNull()); + builder.addStringList(columnInfo.fieldStringListNotNullColKey, unmanagedSource.realmGet$fieldStringListNotNull()); + builder.addStringList(columnInfo.fieldStringListNullColKey, unmanagedSource.realmGet$fieldStringListNull()); + builder.addByteArrayList(columnInfo.fieldBinaryListNotNullColKey, unmanagedSource.realmGet$fieldBinaryListNotNull()); + builder.addByteArrayList(columnInfo.fieldBinaryListNullColKey, unmanagedSource.realmGet$fieldBinaryListNull()); + builder.addBooleanList(columnInfo.fieldBooleanListNotNullColKey, unmanagedSource.realmGet$fieldBooleanListNotNull()); + builder.addBooleanList(columnInfo.fieldBooleanListNullColKey, unmanagedSource.realmGet$fieldBooleanListNull()); + builder.addLongList(columnInfo.fieldLongListNotNullColKey, unmanagedSource.realmGet$fieldLongListNotNull()); + builder.addLongList(columnInfo.fieldLongListNullColKey, unmanagedSource.realmGet$fieldLongListNull()); + builder.addIntegerList(columnInfo.fieldIntegerListNotNullColKey, unmanagedSource.realmGet$fieldIntegerListNotNull()); + builder.addIntegerList(columnInfo.fieldIntegerListNullColKey, unmanagedSource.realmGet$fieldIntegerListNull()); + builder.addShortList(columnInfo.fieldShortListNotNullColKey, unmanagedSource.realmGet$fieldShortListNotNull()); + builder.addShortList(columnInfo.fieldShortListNullColKey, unmanagedSource.realmGet$fieldShortListNull()); + builder.addByteList(columnInfo.fieldByteListNotNullColKey, unmanagedSource.realmGet$fieldByteListNotNull()); + builder.addByteList(columnInfo.fieldByteListNullColKey, unmanagedSource.realmGet$fieldByteListNull()); + builder.addDoubleList(columnInfo.fieldDoubleListNotNullColKey, unmanagedSource.realmGet$fieldDoubleListNotNull()); + builder.addDoubleList(columnInfo.fieldDoubleListNullColKey, unmanagedSource.realmGet$fieldDoubleListNull()); + builder.addFloatList(columnInfo.fieldFloatListNotNullColKey, unmanagedSource.realmGet$fieldFloatListNotNull()); + builder.addFloatList(columnInfo.fieldFloatListNullColKey, unmanagedSource.realmGet$fieldFloatListNull()); + builder.addDateList(columnInfo.fieldDateListNotNullColKey, unmanagedSource.realmGet$fieldDateListNotNull()); + builder.addDateList(columnInfo.fieldDateListNullColKey, unmanagedSource.realmGet$fieldDateListNull()); + builder.addDecimal128List(columnInfo.fieldDecimal128ListNotNullColKey, unmanagedSource.realmGet$fieldDecimal128ListNotNull()); + builder.addDecimal128List(columnInfo.fieldDecimal128ListNullColKey, unmanagedSource.realmGet$fieldDecimal128ListNull()); + builder.addObjectIdList(columnInfo.fieldObjectIdListNotNullColKey, unmanagedSource.realmGet$fieldObjectIdListNotNull()); + builder.addObjectIdList(columnInfo.fieldObjectIdListNullColKey, unmanagedSource.realmGet$fieldObjectIdListNull()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_NullTypesRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_NullTypesRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); // Finally add all fields that reference other Realm Objects, either directly or through a list - some.test.NullTypes fieldObjectNullObj = realmObjectSource.realmGet$fieldObjectNull(); + some.test.NullTypes fieldObjectNullObj = unmanagedSource.realmGet$fieldObjectNull(); if (fieldObjectNullObj == null) { - realmObjectCopy.realmSet$fieldObjectNull(null); + managedCopy.realmSet$fieldObjectNull(null); } else { some.test.NullTypes cachefieldObjectNull = (some.test.NullTypes) cache.get(fieldObjectNullObj); if (cachefieldObjectNull != null) { - realmObjectCopy.realmSet$fieldObjectNull(cachefieldObjectNull); + managedCopy.realmSet$fieldObjectNull(cachefieldObjectNull); } else { - realmObjectCopy.realmSet$fieldObjectNull(some_test_NullTypesRealmProxy.copyOrUpdate(realm, (some_test_NullTypesRealmProxy.NullTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.NullTypes.class), fieldObjectNullObj, update, cache, flags)); + managedCopy.realmSet$fieldObjectNull(some_test_NullTypesRealmProxy.copyOrUpdate(realm, (some_test_NullTypesRealmProxy.NullTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.NullTypes.class), fieldObjectNullObj, update, cache, flags)); } } - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.NullTypes object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java index dcec635152..8c5590e1e2 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_SimpleRealmProxy.java @@ -143,7 +143,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Simple", 2, 0); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("Simple", false, 2, 0); builder.addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); return builder.build(); @@ -221,7 +221,7 @@ public static some.test.Simple createUsingJsonStream(Realm realm, JsonReader rea return realm.copyToRealm(obj); } - private static some_test_SimpleRealmProxy newProxyInstance(BaseRealm realm, Row row) { + static some_test_SimpleRealmProxy newProxyInstance(BaseRealm realm, Row row) { // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.Simple.class), false, Collections.emptyList()); @@ -255,22 +255,22 @@ public static some.test.Simple copy(Realm realm, SimpleColumnInfo columnInfo, so return (some.test.Simple) cachedRealmObject; } - some_test_SimpleRealmProxyInterface realmObjectSource = (some_test_SimpleRealmProxyInterface) newObject; + some_test_SimpleRealmProxyInterface unmanagedSource = (some_test_SimpleRealmProxyInterface) newObject; Table table = realm.getTable(some.test.Simple.class); OsObjectBuilder builder = new OsObjectBuilder(table, flags); // Add all non-"object reference" fields - builder.addString(columnInfo.nameColKey, realmObjectSource.realmGet$name()); - builder.addInteger(columnInfo.ageColKey, realmObjectSource.realmGet$age()); + builder.addString(columnInfo.nameColKey, unmanagedSource.realmGet$name()); + builder.addInteger(columnInfo.ageColKey, unmanagedSource.realmGet$age()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. Row row = builder.createNewObject(); - io.realm.some_test_SimpleRealmProxy realmObjectCopy = newProxyInstance(realm, row); - cache.put(newObject, realmObjectCopy); + io.realm.some_test_SimpleRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); - return realmObjectCopy; + return managedCopy; } public static long insert(Realm realm, some.test.Simple object, Map cache) { diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClass.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClass.java new file mode 100644 index 0000000000..d3bd25f2dd --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClass.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.RealmClass; + +@RealmClass(embedded = true) +public class EmbeddedClass extends RealmObject { + public String name; + public int age; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFieldDescription.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFieldDescription.java new file mode 100644 index 0000000000..2e4895ef6f --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFieldDescription.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassMissingFieldDescription extends RealmObject { + public String name; + public int age; + + @LinkingObjects + public final EmbeddedClassParent parent1 = new EmbeddedClassParent(); +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFinalOnLinkingObjects.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFinalOnLinkingObjects.java new file mode 100644 index 0000000000..f870670852 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMissingFinalOnLinkingObjects.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassMissingFinalOnLinkingObjects extends RealmObject { + public String name; + public int age; + + @LinkingObjects("child5") + public EmbeddedClassParent parent; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMultipleRequiredParents.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMultipleRequiredParents.java new file mode 100644 index 0000000000..e72094ef7f --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassMultipleRequiredParents.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassMultipleRequiredParents extends RealmObject { + public String name; + public int age; + + // If multiple @LinkingObjects are defined + // the @Required annotation is not allowed. + @Required + @LinkingObjects("child6") + public final EmbeddedClassParent parent1 = new EmbeddedClassParent(); + + @Required + @LinkingObjects("child7") + public final EmbeddedClassParent parent2 = new EmbeddedClassParent(); +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassOptionalParents.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassOptionalParents.java new file mode 100644 index 0000000000..75eb54c600 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassOptionalParents.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassOptionalParents extends RealmObject { + public String name; + public int age; + + // If multiple @LinkingObjects are defined + // They are not treated as @Required. + // This mostly impact Kotlin model classes + @LinkingObjects("child3") + public final EmbeddedClassParent parent1 = new EmbeddedClassParent(); // Field must be final, because parent cannot change once set + + @LinkingObjects("child4") + public final EmbeddedClassParent parent2 = new EmbeddedClassParent(); // Field must be final, because parent cannot change once set +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassParent.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassParent.java new file mode 100644 index 0000000000..02b1ae2cc7 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassParent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; + +// This class is only for creating the correct type hiearchy when testing Embedded Objects +// This class can work as a parent for all legal embedded object classes +public class EmbeddedClassParent extends RealmObject { + public String name; + public int age; + + // Valid single children references + public EmbeddedClass child1; + public EmbeddedClassRequiredParent child2; + public EmbeddedClassOptionalParents child3; + public EmbeddedClassOptionalParents child4; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassPrimaryKey.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassPrimaryKey.java new file mode 100644 index 0000000000..174c2e8c21 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassPrimaryKey.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; + +@RealmClass(embedded = true) +public class EmbeddedClassPrimaryKey extends RealmObject { + @PrimaryKey // This is not allowed in embedded classes + public String name; + public int age; +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassRequiredParent.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassRequiredParent.java new file mode 100644 index 0000000000..db7804f876 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassRequiredParent.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.RealmClass; +import io.realm.annotations.Required; + +@RealmClass(embedded = true) +public class EmbeddedClassRequiredParent extends RealmObject { + public String name; + public int age; + + @Required // Optional, is implied if only a single @LinkingObjects parent is defined + @LinkingObjects("child2") + public final EmbeddedClassParent parent = new EmbeddedClassParent(); // Field must be final, because parent cannot change once set +} diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassSimpleParent.java b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassSimpleParent.java new file mode 100644 index 0000000000..1cc405105c --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/some/test/EmbeddedClassSimpleParent.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package some.test; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.LinkingObjects; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.RealmClass; + +// Simple parent of embedded objects. Used to verify the output of the annotation processor. +public class EmbeddedClassSimpleParent extends RealmObject { + @PrimaryKey + public String id; + public EmbeddedClass child; + public RealmList children; + +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java index 13ed88e978..4174c14099 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsManagedTests.java @@ -604,11 +604,11 @@ public void migration_backlinkedSourceFieldDoesntExist() throws ClassNotFoundExc // Mock the schema info so the only difference compared with the original schema is that the LinkingObject field // points to BacklinksSource.childNotExist. - OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", 1, 1) + OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", false, 1, 1) .addPersistedProperty("id", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED) .addComputedLinkProperty("parents", "BacklinksSource", "childNotExist" /*"child" is the original value*/) .build(); - OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", 2, 0) + OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", false, 2, 0) .addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED) .addPersistedLinkProperty("child", RealmFieldType.OBJECT, "BacklinksTarget") .build(); @@ -647,11 +647,11 @@ public void migration_backlinkedSourceFieldWrongType() { // Mock the schema info so the only difference compared with the original schema is that BacklinksSource.child // type is changed to BacklinksSource from BacklinksTarget. - OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", 1, 1) + OsObjectSchemaInfo targetSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksTarget", false, 1, 1) .addPersistedProperty("id", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED) .addComputedLinkProperty("parents", "BacklinksSource", "child") .build(); - OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", 2, 0) + OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", false, 2, 0) .addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED) .addPersistedLinkProperty("child", RealmFieldType.OBJECT, "BacklinksSource"/*"BacklinksTarget" is the original value*/) diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java index 2b79b9d1b2..5f28f7e53a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/OsListTests.java @@ -51,7 +51,7 @@ public class OsListTests { @Before public void setUp() { - OsObjectSchemaInfo objectSchemaInfo = new OsObjectSchemaInfo.Builder("TestModel",14, 0) + OsObjectSchemaInfo objectSchemaInfo = new OsObjectSchemaInfo.Builder("TestModel", false,14, 0) .addPersistedValueListProperty("longList", RealmFieldType.INTEGER_LIST, !Property.REQUIRED) .addPersistedValueListProperty("doubleList", RealmFieldType.DOUBLE_LIST, !Property.REQUIRED) .addPersistedValueListProperty("floatList", RealmFieldType.FLOAT_LIST, !Property.REQUIRED) diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt index c6b3094613..b47a5d8d6b 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt new file mode 100644 index 0000000000..a6be56f3c9 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -0,0 +1,592 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.entities.* +import io.realm.entities.embedded.* +import io.realm.kotlin.addChangeListener +import io.realm.kotlin.createEmbeddedObject +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import io.realm.rule.BlockingLooperThread +import io.realm.rule.TestRealmConfigurationFactory +import org.junit.* +import org.junit.Assert.* +import org.junit.runner.RunWith +import java.util.* +import kotlin.test.assertFailsWith + +/** + * Class testing the Embedded Objects feature. + */ +// FIXME: Move all of these tests out from here. We try to tests by Class, not Feature. +@RunWith(AndroidJUnit4::class) +class EmbeddedObjectsTest { + + @get:Rule + val configFactory = TestRealmConfigurationFactory() + + private val looperThread = BlockingLooperThread() + + private lateinit var realmConfig: RealmConfiguration + private lateinit var realm: Realm + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + realmConfig = configFactory.createConfiguration() + realm = Realm.getInstance(realmConfig) + } + + @After + fun tearDown() { + if (this::realm.isInitialized) { + realm.close() + } + } + + @Test + fun createObject_throwsForEmbeddedClasses() = realm.executeTransaction { realm -> + assertFailsWith { realm.createObject() } + } + + @Test + fun createObjectWithPrimaryKey_throwsForEmbeddedClasses() = realm.executeTransaction { realm -> + assertFailsWith { realm.createObject("foo") } + } + + @Test + fun createEmbeddedObject_nullArgsThrows() = realm.executeTransaction { realm -> + assertFailsWith { realm.createEmbeddedObject(EmbeddedSimpleChild::class.java, TestHelper.getNull(), "foo") } + val parent = realm.createObject("parent") + assertFailsWith { realm.createEmbeddedObject(EmbeddedSimpleChild::class.java, parent, TestHelper.getNull()) } + } + + @Test + fun createEmbeddedObject_nonExistingParentPropertyNameThrows() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + assertFailsWith { realm.createEmbeddedObject(parent, "foo") } + } + + @Test + fun createEmbeddedObject_wrongParentPropertyTypeThrows() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + + // TODO: Smoke-test for wrong type. Figure out how to test all unsupported types. + assertFailsWith { realm.createEmbeddedObject(parent, "id") } + } + + @Test + @Ignore("FIXME") + fun createEmbeddedObject_wrongParentPropertyObjectTypeThrows() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + + assertFailsWith { + // Embedded object is not of the type the parent object links to. + realm.createEmbeddedObject(parent, "child") + } + } + + @Test + @Ignore("FIXME") + fun createEmbeddedObject_wrongParentPropertyListTypeThrows() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + + assertFailsWith { + // Embedded object is not of the type the parent object links to. + realm.createEmbeddedObject(parent, "children") + } + } + + @Test + fun createEmbeddedObject_simpleSingleChild() = realm.executeTransaction { realm -> + val parent = realm.createObject("parent") + val child = realm.createEmbeddedObject(parent, "child"); + assertEquals(child.parent, parent) + } + + @Test + fun createEmbeddedObject_simpleChildList() = realm.executeTransaction { realm -> + // Using createEmbeddedObject() with a parent list, will append the object to the end + // of the list + val parent = realm.createObject(UUID.randomUUID().toString()) + val child1 = realm.createEmbeddedObject(parent, "children") + val child2 = realm.createEmbeddedObject(parent, "children") + assertEquals(2, parent.children.size.toLong()) + assertEquals(child1, parent.children.first()!!) + assertEquals(child2, parent.children.last()!!) + } + + @Test + @Ignore("Placeholder for all tests for DynamicRealm.createEmbeddedObject()") + fun dynamicRealm_createEmbeddedObject() { + TODO() + } + + @Test + fun settingParentFieldDeletesChild() = realm.executeTransaction { realm -> + val parent = EmbeddedSimpleParent("parent") + parent.child = EmbeddedSimpleChild("child") + + val managedParent: EmbeddedSimpleParent = realm.copyToRealm(parent) + val managedChild: EmbeddedSimpleChild = managedParent.child!! + managedParent.child = null // Will delete the embedded object + assertFalse(managedChild.isValid) + assertEquals(0, realm.where().count()) + } + + @Test + fun objectAccessor_willAutomaticallyCopyUnmanaged() = realm.executeTransaction { realm -> + // Checks that adding an unmanaged embedded object to a property will automatically copy it. + val parent = EmbeddedSimpleParent("parent") + val managedParent: EmbeddedSimpleParent = realm.copyToRealm(parent) + + assertEquals(0, realm.where().count()) + managedParent.child = EmbeddedSimpleChild("child") // Will copy the object to Realm + assertEquals(1, realm.where().count()) + assertTrue(managedParent.child!!.isValid) + } + + @Test + fun objectAccessor_willAutomaticallyCopyManaged() = realm.executeTransaction { realm -> + // Checks that setting a link to a managed embedded object will automatically copy it unlike + // normal objects that allow multiple parents. Note: This behavior is a bit controversial + // and was subject to a lot of discussion during API design. The problem is that making + // the behavior explicit will result in an extremely annoying API. We need to carefully + // monitor if people understand how this behaves. + val managedParent1: EmbeddedSimpleParent = realm.copyToRealm(EmbeddedSimpleParent("parent1")) + val managedParent2: EmbeddedSimpleParent = realm.copyToRealm(EmbeddedSimpleParent("parent2")) + + assertEquals(0, realm.where().count()) + managedParent1.child = EmbeddedSimpleChild("child") + assertEquals(1, realm.where().count()) + managedParent2.child = managedParent1.child // Will copy the embedded object + assertEquals(2, realm.where().count()) + assertNotEquals(managedParent1.child, managedParent2.child) + } + + @Test + fun objectAccessor_willCopyUnderConstruction() = realm.executeTransaction { realm -> + val unmanagedObj = EmbeddedWithConstructorArgs() + val managedObj = realm.copyToRealm(unmanagedObj) + assertEquals(EmbeddedWithConstructorArgs.INNER_CHILD_ID, managedObj.child!!.id) + } + + @Test + fun realmList_add_willAutomaticallyCopy() = realm.executeTransaction { realm -> + val parent = realm.copyToRealm(EmbeddedSimpleListParent("parent")) + assertTrue(parent.children.add(EmbeddedSimpleChild("child"))) + val child = parent.children.first()!! + assertTrue(child.isValid) + assertEquals("child", child.id) + + // FIXME: How to handle DynamicRealmObject :( + } + + @Test + fun realmList_addIndex_willAutomaticallyCopy() = realm.executeTransaction { realm -> + val parent = realm.copyToRealm(EmbeddedSimpleListParent("parent")) + parent.children.add(EmbeddedSimpleChild("secondChild")) + parent.children.add(0, EmbeddedSimpleChild("firstChild")) + val child = parent.children.first()!! + assertTrue(child.isValid) + assertEquals("firstChild", child.id) + + // FIXME: How to handle DynamicRealmObject :( + } + + @Test + fun realmList_set_willAutomaticallyCopy() = realm.executeTransaction { realm -> + // Checks that adding an unmanaged embedded object to a list will automatically make + // it managed + val parent = realm.copyToRealm(EmbeddedSimpleListParent("parent")) + assertTrue(parent.children.add(EmbeddedSimpleChild("child"))) + assertEquals(1, realm.where().count()) + parent.children[0] = EmbeddedSimpleChild("OtherChild") + assertEquals("OtherChild", parent.children.first()!!.id) + assertEquals(1, realm.where().count()) + + // FIXME: How to handle DynamicRealmObject :( + } + + @Test + fun copyToRealm_noParentThrows() = realm.executeTransaction { + assertFailsWith { + realm.copyToRealm(EmbeddedSimpleChild("child")) + } + } + + @Test + fun copyToRealmOrUpdate_NoParentThrows() = realm.executeTransaction { + assertFailsWith { + realm.copyToRealmOrUpdate(EmbeddedSimpleChild("child")) + } + } + + @Test + fun copyToRealm_simpleSingleChild() { + realm.executeTransaction { + val parent = EmbeddedSimpleParent("parent1") + parent.child = EmbeddedSimpleChild("child1") + it.copyToRealm(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + @Test + fun copyToRealm_simpleChildList() { + realm.executeTransaction { + val parent = EmbeddedSimpleListParent("parent1") + parent.children = RealmList(EmbeddedSimpleChild("child1")) + it.copyToRealm(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + @Test + fun copyToRealm_treeSchema() { + realm.executeTransaction { + val parent = EmbeddedTreeParent("parent1") + + val node1 = EmbeddedTreeNode("node1") + node1.leafNode = EmbeddedTreeLeaf("leaf1") + parent.middleNode = node1 + val node2 = EmbeddedTreeNode("node2") + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf2")) + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf3")) + parent.middleNodeList.add(node2) + + it.copyToRealm(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + assertEquals(3, realm.where().count()) + } + + @Test + fun copyToRealm_circularSchema() { + realm.executeTransaction { + val parent = EmbeddedCircularParent("parent") + val child1 = EmbeddedCircularChild("child1") + val child2 = EmbeddedCircularChild("child2") + child1.singleChild = child2 + parent.singleChild = child1 + it.copyToRealm(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + } + + @Test + fun copyToRealm_throwsIfMultipleRefsToSingleObjectsExists() { + realm.executeTransaction { r -> + val parent = EmbeddedCircularParent("parent") + val child = EmbeddedCircularChild("child") + child.singleChild = child // Create circle between children + parent.singleChild = child + assertFailsWith { r.copyToRealm(parent) } + } + } + + @Test + fun copyToRealm_throwsIfMultipleRefsToListObjectsExists() { + realm.executeTransaction { r -> + val parent = EmbeddedSimpleListParent("parent") + val child = EmbeddedSimpleChild("child") + parent.children = RealmList(child, child) + assertFailsWith { r.copyToRealm(parent) } + } + } + + @Test + @Ignore("FIXME") + fun copyToRealmOrUpdate_deleteReplacedObjects() { + TODO() + + } + + @Test + @Ignore("Add in another PR") + fun insert_noParentThrows() { + TODO() + } + + @Test + @Ignore("Add in another PR") + fun insertOrUpdate_throws() { + TODO() + } + + @Test + fun insert_simpleSingleChild() { + realm.executeTransaction { + val parent = EmbeddedSimpleParent("parent1") + parent.child = EmbeddedSimpleChild("child1") + it.insert(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + @Test + fun insert_simpleChildList() { + realm.executeTransaction { + val parent = EmbeddedSimpleListParent("parent1") + parent.children = RealmList(EmbeddedSimpleChild("child1")) + it.insert(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + @Test + fun insert_treeSchema() { + realm.executeTransaction { + val parent = EmbeddedTreeParent("parent1") + + val node1 = EmbeddedTreeNode("node1") + node1.leafNode = EmbeddedTreeLeaf("leaf1") + parent.middleNode = node1 + val node2 = EmbeddedTreeNode("node2") + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf2")) + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf3")) + parent.middleNodeList.add(node2) + + it.insert(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + assertEquals(3, realm.where().count()) + } + + @Test + fun insert_circularSchema() { + realm.executeTransaction { + val parent = EmbeddedCircularParent("parent") + val child1 = EmbeddedCircularChild("child1") + val child2 = EmbeddedCircularChild("child2") + child1.singleChild = child2 + parent.singleChild = child1 + it.insert(parent) + } + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + } + + @Test + @Ignore("Add in another PR") + fun insertOrUpdate_deletesOldEmbeddedObject() { + TODO() + } + + @Test + @Ignore("Add in another PR") + fun insert_listWithEmbeddedObjects() { + TODO() + } + + @Test + @Ignore("Add in another PR") + fun insertOrUpdate_listWithEmbeddedObjects() { + TODO() + } + + @Test + @Ignore("Add in another PR") + fun createObjectFromJson() { + TODO("Placeholder for all tests regarding importing from JSON") + } + + @Test + @Ignore("Add in another PR") + fun dynamicRealmObject_createEmbeddedObject() { + TODO("Consider which kind of support there should be for embedded objets in DynamicRealm") + } + + + @Test + fun realmObjectSchema_setEmbedded() { + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val objSchema: RealmObjectSchema = realm.schema[EmbeddedSimpleChild.NAME]!! + assertTrue(objSchema.isEmbedded) + objSchema.isEmbedded = false + assertFalse(objSchema.isEmbedded) + objSchema.isEmbedded = true + assertTrue(objSchema.isEmbedded) + } + } + } + + @Test + fun realmObjectSchema_setEmbedded_throwsWithPrimaryKey() { + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val objSchema: RealmObjectSchema = realm.schema[AllJavaTypes.CLASS_NAME]!! + assertFailsWith { objSchema.isEmbedded = true } + } + } + } + + @Test + fun realmObjectSchema_setEmbedded_throwsIfBreaksParentInvariants() { + // Classes can only be converted to be embedded if all objects have exactly one other + // object pointing to it. + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + + // Create object with no parents + realm.createObject(Dog.CLASS_NAME) + val dogSchema = realm.schema[Dog.CLASS_NAME]!! + // Succeed by mistake right now. + // See https://github.com/realm/realm-core/issues/3729 + // The correct check is just below + dogSchema.isEmbedded = true + // assertFailsWith { + // dogSchema.isEmbedded = true + // } + + // Create object with two parents + val cat: DynamicRealmObject = realm.createObject(Cat.CLASS_NAME) + val owner1: DynamicRealmObject = realm.createObject(Owner.CLASS_NAME) + owner1.setObject(Owner.FIELD_CAT, cat) + val owner2: DynamicRealmObject = realm.createObject(Owner.CLASS_NAME) + owner2.setObject(Owner.FIELD_CAT, cat) + val catSchema = realm.schema[Cat.CLASS_NAME]!! + assertFailsWith { + catSchema.isEmbedded = true + } + } + } + } + + @Test + fun realmObjectSchema_isEmbedded() { + assertTrue(realm.schema[EmbeddedSimpleChild.NAME]!!.isEmbedded) + assertFalse(realm.schema[AllTypes.CLASS_NAME]!!.isEmbedded) + } + + // Check that deleting a non-embedded parent deletes all embedded children + @Test + fun deleteParentObject_deletesEmbeddedChildren() = realm.executeTransaction { + val parent = EmbeddedSimpleParent("parent") + parent.child = EmbeddedSimpleChild("child") + + val managedParent: EmbeddedSimpleParent = it.copyToRealm(parent) + assertEquals(1, realm.where().count()) + val managedChild: EmbeddedSimpleChild = managedParent.child!! + + managedParent.deleteFromRealm() + assertFalse(managedChild.isValid) + assertEquals(0, realm.where().count()) + assertEquals(0, realm.where().count()) + } + + // Check that deleting a embedded parent deletes all embedded children + @Test + fun deleteParentEmbeddedObject_deletesEmbeddedChildren() = realm.executeTransaction { + val parent = EmbeddedTreeParent("parent1") + val middleNode = EmbeddedTreeNode("node1") + middleNode.leafNode = EmbeddedTreeLeaf("leaf1") + middleNode.leafNodeList.add(EmbeddedTreeLeaf("leaf2")) + middleNode.leafNodeList.add(EmbeddedTreeLeaf("leaf3")) + parent.middleNode = middleNode + + val managedParent: EmbeddedTreeParent = it.copyToRealm(parent) + assertEquals(1, realm.where().count()) + assertEquals(3, realm.where().count()) + managedParent.deleteFromRealm() + assertEquals(0, realm.where().count()) + assertEquals(0, realm.where().count()) + } + + // Cascade deleting an embedded object will trigger its object listener. + @Test + fun deleteParent_triggerChildObjectNotifications() = looperThread.runBlocking { + val realm = Realm.getInstance(realm.configuration) + looperThread.closeAfterTest(realm) + + realm.executeTransaction { + val parent = EmbeddedSimpleParent("parent") + val child = EmbeddedSimpleChild("child") + parent.child = child + it.copyToRealm(parent) + } + + val child = realm.where().findFirst()!!.child!! + child.addChangeListener(RealmChangeListener { + if (!it.isValid) { + looperThread.testComplete() + } + }) + + realm.executeTransaction { + child.parent!!.deleteFromRealm() + } + } + + // Cascade deleting a parent will trigger the listener on any lists in child embedded + // objects + @Test + fun deleteParent_triggerChildListObjectNotifications() = looperThread.runBlocking { + val realm = Realm.getInstance(realm.configuration) + looperThread.closeAfterTest(realm) + + realm.executeTransaction { + val parent = EmbeddedSimpleListParent("parent") + val child1 = EmbeddedSimpleChild("child1") + val child2 = EmbeddedSimpleChild("child2") + parent.children.add(child1) + parent.children.add(child2) + it.copyToRealm(parent) + } + + val children: RealmList = realm.where() + .findFirst()!! + .children + + children.addChangeListener { list -> + if (!list.isValid) { + looperThread.testComplete() + } + } + + realm.executeTransaction { + realm.where().findFirst()!!.deleteFromRealm() + } + } + + + @Test + @Ignore("Add in another PR") + fun results_bulkUpdate() { + // What happens if you bulk update a RealmResults. Should it be allowed to use embeded + // objects here? + TODO() + } +} \ No newline at end of file diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt index f1e755a5e1..7162bbb1f2 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt new file mode 100644 index 0000000000..f706aeaab6 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.RealmClass +import java.util.* + +/** + * Embedded object that point to itself. Note, this is only allowed in the schema. The actual + * objects are not allowed to have circular references. + */ +@RealmClass(embedded = true) +open class EmbeddedCircularChild(var id: String = UUID.randomUUID().toString()) : RealmObject() { + var singleChild: EmbeddedCircularChild? = null +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt new file mode 100644 index 0000000000..8209ca9436 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Parent pointing to an embedded object that has a circular schema, i.e. objects can point +// to themselves. Note, this isn't actually allowed at runtime. Only at schema validation time. +open class EmbeddedCircularParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { + var singleChild: EmbeddedCircularChild? = null +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt new file mode 100644 index 0000000000..345cbbf908 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.LinkingObjects +import io.realm.annotations.RealmClass +import java.util.* + +/** + * The embedded object part of a simple object graph. This object can have two parents + * [EmbeddedSimpleParent] and [EmbeddedSimpleListParent]. + */ +@RealmClass(embedded = true) +open class EmbeddedSimpleChild(var id: String = UUID.randomUUID().toString()) : RealmObject() { + + @LinkingObjects("child") + val parent = EmbeddedSimpleParent() + + companion object { + const val NAME = "EmbeddedSimpleChild" + } + +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt new file mode 100644 index 0000000000..507e7f1f22 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Top-level object describing a simple embedded objects structure consisting of only a +// list of embedded objects. +open class EmbeddedSimpleListParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { + var children: RealmList = RealmList() +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt new file mode 100644 index 0000000000..1f7f07c15d --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Top-level object describing a simple embedded objects structure consisting of only an object reference. +open class EmbeddedSimpleParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { + var child: EmbeddedSimpleChild? = null +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt new file mode 100644 index 0000000000..6efb4022b3 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.LinkingObjects +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmClass +import java.util.* + +// Middle-level node in a object-graph that is three-shaped, i.e. no circular references. +// The tree depth can be described as: +// - 1 TreeParent +// - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. +// - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. +@RealmClass(embedded = true) +open class EmbeddedTreeLeaf(var id: String = UUID.randomUUID().toString()) : RealmObject() { + + @LinkingObjects("leafNode") + val parentRef: EmbeddedTreeNode? = null + + @LinkingObjects("leafNodeList") + val parentListRef: EmbeddedTreeNode? = null +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt new file mode 100644 index 0000000000..bd70f05c7e --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmClass +import java.util.* + +// Middle-level node in a object-graph that is three-shaped, i.e. no circular references. +// The tree depth can be described as: +// - 1 TreeParent +// - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. +// - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. +@RealmClass(embedded = true) +open class EmbeddedTreeNode(var id: String = UUID.randomUUID().toString()) : RealmObject() { + var middleNode: EmbeddedTreeNode? = null + var leafNode: EmbeddedTreeLeaf? = null + var middleNodeList: RealmList = RealmList() + var leafNodeList: RealmList = RealmList() +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt new file mode 100644 index 0000000000..4f2b035dbf --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Top-level node in a object-graph that is three-shaped, i.e. no circular references. +// The tree depth can be described as: +// - 1 TreeParent +// - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. +// - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. +open class EmbeddedTreeParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { + var middleNode: EmbeddedTreeNode? = null + var middleNodeList: RealmList = RealmList() +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedWithConstructorArgs.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedWithConstructorArgs.kt new file mode 100644 index 0000000000..7ce1624ba7 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedWithConstructorArgs.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.Ignore + +open class EmbeddedWithConstructorArgs : RealmObject() { + var child: EmbeddedSimpleChild? = null + init { + child = EmbeddedSimpleChild(INNER_CHILD_ID) + } + + companion object { + const val INNER_CHILD_ID = "innerChild" + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/rule/BlockingLooperThread.kt similarity index 100% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/rule/BlockingLooperThread.kt rename to realm/realm-library/src/androidTest/kotlin/io/realm/rule/BlockingLooperThread.kt diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt index 276744468b..2de7baf5de 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt @@ -126,7 +126,7 @@ class SyncedRealmMigrationTests { .build() // Setup initial Realm schema (with a different primary key) - val expectedObjectSchema = OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME, 2, 0) + val expectedObjectSchema = OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME, false,2, 0) .addPersistedProperty(PrimaryKeyAsString.FIELD_PRIMARY_KEY, RealmFieldType.STRING, false, true, false) .addPersistedProperty(PrimaryKeyAsString.FIELD_ID, RealmFieldType.INTEGER, true, true, true) .build() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp index c5f6544108..f2023f6563 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp @@ -22,6 +22,7 @@ #include "observable_collection_wrapper.hpp" #include "java_accessor.hpp" +#include "java_object_accessor.hpp" #include "java_exception_def.hpp" #include "jni_util/java_exception_thrower.hpp" #include "util.hpp" @@ -550,6 +551,43 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_OsList_nativeGetValue(JNIEnv* e return nullptr; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeCreateAndAddEmbeddedObject(JNIEnv* env, jclass, jlong native_list_ptr, jlong j_index) +{ + try { + List& list = reinterpret_cast(native_list_ptr)->collection(); + auto& realm = list.get_realm(); + auto& object_schema = list.get_object_schema(); + JavaContext ctx(env, realm, object_schema); + // Create dummy object. Properties must be added later. + // TODO CreatePolicy::Skip is a hack right after the object is inserted and before Schemas + // are validated. Figure out a better approach. + auto array_index = static_cast(j_index); + list.insert(ctx, array_index, JavaValue(std::map()), CreatePolicy::Skip); + return reinterpret_cast(list.get(array_index).get_key().value); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeCreateAndSetEmbeddedObject(JNIEnv* env, jclass, jlong native_list_ptr, jlong j_index) +{ + try { + List& list = reinterpret_cast(native_list_ptr)->collection(); + auto& realm = list.get_realm(); + auto& object_schema = list.get_object_schema(); + JavaContext ctx(env, realm, object_schema); + size_t array_index = static_cast(j_index); + // Create dummy object. Properties must be added later. + // TODO CreatePolicy::Skip is a hack right after the object is inserted and before Schemas + // are validated. Figure out a better approach. + list.set(ctx, array_index, JavaValue(std::map()), CreatePolicy::Skip); + return reinterpret_cast(list.get(list.size() - 1).get_key().value); + } + CATCH_STD() + return reinterpret_cast(nullptr); +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsList_nativeFreeze(JNIEnv* env, jclass, jlong native_list_ptr, jlong frozen_realm_native_ptr) { try { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 5bfcdf1439..7e3305fcbb 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -389,3 +389,24 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWit CATCH_STD() return 0; } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateEmbeddedObject( + JNIEnv* env, jclass, jlong j_parent_table_ptr, jlong j_parent_object_key, jlong j_parent_column_key) +{ + try { + TableRef table = TBL_REF(j_parent_table_ptr); + ObjKey obj_key(static_cast(j_parent_object_key)); + Obj parent_obj = table->get_object(obj_key); + ColKey col_key(static_cast(j_parent_column_key)); + Obj child_obj; + if (table->get_column_type(col_key) == type_Link) { + child_obj = parent_obj.create_and_set_linked_object(col_key); + } else { + LnkLstPtr list = parent_obj.get_linklist_ptr(col_key); + child_obj = list->create_and_insert_linked_object(list->size()); + } + return to_jlong_or_not_found(child_obj.get_key()); + } + CATCH_STD() + return 0; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp index 8e7841e674..5835ced274 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp @@ -36,12 +36,14 @@ static void finalize_object_schema(jlong ptr) } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeCreateRealmObjectSchema(JNIEnv* env, jclass, - jstring j_name_str) + jstring j_name_str, + jboolean j_embedded) { try { JStringAccessor name(env, j_name_str); ObjectSchema* object_schema = new ObjectSchema(); object_schema->name = name; + object_schema->is_embedded = to_bool(j_embedded); return reinterpret_cast(object_schema); } CATCH_STD() @@ -129,3 +131,13 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeGetPrima CATCH_STD() return reinterpret_cast(nullptr); } + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsObjectSchemaInfo_nativeIsEmbedded(JNIEnv* env, jclass, jlong native_ptr) +{ + try { + auto& object_schema = *reinterpret_cast(native_ptr); + return to_jbool(object_schema.is_embedded); + } + CATCH_STD() + return to_jbool(false); +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 60f3231742..ad93293322 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -939,3 +939,23 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFreeze(JNIEnv*, jclas TableRef* frozen_table = new TableRef(shared_realm->import_copy_of(table)); return reinterpret_cast(frozen_table); } + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsEmbedded(JNIEnv* env, jclass, jlong j_table_ptr) +{ + try { + TableRef table = TableRef(TBL_REF(j_table_ptr)); + return to_jbool(table->is_embedded()); + } + CATCH_STD() + return false; +} + +JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeSetEmbedded(JNIEnv* env, jclass, jlong j_table_ptr, jboolean j_embedded) +{ + try { + TableRef table = TableRef(TBL_REF(j_table_ptr)); + return to_jbool(table->set_embedded(to_bool(j_embedded))); + } + CATCH_STD() + return false; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index fb00218b93..6506ca5216 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -476,3 +476,18 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetObjectId(JNI } CATCH_STD() } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeCreateEmbeddedObject(JNIEnv* env, jobject, + jlong j_obj_ptr, + jlong j_column_key) +{ + if (!ROW_VALID(env, OBJ(j_obj_ptr))) { + return -1; + } + try { + Obj embedded_object = OBJ(j_obj_ptr)->create_and_set_linked_object(ColKey(j_column_key)); + return reinterpret_cast(embedded_object.get_key().value); + } + CATCH_STD() + return -1; +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp index 2427b6c3f3..57bd430a55 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp @@ -176,8 +176,13 @@ static inline const ObjectSchema& get_schema(const Schema& schema, TableRef tabl return *it; } -JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeCreateOrUpdate - (JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong builder_ptr, jboolean update_existing, jboolean ignore_same_values) +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeCreateOrUpdateTopLevelObject(JNIEnv* env, + jclass, + jlong shared_realm_ptr, + jlong table_ref_ptr, + jlong builder_ptr, + jboolean update_existing, + jboolean ignore_same_values) { try { SharedRealm shared_realm = *(reinterpret_cast(shared_realm_ptr)); @@ -202,6 +207,31 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativ return realm::npos; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeUpdateEmbeddedObject(JNIEnv* env, + jclass, + jlong shared_realm_ptr, + jlong table_ref_ptr, + jlong builder_ptr, + jlong j_obj_key, + jboolean ignore_same_values) +{ + try { + SharedRealm shared_realm = *(reinterpret_cast(shared_realm_ptr)); + CreatePolicy policy = (ignore_same_values) ? CreatePolicy::UpdateModified : CreatePolicy::UpdateAll; + TableRef table = TBL_REF(table_ref_ptr); + ObjKey embedded_object_key(j_obj_key); + const auto& schema = shared_realm->schema(); + const ObjectSchema& object_schema = get_schema(schema, table); + JavaContext ctx(env, shared_realm, object_schema); + auto list = *reinterpret_cast(builder_ptr); + JavaValue values = JavaValue(list); + Object obj = Object::create(ctx, shared_realm, object_schema, values, policy, embedded_object_key); + return reinterpret_cast(new Obj(obj.obj())); + } + CATCH_STD() + return realm::npos; +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeStartList (JNIEnv* env, jclass, jlong list_size) { diff --git a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java index 693fe41909..58fc0fb63f 100644 --- a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java @@ -197,6 +197,11 @@ public void setObjectId(long columnKey, ObjectId value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public long createEmbeddedObject(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public boolean isValid() { return false; diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 23bcd5f527..a71594fefd 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -47,6 +47,7 @@ import javax.annotation.Nullable; import io.reactivex.Flowable; +import io.realm.annotations.RealmClass; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; @@ -62,6 +63,7 @@ import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.Util; import io.realm.internal.annotations.ObjectServer; @@ -158,9 +160,9 @@ private Realm(RealmCache cache, OsSharedRealm.VersionID version) { schema = new ImmutableRealmSchema(this, new ColumnIndices(configuration.getSchemaMediator(), sharedRealm.getSchemaInfo())); // FIXME: This is to work around the different behaviour between the read only Realms in the Object Store and - // in current java implementation. Opening a read only Realm with some missing schemas is allowed by Object - // Store and realm-cocoa. In that case, any query based on the missing schema should just return an empty - // results. Fix this together with https://github.com/realm/realm-java/issues/2953 + // in current java implementation. Opening a read only Realm with some missing schemas is allowed by Object + // Store and realm-cocoa. In that case, any query based on the missing schema should just return an empty + // results. Fix this together with https://github.com/realm/realm-java/issues/2953 if (configuration.isReadOnly()) { RealmProxyMediator mediator = configuration.getSchemaMediator(); Set> classes = mediator.getModelClasses(); @@ -964,6 +966,10 @@ private Scanner getFullStringScanner(InputStream in) { */ public E createObject(Class clazz) { checkIfValid(); + RealmProxyMediator mediator = configuration.getSchemaMediator(); + if (mediator.isEmbedded(clazz)) { + throw new IllegalArgumentException("This class is marked embedded. Use `createEmbeddedObject(class, parent, property)` instead: " + mediator.getSimpleClassName(clazz)); + } return createObjectInternal(clazz, true, Collections.emptyList()); } @@ -1011,9 +1017,63 @@ E createObjectInternal( */ public E createObject(Class clazz, @Nullable Object primaryKeyValue) { checkIfValid(); + RealmProxyMediator mediator = configuration.getSchemaMediator(); + if (mediator.isEmbedded(clazz)) { + throw new IllegalArgumentException("This class is marked embedded. Use `createEmbeddedObject(class, parent, property)` instead: " + mediator.getSimpleClassName(clazz)); + } return createObjectInternal(clazz, primaryKeyValue, true, Collections.emptyList()); } + /** + * Instantiates and adds a new embedded object to the Realm. + *

              + * This method should only be used to created objects of types marked as embedded. + * + * @param clazz the Class of the object to create. It must be marked with {@code \@RealmClass(embedded = true)}. + * @param parent The parent object which should a reference to the embedded object. If the parent property is a list + * the embedded object will be added to the end of that list. + * @param parentProperty the property in the parent class which holds the reference. + * @return the newly created embedded object. + * @throws IllegalArgumentException if {@code clazz} is not an embedded class or if the property + * in the parent class cannot hold objects of the appropriate type. + * @see RealmClass#embedded() + */ + public E createEmbeddedObject(Class clazz, RealmModel parentObject, String parentProperty) { + checkIfValid(); + Util.checkNull(parentObject, "parentObject"); + Util.checkEmpty(parentProperty, "parentProperty"); + if (!RealmObject.isManaged(parentObject) || !RealmObject.isValid(parentObject)) { + throw new IllegalArgumentException("Only valid, managed objects can be a parent to an embedded object."); + } + RealmObjectProxy proxy = (RealmObjectProxy) parentObject; + long parentPropertyColKey = schema.getSchemaForClass(parentObject.getClass()).getColumnKey(parentProperty); + RealmFieldType parentPropertyType = schema.getSchemaForClass(parentObject.getClass()).getFieldType(parentProperty); + Row embeddedObject; + switch(parentPropertyType) { + case OBJECT: { + // FIXME: Check type of link + long objKey = proxy.realmGet$proxyState().getRow$realm().createEmbeddedObject(parentPropertyColKey); + embeddedObject = getTable(clazz).getUncheckedRow(objKey); + break; + } + case LIST: { + // FIXME: Check type of link + long objKey = proxy.realmGet$proxyState().getRow$realm().getModelList(parentPropertyColKey).createAndAddEmbeddedObject(); + embeddedObject = getTable(clazz).getUncheckedRow(objKey); + break; + } + default: + throw new IllegalArgumentException("Parent property is not a reference to embedded objects of the appropriate type: " + parentPropertyType); + } + + //noinspection unchecked + return (E) configuration.getSchemaMediator().newInstance(clazz, + this, + embeddedObject, + schema.getColumnInfo(clazz), + true, Collections.EMPTY_LIST); + } + /** * Same as {@link #createObject(Class, Object)} but this does not check the thread. * @@ -1057,6 +1117,7 @@ E createObjectInternal( */ public E copyToRealm(E object, ImportFlag... flags) { checkNotNullObject(object); + return copyOrUpdate(object, false, new HashMap<>(), Util.toSet(flags)); } @@ -1685,6 +1746,10 @@ private E copyOrUpdate(E object, boolean update, Map E copyToRealmIfNeeded(E object) { + private boolean checkCanObjectBeCopied(BaseRealm realm, RealmModel object) { if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; @@ -1559,7 +1604,7 @@ private E copyToRealmIfNeeded(E object) { String objectClassName = ((DynamicRealmObject) object).getType(); if (listClassName.equals(objectClassName)) { // Same Realm instance and same target table - return object; + return false; } else { // Different target table throw new IllegalArgumentException(String.format(Locale.US, @@ -1580,11 +1625,15 @@ private E copyToRealmIfNeeded(E object) { if (realm != proxy.realmGet$proxyState().getRealm$realm()) { throw new IllegalArgumentException("Cannot copy an object from another Realm instance."); } - return object; + return false; } } } + return true; + } + // Transparently copies an unmanaged object or managed object from another Realm to the Realm backing this RealmList. + private E copyToRealm(E object) { // At this point the object can only be a typed object, so the backing Realm cannot be a DynamicRealm. Realm realm = (Realm) this.realm; if (OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), @@ -1594,6 +1643,15 @@ private E copyToRealmIfNeeded(E object) { return realm.copyToRealm(object); } } + + private void updateEmbeddedObject(RealmModel unmanagedObject, long objKey) { + RealmProxyMediator schemaMediator = realm.getConfiguration().getSchemaMediator(); + Class modelClass = Util.getOriginalModelClass(unmanagedObject.getClass()); + Table table = ((Realm) realm).getTable(modelClass); + RealmModel managedObject = schemaMediator.newInstance(modelClass, realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(modelClass), true, Collections.EMPTY_LIST); + schemaMediator.updateEmbeddedObject((Realm) realm, unmanagedObject, managedObject, new HashMap<>(), Collections.EMPTY_SET); + } + } /** diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 85bd1edb88..b96f95632b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -26,6 +26,7 @@ import javax.annotation.Nullable; +import io.realm.annotations.RealmClass; import io.realm.annotations.Required; import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; @@ -419,6 +420,48 @@ public RealmFieldType getFieldType(String fieldName) { return table.getColumnType(columnKey); } + /** + * Returns {@code true} if objects of this type are considered "embedded". + * See {@link RealmClass#embedded()} for further details. + * + * @return {@code true} if objects of this type are embedded. {@code false} if not. + */ + public boolean isEmbedded() { + return table.isEmbedded(); + } + + /** + * Converts the class to be embedded or not. + *

              + * A class can only be marked as embedded if the following invariants are satisfied: + *

                + *
              • + * The class is not allowed to have a primary key defined. + *
              • + *
              • + * All existing objects of this type, must have one and exactly one parent object + * already pointing to it. If 0 or more than 1 object has a reference to an object + * about to be marked embedded an {@link IllegalStateException} will be thrown. + *
              • + *
              + * + * @throws IllegalStateException if the class could not be converted because it broke some of the Embedded Objects invariants. + * @see RealmClass#embedded() + */ + public void setEmbedded(boolean embedded) { + if (hasPrimaryKey()) { + throw new IllegalStateException("Embedded classes cannot have primary keys. This class " + + "has a primary key defined so cannot be marked as embedded: " + getClassName()); + } + boolean setEmbedded = table.setEmbedded(embedded); + if (!setEmbedded && embedded) { + throw new IllegalStateException("The class could not be marked as embedded as some " + + "objects of this type break some of the Embedded Objects invariants. In order to convert " + + "all objects to be embedded, they must have one and exactly one parent object" + + "pointing to them."); + } + } + /** * Get a parser for a field descriptor. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index afd7f74aa6..92545b82bf 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -192,6 +192,11 @@ public void setObjectId(long columnKey, ObjectId value) { throw getStubException(); } + @Override + public long createEmbeddedObject(long columnKey) { + throw getStubException(); + } + @Override public boolean isValid() { return false; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsList.java b/realm/realm-library/src/main/java/io/realm/internal/OsList.java index a95145d955..5e415e00da 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsList.java @@ -320,6 +320,19 @@ public OsList freeze(OsSharedRealm frozenRealm) { (targetTable != null) ? targetTable.freeze(frozenRealm) : null); } + public long createAndAddEmbeddedObject() { + return nativeCreateAndAddEmbeddedObject(nativePtr, size()); + } + + public long createAndAddEmbeddedObject(long index) { + return nativeCreateAndAddEmbeddedObject(nativePtr, index); + } + + public long createAndSetEmbeddedObject(long index) { + return nativeCreateAndSetEmbeddedObject(nativePtr, index); + } + + private static native long nativeGetFinalizerPtr(); // TODO: nativeTablePtr is not necessary. It is used to create FieldDescriptor which should be generated from @@ -418,4 +431,11 @@ public OsList freeze(OsSharedRealm frozenRealm) { private native void nativeStopListening(long nativePtr); private static native long nativeFreeze(long nativePtr, long sharedRealmNativePtr); + + // Create an "empty" embedded object at the end of the list + private static native long nativeCreateAndAddEmbeddedObject(long nativePtr, long index); + + // Replaces the embedded object and index with a new "empty" embedded object + private static native long nativeCreateAndSetEmbeddedObject(long nativePtr, long index); + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index ab139b299b..a7b94cd1a3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -254,6 +254,10 @@ public static long createRowWithPrimaryKey(Table table, long primaryKeyColumnInd } } + public static long createEmbeddedObject(Table parentTable, long parentObjectKey, long parentColumnKey) { + return nativeCreateEmbeddedObject(parentTable.getNativePtr(), parentObjectKey, parentColumnKey); + } + // Called by JNI @SuppressWarnings("unused") private void notifyChangeListeners(String[] changedFields) { @@ -301,4 +305,6 @@ private static native long nativeCreateNewObjectWithObjectIdPrimaryKey(long shar long tableRefPtr, long pk_column_index, @Nullable String data); + private static native long nativeCreateEmbeddedObject(long parentTablePtr, long parentObjectKey, long parentObjectColumnKey); + } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java index cf2e4de4b5..43e7e32105 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectSchemaInfo.java @@ -30,6 +30,7 @@ public class OsObjectSchemaInfo implements NativeObject { public static class Builder { private final String className; private final long[] persistedPropertyPtrArray; + private final boolean embedded; private int persistedPropertyPtrCurPos = 0; private final long[] computedPropertyPtrArray; private int computedPropertyPtrCurPos = 0; @@ -40,8 +41,9 @@ public static class Builder { * * @param className name of the class */ - public Builder(String className, int persistedPropertyCapacity, int computedPropertyCapacity) { + public Builder(String className, boolean embedded, int persistedPropertyCapacity, int computedPropertyCapacity) { this.className = className; + this.embedded = embedded; this.persistedPropertyPtrArray = new long[persistedPropertyCapacity]; this.computedPropertyPtrArray = new long[computedPropertyCapacity]; } @@ -125,7 +127,7 @@ public OsObjectSchemaInfo build() { if (persistedPropertyPtrCurPos == -1 || computedPropertyPtrCurPos == -1) { throw new IllegalStateException("'OsObjectSchemaInfo.build()' has been called before on this object."); } - OsObjectSchemaInfo info = new OsObjectSchemaInfo(className); + OsObjectSchemaInfo info = new OsObjectSchemaInfo(className, embedded); nativeAddProperties(info.nativePtr, persistedPropertyPtrArray, computedPropertyPtrArray); persistedPropertyPtrCurPos = -1; computedPropertyPtrCurPos = -1; @@ -142,8 +144,8 @@ public OsObjectSchemaInfo build() { * * @param className name of the class */ - private OsObjectSchemaInfo(String className) { - this(nativeCreateRealmObjectSchema(className)); + private OsObjectSchemaInfo(String className, boolean embedded) { + this(nativeCreateRealmObjectSchema(className, embedded)); } /** @@ -185,6 +187,11 @@ public Property getProperty(String propertyName) { return propertyPtr == 0 ? null : new Property(nativeGetPrimaryKeyProperty(nativePtr)); } + + public boolean isEmbedded() { + return nativeIsEmbedded(nativePtr); + } + @Override public long getNativePtr() { return nativePtr; @@ -195,7 +202,7 @@ public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } - private static native long nativeCreateRealmObjectSchema(String className); + private static native long nativeCreateRealmObjectSchema(String className, boolean embedded); private static native long nativeGetFinalizerPtr(); @@ -210,4 +217,5 @@ public long getNativeFinalizerPtr() { // Return nullptr if it doesn't have a primary key. private static native long nativeGetPrimaryKeyProperty(long nativePtr); + private static native boolean nativeIsEmbedded(long nativePtr); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index f95bf2a138..70095a7411 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -221,6 +221,11 @@ public void setObjectId(long columnKey, ObjectId value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public long createEmbeddedObject(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public boolean isValid() { return false; diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java index fc28b88e67..cabc4b1a55 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmProxyMediator.java @@ -197,6 +197,23 @@ public abstract E newInstance(Class clazz, */ public abstract E createDetachedCopy(E realmObject, int maxDepth, Map> cache); + /** + * Returns whether or not this class is considered "embedded". + */ + public abstract boolean isEmbedded(Class clazz); + + + /** + * Updates an embedded object with the values from an unmanaged object. + * + * @param realm the reference to the {@link Realm} where the object will be copied. + * @param unmanagedObject the unmanaged objects whose values should be used to update the manged object + * @param managedObject the managed object that should be updated + * @param cache the cache for mapping between unmanaged objects and their {@link RealmObjectProxy} representation. + * @param flags any special flags controlling the behaviour of the import. + */ + public abstract void updateEmbeddedObject(Realm realm, E unmanagedObject, E managedObject, Map cache, Set flags); + /** * Returns whether Realm transformer has been applied or not. Subclasses of this class are * created by the annotation processor and the Realm transformer will add an override of @@ -238,4 +255,8 @@ protected static RealmException getMissingProxyClassException(String className) return new RealmException( String.format("'%s' is not part of the schema for this Realm.", className)); } + + protected static IllegalStateException getNotEmbeddedClassException(String className) { + return new IllegalStateException("This class is not marked embedded: " + className); + } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index 08374fb427..ce5ddeaf3d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -120,6 +120,11 @@ public interface Row { void setObjectId(long columnKey, ObjectId value); + // Creates a new Embedded object in the given property. + // This will replace any existing object which will be + // deleted. The Obj pointer for the new object is returned. + long createEmbeddedObject(long columnKey); + /** * Checks if the row is still valid. * diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index d388ca1bcc..ad1183feb2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -702,6 +702,18 @@ public Table freeze(OsSharedRealm frozenRealm) { return new Table(frozenRealm, nativeFreeze(frozenRealm.getNativePtr(), nativeTableRefPtr)); } + public boolean isEmbedded() { + return nativeIsEmbedded(nativeTableRefPtr); + } + + /** + * Returns true if the state was changed, false if not. If false was returned, it meant + * some invariant was broken when trying to change the state + */ + public boolean setEmbedded(boolean embedded) { + return nativeSetEmbedded(nativeTableRefPtr, embedded); + } + @Nullable public static String getClassNameForTable(@Nullable String name) { if (name == null) { return null; } @@ -844,4 +856,8 @@ public static String getTableNameForClass(String name) { private static native long nativeGetFinalizerPtr(); private static native long nativeFreeze(long frozenSharedRealmPtr, long nativeTableRefPtr); + + private static native boolean nativeIsEmbedded(long nativeTableRefPtr); + + private static native boolean nativeSetEmbedded(long nativeTableRefPtr, boolean isEmbedded); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 095fb4f9a5..9ad00ab02a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -306,6 +306,12 @@ public void setObjectId(long columnKey, @Nullable ObjectId value) { } } + @Override + public long createEmbeddedObject(long columnKey) { + parent.checkImmutable(); + return nativeCreateEmbeddedObject(nativePtr, columnKey); + } + /** * Converts the unchecked Row to a checked variant. * @@ -410,5 +416,7 @@ public boolean isLoaded() { protected native long nativeFreeze(long nativeRowPtr, long frozenRealmNativePtr); + protected native long nativeCreateEmbeddedObject(long nativeRowPtr, long columnKey); + private static native long nativeGetFinalizerPtr(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java index b95207f4d6..4f061c4ffb 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/CompositeMediator.java @@ -164,6 +164,18 @@ public E createDetachedCopy(E realmObject, int maxDepth, return mediator.createDetachedCopy(realmObject, maxDepth, cache); } + @Override + public boolean isEmbedded(Class clazz) { + RealmProxyMediator mediator = getMediator(Util.getOriginalModelClass(clazz)); + return mediator.isEmbedded(clazz); + } + + @Override + public void updateEmbeddedObject(Realm realm, E unmanagedObject, E managedObject, Map cache, Set flags) { + RealmProxyMediator mediator = getMediator(Util.getOriginalModelClass(managedObject.getClass())); + mediator.updateEmbeddedObject(realm, unmanagedObject, managedObject, cache, flags); + } + @Override public boolean transformerApplied() { for (Map.Entry, RealmProxyMediator> entry : mediators.entrySet()) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java index ceb23d7c05..5b87416138 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java +++ b/realm/realm-library/src/main/java/io/realm/internal/modules/FilterableMediator.java @@ -162,6 +162,18 @@ public E createDetachedCopy(E realmObject, int maxDepth, return originalMediator.createDetachedCopy(realmObject, maxDepth, cache); } + @Override + public boolean isEmbedded(Class clazz) { + checkSchemaHasClass(Util.getOriginalModelClass(clazz)); + return originalMediator.isEmbedded(clazz); + } + + @Override + public void updateEmbeddedObject(Realm realm, E unmanagedObject, E managedObject, Map cache, Set flags) { + checkSchemaHasClass(Util.getOriginalModelClass(managedObject.getClass())); + originalMediator.updateEmbeddedObject(realm, unmanagedObject, managedObject, cache, flags); + } + @Override public boolean transformerApplied() { //noinspection SimplifiableIfStatement diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java index 201efe62e5..a2daace840 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java @@ -39,7 +39,8 @@ * This class is a wrapper around building up object data for calling `Object::create()` *

              * Fill the object data by calling the various `addX()` methods, then create a new Object or update - * an existing one by calling {@link #createNewObject()} or {@link #updateExistingObject()}. + * an existing one by calling {@link #createNewObject()}, {@link #updateExistingTopLevelObject()} or. + * {@link #updateExistingEmbeddedObject(RealmObjectProxy)} *

              * This class assumes it is only being used from within a write transaction. Using it outside one * will result in undefined behaviour. @@ -403,12 +404,28 @@ private void addEmptyList(long columnKey) { /** * Updates any existing object if it exists, otherwise creates a new one. + *

              + * Updating an existing object requires that the primary key is defined as one of the fields. + *

              + * The builder is automatically closed after calling this method. + */ + public void updateExistingTopLevelObject() { + try { + nativeCreateOrUpdateTopLevelObject(sharedRealmPtr, tablePtr, builderPtr, true, ignoreFieldsWithSameValue); + } finally { + close(); + } + } + + /** + * Updates an existing embedded object. * * The builder is automatically closed after calling this method. */ - public void updateExistingObject() { + public void updateExistingEmbeddedObject(RealmObjectProxy embeddedObject) { try { - nativeCreateOrUpdate(sharedRealmPtr, tablePtr, builderPtr, true, ignoreFieldsWithSameValue); + long objKey = embeddedObject.realmGet$proxyState().getRow$realm().getObjectKey(); + nativeUpdateEmbeddedObject(sharedRealmPtr, tablePtr, builderPtr, objKey, ignoreFieldsWithSameValue); } finally { close(); } @@ -422,7 +439,7 @@ public void updateExistingObject() { public UncheckedRow createNewObject() { UncheckedRow row; try { - long rowPtr = nativeCreateOrUpdate(sharedRealmPtr, tablePtr, builderPtr, false, false); + long rowPtr = nativeCreateOrUpdateTopLevelObject(sharedRealmPtr, tablePtr, builderPtr, false, false); row = new UncheckedRow(context, table, rowPtr); } finally { close(); @@ -451,12 +468,18 @@ private interface ItemCallback { private static native long nativeCreateBuilder(); private static native void nativeDestroyBuilder(long builderPtr); - private static native long nativeCreateOrUpdate(long sharedRealmPtr, + private static native long nativeCreateOrUpdateTopLevelObject(long sharedRealmPtr, long tablePtr, long builderPtr, boolean updateExistingObject, boolean ignoreFieldsWithSameValue); + private static native long nativeUpdateEmbeddedObject(long sharedRealmPtr, + long tablePtr, + long builderPtr, + long objKey, + boolean ignoreFieldsWithSameValue); + // Add simple properties private static native void nativeAddNull(long builderPtr, long columnKey); private static native void nativeAddInteger(long builderPtr, long columnKey, long val); From 6d71bff79b4d658155b016344388393e7fd91241 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 3 Jun 2020 13:17:40 +0200 Subject: [PATCH 1557/2110] Use supported file name for Docker CI cache --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 998bdfbe99..969d50d38b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -45,7 +45,7 @@ try { def buildEnv = null stage('Prepare Docker Images') { - buildEnv = buildDockerEnv("ci/realm-java:v10", push: currentBranch == 'v10') // TODO Should be renamed to 'master' when merged there. + buildEnv = buildDockerEnv("realm-java-ci:v10", push: currentBranch == 'v10') // TODO Should be renamed to 'master' when merged there. def props = readProperties file: 'dependencies.list' echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") @@ -102,7 +102,7 @@ try { stage('Static code analysis') { try { - gradle('realm', "findbugs ${abiFilter}") // FIXME Renable pmd and checkstyle + gradle('realm', "findbugs pmd checkstyle ${abiFilter}") // FIXME Renable pmd and checkstyle } finally { publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) // publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) From 59d17362999848bfd74004da22a851ac6eb52326 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 3 Jun 2020 16:57:40 +0200 Subject: [PATCH 1558/2110] Disable Docker image caching on CI --- Jenkinsfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 969d50d38b..5a0254899e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -45,7 +45,9 @@ try { def buildEnv = null stage('Prepare Docker Images') { - buildEnv = buildDockerEnv("realm-java-ci:v10", push: currentBranch == 'v10') // TODO Should be renamed to 'master' when merged there. + // TODO Should be renamed to 'master' when merged there. + // TODO Figure out why caching the image doesn't work. + buildEnv = buildDockerEnv("realm-java-ci:v10", push: currentBranch == 'v10-do-not-cache') def props = readProperties file: 'dependencies.list' echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") From fcdf83b91d37812ab5c5d8ff6ee2844ca0556e6c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 3 Jun 2020 17:45:03 +0200 Subject: [PATCH 1559/2110] Disable PMD and Checkstyle again --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5a0254899e..f1e2ba8de1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -104,7 +104,7 @@ try { stage('Static code analysis') { try { - gradle('realm', "findbugs pmd checkstyle ${abiFilter}") // FIXME Renable pmd and checkstyle + gradle('realm', "findbugs ${abiFilter}") // FIXME Renable pmd and checkstyle } finally { publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) // publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) From 20b77b009cac31c946e9546c2b79dbc0693335b6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 4 Jun 2020 09:05:32 +0200 Subject: [PATCH 1560/2110] Add @Beta tag to all MongoDB Realm API's (#6895) --- .../exceptions/DownloadingRealmInterruptedException.java | 2 ++ .../objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java | 3 ++- .../src/objectServer/java/io/realm/mongodb/App.java | 2 ++ .../java/io/realm/mongodb/AppConfiguration.java | 2 ++ .../java/io/realm/mongodb/AuthenticationListener.java | 3 +++ .../objectServer/java/io/realm/mongodb/Credentials.java | 2 ++ .../java/io/realm/mongodb/EmailPasswordAuthImpl.java | 2 ++ .../src/objectServer/java/io/realm/mongodb/ErrorCode.java | 2 ++ .../objectServer/java/io/realm/mongodb/FunctionsImpl.java | 4 ++-- .../java/io/realm/mongodb/ObjectServerError.java | 2 ++ .../src/objectServer/java/io/realm/mongodb/User.java | 2 ++ .../objectServer/java/io/realm/mongodb/UserIdentity.java | 3 +++ .../java/io/realm/mongodb/auth/ApiKeyAuth.java | 2 ++ .../java/io/realm/mongodb/auth/EmailPasswordAuth.java | 2 ++ .../java/io/realm/mongodb/auth/UserApiKey.java | 2 ++ .../java/io/realm/mongodb/functions/Functions.java | 2 ++ .../java/io/realm/mongodb/mongo/MongoClient.java | 2 ++ .../java/io/realm/mongodb/mongo/MongoCollection.java | 2 ++ .../java/io/realm/mongodb/mongo/MongoDatabase.java | 2 ++ .../java/io/realm/mongodb/mongo/MongoNamespace.java | 3 +++ .../java/io/realm/mongodb/mongo/options/CountOptions.java | 3 +++ .../mongodb/mongo/options/FindOneAndModifyOptions.java | 3 +++ .../java/io/realm/mongodb/mongo/options/FindOptions.java | 3 +++ .../io/realm/mongodb/mongo/options/InsertManyResult.java | 3 +++ .../java/io/realm/mongodb/mongo/options/UpdateOptions.java | 3 +++ .../java/io/realm/mongodb/mongo/result/DeleteResult.java | 3 +++ .../io/realm/mongodb/mongo/result/InsertOneResult.java | 3 +++ .../java/io/realm/mongodb/mongo/result/UpdateResult.java | 3 +++ .../src/objectServer/java/io/realm/mongodb/push/Push.java | 7 ++++++- .../io/realm/mongodb/sync/ClientResetRequiredError.java | 2 ++ .../java/io/realm/mongodb/sync/ClientResyncMode.java | 2 ++ .../java/io/realm/mongodb/sync/ConnectionListener.java | 3 +++ .../java/io/realm/mongodb/sync/ConnectionState.java | 3 +++ .../objectServer/java/io/realm/mongodb/sync/Progress.java | 2 ++ .../java/io/realm/mongodb/sync/ProgressListener.java | 3 +++ .../java/io/realm/mongodb/sync/ProgressMode.java | 3 +++ .../src/objectServer/java/io/realm/mongodb/sync/Sync.java | 2 ++ .../java/io/realm/mongodb/sync/SyncConfiguration.java | 1 + .../java/io/realm/mongodb/sync/SyncSession.java | 2 ++ 39 files changed, 96 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java b/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java index 55b4a12bf6..4ff848657c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java +++ b/realm/realm-library/src/objectServer/java/io/realm/exceptions/DownloadingRealmInterruptedException.java @@ -16,6 +16,7 @@ package io.realm.exceptions; +import io.realm.annotations.Beta; import io.realm.mongodb.sync.SyncConfiguration; @@ -23,6 +24,7 @@ * Exception class used when a Realm was interrupted while downloading the initial data set. * This can only happen if {@link SyncConfiguration.Builder#waitForInitialRemoteData()} is set. */ +@Beta public class DownloadingRealmInterruptedException extends RuntimeException { public DownloadingRealmInterruptedException(SyncConfiguration syncConfig, Throwable exception) { super("Realm was interrupted while downloading the latest changes from the server: " + syncConfig.getPath(), diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java index 2a42fb684b..779a387a3e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java @@ -18,10 +18,11 @@ import javax.annotation.Nullable; +import io.realm.annotations.Beta; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.mongodb.auth.ApiKeyAuth; - +@Beta class ApiKeyAuthImpl extends ApiKeyAuth { ApiKeyAuthImpl(User user) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index b49de85ce1..b73e054acd 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -36,6 +36,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.BuildConfig; import io.realm.Realm; +import io.realm.annotations.Beta; import io.realm.internal.mongodb.Request; import io.realm.mongodb.auth.EmailPasswordAuth; import io.realm.RealmAsyncTask; @@ -53,6 +54,7 @@ /** * FIXME */ +@Beta public class App { static final class SyncImpl extends Sync { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index 084e3079bb..3b2b6201dc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -38,6 +38,7 @@ import javax.annotation.Nullable; import io.realm.Realm; +import io.realm.annotations.Beta; import io.realm.mongodb.sync.SyncSession; import io.realm.internal.Util; import io.realm.log.RealmLog; @@ -52,6 +53,7 @@ * Configuring a App is only required if the default settings are not enough. Otherwise calling * {@code new App("app-id")} is sufficient. */ +@Beta public class AppConfiguration { /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AuthenticationListener.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AuthenticationListener.java index 00cffc9b42..329596271f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AuthenticationListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AuthenticationListener.java @@ -16,9 +16,12 @@ package io.realm.mongodb; +import io.realm.annotations.Beta; + /** * Interface describing events related to Users and their authentication */ +@Beta public interface AuthenticationListener { /** * A user was logged into the Object Server diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java index 414e48e37d..4b9274fe74 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java @@ -16,6 +16,7 @@ package io.realm.mongodb; +import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.internal.objectstore.OsAppCredentials; import io.realm.mongodb.auth.EmailPasswordAuth; @@ -48,6 +49,7 @@ *

              * @see Authentication Providers */ +@Beta public class Credentials { OsAppCredentials osCredentials; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java index a6f33deb60..57a7dc60e6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java @@ -16,9 +16,11 @@ package io.realm.mongodb; +import io.realm.annotations.Beta; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.mongodb.auth.EmailPasswordAuth; +@Beta class EmailPasswordAuthImpl extends EmailPasswordAuth { EmailPasswordAuthImpl(App app) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java index 8cdfe9db2a..b698113c93 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java @@ -19,6 +19,7 @@ import java.util.Locale; +import io.realm.annotations.Beta; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.log.RealmLog; import io.realm.mongodb.sync.SyncConfiguration; @@ -26,6 +27,7 @@ /** * This class enumerate all potential errors related to using the Object Server or synchronizing data. */ +@Beta public enum ErrorCode { // See Client::Error in https://github.com/realm/realm-sync/blob/master/src/realm/sync/client.hpp#L1230 diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java index f4b8d4bbdb..30c580f1f2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java @@ -15,8 +15,6 @@ */ package io.realm.mongodb; -import org.bson.BSONException; -import org.bson.BsonElement; import org.bson.codecs.Decoder; import org.bson.codecs.configuration.CodecConfigurationException; import org.bson.codecs.configuration.CodecRegistry; @@ -24,6 +22,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; @@ -35,6 +34,7 @@ * Internal implementation of Functions invoking the actual OS function in the context of the * {@link User}/{@link App}. */ +@Beta class FunctionsImpl extends Functions { FunctionsImpl(User user) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java index 83fb9ef247..f8d790a2dc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; +import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.mongodb.sync.SyncSession; @@ -31,6 +32,7 @@ * * @see ErrorCode for a list of possible errors. */ +@Beta public class ObjectServerError extends RuntimeException { // The Java representation of the error. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index 75eaa6351c..23a50cec2c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -24,6 +24,7 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.annotations.Beta; import io.realm.internal.mongodb.Request; import io.realm.internal.objectstore.OsMongoClient; import io.realm.mongodb.auth.ApiKeyAuth; @@ -42,6 +43,7 @@ /** * FIXME */ +@Beta public class User { OsSyncUser osUser; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java index f133d65325..fc47cdc9fb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java @@ -15,12 +15,15 @@ */ package io.realm.mongodb; +import io.realm.annotations.Beta; + /** * Each User is represented by 1 or more identities each defined by an * {@link Credentials.IdentityProvider}. * * This class represents the identity defined by a specific provider. */ +@Beta public class UserIdentity { private final String userId; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java index 5fc121f50c..9e42da1e35 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java @@ -23,6 +23,7 @@ import javax.annotation.Nullable; +import io.realm.annotations.Beta; import io.realm.internal.mongodb.Request; import io.realm.mongodb.ObjectServerError; import io.realm.RealmAsyncTask; @@ -39,6 +40,7 @@ /** * This class exposes functionality for a user to manage API keys under their control. */ +@Beta public abstract class ApiKeyAuth { private static final int TYPE_CREATE = 1; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java index 4a0aa6f83e..c738b7dc90 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; +import io.realm.annotations.Beta; import io.realm.internal.mongodb.Request; import io.realm.mongodb.ObjectServerError; import io.realm.RealmAsyncTask; @@ -36,6 +37,7 @@ * Class encapsulating functionality provided when {@link User}'s are logged in through the * {@link Credentials.IdentityProvider#EMAIL_PASSWORD} provider. */ +@Beta public abstract class EmailPasswordAuth { private static final int TYPE_REGISTER_USER = 1; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java index 76dbf2bd1b..6101590f44 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java @@ -19,6 +19,7 @@ import javax.annotation.Nullable; +import io.realm.annotations.Beta; import io.realm.mongodb.App; import io.realm.mongodb.User; @@ -32,6 +33,7 @@ * Note that a keys {@link #value} is only available when the key is created, after that it is not * visible. So anyone creating an API key is responsible for storing it safely after that. */ +@Beta public class UserApiKey { private final ObjectId id; private final String value; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java index 7d30b32b07..22b6a5ba0c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java @@ -22,6 +22,7 @@ import java.util.List; import io.realm.RealmAsyncTask; +import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.internal.mongodb.Request; import io.realm.mongodb.App; @@ -45,6 +46,7 @@ * @see AppConfiguration * @see CodecRegistry */ +@Beta public abstract class Functions { protected User user; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java index 17f8110a3d..931948f888 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java @@ -18,6 +18,7 @@ import org.bson.codecs.configuration.CodecRegistry; +import io.realm.annotations.Beta; import io.realm.mongodb.User; import io.realm.internal.Util; import io.realm.internal.objectstore.OsMongoClient; @@ -25,6 +26,7 @@ /** * The remote MongoClient used for working with data in MongoDB remotely via Realm. */ +@Beta abstract public class MongoClient { private OsMongoClient osMongoClient; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java index 1b48bb5282..a36c9575b2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java @@ -23,6 +23,7 @@ import java.util.List; +import io.realm.annotations.Beta; import io.realm.internal.common.TaskDispatcher; import io.realm.internal.objectstore.OsMongoCollection; import io.realm.mongodb.mongo.options.CountOptions; @@ -45,6 +46,7 @@ * to. * @see MongoDatabase */ +@Beta public class MongoCollection { private OsMongoCollection osMongoCollection; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java index bb791f2c8a..482e615e5b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java @@ -18,12 +18,14 @@ import org.bson.Document; +import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.internal.objectstore.OsMongoDatabase; /** * The RemoteMongoDatabase provides access to its {@link Document} {@link MongoCollection}s. */ +@Beta public class MongoDatabase { private String databaseName; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoNamespace.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoNamespace.java index 4a7457b1bd..e4152a0a2c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoNamespace.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoNamespace.java @@ -23,6 +23,8 @@ import java.util.HashSet; import java.util.Set; +import io.realm.annotations.Beta; + import static java.util.Arrays.asList; import static org.bson.assertions.Assertions.isTrueArgument; import static org.bson.assertions.Assertions.notNull; @@ -30,6 +32,7 @@ /** * A MongoDB namespace, which includes a database name and collection name. */ +@Beta public final class MongoNamespace { public static final String COMMAND_COLLECTION_NAME = "$cmd"; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/CountOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/CountOptions.java index 1ad8e51985..5c6dda1f00 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/CountOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/CountOptions.java @@ -16,9 +16,12 @@ package io.realm.mongodb.mongo.options; +import io.realm.annotations.Beta; + /** * The options for a count operation. */ +@Beta public class CountOptions { private int limit; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java index 59234e3edf..aba5be7264 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java @@ -20,10 +20,13 @@ import org.bson.conversions.Bson; +import io.realm.annotations.Beta; + /** * The options to apply to a findOneAndUpdate, findOneAndReplace, or findOneAndDelete operation * (also commonly referred to as findOneAndModify operations). */ +@Beta public class FindOneAndModifyOptions { private Bson projection; private Bson sort; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java index c849e96d5e..e7f84dec1d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java @@ -20,9 +20,12 @@ import org.bson.conversions.Bson; +import io.realm.annotations.Beta; + /** * The options to apply to a find operation (also commonly referred to as a query). */ +@Beta public class FindOptions { private int limit; private Bson projection; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/InsertManyResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/InsertManyResult.java index a7d931664a..119e36d427 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/InsertManyResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/InsertManyResult.java @@ -20,9 +20,12 @@ import org.bson.BsonValue; +import io.realm.annotations.Beta; + /** * The result of an insert many operation. */ +@Beta public class InsertManyResult { private final Map insertedIds; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/UpdateOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/UpdateOptions.java index 154957dca1..b3191f534e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/UpdateOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/UpdateOptions.java @@ -16,9 +16,12 @@ package io.realm.mongodb.mongo.options; +import io.realm.annotations.Beta; + /** * The options to apply when updating documents. */ +@Beta public class UpdateOptions { private boolean upsert; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/DeleteResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/DeleteResult.java index d7e7986e09..be74bb5c0f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/DeleteResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/DeleteResult.java @@ -16,9 +16,12 @@ package io.realm.mongodb.mongo.result; +import io.realm.annotations.Beta; + /** * The result of a delete operation. */ +@Beta public class DeleteResult { private final long deletedCount; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/InsertOneResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/InsertOneResult.java index 2a7a03056f..eb7eab1d9c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/InsertOneResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/InsertOneResult.java @@ -18,9 +18,12 @@ import org.bson.BsonValue; +import io.realm.annotations.Beta; + /** * The result of an insert one operation. */ +@Beta public class InsertOneResult { private final BsonValue insertedId; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/UpdateResult.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/UpdateResult.java index e998648848..f45d1f00bc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/UpdateResult.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/result/UpdateResult.java @@ -20,9 +20,12 @@ import org.bson.BsonValue; +import io.realm.annotations.Beta; + /** * The result of an update operation. */ +@Beta public class UpdateResult { private final long matchedCount; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java index 2be4523db5..2cfc102dce 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java @@ -15,6 +15,11 @@ */ package io.realm.mongodb.push; -// FIXME Javadoc?? Has to be public to live in separate package. +import io.realm.annotations.Beta; + +/** + * FIXME: Add Javadoc and implementation + */ +@Beta public class Push { } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java index 466ec3aa13..e0cfb1298a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java @@ -18,6 +18,7 @@ import java.io.File; +import io.realm.annotations.Beta; import io.realm.mongodb.ErrorCode; import io.realm.mongodb.ObjectServerError; import io.realm.Realm; @@ -29,6 +30,7 @@ * @see SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more information * about when and why Client Reset occurs and how to deal with it. */ +@Beta public class ClientResetRequiredError extends ObjectServerError { private final SyncConfiguration originalConfiguration; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java index 4c0eecde3f..d1101f3c9e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java @@ -16,6 +16,7 @@ package io.realm.mongodb.sync; +import io.realm.annotations.Beta; import io.realm.mongodb.ObjectServerError; import io.realm.internal.OsRealmConfig; @@ -28,6 +29,7 @@ *

              * IMPORTANT: Just having the device offline will not trigger a Client Resync. */ +@Beta enum ClientResyncMode { /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionListener.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionListener.java index 2728b9eba9..3c638cd925 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionListener.java @@ -15,6 +15,8 @@ */ package io.realm.mongodb.sync; +import io.realm.annotations.Beta; + /** * Interface used when reporting changes that happened to the connection used by the session. *

              @@ -27,6 +29,7 @@ * @see SyncSession#isConnected() * @see SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler) */ +@Beta public interface ConnectionListener { /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionState.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionState.java index 08eec29920..745e5e204f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionState.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ConnectionState.java @@ -16,9 +16,12 @@ package io.realm.mongodb.sync; +import io.realm.annotations.Beta; + /** * Enum describing the states of the underlying connection used by a {@link SyncSession}. */ +@Beta public enum ConnectionState { /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Progress.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Progress.java index f9bebacaeb..6b0d5c140d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Progress.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Progress.java @@ -16,6 +16,7 @@ package io.realm.mongodb.sync; +import io.realm.annotations.Beta; import io.realm.log.RealmLog; @@ -34,6 +35,7 @@ * @see SyncSession#addDownloadProgressListener(ProgressMode, ProgressListener) * @see SyncSession#addUploadProgressListener(ProgressMode, ProgressListener) */ +@Beta public class Progress { private final long transferredBytes; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressListener.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressListener.java index bc6c917041..11e931b482 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressListener.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressListener.java @@ -17,10 +17,13 @@ package io.realm.mongodb.sync; +import io.realm.annotations.Beta; + /** * Interface used when interested in updates on data either being uploaded to or downloaded from * a Realm Object Server. */ +@Beta public interface ProgressListener { /** * This method will be called periodically from the underlying Object Server Client responsible diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressMode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressMode.java index ceb7341714..090da93b65 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressMode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ProgressMode.java @@ -16,9 +16,12 @@ package io.realm.mongodb.sync; +import io.realm.annotations.Beta; + /** * Enum describing how to listen to progress changes. */ +@Beta public enum ProgressMode { /** * When registering the {@link ProgressListener}, it will record the current size of changes, and will only diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java index a371cb95e9..f2b160421e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java @@ -24,6 +24,7 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.annotations.Beta; import io.realm.mongodb.ErrorCode; import io.realm.internal.Keep; import io.realm.internal.OsRealmConfig; @@ -40,6 +41,7 @@ */ @Keep @SuppressFBWarnings("MS_CANNOT_BE_FINAL") +@Beta public abstract class Sync { private final App app; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index 339362ed09..5400ce99e6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -92,6 +92,7 @@ * @see The docs for * more information about the two types of synchronization. */ +@Beta public class SyncConfiguration extends RealmConfiguration { // The FAT file system has limitations of length. Also, not all characters are permitted. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 1c21371a7c..6012e88b3f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import io.realm.annotations.Beta; import io.realm.mongodb.ErrorCode; import io.realm.mongodb.ObjectServerError; import io.realm.Realm; @@ -59,6 +60,7 @@ * The {@link SyncSession} object is thread safe. */ @Keep +@Beta public class SyncSession { private final static int DIRECTION_DOWNLOAD = 1; private final static int DIRECTION_UPLOAD = 2; From dcdd421a31ab85bf3f49d16f24d2a8434c2f5c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Thu, 4 Jun 2020 10:45:49 +0200 Subject: [PATCH 1561/2110] Merge Stitch and Realm SDKs - 5: remote collection, added iterables and preliminary support for find and aggregate (#6881) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First iteration: added GMS library (possibly temporarily) to avoid introducing immediate breaking changes in how we process asynchronous operations with AsyncRealmTask. All original Stitch interfaces and proxies have been discarded in favour of Java classes (although this approach might be changed). Some interfaces connected to the collection's iterables have been omitted as it is unclear whether they will be needed or not for the time being. * Added licences to class headers plus a bit of cleanup * Added latest API methods and necessary classes * Added remote mongo client and remote database, their respective Os files and part of the native logic * Moved JNI callbacks outside RealmApp and added more remote collection classes * Updated object store branch to v10 and fixed wrong use of count call * Cleanup * Added task-related classes from Stitch * First steps towards using tasks for the count operation * Added preliminary collection test, only with scaffolding for "count", but still not working as the OS code isn't fully ready yet. Moved Realm initialisation in test cases outside TestRealmApp to setUp method as agreed internally, plus fixed some wrong implementation in the interop layer. Also updated dependencies list to fetch sync version 10 alpha 9 instead of 8. * Fixed wrong finalizer methods and cleanup to interop files * Moved TaskUtils to tests * test * cleanup * Moved classes * Updated OS pointer * Moved classes * Removed duplicate entries in CMakeLists * Added documentClass property to internal collection class * updated OS pointer * updated OS pointer * added suppresswarnings for ignored futures - issue inherited from Stitch's task framework - test to see if Jenkins swallows it * wip * Added test for Task.blockingGet and wip on insertOne * Added insertmany * Added more meaningful tests for count and insert. Temporarily commented out some code in EmailPasswordAuth.cpp after updating OS to v10. Now the remoteMongoClient is fetched as a shared_ptr in our interop layer. Added codec handling for RemoteMongoDatabase and document class for RemoteMongoCollection * Work in progress - insertMany and interop * Added deleteOne * Added deleteMany and adjusted visibility of OS constructors * wip * Updated pointer to OS * Restored curly braces * Removed unnecessary codec parameter in getDatabase * Added findOne and proper use of the BSON parsing protocol for handling, delivering and decoding results from the JNI * Updated OS pointer to branch that contains parsing fixes - update to OS v10 as soon as it is merged * Added missing findOne implementations and updated OS pointer * fixed unboxing that caused findbugs to complain * First batch of cleanup * Addressed error handling in interop layer plus more cleanup * Moved classes to new packages and removed "remote" prefix from class names * Restored wrongly removed public modifier to method * Renamed OS interop classes * Cleanup * Final round of cleanup and updated pointer to OS that allegedly fixes failing stress test * Fixed broken tests * Added updateOne * Renamed missing "remote" options and remote classes and added updateMany * Added findOneAndUpdate * Added another findOneAndUpdate variant * Removed duplicated code and added findOneAndReplace and Delete variants * Updated OS pointer * Reverted OS pointer * Added find * Removed unnecessary classes plus added options to find * A bit more cleanup * Removed unnecessary roundtrip to parser to decode a collection, added parsing guards in native code, added more visible fixmes in test file * Fixing find * Simplified code for update operations * More cleanup * More cleanup. Added comments on failing tests for debugging them with the OS folks * Renamed iterables * More cleanup for find iterable. Tweaks on how to pass options for find operations * First steps towards adding aggregate * Added aggregate and improved passing of filter to find iterable * Added defaults to switch statements handling operation types for collections and fixed static analysis issues * Added missing tests, but there are still bugs in the OS so some had to be ignored and sections of others commented out * Removed unnecessary asynchronicity in cursors, added default branches to switch cases in C++ collection for error handling, added javadoc, cleanup * Removed unwanted nullable parameters from some functions and a bit more cleanup * Added missing operations for document class, codec registry and namespace * Added test for withDocument * Fixed wrong type constant used for findOneAndReplace and Update options * Test breakdown into smaller cases and loooooots of cleanup * Updated javadoc * Added namespace assertions to collection test * Removed unnecessary OS classes for the iterables and added missing and fixed wrong documentation * Renamed test and update pointer to OS latest v10 * Added error handling for encoding/decoding Bsons directly in the protocol rather than in the callers. * Updated OS and added missing callback to findOneAndDelete after update * Cleanup * Fixed bogus logic in bson protocol guards * Removed redundant method * Updated OS pointer * Implemented suggestions for handling ObjectServer exceptions more generically and some more cleanup in test * Changed to Kotlin's foreach Co-authored-by: Eduardo López --- .../io/realm/mongodb/MongoClientTest.kt | 1044 +++++++++++++++++ .../io/realm/mongodb/MongoCollectionTest.kt | 471 -------- .../kotlin/io/realm/util/KotlinTestUtils.kt | 26 +- .../io/realm/util/mongodb/CustomType.java | 118 ++ .../realm-library/src/main/cpp/CMakeLists.txt | 4 + ...internal_objectstore_OsMongoCollection.cpp | 374 +++--- ...ngodb_mongo_iterable_AggregateIterable.cpp | 59 + ...lm_mongodb_mongo_iterable_FindIterable.cpp | 77 ++ realm/realm-library/src/main/cpp/object-store | 2 +- .../realm/internal/jni/JniBsonProtocol.java | 71 +- .../internal/objectstore/OsMongoClient.java | 13 +- .../objectstore/OsMongoCollection.java | 580 ++++----- .../internal/objectstore/OsMongoDatabase.java | 15 +- .../java/io/realm/mongodb/ErrorCode.java | 2 +- .../java/io/realm/mongodb/FunctionsImpl.java | 20 +- .../java/io/realm/mongodb/User.java | 25 +- .../io/realm/mongodb/functions/Functions.java | 15 +- .../io/realm/mongodb/mongo/MongoClient.java | 14 +- .../realm/mongodb/mongo/MongoCollection.java | 354 +++--- .../io/realm/mongodb/mongo/MongoDatabase.java | 26 +- .../mongo/iterable/AggregateIterable.java | 57 + .../mongodb/mongo/iterable/FindIterable.java | 124 ++ .../mongodb/mongo/iterable/MongoCursor.java | 68 ++ .../mongodb/mongo/iterable/MongoIterable.java | 129 ++ .../options/FindOneAndModifyOptions.java | 7 + .../mongodb/mongo/options/FindOptions.java | 4 + 26 files changed, 2423 insertions(+), 1276 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt delete mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/mongodb/CustomType.java create mode 100644 realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp create mode 100644 realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/AggregateIterable.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/FindIterable.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt new file mode 100644 index 0000000000..aad9db46e4 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt @@ -0,0 +1,1044 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.mongodb + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import io.realm.TestApp +import io.realm.TestHelper +import io.realm.mongodb.mongo.MongoClient +import io.realm.mongodb.mongo.MongoCollection +import io.realm.mongodb.mongo.MongoNamespace +import io.realm.mongodb.mongo.options.CountOptions +import io.realm.mongodb.mongo.options.FindOneAndModifyOptions +import io.realm.mongodb.mongo.options.FindOptions +import io.realm.mongodb.mongo.options.UpdateOptions +import io.realm.util.assertFailsWithErrorCode +import io.realm.util.blockingGetResult +import io.realm.util.mongodb.CustomType +import org.bson.Document +import org.bson.codecs.configuration.CodecRegistries +import org.bson.types.ObjectId +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.* + +private const val SERVICE_NAME = "BackingDB" // it comes from the test server's BackingDB/config.json +private const val DATABASE_NAME = "test_data" // same as above +private const val COLLECTION_NAME = "test_data" + +@RunWith(AndroidJUnit4::class) +class MongoClientTest { + + private lateinit var app: TestApp + private lateinit var user: User + private lateinit var client: MongoClient + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestApp() + user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + client = user.getMongoClient(SERVICE_NAME) + } + + @After + fun tearDown() { + with(getCollectionInternal()) { + deleteMany(Document()).blockingGetResult() + } + + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun count() { + with(getCollectionInternal()) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document("hello", "world") + val doc1 = Document(rawDoc) + val doc2 = Document(rawDoc) + insertOne(doc1).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + insertOne(doc2).blockingGetResult() + assertEquals(2, count().blockingGetResult()) + + assertEquals(2, count(rawDoc).blockingGetResult()) + assertEquals(0, count(Document("hello", "Friend")).blockingGetResult()) + assertEquals(1, count(rawDoc, CountOptions().limit(1)).blockingGetResult()) + + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + count(Document("\$who", 1)).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("operator", true)) + } + } + } + + @Test + fun count_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + count(Document("\$who", 1)).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("operator", true)) + } + } + } + + @Test + fun findOne_nullResult() { + with(getCollectionInternal()) { + // Test findOne() on empty collection with no filter and no options + assertNull(findOne().blockingGetResult()) + + // Test findOne() with filter that does not match any documents and no options + assertNull(findOne(Document("hello", "worldDNE")).blockingGetResult()) + + val doc1 = Document("hello", "world1") + insertOne(doc1).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // Test findOne() with filter that does not match any documents and no options + assertNull(findOne(Document("hello", "worldDNE")).blockingGetResult()) + } + } + + @Test + fun findOne_singleDocument() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world1") + + // Insert one document + insertOne(doc1).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // No filter and no options + assertEquals(doc1, findOne().blockingGetResult()!!.withoutId()) + + // Projection (remove "_id") options + val projection = Document("hello", 1).apply { this["_id"] = 0 } + var options = FindOptions() + .limit(2) + .projection(projection) + assertEquals(doc1, findOne(Document(), options).blockingGetResult()!!) + + // Projection (remove "_id") and sort (by desc "hello") options + options = FindOptions() + .limit(2) + .projection(projection) + .sort(Document("hello", -1)) + assertEquals(doc1, findOne(Document(), options).blockingGetResult()!!) + } + } + + @Test + fun findOne_multipleDocuments() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world1") + val doc2 = Document("hello", "world2") + val doc3 = Document("hello", "world3") + + // Insert 3 documents + insertMany(listOf(doc1, doc2, doc3)).blockingGetResult() + assertEquals(3, count().blockingGetResult()) + + // Projection (remove "_id") and sort (by asc "hello") options + val projection = Document("hello", 1).apply { this["_id"] = 0 } + var options = FindOptions() + .limit(2) + .projection(projection) + .sort(Document("hello", 1)) + assertEquals(doc1, findOne(Document(), options).blockingGetResult()!!) + + // Projection (remove "_id") and sort (by desc "hello") options + options = FindOptions() + .limit(2) + .projection(projection) + .sort(Document("hello", -1)) + assertEquals(doc3, findOne(Document(), options).blockingGetResult()!!) + } + } + + @Test + fun findOne_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + findOne(Document("\$who", 1)).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("operator", true)) + } + } + } + + @Test + fun find() { + with(getCollectionInternal()) { + // Find on an empty collection returns false on hasNext and null on first + var iter = find() + assertFalse(iter.iterator().blockingGetResult()!!.hasNext()) + assertNull(iter.first().blockingGetResult()) + + val doc1 = Document("hello", "world") + val doc2 = Document("hello", "friend") + doc2["proj"] = "field" + insertMany(listOf(doc1, doc2)).blockingGetResult() + + // Iterate after inserting two documents + assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) + assertEquals(doc1, iter.first().blockingGetResult()!!.withoutId()) + + // Get next with sort by desc "_id" and limit to 1 document + assertEquals(doc2, + iter.limit(1) + .sort(Document("_id", -1)) + .iterator().blockingGetResult()!! + .next().withoutId()) + + // Find first document + iter = find(doc1) + assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) + assertEquals(doc1, + iter.iterator().blockingGetResult()!! + .next().withoutId()) + + // Find with filter for first document + iter = find().filter(doc1) + assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) + assertEquals(doc1, + iter.iterator().blockingGetResult()!! + .next().withoutId()) + + // Find with projection shows "proj" in result + val expected = Document("proj", "field") + assertEquals(expected, + find(doc2) + .projection(Document("proj", 1)) + .iterator().blockingGetResult()!! + .next().withoutId()) + + // Getting a new iterator returns first element on tryNext + val asyncIter = iter.iterator().blockingGetResult()!! + assertEquals(doc1, asyncIter.tryNext().withoutId()) + } + } + + @Test + fun find_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + find(Document("\$who", 1)).first().blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("operator", true)) + } + } + } + + @Test + fun aggregate() { + with(getCollectionInternal()) { + // Aggregate on an empty collection returns false on hasNext and null on first + var iter = aggregate(listOf()) + assertFalse(iter.iterator().blockingGetResult()!!.hasNext()) + assertNull(iter.first().blockingGetResult()) + + // Iterate after inserting two documents + val doc1 = Document("hello", "world") + val doc2 = Document("hello", "friend") + insertMany(listOf(doc1, doc2)).blockingGetResult() + assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) + assertEquals(doc1.withoutId(), iter.first().blockingGetResult()!!.withoutId()) + + // Aggregate with pipeline, sort by desc "_id" and limit to 1 document + iter = aggregate(listOf(Document("\$sort", Document("_id", -1)), Document("\$limit", 1))) + assertEquals(doc2.withoutId(), + iter.iterator().blockingGetResult()!! + .next().withoutId()) + + // Aggregate with pipeline, match first document + iter = aggregate(listOf(Document("\$match", doc1))) + assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) + assertEquals(doc1.withoutId(), iter.iterator().blockingGetResult()!!.next().withoutId()) + } + } + + @Test + fun aggregate_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + aggregate(listOf(Document("\$who", 1))).first().blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("pipeline", true)) + } + } + } + + @Test + fun insertOne() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } + assertEquals(doc1.getObjectId("_id"), insertOne(doc1).blockingGetResult()!!.insertedId.asObjectId().value) + assertEquals(1, count().blockingGetResult()) + + val doc2 = Document("hello", "world") + assertNotEquals(doc1.getObjectId("_id"), insertOne(doc2).blockingGetResult()!!.insertedId.asObjectId().value) + assertEquals(2, count().blockingGetResult()) + } + } + + @Test + fun insertOne_fails() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } + insertOne(doc1).blockingGetResult() + + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + insertOne(doc1).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("duplicate", true)) + } + } + } + + @Test + fun insertMany_singleDocument() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } + + assertEquals(doc1.getObjectId("_id"), + insertMany(listOf(doc1)).blockingGetResult()!!.insertedIds[0]!!.asObjectId().value) + val doc2 = Document("hello", "world") + + assertNotEquals(doc1.getObjectId("_id"), insertMany(listOf(doc2)).blockingGetResult()!!.insertedIds[0]!!.asObjectId().value) + + val doc3 = Document("one", "two") + val doc4 = Document("three", 4) + + insertMany(listOf(doc3, doc4)).blockingGetResult() + } + } + + @Test + fun insertMany_singleDocument_fails() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } + insertMany(listOf(doc1)).blockingGetResult() + + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + insertMany(listOf(doc1)).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("duplicate", true)) + } + } + } + + @Test + fun insertMany_multipleDocuments() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } + val doc2 = Document("hello", "world").apply { this["_id"] = ObjectId() } + val documents = listOf(doc1, doc2) + + insertMany(documents).blockingGetResult()!! + .insertedIds + .forEach { entry -> + assertEquals(documents[entry.key.toInt()]["_id"], entry.value.asObjectId().value) + } + + val doc3 = Document("one", "two") + val doc4 = Document("three", 4) + + insertMany(listOf(doc3, doc4)).blockingGetResult() + assertEquals(4, count().blockingGetResult()) + } + } + + @Test + fun insertMany_multipleDocuments_fails() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } + val doc2 = Document("hello", "world").apply { this["_id"] = ObjectId() } + val documents = listOf(doc1, doc2) + insertMany(documents).blockingGetResult() + + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + insertMany(documents).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("duplicate", true)) + } + } + } + + @Test + fun deleteOne_singleDocument() { + with(getCollectionInternal()) { + assertEquals(0, deleteOne(Document()).blockingGetResult()!!.deletedCount) + assertEquals(0, deleteOne(Document("hello", "world")).blockingGetResult()!!.deletedCount) + + val doc1 = Document("hello", "world") + + insertOne(doc1).blockingGetResult() + assertEquals(1, deleteOne(doc1).blockingGetResult()!!.deletedCount) + assertEquals(0, count().blockingGetResult()) + } + } + + @Test + fun deleteOne_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + deleteOne(Document("\$who", 1)).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("operator", true)) + } + } + } + + @Test + fun deleteOne_multipleDocuments() { + with(getCollectionInternal()) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document("hello", "world") + val doc1 = Document(rawDoc) + val doc1b = Document(rawDoc) + val doc2 = Document("foo", "bar") + val doc3 = Document("42", "666") + insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() + assertEquals(1, deleteOne(rawDoc).blockingGetResult()!!.deletedCount) + assertEquals(1, deleteOne(Document()).blockingGetResult()!!.deletedCount) + assertEquals(2, count().blockingGetResult()) + } + } + + @Test + fun deleteMany_singleDocument() { + with(getCollectionInternal()) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document("hello", "world") + val doc1 = Document(rawDoc) + + insertOne(doc1).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + assertEquals(1, deleteMany(doc1).blockingGetResult()!!.deletedCount) + assertEquals(0, count().blockingGetResult()) + } + } + + @Test + fun deleteMany_multipleDocuments() { + with(getCollectionInternal()) { + assertEquals(0, count().blockingGetResult()) + + val rawDoc = Document("hello", "world") + val doc1 = Document(rawDoc) + val doc1b = Document(rawDoc) + val doc2 = Document("foo", "bar") + val doc3 = Document("42", "666") + insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() + assertEquals(2, deleteMany(rawDoc).blockingGetResult()!!.deletedCount) // two docs will be deleted + assertEquals(2, count().blockingGetResult()) // two docs still present + assertEquals(2, deleteMany(Document()).blockingGetResult()!!.deletedCount) // delete all + assertEquals(0, count().blockingGetResult()) + + insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() + assertEquals(4, deleteMany(Document()).blockingGetResult()!!.deletedCount) // delete all + assertEquals(0, count().blockingGetResult()) + } + } + + @Test + fun deleteMany_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + deleteMany(Document("\$who", 1)).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("operator", true)) + } + } + } + + @Test + fun updateOne_emptyCollection() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world") + + // Update on an empty collection + updateOne(Document(), doc1) + .blockingGetResult()!! + .let { + assertEquals(0, it.matchedCount) + assertEquals(0, it.modifiedCount) + assertNull(it.upsertedId) + } + + // Update on an empty collection adding some values + val doc2 = Document("\$set", Document("woof", "meow")) + updateOne(Document(), doc2) + .blockingGetResult()!! + .let { + assertEquals(0, it.matchedCount) + assertEquals(0, it.modifiedCount) + assertNull(it.upsertedId) + assertEquals(0, count().blockingGetResult()) + } + } + } + + @Test + fun updateOne_emptyCollectionWithUpsert() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world") + + // Update on empty collection with upsert + val options = UpdateOptions().upsert(true) + updateOne(Document(), doc1, options) + .blockingGetResult()!! + .let { + assertEquals(0, it.matchedCount) + assertEquals(0, it.modifiedCount) + assertFalse(it.upsertedId!!.isNull) + } + assertEquals(1, count().blockingGetResult()) + + assertEquals(doc1, find(Document()).first().blockingGetResult()!!.withoutId()) + } + } + + @Test + fun updateOne_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + updateOne(Document("\$who", 1), Document()).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("operator", true)) + } + } + } + + @Test + fun updateMany_emptyCollection() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world") + + // Update on empty collection + updateMany(Document(), doc1) + .blockingGetResult()!! + .let { + assertEquals(0, it.matchedCount) + assertEquals(0, it.modifiedCount) + assertNull(it.upsertedId) + } + assertEquals(0, count().blockingGetResult()) + } + } + + @Test + fun updateMany_emptyCollectionWithUpsert() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world") + + // Update on empty collection with upsert + updateMany(Document(), doc1, UpdateOptions().upsert(true)) + .blockingGetResult()!! + .let { + assertEquals(0, it.matchedCount) + assertEquals(0, it.modifiedCount) + assertNotNull(it.upsertedId) + } + assertEquals(1, count().blockingGetResult()) + + // Add new value using update + val update = Document("woof", "meow") + updateMany(Document(), Document("\$set", update)) + .blockingGetResult()!! + .let { + assertEquals(1, it.matchedCount) + assertEquals(1, it.modifiedCount) + assertNull(it.upsertedId) + } + assertEquals(1, count().blockingGetResult()) + val expected = Document(doc1).apply { this["woof"] = "meow" } + assertEquals(expected, find().first().blockingGetResult()!!.withoutId()) + + // Insert empty document, add ["woof", "meow"] to it and check it worked + insertOne(Document()).blockingGetResult() + updateMany(Document(), Document("\$set", update)) + .blockingGetResult()!! + .let { + assertEquals(2, it.matchedCount) + assertEquals(2, it.modifiedCount) + } + assertEquals(2, count().blockingGetResult()) + find().iterator() + .blockingGetResult()!! + .let { + assertEquals(expected, it.next().withoutId()) + assertEquals(update, it.next().withoutId()) + assertFalse(it.hasNext()) + } + } + } + + @Test + fun updateMany_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + updateMany(Document("\$who", 1), Document()).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("operator", true)) + } + } + } + + @Test + fun findOneAndUpdate_emptyCollection() { + with(getCollectionInternal()) { + // Test null return format + assertNull(findOneAndUpdate(Document(), Document()).blockingGetResult()) + } + } + + @Test + fun findOneAndUpdate_noUpdates() { + with(getCollectionInternal()) { + assertNull(findOneAndUpdate(Document(), Document()).blockingGetResult()) + assertEquals(0, count().blockingGetResult()) + } + } + + @Test + fun findOneAndUpdate_noUpsert() { + with(getCollectionInternal()) { + val sampleDoc = Document("hello", "world1") + sampleDoc["num"] = 2 + + // Insert a sample Document + insertOne(sampleDoc).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // Sample call to findOneAndUpdate() where we get the previous document back + val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { + this["\$inc"] = Document("num", 1) + } + findOneAndUpdate(Document("hello", "world1"), sampleUpdate) + .blockingGetResult()!! + .withoutId() + .let { + assertEquals(sampleDoc.withoutId(), it) + } + assertEquals(1, count().blockingGetResult()) + + // Make sure the update took place + val expectedDoc = Document("hello", "hellothere") + expectedDoc["num"] = 3 + assertEquals(expectedDoc.withoutId(), find().first().blockingGetResult()!!.withoutId()) + assertEquals(1, count().blockingGetResult()) + + // Call findOneAndUpdate() again but get the new document + sampleUpdate.remove("\$set") + expectedDoc["num"] = 4 + val options = FindOneAndModifyOptions() + .returnNewDocument(true) + findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, options) + .blockingGetResult()!! + .withoutId() + .let { + assertEquals(expectedDoc.withoutId(), it) + } + assertEquals(1, count().blockingGetResult()) + + // Test null behaviour again with a filter that should not match any documents + assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).blockingGetResult()) + assertEquals(1, count().blockingGetResult()) + } + } + + @Test + fun findOneAndUpdate_upsert() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world1").apply { this["num"] = 1 } + val doc2 = Document("hello", "world2").apply { this["num"] = 2 } + val doc3 = Document("hello", "world3").apply { this["num"] = 3 } + + val filter = Document("hello", "hellothere") + + // Test the upsert option where it should not actually be invoked + var options = FindOneAndModifyOptions() + .returnNewDocument(true) + .upsert(true) + val update1 = Document("\$set", doc1) + assertEquals(doc1, + findOneAndUpdate(filter, update1, options) + .blockingGetResult()!! + .withoutId()) + assertEquals(1, count().blockingGetResult()) + assertEquals(doc1.withoutId(), + find().first() + .blockingGetResult()!! + .withoutId()) + + // Test the upsert option where the server should perform upsert and return new document + val update2 = Document("\$set", doc2) + assertEquals(doc2, + findOneAndUpdate(filter, update2, options) + .blockingGetResult()!! + .withoutId()) + assertEquals(2, count().blockingGetResult()) + + // Test the upsert option where the server should perform upsert and return old document + // The old document should be empty + options = FindOneAndModifyOptions() + .upsert(true) + val update = Document("\$set", doc3) + assertNull(findOneAndUpdate(filter, update, options).blockingGetResult()) + assertEquals(3, count().blockingGetResult()) + } + } + + // FIXME: projections and sorts aren't currently working due to a bug in Stitch: https://jira.mongodb.org/browse/REALMC-5787 + @Test + @Ignore("Projections and sorts don't work") + fun findOneAndUpdate_withProjectionAndSort() { + with(getCollectionInternal()) { + val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { + this["\$inc"] = Document("num", 1) + } + sampleUpdate.remove("\$set") // FIXME + val sampleProject = Document("hello", 1) + sampleProject["_id"] = 0 + + var options = FindOneAndModifyOptions() + .projection(sampleProject) + .sort(Document("num", 1)) + assertEquals(Document("hello", "world1"), + findOneAndUpdate(Document(), sampleUpdate, options) + .blockingGetResult()!! + .withoutId()) + assertEquals(3, count().blockingGetResult()) + + options = FindOneAndModifyOptions() + .projection(sampleProject) + .sort(Document("num", -1)) + assertEquals(Document("hello", "world3"), + findOneAndUpdate(Document(), sampleUpdate, options) + .blockingGetResult()!! + .withoutId()) + assertEquals(3, count().blockingGetResult()) + } + } + + @Test + fun findOneAndUpdate_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + findOneAndUpdate(Document(), Document("\$who", 1)).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("modifier", true)) + } + + assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { + findOneAndUpdate(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).blockingGetResult() + }.also { e -> + assertTrue(e.errorMessage!!.contains("modifier", true)) + } + } + } + + @Test + fun findOneAndReplace_noUpdates() { + with(getCollectionInternal()) { + // Test null behaviour again with a filter that should not match any documents + assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).blockingGetResult()) + assertEquals(0, count().blockingGetResult()) + assertNull(findOneAndReplace(Document(), Document()).blockingGetResult()) + assertEquals(0, count().blockingGetResult()) + } + } + + @Test + fun findOneAndReplace_noUpsert() { + with(getCollectionInternal()) { + val sampleDoc = Document("hello", "world1").apply { this["num"] = 2 } + + // Insert a sample Document + insertOne(sampleDoc).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // Sample call to findOneAndReplace() where we get the previous document back + var sampleUpdate = Document("hello", "world2").apply { this["num"] = 2 } + assertEquals(sampleDoc.withoutId(), + findOneAndReplace(Document("hello", "world1"), sampleUpdate).blockingGetResult()!!.withoutId()) + assertEquals(1, count().blockingGetResult()) + + // Make sure the update took place + val expectedDoc = Document("hello", "world2").apply { this["num"] = 2 } + assertEquals(expectedDoc.withoutId(), find().first().blockingGetResult()!!.withoutId()) + assertEquals(1, count().blockingGetResult()) + + // Call findOneAndReplace() again but get the new document + sampleUpdate = Document("hello", "world3").apply { this["num"] = 3 } + val options = FindOneAndModifyOptions().returnNewDocument(true) + assertEquals(sampleUpdate.withoutId(), + findOneAndReplace(Document(), sampleUpdate, options).blockingGetResult()!!.withoutId()) + assertEquals(1, count().blockingGetResult()) + + // Test null behaviour again with a filter that should not match any documents + assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).blockingGetResult()) + assertEquals(1, count().blockingGetResult()) + } + } + + @Test + fun findOneAndReplace_upsert() { + with(getCollectionInternal()) { + val doc4 = Document("hello", "world4").apply { this["num"] = 4 } + val doc5 = Document("hello", "world5").apply { this["num"] = 5 } + val doc6 = Document("hello", "world6").apply { this["num"] = 6 } + + // Test the upsert option where it should not actually be invoked + val sampleUpdate = Document("hello", "world4").apply { this["num"] = 4 } + var options = FindOneAndModifyOptions() + .returnNewDocument(true) + .upsert(true) + assertEquals(doc4.withoutId(), + findOneAndReplace(Document("hello", "world3"), doc4, options) + .blockingGetResult()!! + .withoutId()) + assertEquals(1, count().blockingGetResult()) + assertEquals(doc4.withoutId(), find().first().blockingGetResult()!!.withoutId()) + + // Test the upsert option where the server should perform upsert and return new document + options = FindOneAndModifyOptions().returnNewDocument(true).upsert(true) + assertEquals(doc5.withoutId(), findOneAndReplace(Document("hello", "hellothere"), doc5, options).blockingGetResult()!!.withoutId()) + assertEquals(2, count().blockingGetResult()) + + // Test the upsert option where the server should perform upsert and return old document + // The old document should be empty + options = FindOneAndModifyOptions().upsert(true) + assertNull(findOneAndReplace(Document("hello", "hellothere"), doc6, options).blockingGetResult()) + assertEquals(3, count().blockingGetResult()) + } + } + + // FIXME: projections and sorts aren't currently working due to a bug in Stitch: https://jira.mongodb.org/browse/REALMC-5787 + @Test + @Ignore("Projections and sorts don't work") + fun findOneAndReplace_withProjectionAndSort() { + with(getCollectionInternal()) { + val sampleProject = Document("hello", 1) + sampleProject["_id"] = 0 + + val sampleUpdate = Document("hello", "world0") + sampleUpdate["num"] = 0 + + var options = FindOneAndModifyOptions().projection(sampleProject).sort(Document("num", 1)) + val result = findOneAndReplace(Document(), sampleUpdate, options).blockingGetResult() + assertEquals(Document("hello", "world4"), result!!.withoutId()) + assertEquals(3, count().blockingGetResult()) + + options = FindOneAndModifyOptions() + .projection(sampleProject) + .sort(Document("num", -1)) + assertEquals(Document("hello", "world6"), + findOneAndReplace(Document(), sampleUpdate, options).blockingGetResult()!!.withoutId()) + assertEquals(3, count().blockingGetResult()) + } + } + + @Test + fun findOneAndReplace_fails() { + with(getCollectionInternal()) { + assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { + findOneAndReplace(Document(), Document("\$who", 1)).blockingGetResult() + } + + assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { + findOneAndReplace(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).blockingGetResult() + } + } + } + + @Test + fun findOneAndDelete() { + with(getCollectionInternal()) { + val sampleDoc = Document("hello", "world1").apply { this["num"] = 1 } + + // Collection should start out empty + // This also tests the null return format + assertNull(findOneAndDelete(Document()).blockingGetResult()) + + // Insert a sample Document + insertOne(sampleDoc).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // Sample call to findOneAndDelete() where we delete the only doc in the collection + assertEquals(sampleDoc.withoutId(), + findOneAndDelete(Document()).blockingGetResult()!!.withoutId()) + + // There should be no documents in the collection now + assertEquals(0, count().blockingGetResult()) + + // Insert a sample Document + insertOne(sampleDoc).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // Call findOneAndDelete() again but this time with a filter + assertEquals(sampleDoc.withoutId(), + findOneAndDelete(Document("hello", "world1")).blockingGetResult()!!.withoutId()) + + // There should be no documents in the collection now + assertEquals(0, count().blockingGetResult()) + + // Insert a sample Document + insertOne(sampleDoc).blockingGetResult() + assertEquals(1, count().blockingGetResult()) + + // Test null behaviour again with a filter that should not match any documents + assertNull(findOneAndDelete(Document("hello", "zzzzz")).blockingGetResult()) + assertEquals(1, count().blockingGetResult()) + + val doc2 = Document("hello", "world2").apply { this["num"] = 2 } + val doc3 = Document("hello", "world3").apply { this["num"] = 3 } + + // Insert new documents + insertMany(listOf(doc2, doc3)).blockingGetResult() + assertEquals(3, count().blockingGetResult()) + } + } + + // FIXME: projections and sorts aren't currently working due to a bug in Stitch: https://jira.mongodb.org/browse/REALMC-5787 + @Test + @Ignore("find_one_and_delete function is wrongly implemented in OS and projections and sorts don't work") + fun findOneAndDelete_withProjectionAndSort() { + with(getCollectionInternal()) { + val doc2 = Document("hello", "world2").apply { this["num"] = 2 } + val doc3 = Document("hello", "world3").apply { this["num"] = 3 } + + insertMany(listOf(doc2, doc3)).blockingGetResult() + + // Return "hello", hide "_id" + val sampleProject = Document("hello", 1).apply { this["_id"] = 0 } + + var options = FindOneAndModifyOptions() + .projection(sampleProject) + .sort(Document("num", -1)) + assertEquals(Document("hello", "world3"), + findOneAndDelete(Document(), options).blockingGetResult()!!.withoutId()) + assertEquals(2, count().blockingGetResult()) + + options = FindOneAndModifyOptions() + .projection(sampleProject) + .sort(Document("num", 1)) + assertEquals(Document("hello", "world1"), + findOneAndDelete(Document(), options).blockingGetResult()!!.withoutId()) + assertEquals(1, count().blockingGetResult()) + } + } + + @Test + fun withDocument() { + // aAd default codecs as they too are needed for proper collection initialization + val expandedCodecRegistry = CodecRegistries + .fromRegistries(AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY, + CodecRegistries.fromCodecs(CustomType.Codec())) + + val expected = CustomType(ObjectId(), 42) + + // Get default collection + with(getCollectionInternal()) { + // Now specify custom class + var coll = withDocumentClass(CustomType::class.java) + assertEquals(CustomType::class.java, coll.documentClass) + + assertFailsWith(ObjectServerError::class) { + coll.insertOne(expected).blockingGetResult() + } + + val defaultCodecRegistry = AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY + assertEquals(defaultCodecRegistry, coll.codecRegistry) + + // Use expanded registry + coll = coll.withCodecRegistry(expandedCodecRegistry) + assertEquals(expected.id, + coll.insertOne(expected).blockingGetResult()!!.insertedId.asObjectId().value) + assertEquals(expected, coll.find().first().blockingGetResult()) + } + + val expected2 = CustomType(null, 42) + + // Now get new collection for CustomType + with(getCollectionInternal(CustomType::class.java) + .withCodecRegistry(expandedCodecRegistry)) { + insertOne(expected2).blockingGetResult()!! + val actual: CustomType = find().first().blockingGetResult()!! + assertEquals(expected2.intValue, actual.intValue) + } + + with(getCollectionInternal(CustomType::class.java) + .withCodecRegistry(expandedCodecRegistry)) { + val actual: CustomType = find(Document(), CustomType::class.java) + .first() + .blockingGetResult()!! + assertEquals(expected2.intValue, actual.intValue) + assertNotNull(expected.id) + + val iter = aggregate(listOf(Document("\$match", Document())), CustomType::class.java) + assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) + assertEquals(expected, iter.iterator().blockingGetResult()!!.next()) + } + } + + private fun getCollectionInternal(): MongoCollection { + return client.getDatabase(DATABASE_NAME).let { + assertEquals(it.name, DATABASE_NAME) + it.getCollection(COLLECTION_NAME).also { collection -> + assertEquals(MongoNamespace(DATABASE_NAME, it.name), collection.namespace) + } + } + } + + private fun getCollectionInternal( + resultClass: Class + ): MongoCollection { + return client.getDatabase(DATABASE_NAME).let { + assertEquals(it.name, DATABASE_NAME) + it.getCollection(COLLECTION_NAME, resultClass).also { collection -> + assertEquals(MongoNamespace(DATABASE_NAME, it.name), collection.namespace) + } + } + } + + private fun Document.withId(objectId: ObjectId? = null): Document { + return apply { this["_id"] = objectId ?: ObjectId() } + } + + private fun Document.withoutId(): Document { + return apply { remove("_id") } + } + + private fun List.withoutIds(): List { + return apply { map { it.withoutId() } } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt deleted file mode 100644 index d2b0d55c9f..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoCollectionTest.kt +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ -package io.realm.mongodb - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import io.realm.* -import io.realm.mongodb.mongo.MongoClient -import io.realm.mongodb.mongo.MongoCollection -import io.realm.mongodb.mongo.MongoDatabase -import io.realm.mongodb.mongo.options.CountOptions -import io.realm.mongodb.mongo.options.FindOneAndModifyOptions -import io.realm.mongodb.mongo.options.UpdateOptions -import io.realm.util.blockingGetResult -import org.bson.Document -import org.bson.types.ObjectId -import org.junit.After -import org.junit.Before -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import kotlin.test.* - -private const val SERVICE_NAME = "BackingDB" // it comes from the test server's BackingDB/config.json -private const val DATABASE_NAME = "test_data" // same as above -private const val COLLECTION_NAME = "COLLECTION_NAME" -private const val KEY_1 = "KEY" -private const val VALUE_1 = "666" - -@RunWith(AndroidJUnit4::class) -class MongoCollectionTest { - - private lateinit var app: TestApp - private lateinit var user: User - private lateinit var client: MongoClient - private lateinit var database: MongoDatabase - - @Before - fun setUp() { - Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestApp() - user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - client = user.getMongoClient(SERVICE_NAME) - database = client.getDatabase(DATABASE_NAME) - } - - @After - fun tearDown() { - // FIXME: probably not the best way to "reset" the state - with(getCollectionInternal(COLLECTION_NAME)) { - deleteMany(Document()).blockingGetResult() - } - - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun insertOne() { - with(getCollectionInternal(COLLECTION_NAME)) { - assertEquals(0, count().blockingGetResult()) - val doc1 = Document(mapOf("hello_1" to "1", "hello_2" to "2")) - insertOne(doc1).blockingGetResult() - assertEquals(1, count().blockingGetResult()) - - // FIXME: revisit when parser is fully operational -// val doc2 = Document("hello", "world") -// doc2["_id"] = ObjectId() -// -// assertEquals(doc2.getObjectId("_id"), insertOne(doc2).blockingGetResult()!!.insertedId.asObjectId().value) -// assertFailsWith(ObjectServerError::class) { insertOne(doc2).blockingGetResult() } -// -// val doc3 = Document("hello", "world") -// assertNotEquals(doc2.getObjectId("_id"), insertOne(doc3).blockingGetResult()!!.insertedId.asObjectId().value) - } - } - - @Test - fun insertMany() { - with(getCollectionInternal(COLLECTION_NAME)) { - assertEquals(0, count().blockingGetResult()) - - val rawDoc = Document(KEY_1, VALUE_1) - val doc1 = Document(rawDoc) - val doc2 = Document(rawDoc) - val doc3 = Document(rawDoc) - val doc4 = Document("foo", "bar") - val manyDocuments = listOf(doc1, doc2, doc3, doc4) - - insertMany(manyDocuments) - .blockingGetResult() - .let { assertEquals(manyDocuments.size, it!!.insertedIds.size) } - - assertEquals(manyDocuments.size.toLong(), count().blockingGetResult()) - assertEquals(3, count(rawDoc).blockingGetResult()) - assertEquals(1, count(Document("foo", "bar")).blockingGetResult()) - assertEquals(0, count(Document("bar", "foo")).blockingGetResult()) - } - } - - @Test - fun count() { - with(getCollectionInternal(COLLECTION_NAME)) { - assertEquals(0, count().blockingGetResult()) - - val rawDoc = Document("hello", "world") - val doc1 = Document(rawDoc) - val doc2 = Document(rawDoc) - insertOne(doc1).blockingGetResult() - assertEquals(1, count().blockingGetResult()) - insertOne(doc2).blockingGetResult() - assertEquals(2, count().blockingGetResult()) - - assertEquals(2, count(rawDoc).blockingGetResult()) - assertEquals(0, count(Document("hello", "Friend")).blockingGetResult()) - assertEquals(1,count(rawDoc, CountOptions().limit(1)).blockingGetResult()) - - // FIXME: investigate error handling for malformed payloads -// try { -// count(Document("\$who", 1)).blockingGetResult() -// Assert.fail() -// } catch (ex: ExecutionException) { -// // FIXME: add assertion -// val a = 0 -// } - } - } - - @Test - fun deleteOne_singleDocument() { - with(getCollectionInternal(COLLECTION_NAME)) { - assertEquals(0, count().blockingGetResult()) - - val rawDoc = Document(KEY_1, VALUE_1) - val doc1 = Document(rawDoc) - - insertOne(doc1).blockingGetResult() - assertEquals(1, count().blockingGetResult()) - assertEquals(1, deleteOne(doc1).blockingGetResult()!!.deletedCount) - assertEquals(0, count().blockingGetResult()) - } - } - - @Test - fun deleteOne_listOfDocuments() { - with(getCollectionInternal(COLLECTION_NAME)) { - assertEquals(0, count().blockingGetResult()) - - val rawDoc = Document(KEY_1, VALUE_1) - val doc1 = Document(rawDoc) - val doc1b = Document(rawDoc) - val doc2 = Document("foo", "bar") - val doc3 = Document("42", "666") - insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() - assertEquals(1, deleteOne(rawDoc).blockingGetResult()!!.deletedCount) - assertEquals(1, deleteOne(Document()).blockingGetResult()!!.deletedCount) - } - } - - @Test - fun deleteMany_singleDocument() { - with(getCollectionInternal(COLLECTION_NAME)) { - assertEquals(0, count().blockingGetResult()) - - val rawDoc = Document(KEY_1, VALUE_1) - val doc1 = Document(rawDoc) - - insertOne(doc1).blockingGetResult() - assertEquals(1, count().blockingGetResult()) - assertEquals(1, deleteMany(doc1).blockingGetResult()!!.deletedCount) - assertEquals(0, count().blockingGetResult()) - } - } - - @Test - fun deleteMany_listOfDocuments() { - with(getCollectionInternal(COLLECTION_NAME)) { - assertEquals(0, count().blockingGetResult()) - - val rawDoc = Document(KEY_1, VALUE_1) - val doc1 = Document(rawDoc) - val doc1b = Document(rawDoc) - val doc2 = Document("foo", "bar") - val doc3 = Document("42", "666") - insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() - assertEquals(2, deleteMany(rawDoc).blockingGetResult()!!.deletedCount) // two docs will be deleted - assertEquals(2, count().blockingGetResult()) // two docs still present - assertEquals(2, deleteMany(Document()).blockingGetResult()!!.deletedCount) // delete all - assertEquals(0, count().blockingGetResult()) - - insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() - assertEquals(4, deleteMany(Document()).blockingGetResult()!!.deletedCount) // delete all - assertEquals(0, count().blockingGetResult()) - } - } - - @Test - fun findOne() { - with(getCollectionInternal(COLLECTION_NAME)) { - val doc1 = Document("hello", "world1") - val doc2 = Document("hello", "world2") - val doc3 = Document("hello", "world3") - - // Test findOne() on empty collection with no filter and no options - assertNull(findOne().blockingGetResult()) - - // Insert a document into the collection - insertOne(doc1).blockingGetResult() - assertEquals(1, count().blockingGetResult()) - - // Test findOne() with no filter and no options - assertEquals(doc1, findOne().blockingGetResult()!!.withoutId()) - - // Test findOne() with filter that does not match any documents and no options - assertNull(findOne(Document("hello", "worldDNE")).blockingGetResult()) - - // FIXME: revisit when parser is fully operational - // Insert 2 more documents into the collection -// insertMany(listOf(doc2, doc3)).blockingGetResult() // use insertOne for now - insertOne(doc2).blockingGetResult() - insertOne(doc3).blockingGetResult() - assertEquals(3, count().blockingGetResult()) - -// // test findOne() with projection and sort options -// val projection = Document("hello", 1) -// projection["_id"] = 0 -// val options1 = FindOptions() -// .limit(2) -// .projection(projection) -// .sort(Document("hello", 1)) -// assertEquals(doc1, findOne(Document(), options1).blockingGetResult()!!.withoutId()) -// -// val options2 = FindOptions() -// .limit(2) -// .projection(projection) -// .sort(Document("hello", -1)) -// assertEquals(doc3.withoutId(), findOne(Document(), options2).blockingGetResult()!!.withoutId()) -// -// // test findOne() properly fails -// try { -// Tasks.await(coll.findOne(Document("\$who", 1))) -// Assert.fail() -// } catch (ex: ExecutionException) { -// Assert.assertTrue(ex.cause is StitchServiceException) -// val svcEx = ex.cause as StitchServiceException -// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) -// } - } - } - - @Test - fun updateOne() { - with(getCollectionInternal(COLLECTION_NAME)) { - val doc1 = Document("hello", "world") - val result1 = updateOne(Document(), doc1).blockingGetResult()!! - assertEquals(0, result1.matchedCount) - assertEquals(0, result1.modifiedCount) - assertNull(result1.upsertedId) - - val options2 = UpdateOptions().upsert(true) - val result2 = updateOne(Document(), doc1, options2).blockingGetResult()!! - assertEquals(0, result2.matchedCount) - assertEquals(0, result2.modifiedCount) - assertFalse(result2.upsertedId!!.isNull) - - val result3 = updateOne(Document(), Document("\$set", Document("woof", "meow"))).blockingGetResult()!! - assertEquals(1, result3.matchedCount) - assertEquals(1, result3.modifiedCount) - assertNull(result3.upsertedId) - - // FIXME: revisit when parser is fully operational -// val expectedDoc = Document("hello", "world") -// expectedDoc["woof"] = "meow" -// assertEquals(expectedDoc, withoutId(Tasks.await(coll.find(Document()).first()))) -// -// try { -// Tasks.await(coll.updateOne(Document("\$who", 1), Document())) -// fail() -// } catch (ex: ExecutionException) { -// assertTrue(ex.cause is StitchServiceException) -// val svcEx = ex.cause as StitchServiceException -// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) -// } - } - } - - @Test - fun updateMany() { - with(getCollectionInternal(COLLECTION_NAME)) { - val doc1 = Document("hello", "world") - val result1 = updateMany(Document(), doc1).blockingGetResult()!! - assertEquals(0, result1.matchedCount) - assertEquals(0, result1.modifiedCount) - assertNull(result1.upsertedId) - - val options2 = UpdateOptions().upsert(true) - val result2 = updateMany(Document(), doc1, options2).blockingGetResult()!! - assertEquals(0, result2.matchedCount) - assertEquals(0, result2.modifiedCount) - assertNotNull(result2.upsertedId) - - val result3 = updateMany(Document(), Document("\$set", Document("woof", "meow"))).blockingGetResult()!! - assertEquals(1, result3.matchedCount) - assertEquals(1, result3.modifiedCount) - assertNull(result3.upsertedId) - - insertOne(Document()).blockingGetResult() - val result4 = updateMany(Document(), Document("\$set", Document("woof", "meow"))).blockingGetResult()!! - assertEquals(2, result4.matchedCount) - assertEquals(2, result4.modifiedCount) - - // FIXME: revisit when parser is fully operational -// val expectedDoc1 = Document("hello", "world") -// expectedDoc1["woof"] = "meow" -// val expectedDoc2 = Document("woof", "meow") -// assertEquals(listOf(expectedDoc1, expectedDoc2), withoutIds(Tasks.await>(coll.find(Document()).into(mutableListOf())))) -// -// try { -// Tasks.await(coll.updateMany(Document("\$who", 1), Document())) -// fail() -// } catch (ex: ExecutionException) { -// assertTrue(ex.cause is StitchServiceException) -// val svcEx = ex.cause as StitchServiceException -// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) -// } - } - } - - @Test - @Ignore - // FIXME: revisit when parser is fully operational - fun findOneAndUpdate() { - with(getCollectionInternal(COLLECTION_NAME)) { - val sampleDoc = Document("hello", "world1") - sampleDoc["num"] = 2 - - // Collection should start out empty - // This also tests the null return format - assertNull(findOneAndUpdate(Document(), Document()).blockingGetResult()) - - // Insert a sample Document - insertOne(sampleDoc).blockingGetResult() - assertEquals(1, count().blockingGetResult()) - - // Sample call to findOneAndUpdate() where we get the previous document back - val sampleUpdate = Document("\$set", Document("hello", "hellothere")) - sampleUpdate["\$inc"] = Document("num", 1) - assertEquals(sampleDoc.withoutId(), findOneAndUpdate(Document("hello", "world1"), sampleUpdate).blockingGetResult()) - assertEquals(1, count().blockingGetResult()) - - // Make sure the update took place - val expectedDoc = Document("hello", "hellothere") - expectedDoc["num"] = 3 -// assertEquals(expectedDoc.withoutId(), withoutId(Tasks.await(coll.find().first()))) - assertEquals(1, count().blockingGetResult()) - - // Call findOneAndUpdate() again but get the new document - sampleUpdate.remove("\$set") - expectedDoc["num"] = 4 - val result = findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, FindOneAndModifyOptions().returnNewDocument(true)).blockingGetResult() - assertEquals(expectedDoc.withoutId(), result!!.withoutId()) - assertEquals(1, count().blockingGetResult()) - - // Test null behaviour again with a filter that should not match any documents - assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).blockingGetResult()) - assertEquals(1, count().blockingGetResult()) - - val doc1 = Document("hello", "world1") - doc1["num"] = 1 - - val doc2 = Document("hello", "world2") - doc2["num"] = 2 - - val doc3 = Document("hello", "world3") - doc3["num"] = 3 - - // Test the upsert option where it should not actually be invoked - val result2 = findOneAndUpdate(Document("hello", "hellothere"), Document("\$set", doc1), FindOneAndModifyOptions().returnNewDocument(true).upsert(true)).blockingGetResult() - assertEquals(doc1, result2!!.withoutId()) - assertEquals(1, count().blockingGetResult()) -// assertEquals(doc1.withoutId(), withoutId(Tasks.await(coll.find().first()))) - - // Test the upsert option where the server should perform upsert and return new document - val result3 = findOneAndUpdate(Document("hello", "hellothere"), Document("\$set", doc2), FindOneAndModifyOptions().returnNewDocument(true).upsert(true)).blockingGetResult() - assertEquals(doc2, result3!!.withoutId()) - assertEquals(2, count().blockingGetResult()) - - // Test the upsert option where the server should perform upsert and return old document - // The old document should be empty - val result4 = findOneAndUpdate(Document("hello", "hellothere"), Document("\$set", doc3), FindOneAndModifyOptions().upsert(true)).blockingGetResult() - assertNull(result4) - assertEquals(3, count().blockingGetResult()) - - // Test sort and project -// assertEquals(listOf(doc1, doc2, doc3), -// withoutIds(Tasks.await>(coll.find().into(mutableListOf())))) - - val sampleProject = Document("hello", 1) - sampleProject["_id"] = 0 - - val result5 = findOneAndUpdate(Document(), sampleUpdate, FindOneAndModifyOptions().projection(sampleProject).sort(Document("num", 1))).blockingGetResult() - assertEquals(Document("hello", "world1"), result5!!.withoutId()) - assertEquals(3, count().blockingGetResult()) - - val result6 = findOneAndUpdate(Document(), sampleUpdate, FindOneAndModifyOptions().projection(sampleProject).sort(Document("num", -1))).blockingGetResult() - assertEquals(Document("hello", "world3"), result6!!.withoutId()) - assertEquals(3, count().blockingGetResult()) - - // Test proper failure -// try { -// Tasks.await(coll.findOneAndUpdate(Document(), Document("\$who", 1))) -// fail() -// } catch (ex: ExecutionException) { -// assertTrue(ex.cause is StitchServiceException) -// val svcEx = ex.cause as StitchServiceException -// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) -// } -// -// try { -// Tasks.await(coll.findOneAndUpdate(Document(), Document("\$who", 1), -// FindOneAndModifyOptions().upsert(true))) -// fail() -// } catch (ex: ExecutionException) { -// assertTrue(ex.cause is StitchServiceException) -// val svcEx = ex.cause as StitchServiceException -// assertEquals(StitchServiceErrorCode.MONGODB_ERROR, svcEx.errorCode) -// } - } - } - - @Test - fun find() { - with(getCollectionInternal(COLLECTION_NAME)) { - // FIXME: fix find implementation - ignore this code for code review - val iter = find().blockingGetResult()!! - assertFalse(iter.iterator().hasNext()) - assertFailsWith { iter.first() } - } - } - - // FIXME: more to come - - private fun getCollectionInternal(collectionName: String, javaClass: Class? = null): MongoCollection { - return when (javaClass) { - null -> database.getCollection(collectionName) - else -> database.getCollection(collectionName, javaClass) - } - } - - private fun Document.withId(objectId: ObjectId? = null): Document { - return apply { this["_id"] = objectId ?: ObjectId() } - } - - private fun Document.withoutId(): Document { - return apply { remove("_id") } - } -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt index ede4c5d1c9..343266315d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt @@ -4,39 +4,47 @@ import android.util.ArraySet import io.realm.mongodb.ErrorCode import io.realm.mongodb.ObjectServerError import org.hamcrest.Matcher -import org.junit.Assert.* +import org.hamcrest.MatcherAssert.assertThat import org.junit.rules.ErrorCollector import java.io.Closeable +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.fail // Helper methods for improving Kotlin unit tests. /** * Verify that an [ObjectServerError] exception is thrown with a specific [ErrorCode] */ -inline fun assertFailsWithErrorCode(expectedCode: ErrorCode, method: () -> Unit) { - try { +inline fun assertFailsWithErrorCode( + expectedCode: ErrorCode, + method: () -> Unit +): ObjectServerError { + return assertFailsWith(ObjectServerError::class) { method() fail() - } catch (e: ObjectServerError) { - assertEquals("Unexpected error code", expectedCode, e.errorCode) + }.also { e: ObjectServerError -> + assertEquals(expectedCode, e.errorCode, "Unexpected error code") + assertNotNull(e.errorMessage) } } -inline fun ErrorCollector.assertFailsWith(block : () -> Unit){ +inline fun ErrorCollector.assertFailsWith(block: () -> Unit) { try { block() - } catch (e : Exception) { + } catch (e: Exception) { if (e !is T) { addError(e) } } } -inline fun assertFailsWithMessage(matcher: Matcher, block : () -> Unit){ +inline fun assertFailsWithMessage(matcher: Matcher, block: () -> Unit) { try { block() fail("assertFailsWithMessage completed without expected exception") - } catch (e : Exception) { + } catch (e: Exception) { if (e !is T) { throw AssertionError("assertFailsWithMessage did not throw expected exception: " + T::class.java.name) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/mongodb/CustomType.java b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/mongodb/CustomType.java new file mode 100644 index 0000000000..5484cbfcb1 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/mongodb/CustomType.java @@ -0,0 +1,118 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.util.mongodb; + +import org.bson.BsonReader; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.bson.BsonWriter; +import org.bson.Document; +import org.bson.codecs.CollectibleCodec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.DocumentCodec; +import org.bson.codecs.EncoderContext; +import org.bson.types.ObjectId; + +import java.util.Objects; + +public class CustomType { + + private ObjectId id; + private final int intValue; + + public CustomType(final ObjectId id, final int intValue) { + this.id = id; + this.intValue = intValue; + } + + public ObjectId getId() { + return id; + } + + void setId(final ObjectId id) { + this.id = id; + } + + CustomType withNewObjectId() { + setId(new ObjectId()); + return this; + } + + public int getIntValue() { + return intValue; + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof CustomType)) { + return false; + } + final CustomType other = (CustomType) object; + return id.equals(other.id) && intValue == other.intValue; + } + + @Override + public int hashCode() { + return Objects.hash(id, intValue); + } + + public static class Codec implements CollectibleCodec { + + @Override + public CustomType generateIdIfAbsentFromDocument(final CustomType document) { + return documentHasId(document) ? document.withNewObjectId() : document; + } + + @Override + public boolean documentHasId(final CustomType document) { + return document.getId() == null; + } + + @Override + public BsonValue getDocumentId(final CustomType document) { + return new BsonString(document.getId().toHexString()); + } + + @Override + public CustomType decode(final BsonReader reader, final DecoderContext decoderContext) { + final Document document = (new DocumentCodec()).decode(reader, decoderContext); + return new CustomType(document.getObjectId("_id"), document.getInteger("intValue")); + } + + @Override + public void encode( + final BsonWriter writer, + final CustomType value, + final EncoderContext encoderContext + ) { + final Document document = new Document(); + if (value.getId() != null) { + document.put("_id", value.getId()); + } + document.put("intValue", value.getIntValue()); + (new DocumentCodec()).encode(writer, document, encoderContext); + } + + @Override + public Class getEncoderClass() { + return CustomType.class; + } + } +} diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index c7164a6730..f812b4bf10 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -117,6 +117,8 @@ if (build_SYNC) io.realm.internal.objectstore.OsMongoCollection io.realm.internal.objectstore.OsMongoDatabase io.realm.internal.objectstore.OsSyncUser + io.realm.mongodb.mongo.iterable.AggregateIterable + io.realm.mongodb.mongo.iterable.FindIterable ) endif() create_javah(TARGET jni_headers @@ -212,6 +214,8 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoDatabase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsSyncUser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_mongo_iterable_FindIterable.cpp ${CMAKE_CURRENT_SOURCE_DIR}/jni_util/bson_util.cpp ) endif() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp index e1e7377fce..28d04064da 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp @@ -83,10 +83,6 @@ static std::function)> collection_mapper_find = [](JNIEnv* env, util::Optional array) { - return array ? JniBsonProtocol::bson_to_jstring(env, *array) : NULL; -}; - static void finalize_collection(jlong ptr) { delete reinterpret_cast(ptr); } @@ -116,41 +112,39 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeCount(JNIEnv* env, JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOne(JNIEnv* env, jclass, + jint j_find_one_type, jlong j_collection_ptr, jstring j_filter, + jstring j_projection, + jstring j_sort, + jlong j_limit, jobject j_callback) { try { auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - collection->find_one(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneWithOptions(JNIEnv* env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jstring j_projection, - jstring j_sort, - jlong j_limit, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - uint64_t limit = std::uint64_t(j_limit); - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); - bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOptions options = { - limit, - projection, - sort - }; - - collection->find_one(filter, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + switch (j_find_one_type) { + case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE: + collection->find_one(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + break; + case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_WITH_OPTIONS: { + uint64_t limit = std::uint64_t(j_limit); + + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); + RemoteMongoCollection::RemoteFindOptions options = { + limit, + projection, + sort + }; + + collection->find_one(filter, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + break; + } + default: + throw std::logic_error(util::format("Unknown find_one type: %1", j_find_one_type)); + } } CATCH_STD() } @@ -186,101 +180,61 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeInsertMany(JNIEnv* en } JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeDeleteOne(JNIEnv* env, - jclass, - jlong j_collection_ptr, - jstring j_document, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_document, Bson::Type::Document, "BSON document must be a Document")); - collection->delete_one(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeDeleteMany(JNIEnv* env, - jclass, - jlong j_collection_ptr, - jstring j_document, - jobject j_callback) { +Java_io_realm_internal_objectstore_OsMongoCollection_nativeDelete(JNIEnv* env, + jclass, + jint j_delete_type, + jlong j_collection_ptr, + jstring j_document, + jobject j_callback) { try { auto collection = reinterpret_cast(j_collection_ptr); - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_document, Bson::Type::Document, "BSON document must be a Document")); - collection->delete_many(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdateOne(JNIEnv *env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jstring j_update, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); - collection->update_one(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + switch (j_delete_type) { + case io_realm_internal_objectstore_OsMongoCollection_DELETE_ONE: + collection->delete_one(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); + break; + case io_realm_internal_objectstore_OsMongoCollection_DELETE_MANY: + collection->delete_many(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_count)); + break; + default: + throw std::logic_error(util::format("Unknown delete type: %1", j_delete_type)); + } } CATCH_STD() } JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdateOneWithOptions(JNIEnv *env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jstring j_update, - jboolean j_upsert, - jobject j_callback) { +Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdate(JNIEnv *env, + jclass, + jint j_update_type, + jlong j_collection_ptr, + jstring j_filter, + jstring j_update, + jboolean j_upsert, + jobject j_callback) { try { auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); - collection->update_one(filter, update, to_bool(j_upsert), JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); - } - CATCH_STD() -} -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdateMany(JNIEnv *env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jstring j_update, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); - collection->update_many(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdateManyWithOptions(JNIEnv *env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jstring j_update, - jboolean j_upsert, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); - collection->update_many(filter, update, to_bool(j_upsert), JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + switch (j_update_type) { + case io_realm_internal_objectstore_OsMongoCollection_UPDATE_ONE: + collection->update_one(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + break; + case io_realm_internal_objectstore_OsMongoCollection_UPDATE_ONE_WITH_OPTIONS: + collection->update_one(filter, update, to_bool(j_upsert), JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + break; + case io_realm_internal_objectstore_OsMongoCollection_UPDATE_MANY: + collection->update_many(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + break; + case io_realm_internal_objectstore_OsMongoCollection_UPDATE_MANY_WITH_OPTIONS: + collection->update_many(filter, update, to_bool(j_upsert), JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_update)); + break; + default: + throw std::logic_error(util::format("Unknown update type: %1", j_update_type)); + } } CATCH_STD() } @@ -288,45 +242,40 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdateManyWithOptions JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndUpdate(JNIEnv *env, jclass, + jint j_find_one_and_update_type, jlong j_collection_ptr, jstring j_filter, jstring j_update, + jstring j_projection, + jstring j_sort, + jboolean j_upsert, + jboolean j_return_new_document, jobject j_callback) { try { auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); - collection->find_one_and_update(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); - } - CATCH_STD() -} -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndUpdateWithOptions(JNIEnv *env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jstring j_update, - jstring j_projection, - jstring j_sort, - jboolean j_upsert, - jboolean j_return_new_document, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); - bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); - bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { - projection, - sort, - to_bool(j_upsert), - to_bool(j_return_new_document) - }; - collection->find_one_and_update(filter, update, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + switch (j_find_one_and_update_type) { + case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_AND_UPDATE: + collection->find_one_and_update(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + break; + case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_AND_UPDATE_WITH_OPTIONS: { + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); + RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { + projection, + sort, + to_bool(j_upsert), + to_bool(j_return_new_document) + }; + collection->find_one_and_update(filter, update, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + break; + } + default: + throw std::logic_error(util::format("Unknown find_one_and_update type: %1", j_find_one_and_update_type)); + } } CATCH_STD() } @@ -334,45 +283,40 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndUpdateWithO JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndReplace(JNIEnv *env, jclass, + jint j_find_one_and_replace_type, jlong j_collection_ptr, jstring j_filter, jstring j_update, + jstring j_projection, + jstring j_sort, + jboolean j_upsert, + jboolean j_return_new_document, jobject j_callback) { try { auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); - collection->find_one_and_update(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); - } - CATCH_STD() -} -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndReplaceWithOptions(JNIEnv *env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jstring j_update, - jstring j_projection, - jstring j_sort, - jboolean j_upsert, - jboolean j_return_new_document, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); - bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); - bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { - projection, - sort, - to_bool(j_upsert), - to_bool(j_return_new_document) - }; - collection->find_one_and_replace(filter, update, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + switch (j_find_one_and_replace_type) { + case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_AND_REPLACE: + collection->find_one_and_replace(filter, update, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + break; + case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_AND_REPLACE_WITH_OPTIONS: { + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); + RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { + projection, + sort, + to_bool(j_upsert), + to_bool(j_return_new_document) + }; + collection->find_one_and_replace(filter, update, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + break; + } + default: + throw std::logic_error(util::format("Unknown find_one_and_replace type: %1", j_find_one_and_replace_type)); + } } CATCH_STD() } @@ -380,82 +324,38 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndReplaceWith JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndDelete(JNIEnv *env, jclass, + jint j_find_one_and_delete_type, jlong j_collection_ptr, jstring j_filter, + jstring j_projection, + jstring j_sort, + jboolean j_upsert, + jboolean j_return_new_document, jobject j_callback) { try { auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - collection->find_one_and_delete(filter, JavaNetworkTransport::create_void_callback(env, j_callback)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndDeleteWithOptions(JNIEnv *env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jstring j_projection, - jstring j_sort, - jboolean j_upsert, - jboolean j_return_new_document, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); - bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { - projection, - sort, - to_bool(j_upsert), - to_bool(j_return_new_document) - }; - collection->find_one_and_delete(filter, options, JavaNetworkTransport::create_void_callback(env, j_callback)); - } - CATCH_STD() -} - -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeFind(JNIEnv *env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - collection->find(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find)); - } - CATCH_STD() -} -JNIEXPORT void JNICALL -Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindWithOptions(JNIEnv *env, - jclass, - jlong j_collection_ptr, - jstring j_filter, - jstring j_projection, - jstring j_sort, - jlong j_limit, - jobject j_callback) { - try { - auto collection = reinterpret_cast(j_collection_ptr); - - uint64_t limit = std::uint64_t(j_limit); - bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); - bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); - bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOptions options = { - limit, - projection, - sort - }; - collection->find(filter, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find)); + switch (j_find_one_and_delete_type) { + case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_AND_DELETE: + collection->find_one_and_delete(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + break; + case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_AND_DELETE_WITH_OPTIONS: { + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); + RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { + projection, + sort, + to_bool(j_upsert), + to_bool(j_return_new_document) + }; + collection->find_one_and_delete(filter, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find_one)); + break; + } + default: + throw std::logic_error(util::format("Unknown find_one_and_delete type: %1", j_find_one_and_delete_type)); + } } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp new file mode 100644 index 0000000000..415573d51f --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_mongodb_mongo_iterable_AggregateIterable.h" + +#include "java_class_global_def.hpp" +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" +#include "jni_util/bson_util.hpp" +#include "object-store/src/util/bson/bson.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::bson; +using namespace realm::jni_util; +using namespace realm::_impl; + +static std::function)> collection_mapper_aggregate = [](JNIEnv* env, util::Optional array) { + return array ? JniBsonProtocol::bson_to_jstring(env, *array) : NULL; +}; + +JNIEXPORT void JNICALL +Java_io_realm_mongodb_mongo_iterable_AggregateIterable_nativeAggregate(JNIEnv* env, + jclass, + jlong j_collection_ptr, + jstring j_pipeline, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + BsonArray bson_array(JniBsonProtocol::parse_checked(env, j_pipeline, Bson::Type::Array, "BSON pipeline must be a BsonArray")); + + collection->aggregate(bson_array, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_aggregate)); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp new file mode 100644 index 0000000000..54e1a10d7d --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_mongodb_mongo_iterable_FindIterable.h" + +#include "java_class_global_def.hpp" +#include "java_network_transport.hpp" +#include "util.hpp" +#include "jni_util/java_method.hpp" +#include "jni_util/jni_utils.hpp" +#include "jni_util/bson_util.hpp" +#include "object-store/src/util/bson/bson.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::bson; +using namespace realm::jni_util; +using namespace realm::_impl; + +static std::function)> collection_mapper_find = [](JNIEnv* env, util::Optional array) { + return array ? JniBsonProtocol::bson_to_jstring(env, *array) : NULL; +}; + +JNIEXPORT void JNICALL +Java_io_realm_mongodb_mongo_iterable_FindIterable_nativeFind(JNIEnv *env, + jclass, + jint j_find_type, + jlong j_collection_ptr, + jstring j_filter, + jstring j_projection, + jstring j_sort, + jlong j_limit, + jobject j_callback) { + try { + auto collection = reinterpret_cast(j_collection_ptr); + + bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); + + switch (j_find_type) { + case io_realm_mongodb_mongo_iterable_FindIterable_FIND: + collection->find(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find)); + break; + case io_realm_mongodb_mongo_iterable_FindIterable_FIND_WITH_OPTIONS: + uint64_t limit = std::uint64_t(j_limit); + bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); + bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); + RemoteMongoCollection::RemoteFindOptions options = { + limit, + projection, + sort + }; + collection->find(filter, options, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_find)); + break; + } + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 8ca4a6b01d..b1c0ab7d65 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 8ca4a6b01dbaf0df5179378dc2421af47653f330 +Subproject commit b1c0ab7d658c1dee3c0534ed82b9f77afbf8cb6c diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java index 70ff2b8187..b80b8c3fde 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java @@ -17,10 +17,12 @@ package io.realm.internal.jni; import org.bson.BsonValue; +import org.bson.codecs.Codec; import org.bson.codecs.Decoder; import org.bson.codecs.DecoderContext; import org.bson.codecs.Encoder; import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; import org.bson.codecs.configuration.CodecRegistry; import org.bson.json.JsonMode; import org.bson.json.JsonReader; @@ -30,9 +32,12 @@ import java.io.StringReader; import java.io.StringWriter; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.ObjectServerError; + /** * Protocol for passing {@link BsonValue}s to JNI. - * + *

              * For now this just encapsulated the BSON value in a document with key {@value VALUE}. This * overcomes the shortcoming of {@code org.bson.JsonWrite} not being able to serialize single values. */ @@ -41,34 +46,62 @@ public class JniBsonProtocol { private static final String VALUE = "value"; private static JsonWriterSettings writerSettings = JsonWriterSettings.builder() - .outputMode(JsonMode.EXTENDED) - .build(); + .outputMode(JsonMode.EXTENDED) + .build(); public static String encode(T value, CodecRegistry registry) { - return encode(value, (Encoder)registry.get(value.getClass())); + // catch possible missing codecs before the actual encoding + return encode(value, (Encoder) getCodec(value.getClass(), registry)); } public static String encode(T value, Encoder encoder) { - StringWriter stringWriter = new StringWriter(); - JsonWriter jsonWriter = new JsonWriter(stringWriter, writerSettings); - jsonWriter.writeStartDocument(); - jsonWriter.writeName(VALUE); - encoder.encode(jsonWriter, value, EncoderContext.builder().build()); - jsonWriter.writeEndDocument(); - return stringWriter.toString(); + try { + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter, writerSettings); + jsonWriter.writeStartDocument(); + jsonWriter.writeName(VALUE); + encoder.encode(jsonWriter, value, EncoderContext.builder().build()); + jsonWriter.writeEndDocument(); + return stringWriter.toString(); + } catch (CodecConfigurationException e) { + // same exception as in the guard above, but needed here as well nonetheless as the + // result might be wrapped inside an iterable or a map and the codec for the end type + // might be missing + throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve encoder for end type", e); + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.BSON_ENCODING, "Error encoding value", e); + } } public static T decode(String string, Class clz, CodecRegistry registry) { - return decode(string, registry.get(clz)); + // catch possible missing codecs before the actual decoding + return decode(string, getCodec(clz, registry)); } public static T decode(String string, Decoder decoder) { - StringReader stringReader = new StringReader(string); - JsonReader jsonReader = new JsonReader(stringReader); - jsonReader.readStartDocument(); - jsonReader.readName(VALUE); - T value = decoder.decode(jsonReader, DecoderContext.builder().build()); - jsonReader.readEndDocument(); - return value; + try { + StringReader stringReader = new StringReader(string); + JsonReader jsonReader = new JsonReader(stringReader); + jsonReader.readStartDocument(); + jsonReader.readName(VALUE); + T value = decoder.decode(jsonReader, DecoderContext.builder().build()); + jsonReader.readEndDocument(); + return value; + } catch (CodecConfigurationException e) { + // same exception as in the guard above, but needed here as well nonetheless as the + // result might be wrapped inside an iterable or a map and the codec for the end type + // might be missing + throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve decoder for end type" + string, e); + } catch (Exception e) { + throw new ObjectServerError(ErrorCode.BSON_DECODING, "Error decoding value " + string, e); + } + } + + public static Codec getCodec(Class clz, CodecRegistry registry) { + try { + return registry.get(clz); + } catch (CodecConfigurationException e) { + throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve codec for " + clz.getSimpleName(), e); + } } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java index 5602b1e3a1..1baa4d9f71 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java @@ -18,22 +18,27 @@ import org.bson.codecs.configuration.CodecRegistry; -import io.realm.mongodb.User; import io.realm.internal.NativeObject; +import io.realm.internal.common.TaskDispatcher; public class OsMongoClient implements NativeObject { private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); private final long nativePtr; + private final TaskDispatcher dispatcher; - public OsMongoClient(long appNativePtr, String serviceName) { + public OsMongoClient(final long appNativePtr, + final String serviceName, + final TaskDispatcher dispatcher) { this.nativePtr = nativeCreate(appNativePtr, serviceName); + this.dispatcher = dispatcher; } - public OsMongoDatabase getRemoteDatabase(final String databaseName, final CodecRegistry codecRegistry) { + public OsMongoDatabase getDatabase(final String databaseName, + final CodecRegistry codecRegistry) { long nativeDatabasePtr = nativeCreateDatabase(nativePtr, databaseName); - return new OsMongoDatabase(nativeDatabasePtr, codecRegistry); + return new OsMongoDatabase(nativeDatabasePtr, codecRegistry, dispatcher); } @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java index f155addd22..8d054dc0b5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java @@ -25,7 +25,6 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,11 +32,15 @@ import javax.annotation.Nullable; -import io.realm.mongodb.ObjectServerError; import io.realm.internal.NativeObject; +import io.realm.internal.Util; +import io.realm.internal.common.TaskDispatcher; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.network.ResultHandler; +import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.mongo.iterable.AggregateIterable; +import io.realm.mongodb.mongo.iterable.FindIterable; import io.realm.mongodb.mongo.options.CountOptions; import io.realm.mongodb.mongo.options.FindOneAndModifyOptions; import io.realm.mongodb.mongo.options.FindOptions; @@ -49,16 +52,38 @@ public class OsMongoCollection implements NativeObject { + private static final int DELETE_ONE = 1; + private static final int DELETE_MANY = 2; + private static final int UPDATE_ONE = 3; + private static final int UPDATE_ONE_WITH_OPTIONS = 4; + private static final int UPDATE_MANY = 5; + private static final int UPDATE_MANY_WITH_OPTIONS = 6; + private static final int FIND_ONE_AND_UPDATE = 7; + private static final int FIND_ONE_AND_UPDATE_WITH_OPTIONS = 8; + private static final int FIND_ONE_AND_REPLACE = 9; + private static final int FIND_ONE_AND_REPLACE_WITH_OPTIONS = 10; + private static final int FIND_ONE_AND_DELETE = 11; + private static final int FIND_ONE_AND_DELETE_WITH_OPTIONS = 12; + private static final int FIND_ONE = 13; + private static final int FIND_ONE_WITH_OPTIONS = 14; + private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); private final long nativePtr; private final Class documentClass; private final CodecRegistry codecRegistry; + private final String encodedEmptyDocument; + private final TaskDispatcher dispatcher; - OsMongoCollection(final long nativeCollectionPtr, final Class documentClass, final CodecRegistry codecRegistry) { + OsMongoCollection(final long nativeCollectionPtr, + final Class documentClass, + final CodecRegistry codecRegistry, + final TaskDispatcher dispatcher) { this.nativePtr = nativeCollectionPtr; this.documentClass = documentClass; this.codecRegistry = codecRegistry; + this.dispatcher = dispatcher; + this.encodedEmptyDocument = JniBsonProtocol.encode(new Document(), codecRegistry); } @Override @@ -71,15 +96,36 @@ public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } + public Class getDocumentClass() { + return documentClass; + } + + public CodecRegistry getCodecRegistry() { + return codecRegistry; + } + + public OsMongoCollection withDocumentClass( + final Class clazz) { + return new OsMongoCollection<>(nativePtr, clazz, codecRegistry, dispatcher); + } + + public OsMongoCollection withCodecRegistry(final CodecRegistry codecRegistry) { + return new OsMongoCollection<>(nativePtr, documentClass, codecRegistry, dispatcher); + } + public Long count() { - return count(null); + return countInternal(new Document(), null); } - public Long count(@Nullable final Bson filter) { - return count(filter, null); + public Long count(final Bson filter) { + return countInternal(filter, null); } - public Long count(@Nullable final Bson filter, @Nullable final CountOptions options) { + public Long count(final Bson filter, final CountOptions options) { + return countInternal(filter, options); + } + + private Long countInternal(final Bson filter, @Nullable final CountOptions options) { AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @@ -89,97 +135,133 @@ protected Long mapSuccess(Object result) { } }; - // no filter means count all - String filterString = (filter == null) ? - JniBsonProtocol.encode(new Document(), codecRegistry) : - JniBsonProtocol.encode(filter, codecRegistry); - int limit = (options == null) ? 0 : options.getLimit(); + final String filterString = JniBsonProtocol.encode(filter, codecRegistry); + final int limit = (options == null) ? 0 : options.getLimit(); nativeCount(nativePtr, filterString, limit, callback); return ResultHandler.handleResult(success, error); } - public Collection find() { - return find(new Document()); + public FindIterable find() { + return findInternal(new Document(), documentClass, null); } - public Collection find(final FindOptions options) { - return find(new Document(), options); + public FindIterable find(final FindOptions options) { + return findInternal(new Document(), documentClass, options); } - public Collection find(final Class resultClass) { - return find(new Document(), resultClass); + public FindIterable find(final Class resultClass) { + return findInternal(new Document(), resultClass, null); } - public Collection find(final Class resultClass, final FindOptions options) { - return find(new Document(), resultClass, options); + public FindIterable find(final Class resultClass, final FindOptions options) { + return findInternal(new Document(), resultClass, options); } - public Collection find(final Bson filter) { - return find(filter, documentClass); + public FindIterable find(final Bson filter) { + return findInternal(filter, documentClass, null); } - public Collection find(final Bson filter, final FindOptions options) { - return find(filter, documentClass, options); + public FindIterable find(final Bson filter, final FindOptions options) { + return findInternal(filter, documentClass, options); } - public Collection find(final Bson filter, final Class resultClass) { - return find(filter, resultClass, null); + public FindIterable find(final Bson filter, + final Class resultClass) { + return findInternal(filter, resultClass, null); } - // FIXME: fix find implementation - ignore this code for code review - public Collection find(final Bson filter, - final Class resultClass, - @Nullable final FindOptions options) { - AtomicReference> success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback> callback = new OsJNIResultCallback>(success, error) { - @Override - @SuppressWarnings("unchecked") - protected Collection mapSuccess(Object result) { - return JniBsonProtocol.decode((String) result, Collection.class, codecRegistry); - } - }; - - String filterString = JniBsonProtocol.encode(filter, codecRegistry); + public FindIterable find(final Bson filter, + final Class resultClass, + final FindOptions options) { + return findInternal(filter, resultClass, options); + } - if (options == null) { - nativeFind(nativePtr, filterString, callback); - } else { - String projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); - String sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); + private FindIterable findInternal(final Bson filter, + final Class resultClass, + @Nullable final FindOptions options) { + FindIterable findIterable = + new FindIterable<>(this, codecRegistry, resultClass, dispatcher); + findIterable.filter(filter); - nativeFindWithOptions(nativePtr, filterString, projectionString, sortString, options.getLimit(), callback); + if (options != null) { + findIterable.limit(options.getLimit()); + findIterable.projection(options.getProjection()); } + return findIterable; + } - return ResultHandler.handleResult(success, error); + public AggregateIterable aggregate(final List pipeline) { + return aggregate(pipeline, documentClass); + } + + public AggregateIterable aggregate(final List pipeline, + final Class resultClass) { + return new AggregateIterable<>(this, codecRegistry, dispatcher, resultClass, pipeline); } public DocumentT findOne() { - return findOne(new Document()); + return findOneInternal(FIND_ONE, new Document(), null, documentClass); } public ResultT findOne(final Class resultClass) { - return findOne(null, resultClass); + return findOneInternal(FIND_ONE, new Document(), null, resultClass); } public DocumentT findOne(final Bson filter) { - return findOne(filter, documentClass); + return findOneInternal(FIND_ONE, filter, null, documentClass); } - public ResultT findOne(final @Nullable Bson filter, final Class resultClass) { - return findOneInternal(filter, null, resultClass); + public ResultT findOne(final Bson filter, final Class resultClass) { + return findOneInternal(FIND_ONE, filter, null, resultClass); } - public DocumentT findOne(@Nullable final Bson filter, final FindOptions options) { - return findOne(filter, options, documentClass); + public DocumentT findOne(final Bson filter, final FindOptions options) { + return findOneInternal(FIND_ONE_WITH_OPTIONS, filter, options, documentClass); } - public ResultT findOne(@Nullable final Bson filter, + public ResultT findOne(final Bson filter, final FindOptions options, final Class resultClass) { - return findOneInternal(filter, options, resultClass); + return findOneInternal(FIND_ONE_WITH_OPTIONS, filter, options, resultClass); + } + + private ResultT findOneInternal(final int type, + final Bson filter, + @Nullable final FindOptions options, + final Class resultClass) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected ResultT mapSuccess(Object result) { + return findSuccessMapper(result, resultClass); + } + }; + + final String encodedFilter = JniBsonProtocol.encode(filter, codecRegistry); + + // default to empty docs or update if needed + String projectionString = encodedEmptyDocument; + String sortString = encodedEmptyDocument; + + switch (type) { + case FIND_ONE: + nativeFindOne(FIND_ONE, nativePtr, encodedFilter, projectionString, sortString, 0, callback); + break; + case FIND_ONE_WITH_OPTIONS: + Util.checkNull(options, "options"); + projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); + sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); + + nativeFindOne(FIND_ONE_WITH_OPTIONS, nativePtr, encodedFilter, projectionString, sortString, options.getLimit(), callback); + break; + default: + throw new IllegalArgumentException("Invalid fineOne type: " + type); + } + + return ResultHandler.handleResult(success, error); } public InsertOneResult insertOne(final DocumentT document) { @@ -193,7 +275,7 @@ protected InsertOneResult mapSuccess(Object result) { } }; - String encodedDocument = JniBsonProtocol.encode(document, codecRegistry); + final String encodedDocument = JniBsonProtocol.encode(document, codecRegistry); nativeInsertOne(nativePtr, encodedDocument, callback); return ResultHandler.handleResult(success, error); } @@ -215,37 +297,101 @@ protected InsertManyResult mapSuccess(Object result) { } }; - String encodedDocumentArray = JniBsonProtocol.encode(documents, codecRegistry); + final String encodedDocumentArray = JniBsonProtocol.encode(documents, codecRegistry); nativeInsertMany(nativePtr, encodedDocumentArray, callback); return ResultHandler.handleResult(success, error); } public DeleteResult deleteOne(final Bson filter) { - return deleteInternal(DeleteType.ONE, filter); + return deleteInternal(DELETE_ONE, filter); } public DeleteResult deleteMany(final Bson filter) { - return deleteInternal(DeleteType.MANY, filter); + return deleteInternal(DELETE_MANY, filter); + } + + private DeleteResult deleteInternal(final int type, final Bson filter) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected DeleteResult mapSuccess(Object result) { + return new DeleteResult((Long) result); + } + }; + + final String jsonDocument = JniBsonProtocol.encode(filter, codecRegistry); + switch (type) { + case DELETE_ONE: + nativeDelete(DELETE_ONE, nativePtr, jsonDocument, callback); + break; + case DELETE_MANY: + nativeDelete(DELETE_MANY, nativePtr, jsonDocument, callback); + break; + default: + throw new IllegalArgumentException("Invalid delete type: " + type); + } + return ResultHandler.handleResult(success, error); } public UpdateResult updateOne(final Bson filter, final Bson update) { - return updateOne(filter, update, null); + return updateInternal(UPDATE_ONE, filter, update, null); } public UpdateResult updateOne(final Bson filter, final Bson update, - @Nullable final UpdateOptions options) { - return updateInternal(UpdateType.ONE, filter, update, options); + final UpdateOptions options) { + return updateInternal(UPDATE_ONE_WITH_OPTIONS, filter, update, options); } public UpdateResult updateMany(final Bson filter, final Bson update) { - return updateMany(filter, update, null); + return updateInternal(UPDATE_MANY, filter, update, null); } public UpdateResult updateMany(final Bson filter, final Bson update, - @Nullable final UpdateOptions options) { - return updateInternal(UpdateType.MANY, filter, update, options); + final UpdateOptions options) { + return updateInternal(UPDATE_MANY_WITH_OPTIONS, filter, update, options); + } + + private UpdateResult updateInternal(final int type, + final Bson filter, + final Bson update, + @Nullable final UpdateOptions options) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected UpdateResult mapSuccess(Object result) { + BsonArray array = JniBsonProtocol.decode((String) result, BsonArray.class, codecRegistry); + long matchedCount = array.get(0).asInt32().getValue(); + long modifiedCount = array.get(1).asInt32().getValue(); + BsonValue upsertedId = array.get(2); + + if (upsertedId instanceof BsonNull) { + upsertedId = null; + } + return new UpdateResult(matchedCount, modifiedCount, upsertedId); + } + }; + + final String jsonFilter = JniBsonProtocol.encode(filter, codecRegistry); + final String jsonUpdate = JniBsonProtocol.encode(update, codecRegistry); + + switch (type) { + case UPDATE_ONE: + case UPDATE_MANY: + nativeUpdate(type, nativePtr, jsonFilter, jsonUpdate, false, callback); + break; + case UPDATE_ONE_WITH_OPTIONS: + case UPDATE_MANY_WITH_OPTIONS: + Util.checkNull(options, "options"); + nativeUpdate(type, nativePtr, jsonFilter, jsonUpdate, options.isUpsert(), callback); + break; + default: + throw new IllegalArgumentException("Invalid update type: " + type); + } + return ResultHandler.handleResult(success, error); } public DocumentT findOneAndUpdate(final Bson filter, final Bson update) { @@ -255,7 +401,7 @@ public DocumentT findOneAndUpdate(final Bson filter, final Bson update) { public ResultT findOneAndUpdate(final Bson filter, final Bson update, final Class resultClass) { - return findOneAndInternal(FindOneAndType.UPDATE, filter, update, null, resultClass); + return findOneAndModify(FIND_ONE_AND_UPDATE, filter, update, null, resultClass); } public DocumentT findOneAndUpdate(final Bson filter, @@ -268,7 +414,7 @@ public ResultT findOneAndUpdate(final Bson filter, final Bson update, final FindOneAndModifyOptions options, final Class resultClass) { - return findOneAndInternal(FindOneAndType.UPDATE, filter, update, options, resultClass); + return findOneAndModify(FIND_ONE_AND_UPDATE_WITH_OPTIONS, filter, update, options, resultClass); } public DocumentT findOneAndReplace(final Bson filter, final Bson replacement) { @@ -276,22 +422,22 @@ public DocumentT findOneAndReplace(final Bson filter, final Bson replacement) { } public ResultT findOneAndReplace(final Bson filter, - final Bson update, + final Bson replacement, final Class resultClass) { - return findOneAndInternal(FindOneAndType.REPLACE, filter, update, null, resultClass); + return findOneAndModify(FIND_ONE_AND_REPLACE, filter, replacement, null, resultClass); } public DocumentT findOneAndReplace(final Bson filter, - final Bson update, + final Bson replacement, final FindOneAndModifyOptions options) { - return findOneAndReplace(filter, update, options, documentClass); + return findOneAndReplace(filter, replacement, options, documentClass); } public ResultT findOneAndReplace(final Bson filter, - final Bson update, + final Bson replacement, final FindOneAndModifyOptions options, final Class resultClass) { - return findOneAndInternal(FindOneAndType.REPLACE, filter, update, options, resultClass); + return findOneAndModify(FIND_ONE_AND_REPLACE_WITH_OPTIONS, filter, replacement, options, resultClass); } public DocumentT findOneAndDelete(final Bson filter) { @@ -300,100 +446,25 @@ public DocumentT findOneAndDelete(final Bson filter) { public ResultT findOneAndDelete(final Bson filter, final Class resultClass) { - return findOneAndDeleteInternal(filter, null, resultClass); + return findOneAndModify(FIND_ONE_AND_DELETE, filter, new Document(), null, resultClass); } public DocumentT findOneAndDelete(final Bson filter, final FindOneAndModifyOptions options) { - return findOneAndDeleteInternal(filter, options, documentClass); + return findOneAndDelete(filter, options, documentClass); } public ResultT findOneAndDelete(final Bson filter, - final FindOneAndModifyOptions options, - final Class resultClass) { - return findOneAndDeleteInternal(filter, options, resultClass); - } - - private UpdateResult updateInternal(UpdateType type, final Bson filter, final Bson update, @Nullable final UpdateOptions options) { - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { - @Override - protected UpdateResult mapSuccess(Object result) { - // FIXME: see OsMongoCollection.cpp - collection_mapper_update. There surely is a better way to do this - BsonArray array = JniBsonProtocol.decode((String) result, BsonArray.class, codecRegistry); - long matchedCount = array.get(0).asInt32().getValue(); - long modifiedCount = array.get(1).asInt32().getValue(); - - // FIXME: this seems ugly, but Stitch allows retuning null for upsertedId - BsonValue upsertedId = array.get(2); - if (upsertedId instanceof BsonNull) { - upsertedId = null; - } - return new UpdateResult(matchedCount, modifiedCount, upsertedId); - } - }; - - String jsonFilter = JniBsonProtocol.encode(filter, codecRegistry); - String jsonUpdate = JniBsonProtocol.encode(update, codecRegistry); - - switch (type) { - case ONE: - if (options == null) { - nativeUpdateOne(nativePtr, jsonFilter, jsonUpdate, callback); - } else { - nativeUpdateOneWithOptions(nativePtr, jsonFilter, jsonUpdate, options.isUpsert(), callback); - } - break; - case MANY: - if (options == null) { - nativeUpdateMany(nativePtr, jsonFilter, jsonUpdate, callback); - } else { - nativeUpdateManyWithOptions(nativePtr, jsonFilter, jsonUpdate, options.isUpsert(), callback); - } - break; - } - return ResultHandler.handleResult(success, error); - } - - private ResultT findOneInternal(@Nullable final Bson filter, - @Nullable final FindOptions options, + final FindOneAndModifyOptions options, final Class resultClass) { - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { - @Override - protected ResultT mapSuccess(Object result) { - return findSuccessMapper(result, resultClass); - } - }; - - String encodedFilter = (filter == null) ? - JniBsonProtocol.encode(new Document(), codecRegistry) : - JniBsonProtocol.encode(filter, codecRegistry); - if (options == null) { - nativeFindOne(nativePtr, encodedFilter, callback); - } else { - String projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); - String sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); - - nativeFindOneWithOptions(nativePtr, encodedFilter, projectionString, sortString, options.getLimit(), callback); - } - - return ResultHandler.handleResult(success, error); - } - - private ResultT findOneAndDeleteInternal(final Bson filter, - @Nullable final FindOneAndModifyOptions options, - final Class resultClass) { - return findOneAndInternal(FindOneAndType.DELETE, filter, new Document(), options, resultClass); + return findOneAndModify(FIND_ONE_AND_DELETE_WITH_OPTIONS, filter, new Document(), options, resultClass); } - private ResultT findOneAndInternal(final FindOneAndType type, - final Bson filter, - final Bson update, - @Nullable final FindOneAndModifyOptions options, - final Class resultClass) { + private ResultT findOneAndModify(final int type, + final Bson filter, + final Bson update, + @Nullable final FindOneAndModifyOptions options, + final Class resultClass) { AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @@ -403,61 +474,47 @@ protected ResultT mapSuccess(Object result) { } }; - String encodedFilter = JniBsonProtocol.encode(filter, codecRegistry); - String encodedUpdate = JniBsonProtocol.encode(update, codecRegistry); - String encodedProjection = null; - String encodedSort = null; + final String encodedFilter = JniBsonProtocol.encode(filter, codecRegistry); + final String encodedUpdate = JniBsonProtocol.encode(update, codecRegistry); + + // default to empty docs or update if needed + String encodedProjection = encodedEmptyDocument; + String encodedSort = encodedEmptyDocument; if (options != null) { - encodedProjection = JniBsonProtocol.encode(options.getProjection(), codecRegistry); - encodedSort = JniBsonProtocol.encode(options.getSort(), codecRegistry); + if (options.getProjection() != null) { + encodedProjection = JniBsonProtocol.encode(options.getProjection(), codecRegistry); + } + if (options.getSort() != null) { + encodedSort = JniBsonProtocol.encode(options.getSort(), codecRegistry); + } } switch (type) { - case UPDATE: - if (options == null) { - nativeFindOneAndUpdate(nativePtr, encodedFilter, encodedUpdate, callback); - } else { - nativeFindOneAndUpdateWithOptions(nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); - } + case FIND_ONE_AND_UPDATE: + nativeFindOneAndUpdate(type, nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, false, false, callback); break; - case REPLACE: - if (options == null) { - nativeFindOneAndReplace(nativePtr, encodedFilter, encodedUpdate, callback); - } else { - nativeFindOneAndReplaceWithOptions(nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); - } + case FIND_ONE_AND_UPDATE_WITH_OPTIONS: + Util.checkNull(options, "options"); + nativeFindOneAndUpdate(type, nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); break; - case DELETE: - if (options == null) { - nativeFindOneAndDelete(nativePtr, encodedFilter, callback); - } else { - nativeFindOneAndDeleteWithOptions(nativePtr, encodedFilter, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); - } + case FIND_ONE_AND_REPLACE: + nativeFindOneAndReplace(type, nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, false, false,callback); break; - } - - return ResultHandler.handleResult(success, error); - } - - private DeleteResult deleteInternal(final DeleteType type, final Bson filter) { - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { - @Override - protected DeleteResult mapSuccess(Object result) { - return new DeleteResult((Long) result); - } - }; - - String jsonDocument = JniBsonProtocol.encode(filter, codecRegistry); - switch (type) { - case ONE: - nativeDeleteOne(nativePtr, jsonDocument, callback); + case FIND_ONE_AND_REPLACE_WITH_OPTIONS: + Util.checkNull(options, "options"); + nativeFindOneAndReplace(type, nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); + break; + case FIND_ONE_AND_DELETE: + nativeFindOneAndDelete(type, nativePtr, encodedFilter, encodedProjection, encodedSort, false, false, callback); break; - case MANY: - nativeDeleteMany(nativePtr, jsonDocument, callback); + case FIND_ONE_AND_DELETE_WITH_OPTIONS: + Util.checkNull(options, "options"); + nativeFindOneAndDelete(type, nativePtr, encodedFilter, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); break; + default: + throw new IllegalArgumentException("Invalid modify type: " + type); } + return ResultHandler.handleResult(success, error); } @@ -469,103 +526,58 @@ private T findSuccessMapper(@Nullable Object result, Class resultClass) { } } - private enum UpdateType { - ONE, MANY - } - - private enum DeleteType { - ONE, MANY - } - - private enum FindOneAndType { - UPDATE, REPLACE, DELETE - } - private static native long nativeGetFinalizerMethodPtr(); private static native void nativeCount(long remoteMongoCollectionPtr, String filter, long limit, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFindOne(long nativePtr, + private static native void nativeFindOne(int findOneType, + long nativePtr, String filter, + String projection, + String sort, + long limit, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFindOneWithOptions(long nativePtr, - String filter, - String projection, - String sort, - long limit, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); private static native void nativeInsertOne(long remoteMongoCollectionPtr, String document, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); private static native void nativeInsertMany(long remoteMongoCollectionPtr, String documents, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeDeleteOne(long remoteMongoCollectionPtr, - String document, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeDeleteMany(long remoteMongoCollectionPtr, - String document, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeUpdateOne(long remoteMongoCollectionPtr, - String filter, - String update, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeUpdateOneWithOptions(long remoteMongoCollectionPtr, - String filter, - String update, - boolean upsert, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeUpdateMany(long remoteMongoCollectionPtr, - String filter, - String update, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeUpdateManyWithOptions(long remoteMongoCollectionPtr, - String filter, - String update, - boolean upsert, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFindOneAndUpdate(long remoteMongoCollectionPtr, + private static native void nativeDelete(int deleteType, + long remoteMongoCollectionPtr, + String document, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeUpdate(int updateType, + long remoteMongoCollectionPtr, + String filter, + String update, + boolean upsert, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndUpdate(int findOneAndUpdateType, + long remoteMongoCollectionPtr, String filter, String update, + String projection, + String sort, + boolean upsert, + boolean returnNewDocument, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFindOneAndUpdateWithOptions(long remoteMongoCollectionPtr, - String filter, - String update, - String projection, - String sort, - boolean upsert, - boolean returnNewDocument, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFindOneAndReplace(long remoteMongoCollectionPtr, + private static native void nativeFindOneAndReplace(int findOneAndReplaceType, + long remoteMongoCollectionPtr, String filter, String update, + String projection, + String sort, + boolean upsert, + boolean returnNewDocument, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFindOneAndReplaceWithOptions(long remoteMongoCollectionPtr, - String filter, - String update, - String projection, - String sort, - boolean upsert, - boolean returnNewDocument, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFindOneAndDelete(long remoteMongoCollectionPtr, - String filter, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFindOneAndDeleteWithOptions(long remoteMongoCollectionPtr, - String filter, - String projection, - String sort, - boolean upsert, - boolean returnNewDocument, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFind(long remoteMongoCollectionPtr, - String filter, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - private static native void nativeFindWithOptions(long remoteMongoCollectionPtr, - String filter, - String projection, - String sort, - long limit, - OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndDelete(int findOneAndDeleteType, + long remoteMongoCollectionPtr, + String filter, + String projection, + String sort, + boolean upsert, + boolean returnNewDocument, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java index f4965bba60..372fa06288 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java @@ -20,6 +20,7 @@ import org.bson.codecs.configuration.CodecRegistry; import io.realm.internal.NativeObject; +import io.realm.internal.common.TaskDispatcher; public class OsMongoDatabase implements NativeObject { @@ -27,22 +28,24 @@ public class OsMongoDatabase implements NativeObject { private final long nativePtr; private final CodecRegistry codecRegistry; + private final TaskDispatcher dispatcher; - OsMongoDatabase(long nativeDatabasePtr, CodecRegistry codecRegistry) { + OsMongoDatabase(final long nativeDatabasePtr, + final CodecRegistry codecRegistry, + final TaskDispatcher dispatcher) { this.nativePtr = nativeDatabasePtr; this.codecRegistry = codecRegistry; + this.dispatcher = dispatcher; } public OsMongoCollection getCollection(final String collectionName) { return getCollection(collectionName, Document.class); } - public OsMongoCollection getCollection( - final String collectionName, - final Class documentClass - ) { + public OsMongoCollection getCollection(final String collectionName, + final Class documentClass) { long nativeCollectionPtr = nativeGetCollection(nativePtr, collectionName); - return new OsMongoCollection<>(nativeCollectionPtr, documentClass, codecRegistry); + return new OsMongoCollection<>(nativeCollectionPtr, documentClass, codecRegistry, dispatcher); } @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java index b698113c93..ee5c03ac73 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java @@ -43,7 +43,7 @@ public enum ErrorCode { NETWORK_IO_EXCEPTION(Type.JAVA, OsJavaNetworkTransport.ERROR_IO), NETWORK_INTERRUPTED(Type.JAVA, OsJavaNetworkTransport.ERROR_INTERRUPTED), NETWORK_UNKNOWN(Type.JAVA, OsJavaNetworkTransport.ERROR_UNKNOWN), - // BSON encoding/decoding errors originalting from java + // BSON encoding/decoding errors originating from java BSON_CODEC_NOT_FOUND(Type.JAVA, 1100), BSON_ENCODING(Type.JAVA, 1101), BSON_DECODING(Type.JAVA, 1102), diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java index 30c580f1f2..85f2e2963d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java @@ -16,7 +16,6 @@ package io.realm.mongodb; import org.bson.codecs.Decoder; -import org.bson.codecs.configuration.CodecConfigurationException; import org.bson.codecs.configuration.CodecRegistry; import java.util.List; @@ -50,14 +49,7 @@ class FunctionsImpl extends Functions { public T invoke(String name, List args, CodecRegistry codecRegistry, Decoder resultDecoder) { Util.checkEmpty(name, "name"); - String encodedArgs; - try { - encodedArgs = JniBsonProtocol.encode(args, codecRegistry); - } catch (CodecConfigurationException e) { - throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve encoder for arguments", e); - } catch (Exception e) { - throw new ObjectServerError(ErrorCode.BSON_ENCODING, "Error encoding function arguments", e); - } + String encodedArgs = JniBsonProtocol.encode(args, codecRegistry); // NativePO calling scheme is actually synchronous AtomicReference success = new AtomicReference<>(null); @@ -70,15 +62,9 @@ protected String mapSuccess(Object result) { }; nativeCallFunction(user.getApp().nativePtr, user.osUser.getNativePtr(), name, encodedArgs, callback); String encodedResponse = ResultHandler.handleResult(success, error); - T result; - try { - result = JniBsonProtocol.decode(encodedResponse, resultDecoder); - } catch (Exception e) { - throw new ObjectServerError(ErrorCode.BSON_DECODING, "Error decoding function result", e); - } - return result; + return JniBsonProtocol.decode(encodedResponse, resultDecoder); } - private static native void nativeCallFunction(long nativeAppPtr, long nativeUserPtr, String name, String args_json, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeCallFunction(long nativeAppPtr, long nativeUserPtr, String name, String args_json, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index 23a50cec2c..650225bf67 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -24,18 +24,19 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.annotations.Beta; -import io.realm.internal.mongodb.Request; -import io.realm.internal.objectstore.OsMongoClient; -import io.realm.mongodb.auth.ApiKeyAuth; import io.realm.RealmAsyncTask; -import io.realm.internal.network.ResultHandler; +import io.realm.annotations.Beta; import io.realm.internal.Util; +import io.realm.internal.common.TaskDispatcher; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.jni.OsJNIVoidResultCallback; +import io.realm.internal.mongodb.Request; +import io.realm.internal.network.ResultHandler; import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.internal.objectstore.OsMongoClient; import io.realm.internal.objectstore.OsSyncUser; import io.realm.internal.util.Pair; +import io.realm.mongodb.auth.ApiKeyAuth; import io.realm.mongodb.functions.Functions; import io.realm.mongodb.mongo.MongoClient; import io.realm.mongodb.push.Push; @@ -88,8 +89,10 @@ byte getKey() { } private static class MongoClientImpl extends MongoClient { - protected MongoClientImpl(OsMongoClient osMongoClient, CodecRegistry codecRegistry) { - super(osMongoClient, codecRegistry); + protected MongoClientImpl(OsMongoClient osMongoClient, + CodecRegistry codecRegistry, + TaskDispatcher dispatcher) { + super(osMongoClient, codecRegistry, dispatcher); } } @@ -478,13 +481,15 @@ public Push getPushNotifications() { } /** - * FIXME Add support for the MongoDB wrapper. Name of Class and method still TBD. + * Returns a {@link MongoClient} instance for accessing documents in the database. + * @param serviceName the service name used to connect to the server */ public MongoClient getMongoClient(String serviceName) { Util.checkEmpty(serviceName, "serviceName"); if (mongoClient == null) { - OsMongoClient osMongoClient = new OsMongoClient(app.nativePtr, serviceName); - mongoClient = new MongoClientImpl(osMongoClient, app.getConfiguration().getDefaultCodecRegistry()); + TaskDispatcher dispatcher = new TaskDispatcher(); + OsMongoClient osMongoClient = new OsMongoClient(app.nativePtr, serviceName, dispatcher); + mongoClient = new MongoClientImpl(osMongoClient, app.getConfiguration().getDefaultCodecRegistry(), dispatcher); } return mongoClient; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java index 22b6a5ba0c..90c66226be 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java @@ -24,10 +24,10 @@ import io.realm.RealmAsyncTask; import io.realm.annotations.Beta; import io.realm.internal.Util; +import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.mongodb.Request; import io.realm.mongodb.App; import io.realm.mongodb.AppConfiguration; -import io.realm.mongodb.ErrorCode; import io.realm.mongodb.ObjectServerError; import io.realm.mongodb.User; @@ -75,7 +75,7 @@ protected Functions(User user, CodecRegistry codecRegistry) { * @see AppConfiguration#getDefaultCodecRegistry() */ public ResultT callFunction(String name, List args, Class resultClass, CodecRegistry codecRegistry) { - return invoke(name, args, codecRegistry, decoder(codecRegistry, resultClass)); + return invoke(name, args, codecRegistry, JniBsonProtocol.getCodec(resultClass, codecRegistry)); } /** @@ -142,7 +142,8 @@ public RealmAsyncTask callFunctionAsync(String name, List args, Class return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override public T run() throws ObjectServerError { - return invoke(name, args, codecRegistry, decoder(codecRegistry, resultClass)); + Decoder decoder = JniBsonProtocol.getCodec(resultClass, codecRegistry); + return invoke(name, args, codecRegistry, decoder); } }.start(); } @@ -228,12 +229,4 @@ public User getUser() { protected abstract T invoke(String name, List args, CodecRegistry codecRegistry, Decoder resultDecoder); - private static Decoder decoder(CodecRegistry codecRegistry, Class clz) { - try { - return codecRegistry.get(clz); - } catch (Exception e) { - throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve decoder for " + clz.getName(), e); - } - } - } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java index 931948f888..565e7e4c77 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java @@ -19,8 +19,8 @@ import org.bson.codecs.configuration.CodecRegistry; import io.realm.annotations.Beta; -import io.realm.mongodb.User; import io.realm.internal.Util; +import io.realm.internal.common.TaskDispatcher; import io.realm.internal.objectstore.OsMongoClient; /** @@ -29,12 +29,16 @@ @Beta abstract public class MongoClient { - private OsMongoClient osMongoClient; - private CodecRegistry codecRegistry; + private final OsMongoClient osMongoClient; + private final CodecRegistry codecRegistry; + private final TaskDispatcher dispatcher; - protected MongoClient(OsMongoClient osMongoClient, final CodecRegistry codecRegistry) { + protected MongoClient(final OsMongoClient osMongoClient, + final CodecRegistry codecRegistry, + final TaskDispatcher dispatcher) { this.osMongoClient = osMongoClient; this.codecRegistry = codecRegistry; + this.dispatcher = dispatcher; } /** @@ -45,6 +49,6 @@ protected MongoClient(OsMongoClient osMongoClient, final CodecRegistry codecRegi */ public MongoDatabase getDatabase(final String databaseName) { Util.checkEmpty(databaseName, "databaseName"); - return new MongoDatabase(osMongoClient.getRemoteDatabase(databaseName, codecRegistry), databaseName); + return new MongoDatabase(osMongoClient.getDatabase(databaseName, codecRegistry), databaseName, dispatcher); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java index a36c9575b2..dc96ce7d82 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java @@ -25,6 +25,8 @@ import io.realm.annotations.Beta; import io.realm.internal.common.TaskDispatcher; +import io.realm.mongodb.mongo.iterable.AggregateIterable; +import io.realm.mongodb.mongo.iterable.FindIterable; import io.realm.internal.objectstore.OsMongoCollection; import io.realm.mongodb.mongo.options.CountOptions; import io.realm.mongodb.mongo.options.FindOneAndModifyOptions; @@ -36,7 +38,7 @@ import io.realm.mongodb.mongo.result.UpdateResult; /** - * The RemoteMongoCollection interface provides read and write access to documents. + * The MongoCollection interface provides read and write access to documents. *

              * Use {@link MongoDatabase#getCollection} to get a collection instance. *

              @@ -49,13 +51,16 @@ @Beta public class MongoCollection { - private OsMongoCollection osMongoCollection; + private final MongoNamespace nameSpace; + private final OsMongoCollection osMongoCollection; + private final TaskDispatcher dispatcher; - private TaskDispatcher dispatcher; - - MongoCollection(OsMongoCollection osMongoCollection) { - this.dispatcher = new TaskDispatcher(); + MongoCollection(final MongoNamespace nameSpace, + final OsMongoCollection osMongoCollection, + final TaskDispatcher dispatcher) { + this.nameSpace = nameSpace; this.osMongoCollection = osMongoCollection; + this.dispatcher = dispatcher; } /** @@ -64,32 +69,57 @@ public class MongoCollection { * @return the namespace */ public MongoNamespace getNamespace() { - throw new RuntimeException("Not Implemented"); + return nameSpace; + } + + /** + * Gets the class of documents stored in this collection. + *

              + * If you used the simple {@link MongoDatabase#getCollection(String)} to get this collection, + * this is {@link org.bson.Document}. + *

              + * + * @return the class of documents in this collection + */ + public Class getDocumentClass() { + return osMongoCollection.getDocumentClass(); + } + + /** + * Gets the codec registry for the MongoCollection. + * + * @return the {@link CodecRegistry} for this collection + */ + public CodecRegistry getCodecRegistry() { + return osMongoCollection.getCodecRegistry(); } /** - * Create a new RemoteMongoCollection instance with a different default class to cast any + * Creates a new MongoCollection instance with a different default class to cast any * documents returned from the database into. * - * @param clazz the default class to cast any documents returned from the database into. + * @param clazz the default class to which any documents returned from the database + * will be cast. * @param The type that the new collection will encode documents from and decode * documents to. - * @return a new RemoteMongoCollection instance with the different default class + * @return a new MongoCollection instance with the different default class */ public MongoCollection withDocumentClass( final Class clazz) { - throw new UnsupportedOperationException("Not Implemented"); + return new MongoCollection<>(nameSpace, + osMongoCollection.withDocumentClass(clazz), dispatcher); } /** - * Create a new RemoteMongoCollection instance with a different codec registry. + * Creates a new MongoCollection instance with a different codec registry. * * @param codecRegistry the new {@link CodecRegistry} for the * collection. - * @return a new RemoteMongoCollection instance with the different codec registry + * @return a new MongoCollection instance with the different codec registry */ public MongoCollection withCodecRegistry(final CodecRegistry codecRegistry) { - throw new UnsupportedOperationException("Not Implemented"); + return new MongoCollection<>(nameSpace, + osMongoCollection.withCodecRegistry(codecRegistry), dispatcher); } /** @@ -98,9 +128,7 @@ public MongoCollection withCodecRegistry(final CodecRegistry codecReg * @return a task containing the number of documents in the collection */ public Task count() { - return dispatcher.dispatchTask(() -> - osMongoCollection.count() - ); + return dispatcher.dispatchTask(osMongoCollection::count); } /** @@ -134,9 +162,7 @@ public Task count(final Bson filter, final CountOptions options) { * @return a task containing the result of the find one operation */ public Task findOne() { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOne() - ); + return dispatcher.dispatchTask(osMongoCollection::findOne); } /** @@ -182,7 +208,7 @@ public Task findOne(final Bson filter, final Class r * Finds a document in the collection. * * @param filter the query filter - * @param options A RemoteFindOptions struct + * @param options a {@link FindOptions} struct * @return a task containing the result of the find one operation */ public Task findOne(final Bson filter, final FindOptions options) { @@ -195,15 +221,14 @@ public Task findOne(final Bson filter, final FindOptions options) { * Finds a document in the collection. * * @param filter the query filter - * @param options A RemoteFindOptions struct + * @param options a {@link FindOptions} struct * @param resultClass the class to decode each document into * @param the target document type of the iterable. * @return a task containing the result of the find one operation */ - public Task findOne( - final Bson filter, - final FindOptions options, - final Class resultClass) { + public Task findOne(final Bson filter, + final FindOptions options, + final Class resultClass) { return dispatcher.dispatchTask(() -> osMongoCollection.findOne(filter, options, resultClass) ); @@ -211,110 +236,151 @@ public Task findOne( /** * Finds all documents in the collection. + *

              + * All documents will be delivered in the form of a {@link FindIterable} from which individual + * elements can be extracted. * - * @return the find iterable interface + * @return an iterable containing the result of the find operation */ - // FIXME: fix find implementation - ignore this code for code review - public Task> find() { - return dispatcher.dispatchTask(() -> - osMongoCollection.find() - ); + public FindIterable find() { + return osMongoCollection.find(); } - // FIXME: fix find implementation - ignore this code for code review - public Task> find(final FindOptions options) { - return dispatcher.dispatchTask(() -> - osMongoCollection.find(options) - ); + /** + * Finds all documents in the collection using {@link FindOptions} to build the query. + *

              + * All documents will be delivered in the form of a {@link FindIterable} from which individual + * elements can be extracted. + * + * @param options a {@link FindOptions} struct for building the query + * @return an iterable containing the result of the find operation + */ + public FindIterable find(final FindOptions options) { + return osMongoCollection.find(options); } /** - * Finds all documents in the collection. + * Finds all documents in the collection specifying an output class. + *

              + * All documents will be delivered in the form of a {@link FindIterable} from which individual + * elements can be extracted. * * @param resultClass the class to decode each document into * @param the target document type of the iterable. - * @return the find iterable interface + * @return an iterable containing the result of the find operation */ - // FIXME: fix find implementation - ignore this code for code review - public Task> find(final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.find(resultClass) - ); + public FindIterable find(final Class resultClass) { + return osMongoCollection.find(resultClass); } - // FIXME: fix find implementation - ignore this code for code review - public Task> find(final Class resultClass, final FindOptions options) { - return dispatcher.dispatchTask(() -> - osMongoCollection.find(resultClass, options) - ); + /** + * Finds all documents in the collection specifying an output class and also using + * {@link FindOptions} to build the query. + *

              + * All documents will be delivered in the form of a {@link FindIterable} from which individual + * elements can be extracted. + * + * @param resultClass the class to decode each document into + * @param options a {@link FindOptions} struct for building the query + * @param the target document type of the iterable. + * @return an iterable containing the result of the find operation + */ + public FindIterable find(final Class resultClass, + final FindOptions options) { + return osMongoCollection.find(resultClass, options); } /** * Finds all documents in the collection that match the given filter. + *

              + * All documents will be delivered in the form of a {@link FindIterable} from which individual + * elements can be extracted. * * @param filter the query filter - * @return the find iterable interface + * @return an iterable containing the result of the find operation */ - // FIXME: fix find implementation - ignore this code for code review - public Task> find(final Bson filter) { - return dispatcher.dispatchTask(() -> - osMongoCollection.find(filter) - ); + public FindIterable find(final Bson filter) { + return osMongoCollection.find(filter); } - // FIXME: fix find implementation - ignore this code for code review - public Task> find(final Bson filter, final FindOptions options) { - return dispatcher.dispatchTask(() -> - osMongoCollection.find(filter, options) - ); + /** + * Finds all documents in the collection that match the given filter using {@link FindOptions} + * to build the query. + *

              + * All documents will be delivered in the form of a {@link FindIterable} from which individual + * elements can be extracted. + * + * @param filter the query filter + * @param options a {@link FindOptions} struct + * @return an iterable containing the result of the find operation + */ + public FindIterable find(final Bson filter, final FindOptions options) { + return osMongoCollection.find(filter, options); } /** - * Finds all documents in the collection that match the given filter. + * Finds all documents in the collection that match the given filter specifying an output class. + *

              + * All documents will be delivered in the form of a {@link FindIterable} from which individual + * elements can be extracted. * * @param filter the query filter * @param resultClass the class to decode each document into * @param the target document type of the iterable. - * @return the find iterable interface + * @return an iterable containing the result of the find operation */ - // FIXME: fix find implementation - ignore this code for code review - public Task> find(final Bson filter, final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.find(filter, resultClass) - ); + public FindIterable find(final Bson filter, + final Class resultClass) { + return osMongoCollection.find(filter, resultClass); } - // FIXME: fix find implementation - ignore this code for code review - public Task> find(final Bson filter, - final Class resultClass, - final FindOptions options) { - return dispatcher.dispatchTask(() -> - osMongoCollection.find(filter, resultClass, options) - ); + /** + * Finds all documents in the collection that match the given filter specifying an output class + * and also using {@link FindOptions} to build the query. + *

              + * All documents will be delivered in the form of a {@link FindIterable} from which individual + * elements can be extracted. + * + * @param filter the query filter + * @param resultClass the class to decode each document into + * @param options a {@link FindOptions} struct + * @param the target document type of the iterable. + * @return an iterable containing the result of the find operation + */ + public FindIterable find(final Bson filter, + final Class resultClass, + final FindOptions options) { + return osMongoCollection.find(filter, resultClass, options); } /** * Aggregates documents according to the specified aggregation pipeline. + *

              + * All documents will be delivered in the form of an {@link AggregateIterable} from which + * individual elements can be extracted. * * @param pipeline the aggregation pipeline - * @return an iterable containing the result of the aggregation operation + * @return an {@link AggregateIterable} from which the results can be extracted */ - public Task aggregate(final List pipeline) { - throw new UnsupportedOperationException("Not Implemented"); + public AggregateIterable aggregate(final List pipeline) { + return osMongoCollection.aggregate(pipeline); } /** - * Aggregates documents according to the specified aggregation pipeline. + * Aggregates documents according to the specified aggregation pipeline specifying an output + * class. + *

              + * All documents will be delivered in the form of an {@link AggregateIterable} from which + * individual elements can be extracted. * * @param pipeline the aggregation pipeline * @param resultClass the class to decode each document into * @param the target document type of the iterable. - * @return an iterable containing the result of the aggregation operation + * @return an {@link AggregateIterable} from which the results can be extracted */ - public Task aggregate( - final List pipeline, - final Class resultClass) { - throw new UnsupportedOperationException("Not Implemented"); + public AggregateIterable aggregate(final List pipeline, + final Class resultClass) { + return osMongoCollection.aggregate(pipeline, resultClass); } /** @@ -468,7 +534,7 @@ public Task findOneAndUpdate(final Bson filter, * * @param filter the query filter * @param update the update document - * @param options A RemoteFindOneAndModifyOptions struct + * @param options a {@link FindOneAndModifyOptions} struct * @return a task containing the resulting document */ public Task findOneAndUpdate(final Bson filter, @@ -484,16 +550,15 @@ public Task findOneAndUpdate(final Bson filter, * * @param filter the query filter * @param update the update document - * @param options A RemoteFindOneAndModifyOptions struct + * @param options a {@link FindOneAndModifyOptions} struct * @param resultClass the class to decode each document into * @param the target document type of the iterable. * @return a task containing the resulting document */ - public Task findOneAndUpdate( - final Bson filter, - final Bson update, - final FindOneAndModifyOptions options, - final Class resultClass) { + public Task findOneAndUpdate(final Bson filter, + final Bson update, + final FindOneAndModifyOptions options, + final Class resultClass) { return dispatcher.dispatchTask(() -> osMongoCollection.findOneAndUpdate(filter, update, options, resultClass) ); @@ -534,7 +599,7 @@ public Task findOneAndReplace(final Bson filter, * * @param filter the query filter * @param replacement the document to replace the matched document with - * @param options A RemoteFindOneAndModifyOptions struct + * @param options a {@link FindOneAndModifyOptions} struct * @return a task containing the resulting document */ public Task findOneAndReplace(final Bson filter, @@ -550,16 +615,15 @@ public Task findOneAndReplace(final Bson filter, * * @param filter the query filter * @param replacement the document to replace the matched document with - * @param options A RemoteFindOneAndModifyOptions struct + * @param options a {@link FindOneAndModifyOptions} struct * @param resultClass the class to decode each document into * @param the target document type of the iterable. * @return a task containing the resulting document */ - public Task findOneAndReplace( - final Bson filter, - final Bson replacement, - final FindOneAndModifyOptions options, - final Class resultClass) { + public Task findOneAndReplace(final Bson filter, + final Bson replacement, + final FindOneAndModifyOptions options, + final Class resultClass) { return dispatcher.dispatchTask(() -> osMongoCollection.findOneAndReplace(filter, replacement, options, resultClass) ); @@ -596,7 +660,7 @@ public Task findOneAndDelete(final Bson filter, * Finds a document in the collection and delete it. * * @param filter the query filter - * @param options A RemoteFindOneAndModifyOptions struct + * @param options a {@link FindOneAndModifyOptions} struct * @return a task containing the resulting document */ public Task findOneAndDelete(final Bson filter, @@ -610,7 +674,7 @@ public Task findOneAndDelete(final Bson filter, * Finds a document in the collection and delete it. * * @param filter the query filter - * @param options A RemoteFindOneAndModifyOptions struct + * @param options a {@link FindOneAndModifyOptions} struct * @param resultClass the class to decode each document into * @param the target document type of the iterable. * @return a task containing the resulting document @@ -622,96 +686,4 @@ public Task findOneAndDelete(final Bson filter, osMongoCollection.findOneAndDelete(filter, options, resultClass) ); } - - // FIXME: what about these? -// /** -// * Watches a collection. The resulting stream will be notified of all events on this collection -// * that the active user is authorized to see based on the configured MongoDB rules. -// * -// * @return the stream of change events. -// */ -// Task>> watch(); -// -// /** -// * Watches specified IDs in a collection. This convenience overload supports the use case -// * of non-{@link BsonValue} instances of {@link ObjectId}. -// * -// * @param ids unique object identifiers of the IDs to watch. -// * @return the stream of change events. -// */ -// Task>> watch(final ObjectId... ids); -// -// /** -// * Watches specified IDs in a collection. -// * -// * @param ids the ids to watch. -// * @return the stream of change events. -// */ -// Task>> watch(final BsonValue... ids); -// -// /** -// * Watches a collection. The provided BSON document will be used as a match expression filter on -// * the change events coming from the stream. -// * See https://docs.mongodb.com/manual/reference/operator/aggregation/match/ for documentation -// * around how to define a match filter. Defining the match expression to filter ChangeEvents is -// * similar to defining the match expression for triggers: -// * https://docs.mongodb.com/stitch/triggers/database-triggers/ -// * -// * @param matchFilter the $match filter to apply to incoming change events -// * @return the stream of change events. -// */ -// Task>> watchWithFilter( -// final BsonDocument matchFilter); -// -// /** -// * Watches a collection. The provided BSON document will be used as a match expression filter on -// * the change events coming from the stream. -// * See https://docs.mongodb.com/manual/reference/operator/aggregation/match/ for documentation -// * around how to define a match filter. Defining the match expression to filter ChangeEvents is -// * similar to defining the match expression for triggers: -// * https://docs.mongodb.com/stitch/triggers/database-triggers/ -// * -// * @param matchFilter the $match filter to apply to incoming change events -// * @return the stream of change events. -// */ -// Task>> watchWithFilter( -// final Document matchFilter); -// -// /** -// * Watches specified IDs in a collection. This convenience overload supports the use case -// * of non-{@link BsonValue} instances of {@link ObjectId}. This convenience overload supports the -// * use case of non-{@link BsonValue} instances of {@link ObjectId}. Requests a stream where the -// * full document of update events, and several other unnecessary fields are omitted from the -// * change event objects returned by the server. This can save on network usage when watching -// * large documents. -// * -// * @param ids unique object identifiers of the IDs to watch. -// * @return the stream of change events. -// */ -// Task>> watchCompact( -// final ObjectId... ids); -// -// /** -// * Watches specified IDs in a collection. This convenience overload supports the use case of -// * non-{@link BsonValue} instances of {@link ObjectId}. Requests a stream where the full document -// * of update events, and several other unnecessary fields are omitted from the change event -// * objects returned by the server. This can save on network usage when watching large documents. -// * -// * @param ids the ids to watch. -// * @return the stream of change events. -// */ -// Task>> watchCompact( -// final BsonValue... ids); - - // FIXME: what about this one? -// /** -// * A set of synchronization related operations on this collection. -// * -// *

              -// * WARNING: This is a BETA feature and the API and on-device storage format -// * are subject to change. -// *

              -// * @return set of sync operations for this collection -// */ -// Sync sync(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java index 482e615e5b..8e58fd35c8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java @@ -20,6 +20,7 @@ import io.realm.annotations.Beta; import io.realm.internal.Util; +import io.realm.internal.common.TaskDispatcher; import io.realm.internal.objectstore.OsMongoDatabase; /** @@ -28,15 +29,16 @@ @Beta public class MongoDatabase { - private String databaseName; - private OsMongoDatabase osMongoDatabase; + private final String name; + private final TaskDispatcher dispatcher; + private final OsMongoDatabase osMongoDatabase; - MongoDatabase(OsMongoDatabase osMongoDatabase, String databaseName) { - // we deliver the database name because we don't want to modify the C++ code right now, - // although ideally it should be done there, i.e. remote_mongo_database.hpp should - // include the public (Java) API's methods that aren't there yet. - this.databaseName = databaseName; + MongoDatabase(final OsMongoDatabase osMongoDatabase, + final String name, + final TaskDispatcher dispatcher) { this.osMongoDatabase = osMongoDatabase; + this.name = name; + this.dispatcher = dispatcher; } /** @@ -45,7 +47,7 @@ public class MongoDatabase { * @return the database name */ public String getName() { - return databaseName; + return name; } /** @@ -56,7 +58,9 @@ public String getName() { */ public MongoCollection getCollection(final String collectionName) { Util.checkEmpty(collectionName, "collectionName"); - return new MongoCollection<>(osMongoDatabase.getCollection(collectionName)); + return new MongoCollection<>(new MongoNamespace(name, collectionName), + osMongoDatabase.getCollection(collectionName), + dispatcher); } /** @@ -73,6 +77,8 @@ public MongoCollection getCollection( ) { Util.checkEmpty(collectionName, "collectionName"); Util.checkNull(documentClass, "documentClass"); - return new MongoCollection<>(osMongoDatabase.getCollection(collectionName, documentClass)); + return new MongoCollection<>(new MongoNamespace(name, collectionName), + osMongoDatabase.getCollection(collectionName, documentClass), + dispatcher); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/AggregateIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/AggregateIterable.java new file mode 100644 index 0000000000..b71a01e03c --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/AggregateIterable.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.mongo.iterable; + +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.conversions.Bson; + +import java.util.List; + +import io.realm.internal.common.TaskDispatcher; +import io.realm.internal.jni.JniBsonProtocol; +import io.realm.internal.jni.OsJNIResultCallback; +import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.internal.objectstore.OsMongoCollection; + +/** + * Specific iterable for {@link io.realm.mongodb.mongo.MongoCollection#aggregate(List)} operations. + * + * @param The type to which this iterable will decode documents. + */ +public class AggregateIterable extends MongoIterable { + + private List pipeline; + + public AggregateIterable(final OsMongoCollection osMongoCollection, + final CodecRegistry codecRegistry, + final TaskDispatcher dispatcher, + final Class resultClass, + final List pipeline) { + super(osMongoCollection, codecRegistry, resultClass, dispatcher); + this.pipeline = pipeline; + } + + @Override + void callNative(final OsJNIResultCallback callback) { + String pipelineString = JniBsonProtocol.encode(pipeline, codecRegistry); + nativeAggregate(osMongoCollection.getNativePtr(), pipelineString, callback); + } + + private static native void nativeAggregate(long remoteMongoCollectionPtr, + String pipeline, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/FindIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/FindIterable.java new file mode 100644 index 0000000000..6b0b9f778c --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/FindIterable.java @@ -0,0 +1,124 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.mongo.iterable; + +import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.conversions.Bson; + +import javax.annotation.Nullable; + +import io.realm.internal.common.TaskDispatcher; +import io.realm.internal.jni.JniBsonProtocol; +import io.realm.internal.jni.OsJNIResultCallback; +import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.internal.objectstore.OsMongoCollection; +import io.realm.mongodb.mongo.options.FindOptions; + +/** + * Specific iterable for {@link io.realm.mongodb.mongo.MongoCollection#find()} operations. + * + * @param The type to which this iterable will decode documents. + */ +public class FindIterable extends MongoIterable { + + private static final int FIND = 1; + private static final int FIND_WITH_OPTIONS = 2; + + private final FindOptions options; + private final String encodedEmptyDocument; + + private Bson filter; + + public FindIterable(final OsMongoCollection osMongoCollection, + final CodecRegistry codecRegistry, + final Class resultClass, + final TaskDispatcher dispatcher) { + super(osMongoCollection, codecRegistry, resultClass, dispatcher); + this.options = new FindOptions(); + this.filter = new Document(); + this.encodedEmptyDocument = JniBsonProtocol.encode(new Document(), codecRegistry); + } + + @Override + void callNative(final OsJNIResultCallback callback) { + String filterString = JniBsonProtocol.encode(filter, codecRegistry); + String projectionString = encodedEmptyDocument; + String sortString = encodedEmptyDocument; + + if (options == null) { + nativeFind(FIND, osMongoCollection.getNativePtr(), filterString, projectionString, sortString, 0, callback); + } else { + projectionString = JniBsonProtocol.encode(options.getProjection(), codecRegistry); + sortString = JniBsonProtocol.encode(options.getSort(), codecRegistry); + + nativeFind(FIND_WITH_OPTIONS, osMongoCollection.getNativePtr(), filterString, projectionString, sortString, options.getLimit(), callback); + } + } + + /** + * Sets the query filter to apply to the query. + * + * @param filter the filter, which may be null. + * @return this + */ + public FindIterable filter(@Nullable final Bson filter) { + this.filter = filter; + return this; + } + + /** + * Sets the limit to apply. + * + * @param limit the limit, which may be 0 + * @return this + */ + public FindIterable limit(int limit) { + this.options.limit(limit); + return this; + } + + /** + * Sets a document describing the fields to return for all matching documents. + * + * @param projection the project document, which may be null. + * @return this + */ + public FindIterable projection(@Nullable final Bson projection) { + this.options.projection(projection); + return this; + } + + /** + * Sets the sort criteria to apply to the query. + * + * @param sort the sort criteria, which may be null. + * @return this + */ + public FindIterable sort(@Nullable final Bson sort) { + this.options.sort(sort); + return this; + } + + private static native void nativeFind(int findType, + long remoteMongoCollectionPtr, + String filter, + String projection, + String sort, + long limit, + OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java new file mode 100644 index 0000000000..577489ad16 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.mongo.iterable; + +import com.google.android.gms.tasks.Task; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Iterator; + +/** + * The Mongo Cursor class is fundamentally an {@link Iterator} containing an additional + * {@code tryNext()} method for convenience. + *

              + * An application should ensure that a cursor is closed in all circumstances, e.g. using a + * try-with-resources statement. + * + * @param The type of documents the cursor contains + */ +public class MongoCursor implements Iterator, Closeable { + + private final Iterator iterator; + + MongoCursor(Iterator iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public ResultT next() { + return iterator.next(); + } + + /** + * A special {@code next()} case that returns the next document if available or null. + * + * @return A {@link Task} containing the next document if available or null. + */ + public ResultT tryNext() { + if (!iterator.hasNext()) { + return null; + } + return iterator.next(); + } + + @Override + public void close() { + + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java new file mode 100644 index 0000000000..9ed6759756 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java @@ -0,0 +1,129 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.mongo.iterable; + +import com.google.android.gms.tasks.Task; + +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReference; + +import io.realm.internal.common.TaskDispatcher; +import io.realm.internal.jni.JniBsonProtocol; +import io.realm.internal.jni.OsJNIResultCallback; +import io.realm.internal.network.ResultHandler; +import io.realm.internal.objectstore.OsMongoCollection; +import io.realm.mongodb.ObjectServerError; + +/** + * The MongoIterable is the results from an operation, such as a {@code find()} or an + * {@code aggregate()} query. + *

              + * This class somewhat mimics the behavior of an {@link Iterable} but given its results are + * obtained asynchronously, its values are wrapped inside a {@link Task}. + * + * @param The type to which this iterable will decode documents. + */ +public abstract class MongoIterable { + + final OsMongoCollection osMongoCollection; + final CodecRegistry codecRegistry; + + private final Class resultClass; + private final TaskDispatcher dispatcher; + + MongoIterable(final OsMongoCollection osMongoCollection, + final CodecRegistry codecRegistry, + final Class resultClass, + final TaskDispatcher dispatcher) { + this.osMongoCollection = osMongoCollection; + this.codecRegistry = codecRegistry; + this.resultClass = resultClass; + this.dispatcher = dispatcher; + } + + abstract void callNative(final OsJNIResultCallback callback); + + /** + * Returns a cursor of the operation represented by this iterable. + *

              + * The result is wrapped in a {@link Task} since the iterator should be capable of + * asynchronously retrieve documents from the server. + * + * @return an asynchronous task with cursor of the operation represented by this iterable. + */ + public Task> iterator() { + return dispatcher.dispatchTask(() -> + new MongoCursor<>(getCollection().iterator()) + ); + } + + /** + * Helper to return the first item in the iterator or null. + *

              + * The result is wrapped in a {@link Task} since the iterator should be capable of + * asynchronously retrieve documents from the server. + * + * @return a task containing the first item or null. + */ + public Task first() { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + @Override + protected ResultT mapSuccess(Object result) { + Collection decodedCollection = mapCollection(result); + Iterator iter = decodedCollection.iterator(); + return iter.hasNext() ? iter.next() : null; + } + }; + + callNative(callback); + + return dispatcher.dispatchTask(() -> + ResultHandler.handleResult(success, error) + ); + } + + private Collection getCollection() { + AtomicReference> success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + OsJNIResultCallback> callback = new OsJNIResultCallback>(success, error) { + @Override + protected Collection mapSuccess(Object result) { + return mapCollection(result); + } + }; + + callNative(callback); + + return ResultHandler.handleResult(success, error); + } + + private Collection mapCollection(Object result) { + Collection collection = JniBsonProtocol.decode((String) result, Collection.class, codecRegistry); + Collection decodedCollection = new ArrayList<>(); + for (Object collectionElement: collection) { + String encodedElement = JniBsonProtocol.encode(collectionElement, codecRegistry); + decodedCollection.add(JniBsonProtocol.decode(encodedElement, resultClass, codecRegistry)); + } + return decodedCollection; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java index aba5be7264..da3c0188db 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOneAndModifyOptions.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; +import org.bson.Document; import org.bson.conversions.Bson; import io.realm.annotations.Beta; @@ -28,11 +29,17 @@ */ @Beta public class FindOneAndModifyOptions { + private Bson projection; private Bson sort; private boolean upsert; private boolean returnNewDocument; + public FindOneAndModifyOptions() { + this.projection = new Document(); + this.sort = new Document(); + } + /** * Gets a document describing the fields to return for all matching documents. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java index e7f84dec1d..ac3de8c6e7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/options/FindOptions.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; +import org.bson.Document; import org.bson.conversions.Bson; import io.realm.annotations.Beta; @@ -27,6 +28,7 @@ */ @Beta public class FindOptions { + private int limit; private Bson projection; private Bson sort; @@ -35,6 +37,8 @@ public class FindOptions { * Construct a new instance. */ public FindOptions() { + this.projection = new Document(); + this.sort = new Document(); } /** From 72af903e6b3266b058f73968cf877821256c389d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 4 Jun 2020 12:25:14 +0200 Subject: [PATCH 1562/2110] Fix sync session connection state listeners --- .../cpp/io_realm_mongodb_sync_SyncSession.cpp | 12 ++++++------ .../java/io/realm/mongodb/sync/Sync.java | 16 ---------------- .../java/io/realm/mongodb/sync/SyncSession.java | 15 ++++++++++++--- .../kotlin/io/realm/SyncSessionTests.kt | 12 ------------ 4 files changed, 18 insertions(+), 37 deletions(-) diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp index e16c432689..d417cd272c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp @@ -238,7 +238,7 @@ static jlong get_connection_value(SyncSession::ConnectionState state) { return static_cast(-1); } -JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddConnectionListener(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddConnectionListener(JNIEnv* env, jobject j_session_object, jstring j_local_realm_path) { try { // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda @@ -252,17 +252,17 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddConnecti return 0; } - static JavaClass java_syncmanager_class(env, "io/realm/mongodb/sync/Sync"); - static JavaMethod java_notify_connection_listener(env, java_syncmanager_class, "notifyConnectionListeners", "(Ljava/lang/String;JJ)V", true); + static JavaClass java_syncmanager_class(env, "io/realm/mongodb/sync/SyncSession"); + static JavaMethod java_notify_connection_listener(env, java_syncmanager_class, "notifyConnectionListeners", "(JJ)V"); - std::function callback = [local_realm_path](SyncSession::ConnectionState old_state, SyncSession::ConnectionState new_state) { + auto session_ref = env->NewGlobalRef(j_session_object); // FIXME Leaking reference to session + std::function callback = [session_ref](SyncSession::ConnectionState old_state, SyncSession::ConnectionState new_state) { JNIEnv* local_env = jni_util::JniUtils::get_env(true); jlong old_connection_value = get_connection_value(old_state); jlong new_connection_value = get_connection_value(new_state); - JavaLocalRef path(local_env, to_jstring(local_env, local_realm_path)); - local_env->CallStaticVoidMethod(java_syncmanager_class, java_notify_connection_listener, path.get(), + local_env->CallVoidMethod(session_ref, java_notify_connection_listener, old_connection_value, new_connection_value); // All exceptions will be caught on the Java side of handlers, but Errors will still end diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java index f2b160421e..e3d585316f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java @@ -240,22 +240,6 @@ private synchronized void notifyProgressListener(String localRealmPath, long lis } } - /** - * Called from native code. This method is not allowed to throw as it would be swallowed - * by the native Sync Client thread. Instead log all exceptions to logcat. - */ - @SuppressWarnings("unused") - private synchronized void notifyConnectionListeners(String localRealmPath, long oldState, long newState) { - SyncSession session = sessions.get(localRealmPath); - if (session != null) { - try { - session.notifyConnectionListeners(ConnectionState.fromNativeValue(oldState), ConnectionState.fromNativeValue(newState)); - } catch (Exception exception) { - RealmLog.error(exception); - } - } - } - /** * Realm will automatically detect when a device gets connectivity after being offline and * resume syncing. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 6012e88b3f..b4ca68b56a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -272,9 +272,18 @@ synchronized void notifyProgressListener(long listenerId, long transferredBytes, } } - void notifyConnectionListeners(ConnectionState oldState, ConnectionState newState) { + /** + * Called from native code. This method is not allowed to throw as it would be swallowed + * by the native Sync Client thread. Instead log all exceptions to logcat. + */ + @SuppressWarnings("unused") + void notifyConnectionListeners(long oldState, long newState) { for (ConnectionListener listener : connectionListeners) { - listener.onChange(oldState, newState); + try { + listener.onChange(ConnectionState.fromNativeValue(oldState), ConnectionState.fromNativeValue(newState)); + } catch (Exception exception) { + RealmLog.error(exception); + } } } @@ -721,7 +730,7 @@ public void throwExceptionIfNeeded() { } } - private static native long nativeAddConnectionListener(String localRealmPath); + private native long nativeAddConnectionListener(String localRealmPath); private static native void nativeRemoveConnectionListener(long listenerId, String localRealmPath); private native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); private static native void nativeRemoveProgressListener(String localRealmPath, long listenerToken); diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 146c7e5ce4..bcac480d64 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -650,8 +650,6 @@ class SyncSessionTests { } @Test - // FIXME Implement connection listeners - @Ignore("Connection listener callback is not implemented yet") fun registerConnectionListener() = looperThread.runBlocking { getSession { session: SyncSession -> session.addConnectionChangeListener { oldState: ConnectionState?, newState: ConnectionState -> @@ -665,8 +663,6 @@ class SyncSessionTests { } @Test - // FIXME Implement connection listeners - @Ignore("Connection listener callback is not implemented yet") fun removeConnectionListener() = looperThread.runBlocking { Realm.getInstance(syncConfiguration).use { realm -> val session: SyncSession = realm.syncSession @@ -689,8 +685,6 @@ class SyncSessionTests { } @Test - // FIXME Implement connection listeners - @Ignore("Connection listener callback is not implemented yet") fun getIsConnected() = looperThread.runBlocking { getActiveSession { session: SyncSession -> assertEquals(session.connectionState, ConnectionState.CONNECTED) @@ -700,8 +694,6 @@ class SyncSessionTests { } @Test - // FIXME Implement connection listeners - @Ignore("Connection listener callback is not implemented yet") fun stopStartSession() = looperThread.runBlocking { getActiveSession { session: SyncSession -> assertEquals(SyncSession.State.ACTIVE, session.state) @@ -714,8 +706,6 @@ class SyncSessionTests { } @Test - // FIXME Implement connection listeners - @Ignore("Connection listener callback is not implemented yet") fun start_multipleTimes() = looperThread.runBlocking { getActiveSession { session -> session.start() @@ -727,8 +717,6 @@ class SyncSessionTests { } @Test - // FIXME Implement connection listeners - @Ignore("Connection listener callback is not implemented yet") fun stop_multipleTimes() = looperThread.runBlocking { getActiveSession { session -> session.stop() From 32afe2908fb41834fe2281d5b35538447fce0704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 4 Jun 2020 12:36:59 +0200 Subject: [PATCH 1563/2110] Update App Javadoc (#6894) --- .../java/io/realm/mongodb/App.java | 121 ++++++++++++++++-- .../io/realm/mongodb/AppConfiguration.java | 32 ++++- .../io/realm/mongodb/functions/Functions.java | 38 +++--- .../realm/mongodb/sync/SyncConfiguration.java | 1 - 4 files changed, 155 insertions(+), 37 deletions(-) diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index b73e054acd..50ad3dd89a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -52,7 +52,93 @@ import io.realm.mongodb.functions.Functions; /** - * FIXME + * An App is the main client-side entry point for interacting with a MongoDB Realm App. + * + * The App can be used to: + *

                + *
              • Register uses and perform various user-related operations through authentication providers + * ({@link io.realm.mongodb.auth.ApiKeyAuth}, {@link EmailPasswordAuthImpl})
              • + *
              • Synchronize data between the local device and a remote Realm App with Synchronized Realms
              • + *
              • Invoke Realm App functions with {@link Functions}
              • + *
              • Access remote data from MongoDB databases with a {@link io.realm.mongodb.mongo.MongoClient}
              • + *
              + *

              + * To create an app that is linked with a remote Realm App initialize Realm and configure the + * App as shown below: + *

              + *

              + *    class MyApplication extends Application {
              + *
              + *         App APP;
              + *
              + *         @Override
              + *         public void onCreate() {
              + *             super.onCreate();
              + *
              + *             Realm.init(this);
              + *
              + *             AppConfiguration appConfiguration = new AppConfiguration.Builder(BuildConfig.MONGODB_REALM_APP_ID)
              + *                     .appName(BuildConfig.VERSION_NAME)
              + *                     .appVersion(Integer.toString(BuildConfig.VERSION_CODE))
              + *                     .build();
              + *
              + *             APP = new App(appConfiguration);
              + *         }
              + *
              + *     }
              + * 
              + *

              + * After configuring the App you can start managing users, configure Synchronized Realms, + * call remote Realm Functions and access remote data through Mongo Collections. The examples below + * show the synchronized APIs which cannot be used from the main thread. For the equivalent + * asynchronous counterparts. The example project in please see + * https://github.com/realm/realm-java/tree/v10/examples/mongoDbRealmExample. + * + * To register a new user and/or login with an existing user do as shown below: + *

              + *     // Register new user
              + *     User user = APP.getEmailPasswordAuth().registerUser(username, password);
              + *
              + *     // Login with existing user
              + *     APP.login(Credentials.emailPassword(username, password))
              + * 
              + *

              + * With an authorized user you can synchronize data between the local device and the remote Realm + * App by opening a Realm with a {@link io.realm.mongodb.sync.SyncConfiguration} as indicated below: + *

              + *     SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, "")
              + *              .build();
              + *
              + *     Realm instance = Realm.getInstance(syncConfiguration);
              + *     SyncSession session = APP.getSync().getSession(syncConfiguration);
              + *
              + *     instance.executeTransaction(realm -> {
              + *         realm.insert(...);
              + *     });
              + *     session.uploadAllLocalChanges();
              + *     instance.close();
              + * 
              + *

              + * You can call remove Realm functions as shown below: + *

              + *     Functions functions = user.getFunctions();
              + *     Integer sum = functions.callFunction("sum", Arrays.asList(1, 2, 3, 4), Integer.class);
              + * 
              + *

              + * And access collections from the remote Realm App as shown here: + *

              + *     MongoClient client = user.getMongoClient(SERVICE_NAME)
              + *     MongoDatabase database = client.getDatabase(DATABASE_NAME)
              + *     MongoCollection collection = database.getCollection(COLLECTION_NAME);
              + *     Long count = collection.count().blockingGetResult()
              + * 
              + *

              + * + * @see AppConfiguration.Builder + * @see EmailPasswordAuth + * @see io.realm.mongodb.sync.SyncConfiguration + * @see User#getFunctions() + * @see User#getMongoClient(String) */ @Beta public class App { @@ -95,8 +181,11 @@ public App(String appId) { } /** - * FIXME - * @param config + * Constructor for creating an App according to the given AppConfiguration. + * + * @param config The configuration to use for this App instance. + * + * @see AppConfiguration.Builder */ public App(AppConfiguration config) { this.config = config; @@ -212,6 +301,7 @@ private String getBindingInfo() { /** * Returns the current user that is logged in and still valid. + *

              * A user is invalidated when he/she logs out or the user's refresh token expires or is revoked. *

              * If two or more users are logged in, it is the last valid user that is returned by this method. @@ -244,7 +334,9 @@ public Map allUsers() { } /** - * Switch current user. The current user is the user returned by {@link #currentUser()}. + * Switch current user. + *

              + * The current user is the user returned by {@link #currentUser()}. * * @param user the new current user. * @throws IllegalArgumentException if the user is is not {@link User.State#LOGGED_IN}. @@ -377,32 +469,37 @@ public void removeAuthenticationListener(AuthenticationListener listener) { } /** - * FIXME: Figure out naming of this method and class. - * @return + * Returns the Sync instance managing the ongoing Realm Sync sessions + * synchronizing data between the local and the remote Realm App associated with this app. + * + * @return the Sync instance associated with this App. */ public Sync getSync() { return syncManager; } /** - * Returns a Functions manager for invoking MongoDB Realm Functions. + * Returns a Functions manager for invoking the Realm App's Realm Functions. *

              - * This will use the associated app's default codec registry to encode and decode arguments and - * results. + * This will use the app's default codec registry to encode and decode arguments and results. + * + * @see Functions + * @see AppConfiguration#getDefaultCodecRegistry() */ public Functions getFunctions(User user) { return new FunctionsImpl(user); } /** - * Returns a Functions manager for invoking MongoDB Realm Functions with custom + * Returns a Functions manager for invoking the Realm App's Realm Functions with a custom * codec registry for encoding and decoding arguments and results. + * + * @see Functions */ public Functions getFunctions(User user, CodecRegistry codecRegistry) { return new FunctionsImpl(user, codecRegistry); } - /** * Returns the configuration object for this app. * @@ -502,7 +599,7 @@ public T getOrDefault(T defaultValue) { * is thrown. * * @return the response object in case the request was a success. - * @throws ObjectServer provided error in case the request failed. + * @throws ObjectServerError provided error in case the request failed. */ public T getOrThrow() { if (isSuccess()) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index 3b2b6201dc..cac841279f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -78,6 +78,8 @@ public class AppConfiguration { /** * Default BSON codec registry for encoding/decoding arguments and results to/from MongoDB Realm backend. + *

              + * This will encode/decode most primitive types, list and map types and BsonValues. * * @see AppConfiguration#getDefaultCodecRegistry() * @see AppConfiguration.Builder#codecRegistry(CodecRegistry) @@ -215,7 +217,16 @@ public File getSyncRootDirectory() { return syncRootDir; } - // FIXME Doc + /** + * Returns the default codec registry used to encode and decode BSON arguments and results when + * calling remote Realm {@link io.realm.mongodb.functions.Functions} and accessing a remote + * {@link io.realm.mongodb.mongo.MongoDatabase}. + * + * @return The default codec registry for the App. + * + * @see #DEFAULT_BSON_CODEC_REGISTRY + * @see Builder#getDefaultCodecRegistry() + */ public CodecRegistry getDefaultCodecRegistry() { return codecRegistry; } /** @@ -278,7 +289,7 @@ public Builder(String appId) { /** * Sets the encryption key used to encrypt user meta data only. Individual Realms needs to - * use {@link SyncConfiguration.Builder#encryptionKey(byte[])} to make them encrypted. + * use {@link io.realm.mongodb.sync.SyncConfiguration.Builder#encryptionKey(byte[])} to make them encrypted. * * @param key a 64 byte encryption key. * @throws IllegalArgumentException if the key is not 64 bytes long. @@ -396,8 +407,8 @@ public Builder addCustomRequestHeaders(@Nullable Map headers) { * session. *

              * This default can be overridden by calling - * {@link SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler)} when creating - * the {@link SyncConfiguration}. + * {@link io.realm.mongodb.sync.SyncConfiguration.Builder#errorHandler(SyncSession.ErrorHandler)} when creating + * the {@link io.realm.mongodb.sync.SyncConfiguration}. * * @param errorHandler the default error handler. */ @@ -441,7 +452,18 @@ private URL createUrl(String baseUrl) { } } - // FIXME Doc + /** + * Set the default codec registry used to encode and decode BSON arguments and results when + * calling remote Realm {@link io.realm.mongodb.functions.Functions} and accessing a remote + * {@link io.realm.mongodb.mongo.MongoDatabase}. + *

              + * Will default to {@link #DEFAULT_BSON_CODEC_REGISTRY} if not specified. + * + * @param codecRegistry The default codec registry for the App. + * + * @see #DEFAULT_BSON_CODEC_REGISTRY + * @see Builder#getDefaultCodecRegistry() + */ public Builder codecRegistry(CodecRegistry codecRegistry) { Util.checkNull(codecRegistry, "codecRegistry"); this.codecRegistry = codecRegistry; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java index 90c66226be..5aac33bb54 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java @@ -32,7 +32,7 @@ import io.realm.mongodb.User; /** - * A Functions manager to call MongoDB Realm functions. + * A Functions manager to call remote Realm functions for the associated Realm App. *

              * Arguments and results are encoded/decoded with the Functions' codec registry either * inherited from the {@link AppConfiguration#getDefaultCodecRegistry()} or set explicitly @@ -62,12 +62,12 @@ protected Functions(User user, CodecRegistry codecRegistry) { * Call a MongoDB Realm function synchronously with custom codec registry encoding/decoding * arguments/results. * - * @param name Name of the Stitch function to call. - * @param args Arguments to the Stitch function. + * @param name Name of the Realm function to call. + * @param args Arguments to the Realm function. * @param resultClass The type that the functions result should be converted to. * @param codecRegistry Codec registry to use for argument encoding and result decoding. * @param The type that the response will be decoded as using the {@code codecRegistry}. - * @return Result of the Stitch function. + * @return Result of the Realm function. * * @throws ObjectServerError if the request failed in some way. * @@ -82,11 +82,11 @@ public ResultT callFunction(String name, List args, Class * Call a MongoDB Realm function synchronously with default codec registry encoding/decoding * arguments/results. * - * @param name Name of the Stitch function to call. - * @param args Arguments to the Stitch function. + * @param name Name of the Realm function to call. + * @param args Arguments to the Realm function. * @param resultClass The type that the functions result should be converted to. * @param The type that the response will be decoded as using the default codec registry. - * @return Result of the Stitch function. + * @return Result of the Realm function. * * @throws ObjectServerError if the request failed in some way. * @@ -102,11 +102,11 @@ public ResultT callFunction(String name, List args, Class *

              * The arguments will be encoded with the default codec registry encoding. * - * @param name Name of the Stitch function to call. - * @param args Arguments to the Stitch function. + * @param name Name of the Realm function to call. + * @param args Arguments to the Realm function. * @param resultDecoder The decoder used to decode the result. * @param The type that the response will be decoded as using the {@code resultDecoder} - * @return Result of the Stitch function. + * @return Result of the Realm function. * * @throws ObjectServerError if the request failed in some way. * @@ -123,13 +123,13 @@ public ResultT callFunction(String name, List args, Decoder * This is the asynchronous equivalent of {@link #callFunction(String, List, Class, CodecRegistry)}. * - * @param name Name of the Stitch function to call. - * @param args Arguments to the Stitch function. + * @param name Name of the Realm function to call. + * @param args Arguments to the Realm function. * @param resultClass The type that the functions result should be converted to. * @param codecRegistry Codec registry to use for argument encoding and result decoding. * @param callback The callback that will receive the result or any errors from the request. * @param The type that the response will be decoded as using the default codec registry. - * @return Result of the Stitch function. + * @return Result of the Realm function. * * @throws IllegalStateException if not called on a looper thread. * @@ -154,12 +154,12 @@ public T run() throws ObjectServerError { *

              * This is the asynchronous equivalent of {@link #callFunction(String, List, Class)}. * - * @param name Name of the Stitch function to call. - * @param args Arguments to the Stitch function. + * @param name Name of the Realm function to call. + * @param args Arguments to the Realm function. * @param resultClass The type that the functions result should be converted to. * @param callback The callback that will receive the result or any errors from the request. * @param The type that the response will be decoded as using the default codec registry. - * @return Result of the Stitch function. + * @return Result of the Realm function. * * @throws IllegalStateException if not called on a looper thread. * @@ -176,12 +176,12 @@ public RealmAsyncTask callFunctionAsync(String name, List args, Class *

              * This is the asynchronous equivalent of {@link #callFunction(String, List, Decoder)}. * - * @param name Name of the Stitch function to call. - * @param args Arguments to the Stitch function. + * @param name Name of the Realm function to call. + * @param args Arguments to the Realm function. * @param resultDecoder The decoder used to decode the result. * @param callback The callback that will receive the result or any errors from the request. * @param The type that the response will be decoded as using the {@code resultDecoder} - * @return Result of the Stitch function. + * @return Result of the Realm function. * * @throws IllegalStateException if not called on a looper thread. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index 5400ce99e6..77af94b1e7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -993,7 +993,6 @@ public SyncConfiguration build() { clientResyncMode = ClientResyncMode.MANUAL; } - // FIXME How to get access to this if (rxFactory == null && Util.isRxJavaAvailable()) { rxFactory = new RealmObservableFactory(true); } From 630114ddbd7abb868e126faac7413ea6e8985c02 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 4 Jun 2020 13:49:16 +0200 Subject: [PATCH 1564/2110] Rename ObjectServerError to AppException (#6911) --- .../kotlin/io/realm/ApiKeyAuthTests.kt | 18 +++---- .../kotlin/io/realm/AppTests.kt | 2 +- .../kotlin/io/realm/CredentialsTests.kt | 2 +- .../kotlin/io/realm/EmailPasswordAuthTests.kt | 14 ++--- .../kotlin/io/realm/UserTests.kt | 2 +- .../io/realm/mongodb/MongoClientTest.kt | 2 +- .../io/realm/mongodb/sync/SessionTests.kt | 12 ++--- .../mongodb/sync/SyncConfigurationTests.kt | 4 +- .../transport/OsJavaNetworkTransportTests.kt | 6 +-- .../kotlin/io/realm/util/KotlinTestUtils.kt | 10 ++-- .../realm/internal/jni/JniBsonProtocol.java | 12 ++--- .../internal/jni/OsJNIResultCallback.java | 10 ++-- .../io/realm/internal/mongodb/Request.java | 10 ++-- .../realm/internal/network/ResultHandler.java | 4 +- .../internal/objectstore/OsAsyncOpenTask.java | 4 +- .../objectstore/OsMongoCollection.java | 16 +++--- .../java/io/realm/mongodb/App.java | 18 +++---- .../io/realm/mongodb/AppConfiguration.java | 2 +- ...jectServerError.java => AppException.java} | 18 +++---- .../java/io/realm/mongodb/FunctionsImpl.java | 2 +- .../java/io/realm/mongodb/User.java | 20 +++---- .../io/realm/mongodb/auth/ApiKeyAuth.java | 50 ++++++++--------- .../realm/mongodb/auth/EmailPasswordAuth.java | 54 +++++++++---------- .../io/realm/mongodb/functions/Functions.java | 12 ++--- .../mongodb/mongo/iterable/MongoIterable.java | 6 +-- .../sync/ClientResetRequiredError.java | 6 +-- .../realm/mongodb/sync/ClientResyncMode.java | 6 +-- .../io/realm/mongodb/sync/SyncSession.java | 14 ++--- 28 files changed, 168 insertions(+), 168 deletions(-) rename realm/realm-library/src/objectServer/java/io/realm/mongodb/{ObjectServerError.java => AppException.java} (89%) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt index 86b0ba6c84..41da93fe9c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt @@ -106,7 +106,7 @@ class ApiKeyAuthTests { try { provider.createApiKey("%s") fail() - } catch (e: ObjectServerError) { + } catch (e: AppException) { assertEquals(ErrorCode.INVALID_PARAMETER, e.errorCode) } } @@ -163,7 +163,7 @@ class ApiKeyAuthTests { try { provider.fetchApiKey(ObjectId()) fail() - } catch (e: ObjectServerError) { + } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) } } @@ -224,7 +224,7 @@ class ApiKeyAuthTests { try { provider.fetchApiKey(key1.id) fail() - } catch (e: ObjectServerError) { + } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) } } @@ -234,7 +234,7 @@ class ApiKeyAuthTests { try { provider.deleteApiKey(ObjectId()) fail() - } catch (e: ObjectServerError) { + } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) } } @@ -257,7 +257,7 @@ class ApiKeyAuthTests { try { provider.fetchApiKey(key.id) fail() - } catch (e: ObjectServerError) { + } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) } looperThread.testComplete() @@ -305,7 +305,7 @@ class ApiKeyAuthTests { try { provider.enableApiKey(ObjectId()) fail() - } catch (e: ObjectServerError) { + } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) } } @@ -368,7 +368,7 @@ class ApiKeyAuthTests { try { provider.disableApiKey(ObjectId()) fail() - } catch (e: ObjectServerError) { + } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) } } @@ -423,7 +423,7 @@ class ApiKeyAuthTests { Method.DISABLE -> provider.disableApiKey(ObjectId()) } fail("$method should have thrown an exception") - } catch (error: ObjectServerError) { + } catch (error: AppException) { assertEquals(ErrorCode.NETWORK_UNKNOWN, error.errorCode) } } @@ -461,7 +461,7 @@ class ApiKeyAuthTests { Method.DISABLE -> provider.disableApiKey(ObjectId()) } fail("$method should have thrown an exception") - } catch (error: ObjectServerError) { + } catch (error: AppException) { assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index ec2083aaac..46548dfb7e 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -65,7 +65,7 @@ class AppTests { try { app.login(credentials) fail() - } catch(ex: ObjectServerError) { + } catch(ex: AppException) { assertEquals(ErrorCode.SERVICE_UNKNOWN, ex.errorCode) } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index 50a06ba783..3a09b16863 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -155,7 +155,7 @@ class CredentialsTests { try { app.login(credentials) fail() - } catch (error: ObjectServerError) { + } catch (error: AppException) { assertEquals(expectedCode, error.errorCode) } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index 439520d9cf..8485a2784a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -110,7 +110,7 @@ class EmailPasswordAuthTests { try { provider.registerUser("invalid-email", "1234") fail() - } catch (ex: ObjectServerError) { + } catch (ex: AppException) { assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) } } @@ -161,7 +161,7 @@ class EmailPasswordAuthTests { try { provider.confirmUser("invalid-token", "invalid-token-id") fail() - } catch (ex: ObjectServerError) { + } catch (ex: AppException) { assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) } } @@ -241,7 +241,7 @@ class EmailPasswordAuthTests { try { provider.resendConfirmationEmail("foo") fail() - } catch (error: ObjectServerError) { + } catch (error: AppException) { assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) } finally { admin.setAutomaticConfirmation(true) @@ -309,7 +309,7 @@ class EmailPasswordAuthTests { try { provider.sendResetPasswordEmail("unknown@10gen.com") fail() - } catch (error: ObjectServerError) { + } catch (error: AppException) { assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) } } @@ -386,7 +386,7 @@ class EmailPasswordAuthTests { provider.registerUser(email, "123456") try { provider.callResetPasswordFunction(email, "new-password", "wrong-magic-word") - } catch (error: ObjectServerError) { + } catch (error: AppException) { assertEquals(ErrorCode.SERVICE_UNKNOWN, error.errorCode) } finally { admin.setResetFunction(enabled = false) @@ -448,7 +448,7 @@ class EmailPasswordAuthTests { val provider = app.emailPasswordAuth try { provider.resetPassword("invalid-token", "invalid-token-id", "new-password") - } catch (error: ObjectServerError) { + } catch (error: AppException) { assertEquals(ErrorCode.BAD_REQUEST, error.errorCode) } } @@ -501,7 +501,7 @@ class EmailPasswordAuthTests { Method.RESET_PASSWORD -> provider.resetPassword("token", "token-id", "password") } fail("$method should have thrown an exception") - } catch (error: ObjectServerError) { + } catch (error: AppException) { assertEquals(ErrorCode.NETWORK_UNKNOWN, error.errorCode) } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt index 833cd5870b..841b8ce191 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt @@ -153,7 +153,7 @@ class UserTests { try { anonymousUser.linkCredentials(Credentials.emailPassword(email, password)) fail() - } catch (ex: ObjectServerError) { + } catch (ex: AppException) { assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt index aad9db46e4..c5c2e748e0 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt @@ -972,7 +972,7 @@ class MongoClientTest { var coll = withDocumentClass(CustomType::class.java) assertEquals(CustomType::class.java, coll.documentClass) - assertFailsWith(ObjectServerError::class) { + assertFailsWith(AppException::class) { coll.insertOne(expected).blockingGetResult() } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt index 07c9d8c1fb..29a2fae73f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt @@ -127,7 +127,7 @@ class SessionTests { fun errorHandler_clientResetReported() = looperThread.runBlocking { val config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) - .errorHandler { session: SyncSession, error: ObjectServerError -> + .errorHandler { session: SyncSession, error: AppException -> if (error.errorCode != ErrorCode.CLIENT_RESET) { fail("Wrong error $error") return@errorHandler @@ -156,7 +156,7 @@ class SessionTests { val config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) - .errorHandler { session: SyncSession?, error: ObjectServerError -> + .errorHandler { session: SyncSession?, error: AppException -> if (error.errorCode != ErrorCode.CLIENT_RESET) { fail("Wrong error $error") return@errorHandler @@ -192,7 +192,7 @@ class SessionTests { val config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) .schema(StringOnly::class.java) - .errorHandler { session: SyncSession?, error: ObjectServerError -> + .errorHandler { session: SyncSession?, error: AppException -> if (error.errorCode != ErrorCode.CLIENT_RESET) { fail("Wrong error $error") return@errorHandler @@ -244,7 +244,7 @@ class SessionTests { val resources = ResourceContainer() val config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) - .errorHandler { session: SyncSession?, error: ObjectServerError -> + .errorHandler { session: SyncSession?, error: AppException -> if (error.errorCode != ErrorCode.CLIENT_RESET) { fail("Wrong error $error") return@errorHandler @@ -315,7 +315,7 @@ class SessionTests { .clientResyncMode(ClientResyncMode.MANUAL) .encryptionKey(randomKey) .modules(StringOnlyModule()) - .errorHandler { session: SyncSession?, error: ObjectServerError -> + .errorHandler { session: SyncSession?, error: AppException -> if (error.errorCode != ErrorCode.CLIENT_RESET) { fail("Wrong error $error") return@errorHandler @@ -449,7 +449,7 @@ class SessionTests { fun unrecognizedErrorCode_errorHandler() { val errorHandlerCalled = AtomicBoolean(false) configuration = configFactory.createSyncConfigurationBuilder(user) - .errorHandler { session: SyncSession?, error: ObjectServerError -> + .errorHandler { session: SyncSession?, error: AppException -> errorHandlerCalled.set(true) assertEquals(ErrorCode.UNKNOWN, error.errorCode) assertEquals(ErrorCode.Category.FATAL, error.category) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index a90965b22a..59c255167b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -23,7 +23,7 @@ import io.realm.entities.StringOnly import io.realm.entities.StringOnlyModule import io.realm.kotlin.createObject import io.realm.kotlin.where -import io.realm.mongodb.ObjectServerError +import io.realm.mongodb.AppException import io.realm.mongodb.User import io.realm.mongodb.close import io.realm.mongodb.registerUserAndLogin @@ -63,7 +63,7 @@ class SyncConfigurationTests { fun errorHandler() { val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) val errorHandler: SyncSession.ErrorHandler = object : SyncSession.ErrorHandler { - override fun onError(session: SyncSession, error: ObjectServerError) {} + override fun onError(session: SyncSession, error: AppException) {} } val config = builder.errorHandler(errorHandler).build() assertEquals(errorHandler, config.errorHandler) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt index 1ca87d2ca4..a63ce80bf7 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -135,7 +135,7 @@ class OsJavaNetworkTransportTests { try { app.login(creds) fail() - } catch (ex: ObjectServerError) { + } catch (ex: AppException) { assertEquals(ErrorCode.AUTH_ERROR, ex.errorCode) assertEquals(ErrorCode.Type.SERVICE, ex.errorType) } @@ -155,7 +155,7 @@ class OsJavaNetworkTransportTests { try { app.login(creds) fail() - } catch (ex: ObjectServerError) { + } catch (ex: AppException) { assertEquals(ErrorCode.INTERNAL_SERVER_ERROR, ex.errorCode) assertEquals(ErrorCode.Type.HTTP, ex.errorType) } @@ -174,7 +174,7 @@ class OsJavaNetworkTransportTests { try { app.login(creds) fail() - } catch (ex: ObjectServerError) { + } catch (ex: AppException) { assertEquals(ErrorCode.NETWORK_IO_EXCEPTION, ex.errorCode) assertEquals(ErrorCode.Type.JAVA, ex.errorType) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt index 343266315d..f34cd55a1a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/KotlinTestUtils.kt @@ -2,7 +2,7 @@ package io.realm.util import android.util.ArraySet import io.realm.mongodb.ErrorCode -import io.realm.mongodb.ObjectServerError +import io.realm.mongodb.AppException import org.hamcrest.Matcher import org.hamcrest.MatcherAssert.assertThat import org.junit.rules.ErrorCollector @@ -15,16 +15,16 @@ import kotlin.test.fail // Helper methods for improving Kotlin unit tests. /** - * Verify that an [ObjectServerError] exception is thrown with a specific [ErrorCode] + * Verify that an [AppException] exception is thrown with a specific [ErrorCode] */ inline fun assertFailsWithErrorCode( expectedCode: ErrorCode, method: () -> Unit -): ObjectServerError { - return assertFailsWith(ObjectServerError::class) { +): AppException { + return assertFailsWith(AppException::class) { method() fail() - }.also { e: ObjectServerError -> + }.also { e: AppException -> assertEquals(expectedCode, e.errorCode, "Unexpected error code") assertNotNull(e.errorMessage) } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java index b80b8c3fde..5bd92c0050 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/JniBsonProtocol.java @@ -33,7 +33,7 @@ import java.io.StringWriter; import io.realm.mongodb.ErrorCode; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; /** * Protocol for passing {@link BsonValue}s to JNI. @@ -67,9 +67,9 @@ public static String encode(T value, Encoder encoder) { // same exception as in the guard above, but needed here as well nonetheless as the // result might be wrapped inside an iterable or a map and the codec for the end type // might be missing - throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve encoder for end type", e); + throw new AppException(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve encoder for end type", e); } catch (Exception e) { - throw new ObjectServerError(ErrorCode.BSON_ENCODING, "Error encoding value", e); + throw new AppException(ErrorCode.BSON_ENCODING, "Error encoding value", e); } } @@ -91,9 +91,9 @@ public static T decode(String string, Decoder decoder) { // same exception as in the guard above, but needed here as well nonetheless as the // result might be wrapped inside an iterable or a map and the codec for the end type // might be missing - throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve decoder for end type" + string, e); + throw new AppException(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve decoder for end type" + string, e); } catch (Exception e) { - throw new ObjectServerError(ErrorCode.BSON_DECODING, "Error decoding value " + string, e); + throw new AppException(ErrorCode.BSON_DECODING, "Error decoding value " + string, e); } } @@ -101,7 +101,7 @@ public static Codec getCodec(Class clz, CodecRegistry registry) { try { return registry.get(clz); } catch (CodecConfigurationException e) { - throw new ObjectServerError(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve codec for " + clz.getSimpleName(), e); + throw new AppException(ErrorCode.BSON_CODEC_NOT_FOUND, "Could not resolve codec for " + clz.getSimpleName(), e); } } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java index 7ed3286e4f..2b383e30cb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIResultCallback.java @@ -21,7 +21,7 @@ import javax.annotation.Nullable; import io.realm.mongodb.ErrorCode; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; import io.realm.internal.Keep; import io.realm.internal.objectstore.OsJavaNetworkTransport; @@ -31,9 +31,9 @@ public abstract class OsJNIResultCallback extends OsJavaNetworkTransport.NetworkTransportJNIResultCallback { private final AtomicReference success; - private final AtomicReference error; + private final AtomicReference error; - public OsJNIResultCallback(@Nullable AtomicReference success, AtomicReference error) { + public OsJNIResultCallback(@Nullable AtomicReference success, AtomicReference error) { this.success = success; this.error = error; } @@ -55,9 +55,9 @@ public void onError(String nativeErrorCategory, int nativeErrorCode, String erro if (code == ErrorCode.UNKNOWN) { // In case of UNKNOWN errors parse as much error information on as possible. String detailedErrorMessage = String.format("{%s::%s} %s", nativeErrorCategory, nativeErrorCode, errorMessage); - error.set(new ObjectServerError(code, detailedErrorMessage)); + error.set(new AppException(code, detailedErrorMessage)); } else { - error.set(new ObjectServerError(code, errorMessage)); + error.set(new AppException(code, errorMessage)); } } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/mongodb/Request.java b/realm/realm-library/src/objectServer/java/io/realm/internal/mongodb/Request.java index 11c908adfe..14a77f1a93 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/mongodb/Request.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/mongodb/Request.java @@ -29,7 +29,7 @@ import io.realm.log.RealmLog; import io.realm.mongodb.App; import io.realm.mongodb.ErrorCode; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; // Class wrapping requests made against MongoDB Realm. Is also responsible for calling with success/error on the // correct thread. @@ -46,7 +46,7 @@ public Request(ThreadPoolExecutor networkPoolExecutor, @Nullable App.Callback } // Implements the request. Return the current sync user if the request succeeded. Otherwise throw an error. - public abstract T run() throws ObjectServerError; + public abstract T run() throws AppException; // Start the request public RealmAsyncTask start() { @@ -55,17 +55,17 @@ public RealmAsyncTask start() { public void run() { try { postSuccess(Request.this.run()); - } catch (ObjectServerError e) { + } catch (AppException e) { postError(e); } catch (Throwable e) { - postError(new ObjectServerError(ErrorCode.UNKNOWN, "Unexpected error", e)); + postError(new AppException(ErrorCode.UNKNOWN, "Unexpected error", e)); } } }); return new RealmAsyncTaskImpl(authenticateRequest, networkPoolExecutor); } - private void postError(final ObjectServerError error) { + private void postError(final AppException error) { boolean errorHandled = false; if (callback != null) { Runnable action = new Runnable() { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java index b81d7b75b2..f60f63b3d3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ResultHandler.java @@ -20,13 +20,13 @@ import javax.annotation.Nullable; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; public class ResultHandler { // Handle returning the correct result or throw an exception. Must be separated from // OsJNIResultCallback due to how the Object Store callbacks work. - public static T handleResult(@Nullable AtomicReference success, AtomicReference error) { + public static T handleResult(@Nullable AtomicReference success, AtomicReference error) { if (error.get() != null) { throw error.get(); } else { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java index de115ea771..b7afa67154 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAsyncOpenTask.java @@ -6,7 +6,7 @@ import java.util.concurrent.atomic.AtomicReference; import io.realm.mongodb.ErrorCode; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; import io.realm.internal.KeepMember; import io.realm.internal.OsRealmConfig; @@ -40,7 +40,7 @@ public void start(long timeOut, TimeUnit unit) throws InterruptedException { String errorMessage = error.get(); if (errorMessage != null) { - throw new ObjectServerError(ErrorCode.UNKNOWN, errorMessage); + throw new AppException(ErrorCode.UNKNOWN, errorMessage); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java index 8d054dc0b5..238739a612 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java @@ -38,7 +38,7 @@ import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.network.ResultHandler; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; import io.realm.mongodb.mongo.iterable.AggregateIterable; import io.realm.mongodb.mongo.iterable.FindIterable; import io.realm.mongodb.mongo.options.CountOptions; @@ -127,7 +127,7 @@ public Long count(final Bson filter, final CountOptions options) { private Long countInternal(final Bson filter, @Nullable final CountOptions options) { AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected Long mapSuccess(Object result) { @@ -232,7 +232,7 @@ private ResultT findOneInternal(final int type, @Nullable final FindOptions options, final Class resultClass) { AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected ResultT mapSuccess(Object result) { @@ -266,7 +266,7 @@ protected ResultT mapSuccess(Object result) { public InsertOneResult insertOne(final DocumentT document) { AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected InsertOneResult mapSuccess(Object result) { @@ -282,7 +282,7 @@ protected InsertOneResult mapSuccess(Object result) { public InsertManyResult insertMany(final List documents) { AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected InsertManyResult mapSuccess(Object result) { @@ -312,7 +312,7 @@ public DeleteResult deleteMany(final Bson filter) { private DeleteResult deleteInternal(final int type, final Bson filter) { AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected DeleteResult mapSuccess(Object result) { @@ -359,7 +359,7 @@ private UpdateResult updateInternal(final int type, final Bson update, @Nullable final UpdateOptions options) { AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected UpdateResult mapSuccess(Object result) { @@ -466,7 +466,7 @@ private ResultT findOneAndModify(final int type, @Nullable final FindOneAndModifyOptions options, final Class resultClass) { AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected ResultT mapSuccess(Object result) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index 50ad3dd89a..4e3854aed4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -361,12 +361,12 @@ public User switchUser(User user) { * * @param credentials the credentials representing the type of login. * @return a {@link User} representing the logged in user. - * @throws ObjectServerError if the user could not be logged in. + * @throws AppException if the user could not be logged in. */ - public User login(Credentials credentials) throws ObjectServerError { + public User login(Credentials credentials) throws AppException { Util.checkNull(credentials, "credentials"); AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); nativeLogin(nativePtr, credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { @Override protected User mapSuccess(Object result) { @@ -422,7 +422,7 @@ public RealmAsyncTask loginAsync(Credentials credentials, Callback callbac Util.checkLooperThread("Asynchronous log in is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public User run() throws ObjectServerError { + public User run() throws AppException { return login(credentials); } }.start(); @@ -532,9 +532,9 @@ OsJavaNetworkTransport getNetworkTransport() { */ public static class Result { private T result; - private ObjectServerError error; + private AppException error; - private Result(@Nullable T result, @Nullable ObjectServerError exception) { + private Result(@Nullable T result, @Nullable AppException exception) { this.result = result; this.error = exception; } @@ -561,7 +561,7 @@ public static Result withResult(T result) { * * @param exception error that occurred. */ - public static Result withError(ObjectServerError exception) { + public static Result withError(AppException exception) { return new Result<>(null, exception); } @@ -612,9 +612,9 @@ public T getOrThrow() { /** * Returns the error in case of a failed request. * - * @return the {@link ObjectServerError} in case of a failed request. + * @return the {@link AppException} in case of a failed request. */ - public ObjectServerError getError() { + public AppException getError() { return error; } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index cac841279f..fe3b590298 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -240,7 +240,7 @@ public static class Builder { private URL baseUrl = createUrl(DEFAULT_BASE_URL); private SyncSession.ErrorHandler defaultErrorHandler = new SyncSession.ErrorHandler() { @Override - public void onError(SyncSession session, ObjectServerError error) { + public void onError(SyncSession session, AppException error) { if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { RealmLog.error("Client Reset required for: " + session.getConfiguration().getServerUrl()); return; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppException.java similarity index 89% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/AppException.java index f8d790a2dc..4cae1cac48 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ObjectServerError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppException.java @@ -23,7 +23,7 @@ import io.realm.mongodb.sync.SyncSession; /** - * This class is a wrapper for all errors happening when communicating with the Realm Object Server. + * This class is a wrapper for all errors happening when communicating with a MongoDB Realm app. * This include both exceptions and protocol errors. * * Only {@link #getErrorCode()} is guaranteed to contain a value. If the error was caused by an underlying exception @@ -33,7 +33,7 @@ * @see ErrorCode for a list of possible errors. */ @Beta -public class ObjectServerError extends RuntimeException { +public class AppException extends RuntimeException { // The Java representation of the error. private final ErrorCode error; @@ -52,7 +52,7 @@ public class ObjectServerError extends RuntimeException { * @param errorCode error code for this type of error. * @param errorMessage detailed error message. */ - public ObjectServerError(ErrorCode errorCode, String errorMessage) { + public AppException(ErrorCode errorCode, String errorMessage) { this(errorCode, errorCode.getType(), errorCode.intValue(), errorMessage, (Throwable) null); } @@ -66,7 +66,7 @@ public ObjectServerError(ErrorCode errorCode, String errorMessage) { * @param errorCode error code for this type of error. * @param errorMessage detailed error message. */ - public ObjectServerError(String errorType, int errorCode, String errorMessage) { + public AppException(String errorType, int errorCode, String errorMessage) { this(ErrorCode.UNKNOWN, errorType, errorCode, errorMessage, null); } @@ -76,7 +76,7 @@ public ObjectServerError(String errorType, int errorCode, String errorMessage) { * @param errorCode error code for this type of error. * @param exception underlying exception causing this error. */ - public ObjectServerError(ErrorCode errorCode, Throwable exception) { + public AppException(ErrorCode errorCode, Throwable exception) { this(errorCode, null, exception); } @@ -87,7 +87,7 @@ public ObjectServerError(ErrorCode errorCode, Throwable exception) { * @param title title for this type of error. * @param hint a hint for resolving the error. */ - public ObjectServerError(ErrorCode errorCode, String title, @Nullable String hint) { + public AppException(ErrorCode errorCode, String title, @Nullable String hint) { this(errorCode, (hint != null) ? title + " : " + hint : title, (Throwable) null); } @@ -98,12 +98,12 @@ public ObjectServerError(ErrorCode errorCode, String title, @Nullable String hin * @param errorMessage detailed error message. * @param exception underlying exception if the error was caused by this. */ - public ObjectServerError(ErrorCode errorCode, @Nullable String errorMessage, @Nullable Throwable exception) { + public AppException(ErrorCode errorCode, @Nullable String errorMessage, @Nullable Throwable exception) { this(errorCode, errorCode.getType(), errorCode.intValue(), errorMessage, exception); } - public ObjectServerError(ErrorCode errorCode, String nativeErrorType, int nativeErrorCode, - @Nullable String errorMessage, @Nullable Throwable exception) { + public AppException(ErrorCode errorCode, String nativeErrorType, int nativeErrorCode, + @Nullable String errorMessage, @Nullable Throwable exception) { this.error = errorCode; this.nativeErrorType = nativeErrorType; this.nativeErrorIntValue = nativeErrorCode; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java index 85f2e2963d..bfb5e719ee 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java @@ -53,7 +53,7 @@ public T invoke(String name, List args, CodecRegistry codecRegistry, Deco // NativePO calling scheme is actually synchronous AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected String mapSuccess(Object result) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index 650225bf67..cd6e9add64 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -292,7 +292,7 @@ public User linkCredentials(Credentials credentials) { Util.checkNull(credentials, "credentials"); checkLoggedIn(); AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); nativeLinkUser(app.nativePtr, osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { @Override protected User mapSuccess(Object result) { @@ -330,7 +330,7 @@ public RealmAsyncTask linkCredentialsAsync(Credentials credentials, App.Callback Util.checkLooperThread("Asynchronous linking identities is only possible from looper threads."); return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override - public User run() throws ObjectServerError { + public User run() throws AppException { return linkCredentials(credentials); } }.start(); @@ -342,13 +342,13 @@ public User run() throws ObjectServerError { * affect the user state on the server. * * @return user that was removed. - * @throws ObjectServerError if called from the UI thread or if the user was logged in, but + * @throws AppException if called from the UI thread or if the user was logged in, but * could not be logged out. */ - public User remove() throws ObjectServerError { + public User remove() throws AppException { boolean loggedIn = isLoggedIn(); AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); nativeRemoveUser(app.nativePtr, osUser.getNativePtr(), new OsJNIResultCallback(success, error) { @Override protected User mapSuccess(Object result) { @@ -375,7 +375,7 @@ public RealmAsyncTask removeAsync(App.Callback callback) { Util.checkLooperThread("Asynchronous removal of users is only possible from looper threads."); return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override - public User run() throws ObjectServerError { + public User run() throws AppException { return remove(); } }.start(); @@ -395,12 +395,12 @@ public User run() throws ObjectServerError { * and will still be returned by {@link App#allUsers()}. They can be removed completely by calling * {@link #remove()}. * - * @throws ObjectServerError if an error occurred while trying to log the user out of the Realm + * @throws AppException if an error occurred while trying to log the user out of the Realm * App. */ - public void logOut() throws ObjectServerError { + public void logOut() throws AppException { boolean loggedIn = isLoggedIn(); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); nativeLogOut(app.nativePtr, osUser.getNativePtr(), new OsJNIVoidResultCallback(error)); ResultHandler.handleResult(null, error); if (loggedIn) { @@ -430,7 +430,7 @@ public RealmAsyncTask logOutAsync(App.Callback callback) { Util.checkLooperThread("Asynchronous log out is only possible from looper threads."); return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override - public User run() throws ObjectServerError { + public User run() throws AppException { logOut(); return User.this; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java index 9e42da1e35..c5b8487afa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java @@ -25,7 +25,7 @@ import io.realm.annotations.Beta; import io.realm.internal.mongodb.Request; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; import io.realm.RealmAsyncTask; import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; @@ -87,13 +87,13 @@ public App getApp() { * The key is enabled when created. It can be disabled by calling {@link #disableApiKey(ObjectId)}. * * @param name the name of the key - * @throws ObjectServerError if the server failed to create the API key. + * @throws AppException if the server failed to create the API key. * @return the new API key for the user. */ - public UserApiKey createApiKey(String name) throws ObjectServerError { + public UserApiKey createApiKey(String name) throws AppException { Util.checkEmpty(name, "name"); AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected UserApiKey mapSuccess(Object result) { @@ -120,7 +120,7 @@ public RealmAsyncTask createApiKeyAsync(String name, App.Callback ca Util.checkLooperThread("Asynchronous creation of api keys are only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public UserApiKey run() throws ObjectServerError { + public UserApiKey run() throws AppException { return createApiKey(name); } }.start(); @@ -130,12 +130,12 @@ public UserApiKey run() throws ObjectServerError { * Fetches a specific user API key associated with the user. * * @param id the id of the key to fetch. - * @throws ObjectServerError if the server failed to fetch the API key. + * @throws AppException if the server failed to fetch the API key. */ - public UserApiKey fetchApiKey(ObjectId id) throws ObjectServerError { + public UserApiKey fetchApiKey(ObjectId id) throws AppException { Util.checkNull(id, "id"); AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_FETCH_SINGLE, id.toHexString(), new OsJNIResultCallback(success, error) { @Override protected UserApiKey mapSuccess(Object result) { @@ -157,7 +157,7 @@ public RealmAsyncTask fetchApiKeyAsync(ObjectId id, App.Callback cal Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public UserApiKey run() throws ObjectServerError { + public UserApiKey run() throws AppException { return fetchApiKey(id); } }.start(); @@ -166,11 +166,11 @@ public UserApiKey run() throws ObjectServerError { /** * Fetches all API keys associated with the user. * - * @throws ObjectServerError if the server failed to fetch the API keys. + * @throws AppException if the server failed to fetch the API keys. */ - public List fetchAllApiKeys() throws ObjectServerError { + public List fetchAllApiKeys() throws AppException { AtomicReference> success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_FETCH_ALL, null, new OsJNIResultCallback>(success, error) { @Override protected List mapSuccess(Object result) { @@ -197,7 +197,7 @@ public RealmAsyncTask fetchAllApiKeys(App.Callback> callback) { Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); return new Request>(NETWORK_POOL_EXECUTOR, callback) { @Override - public List run() throws ObjectServerError { + public List run() throws AppException { return fetchAllApiKeys(); } }.start(); @@ -207,11 +207,11 @@ public List run() throws ObjectServerError { * Deletes a specific API key created by the user. * * @param id the id of the key to delete. - * @throws ObjectServerError if the server failed to delete the API key. + * @throws AppException if the server failed to delete the API key. */ - public void deleteApiKey(ObjectId id) throws ObjectServerError { + public void deleteApiKey(ObjectId id) throws AppException { Util.checkNull(id, "id"); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_DELETE, id.toHexString(), new OsJNIVoidResultCallback(error)); ResultHandler.handleResult(null, error); } @@ -228,7 +228,7 @@ public RealmAsyncTask deleteApiKeyAsync(ObjectId id, App.Callback callback Util.checkLooperThread("Asynchronous deleting an api key is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public Void run() throws ObjectServerError { + public Void run() throws AppException { deleteApiKey(id); return null; } @@ -239,11 +239,11 @@ public Void run() throws ObjectServerError { * Disables a specific API key created by the user. * * @param id the id of the key to disable. - * @throws ObjectServerError if the server failed to disable the API key. + * @throws AppException if the server failed to disable the API key. */ - public void disableApiKey(ObjectId id) throws ObjectServerError { + public void disableApiKey(ObjectId id) throws AppException { Util.checkNull(id, "id"); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_DISABLE, id.toHexString(), new OsJNIVoidResultCallback(error)); ResultHandler.handleResult(null, error); } @@ -260,7 +260,7 @@ public RealmAsyncTask disableApiKeyAsync(ObjectId id, App.Callback callbac Util.checkLooperThread("Asynchronous disabling an api key is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public Void run() throws ObjectServerError { + public Void run() throws AppException { disableApiKey(id); return null; } @@ -271,11 +271,11 @@ public Void run() throws ObjectServerError { * Enables a specific API key created by the user. * * @param id the id of the key to enable. - * @throws ObjectServerError if the server failed to enable the API key. + * @throws AppException if the server failed to enable the API key. */ - public void enableApiKey(ObjectId id) throws ObjectServerError { + public void enableApiKey(ObjectId id) throws AppException { Util.checkNull(id, "id"); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_ENABLE, id.toHexString(), new OsJNIVoidResultCallback(error)); ResultHandler.handleResult(null, error); } @@ -292,7 +292,7 @@ public RealmAsyncTask enableApiKeyAsync(ObjectId id, App.Callback callback Util.checkLooperThread("Asynchronous enabling an api key is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public Void run() throws ObjectServerError { + public Void run() throws AppException { enableApiKey(id); return null; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java index c738b7dc90..c26157b28c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java @@ -20,7 +20,7 @@ import io.realm.annotations.Beta; import io.realm.internal.mongodb.Request; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; import io.realm.RealmAsyncTask; import io.realm.internal.network.ResultHandler; import io.realm.internal.Util; @@ -64,12 +64,12 @@ protected EmailPasswordAuth(App app) { * @param password the password to associate with the email. The password must be between * 6 and 128 characters long. * - * @throws ObjectServerError if the server failed to register the user. + * @throws AppException if the server failed to register the user. */ - public void registerUser(String email, String password) throws ObjectServerError { + public void registerUser(String email, String password) throws AppException { Util.checkEmpty(email, "email"); Util.checkEmpty(password, "password"); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_REGISTER_USER, new OsJNIVoidResultCallback(error), email, password); ResultHandler.handleResult(null, error); } @@ -84,13 +84,13 @@ public void registerUser(String email, String password) throws ObjectServerError * happen on the same thread as this method is called on. * * @throws IllegalStateException if called from a non-looper thread. - * @throws ObjectServerError if the server failed to register the user. + * @throws AppException if the server failed to register the user. */ public RealmAsyncTask registerUserAsync(String email, String password, App.Callback callback) { Util.checkLooperThread("Asynchronous registration of a user is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public Void run() throws ObjectServerError { + public Void run() throws AppException { registerUser(email, password); return null; } @@ -102,12 +102,12 @@ public Void run() throws ObjectServerError { * * @param token the confirmation token. * @param tokenId the id of the confirmation token. - * @throws ObjectServerError if the server failed to confirm the user. + * @throws AppException if the server failed to confirm the user. */ - public void confirmUser(String token, String tokenId) throws ObjectServerError { + public void confirmUser(String token, String tokenId) throws AppException { Util.checkEmpty(token, "token"); Util.checkEmpty(tokenId, "tokenId"); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_CONFIRM_USER, new OsJNIVoidResultCallback(error), token, tokenId); ResultHandler.handleResult(null, error); } @@ -125,7 +125,7 @@ public RealmAsyncTask confirmUserAsync(String token, String tokenId, App.Callbac Util.checkLooperThread("Asynchronous confirmation of a user is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public Void run() throws ObjectServerError { + public Void run() throws AppException { confirmUser(token, tokenId); return null; } @@ -136,11 +136,11 @@ public Void run() throws ObjectServerError { * Resend the confirmation for a user to the given email. * * @param email the email of the user. - * @throws ObjectServerError if the server failed to confirm the user. + * @throws AppException if the server failed to confirm the user. */ - public void resendConfirmationEmail(String email) throws ObjectServerError { + public void resendConfirmationEmail(String email) throws AppException { Util.checkEmpty(email, "email"); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_RESEND_CONFIRMATION_EMAIL, new OsJNIVoidResultCallback(error), email); ResultHandler.handleResult(null, error); } @@ -157,7 +157,7 @@ public RealmAsyncTask resendConfirmationEmailAsync(String email, App.Callback(NETWORK_POOL_EXECUTOR, callback) { @Override - public Void run() throws ObjectServerError { + public Void run() throws AppException { resendConfirmationEmail(email); return null; } @@ -168,11 +168,11 @@ public Void run() throws ObjectServerError { * Sends a user a password reset email for the given email. * * @param email the email of the user. - * @throws ObjectServerError if the server failed to confirm the user. + * @throws AppException if the server failed to confirm the user. */ - public void sendResetPasswordEmail(String email) throws ObjectServerError { + public void sendResetPasswordEmail(String email) throws AppException { Util.checkEmpty(email, "email"); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_SEND_RESET_PASSWORD_EMAIL, new OsJNIVoidResultCallback(error), email); ResultHandler.handleResult(null, error); } @@ -183,13 +183,13 @@ public void sendResetPasswordEmail(String email) throws ObjectServerError { * @param email the email of the user. * @param callback callback when sending the email has completed or failed. The callback will * always happen on the same thread as this method is called on. - * @throws ObjectServerError if the server failed to confirm the user. + * @throws AppException if the server failed to confirm the user. */ public RealmAsyncTask sendResetPasswordEmailAsync(String email, App.Callback callback) { Util.checkLooperThread("Asynchronous sending the reset password email is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public Void run() throws ObjectServerError { + public Void run() throws AppException { sendResetPasswordEmail(email); return null; } @@ -204,13 +204,13 @@ public Void run() throws ObjectServerError { * @param newPassword the new password of the user. * @param args any additional arguments provided to the reset function. All arguments must * be able to be converted to JSON compatible values using {@code toString()}. - * @throws ObjectServerError if the server failed to confirm the user. + * @throws AppException if the server failed to confirm the user. */ - public void callResetPasswordFunction(String email, String newPassword, Object... args) throws ObjectServerError { + public void callResetPasswordFunction(String email, String newPassword, Object... args) throws AppException { Util.checkEmpty(email, "email"); Util.checkEmpty(newPassword, "newPassword"); String encodedArgs = JniBsonProtocol.encode(Arrays.asList(args), app.getConfiguration().getDefaultCodecRegistry()); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_CALL_RESET_PASSWORD_FUNCTION, new OsJNIVoidResultCallback(error), email, newPassword, encodedArgs); ResultHandler.handleResult(null, error); } @@ -231,7 +231,7 @@ public RealmAsyncTask callResetPasswordFunctionAsync(String email, String newPas Util.checkLooperThread("Asynchronous calling the password reset function is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public Void run() throws ObjectServerError { + public Void run() throws AppException { callResetPasswordFunction(email, newPassword, args); return null; } @@ -245,13 +245,13 @@ public Void run() throws ObjectServerError { * @param tokenId the id of the reset password token. * @param newPassword the new password for the user identified by the {@code token}. The password * must be between 6 and 128 characters long. - * @throws ObjectServerError if the server failed to confirm the user. + * @throws AppException if the server failed to confirm the user. */ - public void resetPassword(String token, String tokenId, String newPassword) throws ObjectServerError { + public void resetPassword(String token, String tokenId, String newPassword) throws AppException { Util.checkEmpty(token, "token"); Util.checkEmpty(tokenId, "tokenId"); Util.checkEmpty(newPassword, "newPassword"); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); call(TYPE_RESET_PASSWORD, new OsJNIVoidResultCallback(error), token, tokenId, newPassword); ResultHandler.handleResult(null, error); } @@ -271,7 +271,7 @@ public RealmAsyncTask resetPasswordAsync(String token, String tokenId, String ne Util.checkLooperThread("Asynchronous reset of a password is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public Void run() throws ObjectServerError { + public Void run() throws AppException { resetPassword(token, tokenId, newPassword); return null; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java index 5aac33bb54..4e6f33f788 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java @@ -28,7 +28,7 @@ import io.realm.internal.mongodb.Request; import io.realm.mongodb.App; import io.realm.mongodb.AppConfiguration; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; import io.realm.mongodb.User; /** @@ -69,7 +69,7 @@ protected Functions(User user, CodecRegistry codecRegistry) { * @param The type that the response will be decoded as using the {@code codecRegistry}. * @return Result of the Realm function. * - * @throws ObjectServerError if the request failed in some way. + * @throws AppException if the request failed in some way. * * @see #callFunctionAsync(String, List, Class, CodecRegistry, App.Callback) * @see AppConfiguration#getDefaultCodecRegistry() @@ -88,7 +88,7 @@ public ResultT callFunction(String name, List args, Class * @param The type that the response will be decoded as using the default codec registry. * @return Result of the Realm function. * - * @throws ObjectServerError if the request failed in some way. + * @throws AppException if the request failed in some way. * * @see #callFunction(String, List, Class, CodecRegistry) * @see AppConfiguration#getDefaultCodecRegistry() @@ -108,7 +108,7 @@ public ResultT callFunction(String name, List args, Class * @param The type that the response will be decoded as using the {@code resultDecoder} * @return Result of the Realm function. * - * @throws ObjectServerError if the request failed in some way. + * @throws AppException if the request failed in some way. * * @see #callFunction(String, List, Class, CodecRegistry) * @see AppConfiguration#getDefaultCodecRegistry() @@ -141,7 +141,7 @@ public RealmAsyncTask callFunctionAsync(String name, List args, Class Util.checkLooperThread("Asynchronous functions is only possible from looper threads."); return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override - public T run() throws ObjectServerError { + public T run() throws AppException { Decoder decoder = JniBsonProtocol.getCodec(resultClass, codecRegistry); return invoke(name, args, codecRegistry, decoder); } @@ -193,7 +193,7 @@ public RealmAsyncTask callFunctionAsync(String name, List args, Decoder(App.NETWORK_POOL_EXECUTOR, callback) { @Override - public T run() throws ObjectServerError { + public T run() throws AppException { return invoke(name, args, defaultCodecRegistry, resultDecoder); } }.start(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java index 9ed6759756..7f48f7e469 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java @@ -30,7 +30,7 @@ import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.network.ResultHandler; import io.realm.internal.objectstore.OsMongoCollection; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; /** * The MongoIterable is the results from an operation, such as a {@code find()} or an @@ -85,7 +85,7 @@ public Task> iterator() { */ public Task first() { AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected ResultT mapSuccess(Object result) { @@ -104,7 +104,7 @@ protected ResultT mapSuccess(Object result) { private Collection getCollection() { AtomicReference> success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback> callback = new OsJNIResultCallback>(success, error) { @Override protected Collection mapSuccess(Object result) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java index e0cfb1298a..4a260ff7c7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java @@ -20,18 +20,18 @@ import io.realm.annotations.Beta; import io.realm.mongodb.ErrorCode; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; import io.realm.Realm; import io.realm.RealmConfiguration; /** * Class encapsulating information needed for handling a Client Reset event. * - * @see SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more information + * @see SyncSession.ErrorHandler#onError(SyncSession, AppException) for more information * about when and why Client Reset occurs and how to deal with it. */ @Beta -public class ClientResetRequiredError extends ObjectServerError { +public class ClientResetRequiredError extends AppException { private final SyncConfiguration originalConfiguration; private final RealmConfiguration backupConfiguration; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java index d1101f3c9e..88dbfb34db 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResyncMode.java @@ -17,7 +17,7 @@ package io.realm.mongodb.sync; import io.realm.annotations.Beta; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; import io.realm.internal.OsRealmConfig; /** @@ -48,11 +48,11 @@ enum ClientResyncMode { * A manual Client Resync is also known as a Client Reset. *

              * A {@link ClientResetRequiredError} will be sent to - * {@link SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError)}, triggering + * {@link SyncSession.ErrorHandler#onError(SyncSession, AppException)}, triggering * a Client Reset. Doing this provides a handle to both the old and new Realm file, enabling * full control of which changes to move, if any. * - * @see SyncSession.ErrorHandler#onError(SyncSession, ObjectServerError) for more + * @see SyncSession.ErrorHandler#onError(SyncSession, AppException) for more * information about when and why Client Reset occurs and how to deal with it. */ MANUAL(OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 6012e88b3f..a16c65776d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -31,7 +31,7 @@ import io.realm.annotations.Beta; import io.realm.mongodb.ErrorCode; -import io.realm.mongodb.ObjectServerError; +import io.realm.mongodb.AppException; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.internal.Keep; @@ -200,11 +200,11 @@ void notifySessionError(String nativeErrorCategory, int nativeErrorCode, String "Read more here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup.", configuration, backupRealmConfiguration)); } else { - ObjectServerError wrappedError; + AppException wrappedError; if (errCode == ErrorCode.UNKNOWN) { - wrappedError = new ObjectServerError(nativeErrorCategory, nativeErrorCode, errorMessage); + wrappedError = new AppException(nativeErrorCategory, nativeErrorCode, errorMessage); } else { - wrappedError = new ObjectServerError(errCode, errorMessage); + wrappedError = new AppException(errCode, errorMessage); } errorHandler.onError(this, wrappedError); } @@ -585,7 +585,7 @@ private boolean waitForChanges(int direction, long timeout, TimeUnit unit) throw throw new IllegalArgumentException("Unknown direction: " + direction); } - throw new ObjectServerError(ErrorCode.UNKNOWN, errorMsg + " Has the SyncClient been started?"); + throw new AppException(ErrorCode.UNKNOWN, errorMsg + " Has the SyncClient been started?"); } try { result = wrapper.waitForServerChanges(timeout, unit); @@ -669,7 +669,7 @@ public interface ErrorHandler { * @param session {@link SyncSession} this error happened on. * @param error type of error. */ - void onError(SyncSession session, ObjectServerError error); + void onError(SyncSession session, AppException error); } // Wrapper class for handling the async operations of the underlying SyncSession calling @@ -715,7 +715,7 @@ public boolean isSuccess() { */ public void throwExceptionIfNeeded() { if (resultReceived && errorCode != null) { - throw new ObjectServerError(ErrorCode.UNKNOWN, + throw new AppException(ErrorCode.UNKNOWN, String.format(Locale.US, "Internal error (%d): %s", errorCode, errorMessage)); } } From 51d4294e94d0306ca83813712884b32e987f5187 Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Thu, 4 Jun 2020 14:30:43 +0200 Subject: [PATCH 1565/2110] Update README.md Added link to our forum --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 60085b34ec..a4e7019cfc 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,9 @@ The API reference is located at [realm.io/docs/java/api](https://realm.io/docs/j ## Getting Help -- **Got a question?**: Look for previous questions on the [#realm tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) — or [ask a new question](http://stackoverflow.com/questions/ask?tags=realm). We actively monitor & answer questions on StackOverflow! +- **Got a question?**: Look for previous questions on the [#realm tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) — or [ask a new question](http://stackoverflow.com/questions/ask?tags=realm). We actively monitor & answer questions on StackOverflow! You can also check out our [Community Forum](https://developer.mongodb.com/community/forums/tags/c/realm/9/realm-sdk) where general questions about how to do something can be discussed. - **Think you found a bug?** [Open an issue](https://github.com/realm/realm-java/issues/new?template=bug_report.md). If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue. - **Have a feature request?** [Open an issue](https://github.com/realm/realm-java/issues/new?template=feature_request.md). Tell us what the feature should do, and why you want the feature. -- Sign up for our [**Community Newsletter**](https://go.pardot.com/l/210132/2017-04-26/3j74l) to get regular tips, learn about other use-cases and get alerted of blogposts and tutorials about Realm. ## Using Snapshots From f58d7e67756aceb43d9ffecc79cd8e401e9a4800 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 4 Jun 2020 18:22:32 +0200 Subject: [PATCH 1566/2110] Fix copyToRealm deleting all model properties for elements in a RealmList containing embedded objects. (#6917) --- .../processor/RealmProxyClassGenerator.kt | 1 - ...t_EmbeddedClassSimpleParentRealmProxy.java | 1 - .../kotlin/io/realm/EmbeddedObjectsTest.kt | 20 +++++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index be04a300e0..cf82cf6c5b 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -1901,7 +1901,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("%s.updateEmbeddedObject(realm, %sUnmanagedItem, proxyObject, new HashMap(), Collections.EMPTY_SET)", Utils.getProxyClassSimpleName(field), fieldName) endControlFlow() endControlFlow() - emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldColKey, fieldName) } else { beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName) emitStatement("%1\$s %2\$sItem = %2\$sUnmanagedList.get(i)", genericType, fieldName) diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java index 44e2dc1f99..c55e6a570e 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java @@ -791,7 +791,6 @@ static some.test.EmbeddedClassSimpleParent update(Realm realm, EmbeddedClassSimp some_test_EmbeddedClassRealmProxy.updateEmbeddedObject(realm, childrenUnmanagedItem, proxyObject, new HashMap(), Collections.EMPTY_SET); } } - builder.addObjectList(columnInfo.childrenColKey, childrenManagedCopy); } else { builder.addObjectList(columnInfo.childrenColKey, new RealmList()); } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt index a6be56f3c9..976b037cee 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -279,8 +279,18 @@ class EmbeddedObjectsTest { } assertEquals(1, realm.where().count()) + assertEquals("parent1", realm.where().findFirst()!!.id) + assertEquals(2, realm.where().count()) + val nodeResults = realm.where().findAll() + assertTrue(nodeResults.any { it.id == "node1" }) + assertTrue(nodeResults.any { it.id == "node2" }) + assertEquals(3, realm.where().count()) + val leafResults = realm.where().findAll() + assertTrue(leafResults.any { it.id == "leaf1" }) + assertTrue(leafResults.any { it.id == "leaf2" }) + assertTrue(leafResults.any { it.id == "leaf3" }) } @Test @@ -379,8 +389,18 @@ class EmbeddedObjectsTest { } assertEquals(1, realm.where().count()) + assertEquals("parent1", realm.where().findFirst()!!.id) + assertEquals(2, realm.where().count()) + val nodeResults = realm.where().findAll() + assertTrue(nodeResults.any { it.id == "node1" }) + assertTrue(nodeResults.any { it.id == "node2" }) + assertEquals(3, realm.where().count()) + val leafResults = realm.where().findAll() + assertTrue(leafResults.any { it.id == "leaf1" }) + assertTrue(leafResults.any { it.id == "leaf2" }) + assertTrue(leafResults.any { it.id == "leaf3" }) } @Test From d69c6cd1ab52c8b2071a000d0adb73044f0e7ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 4 Jun 2020 19:41:05 +0200 Subject: [PATCH 1567/2110] User/Sync documentation (#6905) --- .../kotlin/io/realm/UserTests.kt | 17 ++++ .../mongodb/sync/SyncConfigurationTests.kt | 29 +++++- .../io/realm/mongodb/sync/SyncedRealmTests.kt | 5 + .../java/io/realm/mongodb/User.java | 97 ++++++++++++------- .../java/io/realm/mongodb/sync/Sync.java | 23 ++++- .../realm/mongodb/sync/SyncConfiguration.java | 75 +++++++------- 6 files changed, 173 insertions(+), 73 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt index 841b8ce191..ef5e4c9c8d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt @@ -282,6 +282,23 @@ class UserTests { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertTrue(user.deviceId.isNotEmpty() && user.deviceId.length == 24) // Server returns a UUID } + + // FIXME Test for all meta data + @Ignore("Not implemented yet") + fun user_metaData() { } + + // FIXME + @Ignore("Not implemented yet") + fun accessToken() { } + + // FIXME + @Ignore("Not implemented yet") + fun refreshToken() { } + + // FIXME + @Ignore("Not implemented yet") + fun isLoggedIn() { } + @Test fun equals() { // TODO Could be that we could use a fake user diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index 59c255167b..2122bff3ad 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -27,10 +27,8 @@ import io.realm.mongodb.AppException import io.realm.mongodb.User import io.realm.mongodb.close import io.realm.mongodb.registerUserAndLogin -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.bson.BsonString +import org.junit.* import org.junit.runner.RunWith import kotlin.test.* @@ -233,6 +231,29 @@ class SyncConfigurationTests { assertFailsWith { SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) } } + @Test + @Ignore("Not implemented yet") + fun shouldWaitForInitialRemoteData() { } + + @Test + @Ignore("Not implemented yet") + fun getInitialRemoteDataTimeout() { } + + @Test + @Ignore("Not implemented yet") + fun getSessionStopPolicy () { } + + @Test + @Ignore("Not implemented yet") + fun getUrlPrefix () { } + + @Test + fun getPartitionValue () { + val user: User = createTestUser(app) + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) + assertEquals(BsonString(DEFAULT_PARTITION), config.partitionValue) + } + @Test fun clientResyncMode() { val user: User = createTestUser(app) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index a9d9ba69d3..c8e66085a6 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -232,6 +232,11 @@ class SyncedRealmTests { } } + @Test + // FIXME Missing test, maybe fitting better in SyncSessionTest.kt...when migrated + @Ignore("Not implemented yet") + fun refreshConnections() {} + private fun createDefaultConfig(user: User, partitionValue: String = defaultPartitionValue): SyncConfiguration { return SyncConfiguration.Builder(user, partitionValue) .modules(DefaultSyncSchema()) diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index cd6e9add64..bd71b0bba8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -42,7 +42,14 @@ import io.realm.mongodb.push.Push; /** - * FIXME + * A user holds the user's meta data and tokens for accessing Realm App functionality. + *

              + * The user is used to configure Synchronized Realms and gives access to calling Realm App Functions + * through {@link Functions} and accessing remote Realm App Mongo Databases through a + * {@link MongoClient}. + * + * @see App#login(Credentials) + * @see io.realm.mongodb.sync.SyncConfiguration.Builder#Builder(User, String) */ @Beta public class User { @@ -54,7 +61,7 @@ public class User { private Functions functions = null; /** - * FIXME + * The different types of users. */ enum UserType { NORMAL("normal"), @@ -72,6 +79,9 @@ public String getKey() { } } + /** + * The user's potential states. + */ public enum State { LOGGED_IN(OsSyncUser.STATE_LOGGED_IN), REMOVED(OsSyncUser.STATE_REMOVED), @@ -102,24 +112,28 @@ protected MongoClientImpl(OsMongoClient osMongoClient, } /** - * FIXME - * @return + * Returns the id of the user. + * + * @return the id of the user. */ public String getId() { return osUser.getIdentity(); } /** - * FIXME - * @return + * Returns the name of the user. + * + * @return the name of the user. */ public String getName() { return osUser.nativeGetName(); } /** - * FIXME - * @return + * Returns the email address of the user. + * + * @return the email address of the user or null if there is no email address associated with the user. + * address. */ @Nullable public String getEmail() { @@ -127,8 +141,9 @@ public String getEmail() { } /** - * FIXME - * @return + * Returns the picture URL of the user. + * + * @return the picture URL of the user or null if there is no picture URL associated with the user. */ @Nullable public String getPictureUrl() { @@ -136,8 +151,9 @@ public String getPictureUrl() { } /** - * FIXME - * @return + * Return the first name of the user. + * + * @return the first name of the user or null if there is no first name associated with the user. */ @Nullable public String getFirstName() { @@ -145,8 +161,9 @@ public String getFirstName() { } /** - * FIXME - * @return + * Return the last name of the user. + * + * @return the last name of the user or null if there is no last name associated with the user. */ @Nullable public String getLastName() { @@ -154,8 +171,9 @@ public String getLastName() { } /** - * FIXME - * @return + * Returns the gender of the user. + * + * @return the gender of the user or null if there is no gender associated with the user. */ @Nullable public String getGender() { @@ -163,8 +181,9 @@ public String getGender() { } /** - * FIXME - * @return + * Returns the birthday of the user. + * + * @return the birthday of the user or null if there is no birthday associated with the user. */ @Nullable public String getBirthday() { @@ -172,8 +191,9 @@ public String getBirthday() { } /** - * FIXME - * @return + * Returns the minimum age of the user. + * + * @return the minimum age of the user or null if there is no minimum age associated with the user. */ @Nullable public Long getMinAge() { @@ -182,8 +202,9 @@ public Long getMinAge() { } /** - * FIXME - * @return + * Returns the maximum age of the user. + * + * @return the maximum age of the user or null if there is no maximum age associated with the user. */ @Nullable public Long getMaxAge() { @@ -192,8 +213,11 @@ public Long getMaxAge() { } /** - * FIXME - * @return + * Returns a new list of the user's identities. + * + * @return the list of identities. + * + * @see UserIdentity */ public List getIdentities() { Pair[] osIdentities = osUser.getIdentities(); @@ -206,22 +230,23 @@ public List getIdentities() { } /** - * FIXME - * @return + * Returns the current access token for the user. + * + * @return the current access token. */ public String getAccessToken() { return osUser.getAccessToken(); } /** - * FIXME - * @return + * Returns the current refresh token for the user. + * + * @return the current refresh token. */ public String getRefreshToken() { return osUser.getRefreshToken(); } - /** * Returns a unique identifier for the device the user logged in to. * @@ -452,10 +477,12 @@ public synchronized ApiKeyAuth getApiKeyAuth() { } /** - * Returns a Realm Functions manager for invoking MongoDB Realm Functions. + * Returns a functions manager for invoking MongoDB Realm Functions. *

              * This will use the associated app's default codec registry to encode and decode arguments and * results. + * + * @see Functions */ public synchronized Functions getFunctions() { checkLoggedIn(); @@ -466,17 +493,21 @@ public synchronized Functions getFunctions() { } /** - * Returns a Realm Functions manager for invoking MongoDB Realm Functions with custom + * Returns a functions manager for invoking Realm Functions with custom * codec registry for encoding and decoding arguments and results. + * + * @param codecRegistry The codec registry to use for encoding and decoding arguments and results + * towards the remote Realm App. + * @see Functions */ public Functions getFunctions(CodecRegistry codecRegistry) { return new FunctionsImpl(this, codecRegistry); } /** - * FIXME Add support for push notifications. Name of Class and method still TBD. + * FIXME Add support for push notifications. */ - public Push getPushNotifications() { + Push getPush() { return null; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java index f2b160421e..af346c66f9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java @@ -35,9 +35,28 @@ import io.realm.mongodb.User; /** - * Class wrapping Sync responsibilities for a {@link App}. + * A sync manager handling synchronization of local Realms with remote Realm Apps. + *

              + * The primary role of this is to access the {@link SyncSession} for a synchronized Realm. After + * opening the synchronized Realm you can access the {@link SyncSession} and perform synchronization + * related operations as shown below: + *

              + *     App app = new App("app-id");
              + *     User user = app.login(Credentials.anonymous());
              + *     SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, "")
              + *              .build();
              + *     Realm instance = Realm.getInstance(syncConfiguration);
              + *     SyncSession session = app.getSync().getSession(syncConfiguration);
                *
              - * FIXME: Better description that makes sense for end users.
              + *     instance.executeTransaction(realm -> {
              + *         realm.insert(...);
              + *     });
              + *     session.uploadAllLocalChanges();
              + *     instance.close();
              + * 
              + * + * @see App#getSync() + * @see Sync#getSession(SyncConfiguration) */ @Keep @SuppressFBWarnings("MS_CANNOT_BE_FINAL") diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index 77af94b1e7..a99725f9fb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -200,28 +200,23 @@ RealmConfiguration forErrorRecovery(String canonicalPath) { } /** - * FIXME + * Returns a default configuration for the given user and partition value. * - * @param user - * @param partitionValue - * @return + * @param user The user that will be used for accessing the Realm App. + * @param partitionValue The partition value identifying the remote Realm that will be synchronized. + * @return the default configuration for the given user and partition value. */ @Beta public static SyncConfiguration defaultConfig(User user, String partitionValue) { return new SyncConfiguration.Builder(user, partitionValue).build(); } - @Override - protected Realm.Transaction getInitialDataTransaction() { - return super.getInitialDataTransaction(); - } - /** - * FIXME + * Returns a default configuration for the given user and partition value. * - * @param user - * @param partitionValue - * @return + * @param user The user that will be used for accessing the Realm App. + * @param partitionValue The partition value identifying the remote Realm that will be synchronized. + * @return the default configuration for the given user and partition value. */ @Beta public static SyncConfiguration defaultConfig(User user, long partitionValue) { @@ -229,11 +224,11 @@ public static SyncConfiguration defaultConfig(User user, long partitionValue) { } /** - * FIXME + * Returns a default configuration for the given user and partition value. * - * @param user - * @param partitionValue - * @return + * @param user The user that will be used for accessing the Realm App. + * @param partitionValue The partition value identifying the remote Realm that will be synchronized. + * @return the default configuration for the given user and partition value. */ @Beta public static SyncConfiguration defaultConfig(User user, int partitionValue) { @@ -241,18 +236,17 @@ public static SyncConfiguration defaultConfig(User user, int partitionValue) { } /** - * FIXME + * Returns a default configuration for the given user and partition value. * - * @param user - * @param partitionValue - * @return + * @param user The user that will be used for accessing the Realm App. + * @param partitionValue The partition value identifying the remote Realm that will be synchronized. + * @return the default configuration for the given user and partition value. */ @Beta public static SyncConfiguration defaultConfig(User user, ObjectId partitionValue) { return new SyncConfiguration.Builder(user, partitionValue).build(); } - /** * Returns a {@link RealmConfiguration} appropriate to open a read-only, non-synced Realm to recover any pending changes. * This is useful when trying to open a backup/recovery Realm (after a client reset). @@ -267,6 +261,10 @@ public static RealmConfiguration forRecovery(String canonicalPath) { return forRecovery(canonicalPath, null); } + @Override + protected Realm.Transaction getInitialDataTransaction() { + return super.getInitialDataTransaction(); + } // Extract the full server path, minus the file name private static String getServerPath(User user, URI serverUrl) { @@ -354,6 +352,11 @@ public URI getServerUrl() { return serverUrl; } + /** + * Returns the error handler for this SyncConfiguration. + * + * @return the error handler. + */ public SyncSession.ErrorHandler getErrorHandler() { return errorHandler; } @@ -469,40 +472,44 @@ public static final class Builder { private final BsonValue partitionValue; /** - * FIXME + * Creates an instance of the builder for a SyncConfiguration with the given user + * and partition value. * - * @param user - * @param partitionValue + * @param user The user that will be used for accessing the Realm App. + * @param partitionValue The partition value identifying the remote Realm that will be synchronized. */ public Builder(User user, String partitionValue) { this(user, new BsonString(partitionValue)); } /** - * FIXME + * Creates an instance of the builder for a SyncConfiguration with the given user + * and partition value. * - * @param user - * @param partitionValue + * @param user The user that will be used for accessing the Realm App. + * @param partitionValue The partition value identifying the remote Realm that will be synchronized. */ public Builder(User user, ObjectId partitionValue) { this(user, new BsonObjectId(partitionValue)); } /** - * FIXME + * Creates an instance of the builder for a SyncConfiguration with the given user + * and partition value. * - * @param user - * @param partitionValue + * @param user The user that will be used for accessing the Realm App. + * @param partitionValue The partition value identifying the remote Realm that will be synchronized. */ public Builder(User user, int partitionValue) { this(user, new BsonInt32(partitionValue)); } /** - * FIXME + * Creates an instance of the builder for a SyncConfiguration with the given user + * and partition value. * - * @param user - * @param partitionValue + * @param user The user that will be used for accessing the Realm App. + * @param partitionValue The partition value identifying the remote Realm that will be synchronized. */ public Builder(User user, long partitionValue) { this(user, new BsonInt64(partitionValue)); From 4eaf0c730b8ca7ce9536c133255d89ec94165c5d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 5 Jun 2020 11:44:48 +0200 Subject: [PATCH 1568/2110] Update dependencies and changelog (#6915) --- CHANGELOG.md | 25 +++++++++++++----- dependencies.list | 10 +++---- .../kotlin/io/realm/AppTests.kt | 13 +++++----- .../io/realm/mongodb/MongoClientTest.kt | 13 +++++----- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/mongodb/ErrorCode.java | 3 +++ .../kotlin/io/realm/mongodb/SyncTestUtils.kt | 4 +-- .../BackingDB/rules/test_data.mongo_data.json | 26 +++++++++++++++++++ 8 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 15707564a0..b3e1fc04c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ -## 10.0.0 (YYYY-MM-DD) +## 10.0.0-BETA.1 (YYYY-MM-DD) + +We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. + +The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes -* Removed all references and API's releated to permissions. These are now managed through MongoDB Realm. Read more [here](XXX). -* Removed Query Based Sync API's and Subscriptions. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. `SyncConfiguration.partionKey()` has been added as a replacement. Read more [here](XXX). -* Destructive updates of a schema of a synced Realm will now consistently throw an `UnsupportedOperationException` instead of some methods throwing `IllegalArgumentException`. The affected methods are `RealmSchema.remove(String)`, `RealmSchema.rename(String, String)`, `RealmObjectSchema.setClassName(String)`, `RealmObjectSchema.removeField(String)`, `RealmObjectSchema.renameField(String, String)`, `RealmObjectSchema.removeIndex(String)`, `RealmObjectSchema.removePrimaryKey()`, `RealmObjectSchema.addPrimaryKey(String)` and `RealmObjectSchema.addField(String, Class, FieldAttribute)` +* [RealmApp] Removed all references and API's releated to permissions. These are now managed through MongoDB Realm. +* [RealmApp] Query Based Sync API's and Subscriptions. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. `SyncConfiguration.partitionKey()` has been added as a replacement. +* [RealmApp] Removed support for Client Resync. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. +* [RealmApp] Removed suppport for custom SSL certificates. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. +* [RealmApp] Destructive updates of a schema of a synced Realm will now consistently throw an `UnsupportedOperationException` instead of some methods throwing `IllegalArgumentException`. The affected methods are `RealmSchema.remove(String)`, `RealmSchema.rename(String, String)`, `RealmObjectSchema.setClassName(String)`, `RealmObjectSchema.removeField(String)`, `RealmObjectSchema.renameField(String, String)`, `RealmObjectSchema.removeIndex(String)`, `RealmObjectSchema.removePrimaryKey()`, `RealmObjectSchema.addPrimaryKey(String)` and `RealmObjectSchema.addField(String, Class, FieldAttribute)` ### Enhancements * Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. @@ -11,12 +17,18 @@ * Added support for "Embedded Objects". They are enabled using `@RealmClass(embedded = true)`. An embedded object must have exactly one parent object linking to it and it will be deleted when the the parent is. Embedded objects can also be the parent of other embedded classes. Read more [here](https://realm.io/docs/java/latest/#embedded-objects). (Issue [#6713](https://github.com/realm/realm-java/issues/6713)) ### Fixed -* None. +* After upgrading a Realm file, you may at some point receive a 'NoSuchTable' exception. (Issue [Core#3701](https://github.com/realm/realm-core/issues/3701), since 7.0.0) +* If the Realm file upgrade process was interrupted/killed for various reasons, the following run would some assertions failing. (Issue [#6866](https://github.com/realm/realm-java/issues/6866), since 7.0.0). ### Compatibility -* TODO. +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal +* Updated to Object Store commit: 6d081a53377514f9b77736cb03051a03d829da92. +* Updated to Realm Sync 10.0.0-beta.1. +* Updated to Realm Core 10.0.0-beta.1. * OKHttp was upgraded to 3.12.0 from 3.10.0. * Updated Android Gradle Plugin to 3.6.1. * Updated Gradle to 5.6.4 @@ -24,6 +36,7 @@ * Updated Android Build Tools to 29.0.2. * Updated compileSdkVersion to 29. + ## 7.0.0(YYYY-MM-DD) NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. diff --git a/dependencies.list b/dependencies.list index c87abde02f..ab481e1e28 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,15 +1,11 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-alpha.15 -REALM_SYNC_SHA256=f7a21bd6945ee5623a0cf93e9d044da28c5b4c76b2e0efa3a12a0d51637a6dcc - -# Object Server Release used by Integration tests. Installed using NPM. -# Use `npm view realm-object-server versions` to get a list of available versions. -REALM_OBJECT_SERVER_VERSION=3.28.2 +REALM_SYNC_VERSION=10.0.0-beta.1 +REALM_SYNC_SHA256=1ce7b45620fa0eb8465130db272f0ebf941ee84f561e3274c12aee423c96c176 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-05-29 +MONGODB_REALM_SERVER_VERSION=2020-06-04 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index 46548dfb7e..f5ad4f91f5 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -119,27 +119,28 @@ class AppTests { assertTrue(allUsers.containsKey(user1.id)) assertEquals(user1, allUsers[user1.id]) + // Only 1 anonymous user exists, so logging in again just returns the old one val user2 = app.login(Credentials.anonymous()) allUsers = app.allUsers() - assertEquals(2, allUsers.size) + assertEquals(1, allUsers.size) assertTrue(allUsers.containsKey(user2.id)) val user3: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") allUsers = app.allUsers() - assertEquals(3, allUsers.size) + assertEquals(2, allUsers.size) assertTrue(allUsers.containsKey(user3.id)) // Logging out users that registered with email/password will just put them in LOGGED_OUT state user3.logOut(); allUsers = app.allUsers() - assertEquals(3, allUsers.size) + assertEquals(2, allUsers.size) assertTrue(allUsers.containsKey(user3.id)) assertEquals(User.State.LOGGED_OUT, allUsers[user3.id]!!.state) // Logging out anonymous users will remove them completely user1.logOut() allUsers = app.allUsers() - assertEquals(2, allUsers.size) + assertEquals(1, allUsers.size) assertFalse(allUsers.containsKey(user1.id)) } @@ -183,8 +184,8 @@ class AppTests { @Test fun currentUser_FallbackToNextValidUser() { - val user1: User = app.login(Credentials.anonymous()) - val user2: User = app.login(Credentials.anonymous()) + val user1: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val user2: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertEquals(user2, app.currentUser()) user2.logOut() assertEquals(user1, app.currentUser()) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt index c5c2e748e0..a18330f45c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt @@ -42,7 +42,7 @@ import kotlin.test.* private const val SERVICE_NAME = "BackingDB" // it comes from the test server's BackingDB/config.json private const val DATABASE_NAME = "test_data" // same as above -private const val COLLECTION_NAME = "test_data" +private const val COLLECTION_NAME = "mongo_data" // name of collection used by tests @RunWith(AndroidJUnit4::class) class MongoClientTest { @@ -61,10 +61,11 @@ class MongoClientTest { @After fun tearDown() { - with(getCollectionInternal()) { - deleteMany(Document()).blockingGetResult() + if (this::client.isInitialized) { + with(getCollectionInternal()) { + deleteMany(Document()).blockingGetResult() + } } - if (this::app.isInitialized) { app.close() } @@ -1014,7 +1015,7 @@ class MongoClientTest { return client.getDatabase(DATABASE_NAME).let { assertEquals(it.name, DATABASE_NAME) it.getCollection(COLLECTION_NAME).also { collection -> - assertEquals(MongoNamespace(DATABASE_NAME, it.name), collection.namespace) + assertEquals(MongoNamespace(DATABASE_NAME, COLLECTION_NAME), collection.namespace) } } } @@ -1025,7 +1026,7 @@ class MongoClientTest { return client.getDatabase(DATABASE_NAME).let { assertEquals(it.name, DATABASE_NAME) it.getCollection(COLLECTION_NAME, resultClass).also { collection -> - assertEquals(MongoNamespace(DATABASE_NAME, it.name), collection.namespace) + assertEquals(MongoNamespace(DATABASE_NAME, COLLECTION_NAME), collection.namespace) } } } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index b1c0ab7d65..6d081a5337 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit b1c0ab7d658c1dee3c0534ed82b9f77afbf8cb6c +Subproject commit 6d081a53377514f9b77736cb03051a03d829da92 diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java index ee5c03ac73..910c9fae31 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java @@ -95,6 +95,9 @@ public enum ErrorCode { USER_BLACKLISTED(Type.PROTOCOL, 220), // User has been blacklisted (BIND) TRANSACT_BEFORE_UPLOAD(Type.PROTOCOL, 221), // Serialized transaction before upload completion CLIENT_FILE_EXPIRED(Type.PROTOCOL, 222), // Client file has expired + USER_MISMATCH(Type.PROTOCOL, 223), // User mismatch for client file identifier (IDENT) + TOO_MANY_SESSIONS(Type.PROTOCOL, 224), // Too many sessions in connection (BIND) + INVALID_SCHEMA_CHANGE(Type.PROTOCOL, 225), // Invalid schema change (UPLOAD) // Sync Network Client errors. // See https://github.com/realm/realm-sync/blob/master/src/realm/sync/client.hpp#L1230 diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt index b6ec982085..ea80308c20 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt @@ -18,6 +18,7 @@ package io.realm.mongodb import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm import io.realm.RealmExt +import io.realm.TestHelper import io.realm.internal.objectstore.OsJavaNetworkTransport import io.realm.log.LogLevel import io.realm.log.RealmLog @@ -25,7 +26,6 @@ import io.realm.mongodb.sync.SyncConfiguration import io.realm.objectserver.utils.UserFactory import io.realm.testClearApplicationContext import java.io.File -import java.lang.IllegalStateException import java.util.* class SyncTestUtils { @@ -134,7 +134,7 @@ class SyncTestUtils { } } } - val user = app.login(Credentials.anonymous()) + val user = app.login(Credentials.emailPassword(TestHelper.getRandomEmail(), "123456")) app.networkTransport = transportBackup return user } diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json new file mode 100644 index 0000000000..45221c81a6 --- /dev/null +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json @@ -0,0 +1,26 @@ +{ + "database": "test_data", + "collection": "mongo_data", + "roles": [ + { + "name": "default", + "apply_when": {}, + "insert": true, + "delete": true, + "additional_fields": {} + } + ], + "schema": { + "properties": { + "_id": { + "bsonType": "objectId" + }, + "realm_id": { + "bsonType": "string" + } + }, + "required": [ + ], + "title": "mongo_data" + } +} From 856b964e2ca12161c0de72ce2ca57ef4df91f97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 5 Jun 2020 12:16:25 +0200 Subject: [PATCH 1569/2110] Remove lambdas to avoid java 8 compile options in consumer projects (#6918) --- .../src/main/java/io/realm/Realm.java | 2 +- .../internal/common/ThreadDispatcher.java | 13 +- .../realm/mongodb/mongo/MongoCollection.java | 231 +++++++++++++----- .../mongodb/mongo/iterable/MongoIterable.java | 21 +- 4 files changed, 199 insertions(+), 68 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index a71594fefd..b2c6b98c8b 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1030,7 +1030,7 @@ public E createObject(Class clazz, @Nullable Object pr * This method should only be used to created objects of types marked as embedded. * * @param clazz the Class of the object to create. It must be marked with {@code \@RealmClass(embedded = true)}. - * @param parent The parent object which should a reference to the embedded object. If the parent property is a list + * @param parentObject The parent object which should a reference to the embedded object. If the parent property is a list * the embedded object will be added to the end of that list. * @param parentProperty the property in the parent class which holds the reference. * @return the newly created embedded object. diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java index eb2aec075f..3dec574130 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java @@ -57,11 +57,14 @@ protected U dispatch( @SuppressWarnings("FutureReturnValueIgnored") private void dispatch(final Callable callable, final Callback callback) { // ignoring the output of this future messes with Findbugs, thus the suppress - executorService.submit(() -> { - try { - callback.onComplete(OperationResult.successfulResultOf(callable.call())); - } catch (final Exception e) { - callback.onComplete(OperationResult.failedResultOf(e)); + executorService.submit(new Runnable() { + @Override + public void run() { + try { + callback.onComplete(OperationResult.successfulResultOf(callable.call())); + } catch (final Exception e) { + callback.onComplete(OperationResult.failedResultOf(e)); + } } }); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java index dc96ce7d82..331df77288 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java @@ -22,6 +22,7 @@ import org.bson.conversions.Bson; import java.util.List; +import java.util.concurrent.Callable; import io.realm.annotations.Beta; import io.realm.internal.common.TaskDispatcher; @@ -128,7 +129,12 @@ public MongoCollection withCodecRegistry(final CodecRegistry codecReg * @return a task containing the number of documents in the collection */ public Task count() { - return dispatcher.dispatchTask(osMongoCollection::count); + return dispatcher.dispatchTask(new Callable() { + @Override + public Long call() throws Exception { + return osMongoCollection.count(); + } + }); } /** @@ -138,8 +144,12 @@ public Task count() { * @return a task containing the number of documents in the collection */ public Task count(final Bson filter) { - return dispatcher.dispatchTask(() -> - osMongoCollection.count(filter) + return dispatcher.dispatchTask(new Callable() { + @Override + public Long call() throws Exception { + return osMongoCollection.count(filter); + } + } ); } @@ -151,8 +161,12 @@ public Task count(final Bson filter) { * @return a task containing the number of documents in the collection */ public Task count(final Bson filter, final CountOptions options) { - return dispatcher.dispatchTask(() -> - osMongoCollection.count(filter, options) + return dispatcher.dispatchTask(new Callable() { + @Override + public Long call() throws Exception { + return osMongoCollection.count(filter, options); + } + } ); } @@ -162,7 +176,12 @@ public Task count(final Bson filter, final CountOptions options) { * @return a task containing the result of the find one operation */ public Task findOne() { - return dispatcher.dispatchTask(osMongoCollection::findOne); + return dispatcher.dispatchTask(new Callable() { + @Override + public DocumentT call() throws Exception { + return osMongoCollection.findOne(); + } + }); } /** @@ -173,8 +192,12 @@ public Task findOne() { * @return a task containing the result of the find one operation */ public Task findOne(final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOne(resultClass) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return osMongoCollection.findOne(resultClass); + } + } ); } @@ -185,8 +208,12 @@ public Task findOne(final Class resultClass) { * @return a task containing the result of the find one operation */ public Task findOne(final Bson filter) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOne(filter) + return dispatcher.dispatchTask(new Callable() { + @Override + public DocumentT call() throws Exception { + return osMongoCollection.findOne(filter); + } + } ); } @@ -199,8 +226,12 @@ public Task findOne(final Bson filter) { * @return a task containing the result of the find one operation */ public Task findOne(final Bson filter, final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOne(filter, resultClass) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return osMongoCollection.findOne(filter, resultClass); + } + } ); } @@ -212,8 +243,12 @@ public Task findOne(final Bson filter, final Class r * @return a task containing the result of the find one operation */ public Task findOne(final Bson filter, final FindOptions options) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOne(filter, options) + return dispatcher.dispatchTask(new Callable() { + @Override + public DocumentT call() throws Exception { + return osMongoCollection.findOne(filter, options); + } + } ); } @@ -229,8 +264,12 @@ public Task findOne(final Bson filter, final FindOptions options) { public Task findOne(final Bson filter, final FindOptions options, final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOne(filter, options, resultClass) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return osMongoCollection.findOne(filter, options, resultClass); + } + } ); } @@ -391,8 +430,12 @@ public AggregateIterable aggregate(final List * @return a task containing the result of the insert one operation */ public Task insertOne(final DocumentT document) { - return dispatcher.dispatchTask(() -> - osMongoCollection.insertOne(document) + return dispatcher.dispatchTask(new Callable() { + @Override + public InsertOneResult call() throws Exception { + return osMongoCollection.insertOne(document); + } + } ); } @@ -403,8 +446,12 @@ public Task insertOne(final DocumentT document) { * @return a task containing the result of the insert many operation */ public Task insertMany(final List documents) { - return dispatcher.dispatchTask(() -> - osMongoCollection.insertMany(documents) + return dispatcher.dispatchTask(new Callable() { + @Override + public InsertManyResult call() throws Exception { + return osMongoCollection.insertMany(documents); + } + } ); } @@ -417,8 +464,12 @@ public Task insertMany(final List documen * @return a task containing the result of the remove one operation */ public Task deleteOne(final Bson filter) { - return dispatcher.dispatchTask(() -> - osMongoCollection.deleteOne(filter) + return dispatcher.dispatchTask(new Callable() { + @Override + public DeleteResult call() throws Exception { + return osMongoCollection.deleteOne(filter); + } + } ); } @@ -430,8 +481,12 @@ public Task deleteOne(final Bson filter) { * @return a task containing the result of the remove many operation */ public Task deleteMany(final Bson filter) { - return dispatcher.dispatchTask(() -> - osMongoCollection.deleteMany(filter) + return dispatcher.dispatchTask(new Callable() { + @Override + public DeleteResult call() throws Exception { + return osMongoCollection.deleteMany(filter); + } + } ); } @@ -444,8 +499,12 @@ public Task deleteMany(final Bson filter) { * @return a task containing the result of the update one operation */ public Task updateOne(final Bson filter, final Bson update) { - return dispatcher.dispatchTask(() -> - osMongoCollection.updateOne(filter, update) + return dispatcher.dispatchTask(new Callable() { + @Override + public UpdateResult call() throws Exception { + return osMongoCollection.updateOne(filter, update); + } + } ); } @@ -462,8 +521,12 @@ public Task updateOne( final Bson filter, final Bson update, final UpdateOptions updateOptions) { - return dispatcher.dispatchTask(() -> - osMongoCollection.updateOne(filter, update, updateOptions) + return dispatcher.dispatchTask(new Callable() { + @Override + public UpdateResult call() throws Exception { + return osMongoCollection.updateOne(filter, update, updateOptions); + } + } ); } @@ -476,8 +539,12 @@ public Task updateOne( * @return a task containing the result of the update many operation */ public Task updateMany(final Bson filter, final Bson update) { - return dispatcher.dispatchTask(() -> - osMongoCollection.updateMany(filter, update) + return dispatcher.dispatchTask(new Callable() { + @Override + public UpdateResult call() throws Exception { + return osMongoCollection.updateMany(filter, update); + } + } ); } @@ -494,8 +561,12 @@ public Task updateMany( final Bson filter, final Bson update, final UpdateOptions updateOptions) { - return dispatcher.dispatchTask(() -> - osMongoCollection.updateMany(filter, update, updateOptions) + return dispatcher.dispatchTask(new Callable() { + @Override + public UpdateResult call() throws Exception { + return osMongoCollection.updateMany(filter, update, updateOptions); + } + } ); } @@ -507,8 +578,12 @@ public Task updateMany( * @return a task containing the resulting document */ public Task findOneAndUpdate(final Bson filter, final Bson update) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndUpdate(filter, update) + return dispatcher.dispatchTask(new Callable() { + @Override + public DocumentT call() throws Exception { + return osMongoCollection.findOneAndUpdate(filter, update); + } + } ); } @@ -524,8 +599,12 @@ public Task findOneAndUpdate(final Bson filter, final Bson update) { public Task findOneAndUpdate(final Bson filter, final Bson update, final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndUpdate(filter, update, resultClass) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return osMongoCollection.findOneAndUpdate(filter, update, resultClass); + } + } ); } @@ -540,8 +619,12 @@ public Task findOneAndUpdate(final Bson filter, public Task findOneAndUpdate(final Bson filter, final Bson update, final FindOneAndModifyOptions options) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndUpdate(filter, update, options) + return dispatcher.dispatchTask(new Callable() { + @Override + public DocumentT call() throws Exception { + return osMongoCollection.findOneAndUpdate(filter, update, options); + } + } ); } @@ -559,8 +642,12 @@ public Task findOneAndUpdate(final Bson filter, final Bson update, final FindOneAndModifyOptions options, final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndUpdate(filter, update, options, resultClass) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return osMongoCollection.findOneAndUpdate(filter, update, options, resultClass); + } + } ); } @@ -572,8 +659,12 @@ public Task findOneAndUpdate(final Bson filter, * @return a task containing the resulting document */ public Task findOneAndReplace(final Bson filter, final Bson replacement) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndReplace(filter, replacement) + return dispatcher.dispatchTask(new Callable() { + @Override + public DocumentT call() throws Exception { + return osMongoCollection.findOneAndReplace(filter, replacement); + } + } ); } @@ -589,8 +680,12 @@ public Task findOneAndReplace(final Bson filter, final Bson replaceme public Task findOneAndReplace(final Bson filter, final Bson replacement, final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndReplace(filter, replacement, resultClass) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return osMongoCollection.findOneAndReplace(filter, replacement, resultClass); + } + } ); } @@ -605,8 +700,12 @@ public Task findOneAndReplace(final Bson filter, public Task findOneAndReplace(final Bson filter, final Bson replacement, final FindOneAndModifyOptions options) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndReplace(filter, replacement, options) + return dispatcher.dispatchTask(new Callable() { + @Override + public DocumentT call() throws Exception { + return osMongoCollection.findOneAndReplace(filter, replacement, options); + } + } ); } @@ -624,8 +723,12 @@ public Task findOneAndReplace(final Bson filter, final Bson replacement, final FindOneAndModifyOptions options, final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndReplace(filter, replacement, options, resultClass) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return osMongoCollection.findOneAndReplace(filter, replacement, options, resultClass); + } + } ); } @@ -636,8 +739,12 @@ public Task findOneAndReplace(final Bson filter, * @return a task containing the resulting document */ public Task findOneAndDelete(final Bson filter) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndDelete(filter) + return dispatcher.dispatchTask(new Callable() { + @Override + public DocumentT call() throws Exception { + return osMongoCollection.findOneAndDelete(filter); + } + } ); } @@ -651,8 +758,12 @@ public Task findOneAndDelete(final Bson filter) { */ public Task findOneAndDelete(final Bson filter, final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndDelete(filter, resultClass) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return osMongoCollection.findOneAndDelete(filter, resultClass); + } + } ); } @@ -665,8 +776,12 @@ public Task findOneAndDelete(final Bson filter, */ public Task findOneAndDelete(final Bson filter, final FindOneAndModifyOptions options) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndDelete(filter, options) + return dispatcher.dispatchTask(new Callable() { + @Override + public DocumentT call() throws Exception { + return osMongoCollection.findOneAndDelete(filter, options); + } + } ); } @@ -682,8 +797,12 @@ public Task findOneAndDelete(final Bson filter, public Task findOneAndDelete(final Bson filter, final FindOneAndModifyOptions options, final Class resultClass) { - return dispatcher.dispatchTask(() -> - osMongoCollection.findOneAndDelete(filter, options, resultClass) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return osMongoCollection.findOneAndDelete(filter, options, resultClass); + } + } ); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java index 7f48f7e469..9ac0239321 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import io.realm.internal.common.TaskDispatcher; @@ -70,8 +71,12 @@ public abstract class MongoIterable { * @return an asynchronous task with cursor of the operation represented by this iterable. */ public Task> iterator() { - return dispatcher.dispatchTask(() -> - new MongoCursor<>(getCollection().iterator()) + return dispatcher.dispatchTask(new Callable>() { + @Override + public MongoCursor call() throws Exception { + return new MongoCursor<>(MongoIterable.this.getCollection().iterator()); + } + } ); } @@ -84,8 +89,8 @@ public Task> iterator() { * @return a task containing the first item or null. */ public Task first() { - AtomicReference success = new AtomicReference<>(null); - AtomicReference error = new AtomicReference<>(null); + final AtomicReference success = new AtomicReference<>(null); + final AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected ResultT mapSuccess(Object result) { @@ -97,8 +102,12 @@ protected ResultT mapSuccess(Object result) { callNative(callback); - return dispatcher.dispatchTask(() -> - ResultHandler.handleResult(success, error) + return dispatcher.dispatchTask(new Callable() { + @Override + public ResultT call() throws Exception { + return ResultHandler.handleResult(success, error); + } + } ); } From 93704fbdca874c35e90379be1e1fb986c956c49a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 5 Jun 2020 13:05:00 +0200 Subject: [PATCH 1570/2110] Fix base builds --- realm/kotlin-extensions/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index c5cdd1aceb..32c026a658 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -80,11 +80,11 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY_VERSION')}" + androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' - androidTestObjectServerImplementation 'com.google.code.findbugs:jsr305:3.0.2' } repositories { From 93540dca0cc3cdef6d8137d69a89545816fa3663 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 5 Jun 2020 13:45:08 +0200 Subject: [PATCH 1571/2110] Release 10.0.0-BETA.1 --- CHANGELOG.md | 4 ++-- version.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3e1fc04c4..3ef62ff4ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.0-BETA.1 (YYYY-MM-DD) +## 10.0.0-BETA.1 (2020-06-05) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. @@ -26,7 +26,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal -* Updated to Object Store commit: 6d081a53377514f9b77736cb03051a03d829da92. +* Updated to Object Store commit: 6d081a53377514f9b77736cb03051a03d829da922. * Updated to Realm Sync 10.0.0-beta.1. * Updated to Realm Core 10.0.0-beta.1. * OKHttp was upgraded to 3.12.0 from 3.10.0. diff --git a/version.txt b/version.txt index f702ec2902..007277241c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-SNAPSHOT +10.0.0-BETA.1 From d026aaf38610f1bb4ba4fa296e674ea1e016c628 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 5 Jun 2020 14:09:38 +0200 Subject: [PATCH 1572/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 007277241c..edbe204602 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.1 +10.0.0-BETA.2-SNAPSHOT From 6d6f33cb1760279f162736f517ae1d9439cd037b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sun, 7 Jun 2020 13:53:03 +0200 Subject: [PATCH 1573/2110] Use the correct default production base url + tests (#6926) --- CHANGELOG.md | 24 +++++++++++++++++++ .../kotlin/io/realm/AppConfigurationTests.kt | 18 ++++++++++---- .../io/realm/mongodb/AppConfiguration.java | 2 +- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ef62ff4ad..52371f3c95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 10.0.0-BETA.2 (YYYY-MM-DD) + +We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. + +The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. + +### Breaking Changes +* None + +### Enhancements +* None + +### Fixed +* [RealmApp] `AppConfiguration` did not fallback to the correct default baseUrl if none was provided. (Since 10.0.0-BETA.1) + +### Compatibility +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 and above is required to open Realms created by this version. + +### Internal +* None + + ## 10.0.0-BETA.1 (2020-06-05) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index e5d5d0007a..ae9e16f0bd 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -30,6 +30,7 @@ import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import java.io.File import java.lang.IllegalArgumentException +import java.net.URL import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) @@ -184,15 +185,24 @@ class AppConfigurationTests { } @Test - @Ignore("FIXME") fun baseUrl() { - TODO() + val url = "http://myurl.com" + val config = AppConfiguration.Builder("foo").baseUrl(url).build() + assertEquals(URL(url), config.baseUrl) } @Test - @Ignore("FIXME") + fun baseUrl_defaultValue() { + val url = "https://realm.mongodb.com" + val config = AppConfiguration.Builder("foo").build() + assertEquals(URL(url), config.baseUrl) + } + @Test fun baseUrl_invalidValuesThrows() { - TODO() + val configBuilder = AppConfiguration.Builder("foo") + assertFailsWith { configBuilder.baseUrl("") } + assertFailsWith { configBuilder.baseUrl(TestHelper.getNull()) } + assertFailsWith { configBuilder.baseUrl("invalid-url") } } @Test diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index fe3b590298..1080692c9f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -61,7 +61,7 @@ public class AppConfiguration { * * @see Builder#baseUrl(String) */ - public final static String DEFAULT_BASE_URL = "https://realm-dev.mongodb.com"; //FIXME change to production url before beta release + public final static String DEFAULT_BASE_URL = "https://realm.mongodb.com"; /** * The default request timeout for network requests towards MongoDB Realm in seconds. From ca09674f714837a59bc3721ab63bfa05866c7a3f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 8 Jun 2020 12:35:54 +0200 Subject: [PATCH 1574/2110] Fix JavaDoc not being generated (#6929) --- realm/build.gradle | 9 +++ realm/realm-library/build.gradle | 59 ++++++++++--------- .../src/main/java/io/realm/RealmResults.java | 2 +- .../java/io/realm/mongodb/App.java | 10 ++-- .../java/io/realm/mongodb/Credentials.java | 5 +- .../java/io/realm/mongodb/User.java | 2 +- .../io/realm/mongodb/functions/Functions.java | 2 +- .../mongodb/mongo/iterable/MongoCursor.java | 2 +- .../mongodb/mongo/iterable/MongoIterable.java | 6 +- .../java/io/realm/mongodb/sync/Sync.java | 4 +- 10 files changed, 56 insertions(+), 45 deletions(-) diff --git a/realm/build.gradle b/realm/build.gradle index 3a314774e9..446809b487 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -45,3 +45,12 @@ allprojects { jcenter() } } + +// Disable JavaDoc strict mode: https://blog.joda.org/2014/02/turning-off-doclint-in-jdk-8-javadoc.html +if (JavaVersion.current().isJava8Compatible()) { + allprojects { + tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:-missing', '-quiet') + } + } +} diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 2d96ec0802..98d95601bb 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -257,35 +257,36 @@ def betaTag = 'Beta:a:
              This soft 'considered at production quality, and should be used with care.
              ' task javadoc(type: Javadoc) { -// FIXME: Disable JavaDoc until API Stabilizes a bit more -// source android.sourceSets.objectServer.java.srcDirs -// source android.sourceSets.main.java.srcDirs -// source "../../realm-annotations/src/main/java" -// classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) -// options { -// title = "Realm ${project.version}" -// memberLevel = JavadocMemberLevel.PUBLIC -// docEncoding = 'UTF-8' -// encoding = 'UTF-8' -// charSet = 'UTF-8' -// locale = 'en_US' -// overview = 'src/overview.html' -// -// links "https://docs.oracle.com/javase/7/docs/api/" -// links "http://reactivex.io/RxJava/javadoc/" -// linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" -// -// tags = [betaTag] -// } -// exclude '**/internal/**' -// exclude '**/BuildConfig.java' -// exclude '**/R.java' -// doLast { -// copy { -// from "src/realm-java-overview.png" -// into "$buildDir/docs/javadoc" -// } -// } + source android.sourceSets.objectServer.java.srcDirs + source android.sourceSets.main.java.srcDirs + source "../../realm-annotations/src/main/java" + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + options { + title = "Realm ${project.version}" + memberLevel = JavadocMemberLevel.PUBLIC + docEncoding = 'UTF-8' + encoding = 'UTF-8' + charSet = 'UTF-8' + locale = 'en_US' + overview = 'src/overview.html' + + links "https://docs.oracle.com/javase/7/docs/api/" + links "http://reactivex.io/RxJava/javadoc/" + // TODO We probably need to add the bson.jar to the classpath for these to work + links "https://www.javadoc.io/doc/org.mongodb/bson/${properties.getProperty('BSON_DEPENDENCY_VERSION')}/" + linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" + + tags = [betaTag] + } + exclude '**/internal/**' + exclude '**/BuildConfig.java' + exclude '**/R.java' + doLast { + copy { + from "src/realm-java-overview.png" + into "$buildDir/docs/javadoc" + } + } } task javadocJar(type: Jar, dependsOn: javadoc) { diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index f3b871d82e..617bd9c2d7 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -451,7 +451,7 @@ public void setDecimal128(String fieldName, @Nullable Decimal128 value) { * * @param fieldName name of the field to update. * @param value new value for the field. - * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a {@code ObjectId field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a {@code ObjectId} field. */ public void setObjectId(String fieldName, @Nullable ObjectId value) { checkNonEmptyFieldName(fieldName); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index 4e3854aed4..cd824e4b54 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -71,7 +71,7 @@ * * App APP; * - * @Override + * \@Override * public void onCreate() { * super.onCreate(); * @@ -106,13 +106,13 @@ * With an authorized user you can synchronize data between the local device and the remote Realm * App by opening a Realm with a {@link io.realm.mongodb.sync.SyncConfiguration} as indicated below: *
              - *     SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, "")
              + *     SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, "<partition value>")
                *              .build();
                *
                *     Realm instance = Realm.getInstance(syncConfiguration);
                *     SyncSession session = APP.getSync().getSession(syncConfiguration);
                *
              - *     instance.executeTransaction(realm -> {
              + *     instance.executeTransaction(realm -> {
                *         realm.insert(...);
                *     });
                *     session.uploadAllLocalChanges();
              @@ -129,7 +129,7 @@
                * 
                *     MongoClient client = user.getMongoClient(SERVICE_NAME)
                *     MongoDatabase database = client.getDatabase(DATABASE_NAME)
              - *     MongoCollection collection = database.getCollection(COLLECTION_NAME);
              + *     MongoCollection<DocumentT> collection = database.getCollection(COLLECTION_NAME);
                *     Long count = collection.count().blockingGetResult()
                * 
              *

              @@ -599,7 +599,7 @@ public T getOrDefault(T defaultValue) { * is thrown. * * @return the response object in case the request was a success. - * @throws ObjectServerError provided error in case the request failed. + * @throws AppException provided error in case the request failed. */ public T getOrThrow() { if (isSuccess()) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java index 4b9274fe74..6362fd2034 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java @@ -35,9 +35,9 @@ * // Example * App app = new App("app-id"); * Credentials credentials = Credentials.emailPassword("email", "password"); - * User user = app.loginAsync(credentials, new App.Callback() { + * User user = app.loginAsync(credentials, new App.Callback<User>() { * \@Override - * public void onResult(Result result) { + * public void onResult(Result<User> result) { * if (result.isSuccess() { * handleLogin(result.get()); * } else { @@ -46,6 +46,7 @@ * } * )); * } + * } *

              * @see Authentication Providers */ diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index bd71b0bba8..72f440d28a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -44,7 +44,7 @@ /** * A user holds the user's meta data and tokens for accessing Realm App functionality. *

              - * The user is used to configure Synchronized Realms and gives access to calling Realm App Functions + * The user is used to configure Synchronized Realms and gives access to calling Realm App Functions * through {@link Functions} and accessing remote Realm App Mongo Databases through a * {@link MongoClient}. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java index 4e6f33f788..1206f3f5b1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java @@ -32,7 +32,7 @@ import io.realm.mongodb.User; /** - * A Functions manager to call remote Realm functions for the associated Realm App. + * A Functions manager to call remote Realm functions for the associated Realm App. *

              * Arguments and results are encoded/decoded with the Functions' codec registry either * inherited from the {@link AppConfiguration#getDefaultCodecRegistry()} or set explicitly diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java index 577489ad16..49e5666b4b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java @@ -52,7 +52,7 @@ public ResultT next() { /** * A special {@code next()} case that returns the next document if available or null. * - * @return A {@link Task} containing the next document if available or null. + * @return A {@code Task} containing the next document if available or null. */ public ResultT tryNext() { if (!iterator.hasNext()) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java index 9ac0239321..84656f8efa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java @@ -38,7 +38,7 @@ * {@code aggregate()} query. *

              * This class somewhat mimics the behavior of an {@link Iterable} but given its results are - * obtained asynchronously, its values are wrapped inside a {@link Task}. + * obtained asynchronously, its values are wrapped inside a {@code Task}. * * @param The type to which this iterable will decode documents. */ @@ -65,7 +65,7 @@ public abstract class MongoIterable { /** * Returns a cursor of the operation represented by this iterable. *

              - * The result is wrapped in a {@link Task} since the iterator should be capable of + * The result is wrapped in a {@code Task} since the iterator should be capable of * asynchronously retrieve documents from the server. * * @return an asynchronous task with cursor of the operation represented by this iterable. @@ -83,7 +83,7 @@ public MongoCursor call() throws Exception { /** * Helper to return the first item in the iterator or null. *

              - * The result is wrapped in a {@link Task} since the iterator should be capable of + * The result is wrapped in a {@code Task} since the iterator should be capable of * asynchronously retrieve documents from the server. * * @return a task containing the first item or null. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java index 6b28b7890e..a3b7eef505 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java @@ -43,12 +43,12 @@ *

                *     App app = new App("app-id");
                *     User user = app.login(Credentials.anonymous());
              - *     SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, "")
              + *     SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, "<partition value>")
                *              .build();
                *     Realm instance = Realm.getInstance(syncConfiguration);
                *     SyncSession session = app.getSync().getSession(syncConfiguration);
                *
              - *     instance.executeTransaction(realm -> {
              + *     instance.executeTransaction(realm -> {
                *         realm.insert(...);
                *     });
                *     session.uploadAllLocalChanges();
              
              From 3574f1d45ce1cfc75217e580df58723edede2634 Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Mon, 8 Jun 2020 19:10:30 +0200
              Subject: [PATCH 1575/2110] Update to Sync Beta 2 (#6928)
              
              ---
               CHANGELOG.md                                                | 4 +++-
               dependencies.list                                           | 6 +++---
               realm/realm-library/src/main/cpp/object-store               | 2 +-
               .../app_config/services/BackingDB/config.json               | 1 +
               4 files changed, 8 insertions(+), 5 deletions(-)
              
              diff --git a/CHANGELOG.md b/CHANGELOG.md
              index 52371f3c95..93747bd4e6 100644
              --- a/CHANGELOG.md
              +++ b/CHANGELOG.md
              @@ -12,6 +12,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new
               
               ### Fixed
               * [RealmApp] `AppConfiguration` did not fallback to the correct default baseUrl if none was provided. (Since 10.0.0-BETA.1) 
              +* [RealmApp] When restarting an app, re-using the already logged in user would result in Sync not resuming. (Since 10.0.0-BETA.1)
               
               ### Compatibility
               * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later).
              @@ -19,7 +20,8 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new
               * Realm Studio 10.0.0 and above is required to open Realms created by this version.
               
               ### Internal
              -* None
              +* Updated to Object Store commit: c50be4dd178ef7e11d453f61a5ac2afa8c1c10bf.
              +* Updated to Realm Sync 10.0.0-beta.2.
               
               
               ## 10.0.0-BETA.1 (2020-06-05)
              diff --git a/dependencies.list b/dependencies.list
              index ab481e1e28..8a5d3b3380 100644
              --- a/dependencies.list
              +++ b/dependencies.list
              @@ -1,11 +1,11 @@
               # Realm Sync release used by Realm Java (This includes Realm Core)
               # https://github.com/realm/realm-sync/releases
              -REALM_SYNC_VERSION=10.0.0-beta.1
              -REALM_SYNC_SHA256=1ce7b45620fa0eb8465130db272f0ebf941ee84f561e3274c12aee423c96c176
              +REALM_SYNC_VERSION=10.0.0-beta.2
              +REALM_SYNC_SHA256=0572f751ced210656ed7d567dce9e56b5cca19c3070096ff38c255c9585ccf0f
               
               # Version of MongoDB Realm used by integration tests
               # See https://github.com/realm/ci/packages/147854 for available versions
              -MONGODB_REALM_SERVER_VERSION=2020-06-04
              +MONGODB_REALM_SERVER_VERSION=2020-06-08
               
               # Common Android settings across projects
               GRADLE_BUILD_TOOLS=3.6.1
              diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store
              index 6d081a5337..c50be4dd17 160000
              --- a/realm/realm-library/src/main/cpp/object-store
              +++ b/realm/realm-library/src/main/cpp/object-store
              @@ -1 +1 @@
              -Subproject commit 6d081a53377514f9b77736cb03051a03d829da92
              +Subproject commit c50be4dd178ef7e11d453f61a5ac2afa8c1c10bf
              diff --git a/tools/sync_test_server/app_config/services/BackingDB/config.json b/tools/sync_test_server/app_config/services/BackingDB/config.json
              index 674e448d9e..d26abdaa48 100644
              --- a/tools/sync_test_server/app_config/services/BackingDB/config.json
              +++ b/tools/sync_test_server/app_config/services/BackingDB/config.json
              @@ -8,6 +8,7 @@
                           "database_name": "test_data",
                           "partition": {
                               "key": "realm_id",
              +                "type": "string",
                               "permissions": {
                                   "read": true,
                                   "write": true
              
              From e77d53cdaad15dae041d672f0616d7b83ddebc7c Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Mon, 8 Jun 2020 19:12:17 +0200
              Subject: [PATCH 1576/2110] Release 10.0.0-BETA.2
              
              ---
               CHANGELOG.md | 2 +-
               version.txt  | 2 +-
               2 files changed, 2 insertions(+), 2 deletions(-)
              
              diff --git a/CHANGELOG.md b/CHANGELOG.md
              index 93747bd4e6..e4c7fd77bd 100644
              --- a/CHANGELOG.md
              +++ b/CHANGELOG.md
              @@ -1,4 +1,4 @@
              -## 10.0.0-BETA.2 (YYYY-MM-DD)
              +## 10.0.0-BETA.2 (2020-06-08)
               
               We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database.
               
              diff --git a/version.txt b/version.txt
              index edbe204602..3b18983462 100644
              --- a/version.txt
              +++ b/version.txt
              @@ -1 +1 @@
              -10.0.0-BETA.2-SNAPSHOT
              +10.0.0-BETA.2
              
              From c6f333c3f2dfb1011fee36a756e6afaab1e6d163 Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Mon, 8 Jun 2020 19:16:58 +0200
              Subject: [PATCH 1577/2110] Prepare next dev iteration
              
              ---
               version.txt | 2 +-
               1 file changed, 1 insertion(+), 1 deletion(-)
              
              diff --git a/version.txt b/version.txt
              index 3b18983462..ce97724f6a 100644
              --- a/version.txt
              +++ b/version.txt
              @@ -1 +1 @@
              -10.0.0-BETA.2
              +10.0.0-BETA.3-SNAPSHOT
              
              From b459a9bc93203aca744a65712b6681169d68fdf6 Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Tue, 9 Jun 2020 15:39:40 +0200
              Subject: [PATCH 1578/2110] Prepare BETA.3 release (#6936)
              
              ---
               CHANGELOG.md                                  | 24 +++++++++++++++++++
               realm/realm-library/src/main/cpp/object-store |  2 +-
               2 files changed, 25 insertions(+), 1 deletion(-)
              
              diff --git a/CHANGELOG.md b/CHANGELOG.md
              index e4c7fd77bd..f29a97d8b1 100644
              --- a/CHANGELOG.md
              +++ b/CHANGELOG.md
              @@ -1,3 +1,27 @@
              +## 10.0.0-BETA.3 (2020-06-09)
              +
              +We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database.
              +
              +The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point.
              +
              +### Breaking Changes 
              +* None
              +
              +### Enhancements
              +* None
              +
              +### Fixed
              +* [RealmApp] When restarting an app, the base URL used would in some cases be incorrect. (Since 10.0.0-BETA.2)
              +
              +### Compatibility
              +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later).
              +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series.
              +* Realm Studio 10.0.0 and above is required to open Realms created by this version.
              +
              +### Internal
              +* Updated to Object Store commit: c02707bc28e1886970c5da29ef481dc0cb6c3dd8.
              +
              +
               ## 10.0.0-BETA.2 (2020-06-08)
               
               We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database.
              diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store
              index c50be4dd17..c02707bc28 160000
              --- a/realm/realm-library/src/main/cpp/object-store
              +++ b/realm/realm-library/src/main/cpp/object-store
              @@ -1 +1 @@
              -Subproject commit c50be4dd178ef7e11d453f61a5ac2afa8c1c10bf
              +Subproject commit c02707bc28e1886970c5da29ef481dc0cb6c3dd8
              
              From f445aec64a185a7299d43b310cfb0af134c74885 Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Tue, 9 Jun 2020 15:50:36 +0200
              Subject: [PATCH 1579/2110] Release 10.0.0-BETA.3
              
              ---
               version.txt | 2 +-
               1 file changed, 1 insertion(+), 1 deletion(-)
              
              diff --git a/version.txt b/version.txt
              index ce97724f6a..56f2e8d486 100644
              --- a/version.txt
              +++ b/version.txt
              @@ -1 +1 @@
              -10.0.0-BETA.3-SNAPSHOT
              +10.0.0-BETA.3
              
              From 050ec0b1cf78d97b2b01881365c7160254ec1c8a Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Tue, 9 Jun 2020 19:06:47 +0200
              Subject: [PATCH 1580/2110] Prepare next dev iteration
              
              ---
               version.txt | 2 +-
               1 file changed, 1 insertion(+), 1 deletion(-)
              
              diff --git a/version.txt b/version.txt
              index 56f2e8d486..fec6ffa46b 100644
              --- a/version.txt
              +++ b/version.txt
              @@ -1 +1 @@
              -10.0.0-BETA.3
              +10.0.0-BETA.4-SNAPSHOT
              
              From 672a355e340b62d6a54a863e44eb864d27d05c92 Mon Sep 17 00:00:00 2001
              From: =?UTF-8?q?Eduardo=20L=C3=B3pez?=
               <1874445+edualonso@users.noreply.github.com>
              Date: Thu, 11 Jun 2020 09:37:04 +0200
              Subject: [PATCH 1581/2110] Add support for push notifications (#6935)
              MIME-Version: 1.0
              Content-Type: text/plain; charset=UTF-8
              Content-Transfer-Encoding: 8bit
              
              * Added support for push notifications
              
              * Added OsPushClient
              
              * Removed unused includes in native code
              
              * Added documentation
              
              * Restore Gradle build tools version
              
              * Removed FCM config file
              
              * Moved FCM config params to script instead of having them in the config files
              
              * Removed rest of needed push test parameters from config files
              
              * Restored server config to not use FCM app keys but placeholder values instead
              
              * Addressed comments from PR review: renamed client to Push, corrected wrong operations in tests and a bit of cleanup
              
              * Added synchronized to mongoclient as well
              
              * Removed Task and favour a sync/async implementation instead. Removed currentUser and use a pointer to a user instead. Fixed wrong initialisation of push client from the Java side, as I was unknowingly instantiating a native PushClient with every register/deregister call.
              
              Co-authored-by: Eduardo López 
              ---
               .../src/androidTest/AndroidManifest.xml       |   1 +
               .../kotlin/io/realm/mongodb/push/PushTest.kt  | 168 ++++++++++++++++++
               .../realm-library/src/main/cpp/CMakeLists.txt |   4 +-
               ...internal_objectstore_OsMongoCollection.cpp |   1 -
               .../io_realm_internal_objectstore_OsPush.cpp  | 101 +++++++++++
               ...ngodb_mongo_iterable_AggregateIterable.cpp |   4 -
               ...lm_mongodb_mongo_iterable_FindIterable.cpp |   4 -
               .../io/realm/internal/objectstore/OsPush.java |  67 +++++++
               .../java/io/realm/mongodb/User.java           |  25 ++-
               .../java/io/realm/mongodb/push/Push.java      |  78 +++++++-
               .../app_config/auth_providers/anon-user.json  |   2 +-
               .../app_config/auth_providers/api-key.json    |   2 +-
               .../auth_providers/custom-function.json       |   2 +-
               .../auth_providers/local-userpass.json        |   2 +-
               .../app_config/functions/authFunc/config.json |   2 +-
               .../functions/authorizedOnly/config.json      |   2 +-
               .../functions/confirmFunc/config.json         |   2 +-
               .../app_config/functions/error/config.json    |   2 +-
               .../app_config/functions/firstArg/config.json |   2 +-
               .../app_config/functions/null/config.json     |   2 +-
               .../functions/resetFunc/config.json           |   2 +-
               .../app_config/functions/sum/config.json      |   2 +-
               .../app_config/functions/void/config.json     |   2 +-
               .../sync_test_server/app_config/secrets.json  |   2 +-
               .../app_config/services/BackingDB/config.json |   2 +-
               .../BackingDB/rules/test_data.SyncDog.json    |  62 +++----
               .../BackingDB/rules/test_data.SyncPerson.json |   2 +-
               .../BackingDB/rules/test_data.mongo_data.json |  46 ++---
               .../app_config/services/gcm/config.json       |  12 ++
               tools/sync_test_server/app_config/stitch.json |   2 +-
               tools/sync_test_server/setup_mongodb_realm.sh |  10 +-
               31 files changed, 528 insertions(+), 89 deletions(-)
               create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/push/PushTest.kt
               create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp
               create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java
               create mode 100644 tools/sync_test_server/app_config/services/gcm/config.json
              
              diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml
              index eaafa2d7d1..eda3aabdc9 100644
              --- a/realm/realm-library/src/androidTest/AndroidManifest.xml
              +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml
              @@ -18,6 +18,7 @@
                       android:largeHeap="true"
                       android:networkSecurityConfig="@xml/network_security_config">
                       
              +
                       
               #include 
              -#include 
               #include 
               #include 
               #include 
              diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp
              new file mode 100644
              index 0000000000..c7229e58f7
              --- /dev/null
              +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp
              @@ -0,0 +1,101 @@
              +/*
              + * Copyright 2020 Realm Inc.
              + *
              + * 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 "io_realm_internal_objectstore_OsPush.h"
              +
              +#include "java_class_global_def.hpp"
              +#include "java_network_transport.hpp"
              +#include "util.hpp"
              +#include "jni_util/java_method.hpp"
              +#include "jni_util/jni_utils.hpp"
              +
              +#include 
              +#include 
              +#include 
              +
              +using namespace realm;
              +using namespace realm::app;
              +using namespace realm::bson;
              +using namespace realm::jni_util;
              +using namespace realm::_impl;
              +
              +static void finalize_push_client(jlong ptr) {
              +    delete reinterpret_cast(ptr);
              +}
              +
              +JNIEXPORT jlong JNICALL
              +Java_io_realm_internal_objectstore_OsPush_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) {
              +    return reinterpret_cast(&finalize_push_client);
              +}
              +
              +JNIEXPORT jlong JNICALL
              +Java_io_realm_internal_objectstore_OsPush_nativeCreate(JNIEnv* env,
              +                                                       jclass,
              +                                                       jlong j_app_ptr,
              +                                                       jstring j_service_name) {
              +    try {
              +        std::shared_ptr &app = *reinterpret_cast *>(j_app_ptr);
              +        JStringAccessor service_name(env, j_service_name);
              +        PushClient client(app->push_notification_client(service_name));
              +        return reinterpret_cast(new PushClient(std::move(client)));
              +    }
              +    CATCH_STD()
              +    return reinterpret_cast(nullptr);
              +}
              +
              +JNIEXPORT void JNICALL
              +Java_io_realm_internal_objectstore_OsPush_nativeRegisterDevice(JNIEnv *env,
              +                                                               jclass,
              +                                                               jlong j_push_client_ptr,
              +                                                               jlong j_user_ptr,
              +                                                               jstring j_service_name,
              +                                                               jstring j_registration_token,
              +                                                               jobject j_callback) {
              +    try {
              +        auto push_client = reinterpret_cast(j_push_client_ptr);
              +        auto user = *reinterpret_cast*>(j_user_ptr);
              +
              +        JStringAccessor service_name(env, j_service_name);
              +        JStringAccessor registration_token(env, j_registration_token);
              +
              +        push_client->register_device(registration_token,
              +                                     user,
              +                                     JavaNetworkTransport::create_void_callback(env, j_callback));
              +    }
              +    CATCH_STD()
              +}
              +
              +JNIEXPORT void JNICALL
              +Java_io_realm_internal_objectstore_OsPush_nativeDeregisterDevice(JNIEnv *env,
              +                                                               jclass,
              +                                                               jlong j_push_client_ptr,
              +                                                               jlong j_user_ptr,
              +                                                               jstring j_service_name,
              +                                                               jstring j_registration_token,
              +                                                               jobject j_callback) {
              +    try {
              +        auto push_client = reinterpret_cast(j_push_client_ptr);
              +        auto user = *reinterpret_cast*>(j_user_ptr);
              +
              +        JStringAccessor service_name(env, j_service_name);
              +        JStringAccessor registration_token(env, j_registration_token);
              +
              +        push_client->deregister_device(registration_token,
              +                                       user,
              +                                       JavaNetworkTransport::create_void_callback(env, j_callback));
              +    }
              +    CATCH_STD()
              +}
              diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp
              index 415573d51f..24efc1349a 100644
              --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp
              +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp
              @@ -25,15 +25,11 @@
               #include "object-store/src/util/bson/bson.hpp"
               
               #include 
              -#include 
              -#include 
              -#include 
               #include 
               #include 
               #include 
               
               using namespace realm;
              -using namespace realm::app;
               using namespace realm::bson;
               using namespace realm::jni_util;
               using namespace realm::_impl;
              diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp
              index 54e1a10d7d..b034dd26fb 100644
              --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp
              +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp
              @@ -25,14 +25,10 @@
               #include "object-store/src/util/bson/bson.hpp"
               
               #include 
              -#include 
              -#include 
              -#include 
               #include 
               #include 
               
               using namespace realm;
              -using namespace realm::app;
               using namespace realm::bson;
               using namespace realm::jni_util;
               using namespace realm::_impl;
              diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java
              new file mode 100644
              index 0000000000..aecf443b6b
              --- /dev/null
              +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java
              @@ -0,0 +1,67 @@
              +/*
              + * Copyright 2020 Realm Inc.
              + *
              + * 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.
              + */
              +
              +package io.realm.internal.objectstore;
              +
              +import java.util.concurrent.atomic.AtomicReference;
              +
              +import io.realm.internal.NativeObject;
              +import io.realm.internal.jni.OsJNIVoidResultCallback;
              +import io.realm.internal.network.ResultHandler;
              +import io.realm.mongodb.AppException;
              +import io.realm.mongodb.User;
              +
              +public class OsPush implements NativeObject {
              +
              +    private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr();
              +
              +    private final long nativePtr;
              +    private final OsSyncUser osSyncUser;
              +    private final String serviceName;
              +
              +    public OsPush(final long appNativePtr, final OsSyncUser osSyncUser, final String serviceName) {
              +        this.nativePtr = nativeCreate(appNativePtr, serviceName);
              +        this.osSyncUser = osSyncUser;
              +        this.serviceName = serviceName;
              +    }
              +
              +    @Override
              +    public long getNativePtr() {
              +        return nativePtr;
              +    }
              +
              +    @Override
              +    public long getNativeFinalizerPtr() {
              +        return nativeFinalizerPtr;
              +    }
              +
              +    public void registerDevice(String registrationToken) {
              +        AtomicReference error = new AtomicReference<>(null);
              +        nativeRegisterDevice(nativePtr, osSyncUser.getNativePtr(), serviceName, registrationToken, new OsJNIVoidResultCallback(error));
              +        ResultHandler.handleResult(null, error);
              +    }
              +
              +    public void deregisterDevice(String registrationToken) {
              +        AtomicReference error = new AtomicReference<>(null);
              +        nativeDeregisterDevice(nativePtr, osSyncUser.getNativePtr(), serviceName, registrationToken, new OsJNIVoidResultCallback(error));
              +        ResultHandler.handleResult(null, error);
              +    }
              +
              +    private static native long nativeCreate(long nativeAppPtr, String serviceName);
              +    private static native long nativeGetFinalizerMethodPtr();
              +    private static native void nativeRegisterDevice(long nativePtr, long nativeUserPtr, String serviceName, String registrationToken, OsJNIVoidResultCallback callback);
              +    private static native void nativeDeregisterDevice(long nativePtr, long nativeUserPtr, String serviceName, String registrationToken, OsJNIVoidResultCallback callback);
              +}
              diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java
              index 72f440d28a..8795e3b4a3 100644
              --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java
              +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java
              @@ -34,6 +34,7 @@
               import io.realm.internal.network.ResultHandler;
               import io.realm.internal.objectstore.OsJavaNetworkTransport;
               import io.realm.internal.objectstore.OsMongoClient;
              +import io.realm.internal.objectstore.OsPush;
               import io.realm.internal.objectstore.OsSyncUser;
               import io.realm.internal.util.Pair;
               import io.realm.mongodb.auth.ApiKeyAuth;
              @@ -59,6 +60,8 @@ public class User {
                   private ApiKeyAuth apiKeyAuthProvider = null;
                   private MongoClient mongoClient = null;
                   private Functions functions = null;
              +    private Push push = null;
              +    private TaskDispatcher dispatcher = null;
               
                   /**
                    * The different types of users.
              @@ -106,6 +109,12 @@ protected MongoClientImpl(OsMongoClient osMongoClient,
                       }
                   }
               
              +    private static class PushImpl extends Push {
              +        protected PushImpl(OsPush osPush) {
              +            super(osPush);
              +        }
              +    }
              +
                   User(long nativePtr, App app) {
                       this.osUser = new OsSyncUser(nativePtr);
                       this.app = app;
              @@ -505,20 +514,26 @@ public Functions getFunctions(CodecRegistry codecRegistry) {
                   }
               
                   /**
              -     * FIXME Add support for push notifications.
              +     * Returns the {@link Push} instance for managing push notification registrations.
                    */
              -    Push getPush() {
              -        return null;
              +    public synchronized Push getPush(String serviceName) {
              +        if (push == null) {
              +            OsPush osPush = new OsPush(app.nativePtr, osUser, serviceName);
              +            push = new PushImpl(osPush);
              +        }
              +        return push;
                   }
               
                   /**
                    * Returns a {@link MongoClient} instance for accessing documents in the database.
                    * @param serviceName the service name used to connect to the server
                    */
              -    public MongoClient getMongoClient(String serviceName) {
              +    public synchronized MongoClient getMongoClient(String serviceName) {
                       Util.checkEmpty(serviceName, "serviceName");
                       if (mongoClient == null) {
              -            TaskDispatcher dispatcher = new TaskDispatcher();
              +            if (dispatcher == null) {
              +                dispatcher = new TaskDispatcher();
              +            }
                           OsMongoClient osMongoClient = new OsMongoClient(app.nativePtr, serviceName, dispatcher);
                           mongoClient = new MongoClientImpl(osMongoClient, app.getConfiguration().getDefaultCodecRegistry(), dispatcher);
                       }
              diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java
              index 2cfc102dce..2efd5b7b9f 100644
              --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java
              +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java
              @@ -15,11 +15,85 @@
                */
               package io.realm.mongodb.push;
               
              +import io.realm.RealmAsyncTask;
               import io.realm.annotations.Beta;
              +import io.realm.internal.Util;
              +import io.realm.internal.mongodb.Request;
              +import io.realm.internal.objectstore.OsPush;
              +import io.realm.mongodb.App;
              +import io.realm.mongodb.AppException;
               
               /**
              - * FIXME: Add Javadoc and implementation
              + * The Push client allows to register/deregister for push notifications from a client app.
                */
               @Beta
              -public class Push {
              +public abstract class Push {
              +
              +    private final OsPush osPush;
              +
              +    public Push(final OsPush osPush) {
              +        this.osPush = osPush;
              +    }
              +
              +    /**
              +     * Registers the given FCM registration token with the currently logged in user's
              +     * device on MongoDB Realm.
              +     *
              +     * @param registrationToken The registration token to register.
              +     */
              +    public void registerDevice(String registrationToken) {
              +        osPush.registerDevice(registrationToken);
              +    }
              +
              +    /**
              +     * Registers the given FCM registration token with the currently logged in user's
              +     * device on MongoDB Realm.
              +     *
              +     * @param registrationToken The registration token to register.
              +     * @param callback          The callback used when the device has been registered or the call
              +     *                          failed - it will always happen on the same thread as this method was
              +     *                          called on.
              +     */
              +    public RealmAsyncTask registerDeviceAsync(String registrationToken,
              +                                              App.Callback callback) {
              +        Util.checkLooperThread("Asynchronous registering a device is only possible from looper threads.");
              +        return new Request(App.NETWORK_POOL_EXECUTOR, callback) {
              +            @Override
              +            public Void run() throws AppException {
              +                osPush.registerDevice(registrationToken);
              +                return null;
              +            }
              +        }.start();
              +    }
              +
              +    /**
              +     * Deregisters the FCM registration token bound to the currently logged in user's
              +     * device on MongoDB Realm.
              +     *
              +     * @param registrationToken the registration token to deregister.
              +     */
              +    public void deregisterDevice(String registrationToken) {
              +        osPush.deregisterDevice(registrationToken);
              +    }
              +
              +    /**
              +     * Deregisters the FCM registration token bound to the currently logged in user's
              +     * device on MongoDB Realm.
              +     *
              +     * @param registrationToken The registration token to register.
              +     * @param callback          The callback used when the device has been registered or the call
              +     *                          failed - it will always happen on the same thread as this method was
              +     *                          called on.
              +     */
              +    public RealmAsyncTask deregisterDeviceAsync(String registrationToken,
              +                                                App.Callback callback) {
              +        Util.checkLooperThread("Asynchronous deregistering a device is only possible from looper threads.");
              +        return new Request(App.NETWORK_POOL_EXECUTOR, callback) {
              +            @Override
              +            public Void run() throws AppException {
              +                osPush.deregisterDevice(registrationToken);
              +                return null;
              +            }
              +        }.start();
              +    }
               }
              diff --git a/tools/sync_test_server/app_config/auth_providers/anon-user.json b/tools/sync_test_server/app_config/auth_providers/anon-user.json
              index a57bb6eae2..44a270d6a6 100644
              --- a/tools/sync_test_server/app_config/auth_providers/anon-user.json
              +++ b/tools/sync_test_server/app_config/auth_providers/anon-user.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5e9578c1a06f8d660afdaa34",
              +    "id": "5edea20f0f99fe616bf40ae9",
                   "name": "anon-user",
                   "type": "anon-user",
                   "disabled": false
              diff --git a/tools/sync_test_server/app_config/auth_providers/api-key.json b/tools/sync_test_server/app_config/auth_providers/api-key.json
              index c9a0167893..526ab8a2a4 100644
              --- a/tools/sync_test_server/app_config/auth_providers/api-key.json
              +++ b/tools/sync_test_server/app_config/auth_providers/api-key.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5e9578c1a06f8d660afdaa35",
              +    "id": "5edea20f0f99fe616bf40aea",
                   "name": "api-key",
                   "type": "api-key",
                   "disabled": false
              diff --git a/tools/sync_test_server/app_config/auth_providers/custom-function.json b/tools/sync_test_server/app_config/auth_providers/custom-function.json
              index 340315f6c6..705071c52a 100644
              --- a/tools/sync_test_server/app_config/auth_providers/custom-function.json
              +++ b/tools/sync_test_server/app_config/auth_providers/custom-function.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5e9578c1a06f8d660afdaa36",
              +    "id": "5edea20f0f99fe616bf40aeb",
                   "name": "custom-function",
                   "type": "custom-function",
                   "config": {
              diff --git a/tools/sync_test_server/app_config/auth_providers/local-userpass.json b/tools/sync_test_server/app_config/auth_providers/local-userpass.json
              index 77056f028f..f0c1e1392d 100644
              --- a/tools/sync_test_server/app_config/auth_providers/local-userpass.json
              +++ b/tools/sync_test_server/app_config/auth_providers/local-userpass.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5e9578c1a06f8d660afdaa37",
              +    "id": "5edea20f0f99fe616bf40aec",
                   "name": "local-userpass",
                   "type": "local-userpass",
                   "config": {
              diff --git a/tools/sync_test_server/app_config/functions/authFunc/config.json b/tools/sync_test_server/app_config/functions/authFunc/config.json
              index 43e28c813c..bb90ef5953 100644
              --- a/tools/sync_test_server/app_config/functions/authFunc/config.json
              +++ b/tools/sync_test_server/app_config/functions/authFunc/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5eba4fbc21bb6f152f45a84d",
              +    "id": "5edea20f0f99fe616bf40ae0",
                   "name": "authFunc",
                   "private": false,
                   "can_evaluate": {}
              diff --git a/tools/sync_test_server/app_config/functions/authorizedOnly/config.json b/tools/sync_test_server/app_config/functions/authorizedOnly/config.json
              index b400765f4f..44b10f4502 100644
              --- a/tools/sync_test_server/app_config/functions/authorizedOnly/config.json
              +++ b/tools/sync_test_server/app_config/functions/authorizedOnly/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5ebd96e125e549dd987715a0",
              +    "id": "5edea20f0f99fe616bf40ae1",
                   "name": "authorizedOnly",
                   "private": false,
                   "can_evaluate": {
              diff --git a/tools/sync_test_server/app_config/functions/confirmFunc/config.json b/tools/sync_test_server/app_config/functions/confirmFunc/config.json
              index 117b601c20..63d671ba8e 100644
              --- a/tools/sync_test_server/app_config/functions/confirmFunc/config.json
              +++ b/tools/sync_test_server/app_config/functions/confirmFunc/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5eba4fbc21bb6f152f45a84e",
              +    "id": "5edea20f0f99fe616bf40ae2",
                   "name": "confirmFunc",
                   "private": false,
                   "can_evaluate": {}
              diff --git a/tools/sync_test_server/app_config/functions/error/config.json b/tools/sync_test_server/app_config/functions/error/config.json
              index 2625f03553..8ce3d08c4d 100644
              --- a/tools/sync_test_server/app_config/functions/error/config.json
              +++ b/tools/sync_test_server/app_config/functions/error/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5eba4fbc21bb6f152f45a850",
              +    "id": "5edea20f0f99fe616bf40ae3",
                   "name": "error",
                   "private": false
               }
              diff --git a/tools/sync_test_server/app_config/functions/firstArg/config.json b/tools/sync_test_server/app_config/functions/firstArg/config.json
              index fc1fc81ca8..8afbba9482 100644
              --- a/tools/sync_test_server/app_config/functions/firstArg/config.json
              +++ b/tools/sync_test_server/app_config/functions/firstArg/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5eba4fbc21bb6f152f45a851",
              +    "id": "5edea20f0f99fe616bf40ae4",
                   "name": "firstArg",
                   "private": false
               }
              diff --git a/tools/sync_test_server/app_config/functions/null/config.json b/tools/sync_test_server/app_config/functions/null/config.json
              index cefb66ac08..5402649ec4 100644
              --- a/tools/sync_test_server/app_config/functions/null/config.json
              +++ b/tools/sync_test_server/app_config/functions/null/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5eba4fbc21bb6f152f45a852",
              +    "id": "5edea20f0f99fe616bf40ae5",
                   "name": "null",
                   "private": false
               }
              diff --git a/tools/sync_test_server/app_config/functions/resetFunc/config.json b/tools/sync_test_server/app_config/functions/resetFunc/config.json
              index 0d014d4c02..8d8da8eb2e 100644
              --- a/tools/sync_test_server/app_config/functions/resetFunc/config.json
              +++ b/tools/sync_test_server/app_config/functions/resetFunc/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5eba4fbc21bb6f152f45a853",
              +    "id": "5edea20f0f99fe616bf40ae6",
                   "name": "resetFunc",
                   "private": false,
                   "can_evaluate": {}
              diff --git a/tools/sync_test_server/app_config/functions/sum/config.json b/tools/sync_test_server/app_config/functions/sum/config.json
              index 6461497828..ca48a1328c 100644
              --- a/tools/sync_test_server/app_config/functions/sum/config.json
              +++ b/tools/sync_test_server/app_config/functions/sum/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5eba4fbc21bb6f152f45a854",
              +    "id": "5edea20f0f99fe616bf40ae7",
                   "name": "sum",
                   "private": false
               }
              diff --git a/tools/sync_test_server/app_config/functions/void/config.json b/tools/sync_test_server/app_config/functions/void/config.json
              index 3fbcfb56f6..9992ccd8c8 100644
              --- a/tools/sync_test_server/app_config/functions/void/config.json
              +++ b/tools/sync_test_server/app_config/functions/void/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5eba4fbc21bb6f152f45a84f",
              +    "id": "5edea20f0f99fe616bf40ae8",
                   "name": "void",
                   "private": false
               }
              diff --git a/tools/sync_test_server/app_config/secrets.json b/tools/sync_test_server/app_config/secrets.json
              index 533fe384b0..36be8d8379 100644
              --- a/tools/sync_test_server/app_config/secrets.json
              +++ b/tools/sync_test_server/app_config/secrets.json
              @@ -1,3 +1,3 @@
               {
                   "BackingDB_uri": "mongodb://localhost:26000"
              -}
              \ No newline at end of file
              +}
              diff --git a/tools/sync_test_server/app_config/services/BackingDB/config.json b/tools/sync_test_server/app_config/services/BackingDB/config.json
              index d26abdaa48..4900710917 100644
              --- a/tools/sync_test_server/app_config/services/BackingDB/config.json
              +++ b/tools/sync_test_server/app_config/services/BackingDB/config.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5e9578c1a06f8d660afdaa2b",
              +    "id": "5edea20f0f99fe616bf40adc",
                   "name": "BackingDB",
                   "type": "mongodb",
                   "config": {
              diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json
              index c1980fbde9..0910f22ec0 100644
              --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json
              +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json
              @@ -1,34 +1,34 @@
               {
              -  "id": "5e9578c1a06f8d660afdaa2c",
              -  "database": "test_data",
              -  "collection": "SyncDog",
              -  "roles": [
              -    {
              -      "name": "default",
              -      "apply_when": {},
              -      "insert": true,
              -      "delete": true,
              -      "additional_fields": {}
              -    }
              -  ],
              -  "schema": {
              -    "properties": {
              -      "_id": {
              -        "bsonType": "objectId"
              -      },
              -      "breed": {
              -        "bsonType": "string"
              -      },
              -      "name": {
              -        "bsonType": "string"
              -      },
              -      "realm_id": {
              -        "bsonType": "string"
              -      }
              -    },
              -    "required": [
              -      "name"
              +    "id": "5edea20f0f99fe616bf40add",
              +    "database": "test_data",
              +    "collection": "SyncDog",
              +    "roles": [
              +        {
              +            "name": "default",
              +            "apply_when": {},
              +            "insert": true,
              +            "delete": true,
              +            "additional_fields": {}
              +        }
                   ],
              -    "title": "SyncDog"
              -  }
              +    "schema": {
              +        "properties": {
              +            "_id": {
              +                "bsonType": "objectId"
              +            },
              +            "breed": {
              +                "bsonType": "string"
              +            },
              +            "name": {
              +                "bsonType": "string"
              +            },
              +            "realm_id": {
              +                "bsonType": "string"
              +            }
              +        },
              +        "required": [
              +            "name"
              +        ],
              +        "title": "SyncDog"
              +    }
               }
              diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json
              index c7e8e69360..fd6a73bf1b 100644
              --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json
              +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json
              @@ -1,5 +1,5 @@
               {
              -    "id": "5e9578c1a06f8d660afdaa2d",
              +    "id": "5edea20f0f99fe616bf40ade",
                   "database": "test_data",
                   "collection": "SyncPerson",
                   "relationships": {
              diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json
              index 45221c81a6..52d43c755d 100644
              --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json
              +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json
              @@ -1,26 +1,26 @@
               {
              -  "database": "test_data",
              -  "collection": "mongo_data",
              -  "roles": [
              -    {
              -      "name": "default",
              -      "apply_when": {},
              -      "insert": true,
              -      "delete": true,
              -      "additional_fields": {}
              -    }
              -  ],
              -  "schema": {
              -    "properties": {
              -      "_id": {
              -        "bsonType": "objectId"
              -      },
              -      "realm_id": {
              -        "bsonType": "string"
              -      }
              -    },
              -    "required": [
              +    "id": "5edea20f0f99fe616bf40adf",
              +    "database": "test_data",
              +    "collection": "mongo_data",
              +    "roles": [
              +        {
              +            "name": "default",
              +            "apply_when": {},
              +            "insert": true,
              +            "delete": true,
              +            "additional_fields": {}
              +        }
                   ],
              -    "title": "mongo_data"
              -  }
              +    "schema": {
              +        "properties": {
              +            "_id": {
              +                "bsonType": "objectId"
              +            },
              +            "realm_id": {
              +                "bsonType": "string"
              +            }
              +        },
              +        "required": [],
              +        "title": "mongo_data"
              +    }
               }
              diff --git a/tools/sync_test_server/app_config/services/gcm/config.json b/tools/sync_test_server/app_config/services/gcm/config.json
              new file mode 100644
              index 0000000000..1560c44316
              --- /dev/null
              +++ b/tools/sync_test_server/app_config/services/gcm/config.json
              @@ -0,0 +1,12 @@
              +{
              +    "id": "5edea2480f99fe616bf40b6a",
              +    "name": "gcm",
              +    "type": "gcm",
              +    "config": {
              +        "senderId": "gcm"
              +    },
              +    "secret_config": {
              +        "apiKey": "gcm"
              +    },
              +    "version": 1
              +}
              diff --git a/tools/sync_test_server/app_config/stitch.json b/tools/sync_test_server/app_config/stitch.json
              index ccbd792c4e..cb08ae9e81 100644
              --- a/tools/sync_test_server/app_config/stitch.json
              +++ b/tools/sync_test_server/app_config/stitch.json
              @@ -1,5 +1,5 @@
               {
              -    "app_id": "realm-sdk-integration-tests-ibecp",
              +    "app_id": "realm-sdk-integration-tests-nldpe",
                   "config_version": 20180301,
                   "name": "realm-sdk-integration-tests",
                   "location": "US-VA",
              diff --git a/tools/sync_test_server/setup_mongodb_realm.sh b/tools/sync_test_server/setup_mongodb_realm.sh
              index 3771a43d25..861c592ef8 100755
              --- a/tools/sync_test_server/setup_mongodb_realm.sh
              +++ b/tools/sync_test_server/setup_mongodb_realm.sh
              @@ -59,6 +59,14 @@ stitch-cli secrets add \
                                 --base-url=http://localhost:9090 \
                                 --config-path=/tmp/stitch-config
               
              +#    - b) GCM (Firebase Cloud Messaging): Requires a server key - add your key here to test actual push notifications.
              +stitch-cli secrets add \
              +                  --name="gcm" \
              +                  --value="gcm" \
              +                  --app-id="realm-sdk-integration-tests-$APP_ID_SUFFIX" \
              +                  --base-url=http://localhost:9090 \
              +                  --config-path=/tmp/stitch-config
              +
               # 4. Now we can correctly import the Stitch app
               stitch-cli import \
                                 --config-path=/tmp/stitch-config \
              @@ -70,4 +78,4 @@ stitch-cli import \
                                 -y
               
               # 5. Store the application id in the Command Server so it can be accessed by Integration Tests on the device
              -curl -X PUT -d id="realm-sdk-integration-tests-$APP_ID_SUFFIX" http://localhost:8888/application-id
              \ No newline at end of file
              +curl -X PUT -d id="realm-sdk-integration-tests-$APP_ID_SUFFIX" http://localhost:8888/application-id
              
              From 239e7aa5c3d1ae46b48860a851f9a87ddef6a86b Mon Sep 17 00:00:00 2001
              From: =?UTF-8?q?Claus=20R=C3=B8rbech?= 
              Date: Thu, 11 Jun 2020 13:08:50 +0200
              Subject: [PATCH 1582/2110] Support for custom user data (#6930)
              
              ---
               .../kotlin/io/realm/UserTests.kt              | 91 ++++++++++++++++++-
               .../io/realm/mongodb/MongoClientTest.kt       |  6 +-
               ..._realm_internal_objectstore_OsSyncUser.cpp | 28 ++++++
               .../src/main/cpp/io_realm_mongodb_User.cpp    |  1 +
               .../internal/objectstore/OsSyncUser.java      | 28 ++++++
               .../java/io/realm/mongodb/User.java           | 46 ++++++++++
               .../io/realm/mongodb/functions/Functions.java |  6 +-
               .../syncTestUtils/kotlin/io/realm/TestApp.kt  |  3 +
               .../rules/test_data.custom_user_data.json     | 15 +++
               tools/sync_test_server/app_config/stitch.json |  6 +-
               10 files changed, 220 insertions(+), 10 deletions(-)
               create mode 100644 tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json
              
              diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt
              index ef5e4c9c8d..7384f175ab 100644
              --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt
              +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt
              @@ -21,13 +21,18 @@ import io.realm.admin.ServerAdmin
               import io.realm.mongodb.*
               import io.realm.mongodb.auth.ApiKeyAuth
               import io.realm.rule.BlockingLooperThread
              +import io.realm.util.blockingGetResult
              +import org.bson.Document
               import org.junit.After
               import org.junit.Assert.*
               import org.junit.Before
               import org.junit.Ignore
               import org.junit.Test
               import org.junit.runner.RunWith
              -import java.lang.IllegalArgumentException
              +import kotlin.test.assertFailsWith
              +
              +val CUSTOM_USER_DATA_FIELD = "custom_field"
              +val CUSTOM_USER_DATA_VALUE = "custom_data"
               
               @RunWith(AndroidJUnit4::class)
               class UserTests {
              @@ -327,4 +332,88 @@ class UserTests {
                       assertEquals(user.hashCode(), sameUserNewLogin.hashCode())
                   }
               
              +    @Test
              +    @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " +
              +            "https://github.com/realm/realm-java/issues/6942")
              +    fun customData_initiallyEmpty() {
              +        val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456")
              +        // Newly registered users do not have any custom data with current test server setup
              +        assertEquals(Document(), user.customData)
              +    }
              +
              +    @Test
              +    @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " +
              +            "https://github.com/realm/realm-java/issues/6942")
              +    fun customData_refresh() {
              +        val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456")
              +        // Newly registered users do not have any custom data with current test server setup
              +        assertEquals(Document(), user.customData)
              +
              +        updateCustomData(user, Document(CUSTOM_USER_DATA_FIELD, CUSTOM_USER_DATA_VALUE))
              +
              +        val updatedCustomData = user.refreshCustomData()
              +        assertEquals(CUSTOM_USER_DATA_VALUE, updatedCustomData[CUSTOM_USER_DATA_FIELD])
              +        assertEquals(CUSTOM_USER_DATA_VALUE, user.customData[CUSTOM_USER_DATA_FIELD])
              +    }
              +
              +    @Test
              +    @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " +
              +            "https://github.com/realm/realm-java/issues/6942")
              +    fun customData_refreshAsync() = looperThread.runBlocking {
              +        val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456")
              +        // Newly registered users do not have any custom data with current test server setup
              +        assertEquals(Document(), user.customData)
              +
              +        updateCustomData(user, Document(CUSTOM_USER_DATA_FIELD, CUSTOM_USER_DATA_VALUE))
              +
              +        val updatedCustomData = user.refreshCustomData { result ->
              +            val updatedCustomData = result.orThrow
              +            assertEquals(CUSTOM_USER_DATA_VALUE, updatedCustomData[CUSTOM_USER_DATA_FIELD])
              +            assertEquals(CUSTOM_USER_DATA_VALUE, user.customData[CUSTOM_USER_DATA_FIELD])
              +            looperThread.testComplete()
              +        }
              +    }
              +
              +    @Test
              +    @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " +
              +            "https://github.com/realm/realm-java/issues/6942")
              +    fun customData_refreshByLogout() {
              +        val password = "123456"
              +        val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), password)
              +        // Newly registered users do not have any custom data with current test server setup
              +        assertEquals(Document(), user.customData)
              +
              +        updateCustomData(user, Document(CUSTOM_USER_DATA_FIELD, CUSTOM_USER_DATA_VALUE))
              +
              +        // But will be updated when authorization token is refreshed
              +        user.logOut()
              +        app.login(Credentials.emailPassword(user.email, password))
              +        assertEquals(CUSTOM_USER_DATA_VALUE, user.customData.get(CUSTOM_USER_DATA_FIELD))
              +    }
              +
              +    @Test
              +    @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " +
              +            "https://github.com/realm/realm-java/issues/6942")
              +    fun customData_refreshAsyncThrowsOnNonLooper() {
              +        val password = "123456"
              +        val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), password)
              +
              +        assertFailsWith {
              +            user.refreshCustomData { }
              +        }
              +    }
              +
              +    private fun updateCustomData(user: User, data: Document) {
              +        // Name of collection and property used for storing custom user data. Must match server config.json
              +        val COLLECTION_NAME = "custom_user_data"
              +        val USER_ID_FIELD = "userid"
              +
              +        val client = user.getMongoClient(SERVICE_NAME)
              +        client.getDatabase(DATABASE_NAME).let {
              +            it.getCollection(COLLECTION_NAME).also { collection ->
              +                collection.insertOne(data.append(USER_ID_FIELD , user.id)).blockingGetResult()
              +            }
              +        }
              +    }
              +
               }
              diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt
              index a18330f45c..b63c23bac5 100644
              --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt
              +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt
              @@ -17,9 +17,7 @@ package io.realm.mongodb
               
               import androidx.test.ext.junit.runners.AndroidJUnit4
               import androidx.test.platform.app.InstrumentationRegistry
              -import io.realm.Realm
              -import io.realm.TestApp
              -import io.realm.TestHelper
              +import io.realm.*
               import io.realm.mongodb.mongo.MongoClient
               import io.realm.mongodb.mongo.MongoCollection
               import io.realm.mongodb.mongo.MongoNamespace
              @@ -40,8 +38,6 @@ import org.junit.Test
               import org.junit.runner.RunWith
               import kotlin.test.*
               
              -private const val SERVICE_NAME = "BackingDB"    // it comes from the test server's BackingDB/config.json
              -private const val DATABASE_NAME = "test_data"   // same as above
               private const val COLLECTION_NAME = "mongo_data" // name of collection used by tests
               
               @RunWith(AndroidJUnit4::class)
              diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp
              index 59075c47d9..37519bb143 100644
              --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp
              +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp
              @@ -19,8 +19,11 @@
               #include "java_class_global_def.hpp"
               #include "util.hpp"
               #include "jni_util/java_class.hpp"
              +#include "java_network_transport.hpp"
               
               #include 
              +#include 
              +#include 
               
               using namespace realm;
               using namespace realm::_impl;
              @@ -239,3 +242,28 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGe
                   CATCH_STD();
                   return nullptr;
               }
              +
              +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeCustomData(JNIEnv* env, jclass, jlong j_native_ptr) {
              +    try {
              +        auto user = *reinterpret_cast*>(j_native_ptr);
              +        const util::Optional custom_data(user->custom_data());
              +        if (custom_data) {
              +            return JniBsonProtocol::bson_to_jstring(env, *custom_data);
              +        } else {
              +            return JniBsonProtocol::bson_to_jstring(env, BsonDocument());
              +        }
              +    }
              +    CATCH_STD()
              +    return JniBsonProtocol::bson_to_jstring(env, BsonDocument());
              +}
              +
              +
              +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeRefreshCustomData
              +        (JNIEnv* env, jclass, jlong j_native_ptr, jobject j_callback) {
              +    try {
              +        auto user = *reinterpret_cast*>(j_native_ptr);
              +        std::function)> callback = JavaNetworkTransport::create_void_callback(env, j_callback);
              +        user->refresh_custom_data(callback);
              +    }
              +    CATCH_STD()
              +}
              diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp
              index d9f8698cc0..3729dbc756 100644
              --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp
              +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp
              @@ -22,6 +22,7 @@
               #include "jni_util/jni_utils.hpp"
               
               #include 
              +#include 
               
               using namespace realm;
               using namespace realm::app;
              diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java
              index 65af02cd51..2ee0bfaf18 100644
              --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java
              +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java
              @@ -15,8 +15,22 @@
                */
               package io.realm.internal.objectstore;
               
              +import org.bson.Document;
              +
              +import java.util.concurrent.atomic.AtomicReference;
              +
              +import io.realm.RealmAsyncTask;
               import io.realm.internal.NativeObject;
              +import io.realm.internal.Util;
              +import io.realm.internal.jni.JniBsonProtocol;
              +import io.realm.internal.jni.OsJNIResultCallback;
              +import io.realm.internal.jni.OsJNIVoidResultCallback;
              +import io.realm.internal.mongodb.Request;
              +import io.realm.internal.network.ResultHandler;
               import io.realm.internal.util.Pair;
              +import io.realm.mongodb.App;
              +import io.realm.mongodb.AppConfiguration;
              +import io.realm.mongodb.AppException;
               
               public class OsSyncUser implements NativeObject {
               
              @@ -110,6 +124,18 @@ public byte getState() {
                       return nativeGetState(nativePtr);
                   }
               
              +    public Document getCustomData() {
              +        String encodedData = nativeCustomData(nativePtr);
              +        // Stitch also used default codec registry for parsing access token
              +        return JniBsonProtocol.decode(encodedData, AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY.get(Document.class));
              +    }
              +
              +    public void refreshCustomData() {
              +        AtomicReference error = new AtomicReference<>(null);
              +        nativeRefreshCustomData(nativePtr, new OsJNIVoidResultCallback(error));
              +        ResultHandler.handleResult(null, error);
              +    }
              +
                   public void invalidate() {
                       nativeSetState(nativePtr, STATE_REMOVED);
                   }
              @@ -150,4 +176,6 @@ public int hashCode() {
                   private static native void nativeSetState(long nativePtr, byte state);
                   private static native String nativeGetProviderType(long nativePtr);
                   private static native String nativeGetDeviceId(long nativePtr);
              +    private static native String nativeCustomData(long nativeUserPtr);
              +    private static native void nativeRefreshCustomData(long nativeUserPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback);
               }
              diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java
              index 8795e3b4a3..218a7f1d99 100644
              --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java
              +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java
              @@ -15,6 +15,7 @@
                */
               package io.realm.mongodb;
               
              +import org.bson.Document;
               import org.bson.codecs.configuration.CodecRegistry;
               
               import java.util.ArrayList;
              @@ -28,6 +29,7 @@
               import io.realm.annotations.Beta;
               import io.realm.internal.Util;
               import io.realm.internal.common.TaskDispatcher;
              +import io.realm.internal.jni.JniBsonProtocol;
               import io.realm.internal.jni.OsJNIResultCallback;
               import io.realm.internal.jni.OsJNIVoidResultCallback;
               import io.realm.internal.mongodb.Request;
              @@ -289,6 +291,50 @@ public State getState() {
                       throw new IllegalStateException("Unknown state: " + nativeState);
                   }
               
              +    /**
              +     * Return the custom user data associated with the user in the Realm App.
              +     * 

              + * The data is only refreshed when the user's access token is refreshed or when explicitly + * calling {@link #refreshCustomData()}. + * + * @return The custom user data associated with the user. + */ + public Document getCustomData() { + return osUser.getCustomData(); + } + + /** + * Re-fetch custom user data from the Realm App. + * + * @return The updated custom user data associated with the user. + * @throws AppException if the request failed in some way. + */ + public Document refreshCustomData() { + osUser.refreshCustomData(); + return getCustomData(); + } + + /** + * Re-fetch custom user data from the Realm App asynchronously. + *

              + * This is the asynchronous variant of {@link #refreshCustomData()}. + * + * @param callback The callback that will receive the result or any errors from the request. + * @return The task representing the ongoing operation. + * + * @throws IllegalStateException if not called on a looper thread. + */ + public RealmAsyncTask refreshCustomData(App.Callback callback) { + Util.checkLooperThread("Asynchronous functions is only possible from looper threads."); + return new Request(App.NETWORK_POOL_EXECUTOR, callback) { + @Override + public Document run() throws AppException { + return refreshCustomData(); + } + }.start(); + } + + /** * Returns true if the user is currently logged in. * Returns whether or not this user is still logged into the MongoDB Realm App. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java index 1206f3f5b1..fe6573c0db 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/functions/Functions.java @@ -129,7 +129,7 @@ public ResultT callFunction(String name, List args, Decoder The type that the response will be decoded as using the default codec registry. - * @return Result of the Realm function. + * @return The task representing the ongoing operation. * * @throws IllegalStateException if not called on a looper thread. * @@ -159,7 +159,7 @@ public T run() throws AppException { * @param resultClass The type that the functions result should be converted to. * @param callback The callback that will receive the result or any errors from the request. * @param The type that the response will be decoded as using the default codec registry. - * @return Result of the Realm function. + * @return The task representing the ongoing operation. * * @throws IllegalStateException if not called on a looper thread. * @@ -181,7 +181,7 @@ public RealmAsyncTask callFunctionAsync(String name, List args, Class * @param resultDecoder The decoder used to decode the result. * @param callback The callback that will receive the result or any errors from the request. * @param The type that the response will be decoded as using the {@code resultDecoder} - * @return Result of the Realm function. + * @return The task representing the ongoing operation. * * @throws IllegalStateException if not called on a looper thread. * diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt index 30e686d0dc..56ac45e192 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt @@ -26,6 +26,9 @@ import io.realm.mongodb.AppConfiguration * * NOTE: This class must remain in the [io.realm] package in order to work. */ +const val SERVICE_NAME = "BackingDB" // it comes from the test server's BackingDB/config.json +const val DATABASE_NAME = "test_data" // same as above + class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: (AppConfiguration.Builder) -> Unit = {}) : App(createConfiguration()) { init { diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json new file mode 100644 index 0000000000..334a2aa626 --- /dev/null +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json @@ -0,0 +1,15 @@ +{ + "id": "5ede0729eab8deea4edf057c", + "database": "test_data", + "collection": "custom_user_data", + "roles": [ + { + "name": "default", + "apply_when": {}, + "insert": true, + "delete": true, + "additional_fields": {} + } + ], + "schema": {} +} diff --git a/tools/sync_test_server/app_config/stitch.json b/tools/sync_test_server/app_config/stitch.json index cb08ae9e81..7fe7df89db 100644 --- a/tools/sync_test_server/app_config/stitch.json +++ b/tools/sync_test_server/app_config/stitch.json @@ -6,7 +6,11 @@ "deployment_model": "GLOBAL", "security": {}, "custom_user_data_config": { - "enabled": false + "enabled": true, + "mongo_service_id": "5ede0729eab8deea4edf0579", + "database_name": "test_data", + "collection_name": "custom_user_data", + "user_id_field": "userid" }, "sync": { "development_mode_enabled": true From 0d4c40c899c45acc73a5534620707fa209ba6d7f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 11 Jun 2020 13:12:31 +0200 Subject: [PATCH 1583/2110] Avoid crash if looking up NetworkTransport response class from C++ thread (#6938) --- CHANGELOG.md | 24 +++++++++++++++++++ .../src/main/cpp/java_class_global_def.hpp | 14 +++++++++++ .../src/main/cpp/java_network_transport.hpp | 10 ++++---- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f29a97d8b1..f29237a510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 10.0.0-BETA.4 (YYYY-MM-DD) + +We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. + +The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. + +### Breaking Changes +* None + +### Enhancements +* None + +### Fixed +* [RealmApp] Opening a synced Realm for a cached user with expired access token would crash the app with `Assertion failed: cls with (class_name) = ["io/realm/internal/objectstore/OsJavaNetworkTransport$Response"]`. (Issue [#6937](https://github.com/realm/realm-java/issues/6937), since 10.0.0-BETA.1) + +### Compatibility +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 and above is required to open Realms created by this version. + +### Internal +* None. + + ## 10.0.0-BETA.3 (2020-06-09) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. diff --git a/realm/realm-library/src/main/cpp/java_class_global_def.hpp b/realm/realm-library/src/main/cpp/java_class_global_def.hpp index 167d0fdc4e..cdc979bb72 100644 --- a/realm/realm-library/src/main/cpp/java_class_global_def.hpp +++ b/realm/realm-library/src/main/cpp/java_class_global_def.hpp @@ -55,6 +55,9 @@ class JavaClassGlobalDef { , m_realm_notifier(env, "io/realm/internal/RealmNotifier", false) , m_bson_decimal128(env, "org/bson/types/Decimal128", false) , m_bson_object_id(env, "org/bson/types/ObjectId", false) +#if REALM_ENABLE_SYNC + , m_network_transport_response(env, "io/realm/internal/objectstore/OsJavaNetworkTransport$Response", false) +#endif { } @@ -71,6 +74,10 @@ class JavaClassGlobalDef { jni_util::JavaClass m_bson_decimal128; jni_util::JavaClass m_bson_object_id; +#if REALM_ENABLE_SYNC + jni_util::JavaClass m_network_transport_response; +#endif + inline static std::unique_ptr& instance() { static std::unique_ptr instance; @@ -181,6 +188,13 @@ class JavaClassGlobalDef { { return instance()->m_java_lang_object; } + +#if REALM_ENABLE_SYNC + inline static const jni_util::JavaClass& network_transport_response_class() + { + return instance()->m_network_transport_response; + } +#endif }; } // namespace realm diff --git a/realm/realm-library/src/main/cpp/java_network_transport.hpp b/realm/realm-library/src/main/cpp/java_network_transport.hpp index 7a0f015b42..25c44e6f59 100644 --- a/realm/realm-library/src/main/cpp/java_network_transport.hpp +++ b/realm/realm-library/src/main/cpp/java_network_transport.hpp @@ -84,11 +84,11 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { return; } else { // Read response - static JavaClass responseClass(env, "io/realm/internal/objectstore/OsJavaNetworkTransport$Response"); - static JavaMethod get_http_code_method(env, responseClass, "getHttpResponseCode", "()I"); - static JavaMethod get_custom_code_method(env, responseClass, "getCustomResponseCode", "()I"); - static JavaMethod get_headers_method(env, responseClass, "getJNIFriendlyHeaders", "()[Ljava/lang/String;"); - static JavaMethod get_body_method(env, responseClass, "getBody", "()Ljava/lang/String;"); + static const JavaClass& response_class(JavaClassGlobalDef::network_transport_response_class()); + static JavaMethod get_http_code_method(env, response_class, "getHttpResponseCode", "()I"); + static JavaMethod get_custom_code_method(env, response_class, "getCustomResponseCode", "()I"); + static JavaMethod get_headers_method(env, response_class, "getJNIFriendlyHeaders", "()[Ljava/lang/String;"); + static JavaMethod get_body_method(env, response_class, "getBody", "()Ljava/lang/String;"); jint http_code = env->CallIntMethod(response, get_http_code_method); jint custom_code = env->CallIntMethod(response, get_custom_code_method); From ac6ae9ad00145921ff454602582c1f4219dadc74 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 11 Jun 2020 16:01:10 +0200 Subject: [PATCH 1584/2110] Prepare BETA.4 release (#6944) --- CHANGELOG.md | 7 ++++--- dependencies.list | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- version.txt | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f29237a510..8e41063fbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.0-BETA.4 (YYYY-MM-DD) +## 10.0.0-BETA.4 (2020-06-11) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. @@ -8,7 +8,8 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new * None ### Enhancements -* None +* [RealmApp] Added support for Custom Data using `User.customData()` and `User.refreshCustomData()`. +* [RealmApp] Added support for managing push notifications using `App.getPush()`. ### Fixed * [RealmApp] Opening a synced Realm for a cached user with expired access token would crash the app with `Assertion failed: cls with (class_name) = ["io/realm/internal/objectstore/OsJavaNetworkTransport$Response"]`. (Issue [#6937](https://github.com/realm/realm-java/issues/6937), since 10.0.0-BETA.1) @@ -19,7 +20,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal -* None. +* Updated to Object Store commit: 017d58fbec8a18ab003976b4c346308df88349a6. ## 10.0.0-BETA.3 (2020-06-09) diff --git a/dependencies.list b/dependencies.list index 8a5d3b3380..e93bf0e03b 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=0572f751ced210656ed7d567dce9e56b5cca19c3070096ff38c255c9585ccf # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-06-08 +MONGODB_REALM_SERVER_VERSION=2020-06-10 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index c02707bc28..017d58fbec 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit c02707bc28e1886970c5da29ef481dc0cb6c3dd8 +Subproject commit 017d58fbec8a18ab003976b4c346308df88349a6 diff --git a/version.txt b/version.txt index fec6ffa46b..0e65af6e66 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.4-SNAPSHOT +10.0.0-BETA.4 From 4e8dcfde2fd1077a79f8f9f7ac948c3b9351a1c8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 11 Jun 2020 16:10:11 +0200 Subject: [PATCH 1585/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 0e65af6e66..812e04e0be 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.4 +10.0.0-BETA.5-SNAPSHOT From dc81114239a99b2a46a5b7619a1b0c3955478d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 18 Jun 2020 12:44:35 +0200 Subject: [PATCH 1586/2110] Fix SyncSessionTests and interrupt tests crashing test runs (#6954) --- .../io/realm/entities/DefaultSyncSchema.kt | 2 +- .../kotlin/io/realm/entities/SyncAllTypes.kt | 23 ++- ...ortedTypes.kt => SyncAllTypesWithFloat.kt} | 44 ++++-- .../transport/OkHttpNetworkTransportTests.kt | 24 ++- .../kotlin/io/realm/SyncSessionTests.kt | 148 ++++++++---------- 5 files changed, 122 insertions(+), 119 deletions(-) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/{SyncSupportedTypes.kt => SyncAllTypesWithFloat.kt} (73%) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt index b448797167..3861aa01ce 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt @@ -22,6 +22,6 @@ const val defaultPartitionValue = "default" /** * The set of classes initially supported by MongoDB Realm. */ -@RealmModule(classes = [SyncDog::class, SyncPerson::class, SyncSupportedTypes::class]) +@RealmModule(classes = [SyncDog::class, SyncPerson::class, SyncAllTypes::class]) class DefaultSyncSchema { } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypes.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypes.kt index 58713f55e1..352b84b350 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypes.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypes.kt @@ -51,9 +51,6 @@ open class SyncAllTypes : RealmObject() { const val FIELD_DOUBLE_LIST = "columnDoubleList" const val FIELD_FLOAT_LIST = "columnFloatList" const val FIELD_DATE_LIST = "columnDateList" - val INVALID_TYPES_FIELDS_FOR_DISTINCT = arrayOf(FIELD_REALMOBJECT, FIELD_REALMLIST, FIELD_DOUBLE, FIELD_FLOAT, - FIELD_STRING_LIST, FIELD_BINARY_LIST, FIELD_BOOLEAN_LIST, FIELD_LONG_LIST, - FIELD_DOUBLE_LIST, FIELD_FLOAT_LIST, FIELD_DATE_LIST) } @PrimaryKey @@ -63,7 +60,10 @@ open class SyncAllTypes : RealmObject() { @Required var columnString = "" var columnLong: Long = 0 - var columnFloat = 0f + + // FIXME Float ruins initial upload of scheme, works if added to schema later +// var columnFloat = 0f + var columnDouble = 0.0 var isColumnBoolean = false @@ -80,16 +80,29 @@ open class SyncAllTypes : RealmObject() { var columnObjectId = ObjectId(TestHelper.randomObjectIdHexString()) val columnRealmInteger = MutableRealmInteger.ofNull() var columnRealmObject: SyncDog? = null + var columnRealmList: RealmList? = null + @Required var columnStringList: RealmList? = null + @Required var columnBinaryList: RealmList? = null + @Required var columnBooleanList: RealmList? = null + @Required var columnLongList: RealmList? = null + @Required var columnDoubleList: RealmList? = null - var columnFloatList: RealmList? = null + + // FIXME Float ruins initial upload of scheme, works if added to schema later +// @Required +// var columnFloatList: RealmList? = null + + @Required var columnDateList: RealmList? = null + @Required var columnDecimal128List: RealmList? = null + @Required var columnObjectIdList: RealmList? = null fun setColumnMutableRealmInteger(value: Int) { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSupportedTypes.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypesWithFloat.kt similarity index 73% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSupportedTypes.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypesWithFloat.kt index 73bbe548e0..120ef9d42e 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSupportedTypes.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncAllTypesWithFloat.kt @@ -28,7 +28,9 @@ import org.bson.types.ObjectId import java.math.BigDecimal import java.util.* -open class SyncSupportedTypes : RealmObject() { +// TODO This class is only for tracking failure when uploading SyncAllTypes including float field. +// Once supported this class can be deleted and we can include the float field in SyncAllTypes +open class SyncAllTypesWithFloat : RealmObject() { companion object { const val CLASS_NAME = "AllTypes" @@ -51,9 +53,6 @@ open class SyncSupportedTypes : RealmObject() { const val FIELD_DOUBLE_LIST = "columnDoubleList" const val FIELD_FLOAT_LIST = "columnFloatList" const val FIELD_DATE_LIST = "columnDateList" - val INVALID_TYPES_FIELDS_FOR_DISTINCT = arrayOf(FIELD_REALMOBJECT, FIELD_REALMLIST, FIELD_DOUBLE, FIELD_FLOAT, - FIELD_STRING_LIST, FIELD_BINARY_LIST, FIELD_BOOLEAN_LIST, FIELD_LONG_LIST, - FIELD_DOUBLE_LIST, FIELD_FLOAT_LIST, FIELD_DATE_LIST) } @PrimaryKey @@ -63,7 +62,10 @@ open class SyncSupportedTypes : RealmObject() { @Required var columnString = "" var columnLong: Long = 0 + var columnFloat = 0f + + var columnDouble = 0.0 var isColumnBoolean = false @Required @@ -79,21 +81,29 @@ open class SyncSupportedTypes : RealmObject() { var columnObjectId = ObjectId(TestHelper.randomObjectIdHexString()) val columnRealmInteger = MutableRealmInteger.ofNull() var columnRealmObject: SyncDog? = null + var columnRealmList: RealmList? = null + @Required + var columnStringList: RealmList? = null + @Required + var columnBinaryList: RealmList? = null + @Required + var columnBooleanList: RealmList? = null + @Required + var columnLongList: RealmList? = null + @Required + var columnDoubleList: RealmList? = null + + @Required + var columnFloatList: RealmList? = null - // FIXME These are the fields needed to be removed from SyncAllTypes for sync to work when - // updating the partitionValue -// var columnDouble = 0.0 -// var columnStringList: RealmList? = null -// var columnBinaryList: RealmList? = null -// var columnBooleanList: RealmList? = null -// var columnLongList: RealmList? = null -// var columnDoubleList: RealmList? = null -// var columnFloatList: RealmList = RealmList() -// var columnDateList: RealmList? = null -// var columnDecimal128List: RealmList? = null - -// var columnObjectIdList: RealmList? = null + @Required + var columnDateList: RealmList? = null + @Required + var columnDecimal128List: RealmList? = null + + @Required + var columnObjectIdList: RealmList? = null fun setColumnMutableRealmInteger(value: Int) { columnRealmInteger.set(value.toLong()) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt index 482b36cc31..5b3ad8ab22 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt @@ -127,21 +127,15 @@ class OkHttpNetworkTransportTests { Pair("Accept", "application/json") ) - val t = Thread(Runnable { - val response: OsJavaNetworkTransport.Response = transport.sendRequest(method.nativeKey, - url, - 5000, - headers, - body) - assertEquals(0, response.httpResponseCode) - assertEquals(OsJavaNetworkTransport.ERROR_IO, response.customResponseCode) - assertTrue(response.body.contains("interrupted")) - }) - t.start() - // There is a very small chance that the network request already completed when getting - // to here, which would cause the test to fail. Ignore this possibility for now. - t.interrupt() - t.join() + Thread.currentThread().interrupt() + val response: OsJavaNetworkTransport.Response = transport.sendRequest(method.nativeKey, + url, + 5000, + headers, + body) + assertEquals(0, response.httpResponseCode) + assertEquals(OsJavaNetworkTransport.ERROR_IO, response.customResponseCode) + assertTrue(response.body.contains("interrupted")) } } diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index bcac480d64..06e0f5e317 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -15,6 +15,7 @@ import io.realm.mongodb.* import io.realm.mongodb.sync.* import io.realm.rule.BlockingLooperThread import io.realm.util.ResourceContainer +import io.realm.util.assertFailsWithErrorCode import io.realm.util.assertFailsWithMessage import org.bson.BsonInt32 import org.bson.BsonInt64 @@ -231,7 +232,7 @@ class SyncSessionTests { fun uploadDownloadAllChanges() { Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(SyncSupportedTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -246,15 +247,35 @@ class SyncSessionTests { Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() realm.refresh() - assertEquals(1, realm.where(SyncSupportedTypes::class.java).count()) + assertEquals(1, realm.where(SyncAllTypes::class.java).count()) + } + } + + // TODO This test is only for tracking failure when uploading SyncAllTypes including float field. + // Once this test fails (meaning that the full schema can be uploaded) the test can be removed + // and we can include the float field in SyncAllTypes + @Test + fun uploadDownloadAllChangesWithFloatFails() { + val config = configFactory + .createSyncConfigurationBuilder(user, syncConfiguration.partitionValue) + .testSchema(SyncAllTypesWithFloat::class.java, SyncDog::class.java, SyncPerson::class.java) + .build() + + Realm.getInstance(config).use { realm -> + realm.executeTransaction { + realm.createObject(SyncAllTypesWithFloat::class.java, ObjectId()) + } + assertFailsWithErrorCode(ErrorCode.UNKNOWN) { + realm.syncSession.uploadAllLocalChanges() + } } } @Test - fun differentPartitionValue_supportedTypes() { + fun differentPartitionValue_allTypes() { Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(SyncSupportedTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -268,7 +289,7 @@ class SyncSessionTests { Realm.getInstance(config2).use { realm -> realm.executeTransaction { - realm.createObject(SyncSupportedTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -278,7 +299,7 @@ class SyncSessionTests { fun differentPartitionValue_noCrosstalk() { Realm.getInstance(syncConfiguration).use { realm -> realm.executeTransaction { - realm.createObject(SyncSupportedTypes::class.java, ObjectId()) + realm.createObject(SyncAllTypes::class.java, ObjectId()) } realm.syncSession.uploadAllLocalChanges() } @@ -293,95 +314,60 @@ class SyncSessionTests { Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() // We should not have any data here - assertEquals(0, realm.where(SyncSupportedTypes::class.java).count()) + assertEquals(0, realm.where(SyncAllTypes::class.java).count()) } } @Test - // FIXME Investigate further - @Ignore("Bad changeset for session with different partitionValue") - fun differentPartitionValue_allTypes() { - val config = configFactory - .createSyncConfigurationBuilder(user) - .modules(SyncAllTypesSchema()) - .build() - Realm.getInstance(config).use { realm -> - realm.executeTransaction { - realm.createObject(SyncAllTypes::class.java, ObjectId()) + fun interruptWaits() { + Realm.getInstance(syncConfiguration).use { userRealm -> + userRealm.executeTransaction { + userRealm.createObject(SyncAllTypes::class.java, ObjectId()) + } + val userSession = userRealm.syncSession + try { + // 1. Start download (which will be interrupted) + Thread.currentThread().interrupt() + userSession.downloadAllServerChanges() + fail() + } catch (ignored: InterruptedException) { + assertFalse(Thread.currentThread().isInterrupted) + } + try { + // 2. Upload all changes + userSession.uploadAllLocalChanges() + } catch (e: InterruptedException) { + fail("Upload interrupted") } - realm.syncSession.uploadAllLocalChanges() } - // New user and different partition value + // New user but same Realm as configuration has the same partition value val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2 = configFactory - .createSyncConfigurationBuilder(user2, BsonObjectId(ObjectId())) - .modules(SyncAllTypesSchema()) + .createSyncConfigurationBuilder(user2, syncConfiguration.partitionValue) + .modules(DefaultSyncSchema()) .build() - Realm.getInstance(config2).use { realm -> - realm.executeTransaction { - realm.createObject(SyncAllTypes::class.java, ObjectId()) + Realm.getInstance(config2).use { adminRealm -> + val adminSession: SyncSession = adminRealm.syncSession + try { + // 3. Start upload (which will be interrupted) + Thread.currentThread().interrupt() + adminSession.uploadAllLocalChanges() + fail() + } catch (ignored: InterruptedException) { + assertFalse(Thread.currentThread().isInterrupted) // clear interrupted flag } - realm.syncSession.uploadAllLocalChanges() - } - } - - @Test - fun interruptWaits() { - // FIXME Convert to BackgroundLooperThread? Is it doable with all the interruptions - val t = Thread(Runnable { - Realm.getInstance(syncConfiguration).use { userRealm -> - userRealm.executeTransaction { - userRealm.createObject(SyncSupportedTypes::class.java, ObjectId()) - } - val userSession = userRealm.syncSession - try { - // 1. Start download (which will be interrupted) - Thread.currentThread().interrupt() - userSession.downloadAllServerChanges() - fail() - } catch (ignored: InterruptedException) { - assertFalse(Thread.currentThread().isInterrupted) - } - try { - // 2. Upload all changes - userSession.uploadAllLocalChanges() - } catch (e: InterruptedException) { - fail("Upload interrupted") - } + try { + // 4. Download all changes + adminSession.downloadAllServerChanges() + } catch (e: InterruptedException) { + fail("Download interrupted") } + adminRealm.refresh() - // New user but same Realm as configuration has the same partition value - val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) - val config2 = configFactory - .createSyncConfigurationBuilder(user2, syncConfiguration.partitionValue) - .modules(DefaultSyncSchema()) - .build() - - Realm.getInstance(config2).use { adminRealm -> - val adminSession: SyncSession = adminRealm.syncSession - try { - // 3. Start upload (which will be interrupted) - Thread.currentThread().interrupt() - adminSession.uploadAllLocalChanges() - fail() - } catch (ignored: InterruptedException) { - assertFalse(Thread.currentThread().isInterrupted) // clear interrupted flag - } - try { - // 4. Download all changes - adminSession.downloadAllServerChanges() - } catch (e: InterruptedException) { - fail("Download interrupted") - } - adminRealm.refresh() - - assertEquals(1, adminRealm.where(SyncSupportedTypes::class.java).count()) - } - }) - t.start() - t.join() + assertEquals(1, adminRealm.where(SyncAllTypes::class.java).count()) + } } // check that logging out a SyncUser used by different Realm will From d0af8deedc75d5fc2c53e2575da12d6421736857 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 18 Jun 2020 17:01:54 +0200 Subject: [PATCH 1587/2110] Use emulator for PR's (#6822) --- Dockerfile | 38 +++++--- Jenkinsfile | 245 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 169 insertions(+), 114 deletions(-) diff --git a/Dockerfile b/Dockerfile index c14bc38961..b36b272afc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,12 @@ -FROM ubuntu:16.04 +FROM ubuntu:18.04 # Locales RUN apt-get clean && apt-get -y update && apt-get install -y locales && locale-gen en_US.UTF-8 ENV LANG "en_US.UTF-8" ENV LANGUAGE "en_US.UTF-8" ENV LC_ALL "en_US.UTF-8" +ENV TZ=Europe/Copenhagen +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # Set the environment variables ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64 @@ -12,31 +14,38 @@ ENV ANDROID_HOME /opt/android-sdk-linux # Need by cmake ENV ANDROID_NDK_HOME /opt/android-ndk ENV ANDROID_NDK /opt/android-ndk -ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools +ENV PATH ${PATH}:${ANDROID_HOME}/emulator:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} ENV NDK_CCACHE /usr/bin/ccache ENV CCACHE_CPP2 yes -# The 32 bit binaries because aapt requires it -# `file` is need by the script that creates NDK toolchains # Keep the packages in alphabetical order to make it easy to avoid duplication -RUN DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 \ +# tzdata needs to be installed first. See https://askubuntu.com/questions/909277/avoiding-user-interaction-with-tzdata-when-installing-certbot-in-a-docker-contai +# `file` is need by the Android Emulator +RUN DEBIAN_FRONTEND=noninteractive \ && apt-get update -qq \ + && apt-get install -y tzdata \ && apt-get install -y bsdmainutils \ + bridge-utils \ build-essential \ ccache \ curl \ file \ git \ jq \ - libc6:i386 \ - libgcc1:i386 \ - libncurses5:i386 \ - libstdc++6:i386 \ - libz1:i386 \ + libc6 \ + libgcc1 \ + libglu1 \ + libncurses5 \ + libstdc++6 \ + libz1 \ + libvirt-clients \ + libvirt-daemon-system \ openjdk-8-jdk-headless \ + qemu-kvm \ s3cmd \ unzip \ + virt-manager \ wget \ zip \ && apt-get clean @@ -56,16 +65,17 @@ RUN sdkmanager --update RUN yes | sdkmanager --licenses # SDKs -# Please keep these in descending order! # The `yes` is for accepting all non-standard tool licenses. # Please keep all sections in descending order! RUN yes | sdkmanager \ - 'platform-tools' \ 'build-tools;29.0.2' \ + 'cmake;3.6.4111459' \ + 'emulator' \ 'extras;android;m2repository' \ 'platforms;android-29' \ - 'cmake;3.6.4111459' \ - 'ndk;21.0.6113669' + 'platform-tools' \ + 'ndk;21.0.6113669' \ + 'system-images;android-29;default;x86' # Make the SDK universally writable RUN chmod -R a+rwX ${ANDROID_HOME} diff --git a/Jenkinsfile b/Jenkinsfile index f1e2ba8de1..d2a1eecd86 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,14 +4,20 @@ import groovy.json.JsonOutput -def buildSuccess = false -def mongoDbRealmContainer = null -def mongoDbRealmCommandServerContainer = null -def dockerNetworkId = UUID.randomUUID().toString() -def releaseBranches = ['master', 'next-major', 'v10'] // Branches from which we release SNAPSHOT's -def currentBranch = env.CHANGE_BRANCH +buildSuccess = false +mongoDbRealmContainer = null +mongoDbRealmCommandServerContainer = null +emulatorContainer = null +dockerNetworkId = UUID.randomUUID().toString() +// Branches from which we release SNAPSHOT's. Only release branches need to run on actual hardware. +releaseBranches = ['master', 'next-major', 'v10'] +// Branches that are "important", so if they do not compile they will generate a Slack notification +slackNotificationBranches = [ 'master', 'releases', 'next-major', 'v10' ] +currentBranch = env.CHANGE_BRANCH +// 'android' nodes have android devices attached and 'brix' are physical machines in Copenhagen. +nodeSelector = (releaseBranches.contains(currentBranch)) ? 'android' : 'docker-cph-03' // Switch to `brix` when all CPH nodes work: https://jira.mongodb.org/browse/RCI-14 try { - node('android') { + node(nodeSelector) { timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { @@ -29,16 +35,21 @@ try { } // Toggles for PR vs. Master builds. - // For PR's, we just build for arm-v7a and run unit tests for the ObjectServer variant - // A full build is done on `master`. - // TODO Once Android emulators are available on all nodes, we can switch to x86 builds - // on PR's for even more throughput. + // - For PR's, we favor speed > absolute correctness. So we just build for x86, use an + // emulator and run unit tests for the ObjectServer variant. + // - For branches from which we make releases, we build all architectures and run tests + // on an actual device. + def useEmulator = false + def emulatorImage = "" def abiFilter = "" def instrumentationTestTarget = "connectedAndroidTest" + def deviceSerial = "" if (!releaseBranches.contains(currentBranch)) { - abiFilter = "-PbuildTargetABIs=armeabi-v7a" + useEmulator = true + emulatorImage = "system-images;android-29;default;x86" + abiFilter = "-PbuildTargetABIs=x86" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" - // Run in debug more for better error reporting + deviceSerial = "emulator-5554" } try { @@ -47,7 +58,7 @@ try { stage('Prepare Docker Images') { // TODO Should be renamed to 'master' when merged there. // TODO Figure out why caching the image doesn't work. - buildEnv = buildDockerEnv("realm-java-ci:v10", push: currentBranch == 'v10-do-not-cache') + buildEnv = buildDockerEnv("realm-java-ci:v10", push: currentBranch == 'v10') def props = readProperties file: 'dependencies.list' echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") @@ -66,95 +77,42 @@ try { sh "docker exec -i ${mongoDbRealmContainer.id} sh /tmp/setup_mongodb_realm.sh" } + // There is a chance that real devices are attached to the host, so if the emulator is + // running we need to make sure that ADB and tests targets the correct device. + String restrictDevice = "" + if (deviceSerial != null) { + restrictDevice = "-e ANDROID_SERIAL=${deviceSerial} " + } buildEnv.inside("-e HOME=/tmp " + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + "--privileged " + + "-v /dev/kvm:/dev/kvm " + "-v /dev/bus/usb:/dev/bus/usb " + "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + "-v ${env.HOME}/.android:/tmp/.android " + "-v ${env.HOME}/ccache:/tmp/.ccache " + + restrictDevice + "-e REALM_CORE_DOWNLOAD_DIR=/tmp/.gradle " + "--network container:${mongoDbRealmContainer.id} ") { // Lock required around all usages of Gradle as it isn't // able to share its cache between builds. lock("${env.NODE_NAME}-android") { - - stage('JVM tests') { - try { - withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" - } - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - storeJunitResults 'realm/realm-library/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) - } - } - - stage('Realm Transformer tests') { + if (useEmulator) { + // TODO: We should wait until the emulator is online. For now assume it starts fast enough + // before the tests will run, since the library needs to build first. + sh """yes '\n' | avdmanager create avd -n CIEmulator -k '${emulatorImage}' --force""" + sh "adb start-server" // https://stackoverflow.com/questions/56198290/problems-with-adb-exe + // Need to go to ANDROID_HOME due to https://askubuntu.com/questions/1005944/emulator-avd-does-not-launch-the-virtual-device + sh "cd \$ANDROID_HOME/tools && emulator -avd CIEmulator -no-boot-anim -no-window -wipe-data -noaudio -partition-size 4098 &" try { - gradle('realm-transformer', 'check') + runBuild(abiFilter, instrumentationTestTarget) } finally { - storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' - } - } - - stage('Static code analysis') { - try { - gradle('realm', "findbugs ${abiFilter}") // FIXME Renable pmd and checkstyle - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) -// publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) -// step([$class: 'CheckStylePublisher', -// canComputeNew: false, -// defaultEncoding: '', -// healthy: '', -// pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', -// unHealthy: '' -// ]) - } - } - - stage('Run instrumented tests') { - String backgroundPid - try { - backgroundPid = startLogCatCollector() - forwardAdbPorts() - gradle('realm', "${instrumentationTestTarget} ${abiFilter}") - } finally { - stopLogCatCollector(backgroundPid) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' - storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' - } - } - - // Gradle plugin tests require that artifacts are available, so this - // step needs to be after the instrumentation tests - stage('Gradle plugin tests') { - try { - gradle('gradle-plugin', 'check --debug') - } finally { - storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' - } - } - - // TODO: add support for running monkey on the example apps - - if (['master'].contains(currentBranch)) { - stage('Collect metrics') { - collectAarMetrics() - } - } - - if (releaseBranches.contains(currentBranch)) { - stage('Publish to OJO') { - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" - } + sh "adb emu kill" } + } else { + runBuild(abiFilter, instrumentationTestTarget) } } } @@ -166,6 +124,9 @@ try { mongoDbRealmCommandServerContainer.stop() sh "docker network rm ${dockerNetworkId}" } + if (emulatorContainer != null) { + emulatorContainer.stop() + } } } } @@ -177,17 +138,17 @@ try { buildSuccess = false throw e } finally { - if (['master', 'releases', 'next-major'].contains(currentBranch) && !buildSuccess) { + if (slackNotificationBranches.contains(currentBranch) && !buildSuccess) { node { withCredentials([[$class: 'StringBinding', credentialsId: 'slack-java-url', variable: 'SLACK_URL']]) { def payload = JsonOutput.toJson([ username: 'Mr. Jenkins', icon_emoji: ':jenkins:', attachments: [[ - 'title': "The ${currentBranch} branch is broken!", - 'text': "<${env.BUILD_URL}|Click here> to check the build.", - 'color': "danger" - ]] + 'title': "The ${currentBranch} branch is broken!", + 'text': "<${env.BUILD_URL}|Click here> to check the build.", + 'color': "danger" + ]] ]) sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" } @@ -195,18 +156,102 @@ try { } } +// Runs all build steps +def runBuild(abiFilter, instrumentationTestTarget) { + + stage('Build') { + sh "chmod +x gradlew && ./gradlew assemble javadoc ${abiFilter} --stacktrace" + } + + stage('JVM tests') { + try { + sh "chmod +x gradlew && ./gradlew check ${abiFilter} --stacktrace" + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + storeJunitResults 'realm/realm-library/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) + } + } + + stage('Realm Transformer tests') { + try { + gradle('realm-transformer', 'check') + } finally { + storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + } + } + + stage('Static code analysis') { + try { + gradle('realm', "findbugs ${abiFilter}") // FIXME Renable pmd and checkstyle + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) +// publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) +// step([$class: 'CheckStylePublisher', +// canComputeNew: false, +// defaultEncoding: '', +// healthy: '', +// pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', +// unHealthy: '' +// ]) + } + } + + stage('Run instrumented tests') { + String backgroundPid + try { + backgroundPid = startLogCatCollector() + forwardAdbPorts() + gradle('realm', "${instrumentationTestTarget} ${abiFilter}") + } finally { + stopLogCatCollector(backgroundPid) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } + } + + // Gradle plugin tests require that artifacts are available, so this + // step needs to be after the instrumentation tests + stage('Gradle plugin tests') { + try { + gradle('gradle-plugin', 'check --debug') + } finally { + storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + } + } + + // TODO: add support for running monkey on the example apps + + if (['master'].contains(currentBranch)) { + stage('Collect metrics') { + collectAarMetrics() + } + } + + if (releaseBranches.contains(currentBranch)) { + stage('Publish to OJO') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + } + } + } +} + def forwardAdbPorts() { - sh ''' adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && + sh """ adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && adb reverse tcp:8888 tcp:8888 && adb reverse tcp:9090 tcp:9090 - ''' + """ } String startLogCatCollector() { // Cancel build quickly if no device is available. The lock acquired already should // ensure we have access to a device. If not, it is most likely a more severe problem. timeout(time: 1, unit: 'MINUTES') { + // Need ADB as root to clear all buffers: https://stackoverflow.com/a/47686978/1389357 sh 'adb devices' - sh """adb logcat -c + sh """adb root + adb logcat -b all -c adb logcat -v time > 'logcat.txt' & echo \$! > pid """ @@ -220,9 +265,9 @@ def stopLogCatCollector(String backgroundPid) { if (backgroundPid != null) { sh "kill ${backgroundPid}" zip([ - 'zipFile': 'logcat.zip', - 'archive': true, - 'glob' : 'logcat.txt' + 'zipFile': 'logcat.zip', + 'archive': true, + 'glob' : 'logcat.txt' ]) sh 'rm logcat.txt' } @@ -268,9 +313,9 @@ def getTagsString(Map tags) { def storeJunitResults(String path) { step([ - $class: 'JUnitResultArchiver', - allowEmptyResults: true, - testResults: path + $class: 'JUnitResultArchiver', + allowEmptyResults: true, + testResults: path ]) } From 5c5371c3379830327382ae00efae31e1ff7fcf8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 18 Jun 2020 17:32:35 +0200 Subject: [PATCH 1588/2110] Distinct for non-indexed and linked properties (#6948) --- .../io/realm/annotations/LinkingObjects.java | 2 +- .../java/io/realm/RealmObjectTests.java | 2 +- .../java/io/realm/RealmQueryTests.java | 360 ++++++++++++------ .../java/io/realm/entities/AllJavaTypes.java | 25 +- .../realm/internal/QueryDescriptorTests.java | 8 +- .../realm/internal/core/QueryDescriptor.java | 12 +- .../java/io/realm/entities/AllTypes.java | 25 +- 7 files changed, 290 insertions(+), 144 deletions(-) diff --git a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java index 93be19f438..680a39ed83 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java +++ b/realm-annotations/src/main/java/io/realm/annotations/LinkingObjects.java @@ -68,7 +68,7 @@ *

            • The annotated field must be `final`.
            • *
            • The annotation argument (the name of the backlinked field) is required.
            • *
            • The annotation argument must be a simple field name. It cannot contain periods ('.').
            • - *
            • The annotated field must be of type `RealmResults>T<` where T is concrete class that extends `RealmModel`.
            • + *
            • The annotated field must be of type `RealmResults<T>` where T is concrete class that extends `RealmModel`.
            • * * * Note that when the source of the reverse reference (`dog` in the case above) is a `List`, there is a reverse diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 16b1180e1a..5dad672644 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -119,7 +119,7 @@ public void row_isValid() { realm.commitTransaction(); assertNotNull("RealmObject.realmGetRow returns zero ", row); - assertEquals(21, row.getColumnCount()); + assertEquals(22, row.getColumnCount()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index f6c050a48e..f5ea0ca3d1 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -20,14 +20,21 @@ import org.bson.types.Decimal128; import org.bson.types.ObjectId; +import org.jetbrains.annotations.NotNull; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.internal.util.collections.Sets; import java.lang.reflect.Field; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; +import java.util.HashSet; +import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -3057,7 +3064,6 @@ public void sort_listOnSubObjectField() { assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); } - // RealmQuery.distinct(): requires indexing, and type = boolean, integer, date, string. private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOfObjects, boolean withNull) { realm.beginTransaction(); for (int i = 0; i < numberOfObjects * numberOfBlocks; i++) { @@ -3077,6 +3083,29 @@ private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOf realm.commitTransaction(); } + private void populateForDistinctAllTypes(Realm realm, long numberOfBlocks, long numberOfObjects) { + realm.beginTransaction(); + for (int i = 0; i < numberOfBlocks; i++) { + Dog dog = realm.createObject(Dog.class); + for (int j = 0; j < numberOfObjects; j++) { + AllTypes obj = realm.createObject(AllTypes.class); + obj.setColumnBinary(new byte[j]); + obj.setColumnString("Test " + j); + obj.setColumnLong(j); + obj.setColumnFloat(j/1000f); + obj.setColumnDouble(j/1000d); + obj.setColumnBoolean(j % 2 == 0); + obj.setColumnDate(new Date(1000L * j)); + obj.setColumnDecimal128(new Decimal128(j)); + obj.setColumnObjectId(new ObjectId(j, j)); + obj.setColumnMutableRealmInteger(j); + obj.setColumnRealmLink(obj); + obj.setColumnRealmObject(dog); + } + } + realm.commitTransaction(); + } + private void populateForDistinctInvalidTypesLinked(Realm realm) { realm.beginTransaction(); AllJavaTypes notEmpty = new AllJavaTypes(); @@ -3120,95 +3149,112 @@ public void distinct_failIfAppliedMultipleTimes() { .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE); } - @Test - public void distinct_notIndexedFields() { + // Helper method to verify distinct behavior an all fields of AllTypes, potentially following + // possible multiple indirection links as given by 'prefix' + private void distinctAllFields(Realm realm, String prefix) { final long numberOfBlocks = 3; final long numberOfObjects = 3; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - RealmResults distinctBool = realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_BOOL) - .findAll(); - assertEquals(2, distinctBool.size()); - for (String field : new String[]{AnnotationIndexTypes.FIELD_NOT_INDEX_LONG, - AnnotationIndexTypes.FIELD_NOT_INDEX_DATE, AnnotationIndexTypes.FIELD_NOT_INDEX_STRING}) { - RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field).findAll(); - assertEquals(field, numberOfBlocks, distinct.size()); - } - } + populateForDistinctAllTypes(realm, numberOfBlocks, numberOfObjects); - @Test - public void distinct_doesNotExist() { - final long numberOfBlocks = 3; - final long numberOfObjects = 3; // Must be greater than 1 - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); + // Dynamic realm for verifying distinct query result against naive manual implementation of + // distinct + DynamicRealm dynamicRealm = DynamicRealm.createInstance(realm.sharedRealm); + RealmResults all = dynamicRealm.where(AllTypes.CLASS_NAME) + .findAll(); - try { - realm.where(AnnotationIndexTypes.class).distinct("doesNotExist").findAll(); - fail(); - } catch (IllegalArgumentException ignored) { - } - } + // Bookkeeping to ensure that we are actually testing all types + HashSet types = new HashSet(Arrays.asList(RealmFieldType.values())); - @Test - public void distinct_invalidTypes() { - populateTestRealm(); + // Iterate all fields of AllTypes table and verify that distinct either: + // - Returns correct number of entries, or + // - Raises an error that distinct cannot be performed on the specific field types (lists) + RealmObjectSchema schema = realm.getSchema().getSchemaForClass(AllTypes.CLASS_NAME); + Set fieldNames = schema.getFieldNames(); + for (String fieldName : fieldNames) { + String field = prefix + fieldName; + RealmFieldType type = schema.getFieldType(fieldName); + if (supportDistinct(type)) { + // Actual query + RealmResults distinct = realm.where(AllTypes.class) + .distinct(field) + .findAll(); - for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { - try { - realm.where(AllTypes.class).distinct(field).findAll(); - fail(field); - } catch (IllegalArgumentException ignored) { + // Assert query result + // Test against manual distinct implementation + Set> values = distinct(all, field); + assertEquals(field, values.size(), distinct.size()); + // Test against expected numbers from setup + switch (type) { + case BOOLEAN: + assertEquals(field, 2, distinct.size()); + break; + case OBJECT: + if (fieldName.equals("columnRealmObject")) { + assertEquals(field, numberOfBlocks, distinct.size()); + } else if (fieldName.equals("columnRealmLink")){ + assertEquals(field, numberOfBlocks * numberOfObjects, distinct.size()); + } else { + fail("Unknown object " + fieldName); + } + break; + default: + assertEquals(field, numberOfObjects, distinct.size()); + break; + } + } else { + // Test that unsupported types throw exception as expected + try { + realm.where(AllTypes.class) + .distinct(field) + .findAll(); + fail(); + } catch (IllegalArgumentException ignore) { + } } + types.remove(type); } + + // Verify that we have tested all field types except LinkingObjects which is not part of + // the schema lookup + assertEquals(types.toString(), Sets.newSet(RealmFieldType.LINKING_OBJECTS), types); + // So verify Linking explicitly + RealmResults distinct = realm.where(AllTypes.class) + .distinct(prefix + AllTypes.FIELD_REALMBACKLINK) + .findAll(); + assertEquals(numberOfBlocks * numberOfObjects, distinct.size()); } @Test - public void distinct_indexedLinkedFields() { - final long numberOfBlocks = 3; - final long numberOfObjects = 3; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - - for (String field : AnnotationIndexTypes.INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field) - .findAll(); - fail("Unsupported Index" + field + " linked field"); - } catch (IllegalArgumentException ignored) { - } - } + public void distinct_allFields() { + distinctAllFields(realm, ""); } @Test - public void distinct_notIndexedLinkedFields() { - final long numberOfBlocks = 3; - final long numberOfObjects = 3; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); + public void distinct_linkedAllFields() { + distinctAllFields(realm, AllTypes.FIELD_REALMLINK + "."); + } - for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class) - .distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field) - .findAll(); - fail("Unsupported notIndex" + field + " linked field"); - } catch (IllegalArgumentException ignored) { - } - } + @Test + public void distinct_nestedLinkedAllFields() { + distinctAllFields(realm, AllTypes.FIELD_REALMLINK + "." + AllTypes.FIELD_REALMLINK + "."); } @Test - public void distinct_invalidTypesLinkedFields() { - populateForDistinctInvalidTypesLinked(realm); + public void distinct_doesNotExist() { + final long numberOfBlocks = 3; + final long numberOfObjects = 3; // Must be greater than 1 + populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { - realm.where(AllJavaTypes.class) - .distinct(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY) - .findAll(); + realm.where(AnnotationIndexTypes.class).distinct("doesNotExist").findAll(); + fail(); } catch (IllegalArgumentException ignored) { } } + // Smoke test of async distinct. Underlying mechanism is the same as for sync test + // (distinct_allFields), so just verifying async mechanism. @Test @RunTestInLooperThread public void distinct_async() throws Throwable { @@ -3344,47 +3390,48 @@ public void distinct_async_doesNotExist() { looperThread.testComplete(); } + // Smoke test of async distinct invalid types. Underlying mechanism is the same as for sync test + // (distinct_allFields), so just verifying async mechanism. @Test @RunTestInLooperThread public void distinct_async_invalidTypes() { populateTestRealm(realm, TEST_DATA_SIZE); - for (String field : new String[]{AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST, AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT}) { - try { - realm.where(AllTypes.class).distinct(field).findAllAsync(); - } catch (IllegalArgumentException ignored) { - } - } - looperThread.testComplete(); - } - - @Test - @RunTestInLooperThread - public void distinct_async_indexedLinkedFields() { - final long numberOfBlocks = 3; - final long numberOfObjects = 3; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); + RealmObjectSchema schema = realm.getSchema().getSchemaForClass(AllTypes.CLASS_NAME); - for (String field : AnnotationIndexTypes.INDEX_FIELDS) { - try { - realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_OBJECT + "." + field).findAllAsync(); - fail("Unsupported " + field + " linked field"); - } catch (IllegalArgumentException ignored) { + Set fieldNames = schema.getFieldNames(); + for (String fieldName : fieldNames) { + String field = fieldName; + RealmFieldType type = schema.getFieldType(fieldName); + if (!supportDistinct(type)) { + try { + realm.where(AllTypes.class).distinct(field).findAllAsync(); + } catch (IllegalArgumentException ignored) { + } } } looperThread.testComplete(); } + // Smoke test of async distinct on unsupported types. Underlying mechanism is the same as for sync test + // (distinct_linkedAllFields), so just verifying async mechanism. @Test - @RunTestInLooperThread - public void distinct_async_notIndexedLinkedFields() { + public void distinct_async_invalidTypesLinkedFields() { populateForDistinctInvalidTypesLinked(realm); - try { - realm.where(AllJavaTypes.class).distinct(AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY).findAllAsync(); - } catch (IllegalArgumentException ignored) { + RealmObjectSchema schema = realm.getSchema().getSchemaForClass(AllTypes.CLASS_NAME); + Set fieldNames = schema.getFieldNames(); + for (String fieldName : fieldNames) { + String field = AllTypes.FIELD_REALMLINK + fieldName; + RealmFieldType type = schema.getFieldType(fieldName); + if (!supportDistinct(type)) { + try { + realm.where(AllTypes.class).distinct(field).findAllAsync(); + fail(field); + } catch (IllegalArgumentException e) { + } + } } - looperThread.testComplete(); } @Test @@ -3421,46 +3468,55 @@ public void distinctMultiArgs_emptyField() { // An empty string field in the middle. try { query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, "", AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); + fail(); } catch (IllegalArgumentException ignored) { } // An empty string field at the end. try { query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, "").findAll(); + fail(); } catch (IllegalArgumentException ignored) { } // A null string field in the middle. try { query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null, AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); + fail(); } catch (IllegalArgumentException ignored) { } // A null string field at the end. try { query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.FIELD_INDEX_INT, (String) null).findAll(); + fail(); } catch (IllegalArgumentException ignored) { } // (String) Null makes varargs a null array. try { query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null).findAll(); + fail(); } catch (IllegalArgumentException ignored) { } // Two (String) null for first and varargs fields. try { query.distinct((String) null, (String) null).findAll(); + fail(); } catch (IllegalArgumentException ignored) { } // "" & (String) null combination. try { query.distinct("", (String) null).findAll(); + fail(); } catch (IllegalArgumentException ignored) { } // "" & (String) null combination. try { query.distinct((String) null, "").findAll(); + fail(); } catch (IllegalArgumentException ignored) { } // Two empty fields tests. try { query.distinct("", "").findAll(); + fail(); } catch (IllegalArgumentException ignored) { } } @@ -3476,19 +3532,6 @@ public void distinctMultiArgs_withNullValues() { assertEquals(1, distinctMulti.size()); } - @Test - public void distinctMultiArgs_notIndexedFields() { - final long numberOfBlocks = 3; - final long numberOfObjects = 3; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); - - RealmQuery query = realm.where(AnnotationIndexTypes.class); - try { - query.distinct(AnnotationIndexTypes.FIELD_NOT_INDEX_STRING, AnnotationIndexTypes.NOT_INDEX_FIELDS).findAll(); - } catch (IllegalArgumentException ignored) { - } - } - @Test public void distinctMultiArgs_doesNotExistField() { final long numberOfBlocks = 3; @@ -3498,6 +3541,7 @@ public void distinctMultiArgs_doesNotExistField() { RealmQuery query = realm.where(AnnotationIndexTypes.class); try { query.distinct(AnnotationIndexTypes.FIELD_INDEX_INT, AnnotationIndexTypes.NONEXISTANT_MIX_FIELDS).findAll(); + fail(); } catch (IllegalArgumentException ignored) { } } @@ -3509,34 +3553,29 @@ public void distinctMultiArgs_invalidTypesFields() { RealmQuery query = realm.where(AllTypes.class); try { query.distinct(AllTypes.FIELD_REALMOBJECT, AllTypes.INVALID_TYPES_FIELDS_FOR_DISTINCT).findAll(); + fail(); } catch (IllegalArgumentException ignored) { } } @Test - public void distinctMultiArgs_indexedLinkedFields() { + public void distinctMultiArgs_LinkedFields() { final long numberOfBlocks = 3; final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); - RealmQuery query = realm.where(AnnotationIndexTypes.class); - try { - query.distinct(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING, AnnotationIndexTypes.INDEX_LINKED_FIELDS).findAll(); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - public void distinctMultiArgs_notIndexedLinkedFields() { - final long numberOfBlocks = 3; - final long numberOfObjects = 3; - populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); + DynamicRealm dynamicRealm = DynamicRealm.createInstance(realm.sharedRealm); + RealmResults all = dynamicRealm.where(AnnotationIndexTypes.CLASS_NAME) + .findAll(); RealmQuery query = realm.where(AnnotationIndexTypes.class); - try { - query.distinct(AnnotationIndexTypes.NOT_INDEX_LINKED_FILED_STRING, AnnotationIndexTypes.NOT_INDEX_LINKED_FIELDS).findAll(); - } catch (IllegalArgumentException ignored) { - } + RealmResults distinct = query.distinct(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING, AnnotationIndexTypes.INDEX_LINKED_FIELDS).findAll(); + + List fields = new ArrayList(); + fields.add(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING); + fields.addAll(Arrays.asList(AnnotationIndexTypes.INDEX_LINKED_FIELDS)); + Set> values = distinct(all, fields.toArray()); + assertEquals(values.size(), distinct.size()); } @Test @@ -3545,7 +3584,9 @@ public void distinctMultiArgs_invalidTypesLinkedFields() { RealmQuery query = realm.where(AllJavaTypes.class); try { - query.distinct(AllJavaTypes.INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT, AllJavaTypes.INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT).findAll(); + // Invalid type (binary) mixed with valid types + query.distinct(AllJavaTypes.FIELD_STRING, AllJavaTypes.INVALID_FIELD_TYPES_FOR_DISTINCT).findAll(); + fail(); } catch (IllegalArgumentException ignored) { } } @@ -3705,5 +3746,80 @@ public void limit_invalidValuesThrows() { } } + // FIXME Maybe move to QueryDescriptor or maybe even to RealmFieldType? + private boolean supportDistinct(RealmFieldType type) { + switch (type) { + case INTEGER: + case BOOLEAN: + case STRING: + case BINARY: + case DATE: + case FLOAT: + case DOUBLE: + case OBJECT: + case DECIMAL128: + case OBJECT_ID: + case LINKING_OBJECTS: + return true; + case LIST: + case INTEGER_LIST: + case BOOLEAN_LIST: + case STRING_LIST: + case BINARY_LIST: + case DATE_LIST: + case FLOAT_LIST: + case DOUBLE_LIST: + case DECIMAL128_LIST: + case OBJECT_ID_LIST: + return false; + } + // Should never reach here as the above switch is exhaustive + throw new UnsupportedOperationException("Unhandled realm field type " + type); + } + + // Manual distinct method for verification. Uses field value's equals. + @NotNull + private Set> distinct(RealmResults all, Object... fields) { + Set> values = new HashSet(); + + // Parsed hierarchical field accessors + List fieldAccessors = new ArrayList<>(); + for (Object field : fields) { + fieldAccessors.add(((String) field).split("\\.")); + } + + for (DynamicRealmObject object : all) { + List elements = new ArrayList<>(fields.length); + for (String[] split : fieldAccessors) { + int i = 0; + while(i < split.length - 1) { + object = object.get(split[i]); + i++; + } + String fieldName = split[i]; + if (!object.isNull(fieldName)) { + Object e = object.get(fieldName); + // Need to convert byte arrays to list to detect duplicates when inserting to values + if (e instanceof byte[]) { + elements.add(convertBytesToList((byte[]) e)); + } else { + elements.add(e); + } + } else { + elements.add(null); + } + } + values.add(elements); + } + return values; + } + + private static List convertBytesToList(byte[] bytes) { + final List list = new ArrayList<>(); + for (byte b : bytes) { + list.add(b); + } + return list; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java index 44f943df69..881a30b457 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java @@ -67,16 +67,21 @@ public class AllJavaTypes extends RealmObject { public static final String FIELD_LO_OBJECT = "objectParents"; public static final String FIELD_LO_LIST = "listParents"; - public static final String[] INVALID_FIELDS_FOR_DISTINCT - = new String[] {FIELD_OBJECT, FIELD_LIST, FIELD_DOUBLE, FIELD_FLOAT, FIELD_LO_OBJECT, FIELD_LO_LIST}; - - public static final String INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT - = AllJavaTypes.FIELD_OBJECT + "." + AllJavaTypes.FIELD_BINARY; - - public static final String[] INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT = new String[] { - FIELD_OBJECT + "." + FIELD_BINARY, - FIELD_OBJECT + "." + FIELD_OBJECT, - FIELD_OBJECT + "." + FIELD_LIST}; + public static final String[] INVALID_FIELD_TYPES_FOR_DISTINCT = new String[] { + FIELD_OBJECT + "." + FIELD_LIST, + FIELD_OBJECT + "." + FIELD_STRING_LIST, + FIELD_OBJECT + "." + FIELD_BINARY_LIST, + FIELD_OBJECT + "." + FIELD_BOOLEAN_LIST, + FIELD_OBJECT + "." + FIELD_LONG_LIST, + FIELD_OBJECT + "." + FIELD_INTEGER_LIST, + FIELD_OBJECT + "." + FIELD_SHORT_LIST, + FIELD_OBJECT + "." + FIELD_BYTE_LIST, + FIELD_OBJECT + "." + FIELD_DOUBLE_LIST, + FIELD_OBJECT + "." + FIELD_FLOAT_LIST, + FIELD_OBJECT + "." + FIELD_DATE_LIST, + FIELD_OBJECT + "." + FIELD_DECIMAL128_LIST, + FIELD_OBJECT + "." + FIELD_OBJECT_ID_LIST, + }; @Ignore private String fieldIgnored; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java index 594c2e2462..22c51c02b5 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java @@ -90,7 +90,7 @@ public void getInstanceForDistinct() { } @Test - public void getInstanceForDistinct_shouldThrowOnLinkAndListListField() { + public void getInstanceForDistinct_shouldThrowOnListField() { RealmFieldType type = RealmFieldType.STRING; RealmFieldType objectType = RealmFieldType.OBJECT; RealmFieldType listType = RealmFieldType.LIST; @@ -103,12 +103,6 @@ public void getInstanceForDistinct_shouldThrowOnLinkAndListListField() { fail(); } catch (IllegalArgumentException ignored) { } - - try { - QueryDescriptor.getInstanceForDistinct(null, table, String.format("%s.%s", objectType.name(), type.name())); - fail(); - } catch (IllegalArgumentException ignored) { - } } @Test diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java index 8f3408f1f9..cbd549c253 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java @@ -51,7 +51,15 @@ public class QueryDescriptor { //@VisibleForTesting public final static Set DISTINCT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( - RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.DATE, RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID))); + RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, + RealmFieldType.BINARY, RealmFieldType.DATE, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, + RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID, RealmFieldType.OBJECT, + RealmFieldType.LINKING_OBJECTS + ))); + + public final static Set DISTINCT_VALID_LINK_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + RealmFieldType.OBJECT, RealmFieldType.LINKING_OBJECTS + ))); public static QueryDescriptor getInstanceForSort(FieldDescriptor.SchemaProxy proxy, Table table, String fieldDescription, Sort sortOrder) { return getInstanceForSort(proxy, table, new String[] {fieldDescription}, new Sort[] {sortOrder}); @@ -73,7 +81,7 @@ public static QueryDescriptor getInstanceForDistinct(FieldDescriptor.SchemaProxy } public static QueryDescriptor getInstanceForDistinct(FieldDescriptor.SchemaProxy proxy, Table table, String[] fieldDescriptions) { - return getInstance(proxy, table, fieldDescriptions, null, FieldDescriptor.NO_LINK_FIELD_TYPE, DISTINCT_VALID_FIELD_TYPES, "Distinct is not supported"); + return getInstance(proxy, table, fieldDescriptions, null, DISTINCT_VALID_LINK_FIELD_TYPES, DISTINCT_VALID_FIELD_TYPES, "Distinct is not supported"); } private static QueryDescriptor getInstance( diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java index e7a0779283..e0177680d4 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java @@ -22,7 +22,9 @@ import io.realm.MutableRealmInteger; import io.realm.RealmList; import io.realm.RealmObject; +import io.realm.RealmResults; import io.realm.TestHelper; +import io.realm.annotations.LinkingObjects; import io.realm.annotations.Required; import org.bson.types.Decimal128; @@ -42,8 +44,10 @@ public class AllTypes extends RealmObject { public static final String FIELD_DECIMAL128 = "columnDecimal128"; public static final String FIELD_OBJECT_ID = "columnObjectId"; public static final String FIELD_REALMOBJECT = "columnRealmObject"; - public static final String FIELD_REALMLIST = "columnRealmList"; + public static final String FIELD_REALMLINK = "columnRealmLink"; + public static final String FIELD_REALMBACKLINK = "columnRealmBackLink"; + public static final String FIELD_REALMLIST = "columnRealmList"; public static final String FIELD_STRING_LIST = "columnStringList"; public static final String FIELD_BINARY_LIST = "columnBinaryList"; public static final String FIELD_BOOLEAN_LIST = "columnBooleanList"; @@ -73,7 +77,13 @@ public class AllTypes extends RealmObject { private ObjectId columnObjectId = new ObjectId(TestHelper.randomObjectIdHexString()); private final MutableRealmInteger columnMutableRealmInteger = MutableRealmInteger.ofNull(); + private Dog columnRealmObject; + private AllTypes columnRealmLink; + + @LinkingObjects("columnRealmLink") + final private RealmResults columnRealmBackLink = null; + private RealmList columnRealmList; private RealmList columnStringList; @@ -146,6 +156,7 @@ public void setColumnMutableRealmInteger(int value) { columnMutableRealmInteger.set(value); } + public void setColumnBinary(byte[] columnBinary) { this.columnBinary = columnBinary; } @@ -158,6 +169,18 @@ public void setColumnRealmObject(Dog columnRealmObject) { this.columnRealmObject = columnRealmObject; } + public AllTypes getColumnRealmLink() { + return columnRealmLink; + } + + public void setColumnRealmLink(AllTypes columnRealmLink) { + this.columnRealmLink = columnRealmLink; + } + + public RealmResults getColumnRealmBackLink() { + return columnRealmBackLink; + } + public RealmList getColumnRealmList() { return columnRealmList; } From a1226aa6f4b3487fb99baa8be4b115d357e2272d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 19 Jun 2020 13:44:28 +0200 Subject: [PATCH 1589/2110] Prepare CHANGELOG for 10.0.0-BETA.5 (#6958) --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e41063fbe..11a918aa31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## 10.0.0-BETA.5 (YYYY-MM-DD) + +We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. + +The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. + +### Breaking Changes +* None. + +### Enhancements +* Added support for `distinct` queries on non-index and linked fields. (Issue [#1906](https://github.com/realm/realm-java/issues/1906)) + +### Fixed +* None. + +### Compatibility +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 and above is required to open Realms created by this version. + +### Internal +* None. + ## 10.0.0-BETA.4 (2020-06-11) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. From 9e17e93175db62300848004dceee8d1cc42971db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:49:48 +0200 Subject: [PATCH 1590/2110] Add server API key and custom function credentials for user authentication (#6953) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added support for server api keys and custom functions as valid credentials * Updated OS pointer * Fixed breaking changes after updating with v10 * Updated OS pointer * Added identity provider to OsAppCredential to distinguish between user and server API keys, as the value is the same for both in the OS but are treated as separate providers. Cleaned up some comments. Removed unnecessary log in tests plus added comparison at enum level * Fixed typo in jwt creds test and changed comparison of users to their ids instead of the whole object * Updated OS pointer to latest v10 * Move identity provider from OS to API level Co-authored-by: Eduardo López --- .../kotlin/io/realm/CredentialsTests.kt | 105 ++++++++++++------ .../kotlin/io/realm/admin/ServerAdmin.kt | 12 ++ .../kotlin/io/realm/mongodb/push/PushTest.kt | 16 +-- ..._internal_objectstore_OsAppCredentials.cpp | 26 ++++- .../io_realm_internal_objectstore_OsPush.cpp | 5 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../objectstore/OsAppCredentials.java | 28 +++-- .../io/realm/internal/objectstore/OsPush.java | 6 +- .../java/io/realm/mongodb/Credentials.java | 83 +++++++++----- .../java/io/realm/mongodb/push/Push.java | 18 ++- .../auth_providers/custom-function.json | 2 +- .../functions/testAuthFunc/config.json | 5 + .../functions/testAuthFunc/source.js | 7 ++ .../app_config/functions/void/config.json | 2 +- 14 files changed, 220 insertions(+), 97 deletions(-) create mode 100644 tools/sync_test_server/app_config/functions/testAuthFunc/config.json create mode 100644 tools/sync_test_server/app_config/functions/testAuthFunc/source.js diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index 3a09b16863..a2977c493c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -15,9 +15,12 @@ */ package io.realm -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.admin.ServerAdmin import io.realm.mongodb.* +import io.realm.mongodb.auth.UserApiKey +import org.bson.Document import org.junit.After import org.junit.Assert.* import org.junit.BeforeClass @@ -30,9 +33,10 @@ import kotlin.test.assertFailsWith class CredentialsTests { private lateinit var app: App - + private lateinit var admin: ServerAdmin companion object { + @BeforeClass @JvmStatic fun setUp() { @@ -51,15 +55,21 @@ class CredentialsTests { @Test fun anonymous() { val creds = Credentials.anonymous() - assertEquals("anon-user", creds.identityProvider.id) + assertEquals(Credentials.IdentityProvider.ANONYMOUS, creds.identityProvider) assertTrue(creds.asJson().contains("anon-user")) // Treat the JSON as an opaque value. } - @Ignore("FIXME: Awaiting ObjectStore support") @Test fun apiKey() { val creds = Credentials.apiKey("token") - assertEquals("anon-user", creds.identityProvider.id) + assertEquals(Credentials.IdentityProvider.API_KEY, creds.identityProvider) + assertTrue(creds.asJson().contains("token")) // Treat the JSON as an opaque value. + } + + @Test + fun serverApiKey() { + val creds = Credentials.serverApiKey("token") + assertEquals(Credentials.IdentityProvider.SERVER_API_KEY, creds.identityProvider) assertTrue(creds.asJson().contains("token")) // Treat the JSON as an opaque value. } @@ -69,10 +79,16 @@ class CredentialsTests { assertFailsWith { Credentials.apiKey(TestHelper.getNull()) } } + @Test + fun serverApiKey_invalidInput() { + assertFailsWith { Credentials.serverApiKey("") } + assertFailsWith { Credentials.serverApiKey(TestHelper.getNull()) } + } + @Test fun apple() { val creds = Credentials.apple("apple-token") - assertEquals("oauth2-apple", creds.identityProvider.id) + assertEquals(Credentials.IdentityProvider.APPLE, creds.identityProvider) assertTrue(creds.asJson().contains("apple-token")) // Treat the JSON as a largely opaque value. } @@ -82,22 +98,28 @@ class CredentialsTests { assertFailsWith { Credentials.apple(TestHelper.getNull()) } } - @Ignore("FIXME: Awaiting ObjectStore support") @Test fun customFunction() { - TODO() + val mail = "myfakemail@mongodb.com" + val id = 666 + val creds = mapOf( + "mail" to "myfakemail@mongodb.com", + "id" to 666 + ).let { Credentials.customFunction(Document(it)) } + assertEquals(Credentials.IdentityProvider.CUSTOM_FUNCTION, creds.identityProvider) + assertTrue(creds.asJson().contains(mail)) + assertTrue(creds.asJson().contains(id.toString())) } - @Ignore("FIXME: Awaiting ObjectStore support") @Test fun customFunction_invalidInput() { - TODO() + assertFailsWith { Credentials.customFunction(null) } } @Test fun emailPassword() { val creds = Credentials.emailPassword("foo@bar.com", "secret") - assertEquals("local-userpass", creds.identityProvider.id) + assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, creds.identityProvider) // Treat the JSON as a largely opaque value. assertTrue(creds.asJson().contains("foo@bar.com")) assertTrue(creds.asJson().contains("secret")) @@ -114,7 +136,7 @@ class CredentialsTests { @Test fun facebook() { val creds = Credentials.facebook("fb-token") - assertEquals("oauth2-facebook", creds.identityProvider.id) + assertEquals(Credentials.IdentityProvider.FACEBOOK, creds.identityProvider) assertTrue(creds.asJson().contains("fb-token")) } @@ -127,7 +149,7 @@ class CredentialsTests { @Test fun google() { val creds = Credentials.google("google-token") - assertEquals("oauth2-google", creds.identityProvider.id) + assertEquals(Credentials.IdentityProvider.GOOGLE, creds.identityProvider) assertTrue(creds.asJson().contains("google-token")) } @@ -140,8 +162,8 @@ class CredentialsTests { @Ignore("FIXME: Awaiting ObjectStore support") @Test fun jwt() { - val creds = Credentials.google("jwt-token") - assertEquals("jwt", creds.identityProvider.id) + val creds = Credentials.jwt("jwt-token") + assertEquals(Credentials.IdentityProvider.JWT, creds.identityProvider) assertTrue(creds.asJson().contains("jwt-token")) } @@ -151,33 +173,43 @@ class CredentialsTests { assertFailsWith { Credentials.jwt(TestHelper.getNull()) } } - fun expectErrorCode(app: App, expectedCode: ErrorCode, credentials: Credentials) { - try { - app.login(credentials) - fail() - } catch (error: AppException) { - assertEquals(expectedCode, error.errorCode) - } - } - @Test fun loginUsingCredentials() { app = TestApp() + admin = ServerAdmin() + Credentials.IdentityProvider.values().forEach { provider -> - when(provider) { + when (provider) { Credentials.IdentityProvider.ANONYMOUS -> { val user = app.login(Credentials.anonymous()) assertNotNull(user) } Credentials.IdentityProvider.API_KEY -> { - // FIXME: Wait for API Key support in OS -// val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") -// val key: UserApiKey = app.apiKeyAuthProvider.createApiKey("my-key"); -// val apiKeyUser = app.login(Credentials.apiKey(key.value!!)) -// assertNotNull(apiKeyUser) + // Log in, create an API key, log out, log in with the key, compare users + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val key: UserApiKey = user.apiKeyAuth.createApiKey("my-key"); + user.logOut() + val apiKeyUser = app.login(Credentials.apiKey(key.value!!)) + assertEquals(user.id, apiKeyUser.id) + } + Credentials.IdentityProvider.SERVER_API_KEY -> { + // Create key using the admin API and then log in + val serverKey = admin.createServerApiKey() + val serverKeyUser = app.login(Credentials.serverApiKey(serverKey)) + assertNotNull(serverKeyUser) } Credentials.IdentityProvider.CUSTOM_FUNCTION -> { - // FIXME Wait for Custom Function support + val customFunction = mapOf( + "mail" to "myfakemail@mongodb.com", + "id" to 666 + ).let { + Credentials.customFunction(Document(it)) + } + + // We are not testing the authentication function itself, but rather that the + // credentials work + val functionUser = app.login(customFunction) + assertNotNull(functionUser) } Credentials.IdentityProvider.EMAIL_PASSWORD -> { val email = TestHelper.getRandomEmail() @@ -200,7 +232,7 @@ class CredentialsTests { Credentials.IdentityProvider.GOOGLE -> { expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.google("google-token")) } - Credentials.IdentityProvider.JWT -> { + Credentials.IdentityProvider.JWT -> { expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.jwt("jwt-token")) } Credentials.IdentityProvider.UNKNOWN -> { @@ -209,4 +241,13 @@ class CredentialsTests { } } } + + private fun expectErrorCode(app: App, expectedCode: ErrorCode, credentials: Credentials) { + try { + app.login(credentials) + fail() + } catch (error: AppException) { + assertEquals(expectedCode, error.errorCode) + } + } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt index 898e767ecf..76bd210b1f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt @@ -188,4 +188,16 @@ class ServerAdmin { } return providerId!! } + + /** + * Creates an admin API key that can be used for testing purposes. + */ + fun createServerApiKey(): String { + val body = mapOf(Pair("name", "SERVER_KEY")) + val builder = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/api_keys") + .post(RequestBody.create(json, JSONObject(body).toString())) + val result = JSONObject(executeRequest(builder)) + return result.getString("key") + } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/push/PushTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/push/PushTest.kt index 308d2fbea9..53897f0d51 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/push/PushTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/push/PushTest.kt @@ -115,20 +115,20 @@ class PushTest { @Test fun deregisterDevice() { - user.getPush(SERVICE_NAME).deregisterDevice(SAMPLE_TOKEN) + user.getPush(SERVICE_NAME).deregisterDevice() } @Test fun deregisterDevice_twice() { // the API allows registering/deregistering twice, just checking we don't get errors - user.getPush(SERVICE_NAME).deregisterDevice(SAMPLE_TOKEN) - user.getPush(SERVICE_NAME).deregisterDevice(SAMPLE_TOKEN) + user.getPush(SERVICE_NAME).deregisterDevice() + user.getPush(SERVICE_NAME).deregisterDevice() } @Test fun deregisterDevice_throwsBecauseOfUnknownService() { assertFailsWithErrorCode(ErrorCode.SERVICE_NOT_FOUND) { - user.getPush("asdf").deregisterDevice(SAMPLE_TOKEN) + user.getPush("asdf").deregisterDevice() } } @@ -136,14 +136,14 @@ class PushTest { fun deregisterDevice_throwsBecauseOfLoggedOutUser() { user.logOut() assertFailsWithErrorCode(ErrorCode.SERVICE_UNKNOWN) { - user.getPush(SERVICE_NAME).deregisterDevice(SAMPLE_TOKEN) + user.getPush(SERVICE_NAME).deregisterDevice() } } @Test fun deregisterDeviceAsync() { looperThread.runBlocking { - user.getPush(SERVICE_NAME).deregisterDeviceAsync(SAMPLE_TOKEN) { + user.getPush(SERVICE_NAME).deregisterDeviceAsync() { looperThread.testComplete() } } @@ -152,14 +152,14 @@ class PushTest { @Test fun deregisterDeviceAsync_throwsBecauseOfWrongThread() { assertFailsWith(IllegalStateException::class) { - user.getPush(SERVICE_NAME).deregisterDeviceAsync(SAMPLE_TOKEN) { /* do nothing */ } + user.getPush(SERVICE_NAME).deregisterDeviceAsync() { /* do nothing */ } } } @Test fun deregisterDeviceAsync_throwsBecauseOfUnknownService() { looperThread.runBlocking { - user.getPush("asdf").deregisterDeviceAsync(SAMPLE_TOKEN) { + user.getPush("asdf").deregisterDeviceAsync() { assertEquals(ErrorCode.SERVICE_NOT_FOUND, it.error.errorCode) looperThread.testComplete() } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp index 29a8d81e7c..22daee9a22 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp @@ -18,10 +18,13 @@ #include "util.hpp" +#include #include using namespace realm; using namespace realm::app; +using namespace realm::bson; +using namespace realm::jni_util; static void finalize_credentials(jlong ptr) { @@ -33,7 +36,10 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nati return reinterpret_cast(&finalize_credentials); } -JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nativeCreate(JNIEnv* env, jclass, jint j_type, jobjectArray j_args) +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nativeCreate(JNIEnv* env, + jclass, + jint j_type, + jobjectArray j_args) { try { AppCredentials creds = AppCredentials::anonymous(); // Is there a way to avoid setting this to a specific value? @@ -67,8 +73,22 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nati creds = AppCredentials::custom(token); break; } - case io_realm_internal_objectstore_OsAppCredentials_TYPE_CUSTOM_FUNCTION: - case io_realm_internal_objectstore_OsAppCredentials_TYPE_API_KEY: + case io_realm_internal_objectstore_OsAppCredentials_TYPE_API_KEY: { + JStringAccessor token(env, (jstring) env->GetObjectArrayElement(j_args, 0)); + creds = AppCredentials::user_api_key(token); + break; + } + case io_realm_internal_objectstore_OsAppCredentials_TYPE_SERVER_API_KEY: { + JStringAccessor token(env, (jstring) env->GetObjectArrayElement(j_args, 0)); + creds = AppCredentials::server_api_key(token); + break; + } + case io_realm_internal_objectstore_OsAppCredentials_TYPE_CUSTOM_FUNCTION: { + jstring j_payload = (jstring) env->GetObjectArrayElement(j_args, 0); + bson::BsonDocument payload(JniBsonProtocol::parse_checked(env, j_payload, Bson::Type::Document, "Payload must be a Document")); + creds = AppCredentials::function(payload); + break; + } default: throw std::runtime_error(util::format("Unknown credentials type: %1", j_type)); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp index c7229e58f7..b718ebbd8e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp @@ -84,17 +84,14 @@ Java_io_realm_internal_objectstore_OsPush_nativeDeregisterDevice(JNIEnv *env, jlong j_push_client_ptr, jlong j_user_ptr, jstring j_service_name, - jstring j_registration_token, jobject j_callback) { try { auto push_client = reinterpret_cast(j_push_client_ptr); auto user = *reinterpret_cast*>(j_user_ptr); JStringAccessor service_name(env, j_service_name); - JStringAccessor registration_token(env, j_registration_token); - push_client->deregister_device(registration_token, - user, + push_client->deregister_device(user, JavaNetworkTransport::create_void_callback(env, j_callback)); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 017d58fbec..e1570f8d3d 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 017d58fbec8a18ab003976b4c346308df88349a6 +Subproject commit e1570f8d3d7cf4d77f049933e6a241a501301383 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java index 3387badb69..e7e3ce3911 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java @@ -15,7 +15,13 @@ */ package io.realm.internal.objectstore; +import org.bson.Document; + import io.realm.internal.NativeObject; +import io.realm.internal.jni.JniBsonProtocol; +import io.realm.mongodb.AppConfiguration; +import io.realm.mongodb.AppException; +import io.realm.mongodb.Credentials; /** * Class wrapping ObjectStores {@code realm::app::AppCredentials}. @@ -24,12 +30,13 @@ public class OsAppCredentials implements NativeObject { private static final int TYPE_ANONYMOUS = 1; private static final int TYPE_API_KEY = 2; - private static final int TYPE_APPLE = 3; - private static final int TYPE_CUSTOM_FUNCTION = 4; - private static final int TYPE_EMAIL_PASSWORD = 5; - private static final int TYPE_FACEBOOK = 6; - private static final int TYPE_GOOGLE = 7; - private static final int TYPE_JWT = 8; + private static final int TYPE_SERVER_API_KEY = 3; + private static final int TYPE_APPLE = 4; + private static final int TYPE_CUSTOM_FUNCTION = 5; + private static final int TYPE_EMAIL_PASSWORD = 6; + private static final int TYPE_FACEBOOK = 7; + private static final int TYPE_GOOGLE = 8; + private static final int TYPE_JWT = 9; private static final long finalizerPtr = nativeGetFinalizerMethodPtr(); public static OsAppCredentials anonymous() { @@ -40,12 +47,17 @@ public static OsAppCredentials apiKey(String key) { return new OsAppCredentials(nativeCreate(TYPE_API_KEY, key)); } + public static OsAppCredentials serverApiKey(String key) { + return new OsAppCredentials(nativeCreate(TYPE_SERVER_API_KEY, key)); + } + public static OsAppCredentials apple(String idToken) { return new OsAppCredentials(nativeCreate(TYPE_APPLE, idToken)); } - public static OsAppCredentials customFunction(String functionName, Object... args) { - return new OsAppCredentials(nativeCreate(TYPE_CUSTOM_FUNCTION, functionName, args)); + public static OsAppCredentials customFunction(Document args) { + String encodedArgs = JniBsonProtocol.encode(args, AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY); + return new OsAppCredentials(nativeCreate(TYPE_CUSTOM_FUNCTION, encodedArgs)); } public static OsAppCredentials emailPassword(String email, String password) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java index aecf443b6b..c01d6604ff 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java @@ -54,14 +54,14 @@ public void registerDevice(String registrationToken) { ResultHandler.handleResult(null, error); } - public void deregisterDevice(String registrationToken) { + public void deregisterDevice() { AtomicReference error = new AtomicReference<>(null); - nativeDeregisterDevice(nativePtr, osSyncUser.getNativePtr(), serviceName, registrationToken, new OsJNIVoidResultCallback(error)); + nativeDeregisterDevice(nativePtr, osSyncUser.getNativePtr(), serviceName, new OsJNIVoidResultCallback(error)); ResultHandler.handleResult(null, error); } private static native long nativeCreate(long nativeAppPtr, String serviceName); private static native long nativeGetFinalizerMethodPtr(); private static native void nativeRegisterDevice(long nativePtr, long nativeUserPtr, String serviceName, String registrationToken, OsJNIVoidResultCallback callback); - private static native void nativeDeregisterDevice(long nativePtr, long nativeUserPtr, String serviceName, String registrationToken, OsJNIVoidResultCallback callback); + private static native void nativeDeregisterDevice(long nativePtr, long nativeUserPtr, String serviceName, OsJNIVoidResultCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java index 6362fd2034..779bb7c533 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java @@ -16,6 +16,8 @@ package io.realm.mongodb; +import org.bson.Document; + import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.internal.objectstore.OsAppCredentials; @@ -48,13 +50,16 @@ * } * } *
              - * @see Authentication Providers + * + * @see Authentication Providers */ @Beta public class Credentials { OsAppCredentials osCredentials; + private final IdentityProvider identityProvider; + /** * Creates credentials representing an anonymous user. *

              @@ -67,11 +72,11 @@ public class Credentials { * {@link App#loginAsync(Credentials, App.Callback)}. */ public static Credentials anonymous() { - return new Credentials(OsAppCredentials.anonymous()); + return new Credentials(OsAppCredentials.anonymous(), IdentityProvider.ANONYMOUS); } /** - * Creates credentials representing a login using an API key. + * Creates credentials representing a login using a user API key. *

              * This provider must be enabled on MongoDB Realm to work. * @@ -80,8 +85,22 @@ public static Credentials anonymous() { * {@link App#loginAsync(Credentials, App.Callback)}. */ public static Credentials apiKey(String key) { - Util.checkEmpty(key, "id"); - return new Credentials(OsAppCredentials.apiKey(key)); + Util.checkEmpty(key, "key"); + return new Credentials(OsAppCredentials.apiKey(key), IdentityProvider.API_KEY); + } + + /** + * Creates credentials representing a login using a server API key. + *

              + * This provider must be enabled on MongoDB Realm to work. + * + * @param key the API key to use for login. + * @return a set of credentials that can be used to log into MongoDB Realm using + * {@link App#loginAsync(Credentials, App.Callback)}. + */ + public static Credentials serverApiKey(String key) { + Util.checkEmpty(key, "key"); + return new Credentials(OsAppCredentials.serverApiKey(key), IdentityProvider.SERVER_API_KEY); } /** @@ -95,27 +114,30 @@ public static Credentials apiKey(String key) { */ public static Credentials apple(String idToken) { Util.checkEmpty(idToken, "idToken"); - return new Credentials(OsAppCredentials.apple(idToken)); + return new Credentials(OsAppCredentials.apple(idToken), IdentityProvider.APPLE); } /** - * FIXME + * Creates credentials representing a remote function from MongoDB Realm using a + * {@link Document} which will be parsed as an argument to the remote function, so the keys must + * match the format and names the function expects. *

              * This provider must be enabled on MongoDB Realm to work. * + * @param arguments document containing the function arguments. * @return a set of credentials that can be used to log into MongoDB Realm using * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static Credentials customFunction(String functionName, Object... arguments) { - // FIXME: How to check arguments? - Util.checkEmpty(functionName, "functionName"); - return new Credentials(OsAppCredentials.customFunction(functionName, arguments)); + public static Credentials customFunction(Document arguments) { + Util.checkNull(arguments, "arguments"); + return new Credentials(OsAppCredentials.customFunction(arguments), + IdentityProvider.CUSTOM_FUNCTION); } /** * Creates credentials representing a login using email and password. * - * @param email email of the user logging in. + * @param email email of the user logging in. * @param password password of the user logging in. * @return a set of credentials that can be used to log into MongoDB Realm using * {@link App#loginAsync(Credentials, App.Callback)}. @@ -123,11 +145,12 @@ public static Credentials customFunction(String functionName, Object... argument public static Credentials emailPassword(String email, String password) { Util.checkEmpty(email, "email"); Util.checkEmpty(password, "password"); - return new Credentials(OsAppCredentials.emailPassword(email, password)); + return new Credentials(OsAppCredentials.emailPassword(email, password), + IdentityProvider.EMAIL_PASSWORD); } /** - * Creates credentials representing a login using an Facebook access token. + * Creates credentials representing a login using a Facebook access token. *

              * This provider must be enabled on MongoDB Realm to work. * @@ -137,11 +160,11 @@ public static Credentials emailPassword(String email, String password) { */ public static Credentials facebook(String accessToken) { Util.checkEmpty(accessToken, "accessToken"); - return new Credentials(OsAppCredentials.facebook(accessToken)); + return new Credentials(OsAppCredentials.facebook(accessToken), IdentityProvider.FACEBOOK); } /** - * Creates credentials representing a login using an Google access token. + * Creates credentials representing a login using a Google access token. *

              * This provider must be enabled on MongoDB Realm to work. * @@ -151,11 +174,11 @@ public static Credentials facebook(String accessToken) { */ public static Credentials google(String googleToken) { Util.checkEmpty(googleToken, "googleToken"); - return new Credentials(OsAppCredentials.google(googleToken)); + return new Credentials(OsAppCredentials.google(googleToken), IdentityProvider.GOOGLE); } /** - * Creates credentials representing a login using an JWT Token. This token is normally generated + * Creates credentials representing a login using a JWT Token. This token is normally generated * after a custom OAuth2 login flow. *

              * This provider must be enabled on MongoDB Realm to work. @@ -166,16 +189,24 @@ public static Credentials google(String googleToken) { */ public static Credentials jwt(String jwtToken) { Util.checkEmpty(jwtToken, "jwtToken"); - return new Credentials(OsAppCredentials.jwt(jwtToken)); + return new Credentials(OsAppCredentials.jwt(jwtToken), IdentityProvider.JWT); } /** - * Returns the id for the provider used to authenticate with. + * Returns the identity provider used to authenticate with. * - * @return the id identifying the chosen authentication provider. + * @return the provider identifying the chosen credentials. */ public IdentityProvider getIdentityProvider() { - return IdentityProvider.fromId(osCredentials.getProvider()); + String nativeProvider = osCredentials.getProvider(); + String id = identityProvider.getId(); + + // Sanity check - ensure nothing changed in the OS + if (nativeProvider.equals(id)) { + return identityProvider; + } else { + throw new AssertionError("The provider from the Object Store differs from the one in Realm."); + } } /** @@ -187,8 +218,9 @@ public String asJson() { return osCredentials.asJson(); } - private Credentials(OsAppCredentials credentials) { + private Credentials(OsAppCredentials credentials, IdentityProvider identityProvider) { this.osCredentials = credentials; + this.identityProvider = identityProvider; } /** @@ -200,9 +232,10 @@ private Credentials(OsAppCredentials credentials) { */ public enum IdentityProvider { ANONYMOUS("anon-user"), - API_KEY(""), // FIXME + API_KEY("api-key"), + SERVER_API_KEY("api-key"), // same value as API_KEY as per OS specifications APPLE("oauth2-apple"), - CUSTOM_FUNCTION(""), // FIXME + CUSTOM_FUNCTION("custom-function"), EMAIL_PASSWORD("local-userpass"), FACEBOOK("oauth2-facebook"), GOOGLE("oauth2-google"), diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java index 2efd5b7b9f..02b3a30a1a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/push/Push.java @@ -69,29 +69,25 @@ public Void run() throws AppException { /** * Deregisters the FCM registration token bound to the currently logged in user's * device on MongoDB Realm. - * - * @param registrationToken the registration token to deregister. */ - public void deregisterDevice(String registrationToken) { - osPush.deregisterDevice(registrationToken); + public void deregisterDevice() { + osPush.deregisterDevice(); } /** * Deregisters the FCM registration token bound to the currently logged in user's * device on MongoDB Realm. * - * @param registrationToken The registration token to register. - * @param callback The callback used when the device has been registered or the call - * failed - it will always happen on the same thread as this method was - * called on. + * @param callback The callback used when the device has been registered or the call + * failed - it will always happen on the same thread as this method was + * called on. */ - public RealmAsyncTask deregisterDeviceAsync(String registrationToken, - App.Callback callback) { + public RealmAsyncTask deregisterDeviceAsync(App.Callback callback) { Util.checkLooperThread("Asynchronous deregistering a device is only possible from looper threads."); return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws AppException { - osPush.deregisterDevice(registrationToken); + osPush.deregisterDevice(); return null; } }.start(); diff --git a/tools/sync_test_server/app_config/auth_providers/custom-function.json b/tools/sync_test_server/app_config/auth_providers/custom-function.json index 705071c52a..4efad48eb0 100644 --- a/tools/sync_test_server/app_config/auth_providers/custom-function.json +++ b/tools/sync_test_server/app_config/auth_providers/custom-function.json @@ -3,7 +3,7 @@ "name": "custom-function", "type": "custom-function", "config": { - "authFunctionName": "authFunc" + "authFunctionName": "testAuthFunc" }, "disabled": false } diff --git a/tools/sync_test_server/app_config/functions/testAuthFunc/config.json b/tools/sync_test_server/app_config/functions/testAuthFunc/config.json new file mode 100644 index 0000000000..f28999d9ef --- /dev/null +++ b/tools/sync_test_server/app_config/functions/testAuthFunc/config.json @@ -0,0 +1,5 @@ +{ + "id": "5edea20f0f99fe616bf40ae8", + "name": "testAuthFunc", + "private": false +} diff --git a/tools/sync_test_server/app_config/functions/testAuthFunc/source.js b/tools/sync_test_server/app_config/functions/testAuthFunc/source.js new file mode 100644 index 0000000000..1bebadb559 --- /dev/null +++ b/tools/sync_test_server/app_config/functions/testAuthFunc/source.js @@ -0,0 +1,7 @@ +exports = ({mail, id}) => { + if (mail != "myfakemail@mongodb.com" || id != 666) { + return 0; + } else { + return "works"; + } +} diff --git a/tools/sync_test_server/app_config/functions/void/config.json b/tools/sync_test_server/app_config/functions/void/config.json index 9992ccd8c8..2aa645d14e 100644 --- a/tools/sync_test_server/app_config/functions/void/config.json +++ b/tools/sync_test_server/app_config/functions/void/config.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40ae8", + "id": "5edea20f0f99fe616bf40ae9", "name": "void", "private": false } From 9aa7396e06c5742572630b0ab55776d1cfbc9889 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 19 Jun 2020 17:59:27 +0200 Subject: [PATCH 1591/2110] Prepare BETA.5 release. (#6960) --- CHANGELOG.md | 15 ++++++++------- version.txt | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11a918aa31..99c7edffa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.0-BETA.5 (YYYY-MM-DD) +## 10.0.0-BETA.5 (2020-06-19) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. @@ -8,6 +8,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new * None. ### Enhancements +* [RealmApp] Added support for Api Keys, Server Api Keys and Custom Functions as Credential types when logging in. * Added support for `distinct` queries on non-index and linked fields. (Issue [#1906](https://github.com/realm/realm-java/issues/1906)) ### Fixed @@ -19,7 +20,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal -* None. +* Upgraded to Object Store commit: e1570f8d3d7cf4d77f049933e6a241a501301383. ## 10.0.0-BETA.4 (2020-06-11) @@ -28,7 +29,7 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes -* None +* None. ### Enhancements * [RealmApp] Added support for Custom Data using `User.customData()` and `User.refreshCustomData()`. @@ -53,10 +54,10 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes -* None +* None. ### Enhancements -* None +* None. ### Fixed * [RealmApp] When restarting an app, the base URL used would in some cases be incorrect. (Since 10.0.0-BETA.2) @@ -77,10 +78,10 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes -* None +* None. ### Enhancements -* None +* None. ### Fixed * [RealmApp] `AppConfiguration` did not fallback to the correct default baseUrl if none was provided. (Since 10.0.0-BETA.1) diff --git a/version.txt b/version.txt index 812e04e0be..3637208e59 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.5-SNAPSHOT +10.0.0-BETA.5 From 17ba5243c620637467c6ab617768f0dc6c6cf7ae Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 19 Jun 2020 18:01:59 +0200 Subject: [PATCH 1592/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 3637208e59..b27ddb8fd2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.5 +10.0.0-BETA.6-SNAPSHOT From e1700c5f0c1e93bc042fb25cd6e4c6aec30499c9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 22 Jun 2020 15:58:00 +0200 Subject: [PATCH 1593/2110] Run CI tests in parallel (#6965) --- Jenkinsfile | 106 +++++++++++++++++++++++++--------------------------- 1 file changed, 51 insertions(+), 55 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d2a1eecd86..5fb1612e11 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -163,61 +163,57 @@ def runBuild(abiFilter, instrumentationTestTarget) { sh "chmod +x gradlew && ./gradlew assemble javadoc ${abiFilter} --stacktrace" } - stage('JVM tests') { - try { - sh "chmod +x gradlew && ./gradlew check ${abiFilter} --stacktrace" - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - storeJunitResults 'realm/realm-library/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) - } - } - - stage('Realm Transformer tests') { - try { - gradle('realm-transformer', 'check') - } finally { - storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' - } - } - - stage('Static code analysis') { - try { - gradle('realm', "findbugs ${abiFilter}") // FIXME Renable pmd and checkstyle - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) -// publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) -// step([$class: 'CheckStylePublisher', -// canComputeNew: false, -// defaultEncoding: '', -// healthy: '', -// pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', -// unHealthy: '' -// ]) - } - } - - stage('Run instrumented tests') { - String backgroundPid - try { - backgroundPid = startLogCatCollector() - forwardAdbPorts() - gradle('realm', "${instrumentationTestTarget} ${abiFilter}") - } finally { - stopLogCatCollector(backgroundPid) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' - storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' - } - } - - // Gradle plugin tests require that artifacts are available, so this - // step needs to be after the instrumentation tests - stage('Gradle plugin tests') { - try { - gradle('gradle-plugin', 'check --debug') - } finally { - storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + stage('Tests') { + parallel 'JVM' : { + try { + sh "chmod +x gradlew && ./gradlew check ${abiFilter} --stacktrace" + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + storeJunitResults 'realm/realm-library/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) + } + }, + 'Realm Transformer' : { + try { + gradle('realm-transformer', 'check') + } finally { + storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + } + }, + 'Static code analysis' : { + try { + gradle('realm', "findbugs ${abiFilter}") // FIXME Renable pmd and checkstyle + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) + // publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) + // step([$class: 'CheckStylePublisher', + // canComputeNew: false, + // defaultEncoding: '', + // healthy: '', + // pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', + // unHealthy: '' + // ]) + } + }, + 'Instrumentation' : { + String backgroundPid + try { + backgroundPid = startLogCatCollector() + forwardAdbPorts() + gradle('realm', "${instrumentationTestTarget} ${abiFilter}") + } finally { + stopLogCatCollector(backgroundPid) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } + }, + 'Gradle Plugin' : { + try { + gradle('gradle-plugin', 'check --debug') + } finally { + storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + } } } From e15510048e1b71ae6f56f0b13f2cf486f120e4ba Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 23 Jun 2020 09:17:10 +0200 Subject: [PATCH 1594/2110] Suggest users to disable gatekeeper when building realm on OSX (#6966) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 60085b34ec..3ac700e1fe 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,8 @@ Note: Building from source with Realm Sync is not enabled yet. Only building the Note: If you want to build from source inside Android Studio, you need to update the Gradle parameters by going into the Realm projects settings `Settings > Build, Execution, Deployment > Compiler > Command-line options` and add `-PcoreSourcePath=` to it. +Note: If building on OSX you might like to prevent Gatekeeper to block all NDK executables by disabling it: `sudo spctl --master-disable`. Remember to enable it afterwards: `sudo spctl --master-enable` + ### Other Commands * `./gradlew tasks` will show all the available tasks From ef91f43702db27d666e506fce835ea8a1c41a7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 23 Jun 2020 15:45:21 +0200 Subject: [PATCH 1595/2110] Migrate and reenable encrypted sync tests (#6963) --- .../EncryptedSynchronizedRealmTests.java | 236 ------------------ .../realm/EncryptedSynchronizedRealmTests.kt | 198 +++++++++++++++ 2 files changed, 198 insertions(+), 236 deletions(-) delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java deleted file mode 100644 index 080b02bdfd..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/EncryptedSynchronizedRealmTests.java +++ /dev/null @@ -1,236 +0,0 @@ -package io.realm.objectserver; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.Timeout; - -import java.util.UUID; - -import io.realm.ObjectServerError; -import io.realm.Realm; -import io.realm.RealmResults; -import io.realm.StandardIntegrationTest; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncTestUtils; -import io.realm.SyncUser; -import io.realm.TestHelper; -import io.realm.entities.StringOnly; -import io.realm.exceptions.RealmFileException; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.StringOnlyModule; -import io.realm.objectserver.utils.UserFactory; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -public class EncryptedSynchronizedRealmTests extends StandardIntegrationTest { - - @Rule - public Timeout globalTimeout = Timeout.seconds(30); - - // Make sure the encryption is local, i.e after deleting a synced Realm - // re-open it again with no (or different) key, should be possible. - @Test - public void setEncryptionKey_canReOpenRealmWithoutKey() throws InterruptedException { - - // STEP 1: open a synced Realm using a local encryption key - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - final byte[] randomKey = TestHelper.getRandomKey(); - - SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .modules(new StringOnlyModule()) - .waitForInitialRemoteData() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail(error.getErrorMessage()); - } - }) - .encryptionKey(randomKey) - .build(); - - Realm realm = Realm.getInstance(configWithEncryption); - assertTrue(realm.isEmpty()); - - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Hi Alice"); - realm.commitTransaction(); - - // STEP 2: make sure the changes gets to the server - SyncManager.getSession(configWithEncryption).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // STEP 3: try to open again the same sync Realm but different local name without the encryption key should not - // fail - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); - SyncConfiguration configWithoutEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .name("newName") - .modules(new StringOnlyModule()) - .waitForInitialRemoteData() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail(error.getErrorMessage()); - } - }) - .build(); - - realm = Realm.getInstance(configWithoutEncryption); - RealmResults all = realm.where(StringOnly.class).findAll(); - assertEquals(1, all.size()); - assertEquals("Hi Alice", all.get(0).getChars()); - - realm.close(); - user.logOut(); - } - - // If an encrypted synced Realm is re-opened with the wrong key, throw an exception. - @Test - public void setEncryptionKey_shouldCrashIfKeyNotProvided() throws InterruptedException { - // STEP 1: open a synced Realm using a local encryption key - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - final byte[] randomKey = TestHelper.getRandomKey(); - - SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .modules(new StringOnlyModule()) - .waitForInitialRemoteData() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail(error.getErrorMessage()); - } - }) - .encryptionKey(randomKey) - .build(); - - Realm realm = Realm.getInstance(configWithEncryption); - assertTrue(realm.isEmpty()); - - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Hi Alice"); - realm.commitTransaction(); - - // STEP 2: Close the Realm and log the user out to forget about it. - realm.close(); - user.logOut(); - - // STEP 3: try to open again the Realm without the encryption key should fail - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); - SyncConfiguration configWithoutEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .modules(new StringOnlyModule()) - .waitForInitialRemoteData() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail(error.getErrorMessage()); - } - }) - .build(); - - try { - realm = Realm.getInstance(configWithoutEncryption); - fail("It should not be possible to open the Realm without the encryption key set previously."); - } catch (RealmFileException ignored) { - } finally { - if (realm != null) { - realm.close(); - } - } - } - - // If client B encrypts its synced Realm, client A should be able to access that Realm with a different encryption key. - @Test - public void setEncryptionKey_differentClientsWithDifferentKeys() throws InterruptedException { - // STEP 1: prepare a synced Realm for client A - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - final byte[] randomKey = TestHelper.getRandomKey(); - - SyncConfiguration configWithEncryption = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .modules(new StringOnlyModule()) - .waitForInitialRemoteData() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail(error.getErrorMessage()); - } - }) - .encryptionKey(randomKey) - .build(); - - Realm realm = Realm.getInstance(configWithEncryption); - assertTrue(realm.isEmpty()); - - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Hi Alice"); - realm.commitTransaction(); - - // STEP 2: make sure the changes gets to the server - SyncManager.getSession(configWithEncryption).uploadAllLocalChanges(); - realm.close(); - - // STEP 3: prepare a synced Realm for client B (admin user) - SyncUser admin = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentials = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(admin).value(), "custom-admin-user"); - SyncUser adminUser = SyncUser.logIn(credentials, Constants.AUTH_URL); - - final byte[] adminRandomKey = TestHelper.getRandomKey(); - - SyncConfiguration adminConfigWithEncryption = configurationFactory.createSyncConfigurationBuilder(adminUser, configWithEncryption.getServerUrl().toString()) - .modules(new StringOnlyModule()) - .waitForInitialRemoteData() - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail(error.getErrorMessage()); - } - }) - .encryptionKey(adminRandomKey) - .build(); - - Realm adminRealm = Realm.getInstance(adminConfigWithEncryption); - RealmResults all = adminRealm.where(StringOnly.class).findAll(); - assertEquals(1, all.size()); - assertEquals("Hi Alice", all.get(0).getChars()); - - adminRealm.beginTransaction(); - adminRealm.createObject(StringOnly.class).setChars("Hi Bob"); - adminRealm.commitTransaction(); - SyncManager.getSession(adminConfigWithEncryption).uploadAllLocalChanges(); - adminRealm.close(); - - // STEP 4: client A can see changes from client B (although they're using different encryption keys) - realm = Realm.getInstance(configWithEncryption); - SyncManager.getSession(configWithEncryption).downloadAllServerChanges();// force download latest commits from ROS - realm.refresh(); // Not calling refresh will still point to the previous version of the Realm without the latest admin commit "Hi Bob" - assertEquals(2, realm.where(StringOnly.class).count()); - - adminRealm = Realm.getInstance(adminConfigWithEncryption); - - RealmResults allSorted = realm.where(StringOnly.class).sort(StringOnly.FIELD_CHARS).findAll(); - RealmResults allSortedAdmin = adminRealm.where(StringOnly.class).sort(StringOnly.FIELD_CHARS).findAll(); - assertEquals("Hi Alice", allSorted.get(0).getChars()); - assertEquals("Hi Bob", allSorted.get(1).getChars()); - - assertEquals("Hi Alice", allSortedAdmin.get(0).getChars()); - assertEquals("Hi Bob", allSortedAdmin.get(1).getChars()); - - adminRealm.close(); - adminUser.logOut(); - - realm.close(); - user.logOut(); - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt new file mode 100644 index 0000000000..40442066f3 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt @@ -0,0 +1,198 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.entities.SyncStringOnly +import io.realm.exceptions.RealmFileException +import io.realm.kotlin.syncSession +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.mongodb.App +import io.realm.mongodb.Credentials +import io.realm.mongodb.close +import io.realm.mongodb.registerUserAndLogin +import io.realm.mongodb.sync.SyncConfiguration +import io.realm.mongodb.sync.testSchema +import org.bson.BsonObjectId +import org.bson.types.ObjectId +import org.junit.After +import org.junit.Assert +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import kotlin.test.assertFailsWith + +private val SECRET_PASSWORD = "123456" + +class EncryptedSynchronizedRealmTests { + + private lateinit var app: App + + private val configurationFactory: TestSyncConfigurationFactory = TestSyncConfigurationFactory() + + @Before + fun setup() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestApp() + } + + @After + fun teardown() { + if (this::app.isInitialized) { + app.close() + } + } + + // Make sure the encryption is local, i.e after deleting a synced Realm + // re-open it again with no (or different) key, should be possible. + @Test + fun setEncryptionKey_canReOpenRealmWithoutKey() { + + // STEP 1: open a synced Realm using a local encryption key + var user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val randomKey = TestHelper.getRandomKey() + val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonObjectId()) + .testSchema(SyncStringOnly::class.java) + .waitForInitialRemoteData() + .errorHandler { session, error -> fail(error.getErrorMessage()) } + .encryptionKey(randomKey) + .build() + + Realm.getInstance(configWithEncryption).use { realm -> + assertTrue(realm.isEmpty) + realm.executeTransaction { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Hi Alice" + } + + // STEP 2: make sure the changes gets to the server + realm.syncSession.uploadAllLocalChanges() + } + user.logOut() + + // STEP 3: try to open again the same sync Realm but different local name without the encryption key should not + // fail + var user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val configWithoutEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, configWithEncryption.partitionValue) + // Using different user with same partition value to trigger a different path instead of + // .name("newName") + .testSchema(SyncStringOnly::class.java) + .waitForInitialRemoteData() + .errorHandler { session, error -> fail(error.getErrorMessage()) } + .build() + + Realm.getInstance(configWithoutEncryption).use { realm -> + val all = realm.where(SyncStringOnly::class.java).findAll() + assertEquals(1, all.size.toLong()) + assertEquals("Hi Alice", all[0]!!.chars) + } + user.logOut() + } + + // If an encrypted synced Realm is re-opened with the wrong key, throw an exception. + @Test + fun setEncryptionKey_shouldCrashIfKeyNotProvided() { + // STEP 1: open a synced Realm using a local encryption key + var user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val randomKey = TestHelper.getRandomKey() + val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonObjectId()) + .testSchema(SyncStringOnly::class.java) + .waitForInitialRemoteData() + .errorHandler { session, error -> fail(error.getErrorMessage()) } + .encryptionKey(randomKey) + .build() + + Realm.getInstance(configWithEncryption).use { realm -> + assertTrue(realm.isEmpty) + realm.executeTransaction { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Hi Alice" + } + // STEP 2: Close the Realm and log the user out to forget about it. + } + user.logOut() + + // STEP 3: try to open again the Realm without the encryption key should fail + user = app.login(Credentials.emailPassword(user.email, SECRET_PASSWORD)) + val configWithoutEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, configWithEncryption.partitionValue) + .testSchema(SyncStringOnly::class.java) + .waitForInitialRemoteData() + .errorHandler { session, error -> fail(error.getErrorMessage()) } + .build() + + assertFailsWith { + Realm.getInstance(configWithoutEncryption).close() + } + } + + // If client B encrypts its synced Realm, client A should be able to access that Realm with a different encryption key. + @Test + fun setEncryptionKey_differentClientsWithDifferentKeys() { + // STEP 1: prepare a synced Realm for client A + var user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val randomKey = TestHelper.getRandomKey() + val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonObjectId()) + .testSchema(SyncStringOnly::class.java) + .waitForInitialRemoteData() + .errorHandler { session, error -> fail(error.getErrorMessage()) } + .encryptionKey(randomKey) + .build() + + Realm.getInstance(configWithEncryption).use { realm -> + assertTrue(realm.isEmpty) + realm.executeTransaction { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Hi Alice" + } + // STEP 2: make sure the changes gets to the server + realm.syncSession.uploadAllLocalChanges() + } + + // STEP 3: prepare a synced Realm for client B + var user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val key2 = TestHelper.getRandomKey() + val configWithEncryption2: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, configWithEncryption.partitionValue) + .testSchema(SyncStringOnly::class.java) + .waitForInitialRemoteData() + .errorHandler { session, error -> fail(error.getErrorMessage()) } + .encryptionKey(key2) + .build() + + Realm.getInstance(configWithEncryption2).use { realm -> + val all = realm.where(SyncStringOnly::class.java).findAll() + assertEquals(1, all.size.toLong()) + assertEquals("Hi Alice", all[0]!!.chars) + realm.executeTransaction { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Hi Bob" + } + realm.syncSession.uploadAllLocalChanges() + } + + // STEP 4: client A can see changes from client B (although they're using different encryption keys) + Realm.getInstance(configWithEncryption).use { realm -> + realm.syncSession.downloadAllServerChanges() // force download latest commits from remote realm + realm.refresh() // Not calling refresh will still point to the previous version of the Realm without the latest admin commit "Hi Bob" + assertEquals(2, realm.where(SyncStringOnly::class.java).count()) + val allSorted = realm.where(SyncStringOnly::class.java).sort(SyncStringOnly.FIELD_CHARS).findAll() + val allSortedAdmin = realm.where(SyncStringOnly::class.java).sort(SyncStringOnly.FIELD_CHARS).findAll() + assertEquals("Hi Alice", allSorted[0]!!.chars) + assertEquals("Hi Bob", allSorted[1]!!.chars) + assertEquals("Hi Alice", allSortedAdmin[0]!!.chars) + assertEquals("Hi Bob", allSortedAdmin[1]!!.chars) + } + + user.logOut() + user2.logOut() + } +} From 49974b88cdaddb76d015eca64dea2469d3e8330e Mon Sep 17 00:00:00 2001 From: clementetb Date: Fri, 26 Jun 2020 17:33:42 +0200 Subject: [PATCH 1596/2110] Add encryption key to sync metadata + tests (#6970) * Add encryption key to sync metadata + tests * Add modifications as suggested in PR --- .../kotlin/io/realm/AppConfigurationTests.kt | 23 +++++++++++---- .../kotlin/io/realm/AppTests.kt | 29 +++++++++++++++++++ .../src/main/cpp/io_realm_mongodb_App.cpp | 12 ++++++-- .../java/io/realm/mongodb/App.java | 2 ++ .../io/realm/mongodb/AppConfiguration.java | 3 +- .../syncTestUtils/kotlin/io/realm/TestApp.kt | 13 +++++---- 6 files changed, 68 insertions(+), 14 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index ae9e16f0bd..265ed26321 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -20,8 +20,7 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.mongodb.AppConfiguration import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue +import org.junit.Assert.* import org.junit.Before import org.junit.Ignore import org.junit.Rule @@ -218,15 +217,27 @@ class AppConfigurationTests { } @Test - @Ignore("FIXME") fun encryptionKey() { - TODO() + val key = TestHelper.getRandomKey() + + val config = AppConfiguration.Builder("app-id") + .encryptionKey(key) + .build() + + assertArrayEquals(key, config.encryptionKey) } @Test - @Ignore("FIXME") fun encryptionKey_invalidValuesThrows() { - TODO() + val builder = AppConfiguration.Builder("app-id") + + assertFailsWith { + builder.encryptionKey(TestHelper.getNull()) + } + + assertFailsWith { + builder.encryptionKey(byteArrayOf(0,0,0,0)) + } } @Test diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index f5ad4f91f5..2115e5c17e 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -18,6 +18,7 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin +import io.realm.exceptions.RealmFileException import io.realm.mongodb.* import io.realm.rule.BlockingLooperThread import org.bson.codecs.StringCodec @@ -28,6 +29,7 @@ import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import java.io.File import java.util.concurrent.atomic.AtomicReference import kotlin.test.assertFailsWith @@ -265,4 +267,31 @@ class AppTests { assertEquals(registry, app.getFunctions(user, registry).defaultCodecRegistry) } + @Test() + fun encryption() { + // Remove the App instance created on setUp() because we need to create + // a custom one and only one App instance is allowed + tearDown() + + val context = InstrumentationRegistry.getInstrumentation().targetContext + + // Setup an App instance with a random encryption key + Realm.init(context) + app = TestApp(customizeConfig = { + it.encryptionKey(TestHelper.getRandomKey()) + }) + + val metadataDir = File(context.filesDir, "realm-object-server/io.realm.object-server-utility/metadata/") + val config = RealmConfiguration.Builder() + .name("sync_metadata.realm") + .directory(metadataDir) + .build() + + assertTrue(File(config.path).exists()) + + // Open the metadata realm file without a valid encryption key + assertFailsWith { + DynamicRealm.getInstance(config) + } + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp index 93a5fcf31e..b545e2ec52 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp @@ -89,6 +89,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_App_nativeCreate(JNIEnv* env, jobj jstring j_app_name, jstring j_app_version, jlong j_request_timeout_ms, + jbyteArray j_encryption_key, jstring j_sync_base_dir, jstring j_user_agent_binding_info, jstring j_user_agent_application_info, @@ -97,7 +98,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_App_nativeCreate(JNIEnv* env, jobj jstring j_sdk_version) { try { - // App Config jobject java_app_obj = env->NewGlobalRef(obj); // FIXME: Leaking the app object std::function()> transport_generator = [java_app_obj] { @@ -114,6 +114,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_App_nativeCreate(JNIEnv* env, jobj JStringAccessor platform(env, j_platform); JStringAccessor platform_version(env, j_platform_version); JStringAccessor sdk_version(env, j_sdk_version); + JByteArrayAccessor encryption_key(env, j_encryption_key); + auto app_config = App::Config{ app_id, transport_generator, @@ -133,10 +135,16 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_App_nativeCreate(JNIEnv* env, jobj SyncClientConfig client_config; client_config.base_file_path = base_file_path; - client_config.metadata_mode = SyncManager::MetadataMode::NoEncryption; client_config.user_agent_binding_info = user_agent_binding_info; client_config.user_agent_application_info = user_agent_application_info; + if(j_encryption_key == nullptr){ + client_config.metadata_mode = SyncManager::MetadataMode::NoEncryption; + } else { + client_config.metadata_mode = SyncManager::MetadataMode::Encryption; + client_config.custom_encryption_key = encryption_key.transform>(); + } + // FIXME: SyncManager is still a singleton. Should be refactored to allow multiple SyncManager::shared().configure(client_config, app_config); // Init logger. Must be called after .configure() diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index cd824e4b54..2db63ff191 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -220,6 +220,7 @@ private long init(AppConfiguration config) { config.getAppName(), config.getAppVersion(), config.getRequestTimeoutMs(), + config.getEncryptionKey(), syncDir, userAgentBindingInfo, appDefinedUserAgent, @@ -638,6 +639,7 @@ private native long nativeCreate(String appId, String appName, String appVersion, long requestTimeoutMs, + byte[] encryptionKey, String syncDirPath, String bindingUserInfo, String appUserInfo, diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index 1080692c9f..affceb2c53 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -36,6 +36,7 @@ import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; import io.realm.Realm; import io.realm.annotations.Beta; @@ -294,7 +295,7 @@ public Builder(String appId) { * @param key a 64 byte encryption key. * @throws IllegalArgumentException if the key is not 64 bytes long. */ - public Builder encryptionKey(byte[] key) { + public Builder encryptionKey(@ParametersAreNonnullByDefault byte[] key) { Util.checkNull(key, "key"); if (key.length != Realm.ENCRYPTION_KEY_LENGTH) { throw new IllegalArgumentException(String.format(Locale.US, diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt index 56ac45e192..c54f75b7a2 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt @@ -29,7 +29,7 @@ import io.realm.mongodb.AppConfiguration const val SERVICE_NAME = "BackingDB" // it comes from the test server's BackingDB/config.json const val DATABASE_NAME = "test_data" // same as above -class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: (AppConfiguration.Builder) -> Unit = {}) : App(createConfiguration()) { +class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it }) : App(createConfiguration(customizeConfig)) { init { if (networkTransport != null) { @@ -38,12 +38,15 @@ class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: } companion object { - fun createConfiguration(): AppConfiguration { - return AppConfiguration.Builder(initializeMongoDbRealm()) + fun createConfiguration(customizeConfig: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it }): AppConfiguration { + var builder = AppConfiguration.Builder(initializeMongoDbRealm()) .baseUrl("http://127.0.0.1:9090") .appName("MongoDB Realm Integration Tests") .appVersion("1.0.") - .build() + + builder = customizeConfig(builder) + + return builder.build() } // Initializes MongoDB Realm. Clears all local state and fetches the application ID. @@ -56,7 +59,7 @@ class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: mapOf(), "" ) - return when(response.httpResponseCode) { + return when (response.httpResponseCode) { 200 -> response.body else -> throw IllegalStateException(response.toString()) } From 6de621931cc7e11f138ba64151d53261e21265db Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Sat, 27 Jun 2020 17:07:34 +0200 Subject: [PATCH 1597/2110] Upgrade to latest Server Docker image + Object Store (#6968) --- CHANGELOG.md | 24 ++++++++++++++ dependencies.list | 2 +- .../io/realm/mongodb/sync/SyncedRealmTests.kt | 8 ++--- realm/realm-library/src/main/cpp/object-store | 2 +- .../kotlin/io/realm/mongodb/SyncTestUtils.kt | 31 ++----------------- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99c7edffa6..317ef7c30a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 10.0.0-BETA.6 (YYYY-MM-DD) + +We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. + +The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. + +### Breaking Changes +* None. + +### Enhancements +* None. + +### Fixed +* [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) + +### Compatibility +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 and above is required to open Realms created by this version. + +### Internal +* Upgraded to Object Store commit: 709e69580f480051da8be8b444df400c64c652f8. + + ## 10.0.0-BETA.5 (2020-06-19) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. diff --git a/dependencies.list b/dependencies.list index e93bf0e03b..8049aebcf5 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=0572f751ced210656ed7d567dce9e56b5cca19c3070096ff38c255c9585ccf # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-06-10 +MONGODB_REALM_SERVER_VERSION=2020-06-27 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index c8e66085a6..de539ee7d2 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -27,10 +27,7 @@ import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.LogLevel import io.realm.log.RealmLog -import io.realm.mongodb.App -import io.realm.mongodb.Credentials -import io.realm.mongodb.User -import io.realm.mongodb.close +import io.realm.mongodb.* import org.junit.* import org.junit.Assert.* import org.junit.runner.RunWith @@ -67,6 +64,7 @@ class SyncedRealmTests { // Smoke test for Sync. Waiting for working Sync support. @Test + @Ignore("FIXME: https://github.com/realm/realm-java/issues/6972") fun connectWithInitialSchema() { val user: User = createNewUser() val config = createDefaultConfig(user) @@ -148,6 +146,7 @@ class SyncedRealmTests { // User 1 creates an object an uploads it to MongoDB Realm val user1: User = createNewUser() val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + Realm.deleteRealm(config1) Realm.getInstance(config1).use { realm -> realm.executeTransaction { val person = SyncPerson() @@ -169,6 +168,7 @@ class SyncedRealmTests { // User 2 logs and using the same partition key should see the object val user2: User = createNewUser() val config2 = createDefaultConfig(user2, partitionValue) + Realm.deleteRealm(config2) Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() realm.refresh() diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index e1570f8d3d..709e69580f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit e1570f8d3d7cf4d77f049933e6a241a501301383 +Subproject commit 709e69580f480051da8be8b444df400c64c652f8 diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt index ea80308c20..a52da63064 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt @@ -37,37 +37,12 @@ class SyncTestUtils { RealmLog.setLevel(LogLevel.DEBUG) } - /** - * Tries to restore the environment as best as possible after a test. - */ - fun restoreEnvironmentAfterTest() { - // Block until all users are logged out - UserFactory.logoutAllUsers() - - // Reset log level - RealmLog.setLevel(originalLogLevel) - if (Realm.getApplicationContext() != null) { - // Realm was already initialized. Reset all internal state - // in order to be able to fully re-initialize. - - // This will set the 'm_metadata_manager' in 'sync_manager.cpp' to be 'null' - // causing the User to remain in memory. - // They're actually not persisted into disk. - // move this call to 'tearDown' to clean in-memory & on-disk users - // once https://github.com/realm/realm-object-store/issues/207 is resolved - // SyncManager.reset(); // FIXME - RealmExt.testClearApplicationContext() // Required for Realm.init() to work - } - deleteRosFiles() - Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - } - // Cleanup filesystem to make sure nothing lives for the next test. // Failing to do so might lead to DIVERGENT_HISTORY errors being thrown if Realms from // previous tests are being accessed. - private fun deleteRosFiles() { - val rosFiles = File(InstrumentationRegistry.getInstrumentation().context.filesDir, "realm-object-server") - deleteFile(rosFiles) + fun deleteSyncFiles() { + val syncFiles = File(InstrumentationRegistry.getInstrumentation().context.filesDir, "mongodb-realm") + deleteFile(syncFiles) } private fun deleteFile(file: File) { From c18a86a7a42ece0eab877db91ae5d665d97b64af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 29 Jun 2020 14:14:56 +0200 Subject: [PATCH 1598/2110] Fix ProgressListenerTests (#6959) --- dependencies.list | 2 +- .../kotlin/io/realm/ProgressListenerTests.kt | 352 +++++++------- .../io/realm/mongodb/sync/SyncSession.java | 10 +- .../objectserver/ProgressListenerTests.java | 438 ------------------ 4 files changed, 196 insertions(+), 606 deletions(-) delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java diff --git a/dependencies.list b/dependencies.list index 8049aebcf5..e772cdfc96 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=0572f751ced210656ed7d567dce9e56b5cca19c3070096ff38c255c9585ccf # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-06-27 +MONGODB_REALM_SERVER_VERSION=2020-06-29 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt index e9fd56bfee..9dea7c506e 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt @@ -19,24 +19,25 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.DefaultSyncSchema import io.realm.entities.SyncDog -import io.realm.kotlin.where import io.realm.kotlin.syncSession +import io.realm.kotlin.where import io.realm.log.LogLevel import io.realm.log.RealmLog -import io.realm.mongodb.Credentials import io.realm.mongodb.User import io.realm.mongodb.close +import io.realm.mongodb.registerUserAndLogin import io.realm.mongodb.sync.* -import io.realm.rule.BlockingLooperThread -import org.junit.* +import org.junit.After import org.junit.Assert.* +import org.junit.Before +import org.junit.Ignore +import org.junit.Test import org.junit.runner.RunWith import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger -@Ignore("These are generally flaky. We need to investigate further.") @RunWith(AndroidJUnit4::class) class ProgressListenerTests { @@ -44,9 +45,7 @@ class ProgressListenerTests { private const val TEST_SIZE: Long = 10 } - private val looperThread = BlockingLooperThread() private lateinit var app: TestApp - private lateinit var realm: Realm private lateinit var partitionValue: String @Before @@ -59,36 +58,30 @@ class ProgressListenerTests { @After fun tearDown() { - if (this::realm.isInitialized) { - realm.close() - } if (this::app.isInitialized) { app.close() } RealmLog.setLevel(LogLevel.WARN) } - @Ignore("See https://mongodb.slack.com/archives/CQLDYRJ3V/p1587563930459100") @Test fun downloadProgressListener_changesOnly() { val allChangesDownloaded = CountDownLatch(1) - val user1: User = app.login(Credentials.anonymous()) + val user1: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") val user1Config = createSyncConfig(user1) createRemoteData(user1Config) - val user2: User = app.login(Credentials.anonymous()) + val user2: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") val user2Config = createSyncConfig(user2) - val realm = Realm.getInstance(user2Config) - val session: SyncSession = realm.syncSession - session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> - RealmLog.error(progress.toString()) - if (progress.isTransferComplete) { - assertTransferComplete(progress, true) - assertEquals(TEST_SIZE, getStoreTestDataSize(user2Config)) - allChangesDownloaded.countDown() + Realm.getInstance(user2Config).use { realm -> + realm.syncSession.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + if (progress.isTransferComplete) { + assertTransferComplete(progress, true) + assertEquals(TEST_SIZE, getStoreTestDataSize(user2Config)) + allChangesDownloaded.countDown() + } } + TestHelper.awaitOrFail(allChangesDownloaded) } - TestHelper.awaitOrFail(allChangesDownloaded) - realm.close() } @Test @@ -96,7 +89,7 @@ class ProgressListenerTests { val transferCompleted = AtomicInteger(0) val allChangesDownloaded = CountDownLatch(1) val startWorker = CountDownLatch(1) - val user1: User = app.login(Credentials.anonymous()) + val user1: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") // login(Credentials.anonymous()) val user1Config: SyncConfiguration = createSyncConfig(user1) // Create worker thread that puts data into another Realm. @@ -106,35 +99,36 @@ class ProgressListenerTests { createRemoteData(user1Config) }) worker.start() - val user2: User = app.login(Credentials.anonymous()) + val user2: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") // login(Credentials.anonymous()) val user2Config: SyncConfiguration = createSyncConfig(user2) - val user2Realm = Realm.getInstance(user2Config) - val session: SyncSession = user2Realm.syncSession - session.addDownloadProgressListener(ProgressMode.INDEFINITELY) { progress -> - val objectCounts = getStoreTestDataSize(user2Config) - // The downloading progress listener could be triggered at the db version where only contains the meta - // data. So we start checking from when the first 10 objects downloaded. - RealmLog.warn(String.format( - Locale.ENGLISH, "downloadProgressListener_indefinitely download %d/%d objects count:%d", - progress.transferredBytes, progress.transferableBytes, objectCounts)) - if (objectCounts != 0L && progress.isTransferComplete) { - when (transferCompleted.incrementAndGet()) { - 1 -> { - assertEquals(TEST_SIZE, objectCounts) - assertTransferComplete(progress, true) - startWorker.countDown() - } - 2 -> { - assertTransferComplete(progress, true) - assertEquals(TEST_SIZE * 2, objectCounts) - allChangesDownloaded.countDown() + Realm.getInstance(user2Config).use { user2Realm -> + val session: SyncSession = user2Realm.syncSession + session.addDownloadProgressListener(ProgressMode.INDEFINITELY) { progress -> + val objectCounts = getStoreTestDataSize(user2Config) + // The downloading progress listener could be triggered at the db version where only contains the meta + // data. So we start checking from when the first 10 objects downloaded. + RealmLog.warn(String.format( + Locale.ENGLISH, "downloadProgressListener_indefinitely download %d/%d objects count:%d", + progress.transferredBytes, progress.transferableBytes, objectCounts)) + if (objectCounts != 0L && progress.isTransferComplete) { + when (transferCompleted.incrementAndGet()) { + 1 -> { + assertEquals(TEST_SIZE, objectCounts) + assertTransferComplete(progress, true) + startWorker.countDown() + } + 2 -> { + assertTransferComplete(progress, true) + assertEquals(TEST_SIZE * 2, objectCounts) + allChangesDownloaded.countDown() + } + else -> fail("Transfer complete called too many times:" + transferCompleted.get()) } - else -> fail("Transfer complete called too many times:" + transferCompleted.get()) } } + writeSampleData(user2Realm) // Write first batch of sample data + TestHelper.awaitOrFail(allChangesDownloaded) } - TestHelper.awaitOrFail(allChangesDownloaded) - user2Realm.close() // worker thread will hang if logout happens before listener triggered. worker.join() user1.logOut() @@ -147,47 +141,46 @@ class ProgressListenerTests { val transferCompleted = AtomicInteger(0) val testDone = CountDownLatch(1) val config = createSyncConfig() - val realm = Realm.getInstance(config) - writeSampleData(realm) // Write first batch of sample data - val session: SyncSession = realm.syncSession - session.addUploadProgressListener(ProgressMode.INDEFINITELY) { progress -> - if (progress.isTransferComplete) { - when (transferCompleted.incrementAndGet()) { - 1 -> { - val realm = Realm.getInstance(config) - writeSampleData(realm) - realm.close() - throw RuntimeException("Crashing the changelistener") - } - 2 -> { - assertTransferComplete(progress, true) - testDone.countDown() + Realm.getInstance(config).use { realm -> + val session: SyncSession = realm.syncSession + session.addUploadProgressListener(ProgressMode.INDEFINITELY) { progress -> + if (progress.isTransferComplete) { + when (transferCompleted.incrementAndGet()) { + 1 -> { + Realm.getInstance(config).use { realm -> + writeSampleData(realm) + } + throw RuntimeException("Crashing the changelistener") + } + 2 -> { + assertTransferComplete(progress, true) + testDone.countDown() + } + else -> fail("Unsupported number of transfers completed: " + transferCompleted.get()) } - else -> fail("Unsupported number of transfers completed: " + transferCompleted.get()) } } + writeSampleData(realm) // Write first batch of sample data + TestHelper.awaitOrFail(testDone) } - TestHelper.awaitOrFail(testDone) - realm.close() } @Test fun uploadProgressListener_changesOnly() { val allChangeUploaded = CountDownLatch(1) val config = createSyncConfig() - val realm = Realm.getInstance(config) - writeSampleData(realm) - val session: SyncSession = realm.syncSession - assertEquals(SyncSession.State.ACTIVE, session.state) - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> - RealmLog.error(progress.toString()); - if (progress.isTransferComplete) { - assertTransferComplete(progress, true) - allChangeUploaded.countDown() + Realm.getInstance(config).use { realm -> + val session: SyncSession = realm.syncSession + assertEquals(SyncSession.State.ACTIVE, session.state) + writeSampleData(realm) + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + if (progress.isTransferComplete) { + assertTransferComplete(progress, true) + allChangeUploaded.countDown() + } } + TestHelper.awaitOrFail(allChangeUploaded) } - TestHelper.awaitOrFail(allChangeUploaded) - realm.close() } @Test @@ -195,50 +188,50 @@ class ProgressListenerTests { val transferCompleted = AtomicInteger(0) val testDone = CountDownLatch(1) val config = createSyncConfig() - val realm = Realm.getInstance(config) - writeSampleData(realm) // Write first batch of sample data - val session: SyncSession = realm.syncSession - session.addUploadProgressListener(ProgressMode.INDEFINITELY) { progress -> - if (progress.isTransferComplete) { - when (transferCompleted.incrementAndGet()) { - 1 -> { - val realm = Realm.getInstance(config) - writeSampleData(realm) - realm.close() - } - 2 -> { - assertTransferComplete(progress, true) - testDone.countDown() + Realm.getInstance(config).use { realm -> + val session: SyncSession = realm.syncSession + session.addUploadProgressListener(ProgressMode.INDEFINITELY) { progress -> + if (progress.isTransferComplete) { + when (transferCompleted.incrementAndGet()) { + 1 -> { + Realm.getInstance(config).use { realm -> + writeSampleData(realm) + } + } + 2 -> { + assertTransferComplete(progress, true) + testDone.countDown() + } + else -> fail("Unsupported number of transfers completed: " + transferCompleted.get()) } - else -> fail("Unsupported number of transfers completed: " + transferCompleted.get()) } } + writeSampleData(realm) // Write first batch of sample data + TestHelper.awaitOrFail(testDone) } - TestHelper.awaitOrFail(testDone) - realm.close() } @Test fun addListenerInsideCallback() { val allChangeUploaded = CountDownLatch(1) val config = createSyncConfig() - val realm = Realm.getInstance(config) - writeSampleData(realm) - val session: SyncSession = realm.syncSession - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> - if (progress.isTransferComplete) { - val realm = Realm.getInstance(config) - writeSampleData(realm) - realm.close() - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> - if (progress.isTransferComplete) { - allChangeUploaded.countDown() + Realm.getInstance(config).use { realm -> + val session: SyncSession = realm.syncSession + writeSampleData(realm) + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + if (progress.isTransferComplete) { + Realm.getInstance(config).use { realm -> + writeSampleData(realm) + } + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + if (progress.isTransferComplete) { + allChangeUploaded.countDown() + } } } } + TestHelper.awaitOrFail(allChangeUploaded) } - TestHelper.awaitOrFail(allChangeUploaded) - realm.close() } @Test @@ -246,72 +239,100 @@ class ProgressListenerTests { val allChangeUploaded = CountDownLatch(3) val progressCompletedReported = AtomicBoolean(false) val config = createSyncConfig() - val realm = Realm.getInstance(config) - writeSampleData(realm) - val session: SyncSession = realm.syncSession - session.addUploadProgressListener(ProgressMode.INDEFINITELY) { progress -> - if (progress.isTransferComplete) { - allChangeUploaded.countDown() - if (progressCompletedReported.compareAndSet(false, true)) { - val realm = Realm.getInstance(config) - writeSampleData(realm) - realm.close() - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> - if (progress.isTransferComplete) { - allChangeUploaded.countDown() + Realm.getInstance(config).use { realm -> + val session: SyncSession = realm.syncSession + session.addUploadProgressListener(ProgressMode.INDEFINITELY) { progress -> + if (progress.isTransferComplete) { + allChangeUploaded.countDown() + if (progressCompletedReported.compareAndSet(false, true)) { + Realm.getInstance(config).use { realm -> + writeSampleData(realm) + } + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + if (progress.isTransferComplete) { + allChangeUploaded.countDown() + } } } } } + writeSampleData(realm) + TestHelper.awaitOrFail(allChangeUploaded) } - TestHelper.awaitOrFail(allChangeUploaded) - realm.close() } @Test fun addProgressListener_triggerImmediatelyWhenRegistered() { val config = createSyncConfig() - val realm = Realm.getInstance(config) - val session: SyncSession = realm.syncSession - checkListener(session, ProgressMode.INDEFINITELY) - checkListener(session, ProgressMode.CURRENT_CHANGES) - realm.close() + Realm.getInstance(config).use { realm -> + val session: SyncSession = realm.syncSession + checkDownloadListener(session, ProgressMode.INDEFINITELY) + checkUploadListener(session, ProgressMode.INDEFINITELY) + checkDownloadListener(session, ProgressMode.CURRENT_CHANGES) + checkUploadListener(session, ProgressMode.CURRENT_CHANGES) + } + } + + @Test + @Ignore("FIXME: Tracked by https://github.com/realm/realm-java/issues/6976") + fun addProgressListener_triggerImmediatelyWhenRegistered_waitForInitialRemoteData() { + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val config = SyncConfiguration.Builder(user, getTestPartitionValue()) + .waitForInitialRemoteData() + .modules(DefaultSyncSchema()) + .build() + Realm.getInstance(config).use { realm -> + val session: SyncSession = realm.syncSession + checkDownloadListener(session, ProgressMode.INDEFINITELY) + checkUploadListener(session, ProgressMode.INDEFINITELY) + checkDownloadListener(session, ProgressMode.CURRENT_CHANGES) + checkUploadListener(session, ProgressMode.CURRENT_CHANGES) + } } @Test fun uploadListener_keepIncreasingInSize() { val config = createSyncConfig() - val realm = Realm.getInstance(config) - val session: SyncSession = realm.syncSession - for (i in 0..9) { - val changesUploaded = CountDownLatch(1) - writeSampleData(realm) - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> - RealmLog.info("Test %s -> %s", Integer.toString(i), progress.toString()) - if (progress.isTransferComplete) { - assertTransferComplete(progress, true) - changesUploaded.countDown() + Realm.getInstance(config).use { realm -> + val session: SyncSession = realm.syncSession + for (i in 0..9) { + val changesUploaded = CountDownLatch(1) + writeSampleData(realm) + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES) { progress -> + if (progress.isTransferComplete) { + assertTransferComplete(progress, true) + changesUploaded.countDown() + } } + TestHelper.awaitOrFail(changesUploaded) } - TestHelper.awaitOrFail(changesUploaded) } - realm.close() } - private fun checkListener(session: SyncSession, progressMode: ProgressMode) { + private fun checkDownloadListener(session: SyncSession, progressMode: ProgressMode) { + val listenerCalled = CountDownLatch(1) + session.addDownloadProgressListener(progressMode) { progress -> + listenerCalled.countDown() + } + TestHelper.awaitOrFail(listenerCalled, 30) + } + private fun checkUploadListener(session: SyncSession, progressMode: ProgressMode) { val listenerCalled = CountDownLatch(1) - session.addDownloadProgressListener(progressMode) { listenerCalled.countDown() } - TestHelper.awaitOrFail(listenerCalled) + session.addUploadProgressListener(progressMode) { progress -> + listenerCalled.countDown() + } + TestHelper.awaitOrFail(listenerCalled, 30) } + private fun writeSampleData(realm: Realm, partitionValue: String = getTestPartitionValue()) { - realm.beginTransaction() - for (i in 0 until TEST_SIZE) { - val obj = SyncDog() - obj.name = "Object $i" - realm.insert(obj) + realm.executeTransaction { + for (i in 0 until TEST_SIZE) { + val obj = SyncDog() + obj.name = "Object $i" + realm.insert(obj) + } } - realm.commitTransaction() } private fun assertTransferComplete(progress: Progress, nonZeroChange: Boolean) { @@ -325,30 +346,29 @@ class ProgressListenerTests { // Create remote data for a given user. private fun createRemoteData(config: SyncConfiguration) { - val realm = Realm.getInstance(config) - val changesUploaded = CountDownLatch(1) - writeSampleData(realm) - val session: SyncSession = realm.syncSession - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, object : ProgressListener { - override fun onChange(progress: Progress) { - if (progress.isTransferComplete) { - session.removeProgressListener(this) - changesUploaded.countDown() + Realm.getInstance(config).use { realm -> + val changesUploaded = CountDownLatch(1) + val session: SyncSession = realm.syncSession + writeSampleData(realm) + session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, object : ProgressListener { + override fun onChange(progress: Progress) { + if (progress.isTransferComplete) { + session.removeProgressListener(this) + changesUploaded.countDown() + } } - } - }) - TestHelper.awaitOrFail(changesUploaded) - realm.close() + }) + TestHelper.awaitOrFail(changesUploaded) + } } private fun getStoreTestDataSize(config: RealmConfiguration): Long { - val realm: Realm = Realm.getInstance(config) - val objectCounts: Long = realm.where().count() - realm.close() - return objectCounts + Realm.getInstance(config).use { realm -> + return realm.where().count() + } } - private fun createSyncConfig(user: User = app.login(Credentials.anonymous()), partitionValue: String = getTestPartitionValue()): SyncConfiguration { + private fun createSyncConfig(user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456"), partitionValue: String = getTestPartitionValue()): SyncConfiguration { return SyncConfiguration.Builder(user, partitionValue) .modules(DefaultSyncSchema()) .build() diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 50328a105b..7487b96d3d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -259,13 +259,21 @@ public boolean isConnected() { return (sessionState == State.ACTIVE || sessionState == State.DYING) && connectionState == ConnectionState.CONNECTED; } + /** + * All progress listener events from native Sync are reported to this method. + */ + @SuppressWarnings("unused") synchronized void notifyProgressListener(long listenerId, long transferredBytes, long transferableBytes) { Pair listener = listenerIdToProgressListenerMap.get(listenerId); if (listener != null) { Progress newProgressNotification = new Progress(transferredBytes, transferableBytes); if (!newProgressNotification.equals(listener.second)) { listener.second = newProgressNotification; - listener.first.onChange(newProgressNotification); + try { + listener.first.onChange(newProgressNotification); + } catch (Exception exception) { + RealmLog.error(exception); + } } } else { RealmLog.debug("Trying unknown listener failed: " + listenerId); diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java deleted file mode 100644 index 9141922c52..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/ProgressListenerTests.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm.objectserver; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.net.URI; -import java.util.Locale; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.annotation.Nonnull; - -import io.realm.Progress; -import io.realm.ProgressListener; -import io.realm.ProgressMode; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.StandardIntegrationTest; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncUser; -import io.realm.TestHelper; -import io.realm.TestSyncConfigurationFactory; -import io.realm.entities.AllTypes; -import io.realm.log.RealmLog; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.UserFactory; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -public class ProgressListenerTests extends StandardIntegrationTest { - - private static final long TEST_SIZE = 10; - @Rule - public TestSyncConfigurationFactory configFactory = new TestSyncConfigurationFactory(); - - @Nonnull - private SyncConfiguration createSyncConfig() { - SyncUser user = UserFactory.createAdminUser(Constants.AUTH_URL); - return configFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL).build(); - } - - private void writeSampleData(Realm realm) { - realm.beginTransaction(); - for (int i = 0; i < TEST_SIZE; i++) { - AllTypes obj = realm.createObject(AllTypes.class); - obj.setColumnString("Object " + i); - } - realm.commitTransaction(); - } - - private void assertTransferComplete(Progress progress, boolean nonZeroChange) { - assertTrue(progress.isTransferComplete()); - assertEquals(1.0D, progress.getFractionTransferred(), 0.0D); - assertEquals(progress.getTransferableBytes(), progress.getTransferredBytes()); - if (nonZeroChange) { - assertTrue(progress.getTransferredBytes() > 0); - } - } - - // Create remote data for a given user. - private URI createRemoteData(final SyncConfiguration config) { - final Realm realm = Realm.getInstance(config); - final CountDownLatch changesUploaded = new CountDownLatch(1); - final SyncSession session = SyncManager.getSession(config); - final long beforeAdd = realm.where(AllTypes.class).count(); - writeSampleData(realm); - - final long threadId = Thread.currentThread().getId(); - - session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - // FIXME: This check is to make sure before this method returns, all the uploads has been done. - // See https://github.com/realm/realm-object-store/issues/581#issuecomment-339353832 - if (threadId == Thread.currentThread().getId()) { - return; - } - if (progress.isTransferComplete()) { - Realm realm = Realm.getInstance(config); - final long afterAdd = realm.where(AllTypes.class).count(); - realm.close(); - - RealmLog.warn(String.format(Locale.ENGLISH,"createRemoteData upload %d/%d objects count:%d", - progress.getTransferredBytes(), progress.getTransferableBytes(), afterAdd)); - // FIXME: Remove this after https://github.com/realm/realm-object-store/issues/581 - if (afterAdd == TEST_SIZE + beforeAdd) { - session.removeProgressListener(this); - changesUploaded.countDown(); - } else if (afterAdd < TEST_SIZE + beforeAdd) { - fail("The added objects are more than expected."); - } - } - } - }); - TestHelper.awaitOrFail(changesUploaded); - realm.close(); - return config.getServerUrl(); - } - - private long getStoreTestDataSize(RealmConfiguration config) { - Realm adminRealm = Realm.getInstance(config); - long objectCounts = adminRealm.where(AllTypes.class).count(); - adminRealm.close(); - - return objectCounts; - } - - @Test - public void downloadProgressListener_changesOnly() { - final CountDownLatch allChangesDownloaded = new CountDownLatch(1); - SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncConfiguration userWithDataConfig = configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM) - .build(); - URI serverUrl = createRemoteData(userWithDataConfig); - SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - - final SyncConfiguration config = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()) - .build(); - Realm realm = Realm.getInstance(config); - SyncSession session = SyncManager.getSession(config); - session.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - assertTransferComplete(progress, true); - assertEquals(TEST_SIZE, getStoreTestDataSize(config)); - allChangesDownloaded.countDown(); - } - } - }); - TestHelper.awaitOrFail(allChangesDownloaded); - realm.close(); - } - - @Test - public void downloadProgressListener_indefinitely() throws InterruptedException { - final AtomicInteger transferCompleted = new AtomicInteger(0); - final CountDownLatch allChangesDownloaded = new CountDownLatch(1); - final CountDownLatch startWorker = new CountDownLatch(1); - final SyncUser userWithData = UserFactory.createUniqueUser(Constants.AUTH_URL); - final SyncConfiguration userWithDataConfig = configFactory.createSyncConfigurationBuilder(userWithData, Constants.USER_REALM) - .name("remote") - .build(); - - URI serverUrl = createRemoteData(userWithDataConfig); - - // Create worker thread that puts data into another Realm. - // This is to avoid blocking one progress listener while waiting for another to complete. - Thread worker = new Thread(new Runnable() { - @Override - public void run() { - TestHelper.awaitOrFail(startWorker); - createRemoteData(userWithDataConfig); - } - }); - worker.start(); - - SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - final SyncConfiguration adminConfig = configFactory.createSyncConfigurationBuilder(adminUser, serverUrl.toString()) - .name("local") - .build(); - Realm adminRealm = Realm.getInstance(adminConfig); - SyncSession session = SyncManager.getSession(adminConfig); - session.addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - long objectCounts = getStoreTestDataSize(adminConfig); - // The downloading progress listener could be triggered at the db version where only contains the meta - // data. So we start checking from when the first 10 objects downloaded. - RealmLog.warn(String.format( - Locale.ENGLISH,"downloadProgressListener_indefinitely download %d/%d objects count:%d", - progress.getTransferredBytes(), progress.getTransferableBytes(), objectCounts)); - if (objectCounts != 0 && progress.isTransferComplete()) { - - switch (transferCompleted.incrementAndGet()) { - case 1: { - assertEquals(TEST_SIZE, objectCounts); - assertTransferComplete(progress, true); - startWorker.countDown(); - break; - } - case 2: { - assertTransferComplete(progress, true); - assertEquals(TEST_SIZE * 2, objectCounts); - allChangesDownloaded.countDown(); - break; - } - default: - fail("Transfer complete called too many times:" + transferCompleted.get()); - } - } - } - }); - TestHelper.awaitOrFail(allChangesDownloaded); - adminRealm.close(); - // worker thread will hang if logout happens before listener triggered. - worker.join(); - userWithData.logOut(); - adminUser.logOut(); - } - - // Make sure that a ProgressListener continues to report the correct thing, even if it crashed - @Test - public void uploadListener_worksEvenIfCrashed() throws InterruptedException { - final AtomicInteger transferCompleted = new AtomicInteger(0); - final CountDownLatch testDone = new CountDownLatch(1); - final SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - - writeSampleData(realm); // Write first batch of sample data - SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - switch(transferCompleted.incrementAndGet()) { - case 1: - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - realm.close(); - throw new RuntimeException("Crashing the changelistener"); - case 2: - assertTransferComplete(progress, true); - testDone.countDown(); - break; - default: - fail("Unsupported number of transfers completed: " + transferCompleted.get()); - } - } - } - }); - - TestHelper.awaitOrFail(testDone); - realm.close(); - } - - @Test - public void uploadProgressListener_changesOnly() { - final CountDownLatch allChangeUploaded = new CountDownLatch(1); - SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - - SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - assertTransferComplete(progress, true); - allChangeUploaded.countDown(); - } - } - }); - - TestHelper.awaitOrFail(allChangeUploaded); - realm.close(); - } - - @Test - public void uploadProgressListener_indefinitely() { - final AtomicInteger transferCompleted = new AtomicInteger(0); - final CountDownLatch testDone = new CountDownLatch(1); - final SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - - writeSampleData(realm); // Write first batch of sample data - SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - Realm tempRealm = Realm.getInstance(config); - long objectsCount = tempRealm.where(AllTypes.class).count(); - tempRealm.close(); - // FIXME: Remove the objectsCount checking when - // https://github.com/realm/realm-object-store/issues/581 gets fixed - if (objectsCount != 0 && progress.isTransferComplete()) { - switch(transferCompleted.incrementAndGet()) { - case 1: - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - realm.close(); - break; - case 2: - assertTransferComplete(progress, true); - testDone.countDown(); - break; - default: - fail("Unsupported number of transfers completed: " + transferCompleted.get()); - } - } - } - }); - - TestHelper.awaitOrFail(testDone); - realm.close(); - } - - @Test - public void addListenerInsideCallback() { - final CountDownLatch allChangeUploaded = new CountDownLatch(1); - final SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - - final SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - realm.close(); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - allChangeUploaded.countDown(); - } - } - }); - } - } - }); - - TestHelper.awaitOrFail(allChangeUploaded); - realm.close(); - } - - @Test - public void addListenerInsideCallback_mixProgressModes() { - final CountDownLatch allChangeUploaded = new CountDownLatch(3); - final AtomicBoolean progressCompletedReported = new AtomicBoolean(false); - final SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - - final SyncSession session = SyncManager.getSession(config); - session.addUploadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - allChangeUploaded.countDown(); - if (progressCompletedReported.compareAndSet(false, true)) { - Realm realm = Realm.getInstance(config); - writeSampleData(realm); - realm.close(); - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - allChangeUploaded.countDown(); - } - } - }); - } - } - } - }); - - TestHelper.awaitOrFail(allChangeUploaded); - realm.close(); - } - - @Test - public void addProgressListener_triggerImmediatelyWhenRegistered() { - final SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - SyncSession session = SyncManager.getSession(config); - - checkListener(session, ProgressMode.INDEFINITELY); - checkListener(session, ProgressMode.CURRENT_CHANGES); - - realm.close(); - } - - @Test - public void uploadListener_keepIncreasingInSize() { - SyncConfiguration config = createSyncConfig(); - Realm realm = Realm.getInstance(config); - SyncSession session = SyncManager.getSession(config); - for (int i = 0; i < 10; i++) { - final CountDownLatch changesUploaded = new CountDownLatch(1); - writeSampleData(realm); - final int testNo = i; - session.addUploadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - RealmLog.info("Test %s -> %s", Integer.toString(testNo), progress.toString()); - if (progress.isTransferComplete()) { - assertTransferComplete(progress, true); - changesUploaded.countDown(); - } - } - }); - TestHelper.awaitOrFail(changesUploaded); - } - - realm.close(); - } - - private void checkListener(SyncSession session, ProgressMode progressMode) { - final CountDownLatch listenerCalled = new CountDownLatch(1); - session.addDownloadProgressListener(progressMode, new ProgressListener() { - @Override - public void onChange(Progress progress) { - listenerCalled.countDown(); - } - }); - TestHelper.awaitOrFail(listenerCalled); - } - -} From 7639e8d4a4a8c4ed0a9bd448a082b4a895a49a15 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 30 Jun 2020 10:08:42 +0200 Subject: [PATCH 1599/2110] Release v10.0.0-BETA.6 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b27ddb8fd2..7c0faa4333 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.6-SNAPSHOT +10.0.0-BETA.6 \ No newline at end of file From 5ce31b50d58184f019bb2800bb1b7cac1e9f544d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 Jul 2020 08:40:57 +0200 Subject: [PATCH 1600/2110] Fix docs for Kotlin Extension (#6979) --- build.gradle | 18 ++++++++++++++++++ realm/kotlin-extensions/build.gradle | 10 +--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index f7bc47804c..ae5eb23c32 100644 --- a/build.gradle +++ b/build.gradle @@ -195,6 +195,24 @@ task javadoc(type:GradleBuild) { configure copyProperties } +task javadocPackage(type: Zip) { + description = 'Generate a Zip file with all SDK docs' + dependsOn javadoc + + group = 'Artifact' + archiveName = "realm-java-${currentVersion}-docs.zip" + destinationDir = file("${buildDir}/outputs/docs") + + from('realm/realm-library/build/docs/javadoc') { + include '**/*' + into 'javadoc' + } + from('realm/kotlin-extensions/build/docs') { + include '**/*' + into 'kotlindocs' + } +} + task sourcesJar(type:GradleBuild) { description = 'Generate the sources Jar for the Realm project' group = 'Docs' diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 32c026a658..fcee6a4164 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -108,17 +108,9 @@ task sourcesJar(type: Jar) { dokka { outputFormat = 'html' outputDirectory = "$buildDir/docs" - configuration { -// FIXME: -// externalDocumentationLink { -// noJdkLink = true -// noStdlibLink = true -// noAndroidSdkLink = true -// } - } } -task javadocJar(type: Jar/* FIXME: , dependsOn: dokka*/) { +task javadocJar(type: Jar, dependsOn: dokka) { classifier = 'javadoc' from "$buildDir/dokka" } From ffac90e7d8668555c9645e3b03ce801512d14c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 1 Jul 2020 09:02:58 +0200 Subject: [PATCH 1601/2110] Remove superflous FIXMEs (#6980) --- .../io/realm/processor/RealmProxyClassGenerator.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index cf82cf6c5b..9fb3e829a4 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -1063,7 +1063,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi when { Utils.isRealmModel(field) -> { - // FIXME: How to support types from other compilation units? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) emitEmptyLine() @@ -1086,7 +1085,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } Utils.isRealmModelList(field) -> { val genericType = Utils.getGenericTypeQualifiedName(field)!! - // FIXME: How to support types from other compilation units? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) emitEmptyLine() @@ -1181,7 +1179,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val getter = metadata.getInternalGetter(fieldName) if (Utils.isRealmModel(field)) { - // FIXME: How to support types from other compilation units? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) emitEmptyLine() @@ -1203,7 +1200,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endControlFlow() } else if (Utils.isRealmModelList(field)) { val genericType = Utils.getGenericTypeQualifiedName(field)!! - // FIXME: How to support types from other compilation units? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) emitEmptyLine() @@ -1287,7 +1283,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val getter = metadata.getInternalGetter(fieldName) if (Utils.isRealmModel(field)) { - // FIXME: How to support types from other compilation units? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) emitEmptyLine() @@ -1312,7 +1307,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endControlFlow() } else if (Utils.isRealmModelList(field)) { val genericType = Utils.getGenericTypeQualifiedName(field)!! - // FIXME: How to support types from other compilation units? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) emitEmptyLine() @@ -1429,7 +1423,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi when { Utils.isRealmModel(field) -> { - // FIXME: How to support types from other compilation units? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) emitEmptyLine() @@ -1455,7 +1448,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } Utils.isRealmModelList(field) -> { val genericType = Utils.getGenericTypeQualifiedName(field)!! - // FIXME: How to support types from other compilation units? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) emitEmptyLine() @@ -1656,7 +1648,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi when { Utils.isRealmModel(field) -> { - // FIXME: How to support Embedded objects defined in another compilation unit? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) val fieldColKey: String = fieldColKeyVariableReference(field) val linkedQualifiedClassName: QualifiedClassName = Utils.getFieldTypeQualifiedName(field) @@ -1691,7 +1682,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() } Utils.isRealmModelList(field) -> { - // FIXME: How to support Embedded objects defined in another compilation unit? val listElementType: QualifiedClassName = Utils.getRealmListType(field)!! val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(listElementType) val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!! @@ -1837,7 +1827,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi when { Utils.isRealmModel(field) -> { - // FIXME: How to support Embedded objects defined in another compilation unit? val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) emitEmptyLine() @@ -1876,7 +1865,6 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endControlFlow() } Utils.isRealmModelList(field) -> { - // FIXME: How to support Embedded objects defined in another compilation unit? val genericType: QualifiedClassName = Utils.getRealmListType(field)!! val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) val proxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) From b99fde2580a375a7aada00c758220bef0875423c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 1 Jul 2020 09:30:43 +0200 Subject: [PATCH 1602/2110] Fix file upgrade bugs (#6975) --- CHANGELOG.md | 21 +++++++++++++++++++ dependencies.list | 4 ++-- .../java/io/realm/RealmMigrationTests.java | 3 ++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8417ed52a..6a3b0098cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 7.0.1(YYYY-MN-DD) + +### Enhancements +* None. + +### Fixes +* Upgrading older Realm files with String indexes was very slow. (Issue [#6875](https://github.com/realm/realm-java/issues/6875), since 7.0.0) +* Aborting upgrading a Realm file could result in the file getting corrupted. (Isse [#6866](https://github.com/realm/realm-java/issues/6866), since 7.0.0) +* Automatic indexes on primary keys are now correctly stripped when upgrading the file as they are no longer needed. (Since 7.0.0) +* `NoSuchTable` was thrown after comitting a transaction. (Issue [#6947](https://github.com/realm/realm-java/issues/6947)) + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. + +### Internal +* Upgraded to Realm Sync 5.0.7. +* Upgraded to Realm Core 6.0.8. + + ## 7.0.0(2020-05-16) NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. diff --git a/dependencies.list b/dependencies.list index f80a113157..579eb34917 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.3 -REALM_SYNC_SHA256=bccf1fb89e32950a78dca4e93074f6479e0d016320897ab8bd1135d8568a18d3 +REALM_SYNC_VERSION=5.0.7 +REALM_SYNC_SHA256=239049c777e4275fe094c73ae6dfa8736ae5ca9aa821c8d58b6d5eb3d84415e0 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index f3de8e9439..8cf23c8fb7 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -1444,7 +1444,8 @@ public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException .schema(MigrationCore6PKStringIndexedByDefault.class) .build()); assertFalse(realm.isEmpty()); - assertTrue(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); + // Upgrading to Core 6 will strip all indexes on primary keys as they are no longer needed. + assertFalse(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); MigrationCore6PKStringIndexedByDefault first = realm.where(MigrationCore6PKStringIndexedByDefault.class).findFirst(); assertNotNull(first); assertEquals("Foo", first.name); From 0b6987cef8434359b08d04a986257981f60e6748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Lo=CC=81pez?= Date: Wed, 1 Jul 2020 15:27:40 +0200 Subject: [PATCH 1603/2110] update release date in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a3b0098cc..5eb52c9f52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 7.0.1(YYYY-MN-DD) +## 7.0.1(2020-07-01) ### Enhancements * None. From 0896160c85fc164ae6184d49e6af09a5e0a71316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Lo=CC=81pez?= Date: Wed, 1 Jul 2020 16:24:44 +0200 Subject: [PATCH 1604/2110] Release v7.0.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 44bad91b17..73a86b1970 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.1-SNAPSHOT \ No newline at end of file +7.0.1 \ No newline at end of file From d0e40e08ca124b8cb0de893436c6152fd9794cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Lo=CC=81pez?= Date: Wed, 1 Jul 2020 16:24:44 +0200 Subject: [PATCH 1605/2110] Prepare next release v7.0.2-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 73a86b1970..215d070d7b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.1 \ No newline at end of file +7.0.2-SNAPSHOT \ No newline at end of file From 8eb12b47c2223fa9c137423590c32465c85eb125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Wed, 1 Jul 2020 17:34:19 +0200 Subject: [PATCH 1606/2110] Implement a typed version of RealmAsyncTask (#6971) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added RealmAsyncResultTask and implementation and tests for the latter. This class exposes the blockingGet and get methods, that allow users to retrieve results from a task * Changed method names, added checks for illegal arguments and looper thread, plus added documentation * Added nonnullbydefault and modified tests accordingly, updated docs. * Changed wrong names in test functions, added test for cancelling tasks * Added guard to avoid callback if task has been marked as cancelled Co-authored-by: Eduardo López --- .../internal/async/RealmResultTaskImplTest.kt | 174 ++++++++++++++++++ .../internal/async/RealmResultTaskImpl.java | 163 ++++++++++++++++ .../io/realm/internal/async/package-info.java | 18 ++ .../io/realm/mongodb/RealmResultTask.java | 48 +++++ 4 files changed, 403 insertions(+) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmResultTaskImplTest.kt create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmResultTaskImpl.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/async/package-info.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmResultTask.java diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmResultTaskImplTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmResultTaskImplTest.kt new file mode 100644 index 0000000000..687abbd9d8 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmResultTaskImplTest.kt @@ -0,0 +1,174 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.async + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.TestHelper +import io.realm.mongodb.App +import io.realm.mongodb.AppException +import io.realm.mongodb.RealmResultTask +import io.realm.rule.BlockingLooperThread +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.atomic.AtomicReference +import kotlin.test.* + +private const val OUTPUT = 42 +private const val EXCEPTION_REASON = "BOOM" + +@RunWith(AndroidJUnit4::class) +class RealmResultTaskImplTest { + + private val looperThread = BlockingLooperThread() + private val service: ThreadPoolExecutor = App.NETWORK_POOL_EXECUTOR + + @Test + fun constructor_throwsOnNullArgs() { + assertFailsWith { + RealmResultTaskImpl(TestHelper.getNull(), object : RealmResultTaskImpl.Executor() { + override fun run(): String { + return "something" + } + }) + } + + assertFailsWith { + RealmResultTaskImpl(service, TestHelper.getNull()) + } + } + + @Test + fun get() = RealmResultTaskImpl( + service, + object : RealmResultTaskImpl.Executor() { + override fun run(): Int { + return OUTPUT + } + } + ).let { task -> assertEquals(OUTPUT, task.get()) } + + @Test + fun get_fails() { + val task: RealmResultTask = RealmResultTaskImpl( + service, + object : RealmResultTaskImpl.Executor() { + override fun run(): String { + throw RuntimeException(EXCEPTION_REASON) + } + } + ) + assertFailsWith { + task.get() + }.let { + assertTrue(it.message!!.contains(EXCEPTION_REASON)) + } + } + + @Test + fun getAsync_success() = looperThread.runBlocking { + val task: RealmResultTask = RealmResultTaskImpl( + service, + object : RealmResultTaskImpl.Executor() { + override fun run(): Int { + return OUTPUT + } + } + ) + + task.getAsync { result -> + assertEquals(OUTPUT, result.get()) + looperThread.testComplete() + } + } + + @Test + fun getAsync_returnsError() = looperThread.runBlocking { + val task: RealmResultTask = RealmResultTaskImpl( + service, + object : RealmResultTaskImpl.Executor() { + override fun run(): String { + throw RuntimeException(EXCEPTION_REASON) + } + } + ) + + task.getAsync { result -> + assertNull(result.get()) + assertNotNull(result.error) + assertEquals(AppException::class.java, result.error::class.java) + result.error.exception!!.let { exception -> + assertEquals(exception::class.java, RuntimeException::class.java) + assertTrue(exception.message.equals(EXCEPTION_REASON)) + } + looperThread.testComplete() + } + } + + @Test + fun getAsync_throwsDueToNoLooper() { + val task: RealmResultTask = RealmResultTaskImpl( + service, + object : RealmResultTaskImpl.Executor() { + override fun run(): String { + fail("Should fail before returning anything") + } + } + ) + assertFailsWith { + task.getAsync { + fail("Should never reach this callback") + } + } + } + + @Test + fun cancel() { + val taskReference = AtomicReference>() + val finishRunnable = Runnable { + looperThread.testComplete() + } + val task: RealmResultTask = RealmResultTaskImpl( + service, + object : RealmResultTaskImpl.Executor() { + override fun run(): String? { + // Ensure we cancel before returning a result + BlockingLooperThread().runBlocking { + taskReference.get().let { + assertNotNull(it) + assertFalse(it.isCancelled) + + it.cancel() + + looperThread.postRunnable(finishRunnable) + } + } + fail("Should fail before returning anything") + } + } + ) + taskReference.set(task) + + looperThread.runBlocking { + task.getAsync { + fail("Should never reach this callback") + } + } + + assertTrue(task.isCancelled) + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmResultTaskImpl.java b/realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmResultTaskImpl.java new file mode 100644 index 0000000000..20b02560b0 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmResultTaskImpl.java @@ -0,0 +1,163 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.async; + +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; + +import javax.annotation.Nullable; + +import io.realm.internal.RealmNotifier; +import io.realm.internal.Util; +import io.realm.internal.android.AndroidCapabilities; +import io.realm.internal.android.AndroidRealmNotifier; +import io.realm.log.RealmLog; +import io.realm.mongodb.App; +import io.realm.mongodb.AppException; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.RealmResultTask; + +/** + * Implementation of RealmResultTask used internally by MongoDB Realm APIs. Implementation is + * separate from the interface so that we can hide the constructor from end users. + * + * @param the result type delivered by this task. + */ +public class RealmResultTaskImpl implements RealmResultTask { + + private Future pendingTask; + private volatile boolean isCancelled = false; + private final ThreadPoolExecutor service; + private Executor executor; + + /** + * Constructor for RealmResultTaskImpl. + * + * @param service pool thread service on which the task will be executed. + * @param executor the code block executed by the task. + */ + public RealmResultTaskImpl(ThreadPoolExecutor service, Executor executor) { + Util.checkNull(service, "service"); + this.service = service; + Util.checkNull(executor, "executor"); + this.executor = executor; + } + + /** + * {@inheritDoc} + */ + @Override + public void cancel() { + if (pendingTask != null) { + pendingTask.cancel(true); + isCancelled = true; + + // From "Java Threads": By Scott Oaks & Henry Wong + // cancelled tasks are never executed, but may + // accumulate in work queues, which may causes a memory leak + // if the task hold references (to an enclosing class for example) + // we can use purge() but one caveat applies: if a second thread attempts to add + // something to the pool (using the execute() method) at the same time the + // first thread is attempting to purge the queue the attempt to purge + // the queue fails and the cancelled object remain in the queue. + // A better way to cancel objects with thread pools is to use the remove() + service.getQueue().remove(pendingTask); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + return isCancelled; + } + + @Override + public T get() { + return executor.run(); + } + + @Override + public void getAsync(App.Callback callback) { + Util.checkNull(callback, "callback"); + Util.checkLooperThread("RealmResultTaskImpl can only run on looper threads."); + + RealmNotifier handler = new AndroidRealmNotifier(null, new AndroidCapabilities()); + + pendingTask = service.submit(new Runnable() { + @Override + public void run() { + try { + postSuccess(handler, executor.run(), callback); + } catch (AppException e) { + postError(handler, e, callback); + } catch (Throwable e) { + postError(handler, new AppException(ErrorCode.UNKNOWN, "Unexpected error", e), callback); + } + } + }); + } + + private void postError(RealmNotifier handler, + final AppException error, + App.Callback callback) { + boolean errorHandled; + Runnable action = new Runnable() { + @Override + public void run() { + if (!isCancelled) { + callback.onResult(App.Result.withError(error)); + } + } + }; + errorHandled = handler.post(action); + + if (!errorHandled) { + RealmLog.error(error, "An error was thrown, but could not be posted: \n" + error.toString()); + } + } + + private void postSuccess(RealmNotifier handler, + @Nullable final T result, + App.Callback callback) { + handler.post(new Runnable() { + @Override + public void run() { + if (!isCancelled) { + callback.onResult((result == null) ? App.Result.success() : App.Result.withResult(result)); + } + } + }); + } + + /** + * The Executor class represent the portion of code the RealmResultTaskImpl will execute. + * + * @param the result type delivered by the task. + */ + public abstract static class Executor { + + /** + * Executes the code block. + * + * @return the result yielded by the task. + */ + @Nullable + public abstract T run(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/async/package-info.java b/realm/realm-library/src/objectServer/java/io/realm/internal/async/package-info.java new file mode 100644 index 0000000000..1933541aca --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/async/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +@javax.annotation.ParametersAreNonnullByDefault +package io.realm.internal.async; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmResultTask.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmResultTask.java new file mode 100644 index 0000000000..657d3f09b6 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmResultTask.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import io.realm.RealmAsyncTask; + +/** + * The RealmResultTask is a specific version of {@link RealmAsyncTask} that provides a mechanism + * to work with asynchronous operations carried out against MongoDB Realm that yield a result. + *

              + * This class offers both blocking ({@code get}) and non-blocking ({@code getAsync}) method calls. + * + * @param the result type delivered by this task. + */ +public interface RealmResultTask extends RealmAsyncTask { + + /** + * Blocks the thread on which the call is made until the result of the operation arrives. + * + * @return the result of the operation executed by this task. + */ + T get(); + + /** + * Provides a way to subscribe to asynchronous operations via a callback, which handles both + * results and errors. + * + * @param callback the {@link App.Callback} designed to receive results. + * @throws IllegalStateException if called from a thread without a {@link android.os.Looper} or + * from an {@link android.app.IntentService} thread. + */ + void getAsync(App.Callback callback); +} + From 66493c899986d6ebc55321a28e80d8b87b84963d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 2 Jul 2020 14:42:42 +0200 Subject: [PATCH 1607/2110] Migrating sync integration auth tests (#6957) --- .../kotlin/io/realm/UserTests.kt | 71 +- .../kotlin/io/realm/admin/ServerAdmin.kt | 10 + .../mongodb/sync/SyncConfigurationTests.kt | 13 + .../java/io/realm/objectserver/AuthTests.java | 902 ------------------ .../kotlin/io/realm/SyncSessionTests.kt | 43 + 5 files changed, 136 insertions(+), 903 deletions(-) delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt index 7384f175ab..fd08152399 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt @@ -93,9 +93,13 @@ class UserTests { // Users registered with Email/Password will register as Logged Out val user2: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(user2, app.currentUser()) + val current: User = app.currentUser()!! + assertEquals(user2, current) user2.logOut() assertEquals(User.State.LOGGED_OUT, user2.state) + // Same effect on all instances + assertEquals(User.State.LOGGED_OUT, current.state) + // And no current user anymore assertNull(app.currentUser()) } @@ -112,6 +116,50 @@ class UserTests { } } + @Test + fun logOutUserInstanceImpactsCurrentUser() { + val currentUser = app.currentUser()!! + assertEquals(User.State.LOGGED_IN, currentUser.state) + assertEquals(User.State.LOGGED_IN, anonUser.state) + assertEquals(currentUser, anonUser) + + anonUser!!.logOut() + + assertNotEquals(User.State.LOGGED_OUT, currentUser.state) + assertNotEquals(User.State.LOGGED_OUT, anonUser.state) + assertNull(app.currentUser()) + } + + @Test + fun logOutCurrentUserImpactsOtherInstances() { + val currentUser = app.currentUser()!! + assertEquals(User.State.LOGGED_IN, currentUser.state) + assertEquals(User.State.LOGGED_IN, anonUser.state) + assertEquals(currentUser, anonUser) + + currentUser!!.logOut() + + assertNotEquals(User.State.LOGGED_OUT, currentUser.state) + assertNotEquals(User.State.LOGGED_OUT, anonUser.state) + assertNull(app.currentUser()) + } + + @Test + fun repeatedLogInAndOut() { + val password = "123456" + val initialUser = app.registerUserAndLogin(TestHelper.getRandomEmail(), password) + assertEquals(User.State.LOGGED_IN, initialUser.state) + initialUser.logOut() + assertEquals(User.State.LOGGED_OUT, initialUser.state) + + repeat(3) { + val user = app.login(Credentials.emailPassword(initialUser.email, password)) + assertEquals(User.State.LOGGED_IN, user.state) + user.logOut() + assertEquals(User.State.LOGGED_OUT, user.state) + } + } + @Test fun logOutAsync_throwsOnNonLooperThread() { val user: User = app.login(Credentials.anonymous()) @@ -300,6 +348,27 @@ class UserTests { @Ignore("Not implemented yet") fun refreshToken() { } + @Test + fun revokedRefreshTokenIsNotSameAfterLogin() = looperThread.runBlocking { + val password = "password" + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), password) + val refreshToken = user.refreshToken + + app.addAuthenticationListener(object: AuthenticationListener { + override fun loggedIn(user: User) { } + + override fun loggedOut(loggerOutUser: User) { + app.loginAsync(Credentials.emailPassword(loggerOutUser.email, password)) { + val loggedInUser = it.orThrow + assertTrue(loggerOutUser !== loggedInUser) + assertNotEquals(refreshToken, loggedInUser.refreshToken) + looperThread.testComplete() + } + } + }) + user.logOut() + } + // FIXME @Ignore("Not implemented yet") fun isLoggedIn() { } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt index 76bd210b1f..a22f9ecf7d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt @@ -2,6 +2,7 @@ package io.realm.admin import io.realm.log.LogLevel import io.realm.log.RealmLog +import io.realm.mongodb.User import okhttp3.* import okio.Buffer import org.json.JSONArray @@ -105,6 +106,15 @@ class ServerAdmin { executeRequest(request) } + val JSON = MediaType.parse("application/json; charset=utf-8") + + fun disableUser(user: User) { + var request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/users/${user.id}/disable") + .put(RequestBody.create(json, "")) + executeRequest(request, true) + } + /** * Deletes all currently registered and pending users on MongoDB Realm. */ diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index 81b299e267..6ca83497a0 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -28,6 +28,7 @@ import io.realm.mongodb.User import io.realm.mongodb.close import io.realm.mongodb.registerUserAndLogin import org.bson.BsonString +import org.bson.types.ObjectId import org.junit.* import org.junit.runner.RunWith import kotlin.test.* @@ -281,4 +282,16 @@ class SyncConfigurationTests { } } + @Test + fun loggedOutUsersThrows() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + user.logOut() + assertFailsWith { + SyncConfiguration.defaultConfig(user, ObjectId()) + } + assertFailsWith { + SyncConfiguration.defaultConfig(app.currentUser(), ObjectId()) + } + } + } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java deleted file mode 100644 index c240f25994..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/objectserver/AuthTests.java +++ /dev/null @@ -1,902 +0,0 @@ -package io.realm.objectserver; - -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.lang.reflect.Field; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.realm.AuthenticationListener; -import io.realm.ErrorCode; -import io.realm.ObjectServerError; -import io.realm.Realm; -import io.realm.RealmConfiguration; -import io.realm.StandardIntegrationTest; -import io.realm.SyncConfiguration; -import io.realm.SyncManager; -import io.realm.SyncSession; -import io.realm.SyncTestUtils; -import io.realm.SyncUser; -import io.realm.SyncUserInfo; -import io.realm.TestHelper; -import io.realm.entities.StringOnly; -import io.realm.internal.Util; -import io.realm.internal.async.RealmAsyncTaskImpl; -import io.realm.internal.objectserver.Token; -import io.realm.objectserver.utils.Constants; -import io.realm.objectserver.utils.StringOnlyModule; -import io.realm.objectserver.utils.UserFactory; -import io.realm.rule.RunTestInLooperThread; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - - -@RunWith(AndroidJUnit4.class) -public class AuthTests extends StandardIntegrationTest { - - @Test - public void login_userNotExist() { - SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); - try { - SyncUser.logIn(credentials, Constants.AUTH_URL); - fail(); - } catch (ObjectServerError expected) { - assertEquals(ErrorCode.INVALID_CREDENTIALS, expected.getErrorCode()); - } - } - - @Test - @RunTestInLooperThread - public void loginAsync_userNotExist() { - SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); - SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.INVALID_CREDENTIALS, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread - public void login_newUser() { - String userId = UUID.randomUUID().toString(); - SyncCredentials credentials = SyncCredentials.usernamePassword(userId, "password", true); - SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - assertFalse(user.isAdmin()); - try { - assertEquals(new URL(Constants.AUTH_URL), user.getAuthenticationUrl()); - } catch (MalformedURLException e) { - fail(e.toString()); - } - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread - public void login_withAccessToken() { - SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncCredentials credentials = SyncCredentials.accessToken(SyncTestUtils.getRefreshToken(adminUser).value(), "custom-admin-user", adminUser.isAdmin()); - SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - assertTrue(user.isAdmin()); - final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .errorHandler((session, error) -> fail("Session failed: " + error)) - .build(); - - final Realm realm = Realm.getInstance(config); - looperThread.closeAfterTest(realm); - assertTrue(config.getUser().isValid()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail("Login failed: " + error); - } - }); - } - - @Test - @RunTestInLooperThread - public void login_withAnonymous() { - SyncCredentials credentials = SyncCredentials.anonymous(); - SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - assertFalse(user.isAdmin()); - final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .errorHandler((session, error) -> fail("Session failed: " + error)) - .build(); - - final Realm realm = Realm.getInstance(config); - looperThread.closeAfterTest(realm); - assertFalse(Util.isEmptyString(config.getUser().getIdentity())); - assertTrue(config.getUser().isValid()); - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail("Login failed: " + error); - } - }); - } - - @Test - public void loginAsync_errorHandlerThrows() throws InterruptedException { - final AtomicBoolean errorThrown = new AtomicBoolean(false); - - // Create custom Looper thread to be able to check for errors thrown when processing Looper events. - Thread t = new Thread(new Runnable() { - private volatile Handler handler; - @Override - public void run() { - Looper.prepare(); - try { - handler = new Handler(); - handler.post(new Runnable() { - @Override - public void run() { - SyncCredentials credentials = SyncCredentials.usernamePassword("IWantToHackYou", "GeneralPassword", false); - SyncUser.logInAsync(credentials, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.INVALID_CREDENTIALS, error.getErrorCode()); - throw new IllegalArgumentException("BOOM"); - } - }); - } - }); - Looper.loop(); // - } catch (IllegalArgumentException e) { - errorThrown.set(true); - } - } - }); - t.start(); - t.join(TimeUnit.SECONDS.toMillis(10)); - assertTrue(errorThrown.get()); - } - - @Test - public void changePassword() { - String username = UUID.randomUUID().toString(); - String originalPassword = "password"; - SyncCredentials credentials = SyncCredentials.usernamePassword(username, originalPassword, true); - SyncUser userOld = SyncUser.logIn(credentials, Constants.AUTH_URL); - assertTrue(userOld.isValid()); - - // Change password and try to log in with new password - String newPassword = "new-password"; - userOld.changePassword(newPassword); - userOld.logOut(); - - // Make sure old password doesn't work - try { - SyncUser.logIn(SyncCredentials.usernamePassword(username, originalPassword, false), Constants.AUTH_URL); - fail(); - } catch (ObjectServerError e) { - assertEquals(ErrorCode.INVALID_CREDENTIALS, e.getErrorCode()); - } - - // Then login with new password - credentials = SyncCredentials.usernamePassword(username, newPassword, false); - SyncUser userNew = SyncUser.logIn(credentials, Constants.AUTH_URL); - assertTrue(userNew.isValid()); - assertEquals(userOld.getIdentity(), userNew.getIdentity()); - } - - @Test - public void changePassword_using_admin() { - String username = UUID.randomUUID().toString(); - String originalPassword = "password"; - SyncCredentials credentials = SyncCredentials.usernamePassword(username, originalPassword, true); - SyncUser userOld = SyncUser.logIn(credentials, Constants.AUTH_URL); - assertTrue(userOld.isValid()); - - // Login an admin user - SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - assertTrue(adminUser.isValid()); - assertTrue(adminUser.isAdmin()); - - // Change password using admin user - String newPassword = "new-password"; - adminUser.changePassword(userOld.getIdentity(), newPassword); - - // Try to log in with new password - userOld.logOut(); - credentials = SyncCredentials.usernamePassword(username, newPassword, false); - SyncUser userNew = SyncUser.logIn(credentials, Constants.AUTH_URL); - - assertTrue(userNew.isValid()); - assertEquals(userOld.getIdentity(), userNew.getIdentity()); - } - - @Test - @RunTestInLooperThread - public void changePassword_using_admin_async() { - final String username = UUID.randomUUID().toString(); - final String originalPassword = "password"; - final SyncCredentials credentials = SyncCredentials.usernamePassword(username, originalPassword, true); - final SyncUser userOld = SyncUser.logIn(credentials, Constants.AUTH_URL); - assertTrue(userOld.isValid()); - - // Login an admin user - final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - assertTrue(adminUser.isValid()); - assertTrue(adminUser.isAdmin()); - - // Change password using admin user - final String newPassword = "new-password"; - adminUser.changePasswordAsync(userOld.getIdentity(), newPassword, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser administratorUser) { - assertEquals(adminUser, administratorUser); - - // Try to log in with new password - userOld.logOut(); - SyncCredentials credentials = SyncCredentials.usernamePassword(username, newPassword, false); - SyncUser userNew = SyncUser.logIn(credentials, Constants.AUTH_URL); - - assertTrue(userNew.isValid()); - assertEquals(userOld.getIdentity(), userNew.getIdentity()); - - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.getErrorMessage()); - } - }); - } - - @Test - @RunTestInLooperThread - public void changePassword_throwWhenUserIsLoggedOut() { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncManager.addAuthenticationListener(new AuthenticationListener() { - @Override - public void loggedIn(SyncUser user) { - SyncManager.removeAuthenticationListener(this); - // callback is happening on different thread, all assertions needs to be done on looper thread - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - fail("loggedIn should not be invoked"); - } - }); - } - - @Override - public void loggedOut(SyncUser user) { - SyncManager.removeAuthenticationListener(this); - try { - user.changePassword("new-password"); - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - fail("changePassword should throw ObjectServerError (INVALID CREDENTIALS)"); - } - }); - } catch (ObjectServerError expected) { - } - looperThread.testComplete(); - } - }); - user.logOut(); - } - - @Test - public void cachedInstanceShouldNotThrowIfRefreshTokenExpires() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - - SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = spy(SyncUser.logIn(credentials, Constants.AUTH_URL)); - - when(user.isValid()).thenReturn(true, true, false); - - final RealmConfiguration configuration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); - Realm realm = Realm.getInstance(configuration); - - assertFalse(user.isValid()); - verify(user, times(3)).isValid(); - - final CountDownLatch backgroundThread = new CountDownLatch(1); - // Should not throw when using an expired refresh_token form a different thread - // It should be able to open a Realm with an expired token - new Thread() { - @Override - public void run() { - Realm instance = Realm.getInstance(configuration); - instance.close(); - backgroundThread.countDown(); - } - }.start(); - - backgroundThread.await(); - - // It should be possible to open a cached Realm with expired token - Realm cachedInstance = Realm.getInstance(configuration); - assertNotNull(cachedInstance); - - realm.close(); - cachedInstance.close(); - user.logOut(); - } - - @Test - public void buildingSyncConfigurationShouldThrowIfInvalidUser() { - String username = UUID.randomUUID().toString(); - String password = "password"; - - SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncUser currentUser = SyncUser.current(); - user.logOut(); - - assertFalse(user.isValid()); - - try { - // We should not be able to build a configuration with an invalid/logged out user - configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); - fail("Invalid user, it should not be possible to create a SyncConfiguration"); - } catch (IllegalStateException expected) { - // User not authenticated or authentication expired. - } - - try { - // We should not be able to build a configuration with an invalid/logged out user - configurationFactory.createSyncConfigurationBuilder(currentUser, Constants.USER_REALM).build(); - fail("Invalid currentUser, it should not be possible to create a SyncConfiguration"); - } catch (IllegalStateException expected) { - // User not authenticated or authentication expired. - } - } - - // using a logout user should not throw - @Test - public void usingConfigurationWithInvalidUserShouldThrow() { - String username = UUID.randomUUID().toString(); - String password = "password"; - - SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - RealmConfiguration configuration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM).build(); - user.logOut(); - assertFalse(user.isValid()); - Realm instance = Realm.getInstance(configuration); - instance.close(); - } - - @Test - public void logout_currentUserMoreThanOne() { - UserFactory.createUniqueUser(Constants.AUTH_URL); - SyncUser.current().logOut(); - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - assertEquals(user, SyncUser.current()); - } - - // logging out 'user' should have the same impact on other instance(s) of the same user - @Test - public void loggingOutUserShouldImpactOtherInstances() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - - SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncUser currentUser = SyncUser.current(); - - assertTrue(user.isValid()); - assertEquals(user, currentUser); - - user.logOut(); - - assertFalse(user.isValid()); - assertFalse(currentUser.isValid()); - } - - // logging out 'current' should have the same impact on other instance(s) of the user - @Test - public void loggingOutCurrentUserShouldImpactOtherInstances() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - - SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncUser currentUser = SyncUser.current(); - - assertTrue(user.isValid()); - assertEquals(user, currentUser); - - SyncUser.current().logOut(); - - assertFalse(user.isValid()); - assertFalse(currentUser.isValid()); - assertNull(SyncUser.current()); - } - - // verify that multiple users can be logged in at the same time - @Test - public void multipleUsersCanBeLoggedInSimultaneously() { - final String password = "password"; - final SyncUser[] users = new SyncUser[3]; - - for (int i = 0; i < users.length; i++) { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), password, - true); - users[i] = SyncUser.logIn(credentials, Constants.AUTH_URL); - } - - for (int i = 0; i < users.length; i++) { - assertTrue(users[i].isValid()); - } - - for (int i = 0; i < users.length; i++) { - users[i].logOut(); - } - - for (int i = 0; i < users.length; i++) { - assertFalse(users[i].isValid()); - } - } - - // verify that a single user can be logged out and back in. - @Test - public void singleUserCanBeLoggedInAndOutRepeatedly() { - final String username = UUID.randomUUID().toString(); - final String password = "password"; - - // register the user the first time - SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - assertTrue(user.isValid()); - user.logOut(); - assertFalse(user.isValid()); - - // on subsequent logins, the user is already registered. - credentials = credentials = SyncCredentials.usernamePassword(username, password, false); - for (int i = 0; i < 3; i++) { - user = SyncUser.logIn(credentials, Constants.AUTH_URL); - assertTrue(user.isValid()); - user.logOut(); - assertFalse(user.isValid()); - } - } - - @Test - public void revokedRefreshTokenIsNotSameAfterLogin() throws InterruptedException { - final CountDownLatch userLoggedInAgain = new CountDownLatch(1); - final String uniqueName = UUID.randomUUID().toString(); - - final SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final Token revokedRefreshToken = SyncTestUtils.getRefreshToken(user); - - SyncManager.addAuthenticationListener(new AuthenticationListener() { - @Override - public void loggedIn(SyncUser user) { - - } - - @Override - public void loggedOut(SyncUser user) { - SyncCredentials credentials = SyncCredentials.usernamePassword(uniqueName, "password", false); - SyncUser loggedInUser = SyncUser.logIn(credentials, Constants.AUTH_URL); - - Token token = SyncTestUtils.getRefreshToken(loggedInUser); - // still comparing the same user - assertEquals(revokedRefreshToken.identity(), token.identity()); - - // different tokens - assertNotEquals(revokedRefreshToken.value(), token.value()); - SyncManager.removeAuthenticationListener(this); - userLoggedInAgain.countDown(); - } - }); - - user.logOut(); - TestHelper.awaitOrFail(userLoggedInAgain); - } - - // The pre-emptive token refresh subsystem should function, and properly refresh the access token. - // WARNING: this test can fail if there's a difference between the server's and device's clock, causing the - // refresh access token to be too far in time. - @Ignore("Test still times out https://github.com/realm/realm-java/issues/5681") - @Test(timeout = 30000) - public void preemptiveTokenRefresh() throws NoSuchFieldException, IllegalAccessException, InterruptedException { - SyncUser user = UserFactory.createUniqueUser(Constants.AUTH_URL); - - // make the access tokens map accessible - Field realmsField = SyncUser.class.getDeclaredField("realms"); - realmsField.setAccessible(true); - @SuppressWarnings("unchecked") // using reflection - Map accessTokens = (Map) realmsField.get(user); - - final SyncConfiguration syncConfiguration = configurationFactory - .createSyncConfigurationBuilder(user, Constants.SYNC_SERVER_URL) - .modules(new StringOnlyModule()) - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - fail(error.getErrorMessage()); - } - }) - .build(); - Realm realm = Realm.getInstance(syncConfiguration); - - // create and wait for a transaction to be uploaded, - // this guarantees that an accessToken is available - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - realm.createObject(StringOnly.class).setChars("1"); - } - }); - SyncSession session = SyncManager.getSession(syncConfiguration); - session.uploadAllLocalChanges(); - - assertFalse(accessTokens.isEmpty()); - Assert.assertEquals(1, accessTokens.size()); - Map.Entry entry = accessTokens.entrySet().iterator().next(); - Assert.assertEquals(syncConfiguration, entry.getKey()); - - final Token accessToken = entry.getValue(); - Assert.assertNotNull(accessToken); - // getting refresh token delay - Field refreshTokenTaskField = SyncSession.class.getDeclaredField("refreshTokenTask"); - refreshTokenTaskField.setAccessible(true); - RealmAsyncTaskImpl task = (RealmAsyncTaskImpl) refreshTokenTaskField.get(session); - Field pendingTaskField = RealmAsyncTaskImpl.class.getDeclaredField("pendingTask"); - pendingTaskField.setAccessible(true); - ScheduledFuture pendingTask = (ScheduledFuture) pendingTaskField.get(task); - long nextRefreshTokenRefreshQueryDelay = pendingTask.getDelay(TimeUnit.MILLISECONDS); - - // current configuration 'realm-java/tools/sync_test_server/configuration.yml' - // is setting the access token to expire every 20 seconds 'access_token: 20' - // we wait approximately actually 10 seconds since the SyncSession.REFRESH_MARGIN_DELAY is 10s - SystemClock.sleep(nextRefreshTokenRefreshQueryDelay); - - // allow 3 seconds for the query to perform and complete - SystemClock.sleep(TimeUnit.SECONDS.toMillis(3)); - - Token newAccessToken = accessTokens.get(syncConfiguration); - assertThat("new Token expires after the old one", newAccessToken.expiresMs(), greaterThan(accessToken.expiresMs())); - assertNotEquals(accessToken, newAccessToken); - - // refresh_token identity is the same - assertEquals(SyncTestUtils.getRefreshToken(user).identity(), newAccessToken.identity()); - assertEquals(accessToken.identity(), newAccessToken.identity()); - - realm.close(); - } - - @Test - public void retrieve() { - final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - - final String username = UUID.randomUUID().toString(); - final String password = "password"; - final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - assertTrue(user.isValid()); - - String identity = user.getIdentity(); - - SyncUserInfo userInfo = adminUser.retrieveInfoForUser(username, SyncCredentials.IdentityProvider.USERNAME_PASSWORD); - - assertNotNull(userInfo); - assertEquals(identity, userInfo.getIdentity()); - assertFalse(userInfo.isAdmin()); - assertTrue(userInfo.getMetadata().isEmpty()); - assertEquals(username, userInfo.getAccounts().get(SyncCredentials.IdentityProvider.USERNAME_PASSWORD)); - } - - - // retrieving a logged out user - @Test - @RunTestInLooperThread - public void retrieve_logout() { - final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - - final String username = UUID.randomUUID().toString(); - final String password = "password"; - final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final String identity = user.getIdentity(); - - // unless the refresh_token is revoked (via logout) the admin user can still retrieve the user - // we make sure the token is revoked before trying to retrieve the user - SyncManager.addAuthenticationListener(new AuthenticationListener() { - @Override - public void loggedIn(SyncUser user) { - SyncManager.removeAuthenticationListener(this); - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - fail("loggedIn should not be invoked"); - } - }); - } - - @Override - public void loggedOut(final SyncUser user) { - SyncManager.removeAuthenticationListener(this); - looperThread.postRunnable(new Runnable() { - @Override - public void run() { - assertFalse(user.isValid()); - SyncUserInfo userInfo = adminUser.retrieveInfoForUser(username, SyncCredentials.IdentityProvider.USERNAME_PASSWORD); - - assertNotNull(userInfo); - assertEquals(identity, userInfo.getIdentity()); - assertFalse(userInfo.isAdmin()); - assertTrue(userInfo.getMetadata().isEmpty()); - assertEquals(username, userInfo.getAccounts().get(SyncCredentials.IdentityProvider.USERNAME_PASSWORD)); - - looperThread.testComplete(); - } - }); - - } - }); - user.logOut(); - } - - @Test - public void retrieve_unknownProviderId() { - final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - SyncUserInfo userInfo = adminUser.retrieveInfoForUser("doesNotExist", SyncCredentials.IdentityProvider.USERNAME_PASSWORD); - assertNull(userInfo); - } - - @Test - public void retrieve_invalidProvider() { - final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - final String username = UUID.randomUUID().toString(); - final String password = "password"; - final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - assertTrue(user.isValid()); - - SyncUserInfo userInfo = adminUser.retrieveInfoForUser("username", "invalid"); - assertNull(userInfo); - } - - @Test - public void retrieve_notAdmin() { - final String username1 = UUID.randomUUID().toString(); - final String password1 = "password"; - final SyncCredentials credentials1 = SyncCredentials.usernamePassword(username1, password1, true); - final SyncUser user1 = SyncUser.logIn(credentials1, Constants.AUTH_URL); - assertTrue(user1.isValid()); - - final String username2 = UUID.randomUUID().toString(); - final String password2 = "password"; - final SyncCredentials credentials2 = SyncCredentials.usernamePassword(username2, password2, true); - final SyncUser user2 = SyncUser.logIn(credentials2, Constants.AUTH_URL); - assertTrue(user2.isValid()); - - // trying to lookup user2 using user1 should not work (requires admin token) - try { - user1.retrieveInfoForUser(SyncCredentials.IdentityProvider.USERNAME_PASSWORD, username2); - fail("It should not be possible to lookup a user using non admin token"); - } catch (IllegalArgumentException ignored) { - } - } - - @Test - @RunTestInLooperThread - public void retrieve_async() { - final String username = UUID.randomUUID().toString(); - final String password = "password"; - final SyncCredentials credentials = SyncCredentials.usernamePassword(username, password, true); - final SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - assertTrue(user.isValid()); - - // Login an admin user - final SyncUser adminUser = UserFactory.createAdminUser(Constants.AUTH_URL); - assertTrue(adminUser.isValid()); - assertTrue(adminUser.isAdmin()); - - final String identity = user.getIdentity(); - adminUser.retrieveInfoForUserAsync(username, SyncCredentials.IdentityProvider.USERNAME_PASSWORD, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUserInfo userInfo) { - assertNotNull(userInfo); - assertEquals(identity, userInfo.getIdentity()); - assertFalse(userInfo.isAdmin()); - assertTrue(userInfo.getMetadata().isEmpty()); - assertEquals(username, userInfo.getAccounts().get(SyncCredentials.IdentityProvider.USERNAME_PASSWORD)); - - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.getErrorMessage()); - } - }); - } - - @Test - @RunTestInLooperThread - @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + - "to support the requested operation.\",\"status\":501,\"detail\"" + - ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") - public void requestPasswordResetAsync() { - String email = "foo@bar.baz"; - UserFactory.createUser(email).logOut(); - - // Currently no easy way to see if we actually got an email. - // Just verify that the network request can complete successfully. - SyncUser.requestPasswordResetAsync(email, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(Void result) { - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread - @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + - "to support the requested operation.\",\"status\":501,\"detail\"" + - ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") - public void requestResetPassword_unknownEmail() { - SyncUser.requestPasswordResetAsync("unknown@realm.io", Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(Void result) { - // Server will respond with SUCCESS if the email is incorrect (for security reasons). - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - @Test - @RunTestInLooperThread - @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + - "to support the requested operation.\",\"status\":501,\"detail\"" + - ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") - public void completeResetPassword_invalidToken() { - SyncUser.completePasswordResetAsync("invalidToken","newPassword", Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(Void result) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } - - @Test - @RunTestInLooperThread - @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + - "to support the requested operation.\",\"status\":501,\"detail\"" + - ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") - public void requestEmailConfirmation() { - String email = "foo@bar.baz"; - UserFactory.createUser(email).logOut(); - - // Currently no easy way to see if we actually get an email. - // Just verify that the network request can complete successfully. - SyncUser.requestEmailConfirmationAsync(email, Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(Void result) { - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - @Test - @RunTestInLooperThread - @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + - "to support the requested operation.\",\"status\":501,\"detail\"" + - ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") - public void requestEmailConfirmation_invalidEmail() { - SyncUser.requestEmailConfirmationAsync("unknown@realm.io", Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(Void result) { - // Server will respond with SUCCESS if the email is incorrect (for security reasons). - looperThread.testComplete(); - } - - @Override - public void onError(ObjectServerError error) { - fail(error.toString()); - } - }); - } - - - @Test - @RunTestInLooperThread - @Ignore("{\"type\":\"https://docs.realm.io/server/troubleshoot/errors#not-enabled\",\"title\":\"The server was not configured " + - "to support the requested operation.\",\"status\":501,\"detail\"" + - ":\"The Password provider is not configured with an emailHandler.\",\"code\":803}") - public void confirmEmail_invalidToken() { - SyncUser.confirmEmailAsync("invalidToken", Constants.AUTH_URL, new SyncUser.Callback() { - @Override - public void onSuccess(Void result) { - fail(); - } - - @Override - public void onError(ObjectServerError error) { - assertEquals(ErrorCode.ACCESS_DENIED, error.getErrorCode()); - looperThread.testComplete(); - } - }); - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 06e0f5e317..a4055786b7 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -5,6 +5,7 @@ import android.os.HandlerThread import android.os.SystemClock import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import io.realm.admin.ServerAdmin import io.realm.entities.* import io.realm.exceptions.DownloadingRealmInterruptedException import io.realm.internal.OsRealmConfig @@ -27,6 +28,7 @@ import org.junit.* import org.junit.Assert.* import org.junit.runner.RunWith import java.io.Closeable +import java.lang.IllegalStateException import java.lang.Thread import java.util.* import java.util.concurrent.CountDownLatch @@ -48,6 +50,7 @@ class SyncSessionTests { private lateinit var app: App private lateinit var user: User private lateinit var syncConfiguration: SyncConfiguration + private lateinit var admin: ServerAdmin private val configFactory: TestSyncConfigurationFactory = TestSyncConfigurationFactory() @@ -92,6 +95,8 @@ class SyncSessionTests { fun setup() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) RealmLog.setLevel(LogLevel.ALL) + + admin = ServerAdmin() app = TestApp() user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) syncConfiguration = configFactory @@ -228,6 +233,14 @@ class SyncSessionTests { } } + @Test + fun session_throwOnLogoutUser() { + user.logOut() + assertFailsWith { + Realm.getInstance(syncConfiguration).use { realm -> } + } + } + @Test fun uploadDownloadAllChanges() { Realm.getInstance(syncConfiguration).use { realm -> @@ -738,4 +751,34 @@ class SyncSessionTests { looperThread.testComplete() } + @Test + fun cachedInstanceShouldNotThrowIfUserTokenIsInvalid() { + val configuration: RealmConfiguration = configFactory.createSyncConfigurationBuilder(user) + .errorHandler { session, error -> + RealmLog.debug("error", error) + } + .build() + + Realm.getInstance(configuration).close() + + admin.disableUser(user) + + // It should be possible to open a cached Realm with expired token + Realm.getInstance(configuration).close() + + // It should also be possible to open a Realm with an expired token from a different thread + looperThread.runBlocking { + val instance = Realm.getInstance(configuration) + instance.close() + looperThread.testComplete() + } + + // TODO We cannot currently easily verify that token is actually invalid and triggering + // refresh. If OS includes support for reacting on this we should verify that it is + // refreshed. + //Realm.getInstance(configuration).use { realm -> + // realm.syncSession.downloadAllServerChanges() + //} + } + } From f9bffd476d1e6ac5b393f2c5cb59e10e2c6f40e2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 3 Jul 2020 12:44:07 +0200 Subject: [PATCH 1608/2110] Use snapshot suffix --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7c0faa4333..b27ddb8fd2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.6 \ No newline at end of file +10.0.0-BETA.6-SNAPSHOT From 5624a4e0e13f1811ef52e2ee1eaeac009c8ee911 Mon Sep 17 00:00:00 2001 From: clementetb Date: Fri, 3 Jul 2020 15:45:01 +0200 Subject: [PATCH 1609/2110] Allow RealmList to be final (#6978) --- CHANGELOG.md | 2 +- .../java/io/realm/processor/ClassMetaData.kt | 2 +- .../realm/some_test_AllTypesRealmProxy.java | 229 +++++++++++++++++- .../test/resources/some/test/AllTypes.java | 1 + 4 files changed, 229 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 317ef7c30a..83cad9ca35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * None. ### Enhancements -* None. +* RealmLists can now be marked final. (Issue [#6892](https://github.com/realm/realm-java/issues/6892)) ### Fixed * [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt index 7c16b9177a..ed07dc98ad 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt @@ -483,7 +483,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat if (!field.modifiers.contains(Modifier.FINAL)) { continue } - if (Utils.isMutableRealmInteger(field)) { + if (Utils.isRealmList(field) || Utils.isMutableRealmInteger(field)) { continue } diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index 918f1b8bb8..e4a6abe7c4 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -51,6 +51,7 @@ static final class AllTypesColumnInfo extends ColumnInfo { long columnMutableRealmIntegerColKey; long columnObjectColKey; long columnRealmListColKey; + long columnRealmFinalListColKey; long columnStringListColKey; long columnBinaryListColKey; long columnBooleanListColKey; @@ -65,7 +66,7 @@ static final class AllTypesColumnInfo extends ColumnInfo { long columnObjectIdListColKey; AllTypesColumnInfo(OsSchemaInfo schemaInfo) { - super(24); + super(25); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("AllTypes"); this.columnStringColKey = addColumnDetails("columnString", "columnString", objectSchemaInfo); this.columnLongColKey = addColumnDetails("columnLong", "columnLong", objectSchemaInfo); @@ -79,6 +80,7 @@ static final class AllTypesColumnInfo extends ColumnInfo { this.columnMutableRealmIntegerColKey = addColumnDetails("columnMutableRealmInteger", "columnMutableRealmInteger", objectSchemaInfo); this.columnObjectColKey = addColumnDetails("columnObject", "columnObject", objectSchemaInfo); this.columnRealmListColKey = addColumnDetails("columnRealmList", "columnRealmList", objectSchemaInfo); + this.columnRealmFinalListColKey = addColumnDetails("columnRealmFinalList", "columnRealmFinalList", objectSchemaInfo); this.columnStringListColKey = addColumnDetails("columnStringList", "columnStringList", objectSchemaInfo); this.columnBinaryListColKey = addColumnDetails("columnBinaryList", "columnBinaryList", objectSchemaInfo); this.columnBooleanListColKey = addColumnDetails("columnBooleanList", "columnBooleanList", objectSchemaInfo); @@ -120,6 +122,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.columnMutableRealmIntegerColKey = src.columnMutableRealmIntegerColKey; dst.columnObjectColKey = src.columnObjectColKey; dst.columnRealmListColKey = src.columnRealmListColKey; + dst.columnRealmFinalListColKey = src.columnRealmFinalListColKey; dst.columnStringListColKey = src.columnStringListColKey; dst.columnBinaryListColKey = src.columnBinaryListColKey; dst.columnBooleanListColKey = src.columnBooleanListColKey; @@ -144,6 +147,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { @Override protected long getColumnIndex() { return columnInfo.columnMutableRealmIntegerColKey; } }; private RealmList columnRealmListRealmList; + private RealmList columnRealmFinalListRealmList; private RealmList columnStringListRealmList; private RealmList columnBinaryListRealmList; private RealmList columnBooleanListRealmList; @@ -503,6 +507,67 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } } + @Override + public RealmList realmGet$columnRealmFinalList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnRealmFinalListRealmList != null) { + return columnRealmFinalListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.columnRealmFinalListColKey); + columnRealmFinalListRealmList = new RealmList(some.test.AllTypes.class, osList, proxyState.getRealm$realm()); + return columnRealmFinalListRealmList; + } + } + + @Override + public void realmSet$columnRealmFinalList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnRealmFinalList")) { + return; + } + // if the list contains unmanaged RealmObjects, convert them to managed. + if (value != null && !value.isManaged()) { + final Realm realm = (Realm) proxyState.getRealm$realm(); + final RealmList original = value; + value = new RealmList(); + for (some.test.AllTypes item : original) { + if (item == null || RealmObject.isManaged(item)) { + value.add(item); + } else { + value.add(realm.copyToRealm(item)); + } + } + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getModelList(columnInfo.columnRealmFinalListColKey); + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + if (value != null && value.size() == osList.size()) { + int objects = value.size(); + for (int i = 0; i < objects; i++) { + some.test.AllTypes linkedObject = value.get(i); + proxyState.checkValidObject(linkedObject); + osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getObjectKey()); + } + } else { + osList.removeAll(); + if (value == null) { + return; + } + int objects = value.size(); + for (int i = 0; i < objects; i++) { + some.test.AllTypes linkedObject = value.get(i); + proxyState.checkValidObject(linkedObject); + osList.addRow(((RealmObjectProxy) linkedObject).realmGet$proxyState().getRow$realm().getObjectKey()); + } + } + } + @Override public RealmList realmGet$columnStringList() { proxyState.getRealm$realm().checkIfValid(); @@ -983,7 +1048,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", false, 24, 1); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", false, 25, 1); builder.addPersistedProperty("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -996,6 +1061,7 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { builder.addPersistedProperty("columnMutableRealmInteger", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedLinkProperty("columnObject", RealmFieldType.OBJECT, "AllTypes"); builder.addPersistedLinkProperty("columnRealmList", RealmFieldType.LIST, "AllTypes"); + builder.addPersistedLinkProperty("columnRealmFinalList", RealmFieldType.LIST, "AllTypes"); builder.addPersistedValueListProperty("columnStringList", RealmFieldType.STRING_LIST, !Property.REQUIRED); builder.addPersistedValueListProperty("columnBinaryList", RealmFieldType.BINARY_LIST, !Property.REQUIRED); builder.addPersistedValueListProperty("columnBooleanList", RealmFieldType.BOOLEAN_LIST, !Property.REQUIRED); @@ -1031,7 +1097,7 @@ public static final class ClassNameHelper { @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { - final List excludeFields = new ArrayList(14); + final List excludeFields = new ArrayList(15); some.test.AllTypes obj = null; if (update) { Table table = realm.getTable(some.test.AllTypes.class); @@ -1060,6 +1126,9 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON if (json.has("columnRealmList")) { excludeFields.add("columnRealmList"); } + if (json.has("columnRealmFinalList")) { + excludeFields.add("columnRealmFinalList"); + } if (json.has("columnStringList")) { excludeFields.add("columnStringList"); } @@ -1210,6 +1279,18 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON } } } + if (json.has("columnRealmFinalList")) { + if (json.isNull("columnRealmFinalList")) { + objProxy.realmSet$columnRealmFinalList(null); + } else { + objProxy.realmGet$columnRealmFinalList().clear(); + JSONArray array = json.getJSONArray("columnRealmFinalList"); + for (int i = 0; i < array.length(); i++) { + some.test.AllTypes item = some_test_AllTypesRealmProxy.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update); + objProxy.realmGet$columnRealmFinalList().add(item); + } + } + } ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnStringList(), json, "columnStringList"); ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnBinaryList(), json, "columnBinaryList"); ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnBooleanList(), json, "columnBooleanList"); @@ -1334,6 +1415,19 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } reader.endArray(); } + } else if (name.equals("columnRealmFinalList")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$columnRealmFinalList(null); + } else { + objProxy.realmSet$columnRealmFinalList(new RealmList()); + reader.beginArray(); + while (reader.hasNext()) { + some.test.AllTypes item = some_test_AllTypesRealmProxy.createUsingJsonStream(realm, reader); + objProxy.realmGet$columnRealmFinalList().add(item); + } + reader.endArray(); + } } else if (name.equals("columnStringList")) { objProxy.realmSet$columnStringList(ProxyUtils.createRealmListWithJsonStream(java.lang.String.class, reader)); } else if (name.equals("columnBinaryList")) { @@ -1491,6 +1585,21 @@ public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo } } + RealmList columnRealmFinalListUnmanagedList = unmanagedSource.realmGet$columnRealmFinalList(); + if (columnRealmFinalListUnmanagedList != null) { + RealmList columnRealmFinalListManagedList = managedCopy.realmGet$columnRealmFinalList(); + columnRealmFinalListManagedList.clear(); + for (int i = 0; i < columnRealmFinalListUnmanagedList.size(); i++) { + some.test.AllTypes columnRealmFinalListUnmanagedItem = columnRealmFinalListUnmanagedList.get(i); + some.test.AllTypes cachecolumnRealmFinalList = (some.test.AllTypes) cache.get(columnRealmFinalListUnmanagedItem); + if (cachecolumnRealmFinalList != null) { + columnRealmFinalListManagedList.add(cachecolumnRealmFinalList); + } else { + columnRealmFinalListManagedList.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnRealmFinalListUnmanagedItem, update, cache, flags)); + } + } + } + return managedCopy; } @@ -1561,6 +1670,18 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnRealmFinalListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmFinalList(); + if (columnRealmFinalListList != null) { + OsList columnRealmFinalListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnRealmFinalListColKey); + for (some.test.AllTypes columnRealmFinalListItem : columnRealmFinalListList) { + Long cacheItemIndexcolumnRealmFinalList = cache.get(columnRealmFinalListItem); + if (cacheItemIndexcolumnRealmFinalList == null) { + cacheItemIndexcolumnRealmFinalList = some_test_AllTypesRealmProxy.insert(realm, columnRealmFinalListItem, cache); + } + columnRealmFinalListOsList.addRow(cacheItemIndexcolumnRealmFinalList); + } + } + RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { OsList columnStringListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnStringListColKey); @@ -1781,6 +1902,18 @@ public static void insert(Realm realm, Iterator objects, M } } + RealmList columnRealmFinalListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmFinalList(); + if (columnRealmFinalListList != null) { + OsList columnRealmFinalListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnRealmFinalListColKey); + for (some.test.AllTypes columnRealmFinalListItem : columnRealmFinalListList) { + Long cacheItemIndexcolumnRealmFinalList = cache.get(columnRealmFinalListItem); + if (cacheItemIndexcolumnRealmFinalList == null) { + cacheItemIndexcolumnRealmFinalList = some_test_AllTypesRealmProxy.insert(realm, columnRealmFinalListItem, cache); + } + columnRealmFinalListOsList.addRow(cacheItemIndexcolumnRealmFinalList); + } + } + RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); if (columnStringListList != null) { OsList columnStringListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnStringListColKey); @@ -2019,6 +2152,33 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnRealmFinalListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmFinalList(); + if (columnRealmFinalListList != null && columnRealmFinalListList.size() == columnRealmFinalListOsList.size()) { + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + int objects = columnRealmFinalListList.size(); + for (int i = 0; i < objects; i++) { + some.test.AllTypes columnRealmFinalListItem = columnRealmFinalListList.get(i); + Long cacheItemIndexcolumnRealmFinalList = cache.get(columnRealmFinalListItem); + if (cacheItemIndexcolumnRealmFinalList == null) { + cacheItemIndexcolumnRealmFinalList = some_test_AllTypesRealmProxy.insertOrUpdate(realm, columnRealmFinalListItem, cache); + } + columnRealmFinalListOsList.setRow(i, cacheItemIndexcolumnRealmFinalList); + } + } else { + columnRealmFinalListOsList.removeAll(); + if (columnRealmFinalListList != null) { + for (some.test.AllTypes columnRealmFinalListItem : columnRealmFinalListList) { + Long cacheItemIndexcolumnRealmFinalList = cache.get(columnRealmFinalListItem); + if (cacheItemIndexcolumnRealmFinalList == null) { + cacheItemIndexcolumnRealmFinalList = some_test_AllTypesRealmProxy.insertOrUpdate(realm, columnRealmFinalListItem, cache); + } + columnRealmFinalListOsList.addRow(cacheItemIndexcolumnRealmFinalList); + } + } + } + + OsList columnStringListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnStringListColKey); columnStringListOsList.removeAll(); RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); @@ -2288,6 +2448,33 @@ public static void insertOrUpdate(Realm realm, Iterator ob } + OsList columnRealmFinalListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnRealmFinalListColKey); + RealmList columnRealmFinalListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnRealmFinalList(); + if (columnRealmFinalListList != null && columnRealmFinalListList.size() == columnRealmFinalListOsList.size()) { + // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same. + int objectCount = columnRealmFinalListList.size(); + for (int i = 0; i < objectCount; i++) { + some.test.AllTypes columnRealmFinalListItem = columnRealmFinalListList.get(i); + Long cacheItemIndexcolumnRealmFinalList = cache.get(columnRealmFinalListItem); + if (cacheItemIndexcolumnRealmFinalList == null) { + cacheItemIndexcolumnRealmFinalList = some_test_AllTypesRealmProxy.insertOrUpdate(realm, columnRealmFinalListItem, cache); + } + columnRealmFinalListOsList.setRow(i, cacheItemIndexcolumnRealmFinalList); + } + } else { + columnRealmFinalListOsList.removeAll(); + if (columnRealmFinalListList != null) { + for (some.test.AllTypes columnRealmFinalListItem : columnRealmFinalListList) { + Long cacheItemIndexcolumnRealmFinalList = cache.get(columnRealmFinalListItem); + if (cacheItemIndexcolumnRealmFinalList == null) { + cacheItemIndexcolumnRealmFinalList = some_test_AllTypesRealmProxy.insertOrUpdate(realm, columnRealmFinalListItem, cache); + } + columnRealmFinalListOsList.addRow(cacheItemIndexcolumnRealmFinalList); + } + } + } + + OsList columnStringListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnStringListColKey); columnStringListOsList.removeAll(); RealmList columnStringListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnStringList(); @@ -2506,6 +2693,21 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje } } + // Deep copy of columnRealmFinalList + if (currentDepth == maxDepth) { + unmanagedCopy.realmSet$columnRealmFinalList(null); + } else { + RealmList managedcolumnRealmFinalListList = realmSource.realmGet$columnRealmFinalList(); + RealmList unmanagedcolumnRealmFinalListList = new RealmList(); + unmanagedCopy.realmSet$columnRealmFinalList(unmanagedcolumnRealmFinalListList); + int nextDepth = currentDepth + 1; + int size = managedcolumnRealmFinalListList.size(); + for (int i = 0; i < size; i++) { + some.test.AllTypes item = some_test_AllTypesRealmProxy.createDetachedCopy(managedcolumnRealmFinalListList.get(i), nextDepth, maxDepth, cache); + unmanagedcolumnRealmFinalListList.add(item); + } + } + unmanagedCopy.realmSet$columnStringList(new RealmList()); unmanagedCopy.realmGet$columnStringList().addAll(realmSource.realmGet$columnStringList()); @@ -2589,6 +2791,23 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som } else { builder.addObjectList(columnInfo.columnRealmListColKey, new RealmList()); } + + RealmList columnRealmFinalListUnmanagedList = realmObjectSource.realmGet$columnRealmFinalList(); + if (columnRealmFinalListUnmanagedList != null) { + RealmList columnRealmFinalListManagedCopy = new RealmList(); + for (int i = 0; i < columnRealmFinalListUnmanagedList.size(); i++) { + some.test.AllTypes columnRealmFinalListItem = columnRealmFinalListUnmanagedList.get(i); + some.test.AllTypes cachecolumnRealmFinalList = (some.test.AllTypes) cache.get(columnRealmFinalListItem); + if (cachecolumnRealmFinalList != null) { + columnRealmFinalListManagedCopy.add(cachecolumnRealmFinalList); + } else { + columnRealmFinalListManagedCopy.add(some_test_AllTypesRealmProxy.copyOrUpdate(realm, (some_test_AllTypesRealmProxy.AllTypesColumnInfo) realm.getSchema().getColumnInfo(some.test.AllTypes.class), columnRealmFinalListItem, true, cache, flags)); + } + } + builder.addObjectList(columnInfo.columnRealmFinalListColKey, columnRealmFinalListManagedCopy); + } else { + builder.addObjectList(columnInfo.columnRealmFinalListColKey, new RealmList()); + } builder.addStringList(columnInfo.columnStringListColKey, realmObjectSource.realmGet$columnStringList()); builder.addByteArrayList(columnInfo.columnBinaryListColKey, realmObjectSource.realmGet$columnBinaryList()); builder.addBooleanList(columnInfo.columnBooleanListColKey, realmObjectSource.realmGet$columnBooleanList()); @@ -2661,6 +2880,10 @@ public String toString() { stringBuilder.append("RealmList[").append(realmGet$columnRealmList().size()).append("]"); stringBuilder.append("}"); stringBuilder.append(","); + stringBuilder.append("{columnRealmFinalList:"); + stringBuilder.append("RealmList[").append(realmGet$columnRealmFinalList().size()).append("]"); + stringBuilder.append("}"); + stringBuilder.append(","); stringBuilder.append("{columnStringList:"); stringBuilder.append("RealmList[").append(realmGet$columnStringList().size()).append("]"); stringBuilder.append("}"); diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java index e44e6cffce..baf86691a1 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java @@ -57,6 +57,7 @@ public class AllTypes extends RealmObject { private AllTypes columnObject; private RealmList columnRealmList; + private final RealmList columnRealmFinalList = new RealmList(); private RealmList columnStringList; private RealmList columnBinaryList; From 9c760563158f805c8d0b20780935704aad3b4974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 3 Jul 2020 15:56:35 +0200 Subject: [PATCH 1610/2110] Obfuscate sensitive login information in debug logs (#6961) --- CHANGELOG.md | 23 +- realm/realm-library/build.gradle | 4 + .../kotlin/io/realm/AppConfigurationTests.kt | 36 ++- .../kotlin/io/realm/CredentialsTests.kt | 3 +- .../network/LoggingInterceptorTest.kt | 265 ++++++++++++++++++ .../transport/OkHttpNetworkTransportTests.kt | 3 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../log/obfuscator/ApiKeyObfuscator.java | 50 ++++ .../obfuscator/CustomFunctionObfuscator.java | 50 ++++ .../obfuscator/EmailPasswordObfuscator.java | 66 +++++ .../obfuscator/RegexPatternObfuscator.java | 75 +++++ .../log/obfuscator/TokenObfuscator.java | 70 +++++ .../internal/network/LoggingInterceptor.java | 93 ++++++ .../network/OkHttpNetworkTransport.java | 43 +-- .../io/realm/internal/util/BsonConverter.java | 0 .../java/io/realm/mongodb/App.java | 2 +- .../io/realm/mongodb/AppConfiguration.java | 94 ++++++- .../log/obfuscator/HttpLogObfuscator.java | 81 ++++++ .../syncTestUtils/kotlin/io/realm/TestApp.kt | 10 +- .../kotlin/io/realm/ObfuscatorHelper.kt | 146 ++++++++++ .../log/obfuscator/ApiKeyObfuscatorTest.kt | 45 +++ .../CustomFunctionObfuscatorTest.kt | 47 ++++ .../obfuscator/EmailPasswordObfuscatorTest.kt | 45 +++ .../log/obfuscator/TokenObfuscatorTest.kt | 54 ++++ .../log/obfuscator/HttpLogObfuscatorTest.kt | 85 ++++++ .../testUtils/java/io/realm/TestHelper.java | 13 +- 26 files changed, 1332 insertions(+), 73 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/ApiKeyObfuscator.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/CustomFunctionObfuscator.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/EmailPasswordObfuscator.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/RegexPatternObfuscator.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/TokenObfuscator.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/LoggingInterceptor.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/log/obfuscator/HttpLogObfuscator.java create mode 100644 realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt create mode 100644 realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/ApiKeyObfuscatorTest.kt create mode 100644 realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/CustomFunctionObfuscatorTest.kt create mode 100644 realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/EmailPasswordObfuscatorTest.kt create mode 100644 realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/TokenObfuscatorTest.kt create mode 100644 realm/realm-library/src/testObjectServer/kotlin/io/realm/mongodb/log/obfuscator/HttpLogObfuscatorTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 83cad9ca35..abf6356a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,11 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * None. ### Enhancements +* Credentials information (e.g. username, password) displayed in Logcat is now obfuscated by default, even if [LogLevel] is set to DEBUG, TRACE or ALL. * RealmLists can now be marked final. (Issue [#6892](https://github.com/realm/realm-java/issues/6892)) ### Fixed -* [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) +* [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). @@ -52,7 +53,7 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. -### Breaking Changes +### Breaking Changes * None. ### Enhancements @@ -77,7 +78,7 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. -### Breaking Changes +### Breaking Changes * None. ### Enhancements @@ -101,14 +102,14 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy API's have undergone significant refactoring. The new API's are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. -### Breaking Changes +### Breaking Changes * None. ### Enhancements * None. ### Fixed -* [RealmApp] `AppConfiguration` did not fallback to the correct default baseUrl if none was provided. (Since 10.0.0-BETA.1) +* [RealmApp] `AppConfiguration` did not fallback to the correct default baseUrl if none was provided. (Since 10.0.0-BETA.1) * [RealmApp] When restarting an app, re-using the already logged in user would result in Sync not resuming. (Since 10.0.0-BETA.1) ### Compatibility @@ -132,7 +133,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new * [RealmApp] Query Based Sync API's and Subscriptions. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. `SyncConfiguration.partitionKey()` has been added as a replacement. * [RealmApp] Removed support for Client Resync. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. * [RealmApp] Removed suppport for custom SSL certificates. These API's are not initially supported by MongoDB Realm. They will be re-introduced in a future release. -* [RealmApp] Destructive updates of a schema of a synced Realm will now consistently throw an `UnsupportedOperationException` instead of some methods throwing `IllegalArgumentException`. The affected methods are `RealmSchema.remove(String)`, `RealmSchema.rename(String, String)`, `RealmObjectSchema.setClassName(String)`, `RealmObjectSchema.removeField(String)`, `RealmObjectSchema.renameField(String, String)`, `RealmObjectSchema.removeIndex(String)`, `RealmObjectSchema.removePrimaryKey()`, `RealmObjectSchema.addPrimaryKey(String)` and `RealmObjectSchema.addField(String, Class, FieldAttribute)` +* [RealmApp] Destructive updates of a schema of a synced Realm will now consistently throw an `UnsupportedOperationException` instead of some methods throwing `IllegalArgumentException`. The affected methods are `RealmSchema.remove(String)`, `RealmSchema.rename(String, String)`, `RealmObjectSchema.setClassName(String)`, `RealmObjectSchema.removeField(String)`, `RealmObjectSchema.renameField(String, String)`, `RealmObjectSchema.removeIndex(String)`, `RealmObjectSchema.removePrimaryKey()`, `RealmObjectSchema.addPrimaryKey(String)` and `RealmObjectSchema.addField(String, Class, FieldAttribute)` ### Enhancements * Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. @@ -141,7 +142,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new ### Fixed * After upgrading a Realm file, you may at some point receive a 'NoSuchTable' exception. (Issue [Core#3701](https://github.com/realm/realm-core/issues/3701), since 7.0.0) -* If the Realm file upgrade process was interrupted/killed for various reasons, the following run would some assertions failing. (Issue [#6866](https://github.com/realm/realm-java/issues/6866), since 7.0.0). +* If the Realm file upgrade process was interrupted/killed for various reasons, the following run would some assertions failing. (Issue [#6866](https://github.com/realm/realm-java/issues/6866), since 7.0.0). ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). @@ -154,7 +155,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new * Updated to Realm Core 10.0.0-beta.1. * OKHttp was upgraded to 3.12.0 from 3.10.0. * Updated Android Gradle Plugin to 3.6.1. -* Updated Gradle to 5.6.4 +* Updated Gradle to 5.6.4 * Updated Dokka to 0.10.1 * Updated Android Build Tools to 29.0.2. * Updated compileSdkVersion to 29. @@ -162,7 +163,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new ## 7.0.0(YYYY-MM-DD) -NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. +NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. ### Breaking Changes * [ObjectServer] Removed deprecated method `SyncConfiguration.Builder.partialRealm()`. Use `SyncConfiguration.Builder.fullSynchronization()` instead. @@ -171,7 +172,7 @@ NOTE: This version bumps the Realm file format to version 10. Files created with * [ObjectServer] Removed deprecated method `SyncCredentials.nickname(name)` and `SyncCredentials.nickname(name, isAdmin)`. Use `SyncCredentials.usernamePassword(username, password)` instead. * [ObjectServer] Deprecated state `SyncSession.State.ERROR` has been removed. Use `SyncConfiguration.Builder.errorHandler(ErrorHandler)` instead. * [ObjectServer] `IncompatibleSyncedFileException` is removed as it is no longer used. -* [ObjectServer] New error codes thrown by the underlying sync layers now have proper enum mappings in `ErrorCode.java`. A few other errors have been renamed in order to have consistent naming. (Issue [#6387](https://github.com/realm/realm-java/issues/6387)) +* [ObjectServer] New error codes thrown by the underlying sync layers now have proper enum mappings in `ErrorCode.java`. A few other errors have been renamed in order to have consistent naming. (Issue [#6387](https://github.com/realm/realm-java/issues/6387)) * RxJava Flowables and Observables are now subscribed to and unsubscribed to asynchronously on the thread holding the live Realm, instead of previously where this was done synchronously. * All RxJava Flowables and Observables now return frozen objects instead of live objects. This can be configured using `RealmConfiguration.Builder.rxFactory(new RealmObservableFactory(true|false))`. By using frozen objects, it is possible to send RealmObjects across threads, which means that all RxJava operators should now be supported without the need to copy Realm data into unmanaged objects. * MIPS is not supported anymore. @@ -232,7 +233,7 @@ NOTE: This version bumps the Realm file format to version 10. Files created with * `RealmResults.asJson()` now encode binary data as Base64 and null object links are reported as `null` instead of `[]`. ### Fixed -* Fixed using `RealmList` with a primitive type sometimes crashing with `Destruction of mutex in use`. (Issue [#6689](https://github.com/realm/realm-java/issues/6689)) +* Fixed using `RealmList` with a primitive type sometimes crashing with `Destruction of mutex in use`. (Issue [#6689](https://github.com/realm/realm-java/issues/6689)) * `RealmObjectSchema.transform()` would crash if one of the `DynamicRealmObject` provided are deleted from the Realm. (Issue [#6657](https://github.com/realm/realm-java/issues/6657), since 0.86.0) * The Realm Transformer will no longer attempt to send anonymous metrics when Gradle is invoked with `--offline`. (Issue [#6691](https://github.com/realm/realm-java/issues/6691)) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 98d95601bb..3ea77e1553 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -210,6 +210,10 @@ dependencies { compileOnly 'io.reactivex.rxjava2:rxjava:2.1.5' compileOnly 'com.google.code.findbugs:findbugs-annotations:3.0.1' + testImplementation 'junit:junit:4.12' + testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + api "io.realm:realm-annotations:${version}" implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.getkeepsafe.relinker:relinker:1.4.0' diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index 265ed26321..4c7a6109ea 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -17,7 +17,9 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import io.realm.internal.network.LoggingInterceptor.LOGIN_FEATURE import io.realm.mongodb.AppConfiguration +import io.realm.mongodb.log.obfuscator.HttpLogObfuscator import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries import org.junit.Assert.* @@ -28,9 +30,9 @@ import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import java.io.File -import java.lang.IllegalArgumentException import java.net.URL import kotlin.test.assertFailsWith +import kotlin.test.assertNull @RunWith(AndroidJUnit4::class) class AppConfigurationTests { @@ -77,9 +79,9 @@ class AppConfigurationTests { @Test fun addCustomRequestHeader() { val config = AppConfiguration.Builder("app-id") - .addCustomRequestHeader("header1", "val1") - .addCustomRequestHeader("header2", "val2") - .build() + .addCustomRequestHeader("header1", "val1") + .addCustomRequestHeader("header2", "val2") + .build() val headers: Map = config.customRequestHeaders assertEquals(2, headers.size.toLong()) assertTrue(headers.any { it.key == "header1" && it.value == "val1" }) @@ -96,9 +98,9 @@ class AppConfigurationTests { inputHeaders["header1"] = "value1" inputHeaders["header2"] = "value2" val config = AppConfiguration.Builder("app-id") - .addCustomRequestHeaders(TestHelper.getNull()) - .addCustomRequestHeaders(inputHeaders) - .build() + .addCustomRequestHeaders(TestHelper.getNull()) + .addCustomRequestHeaders(inputHeaders) + .build() val outputHeaders: Map = config.customRequestHeaders assertEquals(2, outputHeaders.size.toLong()) assertTrue(outputHeaders.any { it.key == "header1" && it.value == "value1" }) @@ -196,6 +198,7 @@ class AppConfigurationTests { val config = AppConfiguration.Builder("foo").build() assertEquals(URL(url), config.baseUrl) } + @Test fun baseUrl_invalidValuesThrows() { val configBuilder = AppConfiguration.Builder("foo") @@ -275,4 +278,23 @@ class AppConfigurationTests { assertEquals(configCodecRegistry, config.defaultCodecRegistry) } + @Test + fun httpLogObfuscator_null() { + AppConfiguration.Builder("app-id") + .httpLogObfuscator(null) + .build() + .let { + assertNull(it.httpLogObfuscator) + } + } + + @Test + fun defaultLoginInfoObfuscator() { + AppConfiguration.Builder("app-id") + .build() + .let { + val defaultHttpLogObfuscator = HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators) + assertEquals(defaultHttpLogObfuscator, it.httpLogObfuscator) + } + } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index a2977c493c..1aa4f3a7bd 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -29,6 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith import kotlin.test.assertFailsWith + @RunWith(AndroidJUnit4::class) class CredentialsTests { @@ -51,7 +52,6 @@ class CredentialsTests { } } - @Test fun anonymous() { val creds = Credentials.anonymous() @@ -177,7 +177,6 @@ class CredentialsTests { fun loginUsingCredentials() { app = TestApp() admin = ServerAdmin() - Credentials.IdentityProvider.values().forEach { provider -> when (provider) { Credentials.IdentityProvider.ANONYMOUS -> { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt new file mode 100644 index 0000000000..1ed03ccda5 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt @@ -0,0 +1,265 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.internal.network + +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import io.realm.TestApp +import io.realm.TestHelper +import io.realm.admin.ServerAdmin +import io.realm.internal.network.LoggingInterceptor.LOGIN_FEATURE +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.mongodb.* +import io.realm.mongodb.log.obfuscator.HttpLogObfuscator +import org.bson.Document +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import kotlin.test.assertTrue + +class LoggingInterceptorTest { + + private lateinit var app: App + private lateinit var testLogger: TestHelper.TestLogger + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + + @After + fun tearDown() { + if (this::app.isInitialized) { + app.close() + } + if (this::testLogger.isInitialized) { + RealmLog.setLevel(LogLevel.WARN) + RealmLog.remove(testLogger) + } + } + + @Test + fun emailPasswordRegistrationAndLogin_noObfuscation() { + app = TestApp() + testLogger = getLogger() + + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuth.registerUser(email, password) + assertMessageExists(""""email":"$email"""", """"password":"$password"""") + + app.login(Credentials.emailPassword(email, password)) + assertMessageExists(""""username":"$email"""", """"password":"$password"""") + } + + @Test + fun emailPasswordRegistrationAndLogin_obfuscation() { + app = TestApp { builder -> + builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) + } + testLogger = getLogger() + + val email = TestHelper.getRandomEmail() + val password = "123456" + app.emailPasswordAuth.registerUser(email, password) + assertMessageExists(""""email":"***"""", """"password":"***"""") + + app.login(Credentials.emailPassword(email, password)) + assertMessageExists(""""username":"***"""", """"password":"***"""") + } + + @Test + fun apiKeyLogin_noObfuscation() { + app = TestApp() + testLogger = getLogger() + val admin = ServerAdmin() + val serverKey = admin.createServerApiKey() + + app.login(Credentials.serverApiKey(serverKey)) + assertMessageExists(""""key":"$serverKey"""") + } + + @Test + fun apiKeyLogin_obfuscation() { + app = TestApp { builder -> + builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) + } + testLogger = getLogger() + val admin = ServerAdmin() + val serverKey = admin.createServerApiKey() + + app.login(Credentials.serverApiKey(serverKey)) + assertMessageExists(""""key":"***"""") + } + + @Test + fun customFunctionLogin_noObfuscation() { + app = TestApp() + testLogger = getLogger() + + val key1 = "mail" + val key2 = "id" + val value1 = "myfakemail@mongodb.com" + val value2 = 666 + val customFunction = mapOf( + key1 to value1, + key2 to value2 + ).let { + Credentials.customFunction(Document(it)) + } + + app.login(customFunction) + assertMessageExists(""""$key1":"$value1"""") + } + + @Test + fun customFunctionLogin_obfuscation() { + app = TestApp { builder -> + builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) + } + testLogger = getLogger() + + val key1 = "mail" + val key2 = "id" + val value1 = "myfakemail@mongodb.com" + val value2 = 666 + val customFunction = mapOf( + key1 to value1, + key2 to value2 + ).let { credsMap -> + Credentials.customFunction(Document(credsMap)) + } + + app.login(customFunction) + assertMessageExists(""""functionArgs":"***"""") + } + + @Test + fun facebookTokenLogin_noObfuscation() { + app = TestApp() + testLogger = getLogger() + val token = "facebook-token" + + try { + app.login(Credentials.facebook(token)) + } catch (error: AppException) { + // It will fail as long as oauth2 tokens aren't supported + } finally { + assertMessageExists(""""access_token":"$token"""") + } + } + + @Test + fun facebookTokenLogin_obfuscation() { + app = TestApp { builder -> + builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) + } + testLogger = getLogger() + val token = "facebook-token" + + try { + app.login(Credentials.facebook(token)) + } catch (error: AppException) { + Assert.assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) + } finally { + assertMessageExists(""""access_token":"***"""") + } + } + + @Test + fun appleTokenLogin_noObfuscation() { + app = TestApp() + testLogger = getLogger() + val token = "apple-token" + + try { + app.login(Credentials.apple(token)) + } catch (error: AppException) { + Assert.assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) + } finally { + assertMessageExists(""""id_token":"$token"""") + } + } + + @Test + fun appleTokenLogin_obfuscation() { + app = TestApp { builder -> + builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) + } + testLogger = getLogger() + val token = "apple-token" + + try { + app.login(Credentials.apple(token)) + } catch (error: AppException) { + Assert.assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) + } finally { + assertMessageExists(""""id_token":"***"""") + } + } + + @Test + fun googleTokenLogin_noObfuscation() { + app = TestApp() + testLogger = getLogger() + val token = "google-token" + + try { + app.login(Credentials.google(token)) + } catch (error: AppException) { + Assert.assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) + } finally { + assertMessageExists(""""authCode":"$token"""") + } + } + + @Test + fun googleTokenLogin_obfuscation() { + app = TestApp { builder -> + builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) + } + testLogger = getLogger() + val token = "google-token" + + try { + app.login(Credentials.google(token)) + } catch (error: AppException) { + Assert.assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) + } finally { + assertMessageExists(""""authCode":"***"""") + } + } + + private fun getLogger(): TestHelper.TestLogger = + TestHelper.TestLogger().also { + RealmLog.add(it) + RealmLog.setLevel(LogLevel.ALL) + } + + // Check whether the expected logcat entries are present in the test logger, either as the + // latest or previous entry + private fun assertMessageExists(vararg entries: String) { + var patternExists = false + for (entry in entries) { + patternExists = patternExists + || testLogger.message.contains(entry) + || testLogger.previousMessage.contains(entry) + } + assertTrue(patternExists) + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt index 5b3ad8ab22..00896e011c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt @@ -19,6 +19,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm import io.realm.internal.network.OkHttpNetworkTransport +import io.realm.internal.network.LoggingInterceptor import io.realm.internal.objectstore.OsJavaNetworkTransport import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -48,7 +49,7 @@ class OkHttpNetworkTransportTests { @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - transport = OkHttpNetworkTransport() + transport = OkHttpNetworkTransport(null) } @Test diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 709e69580f..e1570f8d3d 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 709e69580f480051da8be8b444df400c64c652f8 +Subproject commit e1570f8d3d7cf4d77f049933e6a241a501301383 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/ApiKeyObfuscator.java b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/ApiKeyObfuscator.java new file mode 100644 index 0000000000..431406038a --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/ApiKeyObfuscator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.log.obfuscator; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Obfuscator for API key-related login requests. It will replace the {@code "key":""} pattern + * with {@code "key":"***"}. + */ + +public class ApiKeyObfuscator extends RegexPatternObfuscator { + + public static final String API_KEY_KEY = "key"; + + private ApiKeyObfuscator(Map patternReplacementMap) { + super(patternReplacementMap); + } + + /** + * Creates a {@link RegexPatternObfuscator} for API keys. + * + * @return an obfuscator that keeps API key information from being displayed in the logcat. + */ + public static ApiKeyObfuscator obfuscator() { + return new ApiKeyObfuscator(getPatterns()); + } + + private static Map getPatterns() { + Map map = new HashMap<>(); + map.put(Pattern.compile("((\"" + API_KEY_KEY + "\"):(\\s?\".+?\"))"), "\"" + API_KEY_KEY + "\":\"***\""); + return map; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/CustomFunctionObfuscator.java b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/CustomFunctionObfuscator.java new file mode 100644 index 0000000000..eb60a67405 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/CustomFunctionObfuscator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.log.obfuscator; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Obfuscator for custom function-related login requests. It will replace all function arguments + * that appear before {@code "options":"***"} with {@code "functionArgs":"***"}. + */ +public class CustomFunctionObfuscator extends RegexPatternObfuscator { + + public static final String CUSTOM_FUNCTION_KEY = "functionArgs"; + + private CustomFunctionObfuscator(Map patternReplacementMap) { + super(patternReplacementMap); + } + + /** + * Creates a {@link RegexPatternObfuscator} for custom functions. + * + * @return an obfuscator that keeps custom function information from being displayed in the + * logcat. + */ + public static CustomFunctionObfuscator obfuscator() { + return new CustomFunctionObfuscator(getPatterns()); + } + + private static Map getPatterns() { + Map map = new HashMap<>(); + map.put(Pattern.compile("\\{(.+?),\"options\":"), "{\"" + CUSTOM_FUNCTION_KEY + "\":\"***\",\"options\":"); + return map; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/EmailPasswordObfuscator.java b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/EmailPasswordObfuscator.java new file mode 100644 index 0000000000..d6aba0177d --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/EmailPasswordObfuscator.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.log.obfuscator; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Obfuscator for email- and password-related login requests. + *

              + * It will replace the + *

                + *
              • {@code "email":""},
              • + *
              • {@code "username":""} and
              • + *
              • {@code "password":""}
              • + *
              + * patterns with + *
                + *
              • {@code "email":"***"},
              • + *
              • {@code "username":"***"} and
              • + *
              • {@code "password":"***"}
              • + *
              + * respectively. + */ +public class EmailPasswordObfuscator extends RegexPatternObfuscator { + + public static final String EMAIL_KEY = "email"; + public static final String USERNAME_KEY = "username"; + public static final String PASSWORD_KEY = "password"; + + private EmailPasswordObfuscator(Map patternReplacementMap) { + super(patternReplacementMap); + } + + /** + * Creates a {@link RegexPatternObfuscator} for emails and passwords. + * + * @return an obfuscator that keeps emails and passwords from being displayed in the logcat. + */ + public static EmailPasswordObfuscator obfuscator() { + return new EmailPasswordObfuscator(getPatterns()); + } + + private static Map getPatterns() { + Map map = new HashMap<>(); + map.put(Pattern.compile("((\"" + EMAIL_KEY + "\"):(\".+?\"))"), "\"" + EMAIL_KEY + "\":\"***\""); + map.put(Pattern.compile("((\"" + USERNAME_KEY + "\"):(\".+?\"))"), "\"" + USERNAME_KEY + "\":\"***\""); + map.put(Pattern.compile("((\"" + PASSWORD_KEY + "\"):(\".+?\"))"), "\"" + PASSWORD_KEY + "\":\"***\""); + return map; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/RegexPatternObfuscator.java b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/RegexPatternObfuscator.java new file mode 100644 index 0000000000..1f41dcaeae --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/RegexPatternObfuscator.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.log.obfuscator; + +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.realm.internal.Util; + +/** + * The obfuscator removes sensitive information from logcat entries. + *

              + * Children classes have to provide a map of regex {@link Pattern}s and replacement strings to + * correctly hide the information. + *

              + * For example, the following pattern finds instances of {@code "token":""} in the + * logcat: {@code Pattern.compile("((\"token\"):(\".+?\"))")}. And the replacement string + * {@code "\"token\":\"***\""} replaces those instances with {@code "token":"***"}. + */ +public abstract class RegexPatternObfuscator { + + private Map patternReplacementMap; + + RegexPatternObfuscator(Map patternReplacementMap) { + this.patternReplacementMap = patternReplacementMap; + } + + /** + * Obfuscates a string according to the patterns and replacements an obfuscator has. + * + * @param input the string to obfuscate + * @return the obfuscate string + */ + public String obfuscate(String input) { + String obfuscatedString = input; + Set> entries = patternReplacementMap.entrySet(); + for (Map.Entry entry : entries) { + String replacement = entry.getValue(); + Pattern pattern = entry.getKey(); + Util.checkNull(replacement, "replacement"); + Matcher matcher = pattern.matcher(obfuscatedString); + obfuscatedString = matcher.replaceFirst(replacement); + } + return obfuscatedString; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RegexPatternObfuscator)) return false; + RegexPatternObfuscator that = (RegexPatternObfuscator) o; + return patternReplacementMap.equals(that.patternReplacementMap); + } + + @Override + public int hashCode() { + return patternReplacementMap.hashCode() + 13; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/TokenObfuscator.java b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/TokenObfuscator.java new file mode 100644 index 0000000000..a473fbc686 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/TokenObfuscator.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.log.obfuscator; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Obfuscator for oAuth2 token-related login requests. + *

              + * It will replace the + *

                + *
              • {@code "authCode":""},
              • + *
              • {@code "id_token":""},
              • + *
              • {@code "token":""}, and
              • + *
              • {@code "access_token":""}
              • + *
              + * patterns with + *
                + *
              • {@code "authCode":"***"},
              • + *
              • {@code "id_token":"***"},
              • + *
              • {@code "token":"***"}, and
              • + *
              • {@code "access_token":"***"}
              • + *
              + * respectively. + */ +public class TokenObfuscator extends RegexPatternObfuscator { + + public static final String AUTHCODE_KEY = "authCode"; + public static final String ID_TOKEN_KEY = "id_token"; + public static final String TOKEN_KEY = "token"; + public static final String ACCESS_TOKEN_KEY = "access_token"; + + private TokenObfuscator(Map patternReplacementMap) { + super(patternReplacementMap); + } + + /** + * Creates a {@link RegexPatternObfuscator} for tokens. + * + * @return an obfuscator that keeps token information from being displayed in the logcat. + */ + public static TokenObfuscator obfuscator() { + return new TokenObfuscator(getPatterns()); + } + + private static Map getPatterns() { + Map map = new HashMap<>(); + map.put(Pattern.compile("((\"" + AUTHCODE_KEY + "\"):(\".+?\"))"), "\"" + AUTHCODE_KEY + "\":\"***\""); + map.put(Pattern.compile("((\"" + ID_TOKEN_KEY + "\"):(\".+?\"))"), "\"" + ID_TOKEN_KEY + "\":\"***\""); + map.put(Pattern.compile("((\"" + TOKEN_KEY + "\"):(\".+?\"))"), "\"" + TOKEN_KEY + "\":\"***\""); + map.put(Pattern.compile("((\"" + ACCESS_TOKEN_KEY + "\"):(\".+?\"))"), "\"" + ACCESS_TOKEN_KEY + "\":\"***\""); + return map; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/LoggingInterceptor.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LoggingInterceptor.java new file mode 100644 index 0000000000..6d67657c46 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/LoggingInterceptor.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.network; + +import java.io.IOException; +import java.nio.charset.Charset; + +import javax.annotation.Nullable; + +import io.realm.log.LogLevel; +import io.realm.log.RealmLog; +import io.realm.mongodb.log.obfuscator.HttpLogObfuscator; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import okio.Buffer; + +/** + * The LoggingInterceptor prints information on the HTTP requests produced by a Realm app. + */ +public class LoggingInterceptor implements Interceptor { + + public static final String LOGIN_FEATURE = "providers"; + + private static final Charset UTF8 = Charset.forName("UTF-8"); + + @Nullable + private HttpLogObfuscator httpLogObfuscator; + + LoggingInterceptor(@Nullable HttpLogObfuscator httpLogObfuscator) { + this.httpLogObfuscator = httpLogObfuscator; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + if (RealmLog.getLevel() <= LogLevel.DEBUG) { + StringBuilder sb = new StringBuilder(request.method()); + sb.append(' '); + sb.append(request.url()); + sb.append('\n'); + sb.append(request.headers()); + if (request.body() != null) { + // Stripped down version of https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java + // We only expect request context to be JSON. + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + + // Obfuscate sensitive information if applicable + String input = buffer.readString(UTF8); + if (httpLogObfuscator != null) { + input = httpLogObfuscator.obfuscate(request.url().pathSegments(), input); + } + sb.append(input); + } + RealmLog.debug("HTTP Request = \n%s", sb); + } + return chain.proceed(request); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof LoggingInterceptor)) return false; + LoggingInterceptor that = (LoggingInterceptor) o; + if (httpLogObfuscator == null) { + return that.httpLogObfuscator == null; + } + return httpLogObfuscator.equals(that.httpLogObfuscator); + } + + @Override + public int hashCode() { + if (httpLogObfuscator == null) { + return super.hashCode(); + } + return httpLogObfuscator.hashCode() + 27; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index 896bbed2d2..83cdfeeb78 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -1,32 +1,36 @@ package io.realm.internal.network; import java.io.IOException; -import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +import io.realm.mongodb.log.obfuscator.HttpLogObfuscator; import io.realm.internal.objectstore.OsJavaNetworkTransport; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; import okhttp3.Call; import okhttp3.ConnectionPool; import okhttp3.Headers; -import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.ResponseBody; -import okio.Buffer; public class OkHttpNetworkTransport extends OsJavaNetworkTransport { - public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static final Charset UTF8 = Charset.forName("UTF-8"); + private volatile OkHttpClient client = null; + @Nullable + private final HttpLogObfuscator httpLogObfuscator; + + public OkHttpNetworkTransport(@Nullable HttpLogObfuscator httpLogObfuscator) { + this.httpLogObfuscator = httpLogObfuscator; + } + @Override public Response sendRequest(String method, String url, long timeoutMs, Map headers, String body) { try { @@ -75,28 +79,7 @@ private synchronized OkHttpClient getClient(long timeoutMs) { client = new OkHttpClient.Builder() .callTimeout(timeoutMs, TimeUnit.MILLISECONDS) .followRedirects(true) - .addInterceptor(new Interceptor() { - @Override - public okhttp3.Response intercept(Chain chain) throws IOException { - Request request = chain.request(); - if (RealmLog.getLevel() <= LogLevel.DEBUG) { - StringBuilder sb = new StringBuilder(request.method()); - sb.append(' '); - sb.append(request.url()); - sb.append('\n'); - sb.append(request.headers()); - if (request.body() != null) { - // Stripped down version of https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java - // We only expect request context to be JSON. - Buffer buffer = new Buffer(); - request.body().writeTo(buffer); - sb.append(buffer.readString(UTF8)); - } - RealmLog.debug("HTTP Request = \n%s", sb); - } - return chain.proceed(request); - } - }) + .addInterceptor(new LoggingInterceptor(httpLogObfuscator)) // using custom Connection Pool to evict idle connection after 5 seconds rather than 5 minutes (which is the default) // keeping idle connection on the pool will prevent the ROS to be stopped, since the HttpUtils#stopSyncServer query // will not return before the tests timeout (ex 10 seconds for AuthTests) @@ -107,7 +90,7 @@ public okhttp3.Response intercept(Chain chain) throws IOException { return client; } - // Parse Headers outputtet from OKHttp to the format expected by ObjectStore + // Parse Headers output from OKHttp to the format expected by ObjectStore private Map parseHeaders(Headers headers) { HashMap osHeaders = new HashMap<>(headers.size()/2); for (String key : headers.names()) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/util/BsonConverter.java deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index 2db63ff191..1c17d16be1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -189,7 +189,7 @@ public App(String appId) { */ public App(AppConfiguration config) { this.config = config; - this.networkTransport = new OkHttpNetworkTransport(); + this.networkTransport = new OkHttpNetworkTransport(config.getHttpLogObfuscator()); networkTransport.setAuthorizationHeaderName(config.getAuthorizationHeaderName()); for (Map.Entry entry : config.getCustomRequestHeaders().entrySet()) { networkTransport.addCustomRequestHeader(entry.getKey(), entry.getValue()); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index affceb2c53..44cc177ed6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -40,9 +40,17 @@ import io.realm.Realm; import io.realm.annotations.Beta; -import io.realm.mongodb.sync.SyncSession; import io.realm.internal.Util; +import io.realm.internal.log.obfuscator.ApiKeyObfuscator; +import io.realm.internal.log.obfuscator.CustomFunctionObfuscator; +import io.realm.internal.log.obfuscator.EmailPasswordObfuscator; +import io.realm.internal.log.obfuscator.RegexPatternObfuscator; +import io.realm.internal.log.obfuscator.TokenObfuscator; import io.realm.log.RealmLog; +import io.realm.mongodb.log.obfuscator.HttpLogObfuscator; +import io.realm.mongodb.sync.SyncSession; + +import static io.realm.internal.network.LoggingInterceptor.LOGIN_FEATURE; /** * A AppConfiguration is used to setup a MongoDB Realm application. @@ -103,30 +111,53 @@ public class AppConfiguration { ) ); + /** + * Default obfuscators for login requests used in a MongoDB Realm app. + *

              + * This map is needed to instantiate the default {@link HttpLogObfuscator}, which will keep all + * login-sensitive information from being shown in Logcat. + *

              + * This map's keys represent the different login identity providers which can be used to + * authenticate against an app and the values are the concrete obfuscators used for that + * provider. + * + * @see Credentials.IdentityProvider + * @see RegexPatternObfuscator + * @see ApiKeyObfuscator + * @see TokenObfuscator + * @see CustomFunctionObfuscator + * @see EmailPasswordObfuscator + * @see HttpLogObfuscator + */ + public static final Map loginObfuscators = getLoginObfuscators(); + private final String appId; private final String appName; private final String appVersion; private final URL baseUrl; private final SyncSession.ErrorHandler defaultErrorHandler; - @Nullable private final byte[] encryptionKey; + @Nullable + private final byte[] encryptionKey; private final long requestTimeoutMs; private final String authorizationHeaderName; private final Map customHeaders; private final File syncRootDir; // Root directory for storing Sync related files private final CodecRegistry codecRegistry; + @Nullable + private final HttpLogObfuscator httpLogObfuscator; private AppConfiguration(String appId, String appName, String appVersion, URL baseUrl, - SyncSession.ErrorHandler defaultErrorHandler, - @Nullable byte[] encryptionKey, + SyncSession.ErrorHandler defaultErrorHandler, + @Nullable byte[] encryptionKey, long requestTimeoutMs, String authorizationHeaderName, Map customHeaders, File syncRootdir, - CodecRegistry codecRegistry) { - + CodecRegistry codecRegistry, + @Nullable HttpLogObfuscator httpLogObfuscator) { this.appId = appId; this.appName = appName; this.appVersion = appVersion; @@ -138,6 +169,7 @@ private AppConfiguration(String appId, this.customHeaders = Collections.unmodifiableMap(customHeaders); this.syncRootDir = syncRootdir; this.codecRegistry = codecRegistry; + this.httpLogObfuscator = httpLogObfuscator; } /** @@ -224,11 +256,36 @@ public File getSyncRootDirectory() { * {@link io.realm.mongodb.mongo.MongoDatabase}. * * @return The default codec registry for the App. - * * @see #DEFAULT_BSON_CODEC_REGISTRY * @see Builder#getDefaultCodecRegistry() */ - public CodecRegistry getDefaultCodecRegistry() { return codecRegistry; } + public CodecRegistry getDefaultCodecRegistry() { + return codecRegistry; + } + + /** + * Returns the {@link HttpLogObfuscator} used in the app, which keeps sensitive information in + * HTTP requests from being displayed in the logcat. + * + * @return the HTTP log obfuscator. + */ + @Nullable + public HttpLogObfuscator getHttpLogObfuscator() { + return httpLogObfuscator; + } + + private static Map getLoginObfuscators() { + final HashMap obfuscators = new HashMap<>(); + obfuscators.put(Credentials.IdentityProvider.API_KEY.getId(), ApiKeyObfuscator.obfuscator()); + obfuscators.put(Credentials.IdentityProvider.SERVER_API_KEY.getId(), ApiKeyObfuscator.obfuscator()); + obfuscators.put(Credentials.IdentityProvider.APPLE.getId(), TokenObfuscator.obfuscator()); + obfuscators.put(Credentials.IdentityProvider.CUSTOM_FUNCTION.getId(), CustomFunctionObfuscator.obfuscator()); + obfuscators.put(Credentials.IdentityProvider.EMAIL_PASSWORD.getId(), EmailPasswordObfuscator.obfuscator()); + obfuscators.put(Credentials.IdentityProvider.FACEBOOK.getId(), TokenObfuscator.obfuscator()); + obfuscators.put(Credentials.IdentityProvider.GOOGLE.getId(), TokenObfuscator.obfuscator()); + obfuscators.put(Credentials.IdentityProvider.JWT.getId(), TokenObfuscator.obfuscator()); + return obfuscators; + } /** * Builder used to construct instances of a {@link AppConfiguration} in a fluent manner. @@ -268,6 +325,8 @@ public void onError(SyncSession session, AppException error) { private Map customHeaders = new HashMap<>(); private File syncRootDir; private CodecRegistry codecRegistry = DEFAULT_BSON_CODEC_REGISTRY; + @Nullable + private HttpLogObfuscator httpLogObfuscator = new HttpLogObfuscator(LOGIN_FEATURE, loginObfuscators); /** * Creates an instance of the Builder for the AppConfiguration. @@ -424,7 +483,7 @@ public Builder defaultSyncErrorHandler(SyncSession.ErrorHandler errorHandler) { * between the device and MongoDB Realm. *

              * The default root dir is {@code Context.getFilesDir()/mongodb-realm}. - *

              + * * @param rootDir where to store sync related files. */ public Builder syncRootDirectory(File rootDir) { @@ -461,7 +520,6 @@ private URL createUrl(String baseUrl) { * Will default to {@link #DEFAULT_BSON_CODEC_REGISTRY} if not specified. * * @param codecRegistry The default codec registry for the App. - * * @see #DEFAULT_BSON_CODEC_REGISTRY * @see Builder#getDefaultCodecRegistry() */ @@ -471,6 +529,19 @@ public Builder codecRegistry(CodecRegistry codecRegistry) { return this; } + /** + * Sets the {@link HttpLogObfuscator} used to keep sensitive information in HTTP requests + * from being displayed in the logcat. + *

              + * If left unspecified, it will default to obfuscating HTTP login requests. + * + * @param httpLogObfuscator the default HTTP log obfuscator for the app. + */ + public Builder httpLogObfuscator(@Nullable HttpLogObfuscator httpLogObfuscator) { + this.httpLogObfuscator = httpLogObfuscator; + return this; + } + /** * Creates the AppConfiguration. * @@ -487,7 +558,8 @@ public AppConfiguration build() { authorizationHeaderName, customHeaders, syncRootDir, - codecRegistry); + codecRegistry, + httpLogObfuscator); } } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/log/obfuscator/HttpLogObfuscator.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/log/obfuscator/HttpLogObfuscator.java new file mode 100644 index 0000000000..c792d11f54 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/log/obfuscator/HttpLogObfuscator.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.log.obfuscator; + +import java.util.List; +import java.util.Map; + +import io.realm.internal.Util; +import io.realm.internal.log.obfuscator.RegexPatternObfuscator; + +/** + * The HttpLogObfuscator keeps sensitive information from being displayed in Logcat. + */ +public class HttpLogObfuscator { + + private String feature; + private Map patternObfuscatorMap; + + /** + * Constructor for creating an HTTP log obfuscator. + * + * @param feature the feature to obfuscate, e.g. "providers" for login requests - + * see {@link io.realm.internal.network.LoggingInterceptor}. + * @param patternObfuscatorMap {@link Map} of keys subject to being obfuscated and + * {@link RegexPatternObfuscator}s used to determine which + * obfuscator has to be used for the given feature. + */ + public HttpLogObfuscator(String feature, Map patternObfuscatorMap) { + Util.checkNull(feature, "feature"); + this.feature = feature; + Util.checkNull(patternObfuscatorMap, "patternObfuscatorMap"); + this.patternObfuscatorMap = patternObfuscatorMap; + } + + /** + * Obfuscates a logcat entry or not depending on whether the request being sent matches the + * specified feature. If it doesn't, the logcat entry will be returned unmodified. + * + * @param urlSegments the URL segments of the request to be sent. + * @param input the original logcat entry. + * @return the logcat entry to be shown in the logcat. + */ + public String obfuscate(List urlSegments, String input) { + int featureIndex = urlSegments.indexOf(feature); + if (featureIndex != -1) { + String value = urlSegments.get(featureIndex + 1); // value is in the next segment + RegexPatternObfuscator patternObfuscator = patternObfuscatorMap.get(value); + if (patternObfuscator != null) { + return patternObfuscator.obfuscate(input); + } + } + return input; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof HttpLogObfuscator)) return false; + HttpLogObfuscator that = (HttpLogObfuscator) o; + return patternObfuscatorMap.equals(that.patternObfuscatorMap); + } + + @Override + public int hashCode() { + return patternObfuscatorMap.hashCode() + 13; + } +} diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt index c54f75b7a2..94bdf0f945 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt @@ -29,7 +29,10 @@ import io.realm.mongodb.AppConfiguration const val SERVICE_NAME = "BackingDB" // it comes from the test server's BackingDB/config.json const val DATABASE_NAME = "test_data" // same as above -class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it }) : App(createConfiguration(customizeConfig)) { +class TestApp( + networkTransport: OsJavaNetworkTransport? = null, + customizeConfig: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it } +) : App(createConfiguration(customizeConfig)) { init { if (networkTransport != null) { @@ -38,11 +41,13 @@ class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: } companion object { + fun createConfiguration(customizeConfig: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it }): AppConfiguration { var builder = AppConfiguration.Builder(initializeMongoDbRealm()) .baseUrl("http://127.0.0.1:9090") .appName("MongoDB Realm Integration Tests") .appVersion("1.0.") + .httpLogObfuscator(null) builder = customizeConfig(builder) @@ -51,7 +56,7 @@ class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: // Initializes MongoDB Realm. Clears all local state and fetches the application ID. private fun initializeMongoDbRealm(): String { - val transport = OkHttpNetworkTransport() + val transport = OkHttpNetworkTransport(null) val response = transport.sendRequest( "get", "http://127.0.0.1:8888/application-id", @@ -66,4 +71,3 @@ class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: } } } - diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt new file mode 100644 index 0000000000..c5e5813511 --- /dev/null +++ b/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm + +object ObfuscatorHelper { + const val IRRELEVANT_INPUT = """{"blahblahblah":"blehblehbleh"}""" + + val API_KEY_ORIGINAL_INPUT = """ +{ + "blahblahblah":"blehblehbleh", + "key":"my_key", + "something":"random" +} +""".trimIndent() + val API_KEY_OBFUSCATED_OUTPUT = """ +{ + "blahblahblah":"blehblehbleh", + "key":"***", + "something":"random" +} +""".trimIndent() + + val EMAIL_PASSWORD_ORIGINAL_INPUT = """ +{ + "blahblahblah":"blehblehbleh", + "username":"my_username", + "password":"123456", + "something":"random" +} +""".trimIndent() + val EMAIL_PASSWORD_OBFUSCATED_OUTPUT = """ +{ + "blahblahblah":"blehblehbleh", + "username":"***", + "password":"***", + "something":"random" +} +""".trimIndent() + + val CUSTOM_FUNCTION_ORIGINAL_INPUT = """ +{ + "mail":"myfakemail@mongodb.com", + "id":{ + "{${'$'}}numberInt": "666" + }, + "options":{ + "device":{ + "appVersion":"1.0.", + "appId":"realm-sdk-integration-tests-grbrc", + "platform":"android", + "platformVersion":"10", + "sdkVersion":"10.0.0-BETA.5-SNAPSHOT" + } + } +} +""".trimStartMultiline() + val CUSTOM_FUNCTION_OBFUSCATED_OUTPUT = """ +{ + "functionArgs":"***", + "options":{ + "device":{ + "appVersion":"1.0.", + "appId":"realm-sdk-integration-tests-grbrc", + "platform":"android", + "platformVersion":"10", + "sdkVersion":"10.0.0-BETA.5-SNAPSHOT" + } + } +} +""".trimStartMultiline() + + val TOKEN_ORIGINAL_INPUT_GENERIC = """ +{ + "blahblahblah":"blehblehbleh", + "token":"my_token", + "something":"random" +} +""".trimIndent() + val TOKEN_ORIGINAL_INPUT_APPLE = """ +{ + "blahblahblah":"blehblehbleh", + "id_token":"my_provider", + "something":"random" +} +""".trimIndent() + val TOKEN_ORIGINAL_INPUT_FACEBOOK = """ +{ + "blahblahblah":"blehblehbleh", + "access_token":"my_access_token", + "something":"random" +} +""".trimIndent() + val TOKEN_ORIGINAL_INPUT_GOOGLE = """ +{ + "blahblahblah":"blehblehbleh", + "authCode":"my_authCode", + "something":"random" +} +""".trimIndent() + val TOKEN_OBFUSCATED_OUTPUT_GENERIC = """ +{ + "blahblahblah":"blehblehbleh", + "token":"***", + "something":"random" +} +""".trimIndent() + val TOKEN_OBFUSCATED_OUTPUT_APPLE = """ +{ + "blahblahblah":"blehblehbleh", + "id_token":"***", + "something":"random" +} +""".trimIndent() + val TOKEN_OBFUSCATED_OUTPUT_FACEBOOK = """ +{ + "blahblahblah":"blehblehbleh", + "access_token":"***", + "something":"random" +} +""".trimIndent() + val TOKEN_OBFUSCATED_OUTPUT_GOOGLE = """ +{ + "blahblahblah":"blehblehbleh", + "authCode":"***", + "something":"random" +} +""".trimIndent() + + private fun String.trimStartMultiline(): String { + return this.split("\n").joinToString(separator = "") { it.trimStart() } + } +} diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/ApiKeyObfuscatorTest.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/ApiKeyObfuscatorTest.kt new file mode 100644 index 0000000000..eb7f1cd889 --- /dev/null +++ b/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/ApiKeyObfuscatorTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.internal.log.obfuscator + +import io.realm.ObfuscatorHelper +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class ApiKeyObfuscatorTest { + + @Test + fun obfuscate() { + ApiKeyObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.API_KEY_ORIGINAL_INPUT) + .let { assertEquals(ObfuscatorHelper.API_KEY_OBFUSCATED_OUTPUT, it) } + } + + @Test + fun obfuscate_doesNothing() { + ApiKeyObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.IRRELEVANT_INPUT) + .let { assertEquals(ObfuscatorHelper.IRRELEVANT_INPUT, it) } + } + + @Test + fun obfuscate_fails() { + assertFailsWith { + ApiKeyObfuscator.obfuscator().obfuscate(null) + } + } +} diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/CustomFunctionObfuscatorTest.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/CustomFunctionObfuscatorTest.kt new file mode 100644 index 0000000000..8896c4790d --- /dev/null +++ b/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/CustomFunctionObfuscatorTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.internal.log.obfuscator + +import io.realm.ObfuscatorHelper +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CustomFunctionObfuscatorTest { + + @Test + fun obfuscate() { + CustomFunctionObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.CUSTOM_FUNCTION_ORIGINAL_INPUT) + .let { assertEquals(ObfuscatorHelper.CUSTOM_FUNCTION_OBFUSCATED_OUTPUT, it) } + } + + @Test + fun obfuscate_doesNothing() { + CustomFunctionObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.IRRELEVANT_INPUT) + .let { assertEquals(ObfuscatorHelper.IRRELEVANT_INPUT, it) } + } + + @Test + fun obfuscate_fails() { + assertFailsWith { + CustomFunctionObfuscator.obfuscator().obfuscate(null) + } + } +} + + diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/EmailPasswordObfuscatorTest.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/EmailPasswordObfuscatorTest.kt new file mode 100644 index 0000000000..bf324ebcad --- /dev/null +++ b/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/EmailPasswordObfuscatorTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.internal.log.obfuscator + +import io.realm.ObfuscatorHelper +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class EmailPasswordObfuscatorTest { + + @Test + fun obfuscate() { + EmailPasswordObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.EMAIL_PASSWORD_ORIGINAL_INPUT) + .let { assertEquals(ObfuscatorHelper.EMAIL_PASSWORD_OBFUSCATED_OUTPUT, it) } + } + + @Test + fun obfuscate_doesNothing() { + EmailPasswordObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.IRRELEVANT_INPUT) + .let { assertEquals(ObfuscatorHelper.IRRELEVANT_INPUT, it) } + } + + @Test + fun obfuscate_fails() { + assertFailsWith { + EmailPasswordObfuscator.obfuscator().obfuscate(null) + } + } +} diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/TokenObfuscatorTest.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/TokenObfuscatorTest.kt new file mode 100644 index 0000000000..74d5875c5a --- /dev/null +++ b/realm/realm-library/src/testObjectServer/kotlin/io/realm/internal/log/obfuscator/TokenObfuscatorTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.internal.log.obfuscator + +import io.realm.ObfuscatorHelper +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class TokenObfuscatorTest { + + @Test + fun obfuscate() { + TokenObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.TOKEN_ORIGINAL_INPUT_GENERIC) + .let { assertEquals(ObfuscatorHelper.TOKEN_OBFUSCATED_OUTPUT_GENERIC, it) } + TokenObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.TOKEN_ORIGINAL_INPUT_APPLE) + .let { assertEquals(ObfuscatorHelper.TOKEN_OBFUSCATED_OUTPUT_APPLE, it) } + TokenObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.TOKEN_ORIGINAL_INPUT_FACEBOOK) + .let { assertEquals(ObfuscatorHelper.TOKEN_OBFUSCATED_OUTPUT_FACEBOOK, it) } + TokenObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.TOKEN_ORIGINAL_INPUT_GOOGLE) + .let { assertEquals(ObfuscatorHelper.TOKEN_OBFUSCATED_OUTPUT_GOOGLE, it) } + } + + @Test + fun obfuscate_doesNothing() { + TokenObfuscator.obfuscator() + .obfuscate(ObfuscatorHelper.IRRELEVANT_INPUT) + .let { assertEquals(ObfuscatorHelper.IRRELEVANT_INPUT, it) } + } + + @Test + fun obfuscate_fails() { + assertFailsWith { + TokenObfuscator.obfuscator().obfuscate(null) + } + } +} diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/mongodb/log/obfuscator/HttpLogObfuscatorTest.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/mongodb/log/obfuscator/HttpLogObfuscatorTest.kt new file mode 100644 index 0000000000..9509cf0cf6 --- /dev/null +++ b/realm/realm-library/src/testObjectServer/kotlin/io/realm/mongodb/log/obfuscator/HttpLogObfuscatorTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.mongodb.log.obfuscator + +import io.realm.ObfuscatorHelper +import io.realm.mongodb.AppConfiguration +import org.junit.Test +import kotlin.test.assertEquals + +const val IRRELEVANT_INPUT = """{"blahblahblah":"blehblehbleh"}""" +const val FEATURE = "providers" + +class HttpLogObfuscatorTest { + + private val apiKeyUrlSegments = listOf(FEATURE, "api-key") + private val customFunctionUrlSegments = listOf(FEATURE, "custom-function") + private val emailPasswordUrlSegments = listOf(FEATURE, "local-userpass") + private val tokenUrlSegmentsApple = listOf(FEATURE, "oauth2-apple") + private val tokenUrlSegmentsFacebook = listOf(FEATURE, "oauth2-facebook") + private val tokenUrlSegmentsGoogle = listOf(FEATURE, "oauth2-google") + + private val loginObfuscators = AppConfiguration.loginObfuscators + + @Test + fun obfuscate_nothing() { + with(HttpLogObfuscator(FEATURE, mapOf())) { + assertEquals(IRRELEVANT_INPUT, obfuscate(listOf(), IRRELEVANT_INPUT)) + } + } + + @Test + fun obfuscate_apiKey() { + with(HttpLogObfuscator(FEATURE, loginObfuscators)) { + assertEquals(ObfuscatorHelper.API_KEY_OBFUSCATED_OUTPUT, obfuscate(apiKeyUrlSegments, ObfuscatorHelper.API_KEY_ORIGINAL_INPUT)) + } + } + + @Test + fun obfuscate_customFunction() { + with(HttpLogObfuscator(FEATURE, loginObfuscators)) { + assertEquals(ObfuscatorHelper.CUSTOM_FUNCTION_OBFUSCATED_OUTPUT, obfuscate(customFunctionUrlSegments, ObfuscatorHelper.CUSTOM_FUNCTION_ORIGINAL_INPUT)) + } + } + + @Test + fun obfuscate_emailPassword() { + with(HttpLogObfuscator(FEATURE, loginObfuscators)) { + assertEquals(ObfuscatorHelper.EMAIL_PASSWORD_OBFUSCATED_OUTPUT, obfuscate(emailPasswordUrlSegments, ObfuscatorHelper.EMAIL_PASSWORD_ORIGINAL_INPUT)) + } + } + + @Test + fun obfuscate_tokenApple() { + with(HttpLogObfuscator(FEATURE, loginObfuscators)) { + assertEquals(ObfuscatorHelper.TOKEN_OBFUSCATED_OUTPUT_APPLE, obfuscate(tokenUrlSegmentsApple, ObfuscatorHelper.TOKEN_ORIGINAL_INPUT_APPLE)) + } + } + + @Test + fun obfuscate_tokenFacebook() { + with(HttpLogObfuscator(FEATURE, loginObfuscators)) { + assertEquals(ObfuscatorHelper.TOKEN_OBFUSCATED_OUTPUT_FACEBOOK, obfuscate(tokenUrlSegmentsFacebook, ObfuscatorHelper.TOKEN_ORIGINAL_INPUT_FACEBOOK)) + } + } + + @Test + fun obfuscate_tokenGoogle() { + with(HttpLogObfuscator(FEATURE, loginObfuscators)) { + assertEquals(ObfuscatorHelper.TOKEN_OBFUSCATED_OUTPUT_GOOGLE, obfuscate(tokenUrlSegmentsGoogle, ObfuscatorHelper.TOKEN_ORIGINAL_INPUT_GOOGLE)) + } + } +} diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 31826e8d75..cbeda876bd 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -23,6 +23,8 @@ import androidx.test.platform.app.InstrumentationRegistry; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.junit.Assert; import java.io.BufferedReader; @@ -50,6 +52,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.AnnotationIndexTypes; import io.realm.entities.BacklinksSource; @@ -73,12 +78,6 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; -import org.bson.types.Decimal128; -import org.bson.types.ObjectId; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - public class TestHelper { public static final int VERY_SHORT_WAIT_SECS = 1; public static final int SHORT_WAIT_SECS = 10; @@ -388,6 +387,7 @@ public static class TestLogger implements RealmLogger { private final int minimumLevel; public String message; + public String previousMessage; public Throwable throwable; public TestLogger() { @@ -401,6 +401,7 @@ public TestLogger(int minimumLevel) { @Override public void log(int level, String tag, Throwable throwable, String message) { if (minimumLevel <= level) { + this.previousMessage = this.message; this.message = message; this.throwable = throwable; } From 70013f98626654b472ff25ef405c8f16d4d8214d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 3 Jul 2020 16:52:05 +0200 Subject: [PATCH 1611/2110] Remove GMS Task and use RealmResultTask instead (#6984) --- CHANGELOG.md | 2 +- realm/realm-library/build.gradle | 2 - .../kotlin/io/realm/UserTests.kt | 3 +- .../io/realm/mongodb/MongoClientTest.kt | 383 +++++++------ .../kotlin/io/realm/util/TaskExt.kt | 49 -- .../kotlin/io/realm/util/TaskExtKtTest.kt | 54 -- realm/realm-library/src/main/cpp/object-store | 2 +- .../realm/internal/common/AsyncAdapter.java | 21 - .../io/realm/internal/common/Callback.java | 23 - .../internal/common/CallbackAsyncAdapter.java | 20 - .../io/realm/internal/common/Dispatcher.java | 25 - .../internal/common/OperationResult.java | 66 --- .../internal/common/TaskCallbackAdapter.java | 42 -- .../realm/internal/common/TaskDispatcher.java | 27 - .../internal/common/ThreadDispatcher.java | 76 --- .../internal/objectstore/OsMongoClient.java | 10 +- .../objectstore/OsMongoCollection.java | 17 +- .../internal/objectstore/OsMongoDatabase.java | 10 +- .../java/io/realm/mongodb/App.java | 2 +- .../java/io/realm/mongodb/User.java | 21 +- .../io/realm/mongodb/mongo/MongoClient.java | 11 +- .../realm/mongodb/mongo/MongoCollection.java | 514 +++++++++--------- .../io/realm/mongodb/mongo/MongoDatabase.java | 14 +- .../mongo/iterable/AggregateIterable.java | 10 +- .../mongodb/mongo/iterable/FindIterable.java | 13 +- .../mongodb/mongo/iterable/MongoCursor.java | 3 - .../mongodb/mongo/iterable/MongoIterable.java | 59 +- 27 files changed, 528 insertions(+), 951 deletions(-) delete mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExt.kt delete mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExtKtTest.kt delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/AsyncAdapter.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/Callback.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/CallbackAsyncAdapter.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/Dispatcher.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/OperationResult.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskCallbackAdapter.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskDispatcher.java delete mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java diff --git a/CHANGELOG.md b/CHANGELOG.md index abf6356a8e..40ab2465f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes -* None. +* Removed GMS Task framework and added RealmResultTask to provide with a mechanism to operate with asynchronous operations. MongoCollection has been updated to reflect this change. ### Enhancements * Credentials information (e.g. username, password) displayed in Logcat is now obfuscated by default, even if [LogLevel] is set to DEBUG, TRACE or ALL. diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 3ea77e1553..a594aed7a5 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -221,8 +221,6 @@ dependencies { implementation('io.reactivex.rxjava2:rxandroid:2.1.1') { exclude group: 'io.reactivex.rxjava2', module: 'rxjava' } - // FIXME: Attempt to find a way to remove this dependency - objectServerImplementation "com.google.android.gms:play-services-tasks:17.0.2" // added to support mongo client's asynchronous nature without breaking Stitch's API // TODO: investigate why we can't use the latest multidex version // check baseDebugAndroidTestRuntimeClasspath and objectServerDebugAndroidTestRuntimeClasspath diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt index fd08152399..a42b2a169d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt @@ -21,7 +21,6 @@ import io.realm.admin.ServerAdmin import io.realm.mongodb.* import io.realm.mongodb.auth.ApiKeyAuth import io.realm.rule.BlockingLooperThread -import io.realm.util.blockingGetResult import org.bson.Document import org.junit.After import org.junit.Assert.* @@ -480,7 +479,7 @@ class UserTests { val client = user.getMongoClient(SERVICE_NAME) client.getDatabase(DATABASE_NAME).let { it.getCollection(COLLECTION_NAME).also { collection -> - collection.insertOne(data.append(USER_ID_FIELD , user.id)).blockingGetResult() + collection.insertOne(data.append(USER_ID_FIELD , user.id)).get() } } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt index b63c23bac5..6d9719e701 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt @@ -26,7 +26,6 @@ import io.realm.mongodb.mongo.options.FindOneAndModifyOptions import io.realm.mongodb.mongo.options.FindOptions import io.realm.mongodb.mongo.options.UpdateOptions import io.realm.util.assertFailsWithErrorCode -import io.realm.util.blockingGetResult import io.realm.util.mongodb.CustomType import org.bson.Document import org.bson.codecs.configuration.CodecRegistries @@ -59,7 +58,7 @@ class MongoClientTest { fun tearDown() { if (this::client.isInitialized) { with(getCollectionInternal()) { - deleteMany(Document()).blockingGetResult() + deleteMany(Document()).get() } } if (this::app.isInitialized) { @@ -70,22 +69,22 @@ class MongoClientTest { @Test fun count() { with(getCollectionInternal()) { - assertEquals(0, count().blockingGetResult()) + assertEquals(0, count().get()) val rawDoc = Document("hello", "world") val doc1 = Document(rawDoc) val doc2 = Document(rawDoc) - insertOne(doc1).blockingGetResult() - assertEquals(1, count().blockingGetResult()) - insertOne(doc2).blockingGetResult() - assertEquals(2, count().blockingGetResult()) + insertOne(doc1).get() + assertEquals(1, count().get()) + insertOne(doc2).get() + assertEquals(2, count().get()) - assertEquals(2, count(rawDoc).blockingGetResult()) - assertEquals(0, count(Document("hello", "Friend")).blockingGetResult()) - assertEquals(1, count(rawDoc, CountOptions().limit(1)).blockingGetResult()) + assertEquals(2, count(rawDoc).get()) + assertEquals(0, count(Document("hello", "Friend")).get()) + assertEquals(1, count(rawDoc, CountOptions().limit(1)).get()) assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - count(Document("\$who", 1)).blockingGetResult() + count(Document("\$who", 1)).get() }.also { e -> assertTrue(e.errorMessage!!.contains("operator", true)) } @@ -96,7 +95,7 @@ class MongoClientTest { fun count_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - count(Document("\$who", 1)).blockingGetResult() + count(Document("\$who", 1)).get() }.also { e -> assertTrue(e.errorMessage!!.contains("operator", true)) } @@ -107,17 +106,17 @@ class MongoClientTest { fun findOne_nullResult() { with(getCollectionInternal()) { // Test findOne() on empty collection with no filter and no options - assertNull(findOne().blockingGetResult()) + assertNull(findOne().get()) // Test findOne() with filter that does not match any documents and no options - assertNull(findOne(Document("hello", "worldDNE")).blockingGetResult()) + assertNull(findOne(Document("hello", "worldDNE")).get()) val doc1 = Document("hello", "world1") - insertOne(doc1).blockingGetResult() - assertEquals(1, count().blockingGetResult()) + insertOne(doc1).get() + assertEquals(1, count().get()) // Test findOne() with filter that does not match any documents and no options - assertNull(findOne(Document("hello", "worldDNE")).blockingGetResult()) + assertNull(findOne(Document("hello", "worldDNE")).get()) } } @@ -127,25 +126,25 @@ class MongoClientTest { val doc1 = Document("hello", "world1") // Insert one document - insertOne(doc1).blockingGetResult() - assertEquals(1, count().blockingGetResult()) + insertOne(doc1).get() + assertEquals(1, count().get()) // No filter and no options - assertEquals(doc1, findOne().blockingGetResult()!!.withoutId()) + assertEquals(doc1, findOne().get()!!.withoutId()) // Projection (remove "_id") options val projection = Document("hello", 1).apply { this["_id"] = 0 } var options = FindOptions() .limit(2) .projection(projection) - assertEquals(doc1, findOne(Document(), options).blockingGetResult()!!) + assertEquals(doc1, findOne(Document(), options).get()!!) // Projection (remove "_id") and sort (by desc "hello") options options = FindOptions() .limit(2) .projection(projection) .sort(Document("hello", -1)) - assertEquals(doc1, findOne(Document(), options).blockingGetResult()!!) + assertEquals(doc1, findOne(Document(), options).get()!!) } } @@ -157,8 +156,8 @@ class MongoClientTest { val doc3 = Document("hello", "world3") // Insert 3 documents - insertMany(listOf(doc1, doc2, doc3)).blockingGetResult() - assertEquals(3, count().blockingGetResult()) + insertMany(listOf(doc1, doc2, doc3)).get() + assertEquals(3, count().get()) // Projection (remove "_id") and sort (by asc "hello") options val projection = Document("hello", 1).apply { this["_id"] = 0 } @@ -166,14 +165,14 @@ class MongoClientTest { .limit(2) .projection(projection) .sort(Document("hello", 1)) - assertEquals(doc1, findOne(Document(), options).blockingGetResult()!!) + assertEquals(doc1, findOne(Document(), options).get()!!) // Projection (remove "_id") and sort (by desc "hello") options options = FindOptions() .limit(2) .projection(projection) .sort(Document("hello", -1)) - assertEquals(doc3, findOne(Document(), options).blockingGetResult()!!) + assertEquals(doc3, findOne(Document(), options).get()!!) } } @@ -181,7 +180,7 @@ class MongoClientTest { fun findOne_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - findOne(Document("\$who", 1)).blockingGetResult() + findOne(Document("\$who", 1)).get() }.also { e -> assertTrue(e.errorMessage!!.contains("operator", true)) } @@ -193,37 +192,37 @@ class MongoClientTest { with(getCollectionInternal()) { // Find on an empty collection returns false on hasNext and null on first var iter = find() - assertFalse(iter.iterator().blockingGetResult()!!.hasNext()) - assertNull(iter.first().blockingGetResult()) + assertFalse(iter.iterator().get()!!.hasNext()) + assertNull(iter.first().get()) val doc1 = Document("hello", "world") val doc2 = Document("hello", "friend") doc2["proj"] = "field" - insertMany(listOf(doc1, doc2)).blockingGetResult() + insertMany(listOf(doc1, doc2)).get() // Iterate after inserting two documents - assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) - assertEquals(doc1, iter.first().blockingGetResult()!!.withoutId()) + assertTrue(iter.iterator().get()!!.hasNext()) + assertEquals(doc1, iter.first().get()!!.withoutId()) // Get next with sort by desc "_id" and limit to 1 document assertEquals(doc2, iter.limit(1) .sort(Document("_id", -1)) - .iterator().blockingGetResult()!! + .iterator().get()!! .next().withoutId()) // Find first document iter = find(doc1) - assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) + assertTrue(iter.iterator().get()!!.hasNext()) assertEquals(doc1, - iter.iterator().blockingGetResult()!! + iter.iterator().get()!! .next().withoutId()) // Find with filter for first document iter = find().filter(doc1) - assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) + assertTrue(iter.iterator().get()!!.hasNext()) assertEquals(doc1, - iter.iterator().blockingGetResult()!! + iter.iterator().get()!! .next().withoutId()) // Find with projection shows "proj" in result @@ -231,11 +230,11 @@ class MongoClientTest { assertEquals(expected, find(doc2) .projection(Document("proj", 1)) - .iterator().blockingGetResult()!! + .iterator().get()!! .next().withoutId()) // Getting a new iterator returns first element on tryNext - val asyncIter = iter.iterator().blockingGetResult()!! + val asyncIter = iter.iterator().get()!! assertEquals(doc1, asyncIter.tryNext().withoutId()) } } @@ -244,7 +243,7 @@ class MongoClientTest { fun find_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - find(Document("\$who", 1)).first().blockingGetResult() + find(Document("\$who", 1)).first().get() }.also { e -> assertTrue(e.errorMessage!!.contains("operator", true)) } @@ -256,26 +255,26 @@ class MongoClientTest { with(getCollectionInternal()) { // Aggregate on an empty collection returns false on hasNext and null on first var iter = aggregate(listOf()) - assertFalse(iter.iterator().blockingGetResult()!!.hasNext()) - assertNull(iter.first().blockingGetResult()) + assertFalse(iter.iterator().get()!!.hasNext()) + assertNull(iter.first().get()) // Iterate after inserting two documents val doc1 = Document("hello", "world") val doc2 = Document("hello", "friend") - insertMany(listOf(doc1, doc2)).blockingGetResult() - assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) - assertEquals(doc1.withoutId(), iter.first().blockingGetResult()!!.withoutId()) + insertMany(listOf(doc1, doc2)).get() + assertTrue(iter.iterator().get()!!.hasNext()) + assertEquals(doc1.withoutId(), iter.first().get()!!.withoutId()) // Aggregate with pipeline, sort by desc "_id" and limit to 1 document iter = aggregate(listOf(Document("\$sort", Document("_id", -1)), Document("\$limit", 1))) assertEquals(doc2.withoutId(), - iter.iterator().blockingGetResult()!! + iter.iterator().get()!! .next().withoutId()) // Aggregate with pipeline, match first document iter = aggregate(listOf(Document("\$match", doc1))) - assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) - assertEquals(doc1.withoutId(), iter.iterator().blockingGetResult()!!.next().withoutId()) + assertTrue(iter.iterator().get()!!.hasNext()) + assertEquals(doc1.withoutId(), iter.iterator().get()!!.next().withoutId()) } } @@ -283,7 +282,7 @@ class MongoClientTest { fun aggregate_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - aggregate(listOf(Document("\$who", 1))).first().blockingGetResult() + aggregate(listOf(Document("\$who", 1))).first().get() }.also { e -> assertTrue(e.errorMessage!!.contains("pipeline", true)) } @@ -294,12 +293,12 @@ class MongoClientTest { fun insertOne() { with(getCollectionInternal()) { val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } - assertEquals(doc1.getObjectId("_id"), insertOne(doc1).blockingGetResult()!!.insertedId.asObjectId().value) - assertEquals(1, count().blockingGetResult()) + assertEquals(doc1.getObjectId("_id"), insertOne(doc1).get()!!.insertedId.asObjectId().value) + assertEquals(1, count().get()) val doc2 = Document("hello", "world") - assertNotEquals(doc1.getObjectId("_id"), insertOne(doc2).blockingGetResult()!!.insertedId.asObjectId().value) - assertEquals(2, count().blockingGetResult()) + assertNotEquals(doc1.getObjectId("_id"), insertOne(doc2).get()!!.insertedId.asObjectId().value) + assertEquals(2, count().get()) } } @@ -307,10 +306,10 @@ class MongoClientTest { fun insertOne_fails() { with(getCollectionInternal()) { val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } - insertOne(doc1).blockingGetResult() + insertOne(doc1).get() assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - insertOne(doc1).blockingGetResult() + insertOne(doc1).get() }.also { e -> assertTrue(e.errorMessage!!.contains("duplicate", true)) } @@ -323,15 +322,15 @@ class MongoClientTest { val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } assertEquals(doc1.getObjectId("_id"), - insertMany(listOf(doc1)).blockingGetResult()!!.insertedIds[0]!!.asObjectId().value) + insertMany(listOf(doc1)).get()!!.insertedIds[0]!!.asObjectId().value) val doc2 = Document("hello", "world") - assertNotEquals(doc1.getObjectId("_id"), insertMany(listOf(doc2)).blockingGetResult()!!.insertedIds[0]!!.asObjectId().value) + assertNotEquals(doc1.getObjectId("_id"), insertMany(listOf(doc2)).get()!!.insertedIds[0]!!.asObjectId().value) val doc3 = Document("one", "two") val doc4 = Document("three", 4) - insertMany(listOf(doc3, doc4)).blockingGetResult() + insertMany(listOf(doc3, doc4)).get() } } @@ -339,10 +338,10 @@ class MongoClientTest { fun insertMany_singleDocument_fails() { with(getCollectionInternal()) { val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } - insertMany(listOf(doc1)).blockingGetResult() + insertMany(listOf(doc1)).get() assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - insertMany(listOf(doc1)).blockingGetResult() + insertMany(listOf(doc1)).get() }.also { e -> assertTrue(e.errorMessage!!.contains("duplicate", true)) } @@ -356,7 +355,7 @@ class MongoClientTest { val doc2 = Document("hello", "world").apply { this["_id"] = ObjectId() } val documents = listOf(doc1, doc2) - insertMany(documents).blockingGetResult()!! + insertMany(documents).get()!! .insertedIds .forEach { entry -> assertEquals(documents[entry.key.toInt()]["_id"], entry.value.asObjectId().value) @@ -365,8 +364,8 @@ class MongoClientTest { val doc3 = Document("one", "two") val doc4 = Document("three", 4) - insertMany(listOf(doc3, doc4)).blockingGetResult() - assertEquals(4, count().blockingGetResult()) + insertMany(listOf(doc3, doc4)).get() + assertEquals(4, count().get()) } } @@ -376,10 +375,10 @@ class MongoClientTest { val doc1 = Document("hello", "world").apply { this["_id"] = ObjectId() } val doc2 = Document("hello", "world").apply { this["_id"] = ObjectId() } val documents = listOf(doc1, doc2) - insertMany(documents).blockingGetResult() + insertMany(documents).get() assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - insertMany(documents).blockingGetResult() + insertMany(documents).get() }.also { e -> assertTrue(e.errorMessage!!.contains("duplicate", true)) } @@ -389,14 +388,14 @@ class MongoClientTest { @Test fun deleteOne_singleDocument() { with(getCollectionInternal()) { - assertEquals(0, deleteOne(Document()).blockingGetResult()!!.deletedCount) - assertEquals(0, deleteOne(Document("hello", "world")).blockingGetResult()!!.deletedCount) + assertEquals(0, deleteOne(Document()).get()!!.deletedCount) + assertEquals(0, deleteOne(Document("hello", "world")).get()!!.deletedCount) val doc1 = Document("hello", "world") - insertOne(doc1).blockingGetResult() - assertEquals(1, deleteOne(doc1).blockingGetResult()!!.deletedCount) - assertEquals(0, count().blockingGetResult()) + insertOne(doc1).get() + assertEquals(1, deleteOne(doc1).get()!!.deletedCount) + assertEquals(0, count().get()) } } @@ -404,7 +403,7 @@ class MongoClientTest { fun deleteOne_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - deleteOne(Document("\$who", 1)).blockingGetResult() + deleteOne(Document("\$who", 1)).get() }.also { e -> assertTrue(e.errorMessage!!.contains("operator", true)) } @@ -414,54 +413,54 @@ class MongoClientTest { @Test fun deleteOne_multipleDocuments() { with(getCollectionInternal()) { - assertEquals(0, count().blockingGetResult()) + assertEquals(0, count().get()) val rawDoc = Document("hello", "world") val doc1 = Document(rawDoc) val doc1b = Document(rawDoc) val doc2 = Document("foo", "bar") val doc3 = Document("42", "666") - insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() - assertEquals(1, deleteOne(rawDoc).blockingGetResult()!!.deletedCount) - assertEquals(1, deleteOne(Document()).blockingGetResult()!!.deletedCount) - assertEquals(2, count().blockingGetResult()) + insertMany(listOf(doc1, doc1b, doc2, doc3)).get() + assertEquals(1, deleteOne(rawDoc).get()!!.deletedCount) + assertEquals(1, deleteOne(Document()).get()!!.deletedCount) + assertEquals(2, count().get()) } } @Test fun deleteMany_singleDocument() { with(getCollectionInternal()) { - assertEquals(0, count().blockingGetResult()) + assertEquals(0, count().get()) val rawDoc = Document("hello", "world") val doc1 = Document(rawDoc) - insertOne(doc1).blockingGetResult() - assertEquals(1, count().blockingGetResult()) - assertEquals(1, deleteMany(doc1).blockingGetResult()!!.deletedCount) - assertEquals(0, count().blockingGetResult()) + insertOne(doc1).get() + assertEquals(1, count().get()) + assertEquals(1, deleteMany(doc1).get()!!.deletedCount) + assertEquals(0, count().get()) } } @Test fun deleteMany_multipleDocuments() { with(getCollectionInternal()) { - assertEquals(0, count().blockingGetResult()) + assertEquals(0, count().get()) val rawDoc = Document("hello", "world") val doc1 = Document(rawDoc) val doc1b = Document(rawDoc) val doc2 = Document("foo", "bar") val doc3 = Document("42", "666") - insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() - assertEquals(2, deleteMany(rawDoc).blockingGetResult()!!.deletedCount) // two docs will be deleted - assertEquals(2, count().blockingGetResult()) // two docs still present - assertEquals(2, deleteMany(Document()).blockingGetResult()!!.deletedCount) // delete all - assertEquals(0, count().blockingGetResult()) - - insertMany(listOf(doc1, doc1b, doc2, doc3)).blockingGetResult() - assertEquals(4, deleteMany(Document()).blockingGetResult()!!.deletedCount) // delete all - assertEquals(0, count().blockingGetResult()) + insertMany(listOf(doc1, doc1b, doc2, doc3)).get() + assertEquals(2, deleteMany(rawDoc).get()!!.deletedCount) // two docs will be deleted + assertEquals(2, count().get()) // two docs still present + assertEquals(2, deleteMany(Document()).get()!!.deletedCount) // delete all + assertEquals(0, count().get()) + + insertMany(listOf(doc1, doc1b, doc2, doc3)).get() + assertEquals(4, deleteMany(Document()).get()!!.deletedCount) // delete all + assertEquals(0, count().get()) } } @@ -469,7 +468,7 @@ class MongoClientTest { fun deleteMany_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - deleteMany(Document("\$who", 1)).blockingGetResult() + deleteMany(Document("\$who", 1)).get() }.also { e -> assertTrue(e.errorMessage!!.contains("operator", true)) } @@ -483,7 +482,7 @@ class MongoClientTest { // Update on an empty collection updateOne(Document(), doc1) - .blockingGetResult()!! + .get()!! .let { assertEquals(0, it.matchedCount) assertEquals(0, it.modifiedCount) @@ -493,12 +492,12 @@ class MongoClientTest { // Update on an empty collection adding some values val doc2 = Document("\$set", Document("woof", "meow")) updateOne(Document(), doc2) - .blockingGetResult()!! + .get()!! .let { assertEquals(0, it.matchedCount) assertEquals(0, it.modifiedCount) assertNull(it.upsertedId) - assertEquals(0, count().blockingGetResult()) + assertEquals(0, count().get()) } } } @@ -511,15 +510,15 @@ class MongoClientTest { // Update on empty collection with upsert val options = UpdateOptions().upsert(true) updateOne(Document(), doc1, options) - .blockingGetResult()!! + .get()!! .let { assertEquals(0, it.matchedCount) assertEquals(0, it.modifiedCount) assertFalse(it.upsertedId!!.isNull) } - assertEquals(1, count().blockingGetResult()) + assertEquals(1, count().get()) - assertEquals(doc1, find(Document()).first().blockingGetResult()!!.withoutId()) + assertEquals(doc1, find(Document()).first().get()!!.withoutId()) } } @@ -527,7 +526,7 @@ class MongoClientTest { fun updateOne_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - updateOne(Document("\$who", 1), Document()).blockingGetResult() + updateOne(Document("\$who", 1), Document()).get() }.also { e -> assertTrue(e.errorMessage!!.contains("operator", true)) } @@ -541,13 +540,13 @@ class MongoClientTest { // Update on empty collection updateMany(Document(), doc1) - .blockingGetResult()!! + .get()!! .let { assertEquals(0, it.matchedCount) assertEquals(0, it.modifiedCount) assertNull(it.upsertedId) } - assertEquals(0, count().blockingGetResult()) + assertEquals(0, count().get()) } } @@ -558,38 +557,38 @@ class MongoClientTest { // Update on empty collection with upsert updateMany(Document(), doc1, UpdateOptions().upsert(true)) - .blockingGetResult()!! + .get()!! .let { assertEquals(0, it.matchedCount) assertEquals(0, it.modifiedCount) assertNotNull(it.upsertedId) } - assertEquals(1, count().blockingGetResult()) + assertEquals(1, count().get()) // Add new value using update val update = Document("woof", "meow") updateMany(Document(), Document("\$set", update)) - .blockingGetResult()!! + .get()!! .let { assertEquals(1, it.matchedCount) assertEquals(1, it.modifiedCount) assertNull(it.upsertedId) } - assertEquals(1, count().blockingGetResult()) + assertEquals(1, count().get()) val expected = Document(doc1).apply { this["woof"] = "meow" } - assertEquals(expected, find().first().blockingGetResult()!!.withoutId()) + assertEquals(expected, find().first().get()!!.withoutId()) // Insert empty document, add ["woof", "meow"] to it and check it worked - insertOne(Document()).blockingGetResult() + insertOne(Document()).get() updateMany(Document(), Document("\$set", update)) - .blockingGetResult()!! + .get()!! .let { assertEquals(2, it.matchedCount) assertEquals(2, it.modifiedCount) } - assertEquals(2, count().blockingGetResult()) + assertEquals(2, count().get()) find().iterator() - .blockingGetResult()!! + .get()!! .let { assertEquals(expected, it.next().withoutId()) assertEquals(update, it.next().withoutId()) @@ -602,7 +601,7 @@ class MongoClientTest { fun updateMany_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - updateMany(Document("\$who", 1), Document()).blockingGetResult() + updateMany(Document("\$who", 1), Document()).get() }.also { e -> assertTrue(e.errorMessage!!.contains("operator", true)) } @@ -613,15 +612,15 @@ class MongoClientTest { fun findOneAndUpdate_emptyCollection() { with(getCollectionInternal()) { // Test null return format - assertNull(findOneAndUpdate(Document(), Document()).blockingGetResult()) + assertNull(findOneAndUpdate(Document(), Document()).get()) } } @Test fun findOneAndUpdate_noUpdates() { with(getCollectionInternal()) { - assertNull(findOneAndUpdate(Document(), Document()).blockingGetResult()) - assertEquals(0, count().blockingGetResult()) + assertNull(findOneAndUpdate(Document(), Document()).get()) + assertEquals(0, count().get()) } } @@ -632,26 +631,26 @@ class MongoClientTest { sampleDoc["num"] = 2 // Insert a sample Document - insertOne(sampleDoc).blockingGetResult() - assertEquals(1, count().blockingGetResult()) + insertOne(sampleDoc).get() + assertEquals(1, count().get()) // Sample call to findOneAndUpdate() where we get the previous document back val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { this["\$inc"] = Document("num", 1) } findOneAndUpdate(Document("hello", "world1"), sampleUpdate) - .blockingGetResult()!! + .get()!! .withoutId() .let { assertEquals(sampleDoc.withoutId(), it) } - assertEquals(1, count().blockingGetResult()) + assertEquals(1, count().get()) // Make sure the update took place val expectedDoc = Document("hello", "hellothere") expectedDoc["num"] = 3 - assertEquals(expectedDoc.withoutId(), find().first().blockingGetResult()!!.withoutId()) - assertEquals(1, count().blockingGetResult()) + assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) + assertEquals(1, count().get()) // Call findOneAndUpdate() again but get the new document sampleUpdate.remove("\$set") @@ -659,16 +658,16 @@ class MongoClientTest { val options = FindOneAndModifyOptions() .returnNewDocument(true) findOneAndUpdate(Document("hello", "hellothere"), sampleUpdate, options) - .blockingGetResult()!! + .get()!! .withoutId() .let { assertEquals(expectedDoc.withoutId(), it) } - assertEquals(1, count().blockingGetResult()) + assertEquals(1, count().get()) // Test null behaviour again with a filter that should not match any documents - assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).blockingGetResult()) - assertEquals(1, count().blockingGetResult()) + assertNull(findOneAndUpdate(Document("hello", "zzzzz"), Document()).get()) + assertEquals(1, count().get()) } } @@ -688,29 +687,29 @@ class MongoClientTest { val update1 = Document("\$set", doc1) assertEquals(doc1, findOneAndUpdate(filter, update1, options) - .blockingGetResult()!! + .get()!! .withoutId()) - assertEquals(1, count().blockingGetResult()) + assertEquals(1, count().get()) assertEquals(doc1.withoutId(), find().first() - .blockingGetResult()!! + .get()!! .withoutId()) // Test the upsert option where the server should perform upsert and return new document val update2 = Document("\$set", doc2) assertEquals(doc2, findOneAndUpdate(filter, update2, options) - .blockingGetResult()!! + .get()!! .withoutId()) - assertEquals(2, count().blockingGetResult()) + assertEquals(2, count().get()) // Test the upsert option where the server should perform upsert and return old document // The old document should be empty options = FindOneAndModifyOptions() .upsert(true) val update = Document("\$set", doc3) - assertNull(findOneAndUpdate(filter, update, options).blockingGetResult()) - assertEquals(3, count().blockingGetResult()) + assertNull(findOneAndUpdate(filter, update, options).get()) + assertEquals(3, count().get()) } } @@ -731,18 +730,18 @@ class MongoClientTest { .sort(Document("num", 1)) assertEquals(Document("hello", "world1"), findOneAndUpdate(Document(), sampleUpdate, options) - .blockingGetResult()!! + .get()!! .withoutId()) - assertEquals(3, count().blockingGetResult()) + assertEquals(3, count().get()) options = FindOneAndModifyOptions() .projection(sampleProject) .sort(Document("num", -1)) assertEquals(Document("hello", "world3"), findOneAndUpdate(Document(), sampleUpdate, options) - .blockingGetResult()!! + .get()!! .withoutId()) - assertEquals(3, count().blockingGetResult()) + assertEquals(3, count().get()) } } @@ -750,13 +749,13 @@ class MongoClientTest { fun findOneAndUpdate_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - findOneAndUpdate(Document(), Document("\$who", 1)).blockingGetResult() + findOneAndUpdate(Document(), Document("\$who", 1)).get() }.also { e -> assertTrue(e.errorMessage!!.contains("modifier", true)) } assertFailsWithErrorCode(ErrorCode.MONGODB_ERROR) { - findOneAndUpdate(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).blockingGetResult() + findOneAndUpdate(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() }.also { e -> assertTrue(e.errorMessage!!.contains("modifier", true)) } @@ -767,10 +766,10 @@ class MongoClientTest { fun findOneAndReplace_noUpdates() { with(getCollectionInternal()) { // Test null behaviour again with a filter that should not match any documents - assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).blockingGetResult()) - assertEquals(0, count().blockingGetResult()) - assertNull(findOneAndReplace(Document(), Document()).blockingGetResult()) - assertEquals(0, count().blockingGetResult()) + assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) + assertEquals(0, count().get()) + assertNull(findOneAndReplace(Document(), Document()).get()) + assertEquals(0, count().get()) } } @@ -780,30 +779,30 @@ class MongoClientTest { val sampleDoc = Document("hello", "world1").apply { this["num"] = 2 } // Insert a sample Document - insertOne(sampleDoc).blockingGetResult() - assertEquals(1, count().blockingGetResult()) + insertOne(sampleDoc).get() + assertEquals(1, count().get()) // Sample call to findOneAndReplace() where we get the previous document back var sampleUpdate = Document("hello", "world2").apply { this["num"] = 2 } assertEquals(sampleDoc.withoutId(), - findOneAndReplace(Document("hello", "world1"), sampleUpdate).blockingGetResult()!!.withoutId()) - assertEquals(1, count().blockingGetResult()) + findOneAndReplace(Document("hello", "world1"), sampleUpdate).get()!!.withoutId()) + assertEquals(1, count().get()) // Make sure the update took place val expectedDoc = Document("hello", "world2").apply { this["num"] = 2 } - assertEquals(expectedDoc.withoutId(), find().first().blockingGetResult()!!.withoutId()) - assertEquals(1, count().blockingGetResult()) + assertEquals(expectedDoc.withoutId(), find().first().get()!!.withoutId()) + assertEquals(1, count().get()) // Call findOneAndReplace() again but get the new document sampleUpdate = Document("hello", "world3").apply { this["num"] = 3 } val options = FindOneAndModifyOptions().returnNewDocument(true) assertEquals(sampleUpdate.withoutId(), - findOneAndReplace(Document(), sampleUpdate, options).blockingGetResult()!!.withoutId()) - assertEquals(1, count().blockingGetResult()) + findOneAndReplace(Document(), sampleUpdate, options).get()!!.withoutId()) + assertEquals(1, count().get()) // Test null behaviour again with a filter that should not match any documents - assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).blockingGetResult()) - assertEquals(1, count().blockingGetResult()) + assertNull(findOneAndReplace(Document("hello", "zzzzz"), Document()).get()) + assertEquals(1, count().get()) } } @@ -821,21 +820,21 @@ class MongoClientTest { .upsert(true) assertEquals(doc4.withoutId(), findOneAndReplace(Document("hello", "world3"), doc4, options) - .blockingGetResult()!! + .get()!! .withoutId()) - assertEquals(1, count().blockingGetResult()) - assertEquals(doc4.withoutId(), find().first().blockingGetResult()!!.withoutId()) + assertEquals(1, count().get()) + assertEquals(doc4.withoutId(), find().first().get()!!.withoutId()) // Test the upsert option where the server should perform upsert and return new document options = FindOneAndModifyOptions().returnNewDocument(true).upsert(true) - assertEquals(doc5.withoutId(), findOneAndReplace(Document("hello", "hellothere"), doc5, options).blockingGetResult()!!.withoutId()) - assertEquals(2, count().blockingGetResult()) + assertEquals(doc5.withoutId(), findOneAndReplace(Document("hello", "hellothere"), doc5, options).get()!!.withoutId()) + assertEquals(2, count().get()) // Test the upsert option where the server should perform upsert and return old document // The old document should be empty options = FindOneAndModifyOptions().upsert(true) - assertNull(findOneAndReplace(Document("hello", "hellothere"), doc6, options).blockingGetResult()) - assertEquals(3, count().blockingGetResult()) + assertNull(findOneAndReplace(Document("hello", "hellothere"), doc6, options).get()) + assertEquals(3, count().get()) } } @@ -851,16 +850,16 @@ class MongoClientTest { sampleUpdate["num"] = 0 var options = FindOneAndModifyOptions().projection(sampleProject).sort(Document("num", 1)) - val result = findOneAndReplace(Document(), sampleUpdate, options).blockingGetResult() + val result = findOneAndReplace(Document(), sampleUpdate, options).get() assertEquals(Document("hello", "world4"), result!!.withoutId()) - assertEquals(3, count().blockingGetResult()) + assertEquals(3, count().get()) options = FindOneAndModifyOptions() .projection(sampleProject) .sort(Document("num", -1)) assertEquals(Document("hello", "world6"), - findOneAndReplace(Document(), sampleUpdate, options).blockingGetResult()!!.withoutId()) - assertEquals(3, count().blockingGetResult()) + findOneAndReplace(Document(), sampleUpdate, options).get()!!.withoutId()) + assertEquals(3, count().get()) } } @@ -868,11 +867,11 @@ class MongoClientTest { fun findOneAndReplace_fails() { with(getCollectionInternal()) { assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { - findOneAndReplace(Document(), Document("\$who", 1)).blockingGetResult() + findOneAndReplace(Document(), Document("\$who", 1)).get() } assertFailsWithErrorCode(ErrorCode.INVALID_PARAMETER) { - findOneAndReplace(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).blockingGetResult() + findOneAndReplace(Document(), Document("\$who", 1), FindOneAndModifyOptions().upsert(true)).get() } } } @@ -884,44 +883,44 @@ class MongoClientTest { // Collection should start out empty // This also tests the null return format - assertNull(findOneAndDelete(Document()).blockingGetResult()) + assertNull(findOneAndDelete(Document()).get()) // Insert a sample Document - insertOne(sampleDoc).blockingGetResult() - assertEquals(1, count().blockingGetResult()) + insertOne(sampleDoc).get() + assertEquals(1, count().get()) // Sample call to findOneAndDelete() where we delete the only doc in the collection assertEquals(sampleDoc.withoutId(), - findOneAndDelete(Document()).blockingGetResult()!!.withoutId()) + findOneAndDelete(Document()).get()!!.withoutId()) // There should be no documents in the collection now - assertEquals(0, count().blockingGetResult()) + assertEquals(0, count().get()) // Insert a sample Document - insertOne(sampleDoc).blockingGetResult() - assertEquals(1, count().blockingGetResult()) + insertOne(sampleDoc).get() + assertEquals(1, count().get()) // Call findOneAndDelete() again but this time with a filter assertEquals(sampleDoc.withoutId(), - findOneAndDelete(Document("hello", "world1")).blockingGetResult()!!.withoutId()) + findOneAndDelete(Document("hello", "world1")).get()!!.withoutId()) // There should be no documents in the collection now - assertEquals(0, count().blockingGetResult()) + assertEquals(0, count().get()) // Insert a sample Document - insertOne(sampleDoc).blockingGetResult() - assertEquals(1, count().blockingGetResult()) + insertOne(sampleDoc).get() + assertEquals(1, count().get()) // Test null behaviour again with a filter that should not match any documents - assertNull(findOneAndDelete(Document("hello", "zzzzz")).blockingGetResult()) - assertEquals(1, count().blockingGetResult()) + assertNull(findOneAndDelete(Document("hello", "zzzzz")).get()) + assertEquals(1, count().get()) val doc2 = Document("hello", "world2").apply { this["num"] = 2 } val doc3 = Document("hello", "world3").apply { this["num"] = 3 } // Insert new documents - insertMany(listOf(doc2, doc3)).blockingGetResult() - assertEquals(3, count().blockingGetResult()) + insertMany(listOf(doc2, doc3)).get() + assertEquals(3, count().get()) } } @@ -933,7 +932,7 @@ class MongoClientTest { val doc2 = Document("hello", "world2").apply { this["num"] = 2 } val doc3 = Document("hello", "world3").apply { this["num"] = 3 } - insertMany(listOf(doc2, doc3)).blockingGetResult() + insertMany(listOf(doc2, doc3)).get() // Return "hello", hide "_id" val sampleProject = Document("hello", 1).apply { this["_id"] = 0 } @@ -942,15 +941,15 @@ class MongoClientTest { .projection(sampleProject) .sort(Document("num", -1)) assertEquals(Document("hello", "world3"), - findOneAndDelete(Document(), options).blockingGetResult()!!.withoutId()) - assertEquals(2, count().blockingGetResult()) + findOneAndDelete(Document(), options).get()!!.withoutId()) + assertEquals(2, count().get()) options = FindOneAndModifyOptions() .projection(sampleProject) .sort(Document("num", 1)) assertEquals(Document("hello", "world1"), - findOneAndDelete(Document(), options).blockingGetResult()!!.withoutId()) - assertEquals(1, count().blockingGetResult()) + findOneAndDelete(Document(), options).get()!!.withoutId()) + assertEquals(1, count().get()) } } @@ -970,7 +969,7 @@ class MongoClientTest { assertEquals(CustomType::class.java, coll.documentClass) assertFailsWith(AppException::class) { - coll.insertOne(expected).blockingGetResult() + coll.insertOne(expected).get() } val defaultCodecRegistry = AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY @@ -979,8 +978,8 @@ class MongoClientTest { // Use expanded registry coll = coll.withCodecRegistry(expandedCodecRegistry) assertEquals(expected.id, - coll.insertOne(expected).blockingGetResult()!!.insertedId.asObjectId().value) - assertEquals(expected, coll.find().first().blockingGetResult()) + coll.insertOne(expected).get()!!.insertedId.asObjectId().value) + assertEquals(expected, coll.find().first().get()) } val expected2 = CustomType(null, 42) @@ -988,8 +987,8 @@ class MongoClientTest { // Now get new collection for CustomType with(getCollectionInternal(CustomType::class.java) .withCodecRegistry(expandedCodecRegistry)) { - insertOne(expected2).blockingGetResult()!! - val actual: CustomType = find().first().blockingGetResult()!! + insertOne(expected2).get()!! + val actual: CustomType = find().first().get()!! assertEquals(expected2.intValue, actual.intValue) } @@ -997,13 +996,13 @@ class MongoClientTest { .withCodecRegistry(expandedCodecRegistry)) { val actual: CustomType = find(Document(), CustomType::class.java) .first() - .blockingGetResult()!! + .get()!! assertEquals(expected2.intValue, actual.intValue) assertNotNull(expected.id) val iter = aggregate(listOf(Document("\$match", Document())), CustomType::class.java) - assertTrue(iter.iterator().blockingGetResult()!!.hasNext()) - assertEquals(expected, iter.iterator().blockingGetResult()!!.next()) + assertTrue(iter.iterator().get()!!.hasNext()) + assertEquals(expected, iter.iterator().get()!!.next()) } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExt.kt deleted file mode 100644 index 0be79878c8..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExt.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.util - -import com.google.android.gms.tasks.Task -import java.util.concurrent.CountDownLatch - -/** - * Returns the result of a [Task] in a synchronous way or will throw an exception if a failure is - * detected. This operation blocks the thread on which it is called. - * - * @return the [T] result emitted by the task - */ -fun Task.blockingGetResult(): T? { - val countDownLatch = CountDownLatch(1) - var error: Exception? = null - var result: T? = null - - addOnSuccessListener { successResult -> - result = successResult - countDownLatch.countDown() - } - addOnFailureListener { exception -> - error = exception - countDownLatch.countDown() - } - - countDownLatch.await() - - if (error != null) { - throw error!! - } - - return result -} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExtKtTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExtKtTest.kt deleted file mode 100644 index e48c735fc8..0000000000 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/util/TaskExtKtTest.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.util - -import io.realm.internal.common.TaskDispatcher -import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNull -import kotlin.test.fail - -class TaskUtilsKtTest { - - @Test - fun blockingGetResult() { - TaskDispatcher() - .dispatchTask { RESULT } - .blockingGetResult() - .let { assertEquals(RESULT, it) } - - TaskDispatcher() - .dispatchTask { null } - .blockingGetResult() - .let { assertNull(it) } - } - - @Test - fun blockingGetResultThrows() { - assertFailsWith(RuntimeException::class) { - TaskDispatcher() - .dispatchTask { throw RuntimeException("BOOM!") } - .blockingGetResult() - fail() - } - } - - private companion object { - const val RESULT = 666 - } -} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index e1570f8d3d..709e69580f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit e1570f8d3d7cf4d77f049933e6a241a501301383 +Subproject commit 709e69580f480051da8be8b444df400c64c652f8 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/AsyncAdapter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/AsyncAdapter.java deleted file mode 100644 index 7685528a9c..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/AsyncAdapter.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.common; - -public interface AsyncAdapter { - T getAdapter(); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/Callback.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/Callback.java deleted file mode 100644 index 3af9773892..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/Callback.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.common; - -import javax.annotation.Nonnull; - -public interface Callback { - void onComplete(@Nonnull OperationResult result); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/CallbackAsyncAdapter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/CallbackAsyncAdapter.java deleted file mode 100644 index c90aee9762..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/CallbackAsyncAdapter.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.common; - -public interface CallbackAsyncAdapter extends Callback, AsyncAdapter { -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/Dispatcher.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/Dispatcher.java deleted file mode 100644 index c9e34a0be8..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/Dispatcher.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.common; - -import java.util.concurrent.Callable; - -public interface Dispatcher { - void dispatch(final Callable callable); - - void close(); -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/OperationResult.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/OperationResult.java deleted file mode 100644 index 8a26fc9382..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/OperationResult.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.common; - -public final class OperationResult { - private final SuccessTypeT result; - private final FailureTypeT failureResult; - private final boolean isSuccessful; - - private OperationResult( - final SuccessTypeT result, final FailureTypeT failureResult, final boolean isSuccessful) { - this.result = result; - this.failureResult = failureResult; - this.isSuccessful = isSuccessful; - } - - public static OperationResult successfulResultOf(final T value) { - return new OperationResult<>(value, null, true); - } - - public static OperationResult failedResultOf(final U value) { - return new OperationResult<>(null, value, false); - } - - public boolean isSuccessful() { - return isSuccessful; - } - - /** - * Gets the result of the operation, if successful. - * - * @return The result of the operation. - */ - public SuccessTypeT geResult() { - if (!isSuccessful) { - throw new IllegalStateException("operation was failed, not successful"); - } - return result; - } - - /** - * Gets the failure reason for the operation, if it failed. - * - * @return The failure reason of the operation. - */ - public FailureTypeT getFailure() { - if (isSuccessful) { - throw new IllegalStateException("operation was successful, not failed"); - } - return failureResult; - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskCallbackAdapter.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskCallbackAdapter.java deleted file mode 100644 index 885bff4e0d..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskCallbackAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.common; - -import com.google.android.gms.tasks.Task; -import com.google.android.gms.tasks.TaskCompletionSource; - -public final class TaskCallbackAdapter implements CallbackAsyncAdapter> { - private final TaskCompletionSource taskCompletionSource; - - TaskCallbackAdapter() { - this.taskCompletionSource = new TaskCompletionSource<>(); - } - - @Override - public Task getAdapter() { - return taskCompletionSource.getTask(); - } - - @Override - public void onComplete(final OperationResult result) { - if (result.isSuccessful()) { - taskCompletionSource.setResult(result.geResult()); - } else { - taskCompletionSource.setException(result.getFailure()); - } - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskDispatcher.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskDispatcher.java deleted file mode 100644 index ded1820577..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/TaskDispatcher.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.common; - -import com.google.android.gms.tasks.Task; - -import java.util.concurrent.Callable; - -public final class TaskDispatcher extends ThreadDispatcher { - public Task dispatchTask(final Callable callable) { - return dispatch(callable, new TaskCallbackAdapter()); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java b/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java deleted file mode 100644 index 3dec574130..0000000000 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/common/ThreadDispatcher.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.internal.common; - -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -public class ThreadDispatcher implements Dispatcher { - private final ExecutorService executorService; - - public ThreadDispatcher() { - executorService = - new ThreadPoolExecutor( - 8, - 32, - 60, - TimeUnit.SECONDS, - new LinkedBlockingDeque(), - Executors.defaultThreadFactory() - ); - } - - - @SuppressWarnings("FutureReturnValueIgnored") - @Override - public void dispatch(final Callable callable) { - // ignoring the output of this future messes with Findbugs, thus the suppress - executorService.submit(callable); - } - - protected U dispatch( - final Callable callable, - final CallbackAsyncAdapter callbackAdapter - ) { - dispatch(callable, (Callback) callbackAdapter); - return callbackAdapter.getAdapter(); - } - - @SuppressWarnings("FutureReturnValueIgnored") - private void dispatch(final Callable callable, final Callback callback) { - // ignoring the output of this future messes with Findbugs, thus the suppress - executorService.submit(new Runnable() { - @Override - public void run() { - try { - callback.onComplete(OperationResult.successfulResultOf(callable.call())); - } catch (final Exception e) { - callback.onComplete(OperationResult.failedResultOf(e)); - } - } - }); - } - - @Override - public void close() { - executorService.shutdownNow(); - } -} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java index 1baa4d9f71..6ecd7ddd51 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java @@ -18,27 +18,25 @@ import org.bson.codecs.configuration.CodecRegistry; +import java.util.concurrent.ThreadPoolExecutor; + import io.realm.internal.NativeObject; -import io.realm.internal.common.TaskDispatcher; public class OsMongoClient implements NativeObject { private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); private final long nativePtr; - private final TaskDispatcher dispatcher; public OsMongoClient(final long appNativePtr, - final String serviceName, - final TaskDispatcher dispatcher) { + final String serviceName) { this.nativePtr = nativeCreate(appNativePtr, serviceName); - this.dispatcher = dispatcher; } public OsMongoDatabase getDatabase(final String databaseName, final CodecRegistry codecRegistry) { long nativeDatabasePtr = nativeCreateDatabase(nativePtr, databaseName); - return new OsMongoDatabase(nativeDatabasePtr, codecRegistry, dispatcher); + return new OsMongoDatabase(nativeDatabasePtr, codecRegistry); } @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java index 238739a612..99b3379d2b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java @@ -28,16 +28,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import io.realm.internal.NativeObject; import io.realm.internal.Util; -import io.realm.internal.common.TaskDispatcher; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.network.ResultHandler; +import io.realm.mongodb.App; import io.realm.mongodb.AppException; import io.realm.mongodb.mongo.iterable.AggregateIterable; import io.realm.mongodb.mongo.iterable.FindIterable; @@ -73,16 +74,14 @@ public class OsMongoCollection implements NativeObject { private final Class documentClass; private final CodecRegistry codecRegistry; private final String encodedEmptyDocument; - private final TaskDispatcher dispatcher; + private final ThreadPoolExecutor threadPoolExecutor = App.NETWORK_POOL_EXECUTOR; OsMongoCollection(final long nativeCollectionPtr, final Class documentClass, - final CodecRegistry codecRegistry, - final TaskDispatcher dispatcher) { + final CodecRegistry codecRegistry) { this.nativePtr = nativeCollectionPtr; this.documentClass = documentClass; this.codecRegistry = codecRegistry; - this.dispatcher = dispatcher; this.encodedEmptyDocument = JniBsonProtocol.encode(new Document(), codecRegistry); } @@ -106,11 +105,11 @@ public CodecRegistry getCodecRegistry() { public OsMongoCollection withDocumentClass( final Class clazz) { - return new OsMongoCollection<>(nativePtr, clazz, codecRegistry, dispatcher); + return new OsMongoCollection<>(nativePtr, clazz, codecRegistry); } public OsMongoCollection withCodecRegistry(final CodecRegistry codecRegistry) { - return new OsMongoCollection<>(nativePtr, documentClass, codecRegistry, dispatcher); + return new OsMongoCollection<>(nativePtr, documentClass, codecRegistry); } public Long count() { @@ -182,7 +181,7 @@ private FindIterable findInternal(final Bson filter, final Class resultClass, @Nullable final FindOptions options) { FindIterable findIterable = - new FindIterable<>(this, codecRegistry, resultClass, dispatcher); + new FindIterable<>(threadPoolExecutor, this, codecRegistry, resultClass); findIterable.filter(filter); if (options != null) { @@ -198,7 +197,7 @@ public AggregateIterable aggregate(final List pipelin public AggregateIterable aggregate(final List pipeline, final Class resultClass) { - return new AggregateIterable<>(this, codecRegistry, dispatcher, resultClass, pipeline); + return new AggregateIterable<>(threadPoolExecutor, this, codecRegistry, resultClass, pipeline); } public DocumentT findOne() { diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java index 372fa06288..34b3804c90 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java @@ -19,8 +19,9 @@ import org.bson.Document; import org.bson.codecs.configuration.CodecRegistry; +import java.util.concurrent.ThreadPoolExecutor; + import io.realm.internal.NativeObject; -import io.realm.internal.common.TaskDispatcher; public class OsMongoDatabase implements NativeObject { @@ -28,14 +29,11 @@ public class OsMongoDatabase implements NativeObject { private final long nativePtr; private final CodecRegistry codecRegistry; - private final TaskDispatcher dispatcher; OsMongoDatabase(final long nativeDatabasePtr, - final CodecRegistry codecRegistry, - final TaskDispatcher dispatcher) { + final CodecRegistry codecRegistry) { this.nativePtr = nativeDatabasePtr; this.codecRegistry = codecRegistry; - this.dispatcher = dispatcher; } public OsMongoCollection getCollection(final String collectionName) { @@ -45,7 +43,7 @@ public OsMongoCollection getCollection(final String collectionName) { public OsMongoCollection getCollection(final String collectionName, final Class documentClass) { long nativeCollectionPtr = nativeGetCollection(nativePtr, collectionName); - return new OsMongoCollection<>(nativeCollectionPtr, documentClass, codecRegistry, dispatcher); + return new OsMongoCollection<>(nativeCollectionPtr, documentClass, codecRegistry); } @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index 1c17d16be1..daf359fda8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -130,7 +130,7 @@ * MongoClient client = user.getMongoClient(SERVICE_NAME) * MongoDatabase database = client.getDatabase(DATABASE_NAME) * MongoCollection<DocumentT> collection = database.getCollection(COLLECTION_NAME); - * Long count = collection.count().blockingGetResult() + * Long count = collection.count().get() * *

              * diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index 218a7f1d99..0b6aa0daf5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; @@ -28,8 +29,6 @@ import io.realm.RealmAsyncTask; import io.realm.annotations.Beta; import io.realm.internal.Util; -import io.realm.internal.common.TaskDispatcher; -import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.jni.OsJNIVoidResultCallback; import io.realm.internal.mongodb.Request; @@ -63,7 +62,6 @@ public class User { private MongoClient mongoClient = null; private Functions functions = null; private Push push = null; - private TaskDispatcher dispatcher = null; /** * The different types of users. @@ -105,9 +103,8 @@ byte getKey() { private static class MongoClientImpl extends MongoClient { protected MongoClientImpl(OsMongoClient osMongoClient, - CodecRegistry codecRegistry, - TaskDispatcher dispatcher) { - super(osMongoClient, codecRegistry, dispatcher); + CodecRegistry codecRegistry) { + super(osMongoClient, codecRegistry); } } @@ -561,6 +558,8 @@ public Functions getFunctions(CodecRegistry codecRegistry) { /** * Returns the {@link Push} instance for managing push notification registrations. + * + * @param serviceName the service name used to connect to the server. */ public synchronized Push getPush(String serviceName) { if (push == null) { @@ -572,16 +571,14 @@ public synchronized Push getPush(String serviceName) { /** * Returns a {@link MongoClient} instance for accessing documents in the database. - * @param serviceName the service name used to connect to the server + * + * @param serviceName the service name used to connect to the server. */ public synchronized MongoClient getMongoClient(String serviceName) { Util.checkEmpty(serviceName, "serviceName"); if (mongoClient == null) { - if (dispatcher == null) { - dispatcher = new TaskDispatcher(); - } - OsMongoClient osMongoClient = new OsMongoClient(app.nativePtr, serviceName, dispatcher); - mongoClient = new MongoClientImpl(osMongoClient, app.getConfiguration().getDefaultCodecRegistry(), dispatcher); + OsMongoClient osMongoClient = new OsMongoClient(app.nativePtr, serviceName); + mongoClient = new MongoClientImpl(osMongoClient, app.getConfiguration().getDefaultCodecRegistry()); } return mongoClient; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java index 565e7e4c77..a597fc5917 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java @@ -18,9 +18,10 @@ import org.bson.codecs.configuration.CodecRegistry; +import java.util.concurrent.ThreadPoolExecutor; + import io.realm.annotations.Beta; import io.realm.internal.Util; -import io.realm.internal.common.TaskDispatcher; import io.realm.internal.objectstore.OsMongoClient; /** @@ -31,14 +32,11 @@ abstract public class MongoClient { private final OsMongoClient osMongoClient; private final CodecRegistry codecRegistry; - private final TaskDispatcher dispatcher; protected MongoClient(final OsMongoClient osMongoClient, - final CodecRegistry codecRegistry, - final TaskDispatcher dispatcher) { + final CodecRegistry codecRegistry) { this.osMongoClient = osMongoClient; this.codecRegistry = codecRegistry; - this.dispatcher = dispatcher; } /** @@ -49,6 +47,7 @@ protected MongoClient(final OsMongoClient osMongoClient, */ public MongoDatabase getDatabase(final String databaseName) { Util.checkEmpty(databaseName, "databaseName"); - return new MongoDatabase(osMongoClient.getDatabase(databaseName, codecRegistry), databaseName, dispatcher); + return new MongoDatabase(osMongoClient.getDatabase(databaseName, codecRegistry), + databaseName); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java index 331df77288..607f5ea1dc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java @@ -16,19 +16,21 @@ package io.realm.mongodb.mongo; -import com.google.android.gms.tasks.Task; - import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; import java.util.List; -import java.util.concurrent.Callable; +import java.util.concurrent.ThreadPoolExecutor; + +import javax.annotation.Nullable; import io.realm.annotations.Beta; -import io.realm.internal.common.TaskDispatcher; +import io.realm.internal.async.RealmResultTaskImpl; +import io.realm.internal.objectstore.OsMongoCollection; +import io.realm.mongodb.App; +import io.realm.mongodb.RealmResultTask; import io.realm.mongodb.mongo.iterable.AggregateIterable; import io.realm.mongodb.mongo.iterable.FindIterable; -import io.realm.internal.objectstore.OsMongoCollection; import io.realm.mongodb.mongo.options.CountOptions; import io.realm.mongodb.mongo.options.FindOneAndModifyOptions; import io.realm.mongodb.mongo.options.FindOptions; @@ -54,14 +56,12 @@ public class MongoCollection { private final MongoNamespace nameSpace; private final OsMongoCollection osMongoCollection; - private final TaskDispatcher dispatcher; + private final ThreadPoolExecutor threadPoolExecutor = App.NETWORK_POOL_EXECUTOR; MongoCollection(final MongoNamespace nameSpace, - final OsMongoCollection osMongoCollection, - final TaskDispatcher dispatcher) { + final OsMongoCollection osMongoCollection) { this.nameSpace = nameSpace; this.osMongoCollection = osMongoCollection; - this.dispatcher = dispatcher; } /** @@ -107,8 +107,7 @@ public CodecRegistry getCodecRegistry() { */ public MongoCollection withDocumentClass( final Class clazz) { - return new MongoCollection<>(nameSpace, - osMongoCollection.withDocumentClass(clazz), dispatcher); + return new MongoCollection<>(nameSpace, osMongoCollection.withDocumentClass(clazz)); } /** @@ -119,8 +118,7 @@ public MongoCollection withDocumentClass( * @return a new MongoCollection instance with the different codec registry */ public MongoCollection withCodecRegistry(final CodecRegistry codecRegistry) { - return new MongoCollection<>(nameSpace, - osMongoCollection.withCodecRegistry(codecRegistry), dispatcher); + return new MongoCollection<>(nameSpace, osMongoCollection.withCodecRegistry(codecRegistry)); } /** @@ -128,10 +126,11 @@ public MongoCollection withCodecRegistry(final CodecRegistry codecReg * * @return a task containing the number of documents in the collection */ - public Task count() { - return dispatcher.dispatchTask(new Callable() { + public RealmResultTask count() { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable @Override - public Long call() throws Exception { + public Long run() { return osMongoCollection.count(); } }); @@ -143,14 +142,14 @@ public Long call() throws Exception { * @param filter the query filter * @return a task containing the number of documents in the collection */ - public Task count(final Bson filter) { - return dispatcher.dispatchTask(new Callable() { - @Override - public Long call() throws Exception { - return osMongoCollection.count(filter); - } - } - ); + public RealmResultTask count(final Bson filter) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public Long run() { + return osMongoCollection.count(filter); + } + }); } /** @@ -160,14 +159,14 @@ public Long call() throws Exception { * @param options the options describing the count * @return a task containing the number of documents in the collection */ - public Task count(final Bson filter, final CountOptions options) { - return dispatcher.dispatchTask(new Callable() { - @Override - public Long call() throws Exception { - return osMongoCollection.count(filter, options); - } - } - ); + public RealmResultTask count(final Bson filter, final CountOptions options) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public Long run() { + return osMongoCollection.count(filter, options); + } + }); } /** @@ -175,10 +174,11 @@ public Long call() throws Exception { * * @return a task containing the result of the find one operation */ - public Task findOne() { - return dispatcher.dispatchTask(new Callable() { + public RealmResultTask findOne() { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable @Override - public DocumentT call() throws Exception { + public DocumentT run() { return osMongoCollection.findOne(); } }); @@ -191,14 +191,14 @@ public DocumentT call() throws Exception { * @param the target document type * @return a task containing the result of the find one operation */ - public Task findOne(final Class resultClass) { - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return osMongoCollection.findOne(resultClass); - } - } - ); + public RealmResultTask findOne(final Class resultClass) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return osMongoCollection.findOne(resultClass); + } + }); } /** @@ -207,14 +207,14 @@ public ResultT call() throws Exception { * @param filter the query filter * @return a task containing the result of the find one operation */ - public Task findOne(final Bson filter) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DocumentT call() throws Exception { - return osMongoCollection.findOne(filter); - } - } - ); + public RealmResultTask findOne(final Bson filter) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DocumentT run() { + return osMongoCollection.findOne(filter); + } + }); } /** @@ -225,14 +225,14 @@ public DocumentT call() throws Exception { * @param the target document type of the iterable. * @return a task containing the result of the find one operation */ - public Task findOne(final Bson filter, final Class resultClass) { - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return osMongoCollection.findOne(filter, resultClass); - } - } - ); + public RealmResultTask findOne(final Bson filter, final Class resultClass) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return osMongoCollection.findOne(filter, resultClass); + } + }); } /** @@ -242,14 +242,14 @@ public ResultT call() throws Exception { * @param options a {@link FindOptions} struct * @return a task containing the result of the find one operation */ - public Task findOne(final Bson filter, final FindOptions options) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DocumentT call() throws Exception { - return osMongoCollection.findOne(filter, options); - } - } - ); + public RealmResultTask findOne(final Bson filter, final FindOptions options) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DocumentT run() { + return osMongoCollection.findOne(filter, options); + } + }); } /** @@ -261,16 +261,16 @@ public DocumentT call() throws Exception { * @param the target document type of the iterable. * @return a task containing the result of the find one operation */ - public Task findOne(final Bson filter, - final FindOptions options, - final Class resultClass) { - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return osMongoCollection.findOne(filter, options, resultClass); - } - } - ); + public RealmResultTask findOne(final Bson filter, + final FindOptions options, + final Class resultClass) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return osMongoCollection.findOne(filter, options, resultClass); + } + }); } /** @@ -320,7 +320,7 @@ public FindIterable find(final Class resultClass) { * elements can be extracted. * * @param resultClass the class to decode each document into - * @param options a {@link FindOptions} struct for building the query + * @param options a {@link FindOptions} struct for building the query * @param the target document type of the iterable. * @return an iterable containing the result of the find operation */ @@ -349,7 +349,7 @@ public FindIterable find(final Bson filter) { * All documents will be delivered in the form of a {@link FindIterable} from which individual * elements can be extracted. * - * @param filter the query filter + * @param filter the query filter * @param options a {@link FindOptions} struct * @return an iterable containing the result of the find operation */ @@ -429,14 +429,14 @@ public AggregateIterable aggregate(final List * @param document the document to insert * @return a task containing the result of the insert one operation */ - public Task insertOne(final DocumentT document) { - return dispatcher.dispatchTask(new Callable() { - @Override - public InsertOneResult call() throws Exception { - return osMongoCollection.insertOne(document); - } - } - ); + public RealmResultTask insertOne(final DocumentT document) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public InsertOneResult run() { + return osMongoCollection.insertOne(document); + } + }); } /** @@ -445,14 +445,14 @@ public InsertOneResult call() throws Exception { * @param documents the documents to insert * @return a task containing the result of the insert many operation */ - public Task insertMany(final List documents) { - return dispatcher.dispatchTask(new Callable() { - @Override - public InsertManyResult call() throws Exception { - return osMongoCollection.insertMany(documents); - } - } - ); + public RealmResultTask insertMany(final List documents) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public InsertManyResult run() { + return osMongoCollection.insertMany(documents); + } + }); } /** @@ -463,14 +463,14 @@ public InsertManyResult call() throws Exception { * @param filter the query filter to apply the the delete operation * @return a task containing the result of the remove one operation */ - public Task deleteOne(final Bson filter) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DeleteResult call() throws Exception { - return osMongoCollection.deleteOne(filter); - } - } - ); + public RealmResultTask deleteOne(final Bson filter) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DeleteResult run() { + return osMongoCollection.deleteOne(filter); + } + }); } /** @@ -480,14 +480,14 @@ public DeleteResult call() throws Exception { * @param filter the query filter to apply the the delete operation * @return a task containing the result of the remove many operation */ - public Task deleteMany(final Bson filter) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DeleteResult call() throws Exception { - return osMongoCollection.deleteMany(filter); - } - } - ); + public RealmResultTask deleteMany(final Bson filter) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DeleteResult run() { + return osMongoCollection.deleteMany(filter); + } + }); } /** @@ -498,14 +498,14 @@ public DeleteResult call() throws Exception { * apply must include only update operators. * @return a task containing the result of the update one operation */ - public Task updateOne(final Bson filter, final Bson update) { - return dispatcher.dispatchTask(new Callable() { - @Override - public UpdateResult call() throws Exception { - return osMongoCollection.updateOne(filter, update); - } - } - ); + public RealmResultTask updateOne(final Bson filter, final Bson update) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public UpdateResult run() { + return osMongoCollection.updateOne(filter, update); + } + }); } /** @@ -517,17 +517,17 @@ public UpdateResult call() throws Exception { * @param updateOptions the options to apply to the update operation * @return a task containing the result of the update one operation */ - public Task updateOne( + public RealmResultTask updateOne( final Bson filter, final Bson update, final UpdateOptions updateOptions) { - return dispatcher.dispatchTask(new Callable() { - @Override - public UpdateResult call() throws Exception { - return osMongoCollection.updateOne(filter, update, updateOptions); - } - } - ); + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public UpdateResult run() { + return osMongoCollection.updateOne(filter, update, updateOptions); + } + }); } /** @@ -538,14 +538,14 @@ public UpdateResult call() throws Exception { * apply must include only update operators. * @return a task containing the result of the update many operation */ - public Task updateMany(final Bson filter, final Bson update) { - return dispatcher.dispatchTask(new Callable() { - @Override - public UpdateResult call() throws Exception { - return osMongoCollection.updateMany(filter, update); - } - } - ); + public RealmResultTask updateMany(final Bson filter, final Bson update) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public UpdateResult run() { + return osMongoCollection.updateMany(filter, update); + } + }); } /** @@ -557,17 +557,17 @@ public UpdateResult call() throws Exception { * @param updateOptions the options to apply to the update operation * @return a task containing the result of the update many operation */ - public Task updateMany( + public RealmResultTask updateMany( final Bson filter, final Bson update, final UpdateOptions updateOptions) { - return dispatcher.dispatchTask(new Callable() { - @Override - public UpdateResult call() throws Exception { - return osMongoCollection.updateMany(filter, update, updateOptions); - } - } - ); + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public UpdateResult run() { + return osMongoCollection.updateMany(filter, update, updateOptions); + } + }); } /** @@ -577,14 +577,14 @@ public UpdateResult call() throws Exception { * @param update the update document * @return a task containing the resulting document */ - public Task findOneAndUpdate(final Bson filter, final Bson update) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DocumentT call() throws Exception { - return osMongoCollection.findOneAndUpdate(filter, update); - } - } - ); + public RealmResultTask findOneAndUpdate(final Bson filter, final Bson update) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DocumentT run() { + return osMongoCollection.findOneAndUpdate(filter, update); + } + }); } /** @@ -596,16 +596,16 @@ public DocumentT call() throws Exception { * @param the target document type of the iterable. * @return a task containing the resulting document */ - public Task findOneAndUpdate(final Bson filter, - final Bson update, - final Class resultClass) { - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return osMongoCollection.findOneAndUpdate(filter, update, resultClass); - } - } - ); + public RealmResultTask findOneAndUpdate(final Bson filter, + final Bson update, + final Class resultClass) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return osMongoCollection.findOneAndUpdate(filter, update, resultClass); + } + }); } /** @@ -616,16 +616,16 @@ public ResultT call() throws Exception { * @param options a {@link FindOneAndModifyOptions} struct * @return a task containing the resulting document */ - public Task findOneAndUpdate(final Bson filter, - final Bson update, - final FindOneAndModifyOptions options) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DocumentT call() throws Exception { - return osMongoCollection.findOneAndUpdate(filter, update, options); - } - } - ); + public RealmResultTask findOneAndUpdate(final Bson filter, + final Bson update, + final FindOneAndModifyOptions options) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DocumentT run() { + return osMongoCollection.findOneAndUpdate(filter, update, options); + } + }); } /** @@ -638,17 +638,17 @@ public DocumentT call() throws Exception { * @param the target document type of the iterable. * @return a task containing the resulting document */ - public Task findOneAndUpdate(final Bson filter, - final Bson update, - final FindOneAndModifyOptions options, - final Class resultClass) { - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return osMongoCollection.findOneAndUpdate(filter, update, options, resultClass); - } - } - ); + public RealmResultTask findOneAndUpdate(final Bson filter, + final Bson update, + final FindOneAndModifyOptions options, + final Class resultClass) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return osMongoCollection.findOneAndUpdate(filter, update, options, resultClass); + } + }); } /** @@ -658,14 +658,14 @@ public ResultT call() throws Exception { * @param replacement the document to replace the matched document with * @return a task containing the resulting document */ - public Task findOneAndReplace(final Bson filter, final Bson replacement) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DocumentT call() throws Exception { - return osMongoCollection.findOneAndReplace(filter, replacement); - } - } - ); + public RealmResultTask findOneAndReplace(final Bson filter, final Bson replacement) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DocumentT run() { + return osMongoCollection.findOneAndReplace(filter, replacement); + } + }); } /** @@ -677,16 +677,16 @@ public DocumentT call() throws Exception { * @param the target document type of the iterable. * @return a task containing the resulting document */ - public Task findOneAndReplace(final Bson filter, - final Bson replacement, - final Class resultClass) { - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return osMongoCollection.findOneAndReplace(filter, replacement, resultClass); - } - } - ); + public RealmResultTask findOneAndReplace(final Bson filter, + final Bson replacement, + final Class resultClass) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return osMongoCollection.findOneAndReplace(filter, replacement, resultClass); + } + }); } /** @@ -697,16 +697,16 @@ public ResultT call() throws Exception { * @param options a {@link FindOneAndModifyOptions} struct * @return a task containing the resulting document */ - public Task findOneAndReplace(final Bson filter, - final Bson replacement, - final FindOneAndModifyOptions options) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DocumentT call() throws Exception { - return osMongoCollection.findOneAndReplace(filter, replacement, options); - } - } - ); + public RealmResultTask findOneAndReplace(final Bson filter, + final Bson replacement, + final FindOneAndModifyOptions options) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DocumentT run() { + return osMongoCollection.findOneAndReplace(filter, replacement, options); + } + }); } /** @@ -719,17 +719,17 @@ public DocumentT call() throws Exception { * @param the target document type of the iterable. * @return a task containing the resulting document */ - public Task findOneAndReplace(final Bson filter, - final Bson replacement, - final FindOneAndModifyOptions options, - final Class resultClass) { - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return osMongoCollection.findOneAndReplace(filter, replacement, options, resultClass); - } - } - ); + public RealmResultTask findOneAndReplace(final Bson filter, + final Bson replacement, + final FindOneAndModifyOptions options, + final Class resultClass) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return osMongoCollection.findOneAndReplace(filter, replacement, options, resultClass); + } + }); } /** @@ -738,14 +738,14 @@ public ResultT call() throws Exception { * @param filter the query filter * @return a task containing the resulting document */ - public Task findOneAndDelete(final Bson filter) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DocumentT call() throws Exception { - return osMongoCollection.findOneAndDelete(filter); - } - } - ); + public RealmResultTask findOneAndDelete(final Bson filter) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DocumentT run() { + return osMongoCollection.findOneAndDelete(filter); + } + }); } /** @@ -756,15 +756,15 @@ public DocumentT call() throws Exception { * @param the target document type of the iterable. * @return a task containing the resulting document */ - public Task findOneAndDelete(final Bson filter, - final Class resultClass) { - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return osMongoCollection.findOneAndDelete(filter, resultClass); - } - } - ); + public RealmResultTask findOneAndDelete(final Bson filter, + final Class resultClass) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return osMongoCollection.findOneAndDelete(filter, resultClass); + } + }); } /** @@ -774,15 +774,15 @@ public ResultT call() throws Exception { * @param options a {@link FindOneAndModifyOptions} struct * @return a task containing the resulting document */ - public Task findOneAndDelete(final Bson filter, - final FindOneAndModifyOptions options) { - return dispatcher.dispatchTask(new Callable() { - @Override - public DocumentT call() throws Exception { - return osMongoCollection.findOneAndDelete(filter, options); - } - } - ); + public RealmResultTask findOneAndDelete(final Bson filter, + final FindOneAndModifyOptions options) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public DocumentT run() { + return osMongoCollection.findOneAndDelete(filter, options); + } + }); } /** @@ -794,15 +794,15 @@ public DocumentT call() throws Exception { * @param the target document type of the iterable. * @return a task containing the resulting document */ - public Task findOneAndDelete(final Bson filter, - final FindOneAndModifyOptions options, - final Class resultClass) { - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return osMongoCollection.findOneAndDelete(filter, options, resultClass); - } - } - ); + public RealmResultTask findOneAndDelete(final Bson filter, + final FindOneAndModifyOptions options, + final Class resultClass) { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return osMongoCollection.findOneAndDelete(filter, options, resultClass); + } + }); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java index 8e58fd35c8..7e268ec48b 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java @@ -20,7 +20,6 @@ import io.realm.annotations.Beta; import io.realm.internal.Util; -import io.realm.internal.common.TaskDispatcher; import io.realm.internal.objectstore.OsMongoDatabase; /** @@ -29,16 +28,13 @@ @Beta public class MongoDatabase { - private final String name; - private final TaskDispatcher dispatcher; private final OsMongoDatabase osMongoDatabase; + private final String name; MongoDatabase(final OsMongoDatabase osMongoDatabase, - final String name, - final TaskDispatcher dispatcher) { + final String name) { this.osMongoDatabase = osMongoDatabase; this.name = name; - this.dispatcher = dispatcher; } /** @@ -59,8 +55,7 @@ public String getName() { public MongoCollection getCollection(final String collectionName) { Util.checkEmpty(collectionName, "collectionName"); return new MongoCollection<>(new MongoNamespace(name, collectionName), - osMongoDatabase.getCollection(collectionName), - dispatcher); + osMongoDatabase.getCollection(collectionName)); } /** @@ -78,7 +73,6 @@ public MongoCollection getCollection( Util.checkEmpty(collectionName, "collectionName"); Util.checkNull(documentClass, "documentClass"); return new MongoCollection<>(new MongoNamespace(name, collectionName), - osMongoDatabase.getCollection(collectionName, documentClass), - dispatcher); + osMongoDatabase.getCollection(collectionName, documentClass)); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/AggregateIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/AggregateIterable.java index b71a01e03c..bb86f2b144 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/AggregateIterable.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/AggregateIterable.java @@ -20,8 +20,8 @@ import org.bson.conversions.Bson; import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; -import io.realm.internal.common.TaskDispatcher; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.objectstore.OsJavaNetworkTransport; @@ -36,17 +36,17 @@ public class AggregateIterable extends MongoIterable { private List pipeline; - public AggregateIterable(final OsMongoCollection osMongoCollection, + public AggregateIterable(final ThreadPoolExecutor threadPoolExecutor, + final OsMongoCollection osMongoCollection, final CodecRegistry codecRegistry, - final TaskDispatcher dispatcher, final Class resultClass, final List pipeline) { - super(osMongoCollection, codecRegistry, resultClass, dispatcher); + super(threadPoolExecutor, osMongoCollection, codecRegistry, resultClass); this.pipeline = pipeline; } @Override - void callNative(final OsJNIResultCallback callback) { + void callNative(final OsJNIResultCallback callback) { String pipelineString = JniBsonProtocol.encode(pipeline, codecRegistry); nativeAggregate(osMongoCollection.getNativePtr(), pipelineString, callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/FindIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/FindIterable.java index 6b0b9f778c..1790763e52 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/FindIterable.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/FindIterable.java @@ -20,9 +20,10 @@ import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; +import java.util.concurrent.ThreadPoolExecutor; + import javax.annotation.Nullable; -import io.realm.internal.common.TaskDispatcher; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.objectstore.OsJavaNetworkTransport; @@ -44,18 +45,18 @@ public class FindIterable extends MongoIterable { private Bson filter; - public FindIterable(final OsMongoCollection osMongoCollection, + public FindIterable(final ThreadPoolExecutor threadPoolExecutor, + final OsMongoCollection osMongoCollection, final CodecRegistry codecRegistry, - final Class resultClass, - final TaskDispatcher dispatcher) { - super(osMongoCollection, codecRegistry, resultClass, dispatcher); + final Class resultClass) { + super(threadPoolExecutor, osMongoCollection, codecRegistry, resultClass); this.options = new FindOptions(); this.filter = new Document(); this.encodedEmptyDocument = JniBsonProtocol.encode(new Document(), codecRegistry); } @Override - void callNative(final OsJNIResultCallback callback) { + void callNative(final OsJNIResultCallback callback) { String filterString = JniBsonProtocol.encode(filter, codecRegistry); String projectionString = encodedEmptyDocument; String sortString = encodedEmptyDocument; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java index 49e5666b4b..df50ffbe41 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoCursor.java @@ -16,10 +16,7 @@ package io.realm.mongodb.mongo.iterable; -import com.google.android.gms.tasks.Task; - import java.io.Closeable; -import java.io.IOException; import java.util.Iterator; /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java index 84656f8efa..1d3a8c08e8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java @@ -16,22 +16,23 @@ package io.realm.mongodb.mongo.iterable; -import com.google.android.gms.tasks.Task; - import org.bson.codecs.configuration.CodecRegistry; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; -import java.util.concurrent.Callable; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; -import io.realm.internal.common.TaskDispatcher; +import javax.annotation.Nullable; + +import io.realm.internal.async.RealmResultTaskImpl; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.network.ResultHandler; import io.realm.internal.objectstore.OsMongoCollection; import io.realm.mongodb.AppException; +import io.realm.mongodb.RealmResultTask; /** * The MongoIterable is the results from an operation, such as a {@code find()} or an @@ -44,23 +45,23 @@ */ public abstract class MongoIterable { - final OsMongoCollection osMongoCollection; - final CodecRegistry codecRegistry; + protected final OsMongoCollection osMongoCollection; + protected final CodecRegistry codecRegistry; private final Class resultClass; - private final TaskDispatcher dispatcher; + private final ThreadPoolExecutor threadPoolExecutor; - MongoIterable(final OsMongoCollection osMongoCollection, + MongoIterable(final ThreadPoolExecutor threadPoolExecutor, + final OsMongoCollection osMongoCollection, final CodecRegistry codecRegistry, - final Class resultClass, - final TaskDispatcher dispatcher) { + final Class resultClass) { + this.threadPoolExecutor = threadPoolExecutor; this.osMongoCollection = osMongoCollection; this.codecRegistry = codecRegistry; this.resultClass = resultClass; - this.dispatcher = dispatcher; } - abstract void callNative(final OsJNIResultCallback callback); + abstract void callNative(final OsJNIResultCallback callback); /** * Returns a cursor of the operation represented by this iterable. @@ -70,14 +71,14 @@ public abstract class MongoIterable { * * @return an asynchronous task with cursor of the operation represented by this iterable. */ - public Task> iterator() { - return dispatcher.dispatchTask(new Callable>() { - @Override - public MongoCursor call() throws Exception { - return new MongoCursor<>(MongoIterable.this.getCollection().iterator()); - } - } - ); + public RealmResultTask> iterator() { + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor>() { + @Nullable + @Override + public MongoCursor run() { + return new MongoCursor<>(MongoIterable.this.getCollection().iterator()); + } + }); } /** @@ -88,7 +89,7 @@ public MongoCursor call() throws Exception { * * @return a task containing the first item or null. */ - public Task first() { + public RealmResultTask first() { final AtomicReference success = new AtomicReference<>(null); final AtomicReference error = new AtomicReference<>(null); OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @@ -102,13 +103,13 @@ protected ResultT mapSuccess(Object result) { callNative(callback); - return dispatcher.dispatchTask(new Callable() { - @Override - public ResultT call() throws Exception { - return ResultHandler.handleResult(success, error); - } - } - ); + return new RealmResultTaskImpl<>(threadPoolExecutor, new RealmResultTaskImpl.Executor() { + @Nullable + @Override + public ResultT run() { + return ResultHandler.handleResult(success, error); + } + }); } private Collection getCollection() { @@ -129,7 +130,7 @@ protected Collection mapSuccess(Object result) { private Collection mapCollection(Object result) { Collection collection = JniBsonProtocol.decode((String) result, Collection.class, codecRegistry); Collection decodedCollection = new ArrayList<>(); - for (Object collectionElement: collection) { + for (Object collectionElement : collection) { String encodedElement = JniBsonProtocol.encode(collectionElement, codecRegistry); decodedCollection.add(JniBsonProtocol.decode(encodedElement, resultClass, codecRegistry)); } From ce5cefea0d33fbae5188dcbcbf917e907eb9f3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 3 Jul 2020 17:31:07 +0200 Subject: [PATCH 1612/2110] Add non-streaming based JSON import of embedded objects (#6985) --- CHANGELOG.md | 1 + .../io/realm/processor/RealmJsonTypeHelper.kt | 18 +- .../processor/RealmProxyClassGenerator.kt | 199 ++++---- .../processor/RealmProxyMediatorGenerator.kt | 6 +- .../processor/RealmEmbeddedObjectsTest.java | 8 +- .../some_test_EmbeddedClassRealmProxy.java | 460 ++++++++++++++++++ ...t_EmbeddedClassSimpleParentRealmProxy.java | 6 +- .../kotlin/io/realm/EmbeddedObjectsTest.kt | 58 ++- 8 files changed, 649 insertions(+), 107 deletions(-) create mode 100644 realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassRealmProxy.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ab2465f3..433f4477cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new ### Fixed * [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) +* Added support for Json-import of objects containing embedded objects. Only supported for String/Json based Json import APIs. Stream based Json import APIs is still failing. (Issue [#6896](https://github.com/realm/realm-java/issues/6896)) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt index 0eaff76dad..f99ce2502f 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt @@ -77,14 +77,19 @@ object RealmJsonTypeHelper { fieldName: String, qualifiedFieldType: QualifiedClassName, proxyClass: SimpleClassName, + embedded: Boolean, writer: JavaWriter) { writer.apply { beginControlFlow("if (json.has(\"%s\"))", fieldName) beginControlFlow("if (json.isNull(\"%s\"))", fieldName) emitStatement("%s.%s(null)", varName, setter) nextControlFlow("else") - emitStatement("%s %sObj = %s.createOrUpdateUsingJsonObject(realm, json.getJSONObject(\"%s\"), update)", qualifiedFieldType, fieldName, proxyClass, fieldName) - emitStatement("%s.%s(%sObj)", varName, setter, fieldName) + if (!embedded) { + emitStatement("%s %sObj = %s.createOrUpdateUsingJsonObject(realm, json.getJSONObject(\"%s\"), update)", qualifiedFieldType, fieldName, proxyClass, fieldName) + emitStatement("%s.%s(%sObj)", varName, setter, fieldName) + } else { + emitStatement("%s %sObj = %s.createOrUpdateUsingJsonObject(realm, (RealmModel)%s, \"%s\", json.getJSONObject(\"%s\"), update)", qualifiedFieldType, fieldName, proxyClass, varName, fieldName, fieldName) + } endControlFlow() endControlFlow() } @@ -97,6 +102,7 @@ object RealmJsonTypeHelper { fieldName: String, fieldTypeCanonicalName: String, proxyClass: SimpleClassName, + embedded: Boolean, writer: JavaWriter) { writer.apply { beginControlFlow("if (json.has(\"%s\"))", fieldName) @@ -106,8 +112,12 @@ object RealmJsonTypeHelper { emitStatement("%s.%s().clear()", varName, getter) emitStatement("JSONArray array = json.getJSONArray(\"%s\")", fieldName) beginControlFlow("for (int i = 0; i < array.length(); i++)") - emitStatement("%s item = %s.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update)", fieldTypeCanonicalName, proxyClass, fieldTypeCanonicalName) - emitStatement("%s.%s().add(item)", varName, getter) + if (!embedded) { + emitStatement("%s item = %s.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update)", fieldTypeCanonicalName, proxyClass, fieldTypeCanonicalName) + emitStatement("%s.%s().add(item)", varName, getter) + } else { + emitStatement("%s item = %s.createOrUpdateUsingJsonObject(realm, (RealmModel)%s, \"%s\", array.getJSONObject(i), update)", fieldTypeCanonicalName, proxyClass, varName, fieldName) + } endControlFlow() endControlFlow() endControlFlow() diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index 9fb3e829a4..a85dd59ee0 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -2066,107 +2066,126 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi @Throws(IOException::class) private fun emitCreateOrUpdateUsingJsonObject(writer: JavaWriter) { writer.apply { + val embedded = metadata.embedded emitAnnotation("SuppressWarnings", "\"cast\"") - beginMethod(qualifiedJavaClassName,"createOrUpdateUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "JSONObject", "json", "boolean", "update"), listOf("JSONException")) - val modelOrListCount = countModelOrListFields(metadata.fields) - if (modelOrListCount == 0) { - emitStatement("final List excludeFields = Collections. emptyList()") + if (!embedded) { + beginMethod(qualifiedJavaClassName, "createOrUpdateUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "JSONObject", "json", "boolean", "update"), listOf("JSONException")) } else { - emitStatement("final List excludeFields = new ArrayList(%1\$d)", modelOrListCount) + beginMethod(qualifiedJavaClassName, "createOrUpdateUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "RealmModel", "parent", "String", "parentProperty", "JSONObject", "json", "boolean", "update"), listOf("JSONException")) } - - if (!metadata.hasPrimaryKey()) { - buildExcludeFieldsList(writer, metadata.fields) - emitStatement("%s obj = realm.createObjectInternal(%s.class, true, excludeFields)", qualifiedJavaClassName, qualifiedJavaClassName) - } else { - var pkType = "Long" - var jsonAccessorMethodSuffix = "Long" - var findFirstCast = "" - if (Utils.isString(metadata.primaryKey)) { - pkType = "String" - jsonAccessorMethodSuffix= "String" - } else if (Utils.isObjectId(metadata.primaryKey)) { - pkType = "ObjectId" - findFirstCast = "(org.bson.types.ObjectId)" - jsonAccessorMethodSuffix = "" + val modelOrListCount = countModelOrListFields(metadata.fields) + if (modelOrListCount == 0) { + emitStatement("final List excludeFields = Collections. emptyList()") + } else { + emitStatement("final List excludeFields = new ArrayList(%1\$d)", modelOrListCount) } - emitStatement("%s obj = null", qualifiedJavaClassName) - beginControlFlow("if (update)") - emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) - emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) - emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey)) - emitStatement("long objKey = Table.NO_MATCH") - if (metadata.isNullable(metadata.primaryKey!!)) { - beginControlFlow("if (json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) - emitStatement("objKey = table.findFirstNull(pkColumnKey)") - nextControlFlow("else") - emitStatement("objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))", pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) - endControlFlow() + + if (!metadata.hasPrimaryKey()) { + buildExcludeFieldsList(writer, metadata.fields) + if (!embedded) { + emitStatement("%s obj = realm.createObjectInternal(%s.class, true, excludeFields)", qualifiedJavaClassName, qualifiedJavaClassName) } else { - beginControlFlow("if (!json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) - emitStatement("objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))", pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) - endControlFlow() + emitStatement("%s obj = realm.createEmbeddedObject(%s.class, parent, parentProperty)", qualifiedJavaClassName, qualifiedJavaClassName) + } + } else { + var pkType = "Long" + var jsonAccessorMethodSuffix = "Long" + var findFirstCast = "" + if (Utils.isString(metadata.primaryKey)) { + pkType = "String" + jsonAccessorMethodSuffix= "String" + } else if (Utils.isObjectId(metadata.primaryKey)) { + pkType = "ObjectId" + findFirstCast = "(org.bson.types.ObjectId)" + jsonAccessorMethodSuffix = "" } - beginControlFlow("if (objKey != Table.NO_MATCH)") - emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") - beginControlFlow("try") - emitStatement("objectContext.set(realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", qualifiedJavaClassName) - emitStatement("obj = new %s()", generatedClassName) - nextControlFlow("finally") - emitStatement("objectContext.clear()") + emitStatement("%s obj = null", qualifiedJavaClassName) + beginControlFlow("if (update)") + emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) + emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName) + emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey)) + emitStatement("long objKey = Table.NO_MATCH") + if (metadata.isNullable(metadata.primaryKey!!)) { + beginControlFlow("if (json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) + emitStatement("objKey = table.findFirstNull(pkColumnKey)") + nextControlFlow("else") + emitStatement("objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))", pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) + endControlFlow() + } else { + beginControlFlow("if (!json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) + emitStatement("objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))", pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) + endControlFlow() + } + beginControlFlow("if (objKey != Table.NO_MATCH)") + emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()") + beginControlFlow("try") + emitStatement("objectContext.set(realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(%s.class), false, Collections. emptyList())", qualifiedJavaClassName) + emitStatement("obj = new %s()", generatedClassName) + nextControlFlow("finally") + emitStatement("objectContext.clear()") + endControlFlow() endControlFlow() endControlFlow() - endControlFlow() - beginControlFlow("if (obj == null)") - buildExcludeFieldsList(writer, metadata.fields) - val primaryKeyFieldType = QualifiedClassName(metadata.primaryKey!!.asType().toString()) - val primaryKeyFieldName = metadata.primaryKey!!.simpleName.toString() - RealmJsonTypeHelper.emitCreateObjectWithPrimaryKeyValue(qualifiedJavaClassName, generatedClassName, primaryKeyFieldType, primaryKeyFieldName, writer) - endControlFlow() - } - emitEmptyLine() - emitStatement("final %1\$s objProxy = (%1\$s) obj", interfaceName) - for (field in metadata.fields) { - val fieldName = field.simpleName.toString() - val qualifiedFieldType = QualifiedClassName(field.asType().toString()) - if (metadata.isPrimaryKey(field)) { - continue // Primary key has already been set when adding new row or finding the existing row. + beginControlFlow("if (obj == null)") + buildExcludeFieldsList(writer, metadata.fields) + val primaryKeyFieldType = QualifiedClassName(metadata.primaryKey!!.asType().toString()) + val primaryKeyFieldName = metadata.primaryKey!!.simpleName.toString() + RealmJsonTypeHelper.emitCreateObjectWithPrimaryKeyValue(qualifiedJavaClassName, generatedClassName, primaryKeyFieldType, primaryKeyFieldName, writer) + endControlFlow() } - when { - Utils.isRealmModel(field) -> RealmJsonTypeHelper.emitFillRealmObjectWithJsonValue( - "objProxy", - metadata.getInternalSetter(fieldName), - fieldName, - qualifiedFieldType, - Utils.getProxyClassSimpleName(field), - writer) - Utils.isRealmModelList(field) -> RealmJsonTypeHelper.emitFillRealmListWithJsonValue( - "objProxy", - metadata.getInternalGetter(fieldName), - metadata.getInternalSetter(fieldName), - fieldName, - (field.asType() as DeclaredType).typeArguments[0].toString(), - Utils.getProxyClassSimpleName(field), - writer) - Utils.isRealmValueList(field) -> emitStatement("ProxyUtils.setRealmListWithJsonObject(objProxy.%1\$s(), json, \"%2\$s\")", metadata.getInternalGetter(fieldName), fieldName) - Utils.isMutableRealmInteger(field) -> RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( - "objProxy", - metadata.getInternalGetter(fieldName), - fieldName, - qualifiedFieldType, - writer) - else -> RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( - "objProxy", - metadata.getInternalSetter(fieldName), - fieldName, - qualifiedFieldType, - writer) + emitEmptyLine() + emitStatement("final %1\$s objProxy = (%1\$s) obj", interfaceName) + for (field in metadata.fields) { + val fieldName = field.simpleName.toString() + val qualifiedFieldType = QualifiedClassName(field.asType().toString()) + if (metadata.isPrimaryKey(field)) { + continue // Primary key has already been set when adding new row or finding the existing row. + } + when { + Utils.isRealmModel(field) -> { + val fieldType = QualifiedClassName(field.asType()) + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + RealmJsonTypeHelper.emitFillRealmObjectWithJsonValue( + "objProxy", + metadata.getInternalSetter(fieldName), + fieldName, + qualifiedFieldType, + Utils.getProxyClassSimpleName(field), + fieldTypeMetaData.embedded, + writer) + } + Utils.isRealmModelList(field) -> { + val fieldType = QualifiedClassName((field.asType() as DeclaredType).typeArguments[0]) + val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + RealmJsonTypeHelper.emitFillRealmListWithJsonValue( + "objProxy", + metadata.getInternalGetter(fieldName), + metadata.getInternalSetter(fieldName), + fieldName, + (field.asType() as DeclaredType).typeArguments[0].toString(), + Utils.getProxyClassSimpleName(field), + fieldTypeMetaData.embedded, + writer) + } + Utils.isRealmValueList(field) -> emitStatement("ProxyUtils.setRealmListWithJsonObject(objProxy.%1\$s(), json, \"%2\$s\")", metadata.getInternalGetter(fieldName), fieldName) + Utils.isMutableRealmInteger(field) -> RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( + "objProxy", + metadata.getInternalGetter(fieldName), + fieldName, + qualifiedFieldType, + writer) + else -> RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue( + "objProxy", + metadata.getInternalSetter(fieldName), + fieldName, + qualifiedFieldType, + writer) + } } - } - emitStatement("return obj") - endMethod() - emitEmptyLine() + emitStatement("return obj") + endMethod() + emitEmptyLine() } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt index c6e3d17793..2ea9ca4853 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt @@ -423,7 +423,11 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE Arrays.asList("JSONException") ) emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> - emitStatement("return clazz.cast(%s.createOrUpdateUsingJsonObject(realm, json, update))", qualifiedProxyClasses[i]) + if (!embeddedClass[i]) { + emitStatement("return clazz.cast(%s.createOrUpdateUsingJsonObject(realm, json, update))", qualifiedProxyClasses[i]) + } else { + emitStatement("throw new IllegalArgumentException(\"Importing embedded classes from JSON without a parent is not allowed\")") + } }) endMethod() emitEmptyLine() diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java index 4aae15f466..277e5ea987 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmEmbeddedObjectsTest.java @@ -13,15 +13,17 @@ public class RealmEmbeddedObjectsTest { @Test - public void compileEmbeddedObjectFile() { + public void compileAndCompareEmbeddedObjectFile() { ASSERT.about(javaSource()) .that(JavaFileObjects.forResource("some/test/EmbeddedClass.java")) .processedWith(new RealmProcessor()) - .compilesWithoutError(); + .compilesWithoutError() + .and() + .generatesSources(JavaFileObjects.forResource("io/realm/some_test_EmbeddedClassRealmProxy.java")); } @Test - public void compileParentToEmbeddedObjectFile() { + public void compileAndCompareParentToEmbeddedObjectFile() { ASSERT.about(javaSources()) .that(Arrays.asList( JavaFileObjects.forResource("some/test/EmbeddedClassSimpleParent.java"), diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassRealmProxy.java new file mode 100644 index 0000000000..9ceb48d229 --- /dev/null +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassRealmProxy.java @@ -0,0 +1,460 @@ +package io.realm; + + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.JsonReader; +import android.util.JsonToken; +import io.realm.ImportFlag; +import io.realm.ProxyUtils; +import io.realm.exceptions.RealmMigrationNeededException; +import io.realm.internal.ColumnInfo; +import io.realm.internal.OsList; +import io.realm.internal.OsObject; +import io.realm.internal.OsObjectSchemaInfo; +import io.realm.internal.OsSchemaInfo; +import io.realm.internal.Property; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; +import io.realm.internal.Table; +import io.realm.internal.android.JsonUtils; +import io.realm.internal.objectstore.OsObjectBuilder; +import io.realm.log.RealmLog; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +@SuppressWarnings("all") +public class some_test_EmbeddedClassRealmProxy extends some.test.EmbeddedClass + implements RealmObjectProxy, some_test_EmbeddedClassRealmProxyInterface { + + static final class EmbeddedClassColumnInfo extends ColumnInfo { + long nameColKey; + long ageColKey; + + EmbeddedClassColumnInfo(OsSchemaInfo schemaInfo) { + super(2); + OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("EmbeddedClass"); + this.nameColKey = addColumnDetails("name", "name", objectSchemaInfo); + this.ageColKey = addColumnDetails("age", "age", objectSchemaInfo); + } + + EmbeddedClassColumnInfo(ColumnInfo src, boolean mutable) { + super(src, mutable); + copy(src, this); + } + + @Override + protected final ColumnInfo copy(boolean mutable) { + return new EmbeddedClassColumnInfo(this, mutable); + } + + @Override + protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { + final EmbeddedClassColumnInfo src = (EmbeddedClassColumnInfo) rawSrc; + final EmbeddedClassColumnInfo dst = (EmbeddedClassColumnInfo) rawDst; + dst.nameColKey = src.nameColKey; + dst.ageColKey = src.ageColKey; + } + } + + private static final OsObjectSchemaInfo expectedObjectSchemaInfo = createExpectedObjectSchemaInfo(); + + private EmbeddedClassColumnInfo columnInfo; + private ProxyState proxyState; + + some_test_EmbeddedClassRealmProxy() { + proxyState.setConstructionFinished(); + } + + @Override + public void realm$injectObjectContext() { + if (this.proxyState != null) { + return; + } + final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); + this.columnInfo = (EmbeddedClassColumnInfo) context.getColumnInfo(); + this.proxyState = new ProxyState(this); + proxyState.setRealm$realm(context.getRealm()); + proxyState.setRow$realm(context.getRow()); + proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); + proxyState.setExcludeFields$realm(context.getExcludeFields()); + } + + @Override + @SuppressWarnings("cast") + public String realmGet$name() { + proxyState.getRealm$realm().checkIfValid(); + return (java.lang.String) proxyState.getRow$realm().getString(columnInfo.nameColKey); + } + + @Override + public void realmSet$name(String value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + row.getTable().setNull(columnInfo.nameColKey, row.getObjectKey(), true); + return; + } + row.getTable().setString(columnInfo.nameColKey, row.getObjectKey(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + proxyState.getRow$realm().setNull(columnInfo.nameColKey); + return; + } + proxyState.getRow$realm().setString(columnInfo.nameColKey, value); + } + + @Override + @SuppressWarnings("cast") + public int realmGet$age() { + proxyState.getRealm$realm().checkIfValid(); + return (int) proxyState.getRow$realm().getLong(columnInfo.ageColKey); + } + + @Override + public void realmSet$age(int value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + row.getTable().setLong(columnInfo.ageColKey, row.getObjectKey(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + proxyState.getRow$realm().setLong(columnInfo.ageColKey, value); + } + + private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("EmbeddedClass", true, 2, 0); + builder.addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); + builder.addPersistedProperty("age", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + return builder.build(); + } + + public static OsObjectSchemaInfo getExpectedObjectSchemaInfo() { + return expectedObjectSchemaInfo; + } + + public static EmbeddedClassColumnInfo createColumnInfo(OsSchemaInfo schemaInfo) { + return new EmbeddedClassColumnInfo(schemaInfo); + } + + public static String getSimpleClassName() { + return "EmbeddedClass"; + } + + public static final class ClassNameHelper { + public static final String INTERNAL_CLASS_NAME = "EmbeddedClass"; + } + + @SuppressWarnings("cast") + public static some.test.EmbeddedClass createOrUpdateUsingJsonObject(Realm realm, RealmModel parent, String parentProperty, JSONObject json, boolean update) + throws JSONException { + final List excludeFields = Collections. emptyList(); + some.test.EmbeddedClass obj = realm.createEmbeddedObject(some.test.EmbeddedClass.class, parent, parentProperty); + + final some_test_EmbeddedClassRealmProxyInterface objProxy = (some_test_EmbeddedClassRealmProxyInterface) obj; + if (json.has("name")) { + if (json.isNull("name")) { + objProxy.realmSet$name(null); + } else { + objProxy.realmSet$name((String) json.getString("name")); + } + } + if (json.has("age")) { + if (json.isNull("age")) { + throw new IllegalArgumentException("Trying to set non-nullable field 'age' to null."); + } else { + objProxy.realmSet$age((int) json.getInt("age")); + } + } + return obj; + } + + @SuppressWarnings("cast") + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static some.test.EmbeddedClass createUsingJsonStream(Realm realm, JsonReader reader) + throws IOException { + final some.test.EmbeddedClass obj = new some.test.EmbeddedClass(); + final some_test_EmbeddedClassRealmProxyInterface objProxy = (some_test_EmbeddedClassRealmProxyInterface) obj; + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (false) { + } else if (name.equals("name")) { + if (reader.peek() != JsonToken.NULL) { + objProxy.realmSet$name((String) reader.nextString()); + } else { + reader.skipValue(); + objProxy.realmSet$name(null); + } + } else if (name.equals("age")) { + if (reader.peek() != JsonToken.NULL) { + objProxy.realmSet$age((int) reader.nextInt()); + } else { + reader.skipValue(); + throw new IllegalArgumentException("Trying to set non-nullable field 'age' to null."); + } + } else { + reader.skipValue(); + } + } + reader.endObject(); + return realm.copyToRealm(obj); + } + + static some_test_EmbeddedClassRealmProxy newProxyInstance(BaseRealm realm, Row row) { + // Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + objectContext.set(realm, row, realm.getSchema().getColumnInfo(some.test.EmbeddedClass.class), false, Collections.emptyList()); + io.realm.some_test_EmbeddedClassRealmProxy obj = new io.realm.some_test_EmbeddedClassRealmProxy(); + objectContext.clear(); + return obj; + } + + public static some.test.EmbeddedClass copyOrUpdate(Realm realm, EmbeddedClassColumnInfo columnInfo, some.test.EmbeddedClass object, boolean update, Map cache, Set flags) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null) { + final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm(); + if (otherRealm.threadId != realm.threadId) { + throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); + } + if (otherRealm.getPath().equals(realm.getPath())) { + return object; + } + } + final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); + RealmObjectProxy cachedRealmObject = cache.get(object); + if (cachedRealmObject != null) { + return (some.test.EmbeddedClass) cachedRealmObject; + } + + return copy(realm, columnInfo, object, update, cache, flags); + } + + public static some.test.EmbeddedClass copy(Realm realm, EmbeddedClassColumnInfo columnInfo, some.test.EmbeddedClass newObject, boolean update, Map cache, Set flags) { + RealmObjectProxy cachedRealmObject = cache.get(newObject); + if (cachedRealmObject != null) { + return (some.test.EmbeddedClass) cachedRealmObject; + } + + some_test_EmbeddedClassRealmProxyInterface unmanagedSource = (some_test_EmbeddedClassRealmProxyInterface) newObject; + + Table table = realm.getTable(some.test.EmbeddedClass.class); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); + + // Add all non-"object reference" fields + builder.addString(columnInfo.nameColKey, unmanagedSource.realmGet$name()); + builder.addInteger(columnInfo.ageColKey, unmanagedSource.realmGet$age()); + + // Create the underlying object and cache it before setting any object/objectlist references + // This will allow us to break any circular dependencies by using the object cache. + Row row = builder.createNewObject(); + io.realm.some_test_EmbeddedClassRealmProxy managedCopy = newProxyInstance(realm, row); + cache.put(newObject, managedCopy); + + return managedCopy; + } + + public static long insert(Realm realm, Table parentObjectTable, long parentColumnKey, long parentObjectKey, some.test.EmbeddedClass object, Map cache) { + Table table = realm.getTable(some.test.EmbeddedClass.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassColumnInfo columnInfo = (EmbeddedClassColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClass.class); + long objKey = OsObject.createEmbeddedObject(parentObjectTable, parentObjectKey, parentColumnKey); + cache.put(object, objKey); + String realmGet$name = ((some_test_EmbeddedClassRealmProxyInterface) object).realmGet$name(); + if (realmGet$name != null) { + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, objKey, realmGet$name, false); + } + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, objKey, ((some_test_EmbeddedClassRealmProxyInterface) object).realmGet$age(), false); + return objKey; + } + + public static void insert(Realm realm, Table parentObjectTable, long parentColumnKey, long parentObjectKey, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.EmbeddedClass.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassColumnInfo columnInfo = (EmbeddedClassColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClass.class); + some.test.EmbeddedClass object = null; + while (objects.hasNext()) { + object = (some.test.EmbeddedClass) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); + continue; + } + long objKey = OsObject.createEmbeddedObject(parentObjectTable, parentObjectKey, parentColumnKey); + cache.put(object, objKey); + String realmGet$name = ((some_test_EmbeddedClassRealmProxyInterface) object).realmGet$name(); + if (realmGet$name != null) { + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, objKey, realmGet$name, false); + } + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, objKey, ((some_test_EmbeddedClassRealmProxyInterface) object).realmGet$age(), false); + } + } + + public static long insertOrUpdate(Realm realm, Table parentObjectTable, long parentColumnKey, long parentObjectKey, some.test.EmbeddedClass object, Map cache) { + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + return ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey(); + } + Table table = realm.getTable(some.test.EmbeddedClass.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassColumnInfo columnInfo = (EmbeddedClassColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClass.class); + long objKey = OsObject.createEmbeddedObject(parentObjectTable, parentObjectKey, parentColumnKey); + cache.put(object, objKey); + String realmGet$name = ((some_test_EmbeddedClassRealmProxyInterface) object).realmGet$name(); + if (realmGet$name != null) { + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, objKey, realmGet$name, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.nameColKey, objKey, false); + } + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, objKey, ((some_test_EmbeddedClassRealmProxyInterface) object).realmGet$age(), false); + return objKey; + } + + public static void insertOrUpdate(Realm realm, Table parentObjectTable, long parentColumnKey, long parentObjectKey, Iterator objects, Map cache) { + Table table = realm.getTable(some.test.EmbeddedClass.class); + long tableNativePtr = table.getNativePtr(); + EmbeddedClassColumnInfo columnInfo = (EmbeddedClassColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClass.class); + some.test.EmbeddedClass object = null; + while (objects.hasNext()) { + object = (some.test.EmbeddedClass) objects.next(); + if (cache.containsKey(object)) { + continue; + } + if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + cache.put(object, ((RealmObjectProxy) object).realmGet$proxyState().getRow$realm().getObjectKey()); + continue; + } + long objKey = OsObject.createEmbeddedObject(parentObjectTable, parentObjectKey, parentColumnKey); + cache.put(object, objKey); + String realmGet$name = ((some_test_EmbeddedClassRealmProxyInterface) object).realmGet$name(); + if (realmGet$name != null) { + Table.nativeSetString(tableNativePtr, columnInfo.nameColKey, objKey, realmGet$name, false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.nameColKey, objKey, false); + } + Table.nativeSetLong(tableNativePtr, columnInfo.ageColKey, objKey, ((some_test_EmbeddedClassRealmProxyInterface) object).realmGet$age(), false); + } + } + + public static some.test.EmbeddedClass createDetachedCopy(some.test.EmbeddedClass realmObject, int currentDepth, int maxDepth, Map> cache) { + if (currentDepth > maxDepth || realmObject == null) { + return null; + } + CacheData cachedObject = cache.get(realmObject); + some.test.EmbeddedClass unmanagedObject; + if (cachedObject == null) { + unmanagedObject = new some.test.EmbeddedClass(); + cache.put(realmObject, new RealmObjectProxy.CacheData(currentDepth, unmanagedObject)); + } else { + // Reuse cached object or recreate it because it was encountered at a lower depth. + if (currentDepth >= cachedObject.minDepth) { + return (some.test.EmbeddedClass) cachedObject.object; + } + unmanagedObject = (some.test.EmbeddedClass) cachedObject.object; + cachedObject.minDepth = currentDepth; + } + some_test_EmbeddedClassRealmProxyInterface unmanagedCopy = (some_test_EmbeddedClassRealmProxyInterface) unmanagedObject; + some_test_EmbeddedClassRealmProxyInterface realmSource = (some_test_EmbeddedClassRealmProxyInterface) realmObject; + unmanagedCopy.realmSet$name(realmSource.realmGet$name()); + unmanagedCopy.realmSet$age(realmSource.realmGet$age()); + + return unmanagedObject; + } + + static some.test.EmbeddedClass update(Realm realm, EmbeddedClassColumnInfo columnInfo, some.test.EmbeddedClass realmObject, some.test.EmbeddedClass newObject, Map cache, Set flags) { + some_test_EmbeddedClassRealmProxyInterface realmObjectTarget = (some_test_EmbeddedClassRealmProxyInterface) realmObject; + some_test_EmbeddedClassRealmProxyInterface realmObjectSource = (some_test_EmbeddedClassRealmProxyInterface) newObject; + Table table = realm.getTable(some.test.EmbeddedClass.class); + OsObjectBuilder builder = new OsObjectBuilder(table, flags); + builder.addString(columnInfo.nameColKey, realmObjectSource.realmGet$name()); + builder.addInteger(columnInfo.ageColKey, realmObjectSource.realmGet$age()); + + builder.updateExistingEmbeddedObject((RealmObjectProxy) realmObject); + return realmObject; + } + + public static void updateEmbeddedObject(Realm realm, some.test.EmbeddedClass unmanagedObject, some.test.EmbeddedClass managedObject, Map cache, Set flags) { + update(realm, (some_test_EmbeddedClassRealmProxy.EmbeddedClassColumnInfo) realm.getSchema().getColumnInfo(some.test.EmbeddedClass.class), managedObject, unmanagedObject, cache, flags); + } + + @Override + @SuppressWarnings("ArrayToString") + public String toString() { + if (!RealmObject.isValid(this)) { + return "Invalid object"; + } + StringBuilder stringBuilder = new StringBuilder("EmbeddedClass = proxy["); + stringBuilder.append("{name:"); + stringBuilder.append(realmGet$name() != null ? realmGet$name() : "null"); + stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{age:"); + stringBuilder.append(realmGet$age()); + stringBuilder.append("}"); + stringBuilder.append("]"); + return stringBuilder.toString(); + } + + @Override + public ProxyState realmGet$proxyState() { + return proxyState; + } + + @Override + public int hashCode() { + String realmName = proxyState.getRealm$realm().getPath(); + String tableName = proxyState.getRow$realm().getTable().getName(); + long objKey = proxyState.getRow$realm().getObjectKey(); + + int result = 17; + result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0); + result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0); + result = 31 * result + (int) (objKey ^ (objKey >>> 32)); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + some_test_EmbeddedClassRealmProxy aEmbeddedClass = (some_test_EmbeddedClassRealmProxy)o; + + BaseRealm realm = proxyState.getRealm$realm(); + BaseRealm otherRealm = aEmbeddedClass.proxyState.getRealm$realm(); + String path = realm.getPath(); + String otherPath = otherRealm.getPath(); + if (path != null ? !path.equals(otherPath) : otherPath != null) return false; + if (realm.isFrozen() != otherRealm.isFrozen()) return false; + if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID())) { + return false; + } + + String tableName = proxyState.getRow$realm().getTable().getName(); + String otherTableName = aEmbeddedClass.proxyState.getRow$realm().getTable().getName(); + if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false; + + if (proxyState.getRow$realm().getObjectKey() != aEmbeddedClass.proxyState.getRow$realm().getObjectKey()) return false; + + return true; + } +} diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java index c55e6a570e..4038408eee 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java @@ -292,8 +292,7 @@ public static some.test.EmbeddedClassSimpleParent createOrUpdateUsingJsonObject( if (json.isNull("child")) { objProxy.realmSet$child(null); } else { - some.test.EmbeddedClass childObj = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, json.getJSONObject("child"), update); - objProxy.realmSet$child(childObj); + some.test.EmbeddedClass childObj = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, (RealmModel)objProxy, "child", json.getJSONObject("child"), update); } } if (json.has("children")) { @@ -303,8 +302,7 @@ public static some.test.EmbeddedClassSimpleParent createOrUpdateUsingJsonObject( objProxy.realmGet$children().clear(); JSONArray array = json.getJSONArray("children"); for (int i = 0; i < array.length(); i++) { - some.test.EmbeddedClass item = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update); - objProxy.realmGet$children().add(item); + some.test.EmbeddedClass item = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, (RealmModel)objProxy, "children", array.getJSONObject(i), update); } } } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt index 976b037cee..c238b93cfa 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -19,7 +19,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.* import io.realm.entities.embedded.* -import io.realm.kotlin.addChangeListener import io.realm.kotlin.createEmbeddedObject import io.realm.kotlin.createObject import io.realm.kotlin.where @@ -437,11 +436,60 @@ class EmbeddedObjectsTest { } @Test - @Ignore("Add in another PR") - fun createObjectFromJson() { - TODO("Placeholder for all tests regarding importing from JSON") + fun createEmbeddedObjectFromJson() { + realm.executeTransaction { realm -> + realm.createObjectFromJson(EmbeddedCircularParent::class.java, """ + { + "id": "uuid", + "singleChild": { + "id" : "childId", + "singleChild" : { + "id": "embeddedChildId" + } + } + } + """) + } + val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! + val singleChild = circularParent.singleChild!! + assertEquals("childId", singleChild.id) + assertEquals("embeddedChildId", singleChild.singleChild!!.id) + } + + @Test + fun createEmbeddedObjectListElementFromJson() { + realm.executeTransaction { realm -> + realm.createObjectFromJson(EmbeddedSimpleListParent::class.java, """ + { + "id": "uuid", + "children": [ + { "id" : "child1" }, + { "id" : "child2" }, + { "id" : "child3" } + ] + } + """) + } + val parent = realm.where(EmbeddedSimpleListParent::class.java).findFirst()!! + assertEquals(3, parent.children!!.count()) + assertEquals("child1", parent.children[0]!!.id) + assertEquals("child2", parent.children[1]!!.id) + assertEquals("child3", parent.children[2]!!.id) } + @Test + fun createOrphanedEmbeddedObjectFromJsonThrows() { + assertFailsWith { + realm.executeTransaction { realm -> + realm.createObjectFromJson(EmbeddedSimpleChild::class.java, """ {"id": "uuid" } """ ) + } + } + } + + @Test + @Ignore("FIXME Not implemented yet") + fun createEmbeddedObjectFromJson_streamBased() { } + @Test @Ignore("Add in another PR") fun dynamicRealmObject_createEmbeddedObject() { @@ -609,4 +657,4 @@ class EmbeddedObjectsTest { // objects here? TODO() } -} \ No newline at end of file +} From f379910b466ceaa1b415c6afab9eef9607acbac7 Mon Sep 17 00:00:00 2001 From: clementetb Date: Sat, 4 Jul 2020 09:25:29 +0200 Subject: [PATCH 1613/2110] Leaking objects when registering session listeners (#6986) --- CHANGELOG.md | 1 + .../main/cpp/io_realm_internal_OsObject.cpp | 4 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 9 ++- .../src/main/cpp/io_realm_mongodb_App.cpp | 7 +-- .../cpp/io_realm_mongodb_sync_SyncSession.cpp | 27 ++++----- .../src/main/cpp/java_network_transport.hpp | 16 ++---- .../src/main/cpp/jni_util/java_class.cpp | 4 +- .../src/main/cpp/jni_util/java_class.hpp | 6 +- .../cpp/jni_util/java_global_ref_by_copy.cpp | 44 +++++++++++++++ .../cpp/jni_util/java_global_ref_by_copy.hpp | 56 +++++++++++++++++++ ...al_ref.cpp => java_global_ref_by_move.cpp} | 12 ++-- ...al_ref.hpp => java_global_ref_by_move.hpp} | 28 +++++++--- .../cpp/jni_util/java_global_weak_ref.cpp | 4 +- .../cpp/jni_util/java_global_weak_ref.hpp | 2 +- .../src/main/cpp/jni_util/jni_utils.cpp | 2 +- .../src/main/cpp/jni_util/jni_utils.hpp | 7 ++- 16 files changed, 166 insertions(+), 63 deletions(-) create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_copy.cpp create mode 100644 realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_copy.hpp rename realm/realm-library/src/main/cpp/jni_util/{java_global_ref.cpp => java_global_ref_by_move.cpp} (73%) rename realm/realm-library/src/main/cpp/jni_util/{java_global_ref.hpp => java_global_ref_by_move.hpp} (58%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 433f4477cc..a5c41833f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new ### Fixed * [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) * Added support for Json-import of objects containing embedded objects. Only supported for String/Json based Json import APIs. Stream based Json import APIs is still failing. (Issue [#6896](https://github.com/realm/realm-java/issues/6896)) +* [RealmApp] Leaking objects when registering session listeners. (Issue [#6916](https://github.com/realm/realm-java/issues/6916)) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 7e3305fcbb..9724e2cd6f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -83,7 +83,7 @@ struct ChangeCallback { } // The local ref of jstring needs to be released to avoid reach the local ref table size limitation. - std::vector field_names; + std::vector field_names; auto table = m_wrapper->m_object.obj().get_table(); for (const auto& col: change_set.columns) { if (col.second.empty()) { @@ -91,7 +91,7 @@ struct ChangeCallback { } // FIXME: After full integration of the OS schema, parse the column name from // wrapper->m_object.get_object_schema() will be faster. - field_names.push_back(JavaGlobalRef(env, to_jstring(env, table->get_column_name(ColKey(col.first))), true)); + field_names.push_back(JavaGlobalRefByMove(env, to_jstring(env, table->get_column_name(ColKey(col.first))), true)); } m_field_names_array = env->NewObjectArray(field_names.size(), JavaClassGlobalDef::java_lang_string(), 0); for (size_t i = 0; i < field_names.size(); ++i) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 863ff480f3..8799a744c1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -147,7 +147,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetSchemaConfi JNIEnv* env = JniUtils::get_env(false); // Java needs a new pointer for the OsSharedRealm life control. SharedRealm* new_shared_realm_ptr = new SharedRealm(realm); - JavaGlobalRef config_global = j_config_weak.global_ref(env); + JavaGlobalRefByMove config_global = j_config_weak.global_ref(env); if (!config_global) { return; } @@ -216,7 +216,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeSetInitializat JNIEnv* env = JniUtils::get_env(false); // Java needs a new pointer for the OsSharedRealm life control. SharedRealm* new_shared_realm_ptr = new SharedRealm(realm); - JavaGlobalRef config_global_ref = j_config_weak.global_ref(env); + JavaGlobalRefByMove config_global_ref = j_config_weak.global_ref(env); if (!config_global_ref) { return; } @@ -262,8 +262,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); // error handler will be called form the sync client thread - auto sync_service_object = env->NewGlobalRef(j_java_sync_service); // FIXME: This object is leaking - auto error_handler = [sync_service_object](std::shared_ptr session, SyncError error) { + auto error_handler = [sync_service_object = JavaGlobalRefByCopy(env, j_java_sync_service)](std::shared_ptr session, SyncError error) { auto error_category = error.error_code.category().name(); auto error_message = error.message; auto error_code = error.error_code.value(); @@ -311,7 +310,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe jstring jerror_category = to_jstring(env, error_category); jstring jerror_message = to_jstring(env, error_message); jstring jsession_path = to_jstring(env, session.get()->path()); - env->CallVoidMethod(sync_service_object, java_error_callback_method, jerror_category, error_code, jerror_message, + env->CallVoidMethod(sync_service_object.get(), java_error_callback_method, jerror_category, error_code, jerror_message, jsession_path); env->DeleteLocalRef(jerror_category); env->DeleteLocalRef(jerror_message); diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp index b545e2ec52..bfb49f968b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp @@ -99,11 +99,10 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_App_nativeCreate(JNIEnv* env, jobj { try { // App Config - jobject java_app_obj = env->NewGlobalRef(obj); // FIXME: Leaking the app object - std::function()> transport_generator = [java_app_obj] { + std::function()> transport_generator = [java_app_ref = JavaGlobalRefByCopy(env, obj)] { JNIEnv* env = JniUtils::get_env(true); - static JavaMethod get_network_transport_method(env, java_app_obj, "getNetworkTransport", "()Lio/realm/internal/objectstore/OsJavaNetworkTransport;"); - jobject network_transport_impl = env->CallObjectMethod(java_app_obj, get_network_transport_method); + static JavaMethod get_network_transport_method(env, java_app_ref.get(), "getNetworkTransport", "()Lio/realm/internal/objectstore/OsJavaNetworkTransport;"); + jobject network_transport_impl = env->CallObjectMethod(java_app_ref.get(), get_network_transport_method); return std::unique_ptr(new JavaNetworkTransport(network_transport_impl)); }; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp index d417cd272c..8ec033a3cf 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp @@ -24,7 +24,8 @@ #include "util.hpp" #include "java_class_global_def.hpp" -#include "jni_util/java_global_ref.hpp" +#include "jni_util/java_global_ref_by_move.hpp" +#include "jni_util/java_global_ref_by_copy.hpp" #include "jni_util/java_local_ref.hpp" #include "jni_util/java_method.hpp" #include "jni_util/java_class.hpp" @@ -77,12 +78,11 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddProgress static JavaClass java_syncsession_class(env, "io/realm/mongodb/sync/SyncSession"); static JavaMethod java_notify_progress_listener(env, java_syncsession_class, "notifyProgressListener", "(JJJ)V"); - auto session_ref = env->NewGlobalRef(j_session_object); // This leaks. FIXME - std::function callback = [session_ref, local_realm_path, listener_id](uint64_t transferred, uint64_t transferrable) { + auto callback = [session_ref = JavaGlobalRefByCopy(env, j_session_object), local_realm_path, listener_id](uint64_t transferred, uint64_t transferrable) { JNIEnv* local_env = jni_util::JniUtils::get_env(true); JavaLocalRef path(local_env, to_jstring(local_env, local_realm_path)); - local_env->CallVoidMethod(session_ref, + local_env->CallVoidMethod(session_ref.get(), java_notify_progress_listener, listener_id, static_cast(transferred), @@ -132,8 +132,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForD static JavaClass java_sync_session_class(env, "io/realm/mongodb/sync/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", "(ILjava/lang/Long;Ljava/lang/String;)V"); - auto obj = env->NewGlobalRef(session_object); - session->wait_for_download_completion([obj, callback_id](std::error_code error) { + + session->wait_for_download_completion([session_ref = JavaGlobalRefByCopy(env, session_object), callback_id](std::error_code error) { JNIEnv* env = JniUtils::get_env(true); JavaLocalRef java_error_code; JavaLocalRef java_error_message; @@ -142,9 +142,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForD JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); } - env->CallVoidMethod(obj, java_notify_result_method, + env->CallVoidMethod(session_ref.get(), java_notify_result_method, callback_id, java_error_code.get(), java_error_message.get()); - env->DeleteGlobalRef(obj); }); return to_jbool(JNI_TRUE); } @@ -166,8 +165,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForU static JavaClass java_sync_session_class(env, "io/realm/mongodb/sync/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", "(ILjava/lang/Long;Ljava/lang/String;)V"); - auto obj = env->NewGlobalRef(session_object); - session->wait_for_upload_completion([obj, callback_id] (std::error_code error) { + + session->wait_for_upload_completion([session_ref = JavaGlobalRefByCopy(env, session_object), callback_id] (std::error_code error) { JNIEnv* env = JniUtils::get_env(true); JavaLocalRef java_error_code; JavaLocalRef java_error_message; @@ -175,9 +174,8 @@ JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForU java_error_code = JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); } - env->CallVoidMethod(obj, java_notify_result_method, + env->CallVoidMethod(session_ref.get(), java_notify_result_method, callback_id, java_error_code.get(), java_error_message.get()); - env->DeleteGlobalRef(obj); }); return JNI_TRUE; } @@ -255,14 +253,13 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddConnecti static JavaClass java_syncmanager_class(env, "io/realm/mongodb/sync/SyncSession"); static JavaMethod java_notify_connection_listener(env, java_syncmanager_class, "notifyConnectionListeners", "(JJ)V"); - auto session_ref = env->NewGlobalRef(j_session_object); // FIXME Leaking reference to session - std::function callback = [session_ref](SyncSession::ConnectionState old_state, SyncSession::ConnectionState new_state) { + std::function callback = [session_ref = JavaGlobalRefByCopy(env, j_session_object)](SyncSession::ConnectionState old_state, SyncSession::ConnectionState new_state) { JNIEnv* local_env = jni_util::JniUtils::get_env(true); jlong old_connection_value = get_connection_value(old_state); jlong new_connection_value = get_connection_value(new_state); - local_env->CallVoidMethod(session_ref, java_notify_connection_listener, + local_env->CallVoidMethod(session_ref.get(), java_notify_connection_listener, old_connection_value, new_connection_value); // All exceptions will be caught on the Java side of handlers, but Errors will still end diff --git a/realm/realm-library/src/main/cpp/java_network_transport.hpp b/realm/realm-library/src/main/cpp/java_network_transport.hpp index 25c44e6f59..23ffdbb55c 100644 --- a/realm/realm-library/src/main/cpp/java_network_transport.hpp +++ b/realm/realm-library/src/main/cpp/java_network_transport.hpp @@ -108,8 +108,7 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { // Helper method for constructing callbacks for REST calls that must return an actual result to Java template static std::function)> create_result_callback(JNIEnv* env, jobject j_callback, const std::function& success_mapper) { - jobject callback = env->NewGlobalRef(j_callback); - return [callback, success_mapper](T result, util::Optional error) { + return [callback = JavaGlobalRefByCopy(env, j_callback), success_mapper](T result, util::Optional error) { JNIEnv* env = JniUtils::get_env(true); static JavaClass java_callback_class(env, "io/realm/internal/jni/OsJNIResultCallback"); @@ -119,23 +118,21 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { if (error) { auto err = error.value(); std::string error_category = err.error_code.category().name(); - env->CallVoidMethod(callback, + env->CallVoidMethod(callback.get(), java_notify_onerror, to_jstring(env, error_category), err.error_code.value(), to_jstring(env, err.message)); } else { jobject success_obj = success_mapper(env, result); - env->CallVoidMethod(callback, java_notify_onsuccess, success_obj); + env->CallVoidMethod(callback.get(), java_notify_onsuccess, success_obj); } - env->DeleteGlobalRef(callback); }; } // Helper method for constructing callbacks for REST calls that doesn't return any results to Java. static std::function)> create_void_callback(JNIEnv* env, jobject j_callback) { - jobject callback = env->NewGlobalRef(j_callback); - return [callback](util::Optional error) { + return [callback = JavaGlobalRefByCopy(env, j_callback)](util::Optional error) { JNIEnv* env = JniUtils::get_env(true); static JavaClass java_callback_class(env, "io/realm/internal/jni/OsJNIVoidResultCallback"); @@ -145,15 +142,14 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { if (error) { auto err = error.value(); std::string error_category = err.error_code.category().name(); - env->CallVoidMethod(callback, + env->CallVoidMethod(callback.get(), java_notify_onerror, to_jstring(env, error_category), err.error_code.value(), to_jstring(env, err.message)); } else { - env->CallVoidMethod(callback, java_notify_onsuccess, NULL); + env->CallVoidMethod(callback.get(), java_notify_onsuccess, NULL); } - env->DeleteGlobalRef(callback); }; } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_class.cpp b/realm/realm-library/src/main/cpp/jni_util/java_class.cpp index 357a504703..9f414bba35 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_class.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_class.cpp @@ -44,11 +44,11 @@ JavaClass::JavaClass(JavaClass&& rhs) rhs.m_class = nullptr; } -JavaGlobalRef JavaClass::get_jclass(JNIEnv* env, const char* class_name) +JavaGlobalRefByMove JavaClass::get_jclass(JNIEnv* env, const char* class_name) { jclass cls = env->FindClass(class_name); REALM_ASSERT_RELEASE_EX(cls, class_name); - JavaGlobalRef cls_ref(env, cls, true); + JavaGlobalRefByMove cls_ref(env, cls, true); return cls_ref; } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_class.hpp b/realm/realm-library/src/main/cpp/jni_util/java_class.hpp index e6ee130f73..777fbe9982 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_class.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_class.hpp @@ -19,7 +19,7 @@ #include -#include "java_global_ref.hpp" +#include "java_global_ref_by_move.hpp" namespace realm { namespace jni_util { @@ -58,9 +58,9 @@ class JavaClass { JavaClass& operator=(JavaClass&&) = delete; private: - JavaGlobalRef m_ref_owner; + JavaGlobalRefByMove m_ref_owner; jclass m_class; - static JavaGlobalRef get_jclass(JNIEnv* env, const char* class_name); + static JavaGlobalRefByMove get_jclass(JNIEnv* env, const char* class_name); }; } // jni_util diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_copy.cpp b/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_copy.cpp new file mode 100644 index 0000000000..abbc017cd6 --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_copy.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "java_global_ref_by_copy.hpp" +#include "jni_utils.hpp" + +#include + +using namespace realm::jni_util; + +JavaGlobalRefByCopy::JavaGlobalRefByCopy() : m_ref(nullptr) { + +} + +JavaGlobalRefByCopy::JavaGlobalRefByCopy(JNIEnv *env, jobject obj) + : m_ref(obj ? env->NewGlobalRef(obj) : nullptr) { +} + +JavaGlobalRefByCopy::JavaGlobalRefByCopy(const JavaGlobalRefByCopy &rhs) + : m_ref(rhs.m_ref ? jni_util::JniUtils::get_env(true)->NewGlobalRef(rhs.m_ref) : nullptr) { +} + +JavaGlobalRefByCopy::~JavaGlobalRefByCopy() { + if (m_ref) { + JniUtils::get_env()->DeleteGlobalRef(m_ref); + } +} + +jobject JavaGlobalRefByCopy::get() const noexcept { + return m_ref; +} diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_copy.hpp b/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_copy.hpp new file mode 100644 index 0000000000..60ea485f6c --- /dev/null +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_copy.hpp @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_JAVA_GLOBAL_REF_COPY_HPP +#define REALM_JNI_UTIL_JAVA_GLOBAL_REF_COPY_HPP + +#include + +namespace realm { + namespace jni_util { + + // Manages the lifecycle of jobject's global ref via copy constructors + // + // It prevents leaking global references by automatically referencing and unreferencing Java objects + // any time the instance is copied or destroyed. Its principal use is on data structures that don't support + // moving operations such as in std::function lambdas. + // + // Note that there is another flavor available: JavaGlobalRefByMove. + // + // JavaGlobalRefByCopy: multiple references will exist to the Java object, one on each instance. + // JavaGlobalRefByMove: only one reference will only be available at last moved instance. + + class JavaGlobalRefByCopy { + public: + JavaGlobalRefByCopy(); + + JavaGlobalRefByCopy(JNIEnv *env, jobject obj); + + JavaGlobalRefByCopy(const JavaGlobalRefByCopy &rhs); + + JavaGlobalRefByCopy(JavaGlobalRefByCopy&& rhs) = delete; + + ~JavaGlobalRefByCopy(); + + jobject get() const noexcept; + + private: + jobject m_ref; + }; + } +} + +#endif // REALM_JNI_UTIL_JAVA_GLOBAL_REF_COPY_HPP diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp b/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_move.cpp similarity index 73% rename from realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp rename to realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_move.cpp index 68d9fd99de..110d9d8e75 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_move.cpp @@ -14,28 +14,28 @@ * limitations under the License. */ -#include "java_global_ref.hpp" +#include "java_global_ref_by_move.hpp" #include "jni_utils.hpp" #include using namespace realm::jni_util; -JavaGlobalRef::~JavaGlobalRef() +JavaGlobalRefByMove::~JavaGlobalRefByMove() { if (m_ref) { JniUtils::get_env()->DeleteGlobalRef(m_ref); } } -JavaGlobalRef& JavaGlobalRef::operator=(JavaGlobalRef&& rhs) +JavaGlobalRefByMove& JavaGlobalRefByMove::operator=(JavaGlobalRefByMove&& rhs) { - this->~JavaGlobalRef(); - new (this) JavaGlobalRef(std::move(rhs)); + this->~JavaGlobalRefByMove(); + new (this) JavaGlobalRefByMove(std::move(rhs)); return *this; } -JavaGlobalRef::JavaGlobalRef(JavaGlobalRef& rhs) +JavaGlobalRefByMove::JavaGlobalRefByMove(JavaGlobalRefByMove& rhs) : m_ref(rhs.m_ref ? jni_util::JniUtils::get_env(true)->NewGlobalRef(rhs.m_ref) : nullptr) { } diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_move.hpp similarity index 58% rename from realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp rename to realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_move.hpp index 7c68165b63..e0dd691723 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_global_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_ref_by_move.hpp @@ -22,31 +22,41 @@ namespace realm { namespace jni_util { -// Manage the lifecycle of jobject's global ref. -class JavaGlobalRef { +// Manages the lifecycle of jobject's global ref via move constructors +// +// It prevents leaking global references by automatically referencing and unreferencing Java objects +// any time the instance is moved or destroyed. Its principal use is on data structures that support move +// operations, such as std::vector. +// +// Note that there is another flavor available: JavaGlobalRefByCopy. +// +// JavaGlobalRefByCopy: multiple references will exist to the Java object, one on each instance. +// JavaGlobalRefByMove: only one reference will only be available at last moved instance. + +class JavaGlobalRefByMove { public: - JavaGlobalRef() + JavaGlobalRefByMove() : m_ref(nullptr) { } // Acquire a global ref on the given jobject. The local ref will be released if given release_local_ref is true. - JavaGlobalRef(JNIEnv* env, jobject obj, bool release_local_ref = false) + JavaGlobalRefByMove(JNIEnv* env, jobject obj, bool release_local_ref = false) : m_ref(obj ? env->NewGlobalRef(obj) : nullptr) { if (release_local_ref) { env->DeleteLocalRef(obj); } } - JavaGlobalRef(JavaGlobalRef&& rhs) + JavaGlobalRefByMove(JavaGlobalRefByMove&& rhs) : m_ref(rhs.m_ref) { rhs.m_ref = nullptr; } - ~JavaGlobalRef(); + ~JavaGlobalRefByMove(); - JavaGlobalRef& operator=(JavaGlobalRef&& rhs); - JavaGlobalRef& operator=(JavaGlobalRef& rhs) = delete; - JavaGlobalRef(JavaGlobalRef&); + JavaGlobalRefByMove& operator=(JavaGlobalRefByMove&& rhs); + JavaGlobalRefByMove& operator=(JavaGlobalRefByMove& rhs) = delete; + JavaGlobalRefByMove(JavaGlobalRefByMove&); inline operator bool() const noexcept { diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp index 898cdeeace..3c7341017a 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.cpp @@ -60,12 +60,12 @@ JavaGlobalWeakRef& JavaGlobalWeakRef::operator=(const JavaGlobalWeakRef& rhs) return *this; } -JavaGlobalRef JavaGlobalWeakRef::global_ref(JNIEnv* env) const +JavaGlobalRefByMove JavaGlobalWeakRef::global_ref(JNIEnv* env) const { if (!env) { env = JniUtils::get_env(true); } - return JavaGlobalRef(env, m_weak); + return JavaGlobalRefByMove(env, m_weak); } bool JavaGlobalWeakRef::call_with_local_ref(JNIEnv* env, std::function callback) const diff --git a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp index f21d640f0a..0d511fc905 100644 --- a/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/java_global_weak_ref.hpp @@ -43,7 +43,7 @@ class JavaGlobalWeakRef { return m_weak != nullptr; } - JavaGlobalRef global_ref(JNIEnv* env = nullptr) const; + JavaGlobalRefByMove global_ref(JNIEnv* env = nullptr) const; using Callback = void(JNIEnv* env, jobject obj); diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp index 0ca6f526a3..61f92910f1 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp @@ -60,7 +60,7 @@ void JniUtils::detach_current_thread() s_instance->m_vm->DetachCurrentThread(); } -void JniUtils::keep_global_ref(JavaGlobalRef& ref) +void JniUtils::keep_global_ref(JavaGlobalRefByMove& ref) { s_instance->m_global_refs.push_back(std::move(ref)); } diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp index 689aa1232c..9cd63b69b6 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp @@ -21,7 +21,8 @@ #include -#include "java_global_ref.hpp" +#include "java_global_ref_by_move.hpp" +#include "java_global_ref_by_copy.hpp" namespace realm { namespace jni_util { @@ -44,7 +45,7 @@ class JniUtils { // Failing to do so is a resource leak. static void detach_current_thread(); // Keep the given global reference until JNI_OnUnload is called. - static void keep_global_ref(JavaGlobalRef& ref); + static void keep_global_ref(JavaGlobalRefByMove& ref); private: JniUtils(JavaVM* vm, jint vm_version) noexcept @@ -55,7 +56,7 @@ class JniUtils { JavaVM* m_vm; jint m_vm_version; - std::vector m_global_refs; + std::vector m_global_refs; }; } // namespace realm From bea18c53d2902f0da003e4248da79c95e9dcbfea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Sat, 18 Jul 2020 17:34:26 +0200 Subject: [PATCH 1614/2110] Add DynamicRealm.createEmbeddedObject + add check for invalid Realm model types (#6982) --- CHANGELOG.md | 2 + .../processor/RealmProxyClassGenerator.kt | 6 +- ...t_EmbeddedClassSimpleParentRealmProxy.java | 4 +- .../kotlin/io/realm/EmbeddedObjectsTest.kt | 208 ++++++++++++++++-- .../entities/embedded/EmbeddedSimpleChild.kt | 1 - .../src/main/java/io/realm/BaseRealm.java | 40 +++- .../src/main/java/io/realm/DynamicRealm.java | 49 +++++ .../java/io/realm/DynamicRealmObject.java | 2 +- .../main/java/io/realm/FrozenPendingRow.java | 2 +- .../io/realm/ImmutableRealmObjectSchema.java | 10 + .../io/realm/MutableRealmObjectSchema.java | 11 + .../src/main/java/io/realm/Realm.java | 30 +-- .../main/java/io/realm/RealmObjectSchema.java | 27 ++- .../src/main/java/io/realm/RealmResults.java | 2 +- .../java/io/realm/internal/InvalidRow.java | 2 +- .../java/io/realm/internal/PendingRow.java | 2 +- .../src/main/java/io/realm/internal/Row.java | 2 +- .../java/io/realm/internal/UncheckedRow.java | 13 +- 18 files changed, 359 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c41833f5..7ec43838de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new ### Enhancements * Credentials information (e.g. username, password) displayed in Logcat is now obfuscated by default, even if [LogLevel] is set to DEBUG, TRACE or ALL. * RealmLists can now be marked final. (Issue [#6892](https://github.com/realm/realm-java/issues/6892)) +* It is now possible to create embedded objects using [DynamicRealm]s. (Issue [#6982](https://github.com/realm/realm-java/pull/6982)) +* Added extra validation and more meaningful error messages when creating embedded objects pointing to the wrong parent property. (See issue above) ### Fixed * [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index a85dd59ee0..c16917d38a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -1645,6 +1645,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val fieldName: String = field.simpleName.toString() val getter: String = metadata.getInternalGetter(fieldName) val setter: String = metadata.getInternalSetter(fieldName) + val parentPropertyType: Constants.RealmFieldType = getRealmType(field) when { Utils.isRealmModel(field) -> { @@ -1663,7 +1664,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cache%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) nextControlFlow("else") - emitStatement("long objKey = ((RealmObjectProxy) managedCopy).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s)", fieldColKey) + emitStatement("long objKey = ((RealmObjectProxy) managedCopy).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s, RealmFieldType.%s)", fieldColKey, parentPropertyType.name) emitStatement("Row linkedObjectRow = realm.getTable(%s.class).getUncheckedRow(objKey)", linkedQualifiedClassName) emitStatement("%s linkedObject = %s.newProxyInstance(realm, linkedObjectRow)", linkedQualifiedClassName, linkedProxyClass) emitStatement("cache.put(%sObj, (RealmObjectProxy) linkedObject)", fieldName) @@ -1824,6 +1825,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val fieldName = field.simpleName.toString() val getter = metadata.getInternalGetter(fieldName) val fieldColKey = fieldColKeyVariableReference(field) + val parentPropertyType: Constants.RealmFieldType = getRealmType(field) when { Utils.isRealmModel(field) -> { @@ -1846,7 +1848,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) endControlFlow() emitEmptyLine() - emitStatement("long objKey = ((RealmObjectProxy) realmObject).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s)", fieldColKey) + emitStatement("long objKey = ((RealmObjectProxy) realmObject).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s, RealmFieldType.%s)", fieldColKey, parentPropertyType.name) emitStatement("Row row = realm.getTable(%s.class).getUncheckedRow(objKey)", Utils.getFieldTypeQualifiedName(field)) emitStatement("%s proxyObject = %s.newProxyInstance(realm, row)", fieldType, Utils.getProxyClassSimpleName(field)) emitStatement("cache.put(%sObj, (RealmObjectProxy) proxyObject)", fieldName) diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java index 4038408eee..1169c24e6b 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java @@ -442,7 +442,7 @@ public static some.test.EmbeddedClassSimpleParent copy(Realm realm, EmbeddedClas if (cachechild != null) { throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechild.toString()"); } else { - long objKey = ((RealmObjectProxy) managedCopy).realmGet$proxyState().getRow$realm().createEmbeddedObject(columnInfo.childColKey); + long objKey = ((RealmObjectProxy) managedCopy).realmGet$proxyState().getRow$realm().createEmbeddedObject(columnInfo.childColKey, RealmFieldType.OBJECT); Row linkedObjectRow = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); some.test.EmbeddedClass linkedObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, linkedObjectRow); cache.put(childObj, (RealmObjectProxy) linkedObject); @@ -765,7 +765,7 @@ static some.test.EmbeddedClassSimpleParent update(Realm realm, EmbeddedClassSimp throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechild.toString()"); } - long objKey = ((RealmObjectProxy) realmObject).realmGet$proxyState().getRow$realm().createEmbeddedObject(columnInfo.childColKey); + long objKey = ((RealmObjectProxy) realmObject).realmGet$proxyState().getRow$realm().createEmbeddedObject(columnInfo.childColKey, RealmFieldType.OBJECT); Row row = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey); some.test.EmbeddedClass proxyObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, row); cache.put(childObj, (RealmObjectProxy) proxyObject); diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt index c238b93cfa..04f557ecb2 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -91,7 +91,6 @@ class EmbeddedObjectsTest { } @Test - @Ignore("FIXME") fun createEmbeddedObject_wrongParentPropertyObjectTypeThrows() = realm.executeTransaction { realm -> val parent = realm.createObject("parent") @@ -102,7 +101,6 @@ class EmbeddedObjectsTest { } @Test - @Ignore("FIXME") fun createEmbeddedObject_wrongParentPropertyListTypeThrows() = realm.executeTransaction { realm -> val parent = realm.createObject("parent") @@ -132,11 +130,76 @@ class EmbeddedObjectsTest { } @Test - @Ignore("Placeholder for all tests for DynamicRealm.createEmbeddedObject()") - fun dynamicRealm_createEmbeddedObject() { - TODO() + fun dynamicRealm_createEmbeddedObject() = + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val parent = realm.createObject("EmbeddedSimpleParent", "PK_VALUE") + val child = realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "child") + + val idValue = "ID_VALUE" + child.setString("id", idValue) + + val childInParent = parent.getObject("child") + assertNotNull(childInParent) + assertEquals(childInParent!!.getString("id"), idValue) + assertEquals(child, childInParent) + + val linkingParent = child.linkingObjects("EmbeddedSimpleParent", "child") .first() + assertNotNull(linkingParent) + assertEquals(parent.getString("id"), linkingParent!!.getString("id")) + assertEquals(parent.getObject("child"), linkingParent.getObject("child")) + } + } + + @Test + fun dynamicRealm_createEmbeddedObject_simpleChildList() = + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val parent = realm.createObject("EmbeddedSimpleListParent", UUID.randomUUID().toString()) + val child1 = realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "children") + val child2 = realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "children") + assertEquals(2, parent.getList("children").size.toLong()) + assertEquals(child1, parent.getList("children").first()!!) + assertEquals(child2, parent.getList("children").last()!!) + } + } + + @Test + fun dynamicRealm_createEmbeddedObject_wrongParentPropertyTypeThrows() { + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val parent = realm.createObject("EmbeddedSimpleParent", "parent") + assertFailsWith { realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "id") } + } + } } + @Test + fun dynamicRealm_createEmbeddedObject_wrongParentPropertyObjectTypeThrows() = + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val parent = realm.createObject("EmbeddedSimpleParent", "parent") + + assertFailsWith { + // Embedded object is not of the type the parent object links to. + realm.createEmbeddedObject("EmbeddedTreeLeaf", parent, "child") + } + } + } + + @Test + fun dynamicRealm_createEmbeddedObject_wrongParentPropertyListTypeThrows() = + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val parent = realm.createObject("EmbeddedSimpleListParent", "parent") + + assertFailsWith { + // Embedded object is not of the type the parent object links to. + realm.createEmbeddedObject("EmbeddedTreeLeaf", parent, "children") + } + } + } + @Test fun settingParentFieldDeletesChild() = realm.executeTransaction { realm -> val parent = EmbeddedSimpleParent("parent") @@ -149,6 +212,20 @@ class EmbeddedObjectsTest { assertEquals(0, realm.where().count()) } + @Test + fun dynamicRealm_settingParentFieldDeletesChild() = + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val parent = realm.createObject("EmbeddedSimpleParent", "parent") + val child = realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "child") + + assertEquals(1, realm.where("EmbeddedSimpleChild").count()) + parent.setObject("child", null) + assertFalse(child.isValid) + assertEquals(0, realm.where("EmbeddedSimpleChild").count()) + } + } + @Test fun objectAccessor_willAutomaticallyCopyUnmanaged() = realm.executeTransaction { realm -> // Checks that adding an unmanaged embedded object to a property will automatically copy it. @@ -332,13 +409,14 @@ class EmbeddedObjectsTest { @Ignore("FIXME") fun copyToRealmOrUpdate_deleteReplacedObjects() { TODO() - } @Test - @Ignore("Add in another PR") fun insert_noParentThrows() { - TODO() + realm.executeTransaction { realm -> + val child = EmbeddedSimpleChild("child") + assertFailsWith { realm.insert(child) } + } } @Test @@ -490,13 +568,6 @@ class EmbeddedObjectsTest { @Ignore("FIXME Not implemented yet") fun createEmbeddedObjectFromJson_streamBased() { } - @Test - @Ignore("Add in another PR") - fun dynamicRealmObject_createEmbeddedObject() { - TODO("Consider which kind of support there should be for embedded objets in DynamicRealm") - } - - @Test fun realmObjectSchema_setEmbedded() { DynamicRealm.getInstance(realm.configuration).use { realm -> @@ -559,6 +630,14 @@ class EmbeddedObjectsTest { assertFalse(realm.schema[AllTypes.CLASS_NAME]!!.isEmbedded) } + @Test + fun dynamicRealm_realmObjectSchema_isEmbedded() { + DynamicRealm.getInstance(realm.configuration).use { realm -> + assertTrue(realm.schema[EmbeddedSimpleChild.NAME]!!.isEmbedded) + assertFalse(realm.schema[AllTypes.CLASS_NAME]!!.isEmbedded) + } + } + // Check that deleting a non-embedded parent deletes all embedded children @Test fun deleteParentObject_deletesEmbeddedChildren() = realm.executeTransaction { @@ -575,6 +654,23 @@ class EmbeddedObjectsTest { assertEquals(0, realm.where().count()) } + @Test + fun dynamicRealm_deleteParentObject_deletesEmbeddedChildren() = + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val parent = realm.createObject("EmbeddedSimpleParent", "parent") + assertEquals(0, realm.where("EmbeddedSimpleChild").count()) + + val child = realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "child") + assertEquals(1, realm.where("EmbeddedSimpleChild").count()) + + parent.deleteFromRealm() + assertFalse(child.isValid) + assertEquals(0, realm.where("EmbeddedSimpleParent").count()) + assertEquals(0, realm.where("EmbeddedSimpleChild").count()) + } + } + // Check that deleting a embedded parent deletes all embedded children @Test fun deleteParentEmbeddedObject_deletesEmbeddedChildren() = realm.executeTransaction { @@ -593,6 +689,30 @@ class EmbeddedObjectsTest { assertEquals(0, realm.where().count()) } + @Test + fun dynamic_deleteParentEmbeddedObject_deletesEmbeddedChildren() = + DynamicRealm.getInstance(realm.configuration).use { realm -> + realm.executeTransaction { + val parent = realm.createObject("EmbeddedTreeParent", "parent1") + val middleNode = realm.createEmbeddedObject("EmbeddedTreeNode", parent, "middleNode"); + middleNode.setString("id", "node1") + val leaf1 = realm.createEmbeddedObject("EmbeddedTreeLeaf", middleNode, "leafNode"); + val leaf2 = realm.createEmbeddedObject("EmbeddedTreeLeaf", middleNode, "leafNodeList"); + val leaf3 = realm.createEmbeddedObject("EmbeddedTreeLeaf", middleNode, "leafNodeList"); + + assertEquals(1, realm.where("EmbeddedTreeNode").count()) + assertEquals(3, realm.where("EmbeddedTreeLeaf").count()) + parent.deleteFromRealm() + assertEquals(0, realm.where("EmbeddedTreeNode").count()) + assertEquals(0, realm.where("EmbeddedSimpleChild").count()) + assertFalse(parent.isValid) + assertFalse(middleNode.isValid) + assertFalse(leaf1.isValid) + assertFalse(leaf2.isValid) + assertFalse(leaf3.isValid) + } + } + // Cascade deleting an embedded object will trigger its object listener. @Test fun deleteParent_triggerChildObjectNotifications() = looperThread.runBlocking { @@ -614,7 +734,35 @@ class EmbeddedObjectsTest { }) realm.executeTransaction { - child.parent!!.deleteFromRealm() + child.parent.deleteFromRealm() + } + } + + @Test + fun dynamicRealm_deleteParent_triggerChildObjectNotifications() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(realm.configuration) + looperThread.closeAfterTest(realm) + + realm.executeTransaction { + val parent = realm.createObject("EmbeddedSimpleParent", "parent") + realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "child") + } + + val queriedChild = realm.where("EmbeddedSimpleParent") + .findFirst()!! + .getObject("child")!! + .apply { + addChangeListener(RealmChangeListener { + if (!it.isValid) { + looperThread.testComplete() + } + }) + } + + realm.executeTransaction { + queriedChild.linkingObjects("EmbeddedSimpleParent", "child") + .first()!! + .deleteFromRealm() } } @@ -649,6 +797,34 @@ class EmbeddedObjectsTest { } } + @Test + fun dynamicRealm_deleteParent_triggerChildListObjectNotifications() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(realm.configuration) + looperThread.closeAfterTest(realm) + + realm.executeTransaction { + val parent = realm.createObject("EmbeddedSimpleListParent", "parent") + realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "children") + realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "children") + } + + realm.where("EmbeddedSimpleListParent") + .findFirst()!! + .getList("children") + .apply { + addChangeListener { list -> + if (!list.isValid) { + looperThread.testComplete() + } + } + } + + realm.executeTransaction { + realm.where("EmbeddedSimpleListParent") + .findFirst()!! + .deleteFromRealm() + } + } @Test @Ignore("Add in another PR") diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt index 345cbbf908..0b015f8cd2 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt @@ -33,5 +33,4 @@ open class EmbeddedSimpleChild(var id: String = UUID.randomUUID().toString()) : companion object { const val NAME = "EmbeddedSimpleChild" } - } diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index f058222f8e..270d9ba358 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -35,11 +35,11 @@ import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; import io.realm.internal.InvalidRow; -import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsObjectStore; import io.realm.internal.OsRealmConfig; import io.realm.internal.OsSchemaInfo; import io.realm.internal.OsSharedRealm; +import io.realm.internal.RealmObjectProxy; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Row; import io.realm.internal.Table; @@ -508,6 +508,44 @@ protected void checkIfValidAndInTransaction() { } } + /** + * Creates a row representing an embedded object - for internal use only. + * + * @param className the class name of the object to create. + * @param parentProxy The parent object which should hold a reference to the embedded object. + * @param parentProperty the property in the parent class which holds the reference. + * @param schema the Realm schema from which to obtain table information. + * @param parentObjectSchema the parent object schema from which to obtain property information. + * @return the row representing the newly created embedded object. + * @throws IllegalArgumentException if any embedded object invariants are broken. + */ + Row getEmbeddedObjectRow(final String className, + final RealmObjectProxy parentProxy, + final String parentProperty, + final RealmSchema schema, + final RealmObjectSchema parentObjectSchema) { + final long parentPropertyColKey = parentObjectSchema.getColumnKey(parentProperty); + final RealmFieldType parentPropertyType = parentObjectSchema.getFieldType(parentProperty); + final Row row = parentProxy.realmGet$proxyState().getRow$realm(); + final RealmFieldType fieldType = parentObjectSchema.getFieldType(parentProperty); + boolean propertyAcceptable = parentObjectSchema.isPropertyAcceptableForEmbeddedObject(fieldType); + if (!propertyAcceptable) { + throw new IllegalArgumentException(String.format("Field '%s' does not contain a valid link", parentProperty)); + } + final String linkedType = parentObjectSchema.getPropertyClassName(parentProperty); + + // By now linkedType can only be either OBJECT or LIST, so no exhaustive check needed + Row embeddedObject; + if (linkedType.equals(className)) { + long objKey = row.createEmbeddedObject(parentPropertyColKey, parentPropertyType); + embeddedObject = schema.getTable(className).getCheckedRow(objKey); + } else { + throw new IllegalArgumentException(String.format("Parent type %s expects that property '%s' be of type %s but was %s.", parentObjectSchema.getClassName(), parentProperty, linkedType, className)); + } + + return embeddedObject; + } + /** * Checks if the Realm is not built with a SyncRealmConfiguration. */ diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index eeef10b648..57c743d65f 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -19,13 +19,17 @@ import java.util.Locale; import io.reactivex.Flowable; +import io.realm.annotations.RealmClass; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.internal.CheckedRow; import io.realm.internal.OsObject; import io.realm.internal.OsObjectStore; import io.realm.internal.OsSharedRealm; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.Row; import io.realm.internal.Table; +import io.realm.internal.Util; import io.realm.log.RealmLog; /** @@ -160,6 +164,51 @@ public DynamicRealmObject createObject(String className, Object primaryKeyValue) CheckedRow.getFromRow(OsObject.createWithPrimaryKey(table, primaryKeyValue))); } + /** + * Instantiates and adds a new embedded object to the Realm. + *

              + * This method should only be used to create objects of types marked as embedded. + * + * @param className the class name of the object to create. + * @param parentObject The parent object which should hold a reference to the embedded object. + * If the parent property is a list the embedded object will be added to the + * end of that list. + * @param parentProperty the property in the parent class which holds the reference. + * @return the newly created embedded object. + * @throws IllegalArgumentException if {@code clazz} is not an embedded class or if the property + * in the parent class cannot hold objects of the appropriate type. + * @see RealmClass#embedded() + */ + public DynamicRealmObject createEmbeddedObject(String className, + DynamicRealmObject parentObject, + String parentProperty) { + checkIfValid(); + Util.checkNull(parentObject, "parentObject"); + Util.checkEmpty(parentProperty, "parentProperty"); + if (!RealmObject.isManaged(parentObject) || !RealmObject.isValid(parentObject)) { + throw new IllegalArgumentException("Only valid, managed objects can be a parent to an embedded object."); + } + + String pkField = OsObjectStore.getPrimaryKeyForObject(sharedRealm, className); + // Check and throw the exception earlier for a better exception message. + if (pkField != null) { + throw new RealmException(String.format(Locale.US, + "'%s' has a primary key field '%s', embedded objects cannot have primary keys.", + className, pkField)); + } + + String parentClassName = parentObject.getType(); + RealmObjectSchema parentObjectSchema = schema.get(parentClassName); + + if (parentObjectSchema == null) { + throw new IllegalStateException(String.format("No schema found for '%s'.", parentClassName)); + } + + Row embeddedObject = getEmbeddedObjectRow(className, parentObject, parentProperty, schema, parentObjectSchema); + + return new DynamicRealmObject(this, embeddedObject); + } + /** * Returns a RealmQuery, which can be used to query the provided class. * diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index c376b5e5c1..970f99e412 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -1281,7 +1281,7 @@ public RealmResults linkingObjects(String srcClassName, Stri RealmFieldType.OBJECT.name(), RealmFieldType.LIST.name())); } - return RealmResults.createDynamicBacklinkResults(realm, (CheckedRow) proxyState.getRow$realm(), realmObjectSchema.getTable(), srcFieldName); + return RealmResults.createDynamicBacklinkResults(realm, (UncheckedRow) proxyState.getRow$realm(), realmObjectSchema.getTable(), srcFieldName); } /** diff --git a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java index 58fc0fb63f..027c7722aa 100644 --- a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java @@ -198,7 +198,7 @@ public void setObjectId(long columnKey, ObjectId value) { } @Override - public long createEmbeddedObject(long columnKey) { + public long createEmbeddedObject(long columnKey, RealmFieldType parentPropertyType) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } diff --git a/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java index b7cc1d25ff..b69a49639b 100644 --- a/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/ImmutableRealmObjectSchema.java @@ -106,6 +106,16 @@ public RealmObjectSchema transform(Function function) { throw new UnsupportedOperationException(SCHEMA_IMMUTABLE_EXCEPTION_MSG); } + @Override + String getPropertyClassName(String propertyName) { + ColumnInfo.ColumnDetails columnDetails = columnInfo.getColumnDetails(propertyName); + if (columnDetails == null) { + throw new IllegalArgumentException(String.format("Property '%s' not found.", propertyName)); + } else { + return columnDetails.linkedClassName; + } + } + /** * Returns a field descriptor based on Java field names found in model classes. * diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java index d1a43e37bd..8371c6e7f9 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmObjectSchema.java @@ -24,6 +24,7 @@ import io.realm.internal.OsObjectStore; import io.realm.internal.OsResults; import io.realm.internal.Table; +import io.realm.internal.Util; import io.realm.internal.core.DescriptorOrdering; import io.realm.internal.fields.FieldDescriptor; @@ -315,6 +316,16 @@ public RealmObjectSchema transform(Function function) { return this; } + @Override + String getPropertyClassName(String propertyName) { + String linkedClassName = table.getLinkTarget(getColumnKey(propertyName)).getClassName(); + if (Util.isEmptyString(linkedClassName)) { + throw new IllegalArgumentException(String.format("Property '%s' not found.", propertyName)); + } + + return linkedClassName; + } + /** * Returns a field descriptor based on the internal field names found in the Realm file. * diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index b2c6b98c8b..3122b8afd2 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1027,10 +1027,10 @@ public E createObject(Class clazz, @Nullable Object pr /** * Instantiates and adds a new embedded object to the Realm. *

              - * This method should only be used to created objects of types marked as embedded. + * This method should only be used to create objects of types marked as embedded. * * @param clazz the Class of the object to create. It must be marked with {@code \@RealmClass(embedded = true)}. - * @param parentObject The parent object which should a reference to the embedded object. If the parent property is a list + * @param parentObject The parent object which should hold a reference to the embedded object. If the parent property is a list * the embedded object will be added to the end of that list. * @param parentProperty the property in the parent class which holds the reference. * @return the newly created embedded object. @@ -1045,26 +1045,12 @@ public E createEmbeddedObject(Class clazz, RealmModel if (!RealmObject.isManaged(parentObject) || !RealmObject.isValid(parentObject)) { throw new IllegalArgumentException("Only valid, managed objects can be a parent to an embedded object."); } - RealmObjectProxy proxy = (RealmObjectProxy) parentObject; - long parentPropertyColKey = schema.getSchemaForClass(parentObject.getClass()).getColumnKey(parentProperty); - RealmFieldType parentPropertyType = schema.getSchemaForClass(parentObject.getClass()).getFieldType(parentProperty); - Row embeddedObject; - switch(parentPropertyType) { - case OBJECT: { - // FIXME: Check type of link - long objKey = proxy.realmGet$proxyState().getRow$realm().createEmbeddedObject(parentPropertyColKey); - embeddedObject = getTable(clazz).getUncheckedRow(objKey); - break; - } - case LIST: { - // FIXME: Check type of link - long objKey = proxy.realmGet$proxyState().getRow$realm().getModelList(parentPropertyColKey).createAndAddEmbeddedObject(); - embeddedObject = getTable(clazz).getUncheckedRow(objKey); - break; - } - default: - throw new IllegalArgumentException("Parent property is not a reference to embedded objects of the appropriate type: " + parentPropertyType); - } + + String className = schema.getSchemaForClass(clazz).getClassName(); + Class parentClassName = parentObject.getClass(); + RealmObjectSchema parentObjectSchema = schema.getSchemaForClass(parentClassName); + + Row embeddedObject = getEmbeddedObjectRow(className, (RealmObjectProxy) parentObject, parentProperty, schema, parentObjectSchema); //noinspection unchecked return (E) configuration.getSchemaMediator().newInstance(clazz, diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index b96f95632b..1c70004c55 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -83,7 +83,7 @@ public abstract class RealmObjectSchema { final RealmSchema schema; final BaseRealm realm; final Table table; - private final ColumnInfo columnInfo; + final ColumnInfo columnInfo; /** * Creates a schema object for a given Realm class. @@ -462,6 +462,31 @@ public void setEmbedded(boolean embedded) { } } + /** + * Returns a string with the class name of a given property. + * @param propertyName the property for which we want to know the class name. + * @return the name of the class for the given property. + * @throws IllegalArgumentException if the given property is not found in the schema. + */ + abstract String getPropertyClassName(String propertyName); + + /** + * Checks whether a given property's {@code RealmFieldType} could host an acceptable embedded + * object reference in a parent - acceptable embedded object types are + * {@link RealmFieldType#OBJECT} and {@link RealmFieldType#LIST}, i.e. for the property to be + * acceptable it has to be either a subclass of {@code RealmModel} or a {@code RealmList}. + *

              + * This method does not check the existence of a backlink between the child and the parent nor + * that the parent points at the correct child in their respective schemas nor that the object + * is a suitable parent/child. + * @param property the field type to be checked. + * @return whether the property could host an embedded object in a parent. + */ + boolean isPropertyAcceptableForEmbeddedObject(RealmFieldType property) { + return property == RealmFieldType.OBJECT + || property == RealmFieldType.LIST; + } + /** * Get a parser for a field descriptor. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 617bd9c2d7..270fcec8a7 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -83,7 +83,7 @@ static RealmResults createBacklinkResults(BaseRealm re } // Abandon typing information, all ye who enter here - static RealmResults createDynamicBacklinkResults(DynamicRealm realm, CheckedRow row, Table srcTable, String srcFieldName) { + static RealmResults createDynamicBacklinkResults(DynamicRealm realm, UncheckedRow row, Table srcTable, String srcFieldName) { final String srcClassName = Table.getClassNameForTable(srcTable.getName()); //noinspection ConstantConditions return new RealmResults<>( diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index 92545b82bf..02880c6a59 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -193,7 +193,7 @@ public void setObjectId(long columnKey, ObjectId value) { } @Override - public long createEmbeddedObject(long columnKey) { + public long createEmbeddedObject(long columnKey, RealmFieldType parentPropertyType) { throw getStubException(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index 70095a7411..b8c043db3b 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -222,7 +222,7 @@ public void setObjectId(long columnKey, ObjectId value) { } @Override - public long createEmbeddedObject(long columnKey) { + public long createEmbeddedObject(long columnKey, RealmFieldType parentPropertyType) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index ce5ddeaf3d..27c170ee8e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -123,7 +123,7 @@ public interface Row { // Creates a new Embedded object in the given property. // This will replace any existing object which will be // deleted. The Obj pointer for the new object is returned. - long createEmbeddedObject(long columnKey); + long createEmbeddedObject(long columnKey, RealmFieldType parentPropertyType); /** * Checks if the row is still valid. diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index 9ad00ab02a..c9f6e5be85 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -307,9 +307,16 @@ public void setObjectId(long columnKey, @Nullable ObjectId value) { } @Override - public long createEmbeddedObject(long columnKey) { - parent.checkImmutable(); - return nativeCreateEmbeddedObject(nativePtr, columnKey); + public long createEmbeddedObject(long columnKey, RealmFieldType parentPropertyType) { + switch (parentPropertyType) { + case OBJECT: + parent.checkImmutable(); + return nativeCreateEmbeddedObject(nativePtr, columnKey); + case LIST: + return getModelList(columnKey).createAndAddEmbeddedObject(); + default: + throw new IllegalArgumentException("Wrong parentPropertyType, expected OBJECT or LIST but received " + parentPropertyType); + } } /** From 0ddcc16136d31984aaaec7387a552c1d31a5ccb9 Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Sun, 19 Jul 2020 22:00:36 +0200 Subject: [PATCH 1615/2110] Create no-response.yml --- .github/no-response.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/no-response.yml diff --git a/.github/no-response.yml b/.github/no-response.yml new file mode 100644 index 0000000000..7193eaa3b2 --- /dev/null +++ b/.github/no-response.yml @@ -0,0 +1,13 @@ +# Configuration for probot-no-response - https://github.com/probot/no-response + +# Number of days of inactivity before an Issue is closed for lack of response +daysUntilClose: 14 +# Label requiring a response +responseRequiredLabel: more-information-needed +# Comment to post when closing an Issue for lack of response. Set to `false` to disable +closeComment: > + This issue has been automatically closed because there has been no response + to our request for more information from the original author. With only the + information that is currently in the issue, we don't have enough information + to take action. Please reach out if you have or find the answers we need so + that we can investigate further. From a30d90fca455f4158f4a9f8649657c4e4cebc6af Mon Sep 17 00:00:00 2001 From: clementetb Date: Wed, 29 Jul 2020 10:35:20 +0200 Subject: [PATCH 1616/2110] Enable and fix link user tests (#6993) --- .../kotlin/io/realm/CredentialsTests.kt | 12 +- .../kotlin/io/realm/UserTests.kt | 138 ++++++++++++------ .../network/LoggingInterceptorTest.kt | 8 +- .../internal/objectstore/OsSyncUser.java | 2 +- .../kotlin/io/realm/ObfuscatorHelper.kt | 2 +- .../testUtils/java/io/realm/TestHelper.java | 4 + .../functions/testAuthFunc/source.js | 7 +- 7 files changed, 112 insertions(+), 61 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index 1aa4f3a7bd..0e81663c21 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -100,11 +100,11 @@ class CredentialsTests { @Test fun customFunction() { - val mail = "myfakemail@mongodb.com" - val id = 666 + val mail = TestHelper.getRandomEmail() + val id = 666 + TestHelper.getRandomId() val creds = mapOf( - "mail" to "myfakemail@mongodb.com", - "id" to 666 + "mail" to mail, + "id" to id ).let { Credentials.customFunction(Document(it)) } assertEquals(Credentials.IdentityProvider.CUSTOM_FUNCTION, creds.identityProvider) assertTrue(creds.asJson().contains(mail)) @@ -199,8 +199,8 @@ class CredentialsTests { } Credentials.IdentityProvider.CUSTOM_FUNCTION -> { val customFunction = mapOf( - "mail" to "myfakemail@mongodb.com", - "id" to 666 + "mail" to TestHelper.getRandomEmail(), + "id" to 666 + TestHelper.getRandomId() ).let { Credentials.customFunction(Document(it)) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt index a42b2a169d..29bcd86492 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt @@ -20,6 +20,7 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.mongodb.* import io.realm.mongodb.auth.ApiKeyAuth +import io.realm.mongodb.auth.UserApiKey import io.realm.rule.BlockingLooperThread import org.bson.Document import org.junit.After @@ -28,6 +29,7 @@ import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import kotlin.test.assertFails import kotlin.test.assertFailsWith val CUSTOM_USER_DATA_FIELD = "custom_field" @@ -81,13 +83,10 @@ class UserTests { @Test fun logOut() { - anonUser.logOut(); // Remove user created for other tests - // Anonymous users are removed upon log out - val user1: User = app.login(Credentials.anonymous()) - assertEquals(user1, app.currentUser()) - user1.logOut() - assertEquals(User.State.REMOVED, user1.state) + assertEquals(anonUser, app.currentUser()) + anonUser.logOut() + assertEquals(User.State.REMOVED, anonUser.state) assertNull(app.currentUser()) // Users registered with Email/Password will register as Logged Out @@ -161,56 +160,103 @@ class UserTests { @Test fun logOutAsync_throwsOnNonLooperThread() { - val user: User = app.login(Credentials.anonymous()) try { - user.logOutAsync { fail() } + anonUser.logOutAsync { fail() } fail() } catch (ignore: IllegalStateException) { } } - @Ignore("FIXME: Wait for linkUser support in ObjectStore") @Test - fun linkUser() { - admin.setAutomaticConfirmation(enabled = false) - val anonUser: User = app.login(Credentials.anonymous()) + fun linkUser_emailPassword() { assertEquals(1, anonUser.identities.size) val email = TestHelper.getRandomEmail() val password = "123456" app.emailPasswordAuth.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled var linkedUser: User = anonUser.linkCredentials(Credentials.emailPassword(email, password)) + assertTrue(anonUser === linkedUser) assertEquals(2, linkedUser.identities.size) assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) - admin.setAutomaticConfirmation(enabled = true) + // Validate that we cannot link a second set of credentials val otherEmail = TestHelper.getRandomEmail() val otherPassword = "123456" app.emailPasswordAuth.registerUser(otherEmail, otherPassword) - linkedUser = anonUser.linkCredentials(Credentials.emailPassword(email, password)) + + val credentials = Credentials.emailPassword(otherEmail, otherPassword) + + assertFails { + linkedUser = anonUser.linkCredentials(credentials) + } + } + + @Test + fun linkUser_userApiKey() { + // Generate API key + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val apiKey: UserApiKey = user.apiKeyAuth.createApiKey("my-key"); + user.logOut() + + anonUser = app.login(Credentials.anonymous()) + + assertEquals(1, anonUser.identities.size) + + // Linking with another user's API key is not allowed and must raise an AppException + val exception = assertFailsWith{ + anonUser.linkCredentials(Credentials.apiKey(apiKey.value)) + } + + assertEquals("invalid user link request", exception.errorMessage); + assertEquals(ErrorCode.Category.FATAL, exception.errorCode.category); + assertEquals("realm::app::ServiceError", exception.errorCode.type); + assertEquals(6, exception.errorCode.intValue()); + } + + @Test + fun linkUser_serverApiKey() { + val serverKey = admin.createServerApiKey() + + assertEquals(1, anonUser.identities.size) + + // Linking a server API key is not allowed + val exception = assertFailsWith{ + anonUser.linkCredentials(Credentials.serverApiKey(serverKey)) + } + } + + @Test + fun linkUser_customFunction() { + assertEquals(1, anonUser.identities.size) + + val document = Document(mapOf( + "mail" to TestHelper.getRandomEmail(), + "id" to TestHelper.getRandomId() + 666 + )) + + val credentials = Credentials.customFunction(document) + + val linkedUser = anonUser.linkCredentials(credentials) + assertTrue(anonUser === linkedUser) - assertEquals(3, linkedUser.identities.size) - assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[2].provider) - admin.setAutomaticConfirmation(enabled = true) + assertEquals(2, linkedUser.identities.size) + assertEquals(Credentials.IdentityProvider.CUSTOM_FUNCTION, linkedUser.identities[1].provider) } - @Ignore("FIXME: Wait for linkUser support in ObjectStore") @Test fun linkUser_existingCredentialsThrows() { val email = TestHelper.getRandomEmail() val password = "123456" val emailUser: User = app.registerUserAndLogin(email, password) - val anonymousUser: User = app.login(Credentials.anonymous()) try { - anonymousUser.linkCredentials(Credentials.emailPassword(email, password)) + anonUser.linkCredentials(Credentials.emailPassword(email, password)) fail() } catch (ex: AppException) { - assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) + assertEquals(ErrorCode.INVALID_SESSION, ex.errorCode) } } - @Ignore("FIXME: Wait for linkUser support in ObjectStore") @Test fun linkUser_invalidArgsThrows() { try { @@ -220,27 +266,22 @@ class UserTests { } } - @Ignore("FIXME: Wait for linkUser support in ObjectStore") @Test - fun linkUserAsync() { - admin.setAutomaticConfirmation(enabled = false) - val user: User = app.login(Credentials.anonymous()) - assertEquals(1, user.identities.size) + fun linkUserAsync() = looperThread.runBlocking { + assertEquals(1, anonUser.identities.size) val email = TestHelper.getRandomEmail() val password = "123456" app.emailPasswordAuth.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled - looperThread.runBlocking { - anonUser.linkCredentialsAsync(Credentials.emailPassword(email, password)) { result -> - val linkedUser: User = result.orThrow - assertTrue(user === linkedUser) - assertEquals(2, linkedUser.identities.size) - assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) - admin.setAutomaticConfirmation(enabled = true) - } + + anonUser.linkCredentialsAsync(Credentials.emailPassword(email, password)) { result -> + val linkedUser: User = result.orThrow + assertTrue(anonUser === linkedUser) + assertEquals(2, linkedUser.identities.size) + assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) + looperThread.testComplete() } } - @Ignore("FIXME: Wait for linkUser support in ObjectStore") @Test fun linkUserAsync_throwsOnNonLooperThread() { try { @@ -337,15 +378,18 @@ class UserTests { // FIXME Test for all meta data @Ignore("Not implemented yet") - fun user_metaData() { } + fun user_metaData() { + } // FIXME @Ignore("Not implemented yet") - fun accessToken() { } + fun accessToken() { + } // FIXME @Ignore("Not implemented yet") - fun refreshToken() { } + fun refreshToken() { + } @Test fun revokedRefreshTokenIsNotSameAfterLogin() = looperThread.runBlocking { @@ -353,8 +397,8 @@ class UserTests { val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), password) val refreshToken = user.refreshToken - app.addAuthenticationListener(object: AuthenticationListener { - override fun loggedIn(user: User) { } + app.addAuthenticationListener(object : AuthenticationListener { + override fun loggedIn(user: User) {} override fun loggedOut(loggerOutUser: User) { app.loginAsync(Credentials.emailPassword(loggerOutUser.email, password)) { @@ -370,12 +414,12 @@ class UserTests { // FIXME @Ignore("Not implemented yet") - fun isLoggedIn() { } + fun isLoggedIn() { + } @Test fun equals() { - // TODO Could be that we could use a fake user - val user: User = app.registerUserAndLogin("user1@example.com", "123456") + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertEquals(user, user) assertNotEquals(user, app) user.logOut() @@ -385,13 +429,13 @@ class UserTests { assertFalse(user === sameUserNewLogin) assertEquals(user, sameUserNewLogin) - val differentUser: User = app.registerUserAndLogin("user2@example.com", "123456") + val differentUser: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertNotEquals(user, differentUser) } @Test fun hashCode_user() { - val user: User = app.registerUserAndLogin("user1@example.com", "123456") + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") user.logOut() val sameUserNewLogin = app.login(Credentials.emailPassword(user.email!!, "123456")) @@ -479,7 +523,7 @@ class UserTests { val client = user.getMongoClient(SERVICE_NAME) client.getDatabase(DATABASE_NAME).let { it.getCollection(COLLECTION_NAME).also { collection -> - collection.insertOne(data.append(USER_ID_FIELD , user.id)).get() + collection.insertOne(data.append(USER_ID_FIELD, user.id)).get() } } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt index 1ed03ccda5..2d50cd551f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt @@ -114,8 +114,8 @@ class LoggingInterceptorTest { val key1 = "mail" val key2 = "id" - val value1 = "myfakemail@mongodb.com" - val value2 = 666 + val value1 = TestHelper.getRandomEmail() + val value2 = 666 + TestHelper.getRandomId() val customFunction = mapOf( key1 to value1, key2 to value2 @@ -136,8 +136,8 @@ class LoggingInterceptorTest { val key1 = "mail" val key2 = "id" - val value1 = "myfakemail@mongodb.com" - val value2 = 666 + val value1 = TestHelper.getRandomEmail() + val value2 = 666 + TestHelper.getRandomId() val customFunction = mapOf( key1 to value1, key2 to value2 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java index 2ee0bfaf18..6e04efbd86 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java @@ -108,7 +108,7 @@ public Pair[] getIdentities() { @SuppressWarnings("unchecked") Pair[] identities = new Pair[identityData.length/2]; for (int i = 0; i < identityData.length; i = i + 2) { - identities[i] = new Pair<>(identityData[i], identityData[i+1]); + identities[i/2] = new Pair<>(identityData[i], identityData[i+1]); } return identities; } diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt index c5e5813511..83f2d9f85c 100644 --- a/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt +++ b/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt @@ -55,7 +55,7 @@ object ObfuscatorHelper { { "mail":"myfakemail@mongodb.com", "id":{ - "{${'$'}}numberInt": "666" + "{${'$'}}numberInt": 666" }, "options":{ "device":{ diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index cbeda876bd..6572eb8370 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -335,6 +335,10 @@ public static byte[] getRandomKey() { return key; } + public static int getRandomId() { + return Math.abs(RANDOM.nextInt()); + } + public static String getRandomEmail() { StringBuilder sb = new StringBuilder(UUID.randomUUID().toString().toLowerCase()); sb.append('@'); diff --git a/tools/sync_test_server/app_config/functions/testAuthFunc/source.js b/tools/sync_test_server/app_config/functions/testAuthFunc/source.js index 1bebadb559..4c1e87f25a 100644 --- a/tools/sync_test_server/app_config/functions/testAuthFunc/source.js +++ b/tools/sync_test_server/app_config/functions/testAuthFunc/source.js @@ -1,7 +1,10 @@ exports = ({mail, id}) => { - if (mail != "myfakemail@mongodb.com" || id != 666) { + // Auth function will fail for emails with a domain different to @androidtest.realm.io + // or with id lower than 666 + if (!new RegExp("@androidtest.realm.io$").test(mail) || id < 666) { return 0; } else { - return "works"; + // Use the users email as UID + return mail; } } From aee89e3de51b588fc1c4fb677102d924b4181e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 6 Aug 2020 16:14:15 +0200 Subject: [PATCH 1617/2110] Stream based JSON import of embedded objects (#7015) --- CHANGELOG.md | 2 +- .../io/realm/processor/RealmJsonTypeHelper.kt | 4 +- .../processor/RealmProxyClassGenerator.kt | 10 +- .../processor/RealmProxyMediatorGenerator.kt | 6 +- .../some_test_EmbeddedClassRealmProxy.java | 4 +- ...t_EmbeddedClassSimpleParentRealmProxy.java | 4 +- .../kotlin/io/realm/EmbeddedObjectsTest.kt | 227 +++++++++++++++--- ...EmbeddedCircularParentWithoutPrimaryKey.kt | 26 ++ ...beddedSimpleListParentWithoutPrimaryKey.kt | 27 +++ 9 files changed, 268 insertions(+), 42 deletions(-) create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParentWithoutPrimaryKey.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec43838de..007bb18dff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new ### Fixed * [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) -* Added support for Json-import of objects containing embedded objects. Only supported for String/Json based Json import APIs. Stream based Json import APIs is still failing. (Issue [#6896](https://github.com/realm/realm-java/issues/6896)) +* Added support for Json-import of objects containing embedded objects. (Issue [#6896](https://github.com/realm/realm-java/issues/6896)) * [RealmApp] Leaking objects when registering session listeners. (Issue [#6916](https://github.com/realm/realm-java/issues/6916)) ### Compatibility diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt index f99ce2502f..9c77f3a587 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt @@ -88,7 +88,7 @@ object RealmJsonTypeHelper { emitStatement("%s %sObj = %s.createOrUpdateUsingJsonObject(realm, json.getJSONObject(\"%s\"), update)", qualifiedFieldType, fieldName, proxyClass, fieldName) emitStatement("%s.%s(%sObj)", varName, setter, fieldName) } else { - emitStatement("%s %sObj = %s.createOrUpdateUsingJsonObject(realm, (RealmModel)%s, \"%s\", json.getJSONObject(\"%s\"), update)", qualifiedFieldType, fieldName, proxyClass, varName, fieldName, fieldName) + emitStatement("%s.createOrUpdateEmbeddedUsingJsonObject(realm, (RealmModel)%s, \"%s\", json.getJSONObject(\"%s\"), update)", proxyClass, varName, fieldName, fieldName) } endControlFlow() endControlFlow() @@ -116,7 +116,7 @@ object RealmJsonTypeHelper { emitStatement("%s item = %s.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update)", fieldTypeCanonicalName, proxyClass, fieldTypeCanonicalName) emitStatement("%s.%s().add(item)", varName, getter) } else { - emitStatement("%s item = %s.createOrUpdateUsingJsonObject(realm, (RealmModel)%s, \"%s\", array.getJSONObject(i), update)", fieldTypeCanonicalName, proxyClass, varName, fieldName) + emitStatement("%s.createOrUpdateEmbeddedUsingJsonObject(realm, (RealmModel)%s, \"%s\", array.getJSONObject(i), update)", proxyClass, varName, fieldName) } endControlFlow() endControlFlow() diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index c16917d38a..a2c34b66c0 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -2073,7 +2073,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi if (!embedded) { beginMethod(qualifiedJavaClassName, "createOrUpdateUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "JSONObject", "json", "boolean", "update"), listOf("JSONException")) } else { - beginMethod(qualifiedJavaClassName, "createOrUpdateUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "RealmModel", "parent", "String", "parentProperty", "JSONObject", "json", "boolean", "update"), listOf("JSONException")) + beginMethod(qualifiedJavaClassName, "createOrUpdateEmbeddedUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "RealmModel", "parent", "String", "parentProperty", "JSONObject", "json", "boolean", "update"), listOf("JSONException")) } val modelOrListCount = countModelOrListFields(metadata.fields) if (modelOrListCount == 0) { @@ -2281,7 +2281,13 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.primaryKey) endControlFlow() } - emitStatement("return realm.copyToRealm(obj)") + if (!metadata.embedded) { + emitStatement("return realm.copyToRealm(obj)") + } else { + // Embedded objects are left unmanaged and assumed to be added by their parent. This + // is safe as json import is blocked for embedded objects without a parent. + emitStatement("return obj") + } endMethod() emitEmptyLine() } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt index 2ea9ca4853..e80c29ee96 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyMediatorGenerator.kt @@ -446,7 +446,11 @@ class RealmProxyMediatorGenerator(private val processingEnvironment: ProcessingE Arrays.asList("java.io.IOException") ) emitMediatorShortCircuitSwitch(writer, emitStatement = { i: Int -> - emitStatement("return clazz.cast(%s.createUsingJsonStream(realm, reader))", qualifiedProxyClasses[i]) + if (!embeddedClass[i]) { + emitStatement("return clazz.cast(%s.createUsingJsonStream(realm, reader))", qualifiedProxyClasses[i]) + } else { + emitStatement("throw new IllegalArgumentException(\"Importing embedded classes from JSON without a parent is not allowed\")") + } }) endMethod() emitEmptyLine() diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassRealmProxy.java index 9ceb48d229..5462444792 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassRealmProxy.java @@ -166,7 +166,7 @@ public static final class ClassNameHelper { } @SuppressWarnings("cast") - public static some.test.EmbeddedClass createOrUpdateUsingJsonObject(Realm realm, RealmModel parent, String parentProperty, JSONObject json, boolean update) + public static some.test.EmbeddedClass createOrUpdateEmbeddedUsingJsonObject(Realm realm, RealmModel parent, String parentProperty, JSONObject json, boolean update) throws JSONException { final List excludeFields = Collections. emptyList(); some.test.EmbeddedClass obj = realm.createEmbeddedObject(some.test.EmbeddedClass.class, parent, parentProperty); @@ -218,7 +218,7 @@ public static some.test.EmbeddedClass createUsingJsonStream(Realm realm, JsonRea } } reader.endObject(); - return realm.copyToRealm(obj); + return obj; } static some_test_EmbeddedClassRealmProxy newProxyInstance(BaseRealm realm, Row row) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java index 1169c24e6b..d702a55435 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java @@ -292,7 +292,7 @@ public static some.test.EmbeddedClassSimpleParent createOrUpdateUsingJsonObject( if (json.isNull("child")) { objProxy.realmSet$child(null); } else { - some.test.EmbeddedClass childObj = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, (RealmModel)objProxy, "child", json.getJSONObject("child"), update); + some_test_EmbeddedClassRealmProxy.createOrUpdateEmbeddedUsingJsonObject(realm, (RealmModel)objProxy, "child", json.getJSONObject("child"), update); } } if (json.has("children")) { @@ -302,7 +302,7 @@ public static some.test.EmbeddedClassSimpleParent createOrUpdateUsingJsonObject( objProxy.realmGet$children().clear(); JSONArray array = json.getJSONArray("children"); for (int i = 0; i < array.length(); i++) { - some.test.EmbeddedClass item = some_test_EmbeddedClassRealmProxy.createOrUpdateUsingJsonObject(realm, (RealmModel)objProxy, "children", array.getJSONObject(i), update); + some_test_EmbeddedClassRealmProxy.createOrUpdateEmbeddedUsingJsonObject(realm, (RealmModel)objProxy, "children", array.getJSONObject(i), update); } } } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt index 04f557ecb2..1a373d0103 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -24,9 +24,12 @@ import io.realm.kotlin.createObject import io.realm.kotlin.where import io.realm.rule.BlockingLooperThread import io.realm.rule.TestRealmConfigurationFactory +import org.json.JSONObject import org.junit.* import org.junit.Assert.* import org.junit.runner.RunWith +import java.io.ByteArrayInputStream +import java.nio.charset.Charset import java.util.* import kotlin.test.assertFailsWith @@ -34,6 +37,34 @@ import kotlin.test.assertFailsWith * Class testing the Embedded Objects feature. */ // FIXME: Move all of these tests out from here. We try to tests by Class, not Feature. + +private val UTF_8 = Charset.forName("UTF-8"); + +private const val parentId = "uuid" +private const val childId = "childId" +private const val embeddedChildId = "embeddedChildId" +private const val childId1 = "childId1" +private const val childId2 = "childId2" +private const val childId3 = "childId3" + +private val circularParentData = mapOf( + "id" to parentId, + "singleChild" to mapOf( + "id" to childId, + "singleChild" to mapOf( + "id" to embeddedChildId + ) + ) +) +private val simpleListParentData = mapOf( + "id" to parentId, + "children" to listOf( + mapOf("id" to childId1), + mapOf("id" to childId2), + mapOf("id" to childId3) + ) +) + @RunWith(AndroidJUnit4::class) class EmbeddedObjectsTest { @@ -513,60 +544,185 @@ class EmbeddedObjectsTest { TODO() } + // TODO Move all json import tests to RealmJsonTests when RealmJsonTests have been + // converted to Kotlin + // Sanity check of string based variants. Implementation dispatches to json variant covered + // below, so not covering all cases for the string-variants. @Test - fun createEmbeddedObjectFromJson() { + fun createObjectFromJson_string_embeddedObject() { realm.executeTransaction { realm -> - realm.createObjectFromJson(EmbeddedCircularParent::class.java, """ - { - "id": "uuid", - "singleChild": { - "id" : "childId", - "singleChild" : { - "id": "embeddedChildId" - } - } - } - """) + realm.createObjectFromJson(EmbeddedCircularParent::class.java, JSONObject(circularParentData).toString()) } val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! val singleChild = circularParent.singleChild!! - assertEquals("childId", singleChild.id) + assertEquals(childId, singleChild.id) assertEquals("embeddedChildId", singleChild.singleChild!!.id) } @Test - fun createEmbeddedObjectListElementFromJson() { + fun createObjectFromJson_json_embeddedObject() { realm.executeTransaction { realm -> - realm.createObjectFromJson(EmbeddedSimpleListParent::class.java, """ - { - "id": "uuid", - "children": [ - { "id" : "child1" }, - { "id" : "child2" }, - { "id" : "child3" } - ] - } - """) + realm.createObjectFromJson(EmbeddedCircularParent::class.java, JSONObject(circularParentData).toString()) + } + val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! + val singleChild = circularParent.singleChild!! + assertEquals(childId, singleChild.id) + assertEquals(embeddedChildId, singleChild.singleChild!!.id) + } + + @Test + fun createObjectFromJson_json_embeddedObjectList() { + realm.executeTransaction { realm -> + realm.createObjectFromJson(EmbeddedSimpleListParent::class.java, json(simpleListParentData)) } val parent = realm.where(EmbeddedSimpleListParent::class.java).findFirst()!! assertEquals(3, parent.children!!.count()) - assertEquals("child1", parent.children[0]!!.id) - assertEquals("child2", parent.children[1]!!.id) - assertEquals("child3", parent.children[2]!!.id) + assertEquals(childId1, parent.children[0]!!.id) + assertEquals(childId2, parent.children[1]!!.id) + assertEquals(childId3, parent.children[2]!!.id) + } + + @Test + fun createObjectFromJson_stream_embeddedObject() { + val clz = EmbeddedCircularParent::class.java + realm.executeTransaction { realm -> + assertTrue(realm.schema.getSchemaForClass(clz).hasPrimaryKey()) + realm.createObjectFromJson(clz, stream(circularParentData)) + } + val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! + val singleChild = circularParent.singleChild!! + assertEquals(childId, singleChild.id) + assertEquals(embeddedChildId, singleChild.singleChild!!.id) + } + + // Stream based import implementation is differentiated depending on whether the class has a + // primary key, so add specific tests for that path. + @Test + fun createObjectFromJson_stream_embeddedObjectWithNoPrimaryKeyParent() { + val clz = EmbeddedCircularParentWithoutPrimaryKey::class.java + realm.executeTransaction { realm -> + assertFalse(realm.schema.getSchemaForClass(clz).hasPrimaryKey()) + realm.createObjectFromJson(clz, stream(circularParentData)) + } + val all = realm.where(EmbeddedCircularParentWithoutPrimaryKey::class.java).findAll() + assertEquals(1, all.count()) + val parent = all.first()!! + val child = parent.singleChild!! + assertEquals(childId, child.id) + } + + @Test + fun createObjectFromJson_stream_embeddedObjectList() { + val clz = EmbeddedSimpleListParent::class.java + realm.executeTransaction { realm -> + assertTrue(realm.schema.getSchemaForClass(clz).hasPrimaryKey()) + realm.createObjectFromJson(clz, stream(simpleListParentData)) + } + val all = realm.where(EmbeddedSimpleListParent::class.java).findAll() + assertEquals(1, all.count()) + val parent = all.first()!! + assertEquals(3, parent.children!!.count()) + assertEquals(childId1, parent.children[0]!!.id) + assertEquals(childId2, parent.children[1]!!.id) + assertEquals(childId3, parent.children[2]!!.id) + } + + // Stream based import implementation is differentiated depending on whether the class has a primary key + @Test + fun createObjectFromJson_stream_embeddedObjectListWithNoPrimaryKeyParent() { + val clz = EmbeddedSimpleListParentWithoutPrimaryKey::class.java + realm.executeTransaction { realm -> + assertFalse(realm.schema.getSchemaForClass(clz).hasPrimaryKey()) + realm.createObjectFromJson(clz, stream(simpleListParentData)) + } + val all = realm.where(EmbeddedSimpleListParentWithoutPrimaryKey::class.java).findAll() + assertEquals(1, all.count()) + val parent = all.first()!! + assertEquals(parentId, parent.id) + assertEquals(3, parent.children!!.count()) + assertEquals(childId1, parent.children[0]!!.id) + assertEquals(childId2, parent.children[1]!!.id) + assertEquals(childId3, parent.children[2]!!.id) } @Test - fun createOrphanedEmbeddedObjectFromJsonThrows() { + fun createOrUpdateFromJson_json_ignoreUnsetProperties() { + // Create initial instance + realm.executeTransaction { realm -> + realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, json(circularParentData)) + } + val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! + val singleChild = circularParent.singleChild!! + assertEquals(childId, singleChild.id) + assertEquals(embeddedChildId, singleChild.singleChild!!.id) + + // Update existing objects, but without overwriting any properties + realm.executeTransaction { realm -> + val circularParentData = mapOf( + "id" to parentId + ) + realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, json(circularParentData)) + } + val allParents = realm.where(EmbeddedCircularParent::class.java).findAll() + assertEquals(1, allParents.count()) + val allChildren = realm.where(EmbeddedCircularChild::class.java).findAll() + assertEquals(2, allChildren.count()) + val updatedCircularParent = allParents.first()!! + val updatedSingleChild = circularParent.singleChild!! + assertEquals(parentId, updatedCircularParent.id) + assertEquals(childId, updatedSingleChild.id) + assertEquals(embeddedChildId, updatedSingleChild.singleChild!!.id) + } + + @Test + fun createOrUpdateFromJson_stream_embeddedObject() { + // Create initial instance + realm.executeTransaction { realm -> + realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, stream(circularParentData)) + } + val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! + val singleChild = circularParent.singleChild!! + assertEquals(childId, singleChild.id) + assertEquals(embeddedChildId, singleChild.singleChild!!.id) + + // Update existing objects, updating to new embedded object + realm.executeTransaction { realm -> + val circularParentData = mapOf( + "id" to parentId, + "singleChild" to mapOf( + "id" to childId + ) + ) + realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, stream(circularParentData)) + } + val allParents = realm.where(EmbeddedCircularParent::class.java).findAll() + assertEquals(1, allParents.count()) + val allChildren = realm.where(EmbeddedCircularChild::class.java).findAll() + assertEquals(1, allChildren.count()) + val updatedCircularParent = allParents.first()!! + val updatedSingleChild = circularParent.singleChild!! + assertEquals(parentId, updatedCircularParent.id) + assertEquals(childId, updatedSingleChild.id) + // Sub child will have been deleted as embedded object does not have primary key and is + // comletely replaced + assertNull(updatedSingleChild.singleChild) + } + + @Test + fun createObjectFromJson_orphanedEmbeddedObjectThrows() { + throws { realm.createObjectFromJson(EmbeddedSimpleChild::class.java, json(simpleListParentData)) } + throws { realm.createObjectFromJson(EmbeddedSimpleChild::class.java, string(simpleListParentData)) } + throws { realm.createObjectFromJson(EmbeddedSimpleChild::class.java, stream(simpleListParentData)) } + } + + private fun throws(block: () -> Unit) { assertFailsWith { realm.executeTransaction { realm -> - realm.createObjectFromJson(EmbeddedSimpleChild::class.java, """ {"id": "uuid" } """ ) + block() } } } - @Test - @Ignore("FIXME Not implemented yet") - fun createEmbeddedObjectFromJson_streamBased() { } @Test fun realmObjectSchema_setEmbedded() { @@ -833,4 +989,11 @@ class EmbeddedObjectsTest { // objects here? TODO() } + + // Convenience methods to create json in various forms from a map + private fun json(data: Map) = JSONObject(data) + private fun string(data: Map) = json(data).toString() + private fun stream(data: Map) = + ByteArrayInputStream(JSONObject(data).toString().toByteArray(UTF_8)) + } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt new file mode 100644 index 0000000000..61deef328b --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Parent pointing to an embedded object that has a circular schema, i.e. objects can point +// to themselves. Note, this isn't actually allowed at runtime. Only at schema validation time. +open class EmbeddedCircularParentWithoutPrimaryKey(var id: String = UUID.randomUUID().toString()) : RealmObject() { + var singleChild: EmbeddedCircularChild? = null +} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParentWithoutPrimaryKey.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParentWithoutPrimaryKey.kt new file mode 100644 index 0000000000..821f30b800 --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParentWithoutPrimaryKey.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities.embedded + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import java.util.* + +// Top-level object describing a simple embedded objects structure consisting of only a +// list of embedded objects. +open class EmbeddedSimpleListParentWithoutPrimaryKey(var id: String = UUID.randomUUID().toString()) : RealmObject() { + var children: RealmList = RealmList() +} From f10262128b6ebe656959b1416de3d5d5c56ee11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 7 Aug 2020 17:00:21 +0200 Subject: [PATCH 1618/2110] Add tests for syncing embedded objects (#7007) --- .../kotlin/io/realm/EmbeddedObjectsTest.kt | 134 +++++----- .../embedded/EmbeddedCircularChild.kt | 2 +- .../embedded/EmbeddedCircularParent.kt | 2 +- ...EmbeddedCircularParentWithoutPrimaryKey.kt | 2 +- .../entities/embedded/EmbeddedSimpleChild.kt | 2 +- .../embedded/EmbeddedSimpleListParent.kt | 2 +- ...beddedSimpleListParentWithoutPrimaryKey.kt | 2 +- .../entities/embedded/EmbeddedSimpleParent.kt | 2 +- .../entities/embedded/EmbeddedTreeLeaf.kt | 2 +- .../entities/embedded/EmbeddedTreeNode.kt | 2 +- .../entities/embedded/EmbeddedTreeParent.kt | 4 +- .../io/realm/entities/DefaultSyncSchema.kt | 17 +- .../io/realm/mongodb/sync/SyncedRealmTests.kt | 238 +++++++++++++++++- .../src/main/java/io/realm/BaseRealm.java | 2 +- 14 files changed, 324 insertions(+), 89 deletions(-) diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt index 1a373d0103..86ea265786 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -48,20 +48,20 @@ private const val childId2 = "childId2" private const val childId3 = "childId3" private val circularParentData = mapOf( - "id" to parentId, + "_id" to parentId, "singleChild" to mapOf( - "id" to childId, + "circularChildId" to childId, "singleChild" to mapOf( - "id" to embeddedChildId + "circularChildId" to embeddedChildId ) ) ) private val simpleListParentData = mapOf( - "id" to parentId, + "_id" to parentId, "children" to listOf( - mapOf("id" to childId1), - mapOf("id" to childId2), - mapOf("id" to childId3) + mapOf("childId" to childId1), + mapOf("childId" to childId2), + mapOf("childId" to childId3) ) ) @@ -118,7 +118,7 @@ class EmbeddedObjectsTest { val parent = realm.createObject("parent") // TODO: Smoke-test for wrong type. Figure out how to test all unsupported types. - assertFailsWith { realm.createEmbeddedObject(parent, "id") } + assertFailsWith { realm.createEmbeddedObject(parent, "childId") } } @Test @@ -144,7 +144,7 @@ class EmbeddedObjectsTest { @Test fun createEmbeddedObject_simpleSingleChild() = realm.executeTransaction { realm -> val parent = realm.createObject("parent") - val child = realm.createEmbeddedObject(parent, "child"); + val child = realm.createEmbeddedObject(parent, "child") assertEquals(child.parent, parent) } @@ -168,16 +168,16 @@ class EmbeddedObjectsTest { val child = realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "child") val idValue = "ID_VALUE" - child.setString("id", idValue) + child.setString("childId", idValue) val childInParent = parent.getObject("child") assertNotNull(childInParent) - assertEquals(childInParent!!.getString("id"), idValue) + assertEquals(childInParent!!.getString("childId"), idValue) assertEquals(child, childInParent) - val linkingParent = child.linkingObjects("EmbeddedSimpleParent", "child") .first() + val linkingParent = child.linkingObjects("EmbeddedSimpleParent", "child").first() assertNotNull(linkingParent) - assertEquals(parent.getString("id"), linkingParent!!.getString("id")) + assertEquals(parent.getString("_id"), linkingParent!!.getString("_id")) assertEquals(parent.getObject("child"), linkingParent.getObject("child")) } } @@ -200,7 +200,7 @@ class EmbeddedObjectsTest { DynamicRealm.getInstance(realm.configuration).use { realm -> realm.executeTransaction { val parent = realm.createObject("EmbeddedSimpleParent", "parent") - assertFailsWith { realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "id") } + assertFailsWith { realm.createEmbeddedObject("EmbeddedSimpleChild", parent, "_id") } } } } @@ -291,7 +291,7 @@ class EmbeddedObjectsTest { fun objectAccessor_willCopyUnderConstruction() = realm.executeTransaction { realm -> val unmanagedObj = EmbeddedWithConstructorArgs() val managedObj = realm.copyToRealm(unmanagedObj) - assertEquals(EmbeddedWithConstructorArgs.INNER_CHILD_ID, managedObj.child!!.id) + assertEquals(EmbeddedWithConstructorArgs.INNER_CHILD_ID, managedObj.child!!.childId) } @Test @@ -300,7 +300,7 @@ class EmbeddedObjectsTest { assertTrue(parent.children.add(EmbeddedSimpleChild("child"))) val child = parent.children.first()!! assertTrue(child.isValid) - assertEquals("child", child.id) + assertEquals("child", child.childId) // FIXME: How to handle DynamicRealmObject :( } @@ -312,7 +312,7 @@ class EmbeddedObjectsTest { parent.children.add(0, EmbeddedSimpleChild("firstChild")) val child = parent.children.first()!! assertTrue(child.isValid) - assertEquals("firstChild", child.id) + assertEquals("firstChild", child.childId) // FIXME: How to handle DynamicRealmObject :( } @@ -325,7 +325,7 @@ class EmbeddedObjectsTest { assertTrue(parent.children.add(EmbeddedSimpleChild("child"))) assertEquals(1, realm.where().count()) parent.children[0] = EmbeddedSimpleChild("OtherChild") - assertEquals("OtherChild", parent.children.first()!!.id) + assertEquals("OtherChild", parent.children.first()!!.childId) assertEquals(1, realm.where().count()) // FIXME: How to handle DynamicRealmObject :( @@ -386,18 +386,18 @@ class EmbeddedObjectsTest { } assertEquals(1, realm.where().count()) - assertEquals("parent1", realm.where().findFirst()!!.id) + assertEquals("parent1", realm.where().findFirst()!!._id) assertEquals(2, realm.where().count()) val nodeResults = realm.where().findAll() - assertTrue(nodeResults.any { it.id == "node1" }) - assertTrue(nodeResults.any { it.id == "node2" }) + assertTrue(nodeResults.any { it.treeNodeId == "node1" }) + assertTrue(nodeResults.any { it.treeNodeId == "node2" }) assertEquals(3, realm.where().count()) val leafResults = realm.where().findAll() - assertTrue(leafResults.any { it.id == "leaf1" }) - assertTrue(leafResults.any { it.id == "leaf2" }) - assertTrue(leafResults.any { it.id == "leaf3" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf1" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf2" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf3" }) } @Test @@ -497,18 +497,18 @@ class EmbeddedObjectsTest { } assertEquals(1, realm.where().count()) - assertEquals("parent1", realm.where().findFirst()!!.id) + assertEquals("parent1", realm.where().findFirst()!!._id) assertEquals(2, realm.where().count()) val nodeResults = realm.where().findAll() - assertTrue(nodeResults.any { it.id == "node1" }) - assertTrue(nodeResults.any { it.id == "node2" }) + assertTrue(nodeResults.any { it.treeNodeId == "node1" }) + assertTrue(nodeResults.any { it.treeNodeId == "node2" }) assertEquals(3, realm.where().count()) val leafResults = realm.where().findAll() - assertTrue(leafResults.any { it.id == "leaf1" }) - assertTrue(leafResults.any { it.id == "leaf2" }) - assertTrue(leafResults.any { it.id == "leaf3" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf1" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf2" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf3" }) } @Test @@ -555,8 +555,8 @@ class EmbeddedObjectsTest { } val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.id) - assertEquals("embeddedChildId", singleChild.singleChild!!.id) + assertEquals(childId, singleChild.circularChildId) + assertEquals("embeddedChildId", singleChild.singleChild!!.circularChildId) } @Test @@ -566,8 +566,8 @@ class EmbeddedObjectsTest { } val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.id) - assertEquals(embeddedChildId, singleChild.singleChild!!.id) + assertEquals(childId, singleChild.circularChildId) + assertEquals(embeddedChildId, singleChild.singleChild!!.circularChildId) } @Test @@ -576,10 +576,10 @@ class EmbeddedObjectsTest { realm.createObjectFromJson(EmbeddedSimpleListParent::class.java, json(simpleListParentData)) } val parent = realm.where(EmbeddedSimpleListParent::class.java).findFirst()!! - assertEquals(3, parent.children!!.count()) - assertEquals(childId1, parent.children[0]!!.id) - assertEquals(childId2, parent.children[1]!!.id) - assertEquals(childId3, parent.children[2]!!.id) + assertEquals(3, parent.children.count()) + assertEquals(childId1, parent.children[0]!!.childId) + assertEquals(childId2, parent.children[1]!!.childId) + assertEquals(childId3, parent.children[2]!!.childId) } @Test @@ -591,8 +591,8 @@ class EmbeddedObjectsTest { } val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.id) - assertEquals(embeddedChildId, singleChild.singleChild!!.id) + assertEquals(childId, singleChild.circularChildId) + assertEquals(embeddedChildId, singleChild.singleChild!!.circularChildId) } // Stream based import implementation is differentiated depending on whether the class has a @@ -608,7 +608,7 @@ class EmbeddedObjectsTest { assertEquals(1, all.count()) val parent = all.first()!! val child = parent.singleChild!! - assertEquals(childId, child.id) + assertEquals(childId, child.circularChildId) } @Test @@ -621,10 +621,10 @@ class EmbeddedObjectsTest { val all = realm.where(EmbeddedSimpleListParent::class.java).findAll() assertEquals(1, all.count()) val parent = all.first()!! - assertEquals(3, parent.children!!.count()) - assertEquals(childId1, parent.children[0]!!.id) - assertEquals(childId2, parent.children[1]!!.id) - assertEquals(childId3, parent.children[2]!!.id) + assertEquals(3, parent.children.count()) + assertEquals(childId1, parent.children[0]!!.childId) + assertEquals(childId2, parent.children[1]!!.childId) + assertEquals(childId3, parent.children[2]!!.childId) } // Stream based import implementation is differentiated depending on whether the class has a primary key @@ -638,11 +638,11 @@ class EmbeddedObjectsTest { val all = realm.where(EmbeddedSimpleListParentWithoutPrimaryKey::class.java).findAll() assertEquals(1, all.count()) val parent = all.first()!! - assertEquals(parentId, parent.id) - assertEquals(3, parent.children!!.count()) - assertEquals(childId1, parent.children[0]!!.id) - assertEquals(childId2, parent.children[1]!!.id) - assertEquals(childId3, parent.children[2]!!.id) + assertEquals(parentId, parent._id) + assertEquals(3, parent.children.count()) + assertEquals(childId1, parent.children[0]!!.childId) + assertEquals(childId2, parent.children[1]!!.childId) + assertEquals(childId3, parent.children[2]!!.childId) } @Test @@ -653,13 +653,13 @@ class EmbeddedObjectsTest { } val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.id) - assertEquals(embeddedChildId, singleChild.singleChild!!.id) + assertEquals(childId, singleChild.circularChildId) + assertEquals(embeddedChildId, singleChild.singleChild!!.circularChildId) // Update existing objects, but without overwriting any properties realm.executeTransaction { realm -> val circularParentData = mapOf( - "id" to parentId + "_id" to parentId ) realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, json(circularParentData)) } @@ -669,9 +669,9 @@ class EmbeddedObjectsTest { assertEquals(2, allChildren.count()) val updatedCircularParent = allParents.first()!! val updatedSingleChild = circularParent.singleChild!! - assertEquals(parentId, updatedCircularParent.id) - assertEquals(childId, updatedSingleChild.id) - assertEquals(embeddedChildId, updatedSingleChild.singleChild!!.id) + assertEquals(parentId, updatedCircularParent._id) + assertEquals(childId, updatedSingleChild.circularChildId) + assertEquals(embeddedChildId, updatedSingleChild.singleChild!!.circularChildId) } @Test @@ -682,15 +682,15 @@ class EmbeddedObjectsTest { } val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.id) - assertEquals(embeddedChildId, singleChild.singleChild!!.id) + assertEquals(childId, singleChild.circularChildId) + assertEquals(embeddedChildId, singleChild.singleChild!!.circularChildId) // Update existing objects, updating to new embedded object realm.executeTransaction { realm -> val circularParentData = mapOf( - "id" to parentId, + "_id" to parentId, "singleChild" to mapOf( - "id" to childId + "circularChildId" to childId ) ) realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, stream(circularParentData)) @@ -701,8 +701,8 @@ class EmbeddedObjectsTest { assertEquals(1, allChildren.count()) val updatedCircularParent = allParents.first()!! val updatedSingleChild = circularParent.singleChild!! - assertEquals(parentId, updatedCircularParent.id) - assertEquals(childId, updatedSingleChild.id) + assertEquals(parentId, updatedCircularParent._id) + assertEquals(childId, updatedSingleChild.circularChildId) // Sub child will have been deleted as embedded object does not have primary key and is // comletely replaced assertNull(updatedSingleChild.singleChild) @@ -850,11 +850,11 @@ class EmbeddedObjectsTest { DynamicRealm.getInstance(realm.configuration).use { realm -> realm.executeTransaction { val parent = realm.createObject("EmbeddedTreeParent", "parent1") - val middleNode = realm.createEmbeddedObject("EmbeddedTreeNode", parent, "middleNode"); - middleNode.setString("id", "node1") - val leaf1 = realm.createEmbeddedObject("EmbeddedTreeLeaf", middleNode, "leafNode"); - val leaf2 = realm.createEmbeddedObject("EmbeddedTreeLeaf", middleNode, "leafNodeList"); - val leaf3 = realm.createEmbeddedObject("EmbeddedTreeLeaf", middleNode, "leafNodeList"); + val middleNode = realm.createEmbeddedObject("EmbeddedTreeNode", parent, "middleNode") + middleNode.setString("treeNodeId", "node1") + val leaf1 = realm.createEmbeddedObject("EmbeddedTreeLeaf", middleNode, "leafNode") + val leaf2 = realm.createEmbeddedObject("EmbeddedTreeLeaf", middleNode, "leafNodeList") + val leaf3 = realm.createEmbeddedObject("EmbeddedTreeLeaf", middleNode, "leafNodeList") assertEquals(1, realm.where("EmbeddedTreeNode").count()) assertEquals(3, realm.where("EmbeddedTreeLeaf").count()) diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt index f706aeaab6..d1b0e82ed4 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt @@ -24,6 +24,6 @@ import java.util.* * objects are not allowed to have circular references. */ @RealmClass(embedded = true) -open class EmbeddedCircularChild(var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedCircularChild(var circularChildId: String = UUID.randomUUID().toString()) : RealmObject() { var singleChild: EmbeddedCircularChild? = null } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt index 8209ca9436..eaf76cb52d 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt @@ -21,6 +21,6 @@ import java.util.* // Parent pointing to an embedded object that has a circular schema, i.e. objects can point // to themselves. Note, this isn't actually allowed at runtime. Only at schema validation time. -open class EmbeddedCircularParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedCircularParent(@PrimaryKey var _id: String = UUID.randomUUID().toString()) : RealmObject() { var singleChild: EmbeddedCircularChild? = null } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt index 61deef328b..22612ebaa1 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt @@ -21,6 +21,6 @@ import java.util.* // Parent pointing to an embedded object that has a circular schema, i.e. objects can point // to themselves. Note, this isn't actually allowed at runtime. Only at schema validation time. -open class EmbeddedCircularParentWithoutPrimaryKey(var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedCircularParentWithoutPrimaryKey(var _id: String = UUID.randomUUID().toString()) : RealmObject() { var singleChild: EmbeddedCircularChild? = null } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt index 0b015f8cd2..f38e6ba36d 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleChild.kt @@ -25,7 +25,7 @@ import java.util.* * [EmbeddedSimpleParent] and [EmbeddedSimpleListParent]. */ @RealmClass(embedded = true) -open class EmbeddedSimpleChild(var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedSimpleChild(var childId: String = UUID.randomUUID().toString()) : RealmObject() { @LinkingObjects("child") val parent = EmbeddedSimpleParent() diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt index 507e7f1f22..e8e1bbdb02 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParent.kt @@ -22,6 +22,6 @@ import java.util.* // Top-level object describing a simple embedded objects structure consisting of only a // list of embedded objects. -open class EmbeddedSimpleListParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedSimpleListParent(@PrimaryKey var _id: String = UUID.randomUUID().toString()) : RealmObject() { var children: RealmList = RealmList() } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParentWithoutPrimaryKey.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParentWithoutPrimaryKey.kt index 821f30b800..c661d5aa58 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParentWithoutPrimaryKey.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleListParentWithoutPrimaryKey.kt @@ -22,6 +22,6 @@ import java.util.* // Top-level object describing a simple embedded objects structure consisting of only a // list of embedded objects. -open class EmbeddedSimpleListParentWithoutPrimaryKey(var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedSimpleListParentWithoutPrimaryKey(var _id: String = UUID.randomUUID().toString()) : RealmObject() { var children: RealmList = RealmList() } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt index 1f7f07c15d..3719e68e30 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedSimpleParent.kt @@ -20,6 +20,6 @@ import io.realm.annotations.PrimaryKey import java.util.* // Top-level object describing a simple embedded objects structure consisting of only an object reference. -open class EmbeddedSimpleParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedSimpleParent(@PrimaryKey var _id: String = UUID.randomUUID().toString()) : RealmObject() { var child: EmbeddedSimpleChild? = null } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt index 6efb4022b3..f420f45597 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeLeaf.kt @@ -27,7 +27,7 @@ import java.util.* // - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. // - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. @RealmClass(embedded = true) -open class EmbeddedTreeLeaf(var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedTreeLeaf(var treeLeafId: String = UUID.randomUUID().toString()) : RealmObject() { @LinkingObjects("leafNode") val parentRef: EmbeddedTreeNode? = null diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt index bd70f05c7e..db8d4a40d8 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt @@ -27,7 +27,7 @@ import java.util.* // - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. // - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. @RealmClass(embedded = true) -open class EmbeddedTreeNode(var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedTreeNode(var treeNodeId: String = UUID.randomUUID().toString()) : RealmObject() { var middleNode: EmbeddedTreeNode? = null var leafNode: EmbeddedTreeLeaf? = null var middleNodeList: RealmList = RealmList() diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt index 4f2b035dbf..d051801e8c 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeParent.kt @@ -23,9 +23,9 @@ import java.util.* // Top-level node in a object-graph that is three-shaped, i.e. no circular references. // The tree depth can be described as: // - 1 TreeParent -// - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. +// - 1 or more TreeNodes. I.e. a TreeNode can be the child of another TreeNode. // - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. -open class EmbeddedTreeParent(@PrimaryKey var id: String = UUID.randomUUID().toString()) : RealmObject() { +open class EmbeddedTreeParent(@PrimaryKey var _id: String = UUID.randomUUID().toString()) : RealmObject() { var middleNode: EmbeddedTreeNode? = null var middleNodeList: RealmList = RealmList() } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt index 3861aa01ce..8cea776150 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/DefaultSyncSchema.kt @@ -16,12 +16,23 @@ package io.realm.entities import io.realm.annotations.RealmModule +import io.realm.entities.embedded.* const val defaultPartitionValue = "default" /** * The set of classes initially supported by MongoDB Realm. */ -@RealmModule(classes = [SyncDog::class, SyncPerson::class, SyncAllTypes::class]) -class DefaultSyncSchema { -} +@RealmModule(classes = [ + SyncDog::class, + SyncPerson::class, + SyncAllTypes::class, + EmbeddedSimpleParent::class, + EmbeddedSimpleChild::class, + EmbeddedSimpleListParent::class + // FIXME: add these to schema once https://jira.mongodb.org/projects/HELP/queues/issue/HELP-17759 is fixed +// EmbeddedTreeParent::class, +// EmbeddedTreeNode::class, +// EmbeddedTreeLeaf::class +]) +class DefaultSyncSchema diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index de539ee7d2..7c81abeacf 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -17,22 +17,29 @@ package io.realm.mongodb.sync import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import io.realm.Realm -import io.realm.mongodb.SyncTestUtils.Companion.createTestUser -import io.realm.TestApp -import io.realm.TestHelper -import io.realm.TestSyncConfigurationFactory +import io.realm.* import io.realm.entities.* +import io.realm.entities.embedded.* +import io.realm.kotlin.createEmbeddedObject +import io.realm.kotlin.createObject import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.LogLevel import io.realm.log.RealmLog -import io.realm.mongodb.* +import io.realm.mongodb.App +import io.realm.mongodb.Credentials +import io.realm.mongodb.SyncTestUtils.Companion.createTestUser +import io.realm.mongodb.User +import io.realm.mongodb.close import org.junit.* -import org.junit.Assert.* import org.junit.runner.RunWith import java.io.File import java.util.* +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import kotlin.test.fail /** * Testing sync specific methods on [Realm]. @@ -232,6 +239,223 @@ class SyncedRealmTests { } } + @Test + fun embeddedObject_roundTrip() { + val user1: User = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + val primaryKeyValue = UUID.randomUUID().toString() + Realm.getInstance(config1).use { realm -> + assertTrue(realm.isEmpty) + + realm.executeTransaction { + realm.createObject(primaryKeyValue).let { parent -> + realm.createEmbeddedObject(parent, "child") + } + } + realm.syncSession.uploadAllLocalChanges() + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + val user2: User = createNewUser() + val config2: SyncConfiguration = createDefaultConfig(user2, partitionValue) + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges(5, TimeUnit.SECONDS).let { + if (!it) fail() + } + realm.refresh() + + val childResults = realm.where() + assertEquals(1, childResults.count()) + val parentResults = realm.where() + assertEquals(1, parentResults.count()) + val parent = parentResults.findFirst()!! + assertEquals(primaryKeyValue, parent._id) + assertEquals(parent._id, parent.child!!.parent._id) + } + } + + // FIXME: remove ignore when sync issue fixed + @Test + @Ignore("ignored until https://jira.mongodb.org/browse/REALMC-6541 is fixed") + fun embeddedObject_copyUnmanaged_roundTrip() { + val user1: User = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + val primaryKeyValue = UUID.randomUUID().toString() + + Realm.getInstance(config1).use { realm -> + assertTrue(realm.isEmpty) + + realm.executeTransaction { + val parent = EmbeddedSimpleParent(primaryKeyValue) + +// parent.child = EmbeddedSimpleChild() + val managedParent = it.copyToRealmOrUpdate(parent) + // FIXME: instantiating the child in managedParent yields this from sync: + // "MongoDB error: Updating the path 'child.childID' would create a conflict at 'child'" + managedParent.child = EmbeddedSimpleChild() // Will copy the object to Realm + } + realm.syncSession.uploadAllLocalChanges() + + assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) + } + + val user2: User = createNewUser() + val config2: SyncConfiguration = createDefaultConfig(user2, partitionValue) + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges(5, TimeUnit.SECONDS).let { + if (!it) fail() + } + realm.refresh() + + val childResults = realm.where() + assertEquals(1, childResults.count()) + val parentResults = realm.where() + assertEquals(1, parentResults.count()) + val parent = parentResults.findFirst()!! + assertEquals(primaryKeyValue, parent._id) + assertEquals(parent._id, parent.child!!.parent._id) + } + } + + @Test + fun embeddedObject_realmList_roundTrip() { + val user1: User = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + val primaryKeyValue = UUID.randomUUID().toString() + Realm.getInstance(config1).use { realm -> + realm.executeTransaction { + realm.createObject(EmbeddedSimpleListParent::class.java, primaryKeyValue).let { parent -> + realm.createEmbeddedObject(EmbeddedSimpleChild::class.java, parent, "children") + realm.createEmbeddedObject(EmbeddedSimpleChild::class.java, parent, "children") + } + } + realm.syncSession.uploadAllLocalChanges() + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + } + + val user2: User = createNewUser() + val config2: SyncConfiguration = createDefaultConfig(user2, partitionValue) + Realm.getInstance(config2).use { realm -> + assertEquals(0, realm.where().count()) + assertEquals(0, realm.where().count()) + + realm.syncSession.downloadAllServerChanges(5, TimeUnit.SECONDS).let { + if (!it) fail() + } + realm.refresh() + + val childResults = realm.where() + assertEquals(2, childResults.count()) + val parentResults = realm.where() + assertEquals(1, parentResults.count()) + val parentFromResults = parentResults.findFirst()!! + assertEquals(primaryKeyValue, parentFromResults._id) + + parentFromResults.children.also { childrenInParent -> + val childrenFromResults = childResults.findAll() + childrenInParent.forEach { childInParent -> + assertTrue(childrenFromResults.contains(childInParent)) + } + } + } + } + + @Test + fun embeddedObject_realmList_copyUnmanaged_roundTrip() { + val user1: User = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + val primaryKeyValue = UUID.randomUUID().toString() + Realm.getInstance(config1).use { realm -> + realm.executeTransaction { + val parent = EmbeddedSimpleListParent(primaryKeyValue) + parent.children = RealmList(EmbeddedSimpleChild("child1"), EmbeddedSimpleChild("child2")) + realm.insert(parent) + } + realm.syncSession.uploadAllLocalChanges() + + assertEquals(1, realm.where().count()) + assertEquals(2, realm.where().count()) + } + + val user2: User = createNewUser() + val config2: SyncConfiguration = createDefaultConfig(user2, partitionValue) + Realm.getInstance(config2).use { realm -> + assertEquals(0, realm.where().count()) + assertEquals(0, realm.where().count()) + + realm.syncSession.downloadAllServerChanges(1, TimeUnit.SECONDS).let { + if (!it) fail() + } + realm.refresh() + + val childResults = realm.where() + assertEquals(2, childResults.count()) + val parentResults = realm.where() + assertEquals(1, parentResults.count()) + val parentFromResults = parentResults.findFirst()!! + assertEquals(primaryKeyValue, parentFromResults._id) + assertEquals("child1", childResults.findAll()[0]!!.childId) + assertEquals("child2", childResults.findAll()[1]!!.childId) + } + } + + // FIXME: remember to add tree structure classes to DefaultSyncSchema.kt + @Test + @Ignore("Enable when https://jira.mongodb.org/projects/HELP/queues/issue/HELP-17759 is fixed") + fun copyToRealm_treeSchema() { + val user1: User = createNewUser() + val config1: SyncConfiguration = createDefaultConfig(user1, partitionValue) + val primaryKeyValue = UUID.randomUUID().toString() + + Realm.getInstance(config1).use { realm -> + realm.executeTransaction { + val parent = EmbeddedTreeParent("parent1") + + val node1 = EmbeddedTreeNode("node1") + node1.leafNode = EmbeddedTreeLeaf("leaf1") + parent.middleNode = node1 + val node2 = EmbeddedTreeNode("node2") + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf2")) + node2.leafNodeList.add(EmbeddedTreeLeaf("leaf3")) + parent.middleNodeList.add(node2) + + it.copyToRealm(parent) + } + realm.syncSession.uploadAllLocalChanges() + } + + val user2: User = createNewUser() + val config2: SyncConfiguration = createDefaultConfig(user2, partitionValue) + Realm.getInstance(config2).use { realm -> + assertEquals(0, realm.where().count()) + assertEquals(0, realm.where().count()) + + realm.syncSession.downloadAllServerChanges(1, TimeUnit.SECONDS).let { + if (!it) fail() + } + realm.refresh() + + Assert.assertEquals(1, realm.where().count()) + Assert.assertEquals("parent1", realm.where().findFirst()!!._id) + + Assert.assertEquals(2, realm.where().count()) + val nodeResults = realm.where().findAll() + Assert.assertTrue(nodeResults.any { it.treeNodeId == "node1" }) + Assert.assertTrue(nodeResults.any { it.treeNodeId == "node2" }) + + Assert.assertEquals(3, realm.where().count()) + val leafResults = realm.where().findAll() + Assert.assertTrue(leafResults.any { it.treeLeafId == "leaf1" }) + Assert.assertTrue(leafResults.any { it.treeLeafId == "leaf2" }) + Assert.assertTrue(leafResults.any { it.treeLeafId == "leaf3" }) + } + } + @Test // FIXME Missing test, maybe fitting better in SyncSessionTest.kt...when migrated @Ignore("Not implemented yet") diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 270d9ba358..9229240537 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -174,7 +174,7 @@ public boolean isAutoRefresh() { /** * Refreshes the Realm instance and all the RealmResults and RealmObjects instances coming from it. - * It also calls any listeners associated with the Realm if neeeded. + * It also calls any listeners associated with the Realm if needed. *

              * WARNING: Calling this on a thread with async queries will turn those queries into synchronous queries. * In most cases it is better to use {@link RealmChangeListener}s to be notified about changes to the From 96625f23d521f425cb31e814620a07232fa7f687 Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 11 Aug 2020 21:01:12 +0200 Subject: [PATCH 1619/2110] Finish RealmUser, Javadoc and tests (#7021) --- .../kotlin/io/realm/UserMetadataTests.kt | 298 ++++++++++++++++++ .../kotlin/io/realm/UserTests.kt | 41 +-- .../java/io/realm/mongodb/User.java | 36 ++- 3 files changed, 336 insertions(+), 39 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt new file mode 100644 index 0000000000..abcbab2538 --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt @@ -0,0 +1,298 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.internal.objectstore.OsJavaNetworkTransport +import io.realm.mongodb.* +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertNull + +@RunWith(AndroidJUnit4::class) +class UserMetadataTests { + companion object { + const val ACCESS_TOKEN = """eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODM5NjcyMDgsImlhdCI6MTU4Mzk2NTQwOCwiaXNzIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWEzIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWU2OTYzZGVhZmVhNjMyNTQ1ODFjMDI1Iiwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoiYWNjZXNzIn0.J4mp8LnlsxTQRV_7W2Er4qY0tptR76PJGG1k6HSMmUYqgfpJC2Fnbcf1VCoebzoNolH2-sr8AHDVBBCyjxRjqoY9OudFHmWZKmhDV1ysxPP4XmID0nUuN45qJSO8QEAqoOmP1crXjrUZWedFw8aaCZE-bxYfvcDHyjBcbNKZqzawwUw2PyTOlrNjgs01k2J4o5a5XzYkEsJuzr4_8UqKW6zXvYj24UtqnqoYatW5EzpX63m2qig8AcBwPK4ZHb5wEEUdf4QZxkRY5QmTgRHP8SSqVUB_mkHgKaizC_tSB3E0BekaDfLyWVC1taAstXJNfzgFtLI86AzuXS2dCiCfqQ""" + const val REFRESH_TOKEN = """eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODkxNDk0MDgsImlhdCI6MTU4Mzk2NTQwOCwic3RpdGNoX2RhdGEiOm51bGwsInN0aXRjaF9kZXZJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInN0aXRjaF9kb21haW5JZCI6IjVlNjk2M2RlYWZlYTYzMjU0NTgxYzAyNSIsInN0aXRjaF9pZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMyIsInN0aXRjaF9pZGVudCI6eyJpZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMC1oaWF2b3ZkbmJxbGNsYXBwYnl1cmJpaW8iLCJwcm92aWRlcl90eXBlIjoiYW5vbi11c2VyIiwicHJvdmlkZXJfaWQiOiI1ZTY5NjNlMGFmZWE2MzI1NDU4MWMwNGEifSwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoicmVmcmVzaCJ9.FhLdpmL48Mw0SyUKWuaplz3wfeS8TCO8S7I9pIJenQww9nPqQ7lIvykQxjCCtinGvsZIJKt_7R31xYCq4Jp53Nw81By79IwkXtO7VXHPsXXZG5_2xV-s0u44e85sYD5su_H-xnx03sU2piJbWJLSB8dKu3rMD4mO-S0HNXCCAty-JkYKSaM2-d_nS8MNb6k7Vfm7y69iz_uwHc-bb_1rPg7r827K6DEeEMF41Hy3Nx1kCdAUOM9-6nYv3pZSU1PFrGYi2uyTXPJ7R7HigY5IGHWd0hwONb_NUr4An2omqfvlkLEd77ut4V9m6mExFkoKzRz7shzn-IGkh3e4h7ECGA""" + const val USER_ID = "5e6964e0afea63254581c1a1" + const val DEVICE_ID = "000000000000000000000000" + const val NAME = "NAME" + const val EMAIL = "unique_user@domain.com" + const val PICTURE_URL = "PICTURE_URL" + const val FIRST_NAME = "FIRST_NAME" + const val LAST_NAME = "LAST_NAME" + const val GENDER = "GENDER" + const val BIRTHDAY = "BIRTHDAY" + const val MIN_AGE = 1L + const val MAX_AGE = 99L + } + + private lateinit var app: App + lateinit var profileBody : String + + private fun setDefaultProfile(){ + profileBody = """ + { + "name": "$NAME", + "email": "$EMAIL", + "picture_url": "$PICTURE_URL", + "first_name": "$FIRST_NAME", + "last_name": "$LAST_NAME", + "gender": "$GENDER", + "birthday": "$BIRTHDAY", + "min_age": "$MIN_AGE", + "max_age": "$MAX_AGE" + } + """.trimIndent() + } + + private fun setNullProfile(){ + profileBody = """ + { + + } + """.trimIndent() + } + + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + + setDefaultProfile(); + + app = TestApp(object : OsJavaNetworkTransport() { + override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { + var result = "" + if (url.endsWith("/providers/${Credentials.IdentityProvider.EMAIL_PASSWORD.id}/login")) { + result = """ + { + "access_token": "$ACCESS_TOKEN", + "refresh_token": "$REFRESH_TOKEN", + "user_id": "$USER_ID", + "device_id": "$DEVICE_ID" + } + """.trimIndent() + } else if (url.endsWith("/auth/profile")) { + result = """ + { + "user_id": "5e6964e0afea63254581c1a1", + "domain_id": "000000000000000000000000", + "identities": [ + { + "id": "5e68f51ade5ba998bb17500d", + "provider_type": "local-userpass", + "provider_id": "000000000000000000000003", + "provider_data": { + "email": "unique_user@domain.com" + } + } + ], + "data": $profileBody, + "type": "normal", + "roles": [ + { + "role_name": "GROUP_OWNER", + "group_id": "5e68f51e087b1b33a53f56d5" + } + ] + } + """.trimIndent() + } else if (url.endsWith("/location")) { + return Response.httpResponse(200, mapOf(), """ + { "deployment_model" : "GLOBAL", + "location": "US-VA", + "hostname": "http://localhost:9090", + "ws_hostname": "ws://localhost:9090" + } + """.trimIndent()) + } else if (url.endsWith("/providers/${Credentials.IdentityProvider.EMAIL_PASSWORD.id}/register")) { + result = "" + } else { + fail("Unexpected request url: $url") + } + return Response.httpResponse(200, mapOf(Pair("Content-Type", "application/json")), result) + } + }) + } + + @After + fun tearDown() { + if (this::app.isInitialized) { + app.close() + } + } + + @Test + fun getUserId() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(USER_ID, user.id) + } + + @Test + fun getDeviceId() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(DEVICE_ID, user.deviceId) + } + + @Test + fun accessToken() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(ACCESS_TOKEN, user.accessToken) + } + + @Test + fun refreshToken() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(REFRESH_TOKEN, user.refreshToken) + } + + @Test + fun getName() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(NAME, user.name) + } + + @Test + fun getEmail() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(EMAIL, user.email) + } + + @Test + fun getFirstName() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(FIRST_NAME, user.firstName) + } + + @Test + fun getLastName() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(LAST_NAME, user.lastName) + } + + @Test + fun getBirthday() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(BIRTHDAY, user.birthday) + } + + @Test + fun getGender() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(GENDER, user.gender) + } + + @Test + fun getMinAge() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(MIN_AGE, user.minAge) + } + + @Test + fun getMaxAge() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(MAX_AGE, user.maxAge) + } + + @Test + fun getPictureUrl() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(PICTURE_URL, user.pictureUrl) + } + + @Test + fun getNameNullable() { + setNullProfile() + + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNull(user.name) + } + + @Test + fun getEmailNullable() { + setNullProfile() + + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNull(user.email) + } + + @Test + fun getFirstNameNullable() { + setNullProfile() + + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNull(user.firstName) + } + + @Test + fun getLastNameNullable() { + setNullProfile() + + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNull(user.lastName) + } + + @Test + fun getBirthdayNullable() { + setNullProfile() + + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNull(user.birthday) + } + + @Test + fun getGenderNullable() { + setNullProfile() + + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNull(user.gender) + } + + @Test + fun getMinAgeNullable() { + setNullProfile() + + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNull(user.minAge) + } + + @Test + fun getMaxAgeNullable() { + setNullProfile() + + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNull(user.maxAge) + } + + @Test + fun getPictureUrlNullable() { + setNullProfile() + + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNull(user.pictureUrl) + } + + @Test + fun getProviderType() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, user.providerType) + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt index 29bcd86492..1ee96b21d3 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt @@ -204,7 +204,7 @@ class UserTests { assertEquals(1, anonUser.identities.size) // Linking with another user's API key is not allowed and must raise an AppException - val exception = assertFailsWith{ + val exception = assertFailsWith { anonUser.linkCredentials(Credentials.apiKey(apiKey.value)) } @@ -221,7 +221,7 @@ class UserTests { assertEquals(1, anonUser.identities.size) // Linking a server API key is not allowed - val exception = assertFailsWith{ + val exception = assertFailsWith { anonUser.linkCredentials(Credentials.serverApiKey(serverKey)) } } @@ -369,28 +369,6 @@ class UserTests { } } - @Test - fun getDeviceId() { - // TODO No reason to integration test this. Use a stubbed response instead. - val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertTrue(user.deviceId.isNotEmpty() && user.deviceId.length == 24) // Server returns a UUID - } - - // FIXME Test for all meta data - @Ignore("Not implemented yet") - fun user_metaData() { - } - - // FIXME - @Ignore("Not implemented yet") - fun accessToken() { - } - - // FIXME - @Ignore("Not implemented yet") - fun refreshToken() { - } - @Test fun revokedRefreshTokenIsNotSameAfterLogin() = looperThread.runBlocking { val password = "password" @@ -412,9 +390,20 @@ class UserTests { user.logOut() } - // FIXME - @Ignore("Not implemented yet") + @Test fun isLoggedIn() { + var anonUser = app.login(Credentials.anonymous()) + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + + assertTrue(anonUser.isLoggedIn) + assertTrue(user.isLoggedIn) + + anonUser.logOut() + assertFalse(anonUser.isLoggedIn) + assertTrue(user.isLoggedIn) + + user.logOut() + assertFalse(user.isLoggedIn) } @Test diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index 0b6aa0daf5..c389401381 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; @@ -133,6 +132,7 @@ public String getId() { * * @return the name of the user. */ + @Nullable public String getName() { return osUser.nativeGetName(); } @@ -224,7 +224,6 @@ public Long getMaxAge() { * Returns a new list of the user's identities. * * @return the list of identities. - * * @see UserIdentity */ public List getIdentities() { @@ -237,6 +236,15 @@ public List getIdentities() { return identities; } + /** + * Returns the provider type used to log the user + * + * @return the provider type of the user + */ + public Credentials.IdentityProvider getProviderType() { + return Credentials.IdentityProvider.fromId(osUser.getProviderType()); + } + /** * Returns the current access token for the user. * @@ -318,7 +326,6 @@ public Document refreshCustomData() { * * @param callback The callback that will receive the result or any errors from the request. * @return The task representing the ongoing operation. - * * @throws IllegalStateException if not called on a looper thread. */ public RealmAsyncTask refreshCustomData(App.Callback callback) { @@ -336,7 +343,6 @@ public Document run() throws AppException { * Returns true if the user is currently logged in. * Returns whether or not this user is still logged into the MongoDB Realm App. * - * @return {@code true} if the user is logged in. {@code false} otherwise. * @return {@code true} if still logged in, {@code false} if not. */ public boolean isLoggedIn() { @@ -362,8 +368,9 @@ public boolean isLoggedIn() { * must not have been used by another user. * * @param credentials the credentials to link with the current user. - * @throws IllegalStateException if no user is currently logged in. * @return the {@link User} the credentials were linked to. + * + * @throws IllegalStateException if no user is currently logged in. */ public User linkCredentials(Credentials credentials) { Util.checkNull(credentials, "credentials"); @@ -399,8 +406,9 @@ protected User mapSuccess(Object result) { * must not have been used by another user. * * @param credentials the credentials to link with the current user. - * @param callback callback when user identities has been linked or it failed. The callback will - * always happen on the same thread as this method is called on. + * @param callback callback when user identities has been linked or it failed. The callback will + * always happen on the same thread as this method is called on. + * * @throws IllegalStateException if called from a non-looper thread. */ public RealmAsyncTask linkCredentialsAsync(Credentials credentials, App.Callback callback) { @@ -420,7 +428,7 @@ public User run() throws AppException { * * @return user that was removed. * @throws AppException if called from the UI thread or if the user was logged in, but - * could not be logged out. + * could not be logged out. */ public User remove() throws AppException { boolean loggedIn = isLoggedIn(); @@ -445,7 +453,7 @@ protected User mapSuccess(Object result) { * affect the user state on the server. * * @param callback callback when removing the user has completed or failed. The callback will always - * happen on the same thread as this method is called on. + * happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ public RealmAsyncTask removeAsync(App.Callback callback) { @@ -473,7 +481,7 @@ public User run() throws AppException { * {@link #remove()}. * * @throws AppException if an error occurred while trying to log the user out of the Realm - * App. + * App. */ public void logOut() throws AppException { boolean loggedIn = isLoggedIn(); @@ -500,7 +508,7 @@ public void logOut() throws AppException { * {@link #remove()}. * * @param callback callback when logging out has completed or failed. The callback will always - * happen on the same thread as this method is called on. + * happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ public RealmAsyncTask logOutAsync(App.Callback callback) { @@ -559,7 +567,7 @@ public Functions getFunctions(CodecRegistry codecRegistry) { /** * Returns the {@link Push} instance for managing push notification registrations. * - * @param serviceName the service name used to connect to the server. + * @param serviceName the service name used to connect to the server. */ public synchronized Push getPush(String serviceName) { if (push == null) { @@ -572,7 +580,7 @@ public synchronized Push getPush(String serviceName) { /** * Returns a {@link MongoClient} instance for accessing documents in the database. * - * @param serviceName the service name used to connect to the server. + * @param serviceName the service name used to connect to the server. */ public synchronized MongoClient getMongoClient(String serviceName) { Util.checkEmpty(serviceName, "serviceName"); @@ -609,6 +617,8 @@ private void checkLoggedIn() { } private static native void nativeRemoveUser(long nativeAppPtr, long nativeUserPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeLinkUser(long nativeAppPtr, long nativeUserPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeLogOut(long appNativePtr, long userNativePtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } From 039da102fa0c7a130ac408a8d7d9e471a177366e Mon Sep 17 00:00:00 2001 From: clementetb Date: Wed, 12 Aug 2020 18:00:22 +0200 Subject: [PATCH 1620/2110] Finish RealmAppConfiguration Javadoc + Tests (#7023) --- .../kotlin/io/realm/AppConfigurationTests.kt | 87 ++++++++++--------- .../io/realm/mongodb/AppConfiguration.java | 22 ++++- 2 files changed, 66 insertions(+), 43 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index 4c7a6109ea..578f4d518c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -20,17 +20,18 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.internal.network.LoggingInterceptor.LOGIN_FEATURE import io.realm.mongodb.AppConfiguration import io.realm.mongodb.log.obfuscator.HttpLogObfuscator +import io.realm.mongodb.sync.SyncSession import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries import org.junit.Assert.* import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import java.io.File import java.net.URL +import java.util.concurrent.TimeUnit import kotlin.test.assertFailsWith import kotlin.test.assertNull @@ -61,10 +62,6 @@ class AppConfigurationTests { .authorizationHeaderName("CustomAuth") .build() assertEquals("CustomAuth", config2.authorizationHeaderName) - - // FIXME Add network check - - // FIXME Add sync session check } @Test @@ -73,7 +70,6 @@ class AppConfigurationTests { assertFailsWith { builder.addCustomRequestHeader("", "val") } assertFailsWith { builder.addCustomRequestHeader(TestHelper.getNull(), "val") } assertFailsWith { builder.addCustomRequestHeader("header", TestHelper.getNull()) } - // FIXME: Add tests for illegally formatted headers. Figure out what legal headers look like. } @Test @@ -86,10 +82,6 @@ class AppConfigurationTests { assertEquals(2, headers.size.toLong()) assertTrue(headers.any { it.key == "header1" && it.value == "val1" }) assertTrue(headers.any { it.key == "header2" && it.value == "val2" }) - - // FIXME Add network check - - // FIXME Add sync session check } @Test @@ -124,8 +116,6 @@ class AppConfigurationTests { val config = AppConfiguration.Builder("app-id").build() val expectedDefaultRoot = File(InstrumentationRegistry.getInstrumentation().targetContext.filesDir, "mongodb-realm") assertEquals(expectedDefaultRoot, config.syncRootDirectory) - - // FIXME Add check when opening Realm } @Test @@ -136,8 +126,6 @@ class AppConfigurationTests { .syncRootDirectory(expectedRoot) .build() assertEquals(expectedRoot, config.syncRootDirectory) - - // FIXME Add check when opening Realm } @Test @@ -162,27 +150,35 @@ class AppConfigurationTests { } @Test - @Ignore("FIXME") fun appName() { - TODO("FIXME: When support has been added in ObjectStore") + val config = AppConfiguration.Builder("app-id") + .appName("app-name") + .build() + assertEquals("app-name", config.appName) } @Test - @Ignore("FIXME") fun appName_invalidValuesThrows() { - TODO() + val builder = AppConfiguration.Builder("app-id") + + assertFailsWith { builder.appName(TestHelper.getNull()) } + assertFailsWith { builder.appName("") } } @Test - @Ignore("FIXME") fun appVersion() { - TODO("FIXME: When support has been added in ObjectStore") + val config = AppConfiguration.Builder("app-id") + .appVersion("app-version") + .build() + assertEquals("app-version", config.appVersion) } @Test - @Ignore("FIXME") fun appVersion_invalidValuesThrows() { - TODO() + val builder = AppConfiguration.Builder("app-id") + + assertFailsWith { builder.appVersion(TestHelper.getNull()) } + assertFailsWith { builder.appVersion("") } } @Test @@ -208,15 +204,22 @@ class AppConfigurationTests { } @Test - @Ignore("FIXME") fun defaultSyncErrorHandler() { - TODO() + val errorHandler = SyncSession.ErrorHandler { _, _ -> } + + val config = AppConfiguration.Builder("app-id") + .defaultSyncErrorHandler(errorHandler) + .build() + assertEquals(config.defaultErrorHandler, errorHandler) } @Test - @Ignore("FIXME") fun defaultSyncErrorHandler_invalidValuesThrows() { - TODO() + assertFailsWith { + AppConfiguration.Builder("app-id") + .defaultSyncErrorHandler(TestHelper.getNull()) + } + } @Test @@ -239,20 +242,24 @@ class AppConfigurationTests { } assertFailsWith { - builder.encryptionKey(byteArrayOf(0,0,0,0)) + builder.encryptionKey(byteArrayOf(0, 0, 0, 0)) } } @Test - @Ignore("FIXME") fun requestTimeout() { - TODO() + val config = AppConfiguration.Builder("app-id") + .requestTimeout(1, TimeUnit.MILLISECONDS) + .build() + assertEquals(1000L, config.requestTimeoutMs) } @Test - @Ignore("FIXME") fun requestTimeout_invalidValuesThrows() { - TODO() + val builder = AppConfiguration.Builder("app-id") + + assertFailsWith { builder.requestTimeout(-1, TimeUnit.MILLISECONDS) } + assertFailsWith { builder.requestTimeout(1, TestHelper.getNull()) } } @Test @@ -280,21 +287,17 @@ class AppConfigurationTests { @Test fun httpLogObfuscator_null() { - AppConfiguration.Builder("app-id") - .httpLogObfuscator(null) + val config = AppConfiguration.Builder("app-id") + .httpLogObfuscator(TestHelper.getNull()) .build() - .let { - assertNull(it.httpLogObfuscator) - } + assertNull(config.httpLogObfuscator) } @Test fun defaultLoginInfoObfuscator() { - AppConfiguration.Builder("app-id") - .build() - .let { - val defaultHttpLogObfuscator = HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators) - assertEquals(defaultHttpLogObfuscator, it.httpLogObfuscator) - } + val config = AppConfiguration.Builder("app-id").build() + + val defaultHttpLogObfuscator = HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators) + assertEquals(defaultHttpLogObfuscator, config.httpLogObfuscator) } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index 44cc177ed6..760cd51199 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -174,6 +174,8 @@ private AppConfiguration(String appId, /** * Returns the unique app id that identities the Realm application. + * + * @return the app unique identifier. */ public String getAppId() { return appId; @@ -182,6 +184,8 @@ public String getAppId() { /** * Returns the name used to describe the Realm application. This is only used as debug * information. + * + * @return the app name. */ public String getAppName() { return appName; @@ -189,6 +193,8 @@ public String getAppName() { /** * Returns the version of this Realm application. This is only used as debug information. + * + * @return the app version. */ public String getAppVersion() { return appVersion; @@ -196,6 +202,8 @@ public String getAppVersion() { /** * Returns the base url for this Realm application. + * + * @return the app base url. */ public URL getBaseUrl() { return baseUrl; @@ -204,6 +212,8 @@ public URL getBaseUrl() { /** * Returns the encryption key, if any, that is used to encrypt Realm users meta data on this * device. If no key is returned, the data is not encrypted. + * + * @return the encryption key if exists, or {@code null} otherwise. */ @Nullable public byte[] getEncryptionKey() { @@ -213,6 +223,8 @@ public byte[] getEncryptionKey() { /** * Returns the default timeout for network requests against the Realm application in * milliseconds. + * + * @return the default timeout for network requests in milliseconds. */ public long getRequestTimeoutMs() { return requestTimeoutMs; @@ -221,6 +233,8 @@ public long getRequestTimeoutMs() { /** * Returns the name of the header used to carry authentication data when making network * requests towards MongoDB Realm. + * + * @return the authentication header name. */ public String getAuthorizationHeaderName() { return authorizationHeaderName; @@ -229,6 +243,8 @@ public String getAuthorizationHeaderName() { /** * Returns any custom configured headers that will be sent alongside other headers when * making network requests towards MongoDB Realm. + * + * @return a {@code Map} of custom configured headers. */ public Map getCustomRequestHeaders() { return customHeaders; @@ -237,6 +253,8 @@ public Map getCustomRequestHeaders() { /** * Returns the default error handler used by synced Realms if there are problems with their * {@link SyncSession}. + * + * @return the app default error handler. */ public SyncSession.ErrorHandler getDefaultErrorHandler() { return defaultErrorHandler; @@ -245,6 +263,8 @@ public SyncSession.ErrorHandler getDefaultErrorHandler() { /** * Returns the root folder containing all files and Realms used when synchronizing data * between the device and MongoDB Realm. + * + * @return the sync root directory. */ public File getSyncRootDirectory() { return syncRootDir; @@ -411,7 +431,7 @@ public Builder appVersion(String appVersion) { */ public Builder requestTimeout(long time, TimeUnit unit) { if (time < 1) { - throw new IllegalStateException("A timeout above 0 is required: " + time); + throw new IllegalArgumentException("A timeout above 0 is required: " + time); } Util.checkNull(unit, "unit"); this.requestTimeoutMs = TimeUnit.MICROSECONDS.convert(time, unit); From 022fa93ae6ae127e576f0aef7493ff0187ee9d11 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 13 Aug 2020 14:51:35 +0200 Subject: [PATCH 1621/2110] Fix support for multiple Realms by same user (#6973) --- CHANGELOG.md | 10 ++- .../java/io/realm/RealmMigrationTests.java | 2 +- .../kotlin/io/realm/AppTests.kt | 2 +- .../kotlin/io/realm/UserTests.kt | 6 ++ .../network/LoggingInterceptorTest.kt | 4 +- .../mongodb/sync/SyncConfigurationTests.kt | 43 +++++++++- ..._realm_internal_objectstore_OsSyncUser.cpp | 11 +++ .../main/cpp/io_realm_mongodb_sync_Sync.cpp | 32 +++++++ realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/RealmConfiguration.java | 27 ++---- .../log/obfuscator/TokenObfuscator.java | 2 +- .../internal/objectstore/OsSyncUser.java | 5 ++ .../java/io/realm/mongodb/User.java | 13 ++- .../java/io/realm/mongodb/sync/Sync.java | 36 +++++--- .../realm/mongodb/sync/SyncConfiguration.java | 85 +++++++------------ .../kotlin/io/realm/ObfuscatorHelper.kt | 4 +- 16 files changed, 181 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 007bb18dff..198fe3244e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,18 +5,20 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes -* Removed GMS Task framework and added RealmResultTask to provide with a mechanism to operate with asynchronous operations. MongoCollection has been updated to reflect this change. +* [RealmApp] Realm files have changed location on disk, so Realms should upload all their data to the server before upgrading. +* [RealmApp] Removed GMS Task framework and added RealmResultTask to provide with a mechanism to operate with asynchronous operations. MongoCollection has been updated to reflect this change. ### Enhancements -* Credentials information (e.g. username, password) displayed in Logcat is now obfuscated by default, even if [LogLevel] is set to DEBUG, TRACE or ALL. +* [RealmApp] Credentials information (e.g. username, password) displayed in Logcat is now obfuscated by default, even if [LogLevel] is set to DEBUG, TRACE or ALL. * RealmLists can now be marked final. (Issue [#6892](https://github.com/realm/realm-java/issues/6892)) * It is now possible to create embedded objects using [DynamicRealm]s. (Issue [#6982](https://github.com/realm/realm-java/pull/6982)) * Added extra validation and more meaningful error messages when creating embedded objects pointing to the wrong parent property. (See issue above) ### Fixed +* [RealmApp] The same user opening different Realms with different partion key values would crash with an IllegalArgumentException. (Issue [#6882](https://github.com/realm/realm-java/issues/6882), since 10.0.0-BETA.1) * [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) -* Added support for Json-import of objects containing embedded objects. (Issue [#6896](https://github.com/realm/realm-java/issues/6896)) * [RealmApp] Leaking objects when registering session listeners. (Issue [#6916](https://github.com/realm/realm-java/issues/6916)) +* Added support for Json-import of objects containing embedded objects. (Issue [#6896](https://github.com/realm/realm-java/issues/6896)) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). @@ -24,7 +26,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal -* Upgraded to Object Store commit: 709e69580f480051da8be8b444df400c64c652f8. +* Upgraded to Object Store commit: 5b5fb8a90192cb4ee6799e7465745cd2067f939b. ## 10.0.0-BETA.5 (2020-06-19) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 4e83ab71ec..8ae2f20198 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -898,7 +898,7 @@ public void migrationException_getPath() throws IOException { Realm.getInstance(configFactory.createConfiguration()); fail(); } catch (RealmMigrationNeededException expected) { - assertEquals(expected.getPath(), realm.getCanonicalPath()); + assertEquals(expected.getPath(), realm.getAbsolutePath()); } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index 2115e5c17e..583c032cc8 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -281,7 +281,7 @@ class AppTests { it.encryptionKey(TestHelper.getRandomKey()) }) - val metadataDir = File(context.filesDir, "realm-object-server/io.realm.object-server-utility/metadata/") + val metadataDir = File(context.filesDir, "mongodb-realm/server-utility/metadata/") val config = RealmConfiguration.Builder() .name("sync_metadata.realm") .directory(metadataDir) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt index 1ee96b21d3..c4ab7aa566 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt @@ -391,10 +391,16 @@ class UserTests { } @Test + fun getLocalId() { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertNotNull(user.localId) + } + fun isLoggedIn() { var anonUser = app.login(Credentials.anonymous()) val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + assertTrue(anonUser.isLoggedIn) assertTrue(user.isLoggedIn) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt index 2d50cd551f..19c50497b5 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt @@ -160,7 +160,7 @@ class LoggingInterceptorTest { } catch (error: AppException) { // It will fail as long as oauth2 tokens aren't supported } finally { - assertMessageExists(""""access_token":"$token"""") + assertMessageExists("""accessToken":"$token"""") } } @@ -177,7 +177,7 @@ class LoggingInterceptorTest { } catch (error: AppException) { Assert.assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) } finally { - assertMessageExists(""""access_token":"***"""") + assertMessageExists(""""accessToken":"***"""") } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index 6ca83497a0..6ce774b958 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -133,6 +133,26 @@ class SyncConfigurationTests { assertTrue(config.isSyncConfiguration) } + @Test + fun name() { + val user: User = createTestUser(app) + val filename = "my-file-name.realm" + val config: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION) + .name(filename) + .build() + val suffix = "/mongodb-realm/${user.app.configuration.appId}/${user.localId}/$filename" + assertTrue(config.path.endsWith(suffix)) + } + + @Test + fun name_illegalValuesThrows() { + val user: User = createTestUser(app) + val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) + + assertFailsWith { builder.name(TestHelper.getNull()) } + assertFailsWith { builder.name(".realm") } + } + @Test fun encryption() { val user: User = createTestUser(app) @@ -168,7 +188,6 @@ class SyncConfigurationTests { } }) .build() - config assertNotNull(config.initialDataTransaction) // open the first time - initialData must be triggered @@ -282,6 +301,28 @@ class SyncConfigurationTests { } } + // If the same user create two configurations with different partition values they must + // resolve to different paths on disk. + @Test + fun differentPartitionValuesAreDifferentRealms() { + val user: User = createTestUser(app) + val config1 = SyncConfiguration.defaultConfig(user, "realm1") + val config2 = SyncConfiguration.defaultConfig(user, "realm2") + assertNotEquals(config1.path, config2.path) + + assertTrue(config1.path.endsWith("${app.configuration.appId}/${user.localId}/s_realm1.realm")) + assertTrue(config2.path.endsWith("${app.configuration.appId}/${user.localId}/s_realm2.realm")) + + // Check for https://github.com/realm/realm-java/issues/6882 + val realm1 = Realm.getInstance(config1) + try { + val realm2 = Realm.getInstance(config2) + realm2.close() + } finally { + realm1.close() + } + } + @Test fun loggedOutUsersThrows() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp index 37519bb143..942f401717 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp @@ -185,6 +185,17 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGe return nullptr; } +JNIEXPORT jstring JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetLocalIdentity(JNIEnv* env, jclass, jlong j_native_ptr) +{ + try { + auto user = *reinterpret_cast*>(j_native_ptr); + return to_jstring(env, user->local_identity()); + } + CATCH_STD(); + return nullptr; +} + + JNIEXPORT jbyte JNICALL Java_io_realm_internal_objectstore_OsSyncUser_nativeGetState(JNIEnv* env, jclass, jlong j_native_ptr) { try { diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp index 5c5535e8a8..fbcbf79b7e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp @@ -22,6 +22,7 @@ #include #include "util.hpp" +#include #include "jni_util/java_class.hpp" #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" @@ -73,3 +74,34 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeCreateSession(JNIEn } CATCH_STD() } + +JNIEXPORT jstring JNICALL Java_io_realm_mongodb_sync_Sync_nativeGetPathForRealm(JNIEnv* env, + jclass, + jstring j_user_id, + jstring j_encoded_partition_value, + jstring j_override_filename) +{ + try { + // This is a little bit of a hack. Normally Realm Java doesn't generate the C++ SyncConfig + // until the Realm is opened, but the Sync API for creating the Realm path require that + // it is created up front. So we cheat and create a SyncConfig with the minimal values + // needed for the path to be calculated. + JStringAccessor user_id(env, j_user_id); + std::shared_ptr user = SyncManager::shared().get_existing_logged_in_user(user_id); + if (!user) { + throw std::logic_error("User is not logged in"); + } + Bson bson(JniBsonProtocol::jstring_to_bson(env, j_encoded_partition_value)); + std::stringstream buffer; + buffer << bson; + SyncConfig config{user, buffer.str()}; + util::Optional file_name = util::none; + if (j_override_filename != nullptr) { + JStringAccessor override_file_name(env, j_override_filename); + file_name = std::string(override_file_name); + } + return to_jstring(env, SyncManager::shared().path_for_realm(config, file_name)); + } + CATCH_STD() + return nullptr; +} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 709e69580f..5b5fb8a901 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 709e69580f480051da8be8b444df400c64c652f8 +Subproject commit 5b5fb8a90192cb4ee6799e7465745cd2067f939b diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index cd81d56373..ea0379494e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -109,9 +109,7 @@ public class RealmConfiguration { // We need to enumerate all parameters since SyncConfiguration and RealmConfiguration supports different // subsets of them. - protected RealmConfiguration(@Nullable File realmDirectory, - @Nullable String realmFileName, - String canonicalPath, + protected RealmConfiguration(File realmPath, @Nullable String assetFilePath, @Nullable byte[] key, long schemaVersion, @@ -125,9 +123,9 @@ protected RealmConfiguration(@Nullable File realmDirectory, @Nullable CompactOnLaunchCallback compactOnLaunch, boolean isRecoveryConfiguration, long maxNumberOfActiveVersions) { - this.realmDirectory = realmDirectory; - this.realmFileName = realmFileName; - this.canonicalPath = canonicalPath; + this.realmDirectory = realmPath.getParentFile(); + this.realmFileName = realmPath.getName(); + this.canonicalPath = realmPath.getAbsolutePath(); this.assetFilePath = assetFilePath; this.key = key; this.schemaVersion = schemaVersion; @@ -428,24 +426,13 @@ public String toString() { return stringBuilder.toString(); } - // Gets the canonical path for a given file. - protected static String getCanonicalPath(File realmFile) { - try { - return realmFile.getCanonicalPath(); - } catch (IOException e) { - throw new RealmFileException(RealmFileException.Kind.ACCESS_ERROR, - "Could not resolve the canonical path to the Realm file: " + realmFile.getAbsolutePath(), - e); - } - } - // Checks if this configuration is a SyncConfiguration instance. protected boolean isSyncConfiguration() { return false; } protected static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) { - return new RealmConfiguration(null,null, canonicalPath,null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true, Long.MAX_VALUE); + return new RealmConfiguration(new File(canonicalPath),null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true, Long.MAX_VALUE); } /** @@ -837,9 +824,7 @@ public RealmConfiguration build() { rxFactory = new RealmObservableFactory(true); } - return new RealmConfiguration(directory, - fileName, - getCanonicalPath(new File(directory, fileName)), + return new RealmConfiguration(new File(directory, fileName), assetFilePath, key, schemaVersion, diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/TokenObfuscator.java b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/TokenObfuscator.java index a473fbc686..9cf3da31de 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/TokenObfuscator.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/log/obfuscator/TokenObfuscator.java @@ -44,7 +44,7 @@ public class TokenObfuscator extends RegexPatternObfuscator { public static final String AUTHCODE_KEY = "authCode"; public static final String ID_TOKEN_KEY = "id_token"; public static final String TOKEN_KEY = "token"; - public static final String ACCESS_TOKEN_KEY = "access_token"; + public static final String ACCESS_TOKEN_KEY = "accessToken"; private TokenObfuscator(Map patternReplacementMap) { super(patternReplacementMap); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java index 6e04efbd86..bdf463a0aa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsSyncUser.java @@ -95,6 +95,10 @@ public String getIdentity() { return nativeGetIdentity(nativePtr); } + public String getLocalIdentity() { + return nativeGetLocalIdentity(nativePtr); + } + public String getAccessToken() { return nativeGetAccessToken(nativePtr); } @@ -169,6 +173,7 @@ public int hashCode() { private static native String nativeGetMinAge(long nativePtr); private static native String nativeGetMaxAge(long nativePtr); private static native String nativeGetIdentity(long nativePtr); + private static native String nativeGetLocalIdentity(long nativePtr); private static native String nativeGetAccessToken(long nativePtr); private static native String nativeGetRefreshToken(long nativePtr); private static native String[] nativeGetIdentities(long nativePtr); // Returns pairs of {id, provider} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index c389401381..7c74ae9e4a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -119,14 +119,23 @@ protected PushImpl(OsPush osPush) { } /** - * Returns the id of the user. + * Returns the server id of the user. * - * @return the id of the user. + * @return the server id of the user. */ public String getId() { return osUser.getIdentity(); } + /** + * Returns the local id for this user. It is only guaranteed to be unique on this device. + * + * @return the local id of the user + */ + public String getLocalId() { + return osUser.getLocalIdentity(); + } + /** * Returns the name of the user. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java index a3b7eef505..579b987b80 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java @@ -16,8 +16,12 @@ package io.realm.mongodb.sync; +import org.bson.BsonValue; + +import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -25,6 +29,9 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.annotations.Beta; +import io.realm.internal.jni.JniBsonProtocol; +import io.realm.internal.objectstore.OsSyncUser; +import io.realm.mongodb.AppConfiguration; import io.realm.mongodb.ErrorCode; import io.realm.internal.Keep; import io.realm.internal.OsRealmConfig; @@ -167,22 +174,24 @@ public synchronized SyncSession getOrCreateSession(SyncConfiguration syncConfigu return session; } - List getAllSyncSessions(User user) { - //noinspection ConstantConditions - if (user == null) { - throw new IllegalArgumentException("A non-empty 'syncUser' is required."); - } - ArrayList allSessions = new ArrayList(); - for (SyncSession syncSession : sessions.values()) { - if (syncSession.getUser().equals(user)) { - allSessions.add(syncSession); - } + /** + * Returns the absolute path for the location of the Realm file on disk + */ + String getAbsolutePathForRealm(String userId, BsonValue partitionValue, @Nullable String overrideFileName) { + String encodedPartitionValue; + switch (partitionValue.getBsonType()) { + case STRING: + case OBJECT_ID: + case INT32: + case INT64: + encodedPartitionValue = JniBsonProtocol.encode(partitionValue, AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY); + break; + default: + throw new IllegalArgumentException("Unsupported type: " + partitionValue); } - return allSessions; + return nativeGetPathForRealm(userId, encodedPartitionValue, overrideFileName); } - - /** * Remove the wrapped Java session. * @param syncConfiguration configuration object for the synchronized Realm. @@ -304,4 +313,5 @@ void simulateClientReset(SyncSession session) { private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); private static native void nativeReconnect(); private static native void nativeCreateSession(long nativeConfigPtr); + private static native String nativeGetPathForRealm(String userId, String partitionValue, @Nullable String overrideFileName); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index df3ffde8c4..9b8f774877 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -37,7 +37,6 @@ import java.util.HashSet; import java.util.Locale; import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; import javax.annotation.Nullable; @@ -95,11 +94,6 @@ @Beta public class SyncConfiguration extends RealmConfiguration { - // The FAT file system has limitations of length. Also, not all characters are permitted. - // https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx - static final int MAX_FULL_PATH_LENGTH = 256; - static final int MAX_FILE_NAME_LENGTH = 255; - private static final char[] INVALID_CHARS = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'}; private final URI serverUrl; private final User user; private final SyncSession.ErrorHandler errorHandler; @@ -111,9 +105,7 @@ public class SyncConfiguration extends RealmConfiguration { private final ClientResyncMode clientResyncMode; private final BsonValue partitionValue; - private SyncConfiguration(File directory, - String filename, - String canonicalPath, + private SyncConfiguration(File realmPath, @Nullable String assetFilePath, @Nullable byte[] key, long schemaVersion, @@ -136,9 +128,7 @@ private SyncConfiguration(File directory, @Nullable String syncUrlPrefix, ClientResyncMode clientResyncMode, BsonValue partitionValue) { - super(directory, - filename, - canonicalPath, + super(realmPath, assetFilePath, key, schemaVersion, @@ -455,10 +445,9 @@ public static final class Builder { private RxObservableFactory rxFactory; @Nullable private Realm.Transaction initialDataTransaction; - private File defaultFolder; - private String defaultLocalFileName; + @Nullable + private String filename; private OsRealmConfig.Durability durability = OsRealmConfig.Durability.FULL; - private final Pattern pattern = Pattern.compile("^[A-Za-z0-9_\\-\\.]+$"); // for checking serverUrl private boolean readOnly = false; private boolean waitForServerChanges = false; private long initialDataTimeoutMillis = Long.MAX_VALUE; @@ -538,7 +527,6 @@ public Builder(User user, long partitionValue) { validateAndSet(user); validateAndSet(user.getApp().getConfiguration().getBaseUrl()); this.partitionValue = partitionValue; - this.defaultFolder = user.getApp().getConfiguration().getSyncRootDirectory(); if (Realm.getDefaultModule() != null) { this.modules.add(Realm.getDefaultModule()); } @@ -592,8 +580,30 @@ private void validateAndSet(URL baseUrl ) { } catch (URISyntaxException e) { throw new IllegalArgumentException("Invalid URI: " + baseUrl, e); } + } + + /** + * FIXME: Make public once https://github.com/realm/realm-object-store/pull/1049 is merged. + * + * Sets the filename for the Realm file on this device. + */ + Builder name(String filename) { + //noinspection ConstantConditions + if (filename == null || filename.isEmpty()) { + throw new IllegalArgumentException("A non-empty filename must be provided"); + } + + // Strip `.realm` suffix as it will be appended by Object Store later + if (filename.endsWith(".realm")) { + if (filename.length() == 6) { + throw new IllegalArgumentException("'.realm' is not a valid filename"); + } else { + filename = filename.substring(0, filename.length() - 6); + } + } - this.defaultLocalFileName = "default.realm"; + this.filename = filename; + return this; } /** @@ -1008,47 +1018,14 @@ public SyncConfiguration build() { rxFactory = new RealmObservableFactory(true); } - // FIXME: Figure out how to map to on-disk path. Partition key can be up to 16MB in size. - // Determine location on disk - // Use the serverUrl + user to create a unique filepath. - // The following types of paths can be generated - // //default.realm - // ///default.realm - URI resolvedServerUrl = serverUrl; // resolveServerUrl(serverUrl, user); + URI resolvedServerUrl = serverUrl; syncUrlPrefix = String.format("/api/client/v2.0/app/%s/realm-sync", user.getApp().getConfiguration().getAppId()); - String realmPathFromRootDir = user.getId() + "/" + getServerPath(user, resolvedServerUrl); - File realmFileDirectory = new File(defaultFolder, realmPathFromRootDir); - String realmFileName = defaultLocalFileName; - String fullPathName = realmFileDirectory.getAbsolutePath() + File.pathSeparator + realmFileName; - - // full path must not exceed 256 characters (on FAT) - if (fullPathName.length() > MAX_FULL_PATH_LENGTH) { - throw new IllegalStateException(String.format(Locale.US, - "Full path name must not exceed %d characters: %s", - MAX_FULL_PATH_LENGTH, fullPathName)); - } - if (realmFileName.length() > MAX_FILE_NAME_LENGTH) { - throw new IllegalStateException(String.format(Locale.US, - "File name exceed %d characters: %d", MAX_FILE_NAME_LENGTH, - realmFileName.length())); - } - - // substitute invalid characters - for (char c : INVALID_CHARS) { - realmFileName = realmFileName.replace(c, '_'); - } - - // Create the folder on disk (if needed) - if (!realmFileDirectory.exists() && !realmFileDirectory.mkdirs()) { - throw new IllegalStateException("Could not create directory for saving the Realm: " + realmFileDirectory); - } + String absolutePathForRealm = user.getApp().getSync().getAbsolutePathForRealm(user.getId(), partitionValue, filename); + File realmFile = new File(absolutePathForRealm); return new SyncConfiguration( - // Realm Configuration options - realmFileDirectory, - realmFileName, - getCanonicalPath(new File(realmFileDirectory, realmFileName)), + realmFile, null, // assetFile not supported by Sync. See https://github.com/realm/realm-sync/issues/241 key, schemaVersion, diff --git a/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt b/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt index 83f2d9f85c..161ad777ae 100644 --- a/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt +++ b/realm/realm-library/src/testObjectServer/kotlin/io/realm/ObfuscatorHelper.kt @@ -100,7 +100,7 @@ object ObfuscatorHelper { val TOKEN_ORIGINAL_INPUT_FACEBOOK = """ { "blahblahblah":"blehblehbleh", - "access_token":"my_access_token", + "accessToken":"my_access_token", "something":"random" } """.trimIndent() @@ -128,7 +128,7 @@ object ObfuscatorHelper { val TOKEN_OBFUSCATED_OUTPUT_FACEBOOK = """ { "blahblahblah":"blehblehbleh", - "access_token":"***", + "accessToken":"***", "something":"random" } """.trimIndent() From 7a926719cc4608bc3ec3fb3b253c33a2e4b22056 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 13 Aug 2020 16:28:07 +0200 Subject: [PATCH 1622/2110] Correctly refresh Java connections (#7019) --- .gitignore | 1 + CHANGELOG.md | 17 + Dockerfile | 50 +-- Jenkinsfile | 344 ++++++++++-------- dependencies.list | 7 +- examples/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/settings.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../groovy/io/realm/gradle/PluginTest.groovy | 38 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/build.gradle | 13 +- realm/config/findbugs/findbugs-filter.xml | 46 ++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../realm-annotations-processor/build.gradle | 18 +- realm/realm-library/build.gradle | 61 +--- .../java/io/realm/RealmInterprocessTest.java | 2 +- .../java/io/realm/RealmMigrationTests.java | 4 +- .../androidTest/java/io/realm/SortTest.java | 4 +- .../instrumentation/MockActivityManager.java | 99 ----- .../src/main/cpp/CMake/RealmCore.cmake | 4 +- .../realm-library/src/main/cpp/CMakeLists.txt | 16 +- .../internal/android/AndroidCapabilities.java | 2 +- .../internal/async/RealmAsyncTaskImpl.java | 2 +- .../java/io/realm/SyncManager.java | 3 + .../java/io/realm/SyncSession.java | 63 +++- .../objectServer/java/io/realm/SyncUser.java | 6 + .../async/ResetableRealmAsyncTask.java | 31 ++ .../network/AuthenticateResponse.java | 2 +- .../network/ExponentialBackoffTask.java | 56 ++- .../io/realm/SyncedRealmIntegrationTests.java | 25 ++ .../java/io/realm/SyncTestUtils.java | 7 +- .../realm/objectserver/utils/Constants.java | 2 +- .../testUtils/java/io/realm/TestHelper.java | 37 +- .../rule/TestRealmConfigurationFactory.java | 8 +- .../io/realm/services/RemoteTestService.java | 24 +- 39 files changed, 558 insertions(+), 452 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/async/ResetableRealmAsyncTask.java diff --git a/.gitignore b/.gitignore index 2e810308be..8754b69638 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ realm/realm-library/src/main/cpp/jni_include realm/realm-library/distribution # Cmake output realm/realm-library/.externalNativeBuild +realm/realm-library/.cxx diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb52c9f52..77d8c07e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 7.0.2(YYYY-MM-DD) + +### Enhancements +* None. + +### Fixes +* [ObjectServer] Calling `SyncManager.refreshConnections()` did not correctly refresh connections in all cases, which could delay reconnects up to 5 minutes. (Issue [#7003](https://github.com/realm/realm-java/issues/7003)) + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. + +### Internal +* None. + + ## 7.0.1(2020-07-01) ### Enhancements diff --git a/Dockerfile b/Dockerfile index fe1790d66a..4265616585 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,12 @@ -FROM ubuntu:16.04 +FROM ubuntu:18.04 # Locales RUN apt-get clean && apt-get -y update && apt-get install -y locales && locale-gen en_US.UTF-8 ENV LANG "en_US.UTF-8" ENV LANGUAGE "en_US.UTF-8" ENV LC_ALL "en_US.UTF-8" +ENV TZ=Europe/Copenhagen +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # Set the environment variables ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64 @@ -12,29 +14,38 @@ ENV ANDROID_HOME /opt/android-sdk-linux # Need by cmake ENV ANDROID_NDK_HOME /opt/android-ndk ENV ANDROID_NDK /opt/android-ndk -ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools +ENV PATH ${PATH}:${ANDROID_HOME}/emulator:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} ENV NDK_CCACHE /usr/bin/ccache +ENV CCACHE_CPP2 yes -# The 32 bit binaries because aapt requires it -# `file` is need by the script that creates NDK toolchains # Keep the packages in alphabetical order to make it easy to avoid duplication -RUN DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 \ +# tzdata needs to be installed first. See https://askubuntu.com/questions/909277/avoiding-user-interaction-with-tzdata-when-installing-certbot-in-a-docker-contai +# `file` is need by the Android Emulator +RUN DEBIAN_FRONTEND=noninteractive \ && apt-get update -qq \ + && apt-get install -y tzdata \ && apt-get install -y bsdmainutils \ + bridge-utils \ build-essential \ ccache \ curl \ file \ git \ - libc6:i386 \ - libgcc1:i386 \ - libncurses5:i386 \ - libstdc++6:i386 \ - libz1:i386 \ + jq \ + libc6 \ + libgcc1 \ + libglu1 \ + libncurses5 \ + libstdc++6 \ + libz1 \ + libvirt-clients \ + libvirt-daemon-system \ openjdk-8-jdk-headless \ + qemu-kvm \ s3cmd \ unzip \ + virt-manager \ wget \ zip \ && apt-get clean @@ -54,24 +65,17 @@ RUN sdkmanager --update RUN yes | sdkmanager --licenses # SDKs -# Please keep these in descending order! # The `yes` is for accepting all non-standard tool licenses. # Please keep all sections in descending order! RUN yes | sdkmanager \ - 'platform-tools' \ - 'build-tools;29.0.2' \ + 'build-tools;29.0.3' \ + 'cmake;3.6.4111459' \ + 'emulator' \ 'extras;android;m2repository' \ 'platforms;android-29' \ - 'cmake;3.6.4111459' - -# Install the NDK -RUN mkdir /opt/android-ndk-tmp && \ - cd /opt/android-ndk-tmp && \ - wget -q https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip -O android-ndk.zip && \ - unzip android-ndk.zip && \ - mv android-ndk-r21 /opt/android-ndk && \ - rm -rf /opt/android-ndk-tmp && \ - chmod -R a+rX /opt/android-ndk + 'platform-tools' \ + 'ndk;21.0.6113669' \ + 'system-images;android-29;default;x86' # Make the SDK universally writable RUN chmod -R a+rwX ${ANDROID_HOME} diff --git a/Jenkinsfile b/Jenkinsfile index 6fa2761da5..53e85b3020 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,149 +1,112 @@ #!groovy +@Library('realm-ci') _ + import groovy.json.JsonOutput -def buildSuccess = false -def rosContainer +buildSuccess = false +rosContainer = null +dockerNetworkId = UUID.randomUUID().toString() +// Branches from which we release SNAPSHOT's. Only release branches need to run on actual hardware. +releaseBranches = ['master', 'next-major', 'v10'] +// Branches that are "important", so if they do not compile they will generate a Slack notification +slackNotificationBranches = [ 'master', 'releases', 'next-major', 'v10' ] +currentBranch = env.CHANGE_BRANCH +// 'android' nodes have android devices attached and 'brix' are physical machines in Copenhagen. +nodeSelector = (releaseBranches.contains(currentBranch)) ? 'android' : 'docker-cph-03' // Switch to `brix` when all CPH nodes work: https://jira.mongodb.org/browse/RCI-14 try { - node('docker-cph-01') { + node(nodeSelector) { timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { stage('SCM') { checkout([ - $class: 'GitSCM', - branches: scm.branches, - gitTool: 'native git', - extensions: scm.extensions + [ - [$class: 'CleanCheckout'], - [$class: 'SubmoduleOption', recursiveSubmodules: true] - ], - userRemoteConfigs: scm.userRemoteConfigs - ]) + $class : 'GitSCM', + branches : scm.branches, + gitTool : 'native git', + extensions : scm.extensions + [ + [$class: 'CleanCheckout'], + [$class: 'SubmoduleOption', recursiveSubmodules: true] + ], + userRemoteConfigs: scm.userRemoteConfigs + ]) } // Toggles for PR vs. Master builds. - // For PR's, we just build for arm-v7a and run unit tests for the ObjectServer variant - // A full build is done on `master`. - // TODO Once Android emulators are available on all nodes, we can switch to x86 builds - // on PR's for even more throughput. + // - For PR's, we favor speed > absolute correctness. So we just build for x86, use an + // emulator and run unit tests for the ObjectServer variant. + // - For branches from which we make releases, we build all architectures and run tests + // on an actual device. + def useEmulator = false + def emulatorImage = "" def abiFilter = "" def instrumentationTestTarget = "connectedAndroidTest" - if (!['master', 'next-major'].contains(env.BRANCH_NAME)) { - abiFilter = "-PbuildTargetABIs=armeabi-v7a" - instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting + def deviceSerial = "" + if (!releaseBranches.contains(currentBranch)) { + useEmulator = true + emulatorImage = "system-images;android-29;default;x86" + abiFilter = "-PbuildTargetABIs=x86" + instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" + deviceSerial = "emulator-5554" } - def buildEnv - def rosEnv - stage('Docker build') { - // Docker image for build - buildEnv = docker.build 'realm-java:snapshot' - // Docker image for testing Realm Object Server - def dependProperties = readProperties file: 'dependencies.list' - def rosVersion = dependProperties["REALM_OBJECT_SERVER_VERSION"] - withCredentials([string(credentialsId: 'realm-sync-feature-token-enterprise', variable: 'realmFeatureToken')]) { - rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_VERSION=${rosVersion} --build-arg REALM_FEATURE_TOKEN=${realmFeatureToken} tools/sync_test_server" + try { + + def buildEnv = null + stage('Prepare Docker Images') { + buildEnv = docker.build 'realm-java:snapshot' + // Docker image for testing Realm Object Server + def dependProperties = readProperties file: 'dependencies.list' + def rosVersion = dependProperties["REALM_OBJECT_SERVER_VERSION"] + withCredentials([string(credentialsId: 'realm-sync-feature-token-enterprise', variable: 'realmFeatureToken')]) { + rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_VERSION=${rosVersion} --build-arg REALM_FEATURE_TOKEN=${realmFeatureToken} tools/sync_test_server" + } + rosContainer = rosEnv.run() } - } - rosContainer = rosEnv.run() + // There is a chance that real devices are attached to the host, so if the emulator is + // running we need to make sure that ADB and tests targets the correct device. + String restrictDevice = "" + if (deviceSerial != null) { + restrictDevice = "-e ANDROID_SERIAL=${deviceSerial} " + } - try { - buildEnv.inside("-e HOME=/tmp " + + buildEnv.inside("-e HOME=/tmp " + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + "--privileged " + + "-v /dev/kvm:/dev/kvm " + "-v /dev/bus/usb:/dev/bus/usb " + "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + "-v ${env.HOME}/.android:/tmp/.android " + "-v ${env.HOME}/ccache:/tmp/.ccache " + + restrictDevice + "-e REALM_CORE_DOWNLOAD_DIR=/tmp/.gradle " + - "--network container:${rosContainer.id}") { - - // Lock required around all usages of Gradle as it isn't - // able to share its cache between builds. - lock("${env.NODE_NAME}-android") { - - stage('JVM tests') { - try { - withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" - } - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) - } - } - - stage('Realm Transformer tests') { - try { - gradle('realm-transformer', 'check') - } finally { - storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' - } - } - - stage('Static code analysis') { - try { - gradle('realm', "findbugs pmd checkstyle ${abiFilter}") - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) - step([$class: 'CheckStylePublisher', - canComputeNew: false, - defaultEncoding: '', - healthy: '', - pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - unHealthy: '' - ]) - } - } - - stage('Run instrumented tests') { - String backgroundPid - try { - backgroundPid = startLogCatCollector() - forwardAdbPorts() - gradle('realm', "${instrumentationTestTarget}") - } finally { - stopLogCatCollector(backgroundPid) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' - storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' - } - } + "--network container:${rosContainer.id} ") { - // Gradle plugin tests require that artifacts are available, so this - // step needs to be after the instrumentation tests - stage('Gradle plugin tests') { - try { - gradle('gradle-plugin', 'check --debug') - } finally { - storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' - } - } - - // TODO: add support for running monkey on the example apps - - if (['master'].contains(env.BRANCH_NAME)) { - stage('Collect metrics') { - collectAarMetrics() - } - } - - if (['master', 'next-major'].contains(env.BRANCH_NAME)) { - stage('Publish to OJO') { - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" - } - } - } + // Lock required around all usages of Gradle as it isn't + // able to share its cache between builds. + lock("${env.NODE_NAME}-android") { + if (useEmulator) { + // TODO: We should wait until the emulator is online. For now assume it starts fast enough + // before the tests will run, since the library needs to build first. + sh """yes '\n' | avdmanager create avd -n CIEmulator -k '${emulatorImage}' --force""" + sh "adb start-server" // https://stackoverflow.com/questions/56198290/problems-with-adb-exe + // Need to go to ANDROID_HOME due to https://askubuntu.com/questions/1005944/emulator-avd-does-not-launch-the-virtual-device + sh "cd \$ANDROID_HOME/tools && emulator -avd CIEmulator -no-boot-anim -no-window -wipe-data -noaudio -partition-size 4098 &" + try { + runBuild(abiFilter, instrumentationTestTarget) + } finally { + sh "adb emu kill" } + } else { + runBuild(abiFilter, instrumentationTestTarget) } + } + } } finally { - archiveRosLog(rosContainer.id) - sh "docker logs ${rosContainer.id}" - rosContainer.stop() + archiveServerLogs(rosContainer.id) + sh "docker logs ${rosContainer.id}" + rosContainer.stop() } } } @@ -155,50 +118,139 @@ try { buildSuccess = false throw e } finally { - if (['master', 'releases', 'next-major'].contains(env.BRANCH_NAME) && !buildSuccess) { + if (slackNotificationBranches.contains(currentBranch) && !buildSuccess) { node { withCredentials([[$class: 'StringBinding', credentialsId: 'slack-java-url', variable: 'SLACK_URL']]) { def payload = JsonOutput.toJson([ - username: 'Mr. Jenkins', - icon_emoji: ':jenkins:', - attachments: [[ - 'title': "The ${env.BRANCH_NAME} branch is broken!", - 'text': "<${env.BUILD_URL}|Click here> to check the build.", - 'color': "danger" - ]] - ]) + username: 'Mr. Jenkins', + icon_emoji: ':jenkins:', + attachments: [[ + 'title': "The ${currentBranch} branch is broken!", + 'text': "<${env.BUILD_URL}|Click here> to check the build.", + 'color': "danger" + ]] + ]) sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" } } } } +// Runs all build steps +def runBuild(abiFilter, instrumentationTestTarget) { + + stage('Build') { + sh "chmod +x gradlew && ./gradlew assemble javadoc ${abiFilter} --stacktrace" + } + + stage('Tests') { + parallel 'JVM' : { + try { + sh "chmod +x gradlew && ./gradlew check ${abiFilter} --stacktrace" + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + storeJunitResults 'realm/realm-library/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) + } + }, + 'Realm Transformer' : { + try { + gradle('realm-transformer', 'check') + } finally { + storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + } + }, + 'Static code analysis' : { + try { + gradle('realm', "findbugs checkstyle") // FIXME: pmd disabled: https://github.com/realm/realm-java/issues/7024 + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) + // publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) + step([$class: 'CheckStylePublisher', + canComputeNew: false, + defaultEncoding: '', + healthy: '', + pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', + unHealthy: '' + ]) + } + }, + 'Instrumentation' : { + String backgroundPid + try { + backgroundPid = startLogCatCollector() + forwardAdbPorts() + gradle('realm', "${instrumentationTestTarget} ${abiFilter}") + } finally { + stopLogCatCollector(backgroundPid) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } + }, + 'Gradle Plugin' : { + try { + gradle('gradle-plugin', 'check --debug') + } finally { + storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + } + } + } + + // TODO: add support for running monkey on the example apps + + if (['master'].contains(currentBranch)) { + stage('Collect metrics') { + collectAarMetrics() + } + } + + if (releaseBranches.contains(currentBranch)) { + stage('Publish to OJO') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + } + } + } +} + def forwardAdbPorts() { - sh ''' adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && - adb reverse tcp:8888 tcp:8888 - ''' + sh """ adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && + adb reverse tcp:8888 tcp:8888 && adb reverse tcp:9090 tcp:9090 + """ } -def String startLogCatCollector() { - sh '''adb logcat -c - adb logcat -v time > "logcat.txt" & - echo $! > pid - ''' - return readFile("pid").trim() +String startLogCatCollector() { + // Cancel build quickly if no device is available. The lock acquired already should + // ensure we have access to a device. If not, it is most likely a more severe problem. + timeout(time: 1, unit: 'MINUTES') { + // Need ADB as root to clear all buffers: https://stackoverflow.com/a/47686978/1389357 + sh 'adb devices' + sh """adb root + adb logcat -b all -c + adb logcat -v time > 'logcat.txt' & + echo \$! > pid + """ + return readFile("pid").trim() + } } def stopLogCatCollector(String backgroundPid) { - sh "kill ${backgroundPid}" - zip([ - 'zipFile': 'logcat.zip', - 'archive': true, - 'glob' : 'logcat.txt' - ]) - sh 'rm logcat.txt' + // The pid might not be available if the build was terminated early or stopped due to + // a build error. + if (backgroundPid != null) { + sh "kill ${backgroundPid}" + zip([ + 'zipFile': 'logcat.zip', + 'archive': true, + 'glob' : 'logcat.txt' + ]) + sh 'rm logcat.txt' + } } -def archiveRosLog(String id) { - sh "docker cp ${id}:/tmp/integration-test-command-server.log ./ros.log" +def archiveServerLogs(String rosContainerId) { + sh "docker cp ${rosContainerId}:/tmp/integration-test-command-server.log ./ros.log" zip([ 'zipFile': 'roslog.zip', 'archive': true, @@ -221,10 +273,10 @@ def getTagsString(Map tags) { def storeJunitResults(String path) { step([ - $class: 'JUnitResultArchiver', - allowEmptyResults: true, - testResults: path - ]) + $class: 'JUnitResultArchiver', + allowEmptyResults: true, + testResults: path + ]) } def collectAarMetrics() { diff --git a/dependencies.list b/dependencies.list index 579eb34917..b16e9b3738 100644 --- a/dependencies.list +++ b/dependencies.list @@ -8,12 +8,11 @@ REALM_SYNC_SHA256=239049c777e4275fe094c73ae6dfa8736ae5ca9aa821c8d58b6d5eb3d84415 REALM_OBJECT_SERVER_VERSION=3.28.2 # Common Android settings across projects -GRADLE_BUILD_TOOLS=3.3.2 -ANDROID_BUILD_TOOLS=28.0.3 +GRADLE_BUILD_TOOLS=3.6.1 +ANDROID_BUILD_TOOLS=29.0.3 # Common classpath dependencies -# Gradle 5 is not supported yet: https://issuetracker.google.com/issues/126433059 -gradleVersion=4.10.1 +gradleVersion=5.6.4 ndkVersion=21.0.6113669 BUILD_INFO_EXTRACTOR_GRADLE=4.7.5 GRADLE_BINTRAY_PLUGIN=1.8.4 diff --git a/examples/build.gradle b/examples/build.gradle index 0436277664..04c40a67c5 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,6 +1,6 @@ def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) -project.ext.sdkVersion = 27 +project.ext.sdkVersion = 29 project.ext.minSdkVersion = 16 project.ext.buildTools = projectDependencies.get("ANDROID_BUILD_TOOLS") diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/examples/settings.gradle b/examples/settings.gradle index 15f6d6c37d..519f5eb74d 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -12,6 +12,6 @@ include 'newsreaderExample' include 'rxJavaExample' include 'secureTokenAndroidKeyStore' include 'threadExample' -include 'unitTestExample' +// include 'unitTestExample' FIXME: https://github.com/realm/realm-java/issues/6834 include 'objectServerExample' include 'multiprocessExample' diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index def91cc266..003b0819c7 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -39,7 +39,7 @@ import static org.junit.Assert.fail * Comment about the order of repositories. * The order of repositories do matter, and might need to change depending on the * version of the Build Tools being used. See e.g.: - * + * * https://stackoverflow.com/questions/55278227/android-gradle-build-error-artifacts-for-configuration-classpath/55278968#55278968 * https://stackoverflow.com/questions/52968576/could-not-find-aapt2-proto-jar-com-android-tools-buildaapt2-proto0-3-1 */ @@ -121,7 +121,9 @@ class PluginTest { void pluginAddsRightRepositories_noRepositorySet() { project.buildscript { repositories { - google() + maven { + url 'https://maven.google.com/' + } mavenCentral() jcenter() } @@ -149,7 +151,7 @@ class PluginTest { project.evaluate() assertEquals(3, project.buildscript.repositories.size()) - assertEquals(4, project.repositories.size()) + assertEquals(1, project.repositories.size()) assertEquals('jcenter.bintray.com', project.repositories.last().url.host) } @@ -157,9 +159,11 @@ class PluginTest { void pluginAddsRightRepositories_withRepositoriesSet() { project.buildscript { repositories { - google() mavenCentral() jcenter() + maven { + url 'https://maven.google.com/' + } } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" @@ -168,7 +172,6 @@ class PluginTest { project.repositories { google() - mavenCentral() } def manifest = project.file("src/main/AndroidManifest.xml") @@ -190,10 +193,10 @@ class PluginTest { project.evaluate() assertEquals(3, project.buildscript.repositories.size()) - assertEquals('jcenter.bintray.com', project.buildscript.repositories.last().url.host) + assertEquals('maven.google.com', project.buildscript.repositories.last().url.host) - assertEquals(5, project.repositories.size()) - assertEquals('repo.maven.apache.org', project.repositories.last().url.host) + assertEquals(1, project.repositories.size()) + assertEquals('dl.google.com', project.repositories.last().url.host) } // Test for https://github.com/realm/realm-java/issues/6610 @@ -201,9 +204,11 @@ class PluginTest { void pluginAddsRightRepositories_withFlatDirs() { project.buildscript { repositories { - mavenCentral() - google() jcenter() + maven { + url 'https://maven.google.com/' + } + mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" @@ -214,7 +219,6 @@ class PluginTest { flatDir { dirs 'libs' } - mavenCentral() google() } @@ -237,9 +241,9 @@ class PluginTest { project.evaluate() assertEquals(3, project.buildscript.repositories.size()) - assertEquals('jcenter.bintray.com', project.buildscript.repositories.last().url.host) + assertEquals('repo.maven.apache.org', project.buildscript.repositories.last().url.host) - assertEquals(6, project.repositories.size()) + assertEquals(2, project.repositories.size()) assertEquals('dl.google.com', project.repositories.last().url.host) } @@ -248,7 +252,9 @@ class PluginTest { void pluginAddsRightRepositories_withRepositoriesSetAfterPluginIsApplied() { project.buildscript { repositories { - google() + maven { + url 'https://maven.google.com/' + } mavenCentral() jcenter() } @@ -280,9 +286,9 @@ class PluginTest { project.evaluate() assertEquals(3, project.buildscript.repositories.size()) - assertEquals('dl.google.com', project.buildscript.repositories.first().url.host) + assertEquals('maven.google.com', project.buildscript.repositories.first().url.host) - assertEquals(4, project.repositories.size()) + assertEquals(1, project.repositories.size()) assertEquals('dl.google.com', project.repositories.last().url.host) } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm/build.gradle b/realm/build.gradle index 673c8b0e92..eabeb14244 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get('GRADLE_BUILD_TOOLS')}" - classpath 'de.undercouch:gradle-download-task:3.3.0' + classpath 'de.undercouch:gradle-download-task:4.0.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.4' @@ -35,7 +35,7 @@ allprojects { project.ext.set(key, val) } project.ext.minSdkVersion = 16 - project.ext.compileSdkVersion = 28 + project.ext.compileSdkVersion = 29 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") group = 'io.realm' version = file("${rootDir}/../version.txt").text.trim() @@ -45,3 +45,12 @@ allprojects { jcenter() } } + +// Disable JavaDoc strict mode: https://blog.joda.org/2014/02/turning-off-doclint-in-jdk-8-javadoc.html +if (JavaVersion.current().isJava8Compatible()) { + allprojects { + tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:-missing', '-quiet') + } + } +} diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index 5a5d451546..5c1dc1118d 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -4,43 +4,53 @@ In code, please prefer annotations as a way of ignoring Findbugs issues --> - - - + + + + - + - + - + - + - + - + - + - + + + - + + + - + + + + + + + + + + + diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index ae79c01a89..3d30713f96 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -7,16 +7,18 @@ apply plugin: 'com.jfrog.bintray' sourceCompatibility = '1.8' targetCompatibility = '1.8' +def properties = new Properties() +properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) + dependencies { - compile "com.squareup:javawriter:2.5.1" - compile "io.realm:realm-annotations:${version}" - - testCompile files('../realm-library/build/intermediates/intermediate-jars/objectServer/release/classes.jar') // Java projects cannot depend on AAR files - testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it - testCompile group:'junit', name:'junit', version:'4.12' - testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' - testCompile files(file("${System.env.ANDROID_HOME}/platforms/android-27/android.jar")) + implementation "com.squareup:javawriter:2.5.1" + implementation "io.realm:realm-annotations:${version}" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + testImplementation files('../realm-library/build/intermediates/aar_main_jar/baseRelease/classes.jar') // Java projects cannot depend on AAR files + testImplementation files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it + testImplementation group:'junit', name:'junit', version:'4.12' + testImplementation group:'com.google.testing.compile', name:'compile-testing', version:'0.6' + testImplementation files(file("${System.env.ANDROID_HOME}/platforms/android-29/android.jar")) } // for Ant filter diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index bc289641a1..85a27020fc 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -74,6 +74,8 @@ android { } } + ndkVersion = "21.0.6113669" + externalNativeBuild { cmake { path 'src/main/cpp/CMakeLists.txt' @@ -573,6 +575,7 @@ if (project.hasProperty('dontCleanJniFiles')) { } else { task cleanExternalBuildFiles(type: Delete) { delete project.file('.externalNativeBuild') + delete project.file('.cxx') // Clean .so files that were created by old build script (realm/realm-jni/build.gradle). delete project.file('src/main/jniLibs') } @@ -581,15 +584,19 @@ if (project.hasProperty('dontCleanJniFiles')) { project.afterEvaluate { android.libraryVariants.all { variant -> - variant.externalNativeBuildTasks[0].dependsOn(checkNdk) if (project.hasProperty('buildTargetABIs') && project.getProperty('buildTargetABIs').trim().isEmpty()) { - variant.externalNativeBuildTasks[0].enabled = false + variant.externalNativeBuildProviders[0].configure { + enabled = false + } } // all Java files must be compiled before native build + // See https://github.com/android/ndk-samples/issues/284 android.libraryVariants.all { anotherVariant -> if (variant.flavorName == anotherVariant.flavorName) { - variant.externalNativeBuildTasks[0].dependsOn("compile${anotherVariant.name.capitalize()}JavaWithJavac") + variant.externalNativeBuildProviders[0].configure { + dependsOn "compile${anotherVariant.name.capitalize()}JavaWithJavac" + } } } // as of android gradle plugin 3.0.0-alpha5, generateJsonModel* triggers native build. Java files must be compiled before them. @@ -599,34 +606,6 @@ project.afterEvaluate { } } -task checkNdk() { - doLast { - def ndkPathInEnvVariable = System.env.ANDROID_NDK_HOME - if (!ndkPathInEnvVariable) { - throw new GradleException("The environment variable 'ANDROID_NDK_HOME' must be set.") - } - checkNdk(ndkPathInEnvVariable) - - def localPropFile = rootProject.file('local.properties') - if (!localPropFile.exists()) { - // we can skip the checks since 'ANDROID_NDK_HOME' will be used instead. - } else { - def String ndkPathInLocalProperties = getValueFromPropertiesFile(localPropFile, 'ndk.dir') - if (!ndkPathInLocalProperties) { - throw new GradleException("'ndk.dir' must be set in ${localPropFile.getAbsolutePath()}.") - } - checkNdk(ndkPathInLocalProperties) - if (new File(ndkPathInLocalProperties).getCanonicalPath() - != new File(ndkPathInEnvVariable).getCanonicalPath()) { - throw new GradleException( - "The value of environment variable 'ANDROID_NDK_HOME' (${ndkPathInEnvVariable}) and" - + " 'ndk.dir' in 'local.properties' (${ndkPathInLocalProperties}) " - + ' must point the same directory.') - } - } - } -} - android.productFlavors.all { flavor -> def librarySuffix = flavor.name == 'base' ? '' : '-object-server' def userName = project.findProperty('bintrayUser') ?: 'noUser' @@ -777,26 +756,6 @@ task ojoUpload() { group = 'Publishing' } -def checkNdk(String ndkPath) { - def detectedNdkVersion - def releaseFile = new File(ndkPath, 'RELEASE.TXT') - def propertyFile = new File(ndkPath, 'source.properties') - if (releaseFile.isFile()) { - detectedNdkVersion = releaseFile.text.trim().split()[0].split('-')[0] - } else if (propertyFile.isFile()) { - detectedNdkVersion = getValueFromPropertiesFile(propertyFile, 'Pkg.Revision') - if (detectedNdkVersion == null) { - throw new GradleException("Failed to obtain the NDK version information from ${ndkPath}/source.properties") - } - } else { - throw new GradleException("Neither ${releaseFile.getAbsolutePath()} nor ${propertyFile.getAbsolutePath()} is a file.") - } - if (detectedNdkVersion != project.ndkVersion) { - throw new GradleException("Your NDK version: ${detectedNdkVersion}." - + " Realm JNI must be compiled with version ${project.ndkVersion} of the NDK.") - } -} - static def getValueFromPropertiesFile(File propFile, String key) { if (!propFile.isFile() || !propFile.canRead()) { return null diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java index 55dba9fe68..7a904ea333 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java @@ -126,7 +126,7 @@ public void onServiceDisconnected(ComponentName componentName) { private class InterprocessHandler extends Handler { // Timeout Watchdog. In case the service crashed or expected response is not returned. // It is very important to feed the dog after the expected message arrived. - private final int timeout = 5000; + private final static int timeout = 5000; private volatile boolean isTimeout = true; private Runnable timeoutRunnable = new Runnable() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 8cf23c8fb7..92e4d208ea 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -648,7 +648,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { public void apply(DynamicRealmObject obj) { String fieldValue = obj.getString(MigrationPrimaryKey.FIELD_PRIMARY); if (fieldValue != null && fieldValue.length() != 0) { - obj.setInt(TEMP_FIELD_ID, Integer.valueOf(fieldValue).intValue()); + obj.setInt(TEMP_FIELD_ID, Integer.parseInt(fieldValue)); } else { // Since this cannot be accepted as proper pk value, we'll delete it. // *You can modify with some other value such as 0, but that's not @@ -695,7 +695,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { public void apply(DynamicRealmObject obj) { String fieldValue = obj.getString(MigrationPrimaryKey.FIELD_PRIMARY); if (fieldValue != null && fieldValue.length() != 0) { - obj.setInt(TEMP_FIELD_ID, Integer.valueOf(fieldValue)); + obj.setInt(TEMP_FIELD_ID, Integer.parseInt(fieldValue)); } else { obj.setNull(TEMP_FIELD_ID); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 45b23de504..003c8f1738 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -521,7 +521,7 @@ public void run() { @Override public void onChange(RealmResults element) { assertEquals(TEST_SIZE + 1, element.size()); - int i = 0; + long i = 0; for (AllTypes allTypes : element) { assertEquals(new Date(i), allTypes.getColumnDate()); i++; @@ -537,7 +537,7 @@ public void onChange(RealmResults element) { @Override public void onChange(RealmResults element) { assertEquals(TEST_SIZE + 1, element.size()); - int i = element.size() - 1; + long i = ((long) element.size()) - 1; for (AllTypes allTypes : element) { assertEquals(new Date(i), allTypes.getColumnDate()); i--; diff --git a/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java b/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java deleted file mode 100644 index 8dfdc64114..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.instrumentation; - -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - -import io.realm.RealmConfiguration; - -public class MockActivityManager { - private Lifecycle instance; - private final RealmConfiguration realmConfiguration; - private final ReferenceQueue queue; - private static final Set> references = new CopyOnWriteArraySet>(); - - private MockActivityManager(RealmConfiguration realmConfiguration) { - this.realmConfiguration = realmConfiguration; - - instance = LifecycleComponentFactory.newInstance(realmConfiguration); - - this.queue = new ReferenceQueue(); - references.add(new WeakReference(instance, queue)); - - instance.onStart(); - } - - public static MockActivityManager newInstance (RealmConfiguration realmConfiguration) { - return new MockActivityManager(realmConfiguration); - } - - // simulates a configuration change, that should trigger - // to recreate the Lifecycle component - public void sendConfigurationChange () { - instance.onStop(); - // creates a new instance - instance = LifecycleComponentFactory.newInstance(realmConfiguration); - references.add(new WeakReference(instance, queue)); - - instance.onStart(); - } - - public int numberOfInstances () { - triggerGC(); - // WeakReferences are enqueued as soon as the object to which they point to becomes - // weakly reachable. - deleteWeaklyReachableReferences(); - return references.size(); - } - - // call onStop on the Activity, this help closing any open open realm - public void onStop() { - instance.onStop(); - } - - private void triggerGC () { - // From the AOSP FinalizationTest: - // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/ - // java/lang/ref/FinalizationTester.java - // System.gc() does not garbage collect every time. Runtime.gc() is - // more likely to perform a gc. - Runtime.getRuntime().gc(); - enqueueReferences(); - System.runFinalization(); - } - - private void enqueueReferences() { - // Hack. We don't have a programmatic way to wait for the reference queue daemon to move - // references to the appropriate queues. - try { - Thread.sleep(100); - } catch (InterruptedException e) { - throw new AssertionError(); - } - } - - private void deleteWeaklyReachableReferences() { - Reference weakReference; - while ((weakReference = queue.poll()) != null ) { // Does not wait for a reference to become available. - references.remove(weakReference); - } - } -} diff --git a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake index ee22a08fc9..6014163b4f 100644 --- a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake +++ b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake @@ -89,9 +89,11 @@ function(use_sync_release enable_sync sync_dist_path) # -latomic is not set by default for mips and armv5. # See https://code.google.com/p/android/issues/detail?id=182094 + list(APPEND LIB_INCLUDE_DIRS "${sync_dist_path}/include") + list(APPEND LIB_INCLUDE_DIRS "${sync_dist_path}/include/realm") set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_path} IMPORTED_LINK_INTERFACE_LIBRARIES atomic - INTERFACE_INCLUDE_DIRECTORIES "${sync_dist_path}/include") + INTERFACE_INCLUDE_DIRECTORIES "${LIB_INCLUDE_DIRS}") if (enable_sync) # Sync static library diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 677d592023..cfc1d28159 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -17,6 +17,18 @@ ########################################################################### cmake_minimum_required(VERSION 3.6.0) +# loading dependencies properties +file(STRINGS "${CMAKE_SOURCE_DIR}/../../../../../dependencies.list" DEPENDENCIES) +foreach(LINE IN LISTS DEPENDENCIES) + string(REGEX MATCHALL "([^=]+)" KEY_VALUE "${LINE}") + list(LENGTH KEY_VALUE matches_count) + if(matches_count STREQUAL 2) + list(GET KEY_VALUE 0 KEY) + list(GET KEY_VALUE 1 VALUE) + set(DEP_${KEY} ${VALUE}) + endif() +endforeach() + FUNCTION(capitalizeFirstLetter var value) string(SUBSTRING ${value} 0 1 firstLetter) string(TOUPPER ${firstLetter} firstLetter) @@ -69,7 +81,7 @@ capitalizeFirstLetter(buildTypeCap "${CMAKE_BUILD_TYPE}") # Generate JNI header files. Each build has its own JNI header in its build_dir/jni_include. # WARNING: The classes_PATH is not part the public API offered by the Android Gradle Plugin # so it might change without warning when upgrading the plugin. -set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/compile${realmFlavorCap}${buildTypeCap}JavaWithJavac/classes/) +set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/classes/) set(classes_LIST io.realm.RealmQuery io.realm.internal.Table io.realm.internal.CheckedRow @@ -95,7 +107,7 @@ if (build_SYNC) endif() create_javah(TARGET jni_headers CLASSES ${classes_LIST} - CLASSPATH ${classes_PATH} + CLASSPATH ${classes_PATH} $ENV{ANDROID_HOME}/platforms/android-29/android.jar OUTPUT_DIR ${jni_headers_PATH} DEPENDS ${classes_PATH} ) diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java index 0a2d95496e..e5f8255a1f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java @@ -32,7 +32,7 @@ public class AndroidCapabilities implements Capabilities { // If set, it will treat the current looper thread as the main thread. // It is up to the caller to handle any race conditions around this. Right now only // RunInLooperThread.java does this as part of setting up the test. - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings("MS_CANNOT_BE_FINAL") public static boolean EMULATE_MAIN_THREAD = false; private final Looper looper; diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java index cd1b62161b..8081650da2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java @@ -22,7 +22,7 @@ import io.realm.RealmAsyncTask; -public final class RealmAsyncTaskImpl implements RealmAsyncTask { +public class RealmAsyncTaskImpl implements RealmAsyncTask { private final Future pendingTask; private final ThreadPoolExecutor service; private volatile boolean isCancelled = false; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index d28319f723..3b6f7e7e78 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -535,6 +535,9 @@ private static synchronized void notifyErrorHandler(String nativeErrorCategory, private static synchronized void notifyNetworkIsBack() { try { + for (SyncSession session : sessions.values()) { + session.refreshConnection(); + } nativeReconnect(); } catch (Exception exception) { RealmLog.error(exception); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 43a77d53b0..1ec529dc71 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -44,6 +44,7 @@ import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.async.RealmAsyncTaskImpl; +import io.realm.internal.async.ResetableRealmAsyncTask; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.network.RealmObjectServer; import io.realm.internal.network.ExponentialBackoffTask; @@ -81,9 +82,9 @@ public class SyncSession { private final SyncConfiguration configuration; private final ErrorHandler errorHandler; - private RealmAsyncTask networkRequest; + private ResetableRealmAsyncTask networkRequest; // private RealmAsyncTask refreshTokenTask; - private RealmAsyncTask refreshTokenNetworkRequest; + private ResetableRealmAsyncTask refreshTokenNetworkRequest; private AtomicBoolean onGoingAccessTokenQuery = new AtomicBoolean(false); private volatile boolean isClosed = false; private final AtomicReference waitingForServerChanges = new AtomicReference<>(null); @@ -429,7 +430,7 @@ public synchronized void removeConnectionChangeListener(ConnectionListener liste } } - void close() { + synchronized void close() { isClosed = true; if (networkRequest != null) { networkRequest.cancel(); @@ -735,7 +736,7 @@ public interface ErrorHandler { } // Return the access token for the Realm this Session is connected to. - String getAccessToken(final RealmObjectServer authServer, String refreshToken) { + synchronized String getAccessToken(final RealmObjectServer authServer, String refreshToken) { // check first if there's a valid access_token we can return immediately if (getUser().isRealmAuthenticated(configuration)) { Token accessToken = getUser().getAccessToken(configuration); @@ -767,15 +768,16 @@ String getAccessToken(final RealmObjectServer authServer, String refreshToken) { } // Authenticate by getting access tokens for the specific Realm - private void authenticateRealm(final RealmObjectServer authServer) { + synchronized void authenticateRealm(final RealmObjectServer authServer) { if (networkRequest != null) { networkRequest.cancel(); } clearScheduledAccessTokenRefresh(); onGoingAccessTokenQuery.set(true); + final String taskName = "Session[" + configuration.getPath() + "][AuthenticateRealm]"; // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { + networkRequest = new ResetableRealmAsyncTask(new ExponentialBackoffTask() { @Override protected AuthenticateResponse execute() { if (!isClosed && !Thread.currentThread().isInterrupted()) { @@ -802,6 +804,7 @@ protected void onSuccess(AuthenticateResponse response) { onGoingAccessTokenQuery.set(false); } } + networkRequest = null; } @Override @@ -817,12 +820,17 @@ protected void onError(AuthenticateResponse response) { && !(response.getError().getException() instanceof InterruptedIOException)) { errorHandler.onError(SyncSession.this, response.getError()); } + networkRequest = null; } - }); - networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); + + @Override + protected String getName() { + return taskName; + } + }, SyncManager.NETWORK_POOL_EXECUTOR); } - private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long expireDateInMs) { + private synchronized void scheduleRefreshAccessToken(final RealmObjectServer authServer, long expireDateInMs) { onGoingAccessTokenQuery.set(true); // calculate the delay time before which we should refresh the access_token, // we adjust to 10 second to proactively refresh the access_token before the session @@ -830,7 +838,7 @@ private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; if (refreshAfter < 0) { // Token already expired - RealmLog.debug("Expires time already reached for the access token, refresh as soon as possible"); + RealmLog.debug("Session[%s]: Expires time already reached for the access token, refresh as soon as possible", configuration.getPath()); // we avoid refreshing directly to avoid an edge case where the client clock is ahead // of the server, causing all access_token received from the server to be always // expired, we will flood the server with refresh token requests then, so adding @@ -838,7 +846,7 @@ private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long refreshAfter = REFRESH_MARGIN_DELAY; } - RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); + RealmLog.debug("Session[%s]: Schedule refresh of access token in %s sec.", configuration.getPath(), TimeUnit.SECONDS.convert(refreshAfter, TimeUnit.MILLISECONDS)); if (refreshTokenTask != null) { refreshTokenTask.cancel(); @@ -856,11 +864,11 @@ public void run() { } // Authenticate by getting access tokens for the specific Realm - private void refreshAccessToken(final RealmObjectServer authServer) { + private synchronized void refreshAccessToken(final RealmObjectServer authServer) { // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. clearScheduledAccessTokenRefresh(); - - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { + final String taskName = "Session[" + configuration.getPath() + "][RefreshAccessToken]"; + refreshTokenNetworkRequest = new ResetableRealmAsyncTask(new ExponentialBackoffTask() { @Override protected AuthenticateResponse execute() { if (!isClosed && !Thread.currentThread().isInterrupted()) { @@ -873,7 +881,7 @@ protected AuthenticateResponse execute() { protected void onSuccess(AuthenticateResponse response) { synchronized (SyncSession.this) { if (!isClosed && !Thread.currentThread().isInterrupted() && !refreshTokenNetworkRequest.isCancelled()) { - RealmLog.debug("Access Token refreshed successfully, Sync URL: " + configuration.getServerUrl()); + RealmLog.debug("Session[%s]: Access Token refreshed successfully.", configuration.getPath()); SyncWorker syncWorker = response.getSyncWorker(); if (syncWorker != null) { @@ -888,6 +896,7 @@ protected void onSuccess(AuthenticateResponse response) { scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); } } + refreshTokenNetworkRequest = null; } } @@ -895,19 +904,35 @@ protected void onSuccess(AuthenticateResponse response) { protected void onError(AuthenticateResponse response) { if (!isClosed && !Thread.currentThread().isInterrupted()) { onGoingAccessTokenQuery.set(false); - RealmLog.error("Unrecoverable error, while refreshing the access Token (" + response.getError().toString() + ") reschedule will not happen"); + RealmLog.debug("Session[%s]: Unrecoverable error, while refreshing the access Token. Reschedule will not happen. %s", configuration.getPath(), response.getError()); } + refreshTokenNetworkRequest = null; + } + + @Override + protected String getName() { + return taskName; } - }); - refreshTokenNetworkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); + }, SyncManager.NETWORK_POOL_EXECUTOR); + } + + synchronized void refreshConnection() { + if (networkRequest != null) { + networkRequest.resetTask(); + } + if (refreshTokenNetworkRequest != null) { + refreshTokenNetworkRequest.resetTask(); + } } - void clearScheduledAccessTokenRefresh() { + synchronized void clearScheduledAccessTokenRefresh() { if (refreshTokenTask != null) { refreshTokenTask.cancel(); + refreshTokenTask = null; } if (refreshTokenNetworkRequest != null) { refreshTokenNetworkRequest.cancel(); + refreshTokenNetworkRequest = null; } onGoingAccessTokenQuery.set(false); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index d8cdc0847f..a65cf03064 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -356,6 +356,7 @@ public void logOut() { final Token refreshTokenToBeRevoked = refreshToken; ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; + String taskName = "LogOutUser[" + identity + "]"; networkPoolExecutor.submit(new ExponentialBackoffTask(3) { @Override @@ -372,6 +373,11 @@ protected void onSuccess(LogoutResponse response) { protected void onError(LogoutResponse response) { RealmLog.error("Failed to log user out.\n" + response.getError().toString()); } + + @Override + protected String getName() { + return taskName; + } }); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/async/ResetableRealmAsyncTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/async/ResetableRealmAsyncTask.java new file mode 100644 index 0000000000..c6a36f71fd --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/async/ResetableRealmAsyncTask.java @@ -0,0 +1,31 @@ +package io.realm.internal.async; + +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadPoolExecutor; + +import io.realm.internal.network.ExponentialBackoffTask; + +/** + * Wrapper class for tasks that can internally retry operations until they succeed. + * + * Using this class will automatically add the task to the ThreadPoolExecutor. + */ +public class ResetableRealmAsyncTask extends RealmAsyncTaskImpl { + + private final ExponentialBackoffTask task; + + public ResetableRealmAsyncTask(ExponentialBackoffTask pendingTask, ThreadPoolExecutor service) { + super(service.submit(pendingTask) , service); + this.task = pendingTask; + } + + /** + * If this task is currently waiting to retry. Calling this method will reset it and make it + * retry again immediately. + */ + public void resetTask() { + task.resetDelay(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index 14b205cc06..e872157735 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -147,7 +147,7 @@ private AuthenticateResponse(String serverResponse) { error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, exceptionMessage, ex); debugMessage = String.format(Locale.US, "Error %s", error.getErrorMessage()); } - RealmLog.debug("AuthenticateResponse. " + debugMessage); + RealmLog.debug("AuthenticateResponse: %s", debugMessage); setError(error); this.accessToken = accessToken; this.refreshToken = refreshToken; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java index f23dc5022b..92f8691415 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java @@ -26,6 +26,8 @@ */ public abstract class ExponentialBackoffTask implements Runnable { private final int maxRetries; + private final Object sleepLock = new Object(); // Used to block the thread but still allow other thread to resume it. + private int attempt = 0; // Number of failed attempts previously made by this task. public ExponentialBackoffTask(int maxRetries) { this.maxRetries = maxRetries; @@ -62,17 +64,33 @@ protected boolean shouldAbortTask(T response) { // Callback when task has failed protected abstract void onError(T response); + // Returns the name of this task + protected abstract String getName(); + + /** + * Resets any exponential delays and retry the task immediately. + */ + public void resetDelay() { + synchronized (sleepLock) { + RealmLog.debug(getName() + " Reset delay for task."); + attempt = 0; + sleepLock.notify(); + } + } + @Override public void run() { - int attempt = 0; while (!Thread.interrupted()) { attempt++; long sleep = calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); if (sleep > 0) { + RealmLog.debug(getName() + " Delaying for " + TimeUnit.SECONDS.convert(sleep, TimeUnit.MILLISECONDS) + " sec."); try { - Thread.sleep(sleep); + synchronized (sleepLock) { + sleepLock.wait(sleep); + } } catch (InterruptedException e) { - RealmLog.debug("Incremental backoff was interrupted."); + RealmLog.debug(getName() + " Incremental backoff was interrupted."); return; // Abort if interrupted } } @@ -93,23 +111,23 @@ public void run() { private static long calculateExponentialDelay(int failedAttempts, long maxDelayInMs) { // https://en.wikipedia.org/wiki/Exponential_backoff //Attempt = FailedAttempts + 1 - //Attempt 1 0s 0s - //Attempt 2 2s 2s - //Attempt 3 4s 4s - //Attempt 4 8s 8s - //Attempt 5 16s 16s - //Attempt 6 32s 32s - //Attempt 7 64s 1m 4s - //Attempt 8 128s 2m 8s - //Attempt 9 256s 4m 16s - //Attempt 10 512 8m 32s - //Attempt 11 1024 17m 4s - //Attempt 12 2048 34m 8s - //Attempt 13 4096 1h 8m 16s - //Attempt 14 8192 2h 16m 32s - //Attempt 15 16384 4h 33m 4s + //Attempt 1 0 0s + //Attempt 2 1 1s + //Attempt 3 3 3s + //Attempt 4 7 7s + //Attempt 5 15 15s + //Attempt 6 31 31s + //Attempt 7 63 1m 3s + //Attempt 8 127 2m 7s + //Attempt 9 255 4m 15s + //Attempt 10 511 8m 31s + //Attempt 11 1023 17m 3s + //Attempt 12 2047 34m 7s + //Attempt 13 4095 1h 8m 15s + //Attempt 14 8191 2h 16m 31s + //Attempt 15 16383 4h 33m 3s double SCALE = 1.0D; // Scale the exponential backoff - double delayInMs = ((Math.pow(2.0D, failedAttempts) - 1d) / 2.0D) * 1000 * SCALE; + double delayInMs = (Math.pow(2.0D, failedAttempts) - 1.0D) * 1000.D * SCALE; // Just use maximum back-off value. We are not afraid of many threads using this value // to trigger at once. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 5995ae7beb..0f87375628 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -554,4 +554,29 @@ public void onChange(Progress progress) { } }); } + + // Smoke test to check that `refreshConnections` doesn't crash. + // Testing that it actually works is not feasible in a unit test. + @Test + @RunTestInLooperThread + public void refreshConnections() { + RealmLog.setLevel(LogLevel.DEBUG); + SyncManager.refreshConnections(); // No Realms + + // A single active Realm + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() + .schema(StringOnly.class) + .build(); + Realm realm = Realm.getInstance(config); + SyncManager.refreshConnections(); + + // A single logged out Realm + realm.close(); + SyncManager.refreshConnections(); + looperThread.testComplete(); + } } diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java index 8a7c228a5c..19101582fa 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java @@ -94,8 +94,11 @@ private static void deleteRosFiles() throws IOException { private static void deleteFile(File file) throws IOException { if (file.isDirectory()) { - for (File c : file.listFiles()) { - deleteFile(c); + File[] files = file.listFiles(); + if (files != null) { + for (File c : files) { + deleteFile(c); + } } } if (!file.delete()) { diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java index d39ae9dd24..504bffd43b 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java @@ -18,7 +18,7 @@ public class Constants { - public static String HOST = "127.0.0.1"; + public static final String HOST = "127.0.0.1"; public static final String USER_REALM = "realm://" + HOST + ":9080/~/tests"; public static final String USER_REALM_2 = "realm://" + HOST + ":9080/~/tests2"; public static final String GLOBAL_REALM = "realm://" + HOST + ":9080/tests"; diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 2c3fdeedd9..a1772a2f4d 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -47,6 +47,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.AnnotationIndexTypes; import io.realm.entities.BacklinksSource; @@ -146,12 +149,7 @@ public static long addRowWithValues(Table table, long[] columnKeys, Object[] val colTypes[i] = colType; if (!colType.isValid(value)) { // String representation of the provided value type. - String providedType; - if (value == null) { - providedType = "null"; - } else { - providedType = value.getClass().toString(); - } + String providedType = value.getClass().toString(); throw new IllegalArgumentException("Invalid argument no " + (i + 1) + ". Expected a value compatible with column type " + colType + ", but got " + providedType + "."); @@ -275,7 +273,7 @@ public interface AdditionalTableSetup { void execute(Table table); } - public static Table createTable(OsSharedRealm sharedRealm, String name, AdditionalTableSetup additionalSetup) { + public static Table createTable(OsSharedRealm sharedRealm, String name, @Nullable AdditionalTableSetup additionalSetup) { boolean wasInTransaction = sharedRealm.isInTransaction(); if (!wasInTransaction) { sharedRealm.beginTransaction(); @@ -465,7 +463,7 @@ public static RealmConfiguration createConfiguration(File folder, String name) { * @deprecated Use {@link TestRealmConfigurationFactory#createConfiguration(String, byte[])} instead. */ @Deprecated - public static RealmConfiguration createConfiguration(Context context, String name, byte[] key) { + public static RealmConfiguration createConfiguration(Context context, String name, @Nullable byte[] key) { return createConfiguration(context.getFilesDir(), name, key); } @@ -473,7 +471,7 @@ public static RealmConfiguration createConfiguration(Context context, String nam * @deprecated Use {@link TestRealmConfigurationFactory#createConfiguration(String, byte[])} instead. */ @Deprecated - public static RealmConfiguration createConfiguration(File dir, String name, byte[] key) { + public static RealmConfiguration createConfiguration(File dir, String name, @Nullable byte[] key) { RealmConfiguration.Builder config = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) .directory(dir) .name(name); @@ -1177,9 +1175,13 @@ public static void deleteRecursively(File file) { if (!file.exists()) { return; } + if (file.isDirectory()) { - for (File f : file.listFiles()) { - deleteRecursively(f); + File[] files = file.listFiles(); + for (File f : files) { + if (f != null) { + deleteRecursively(f); + } } } @@ -1202,7 +1204,12 @@ public static boolean isSelinuxEnforcing() { final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8)); //noinspection TryFinallyCanBeTryWithResources try { - return reader.readLine().toLowerCase(Locale.ENGLISH).equals("enforcing"); + String line = reader.readLine(); + if (line != null) { + return line.toLowerCase(Locale.ENGLISH).equals("enforcing"); + } else { + return false; + } } finally { try { reader.close(); @@ -1299,4 +1306,10 @@ public static void waitForNetworkThreadExecutorToFinish() { } } + // Workaround to cheat Kotlins type system when testing interop with Java + @SuppressWarnings("TypeParameterUnusedInFormals") + public static T getNull() { + return null; + } + } diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java index 94662ecaae..0ef2c21952 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -99,8 +99,12 @@ protected void after() { public void create() throws IOException { super.create(); tempFolder = new File(super.getRoot(), testName); - tempFolder.delete(); - tempFolder.mkdir(); + if (tempFolder.exists() && !tempFolder.delete()) { + throw new IllegalStateException("Could not delete folder: " + tempFolder.getAbsolutePath()); + } + if (!tempFolder.mkdir()) { + throw new IllegalStateException("Could not create folder: " + tempFolder.getAbsolutePath()); + } } @Override diff --git a/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java index 57b0deec15..fa4c6acb56 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java +++ b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java @@ -43,9 +43,9 @@ public abstract class RemoteTestService extends Service { // There is no easy way to dynamically ensure step IDs have same value for different processes. So, use the stupid // way. - private static int BASE_MSG_ID = 0; - protected static int BASE_SIMPLE_COMMIT = BASE_MSG_ID; - protected static int BASE_A_LOT_COMMITS = BASE_SIMPLE_COMMIT + 100; + private static final int BASE_MSG_ID = 0; + protected static final int BASE_SIMPLE_COMMIT = BASE_MSG_ID; + protected static final int BASE_A_LOT_COMMITS = BASE_SIMPLE_COMMIT + 100; public static abstract class Step { public final int message; @@ -81,7 +81,7 @@ private void response(String error) { public static final String BUNDLE_KEY_ERROR = "error"; @SuppressLint("UseSparseArrays") private static Map stepMap = new HashMap(); - public static RemoteTestService thiz; + static RemoteTestService thiz; private final Messenger messenger = new Messenger(new IncomingHandler()); private Messenger client; private File rootFolder; @@ -104,10 +104,12 @@ public void onCreate() { } catch (IOException e) { RealmLog.error(e); } - //noinspection ResultOfMethodCallIgnored - rootFolder.delete(); - //noinspection ResultOfMethodCallIgnored - rootFolder.mkdir(); + if (rootFolder.exists() && !rootFolder.delete()) { + throw new IllegalStateException("Could not delete folder: " + rootFolder.getAbsolutePath()); + } + if (!rootFolder.mkdir()) { + throw new IllegalStateException("Could not create folder: " + rootFolder.getAbsolutePath()); + } Realm.init(getApplicationContext()); } @@ -167,8 +169,10 @@ private void recursiveDelete(File file) { recursiveDelete(each); } } - //noinspection ResultOfMethodCallIgnored - file.delete(); + + if (!file.delete()) { + throw new IllegalStateException("Could not delete file: " + file.getAbsolutePath()); + } } public Realm getRealm() { From 31b07c3faa5ee1bce3aa3b79a89a61704efa312e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 13 Aug 2020 23:36:33 +0200 Subject: [PATCH 1623/2110] Reenabling unit test example (#7026) --- .../secureTokenAndroidKeyStore/build.gradle | 43 ------- examples/secureTokenAndroidKeyStore/lint.xml | 5 - .../proguard-rules.pro | 17 --- .../src/main/AndroidManifest.xml | 16 --- .../MainActivity.java | 113 ------------------ .../MyApplication.java | 31 ----- .../src/main/res/layout/activity_main.xml | 18 --- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 4906 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2968 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 7076 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 11165 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 16078 -> 0 bytes .../src/main/res/values/colors.xml | 8 -- .../src/main/res/values/strings.xml | 5 - .../src/main/res/values/styles.xml | 11 -- examples/settings.gradle | 3 +- examples/unitTestExample/build.gradle | 13 +- examples/unitTestExample/gradle.properties | 1 + .../unittesting/jUnit3ExampleTest.java | 36 ------ .../unittesting/ExampleActivityTest.java | 2 +- .../unittesting/ExampleRealmTest.java | 2 +- 21 files changed, 13 insertions(+), 311 deletions(-) delete mode 100644 examples/secureTokenAndroidKeyStore/build.gradle delete mode 100644 examples/secureTokenAndroidKeyStore/lint.xml delete mode 100644 examples/secureTokenAndroidKeyStore/proguard-rules.pro delete mode 100644 examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml delete mode 100644 examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java delete mode 100644 examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MyApplication.java delete mode 100644 examples/secureTokenAndroidKeyStore/src/main/res/layout/activity_main.xml delete mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100755 examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 examples/secureTokenAndroidKeyStore/src/main/res/values/colors.xml delete mode 100644 examples/secureTokenAndroidKeyStore/src/main/res/values/strings.xml delete mode 100644 examples/secureTokenAndroidKeyStore/src/main/res/values/styles.xml create mode 100644 examples/unitTestExample/gradle.properties delete mode 100644 examples/unitTestExample/src/androidTest/java/io/realm/examples/unittesting/jUnit3ExampleTest.java diff --git a/examples/secureTokenAndroidKeyStore/build.gradle b/examples/secureTokenAndroidKeyStore/build.gradle deleted file mode 100644 index fd08fb3f9a..0000000000 --- a/examples/secureTokenAndroidKeyStore/build.gradle +++ /dev/null @@ -1,43 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'realm-android' - -android { - compileSdkVersion rootProject.sdkVersion - buildToolsVersion rootProject.buildTools - - defaultConfig { - applicationId "io.realm.examples.securetokenandroidkeystore" - minSdkVersion rootProject.minSdkVersion - targetSdkVersion rootProject.sdkVersion - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - - } - buildTypes { - release { - minifyEnabled true - signingConfig signingConfigs.debug - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - debug { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', { - exclude group: 'com.android.support', module: 'support-annotations' - }) - implementation 'com.android.support:appcompat-v7:27.1.1' - testImplementation 'junit:junit:4.12' - implementation 'io.realm:secure-userstore:1.0.1' -} - -realm { - syncEnabled = true -} diff --git a/examples/secureTokenAndroidKeyStore/lint.xml b/examples/secureTokenAndroidKeyStore/lint.xml deleted file mode 100644 index 5f242796c5..0000000000 --- a/examples/secureTokenAndroidKeyStore/lint.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/secureTokenAndroidKeyStore/proguard-rules.pro b/examples/secureTokenAndroidKeyStore/proguard-rules.pro deleted file mode 100644 index 740907a636..0000000000 --- a/examples/secureTokenAndroidKeyStore/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/Nabil/Library/Android/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml b/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml deleted file mode 100644 index 57be5d1083..0000000000 --- a/examples/secureTokenAndroidKeyStore/src/main/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java deleted file mode 100644 index 2763c44b44..0000000000 --- a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MainActivity.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.examples.securetokenandroidkeystore; - -import android.os.Bundle; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; -import android.widget.TextView; - -import com.example.securetokenandroidkeystore.R; - -import java.security.KeyStoreException; - -import io.realm.ObjectServerError; -import io.realm.Realm; -import io.realm.SyncConfiguration; -import io.realm.SyncCredentials; -import io.realm.SyncManager; -import io.realm.SyncUser; -import io.realm.android.SecureUserStore; - -/** - * Activity responsible of unlocking the KeyStore - * before using the {@link io.realm.android.SecureUserStore} to encrypt - * the Token we get from the session - */ -public class MainActivity extends AppCompatActivity { - private TextView txtKeystoreState; - - private SecureUserStore secureUserStore; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - txtKeystoreState = (TextView) findViewById(R.id.txtLabelKeyStore); - - try { - secureUserStore = new SecureUserStore(this); - SyncManager.setUserStore(secureUserStore); - - if (secureUserStore.isKeystoreUnlocked()) { - buildSyncConf(); - keystoreUnlockedMessage(); - } else { - secureUserStore.unlockKeystore(); - } - } catch (KeyStoreException e) { - e.printStackTrace(); - } - } - - @Override - protected void onResume() { - super.onResume(); - try { - // We return to the app after the KeyStore is unlocked or not. - if (secureUserStore.isKeystoreUnlocked()) { - buildSyncConf(); - keystoreUnlockedMessage(); - } else { - keystoreLockedMessage(); - } - } catch (KeyStoreException e) { - e.printStackTrace(); - } - } - - // build SyncConfiguration with a user store to store encrypted Token. - private void buildSyncConf() { - // the rest of Sync logic ... - SyncCredentials credentials = SyncCredentials.usernamePassword("username", "password"); - final String urlAuth = "http://objectserver.realm.io:9080/auth"; - final String url = "realm://objectserver.realm.io/default"; - - SyncUser.logInAsync(credentials, urlAuth, new SyncUser.Callback() { - @Override - public void onSuccess(SyncUser user) { - SyncConfiguration secureConfig = user.createConfiguration(url).build(); - Realm realm = Realm.getInstance(secureConfig); - // ... - } - - @Override - public void onError(ObjectServerError error) {} - }); - } - - private void keystoreLockedMessage() { - txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorLocked)); - txtKeystoreState.setText(R.string.locked_text); - } - - private void keystoreUnlockedMessage() { - txtKeystoreState.setBackgroundColor(ContextCompat.getColor(this, R.color.colorActivated)); - txtKeystoreState.setText(R.string.unlocked_text); - } -} - diff --git a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MyApplication.java b/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MyApplication.java deleted file mode 100644 index 8af665821a..0000000000 --- a/examples/secureTokenAndroidKeyStore/src/main/java/io/realm/examples/securetokenandroidkeystore/MyApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * 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. - */ - -package io.realm.examples.securetokenandroidkeystore; - -import android.app.Application; - -import io.realm.Realm; - -public class MyApplication extends Application { - - @Override - public void onCreate() { - super.onCreate(); - // Initialize Realm. Should only be done once when the application starts. - Realm.init(this); - } -} diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/layout/activity_main.xml b/examples/secureTokenAndroidKeyStore/src/main/res/layout/activity_main.xml deleted file mode 100644 index eff5fd5e7b..0000000000 --- a/examples/secureTokenAndroidKeyStore/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/secureTokenAndroidKeyStore/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index 58303aff5b97f3c5a1757573ef73347d3475e16c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4906 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 O0000 diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/secureTokenAndroidKeyStore/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index eb9ece04b26b69f1d98f9294716e8c982a4577b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11165 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 - - #3F51B5 - #303F9F - #FF4081 - #00CC00 - #CC0000 - diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/values/strings.xml b/examples/secureTokenAndroidKeyStore/src/main/res/values/strings.xml deleted file mode 100644 index d028dcda8a..0000000000 --- a/examples/secureTokenAndroidKeyStore/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - secureTokenAndroidKeyStore - Key Store Locked/Uninitialised:\nYou can not encrypt the Token - Key Store Unlocked:\nYou can encrypt the Token - diff --git a/examples/secureTokenAndroidKeyStore/src/main/res/values/styles.xml b/examples/secureTokenAndroidKeyStore/src/main/res/values/styles.xml deleted file mode 100644 index daa2a5c2f0..0000000000 --- a/examples/secureTokenAndroidKeyStore/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/examples/settings.gradle b/examples/settings.gradle index 6df1628f5e..d6a88d7c9b 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -10,8 +10,7 @@ include 'moduleExample:app' include 'moduleExample:library' include 'newsreaderExample' include 'rxJavaExample' -// FIXME include 'secureTokenAndroidKeyStore' include 'threadExample' -// FIXME include 'unitTestExample' Disable project because fixing it requires AndroidX +include 'unitTestExample' include 'mongoDbRealmExample' include 'multiprocessExample' diff --git a/examples/unitTestExample/build.gradle b/examples/unitTestExample/build.gradle index 9b6bb2c11d..6ea31155fe 100644 --- a/examples/unitTestExample/build.gradle +++ b/examples/unitTestExample/build.gradle @@ -46,7 +46,12 @@ dependencies { // Testing testImplementation 'junit:junit:4.12' - testImplementation "org.robolectric:robolectric:3.8" + // Seen quite many issues when bumping from 3.8 (due to AndroidX) + // 4.1 seems to be the only one not exibiting these. Some of the relevant links + // - https://github.com/robolectric/robolectric/issues/4015 + // - https://github.com/roObolectric/robolectric/issues/5082 + // - https://stackoverflow.com/questions/29072148/mockito-with-robolectric-classcastexception-occurred-when-creating-the-proxy/36720803 + testImplementation "org.robolectric:robolectric:4.1" testImplementation "org.mockito:mockito-core:1.10.19" testImplementation 'org.robolectric:shadows-support-v4:3.0' @@ -56,9 +61,9 @@ dependencies { testImplementation "org.powermock:powermock-classloading-xstream:1.6.5" - androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test:runner:1.0.2' // Set this dependency to use JUnit 4 rules - androidTestImplementation 'com.android.support.test:rules:1.0.1' + androidTestImplementation 'com.android.support.test:rules:1.0.2' // Set this dependency to build and run Espresso tests - androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } diff --git a/examples/unitTestExample/gradle.properties b/examples/unitTestExample/gradle.properties new file mode 100644 index 0000000000..5bac8ac504 --- /dev/null +++ b/examples/unitTestExample/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/examples/unitTestExample/src/androidTest/java/io/realm/examples/unittesting/jUnit3ExampleTest.java b/examples/unitTestExample/src/androidTest/java/io/realm/examples/unittesting/jUnit3ExampleTest.java deleted file mode 100644 index 37d27ded51..0000000000 --- a/examples/unitTestExample/src/androidTest/java/io/realm/examples/unittesting/jUnit3ExampleTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * 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. - */ - -package io.realm.examples.unittesting; - -import android.test.ActivityInstrumentationTestCase2; - -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withText; - -public class jUnit3ExampleTest extends ActivityInstrumentationTestCase2 { - - public jUnit3ExampleTest() { - super(ExampleActivity.class); - } - - public void testShouldBeAbleToLaunchActivityAndSeeRealmResults() { - getActivity(); - onView(withText("John Senior got older: 89")).check(matches(isDisplayed())); - } -} diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java index 6123c0784f..ecf3d72d63 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleActivityTest.java @@ -64,7 +64,7 @@ @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 21) +@Config(sdk = 21) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) @SuppressStaticInitializationFor("io.realm.internal.Util") @PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class}) diff --git a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java index 3f1b9c87d3..d389735289 100644 --- a/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java +++ b/examples/unitTestExample/src/test/java/io/realm/examples/unittesting/ExampleRealmTest.java @@ -46,7 +46,7 @@ @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 19) +@Config(sdk = 19) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) @SuppressStaticInitializationFor("io.realm.internal.Util") @PrepareForTest({Realm.class, RealmLog.class}) From 17b40680abe3be7c916edfab9164499451a250b6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 14 Aug 2020 08:19:26 +0200 Subject: [PATCH 1624/2110] Upgrade to latest Core --- CHANGELOG.md | 5 ++++- dependencies.list | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d8c07e5e..183cfc525a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Fixes * [ObjectServer] Calling `SyncManager.refreshConnections()` did not correctly refresh connections in all cases, which could delay reconnects up to 5 minutes. (Issue [#7003](https://github.com/realm/realm-java/issues/7003)) +* Upgrading the file format result did in some cases not work correctly. This could result in a number of crashes, e.g. `FORMAT_UPGRADE_REQUIRED`. (Issue [#6889](https://github.com/realm/realm-java/issues/6889), since 7.0.0) +* Bug in memory mapping management. This bug could result in multiple different asserts as well as segfaults. In many cases stack backtraces would include members of the EncyptedFileMapping near the top - even if encryption was not used at all. In other cases asserts or crashes would be in methods reading an array header or array element. In all cases the application would terminate immediately. (Issue [#3838](https://github.com/realm/realm-core/pull/3838), since 7.0.0) ### Compatibility * Realm Object Server: 3.23.1 or later. @@ -12,7 +14,8 @@ * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal -* None. +* Upgraded to Realm Sync 5.0.15. +* Upgraded to Realm Core 6.0.17. ## 7.0.1(2020-07-01) diff --git a/dependencies.list b/dependencies.list index b16e9b3738..afcf91f325 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.7 -REALM_SYNC_SHA256=239049c777e4275fe094c73ae6dfa8736ae5ca9aa821c8d58b6d5eb3d84415e0 +REALM_SYNC_VERSION=5.0.15 +REALM_SYNC_SHA256=f8d84ce3867e2d47bc6566bb9f9c35f725415f268f4136772beb4f5a041ac8d5 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. From 94fa3c8762bd55fc547ec888663475216e952ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 14 Aug 2020 08:29:36 +0200 Subject: [PATCH 1625/2110] Add tests for insert lists of Realm models containing embedded objects (#7016) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eduardo López Co-authored-by: Claus Rørbech --- .../kotlin/io/realm/EmbeddedObjectsTest.kt | 150 +++++++++++++++++- 1 file changed, 144 insertions(+), 6 deletions(-) diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt index 86ea265786..cdf883306c 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -19,6 +19,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.* import io.realm.entities.embedded.* +import io.realm.exceptions.RealmPrimaryKeyConstraintException import io.realm.kotlin.createEmbeddedObject import io.realm.kotlin.createObject import io.realm.kotlin.where @@ -527,21 +528,158 @@ class EmbeddedObjectsTest { } @Test - @Ignore("Add in another PR") fun insertOrUpdate_deletesOldEmbeddedObject() { - TODO() + realm.executeTransaction { realm -> + val parent = EmbeddedSimpleParent("parent") + val originalChild = EmbeddedSimpleChild("originalChild") + parent.child = originalChild + realm.insert(parent) + + assertEquals(1, realm.where().count()) + val managedChild = realm.where() + .equalTo("childId", "originalChild") + .findFirst() + assertTrue(managedChild!!.isValid) + + val newChild = EmbeddedSimpleChild("newChild") + parent.child = newChild + realm.insertOrUpdate(parent) + assertTrue(!managedChild.isValid) + + assertEquals(1, realm.where().count()) + val managedNewChild = realm.where() + .equalTo("childId", "newChild") + .findFirst() + assertEquals(managedNewChild!!.childId, "newChild") + assertEquals( + 0, + realm.where() + .equalTo("childId", "originalChild") + .findAll() + .size + ) + } } @Test - @Ignore("Add in another PR") fun insert_listWithEmbeddedObjects() { - TODO() + realm.executeTransaction { realm -> + val list = listOf( + EmbeddedSimpleParent("parent1").apply { child = EmbeddedSimpleChild("child1") }, + EmbeddedSimpleParent("parent2").apply { child = EmbeddedSimpleChild("child2") }, + EmbeddedSimpleParent("parent3").apply { child = EmbeddedSimpleChild("child3") } + ) + realm.insert(list) + + realm.where() + .findAll() + .sort("_id") + .also { results -> + assertEquals(3, results.count()) + assertEquals(list[0]._id, results[0]!!._id) + assertEquals(list[1]._id, results[1]!!._id) + assertEquals(list[2]._id, results[2]!!._id) + assertEquals(list[0].child!!.childId, results[0]!!.child!!.childId) + assertEquals(list[1].child!!.childId, results[1]!!.child!!.childId) + assertEquals(list[2].child!!.childId, results[2]!!.child!!.childId) + } + + realm.where() + .findAll() + .sort("childId") + .also { results -> + assertEquals(3, results.count()) + assertEquals(list[0].child!!.childId, results[0]!!.childId) + assertEquals(list[1].child!!.childId, results[1]!!.childId) + assertEquals(list[2].child!!.childId, results[2]!!.childId) + assertEquals(list[0]._id, results[0]!!.parent._id) + assertEquals(list[1]._id, results[1]!!.parent._id) + assertEquals(list[2]._id, results[2]!!.parent._id) + } + } + } + + @Test + fun insert_listWithEmbeddedObjects_duplicatePrimaryKeyThrows() { + realm.executeTransaction { realm -> + val list = listOf( + EmbeddedSimpleParent("parent1").apply { child = EmbeddedSimpleChild("child1") }, + EmbeddedSimpleParent("parent2").apply { child = EmbeddedSimpleChild("child2") }, + EmbeddedSimpleParent("parent3").apply { child = EmbeddedSimpleChild("child3") } + ) + realm.insert(list) + + assertFailsWith { + realm.insert(list) + } + } + } + + @Test + fun insert_listWithEmbeddedObjects_insertingChildrenDirectlyThrows() { + val list = listOf( + EmbeddedSimpleChild("child1"), + EmbeddedSimpleChild("child2"), + EmbeddedSimpleChild("child3") + ) + + realm.executeTransaction { realm -> + assertFailsWith { + realm.insert(list); + } + } } @Test - @Ignore("Add in another PR") fun insertOrUpdate_listWithEmbeddedObjects() { - TODO() + realm.executeTransaction { realm -> + val list = listOf( + EmbeddedSimpleParent("parent1").apply { child = EmbeddedSimpleChild("child1") }, + EmbeddedSimpleParent("parent2").apply { child = EmbeddedSimpleChild("child2") }, + EmbeddedSimpleParent("parent3").apply { child = EmbeddedSimpleChild("child3") } + ) + realm.insert(list) + + val newList = listOf( + EmbeddedSimpleParent("parent1").apply { child = EmbeddedSimpleChild("newChild1") }, + EmbeddedSimpleParent("parent2").apply { child = EmbeddedSimpleChild("newChild2") }, + EmbeddedSimpleParent("parent3").apply { child = EmbeddedSimpleChild("newChild3") } + ) + realm.insertOrUpdate(newList) + + assertNull(realm.where().equalTo("childId", list[0].child!!.childId).findFirst()) + assertNull(realm.where().equalTo("childId", list[1].child!!.childId).findFirst()) + assertNull(realm.where().equalTo("childId", list[2].child!!.childId).findFirst()) + assertNotNull(realm.where().equalTo("childId", newList[0].child!!.childId).findFirst()) + assertNotNull(realm.where().equalTo("childId", newList[1].child!!.childId).findFirst()) + assertNotNull(realm.where().equalTo("childId", newList[2].child!!.childId).findFirst()) + + val query = realm.where() + assertEquals(3, query.count()) + query.findAll() + .sort("_id") + .also { results -> + assertEquals(newList[0]._id, results[0]!!._id) + assertEquals(newList[1]._id, results[1]!!._id) + assertEquals(newList[2]._id, results[2]!!._id) + assertEquals(newList[0].child!!.childId, results[0]!!.child!!.childId) + assertEquals(newList[1].child!!.childId, results[1]!!.child!!.childId) + assertEquals(newList[2].child!!.childId, results[2]!!.child!!.childId) + } + + realm.where() + .also { assertEquals(3, it.count()) } + .findAll() + .sort("_id") + .also { results -> + assertEquals(newList[0]._id, results[0]!!._id) + assertEquals(newList[1]._id, results[1]!!._id) + assertEquals(newList[2]._id, results[2]!!._id) + assertEquals(newList[0].child!!.childId, results[0]!!.child!!.childId) + assertEquals(newList[1].child!!.childId, results[1]!!.child!!.childId) + assertEquals(newList[2].child!!.childId, results[2]!!.child!!.childId) + } + } } // TODO Move all json import tests to RealmJsonTests when RealmJsonTests have been From bd42f27d7306487a408b747a4fcc029e40125168 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 14 Aug 2020 12:04:38 +0200 Subject: [PATCH 1626/2110] Update release date for 7.0.2 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 183cfc525a..8e0037ca61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 7.0.2(YYYY-MM-DD) +## 7.0.2(2020-08-14) ### Enhancements * None. From b5e0a6a3dace3aae8e41584411957191cda56004 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 14 Aug 2020 12:06:35 +0200 Subject: [PATCH 1627/2110] Release v7.0.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 215d070d7b..2f963cd6d1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.2-SNAPSHOT \ No newline at end of file +7.0.2 \ No newline at end of file From 0d47e37f1da9b9e3d29cd2cae4f1a66541ceb860 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 14 Aug 2020 12:06:35 +0200 Subject: [PATCH 1628/2110] Prepare next release v7.0.3-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 2f963cd6d1..d4bcf7ada0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.2 \ No newline at end of file +7.0.3-SNAPSHOT \ No newline at end of file From 23b3a7b8652b4d48a1fd8d6066afb3cd1b4015f8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 17 Aug 2020 08:20:23 +0200 Subject: [PATCH 1629/2110] Upgrade to Sync 10.0.0-beta.6 (#7033) --- CHANGELOG.md | 4 ++++ dependencies.list | 4 ++-- .../androidTest/java/io/realm/RealmMigrationTests.java | 7 ++++--- .../androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt | 10 +++------- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 198fe3244e..b3d4cc5303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * [RealmApp] Sync would not refresh the access token if started with an expired one. (Since 10.0.0-BETA.1) * [RealmApp] Leaking objects when registering session listeners. (Issue [#6916](https://github.com/realm/realm-java/issues/6916)) * Added support for Json-import of objects containing embedded objects. (Issue [#6896](https://github.com/realm/realm-java/issues/6896)) +* Upgrading the file format result did in some cases not work correctly. This could result in a number of crashes, e.g. `FORMAT_UPGRADE_REQUIRED`. (Issue [#6889](https://github.com/realm/realm-java/issues/6889), since 7.0.0) +* Bug in memory mapping management. This bug could result in multiple different asserts as well as segfaults. In many cases stack backtraces would include members of the EncyptedFileMapping near the top - even if encryption was not used at all. In other cases asserts or crashes would be in methods reading an array header or array element. In all cases the application would terminate immediately. (Realm Core PR [#3838](https://github.com/realm/realm-core/pull/3838), since 7.0.0) ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). @@ -27,6 +29,8 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new ### Internal * Upgraded to Object Store commit: 5b5fb8a90192cb4ee6799e7465745cd2067f939b. +* Upgraded to Realm Sync 10.0.0-beta.6. +* Upgraded to Realm Core 10.0.0-beta.4. ## 10.0.0-BETA.5 (2020-06-19) diff --git a/dependencies.list b/dependencies.list index e772cdfc96..234fc9796b 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-beta.2 -REALM_SYNC_SHA256=0572f751ced210656ed7d567dce9e56b5cca19c3070096ff38c255c9585ccf0f +REALM_SYNC_VERSION=10.0.0-beta.6 +REALM_SYNC_SHA256=824192a67e7ded59d33707265f78c8d5546b1fef473e9ee5ecff8a5bc9f8506e # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 8ae2f20198..d0134ec776 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -1433,8 +1433,9 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { } } - // File format 9 (up to Core5) added an index automatically to the primary key, in Core6 (File format 10) string based PK are not - // indexed because the search index is derived from the ObjectKey. + // File format 9 (up to Core5) added an index automatically to the primary key, in Core6 + // (File format 10) string based PK are not indexed because the search index is derived from + // the ObjectKey. In this case the Core 5 index is now automatically removed. @Test public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException { configFactory.copyRealmFromAssets(context, @@ -1444,7 +1445,7 @@ public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException .schema(MigrationCore6PKStringIndexedByDefault.class) .build()); assertFalse(realm.isEmpty()); - assertTrue(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); + assertFalse(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); MigrationCore6PKStringIndexedByDefault first = realm.where(MigrationCore6PKStringIndexedByDefault.class).findFirst(); assertNotNull(first); assertEquals("Foo", first.name); diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt index cdf883306c..1bc759b603 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -896,13 +896,9 @@ class EmbeddedObjectsTest { // Create object with no parents realm.createObject(Dog.CLASS_NAME) val dogSchema = realm.schema[Dog.CLASS_NAME]!! - // Succeed by mistake right now. - // See https://github.com/realm/realm-core/issues/3729 - // The correct check is just below - dogSchema.isEmbedded = true - // assertFailsWith { - // dogSchema.isEmbedded = true - // } + assertFailsWith { + dogSchema.isEmbedded = true + } // Create object with two parents val cat: DynamicRealmObject = realm.createObject(Cat.CLASS_NAME) From ae576babb90534d5e2247f1208961954857d1530 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 17 Aug 2020 08:21:31 +0200 Subject: [PATCH 1630/2110] Prepare BETA-6 release --- CHANGELOG.md | 3 ++- version.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d4cc5303..84dc89d231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.0-BETA.6 (YYYY-MM-DD) +## 10.0.0-BETA.6 (2020-08-17) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. @@ -21,6 +21,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * Added support for Json-import of objects containing embedded objects. (Issue [#6896](https://github.com/realm/realm-java/issues/6896)) * Upgrading the file format result did in some cases not work correctly. This could result in a number of crashes, e.g. `FORMAT_UPGRADE_REQUIRED`. (Issue [#6889](https://github.com/realm/realm-java/issues/6889), since 7.0.0) * Bug in memory mapping management. This bug could result in multiple different asserts as well as segfaults. In many cases stack backtraces would include members of the EncyptedFileMapping near the top - even if encryption was not used at all. In other cases asserts or crashes would be in methods reading an array header or array element. In all cases the application would terminate immediately. (Realm Core PR [#3838](https://github.com/realm/realm-core/pull/3838), since 7.0.0) +* It was possible to use `RealmObjectSchema` to mark a Class as embedded even if some of the objects broke the constraints for being embedded. ### Compatibility * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). diff --git a/version.txt b/version.txt index b27ddb8fd2..7c0faa4333 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.6-SNAPSHOT +10.0.0-BETA.6 \ No newline at end of file From 2430dfe1211490d16aa0ee48ec21d6c585052bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 17 Aug 2020 09:31:45 +0200 Subject: [PATCH 1631/2110] Add compatibility project verifying builds without AndroidX/Java8 (#7029) --- examples/compatibilityExample/README.md | 5 ++ examples/compatibilityExample/build.gradle | 72 ++++++++++++++++++ .../compatibilityExample/gradle.properties | 2 + examples/compatibilityExample/lint.xml | 9 +++ .../src/main/AndroidManifest.xml | 21 +++++ .../examples/compatibility/MyActivity.kt | 11 +++ .../examples/compatibility/MyApplication.kt | 45 +++++++++++ .../ic_exit_to_app_white_24dp.png | Bin 0 -> 364 bytes .../ic_exit_to_app_white_24dp.png | Bin 0 -> 444 bytes .../src/main/res/drawable/logo.png | Bin 0 -> 16078 bytes .../main/res/layout/activity_my_activty.xml | 9 +++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4906 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2968 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7076 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11165 bytes .../src/main/res/values/dimens.xml | 5 ++ .../src/main/res/values/realm_colors.xml | 28 +++++++ .../src/main/res/values/strings.xml | 5 ++ .../src/main/res/values/styles.xml | 11 +++ examples/settings.gradle | 1 + 20 files changed, 224 insertions(+) create mode 100644 examples/compatibilityExample/README.md create mode 100644 examples/compatibilityExample/build.gradle create mode 100644 examples/compatibilityExample/gradle.properties create mode 100644 examples/compatibilityExample/lint.xml create mode 100644 examples/compatibilityExample/src/main/AndroidManifest.xml create mode 100644 examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyActivity.kt create mode 100644 examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyApplication.kt create mode 100644 examples/compatibilityExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png create mode 100644 examples/compatibilityExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png create mode 100755 examples/compatibilityExample/src/main/res/drawable/logo.png create mode 100644 examples/compatibilityExample/src/main/res/layout/activity_my_activty.xml create mode 100755 examples/compatibilityExample/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100755 examples/compatibilityExample/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100755 examples/compatibilityExample/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100755 examples/compatibilityExample/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 examples/compatibilityExample/src/main/res/values/dimens.xml create mode 100644 examples/compatibilityExample/src/main/res/values/realm_colors.xml create mode 100644 examples/compatibilityExample/src/main/res/values/strings.xml create mode 100644 examples/compatibilityExample/src/main/res/values/styles.xml diff --git a/examples/compatibilityExample/README.md b/examples/compatibilityExample/README.md new file mode 100644 index 0000000000..23925e91d4 --- /dev/null +++ b/examples/compatibilityExample/README.md @@ -0,0 +1,5 @@ +# Using this example + +This example is not really meant for use, but only as a compatibility check to ensure that we can +build projects without AndroidX and Java 8 features. + diff --git a/examples/compatibilityExample/build.gradle b/examples/compatibilityExample/build.gradle new file mode 100644 index 0000000000..46f231ed3a --- /dev/null +++ b/examples/compatibilityExample/build.gradle @@ -0,0 +1,72 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'realm-android' + +android { + compileSdkVersion rootProject.sdkVersion + buildToolsVersion rootProject.buildTools + + defaultConfig { + applicationId 'io.realm.examples.compatibility' + targetSdkVersion rootProject.sdkVersion + minSdkVersion rootProject.minSdkVersion + versionCode 1 + versionName "1.0" + } + + buildTypes { + // Configure server and App Id. + // The default server is https://realm-dev.mongodb.com/ . Go to that and copy the MongoDB + // Realm App Id. + // + // If you are running a local version of MongoDB Realm, modify endpoint accordingly. Most + // likely it is "http://localhost:9090" + def mongodbRealmUrl = "https://realm-dev.mongodb.com" + def appId = "my-app-id" + debug { + buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" + buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\"" + } + release { + buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" + buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\"" + minifyEnabled true + signingConfig signingConfigs.debug + } + } + + // Ensure that we can compile an app without Java 8 features + compileOptions { + sourceCompatibility 1.7 + targetCompatibility 1.7 + } +} + +realm { + syncEnabled = true +} + +dependencies { + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:design:27.1.1' + implementation 'me.zhanghai.android.materialprogressbar:library:1.3.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support.constraint:constraint-layout:1.1.3' +} + +if ((project.findProperty("android.useAndroidX") ?: false).toBoolean()) + throw new RuntimeException("Compatibility project should run without AndroidX") diff --git a/examples/compatibilityExample/gradle.properties b/examples/compatibilityExample/gradle.properties new file mode 100644 index 0000000000..3b465e0263 --- /dev/null +++ b/examples/compatibilityExample/gradle.properties @@ -0,0 +1,2 @@ +# Ensure that we do not use AndroidX for this project +android.useAndroidX=false diff --git a/examples/compatibilityExample/lint.xml b/examples/compatibilityExample/lint.xml new file mode 100644 index 0000000000..da1621f226 --- /dev/null +++ b/examples/compatibilityExample/lint.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/compatibilityExample/src/main/AndroidManifest.xml b/examples/compatibilityExample/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..dcda1e99a9 --- /dev/null +++ b/examples/compatibilityExample/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyActivity.kt b/examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyActivity.kt new file mode 100644 index 0000000000..8eee9d5c2e --- /dev/null +++ b/examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyActivity.kt @@ -0,0 +1,11 @@ +package io.realm.examples.compatibility + +import android.support.v7.app.AppCompatActivity +import android.os.Bundle + +class MyActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_my_activty) + } +} diff --git a/examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyApplication.kt b/examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyApplication.kt new file mode 100644 index 0000000000..8e8f4276ed --- /dev/null +++ b/examples/compatibilityExample/src/main/java/io/realm/examples/compatibility/MyApplication.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2019 Realm Inc. + * + * 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. + */ + +package io.realm.examples.compatibility + +import android.app.Application + +import io.realm.Realm +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.mongodb.App +import io.realm.mongodb.AppConfiguration + +lateinit var APP: App + +class MyApplication : Application() { + + override fun onCreate() { + super.onCreate() + Realm.init(this) + APP = App(AppConfiguration.Builder(BuildConfig.MONGODB_REALM_APP_ID) + .baseUrl(BuildConfig.MONGODB_REALM_URL) + .appName(BuildConfig.VERSION_NAME) + .appVersion(BuildConfig.VERSION_CODE.toString()) + .build()) + + // Enable more logging in debug mode + if (BuildConfig.DEBUG) { + RealmLog.setLevel(LogLevel.DEBUG) + } + } +} diff --git a/examples/compatibilityExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png b/examples/compatibilityExample/src/main/res/drawable-xxhdpi/ic_exit_to_app_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c04fe6e0e39ff126795317e64eeb057ec5f628b5 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXoKNz-a90;uuoF`1Yow*Wm;a*N5g_ zG0jR#*7>yvl`e>gzB?;8+OKMqZbdnPCS+0Q*8dhG2_$nP9Lq)n|fwV zOW!whv8{egkZ`-0%q>mX;_HQrnzF29_6BDz5DQ<+cI#2r^2WLX(~Jd$hKG--YB-(} zy7Iv#m@mVT|BFd#gIEnChYI(LH~q>Jd=?ZlG^XrHF}Lz$_r1N=VN+9+bz=%Et2T(p z1`*OAqF?Fd{RNzyD$E`Vk&ad?YXagn&0BV3{$BYn0oQs9gRd=4j=gsH>%KLQk9$3k z=-Y7V;Tis28gD1uzi>OgnPt*<*FdFS%b)3ozW|-Wd29N&%!i`k(Yz(StCzA_JrZ`$ z5Ba5jj-UDYi~vW+v!d2)&oUBDPwEPA_ysrZgQu&X%Q~lo FCIHRMl<)um literal 0 HcmV?d00001 diff --git a/examples/compatibilityExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png b/examples/compatibilityExample/src/main/res/drawable-xxxhdpi/ic_exit_to_app_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..27a9d7b05ab3990cc2476d892407323546b4b1bf GIT binary patch literal 444 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 + + + diff --git a/examples/compatibilityExample/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/compatibilityExample/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..58303aff5b97f3c5a1757573ef73347d3475e16c GIT binary patch literal 4906 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 O0000 literal 0 HcmV?d00001 diff --git a/examples/compatibilityExample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/compatibilityExample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..eb9ece04b26b69f1d98f9294716e8c982a4577b9 GIT binary patch literal 11165 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 vL;xs~^hI0s(5?>@cEQ$g3}{%|s>uHXFw5o4 + + 16dp + 16dp + diff --git a/examples/compatibilityExample/src/main/res/values/realm_colors.xml b/examples/compatibilityExample/src/main/res/values/realm_colors.xml new file mode 100644 index 0000000000..3d435a5c44 --- /dev/null +++ b/examples/compatibilityExample/src/main/res/values/realm_colors.xml @@ -0,0 +1,28 @@ + + + + #1C233F + #9A9BA5 + #b1b3bf + #EBEBF2 + + + #39477F + #59569E + #9A59A5 + #D34CA3 + #F25192 + #F77C88 + #FC9F95 + #FCC397 + + + #d64881 + #dadada + + + #EF5350 + #9CCC65 + #FFA726 + + \ No newline at end of file diff --git a/examples/compatibilityExample/src/main/res/values/strings.xml b/examples/compatibilityExample/src/main/res/values/strings.xml new file mode 100644 index 0000000000..f54b795456 --- /dev/null +++ b/examples/compatibilityExample/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Realm Compatibility Example + Realm Logo + diff --git a/examples/compatibilityExample/src/main/res/values/styles.xml b/examples/compatibilityExample/src/main/res/values/styles.xml new file mode 100644 index 0000000000..333a4944af --- /dev/null +++ b/examples/compatibilityExample/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/examples/settings.gradle b/examples/settings.gradle index d6a88d7c9b..84ffd810ab 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -14,3 +14,4 @@ include 'threadExample' include 'unitTestExample' include 'mongoDbRealmExample' include 'multiprocessExample' +include 'compatibilityExample' From 2e7207477f67ecb3fb37a4dd363f5d7c45905bea Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 17 Aug 2020 09:37:53 +0200 Subject: [PATCH 1632/2110] Prepare next dev release --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7c0faa4333..879aafef9e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.6 \ No newline at end of file +10.0.0-BETA.7-SNAPSHOT \ No newline at end of file From f75bba8eb3f02c98133feda5393d909a249c02f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 17 Aug 2020 12:07:49 +0200 Subject: [PATCH 1633/2110] Ensure alignment between test app and server admin interface (#7035) --- .../kotlin/io/realm/ApiKeyAuthTests.kt | 2 +- .../kotlin/io/realm/AppTests.kt | 2 +- .../kotlin/io/realm/CredentialsTests.kt | 2 +- .../kotlin/io/realm/EmailPasswordAuthTests.kt | 2 +- .../kotlin/io/realm/FunctionsTests.kt | 2 +- .../kotlin/io/realm/UserTests.kt | 2 +- .../kotlin/io/realm/admin/ServerAdmin.kt | 17 +++++++++++++---- .../internal/network/LoggingInterceptorTest.kt | 4 ++-- .../kotlin/io/realm/mongodb/AppExt.kt | 2 +- .../kotlin/io/realm/SyncSessionTests.kt | 2 +- 10 files changed, 23 insertions(+), 14 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt index 41da93fe9c..b04edc1f80 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt @@ -70,8 +70,8 @@ class ApiKeyAuthTests { @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - admin = ServerAdmin() app = TestApp() + admin = ServerAdmin(app) user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") provider = user.apiKeyAuth } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index 583c032cc8..145cdd4cce 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -44,7 +44,7 @@ class AppTests { fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) app = TestApp() - admin = ServerAdmin() + admin = ServerAdmin(app) } @After diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index 0e81663c21..951ac5d750 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -176,7 +176,7 @@ class CredentialsTests { @Test fun loginUsingCredentials() { app = TestApp() - admin = ServerAdmin() + admin = ServerAdmin(app) Credentials.IdentityProvider.values().forEach { provider -> when (provider) { Credentials.IdentityProvider.ANONYMOUS -> { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index 8485a2784a..982ca91cd5 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -66,7 +66,7 @@ class EmailPasswordAuthTests { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) app = TestApp() RealmLog.setLevel(LogLevel.DEBUG) - admin = ServerAdmin() + admin = ServerAdmin(app) admin.deleteAllUsers() } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt index 1b2ce4e44d..bfa750aedc 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt @@ -108,7 +108,7 @@ class FunctionsTests { fun setup() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) app = TestApp() - admin = ServerAdmin() + admin = ServerAdmin(app) anonUser = app.login(Credentials.anonymous()) functions = anonUser.functions } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt index c4ab7aa566..cfa1df182e 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt @@ -48,7 +48,7 @@ class UserTests { fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) app = TestApp() - admin = ServerAdmin() + admin = ServerAdmin(app) anonUser = app.login(Credentials.anonymous()) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt index a22f9ecf7d..e18c874851 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt @@ -2,6 +2,7 @@ package io.realm.admin import io.realm.log.LogLevel import io.realm.log.RealmLog +import io.realm.mongodb.App import io.realm.mongodb.User import okhttp3.* import okio.Buffer @@ -13,7 +14,7 @@ import java.util.concurrent.TimeUnit /** * Wrapper around MongoDB Realm Server Admin functions needed for tests. */ -class ServerAdmin { +class ServerAdmin(private val app: App) { private lateinit var accessToken: String private lateinit var groupId: String @@ -80,10 +81,18 @@ class ServerAdmin { result = JSONObject(executeRequest(builder)) groupId = (result.getJSONArray("roles")[0] as JSONObject).getString("group_id") - // Get Internal App Id + // Get Internal App Id of the requested app + val appId = this.app.configuration.appId builder = Request.Builder().url("$baseUrl/groups/$groupId/apps").get() - result = JSONArray(executeRequest(builder))[0] as JSONObject - appId = result.getString("_id") + val response = JSONArray(executeRequest(builder)) + for (i in 0 until response.length()) { + val appObject = response[i] as JSONObject + if (appObject.getString("client_app_id") == appId) { + this.appId = appObject.getString("_id") + return + } + } + throw IllegalArgumentException("Could not find app: $") } /** diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt index 19c50497b5..f93a450d59 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt @@ -87,7 +87,7 @@ class LoggingInterceptorTest { fun apiKeyLogin_noObfuscation() { app = TestApp() testLogger = getLogger() - val admin = ServerAdmin() + val admin = ServerAdmin(app) val serverKey = admin.createServerApiKey() app.login(Credentials.serverApiKey(serverKey)) @@ -100,7 +100,7 @@ class LoggingInterceptorTest { builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) } testLogger = getLogger() - val admin = ServerAdmin() + val admin = ServerAdmin(app) val serverKey = admin.createServerApiKey() app.login(Credentials.serverApiKey(serverKey)) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt index f73cef3e9c..e40d026a71 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt @@ -27,7 +27,7 @@ import io.realm.testClearApplicationContext * behavior. */ fun App.close() { - ServerAdmin().deleteAllUsers() + ServerAdmin(this).deleteAllUsers() this.syncManager.testReset() this.networkTransport.resetHeaders() App.CREATED = false diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index a4055786b7..a87f013747 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -96,8 +96,8 @@ class SyncSessionTests { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) RealmLog.setLevel(LogLevel.ALL) - admin = ServerAdmin() app = TestApp() + admin = ServerAdmin(app) user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) syncConfiguration = configFactory // TODO We generate new partition value for each test to avoid overlaps in data. We From d830bc0c09ef8d3e8d73728721f4bd29c7721451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Tue, 18 Aug 2020 15:29:50 +0200 Subject: [PATCH 1634/2110] Revisit MongoCollection tests for findOneAndModify operations (#7037) --- dependencies.list | 2 +- .../io/realm/mongodb/MongoClientTest.kt | 163 ++++++++++-------- ...internal_objectstore_OsMongoCollection.cpp | 2 +- .../realm/EncryptedSynchronizedRealmTests.kt | 23 +-- .../kotlin/io/realm/SyncSessionTests.kt | 67 ++++--- tools/sync_test_server/start_server.sh | 5 +- 6 files changed, 151 insertions(+), 111 deletions(-) diff --git a/dependencies.list b/dependencies.list index 5ad42b8227..d3ef2187a1 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=824192a67e7ded59d33707265f78c8d5546b1fef473e9ee5ecff8a5bc9f850 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-06-29 +MONGODB_REALM_SERVER_VERSION=2020-08-17 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt index 6d9719e701..584f03c83b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt @@ -32,7 +32,6 @@ import org.bson.codecs.configuration.CodecRegistries import org.bson.types.ObjectId import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import kotlin.test.* @@ -713,35 +712,42 @@ class MongoClientTest { } } - // FIXME: projections and sorts aren't currently working due to a bug in Stitch: https://jira.mongodb.org/browse/REALMC-5787 @Test - @Ignore("Projections and sorts don't work") fun findOneAndUpdate_withProjectionAndSort() { with(getCollectionInternal()) { - val sampleUpdate = Document("\$set", Document("hello", "hellothere")).apply { - this["\$inc"] = Document("num", 1) - } - sampleUpdate.remove("\$set") // FIXME - val sampleProject = Document("hello", 1) - sampleProject["_id"] = 0 - - var options = FindOneAndModifyOptions() - .projection(sampleProject) - .sort(Document("num", 1)) - assertEquals(Document("hello", "world1"), - findOneAndUpdate(Document(), sampleUpdate, options) - .get()!! - .withoutId()) - assertEquals(3, count().get()) - - options = FindOneAndModifyOptions() - .projection(sampleProject) - .sort(Document("num", -1)) - assertEquals(Document("hello", "world3"), - findOneAndUpdate(Document(), sampleUpdate, options) - .get()!! - .withoutId()) - assertEquals(3, count().get()) + insertMany(listOf( + Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), + Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), + Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), + Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), + Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) + )).get() + + assertEquals(5, count().get()) + assertNotNull(findOne(Document("team", "Cuddly Zebras"))) + + // Project: team, hide _id; Sort: score ascending + val project = Document(mapOf(Pair("_id", 0), Pair("team", 1), Pair("score", 1))) + val sort = Document("score", 1) + + // This results in the update of Cuddly Zebras + val updatedDocument = findOneAndUpdate( + Document("score", Document("\$lt", 22250)), + Document("\$inc", Document("score", 1)), + FindOneAndModifyOptions() + .projection(project) + .sort(sort) + ).get() + + assertEquals(5, count().get()) + assertEquals( + Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), + updatedDocument + ) + assertEquals( + Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235 + 1))), + findOne(Document("team", "Cuddly Zebras")).get().withoutId() + ) } } @@ -838,28 +844,46 @@ class MongoClientTest { } } - // FIXME: projections and sorts aren't currently working due to a bug in Stitch: https://jira.mongodb.org/browse/REALMC-5787 @Test - @Ignore("Projections and sorts don't work") fun findOneAndReplace_withProjectionAndSort() { with(getCollectionInternal()) { - val sampleProject = Document("hello", 1) - sampleProject["_id"] = 0 - - val sampleUpdate = Document("hello", "world0") - sampleUpdate["num"] = 0 - - var options = FindOneAndModifyOptions().projection(sampleProject).sort(Document("num", 1)) - val result = findOneAndReplace(Document(), sampleUpdate, options).get() - assertEquals(Document("hello", "world4"), result!!.withoutId()) - assertEquals(3, count().get()) - - options = FindOneAndModifyOptions() - .projection(sampleProject) - .sort(Document("num", -1)) - assertEquals(Document("hello", "world6"), - findOneAndReplace(Document(), sampleUpdate, options).get()!!.withoutId()) - assertEquals(3, count().get()) + insertMany(listOf( + Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), + Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), + Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), + Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), + Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) + )).get() + + assertEquals(5, count().get()) + assertNotNull(findOne(Document("team", "Cuddly Zebras"))) + + // Project: team, hide _id; Sort: score ascending + val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) + val sort = Document("score", 1) + + // This results in the replacement of Cuddly Zebras + val replacedDocument = findOneAndReplace( + Document("score", Document("\$lt", 22250)), + Document(mapOf(Pair("team", "Therapeutic Hamsters"), Pair("score", 22250))), + FindOneAndModifyOptions() + .projection(project) + .sort(sort) + ).get() + + assertEquals(5, count().get()) + assertEquals(Document("team", "Cuddly Zebras"), replacedDocument) + assertNull(findOne(Document("team", "Cuddly Zebras")).get()) + assertNotNull(findOne(Document("team", "Therapeutic Hamsters")).get()) + + // Check returnNewDocument + val newDocument = findOneAndReplace( + Document("score", 22250), + Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), + FindOneAndModifyOptions().returnNewDocument(true) + ).get() + + assertEquals(Document(mapOf(Pair("team", "New Therapeutic Hamsters"), Pair("score", 30000))), newDocument.withoutId()) } } @@ -924,32 +948,35 @@ class MongoClientTest { } } - // FIXME: projections and sorts aren't currently working due to a bug in Stitch: https://jira.mongodb.org/browse/REALMC-5787 @Test - @Ignore("find_one_and_delete function is wrongly implemented in OS and projections and sorts don't work") fun findOneAndDelete_withProjectionAndSort() { with(getCollectionInternal()) { - val doc2 = Document("hello", "world2").apply { this["num"] = 2 } - val doc3 = Document("hello", "world3").apply { this["num"] = 3 } - - insertMany(listOf(doc2, doc3)).get() + insertMany(listOf( + Document(mapOf(Pair("team", "Fearful Mallards"), Pair("score", 25000))), + Document(mapOf(Pair("team", "Tactful Mooses"), Pair("score", 23500))), + Document(mapOf(Pair("team", "Aquatic Ponies"), Pair("score", 19250))), + Document(mapOf(Pair("team", "Cuddly Zebras"), Pair("score", 15235))), + Document(mapOf(Pair("team", "Garrulous Bears"), Pair("score", 18000))) + )).get() + + assertEquals(5, count().get()) + assertNotNull(findOne(Document("team", "Cuddly Zebras"))) + + // Project: team, hide _id; Sort: score ascending + val project = Document(mapOf(Pair("_id", 0), Pair("team", 1))) + val sort = Document("score", 1) + + // This results in the deletion of Cuddly Zebras + val deletedDocument = findOneAndDelete( + Document("score", Document("\$lt", 22250)), + FindOneAndModifyOptions() + .projection(project) + .sort(sort) + ).get() - // Return "hello", hide "_id" - val sampleProject = Document("hello", 1).apply { this["_id"] = 0 } - - var options = FindOneAndModifyOptions() - .projection(sampleProject) - .sort(Document("num", -1)) - assertEquals(Document("hello", "world3"), - findOneAndDelete(Document(), options).get()!!.withoutId()) - assertEquals(2, count().get()) - - options = FindOneAndModifyOptions() - .projection(sampleProject) - .sort(Document("num", 1)) - assertEquals(Document("hello", "world1"), - findOneAndDelete(Document(), options).get()!!.withoutId()) - assertEquals(1, count().get()) + assertEquals(4, count().get()) + assertEquals(Document("team", "Cuddly Zebras"), deletedDocument.withoutId()) + assertNull(findOne(Document("team", "Cuddly Zebras")).get()) } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp index b91f8ed490..217ec621cf 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp @@ -74,7 +74,7 @@ static std::function bson_vector = { matched_count, modified_count, upserted_value }; diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt index 40442066f3..6e3fe4cbd2 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt @@ -19,21 +19,20 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.SyncStringOnly import io.realm.exceptions.RealmFileException import io.realm.kotlin.syncSession -import io.realm.log.LogLevel -import io.realm.log.RealmLog import io.realm.mongodb.App import io.realm.mongodb.Credentials import io.realm.mongodb.close import io.realm.mongodb.registerUserAndLogin import io.realm.mongodb.sync.SyncConfiguration import io.realm.mongodb.sync.testSchema -import org.bson.BsonObjectId +import org.bson.BsonString import org.bson.types.ObjectId import org.junit.After -import org.junit.Assert import org.junit.Assert.* import org.junit.Before +import org.junit.Ignore import org.junit.Test +import java.util.* import kotlin.test.assertFailsWith private val SECRET_PASSWORD = "123456" @@ -63,9 +62,9 @@ class EncryptedSynchronizedRealmTests { fun setEncryptionKey_canReOpenRealmWithoutKey() { // STEP 1: open a synced Realm using a local encryption key - var user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val randomKey = TestHelper.getRandomKey() - val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonObjectId()) + val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonString(UUID.randomUUID().toString())) .testSchema(SyncStringOnly::class.java) .waitForInitialRemoteData() .errorHandler { session, error -> fail(error.getErrorMessage()) } @@ -85,7 +84,7 @@ class EncryptedSynchronizedRealmTests { // STEP 3: try to open again the same sync Realm but different local name without the encryption key should not // fail - var user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val configWithoutEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, configWithEncryption.partitionValue) // Using different user with same partition value to trigger a different path instead of // .name("newName") @@ -102,13 +101,15 @@ class EncryptedSynchronizedRealmTests { user.logOut() } + // FIXME: ignore until https://github.com/realm/realm-java/issues/7028 is fixed // If an encrypted synced Realm is re-opened with the wrong key, throw an exception. @Test + @Ignore("Crashes at random - https://github.com/realm/realm-java/issues/7028") fun setEncryptionKey_shouldCrashIfKeyNotProvided() { // STEP 1: open a synced Realm using a local encryption key var user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val randomKey = TestHelper.getRandomKey() - val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonObjectId()) + val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonString(UUID.randomUUID().toString())) .testSchema(SyncStringOnly::class.java) .waitForInitialRemoteData() .errorHandler { session, error -> fail(error.getErrorMessage()) } @@ -141,9 +142,9 @@ class EncryptedSynchronizedRealmTests { @Test fun setEncryptionKey_differentClientsWithDifferentKeys() { // STEP 1: prepare a synced Realm for client A - var user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val randomKey = TestHelper.getRandomKey() - val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonObjectId()) + val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonString(UUID.randomUUID().toString())) .testSchema(SyncStringOnly::class.java) .waitForInitialRemoteData() .errorHandler { session, error -> fail(error.getErrorMessage()) } @@ -160,7 +161,7 @@ class EncryptedSynchronizedRealmTests { } // STEP 3: prepare a synced Realm for client B - var user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val key2 = TestHelper.getRandomKey() val configWithEncryption2: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, configWithEncryption.partitionValue) .testSchema(SyncStringOnly::class.java) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index a87f013747..2f05e89ee4 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -20,7 +20,6 @@ import io.realm.util.assertFailsWithErrorCode import io.realm.util.assertFailsWithMessage import org.bson.BsonInt32 import org.bson.BsonInt64 -import org.bson.BsonObjectId import org.bson.BsonString import org.bson.types.ObjectId import org.hamcrest.CoreMatchers @@ -28,7 +27,6 @@ import org.junit.* import org.junit.Assert.* import org.junit.runner.RunWith import java.io.Closeable -import java.lang.IllegalStateException import java.lang.Thread import java.util.* import java.util.concurrent.CountDownLatch @@ -103,7 +101,7 @@ class SyncSessionTests { // TODO We generate new partition value for each test to avoid overlaps in data. We // could make test booting with a cleaner state by somehow flushing data between // tests. - .createSyncConfigurationBuilder(user, BsonObjectId(ObjectId())) + .createSyncConfigurationBuilder(user, BsonString(UUID.randomUUID().toString())) .modules(DefaultSyncSchema()) .build() } @@ -132,40 +130,57 @@ class SyncSessionTests { } @Test - fun partitionValue_int32() { + fun partitionValue_int32_failsWhenServerConfiguredWithStringPartition() { val int = 123536462 val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonInt32(int)) .modules(DefaultSyncSchema()) .build() - Realm.getInstance(syncConfiguration).use { realm -> - realm.executeTransaction { - realm.createObject(SyncDog::class.java, ObjectId()) + + looperThread.runBlocking { + Realm.getInstance(syncConfiguration).use { realm -> + realm.executeTransaction { + realm.createObject(SyncDog::class.java, ObjectId()) + } + + assertFailsWith { + // This throws as the server has NOT been configured to have int partitions! + realm.syncSession.uploadAllLocalChanges() + }.also { + looperThread.testComplete() + } } - realm.syncSession.uploadAllLocalChanges() } } @Test - fun partitionValue_int64() { + fun partitionValue_int64_failsWhenServerConfiguredWithStringPartition() { val long = 1243513244L val syncConfiguration = configFactory .createSyncConfigurationBuilder(user, BsonInt64(long)) .modules(DefaultSyncSchema()) .build() - Realm.getInstance(syncConfiguration).use { realm -> - realm.executeTransaction { - realm.createObject(SyncDog::class.java, ObjectId()) + + looperThread.runBlocking { + Realm.getInstance(syncConfiguration).use { realm -> + realm.executeTransaction { + realm.createObject(SyncDog::class.java, ObjectId()) + } + + assertFailsWith { + // This throws as the server has NOT been configured to have long partitions! + realm.syncSession.uploadAllLocalChanges() + }.also { + looperThread.testComplete() + } } - realm.syncSession.uploadAllLocalChanges() } } @Test fun partitionValue_objectId() { - val objectId = ObjectId("5ecf72df02aa3c32ab6b4ce0") val syncConfiguration = configFactory - .createSyncConfigurationBuilder(user, BsonObjectId(objectId)) + .createSyncConfigurationBuilder(user, BsonString(UUID.randomUUID().toString())) .modules(DefaultSyncSchema()) .build() Realm.getInstance(syncConfiguration).use { realm -> @@ -228,8 +243,8 @@ class SyncSessionTests { fun getState_loggedOut() { Realm.getInstance(syncConfiguration).use { realm -> val session = realm.syncSession - user.logOut(); - assertEquals(SyncSession.State.INACTIVE, session.state); + user.logOut() + assertEquals(SyncSession.State.INACTIVE, session.state) } } @@ -237,7 +252,7 @@ class SyncSessionTests { fun session_throwOnLogoutUser() { user.logOut() assertFailsWith { - Realm.getInstance(syncConfiguration).use { realm -> } + Realm.getInstance(syncConfiguration).use { } } } @@ -296,7 +311,7 @@ class SyncSessionTests { // New user and different partition value val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2 = configFactory - .createSyncConfigurationBuilder(user2, BsonObjectId(ObjectId())) + .createSyncConfigurationBuilder(user2, BsonString(UUID.randomUUID().toString())) .modules(DefaultSyncSchema()) .build() @@ -320,7 +335,7 @@ class SyncSessionTests { // New user and different partition value val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2 = configFactory - .createSyncConfigurationBuilder(user2, BsonObjectId(ObjectId())) + .createSyncConfigurationBuilder(user2, BsonString(UUID.randomUUID().toString())) .modules(DefaultSyncSchema()) .build() @@ -392,7 +407,7 @@ class SyncSessionTests { Realm.getInstance(syncConfiguration).use { realm1 -> // New partitionValue to differentiate sync session val syncConfiguration2 = configFactory - .createSyncConfigurationBuilder(user, BsonObjectId(ObjectId())) + .createSyncConfigurationBuilder(user, BsonString(UUID.randomUUID().toString())) .modules(DefaultSyncSchema()) .build() @@ -561,7 +576,7 @@ class SyncSessionTests { @Ignore("Does not terminate") fun downloadChangesWhenRealmOutOfScope() { val uniqueName = UUID.randomUUID().toString() - var credentials = app.emailPasswordAuth.registerUser(uniqueName, "password") + app.emailPasswordAuth.registerUser(uniqueName, "password") val config1 = configFactory .createSyncConfigurationBuilder(user) .modules(SyncStringOnlyModule()) @@ -670,11 +685,9 @@ class SyncSessionTests { fail("Listener should have been removed") } } - var listener2 = object : ConnectionListener { - override fun onChange(oldState: ConnectionState, newState: ConnectionState) { - if (newState == ConnectionState.DISCONNECTED) { - looperThread.testComplete() - } + val listener2 = ConnectionListener { oldState, newState -> + if (newState == ConnectionState.DISCONNECTED) { + looperThread.testComplete() } } session.addConnectionChangeListener(listener1) diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 2dabbda6b4..4cc01512cf 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -12,12 +12,12 @@ # Verify that Github username and tokens are available as environment vars if [[ -z "${GITHUB_DOCKER_USER}" ]]; then - echo "Could not find \$GITHUB_DOCKER_USER as an environment variabel" + echo "Could not find \$GITHUB_DOCKER_USER as an environment variable" exit 1 fi if [[ -z "${GITHUB_DOCKER_TOKEN}" ]]; then - echo "Could not find \$GITHUB_DOCKER_TOKEN as an environment variabel. This is used to download Docker Registry packages." + echo "Could not find \$GITHUB_DOCKER_TOKEN as an environment variable. This is used to download Docker Registry packages." exit 1 fi @@ -43,4 +43,3 @@ docker run --rm -i -t -d --network container:$ID -v$TMP_DIR:/tmp --name mongodb- docker cp "$DOCKERFILE_DIR"/app_config mongodb-realm:/tmp/app_config docker cp "$DOCKERFILE_DIR"/setup_mongodb_realm.sh mongodb-realm:/tmp/ docker exec -it mongodb-realm sh /tmp/setup_mongodb_realm.sh - From 88d34c441600d17f82672b1512ea173af05e7883 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 19 Aug 2020 11:34:05 +0200 Subject: [PATCH 1635/2110] Re-enable Slack notifications (#7042) --- Jenkinsfile | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5fb1612e11..5e64566595 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -138,18 +138,22 @@ try { buildSuccess = false throw e } finally { - if (slackNotificationBranches.contains(currentBranch) && !buildSuccess) { + if (slackNotificationBranches.contains(currentBranch)) { node { - withCredentials([[$class: 'StringBinding', credentialsId: 'slack-java-url', variable: 'SLACK_URL']]) { - def payload = JsonOutput.toJson([ - username: 'Mr. Jenkins', - icon_emoji: ':jenkins:', - attachments: [[ - 'title': "The ${currentBranch} branch is broken!", - 'text': "<${env.BUILD_URL}|Click here> to check the build.", - 'color': "danger" - ]] - ]) + withCredentials([[$class: 'StringBinding', credentialsId: 'slack-webhook-java-ci-channel', variable: 'SLACK_URL']]) { + def payload = null + if (!buildSuccess) { + payload = JsonOutput.toJson([ + text: "*The ${currentBranch} branch is broken!*\n<${env.BUILD_URL}|Click here> to check the build." + ]) + } + + if (currentBuild.getPreviousBuild() && currentBuild.getPreviousBuild().getResult().toString() != "SUCCESS" && buildSuccess) { + payload = JsonOutput.toJson([ + text: "*${currentBranch} is back to normal!*\n<${env.BUILD_URL}|Click here> to check the build." + ]) + } + sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" } } From 14604191e279269549c3bf9e7d6b4157848d63b1 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 19 Aug 2020 13:04:55 +0200 Subject: [PATCH 1636/2110] Fix CI cache --- Jenkinsfile | 2 +- tools/unroll_stacktrace.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5e64566595..896e38412e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -58,7 +58,7 @@ try { stage('Prepare Docker Images') { // TODO Should be renamed to 'master' when merged there. // TODO Figure out why caching the image doesn't work. - buildEnv = buildDockerEnv("realm-java-ci:v10", push: currentBranch == 'v10') + buildEnv = buildDockerEnv("ci/realm-java:v10", push: currentBranch == 'v10') def props = readProperties file: 'dependencies.list' echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") diff --git a/tools/unroll_stacktrace.sh b/tools/unroll_stacktrace.sh index 6c2d993fa5..df6c7aa7d5 100644 --- a/tools/unroll_stacktrace.sh +++ b/tools/unroll_stacktrace.sh @@ -15,9 +15,9 @@ IFS=$'\n\t' usage() { cat < + - flavor: base, objectServer - version: version number on Bintray - abi: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips - - flavor: base, objectServer - stacktrace: absolute or relative path to file with dump information Example: $0 base 5.0.0 armeabi-v7a ./dump.txt From 92354834cb78063e7815e17715d96220eb07a989 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 19 Aug 2020 14:46:43 +0200 Subject: [PATCH 1637/2110] Disable CI caching --- Jenkinsfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 896e38412e..dd3d016b95 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -57,8 +57,9 @@ try { def buildEnv = null stage('Prepare Docker Images') { // TODO Should be renamed to 'master' when merged there. - // TODO Figure out why caching the image doesn't work. - buildEnv = buildDockerEnv("ci/realm-java:v10", push: currentBranch == 'v10') + // TODO Caching is currently disabled (with -do-not-cache suffix) due to the upload speed + // in Copenhagen being too slow. So the upload times out. + buildEnv = buildDockerEnv("ci/realm-java:v10", push: currentBranch == 'v10-do-not-cache') def props = readProperties file: 'dependencies.list' echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") From fad9de8e62346d959cac3ac0dc41a6c2cb101fe7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 19 Aug 2020 15:46:53 +0200 Subject: [PATCH 1638/2110] Always use emulator for all build types --- Jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index dd3d016b95..1d08067bf5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -43,13 +43,12 @@ try { def emulatorImage = "" def abiFilter = "" def instrumentationTestTarget = "connectedAndroidTest" - def deviceSerial = "" + def deviceSerial = "emulator-5554" // FIXME: Always used the emulator until we can enable more reliable devices if (!releaseBranches.contains(currentBranch)) { useEmulator = true emulatorImage = "system-images;android-29;default;x86" abiFilter = "-PbuildTargetABIs=x86" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" - deviceSerial = "emulator-5554" } try { From eb79312eaa06ef24c6203e73c65fe05648901ba5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 19 Aug 2020 16:05:42 +0200 Subject: [PATCH 1639/2110] Only use emulator machine --- Jenkinsfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1d08067bf5..9616143e92 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,8 +14,10 @@ releaseBranches = ['master', 'next-major', 'v10'] // Branches that are "important", so if they do not compile they will generate a Slack notification slackNotificationBranches = [ 'master', 'releases', 'next-major', 'v10' ] currentBranch = env.CHANGE_BRANCH +// FIXME: Always used the emulator until we can enable more reliable devices // 'android' nodes have android devices attached and 'brix' are physical machines in Copenhagen. -nodeSelector = (releaseBranches.contains(currentBranch)) ? 'android' : 'docker-cph-03' // Switch to `brix` when all CPH nodes work: https://jira.mongodb.org/browse/RCI-14 +// nodeSelector = (releaseBranches.contains(currentBranch)) ? 'android' : 'docker-cph-03' // Switch to `brix` when all CPH nodes work: https://jira.mongodb.org/browse/RCI-14 +nodeSelector = 'docker-cph-03' try { node(nodeSelector) { timeout(time: 90, unit: 'MINUTES') { From c66f0386d3b895e0ae5d000e3566e55c2defc4b7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 19 Aug 2020 17:32:32 +0200 Subject: [PATCH 1640/2110] Make v10 branch also use emulator for now --- Jenkinsfile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 9616143e92..d4b174ca47 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -45,12 +45,21 @@ try { def emulatorImage = "" def abiFilter = "" def instrumentationTestTarget = "connectedAndroidTest" - def deviceSerial = "emulator-5554" // FIXME: Always used the emulator until we can enable more reliable devices + def deviceSerial = "" if (!releaseBranches.contains(currentBranch)) { + // Bui useEmulator = true emulatorImage = "system-images;android-29;default;x86" abiFilter = "-PbuildTargetABIs=x86" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" + deviceSerial = "emulator-5554" + } else { + // FIXME: Use emulator until we can get reliable devices on CI. + // But still build all ABI's and run all types of tests. + useEmulator = true + emulatorImage = "system-images;android-29;default;x86" + instrumentationTestTarget = "connectedAndroidTest" + deviceSerial = "emulator-5554" } try { From 222ae303ec1890581e36a324bd466acff2786cee Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Mon, 24 Aug 2020 09:11:12 +0200 Subject: [PATCH 1641/2110] Update no-response.yml --- .github/no-response.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/no-response.yml b/.github/no-response.yml index 7193eaa3b2..e71a764961 100644 --- a/.github/no-response.yml +++ b/.github/no-response.yml @@ -3,7 +3,7 @@ # Number of days of inactivity before an Issue is closed for lack of response daysUntilClose: 14 # Label requiring a response -responseRequiredLabel: more-information-needed +responseRequiredLabel: More-information-needed # Comment to post when closing an Issue for lack of response. Set to `false` to disable closeComment: > This issue has been automatically closed because there has been no response From d37d73d6f849bf9823b90c28e2638987a4c6cfb8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 24 Aug 2020 09:55:26 +0200 Subject: [PATCH 1642/2110] Automate uploading docs to MongoDB Realm website (#7052) --- build.gradle | 84 ++++++++++++++++++++++---- realm/templates/README.md | 4 ++ realm/templates/redirect.html.template | 8 +++ 3 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 realm/templates/README.md create mode 100644 realm/templates/redirect.html.template diff --git a/build.gradle b/build.gradle index ae5eb23c32..34736008b5 100644 --- a/build.gradle +++ b/build.gradle @@ -195,21 +195,81 @@ task javadoc(type:GradleBuild) { configure copyProperties } -task javadocPackage(type: Zip) { - description = 'Generate a Zip file with all SDK docs' +// Find property in either System environment or Gradle properties. +// If set in both places, Gradle properties win. +def getPropertyValueOrThrow(String propertyName) { + def value = System.getenv(propertyName) + if (project.hasProperty(propertyName)) { + value = project.getProperty(propertyName) + } + if (value == null || value.trim().isEmpty()) { + throw new GradleException("Could not find '$propertyName'. " + + "Most be provided as either environment variable or " + + "a Gradle property.") + } + return value +} + +task createLatestJavadocRedirectFile(type: Copy) { + description = 'Redirects from /java/latest/ to correct version' + from 'realm/templates' + into "$buildDir/outputs/doc_redirects/java_latest" + include 'redirect.html.template' + rename { file -> 'index.html' } + expand(title: "Realm Java ${currentVersion}", url: "../${currentVersion}/index.html") +} + +task createLatestKotlindocRedirectFile(type: Copy) { + description = 'Redirects from /kotlin/latest/ to correct version' + from 'realm/templates' + into "$buildDir/outputs/doc_redirects/kotlin_latest" + include 'redirect.html.template' + rename { file -> 'index.html' } + expand(title: "Kotlin Extensions ${currentVersion}", url: "../${currentVersion}/index.html") +} + +task createKotlinRootRedirectFile(type: Copy) { + description = 'Redirects from /kotlin// to /kotlin//kotlin-extensions, which is the real root folder' + from 'realm/templates' + into "$buildDir/outputs/doc_redirects/kotlin_root" + include 'redirect.html.template' + rename { file -> 'index.html' } + expand(title: "Kotlin Extensions ${currentVersion}", url: "./kotlin-extensions/index.html") +} + +task uploadJavadoc { + group = 'Release' + description = 'Upload Java and Kotlin docs to S3' dependsOn javadoc + dependsOn createLatestJavadocRedirectFile + dependsOn createLatestKotlindocRedirectFile + dependsOn createKotlinRootRedirectFile - group = 'Artifact' - archiveName = "realm-java-${currentVersion}-docs.zip" - destinationDir = file("${buildDir}/outputs/docs") + doLast { + def awsAccessKey = getPropertyValueOrThrow("SDK_DOCS_AWS_ACCESS_KEY") + def awsSecretKey = getPropertyValueOrThrow("SDK_DOCS_AWS_SECRET_KEY") - from('realm/realm-library/build/docs/javadoc') { - include '**/*' - into 'javadoc' - } - from('realm/kotlin-extensions/build/docs') { - include '**/*' - into 'kotlindocs' + // Upload the versioned folder of the docs + exec { + commandLine 's3cmd', 'put', '--recursive', '--acl-public', "--access_key=${awsAccessKey}", "--secret_key=${awsSecretKey}", 'realm/realm-library/build/docs/javadoc/', "s3://realm-sdks/realm-sdks/java/${currentVersion}/" + } + exec { + commandLine 's3cmd', 'put', '--recursive', '--acl-public', "--access_key=${awsAccessKey}", "--secret_key=${awsSecretKey}", 'realm/kotlin-extensions/build/docs/', "s3://realm-sdks/realm-sdks/kotlin/${currentVersion}/" + } + + // Upload automatic redirect for Kotlin extensions, since the directory structure created + // by Dokka only have a style.css in the root dir. + exec { + commandLine 's3cmd', 'put', '--acl-public', "--access_key=${awsAccessKey}", "--secret_key=${awsSecretKey}", "$buildDir/outputs/doc_redirects/kotlin_root/index.html", "s3://realm-sdks/realm-sdks/kotlin/${currentVersion}/index.html" + } + + // Upload redirects to /latest end point so it points to the just uploaded version + exec { + commandLine 's3cmd', 'put', '--acl-public', "--access_key=${awsAccessKey}", "--secret_key=${awsSecretKey}", "$buildDir/outputs/doc_redirects/java_latest/index.html", "s3://realm-sdks/realm-sdks/java/latest/index.html" + } + exec { + commandLine 's3cmd', 'put', '--acl-public', "--access_key=${awsAccessKey}", "--secret_key=${awsSecretKey}", "$buildDir/outputs/doc_redirects/kotlin_latest/index.html", "s3://realm-sdks/realm-sdks/kotlin/latest/index.html" + } } } diff --git a/realm/templates/README.md b/realm/templates/README.md new file mode 100644 index 0000000000..8205acb9dc --- /dev/null +++ b/realm/templates/README.md @@ -0,0 +1,4 @@ +This folder contains template files used when releasing Realm Java + +**redirect.html.template** +Template file that can be used to redirect people to other parts of the documentation. diff --git a/realm/templates/redirect.html.template b/realm/templates/redirect.html.template new file mode 100644 index 0000000000..b366d6e66f --- /dev/null +++ b/realm/templates/redirect.html.template @@ -0,0 +1,8 @@ + + + + +${title} +If you are not redirected automatically, follow the link to the documentation. \ No newline at end of file From dfdc735c6f0e43d7a1e74370b8f78c37b2c07374 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 25 Aug 2020 08:57:03 +0200 Subject: [PATCH 1643/2110] Add support for the Null partition (#7050) --- CHANGELOG.md | 25 +++++++++++++++++++ dependencies.list | 2 +- .../mongodb/sync/SyncConfigurationTests.kt | 20 +++++++++++++++ .../io/realm/mongodb/sync/SyncedRealmTests.kt | 20 ++++++++++++--- .../cpp/io_realm_mongodb_sync_SyncSession.cpp | 15 ++++++----- realm/realm-library/src/main/cpp/object-store | 2 +- .../internal/SyncObjectServerFacade.java | 1 + .../java/io/realm/mongodb/sync/Sync.java | 1 + .../realm/mongodb/sync/SyncConfiguration.java | 25 ++++++++++--------- .../io/realm/mongodb/sync/SyncSession.java | 20 +++++++++++---- .../kotlin/io/realm/SyncSessionTests.kt | 2 +- .../app_config/services/BackingDB/config.json | 1 + 12 files changed, 104 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62de2be53b..562f4c9d76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +## 10.0.0-BETA.7 (YYYY-MM-DD) + +We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. + +The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. + +### Breaking Changes +* None. + +### Enhancements +* [RealmApp] Support for using `null` as a partition value. +* [RealmApp] Improve errors exception messages from `SyncSession.downloadAllServerChanges()` and `SyncSession.uploadAllLocalChanges()`. + +### Fixed +* None. + +### Compatibility +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 and above is required to open Realms created by this version. + +### Internal +* Updated to Object Store commit: 39e20006761e77014ceb19a2bd8f43018cc96f5a. + + ## 10.0.0-BETA.6 (2020-08-17) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. diff --git a/dependencies.list b/dependencies.list index d3ef2187a1..0a6e0e73fb 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=824192a67e7ded59d33707265f78c8d5546b1fef473e9ee5ecff8a5bc9f850 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-08-17 +MONGODB_REALM_SERVER_VERSION=2020-08-24 # Common Android settings across projects GRADLE_BUILD_TOOLS=3.6.1 diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index 6ce774b958..ec9a918a19 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -323,6 +323,26 @@ class SyncConfigurationTests { } } + @Test + fun nullPartitionValue() { + val user: User = createTestUser(app) + + val configs = listOf( + SyncConfiguration.defaultConfig(user, null as String?), + SyncConfiguration.defaultConfig(user, null as Int?), + SyncConfiguration.defaultConfig(user, null as Long?), + SyncConfiguration.defaultConfig(user, null as ObjectId?), + SyncConfiguration.Builder(user, null as String?).build(), + SyncConfiguration.Builder(user, null as Int?).build(), + SyncConfiguration.Builder(user, null as Long?).build(), + SyncConfiguration.Builder(user, null as ObjectId?).build() + ) + + configs.forEach { config -> + assertTrue(config.path.endsWith("/null.realm")) + } + } + @Test fun loggedOutUsersThrows() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index 7c81abeacf..01b05af25d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -26,11 +26,10 @@ import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.LogLevel import io.realm.log.RealmLog -import io.realm.mongodb.App -import io.realm.mongodb.Credentials +import io.realm.mongodb.* import io.realm.mongodb.SyncTestUtils.Companion.createTestUser -import io.realm.mongodb.User -import io.realm.mongodb.close +import io.realm.util.assertFailsWithErrorCode +import org.bson.BsonNull import org.junit.* import org.junit.runner.RunWith import java.io.File @@ -194,6 +193,19 @@ class SyncedRealmTests { } } + @Test + fun nullPartition() { + val config = configFactory.createSyncConfigurationBuilder(createNewUser(), BsonNull()).build() + assertTrue(config.path.endsWith("null.realm")) + Realm.getInstance(config).use { realm -> + // FIXME: This currently fails because the server does not yet support the Null partition + // Remove the catch once it is supported, after which uploading changes should work. + assertFailsWithErrorCode(ErrorCode.ILLEGAL_REALM_PATH) { + realm.syncSession.uploadAllLocalChanges() // Ensures that we can actually connect + } + } + } + @Test @Ignore("FIXME Flaky, seems like Realm.compactRealm(config) sometimes returns false") fun compactRealm_populatedRealm() { diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp index 8ec033a3cf..e814e8ebc3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp @@ -131,19 +131,20 @@ JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForD if (session) { static JavaClass java_sync_session_class(env, "io/realm/mongodb/sync/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", - "(ILjava/lang/Long;Ljava/lang/String;)V"); + "(ILjava/lang/String;Ljava/lang/Long;Ljava/lang/String;)V"); session->wait_for_download_completion([session_ref = JavaGlobalRefByCopy(env, session_object), callback_id](std::error_code error) { JNIEnv* env = JniUtils::get_env(true); + JavaLocalRef java_error_category; JavaLocalRef java_error_code; JavaLocalRef java_error_message; if (error != std::error_code{}) { - java_error_code = - JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); + java_error_category = JavaLocalRef(env, env->NewStringUTF(error.category().name())); + java_error_code = JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); } env->CallVoidMethod(session_ref.get(), java_notify_result_method, - callback_id, java_error_code.get(), java_error_message.get()); + callback_id, java_error_category.get(), java_error_code.get(), java_error_message.get()); }); return to_jbool(JNI_TRUE); } @@ -164,18 +165,20 @@ JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForU if (session) { static JavaClass java_sync_session_class(env, "io/realm/mongodb/sync/SyncSession"); static JavaMethod java_notify_result_method(env, java_sync_session_class, "notifyAllChangesSent", - "(ILjava/lang/Long;Ljava/lang/String;)V"); + "(ILjava/lang/String;Ljava/lang/Long;Ljava/lang/String;)V"); session->wait_for_upload_completion([session_ref = JavaGlobalRefByCopy(env, session_object), callback_id] (std::error_code error) { JNIEnv* env = JniUtils::get_env(true); + JavaLocalRef java_error_category; JavaLocalRef java_error_code; JavaLocalRef java_error_message; if (error != std::error_code{}) { + java_error_category = JavaLocalRef(env, env->NewStringUTF(error.category().name())); java_error_code = JavaLocalRef(env, JavaClassGlobalDef::new_long(env, error.value())); java_error_message = JavaLocalRef(env, env->NewStringUTF(error.message().c_str())); } env->CallVoidMethod(session_ref.get(), java_notify_result_method, - callback_id, java_error_code.get(), java_error_message.get()); + callback_id, java_error_category.get(), java_error_code.get(), java_error_message.get()); }); return JNI_TRUE; } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 5b5fb8a901..39e2000676 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 5b5fb8a90192cb4ee6799e7465745cd2067f939b +Subproject commit 39e20006761e77014ceb19a2bd8f43018cc96f5a diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 3b18b3d939..32c47fccc1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -98,6 +98,7 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { case OBJECT_ID: case INT32: case INT64: + case NULL: encodedPartitionValue = JniBsonProtocol.encode(partitionValue, AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY); break; default: diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java index 579b987b80..7ff90d7414 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java @@ -184,6 +184,7 @@ String getAbsolutePathForRealm(String userId, BsonValue partitionValue, @Nullabl case OBJECT_ID: case INT32: case INT64: + case NULL: encodedPartitionValue = JniBsonProtocol.encode(partitionValue, AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY); break; default: diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index 9b8f774877..1f21bfa9e9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -20,6 +20,7 @@ import org.bson.BsonInt32; import org.bson.BsonInt64; +import org.bson.BsonNull; import org.bson.BsonObjectId; import org.bson.BsonString; import org.bson.BsonValue; @@ -197,7 +198,7 @@ RealmConfiguration forErrorRecovery(String canonicalPath) { * @return the default configuration for the given user and partition value. */ @Beta - public static SyncConfiguration defaultConfig(User user, String partitionValue) { + public static SyncConfiguration defaultConfig(User user, @Nullable String partitionValue) { return new SyncConfiguration.Builder(user, partitionValue).build(); } @@ -209,7 +210,7 @@ public static SyncConfiguration defaultConfig(User user, String partitionValue) * @return the default configuration for the given user and partition value. */ @Beta - public static SyncConfiguration defaultConfig(User user, long partitionValue) { + public static SyncConfiguration defaultConfig(User user, @Nullable Long partitionValue) { return new SyncConfiguration.Builder(user, partitionValue).build(); } @@ -221,7 +222,7 @@ public static SyncConfiguration defaultConfig(User user, long partitionValue) { * @return the default configuration for the given user and partition value. */ @Beta - public static SyncConfiguration defaultConfig(User user, int partitionValue) { + public static SyncConfiguration defaultConfig(User user, @Nullable Integer partitionValue) { return new SyncConfiguration.Builder(user, partitionValue).build(); } @@ -233,7 +234,7 @@ public static SyncConfiguration defaultConfig(User user, int partitionValue) { * @return the default configuration for the given user and partition value. */ @Beta - public static SyncConfiguration defaultConfig(User user, ObjectId partitionValue) { + public static SyncConfiguration defaultConfig(User user, @Nullable ObjectId partitionValue) { return new SyncConfiguration.Builder(user, partitionValue).build(); } @@ -471,8 +472,8 @@ public static final class Builder { * @param user The user that will be used for accessing the Realm App. * @param partitionValue The partition value identifying the remote Realm that will be synchronized. */ - public Builder(User user, String partitionValue) { - this(user, new BsonString(partitionValue)); + public Builder(User user, @Nullable String partitionValue) { + this(user, (partitionValue == null? new BsonNull() : new BsonString(partitionValue))); } /** @@ -482,8 +483,8 @@ public Builder(User user, String partitionValue) { * @param user The user that will be used for accessing the Realm App. * @param partitionValue The partition value identifying the remote Realm that will be synchronized. */ - public Builder(User user, ObjectId partitionValue) { - this(user, new BsonObjectId(partitionValue)); + public Builder(User user, @Nullable ObjectId partitionValue) { + this(user, (partitionValue == null? new BsonNull() : new BsonObjectId(partitionValue))); } /** @@ -493,8 +494,8 @@ public Builder(User user, ObjectId partitionValue) { * @param user The user that will be used for accessing the Realm App. * @param partitionValue The partition value identifying the remote Realm that will be synchronized. */ - public Builder(User user, int partitionValue) { - this(user, new BsonInt32(partitionValue)); + public Builder(User user, @Nullable Integer partitionValue) { + this(user, (partitionValue == null? new BsonNull() : new BsonInt32(partitionValue))); } /** @@ -504,8 +505,8 @@ public Builder(User user, int partitionValue) { * @param user The user that will be used for accessing the Realm App. * @param partitionValue The partition value identifying the remote Realm that will be synchronized. */ - public Builder(User user, long partitionValue) { - this(user, new BsonInt64(partitionValue)); + public Builder(User user, @Nullable Long partitionValue) { + this(user, (partitionValue == null? new BsonNull() : new BsonInt64(partitionValue))); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 5658f7818b..c907d443f9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -417,7 +417,7 @@ synchronized void close() { // If the native listener was successfully registered, Object Store guarantees that this method will be called at // least once, even if the session is closed. @SuppressWarnings("unused") - private void notifyAllChangesSent(int callbackId, Long errorcode, String errorMessage) { + private void notifyAllChangesSent(int callbackId, String errorCategory, Long errorCode, String errorMessage) { WaitForSessionWrapper wrapper = waitingForServerChanges.get(); if (wrapper != null) { // Only react to callback if the callback is "active" @@ -427,7 +427,7 @@ private void notifyAllChangesSent(int callbackId, Long errorcode, String errorMe // 3. Call `uploadAllLocalChanges()` ( callback = 2) // 4. Sync notifies session that callback:1 is done. It should be ignored. if (waitCounter.get() == callbackId) { - wrapper.handleResult(errorcode, errorMessage); + wrapper.handleResult(errorCategory, errorCode, errorMessage); } } } @@ -695,6 +695,7 @@ private static class WaitForSessionWrapper { private final CountDownLatch waiter = new CountDownLatch(1); private volatile boolean resultReceived = false; + private String errorCategory; private Long errorCode = null; private String errorMessage; @@ -715,7 +716,8 @@ public boolean waitForServerChanges(long timeout, TimeUnit unit) throws Interrup * @param errorCode error code if an error occurred, {@code null} if changes were successfully downloaded. * @param errorMessage error message (if any). */ - public void handleResult(Long errorCode, String errorMessage) { + public void handleResult(String errorCategory, Long errorCode, String errorMessage) { + this.errorCategory = errorCategory; this.errorCode = errorCode; this.errorMessage = errorMessage; this.resultReceived = true; @@ -732,8 +734,16 @@ public boolean isSuccess() { */ public void throwExceptionIfNeeded() { if (resultReceived && errorCode != null) { - throw new AppException(ErrorCode.UNKNOWN, - String.format(Locale.US, "Internal error (%d): %s", errorCode, errorMessage)); + // Core report errors with int64, so we need to add some extra checks + // to make sure the value is within a range of known errors we can map to, + // which are all inside Integer range + long longErrorCode = errorCode; + ErrorCode mappedError = ErrorCode.fromNativeError(errorCategory, (int) longErrorCode); + if (longErrorCode >= Integer.MIN_VALUE && longErrorCode <= Integer.MAX_VALUE && mappedError != ErrorCode.UNKNOWN) { + throw new AppException(mappedError, errorMessage); + } else { + throw new AppException(mappedError, String.format(Locale.US, "Internal error (%d): %s", errorCode, errorMessage)); + } } } } diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 2f05e89ee4..6edb9eeb2b 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -293,7 +293,7 @@ class SyncSessionTests { realm.executeTransaction { realm.createObject(SyncAllTypesWithFloat::class.java, ObjectId()) } - assertFailsWithErrorCode(ErrorCode.UNKNOWN) { + assertFailsWithErrorCode(ErrorCode.INVALID_SCHEMA_CHANGE) { realm.syncSession.uploadAllLocalChanges() } } diff --git a/tools/sync_test_server/app_config/services/BackingDB/config.json b/tools/sync_test_server/app_config/services/BackingDB/config.json index 4900710917..2978971ea1 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/config.json +++ b/tools/sync_test_server/app_config/services/BackingDB/config.json @@ -9,6 +9,7 @@ "partition": { "key": "realm_id", "type": "string", + "required": false, "permissions": { "read": true, "write": true From 134e039480c5d8152f839f99f2cf92e62ff516d3 Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 25 Aug 2020 10:54:56 +0200 Subject: [PATCH 1644/2110] Add support for watch collection watch streams (#7014) --- CHANGELOG.md | 1 + .../io/realm/rule/BlockingLooperThread.kt | 64 ++- .../kotlin/io/realm/UserMetadataTests.kt | 9 +- .../internal/async/RealmStreamTaskImplTest.kt | 142 +++++++ .../kotlin/io/realm/mongodb/AppExt.kt | 2 +- .../io/realm/mongodb/MongoClientTest.kt | 393 +++++++++++++++++- .../io/realm/{ => mongodb}/UserTests.kt | 4 +- .../transport/OkHttpNetworkTransportTests.kt | 23 +- .../transport/OsJavaNetworkTransportTests.kt | 35 +- .../realm-library/src/main/cpp/CMakeLists.txt | 5 +- ...> io_realm_internal_objectstore_OsApp.cpp} | 110 ++++- ...alm_internal_objectstore_OsWatchStream.cpp | 128 ++++++ .../src/main/cpp/jni_util/jni_utils.cpp | 27 ++ .../src/main/cpp/jni_util/jni_utils.hpp | 3 + .../src/main/java/io/realm/internal/Util.java | 14 + .../async/RealmEventStreamAsyncTaskImpl.java | 101 +++++ .../async/RealmEventStreamTaskImpl.java | 82 ++++ .../io/realm/internal/events/ChangeEvent.java | 251 +++++++++++ .../internal/events/NetworkEventStream.java | 69 +++ .../realm/internal/events/package-info.java | 18 + .../network/OkHttpNetworkTransport.java | 145 ++++++- .../network/StreamNetworkTransport.java | 63 +++ .../internal/objectserver/EventStream.java | 46 ++ .../io/realm/internal/objectstore/OsApp.java | 139 +++++++ .../objectstore/OsJavaNetworkTransport.java | 85 +++- .../internal/objectstore/OsMongoClient.java | 16 +- .../objectstore/OsMongoCollection.java | 86 +++- .../internal/objectstore/OsMongoDatabase.java | 17 +- .../io/realm/internal/objectstore/OsPush.java | 4 +- .../internal/objectstore/OsWatchStream.java | 56 +++ .../java/io/realm/mongodb/ApiKeyAuthImpl.java | 2 +- .../java/io/realm/mongodb/App.java | 117 ++---- .../java/io/realm/mongodb/Credentials.java | 2 +- .../realm/mongodb/EmailPasswordAuthImpl.java | 2 +- .../java/io/realm/mongodb/ErrorCode.java | 4 + .../java/io/realm/mongodb/FunctionsImpl.java | 2 +- .../mongodb/RealmEventStreamAsyncTask.java | 45 ++ .../realm/mongodb/RealmEventStreamTask.java | 49 +++ .../java/io/realm/mongodb/User.java | 17 +- .../io/realm/mongodb/mongo/MongoClient.java | 2 - .../realm/mongodb/mongo/MongoCollection.java | 202 +++++++++ .../io/realm/mongodb/mongo/MongoDatabase.java | 11 +- .../mongodb/mongo/events/BaseChangeEvent.java | 116 ++++++ .../mongo/events/UpdateDescription.java | 312 ++++++++++++++ .../syncTestUtils/kotlin/io/realm/TestApp.kt | 2 +- .../kotlin/io/realm/mongodb/SyncTestUtils.kt | 20 +- .../mongodb-realm-command-server.js | 10 + 47 files changed, 2852 insertions(+), 201 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmStreamTaskImplTest.kt rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{ => mongodb}/UserTests.kt (99%) rename realm/realm-library/src/main/cpp/{io_realm_mongodb_App.cpp => io_realm_internal_objectstore_OsApp.cpp} (64%) create mode 100644 realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmEventStreamAsyncTaskImpl.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmEventStreamTaskImpl.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/events/ChangeEvent.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/events/NetworkEventStream.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/events/package-info.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/network/StreamNetworkTransport.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/EventStream.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsApp.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsWatchStream.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmEventStreamAsyncTask.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmEventStreamTask.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/events/BaseChangeEvent.java create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/events/UpdateDescription.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 562f4c9d76..a0a617b966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new ### Enhancements * [RealmApp] Support for using `null` as a partition value. * [RealmApp] Improve errors exception messages from `SyncSession.downloadAllServerChanges()` and `SyncSession.uploadAllLocalChanges()`. +* Support for watching MongoCollection change streams (Issue [#6912](https://github.com/realm/realm-java/issues/6912)) ### Fixed * None. diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/rule/BlockingLooperThread.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/rule/BlockingLooperThread.kt index a95991a25e..233ed7f34f 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/rule/BlockingLooperThread.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/rule/BlockingLooperThread.kt @@ -20,12 +20,12 @@ import android.os.Looper import io.realm.TestHelper import io.realm.TestHelper.LooperTest import io.realm.internal.android.AndroidCapabilities -import io.realm.rule.RunTestInLooperThread import org.junit.runners.model.MultipleFailureException import java.io.Closeable import java.io.PrintStream import java.util.* import java.util.concurrent.CountDownLatch +import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock @@ -91,6 +91,13 @@ class BlockingLooperThread { RunInLooperThreadStatement(threadName, emulateMainThread, test).evaluate() } + /** + * Runs the test on a Looper thread. Returns an object that can be used to wait for the test to complete + */ + fun runDetached(threadName: String = "TestLooperThread", emulateMainThread: Boolean = false, test: () -> Unit): Condition { + return RunInLooperThreadStatement(threadName, emulateMainThread, test).evaluateDetached() + } + /** * Hold a reference to an object, to prevent it from being GCed, * until after the test completes. @@ -166,7 +173,7 @@ class BlockingLooperThread { synchronized(lock) { while (backgroundHandler == null) { try { - condition.await(5*1000, TimeUnit.MILLISECONDS) + condition.await(5 * 1000, TimeUnit.MILLISECONDS) } catch (e: InterruptedException) { throw AssertionError("Could not acquire the test handler.", e) } @@ -215,9 +222,9 @@ class BlockingLooperThread { } } - private inner class RunInLooperThreadStatement(private val threadName: String, - private val emulateMainThread: Boolean, - private val test: () -> Unit) { + inner class RunInLooperThreadStatement(private val threadName: String, + private val emulateMainThread: Boolean, + private val test: () -> Unit) { fun evaluate() { before() @@ -226,8 +233,16 @@ class BlockingLooperThread { after() } + fun evaluateDetached(): Condition { + before() + AndroidCapabilities.EMULATE_MAIN_THREAD = emulateMainThread + + return runTestDetached(threadName) + } + private fun runTest(threadName: String) { var failure: Throwable? = null + try { val executorService = Executors.newSingleThreadExecutor { runnable -> Thread(runnable, threadName) } val test = TestThread(test) @@ -240,12 +255,21 @@ class BlockingLooperThread { // Tries as hard as possible to close down gracefully, while still keeping all exceptions intact. failure = cleanUp(failure) } + if (failure != null) { throw failure } } - private fun cleanUp(testfailure: Throwable?): Throwable? { + private fun runTestDetached(threadName: String): Condition { + val executorService = Executors.newSingleThreadExecutor { runnable -> Thread(runnable, threadName) } + val test = TestThread(test) + executorService.submit(test) + + return Condition(this, executorService, signalTestCompleted, test) + } + + fun cleanUp(testfailure: Throwable?): Throwable? { return try { after() testfailure @@ -268,7 +292,33 @@ class BlockingLooperThread { } } - private inner class TestThread internal constructor(private val test: () -> Unit) : Runnable, TestHelper.LooperTest { + inner class Condition(private val threadStatement: RunInLooperThreadStatement, + private val executorService: ExecutorService, + private val signalTestCompleted: CountDownLatch, + private val test: LooperTest) { + + fun await() { + var failure: Throwable? = null + + try { + TestHelper.exitOrThrow(executorService, signalTestCompleted, test) + } catch (testFailure: Throwable) { + // These exceptions should only come from TestHelper.awaitOrFail() + failure = testFailure + } finally { + // Tries as hard as possible to close down gracefully, while still keeping all exceptions intact. + failure = threadStatement.cleanUp(failure) + } + + if (failure != null) { + throw failure + } + TestHelper.exitOrThrow(executorService, signalTestCompleted, test) + after() + } + } + + private inner class TestThread(private val test: () -> Unit) : Runnable, LooperTest { private var threadAssertionError: Throwable? = null private var looper: Looper? = null diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt index abcbab2538..af405bcf6b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt @@ -17,6 +17,7 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import io.realm.internal.network.OkHttpNetworkTransport import io.realm.internal.objectstore.OsJavaNetworkTransport import io.realm.mongodb.* import org.junit.After @@ -116,7 +117,7 @@ class UserMetadataTests { } """.trimIndent() } else if (url.endsWith("/location")) { - return Response.httpResponse(200, mapOf(), """ + return OkHttpNetworkTransport.Response.httpResponse(200, mapOf(), """ { "deployment_model" : "GLOBAL", "location": "US-VA", "hostname": "http://localhost:9090", @@ -128,7 +129,11 @@ class UserMetadataTests { } else { fail("Unexpected request url: $url") } - return Response.httpResponse(200, mapOf(Pair("Content-Type", "application/json")), result) + return OkHttpNetworkTransport.Response.httpResponse(200, mapOf(Pair("Content-Type", "application/json")), result) + } + + override fun sendStreamingRequest(request: Request): Response? { + return null } }) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmStreamTaskImplTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmStreamTaskImplTest.kt new file mode 100644 index 0000000000..811a80d6fc --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmStreamTaskImplTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.async + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.internal.objectserver.EventStream +import io.realm.mongodb.mongo.events.BaseChangeEvent +import io.realm.rule.BlockingLooperThread +import junit.framework.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.IllegalStateException +import kotlin.test.assertFailsWith + +@RunWith(AndroidJUnit4::class) +class RealmStreamTaskImplTest { + private val looperThread = BlockingLooperThread() + + @Test + fun asyncExclusiveAccess() { + // Validates that we cannot access synchronously if we are already + // accessing the stream asynchronously. + + val task = RealmEventStreamAsyncTaskImpl("test", object : RealmEventStreamAsyncTaskImpl.Executor() { + override fun run(): EventStream { + return object : EventStream { + var opened: Boolean = true + + override fun getNextEvent(): BaseChangeEvent? { + return null + } + + override fun close() { + opened = false + } + + override fun isOpen(): Boolean { + return opened + } + } + } + }) + + task.get { } + + assertFailsWith { + task.get { } + } + } + + @Test + fun openClose() { + val task = RealmEventStreamTaskImpl("test", object : RealmEventStreamTaskImpl.Executor() { + override fun run(): EventStream { + return object : EventStream { + var opened: Boolean = false + + override fun getNextEvent(): BaseChangeEvent? { + opened = true + return null + } + + override fun close() { + opened = false + } + + override fun isOpen(): Boolean { + return opened + } + } + } + }) + + assertEquals(false, task.isOpen) + assertEquals(false, task.isCancelled) + + task.next + + assertEquals(true, task.isOpen) + assertEquals(false, task.isCancelled) + + task.cancel() + + assertEquals(false, task.isOpen) + assertEquals(true, task.isCancelled) + } + + @Test + fun openCloseAsync() { + val task = RealmEventStreamAsyncTaskImpl("test", object : RealmEventStreamAsyncTaskImpl.Executor() { + override fun run(): EventStream { + return object : EventStream { + var opened: Boolean = false + + override fun getNextEvent(): BaseChangeEvent? { + opened = true + return null + } + + override fun close() { + opened = false + } + + override fun isOpen(): Boolean { + return opened + } + } + } + }) + + assertEquals(false, task.isOpen) + assertEquals(false, task.isCancelled) + + looperThread.runBlocking { + task.get { + looperThread.testComplete() + } + } + + assertEquals(true, task.isOpen) + assertEquals(false, task.isCancelled) + + task.cancel() + + assertEquals(false, task.isOpen) + assertEquals(true, task.isCancelled) + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt index e40d026a71..d4798e3451 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt @@ -29,7 +29,7 @@ import io.realm.testClearApplicationContext fun App.close() { ServerAdmin(this).deleteAllUsers() this.syncManager.testReset() - this.networkTransport.resetHeaders() + this.osApp.networkTransport.resetHeaders() App.CREATED = false RealmExt.testClearApplicationContext() } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt index 584f03c83b..54876e26a9 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt @@ -21,12 +21,16 @@ import io.realm.* import io.realm.mongodb.mongo.MongoClient import io.realm.mongodb.mongo.MongoCollection import io.realm.mongodb.mongo.MongoNamespace +import io.realm.mongodb.mongo.events.BaseChangeEvent.OperationType import io.realm.mongodb.mongo.options.CountOptions import io.realm.mongodb.mongo.options.FindOneAndModifyOptions import io.realm.mongodb.mongo.options.FindOptions import io.realm.mongodb.mongo.options.UpdateOptions +import io.realm.rule.BlockingLooperThread import io.realm.util.assertFailsWithErrorCode import io.realm.util.mongodb.CustomType +import org.bson.BsonDocument +import org.bson.BsonString import org.bson.Document import org.bson.codecs.configuration.CodecRegistries import org.bson.types.ObjectId @@ -34,17 +38,19 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import java.io.IOException import kotlin.test.* private const val COLLECTION_NAME = "mongo_data" // name of collection used by tests @RunWith(AndroidJUnit4::class) class MongoClientTest { - private lateinit var app: TestApp private lateinit var user: User private lateinit var client: MongoClient + private val looperThread = BlockingLooperThread() + @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) @@ -900,6 +906,391 @@ class MongoClientTest { } } + private fun assertDocumentEquals(expected: Document, actual: Document) { + // Accounts for the missing _id field in the expected document + assertTrue { + actual.remove("_id") != null + } + + assertEquals(expected.keys.size, actual.keys.size) + + for (key in expected.keys) { + assertTrue(actual.keys.contains(key)) + assertEquals(expected[key], actual[key]) + } + } + + @Test + fun watchStreamSynchronous() { + with(getCollectionInternal()) { + val insertedDocument = Document("watch", "1") + .apply { + this["num"] = 1 + } + + val updatedDocument = Document("watch", "1") + .apply { + this["num"] = 2 + } + + + val watcher = this.watch() + + val condition = looperThread.runDetached { + watcher.next.let { changeEvent -> + assertEquals(OperationType.INSERT, changeEvent.operationType) + assertDocumentEquals(insertedDocument, changeEvent.fullDocument!!) + } + + watcher.next.let { changeEvent -> + assertEquals(OperationType.REPLACE, changeEvent.operationType) + assertDocumentEquals(updatedDocument, changeEvent.fullDocument!!) + } + + watcher.next.let { changeEvent -> + assertEquals(OperationType.DELETE, changeEvent.operationType) + assertNull(changeEvent.fullDocument) + } + + looperThread.testComplete() + } + + // Busy wait till watcher is ready to receive updates. + // It syncs the event producer thread (current thread) with + // the event consumer thread. + while (!watcher.isOpen) { + } + + this.insertOne(insertedDocument).get() + + val filter = Document("watch", "1") + this.updateOne(filter, updatedDocument).get() + this.deleteOne(filter).get() + + condition.await() + } + } + + @Test + fun watchStreamDocumentsFilterSynchronous() { + with(getCollectionInternal()) { + val type1 = Document("type", "1") + .apply { + this["num"] = 1 + } + + val type2 = Document("type", "2") + .apply { + this["num"] = 1 + } + + val filter = Document("fullDocument.type", "1") + val watcher = this.watchWithFilter(filter) + + val condition = looperThread.runDetached { + watcher.next.let { changeEvent -> + assertEquals(OperationType.INSERT, changeEvent.operationType) + assertEquals("1", changeEvent.fullDocument!!["type"]) + } + + watcher.cancel() + looperThread.testComplete() + } + + // Busy wait till watcher is ready to receive updates. + // It syncs the event producer thread (current thread) with + // the event consumer thread. + while (!watcher.isOpen) { + } + + this.insertOne(type2).get() + this.insertOne(type1).get() + + condition.await() + } + } + + @Test + fun watchStreamBsonDocumentFilterSynchronous() { + with(getCollectionInternal()) { + val type1 = Document("type", "1") + .apply { + this["num"] = 1 + } + + val type2 = Document("type", "2") + .apply { + this["num"] = 1 + } + + val filter = BsonDocument("fullDocument.type", BsonString("1")) + val watcher = this.watchWithFilter(filter) + + val condition = looperThread.runDetached { + watcher.next.let { changeEvent -> + assertEquals(OperationType.INSERT, changeEvent.operationType) + assertEquals("1", changeEvent.fullDocument!!["type"]) + } + + watcher.cancel() + looperThread.testComplete() + + } + + // Busy wait till watcher is ready to receive updates. + // It syncs the event producer thread (current thread) with + // the event consumer thread. + while (!watcher.isOpen) { + } + + this.insertOne(type2).get() + this.insertOne(type1).get() + + condition.await() + } + } + + @Test + fun watchStreamObjectIdsSynchronous() { + with(getCollectionInternal()) { + val doc1 = Document("document", "1") + .apply { + this["num"] = 1 + } + + val doc2 = Document("document", "2") + .apply { + this["num"] = 1 + } + + val doc1Id = this.insertOne(doc1).get() + val doc2Id = this.insertOne(doc2).get() + + val watcherObjectId = this.watch(doc1Id.insertedId.asObjectId().value) + + val condition = looperThread.runDetached { + watcherObjectId.next.let { changeEvent -> + assertEquals(OperationType.REPLACE, changeEvent.operationType) + assertEquals("1", changeEvent.fullDocument!!["document"]) + } + watcherObjectId.cancel() + + looperThread.testComplete() + } + + // Busy wait till watcher is ready to receive updates. + // It syncs the event producer thread (current thread) with + // the event consumer thread. + while (!watcherObjectId.isOpen) { + } + + doc1.apply { + this["num"] = 2 + } + + doc2.apply { + this["num"] = 2 + } + + val filter1 = Document("_id", doc1Id.insertedId) + val filter2 = Document("_id", doc2Id.insertedId) + + this.updateOne(filter2, doc2).get() + this.updateOne(filter1, doc1).get() + + condition.await() + } + } + + @Test + fun watchStreamIdsSynchronous() { + with(getCollectionInternal()) { + val doc1 = Document("document", "1") + .apply { + this["num"] = 1 + } + + val doc2 = Document("document", "2") + .apply { + this["num"] = 1 + } + + val doc1Id = this.insertOne(doc1).get() + val doc2Id = this.insertOne(doc2).get() + + val watcherBsonValue = this.watch(doc1Id.insertedId) + + val condition = looperThread.runDetached { + watcherBsonValue.next.let { changeEvent -> + assertEquals(OperationType.REPLACE, changeEvent.operationType) + assertEquals("1", changeEvent.fullDocument!!["document"]) + } + + watcherBsonValue.cancel() + looperThread.testComplete() + } + + // Busy wait till watcher is ready to receive updates. + // It syncs the event producer thread (current thread) with + // the event consumer thread. + while (!watcherBsonValue.isOpen) { + } + + doc1.apply { + this["num"] = 2 + } + + doc2.apply { + this["num"] = 2 + } + + val filter1 = Document("_id", doc1Id.insertedId) + val filter2 = Document("_id", doc2Id.insertedId) + + this.updateOne(filter2, doc2).get() + this.updateOne(filter1, doc1).get() + + condition.await() + } + + } + + @Test + fun watchStreamAsynchronous() { + looperThread.runBlocking { + with(getCollectionInternal()) { + val insertedDocument = Document("watch", "1") + .apply { + this["num"] = 1 + } + + val updatedDocument = Document("watch", "1") + .apply { + this["num"] = 2 + } + + + val watcher = this.watchAsync() + + var eventCount = 0 + watcher.get { it -> + if (it.isSuccess) { + it.get().let { changeEvent -> + when (eventCount) { + 0 -> { + assertEquals(OperationType.INSERT, changeEvent.operationType) + assertDocumentEquals(insertedDocument, changeEvent.fullDocument!!) + } + 1 -> { + assertEquals(OperationType.REPLACE, changeEvent.operationType) + assertDocumentEquals(updatedDocument, changeEvent.fullDocument!!) + } + 2 -> { + assertEquals(OperationType.DELETE, changeEvent.operationType) + assertNull(changeEvent.fullDocument) + + watcher.cancel() + } + } + } + + eventCount++ + } else { + when (it.error.errorCode) { + ErrorCode.NETWORK_IO_EXCEPTION -> looperThread.testComplete() + else -> fail() + } + looperThread.testComplete() + } + } + + // Busy wait till watcher is ready to receive updates. + // It syncs the event producer thread (current thread) with + // the event consumer thread. + while (!watcher.isOpen) { + } + + this.insertOne(insertedDocument).get() + + val filter = Document("watch", "1") + this.updateOne(filter, updatedDocument).get() + this.deleteOne(filter).get() + } + } + } + + @Test + fun watchStreamCancelSynchronous() { + with(getCollectionInternal()) { + val watcher = this.watch() + + val condition = looperThread.runDetached { + assertFailsWith { + watcher.next + } + + assertEquals(false, watcher.isOpen) + assertEquals(true, watcher.isCancelled) + + looperThread.testComplete() + } + + // Busy wait till watcher is ready to receive updates. + // It syncs the event producer thread (current thread) with + // the event consumer thread. + while (!watcher.isOpen) { + } + + watcher.cancel() + + condition.await() + } + } + + @Test + fun watchStreamCancelAsynchronous() { + looperThread.runBlocking { + with(getCollectionInternal()) { + val watcher = this.watchAsync() + + watcher.get { + if (it.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.NETWORK_IO_EXCEPTION, it.error.errorCode) + + assertEquals(false, watcher.isOpen) + assertEquals(true, watcher.isCancelled) + + looperThread.testComplete() + } + } + + // Busy wait till watcher is ready to receive updates. + // It syncs the event producer thread (current thread) with + // the event consumer thread. + while (!watcher.isOpen) { + } + + watcher.cancel() + } + } + } + + @Test + fun watchError() { + with(getCollectionInternal()) { + val watcher = this.watch() + + assertFailsWith { + watcher.next + } + + assertEquals(false, watcher.isOpen) + assertEquals(false, watcher.isCancelled) + } + } + @Test fun findOneAndDelete() { with(getCollectionInternal()) { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt similarity index 99% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt index cfa1df182e..f36b0a5998 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.realm +package io.realm.mongodb import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import io.realm.* import io.realm.admin.ServerAdmin -import io.realm.mongodb.* import io.realm.mongodb.auth.ApiKeyAuth import io.realm.mongodb.auth.UserApiKey import io.realm.rule.BlockingLooperThread diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt index 00896e011c..6e5ff7639c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OkHttpNetworkTransportTests.kt @@ -19,7 +19,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm import io.realm.internal.network.OkHttpNetworkTransport -import io.realm.internal.network.LoggingInterceptor import io.realm.internal.objectstore.OsJavaNetworkTransport import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -118,6 +117,28 @@ class OkHttpNetworkTransportTests { } } + + // Validate that we can access a text/event-stream streamed response + @Test + fun streamRequest() { + val url = "$baseUrl/watcher" + + val headers = mapOf( + Pair("Accept", "text/event-stream") + ) + + val request = OsJavaNetworkTransport.Request("get", url, headers, "") + val response = transport.sendStreamingRequest(request) + + assertEquals(200, response.httpResponseCode) + assertEquals(0, response.customResponseCode) + assertEquals("hello world 1", response.readBodyLine()) + assertEquals("hello world 2", response.readBodyLine()) + + response.close() + } + + @Test fun requestInterrupted() { val url = "$baseUrl/okhttp?success=true" diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt index a63ce80bf7..a9aabc6d91 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -18,6 +18,7 @@ package io.realm.transport import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.* +import io.realm.internal.network.OkHttpNetworkTransport import io.realm.internal.objectstore.OsJavaNetworkTransport import io.realm.mongodb.* import org.junit.After @@ -95,7 +96,7 @@ class OsJavaNetworkTransportTests { } """.trimIndent() } else if (url.endsWith("/location")) { - return Response.httpResponse(200, mapOf(), """ + return OkHttpNetworkTransport.Response.httpResponse(200, mapOf(), """ { "deployment_model" : "GLOBAL", "location": "US-VA", "hostname": "http://localhost:9090", @@ -105,7 +106,11 @@ class OsJavaNetworkTransportTests { } else { fail("Unexpected request url: $url") } - return Response.httpResponse(200, successHeaders, result) + return OkHttpNetworkTransport.Response.httpResponse(200, successHeaders, result) + } + + override fun sendStreamingRequest(request: Request): Response { + throw IllegalAccessError() } }) @@ -127,7 +132,11 @@ class OsJavaNetworkTransportTests { "link": "http://localhost:9090/some_link" } """.trimIndent() - return Response.httpResponse(200, successHeaders, result) + return OkHttpNetworkTransport.Response.httpResponse(200, successHeaders, result) + } + + override fun sendStreamingRequest(request: Request): Response { + throw IllegalAccessError() } }) @@ -147,7 +156,11 @@ class OsJavaNetworkTransportTests { fun requestFailWithHttpError() { app = TestApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { - return Response.httpResponse(500, mapOf(), "Boom!") + return OkHttpNetworkTransport.Response.httpResponse(500, mapOf(), "Boom!") + } + + override fun sendStreamingRequest(request: Request): Response { + throw IllegalAccessError() } }) @@ -166,7 +179,11 @@ class OsJavaNetworkTransportTests { fun requestFailWithCustomError() { app = TestApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { - return Response.ioError("Boom!") + return OkHttpNetworkTransport.Response.ioError("Boom!") + } + + override fun sendStreamingRequest(request: Request): Response { + throw IllegalAccessError() } }) @@ -189,6 +206,10 @@ class OsJavaNetworkTransportTests { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { throw IllegalStateException("Boom!") } + + override fun sendStreamingRequest(request: Request): Response { + throw IllegalAccessError() + } }) val creds = Credentials.anonymous() @@ -207,6 +228,10 @@ class OsJavaNetworkTransportTests { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { throw Error("Boom!") } + + override fun sendStreamingRequest(request: Request): Response { + throw IllegalAccessError() + } }) val creds = Credentials.anonymous() diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index ca9cba332f..b05a22231e 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -110,11 +110,13 @@ if (build_SYNC) io.realm.mongodb.sync.Sync io.realm.mongodb.sync.SyncSession io.realm.mongodb.User + io.realm.internal.objectstore.OsApp io.realm.internal.objectstore.OsAppCredentials io.realm.internal.objectstore.OsAsyncOpenTask io.realm.internal.objectstore.OsJavaNetworkTransport io.realm.internal.objectstore.OsMongoClient io.realm.internal.objectstore.OsMongoCollection + io.realm.internal.objectstore.OsWatchStream io.realm.internal.objectstore.OsMongoDatabase io.realm.internal.objectstore.OsPush io.realm.internal.objectstore.OsSyncUser @@ -200,7 +202,6 @@ file(GLOB jni_SRC # Those source file are only needed for sync. if (NOT build_SYNC) list(REMOVE_ITEM jni_SRC - ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_App.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_User.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_FunctionsImpl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_EmailPasswordAuthImpl.cpp @@ -208,11 +209,13 @@ if (NOT build_SYNC) ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_sync_ClientResetRequiredError.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_sync_Sync.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_sync_SyncSession.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsApp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAsyncOpenTask.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsAppCredentials.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsJavaNetworkTransport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoClient.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoCollection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsWatchStream.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsMongoDatabase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsPush.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_internal_objectstore_OsSyncUser.cpp diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp similarity index 64% rename from realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp rename to realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp index bfb49f968b..9fc29a1935 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_App.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io_realm_mongodb_App.h" +#include "io_realm_internal_objectstore_OsApp.h" #include "java_network_transport.hpp" #include "util.hpp" @@ -25,6 +25,8 @@ #include #include +#include + using namespace realm; using namespace realm::app; using namespace realm::jni_util; @@ -83,19 +85,28 @@ struct AndroidSyncLoggerFactory : public realm::SyncLoggerFactory { } } s_sync_logger_factory; -JNIEXPORT jlong JNICALL Java_io_realm_mongodb_App_nativeCreate(JNIEnv* env, jobject obj, - jstring j_app_id, - jstring j_base_url, - jstring j_app_name, - jstring j_app_version, - jlong j_request_timeout_ms, - jbyteArray j_encryption_key, - jstring j_sync_base_dir, - jstring j_user_agent_binding_info, - jstring j_user_agent_application_info, - jstring j_platform, - jstring j_platform_version, - jstring j_sdk_version) +static void finalize_client(jlong ptr) { + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsApp_nativeGetFinalizerMethodPtr(JNIEnv*, jclass) { + return reinterpret_cast(&finalize_client); +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsApp_nativeCreate(JNIEnv* env, jobject obj, + jstring j_app_id, + jstring j_base_url, + jstring j_app_name, + jstring j_app_version, + jlong j_request_timeout_ms, + jbyteArray j_encryption_key, + jstring j_sync_base_dir, + jstring j_user_agent_binding_info, + jstring j_user_agent_application_info, + jstring j_platform, + jstring j_platform_version, + jstring j_sdk_version) { try { // App Config @@ -159,7 +170,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_App_nativeCreate(JNIEnv* env, jobj } -JNIEXPORT void JNICALL Java_io_realm_mongodb_App_nativeLogin(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_credentials_ptr, jobject j_callback) +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsApp_nativeLogin(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_credentials_ptr, jobject j_callback) { try { auto app = *reinterpret_cast*>(j_app_ptr); @@ -174,7 +185,7 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_App_nativeLogin(JNIEnv* env, jclass CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_mongodb_App_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsApp_nativeLogOut(JNIEnv* env, jclass, jlong j_app_ptr, jlong j_user_ptr, jobject j_callback) { try { auto app = *reinterpret_cast*>(j_app_ptr); @@ -184,7 +195,7 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_App_nativeLogOut(JNIEnv* env, jclas CATCH_STD() } -JNIEXPORT jobject JNICALL Java_io_realm_mongodb_App_nativeCurrentUser(JNIEnv* env, jclass, jlong j_app_ptr) +JNIEXPORT jobject JNICALL Java_io_realm_internal_objectstore_OsApp_nativeCurrentUser(JNIEnv* env, jclass, jlong j_app_ptr) { try { auto app = *reinterpret_cast*>(j_app_ptr); @@ -201,7 +212,7 @@ JNIEXPORT jobject JNICALL Java_io_realm_mongodb_App_nativeCurrentUser(JNIEnv* en return NULL; } -JNIEXPORT jlongArray JNICALL Java_io_realm_mongodb_App_nativeGetAllUsers(JNIEnv* env, jclass, jlong j_app_ptr) +JNIEXPORT jlongArray JNICALL Java_io_realm_internal_objectstore_OsApp_nativeGetAllUsers(JNIEnv* env, jclass, jlong j_app_ptr) { try { auto app = *reinterpret_cast*>(j_app_ptr); @@ -228,10 +239,10 @@ JNIEXPORT jlongArray JNICALL Java_io_realm_mongodb_App_nativeGetAllUsers(JNIEnv* return nullptr; } -JNIEXPORT void JNICALL Java_io_realm_mongodb_App_nativeSwitchUser(JNIEnv* env, - jclass, - jlong j_app_ptr, - jlong j_user_ptr) +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsApp_nativeSwitchUser(JNIEnv* env, + jclass, + jlong j_app_ptr, + jlong j_user_ptr) { try { auto app = *reinterpret_cast*>(j_app_ptr); @@ -241,3 +252,58 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_App_nativeSwitchUser(JNIEnv* env, CATCH_STD() } +JNIEXPORT jobject JNICALL Java_io_realm_internal_objectstore_OsApp_nativeMakeStreamingRequest(JNIEnv* env, + jclass, + jlong j_app_ptr, + jlong j_user_ptr, + jstring j_function_name, + jstring j_bson_args, + jstring j_service_name) +{ + try { + auto app = *reinterpret_cast*>(j_app_ptr); + auto user = *reinterpret_cast*>(j_user_ptr); + + JStringAccessor function_name(env, j_function_name); + JStringAccessor service_name(env, j_service_name); + + bson::BsonArray filter(JniBsonProtocol::parse_checked(env, j_bson_args, Bson::Type::Array, "BSON filter must be an Array")); + + const Request &request = app->make_streaming_request(user, function_name, filter, + std::string(service_name)); + + jstring j_method; + + switch (request.method){ + case HttpMethod::get: + j_method = env->NewStringUTF("get"); + break; + case HttpMethod::post: + j_method = env->NewStringUTF("post"); + break; + case HttpMethod::patch: + j_method = env->NewStringUTF("patch"); + break; + case HttpMethod::put: + j_method = env->NewStringUTF("put"); + break; + case HttpMethod::del: + j_method = env->NewStringUTF("del"); + break; + } + + jstring j_url = env->NewStringUTF(request.url.c_str()); + jobject j_headers = JniUtils::to_hash_map(env, request.headers); + jstring j_body = env->NewStringUTF(request.body.c_str()); + + static JavaClass request_class(env, "io/realm/internal/objectstore/OsJavaNetworkTransport$Request"); + static JavaMethod request_constructor(env, request_class, "","(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;)V"); + jobject j_request = env->NewObject(request_class, request_constructor, j_method, j_url, j_headers, j_body); + + return j_request; + } + CATCH_STD() + + return nullptr; +} + diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp new file mode 100644 index 0000000000..2f46deb4c0 --- /dev/null +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp @@ -0,0 +1,128 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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 "io_realm_internal_objectstore_OsWatchStream.h" + +#include "java_class_global_def.hpp" +#include "jni_util/bson_util.hpp" + +#include + +using namespace realm; +using namespace realm::app; +using namespace realm::bson; +using namespace realm::jni_util; +using namespace realm::_impl; + +static void finalize_watchstream(jlong ptr) { + delete reinterpret_cast(ptr); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsWatchStream_nativeGetFinalizerMethodPtr(JNIEnv *, jclass) { + return reinterpret_cast(&finalize_watchstream); +} + +JNIEXPORT jlong JNICALL +Java_io_realm_internal_objectstore_OsWatchStream_nativeCreateWatchStream(JNIEnv *env, jclass) { + try { + return (jlong) new WatchStream(); + } + CATCH_STD() + + return 0; +} + +JNIEXPORT void JNICALL +Java_io_realm_internal_objectstore_OsWatchStream_nativeFeedLine(JNIEnv *env, jclass, + jlong j_watch_stream_ptr, + jstring j_line) { + try { + WatchStream *watch_stream = reinterpret_cast(j_watch_stream_ptr); + JStringAccessor line(env, j_line); + + watch_stream->feed_line(std::string(line)); + } + CATCH_STD() +} + +JNIEXPORT jstring JNICALL +Java_io_realm_internal_objectstore_OsWatchStream_nativeGetState(JNIEnv *env, jclass, + jlong j_watch_stream_ptr) { + try { + WatchStream *watch_stream = reinterpret_cast(j_watch_stream_ptr); + + switch (watch_stream->state()) { + case WatchStream::NEED_DATA: + return env->NewStringUTF("NEED_DATA"); + case WatchStream::HAVE_EVENT: + return env->NewStringUTF("HAVE_EVENT"); + case WatchStream::HAVE_ERROR: + return env->NewStringUTF("HAVE_ERROR"); + } + } + CATCH_STD() + + return nullptr; +} + +JNIEXPORT jstring JNICALL +Java_io_realm_internal_objectstore_OsWatchStream_nativeGetNextEvent(JNIEnv *env, jclass, + jlong j_watch_stream_ptr) { + try { + WatchStream *watch_stream = reinterpret_cast(j_watch_stream_ptr); + return JniBsonProtocol::bson_to_jstring(env, watch_stream->next_event()); + } + CATCH_STD() + + return nullptr; +} + + +JNIEXPORT jthrowable JNICALL +Java_io_realm_internal_objectstore_OsWatchStream_nativeGetError(JNIEnv *env, jclass, + jlong j_watch_stream_ptr) { + try { + WatchStream *watch_stream = reinterpret_cast(j_watch_stream_ptr); + + auto app_error = watch_stream->error(); + + jstring error_code_category = env->NewStringUTF(app_error.error_code.category().name()); + jstring error_code_message = env->NewStringUTF(app_error.error_code.message().c_str()); + + jstring app_error_message = env->NewStringUTF(app_error.message.c_str()); + + static JavaClass app_exception_class(env, "io/realm/mongodb/AppException"); + static JavaMethod app_exception_constructor(env, app_exception_class, "", + "(Lio/realm/mongodb/ErrorCode;Ljava/lang/String;)V"); + + static JavaClass error_code_class(env, "io/realm/mongodb/ErrorCode"); + static JavaMethod error_code_constructor(env, error_code_class, "fromNativeError", + "(Ljava/lang/String;I)Lio/realm/mongodb/ErrorCode;", + true); + + jobject j_error_code = env->CallStaticObjectMethod(error_code_class, error_code_constructor, + error_code_category, error_code_message); + jobject j_app_error = env->NewObject(app_exception_class, app_exception_constructor, + j_error_code, app_error_message); + + return static_cast(j_app_error); + } + CATCH_STD() + + return nullptr; +} + diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp index 61f92910f1..824550813f 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.cpp @@ -15,12 +15,15 @@ */ #include "jni_utils.hpp" +#include "java_class.hpp" +#include "java_method.hpp" #include #include using namespace realm::jni_util; +using namespace std; static std::unique_ptr s_instance; @@ -65,3 +68,27 @@ void JniUtils::keep_global_ref(JavaGlobalRefByMove& ref) s_instance->m_global_refs.push_back(std::move(ref)); } +jobject JniUtils::to_hash_map(JNIEnv* env, std::map map) +{ + static JavaClass hash_map_class(env, "java/util/HashMap"); + static JavaMethod hash_map_constructor(env, hash_map_class, "", "(I)V"); + static JavaMethod hash_map_put(env, hash_map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + + jobject hash_map = env->NewObject(hash_map_class, hash_map_constructor, (jint) map.size()); + + for (const auto& it : map) + { + jstring key = env->NewStringUTF(it.first.c_str()); + jstring value = env->NewStringUTF(it.second.c_str()); + + env->CallObjectMethod(hash_map, hash_map_put, + key, + value); + + env->DeleteLocalRef(key); + env->DeleteLocalRef(value); + } + + return hash_map; +} + diff --git a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp index 9cd63b69b6..986568fd85 100644 --- a/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/jni_utils.hpp @@ -20,6 +20,7 @@ #include #include +#include #include "java_global_ref_by_move.hpp" #include "java_global_ref_by_copy.hpp" @@ -46,6 +47,8 @@ class JniUtils { static void detach_current_thread(); // Keep the given global reference until JNI_OnUnload is called. static void keep_global_ref(JavaGlobalRefByMove& ref); + // Transforms a string map into a Java String HashMap + static jobject to_hash_map(JNIEnv* env, std::map map); private: JniUtils(JavaVM* vm, jint vm_version) noexcept diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 8fe0faff42..83ae8289b2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.Locale; +import java.util.Map; import java.util.Set; import javax.annotation.Nullable; @@ -220,4 +221,17 @@ public static synchronized boolean isRxJavaAvailable() { return rxJavaAvailable; } + /** + * Validates that a key is present in a given map + * + * @param key the key to expect. + * @param map the map to search. + * @param argName the map argument name + * @throws IllegalArgumentException if key is not present. + */ + public static void checkContainsKey(final String key, final Map map, final String argName) { + if (!map.containsKey(key)) { + throw new IllegalArgumentException("Key '" + key + "' required in '"+ argName +"'."); + } + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmEventStreamAsyncTaskImpl.java b/realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmEventStreamAsyncTaskImpl.java new file mode 100644 index 0000000000..e30dfa5f3a --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmEventStreamAsyncTaskImpl.java @@ -0,0 +1,101 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.async; + +import java.io.IOException; + +import io.realm.internal.Util; +import io.realm.internal.objectserver.EventStream; +import io.realm.mongodb.App; +import io.realm.mongodb.AppException; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.RealmEventStreamAsyncTask; +import io.realm.mongodb.mongo.events.BaseChangeEvent; + +public class RealmEventStreamAsyncTaskImpl implements RealmEventStreamAsyncTask { + private final String name; + private final Executor executor; + private volatile EventStream eventStream; + private volatile boolean isCancelled; + private Thread thread; + + public RealmEventStreamAsyncTaskImpl(final String name, final Executor executor) { + Util.checkNull(executor, "name"); + Util.checkNull(executor, "executor"); + + this.executor = executor; + this.name = name; + } + + @Override + public synchronized void get(App.Callback> callback) throws IllegalStateException { + Util.checkNull(callback, "callback"); + + if (thread != null) { + throw new IllegalStateException("Resource already open"); + } else { + thread = new Thread(new Runnable() { + @Override + public void run() { + try { + eventStream = executor.run(); + + while (true) { + BaseChangeEvent nextEvent = eventStream.getNextEvent(); + callback.onResult(App.Result.withResult(nextEvent)); + } + } catch (AppException exception) { + callback.onResult(App.Result.withError(exception)); + } catch (IOException exception) { + AppException appException = new AppException(ErrorCode.NETWORK_IO_EXCEPTION, exception); + callback.onResult(App.Result.withError(appException)); + } + } + }, String.format("RealmStreamTask|%s", name)); + + thread.start(); + } + } + + @Override + public boolean isOpen() { + return (eventStream != null) && eventStream.isOpen(); + } + + @Override + public void cancel() { + if (eventStream != null) { + isCancelled = true; + eventStream.close(); + } + } + + @Override + public boolean isCancelled() { + return isCancelled; + } + + public abstract static class Executor { + + /** + * Executes the code block. + * + * @return the result yielded by the task. + */ + public abstract EventStream run() throws IOException; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmEventStreamTaskImpl.java b/realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmEventStreamTaskImpl.java new file mode 100644 index 0000000000..69aa82eac5 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/async/RealmEventStreamTaskImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.async; + +import java.io.IOException; + +import io.realm.internal.Util; +import io.realm.internal.objectserver.EventStream; +import io.realm.mongodb.AppException; +import io.realm.mongodb.RealmEventStreamTask; +import io.realm.mongodb.mongo.events.BaseChangeEvent; + +public class RealmEventStreamTaskImpl implements RealmEventStreamTask { + private final String name; + private final Executor executor; + private volatile EventStream eventStream; + private volatile boolean isCancelled; + + public RealmEventStreamTaskImpl(final String name, final Executor executor) { + Util.checkNull(executor, "name"); + Util.checkNull(executor, "executor"); + + this.executor = executor; + this.name = name; + } + + private EventStream getEventStream() throws IOException { + if (eventStream == null) { + eventStream = executor.run(); + } + + return this.eventStream; + } + + @Override + public synchronized BaseChangeEvent getNext() throws AppException, IOException { + eventStream = getEventStream(); + return eventStream.getNextEvent(); + } + + @Override + public boolean isOpen() { + return (eventStream != null) && eventStream.isOpen(); + } + + @Override + public void cancel() { + if (eventStream != null) { + isCancelled = true; + eventStream.close(); + } + } + + @Override + public boolean isCancelled() { + return isCancelled; + } + + public abstract static class Executor { + + /** + * Executes the code block. + * + * @return the result yielded by the task. + */ + public abstract EventStream run() throws IOException; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/events/ChangeEvent.java b/realm/realm-library/src/objectServer/java/io/realm/internal/events/ChangeEvent.java new file mode 100644 index 0000000000..0c7b0d5ca7 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/events/ChangeEvent.java @@ -0,0 +1,251 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.events; + +import org.bson.BsonBoolean; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.configuration.CodecRegistry; + +import io.realm.mongodb.AppException; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.mongo.MongoNamespace; +import io.realm.mongodb.mongo.events.BaseChangeEvent; +import io.realm.mongodb.mongo.events.UpdateDescription; + +import static io.realm.internal.Util.checkContainsKey; +import static io.realm.mongodb.mongo.events.BaseChangeEvent.OperationType.DELETE; +import static io.realm.mongodb.mongo.events.BaseChangeEvent.OperationType.INSERT; +import static io.realm.mongodb.mongo.events.BaseChangeEvent.OperationType.REPLACE; +import static io.realm.mongodb.mongo.events.BaseChangeEvent.OperationType.UNKNOWN; +import static io.realm.mongodb.mongo.events.BaseChangeEvent.OperationType.UPDATE; + +/** + * The representation of a MongoDB Realm change event + * + * @param the full document type + * @see Realm - Database change events + */ + +public class ChangeEvent extends BaseChangeEvent { + + private final BsonDocument id; // Metadata related to the operation (the resumeToken). + private final MongoNamespace ns; + + /** + * Constructs a change event. + * + * @param id The id of the change event. + * @param operationType The operation type represented by the change event. + * @param fullDocument The full document at some point after the change is applied. + * @param ns The namespace (database and collection) of the document. + * @param documentKey The id if the underlying document that changed. + * @param updateDescription The description of what has changed (for updates only). + * @param hasUncommittedWrites Whether this represents a local uncommitted write. + */ + private ChangeEvent( + final BsonDocument id, + final OperationType operationType, + final DocumentT fullDocument, + final MongoNamespace ns, + final BsonDocument documentKey, + final UpdateDescription updateDescription, + final boolean hasUncommittedWrites + ) { + super(operationType, fullDocument, documentKey, updateDescription, hasUncommittedWrites); + + this.id = id; + this.ns = ns; + } + + /** + * Returns the ID of the change event itself. + * + * @return the id of this change event. + */ + public BsonDocument getId() { + return id; + } + + /** + * The namespace the change relates to. + * + * @return the namespace. + */ + public MongoNamespace getNamespace() { + return ns; + } + + /** + * Creates a copy of this change event with uncommitted writes flag set to false. + * + * @return new change event without uncommitted writes flag + */ + public ChangeEvent withoutUncommittedWrites() { + return new ChangeEvent<>(this.getId(), + this.getOperationType(), + this.getFullDocument(), + this.getNamespace(), + this.getDocumentKey(), + this.getUpdateDescription(), + false); + } + + /** + * Serializes this change event into a {@link BsonDocument}. + * + * @return the serialized document. + */ + @Override + public BsonDocument toBsonDocument() { + final BsonDocument asDoc = new BsonDocument(); + asDoc.put(Fields.ID_FIELD, id); + + asDoc.put(Fields.OPERATION_TYPE_FIELD, new BsonString(toRemote(getOperationType()))); + + final BsonDocument nsDoc = new BsonDocument(); + nsDoc.put(Fields.NS_DB_FIELD, new BsonString(ns.getDatabaseName())); + nsDoc.put(Fields.NS_COLL_FIELD, new BsonString(getNamespace().getCollectionName())); + asDoc.put(Fields.NS_FIELD, nsDoc); + + asDoc.put(Fields.DOCUMENT_KEY_FIELD, getDocumentKey()); + DocumentT fullDocument = getFullDocument(); + + if ((fullDocument instanceof BsonValue) && ((BsonValue) fullDocument).isDocument()) { + asDoc.put(Fields.FULL_DOCUMENT_FIELD, (BsonValue) fullDocument); + } + + UpdateDescription updateDescription = getUpdateDescription(); + if (updateDescription != null) { + asDoc.put(Fields.UPDATE_DESCRIPTION_FIELD, updateDescription.toBsonDocument()); + } + + asDoc.put(Fields.WRITE_PENDING_FIELD, new BsonBoolean(hasUncommittedWrites())); + return asDoc; + } + + /** + * Deserializes a {@link BsonDocument} into an instance of change event. + * + * @param document the serialized document + * @return the deserialized change event + */ + static ChangeEvent fromBsonDocument(final BsonDocument document, final Class documentClass, CodecRegistry codecRegistry) { + try { + checkContainsKey(Fields.ID_FIELD, document, "document"); + checkContainsKey(Fields.OPERATION_TYPE_FIELD, document, "document"); + checkContainsKey(Fields.NS_FIELD, document, "document"); + checkContainsKey(Fields.DOCUMENT_KEY_FIELD, document, "document"); + } catch (IllegalArgumentException exception) { + throw new AppException(ErrorCode.EVENT_DESERIALIZING, exception); + } + + final BsonDocument nsDoc = document.getDocument(Fields.NS_FIELD); + + final UpdateDescription updateDescription; + if (document.containsKey(Fields.UPDATE_DESCRIPTION_FIELD)) { + updateDescription = UpdateDescription.fromBsonDocument( + document.getDocument(Fields.UPDATE_DESCRIPTION_FIELD) + ); + } else { + updateDescription = null; + } + + final T fullDocument; + + if (document.containsKey(Fields.FULL_DOCUMENT_FIELD)) { + final BsonValue fdVal = document.get(Fields.FULL_DOCUMENT_FIELD); + if (fdVal.isDocument()) { + fullDocument = codecRegistry.get(documentClass).decode(fdVal.asDocument().asBsonReader(), DecoderContext.builder().build()); + } else { + fullDocument = null; + } + } else { + fullDocument = null; + } + + return new ChangeEvent<>( + document.getDocument(Fields.ID_FIELD), + fromRemote(document.getString(Fields.OPERATION_TYPE_FIELD).getValue()), + fullDocument, + new MongoNamespace( + nsDoc.getString(Fields.NS_DB_FIELD).getValue(), + nsDoc.getString(Fields.NS_COLL_FIELD).getValue()), + document.getDocument(Fields.DOCUMENT_KEY_FIELD), + updateDescription, + document.getBoolean(Fields.WRITE_PENDING_FIELD, BsonBoolean.FALSE).getValue()); + } + + private static final class Fields { + static final String ID_FIELD = "_id"; + static final String OPERATION_TYPE_FIELD = "operationType"; + static final String FULL_DOCUMENT_FIELD = "fullDocument"; + static final String DOCUMENT_KEY_FIELD = "documentKey"; + + static final String NS_FIELD = "ns"; + static final String NS_DB_FIELD = "db"; + static final String NS_COLL_FIELD = "coll"; + + static final String UPDATE_DESCRIPTION_FIELD = "updateDescription"; + static final String WRITE_PENDING_FIELD = "writePending"; + } + + /** + * Returns the appropriate local operation type enum value based on the remote operation type + * string from a change stream event. + * + * @param type the string description of the operation type. + * @return the operation type. + */ + private static OperationType fromRemote(final String type) { + switch (type) { + case "insert": + return INSERT; + case "delete": + return DELETE; + case "replace": + return REPLACE; + case "update": + return UPDATE; + default: + return UNKNOWN; + } + } + + /** + * Converts this operation to the remote string representation of the operation as + * represented in a {@link ChangeEvent} from a remote cluster. + * + * @return the remote representation of the update operation. + */ + private String toRemote(OperationType operationType) { + switch (operationType) { + case INSERT: + return "insert"; + case DELETE: + return "delete"; + case REPLACE: + return "replace"; + case UPDATE: + return "update"; + default: + return "unknown"; + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/events/NetworkEventStream.java b/realm/realm-library/src/objectServer/java/io/realm/internal/events/NetworkEventStream.java new file mode 100644 index 0000000000..2a4de76a91 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/events/NetworkEventStream.java @@ -0,0 +1,69 @@ +package io.realm.internal.events; + +import org.bson.codecs.configuration.CodecRegistry; + +import java.io.IOException; + +import io.realm.internal.objectserver.EventStream; +import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.internal.objectstore.OsWatchStream; +import io.realm.mongodb.AppException; +import io.realm.mongodb.mongo.events.BaseChangeEvent; + +public class NetworkEventStream implements EventStream { + private final OsJavaNetworkTransport.Response response; + private final OsWatchStream watchStream; + private final CodecRegistry codecRegistry; + private final Class documentClass; + + public NetworkEventStream(OsJavaNetworkTransport.Response response, CodecRegistry codecRegistry, Class documentClass) { + this.response = response; + this.watchStream = new OsWatchStream(codecRegistry); + this.codecRegistry = codecRegistry; + this.documentClass = documentClass; + } + + /** + * Fetch the next event from a given stream + * + * @return the next event + * @throws AppException on a stream error + */ + @Override + public BaseChangeEvent getNextEvent() throws AppException, IOException { + while (true) { + watchStream.feedLine(response.readBodyLine()); + String watchStreamState = watchStream.getState(); + + if (watchStreamState.equals(OsWatchStream.HAVE_EVENT)) { + return ChangeEvent.fromBsonDocument(watchStream.getNextEvent(), documentClass, codecRegistry); + } + if (watchStreamState.equals(OsWatchStream.HAVE_ERROR)) { + response.close(); + throw watchStream.getError(); + } + } + } + + /** + * Closes the current stream. + *

            + * Note: we use a close flag because the underlaying input stream might not be thread safe. + * + * @see https://github.com/square/okio/issues/163#issuecomment-127052956 + */ + @Override + public void close() { + response.close(); + } + + /** + * Indicates whether or not the change stream is currently open. + * + * @return True if the underlying change stream is open. + */ + @Override + public boolean isOpen() { + return response.isOpen(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/events/package-info.java b/realm/realm-library/src/objectServer/java/io/realm/internal/events/package-info.java new file mode 100644 index 0000000000..6f875d4bd4 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/events/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +@javax.annotation.ParametersAreNonnullByDefault +package io.realm.internal.events; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index 83cdfeeb78..cda03b7b17 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -7,22 +7,25 @@ import javax.annotation.Nullable; -import io.realm.mongodb.log.obfuscator.HttpLogObfuscator; import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.mongodb.AppException; +import io.realm.mongodb.ErrorCode; +import io.realm.mongodb.log.obfuscator.HttpLogObfuscator; import okhttp3.Call; import okhttp3.ConnectionPool; import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.OkHttpClient; -import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.ResponseBody; +import okio.BufferedSource; public class OkHttpNetworkTransport extends OsJavaNetworkTransport { public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private volatile OkHttpClient client = null; + private volatile OkHttpClient streamClient = null; @Nullable private final HttpLogObfuscator httpLogObfuscator; @@ -31,26 +34,45 @@ public OkHttpNetworkTransport(@Nullable HttpLogObfuscator httpLogObfuscator) { this.httpLogObfuscator = httpLogObfuscator; } + private okhttp3.Request makeRequest(String method, String url, Map headers, String body){ + okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(url); + switch (method) { + case "get": + builder.get(); + break; + case "delete": + builder.delete(RequestBody.create(JSON, body)); + break; + case "patch": + builder.patch(RequestBody.create(JSON, body)); + break; + case "post": + builder.post(RequestBody.create(JSON, body)); + break; + case "put": + builder.put(RequestBody.create(JSON, body)); + break; + default: + throw new IllegalArgumentException("Unknown method type: " + method); + } + + for (Map.Entry entry : headers.entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + + return builder.build(); + } + @Override - public Response sendRequest(String method, String url, long timeoutMs, Map headers, String body) { + public OsJavaNetworkTransport.Response sendRequest(String method, String url, long timeoutMs, Map headers, String body) { try { OkHttpClient client = getClient(timeoutMs); + okhttp3.Response response = null; try { - Request.Builder builder = new Request.Builder().url(url); - switch(method) { - case "get": builder.get(); break; - case "delete": builder.delete(RequestBody.create(JSON, body)); break; - case "patch": builder.patch(RequestBody.create(JSON, body)); break; - case "post": builder.post(RequestBody.create(JSON, body)); break; - case "put": builder.put(RequestBody.create(JSON, body)); break; - default: throw new IllegalArgumentException("Unknown method type: "+ method); - } + okhttp3.Request request = makeRequest(method, url, headers, body); - for (Map.Entry entry : headers.entrySet()) { - builder.addHeader(entry.getKey(), entry.getValue()); - } - Call call = client.newCall(builder.build()); + Call call = client.newCall(request); response = call.execute(); ResponseBody responseBody = response.body(); String result = ""; @@ -72,6 +94,22 @@ public Response sendRequest(String method, String url, long timeoutMs, Map= 300) || ((response.code() < 200) && (response.code() != 0))) { + throw new AppException(ErrorCode.fromNativeError(ErrorCode.Type.HTTP, response.code()), response.message()); + } + + return Response.httpResponse(response.code(), parseHeaders(response.headers()), response.body().source()); + } + // Lazily creates the client if not already created // TODO: timeOuts are not expected to change between requests. So for now just use the timeout first send. private synchronized OkHttpClient getClient(long timeoutMs) { @@ -90,14 +128,87 @@ private synchronized OkHttpClient getClient(long timeoutMs) { return client; } + private synchronized OkHttpClient getStreamClient() { + if (streamClient == null) { + streamClient = new OkHttpClient.Builder() + .readTimeout(0, TimeUnit.MILLISECONDS) + .followRedirects(true) + .addInterceptor(new LoggingInterceptor(httpLogObfuscator)) + .build(); + } + + return streamClient; + } + // Parse Headers output from OKHttp to the format expected by ObjectStore private Map parseHeaders(Headers headers) { - HashMap osHeaders = new HashMap<>(headers.size()/2); + HashMap osHeaders = new HashMap<>(headers.size() / 2); for (String key : headers.names()) { osHeaders.put(key, headers.get(key)); } return osHeaders; } + public static class Response extends OsJavaNetworkTransport.Response { + private BufferedSource bufferedSource; + private volatile boolean closed; + + public static OsJavaNetworkTransport.Response unknownError(String stacktrace) { + return new Response(0, ERROR_UNKNOWN, new HashMap<>(), stacktrace); + } + + public static OsJavaNetworkTransport.Response ioError(String stackTrace) { + return new Response(0, ERROR_IO, new HashMap<>(), stackTrace); + } + + public static OsJavaNetworkTransport.Response interruptedError(String stackTrace) { + return new Response(0, ERROR_INTERRUPTED, new HashMap<>(), stackTrace); + } + + public static OsJavaNetworkTransport.Response httpResponse(int statusCode, Map responseHeaders, String body) { + return new Response(statusCode, 0, responseHeaders, body); + } + + private Response(int httpResponseCode, int customResponseCode, Map headers, String body) { + super(httpResponseCode, customResponseCode, headers, body); + } + + private Response(int httpResponseCode, Map headers, BufferedSource bufferedSource) { + super(httpResponseCode, 0, headers, ""); + + this.bufferedSource = bufferedSource; + } + + public static OsJavaNetworkTransport.Response httpResponse(int httpResponseCode, Map headers, BufferedSource originalResponse) { + return new Response(httpResponseCode, headers, originalResponse); + } + + @Override + public String readBodyLine() throws IOException { + if(!closed){ + return bufferedSource.readUtf8LineStrict(); + } else{ + bufferedSource.close(); + throw new IOException("Stream closed"); + } + } + + /** + * Closes the current stream. + * + * Note: we use a close flag because okio buffers are not thread safe: + * @see https://github.com/square/okio/issues/163#issuecomment-127052956 + */ + @Override + public void close() { + closed = true; + } + + @Override + public boolean isOpen() { + return !closed && bufferedSource.isOpen(); + } + } + } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/StreamNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/StreamNetworkTransport.java new file mode 100644 index 0000000000..fb2a9bd953 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/StreamNetworkTransport.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.network; + +import java.io.IOException; + +import io.realm.internal.objectstore.OsApp; +import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.internal.objectstore.OsSyncUser; + +/** + * StreamNetworkTransport provides an interface to the Realm remote streaming functions. + * Such functions are package protected and this class offers a portable way to access them. + */ +public class StreamNetworkTransport { + private final OsApp app; + private final OsSyncUser user; + + public StreamNetworkTransport(OsApp app, OsSyncUser user) { + this.app = app; + this.user = user; + } + + /** + * Creates a request for a streaming function + * + * @param functionName name of the function + * @param arguments function arguments encoded as a {@link String} + * @param serviceName service that will handle the function + * @return {@link io.realm.internal.objectstore.OsJavaNetworkTransport.Request} + */ + public OsJavaNetworkTransport.Request makeStreamingRequest(String functionName, + String arguments, + String serviceName){ + return app.makeStreamingRequest(user, functionName, arguments, serviceName); + } + + /** + * Executes a given request + * + * @param request to execute + * @return {@link io.realm.internal.objectstore.OsJavaNetworkTransport.Response} + * @throws IOException + */ + public OsJavaNetworkTransport.Response + sendRequest(OsJavaNetworkTransport.Request request) throws IOException{ + return app.getNetworkTransport().sendStreamingRequest(request); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/EventStream.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/EventStream.java new file mode 100644 index 0000000000..02912e7b18 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectserver/EventStream.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.objectserver; + +import java.io.IOException; + +import io.realm.mongodb.AppException; +import io.realm.mongodb.mongo.events.BaseChangeEvent; + +public interface EventStream { + /** + * Fetch the next event from a given stream. + * + * @return the next event + * @throws IOException any io exception that could occur + */ + BaseChangeEvent getNextEvent() throws AppException, IOException; + + /** + * Closes the current stream. + * + * @throws IOException can throw exception if internal buffer not closed properly + */ + void close(); + + /** + * Indicates whether or not the change stream is currently open. + * + * @return True if the underlying change stream is open. + */ + boolean isOpen(); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsApp.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsApp.java new file mode 100644 index 0000000000..fd150cd786 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsApp.java @@ -0,0 +1,139 @@ +package io.realm.internal.objectstore; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.Nullable; + +import io.realm.internal.KeepMember; +import io.realm.internal.NativeObject; +import io.realm.internal.jni.OsJNIResultCallback; +import io.realm.internal.network.OkHttpNetworkTransport; +import io.realm.internal.network.ResultHandler; +import io.realm.mongodb.AppConfiguration; +import io.realm.mongodb.AppException; + +public class OsApp implements NativeObject { + private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); + + private OsJavaNetworkTransport networkTransport; + private final long nativePtr; + + @Override + public long getNativePtr() { + return this.nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeFinalizerPtr; + } + + public OsApp(AppConfiguration config, String userAgentBindingInfo, String appDefinedUserAgent, String syncDir) { + nativePtr = nativeCreate( + config.getAppId(), + config.getBaseUrl().toString(), + config.getAppName(), + config.getAppVersion(), + config.getRequestTimeoutMs(), + config.getEncryptionKey(), + syncDir, + userAgentBindingInfo, + appDefinedUserAgent, + "android", + android.os.Build.VERSION.RELEASE, + io.realm.BuildConfig.VERSION_NAME); + + this.networkTransport = new OkHttpNetworkTransport(config.getHttpLogObfuscator()); + networkTransport.setAuthorizationHeaderName(config.getAuthorizationHeaderName()); + for (Map.Entry entry : config.getCustomRequestHeaders().entrySet()) { + networkTransport.addCustomRequestHeader(entry.getKey(), entry.getValue()); + } + } + + public void setNetworkTransport(OsJavaNetworkTransport transport) { + networkTransport = transport; + } + + /** + * Creates a request for a streaming function + * + * @param user that requests the execution + * @param functionName name of the function + * @param arguments function arguments encoded as a {@link String} + * @param serviceName service that will handle the function + * @return {@link io.realm.internal.objectstore.OsJavaNetworkTransport.Request} + */ + public OsJavaNetworkTransport.Request makeStreamingRequest(OsSyncUser user, + String functionName, + String arguments, + String serviceName) { + return nativeMakeStreamingRequest(nativePtr, user.getNativePtr(), functionName, arguments, serviceName); + } + + public OsSyncUser currentUser() { + Long userPtr = nativeCurrentUser(nativePtr); + return (userPtr != null) ? new OsSyncUser(userPtr) : null; + } + + public OsSyncUser[] allUsers() { + long[] nativeUsers = nativeGetAllUsers(nativePtr); + OsSyncUser[] osSyncUsers = new OsSyncUser[nativeUsers.length]; + + for (int i = 0; i < nativeUsers.length; i++) { + osSyncUsers[i] = new OsSyncUser(nativeUsers[i]); + } + return osSyncUsers; + } + + public void switchUser(OsSyncUser osUser) { + nativeSwitchUser(nativePtr, osUser.getNativePtr()); + } + + public OsSyncUser login(OsAppCredentials credentials) { + AtomicReference success = new AtomicReference<>(null); + AtomicReference error = new AtomicReference<>(null); + + nativeLogin(nativePtr, credentials.getNativePtr(), new OsJNIResultCallback(success, error) { + @Override + protected OsSyncUser mapSuccess(Object result) { + Long nativePtr = (Long) result; + return new OsSyncUser(nativePtr); + } + }); + + return ResultHandler.handleResult(success, error); + } + + // Called from JNI + @KeepMember + public OsJavaNetworkTransport getNetworkTransport() { + return networkTransport; + } + + private native long nativeCreate(String appId, + String baseUrl, + String appName, + String appVersion, + long requestTimeoutMs, + byte[] encryptionKey, + String syncDirPath, + String bindingUserInfo, + String appUserInfo, + String platform, + String platformVersion, + String sdkVersion); + + private static native void nativeLogin(long nativeAppPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + + @Nullable + private static native Long nativeCurrentUser(long nativePtr); + + private static native long[] nativeGetAllUsers(long nativePtr); + + private static native void nativeSwitchUser(long nativeAppPtr, long nativeUserPtr); + + private static native long nativeGetFinalizerMethodPtr(); + + private static native OsJavaNetworkTransport.Request nativeMakeStreamingRequest(long nativeAppPtr, long nativeUserPtr, String functionName, String bsonArgs, String serviceName); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java index d97c9db45f..33fe3b895d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java @@ -15,11 +15,14 @@ */ package io.realm.internal.objectstore; +import java.io.Closeable; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import io.realm.internal.Keep; import io.realm.mongodb.AppConfiguration; +import io.realm.mongodb.AppException; /** * Java implementation of the transport layer exposed by ObjectStore when communicating with @@ -53,6 +56,19 @@ public abstract class OsJavaNetworkTransport { */ protected abstract Response sendRequest(String method, String url, long timeoutMs, Map headers, String body); + /** + * This method is being called from Java when executing streaming requests. + * It returns a {@link Response} which body can be access line by line. + * + * Warning: this method and the returning {@link Response} throws exceptions + * + * @param request streaming request + * @return Result of the request. + * @throws IOException if the request fails to execute + * @throws AppException on an http error + */ + public abstract Response sendStreamingRequest(Request request) throws IOException, AppException; + public void setAuthorizationHeaderName(String headerName) { authorizationHeaderName = headerName; } @@ -78,29 +94,13 @@ public void resetHeaders() { customHeaders.clear(); } - public static class Response { + public static abstract class Response { private final int httpResponseCode; private final int customResponseCode; private final Map headers; private final String body; - public static Response unknownError(String stacktrace) { - return new Response(0, ERROR_UNKNOWN, new HashMap<>(), stacktrace); - } - - public static Response ioError(String stackTrace) { - return new Response(0, ERROR_IO, new HashMap<>(), stackTrace); - } - - public static Response interruptedError(String stackTrace) { - return new Response(0, ERROR_INTERRUPTED, new HashMap<>(), stackTrace); - } - - public static Response httpResponse(int statusCode, Map responseHeaders, String body) { - return new Response(statusCode, 0, responseHeaders, body); - } - - private Response(int httpResponseCode, int customResponseCode, Map headers, String body) { + protected Response(int httpResponseCode, int customResponseCode, Map headers, String body) { this.httpResponseCode = httpResponseCode; this.customResponseCode = customResponseCode; this.headers = headers; @@ -136,6 +136,15 @@ public String getBody() { return body; } + public String readBodyLine() throws IOException { + return null; + } + + public boolean isOpen() { + return false; + } + + @Override public String toString() { return "Response{" + @@ -145,6 +154,46 @@ public String toString() { ", body='" + body + '\'' + '}'; } + + /** + * Closes the current stream. + * + * Note: we use a close flag because the underlaying input stream might not be thread safe. + * @see https://github.com/square/okio/issues/163#issuecomment-127052956 + * + * @throws IOException can throw exception if internal buffer not closed properly + */ + public abstract void close(); + } + + public static class Request { + private String method; + private String url; + private Map headers; + private String body; + + public Request(String method, String url, Map headers, String body) { + this.method = method; + this.url = url; + this.headers = headers; + this.body = body; + } + + public String getMethod() { + return method; + } + + public String getUrl() { + return url; + } + + public Map getHeaders() { + return headers; + } + + public String getBody() { + return body; + } } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java index 6ecd7ddd51..ba65420065 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java @@ -21,22 +21,28 @@ import java.util.concurrent.ThreadPoolExecutor; import io.realm.internal.NativeObject; +import io.realm.internal.network.StreamNetworkTransport; public class OsMongoClient implements NativeObject { private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); private final long nativePtr; - - public OsMongoClient(final long appNativePtr, - final String serviceName) { - this.nativePtr = nativeCreate(appNativePtr, serviceName); + private final String serviceName; + private final StreamNetworkTransport streamNetworkTransport; + + public OsMongoClient(final OsApp osApp, + final String serviceName, + final StreamNetworkTransport streamNetworkTransport) { + this.nativePtr = nativeCreate(osApp.getNativePtr(), serviceName); + this.serviceName = serviceName; + this.streamNetworkTransport = streamNetworkTransport; } public OsMongoDatabase getDatabase(final String databaseName, final CodecRegistry codecRegistry) { long nativeDatabasePtr = nativeCreateDatabase(nativePtr, databaseName); - return new OsMongoDatabase(nativeDatabasePtr, codecRegistry); + return new OsMongoDatabase(nativeDatabasePtr, serviceName, codecRegistry, streamNetworkTransport); } @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java index 99b3379d2b..0fb72f116d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java @@ -17,6 +17,7 @@ package io.realm.internal.objectstore; import org.bson.BsonArray; +import org.bson.BsonDocument; import org.bson.BsonNull; import org.bson.BsonObjectId; import org.bson.BsonValue; @@ -25,6 +26,8 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; +import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,11 +38,15 @@ import io.realm.internal.NativeObject; import io.realm.internal.Util; +import io.realm.internal.events.NetworkEventStream; import io.realm.internal.jni.JniBsonProtocol; import io.realm.internal.jni.OsJNIResultCallback; import io.realm.internal.network.ResultHandler; +import io.realm.internal.network.StreamNetworkTransport; +import io.realm.internal.objectserver.EventStream; import io.realm.mongodb.App; import io.realm.mongodb.AppException; +import io.realm.mongodb.mongo.MongoNamespace; import io.realm.mongodb.mongo.iterable.AggregateIterable; import io.realm.mongodb.mongo.iterable.FindIterable; import io.realm.mongodb.mongo.options.CountOptions; @@ -67,6 +74,10 @@ public class OsMongoCollection implements NativeObject { private static final int FIND_ONE_AND_DELETE_WITH_OPTIONS = 12; private static final int FIND_ONE = 13; private static final int FIND_ONE_WITH_OPTIONS = 14; + private static final int WATCH = 15; + private static final int WATCH_IDS = 16; + private static final int WATCH_WITH_FILTER= 17; + private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); @@ -75,14 +86,23 @@ public class OsMongoCollection implements NativeObject { private final CodecRegistry codecRegistry; private final String encodedEmptyDocument; private final ThreadPoolExecutor threadPoolExecutor = App.NETWORK_POOL_EXECUTOR; + private final String serviceName; + private final MongoNamespace namespace; + private final StreamNetworkTransport streamNetworkTransport; OsMongoCollection(final long nativeCollectionPtr, + final MongoNamespace namespace, + final String serviceName, final Class documentClass, - final CodecRegistry codecRegistry) { + final CodecRegistry codecRegistry, + final StreamNetworkTransport streamNetworkTransport) { this.nativePtr = nativeCollectionPtr; + this.namespace = namespace; + this.serviceName = serviceName; this.documentClass = documentClass; this.codecRegistry = codecRegistry; this.encodedEmptyDocument = JniBsonProtocol.encode(new Document(), codecRegistry); + this.streamNetworkTransport = streamNetworkTransport; } @Override @@ -105,11 +125,11 @@ public CodecRegistry getCodecRegistry() { public OsMongoCollection withDocumentClass( final Class clazz) { - return new OsMongoCollection<>(nativePtr, clazz, codecRegistry); + return new OsMongoCollection<>(nativePtr, namespace, serviceName, clazz, codecRegistry, streamNetworkTransport); } public OsMongoCollection withCodecRegistry(final CodecRegistry codecRegistry) { - return new OsMongoCollection<>(nativePtr, documentClass, codecRegistry); + return new OsMongoCollection<>(nativePtr, namespace, serviceName, documentClass, codecRegistry, streamNetworkTransport); } public Long count() { @@ -459,6 +479,10 @@ public ResultT findOneAndDelete(final Bson filter, return findOneAndModify(FIND_ONE_AND_DELETE_WITH_OPTIONS, filter, new Document(), options, resultClass); } + public String getServiceName() { + return serviceName; + } + private ResultT findOneAndModify(final int type, final Bson filter, final Bson update, @@ -497,7 +521,7 @@ protected ResultT mapSuccess(Object result) { nativeFindOneAndUpdate(type, nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, options.isUpsert(), options.isReturnNewDocument(), callback); break; case FIND_ONE_AND_REPLACE: - nativeFindOneAndReplace(type, nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, false, false,callback); + nativeFindOneAndReplace(type, nativePtr, encodedFilter, encodedUpdate, encodedProjection, encodedSort, false, false, callback); break; case FIND_ONE_AND_REPLACE_WITH_OPTIONS: Util.checkNull(options, "options"); @@ -525,11 +549,58 @@ private T findSuccessMapper(@Nullable Object result, Class resultClass) { } } + private EventStream watchInternal(int type, @Nullable List ids, @Nullable BsonDocument matchFilter) throws IOException { + List args = new ArrayList<>(); + + Document watchArgs = new Document("database", namespace.getDatabaseName()); + watchArgs.put("collection", namespace.getCollectionName()); + + switch (type) { + case WATCH: + break; + case WATCH_IDS: + watchArgs.put("ids", ids); + break; + case WATCH_WITH_FILTER: + watchArgs.put("filter", matchFilter); + break; + default: + throw new IllegalArgumentException("Invalid watch type: " + type); + } + + args.add(watchArgs); + + String encodedArguments = JniBsonProtocol.encode(args, codecRegistry); + + OsJavaNetworkTransport.Request request = streamNetworkTransport.makeStreamingRequest("watch", encodedArguments, serviceName); + OsJavaNetworkTransport.Response response = streamNetworkTransport.sendRequest(request); + + return new NetworkEventStream<>(response, codecRegistry, documentClass); + } + + public EventStream watch() throws IOException { + return watchInternal(WATCH, null, null); + } + + public EventStream watch(final List ids) throws IOException { + return watchInternal(WATCH_IDS, ids, null); + } + + public EventStream watchWithFilter(Document matchFilter) throws IOException { + return watchInternal(WATCH_WITH_FILTER, null, matchFilter.toBsonDocument(getDocumentClass(), getCodecRegistry())); + } + + public EventStream watchWithFilter(BsonDocument matchFilter) throws IOException { + return watchInternal(WATCH_WITH_FILTER, null, matchFilter); + } + private static native long nativeGetFinalizerMethodPtr(); + private static native void nativeCount(long remoteMongoCollectionPtr, String filter, long limit, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOne(int findOneType, long nativePtr, String filter, @@ -537,22 +608,27 @@ private static native void nativeFindOne(int findOneType, String sort, long limit, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeInsertOne(long remoteMongoCollectionPtr, String document, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeInsertMany(long remoteMongoCollectionPtr, String documents, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeDelete(int deleteType, long remoteMongoCollectionPtr, String document, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeUpdate(int updateType, long remoteMongoCollectionPtr, String filter, String update, boolean upsert, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndUpdate(int findOneAndUpdateType, long remoteMongoCollectionPtr, String filter, @@ -562,6 +638,7 @@ private static native void nativeFindOneAndUpdate(int findOneAndUpdateType, boolean upsert, boolean returnNewDocument, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndReplace(int findOneAndReplaceType, long remoteMongoCollectionPtr, String filter, @@ -571,6 +648,7 @@ private static native void nativeFindOneAndReplace(int findOneAndReplaceType, boolean upsert, boolean returnNewDocument, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + private static native void nativeFindOneAndDelete(int findOneAndDeleteType, long remoteMongoCollectionPtr, String filter, diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java index 34b3804c90..86f785d2fb 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoDatabase.java @@ -22,28 +22,37 @@ import java.util.concurrent.ThreadPoolExecutor; import io.realm.internal.NativeObject; +import io.realm.internal.network.StreamNetworkTransport; +import io.realm.mongodb.mongo.MongoNamespace; public class OsMongoDatabase implements NativeObject { private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); private final long nativePtr; + private final String serviceName; private final CodecRegistry codecRegistry; + private final StreamNetworkTransport streamNetworkTransport; OsMongoDatabase(final long nativeDatabasePtr, - final CodecRegistry codecRegistry) { + final String serviceName, + final CodecRegistry codecRegistry, + final StreamNetworkTransport streamNetworkTransport) { this.nativePtr = nativeDatabasePtr; + this.serviceName = serviceName; this.codecRegistry = codecRegistry; + this.streamNetworkTransport = streamNetworkTransport; } - public OsMongoCollection getCollection(final String collectionName) { - return getCollection(collectionName, Document.class); + public OsMongoCollection getCollection(final String collectionName, final MongoNamespace namespace) { + return getCollection(collectionName, namespace, Document.class); } public OsMongoCollection getCollection(final String collectionName, + final MongoNamespace namespace, final Class documentClass) { long nativeCollectionPtr = nativeGetCollection(nativePtr, collectionName); - return new OsMongoCollection<>(nativeCollectionPtr, documentClass, codecRegistry); + return new OsMongoCollection<>(nativeCollectionPtr, namespace, serviceName, documentClass, codecRegistry, streamNetworkTransport); } @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java index c01d6604ff..0d3b0182e9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsPush.java @@ -32,8 +32,8 @@ public class OsPush implements NativeObject { private final OsSyncUser osSyncUser; private final String serviceName; - public OsPush(final long appNativePtr, final OsSyncUser osSyncUser, final String serviceName) { - this.nativePtr = nativeCreate(appNativePtr, serviceName); + public OsPush(final OsApp osApp, final OsSyncUser osSyncUser, final String serviceName) { + this.nativePtr = nativeCreate(osApp.getNativePtr(), serviceName); this.osSyncUser = osSyncUser; this.serviceName = serviceName; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsWatchStream.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsWatchStream.java new file mode 100644 index 0000000000..0688752458 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsWatchStream.java @@ -0,0 +1,56 @@ +package io.realm.internal.objectstore; + +import org.bson.BsonDocument; +import org.bson.codecs.configuration.CodecRegistry; + +import io.realm.internal.NativeObject; +import io.realm.internal.jni.JniBsonProtocol; +import io.realm.mongodb.AppException; + +public class OsWatchStream implements NativeObject { + public static final String NEED_DATA = "NEED_DATA"; + public static final String HAVE_EVENT = "HAVE_EVENT"; + public static final String HAVE_ERROR = "HAVE_ERROR"; + + private final long nativePtr; + private final CodecRegistry codecRegistry; + + public OsWatchStream(CodecRegistry codecRegistry) { + this.codecRegistry = codecRegistry; + this.nativePtr = nativeCreateWatchStream(); + } + + @Override + public long getNativePtr() { + return this.nativePtr; + } + + @Override + public long getNativeFinalizerPtr() { + return nativeGetFinalizerMethodPtr(); + } + + public BsonDocument getNextEvent() { + String bsonEvent = nativeGetNextEvent(nativePtr); + return JniBsonProtocol.decode(bsonEvent, codecRegistry.get(BsonDocument.class)); + } + + public String getState() { + return nativeGetState(nativePtr); + } + + public AppException getError() { + return nativeGetError(nativePtr); + } + + public void feedLine(String line) { + nativeFeedLine(nativePtr, line); + } + + private static native long nativeGetFinalizerMethodPtr(); + private static native long nativeCreateWatchStream(); + private static native void nativeFeedLine(long nativePtr, String line); + private static native String nativeGetState(long nativePtr); + private static native String nativeGetNextEvent(long nativePtr); + private static native AppException nativeGetError(long nativePtr); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java index 779a387a3e..1d5b2d1468 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ApiKeyAuthImpl.java @@ -31,7 +31,7 @@ class ApiKeyAuthImpl extends ApiKeyAuth { @Override protected void call(int functionType, @Nullable String arg, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback) { - nativeCallFunction(functionType, getApp().nativePtr, getUser().osUser.getNativePtr(), arg, callback); + nativeCallFunction(functionType, getApp().osApp.getNativePtr(), getUser().osUser.getNativePtr(), arg, callback); } private static native void nativeCallFunction(int functionType, long nativeAppPtr, long nativeUserPtr, @Nullable String arg, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index daf359fda8..70d400a6a6 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -29,31 +29,28 @@ import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.realm.BuildConfig; import io.realm.Realm; -import io.realm.annotations.Beta; -import io.realm.internal.mongodb.Request; -import io.realm.mongodb.auth.EmailPasswordAuth; import io.realm.RealmAsyncTask; -import io.realm.mongodb.sync.Sync; -import io.realm.internal.KeepMember; -import io.realm.internal.network.ResultHandler; +import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.internal.async.RealmThreadPoolExecutor; -import io.realm.internal.jni.OsJNIResultCallback; -import io.realm.internal.network.OkHttpNetworkTransport; +import io.realm.internal.mongodb.Request; +import io.realm.internal.objectstore.OsApp; import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.internal.objectstore.OsSyncUser; import io.realm.log.RealmLog; +import io.realm.mongodb.auth.EmailPasswordAuth; import io.realm.mongodb.functions.Functions; +import io.realm.mongodb.sync.Sync; /** * An App is the main client-side entry point for interacting with a MongoDB Realm App. - * + *

            * The App can be used to: *

              *
            • Register uses and perform various user-related operations through authentication providers @@ -93,7 +90,7 @@ * show the synchronized APIs which cannot be used from the main thread. For the equivalent * asynchronous counterparts. The example project in please see * https://github.com/realm/realm-java/tree/v10/examples/mongoDbRealmExample. - * + *

              * To register a new user and/or login with an existing user do as shown below: *

                *     // Register new user
              @@ -143,6 +140,8 @@
               @Beta
               public class App {
               
              +    final OsApp osApp;
              +
                   static final class SyncImpl extends Sync {
                       protected SyncImpl(App app) {
                           super(app);
              @@ -169,9 +168,7 @@ protected SyncImpl(App app) {
                   public static ThreadPoolExecutor NETWORK_POOL_EXECUTOR = RealmThreadPoolExecutor.newDefaultExecutor();
               
                   private final AppConfiguration config;
              -    protected OsJavaNetworkTransport networkTransport;
                   final Sync syncManager;
              -    final long nativePtr;
                   private final EmailPasswordAuth emailAuthProvider = new EmailPasswordAuthImpl(this);
                   private CopyOnWriteArrayList authListeners = new CopyOnWriteArrayList<>();
                   private Handler mainHandler = new Handler(Looper.getMainLooper());
              @@ -184,18 +181,12 @@ public App(String appId) {
                    * Constructor for creating an App according to the given AppConfiguration.
                    *
                    * @param config The configuration to use for this App instance.
              -     *
                    * @see AppConfiguration.Builder
                    */
                   public App(AppConfiguration config) {
                       this.config = config;
              -        this.networkTransport = new OkHttpNetworkTransport(config.getHttpLogObfuscator());
              -        networkTransport.setAuthorizationHeaderName(config.getAuthorizationHeaderName());
              -        for (Map.Entry entry : config.getCustomRequestHeaders().entrySet()) {
              -            networkTransport.addCustomRequestHeader(entry.getKey(), entry.getValue());
              -        }
                       this.syncManager = new SyncImpl(this);
              -        this.nativePtr = init(config);
              +        this.osApp = init(config);
               
                       // FIXME: Right now we only support one App. This class will throw a
                       // exception if you try to create it twice. This is a really hacky way to do this
              @@ -210,23 +201,12 @@ public App(AppConfiguration config) {
                       }
                   }
               
              -    private long init(AppConfiguration config) {
              +    private OsApp init(AppConfiguration config) {
                       String userAgentBindingInfo = getBindingInfo();
                       String appDefinedUserAgent = getAppInfo(config);
                       String syncDir = getSyncBaseDirectory();
              -        return nativeCreate(
              -                config.getAppId(),
              -                config.getBaseUrl().toString(),
              -                config.getAppName(),
              -                config.getAppVersion(),
              -                config.getRequestTimeoutMs(),
              -                config.getEncryptionKey(),
              -                syncDir,
              -                userAgentBindingInfo,
              -                appDefinedUserAgent,
              -                "android",
              -                android.os.Build.VERSION.RELEASE,
              -                io.realm.BuildConfig.VERSION_NAME);
              +
              +        return new OsApp(config, userAgentBindingInfo, appDefinedUserAgent, syncDir);
                   }
               
                   private String getSyncBaseDirectory() {
              @@ -312,8 +292,8 @@ private String getBindingInfo() {
                    */
                   @Nullable
                   public User currentUser() {
              -        Long userPtr = nativeCurrentUser(nativePtr);
              -        return (userPtr != null) ? new User(userPtr, this) : null;
              +        OsSyncUser osSyncUser = osApp.currentUser();
              +        return (osSyncUser != null) ? new User(osSyncUser, this) : null;
                   }
               
                   /**
              @@ -325,10 +305,11 @@ public User currentUser() {
                    * @return a map of user identifiers and users known locally.
                    */
                   public Map allUsers() {
              -        long[] nativeUsers = nativeGetAllUsers(nativePtr);
              -        HashMap users = new HashMap<>(nativeUsers.length);
              -        for (int i = 0; i < nativeUsers.length; i++) {
              -            User user = new User(nativeUsers[i], this);
              +        OsSyncUser[] allUsers = osApp.allUsers();
              +
              +        HashMap users = new HashMap<>(allUsers.length);
              +        for (int i = 0; i < allUsers.length; i++) {
              +            User user = new User(allUsers[i], this);
                           users.put(user.getId(), user);
                       }
                       return users;
              @@ -344,7 +325,8 @@ public Map allUsers() {
                    */
                   public User switchUser(User user) {
                       Util.checkNull(user, "user");
              -        nativeSwitchUser(nativePtr, user.osUser.getNativePtr());
              +        osApp.switchUser(user.osUser);
              +
                       return user;
                   }
               
              @@ -366,16 +348,10 @@ public User switchUser(User user) {
                    */
                   public User login(Credentials credentials) throws AppException {
                       Util.checkNull(credentials, "credentials");
              -        AtomicReference success = new AtomicReference<>(null);
              -        AtomicReference error = new AtomicReference<>(null);
              -        nativeLogin(nativePtr, credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) {
              -            @Override
              -            protected User mapSuccess(Object result) {
              -                Long nativePtr = (Long) result;
              -                return new User(nativePtr, App.this);
              -            }
              -        });
              -        User user = ResultHandler.handleResult(success, error);
              +
              +        OsSyncUser osSyncUser = osApp.login(credentials.osCredentials);
              +        User user = new User(osSyncUser, this);
              +
                       notifyUserLoggedIn(user);
                       return user;
                   }
              @@ -415,11 +391,11 @@ public void run() {
                    * {@link #switchUser(User)}.
                    *
                    * @param credentials the credentials representing the type of login.
              -     * @param callback callback when logging in has completed or failed. The callback will always
              -     * happen on the same thread as this method is called on.
              +     * @param callback    callback when logging in has completed or failed. The callback will always
              +     *                    happen on the same thread as this method is called on.
                    * @throws IllegalStateException if not called on a looper thread.
                    */
              -     public RealmAsyncTask loginAsync(Credentials credentials, Callback callback) {
              +    public RealmAsyncTask loginAsync(Credentials credentials, Callback callback) {
                       Util.checkLooperThread("Asynchronous log in is only possible from looper threads.");
                       return new Request(NETWORK_POOL_EXECUTOR, callback) {
                           @Override
              @@ -436,8 +412,8 @@ public User run() throws AppException {
                    * @return wrapper for interacting with the {@link Credentials.IdentityProvider#EMAIL_PASSWORD} identity provider.
                    */
                   public EmailPasswordAuth getEmailPasswordAuth() {
              -         return emailAuthProvider;
              -     }
              +        return emailAuthProvider;
              +    }
               
                   /**
                    * Sets a global authentication listener that will be notified about User events like
              @@ -512,17 +488,12 @@ public AppConfiguration getConfiguration() {
               
                   /**
                    * Exposed for testing.
              -     *
              +     * 

              * Swap the currently configured network transport with the provided one. * This should only be done if no network requests are currently running. */ - void setNetworkTransport(OsJavaNetworkTransport transport) { - networkTransport = transport; - } - - @KeepMember // Called from JNI - OsJavaNetworkTransport getNetworkTransport() { - return networkTransport; + protected void setNetworkTransport(OsJavaNetworkTransport transport) { + osApp.setNetworkTransport(transport); } /** @@ -633,22 +604,4 @@ public interface Callback { */ void onResult(Result result); } - - private native long nativeCreate(String appId, - String baseUrl, - String appName, - String appVersion, - long requestTimeoutMs, - byte[] encryptionKey, - String syncDirPath, - String bindingUserInfo, - String appUserInfo, - String platform, - String platformVersion, - String sdkVersion); - private static native void nativeLogin(long nativeAppPtr, long nativeCredentialsPtr, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); - @Nullable - private static native Long nativeCurrentUser(long nativePtr); - private static native long[] nativeGetAllUsers(long nativePtr); - private static native void nativeSwitchUser(long nativeAppPtr, long nativeUserPtr); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java index 779bb7c533..4048dc4379 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java @@ -228,7 +228,7 @@ private Credentials(OsAppCredentials credentials, IdentityProvider identityProvi * All of these except {@link #EMAIL_PASSWORD} must be enabled manually on MongoDB Realm to * work. * - * @see Authentication Providers + * @see Authentication Providers */ public enum IdentityProvider { ANONYMOUS("anon-user"), diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java index 57a7dc60e6..9a72546631 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/EmailPasswordAuthImpl.java @@ -29,7 +29,7 @@ class EmailPasswordAuthImpl extends EmailPasswordAuth { @Override protected void call(int functionType, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback, String... args) { - nativeCallFunction(functionType, app.nativePtr, callback, args); + nativeCallFunction(functionType, app.osApp.getNativePtr(), callback, args); } private static native void nativeCallFunction(int functionType, diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java index 910c9fae31..f2fb7dd4ac 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java @@ -43,11 +43,15 @@ public enum ErrorCode { NETWORK_IO_EXCEPTION(Type.JAVA, OsJavaNetworkTransport.ERROR_IO), NETWORK_INTERRUPTED(Type.JAVA, OsJavaNetworkTransport.ERROR_INTERRUPTED), NETWORK_UNKNOWN(Type.JAVA, OsJavaNetworkTransport.ERROR_UNKNOWN), + // BSON encoding/decoding errors originating from java BSON_CODEC_NOT_FOUND(Type.JAVA, 1100), BSON_ENCODING(Type.JAVA, 1101), BSON_DECODING(Type.JAVA, 1102), + // Stream serializing errors originated from Java + EVENT_DESERIALIZING(Type.JAVA, 1200), + // Custom Object Store errors CLIENT_RESET(Type.PROTOCOL, 7), // Client Reset required. Don't change this value without modifying io_realm_internal_OsSharedRealm.cpp diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java index bfb5e719ee..6723460528 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/FunctionsImpl.java @@ -60,7 +60,7 @@ protected String mapSuccess(Object result) { return (String) result; } }; - nativeCallFunction(user.getApp().nativePtr, user.osUser.getNativePtr(), name, encodedArgs, callback); + nativeCallFunction(user.getApp().osApp.getNativePtr(), user.osUser.getNativePtr(), name, encodedArgs, callback); String encodedResponse = ResultHandler.handleResult(success, error); return JniBsonProtocol.decode(encodedResponse, resultDecoder); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmEventStreamAsyncTask.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmEventStreamAsyncTask.java new file mode 100644 index 0000000000..808cb79a6a --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmEventStreamAsyncTask.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import io.realm.RealmAsyncTask; +import io.realm.mongodb.mongo.events.BaseChangeEvent; + + +/** + * The RealmEventStreamAsyncTask is a specific version of {@link RealmAsyncTask} that provides a non-blocking mechanism + * to work with asynchronous operations carried out against MongoDB Realm that yield stream results. + * + * @param the result type delivered by this task. + */ +public interface RealmEventStreamAsyncTask extends RealmAsyncTask { + /** + * Provides a way to subscribe to asynchronous operations via a callback, which handles both + * results and errors. + * + * @param callback the {@link App.Callback} designed to receive event results. + * @throws IllegalStateException if the stream is already open. + */ + void get(App.Callback> callback) throws IllegalStateException; + + /** + * Whether or not the stream is currently open. + * @return true if open, false if not. + */ + boolean isOpen(); +} + diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmEventStreamTask.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmEventStreamTask.java new file mode 100644 index 0000000000..c73cc6588f --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/RealmEventStreamTask.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import java.io.IOException; + +import io.realm.RealmAsyncTask; +import io.realm.mongodb.mongo.events.BaseChangeEvent; + + +/** + * The RealmEventStreamTask is a specific version of {@link RealmAsyncTask} that provides a blocking mechanism + * to work with asynchronous operations carried out against MongoDB Realm that yield stream results. + * + * @param the result type delivered by this task. + */ +public interface RealmEventStreamTask extends RealmAsyncTask { + + /** + * Blocks the thread on which the call is made until the result of the operation arrives. + * + * @return the next event in the stream. + * @throws AppException if the server raises an error + * @throws IOException if something is wrong with the input stream + */ + BaseChangeEvent getNext() throws AppException, IOException; + + /** + * Whether or not the stream is currently open. + * + * @return true if open, false if not. + */ + boolean isOpen(); +} + diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index 7c74ae9e4a..755132eff0 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -32,6 +32,7 @@ import io.realm.internal.jni.OsJNIVoidResultCallback; import io.realm.internal.mongodb.Request; import io.realm.internal.network.ResultHandler; +import io.realm.internal.network.StreamNetworkTransport; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.internal.objectstore.OsMongoClient; import io.realm.internal.objectstore.OsPush; @@ -113,8 +114,8 @@ protected PushImpl(OsPush osPush) { } } - User(long nativePtr, App app) { - this.osUser = new OsSyncUser(nativePtr); + User(OsSyncUser osUser, App app) { + this.osUser = osUser; this.app = app; } @@ -386,7 +387,7 @@ public User linkCredentials(Credentials credentials) { checkLoggedIn(); AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeLinkUser(app.nativePtr, osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { + nativeLinkUser(app.osApp.getNativePtr(), osUser.getNativePtr(), credentials.osCredentials.getNativePtr(), new OsJNIResultCallback(success, error) { @Override protected User mapSuccess(Object result) { osUser = new OsSyncUser((long) result); // OS returns the updated user as a new one. @@ -443,7 +444,7 @@ public User remove() throws AppException { boolean loggedIn = isLoggedIn(); AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - nativeRemoveUser(app.nativePtr, osUser.getNativePtr(), new OsJNIResultCallback(success, error) { + nativeRemoveUser(app.osApp.getNativePtr(), osUser.getNativePtr(), new OsJNIResultCallback(success, error) { @Override protected User mapSuccess(Object result) { return User.this; @@ -495,7 +496,7 @@ public User run() throws AppException { public void logOut() throws AppException { boolean loggedIn = isLoggedIn(); AtomicReference error = new AtomicReference<>(null); - nativeLogOut(app.nativePtr, osUser.getNativePtr(), new OsJNIVoidResultCallback(error)); + nativeLogOut(app.osApp.getNativePtr(), osUser.getNativePtr(), new OsJNIVoidResultCallback(error)); ResultHandler.handleResult(null, error); if (loggedIn) { app.notifyUserLoggedOut(this); @@ -580,7 +581,7 @@ public Functions getFunctions(CodecRegistry codecRegistry) { */ public synchronized Push getPush(String serviceName) { if (push == null) { - OsPush osPush = new OsPush(app.nativePtr, osUser, serviceName); + OsPush osPush = new OsPush(app.osApp, osUser, serviceName); push = new PushImpl(osPush); } return push; @@ -594,7 +595,9 @@ public synchronized Push getPush(String serviceName) { public synchronized MongoClient getMongoClient(String serviceName) { Util.checkEmpty(serviceName, "serviceName"); if (mongoClient == null) { - OsMongoClient osMongoClient = new OsMongoClient(app.nativePtr, serviceName); + StreamNetworkTransport streamNetworkTransport = new StreamNetworkTransport(app.osApp, this.osUser); + + OsMongoClient osMongoClient = new OsMongoClient(app.osApp, serviceName, streamNetworkTransport); mongoClient = new MongoClientImpl(osMongoClient, app.getConfiguration().getDefaultCodecRegistry()); } return mongoClient; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java index a597fc5917..9069dda4c4 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java @@ -18,8 +18,6 @@ import org.bson.codecs.configuration.CodecRegistry; -import java.util.concurrent.ThreadPoolExecutor; - import io.realm.annotations.Beta; import io.realm.internal.Util; import io.realm.internal.objectstore.OsMongoClient; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java index 607f5ea1dc..d6a46506ef 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java @@ -16,18 +16,30 @@ package io.realm.mongodb.mongo; +import org.bson.BsonDocument; +import org.bson.BsonObjectId; +import org.bson.BsonValue; +import org.bson.Document; import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; +import org.bson.types.ObjectId; +import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.concurrent.ThreadPoolExecutor; import javax.annotation.Nullable; import io.realm.annotations.Beta; +import io.realm.internal.async.RealmEventStreamAsyncTaskImpl; +import io.realm.internal.async.RealmEventStreamTaskImpl; import io.realm.internal.async.RealmResultTaskImpl; +import io.realm.internal.objectserver.EventStream; import io.realm.internal.objectstore.OsMongoCollection; import io.realm.mongodb.App; +import io.realm.mongodb.RealmEventStreamAsyncTask; +import io.realm.mongodb.RealmEventStreamTask; import io.realm.mongodb.RealmResultTask; import io.realm.mongodb.mongo.iterable.AggregateIterable; import io.realm.mongodb.mongo.iterable.FindIterable; @@ -805,4 +817,194 @@ public ResultT run() { } }); } + + /** + * Watches a collection. The resulting stream will be notified of all events on this collection + * that the active user is authorized to see based on the configured MongoDB Realm rules. + * + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamTask watch() { + return new RealmEventStreamTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watch(); + } + }); + } + + /** + * Watches specified IDs in a collection. + * + * @param ids the ids to watch. + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamTask watch(final BsonValue... ids) { + return new RealmEventStreamTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watch(Arrays.asList(ids)); + } + }); + } + + /** + * Watches specified IDs in a collection. This convenience overload supports the use case + * of non-{@link BsonValue} instances of {@link ObjectId} by wrapping them in + * {@link BsonObjectId} instances for the user. + * + * @param ids unique object identifiers of the IDs to watch. + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamTask watch(final ObjectId... ids) { + return new RealmEventStreamTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watch(Arrays.asList(ids)); + } + }); + } + + /** + * Watches a collection. The provided document will be used as a match expression filter on + * the change events coming from the stream. This convenience overload supports the use of + * non-{@link BsonDocument} instances for the user. + *

              + * See how to define a match filter. + *

              + * Defining the match expression to filter ChangeEvents is similar to + * how to define the match expression for triggers + * + * @param matchFilter the $match filter to apply to incoming change events + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamTask watchWithFilter(Document matchFilter) { + return new RealmEventStreamTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watchWithFilter(matchFilter); + } + }); + } + + /** + * Watches a collection. The provided BSON document will be used as a match expression filter on + * the change events coming from the stream. + *

              + * See how to define a match filter. + *

              + * Defining the match expression to filter ChangeEvents is similar to + * how to define the match expression for triggers + * + * @param matchFilter the $match filter to apply to incoming change events + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamTask watchWithFilter(BsonDocument matchFilter) { + return new RealmEventStreamTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watchWithFilter(matchFilter); + } + }); + } + + /** + * Watches a collection asynchronously. The resulting stream will be notified of all events on this collection + * that the active user is authorized to see based on the configured MongoDB Realm rules. + * + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamAsyncTask watchAsync() { + return new RealmEventStreamAsyncTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamAsyncTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watch(); + } + }); + } + + /** + * Watches specified IDs in a collection asynchronously. + * + * @param ids the ids to watch. + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamAsyncTask watchAsync(final BsonValue... ids) { + return new RealmEventStreamAsyncTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamAsyncTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watch(Arrays.asList(ids)); + } + }); + } + + /** + * Watches specified IDs in a collection asynchronously. This convenience overload supports the use case + * of non-{@link BsonValue} instances of {@link ObjectId} by wrapping them in + * {@link BsonObjectId} instances for the user. + * + * @param ids unique object identifiers of the IDs to watch. + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamAsyncTask watchAsync(final ObjectId... ids) { + return new RealmEventStreamAsyncTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamAsyncTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watch(Arrays.asList(ids)); + } + }); + } + + /** + * Watches a collection asynchronously. The provided document will be used as a match expression filter on + * the change events coming from the stream. This convenience overload supports the use of + * non-{@link BsonDocument} instances for the user. + *

              + * See how to define a match filter. + *

              + * Defining the match expression to filter ChangeEvents is similar to + * how to define the match expression for triggers + * + * @param matchFilter the $match filter to apply to incoming change events + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamAsyncTask watchWithFilterAsync(Document matchFilter) { + return new RealmEventStreamAsyncTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamAsyncTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watchWithFilter(matchFilter); + } + }); + } + + /** + * Watches a collection asynchronously. The provided BSON document will be used as a match expression filter on + * the change events coming from the stream. + *

              + * See how to define a match filter. + *

              + * Defining the match expression to filter ChangeEvents is similar to + * how to define the match expression for triggers + * + * @param matchFilter the $match filter to apply to incoming change events + * @return a task that provides access to the stream of change events. + */ + public RealmEventStreamAsyncTask watchWithFilterAsync(BsonDocument matchFilter) { + return new RealmEventStreamAsyncTaskImpl<>(getNamespace().getFullName(), + new RealmEventStreamAsyncTaskImpl.Executor() { + @Override + public EventStream run() throws IOException { + return osMongoCollection.watchWithFilter(matchFilter); + } + }); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java index 7e268ec48b..aa34471b5c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoDatabase.java @@ -54,8 +54,10 @@ public String getName() { */ public MongoCollection getCollection(final String collectionName) { Util.checkEmpty(collectionName, "collectionName"); - return new MongoCollection<>(new MongoNamespace(name, collectionName), - osMongoDatabase.getCollection(collectionName)); + MongoNamespace namespace = new MongoNamespace(name, collectionName); + + return new MongoCollection<>(namespace, + osMongoDatabase.getCollection(collectionName, namespace)); } /** @@ -72,7 +74,8 @@ public MongoCollection getCollection( ) { Util.checkEmpty(collectionName, "collectionName"); Util.checkNull(documentClass, "documentClass"); - return new MongoCollection<>(new MongoNamespace(name, collectionName), - osMongoDatabase.getCollection(collectionName, documentClass)); + MongoNamespace namespace = new MongoNamespace(name, collectionName); + return new MongoCollection<>(namespace, + osMongoDatabase.getCollection(collectionName, namespace, documentClass)); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/events/BaseChangeEvent.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/events/BaseChangeEvent.java new file mode 100644 index 0000000000..ffd7c2849f --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/events/BaseChangeEvent.java @@ -0,0 +1,116 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.mongo.events; + +import org.bson.BsonDocument; + +import javax.annotation.Nullable; + +/** + * Represents the set of properties that exist on all MongoDB realm change events produced + * by watch streams in this SDK. Other change event types inherit from this type. + * + * @param The type of the full document in the change event. + */ +public abstract class BaseChangeEvent { + private final OperationType operationType; + @Nullable + private final DocumentT fullDocument; + private final BsonDocument documentKey; + @Nullable + private final UpdateDescription updateDescription; + + private final boolean hasUncommittedWrites; + + /** + * Returns the operation type of the change that triggered the change event. + * + * @return the operation type of this change event. + */ + public OperationType getOperationType() { + return operationType; + } + + /** + * The full document at some point after the change has been applied. + * + * @return the full document. + */ + @Nullable + public DocumentT getFullDocument() { + return fullDocument; + } + + /** + * The unique identifier for the document that was actually changed. + * + * @return the document key. + */ + public BsonDocument getDocumentKey() { + return documentKey; + } + + /** + * In the case of an update, the description of which fields have been added, removed or updated. + * + * @return the update description. + */ + @Nullable + public UpdateDescription getUpdateDescription() { + return updateDescription; + } + + /** + * Indicates a local change event that has not yet been synchronized with a remote data store. + * Used only for the sync use case. + * + * @return whether or not this change event represents uncommitted writes. + */ + public boolean hasUncommittedWrites() { + return hasUncommittedWrites; + } + + protected BaseChangeEvent( + final OperationType operationType, + @Nullable final DocumentT fullDocument, + final BsonDocument documentKey, + @Nullable final UpdateDescription updateDescription, + final boolean hasUncommittedWrites + ) { + this.operationType = operationType; + this.fullDocument = fullDocument; + this.documentKey = documentKey; + this.updateDescription = (updateDescription == null) + ? new UpdateDescription(null, null) : updateDescription; + this.hasUncommittedWrites = hasUncommittedWrites; + } + + /** + * Converts the change event to a BSON representation, as it would look on a MongoDB realm change + * stream, or a Realm compact watch stream. + * + * @return The BSON document representation of the change event. + */ + public abstract BsonDocument toBsonDocument(); + + /** + * Represents the different MongoDB operations that can occur. + */ + public enum OperationType { + INSERT, DELETE, REPLACE, UPDATE, UNKNOWN; + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/events/UpdateDescription.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/events/UpdateDescription.java new file mode 100644 index 0000000000..08c85fd58b --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/events/UpdateDescription.java @@ -0,0 +1,312 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.mongo.events; + +import org.bson.BsonArray; +import org.bson.BsonBoolean; +import org.bson.BsonDocument; +import org.bson.BsonElement; +import org.bson.BsonString; +import org.bson.BsonValue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +import io.realm.mongodb.AppException; +import io.realm.mongodb.ErrorCode; + +import static io.realm.internal.Util.checkContainsKey; + +/** + * Indicates which fields have been modified in a given update operation. + */ +public final class UpdateDescription { + private static final String DOCUMENT_VERSION_FIELD = "__stitch_sync_version"; + + private final BsonDocument updatedFields; + private final Set removedFields; + + /** + * Creates an update description with the specified updated fields and removed field names. + * + * @param updatedFields Nested key-value pair representation of updated fields. + * @param removedFields Collection of removed field names. + */ + UpdateDescription( + final BsonDocument updatedFields, + final Collection removedFields + ) { + this.updatedFields = (updatedFields == null) ? new BsonDocument() : updatedFields; + this.removedFields = (removedFields == null) ? new HashSet<>() : new HashSet<>(removedFields); + } + + /** + * Returns a {@link BsonDocument} containing keys and values representing (respectively) the + * fields that have changed in the corresponding update and their new values. + * + * @return the updated field names and their new values. + */ + public BsonDocument getUpdatedFields() { + return updatedFields; + } + + /** + * Returns a {@link List} containing the field names that have been removed in the corresponding + * update. + * + * @return the removed fields names. + */ + public Collection getRemovedFields() { + return removedFields; + } + + /** + * Convert this update description to an update document. + * + * @return an update document with the appropriate $set and $unset documents. + */ + public BsonDocument toUpdateDocument() { + final List unsets = new ArrayList<>(); + for (final String removedField : this.removedFields) { + unsets.add(new BsonElement(removedField, new BsonBoolean(true))); + } + final BsonDocument updateDocument = new BsonDocument(); + + if (this.updatedFields.size() > 0) { + updateDocument.append("$set", this.updatedFields); + } + + if (unsets.size() > 0) { + updateDocument.append("$unset", new BsonDocument(unsets)); + } + + return updateDocument; + } + + /** + * Converts this update description to its document representation as it would appear in a + * MongoDB Change Event. + * + * @return the update description document as it would appear in a change event + */ + public BsonDocument toBsonDocument() { + final BsonDocument updateDescDoc = new BsonDocument(); + updateDescDoc.put( + Fields.UPDATED_FIELDS_FIELD, + this.getUpdatedFields()); + + final BsonArray removedFields = new BsonArray(); + for (final String field : this.getRemovedFields()) { + removedFields.add(new BsonString(field)); + } + updateDescDoc.put( + Fields.REMOVED_FIELDS_FIELD, + removedFields); + + return updateDescDoc; + } + + /** + * Converts an update description BSON document from a MongoDB Change Event into an + * UpdateDescription object. + * + * @param document the + * @return the converted UpdateDescription + */ + public static UpdateDescription fromBsonDocument(final BsonDocument document) { + try { + checkContainsKey(Fields.UPDATED_FIELDS_FIELD, document, "document"); + checkContainsKey(Fields.REMOVED_FIELDS_FIELD, document, "document"); + } catch (IllegalArgumentException exception) { + throw new AppException(ErrorCode.EVENT_DESERIALIZING, exception); + } + + final BsonArray removedFieldsArr = + document.getArray(Fields.REMOVED_FIELDS_FIELD); + final Set removedFields = new HashSet<>(removedFieldsArr.size()); + for (final BsonValue field : removedFieldsArr) { + removedFields.add(field.asString().getValue()); + } + + return new UpdateDescription(document.getDocument(Fields.UPDATED_FIELDS_FIELD), removedFields); + } + + /** + * Unilaterally merge an update description into this update description. + * + * @param otherDescription the update description to merge into this + * @return this merged update description + */ + public UpdateDescription merge(@Nullable final UpdateDescription otherDescription) { + if (otherDescription != null) { + for (final Map.Entry entry : this.updatedFields.entrySet()) { + if (otherDescription.removedFields.contains(entry.getKey())) { + this.updatedFields.remove(entry.getKey()); + } + } + for (final String removedField : this.removedFields) { + if (otherDescription.updatedFields.containsKey(removedField)) { + this.removedFields.remove(removedField); + } + } + + this.removedFields.addAll(otherDescription.removedFields); + this.updatedFields.putAll(otherDescription.updatedFields); + } + + return this; + } + + /** + * Find the diff between two documents. + * + *

              NOTE: This does not do a full diff on {@link BsonArray}. If there is + * an inequality between the old and new array, the old array will + * simply be replaced by the new one. + * + * @param beforeDocument original document + * @param afterDocument document to diff on + * @param onKey the key for our depth level + * @param updatedFields contiguous document of updated fields, + * nested or otherwise + * @param removedFields contiguous list of removedFields, + * nested or otherwise + * @return a description of the updated fields and removed keys between the documents + */ + private static UpdateDescription diff( + final BsonDocument beforeDocument, + final BsonDocument afterDocument, + final @Nullable String onKey, + final BsonDocument updatedFields, + final Set removedFields) { + // for each key in this document... + for (final Map.Entry entry : beforeDocument.entrySet()) { + final String key = entry.getKey(); + // don't worry about the _id or version field for now + if (key.equals("_id") || key.equals(DOCUMENT_VERSION_FIELD)) { + continue; + } + final BsonValue oldValue = entry.getValue(); + + final String actualKey = onKey == null ? key : String.format("%s.%s", onKey, key); + // if the key exists in the other document AND both are BsonDocuments + // diff the documents recursively, carrying over the keys to keep + // updatedFields and removedFields flat. + // this will allow us to reference whole objects as well as nested + // properties. + // else if the key does not exist, the key has been removed. + if (afterDocument.containsKey(key)) { + final BsonValue newValue = afterDocument.get(key); + if ((oldValue instanceof BsonDocument) && (newValue instanceof BsonDocument)) { + diff((BsonDocument) oldValue, + (BsonDocument) newValue, + actualKey, + updatedFields, + removedFields); + } else if (!oldValue.equals(newValue)) { + updatedFields.put(actualKey, newValue); + } + } else { + removedFields.add(actualKey); + } + } + + // for each key in the other document... + for (final Map.Entry entry : afterDocument.entrySet()) { + final String key = entry.getKey(); + // don't worry about the _id or version field for now + if (key.equals("_id") || key.equals(DOCUMENT_VERSION_FIELD)) { + continue; + } + + final BsonValue newValue = entry.getValue(); + // if the key is not in the this document, + // it is a new key with a new value. + // updatedFields will included keys that must + // be newly created. + final String actualKey = (onKey == null) ? key : String.format("%s.%s", onKey, key); + if (!beforeDocument.containsKey(key)) { + updatedFields.put(actualKey, newValue); + } + } + + return new UpdateDescription(updatedFields, removedFields); + } + + /** + * Find the diff between two documents. + * + *

              NOTE: This does not do a full diff on [BsonArray]. If there is + * an inequality between the old and new array, the old array will + * simply be replaced by the new one. + * + * @param beforeDocument original document + * @param afterDocument document to diff on + * @return a description of the updated fields and removed keys between the documents. + */ + public static UpdateDescription diff( + @Nullable final BsonDocument beforeDocument, + @Nullable final BsonDocument afterDocument) { + if ((beforeDocument == null) || (afterDocument == null)) { + return new UpdateDescription(new BsonDocument(), new HashSet<>()); + } + + return UpdateDescription.diff( + beforeDocument, + afterDocument, + null, + new BsonDocument(), + new HashSet<>() + ); + } + + /** + * Determines whether this update description is empty. + * + * @return true if the update description is empty, false otherwise + */ + public boolean isEmpty() { + return (this.updatedFields.isEmpty()) && (this.removedFields.isEmpty()); + } + + @Override + public boolean equals(final Object obj) { + if ((obj == null) || !obj.getClass().equals(UpdateDescription.class)) { + return false; + } + final UpdateDescription other = (UpdateDescription) obj; + + return other.getRemovedFields().equals(this.removedFields) + && other.getUpdatedFields().equals(this.updatedFields); + } + + @Override + public int hashCode() { + return removedFields.hashCode() + (31 * updatedFields.hashCode()); + } + + private static final class Fields { + static final String UPDATED_FIELDS_FIELD = "updatedFields"; + static final String REMOVED_FIELDS_FIELD = "removedFields"; + } +} diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt index 94bdf0f945..a39443d57a 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt @@ -36,7 +36,7 @@ class TestApp( init { if (networkTransport != null) { - this.networkTransport = networkTransport; + this.setNetworkTransport(networkTransport) } } diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt index a52da63064..19b0e42f85 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/SyncTestUtils.kt @@ -17,14 +17,12 @@ package io.realm.mongodb import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm -import io.realm.RealmExt import io.realm.TestHelper +import io.realm.internal.network.OkHttpNetworkTransport import io.realm.internal.objectstore.OsJavaNetworkTransport import io.realm.log.LogLevel import io.realm.log.RealmLog import io.realm.mongodb.sync.SyncConfiguration -import io.realm.objectserver.utils.UserFactory -import io.realm.testClearApplicationContext import java.io.File import java.util.* @@ -57,11 +55,11 @@ class SyncTestUtils { @JvmStatic @JvmOverloads fun createTestUser(app: App, userIdentifier: String = UUID.randomUUID().toString()): User { - val transportBackup = app.networkTransport - app.networkTransport = object : OsJavaNetworkTransport() { + val transportBackup = app.osApp.networkTransport + app.osApp.networkTransport = object : OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: Map, body: String): Response { if (url.endsWith("/login")) { - return Response.httpResponse(200, mapOf(), """ + return OkHttpNetworkTransport.Response.httpResponse(200, mapOf(), """ { "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODM5NjcyMDgsImlhdCI6MTU4Mzk2NTQwOCwiaXNzIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWEzIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWU2OTYzZGVhZmVhNjMyNTQ1ODFjMDI1Iiwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoiYWNjZXNzIn0.J4mp8LnlsxTQRV_7W2Er4qY0tptR76PJGG1k6HSMmUYqgfpJC2Fnbcf1VCoebzoNolH2-sr8AHDVBBCyjxRjqoY9OudFHmWZKmhDV1ysxPP4XmID0nUuN45qJSO8QEAqoOmP1crXjrUZWedFw8aaCZE-bxYfvcDHyjBcbNKZqzawwUw2PyTOlrNjgs01k2J4o5a5XzYkEsJuzr4_8UqKW6zXvYj24UtqnqoYatW5EzpX63m2qig8AcBwPK4ZHb5wEEUdf4QZxkRY5QmTgRHP8SSqVUB_mkHgKaizC_tSB3E0BekaDfLyWVC1taAstXJNfzgFtLI86AzuXS2dCiCfqQ", "refresh_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODkxNDk0MDgsImlhdCI6MTU4Mzk2NTQwOCwic3RpdGNoX2RhdGEiOm51bGwsInN0aXRjaF9kZXZJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInN0aXRjaF9kb21haW5JZCI6IjVlNjk2M2RlYWZlYTYzMjU0NTgxYzAyNSIsInN0aXRjaF9pZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMyIsInN0aXRjaF9pZGVudCI6eyJpZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMC1oaWF2b3ZkbmJxbGNsYXBwYnl1cmJpaW8iLCJwcm92aWRlcl90eXBlIjoiYW5vbi11c2VyIiwicHJvdmlkZXJfaWQiOiI1ZTY5NjNlMGFmZWE2MzI1NDU4MWMwNGEifSwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoicmVmcmVzaCJ9.FhLdpmL48Mw0SyUKWuaplz3wfeS8TCO8S7I9pIJenQww9nPqQ7lIvykQxjCCtinGvsZIJKt_7R31xYCq4Jp53Nw81By79IwkXtO7VXHPsXXZG5_2xV-s0u44e85sYD5su_H-xnx03sU2piJbWJLSB8dKu3rMD4mO-S0HNXCCAty-JkYKSaM2-d_nS8MNb6k7Vfm7y69iz_uwHc-bb_1rPg7r827K6DEeEMF41Hy3Nx1kCdAUOM9-6nYv3pZSU1PFrGYi2uyTXPJ7R7HigY5IGHWd0hwONb_NUr4An2omqfvlkLEd77ut4V9m6mExFkoKzRz7shzn-IGkh3e4h7ECGA", @@ -70,7 +68,7 @@ class SyncTestUtils { } """.trimIndent()) } else if (url.endsWith("/auth/profile")) { - return Response.httpResponse(200, mapOf(), """ + return OkHttpNetworkTransport.Response.httpResponse(200, mapOf(), """ { "user_id": "$userIdentifier", "domain_id": "000000000000000000000000", @@ -97,7 +95,7 @@ class SyncTestUtils { } """.trimIndent()) } else if (url.endsWith("/location")) { - return Response.httpResponse(200, mapOf(), """ + return OkHttpNetworkTransport.Response.httpResponse(200, mapOf(), """ { "deployment_model" : "GLOBAL", "location": "US-VA", "hostname": "http://localhost:9090", @@ -108,9 +106,13 @@ class SyncTestUtils { throw IllegalStateException("Unsupported URL: $url") } } + + override fun sendStreamingRequest(request: Request): Response { + TODO("Not yet implemented") + } } val user = app.login(Credentials.emailPassword(TestHelper.getRandomEmail(), "123456")) - app.networkTransport = transportBackup + app.osApp.networkTransport = transportBackup return user } diff --git a/tools/sync_test_server/mongodb-realm-command-server.js b/tools/sync_test_server/mongodb-realm-command-server.js index e26f4ee323..2ff237cfda 100755 --- a/tools/sync_test_server/mongodb-realm-command-server.js +++ b/tools/sync_test_server/mongodb-realm-command-server.js @@ -31,6 +31,14 @@ function handleOkHttp(req, resp) { } } +function handleWatcher(req, resp) { + resp.writeHead(200, {'Content-Type': 'text/event-stream'}); + + resp.write("hello world 1\n"); + resp.write("hello world 2\n"); + resp.write("hello world 3\n"); +} + function handleApplicationId(req, resp) { switch(req.method) { case "GET": @@ -63,6 +71,8 @@ var server = http.createServer(function(req, resp) { handleOkHttp(req, resp); } else if (req.url.includes('/application-id')) { handleApplicationId(req, resp); + } else if (req.url.includes('/watcher')) { + handleWatcher(req, resp); } else { handleUnknownEndPoint(req, resp); } From 4aa94b1cde481d5d42c81ce0bf1ee7a170aa2636 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 25 Aug 2020 13:57:15 +0200 Subject: [PATCH 1645/2110] Upgrade to latest Build Tools and Gradle (#7036) --- Jenkinsfile | 47 ++++++--- README.md | 4 +- dependencies.list | 19 ++-- examples/build.gradle | 1 + examples/compatibilityExample/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- examples/kotlinExample/build.gradle | 2 +- examples/mongoDbRealmExample/build.gradle | 2 +- examples/rxJavaExample/build.gradle | 2 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle.properties | 4 - gradle/wrapper/gradle-wrapper.properties | 2 +- library-benchmarks/build.gradle | 5 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- library-build-transformer/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-transformer/build.gradle | 5 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/build.gradle | 3 +- .../checkstyle/checkstyle-suppressions.xml | 1 + realm/config/checkstyle/checkstyle.xml | 82 +-------------- realm/config/pmd/ruleset.xml | 5 +- realm/gradle.properties | 3 - .../gradle/wrapper/gradle-wrapper.properties | 3 +- realm/kotlin-extensions/build.gradle | 4 +- .../realm-annotations-processor/build.gradle | 2 +- realm/realm-library/build.gradle | 99 +++++++++---------- .../realm-library/src/main/cpp/CMakeLists.txt | 4 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../network/OkHttpNetworkTransport.java | 4 + tools/sync_test_server/start_server.sh | 2 +- tools/update_gradle_wrapper.sh | 7 +- 33 files changed, 135 insertions(+), 195 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d4b174ca47..60ac418429 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -71,8 +71,8 @@ try { // in Copenhagen being too slow. So the upload times out. buildEnv = buildDockerEnv("ci/realm-java:v10", push: currentBranch == 'v10-do-not-cache') def props = readProperties file: 'dependencies.list' - echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER_VERSION}" - def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER_VERSION}") + echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER}" + def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER}") docker.withRegistry('https://docker.pkg.github.com', 'github-packages-token') { mdbRealmImage.pull() } @@ -175,7 +175,11 @@ try { def runBuild(abiFilter, instrumentationTestTarget) { stage('Build') { - sh "chmod +x gradlew && ./gradlew assemble javadoc ${abiFilter} --stacktrace" + sh "chmod +x gradlew && ./gradlew assemble ${abiFilter} --stacktrace" + } + + stage('JavaDoc') { + sh "chmod +x gradlew && ./gradlew javadoc ${abiFilter} --stacktrace" } stage('Tests') { @@ -198,17 +202,34 @@ def runBuild(abiFilter, instrumentationTestTarget) { }, 'Static code analysis' : { try { - gradle('realm', "findbugs ${abiFilter}") // FIXME Renable pmd and checkstyle + gradle('realm', "spotbugsMain pmd checkstyle ${abiFilter}") } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) - // publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) - // step([$class: 'CheckStylePublisher', - // canComputeNew: false, - // defaultEncoding: '', - // healthy: '', - // pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - // unHealthy: '' - // ]) + publishHTML(target: [ + allowMissing: false, + alwaysLinkToLastBuild: false, + keepAll: true, + reportDir: 'realm/realm-library/build/reports/spotbugs', + reportFiles: 'main.html', + reportName: 'Spotbugs report' + ]) + + publishHTML(target: [ + allowMissing: false, + alwaysLinkToLastBuild: false, + keepAll: true, + reportDir: 'realm/realm-library/build/reports/pmd', + reportFiles: 'pmd.html', + reportName: 'PMD report' + ]) + + publishHTML(target: [ + allowMissing: false, + alwaysLinkToLastBuild: false, + keepAll: true, + reportDir: 'realm/realm-library/build/reports/checkstyle', + reportFiles: 'checkstyle.html', + reportName: 'Checkstyle report' + ]) } }, 'Instrumentation' : { diff --git a/README.md b/README.md index a4a8da8594..96089aec8a 100644 --- a/README.md +++ b/README.md @@ -180,10 +180,10 @@ Generating the Javadoc using the command above may generate warnings. The Javado ### Upgrading Gradle Wrappers - All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer to `gradleVersion` property defined in `/dependencies.list` to determine Gradle Version of generating wrappers. + All gradle projects in this repository have `wrapper` task to generate Gradle Wrappers. Those tasks refer to `gradle` property defined in `/dependencies.list` to determine Gradle Version of generating wrappers. We have a script `./tools/update_gradle_wrapper.sh` to automate these steps. When you update Gradle Wrappers, please obey the following steps. - 1. Edit `gradleVersion` property in defined in `/dependencies.list` to new Gradle Wrapper version. + 1. Edit `gradle` property in defined in `/dependencies.list` to new Gradle Wrapper version. 2. Execute `/tools/update_gradle_wrapper.sh`. ### Gotchas diff --git a/dependencies.list b/dependencies.list index 0a6e0e73fb..11bd18e52b 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,21 +1,26 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=10.0.0-beta.6 +REALM_SYNC=10.0.0-beta.6 REALM_SYNC_SHA256=824192a67e7ded59d33707265f78c8d5546b1fef473e9ee5ecff8a5bc9f8506e # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER_VERSION=2020-08-24 +MONGODB_REALM_SERVER=2020-08-24 # Common Android settings across projects -GRADLE_BUILD_TOOLS=3.6.1 +GRADLE_BUILD_TOOLS=4.0.0 ANDROID_BUILD_TOOLS=29.0.3 +KOTLIN=1.3.72 # Common classpath dependencies -gradleVersion=5.6.4 +gradle=6.5 ndkVersion=21.0.6113669 -BUILD_INFO_EXTRACTOR_GRADLE=4.7.5 -GRADLE_BINTRAY_PLUGIN=1.8.4 +BUILD_INFO_EXTRACTOR_GRADLE=4.17.0 +GRADLE_BINTRAY_PLUGIN=1.8.5 # Bson dependency version -BSON_DEPENDENCY_VERSION=3.12.1 +BSON_DEPENDENCY=3.12.1 + +# RxJava dependency version +RXJAVA_DEPENDENCY=2.1.5 +RXANDROID_DEPENDENCY=2.1.1 diff --git a/examples/build.gradle b/examples/build.gradle index 04c40a67c5..e14b5d816a 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -3,6 +3,7 @@ projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) project.ext.sdkVersion = 29 project.ext.minSdkVersion = 16 project.ext.buildTools = projectDependencies.get("ANDROID_BUILD_TOOLS") +project.ext.kotlinVersion = projectDependencies.get('KOTLIN') // Don't cache SNAPSHOT (changing) dependencies. configurations.all { diff --git a/examples/compatibilityExample/build.gradle b/examples/compatibilityExample/build.gradle index 46f231ed3a..c16dc995f5 100644 --- a/examples/compatibilityExample/build.gradle +++ b/examples/compatibilityExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = rootProject.kotlinVersion repositories { google() jcenter() diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 0ebb3108e2..186b71557c 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 46a1af33a3..1aee951c26 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.21' + ext.kotlin_version = rootProject.kotlinVersion repositories { jcenter() mavenCentral() diff --git a/examples/mongoDbRealmExample/build.gradle b/examples/mongoDbRealmExample/build.gradle index 989a65a9bb..7d0697752c 100644 --- a/examples/mongoDbRealmExample/build.gradle +++ b/examples/mongoDbRealmExample/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = rootProject.kotlinVersion repositories { google() jcenter() diff --git a/examples/rxJavaExample/build.gradle b/examples/rxJavaExample/build.gradle index 63fddcb435..b97c3cfa87 100644 --- a/examples/rxJavaExample/build.gradle +++ b/examples/rxJavaExample/build.gradle @@ -18,9 +18,7 @@ android { buildTypes { release { - // FIXME: Fix the proguard with 3rd party libs minifyEnabled false - signingConfig signingConfigs.debug } } diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 0ebb3108e2..186b71557c 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle.properties b/gradle.properties index 9306f6c0a1..1e7f6c617c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,3 @@ org.gradle.jvmargs=-XX:MaxPermSize=512m org.gradle.caching=true android.enableD8=true - -# See https://issuetracker.google.com/issues/80464216 -# Can be removed when we upgrade to Android Build Tools 3.3.0 -org.gradle.workers.max=1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0ebb3108e2..186b71557c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 8217ff06be..313e9729b7 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -1,8 +1,7 @@ buildscript { - ext.kotlin_version = '1.3.50' def properties = new Properties() properties.load(new FileInputStream("${rootDir}/../dependencies.list")) - + ext.kotlin_version = properties.get('KOTLIN') repositories { mavenLocal() google() @@ -12,7 +11,7 @@ buildscript { classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0-rc01" classpath "com.android.tools.build:gradle:${properties.get("GRADLE_BUILD_TOOLS")}" classpath "io.realm:realm-gradle-plugin:${file("${rootDir}/../version.txt").text.trim()}" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${properties.get("KOTLIN")}" } } diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index 0ebb3108e2..186b71557c 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-build-transformer/build.gradle b/library-build-transformer/build.gradle index f6268d9368..2f0eb54364 100644 --- a/library-build-transformer/build.gradle +++ b/library-build-transformer/build.gradle @@ -4,7 +4,7 @@ version '1.0.0' buildscript { def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../dependencies.list")) - ext.kotlin_version = '1.2.61' + ext.kotlin_version = properties.get('KOTLIN') repositories { mavenCentral() diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties index 0ebb3108e2..186b71557c 100644 --- a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 0ebb3108e2..186b71557c 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index d5cac03d0e..48fda83a40 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -1,8 +1,7 @@ buildscript { def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../dependencies.list")) - - ext.kotlin_version = '1.2.50' + ext.kotlin_version = properties.get('KOTLIN') repositories { google() jcenter() @@ -27,7 +26,7 @@ version = file("${projectDir}/../version.txt").text.trim() def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../dependencies.list")) -def syncVersion = properties.getProperty('REALM_SYNC_VERSION') +def syncVersion = properties.getProperty('REALM_SYNC') sourceCompatibility = '1.8' targetCompatibility = '1.8' diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 0ebb3108e2..186b71557c 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm/build.gradle b/realm/build.gradle index 446809b487..5536004072 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -1,7 +1,7 @@ buildscript { def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) - ext.kotlin_version = '1.3.70' + ext.kotlin_version = projectDependencies.get('KOTLIN') ext.dokka_version = '0.10.1' repositories { mavenLocal() @@ -25,6 +25,7 @@ buildscript { classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.13' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" + classpath "gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.5.0" } } diff --git a/realm/config/checkstyle/checkstyle-suppressions.xml b/realm/config/checkstyle/checkstyle-suppressions.xml index 6e71089d31..6e0a9a0d16 100644 --- a/realm/config/checkstyle/checkstyle-suppressions.xml +++ b/realm/config/checkstyle/checkstyle-suppressions.xml @@ -8,4 +8,5 @@ + diff --git a/realm/config/checkstyle/checkstyle.xml b/realm/config/checkstyle/checkstyle.xml index 1dada765c3..107f3e9e6a 100644 --- a/realm/config/checkstyle/checkstyle.xml +++ b/realm/config/checkstyle/checkstyle.xml @@ -8,7 +8,7 @@ - + @@ -18,12 +18,6 @@ - @@ -38,65 +32,24 @@ - - - - - - - - - - - - - - @@ -113,11 +66,6 @@ - - - - - @@ -125,33 +73,10 @@ - - - - @@ -160,11 +85,6 @@ - - - diff --git a/realm/config/pmd/ruleset.xml b/realm/config/pmd/ruleset.xml index c777aa8319..6e85b40d96 100644 --- a/realm/config/pmd/ruleset.xml +++ b/realm/config/pmd/ruleset.xml @@ -8,6 +8,7 @@ Realm PMD ruleset - - + + + diff --git a/realm/gradle.properties b/realm/gradle.properties index d3b9be87a4..f2a177a0f1 100644 --- a/realm/gradle.properties +++ b/realm/gradle.properties @@ -3,8 +3,5 @@ org.gradle.caching=true kotlin.incremental=false org.gradle.parallel=false -# See https://issuetracker.google.com/issues/80464216 -# Can be removed when we upgrade to Android Build Tools 3.3.0 -org.gradle.workers.max=1 android.useAndroidX=true android.enableJetifier=true diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 0ebb3108e2..a484c5678e 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Fri Mar 06 14:53:06 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index fcee6a4164..a9ba048efe 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -79,12 +79,12 @@ dependencies { androidTestImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:rules:1.2.0' - androidTestImplementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY_VERSION')}" + androidTestImplementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY')}" androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2' kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" androidTestObjectServerImplementation 'com.squareup.okhttp3:okhttp:3.9.0' - androidTestObjectServerImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' + androidTestObjectServerImplementation "io.reactivex.rxjava2:rxjava:${properties.getProperty('RXJAVA_DEPENDENCY')}" } repositories { diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 21734dbcd4..24d50f40b9 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -13,7 +13,7 @@ properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) dependencies { implementation "com.squareup:javawriter:2.5.1" implementation "io.realm:realm-annotations:${version}" - implementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY_VERSION')}" + implementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY')}" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testImplementation files('../realm-library/build/intermediates/aar_main_jar/baseRelease/classes.jar') // Java projects cannot depend on AAR files testImplementation files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index a594aed7a5..b7764a6bd6 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -6,8 +6,8 @@ apply plugin: 'kotlin-kapt' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.artifactory' apply plugin: 'maven-publish' -apply plugin: 'findbugs' -// apply plugin: 'pmd' FIXME Disabled PMD after upgrading Gradle Plugin. It resulted in a ton of false positives. Review those later. +apply plugin: 'com.github.spotbugs-base' +apply plugin: 'pmd' apply plugin: 'checkstyle' apply plugin: 'com.github.kt3k.coveralls' apply plugin: 'de.undercouch.download' @@ -16,7 +16,7 @@ apply plugin: 'net.ltgt.errorprone' def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) -ext.coreVersion = properties.getProperty('REALM_SYNC_VERSION') +ext.coreVersion = properties.getProperty('REALM_SYNC') // empty or comment out this to disable hash checking ext.coreSha256Hash = properties.getProperty('REALM_SYNC_SHA256') ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false @@ -164,6 +164,7 @@ android { } } + project.afterEvaluate { tasks.withType(JavaCompile) { options.compilerArgs << '-Werror' @@ -207,8 +208,8 @@ repositories { dependencies { - compileOnly 'io.reactivex.rxjava2:rxjava:2.1.5' - compileOnly 'com.google.code.findbugs:findbugs-annotations:3.0.1' + compileOnly "io.reactivex.rxjava2:rxjava:${properties.getProperty('RXJAVA_DEPENDENCY')}" + compileOnly 'com.github.spotbugs:spotbugs-annotations:4.1.2' testImplementation 'junit:junit:4.12' testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -217,22 +218,19 @@ dependencies { api "io.realm:realm-annotations:${version}" implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.getkeepsafe.relinker:relinker:1.4.0' - implementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY_VERSION')}" - implementation('io.reactivex.rxjava2:rxandroid:2.1.1') { + api "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY')}" + implementation("io.reactivex.rxjava2:rxandroid:${properties.getProperty('RXANDROID_DEPENDENCY')}") { exclude group: 'io.reactivex.rxjava2', module: 'rxjava' } - // TODO: investigate why we can't use the latest multidex version - // check baseDebugAndroidTestRuntimeClasspath and objectServerDebugAndroidTestRuntimeClasspath - // tasks as they introduce version 2.0.0 strictly, even when specifying 2.0.1 from here - androidTestImplementation "androidx.multidex:multidex:2.0.0" + androidTestImplementation "androidx.multidex:multidex:2.0.1" kapt project(':realm-annotations-processor') // See https://github.com/realm/realm-java/issues/5799 objectServerImplementation 'com.squareup.okhttp3:okhttp:3.12.0' // Going above this requires minSDK 21 kaptAndroidTest project(':realm-annotations-processor') androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - androidTestImplementation 'io.reactivex.rxjava2:rxjava:2.1.5' - androidTestImplementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + androidTestImplementation "io.reactivex.rxjava2:rxjava:${properties.getProperty('RXJAVA_DEPENDENCY')}" + androidTestImplementation "io.reactivex.rxjava2:rxandroid:${properties.getProperty('RXANDROID_DEPENDENCY')}" androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' @@ -275,7 +273,7 @@ task javadoc(type: Javadoc) { links "https://docs.oracle.com/javase/7/docs/api/" links "http://reactivex.io/RxJava/javadoc/" // TODO We probably need to add the bson.jar to the classpath for these to work - links "https://www.javadoc.io/doc/org.mongodb/bson/${properties.getProperty('BSON_DEPENDENCY_VERSION')}/" + links "https://www.javadoc.io/doc/org.mongodb/bson/${properties.getProperty('BSON_DEPENDENCY')}/" linksOffline "https://developer.android.com/reference/", "${project.android.sdkDirectory}/docs/reference" tags = [betaTag] @@ -296,66 +294,59 @@ task javadocJar(type: Jar, dependsOn: javadoc) { from javadoc.destinationDir } -task findbugs(type: FindBugs) { - dependsOn assemble - group = 'Verification' - +// See https://spotbugs-gradle-plugin.netlify.app/com/github/spotbugs/snom/spotbugsextension +spotbugs { + toolVersion = '4.1.1' ignoreFailures = false effort = "default" reportLevel = "medium" excludeFilter = file("${projectDir}/../config/findbugs/findbugs-filter.xml") - classes = files("${projectDir}/build/intermediates/javac") - source = fileTree('src/main/java/') - classpath = files() +} + +// See https://spotbugs.readthedocs.io/en/latest/migration.html#findbugs-gradle-plugin +task spotbugsMain(type: com.github.spotbugs.snom.SpotBugsTask) { + dependsOn 'assemble' + group = 'verification' + classes = fileTree("build/intermediates/javac/") + reports { xml.enabled = false html.enabled = true - xml { - destination file("$project.buildDir/findbugs/findbugs-output.xml") - } - html { - destination file("$project.buildDir/findbugs/findbugs-output.html") - } } } -//task pmd(type: Pmd) { -// group = 'Verification' -// -// source = fileTree('src/main/java') -// ruleSetFiles = files("${projectDir}/../config/pmd/ruleset.xml") -// -// reports { -// xml.enabled = false -// html.enabled = true -// } -//} +task pmd(type: Pmd) { + group = 'verification' + + source = fileTree('src/main/java') + fileTree('src/objectServer/java') + ruleSetFiles = files("${projectDir}/../config/pmd/ruleset.xml") + + ruleSets = [] + + reports { + xml.enabled = false + html.enabled = true + } +} // Configure Checkstyle // Android sourceSets are not sourceSets, so we can't confgure this with the DSL. task checkstyle(type: Checkstyle) { - group = 'Verification' + group = 'verification' + configFile file("${projectDir}/../config/checkstyle/checkstyle.xml") - source 'src' - include '*/java/**/*.java' - // Ignore tests for now. - exclude '*Test*/**' + source fileTree('src/main/java') + fileTree('src/objectServer/java') + include '**/*.java' + exclude '**/gen/**' // empty classpath classpath = files() -} - -checkstyle { - toolVersion ="7.6" - configFile = file("${projectDir}/../config/checkstyle/checkstyle.xml") - - def configProps = ['proj.module.dir': projectDir.absolutePath] - configProperties configProps - - ignoreFailures = true + reports { + xml.enabled = false + html.enabled = true + } } -check.dependsOn tasks.checkstyle install { repositories.mavenInstaller { diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index b05a22231e..ebf8cd36a5 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -81,8 +81,8 @@ capitalizeFirstLetter(buildTypeCap "${CMAKE_BUILD_TYPE}") # Generate JNI header files. Each build has its own JNI header in its build_dir/jni_include. # WARNING: The classes_PATH is not part the public API offered by the Android Gradle Plugin # so it might change without warning when upgrading the plugin. -file(DOWNLOAD "https://repo1.maven.org/maven2/org/mongodb/bson/${DEP_BSON_DEPENDENCY_VERSION}/bson-${DEP_BSON_DEPENDENCY_VERSION}.jar" "${PROJECT_BINARY_DIR}/bson-${DEP_BSON_DEPENDENCY_VERSION}.jar") -set(bsonlib_PATH ${PROJECT_BINARY_DIR}/bson-${DEP_BSON_DEPENDENCY_VERSION}.jar) +file(DOWNLOAD "https://repo1.maven.org/maven2/org/mongodb/bson/${DEP_BSON_DEPENDENCY}/bson-${DEP_BSON_DEPENDENCY}.jar" "${PROJECT_BINARY_DIR}/bson-${DEP_BSON_DEPENDENCY}.jar") +set(bsonlib_PATH ${PROJECT_BINARY_DIR}/bson-${DEP_BSON_DEPENDENCY}.jar) set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/classes/) set(classes_LIST io.realm.RealmQuery diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 39e2000676..5b5fb8a901 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 39e20006761e77014ceb19a2bd8f43018cc96f5a +Subproject commit 5b5fb8a90192cb4ee6799e7465745cd2067f939b diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index cda03b7b17..62149c0de1 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -7,6 +7,8 @@ import javax.annotation.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.realm.mongodb.log.obfuscator.HttpLogObfuscator; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.mongodb.AppException; import io.realm.mongodb.ErrorCode; @@ -63,6 +65,8 @@ private okhttp3.Request makeRequest(String method, String url, Map headers, String body) { try { diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 4cc01512cf..7ebf51b0f9 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -24,7 +24,7 @@ fi # Get the script dir which contains the Dockerfile DOCKERFILE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -MONGODB_REALM_VERSION=$(grep MONGODB_REALM_SERVER_VERSION $DOCKERFILE_DIR/../../dependencies.list | cut -d'=' -f2) +MONGODB_REALM_VERSION=$(grep MONGODB_REALM_SERVER $DOCKERFILE_DIR/../../dependencies.list | cut -d'=' -f2) adb reverse tcp:9443 tcp:9443 && \ adb reverse tcp:9080 tcp:9080 && \ diff --git a/tools/update_gradle_wrapper.sh b/tools/update_gradle_wrapper.sh index 6096d3535e..ae7b941f48 100755 --- a/tools/update_gradle_wrapper.sh +++ b/tools/update_gradle_wrapper.sh @@ -10,10 +10,15 @@ HERE=`pwd` cd "$(dirname $0)/.." +GRADLE=`grep gradle dependencies.list | cut -d = -f2` +echo "==> Update gradle to version: $GRADLE <==" +echo +read -n1 -r -p "Press any key to continue..." key + for i in $(find $(pwd) -type f -name gradlew); do cd $(dirname $i) pwd - ./gradlew wrapper + ./gradlew wrapper --gradle-version=$GRADLE done cd $HERE From 85b4e079394e412237997cffd5109c92e511e8cb Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 25 Aug 2020 16:54:35 +0200 Subject: [PATCH 1646/2110] Fix CI build process after merging upgrade build tools and gradle (#7059) --- realm/realm-library/build.gradle | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index b7764a6bd6..1cebf9c292 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -338,7 +338,7 @@ task checkstyle(type: Checkstyle) { source fileTree('src/main/java') + fileTree('src/objectServer/java') include '**/*.java' exclude '**/gen/**' - + ignoreFailures true // empty classpath classpath = files() diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 5b5fb8a901..39e2000676 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 5b5fb8a90192cb4ee6799e7465745cd2067f939b +Subproject commit 39e20006761e77014ceb19a2bd8f43018cc96f5a From a813aa8cbe5b736febad667362f5edcddbdf8415 Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 25 Aug 2020 18:44:29 +0200 Subject: [PATCH 1647/2110] Fix checkstyle issues (#7058) --- realm/config/checkstyle/checkstyle.xml | 11 +- realm/realm-library/build.gradle | 2 +- .../src/main/java/io/realm/BaseRealm.java | 2 +- .../src/main/java/io/realm/DynamicRealm.java | 2 +- .../java/io/realm/ManagedListOperator.java | 809 ++++++++++++++++++ .../io/realm/OrderedCollectionChangeSet.java | 2 +- .../io/realm/OrderedRealmCollectionImpl.java | 4 +- .../src/main/java/io/realm/Realm.java | 2 +- .../src/main/java/io/realm/RealmCache.java | 2 +- .../src/main/java/io/realm/RealmList.java | 782 +---------------- .../main/java/io/realm/SchemaConnector.java | 2 +- .../java/io/realm/internal/IdentitySet.java | 2 +- .../io/realm/internal/ObjectServerFacade.java | 4 +- .../realm/internal/ObservableCollection.java | 2 +- .../realm/internal/OsCollectionChangeSet.java | 6 +- .../java/io/realm/internal/OsObjectStore.java | 14 +- .../java/io/realm/internal/OsRealmConfig.java | 2 +- .../java/io/realm/internal/OsResults.java | 4 +- .../java/io/realm/internal/OsSharedRealm.java | 4 +- .../java/io/realm/internal/RealmNotifier.java | 2 +- .../main/java/io/realm/internal/Table.java | 4 +- .../realm/internal/core/QueryDescriptor.java | 6 +- .../java/io/realm/internal/util/Pair.java | 2 +- .../java/io/realm/rx/CollectionChange.java | 2 +- .../main/java/io/realm/rx/ObjectChange.java | 2 +- .../network/OkHttpNetworkTransport.java | 4 +- .../objectstore/OsJavaNetworkTransport.java | 4 +- .../java/io/realm/mongodb/App.java | 2 +- .../io/realm/mongodb/AppConfiguration.java | 4 +- .../io/realm/mongodb/auth/ApiKeyAuth.java | 2 +- .../io/realm/mongodb/mongo/MongoClient.java | 2 +- .../mongodb/mongo/iterable/MongoIterable.java | 2 +- .../io/realm/mongodb/sync/SyncSession.java | 4 +- 33 files changed, 869 insertions(+), 831 deletions(-) create mode 100644 realm/realm-library/src/main/java/io/realm/ManagedListOperator.java diff --git a/realm/config/checkstyle/checkstyle.xml b/realm/config/checkstyle/checkstyle.xml index 107f3e9e6a..4fd7feadb3 100644 --- a/realm/config/checkstyle/checkstyle.xml +++ b/realm/config/checkstyle/checkstyle.xml @@ -14,7 +14,11 @@ - + + + + + @@ -31,7 +35,12 @@ + + + + + diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 1cebf9c292..b7764a6bd6 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -338,7 +338,7 @@ task checkstyle(type: Checkstyle) { source fileTree('src/main/java') + fileTree('src/objectServer/java') include '**/*.java' exclude '**/gen/**' - ignoreFailures true + // empty classpath classpath = files() diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 9229240537..0d45b0d5ac 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -634,7 +634,7 @@ public boolean isClosed() { * * @return {@code true} if empty, @{code false} otherwise. */ - abstract public boolean isEmpty(); + public abstract boolean isEmpty(); /** * Returns the schema for this Realm. diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 57c743d65f..054b73336d 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -400,7 +400,7 @@ public interface Transaction { /** * {@inheritDoc} */ - public static abstract class Callback extends InstanceCallback { + public abstract static class Callback extends InstanceCallback { /** * {@inheritDoc} */ diff --git a/realm/realm-library/src/main/java/io/realm/ManagedListOperator.java b/realm/realm-library/src/main/java/io/realm/ManagedListOperator.java new file mode 100644 index 0000000000..36382c9406 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/ManagedListOperator.java @@ -0,0 +1,809 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm; + +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.realm.internal.OsList; +import io.realm.internal.OsObjectStore; +import io.realm.internal.RealmObjectProxy; +import io.realm.internal.RealmProxyMediator; +import io.realm.internal.Table; +import io.realm.internal.Util; + +/** + * This class provides facade for against {@link OsList}. {@link OsList} is used for both {@link RealmModel}s + * and values, but there are some subtle differences in actual operation. + *

              + * This class provides common interface for them. + *

              + * You need to use appropriate sub-class for underlying field type. + * + * @param class of element which is returned on read operation. + */ +abstract class ManagedListOperator { + static final String NULL_OBJECTS_NOT_ALLOWED_MESSAGE = "RealmList does not accept null values."; + static final String INVALID_OBJECT_TYPE_MESSAGE = "Unacceptable value type. Acceptable: %1$s, actual: %2$s ."; + + final BaseRealm realm; + final OsList osList; + @Nullable + final Class clazz; + + ManagedListOperator(BaseRealm realm, OsList osList, @Nullable Class clazz) { + this.realm = realm; + this.clazz = clazz; + this.osList = osList; + } + + public abstract boolean forRealmModel(); + + public final OsList getOsList() { + return osList; + } + + public final boolean isValid() { + return osList.isValid(); + } + + public final int size() { + final long actualSize = osList.size(); + return actualSize < Integer.MAX_VALUE ? (int) actualSize : Integer.MAX_VALUE; + } + + public final boolean isEmpty() { + return osList.isEmpty(); + } + + protected abstract void checkValidValue(@Nullable Object value); + + @Nullable + public abstract T get(int index); + + public final void append(@Nullable Object value) { + checkValidValue(value); + + if (value == null) { + appendNull(); + } else { + appendValue(value); + } + } + + private void appendNull() { + osList.addNull(); + } + + protected abstract void appendValue(Object value); + + public final void insert(int index, @Nullable Object value) { + checkValidValue(value); + + if (value == null) { + insertNull(index); + } else { + insertValue(index, value); + } + + } + + protected void insertNull(int index) { + osList.insertNull(index); + } + + protected abstract void insertValue(int index, Object value); + + @Nullable + public final T set(int index, @Nullable Object value) { + checkValidValue(value); + + //noinspection unchecked + final T oldObject = get(index); + if (value == null) { + setNull(index); + } else { + setValue(index, value); + } + return oldObject; + } + + protected void setNull(int index) { + osList.setNull(index); + } + + protected abstract void setValue(int index, Object value); + + final void move(int oldPos, int newPos) { + osList.move(oldPos, newPos); + } + + final void remove(int index) { + osList.remove(index); + } + + final void removeAll() { + osList.removeAll(); + } + + final void delete(int index) { + osList.delete(index); + } + + final void deleteLast() { + osList.delete(osList.size() - 1); + } + + final void deleteAll() { + osList.deleteAll(); + } + +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link RealmModel} list field. + */ +final class RealmModelListOperator extends ManagedListOperator { + + @Nullable + private final String className; + + RealmModelListOperator(BaseRealm realm, OsList osList, @Nullable Class clazz, @Nullable String className) { + super(realm, osList, clazz); + this.className = className; + } + + @Override + public boolean forRealmModel() { + return true; + } + + @Override + public T get(int index) { + //noinspection unchecked + return (T) realm.get((Class) clazz, className, osList.getUncheckedRow(index)); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + throw new IllegalArgumentException(NULL_OBJECTS_NOT_ALLOWED_MESSAGE); + } + if (!(value instanceof RealmModel)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.String", + value.getClass().getName())); + } + } + + private void checkInsertIndex(int index) { + final int size = size(); + if (index < 0 || size < index) { + throw new IndexOutOfBoundsException("Invalid index " + index + ", size is " + osList.size()); + } + } + + @Override + public void appendValue(Object value) { + RealmModel realmObject = (RealmModel) value; + boolean copyObject = checkCanObjectBeCopied(realm, realmObject); + if (isEmbedded((RealmModel) value)) { + if (value instanceof DynamicRealmObject) { + throw new IllegalArgumentException("Embedded objects are not supported by RealmLists of DynamicRealmObjects yet."); + } + long objKey = osList.createAndAddEmbeddedObject(); + updateEmbeddedObject(realmObject, objKey); + } else { + RealmObjectProxy proxy = (RealmObjectProxy) ((copyObject) ? copyToRealm((RealmModel) value) : realmObject); + osList.addRow(proxy.realmGet$proxyState().getRow$realm().getObjectKey()); + } + } + + @Override + protected void insertNull(int index) { + throw new RuntimeException("Should not reach here."); + } + + @Override + public void insertValue(int index, Object value) { + // need to check in advance to avoid unnecessary copy of unmanaged object into Realm. + checkInsertIndex(index); + RealmModel realmObject = (RealmModel) value; + boolean copyObject = checkCanObjectBeCopied(realm, realmObject); + if (isEmbedded(realmObject)) { + if (value instanceof DynamicRealmObject) { + throw new IllegalArgumentException("Embedded objects are not supported by RealmLists of DynamicRealmObjects yet."); + } + long objKey = osList.createAndAddEmbeddedObject(index); + updateEmbeddedObject(realmObject, objKey); + } else { + RealmObjectProxy proxy = (RealmObjectProxy) ((copyObject) ? copyToRealm((RealmModel) value) : realmObject); + osList.insertRow(index, proxy.realmGet$proxyState().getRow$realm().getObjectKey()); + } + } + + private boolean isEmbedded(RealmModel value) { + if (realm instanceof Realm) { + return realm.getSchema().getSchemaForClass(value.getClass()).isEmbedded(); + } else { + String objectType = ((DynamicRealmObject) value).getType(); + return realm.getSchema().getSchemaForClass(objectType).isEmbedded(); + } + } + + @Override + protected void setNull(int index) { + throw new RuntimeException("Should not reach here."); + } + + @Override + protected void setValue(int index, Object value) { + RealmModel realmObject = (RealmModel) value; + boolean copyObject = checkCanObjectBeCopied(realm, realmObject); + if (isEmbedded(realmObject)) { + if (value instanceof DynamicRealmObject) { + throw new IllegalArgumentException("Embedded objects are not supported by RealmLists of DynamicRealmObjects yet."); + } + long objKey = osList.createAndSetEmbeddedObject(index); + updateEmbeddedObject(realmObject, objKey); + } else { + RealmObjectProxy proxy = (RealmObjectProxy) ((copyObject) ? copyToRealm((RealmModel) value) : realmObject); + osList.setRow(index, proxy.realmGet$proxyState().getRow$realm().getObjectKey()); + } + } + + private boolean checkCanObjectBeCopied(BaseRealm realm, RealmModel object) { + if (object instanceof RealmObjectProxy) { + RealmObjectProxy proxy = (RealmObjectProxy) object; + + if (proxy instanceof DynamicRealmObject) { + //noinspection ConstantConditions + @Nonnull + String listClassName = className; + if (proxy.realmGet$proxyState().getRealm$realm() == realm) { + String objectClassName = ((DynamicRealmObject) object).getType(); + if (listClassName.equals(objectClassName)) { + // Same Realm instance and same target table + return false; + } else { + // Different target table + throw new IllegalArgumentException(String.format(Locale.US, + "The object has a different type from list's." + + " Type of the list is '%s', type of object is '%s'.", listClassName, objectClassName)); + } + } else if (realm.threadId == proxy.realmGet$proxyState().getRealm$realm().threadId) { + // We don't support moving DynamicRealmObjects across Realms automatically. The overhead is too big as + // you have to run a full schema validation for each object. + // And copying from another Realm instance pointed to the same Realm file is not supported as well. + throw new IllegalArgumentException("Cannot copy DynamicRealmObject between Realm instances."); + } else { + throw new IllegalStateException("Cannot copy an object to a Realm instance created in another thread."); + } + } else { + // Object is already in this realm + if (proxy.realmGet$proxyState().getRow$realm() != null && proxy.realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { + if (realm != proxy.realmGet$proxyState().getRealm$realm()) { + throw new IllegalArgumentException("Cannot copy an object from another Realm instance."); + } + return false; + } + } + } + return true; + } + + // Transparently copies an unmanaged object or managed object from another Realm to the Realm backing this RealmList. + private E copyToRealm(E object) { + // At this point the object can only be a typed object, so the backing Realm cannot be a DynamicRealm. + Realm realm = (Realm) this.realm; + if (OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), + realm.getConfiguration().getSchemaMediator().getSimpleClassName(object.getClass())) != null) { + return realm.copyToRealmOrUpdate(object); + } else { + return realm.copyToRealm(object); + } + } + + private void updateEmbeddedObject(RealmModel unmanagedObject, long objKey) { + RealmProxyMediator schemaMediator = realm.getConfiguration().getSchemaMediator(); + Class modelClass = Util.getOriginalModelClass(unmanagedObject.getClass()); + Table table = ((Realm) realm).getTable(modelClass); + RealmModel managedObject = schemaMediator.newInstance(modelClass, realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(modelClass), true, Collections.EMPTY_LIST); + schemaMediator.updateEmbeddedObject((Realm) realm, unmanagedObject, managedObject, new HashMap<>(), Collections.EMPTY_SET); + } + +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link String} list field. + */ +final class StringListOperator extends ManagedListOperator { + + StringListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public String get(int index) { + return (String) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof String)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.String", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addString((String) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertString(index, (String) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setString(index, (String) value); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code long} list field. + */ +final class LongListOperator extends ManagedListOperator { + + LongListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public T get(int index) { + final Long value = (Long) osList.getValue(index); + if (value == null) { + return null; + } + if (clazz == Long.class) { + //noinspection unchecked + return (T) value; + } + if (clazz == Integer.class) { + //noinspection unchecked,UnnecessaryBoxing,ConstantConditions + return clazz.cast(Integer.valueOf(value.intValue())); + } + if (clazz == Short.class) { + //noinspection unchecked,UnnecessaryBoxing,ConstantConditions + return clazz.cast(Short.valueOf(value.shortValue())); + } + if (clazz == Byte.class) { + //noinspection unchecked,UnnecessaryBoxing,ConstantConditions + return clazz.cast(Byte.valueOf(value.byteValue())); + } + //noinspection ConstantConditions + throw new IllegalStateException("Unexpected element type: " + clazz.getName()); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Number)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.Long, java.lang.Integer, java.lang.Short, java.lang.Byte", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addLong(((Number) value).longValue()); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertLong(index, ((Number) value).longValue()); + } + + @Override + protected void setValue(int index, Object value) { + osList.setLong(index, ((Number) value).longValue()); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code boolean} list field. + */ +final class BooleanListOperator extends ManagedListOperator { + + BooleanListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Boolean get(int index) { + return (Boolean) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Boolean)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.Boolean", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addBoolean((Boolean) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertBoolean(index, (Boolean) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setBoolean(index, (Boolean) value); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code byte[]} list field. + */ +final class BinaryListOperator extends ManagedListOperator { + + BinaryListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public byte[] get(int index) { + return (byte[]) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof byte[])) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "byte[]", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addBinary((byte[]) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertBinary(index, (byte[]) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setBinary(index, (byte[]) value); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code double} list field. + */ +final class DoubleListOperator extends ManagedListOperator { + + DoubleListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Double get(int index) { + return (Double) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Number)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.Number", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addDouble(((Number) value).doubleValue()); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertDouble(index, ((Number) value).doubleValue()); + } + + @Override + protected void setValue(int index, Object value) { + osList.setDouble(index, ((Number) value).doubleValue()); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@code float} list field. + */ +final class FloatListOperator extends ManagedListOperator { + + FloatListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Float get(int index) { + return (Float) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Number)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.lang.Number", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addFloat(((Number) value).floatValue()); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertFloat(index, ((Number) value).floatValue()); + } + + @Override + protected void setValue(int index, Object value) { + osList.setFloat(index, ((Number) value).floatValue()); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link Date} list field. + */ +final class DateListOperator extends ManagedListOperator { + + DateListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Date get(int index) { + return (Date) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Date)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.util.Date", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addDate((Date) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertDate(index, (Date) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setDate(index, (Date) value); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link Decimal128} list field. + */ +final class Decimal128ListOperator extends ManagedListOperator { + + Decimal128ListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public Decimal128 get(int index) { + return (Decimal128) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof Decimal128)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "org.bson.types.Decimal128", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addDecimal128((Decimal128) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertDecimal128(index, (Decimal128) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setDecimal128(index, (Decimal128) value); + } +} + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link ObjectId} list field. + */ +final class ObjectIdListOperator extends ManagedListOperator { + + ObjectIdListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public ObjectId get(int index) { + return (ObjectId) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof ObjectId)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "org.bson.types.ObjectId", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addObjectId((ObjectId) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertObjectId(index, (ObjectId) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setObjectId(index, (ObjectId) value); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java index 8cf1e5ba45..06192846ce 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedCollectionChangeSet.java @@ -34,7 +34,7 @@ public interface OrderedCollectionChangeSet { /** * State describing the nature of the changeset. */ - public enum State { + enum State { /** * This state is used first time the callback is invoked. The query will have completed and * data is ready for the UI. diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index 86653d1980..e26de17682 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -22,7 +22,7 @@ * General implementation for {@link OrderedRealmCollection} which is based on the {@code Collection}. */ abstract class OrderedRealmCollectionImpl extends AbstractList implements OrderedRealmCollection { - private final static String NOT_SUPPORTED_MESSAGE = "This method is not supported by 'RealmResults' or" + + private static final String NOT_SUPPORTED_MESSAGE = "This method is not supported by 'RealmResults' or" + " 'OrderedRealmCollectionSnapshot'."; final BaseRealm realm; @@ -311,7 +311,7 @@ public RealmResults sort(String fieldName, Sort sortOrder) { * {@inheritDoc} */ @Override - public RealmResults sort(String fieldNames[], Sort sortOrders[]) { + public RealmResults sort(String[] fieldNames, Sort[] sortOrders) { QueryDescriptor sortDescriptor = QueryDescriptor.getInstanceForSort(getSchemaConnector(), osResults.getTable(), fieldNames, sortOrders); diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 3122b8afd2..5acc74f051 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1970,7 +1970,7 @@ interface OnError { /** * {@inheritDoc} */ - public static abstract class Callback extends InstanceCallback { + public abstract static class Callback extends InstanceCallback { /** * {@inheritDoc} */ diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index f3c06c81bf..76d36baf8f 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -66,7 +66,7 @@ interface Callback0 { void onCall(); } - private static abstract class ReferenceCounter { + private abstract static class ReferenceCounter { // How many references to this Realm instance in this thread. protected final ThreadLocal localCount = new ThreadLocal<>(); diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index e0a5527380..328f094a25 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -25,11 +25,9 @@ import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Date; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; -import java.util.Locale; import java.util.NoSuchElementException; import javax.annotation.Nonnull; @@ -39,12 +37,8 @@ import io.reactivex.Observable; import io.realm.internal.InvalidRow; import io.realm.internal.OsList; -import io.realm.internal.OsObjectStore; import io.realm.internal.OsResults; import io.realm.internal.RealmObjectProxy; -import io.realm.internal.RealmProxyMediator; -import io.realm.internal.Table; -import io.realm.internal.Util; import io.realm.rx.CollectionChange; @@ -79,7 +73,7 @@ public class RealmList extends AbstractList implements OrderedRealmCollect // Always null if RealmList is unmanaged, always non-null if managed. private final ManagedListOperator osListOperator; - final protected BaseRealm realm; + protected final BaseRealm realm; private List unmanagedList; /** @@ -1350,777 +1344,3 @@ private ManagedListOperator getOperator(BaseRealm realm, OsList osList, @Null throw new IllegalArgumentException("Unexpected value class: " + clazz.getName()); } } - -/** - * This class provides facade for against {@link OsList}. {@link OsList} is used for both {@link RealmModel}s - * and values, but there are some subtle differences in actual operation. - *

              - * This class provides common interface for them. - *

              - * You need to use appropriate sub-class for underlying field type. - * - * @param class of element which is returned on read operation. - */ -abstract class ManagedListOperator { - static final String NULL_OBJECTS_NOT_ALLOWED_MESSAGE = "RealmList does not accept null values."; - static final String INVALID_OBJECT_TYPE_MESSAGE = "Unacceptable value type. Acceptable: %1$s, actual: %2$s ."; - - final BaseRealm realm; - final OsList osList; - @Nullable - final Class clazz; - - ManagedListOperator(BaseRealm realm, OsList osList, @Nullable Class clazz) { - this.realm = realm; - this.clazz = clazz; - this.osList = osList; - } - - public abstract boolean forRealmModel(); - - public final OsList getOsList() { - return osList; - } - - public final boolean isValid() { - return osList.isValid(); - } - - public final int size() { - final long actualSize = osList.size(); - return actualSize < Integer.MAX_VALUE ? (int) actualSize : Integer.MAX_VALUE; - } - - public final boolean isEmpty() { - return osList.isEmpty(); - } - - protected abstract void checkValidValue(@Nullable Object value); - - @Nullable - public abstract T get(int index); - - public final void append(@Nullable Object value) { - checkValidValue(value); - - if (value == null) { - appendNull(); - } else { - appendValue(value); - } - } - - private void appendNull() { - osList.addNull(); - } - - abstract protected void appendValue(Object value); - - public final void insert(int index, @Nullable Object value) { - checkValidValue(value); - - if (value == null) { - insertNull(index); - } else { - insertValue(index, value); - } - - } - - protected void insertNull(int index) { - osList.insertNull(index); - } - - protected abstract void insertValue(int index, Object value); - - @Nullable - public final T set(int index, @Nullable Object value) { - checkValidValue(value); - - //noinspection unchecked - final T oldObject = get(index); - if (value == null) { - setNull(index); - } else { - setValue(index, value); - } - return oldObject; - } - - protected void setNull(int index) { - osList.setNull(index); - } - - abstract protected void setValue(int index, Object value); - - final void move(int oldPos, int newPos) { - osList.move(oldPos, newPos); - } - - final void remove(int index) { - osList.remove(index); - } - - final void removeAll() { - osList.removeAll(); - } - - final void delete(int index) { - osList.delete(index); - } - - final void deleteLast() { - osList.delete(osList.size() - 1); - } - - final void deleteAll() { - osList.deleteAll(); - } - -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@link RealmModel} list field. - */ -final class RealmModelListOperator extends ManagedListOperator { - - @Nullable - private final String className; - - RealmModelListOperator(BaseRealm realm, OsList osList, @Nullable Class clazz, @Nullable String className) { - super(realm, osList, clazz); - this.className = className; - } - - @Override - public boolean forRealmModel() { - return true; - } - - @Override - public T get(int index) { - //noinspection unchecked - return (T) realm.get((Class) clazz, className, osList.getUncheckedRow(index)); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - throw new IllegalArgumentException(NULL_OBJECTS_NOT_ALLOWED_MESSAGE); - } - if (!(value instanceof RealmModel)) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "java.lang.String", - value.getClass().getName())); - } - } - - private void checkInsertIndex(int index) { - final int size = size(); - if (index < 0 || size < index) { - throw new IndexOutOfBoundsException("Invalid index " + index + ", size is " + osList.size()); - } - } - - @Override - public void appendValue(Object value) { - RealmModel realmObject = (RealmModel) value; - boolean copyObject = checkCanObjectBeCopied(realm, realmObject); - if (isEmbedded((RealmModel) value)) { - if (value instanceof DynamicRealmObject) { - throw new IllegalArgumentException("Embedded objects are not supported by RealmLists of DynamicRealmObjects yet."); - } - long objKey = osList.createAndAddEmbeddedObject(); - updateEmbeddedObject(realmObject, objKey); - } else { - RealmObjectProxy proxy = (RealmObjectProxy) ((copyObject) ? copyToRealm((RealmModel) value) : realmObject); - osList.addRow(proxy.realmGet$proxyState().getRow$realm().getObjectKey()); - } - } - - @Override - protected void insertNull(int index) { - throw new RuntimeException("Should not reach here."); - } - - @Override - public void insertValue(int index, Object value) { - // need to check in advance to avoid unnecessary copy of unmanaged object into Realm. - checkInsertIndex(index); - RealmModel realmObject = (RealmModel) value; - boolean copyObject = checkCanObjectBeCopied(realm, realmObject); - if (isEmbedded(realmObject)) { - if (value instanceof DynamicRealmObject) { - throw new IllegalArgumentException("Embedded objects are not supported by RealmLists of DynamicRealmObjects yet."); - } - long objKey = osList.createAndAddEmbeddedObject(index); - updateEmbeddedObject(realmObject, objKey); - } else { - RealmObjectProxy proxy = (RealmObjectProxy) ((copyObject) ? copyToRealm((RealmModel) value) : realmObject); - osList.insertRow(index, proxy.realmGet$proxyState().getRow$realm().getObjectKey()); - } - } - - private boolean isEmbedded(RealmModel value) { - if (realm instanceof Realm) { - return realm.getSchema().getSchemaForClass(value.getClass()).isEmbedded(); - } else { - String objectType = ((DynamicRealmObject) value).getType(); - return realm.getSchema().getSchemaForClass(objectType).isEmbedded(); - } - } - - @Override - protected void setNull(int index) { - throw new RuntimeException("Should not reach here."); - } - - @Override - protected void setValue(int index, Object value) { - RealmModel realmObject = (RealmModel) value; - boolean copyObject = checkCanObjectBeCopied(realm, realmObject); - if (isEmbedded(realmObject)) { - if (value instanceof DynamicRealmObject) { - throw new IllegalArgumentException("Embedded objects are not supported by RealmLists of DynamicRealmObjects yet."); - } - long objKey = osList.createAndSetEmbeddedObject(index); - updateEmbeddedObject(realmObject, objKey); - } else { - RealmObjectProxy proxy = (RealmObjectProxy) ((copyObject) ? copyToRealm((RealmModel) value) : realmObject); - osList.setRow(index, proxy.realmGet$proxyState().getRow$realm().getObjectKey()); - } - } - - private boolean checkCanObjectBeCopied(BaseRealm realm, RealmModel object) { - if (object instanceof RealmObjectProxy) { - RealmObjectProxy proxy = (RealmObjectProxy) object; - - if (proxy instanceof DynamicRealmObject) { - //noinspection ConstantConditions - @Nonnull - String listClassName = className; - if (proxy.realmGet$proxyState().getRealm$realm() == realm) { - String objectClassName = ((DynamicRealmObject) object).getType(); - if (listClassName.equals(objectClassName)) { - // Same Realm instance and same target table - return false; - } else { - // Different target table - throw new IllegalArgumentException(String.format(Locale.US, - "The object has a different type from list's." + - " Type of the list is '%s', type of object is '%s'.", listClassName, objectClassName)); - } - } else if (realm.threadId == proxy.realmGet$proxyState().getRealm$realm().threadId) { - // We don't support moving DynamicRealmObjects across Realms automatically. The overhead is too big as - // you have to run a full schema validation for each object. - // And copying from another Realm instance pointed to the same Realm file is not supported as well. - throw new IllegalArgumentException("Cannot copy DynamicRealmObject between Realm instances."); - } else { - throw new IllegalStateException("Cannot copy an object to a Realm instance created in another thread."); - } - } else { - // Object is already in this realm - if (proxy.realmGet$proxyState().getRow$realm() != null && proxy.realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { - if (realm != proxy.realmGet$proxyState().getRealm$realm()) { - throw new IllegalArgumentException("Cannot copy an object from another Realm instance."); - } - return false; - } - } - } - return true; - } - - // Transparently copies an unmanaged object or managed object from another Realm to the Realm backing this RealmList. - private E copyToRealm(E object) { - // At this point the object can only be a typed object, so the backing Realm cannot be a DynamicRealm. - Realm realm = (Realm) this.realm; - if (OsObjectStore.getPrimaryKeyForObject(realm.getSharedRealm(), - realm.getConfiguration().getSchemaMediator().getSimpleClassName(object.getClass())) != null) { - return realm.copyToRealmOrUpdate(object); - } else { - return realm.copyToRealm(object); - } - } - - private void updateEmbeddedObject(RealmModel unmanagedObject, long objKey) { - RealmProxyMediator schemaMediator = realm.getConfiguration().getSchemaMediator(); - Class modelClass = Util.getOriginalModelClass(unmanagedObject.getClass()); - Table table = ((Realm) realm).getTable(modelClass); - RealmModel managedObject = schemaMediator.newInstance(modelClass, realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(modelClass), true, Collections.EMPTY_LIST); - schemaMediator.updateEmbeddedObject((Realm) realm, unmanagedObject, managedObject, new HashMap<>(), Collections.EMPTY_SET); - } - -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@link String} list field. - */ -final class StringListOperator extends ManagedListOperator { - - StringListOperator(BaseRealm realm, OsList osList, Class clazz) { - super(realm, osList, clazz); - } - - @Override - public boolean forRealmModel() { - return false; - } - - @Nullable - @Override - public String get(int index) { - return (String) osList.getValue(index); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - // null is always valid (but schema may reject null on insertion). - return; - } - if (!(value instanceof String)) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "java.lang.String", - value.getClass().getName())); - } - } - - @Override - public void appendValue(Object value) { - osList.addString((String) value); - } - - @Override - public void insertValue(int index, Object value) { - osList.insertString(index, (String) value); - } - - @Override - protected void setValue(int index, Object value) { - osList.setString(index, (String) value); - } -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@code long} list field. - */ -final class LongListOperator extends ManagedListOperator { - - LongListOperator(BaseRealm realm, OsList osList, Class clazz) { - super(realm, osList, clazz); - } - - @Override - public boolean forRealmModel() { - return false; - } - - @Nullable - @Override - public T get(int index) { - final Long value = (Long) osList.getValue(index); - if (value == null) { - return null; - } - if (clazz == Long.class) { - //noinspection unchecked - return (T) value; - } - if (clazz == Integer.class) { - //noinspection unchecked,UnnecessaryBoxing,ConstantConditions - return clazz.cast(Integer.valueOf(value.intValue())); - } - if (clazz == Short.class) { - //noinspection unchecked,UnnecessaryBoxing,ConstantConditions - return clazz.cast(Short.valueOf(value.shortValue())); - } - if (clazz == Byte.class) { - //noinspection unchecked,UnnecessaryBoxing,ConstantConditions - return clazz.cast(Byte.valueOf(value.byteValue())); - } - //noinspection ConstantConditions - throw new IllegalStateException("Unexpected element type: " + clazz.getName()); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - // null is always valid (but schema may reject null on insertion). - return; - } - if (!(value instanceof Number)) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "java.lang.Long, java.lang.Integer, java.lang.Short, java.lang.Byte", - value.getClass().getName())); - } - } - - @Override - public void appendValue(Object value) { - osList.addLong(((Number) value).longValue()); - } - - @Override - public void insertValue(int index, Object value) { - osList.insertLong(index, ((Number) value).longValue()); - } - - @Override - protected void setValue(int index, Object value) { - osList.setLong(index, ((Number) value).longValue()); - } -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@code boolean} list field. - */ -final class BooleanListOperator extends ManagedListOperator { - - BooleanListOperator(BaseRealm realm, OsList osList, Class clazz) { - super(realm, osList, clazz); - } - - @Override - public boolean forRealmModel() { - return false; - } - - @Nullable - @Override - public Boolean get(int index) { - return (Boolean) osList.getValue(index); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - // null is always valid (but schema may reject null on insertion). - return; - } - if (!(value instanceof Boolean)) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "java.lang.Boolean", - value.getClass().getName())); - } - } - - @Override - public void appendValue(Object value) { - osList.addBoolean((Boolean) value); - } - - @Override - public void insertValue(int index, Object value) { - osList.insertBoolean(index, (Boolean) value); - } - - @Override - protected void setValue(int index, Object value) { - osList.setBoolean(index, (Boolean) value); - } -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@code byte[]} list field. - */ -final class BinaryListOperator extends ManagedListOperator { - - BinaryListOperator(BaseRealm realm, OsList osList, Class clazz) { - super(realm, osList, clazz); - } - - @Override - public boolean forRealmModel() { - return false; - } - - @Nullable - @Override - public byte[] get(int index) { - return (byte[]) osList.getValue(index); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - // null is always valid (but schema may reject null on insertion). - return; - } - if (!(value instanceof byte[])) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "byte[]", - value.getClass().getName())); - } - } - - @Override - public void appendValue(Object value) { - osList.addBinary((byte[]) value); - } - - @Override - public void insertValue(int index, Object value) { - osList.insertBinary(index, (byte[]) value); - } - - @Override - protected void setValue(int index, Object value) { - osList.setBinary(index, (byte[]) value); - } -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@code double} list field. - */ -final class DoubleListOperator extends ManagedListOperator { - - DoubleListOperator(BaseRealm realm, OsList osList, Class clazz) { - super(realm, osList, clazz); - } - - @Override - public boolean forRealmModel() { - return false; - } - - @Nullable - @Override - public Double get(int index) { - return (Double) osList.getValue(index); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - // null is always valid (but schema may reject null on insertion). - return; - } - if (!(value instanceof Number)) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "java.lang.Number", - value.getClass().getName())); - } - } - - @Override - public void appendValue(Object value) { - osList.addDouble(((Number) value).doubleValue()); - } - - @Override - public void insertValue(int index, Object value) { - osList.insertDouble(index, ((Number) value).doubleValue()); - } - - @Override - protected void setValue(int index, Object value) { - osList.setDouble(index, ((Number) value).doubleValue()); - } -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@code float} list field. - */ -final class FloatListOperator extends ManagedListOperator { - - FloatListOperator(BaseRealm realm, OsList osList, Class clazz) { - super(realm, osList, clazz); - } - - @Override - public boolean forRealmModel() { - return false; - } - - @Nullable - @Override - public Float get(int index) { - return (Float) osList.getValue(index); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - // null is always valid (but schema may reject null on insertion). - return; - } - if (!(value instanceof Number)) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "java.lang.Number", - value.getClass().getName())); - } - } - - @Override - public void appendValue(Object value) { - osList.addFloat(((Number) value).floatValue()); - } - - @Override - public void insertValue(int index, Object value) { - osList.insertFloat(index, ((Number) value).floatValue()); - } - - @Override - protected void setValue(int index, Object value) { - osList.setFloat(index, ((Number) value).floatValue()); - } -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@link Date} list field. - */ -final class DateListOperator extends ManagedListOperator { - - DateListOperator(BaseRealm realm, OsList osList, Class clazz) { - super(realm, osList, clazz); - } - - @Override - public boolean forRealmModel() { - return false; - } - - @Nullable - @Override - public Date get(int index) { - return (Date) osList.getValue(index); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - // null is always valid (but schema may reject null on insertion). - return; - } - if (!(value instanceof Date)) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "java.util.Date", - value.getClass().getName())); - } - } - - @Override - public void appendValue(Object value) { - osList.addDate((Date) value); - } - - @Override - public void insertValue(int index, Object value) { - osList.insertDate(index, (Date) value); - } - - @Override - protected void setValue(int index, Object value) { - osList.setDate(index, (Date) value); - } -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@link Decimal128} list field. - */ -final class Decimal128ListOperator extends ManagedListOperator { - - Decimal128ListOperator(BaseRealm realm, OsList osList, Class clazz) { - super(realm, osList, clazz); - } - - @Override - public boolean forRealmModel() { - return false; - } - - @Nullable - @Override - public Decimal128 get(int index) { - return (Decimal128) osList.getValue(index); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - // null is always valid (but schema may reject null on insertion). - return; - } - if (!(value instanceof Decimal128)) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "org.bson.types.Decimal128", - value.getClass().getName())); - } - } - - @Override - public void appendValue(Object value) { - osList.addDecimal128((Decimal128)value); - } - - @Override - public void insertValue(int index, Object value) { - osList.insertDecimal128(index, (Decimal128) value); - } - - @Override - protected void setValue(int index, Object value) { - osList.setDecimal128(index, (Decimal128) value); - } -} - -/** - * A subclass of {@link ManagedListOperator} that deal with {@link ObjectId} list field. - */ -final class ObjectIdListOperator extends ManagedListOperator { - - ObjectIdListOperator(BaseRealm realm, OsList osList, Class clazz) { - super(realm, osList, clazz); - } - - @Override - public boolean forRealmModel() { - return false; - } - - @Nullable - @Override - public ObjectId get(int index) { - return (ObjectId) osList.getValue(index); - } - - @Override - protected void checkValidValue(@Nullable Object value) { - if (value == null) { - // null is always valid (but schema may reject null on insertion). - return; - } - if (!(value instanceof ObjectId)) { - throw new IllegalArgumentException( - String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, - "org.bson.types.ObjectId", - value.getClass().getName())); - } - } - - @Override - public void appendValue(Object value) { - osList.addObjectId((ObjectId)value); - } - - @Override - public void insertValue(int index, Object value) { - osList.insertObjectId(index, (ObjectId) value); - } - - @Override - protected void setValue(int index, Object value) { - osList.setObjectId(index, (ObjectId) value); - } -} diff --git a/realm/realm-library/src/main/java/io/realm/SchemaConnector.java b/realm/realm-library/src/main/java/io/realm/SchemaConnector.java index 5a695e474c..263281f448 100644 --- a/realm/realm-library/src/main/java/io/realm/SchemaConnector.java +++ b/realm/realm-library/src/main/java/io/realm/SchemaConnector.java @@ -34,7 +34,7 @@ class SchemaConnector implements FieldDescriptor.SchemaProxy { private final RealmSchema schema; - public SchemaConnector(RealmSchema schema) { + SchemaConnector(RealmSchema schema) { this.schema = schema; } diff --git a/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java b/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java index 7d46a32524..1bbe564924 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java @@ -23,7 +23,7 @@ * without a huge overhead in space complexity. */ public class IdentitySet extends IdentityHashMap { - private final static Integer PLACE_HOLDER = 0; + private static final Integer PLACE_HOLDER = 0; public void add(K key) { put(key, PLACE_HOLDER); diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index d5b15f9126..d0796508b0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -30,9 +30,9 @@ */ public class ObjectServerFacade { - public final static int SYNC_CONFIG_OPTIONS = 13; + public static final int SYNC_CONFIG_OPTIONS = 13; - private final static ObjectServerFacade nonSyncFacade = new ObjectServerFacade(); + private static final ObjectServerFacade nonSyncFacade = new ObjectServerFacade(); private static ObjectServerFacade syncFacade = null; static { diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java b/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java index be7ba607dd..68d2c9a501 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObservableCollection.java @@ -10,7 +10,7 @@ @Keep interface ObservableCollection { class CollectionObserverPair extends ObserverPairList.ObserverPair { - public CollectionObserverPair(T observer, Object listener) { + CollectionObserverPair(T observer, Object listener) { super(observer, listener); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java index 67addc40de..ade0fe9814 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsCollectionChangeSet.java @@ -171,11 +171,11 @@ public long getNativeFinalizerPtr() { return finalizerPtr; } - private native static long nativeGetFinalizerPtr(); + private static native long nativeGetFinalizerPtr(); // Returns the ranges as a long array. eg.: [startIndex1, length1, startIndex2, length2, ...] - private native static int[] nativeGetRanges(long nativePtr, int type); + private static native int[] nativeGetRanges(long nativePtr, int type); // Returns the indices array. - private native static int[] nativeGetIndices(long nativePtr, int type); + private static native int[] nativeGetIndices(long nativePtr, int type); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java b/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java index 5623f8d086..d28953dc30 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObjectStore.java @@ -25,7 +25,7 @@ */ public class OsObjectStore { - public final static long SCHEMA_NOT_VERSIONED = -1; + public static final long SCHEMA_NOT_VERSIONED = -1; /** * Sets the primary key field for the given class. @@ -86,16 +86,16 @@ public static boolean callWithLock(RealmConfiguration configuration, Runnable ru return nativeCallWithLock(configuration.getPath(), runnable); } - private native static void nativeSetPrimaryKeyForObject(long sharedRealmPtr, String className, + private static native void nativeSetPrimaryKeyForObject(long sharedRealmPtr, String className, @Nullable String primaryKeyFieldName); - private native static @Nullable String nativeGetPrimaryKeyForObject(long sharedRealmPtr, String className); + private static native @Nullable String nativeGetPrimaryKeyForObject(long sharedRealmPtr, String className); - private native static void nativeSetSchemaVersion(long sharedRealmPtr, long schemaVersion); + private static native void nativeSetSchemaVersion(long sharedRealmPtr, long schemaVersion); - private native static long nativeGetSchemaVersion(long sharedRealmPtr); + private static native long nativeGetSchemaVersion(long sharedRealmPtr); - private native static boolean nativeDeleteTableForObject(long sharedRealmPtr, String className); + private static native boolean nativeDeleteTableForObject(long sharedRealmPtr, String className); - private native static boolean nativeCallWithLock(String realmPath, Runnable runnable); + private static native boolean nativeCallWithLock(String realmPath, Runnable runnable); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 98a2f39902..2d515bc309 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -174,7 +174,7 @@ public Builder fifoFallbackDir(File dir) { public static final byte CLIENT_RESYNC_MODE_DISCARD = 1; public static final byte CLIENT_RESYNC_MODE_MANUAL = 2; - private final static long nativeFinalizerPtr = nativeGetFinalizerPtr(); + private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final RealmConfiguration realmConfiguration; private final URI resolvedRealmURI; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 6ea7bd9a66..356697a422 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -45,7 +45,7 @@ public class OsResults implements NativeObject, ObservableCollection { "This Realm instance has already been closed, making it unusable."; // Custom OsResults iterator. It ensures that we only iterate on a Realm OsResults that hasn't changed. - public static abstract class Iterator implements java.util.Iterator { + public abstract static class Iterator implements java.util.Iterator { OsResults iteratorOsResults; protected int pos = -1; @@ -131,7 +131,7 @@ T get(int pos) { } // Custom Realm collection list iterator. - public static abstract class ListIterator extends Iterator implements java.util.ListIterator { + public abstract static class ListIterator extends Iterator implements java.util.ListIterator { public ListIterator(OsResults osResults, int start) { super(osResults); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 7b4c897f1e..4ee9bb2a3e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -154,7 +154,7 @@ public interface SchemaChangedCallback { // SharedRealm which means the SharedRealm won't be closed automatically if there is any exception throws during // construction. GC will clear them later, but that would be too late. So we are tracking the temp OsSharedRealm // during the construction stage and manually close them if exception throws. - private final static List sharedRealmsUnderConstruction = new CopyOnWriteArrayList(); + private static final List sharedRealmsUnderConstruction = new CopyOnWriteArrayList(); private final List tempSharedRealmsForCallback = new ArrayList(); private final List> pendingRows = new CopyOnWriteArrayList<>(); @@ -172,7 +172,7 @@ private OsSharedRealm(OsRealmConfig osRealmConfig, VersionID version) { this.nativePtr = nativeGetSharedRealm(osRealmConfig.getNativePtr(), version.version, version.index, realmNotifier); } catch (Throwable t) { // The SharedRealm instances have to be closed before throw. - for (OsSharedRealm sharedRealm: tempSharedRealmsForCallback) { + for (OsSharedRealm sharedRealm : tempSharedRealmsForCallback) { if (!sharedRealm.isClosed()) { sharedRealm.close(); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java index 96e4f78481..464e16fddf 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java +++ b/realm/realm-library/src/main/java/io/realm/internal/RealmNotifier.java @@ -50,7 +50,7 @@ public abstract class RealmNotifier implements Closeable { // |-------------------------------+--------------+-----------------------------------| private static class RealmObserverPair extends ObserverPairList.ObserverPair> { - public RealmObserverPair(T observer, RealmChangeListener listener) { + RealmObserverPair(T observer, RealmChangeListener listener) { super(observer, listener); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index ad1183feb2..4a9fa9b0a1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -659,8 +659,8 @@ public String toString() { stringBuilder.append(" columns: "); boolean isFirst = true; - for (String column: getColumnNames()) { - if(!isFirst) { + for (String column : getColumnNames()) { + if (!isFirst) { stringBuilder.append(", "); } isFirst = false; diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java index cbd549c253..b8aaeab4e8 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java @@ -45,19 +45,19 @@ @Keep public class QueryDescriptor { //@VisibleForTesting - public final static Set SORT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + public static final Set SORT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, RealmFieldType.STRING, RealmFieldType.DATE, RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID))); //@VisibleForTesting - public final static Set DISTINCT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + public static final Set DISTINCT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.DATE, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID, RealmFieldType.OBJECT, RealmFieldType.LINKING_OBJECTS ))); - public final static Set DISTINCT_VALID_LINK_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + public static final Set DISTINCT_VALID_LINK_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( RealmFieldType.OBJECT, RealmFieldType.LINKING_OBJECTS ))); diff --git a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java index b57b6c0ae3..d07789d529 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java +++ b/realm/realm-library/src/main/java/io/realm/internal/util/Pair.java @@ -90,4 +90,4 @@ public String toString() { public static Pair create(A a, B b) { return new Pair<>(a, b); } -} \ No newline at end of file +} diff --git a/realm/realm-library/src/main/java/io/realm/rx/CollectionChange.java b/realm/realm-library/src/main/java/io/realm/rx/CollectionChange.java index 13e25f59af..fbc091b6ec 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/CollectionChange.java +++ b/realm/realm-library/src/main/java/io/realm/rx/CollectionChange.java @@ -97,4 +97,4 @@ public int hashCode() { result = 31 * result + (changeset != null ? changeset.hashCode() : 0); return result; } -} \ No newline at end of file +} diff --git a/realm/realm-library/src/main/java/io/realm/rx/ObjectChange.java b/realm/realm-library/src/main/java/io/realm/rx/ObjectChange.java index b65ad15599..408346a3f6 100644 --- a/realm/realm-library/src/main/java/io/realm/rx/ObjectChange.java +++ b/realm/realm-library/src/main/java/io/realm/rx/ObjectChange.java @@ -100,4 +100,4 @@ public String toString() { ", changeset=" + changeset + '}'; } -} \ No newline at end of file +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index 62149c0de1..370ea7db46 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -107,7 +107,7 @@ public OsJavaNetworkTransport.Response sendStreamingRequest(Request request) thr Call call = client.newCall(okRequest); okhttp3.Response response = call.execute(); - if((response.code() >= 300) || ((response.code() < 200) && (response.code() != 0))) { + if ((response.code() >= 300) || ((response.code() < 200) && (response.code() != 0))) { throw new AppException(ErrorCode.fromNativeError(ErrorCode.Type.HTTP, response.code()), response.message()); } @@ -189,7 +189,7 @@ public static OsJavaNetworkTransport.Response httpResponse(int httpResponseCode, @Override public String readBodyLine() throws IOException { - if(!closed){ + if (!closed){ return bufferedSource.readUtf8LineStrict(); } else{ bufferedSource.close(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java index 33fe3b895d..66aa9ad96f 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsJavaNetworkTransport.java @@ -94,7 +94,7 @@ public void resetHeaders() { customHeaders.clear(); } - public static abstract class Response { + public abstract static class Response { private final int httpResponseCode; private final int customResponseCode; private final Map headers; @@ -204,7 +204,7 @@ public String getBody() { */ // Abstract because these methods needs to be called from JNI and we cannot look up interface methods. @Keep - public static abstract class NetworkTransportJNIResultCallback { + public abstract static class NetworkTransportJNIResultCallback { public void onSuccess(Object result) {} public void onError(String nativeErrorCategory, int nativeErrorCode, String errorMessage) {} } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index 70d400a6a6..3eabeed502 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -156,7 +156,7 @@ protected SyncImpl(App app) { // Currently we only allow one instance of App (due to restrictions in ObjectStore that // only allows one underlying SyncClient). // FIXME: Lift this restriction so it is possible to create multiple app instances. - public volatile static boolean CREATED = false; + public static volatile boolean CREATED = false; /** * Thread pool used when doing network requests against MongoDB Realm. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index 760cd51199..735af55086 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -70,14 +70,14 @@ public class AppConfiguration { * * @see Builder#baseUrl(String) */ - public final static String DEFAULT_BASE_URL = "https://realm.mongodb.com"; + public static final String DEFAULT_BASE_URL = "https://realm.mongodb.com"; /** * The default request timeout for network requests towards MongoDB Realm in seconds. * * @see Builder#requestTimeout(long, TimeUnit) */ - public final static long DEFAULT_REQUEST_TIMEOUT = 60; + public static final long DEFAULT_REQUEST_TIMEOUT = 60; /** * The default header name used to carry authorization data when making network requests diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java index c5b8487afa..fcde3fad8c 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java @@ -328,6 +328,6 @@ public String toString() { '}'; } - abstract protected void call(int functionType, @Nullable String arg, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); + protected abstract void call(int functionType, @Nullable String arg, OsJavaNetworkTransport.NetworkTransportJNIResultCallback callback); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java index 9069dda4c4..f0b222d412 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java @@ -26,7 +26,7 @@ * The remote MongoClient used for working with data in MongoDB remotely via Realm. */ @Beta -abstract public class MongoClient { +public abstract class MongoClient { private final OsMongoClient osMongoClient; private final CodecRegistry codecRegistry; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java index 1d3a8c08e8..a9f601a5b9 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/iterable/MongoIterable.java @@ -61,7 +61,7 @@ public abstract class MongoIterable { this.resultClass = resultClass; } - abstract void callNative(final OsJNIResultCallback callback); + abstract void callNative(OsJNIResultCallback callback); /** * Returns a cursor of the operation represented by this iterable. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index c907d443f9..4e1e66c3ad 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -62,8 +62,8 @@ @Keep @Beta public class SyncSession { - private final static int DIRECTION_DOWNLOAD = 1; - private final static int DIRECTION_UPLOAD = 2; + private static final int DIRECTION_DOWNLOAD = 1; + private static final int DIRECTION_UPLOAD = 2; private final SyncConfiguration configuration; private final ErrorHandler errorHandler; From ecd1d386434c0afa91f91931ae4997cfb1473224 Mon Sep 17 00:00:00 2001 From: clementetb Date: Wed, 26 Aug 2020 15:47:09 +0200 Subject: [PATCH 1648/2110] Split assemble and ojoUpload to avoid gradle cyclic dependency issue (#7061) --- Jenkinsfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 60ac418429..984fb56280 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -175,11 +175,8 @@ try { def runBuild(abiFilter, instrumentationTestTarget) { stage('Build') { - sh "chmod +x gradlew && ./gradlew assemble ${abiFilter} --stacktrace" - } - - stage('JavaDoc') { - sh "chmod +x gradlew && ./gradlew javadoc ${abiFilter} --stacktrace" + sh "chmod +x gradlew" + sh "./gradlew assemble ${abiFilter} --stacktrace" } stage('Tests') { @@ -250,6 +247,9 @@ def runBuild(abiFilter, instrumentationTestTarget) { } finally { storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' } + }, + 'JavaDoc': { + sh "./gradlew javadoc ${abiFilter} --stacktrace" } } @@ -264,7 +264,7 @@ def runBuild(abiFilter, instrumentationTestTarget) { if (releaseBranches.contains(currentBranch)) { stage('Publish to OJO') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} ojoUpload --stacktrace" } } } From 4ac9ce56017b375ae1392712074275e54a305a5d Mon Sep 17 00:00:00 2001 From: nakawai Date: Thu, 27 Aug 2020 18:24:53 +0900 Subject: [PATCH 1649/2110] Update newsreaderExample's NYTimes api version to v2 (#7048) Co-authored-by: Naoki Kawai --- .../examples/newsreader/model/network/NYTimesDataLoader.java | 5 +++-- .../examples/newsreader/model/network/NYTimesService.java | 2 +- examples/newsreaderExample/src/main/res/values/strings.xml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesDataLoader.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesDataLoader.java index 7e3d6a4624..1d6bb063a0 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesDataLoader.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesDataLoader.java @@ -51,7 +51,7 @@ public NYTimesDataLoader() { Retrofit retrofit = new Retrofit.Builder() .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(JacksonConverterFactory.create()) - .baseUrl("http://api.nytimes.com/") + .baseUrl("https://api.nytimes.com/") .build(); nyTimesService = retrofit.create(NYTimesService.class); } @@ -71,7 +71,8 @@ private void loadNextSection(@NonNull final String sectionKey) { .observeOn(AndroidSchedulers.mainThread()) .subscribe(response -> { Timber.d("Success - Data received: %s", sectionKey); - processAndAddData(realm, response.section, response.results); + // response.section is different from sectionKey + processAndAddData(realm, sectionKey, response.results); networkInUse.onNext(false); }, throwable -> { networkInUse.onNext(false); diff --git a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesService.java b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesService.java index 41c799c578..4457573d25 100644 --- a/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesService.java +++ b/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesService.java @@ -29,7 +29,7 @@ * Retrofit interface for the New York Times WebService */ public interface NYTimesService { - @GET("svc/topstories/v1/{section}.json") + @GET("svc/topstories/v2/{section}.json") Observable>> topStories( @Path("section") String section, @Query(value = "api-key", encoded = true) String apiKey); diff --git a/examples/newsreaderExample/src/main/res/values/strings.xml b/examples/newsreaderExample/src/main/res/values/strings.xml index bd3c07f797..990aac5f13 100644 --- a/examples/newsreaderExample/src/main/res/values/strings.xml +++ b/examples/newsreaderExample/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ NewsReader - 6fd358f33363137b6b5e0b5758bd9052:18:73641711 + YUPmyj0Q09Fm2VlCHmD9FU7rpCcI5dUD READ No news From 46de5fc5917de918ff07445a087d2b7e374f60ac Mon Sep 17 00:00:00 2001 From: clementetb Date: Fri, 28 Aug 2020 12:31:21 +0200 Subject: [PATCH 1650/2110] Add rules for bson type references (#7064) --- examples/mongoDbRealmExample/build.gradle | 1 + .../proguard-rules-consumer-common.pro | 4 ++++ .../proguard-rules-consumer-objectServer.pro | 22 ++++++++++++++++++- .../internal/jni/OsJNIVoidResultCallback.java | 3 +++ .../java/io/realm/mongodb/AppException.java | 2 ++ .../java/io/realm/mongodb/ErrorCode.java | 2 ++ 6 files changed, 33 insertions(+), 1 deletion(-) diff --git a/examples/mongoDbRealmExample/build.gradle b/examples/mongoDbRealmExample/build.gradle index 7d0697752c..8bba9f531e 100644 --- a/examples/mongoDbRealmExample/build.gradle +++ b/examples/mongoDbRealmExample/build.gradle @@ -44,6 +44,7 @@ android { debug { buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" buildConfigField "String", "MONGODB_REALM_APP_ID", "\"${appId}\"" + minifyEnabled true } release { buildConfigField "String", "MONGODB_REALM_URL", "\"${mongodbRealmUrl}\"" diff --git a/realm/realm-library/proguard-rules-consumer-common.pro b/realm/realm-library/proguard-rules-consumer-common.pro index 6a99bdbc66..7b7b891dc2 100644 --- a/realm/realm-library/proguard-rules-consumer-common.pro +++ b/realm/realm-library/proguard-rules-consumer-common.pro @@ -20,3 +20,7 @@ } -dontnote rx.Observable + +# Referenced from JNI +-keep class org.bson.types.Decimal128 +-keep class org.bson.types.ObjectId \ No newline at end of file diff --git a/realm/realm-library/proguard-rules-consumer-objectServer.pro b/realm/realm-library/proguard-rules-consumer-objectServer.pro index 30502c0f9b..5fb3296d30 100644 --- a/realm/realm-library/proguard-rules-consumer-objectServer.pro +++ b/realm/realm-library/proguard-rules-consumer-objectServer.pro @@ -7,4 +7,24 @@ -dontnote sun.security.ssl.SSLContextImpl # See https://github.com/square/okhttp/issues/3922 --dontwarn okhttp3.internal.platform.* \ No newline at end of file +-dontwarn okhttp3.internal.platform.* + +# Referenced from JNI +-keep class io.realm.internal.objectstore.OsJavaNetworkTransport$Response { + int getHttpResponseCode(); + int getCustomResponseCode(); + java.lang.String[] getJNIFriendlyHeaders(); + java.lang.String getBody(); +} + +-keep class io.realm.internal.objectstore.OsJavaNetworkTransport$Request { + (...); +} + +-keep class io.realm.internal.OsSharedRealm$SchemaChangedCallback { + void onSchemaChanged(); +} + +-keep class io.realm.internal.objectstore.OsApp { + io.realm.internal.objectstore.OsJavaNetworkTransport getNetworkTransport(); +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIVoidResultCallback.java b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIVoidResultCallback.java index f7c11fb195..5b864c6dd7 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIVoidResultCallback.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/jni/OsJNIVoidResultCallback.java @@ -18,6 +18,9 @@ import java.util.concurrent.atomic.AtomicReference; +import io.realm.internal.Keep; + +@Keep public class OsJNIVoidResultCallback extends OsJNIResultCallback { public OsJNIVoidResultCallback(AtomicReference error) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppException.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppException.java index 4cae1cac48..643ab7526d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppException.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppException.java @@ -19,6 +19,7 @@ import javax.annotation.Nullable; import io.realm.annotations.Beta; +import io.realm.internal.Keep; import io.realm.internal.Util; import io.realm.mongodb.sync.SyncSession; @@ -33,6 +34,7 @@ * @see ErrorCode for a list of possible errors. */ @Beta +@Keep public class AppException extends RuntimeException { // The Java representation of the error. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java index f2fb7dd4ac..68dd3b6f51 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java @@ -20,6 +20,7 @@ import java.util.Locale; import io.realm.annotations.Beta; +import io.realm.internal.Keep; import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.log.RealmLog; import io.realm.mongodb.sync.SyncConfiguration; @@ -28,6 +29,7 @@ * This class enumerate all potential errors related to using the Object Server or synchronizing data. */ @Beta +@Keep public enum ErrorCode { // See Client::Error in https://github.com/realm/realm-sync/blob/master/src/realm/sync/client.hpp#L1230 From 33a2c9e94205e96d3c23807619a7364f8e52857f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 31 Aug 2020 08:37:52 +0200 Subject: [PATCH 1651/2110] Include ignored custom user data tests (#7063) --- dependencies.list | 2 +- .../kotlin/io/realm/mongodb/UserTests.kt | 10 ---------- .../kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt | 10 ++++------ .../app_config/auth_providers/anon-user.json | 2 +- .../app_config/auth_providers/api-key.json | 2 +- .../app_config/auth_providers/custom-function.json | 2 +- .../app_config/auth_providers/local-userpass.json | 2 +- .../app_config/functions/authFunc/config.json | 6 +++--- .../app_config/functions/authorizedOnly/config.json | 8 ++++---- .../app_config/functions/confirmFunc/config.json | 6 +++--- .../app_config/functions/error/config.json | 2 +- .../app_config/functions/firstArg/config.json | 2 +- .../app_config/functions/null/config.json | 2 +- .../app_config/functions/resetFunc/config.json | 6 +++--- .../app_config/functions/sum/config.json | 2 +- .../app_config/functions/testAuthFunc/config.json | 2 +- .../app_config/functions/void/config.json | 2 +- tools/sync_test_server/app_config/graphql/config.json | 3 +++ .../app_config/services/BackingDB/config.json | 3 +-- .../services/BackingDB/rules/test_data.SyncDog.json | 4 ++-- .../services/BackingDB/rules/test_data.SyncPerson.json | 6 +++--- .../BackingDB/rules/test_data.custom_user_data.json | 4 ++-- .../services/BackingDB/rules/test_data.mongo_data.json | 4 ++-- .../app_config/services/gcm/config.json | 2 +- tools/sync_test_server/app_config/stitch.json | 6 +++--- 25 files changed, 45 insertions(+), 55 deletions(-) create mode 100644 tools/sync_test_server/app_config/graphql/config.json diff --git a/dependencies.list b/dependencies.list index 11bd18e52b..d7b53164d4 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=824192a67e7ded59d33707265f78c8d5546b1fef473e9ee5ecff8a5bc9f850 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER=2020-08-24 +MONGODB_REALM_SERVER=2020-08-27 # Common Android settings across projects GRADLE_BUILD_TOOLS=4.0.0 diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt index f36b0a5998..fbba49d25f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt @@ -440,8 +440,6 @@ class UserTests { } @Test - @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " + - "https://github.com/realm/realm-java/issues/6942") fun customData_initiallyEmpty() { val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") // Newly registered users do not have any custom data with current test server setup @@ -449,8 +447,6 @@ class UserTests { } @Test - @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " + - "https://github.com/realm/realm-java/issues/6942") fun customData_refresh() { val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") // Newly registered users do not have any custom data with current test server setup @@ -464,8 +460,6 @@ class UserTests { } @Test - @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " + - "https://github.com/realm/realm-java/issues/6942") fun customData_refreshAsync() = looperThread.runBlocking { val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") // Newly registered users do not have any custom data with current test server setup @@ -482,8 +476,6 @@ class UserTests { } @Test - @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " + - "https://github.com/realm/realm-java/issues/6942") fun customData_refreshByLogout() { val password = "123456" val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), password) @@ -499,8 +491,6 @@ class UserTests { } @Test - @Ignore("Cannot automate custom user data cluster setup yet due to missing CLI support " + - "https://github.com/realm/realm-java/issues/6942") fun customData_refreshAsyncThrowsOnNonLooper() { val password = "123456" val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), password) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index 01b05af25d..4c787860fb 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -195,14 +195,12 @@ class SyncedRealmTests { @Test fun nullPartition() { - val config = configFactory.createSyncConfigurationBuilder(createNewUser(), BsonNull()).build() + val config = configFactory.createSyncConfigurationBuilder(createNewUser(), BsonNull()) + .modules(DefaultSyncSchema()) + .build() assertTrue(config.path.endsWith("null.realm")) Realm.getInstance(config).use { realm -> - // FIXME: This currently fails because the server does not yet support the Null partition - // Remove the catch once it is supported, after which uploading changes should work. - assertFailsWithErrorCode(ErrorCode.ILLEGAL_REALM_PATH) { - realm.syncSession.uploadAllLocalChanges() // Ensures that we can actually connect - } + realm.syncSession.uploadAllLocalChanges() // Ensures that we can actually connect } } diff --git a/tools/sync_test_server/app_config/auth_providers/anon-user.json b/tools/sync_test_server/app_config/auth_providers/anon-user.json index 44a270d6a6..59a7a97ea1 100644 --- a/tools/sync_test_server/app_config/auth_providers/anon-user.json +++ b/tools/sync_test_server/app_config/auth_providers/anon-user.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40ae9", + "id": "5f47673420f7388c73e8691d", "name": "anon-user", "type": "anon-user", "disabled": false diff --git a/tools/sync_test_server/app_config/auth_providers/api-key.json b/tools/sync_test_server/app_config/auth_providers/api-key.json index 526ab8a2a4..3c08c353c9 100644 --- a/tools/sync_test_server/app_config/auth_providers/api-key.json +++ b/tools/sync_test_server/app_config/auth_providers/api-key.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40aea", + "id": "5f47673420f7388c73e8691e", "name": "api-key", "type": "api-key", "disabled": false diff --git a/tools/sync_test_server/app_config/auth_providers/custom-function.json b/tools/sync_test_server/app_config/auth_providers/custom-function.json index 4efad48eb0..f743d3b68b 100644 --- a/tools/sync_test_server/app_config/auth_providers/custom-function.json +++ b/tools/sync_test_server/app_config/auth_providers/custom-function.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40aeb", + "id": "5f47673420f7388c73e8691f", "name": "custom-function", "type": "custom-function", "config": { diff --git a/tools/sync_test_server/app_config/auth_providers/local-userpass.json b/tools/sync_test_server/app_config/auth_providers/local-userpass.json index f0c1e1392d..a020250a87 100644 --- a/tools/sync_test_server/app_config/auth_providers/local-userpass.json +++ b/tools/sync_test_server/app_config/auth_providers/local-userpass.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40aec", + "id": "5f47673420f7388c73e86920", "name": "local-userpass", "type": "local-userpass", "config": { diff --git a/tools/sync_test_server/app_config/functions/authFunc/config.json b/tools/sync_test_server/app_config/functions/authFunc/config.json index bb90ef5953..5de1d4c1bb 100644 --- a/tools/sync_test_server/app_config/functions/authFunc/config.json +++ b/tools/sync_test_server/app_config/functions/authFunc/config.json @@ -1,6 +1,6 @@ { - "id": "5edea20f0f99fe616bf40ae0", + "can_evaluate": {}, + "id": "5f47673420f7388c73e86913", "name": "authFunc", - "private": false, - "can_evaluate": {} + "private": false } diff --git a/tools/sync_test_server/app_config/functions/authorizedOnly/config.json b/tools/sync_test_server/app_config/functions/authorizedOnly/config.json index 44b10f4502..f4c2297eb6 100644 --- a/tools/sync_test_server/app_config/functions/authorizedOnly/config.json +++ b/tools/sync_test_server/app_config/functions/authorizedOnly/config.json @@ -1,12 +1,12 @@ { - "id": "5edea20f0f99fe616bf40ae1", - "name": "authorizedOnly", - "private": false, "can_evaluate": { "%%user.data.email": { "%in": [ "authorizeduser@example.org" ] } - } + }, + "id": "5f47673420f7388c73e86914", + "name": "authorizedOnly", + "private": false } diff --git a/tools/sync_test_server/app_config/functions/confirmFunc/config.json b/tools/sync_test_server/app_config/functions/confirmFunc/config.json index 63d671ba8e..53737402fb 100644 --- a/tools/sync_test_server/app_config/functions/confirmFunc/config.json +++ b/tools/sync_test_server/app_config/functions/confirmFunc/config.json @@ -1,6 +1,6 @@ { - "id": "5edea20f0f99fe616bf40ae2", + "can_evaluate": {}, + "id": "5f47673420f7388c73e86915", "name": "confirmFunc", - "private": false, - "can_evaluate": {} + "private": false } diff --git a/tools/sync_test_server/app_config/functions/error/config.json b/tools/sync_test_server/app_config/functions/error/config.json index 8ce3d08c4d..712d3db403 100644 --- a/tools/sync_test_server/app_config/functions/error/config.json +++ b/tools/sync_test_server/app_config/functions/error/config.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40ae3", + "id": "5f47673420f7388c73e86916", "name": "error", "private": false } diff --git a/tools/sync_test_server/app_config/functions/firstArg/config.json b/tools/sync_test_server/app_config/functions/firstArg/config.json index 8afbba9482..b2474dd471 100644 --- a/tools/sync_test_server/app_config/functions/firstArg/config.json +++ b/tools/sync_test_server/app_config/functions/firstArg/config.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40ae4", + "id": "5f47673420f7388c73e86917", "name": "firstArg", "private": false } diff --git a/tools/sync_test_server/app_config/functions/null/config.json b/tools/sync_test_server/app_config/functions/null/config.json index 5402649ec4..27510ca766 100644 --- a/tools/sync_test_server/app_config/functions/null/config.json +++ b/tools/sync_test_server/app_config/functions/null/config.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40ae5", + "id": "5f47673420f7388c73e86918", "name": "null", "private": false } diff --git a/tools/sync_test_server/app_config/functions/resetFunc/config.json b/tools/sync_test_server/app_config/functions/resetFunc/config.json index 8d8da8eb2e..30b195e912 100644 --- a/tools/sync_test_server/app_config/functions/resetFunc/config.json +++ b/tools/sync_test_server/app_config/functions/resetFunc/config.json @@ -1,6 +1,6 @@ { - "id": "5edea20f0f99fe616bf40ae6", + "can_evaluate": {}, + "id": "5f47673420f7388c73e86919", "name": "resetFunc", - "private": false, - "can_evaluate": {} + "private": false } diff --git a/tools/sync_test_server/app_config/functions/sum/config.json b/tools/sync_test_server/app_config/functions/sum/config.json index ca48a1328c..705b359ca7 100644 --- a/tools/sync_test_server/app_config/functions/sum/config.json +++ b/tools/sync_test_server/app_config/functions/sum/config.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40ae7", + "id": "5f47673420f7388c73e8691a", "name": "sum", "private": false } diff --git a/tools/sync_test_server/app_config/functions/testAuthFunc/config.json b/tools/sync_test_server/app_config/functions/testAuthFunc/config.json index f28999d9ef..3be44599de 100644 --- a/tools/sync_test_server/app_config/functions/testAuthFunc/config.json +++ b/tools/sync_test_server/app_config/functions/testAuthFunc/config.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40ae8", + "id": "5f47673420f7388c73e8691b", "name": "testAuthFunc", "private": false } diff --git a/tools/sync_test_server/app_config/functions/void/config.json b/tools/sync_test_server/app_config/functions/void/config.json index 2aa645d14e..af2fc71676 100644 --- a/tools/sync_test_server/app_config/functions/void/config.json +++ b/tools/sync_test_server/app_config/functions/void/config.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40ae9", + "id": "5f47673420f7388c73e8691c", "name": "void", "private": false } diff --git a/tools/sync_test_server/app_config/graphql/config.json b/tools/sync_test_server/app_config/graphql/config.json new file mode 100644 index 0000000000..a9c2f4bb87 --- /dev/null +++ b/tools/sync_test_server/app_config/graphql/config.json @@ -0,0 +1,3 @@ +{ + "use_natural_pluralization": false +} diff --git a/tools/sync_test_server/app_config/services/BackingDB/config.json b/tools/sync_test_server/app_config/services/BackingDB/config.json index 2978971ea1..96f12b5947 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/config.json +++ b/tools/sync_test_server/app_config/services/BackingDB/config.json @@ -1,5 +1,5 @@ { - "id": "5edea20f0f99fe616bf40adc", + "id": "5f47673420f7388c73e8690d", "name": "BackingDB", "type": "mongodb", "config": { @@ -9,7 +9,6 @@ "partition": { "key": "realm_id", "type": "string", - "required": false, "permissions": { "read": true, "write": true diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json index 0910f22ec0..554099bb54 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json @@ -1,7 +1,7 @@ { - "id": "5edea20f0f99fe616bf40add", - "database": "test_data", "collection": "SyncDog", + "database": "test_data", + "id": "5f47673420f7388c73e8690e", "roles": [ { "name": "default", diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json index fd6a73bf1b..0d24d238d3 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json @@ -1,10 +1,10 @@ { - "id": "5edea20f0f99fe616bf40ade", - "database": "test_data", "collection": "SyncPerson", + "database": "test_data", + "id": "5f47673420f7388c73e8690f", "relationships": { "dogs": { - "ref": "#/stitch/BackingDB/test_data/SyncDog", + "ref": "#/relationship/BackingDB/test_data/SyncDog", "source_key": "dogs", "foreign_key": "_id", "is_list": true diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json index 334a2aa626..093809a7fd 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json @@ -1,7 +1,7 @@ { - "id": "5ede0729eab8deea4edf057c", - "database": "test_data", "collection": "custom_user_data", + "database": "test_data", + "id": "5f47673420f7388c73e86910", "roles": [ { "name": "default", diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json index 52d43c755d..88c1a3dfea 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json @@ -1,7 +1,7 @@ { - "id": "5edea20f0f99fe616bf40adf", - "database": "test_data", "collection": "mongo_data", + "database": "test_data", + "id": "5f47673420f7388c73e86911", "roles": [ { "name": "default", diff --git a/tools/sync_test_server/app_config/services/gcm/config.json b/tools/sync_test_server/app_config/services/gcm/config.json index 1560c44316..264021375c 100644 --- a/tools/sync_test_server/app_config/services/gcm/config.json +++ b/tools/sync_test_server/app_config/services/gcm/config.json @@ -1,5 +1,5 @@ { - "id": "5edea2480f99fe616bf40b6a", + "id": "5f47673420f7388c73e86912", "name": "gcm", "type": "gcm", "config": { diff --git a/tools/sync_test_server/app_config/stitch.json b/tools/sync_test_server/app_config/stitch.json index 7fe7df89db..12e88c983d 100644 --- a/tools/sync_test_server/app_config/stitch.json +++ b/tools/sync_test_server/app_config/stitch.json @@ -1,13 +1,13 @@ { - "app_id": "realm-sdk-integration-tests-nldpe", - "config_version": 20180301, + "app_id": "realm-sdk-integration-tests-werwa", + "config_version": 20200603, "name": "realm-sdk-integration-tests", "location": "US-VA", "deployment_model": "GLOBAL", "security": {}, "custom_user_data_config": { "enabled": true, - "mongo_service_id": "5ede0729eab8deea4edf0579", + "mongo_service_name": "BackingDB", "database_name": "test_data", "collection_name": "custom_user_data", "user_id_field": "userid" From fd1554f2c8885517f53551da72dbd0bbd7eef467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 31 Aug 2020 11:26:58 +0200 Subject: [PATCH 1652/2110] Improved metrics (#7041) --- .../io/realm/transformer/RealmAnalytics.java | 47 ++------------ .../transformer/UrlEncodedAnalytics.java | 64 +++++++++++++++++++ .../io/realm/transformer/RealmTransformer.kt | 48 +++++++++----- 3 files changed, 101 insertions(+), 58 deletions(-) create mode 100644 realm-transformer/src/main/java/io/realm/transformer/UrlEncodedAnalytics.java diff --git a/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java b/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java index 9e0a240aea..56a0c8df29 100644 --- a/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java +++ b/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java @@ -50,11 +50,6 @@ // - What OS you are running on // - An anonymized MAC address and bundle ID to aggregate the other information on. public class RealmAnalytics { - private static RealmAnalytics instance; - private static final int READ_TIMEOUT = 2000; - private static final int CONNECT_TIMEOUT = 4000; - private static final String ADDRESS_PREFIX = "https://api.mixpanel.com/track/?data="; - private static final String ADDRESS_SUFFIX = "&ip=1"; private static final String TOKEN = "ce0fac19508f6c8f20066d345d360fd0"; private static final String EVENT_NAME = "Run"; private static final String JSON_TEMPLATE @@ -66,6 +61,7 @@ public class RealmAnalytics { + " \"Anonymized MAC Address\": \"%USER_ID%\",\n" + " \"Anonymized Bundle ID\": \"%APP_ID%\",\n" + " \"Binding\": \"java\",\n" + + " \"Target\": \"%TARGET%\",\n" + " \"Language\": \"%LANGUAGE%\",\n" + " \"Sync Version\": %SYNC_VERSION%,\n" + " \"Realm Version\": \"%REALM_VERSION%\",\n" @@ -84,49 +80,15 @@ public class RealmAnalytics { private boolean usesSync; private String targetSdk; private String minSdk; + private String target;; - public RealmAnalytics(Set packages, boolean usesKotlin, boolean usesSync, String targetSdk, String minSdk) { + public RealmAnalytics(Set packages, boolean usesKotlin, boolean usesSync, String targetSdk, String minSdk, String target) { this.packages = packages; this.usesKotlin = usesKotlin; this.usesSync = usesSync; this.targetSdk = targetSdk; this.minSdk = minSdk; - } - - private void send() { - try { - URL url = getUrl(); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.connect(); - connection.getResponseCode(); - } catch (Exception ignored) { - } - } - - public void execute() { - Thread backgroundThread = new Thread(new Runnable() { - @Override - public void run() { - send(); - } - }); - backgroundThread.start(); - try { - backgroundThread.join(CONNECT_TIMEOUT + READ_TIMEOUT); - } catch (InterruptedException ignored) { - // We ignore this exception on purpose not to break the build system if this class fails - } catch (IllegalArgumentException ignored) { - // We ignore this exception on purpose not to break the build system if this class fails - } - } - - public URL getUrl() throws - MalformedURLException, - SocketException, - NoSuchAlgorithmException, - UnsupportedEncodingException { - return new URL(ADDRESS_PREFIX + Utils.base64Encode(generateJson()) + ADDRESS_SUFFIX); + this.target = target; } public String generateJson() throws SocketException, NoSuchAlgorithmException { @@ -135,6 +97,7 @@ public String generateJson() throws SocketException, NoSuchAlgorithmException { .replaceAll("%TOKEN%", TOKEN) .replaceAll("%USER_ID%", ComputerIdentifierGenerator.get()) .replaceAll("%APP_ID%", getAnonymousAppId()) + .replaceAll("%TARGET%", target) .replaceAll("%LANGUAGE%", usesKotlin ? "kotlin" : "java") .replaceAll("%SYNC_VERSION%", usesSync ? "\"" + Version.SYNC_VERSION + "\"": "null") .replaceAll("%REALM_VERSION%", Version.VERSION) diff --git a/realm-transformer/src/main/java/io/realm/transformer/UrlEncodedAnalytics.java b/realm-transformer/src/main/java/io/realm/transformer/UrlEncodedAnalytics.java new file mode 100644 index 0000000000..f5a87e162d --- /dev/null +++ b/realm-transformer/src/main/java/io/realm/transformer/UrlEncodedAnalytics.java @@ -0,0 +1,64 @@ +package io.realm.transformer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketException; +import java.net.URL; +import java.security.NoSuchAlgorithmException; + + + +public class UrlEncodedAnalytics { + + private String prefix; + private String suffix; + + public UrlEncodedAnalytics(String prefix, String suffix) { + this.prefix = prefix; + this.suffix = suffix; + } + + public void execute(RealmAnalytics analytics) { + try { + URL url = getUrl(analytics); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + connection.getResponseCode(); + } catch (Exception ignored) { + } + } + + private URL getUrl(RealmAnalytics analytics) throws + MalformedURLException, + SocketException, + NoSuchAlgorithmException, + UnsupportedEncodingException { + return new URL(prefix + Utils.base64Encode(analytics.generateJson()) + suffix); + } + + public static class MixPanel extends UrlEncodedAnalytics { + private static final String ADDRESS_PREFIX = "https://api.mixpanel.com/track/?data="; + private static final String ADDRESS_SUFFIX = "&ip=1"; + + public MixPanel() { + super(ADDRESS_PREFIX, ADDRESS_SUFFIX); + } + } + + public static class Segment extends UrlEncodedAnalytics { + private static final String ADDRESS_PREFIX = + "https://webhooks.mongodb-realm.com/api/client/v2.0/app/realmsdkmetrics-zmhtm/service/metric_webhook/incoming_webhook/metric?data="; + private static final String ADDRESS_SUFFIX = ""; + + public Segment() { + super(ADDRESS_PREFIX, ADDRESS_SUFFIX); + } + } + +} diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 8788019830..90b6f2d68d 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -17,20 +17,24 @@ package io.realm.transformer import com.android.build.api.transform.* +import io.realm.transformer.build.BuildTemplate import io.realm.transformer.build.FullBuild import io.realm.transformer.build.IncrementalBuild -import io.realm.transformer.build.BuildTemplate import io.realm.transformer.ext.getMinSdk import io.realm.transformer.ext.getTargetSdk import javassist.CtClass import org.gradle.api.Project import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.File +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit // Package level logger val logger: Logger = LoggerFactory.getLogger("realm-logger") +val CONNECT_TIMEOUT = 4000L; +val READ_TIMEOUT = 2000L; + /** * This class implements the Transform API provided by the Android Gradle plugin. */ @@ -133,19 +137,14 @@ class RealmTransformer(val project: Project) : Transform() { } var containsKotlin = false - + // Should be safe to iterate the configurations as we are way beyond the configuration + // phase outer@ - for(input: TransformInput in inputs) { - for (di: DirectoryInput in input.directoryInputs) { - val path: String = di.file.absolutePath - val index: Int = path.indexOf("build${File.separator}intermediates${File.separator}classes") - if (index != -1) { - val projectPath: String = path.substring(0, index) - val buildFile = File(projectPath + "build.gradle") - if (buildFile.exists() && buildFile.readText().contains("kotlin")) { - containsKotlin = true - break@outer - } + for (configuration in project.configurations) { + for (dependency in configuration.dependencies) { + if (dependency.name.startsWith("kotlin-stdlib")) { + containsKotlin = true + break@outer } } } @@ -154,8 +153,25 @@ class RealmTransformer(val project: Project) : Transform() { val targetSdk: String? = project.getTargetSdk() val minSdk: String? = project.getMinSdk() val sync: Boolean = Utils.isSyncEnabled(project) - val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk) - analytics.execute() + val target = + if (project.plugins.findPlugin("com.android.application") != null) { + "app" + } else if (project.plugins.findPlugin("com.android.library") != null) { + "library" + } else { + "unknown" + } + + val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk, target) + + val pool = Executors.newFixedThreadPool(2); + try { + pool.execute { UrlEncodedAnalytics.MixPanel().execute(analytics) } + pool.execute { UrlEncodedAnalytics.Segment().execute(analytics) } + pool.awaitTermination(CONNECT_TIMEOUT + READ_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (e: InterruptedException) { + pool.shutdownNow() + } } catch (e: Exception) { // Analytics failing for any reason should not crash the build logger.debug("Could not send analytics: $e") From bd9858950c7922d943cdf81221479a91b3c70de1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 31 Aug 2020 14:34:34 +0200 Subject: [PATCH 1653/2110] Improved metrics (#7068) --- .../io/realm/transformer/RealmAnalytics.java | 47 ++------------ .../transformer/UrlEncodedAnalytics.java | 64 +++++++++++++++++++ .../io/realm/transformer/RealmTransformer.kt | 48 +++++++++----- 3 files changed, 101 insertions(+), 58 deletions(-) create mode 100644 realm-transformer/src/main/java/io/realm/transformer/UrlEncodedAnalytics.java diff --git a/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java b/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java index 9e0a240aea..56a0c8df29 100644 --- a/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java +++ b/realm-transformer/src/main/java/io/realm/transformer/RealmAnalytics.java @@ -50,11 +50,6 @@ // - What OS you are running on // - An anonymized MAC address and bundle ID to aggregate the other information on. public class RealmAnalytics { - private static RealmAnalytics instance; - private static final int READ_TIMEOUT = 2000; - private static final int CONNECT_TIMEOUT = 4000; - private static final String ADDRESS_PREFIX = "https://api.mixpanel.com/track/?data="; - private static final String ADDRESS_SUFFIX = "&ip=1"; private static final String TOKEN = "ce0fac19508f6c8f20066d345d360fd0"; private static final String EVENT_NAME = "Run"; private static final String JSON_TEMPLATE @@ -66,6 +61,7 @@ public class RealmAnalytics { + " \"Anonymized MAC Address\": \"%USER_ID%\",\n" + " \"Anonymized Bundle ID\": \"%APP_ID%\",\n" + " \"Binding\": \"java\",\n" + + " \"Target\": \"%TARGET%\",\n" + " \"Language\": \"%LANGUAGE%\",\n" + " \"Sync Version\": %SYNC_VERSION%,\n" + " \"Realm Version\": \"%REALM_VERSION%\",\n" @@ -84,49 +80,15 @@ public class RealmAnalytics { private boolean usesSync; private String targetSdk; private String minSdk; + private String target;; - public RealmAnalytics(Set packages, boolean usesKotlin, boolean usesSync, String targetSdk, String minSdk) { + public RealmAnalytics(Set packages, boolean usesKotlin, boolean usesSync, String targetSdk, String minSdk, String target) { this.packages = packages; this.usesKotlin = usesKotlin; this.usesSync = usesSync; this.targetSdk = targetSdk; this.minSdk = minSdk; - } - - private void send() { - try { - URL url = getUrl(); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.connect(); - connection.getResponseCode(); - } catch (Exception ignored) { - } - } - - public void execute() { - Thread backgroundThread = new Thread(new Runnable() { - @Override - public void run() { - send(); - } - }); - backgroundThread.start(); - try { - backgroundThread.join(CONNECT_TIMEOUT + READ_TIMEOUT); - } catch (InterruptedException ignored) { - // We ignore this exception on purpose not to break the build system if this class fails - } catch (IllegalArgumentException ignored) { - // We ignore this exception on purpose not to break the build system if this class fails - } - } - - public URL getUrl() throws - MalformedURLException, - SocketException, - NoSuchAlgorithmException, - UnsupportedEncodingException { - return new URL(ADDRESS_PREFIX + Utils.base64Encode(generateJson()) + ADDRESS_SUFFIX); + this.target = target; } public String generateJson() throws SocketException, NoSuchAlgorithmException { @@ -135,6 +97,7 @@ public String generateJson() throws SocketException, NoSuchAlgorithmException { .replaceAll("%TOKEN%", TOKEN) .replaceAll("%USER_ID%", ComputerIdentifierGenerator.get()) .replaceAll("%APP_ID%", getAnonymousAppId()) + .replaceAll("%TARGET%", target) .replaceAll("%LANGUAGE%", usesKotlin ? "kotlin" : "java") .replaceAll("%SYNC_VERSION%", usesSync ? "\"" + Version.SYNC_VERSION + "\"": "null") .replaceAll("%REALM_VERSION%", Version.VERSION) diff --git a/realm-transformer/src/main/java/io/realm/transformer/UrlEncodedAnalytics.java b/realm-transformer/src/main/java/io/realm/transformer/UrlEncodedAnalytics.java new file mode 100644 index 0000000000..f5a87e162d --- /dev/null +++ b/realm-transformer/src/main/java/io/realm/transformer/UrlEncodedAnalytics.java @@ -0,0 +1,64 @@ +package io.realm.transformer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketException; +import java.net.URL; +import java.security.NoSuchAlgorithmException; + + + +public class UrlEncodedAnalytics { + + private String prefix; + private String suffix; + + public UrlEncodedAnalytics(String prefix, String suffix) { + this.prefix = prefix; + this.suffix = suffix; + } + + public void execute(RealmAnalytics analytics) { + try { + URL url = getUrl(analytics); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + connection.getResponseCode(); + } catch (Exception ignored) { + } + } + + private URL getUrl(RealmAnalytics analytics) throws + MalformedURLException, + SocketException, + NoSuchAlgorithmException, + UnsupportedEncodingException { + return new URL(prefix + Utils.base64Encode(analytics.generateJson()) + suffix); + } + + public static class MixPanel extends UrlEncodedAnalytics { + private static final String ADDRESS_PREFIX = "https://api.mixpanel.com/track/?data="; + private static final String ADDRESS_SUFFIX = "&ip=1"; + + public MixPanel() { + super(ADDRESS_PREFIX, ADDRESS_SUFFIX); + } + } + + public static class Segment extends UrlEncodedAnalytics { + private static final String ADDRESS_PREFIX = + "https://webhooks.mongodb-realm.com/api/client/v2.0/app/realmsdkmetrics-zmhtm/service/metric_webhook/incoming_webhook/metric?data="; + private static final String ADDRESS_SUFFIX = ""; + + public Segment() { + super(ADDRESS_PREFIX, ADDRESS_SUFFIX); + } + } + +} diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 8788019830..90b6f2d68d 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -17,20 +17,24 @@ package io.realm.transformer import com.android.build.api.transform.* +import io.realm.transformer.build.BuildTemplate import io.realm.transformer.build.FullBuild import io.realm.transformer.build.IncrementalBuild -import io.realm.transformer.build.BuildTemplate import io.realm.transformer.ext.getMinSdk import io.realm.transformer.ext.getTargetSdk import javassist.CtClass import org.gradle.api.Project import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.File +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit // Package level logger val logger: Logger = LoggerFactory.getLogger("realm-logger") +val CONNECT_TIMEOUT = 4000L; +val READ_TIMEOUT = 2000L; + /** * This class implements the Transform API provided by the Android Gradle plugin. */ @@ -133,19 +137,14 @@ class RealmTransformer(val project: Project) : Transform() { } var containsKotlin = false - + // Should be safe to iterate the configurations as we are way beyond the configuration + // phase outer@ - for(input: TransformInput in inputs) { - for (di: DirectoryInput in input.directoryInputs) { - val path: String = di.file.absolutePath - val index: Int = path.indexOf("build${File.separator}intermediates${File.separator}classes") - if (index != -1) { - val projectPath: String = path.substring(0, index) - val buildFile = File(projectPath + "build.gradle") - if (buildFile.exists() && buildFile.readText().contains("kotlin")) { - containsKotlin = true - break@outer - } + for (configuration in project.configurations) { + for (dependency in configuration.dependencies) { + if (dependency.name.startsWith("kotlin-stdlib")) { + containsKotlin = true + break@outer } } } @@ -154,8 +153,25 @@ class RealmTransformer(val project: Project) : Transform() { val targetSdk: String? = project.getTargetSdk() val minSdk: String? = project.getMinSdk() val sync: Boolean = Utils.isSyncEnabled(project) - val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk) - analytics.execute() + val target = + if (project.plugins.findPlugin("com.android.application") != null) { + "app" + } else if (project.plugins.findPlugin("com.android.library") != null) { + "library" + } else { + "unknown" + } + + val analytics = RealmAnalytics(packages, containsKotlin, sync, targetSdk, minSdk, target) + + val pool = Executors.newFixedThreadPool(2); + try { + pool.execute { UrlEncodedAnalytics.MixPanel().execute(analytics) } + pool.execute { UrlEncodedAnalytics.Segment().execute(analytics) } + pool.awaitTermination(CONNECT_TIMEOUT + READ_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (e: InterruptedException) { + pool.shutdownNow() + } } catch (e: Exception) { // Analytics failing for any reason should not crash the build logger.debug("Could not send analytics: $e") From a118124ed7223eaac09bfadc826bfbbc5007ae16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 1 Sep 2020 08:06:34 +0200 Subject: [PATCH 1654/2110] Fix version pinning when running queries in change listeners (#7055) --- CHANGELOG.md | 23 ++++- .../androidTest/java/io/realm/RealmTests.java | 91 +++++++++++++++++++ .../cpp/io_realm_internal_OsSharedRealm.cpp | 10 ++ realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/BaseRealm.java | 15 +++ .../java/io/realm/internal/OsSharedRealm.java | 6 ++ 6 files changed, 143 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e0037ca61..ee0b6564fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,21 @@ -## 7.0.2(2020-08-14) +## 7.0.3 (YYYY-MM-DD) + +### Enhancements +* Added `Realm.getNumberOfActiveVersions()`, which returns the current number of active versions maintained by the Realm file. + +### Fixes +* Creating a query inside a change listener could in some cases result in the version being pinned, which would either drastically increase filesize or cause `RealmConfiguration.maxNumberOfActiveVersions()` to trigger. (Issue [#6977](https://github.com/realm/realm-java/issues/6977), since 7.0.0) + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. + +### Internal +* Upgraded to Object Store commit: fec731c09fed54e20e18eead586ce23a3071bcd0. + + +## 7.0.2 (2020-08-14) ### Enhancements * None. @@ -18,7 +35,7 @@ * Upgraded to Realm Core 6.0.17. -## 7.0.1(2020-07-01) +## 7.0.1 (2020-07-01) ### Enhancements * None. @@ -39,7 +56,7 @@ * Upgraded to Realm Core 6.0.8. -## 7.0.0(2020-05-16) +## 7.0.0 (2020-05-16) NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 2028a668aa..fcaf0c3284 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -4560,6 +4560,64 @@ public void hittingMaxNumberOfVersionsThrows() { } } + // Test for https://github.com/realm/realm-java/issues/6977 + @Test + @RunTestInLooperThread + public void numberOfVersionsDecreasedOnClose() { + realm.close(); + int count = 50; + final CountDownLatch bgThreadDoneLatch = new CountDownLatch(count); + + RealmConfiguration config = configFactory.createConfigurationBuilder() + // The multiple embedded threads seems to cause trouble with factory's directory setting + .directory(context.getFilesDir()) + .name("versions-test.realm") + .maxNumberOfActiveVersions(5) + .build(); + Realm.deleteRealm(config); + + // Synchronizes between change listener and Background writes so they operate in lockstep. + AtomicReference guard = new AtomicReference<>(new CountDownLatch(1)); + + Realm realm = Realm.getInstance(config); + looperThread.closeAfterTest(realm); + realm.addChangeListener(callbackRealm -> { + // This test catches a bug that caused ObjectStore to pin Realm versions + // if a TableView was created inside a change notification and no elements + // in the TableView was accessed. + RealmResults query = realm.where(AllJavaTypes.class).findAll(); + guard.get().countDown(); + bgThreadDoneLatch.countDown(); + if (bgThreadDoneLatch.getCount() == 0) { + looperThread.testComplete(); + } + }); + + // Write a number of transactions in the background in a serial manner + // in order to create a number of different versions. Done in serial + // to allow the LooperThread to catch up. + new Thread(() -> { + for (int i = 0; i < count; i++) { + Thread t = new Thread() { + @Override + public void run() { + Realm realm = Realm.getInstance(config); + realm.executeTransaction(bgRealm -> { }); + realm.close(); + } + }; + t.start(); + try { + t.join(); + TestHelper.awaitOrFail(guard.get()); + guard.set(new CountDownLatch(1)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }).start(); + } + // Test for https://github.com/realm/realm-java/issues/6152 @Test @RunTestInLooperThread @@ -4615,4 +4673,37 @@ public void onChange(RealmResults results, OrderedCollectionChange } }); } + + @Test + public void getNumberOfActiveVersions() throws InterruptedException { + CountDownLatch bgWritesCompleted = new CountDownLatch(1); + CountDownLatch closeBgRealm = new CountDownLatch(1); + assertEquals(2, realm.getNumberOfActiveVersions()); + Thread t = new Thread(() -> { + Realm bgRealm = Realm.getInstance(realmConfig); + assertEquals(2, bgRealm.getNumberOfActiveVersions()); + for (int i = 0; i < 5; i++) { + bgRealm.executeTransaction(r -> { /* empty */ }); + } + assertEquals(6, bgRealm.getNumberOfActiveVersions()); + bgWritesCompleted.countDown(); + TestHelper.awaitOrFail(closeBgRealm); + bgRealm.close(); + }); + t.start(); + TestHelper.awaitOrFail(bgWritesCompleted); + assertEquals(6, realm.getNumberOfActiveVersions()); + closeBgRealm.countDown(); + t.join(); + realm.refresh(); // Release old versions for GC + realm.beginTransaction(); + realm.commitTransaction(); // Actually release the versions + assertEquals(2, realm.getNumberOfActiveVersions()); + realm.close(); + try { + realm.getNumberOfActiveVersions(); + fail(); + } catch (IllegalStateException ignore) { + } + } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 498fd40e0b..9d4b9c48de 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -542,3 +542,13 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeFreeze(JNIEnv CATCH_STD() return reinterpret_cast(nullptr); } + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeNumberOfVersions(JNIEnv* env, jclass, jlong shared_realm_ptr) +{ + try { + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + return to_jlong_or_not_found(shared_realm->get_number_of_versions()); + } + CATCH_STD() + return 0; +} diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 820b74e237..fec731c09f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 820b74e2378f111991877d43068a95d2b7a2e404 +Subproject commit fec731c09fed54e20e18eead586ce23a3071bcd0 diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index e91c245c1b..2ed948504d 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -484,6 +484,21 @@ public boolean isFrozen() { return frozen; } + /** + * Returns the current number of active versions currently being held by this Realm. + *

              + * Having a large number of active versions have a negative impact on the size of the + * Realm file. See the FAQ + * for more information. + * + * @return number of active versions currently being held by the Realm. + * @see RealmConfiguration.Builder#maxNumberOfActiveVersions(long) + */ + public long getNumberOfActiveVersions() { + checkIfValid(); + return getSharedRealm().getNumberOfVersions(); + } + /** * Checks if a Realm's underlying resources are still available or not getting accessed from the wrong thread. */ diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 314d7a5e64..df6f50018c 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -422,6 +422,10 @@ public RealmConfiguration getConfiguration() { return osRealmConfig.getRealmConfiguration(); } + public long getNumberOfVersions() { + return nativeNumberOfVersions(nativePtr); + } + @Override public void close() { if (realmNotifier != null) { @@ -648,4 +652,6 @@ private static native long nativeCreateTableWithPrimaryKeyField(long nativeShare private static native long nativeFreeze(long nativePtr); + private static native long nativeNumberOfVersions(long nativePtr); + } From be8eadf76fbe9cb2fe842c37f0064d97042464f7 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 1 Sep 2020 10:10:46 +0200 Subject: [PATCH 1655/2110] Upgrade to Sync 5.0.19 (#7069) --- CHANGELOG.md | 7 ++++++- dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee0b6564fb..3b2de45019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Fixes * Creating a query inside a change listener could in some cases result in the version being pinned, which would either drastically increase filesize or cause `RealmConfiguration.maxNumberOfActiveVersions()` to trigger. (Issue [#6977](https://github.com/realm/realm-java/issues/6977), since 7.0.0) +* If you upgrade a Realm file where you have "" elements in a list of non-nullable strings, the upgrade would crash. +* If an attempt to upgrade a Realm file has ended with a crash with "migrate_links" in the call stack, the Realm ended in a corrupt state where further upgrade was not possible. A remedy for this situation is now provided. ### Compatibility * Realm Object Server: 3.23.1 or later. @@ -12,7 +14,10 @@ * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal -* Upgraded to Object Store commit: fec731c09fed54e20e18eead586ce23a3071bcd0. +* Upgraded to Object Store commit: eef80f42e6ede2294eb60f048228012d9b7bc627. +* Upgraded to Realm Sync: 5.0.19. +* Upgraded to Realm Core: 6.0.22. +* The upgrade logic for upgrading fileformats has changed so that progress is now recorded explicitly in a table. This makes the logic simpler and reduces the chance of errors. It will also make it easier to detect if a file has only been partially upgraded. ## 7.0.2 (2020-08-14) diff --git a/dependencies.list b/dependencies.list index afcf91f325..8ed51e4f5e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.15 -REALM_SYNC_SHA256=f8d84ce3867e2d47bc6566bb9f9c35f725415f268f4136772beb4f5a041ac8d5 +REALM_SYNC_VERSION=5.0.19 +REALM_SYNC_SHA256=a98b66bbd4ff27798a138daa7195e68df6b762f1d91fc548d8e287e7c9d2e0ba # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index fec731c09f..eef80f42e6 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit fec731c09fed54e20e18eead586ce23a3071bcd0 +Subproject commit eef80f42e6ede2294eb60f048228012d9b7bc627 From cc6de27882d7044a66775a06095427c3c817556d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 1 Sep 2020 10:16:44 +0200 Subject: [PATCH 1656/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b2de45019..ded42e0a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 7.0.3 (YYYY-MM-DD) +## 7.0.3 (2020-09-01) ### Enhancements * Added `Realm.getNumberOfActiveVersions()`, which returns the current number of active versions maintained by the Realm file. From 1b52b78048b368c7eab2b0ba1433a7d71bf5de7a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 1 Sep 2020 10:17:25 +0200 Subject: [PATCH 1657/2110] Release v7.0.3 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index d4bcf7ada0..5febf2603f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.3-SNAPSHOT \ No newline at end of file +7.0.3 \ No newline at end of file From cb01a827db6e25fe559b32bb30fdd677ce9037c5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 1 Sep 2020 10:17:25 +0200 Subject: [PATCH 1658/2110] Prepare next release v7.0.4-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5febf2603f..f02aafd73f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.3 \ No newline at end of file +7.0.4-SNAPSHOT \ No newline at end of file From ea7d3799be8ea4610c6abc4c31c5ffe5eff6d9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 8 Sep 2020 10:28:13 +0200 Subject: [PATCH 1659/2110] Fix retrieval of null valued primitive property from dynamic realm (#7053) --- CHANGELOG.md | 1 + .../io/realm/DynamicRealmObjectTests.java | 93 +++++++++++++++++ .../entities/NullablePrimitiveFields.java | 99 +++++++++++++++++++ .../java/io/realm/DynamicRealmObject.java | 3 + 4 files changed, 196 insertions(+) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ded42e0a62..8a6317e006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ * [ObjectServer] Calling `SyncManager.refreshConnections()` did not correctly refresh connections in all cases, which could delay reconnects up to 5 minutes. (Issue [#7003](https://github.com/realm/realm-java/issues/7003)) * Upgrading the file format result did in some cases not work correctly. This could result in a number of crashes, e.g. `FORMAT_UPGRADE_REQUIRED`. (Issue [#6889](https://github.com/realm/realm-java/issues/6889), since 7.0.0) * Bug in memory mapping management. This bug could result in multiple different asserts as well as segfaults. In many cases stack backtraces would include members of the EncyptedFileMapping near the top - even if encryption was not used at all. In other cases asserts or crashes would be in methods reading an array header or array element. In all cases the application would terminate immediately. (Issue [#3838](https://github.com/realm/realm-core/pull/3838), since 7.0.0) +* Crash when retrieving `null` valued primitive fields from dynamic realm. (Issue [#7025](https://github.com/realm/realm-java/issues/7025)) ### Compatibility * Realm Object Server: 3.23.1 or later. diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 0acf690f3c..d4d584d6cf 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -31,6 +31,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; @@ -39,6 +40,7 @@ import io.realm.entities.CyclicType; import io.realm.entities.Dog; import io.realm.entities.NullTypes; +import io.realm.entities.NullablePrimitiveFields; import io.realm.entities.Owner; import io.realm.entities.PrimaryKeyAsBoxedByte; import io.realm.entities.PrimaryKeyAsBoxedInteger; @@ -1629,4 +1631,95 @@ public void run() { thread.start(); TestHelper.awaitOrFail(threadFinished); } + + @Test + public void getNullableFields() { + realm.executeTransaction(realm -> { + NullablePrimitiveFields primitiveNullables = realm.createObject(NullablePrimitiveFields.class); + primitiveNullables.setFieldBoolean(null); + + assertNull(primitiveNullables.getFieldBoolean()); + assertNull(primitiveNullables.getFieldInt()); + assertNull(primitiveNullables.getFieldFloat()); + assertNull(primitiveNullables.getFieldDouble()); + assertNull(primitiveNullables.getFieldString()); + assertNull(primitiveNullables.getFieldBinary()); + assertNull(primitiveNullables.getFieldDate()); + + realm.delete(AllJavaTypes.class); + AllJavaTypes allJavaTypes = realm.createObject(AllJavaTypes.class, UUID.randomUUID().getLeastSignificantBits()); + + assertNull(allJavaTypes.getFieldObject()); + allJavaTypes.getFieldBooleanList().add(null); + allJavaTypes.getFieldIntegerList().add(null); + allJavaTypes.getFieldFloatList().add(null); + allJavaTypes.getFieldDoubleList().add(null); + allJavaTypes.getFieldStringList().add(null); + allJavaTypes.getFieldBinaryList().add(null); + allJavaTypes.getFieldDateList().add(null); + }); + realm.close(); + dynamicRealm.refresh(); + + DynamicRealmObject primitiveNullables = dynamicRealm.where(NullablePrimitiveFields.CLASS_NAME).findFirst(); + DynamicRealmObject allJavaTypes = dynamicRealm.where(AllJavaTypes.CLASS_NAME).findFirst(); + + for (RealmFieldType value : RealmFieldType.values()) { + switch (value) { + case INTEGER: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_INT)); + break; + case BOOLEAN: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_BOOLEAN)); + break; + case STRING: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_STRING)); + break; + case BINARY: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_BINARY)); + break; + case DATE: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_DATE)); + break; + case FLOAT: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_FLOAT)); + break; + case DOUBLE: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_DOUBLE)); + break; + case OBJECT: + assertNull(allJavaTypes.get(AllJavaTypes.FIELD_OBJECT)); + break; + case INTEGER_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_INTEGER_LIST, Integer.class).get(0)); + break; + case BOOLEAN_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_BOOLEAN_LIST, Boolean.class).get(0)); + break; + case STRING_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_STRING_LIST, String.class).get(0)); + break; + case BINARY_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_BINARY_LIST, byte[].class).get(0)); + break; + case DATE_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_DATE_LIST, Date.class).get(0)); + break; + case FLOAT_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_FLOAT_LIST, Float.class).get(0)); + break; + case DOUBLE_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_DOUBLE_LIST, Double.class).get(0)); + break; + case LIST: + case LINKING_OBJECTS: + // Realm lists and back links cannot be null + break; + default: + fail("Not testing all types"); + } + } + dynamicRealm.close(); + } + } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java b/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java new file mode 100644 index 0000000000..197ec22d12 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java @@ -0,0 +1,99 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import java.util.Date; + +import io.realm.RealmObject; + +public class NullablePrimitiveFields extends RealmObject { + + public static final String CLASS_NAME = "NullablePrimitiveFields"; + + public static final String FIELD_STRING = "fieldString"; + public static final String FIELD_INT = "fieldInt"; + public static final String FIELD_FLOAT = "fieldFloat"; + public static final String FIELD_DOUBLE = "fieldDouble"; + public static final String FIELD_BOOLEAN = "fieldBoolean"; + public static final String FIELD_DATE = "fieldDate"; + public static final String FIELD_BINARY = "fieldBinary"; + + private Boolean fieldBoolean; + private Integer fieldInt; + private Float fieldFloat; + private Double fieldDouble; + private String fieldString; + private Byte fieldBinary; + private Date fieldDate; + + public Integer getFieldInt() { + return fieldInt; + } + + public void setFieldInt(Integer fieldInt) { + this.fieldInt = fieldInt; + } + + public Float getFieldFloat() { + return fieldFloat; + } + + public void setFieldFloat(Float fieldFloat) { + this.fieldFloat = fieldFloat; + } + + public Double getFieldDouble() { + return fieldDouble; + } + + public void setFieldDouble(Double fieldDouble) { + this.fieldDouble = fieldDouble; + } + + public Boolean getFieldBoolean() { + return fieldBoolean; + } + + public void setFieldBoolean(Boolean fieldBoolean) { + this.fieldBoolean = fieldBoolean; + } + + public String getFieldString() { + return fieldString; + } + + public void setFieldString(String fieldString) { + this.fieldString = fieldString; + } + + public Date getFieldDate() { + return fieldDate; + } + + public void setFieldDate(Date fieldDate) { + this.fieldDate = fieldDate; + } + + public Byte getFieldBinary() { + return fieldBinary; + } + + public void setFieldBinary(Byte fieldBinary) { + this.fieldBinary = fieldBinary; + } + +} diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index 24367b165d..bf2fc27f9f 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -94,6 +94,9 @@ public E get(String fieldName) { proxyState.getRealm$realm().checkIfValid(); long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + if (proxyState.getRow$realm().isNull(columnKey)) { + return null; + } RealmFieldType type = proxyState.getRow$realm().getColumnType(columnKey); switch (type) { case BOOLEAN: From 12e46ec8e9b114fd2d7bbf6cd8012ce015c0e444 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 8 Sep 2020 11:34:46 +0200 Subject: [PATCH 1660/2110] Upgrade to Sync 5.0.21 (#7085) --- CHANGELOG.md | 21 +++++++++++++++++++ dependencies.list | 4 ++-- .../java/io/realm/RealmMigrationTests.java | 8 ++++--- realm/realm-library/src/main/cpp/object-store | 2 +- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6317e006..76f8115435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 7.0.4 (YYYY-MM-DD) + +### Enhancements +* None. + +### Fixes +* In some cases a frozen Realm of the wrong version could be returned. +* Upgrading files with string primary keys would result in a file where it was not possible to find the objects by primary key. ([Core issue #3893](https://github.com/realm/realm-core/pull/3893), since 7.0.0) + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. + +### Internal +* Upgraded to Object Store commit: S286d7cb2f10c41f89a2efb43b22938610ccad4cf. +* Upgraded to Realm Sync: 5.0.21. +* Upgraded to Realm Core: 6.0.24. +* Fileformat has been bumped from 10 to 11. + + ## 7.0.3 (2020-09-01) ### Enhancements diff --git a/dependencies.list b/dependencies.list index 8ed51e4f5e..5d3fc0142e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.19 -REALM_SYNC_SHA256=a98b66bbd4ff27798a138daa7195e68df6b762f1d91fc548d8e287e7c9d2e0ba +REALM_SYNC_VERSION=5.0.21 +REALM_SYNC_SHA256=49a9f4c7a0ab5ffa43fc7c630d2a8c8f9cf88da5b50dec86ca4eed62772900f5 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 92e4d208ea..1fd16d36ba 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -1433,8 +1433,10 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { } } - // File format 9 (up to Core5) added an index automatically to the primary key, in Core6 (File format 10) string based PK are not - // indexed because the search index is derived from the ObjectKey. + // File format 9 (up to Core5) added an index automatically to the primary key, in Core6 + // (File format 10) string based PK are not indexed because the search index is derived from + // the ObjectKey. So the Index was stripped if found. However, this turned out to cause other bugs: https://github.com/realm/realm-core/pull/3893 + // In file format 11, the index is thus re-added for String primary keys. @Test public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException { configFactory.copyRealmFromAssets(context, @@ -1445,7 +1447,7 @@ public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException .build()); assertFalse(realm.isEmpty()); // Upgrading to Core 6 will strip all indexes on primary keys as they are no longer needed. - assertFalse(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); + assertTrue(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); MigrationCore6PKStringIndexedByDefault first = realm.where(MigrationCore6PKStringIndexedByDefault.class).findFirst(); assertNotNull(first); assertEquals("Foo", first.name); diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index eef80f42e6..286d7cb2f1 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit eef80f42e6ede2294eb60f048228012d9b7bc627 +Subproject commit 286d7cb2f10c41f89a2efb43b22938610ccad4cf From 3a0b2c8297c3eb62a0f19d6419a2f21859b3c7e5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 8 Sep 2020 12:41:48 +0200 Subject: [PATCH 1661/2110] Fix NPE when calling toString on RealmObject with a binary field containing null (#7087) --- CHANGELOG.md | 1 + .../io/realm/processor/RealmProxyClassGenerator.kt | 6 +++++- .../io/realm/some_test_NullTypesRealmProxy.java | 2 +- .../androidTest/java/io/realm/RealmObjectTests.java | 11 +++++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f8115435..3ef0bc8e9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes * In some cases a frozen Realm of the wrong version could be returned. * Upgrading files with string primary keys would result in a file where it was not possible to find the objects by primary key. ([Core issue #3893](https://github.com/realm/realm-core/pull/3893), since 7.0.0) +* NullPointerException when calling `toString` on RealmObjects with a binary field containing `null`. (Issue [#7084](https://github.com/realm/realm-java/issues/7084), since 7.0.0) ### Compatibility * Realm Object Server: 3.23.1 or later. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index 93070e0cab..1d56252456 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -1630,7 +1630,11 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("stringBuilder.append(%s().get())", metadata.getInternalGetter(fieldName)) } Utils.isByteArray(field) -> { - emitStatement("stringBuilder.append(\"binary(\" + %s().length + \")\")", metadata.getInternalGetter(fieldName)) + if (metadata.isNullable(field)) { + emitStatement("stringBuilder.append((%1\$s() == null) ? \"null\" : \"binary(\" + %1\$s().length + \")\")", metadata.getInternalGetter(fieldName)) + } else { + emitStatement("stringBuilder.append(\"binary(\" + %1\$s().length + \")\")", metadata.getInternalGetter(fieldName)) + } } else -> { if (metadata.isNullable(field)) { diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java index 082f02cf1d..77b2312e41 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_NullTypesRealmProxy.java @@ -3960,7 +3960,7 @@ public String toString() { stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{fieldBytesNull:"); - stringBuilder.append("binary(" + realmGet$fieldBytesNull().length + ")"); + stringBuilder.append((realmGet$fieldBytesNull() == null) ? "null" : "binary(" + realmGet$fieldBytesNull().length + ")"); stringBuilder.append("}"); stringBuilder.append(","); stringBuilder.append("{fieldByteNotNull:"); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index dee26d5ef6..38399fa78d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -478,6 +478,17 @@ public void toString_customMethod() { assertEquals(expected, cm.toString()); } + // Test for https://github.com/realm/realm-java/issues/7084 + @Test + public void toString_nullBinary() { + realm.beginTransaction(); + AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 1); + obj.setFieldBinary(null); + realm.commitTransaction(); + String desc = obj.toString(); + assertTrue(desc.contains("fieldBinary:null")); + } + @Test public void hashCode_cyclicObject() { realm.beginTransaction(); From 64d7ea1fec2f15b37f367f68607424703c4e3bb4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 8 Sep 2020 12:46:28 +0200 Subject: [PATCH 1662/2110] Add release date --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ef0bc8e9c..2f6b4720ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ -## 7.0.4 (YYYY-MM-DD) +## 7.0.4 (2020-09-08) ### Enhancements * None. ### Fixes -* In some cases a frozen Realm of the wrong version could be returned. +* In some cases a frozen Realm of the wrong version could be returned. ([ObjectStore issue #1078](https://github.com/realm/realm-object-store/pull/1078)) * Upgrading files with string primary keys would result in a file where it was not possible to find the objects by primary key. ([Core issue #3893](https://github.com/realm/realm-core/pull/3893), since 7.0.0) * NullPointerException when calling `toString` on RealmObjects with a binary field containing `null`. (Issue [#7084](https://github.com/realm/realm-java/issues/7084), since 7.0.0) From 2a9645f74be245278b8ae2468e2451f8b54652c4 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 8 Sep 2020 12:47:07 +0200 Subject: [PATCH 1663/2110] Release v7.0.4 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f02aafd73f..1b19111839 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.4-SNAPSHOT \ No newline at end of file +7.0.4 \ No newline at end of file From a03249dc2b71ee7a9df402207a07a2e867d695f6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 8 Sep 2020 12:47:07 +0200 Subject: [PATCH 1664/2110] Prepare next release v7.0.5-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 1b19111839..cd1c1513e8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.4 \ No newline at end of file +7.0.5-SNAPSHOT \ No newline at end of file From b14873fd81276eebc63887dae529d9b4a59fc50e Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 8 Sep 2020 14:57:48 +0200 Subject: [PATCH 1665/2110] Enable tests with resolved issues (#7078) --- .../src/androidTest/java/io/realm/RealmQueryTests.java | 7 ------- .../src/androidTest/java/io/realm/RealmTests.java | 1 - .../kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt | 7 ++++--- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index f5ea0ca3d1..593c5132f6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -1561,7 +1561,6 @@ public void isNull_notNullableFields() { } // Queries nullable PrimaryKey. - @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void equalTo_nullPrimaryKeys() { final long SECONDARY_FIELD_NUMBER = 49992417L; @@ -1585,7 +1584,6 @@ public void equalTo_nullPrimaryKeys() { assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedLong.class).equalTo(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, (Long) null).findAll().first().getName()); } - @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void isNull_nullPrimaryKeys() { final long SECONDARY_FIELD_NUMBER = 49992417L; @@ -1667,7 +1665,6 @@ public void like_nullStringPrimaryKey() { .findAll().first().getId()); } - @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void between_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. @@ -1686,7 +1683,6 @@ public void between_nullPrimaryKeysIsNotZero() { assertEquals(3, realm.where(PrimaryKeyAsBoxedLong.class).between(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, -1, 1).count()); } - @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void greaterThan_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. @@ -1705,7 +1701,6 @@ public void greaterThan_nullPrimaryKeysIsNotZero() { assertEquals(4, realm.where(PrimaryKeyAsBoxedLong.class).greaterThan(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, -1).count()); } - @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void greaterThanOrEqualTo_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. @@ -1724,7 +1719,6 @@ public void greaterThanOrEqualTo_nullPrimaryKeysIsNotZero() { assertEquals(5, realm.where(PrimaryKeyAsBoxedLong.class).greaterThanOrEqualTo(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, -1).count()); } - @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void lessThan_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. @@ -1743,7 +1737,6 @@ public void lessThan_nullPrimaryKeysIsNotZero() { assertEquals(6, realm.where(PrimaryKeyAsBoxedLong.class).lessThan(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, 1).count()); } - @Ignore("FIXME: https://github.com/realm/realm-object-store/pull/935") @Test public void lessThanOrEqualTo_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index af3e3508cf..565ff844ee 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -4609,7 +4609,6 @@ public void hittingMaxNumberOfVersionsThrows() { } // Test for https://github.com/realm/realm-java/issues/6152 - @Ignore("FIXME: https://github.com/realm/realm-java/issues/6792") @Test @RunTestInLooperThread public void encryption_stressTest() { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index 4c787860fb..d5364ce604 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -26,9 +26,11 @@ import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.LogLevel import io.realm.log.RealmLog -import io.realm.mongodb.* +import io.realm.mongodb.App +import io.realm.mongodb.Credentials import io.realm.mongodb.SyncTestUtils.Companion.createTestUser -import io.realm.util.assertFailsWithErrorCode +import io.realm.mongodb.User +import io.realm.mongodb.close import org.bson.BsonNull import org.junit.* import org.junit.runner.RunWith @@ -70,7 +72,6 @@ class SyncedRealmTests { // Smoke test for Sync. Waiting for working Sync support. @Test - @Ignore("FIXME: https://github.com/realm/realm-java/issues/6972") fun connectWithInitialSchema() { val user: User = createNewUser() val config = createDefaultConfig(user) From 66599ce46892e145d2949fb358f0be38986cb8dd Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 8 Sep 2020 15:04:51 +0200 Subject: [PATCH 1666/2110] Align MongoDB Realm API's with other SDK's (#7060) --- CHANGELOG.md | 22 ++- .../mongodb/realm/example/LoginActivity.kt | 2 +- .../kotlin/io/realm/ApiKeyAuthTests.kt | 182 +++++++++--------- .../kotlin/io/realm/CredentialsTests.kt | 61 ++---- .../kotlin/io/realm/EmailPasswordAuthTests.kt | 56 +++--- ...tadataTests.kt => UserProfileInfoTests.kt} | 75 +++++--- .../network/LoggingInterceptorTest.kt | 29 +-- .../kotlin/io/realm/mongodb/AppExt.kt | 2 +- .../io/realm/mongodb/MongoClientTest.kt | 20 ++ .../kotlin/io/realm/mongodb/UserTests.kt | 54 ++---- .../io/realm/mongodb/sync/SessionTests.kt | 11 ++ .../mongodb/sync/SyncConfigurationTests.kt | 8 +- .../io/realm/mongodb/sync/SyncedRealmTests.kt | 2 +- .../transport/OsJavaNetworkTransportTests.kt | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../internal/objectstore/OsMongoClient.java | 6 + .../java/io/realm/mongodb/App.java | 34 +++- .../io/realm/mongodb/AppConfiguration.java | 17 +- .../java/io/realm/mongodb/Credentials.java | 49 ++--- .../java/io/realm/mongodb/User.java | 135 ++----------- .../java/io/realm/mongodb/UserIdentity.java | 8 +- .../java/io/realm/mongodb/UserProfile.java | 162 ++++++++++++++++ .../auth/{UserApiKey.java => ApiKey.java} | 11 +- .../io/realm/mongodb/auth/ApiKeyAuth.java | 76 ++++---- .../realm/mongodb/auth/EmailPasswordAuth.java | 6 +- .../io/realm/mongodb/mongo/MongoClient.java | 9 + .../realm/mongodb/mongo/MongoCollection.java | 9 + .../java/io/realm/mongodb/sync/Sync.java | 12 +- .../realm/EncryptedSynchronizedRealmTests.kt | 2 +- .../kotlin/io/realm/SyncSessionTests.kt | 8 +- 30 files changed, 591 insertions(+), 481 deletions(-) rename realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/{UserMetadataTests.kt => UserProfileInfoTests.kt} (84%) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/UserProfile.java rename realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/{UserApiKey.java => ApiKey.java} (92%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a617b966..ac3a84add7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,30 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes -* None. +* [RealmApp] Moved `User.remove()` to `App.removeUser()`. +* [RealmApp] Renamed `ApiKeyAuth.createApiKey()` to `ApiKeyAuth.create()` and `ApiKeyAuth.createApiKeyAsync()` to `ApiKeyAuth.createAsync()`. +* [RealmApp] Renamed `ApiKeyAuth.fetchApiKey()` to `ApiKeyAuth.fetch()` and `ApiKeyAuth.fetchApiKeyAsync()` to `ApiKeyAuth.fetchAsync()`. +* [RealmApp] Renamed `ApiKeyAuth.fetchAllApiKeys()` to `ApiKeyAuth.fetchAll()` and `ApiKeyAuth.fetchAllApiKeysAsync()` to `ApiKeyAuth.fetchAllAsync()`. +* [RealmApp] Renamed `ApiKeyAuth.deleteApiKey()` to `ApiKeyAuth.delete()` and `ApiKeyAuth.deleteApiKeyAsync()` to `ApiKeyAuth.deleteAsync()`. +* [RealmApp] Renamed `ApiKeyAuth.enableApiKey()` to `ApiKeyAuth.enable()` and `ApiKeyAuth.enableApiKeyAsync()` to `ApiKeyAuth.enableAsync()`. +* [RealmApp] Renamed `ApiKeyAuth.disableApiKey()` to `ApiKeyAuth.disable()` and `ApiKeyAuth.disableApiKeyAsync()` to `ApiKeyAuth.disableAsync()`. +* [RealmApp] Renamed `User.getApiKeysAuth()` to `User.getApiKeys()`. +* [RealmApp] Renamed `UserApiKey` class to `ApiKey`. +* [RealmApp] Removed support for `Credentials.serverApiKey()`. +* [RealmApp] Renamed `App.getEmailPasswordAuth()` to `App.getEmailPassword()`. +* [RealmApp] User profile methods `getName()`, `getEmail()`, `getPictureUrl()`, `getFirstName()`, `getLastName()`, `getGender()`, `getBirthday()`, `getMinAge()` and `getMaxAge()` are now available under a new class `UserProfile`. It can be accessed using `User.getProfile()`. +* [RealmApp] Renamed `Sync.refreshConnections()` to `Sync.reconnect()`. +* [RealmApp] Renamed `Credentials.IdentityProvider` to `Credentials.Provider`. +* [RealmApp] Removed support for `User.getLocalId()`. ### Enhancements * [RealmApp] Support for using `null` as a partition value. * [RealmApp] Improve errors exception messages from `SyncSession.downloadAllServerChanges()` and `SyncSession.uploadAllLocalChanges()`. * Support for watching MongoCollection change streams (Issue [#6912](https://github.com/realm/realm-java/issues/6912)) +* [RealmApp] Support for getting all app sessions via `Sync.getAllSessions()`. +* [RealmApp] Support to retrieve the MongoClient service name using `MongoClient.getServiceName()` +* [RealmApp] Support to retrieve the MongoDatabase name using `MongoDatabase.getName()` +* [RealmApp] Support to retrieve the MongoCollection name using `MongoCollection.getName()` ### Fixed * None. @@ -21,7 +39,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal -* Updated to Object Store commit: 39e20006761e77014ceb19a2bd8f43018cc96f5a. +* Updated to Object Store commit: ffda21e28d7dd47793ad2d36394de0328676ca30. ## 10.0.0-BETA.6 (2020-08-17) diff --git a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt index bfa09d78bd..0513d2ba85 100644 --- a/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt +++ b/examples/mongoDbRealmExample/src/main/java/com/mongodb/realm/example/LoginActivity.kt @@ -68,7 +68,7 @@ class LoginActivity : AppCompatActivity() { if (createUser) { - APP.emailPasswordAuth.registerUserAsync(username, password) { + APP.emailPassword.registerUserAsync(username, password) { progressDialog.dismiss() binding.buttonCreate.isEnabled = true binding.buttonLogin.isEnabled = true diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt index b04edc1f80..81c46fddd2 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt @@ -21,7 +21,7 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.mongodb.* import io.realm.mongodb.auth.ApiKeyAuth -import io.realm.mongodb.auth.UserApiKey +import io.realm.mongodb.auth.ApiKey import io.realm.rule.BlockingLooperThread import org.bson.types.ObjectId import org.junit.After @@ -48,7 +48,7 @@ class ApiKeyAuthTests { } } - private val checkNullInApiKeyCallback = App.Callback { result -> + private val checkNullInApiKeyCallback = App.Callback { result -> if (result.isSuccess) { fail() } else { @@ -73,7 +73,7 @@ class ApiKeyAuthTests { app = TestApp() admin = ServerAdmin(app) user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - provider = user.apiKeyAuth + provider = user.apiKeys } @After @@ -94,7 +94,7 @@ class ApiKeyAuthTests { @Test fun createApiKey() { - val key: UserApiKey = provider.createApiKey("my-key") + val key: ApiKey = provider.create("my-key") assertEquals("my-key", key.name) assertNotNull("my-key", key.value) assertNotNull("my-key", key.id) @@ -104,7 +104,7 @@ class ApiKeyAuthTests { @Test fun createApiKey_invalidServerArgsThrows() { try { - provider.createApiKey("%s") + provider.create("%s") fail() } catch (e: AppException) { assertEquals(ErrorCode.INVALID_PARAMETER, e.errorCode) @@ -113,19 +113,19 @@ class ApiKeyAuthTests { @Test fun createApiKey_invalidArgumentThrows() { - testNullArg { provider.createApiKey(TestHelper.getNull()) } - testNullArg { provider.createApiKey("") } + testNullArg { provider.create(TestHelper.getNull()) } + testNullArg { provider.create("") } looperThread.runBlocking { - provider.createApiKeyAsync(TestHelper.getNull(), checkNullInApiKeyCallback) + provider.createAsync(TestHelper.getNull(), checkNullInApiKeyCallback) } looperThread.runBlocking { - provider.createApiKeyAsync("", checkNullInApiKeyCallback) + provider.createAsync("", checkNullInApiKeyCallback) } } @Test fun createApiKeyAsync() = looperThread.runBlocking { - provider.createApiKeyAsync("my-key") { result -> + provider.createAsync("my-key") { result -> val key = result.orThrow assertEquals("my-key", key.name) assertNotNull("my-key", key.value) @@ -137,7 +137,7 @@ class ApiKeyAuthTests { @Test fun createApiKeyAsync_invalidServerArgsThrows() = looperThread.runBlocking { - provider.createApiKeyAsync("%s") { result -> + provider.createAsync("%s") { result -> if (result.isSuccess) { fail() } else { @@ -149,8 +149,8 @@ class ApiKeyAuthTests { @Test fun fetchApiKey() { - val key1: UserApiKey = provider.createApiKey("my-key") - val key2: UserApiKey = provider.fetchApiKey(key1.id) + val key1: ApiKey = provider.create("my-key") + val key2: ApiKey = provider.fetch(key1.id) assertEquals(key1.id, key2.id) assertEquals(key1.name, key2.name) @@ -161,7 +161,7 @@ class ApiKeyAuthTests { @Test fun fetchApiKey_nonExistingKey() { try { - provider.fetchApiKey(ObjectId()) + provider.fetch(ObjectId()) fail() } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) @@ -170,17 +170,17 @@ class ApiKeyAuthTests { @Test fun fetchApiKey_invalidArgumentThrows() { - testNullArg { provider.fetchApiKey(TestHelper.getNull()) } + testNullArg { provider.fetch(TestHelper.getNull()) } looperThread.runBlocking { - provider.fetchApiKeyAsync(TestHelper.getNull(), checkNullInApiKeyCallback) + provider.fetchAsync(TestHelper.getNull(), checkNullInApiKeyCallback) } } @Test fun fetchApiKeyAsync() { - val key1: UserApiKey = provider.createApiKey("my-key") + val key1: ApiKey = provider.create("my-key") looperThread.runBlocking { - provider.fetchApiKeyAsync(key1.id) { result -> + provider.fetchAsync(key1.id) { result -> val key2 = result.orThrow assertEquals(key1.id, key2.id) assertEquals(key1.name, key2.name) @@ -193,9 +193,9 @@ class ApiKeyAuthTests { @Test fun fetchAllApiKeys() { - val key1: UserApiKey = provider.createApiKey("my-key") - val key2: UserApiKey = provider.createApiKey("other-key") - val allKeys: List = provider.fetchAllApiKeys() + val key1: ApiKey = provider.create("my-key") + val key2: ApiKey = provider.create("other-key") + val allKeys: List = provider.fetchAll() assertEquals(2, allKeys.size) assertTrue(allKeys.any { it.id == key1.id }) assertTrue(allKeys.any { it.id == key2.id }) @@ -203,11 +203,11 @@ class ApiKeyAuthTests { @Test fun fetchAllApiKeysAsync() { - val key1: UserApiKey = provider.createApiKey("my-key") - val key2: UserApiKey = provider.createApiKey("other-key") + val key1: ApiKey = provider.create("my-key") + val key2: ApiKey = provider.create("other-key") looperThread.runBlocking { - provider.fetchAllApiKeys() { result -> - val keys: List = result.orThrow + provider.fetchAll() { result -> + val keys: List = result.orThrow assertEquals(2, keys.size) assertTrue(keys.any { it.id == key1.id }) assertTrue(keys.any { it.id == key2.id }) @@ -218,11 +218,11 @@ class ApiKeyAuthTests { @Test fun deleteApiKey() { - val key1: UserApiKey = provider.createApiKey("my-key") - assertNotNull(provider.fetchApiKey(key1.id)) - provider.deleteApiKey(key1.id) + val key1: ApiKey = provider.create("my-key") + assertNotNull(provider.fetch(key1.id)) + provider.delete(key1.id) try { - provider.fetchApiKey(key1.id) + provider.fetch(key1.id) fail() } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) @@ -232,7 +232,7 @@ class ApiKeyAuthTests { @Test fun deleteApiKey_invalidServerArgsThrows() { try { - provider.deleteApiKey(ObjectId()) + provider.delete(ObjectId()) fail() } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) @@ -241,21 +241,21 @@ class ApiKeyAuthTests { @Test fun deleteApiKey_invalidArgumentThrows() { - testNullArg { provider.deleteApiKey(TestHelper.getNull()) } + testNullArg { provider.delete(TestHelper.getNull()) } looperThread.runBlocking { - provider.deleteApiKeyAsync(TestHelper.getNull(), checkNullInVoidCallback) + provider.deleteAsync(TestHelper.getNull(), checkNullInVoidCallback) } } @Test fun deleteApiKeyAsync() { - val key: UserApiKey = provider.createApiKey("my-key") - assertNotNull(provider.fetchApiKey(key.id)) + val key: ApiKey = provider.create("my-key") + assertNotNull(provider.fetch(key.id)) looperThread.runBlocking { - provider.deleteApiKeyAsync(key.id) { result -> + provider.deleteAsync(key.id) { result -> if (result.isSuccess) { try { - provider.fetchApiKey(key.id) + provider.fetch(key.id) fail() } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) @@ -270,7 +270,7 @@ class ApiKeyAuthTests { @Test fun deleteApiKeyAsync_invalidServerArgsThrows() = looperThread.runBlocking { - provider.deleteApiKeyAsync(ObjectId()) { result -> + provider.deleteAsync(ObjectId()) { result -> if (result.isSuccess) { fail() } else { @@ -282,28 +282,28 @@ class ApiKeyAuthTests { @Test fun enableApiKey() { - val key: UserApiKey = provider.createApiKey("my-key") - provider.disableApiKey(key.id) - assertFalse(provider.fetchApiKey(key.id).isEnabled) - provider.enableApiKey(key.id) - assertTrue(provider.fetchApiKey(key.id).isEnabled) + val key: ApiKey = provider.create("my-key") + provider.disable(key.id) + assertFalse(provider.fetch(key.id).isEnabled) + provider.enable(key.id) + assertTrue(provider.fetch(key.id).isEnabled) } @Test fun enableApiKey_alreadyEnabled() { - val key: UserApiKey = provider.createApiKey("my-key") - provider.disableApiKey(key.id) - assertFalse(provider.fetchApiKey(key.id).isEnabled) - provider.enableApiKey(key.id) - assertTrue(provider.fetchApiKey(key.id).isEnabled) - provider.enableApiKey(key.id) - assertTrue(provider.fetchApiKey(key.id).isEnabled) + val key: ApiKey = provider.create("my-key") + provider.disable(key.id) + assertFalse(provider.fetch(key.id).isEnabled) + provider.enable(key.id) + assertTrue(provider.fetch(key.id).isEnabled) + provider.enable(key.id) + assertTrue(provider.fetch(key.id).isEnabled) } @Test fun enableApiKey_invalidServerArgsThrows() { try { - provider.enableApiKey(ObjectId()) + provider.enable(ObjectId()) fail() } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) @@ -312,21 +312,21 @@ class ApiKeyAuthTests { @Test fun enableApiKey_invalidArgumentThrows() { - testNullArg { provider.enableApiKey(TestHelper.getNull()) } + testNullArg { provider.enable(TestHelper.getNull()) } looperThread.runBlocking { - provider.enableApiKeyAsync(TestHelper.getNull(), checkNullInVoidCallback) + provider.enableAsync(TestHelper.getNull(), checkNullInVoidCallback) } } @Test fun enableApiKeyAsync() { - val key: UserApiKey = provider.createApiKey("my-key") - provider.disableApiKey(key.id) - assertFalse(provider.fetchApiKey(key.id).isEnabled) + val key: ApiKey = provider.create("my-key") + provider.disable(key.id) + assertFalse(provider.fetch(key.id).isEnabled) looperThread.runBlocking { - provider.enableApiKeyAsync(key.id) { result -> + provider.enableAsync(key.id) { result -> if (result.isSuccess) { - assertTrue(provider.fetchApiKey(key.id).isEnabled) + assertTrue(provider.fetch(key.id).isEnabled) looperThread.testComplete() } else { fail(result.error.toString()) @@ -337,7 +337,7 @@ class ApiKeyAuthTests { @Test fun enableApiKeyAsync_invalidServerArgsThrows() = looperThread.runBlocking { - provider.disableApiKeyAsync(ObjectId()) { result -> + provider.disableAsync(ObjectId()) { result -> if (result.isSuccess) { fail() } else { @@ -349,24 +349,24 @@ class ApiKeyAuthTests { @Test fun disableApiKey() { - val key: UserApiKey = provider.createApiKey("my-key") - provider.disableApiKey(key.id) - assertFalse(provider.fetchApiKey(key.id).isEnabled) + val key: ApiKey = provider.create("my-key") + provider.disable(key.id) + assertFalse(provider.fetch(key.id).isEnabled) } @Test fun disableApiKey_alreadyDisabled() { - val key: UserApiKey = provider.createApiKey("my-key") - provider.disableApiKey(key.id) - assertFalse(provider.fetchApiKey(key.id).isEnabled) - provider.disableApiKey(key.id) - assertFalse(provider.fetchApiKey(key.id).isEnabled) + val key: ApiKey = provider.create("my-key") + provider.disable(key.id) + assertFalse(provider.fetch(key.id).isEnabled) + provider.disable(key.id) + assertFalse(provider.fetch(key.id).isEnabled) } @Test fun disableApiKey_invalidServerArgsThrows() { try { - provider.disableApiKey(ObjectId()) + provider.disable(ObjectId()) fail() } catch (e: AppException) { assertEquals(ErrorCode.API_KEY_NOT_FOUND, e.errorCode) @@ -375,20 +375,20 @@ class ApiKeyAuthTests { @Test fun disableApiKey_invalidArgumentThrows() { - testNullArg { provider.disableApiKey(TestHelper.getNull()) } + testNullArg { provider.disable(TestHelper.getNull()) } looperThread.runBlocking { - provider.disableApiKeyAsync(TestHelper.getNull(), checkNullInVoidCallback) + provider.disableAsync(TestHelper.getNull(), checkNullInVoidCallback) } } @Test fun disableApiKeyAsync() { - val key: UserApiKey = provider.createApiKey("my-key") + val key: ApiKey = provider.create("my-key") assertTrue(key.isEnabled) looperThread.runBlocking { - provider.disableApiKeyAsync(key.id) { result -> + provider.disableAsync(key.id) { result -> if (result.isSuccess) { - assertFalse(provider.fetchApiKey(key.id).isEnabled) + assertFalse(provider.fetch(key.id).isEnabled) looperThread.testComplete() } else { fail(result.error.toString()) @@ -399,7 +399,7 @@ class ApiKeyAuthTests { @Test fun disableApiKeyAsync_invalidServerArgsThrows() = looperThread.runBlocking { - provider.disableApiKeyAsync(ObjectId()) { result -> + provider.disableAsync(ObjectId()) { result -> if (result.isSuccess) { fail() } else { @@ -415,12 +415,12 @@ class ApiKeyAuthTests { for (method in Method.values()) { try { when(method) { - Method.CREATE -> provider.createApiKey("name") - Method.FETCH_SINGLE -> provider.fetchApiKey(ObjectId()) - Method.FETCH_ALL -> provider.fetchAllApiKeys() - Method.DELETE -> provider.deleteApiKey(ObjectId()) - Method.ENABLE -> provider.enableApiKey(ObjectId()) - Method.DISABLE -> provider.disableApiKey(ObjectId()) + Method.CREATE -> provider.create("name") + Method.FETCH_SINGLE -> provider.fetch(ObjectId()) + Method.FETCH_ALL -> provider.fetchAll() + Method.DELETE -> provider.delete(ObjectId()) + Method.ENABLE -> provider.enable(ObjectId()) + Method.DISABLE -> provider.disable(ObjectId()) } fail("$method should have thrown an exception") } catch (error: AppException) { @@ -434,12 +434,12 @@ class ApiKeyAuthTests { for (method in Method.values()) { try { when(method) { - Method.CREATE -> provider.createApiKeyAsync("key") { fail() } - Method.FETCH_SINGLE -> provider.fetchApiKeyAsync(ObjectId()) { fail() } - Method.FETCH_ALL -> provider.fetchAllApiKeys { fail() } - Method.DELETE -> provider.deleteApiKeyAsync(ObjectId()) { fail() } - Method.ENABLE -> provider.enableApiKeyAsync(ObjectId()) { fail() } - Method.DISABLE -> provider.disableApiKeyAsync(ObjectId()) { fail() } + Method.CREATE -> provider.createAsync("key") { fail() } + Method.FETCH_SINGLE -> provider.fetchAsync(ObjectId()) { fail() } + Method.FETCH_ALL -> provider.fetchAll { fail() } + Method.DELETE -> provider.deleteAsync(ObjectId()) { fail() } + Method.ENABLE -> provider.enableAsync(ObjectId()) { fail() } + Method.DISABLE -> provider.disableAsync(ObjectId()) { fail() } } fail("$method should have thrown an exception") } catch (ignore: IllegalStateException) { @@ -453,12 +453,12 @@ class ApiKeyAuthTests { for (method in Method.values()) { try { when(method) { - Method.CREATE -> provider.createApiKey("name") - Method.FETCH_SINGLE -> provider.fetchApiKey(ObjectId()) - Method.FETCH_ALL -> provider.fetchAllApiKeys() - Method.DELETE -> provider.deleteApiKey(ObjectId()) - Method.ENABLE -> provider.enableApiKey(ObjectId()) - Method.DISABLE -> provider.disableApiKey(ObjectId()) + Method.CREATE -> provider.create("name") + Method.FETCH_SINGLE -> provider.fetch(ObjectId()) + Method.FETCH_ALL -> provider.fetchAll() + Method.DELETE -> provider.delete(ObjectId()) + Method.ENABLE -> provider.enable(ObjectId()) + Method.DISABLE -> provider.disable(ObjectId()) } fail("$method should have thrown an exception") } catch (error: AppException) { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index 951ac5d750..3f9221af02 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -19,7 +19,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.mongodb.* -import io.realm.mongodb.auth.UserApiKey +import io.realm.mongodb.auth.ApiKey import org.bson.Document import org.junit.After import org.junit.Assert.* @@ -55,21 +55,14 @@ class CredentialsTests { @Test fun anonymous() { val creds = Credentials.anonymous() - assertEquals(Credentials.IdentityProvider.ANONYMOUS, creds.identityProvider) + assertEquals(Credentials.Provider.ANONYMOUS, creds.identityProvider) assertTrue(creds.asJson().contains("anon-user")) // Treat the JSON as an opaque value. } @Test fun apiKey() { val creds = Credentials.apiKey("token") - assertEquals(Credentials.IdentityProvider.API_KEY, creds.identityProvider) - assertTrue(creds.asJson().contains("token")) // Treat the JSON as an opaque value. - } - - @Test - fun serverApiKey() { - val creds = Credentials.serverApiKey("token") - assertEquals(Credentials.IdentityProvider.SERVER_API_KEY, creds.identityProvider) + assertEquals(Credentials.Provider.API_KEY, creds.identityProvider) assertTrue(creds.asJson().contains("token")) // Treat the JSON as an opaque value. } @@ -79,16 +72,10 @@ class CredentialsTests { assertFailsWith { Credentials.apiKey(TestHelper.getNull()) } } - @Test - fun serverApiKey_invalidInput() { - assertFailsWith { Credentials.serverApiKey("") } - assertFailsWith { Credentials.serverApiKey(TestHelper.getNull()) } - } - @Test fun apple() { val creds = Credentials.apple("apple-token") - assertEquals(Credentials.IdentityProvider.APPLE, creds.identityProvider) + assertEquals(Credentials.Provider.APPLE, creds.identityProvider) assertTrue(creds.asJson().contains("apple-token")) // Treat the JSON as a largely opaque value. } @@ -106,7 +93,7 @@ class CredentialsTests { "mail" to mail, "id" to id ).let { Credentials.customFunction(Document(it)) } - assertEquals(Credentials.IdentityProvider.CUSTOM_FUNCTION, creds.identityProvider) + assertEquals(Credentials.Provider.CUSTOM_FUNCTION, creds.identityProvider) assertTrue(creds.asJson().contains(mail)) assertTrue(creds.asJson().contains(id.toString())) } @@ -119,7 +106,7 @@ class CredentialsTests { @Test fun emailPassword() { val creds = Credentials.emailPassword("foo@bar.com", "secret") - assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, creds.identityProvider) + assertEquals(Credentials.Provider.EMAIL_PASSWORD, creds.identityProvider) // Treat the JSON as a largely opaque value. assertTrue(creds.asJson().contains("foo@bar.com")) assertTrue(creds.asJson().contains("secret")) @@ -136,7 +123,7 @@ class CredentialsTests { @Test fun facebook() { val creds = Credentials.facebook("fb-token") - assertEquals(Credentials.IdentityProvider.FACEBOOK, creds.identityProvider) + assertEquals(Credentials.Provider.FACEBOOK, creds.identityProvider) assertTrue(creds.asJson().contains("fb-token")) } @@ -149,7 +136,7 @@ class CredentialsTests { @Test fun google() { val creds = Credentials.google("google-token") - assertEquals(Credentials.IdentityProvider.GOOGLE, creds.identityProvider) + assertEquals(Credentials.Provider.GOOGLE, creds.identityProvider) assertTrue(creds.asJson().contains("google-token")) } @@ -163,7 +150,7 @@ class CredentialsTests { @Test fun jwt() { val creds = Credentials.jwt("jwt-token") - assertEquals(Credentials.IdentityProvider.JWT, creds.identityProvider) + assertEquals(Credentials.Provider.JWT, creds.identityProvider) assertTrue(creds.asJson().contains("jwt-token")) } @@ -177,27 +164,21 @@ class CredentialsTests { fun loginUsingCredentials() { app = TestApp() admin = ServerAdmin(app) - Credentials.IdentityProvider.values().forEach { provider -> + Credentials.Provider.values().forEach { provider -> when (provider) { - Credentials.IdentityProvider.ANONYMOUS -> { + Credentials.Provider.ANONYMOUS -> { val user = app.login(Credentials.anonymous()) assertNotNull(user) } - Credentials.IdentityProvider.API_KEY -> { + Credentials.Provider.API_KEY -> { // Log in, create an API key, log out, log in with the key, compare users val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - val key: UserApiKey = user.apiKeyAuth.createApiKey("my-key"); + val key: ApiKey = user.apiKeys.create("my-key"); user.logOut() val apiKeyUser = app.login(Credentials.apiKey(key.value!!)) assertEquals(user.id, apiKeyUser.id) } - Credentials.IdentityProvider.SERVER_API_KEY -> { - // Create key using the admin API and then log in - val serverKey = admin.createServerApiKey() - val serverKeyUser = app.login(Credentials.serverApiKey(serverKey)) - assertNotNull(serverKeyUser) - } - Credentials.IdentityProvider.CUSTOM_FUNCTION -> { + Credentials.Provider.CUSTOM_FUNCTION -> { val customFunction = mapOf( "mail" to TestHelper.getRandomEmail(), "id" to 666 + TestHelper.getRandomId() @@ -210,10 +191,10 @@ class CredentialsTests { val functionUser = app.login(customFunction) assertNotNull(functionUser) } - Credentials.IdentityProvider.EMAIL_PASSWORD -> { + Credentials.Provider.EMAIL_PASSWORD -> { val email = TestHelper.getRandomEmail() val password = "123456" - app.emailPasswordAuth.registerUser(email, password) + app.emailPassword.registerUser(email, password) val user = app.login(Credentials.emailPassword(email, password)) assertNotNull(user) } @@ -222,19 +203,19 @@ class CredentialsTests { // login service. Instead we attempt to login and verify that a proper exception // is thrown. At least that should verify that correctly formatted JSON is being // sent across the wire. - Credentials.IdentityProvider.FACEBOOK -> { + Credentials.Provider.FACEBOOK -> { expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.facebook("facebook-token")) } - Credentials.IdentityProvider.APPLE -> { + Credentials.Provider.APPLE -> { expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.apple("apple-token")) } - Credentials.IdentityProvider.GOOGLE -> { + Credentials.Provider.GOOGLE -> { expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.google("google-token")) } - Credentials.IdentityProvider.JWT -> { + Credentials.Provider.JWT -> { expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.jwt("jwt-token")) } - Credentials.IdentityProvider.UNKNOWN -> { + Credentials.Provider.UNKNOWN -> { // Ignore } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index 982ca91cd5..359cce4d80 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -82,7 +82,7 @@ class EmailPasswordAuthTests { fun registerUser() { val email = TestHelper.getRandomEmail() val password = "password1234" - app.emailPasswordAuth.registerUser(email, password) + app.emailPassword.registerUser(email, password) val user = app.login(Credentials.emailPassword(email, password)) assertEquals(User.State.LOGGED_IN, user.state) } @@ -92,7 +92,7 @@ class EmailPasswordAuthTests { val email = TestHelper.getRandomEmail() val password = "password1234" looperThread.runBlocking { - app.emailPasswordAuth.registerUserAsync(email, password) { result -> + app.emailPassword.registerUserAsync(email, password) { result -> if (result.isSuccess) { val user2 = app.login(Credentials.emailPassword(email, password)) assertEquals(User.State.LOGGED_IN, user2.state) @@ -106,7 +106,7 @@ class EmailPasswordAuthTests { @Test fun registerUser_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword try { provider.registerUser("invalid-email", "1234") fail() @@ -117,7 +117,7 @@ class EmailPasswordAuthTests { @Test fun registerUserAsync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword looperThread.runBlocking { provider.registerUserAsync("invalid-email", "1234") { result -> if (result.isSuccess) { @@ -132,7 +132,7 @@ class EmailPasswordAuthTests { @Test fun registerUser_invalidArgumentsThrows() { - val provider: EmailPasswordAuth = app.emailPasswordAuth + val provider: EmailPasswordAuth = app.emailPassword assertFailsWith { provider.registerUser(TestHelper.getNull(), "123456") } assertFailsWith { provider.registerUser("foo@bar.baz", TestHelper.getNull()) } looperThread.runBlocking { @@ -157,7 +157,7 @@ class EmailPasswordAuthTests { @Test fun confirmUser_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword try { provider.confirmUser("invalid-token", "invalid-token-id") fail() @@ -168,7 +168,7 @@ class EmailPasswordAuthTests { @Test fun confirmUserAsync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword looperThread.runBlocking { provider.confirmUserAsync("invalid-email", "1234") { result -> if (result.isSuccess) { @@ -183,7 +183,7 @@ class EmailPasswordAuthTests { @Test fun confirmUser_invalidArgumentsThrows() { - val provider: EmailPasswordAuth = app.emailPasswordAuth + val provider: EmailPasswordAuth = app.emailPassword assertFailsWith { provider.confirmUser(TestHelper.getNull(), "token-id") } assertFailsWith { provider.confirmUser("token", TestHelper.getNull()) } looperThread.runBlocking { @@ -202,7 +202,7 @@ class EmailPasswordAuthTests { val email = "test@10gen.com" admin.setAutomaticConfirmation(false) try { - val provider = app.emailPasswordAuth + val provider = app.emailPassword provider.registerUser(email, "123456") provider.resendConfirmationEmail(email) } finally { @@ -218,7 +218,7 @@ class EmailPasswordAuthTests { admin.setAutomaticConfirmation(false) try { looperThread.runBlocking { - val provider = app.emailPasswordAuth + val provider = app.emailPassword provider.registerUser(email, "123456") provider.resendConfirmationEmailAsync(email) { result -> when(result.isSuccess) { @@ -236,7 +236,7 @@ class EmailPasswordAuthTests { fun resendConfirmationEmail_invalidServerArgsThrows() { val email = "test@10gen.com" admin.setAutomaticConfirmation(false) - val provider = app.emailPasswordAuth + val provider = app.emailPassword provider.registerUser(email, "123456") try { provider.resendConfirmationEmail("foo") @@ -252,7 +252,7 @@ class EmailPasswordAuthTests { fun resendConfirmationEmailAsync_invalidServerArgsThrows() { val email = "test@10gen.com" admin.setAutomaticConfirmation(false) - val provider = app.emailPasswordAuth + val provider = app.emailPassword provider.registerUser(email, "123456") try { looperThread.runBlocking { @@ -272,7 +272,7 @@ class EmailPasswordAuthTests { @Test fun resendConfirmationEmail_invalidArgumentsThrows() { - val provider: EmailPasswordAuth = app.emailPasswordAuth + val provider: EmailPasswordAuth = app.emailPassword assertFailsWith { provider.resendConfirmationEmail(TestHelper.getNull()) } looperThread.runBlocking { provider.resendConfirmationEmailAsync(TestHelper.getNull(), checkNullArgCallback) @@ -281,7 +281,7 @@ class EmailPasswordAuthTests { @Test fun sendResetPasswordEmail() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword val email: String = "test@10gen.com" // Must be a valid email, otherwise the server will fail provider.registerUser(email, "123456") provider.sendResetPasswordEmail(email) @@ -289,7 +289,7 @@ class EmailPasswordAuthTests { @Test fun sendResetPasswordEmailAsync() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword val email: String = "test@10gen.com" // Must be a valid email, otherwise the server will fail provider.registerUser(email, "123456") looperThread.runBlocking { @@ -305,7 +305,7 @@ class EmailPasswordAuthTests { @Test fun sendResetPasswordEmail_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword try { provider.sendResetPasswordEmail("unknown@10gen.com") fail() @@ -316,7 +316,7 @@ class EmailPasswordAuthTests { @Test fun sendResetPasswordEmailAsync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword looperThread.runBlocking { provider.sendResetPasswordEmailAsync("unknown@10gen.com") { result -> if (result.isSuccess) { @@ -331,7 +331,7 @@ class EmailPasswordAuthTests { @Test fun sendResetPasswordEmail_invalidArgumentsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword assertFailsWith { provider.sendResetPasswordEmail(TestHelper.getNull()) } looperThread.runBlocking { provider.sendResetPasswordEmailAsync(TestHelper.getNull(), checkNullArgCallback) @@ -340,7 +340,7 @@ class EmailPasswordAuthTests { @Test fun callResetPasswordFunction() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword admin.setResetFunction(enabled = true) val email = TestHelper.getRandomEmail() provider.registerUser(email, "123456") @@ -355,7 +355,7 @@ class EmailPasswordAuthTests { @Test fun callResetPasswordFunctionAsync() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword admin.setResetFunction(enabled = true) val email = TestHelper.getRandomEmail() provider.registerUser(email, "123456") @@ -380,7 +380,7 @@ class EmailPasswordAuthTests { @Test fun callResetPasswordFunction_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword admin.setResetFunction(enabled = true) val email = TestHelper.getRandomEmail() provider.registerUser(email, "123456") @@ -395,7 +395,7 @@ class EmailPasswordAuthTests { @Test fun callResetPasswordFunctionAsync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword admin.setResetFunction(enabled = true) val email = TestHelper.getRandomEmail() provider.registerUser(email, "123456") @@ -420,7 +420,7 @@ class EmailPasswordAuthTests { @Test fun callResetPasswordFunction_invalidArgumentsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword assertFailsWith { provider.callResetPasswordFunction(TestHelper.getNull(), "password") } assertFailsWith { provider.callResetPasswordFunction("foo@bar.baz", TestHelper.getNull()) } looperThread.runBlocking { @@ -445,7 +445,7 @@ class EmailPasswordAuthTests { @Test fun resetPassword_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword try { provider.resetPassword("invalid-token", "invalid-token-id", "new-password") } catch (error: AppException) { @@ -455,7 +455,7 @@ class EmailPasswordAuthTests { @Test fun resetPasswordASync_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword looperThread.runBlocking { provider.resetPasswordAsync("invalid-token", "invalid-token-id", "new-password") { result -> if (result.isSuccess) { @@ -470,7 +470,7 @@ class EmailPasswordAuthTests { @Test fun resetPassword_invalidArgumentsThrows() { - val provider = app.emailPasswordAuth + val provider = app.emailPassword assertFailsWith { provider.resetPassword(TestHelper.getNull(), "token-id", "password") } assertFailsWith { provider.resetPassword("token", TestHelper.getNull(), "password") } assertFailsWith { provider.resetPassword("token", "token-id", TestHelper.getNull()) } @@ -488,7 +488,7 @@ class EmailPasswordAuthTests { @Test @UiThreadTest fun callMethodsOnMainThreadThrows() { - val provider: EmailPasswordAuth = app.emailPasswordAuth + val provider: EmailPasswordAuth = app.emailPassword val email: String = TestHelper.getRandomEmail() for (method in Method.values()) { try { @@ -509,7 +509,7 @@ class EmailPasswordAuthTests { @Test fun callAsyncMethodsOnNonLooperThreadThrows() { - val provider: EmailPasswordAuth = app.emailPasswordAuth + val provider: EmailPasswordAuth = app.emailPassword val email: String = TestHelper.getRandomEmail() val callback = App.Callback { fail() } for (method in Method.values()) { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserProfileInfoTests.kt similarity index 84% rename from realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt rename to realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserProfileInfoTests.kt index af405bcf6b..ba0ebc0a35 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserMetadataTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/UserProfileInfoTests.kt @@ -29,7 +29,7 @@ import org.junit.runner.RunWith import kotlin.test.assertNull @RunWith(AndroidJUnit4::class) -class UserMetadataTests { +class UserProfileTests { companion object { const val ACCESS_TOKEN = """eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODM5NjcyMDgsImlhdCI6MTU4Mzk2NTQwOCwiaXNzIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWEzIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWU2OTYzZGVhZmVhNjMyNTQ1ODFjMDI1Iiwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoiYWNjZXNzIn0.J4mp8LnlsxTQRV_7W2Er4qY0tptR76PJGG1k6HSMmUYqgfpJC2Fnbcf1VCoebzoNolH2-sr8AHDVBBCyjxRjqoY9OudFHmWZKmhDV1ysxPP4XmID0nUuN45qJSO8QEAqoOmP1crXjrUZWedFw8aaCZE-bxYfvcDHyjBcbNKZqzawwUw2PyTOlrNjgs01k2J4o5a5XzYkEsJuzr4_8UqKW6zXvYj24UtqnqoYatW5EzpX63m2qig8AcBwPK4ZHb5wEEUdf4QZxkRY5QmTgRHP8SSqVUB_mkHgKaizC_tSB3E0BekaDfLyWVC1taAstXJNfzgFtLI86AzuXS2dCiCfqQ""" const val REFRESH_TOKEN = """eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODkxNDk0MDgsImlhdCI6MTU4Mzk2NTQwOCwic3RpdGNoX2RhdGEiOm51bGwsInN0aXRjaF9kZXZJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInN0aXRjaF9kb21haW5JZCI6IjVlNjk2M2RlYWZlYTYzMjU0NTgxYzAyNSIsInN0aXRjaF9pZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMyIsInN0aXRjaF9pZGVudCI6eyJpZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMC1oaWF2b3ZkbmJxbGNsYXBwYnl1cmJpaW8iLCJwcm92aWRlcl90eXBlIjoiYW5vbi11c2VyIiwicHJvdmlkZXJfaWQiOiI1ZTY5NjNlMGFmZWE2MzI1NDU4MWMwNGEifSwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoicmVmcmVzaCJ9.FhLdpmL48Mw0SyUKWuaplz3wfeS8TCO8S7I9pIJenQww9nPqQ7lIvykQxjCCtinGvsZIJKt_7R31xYCq4Jp53Nw81By79IwkXtO7VXHPsXXZG5_2xV-s0u44e85sYD5su_H-xnx03sU2piJbWJLSB8dKu3rMD4mO-S0HNXCCAty-JkYKSaM2-d_nS8MNb6k7Vfm7y69iz_uwHc-bb_1rPg7r827K6DEeEMF41Hy3Nx1kCdAUOM9-6nYv3pZSU1PFrGYi2uyTXPJ7R7HigY5IGHWd0hwONb_NUr4An2omqfvlkLEd77ut4V9m6mExFkoKzRz7shzn-IGkh3e4h7ECGA""" @@ -47,9 +47,9 @@ class UserMetadataTests { } private lateinit var app: App - lateinit var profileBody : String + lateinit var profileBody: String - private fun setDefaultProfile(){ + private fun setDefaultProfile() { profileBody = """ { "name": "$NAME", @@ -65,7 +65,7 @@ class UserMetadataTests { """.trimIndent() } - private fun setNullProfile(){ + private fun setNullProfile() { profileBody = """ { @@ -82,7 +82,7 @@ class UserMetadataTests { app = TestApp(object : OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { var result = "" - if (url.endsWith("/providers/${Credentials.IdentityProvider.EMAIL_PASSWORD.id}/login")) { + if (url.endsWith("/providers/${Credentials.Provider.EMAIL_PASSWORD.id}/login")) { result = """ { "access_token": "$ACCESS_TOKEN", @@ -124,7 +124,7 @@ class UserMetadataTests { "ws_hostname": "ws://localhost:9090" } """.trimIndent()) - } else if (url.endsWith("/providers/${Credentials.IdentityProvider.EMAIL_PASSWORD.id}/register")) { + } else if (url.endsWith("/providers/${Credentials.Provider.EMAIL_PASSWORD.id}/register")) { result = "" } else { fail("Unexpected request url: $url") @@ -169,58 +169,74 @@ class UserMetadataTests { assertEquals(REFRESH_TOKEN, user.refreshToken) } + @Test + fun getUser(){ + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + val profile = user.profile + assertEquals(user, profile.user) + } + @Test fun getName() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(NAME, user.name) + val profile = user.profile + assertEquals(NAME, profile.name) } @Test fun getEmail() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(EMAIL, user.email) + val profile = user.profile + assertEquals(EMAIL, profile.email) } @Test fun getFirstName() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(FIRST_NAME, user.firstName) + val profile = user.profile + assertEquals(FIRST_NAME, profile.firstName) } @Test fun getLastName() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(LAST_NAME, user.lastName) + val profile = user.profile + assertEquals(LAST_NAME, profile.lastName) } @Test fun getBirthday() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(BIRTHDAY, user.birthday) + val profile = user.profile + assertEquals(BIRTHDAY, profile.birthday) } @Test fun getGender() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(GENDER, user.gender) + val profile = user.profile + assertEquals(GENDER, profile.gender) } @Test fun getMinAge() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(MIN_AGE, user.minAge) + val profile = user.profile + assertEquals(MIN_AGE, profile.minAge) } @Test fun getMaxAge() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(MAX_AGE, user.maxAge) + val profile = user.profile + assertEquals(MAX_AGE, profile.maxAge) } @Test fun getPictureUrl() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(PICTURE_URL, user.pictureUrl) + val profile = user.profile + assertEquals(PICTURE_URL, profile.pictureUrl) } @Test @@ -228,7 +244,8 @@ class UserMetadataTests { setNullProfile() val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNull(user.name) + val profile = user.profile + assertNull(profile.name) } @Test @@ -236,7 +253,8 @@ class UserMetadataTests { setNullProfile() val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNull(user.email) + val profile = user.profile + assertNull(profile.email) } @Test @@ -244,7 +262,8 @@ class UserMetadataTests { setNullProfile() val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNull(user.firstName) + val profile = user.profile + assertNull(profile.firstName) } @Test @@ -252,7 +271,8 @@ class UserMetadataTests { setNullProfile() val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNull(user.lastName) + val profile = user.profile + assertNull(profile.lastName) } @Test @@ -260,7 +280,8 @@ class UserMetadataTests { setNullProfile() val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNull(user.birthday) + val profile = user.profile + assertNull(profile.birthday) } @Test @@ -268,7 +289,8 @@ class UserMetadataTests { setNullProfile() val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNull(user.gender) + val profile = user.profile + assertNull(profile.gender) } @Test @@ -276,7 +298,8 @@ class UserMetadataTests { setNullProfile() val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNull(user.minAge) + val profile = user.profile + assertNull(profile.minAge) } @Test @@ -284,7 +307,8 @@ class UserMetadataTests { setNullProfile() val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNull(user.maxAge) + val profile = user.profile + assertNull(profile.maxAge) } @Test @@ -292,12 +316,13 @@ class UserMetadataTests { setNullProfile() val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNull(user.pictureUrl) + val profile = user.profile + assertNull(profile.pictureUrl) } @Test fun getProviderType() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, user.providerType) + assertEquals(Credentials.Provider.EMAIL_PASSWORD, user.providerType) } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt index f93a450d59..656e98c351 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt @@ -19,7 +19,6 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.Realm import io.realm.TestApp import io.realm.TestHelper -import io.realm.admin.ServerAdmin import io.realm.internal.network.LoggingInterceptor.LOGIN_FEATURE import io.realm.log.LogLevel import io.realm.log.RealmLog @@ -60,7 +59,7 @@ class LoggingInterceptorTest { val email = TestHelper.getRandomEmail() val password = "123456" - app.emailPasswordAuth.registerUser(email, password) + app.emailPassword.registerUser(email, password) assertMessageExists(""""email":"$email"""", """"password":"$password"""") app.login(Credentials.emailPassword(email, password)) @@ -76,37 +75,13 @@ class LoggingInterceptorTest { val email = TestHelper.getRandomEmail() val password = "123456" - app.emailPasswordAuth.registerUser(email, password) + app.emailPassword.registerUser(email, password) assertMessageExists(""""email":"***"""", """"password":"***"""") app.login(Credentials.emailPassword(email, password)) assertMessageExists(""""username":"***"""", """"password":"***"""") } - @Test - fun apiKeyLogin_noObfuscation() { - app = TestApp() - testLogger = getLogger() - val admin = ServerAdmin(app) - val serverKey = admin.createServerApiKey() - - app.login(Credentials.serverApiKey(serverKey)) - assertMessageExists(""""key":"$serverKey"""") - } - - @Test - fun apiKeyLogin_obfuscation() { - app = TestApp { builder -> - builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) - } - testLogger = getLogger() - val admin = ServerAdmin(app) - val serverKey = admin.createServerApiKey() - - app.login(Credentials.serverApiKey(serverKey)) - assertMessageExists(""""key":"***"""") - } - @Test fun customFunctionLogin_noObfuscation() { app = TestApp() diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt index d4798e3451..e0fc7ace62 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt @@ -39,6 +39,6 @@ fun App.close() { * This only works if users in the Realm Application are configured to be automatically confirmed. */ fun App.registerUserAndLogin(email: String, password: String): User { - emailPasswordAuth.registerUser(email, password) + emailPassword.registerUser(email, password) return login(Credentials.emailPassword(email, password)) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt index 54876e26a9..22fc5b339d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt @@ -71,6 +71,12 @@ class MongoClientTest { } } + @Test + fun serviceName() { + assertNotNull(client.serviceName) + assertEquals(SERVICE_NAME, client.serviceName) + } + @Test fun count() { with(getCollectionInternal()) { @@ -1424,6 +1430,20 @@ class MongoClientTest { } } + @Test + fun getMongoClientServiceName() { + assertNotNull(client.serviceName) + assertEquals(SERVICE_NAME, client.serviceName) + } + + @Test + fun getCollectionName() { + with(getCollectionInternal()) { + assertNotNull(this) + assertEquals(COLLECTION_NAME, this.name) + } + } + private fun getCollectionInternal(): MongoCollection { return client.getDatabase(DATABASE_NAME).let { assertEquals(it.name, DATABASE_NAME) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt index fbba49d25f..e3c29935b5 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/UserTests.kt @@ -19,8 +19,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.* import io.realm.admin.ServerAdmin +import io.realm.mongodb.auth.ApiKey import io.realm.mongodb.auth.ApiKeyAuth -import io.realm.mongodb.auth.UserApiKey import io.realm.rule.BlockingLooperThread import org.bson.Document import org.junit.After @@ -77,7 +77,7 @@ class UserTests { assertEquals(User.State.LOGGED_IN, emailUser.state) emailUser.logOut() assertEquals(User.State.LOGGED_OUT, emailUser.state) - emailUser.remove() + app.removeUser(emailUser) assertEquals(User.State.REMOVED, emailUser.state) } @@ -151,7 +151,7 @@ class UserTests { assertEquals(User.State.LOGGED_OUT, initialUser.state) repeat(3) { - val user = app.login(Credentials.emailPassword(initialUser.email, password)) + val user = app.login(Credentials.emailPassword(initialUser.profile.email, password)) assertEquals(User.State.LOGGED_IN, user.state) user.logOut() assertEquals(User.State.LOGGED_OUT, user.state) @@ -173,17 +173,17 @@ class UserTests { val email = TestHelper.getRandomEmail() val password = "123456" - app.emailPasswordAuth.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled + app.emailPassword.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled var linkedUser: User = anonUser.linkCredentials(Credentials.emailPassword(email, password)) assertTrue(anonUser === linkedUser) assertEquals(2, linkedUser.identities.size) - assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) + assertEquals(Credentials.Provider.EMAIL_PASSWORD, linkedUser.identities[1].provider) // Validate that we cannot link a second set of credentials val otherEmail = TestHelper.getRandomEmail() val otherPassword = "123456" - app.emailPasswordAuth.registerUser(otherEmail, otherPassword) + app.emailPassword.registerUser(otherEmail, otherPassword) val credentials = Credentials.emailPassword(otherEmail, otherPassword) @@ -196,7 +196,7 @@ class UserTests { fun linkUser_userApiKey() { // Generate API key val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - val apiKey: UserApiKey = user.apiKeyAuth.createApiKey("my-key"); + val apiKey: ApiKey = user.apiKeys.create("my-key"); user.logOut() anonUser = app.login(Credentials.anonymous()) @@ -214,18 +214,6 @@ class UserTests { assertEquals(6, exception.errorCode.intValue()); } - @Test - fun linkUser_serverApiKey() { - val serverKey = admin.createServerApiKey() - - assertEquals(1, anonUser.identities.size) - - // Linking a server API key is not allowed - val exception = assertFailsWith { - anonUser.linkCredentials(Credentials.serverApiKey(serverKey)) - } - } - @Test fun linkUser_customFunction() { assertEquals(1, anonUser.identities.size) @@ -241,7 +229,7 @@ class UserTests { assertTrue(anonUser === linkedUser) assertEquals(2, linkedUser.identities.size) - assertEquals(Credentials.IdentityProvider.CUSTOM_FUNCTION, linkedUser.identities[1].provider) + assertEquals(Credentials.Provider.CUSTOM_FUNCTION, linkedUser.identities[1].provider) } @Test @@ -271,13 +259,13 @@ class UserTests { assertEquals(1, anonUser.identities.size) val email = TestHelper.getRandomEmail() val password = "123456" - app.emailPasswordAuth.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled + app.emailPassword.registerUser(email, password) // TODO: Test what happens if auto-confirm is enabled anonUser.linkCredentialsAsync(Credentials.emailPassword(email, password)) { result -> val linkedUser: User = result.orThrow assertTrue(anonUser === linkedUser) assertEquals(2, linkedUser.identities.size) - assertEquals(Credentials.IdentityProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) + assertEquals(Credentials.Provider.EMAIL_PASSWORD, linkedUser.identities[1].provider) looperThread.testComplete() } } @@ -299,7 +287,7 @@ class UserTests { val user1 = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") assertEquals(user1, app.currentUser()) assertEquals(1, app.allUsers().size) - user1.remove() + app.removeUser(user1) assertEquals(User.State.REMOVED, user1.state) assertNull(app.currentUser()) assertEquals(0, app.allUsers().size) @@ -309,7 +297,7 @@ class UserTests { user2.logOut() assertNull(app.currentUser()) assertEquals(1, app.allUsers().size) - user2.remove() + app.removeUser(user2) assertEquals(User.State.REMOVED, user2.state) assertEquals(0, app.allUsers().size) } @@ -357,13 +345,13 @@ class UserTests { @Test fun getApiKeyAuthProvider() { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - val provider1: ApiKeyAuth = user.apiKeyAuth + val provider1: ApiKeyAuth = user.apiKeys assertEquals(user, provider1.user) user.logOut() try { - user.apiKeyAuth + user.apiKeys fail() } catch (ex: IllegalStateException) { } @@ -379,7 +367,7 @@ class UserTests { override fun loggedIn(user: User) {} override fun loggedOut(loggerOutUser: User) { - app.loginAsync(Credentials.emailPassword(loggerOutUser.email, password)) { + app.loginAsync(Credentials.emailPassword(loggerOutUser.profile.email, password)) { val loggedInUser = it.orThrow assertTrue(loggerOutUser !== loggedInUser) assertNotEquals(refreshToken, loggedInUser.refreshToken) @@ -390,12 +378,6 @@ class UserTests { user.logOut() } - @Test - fun getLocalId() { - val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - assertNotNull(user.localId) - } - fun isLoggedIn() { var anonUser = app.login(Credentials.anonymous()) val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") @@ -419,7 +401,7 @@ class UserTests { assertNotEquals(user, app) user.logOut() - val sameUserNewLogin = app.login(Credentials.emailPassword(user.email!!, "123456")) + val sameUserNewLogin = app.login(Credentials.emailPassword(user.profile.email!!, "123456")) // Verify that it is not same object but uses underlying OSSyncUser equality on identity assertFalse(user === sameUserNewLogin) assertEquals(user, sameUserNewLogin) @@ -433,7 +415,7 @@ class UserTests { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") user.logOut() - val sameUserNewLogin = app.login(Credentials.emailPassword(user.email!!, "123456")) + val sameUserNewLogin = app.login(Credentials.emailPassword(user.profile.email!!, "123456")) // Verify that two equal users also returns same hashCode assertFalse(user === sameUserNewLogin) assertEquals(user.hashCode(), sameUserNewLogin.hashCode()) @@ -486,7 +468,7 @@ class UserTests { // But will be updated when authorization token is refreshed user.logOut() - app.login(Credentials.emailPassword(user.email, password)) + app.login(Credentials.emailPassword(user.profile.email, password)) assertEquals(CUSTOM_USER_DATA_VALUE, user.customData.get(CUSTOM_USER_DATA_FIELD)) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt index 29a2fae73f..13a8f39970 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt @@ -502,5 +502,16 @@ class SessionTests { fun getOrCreateSession() { assertNotNull(app.sync.getOrCreateSession(configuration)) } + + @Test + fun getAllSessions(){ + val realm = Realm.getInstance(configuration) + val sessions = app.sync.allSessions + + assertNotNull(sessions) + assertEquals(1, sessions.size) + + realm.close() + } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index ec9a918a19..fc6a81c54f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -18,12 +18,12 @@ package io.realm.mongodb.sync import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.* -import io.realm.mongodb.SyncTestUtils.Companion.createTestUser import io.realm.entities.StringOnly import io.realm.entities.StringOnlyModule import io.realm.kotlin.createObject import io.realm.kotlin.where import io.realm.mongodb.AppException +import io.realm.mongodb.SyncTestUtils.Companion.createTestUser import io.realm.mongodb.User import io.realm.mongodb.close import io.realm.mongodb.registerUserAndLogin @@ -140,7 +140,7 @@ class SyncConfigurationTests { val config: SyncConfiguration = SyncConfiguration.Builder(user, DEFAULT_PARTITION) .name(filename) .build() - val suffix = "/mongodb-realm/${user.app.configuration.appId}/${user.localId}/$filename" + val suffix = "/mongodb-realm/${user.app.configuration.appId}/${user.id}/$filename" assertTrue(config.path.endsWith(suffix)) } @@ -310,8 +310,8 @@ class SyncConfigurationTests { val config2 = SyncConfiguration.defaultConfig(user, "realm2") assertNotEquals(config1.path, config2.path) - assertTrue(config1.path.endsWith("${app.configuration.appId}/${user.localId}/s_realm1.realm")) - assertTrue(config2.path.endsWith("${app.configuration.appId}/${user.localId}/s_realm2.realm")) + assertTrue(config1.path.endsWith("${app.configuration.appId}/${user.id}/s_realm1.realm")) + assertTrue(config2.path.endsWith("${app.configuration.appId}/${user.id}/s_realm2.realm")) // Check for https://github.com/realm/realm-java/issues/6882 val realm1 = Realm.getInstance(config1) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index d5364ce604..eb5438ab52 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -487,7 +487,7 @@ class SyncedRealmTests { private fun createNewUser(): User { val email = TestHelper.getRandomEmail() val password = "123456" - app.emailPasswordAuth.registerUser(email, password) + app.emailPassword.registerUser(email, password) return app.login(Credentials.emailPassword(email, password)) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt index a9aabc6d91..fea1a70200 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/transport/OsJavaNetworkTransportTests.kt @@ -59,7 +59,7 @@ class OsJavaNetworkTransportTests { app = TestApp(object: OsJavaNetworkTransport() { override fun sendRequest(method: String, url: String, timeoutMs: Long, headers: MutableMap, body: String): Response { var result = "" - if (url.endsWith("/providers/${Credentials.IdentityProvider.ANONYMOUS.id}/login")) { + if (url.endsWith("/providers/${Credentials.Provider.ANONYMOUS.id}/login")) { result = """ { "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODM5NjcyMDgsImlhdCI6MTU4Mzk2NTQwOCwiaXNzIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWEzIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWU2OTYzZGVhZmVhNjMyNTQ1ODFjMDI1Iiwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoiYWNjZXNzIn0.J4mp8LnlsxTQRV_7W2Er4qY0tptR76PJGG1k6HSMmUYqgfpJC2Fnbcf1VCoebzoNolH2-sr8AHDVBBCyjxRjqoY9OudFHmWZKmhDV1ysxPP4XmID0nUuN45qJSO8QEAqoOmP1crXjrUZWedFw8aaCZE-bxYfvcDHyjBcbNKZqzawwUw2PyTOlrNjgs01k2J4o5a5XzYkEsJuzr4_8UqKW6zXvYj24UtqnqoYatW5EzpX63m2qig8AcBwPK4ZHb5wEEUdf4QZxkRY5QmTgRHP8SSqVUB_mkHgKaizC_tSB3E0BekaDfLyWVC1taAstXJNfzgFtLI86AzuXS2dCiCfqQ", diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 39e2000676..d56125d6b3 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 39e20006761e77014ceb19a2bd8f43018cc96f5a +Subproject commit d56125d6b34a44b96b287a53a18744f06da44890 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java index ba65420065..9ac0e1903a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java @@ -45,6 +45,10 @@ public OsMongoDatabase getDatabase(final String databaseName, return new OsMongoDatabase(nativeDatabasePtr, serviceName, codecRegistry, streamNetworkTransport); } + public String getServiceName() { + return serviceName; + } + @Override public long getNativePtr() { return nativePtr; @@ -56,6 +60,8 @@ public long getNativeFinalizerPtr() { } private static native long nativeCreate(long nativeAppPtr, String serviceName); + private static native long nativeCreateDatabase(long nativeAppPtr, String databaseName); + private static native long nativeGetFinalizerMethodPtr(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index 3eabeed502..c0ff271a05 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -330,6 +330,34 @@ public User switchUser(User user) { return user; } + /** + * Removes a users credentials from this device. If the user was currently logged in, they + * will be logged out as part of the process. This is only a local change and does not + * affect the user state on the server. + * + * @param user to remove + * @return user that was removed. + * @throws AppException if called from the UI thread or if the user was logged in, but + * could not be logged out. + */ + public User removeUser(User user) throws AppException { + return user.remove(); + } + + /** + * Removes a user's credentials from this device. If the user was currently logged in, they + * will be logged out as part of the process. This is only a local change and does not + * affect the user state on the server. + * + * @param user to remove + * @param callback callback when removing the user has completed or failed. The callback will always + * happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + RealmAsyncTask removeAsync(User user, App.Callback callback) { + return user.removeAsync(callback); + } + /** * Logs in as a user with the given credentials associated with an authentication provider. *

              @@ -407,11 +435,11 @@ public User run() throws AppException { /** * Returns a wrapper for interacting with functionality related to users either being created or - * logged in using the {@link Credentials.IdentityProvider#EMAIL_PASSWORD} identity provider. + * logged in using the {@link Credentials.Provider#EMAIL_PASSWORD} identity provider. * - * @return wrapper for interacting with the {@link Credentials.IdentityProvider#EMAIL_PASSWORD} identity provider. + * @return wrapper for interacting with the {@link Credentials.Provider#EMAIL_PASSWORD} identity provider. */ - public EmailPasswordAuth getEmailPasswordAuth() { + public EmailPasswordAuth getEmailPassword() { return emailAuthProvider; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index 735af55086..3c91db1be8 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -121,7 +121,7 @@ public class AppConfiguration { * authenticate against an app and the values are the concrete obfuscators used for that * provider. * - * @see Credentials.IdentityProvider + * @see Credentials.Provider * @see RegexPatternObfuscator * @see ApiKeyObfuscator * @see TokenObfuscator @@ -296,14 +296,13 @@ public HttpLogObfuscator getHttpLogObfuscator() { private static Map getLoginObfuscators() { final HashMap obfuscators = new HashMap<>(); - obfuscators.put(Credentials.IdentityProvider.API_KEY.getId(), ApiKeyObfuscator.obfuscator()); - obfuscators.put(Credentials.IdentityProvider.SERVER_API_KEY.getId(), ApiKeyObfuscator.obfuscator()); - obfuscators.put(Credentials.IdentityProvider.APPLE.getId(), TokenObfuscator.obfuscator()); - obfuscators.put(Credentials.IdentityProvider.CUSTOM_FUNCTION.getId(), CustomFunctionObfuscator.obfuscator()); - obfuscators.put(Credentials.IdentityProvider.EMAIL_PASSWORD.getId(), EmailPasswordObfuscator.obfuscator()); - obfuscators.put(Credentials.IdentityProvider.FACEBOOK.getId(), TokenObfuscator.obfuscator()); - obfuscators.put(Credentials.IdentityProvider.GOOGLE.getId(), TokenObfuscator.obfuscator()); - obfuscators.put(Credentials.IdentityProvider.JWT.getId(), TokenObfuscator.obfuscator()); + obfuscators.put(Credentials.Provider.API_KEY.getId(), ApiKeyObfuscator.obfuscator()); + obfuscators.put(Credentials.Provider.APPLE.getId(), TokenObfuscator.obfuscator()); + obfuscators.put(Credentials.Provider.CUSTOM_FUNCTION.getId(), CustomFunctionObfuscator.obfuscator()); + obfuscators.put(Credentials.Provider.EMAIL_PASSWORD.getId(), EmailPasswordObfuscator.obfuscator()); + obfuscators.put(Credentials.Provider.FACEBOOK.getId(), TokenObfuscator.obfuscator()); + obfuscators.put(Credentials.Provider.GOOGLE.getId(), TokenObfuscator.obfuscator()); + obfuscators.put(Credentials.Provider.JWT.getId(), TokenObfuscator.obfuscator()); return obfuscators; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java index 4048dc4379..7861665804 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java @@ -25,7 +25,7 @@ /** * Credentials represent a login with a given login provider, and are used by the MongoDB Realm to - * verify the user and grant access. The {@link IdentityProvider#EMAIL_PASSWORD} provider is enabled + * verify the user and grant access. The {@link Provider#EMAIL_PASSWORD} provider is enabled * by default. All other providers must be enabled on MongoDB Realm to work. *

              * Note that users wanting to login using Email/Password must register first using @@ -58,7 +58,7 @@ public class Credentials { OsAppCredentials osCredentials; - private final IdentityProvider identityProvider; + private final Provider identityProvider; /** * Creates credentials representing an anonymous user. @@ -72,7 +72,7 @@ public class Credentials { * {@link App#loginAsync(Credentials, App.Callback)}. */ public static Credentials anonymous() { - return new Credentials(OsAppCredentials.anonymous(), IdentityProvider.ANONYMOUS); + return new Credentials(OsAppCredentials.anonymous(), Provider.ANONYMOUS); } /** @@ -86,21 +86,7 @@ public static Credentials anonymous() { */ public static Credentials apiKey(String key) { Util.checkEmpty(key, "key"); - return new Credentials(OsAppCredentials.apiKey(key), IdentityProvider.API_KEY); - } - - /** - * Creates credentials representing a login using a server API key. - *

              - * This provider must be enabled on MongoDB Realm to work. - * - * @param key the API key to use for login. - * @return a set of credentials that can be used to log into MongoDB Realm using - * {@link App#loginAsync(Credentials, App.Callback)}. - */ - public static Credentials serverApiKey(String key) { - Util.checkEmpty(key, "key"); - return new Credentials(OsAppCredentials.serverApiKey(key), IdentityProvider.SERVER_API_KEY); + return new Credentials(OsAppCredentials.apiKey(key), Provider.API_KEY); } /** @@ -114,7 +100,7 @@ public static Credentials serverApiKey(String key) { */ public static Credentials apple(String idToken) { Util.checkEmpty(idToken, "idToken"); - return new Credentials(OsAppCredentials.apple(idToken), IdentityProvider.APPLE); + return new Credentials(OsAppCredentials.apple(idToken), Provider.APPLE); } /** @@ -131,7 +117,7 @@ public static Credentials apple(String idToken) { public static Credentials customFunction(Document arguments) { Util.checkNull(arguments, "arguments"); return new Credentials(OsAppCredentials.customFunction(arguments), - IdentityProvider.CUSTOM_FUNCTION); + Provider.CUSTOM_FUNCTION); } /** @@ -146,7 +132,7 @@ public static Credentials emailPassword(String email, String password) { Util.checkEmpty(email, "email"); Util.checkEmpty(password, "password"); return new Credentials(OsAppCredentials.emailPassword(email, password), - IdentityProvider.EMAIL_PASSWORD); + Provider.EMAIL_PASSWORD); } /** @@ -160,7 +146,7 @@ public static Credentials emailPassword(String email, String password) { */ public static Credentials facebook(String accessToken) { Util.checkEmpty(accessToken, "accessToken"); - return new Credentials(OsAppCredentials.facebook(accessToken), IdentityProvider.FACEBOOK); + return new Credentials(OsAppCredentials.facebook(accessToken), Provider.FACEBOOK); } /** @@ -174,7 +160,7 @@ public static Credentials facebook(String accessToken) { */ public static Credentials google(String googleToken) { Util.checkEmpty(googleToken, "googleToken"); - return new Credentials(OsAppCredentials.google(googleToken), IdentityProvider.GOOGLE); + return new Credentials(OsAppCredentials.google(googleToken), Provider.GOOGLE); } /** @@ -189,7 +175,7 @@ public static Credentials google(String googleToken) { */ public static Credentials jwt(String jwtToken) { Util.checkEmpty(jwtToken, "jwtToken"); - return new Credentials(OsAppCredentials.jwt(jwtToken), IdentityProvider.JWT); + return new Credentials(OsAppCredentials.jwt(jwtToken), Provider.JWT); } /** @@ -197,7 +183,7 @@ public static Credentials jwt(String jwtToken) { * * @return the provider identifying the chosen credentials. */ - public IdentityProvider getIdentityProvider() { + public Provider getIdentityProvider() { String nativeProvider = osCredentials.getProvider(); String id = identityProvider.getId(); @@ -218,7 +204,7 @@ public String asJson() { return osCredentials.asJson(); } - private Credentials(OsAppCredentials credentials, IdentityProvider identityProvider) { + private Credentials(OsAppCredentials credentials, Provider identityProvider) { this.osCredentials = credentials; this.identityProvider = identityProvider; } @@ -230,10 +216,9 @@ private Credentials(OsAppCredentials credentials, IdentityProvider identityProvi * * @see Authentication Providers */ - public enum IdentityProvider { + public enum Provider { ANONYMOUS("anon-user"), - API_KEY("api-key"), - SERVER_API_KEY("api-key"), // same value as API_KEY as per OS specifications + API_KEY("api-key"), // same value as API_KEY as per OS specifications APPLE("oauth2-apple"), CUSTOM_FUNCTION("custom-function"), EMAIL_PASSWORD("local-userpass"), @@ -249,8 +234,8 @@ public enum IdentityProvider { * @return the enum representing the provider or {@link #UNKNOWN} if no matching provider * was found. */ - public static IdentityProvider fromId(String id) { - for (IdentityProvider value : values()) { + public static Provider fromId(String id) { + for (Provider value : values()) { if (value.getId().equals(id)) { return value; } @@ -260,7 +245,7 @@ public static IdentityProvider fromId(String id) { private final String id; - IdentityProvider(String id) { + Provider(String id) { this.id = id; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index 755132eff0..9074eaa1ac 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -58,6 +58,7 @@ public class User { OsSyncUser osUser; private final App app; + private final UserProfile profile; private ApiKeyAuth apiKeyAuthProvider = null; private MongoClient mongoClient = null; private Functions functions = null; @@ -117,6 +118,7 @@ protected PushImpl(OsPush osPush) { User(OsSyncUser osUser, App app) { this.osUser = osUser; this.app = app; + this.profile = new UserProfile(this); } /** @@ -129,105 +131,12 @@ public String getId() { } /** - * Returns the local id for this user. It is only guaranteed to be unique on this device. + * Returns the profile for this user. * - * @return the local id of the user + * @return the profile for this user */ - public String getLocalId() { - return osUser.getLocalIdentity(); - } - - /** - * Returns the name of the user. - * - * @return the name of the user. - */ - @Nullable - public String getName() { - return osUser.nativeGetName(); - } - - /** - * Returns the email address of the user. - * - * @return the email address of the user or null if there is no email address associated with the user. - * address. - */ - @Nullable - public String getEmail() { - return osUser.getEmail(); - } - - /** - * Returns the picture URL of the user. - * - * @return the picture URL of the user or null if there is no picture URL associated with the user. - */ - @Nullable - public String getPictureUrl() { - return osUser.getPictureUrl(); - } - - /** - * Return the first name of the user. - * - * @return the first name of the user or null if there is no first name associated with the user. - */ - @Nullable - public String getFirstName() { - return osUser.getFirstName(); - } - - /** - * Return the last name of the user. - * - * @return the last name of the user or null if there is no last name associated with the user. - */ - @Nullable - public String getLastName() { - return osUser.getLastName(); - } - - /** - * Returns the gender of the user. - * - * @return the gender of the user or null if there is no gender associated with the user. - */ - @Nullable - public String getGender() { - return osUser.getGender(); - } - - /** - * Returns the birthday of the user. - * - * @return the birthday of the user or null if there is no birthday associated with the user. - */ - @Nullable - public String getBirthday() { - return osUser.getBirthday(); - } - - /** - * Returns the minimum age of the user. - * - * @return the minimum age of the user or null if there is no minimum age associated with the user. - */ - @Nullable - public Long getMinAge() { - String minAge = osUser.getMinAge(); - return (minAge == null) ? null : Long.parseLong(minAge); - } - - /** - * Returns the maximum age of the user. - * - * @return the maximum age of the user or null if there is no maximum age associated with the user. - */ - @Nullable - public Long getMaxAge() { - String maxAge = osUser.getMaxAge(); - return (maxAge == null) ? null : Long.parseLong(maxAge); + public UserProfile getProfile(){ + return profile; } /** @@ -251,8 +160,8 @@ public List getIdentities() { * * @return the provider type of the user */ - public Credentials.IdentityProvider getProviderType() { - return Credentials.IdentityProvider.fromId(osUser.getProviderType()); + public Credentials.Provider getProviderType() { + return Credentials.Provider.fromId(osUser.getProviderType()); } /** @@ -431,16 +340,7 @@ public User run() throws AppException { }.start(); } - /** - * Removes a users credentials from this device. If the user was currently logged in, they - * will be logged out as part of the process. This is only a local change and does not - * affect the user state on the server. - * - * @return user that was removed. - * @throws AppException if called from the UI thread or if the user was logged in, but - * could not be logged out. - */ - public User remove() throws AppException { + User remove() throws AppException { boolean loggedIn = isLoggedIn(); AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); @@ -457,16 +357,7 @@ protected User mapSuccess(Object result) { return this; } - /** - * Removes a user's credentials from this device. If the user was currently logged in, they - * will be logged out as part of the process. This is only a local change and does not - * affect the user state on the server. - * - * @param callback callback when removing the user has completed or failed. The callback will always - * happen on the same thread as this method is called on. - * @throws IllegalStateException if called from a non-looper thread. - */ - public RealmAsyncTask removeAsync(App.Callback callback) { + RealmAsyncTask removeAsync(App.Callback callback) { Util.checkLooperThread("Asynchronous removal of users is only possible from looper threads."); return new Request(App.NETWORK_POOL_EXECUTOR, callback) { @Override @@ -488,7 +379,7 @@ public User run() throws AppException { * Logging out anonymous users will remove them immediately instead of marking them as * {@link User.State#LOGGED_OUT}. All other users will be marked as {@link User.State#LOGGED_OUT} * and will still be returned by {@link App#allUsers()}. They can be removed completely by calling - * {@link #remove()}. + * {@link App#removeUser(User} ()}. * * @throws AppException if an error occurred while trying to log the user out of the Realm * App. @@ -515,7 +406,7 @@ public void logOut() throws AppException { * Logging out anonymous users will remove them immediately instead of marking them as * {@link User.State#LOGGED_OUT}. All other users will be marked as {@link User.State#LOGGED_OUT} * and will still be returned by {@link App#allUsers()}. They can be removed completely by calling - * {@link #remove()}. + * {@link App#removeUser(User)} ()}. * * @param callback callback when logging out has completed or failed. The callback will always * happen on the same thread as this method is called on. @@ -538,7 +429,7 @@ public User run() throws AppException { * @return wrapper for managing API keys controlled by the current user. * @throws IllegalStateException if no user is currently logged in. */ - public synchronized ApiKeyAuth getApiKeyAuth() { + public synchronized ApiKeyAuth getApiKeys() { checkLoggedIn(); if (apiKeyAuthProvider == null) { apiKeyAuthProvider = new ApiKeyAuthImpl(this); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java index fc47cdc9fb..80a076c215 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserIdentity.java @@ -19,7 +19,7 @@ /** * Each User is represented by 1 or more identities each defined by an - * {@link Credentials.IdentityProvider}. + * {@link Credentials.Provider}. * * This class represents the identity defined by a specific provider. */ @@ -28,12 +28,12 @@ public class UserIdentity { private final String userId; private final String providerId; - private final Credentials.IdentityProvider provider; + private final Credentials.Provider provider; UserIdentity(String id, String providerId) { this.userId = id; this.providerId = providerId; - this.provider = Credentials.IdentityProvider.fromId(providerId); + this.provider = Credentials.Provider.fromId(providerId); } /** @@ -50,7 +50,7 @@ public String getId() { * * @return */ - public Credentials.IdentityProvider getProvider() { + public Credentials.Provider getProvider() { return provider; } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserProfile.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserProfile.java new file mode 100644 index 0000000000..21590cf84a --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/UserProfile.java @@ -0,0 +1,162 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb; + +import javax.annotation.Nullable; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class UserProfile { + private final User user; + + UserProfile(User user) { + this.user = user; + } + + /** + * Returns the name of the user. + * + * @return the name of the user. + */ + @Nullable + public String getName() { + return user.osUser.nativeGetName(); + } + + /** + * Returns the email address of the user. + * + * @return the email address of the user or null if there is no email address associated with the user. + * address. + */ + @Nullable + public String getEmail() { + return user.osUser.getEmail(); + } + + /** + * Returns the picture URL of the user. + * + * @return the picture URL of the user or null if there is no picture URL associated with the user. + */ + @Nullable + public String getPictureUrl() { + return user.osUser.getPictureUrl(); + } + + /** + * Return the first name of the user. + * + * @return the first name of the user or null if there is no first name associated with the user. + */ + @Nullable + public String getFirstName() { + return user.osUser.getFirstName(); + } + + /** + * Return the last name of the user. + * + * @return the last name of the user or null if there is no last name associated with the user. + */ + @Nullable + public String getLastName() { + return user.osUser.getLastName(); + } + + /** + * Returns the gender of the user. + * + * @return the gender of the user or null if there is no gender associated with the user. + */ + @Nullable + public String getGender() { + return user.osUser.getGender(); + } + + /** + * Returns the birthday of the user. + * + * @return the birthday of the user or null if there is no birthday associated with the user. + */ + @Nullable + public String getBirthday() { + return user.osUser.getBirthday(); + } + + /** + * Returns the minimum age of the user. + * + * @return the minimum age of the user or null if there is no minimum age associated with the user. + */ + @Nullable + public Long getMinAge() { + String minAge = user.osUser.getMinAge(); + return (minAge == null) ? null : Long.parseLong(minAge); + } + + /** + * Returns the maximum age of the user. + * + * @return the maximum age of the user or null if there is no maximum age associated with the user. + */ + @Nullable + public Long getMaxAge() { + String maxAge = user.osUser.getMaxAge(); + return (maxAge == null) ? null : Long.parseLong(maxAge); + } + + /** + * Returns the {@link User} that this instance in associated with. + * + * @return The {@link User} that this instance in associated with. + */ + public User getUser() { + return user; + } + + @Override + public String toString() { + return "Profile{" + + "name='" + getName() + '\'' + + ", email='" + getEmail() + '\'' + + ", pictureUrl='" + getPictureUrl() + '\'' + + ", firstName='" + getFirstName() + '\'' + + ", lastName='" + getLastName() + '\'' + + ", gender='" + getGender() + '\'' + + ", birthday='" + getBirthday() + '\'' + + ", minAge=" + getMinAge() + + ", maxAge=" + getMaxAge() + + '}'; + } + + @SuppressFBWarnings("NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION") + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + UserProfile profile = (UserProfile) o; + + return user.equals(profile.user); + } + + @Override + public int hashCode() { + return user.hashCode(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKey.java similarity index 92% rename from realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java rename to realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKey.java index 6101590f44..0eb244ae6a 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/UserApiKey.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKey.java @@ -20,27 +20,26 @@ import javax.annotation.Nullable; import io.realm.annotations.Beta; -import io.realm.mongodb.App; import io.realm.mongodb.User; /** * Class representing an API key for a {@link User}. An API can be used to represent the * user when logging instead of using email and password. *

              - * These keys are created or fetched through {@link ApiKeyAuth#createApiKey(String)} or the various + * These keys are created or fetched through {@link ApiKeyAuth#create(String)} or the various * {@code fetch}-methods. *

              * Note that a keys {@link #value} is only available when the key is created, after that it is not * visible. So anyone creating an API key is responsible for storing it safely after that. */ @Beta -public class UserApiKey { +public class ApiKey { private final ObjectId id; private final String value; private final String name; private final boolean enabled; - UserApiKey(ObjectId id, @Nullable String value, String name, boolean enabled) { + ApiKey(ObjectId id, @Nullable String value, String name, boolean enabled) { this.id = id; this.value = value; this.name = name; @@ -91,7 +90,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - UserApiKey that = (UserApiKey) o; + ApiKey that = (ApiKey) o; if (enabled != that.enabled) return false; if (!id.equals(that.id)) return false; @@ -110,7 +109,7 @@ public int hashCode() { @Override public String toString() { - return "UserApiKey{" + + return "ApiKey{" + "id=" + id + ", value='" + value + '\'' + ", name='" + name + '\'' + diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java index fcde3fad8c..d8b6738744 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/ApiKeyAuth.java @@ -84,19 +84,19 @@ public App getApp() { *

              * The value of the key must be persisted at this time as this is the only time it is visible. *

              - * The key is enabled when created. It can be disabled by calling {@link #disableApiKey(ObjectId)}. + * The key is enabled when created. It can be disabled by calling {@link #disable(ObjectId)}. * * @param name the name of the key * @throws AppException if the server failed to create the API key. * @return the new API key for the user. */ - public UserApiKey createApiKey(String name) throws AppException { + public ApiKey create(String name) throws AppException { Util.checkEmpty(name, "name"); - AtomicReference success = new AtomicReference<>(null); + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { + OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override - protected UserApiKey mapSuccess(Object result) { + protected ApiKey mapSuccess(Object result) { return createKeyFromNative((Object[]) result); } }; @@ -109,19 +109,19 @@ protected UserApiKey mapSuccess(Object result) { *

              * The value of the key must be persisted at this time as this is the only time it is visible. *

              - * The key is enabled when created. It can be disabled by calling {@link #disableApiKey(ObjectId)}. + * The key is enabled when created. It can be disabled by calling {@link #disable(ObjectId)}. * * @param name the name of the key * @param callback callback when key creation has completed or failed. The callback will always * happen on the same thread as this method is called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask createApiKeyAsync(String name, App.Callback callback) { + public RealmAsyncTask createAsync(String name, App.Callback callback) { Util.checkLooperThread("Asynchronous creation of api keys are only possible from looper threads."); - return new Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public UserApiKey run() throws AppException { - return createApiKey(name); + public ApiKey run() throws AppException { + return create(name); } }.start(); } @@ -132,13 +132,13 @@ public UserApiKey run() throws AppException { * @param id the id of the key to fetch. * @throws AppException if the server failed to fetch the API key. */ - public UserApiKey fetchApiKey(ObjectId id) throws AppException { + public ApiKey fetch(ObjectId id) throws AppException { Util.checkNull(id, "id"); - AtomicReference success = new AtomicReference<>(null); + AtomicReference success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - call(TYPE_FETCH_SINGLE, id.toHexString(), new OsJNIResultCallback(success, error) { + call(TYPE_FETCH_SINGLE, id.toHexString(), new OsJNIResultCallback(success, error) { @Override - protected UserApiKey mapSuccess(Object result) { + protected ApiKey mapSuccess(Object result) { return createKeyFromNative((Object[]) result); } }); @@ -153,12 +153,12 @@ protected UserApiKey mapSuccess(Object result) { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask fetchApiKeyAsync(ObjectId id, App.Callback callback) { + public RealmAsyncTask fetchAsync(ObjectId id, App.Callback callback) { Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); - return new Request(NETWORK_POOL_EXECUTOR, callback) { + return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override - public UserApiKey run() throws AppException { - return fetchApiKey(id); + public ApiKey run() throws AppException { + return fetch(id); } }.start(); } @@ -168,14 +168,14 @@ public UserApiKey run() throws AppException { * * @throws AppException if the server failed to fetch the API keys. */ - public List fetchAllApiKeys() throws AppException { - AtomicReference> success = new AtomicReference<>(null); + public List fetchAll() throws AppException { + AtomicReference> success = new AtomicReference<>(null); AtomicReference error = new AtomicReference<>(null); - call(TYPE_FETCH_ALL, null, new OsJNIResultCallback>(success, error) { + call(TYPE_FETCH_ALL, null, new OsJNIResultCallback>(success, error) { @Override - protected List mapSuccess(Object result) { + protected List mapSuccess(Object result) { Object[] keyData = (Object[]) result; - List list = new ArrayList<>(); + List list = new ArrayList<>(); for (int i = 0; i < keyData.length; i++) { list.add(createKeyFromNative((Object[]) keyData[i])); } @@ -193,12 +193,12 @@ protected List mapSuccess(Object result) { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask fetchAllApiKeys(App.Callback> callback) { + public RealmAsyncTask fetchAll(App.Callback> callback) { Util.checkLooperThread("Asynchronous fetching an api key is only possible from looper threads."); - return new Request>(NETWORK_POOL_EXECUTOR, callback) { + return new Request>(NETWORK_POOL_EXECUTOR, callback) { @Override - public List run() throws AppException { - return fetchAllApiKeys(); + public List run() throws AppException { + return fetchAll(); } }.start(); } @@ -209,7 +209,7 @@ public List run() throws AppException { * @param id the id of the key to delete. * @throws AppException if the server failed to delete the API key. */ - public void deleteApiKey(ObjectId id) throws AppException { + public void delete(ObjectId id) throws AppException { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); call(TYPE_DELETE, id.toHexString(), new OsJNIVoidResultCallback(error)); @@ -224,12 +224,12 @@ public void deleteApiKey(ObjectId id) throws AppException { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask deleteApiKeyAsync(ObjectId id, App.Callback callback) { + public RealmAsyncTask deleteAsync(ObjectId id, App.Callback callback) { Util.checkLooperThread("Asynchronous deleting an api key is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws AppException { - deleteApiKey(id); + delete(id); return null; } }.start(); @@ -241,7 +241,7 @@ public Void run() throws AppException { * @param id the id of the key to disable. * @throws AppException if the server failed to disable the API key. */ - public void disableApiKey(ObjectId id) throws AppException { + public void disable(ObjectId id) throws AppException { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); call(TYPE_DISABLE, id.toHexString(), new OsJNIVoidResultCallback(error)); @@ -256,12 +256,12 @@ public void disableApiKey(ObjectId id) throws AppException { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask disableApiKeyAsync(ObjectId id, App.Callback callback) { + public RealmAsyncTask disableAsync(ObjectId id, App.Callback callback) { Util.checkLooperThread("Asynchronous disabling an api key is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws AppException { - disableApiKey(id); + disable(id); return null; } }.start(); @@ -273,7 +273,7 @@ public Void run() throws AppException { * @param id the id of the key to enable. * @throws AppException if the server failed to enable the API key. */ - public void enableApiKey(ObjectId id) throws AppException { + public void enable(ObjectId id) throws AppException { Util.checkNull(id, "id"); AtomicReference error = new AtomicReference<>(null); call(TYPE_ENABLE, id.toHexString(), new OsJNIVoidResultCallback(error)); @@ -288,19 +288,19 @@ public void enableApiKey(ObjectId id) throws AppException { * will always happen on the same thread as this method was called on. * @throws IllegalStateException if called from a non-looper thread. */ - public RealmAsyncTask enableApiKeyAsync(ObjectId id, App.Callback callback) { + public RealmAsyncTask enableAsync(ObjectId id, App.Callback callback) { Util.checkLooperThread("Asynchronous enabling an api key is only possible from looper threads."); return new Request(NETWORK_POOL_EXECUTOR, callback) { @Override public Void run() throws AppException { - enableApiKey(id); + enable(id); return null; } }.start(); } - private UserApiKey createKeyFromNative(Object[] keyData) { - return new UserApiKey(new ObjectId((String) keyData[0]), + private ApiKey createKeyFromNative(Object[] keyData) { + return new ApiKey(new ObjectId((String) keyData[0]), (String) keyData[1], (String) keyData[2], !(Boolean) keyData[3]); // Server returns disabled state instead of enabled diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java index c26157b28c..22b0f22be2 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java @@ -35,7 +35,7 @@ /** * Class encapsulating functionality provided when {@link User}'s are logged in through the - * {@link Credentials.IdentityProvider#EMAIL_PASSWORD} provider. + * {@link Credentials.Provider#EMAIL_PASSWORD} provider. */ @Beta public abstract class EmailPasswordAuth { @@ -198,7 +198,7 @@ public Void run() throws AppException { /** * Call the reset password function configured to the - * {@link Credentials.IdentityProvider#EMAIL_PASSWORD} provider. + * {@link Credentials.Provider#EMAIL_PASSWORD} provider. * * @param email the email of the user. * @param newPassword the new password of the user. @@ -217,7 +217,7 @@ public void callResetPasswordFunction(String email, String newPassword, Object.. /** * Call the reset password function configured to the - * {@link Credentials.IdentityProvider#EMAIL_PASSWORD} provider. + * {@link Credentials.Provider#EMAIL_PASSWORD} provider. * * @param email the email of the user. * @param newPassword the new password of the user. diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java index f0b222d412..0da759e937 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoClient.java @@ -48,4 +48,13 @@ public MongoDatabase getDatabase(final String databaseName) { return new MongoDatabase(osMongoClient.getDatabase(databaseName, codecRegistry), databaseName); } + + /** + * Returns the service name for this client. + * + * @return the service name. + */ + public String getServiceName() { + return osMongoClient.getServiceName(); + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java index d6a46506ef..7b9aa6ae20 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/mongo/MongoCollection.java @@ -85,6 +85,15 @@ public MongoNamespace getNamespace() { return nameSpace; } + /** + * Gets the name of this collection + * + * @return the name + */ + public String getName() { + return nameSpace.getCollectionName(); + } + /** * Gets the class of documents stored in this collection. *

              diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java index 7ff90d7414..8c21ba9674 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; @@ -133,6 +134,15 @@ public synchronized SyncSession getSession(SyncConfiguration syncConfiguration) return session; } + /** + * Gets a collection of all the cached {@link SyncSession}. + * + * @return a collection of {@link SyncSession}. + */ + public synchronized Collection getAllSessions(){ + return this.sessions.values(); + } + /** * Gets any cached {@link SyncSession} for the given {@link SyncConfiguration} or create a new one if * no one exists. @@ -280,7 +290,7 @@ private synchronized void notifyProgressListener(String localRealmPath, long lis * sessions to attempt to reconnect immediately and reset any timers they are using for * incremental backoff. */ - public static void refreshConnections() { + public static void reconnect() { notifyNetworkIsBack(); } diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt index 6e3fe4cbd2..31d9277f02 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt @@ -126,7 +126,7 @@ class EncryptedSynchronizedRealmTests { user.logOut() // STEP 3: try to open again the Realm without the encryption key should fail - user = app.login(Credentials.emailPassword(user.email, SECRET_PASSWORD)) + user = app.login(Credentials.emailPassword(user.profile.email, SECRET_PASSWORD)) val configWithoutEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, configWithEncryption.partitionValue) .testSchema(SyncStringOnly::class.java) .waitForInitialRemoteData() diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 6edb9eeb2b..225932df5a 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -432,7 +432,7 @@ class SyncSessionTests { assertEquals(SyncSession.State.INACTIVE, session2.state) // Login again - app.login(Credentials.emailPassword(user.email!!, SECRET_PASSWORD)) + app.login(Credentials.emailPassword(user.profile.email!!, SECRET_PASSWORD)) // reviving the sessions. The state could be changed concurrently. // FIXME Reavaluate with new sync states @@ -501,7 +501,7 @@ class SyncSessionTests { allResults.get().addChangeListener(realmChangeListener) // login again to re-activate the user - val credentials = Credentials.emailPassword(user.email!!, SECRET_PASSWORD) + val credentials = Credentials.emailPassword(user.profile.email!!, SECRET_PASSWORD) // this login will re-activate the logged out user, and resume all it's pending sessions // the OS will trigger bindSessionWithConfig with the new refresh_token, in order to obtain // a new access_token. @@ -576,7 +576,7 @@ class SyncSessionTests { @Ignore("Does not terminate") fun downloadChangesWhenRealmOutOfScope() { val uniqueName = UUID.randomUUID().toString() - app.emailPasswordAuth.registerUser(uniqueName, "password") + app.emailPassword.registerUser(uniqueName, "password") val config1 = configFactory .createSyncConfigurationBuilder(user) .modules(SyncStringOnlyModule()) @@ -592,7 +592,7 @@ class SyncSessionTests { user.logOut() // Log the user back in. - val credentials = Credentials.emailPassword(user.email!!, SECRET_PASSWORD) + val credentials = Credentials.emailPassword(user.profile.email!!, SECRET_PASSWORD) app.login(credentials) // now let the admin upload some commits From 0305dfa679dc6585c1a6c8530441479dad0197c3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 9 Sep 2020 22:22:26 +0200 Subject: [PATCH 1667/2110] Update to Sync 5.0.22 (#7092) --- CHANGELOG.md | 21 ++++++++++++++++++++- dependencies.list | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f6b4720ca..a5122bdd3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## 7.0.5 (2020-09-09) + +### Enhancements +* None. + +### Fixes +* If you have a Realm file growing towards 2Gb and have a model class with more than 16 properties, then you may get a "Key not found" exception when updating an object. (Realm JS issue [#3194](https://github.com/realm/realm-js/issues/3194), since v7.0.0) +* In cases where you have more than 32 properties in a model class, you may get a currrupted file resulting in various crashes (Issue [#7057](https://github.com/realm/realm-java/issues/7057), since v7.0.0) + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. + +### Internal +* Upgraded to Object Store commit: S286d7cb2f10c41f89a2efb43b22938610ccad4cf. +* Upgraded to Realm Sync: 5.0.22. +* Upgraded to Realm Core: 6.0.25. + + ## 7.0.4 (2020-09-08) ### Enhancements @@ -19,7 +39,6 @@ * Upgraded to Realm Core: 6.0.24. * Fileformat has been bumped from 10 to 11. - ## 7.0.3 (2020-09-01) ### Enhancements diff --git a/dependencies.list b/dependencies.list index 5d3fc0142e..d3302f0eae 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.21 -REALM_SYNC_SHA256=49a9f4c7a0ab5ffa43fc7c630d2a8c8f9cf88da5b50dec86ca4eed62772900f5 +REALM_SYNC_VERSION=5.0.22 +REALM_SYNC_SHA256=bdb9b2655f87808faaf6a26b3b5faa3c710965303d082c6e7557bae779dd1a0b # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. From 37ab93c33d75748c4cb2e078440cb0add08d87b3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 9 Sep 2020 22:29:56 +0200 Subject: [PATCH 1668/2110] Release v7.0.5 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index cd1c1513e8..c60ebc1807 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.5-SNAPSHOT \ No newline at end of file +7.0.5 \ No newline at end of file From f1ef5d88ccfe209f0c9ad4f1b31747540212f9f3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 9 Sep 2020 22:29:56 +0200 Subject: [PATCH 1669/2110] Prepare next release v7.0.6-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index c60ebc1807..df3ac08d90 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.5 \ No newline at end of file +7.0.6-SNAPSHOT \ No newline at end of file From 9af9d55ffbc73b120adaf38c9d4562baf3b3a0eb Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 15 Sep 2020 12:53:36 +0200 Subject: [PATCH 1670/2110] Show error messages for UTF decoding issues (#7093) --- CHANGELOG.md | 15 +++ .../java/io/realm/UTFStringsTests.java | 109 ++++++++++++++++++ realm/realm-library/src/main/cpp/util.cpp | 20 +++- 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/UTFStringsTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a5122bdd3f..e595340854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 7.0.6 (yyyy-mm-dd) + +### Enhancements +* Better exception messaging for UTF encoding errors. ([Issue #7093](https://github.com/realm/realm-java/pull/7093)) + +### Fixes +* None. + +### Compatibility +* None. + +### Internal +* None. + + ## 7.0.5 (2020-09-09) ### Enhancements diff --git a/realm/realm-library/src/androidTest/java/io/realm/UTFStringsTests.java b/realm/realm-library/src/androidTest/java/io/realm/UTFStringsTests.java new file mode 100644 index 0000000000..f48003f240 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/UTFStringsTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import io.realm.entities.StringOnly; +import io.realm.rule.TestRealmConfigurationFactory; + +@RunWith(AndroidJUnit4.class) +public class UTFStringsTests { + @Rule + public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Realm realm; + + @Before + public void setUp() { + RealmConfiguration config = configFactory.createConfiguration(); + realm = Realm.getInstance(config); + } + + @After + public void tearDown() { + if (realm != null) { + realm.close(); + } + } + + @Test + public void valid_utf() { + realm.beginTransaction(); + + StringOnly validString = new StringOnly(); + validString.setChars("\uD800\uDC00"); + realm.copyToRealm(validString); + + realm.commitTransaction(); + } + + @Test + public void invalid_first_half() { + realm.beginTransaction(); + + // Test invalid first surrogate + StringOnly invalidFirstSurrogate = new StringOnly(); + invalidFirstSurrogate.setChars("\uDC00\uD800"); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Illegal Argument: Failure when converting to UTF-8: Invalid first half of surrogate pair; error_code = 5; 0xdc00 0xd800"); + + realm.copyToRealm(invalidFirstSurrogate); + realm.commitTransaction(); + } + + @Test + public void invalid_second_half() { + realm.beginTransaction(); + + // Test invalid second surrogate + StringOnly invalidSecondSurrogate = new StringOnly(); + invalidSecondSurrogate.setChars("\uD800\uD800"); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Illegal Argument: Failure when converting to UTF-8: Invalid second half of surrogate pair; error_code = 7; 0xd800 0xd800"); + + realm.copyToRealm(invalidSecondSurrogate); + + realm.commitTransaction(); + } + + @Test + public void incomplete_surrogate() { + realm.beginTransaction(); + + // Test incomplete surrogate + StringOnly incompleteSurrogate = new StringOnly(); + incompleteSurrogate.setChars("\u0000\uD800"); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Illegal Argument: Failure when converting to UTF-8: Incomplete surrogate pair; error_code = 6; 0x0000 0xd800"); + + realm.copyToRealm(incompleteSurrogate); + + realm.commitTransaction(); + } +} diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index 78f4d3b17b..a51eb408d0 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -362,11 +362,29 @@ static string string_to_hex(const string& message, StringData& str, const char* return ret.str(); } +static string str_to_hex_error_code_to_message(size_t error_code){ + switch (error_code){ + case 1: + case 2: + case 3: + case 4: + return "Not enough output buffer space"; + case 5: + return "Invalid first half of surrogate pair"; + case 6: + return "Incomplete surrogate pair"; + case 7: + return "Invalid second half of surrogate pair"; + default: + return "Unknown"; + } +} + static string string_to_hex(const string& message, const jchar* str, size_t size, size_t error_code) { ostringstream ret; - ret << message << "; "; + ret << message << ": " << str_to_hex_error_code_to_message(error_code) << "; "; ret << "error_code = " << error_code << "; "; for (size_t i = 0; i < size; ++i) { ret << " 0x" << std::hex << std::setfill('0') << std::setw(4) << (int) str[i]; From 26685d186faa1c00334d3e0e5dc8dc1bc2f3fcde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 15 Sep 2020 12:58:17 +0200 Subject: [PATCH 1671/2110] Migrate and reenable sync realm integration test (#6939) --- .../kotlin/io/realm/AppConfigurationTests.kt | 60 +- .../kotlin/io/realm/AppTests.kt | 2 +- .../kotlin/io/realm/ProgressListenerTests.kt | 87 +++ .../io/realm/entities/SyncSchemeMigration.kt | 33 ++ .../io/realm/mongodb/sync/SyncSessionExt.kt | 4 + .../cpp/io_realm_mongodb_sync_SyncSession.cpp | 11 + .../java/io/realm/RealmConfiguration.java | 2 +- .../network/OkHttpNetworkTransport.java | 18 +- .../realm/mongodb/sync/SyncConfiguration.java | 5 + .../io/realm/mongodb/sync/SyncSession.java | 5 + .../io/realm/SyncedRealmIntegrationTests.java | 546 ------------------ .../kotlin/io/realm/SyncSessionTests.kt | 80 +-- .../io/realm/SyncedRealmIntegrationTests.kt | 333 +++++++++++ .../syncTestUtils/kotlin/io/realm/TestApp.kt | 12 +- .../io/realm/TestSyncConfigurationFactory.kt | 5 + .../mongodb/sync/SyncConfigurationExt.kt | 4 + 16 files changed, 592 insertions(+), 615 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSchemeMigration.kt delete mode 100644 realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index 578f4d518c..035076f33c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -18,9 +18,14 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.internal.network.LoggingInterceptor.LOGIN_FEATURE -import io.realm.mongodb.AppConfiguration +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.log.RealmLogger +import io.realm.mongodb.* import io.realm.mongodb.log.obfuscator.HttpLogObfuscator import io.realm.mongodb.sync.SyncSession +import io.realm.rule.BlockingLooperThread +import io.realm.util.assertFailsWithErrorCode import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries import org.junit.Assert.* @@ -31,13 +36,22 @@ import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import java.io.File import java.net.URL +import java.util.* import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.collections.LinkedHashMap import kotlin.test.assertFailsWith import kotlin.test.assertNull +private const val CUSTOM_HEADER_NAME = "Foo" +private const val CUSTOM_HEADER_VALUE = "bar" +private const val AUTH_HEADER_NAME = "RealmAuth" + @RunWith(AndroidJUnit4::class) class AppConfigurationTests { + val looperThread = BlockingLooperThread() + @get:Rule val tempFolder = TemporaryFolder() @@ -300,4 +314,48 @@ class AppConfigurationTests { val defaultHttpLogObfuscator = HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators) assertEquals(defaultHttpLogObfuscator, config.httpLogObfuscator) } + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + fun javaRequestCustomHeaders() { + var app: App? = null + try { + looperThread.runBlocking { + app = TestApp(builder = { builder -> + builder.addCustomRequestHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_VALUE) + builder.authorizationHeaderName(AUTH_HEADER_NAME) + }) + runJavaRequestCustomHeadersTest(app!!) + } + } finally { + app?.close() + } + } + + private fun runJavaRequestCustomHeadersTest(app: App) { + val username = UUID.randomUUID().toString() + val password = "password" + val headerSet = AtomicBoolean(false) + + // Setup logger to inspect that we get a log message with the custom headers + val level = RealmLog.getLevel() + RealmLog.setLevel(LogLevel.ALL) + val logger = RealmLogger { level: Int, tag: String?, throwable: Throwable?, message: String? -> + if (level > LogLevel.TRACE && message!!.contains(CUSTOM_HEADER_NAME) && message.contains(CUSTOM_HEADER_VALUE) + && message.contains("RealmAuth: ")) { + headerSet.set(true) + } + } + RealmLog.add(logger) + assertFailsWithErrorCode(ErrorCode.SERVICE_UNKNOWN) { + app.registerUserAndLogin(username, password) + } + RealmLog.remove(logger) + RealmLog.setLevel(level) + + assertTrue(headerSet.get()) + looperThread.testComplete() + } + + } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index 145cdd4cce..da226cc63d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -277,7 +277,7 @@ class AppTests { // Setup an App instance with a random encryption key Realm.init(context) - app = TestApp(customizeConfig = { + app = TestApp(builder = { it.encryptionKey(TestHelper.getRandomKey()) }) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt index 9dea7c506e..e12d281e43 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt @@ -19,6 +19,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.DefaultSyncSchema import io.realm.entities.SyncDog +import io.realm.entities.SyncStringOnly +import io.realm.internal.OsRealmConfig import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.LogLevel @@ -27,16 +29,21 @@ import io.realm.mongodb.User import io.realm.mongodb.close import io.realm.mongodb.registerUserAndLogin import io.realm.mongodb.sync.* +import io.realm.rule.BlockingLooperThread +import org.bson.types.ObjectId import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import java.lang.IllegalStateException import java.util.* import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) class ProgressListenerTests { @@ -45,6 +52,9 @@ class ProgressListenerTests { private const val TEST_SIZE: Long = 10 } + private val looperThread = BlockingLooperThread() + private val configurationFactory = TestSyncConfigurationFactory() + private lateinit var app: TestApp private lateinit var partitionValue: String @@ -290,6 +300,83 @@ class ProgressListenerTests { } } + @Test + @Ignore("FIXME: Flacky: Tracked by https://github.com/realm/realm-java/issues/6976") + fun progressListenersWorkWhenUsingWaitForInitialRemoteData() = looperThread.runBlocking { + val username = UUID.randomUUID().toString() + val password = "password" + var user: User = app.registerUserAndLogin(username, password) + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + Realm.getInstance(configOld).use { realm -> + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" + } + } + realm.syncSession.uploadAllLocalChanges() + } + user.logOut() + + assertFailsWith { + app.sync.getSession(configOld) + } + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + // Use different user to trigger different path + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), password) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, user.id) + .testSchema(SyncStringOnly::class.java) + .waitForInitialRemoteData() + .build() + + assertFalse(config.testRealmExists()) + + val countDownLatch = CountDownLatch(2) + + val indefiniteListenerComplete = AtomicBoolean(false) + val currentChangesListenerComplete = AtomicBoolean(false) + val task = Realm.getInstanceAsync(config, object : Realm.Callback() { + override fun onSuccess(realm: Realm) { + realm.syncSession.addDownloadProgressListener(ProgressMode.INDEFINITELY, object : ProgressListener { + override fun onChange(progress: Progress) { + if (progress.isTransferComplete()) { + indefiniteListenerComplete.set(true) + countDownLatch.countDown() + } + } + }) + realm.syncSession.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, object : ProgressListener { + override fun onChange(progress: Progress) { + if (progress.isTransferComplete()) { + currentChangesListenerComplete.set(true) + countDownLatch.countDown() + } + } + }) + countDownLatch.await(100, TimeUnit.SECONDS) + realm.close() + if (!indefiniteListenerComplete.get()) { + fail("Indefinite progress listener did not report complete.") + } + if (!currentChangesListenerComplete.get()) { + fail("Current changes progress listener did not report complete.") + } + looperThread.testComplete() + } + + override fun onError(exception: Throwable) { + fail(exception.toString()) + } + }) + looperThread.keepStrongReference(task) + } + @Test fun uploadListener_keepIncreasingInSize() { val config = createSyncConfig() diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSchemeMigration.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSchemeMigration.kt new file mode 100644 index 0000000000..018288a99c --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSchemeMigration.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm.entities + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmField +import org.bson.types.ObjectId + +open class SyncSchemeMigration : RealmObject() { + + companion object { + const val CLASS_NAME = "SyncSchemeMigration" + } + + @PrimaryKey + @RealmField(name = "_id") + var id = ObjectId() + +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncSessionExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncSessionExt.kt index 872d230255..399a9f9a6a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncSessionExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncSessionExt.kt @@ -20,3 +20,7 @@ package io.realm.mongodb.sync fun SyncSession.testClose() { this.close() } + +fun SyncSession.testShutdownAndWait() { + this.shutdownAndWait() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp index e814e8ebc3..71892e677e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp @@ -323,3 +323,14 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStop(JNIEnv* } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeShutdownAndWait (JNIEnv* env, jclass, jstring j_local_realm_path) { + try { + JStringAccessor local_realm_path(env, j_local_realm_path); + auto session = SyncManager::shared().get_existing_session(local_realm_path); + if (session) { + session->shutdown_and_wait(); + } + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index ea0379494e..1ece2260e2 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -243,7 +243,7 @@ public String getPath() { * * @return {@code true} if the Realm file exists, {@code false} otherwise. */ - boolean realmExists() { + protected boolean realmExists() { return new File(canonicalPath).exists(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index 370ea7db46..8494dbad6d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -8,8 +8,8 @@ import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.realm.mongodb.log.obfuscator.HttpLogObfuscator; import io.realm.internal.objectstore.OsJavaNetworkTransport; +import io.realm.mongodb.AppConfiguration; import io.realm.mongodb.AppException; import io.realm.mongodb.ErrorCode; import io.realm.mongodb.log.obfuscator.HttpLogObfuscator; @@ -38,6 +38,22 @@ public OkHttpNetworkTransport(@Nullable HttpLogObfuscator httpLogObfuscator) { private okhttp3.Request makeRequest(String method, String url, Map headers, String body){ okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(url); + // TODO Ensure that we have correct custom headers until OS handles it + // first of all add all custom headers + for (Map.Entry entry : getCustomRequestHeaders().entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + // and then replace default authorization header with custom one if present + String authorizationHeaderValue = headers.get(AppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME); + String authorizationHeaderName = getAuthorizationHeaderName(); + if (authorizationHeaderValue != null && !AppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME.equals(authorizationHeaderName)) { + headers.remove(AppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME); + headers.put(authorizationHeaderName, authorizationHeaderValue); + } + + for (Map.Entry entry : headers.entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } switch (method) { case "get": builder.get(); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index 1f21bfa9e9..2bbeb42baa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -432,6 +432,11 @@ public BsonValue getPartitionValue() { return partitionValue; } + @Override + protected boolean realmExists() { + return super.realmExists(); + } + /** * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 4e1e66c3ad..695edeb8aa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -635,6 +635,10 @@ private void checkTimeout(long timeout, TimeUnit unit) { } } + void shutdownAndWait() { + nativeShutdownAndWait(configuration.getPath()); + } + /** * Interface used to report any session errors. * @@ -758,4 +762,5 @@ public void throwExceptionIfNeeded() { private static native byte nativeGetConnectionState(String localRealmPath); private static native void nativeStart(String localRealmPath); private static native void nativeStop(String localRealmPath); + private static native void nativeShutdownAndWait(String localRealmPath); } diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java deleted file mode 100644 index 13bed56a5f..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ /dev/null @@ -1,546 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * 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. - */ - -package io.realm; - -import android.os.SystemClock; -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.realm.entities.AllTypes; -import io.realm.entities.StringOnly; -import io.realm.exceptions.DownloadingRealmInterruptedException; -import io.realm.exceptions.RealmMigrationNeededException; -import io.realm.internal.OsRealmConfig; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; -import io.realm.log.RealmLogger; -import io.realm.objectserver.utils.Constants; -import io.realm.rule.RunTestInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - - -/** - * Catch all class for tests that not naturally fit anywhere else. - */ -@RunWith(AndroidJUnit4.class) -public class SyncedRealmIntegrationTests extends StandardIntegrationTest { - - @Test - @RunTestInLooperThread - public void loginLogoutResumeSyncing() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - - Realm realm = Realm.getInstance(config); - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - SyncManager.getSession(config).uploadAllLocalChanges(); - user.logOut(); - realm.close(); - try { - assertTrue(Realm.deleteRealm(config)); - } catch (IllegalStateException e) { - // FIXME: We don't have a way to ensure that the Realm instance on client thread has been - // closed for now https://github.com/realm/realm-java/issues/5416 - if (e.getMessage().contains("It's not allowed to delete the file")) { - // retry after 1 second - SystemClock.sleep(1000); - assertTrue(Realm.deleteRealm(config)); - } - } - - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); - SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - - Realm realm2 = Realm.getInstance(config2); - SyncManager.getSession(config2).downloadAllServerChanges(); - realm2.refresh(); - assertEquals(1, realm2.where(StringOnly.class).count()); - realm2.close(); - looperThread.testComplete(); - } - - @Test - @UiThreadTest - public void waitForInitialRemoteData_mainThreadThrows() { - final SyncUser user = SyncTestUtils.createTestUser(Constants.AUTH_URL); - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .waitForInitialRemoteData() - .build(); - - Realm realm = null; - try { - realm = Realm.getInstance(config); - fail(); - } catch (IllegalStateException ignore) { - } finally { - if (realm != null) { - realm.close(); - } - } - } - - @Test - public void waitForInitialRemoteData() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - Realm realm = Realm.getInstance(configOld); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo" + i); - } - } - }); - SyncManager.getSession(configOld).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with - // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .build(); - - realm = Realm.getInstance(config); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo 1" + i); - } - } - }); - try { - assertEquals(20, realm.where(StringOnly.class).count()); - } finally { - realm.close(); - } - } - - // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that - // We cannot do much better since we cannot control the order of events internally in Realm which would be - // needed to correctly test all error paths. - @Test - @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + - " https://github.com/realm/realm-java/issues/5416") - public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .waitForInitialRemoteData() - .build(); - - for (int i = 0; i < 10; i++) { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - Realm realm = null; - try { - // This will cause the download latch called later to immediately throw an InterruptedException. - Thread.currentThread().interrupt(); - realm = Realm.getInstance(config); - } catch (DownloadingRealmInterruptedException ignored) { - assertFalse(new File(config.getPath()).exists()); - } finally { - if (realm != null) { - realm.close(); - Realm.deleteRealm(config); - } - } - } - }); - t.start(); - t.join(); - } - } - - // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that - // We cannot do much better since we cannot control the order of events internally in Realm which would be - // needed to correctly test all error paths. - @Test - @RunTestInLooperThread - @Ignore("See https://github.com/realm/realm-java/issues/5373") - public void waitForInitialData_resilientInCaseOfRetriesAsync() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .directory(configurationFactory.getRoot()) - .waitForInitialRemoteData() - .build(); - Random randomizer = new Random(); - - for (int i = 0; i < 10; i++) { - RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { - @Override - public void onSuccess(Realm realm) { - fail(); - } - - @Override - public void onError(Throwable exception) { - fail(exception.toString()); - } - }); - SystemClock.sleep(randomizer.nextInt(5)); - task.cancel(); - } - looperThread.testComplete(); - } - - @Test - public void waitForInitialRemoteData_readOnlyTrue() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - Realm realm = Realm.getInstance(configOld); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo" + i); - } - } - }); - SyncManager.getSession(configOld).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); - final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .name("newRealm") - .waitForInitialRemoteData() - .readOnly() - .schema(StringOnly.class) - .build(); - assertFalse(configNew.realmExists()); - - realm = Realm.getInstance(configNew); - assertEquals(10, realm.where(StringOnly.class).count()); - realm.close(); - user.logOut(); - } - - @Test - public void waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .waitForInitialRemoteData() - .readOnly() - .schema(StringOnly.class) - .build(); - assertFalse(configNew.realmExists()); - - Realm realm = null; - try { - // This will fail, because the server Realm is completely empty and the Client is not allowed to write the - // schema. - realm = Realm.getInstance(configNew); - fail(); - } catch (RealmMigrationNeededException ignore) { - } finally { - if (realm != null) { - realm.close(); - } - user.logOut(); - } - } - - @Test - public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema - .schema(StringOnly.class) // This schema should be written when opening the empty Realm. - .schemaVersion(2) - .build(); - assertFalse(config.realmExists()); - - Realm realm = Realm.getInstance(config); - try { - assertEquals(0, realm.where(StringOnly.class).count()); - } finally { - realm.close(); - user.logOut(); - } - } - - @Ignore("FIXME: Re-enable this once we can test againt a proper Stitch server") - @Test - public void defaultRealm() throws InterruptedException { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncConfiguration config = user.getDefaultConfiguration(); - Realm realm = Realm.getInstance(config); - SyncManager.getSession(config).downloadAllServerChanges(); - realm.refresh(); - - try { - assertTrue(realm.isEmpty()); - } finally { - realm.close(); - user.logOut(); - } - } - - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - @Test - @RunTestInLooperThread - public void javaRequestCustomHeaders() { - SyncManager.addCustomRequestHeader("Foo", "bar"); - SyncManager.setAuthorizationHeaderName("RealmAuth"); - runJavaRequestCustomHeadersTest(); - } - - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - @Test - @RunTestInLooperThread - public void javaRequestCustomHeaders_specificHost() { - SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST); - SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST); - runJavaRequestCustomHeadersTest(); - } - - private void runJavaRequestCustomHeadersTest() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); - - AtomicBoolean headerSet = new AtomicBoolean(false); - RealmLog.setLevel(LogLevel.ALL); - RealmLogger logger = (level, tag, throwable, message) -> { - if (level == LogLevel.TRACE - && message.contains("Foo: bar") - && message.contains("RealmAuth: ")) { - headerSet.set(true); - } - }; - looperThread.runAfterTest(() -> { - RealmLog.remove(logger); - }); - RealmLog.add(logger); - - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - try { - user.changePassword("foo"); - } catch (ObjectServerError e) { - if (e.getErrorCode() != ErrorCode.INVALID_CREDENTIALS) { - throw e; - } - } - - assertTrue(headerSet.get()); - looperThread.testComplete(); - } - - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. - @Test - @RunTestInLooperThread - public void syncAuthHeaderAndUrlPrefix() { - SyncManager.setAuthorizationHeaderName("TestAuth"); - SyncManager.addCustomRequestHeader("Test", "test"); - runSyncAuthHeadersAndUrlPrefixTest(); - } - - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. - @Test - @RunTestInLooperThread - public void syncAuthHeaderAndUrlPrefix_specificHost() { - SyncManager.setAuthorizationHeaderName("TestAuth", Constants.HOST); - SyncManager.addCustomRequestHeader("Test", "test", Constants.HOST); - runSyncAuthHeadersAndUrlPrefixTest(); - } - - private void runSyncAuthHeadersAndUrlPrefixTest() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .urlPrefix("/foo") - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - RealmLog.error(error.toString()); - } - }) - .build(); - - RealmLog.setLevel(LogLevel.ALL); - RealmLogger logger = (level, tag, throwable, message) -> { - if (tag.equals("REALM_SYNC") - && message.contains("GET /foo/") - && message.contains("TestAuth: Realm-Access-Token version=1") - && message.contains("Test: test")) { - looperThread.testComplete(); - } - }; - looperThread.runAfterTest(() -> { - RealmLog.remove(logger); - }); - RealmLog.add(logger); - Realm realm = Realm.getInstance(config); - looperThread.closeAfterTest(realm); - } - - @Test - @RunTestInLooperThread - public void progressListenersWorkWhenUsingWaitForInitialRemoteData() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - Realm realm = Realm.getInstance(configOld); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo" + i); - } - } - }); - SyncManager.getSession(configOld).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - assertTrue(SyncManager.getAllSessions(user).isEmpty()); - - // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with - // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .build(); - assertFalse(config.realmExists()); - AtomicBoolean indefineteListenerComplete = new AtomicBoolean(false); - AtomicBoolean currentChangesListenerComplete = new AtomicBoolean(false); - RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { - - @Override - public void onSuccess(Realm realm) { - realm.close(); - if (!indefineteListenerComplete.get()) { - fail("Indefinete progress listener did not report complete."); - } - if (!currentChangesListenerComplete.get()) { - fail("Current changes progress listener did not report complete."); - } - looperThread.testComplete(); - } - - @Override - public void onError(Throwable exception) { - fail(exception.toString()); - } - }); - looperThread.keepStrongReference(task); - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - indefineteListenerComplete.set(true); - } - } - }); - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - currentChangesListenerComplete.set(true); - } - } - }); - } - - // Smoke test to check that `refreshConnections` doesn't crash. - // Testing that it actually works is not feasible in a unit test. - @Test - @RunTestInLooperThread - public void refreshConnections() { - RealmLog.setLevel(LogLevel.DEBUG); - SyncManager.refreshConnections(); // No Realms - - // A single active Realm - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .fullSynchronization() - .schema(StringOnly.class) - .build(); - Realm realm = Realm.getInstance(config); - SyncManager.refreshConnections(); - - // A single logged out Realm - realm.close(); - SyncManager.refreshConnections(); - looperThread.testComplete(); - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 225932df5a..124d897993 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -191,28 +191,6 @@ class SyncSessionTests { } } - @Test - // FIXME Differentiate path for Realms with different partition values - @Ignore("Partition value does not generate different paths") - fun differentPathsForDifferentPartitionValues() { - val syncConfiguration1 = configFactory - .createSyncConfigurationBuilder(user, BsonString("partitionvalue1")) - .modules(DefaultSyncSchema()) - - .build() - val syncConfiguration2 = configFactory - .createSyncConfigurationBuilder(user, BsonString("partitionvalue2")) - .modules(DefaultSyncSchema()) - - .build() - Realm.getInstance(syncConfiguration1).use { realm1 -> - Realm.getInstance(syncConfiguration2).use { realm2 -> - assertNotEquals(realm1, realm2) - assertNotEquals(realm1.path, realm2.path) - } - } - } - @Test(timeout = 3000) fun getState_active() { Realm.getInstance(syncConfiguration).use { realm -> @@ -401,8 +379,6 @@ class SyncSessionTests { // check that logging out a SyncUser used by different Realm will // affect all associated sessions. @Test(timeout = 5000) - // FIXME Differentiate path for Realms with different partition values, see differentPathsForDifferentPartitionValues - @Ignore("Partition value does not generate different paths") fun logout_sameSyncUserMultipleSessions() { Realm.getInstance(syncConfiguration).use { realm1 -> // New partitionValue to differentiate sync session @@ -435,7 +411,6 @@ class SyncSessionTests { app.login(Credentials.emailPassword(user.profile.email!!, SECRET_PASSWORD)) // reviving the sessions. The state could be changed concurrently. - // FIXME Reavaluate with new sync states assertTrue( //session1.state == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || session1.state == SyncSession.State.ACTIVE) @@ -448,12 +423,9 @@ class SyncSessionTests { // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. @Test - // FIXME Investigate further - // FIXME Rewrite to use BlockingLooperThread - @Ignore("Re-logging in does not authorize") fun logBackResumeUpload() { val config1 = configFactory - .createSyncConfigurationBuilder(user) + .createSyncConfigurationBuilder(user, UUID.randomUUID().toString()) .modules(SyncStringOnlyModule()) .waitForInitialRemoteData() .build() @@ -473,11 +445,9 @@ class SyncSessionTests { val allResults = AtomicReference>() // notifier could be GC'ed before it get a chance to trigger the second commit, so declaring it outside the Runnable handler.post { // access the Realm from an different path on the device (using admin user), then monitor // when the offline commits get synchronized - // FIXME Do we somehow need to extract the refreshtoken...and could it be the reason for app.login not working later on val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) .modules(SyncStringOnlyModule()) - .waitForInitialRemoteData() .build() val realm2 = Realm.getInstance(config2) @@ -514,15 +484,13 @@ class SyncSessionTests { // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. // this test validate the behaviour of SyncSessionStopPolicy::AfterChangesUploaded @Test - // FIXME Investigate why it does not terminate...probably rewrite to BlockingLooperThread - @Ignore("Does not terminate") - fun uploadChangesWhenRealmOutOfScope() { + fun uploadChangesWhenRealmOutOfScope() = looperThread.runBlocking { val strongRefs: MutableList = ArrayList() val chars = CharArray(1000000) // 2MB Arrays.fill(chars, '.') val twoMBString = String(chars) val config1 = configFactory - .createSyncConfigurationBuilder(user) + .createSyncConfigurationBuilder(user, UUID.randomUUID().toString()) .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED) .modules(SyncStringOnlyModule()) .build() @@ -545,40 +513,37 @@ class SyncSessionTests { val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) .modules(SyncStringOnlyModule()) .build() - Realm.getInstance(config2).use { realm2 -> - val all = realm2.where(SyncStringOnly::class.java).findAll() - if (all.size == 5) { - realm2.close() - testCompleted.countDown() - handlerThread.quit() - } else { - strongRefs.add(all) - val realmChangeListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet? -> - if (results.size == 5) { - realm2.close() - testCompleted.countDown() - handlerThread.quit() - } + val realm2 = Realm.getInstance(config2) + val all = realm2.where(SyncStringOnly::class.java).findAll() + if (all.size == 5) { + realm2.close() + testCompleted.countDown() + handlerThread.quit() + } else { + strongRefs.add(all) + val realmChangeListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet? -> + if (results.size == 5) { + realm2.close() + testCompleted.countDown() + handlerThread.quit() } - all.addChangeListener(realmChangeListener) } + all.addChangeListener(realmChangeListener) } - handlerThread.quit() } TestHelper.awaitOrFail(testCompleted, TestHelper.STANDARD_WAIT_SECS) handlerThread.join() user.logOut() + looperThread.testComplete() } // A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. @Test - // FIXME Investigate why it does not terminate...probably rewrite to BlockingLooperThread - @Ignore("Does not terminate") fun downloadChangesWhenRealmOutOfScope() { val uniqueName = UUID.randomUUID().toString() app.emailPassword.registerUser(uniqueName, "password") val config1 = configFactory - .createSyncConfigurationBuilder(user) + .createSyncConfigurationBuilder(user, UUID.randomUUID().toString()) .modules(SyncStringOnlyModule()) .build() Realm.getInstance(config1).use { realm -> @@ -595,13 +560,13 @@ class SyncSessionTests { val credentials = Credentials.emailPassword(user.profile.email!!, SECRET_PASSWORD) app.login(credentials) - // now let the admin upload some commits + // Write updates from a different user val backgroundUpload = CountDownLatch(1) val handlerThread = HandlerThread("HandlerThread") handlerThread.start() val looper = handlerThread.looper val handler = Handler(looper) - handler.post { // using an admin user to open the Realm on different path on the device then some commits + handler.post { // Using a different user to open the Realm on different path on the device then some commits val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) .modules(SyncStringOnlyModule()) @@ -742,7 +707,8 @@ class SyncSessionTests { @Test // FIXME Investigate @Ignore("Asserts with no_session when tearing down, meaning that all session are not " + - "closed, but realm seems to be closed, so further investigation is needed") + "closed, but realm seems to be closed, so further investigation is needed " + + "seems to be caused by https://github.com/realm/realm-java/issues/5416") fun waitForInitialRemoteData_throwsOnTimeout() = looperThread.runBlocking { val syncConfiguration = configFactory .createSyncConfigurationBuilder(user) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt new file mode 100644 index 0000000000..b084b35b2e --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -0,0 +1,333 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import android.os.SystemClock +import androidx.test.annotation.UiThreadTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.entities.DefaultSyncSchema +import io.realm.entities.StringOnly +import io.realm.entities.SyncSchemeMigration +import io.realm.entities.SyncStringOnly +import io.realm.exceptions.DownloadingRealmInterruptedException +import io.realm.exceptions.RealmMigrationNeededException +import io.realm.internal.OsRealmConfig +import io.realm.kotlin.syncSession +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.mongodb.* +import io.realm.mongodb.sync.* +import io.realm.objectserver.utils.Constants +import io.realm.rule.BlockingLooperThread +import org.bson.BsonObjectId +import org.bson.types.ObjectId +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +private const val SECRET_PASSWORD = "123456" + +/** + * Catch all class for tests that not naturally fit anywhere else. + */ +@RunWith(AndroidJUnit4::class) +class SyncedRealmIntegrationTests { + + private val looperThread = BlockingLooperThread() + + private lateinit var app: App + private lateinit var user: User + private lateinit var syncConfiguration: SyncConfiguration + + private val configurationFactory: TestSyncConfigurationFactory = TestSyncConfigurationFactory() + + @Before + fun setup() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + RealmLog.setLevel(LogLevel.ALL) + app = TestApp() + user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + syncConfiguration = configurationFactory + // TODO We generate new partition value for each test to avoid overlaps in data. We + // could make test booting with a cleaner state by somehow flushing data between + // tests. + .createSyncConfigurationBuilder(user, BsonObjectId(ObjectId())) + .modules(DefaultSyncSchema()) + .build() + } + + @After + fun teardown() { + if (this::app.isInitialized) { + app.close() + } + RealmLog.setLevel(LogLevel.WARN) + } + + @Test + fun loginLogoutResumeSyncing() = looperThread.runBlocking { + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + Realm.getInstance(config).use { realm -> + realm.executeTransaction { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo" + } + realm.syncSession.uploadAllLocalChanges() + user.logOut() + } + try { + assertTrue(Realm.deleteRealm(config)) + } catch (e: IllegalStateException) { + // TODO: We don't have a way to ensure that the Realm instance on client thread has been + // closed for now https://github.com/realm/realm-java/issues/5416 + if (e.message!!.contains("It's not allowed to delete the file")) { + // retry after 1 second + SystemClock.sleep(1000) + assertTrue(Realm.deleteRealm(config)) + } + } + + user = app.login(Credentials.emailPassword(user.profile.email, SECRET_PASSWORD)) + val config2: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .build() + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges() + realm.refresh() + assertEquals(1, realm.where(SyncStringOnly::class.java).count()) + } + looperThread.testComplete() + } + + @Test + @UiThreadTest + fun waitForInitialRemoteData_mainThreadThrows() { + val user: User = SyncTestUtils.createTestUser(app) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .waitForInitialRemoteData() + .build() + assertFailsWith { + Realm.getInstance(config).close() + } + } + + @Test + fun waitForInitialRemoteData() { + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + Realm.getInstance(configOld).use { realm -> + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" + } + } + realm.syncSession.uploadAllLocalChanges() + } + user.logOut() + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + // Use different user to trigger different path + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, user.id) + .testSchema(SyncStringOnly::class.java) + .waitForInitialRemoteData() + .build() + Realm.getInstance(config).use { realm -> + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo 1$i" + } + } + assertEquals(20, realm.where(SyncStringOnly::class.java).count()) + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + + " https://github.com/realm/realm-java/issues/5416") + fun waitForInitialData_resilientInCaseOfRetries() = looperThread.runBlocking { + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .waitForInitialRemoteData() + .build() + for (i in 0..9) { + val blockingLooperThread = BlockingLooperThread() + blockingLooperThread.runDetached { + var realm: Realm? = null + try { + Thread.currentThread().interrupt() + Realm.getInstance(config).close() + } catch (e: DownloadingRealmInterruptedException) { + } + // TODO: We don't have a way to ensure that the Realm instance on client thread has been + // closed for now https://github.com/realm/realm-java/issues/5416 + app.sync.getSession(config).testShutdownAndWait() + try { + Realm.deleteRealm(config) + } catch (e: IllegalStateException) { + if (e.message!!.contains("It's not allowed to delete the file")) { + // retry after 1 second + SystemClock.sleep(1000) + assertTrue(Realm.deleteRealm(config)) + } + } + blockingLooperThread.testComplete() + }.await() + } + looperThread.testComplete() + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + fun waitForInitialData_resilientInCaseOfRetriesAsync() = looperThread.runBlocking { + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .waitForInitialRemoteData() + .build() + val randomizer = Random() + for (i in 0..9) { + val task = Realm.getInstanceAsync(config, object : Realm.Callback() { + override fun onSuccess(realm: Realm) { fail() } + override fun onError(exception: Throwable) { fail(exception.toString()) } + }) + SystemClock.sleep(randomizer.nextInt(5).toLong()) + task.cancel() + } + // Leave some time for the async callbacks to actually get through + looperThread.postRunnableDelayed( + Runnable { looperThread.testComplete() }, + 1000 + ) + } + + @Test + fun waitForInitialRemoteData_readOnlyTrue() { + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .build() + Realm.getInstance(configOld).use { realm -> + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" + } + } + realm.syncSession.uploadAllLocalChanges() + } + user.logOut() + + // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should + // download the uploaded changes (pray it managed to do so within the time frame). + // Use different user to trigger different path + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, user.id) + .waitForInitialRemoteData() + .readOnly() + .testSchema(SyncStringOnly::class.java) + .build() + assertFalse(configNew.testRealmExists()) + Realm.getInstance(configNew).use { realm -> + assertEquals(10, realm.where(SyncStringOnly::class.java).count()) + } + user.logOut() + } + + @Test + fun waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { + val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .waitForInitialRemoteData() + .readOnly() + .testSchema(SyncSchemeMigration::class.java) + .build() + assertFalse(configNew.testRealmExists()) + assertFailsWith { + Realm.getInstance(configNew).use { realm -> + realm.executeTransaction { + it.createObject(SyncSchemeMigration::class.java, ObjectId()) + } + } + } + user.logOut() + } + + @Test + fun waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema + .testSchema(SyncStringOnly::class.java) // This schema should be written when opening the empty Realm. + .schemaVersion(2) + .build() + assertFalse(config.testRealmExists()) + Realm.getInstance(config).use { realm -> + assertEquals(0, realm.where(SyncStringOnly::class.java).count()) + } + user.logOut() + } + + @Test + fun defaultRealm() { + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, user.id) + Realm.getInstance(config).use { realm -> + realm.syncSession.downloadAllServerChanges() + realm.refresh() + assertTrue(realm.isEmpty) + } + user.logOut() + } + + + // Smoke test to check that `refreshConnections` doesn't crash. + // Testing that it actually works is not feasible in a unit test. + @Test + fun refreshConnections() = looperThread.runBlocking { + RealmLog.setLevel(LogLevel.DEBUG) + Sync.reconnect() // No Realms + + // A single active Realm + val username = UUID.randomUUID().toString() + val password = "password" + val user: User = app.registerUserAndLogin(username, password) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .testSchema(StringOnly::class.java) + .build() + val realm = Realm.getInstance(config) + Sync.reconnect() + + // A single logged out Realm + realm.close() + Sync.reconnect() + looperThread.testComplete() + } + +} diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt index a39443d57a..393eefe64b 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt @@ -31,8 +31,8 @@ const val DATABASE_NAME = "test_data" // same as above class TestApp( networkTransport: OsJavaNetworkTransport? = null, - customizeConfig: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it } -) : App(createConfiguration(customizeConfig)) { + builder: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it } +) : App(builder(configurationBuilder()).build()) { init { if (networkTransport != null) { @@ -42,16 +42,12 @@ class TestApp( companion object { - fun createConfiguration(customizeConfig: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it }): AppConfiguration { - var builder = AppConfiguration.Builder(initializeMongoDbRealm()) + fun configurationBuilder(customizeConfig: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it }): AppConfiguration.Builder { + return AppConfiguration.Builder(initializeMongoDbRealm()) .baseUrl("http://127.0.0.1:9090") .appName("MongoDB Realm Integration Tests") .appVersion("1.0.") .httpLogObfuscator(null) - - builder = customizeConfig(builder) - - return builder.build() } // Initializes MongoDB Realm. Clears all local state and fetches the application ID. diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt index 1b03689832..f098e1228f 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt @@ -35,6 +35,11 @@ class TestSyncConfigurationFactory : TestRealmConfigurationFactory() { .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) } + fun createSyncConfigurationBuilder(user: User, partitionValue: String): SyncConfiguration.Builder { + return SyncConfiguration.Builder(user, partitionValue) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + } + fun createSyncConfigurationBuilder(user: User, partitionValue: BsonValue): SyncConfiguration.Builder { return SyncConfigurationExt.Builder(user, partitionValue) .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY); diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt index ceeeb7b785..43b8dae7ce 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt @@ -25,6 +25,10 @@ class SyncConfigurationExt { companion object } +fun SyncConfiguration.testRealmExists(): Boolean{ + return this.realmExists() +} + // Added to expose Builder(User, BsonValue) outside io.realm.mongodb.sync package for test fun SyncConfigurationExt.Companion.Builder(user: User, partitionValue: BsonValue): SyncConfiguration.Builder { return SyncConfiguration.Builder(user, partitionValue) From 1c3aebfac3790a63bd4e25b0c8d97b247a745bd5 Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 15 Sep 2020 13:32:43 +0200 Subject: [PATCH 1672/2110] Fixes concurrent modification exception in ColumnIndices class (#7094) --- CHANGELOG.md | 6 ++++-- .../src/main/java/io/realm/internal/ColumnIndices.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e595340854..acaf772383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ * Better exception messaging for UTF encoding errors. ([Issue #7093](https://github.com/realm/realm-java/pull/7093)) ### Fixes -* None. +* Fixes concurrent modification exceptions in the schema when refreshing a Realm (Issue [#6876](https://github.com/realm/realm-java/issues/6876)) ### Compatibility -* None. +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal * None. diff --git a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java index d90b47c31d..116ec0300a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ColumnIndices.java @@ -21,6 +21,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; @@ -49,7 +50,7 @@ public final class ColumnIndices { // Class to ColumnInfo map private final Map, ColumnInfo> classToColumnInfoMap = - new HashMap, ColumnInfo>(); + new ConcurrentHashMap, ColumnInfo>(); // Class name to ColumnInfo map. All the elements in this map should be existing in classToColumnInfoMap. private final Map simpleClassNameToColumnInfoMap = new HashMap(); From b7435b467f0247c16691e09ac870d20e0750bbb8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 16 Sep 2020 11:56:14 +0200 Subject: [PATCH 1673/2110] Add support for multiple apps (#7095) --- CHANGELOG.md | 3 +- Jenkinsfile | 3 +- .../kotlin/io/realm/AppConfigurationTests.kt | 12 +++ .../kotlin/io/realm/AppTests.kt | 72 ++++++++++---- .../kotlin/io/realm/mongodb/AppExt.kt | 1 - .../io/realm/mongodb/sync/SyncedRealmTests.kt | 36 +++++++ .../cpp/io_realm_internal_OsRealmConfig.cpp | 9 +- .../io_realm_internal_objectstore_OsApp.cpp | 20 +++- ..._mongodb_sync_ClientResetRequiredError.cpp | 7 +- .../main/cpp/io_realm_mongodb_sync_Sync.cpp | 25 +++-- .../cpp/io_realm_mongodb_sync_SyncSession.cpp | 62 +++++++----- realm/realm-library/src/main/cpp/object-store | 2 +- .../io/realm/internal/ObjectServerFacade.java | 2 +- .../java/io/realm/internal/OsRealmConfig.java | 4 +- .../internal/SyncObjectServerFacade.java | 24 +++++ .../io/realm/internal/objectstore/OsApp.java | 28 +++--- .../java/io/realm/mongodb/App.java | 56 +++++------ .../io/realm/mongodb/AppConfiguration.java | 41 ++++++++ .../java/io/realm/mongodb/User.java | 8 +- .../sync/ClientResetRequiredError.java | 8 +- .../java/io/realm/mongodb/sync/Sync.java | 32 ++++--- .../io/realm/mongodb/sync/SyncSession.java | 52 +++++----- .../realm/EncryptedSynchronizedRealmTests.kt | 4 +- .../io/realm/SyncedRealmIntegrationTests.kt | 6 +- .../syncTestUtils/kotlin/io/realm/TestApp.kt | 13 ++- .../mongodb-realm-command-server.js | 14 +-- tools/sync_test_server/setup_mongodb_realm.sh | 96 +++++++++++-------- tools/sync_test_server/start_server.sh | 3 +- 28 files changed, 429 insertions(+), 214 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac3a84add7..9a6ac04a64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * [RealmApp] Removed support for `User.getLocalId()`. ### Enhancements +* [RealmApp] It is now possible to create App instances with different app id's. * [RealmApp] Support for using `null` as a partition value. * [RealmApp] Improve errors exception messages from `SyncSession.downloadAllServerChanges()` and `SyncSession.uploadAllLocalChanges()`. * Support for watching MongoCollection change streams (Issue [#6912](https://github.com/realm/realm-java/issues/6912)) @@ -39,7 +40,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal -* Updated to Object Store commit: ffda21e28d7dd47793ad2d36394de0328676ca30. +* Updated to Object Store commit: 6ab48d3b4b1e0865f68b84d5993bb2aad910320b. ## 10.0.0-BETA.6 (2020-08-17) diff --git a/Jenkinsfile b/Jenkinsfile index 984fb56280..a719103966 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -83,7 +83,8 @@ try { sh "docker network create ${dockerNetworkId}" mongoDbRealmContainer = mdbRealmImage.run("--network ${dockerNetworkId}") mongoDbRealmCommandServerContainer = commandServerEnv.run("--network container:${mongoDbRealmContainer.id}") - sh "docker cp tools/sync_test_server/app_config ${mongoDbRealmContainer.id}:/tmp/app_config" + sh "docker cp tools/sync_test_server/app_config ${mongoDbRealmContainer.id}:/tmp/app_config-testapp1" + sh "docker cp tools/sync_test_server/app_config ${mongoDbRealmContainer.id}:/tmp/app_config-testapp2" sh "docker cp tools/sync_test_server/setup_mongodb_realm.sh ${mongoDbRealmContainer.id}:/tmp/" sh "docker exec -i ${mongoDbRealmContainer.id} sh /tmp/setup_mongodb_realm.sh" } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index 035076f33c..883f8ad051 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -171,6 +171,12 @@ class AppConfigurationTests { assertEquals("app-name", config.appName) } + @Test + fun appName_defaultValue() { + val config = AppConfiguration.Builder("app-id").build() + assertEquals(null, config.appName) + } + @Test fun appName_invalidValuesThrows() { val builder = AppConfiguration.Builder("app-id") @@ -187,6 +193,12 @@ class AppConfigurationTests { assertEquals("app-version", config.appVersion) } + @Test + fun appVersion_defaultValue() { + val config = AppConfiguration.Builder("app-id").build() + assertEquals(null, config.appVersion) + } + @Test fun appVersion_invalidValuesThrows() { val builder = AppConfiguration.Builder("app-id") diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index da226cc63d..1961dbee67 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -18,16 +18,17 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin +import io.realm.entities.DefaultSyncSchema import io.realm.exceptions.RealmFileException +import io.realm.kotlin.syncSession import io.realm.mongodb.* +import io.realm.mongodb.sync.SyncConfiguration import io.realm.rule.BlockingLooperThread +import org.bson.BsonString import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries -import org.junit.After +import org.junit.* import org.junit.Assert.* -import org.junit.Before -import org.junit.Ignore -import org.junit.Test import org.junit.runner.RunWith import java.io.File import java.util.concurrent.atomic.AtomicReference @@ -36,6 +37,9 @@ import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) class AppTests { + @get:Rule + val configFactory = TestSyncConfigurationFactory() + private val looperThread = BlockingLooperThread() private lateinit var app: TestApp private lateinit var admin: ServerAdmin @@ -269,29 +273,55 @@ class AppTests { @Test() fun encryption() { - // Remove the App instance created on setUp() because we need to create - // a custom one and only one App instance is allowed - tearDown() - val context = InstrumentationRegistry.getInstrumentation().targetContext - // Setup an App instance with a random encryption key - Realm.init(context) - app = TestApp(builder = { + // Create new test app with a random encryption key + val testApp = TestApp(appName = TEST_APP_2, builder = { it.encryptionKey(TestHelper.getRandomKey()) }) - val metadataDir = File(context.filesDir, "mongodb-realm/server-utility/metadata/") - val config = RealmConfiguration.Builder() - .name("sync_metadata.realm") - .directory(metadataDir) - .build() - - assertTrue(File(config.path).exists()) + try { + // Create Realm in order to create the sync metadata Realm + var user = testApp.login(Credentials.anonymous()) + val syncConfig = SyncConfiguration.defaultConfig(user, "foo") + Realm.getInstance(syncConfig).close() + + // Create a configuration pointing to the metadata Realm for that app + val metadataDir = File(context.filesDir, "mongodb-realm/${testApp.configuration.appId}/server-utility/metadata/") + val config = RealmConfiguration.Builder() + .name("sync_metadata.realm") + .directory(metadataDir) + .build() + assertTrue(File(config.path).exists()) + + // Open the metadata realm file without a valid encryption key + assertFailsWith { + DynamicRealm.getInstance(config) + } + } finally { + testApp.close() + } + } - // Open the metadata realm file without a valid encryption key - assertFailsWith { - DynamicRealm.getInstance(config) + // Check that it is possible to have two Java instances of an App class, but they will + // share the underlying App state. + @Test + fun multipleInstancesSameApp() { + // Create a second copy of the test app + val app2 = TestApp() + try { + // User handling are shared between each app + val user = app.login(Credentials.anonymous()); + assertEquals(user, app2.currentUser()) + assertEquals(user, app.allUsers().values.first()) + assertEquals(user, app2.allUsers().values.first()) + + user.logOut(); + + assertNull(app.currentUser()) + assertNull(app2.currentUser()) + } finally { + app2.close() } } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt index e0fc7ace62..d49c8a4744 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/AppExt.kt @@ -30,7 +30,6 @@ fun App.close() { ServerAdmin(this).deleteAllUsers() this.syncManager.testReset() this.osApp.networkTransport.resetHeaders() - App.CREATED = false RealmExt.testClearApplicationContext() } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index eb5438ab52..3eb9bb56c7 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -32,7 +32,9 @@ import io.realm.mongodb.SyncTestUtils.Companion.createTestUser import io.realm.mongodb.User import io.realm.mongodb.close import org.bson.BsonNull +import org.bson.BsonString import org.junit.* +import org.junit.Assert.assertNotEquals import org.junit.runner.RunWith import java.io.File import java.util.* @@ -467,6 +469,40 @@ class SyncedRealmTests { } } + // Check that we can create multiple apps that synchronize with each other + @Test + fun multipleAppsCanSync() { + val app2 = TestApp(appName = TEST_APP_2) + var realm1: Realm? = null + var realm2: Realm? = null + try { + // Login users on both Realms + val app1User = app.login(Credentials.anonymous()) + val app2User = app2.login(Credentials.anonymous()) + assertNotEquals(app1User, app2User) + + // Create one Realm against each app + val config1 = configFactory.createSyncConfigurationBuilder(app1User, BsonString("foo")) + .modules(DefaultSyncSchema()) + .build() + val config2 = configFactory.createSyncConfigurationBuilder(app2User, BsonString("foo")) + .modules(DefaultSyncSchema()) + .build() + + // Make sure we can synchronize changes + realm1 = Realm.getInstance(config1) + realm2 = Realm.getInstance(config2) + realm1.syncSession.downloadAllServerChanges() + realm2.syncSession.downloadAllServerChanges() + Assert.assertTrue(realm1.isEmpty) + Assert.assertTrue(realm2.isEmpty) + } finally { + realm1?.close() + realm2?.close() + app2.close() + } + } + @Test // FIXME Missing test, maybe fitting better in SyncSessionTest.kt...when migrated @Ignore("Not implemented yet") diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 8799a744c1..37993bacff 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -245,12 +245,13 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsRealmConfig_nativeEnableChangeNo #if REALM_ENABLE_SYNC JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSetSyncConfig( - JNIEnv* env, jclass, jlong native_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, + JNIEnv* env, jclass, jlong j_app_ptr, jlong j_config_ptr, jstring j_sync_realm_url, jstring j_auth_url, jstring j_user_id, jstring j_refresh_token, jstring j_access_token, jstring j_device_id, jbyte j_session_stop_policy, jstring j_url_prefix, jstring j_custom_auth_header_name, jobjectArray j_custom_headers_array, jbyte j_client_reset_mode, jstring j_partion_key_value, jobject j_java_sync_service) { - auto& config = *reinterpret_cast(native_ptr); + auto app = *reinterpret_cast*>(j_app_ptr); + auto& config = *reinterpret_cast(j_config_ptr); // sync_config should only be initialized once! REALM_ASSERT(!config.sync_config); @@ -320,13 +321,13 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_OsRealmConfig_nativeCreateAndSe // Get logged in user JStringAccessor user_id(env, j_user_id); JStringAccessor auth_url(env, j_auth_url); - std::shared_ptr user = SyncManager::shared().get_existing_logged_in_user(user_id); + std::shared_ptr user = app->sync_manager()->get_existing_logged_in_user(user_id); if (!user) { JStringAccessor realm_auth_url(env, j_auth_url); JStringAccessor refresh_token(env, j_refresh_token); JStringAccessor access_token(env, j_access_token); JStringAccessor device_id(env, j_device_id); - user = SyncManager::shared().get_user(user_id, auth_url, refresh_token, access_token, device_id); + user = app->sync_manager()->get_user(user_id, auth_url, refresh_token, access_token, device_id); } SyncSessionStopPolicy session_stop_policy = static_cast(j_session_stop_policy); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp index 9fc29a1935..b792f3a263 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp @@ -109,6 +109,18 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsApp_nativeCreate(JN jstring j_sdk_version) { try { + + JStringAccessor app_id(env, j_app_id); + + // Check if we already have a cached instance, if yes, return that instead. The Java GC + // will only cleanup the shared pointer, but leave the cached instance alone. This also + // means that no App is never fully closed. This should be safe as App doesn't implement + // Closable in Java, so it doesn't have a a visible lifecycle. + auto cached_app = App::get_cached_app(app_id); + if (cached_app) { + return reinterpret_cast(new std::shared_ptr(cached_app)); + } + // App Config std::function()> transport_generator = [java_app_ref = JavaGlobalRefByCopy(env, obj)] { JNIEnv* env = JniUtils::get_env(true); @@ -117,7 +129,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsApp_nativeCreate(JN return std::unique_ptr(new JavaNetworkTransport(network_transport_impl)); }; - JStringAccessor app_id(env, j_app_id); JStringAccessor base_url(env, j_base_url); JStringAccessor app_name(env, j_app_name); JStringAccessor app_version(env, j_app_version); @@ -155,15 +166,14 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsApp_nativeCreate(JN client_config.custom_encryption_key = encryption_key.transform>(); } - // FIXME: SyncManager is still a singleton. Should be refactored to allow multiple - SyncManager::shared().configure(client_config, app_config); + SharedApp app = App::get_shared_app(app_config, client_config); // Init logger. Must be called after .configure() - SyncManager::shared().set_logger_factory(s_sync_logger_factory); + app->sync_manager()->set_logger_factory(s_sync_logger_factory); // Register Sync Client thread start/stop callback. Must be called after .configure() static AndroidClientListener client_thread_listener(env); g_binding_callback_thread_observer = &client_thread_listener; - return reinterpret_cast(new std::shared_ptr(SyncManager::shared().app())); + return reinterpret_cast(new std::shared_ptr(app)); } CATCH_STD() return 0; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp index aa618745cf..9f55fddc7b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp @@ -16,6 +16,7 @@ #include +#include #include #include "util.hpp" @@ -24,11 +25,13 @@ using namespace realm; JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_ClientResetRequiredError_nativeExecuteClientReset(JNIEnv* env, jobject, - jstring localRealmPath) + jlong j_app_ptr, + jstring localRealmPath) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor local_realm_path(env, localRealmPath); - if (!SyncManager::shared().immediately_run_file_actions(std::string(local_realm_path))) { + if (!app->sync_manager()->immediately_run_file_actions(std::string(local_realm_path))) { ThrowException( env, IllegalState, concat_stringdata("Realm was not configured correctly. Client Reset could not be run for Realm at: ", diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp index fbcbf79b7e..8fb690f522 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp @@ -16,7 +16,8 @@ #include "io_realm_mongodb_sync_Sync.h" -#include +#include +#include #include #include #include @@ -31,23 +32,26 @@ using namespace realm; using namespace realm::jni_util; using namespace realm::util; -JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeReset(JNIEnv* env, jclass) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeReset(JNIEnv* env, jclass, jlong j_app_ptr) { try { - SyncManager::shared().reset_for_testing(); + auto app = *reinterpret_cast*>(j_app_ptr); + app->sync_manager()->reset_for_testing(); + app::App::clear_cached_apps(); } CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeSimulateSyncError(JNIEnv* env, jclass, jstring local_realm_path, +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeSimulateSyncError(JNIEnv* env, jclass, jlong j_app_ptr, jstring local_realm_path, jint err_code, jstring err_message, jboolean is_fatal) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor path(env, local_realm_path); JStringAccessor message(env, err_message); - auto session = SyncManager::shared().get_existing_active_session(path); + auto session = app->sync_manager()->get_existing_active_session(path); if (!session) { ThrowException(env, IllegalArgument, concat_stringdata("Session not found: ", path)); return; @@ -58,10 +62,11 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeSimulateSyncError(J CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeReconnect(JNIEnv* env, jclass) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeReconnect(JNIEnv* env, jclass, jlong j_app_ptr) { try { - SyncManager::shared().reconnect(); + auto app = *reinterpret_cast*>(j_app_ptr); + app->sync_manager()->reconnect(); } CATCH_STD() } @@ -77,6 +82,7 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeCreateSession(JNIEn JNIEXPORT jstring JNICALL Java_io_realm_mongodb_sync_Sync_nativeGetPathForRealm(JNIEnv* env, jclass, + jlong j_app_ptr, jstring j_user_id, jstring j_encoded_partition_value, jstring j_override_filename) @@ -86,8 +92,9 @@ JNIEXPORT jstring JNICALL Java_io_realm_mongodb_sync_Sync_nativeGetPathForRealm( // until the Realm is opened, but the Sync API for creating the Realm path require that // it is created up front. So we cheat and create a SyncConfig with the minimal values // needed for the path to be calculated. + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor user_id(env, j_user_id); - std::shared_ptr user = SyncManager::shared().get_existing_logged_in_user(user_id); + std::shared_ptr user = app->sync_manager()->get_existing_logged_in_user(user_id); if (!user) { throw std::logic_error("User is not logged in"); } @@ -100,7 +107,7 @@ JNIEXPORT jstring JNICALL Java_io_realm_mongodb_sync_Sync_nativeGetPathForRealm( JStringAccessor override_file_name(env, j_override_filename); file_name = std::string(override_file_name); } - return to_jstring(env, SyncManager::shared().path_for_realm(config, file_name)); + return to_jstring(env, app->sync_manager()->path_for_realm(config, file_name)); } CATCH_STD() return nullptr; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp index 71892e677e..42a647e688 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp @@ -19,8 +19,9 @@ #include "io_realm_mongodb_sync_SyncSession.h" -#include "object-store/src/sync/sync_manager.hpp" -#include "object-store/src/sync/sync_session.hpp" +#include "sync/app.hpp" +#include "sync/sync_manager.hpp" +#include "sync/sync_session.hpp" #include "util.hpp" #include "java_class_global_def.hpp" @@ -57,14 +58,16 @@ static_assert(SyncSession::ConnectionState::Connected == ""); JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddProgressListener(JNIEnv* env, jobject j_session_object, - jstring j_local_realm_path, - jlong listener_id, jint direction, - jboolean is_streaming) + jlong j_app_ptr, + jstring j_local_realm_path, + jlong listener_id, jint direction, + jboolean is_streaming) { try { + auto app = *reinterpret_cast*>(j_app_ptr); // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda std::string local_realm_path(JStringAccessor(env, j_local_realm_path)); - std::shared_ptr session = SyncManager::shared().get_existing_session(local_realm_path); + std::shared_ptr session = app->sync_manager()->get_existing_session(local_realm_path); if (!session) { // FIXME: We should lift this restriction ThrowException(env, IllegalState, @@ -106,12 +109,14 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddProgress } JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeRemoveProgressListener(JNIEnv* env, jclass, + jlong j_app_ptr, jstring j_local_realm_path, jlong listener_token) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor local_realm_path(env, j_local_realm_path); - std::shared_ptr session = SyncManager::shared().get_existing_session(local_realm_path); + std::shared_ptr session = app->sync_manager()->get_existing_session(local_realm_path); if (session) { session->unregister_progress_notifier(static_cast(listener_token)); } @@ -121,12 +126,14 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeRemoveProgre JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForDownloadCompletion(JNIEnv* env, jobject session_object, + jlong j_app_ptr, jint callback_id, jstring j_local_realm_path) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor local_realm_path(env, j_local_realm_path); - auto session = SyncManager::shared().get_existing_session(local_realm_path); + auto session = app->sync_manager()->get_existing_session(local_realm_path); if (session) { static JavaClass java_sync_session_class(env, "io/realm/mongodb/sync/SyncSession"); @@ -155,12 +162,14 @@ JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForD JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForUploadCompletion(JNIEnv* env, jobject session_object, + jlong j_app_ptr, jint callback_id, jstring j_local_realm_path) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor local_realm_path(env, j_local_realm_path); - auto session = SyncManager::shared().get_existing_session(local_realm_path); + auto session = app->sync_manager()->get_existing_session(local_realm_path); if (session) { static JavaClass java_sync_session_class(env, "io/realm/mongodb/sync/SyncSession"); @@ -188,11 +197,12 @@ JNIEXPORT jboolean JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeWaitForU } -JNIEXPORT jbyte JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeGetState(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT jbyte JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeGetState(JNIEnv* env, jclass, jlong j_app_ptr, jstring j_local_realm_path) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor local_realm_path(env, j_local_realm_path); - auto session = SyncManager::shared().get_existing_session(local_realm_path); + auto session = app->sync_manager()->get_existing_session(local_realm_path); if (session) { switch (session->state()) { @@ -209,11 +219,12 @@ JNIEXPORT jbyte JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeGetState(JN return -1; } -JNIEXPORT jbyte JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeGetConnectionState(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT jbyte JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeGetConnectionState(JNIEnv* env, jclass, jlong j_app_ptr, jstring j_local_realm_path) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor local_realm_path(env, j_local_realm_path); - auto session = SyncManager::shared().get_existing_session(local_realm_path); + auto session = app->sync_manager()->get_existing_session(local_realm_path); if (session) { switch (session->connection_state()) { @@ -239,12 +250,13 @@ static jlong get_connection_value(SyncSession::ConnectionState state) { return static_cast(-1); } -JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddConnectionListener(JNIEnv* env, jobject j_session_object, jstring j_local_realm_path) +JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddConnectionListener(JNIEnv* env, jobject j_session_object, jlong j_app_ptr, jstring j_local_realm_path) { try { + auto app = *reinterpret_cast*>(j_app_ptr); // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda std::string local_realm_path(JStringAccessor(env, j_local_realm_path)); - std::shared_ptr session = SyncManager::shared().get_existing_session(local_realm_path); + std::shared_ptr session = app->sync_manager()->get_existing_session(local_realm_path); if (!session) { // FIXME: We should lift this restriction ThrowException(env, IllegalState, @@ -282,12 +294,13 @@ JNIEXPORT jlong JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeAddConnecti return 0; } -JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeRemoveConnectionListener(JNIEnv* env, jclass, jlong listener_id, jstring j_local_realm_path) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeRemoveConnectionListener(JNIEnv* env, jclass, jlong j_app_ptr, jlong listener_id, jstring j_local_realm_path) { try { + auto app = *reinterpret_cast*>(j_app_ptr); // JNIEnv is thread confined, so we need a deep copy in order to capture the string in the lambda std::string local_realm_path(JStringAccessor(env, j_local_realm_path)); - std::shared_ptr session = SyncManager::shared().get_existing_session(local_realm_path); + std::shared_ptr session = app->sync_manager()->get_existing_session(local_realm_path); if (session) { session->unregister_connection_change_callback(static_cast(listener_id)); } @@ -295,11 +308,12 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeRemoveConnec CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStart(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStart(JNIEnv* env, jclass, jlong j_app_ptr, jstring j_local_realm_path) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor local_realm_path(env, j_local_realm_path); - auto session = SyncManager::shared().get_existing_session(local_realm_path); + auto session = app->sync_manager()->get_existing_session(local_realm_path); if (!session) { // FIXME: We should lift this restriction ThrowException(env, IllegalState, @@ -312,11 +326,12 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStart(JNIEnv CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStop(JNIEnv* env, jclass, jstring j_local_realm_path) +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStop(JNIEnv* env, jclass, jlong j_app_ptr, jstring j_local_realm_path) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor local_realm_path(env, j_local_realm_path); - auto session = SyncManager::shared().get_existing_session(local_realm_path); + auto session = app->sync_manager()->get_existing_session(local_realm_path); if (session) { session->log_out(); } @@ -324,10 +339,11 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStop(JNIEnv* CATCH_STD() } -JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeShutdownAndWait (JNIEnv* env, jclass, jstring j_local_realm_path) { +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeShutdownAndWait (JNIEnv* env, jclass, jlong j_app_ptr, jstring j_local_realm_path) { try { + auto app = *reinterpret_cast*>(j_app_ptr); JStringAccessor local_realm_path(env, j_local_realm_path); - auto session = SyncManager::shared().get_existing_session(local_realm_path); + auto session = app->sync_manager()->get_existing_session(local_realm_path); if (session) { session->shutdown_and_wait(); } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index d56125d6b3..6ab48d3b4b 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit d56125d6b34a44b96b287a53a18744f06da44890 +Subproject commit 6ab48d3b4b1e0865f68b84d5993bb2aad910320b diff --git a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java index d0796508b0..d4cf6b2b12 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java +++ b/realm/realm-library/src/main/java/io/realm/internal/ObjectServerFacade.java @@ -30,7 +30,7 @@ */ public class ObjectServerFacade { - public static final int SYNC_CONFIG_OPTIONS = 13; + public static final int SYNC_CONFIG_OPTIONS = 14; private static final ObjectServerFacade nonSyncFacade = new ObjectServerFacade(); private static ObjectServerFacade syncFacade = null; diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java index 2d515bc309..1db8728c09 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsRealmConfig.java @@ -222,6 +222,7 @@ private OsRealmConfig(final RealmConfiguration config, Byte clientResyncMode = (Byte) syncConfigurationOptions[j++]; String encodedPartitionValue = (String) syncConfigurationOptions[j++]; Object syncService = syncConfigurationOptions[j++]; + Long appPtr = (Long) syncConfigurationOptions[j++]; // Convert the headers into a String array to make it easier to send through JNI // [key1, value1, key2, value2, ...] @@ -279,6 +280,7 @@ private OsRealmConfig(final RealmConfiguration config, // Set sync config if (syncRealmUrl != null) { String resolvedSyncRealmUrl = nativeCreateAndSetSyncConfig( + appPtr, nativePtr, syncRealmUrl, syncRealmAuthUrl, @@ -384,7 +386,7 @@ private native void nativeSetSchemaConfig(long nativePtr, byte schemaMode, long private static native void nativeEnableChangeNotification(long nativePtr, boolean enableNotification); - private static native String nativeCreateAndSetSyncConfig(long nativePtr, String syncRealmUrl, String authUrl, + private static native String nativeCreateAndSetSyncConfig(long appPtr, long configPtr, String syncRealmUrl, String authUrl, String userId, String refreshToken, String accessToken, String deviceId, byte sessionStopPolicy, String urlPrefix, String customAuthorizationHeaderName, diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index 32c47fccc1..2d5211a76e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -23,11 +23,13 @@ import org.bson.BsonValue; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.TimeUnit; +import io.realm.internal.objectstore.OsApp; import io.realm.mongodb.App; import io.realm.RealmConfiguration; import io.realm.mongodb.AppConfiguration; @@ -50,6 +52,7 @@ public class SyncObjectServerFacade extends ObjectServerFacade { @SuppressLint("StaticFieldLeak") // private static Context applicationContext; private static volatile Method removeSessionMethod; + private static volatile Field osAppField; @Override public void initialize(Context context, String userAgent) { @@ -88,6 +91,26 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { String urlPrefix = syncConfig.getUrlPrefix(); String customAuthorizationHeaderName = app.getConfiguration().getAuthorizationHeaderName(); Map customHeaders = app.getConfiguration().getCustomRequestHeaders(); + long appNativePointer; + + // We cannot get the app native pointer without exposing it in the public API due to + // how our packages are structured. Instead of polluting the API we use reflection to + // access it. + try { + if (osAppField == null) { + synchronized (SyncObjectServerFacade.class) { + if (osAppField == null) { + Field field = App.class.getDeclaredField("osApp"); + field.setAccessible(true); + osAppField = field; + } + } + } + OsApp osApp = (OsApp) osAppField.get(app); + appNativePointer = osApp.getNativePtr(); + } catch (Exception e) { + throw new RuntimeException(e); + } // TODO Simplify. org.bson serialization only allows writing full documents, so the partition // key is embedded in a document with key 'value' and unwrapped in JNI. @@ -120,6 +143,7 @@ public Object[] getSyncConfigurationOptions(RealmConfiguration config) { configObj[i++] = OsRealmConfig.CLIENT_RESYNC_MODE_MANUAL; configObj[i++] = encodedPartitionValue; configObj[i++] = app.getSync(); + configObj[i++] = appNativePointer; return configObj; } else { return new Object[SYNC_CONFIG_OPTIONS]; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsApp.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsApp.java index fd150cd786..770f9c5374 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsApp.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsApp.java @@ -30,19 +30,21 @@ public long getNativeFinalizerPtr() { } public OsApp(AppConfiguration config, String userAgentBindingInfo, String appDefinedUserAgent, String syncDir) { - nativePtr = nativeCreate( - config.getAppId(), - config.getBaseUrl().toString(), - config.getAppName(), - config.getAppVersion(), - config.getRequestTimeoutMs(), - config.getEncryptionKey(), - syncDir, - userAgentBindingInfo, - appDefinedUserAgent, - "android", - android.os.Build.VERSION.RELEASE, - io.realm.BuildConfig.VERSION_NAME); + synchronized (OsApp.class) { // We need to synchronize access as OS caches the App instance + nativePtr = nativeCreate( + config.getAppId(), + config.getBaseUrl().toString(), + config.getAppName(), + config.getAppVersion(), + config.getRequestTimeoutMs(), + config.getEncryptionKey(), + syncDir, + userAgentBindingInfo, + appDefinedUserAgent, + "android", + android.os.Build.VERSION.RELEASE, + io.realm.BuildConfig.VERSION_NAME); + } this.networkTransport = new OkHttpNetworkTransport(config.getHttpLogObfuscator()); networkTransport.setAuthorizationHeaderName(config.getAuthorizationHeaderName()); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java index c0ff271a05..bb366a8d85 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/App.java @@ -37,6 +37,7 @@ import io.realm.Realm; import io.realm.RealmAsyncTask; import io.realm.annotations.Beta; +import io.realm.internal.KeepMember; import io.realm.internal.Util; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.internal.mongodb.Request; @@ -66,7 +67,7 @@ *

                *    class MyApplication extends Application {
                *
              - *         App APP;
              + *         App APP; // The App instance should be a global singleton
                *
                *         \@Override
                *         public void onCreate() {
              @@ -94,10 +95,10 @@
                * To register a new user and/or login with an existing user do as shown below:
                * 
                *     // Register new user
              - *     User user = APP.getEmailPasswordAuth().registerUser(username, password);
              + *     APP.getEmailPassword().registerUser(username, password);
                *
                *     // Login with existing user
              - *     APP.login(Credentials.emailPassword(username, password))
              + *     User user = APP.login(Credentials.emailPassword(username, password))
                * 
              *

              * With an authorized user you can synchronize data between the local device and the remote Realm @@ -140,24 +141,15 @@ @Beta public class App { + @KeepMember final OsApp osApp; static final class SyncImpl extends Sync { protected SyncImpl(App app) { - super(app); + super(app, app.osApp.getNativePtr()); } } - // Implementation notes: - // The public API's currently only allow for one App, however this is a restriction - // we might want to lift in the future. So any implementation details so ideally be made - // with that in mind, i.e. keep static state to minimum. - - // Currently we only allow one instance of App (due to restrictions in ObjectStore that - // only allows one underlying SyncClient). - // FIXME: Lift this restriction so it is possible to create multiple app instances. - public static volatile boolean CREATED = false; - /** * Thread pool used when doing network requests against MongoDB Realm. *

              @@ -185,20 +177,8 @@ public App(String appId) { */ public App(AppConfiguration config) { this.config = config; - this.syncManager = new SyncImpl(this); this.osApp = init(config); - - // FIXME: Right now we only support one App. This class will throw a - // exception if you try to create it twice. This is a really hacky way to do this - // Figure out a better API that is always forward compatible - synchronized (Sync.class) { - if (CREATED) { - throw new IllegalStateException("Only one App is currently supported. " + - "This restriction will be lifted soon. Instead, store the App" + - "instance in a shared global variable."); - } - CREATED = true; - } + this.syncManager = new SyncImpl(this); } private OsApp init(AppConfiguration config) { @@ -524,6 +504,28 @@ protected void setNetworkTransport(OsJavaNetworkTransport transport) { osApp.setNetworkTransport(transport); } + /** + * Two Apps are considered equal and will share their underlying state if they both refer + * to the same {@link AppConfiguration#getAppId()}. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + App app = (App) o; + + if (!osApp.equals(app.osApp)) return false; + return config.equals(app.config); + } + + @Override + public int hashCode() { + int result = osApp.hashCode(); + result = 31 * result + config.hashCode(); + return result; + } + /** * Result class representing the result of an async request from this app towards MongoDB Realm. * diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index 3c91db1be8..698c1b87c5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -187,6 +187,7 @@ public String getAppId() { * * @return the app name. */ + @Nullable public String getAppName() { return appName; } @@ -196,6 +197,7 @@ public String getAppName() { * * @return the app version. */ + @Nullable public String getAppVersion() { return appVersion; } @@ -294,6 +296,45 @@ public HttpLogObfuscator getHttpLogObfuscator() { return httpLogObfuscator; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AppConfiguration that = (AppConfiguration) o; + + if (requestTimeoutMs != that.requestTimeoutMs) return false; + if (!appId.equals(that.appId)) return false; + if (appName != null ? !appName.equals(that.appName) : that.appName != null) return false; + if (appVersion != null ? !appVersion.equals(that.appVersion) : that.appVersion != null) + return false; + if (!baseUrl.toString().equals(that.baseUrl.toString())) return false; + if (!defaultErrorHandler.equals(that.defaultErrorHandler)) return false; + if (!Arrays.equals(encryptionKey, that.encryptionKey)) return false; + if (!authorizationHeaderName.equals(that.authorizationHeaderName)) return false; + if (!customHeaders.equals(that.customHeaders)) return false; + if (!syncRootDir.equals(that.syncRootDir)) return false; + if (!codecRegistry.equals(that.codecRegistry)) return false; + return httpLogObfuscator != null ? httpLogObfuscator.equals(that.httpLogObfuscator) : that.httpLogObfuscator == null; + } + + @Override + public int hashCode() { + int result = appId.hashCode(); + result = 31 * result + (appName != null ? appName.hashCode() : 0); + result = 31 * result + (appVersion != null ? appVersion.hashCode() : 0); + result = 31 * result + baseUrl.toString().hashCode(); + result = 31 * result + defaultErrorHandler.hashCode(); + result = 31 * result + Arrays.hashCode(encryptionKey); + result = 31 * result + (int) (requestTimeoutMs ^ (requestTimeoutMs >>> 32)); + result = 31 * result + authorizationHeaderName.hashCode(); + result = 31 * result + customHeaders.hashCode(); + result = 31 * result + syncRootDir.hashCode(); + result = 31 * result + codecRegistry.hashCode(); + result = 31 * result + (httpLogObfuscator != null ? httpLogObfuscator.hashCode() : 0); + return result; + } + private static Map getLoginObfuscators() { final HashMap obfuscators = new HashMap<>(); obfuscators.put(Credentials.Provider.API_KEY.getId(), ApiKeyObfuscator.obfuscator()); diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index 9074eaa1ac..afd4620ffe 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -494,6 +494,10 @@ public synchronized MongoClient getMongoClient(String serviceName) { return mongoClient; } + /** + * Two Users are considered equal if they have the same user identity and are associated + * with the same app. + */ @SuppressFBWarnings("NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION") @Override public boolean equals(@Nullable Object o) { @@ -502,8 +506,8 @@ public boolean equals(@Nullable Object o) { User user = (User) o; - if (!osUser.equals(user.osUser)) return false; - return app.equals(user.app); + if (!osUser.getIdentity().equals(user.osUser.getIdentity())) return false; + return app.getConfiguration().getAppId().equals(user.app.getConfiguration().getAppId()); } @Override diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java index 4a260ff7c7..5d10663d4e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/ClientResetRequiredError.java @@ -33,17 +33,19 @@ @Beta public class ClientResetRequiredError extends AppException { + private final long appNativePointer; private final SyncConfiguration originalConfiguration; private final RealmConfiguration backupConfiguration; private final File backupFile; private final File originalFile; - ClientResetRequiredError(ErrorCode errorCode, String errorMessage, SyncConfiguration originalConfiguration, RealmConfiguration backupConfiguration) { + ClientResetRequiredError(long appNativePointer, ErrorCode errorCode, String errorMessage, SyncConfiguration originalConfiguration, RealmConfiguration backupConfiguration) { super(errorCode, errorMessage); this.originalConfiguration = originalConfiguration; this.backupConfiguration = backupConfiguration; this.backupFile = new File(backupConfiguration.getPath()); this.originalFile = new File(originalConfiguration.getPath()); + this.appNativePointer = appNativePointer; } /** @@ -63,7 +65,7 @@ public void executeClientReset() { throw new IllegalStateException("Realm has not been fully closed. Client Reset cannot run before all " + "instances have been closed."); } - nativeExecuteClientReset(originalConfiguration.getPath()); + nativeExecuteClientReset(appNativePointer, originalConfiguration.getPath()); } } @@ -97,5 +99,5 @@ public File getOriginalFile() { } // PRECONDITION: All Realm instances for this path must have been closed. - private native void nativeExecuteClientReset(String originalPath); + private native void nativeExecuteClientReset(long appNativePointer, String originalPath); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java index 8c21ba9674..5099888d94 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/Sync.java @@ -72,11 +72,13 @@ public abstract class Sync { private final App app; + private final long appNativePointer; // keeps track of SyncSession, using 'realm_path'. Java interface with the ObjectStore using the 'realm_path' private Map sessions = new ConcurrentHashMap<>(); - protected Sync(App app) { + protected Sync(App app, long appNativePointer) { this.app = app; + this.appNativePointer = appNativePointer; } /** @@ -96,15 +98,15 @@ public static class Debug { public static boolean separatedDirForSyncManager = false; } - private static NetworkStateReceiver.ConnectionListener networkListener = new NetworkStateReceiver.ConnectionListener() { + private NetworkStateReceiver.ConnectionListener networkListener = new NetworkStateReceiver.ConnectionListener() { @Override public void onChange(boolean connectionAvailable) { if (connectionAvailable) { - RealmLog.debug("NetworkListener: Connection available"); + RealmLog.debug("[App(%s)] NetworkListener: Connection available", app.getConfiguration().getAppId()); // notify all sessions notifyNetworkIsBack(); } else { - RealmLog.debug("NetworkListener: Connection lost"); + RealmLog.debug("[App(%s)] NetworkListener: Connection lost", app.getConfiguration().getAppId()); } } }; @@ -167,7 +169,7 @@ public synchronized SyncSession getOrCreateSession(SyncConfiguration syncConfigu SyncSession session = sessions.get(syncConfiguration.getPath()); if (session == null) { RealmLog.debug("Creating session for: %s", syncConfiguration.getPath()); - session = new SyncSession(syncConfiguration); + session = new SyncSession(syncConfiguration, appNativePointer); sessions.put(syncConfiguration.getPath(), session); if (sessions.size() == 1) { RealmLog.debug("First session created. Adding network listener."); @@ -200,7 +202,7 @@ String getAbsolutePathForRealm(String userId, BsonValue partitionValue, @Nullabl default: throw new IllegalArgumentException("Unsupported type: " + partitionValue); } - return nativeGetPathForRealm(userId, encodedPartitionValue, overrideFileName); + return nativeGetPathForRealm(appNativePointer, userId, encodedPartitionValue, overrideFileName); } /** @@ -253,9 +255,9 @@ private synchronized void notifyErrorHandler(String nativeErrorCategory, int nat } } - private static synchronized void notifyNetworkIsBack() { + private synchronized void notifyNetworkIsBack() { try { - nativeReconnect(); + nativeReconnect(appNativePointer); } catch (Exception exception) { RealmLog.error(exception); } @@ -290,7 +292,7 @@ private synchronized void notifyProgressListener(String localRealmPath, long lis * sessions to attempt to reconnect immediately and reset any timers they are using for * incremental backoff. */ - public static void reconnect() { + public void reconnect() { notifyNetworkIsBack(); } @@ -301,7 +303,7 @@ public static void reconnect() { * Only call this method when testing. */ synchronized void reset() { - nativeReset(); + nativeReset(appNativePointer); sessions.clear(); } @@ -314,15 +316,15 @@ synchronized void reset() { * @param session Session to trigger Client Reset for. */ void simulateClientReset(SyncSession session) { - nativeSimulateSyncError(session.getConfiguration().getPath(), + nativeSimulateSyncError(appNativePointer, session.getConfiguration().getPath(), ErrorCode.DIVERGING_HISTORIES.intValue(), "Simulate Client Reset", true); } - private static native void nativeReset(); - private static native void nativeSimulateSyncError(String realmPath, int errorCode, String errorMessage, boolean isFatal); - private static native void nativeReconnect(); + private static native void nativeReset(long appNativePointer); + private static native void nativeSimulateSyncError(long appNativePointer, String realmPath, int errorCode, String errorMessage, boolean isFatal); + private static native void nativeReconnect(long appNativePointer); private static native void nativeCreateSession(long nativeConfigPtr); - private static native String nativeGetPathForRealm(String userId, String partitionValue, @Nullable String overrideFileName); + private static native String nativeGetPathForRealm(long appNativePointer, String userId, String partitionValue, @Nullable String overrideFileName); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 695edeb8aa..2fa81ac217 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -65,6 +65,7 @@ public class SyncSession { private static final int DIRECTION_DOWNLOAD = 1; private static final int DIRECTION_UPLOAD = 2; + private final long appNativePointer; private final SyncConfiguration configuration; private final ErrorHandler errorHandler; private volatile boolean isClosed = false; @@ -154,9 +155,10 @@ static State fromNativeValue(long value) { } } - SyncSession(SyncConfiguration configuration) { + SyncSession(SyncConfiguration configuration, long appNativePointer) { this.configuration = configuration; this.errorHandler = configuration.getErrorHandler(); + this.appNativePointer = appNativePointer; } /** @@ -196,7 +198,7 @@ void notifySessionError(String nativeErrorCategory, int nativeErrorCode, String if (errCode == ErrorCode.CLIENT_RESET) { // errorMessage contains the path to the backed up file RealmConfiguration backupRealmConfiguration = configuration.forErrorRecovery(errorMessage); - errorHandler.onError(this, new ClientResetRequiredError(errCode, "A Client Reset is required. " + + errorHandler.onError(this, new ClientResetRequiredError(appNativePointer, errCode, "A Client Reset is required. " + "Read more here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup.", configuration, backupRealmConfiguration)); } else { @@ -219,7 +221,7 @@ void notifySessionError(String nativeErrorCategory, int nativeErrorCode, String * @see SyncSession.State */ public State getState() { - byte state = nativeGetState(configuration.getPath()); + byte state = nativeGetState(appNativePointer, configuration.getPath()); if (state == -1) { // session was not found, probably the Realm was closed throw new IllegalStateException("Could not find session, Realm was probably closed"); @@ -234,7 +236,7 @@ public State getState() { * @see ConnectionState */ public ConnectionState getConnectionState() { - byte state = nativeGetConnectionState(configuration.getPath()); + byte state = nativeGetConnectionState(appNativePointer, configuration.getPath()); if (state == -1) { // session was not found, probably the Realm was closed throw new IllegalStateException("Could not find session, Realm was probably closed"); @@ -254,7 +256,7 @@ public ConnectionState getConnectionState() { * if not or if it is in the process of connecting. */ public boolean isConnected() { - ConnectionState connectionState = ConnectionState.fromNativeValue(nativeGetConnectionState(configuration.getPath())); + ConnectionState connectionState = ConnectionState.fromNativeValue(nativeGetConnectionState(appNativePointer, configuration.getPath())); State sessionState = getState(); return (sessionState == State.ACTIVE || sessionState == State.DYING) && connectionState == ConnectionState.CONNECTED; } @@ -345,7 +347,7 @@ public synchronized void removeProgressListener(ProgressListener listener) { break; } } - nativeRemoveProgressListener(configuration.getPath(), token); + nativeRemoveProgressListener(appNativePointer, configuration.getPath(), token); } } @@ -357,7 +359,7 @@ private void addProgressListener(ProgressMode mode, int direction, ProgressListe // A listener might be triggered immediately as part of `nativeAddProgressListener`, so // we need to make sure it can be found by SyncManager.notifyProgressListener() listenerIdToProgressListenerMap.put(listenerId, new Pair(listener, null)); - long listenerToken = nativeAddProgressListener(configuration.getPath(), listenerId , direction, isStreaming); + long listenerToken = nativeAddProgressListener(appNativePointer, configuration.getPath(), listenerId , direction, isStreaming); if (listenerToken == 0) { // ObjectStore did not register the listener. This can happen if a // listener is registered with ProgressMode.CURRENT_CHANGES and no changes actually @@ -386,7 +388,7 @@ private void checkProgressListenerArguments(ProgressMode mode, ProgressListener public synchronized void addConnectionChangeListener(ConnectionListener listener) { Util.checkNull(listener, "listener"); if (connectionListeners.isEmpty()) { - nativeConnectionListenerToken = nativeAddConnectionListener(configuration.getPath()); + nativeConnectionListenerToken = nativeAddConnectionListener(appNativePointer, configuration.getPath()); } connectionListeners.add(listener); } @@ -401,7 +403,7 @@ public synchronized void removeConnectionChangeListener(ConnectionListener liste Util.checkNull(listener, "listener"); connectionListeners.remove(listener); if (connectionListeners.isEmpty()) { - nativeRemoveConnectionListener(nativeConnectionListenerToken, configuration.getPath()); + nativeRemoveConnectionListener(appNativePointer, nativeConnectionListenerToken, configuration.getPath()); } } @@ -554,7 +556,7 @@ public boolean uploadAllLocalChanges(long timeout, TimeUnit unit) throws Interru * @see #stop() */ public synchronized void start() { - nativeStart(configuration.getPath()); + nativeStart(appNativePointer, configuration.getPath()); } /** @@ -567,7 +569,7 @@ public synchronized void start() { */ public synchronized void stop() { close(); - nativeStop(configuration.getPath()); + nativeStop(appNativePointer, configuration.getPath()); } /** @@ -590,8 +592,8 @@ private boolean waitForChanges(int direction, long timeout, TimeUnit unit) throw waitingForServerChanges.set(wrapper); int callbackId = waitCounter.incrementAndGet(); boolean listenerRegistered = (direction == DIRECTION_DOWNLOAD) - ? nativeWaitForDownloadCompletion(callbackId, realmPath) - : nativeWaitForUploadCompletion(callbackId, realmPath); + ? nativeWaitForDownloadCompletion(appNativePointer, callbackId, realmPath) + : nativeWaitForUploadCompletion(appNativePointer, callbackId, realmPath); if (!listenerRegistered) { waitingForServerChanges.set(null); String errorMsg; @@ -636,7 +638,7 @@ private void checkTimeout(long timeout, TimeUnit unit) { } void shutdownAndWait() { - nativeShutdownAndWait(configuration.getPath()); + nativeShutdownAndWait(appNativePointer, configuration.getPath()); } /** @@ -752,15 +754,15 @@ public void throwExceptionIfNeeded() { } } - private native long nativeAddConnectionListener(String localRealmPath); - private static native void nativeRemoveConnectionListener(long listenerId, String localRealmPath); - private native long nativeAddProgressListener(String localRealmPath, long listenerId, int direction, boolean isStreaming); - private static native void nativeRemoveProgressListener(String localRealmPath, long listenerToken); - private native boolean nativeWaitForDownloadCompletion(int callbackId, String localRealmPath); - private native boolean nativeWaitForUploadCompletion(int callbackId, String localRealmPath); - private static native byte nativeGetState(String localRealmPath); - private static native byte nativeGetConnectionState(String localRealmPath); - private static native void nativeStart(String localRealmPath); - private static native void nativeStop(String localRealmPath); - private static native void nativeShutdownAndWait(String localRealmPath); + private native long nativeAddConnectionListener(long appNativePointer, String localRealmPath); + private static native void nativeRemoveConnectionListener(long appNativePointer, long listenerId, String localRealmPath); + private native long nativeAddProgressListener(long appNativePointer, String localRealmPath, long listenerId, int direction, boolean isStreaming); + private static native void nativeRemoveProgressListener(long appNativePointer, String localRealmPath, long listenerToken); + private native boolean nativeWaitForDownloadCompletion(long appNativePointer, int callbackId, String localRealmPath); + private native boolean nativeWaitForUploadCompletion(long appNativePointer, int callbackId, String localRealmPath); + private static native byte nativeGetState(long appNativePointer, String localRealmPath); + private static native byte nativeGetConnectionState(long appNativePointer, String localRealmPath); + private static native void nativeStart(long appNativePointer, String localRealmPath); + private static native void nativeStop(long appNativePointer, String localRealmPath); + private static native void nativeShutdownAndWait(long appNativePointer, String localRealmPath); } diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt index 31d9277f02..10b19304ec 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/EncryptedSynchronizedRealmTests.kt @@ -67,7 +67,7 @@ class EncryptedSynchronizedRealmTests { val configWithEncryption: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, BsonString(UUID.randomUUID().toString())) .testSchema(SyncStringOnly::class.java) .waitForInitialRemoteData() - .errorHandler { session, error -> fail(error.getErrorMessage()) } + .errorHandler { _, error -> fail(error.errorMessage) } .encryptionKey(randomKey) .build() @@ -90,7 +90,7 @@ class EncryptedSynchronizedRealmTests { // .name("newName") .testSchema(SyncStringOnly::class.java) .waitForInitialRemoteData() - .errorHandler { session, error -> fail(error.getErrorMessage()) } + .errorHandler { _, error -> fail(error.errorMessage) } .build() Realm.getInstance(configWithoutEncryption).use { realm -> diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index b084b35b2e..1c02510a29 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -312,7 +312,7 @@ class SyncedRealmIntegrationTests { @Test fun refreshConnections() = looperThread.runBlocking { RealmLog.setLevel(LogLevel.DEBUG) - Sync.reconnect() // No Realms + app.sync.reconnect() // No Realms // A single active Realm val username = UUID.randomUUID().toString() @@ -322,11 +322,11 @@ class SyncedRealmIntegrationTests { .testSchema(StringOnly::class.java) .build() val realm = Realm.getInstance(config) - Sync.reconnect() + app.sync.reconnect() // A single logged out Realm realm.close() - Sync.reconnect() + app.sync.reconnect() looperThread.testComplete() } diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt index 393eefe64b..a116a74543 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt @@ -28,11 +28,14 @@ import io.realm.mongodb.AppConfiguration */ const val SERVICE_NAME = "BackingDB" // it comes from the test server's BackingDB/config.json const val DATABASE_NAME = "test_data" // same as above +const val TEST_APP_1 = "testapp1" // Id for the default test app +const val TEST_APP_2 = "testapp2" // ID for the 2nd test app, which is a direct copy of the default test app. class TestApp( networkTransport: OsJavaNetworkTransport? = null, + appName: String = TEST_APP_1, builder: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it } -) : App(builder(configurationBuilder()).build()) { +) : App(builder(configurationBuilder(appName)).build()) { init { if (networkTransport != null) { @@ -42,8 +45,8 @@ class TestApp( companion object { - fun configurationBuilder(customizeConfig: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it }): AppConfiguration.Builder { - return AppConfiguration.Builder(initializeMongoDbRealm()) + fun configurationBuilder(appName: String): AppConfiguration.Builder { + return AppConfiguration.Builder(initializeMongoDbRealm(appName)) .baseUrl("http://127.0.0.1:9090") .appName("MongoDB Realm Integration Tests") .appVersion("1.0.") @@ -51,11 +54,11 @@ class TestApp( } // Initializes MongoDB Realm. Clears all local state and fetches the application ID. - private fun initializeMongoDbRealm(): String { + private fun initializeMongoDbRealm(appName: String): String { val transport = OkHttpNetworkTransport(null) val response = transport.sendRequest( "get", - "http://127.0.0.1:8888/application-id", + "http://127.0.0.1:8888/$appName", 5000, mapOf(), "" diff --git a/tools/sync_test_server/mongodb-realm-command-server.js b/tools/sync_test_server/mongodb-realm-command-server.js index 2ff237cfda..be19377107 100755 --- a/tools/sync_test_server/mongodb-realm-command-server.js +++ b/tools/sync_test_server/mongodb-realm-command-server.js @@ -39,11 +39,11 @@ function handleWatcher(req, resp) { resp.write("hello world 3\n"); } -function handleApplicationId(req, resp) { +function handleApplicationId(appName, req, resp) { switch(req.method) { case "GET": resp.writeHead(200, {'Content-Type': 'text/plain'}); - resp.end(applicationId); + resp.end(applicationIds[appName]); break; case "PUT": var body = []; @@ -51,7 +51,7 @@ function handleApplicationId(req, resp) { body.push(chunk); }).on('end', () => { body = Buffer.concat(body).toString(); - applicationId = body.split("=")[1]; + applicationIds[appName] = body.split("=")[1]; resp.writeHead(201, {'Content-Location': '/application-id'}); resp.end(); }); @@ -63,14 +63,16 @@ function handleApplicationId(req, resp) { //Create and start the Http server const PORT = 8888; -var applicationId = "unknown" // Should be updated by the Docker setup script before any tests are run. +var applicationIds = {} // Should be updated by the Docker setup script before any tests are run. var server = http.createServer(function(req, resp) { try { winston.info('command-server: ' + req.method + " " + req.url); if (req.url.includes("/okhttp")) { handleOkHttp(req, resp); - } else if (req.url.includes('/application-id')) { - handleApplicationId(req, resp); + } else if (req.url.includes('/testapp1')) { + handleApplicationId('testapp1', req, resp); + } else if (req.url.includes('/testapp2')) { + handleApplicationId('testapp2', req, resp); } else if (req.url.includes('/watcher')) { handleWatcher(req, resp); } else { diff --git a/tools/sync_test_server/setup_mongodb_realm.sh b/tools/sync_test_server/setup_mongodb_realm.sh index 861c592ef8..8bbc3674ef 100755 --- a/tools/sync_test_server/setup_mongodb_realm.sh +++ b/tools/sync_test_server/setup_mongodb_realm.sh @@ -37,45 +37,57 @@ yes | stitch-cli login --config-path=/tmp/stitch-config \ --username=unique_user@domain.com \ --password=password -# 2. Attempt to import project. It will fail because of lacking secret, but create the App ID -# which we need to extract from the commandline output -IMPORT_RESPONSE=$(stitch-cli import \ - --config-path=/tmp/stitch-config \ - --base-url=http://localhost:9090 \ - --path=/tmp/app_config \ - --app-name realm-sdk-integration-tests \ - --project-id "$GROUP_ID" \ - --strategy replace \ - -y) -APP_ID_SUFFIX=$(echo "$IMPORT_RESPONSE" | grep "New app created:" | cut -d ':' -f 2 | cut -d '-' -f 5) -echo "App ID Suffix: $APP_ID_SUFFIX" - -# 3. Create the secret(s) needed to start the Stitch app: -# - a) MongoDB Service: Requires an URI. -stitch-cli secrets add \ - --name="BackingDB_uri" \ - --value="mongodb://localhost:26000" \ - --app-id="realm-sdk-integration-tests-$APP_ID_SUFFIX" \ - --base-url=http://localhost:9090 \ - --config-path=/tmp/stitch-config - -# - b) GCM (Firebase Cloud Messaging): Requires a server key - add your key here to test actual push notifications. -stitch-cli secrets add \ - --name="gcm" \ - --value="gcm" \ - --app-id="realm-sdk-integration-tests-$APP_ID_SUFFIX" \ - --base-url=http://localhost:9090 \ - --config-path=/tmp/stitch-config - -# 4. Now we can correctly import the Stitch app -stitch-cli import \ - --config-path=/tmp/stitch-config \ - --base-url=http://localhost:9090 \ - --path=/tmp/app_config \ - --app-name realm-sdk-integration-tests \ - --project-id "$GROUP_ID" \ - --strategy replace \ - -y - -# 5. Store the application id in the Command Server so it can be accessed by Integration Tests on the device -curl -X PUT -d id="realm-sdk-integration-tests-$APP_ID_SUFFIX" http://localhost:8888/application-id + +# 2. Import two identical app projects +APPS=( "testapp1" "testapp2" ) + +for APP_NAME in "${APPS[@]}" +do + + echo "importing $APP_NAME" + sed -i "s/\"app_id\": \"[a-z\-]*\"/\"app_id\": \"$APP_NAME-xxxxx\"/g" "/tmp/app_config-$APP_NAME/stitch.json" + + # 3. Attempt to import project. It will fail because of lacking secret, but create the App ID + # which we need to extract from the commandline output + IMPORT_RESPONSE=$(stitch-cli import \ + --config-path=/tmp/stitch-config \ + --base-url=http://localhost:9090 \ + --path="/tmp/app_config-$APP_NAME" \ + --app-name "$APP_NAME" \ + --project-id "$GROUP_ID" \ + --strategy replace \ + -y) + + APP_ID_SUFFIX=$(echo "$IMPORT_RESPONSE" | grep "New app created:" | cut -d':' -f 2 | cut -d '-' -f 2) + echo "App ID Suffix: $APP_ID_SUFFIX" + + # 4. Create the secret(s) needed to start the Stitch app: + # - a) MongoDB Service: Requires an URI. + stitch-cli secrets add \ + --name="BackingDB_uri" \ + --value="mongodb://localhost:26000" \ + --app-id="$APP_NAME-$APP_ID_SUFFIX" \ + --base-url=http://localhost:9090 \ + --config-path=/tmp/stitch-config + + # - b) GCM (Firebase Cloud Messaging): Requires a server key - add your key here to test actual push notifications. + stitch-cli secrets add \ + --name="gcm" \ + --value="gcm" \ + --app-id="$APP_NAME-$APP_ID_SUFFIX" \ + --base-url=http://localhost:9090 \ + --config-path=/tmp/stitch-config + + # 5. Now we can correctly import the Stitch app + stitch-cli import \ + --config-path=/tmp/stitch-config \ + --base-url=http://localhost:9090 \ + --path="/tmp/app_config-$APP_NAME" \ + --app-name "$APP_NAME" \ + --project-id "$GROUP_ID" \ + --strategy replace \ + -y + + # 6. Store the application id in the Command Server so it can be accessed by Integration Tests on the device + curl -X PUT -d id="$APP_NAME-$APP_ID_SUFFIX" "http://localhost:8888/$APP_NAME" +done diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 7ebf51b0f9..512392fee7 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -40,6 +40,7 @@ docker build $DOCKERFILE_DIR -t mongodb-realm-command-server || { echo "Failed t ID=$(docker run --rm -i -t -d --network mongodb-realm-network -p9090:9090 -p8888:8888 -p26000:26000 --name mongodb-realm docker.pkg.github.com/realm/ci/mongodb-realm-test-server:$MONGODB_REALM_VERSION) docker run --rm -i -t -d --network container:$ID -v$TMP_DIR:/tmp --name mongodb-realm-command-server mongodb-realm-command-server -docker cp "$DOCKERFILE_DIR"/app_config mongodb-realm:/tmp/app_config +docker cp "$DOCKERFILE_DIR"/app_config mongodb-realm:/tmp/app_config-testapp1 +docker cp "$DOCKERFILE_DIR"/app_config mongodb-realm:/tmp/app_config-testapp2 docker cp "$DOCKERFILE_DIR"/setup_mongodb_realm.sh mongodb-realm:/tmp/ docker exec -it mongodb-realm sh /tmp/setup_mongodb_realm.sh From d150f48619ca72ff86285cf5a178cc24561bec45 Mon Sep 17 00:00:00 2001 From: clementetb Date: Wed, 16 Sep 2020 14:18:27 +0200 Subject: [PATCH 1674/2110] Add retry custom confirmation logic (#7079) --- CHANGELOG.md | 3 +- .../kotlin/io/realm/EmailPasswordAuthTests.kt | 122 +++++++++++++++++- .../kotlin/io/realm/admin/ServerAdmin.kt | 21 +++ ...io_realm_mongodb_EmailPasswordAuthImpl.cpp | 3 + .../realm/mongodb/auth/EmailPasswordAuth.java | 33 +++++ .../auth_providers/local-userpass.json | 1 + 6 files changed, 176 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a6ac04a64..61e5d98dcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,8 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * [RealmApp] It is now possible to create App instances with different app id's. * [RealmApp] Support for using `null` as a partition value. * [RealmApp] Improve errors exception messages from `SyncSession.downloadAllServerChanges()` and `SyncSession.uploadAllLocalChanges()`. -* Support for watching MongoCollection change streams (Issue [#6912](https://github.com/realm/realm-java/issues/6912)) +* [RealmApp] Support for watching MongoCollection change streams (Issue [#6912](https://github.com/realm/realm-java/issues/6912)) +* [RealmApp] Support for retrying a custom confirmation function on an User for a given email (Issue [#7079](https://github.com/realm/realm-java/pull/7079)) * [RealmApp] Support for getting all app sessions via `Sync.getAllSessions()`. * [RealmApp] Support to retrieve the MongoClient service name using `MongoClient.getServiceName()` * [RealmApp] Support to retrieve the MongoDatabase name using `MongoDatabase.getName()` diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index 359cce4d80..887e722b63 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -31,7 +31,6 @@ import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith -import java.lang.IllegalStateException import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) @@ -58,6 +57,7 @@ class EmailPasswordAuthTests { RESEND_CONFIRMATION_EMAIL, SEND_RESET_PASSWORD_EMAIL, CALL_RESET_PASSWORD_FUNCTION, + RETRY_CUSTOM_CONFIRMATION, RESET_PASSWORD } @@ -221,7 +221,7 @@ class EmailPasswordAuthTests { val provider = app.emailPassword provider.registerUser(email, "123456") provider.resendConfirmationEmailAsync(email) { result -> - when(result.isSuccess) { + when (result.isSuccess) { true -> looperThread.testComplete() false -> fail(result.error.toString()) } @@ -279,6 +279,114 @@ class EmailPasswordAuthTests { } } + @Test + fun retryCustomConfirmation() { + val email = "test_realm_tests_do_autoverify@10gen.com" + admin.setAutomaticConfirmation(false) + try { + val provider = app.emailPassword + provider.registerUser(email, "123456") + admin.setCustomConfirmation(true) + + provider.retryCustomConfirmation(email) + } finally { + admin.setCustomConfirmation(false) + } + } + + @Test + fun retryCustomConfirmation_failConfirmation() { + // Only emails containing realm_tests_do_autoverify will be confirmed + val email = "test@10gen.com" + admin.setAutomaticConfirmation(false) + try { + val provider = app.emailPassword + provider.registerUser(email, "123456") + admin.setCustomConfirmation(true) + + val exception = assertFailsWith { + provider.retryCustomConfirmation(email) + } + + assertEquals("failed to confirm user test@10gen.com", exception.errorMessage) + + } finally { + admin.setCustomConfirmation(false) + } + } + + @Test + fun retryCustomConfirmationAsync() { + val email = "test_realm_tests_do_autoverify@10gen.com" + admin.setAutomaticConfirmation(false) + try { + looperThread.runBlocking { + val provider = app.emailPassword + provider.registerUser(email, "123456") + admin.setCustomConfirmation(true) + + provider.retryCustomConfirmationAsync(email) { result -> + when (result.isSuccess) { + true -> looperThread.testComplete() + false -> fail(result.error.toString()) + } + } + } + } finally { + admin.setCustomConfirmation(false) + } + } + + @Test + fun retryCustomConfirmation_invalidServerArgsThrows() { + val email = "test@10gen.com" + admin.setAutomaticConfirmation(false) + val provider = app.emailPassword + provider.registerUser(email, "123456") + admin.setCustomConfirmation(true) + + try { + provider.retryCustomConfirmation("foo") + fail() + } catch (error: AppException) { + assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) + } finally { + admin.setCustomConfirmation(false) + } + } + + @Test + fun retryCustomConfirmationAsync_invalidServerArgsThrows() { + val email = "test@10gen.com" + admin.setAutomaticConfirmation(false) + val provider = app.emailPassword + provider.registerUser(email, "123456") + admin.setCustomConfirmation(true) + try { + looperThread.runBlocking { + provider.retryCustomConfirmationAsync("foo") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + } finally { + admin.setCustomConfirmation(false) + } + } + + @Test + fun retryCustomConfirmation_invalidArgumentsThrows() { + val provider: EmailPasswordAuth = app.emailPassword + assertFailsWith { provider.retryCustomConfirmation(TestHelper.getNull()) } + looperThread.runBlocking { + provider.retryCustomConfirmationAsync(TestHelper.getNull(), checkNullArgCallback) + } + } + @Test fun sendResetPasswordEmail() { val provider = app.emailPassword @@ -294,7 +402,7 @@ class EmailPasswordAuthTests { provider.registerUser(email, "123456") looperThread.runBlocking { provider.sendResetPasswordEmailAsync(email) { result -> - when(result.isSuccess) { + when (result.isSuccess) { true -> looperThread.testComplete() false -> fail(result.error.toString()) } @@ -481,7 +589,7 @@ class EmailPasswordAuthTests { provider.resetPasswordAsync("token", TestHelper.getNull(), "password", checkNullArgCallback) } looperThread.runBlocking { - provider.resetPasswordAsync("token","token-id", TestHelper.getNull(), checkNullArgCallback) + provider.resetPasswordAsync("token", "token-id", TestHelper.getNull(), checkNullArgCallback) } } @@ -492,12 +600,13 @@ class EmailPasswordAuthTests { val email: String = TestHelper.getRandomEmail() for (method in Method.values()) { try { - when(method) { + when (method) { Method.REGISTER_USER -> provider.registerUser(email, "123456") Method.CONFIRM_USER -> provider.confirmUser("token", "tokenId") Method.RESEND_CONFIRMATION_EMAIL -> provider.resendConfirmationEmail(email) Method.SEND_RESET_PASSWORD_EMAIL -> provider.sendResetPasswordEmail(email) Method.CALL_RESET_PASSWORD_FUNCTION -> provider.callResetPasswordFunction(email, "123456") + Method.RETRY_CUSTOM_CONFIRMATION -> provider.retryCustomConfirmation(email) Method.RESET_PASSWORD -> provider.resetPassword("token", "token-id", "password") } fail("$method should have thrown an exception") @@ -514,12 +623,13 @@ class EmailPasswordAuthTests { val callback = App.Callback { fail() } for (method in Method.values()) { try { - when(method) { + when (method) { Method.REGISTER_USER -> provider.registerUserAsync(email, "123456", callback) Method.CONFIRM_USER -> provider.confirmUserAsync("token", "tokenId", callback) Method.RESEND_CONFIRMATION_EMAIL -> provider.resendConfirmationEmailAsync(email, callback) Method.SEND_RESET_PASSWORD_EMAIL -> provider.sendResetPasswordEmailAsync(email, callback) Method.CALL_RESET_PASSWORD_FUNCTION -> provider.callResetPasswordFunctionAsync(email, "123456", arrayOf(), callback) + Method.RETRY_CUSTOM_CONFIRMATION -> provider.retryCustomConfirmationAsync(email, callback) Method.RESET_PASSWORD -> provider.resetPasswordAsync("token", "token-id", "password", callback) } fail("$method should have thrown an exception") diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt index e18c874851..595f7ed47b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt @@ -115,6 +115,27 @@ class ServerAdmin(private val app: App) { executeRequest(request) } + /** + * Toggle whether or not custom confirmation functions are enabled. + */ + fun setCustomConfirmation(enabled: Boolean) { + val providerId: String = getLocalUserPassProviderId() + var request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId") + .get() + val authProviderConfig = JSONObject(executeRequest(request, true)) + + authProviderConfig.getJSONObject("config").apply { + put("autoConfirm", !enabled) + put("runConfirmationFunction", enabled) + } + // Change autoConfirm and update the provider + request = Request.Builder() + .url("$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId") + .patch(RequestBody.create(json, authProviderConfig.toString())) + executeRequest(request) + } + val JSON = MediaType.parse("application/json; charset=utf-8") fun disableUser(user: User) { diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp index ef7fe7df2a..c193355f0f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp @@ -61,6 +61,9 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_EmailPasswordAuthImpl_nativeCallFun case io_realm_mongodb_EmailPasswordAuthImpl_TYPE_RESET_PASSWORD: client.reset_password(args[0], args[1], args[2], JavaNetworkTransport::create_void_callback(env, j_callback)); break; + case io_realm_mongodb_EmailPasswordAuthImpl_TYPE_RETRY_CUSTOM_CONFIRMATION: + client.retry_custom_confirmation(args[0], JavaNetworkTransport::create_void_callback(env, j_callback)); + break; default: throw std::logic_error(util::format("Unknown function: %1", j_function_type)); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java index 22b0f22be2..eb366c49ad 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java @@ -46,6 +46,7 @@ public abstract class EmailPasswordAuth { private static final int TYPE_SEND_RESET_PASSWORD_EMAIL = 4; private static final int TYPE_CALL_RESET_PASSWORD_FUNCTION = 5; private static final int TYPE_RESET_PASSWORD = 6; + private static final int TYPE_RETRY_CUSTOM_CONFIRMATION = 7; protected final App app; @@ -164,6 +165,38 @@ public Void run() throws AppException { }.start(); } + /** + * Retries the custom confirmation on a user for a given email. + * + * @param email the email of the user. + * @throws AppException if the server failed to confirm the user. + */ + public void retryCustomConfirmation(String email) throws AppException { + Util.checkEmpty(email, "email"); + AtomicReference error = new AtomicReference<>(null); + call(TYPE_RETRY_CUSTOM_CONFIRMATION, new OsJNIVoidResultCallback(error), email); + ResultHandler.handleResult(null, error); + } + + /** + * Retries the custom confirmation on a user for a given email. + * + * @param email the email of the user. + * @param callback callback when retrying the custom confirmation has completed or failed. The callback will + * always happen on the same thread as this method is called on. + * @throws IllegalStateException if called from a non-looper thread. + */ + public RealmAsyncTask retryCustomConfirmationAsync(String email, App.Callback callback) { + Util.checkLooperThread("Asynchronous retry custom confirmation is only possible from looper threads."); + return new Request(NETWORK_POOL_EXECUTOR, callback) { + @Override + public Void run() throws AppException { + retryCustomConfirmation(email); + return null; + } + }.start(); + } + /** * Sends a user a password reset email for the given email. * diff --git a/tools/sync_test_server/app_config/auth_providers/local-userpass.json b/tools/sync_test_server/app_config/auth_providers/local-userpass.json index a020250a87..b509b623d2 100644 --- a/tools/sync_test_server/app_config/auth_providers/local-userpass.json +++ b/tools/sync_test_server/app_config/auth_providers/local-userpass.json @@ -8,6 +8,7 @@ "resetFunctionName": "resetFunc", "resetPasswordSubject": "Reset Password", "resetPasswordUrl": "http://realm.io/reset-password", + "confirmationFunctionName": "confirmFunc", "runConfirmationFunction": false, "runResetFunction": false }, From 4cfb10ac334f2992f85b27cbe64a0af905eeaa3c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 16 Sep 2020 19:09:21 +0200 Subject: [PATCH 1675/2110] Handle Client Reset in a separate callback (#7097) --- CHANGELOG.md | 1 + .../kotlin/io/realm/AppConfigurationTests.kt | 18 ++++ .../io/realm/mongodb/sync/SessionTests.kt | 72 +++++---------- .../mongodb/sync/SyncConfigurationTests.kt | 26 +++++- .../io/realm/mongodb/AppConfiguration.java | 42 ++++++++- .../realm/mongodb/sync/SyncConfiguration.java | 32 ++++++- .../io/realm/mongodb/sync/SyncSession.java | 92 +++++++++++-------- .../kotlin/io/realm/SyncSessionTests.kt | 9 +- 8 files changed, 190 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e5d98dcb..0084009b90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * [RealmApp] Renamed `Sync.refreshConnections()` to `Sync.reconnect()`. * [RealmApp] Renamed `Credentials.IdentityProvider` to `Credentials.Provider`. * [RealmApp] Removed support for `User.getLocalId()`. +* [RealmApp] Client Resets are now handled through a custom `SyncConfiguration.Builder.clientResetHandler()` instead of through the default session error handler `SyncConfiguration.Builder.errorHandler()` ### Enhancements * [RealmApp] It is now possible to create App instances with different app id's. diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index 883f8ad051..ca86a78df2 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -248,6 +248,24 @@ class AppConfigurationTests { } + @Test + fun defaultClientResetHandler() { + val handler = SyncSession.ClientResetHandler { _, _ -> } + + val config = AppConfiguration.Builder("app-id") + .defaultClientResetHandler(handler) + .build() + assertEquals(config.defaultClientResetHandler, handler) + } + + @Test + fun defaultClientResetHandler_invalidValuesThrows() { + val builder = AppConfiguration.Builder("app-id") + assertFailsWith { + builder.defaultClientResetHandler(TestHelper.getNull()) + } + } + @Test fun encryptionKey() { val key = TestHelper.getRandomKey() diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt index 13a8f39970..bc9fafa716 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt @@ -126,18 +126,12 @@ class SessionTests { @Test fun errorHandler_clientResetReported() = looperThread.runBlocking { val config = configFactory.createSyncConfigurationBuilder(user) - .clientResyncMode(ClientResyncMode.MANUAL) - .errorHandler { session: SyncSession, error: AppException -> - if (error.errorCode != ErrorCode.CLIENT_RESET) { - fail("Wrong error $error") - return@errorHandler - } - val handler = error as ClientResetRequiredError - val filePathFromError = handler.originalFile.absolutePath + .clientResetHandler { session: SyncSession, error: ClientResetRequiredError -> + val filePathFromError = error.originalFile.absolutePath val filePathFromConfig = session.configuration.path assertEquals(filePathFromError, filePathFromConfig) - assertFalse(handler.backupFile.exists()) - assertTrue(handler.originalFile.exists()) + assertFalse(error.backupFile.exists()) + assertTrue(error.originalFile.exists()) looperThread.testComplete() } .build() @@ -156,25 +150,20 @@ class SessionTests { val config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) - .errorHandler { session: SyncSession?, error: AppException -> - if (error.errorCode != ErrorCode.CLIENT_RESET) { - fail("Wrong error $error") - return@errorHandler - } - val handler = error as ClientResetRequiredError + .clientResetHandler { _: SyncSession, error: ClientResetRequiredError -> try { - handler.executeClientReset() + error.executeClientReset() fail("All Realms should be closed before executing Client Reset can be allowed") } catch (ignored: IllegalStateException) { } // Execute Client Reset resources.close() - handler.executeClientReset() + error.executeClientReset() // Validate that files have been moved - assertFalse(handler.originalFile.exists()) - assertTrue(handler.backupFile.exists()) + assertFalse(error.originalFile.exists()) + assertTrue(error.backupFile.exists()) looperThread.testComplete() } .build() @@ -192,20 +181,15 @@ class SessionTests { val config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) .schema(StringOnly::class.java) - .errorHandler { session: SyncSession?, error: AppException -> - if (error.errorCode != ErrorCode.CLIENT_RESET) { - fail("Wrong error $error") - return@errorHandler - } - val handler = error as ClientResetRequiredError + .clientResetHandler { _: SyncSession?, error: ClientResetRequiredError -> // Execute Client Reset resources.close() - handler.executeClientReset() + error.executeClientReset() // Validate that files have been moved - assertFalse(handler.originalFile.exists()) - assertTrue(handler.backupFile.exists()) - val backupRealmConfiguration = handler.backupRealmConfiguration + assertFalse(error.originalFile.exists()) + assertTrue(error.backupFile.exists()) + val backupRealmConfiguration = error.backupRealmConfiguration assertNotNull(backupRealmConfiguration) assertFalse(backupRealmConfiguration is SyncConfiguration) assertTrue(backupRealmConfiguration.isRecoveryConfiguration) @@ -244,20 +228,15 @@ class SessionTests { val resources = ResourceContainer() val config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) - .errorHandler { session: SyncSession?, error: AppException -> - if (error.errorCode != ErrorCode.CLIENT_RESET) { - fail("Wrong error $error") - return@errorHandler - } - val handler = error as ClientResetRequiredError + .clientResetHandler { session: SyncSession?, error: ClientResetRequiredError -> // Execute Client Reset resources.close() - handler.executeClientReset() + error.executeClientReset() // Validate that files have been moved - assertFalse(handler.originalFile.exists()) - assertTrue(handler.backupFile.exists()) - val backupFile = handler.backupFile.absolutePath + assertFalse(error.originalFile.exists()) + assertTrue(error.backupFile.exists()) + val backupFile = error.backupFile.absolutePath // this SyncConf doesn't specify any module, it will throw a migration required // exception since the backup Realm contain only StringOnly table @@ -315,16 +294,11 @@ class SessionTests { .clientResyncMode(ClientResyncMode.MANUAL) .encryptionKey(randomKey) .modules(StringOnlyModule()) - .errorHandler { session: SyncSession?, error: AppException -> - if (error.errorCode != ErrorCode.CLIENT_RESET) { - fail("Wrong error $error") - return@errorHandler - } - val handler = error as ClientResetRequiredError + .clientResetHandler { session: SyncSession?, error: ClientResetRequiredError -> // Execute Client Reset resources.close() - handler.executeClientReset() - var backupRealmConfiguration = handler.backupRealmConfiguration + error.executeClientReset() + var backupRealmConfiguration = error.backupRealmConfiguration // can open encrypted backup Realm Realm.getInstance(backupRealmConfiguration).use { backupEncryptedRealm -> @@ -332,7 +306,7 @@ class SessionTests { val allSorted = backupEncryptedRealm.where(StringOnly::class.java).findAll() assertEquals("Foo", allSorted[0]!!.chars) } - val backupFile = handler.backupFile.absolutePath + val backupFile = error.backupFile.absolutePath // build a conf to open a DynamicRealm backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, randomKey, StringOnlyModule()) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index fc6a81c54f..60f878c169 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -69,7 +69,7 @@ class SyncConfigurationTests { } @Test - fun errorHandler_fromSyncManager() { + fun errorHandler_fromAppConfiguration() { val user: User = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) assertEquals(app.configuration.defaultErrorHandler, config.errorHandler) @@ -82,6 +82,30 @@ class SyncConfigurationTests { assertFailsWith { builder.errorHandler(TestHelper.getNull()) } } + @Test + fun clientResetHandler() { + val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + val handler = object : SyncSession.ClientResetHandler { + override fun onClientReset(session: SyncSession, error: ClientResetRequiredError) {} + } + val config = builder.clientResetHandler(handler).build() + assertEquals(handler, config.clientResetHandler) + } + + @Test + fun clientResetHandler_fromAppConfiguration() { + val user: User = createTestUser(app) + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) + assertEquals(app.configuration.defaultClientResetHandler, config.clientResetHandler) + } + + @Test + fun clientResetHandler_nullThrows() { + val user: User = createTestUser(app) + val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) + assertFailsWith { builder.clientResetHandler(TestHelper.getNull()) } + } + @Test fun equals() { val user: User = createTestUser(app) diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java index 698c1b87c5..e80f8c7ab5 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/AppConfiguration.java @@ -48,6 +48,7 @@ import io.realm.internal.log.obfuscator.TokenObfuscator; import io.realm.log.RealmLog; import io.realm.mongodb.log.obfuscator.HttpLogObfuscator; +import io.realm.mongodb.sync.ClientResetRequiredError; import io.realm.mongodb.sync.SyncSession; import static io.realm.internal.network.LoggingInterceptor.LOGIN_FEATURE; @@ -136,6 +137,7 @@ public class AppConfiguration { private final String appVersion; private final URL baseUrl; private final SyncSession.ErrorHandler defaultErrorHandler; + private final SyncSession.ClientResetHandler defaultClientResetHandler; @Nullable private final byte[] encryptionKey; private final long requestTimeoutMs; @@ -151,6 +153,7 @@ private AppConfiguration(String appId, String appVersion, URL baseUrl, SyncSession.ErrorHandler defaultErrorHandler, + SyncSession.ClientResetHandler defaultClientResetHandler, @Nullable byte[] encryptionKey, long requestTimeoutMs, String authorizationHeaderName, @@ -163,6 +166,7 @@ private AppConfiguration(String appId, this.appVersion = appVersion; this.baseUrl = baseUrl; this.defaultErrorHandler = defaultErrorHandler; + this.defaultClientResetHandler = defaultClientResetHandler; this.encryptionKey = (encryptionKey == null) ? null : Arrays.copyOf(encryptionKey, encryptionKey.length); this.requestTimeoutMs = requestTimeoutMs; this.authorizationHeaderName = (!Util.isEmptyString(authorizationHeaderName)) ? authorizationHeaderName : "Authorization"; @@ -262,6 +266,16 @@ public SyncSession.ErrorHandler getDefaultErrorHandler() { return defaultErrorHandler; } + /** + * Returns the default Client Reset handler used by synced Realms if there are problems with their + * {@link SyncSession}. + * + * @return the app default error handler. + */ + public SyncSession.ClientResetHandler getDefaultClientResetHandler() { + return defaultClientResetHandler; + } + /** * Returns the root folder containing all files and Realms used when synchronizing data * between the device and MongoDB Realm. @@ -359,11 +373,6 @@ public static class Builder { private SyncSession.ErrorHandler defaultErrorHandler = new SyncSession.ErrorHandler() { @Override public void onError(SyncSession session, AppException error) { - if (error.getErrorCode() == ErrorCode.CLIENT_RESET) { - RealmLog.error("Client Reset required for: " + session.getConfiguration().getServerUrl()); - return; - } - String errorMsg = String.format(Locale.US, "Session Error[%s]: %s", session.getConfiguration().getServerUrl(), error.toString()); @@ -379,6 +388,12 @@ public void onError(SyncSession session, AppException error) { } } }; + private SyncSession.ClientResetHandler defaultClientResetHandler = new SyncSession.ClientResetHandler() { + @Override + public void onClientReset(SyncSession session, ClientResetRequiredError error) { + RealmLog.error("Client Reset required for: " + session.getConfiguration().getServerUrl()); + } + }; private byte[] encryptionKey; private long requestTimeoutMs = TimeUnit.MILLISECONDS.convert(DEFAULT_REQUEST_TIMEOUT, TimeUnit.SECONDS); private String authorizationHeaderName; @@ -538,6 +553,22 @@ public Builder defaultSyncErrorHandler(SyncSession.ErrorHandler errorHandler) { return this; } + /** + * Sets the default Client Reset handler used by Synced Realms when they report a Client Reset. + * session. + *

              + * This default can be overridden by calling + * {@link io.realm.mongodb.sync.SyncConfiguration.Builder#clientResetHandler(SyncSession.ClientResetHandler)} when creating + * the {@link io.realm.mongodb.sync.SyncConfiguration}. + * + * @param handler the default Client Reset handler. + */ + public Builder defaultClientResetHandler(SyncSession.ClientResetHandler handler) { + Util.checkNull(handler, "handler"); + defaultClientResetHandler = handler; + return this; + } + /** * Configures the root folder containing all files and Realms used when synchronizing data * between the device and MongoDB Realm. @@ -613,6 +644,7 @@ public AppConfiguration build() { appVersion, baseUrl, defaultErrorHandler, + defaultClientResetHandler, encryptionKey, requestTimeoutMs, authorizationHeaderName, diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index 2bbeb42baa..43000c5092 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -98,6 +98,7 @@ public class SyncConfiguration extends RealmConfiguration { private final URI serverUrl; private final User user; private final SyncSession.ErrorHandler errorHandler; + private final SyncSession.ClientResetHandler clientResetHandler; private final boolean deleteRealmOnLogout; private final boolean waitForInitialData; private final long initialDataTimeoutMillis; @@ -121,6 +122,7 @@ private SyncConfiguration(File realmPath, User user, URI serverUrl, SyncSession.ErrorHandler errorHandler, + SyncSession.ClientResetHandler clientResetHandler, boolean deleteRealmOnLogout, boolean waitForInitialData, long initialDataTimeoutMillis, @@ -148,6 +150,7 @@ private SyncConfiguration(File realmPath, this.user = user; this.serverUrl = serverUrl; this.errorHandler = errorHandler; + this.clientResetHandler = clientResetHandler; this.deleteRealmOnLogout = deleteRealmOnLogout; this.waitForInitialData = waitForInitialData; this.initialDataTimeoutMillis = initialDataTimeoutMillis; @@ -356,6 +359,15 @@ public SyncSession.ErrorHandler getErrorHandler() { return errorHandler; } + /** + * Returns the Client Reset handler for this SyncConfiguration. + * + * @return the Client Reset handler. + */ + public SyncSession.ClientResetHandler getClientResetHandler() { + return clientResetHandler; + } + /** * Returns {@code true} if the Realm file must be deleted once the {@link User} owning it logs out. * @@ -462,6 +474,7 @@ public static final class Builder { private URI serverUrl; private User user = null; private SyncSession.ErrorHandler errorHandler; + private SyncSession.ClientResetHandler clientResetHandler; private OsRealmConfig.SyncSessionStopPolicy sessionStopPolicy = OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED; private CompactOnLaunchCallback compactOnLaunch; private String syncUrlPrefix = null; @@ -537,6 +550,7 @@ public Builder(User user, @Nullable Long partitionValue) { this.modules.add(Realm.getDefaultModule()); } this.errorHandler = user.getApp().getConfiguration().getDefaultErrorHandler(); + this.clientResetHandler = user.getApp().getConfiguration().getDefaultClientResetHandler(); } private void validateAndSet(User user) { @@ -804,14 +818,23 @@ public Builder inMemory() { * @throws IllegalArgumentException if {@code null} is given as an error handler. */ public Builder errorHandler(SyncSession.ErrorHandler errorHandler) { - //noinspection ConstantConditions - if (errorHandler == null) { - throw new IllegalArgumentException("Non-null 'errorHandler' required."); - } + Util.checkNull(errorHandler, "handler"); this.errorHandler = errorHandler; return this; } + /** + * Sets the handler for when a Client Reset occurs. If no handler is set, and error is + * logged when a Client Reset occurs. + * + * @param handler custom handler in case of a Client Reset. + */ + public Builder clientResetHandler(SyncSession.ClientResetHandler handler) { + Util.checkNull(handler, "handler"); + this.clientResetHandler = handler; + return this; + } + /** * Setting this will cause the Realm to download all known changes from the server the first time a Realm is * opened. The Realm will not open until all the data has been downloaded. This means that if a device is @@ -1048,6 +1071,7 @@ public SyncConfiguration build() { user, resolvedServerUrl, errorHandler, + clientResetHandler, deleteRealmOnLogout, waitForServerChanges, initialDataTimeoutMillis, diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 2fa81ac217..09ad3b64d3 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -68,6 +68,7 @@ public class SyncSession { private final long appNativePointer; private final SyncConfiguration configuration; private final ErrorHandler errorHandler; + private final ClientResetHandler clientResetHandler; private volatile boolean isClosed = false; private final AtomicReference waitingForServerChanges = new AtomicReference<>(null); @@ -158,6 +159,7 @@ static State fromNativeValue(long value) { SyncSession(SyncConfiguration configuration, long appNativePointer) { this.configuration = configuration; this.errorHandler = configuration.getErrorHandler(); + this.clientResetHandler = configuration.getClientResetHandler(); this.appNativePointer = appNativePointer; } @@ -198,8 +200,8 @@ void notifySessionError(String nativeErrorCategory, int nativeErrorCode, String if (errCode == ErrorCode.CLIENT_RESET) { // errorMessage contains the path to the backed up file RealmConfiguration backupRealmConfiguration = configuration.forErrorRecovery(errorMessage); - errorHandler.onError(this, new ClientResetRequiredError(appNativePointer, errCode, "A Client Reset is required. " + - "Read more here: https://realm.io/docs/realm-object-server/#client-recovery-from-a-backup.", + clientResetHandler.onClientReset(this, new ClientResetRequiredError(appNativePointer, errCode, "A Client Reset is required. " + + "Read more here: https://docs.realm.io/sync/using-synced-realms/errors#client-reset.", configuration, backupRealmConfiguration)); } else { AppException wrappedError; @@ -653,48 +655,62 @@ public interface ErrorHandler { * When an exception is thrown in the error handler, the occurrence will be logged and the exception * will be ignored. * - *

              - * When the {@code error.getErrorCode()} returns {@link ErrorCode#CLIENT_RESET}, it indicates the Realm - * needs to be reset and the {@code error} can be cast to {@link ClientResetRequiredError}. - *

              - * A synced Realm may need to be reset because the Realm Object Server encountered an error and had - * to be restored from a backup. If the backup copy of the remote Realm is of an earlier version - * than the local copy of the Realm, the server will ask the client to reset the Realm. - *

              - * The reset process is as follows: the local copy of the Realm is copied into a recovery directory - * for safekeeping, and then deleted from the original location. The next time the Realm for that - * URL is opened, the Realm will automatically be re-downloaded from the Realm Object Server, and - * can be used as normal. - *

              - * Data written to the Realm after the local copy of the Realm diverged from the backup remote copy - * will be present in the local recovery copy of the Realm file. The re-downloaded Realm will - * initially contain only the data present at the time the Realm was backed up on the server. - *

              - * The client reset process can be initiated in one of two ways: - *

                - *
              1. - * Run {@link ClientResetRequiredError#executeClientReset()} manually. All Realm instances must be - * closed before this method is called. - *
              2. - *
              3. - * If Client Reset isn't executed manually, it will automatically be carried out the next time all - * Realm instances have been closed and re-opened. This will most likely be - * when the app is restarted. - *
              4. - *
              - * - * WARNING: - * Any writes to the Realm file between this callback and Client Reset has been executed, will not be - * synchronized to the Object Server. Those changes will only be present in the backed up file. It is therefore - * recommended to close all open Realm instances as soon as possible. - * - * * @param session {@link SyncSession} this error happened on. * @param error type of error. */ void onError(SyncSession session, AppException error); } + /** + * Callback for the specific error event known as a Client Reset, determined by the error code + * {@link ErrorCode#CLIENT_RESET}. + *

              + * A synced Realm may need to be reset because the MongoDB Realm Server encountered an error and had + * to be restored from a backup or because it has been too long since the client connected to the + * server so the server has rotated the logs. + *

              + * The Client Reset thus occurs because the server does not have the full information required to + * bring the Client fully up to date. + *

              + * The reset process is as follows: the local copy of the Realm is copied into a recovery directory + * for safekeeping, and then deleted from the original location. The next time the Realm for that + * URL is opened, the Realm will automatically be re-downloaded from MongoDB Realm, and + * can be used as normal. + *

              + * Data written to the Realm after the local copy of the Realm diverged from the backup remote copy + * will be present in the local recovery copy of the Realm file. The re-downloaded Realm will + * initially contain only the data present at the time the Realm was backed up on the server. + *

              + * The client reset process can be initiated in one of two ways: + *

                + *
              1. + * Run {@link ClientResetRequiredError#executeClientReset()} manually. All Realm instances must be + * closed before this method is called. + *
              2. + *
              3. + * If Client Reset isn't executed manually, it will automatically be carried out the next time all + * Realm instances have been closed and re-opened. This will most likely be + * when the app is restarted. + *
              4. + *
              + * + * WARNING: + * Any writes to the Realm file between this callback and Client Reset has been executed, will not be + * synchronized to MongoDB Realm. Those changes will only be present in the backed up file. It is therefore + * recommended to close all open Realm instances as soon as possible. + */ + public interface ClientResetHandler { + /** + * Callback that indicates a Client Reset has happened. This should be handled as quickly as + * possible as any further changes to the Realm will not be synchronized with the server and + * must be moved manually from the backup Realm to the new one. + * + * @param session {@link SyncSession} this error happened on. + * @param error {@link ClientResetRequiredError} the specific Client Reset error. + */ + void onClientReset(SyncSession session, ClientResetRequiredError error); + } + // Wrapper class for handling the async operations of the underlying SyncSession calling // `async_wait_for_download_completion` or `async_wait_for_upload_completion` private static class WaitForSessionWrapper { diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 124d897993..bf5622dce1 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -603,16 +603,15 @@ class SyncSessionTests { // .clientResyncMode(ClientResyncMode.MANUAL) // FIXME Is this critical for the test //.directory(looperThread.getRoot()) - .errorHandler { session, error -> - val handler = error as ClientResetRequiredError + .clientResetHandler { session, error -> // Execute Client Reset resources.close() - handler.executeClientReset() + error.executeClientReset() // Try to re-open Realm and download it again looperThread.postRunnable(Runnable { // Validate that files have been moved - assertFalse(handler.originalFile.exists()) - assertTrue(handler.backupFile.exists()) + assertFalse(error.originalFile.exists()) + assertTrue(error.backupFile.exists()) val config = configRef.get() Realm.getInstance(config!!).use { realm -> realm.syncSession.downloadAllServerChanges() From af4b20015df15b12ca4209fbbdaa9473abd1c535 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 16 Sep 2020 19:12:00 +0200 Subject: [PATCH 1676/2110] Automate 10.x releases (#7089) --- Jenkinsfile | 59 ++++++++++++- build.gradle | 173 ++++++++++++++++++++++--------------- tools/publish_release.sh | 182 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 343 insertions(+), 71 deletions(-) create mode 100644 tools/publish_release.sh diff --git a/Jenkinsfile b/Jenkinsfile index a719103966..c8e101f8d5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,6 +5,7 @@ import groovy.json.JsonOutput buildSuccess = false +releaseBuild = false mongoDbRealmContainer = null mongoDbRealmCommandServerContainer = null emulatorContainer = null @@ -36,6 +37,26 @@ try { ]) } + // Check type of Build. We are treating this as a release build if we are building + // the exact Git SHA that was tagged. + gitTag = readGitTag() + echo "Git tag: ${gitTag ?: 'none'}" + if (!gitTag) { + gitSha = sh(returnStdout: true, script: 'git rev-parse HEAD').trim().take(8) + echo "Building non-release: ${gitSha}" + setBuildName(gitSha) + buildRelease = false + } else { + def version = readFile('version.txt').trim() + if (gitTag != "v${version}") { + error "Git tag '${gitTag}' does not match v${version}" + } else { + echo "Building release: '${gitTag}'" + setBuildName("Tag ${gitTag}") + buildRelease = true + } + } + // Toggles for PR vs. Master builds. // - For PR's, we favor speed > absolute correctness. So we just build for x86, use an // emulator and run unit tests for the ObjectServer variant. @@ -47,13 +68,14 @@ try { def instrumentationTestTarget = "connectedAndroidTest" def deviceSerial = "" if (!releaseBranches.contains(currentBranch)) { - // Bui + // Build development branch useEmulator = true emulatorImage = "system-images;android-29;default;x86" abiFilter = "-PbuildTargetABIs=x86" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" deviceSerial = "emulator-5554" } else { + // Build main/release branch // FIXME: Use emulator until we can get reliable devices on CI. // But still build all ABI's and run all types of tests. useEmulator = true @@ -127,6 +149,11 @@ try { runBuild(abiFilter, instrumentationTestTarget) } } + + // Release the library if needed + if (releaseBuild) { + runRelease() + } } } finally { // We assume that creating these containers and the docker network can be considered an atomic operation. @@ -271,6 +298,27 @@ def runBuild(abiFilter, instrumentationTestTarget) { } } +def runRelease() { + stage('Publish Release') { + withCredentials([ + [$class: 'StringBinding', credentialsId: 'slack-webhook-java-ci-channel', variable: 'SLACK_URL_CI'], + [$class: 'StringBinding', credentialsId: 'slack-webhook-releases-channel', variable: 'SLACK_URL_RELEASE'], + [$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER'], + [$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'DOCS_S3_ACCESS_KEY', credentialsId: 'mongodb-realm-docs-s3', secretKeyVariable: 'DOCS_S3_SECRET_ACCESS'], + [$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'REALM_S3_ACCESS_KEY', credentialsId: 'realm-s3', secretKeyVariable: 'REALM_S3_SECRET_ACCESS'] + ]) { + sh ''' + set +x + tools/publish_release.sh "$BINTRAY_USER" "$BINTRAY_KEY" \ + "$REALM_S3_ACCESS_KEY" "$REALM_S3_SECRET_KEY" \ + "$DOCS_S3_ACCESS_KEY" "$DOCS_S3_SECRET_KEY" \ + "$SLACK_URL_RELEASE" \ + "SLACK_URL_CI" + ''' + } + } +} + def forwardAdbPorts() { sh """ adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && adb reverse tcp:8888 tcp:8888 && adb reverse tcp:9090 tcp:9090 @@ -387,3 +435,12 @@ def gradle(String commands) { def gradle(String relativePath, String commands) { sh "cd ${relativePath} && chmod +x gradlew && ./gradlew ${commands} --stacktrace" } + +def readGitTag() { + def command = 'git describe --exact-match --tags HEAD' + def returnStatus = sh(returnStatus: true, script: command) + if (returnStatus != 0) { + return null + } + return sh(returnStdout: true, script: command).trim() +} diff --git a/build.gradle b/build.gradle index 34736008b5..09d29884d3 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,21 @@ apply plugin: 'ch.netzwerg.release' def currentVersion = file("${projectDir}/version.txt").text.trim() +// Find property in either System environment or Gradle properties. +// If set in both places, Gradle properties win. +def getPropertyValueOrThrow(String propertyName) { + def value = System.getenv(propertyName) + if (project.hasProperty(propertyName)) { + value = project.getProperty(propertyName) + } + if (value == null || value.trim().isEmpty()) { + throw new GradleException("Could not find '$propertyName'. " + + "Most be provided as either environment variable or " + + "a Gradle property.") + } + return value +} + // Shared configuration that copies relevant properties from the root level and parse them on to // child projects. def copyProperties = { @@ -195,21 +210,6 @@ task javadoc(type:GradleBuild) { configure copyProperties } -// Find property in either System environment or Gradle properties. -// If set in both places, Gradle properties win. -def getPropertyValueOrThrow(String propertyName) { - def value = System.getenv(propertyName) - if (project.hasProperty(propertyName)) { - value = project.getProperty(propertyName) - } - if (value == null || value.trim().isEmpty()) { - throw new GradleException("Could not find '$propertyName'. " + - "Most be provided as either environment variable or " + - "a Gradle property.") - } - return value -} - task createLatestJavadocRedirectFile(type: Copy) { description = 'Redirects from /java/latest/ to correct version' from 'realm/templates' @@ -287,35 +287,6 @@ task assemble { dependsOn assembleExamples } -task distributionPackage(type:Zip) { - description = 'Generate the distribution package' - dependsOn assembleRealm - dependsOn javadoc - - group = 'Artifact' - archiveName = "realm-java-${currentVersion}.zip" - destinationDir = file("${buildDir}/outputs/distribution") - - from('changelog.txt') - from('LICENSE') - from('version.txt') - from('realm.properties') - from('realm/realm-library/build/libs') { - include 'realm-android-${currentVersion}-javadoc.jar' - into 'docs' - } - from('realm/realm-library/build/docs') { - include '**/*' - into 'docs' - } - from('examples') { - exclude 'local.properties' - exclude '**/.gradle' - exclude '**/build' - into 'examples' - } -} - task distributionJniUnstrippedPackage(type:Zip) { description = 'Generate native libs package with debug symbols' dependsOn assembleRealm @@ -400,48 +371,29 @@ task manualClean { task uploadDistributionPackage { group = 'Release' description = 'Upload the distribution package to S3' - dependsOn distributionPackage dependsOn distributionJniUnstrippedPackage + def s3AccessKey = "${ -> getPropertyValueOrThrow('REALM_S3_ACCESS_KEY')}" + def s3SecretKey = "${ -> getPropertyValueOrThrow('REALM_S3_SECRET_KEY')}" doLast { exec { workingDir "${buildDir}/outputs/distribution/" - commandLine 's3cmd', 'put', "realm-java-${currentVersion}.zip", 's3://static.realm.io/downloads/java/' + commandLine 's3cmd', "--access_key=${s3AccessKey}", "--secret_key=${s3SecretKey}", 'put', "realm-java-jni-libs-unstripped-${currentVersion}.zip", 's3://static.realm.io/downloads/java/' } - exec { - workingDir "${buildDir}/outputs/distribution/" - commandLine 's3cmd', 'put', "realm-java-jni-libs-unstripped-${currentVersion}.zip", 's3://static.realm.io/downloads/java/' - } - } -} - -task createEmptyFile(type: Exec) { - group = 'Release' - description = 'Create an empty file that will serve as a link on S3' - dependsOn uploadDistributionPackage - commandLine 'touch', 'latest' -} - -['java', 'android'].each() { link -> - task "upload${link.capitalize()}LatestLink"(type: Exec) { - group = 'Release' - description = "Update the link to the latest version for ${link.capitalize()}" - dependsOn createEmptyFile - commandLine 's3cmd', 'put', 'latest', "--add-header=x-amz-website-redirect-location:/downloads/java/realm-java-${currentVersion}.zip", "s3://static.realm.io/downloads/${link}/latest" } } task uploadUpdateVersion(type: Exec) { group = 'Release' description = 'Update the file on S3 containing the latest version' - ['java', 'android'].each() { link -> - dependsOn "upload${link.capitalize()}LatestLink" - } - commandLine 's3cmd', 'put', "${rootDir}/version.txt", 's3://static.realm.io/update/java' + def s3AccessKey = "${ -> getPropertyValueOrThrow('REALM_S3_ACCESS_KEY')}" + def s3SecretKey = "${ -> getPropertyValueOrThrow('REALM_S3_SECRET_KEY')}" + commandLine 's3cmd', "--access_key=${s3AccessKey}", "--secret_key=${s3SecretKey}", 'put', "${rootDir}/version.txt", 's3://static.realm.io/update/java' } task distribute { group = 'Release' description = 'Distribute release artifacts to S3' + dependsOn uploadDistributionPackage dependsOn uploadUpdateVersion } @@ -478,6 +430,53 @@ task bintrayTransformer(type: GradleBuild) { tasks = ['bintrayUpload'] } +import groovy.json.JsonSlurper +def checkPackageIsReady(String packageName, String version) { + // See https://bintray.com/docs/api/#_get_version + def userName = project.findProperty('bintrayUser') ?: 'noUser' + def accessKey = project.findProperty('bintrayKey') ?: 'noKey' + def result = new ByteArrayOutputStream() + exec { + commandLine 'curl', + '-X', + 'GET', + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/packages/realm/maven/$packageName/versions/$version" + standardOutput = result + } + def parser = new JsonSlurper() + def commandlineResponse = result.toString() + def json = parser.parseText(commandlineResponse) + if (json["published"] != false) { + throw new GradleException("$packageName was not ready to be published: $commandlineResponse") + } +} + +int publishPackage(String packageName, String version) { + // See https://bintray.com/docs/api/#_publish_discard_uploaded_content + def userName = project.findProperty('bintrayUser') ?: 'noUser' + def accessKey = project.findProperty('bintrayKey') ?: 'noKey' + def result = new ByteArrayOutputStream() + exec { + commandLine 'curl', + '-X', + 'POST', + '-u', + "${userName}:${accessKey}", + "https://api.bintray.com/content/realm/maven/$packageName/$version/publish" + standardOutput = result + } + def parser = new JsonSlurper() + def commandlineResponse = result.toString() + println "$commandlineResponse" + def json = parser.parseText(commandlineResponse) + if (!json.keySet().contains('files')) { + throw new GradleException("$packageName was not correctly published: $commandlineResponse") + } + return json['files'] +} + task bintrayUpload { description = 'Publish all the Realm artifacts to Bintray' group = 'Publishing' @@ -485,6 +484,40 @@ task bintrayUpload { dependsOn bintrayAnnotations dependsOn bintrayGradlePlugin dependsOn bintrayTransformer + + doLast { + // All the packages we want to release + def packages = [ "realm-annotations", + "realm-transformer", + "realm-annotations-processor", + "realm-android-library", + "realm-android-library-object-server", + "realm-android-kotlin-extensions", + "realm-android-kotlin-extensions-object-server", + "realm-gradle-plugin" ] + + // Number of files we expect to have been released. If this + // does not happen, an error is thrown and manual intervention + // is needed. + def expectedFilesReleasedCount = 24 + + // Check that all packages are available on BinTray before publishing them + // This makes it possible to quickly release them using the Web UI if + // anything goes wrong. + packages.each {pckg -> + checkPackageIsReady(pckg, currentVersion); + } + + // Once we verified all packages are ready, we release them while keeping + // track of the number of released files. + def releasedFilesCount = 0 + packages.each {pckg -> + releasedFilesCount += publishPackage(pckg, currentVersion); + } + if (releasedFilesCount != expectedFilesReleasedCount) { + throw new GradleException("Something went wrong when releasing on BinTray. Number of files did not match the expected: $releasedFilesCount vs. $expectedFilesReleasedCount") + } + } } task ojoRealm(type: GradleBuild) { diff --git a/tools/publish_release.sh b/tools/publish_release.sh new file mode 100644 index 0000000000..e8a1b54417 --- /dev/null +++ b/tools/publish_release.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash + +# Script that make sure release build is correctly published in the appropriate channels. +# +# The following steps are executed: +# +# 1. Check that version in version.txt matches git tag which indicate a release. +# 2. Check that the changelog has a correct set date. +# 3. Build Javadoc +# 4. Upload all artifacts to Bintray without releasing them. +# 5. Verify that all artifacts have been uploaded, then release all of them at once. +# 6. Upload native debug symobols and update latest version number on S3. +# 7. Upload Javadoc to MongoDB Realm S3 bucket. +# 8. Notify #realm-releases and #realm-java-team-ci about the new release. + +IFS=$'\n\t' + +###################################### +# Input Validation +###################################### + +usage() { +cat < +EOF +} + +if [ "$#" -ne 8 ]; then + usage + exit 1 +fi + +###################################### +# Define Release steps +###################################### + +HERE="$(pwd)" +REALM_JAVA_PATH="$(pwd)/.." +RELEASE_VERSION="" +BINTRAY_USER="$1" +BINTRAY_KEY="$2" +REALM_S3_ACCESS_KEY="$3" +REALM_S3_SECRET_KEY="$4" +DOCS_S3_ACCESS_KEY="$5" +DOCS_S3_SECRET_KEY="$6" +SLACK_WEBHOOK_RELEASES_URL="$7" +SLACK_WEBHOOK_JAVA_CI_URL="$8" + +abort_release() { + # Reporting failures to #realm-java-team-ci is done from Jenkins + exit 1 +} + +check_env() { + echo "Checking environment..." + + # Try to find s3cmd + path_to_s3cmd=$(which s3cmd) + if [[ ! -x "$path_to_s3cmd" ]] ; then + echo "Cannot find executable file 's3cmd'. Aborting." + abort_release + exit -1 + fi + + # Try to find git + path_to_git=$(which git) + if [[ ! -x "$path_to_git" ]] ; then + echo "Cannot find executable file 'git'. Aborting." + abort_release + exit -1 + fi + + # Try to find pcregrep + path_to_pcregrep=$(which pcregrep) + if [[ ! -x "$path_to_pcregrep" ]] ; then + echo "Cannot find executable file 'pcregrep'. Aborting." + abort_release + exit -1 + fi + + echo "Environment is OK." +} + +verify_release_preconditions() { + echo "Checking release branch..." + gitTag=`git describe --tags | tr -d '[:space:]'` + version=`cat $HERE/../version.txt | tr -d '[:space:]'` + + if [[ "v$version" == "$gitTag" ]]; then + RELEASE_VERSION=$version + echo "Git tag and version.txt matches: $version. Continue releasing." + else + echo "Version in version.txt was '$version' while the branch was tagged with '$gitTag'. Aborting release." + abort_release + exit -1 + fi +} + +verify_changelog() { + echo "Checking CHANGELOG.md..." + query="grep -c '^## $RELEASE_VERSION ([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])' $HERE/../CHANGELOG.md" + + if [[ `eval $query` -ne 1 ]]; then + echo "Changelog does not appear to be correct. First line should match the version being released and the date should be set. Aborting." + abort_release + exit -1 + else + echo "CHANGELOG date and version is correctly set." + fi +} + +create_javadoc() { + echo "Creating JavaDoc..." + cd $REALM_JAVA_PATH + ./gradlew javadoc + cd $HERE +} + +create_native_debug_symbols_package() { + echo "Creating zip file with native debug symbols.." + cd $REALM_JAVA_PATH + ./gradlew distributionPackage + cd $HERE +} + +upload_to_bintray() { + echo "Releasing on Bintray..." + cd $REALM_JAVA_PATH + ./gradlew bintrayUpload -PbintrayUser=$BINTRAY_USER -PbintrayKey=$BINTRAY_KEY + cd $HERE +} + +upload_debug_symbols() { + echo "Uploading native debug symbols..." + cd $REALM_JAVA_PATH + ./gradlew distribute -PREALM_S3_ACCESS_KEY=$REALM_S3_ACCESS_KEY -PREALM_S3_ACCESS_KEY=$REALM_S3_SECRET_KEY + cd $HERE +} + +upload_javadoc() { + echo "Uploading docs..." + cd $REALM_JAVA_PATH + ./gradlew uploadJavadoc -PSDK_DOCS_AWS_ACCESS_KEY=$DOCS_S3_ACCESS_KEY -PSDK_DOCS_AWS_SECRET_KEY=$DOCS_S3_SECRET_KEY + cd $HERE +} + +notify_slack_channels() { + echo "Notifying Slack channels..." + + # Read first . Link is the value with ".",")","(" and space removed. + tag=`grep '$RELEASE_VERSION' $REALM_JAVA_PATH/CHANGELOG.md | cut -c 4- | sed 's/[.)(]//g' | sed 's/ /-/g'` + if [ -z "$tag" ]; then + echo "\$tag did not resolve correctly. Aborting." + abort_release + fi + current_branch=`git rev-parse --abbrev-ref HEAD` + if [ -z "$current_branch" ]; then + echo "Could not find current branch. Aborting." + abort_release + fi + + link_to_changelog="https://github.com/realm/realm-java/blob/$current_branch/CHANGELOG.md#$tag" + payload="{ \"username\": \"Realm CI\", \"icon_emoji\": \":realm_new:\", \"text\": \"<$link_to_changelog|*Realm Java $RELEASE_VERSION has been released*>\\nSee the Release Notes for more details.\" }" + + echo "Pinging #realm-releases" + curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_RELEASES_URL} + echo "Pinging #realm-java-team-ci" + curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_JAVA_CI_URL} +} + +###################################### +# Run Release steps +######################################\ + +check_env +verify_release_preconditions +verify_changelog +create_javadoc +upload_to_bintray +upload_debug_symbols +upload_javadoc +notify_slack_channels \ No newline at end of file From 0e1d12bed091b92c83b6df1a661178edeb2cab7b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 16 Sep 2020 20:56:45 +0200 Subject: [PATCH 1677/2110] Update to Realm Sync beta.11 (#7105) --- CHANGELOG.md | 9 +++++++-- dependencies.list | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0084009b90..cb8bd45667 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ -## 10.0.0-BETA.7 (YYYY-MM-DD) +## 10.0.0-BETA.7 (2020-09-16) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. +WARNING: This release upgrades the fileformat to 20. Non-sync Realms will be upgraded automatically. Synced Realms can only be automatically upgraded if created with Realm Java 10.0.0-BETA.1 and above. + + ### Breaking Changes * [RealmApp] Moved `User.remove()` to `App.removeUser()`. * [RealmApp] Renamed `ApiKeyAuth.createApiKey()` to `ApiKeyAuth.create()` and `ApiKeyAuth.createApiKeyAsync()` to `ApiKeyAuth.createAsync()`. @@ -37,12 +40,14 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * None. ### Compatibility -* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java 10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal * Updated to Object Store commit: 6ab48d3b4b1e0865f68b84d5993bb2aad910320b. +* Updated to Realm Sync 10.0.0-beta.11. +* Updated to Realm Core 10.0.0-beta.7. ## 10.0.0-BETA.6 (2020-08-17) diff --git a/dependencies.list b/dependencies.list index d7b53164d4..4dd0dab4e3 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC=10.0.0-beta.6 -REALM_SYNC_SHA256=824192a67e7ded59d33707265f78c8d5546b1fef473e9ee5ecff8a5bc9f8506e +REALM_SYNC=10.0.0-beta.11 +REALM_SYNC_SHA256=da27012959fdd7e35b9b0f3274a1dd7ad391e027217527cf8897020a4c1562d6 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions From fe76bb5aaa7777dec8da7446c7111d7eec43ef97 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 16 Sep 2020 22:01:09 +0200 Subject: [PATCH 1678/2110] Release 10.0.0-BETA.7 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 35024f8540..80147b05e7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.7-SNAPSHOT +10.0.0-BETA.7 \ No newline at end of file From abee5ebd313b177b87bd53211edb9320bafdca49 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 17 Sep 2020 08:24:13 +0200 Subject: [PATCH 1679/2110] Fix issues with building on v10 (#7106) --- Jenkinsfile | 14 +++++++------- realm/gradle.properties | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c8e101f8d5..869f7a9bc3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,7 @@ import groovy.json.JsonOutput buildSuccess = false -releaseBuild = false +publishBuild = false // True if this build is a full release that should be availble on Bintray mongoDbRealmContainer = null mongoDbRealmCommandServerContainer = null emulatorContainer = null @@ -45,7 +45,7 @@ try { gitSha = sh(returnStdout: true, script: 'git rev-parse HEAD').trim().take(8) echo "Building non-release: ${gitSha}" setBuildName(gitSha) - buildRelease = false + publishBuild = false } else { def version = readFile('version.txt').trim() if (gitTag != "v${version}") { @@ -53,7 +53,7 @@ try { } else { echo "Building release: '${gitTag}'" setBuildName("Tag ${gitTag}") - buildRelease = true + publishBuild = true } } @@ -151,8 +151,8 @@ try { } // Release the library if needed - if (releaseBuild) { - runRelease() + if (publishBuild) { + runPublish() } } } finally { @@ -289,7 +289,7 @@ def runBuild(abiFilter, instrumentationTestTarget) { } } - if (releaseBranches.contains(currentBranch)) { + if (releaseBranches.contains(currentBranch) && !publishBuild) { stage('Publish to OJO') { withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} ojoUpload --stacktrace" @@ -298,7 +298,7 @@ def runBuild(abiFilter, instrumentationTestTarget) { } } -def runRelease() { +def runPublish() { stage('Publish Release') { withCredentials([ [$class: 'StringBinding', credentialsId: 'slack-webhook-java-ci-channel', variable: 'SLACK_URL_CI'], diff --git a/realm/gradle.properties b/realm/gradle.properties index f2a177a0f1..29b7bf620b 100644 --- a/realm/gradle.properties +++ b/realm/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xms512m -Xmx2048m +org.gradle.jvmargs=-Xms1024m -Xmx4096m org.gradle.caching=true kotlin.incremental=false org.gradle.parallel=false From 2aec66f50bce4b0b941ed4e50717afac7c916119 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 17 Sep 2020 12:10:26 +0200 Subject: [PATCH 1680/2110] Fixes to release pipeline + correct changelog notes (#7109) --- CHANGELOG.md | 3 ++- Jenkinsfile | 2 +- tools/publish_release.sh | 13 ++----------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8bd45667..e69ddfbfa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,8 @@ WARNING: This release upgrades the fileformat to 20. Non-sync Realms will be upg * [RealmApp] Support to retrieve the MongoCollection name using `MongoCollection.getName()` ### Fixed -* None. +* If you have a realm file growing towards 2Gb and have a table with more than 16 columns, then you may get a "Key not found" exception when updating an object. If asserts are enabled at the sdk level, you may get an "assert(m_has_refs)" instead. ([#3194](https://github.com/realm/realm-js/issues/3194), since v7.0.0) +* In cases where you have more than 32 columns in a table, you may get a currrupted file resulting in various crashes ([#7057](https://github.com/realm/realm-java/issues/7057), since v7.0.0) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java 10.0.0-BETA.1. diff --git a/Jenkinsfile b/Jenkinsfile index 869f7a9bc3..65f554f43a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -309,7 +309,7 @@ def runPublish() { ]) { sh ''' set +x - tools/publish_release.sh "$BINTRAY_USER" "$BINTRAY_KEY" \ + sh tools/publish_release.sh "$BINTRAY_USER" "$BINTRAY_KEY" \ "$REALM_S3_ACCESS_KEY" "$REALM_S3_SECRET_KEY" \ "$DOCS_S3_ACCESS_KEY" "$DOCS_S3_SECRET_KEY" \ "$SLACK_URL_RELEASE" \ diff --git a/tools/publish_release.sh b/tools/publish_release.sh index e8a1b54417..ab0b731cd9 100644 --- a/tools/publish_release.sh +++ b/tools/publish_release.sh @@ -12,7 +12,6 @@ # 6. Upload native debug symobols and update latest version number on S3. # 7. Upload Javadoc to MongoDB Realm S3 bucket. # 8. Notify #realm-releases and #realm-java-team-ci about the new release. - IFS=$'\n\t' ###################################### @@ -34,8 +33,8 @@ fi # Define Release steps ###################################### -HERE="$(pwd)" -REALM_JAVA_PATH="$(pwd)/.." +HERE="$(dirname $0)" +REALM_JAVA_PATH="$HERE/.." RELEASE_VERSION="" BINTRAY_USER="$1" BINTRAY_KEY="$2" @@ -70,14 +69,6 @@ check_env() { exit -1 fi - # Try to find pcregrep - path_to_pcregrep=$(which pcregrep) - if [[ ! -x "$path_to_pcregrep" ]] ; then - echo "Cannot find executable file 'pcregrep'. Aborting." - abort_release - exit -1 - fi - echo "Environment is OK." } From 72a613390504c7539ad906862bc53afab99abe0a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 17 Sep 2020 16:04:20 +0200 Subject: [PATCH 1681/2110] More CI improvements (#7111) --- Jenkinsfile | 80 +++++++++++++++++++++++++--------------- tools/publish_release.sh | 2 +- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 65f554f43a..b86ac9e2d6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,16 +4,27 @@ import groovy.json.JsonOutput +// CONSTANTS + +// Branches from which we release SNAPSHOT's. Only release branches need to run on actual hardware. +releaseBranches = ['master', 'next-major', 'v10'] +// Branches that are "important", so if they do not compile they will generate a Slack notification +slackNotificationBranches = [ 'master', 'releases', 'next-major', 'v10' ] +// WARNING: Only set to `false` as an absolute last resort. Doing this will disable all integration +// tests. +enableIntegrationTests = true + +// RUNTIME PROPERTIES + +// Will store whether or not this build was successful. buildSuccess = false -publishBuild = false // True if this build is a full release that should be availble on Bintray +// Will be set to `true` if this build is a full release that should be available on Bintray. +// This is determined by comparing the current git tag to the version number of the build. +publishBuild = false mongoDbRealmContainer = null mongoDbRealmCommandServerContainer = null emulatorContainer = null dockerNetworkId = UUID.randomUUID().toString() -// Branches from which we release SNAPSHOT's. Only release branches need to run on actual hardware. -releaseBranches = ['master', 'next-major', 'v10'] -// Branches that are "important", so if they do not compile they will generate a Slack notification -slackNotificationBranches = [ 'master', 'releases', 'next-major', 'v10' ] currentBranch = env.CHANGE_BRANCH // FIXME: Always used the emulator until we can enable more reliable devices // 'android' nodes have android devices attached and 'brix' are physical machines in Copenhagen. @@ -183,17 +194,21 @@ try { def payload = null if (!buildSuccess) { payload = JsonOutput.toJson([ + username: "Realm CI", + icon_emoji: ":realm_new:", text: "*The ${currentBranch} branch is broken!*\n<${env.BUILD_URL}|Click here> to check the build." ]) - } - - if (currentBuild.getPreviousBuild() && currentBuild.getPreviousBuild().getResult().toString() != "SUCCESS" && buildSuccess) { + } else if (currentBuild.getPreviousBuild() && currentBuild.getPreviousBuild().getResult().toString() != "SUCCESS" && buildSuccess) { payload = JsonOutput.toJson([ + username: "Realm CI", + icon_emoji: ":realm_new:", text: "*${currentBranch} is back to normal!*\n<${env.BUILD_URL}|Click here> to check the build." ]) } - sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" + if (payload != null) { + sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" + } } } } @@ -258,15 +273,19 @@ def runBuild(abiFilter, instrumentationTestTarget) { } }, 'Instrumentation' : { - String backgroundPid - try { - backgroundPid = startLogCatCollector() - forwardAdbPorts() - gradle('realm', "${instrumentationTestTarget} ${abiFilter}") - } finally { - stopLogCatCollector(backgroundPid) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' - storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' + if (enableIntegrationTests) { + String backgroundPid + try { + backgroundPid = startLogCatCollector() + forwardAdbPorts() + gradle('realm', "${instrumentationTestTarget} ${abiFilter}") + } finally { + stopLogCatCollector(backgroundPid) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } + } else { + echo "Instrumentation tests were disabled." } }, 'Gradle Plugin' : { @@ -301,24 +320,25 @@ def runBuild(abiFilter, instrumentationTestTarget) { def runPublish() { stage('Publish Release') { withCredentials([ - [$class: 'StringBinding', credentialsId: 'slack-webhook-java-ci-channel', variable: 'SLACK_URL_CI'], - [$class: 'StringBinding', credentialsId: 'slack-webhook-releases-channel', variable: 'SLACK_URL_RELEASE'], - [$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER'], - [$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'DOCS_S3_ACCESS_KEY', credentialsId: 'mongodb-realm-docs-s3', secretKeyVariable: 'DOCS_S3_SECRET_ACCESS'], - [$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'REALM_S3_ACCESS_KEY', credentialsId: 'realm-s3', secretKeyVariable: 'REALM_S3_SECRET_ACCESS'] + [$class: 'StringBinding', credentialsId: 'slack-webhook-java-ci-channel', variable: 'SLACK_URL_CI'], + [$class: 'StringBinding', credentialsId: 'slack-webhook-releases-channel', variable: 'SLACK_URL_RELEASE'], + [$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER'], + [$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'DOCS_S3_ACCESS_KEY', credentialsId: 'mongodb-realm-docs-s3', secretKeyVariable: 'DOCS_S3_SECRET_KEY'], + [$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'REALM_S3_ACCESS_KEY', credentialsId: 'realm-s3', secretKeyVariable: 'REALM_S3_SECRET_KEY'] ]) { - sh ''' + sh """ set +x - sh tools/publish_release.sh "$BINTRAY_USER" "$BINTRAY_KEY" \ - "$REALM_S3_ACCESS_KEY" "$REALM_S3_SECRET_KEY" \ - "$DOCS_S3_ACCESS_KEY" "$DOCS_S3_SECRET_KEY" \ - "$SLACK_URL_RELEASE" \ - "SLACK_URL_CI" - ''' + sh tools/publish_release.sh '$BINTRAY_USER' '$BINTRAY_KEY' \ + '$REALM_S3_ACCESS_KEY' '$REALM_S3_SECRET_KEY' \ + '$DOCS_S3_ACCESS_KEY' '$DOCS_S3_SECRET_KEY' \ + '$SLACK_URL_RELEASE' \ + '$SLACK_URL_CI' + """ } } } + def forwardAdbPorts() { sh """ adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && adb reverse tcp:8888 tcp:8888 && adb reverse tcp:9090 tcp:9090 diff --git a/tools/publish_release.sh b/tools/publish_release.sh index ab0b731cd9..5fc97abd6a 100644 --- a/tools/publish_release.sh +++ b/tools/publish_release.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash # Script that make sure release build is correctly published in the appropriate channels. # From 956b556531d4eee95dc996f5a287d43c748473e2 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 17 Sep 2020 17:21:00 +0200 Subject: [PATCH 1682/2110] Use sh instead of bash --- tools/publish_release.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/publish_release.sh b/tools/publish_release.sh index 5fc97abd6a..ab6eed6e26 100644 --- a/tools/publish_release.sh +++ b/tools/publish_release.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # Script that make sure release build is correctly published in the appropriate channels. # @@ -12,6 +12,7 @@ # 6. Upload native debug symobols and update latest version number on S3. # 7. Upload Javadoc to MongoDB Realm S3 bucket. # 8. Notify #realm-releases and #realm-java-team-ci about the new release. +set -e IFS=$'\n\t' ###################################### From e26686b47734e02eadc29a3a8ad2ab359a561298 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 17 Sep 2020 20:39:19 +0200 Subject: [PATCH 1683/2110] Run publish outside the docker image used for building --- Jenkinsfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b86ac9e2d6..0a3e6952e5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -160,12 +160,13 @@ try { runBuild(abiFilter, instrumentationTestTarget) } } + } - // Release the library if needed - if (publishBuild) { - runPublish() - } + // Release the library if needed + if (publishBuild) { + runPublish() } + } finally { // We assume that creating these containers and the docker network can be considered an atomic operation. if (mongoDbRealmContainer != null && mongoDbRealmCommandServerContainer != null) { From 6d4ea1267a45ce7f57d7c42171b39de71ee6844a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 17 Sep 2020 23:49:03 +0200 Subject: [PATCH 1684/2110] Make publish script work in bourne shell. Call publish steps from docker image responsible for builds. --- Jenkinsfile | 11 +++++----- tools/publish_release.sh | 47 ++++++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0a3e6952e5..c66a5dd577 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -159,14 +159,13 @@ try { } else { runBuild(abiFilter, instrumentationTestTarget) } - } - } - // Release the library if needed - if (publishBuild) { - runPublish() + // Release the library if needed + if (publishBuild) { + runPublish() + } + } } - } finally { // We assume that creating these containers and the docker network can be considered an atomic operation. if (mongoDbRealmContainer != null && mongoDbRealmCommandServerContainer != null) { diff --git a/tools/publish_release.sh b/tools/publish_release.sh index ab6eed6e26..28c688e95c 100644 --- a/tools/publish_release.sh +++ b/tools/publish_release.sh @@ -13,7 +13,6 @@ # 7. Upload Javadoc to MongoDB Realm S3 bucket. # 8. Notify #realm-releases and #realm-java-team-ci about the new release. set -e -IFS=$'\n\t' ###################################### # Input Validation @@ -34,7 +33,7 @@ fi # Define Release steps ###################################### -HERE="$(dirname $0)" +HERE=$(dirname `realpath "$0"`) REALM_JAVA_PATH="$HERE/.." RELEASE_VERSION="" BINTRAY_USER="$1" @@ -55,19 +54,19 @@ check_env() { echo "Checking environment..." # Try to find s3cmd - path_to_s3cmd=$(which s3cmd) - if [[ ! -x "$path_to_s3cmd" ]] ; then + path_to_s3cmd=$(type s3cmd) + if [ -x "$path_to_s3cmd" ] + then echo "Cannot find executable file 's3cmd'. Aborting." abort_release - exit -1 fi # Try to find git - path_to_git=$(which git) - if [[ ! -x "$path_to_git" ]] ; then + path_to_git=$(type git) + if [ -x "$path_to_git" ] + then echo "Cannot find executable file 'git'. Aborting." abort_release - exit -1 fi echo "Environment is OK." @@ -76,26 +75,26 @@ check_env() { verify_release_preconditions() { echo "Checking release branch..." gitTag=`git describe --tags | tr -d '[:space:]'` - version=`cat $HERE/../version.txt | tr -d '[:space:]'` - - if [[ "v$version" == "$gitTag" ]]; then - RELEASE_VERSION=$version - echo "Git tag and version.txt matches: $version. Continue releasing." + version=`cat $REALM_JAVA_PATH/version.txt | tr -d '[:space:]'` + + if [ "v$version" = "$gitTag" ] + then + RELEASE_VERSION=$version + echo "Git tag and version.txt matches: $version. Continue releasing." else echo "Version in version.txt was '$version' while the branch was tagged with '$gitTag'. Aborting release." - abort_release - exit -1 + abort_release fi } verify_changelog() { echo "Checking CHANGELOG.md..." - query="grep -c '^## $RELEASE_VERSION ([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])' $HERE/../CHANGELOG.md" + query="grep -c '^## $RELEASE_VERSION ([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])' $REALM_JAVA_PATH/CHANGELOG.md" - if [[ `eval $query` -ne 1 ]]; then + if [ `eval $query` -ne 1 ] + then echo "Changelog does not appear to be correct. First line should match the version being released and the date should be set. Aborting." - abort_release - exit -1 + abort_release else echo "CHANGELOG date and version is correctly set." fi @@ -141,12 +140,14 @@ notify_slack_channels() { # Read first . Link is the value with ".",")","(" and space removed. tag=`grep '$RELEASE_VERSION' $REALM_JAVA_PATH/CHANGELOG.md | cut -c 4- | sed 's/[.)(]//g' | sed 's/ /-/g'` - if [ -z "$tag" ]; then + if [ -z "$tag" ] + then echo "\$tag did not resolve correctly. Aborting." abort_release fi current_branch=`git rev-parse --abbrev-ref HEAD` - if [ -z "$current_branch" ]; then + if [ -z "$current_branch" ] + then echo "Could not find current branch. Aborting." abort_release fi @@ -154,9 +155,9 @@ notify_slack_channels() { link_to_changelog="https://github.com/realm/realm-java/blob/$current_branch/CHANGELOG.md#$tag" payload="{ \"username\": \"Realm CI\", \"icon_emoji\": \":realm_new:\", \"text\": \"<$link_to_changelog|*Realm Java $RELEASE_VERSION has been released*>\\nSee the Release Notes for more details.\" }" - echo "Pinging #realm-releases" + echo "Pinging #realm-releases" curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_RELEASES_URL} - echo "Pinging #realm-java-team-ci" + echo "Pinging #realm-java-team-ci" curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_JAVA_CI_URL} } From f2e992a6817ba1d154bfcd7e1314f8e3b5d00c37 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 18 Sep 2020 00:57:33 +0200 Subject: [PATCH 1685/2110] Use correct credentials when uploading to S3 --- tools/publish_release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/publish_release.sh b/tools/publish_release.sh index 28c688e95c..ddbbd9de17 100644 --- a/tools/publish_release.sh +++ b/tools/publish_release.sh @@ -124,7 +124,7 @@ upload_to_bintray() { upload_debug_symbols() { echo "Uploading native debug symbols..." cd $REALM_JAVA_PATH - ./gradlew distribute -PREALM_S3_ACCESS_KEY=$REALM_S3_ACCESS_KEY -PREALM_S3_ACCESS_KEY=$REALM_S3_SECRET_KEY + ./gradlew distribute -PREALM_S3_ACCESS_KEY=$REALM_S3_ACCESS_KEY -PREALM_S3_SECRET_KEY=$REALM_S3_SECRET_KEY cd $HERE } From a3e48e3dbcb1caa9585a7388d4ef3c1ed8db0f7c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 18 Sep 2020 08:10:10 +0200 Subject: [PATCH 1686/2110] Fix not creating tag for Slack notifications. Script formatting. --- tools/publish_release.sh | 55 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/tools/publish_release.sh b/tools/publish_release.sh index ddbbd9de17..ae3013ebf2 100644 --- a/tools/publish_release.sh +++ b/tools/publish_release.sh @@ -25,8 +25,8 @@ EOF } if [ "$#" -ne 8 ]; then - usage - exit 1 + usage + exit 1 fi ###################################### @@ -51,25 +51,25 @@ abort_release() { } check_env() { - echo "Checking environment..." - - # Try to find s3cmd - path_to_s3cmd=$(type s3cmd) - if [ -x "$path_to_s3cmd" ] - then - echo "Cannot find executable file 's3cmd'. Aborting." - abort_release - fi - - # Try to find git - path_to_git=$(type git) - if [ -x "$path_to_git" ] - then - echo "Cannot find executable file 'git'. Aborting." - abort_release - fi - - echo "Environment is OK." + echo "Checking environment..." + + # Try to find s3cmd + path_to_s3cmd=$(type s3cmd) + if [ -x "$path_to_s3cmd" ] + then + echo "Cannot find executable file 's3cmd'. Aborting." + abort_release + fi + + # Try to find git + path_to_git=$(type git) + if [ -x "$path_to_git" ] + then + echo "Cannot find executable file 'git'. Aborting." + abort_release + fi + + echo "Environment is OK." } verify_release_preconditions() { @@ -139,7 +139,8 @@ notify_slack_channels() { echo "Notifying Slack channels..." # Read first . Link is the value with ".",")","(" and space removed. - tag=`grep '$RELEASE_VERSION' $REALM_JAVA_PATH/CHANGELOG.md | cut -c 4- | sed 's/[.)(]//g' | sed 's/ /-/g'` + command="grep '$RELEASE_VERSION' $REALM_JAVA_PATH/CHANGELOG.md | cut -c 4- | sed -e 's/[.)(]//g' | sed -e 's/ /-/g'" + tag=`eval $command` if [ -z "$tag" ] then echo "\$tag did not resolve correctly. Aborting." @@ -154,11 +155,11 @@ notify_slack_channels() { link_to_changelog="https://github.com/realm/realm-java/blob/$current_branch/CHANGELOG.md#$tag" payload="{ \"username\": \"Realm CI\", \"icon_emoji\": \":realm_new:\", \"text\": \"<$link_to_changelog|*Realm Java $RELEASE_VERSION has been released*>\\nSee the Release Notes for more details.\" }" - - echo "Pinging #realm-releases" - curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_RELEASES_URL} - echo "Pinging #realm-java-team-ci" - curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_JAVA_CI_URL} + echo $link_to_changelog + echo "Pinging #realm-releases" + curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_RELEASES_URL} + echo "Pinging #realm-java-team-ci" + curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_JAVA_CI_URL} } ###################################### From e2952e45560e1a470a7f1ed95a22c0ca00c5eb62 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 18 Sep 2020 08:49:59 +0200 Subject: [PATCH 1687/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 80147b05e7..0d27bcbba7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.7 \ No newline at end of file +10.0.0-BETA.8-SNAPSHOT \ No newline at end of file From bf4d30f5fa83f85c938bbb9370e1fed3f33eb58c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 18 Sep 2020 13:56:25 +0200 Subject: [PATCH 1688/2110] Upgrade to Realm Sync 5.0.23 (#7115) --- CHANGELOG.md | 6 ++++-- dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acaf772383..0c3985c084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes * Fixes concurrent modification exceptions in the schema when refreshing a Realm (Issue [#6876](https://github.com/realm/realm-java/issues/6876)) +* If you use encryption your application cound crash with a message like "Opening Realm files of format version 0 is not supported by this version of Realm". ([#6889](https://github.com/realm/realm-java/issues/6889) among others, since v7.0.0) ### Compatibility * Realm Object Server: 3.23.1 or later. @@ -12,8 +13,9 @@ * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. ### Internal -* None. - +* Upgraded to Object Store commit: e29b5515df8b8adfe2454424b78878bb63879307. +* Upgraded to Realm Sync: 5.0.23. +* Upgraded to Realm Core: 6.0.26. ## 7.0.5 (2020-09-09) diff --git a/dependencies.list b/dependencies.list index d3302f0eae..72eafa433a 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.22 -REALM_SYNC_SHA256=bdb9b2655f87808faaf6a26b3b5faa3c710965303d082c6e7557bae779dd1a0b +REALM_SYNC_VERSION=5.0.23 +REALM_SYNC_SHA256=30f0dcc7f2975b0a1aced8255080289e59836fd8b8ca8816a4cac499db5c2b21 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 286d7cb2f1..e29b5515df 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 286d7cb2f10c41f89a2efb43b22938610ccad4cf +Subproject commit e29b5515df8b8adfe2454424b78878bb63879307 From d461ad514f7bcde59166adbb1716ee03ebddccad Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 18 Sep 2020 14:01:48 +0200 Subject: [PATCH 1689/2110] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c3985c084..6721e719fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 7.0.6 (yyyy-mm-dd) +## 7.0.6 (2020-09-18) ### Enhancements * Better exception messaging for UTF encoding errors. ([Issue #7093](https://github.com/realm/realm-java/pull/7093)) From a6cbc1ee60ea748e2895190d20d3e517fa118061 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 18 Sep 2020 14:02:26 +0200 Subject: [PATCH 1690/2110] Release v7.0.6 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index df3ac08d90..ec997167de 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.6-SNAPSHOT \ No newline at end of file +7.0.6 \ No newline at end of file From 5da02255366e575a7a2503fbf55dda6fb637a15c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 18 Sep 2020 14:02:26 +0200 Subject: [PATCH 1691/2110] Prepare next release v7.0.7-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index ec997167de..7a4737edf0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.6 \ No newline at end of file +7.0.7-SNAPSHOT \ No newline at end of file From 789497ea3cf8112ff4579f7cb00073e722131707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 22 Sep 2020 14:41:55 +0200 Subject: [PATCH 1692/2110] Update changelog to reflect Realm Studio releases (#7120) --- CHANGELOG.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 219562bcf5..2e7d0d7495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Compatibility * Realm Object Server: 3.23.1 or later. +* Realm Studio: 5.0.0 or later. * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. @@ -28,6 +29,7 @@ ### Compatibility * Realm Object Server: 3.23.1 or later. +* Realm Studio: 5.0.0 or later. * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. @@ -38,7 +40,7 @@ ## 7.0.4 (2020-09-08) -Note: Fileformat has been bumped from 10 to 11. This means that downgrading to an earlier version of Realm is not possible and the latest version of Realm Studio must be used to view Realm files. +Note: Fileformat has been bumped from 10 to 11. This means that downgrading to an earlier version of Realm is not possible and Realm Studio 5.0.0 must be used to view Realm files. ### Enhancements * None. @@ -50,6 +52,7 @@ Note: Fileformat has been bumped from 10 to 11. This means that downgrading to a ### Compatibility * Realm Object Server: 3.23.1 or later. +* Realm Studio: 5.0.0 or later. * File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. @@ -70,6 +73,7 @@ Note: Fileformat has been bumped from 10 to 11. This means that downgrading to a ### Compatibility * Realm Object Server: 3.23.1 or later. +* Realm Studio: 4.0.0 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. @@ -93,6 +97,7 @@ Note: Fileformat has been bumped from 10 to 11. This means that downgrading to a ### Compatibility * Realm Object Server: 3.23.1 or later. +* Realm Studio: 4.0.0 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. @@ -114,6 +119,7 @@ Note: Fileformat has been bumped from 10 to 11. This means that downgrading to a ### Compatibility * Realm Object Server: 3.23.1 or later. +* Realm Studio: 4.0.0 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. @@ -124,7 +130,7 @@ Note: Fileformat has been bumped from 10 to 11. This means that downgrading to a ## 7.0.0 (2020-05-16) -NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Studio 3.11](https://github.com/realm/realm-studio/releases/tag/v3.11.0) or later will be able to open the new file format. +NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Realm Studio 4](https://github.com/realm/realm-studio/releases/tag/v4.0.0) or later will be able to open the new file format. ### Breaking Changes * [ObjectServer] Removed deprecated method `SyncConfiguration.Builder.partialRealm()`. Use `SyncConfiguration.Builder.fullSynchronization()` instead. @@ -152,7 +158,7 @@ NOTE: This version bumps the Realm file format to version 10. Files created with ### Compatibility * Realm Object Server: 3.23.1 or later. -* Realm Studio: 3.11 or later. +* Realm Studio: 4.0.0 or later. * File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). * APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. From 869a6b8e4140fd518cedee96781cc3a10a5a502d Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 23 Sep 2020 09:55:28 +0200 Subject: [PATCH 1693/2110] Fix incorrect HTTP headers and resetPassword args (#7121) --- CHANGELOG.md | 19 +++++++++++++++++++ dependencies.list | 2 +- .../kotlin/io/realm/ApiKeyAuthTests.kt | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../network/OkHttpNetworkTransport.java | 14 +++++++------- .../java/io/realm/mongodb/Credentials.java | 10 +++++----- .../realm/mongodb/auth/EmailPasswordAuth.java | 4 +++- 7 files changed, 37 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69ddfbfa5..e04c151a8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## 10.0.0-BETA.8 (YYYY-MM-DD) + +We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. + +The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. + +### Fixed +* [RealmApp] Logging in caused an `token contains an invalid number of segments` error. (Issue [#7117](https://github.com/realm/realm-java/issues/7117), since 10.0.0-BETA.7) +* [RealmApp] The order of arguments to `EmailPassword.resetPassword()` was not handled correctly, resulting in resetting the password failing. (Issue [#7116](https://github.com/realm/realm-java/issues/7116), since 10.0.0-BETA.1) + +### Compatibility +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java 10.0.0-BETA.1. +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 and above is required to open Realms created by this version. + +### Internal +* Updated to Object Store commit: 035eb07f3ef313bfb78c046be9cf6b4f065d6772. + + ## 10.0.0-BETA.7 (2020-09-16) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. diff --git a/dependencies.list b/dependencies.list index 4dd0dab4e3..b78ab32718 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=da27012959fdd7e35b9b0f3274a1dd7ad391e027217527cf8897020a4c1562 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER=2020-08-27 +MONGODB_REALM_SERVER=2020-09-21 # Common Android settings across projects GRADLE_BUILD_TOOLS=4.0.0 diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt index 81c46fddd2..970d96c410 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ApiKeyAuthTests.kt @@ -462,7 +462,7 @@ class ApiKeyAuthTests { } fail("$method should have thrown an exception") } catch (error: AppException) { - assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) + assertEquals(ErrorCode.SERVICE_UNKNOWN, error.errorCode) } } } diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 6ab48d3b4b..035eb07f3e 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 6ab48d3b4b1e0865f68b84d5993bb2aad910320b +Subproject commit 035eb07f3ef313bfb78c046be9cf6b4f065d6772 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index 8494dbad6d..72fcc0da78 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -38,12 +38,14 @@ public OkHttpNetworkTransport(@Nullable HttpLogObfuscator httpLogObfuscator) { private okhttp3.Request makeRequest(String method, String url, Map headers, String body){ okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(url); - // TODO Ensure that we have correct custom headers until OS handles it - // first of all add all custom headers + + // Ensure that we have correct custom headers until OS handles it. + + // 1. First of all add all custom headers for (Map.Entry entry : getCustomRequestHeaders().entrySet()) { builder.addHeader(entry.getKey(), entry.getValue()); } - // and then replace default authorization header with custom one if present + // 2. Then replace default authorization header with custom one if present String authorizationHeaderValue = headers.get(AppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME); String authorizationHeaderName = getAuthorizationHeaderName(); if (authorizationHeaderValue != null && !AppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME.equals(authorizationHeaderName)) { @@ -51,9 +53,11 @@ private okhttp3.Request makeRequest(String method, String url, Map entry : headers.entrySet()) { builder.addHeader(entry.getKey(), entry.getValue()); } + switch (method) { case "get": builder.get(); @@ -74,10 +78,6 @@ private okhttp3.Request makeRequest(String method, String url, Map entry : headers.entrySet()) { - builder.addHeader(entry.getKey(), entry.getValue()); - } - return builder.build(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java index 7861665804..8a4f53d52d 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java @@ -150,17 +150,17 @@ public static Credentials facebook(String accessToken) { } /** - * Creates credentials representing a login using a Google access token. + * Creates credentials representing a login using a Google Authorization Code. *

              * This provider must be enabled on MongoDB Realm to work. * - * @param googleToken the access token returned when logging in to Google. + * @param authorizationCode the authorization code returned when logging in to Google. * @return a set of credentials that can be used to log into MongoDB Realm using * {@link App#loginAsync(Credentials, App.Callback)}. */ - public static Credentials google(String googleToken) { - Util.checkEmpty(googleToken, "googleToken"); - return new Credentials(OsAppCredentials.google(googleToken), Provider.GOOGLE); + public static Credentials google(String authorizationCode) { + Util.checkEmpty(authorizationCode, "authorizationCode"); + return new Credentials(OsAppCredentials.google(authorizationCode), Provider.GOOGLE); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java index eb366c49ad..e67336eafc 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/EmailPasswordAuth.java @@ -285,7 +285,9 @@ public void resetPassword(String token, String tokenId, String newPassword) thro Util.checkEmpty(tokenId, "tokenId"); Util.checkEmpty(newPassword, "newPassword"); AtomicReference error = new AtomicReference<>(null); - call(TYPE_RESET_PASSWORD, new OsJNIVoidResultCallback(error), token, tokenId, newPassword); + // The order of arguments in ObjectStore is different than the order of arguments in the + // Java API. The Java API order came from the old Stitch API. + call(TYPE_RESET_PASSWORD, new OsJNIVoidResultCallback(error), newPassword, token, tokenId); ResultHandler.handleResult(null, error); } From 69632c31e615c67334c6f4e343791f427d2470ea Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 23 Sep 2020 09:56:55 +0200 Subject: [PATCH 1694/2110] Release 10.0.0-BETA.8 --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e04c151a8d..3651cf2677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.0-BETA.8 (YYYY-MM-DD) +## 10.0.0-BETA.8 (2020-09-23) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. diff --git a/version.txt b/version.txt index 0d27bcbba7..08f0df546e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.8-SNAPSHOT \ No newline at end of file +10.0.0-BETA.8 \ No newline at end of file From 6db099b642833b9a0c6979c9f01c7cff4e6e332b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 23 Sep 2020 11:41:34 +0200 Subject: [PATCH 1695/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 08f0df546e..43447710d9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.8 \ No newline at end of file +10.0.0-BETA.9-SNAPSHOT \ No newline at end of file From 40daa8b1ae58195b464b22ea525c42db4330a67b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 23 Sep 2020 12:03:08 +0200 Subject: [PATCH 1696/2110] Use commit instead of branch to create changelog link. Replace tabs with spaces for indentation. (#7114) --- tools/publish_release.sh | 134 +++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/tools/publish_release.sh b/tools/publish_release.sh index ae3013ebf2..79ae740601 100644 --- a/tools/publish_release.sh +++ b/tools/publish_release.sh @@ -57,109 +57,109 @@ check_env() { path_to_s3cmd=$(type s3cmd) if [ -x "$path_to_s3cmd" ] then - echo "Cannot find executable file 's3cmd'. Aborting." - abort_release + echo "Cannot find executable file 's3cmd'. Aborting." + abort_release fi # Try to find git path_to_git=$(type git) if [ -x "$path_to_git" ] then - echo "Cannot find executable file 'git'. Aborting." - abort_release + echo "Cannot find executable file 'git'. Aborting." + abort_release fi echo "Environment is OK." } verify_release_preconditions() { - echo "Checking release branch..." - gitTag=`git describe --tags | tr -d '[:space:]'` - version=`cat $REALM_JAVA_PATH/version.txt | tr -d '[:space:]'` - - if [ "v$version" = "$gitTag" ] - then - RELEASE_VERSION=$version - echo "Git tag and version.txt matches: $version. Continue releasing." - else - echo "Version in version.txt was '$version' while the branch was tagged with '$gitTag'. Aborting release." - abort_release - fi + echo "Checking release branch..." + gitTag=`git describe --tags | tr -d '[:space:]'` + version=`cat $REALM_JAVA_PATH/version.txt | tr -d '[:space:]'` + + if [ "v$version" = "$gitTag" ] + then + RELEASE_VERSION=$version + echo "Git tag and version.txt matches: $version. Continue releasing." + else + echo "Version in version.txt was '$version' while the branch was tagged with '$gitTag'. Aborting release." + abort_release + fi } verify_changelog() { - echo "Checking CHANGELOG.md..." - query="grep -c '^## $RELEASE_VERSION ([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])' $REALM_JAVA_PATH/CHANGELOG.md" - - if [ `eval $query` -ne 1 ] - then - echo "Changelog does not appear to be correct. First line should match the version being released and the date should be set. Aborting." - abort_release - else - echo "CHANGELOG date and version is correctly set." - fi + echo "Checking CHANGELOG.md..." + query="grep -c '^## $RELEASE_VERSION ([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])' $REALM_JAVA_PATH/CHANGELOG.md" + + if [ `eval $query` -ne 1 ] + then + echo "Changelog does not appear to be correct. First line should match the version being released and the date should be set. Aborting." + abort_release + else + echo "CHANGELOG date and version is correctly set." + fi } create_javadoc() { - echo "Creating JavaDoc..." - cd $REALM_JAVA_PATH - ./gradlew javadoc - cd $HERE + echo "Creating JavaDoc..." + cd $REALM_JAVA_PATH + ./gradlew javadoc + cd $HERE } create_native_debug_symbols_package() { - echo "Creating zip file with native debug symbols.." - cd $REALM_JAVA_PATH - ./gradlew distributionPackage - cd $HERE + echo "Creating zip file with native debug symbols.." + cd $REALM_JAVA_PATH + ./gradlew distributionPackage + cd $HERE } upload_to_bintray() { - echo "Releasing on Bintray..." - cd $REALM_JAVA_PATH - ./gradlew bintrayUpload -PbintrayUser=$BINTRAY_USER -PbintrayKey=$BINTRAY_KEY - cd $HERE + echo "Releasing on Bintray..." + cd $REALM_JAVA_PATH + ./gradlew bintrayUpload -PbintrayUser=$BINTRAY_USER -PbintrayKey=$BINTRAY_KEY + cd $HERE } upload_debug_symbols() { - echo "Uploading native debug symbols..." - cd $REALM_JAVA_PATH - ./gradlew distribute -PREALM_S3_ACCESS_KEY=$REALM_S3_ACCESS_KEY -PREALM_S3_SECRET_KEY=$REALM_S3_SECRET_KEY - cd $HERE + echo "Uploading native debug symbols..." + cd $REALM_JAVA_PATH + ./gradlew distribute -PREALM_S3_ACCESS_KEY=$REALM_S3_ACCESS_KEY -PREALM_S3_SECRET_KEY=$REALM_S3_SECRET_KEY + cd $HERE } upload_javadoc() { - echo "Uploading docs..." - cd $REALM_JAVA_PATH - ./gradlew uploadJavadoc -PSDK_DOCS_AWS_ACCESS_KEY=$DOCS_S3_ACCESS_KEY -PSDK_DOCS_AWS_SECRET_KEY=$DOCS_S3_SECRET_KEY - cd $HERE + echo "Uploading docs..." + cd $REALM_JAVA_PATH + ./gradlew uploadJavadoc -PSDK_DOCS_AWS_ACCESS_KEY=$DOCS_S3_ACCESS_KEY -PSDK_DOCS_AWS_SECRET_KEY=$DOCS_S3_SECRET_KEY + cd $HERE } notify_slack_channels() { - echo "Notifying Slack channels..." - - # Read first . Link is the value with ".",")","(" and space removed. - command="grep '$RELEASE_VERSION' $REALM_JAVA_PATH/CHANGELOG.md | cut -c 4- | sed -e 's/[.)(]//g' | sed -e 's/ /-/g'" - tag=`eval $command` - if [ -z "$tag" ] - then - echo "\$tag did not resolve correctly. Aborting." - abort_release - fi - current_branch=`git rev-parse --abbrev-ref HEAD` - if [ -z "$current_branch" ] - then - echo "Could not find current branch. Aborting." - abort_release - fi - - link_to_changelog="https://github.com/realm/realm-java/blob/$current_branch/CHANGELOG.md#$tag" - payload="{ \"username\": \"Realm CI\", \"icon_emoji\": \":realm_new:\", \"text\": \"<$link_to_changelog|*Realm Java $RELEASE_VERSION has been released*>\\nSee the Release Notes for more details.\" }" + echo "Notifying Slack channels..." + + # Read first . Link is the value with ".",")","(" and space removed. + command="grep '$RELEASE_VERSION' $REALM_JAVA_PATH/CHANGELOG.md | cut -c 4- | sed -e 's/[.)(]//g' | sed -e 's/ /-/g'" + tag=`eval $command` + if [ -z "$tag" ] + then + echo "\$tag did not resolve correctly. Aborting." + abort_release + fi + current_commit=`git rev-parse HEAD` + if [ -z "$current_commit" ] + then + echo "Could not find current commit. Aborting." + abort_release + fi + + link_to_changelog="https://github.com/realm/realm-java/blob/$current_commit/CHANGELOG.md#$tag" + payload="{ \"username\": \"Realm CI\", \"icon_emoji\": \":realm_new:\", \"text\": \"<$link_to_changelog|*Realm Java $RELEASE_VERSION has been released*>\\nSee the Release Notes for more details.\" }" echo $link_to_changelog echo "Pinging #realm-releases" - curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_RELEASES_URL} + curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_RELEASES_URL} echo "Pinging #realm-java-team-ci" - curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_JAVA_CI_URL} + curl -X POST --data-urlencode "payload=${payload}" ${SLACK_WEBHOOK_JAVA_CI_URL} } ###################################### From ca97c8758175e5eab6c7fa5a10f4319eeda712f0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 25 Sep 2020 15:08:43 +0200 Subject: [PATCH 1697/2110] Bump to Sync 5.0.25 (#7127) --- CHANGELOG.md | 19 +++++++++++++++++++ dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6721e719fe..60220a9ec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## 7.0.7 (YYYY-MM-DD) + +### Enhancements +* None. + +### Fixes +* When querying a class where object references are part of the condition, the application may crash if objects have recently been added to the target table. (Issue [#7118](https://github.com/realm/realm-java/issues/7118), since v7.0.0) + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. + +### Internal +* Upgraded to Object Store commit: 37e86c2905bfd424c16fc5d7860a1298bfc0ffa2. +* Upgraded to Realm Sync: 5.0.25. +* Upgraded to Realm Core: 6.1.1. + + ## 7.0.6 (2020-09-18) ### Enhancements diff --git a/dependencies.list b/dependencies.list index 72eafa433a..c954128142 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.23 -REALM_SYNC_SHA256=30f0dcc7f2975b0a1aced8255080289e59836fd8b8ca8816a4cac499db5c2b21 +REALM_SYNC_VERSION=5.0.25 +REALM_SYNC_SHA256=c5149e87fa476699c6200b210c4c5cb9ef8146e711a705c1dca70370a5ee3189 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index e29b5515df..37e86c2905 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit e29b5515df8b8adfe2454424b78878bb63879307 +Subproject commit 37e86c2905bfd424c16fc5d7860a1298bfc0ffa2 From f4a2935fb570c7bc0a78336312883333ceb13996 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 25 Sep 2020 15:15:32 +0200 Subject: [PATCH 1698/2110] Set release date for 7.0.7 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60220a9ec0..01058448c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 7.0.7 (YYYY-MM-DD) +## 7.0.7 (2020-09-25) ### Enhancements * None. From 440e4c5227d57e57d4cfe2c79899ed459742e970 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 25 Sep 2020 15:15:54 +0200 Subject: [PATCH 1699/2110] Release v7.0.7 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 7a4737edf0..bf993904af 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.7-SNAPSHOT \ No newline at end of file +7.0.7 \ No newline at end of file From 820a2e6238debd2db2909b37071770b5c5ee5c82 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 25 Sep 2020 15:15:55 +0200 Subject: [PATCH 1700/2110] Prepare next release v7.0.8-SNAPSHOT --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index bf993904af..a1aabc201f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.0.7 \ No newline at end of file +7.0.8-SNAPSHOT \ No newline at end of file From 5b4ec918faec47ab5ae00a31f49819397fb8eab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Tue, 29 Sep 2020 11:28:32 +0200 Subject: [PATCH 1701/2110] Add support for Realm.executeTransaction using coroutines and RealmResults.toFlow (#7088) --- CHANGELOG.md | 26 +- dependencies.list | 1 + examples/build.gradle | 4 + .../src/main/AndroidManifest.xml | 1 + examples/coroutinesExample/build.gradle | 72 +++ examples/coroutinesExample/proguard-rules.pro | 21 + .../src/main/AndroidManifest.xml | 24 + .../coroutinesexample/MainActivity.kt | 34 ++ .../coroutinesexample/MainApplication.kt | 30 + .../examples/coroutinesexample/model/Dog.kt | 11 + .../repository/Repository.kt | 62 +++ .../coroutinesexample/ui/main/MainFragment.kt | 96 ++++ .../ui/main/MainViewModel.kt | 82 +++ .../drawable-v24/ic_launcher_foreground.xml | 30 + .../res/drawable/ic_launcher_background.xml | 170 ++++++ .../src/main/res/layout/main_activity.xml | 7 + .../src/main/res/layout/main_fragment.xml | 64 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3593 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5339 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2636 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3388 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7472 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11873 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16570 bytes .../src/main/res/values/colors.xml | 6 + .../src/main/res/values/strings.xml | 3 + .../src/main/res/values/styles.xml | 10 + .../src/main/AndroidManifest.xml | 9 +- examples/gradle.properties | 1 + .../src/main/AndroidManifest.xml | 9 +- .../src/main/AndroidManifest.xml | 10 +- .../src/main/AndroidManifest.xml | 9 +- examples/settings.gradle | 1 + realm/build.gradle | 1 + realm/kotlin-extensions/build.gradle | 12 + .../kotlin/io/realm/CoroutineTests.kt | 512 ++++++++++++++++++ .../kotlin/io/realm/KotlinRealmTests.kt | 11 +- .../kotlin/io/realm/entities/SimpleClass.kt | 1 - .../src/main/AndroidManifest.xml | 3 +- .../kotlin/io/realm/kotlin/RealmExtensions.kt | 33 +- .../io/realm/kotlin/RealmListExtensions.kt | 92 ++++ .../io/realm/kotlin/RealmModelExtensions.kt | 32 +- .../io/realm/kotlin/RealmObjectExtensions.kt | 149 +++++ .../io/realm/kotlin/RealmResultsExtensions.kt | 100 ++++ .../src/main/java/io/realm/BaseRealm.java | 6 + 50 files changed, 1719 insertions(+), 36 deletions(-) create mode 100644 examples/coroutinesExample/build.gradle create mode 100644 examples/coroutinesExample/proguard-rules.pro create mode 100644 examples/coroutinesExample/src/main/AndroidManifest.xml create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainActivity.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainApplication.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/model/Dog.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/repository/Repository.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainFragment.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainViewModel.kt create mode 100644 examples/coroutinesExample/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 examples/coroutinesExample/src/main/res/drawable/ic_launcher_background.xml create mode 100644 examples/coroutinesExample/src/main/res/layout/main_activity.xml create mode 100644 examples/coroutinesExample/src/main/res/layout/main_fragment.xml create mode 100644 examples/coroutinesExample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 examples/coroutinesExample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 examples/coroutinesExample/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 examples/coroutinesExample/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 examples/coroutinesExample/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 examples/coroutinesExample/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 examples/coroutinesExample/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 examples/coroutinesExample/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 examples/coroutinesExample/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 examples/coroutinesExample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 examples/coroutinesExample/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 examples/coroutinesExample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 examples/coroutinesExample/src/main/res/values/colors.xml create mode 100644 examples/coroutinesExample/src/main/res/values/strings.xml create mode 100644 examples/coroutinesExample/src/main/res/values/styles.xml create mode 100644 realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt create mode 100644 realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmListExtensions.kt create mode 100644 realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmObjectExtensions.kt create mode 100644 realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmResultsExtensions.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 3651cf2677..fdf2556089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## 10.0.0-BETA.9 (YYYY-MM-DD) + +We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. + +The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. + +### Fixed +* None. + +### Compatibility +* None. + +### Breaking Changes +* None. + +### Enhancements +* Added Kotlin extension suspend function `Realm.executeTransactionAwait` which runs transactions inside coroutines. +* Added Kotlin extension function `RealmResults.toFlow` which returns a Kotlin flow, similar to RxJava's convenience method `asFlowable`. +* Added Kotlin extension function `RealmList.toFlow` which returns a Kotlin flow, similar to RxJava's convenience method `asFlowable`. +* Added Kotlin extension function `RealmModel.toFlow` which returns a Kotlin flow, similar to RxJava's convenience method `asFlowable`. + + ## 10.0.0-BETA.8 (2020-09-23) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. @@ -5,7 +27,7 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Fixed -* [RealmApp] Logging in caused an `token contains an invalid number of segments` error. (Issue [#7117](https://github.com/realm/realm-java/issues/7117), since 10.0.0-BETA.7) +* [RealmApp] Logging in caused an `token contains an invalid number of segments` error. (Issue [#7117](https://github.com/realm/realm-java/issues/7117), since 10.0.0-BETA.7) * [RealmApp] The order of arguments to `EmailPassword.resetPassword()` was not handled correctly, resulting in resetting the password failing. (Issue [#7116](https://github.com/realm/realm-java/issues/7116), since 10.0.0-BETA.1) ### Compatibility @@ -287,7 +309,7 @@ The old Realm Cloud legacy API's have undergone significant refactoring. The new ## 7.0.0(2020-05-16) -NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Studio 3.11](https://github.com/realm/realm-studio/releases/tag/v3.11.0) or later will be able to open the new file format. +NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Studio 3.11](https://github.com/realm/realm-studio/releases/tag/v3.11.0) or later will be able to open the new file format. ### Breaking Changes * [ObjectServer] Removed deprecated method `SyncConfiguration.Builder.partialRealm()`. Use `SyncConfiguration.Builder.fullSynchronization()` instead. diff --git a/dependencies.list b/dependencies.list index b78ab32718..679e3a2219 100644 --- a/dependencies.list +++ b/dependencies.list @@ -11,6 +11,7 @@ MONGODB_REALM_SERVER=2020-09-21 GRADLE_BUILD_TOOLS=4.0.0 ANDROID_BUILD_TOOLS=29.0.3 KOTLIN=1.3.72 +KOTLIN_COROUTINES=1.3.9 # Common classpath dependencies gradle=6.5 diff --git a/examples/build.gradle b/examples/build.gradle index e14b5d816a..01abaae78c 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -29,6 +29,9 @@ allprojects { } buildscript { + ext { + kotlin_version = projectDependencies.get('KOTLIN') + } repositories { google() mavenLocal() @@ -39,6 +42,7 @@ allprojects { classpath "com.android.tools.build:gradle:${props.get("GRADLE_BUILD_TOOLS")}" classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:${props.get('GRADLE_BINTRAY_PLUGIN')}" classpath "io.realm:realm-gradle-plugin:${currentVersion}" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/examples/compatibilityExample/src/main/AndroidManifest.xml b/examples/compatibilityExample/src/main/AndroidManifest.xml index dcda1e99a9..6e5500a498 100644 --- a/examples/compatibilityExample/src/main/AndroidManifest.xml +++ b/examples/compatibilityExample/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + + + + + + + + + + + + + + diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainActivity.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainActivity.kt new file mode 100644 index 0000000000..488efbae97 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainActivity.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import io.realm.examples.coroutinesexample.ui.main.MainFragment + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.main_activity) + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction() + .replace(R.id.container, MainFragment.newInstance()) + .commitNow() + } + } +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainApplication.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainApplication.kt new file mode 100644 index 0000000000..a7bd176646 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainApplication.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample + +import android.app.Application +import io.realm.Realm + +const val TAG = "--- CoroutinesExample" + +class MainApplication : Application() { + + override fun onCreate() { + super.onCreate() + Realm.init(this) + } +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/model/Dog.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/model/Dog.kt new file mode 100644 index 0000000000..0aa35bea25 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/model/Dog.kt @@ -0,0 +1,11 @@ +package io.realm.examples.coroutinesexample.model + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +open class Dog : RealmObject() { + @PrimaryKey + var name: String = "" + var age: Int = 1 + var owner: String = "" +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/repository/Repository.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/repository/Repository.kt new file mode 100644 index 0000000000..c69ac95a09 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/repository/Repository.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.repository + +import android.util.Log +import io.realm.Realm +import io.realm.examples.coroutinesexample.TAG +import io.realm.examples.coroutinesexample.model.Dog +import io.realm.kotlin.executeTransactionAwait +import io.realm.kotlin.toFlow +import io.realm.kotlin.where +import kotlinx.coroutines.flow.Flow + +interface Repository { + suspend fun insertDogs(number: Int) + suspend fun deleteDogs() + fun getDogs(): Flow> + fun countDogs(): Long +} + +class RealmRepository(private val realm: Realm) : Repository { + + override suspend fun insertDogs(number: Int) { + realm.executeTransactionAwait { transactionRealm -> + Log.d(TAG, "--- BEFORE") + for (i in 1..number) { + transactionRealm.insertOrUpdate(Dog().apply { + name = "Mr. Snuffles $i" + age = 1 + owner = "Mortimer Smith" + }) + } + Log.d(TAG, "--- AFTER") + } + } + + override suspend fun deleteDogs() { + realm.executeTransactionAwait { transactionRealm -> + transactionRealm.deleteAll() + } + } + + override fun getDogs(): Flow> = realm.where(Dog::class.java) + .findAllAsync() + .toFlow() + + override fun countDogs(): Long = realm.where().count() +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainFragment.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainFragment.kt new file mode 100644 index 0000000000..b59e03c414 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainFragment.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.ui.main + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import io.realm.examples.coroutinesexample.R +import io.realm.examples.coroutinesexample.TAG +import io.realm.examples.coroutinesexample.model.Dog + +class MainFragment : Fragment() { + + companion object { + fun newInstance() = MainFragment() + } + + private val viewModel: MainViewModel by viewModels() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + return inflater.inflate(R.layout.main_fragment, container, false) + .also { view -> addClickListeners(view) } + } + + override fun onResume() { + super.onResume() + addObservers() + } + + override fun onPause() { + super.onPause() + removeObservers() + } + + private fun addClickListeners(view: View) { + view.findViewById

              *

              - * You can call remove Realm functions as shown below: + * You can call remote Realm functions as shown below: *

                *     Functions functions = user.getFunctions();
                *     Integer sum = functions.callFunction("sum", Arrays.asList(1, 2, 3, 4), Integer.class);
              
              From 96d53dfc4cdf37b94d3cace5647d90a7721d6389 Mon Sep 17 00:00:00 2001
              From: =?UTF-8?q?Eduardo=20L=C3=B3pez?=
               <1874445+edualonso@users.noreply.github.com>
              Date: Wed, 30 Sep 2020 09:47:20 +0200
              Subject: [PATCH 1703/2110] Fixed race condition in toFlow_multipleSubscribers
               test (#7130)
              
              ---
               .../androidTest/kotlin/io/realm/CoroutineTests.kt  | 14 ++++++++------
               1 file changed, 8 insertions(+), 6 deletions(-)
              
              diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt
              index 0fb325b188..979371f187 100644
              --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt
              +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt
              @@ -207,10 +207,11 @@ class CoroutineTests {
                                       assertTrue(flowResults.isFrozen)
                                       assertEquals(0, flowResults.size)
                                   }.onCompletion {
              -                        countDownLatch.countDown()
              -
              -                        if (countDownLatch.count == 0L && !realmInstance.isClosed) {
              +                        if (countDownLatch.count > 1) {
              +                            countDownLatch.countDown()
              +                        } else {
                                           realmInstance.close()
              +                            countDownLatch.countDown()
                                       }
                                   }.launchIn(scope)
               
              @@ -220,10 +221,11 @@ class CoroutineTests {
                                       assertTrue(flowResults.isFrozen)
                                       assertEquals(0, flowResults.size)
                                   }.onCompletion {
              -                        countDownLatch.countDown()
              -
              -                        if (countDownLatch.count == 0L && !realmInstance.isClosed) {
              +                        if (countDownLatch.count > 1) {
              +                            countDownLatch.countDown()
              +                        } else {
                                           realmInstance.close()
              +                            countDownLatch.countDown()
                                       }
                                   }.launchIn(scope)
               
              
              From 087a7d134d2e9100b0232d86728aa2ddddf366eb Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Thu, 1 Oct 2020 13:54:58 +0200
              Subject: [PATCH 1704/2110] Upgrade to Sync 5.0.28 (#7135)
              
              ---
               CHANGELOG.md                                  | 21 +++++++++++++++++++
               dependencies.list                             |  4 ++--
               realm/realm-library/src/main/cpp/object-store |  2 +-
               3 files changed, 24 insertions(+), 3 deletions(-)
              
              diff --git a/CHANGELOG.md b/CHANGELOG.md
              index 01058448c0..805566587b 100644
              --- a/CHANGELOG.md
              +++ b/CHANGELOG.md
              @@ -1,3 +1,24 @@
              +## 7.0.8 (YYYY-MM-DD)
              +
              +### Enhancements
              +* Slightly improve performance of most operations which read data from the Realm file.
              +
              +### Fixes
              +* Making a query in an indexed property may give a "Key not found" exception. (.NET issue [#2025](https://github.com/realm/realm-dotnet/issues/2025), since 7.0.0)
              +* Queries for null on non-nullable indexed integer properties could return wrong results if 0 entries should be found. (Since 7.0.0)
              +* Rerunning an equals query on an indexed string column which previously had more than one match and now has one match would sometimes throw a "key not found" exception. (Cocoa issue [#6536](https://github.com/realm/realm-cocoa/issues/6536), Since 7.0.0)
              +
              +### Compatibility
              +* Realm Object Server: 3.23.1 or later.
              +* File format: Generates Realms with format v11 (Reads and upgrades all previous formats from Realm Java 2.0 and later).
              +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series.
              +
              +### Internal
              +* Upgraded to Object Store commit: 8a68df3e9fa7743c13d927eb7fc330ed9bb06693.
              +* Upgraded to Realm Sync: 5.0.28.
              +* Upgraded to Realm Core: 6.1.3.
              +
              +
               ## 7.0.7 (2020-09-25)
               
               ### Enhancements
              diff --git a/dependencies.list b/dependencies.list
              index c954128142..af2fa14963 100644
              --- a/dependencies.list
              +++ b/dependencies.list
              @@ -1,7 +1,7 @@
               # Realm Sync release used by Realm Java (This includes Realm Core)
               # https://github.com/realm/realm-sync/releases
              -REALM_SYNC_VERSION=5.0.25
              -REALM_SYNC_SHA256=c5149e87fa476699c6200b210c4c5cb9ef8146e711a705c1dca70370a5ee3189
              +REALM_SYNC_VERSION=5.0.28
              +REALM_SYNC_SHA256=827b3b9057d8c47edbb7211723787665ae93ef18b664d07f11cfa4967016a959
               
               # Object Server Release used by Integration tests. Installed using NPM.
               # Use `npm view realm-object-server versions` to get a list of available versions.
              diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store
              index 37e86c2905..8a68df3e9f 160000
              --- a/realm/realm-library/src/main/cpp/object-store
              +++ b/realm/realm-library/src/main/cpp/object-store
              @@ -1 +1 @@
              -Subproject commit 37e86c2905bfd424c16fc5d7860a1298bfc0ffa2
              +Subproject commit 8a68df3e9fa7743c13d927eb7fc330ed9bb06693
              
              From a799d9202d04cda1728b600c777a9d3c8232791e Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Thu, 1 Oct 2020 14:02:27 +0200
              Subject: [PATCH 1705/2110] Set release date
              
              ---
               CHANGELOG.md | 2 +-
               1 file changed, 1 insertion(+), 1 deletion(-)
              
              diff --git a/CHANGELOG.md b/CHANGELOG.md
              index 805566587b..34e4948da9 100644
              --- a/CHANGELOG.md
              +++ b/CHANGELOG.md
              @@ -1,4 +1,4 @@
              -## 7.0.8 (YYYY-MM-DD)
              +## 7.0.8 (2020-10-01)
               
               ### Enhancements
               * Slightly improve performance of most operations which read data from the Realm file.
              
              From b789e7e533a150a6f6a0a4e4ba727aef06ffbe90 Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Thu, 1 Oct 2020 14:03:01 +0200
              Subject: [PATCH 1706/2110] Release v7.0.8
              
              ---
               version.txt | 2 +-
               1 file changed, 1 insertion(+), 1 deletion(-)
              
              diff --git a/version.txt b/version.txt
              index a1aabc201f..19300b7be4 100644
              --- a/version.txt
              +++ b/version.txt
              @@ -1 +1 @@
              -7.0.8-SNAPSHOT
              \ No newline at end of file
              +7.0.8
              \ No newline at end of file
              
              From 1b58cb133c0d48e2e78cc295e97923286948f51a Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Thu, 1 Oct 2020 14:03:01 +0200
              Subject: [PATCH 1707/2110] Prepare next release v7.0.9-SNAPSHOT
              
              ---
               version.txt | 2 +-
               1 file changed, 1 insertion(+), 1 deletion(-)
              
              diff --git a/version.txt b/version.txt
              index 19300b7be4..aecab01a9d 100644
              --- a/version.txt
              +++ b/version.txt
              @@ -1 +1 @@
              -7.0.8
              \ No newline at end of file
              +7.0.9-SNAPSHOT
              \ No newline at end of file
              
              From ef558b23d6767c87e5931a34f0a238e895463f6c Mon Sep 17 00:00:00 2001
              From: Christian Melchior 
              Date: Thu, 1 Oct 2020 16:05:45 +0200
              Subject: [PATCH 1708/2110] Fix lists of embedded objects not being updated
               correctly (#7133)
              
              ---
               CHANGELOG.md                                  | 17 ++++---
               .../processor/RealmProxyClassGenerator.kt     | 50 +++++++++----------
               ...t_EmbeddedClassSimpleParentRealmProxy.java | 24 +++------
               .../kotlin/io/realm/EmbeddedObjectsTest.kt    | 40 +++++++++++++++
               4 files changed, 82 insertions(+), 49 deletions(-)
              
              diff --git a/CHANGELOG.md b/CHANGELOG.md
              index fdf2556089..94b260cf30 100644
              --- a/CHANGELOG.md
              +++ b/CHANGELOG.md
              @@ -4,12 +4,6 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo
               
               The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point.
               
              -### Fixed
              -* None.
              -
              -### Compatibility
              -* None.
              -
               ### Breaking Changes
               * None.
               
              @@ -19,6 +13,17 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new
               * Added Kotlin extension function `RealmList.toFlow` which returns a Kotlin flow, similar to RxJava's convenience method `asFlowable`.
               * Added Kotlin extension function `RealmModel.toFlow` which returns a Kotlin flow, similar to RxJava's convenience method `asFlowable`.
               
              +### Fixed
              +* Using `Realm.copyToRealmOrUpdate()` and `Realm.insertOrUpdate()` did not correctly update objects if they contained lists of embedded objets. Instead of replacing the original list, list items was appended to the original list. Note, some corner cases are still not supported. See [#7138](https://github.com/realm/realm-java/issues/7138) for more information. (Issue [#7131](https://github.com/realm/realm-java/issues/7131), since 10.0.0-BETA.1).
              +
              +### Compatibility
              +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java 10.0.0-BETA.1.
              +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series.
              +* Realm Studio 10.0.0 and above is required to open Realms created by this version.
              +
              +### Internal
              +* None.
              +
               
               ## 10.0.0-BETA.8 (2020-09-23)
               
              diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt
              index a2c34b66c0..00b4da0dca 100644
              --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt
              +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt
              @@ -1312,45 +1312,43 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi
                                   emitEmptyLine()
                                   emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName)
                                   emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter)
              -                    beginControlFlow("if (%1\$sList != null && %1\$sList.size() == %1\$sOsList.size())", fieldName)
              -                        emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.")
              -                        emitStatement("int objects = %1\$sList.size()", fieldName)
              -                        beginControlFlow("for (int i = 0; i < objects; i++)")
              -                            emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName)
              -                            emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
              -                            if (fieldTypeMetaData.embedded) {
              +                    if (fieldTypeMetaData.embedded) {
              +                        emitStatement("%1\$sOsList.removeAll()", fieldName)
              +                        beginControlFlow("if (%sList != null)", fieldName)
              +                            beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName)
              +                                emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
                                               beginControlFlow("if (cacheItemIndex%s != null)", fieldName)
                                                   emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName)
                                               nextControlFlow("else")
                                                   emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName)
                                               endControlFlow()
              -                            } else {
              +                            endControlFlow()
              +                        endControlFlow()
              +                    } else {
              +                        beginControlFlow("if (%1\$sList != null && %1\$sList.size() == %1\$sOsList.size())", fieldName)
              +                            emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.")
              +                            emitStatement("int objects = %1\$sList.size()", fieldName)
              +                            beginControlFlow("for (int i = 0; i < objects; i++)")
              +                                emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName)
              +                                emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
                                               beginControlFlow("if (cacheItemIndex%s == null)", fieldName)
                                                   emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field))
                                               endControlFlow()
                                               emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName)
              -                            }
              -                        endControlFlow()
              -                    nextControlFlow("else")
              -                        emitStatement("%1\$sOsList.removeAll()", fieldName)
              -                        beginControlFlow("if (%sList != null)", fieldName)
              -                            beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName)
              -                                emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
              -                                if (fieldTypeMetaData.embedded) {
              -                                    beginControlFlow("if (cacheItemIndex%s != null)", fieldName)
              -                                        emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName)
              -                                    nextControlFlow("else")
              -                                        emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName)
              -                                    endControlFlow()
              -                                } else {
              +                            endControlFlow()
              +                        nextControlFlow("else")
              +                            emitStatement("%1\$sOsList.removeAll()", fieldName)
              +                            beginControlFlow("if (%sList != null)", fieldName)
              +                                beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName)
              +                                    emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
                                                   beginControlFlow("if (cacheItemIndex%s == null)", fieldName)
                                                       emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field))
                                                   endControlFlow()
                                                   emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName)
              -                                }
              +                                endControlFlow()
                                           endControlFlow()
                                       endControlFlow()
              -                    endControlFlow()
              +                    }
                                   emitEmptyLine()
                               } else if (Utils.isRealmValueList(field)) {
                                   val genericType = Utils.getGenericTypeQualifiedName(field)
              @@ -1877,13 +1875,15 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi
                                               emitStatement("RealmList<%s> %sManagedCopy = new RealmList<%s>()", genericType, fieldName, genericType)
               
                                               if (fieldTypeMetaData.embedded) {
              +                                    emitStatement("OsList targetList = realmObjectTarget.realmGet\$%s().getOsList()", fieldName)
              +                                    emitStatement("targetList.deleteAll()")
                                                   beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName)
                                                       emitStatement("%1\$s %2\$sUnmanagedItem = %2\$sUnmanagedList.get(i)", genericType, fieldName)
                                                       emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sUnmanagedItem)", genericType, fieldName)
                                                       beginControlFlow("if (cache%s != null)", fieldName)
                                                           emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName)
                                                       nextControlFlow("else")
              -                                            emitStatement("long objKey = realmObjectTarget.%s().getOsList().createAndAddEmbeddedObject()", getter)
              +                                            emitStatement("long objKey = targetList.createAndAddEmbeddedObject()")
                                                           emitStatement("Row row = realm.getTable(%s.class).getUncheckedRow(objKey)", genericType)
                                                           emitStatement("%s proxyObject = %s.newProxyInstance(realm, row)", genericType, proxyClass)
                                                           emitStatement("cache.put(%sUnmanagedItem, (RealmObjectProxy) proxyObject)", fieldName)
              diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java
              index d702a55435..31ecc8e444 100644
              --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java
              +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_EmbeddedClassSimpleParentRealmProxy.java
              @@ -607,11 +607,9 @@ public static long insertOrUpdate(Realm realm, some.test.EmbeddedClassSimplePare
               
                       OsList childrenOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.childrenColKey);
                       RealmList childrenList = ((some_test_EmbeddedClassSimpleParentRealmProxyInterface) object).realmGet$children();
              -        if (childrenList != null && childrenList.size() == childrenOsList.size()) {
              -            // For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.
              -            int objects = childrenList.size();
              -            for (int i = 0; i < objects; i++) {
              -                some.test.EmbeddedClass childrenItem = childrenList.get(i);
              +        childrenOsList.removeAll();
              +        if (childrenList != null) {
              +            for (some.test.EmbeddedClass childrenItem : childrenList) {
                               Long cacheItemIndexchildren = cache.get(childrenItem);
                               if (cacheItemIndexchildren != null) {
                                   throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString());
              @@ -619,18 +617,6 @@ public static long insertOrUpdate(Realm realm, some.test.EmbeddedClassSimplePare
                                   cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache);
                               }
                           }
              -        } else {
              -            childrenOsList.removeAll();
              -            if (childrenList != null) {
              -                for (some.test.EmbeddedClass childrenItem : childrenList) {
              -                    Long cacheItemIndexchildren = cache.get(childrenItem);
              -                    if (cacheItemIndexchildren != null) {
              -                        throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: " + cacheItemIndexchildren.toString());
              -                    } else {
              -                        cacheItemIndexchildren = some_test_EmbeddedClassRealmProxy.insertOrUpdate(realm, table, columnInfo.childrenColKey, objKey, childrenItem, cache);
              -                    }
              -                }
              -            }
                       }
               
                       return objKey;
              @@ -775,13 +761,15 @@ static some.test.EmbeddedClassSimpleParent update(Realm realm, EmbeddedClassSimp
                       RealmList childrenUnmanagedList = realmObjectSource.realmGet$children();
                       if (childrenUnmanagedList != null) {
                           RealmList childrenManagedCopy = new RealmList();
              +            OsList targetList = realmObjectTarget.realmGet$children().getOsList();
              +            targetList.deleteAll();
                           for (int i = 0; i < childrenUnmanagedList.size(); i++) {
                               some.test.EmbeddedClass childrenUnmanagedItem = childrenUnmanagedList.get(i);
                               some.test.EmbeddedClass cachechildren = (some.test.EmbeddedClass) cache.get(childrenUnmanagedItem);
                               if (cachechildren != null) {
                                   throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cachechildren.toString()");
                               } else {
              -                    long objKey = realmObjectTarget.realmGet$children().getOsList().createAndAddEmbeddedObject();
              +                    long objKey = targetList.createAndAddEmbeddedObject();
                                   Row row = realm.getTable(some.test.EmbeddedClass.class).getUncheckedRow(objKey);
                                   some.test.EmbeddedClass proxyObject = some_test_EmbeddedClassRealmProxy.newProxyInstance(realm, row);
                                   cache.put(childrenUnmanagedItem, (RealmObjectProxy) proxyObject);
              diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt
              index 1bc759b603..eabe7144a1 100644
              --- a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt
              +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt
              @@ -1116,6 +1116,46 @@ class EmbeddedObjectsTest {
                       }
                   }
               
              +    @Test
              +    fun copyToRealmOrUpdate_replacesEmbededdList() {
              +        realm.beginTransaction()
              +        val parent = EmbeddedTreeParent()
              +        parent.middleNodeList = RealmList(EmbeddedTreeNode("1"), EmbeddedTreeNode("2"))
              +        parent._id = "1"
              +        realm.copyToRealm(parent)
              +        realm.commitTransaction()
              +
              +        realm.beginTransaction()
              +        parent.middleNodeList.add(EmbeddedTreeNode("3"))
              +        val managedParent = realm.copyToRealmOrUpdate(parent)
              +        assertEquals(3, managedParent.middleNodeList.size)
              +        realm.commitTransaction()
              +    }
              +
              +    @Test
              +    fun insertOrUpdate_replacesEmbededdList() {
              +        realm.beginTransaction()
              +        val parent = EmbeddedTreeParent()
              +        parent.middleNodeList = RealmList(EmbeddedTreeNode("1"), EmbeddedTreeNode("2"))
              +        parent._id = "1"
              +        val managedParent = realm.copyToRealm(parent)
              +        realm.commitTransaction()
              +
              +        realm.beginTransaction()
              +
              +        // insertOrUpdate has different code paths for lists of equal size vs. lists of different sizes
              +        parent.middleNodeList = RealmList(EmbeddedTreeNode("3"), EmbeddedTreeNode("4"))
              +        realm.insertOrUpdate(parent)
              +        assertEquals(2, managedParent.middleNodeList.size)
              +        assertEquals("3", managedParent.middleNodeList[0]!!.treeNodeId)
              +        assertEquals("4", managedParent.middleNodeList[1]!!.treeNodeId)
              +
              +        parent.middleNodeList.add(EmbeddedTreeNode("5"))
              +        realm.insertOrUpdate(parent)
              +        assertEquals(3, managedParent.middleNodeList.size)
              +        realm.commitTransaction()
              +    }
              +
                   @Test
                   @Ignore("Add in another PR")
                   fun results_bulkUpdate() {
              
              From 985c1a85a6a7e144e2022349261c1c6dd9de4993 Mon Sep 17 00:00:00 2001
              From: =?UTF-8?q?Eduardo=20L=C3=B3pez?=
               <1874445+edualonso@users.noreply.github.com>
              Date: Fri, 2 Oct 2020 13:31:29 +0200
              Subject: [PATCH 1709/2110] Fix flaky test: executeTransactionAwait (#7140)
              
              ---
               .../kotlin/io/realm/CoroutineTests.kt         | 130 ++++++++++++------
               .../kotlin/io/realm/kotlin/RealmExtensions.kt |  30 +++-
               2 files changed, 113 insertions(+), 47 deletions(-)
              
              diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt
              index 979371f187..002358ccc7 100644
              --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt
              +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt
              @@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.*
               import kotlinx.coroutines.test.TestCoroutineDispatcher
               import kotlinx.coroutines.test.TestCoroutineScope
               import kotlinx.coroutines.test.runBlockingTest
              -import org.junit.After
               import org.junit.Before
               import org.junit.Rule
               import org.junit.Test
              @@ -28,19 +27,12 @@ class CoroutineTests {
                   private lateinit var configuration: RealmConfiguration
                   private lateinit var testDispatcher: TestCoroutineDispatcher
                   private lateinit var testScope: TestCoroutineScope
              -    private lateinit var realm: Realm
               
                   @Before
                   fun setUp() {
                       testDispatcher = TestCoroutineDispatcher()
                       testScope = TestCoroutineScope(testDispatcher)
                       configuration = configFactory.createConfiguration()
              -        realm = Realm.getInstance(configuration)
              -    }
              -
              -    @After
              -    fun tearDown() {
              -        realm.close()
                   }
               
                   @Test
              @@ -73,9 +65,11 @@ class CoroutineTests {
               
                   @Test
                   fun toFlow_resultsEmittedAfterCollect() {
              -        realm.executeTransactionAsync { transactionRealm ->
              -            transactionRealm.createObject().name = "Foo"
              -            transactionRealm.createObject().name = "Bar"
              +        Realm.getInstance(configuration).use { realm ->
              +            realm.executeTransaction { transactionRealm ->
              +                transactionRealm.createObject().name = "Foo"
              +                transactionRealm.createObject().name = "Bar"
              +            }
                       }
               
                       val countDownLatch = CountDownLatch(1)
              @@ -131,20 +125,22 @@ class CoroutineTests {
               
                   @Test
                   fun toFlow_throwsDueToThreadViolation() {
              -        val countDownLatch = CountDownLatch(1)
              +        Realm.getInstance(configuration).use { realm ->
              +            val countDownLatch = CountDownLatch(1)
               
              -        // Get results from the test thread
              -        val findAll = realm.where().findAll()
              +            // Get results from the test thread
              +            val findAll = realm.where().findAll()
               
              -        CoroutineScope(Dispatchers.Main).launch {
              -            assertFailsWith {
              -                // Now we are on the main thread, which means crash
              -                findAll.toFlow()
              -                fail("toFlow() must be called from the thread that retrieved the results!")
              +            CoroutineScope(Dispatchers.Main).launch {
              +                assertFailsWith {
              +                    // Now we are on the main thread, which means crash
              +                    findAll.toFlow()
              +                    fail("toFlow() must be called from the thread that retrieved the results!")
              +                }
              +                countDownLatch.countDown()
                           }
              -            countDownLatch.countDown()
              +            TestHelper.awaitOrFail(countDownLatch)
                       }
              -        TestHelper.awaitOrFail(countDownLatch)
                   }
               
                   @Test
              @@ -192,7 +188,7 @@ class CoroutineTests {
                       val context = Dispatchers.Main
                       val scope = CoroutineScope(context)
               
              -        var flow: Flow>? = null
              +        var flow: Flow>?
               
                       scope.launch {
                           val realmInstance = Realm.getInstance(configuration)
              @@ -416,10 +412,11 @@ class CoroutineTests {
                       val scope = CoroutineScope(context)
               
                       scope.launch {
              -            val realmInstance = Realm.getInstance(configuration)
              -            realmInstance.beginTransaction()
              -            realmInstance.createObject()
              -            realmInstance.commitTransaction()
              +            Realm.getInstance(configuration).use { realmInstance ->
              +                realmInstance.executeTransaction {
              +                    realmInstance.createObject()
              +                }
              +            }
               
                           val dynamicRealm = DynamicRealm.getInstance(configuration)
                           dynamicRealm.where(AllTypes.CLASS_NAME)
              @@ -430,7 +427,6 @@ class CoroutineTests {
                                       assertTrue(flowObject.isFrozen)
                                       scope.cancel("Cancelling scope...")
                                   }.onCompletion {
              -                        realmInstance.close()
                                       dynamicRealm.close()
                                       countDownLatch.countDown()
                                   }.collect()
              @@ -455,12 +451,11 @@ class CoroutineTests {
                   }
               
                   @Test
              -    fun executeTransactionAwait_cancel() {
              +    fun executeTransactionAwait_cancelCoroutineWithMultipleTransactions() {
                       val upperBound = 10
                       var realmInstance: Realm? = null
               
              -        val mainScope = CoroutineScope(Dispatchers.Main)
              -        mainScope.launch {
              +        val job = CoroutineScope(Dispatchers.Main).launch {
                           realmInstance = Realm.getInstance(configuration)
               
                           for (i in 1..upperBound) {
              @@ -475,17 +470,61 @@ class CoroutineTests {
                       }
               
                       val countDownLatch = CountDownLatch(1)
              -        val otherMainScope = CoroutineScope(Dispatchers.Main)
              -        otherMainScope.launch {
              -            // Wait for 50 ms and cancel scope so that not all planned 10 elements are inserted
              +        CoroutineScope(Dispatchers.Main).launch {
              +            // Wait for 50 ms and cancel job so that not all planned 10 elements are inserted
                           delay(50)
              -            mainScope.cancel("Cancelling")
              +            job.cancelAndJoin()
              +
              +            assertNotEquals(upperBound.toLong(), realmInstance!!.where().count())
              +
              +            realmInstance!!.close()
               
              +            countDownLatch.countDown()
              +            this.cancel()
              +        }
              +
              +        TestHelper.awaitOrFail(countDownLatch)
              +    }
              +
              +    @Test
              +    fun executeTransactionAwait_cancelCoroutineWithHeavyCooperativeTransaction() {
              +        val upperBound = 100000
              +        var realmInstance: Realm? = null
              +
              +        val job = CoroutineScope(Dispatchers.Main).launch {
              +            realmInstance = Realm.getInstance(configuration)
              +
              +            realmInstance!!.executeTransactionAwait { transactionRealm ->
              +                // Try to insert 100000 objects to give time to be cancelled after 5ms
              +                for (i in 1..upperBound) {
              +                    // The coroutine itself will not cancel the transaction, but we can make it cooperative ourselves
              +                    if (isActive) {
              +                        val simpleObject = SimpleClass().apply { name = "simpleName $i" }
              +                        transactionRealm.insert(simpleObject)
              +                    }
              +                }
              +            }
              +        }
              +
              +        val countDownLatch = CountDownLatch(1)
              +        CoroutineScope(Dispatchers.Main).launch {
              +            // Wait for 5 ms and cancel job
              +            delay(5)
              +            job.cancelAndJoin()
              +
              +            // The coroutine won't finish until the transaction is completely done but not all
              +            // elements will have been inserted since the transaction is cooperative.
              +            // It isn't possible to guarantee we have inserted any element at all either because
              +            // another coroutine is launched inside executeTransactionAwait and that triggers a
              +            // context switching, which might result in that the call to cancelAndJoin above this
              +            // comment be executed even before we check for isActive inside executeTransactionAwait.
              +            // So the result yielded by count() will be a number from 0 to anywhere below 100000.
                           assertNotEquals(upperBound.toLong(), realmInstance!!.where().count())
               
                           realmInstance!!.close()
              +
                           countDownLatch.countDown()
              -            otherMainScope.cancel()
              +            this.cancel()
                       }
               
                       TestHelper.awaitOrFail(countDownLatch)
              @@ -497,18 +536,23 @@ class CoroutineTests {
                       val countDownLatch = CountDownLatch(1)
                       var exception: IllegalStateException? = null
               
              -        // It will crash so long we aren't using Dispatchers.Unconfined
              -        CoroutineScope(Dispatchers.IO).launch {
              -            assertFailsWith {
              -                realm.where().findAll()
              -            }.let {
              -                exception = it
              -                countDownLatch.countDown()
              +        Realm.getInstance(configuration).use { realm ->
              +            // It will crash so long we aren't using Dispatchers.Unconfined
              +            CoroutineScope(Dispatchers.IO).launch {
              +                assertFailsWith {
              +                    realm.executeTransactionAwait {
              +                        // no-op
              +                    }
              +                }.let {
              +                    exception = it
              +                    countDownLatch.countDown()
              +                }
                           }
              +            TestHelper.awaitOrFail(countDownLatch)
                       }
              -        TestHelper.awaitOrFail(countDownLatch)
               
                       // Ensure we failed
                       assertNotNull(exception)
              +        assertTrue(exception!!.message!!.contains("incorrect thread"))
                   }
               }
              diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt
              index 299caef7e7..9a328cdf15 100644
              --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt
              +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt
              @@ -19,7 +19,9 @@ import io.realm.Realm
               import io.realm.RealmModel
               import io.realm.RealmQuery
               import io.realm.exceptions.RealmException
              +import kotlinx.coroutines.CoroutineScope
               import kotlinx.coroutines.asCoroutineDispatcher
              +import kotlinx.coroutines.isActive
               import kotlinx.coroutines.withContext
               import kotlin.coroutines.CoroutineContext
               
              @@ -79,15 +81,15 @@ inline fun  Realm.createObject(primaryKeyValue: Any?): T
               
               /**
                * Instantiates and adds a new embedded object to the Realm.
              - * 

              + * * This method should only be used to create objects of types marked as embedded. * - * @param T the Class of the object to create. It must be marked with {@code \@RealmClass(embedded = true)}. + * @param T the Class of the object to create. It must be marked with `@RealmClass(embedded = true)`. * @param parentObject The parent object which should hold a reference to the embedded object. If the parent property is a list * the embedded object will be added to the end of that list. * @param parentProperty the property in the parent class which holds the reference. * @return the newly created embedded object. - * @throws IllegalArgumentException if {@code clazz} is not an embedded class or if the property + * @throws IllegalArgumentException if `clazz` is not an embedded class or if the property * in the parent class cannot hold objects of the appropriate type. */ inline fun Realm.createEmbeddedObject(parentObject: RealmModel, parentProperty: String): T { @@ -97,6 +99,23 @@ inline fun Realm.createEmbeddedObject(parentObject: Rea /** * Suspend version of [Realm.executeTransaction] to use within coroutines. * + * Canceling the scope or job in which this function is executed does not cancel the transaction itself. If you want to ensure + * your transaction is cooperative, you have to check for the value of [CoroutineScope.isActive] while running the transaction: + * + * ``` + * coroutineScope.launch { + * // insert 100 objects + * realm.executeTransactionAwait { transactionRealm -> + * for (i in 1..100) { + * // all good if active, otherwise do nothing + * if (isActive) { + * transactionRealm.insert(MyObject(i)) + * } + * } + * } + * } + * ``` + * * @param context optional [CoroutineContext] in which this coroutine will run. * @param transaction the [Realm.Transaction] to execute. * @throws IllegalArgumentException if the `transaction` is `null`. @@ -110,7 +129,10 @@ suspend fun Realm.executeTransactionAwait( withContext(context) { // Get a new coroutine-confined Realm instance from the original Realm's configuration Realm.getInstance(configuration).use { coroutineRealm -> - coroutineRealm.executeTransaction(transaction) + // Ensure cooperation and prevent execution if the scope is not active. + if (isActive) { + coroutineRealm.executeTransaction(transaction) + } } } From ac242ddd58798419bce9da84a1b5adbf136d519d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 2 Oct 2020 14:49:43 +0200 Subject: [PATCH 1710/2110] Fixed test logic (#7134) --- .../internal/async/RealmResultTaskImplTest.kt | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmResultTaskImplTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmResultTaskImplTest.kt index 687abbd9d8..ad0729cc7c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmResultTaskImplTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/async/RealmResultTaskImplTest.kt @@ -139,25 +139,24 @@ class RealmResultTaskImplTest { @Test fun cancel() { val taskReference = AtomicReference>() - val finishRunnable = Runnable { - looperThread.testComplete() - } val task: RealmResultTask = RealmResultTaskImpl( service, object : RealmResultTaskImpl.Executor() { override fun run(): String? { // Ensure we cancel before returning a result - BlockingLooperThread().runBlocking { - taskReference.get().let { - assertNotNull(it) - assertFalse(it.isCancelled) + taskReference.get().let { + assertNotNull(it) + assertFalse(it.isCancelled) - it.cancel() + // Cancel task here + it.cancel() - looperThread.postRunnable(finishRunnable) - } + // Makes no difference to complete here or from another thread + looperThread.testComplete() } - fail("Should fail before returning anything") + + // It does not matter we return something here, it will not be delivered + return null } } ) From 4a2257d554e9a323a97b277aa094872b67ad0987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 2 Oct 2020 15:51:21 +0200 Subject: [PATCH 1711/2110] Add "allowWritesOnUiThread" and "allowReadsOnUiThread" to RealmConfiguration (#7123) --- CHANGELOG.md | 13 +- .../io/realm/DynamicRealmAsyncQueryTests.kt | 370 ++++++++++++++++++ .../java/io/realm/DynamicRealmTests.java | 56 +++ .../androidTest/java/io/realm/QueryTests.java | 4 + .../java/io/realm/RealmAsyncQueryTests.java | 4 +- .../io/realm/RealmConfigurationTests.java | 44 +++ .../java/io/realm/RealmQueryTests.java | 207 ++++++++++ .../androidTest/java/io/realm/RealmTests.java | 67 +++- .../src/main/java/io/realm/BaseRealm.java | 33 +- .../src/main/java/io/realm/DynamicRealm.java | 215 +++++++++- .../src/main/java/io/realm/Realm.java | 7 + .../java/io/realm/RealmConfiguration.java | 68 +++- .../src/main/java/io/realm/RealmQuery.java | 41 ++ .../realm/mongodb/sync/SyncConfiguration.java | 36 +- .../rule/TestRealmConfigurationFactory.java | 3 + 15 files changed, 1150 insertions(+), 18 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/DynamicRealmAsyncQueryTests.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 94b260cf30..42890b709b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,16 @@ We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Clo The old Realm Cloud legacy APIs have undergone significant refactoring. The new APIs are all located in the `io.realm.mongodb` package with `io.realm.mongodb.App` as the entry point. ### Breaking Changes -* None. +* From now on it is not allowed by default to run transactions with either `Realm.executeTransaction()` or `DynamicRealm.executeTransaction()` from the UI thread. Doing so will yield a `RealmException`. Users can override this behavior by using `RealmConfiguration.Builder.allowWritesOnUiThread(true)` when building a `RealmConfiguration` to obtain a Realm or DynamicRealm instance, though we do not recommend doing so. Instead, we recommend using `executeTransactionAsync()` or, alternatively, using non-UI threads when calling `executeTransaction()` for both `Realm`s and `DynamicRealm`s. ### Enhancements -* Added Kotlin extension suspend function `Realm.executeTransactionAwait` which runs transactions inside coroutines. -* Added Kotlin extension function `RealmResults.toFlow` which returns a Kotlin flow, similar to RxJava's convenience method `asFlowable`. -* Added Kotlin extension function `RealmList.toFlow` which returns a Kotlin flow, similar to RxJava's convenience method `asFlowable`. -* Added Kotlin extension function `RealmModel.toFlow` which returns a Kotlin flow, similar to RxJava's convenience method `asFlowable`. +* Users can now opt out from allowing queries to be launched from the UI thread by using `RealmConfiguration.Builder.allowQueriesOnUiThread(false)`. A `RealmException` will be thrown when calling `RealmQuery.findAll()`, `RealmQuery.findFirst()`, `RealmQuery.minimumDate()`, `RealmQuery.maximumDate()`, `RealmQuery.count()`, `RealmQuery.sum()`, `RealmQuery.max()`, `RealmQuery.min()`, `RealmQuery.average()` and `RealmQuery.averageDecimal128()` from the UI thread after having used `allowQueriesOnUiThread(false)`. Queries will be allowed from the thread from which the Realm instance was obtained as it always has been by default, although we recommend using `RealmQuery.findAllAsync()` or `RealmQuery.findFirstAsync()`, or, alternatively, using a non-UI thread to launch them. +* `BaseRealm.refresh()` will throw a `RealmException` if it is being called from the UI thread if `allowQueriesOnUiThread` is set to `false`, though it will be allowed by default. +* Added `DynamicRealm.executeTransactionAsync()`. +* Added Kotlin extension suspend function `Realm.executeTransactionAwait()` which runs transactions inside coroutines. +* Added Kotlin extension function `RealmResults.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. +* Added Kotlin extension function `RealmList.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. +* Added Kotlin extension function `RealmModel.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. ### Fixed * Using `Realm.copyToRealmOrUpdate()` and `Realm.insertOrUpdate()` did not correctly update objects if they contained lists of embedded objets. Instead of replacing the original list, list items was appended to the original list. Note, some corner cases are still not supported. See [#7138](https://github.com/realm/realm-java/issues/7138) for more information. (Issue [#7131](https://github.com/realm/realm-java/issues/7131), since 10.0.0-BETA.1). diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmAsyncQueryTests.kt b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmAsyncQueryTests.kt new file mode 100644 index 0000000000..aa1d5ec4bf --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmAsyncQueryTests.kt @@ -0,0 +1,370 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.DynamicRealm +import io.realm.TestHelper.TestLogger +import io.realm.entities.AllTypes +import io.realm.entities.Owner +import io.realm.internal.async.RealmThreadPoolExecutor +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.rule.BlockingLooperThread +import io.realm.rule.TestRealmConfigurationFactory +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.junit.runner.RunWith +import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail + +@RunWith(AndroidJUnit4::class) +class DynamicRealmAsyncQueryTests { + + @get:Rule + val configFactory = TestRealmConfigurationFactory() + @get:Rule + val thrown: ExpectedException = ExpectedException.none() + + private val looperThread = BlockingLooperThread() + + private lateinit var config: RealmConfiguration + + @Before + fun setUp() { + config = configFactory.createConfiguration() + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(config).close() + } + + // **************************** + // **** Async transaction *** + // **************************** + // Starts asynchronously a transaction to insert one element. + @Test + fun executeTransactionAsync() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(config) + .also { looperThread.closeAfterTest(it) } + + assertEquals(0, realm.where(Owner.CLASS_NAME).count()) + + realm.executeTransactionAsync({ transactionRealm -> + val owner = transactionRealm.createObject(Owner.CLASS_NAME) + owner.setString(Owner.FIELD_NAME, "Owner") + }, { + assertEquals(1, realm.where(Owner.CLASS_NAME).count()) + assertEquals("Owner", realm.where(Owner.CLASS_NAME).findFirst()!!.getString(Owner.FIELD_NAME)) + looperThread.testComplete() + }) { error -> + fail(error.message) + } + } + + @Test + fun executeTransactionAsync_onSuccess() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(config) + .also { looperThread.closeAfterTest(it) } + + assertEquals(0, realm.where(Owner.CLASS_NAME).count()) + + realm.executeTransactionAsync(DynamicRealm.Transaction { transactionRealm -> + val owner = transactionRealm.createObject(Owner.CLASS_NAME) + owner.setString(Owner.FIELD_NAME, "Owner") + }, DynamicRealm.Transaction.OnSuccess { + assertEquals(1, realm.where(Owner.CLASS_NAME).count()) + assertEquals("Owner", realm.where(Owner.CLASS_NAME).findFirst()!!.getString(Owner.FIELD_NAME)) + looperThread.testComplete() + }) + } + + @Test + fun executeTransactionAsync_onSuccessCallerRealmClosed() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(config) + + assertEquals(0, realm.where(Owner.CLASS_NAME).count()) + realm.executeTransactionAsync(DynamicRealm.Transaction { transactionRealm -> + val owner = transactionRealm.createObject(Owner.CLASS_NAME) + owner.setString(Owner.FIELD_NAME, "Owner") + }, DynamicRealm.Transaction.OnSuccess { + assertTrue(realm.isClosed) + + DynamicRealm.getInstance(config).use { newRealm -> + assertEquals(1, newRealm.where(Owner.CLASS_NAME).count()) + assertEquals("Owner", newRealm.where(Owner.CLASS_NAME).findFirst()!!.getString(Owner.FIELD_NAME)) + } + + looperThread.testComplete() + }) + realm.close() + } + + @Test + fun executeTransactionAsync_onError() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(config) + .also { looperThread.closeAfterTest(it) } + + val runtimeException = RuntimeException("Oh! What a Terrible Failure") + + assertEquals(0, realm.where(Owner.CLASS_NAME).count()) + + realm.executeTransactionAsync({ + throw runtimeException + }) { error -> + assertEquals(0, realm.where(Owner.CLASS_NAME).count()) + assertNull(realm.where(Owner.CLASS_NAME).findFirst()) + assertEquals(runtimeException, error) + + looperThread.testComplete() + } + } + + @Test + fun executeTransactionAsync_onErrorCallerRealmClosed() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(config) + val runtimeException = RuntimeException("Oh! What a Terrible Failure") + + assertEquals(0, realm.where(Owner.CLASS_NAME).count()) + + realm.executeTransactionAsync({ + throw runtimeException + }) { error -> + assertTrue(realm.isClosed) + + DynamicRealm.getInstance(config).use { newRealm -> + assertEquals(0, newRealm.where(Owner.CLASS_NAME).count()) + assertNull(newRealm.where(Owner.CLASS_NAME).findFirst()) + assertEquals(runtimeException, error) + } + + looperThread.testComplete() + } + realm.close() + } + + @Test + fun executeTransactionAsync_NoCallbacks() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(config) + .also { looperThread.closeAfterTest(it) } + + assertEquals(0, realm.where(Owner.CLASS_NAME).count()) + + realm.executeTransactionAsync { transactionRealm -> + val owner = transactionRealm.createObject(Owner.CLASS_NAME) + owner.setString(Owner.FIELD_NAME, "Owner") + } + + realm.addChangeListener { listenerRealm -> + assertEquals("Owner", listenerRealm.where(Owner.CLASS_NAME).findFirst()!!.getString(Owner.FIELD_NAME)) + + looperThread.testComplete() + } + } + + // Tests that an async transaction that throws when call cancelTransaction manually. + @Test + fun executeTransactionAsync_cancelTransactionInside() = looperThread.runBlocking { + val testLogger = TestLogger(LogLevel.DEBUG) + RealmLog.add(testLogger) + + val realm = DynamicRealm.getInstance(config) + .also { looperThread.closeAfterTest(it) } + + assertEquals(0, realm.where(Owner.CLASS_NAME).count()) + + realm.executeTransactionAsync({ transactionRealm -> + val owner = transactionRealm.createObject(Owner.CLASS_NAME) + owner.setString(Owner.FIELD_NAME, "Owner") + transactionRealm.cancelTransaction() + }, { + fail("Should not reach success if runtime exception is thrown in callback.") + }) { error -> + // Ensure we are giving developers quality messages in the logs. + assertTrue(testLogger.message.contains("Exception has been thrown: Can't commit a non-existing write transaction")) + assertTrue(error is java.lang.IllegalStateException) + + RealmLog.remove(testLogger) + + looperThread.testComplete() + } + } + + // Tests if the background Realm is closed when transaction success returned. + @Test + fun executeTransactionAsync_realmClosedOnSuccess() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(config) + .also { looperThread.closeAfterTest(it) } + + val counter = AtomicInteger(100) + + val cacheCallback = RealmCache.Callback { count -> + assertEquals(1, count.toLong()) + if (counter.decrementAndGet() == 0) { + looperThread.testComplete() + } + } + + val onSuccessCallback = object : DynamicRealm.Transaction.OnSuccess { + override fun onSuccess() { + RealmCache.invokeWithGlobalRefCount(realm.getConfiguration(), cacheCallback) + if (counter.get() == 0) { + // Finishes testing. + return + } + realm.executeTransactionAsync(DynamicRealm.Transaction { + // no-op + }, this) + } + } + realm.executeTransactionAsync(DynamicRealm.Transaction { + // no-op + }, onSuccessCallback) + } + + // Tests if the background Realm is closed when transaction error returned. + @Test + fun executeTransaction_async_realmClosedOnError() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(config) + .also { looperThread.closeAfterTest(it) } + + val counter = AtomicInteger(100) + + val cacheCallback = RealmCache.Callback { count -> + assertEquals(1, count.toLong()) + if (counter.decrementAndGet() == 0) { + looperThread.testComplete() + } + } + + val onErrorCallback = object : DynamicRealm.Transaction.OnError { + override fun onError(error: Throwable) { + RealmCache.invokeWithGlobalRefCount(realm.getConfiguration(), cacheCallback) + if (counter.get() == 0) { + // Finishes testing. + return + } + realm.executeTransactionAsync(DynamicRealm.Transaction { throw RuntimeException("Dummy exception") }, this) + } + } + + realm.executeTransactionAsync(DynamicRealm.Transaction { + throw RuntimeException("Dummy exception") + }, onErrorCallback) + } + + // Test case for https://github.com/realm/realm-java/issues/1893 + // Ensures that onSuccess is called with the correct Realm version for async transaction. + @Test + fun executeTransactionAsync_asyncQuery() = looperThread.runBlocking { + val realm = DynamicRealm.getInstance(config) + .also { looperThread.closeAfterTest(it) } + + val results = realm.where(AllTypes.CLASS_NAME).findAllAsync() + + assertEquals(0, results.size.toLong()) + + realm.executeTransactionAsync({ transactionRealm -> + transactionRealm.createObject(AllTypes.CLASS_NAME) + }, { + assertEquals(1, realm.where(AllTypes.CLASS_NAME).count()) + + // We cannot guarantee the async results get delivered from OS. + if (results.isLoaded) { + assertEquals(1, results.size.toLong()) + } else { + assertEquals(0, results.size.toLong()) + } + + looperThread.testComplete() + }) { + fail(it.message) + } + } + + @Test + fun executeTransactionAsync_onSuccessOnNonLooperThreadThrows() { + DynamicRealm.getInstance(config).use { realm -> + thrown.expect(IllegalStateException::class.java) + realm.executeTransactionAsync(DynamicRealm.Transaction { + // no-op + }, DynamicRealm.Transaction.OnSuccess { + // no-op + }) + } + } + + @Test + fun executeTransactionAsync_onErrorOnNonLooperThreadThrows() { + DynamicRealm.getInstance(config).use { realm -> + thrown.expect(IllegalStateException::class.java) + realm.executeTransactionAsync(DynamicRealm.Transaction { + // no-op + }, DynamicRealm.Transaction.OnError { + // no-op + }) + } + } + + // https://github.com/realm/realm-java/issues/4595#issuecomment-298830411 + // onSuccess might commit another transaction which will call didChange. So before calling async transaction + // callbacks, the callback should be cleared. + @Test + @Throws(NoSuchFieldException::class, IllegalAccessException::class) + fun executeTransactionAsync_callbacksShouldBeClearedBeforeCalling() = looperThread.runBlocking { + val callbackCounter = AtomicInteger(0) + val foregroundRealm = DynamicRealm.getInstance(config) + .also { looperThread.closeAfterTest(it) } + + // Use single thread executor + TestHelper.replaceRealmThreadExecutor(RealmThreadPoolExecutor.newSingleThreadExecutor()) + + // To reproduce the issue, the posted callback needs to arrived before the Object Store did_change called. + // We just disable the auto refresh here then the did_change won't be called. + foregroundRealm.isAutoRefresh = false + foregroundRealm.executeTransactionAsync(DynamicRealm.Transaction { transactionRealm -> + transactionRealm.createObject(AllTypes.CLASS_NAME) + }, DynamicRealm.Transaction.OnSuccess { + // This will be called first and only once + assertEquals(0, callbackCounter.getAndIncrement().toLong()) + + // This transaction should never trigger the onSuccess. + foregroundRealm.beginTransaction() + foregroundRealm.createObject(AllTypes.CLASS_NAME) + foregroundRealm.commitTransaction() + }) + + foregroundRealm.executeTransactionAsync(DynamicRealm.Transaction { transactionRealm -> + transactionRealm.createObject(AllTypes.CLASS_NAME) + }, DynamicRealm.Transaction.OnSuccess { + // This will be called 2nd and only once + assertEquals(1, callbackCounter.getAndIncrement().toLong()) + + looperThread.testComplete() + }) + + // Wait for all async tasks finish to ensure the async transaction posted callback will arrive first. + TestHelper.resetRealmThreadExecutor() + looperThread.postRunnable(Runnable { + // Manually call refresh, so the did_change will be triggered. + foregroundRealm.sharedRealm.refresh() + }) + } +} diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java index e8b7cbb295..9b46489d95 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmTests.java @@ -16,6 +16,7 @@ package io.realm; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; @@ -26,6 +27,7 @@ import org.junit.runner.RunWith; import java.util.Date; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -47,6 +49,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -339,6 +342,59 @@ public void execute(DynamicRealm realm) { assertEquals(0, realm.where("Owner").count()); } + @Test + @UiThreadTest + public void executeTransaction_mainThreadWritesAllowed() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowWritesOnUiThread(true) + .name("ui_realm") + .build(); + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(configuration).close(); + + DynamicRealm uiRealm = DynamicRealm.getInstance(configuration); + uiRealm.executeTransaction(new DynamicRealm.Transaction() { + @Override + public void execute(DynamicRealm realm) { + DynamicRealmObject owner = realm.createObject(Owner.CLASS_NAME); + owner.setString("name", "Mortimer Smith"); + } + }); + + RealmResults results = uiRealm.where(Owner.CLASS_NAME).equalTo("name", "Mortimer Smith").findAll(); + assertEquals(1, results.size()); + assertNotNull(results.first()); + assertEquals("Mortimer Smith", Objects.requireNonNull(results.first()).getString(Dog.FIELD_NAME)); + + uiRealm.close(); + } + + @Test + @UiThreadTest + public void executeTransaction_mainThreadWritesNotAllowed() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowWritesOnUiThread(false) + .name("ui_realm") + .build(); + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(configuration).close(); + + // Try-with-resources + try (DynamicRealm uiRealm = DynamicRealm.getInstance(configuration);) { + uiRealm.executeTransaction(new DynamicRealm.Transaction() { + @Override + public void execute(DynamicRealm realm) { + // no-op + } + }); + fail("the call to executeTransaction should have failed, this line should not be reached."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowWritesOnUiThread")); + } + } + @Test public void findFirst() { populateTestRealm(realm, 10); diff --git a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java index 2fd36f16f3..1f9bb65090 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java @@ -15,6 +15,8 @@ */ package io.realm; +import androidx.test.rule.UiThreadTestRule; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -45,6 +47,8 @@ public abstract class QueryTests { public final ExpectedException thrown = ExpectedException.none(); @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); + @Rule + public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); protected static final List SUPPORTED_IS_EMPTY_TYPES; protected static final List NOT_SUPPORTED_IS_EMPTY_TYPES; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java index d3b4754d2c..900c6fab62 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmAsyncQueryTests.java @@ -17,7 +17,7 @@ package io.realm; import android.os.SystemClock; -import androidx.test.rule.UiThreadTestRule; + import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; @@ -59,8 +59,6 @@ public class RealmAsyncQueryTests { @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule - public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); - @Rule public final ExpectedException thrown = ExpectedException.none(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 2f437475ac..8d83296f3b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -1117,4 +1117,48 @@ public void maxNumberOfActiveVersions_throwsIfZeroOrNegative() { } catch (IllegalArgumentException ignore) { } } + + @Test + public void allowQueriesOnUiThread_defaultsToTrue() { + RealmConfiguration configuration = new RealmConfiguration.Builder().build(); + assertTrue(configuration.isAllowQueriesOnUiThread()); + } + + @Test + public void allowQueriesOnUiThread_explicitFalse() { + RealmConfiguration configuration = new RealmConfiguration.Builder() + .allowQueriesOnUiThread(false) + .build(); + assertFalse(configuration.isAllowQueriesOnUiThread()); + } + + @Test + public void allowQueriesOnUiThread_explicitTrue() { + RealmConfiguration configuration = new RealmConfiguration.Builder() + .allowQueriesOnUiThread(true) + .build(); + assertTrue(configuration.isAllowQueriesOnUiThread()); + } + + @Test + public void allowWritesOnUiThread_defaultsToFalse() { + RealmConfiguration configuration = new RealmConfiguration.Builder().build(); + assertFalse(configuration.isAllowWritesOnUiThread()); + } + + @Test + public void allowWritesOnUiThread_explicitFalse() { + RealmConfiguration configuration = new RealmConfiguration.Builder() + .allowWritesOnUiThread(false) + .build(); + assertFalse(configuration.isAllowWritesOnUiThread()); + } + + @Test + public void allowWritesOnUiThread_explicitTrue() { + RealmConfiguration configuration = new RealmConfiguration.Builder() + .allowWritesOnUiThread(true) + .build(); + assertTrue(configuration.isAllowWritesOnUiThread()); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 593c5132f6..945cc30d8c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -16,6 +16,7 @@ package io.realm; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.bson.types.Decimal128; @@ -34,6 +35,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -55,6 +57,7 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; +import io.realm.exceptions.RealmException; import io.realm.rule.RunTestInLooperThread; import static org.junit.Assert.assertEquals; @@ -3739,6 +3742,210 @@ public void limit_invalidValuesThrows() { } } + @Test + @UiThreadTest + public void findAll_runOnMainThreadAllowed() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(true) + .name("ui_realm") + .build(); + + Realm uiRealm = Realm.getInstance(configuration); + uiRealm.where(Dog.class).findAll(); + uiRealm.close(); + } + + @Test + @UiThreadTest + public void findFirst_runOnMainThreadAllowed() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(true) + .name("ui_realm") + .build(); + + Realm uiRealm = Realm.getInstance(configuration); + uiRealm.where(Dog.class).findFirst(); + uiRealm.close(); + } + + @Test + @UiThreadTest + public void findAll_runOnMainThreadThrows() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.where(Dog.class).findAll(); + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + + @Test + @UiThreadTest + public void findFirst_runOnMainThreadThrows() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.where(Dog.class).findFirst(); + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + + @Test + @UiThreadTest + public void asyncQuery_throwsWhenCallingRefresh() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.refresh(); + + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + + @Test + @UiThreadTest + public void count_runOnMainThreadThrows() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.where(Dog.class).count(); + + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + + @Test + @UiThreadTest + public void max_runOnMainThreadThrows() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.where(Dog.class).max("age"); + + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + + @Test + @UiThreadTest + public void min_runOnMainThreadThrows() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.where(Dog.class).min("age"); + + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + + @Test + @UiThreadTest + public void average_runOnMainThreadThrows() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.where(Dog.class).average("age"); + + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + + @Test + @UiThreadTest + public void averageDecimal128_runOnMainThreadThrows() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.where(AllTypes.class).averageDecimal128(AllTypes.FIELD_DECIMAL128); + + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + + @Test + @UiThreadTest + public void maximumDate_runOnMainThreadThrows() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.where(Dog.class).maximumDate("birthday"); + + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + + @Test + @UiThreadTest + public void minimumDate_runOnMainThreadThrows() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowQueriesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.where(Dog.class).minimumDate("birthday"); + + fail("In this test queries are not allowed to run on the UI thread, so something went awry."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowQueriesOnUiThread")); + } + } + // FIXME Maybe move to QueryDescriptor or maybe even to RealmFieldType? private boolean supportDistinct(RealmFieldType type) { switch (type) { diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 565ff844ee..430671eaeb 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -21,6 +21,7 @@ import android.os.Looper; import android.os.SystemClock; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.UiThreadTestRule; @@ -36,7 +37,6 @@ import org.junit.After; import org.junit.Assume; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -55,6 +55,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.Random; import java.util.Scanner; import java.util.concurrent.Callable; @@ -763,6 +764,70 @@ public void execute(Realm realm) { assertEquals(0, realm.where(Owner.class).count()); } + @Test + @UiThreadTest + public void executeTransaction_mainThreadWritesAllowed() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowWritesOnUiThread(true) + .name("ui_realm") + .build(); + + Realm uiRealm = Realm.getInstance(configuration); + uiRealm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + realm.insert(new Dog("Snuffles")); + } + }); + + RealmResults results = uiRealm.where(Dog.class).equalTo("name", "Snuffles").findAll(); + assertEquals(1, results.size()); + assertNotNull(results.first()); + assertEquals("Snuffles", Objects.requireNonNull(results.first()).getName()); + + uiRealm.close(); + } + + @Test + @UiThreadTest + public void executeTransaction_mainThreadWritesNotAllowed() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowWritesOnUiThread(false) + .name("ui_realm") + .build(); + + // Try-with-resources + try (Realm uiRealm = Realm.getInstance(configuration)) { + uiRealm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + // no-op + } + }); + fail("the call to executeTransaction should have failed, this line should not be reached."); + } catch (RealmException e) { + assertTrue(Objects.requireNonNull(e.getMessage()).contains("allowWritesOnUiThread")); + } + } + + @Test + public void executeTransaction_runsOnNonUiThread() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .allowWritesOnUiThread(false) + .name("ui_realm") + .build(); + + Realm uiRealm = Realm.getInstance(configuration); + uiRealm.executeTransaction(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + // no-op + } + }); + + uiRealm.close(); + } + @Test public void delete_type() { // ** Deletes non existing table should succeed. diff --git a/realm/realm-library/src/main/java/io/realm/BaseRealm.java b/realm/realm-library/src/main/java/io/realm/BaseRealm.java index 3da480ff26..82a8e8b52b 100644 --- a/realm/realm-library/src/main/java/io/realm/BaseRealm.java +++ b/realm/realm-library/src/main/java/io/realm/BaseRealm.java @@ -183,13 +183,18 @@ public boolean isAutoRefresh() { * It also calls any listeners associated with the Realm if needed. *

              * WARNING: Calling this on a thread with async queries will turn those queries into synchronous queries. - * In most cases it is better to use {@link RealmChangeListener}s to be notified about changes to the - * Realm on a given thread than it is to use this method. + * This means this method will throw a {@link RealmException} if + * {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)} was used with {@code true} to + * obtain a Realm instance. In most cases it is better to use {@link RealmChangeListener}s to be notified + * about changes to the Realm on a given thread than it is to use this method. * * @throws IllegalStateException if attempting to refresh from within a transaction. + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. */ public void refresh() { checkIfValid(); + checkAllowQueriesOnUiThread(); + if (isInTransaction()) { throw new IllegalStateException("Cannot refresh a Realm instance inside a transaction."); } @@ -499,6 +504,30 @@ protected void checkIfValid() { } } + /** + * Checks whether queries are allowed from the UI thread in the current RealmConfiguration. + */ + protected void checkAllowQueriesOnUiThread() { + // Warn on query being executed on UI thread if isAllowQueriesOnUiThread is set to true, throw otherwise + if (getSharedRealm().capabilities.isMainThread()) { + if (!getConfiguration().isAllowQueriesOnUiThread()) { + throw new RealmException("Queries on the UI thread have been disabled. They can be enabled by setting 'RealmConfiguration.Builder.allowQueriesOnUiThread(true)'."); + } + } + } + + /** + * Checks whether writes are allowed from the UI thread in the current RealmConfiguration. + */ + protected void checkAllowWritesOnUiThread() { + // Warn on transaction being executed on UI thread if allowWritesOnUiThread is set to true, throw otherwise + if (getSharedRealm().capabilities.isMainThread()) { + if (!getConfiguration().isAllowWritesOnUiThread()) { + throw new RealmException("Running transactions on the UI thread has been disabled. It can be enabled by setting 'RealmConfiguration.Builder.allowWritesOnUiThread(true)'."); + } + } + } + protected void checkIfInTransaction() { if (!sharedRealm.isInTransaction()) { throw new IllegalStateException("Changing Realm data can only be done from inside a transaction."); diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index 054b73336d..87b4e195e0 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -17,6 +17,9 @@ package io.realm; import java.util.Locale; +import java.util.concurrent.Future; + +import javax.annotation.Nullable; import io.reactivex.Flowable; import io.realm.annotations.RealmClass; @@ -26,10 +29,12 @@ import io.realm.internal.OsObject; import io.realm.internal.OsObjectStore; import io.realm.internal.OsSharedRealm; +import io.realm.internal.RealmNotifier; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; import io.realm.internal.Table; import io.realm.internal.Util; +import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.log.RealmLog; /** @@ -284,9 +289,14 @@ public void delete(String className) { * Executes a given transaction on the DynamicRealm. {@link #beginTransaction()} and * {@link #commitTransaction()} will be called automatically. If any exception is thrown * during the transaction {@link #cancelTransaction()} will be called instead of {@link #commitTransaction()}. + *

              + * Calling this method from the UI thread will throw a {@link RealmException}. Doing so may result in a drop of frames + * or even ANRs. We recommend calling this method from a non-UI thread or using + * {@link #executeTransactionAsync(Transaction)} instead. * - * @param transaction {@link io.realm.DynamicRealm.Transaction} to execute. + * @param transaction {@link Transaction} to execute. * @throws IllegalArgumentException if the {@code transaction} is {@code null}. + * @throws RealmException if called from the UI thread, unless an explicit opt-in has been declared in {@link RealmConfiguration.Builder#allowWritesOnUiThread(boolean)}. */ public void executeTransaction(Transaction transaction) { //noinspection ConstantConditions @@ -294,6 +304,8 @@ public void executeTransaction(Transaction transaction) { throw new IllegalArgumentException("Transaction should not be null"); } + checkAllowWritesOnUiThread(); + beginTransaction(); try { transaction.execute(this); @@ -308,6 +320,182 @@ public void executeTransaction(Transaction transaction) { } } + /** + * Similar to {@link #executeTransaction(Transaction)} but runs asynchronously on a worker thread. + * + * @param transaction {@link Transaction} to execute. + * @return a {@link RealmAsyncTask} representing a cancellable task. + * @throws IllegalArgumentException if the {@code transaction} is {@code null}, or if the Realm is opened from + * another thread. + */ + public RealmAsyncTask executeTransactionAsync(final Transaction transaction) { + return executeTransactionAsync(transaction, null, null); + } + + /** + * Similar to {@link #executeTransactionAsync(Transaction)}, but also accepts an OnSuccess callback. + * + * @param transaction {@link Transaction} to execute. + * @param onSuccess callback invoked when the transaction succeeds. + * @return a {@link RealmAsyncTask} representing a cancellable task. + * @throws IllegalArgumentException if the {@code transaction} is {@code null}, or if the realm is opened from + * another thread. + */ + public RealmAsyncTask executeTransactionAsync(final Transaction transaction, final Transaction.OnSuccess onSuccess) { + //noinspection ConstantConditions + if (onSuccess == null) { + throw new IllegalArgumentException("onSuccess callback can't be null"); + } + + return executeTransactionAsync(transaction, onSuccess, null); + } + + /** + * Similar to {@link #executeTransactionAsync(Transaction)}, but also accepts an OnError callback. + * + * @param transaction {@link Transaction} to execute. + * @param onError callback invoked when the transaction fails. + * @return a {@link RealmAsyncTask} representing a cancellable task. + * @throws IllegalArgumentException if the {@code transaction} is {@code null}, or if the realm is opened from + * another thread. + */ + public RealmAsyncTask executeTransactionAsync(final Transaction transaction, final Transaction.OnError onError) { + //noinspection ConstantConditions + if (onError == null) { + throw new IllegalArgumentException("onError callback can't be null"); + } + + return executeTransactionAsync(transaction, null, onError); + } + + /** + * Similar to {@link #executeTransactionAsync(Transaction)}, but also accepts an OnSuccess and OnError callbacks. + * + * @param transaction {@link Transaction} to execute. + * @param onSuccess callback invoked when the transaction succeeds. + * @param onError callback invoked when the transaction fails. + * @return a {@link RealmAsyncTask} representing a cancellable task. + * @throws IllegalArgumentException if the {@code transaction} is {@code null}, or if the realm is opened from + * another thread. + */ + public RealmAsyncTask executeTransactionAsync(final Transaction transaction, + @Nullable final Transaction.OnSuccess onSuccess, + @Nullable final Transaction.OnError onError) { + checkIfValid(); + + //noinspection ConstantConditions + if (transaction == null) { + throw new IllegalArgumentException("Transaction should not be null"); + } + + if (isFrozen()) { + throw new IllegalStateException("Write transactions on a frozen Realm is not allowed."); + } + + // Avoid to call canDeliverNotification() in bg thread. + final boolean canDeliverNotification = sharedRealm.capabilities.canDeliverNotification(); + + // If the user provided a Callback then we have to make sure the current Realm has an events looper to deliver + // the results. + if ((onSuccess != null || onError != null)) { + sharedRealm.capabilities.checkCanDeliverNotification("Callback cannot be delivered on current thread."); + } + + // We need to use the same configuration to open a background OsSharedRealm (i.e Realm) + // to perform the transaction + final RealmConfiguration realmConfiguration = getConfiguration(); + // We need to deliver the callback even if the Realm is closed. So acquire a reference to the notifier here. + final RealmNotifier realmNotifier = sharedRealm.realmNotifier; + + final Future pendingTransaction = asyncTaskExecutor.submitTransaction(new Runnable() { + @Override + public void run() { + if (Thread.currentThread().isInterrupted()) { + return; + } + + OsSharedRealm.VersionID versionID = null; + Throwable exception = null; + + final DynamicRealm bgRealm = DynamicRealm.getInstance(realmConfiguration); + bgRealm.beginTransaction(); + try { + transaction.execute(bgRealm); + + if (Thread.currentThread().isInterrupted()) { + return; + } + + bgRealm.commitTransaction(); + // The bgRealm needs to be closed before post event to caller's handler to avoid concurrency + // problem. This is currently guaranteed by posting callbacks later below. + versionID = bgRealm.sharedRealm.getVersionID(); + } catch (final Throwable e) { + exception = e; + } finally { + try { + if (bgRealm.isInTransaction()) { + bgRealm.cancelTransaction(); + } + } finally { + bgRealm.close(); + } + } + + final Throwable backgroundException = exception; + final OsSharedRealm.VersionID backgroundVersionID = versionID; + // Cannot be interrupted anymore. + if (canDeliverNotification) { + if (backgroundVersionID != null && onSuccess != null) { + realmNotifier.post(new Runnable() { + @Override + public void run() { + if (isClosed()) { + // The caller Realm is closed. Just call the onSuccess. Since the new created Realm + // cannot be behind the background one. + onSuccess.onSuccess(); + return; + } + + if (sharedRealm.getVersionID().compareTo(backgroundVersionID) < 0) { + sharedRealm.realmNotifier.addTransactionCallback(new Runnable() { + @Override + public void run() { + onSuccess.onSuccess(); + } + }); + } else { + onSuccess.onSuccess(); + } + } + }); + } else if (backgroundException != null) { + realmNotifier.post(new Runnable() { + @Override + public void run() { + if (onError != null) { + onError.onError(backgroundException); + } else { + throw new RealmException("Async transaction failed", backgroundException); + } + } + }); + } + } else { + if (backgroundException != null) { + // FIXME: ThreadPoolExecutor will never throw the exception in the background. + // We need a redesign of the async transaction API. + // Throw in the worker thread since the caller thread cannot get notifications. + throw new RealmException("Async transaction failed", backgroundException); + } + } + + } + }); + + return new RealmAsyncTaskImpl(pendingTransaction, asyncTaskExecutor); + } + /** * Creates a {@link DynamicRealm} instance without checking the existence in the {@link RealmCache}. * @@ -395,6 +583,31 @@ void setVersion(long version) { */ public interface Transaction { void execute(DynamicRealm realm); + + /** + * Callback invoked to notify the caller thread. + */ + class Callback { + public void onSuccess() {} + + public void onError(Exception ignore) {} + } + + /** + * Callback invoked to notify the caller thread about the success of the transaction. + */ + interface OnSuccess { + void onSuccess(); + } + + /** + * Callback invoked to notify the caller thread about error during the transaction. + * The transaction will be rolled back and the background Realm will be closed before + * invoking {@link #onError(Throwable)}. + */ + interface OnError { + void onError(Throwable error); + } } /** diff --git a/realm/realm-library/src/main/java/io/realm/Realm.java b/realm/realm-library/src/main/java/io/realm/Realm.java index 5acc74f051..e4574b1c65 100644 --- a/realm/realm-library/src/main/java/io/realm/Realm.java +++ b/realm/realm-library/src/main/java/io/realm/Realm.java @@ -1514,10 +1514,15 @@ public void removeAllChangeListeners() { * Executes a given transaction on the Realm. {@link #beginTransaction()} and {@link #commitTransaction()} will be * called automatically. If any exception is thrown during the transaction {@link #cancelTransaction()} will be * called instead of {@link #commitTransaction()}. + *

              + * Calling this method from the UI thread will throw a {@link RealmException}. Doing so may result in a drop of frames + * or even ANRs. We recommend calling this method from a non-UI thread or using + * {@link #executeTransactionAsync(Transaction)} instead. * * @param transaction the {@link io.realm.Realm.Transaction} to execute. * @throws IllegalArgumentException if the {@code transaction} is {@code null}. * @throws RealmMigrationNeededException if the latest version contains incompatible schema changes. + * @throws RealmException if called from the UI thread, unless an explicit opt-in has been declared in {@link RealmConfiguration.Builder#allowWritesOnUiThread(boolean)}. */ public void executeTransaction(Transaction transaction) { //noinspection ConstantConditions @@ -1525,6 +1530,8 @@ public void executeTransaction(Transaction transaction) { throw new IllegalArgumentException("Transaction should not be null"); } + checkAllowWritesOnUiThread(); + beginTransaction(); try { transaction.execute(this); diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 1ece2260e2..90dc3f4f6b 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -19,7 +19,6 @@ import android.content.Context; import java.io.File; -import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; @@ -32,7 +31,6 @@ import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; -import io.realm.exceptions.RealmFileException; import io.realm.internal.OsRealmConfig; import io.realm.internal.RealmCore; import io.realm.internal.RealmProxyMediator; @@ -100,6 +98,8 @@ public class RealmConfiguration { private final boolean readOnly; private final CompactOnLaunchCallback compactOnLaunch; private final long maxNumberOfActiveVersions; + private final boolean allowWritesOnUiThread; + private final boolean allowQueriesOnUiThread; /** * Whether this RealmConfiguration is intended to open a @@ -122,7 +122,9 @@ protected RealmConfiguration(File realmPath, boolean readOnly, @Nullable CompactOnLaunchCallback compactOnLaunch, boolean isRecoveryConfiguration, - long maxNumberOfActiveVersions) { + long maxNumberOfActiveVersions, + boolean allowWritesOnUiThread, + boolean allowQueriesOnUiThread) { this.realmDirectory = realmPath.getParentFile(); this.realmFileName = realmPath.getName(); this.canonicalPath = realmPath.getAbsolutePath(); @@ -139,6 +141,8 @@ protected RealmConfiguration(File realmPath, this.compactOnLaunch = compactOnLaunch; this.isRecoveryConfiguration = isRecoveryConfiguration; this.maxNumberOfActiveVersions = maxNumberOfActiveVersions; + this.allowWritesOnUiThread = allowWritesOnUiThread; + this.allowQueriesOnUiThread = allowQueriesOnUiThread; } public File getRealmDirectory() { @@ -288,6 +292,30 @@ public long getMaxNumberOfActiveVersions() { return maxNumberOfActiveVersions; } + /** + * Returns whether calls to {@link Realm#executeTransaction} can be done on the UI thread. + *

              + * Note: Realm does not allow blocking transactions to be run on the main thread unless users explicitly opt in with + * {@link Builder#allowWritesOnUiThread(boolean)} or its Realm Sync builder counterpart. + * + * @return whether or not write operations are allowed to be run from the UI thread. + */ + public boolean isAllowWritesOnUiThread() { + return allowWritesOnUiThread; + } + + /** + * Returns whether a {@link RealmQuery} is allowed to be launched from the UI thread. + *

              + * By default Realm allows queries on the main thread. To disallow this users have to explicitly opt in with + * {@link Builder#allowQueriesOnUiThread(boolean)} or its Realm Sync builder counterpart. + * + * @return whether or not queries are allowed to be run from the UI thread. + */ + public boolean isAllowQueriesOnUiThread() { + return allowQueriesOnUiThread; + } + @Override public boolean equals(Object obj) { if (this == obj) { return true; } @@ -432,7 +460,7 @@ protected boolean isSyncConfiguration() { } protected static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) { - return new RealmConfiguration(new File(canonicalPath),null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true, Long.MAX_VALUE); + return new RealmConfiguration(new File(canonicalPath),null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true, Long.MAX_VALUE, false, true); } /** @@ -455,6 +483,8 @@ public static class Builder { private boolean readOnly; private CompactOnLaunchCallback compactOnLaunch; private long maxNumberOfActiveVersions = Long.MAX_VALUE; + private boolean allowWritesOnUiThread; + private boolean allowQueriesOnUiThread; /** * Creates an instance of the Builder for the RealmConfiguration. @@ -490,6 +520,8 @@ private void initializeBuilder(Context context) { if (DEFAULT_MODULE != null) { this.modules.add(DEFAULT_MODULE); } + this.allowWritesOnUiThread = false; + this.allowQueriesOnUiThread = true; } /** @@ -797,6 +829,30 @@ final Builder schema(Class firstClass, Class + * WARNING: Realm does not allow synchronous transactions to be run on the main thread unless users explicitly opt in + * with this method. We recommend diverting calls to {@code executeTransaction} to non-UI threads or, alternatively, + * using {@link Realm#executeTransactionAsync}. + */ + public Builder allowWritesOnUiThread(boolean allowWritesOnUiThread) { + this.allowWritesOnUiThread = allowWritesOnUiThread; + return this; + } + + /** + * Sets whether or not a {@link RealmQuery} can be launched from the UI thread. + *

              + * By default Realm allows queries on the main thread. However, by doing so your application may experience a drop of + * frames or even ANRs. We recommend diverting queries to non-UI threads or, alternatively, using + * {@link RealmQuery#findAllAsync()} or {@link RealmQuery#findFirstAsync()}. + */ + public Builder allowQueriesOnUiThread(boolean allowQueriesOnUiThread) { + this.allowQueriesOnUiThread = allowQueriesOnUiThread; + return this; + } + /** * Creates the RealmConfiguration based on the builder parameters. * @@ -837,7 +893,9 @@ public RealmConfiguration build() { readOnly, compactOnLaunch, false, - maxNumberOfActiveVersions + maxNumberOfActiveVersions, + allowWritesOnUiThread, + allowQueriesOnUiThread ); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 0f29dc4ac3..494b541efa 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -27,6 +27,7 @@ import javax.annotation.Nullable; import io.realm.annotations.Required; +import io.realm.exceptions.RealmException; import io.realm.internal.OsList; import io.realm.internal.OsResults; import io.realm.internal.PendingRow; @@ -51,6 +52,12 @@ * is required. *

              * A RealmQuery cannot be passed between different threads. + *

              + * Results are obtained quickly most of the times. However, launching heavy queries from the UI thread may result + * in a drop of frames or even ANRs. If you want to prevent these behaviors, you can instantiate a Realm using a + * {@link RealmConfiguration} that explicitly sets {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)} to + * {@code false}. This way queries will be forced to be launched from a non-UI thread. Alternatively, you can also use + * {@link #findAllAsync()} or {@link #findFirstAsync()}. * * @param the class of the objects to be queried. * @see Builder pattern @@ -1825,9 +1832,11 @@ public RealmQuery isNotEmpty(String fieldName) { * for the given field, {@code 0} will be returned. When computing the sum, objects with {@code null} values * are ignored. * @throws java.lang.IllegalArgumentException if the field is not a number type. + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. */ public Number sum(String fieldName) { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); long columnKey = schema.getAndCheckFieldColumnKey(fieldName); switch (table.getColumnType(columnKey)) { @@ -1854,9 +1863,11 @@ public Number sum(String fieldName) { * types of number fields. If no objects exist or they all have {@code null} as the value for the given field, * {@code 0} will be returned. When computing the average, objects with {@code null} values are ignored. * @throws java.lang.IllegalArgumentException if the field is not a number type. + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. */ public double average(String fieldName) { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); switch (table.getColumnType(columnIndex)) { @@ -1879,9 +1890,11 @@ public double average(String fieldName) { * @return the average for the given field amongst objects in query results. This will be of type Decimal128. If no objects exist or they all have {@code null} * as the value for the given field {@code 0} will be returned. When computing the average, objects with {@code null} values are ignored. * @throws java.lang.IllegalArgumentException if the field is not a Decimal128 type. + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. */ public @Nullable Decimal128 averageDecimal128(String fieldName) { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); return query.averageDecimal128(columnIndex); @@ -1894,10 +1907,12 @@ public double average(String fieldName) { * returned. Otherwise the minimum value is returned. When determining the minimum value, objects with {@code null} * values are ignored. * @throws java.lang.IllegalArgumentException if the field is not a number type. + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. */ @Nullable public Number min(String fieldName) { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); switch (table.getColumnType(columnIndex)) { @@ -1923,10 +1938,12 @@ public Number min(String fieldName) { * will be returned. Otherwise the minimum date is returned. When determining the minimum date, objects with * {@code null} values are ignored. * @throws java.lang.UnsupportedOperationException if the query is not valid ("syntax error"). + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. */ @Nullable public Date minimumDate(String fieldName) { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); return this.query.minimumDate(columnIndex); @@ -1940,10 +1957,12 @@ public Date minimumDate(String fieldName) { * returned. Otherwise the maximum value is returned. When determining the maximum value, objects with {@code null} * values are ignored. * @throws java.lang.IllegalArgumentException if the field is not a number type. + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. */ @Nullable public Number max(String fieldName) { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); switch (table.getColumnType(columnIndex)) { @@ -1973,6 +1992,7 @@ public Number max(String fieldName) { @Nullable public Date maximumDate(String fieldName) { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); long columnIndex = schema.getAndCheckFieldColumnKey(fieldName); return this.query.maximumDate(columnIndex); @@ -1983,9 +2003,12 @@ public Date maximumDate(String fieldName) { * * @return the number of matching objects. * @throws java.lang.UnsupportedOperationException if the query is not valid ("syntax error"). + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. */ public long count() { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); + // The fastest way of doing `count()` is going through `TableQuery.count()`. Unfortunately // doing this does not correctly apply all side effects of queries (like subscriptions). Also // some queries constructs, like doing distinct is not easily supported this way. @@ -1996,14 +2019,23 @@ public long count() { /** * Finds all objects that fulfill the query conditions. + *

              + * Launching heavy queries from the UI thread may result in a drop of frames or even ANRs. We do not recommend + * doing so and therefore it is not allowed by default. If you want to prevent these behaviors you can obtain + * a Realm using a {@link RealmConfiguration} that explicitly sets + * {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)} to {@code false}. This way you will be forced + * to launch your queries from a non-UI thread, otherwise calls to this method will throw a {@link RealmException}. + * Alternatively, you can use {@link #findAllAsync()}. * * @return a {@link io.realm.RealmResults} containing objects. If no objects match the condition, a list with zero * objects is returned. + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. * @see io.realm.RealmResults */ @SuppressWarnings("unchecked") public RealmResults findAll() { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); return createRealmResults(query, queryDescriptors, true); } @@ -2231,13 +2263,22 @@ private boolean isDynamicQuery() { /** * Finds the first object that fulfills the query conditions. + *

              + * Launching heavy queries from the UI thread may result in a drop of frames or even ANRs. We do not recommend + * doing so, but it is allowed by default. If you want to prevent these behaviors you can obtain a Realm using + * a {@link RealmConfiguration} that explicitly sets + * {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)} to {@code false}. This way you will be forced + * to launch your queries from a non-UI thread, otherwise calls to this method will throw a {@link RealmException}. + * Alternatively, you can use {@link #findFirstAsync()}. * * @return the object found or {@code null} if no object matches the query conditions. + * @throws RealmException if called from the UI thread after opting out via {@link RealmConfiguration.Builder#allowQueriesOnUiThread(boolean)}. * @see io.realm.RealmObject */ @Nullable public E findFirst() { realm.checkIfValid(); + realm.checkAllowQueriesOnUiThread(); if (forValues) { // TODO implement this; diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index 43000c5092..8c947aff21 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -48,6 +48,7 @@ import io.realm.RealmConfiguration; import io.realm.RealmMigration; import io.realm.RealmModel; +import io.realm.RealmQuery; import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; import io.realm.exceptions.RealmException; @@ -119,6 +120,8 @@ private SyncConfiguration(File realmPath, @Nullable Realm.Transaction initialDataTransaction, boolean readOnly, long maxNumberOfActiveVersions, + boolean allowWritesOnUiThread, + boolean allowQueriesOnUiThread, User user, URI serverUrl, SyncSession.ErrorHandler errorHandler, @@ -144,7 +147,9 @@ private SyncConfiguration(File realmPath, readOnly, compactOnLaunch, false, - maxNumberOfActiveVersions + maxNumberOfActiveVersions, + allowWritesOnUiThread, + allowQueriesOnUiThread ); this.user = user; @@ -481,6 +486,8 @@ public static final class Builder { @Nullable // null means the user hasn't explicitly set one. An appropriate default is chosen when calling build() private ClientResyncMode clientResyncMode = null; private long maxNumberOfActiveVersions = Long.MAX_VALUE; + private boolean allowWritesOnUiThread; + private boolean allowQueriesOnUiThread; private final BsonValue partitionValue; /** @@ -551,6 +558,7 @@ public Builder(User user, @Nullable Long partitionValue) { } this.errorHandler = user.getApp().getConfiguration().getDefaultErrorHandler(); this.clientResetHandler = user.getApp().getConfiguration().getDefaultClientResetHandler(); + this.allowWritesOnUiThread = false; } private void validateAndSet(User user) { @@ -1012,6 +1020,30 @@ public Builder maxNumberOfActiveVersions(long number) { return this; } + /** + * Sets whether or not calls to {@link Realm#executeTransaction} are allowed from the UI thread. + *

              + * WARNING: Realm does not allow synchronous transactions to be run on the main thread unless users explicitly opt in + * with this method. We recommend diverting calls to {@code executeTransaction} to non-UI threads or, alternatively, + * using {@link Realm#executeTransactionAsync}. + */ + public Builder allowWritesOnUiThread(boolean allowWritesOnUiThread) { + this.allowWritesOnUiThread = allowWritesOnUiThread; + return this; + } + + /** + * Sets whether or not {@code RealmQueries} are allowed from the UI thread. + *

              + * By default Realm allows queries on the main thread. However, by doing so your application may experience a drop of + * frames or even ANRs. We recommend diverting queries to non-UI threads or, alternatively, using + * {@link RealmQuery#findAllAsync()} or {@link RealmQuery#findFirstAsync()}. + */ + public Builder allowQueriesOnUiThread(boolean allowQueriesOnUiThread) { + this.allowQueriesOnUiThread = allowQueriesOnUiThread; + return this; + } + /** * Creates the RealmConfiguration based on the builder parameters. * @@ -1066,6 +1098,8 @@ public SyncConfiguration build() { initialDataTransaction, readOnly, maxNumberOfActiveVersions, + allowWritesOnUiThread, + allowQueriesOnUiThread, // Sync Configuration specific user, diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java index 9a41db3390..b634d29e41 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -179,6 +179,9 @@ public RealmConfiguration createConfiguration(String subDir, String name, Object builder.encryptionKey(key); } + // Allow writes on UI + builder.allowWritesOnUiThread(true); + RealmConfiguration configuration = builder.build(); configurations.add(configuration); From 0a9b48d58e5b30dee100088b0399f15865250996 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 2 Oct 2020 19:01:15 +0200 Subject: [PATCH 1712/2110] Upgrade to Sync 10.0.0-beta.14 i preperation for RC.1 release (#7139) --- CHANGELOG.md | 6 ++++-- dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- version.txt | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42890b709b..418eb4c5c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.0-BETA.9 (YYYY-MM-DD) +## 10.0.0-RC.1 (YYYY-MM-DD) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. @@ -25,7 +25,9 @@ The old Realm Cloud legacy APIs have undergone significant refactoring. The new * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal -* None. +* Updated to Object Store commit: ef6736cc07a8b94d1242c522969114bb8047deef +* Updated to Realm Sync 10.0.0-beta.14. +* Updated to Realm Core 10.0.0-beta.9. ## 10.0.0-BETA.8 (2020-09-23) diff --git a/dependencies.list b/dependencies.list index 679e3a2219..8b533ad6f6 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC=10.0.0-beta.11 -REALM_SYNC_SHA256=da27012959fdd7e35b9b0f3274a1dd7ad391e027217527cf8897020a4c1562d6 +REALM_SYNC=10.0.0-beta.14 +REALM_SYNC_SHA256=2f0d2bbbaa42c8644487c3fcaeff0aab8b439e6170203c776892665f59f779e2 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 035eb07f3e..ef6736cc07 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 035eb07f3ef313bfb78c046be9cf6b4f065d6772 +Subproject commit ef6736cc07a8b94d1242c522969114bb8047deef diff --git a/version.txt b/version.txt index 43447710d9..b3b6f2ba91 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-BETA.9-SNAPSHOT \ No newline at end of file +10.0.0-RC.1-SNAPSHOT \ No newline at end of file From 485e3e6d5979fa18873c22ec5fec305811c6b2ba Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 2 Oct 2020 19:02:48 +0200 Subject: [PATCH 1713/2110] Release 10.0.0-RC.1 --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 418eb4c5c1..a195897095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.0-RC.1 (YYYY-MM-DD) +## 10.0.0-RC.1 (2020-10-02) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. diff --git a/version.txt b/version.txt index b3b6f2ba91..726b6f85e0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-RC.1-SNAPSHOT \ No newline at end of file +10.0.0-RC.1 \ No newline at end of file From 9dcb17d5ea16ce787e759348e93d1b207d6d6eb9 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 2 Oct 2020 20:33:44 +0200 Subject: [PATCH 1714/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 726b6f85e0..2852352017 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-RC.1 \ No newline at end of file +10.0.0-RC.2-SNAPSHOT \ No newline at end of file From 8fdad881838c49514f9ace9d433e6c12850d7647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Mon, 5 Oct 2020 11:59:10 +0200 Subject: [PATCH 1715/2110] Enforced assemble task to be run to generate the aar needed by the publish to local maven task (#7142) --- realm/kotlin-extensions/build.gradle | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 8e8db72272..0f6f0e741d 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -196,7 +196,9 @@ publishing { groupId 'io.realm' artifactId 'realm-android-kotlin-extensions' version project.version - artifact file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-base-release.aar") + artifact (file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-base-release.aar")) { + builtBy assemble + } artifact sourcesJar artifact javadocJar @@ -207,7 +209,9 @@ publishing { groupId 'io.realm' artifactId 'realm-android-kotlin-extensions-object-server' version project.version - artifact file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-objectServer-release.aar") + artifact (file("${rootDir}/kotlin-extensions/build/outputs/aar/realm-kotlin-extensions-objectServer-release.aar")) { + builtBy assemble + } artifact sourcesJar artifact javadocJar From 20f49687f96cfdee025d1ab0d0104ae3dff7cfc1 Mon Sep 17 00:00:00 2001 From: Sergey Gerasimenko Date: Wed, 7 Oct 2020 18:30:11 +0300 Subject: [PATCH 1716/2110] Update CONTRIBUTING.md (#7149) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3a859633f..b350475a6e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ We love contributions to Realm! If you'd like to contribute code, documentation, Realm welcomes all contributions! The only requirement we have is that, like many other projects, we need to have a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) (CLA) in place before we can accept any external code. Our own CLA is a modified version of the Apache Software Foundation’s CLA. -[Please submit your CLA electronically using our Google form](https://docs.google.com/forms/d/1bVp-Wp5nmNFz9Nx-ngTmYBVWVdwTyKj4T0WtfVm0Ozs/viewform?fbzx=4154977190905366979) so we can accept your submissions. The GitHub username you file there will need to match that of your Pull Requests. If you have any questions or cannot file the CLA electronically, you can email . +[Please submit your CLA electronically using our Google form](https://docs.google.com/forms/d/e/1FAIpQLSeQ9ROFaTu9pyrmPhXc-dEnLD84DbLuT_-tPNZDOL9J10tOKQ/viewform) so we can accept your submissions. The GitHub username you file there will need to match that of your Pull Requests. If you have any questions or cannot file the CLA electronically, you can email . ## Repository Guidelines From 9e1aa152b4fb41793ea0cac9629355c38f8b5db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:16:57 +0200 Subject: [PATCH 1717/2110] Preparing v10 RC2 (#7150) --- CHANGELOG.md | 21 +++ .../kotlin/io/realm/EmbeddedObjectsTest.kt | 164 ------------------ .../embedded/EmbeddedCircularChild.kt | 29 ---- .../embedded/EmbeddedCircularParent.kt | 26 --- ...EmbeddedCircularParentWithoutPrimaryKey.kt | 26 --- .../entities/embedded/EmbeddedTreeNode.kt | 3 - .../kotlin/io/realm/FunctionsTests.kt | 2 +- .../io/realm/mongodb/MongoClientTest.kt | 85 ++++++++- ...alm_internal_objectstore_OsMongoClient.cpp | 16 +- ...internal_objectstore_OsMongoCollection.cpp | 54 +++--- ...m_internal_objectstore_OsMongoDatabase.cpp | 8 +- ...ngodb_mongo_iterable_AggregateIterable.cpp | 2 +- ...lm_mongodb_mongo_iterable_FindIterable.cpp | 4 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../internal/objectstore/OsMongoClient.java | 6 +- .../objectstore/OsMongoCollection.java | 9 +- .../java/io/realm/mongodb/User.java | 3 +- .../app_config/auth_providers/anon-user.json | 2 +- .../app_config/auth_providers/api-key.json | 2 +- .../auth_providers/custom-function.json | 2 +- .../auth_providers/local-userpass.json | 4 +- .../app_config/functions/authFunc/config.json | 2 +- .../functions/authorizedOnly/config.json | 2 +- .../functions/confirmFunc/config.json | 2 +- .../app_config/functions/error/config.json | 2 +- .../app_config/functions/firstArg/config.json | 2 +- .../app_config/functions/null/config.json | 2 +- .../functions/resetFunc/config.json | 2 +- .../app_config/functions/sum/config.json | 2 +- .../functions/testAuthFunc/config.json | 2 +- .../app_config/functions/void/config.json | 2 +- .../app_config/services/BackingDB/config.json | 2 +- .../BackingDB/rules/test_data.SyncDog.json | 2 +- .../BackingDB/rules/test_data.SyncPerson.json | 2 +- .../rules/test_data.custom_user_data.json | 2 +- .../BackingDB/rules/test_data.mongo_data.json | 2 +- .../rules/test_data.mongo_data_alt.json | 16 ++ .../app_config/services/gcm/config.json | 2 +- 38 files changed, 185 insertions(+), 333 deletions(-) delete mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt delete mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt delete mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt create mode 100644 tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data_alt.json diff --git a/CHANGELOG.md b/CHANGELOG.md index a195897095..2b48a64b23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 10.0.0-RC.2 (YYYY-MM-DD) + +### Breaking Changes +* None. + +### Enhancements +* [RealmApp] Illegal schemas where embedded object classes referenced each other is now correctly detected and throws and exception when opening a Realm with such a schema. + +### Fixed +* [RealmApp] It is now possible to use types different than `ObjectId` for the `_id` field in documents inserted with `MongoCollection.insertOne` and `MongoCollection.insertMany`. +* [RealmApp] Lossy round trip of Double and Timestamps through functions when using Bson. (ObjectStore issue (#1106)[https://github.com/realm/realm-object-store/issues/1106]) + +### Compatibility +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java 10.0.0-BETA.1. +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 and above is required to open Realms created by this version. + +### Internal +* Updated to Object Store commit: da02579eea7b36eb84d2c9f88988b7f32c83eb83. + + ## 10.0.0-RC.1 (2020-10-02) We no longer support Realm Cloud (legacy), but instead the new MongoDB Realm Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt index eabe7144a1..d7d1b6a89f 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/EmbeddedObjectsTest.kt @@ -48,15 +48,6 @@ private const val childId1 = "childId1" private const val childId2 = "childId2" private const val childId3 = "childId3" -private val circularParentData = mapOf( - "_id" to parentId, - "singleChild" to mapOf( - "circularChildId" to childId, - "singleChild" to mapOf( - "circularChildId" to embeddedChildId - ) - ) -) private val simpleListParentData = mapOf( "_id" to parentId, "children" to listOf( @@ -401,32 +392,6 @@ class EmbeddedObjectsTest { assertTrue(leafResults.any { it.treeLeafId == "leaf3" }) } - @Test - fun copyToRealm_circularSchema() { - realm.executeTransaction { - val parent = EmbeddedCircularParent("parent") - val child1 = EmbeddedCircularChild("child1") - val child2 = EmbeddedCircularChild("child2") - child1.singleChild = child2 - parent.singleChild = child1 - it.copyToRealm(parent) - } - - assertEquals(1, realm.where().count()) - assertEquals(2, realm.where().count()) - } - - @Test - fun copyToRealm_throwsIfMultipleRefsToSingleObjectsExists() { - realm.executeTransaction { r -> - val parent = EmbeddedCircularParent("parent") - val child = EmbeddedCircularChild("child") - child.singleChild = child // Create circle between children - parent.singleChild = child - assertFailsWith { r.copyToRealm(parent) } - } - } - @Test fun copyToRealm_throwsIfMultipleRefsToListObjectsExists() { realm.executeTransaction { r -> @@ -512,21 +477,6 @@ class EmbeddedObjectsTest { assertTrue(leafResults.any { it.treeLeafId == "leaf3" }) } - @Test - fun insert_circularSchema() { - realm.executeTransaction { - val parent = EmbeddedCircularParent("parent") - val child1 = EmbeddedCircularChild("child1") - val child2 = EmbeddedCircularChild("child2") - child1.singleChild = child2 - parent.singleChild = child1 - it.insert(parent) - } - - assertEquals(1, realm.where().count()) - assertEquals(2, realm.where().count()) - } - @Test fun insertOrUpdate_deletesOldEmbeddedObject() { realm.executeTransaction { realm -> @@ -686,28 +636,6 @@ class EmbeddedObjectsTest { // converted to Kotlin // Sanity check of string based variants. Implementation dispatches to json variant covered // below, so not covering all cases for the string-variants. - @Test - fun createObjectFromJson_string_embeddedObject() { - realm.executeTransaction { realm -> - realm.createObjectFromJson(EmbeddedCircularParent::class.java, JSONObject(circularParentData).toString()) - } - val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! - val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.circularChildId) - assertEquals("embeddedChildId", singleChild.singleChild!!.circularChildId) - } - - @Test - fun createObjectFromJson_json_embeddedObject() { - realm.executeTransaction { realm -> - realm.createObjectFromJson(EmbeddedCircularParent::class.java, JSONObject(circularParentData).toString()) - } - val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! - val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.circularChildId) - assertEquals(embeddedChildId, singleChild.singleChild!!.circularChildId) - } - @Test fun createObjectFromJson_json_embeddedObjectList() { realm.executeTransaction { realm -> @@ -720,35 +648,6 @@ class EmbeddedObjectsTest { assertEquals(childId3, parent.children[2]!!.childId) } - @Test - fun createObjectFromJson_stream_embeddedObject() { - val clz = EmbeddedCircularParent::class.java - realm.executeTransaction { realm -> - assertTrue(realm.schema.getSchemaForClass(clz).hasPrimaryKey()) - realm.createObjectFromJson(clz, stream(circularParentData)) - } - val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! - val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.circularChildId) - assertEquals(embeddedChildId, singleChild.singleChild!!.circularChildId) - } - - // Stream based import implementation is differentiated depending on whether the class has a - // primary key, so add specific tests for that path. - @Test - fun createObjectFromJson_stream_embeddedObjectWithNoPrimaryKeyParent() { - val clz = EmbeddedCircularParentWithoutPrimaryKey::class.java - realm.executeTransaction { realm -> - assertFalse(realm.schema.getSchemaForClass(clz).hasPrimaryKey()) - realm.createObjectFromJson(clz, stream(circularParentData)) - } - val all = realm.where(EmbeddedCircularParentWithoutPrimaryKey::class.java).findAll() - assertEquals(1, all.count()) - val parent = all.first()!! - val child = parent.singleChild!! - assertEquals(childId, child.circularChildId) - } - @Test fun createObjectFromJson_stream_embeddedObjectList() { val clz = EmbeddedSimpleListParent::class.java @@ -783,69 +682,6 @@ class EmbeddedObjectsTest { assertEquals(childId3, parent.children[2]!!.childId) } - @Test - fun createOrUpdateFromJson_json_ignoreUnsetProperties() { - // Create initial instance - realm.executeTransaction { realm -> - realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, json(circularParentData)) - } - val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! - val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.circularChildId) - assertEquals(embeddedChildId, singleChild.singleChild!!.circularChildId) - - // Update existing objects, but without overwriting any properties - realm.executeTransaction { realm -> - val circularParentData = mapOf( - "_id" to parentId - ) - realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, json(circularParentData)) - } - val allParents = realm.where(EmbeddedCircularParent::class.java).findAll() - assertEquals(1, allParents.count()) - val allChildren = realm.where(EmbeddedCircularChild::class.java).findAll() - assertEquals(2, allChildren.count()) - val updatedCircularParent = allParents.first()!! - val updatedSingleChild = circularParent.singleChild!! - assertEquals(parentId, updatedCircularParent._id) - assertEquals(childId, updatedSingleChild.circularChildId) - assertEquals(embeddedChildId, updatedSingleChild.singleChild!!.circularChildId) - } - - @Test - fun createOrUpdateFromJson_stream_embeddedObject() { - // Create initial instance - realm.executeTransaction { realm -> - realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, stream(circularParentData)) - } - val circularParent = realm.where(EmbeddedCircularParent::class.java).findFirst()!! - val singleChild = circularParent.singleChild!! - assertEquals(childId, singleChild.circularChildId) - assertEquals(embeddedChildId, singleChild.singleChild!!.circularChildId) - - // Update existing objects, updating to new embedded object - realm.executeTransaction { realm -> - val circularParentData = mapOf( - "_id" to parentId, - "singleChild" to mapOf( - "circularChildId" to childId - ) - ) - realm.createOrUpdateObjectFromJson(EmbeddedCircularParent::class.java, stream(circularParentData)) - } - val allParents = realm.where(EmbeddedCircularParent::class.java).findAll() - assertEquals(1, allParents.count()) - val allChildren = realm.where(EmbeddedCircularChild::class.java).findAll() - assertEquals(1, allChildren.count()) - val updatedCircularParent = allParents.first()!! - val updatedSingleChild = circularParent.singleChild!! - assertEquals(parentId, updatedCircularParent._id) - assertEquals(childId, updatedSingleChild.circularChildId) - // Sub child will have been deleted as embedded object does not have primary key and is - // comletely replaced - assertNull(updatedSingleChild.singleChild) - } - @Test fun createObjectFromJson_orphanedEmbeddedObjectThrows() { throws { realm.createObjectFromJson(EmbeddedSimpleChild::class.java, json(simpleListParentData)) } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt deleted file mode 100644 index d1b0e82ed4..0000000000 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularChild.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ -package io.realm.entities.embedded - -import io.realm.RealmObject -import io.realm.annotations.RealmClass -import java.util.* - -/** - * Embedded object that point to itself. Note, this is only allowed in the schema. The actual - * objects are not allowed to have circular references. - */ -@RealmClass(embedded = true) -open class EmbeddedCircularChild(var circularChildId: String = UUID.randomUUID().toString()) : RealmObject() { - var singleChild: EmbeddedCircularChild? = null -} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt deleted file mode 100644 index eaf76cb52d..0000000000 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParent.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ -package io.realm.entities.embedded - -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey -import java.util.* - -// Parent pointing to an embedded object that has a circular schema, i.e. objects can point -// to themselves. Note, this isn't actually allowed at runtime. Only at schema validation time. -open class EmbeddedCircularParent(@PrimaryKey var _id: String = UUID.randomUUID().toString()) : RealmObject() { - var singleChild: EmbeddedCircularChild? = null -} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt deleted file mode 100644 index 22612ebaa1..0000000000 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedCircularParentWithoutPrimaryKey.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ -package io.realm.entities.embedded - -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey -import java.util.* - -// Parent pointing to an embedded object that has a circular schema, i.e. objects can point -// to themselves. Note, this isn't actually allowed at runtime. Only at schema validation time. -open class EmbeddedCircularParentWithoutPrimaryKey(var _id: String = UUID.randomUUID().toString()) : RealmObject() { - var singleChild: EmbeddedCircularChild? = null -} diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt index db8d4a40d8..ddec884f96 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/entities/embedded/EmbeddedTreeNode.kt @@ -24,12 +24,9 @@ import java.util.* // Middle-level node in a object-graph that is three-shaped, i.e. no circular references. // The tree depth can be described as: // - 1 TreeParent -// - 1 or more TreeNode's. I.e. a TreeNode can be the child of another TreeNode. // - 1 or more TreeLeaf objects. TreeLeaf objects are always at the bottom of tree. @RealmClass(embedded = true) open class EmbeddedTreeNode(var treeNodeId: String = UUID.randomUUID().toString()) : RealmObject() { - var middleNode: EmbeddedTreeNode? = null var leafNode: EmbeddedTreeLeaf? = null - var middleNodeList: RealmList = RealmList() var leafNodeList: RealmList = RealmList() } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt index bfa750aedc..c2c68b3ffd 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/FunctionsTests.kt @@ -131,7 +131,7 @@ class FunctionsTests { when (type) { BsonType.DOUBLE -> { assertEquals(1.4f, functions.callFunction(FIRST_ARG_FUNCTION, listOf(1.4f), java.lang.Float::class.java).toFloat()) - assertEquals(1.4, functions.callFunction(FIRST_ARG_FUNCTION, listOf(1.4f), java.lang.Double::class.java).toDouble()) + assertEquals(1.4, functions.callFunction(FIRST_ARG_FUNCTION, listOf(1.4), java.lang.Double::class.java).toDouble()) assertTypeOfFirstArgFunction(BsonDouble(1.4), BsonDouble::class.java) } BsonType.STRING -> { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt index 22fc5b339d..33f4dc5682 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt @@ -41,7 +41,8 @@ import org.junit.runner.RunWith import java.io.IOException import kotlin.test.* -private const val COLLECTION_NAME = "mongo_data" // name of collection used by tests +private const val COLLECTION_NAME = "mongo_data" // uses ObjectId as _id +private const val COLLECTION_NAME_ALT = "mongo_data_alt" // uses Integer as _id @RunWith(AndroidJUnit4::class) class MongoClientTest { @@ -62,7 +63,10 @@ class MongoClientTest { @After fun tearDown() { if (this::client.isInitialized) { - with(getCollectionInternal()) { + with(getCollectionInternal(COLLECTION_NAME)) { + deleteMany(Document()).get() + } + with(getCollectionInternal(COLLECTION_NAME_ALT)) { deleteMany(Document()).get() } } @@ -308,11 +312,35 @@ class MongoClientTest { assertEquals(1, count().get()) val doc2 = Document("hello", "world") - assertNotEquals(doc1.getObjectId("_id"), insertOne(doc2).get()!!.insertedId.asObjectId().value) + val insertOneResult = insertOne(doc2).get()!! + assertNotNull(insertOneResult.insertedId.asObjectId().value) assertEquals(2, count().get()) } } + @Test + fun insertOne_throwsWhenMixingIdTypes() { + with(getCollectionInternal()) { + // The default collection uses ObjectId for "_id" + val doc1 = Document("hello", "world").apply { this["_id"] = 666 } + assertFailsWith { + insertOne(doc1).get()!! + }.let { e -> + assertEquals("insert not permitted", e.errorMessage) + } + } + } + + @Test + fun insertOne_integerId() { + with(getCollectionInternal(COLLECTION_NAME_ALT)) { + val doc1 = Document("hello", "world").apply { this["_id"] = 666 } + val insertOneResult = insertOne(doc1).get()!! + assertEquals(doc1.getInteger("_id"), insertOneResult.insertedId.asInt32().value) + assertEquals(1, count().get()) + } + } + @Test fun insertOne_fails() { with(getCollectionInternal()) { @@ -380,6 +408,42 @@ class MongoClientTest { } } + @Test + fun insertMany_multipleDocuments_IntegerId() { + with(getCollectionInternal(COLLECTION_NAME_ALT)) { + val doc1 = Document("hello", "world").apply { this["_id"] = 42 } + val doc2 = Document("hello", "world").apply { this["_id"] = 42 + 1 } + val documents = listOf(doc1, doc2) + + insertMany(documents).get()!! + .insertedIds + .forEach { entry -> + assertEquals(documents[entry.key.toInt()]["_id"], entry.value.asInt32().value) + } + + val doc3 = Document("one", "two") + val doc4 = Document("three", 4) + + insertMany(listOf(doc3, doc4)).get() + assertEquals(4, count().get()) + } + } + + @Test + fun insertMany_throwsWhenMixingIdTypes() { + with(getCollectionInternal()) { + val doc1 = Document("hello", "world").apply { this["_id"] = 42 } + val doc2 = Document("hello", "world").apply { this["_id"] = 42 + 1 } + val documents = listOf(doc1, doc2) + + assertFailsWith { + insertMany(documents).get()!! + }.let { e -> + assertEquals("insert not permitted", e.errorMessage) + } + } + } + @Test fun insertMany_multipleDocuments_fails() { with(getCollectionInternal()) { @@ -1444,22 +1508,25 @@ class MongoClientTest { } } - private fun getCollectionInternal(): MongoCollection { + private fun getCollectionInternal( + collectionName: String = COLLECTION_NAME + ): MongoCollection { return client.getDatabase(DATABASE_NAME).let { assertEquals(it.name, DATABASE_NAME) - it.getCollection(COLLECTION_NAME).also { collection -> - assertEquals(MongoNamespace(DATABASE_NAME, COLLECTION_NAME), collection.namespace) + it.getCollection(collectionName).also { collection -> + assertEquals(MongoNamespace(DATABASE_NAME, collectionName), collection.namespace) } } } private fun getCollectionInternal( - resultClass: Class + resultClass: Class, + collectionName: String = COLLECTION_NAME ): MongoCollection { return client.getDatabase(DATABASE_NAME).let { assertEquals(it.name, DATABASE_NAME) - it.getCollection(COLLECTION_NAME, resultClass).also { collection -> - assertEquals(MongoNamespace(DATABASE_NAME, COLLECTION_NAME), collection.namespace) + it.getCollection(collectionName, resultClass).also { collection -> + assertEquals(MongoNamespace(DATABASE_NAME, collectionName), collection.namespace) } } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp index 745757da20..0ea793c7b9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp @@ -34,7 +34,7 @@ using namespace realm::jni_util; using namespace realm::_impl; static void finalize_client(jlong ptr) { - delete reinterpret_cast(ptr); + delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL @@ -45,13 +45,13 @@ Java_io_realm_internal_objectstore_OsMongoClient_nativeGetFinalizerMethodPtr(JNI JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsMongoClient_nativeCreate(JNIEnv* env, jclass, - jlong j_app_ptr, + jlong j_user_ptr, jstring j_service_name) { try { - std::shared_ptr &app = *reinterpret_cast *>(j_app_ptr); + std::shared_ptr& user = *reinterpret_cast*>(j_user_ptr); JStringAccessor name(env, j_service_name); - RemoteMongoClient client(app->remote_mongo_client(name)); - return reinterpret_cast(new RemoteMongoClient(std::move(client))); + MongoClient client(user->mongo_client(name)); + return reinterpret_cast(new MongoClient(std::move(client))); } CATCH_STD() return reinterpret_cast(nullptr); @@ -63,10 +63,10 @@ Java_io_realm_internal_objectstore_OsMongoClient_nativeCreateDatabase(JNIEnv* en jlong j_client_ptr, jstring j_database_name) { try { - RemoteMongoClient* client = reinterpret_cast(j_client_ptr); + auto client = reinterpret_cast(j_client_ptr); JStringAccessor name(env, j_database_name); - RemoteMongoDatabase database(client->db(name)); - return reinterpret_cast(new RemoteMongoDatabase(std::move(database))); + MongoDatabase database(client->db(name)); + return reinterpret_cast(new MongoDatabase(std::move(database))); } CATCH_STD() return reinterpret_cast(nullptr); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp index 217ec621cf..3b4023a165 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp @@ -43,47 +43,45 @@ static std::function collection_mapper_count = [](JN // This mapper works for both findOne and findOneAndUpdate/Replace functions static std::function)> collection_mapper_find_one = [](JNIEnv* env, util::Optional document) { - return document ? JniBsonProtocol::bson_to_jstring(env, *document) : NULL; + return document ? JniBsonProtocol::bson_to_jstring(env, *document) : nullptr; }; -static std::function)> collection_mapper_insert_one = [](JNIEnv* env, util::Optional object_id) { - if (object_id) { - return JavaClassGlobalDef::new_object_id(env, object_id.value()); +static std::function)> collection_mapper_insert_one = [](JNIEnv* env, util::Optional bson_id) { + if (bson_id) { + return JniBsonProtocol::bson_to_jstring(env, bson_id.value()); } - throw std::logic_error("Error in 'insert_one', parameter 'object_id' has no value."); + throw std::logic_error("Error in 'insert_one', parameter 'bson_id' has no value."); }; -static std::function)> collection_mapper_insert_many = [](JNIEnv* env, std::vector object_ids) { - if (object_ids.size() == 0) { +static std::function)> collection_mapper_insert_many = [](JNIEnv* env, std::vector bson_ids) { + if (bson_ids.empty()) { throw std::logic_error("Error in 'insert_many', parameter 'object_ids' is empty."); } - jobjectArray arr = (jobjectArray)env->NewObjectArray(static_cast(object_ids.size()), JavaClassGlobalDef::java_lang_object(), NULL); - if (arr == NULL) { + auto arr = (jobjectArray)env->NewObjectArray(static_cast(bson_ids.size()), JavaClassGlobalDef::java_lang_object(), nullptr); + if (arr == nullptr) { ThrowException(env, OutOfMemory, "Could not allocate memory to return list of ObjectIds of inserted documents."); return arr; } - for (size_t i = 0; i < object_ids.size(); ++i) { - jobject j_object_id = JavaClassGlobalDef::new_object_id(env, object_ids[i]); - env->SetObjectArrayElement(arr, i, j_object_id); + for (size_t i = 0; i < bson_ids.size(); ++i) { + env->SetObjectArrayElement(arr, i, JniBsonProtocol::bson_to_jstring(env, bson_ids[i])); } return arr; }; -static std::function collection_mapper_update = [](JNIEnv* env, RemoteMongoCollection::RemoteUpdateResult result) { +static std::function collection_mapper_update = [](JNIEnv* env, MongoCollection::UpdateResult result) { Bson matched_count(result.matched_count); Bson modified_count(result.modified_count); Bson upserted_value; if (result.upserted_id) { upserted_value = Bson(result.upserted_id.value()); } - // FIXME: maybe not the most efficient way. Suggestions? std::vector bson_vector = { matched_count, modified_count, upserted_value }; Bson output(bson_vector); return JniBsonProtocol::bson_to_jstring(env, output); }; static void finalize_collection(jlong ptr) { - delete reinterpret_cast(ptr); + delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL @@ -99,7 +97,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeCount(JNIEnv* env, jlong j_limit, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); uint64_t limit = std::uint64_t(j_limit); @@ -119,7 +117,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOne(JNIEnv* env, jlong j_limit, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); @@ -132,7 +130,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOne(JNIEnv* env, bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOptions options = { + MongoCollection::FindOptions options = { limit, projection, sort @@ -155,7 +153,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeInsertOne(JNIEnv* env jstring j_document, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_document, Bson::Type::Document, "BSON document must be a Document")); collection->insert_one(filter, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_insert_one)); @@ -170,7 +168,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeInsertMany(JNIEnv* en jstring j_documents, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); BsonArray bson_array(JniBsonProtocol::parse_checked(env, j_documents, Bson::Type::Array, "BSON documents must be a BsonArray")); collection->insert_many(bson_array, JavaNetworkTransport::create_result_callback(env, j_callback, collection_mapper_insert_many)); @@ -186,7 +184,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeDelete(JNIEnv* env, jstring j_document, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_document, Bson::Type::Document, "BSON document must be a Document")); switch (j_delete_type) { @@ -213,7 +211,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeUpdate(JNIEnv *env, jboolean j_upsert, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); @@ -251,7 +249,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndUpdate(JNIE jboolean j_return_new_document, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); @@ -263,7 +261,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndUpdate(JNIE case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_AND_UPDATE_WITH_OPTIONS: { bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { + MongoCollection::FindOneAndModifyOptions options = { projection, sort, to_bool(j_upsert), @@ -292,7 +290,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndReplace(JNI jboolean j_return_new_document, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); bson::BsonDocument update(JniBsonProtocol::parse_checked(env, j_update, Bson::Type::Document, "BSON update must be a Document")); @@ -304,7 +302,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndReplace(JNI case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_AND_REPLACE_WITH_OPTIONS: { bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { + MongoCollection::FindOneAndModifyOptions options = { projection, sort, to_bool(j_upsert), @@ -332,7 +330,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndDelete(JNIE jboolean j_return_new_document, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); @@ -343,7 +341,7 @@ Java_io_realm_internal_objectstore_OsMongoCollection_nativeFindOneAndDelete(JNIE case io_realm_internal_objectstore_OsMongoCollection_FIND_ONE_AND_DELETE_WITH_OPTIONS: { bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOneAndModifyOptions options = { + MongoCollection::FindOneAndModifyOptions options = { projection, sort, to_bool(j_upsert), diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp index d71c3697b5..12280b16e9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp @@ -34,7 +34,7 @@ using namespace realm::jni_util; using namespace realm::_impl; static void finalize_database(jlong ptr) { - delete reinterpret_cast(ptr); + delete reinterpret_cast(ptr); } JNIEXPORT jlong JNICALL @@ -48,10 +48,10 @@ Java_io_realm_internal_objectstore_OsMongoDatabase_nativeGetCollection(JNIEnv* e jlong j_database_ptr, jstring j_collection_name) { try { - RemoteMongoDatabase* database = reinterpret_cast(j_database_ptr); + auto database = reinterpret_cast(j_database_ptr); JStringAccessor name(env, j_collection_name); - RemoteMongoCollection collection(database->collection(name)); - return reinterpret_cast(new RemoteMongoCollection(std::move(collection))); + MongoCollection collection(database->collection(name)); + return reinterpret_cast(new MongoCollection(std::move(collection))); } CATCH_STD() return reinterpret_cast(nullptr); diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp index 24efc1349a..fe2d6370de 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp @@ -45,7 +45,7 @@ Java_io_realm_mongodb_mongo_iterable_AggregateIterable_nativeAggregate(JNIEnv* e jstring j_pipeline, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); BsonArray bson_array(JniBsonProtocol::parse_checked(env, j_pipeline, Bson::Type::Array, "BSON pipeline must be a BsonArray")); diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp index b034dd26fb..ed911a20ea 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp @@ -48,7 +48,7 @@ Java_io_realm_mongodb_mongo_iterable_FindIterable_nativeFind(JNIEnv *env, jlong j_limit, jobject j_callback) { try { - auto collection = reinterpret_cast(j_collection_ptr); + auto collection = reinterpret_cast(j_collection_ptr); bson::BsonDocument filter(JniBsonProtocol::parse_checked(env, j_filter, Bson::Type::Document, "BSON filter must be a Document")); @@ -60,7 +60,7 @@ Java_io_realm_mongodb_mongo_iterable_FindIterable_nativeFind(JNIEnv *env, uint64_t limit = std::uint64_t(j_limit); bson::BsonDocument projection(JniBsonProtocol::parse_checked(env, j_projection, Bson::Type::Document, "BSON projection must be a Document")); bson::BsonDocument sort(JniBsonProtocol::parse_checked(env, j_sort, Bson::Type::Document, "BSON sort must be a Document")); - RemoteMongoCollection::RemoteFindOptions options = { + MongoCollection::FindOptions options = { limit, projection, sort diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index ef6736cc07..6b44209e6f 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit ef6736cc07a8b94d1242c522969114bb8047deef +Subproject commit 6b44209e6fcac0137e193c96444f93c50d184d06 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java index 9ac0e1903a..775f4a1126 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoClient.java @@ -31,10 +31,10 @@ public class OsMongoClient implements NativeObject { private final String serviceName; private final StreamNetworkTransport streamNetworkTransport; - public OsMongoClient(final OsApp osApp, + public OsMongoClient(final OsSyncUser osSyncUser, final String serviceName, final StreamNetworkTransport streamNetworkTransport) { - this.nativePtr = nativeCreate(osApp.getNativePtr(), serviceName); + this.nativePtr = nativeCreate(osSyncUser.getNativePtr(), serviceName); this.serviceName = serviceName; this.streamNetworkTransport = streamNetworkTransport; } @@ -59,7 +59,7 @@ public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } - private static native long nativeCreate(long nativeAppPtr, String serviceName); + private static native long nativeCreate(long nativeUserPtr, String serviceName); private static native long nativeCreateDatabase(long nativeAppPtr, String databaseName); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java index 0fb72f116d..89b34c2481 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsMongoCollection.java @@ -289,8 +289,8 @@ public InsertOneResult insertOne(final DocumentT document) { OsJNIResultCallback callback = new OsJNIResultCallback(success, error) { @Override protected InsertOneResult mapSuccess(Object result) { - BsonValue bsonObjectId = new BsonObjectId((ObjectId) result); - return new InsertOneResult(bsonObjectId); + BsonValue id = JniBsonProtocol.decode((String) result, BsonValue.class, codecRegistry); + return new InsertOneResult(id); } }; @@ -308,9 +308,8 @@ protected InsertManyResult mapSuccess(Object result) { Object[] objects = (Object[]) result; Map insertedIdsMap = new HashMap<>(); for (int i = 0; i < objects.length; i++) { - ObjectId objectId = (ObjectId) objects[i]; - BsonValue bsonObjectId = new BsonObjectId(objectId); - insertedIdsMap.put((long) i, bsonObjectId); + BsonValue id = JniBsonProtocol.decode((String) objects[i], BsonValue.class, codecRegistry); + insertedIdsMap.put((long) i, id); } return new InsertManyResult(insertedIdsMap); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java index afd4620ffe..cb811332de 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/User.java @@ -487,8 +487,7 @@ public synchronized MongoClient getMongoClient(String serviceName) { Util.checkEmpty(serviceName, "serviceName"); if (mongoClient == null) { StreamNetworkTransport streamNetworkTransport = new StreamNetworkTransport(app.osApp, this.osUser); - - OsMongoClient osMongoClient = new OsMongoClient(app.osApp, serviceName, streamNetworkTransport); + OsMongoClient osMongoClient = new OsMongoClient(osUser, serviceName, streamNetworkTransport); mongoClient = new MongoClientImpl(osMongoClient, app.getConfiguration().getDefaultCodecRegistry()); } return mongoClient; diff --git a/tools/sync_test_server/app_config/auth_providers/anon-user.json b/tools/sync_test_server/app_config/auth_providers/anon-user.json index 59a7a97ea1..c3a0fa7961 100644 --- a/tools/sync_test_server/app_config/auth_providers/anon-user.json +++ b/tools/sync_test_server/app_config/auth_providers/anon-user.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e8691d", + "id": "5f8026eb41a54eac4113072c", "name": "anon-user", "type": "anon-user", "disabled": false diff --git a/tools/sync_test_server/app_config/auth_providers/api-key.json b/tools/sync_test_server/app_config/auth_providers/api-key.json index 3c08c353c9..ae5629618a 100644 --- a/tools/sync_test_server/app_config/auth_providers/api-key.json +++ b/tools/sync_test_server/app_config/auth_providers/api-key.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e8691e", + "id": "5f8026eb41a54eac4113072d", "name": "api-key", "type": "api-key", "disabled": false diff --git a/tools/sync_test_server/app_config/auth_providers/custom-function.json b/tools/sync_test_server/app_config/auth_providers/custom-function.json index f743d3b68b..e0d762d762 100644 --- a/tools/sync_test_server/app_config/auth_providers/custom-function.json +++ b/tools/sync_test_server/app_config/auth_providers/custom-function.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e8691f", + "id": "5f8026eb41a54eac4113072e", "name": "custom-function", "type": "custom-function", "config": { diff --git a/tools/sync_test_server/app_config/auth_providers/local-userpass.json b/tools/sync_test_server/app_config/auth_providers/local-userpass.json index b509b623d2..cf8a6c2ded 100644 --- a/tools/sync_test_server/app_config/auth_providers/local-userpass.json +++ b/tools/sync_test_server/app_config/auth_providers/local-userpass.json @@ -1,14 +1,14 @@ { - "id": "5f47673420f7388c73e86920", + "id": "5f8026eb41a54eac4113072f", "name": "local-userpass", "type": "local-userpass", "config": { "autoConfirm": true, + "confirmationFunctionName": "confirmFunc", "emailConfirmationUrl": "http://realm.io/confirm-user", "resetFunctionName": "resetFunc", "resetPasswordSubject": "Reset Password", "resetPasswordUrl": "http://realm.io/reset-password", - "confirmationFunctionName": "confirmFunc", "runConfirmationFunction": false, "runResetFunction": false }, diff --git a/tools/sync_test_server/app_config/functions/authFunc/config.json b/tools/sync_test_server/app_config/functions/authFunc/config.json index 5de1d4c1bb..2b62343956 100644 --- a/tools/sync_test_server/app_config/functions/authFunc/config.json +++ b/tools/sync_test_server/app_config/functions/authFunc/config.json @@ -1,6 +1,6 @@ { "can_evaluate": {}, - "id": "5f47673420f7388c73e86913", + "id": "5f8026eb41a54eac41130722", "name": "authFunc", "private": false } diff --git a/tools/sync_test_server/app_config/functions/authorizedOnly/config.json b/tools/sync_test_server/app_config/functions/authorizedOnly/config.json index f4c2297eb6..203b3313f9 100644 --- a/tools/sync_test_server/app_config/functions/authorizedOnly/config.json +++ b/tools/sync_test_server/app_config/functions/authorizedOnly/config.json @@ -6,7 +6,7 @@ ] } }, - "id": "5f47673420f7388c73e86914", + "id": "5f8026eb41a54eac41130723", "name": "authorizedOnly", "private": false } diff --git a/tools/sync_test_server/app_config/functions/confirmFunc/config.json b/tools/sync_test_server/app_config/functions/confirmFunc/config.json index 53737402fb..5e1788fd07 100644 --- a/tools/sync_test_server/app_config/functions/confirmFunc/config.json +++ b/tools/sync_test_server/app_config/functions/confirmFunc/config.json @@ -1,6 +1,6 @@ { "can_evaluate": {}, - "id": "5f47673420f7388c73e86915", + "id": "5f8026eb41a54eac41130724", "name": "confirmFunc", "private": false } diff --git a/tools/sync_test_server/app_config/functions/error/config.json b/tools/sync_test_server/app_config/functions/error/config.json index 712d3db403..98efb4d232 100644 --- a/tools/sync_test_server/app_config/functions/error/config.json +++ b/tools/sync_test_server/app_config/functions/error/config.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e86916", + "id": "5f8026eb41a54eac41130725", "name": "error", "private": false } diff --git a/tools/sync_test_server/app_config/functions/firstArg/config.json b/tools/sync_test_server/app_config/functions/firstArg/config.json index b2474dd471..29c6ad4260 100644 --- a/tools/sync_test_server/app_config/functions/firstArg/config.json +++ b/tools/sync_test_server/app_config/functions/firstArg/config.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e86917", + "id": "5f8026eb41a54eac41130726", "name": "firstArg", "private": false } diff --git a/tools/sync_test_server/app_config/functions/null/config.json b/tools/sync_test_server/app_config/functions/null/config.json index 27510ca766..9c702eae69 100644 --- a/tools/sync_test_server/app_config/functions/null/config.json +++ b/tools/sync_test_server/app_config/functions/null/config.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e86918", + "id": "5f8026eb41a54eac41130727", "name": "null", "private": false } diff --git a/tools/sync_test_server/app_config/functions/resetFunc/config.json b/tools/sync_test_server/app_config/functions/resetFunc/config.json index 30b195e912..7f4106ad68 100644 --- a/tools/sync_test_server/app_config/functions/resetFunc/config.json +++ b/tools/sync_test_server/app_config/functions/resetFunc/config.json @@ -1,6 +1,6 @@ { "can_evaluate": {}, - "id": "5f47673420f7388c73e86919", + "id": "5f8026eb41a54eac41130728", "name": "resetFunc", "private": false } diff --git a/tools/sync_test_server/app_config/functions/sum/config.json b/tools/sync_test_server/app_config/functions/sum/config.json index 705b359ca7..88ce54395c 100644 --- a/tools/sync_test_server/app_config/functions/sum/config.json +++ b/tools/sync_test_server/app_config/functions/sum/config.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e8691a", + "id": "5f8026eb41a54eac41130729", "name": "sum", "private": false } diff --git a/tools/sync_test_server/app_config/functions/testAuthFunc/config.json b/tools/sync_test_server/app_config/functions/testAuthFunc/config.json index 3be44599de..7e0d7ac676 100644 --- a/tools/sync_test_server/app_config/functions/testAuthFunc/config.json +++ b/tools/sync_test_server/app_config/functions/testAuthFunc/config.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e8691b", + "id": "5f8026eb41a54eac4113072a", "name": "testAuthFunc", "private": false } diff --git a/tools/sync_test_server/app_config/functions/void/config.json b/tools/sync_test_server/app_config/functions/void/config.json index af2fc71676..a4b451ca7a 100644 --- a/tools/sync_test_server/app_config/functions/void/config.json +++ b/tools/sync_test_server/app_config/functions/void/config.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e8691c", + "id": "5f8026eb41a54eac4113072b", "name": "void", "private": false } diff --git a/tools/sync_test_server/app_config/services/BackingDB/config.json b/tools/sync_test_server/app_config/services/BackingDB/config.json index 96f12b5947..48edb290b7 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/config.json +++ b/tools/sync_test_server/app_config/services/BackingDB/config.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e8690d", + "id": "5f8026eb41a54eac4113071c", "name": "BackingDB", "type": "mongodb", "config": { diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json index 554099bb54..c1f39a7fb3 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncDog.json @@ -1,7 +1,7 @@ { "collection": "SyncDog", "database": "test_data", - "id": "5f47673420f7388c73e8690e", + "id": "5f8026eb41a54eac4113071d", "roles": [ { "name": "default", diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json index 0d24d238d3..d90ba02dea 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.SyncPerson.json @@ -1,7 +1,7 @@ { "collection": "SyncPerson", "database": "test_data", - "id": "5f47673420f7388c73e8690f", + "id": "5f8026eb41a54eac4113071e", "relationships": { "dogs": { "ref": "#/relationship/BackingDB/test_data/SyncDog", diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json index 093809a7fd..a3aa7044d3 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.custom_user_data.json @@ -1,7 +1,7 @@ { "collection": "custom_user_data", "database": "test_data", - "id": "5f47673420f7388c73e86910", + "id": "5f8026eb41a54eac4113071f", "roles": [ { "name": "default", diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json index 88c1a3dfea..7ccb913f5c 100644 --- a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data.json @@ -1,7 +1,7 @@ { "collection": "mongo_data", "database": "test_data", - "id": "5f47673420f7388c73e86911", + "id": "5f8026eb41a54eac41130720", "roles": [ { "name": "default", diff --git a/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data_alt.json b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data_alt.json new file mode 100644 index 0000000000..181837b98f --- /dev/null +++ b/tools/sync_test_server/app_config/services/BackingDB/rules/test_data.mongo_data_alt.json @@ -0,0 +1,16 @@ +{ + "collection": "mongo_data_alt", + "database": "test_data", + "id": "5f80274e41a54eac41130879", + "roles": [ + { + "name": "default", + "apply_when": {}, + "insert": true, + "delete": true, + "search": true, + "additional_fields": {} + } + ], + "schema": {} +} diff --git a/tools/sync_test_server/app_config/services/gcm/config.json b/tools/sync_test_server/app_config/services/gcm/config.json index 264021375c..ac9e491347 100644 --- a/tools/sync_test_server/app_config/services/gcm/config.json +++ b/tools/sync_test_server/app_config/services/gcm/config.json @@ -1,5 +1,5 @@ { - "id": "5f47673420f7388c73e86912", + "id": "5f8026eb41a54eac41130721", "name": "gcm", "type": "gcm", "config": { From 4264bfa7ae1f50393af8f5bc21d35b7486dbaec8 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 12 Oct 2020 21:25:54 +0200 Subject: [PATCH 1718/2110] Release 10.0.0-RC.2 --- CHANGELOG.md | 4 ++-- version.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b48a64b23..363d9ade22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.0-RC.2 (YYYY-MM-DD) +## 10.0.0-RC.2 (2020-10-12) ### Breaking Changes * None. @@ -16,7 +16,7 @@ * Realm Studio 10.0.0 and above is required to open Realms created by this version. ### Internal -* Updated to Object Store commit: da02579eea7b36eb84d2c9f88988b7f32c83eb83. +* Updated to Object Store commit: 6b44209e6fcac0137e193c96444f93c50d184d06. ## 10.0.0-RC.1 (2020-10-02) diff --git a/version.txt b/version.txt index 2852352017..f75d1764c1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-RC.2-SNAPSHOT \ No newline at end of file +10.0.0-RC.2 \ No newline at end of file From 8a2bda6e9c623978d5d3d1a4d95bbb88d0568ca6 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Oct 2020 09:17:21 +0200 Subject: [PATCH 1719/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f75d1764c1..c41e0a2c8c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-RC.2 \ No newline at end of file +10.0.0-RC.3-SNAPSHOT \ No newline at end of file From a7326766ba57a64635e6e442f501840e0875534c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Oct 2020 09:41:04 +0200 Subject: [PATCH 1720/2110] Rename CI docker cache --- Jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c66a5dd577..12a105ead1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -99,10 +99,9 @@ try { def buildEnv = null stage('Prepare Docker Images') { - // TODO Should be renamed to 'master' when merged there. // TODO Caching is currently disabled (with -do-not-cache suffix) due to the upload speed // in Copenhagen being too slow. So the upload times out. - buildEnv = buildDockerEnv("ci/realm-java:v10", push: currentBranch == 'v10-do-not-cache') + buildEnv = buildDockerEnv("ci/realm-java:master", push: currentBranch == 'master-do-not-cache') def props = readProperties file: 'dependencies.list' echo "Version in dependencies.list: ${props.MONGODB_REALM_SERVER}" def mdbRealmImage = docker.image("docker.pkg.github.com/realm/ci/mongodb-realm-test-server:${props.MONGODB_REALM_SERVER}") From 255857a9237eb29bd6e77fd121e4d4ec56e6d452 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Oct 2020 10:49:07 +0200 Subject: [PATCH 1721/2110] Fix AndroidX import --- .../src/androidTest/java/io/realm/UTFStringsTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/UTFStringsTests.java b/realm/realm-library/src/androidTest/java/io/realm/UTFStringsTests.java index f48003f240..86eb5354f4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/UTFStringsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/UTFStringsTests.java @@ -15,7 +15,7 @@ */ package io.realm; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; From 73eff89345367c7ce60a64ebe8d05824776bf71f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Oct 2020 11:52:26 +0200 Subject: [PATCH 1722/2110] Fix test --- .../src/androidTest/java/io/realm/RealmMigrationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index f18238e906..bec7e59806 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -1447,7 +1447,7 @@ public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException .build()); assertFalse(realm.isEmpty()); // Upgrading to Core 6 will strip all indexes on primary keys as they are no longer needed. - assertTrue(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); + assertFalse(realm.getSchema().get("MigrationCore6PKStringIndexedByDefault").hasIndex("name")); MigrationCore6PKStringIndexedByDefault first = realm.where(MigrationCore6PKStringIndexedByDefault.class).findFirst(); assertNotNull(first); assertEquals("Foo", first.name); From 6d7a200d3c0ae24c4ccb5cb2b3e6b74105661788 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Oct 2020 14:47:01 +0200 Subject: [PATCH 1723/2110] Fix nullable tests --- .../io/realm/DynamicRealmObjectTests.java | 15 +++++++++++++ .../entities/NullablePrimitiveFields.java | 22 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index a916cfc061..11e68345dc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -1755,6 +1755,9 @@ public void getNullableFields() { allJavaTypes.getFieldStringList().add(null); allJavaTypes.getFieldBinaryList().add(null); allJavaTypes.getFieldDateList().add(null); + allJavaTypes.getFieldObjectIdList().add(null); + allJavaTypes.getFieldDecimal128List().add(null); + }); realm.close(); dynamicRealm.refresh(); @@ -1788,6 +1791,12 @@ public void getNullableFields() { case OBJECT: assertNull(allJavaTypes.get(AllJavaTypes.FIELD_OBJECT)); break; + case DECIMAL128: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_DECIMAL128)); + break; + case OBJECT_ID: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_OBJECT_ID)); + break; case INTEGER_LIST: assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_INTEGER_LIST, Integer.class).get(0)); break; @@ -1809,6 +1818,12 @@ public void getNullableFields() { case DOUBLE_LIST: assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_DOUBLE_LIST, Double.class).get(0)); break; + case DECIMAL128_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_DECIMAL128_LIST, Decimal128.class).get(0)); + break; + case OBJECT_ID_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_OBJECT_ID_LIST, ObjectId.class).get(0)); + break; case LIST: case LINKING_OBJECTS: // Realm lists and back links cannot be null diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java b/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java index 197ec22d12..85b52fc8b9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java @@ -16,6 +16,9 @@ package io.realm.entities; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Date; import io.realm.RealmObject; @@ -31,6 +34,8 @@ public class NullablePrimitiveFields extends RealmObject { public static final String FIELD_BOOLEAN = "fieldBoolean"; public static final String FIELD_DATE = "fieldDate"; public static final String FIELD_BINARY = "fieldBinary"; + public static final String FIELD_OBJECT_ID = "fieldObjectId"; + public static final String FIELD_DECIMAL128 = "fieldDecimal128"; private Boolean fieldBoolean; private Integer fieldInt; @@ -39,6 +44,8 @@ public class NullablePrimitiveFields extends RealmObject { private String fieldString; private Byte fieldBinary; private Date fieldDate; + private ObjectId fieldObjectId; + private Decimal128 fieldDecimal128; public Integer getFieldInt() { return fieldInt; @@ -96,4 +103,19 @@ public void setFieldBinary(Byte fieldBinary) { this.fieldBinary = fieldBinary; } + public ObjectId getFieldObjectId() { + return fieldObjectId; + } + + public void setFieldObjectId(ObjectId fieldObjectId) { + this.fieldObjectId = fieldObjectId; + } + + public Decimal128 getFieldDecimal128() { + return fieldDecimal128; + } + + public void setFieldDecimal128(Decimal128 fieldDecimal128) { + this.fieldDecimal128 = fieldDecimal128; + } } From 0fc151bc4b28369b64852a9841c5b697be0d4d6e Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 14 Oct 2020 13:21:23 +0200 Subject: [PATCH 1724/2110] Prepare 10.0.0 release notes and core versions (#7154) --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ dependencies.list | 4 ++-- version.txt | 2 +- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c62707512f..1776f5f0cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +## 10.0.0 (YYYY-MM-DD) + +NOTE: This is a unified release note covering all v10.0.0-BETA.X v10.0.0-RC.X releases. + +NOTE: Support for syncing with realm.cloud.io and/or Realm Object Server has been replaced with support for syncing with MongoDB Realm Cloud. + +NOTE: This version upgrades the Realm file format to version 20. It is not possible to downgrade to earlier versions than v10.0.0-BETA.7. Non-sync Realms will be upgraded automatically. Synced Realms can only be automatically upgraded if created with Realm Java v10.0.0-BETA.1 and above. + +### Breaking Changes +* [RealmApp] Most APIs for interacting with Realm Cloud have changed significantly. All new APIs can be found in the `io.realm.mongodb` package. The entry point is through the `App` class from which you can create and login users and otherwise interact with MongoDB Realm. See [the docs](https://docs.mongodb.com/realm/android/) for further details. Synced Realms still use a `SyncConfiguration` that are largely created the same way. +* [RealmApp] Client Resets are now handled through a custom `SyncConfiguration.Builder.clientResetHandler()` instead of through the default session error handler `SyncConfiguration.Builder.errorHandler()` +* [RealmApp] Realm files have changed location on disk. They are now located in `getFiles()/mongodb-realm`. +* [RealmApp] All synced model classes not marked as embedded are required to have a primary key named `_id`. It is possible to use `@RealmField(name = "_id")` to map from any Java or Kotlin property. +* From now on it is by default not allowed to run transactions with either `Realm.executeTransaction()` or `DynamicRealm.executeTransaction()` from the UI thread. Doing so will yield a `RealmException`. Users can override this behavior by using `RealmConfiguration.Builder.allowWritesOnUiThread(true)` when building a `RealmConfiguration` to obtain a Realm or DynamicRealm instance, however, we do not recommend doing so. Instead, we recommend using `executeTransactionAsync()` or, alternatively, using non-UI threads when calling `executeTransaction()` for both `Realm`s and `DynamicRealm`s. + +### Enhancements +* Users can now opt out from allowing queries to be launched from the UI thread by using `RealmConfiguration.Builder.allowQueriesOnUiThread(false)`. A `RealmException` will be thrown when calling `RealmQuery.findAll()`, `RealmQuery.findFirst()`, `RealmQuery.minimumDate()`, `RealmQuery.maximumDate()`, `RealmQuery.count()`, `RealmQuery.sum()`, `RealmQuery.max()`, `RealmQuery.min()`, `RealmQuery.average()` and `RealmQuery.averageDecimal128()` from the UI thread after having used `allowQueriesOnUiThread(false)`. Queries will be allowed from the thread from which the Realm instance was obtained as it always has been by default, although we recommend using `RealmQuery.findAllAsync()` or `RealmQuery.findFirstAsync()`, or, alternatively, using a non-UI thread to launch them. +* `BaseRealm.refresh()` will throw a `RealmException` if it is being called from the UI thread if `allowQueriesOnUiThread` is set to `false`, though it will be allowed by default. +* Added `DynamicRealm.executeTransactionAsync()`. +* Added Kotlin extension suspend function `Realm.executeTransactionAwait()` which runs transactions inside coroutines. +* Added Kotlin extension function `RealmResults.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. +* Added Kotlin extension function `RealmList.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. +* Added Kotlin extension function `RealmModel.toFlow()` which returns a Kotlin flow, similar to our RxJava convenience method `asFlowable()`. +* RealmLists can now be marked final. (Issue [#6892](https://github.com/realm/realm-java/issues/6892)) +* Added support for `distinct` queries on non-index and linked fields. (Issue [#1906](https://github.com/realm/realm-java/issues/1906)) +* Added support for `org.bson.types.Decimal128` and `org.bson.types.ObjectId` as supported fields in model classes. +* Added support for `org.bson.types.ObjectId` as a primary key. +* Added support for "Embedded Objects". They are enabled using `@RealmClass(embedded = true)`. An embedded object must have exactly one parent object linking to it and it will be deleted when the parent is. Embedded objects can also be the parent of other embedded classes. Read more [here](https://docs.mongodb.com/realm/android/embedded-objects/). (Issue [#6713](https://github.com/realm/realm-java/issues/6713)) + + +### Fixes +* None. + +### Compatibility +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 or above is required to open Realms created by this version. + +### Internal +* Updated to Realm Sync: 10.0.0. +* Updated to Realm Core: 10.0.0. + + ## 10.0.0-RC.2 (2020-10-12) ### Enhancements diff --git a/dependencies.list b/dependencies.list index 8b533ad6f6..3c769a19da 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC=10.0.0-beta.14 -REALM_SYNC_SHA256=2f0d2bbbaa42c8644487c3fcaeff0aab8b439e6170203c776892665f59f779e2 +REALM_SYNC=10.0.0 +REALM_SYNC_SHA256=e0cf26042beb9909658e40fe486ac3ac8d023989f9c7a40440d00a34132e14be # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions diff --git a/version.txt b/version.txt index c41e0a2c8c..f702ec2902 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-RC.3-SNAPSHOT \ No newline at end of file +10.0.0-SNAPSHOT From b94d1755436ec1896ae36d78dec08332708f6b45 Mon Sep 17 00:00:00 2001 From: clementetb Date: Thu, 15 Oct 2020 11:36:47 +0200 Subject: [PATCH 1725/2110] Bump the object-store version (#7156) --- realm/realm-library/src/main/cpp/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 6b44209e6f..d0ac41bee0 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 6b44209e6fcac0137e193c96444f93c50d184d06 +Subproject commit d0ac41bee0ccab83eb887b9fff8f417875ecf771 From 7fbc4d405b45e63ee6a9497682bc67eaf11b169e Mon Sep 17 00:00:00 2001 From: Clemente Tort Date: Thu, 15 Oct 2020 11:40:05 +0200 Subject: [PATCH 1726/2110] Release 10.0.0 --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1776f5f0cf..a0682feaae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.0 (YYYY-MM-DD) +## 10.0.0 (2020-10-15) NOTE: This is a unified release note covering all v10.0.0-BETA.X v10.0.0-RC.X releases. diff --git a/version.txt b/version.txt index f702ec2902..a13e7b9c87 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0-SNAPSHOT +10.0.0 From 02ee7265848dc1c7ea94446c4416bbc706fa9e36 Mon Sep 17 00:00:00 2001 From: Clemente Tort Date: Thu, 15 Oct 2020 13:28:01 +0200 Subject: [PATCH 1727/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a13e7b9c87..e988cdd41c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.0 +10.0.1-SNAPSHOT From 973d43725d3bc2a8bd053892ddab0ff2856daa12 Mon Sep 17 00:00:00 2001 From: Clemente Tort Date: Thu, 15 Oct 2020 13:57:14 +0200 Subject: [PATCH 1728/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e988cdd41c..bb2a9abc36 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.1-SNAPSHOT +10.1.0-SNAPSHOT From 2647c0a61f349abe9a6f113f963ebf761c4aa599 Mon Sep 17 00:00:00 2001 From: clementetb Date: Thu, 22 Oct 2020 11:34:51 +0200 Subject: [PATCH 1729/2110] Keep constructors for Decimal128 and ObjectId (#7159) --- CHANGELOG.md | 18 ++++++++++++++++++ .../proguard-rules-consumer-common.pro | 8 ++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0682feaae..d9a061e835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## 10.0.1 (YYYY-MM-DD) + +### Breaking Changes +* None. + +### Enhancements +* None. + +### Fixes +* Crash with `Assertion failed: m_method_id != nullptr with (method_name, signature) = ["", "(Ljava/lang/String;)V"]` when `Minify` is enabled. + +### Compatibility +* None. + +### Internal +* None. + + ## 10.0.0 (2020-10-15) NOTE: This is a unified release note covering all v10.0.0-BETA.X v10.0.0-RC.X releases. diff --git a/realm/realm-library/proguard-rules-consumer-common.pro b/realm/realm-library/proguard-rules-consumer-common.pro index 7b7b891dc2..65b088ede9 100644 --- a/realm/realm-library/proguard-rules-consumer-common.pro +++ b/realm/realm-library/proguard-rules-consumer-common.pro @@ -22,5 +22,9 @@ -dontnote rx.Observable # Referenced from JNI --keep class org.bson.types.Decimal128 --keep class org.bson.types.ObjectId \ No newline at end of file +-keep class org.bson.types.Decimal128 { + public static org.bson.types.Decimal128 fromIEEE754BIDEncoding(...); +} +-keep class org.bson.types.ObjectId { + (...); +} From 0a5720baf2aa2b2e3f1348cb975b3343faa6b41f Mon Sep 17 00:00:00 2001 From: clementetb Date: Thu, 22 Oct 2020 11:51:39 +0200 Subject: [PATCH 1730/2110] Add missing tests for primitive lists, ObjectId and Decimal128 (#7160) --- .../io/realm/LinkingObjectsDynamicTests.java | 60 ++++------ .../androidTest/java/io/realm/QueryTests.java | 11 -- .../java/io/realm/RealmQueryTests.java | 54 +++++++++ .../java/io/realm/internal/JNIQueryTest.java | 106 ++++++++++++++++-- .../java/io/realm/internal/JNIRowTest.java | 31 ++++- .../io/realm/internal/JNITableInsertTest.java | 4 + .../java/io/realm/internal/JNITableTest.java | 31 ++++- .../realm/internal/QueryDescriptorTests.java | 11 +- .../src/main/cpp/io_realm_internal_Table.cpp | 16 +++ .../main/java/io/realm/internal/Table.java | 13 +++ .../testUtils/java/io/realm/TestHelper.java | 22 ++++ 11 files changed, 281 insertions(+), 78 deletions(-) diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index 7e3bbdef1f..a622566d2a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -178,7 +178,7 @@ public void linkingObjects_invalidFieldType() { case OBJECT: // fall-through case LIST: continue; - // skip special case + // skip special case case LINKING_OBJECTS: continue; case INTEGER: @@ -209,50 +209,32 @@ public void linkingObjects_invalidFieldType() { object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_OBJECT_ID); break; case INTEGER_LIST: - // FIXME zaki50 enable this once Primitive List is implemented - //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_INT_LIST); - //break; - throw new IllegalArgumentException("Unexpected field type"); + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_INTEGER_LIST); + break; case BOOLEAN_LIST: - // FIXME zaki50 enable this once Primitive List is implemented - //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_BOOLEAN_LIST); - //break; - throw new IllegalArgumentException("Unexpected field type"); + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_BOOLEAN_LIST); + break; case STRING_LIST: - // FIXME zaki50 enable this once Primitive List is implemented - //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_STRING_LIST); - //break; - throw new IllegalArgumentException("Unexpected field type"); + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_STRING_LIST); + break; case BINARY_LIST: - // FIXME zaki50 enable this once Primitive List is implemented - //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_BINARY_LIST); - //break; - throw new IllegalArgumentException("Unexpected field type"); + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_BINARY_LIST); + break; case DATE_LIST: - // FIXME zaki50 enable this once Primitive List is implemented - //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DATE_LIST); - //break; - throw new IllegalArgumentException("Unexpected field type"); + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DATE_LIST); + break; case FLOAT_LIST: - // FIXME zaki50 enable this once Primitive List is implemented - //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_FLOAT_LIST); - //break; - throw new IllegalArgumentException("Unexpected field type"); + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_FLOAT_LIST); + break; case DOUBLE_LIST: - // FIXME zaki50 enable this once Primitive List is implemented - //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE_LIST); - //break; - throw new IllegalArgumentException("Unexpected field type"); + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE_LIST); + break; case DECIMAL128_LIST: - // FIXME enable this once Primitive List is implemented - //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE_LIST); - //break; - throw new IllegalArgumentException("Unexpected field type"); + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DECIMAL128_LIST); + break; case OBJECT_ID_LIST: - // FIXME enable this once Primitive List is implemented - //object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_DOUBLE_LIST); - //break; - throw new IllegalArgumentException("Unexpected field type"); + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_OBJECT_ID_LIST); + break; default: fail("unknown type: " + fieldType); break; @@ -264,7 +246,7 @@ public void linkingObjects_invalidFieldType() { } // Linking Object fields are implicit and do not exist. - for (String field : new String[] {AllJavaTypes.FIELD_LO_OBJECT, AllJavaTypes.FIELD_LO_LIST}) { + for (String field : new String[]{AllJavaTypes.FIELD_LO_OBJECT, AllJavaTypes.FIELD_LO_LIST}) { try { object.linkingObjects(AllJavaTypes.CLASS_NAME, field); fail(); @@ -552,7 +534,7 @@ public void execute(DynamicRealm realm) { @Test public void dynamicQuery_invalidSyntax() { - String[] invalidBacklinks = new String[] { + String[] invalidBacklinks = new String[]{ "linkingObject(x", "linkingObject(x.y", "linkingObject(x.y)", diff --git a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java index 1f9bb65090..73875fcaa2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/QueryTests.java @@ -67,17 +67,6 @@ public abstract class QueryTests { list = new ArrayList<>(Arrays.asList(RealmFieldType.values())); list.removeAll(SUPPORTED_IS_EMPTY_TYPES); - // FIXME zaki50 revisit once we implement query for Primitive List - list.remove(RealmFieldType.STRING_LIST); - list.remove(RealmFieldType.BINARY_LIST); - list.remove(RealmFieldType.BOOLEAN_LIST); - list.remove(RealmFieldType.INTEGER_LIST); - list.remove(RealmFieldType.DOUBLE_LIST); - list.remove(RealmFieldType.FLOAT_LIST); - list.remove(RealmFieldType.DATE_LIST); - list.remove(RealmFieldType.DECIMAL128_LIST); - list.remove(RealmFieldType.OBJECT_ID_LIST); - NOT_SUPPORTED_IS_EMPTY_TYPES = Collections.unmodifiableList(list); NOT_SUPPORTED_IS_NOT_EMPTY_TYPES = Collections.unmodifiableList(list); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 945cc30d8c..7e21375230 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -2828,6 +2828,33 @@ public void isEmpty_illegalFieldTypeThrows() { case OBJECT_ID: realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT_ID).findAll(); break; + case INTEGER_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_INTEGER_LIST).findAll(); + break; + case BOOLEAN_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_BOOLEAN_LIST).findAll(); + break; + case STRING_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_STRING_LIST).findAll(); + break; + case BINARY_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_BINARY_LIST).findAll(); + break; + case DATE_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_DATE_LIST).findAll(); + break; + case FLOAT_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_FLOAT_LIST).findAll(); + break; + case DOUBLE_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_DOUBLE_LIST).findAll(); + break; + case DECIMAL128_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_DECIMAL128_LIST).findAll(); + break; + case OBJECT_ID_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT_ID_LIST).findAll(); + break; default: fail("Unknown type: " + type); } @@ -2947,6 +2974,33 @@ public void isNotEmpty_illegalFieldTypeThrows() { case OBJECT_ID: realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_OBJECT_ID).findAll(); break; + case INTEGER_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_INTEGER_LIST).findAll(); + break; + case BOOLEAN_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_BOOLEAN_LIST).findAll(); + break; + case STRING_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_STRING_LIST).findAll(); + break; + case BINARY_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_BINARY_LIST).findAll(); + break; + case DATE_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_DATE_LIST).findAll(); + break; + case FLOAT_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_FLOAT_LIST).findAll(); + break; + case DOUBLE_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_DOUBLE_LIST).findAll(); + break; + case DECIMAL128_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_DECIMAL128_LIST).findAll(); + break; + case OBJECT_ID_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_OBJECT_ID_LIST).findAll(); + break; default: fail("Unknown type: " + type); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java index f3e30ae85d..258dc9e35b 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIQueryTest.java @@ -19,6 +19,8 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -171,11 +173,13 @@ public void queryTestForNoMatches() { long columnKey5 = t.getColumnKey("float"); long columnKey6 = t.getColumnKey("long"); long columnKey7 = t.getColumnKey("string"); + long columnKey8 = t.getColumnKey("decimal128"); + long columnKey9 = t.getColumnKey("object_id"); sharedRealm.beginTransaction(); - TestHelper.addRowWithValues(t, new long[]{columnKey1, columnKey2, columnKey3, columnKey4, columnKey5, columnKey6, columnKey7}, - new Object[]{new byte[]{1,2,3}, true, new Date(1384423149761L), 4.5d, 5.7f, 100, "string"}); + TestHelper.addRowWithValues(t, new long[]{columnKey1, columnKey2, columnKey3, columnKey4, columnKey5, columnKey6, columnKey7, columnKey8, columnKey9}, + new Object[]{new byte[]{1,2,3}, true, new Date(1384423149761L), 4.5d, 5.7f, 100, "string", new Decimal128(0), new ObjectId()}); sharedRealm.commitTransaction(); TableQuery q = t.where().greaterThan(new long[]{columnKey6}, oneNullTable, 1000); // No matches @@ -258,18 +262,16 @@ public void queryWithWrongDataType() { } // Compares date. - /* TODO: - for (int i = 0; i <= 8; i++) { + for (int i = 0; i <= 6; i++) { if (i != 2) { - try { query.equal(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} - try { query.lessThan(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} - try { query.lessThanOrEqual(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} - try { query.greaterThan(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} - try { query.greaterThanOrEqual(i, new Date()); fail(); } catch(IllegalArgumentException ignore) {} - try { query.between(i, new Date(), new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.equalTo(new long[]{columnKeys[i]}, oneNullTable, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThan(new long[]{columnKeys[i]}, oneNullTable, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.lessThanOrEqual(new long[]{columnKeys[i]}, oneNullTable, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThan(new long[]{columnKeys[i]}, oneNullTable, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.greaterThanOrEqual(new long[]{columnKeys[i]}, oneNullTable, new Date()); fail(); } catch(IllegalArgumentException ignore) {} + try { query.between(new long[]{columnKeys[i]}, new Date(), new Date()); fail(); } catch(IllegalArgumentException ignore) {} } } - */ } @Test @@ -443,4 +445,86 @@ public void execute(Table table) { assertEquals(3L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, binary2).count()); assertEquals(3L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, binary4).count()); } + + @Test + public void decimal128Query() throws Exception { + final Decimal128 one = new Decimal128(1); + final Decimal128 two = new Decimal128(2); + final Decimal128 three = new Decimal128(3); + + final AtomicLong columnKey = new AtomicLong(-1); + + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + columnKey.set(table.addColumn(RealmFieldType.DECIMAL128, "date")); + + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{one}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{two}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{three}); + } + }); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(2L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(0L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(1L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(2L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(3L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, one).count()); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(2L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(1L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(2L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(1L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(2L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, two).count()); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(2L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(2L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(3L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(0L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(1L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, three).count()); + } + + @Test + public void objectIdQuery() throws Exception { + final ObjectId one = new ObjectId(new Date(10)); + final ObjectId two = new ObjectId(new Date(20)); + final ObjectId three = new ObjectId(new Date(30)); + + final AtomicLong columnKey = new AtomicLong(-1); + + Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { + @Override + public void execute(Table table) { + columnKey.set(table.addColumn(RealmFieldType.OBJECT_ID, "date")); + + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{one}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{two}); + TestHelper.addRowWithValues(table, new long[]{columnKey.get()}, new Object[]{three}); + } + }); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(2L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(0L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(1L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(2L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, one).count()); + assertEquals(3L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, one).count()); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(2L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(1L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(2L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(1L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, two).count()); + assertEquals(2L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, two).count()); + + assertEquals(1L, table.where().equalTo(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(2L, table.where().notEqualTo(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(2L, table.where().lessThan(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(3L, table.where().lessThanOrEqual(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(0L, table.where().greaterThan(new long[]{columnKey.get()}, oneNullTable, three).count()); + assertEquals(1L, table.where().greaterThanOrEqual(new long[]{columnKey.get()}, oneNullTable, three).count()); + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java index 83966cc5be..105da96678 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNIRowTest.java @@ -19,6 +19,8 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -81,6 +83,8 @@ public void nonNullValues() { final AtomicLong colKey5 = new AtomicLong(-1); final AtomicLong colKey6 = new AtomicLong(-1); final AtomicLong colKey7 = new AtomicLong(-1); + final AtomicLong colKey8 = new AtomicLong(-1); + final AtomicLong colKey9 = new AtomicLong(-1); Table table = TestHelper.createTable(sharedRealm, "temp", new TestHelper.AdditionalTableSetup() { @Override @@ -92,8 +96,13 @@ public void execute(Table table) { colKey5.set(table.addColumn(RealmFieldType.BOOLEAN, "boolean")); colKey6.set(table.addColumn(RealmFieldType.DATE, "date")); colKey7.set(table.addColumn(RealmFieldType.BINARY, "binary")); + colKey8.set(table.addColumn(RealmFieldType.DECIMAL128, "decimal128")); + colKey9.set(table.addColumn(RealmFieldType.OBJECT_ID, "object_id")); - TestHelper.addRowWithValues(table, new long[]{colKey1.get(), colKey2.get(), colKey3.get(), colKey4.get(), colKey5.get(), colKey6.get(), colKey7.get()}, new Object[]{"abc", 3, (float) 1.2, 1.3, true, new Date(0), data}); + TestHelper.addRowWithValues(table, + new long[]{colKey1.get(), colKey2.get(), colKey3.get(), colKey4.get(), colKey5.get(), colKey6.get(), colKey7.get(), colKey8.get(), colKey9.get()}, + new Object[]{"abc", 3, (float) 1.2, 1.3, true, new Date(0), data, new Decimal128(1), new ObjectId(TestHelper.generateObjectIdHexString(1))} + ); } }); @@ -106,6 +115,8 @@ public void execute(Table table) { assertEquals(true, row.getBoolean(colKey5.get())); assertEquals(new Date(0), row.getDate(colKey6.get())); assertArrayEquals(data, row.getBinaryByteArray(colKey7.get())); + assertEquals(new Decimal128(1), row.getDecimal128(colKey8.get())); + assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(1)), row.getObjectId(colKey9.get())); row.setString(colKey1.get(), "a"); row.setLong(colKey2.get(), 1); @@ -113,6 +124,8 @@ public void execute(Table table) { row.setDouble(colKey4.get(), 9.9); row.setBoolean(colKey5.get(), false); row.setDate(colKey6.get(), new Date(10000)); + row.setDecimal128(colKey8.get(), new Decimal128(2)); + row.setObjectId(colKey9.get(), new ObjectId(TestHelper.generateObjectIdHexString(2))); byte[] newData = new byte[3]; row.setBinaryByteArray(colKey7.get(), newData); @@ -124,11 +137,12 @@ public void execute(Table table) { assertEquals(false, row.getBoolean(colKey5.get())); assertEquals(new Date(10000), row.getDate(colKey6.get())); assertArrayEquals(newData, row.getBinaryByteArray(colKey7.get())); + assertEquals(new Decimal128(2), row.getDecimal128(colKey8.get())); + assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(2)), row.getObjectId(colKey9.get())); } @Test public void nullValues() { - Table table = TestHelper.createTable(sharedRealm, "temp"); long colStringIndex = table.addColumn(RealmFieldType.STRING, "string", true); long colIntIndex = table.addColumn(RealmFieldType.INTEGER, "integer", true); @@ -137,6 +151,9 @@ public void nullValues() { long colBoolIndex = table.addColumn(RealmFieldType.BOOLEAN, "boolean", true); table.addColumn(RealmFieldType.DATE, "date"); table.addColumn(RealmFieldType.BINARY, "binary"); + long colDecimalIndex = table.addColumn(RealmFieldType.DECIMAL128, "decimal128", true); + long colObjectIdIndex = table.addColumn(RealmFieldType.OBJECT_ID, "object_id", true); + long rowIndex = OsObject.createRow(table); UncheckedRow row = table.getUncheckedRow(rowIndex); @@ -155,6 +172,16 @@ public void nullValues() { assertFalse(row.isNull(colBoolIndex)); row.setNull(colBoolIndex); assertTrue(row.isNull(colBoolIndex)); + + row.setDecimal128(colDecimalIndex, new Decimal128(0)); + assertFalse(row.isNull(colDecimalIndex)); + row.setNull(colDecimalIndex); + assertTrue(row.isNull(colDecimalIndex)); + + row.setObjectId(colObjectIdIndex, new ObjectId()); + assertFalse(row.isNull(colObjectIdIndex)); + row.setNull(colObjectIdIndex); + assertTrue(row.isNull(colObjectIdIndex)); } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java index c0339e1cd5..deaccb11e2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableInsertTest.java @@ -18,6 +18,8 @@ import androidx.test.platform.app.InstrumentationRegistry; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -76,6 +78,8 @@ public static Collection parameters() { value.add(4, 1234567.898d); value.add(5, new Date(645342)); value.add(6, new byte[]{1, 2, 3, 4, 5}); + value.add(7, new Decimal128(1)); + value.add(8, new ObjectId()); return Arrays.asList( new Object[]{value}, new Object[]{value} diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java index bae1190be5..0384b58e5f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/JNITableTest.java @@ -18,6 +18,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -90,9 +92,13 @@ public void findFirstNonExisting() { long colKey5 = t.getColumnKey("float"); long colKey6 = t.getColumnKey("long"); long colKey7 = t.getColumnKey("string"); + long colKey8 = t.getColumnKey("decimal128"); + long colKey9 = t.getColumnKey("object_id"); sharedRealm.beginTransaction(); - TestHelper.addRowWithValues(t, new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7}, new Object[] {new byte[] {1, 2, 3}, true, new Date(1384423149761L), 4.5D, 5.7F, 100, "string"}); + TestHelper.addRowWithValues(t, + new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7, colKey8, colKey9}, + new Object[] {new byte[] {1, 2, 3}, true, new Date(1384423149761L), 4.5D, 5.7F, 100, "string", new Decimal128(0), new ObjectId(TestHelper.generateObjectIdHexString(0))}); sharedRealm.commitTransaction(); assertEquals(-1, t.findFirstBoolean(colKey2, false)); @@ -100,6 +106,9 @@ public void findFirstNonExisting() { assertEquals(-1, t.findFirstDouble(colKey4, 1.0D)); assertEquals(-1, t.findFirstFloat(colKey5, 1.0F)); assertEquals(-1, t.findFirstLong(colKey6, 50)); + assertEquals(-1, t.findFirstString(colKey7, "anotherstring")); + assertEquals(-1, t.findFirstDecimal128(colKey8, new Decimal128(1))); + assertEquals(-1, t.findFirstObjectId(colKey9, new ObjectId(TestHelper.generateObjectIdHexString(1)))); } @Test @@ -113,11 +122,19 @@ public void findFirst() { long colKey5 = t.getColumnKey("float"); long colKey6 = t.getColumnKey("long"); long colKey7 = t.getColumnKey("string"); + long colKey8 = t.getColumnKey("decimal128"); + long colKey9 = t.getColumnKey("object_id"); + sharedRealm.beginTransaction(); for (int i = 0; i < TEST_SIZE; i++) { - TestHelper.addRowWithValues(t, new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7}, new Object[] {new byte[] {1, 2, 3}, true, new Date(i), (double) i, (float) i, i, "string " + i}); + TestHelper.addRowWithValues(t, + new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7, colKey8, colKey9}, + new Object[] {new byte[] {1, 2, 3}, true, new Date(i), (double) i, (float) i, i, "string " + i, new Decimal128(i), new ObjectId(TestHelper.generateObjectIdHexString(i))}); } - TestHelper.addRowWithValues(t, new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7}, new Object[] {new byte[] {1, 2, 3}, true, new Date(TEST_SIZE), (double) TEST_SIZE, (float) TEST_SIZE, TEST_SIZE, ""}); + TestHelper.addRowWithValues(t, + new long[]{colKey1, colKey2, colKey3, colKey4, colKey5, colKey6, colKey7, colKey8, colKey9}, + new Object[] {new byte[] {1, 2, 3}, true, new Date(TEST_SIZE), (double) TEST_SIZE, (float) TEST_SIZE, TEST_SIZE, "", new Decimal128(TEST_SIZE), new ObjectId(TestHelper.generateObjectIdHexString(TEST_SIZE))}); + sharedRealm.commitTransaction(); assertEquals(0, t.findFirstBoolean(colKey2, true)); @@ -126,6 +143,8 @@ public void findFirst() { assertEquals(i, t.findFirstDouble(colKey4, (double) i)); assertEquals(i, t.findFirstFloat(colKey5, (float) i)); assertEquals(i, t.findFirstLong(colKey6, i)); + assertEquals(i, t.findFirstDecimal128(colKey8, new Decimal128(i))); + assertEquals(i, t.findFirstObjectId(colKey9, new ObjectId(TestHelper.generateObjectIdHexString(i)))); } try { @@ -374,14 +393,16 @@ public void defaultValue_setAndGet() { Math.PI, 0L}; - RealmFieldType[] types = new RealmFieldType[]{RealmFieldType.STRING, + RealmFieldType[] types = new RealmFieldType[]{ + RealmFieldType.STRING, RealmFieldType.INTEGER, RealmFieldType.BOOLEAN, RealmFieldType.BINARY, RealmFieldType.DATE, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, - RealmFieldType.OBJECT}; + RealmFieldType.OBJECT + }; long rowKey = OsObject.createRow(table); diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java index 22c51c02b5..8221b7efe8 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/QueryDescriptorTests.java @@ -253,16 +253,7 @@ private Set getValidFieldTypes(Set filter) { for (RealmFieldType type : RealmFieldType.values()) { if (!filter.contains(type)) { switch (type) { - case LINKING_OBJECTS: // TODO: should be supported?s - case INTEGER_LIST: // FIXME zaki50 revisit this once Primitive List query is implemented - case BOOLEAN_LIST: - case STRING_LIST: - case BINARY_LIST: - case DATE_LIST: - case FLOAT_LIST: - case DOUBLE_LIST: - case DECIMAL128_LIST: - case OBJECT_ID_LIST: + case LINKING_OBJECTS: break; case LIST: case OBJECT: diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index ad93293322..6f4e168a87 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -854,6 +854,22 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstString(JNIEn return -1; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstDecimal128(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong low, jlong high) +{ + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_Decimal)) { + return -1; + } + + try { + Decimal128::Bid128 raw {static_cast(low), static_cast(high)}; + return to_jlong_or_not_found(table->find_first_decimal(ColKey(columnKey), Decimal128(raw))); + } + CATCH_STD() + return -1; +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstObjectId(JNIEnv* env, jclass, jlong nativeTableRefPtr, jlong columnKey, jstring j_value) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 4a9fa9b0a1..149316d6c6 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -107,6 +107,8 @@ public long addColumn(RealmFieldType type, String name, boolean isNullable) { case DATE: case FLOAT: case DOUBLE: + case DECIMAL128: + case OBJECT_ID: return nativeAddColumn(nativeTableRefPtr, type.getNativeValue(), name, isNullable); case INTEGER_LIST: @@ -116,6 +118,8 @@ public long addColumn(RealmFieldType type, String name, boolean isNullable) { case DATE_LIST: case FLOAT_LIST: case DOUBLE_LIST: + case DECIMAL128_LIST: + case OBJECT_ID_LIST: return nativeAddPrimitiveListColumn(nativeTableRefPtr, type.getNativeValue() - 128, name, isNullable); default: @@ -602,6 +606,13 @@ public long findFirstString(long columnKey, String value) { return nativeFindFirstString(nativeTableRefPtr, columnKey, value); } + public long findFirstDecimal128(long columnKey, Decimal128 value) { + if (value == null) { + throw new IllegalArgumentException("null is not supported"); + } + return nativeFindFirstDecimal128(nativeTableRefPtr, columnKey, value.getLow(), value.getHigh()); + } + public long findFirstObjectId(long columnKey, ObjectId value) { if (value == null) { throw new IllegalArgumentException("null is not supported"); @@ -845,6 +856,8 @@ public static String getTableNameForClass(String name) { public static native long nativeFindFirstString(long nativeTableRefPtr, long columnKey, String value); + public static native long nativeFindFirstDecimal128(long nativeTableRefPtr, long columnKey, long low, long high); + public static native long nativeFindFirstObjectId(long nativeTableRefPtr, long columnKey, String value); public static native long nativeFindFirstNull(long nativeTableRefPtr, long columnKey); diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index cbf36e3946..6d0412f2a2 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -122,6 +122,12 @@ public static RealmFieldType getColumnType(Object o) { if (o instanceof byte[]) { return RealmFieldType.BINARY; } + if (o instanceof Decimal128) { + return RealmFieldType.DECIMAL128; + } + if (o instanceof ObjectId) { + return RealmFieldType.OBJECT_ID; + } throw new IllegalArgumentException("Unsupported type"); } @@ -215,6 +221,20 @@ public static long addRowWithValues(Table table, long[] columnKeys, Object[] val table.setBinaryByteArray(columnKeys[i], rowKey, (byte[]) value, false); } break; + case DECIMAL128: + if (value == null) { + table.setNull(columnKeys[i], rowKey, false); + } else { + table.setDecimal128(columnKeys[i], rowKey, (Decimal128) value, false); + } + break; + case OBJECT_ID: + if (value == null) { + table.setNull(columnKeys[i], rowKey, false); + } else { + table.setObjectId(columnKeys[i], rowKey, (ObjectId) value, false); + } + break; default: throw new RuntimeException("Unexpected columnType: " + String.valueOf(colTypes[i])); } @@ -256,6 +276,8 @@ public static Table createTableWithAllColumnTypes(OsSharedRealm sharedRealm, t.addColumn(RealmFieldType.FLOAT, "float"); t.addColumn(RealmFieldType.INTEGER, "long"); t.addColumn(RealmFieldType.STRING, "string"); + t.addColumn(RealmFieldType.DECIMAL128, "decimal128"); + t.addColumn(RealmFieldType.OBJECT_ID, "object_id"); return t; } catch (RuntimeException e) { From 3a92d28b0b32621522ea95acbf6c4a7ff8fd3ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 2 Nov 2020 12:42:51 +0100 Subject: [PATCH 1731/2110] Updated migration example realms to new format (#7172) --- .../src/main/res/raw/default0.realm | Bin 4096 -> 4096 bytes .../src/main/res/raw/default1.realm | Bin 4096 -> 4096 bytes .../src/main/res/raw/default2.realm | Bin 4096 -> 656 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/migrationExample/src/main/res/raw/default0.realm b/examples/migrationExample/src/main/res/raw/default0.realm index fe8d65e0ab17493fefbd2a853b7e006367d48d22..d55ec9411abb2dc998ee3991105dd6bf0266cc6a 100644 GIT binary patch literal 4096 zcmeHIJ8Tg2OTi*zVTAPGYq zU8;+U6fRjZDN_ka!%`(n0cng>EL93=l=t>_C7Fnee7bPSy}6y4otb?zJNHFV%s92Q z@#)&VrIM(-81IUBTb-=m-%Ojm{$3Y~dbc^qwzEN&?~;&u9{BHAhTW&1wR`=+*V%3} z=NDk++ET)7$4YYfGWX$Clg5(!$69e+7g?UdNkwUBC<&#gD;n<@R<4iKDzTwZr0g0npo(-E^aT)U0}`$s zUjeT?syr^(2P+@c!#UK3s;OAXORhucuMdCx?audSAAR-7&)MxCe!oq=vv&h86**Mj z!YKLgA>O%sSnHSM=3IU*1@`5`j~}ONcN_K1bp4wL>-9&E<`h=wrvN_)4Z(Z(<%4|0 zUx+1@L&GY2zYeePAA2KiayXfIq39QnY5e2$@cUZa%5 z-g1P1jsqnvk9Dx`9eUVh_k$>_SBP?)!8*B&)9JwucF#QI!T62sKE$Qy+v&kF*BnFV zf9kc=8MBN!)TR!po!j9(_#j^zyUf=hd)jGkb}ui26+80&-qYvJtwDctx4qNL22EF= zPQKD-*>zV<0r;g|3Rc20h0t!+sJYKY-tvsVQ?t^QfUnI2=9^@ZxZG9lvJziKV&L)ssz~d*}y#^+^!vxRCc5*eEft zBb(XS8++on{2C5Qeae~N1DoMUsuIa-nc`bVIZ|Bu<&PEPjBA(YF=AI$^cm1o%yUeb zG=qAG1K%Uo6Aq0&5X^m#5bqn`0qFY&sv?O>&G+)J@Ook$AZ9b>@pAtEK3=tOKfJi`&4fD)fMT8SEkV){2uTUxEHe!hwbuj6%!;8ay)23mxz z2y+QK_kj-eVD}dDGz9`1xPc=zR;lry=&~RAQ$Gn#f>}@t%S%o_lAlEBg_eA3Gq>Nf~o8TS&Rh& Q3j`JjED%^A@V`LdPwLNhg8%>k literal 4096 zcmeHFu}T9$5S^V(a#11#f)K1yS}15|DKSD2B3D?X)5SzRNzRZ9VtHUARw+~Z2`N*? z&eGCPu(T3OXLjdI5I?|T7sBSfnR)y6-R@|x>kZw0vfZd7{a3WsXglwjBsmSc!{jEW z4$VB3^{Q*n`t(IKOh(71-*tR}#+D!i=29xIUSge0uR(e{`**g&Je`Zb&+T_drej9N zjYA;wyr~lTyd{SbSzP81Np}Wwc%>ocbF|64#Z*`CLU5fhzQLDe7?ZB9Uz=gv1$7ej zna>@Cww&iW`P2tj!zdX=CYF3!Ccf;MPIlR#b>6s%O|N68!1*R0y0%-f6zj0hs@hWn zwY4G`Q%KbYbqH2t_58`7h;g2Lj^WGRCBqL;{K3?R_my;=50Lqw3QtNErfRNUZScb5 zSS{vAS94z|-{0XjOht#k`{OTN_Mr+ztl}oJ0r8V#ogz+ff!ol8tL**cYJcgJLOjEP z)d2|ko_VJe;s;;eu8e2BCY0sh8} wo5^|1FY7X1=zd^P*YwY$$}IZbC*TwC3HStj0zLtsfKR|D;1lo({KEvk0if4hcK`qY diff --git a/examples/migrationExample/src/main/res/raw/default1.realm b/examples/migrationExample/src/main/res/raw/default1.realm index 777670cc4d164d09f0904d09265b5ce110d3edd7..0d43bce08733199ab33a8640d14061133b50668b 100644 GIT binary patch delta 192 zcmZorXi$&{U}FG-4J;5kMAyZMlZk<0qr?Ul4M!jV3NwOeK_CM}!}zWY49q83F0kBS zVMy3~kwt(p8zjrhz`!lRFo8jWv4PQn=>d}kGY3lo%LkSMpfxc03Wg4b2nGWN4|(UDw4}7;6Ziy5 zOFqHI#F9#5g){pKVUpdu=gc`Xb7vQeI3S5uxnA9%?+c1e0NHU=5ZoEoAn-k+rE7)C zRG~`b`7|HK#5(&V=`=EPq;*u+ii!ALd>A^;m2y)u-?zeGNl;)PVaaC=0FSCaf4_N{ zGQI@J8PJAz7)SMU-0yTRvU8sC{y{mzI{@YCqZ)XYR_hJyAheao*J;_V)>K~CQKkhA zLu+>GzNZ|MjY%IdgHK5N5{(H@R|Gi0Y#up<66zSwL-UCVRFHgg)~CKA`JZT$HuYZS zNiW;j!)JsM^MZ8rM8TNyI9kec{Z|}k^?BciTW@d+dQ_MC2ru5P3}5;d6^izlp0rPK zy|T|K&hQ)O$o+WEhV9<_q+?tlZ2H}79`djL54kNgp^Mnh^MR;{^!|jUffFEpQ}+l*o}N=)wgI=g1ffJCW15q`Z>S zC8bMB(z&FhbcsYm6;bBR-tO5B5%~jNOYFU!oi{UY=D2D=W)1DWxm;P5k_4}ZL}I5A zwcB5+wWIc76BiYqOYC#0o4JU4YOj9O?tF?4YU#NTw+To=v~Mk`eSvu#Uj_cfa@Sb+ zJcp;roehjX;9f!})E{Hnqf<6JyEro_#U8ehqhPT=<2Z!|+hgVpLK>1#mvlw9#4db) z0r6J9MMupViOu=}d+LoUZ{)F^>t2hG>g`TFYFd3hM$P?3v|BT`!25Bha@dR-yAHFT zhF{_B6LZ8E@l_~Fq9xuuuo!jq#By4Ka!~6;yHO{ajD!0!RcGw? zcOUk?HyY!%QNZ!QA#GfBDXv5wM2tD_r!SUG2*x%Y)>S7o5Vzvk_%S}Fb^N++Ui9lH zy=UymeyA6E_aSguR^_&IM30D;Q@smQvO@bprt3zvFsZ5Kldt44%j5WEOPouWH}dh* z{K~jhQtlp~r+Kc^77a6hJowj-|CR4Z-pagk`NBUK^NwT!X3mGt$GltAvie~Qt{4Bs zTT)dOXS^Sve<&}qd@zUN2j0LsUP0!!WtzDu$-WxcxG)%_s}DS4sK$2YJyVxi{frZJ zm0^!47g9o@=!vo%L8)zR>&bQL7#bDP(=Gh^d@SIYTdBOqSfq7>FTfNiv5?aRUD7U5 zeoy{4ug>0KGU#vlRRPYt7-KW@v<8S=&9djb@!8yT{yDv^i{`4cPuHh;ZR@coagER;r!h=Lwy1R`?39QoS0%nVmso@bDVc~;uO8A*UI9sZvT5< zTeVJ`^S5iB`H{CiK{ls)VEPA)kVNO`l(j2u4ecwhQG)o_X&T@ zQDIUy^>Q(t59Y_b%4Yg0P@SKx8fZ-4+>ZXq&omWZm;I_A`z_OVp7)LV!Pi6FW4?Ld z-NSvj;{gl)(0IyUC4S!@SbyXzd;WQrAFv|7=yRvp-y>Rb{TpD?FU+hbYN&F0hAG?_RyqWzxm9e9nbYk^{xJxUQm6gion$tJf)+1kpH+~qDgNeDx-xv4BADREZ y$S;3i+%6SNUlBKNa=m=APLS)TqD}-&rE@t0y70>3d|IkDKJxDLV-WCvh@T2 From 7c731c0c3dc7e8c3fff50efef914cee2f59f8fda Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Wed, 4 Nov 2020 10:23:23 +0100 Subject: [PATCH 1732/2110] Update to Sync 10.1.0. (#7181) --- CHANGELOG.md | 14 ++++++++++---- dependencies.list | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9a061e835..6d9fb9d06a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,16 +4,22 @@ * None. ### Enhancements -* None. +* Improved the error message for `NoSuchTable` errors. In some cases an outdated native reference was used,but the table was still there. In those cases an `InvalidTableRef` error is now used. ### Fixes -* Crash with `Assertion failed: m_method_id != nullptr with (method_name, signature) = ["", "(Ljava/lang/String;)V"]` when `Minify` is enabled. +* Crash with `Assertion failed: m_method_id != nullptr with (method_name, signature) = ["", "(Ljava/lang/String;)V"]` when `Minify` is enabled. (Issue [#7159](https://github.com/realm/realm-java/pull/7159), since 10.0.0) +* Fix crash in case insensitive query on indexed string columns when nothing matches (Cocoa issue [#6836](https://github.com/realm/realm-cocoa/issues/6836), since v10.0.0) +* Fix list of primitives with nullable values where `Lst::is_null(ndx)` always false even on null values, (Core issue [#3987](https://github.com/realm/realm-core/pull/3987), since v10.0.0). +* Fix queries for the size of a list of primitive nullable ints returning size + 1. (Core issue [#4016](https://github.com/realm/realm-core/pull/4016), since v10.0.0). ### Compatibility -* None. +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal -* None. +* Updated to Realm Sync: 10.1.0. +* Updated to Realm Core: 10.1.0. ## 10.0.0 (2020-10-15) diff --git a/dependencies.list b/dependencies.list index 3c769a19da..a8a2fe163e 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC=10.0.0 -REALM_SYNC_SHA256=e0cf26042beb9909658e40fe486ac3ac8d023989f9c7a40440d00a34132e14be +REALM_SYNC=10.1.0 +REALM_SYNC_SHA256=a074550f573b5b9f35d1efe84eef0145f2181f1c096a76ff559e7103091bae7e # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions From 20089d50c2a7cd84f9baa1aa639422d7c3616d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Wed, 4 Nov 2020 11:35:16 +0100 Subject: [PATCH 1733/2110] Add default allowQueriesOnUiThread to true also for SyncConfiguration (#7178) --- CHANGELOG.md | 4 +- .../mongodb/sync/SyncConfigurationTests.kt | 61 ++++++++++++++++--- .../realm/mongodb/sync/SyncConfiguration.java | 1 + 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d9fb9d06a..12b5dd1423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Improved the error message for `NoSuchTable` errors. In some cases an outdated native reference was used,but the table was still there. In those cases an `InvalidTableRef` error is now used. ### Fixes +* [SyncConfiguration] The `SyncConfiguration.Builder.allowQueriesOnUiThread` flag was wrongly initialized to `false` keeping users from running queries from the UI thread when using synced Realms. It now defaults to `true`, allowing queries to be run from the UI. (Issue [#7177](https://github.com/realm/realm-java/issues/7177), since 10.0.0) * Crash with `Assertion failed: m_method_id != nullptr with (method_name, signature) = ["", "(Ljava/lang/String;)V"]` when `Minify` is enabled. (Issue [#7159](https://github.com/realm/realm-java/pull/7159), since 10.0.0) * Fix crash in case insensitive query on indexed string columns when nothing matches (Cocoa issue [#6836](https://github.com/realm/realm-cocoa/issues/6836), since v10.0.0) * Fix list of primitives with nullable values where `Lst::is_null(ndx)` always false even on null values, (Core issue [#3987](https://github.com/realm/realm-core/pull/3987), since v10.0.0). @@ -531,9 +532,8 @@ Note: Fileformat has been bumped from 10 to 11. This means that downgrading to a ## 7.0.0 (2020-05-16) -NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Studio 3.11](https://github.com/realm/realm-studio/releases/tag/v3.11.0) or later will be able to open the new file format. NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Studio 3.11](https://github.com/realm/realm-studio/releases/tag/v3.11.0) or later will be able to open the new file format. -NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Realm Studio 4](https://github.com/realm/realm-studio/releases/tag/v4.0.0) or later will be able to open the new file format. +NOTE: This version bumps the Realm file format to version 10. Files created with previous versions of Realm will be automatically upgraded. It is not possible to downgrade to version 9 or earlier. Only [Realm Studio 4](https://github.com/realm/realm-studio/releases/tag/v4.0.0) or later will be able to open the new file format. ### Breaking Changes * [ObjectServer] Removed deprecated method `SyncConfiguration.Builder.partialRealm()`. Use `SyncConfiguration.Builder.fullSynchronization()` instead. diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index 60f878c169..d2e7e4b714 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -352,14 +352,14 @@ class SyncConfigurationTests { val user: User = createTestUser(app) val configs = listOf( - SyncConfiguration.defaultConfig(user, null as String?), - SyncConfiguration.defaultConfig(user, null as Int?), - SyncConfiguration.defaultConfig(user, null as Long?), - SyncConfiguration.defaultConfig(user, null as ObjectId?), - SyncConfiguration.Builder(user, null as String?).build(), - SyncConfiguration.Builder(user, null as Int?).build(), - SyncConfiguration.Builder(user, null as Long?).build(), - SyncConfiguration.Builder(user, null as ObjectId?).build() + SyncConfiguration.defaultConfig(user, null as String?), + SyncConfiguration.defaultConfig(user, null as Int?), + SyncConfiguration.defaultConfig(user, null as Long?), + SyncConfiguration.defaultConfig(user, null as ObjectId?), + SyncConfiguration.Builder(user, null as String?).build(), + SyncConfiguration.Builder(user, null as Int?).build(), + SyncConfiguration.Builder(user, null as Long?).build(), + SyncConfiguration.Builder(user, null as ObjectId?).build() ) configs.forEach { config -> @@ -379,4 +379,49 @@ class SyncConfigurationTests { } } + @Test + fun allowQueriesOnUiThread_defaultsToTrue() { + val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + val configuration = builder.build() + assertTrue(configuration.isAllowQueriesOnUiThread) + } + + @Test + fun allowQueriesOnUiThread_explicitFalse() { + val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + val configuration = builder.allowQueriesOnUiThread(false) + .build() + assertFalse(configuration.isAllowQueriesOnUiThread) + } + + @Test + fun allowQueriesOnUiThread_explicitTrue() { + val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + val configuration = builder.allowQueriesOnUiThread(true) + .build() + assertTrue(configuration.isAllowQueriesOnUiThread) + } + + @Test + fun allowWritesOnUiThread_defaultsToFalse() { + val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + val configuration = builder.build() + assertFalse(configuration.isAllowWritesOnUiThread) + } + + @Test + fun allowWritesOnUiThread_explicitFalse() { + val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + val configuration = builder.allowWritesOnUiThread(false) + .build() + assertFalse(configuration.isAllowWritesOnUiThread) + } + + @Test + fun allowWritesOnUiThread_explicitTrue() { + val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + val configuration = builder.allowWritesOnUiThread(true) + .build() + assertTrue(configuration.isAllowWritesOnUiThread) + } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index 8c947aff21..d489f81637 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -558,6 +558,7 @@ public Builder(User user, @Nullable Long partitionValue) { } this.errorHandler = user.getApp().getConfiguration().getDefaultErrorHandler(); this.clientResetHandler = user.getApp().getConfiguration().getDefaultClientResetHandler(); + this.allowQueriesOnUiThread = true; this.allowWritesOnUiThread = false; } From 69a044b05ee93656f0fb7e2f8a0c9e32ae4b50e2 Mon Sep 17 00:00:00 2001 From: nate contino Date: Thu, 5 Nov 2020 03:06:07 -0700 Subject: [PATCH 1734/2110] Add Decimal128 and ObjectId to Javadoc on supported types (#7183) --- realm/realm-library/src/main/java/io/realm/RealmObject.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 1b2131324a..0816521548 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -47,6 +47,8 @@ *

            • byte[]
            • *
            • String
            • *
            • Date
            • + *
            • org.bson.types.Decimal128
            • + *
            • org.bson.types.ObjectId
            • *
            • Any RealmObject subclass
            • *
            • RealmList
            • *
            From 4459b7e3134f5100c7e0250a4b33865806058874 Mon Sep 17 00:00:00 2001 From: clementetb Date: Thu, 5 Nov 2020 13:39:32 +0100 Subject: [PATCH 1735/2110] Update object store (#7182) --- CHANGELOG.md | 1 + dependencies.list | 2 +- .../java/io/realm/RealmQueryTests.java | 4 +- .../java/io/realm/entities/IndexedFields.java | 9 +- .../io/realm/internal/PrimaryKeyTests.java | 132 ++++++++++++++++++ .../kotlin/io/realm/AppTests.kt | 13 +- .../kotlin/io/realm/SchemaTests.kt | 33 +++-- .../io/realm/SyncedRealmMigrationTests.kt | 56 ++++---- .../io/realm/entities/SyncStringOnly.kt | 5 +- .../io/realm/mongodb/sync/SessionTests.kt | 57 ++++---- .../mongodb/sync/SyncConfigurationTests.kt | 24 ++-- .../io/realm/mongodb/sync/SyncedRealmTests.kt | 9 +- .../main/cpp/io_realm_internal_OsObject.cpp | 2 - .../cpp/io_realm_internal_OsSharedRealm.cpp | 5 +- ...alm_internal_objectstore_OsMongoClient.cpp | 4 +- ...internal_objectstore_OsMongoCollection.cpp | 4 +- ...m_internal_objectstore_OsMongoDatabase.cpp | 4 +- ...alm_internal_objectstore_OsWatchStream.cpp | 2 +- ...ngodb_mongo_iterable_AggregateIterable.cpp | 2 +- ...lm_mongodb_mongo_iterable_FindIterable.cpp | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- .../java/io/realm/MutableRealmSchema.java | 10 +- .../main/java/io/realm/RealmObjectSchema.java | 7 +- .../java/io/realm/internal/OsSharedRealm.java | 8 +- .../kotlin/io/realm/SyncSessionTests.kt | 3 + .../io/realm/SyncedRealmIntegrationTests.kt | 10 +- .../java/io/realm/entities/SyncByteArray.java | 14 ++ 27 files changed, 303 insertions(+), 121 deletions(-) create mode 100644 realm/realm-library/src/testUtils/java/io/realm/entities/SyncByteArray.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b5dd1423..05c00f973f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Internal * Updated to Realm Sync: 10.1.0. * Updated to Realm Core: 10.1.0. +* Updated to Object Store commit: fd246c54de7d1fee6bcbeb3609de75a4eccd5b70. ## 10.0.0 (2020-10-15) diff --git a/dependencies.list b/dependencies.list index a8a2fe163e..9b809b27df 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=a074550f573b5b9f35d1efe84eef0145f2181f1c096a76ff559e7103091bae # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER=2020-09-21 +MONGODB_REALM_SERVER=2020-10-03 # Common Android settings across projects GRADLE_BUILD_TOOLS=4.0.0 diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 7e21375230..663815a62f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -3086,8 +3086,8 @@ public void onChange(RealmResults results) { public void findAll_indexedCaseInsensitiveFields() { // Catches https://github.com/realm/realm-java/issues/4788 realm.beginTransaction(); - realm.createObject(IndexedFields.class).indexedString = "ROVER"; - realm.createObject(IndexedFields.class).indexedString = "Rover"; + realm.createObject(IndexedFields.class, new ObjectId()).indexedString = "ROVER"; + realm.createObject(IndexedFields.class, new ObjectId()).indexedString = "Rover"; realm.commitTransaction(); RealmResults results = realm.where(IndexedFields.class) diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/IndexedFields.java b/realm/realm-library/src/androidTest/java/io/realm/entities/IndexedFields.java index 85109c3c4c..e644dde117 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/IndexedFields.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/IndexedFields.java @@ -16,15 +16,22 @@ package io.realm.entities; +import org.bson.types.ObjectId; + import io.realm.RealmObject; import io.realm.annotations.Index; +import io.realm.annotations.PrimaryKey; public class IndexedFields extends RealmObject { - + public static final String CLASS_NAME = IndexedFields.class.getSimpleName(); + public static final String FIELD_PRIMARY_STRING = "_id"; public static final String FIELD_INDEXED_STRING = "indexedString"; public static final String FIELD_NON_INDEXED_STRING = "nonIndexedString"; + @PrimaryKey + public ObjectId _id = new ObjectId(); + @Index public String indexedString; public String nonIndexedString; diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index 3ef3916881..d6be5c1072 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -18,12 +18,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.bson.types.ObjectId; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Date; + import io.realm.DynamicRealm; import io.realm.DynamicRealmObject; import io.realm.FieldAttribute; @@ -34,6 +37,8 @@ import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -79,6 +84,11 @@ private Table getTableWithIntegerPrimaryKey() { return t; } + private Table getTableWithPrimaryKey(RealmFieldType fieldType, boolean isNullable) { + OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table + return sharedRealm.createTableWithPrimaryKey(Table.getTableNameForClass("TestTable"), "colName", fieldType, isNullable); + } + /** * This test surfaces a bunch of problems, most of them seem to be around caching of the schema * during a transaction @@ -161,4 +171,126 @@ public void addEmptyRowWithPrimaryKeyLong() { assertEquals(42L, row.getLong(row.getColumnKey("colName"))); sharedRealm.cancelTransaction(); } + + @Test + public void createTableWithIntegerPrimaryKeyNullable(){ + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); + sharedRealm.beginTransaction(); + + Table t = getTableWithPrimaryKey(RealmFieldType.INTEGER, true); + + UncheckedRow row = OsObject.createWithPrimaryKey(t, 42); + assertEquals(1, t.size()); + assertEquals(42, row.getLong(row.getColumnKey("colName"))); + + + row = OsObject.createWithPrimaryKey(t, null); + assertEquals(2, t.size()); + assertTrue(row.isNull(row.getColumnKey("colName"))); + + sharedRealm.cancelTransaction(); + } + + @Test + public void createTableWithStringPrimaryKeyNullable(){ + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); + sharedRealm.beginTransaction(); + + Table t = getTableWithPrimaryKey(RealmFieldType.STRING, true); + + UncheckedRow row = OsObject.createWithPrimaryKey(t, "Foo"); + assertEquals(1, t.size()); + assertEquals("Foo", row.getString(row.getColumnKey("colName"))); + + row = OsObject.createWithPrimaryKey(t, null); + assertEquals(2, t.size()); + assertTrue(row.isNull(row.getColumnKey("colName"))); + + sharedRealm.cancelTransaction(); + } + + @Test + public void createTableWithObjectIdPrimaryKeyNullable(){ + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); + sharedRealm.beginTransaction(); + + Table t = getTableWithPrimaryKey(RealmFieldType.OBJECT_ID, true); + + Date date = new Date(); + + UncheckedRow row = OsObject.createWithPrimaryKey(t, new ObjectId(date, 0)); + assertEquals(1, t.size()); + assertEquals(new ObjectId(date, 0), row.getObjectId(row.getColumnKey("colName"))); + + row = OsObject.createWithPrimaryKey(t, null); + assertEquals(2, t.size()); + assertTrue(row.isNull(row.getColumnKey("colName"))); + + sharedRealm.cancelTransaction(); + } + + @Test + public void createTableWithIntegerPrimaryKey(){ + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); + sharedRealm.beginTransaction(); + + Table t = getTableWithPrimaryKey(RealmFieldType.INTEGER, false); + + UncheckedRow row = OsObject.createWithPrimaryKey(t, 42); + assertEquals(1, t.size()); + assertEquals(42, row.getLong(row.getColumnKey("colName"))); + + try { + OsObject.createWithPrimaryKey(t, null); + fail("Non-nullable primary key created with a null value."); + } catch (IllegalArgumentException e){ + assertEquals("Illegal Argument: This field(colName) is not nullable.", e.getMessage()); + } + + sharedRealm.cancelTransaction(); + } + + @Test + public void createTableWithStringPrimaryKey(){ + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); + sharedRealm.beginTransaction(); + + Table t = getTableWithPrimaryKey(RealmFieldType.STRING, false); + + UncheckedRow row = OsObject.createWithPrimaryKey(t, "Foo"); + assertEquals(1, t.size()); + assertEquals("Foo", row.getString(row.getColumnKey("colName"))); + + try { + OsObject.createWithPrimaryKey(t, null); + fail("Non-nullable primary key created with a null value."); + } catch (IllegalArgumentException e){ + assertEquals("Illegal Argument: This field(colName) is not nullable.", e.getMessage()); + } + + sharedRealm.cancelTransaction(); + } + + @Test + public void createTableWithObjectIdPrimaryKey(){ + sharedRealm = OsSharedRealm.getInstance(config, OsSharedRealm.VersionID.LIVE); + sharedRealm.beginTransaction(); + + Table t = getTableWithPrimaryKey(RealmFieldType.OBJECT_ID, false); + + Date date = new Date(); + + UncheckedRow row = OsObject.createWithPrimaryKey(t, new ObjectId(date, 0)); + assertEquals(1, t.size()); + assertEquals(new ObjectId(date, 0), row.getObjectId(row.getColumnKey("colName"))); + + try { + OsObject.createWithPrimaryKey(t, null); + fail("Non-nullable primary key created with a null value."); + } catch (IllegalArgumentException e){ + assertEquals("Illegal Argument: This field(colName) is not nullable.", e.getMessage()); + } + + sharedRealm.cancelTransaction(); + } } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index 1961dbee67..da74111471 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -18,13 +18,12 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin -import io.realm.entities.DefaultSyncSchema +import io.realm.entities.SyncStringOnly import io.realm.exceptions.RealmFileException -import io.realm.kotlin.syncSession import io.realm.mongodb.* import io.realm.mongodb.sync.SyncConfiguration +import io.realm.mongodb.sync.testSchema import io.realm.rule.BlockingLooperThread -import org.bson.BsonString import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries import org.junit.* @@ -36,7 +35,6 @@ import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) class AppTests { - @get:Rule val configFactory = TestSyncConfigurationFactory() @@ -283,7 +281,12 @@ class AppTests { try { // Create Realm in order to create the sync metadata Realm var user = testApp.login(Credentials.anonymous()) - val syncConfig = SyncConfiguration.defaultConfig(user, "foo") + + val syncConfig = SyncConfiguration + .Builder(user, "foo") + .testSchema(SyncStringOnly::class.java) + .build() + Realm.getInstance(syncConfig).close() // Create a configuration pointing to the metadata Realm for that app diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt index ace5426cab..f9191475a4 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SchemaTests.kt @@ -16,13 +16,15 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.entities.SyncStringOnly import io.realm.mongodb.SyncTestUtils.Companion.createTestUser -import io.realm.entities.StringOnly import io.realm.mongodb.close import io.realm.mongodb.sync.SyncConfiguration +import io.realm.mongodb.sync.testSchema import io.realm.util.assertFailsWith import junit.framework.Assert.* import junit.framework.TestCase +import org.bson.types.ObjectId import org.junit.After import org.junit.Before import org.junit.Rule @@ -46,7 +48,10 @@ class SchemaTests { fun setUp() { app = TestApp() val user = createTestUser(app) - config = configFactory.createSyncConfigurationBuilder(user).build() + config = configFactory + .createSyncConfigurationBuilder(user) + .testSchema(SyncStringOnly::class.java) + .build() } @After @@ -69,11 +74,10 @@ class SchemaTests { fun createObject() { Realm.getInstance(config).use { realm -> realm.executeTransaction { - assertTrue(realm.schema.contains("StringOnly")) - val stringOnly = realm.createObject(StringOnly::class.java) - stringOnly.chars = "TEST" + assertTrue(realm.schema.contains(SyncStringOnly.CLASS_NAME)) + val stringOnly = realm.createObject(SyncStringOnly::class.java, ObjectId()) } - assertEquals(1, realm.where(StringOnly::class.java).count()) + assertEquals(1, realm.where(SyncStringOnly::class.java).count()) } } @@ -92,9 +96,8 @@ class SchemaTests { fun allow_addField() { // Init schema Realm.getInstance(config).close() - val className = "StringOnly" DynamicRealm.getInstance(config).use { realm -> - val objectSchema = realm.schema[className]!! + val objectSchema = realm.schema[SyncStringOnly.CLASS_NAME]!! assertNotNull(objectSchema) realm.executeTransaction { objectSchema.addField("foo", String::class.java) @@ -107,13 +110,15 @@ class SchemaTests { // Special column "__OID" should be hidden from users. @Test fun fieldNames_stableIdColumnShouldBeHidden() { - val className = "StringOnly" Realm.getInstance(config).use { realm -> - val objectSchema = realm.schema[className]!! + val objectSchema = realm.schema[SyncStringOnly.CLASS_NAME]!! assertNotNull(objectSchema) val names = objectSchema.fieldNames - assertEquals(1, names.size) - assertEquals(StringOnly.FIELD_CHARS, names.iterator().next()) + assertEquals(2, names.size) + + val iter = names.iterator() + assertEquals(SyncStringOnly.FIELD_ID, iter.next()) + assertEquals(SyncStringOnly.FIELD_CHARS, iter.next()) } } @@ -134,9 +139,9 @@ class SchemaTests { for (operation in DestructiveSchemaOperation.values()) { // Init schema Realm.getInstance(config).close() - val className = "StringOnly" + val className = SyncStringOnly.CLASS_NAME val newClassName = "Dogplace" - val fieldName = "chars" + val fieldName = SyncStringOnly.FIELD_CHARS val newFieldName = "newchars" DynamicRealm.getInstance(config).use { realm -> diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt index 2de7baf5de..a18ba05f5f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/SyncedRealmMigrationTests.kt @@ -16,20 +16,25 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.realm.mongodb.SyncTestUtils.Companion.createTestUser import io.realm.entities.IndexedFields import io.realm.entities.PrimaryKeyAsString import io.realm.entities.StringOnly +import io.realm.entities.SyncStringOnly import io.realm.internal.OsObjectSchemaInfo import io.realm.internal.OsRealmConfig import io.realm.internal.OsSchemaInfo import io.realm.internal.OsSharedRealm +import io.realm.mongodb.SyncTestUtils.Companion.createTestUser import io.realm.mongodb.close import io.realm.mongodb.sync.testSchema import io.realm.util.assertFailsWithMessage +import org.bson.types.ObjectId import org.hamcrest.CoreMatchers -import org.junit.* +import org.junit.After import org.junit.Assert.* +import org.junit.Before +import org.junit.Rule +import org.junit.Test import org.junit.runner.RunWith import kotlin.test.assertFailsWith @@ -69,21 +74,20 @@ class SyncedRealmMigrationTests { @Test fun addField_worksWithMigrationError() { val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) - .testSchema(StringOnly::class.java) + .testSchema(SyncStringOnly::class.java) .build() // Setup initial Realm schema (with missing fields) - val className = StringOnly::class.java.simpleName DynamicRealm.getInstance(config).use { dynamicRealm -> val schema = dynamicRealm.schema dynamicRealm.executeTransaction { - schema.create(className) // Create empty class + schema.createWithPrimaryKeyField(SyncStringOnly.CLASS_NAME, SyncStringOnly.FIELD_ID, ObjectId::class.java, FieldAttribute.REQUIRED) // Create empty class } } // Open typed Realm, which will validate the schema Realm.getInstance(config).use { realm -> - assertTrue(realm.schema[className]!!.hasField(StringOnly.FIELD_CHARS)) // Field has been added + assertTrue(realm.schema[SyncStringOnly.CLASS_NAME]!!.hasField(StringOnly.FIELD_CHARS)) // Field has been added } } @@ -92,16 +96,15 @@ class SyncedRealmMigrationTests { @Test fun missingFields_hiddenSilently() { val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) - .testSchema(StringOnly::class.java) + .testSchema(SyncStringOnly::class.java) .build() // Setup initial Realm schema (with too many fields) - val className = StringOnly::class.java.simpleName DynamicRealm.getInstance(config).use { dynamicRealm -> val schema = dynamicRealm.schema dynamicRealm.executeTransaction { - schema.create(className) - .addField(StringOnly.FIELD_CHARS, String::class.java) + schema.createWithPrimaryKeyField(SyncStringOnly.CLASS_NAME, SyncStringOnly.FIELD_ID, ObjectId::class.java, FieldAttribute.REQUIRED) + .addField(SyncStringOnly.FIELD_CHARS, String::class.java) .addField("newField", String::class.java) // A schema version has to be set otherwise Object Store will try to initialize the schema again and reach an // error branch. That is not a real case. @@ -111,10 +114,10 @@ class SyncedRealmMigrationTests { // Open typed Realm, which will validate the schema Realm.getInstance(config).use { realm -> - val stringOnlySchema = realm.schema[className]!! - assertTrue(stringOnlySchema.hasField(StringOnly.FIELD_CHARS)) + val stringOnlySchema = realm.schema[SyncStringOnly.CLASS_NAME]!! + assertTrue(stringOnlySchema.hasField(SyncStringOnly.FIELD_CHARS)) assertTrue(stringOnlySchema.hasField("newField")) - assertEquals(2, stringOnlySchema.fieldNames.size.toLong()) + assertEquals(3, stringOnlySchema.fieldNames.size.toLong()) } } @@ -126,15 +129,15 @@ class SyncedRealmMigrationTests { .build() // Setup initial Realm schema (with a different primary key) - val expectedObjectSchema = OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME, false,2, 0) + val expectedObjectSchema = OsObjectSchemaInfo.Builder(PrimaryKeyAsString.CLASS_NAME, false, 2, 0) .addPersistedProperty(PrimaryKeyAsString.FIELD_PRIMARY_KEY, RealmFieldType.STRING, false, true, false) - .addPersistedProperty(PrimaryKeyAsString.FIELD_ID, RealmFieldType.INTEGER, true, true, true) + .addPersistedProperty("_id", RealmFieldType.INTEGER, true, true, true) .build() val schemaInfo = OsSchemaInfo(listOf(expectedObjectSchema)) val configBuilder = OsRealmConfig.Builder(config).schemaInfo(schemaInfo) OsSharedRealm.getInstance(configBuilder, OsSharedRealm.VersionID.LIVE).close() assertFailsWithMessage( - CoreMatchers.containsString("The following changes cannot be made in additive-only schema mode:") + CoreMatchers.containsString("Schema validation failed due to the following errors:") ) { Realm.getInstance(config).close() } @@ -149,12 +152,10 @@ class SyncedRealmMigrationTests { .build() // Setup initial Realm schema (with no indexes) - val className = IndexedFields::class.java.simpleName - DynamicRealm.getInstance(config).use { dynamicRealm -> val schema = dynamicRealm.schema dynamicRealm.executeTransaction { - schema.create(className) + schema.createWithPrimaryKeyField(IndexedFields.CLASS_NAME, IndexedFields.FIELD_PRIMARY_STRING, ObjectId::class.java) .addField(IndexedFields.FIELD_INDEXED_STRING, String::class.java) // No index .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String::class.java) dynamicRealm.version = 42 @@ -163,7 +164,7 @@ class SyncedRealmMigrationTests { Realm.getInstance(config).use { realm -> // Opening at same schema version (42) will not rebuild indexes - val indexedFieldsSchema = realm.schema[className]!! + val indexedFieldsSchema = realm.schema[IndexedFields.CLASS_NAME]!! assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_INDEXED_STRING)) assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_NON_INDEXED_STRING)) } @@ -178,11 +179,10 @@ class SyncedRealmMigrationTests { .build() // Setup initial Realm schema (with no indexes) - val className = IndexedFields::class.java.simpleName DynamicRealm.getInstance(config).use { dynamicRealm -> val schema = dynamicRealm.schema dynamicRealm.executeTransaction { - schema.create(className) + schema.createWithPrimaryKeyField(IndexedFields.CLASS_NAME, IndexedFields.FIELD_PRIMARY_STRING, ObjectId::class.java) .addField(IndexedFields.FIELD_INDEXED_STRING, String::class.java) // No index .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String::class.java) dynamicRealm.version = 43 @@ -191,7 +191,7 @@ class SyncedRealmMigrationTests { Realm.getInstance(config).use { realm -> // Opening at different schema version (42) should rebuild indexes - val indexedFieldsSchema = realm.schema[className]!! + val indexedFieldsSchema = realm.schema[IndexedFields.CLASS_NAME]!! assertNotNull(indexedFieldsSchema) assertTrue(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_INDEXED_STRING)) assertFalse(indexedFieldsSchema.hasIndex(IndexedFields.FIELD_NON_INDEXED_STRING)) @@ -211,7 +211,7 @@ class SyncedRealmMigrationTests { DynamicRealm.getInstance(config).use { dynamicRealm -> val schema = dynamicRealm.schema dynamicRealm.executeTransaction { - schema.create(className) + schema.createWithPrimaryKeyField(className, IndexedFields.FIELD_PRIMARY_STRING, ObjectId::class.java) .addField(IndexedFields.FIELD_INDEXED_STRING, String::class.java) // No index // .addField(IndexedFields.FIELD_NON_INDEXED_STRING, String.class); // Missing field dynamicRealm.version = 41 @@ -229,15 +229,15 @@ class SyncedRealmMigrationTests { @Test fun schemaVersionUpgradedWhenMigrating() { val config = configFactory.createSyncConfigurationBuilder(createTestUser(app)) + .testSchema(SyncStringOnly::class.java) .schemaVersion(42) .build() // Setup initial Realm schema (with missing fields) DynamicRealm.getInstance(config).use { dynamicRealm -> - val className = StringOnly::class.java.simpleName val schema = dynamicRealm.schema dynamicRealm.executeTransaction { - schema.create(className) // Create empty class + schema.createWithPrimaryKeyField(SyncStringOnly.CLASS_NAME, SyncStringOnly.FIELD_ID, ObjectId::class.java, FieldAttribute.REQUIRED) // Create empty class dynamicRealm.version = 1 } } @@ -253,14 +253,14 @@ class SyncedRealmMigrationTests { fun moreFieldsThanExpectedIsAllowed() { val config = configFactory .createSyncConfigurationBuilder(createTestUser(app)) - .testSchema(StringOnly::class.java) + .testSchema(SyncStringOnly::class.java) .build() // Initialize schema Realm.getInstance(config).close() DynamicRealm.getInstance(config).use { dynamicRealm -> dynamicRealm.executeTransaction { - val objectSchema = dynamicRealm.schema[StringOnly.CLASS_NAME]!! + val objectSchema = dynamicRealm.schema[SyncStringOnly.CLASS_NAME]!! // Add one extra field which doesn't exist in the typed Realm. objectSchema.addField("oneMoreField", Integer::class.java) } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnly.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnly.kt index c36c0084bc..13c8075e0b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnly.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncStringOnly.kt @@ -23,13 +23,14 @@ import org.bson.types.ObjectId open class SyncStringOnly : RealmObject() { companion object { - const val CLASS_NAME = "StringOnly" + const val CLASS_NAME = "SyncStringOnly" + const val FIELD_ID = "_id" const val FIELD_CHARS = "chars" } @PrimaryKey @RealmField(name = "_id") - var id = ObjectId() + var id: ObjectId = ObjectId() var chars: String? = null diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt index bc9fafa716..276949576a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SessionTests.kt @@ -21,8 +21,8 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.* import io.realm.TestHelper.TestLogger import io.realm.entities.DefaultSyncSchema -import io.realm.entities.StringOnly -import io.realm.entities.StringOnlyModule +import io.realm.entities.SyncStringOnly +import io.realm.entities.SyncStringOnlyModule import io.realm.exceptions.RealmFileException import io.realm.exceptions.RealmMigrationNeededException import io.realm.kotlin.syncSession @@ -32,6 +32,7 @@ import io.realm.mongodb.* import io.realm.rule.BlockingLooperThread import io.realm.util.ResourceContainer import io.realm.util.assertFailsWithMessage +import org.bson.types.ObjectId import org.hamcrest.CoreMatchers import org.junit.After import org.junit.Before @@ -126,6 +127,7 @@ class SessionTests { @Test fun errorHandler_clientResetReported() = looperThread.runBlocking { val config = configFactory.createSyncConfigurationBuilder(user) + .testSchema(SyncStringOnly::class.java) .clientResetHandler { session: SyncSession, error: ClientResetRequiredError -> val filePathFromError = error.originalFile.absolutePath val filePathFromConfig = session.configuration.path @@ -149,6 +151,7 @@ class SessionTests { val resources = ResourceContainer() val config = configFactory.createSyncConfigurationBuilder(user) + .testSchema(SyncStringOnly::class.java) .clientResyncMode(ClientResyncMode.MANUAL) .clientResetHandler { _: SyncSession, error: ClientResetRequiredError -> try { @@ -180,7 +183,7 @@ class SessionTests { val resources = ResourceContainer() val config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) - .schema(StringOnly::class.java) + .schema(SyncStringOnly::class.java) .clientResetHandler { _: SyncSession?, error: ClientResetRequiredError -> // Execute Client Reset resources.close() @@ -195,24 +198,23 @@ class SessionTests { assertTrue(backupRealmConfiguration.isRecoveryConfiguration) Realm.getInstance(backupRealmConfiguration).use { backupRealm -> assertFalse(backupRealm.isEmpty) - assertEquals(1, backupRealm.where(StringOnly::class.java).count()) - assertEquals("Foo", backupRealm.where(StringOnly::class.java).findAll().first()!!.chars) + assertEquals(1, backupRealm.where(SyncStringOnly::class.java).count()) + assertEquals("Foo", backupRealm.where(SyncStringOnly::class.java).findAll().first()!!.chars) } // opening a Dynamic Realm should also work DynamicRealm.getInstance(backupRealmConfiguration).use { dynamicRealm -> - assertNotNull(dynamicRealm.schema.get(StringOnly.CLASS_NAME)) - val all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll() + assertNotNull(dynamicRealm.schema.get(SyncStringOnly.CLASS_NAME)) + val all = dynamicRealm.where(SyncStringOnly.CLASS_NAME).findAll() assertEquals(1, all.size.toLong()) - assertEquals("Foo", all.first()!!.getString(StringOnly.FIELD_CHARS)) + assertEquals("Foo", all.first()!!.getString(SyncStringOnly.FIELD_CHARS)) } looperThread.testComplete() } - .modules(StringOnlyModule()) .build() val realm = Realm.getInstance(config) realm.executeTransaction { - realm.createObject(StringOnly::class.java).chars = "Foo" + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo" } resources.add(realm) @@ -227,6 +229,7 @@ class SessionTests { fun errorHandler_useBackupSyncConfigurationAfterClientReset() = looperThread.runBlocking { val resources = ResourceContainer() val config = configFactory.createSyncConfigurationBuilder(user) + .modules(SyncStringOnlyModule()) .clientResyncMode(ClientResyncMode.MANUAL) .clientResetHandler { session: SyncSession?, error: ClientResetRequiredError -> // Execute Client Reset @@ -247,10 +250,10 @@ class SessionTests { // opening a DynamicRealm will work though DynamicRealm.getInstance(backupRealmConfiguration).use { dynamicRealm -> - assertNotNull(dynamicRealm.schema.get(StringOnly.CLASS_NAME)) - val all = dynamicRealm.where(StringOnly.CLASS_NAME).findAll() + assertNotNull(dynamicRealm.schema.get(SyncStringOnly.CLASS_NAME)) + val all = dynamicRealm.where(SyncStringOnly.CLASS_NAME).findAll() assertEquals(1, all.size.toLong()) - assertEquals("Foo", all.first()!!.getString(StringOnly.FIELD_CHARS)) + assertEquals("Foo", all.first()!!.getString(SyncStringOnly.FIELD_CHARS)) // make sure we can't write to it (read-only Realm) assertFailsWith { dynamicRealm.beginTransaction() @@ -258,25 +261,24 @@ class SessionTests { } assertFailsWith { - SyncConfiguration.forRecovery(backupFile, null, StringOnly::class.java) + SyncConfiguration.forRecovery(backupFile, null, SyncStringOnly::class.java) } // specifying the module will allow to open the typed Realm - backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, null, StringOnlyModule()) + backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, null, SyncStringOnlyModule()) Realm.getInstance(backupRealmConfiguration).use { backupRealm -> assertFalse(backupRealm.isEmpty) - assertEquals(1, backupRealm.where(StringOnly::class.java).count()) - val allSorted = backupRealm.where(StringOnly::class.java).findAll() + assertEquals(1, backupRealm.where(SyncStringOnly::class.java).count()) + val allSorted = backupRealm.where(SyncStringOnly::class.java).findAll() assertEquals("Foo", allSorted[0]!!.chars) } looperThread.testComplete() } - .modules(StringOnlyModule()) .build() val realm = Realm.getInstance(config) realm.executeTransaction { - realm.createObject(StringOnly::class.java).chars = "Foo" + it.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo" } resources.add(realm) @@ -293,7 +295,7 @@ class SessionTests { val config = configFactory.createSyncConfigurationBuilder(user) .clientResyncMode(ClientResyncMode.MANUAL) .encryptionKey(randomKey) - .modules(StringOnlyModule()) + .modules(SyncStringOnlyModule()) .clientResetHandler { session: SyncSession?, error: ClientResetRequiredError -> // Execute Client Reset resources.close() @@ -302,23 +304,23 @@ class SessionTests { // can open encrypted backup Realm Realm.getInstance(backupRealmConfiguration).use { backupEncryptedRealm -> - assertEquals(1, backupEncryptedRealm.where(StringOnly::class.java).count()) - val allSorted = backupEncryptedRealm.where(StringOnly::class.java).findAll() + assertEquals(1, backupEncryptedRealm.where(SyncStringOnly::class.java).count()) + val allSorted = backupEncryptedRealm.where(SyncStringOnly::class.java).findAll() assertEquals("Foo", allSorted[0]!!.chars) } val backupFile = error.backupFile.absolutePath // build a conf to open a DynamicRealm - backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, randomKey, StringOnlyModule()) + backupRealmConfiguration = SyncConfiguration.forRecovery(backupFile, randomKey, SyncStringOnlyModule()) Realm.getInstance(backupRealmConfiguration).use { backupEncryptedRealm -> - assertEquals(1, backupEncryptedRealm.where(StringOnly::class.java).count()) - val allSorted = backupEncryptedRealm.where(StringOnly::class.java).findAll() + assertEquals(1, backupEncryptedRealm.where(SyncStringOnly::class.java).count()) + val allSorted = backupEncryptedRealm.where(SyncStringOnly::class.java).findAll() assertEquals("Foo", allSorted[0]!!.chars) } // using wrong key throw assertFailsWith { - Realm.getInstance(SyncConfiguration.forRecovery(backupFile, TestHelper.getRandomKey(), StringOnlyModule())) + Realm.getInstance(SyncConfiguration.forRecovery(backupFile, TestHelper.getRandomKey(), SyncStringOnlyModule())) } looperThread.testComplete() } @@ -326,7 +328,7 @@ class SessionTests { val realm = Realm.getInstance(config) realm.executeTransaction { - realm.createObject(StringOnly::class.java).chars = "Foo" + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo" } resources.add(realm) @@ -423,6 +425,7 @@ class SessionTests { fun unrecognizedErrorCode_errorHandler() { val errorHandlerCalled = AtomicBoolean(false) configuration = configFactory.createSyncConfigurationBuilder(user) + .testSchema(SyncStringOnly::class.java) .errorHandler { session: SyncSession?, error: AppException -> errorHandlerCalled.set(true) assertEquals(ErrorCode.UNKNOWN, error.errorCode) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index d2e7e4b714..0cc86b1895 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -18,8 +18,8 @@ package io.realm.mongodb.sync import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.* -import io.realm.entities.StringOnly -import io.realm.entities.StringOnlyModule +import io.realm.entities.SyncStringOnly +import io.realm.entities.SyncStringOnlyModule import io.realm.kotlin.createObject import io.realm.kotlin.where import io.realm.mongodb.AppException @@ -204,11 +204,11 @@ class SyncConfigurationTests { fun initialData() { val user: User = createTestUser(app) val config = configFactory.createSyncConfigurationBuilder(user) - .schema(StringOnly::class.java) + .schema(SyncStringOnly::class.java) .initialData(object : Realm.Transaction { override fun execute(realm: Realm) { - val stringOnly: StringOnly = realm.createObject() - stringOnly.setChars("TEST 42") + val stringOnly: SyncStringOnly = realm.createObject(ObjectId()) + stringOnly.chars = "TEST 42" } }) .build() @@ -216,14 +216,14 @@ class SyncConfigurationTests { // open the first time - initialData must be triggered Realm.getInstance(config).use { realm -> - val results: RealmResults = realm.where().findAll() + val results: RealmResults = realm.where().findAll() assertEquals(1, results.size) - assertEquals("TEST 42", results.first()!!.getChars()) + assertEquals("TEST 42", results.first()!!.chars) } // open the second time - initialData must not be triggered Realm.getInstance(config).use { realm -> - assertEquals(1, realm.where().count()) + assertEquals(1, realm.where().count()) } } @@ -250,10 +250,10 @@ class SyncConfigurationTests { val user2: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") val config1: SyncConfiguration = SyncConfiguration.Builder(user1, DEFAULT_PARTITION) - .modules(StringOnlyModule()) + .modules(SyncStringOnlyModule()) .build() val config2: SyncConfiguration = SyncConfiguration.Builder(user2, DEFAULT_PARTITION) - .modules(StringOnlyModule()) + .modules(SyncStringOnlyModule()) .build() // Verify that two different configurations can be used for the same URL @@ -330,8 +330,8 @@ class SyncConfigurationTests { @Test fun differentPartitionValuesAreDifferentRealms() { val user: User = createTestUser(app) - val config1 = SyncConfiguration.defaultConfig(user, "realm1") - val config2 = SyncConfiguration.defaultConfig(user, "realm2") + val config1 = SyncConfiguration.Builder(user, "realm1").modules(SyncStringOnlyModule()).build() + val config2 = SyncConfiguration.Builder(user, "realm2").modules(SyncStringOnlyModule()).build() assertNotEquals(config1.path, config2.path) assertTrue(config1.path.endsWith("${app.configuration.appId}/${user.id}/s_realm1.realm")) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index 3eb9bb56c7..771add740f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -33,6 +33,7 @@ import io.realm.mongodb.User import io.realm.mongodb.close import org.bson.BsonNull import org.bson.BsonString +import org.bson.types.ObjectId import org.junit.* import org.junit.Assert.assertNotEquals import org.junit.runner.RunWith @@ -230,13 +231,16 @@ class SyncedRealmTests { val user = createTestUser(app) // Fill Realm with data and record size - val config1 = configFactory.createSyncConfigurationBuilder(user).build() + val config1 = configFactory.createSyncConfigurationBuilder(user) + .testSchema(SyncByteArray::class.java) + .build() + var originalSize : Long? = null Realm.getInstance(config1).use { realm -> val oneMBData = ByteArray(1024 * 1024) realm.executeTransaction { for (i in 0..9) { - realm.createObject(AllTypes::class.java).columnBinary = oneMBData + realm.createObject(SyncByteArray::class.java, ObjectId()).columnBinary = oneMBData } } originalSize = File(realm.path).length() @@ -245,6 +249,7 @@ class SyncedRealmTests { // Open Realm with CompactOnLaunch val config2 = configFactory.createSyncConfigurationBuilder(user) .compactOnLaunch { totalBytes, usedBytes -> true } + .testSchema(SyncByteArray::class.java) .build() Realm.getInstance(config2).use { realm -> val compactedSize = File(realm.path).length() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 9724e2cd6f..699ebd3d85 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -382,8 +382,6 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWit Obj obj = do_create_row_with_object_id_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value); if (bool(obj)) { return reinterpret_cast(new Obj(obj)); - } else { - THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, "Invalid Object returned from 'do_create_row_with_object_id_primary_key'"); } } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 875d2a3062..7cac296a38 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -275,7 +275,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTable(J } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWithPrimaryKeyField( - JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_table_name, jstring j_field_name, jboolean is_string_type, + JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_table_name, jstring j_field_name, jint j_field_type, jboolean is_nullable) { std::string class_name_str; @@ -285,7 +285,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWi JStringAccessor field_name(env, j_field_name); // throws auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); shared_realm->verify_in_write(); // throws - DataType pkType = is_string_type ? DataType::type_String : DataType::type_Int; + + DataType pkType = static_cast(j_field_type); TableRef table; auto& group = shared_realm->read_group(); #if REALM_ENABLE_SYNC diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp index 0ea793c7b9..dc5876a0cb 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp @@ -25,8 +25,8 @@ #include #include #include -#include -#include +#include +#include using namespace realm; using namespace realm::app; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp index 3b4023a165..e7597399ae 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp @@ -26,8 +26,8 @@ #include #include -#include -#include +#include +#include #include using namespace realm; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp index 12280b16e9..51626da135 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp @@ -25,8 +25,8 @@ #include #include #include -#include -#include +#include +#include using namespace realm; using namespace realm::app; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp index 2f46deb4c0..735175d84f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp @@ -19,7 +19,7 @@ #include "java_class_global_def.hpp" #include "jni_util/bson_util.hpp" -#include +#include using namespace realm; using namespace realm::app; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp index fe2d6370de..cd9144e696 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp @@ -25,7 +25,7 @@ #include "object-store/src/util/bson/bson.hpp" #include -#include +#include #include #include diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp index ed911a20ea..1507682960 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp @@ -25,7 +25,7 @@ #include "object-store/src/util/bson/bson.hpp" #include -#include +#include #include using namespace realm; diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index d0ac41bee0..fd246c54de 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit d0ac41bee0ccab83eb887b9fff8f417875ecf771 +Subproject commit fd246c54de7d1fee6bcbeb3609de75a4eccd5b70 diff --git a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java index 0c996c9c04..d42ad6f759 100644 --- a/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java +++ b/realm/realm-library/src/main/java/io/realm/MutableRealmSchema.java @@ -83,12 +83,14 @@ public RealmObjectSchema createWithPrimaryKeyField(String className, String prim String internalTableName = checkAndGetTableNameFromClassName(className); RealmObjectSchema.FieldMetaData metadata = RealmObjectSchema.getSupportedSimpleFields().get(fieldType); - if (metadata == null || (metadata.fieldType != RealmFieldType.STRING && - metadata.fieldType != RealmFieldType.INTEGER)) { + if ((metadata == null) || ( + (metadata.fieldType != RealmFieldType.STRING) && + (metadata.fieldType != RealmFieldType.INTEGER) && + (metadata.fieldType != RealmFieldType.OBJECT_ID) + )) { throw new IllegalArgumentException(String.format("Realm doesn't support primary key field type '%s'.", fieldType)); } - boolean isStringField = (metadata.fieldType == RealmFieldType.STRING); boolean nullable = metadata.defaultNullable; if (MutableRealmObjectSchema.containsAttribute(attributes, FieldAttribute.REQUIRED)) { @@ -97,7 +99,7 @@ public RealmObjectSchema createWithPrimaryKeyField(String className, String prim return new MutableRealmObjectSchema(realm, this, realm.getSharedRealm().createTableWithPrimaryKey(internalTableName, primaryKeyFieldName, - isStringField, nullable)); + metadata.fieldType, nullable)); } @Override diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 1c70004c55..834c3219e6 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -16,6 +16,9 @@ package io.realm; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -28,10 +31,8 @@ import io.realm.annotations.RealmClass; import io.realm.annotations.Required; -import io.realm.internal.CheckedRow; import io.realm.internal.ColumnInfo; import io.realm.internal.OsObjectStore; -import io.realm.internal.OsResults; import io.realm.internal.Table; import io.realm.internal.fields.FieldDescriptor; @@ -68,6 +69,8 @@ public abstract class RealmObjectSchema { m.put(Byte.class, new FieldMetaData(RealmFieldType.INTEGER, RealmFieldType.INTEGER_LIST, true)); m.put(byte[].class, new FieldMetaData(RealmFieldType.BINARY, RealmFieldType.BINARY_LIST, true)); m.put(Date.class, new FieldMetaData(RealmFieldType.DATE, RealmFieldType.DATE_LIST, true)); + m.put(ObjectId.class, new FieldMetaData(RealmFieldType.OBJECT_ID, RealmFieldType.OBJECT_ID_LIST, true)); + m.put(Decimal128.class, new FieldMetaData(RealmFieldType.DECIMAL128, RealmFieldType.DECIMAL128_LIST, true)); SUPPORTED_SIMPLE_FIELDS = Collections.unmodifiableMap(m); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java index 49b8850c1b..ae466d52c3 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsSharedRealm.java @@ -26,6 +26,7 @@ import javax.annotation.Nullable; import io.realm.RealmConfiguration; +import io.realm.RealmFieldType; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; import io.realm.internal.annotations.ObjectServer; @@ -326,10 +327,9 @@ public Table createTable(String name) { * @param isNullable if the primary key field is nullable or not. * @return a newly created {@link Table} object. */ - public Table createTableWithPrimaryKey(String tableName, String primaryKeyFieldName, boolean isStringType, + public Table createTableWithPrimaryKey(String tableName, String primaryKeyFieldName, RealmFieldType primaryKeyFieldType, boolean isNullable) { - return new Table(this, nativeCreateTableWithPrimaryKeyField(nativePtr, tableName, primaryKeyFieldName, - isStringType, isNullable)); + return new Table(this, nativeCreateTableWithPrimaryKeyField(nativePtr, tableName, primaryKeyFieldName, primaryKeyFieldType.getNativeValue(), isNullable)); } public void renameTable(String oldName, String newName) { @@ -602,7 +602,7 @@ private static void runInitializationCallback(long nativeSharedRealmPtr, OsRealm // If isStringType is false, the PK field will be created as an integer PK field. private static native long nativeCreateTableWithPrimaryKeyField(long nativeSharedRealmPtr, String tableName, String primaryKeyFieldName, - boolean isStringType, boolean isNullable); + int primaryKeyFieldType, boolean isNullable); private static native String[] nativeGetTablesName(long nativeSharedRealmPtr); diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index bf5622dce1..311ccdacfd 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -65,6 +65,7 @@ class SyncSessionTests { val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val syncConfiguration = configFactory .createSyncConfigurationBuilder(user) + .testSchema(SyncStringOnly::class.java) .build() val realm = Realm.getInstance(syncConfiguration) looperThread.closeAfterTest(realm) @@ -599,6 +600,7 @@ class SyncSessionTests { val configRef = AtomicReference(null) val config: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user) + .testSchema(SyncStringOnly::class.java) // ClientResyncMode is currently hidden, but MANUAL is the default // .clientResyncMode(ClientResyncMode.MANUAL) // FIXME Is this critical for the test @@ -732,6 +734,7 @@ class SyncSessionTests { @Test fun cachedInstanceShouldNotThrowIfUserTokenIsInvalid() { val configuration: RealmConfiguration = configFactory.createSyncConfigurationBuilder(user) + .testSchema(SyncStringOnly::class.java) .errorHandler { session, error -> RealmLog.debug("error", error) } diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 1c02510a29..97e3df1781 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -41,7 +41,6 @@ import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith -import java.io.File import java.util.* import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -73,6 +72,7 @@ class SyncedRealmIntegrationTests { // could make test booting with a cleaner state by somehow flushing data between // tests. .createSyncConfigurationBuilder(user, BsonObjectId(ObjectId())) + .testSchema(StringOnly::class.java) .modules(DefaultSyncSchema()) .build() } @@ -213,6 +213,7 @@ class SyncedRealmIntegrationTests { fun waitForInitialData_resilientInCaseOfRetriesAsync() = looperThread.runBlocking { val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .testSchema(SyncStringOnly::class.java) .waitForInitialRemoteData() .build() val randomizer = Random() @@ -297,7 +298,10 @@ class SyncedRealmIntegrationTests { @Test fun defaultRealm() { - val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, user.id) + val config: SyncConfiguration = SyncConfiguration.Builder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .build() + Realm.getInstance(config).use { realm -> realm.syncSession.downloadAllServerChanges() realm.refresh() @@ -319,7 +323,7 @@ class SyncedRealmIntegrationTests { val password = "password" val user: User = app.registerUserAndLogin(username, password) val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .testSchema(StringOnly::class.java) + .testSchema(SyncStringOnly::class.java) .build() val realm = Realm.getInstance(config) app.sync.reconnect() diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/SyncByteArray.java b/realm/realm-library/src/testUtils/java/io/realm/entities/SyncByteArray.java new file mode 100644 index 0000000000..55e682bb43 --- /dev/null +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/SyncByteArray.java @@ -0,0 +1,14 @@ +package io.realm.entities; + +import org.bson.types.ObjectId; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; + +public class SyncByteArray extends RealmObject { + @PrimaryKey + public ObjectId _id; + @Required + public byte[] columnBinary = new byte[0]; +} From fba7b2a0062f9c4af5866375fd4626c842b0cb67 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 6 Nov 2020 09:43:00 +0100 Subject: [PATCH 1736/2110] Release v10.0.1 --- CHANGELOG.md | 4 ++-- version.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05c00f973f..cfd1d3c1c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.1 (YYYY-MM-DD) +## 10.0.1 (20202-11-06) ### Breaking Changes * None. @@ -7,7 +7,7 @@ * Improved the error message for `NoSuchTable` errors. In some cases an outdated native reference was used,but the table was still there. In those cases an `InvalidTableRef` error is now used. ### Fixes -* [SyncConfiguration] The `SyncConfiguration.Builder.allowQueriesOnUiThread` flag was wrongly initialized to `false` keeping users from running queries from the UI thread when using synced Realms. It now defaults to `true`, allowing queries to be run from the UI. (Issue [#7177](https://github.com/realm/realm-java/issues/7177), since 10.0.0) +* [RealmApp] The `SyncConfiguration.Builder.allowQueriesOnUiThread` flag was wrongly initialized to `false` keeping users from running queries from the UI thread when using synced Realms. It now defaults to `true`, allowing queries to be run from the UI. (Issue [#7177](https://github.com/realm/realm-java/issues/7177), since 10.0.0) * Crash with `Assertion failed: m_method_id != nullptr with (method_name, signature) = ["", "(Ljava/lang/String;)V"]` when `Minify` is enabled. (Issue [#7159](https://github.com/realm/realm-java/pull/7159), since 10.0.0) * Fix crash in case insensitive query on indexed string columns when nothing matches (Cocoa issue [#6836](https://github.com/realm/realm-cocoa/issues/6836), since v10.0.0) * Fix list of primitives with nullable values where `Lst::is_null(ndx)` always false even on null values, (Core issue [#3987](https://github.com/realm/realm-core/pull/3987), since v10.0.0). diff --git a/version.txt b/version.txt index e988cdd41c..1532420512 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.1-SNAPSHOT +10.0.1 From 3cb4441bf87dcb49c7f87cd0dd9afb731e2ccd8b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 6 Nov 2020 10:41:21 +0100 Subject: [PATCH 1737/2110] Release v10.0.1 --- Jenkinsfile | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 12a105ead1..837242a829 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,7 +7,7 @@ import groovy.json.JsonOutput // CONSTANTS // Branches from which we release SNAPSHOT's. Only release branches need to run on actual hardware. -releaseBranches = ['master', 'next-major', 'v10'] +releaseBranches = ['master', 'next-major', 'v10', 'releases'] // Branches that are "important", so if they do not compile they will generate a Slack notification slackNotificationBranches = [ 'master', 'releases', 'next-major', 'v10' ] // WARNING: Only set to `false` as an absolute last resort. Doing this will disable all integration diff --git a/version.txt b/version.txt index 1532420512..5a02812980 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.1 +10.0.1 \ No newline at end of file From 94ba65b837532feffd44f10ba7a591366f183a7b Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 6 Nov 2020 10:55:36 +0100 Subject: [PATCH 1738/2110] Release v10.0.1 --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 837242a829..89e788e797 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,7 +25,7 @@ mongoDbRealmContainer = null mongoDbRealmCommandServerContainer = null emulatorContainer = null dockerNetworkId = UUID.randomUUID().toString() -currentBranch = env.CHANGE_BRANCH +currentBranch = env.BRANCH_NAME // FIXME: Always used the emulator until we can enable more reliable devices // 'android' nodes have android devices attached and 'brix' are physical machines in Copenhagen. // nodeSelector = (releaseBranches.contains(currentBranch)) ? 'android' : 'docker-cph-03' // Switch to `brix` when all CPH nodes work: https://jira.mongodb.org/browse/RCI-14 From d5035410eca5c4d780172e8d698543774254adbd Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 6 Nov 2020 11:41:55 +0100 Subject: [PATCH 1739/2110] Release v10.0.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfd1d3c1c5..84aeebadc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.1 (20202-11-06) +## 10.0.1 (2020-11-06) ### Breaking Changes * None. From ff75ac5fd0ca22bb855574004ca116f13d547cb5 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Fri, 6 Nov 2020 12:15:16 +0100 Subject: [PATCH 1740/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5a02812980..a27abb9ad0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.1 \ No newline at end of file +10.0.2-SNAPSHOT \ No newline at end of file From 65130524043b17acdf0aabaa3c36d0361c2c96fb Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Mon, 9 Nov 2020 13:08:07 +0100 Subject: [PATCH 1741/2110] RJAVA-564: Use monorepo (#7146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use monorepo * Use newer CMake * use latest monorepo * Point to right cmake version Jenkins to use right cmake version Fix imports and executables linking * Fix cmake url * Add ninja-build package to docker image * Add new cmake instructions to readme * don’t worry about realm-core’s command-line targets * use latest monorepo * Add support for LTO. It is enabled by default for release builds, but can be opted out of. * Rename abiFilter to indicate it support multiple flags * Set an optimization level, it is required when enabling LTO. * Bump core commit * Bump monorepo core version * rollback mongodb realm server version * Fix checkstyle step in Jenkins file * remove the gradle logic to download core this should be moved to CMake Co-authored-by: Clemente Tort Co-authored-by: Christian Melchior --- .gitmodules | 6 +- Dockerfile | 9 +- Jenkinsfile | 20 +-- README.md | 2 +- build.gradle | 3 + dependencies.list | 3 +- realm/realm-library/build.gradle | 143 ++---------------- .../src/main/cpp/CMake/RealmCore.cmake | 130 ---------------- .../realm-library/src/main/cpp/CMakeLists.txt | 82 +++------- .../src/main/cpp/io_realm_RealmQuery.cpp | 4 +- ...o_realm_internal_OsCollectionChangeSet.cpp | 2 +- .../src/main/cpp/io_realm_internal_OsList.cpp | 6 +- .../main/cpp/io_realm_internal_OsObject.cpp | 6 +- .../io_realm_internal_OsObjectSchemaInfo.cpp | 4 +- .../cpp/io_realm_internal_OsObjectStore.cpp | 4 +- .../cpp/io_realm_internal_OsRealmConfig.cpp | 11 +- .../main/cpp/io_realm_internal_OsResults.cpp | 6 +- .../cpp/io_realm_internal_OsSchemaInfo.cpp | 6 +- .../cpp/io_realm_internal_OsSharedRealm.cpp | 13 +- .../main/cpp/io_realm_internal_Property.cpp | 4 +- .../src/main/cpp/io_realm_internal_Table.cpp | 17 ++- .../main/cpp/io_realm_internal_TableQuery.cpp | 6 +- ..._realm_internal_core_IncludeDescriptor.cpp | 10 +- .../io_realm_internal_objectstore_OsApp.cpp | 6 +- ..._internal_objectstore_OsAppCredentials.cpp | 2 +- ...m_internal_objectstore_OsAsyncOpenTask.cpp | 9 +- ...alm_internal_objectstore_OsMongoClient.cpp | 8 +- ...internal_objectstore_OsMongoCollection.cpp | 8 +- ...m_internal_objectstore_OsMongoDatabase.cpp | 8 +- .../io_realm_internal_objectstore_OsPush.cpp | 2 +- ..._realm_internal_objectstore_OsSyncUser.cpp | 4 +- ...alm_internal_objectstore_OsWatchStream.cpp | 2 +- .../cpp/io_realm_mongodb_ApiKeyAuthImpl.cpp | 3 +- ...io_realm_mongodb_EmailPasswordAuthImpl.cpp | 2 +- .../cpp/io_realm_mongodb_FunctionsImpl.cpp | 2 +- .../src/main/cpp/io_realm_mongodb_User.cpp | 2 +- ...ngodb_mongo_iterable_AggregateIterable.cpp | 4 +- ...lm_mongodb_mongo_iterable_FindIterable.cpp | 4 +- ..._mongodb_sync_ClientResetRequiredError.cpp | 4 +- .../main/cpp/io_realm_mongodb_sync_Sync.cpp | 11 +- .../cpp/io_realm_mongodb_sync_SyncSession.cpp | 6 +- .../src/main/cpp/java_accessor.hpp | 3 +- .../src/main/cpp/java_binding_context.hpp | 2 +- .../src/main/cpp/java_network_transport.hpp | 2 +- .../src/main/cpp/java_object_accessor.hpp | 4 +- .../src/main/cpp/jni_util/bson_util.hpp | 2 +- .../src/main/cpp/jni_util/log.hpp | 2 +- realm/realm-library/src/main/cpp/object-store | 1 - .../cpp/observable_collection_wrapper.hpp | 2 +- realm/realm-library/src/main/cpp/realm-core | 1 + realm/realm-library/src/main/cpp/util.cpp | 14 +- 51 files changed, 173 insertions(+), 444 deletions(-) delete mode 100644 realm/realm-library/src/main/cpp/CMake/RealmCore.cmake delete mode 160000 realm/realm-library/src/main/cpp/object-store create mode 160000 realm/realm-library/src/main/cpp/realm-core diff --git a/.gitmodules b/.gitmodules index 35b419c520..b0bd09c398 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "realm/realm-library/src/main/cpp/object-store"] - path = realm/realm-library/src/main/cpp/object-store - url = https://github.com/realm/realm-object-store.git +[submodule "realm/realm-library/src/main/cpp/realm-core"] + path = realm/realm-library/src/main/cpp/realm-core + url = https://github.com/realm/realm-core.git diff --git a/Dockerfile b/Dockerfile index 4265616585..c65e37b706 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,6 +48,7 @@ RUN DEBIAN_FRONTEND=noninteractive \ virt-manager \ wget \ zip \ + ninja-build \ && apt-get clean # Install the Android SDK @@ -69,7 +70,6 @@ RUN yes | sdkmanager --licenses # Please keep all sections in descending order! RUN yes | sdkmanager \ 'build-tools;29.0.3' \ - 'cmake;3.6.4111459' \ 'emulator' \ 'extras;android;m2repository' \ 'platforms;android-29' \ @@ -79,3 +79,10 @@ RUN yes | sdkmanager \ # Make the SDK universally writable RUN chmod -R a+rwX ${ANDROID_HOME} + +# Ensure a new enough version of CMake is available. +RUN cd /opt \ + && wget -nv https://cmake.org/files/v3.18/cmake-3.18.4-Linux-x86_64.tar.gz \ + && tar zxf cmake-3.18.4-Linux-x86_64.tar.gz + +ENV PATH "/opt/cmake-3.18.4-Linux-x86_64/bin:$PATH" diff --git a/Jenkinsfile b/Jenkinsfile index 89e788e797..9cd5885de3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -75,14 +75,14 @@ try { // on an actual device. def useEmulator = false def emulatorImage = "" - def abiFilter = "" + def buildFlags = "" def instrumentationTestTarget = "connectedAndroidTest" def deviceSerial = "" if (!releaseBranches.contains(currentBranch)) { // Build development branch useEmulator = true emulatorImage = "system-images;android-29;default;x86" - abiFilter = "-PbuildTargetABIs=x86" + buildFlags = "-PbuildTargetABIs=x86 -PdisableLTO=1" instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" deviceSerial = "emulator-5554" } else { @@ -151,12 +151,12 @@ try { // Need to go to ANDROID_HOME due to https://askubuntu.com/questions/1005944/emulator-avd-does-not-launch-the-virtual-device sh "cd \$ANDROID_HOME/tools && emulator -avd CIEmulator -no-boot-anim -no-window -wipe-data -noaudio -partition-size 4098 &" try { - runBuild(abiFilter, instrumentationTestTarget) + runBuild(buildFlags, instrumentationTestTarget) } finally { sh "adb emu kill" } } else { - runBuild(abiFilter, instrumentationTestTarget) + runBuild(buildFlags, instrumentationTestTarget) } // Release the library if needed @@ -214,17 +214,17 @@ try { } // Runs all build steps -def runBuild(abiFilter, instrumentationTestTarget) { +def runBuild(buildFlags, instrumentationTestTarget) { stage('Build') { sh "chmod +x gradlew" - sh "./gradlew assemble ${abiFilter} --stacktrace" + sh "./gradlew assemble ${buildFlags} --stacktrace" } stage('Tests') { parallel 'JVM' : { try { - sh "chmod +x gradlew && ./gradlew check ${abiFilter} --stacktrace" + sh "chmod +x gradlew && ./gradlew check ${buildFlags} --stacktrace" } finally { storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' @@ -241,7 +241,7 @@ def runBuild(abiFilter, instrumentationTestTarget) { }, 'Static code analysis' : { try { - gradle('realm', "spotbugsMain pmd checkstyle ${abiFilter}") + gradle('realm', "spotbugsMain pmd checkstyle ${buildFlags}") } finally { publishHTML(target: [ allowMissing: false, @@ -277,7 +277,7 @@ def runBuild(abiFilter, instrumentationTestTarget) { try { backgroundPid = startLogCatCollector() forwardAdbPorts() - gradle('realm', "${instrumentationTestTarget} ${abiFilter}") + gradle('realm', "${instrumentationTestTarget} ${buildFlags}") } finally { stopLogCatCollector(backgroundPid) storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' @@ -295,7 +295,7 @@ def runBuild(abiFilter, instrumentationTestTarget) { } }, 'JavaDoc': { - sh "./gradlew javadoc ${abiFilter} --stacktrace" + sh "./gradlew javadoc ${buildFlags} --stacktrace" } } diff --git a/README.md b/README.md index 96089aec8a..e2d01e79fe 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ In case you don't want to use the precompiled version, you can build Realm yours * Download the [**JDK 8**](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) from Oracle and install it. * The latest stable version of Android Studio. Currently [3.6.2](https://developer.android.com/studio/). * Download & install the Android SDK **Build-Tools 28.0.3**, **Android Pie (API 28)** (for example through Android Studio’s **Android SDK Manager**). - * Install CMake from SDK manager in Android Studio ("SDK Tools" -> "CMake"). + * Install CMake version 3.18.2 and build Ninja. * Install the NDK (currently r21) from the SDK Manager in Android Studio or using the [website](https://developer.android.com/ndk/downloads). If downloaded You may unzip the file wherever you choose. For macOS, a suggested location is `~/Library`. The download will unzip as the directory `android-ndk-r21`. diff --git a/build.gradle b/build.gradle index 09d29884d3..06d28fa46e 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,9 @@ def copyProperties = { if (project.hasProperty('s3cfg')) { startParameter.projectProperties += [s3cfg: project.getProperty('s3cfg')] } + if (project.hasProperty('disableLTO')) { + startParameter.projectProperties += [disableLTO: project.getProperty('disableLTO')] + } } task assembleAnnotations(type:GradleBuild) { diff --git a/dependencies.list b/dependencies.list index 9b809b27df..32cc9bb4b9 100644 --- a/dependencies.list +++ b/dependencies.list @@ -5,7 +5,7 @@ REALM_SYNC_SHA256=a074550f573b5b9f35d1efe84eef0145f2181f1c096a76ff559e7103091bae # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER=2020-10-03 +MONGODB_REALM_SERVER=2020-09-21 # Common Android settings across projects GRADLE_BUILD_TOOLS=4.0.0 @@ -18,6 +18,7 @@ gradle=6.5 ndkVersion=21.0.6113669 BUILD_INFO_EXTRACTOR_GRADLE=4.17.0 GRADLE_BINTRAY_PLUGIN=1.8.5 +CMAKE=3.18.4 # Bson dependency version BSON_DEPENDENCY=3.12.1 diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index b7764a6bd6..585b2e4ba2 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -16,26 +16,8 @@ apply plugin: 'net.ltgt.errorprone' def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) -ext.coreVersion = properties.getProperty('REALM_SYNC') -// empty or comment out this to disable hash checking -ext.coreSha256Hash = properties.getProperty('REALM_SYNC_SHA256') -ext.forceDownloadCore = project.hasProperty('forceDownloadCore') ? project.getProperty('forceDownloadCore').toBoolean() : false - -// Set the core source code path. By setting this, the core will be built from source. And coreVersion will be read from -// core source code. -ext.coreSourcePath = project.hasProperty('coreSourcePath') ? file(project.getProperty('coreSourcePath')) : null -// The location of pre-compiled Realm Core/Sync archive. -ext.coreArchiveDir = System.getenv("REALM_CORE_DOWNLOAD_DIR") -if (!ext.coreArchiveDir) { - ext.coreArchiveDir = ".." -} -ext.coreArchiveFile = rootProject.file("${ext.coreArchiveDir}/realm-sync-android-${project.coreVersion}.tar.gz") -ext.coreDistributionDir = file("${projectDir}/distribution/realm-core/") -ext.coreDir = file("${project.coreDistributionDir.getAbsolutePath()}/core-${project.coreVersion}") ext.ccachePath = project.findProperty('ccachePath') ?: System.getenv('NDK_CCACHE') ext.lcachePath = project.findProperty('lcachePath') ?: System.getenv('NDK_LCACHE') -// Set to true to enable linking with debug core. -ext.enableDebugCore = project.hasProperty('enableDebugCore') ? project.getProperty('enableDebugCore') : true android { compileSdkVersion rootProject.compileSdkVersion @@ -50,11 +32,8 @@ android { multiDexEnabled true externalNativeBuild { cmake { - arguments "-DREALM_CORE_DIST_DIR:STRING=${project.coreDir.getAbsolutePath()}", - "-DENABLE_DEBUG_CORE=$project.enableDebugCore" if (project.ccachePath) arguments "-DNDK_CCACHE=$project.ccachePath" if (project.lcachePath) arguments "-DNDK_LCACHE=$project.lcachePath" - if (project.coreSourcePath) arguments "-DCORE_SOURCE_PATH=${project.coreSourcePath.getAbsolutePath()}" if (project.hasProperty('buildTargetABIs') && !project.getProperty('buildTargetABIs').trim().isEmpty()) { abiFilters(*project.getProperty('buildTargetABIs').trim().split('\\s*,\\s*')) } else { @@ -79,6 +58,7 @@ android { externalNativeBuild { cmake { + version=properties.getProperty('CMAKE') path 'src/main/cpp/CMakeLists.txt' } } @@ -92,6 +72,14 @@ android { } release { + externalNativeBuild { + cmake { + // LTO is enabled for release builds unless specifically opted out of. + if (!project.hasProperty("disableLTO")) { + arguments "-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON" + } + } + } // minifyEnabled = true; } } @@ -153,15 +141,6 @@ android { proguardFiles 'proguard-rules-build-common.pro', 'proguard-rules-build-objectServer.pro' } } - - variantFilter { variant -> - def names = variant.flavors*.name - - // Ignore the objectServer flavour when building from core source. - if (coreSourcePath && names.contains("objectServer")) { - variant.ignore = true - } - } } @@ -469,111 +448,7 @@ artifacts { archives sourcesJar } - -def coreDownloaded = false - -task downloadCore() { - group = 'build setup' - description = 'Download the latest version of Realm Core' - def isHashCheckingEnabled = { - return project.hasProperty('coreSha256Hash') && !project.coreSha256Hash.empty - } - - def calcSha256Hash = { File targetFile -> - MessageDigest sha = MessageDigest.getInstance("SHA-256") - Formatter hexHash = new Formatter() - sha.digest(targetFile.bytes).each { b -> hexHash.format('%02x', b) } - return hexHash.toString() - } - - def shouldDownloadCore = { - if (coreSourcePath) { - println "shouldDownloadCore: skipping, using local Core at: ${coreSourcePath}." - return false - } - - if (!project.coreArchiveFile.exists()) { - return true - } - if (project.forceDownloadCore) { - return true - } - if (!isHashCheckingEnabled()) { - println "shouldDownloadCore: skipping hash check(empty \'coreSha256Hash\')." - return false - } - - def calculatedHash = calcSha256Hash(project.coreArchiveFile) - if (project.coreSha256Hash.equalsIgnoreCase(calculatedHash)) { - return false - } - - println "Existing archive hash mismatch (Expected: ${project.coreSha256Hash.toLowerCase()}" + - " but got ${calculatedHash.toLowerCase()}). Download new version." - return true - } - - doLast { - if (shouldDownloadCore()) { - download { - src "http://static.realm.io/downloads/sync/realm-sync-android-${project.coreVersion}.tar.gz" - dest project.coreArchiveFile - onlyIfNewer false - } - coreDownloaded = true - - if (isHashCheckingEnabled()) { - def calculatedHash = calcSha256Hash(project.coreArchiveFile) - if (!project.coreSha256Hash.equalsIgnoreCase(calculatedHash)) { - throw new GradleException("Invalid checksum for file '" + - "${project.coreArchiveFile.getName()}'. Expected " + - "${project.coreSha256Hash.toLowerCase()} but got " + - "${calculatedHash.toLowerCase()}.") - } - } else { - println 'Skipping hash check (empty \'coreSha256Hash\').' - } - } - } -} - -task deployCore(group: 'build setup', description: 'Deploy the latest version of Realm Core') { - dependsOn { - downloadCore - } - - // Build with the output from core source dir. No need to deploy anything. - onlyIf { - return !coreSourcePath - } - - outputs.upToDateWhen { - // Clean up the coreDir if it is newly downloaded - if (coreDownloaded) { - return false - } - - return project.coreDir.exists() - } - - doLast { - // Delete all files to avoid multiple copies of the same header file in Android Studio. - exec { - commandLine = [ - 'rm', - '-rf', - project.coreDistributionDir.getAbsolutePath() - ] - } - copy { - from tarTree(project.coreArchiveFile) - into project.coreDir - } - } -} - publishToMavenLocal.dependsOn assemble -preBuild.dependsOn deployCore if (project.hasProperty('dontCleanJniFiles')) { project.afterEvaluate { diff --git a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake deleted file mode 100644 index 6014163b4f..0000000000 --- a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake +++ /dev/null @@ -1,130 +0,0 @@ -########################################################################### -# -# Copyright 2017 Realm Inc. -# -# 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(ExternalProject) - -function(build_existing_realm_core core_source_path) - if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(debug_lib_suffix "-dbg") - add_compile_options(-DREALM_DEBUG) - else() - add_compile_options(-DNDEBUG) - endif() - - # We mirror relevant flags from this script - # https://github.com/realm/realm-core/blob/master/tools/cross_compile.sh#L68 - ExternalProject_Add(realm-core - SOURCE_DIR ${core_source_path} - PREFIX ${core_source_path}/build-android-${ANDROID_ABI}-${CMAKE_BUILD_TYPE} - CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} - -DANDROID_ABI=${ANDROID_ABI} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DREALM_BUILD_LIB_ONLY=YES - -DREALM_ENABLE_ENCRYPTION=1 - INSTALL_COMMAND "" - LOG_CONFIGURE 1 - LOG_BUILD 1 - ) - - ExternalProject_Get_Property(realm-core SOURCE_DIR) - ExternalProject_Get_Property(realm-core BINARY_DIR) - - # Create directories that are included in INTERFACE_INCLUDE_DIRECTORIES, as CMake requires they exist at - # configure time, when they'd otherwise not be created until we download and extract core. - file(MAKE_DIRECTORY "${BINARY_DIR}/src") - - set(core_lib_file "${BINARY_DIR}/src/realm/librealm${debug_lib_suffix}.a") - add_library(lib_realm_core STATIC IMPORTED) - set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_file} - IMPORTED_LINK_INTERFACE_LIBRARIES atomic - INTERFACE_INCLUDE_DIRECTORIES "${SOURCE_DIR}/src;${BINARY_DIR}/src") - - ExternalProject_Add_Step(realm-core ensure-libraries - DEPENDEES build - BYPRODUCTS ${core_lib_file} - ) - - add_dependencies(lib_realm_core realm-core) -endfunction() - -# Add the sync released as the library. -function(use_sync_release enable_sync sync_dist_path) - # Link to core/sync debug lib for debug build if it is debug build and linking with debug core is enabled. - if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND ${ENABLE_DEBUG_CORE}) - set(debug_lib_suffix "-dbg") - add_compile_options(-DREALM_DEBUG) - else() - add_compile_options(-DNDEBUG) - endif() - - # Configure import realm core lib - set(core_lib_path ${sync_dist_path}/librealm-android-${ANDROID_ABI}${debug_lib_suffix}.a) - if (NOT EXISTS ${core_lib_path}) - if (ARMEABI) - set(core_lib_path ${sync_dist_path}/librealm-android-arm${debug_lib_suffix}.a) - elseif (ARMEABI_V7A) - set(core_lib_path ${sync_dist_path}/librealm-android-arm-v7a${debug_lib_suffix}.a) - elseif (ARM64_V8A) - set(core_lib_path ${sync_dist_path}/librealm-android-arm64${debug_lib_suffix}.a) - else() - message(FATAL_ERROR "Cannot find core lib file: ${core_lib_path}") - endif() - endif() - - add_library(lib_realm_core STATIC IMPORTED) - - # -latomic is not set by default for mips and armv5. - # See https://code.google.com/p/android/issues/detail?id=182094 - list(APPEND LIB_INCLUDE_DIRS "${sync_dist_path}/include") - list(APPEND LIB_INCLUDE_DIRS "${sync_dist_path}/include/realm") - set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_path} - IMPORTED_LINK_INTERFACE_LIBRARIES atomic - INTERFACE_INCLUDE_DIRECTORIES "${LIB_INCLUDE_DIRS}") - - if (enable_sync) - # Sync static library - set(sync_lib_path ${sync_dist_path}/librealm-sync-android-${ANDROID_ABI}${debug_lib_suffix}.a) - # Workaround for old core's funny ABI nicknames - if (NOT EXISTS ${sync_lib_path}) - if (ARMEABI) - set(sync_lib_path ${sync_dist_path}/librealm-sync-android-arm${debug_lib_suffix}.a) - elseif (ARMEABI_V7A) - set(sync_lib_path ${sync_dist_path}/librealm-sync-android-arm-v7a${debug_lib_suffix}.a) - elseif (ARM64_V8A) - set(sync_lib_path ${sync_dist_path}/librealm-sync-android-arm64${debug_lib_suffix}.a) - else() - message(FATAL_ERROR "Cannot find sync lib file: ${sync_lib_path}") - endif() - endif() - add_library(lib_realm_sync STATIC IMPORTED) - set_target_properties(lib_realm_sync PROPERTIES IMPORTED_LOCATION ${sync_lib_path} - IMPORTED_LINK_INTERFACE_LIBRARIES lib_realm_core) - endif() - - set(REALM_CORE_INCLUDE_DIR "${sync_dist_path}/include") -endfunction() - -# Add core/sync libraries. Set the core_source_path to build core from source. -# FIXME: Build from sync source is not supported yet. -function(use_realm_core enable_sync sync_dist_path core_source_path) - if (core_source_path) - message("Building Realm Core from local source in ${core_source_path}.") - build_existing_realm_core(${core_source_path}) - else() - use_sync_release(${enable_sync} ${sync_dist_path}) - endif() -endfunction() diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index ebf8cd36a5..add07a597e 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -15,7 +15,8 @@ # limitations under the License. # ########################################################################### -cmake_minimum_required(VERSION 3.6.0) +cmake_minimum_required(VERSION 3.15.0) +project(RealmJava) # loading dependencies properties file(STRINGS "${CMAKE_SOURCE_DIR}/../../../../../dependencies.list" DEPENDENCIES) @@ -36,8 +37,6 @@ FUNCTION(capitalizeFirstLetter var value) set(${var} "${value}" PARENT_SCOPE) ENDFUNCTION(capitalizeFirstLetter) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake") - # find javah find_package(Java COMPONENTS Development) if (NOT Java_Development_FOUND) @@ -64,11 +63,11 @@ if(NDK_LCACHE) set(CMAKE_CXX_CREATE_SHARED_LIBRARY "${NDK_LCACHE} ${CMAKE_CXX_CREATE_SHARED_LIBRARY}") endif() -# Set flag build_SYNC +# Set flag REALM_ENABLE_SYNC if (REALM_FLAVOR STREQUAL base) - set(build_SYNC OFF) + set(REALM_ENABLE_SYNC OFF) else() - set(build_SYNC ON) + set(REALM_ENABLE_SYNC ON) endif() # Format strings used to represent build parameters: Variant and Type @@ -100,7 +99,7 @@ set(classes_LIST # /./ is the workaround for the problem that AS cannot find the jni headers. # See https://github.com/googlesamples/android-ndk/issues/319 set(jni_headers_PATH /./${PROJECT_BINARY_DIR}/jni_include) -if (build_SYNC) +if (REALM_ENABLE_SYNC) list(APPEND classes_LIST io.realm.mongodb.App io.realm.mongodb.ApiKeyAuthImpl @@ -131,32 +130,19 @@ create_javah(TARGET jni_headers DEPENDS ${classes_PATH} ) -include(RealmCore) -use_realm_core(${build_SYNC} "${REALM_CORE_DIST_DIR}" "${CORE_SOURCE_PATH}") - -# Download OpenSSL lib -# FIXME Read the openssl version from core when the core/sync release has that information. -set(openssl_VERSION "1.1.1b") -set(openssl_FILENAME "openssl.tgz") -set(openssl_URL "https://static.realm.io/downloads/openssl/${openssl_VERSION}/Android/${ANDROID_ABI}/${openssl_FILENAME}") - -message(STATUS "Downloading OpenSSL...") -file(DOWNLOAD "${openssl_URL}" "${PROJECT_BINARY_DIR}/${openssl_FILENAME}") - -message(STATUS "Uncompressing OpenSSL: ${PROJECT_BINARY_DIR}/${openssl_FILENAME}") -execute_process(COMMAND ${CMAKE_COMMAND} -E tar xfz "${openssl_FILENAME}" WORKING_DIRECTORY "${PROJECT_BINARY_DIR}") -message(STATUS "Importing OpenSSL...") -include(${PROJECT_BINARY_DIR}/lib/cmake/OpenSSL/OpenSSLConfig.cmake) -get_target_property(openssl_include_DIR OpenSSL::Crypto INTERFACE_INCLUDE_DIRECTORIES) -get_target_property(crypto_LIB OpenSSL::Crypto IMPORTED_LOCATION) -get_target_property(ssl_LIB OpenSSL::SSL IMPORTED_LOCATION) +option(REALM_JAVA_BUILD_CORE_FROM_SOURCE "Build Realm Core from source, as opposed to downloading prebuilt binaries" ON) +if(REALM_JAVA_BUILD_CORE_FROM_SOURCE) + set(REALM_BUILD_LIB_ONLY ON) + add_subdirectory(realm-core) +else() + message(FATAL_ERROR "TODO: Implement downloading monorepo release artifacts") +endif() # build application's shared lib include_directories( - ${CMAKE_SOURCE_DIR} - ${jni_headers_PATH} - ${CMAKE_SOURCE_DIR}/object-store/src - ${CMAKE_SOURCE_DIR}/object-store/external/json) + ${CMAKE_SOURCE_DIR} + ${jni_headers_PATH} +) # Hack the memmove bug on Samsung device. if (ARMEABI OR ARMEABI_V7A) @@ -176,10 +162,10 @@ set(WARNING_CXX_FLAGS "-Werror -Wall -Wextra -pedantic -Wmissing-declarations \ -Wno-missing-field-initializers -Wno-unevaluated-expression -Wno-unreachable-code \ -Wno-c99-extensions") set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ANDROID -DREALM_HAVE_CONFIG -DPIC -fdata-sections -pthread -frtti -fvisibility=hidden -fsigned-char -fno-stack-protector -std=c++17") -if (build_SYNC) +if (REALM_ENABLE_SYNC) set(REALM_COMMON_CXX_FLAGS "${REALM_COMMON_CXX_FLAGS} -DREALM_ENABLE_SYNC=1") endif() -set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Oz") +set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O2") # -ggdb doesn't play well with -flto set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -glldb -g") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${REALM_COMMON_CXX_FLAGS} ${WARNING_CXX_FLAGS} ${ABI_CXX_FLAGS}") @@ -188,7 +174,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${REALM_COMMON_CXX_FLAGS} ${WARNING_CXX_ if (CMAKE_BUILD_TYPE STREQUAL "Release") set(REALM_LINKER_FLAGS "${REALM_LINKER_FLAGS} -Wl,-gc-sections") endif() -if (build_SYNC) +if (REALM_ENABLE_SYNC) set(REALM_LINKER_FLAGS "${REALM_LINKER_FLAGS} -lz") endif() set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${REALM_LINKER_FLAGS}") @@ -200,7 +186,7 @@ file(GLOB jni_SRC "jni_impl/android_logger.cpp" ) # Those source file are only needed for sync. -if (NOT build_SYNC) +if (NOT REALM_ENABLE_SYNC) list(REMOVE_ITEM jni_SRC ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_User.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io_realm_mongodb_FunctionsImpl.cpp @@ -225,34 +211,10 @@ if (NOT build_SYNC) ) endif() -# Object Store source files -file(GLOB objectstore_SRC - "object-store/src/*.cpp" - "object-store/src/impl/*.cpp" - "object-store/src/impl/epoll/*.cpp" - "object-store/src/util/*.cpp" - "object-store/src/impl/epoll/*.cpp" - "object-store/src/util/android/*.cpp") - -# Sync needed Object Store files -if (build_SYNC) - file(GLOB objectstore_sync_SRC - "object-store/src/results.cpp" - "object-store/src/impl/results_notifier.cpp" - "object-store/src/sync/*.cpp" - "object-store/src/util/bson/*.cpp" - "object-store/src/sync/impl/*.cpp") -endif() - -add_library(realm-jni SHARED ${jni_SRC} ${objectstore_SRC} ${objectstore_sync_SRC}) +add_library(realm-jni SHARED ${jni_SRC}) +target_link_libraries(realm-jni log android Realm::ObjectStore) add_dependencies(realm-jni jni_headers) -if (build_SYNC) - target_link_libraries(realm-jni log android lib_realm_sync OpenSSL::SSL OpenSSL::Crypto) -else() - target_link_libraries(realm-jni log android lib_realm_core OpenSSL::Crypto) -endif() - # Strip the release so files and backup the unstripped versions if (CMAKE_BUILD_TYPE STREQUAL "Release") set(unstripped_SO_DIR diff --git a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp index b2754775a9..dcd77ccaf2 100644 --- a/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_RealmQuery.cpp @@ -16,8 +16,8 @@ #include "io_realm_RealmQuery.h" -#include -#include +#include +#include #include "util.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp index 7048bc12e6..eed33dec4d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsCollectionChangeSet.cpp @@ -16,7 +16,7 @@ #include "io_realm_internal_OsCollectionChangeSet.h" -#include +#include #include "util.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp index f2023f6563..e12b5fa6f1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp @@ -16,9 +16,9 @@ #include "io_realm_internal_OsList.h" -#include -#include -#include +#include +#include +#include #include "observable_collection_wrapper.hpp" #include "java_accessor.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 699ebd3d85..8582f220f7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -20,9 +20,9 @@ #include #endif -#include -#include -#include +#include +#include +#include #include "util.hpp" #include "java_class_global_def.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp index 5835ced274..d108b2da85 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectSchemaInfo.cpp @@ -18,8 +18,8 @@ #include -#include -#include +#include +#include #include "java_accessor.hpp" #include "java_exception_def.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp index 79850f64e6..10b50a53ad 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp @@ -16,8 +16,8 @@ #include "io_realm_internal_OsObjectStore.h" -#include -#include +#include +#include #include "util.hpp" #include "jni_util/java_method.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp index 37993bacff..4e70ae27a3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsRealmConfig.cpp @@ -16,17 +16,18 @@ #include "io_realm_internal_OsRealmConfig.h" -#include +#include #if REALM_ENABLE_SYNC -#include -#include -#include -#include +#include +#include +#include +#include #include #endif #include #include +#include #include "java_accessor.hpp" #include "util.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index 8873d832e3..0515a14158 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -16,9 +16,9 @@ #include "io_realm_internal_OsResults.h" -#include -#include -#include +#include +#include +#include #include #include "java_class_global_def.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp index 8675e9e18a..5827402777 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSchemaInfo.cpp @@ -16,9 +16,9 @@ #include "io_realm_internal_OsSchemaInfo.h" -#include -#include -#include +#include +#include +#include #include "java_accessor.hpp" #include "java_exception_def.hpp" #include "util.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp index 7cac296a38..d44892858d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp @@ -16,22 +16,23 @@ #include "io_realm_internal_OsSharedRealm.h" #if REALM_ENABLE_SYNC -#include "object-store/src/sync/sync_manager.hpp" -#include "object-store/src/sync/sync_config.hpp" -#include "object-store/src/sync/sync_session.hpp" -#include "object-store/src/results.hpp" +#include +#include +#include +#include +#include #include "observable_collection_wrapper.hpp" #endif #include -#include +#include #include "java_accessor.hpp" #include "java_binding_context.hpp" #include "java_exception_def.hpp" -#include "object_store.hpp" +#include #include "util.hpp" #include "jni_util/java_method.hpp" #include "jni_util/java_class.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp index ada73fef66..f76eab7ef1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp @@ -16,8 +16,8 @@ #include "io_realm_internal_Property.h" -#include -#include +#include +#include #include "util.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 6f4e168a87..75e31608b4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -22,7 +22,7 @@ #include "java_accessor.hpp" #include "java_exception_def.hpp" -#include "shared_realm.hpp" +#include #include "jni_util/java_exception_thrower.hpp" #include @@ -96,9 +96,20 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeAddColumnLink(JNIEnv* return 0; } try { - JStringAccessor name2(env, name); // throws + JStringAccessor name_accessor(env, name); // throws TableRef table = TBL_REF(nativeTableRefPtr); - return static_cast(table->add_column_link(DataType(colType), name2, *targetTableRef).value); + auto data_type = DataType(colType); + + if (REALM_UNLIKELY(!Table::is_link_type(ColumnType(data_type)))) + throw LogicError(LogicError::illegal_type); + + if (data_type == type_LinkList) { + return static_cast(table->add_column_list(*targetTableRef, name_accessor).value); + } + else { + REALM_ASSERT(data_type == type_Link); + return static_cast(table->add_column(*targetTableRef, name_accessor).value); + } } CATCH_STD() return 0; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index f315099bb2..7bca0a4c02 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -20,9 +20,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "java_accessor.hpp" #include "java_class_global_def.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp index 010e8c8eef..4ab4a28049 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_core_IncludeDescriptor.cpp @@ -18,11 +18,11 @@ #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "java_accessor.hpp" #include "java_query_descriptor.hpp" diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp index b792f3a263..1899ddc9b9 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsApp.cpp @@ -21,9 +21,9 @@ #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" -#include -#include -#include +#include +#include +#include #include diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp index 22daee9a22..ff77c8d738 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp @@ -19,7 +19,7 @@ #include "util.hpp" #include -#include +#include using namespace realm; using namespace realm::app; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp index e69eae0b86..f125e32fa1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAsyncOpenTask.cpp @@ -17,15 +17,14 @@ #include "io_realm_internal_objectstore_OsAsyncOpenTask.h" #include "util.hpp" -#include "thread_safe_reference.hpp" +#include #include "jni_util/java_method.hpp" #include "jni_util/java_class.hpp" #include "jni_util/jni_utils.hpp" -#include "object-store/src/sync/async_open_task.hpp" -#include "object-store/src/sync/sync_config.hpp" +#include +#include -#include -#include +#include #include using namespace realm; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp index dc5876a0cb..8c5576beb3 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoClient.cpp @@ -23,10 +23,10 @@ #include "jni_util/jni_utils.hpp" #include -#include -#include -#include -#include +#include +#include +#include +#include using namespace realm; using namespace realm::app; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp index e7597399ae..b171a7627a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoCollection.cpp @@ -22,12 +22,12 @@ #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" #include "jni_util/bson_util.hpp" -#include "object-store/src/util/bson/bson.hpp" +#include #include -#include -#include -#include +#include +#include +#include #include using namespace realm; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp index 51626da135..645a21b828 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsMongoDatabase.cpp @@ -23,10 +23,10 @@ #include "jni_util/jni_utils.hpp" #include -#include -#include -#include -#include +#include +#include +#include +#include using namespace realm; using namespace realm::app; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp index b718ebbd8e..5689d70b4a 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsPush.cpp @@ -23,7 +23,7 @@ #include "jni_util/jni_utils.hpp" #include -#include +#include #include using namespace realm; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp index 942f401717..0b332cfa2f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsSyncUser.cpp @@ -21,9 +21,9 @@ #include "jni_util/java_class.hpp" #include "java_network_transport.hpp" -#include +#include +#include #include -#include using namespace realm; using namespace realm::_impl; diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp index 735175d84f..4609244092 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsWatchStream.cpp @@ -19,7 +19,7 @@ #include "java_class_global_def.hpp" #include "jni_util/bson_util.hpp" -#include +#include using namespace realm; using namespace realm::app; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_ApiKeyAuthImpl.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_ApiKeyAuthImpl.cpp index 4122558592..68790aa0bf 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_ApiKeyAuthImpl.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_ApiKeyAuthImpl.cpp @@ -21,10 +21,9 @@ #include "util.hpp" #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" -#include "object-store/src/sync/app.hpp" #include -#include +#include using namespace realm; using namespace realm::app; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp index c193355f0f..8af10fe2b5 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_EmailPasswordAuthImpl.cpp @@ -22,7 +22,7 @@ #include "jni_util/jni_utils.hpp" #include "jni_util/bson_util.hpp" -#include +#include using namespace realm; using namespace realm::app; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp index 7d4ba9a834..c27de9d41d 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_FunctionsImpl.cpp @@ -19,7 +19,7 @@ #include "util.hpp" #include "jni_util/bson_util.hpp" #include "java_network_transport.hpp" -#include "object-store/src/sync/app.hpp" +#include using namespace realm; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp index 3729dbc756..95cd3618d4 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_User.cpp @@ -21,7 +21,7 @@ #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" -#include +#include #include using namespace realm; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp index cd9144e696..f33ef60a3f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_AggregateIterable.cpp @@ -22,10 +22,10 @@ #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" #include "jni_util/bson_util.hpp" -#include "object-store/src/util/bson/bson.hpp" #include -#include +#include +#include #include #include diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp index 1507682960..3fee3c8805 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_mongo_iterable_FindIterable.cpp @@ -22,10 +22,10 @@ #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" #include "jni_util/bson_util.hpp" -#include "object-store/src/util/bson/bson.hpp" #include -#include +#include +#include #include using namespace realm; diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp index 9f55fddc7b..fc3b3b068e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_ClientResetRequiredError.cpp @@ -16,8 +16,8 @@ #include -#include -#include +#include +#include #include "util.hpp" #include "io_realm_mongodb_sync_ClientResetRequiredError.h" diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp index 8fb690f522..e0d325ada1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp @@ -16,11 +16,12 @@ #include "io_realm_mongodb_sync_Sync.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include "util.hpp" #include diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp index 42a647e688..61c2931a7b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp @@ -19,9 +19,9 @@ #include "io_realm_mongodb_sync_SyncSession.h" -#include "sync/app.hpp" -#include "sync/sync_manager.hpp" -#include "sync/sync_session.hpp" +#include +#include +#include #include "util.hpp" #include "java_class_global_def.hpp" diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index 83eed70bd7..f164cf393e 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -28,8 +28,7 @@ #include #include -#include -#include +#include #include "java_class_global_def.hpp" #include "java_exception_def.hpp" diff --git a/realm/realm-library/src/main/cpp/java_binding_context.hpp b/realm/realm-library/src/main/cpp/java_binding_context.hpp index eeff8523b4..f8270c3f19 100644 --- a/realm/realm-library/src/main/cpp/java_binding_context.hpp +++ b/realm/realm-library/src/main/cpp/java_binding_context.hpp @@ -20,7 +20,7 @@ #include #include -#include "binding_context.hpp" +#include #include "jni_util/java_global_weak_ref.hpp" diff --git a/realm/realm-library/src/main/cpp/java_network_transport.hpp b/realm/realm-library/src/main/cpp/java_network_transport.hpp index 23ffdbb55c..4c44955105 100644 --- a/realm/realm-library/src/main/cpp/java_network_transport.hpp +++ b/realm/realm-library/src/main/cpp/java_network_transport.hpp @@ -19,7 +19,7 @@ #include "java_accessor.hpp" #include "util.hpp" -#include "sync/generic_network_transport.hpp" +#include #include "jni_util/java_class.hpp" #include "jni_util/java_method.hpp" #include "jni_util/jni_utils.hpp" diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp index cd761f607a..16af02f49d 100644 --- a/realm/realm-library/src/main/cpp/java_object_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -25,8 +25,8 @@ #include "java_accessor.hpp" #include "java_class_global_def.hpp" -#include "object_accessor.hpp" -#include "object-store/src/property.hpp" +#include +#include #include #include diff --git a/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp b/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp index eb5377d7a7..fb0d822903 100644 --- a/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/bson_util.hpp @@ -18,7 +18,7 @@ #define REALM_BSON_UTIL_HPP #include -#include +#include namespace realm { namespace jni_util { diff --git a/realm/realm-library/src/main/cpp/jni_util/log.hpp b/realm/realm-library/src/main/cpp/jni_util/log.hpp index 3b694571d6..c38db7c94a 100644 --- a/realm/realm-library/src/main/cpp/jni_util/log.hpp +++ b/realm/realm-library/src/main/cpp/jni_util/log.hpp @@ -26,7 +26,7 @@ #include "io_realm_log_LogLevel.h" -#include "realm/util/logger.hpp" +#include namespace realm { diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store deleted file mode 160000 index fd246c54de..0000000000 --- a/realm/realm-library/src/main/cpp/object-store +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fd246c54de7d1fee6bcbeb3609de75a4eccd5b70 diff --git a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp index 0f6ff53b76..e5f8ebf919 100644 --- a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp +++ b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp @@ -22,7 +22,7 @@ #include "jni_util/java_method.hpp" #include "jni_util/log.hpp" -#include +#include #include namespace realm { diff --git a/realm/realm-library/src/main/cpp/realm-core b/realm/realm-library/src/main/cpp/realm-core new file mode 160000 index 0000000000..2bff29de34 --- /dev/null +++ b/realm/realm-library/src/main/cpp/realm-core @@ -0,0 +1 @@ +Subproject commit 2bff29de34b03f4bed448512d84ab0f6fd2c1d08 diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index bb4bf4e3ec..3c28045480 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -26,16 +26,16 @@ #include "util.hpp" #include "io_realm_internal_Util.h" #include "io_realm_internal_OsSharedRealm.h" -#include "shared_realm.hpp" -#include "results.hpp" -#include "list.hpp" -#include "java_exception_def.hpp" -#include "java_object_accessor.hpp" -#include "object.hpp" +#include +#include +#include +#include #if REALM_ENABLE_SYNC -#include "sync/app.hpp" +#include #endif +#include "java_exception_def.hpp" +#include "java_object_accessor.hpp" #include "jni_util/java_exception_thrower.hpp" using namespace std; From 9e9f4238f40b744645e58ab2289cd25ed9f663ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Wed, 11 Nov 2020 11:45:25 +0100 Subject: [PATCH 1742/2110] Add support for addPrimaryKey with ObjectId keys on migrations (#7193) --- CHANGELOG.md | 20 ++++++++++ .../java/io/realm/annotations/PrimaryKey.java | 4 +- .../java/io/realm/RealmMigrationTests.java | 37 +++++++++++++++++- .../io/realm/entities/ObjectIdPrimaryKey.java | 39 +++++++++++++++++++ .../cpp/io_realm_internal_OsObjectStore.cpp | 2 +- .../src/main/cpp/io_realm_internal_Table.cpp | 17 +++++--- 6 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/ObjectIdPrimaryKey.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 84aeebadc0..a08e69e01d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## 10.0.2 (YYYY-MM-DD) + +### Breaking Changes +* None. + +### Enhancements +* None. + +### Fixes +* Fixed crash when adding classes containing an `ObjectId` as primary key to the schema (Issue [#7189](https://github.com/realm/realm-java/issues/7189), since v10.0.0). + +### Compatibility +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 or above is required to open Realms created by this version. + +### Internal +* None. + + ## 10.0.1 (2020-11-06) ### Breaking Changes diff --git a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java index b654e69451..6790fa9216 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java +++ b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java @@ -31,8 +31,8 @@ * Primary keys also count as having the {@link Index} annotation. *

            * It is allowed to apply this annotation on the following primitive types: byte, short, int, and long. - * String, Byte, Short, Integer, and Long are also allowed, and further permitted to have {@code null} - * as a primary key value. + * String, Byte, Short, Integer, Long and ObjectId are also allowed, and further permitted to have + * {@code null} as a primary key value. *

            * This annotation is not allowed inside Realm classes marked as {@code \@RealmClass(embedded = true)}. *

            diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index bec7e59806..7e246a46f9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -17,9 +17,11 @@ package io.realm; import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; + import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import org.bson.types.ObjectId; import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; @@ -41,6 +43,7 @@ import io.realm.entities.Dog; import io.realm.entities.FieldOrder; import io.realm.entities.NullTypes; +import io.realm.entities.ObjectIdPrimaryKey; import io.realm.entities.PrimaryKeyAsBoxedByte; import io.realm.entities.PrimaryKeyAsBoxedInteger; import io.realm.entities.PrimaryKeyAsBoxedLong; @@ -1453,6 +1456,38 @@ public void core5AutomaticIndexOnStringPKShouldOpenInCore6() throws IOException assertEquals("Foo", first.name); } + @Test + public void migrateRealm_addPrimaryKey_objectId() { + // Creates v0 of the Realm. + RealmConfiguration originalConfig = configFactory.createConfigurationBuilder() + .schema(StringOnly.class) + .build(); + Realm.getInstance(originalConfig).close(); + + RealmMigration migration = new RealmMigration() { + @Override + public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { + RealmSchema schema = realm.getSchema(); + schema.create(ObjectIdPrimaryKey.CLASS_NAME) + .addField(ObjectIdPrimaryKey.PROPERTY_OBJECT_ID, ObjectId.class) + .addPrimaryKey(ObjectIdPrimaryKey.PROPERTY_OBJECT_ID); + } + }; + + // Creates v1 of the Realm. + RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() + .schemaVersion(1) + .schema(StringOnly.class, ObjectIdPrimaryKey.class) + .migration(migration) + .build(); + + realm = Realm.getInstance(realmConfig); + RealmObjectSchema schema = realm.getSchema().get(ObjectIdPrimaryKey.CLASS_NAME); + assertTrue(schema.hasPrimaryKey()); + assertFalse(schema.hasIndex(ObjectIdPrimaryKey.PROPERTY_OBJECT_ID)); + realm.close(); + } + // TODO Add unit tests for default nullability // TODO Add unit tests for default Indexing for Primary keys } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/ObjectIdPrimaryKey.java b/realm/realm-library/src/androidTest/java/io/realm/entities/ObjectIdPrimaryKey.java new file mode 100644 index 0000000000..f008ae6f7c --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/ObjectIdPrimaryKey.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import org.bson.types.ObjectId; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + +public class ObjectIdPrimaryKey extends RealmObject { + + public static final String CLASS_NAME = "ObjectIdPrimaryKey"; + public static final String PROPERTY_OBJECT_ID = "objectId"; + + @PrimaryKey + private ObjectId objectId; + + public ObjectId getObjectId() { + return objectId; + } + + public void setObjectId(ObjectId objectId) { + this.objectId = objectId; + } +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp index 79850f64e6..b2d3d3cd2c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp @@ -61,7 +61,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetPrimaryKeyF // Check valid column type auto field_type = table->get_column_type(pk_column_col); - if (field_type != type_Int && field_type != type_String) { + if (field_type != type_Int && field_type != type_String && field_type != type_ObjectId) { THROW_JAVA_EXCEPTION( env, JavaExceptionDef::IllegalArgument, format("Field '%1' is not a valid primary key type.", StringData(pk_field_name_accessor))); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 6f4e168a87..8acf09e59c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -40,13 +40,18 @@ static void finalize_table(jlong ptr); inline static bool is_allowed_to_index(JNIEnv* env, DataType column_type) { - if (!(column_type == type_String || column_type == type_Int || column_type == type_Bool || - column_type == type_Timestamp || column_type == type_OldDateTime)) { - ThrowException(env, IllegalArgument, "This field cannot be indexed - " - "Only String/byte/short/int/long/boolean/Date fields are supported."); - return false; + if (column_type == type_String + || column_type == type_Int + || column_type == type_Bool + || column_type == type_Timestamp + || column_type == type_OldDateTime + || column_type == type_ObjectId) { + return true; } - return true; + + ThrowException(env, IllegalArgument, "This field cannot be indexed - " + "Only String/byte/short/int/long/boolean/Date/ObjectId fields are supported."); + return false; } // Note: Don't modify spec on a table which has a shared_spec. From d80a23e709e5feded782358d771039f41208908b Mon Sep 17 00:00:00 2001 From: Brian Munkholm Date: Thu, 12 Nov 2020 06:53:42 +0100 Subject: [PATCH 1743/2110] Update license. Remove Realm Component license. (#7194) --- LICENSE | 46 +--------------------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/LICENSE b/LICENSE index 57a0e0b24a..e163ae2f84 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,7 @@ TABLE OF CONTENTS 1. Apache License version 2.0 -2. Realm Components -3. Export Compliance +2. Export Compliance 1. ------------------------------------------------------------------------------- @@ -183,49 +182,6 @@ TABLE OF CONTENTS 2. ------------------------------------------------------------------------------- -REALM COMPONENTS - -This software contains components with separate copyright and license terms. -Your use of these components is subject to the terms and conditions of the -following licenses. - -For the Realm Platform Extensions component - - Realm Platform Extensions License - - Copyright (c) 2011-2017 Realm Inc All rights reserved - - Redistribution and use in binary form, with or without modification, is - permitted provided that the following conditions are met: - - 1. You agree not to attempt to decompile, disassemble, reverse engineer or - otherwise discover the source code from which the binary code was derived. - You may, however, access and obtain a separate license for most of the - source code from which this Software was created, at - http://realm.io/pricing/. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - -3. ------------------------------------------------------------------------------- - EXPORT COMPLIANCE You understand that the Software may contain cryptographic functions that may be From 61011035c7536e311785d72dcb3deb85739e2647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Thu, 12 Nov 2020 19:18:11 +0100 Subject: [PATCH 1744/2110] Fix wrong logic in annotation processor for generating ObjectId PKs (#7198) --- CHANGELOG.md | 3 +- .../processor/RealmProxyClassGenerator.kt | 12 +++- .../realm/RealmJsonAbsentPrimaryKeyTests.java | 12 ++-- .../realm/RealmJsonNullPrimaryKeyTests.java | 35 ++++++----- .../java/io/realm/RealmJsonTests.java | 15 +++++ .../realm/entities/PrimaryKeyAsObjectId.java | 63 +++++++++++++++++++ 6 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsObjectId.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a08e69e01d..5983bf562b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ * None. ### Fixes -* Fixed crash when adding classes containing an `ObjectId` as primary key to the schema (Issue [#7189](https://github.com/realm/realm-java/issues/7189), since v10.0.0). +* Fixed crash when adding classes containing an `ObjectId` as primary key to the schema. (Issue [#7189](https://github.com/realm/realm-java/issues/7189), since v10.0.0) +* Fixed crash when creating proxy classes containing an `ObjectId` as primary key. (Issue [#7197](https://github.com/realm/realm-java/issues/7197), since v10.0.0) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index a49ae693a0..b9ff7e72a5 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -19,6 +19,7 @@ package io.realm.processor import com.squareup.javawriter.JavaWriter import io.realm.processor.ext.beginMethod import io.realm.processor.ext.beginType +import org.bson.types.ObjectId import java.io.BufferedWriter import java.io.IOException import java.util.* @@ -2105,6 +2106,13 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi findFirstCast = "(org.bson.types.ObjectId)" jsonAccessorMethodSuffix = "" } + val nullableMetadata = if (Utils.isObjectId(metadata.primaryKey)) { + "objKey = table.findFirst%s(pkColumnKey, new org.bson.types.ObjectId((String)json.get%s(\"%s\")))".format(pkType, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) + } else { + "objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))".format(pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) + } + val nonNullableMetadata = "objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))".format(pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) + emitStatement("%s obj = null", qualifiedJavaClassName) beginControlFlow("if (update)") emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName) @@ -2115,11 +2123,11 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) emitStatement("objKey = table.findFirstNull(pkColumnKey)") nextControlFlow("else") - emitStatement("objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))", pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) + emitStatement(nullableMetadata) endControlFlow() } else { beginControlFlow("if (!json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName) - emitStatement("objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))", pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) + emitStatement(nonNullableMetadata) endControlFlow() } beginControlFlow("if (objKey != Table.NO_MATCH)") diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java index 1e023847cd..aae0c1c0b4 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java @@ -36,6 +36,7 @@ import io.realm.entities.PrimaryKeyAsBoxedInteger; import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; +import io.realm.entities.PrimaryKeyAsObjectId; import io.realm.entities.PrimaryKeyAsString; import io.realm.rule.TestRealmConfigurationFactory; @@ -68,11 +69,12 @@ public void tearDown() { @Parameterized.Parameters public static Iterable data() { return Arrays.asList(new Object[][]{ - {PrimaryKeyAsBoxedByte.class, "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, - {PrimaryKeyAsBoxedShort.class, "{ \"name\":\"KeyValueTestIsFun\" }"}, - {PrimaryKeyAsBoxedInteger.class, "{ \"name\":\"FunValueTestIsKey\" }"}, - {PrimaryKeyAsBoxedLong.class, "{ \"name\":\"NameAsBoxedLong-!\" }"}, - {PrimaryKeyAsString.class, "{ \"id\":2429214 }"} + {PrimaryKeyAsBoxedByte.class, "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, + {PrimaryKeyAsBoxedShort.class, "{ \"name\":\"KeyValueTestIsFun\" }"}, + {PrimaryKeyAsBoxedInteger.class, "{ \"name\":\"FunValueTestIsKey\" }"}, + {PrimaryKeyAsBoxedLong.class, "{ \"name\":\"NameAsBoxedLong-!\" }"}, + {PrimaryKeyAsString.class, "{ \"id\":2429214 }"}, + {PrimaryKeyAsObjectId.class, "{ \"name\":\"789ABCDEF0123456789ABCDE\" }"} }); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java index 78d8c7f56e..9f8e7fb166 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java @@ -31,11 +31,13 @@ import io.realm.entities.PrimaryKeyAsBoxedInteger; import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; +import io.realm.entities.PrimaryKeyAsObjectId; import io.realm.entities.PrimaryKeyAsString; import io.realm.objectid.NullPrimaryKey; import io.realm.rule.TestRealmConfigurationFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; @RunWith(Parameterized.class) public class RealmJsonNullPrimaryKeyTests { @@ -61,11 +63,12 @@ public void tearDown() { @Parameterized.Parameters public static Iterable data() { return Arrays.asList(new Object[][]{ - {PrimaryKeyAsBoxedByte.class, "OhThisIsNullKey?!", "{ \"id\":null, \"name\":\"OhThisIsNullKey?!\" }"}, - {PrimaryKeyAsBoxedShort.class, "YouBetItIsNullKey", "{ \"id\":null, \"name\":\"YouBetItIsNullKey\" }"}, - {PrimaryKeyAsBoxedInteger.class, "Gosh Didnt KnowIt", "{ \"id\":null, \"name\":\"Gosh Didnt KnowIt\" }"}, - {PrimaryKeyAsBoxedLong.class, "?YOUNOWKNOWRIGHT?", "{ \"id\":null, \"name\":\"?YOUNOWKNOWRIGHT?\" }"}, - {PrimaryKeyAsString.class, "4299121", "{ \"name\":null, \"id\":4299121 }"}, + {PrimaryKeyAsBoxedByte.class, "OhThisIsNullKey?!", "{ \"id\":null, \"name\":\"OhThisIsNullKey?!\" }"}, + {PrimaryKeyAsBoxedShort.class, "YouBetItIsNullKey", "{ \"id\":null, \"name\":\"YouBetItIsNullKey\" }"}, + {PrimaryKeyAsBoxedInteger.class, "Gosh Didnt KnowIt", "{ \"id\":null, \"name\":\"Gosh Didnt KnowIt\" }"}, + {PrimaryKeyAsBoxedLong.class, "?YOUNOWKNOWRIGHT?", "{ \"id\":null, \"name\":\"?YOUNOWKNOWRIGHT?\" }"}, + {PrimaryKeyAsString.class, "4299121", "{ \"name\":null, \"id\":4299121 }"}, + {PrimaryKeyAsObjectId.class, "Samsquanch", "{ \"id\":null, \"name\":\"Samsquanch\" }"}, }); } @@ -91,14 +94,13 @@ public void createObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONE RealmResults results = realm.where(PrimaryKeyAsString.class).findAll(); assertEquals(1, results.size()); assertEquals(Long.valueOf(secondaryFieldValue).longValue(), results.first().getId()); - assertEquals(null, results.first().getName()); - - // PrimaryKeyAsNumber + assertNull(results.first().getName()); } else { + // Other types RealmResults results = realm.where(clazz).findAll(); assertEquals(1, results.size()); - assertEquals(null, ((NullPrimaryKey)results.first()).getId()); - assertEquals(secondaryFieldValue, ((NullPrimaryKey)results.first()).getName()); + assertNull(((NullPrimaryKey) results.first()).getId()); + assertEquals(secondaryFieldValue, ((NullPrimaryKey) results.first()).getName()); } } @@ -115,13 +117,12 @@ public void createOrUpdateObjectFromJson_primaryKey_isNull_fromJsonObject() thro assertEquals(1, results.size()); assertEquals(Long.valueOf(secondaryFieldValue).longValue(), results.first().getId()); assertEquals(null, results.first().getName()); - - // PrimaryKeyAsNumber } else { + // Other types RealmResults results = realm.where(clazz).findAll(); assertEquals(1, results.size()); - assertEquals(null, ((NullPrimaryKey)results.first()).getId()); - assertEquals(secondaryFieldValue, ((NullPrimaryKey)results.first()).getName()); + assertEquals(null, ((NullPrimaryKey) results.first()).getId()); + assertEquals(secondaryFieldValue, ((NullPrimaryKey) results.first()).getName()); } } @@ -139,12 +140,12 @@ public void createOrUpdateObjectFromJson_primaryKey_isNull_updateFromJsonObject( assertEquals(1, results.size()); assertEquals(Long.valueOf(secondaryFieldValue).longValue(), results.first().getId()); assertEquals(null, results.first().getName()); - // PrimaryKeyAsNumber } else { + // Other types RealmResults results = realm.where(clazz).findAll(); assertEquals(1, results.size()); - assertEquals(null, ((NullPrimaryKey)results.first()).getId()); - assertEquals(secondaryFieldValue, ((NullPrimaryKey)results.first()).getName()); + assertEquals(null, ((NullPrimaryKey) results.first()).getId()); + assertEquals(secondaryFieldValue, ((NullPrimaryKey) results.first()).getName()); } } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index 130a23561e..13d71fc6bc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -57,6 +57,7 @@ import io.realm.entities.NoPrimaryKeyNullTypes; import io.realm.entities.NullTypes; import io.realm.entities.OwnerPrimaryKey; +import io.realm.entities.PrimaryKeyAsObjectId; import io.realm.entities.PrimitiveListTypes; import io.realm.entities.RandomPrimaryKey; import io.realm.exceptions.RealmException; @@ -1559,6 +1560,20 @@ public void createOrUpdateAllFromJson_inputString() throws IOException { assertAllTypesPrimaryKeyUpdated(); } + @Test + public void createOrUpdateObjectFromJson_objectIdPK() throws JSONException { + String stringId = "789ABCDEF0123456789ABCDE"; + JSONObject jsonObject = new JSONObject("{\"id\": \""+ stringId + "\", \"name\": \"bar\"}"); + realm.beginTransaction(); + realm.createOrUpdateObjectFromJson(PrimaryKeyAsObjectId.class, jsonObject); + realm.commitTransaction(); + + RealmResults owners = realm.where(PrimaryKeyAsObjectId.class).findAll(); + assertEquals(1, owners.size()); + assertEquals(new ObjectId(stringId), owners.get(0).getId()); + assertEquals("bar", owners.get(0).getName()); + } + // Tests creating objects from Json, all nullable fields with null values or non-null values. @Test public void createAllFromJson_nullTypesJsonWithNulls() throws IOException, JSONException { diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsObjectId.java b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsObjectId.java new file mode 100644 index 0000000000..23c255bfa8 --- /dev/null +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/PrimaryKeyAsObjectId.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import org.bson.types.ObjectId; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; +import io.realm.objectid.NullPrimaryKey; + +public class PrimaryKeyAsObjectId extends RealmObject implements NullPrimaryKey { + + public static final String CLASS_NAME = "PrimaryKeyAsObjectId"; + public static final String FIELD_PRIMARY_KEY = "name"; + public static final String FIELD_ID = "id"; + + @PrimaryKey + private ObjectId id; + private String name; + + public PrimaryKeyAsObjectId() { + } + + public PrimaryKeyAsObjectId(ObjectId id, String name) { + this.id = id; + this.name = name; + } + + @Override + public ObjectId getId() { + return id; + } + + @Override + public void setId(ObjectId id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } +} From 0c43d47436880d9993a07548c567257efcc30cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Tue, 17 Nov 2020 12:36:37 +0100 Subject: [PATCH 1745/2110] Added FlowFactory interface and default implementation for coroutines flows (#7191) --- CHANGELOG.md | 3 +- .../kotlin/io/realm/CoroutineTests.kt | 558 -------- .../kotlin/io/realm/CoroutinesTests.kt | 1166 +++++++++++++++++ .../io/realm/kotlin/DynamicRealmExtensions.kt | 32 + .../kotlin/io/realm/kotlin/RealmExtensions.kt | 13 + .../io/realm/kotlin/RealmListExtensions.kt | 88 +- .../io/realm/kotlin/RealmObjectExtensions.kt | 161 ++- .../io/realm/kotlin/RealmResultsExtensions.kt | 92 +- realm/realm-library/build.gradle | 7 +- .../io/realm/RealmConfigurationTests.java | 131 +- .../mongodb/sync/SyncConfigurationTests.kt | 215 ++- .../io/realm/OrderedRealmCollectionImpl.java | 68 +- .../realm/OrderedRealmCollectionSnapshot.java | 8 +- .../java/io/realm/RealmConfiguration.java | 52 +- .../src/main/java/io/realm/RealmList.java | 93 +- .../src/main/java/io/realm/RealmObject.java | 2 + .../src/main/java/io/realm/RealmQuery.java | 8 +- .../src/main/java/io/realm/RealmResults.java | 87 +- .../java/io/realm/coroutines/FlowFactory.java | 224 ++++ .../io/realm/coroutines/RealmFlowFactory.java | 121 ++ .../src/main/java/io/realm/internal/Util.java | 22 +- .../coroutines/InternalFlowFactory.kt | 666 ++++++++++ .../realm/mongodb/sync/SyncConfiguration.java | 33 +- version.txt | 2 +- 24 files changed, 2981 insertions(+), 871 deletions(-) delete mode 100644 realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt create mode 100644 realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutinesTests.kt create mode 100644 realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/DynamicRealmExtensions.kt create mode 100644 realm/realm-library/src/main/java/io/realm/coroutines/FlowFactory.java create mode 100644 realm/realm-library/src/main/java/io/realm/coroutines/RealmFlowFactory.java create mode 100644 realm/realm-library/src/main/java/io/realm/internal/coroutines/InternalFlowFactory.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 5983bf562b..89cef51ac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ * None. ### Enhancements -* None. +* Added `FlowFactory` interface that allows customization of `Flow` emissions, just as we do with `RxObservableFactory`. A default implementation, `RealmFlowFactory`, is provided when building `RealmConfiguration`s. +* Added `toChangeSetFlow` methods (similar to the Rx `asChangesetFlowable` methods) for `RealmObject`, `RealmResults` and `RealmList`. ### Fixes * Fixed crash when adding classes containing an `ObjectId` as primary key to the schema. (Issue [#7189](https://github.com/realm/realm-java/issues/7189), since v10.0.0) diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt deleted file mode 100644 index 002358ccc7..0000000000 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutineTests.kt +++ /dev/null @@ -1,558 +0,0 @@ -package io.realm - -import io.realm.entities.AllTypes -import io.realm.entities.Dog -import io.realm.entities.SimpleClass -import io.realm.kotlin.* -import io.realm.rule.TestRealmConfigurationFactory -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import java.util.concurrent.CountDownLatch -import kotlin.test.* - -@ExperimentalCoroutinesApi -class CoroutineTests { - - @Suppress("MemberVisibilityCanPrivate") - @Rule - @JvmField - val configFactory = TestRealmConfigurationFactory() - - private lateinit var configuration: RealmConfiguration - private lateinit var testDispatcher: TestCoroutineDispatcher - private lateinit var testScope: TestCoroutineScope - - @Before - fun setUp() { - testDispatcher = TestCoroutineDispatcher() - testScope = TestCoroutineScope(testDispatcher) - configuration = configFactory.createConfiguration() - } - - @Test - fun toFlow_emittedOnCollect() { - val countDownLatch = CountDownLatch(1) - - // TODO check this out for better testing: https://proandroiddev.com/from-rxjava-to-kotlin-flow-testing-42f1641d8433 - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - - realmInstance.where() - .findAllAsync() - .toFlow() - .flowOn(context) - .onEach { flowResults -> - assertTrue(flowResults.isFrozen) - assertEquals(0, flowResults.size) - scope.cancel("Cancelling scope...") - }.onCompletion { - realmInstance.close() - countDownLatch.countDown() - }.collect() - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun toFlow_resultsEmittedAfterCollect() { - Realm.getInstance(configuration).use { realm -> - realm.executeTransaction { transactionRealm -> - transactionRealm.createObject().name = "Foo" - transactionRealm.createObject().name = "Bar" - } - } - - val countDownLatch = CountDownLatch(1) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - realmInstance.where() - .findAllAsync() - .toFlow() - .flowOn(context) - .onEach { flowResults -> - assertTrue(flowResults.isFrozen) - if (flowResults.size == 2) { - scope.cancel("Cancelling scope...") - } - }.onCompletion { - realmInstance.close() - countDownLatch.countDown() - }.collect() - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun toFlow_resultsCancelBeforeCollectActualResults() { - val countDownLatch = CountDownLatch(1) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - realmInstance.where() - .findAllAsync() - .toFlow() - .flowOn(context) - .onCompletion { - realmInstance.close() - countDownLatch.countDown() - }.launchIn(scope) - - // Simulate asynchronous event and then cancel - delay(100) - scope.cancel("Cancelling") - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun toFlow_throwsDueToThreadViolation() { - Realm.getInstance(configuration).use { realm -> - val countDownLatch = CountDownLatch(1) - - // Get results from the test thread - val findAll = realm.where().findAll() - - CoroutineScope(Dispatchers.Main).launch { - assertFailsWith { - // Now we are on the main thread, which means crash - findAll.toFlow() - fail("toFlow() must be called from the thread that retrieved the results!") - } - countDownLatch.countDown() - } - TestHelper.awaitOrFail(countDownLatch) - } - } - - @Test - fun toFlow_throwsDueToBeingCancelled() { - val countDownLatch = CountDownLatch(1) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - var flow: Flow<*>? = null - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - - flow = realmInstance.where() - .findAllAsync() - .toFlow() - - flow!!.flowOn(context) - .onCompletion { - realmInstance.close() - countDownLatch.countDown() - }.launchIn(scope) - - // Simulate asynchronous event and then cancel - delay(100) - scope.cancel("Cancelling") - } - - TestHelper.awaitOrFail(countDownLatch) - - // Throws when re-subscribing to an already-cancelled flow - val errorLatch = CountDownLatch(1) - CoroutineScope(context).launch { - assertFailsWith { flow!!.collect() } - errorLatch.countDown() - } - TestHelper.awaitOrFail(errorLatch) - } - - @Test - fun toFlow_multipleSubscribers() { - val countDownLatch = CountDownLatch(2) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - var flow: Flow>? - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - - flow = realmInstance.where() - .findAllAsync() - .toFlow() - - // Subscriber 1 - flow!!.flowOn(context) - .onEach { flowResults -> - assertTrue(flowResults.isFrozen) - assertEquals(0, flowResults.size) - }.onCompletion { - if (countDownLatch.count > 1) { - countDownLatch.countDown() - } else { - realmInstance.close() - countDownLatch.countDown() - } - }.launchIn(scope) - - // Subscriber 2 - flow!!.flowOn(context) - .onEach { flowResults -> - assertTrue(flowResults.isFrozen) - assertEquals(0, flowResults.size) - }.onCompletion { - if (countDownLatch.count > 1) { - countDownLatch.countDown() - } else { - realmInstance.close() - countDownLatch.countDown() - } - }.launchIn(scope) - - // Simulate asynchronous event and then cancel - delay(100) - scope.cancel("Cancelling") - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun toFlow_emitObjectOnCollect() { - val countDownLatch = CountDownLatch(1) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - realmInstance.beginTransaction() - val obj = realmInstance.createObject() - .apply { name = "Foo" } - realmInstance.commitTransaction() - - obj.toFlow() - .flowOn(context) - .onEach { flowObject -> - assertTrue(flowObject.isFrozen()) - if (flowObject.name == "Foo") { - scope.cancel("Cancelling scope...") - } - }.onCompletion { - realmInstance.close() - countDownLatch.countDown() - }.launchIn(scope) - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun toFlow_emitObjectOnObjectUpdates() { - val countDownLatch = CountDownLatch(1) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - realmInstance.beginTransaction() - val obj = realmInstance.createObject() - .apply { name = "Foo" } - realmInstance.commitTransaction() - - obj.toFlow() - .flowOn(context) - .onEach { flowObject -> - assertTrue(flowObject.isFrozen()) - if (flowObject.name == "Bar") { - scope.cancel("Cancelling scope...") - } - }.onCompletion { - realmInstance.close() - countDownLatch.countDown() - }.launchIn(scope) - - // Simulate asynchronous event and then update object - delay(100) - realmInstance.beginTransaction() - obj.name = "Bar" - realmInstance.commitTransaction() - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun toFlow_emitListOnCollect() { - val countDownLatch = CountDownLatch(1) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - - realmInstance.beginTransaction() - val list = realmInstance.createObject().columnRealmList - list.add(Dog("dog")) - realmInstance.commitTransaction() - - list.toFlow() - .onEach { flowList -> - assertTrue(flowList.isFrozen) - assertEquals(1, flowList.size) - assertEquals("dog", flowList.first()!!.name) - scope.cancel("Cancelling scope...") - }.onCompletion { - realmInstance.close() - countDownLatch.countDown() - }.collect() - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun toFlow_emitListOnListUpdates() { - val countDownLatch = CountDownLatch(1) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - - realmInstance.beginTransaction() - val list = realmInstance.createObject().columnRealmList - list.add(Dog("dog")) - realmInstance.commitTransaction() - - list.toFlow() - .onEach { flowList -> - assertTrue(flowList.isFrozen) - assertEquals(1, flowList.size) - - val dogName = flowList.first()!!.name - if (dogName != "doggo") { - // Before update we have original name - assertEquals("dog", flowList.first()!!.name) - } else { - // Name has been updated, close everything - scope.cancel("Cancelling scope...") - } - }.onCompletion { - realmInstance.close() - countDownLatch.countDown() - }.launchIn(scope) - - // Simulate asynchronous event and then update list - delay(100) - realmInstance.beginTransaction() - list.first()?.apply { - this.name = "doggo" - } - realmInstance.commitTransaction() - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun toFlow_realmModel_emitsOnCollect() { - val countDownLatch = CountDownLatch(1) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - scope.launch { - val realmInstance = Realm.getInstance(configuration) - realmInstance.beginTransaction() - val obj = realmInstance.createObject() - .apply { name = "Foo" } - realmInstance.commitTransaction() - - obj.toFlow() - .flowOn(context) - .onEach { flowObject -> - assertTrue(flowObject.isFrozen()) - if (flowObject.name == "Foo") { - scope.cancel("Cancelling scope...") - } - }.onCompletion { - realmInstance.close() - countDownLatch.countDown() - }.collect() - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun toFlow_dynamicRealmObject_emitsOnCollect() { - val countDownLatch = CountDownLatch(1) - - val context = Dispatchers.Main - val scope = CoroutineScope(context) - - scope.launch { - Realm.getInstance(configuration).use { realmInstance -> - realmInstance.executeTransaction { - realmInstance.createObject() - } - } - - val dynamicRealm = DynamicRealm.getInstance(configuration) - dynamicRealm.where(AllTypes.CLASS_NAME) - .findFirst()!! - .toFlow() - .flowOn(context) - .onEach { flowObject -> - assertTrue(flowObject.isFrozen) - scope.cancel("Cancelling scope...") - }.onCompletion { - dynamicRealm.close() - countDownLatch.countDown() - }.collect() - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun executeTransactionAwait() { - testScope.runBlockingTest { - Realm.getInstance(configuration).use { realmInstance -> - assertEquals(0, realmInstance.where().findAll().size) - - realmInstance.executeTransactionAwait(testDispatcher) { transactionRealm -> - val simpleObject = SimpleClass().apply { name = "simpleName" } - transactionRealm.insert(simpleObject) - } - assertEquals(1, realmInstance.where().findAll().size) - } - } - } - - @Test - fun executeTransactionAwait_cancelCoroutineWithMultipleTransactions() { - val upperBound = 10 - var realmInstance: Realm? = null - - val job = CoroutineScope(Dispatchers.Main).launch { - realmInstance = Realm.getInstance(configuration) - - for (i in 1..upperBound) { - realmInstance!!.executeTransactionAwait { transactionRealm -> - val simpleObject = SimpleClass().apply { name = "simpleName $i" } - transactionRealm.insert(simpleObject) - } - - // Wait for 10 ms between inserts - delay(10) - } - } - - val countDownLatch = CountDownLatch(1) - CoroutineScope(Dispatchers.Main).launch { - // Wait for 50 ms and cancel job so that not all planned 10 elements are inserted - delay(50) - job.cancelAndJoin() - - assertNotEquals(upperBound.toLong(), realmInstance!!.where().count()) - - realmInstance!!.close() - - countDownLatch.countDown() - this.cancel() - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun executeTransactionAwait_cancelCoroutineWithHeavyCooperativeTransaction() { - val upperBound = 100000 - var realmInstance: Realm? = null - - val job = CoroutineScope(Dispatchers.Main).launch { - realmInstance = Realm.getInstance(configuration) - - realmInstance!!.executeTransactionAwait { transactionRealm -> - // Try to insert 100000 objects to give time to be cancelled after 5ms - for (i in 1..upperBound) { - // The coroutine itself will not cancel the transaction, but we can make it cooperative ourselves - if (isActive) { - val simpleObject = SimpleClass().apply { name = "simpleName $i" } - transactionRealm.insert(simpleObject) - } - } - } - } - - val countDownLatch = CountDownLatch(1) - CoroutineScope(Dispatchers.Main).launch { - // Wait for 5 ms and cancel job - delay(5) - job.cancelAndJoin() - - // The coroutine won't finish until the transaction is completely done but not all - // elements will have been inserted since the transaction is cooperative. - // It isn't possible to guarantee we have inserted any element at all either because - // another coroutine is launched inside executeTransactionAwait and that triggers a - // context switching, which might result in that the call to cancelAndJoin above this - // comment be executed even before we check for isActive inside executeTransactionAwait. - // So the result yielded by count() will be a number from 0 to anywhere below 100000. - assertNotEquals(upperBound.toLong(), realmInstance!!.where().count()) - - realmInstance!!.close() - - countDownLatch.countDown() - this.cancel() - } - - TestHelper.awaitOrFail(countDownLatch) - } - - @Test - fun executeTransactionAwait_throwsDueToThreadViolation() { - // Just to prevent the test to end prematurely - val countDownLatch = CountDownLatch(1) - var exception: IllegalStateException? = null - - Realm.getInstance(configuration).use { realm -> - // It will crash so long we aren't using Dispatchers.Unconfined - CoroutineScope(Dispatchers.IO).launch { - assertFailsWith { - realm.executeTransactionAwait { - // no-op - } - }.let { - exception = it - countDownLatch.countDown() - } - } - TestHelper.awaitOrFail(countDownLatch) - } - - // Ensure we failed - assertNotNull(exception) - assertTrue(exception!!.message!!.contains("incorrect thread")) - } -} diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutinesTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutinesTests.kt new file mode 100644 index 0000000000..b415183fdf --- /dev/null +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutinesTests.kt @@ -0,0 +1,1166 @@ +package io.realm + +import io.realm.entities.AllTypes +import io.realm.entities.Dog +import io.realm.entities.SimpleClass +import io.realm.kotlin.* +import io.realm.rule.TestRealmConfigurationFactory +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.util.concurrent.CountDownLatch +import kotlin.test.* + +@ExperimentalCoroutinesApi +class CoroutinesTests { + + @Suppress("MemberVisibilityCanPrivate") + @Rule + @JvmField + val configFactory = TestRealmConfigurationFactory() + + private lateinit var configuration: RealmConfiguration + private lateinit var testDispatcher: TestCoroutineDispatcher + private lateinit var testScope: TestCoroutineScope + + @Before + fun setUp() { + testDispatcher = TestCoroutineDispatcher() + testScope = TestCoroutineScope(testDispatcher) + configuration = configFactory.createConfiguration() + } + + @Test + fun realm_toFlow_emittedOnCollect() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.toflow() + .flowOn(context) + .onEach { emittedRealm -> + assertNotNull(emittedRealm) + scope.cancel("Cancelling scope...") + } + .onCompletion { + realmInstance.close() + countDownLatch.countDown() + } + .collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realm_toFlow_emittedOnUpdate() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.toflow() + .flowOn(context) + .onEach { emittedRealm -> + assertNotNull(emittedRealm) + + if (emittedRealm.isEmpty) { + realmInstance.beginTransaction() + realmInstance.createObject(AllTypes::class.java) + realmInstance.commitTransaction() + } else { + assertTrue(emittedRealm.where().count() > 0) + scope.cancel("Cancelling scope...") + } + } + .onCompletion { + realmInstance.close() + countDownLatch.countDown() + } + .collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun dynamicRealm_toFlow_emittedOnCollect() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = DynamicRealm.getInstance(configuration) + realmInstance.toflow() + .flowOn(context) + .onEach { emittedRealm -> + assertNotNull(emittedRealm) + scope.cancel("Cancelling scope...") + } + .onCompletion { + realmInstance.close() + countDownLatch.countDown() + } + .collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun dynamicRealm_toFlow_emittedOnUpdate() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(configuration).close() + + scope.launch { + val realmInstance = DynamicRealm.getInstance(configuration) + realmInstance.toflow() + .flowOn(context) + .onEach { emittedRealm -> + assertNotNull(emittedRealm) + + if (emittedRealm.isEmpty) { + realmInstance.beginTransaction() + realmInstance.createObject(AllTypes.CLASS_NAME) + realmInstance.commitTransaction() + } else { + assertTrue(emittedRealm.where(AllTypes.CLASS_NAME).count() > 0) + scope.cancel("Cancelling scope...") + } + } + .onCompletion { + realmInstance.close() + countDownLatch.countDown() + } + .collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmResults_toFlow_throwsOnClosed() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + + // No updates will be emitted, but at least ensure that we are not + // triggering coroutines internal java.lang.IllegalStateException due to missing + // 'awaitClose { yourCallbackOrListener.cancel() }', which should be used in the end of + // a callbackFlow block. + assertFailsWith { + withTimeout(100) { + realmInstance.where() + .findAllAsync() + .toFlow() + .onStart { + realmInstance.close() + }.onCompletion { + countDownLatch.countDown() + }.collect() + } + } + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmResults_toFlow_emittedOnCollect() { + val countDownLatch = CountDownLatch(1) + + // TODO check this out for better testing: https://proandroiddev.com/from-rxjava-to-kotlin-flow-testing-42f1641d8433 + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + + realmInstance.where() + .findAllAsync() + .toFlow() + .flowOn(context) + .onEach { flowResults -> + assertTrue(flowResults.isFrozen) + assertEquals(0, flowResults.size) + scope.cancel("Cancelling scope...") + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmResults_toFlow_resultsEmittedAfterCollect() { + Realm.getInstance(configuration).use { realm -> + realm.executeTransaction { transactionRealm -> + transactionRealm.createObject().name = "Foo" + transactionRealm.createObject().name = "Bar" + } + } + + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.where() + .findAllAsync() + .toFlow() + .flowOn(context) + .onEach { flowResults -> + assertTrue(flowResults.isFrozen) + if (flowResults.size == 2) { + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmResults_toChangesetFlow_emittedAfterCollect() { + Realm.getInstance(configuration).use { realm -> + realm.executeTransaction { transactionRealm -> + transactionRealm.createObject().name = "Foo" + } + } + + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.where() + .findAllAsync() + .toChangesetFlow() + .flowOn(context) + .onEach { collectionChange -> + assertTrue(collectionChange.collection.isFrozen) + + if (collectionChange.collection.size == 0) { + assertNull(collectionChange.changeset) + } else { + assertNotNull(collectionChange.changeset) + assertEquals(1, collectionChange.collection.size) + assertEquals("Foo", collectionChange.collection.first()!!.name) + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmResults_dynamicRealm_toChangesetFlow_emittedAfterCollect() { + Realm.getInstance(configuration).use { realm -> + realm.executeTransaction { transactionRealm -> + transactionRealm.createObject().name = "Foo" + } + } + + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = DynamicRealm.getInstance(configuration) + realmInstance.where("SimpleClass") + .findAllAsync() + .toChangesetFlow() + .flowOn(context) + .onEach { collectionChange -> + assertTrue(collectionChange.collection.isFrozen) + + if (collectionChange.collection.size == 0) { + assertNull(collectionChange.changeset) + } else { + assertNotNull(collectionChange.changeset) + assertEquals(1, collectionChange.collection.size) + assertEquals("Foo", collectionChange.collection.first()!!.getString("name")) + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmResults_toFlow_resultsCancelBeforeCollectActualResults() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.where() + .findAllAsync() + .toFlow() + .flowOn(context) + .onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.launchIn(scope) + + // Simulate asynchronous event and then cancel + delay(100) + scope.cancel("Cancelling") + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmResults_toFlow_throwsDueToThreadViolation() { + Realm.getInstance(configuration).use { realm -> + val countDownLatch = CountDownLatch(1) + + // Get results from the test thread + val findAll = realm.where().findAll() + + CoroutineScope(Dispatchers.Main).launch { + assertFailsWith { + // Now we are on the main thread, which means crash + findAll.toFlow().collect() + fail("toFlow() must be called from the thread that retrieved the results!") + } + countDownLatch.countDown() + } + TestHelper.awaitOrFail(countDownLatch) + } + } + + @Test + fun realmResults_toFlow_multipleSubscribers() { + val countDownLatch = CountDownLatch(2) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + var flow: Flow>? + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + + flow = realmInstance.where() + .findAllAsync() + .toFlow() + + // Subscriber 1 + flow!!.flowOn(context) + .onEach { flowResults -> + assertTrue(flowResults.isFrozen) + assertEquals(0, flowResults.size) + }.onCompletion { + if (countDownLatch.count > 1) { + countDownLatch.countDown() + } else { + realmInstance.close() + countDownLatch.countDown() + } + }.launchIn(scope) + + // Subscriber 2 + flow!!.flowOn(context) + .onEach { flowResults -> + assertTrue(flowResults.isFrozen) + assertEquals(0, flowResults.size) + }.onCompletion { + if (countDownLatch.count > 1) { + countDownLatch.countDown() + } else { + realmInstance.close() + countDownLatch.countDown() + } + }.launchIn(scope) + + // Simulate asynchronous event and then cancel + delay(100) + scope.cancel("Cancelling") + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun dynamicRealm_realmResults_toFlow_emittedOnCollect() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(configuration).close() + + scope.launch { + val realmInstance = DynamicRealm.getInstance(configuration) + + realmInstance.where("SimpleClass") + .findAllAsync() + .toFlow() + .flowOn(context) + .onEach { flowResults -> + assertTrue(flowResults.isFrozen) + assertEquals(0, flowResults.size) + scope.cancel("Cancelling scope...") + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun dynamicRealm_realmResults_toFlow_resultsEmittedAfterCollect() { + Realm.getInstance(configuration).use { realm -> + realm.executeTransaction { transactionRealm -> + transactionRealm.createObject().name = "Foo" + transactionRealm.createObject().name = "Bar" + } + } + + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = DynamicRealm.getInstance(configuration) + realmInstance.where("SimpleClass") + .findAllAsync() + .toFlow() + .flowOn(context) + .onEach { flowResults -> + assertTrue(flowResults.isFrozen) + if (flowResults.size == 2) { + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmObject_toFlow_emitObjectOnCollect() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.beginTransaction() + val obj = realmInstance.createObject() + .apply { name = "Foo" } + realmInstance.commitTransaction() + + obj.toFlow() + .flowOn(context) + .onEach { flowObject -> + assertTrue(flowObject!!.isFrozen()) + if (flowObject.name == "Foo") { + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.launchIn(scope) + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmObject_toFlow_emitObjectOnObjectUpdates() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.beginTransaction() + val obj = realmInstance.createObject() + .apply { name = "Foo" } + realmInstance.commitTransaction() + + obj.toFlow() + .flowOn(context) + .onEach { flowObject -> + assertTrue(flowObject!!.isFrozen()) + + if (flowObject.name == "Foo") { + realmInstance.beginTransaction() + obj.name = "Bar" + realmInstance.commitTransaction() + } else { + assertEquals("Bar", flowObject.name) + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.launchIn(scope) + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmObject_toChangesetFlow_emitObjectOnObjectUpdates() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.beginTransaction() + val obj = realmInstance.createObject() + .apply { name = "Foo" } + realmInstance.commitTransaction() + + obj.toChangesetFlow() + .flowOn(context) + .onEach { objectChange -> + assertNotNull(objectChange) + assertTrue(objectChange.`object`.isFrozen()) + + if (objectChange.`object`.name == "Foo") { + realmInstance.beginTransaction() + obj.name = "Bar" + realmInstance.commitTransaction() + } else { + assertEquals("Bar", objectChange.`object`.name) + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.launchIn(scope) + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmObject_toFlow_nullObjectEmitsNullFlow() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + val obj = realmInstance.where().findFirst() + + obj.toFlow() + .flowOn(context) + .onEach { + assertNull(it) + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.launchIn(scope) + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmList_toFlow_emitListOnCollect() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + + realmInstance.beginTransaction() + val list = realmInstance.createObject().columnRealmList + list.add(Dog("dog")) + realmInstance.commitTransaction() + + list.toFlow() + .onEach { flowList -> + assertTrue(flowList.isFrozen) + assertEquals(1, flowList.size) + assertEquals("dog", flowList.first()!!.name) + scope.cancel("Cancelling scope...") + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmList_dynamicRealm_toFlow_emitListOnCollect() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(configuration).close() + + scope.launch { + Realm.getInstance(configuration).use { realmInstance -> + realmInstance.beginTransaction() + realmInstance.createObject() + .columnRealmList + .apply { add(Dog("dog")) } + realmInstance.commitTransaction() + } + + val dynamicRealmInstance = DynamicRealm.getInstance(configuration) + val list = dynamicRealmInstance.where(AllTypes.CLASS_NAME) + .findFirst()!! + .getList(AllTypes.FIELD_REALMLIST) + .freeze() + + list.toFlow() + .onEach { flowList -> + assertTrue(flowList.isFrozen) + assertEquals(1, flowList.size) + assertEquals("dog", flowList.first()!!.getString(Dog.FIELD_NAME)) + scope.cancel("Cancelling scope...") + }.onCompletion { + dynamicRealmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmList_toFlow_emitListOnListUpdates() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + + realmInstance.beginTransaction() + val list = realmInstance.createObject().columnRealmList + list.add(Dog("dog")) + realmInstance.commitTransaction() + + list.toFlow() + .onEach { flowList -> + assertTrue(flowList.isFrozen) + assertEquals(1, flowList.size) + + val dogName = flowList.first()!!.name + if (dogName != "doggo") { + // Before update we have original name + assertEquals("dog", flowList.first()!!.name) + + // Now update object + realmInstance.beginTransaction() + list.first()?.apply { + this.name = "doggo" + } + realmInstance.commitTransaction() + } else { + assertEquals("doggo", dogName) + + // Name has been updated, close everything + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.launchIn(scope) + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmList_toChangesetFlow_emitListOnListUpdates() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + + realmInstance.beginTransaction() + val list = realmInstance.createObject().columnRealmList + list.add(Dog("dog")) + realmInstance.commitTransaction() + + list.toChangesetFlow() + .onEach { collectionChange -> + assertTrue(collectionChange.collection.isFrozen) + assertEquals(1, collectionChange.collection.size) + + val listDog = collectionChange.collection.first()!! + val dogName = listDog.name + if (dogName != "doggo") { + assertNull(collectionChange.changeset) + + // Before update we have the original name + assertEquals("dog", dogName) + + // Now update object + realmInstance.beginTransaction() + list.first()?.apply { + this.name = "doggo" + } + realmInstance.commitTransaction() + } else { + assertNotNull(collectionChange.changeset) + assertEquals("doggo", dogName) + + // Name has been updated, close everything + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.launchIn(scope) + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmList_dynamicRealm_toFlow_emitListOnListUpdates() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(configuration).close() + + scope.launch { + val realmInstance = DynamicRealm.getInstance(configuration) + + realmInstance.beginTransaction() + val dynamicRealmObject = realmInstance.createObject(AllTypes.CLASS_NAME) + val dog = realmInstance.createObject("Dog") + .apply { setString(Dog.FIELD_NAME, "dog") } + val list = dynamicRealmObject.getList(AllTypes.FIELD_REALMLIST) + .apply { add(dog) } + realmInstance.commitTransaction() + + list.toFlow() + .onEach { flowList -> + assertTrue(flowList.isFrozen) + assertEquals(1, flowList.size) + + val flowDog = flowList.first()!! + val dogName = flowDog.getString(Dog.FIELD_NAME) + if (dogName != "doggo") { + // Before update we have original name + assertEquals("dog", dogName) + + // Now update object + realmInstance.beginTransaction() + list.first()!!.apply { + this.setString(Dog.FIELD_NAME, "doggo") + } + realmInstance.commitTransaction() + } else { + assertEquals("doggo", dogName) + + // Name has been updated, close everything + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.launchIn(scope) + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmList_dynamicRealm_toChangesetFlow_emitListOnListUpdates() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(configuration).close() + + scope.launch { + val realmInstance = DynamicRealm.getInstance(configuration) + + realmInstance.beginTransaction() + val dynamicRealmObject = realmInstance.createObject(AllTypes.CLASS_NAME) + val dog = realmInstance.createObject("Dog") + .apply { setString(Dog.FIELD_NAME, "dog") } + val list = dynamicRealmObject.getList(AllTypes.FIELD_REALMLIST) + .apply { add(dog) } + realmInstance.commitTransaction() + + list.toChangesetFlow() + .onEach { collectionChange -> + assertTrue(collectionChange.collection.isFrozen) + assertEquals(1, collectionChange.collection.size) + + val listDog = collectionChange.collection.first()!! + val dogName = listDog.getString(Dog.FIELD_NAME) + if (dogName != "doggo") { + assertNull(collectionChange.changeset) + + // Before update we have the original name + assertEquals("dog", dogName) + + // Now update object + realmInstance.beginTransaction() + list.first()?.apply { + this.setString(Dog.FIELD_NAME, "doggo") + } + realmInstance.commitTransaction() + } else { + assertNotNull(collectionChange.changeset) + assertEquals("doggo", dogName) + + // Name has been updated, close everything + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.launchIn(scope) + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun realmObject_toFlow_emitsOnCollect() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.beginTransaction() + val obj = realmInstance.createObject() + .apply { name = "Foo" } + realmInstance.commitTransaction() + + obj.toFlow() + .flowOn(context) + .onEach { flowObject -> + assertTrue(flowObject!!.isFrozen()) + if (flowObject.name == "Foo") { + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun dynamicRealmObject_toFlow_emitsOnCollect() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + Realm.getInstance(configuration).use { realmInstance -> + realmInstance.executeTransaction { + realmInstance.createObject() + } + } + + val dynamicRealm = DynamicRealm.getInstance(configuration) + dynamicRealm.where(AllTypes.CLASS_NAME) + .findFirst()!! + .toFlow() + .flowOn(context) + .onEach { flowObject -> + assertTrue(flowObject!!.isFrozen) + scope.cancel("Cancelling scope...") + }.onCompletion { + dynamicRealm.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun dynamicRealmObject_toFlow_emitsOnUpdate() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(configuration).close() + + scope.launch { + val realmInstance = DynamicRealm.getInstance(configuration) + realmInstance.beginTransaction() + val simpleObject = realmInstance.createObject("SimpleClass") + .apply { setString("name", "simpleName") } + realmInstance.commitTransaction() + + val dynamicRealm = DynamicRealm.getInstance(configuration) + dynamicRealm.where("SimpleClass") + .findFirst() + .toFlow() + .flowOn(context) + .onEach { flowObject -> + assertNotNull(flowObject) + assertTrue(flowObject.isFrozen) + + val name = flowObject.getString("name") + if (name == "simpleName") { + realmInstance.beginTransaction() + simpleObject.setString("name", "advancedName") + realmInstance.commitTransaction() + } else { + assertEquals("advancedName", name) + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + dynamicRealm.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun dynamicRealmObject_toChangesetFlow_emitsOnUpdate() { + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + // Initializes schema. DynamicRealm will not do that, so let a normal Realm create the file first. + Realm.getInstance(configuration).close() + + scope.launch { + val realmInstance = DynamicRealm.getInstance(configuration) + realmInstance.beginTransaction() + val simpleObject = realmInstance.createObject("SimpleClass") + .apply { setString("name", "simpleName") } + realmInstance.commitTransaction() + + val dynamicRealm = DynamicRealm.getInstance(configuration) + dynamicRealm.where("SimpleClass") + .findFirst() + .toChangesetFlow() + .flowOn(context) + .onEach { objectChange -> + assertNotNull(objectChange) + assertNotNull(objectChange.`object`) + assertTrue(objectChange.`object`.isFrozen) + + val name = objectChange.`object`.getString("name") + if (name == "simpleName") { + assertNull(objectChange.changeset) + realmInstance.beginTransaction() + simpleObject.setString("name", "advancedName") + realmInstance.commitTransaction() + } else { + assertNotNull(objectChange.changeset) + assertEquals("advancedName", name) + scope.cancel("Cancelling scope...") + } + }.onCompletion { + realmInstance.close() + dynamicRealm.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun executeTransactionAwait() { + testScope.runBlockingTest { + Realm.getInstance(configuration).use { realmInstance -> + assertEquals(0, realmInstance.where().findAll().size) + + realmInstance.executeTransactionAwait(testDispatcher) { transactionRealm -> + val simpleObject = SimpleClass().apply { name = "simpleName" } + transactionRealm.insert(simpleObject) + } + assertEquals(1, realmInstance.where().findAll().size) + } + } + } + + @Test + fun executeTransactionAwait_cancelCoroutineWithMultipleTransactions() { + val upperBound = 10 + var realmInstance: Realm? = null + + val job = CoroutineScope(Dispatchers.Main).launch { + realmInstance = Realm.getInstance(configuration) + + for (i in 1..upperBound) { + realmInstance!!.executeTransactionAwait { transactionRealm -> + val simpleObject = SimpleClass().apply { name = "simpleName $i" } + transactionRealm.insert(simpleObject) + } + + // Wait for 10 ms between inserts + delay(10) + } + } + + val countDownLatch = CountDownLatch(1) + CoroutineScope(Dispatchers.Main).launch { + // Wait for 50 ms and cancel job so that not all planned 10 elements are inserted + delay(50) + job.cancelAndJoin() + + assertNotEquals(upperBound.toLong(), realmInstance!!.where().count()) + + realmInstance!!.close() + + countDownLatch.countDown() + this.cancel() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun executeTransactionAwait_cancelCoroutineWithHeavyCooperativeTransaction() { + val upperBound = 100000 + var realmInstance: Realm? = null + + val job = CoroutineScope(Dispatchers.Main).launch { + realmInstance = Realm.getInstance(configuration) + + realmInstance!!.executeTransactionAwait { transactionRealm -> + // Try to insert 100000 objects to give time to be cancelled after 5ms + for (i in 1..upperBound) { + // The coroutine itself will not cancel the transaction, but we can make it cooperative ourselves + if (isActive) { + val simpleObject = SimpleClass().apply { name = "simpleName $i" } + transactionRealm.insert(simpleObject) + } + } + } + } + + val countDownLatch = CountDownLatch(1) + CoroutineScope(Dispatchers.Main).launch { + // Wait for 5 ms and cancel job + delay(5) + job.cancelAndJoin() + + // The coroutine won't finish until the transaction is completely done but not all + // elements will have been inserted since the transaction is cooperative. + // It isn't possible to guarantee we have inserted any element at all either because + // another coroutine is launched inside executeTransactionAwait and that triggers a + // context switching, which might result in that the call to cancelAndJoin above this + // comment be executed even before we check for isActive inside executeTransactionAwait. + // So the result yielded by count() will be a number from 0 to anywhere below 100000. + assertNotEquals(upperBound.toLong(), realmInstance!!.where().count()) + + realmInstance!!.close() + + countDownLatch.countDown() + this.cancel() + } + + TestHelper.awaitOrFail(countDownLatch) + } + + @Test + fun executeTransactionAwait_throwsDueToThreadViolation() { + // Just to prevent the test to end prematurely + val countDownLatch = CountDownLatch(1) + var exception: IllegalStateException? = null + + Realm.getInstance(configuration).use { realm -> + // It will crash so long we aren't using Dispatchers.Unconfined + CoroutineScope(Dispatchers.IO).launch { + assertFailsWith { + realm.executeTransactionAwait { + // no-op + } + }.let { + exception = it + countDownLatch.countDown() + } + } + TestHelper.awaitOrFail(countDownLatch) + } + + // Ensure we failed + assertNotNull(exception) + assertTrue(exception!!.message!!.contains("incorrect thread")) + } +} diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/DynamicRealmExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/DynamicRealmExtensions.kt new file mode 100644 index 0000000000..ec8ca03c16 --- /dev/null +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/DynamicRealmExtensions.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.kotlin + +import io.realm.DynamicRealm +import io.realm.annotations.Beta +import kotlinx.coroutines.flow.Flow + +/** + * Creates a [Flow] for a [DynamicRealm]. It should emit the initial state of the Realm when subscribed to and + * on each subsequent update of the Realm. + * + * @return Kotlin [Flow] that emit all updates to the Realm. + */ +@Beta +fun DynamicRealm.toflow(): Flow { + return configuration.flowFactory.from(this) +} diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt index 9a328cdf15..db0abd7256 100644 --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmExtensions.kt @@ -18,9 +18,11 @@ package io.realm.kotlin import io.realm.Realm import io.realm.RealmModel import io.realm.RealmQuery +import io.realm.annotations.Beta import io.realm.exceptions.RealmException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext @@ -96,6 +98,17 @@ inline fun Realm.createEmbeddedObject(parentObject: Rea return this.createEmbeddedObject(T::class.java, parentObject, parentProperty) } +/** + * Creates a [Flow] for a [Realm]. It should emit the initial state of the Realm when subscribed to and + * on each subsequent update of the Realm. + * + * @return Kotlin [Flow] that emit all updates to the Realm. + */ +@Beta +fun Realm.toflow(): Flow { + return configuration.flowFactory.from(this) +} + /** * Suspend version of [Realm.executeTransaction] to use within coroutines. * diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmListExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmListExtensions.kt index bd36d3054c..0fab3d9d22 100644 --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmListExtensions.kt +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmListExtensions.kt @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package io.realm.kotlin import io.realm.* import io.realm.annotations.Beta -import kotlinx.coroutines.channels.awaitClose +import io.realm.rx.CollectionChange import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.flowOf /** * Returns a [Flow] that monitors changes to this RealmList. It will emit the current @@ -54,39 +53,58 @@ import kotlinx.coroutines.flow.flowOf * @return Kotlin [Flow] on which calls to `onEach` or `collect` can be made. */ @Beta -fun RealmList.toFlow(): Flow> { - // Return "as is" if frozen, there will be no listening for changes - if (realm.isFrozen) { - return flowOf(this) +fun RealmList.toFlow(): Flow> { + @Suppress("INACCESSIBLE_TYPE") + return when (val realmInstance = baseRealm) { + is Realm -> realmInstance.configuration.flowFactory.from(realmInstance, this) + is DynamicRealm -> realmInstance.configuration.flowFactory.from(realmInstance, this) + else -> throw IllegalStateException("Wrong type of Realm.") } +} - val config = realm.configuration - - return callbackFlow { - val results = this@toFlow - - // Do nothing if the results are invalid - if (!results.isValid) { - return@callbackFlow - } - - // Get instance to ensure the Realm is open for as long as we are listening - val flowRealm = Realm.getInstance(config) - val listener = RealmChangeListener> { listenerResults -> - offer(listenerResults.freeze()) - } - - results.addChangeListener(listener) - - // Emit current (frozen) value - offer(freeze()) - - awaitClose { - // Remove listener and cleanup - if (!flowRealm.isClosed) { - results.removeChangeListener(listener) - flowRealm.close() - } - } +/** + * Returns a [Flow] that monitors changes to this RealmList. It will emit the current + * RealmList upon subscription. For each update to the RealmList a [CollectionChange] consisting of + * a pair with the RealmList and its corresponding [OrderedCollectionChangeSet] will be sent. The + * changeset will be `null` the first time the RealmList is emitted. + * + * The RealmList will continually be emitted as it is updated. This flow will never complete. + * + * Items emitted are frozen (see [RealmList.freeze]). This means that they are immutable and can + * be read on any thread. + * + * Realm flows always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommended to collect the values on a computation + * dispatcher: + * + * ``` + * list.toChangesetFlow() + * .map { change -> doExpensiveWork(change) } + * .flowOn(Dispatchers.IO) + * .onEach { change -> + * // ... + * }.launchIn(Dispatchers.Main) + * ``` + * + * If you would like `toChangesetFlow()` to stop emitting items you can instruct the flow to only + * emit the first item by calling [kotlinx.coroutines.flow.first]: + * ``` + * val foo = list.toChangesetFlow() + * .flowOn(context) + * .first() + * ``` + * + * @return Kotlin [Flow] that will never complete. + * @throws UnsupportedOperationException if the required coroutines framework is not on the + * classpath or the corresponding Realm instance doesn't support flows. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. + */ +@Beta +fun RealmList.toChangesetFlow(): Flow>> { + @Suppress("INACCESSIBLE_TYPE") + return when (val realmInstance = baseRealm) { + is Realm -> realmInstance.configuration.flowFactory.changesetFrom(realmInstance, this) + is DynamicRealm -> realmInstance.configuration.flowFactory.changesetFrom(realmInstance, this) + else -> throw IllegalStateException("Wrong type of Realm.") } } diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmObjectExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmObjectExtensions.kt index 2337afac2e..1d3889fe64 100644 --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmObjectExtensions.kt +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmObjectExtensions.kt @@ -13,20 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package io.realm.kotlin import io.realm.* -import io.realm.RealmObject.freeze import io.realm.annotations.Beta import io.realm.internal.RealmObjectProxy -import kotlinx.coroutines.channels.awaitClose +import io.realm.rx.ObjectChange import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flowOf /** * Returns a [Flow] that monitors changes to this RealmObject. It will emit the current - * RealmObject when subscribed to. Object updates will continually be emitted as the RealmObject is + * RealmObject upon subscription. Object updates will continually be emitted as the RealmObject is * updated - `onCompletion` will never be called. * * Items emitted from Realm flows are frozen - see [RealmObject.freeze]. This means that they are @@ -56,94 +55,82 @@ import kotlinx.coroutines.flow.flowOf * @return Kotlin [Flow] on which calls to `onEach` or `collect` can be made. */ @Beta -fun T.toFlow(): Flow { - val obj = this - return if (obj is RealmObjectProxy) { - val proxy = obj as RealmObjectProxy - val realm = proxy.`realmGet$proxyState`().`realm$realm` - - when (realm) { - is Realm -> flowFromRealm(realm, obj) - is DynamicRealm -> { - val dynamicObject = obj as DynamicRealmObject - flowFromDynamicRealm(realm, dynamicObject) as Flow +fun T?.toFlow(): Flow { + // Return flow with object or null flow if this function is called on null + return this?.let { obj -> + if (obj is RealmObjectProxy) { + val proxy = obj as RealmObjectProxy + + @Suppress("INACCESSIBLE_TYPE") + when (val realm = proxy.`realmGet$proxyState`().`realm$realm`) { + is Realm -> realm.configuration.flowFactory.from(realm, obj) + is DynamicRealm -> (obj as DynamicRealmObject).let { dynamicRealmObject -> + realm.configuration.flowFactory.from(realm, dynamicRealmObject) as Flow + } + else -> throw UnsupportedOperationException("${realm.javaClass} is not supported as a candidate for 'toFlow'. Only subclasses of RealmModel/RealmObject can be used.") } - else -> throw UnsupportedOperationException("${realm.javaClass} does not support RxJava. See https://realm.io/docs/java/latest/#rxjava for more details.") + } else { + // Return a one-time emission in case the object is unmanaged + return flowOf(this) } - } else { - return flowOf(this) - } + } ?: flowOf(null) } -private fun flowFromRealm(realm: Realm, obj: T): Flow { - // Return "as is" if frozen, there will be no listening for changes - if (realm.isFrozen) { - return flowOf(obj) - } - - val config = realm.configuration - - return callbackFlow { - // Do nothing if the object is invalid - if (!obj.isValid()) { - return@callbackFlow - } - - // Get instance to ensure the Realm is open for as long as we are listening - val flowRealm = Realm.getInstance(config) - val listener = RealmChangeListener { listenerObj -> - offer(listenerObj.freeze()) - } - - obj.addChangeListener(listener) - - // Emit current (frozen) value - offer(freeze(obj)) - - awaitClose { - // Remove listener and cleanup - if (!flowRealm.isClosed) { - obj.removeChangeListener(listener) - flowRealm.close() +/** + * Returns a [Flow] that monitors changes to this RealmObject. It will emit the current + * RealmObject upon subscription. For each update to the RealmObject a [ObjectChange] consisting of + * a pair with the RealmObject and its corresponding [ObjectChangeSet] will be sent. The changeset + * will be `null` the first time the RealmObject is emitted. + * + * The RealmObject will continually be emitted as it is updated. This flow will never complete. + * + * Items emitted are frozen (see [RealmObject.freeze]). This means that they are immutable and can + * be read on any thread. + * + * Realm flows always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommended to collect the values on a computation + * dispatcher: + * + * ``` + * object.toChangesetFlow() + * .map { change -> doExpensiveWork(change) } + * .flowOn(Dispatchers.IO) + * .onEach { change -> + * // ... + * }.launchIn(Dispatchers.Main) + * ``` + * + * If you would like `toChangesetFlow()` to stop emitting items you can instruct the flow to only + * emit the first item by calling [kotlinx.coroutines.flow.first]: + * ``` + * val foo = object.toChangesetFlow() + * .flowOn(context) + * .first() + * ``` + * + * @return Kotlin [Flow] that will never complete. + * @throws UnsupportedOperationException if the required coroutines framework is not on the + * classpath or the corresponding Realm instance doesn't support flows. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. + */ +@Beta +fun T?.toChangesetFlow(): Flow?> { + // Return flow with objectchange containing this object or null flow if this function is called on null + return this?.let { obj -> + if (obj is RealmObjectProxy) { + val proxy = obj as RealmObjectProxy + @Suppress("INACCESSIBLE_TYPE") + when (val realm = proxy.`realmGet$proxyState`().`realm$realm`) { + is Realm -> realm.configuration.flowFactory.changesetFrom(realm, obj) + is DynamicRealm -> (obj as DynamicRealmObject).let { dynamicRealmObject -> + realm.configuration.flowFactory.changesetFrom(realm, dynamicRealmObject) as Flow?> + } + else -> throw UnsupportedOperationException("${realm.javaClass} is not supported as a candidate for 'toFlow'. Only subclasses of RealmModel/RealmObject can be used.") } - } - } -} - -private fun flowFromDynamicRealm( - dynamicRealm: DynamicRealm, - dynamicObject: DynamicRealmObject -): Flow { - // Return "as is" if frozen, there will be no listening for changes - if (dynamicRealm.isFrozen) { - return flowOf(dynamicObject) - } - - val config = dynamicRealm.configuration - - return callbackFlow { - // Do nothing if the object is invalid - if (!dynamicObject.isValid()) { - return@callbackFlow - } - // Get instance to ensure the Realm is open for as long as we are listening - val flowRealm = Realm.getInstance(config) - val listener = RealmChangeListener { listenerObj -> - offer(listenerObj.freeze()) - } - - dynamicObject.addChangeListener(listener) - - // Emit current (frozen) value - offer(freeze(dynamicObject)) - - awaitClose { - // Remove listener and cleanup - if (!flowRealm.isClosed) { - dynamicObject.removeChangeListener(listener) - flowRealm.close() - } + } else { + // Return a one-time emission in case the object is unmanaged + return flowOf(ObjectChange(this, null)) } - } + } ?: flowOf(null) } diff --git a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmResultsExtensions.kt b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmResultsExtensions.kt index 1f12c9218b..687cfa4ccd 100644 --- a/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmResultsExtensions.kt +++ b/realm/kotlin-extensions/src/main/kotlin/io/realm/kotlin/RealmResultsExtensions.kt @@ -13,18 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package io.realm.kotlin -import io.realm.Realm -import io.realm.RealmChangeListener -import io.realm.RealmModel -import io.realm.RealmResults +import io.realm.* import io.realm.annotations.Beta -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.ensureActive +import io.realm.rx.CollectionChange import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.flowOf /** * Returns a [Flow] that monitors changes to this RealmResults. It will emit the current @@ -63,38 +58,57 @@ import kotlinx.coroutines.flow.flowOf */ @Beta fun RealmResults.toFlow(): Flow> { - // Return "as is" if frozen, there will be no listening for changes - if (realm.isFrozen) { - return flowOf(this) + @Suppress("INACCESSIBLE_TYPE") + return when (val realmInstance = baseRealm) { + is Realm -> realmInstance.configuration.flowFactory.from(realmInstance, this) + is DynamicRealm -> realmInstance.configuration.flowFactory.from(realmInstance, this) + else -> throw IllegalStateException("Wrong type of Realm.") } +} - val config = realm.configuration - - return callbackFlow { - val results = this@toFlow - - // Do nothing if the results are invalid - if (!results.isValid) { - return@callbackFlow - } - - // Get instance to ensure the Realm is open for as long as we are listening - val flowRealm = Realm.getInstance(config) - val listener = RealmChangeListener> { listenerResults -> - offer(listenerResults.freeze()) - } - - results.addChangeListener(listener) - - // Emit current (frozen) value - offer(freeze()) - - awaitClose { - // Remove listener and cleanup - if (!flowRealm.isClosed) { - results.removeChangeListener(listener) - flowRealm.close() - } - } +/** + * Returns a [Flow] that monitors changes to this RealmResults. It will emit the current + * RealmResults upon subscription. For each update to the RealmResults a [CollectionChange] + * consisting of a pair with the RealmResults and its corresponding [OrderedCollectionChangeSet] + * will be sent. The changeset will be `null` the first time the RealmResults is emitted. + * + * The RealmResults will continually be emitted as they are updated. This flow will never complete. + * + * Items emitted are frozen (see [RealmResults.freeze]). This means that they are immutable and can + * be read on any thread. + * + * Realm flows always emit items from the thread holding the live Realm. This means that if + * you need to do further processing, it is recommended to collect the values on a computation + * dispatcher: + * + * ``` + * results.toChangesetFlow() + * .map { change -> doExpensiveWork(change) } + * .flowOn(Dispatchers.IO) + * .onEach { change -> + * // ... + * }.launchIn(Dispatchers.Main) + * ``` + * + * If you would like `toChangesetFlow()` to stop emitting items you can instruct the flow to only + * emit the first item by calling [kotlinx.coroutines.flow.first]: + * ``` + * val foo = results.toChangesetFlow() + * .flowOn(context) + * .first() + * ``` + * + * @return Kotlin [Flow] that will never complete. + * @throws UnsupportedOperationException if the required coroutines framework is not on the + * classpath or the corresponding Realm instance doesn't support flows. + * @throws IllegalStateException if the Realm wasn't opened on a Looper thread. + */ +@Beta +fun RealmResults.toChangesetFlow(): Flow>> { + @Suppress("INACCESSIBLE_TYPE") + return when (val realmInstance = baseRealm) { + is Realm -> realmInstance.configuration.flowFactory.changesetFrom(realmInstance, this) + is DynamicRealm -> realmInstance.configuration.flowFactory.changesetFrom(realmInstance, this) + else -> throw IllegalStateException("Wrong type of Realm.") } } diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index b7764a6bd6..5a1f3a1c9a 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -207,9 +207,13 @@ repositories { } dependencies { - compileOnly "io.reactivex.rxjava2:rxjava:${properties.getProperty('RXJAVA_DEPENDENCY')}" compileOnly 'com.github.spotbugs:spotbugs-annotations:4.1.2' + compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_android_version" + compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_android_version" + androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_android_version" + androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_android_version" testImplementation 'junit:junit:4.12' testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -281,6 +285,7 @@ task javadoc(type: Javadoc) { exclude '**/internal/**' exclude '**/BuildConfig.java' exclude '**/R.java' + exclude '**/*.kt' doLast { copy { from "src/realm-java-overview.png" diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java index 8d83296f3b..547caaaa26 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmConfigurationTests.java @@ -17,8 +17,9 @@ package io.realm; import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; + import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.After; import org.junit.Before; @@ -33,9 +34,12 @@ import java.util.Arrays; import java.util.Set; +import javax.annotation.Nonnull; + import io.reactivex.Flowable; import io.reactivex.Observable; import io.reactivex.Single; +import io.realm.coroutines.FlowFactory; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesModelModule; import io.realm.entities.AnimalModule; @@ -57,6 +61,7 @@ import io.realm.rx.ObjectChange; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; +import kotlinx.coroutines.flow.Flow; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -82,7 +87,7 @@ public class RealmConfigurationTests { public final ExpectedException thrown = ExpectedException.none(); private Context context; - private RealmConfiguration defaultConfig; + private RealmConfiguration defaultConfig; private Realm realm; @Before @@ -199,7 +204,7 @@ public void constructBuilder_nullKeyThrows() { @Test public void constructBuilder_wrongKeyLengthThrows() { - byte[][] wrongKeys = new byte[][] { + byte[][] wrongKeys = new byte[][]{ new byte[0], new byte[Realm.ENCRYPTION_KEY_LENGTH - 1], new byte[Realm.ENCRYPTION_KEY_LENGTH + 1] @@ -230,7 +235,7 @@ public void constructBuilder_versionLessThanDiscVersionThrows() { .build()); realm.close(); - int[] wrongVersions = new int[] { 0, 1, 41 }; + int[] wrongVersions = new int[]{0, 1, 41}; for (int version : wrongVersions) { try { realm = Realm.getInstance(configFactory.createConfigurationBuilder() @@ -383,8 +388,8 @@ public void deleteRealmIfMigrationNeeded_failsWhenAssetFileProvided() { RealmConfiguration.Builder builder = configFactory.createConfigurationBuilder(); try { builder - .assetFile("asset_file.realm") - .deleteRealmIfMigrationNeeded(); + .assetFile("asset_file.realm") + .deleteRealmIfMigrationNeeded(); fail(); } catch (IllegalStateException expected) { assertEquals("Realm cannot clear its schema when previously configured to use an asset file by calling assetFile().", @@ -796,6 +801,120 @@ public Single> from(DynamicRealm realm, RealmQuery query) { assertFalse(configuration2.getRxFactory() == dummyFactory); } + @Test + public void rxFactory_nullThrows() { + RealmConfiguration.Builder builder = configFactory.createConfigurationBuilder(); + try { + builder.rxFactory(null); + fail("Setting a null factory from Java should fail."); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("null")); + } + } + + @Test + public void flowFactory_defaultNotNull() { + RealmConfiguration configuration = configFactory.createConfigurationBuilder() + .build(); + assertNotNull(configuration.getFlowFactory()); + } + + @Test + public void flowFactory() { + final FlowFactory dummyFactory = new FlowFactory() { + @Override + public Flow from(@Nonnull Realm realm) { + return null; + } + + @Override + public Flow from(@Nonnull DynamicRealm dynamicRealm) { + return null; + } + + @Override + public Flow> from(@Nonnull Realm realm, @Nonnull RealmResults results) { + return null; + } + + @Override + public Flow>> changesetFrom(@Nonnull Realm realm, @Nonnull RealmResults results) { + return null; + } + + @Override + public Flow> from(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmResults results) { + return null; + } + + @Override + public Flow>> changesetFrom(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmResults results) { + return null; + } + + @Override + public Flow> from(@Nonnull Realm realm, @Nonnull RealmList realmList) { + return null; + } + + @Override + public Flow>> changesetFrom(@Nonnull Realm realm, @Nonnull RealmList list) { + return null; + } + + @Override + public Flow> from(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmList realmList) { + return null; + } + + @Override + public Flow>> changesetFrom(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmList list) { + return null; + } + + @Override + public Flow from(@Nonnull Realm realm, @Nonnull T realmObject) { + return null; + } + + @Override + public Flow> changesetFrom(@Nonnull Realm realm, @Nonnull T realmObject) { + return null; + } + + @Override + public Flow from(@Nonnull DynamicRealm dynamicRealm, @Nonnull DynamicRealmObject dynamicRealmObject) { + return null; + } + + @Override + public Flow> changesetFrom(@Nonnull DynamicRealm dynamicRealm, @Nonnull DynamicRealmObject dynamicRealmObject) { + return null; + } + }; + + RealmConfiguration configuration1 = configFactory.createConfigurationBuilder() + .flowFactory(dummyFactory) + .build(); + assertTrue(configuration1.getFlowFactory() == dummyFactory); + + RealmConfiguration configuration2 = configFactory.createConfigurationBuilder() + .build(); + assertNotNull(configuration2.getFlowFactory()); + assertFalse(configuration2.getFlowFactory() == dummyFactory); + } + + @Test + public void flowFactory_nullThrows() { + try { + configFactory.createConfigurationBuilder() + .flowFactory(null); + fail("Setting a null factory from Java should fail."); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("null")); + } + } + @Test public void initialDataTransactionEqual() { final Realm.Transaction transaction = new Realm.Transaction() { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt index 0cc86b1895..a6bb81025c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncConfigurationTests.kt @@ -17,7 +17,11 @@ package io.realm.mongodb.sync import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import io.reactivex.Flowable +import io.reactivex.Observable +import io.reactivex.Single import io.realm.* +import io.realm.coroutines.FlowFactory import io.realm.entities.SyncStringOnly import io.realm.entities.SyncStringOnlyModule import io.realm.kotlin.createObject @@ -27,6 +31,12 @@ import io.realm.mongodb.SyncTestUtils.Companion.createTestUser import io.realm.mongodb.User import io.realm.mongodb.close import io.realm.mongodb.registerUserAndLogin +import io.realm.rx.CollectionChange +import io.realm.rx.ObjectChange +import io.realm.rx.RealmObservableFactory +import io.realm.rx.RxObservableFactory +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import org.bson.BsonString import org.bson.types.ObjectId import org.junit.* @@ -79,7 +89,7 @@ class SyncConfigurationTests { fun errorHandler_nullThrows() { val user: User = createTestUser(app) val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) - assertFailsWith { builder.errorHandler(TestHelper.getNull()) } + assertFailsWith { builder.errorHandler(TestHelper.getNull()) } } @Test @@ -103,7 +113,7 @@ class SyncConfigurationTests { fun clientResetHandler_nullThrows() { val user: User = createTestUser(app) val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) - assertFailsWith { builder.clientResetHandler(TestHelper.getNull()) } + assertFailsWith { builder.clientResetHandler(TestHelper.getNull()) } } @Test @@ -278,22 +288,26 @@ class SyncConfigurationTests { @Test @Ignore("Not implemented yet") - fun shouldWaitForInitialRemoteData() { } + fun shouldWaitForInitialRemoteData() { + } @Test @Ignore("Not implemented yet") - fun getInitialRemoteDataTimeout() { } + fun getInitialRemoteDataTimeout() { + } @Test @Ignore("Not implemented yet") - fun getSessionStopPolicy () { } + fun getSessionStopPolicy() { + } @Test @Ignore("Not implemented yet") - fun getUrlPrefix () { } + fun getUrlPrefix() { + } @Test - fun getPartitionValue () { + fun getPartitionValue() { val user: User = createTestUser(app) val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) assertEquals(BsonString(DEFAULT_PARTITION), config.partitionValue) @@ -334,8 +348,8 @@ class SyncConfigurationTests { val config2 = SyncConfiguration.Builder(user, "realm2").modules(SyncStringOnlyModule()).build() assertNotEquals(config1.path, config2.path) - assertTrue(config1.path.endsWith("${app.configuration.appId}/${user.id}/s_realm1.realm")) - assertTrue(config2.path.endsWith("${app.configuration.appId}/${user.id}/s_realm2.realm")) + assertTrue(config1.path.endsWith("${app.configuration.appId}/${user.id}/s_realm1.realm")) + assertTrue(config2.path.endsWith("${app.configuration.appId}/${user.id}/s_realm2.realm")) // Check for https://github.com/realm/realm-java/issues/6882 val realm1 = Realm.getInstance(config1) @@ -424,4 +438,187 @@ class SyncConfigurationTests { .build() assertTrue(configuration.isAllowWritesOnUiThread) } + + @Test + fun rxFactory_defaultNonNull() { + val configuration = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + .build() + assertNotNull(configuration.rxFactory) + } + + @Test + fun rxFactory_nullThrows() { + assertFailsWith { + SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + .rxFactory(TestHelper.getNull()) + }.let { + assertTrue(it.message!!.contains("null")) + } + } + + @Test + fun rxFactory() { + val factory = object: RxObservableFactory { + override fun from(realm: Realm): Flowable { + return Flowable.just(null) + } + + override fun from(realm: DynamicRealm): Flowable { + return Flowable.just(null) + } + + override fun from(realm: Realm, results: RealmResults): Flowable> { + return Flowable.just(null) + } + + override fun from(realm: DynamicRealm, results: RealmResults): Flowable> { + return Flowable.just(null) + } + + override fun from(realm: Realm, list: RealmList): Flowable> { + return Flowable.just(null) + } + + override fun from(realm: DynamicRealm, list: RealmList): Flowable> { + return Flowable.just(null) + } + + override fun from(realm: Realm, `object`: E): Flowable { + return Flowable.just(null) + } + + override fun from(realm: DynamicRealm, `object`: DynamicRealmObject): Flowable { + return Flowable.just(null) + } + + override fun from(realm: Realm, query: RealmQuery): Single> { + return Single.just(null) + } + + override fun from(realm: DynamicRealm, query: RealmQuery): Single> { + return Single.just(null) + } + + override fun changesetsFrom(realm: Realm, results: RealmResults): Observable>> { + return Observable.just(null) + } + + override fun changesetsFrom(realm: DynamicRealm, results: RealmResults): Observable>> { + return Observable.just(null) + } + + override fun changesetsFrom(realm: Realm, list: RealmList): Observable>> { + return Observable.just(null) + } + + override fun changesetsFrom(realm: DynamicRealm, list: RealmList): Observable>> { + return Observable.just(null) + } + + override fun changesetsFrom(realm: Realm, `object`: E): Observable> { + return Observable.just(null) + } + + override fun changesetsFrom(realm: DynamicRealm, `object`: DynamicRealmObject): Observable> { + return Observable.just(null) + } + + } + + val configuration1 = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + .rxFactory(factory) + .build() + assertEquals(factory, configuration1.rxFactory) + + val configuration2 = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + .build() + assertNotEquals(factory, configuration2.rxFactory) + } + + @Test + fun flowFactory_defaultNonNull() { + val configuration = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + .build() + assertNotNull(configuration.flowFactory) + } + + @Test + fun flowFactory_nullThrows() { + assertFailsWith { + SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + .flowFactory(TestHelper.getNull()) + }.let { + assertTrue(it.message!!.contains("null")) + } + } + + @Test + fun flowFactory() { + val factory = object : FlowFactory { + override fun from(realm: Realm): Flow { + return flowOf() + } + + override fun from(dynamicRealm: DynamicRealm): Flow { + return flowOf() + } + + override fun from(realm: Realm, results: RealmResults): Flow> { + return flowOf() + } + + override fun from(dynamicRealm: DynamicRealm, results: RealmResults): Flow> { + return flowOf() + } + + override fun from(realm: Realm, realmList: RealmList): Flow> { + return flowOf() + } + + override fun from(dynamicRealm: DynamicRealm, realmList: RealmList): Flow> { + return flowOf() + } + + override fun from(realm: Realm, realmObject: T): Flow { + return flowOf() + } + + override fun from(dynamicRealm: DynamicRealm, dynamicRealmObject: DynamicRealmObject): Flow { + return flowOf() + } + + override fun changesetFrom(realm: Realm, results: RealmResults): Flow>> { + return flowOf() + } + + override fun changesetFrom(dynamicRealm: DynamicRealm, results: RealmResults): Flow>> { + return flowOf() + } + + override fun changesetFrom(realm: Realm, list: RealmList): Flow>> { + return flowOf() + } + + override fun changesetFrom(dynamicRealm: DynamicRealm, list: RealmList): Flow>> { + return flowOf() + } + + override fun changesetFrom(realm: Realm, realmObject: T): Flow> { + return flowOf() + } + + override fun changesetFrom(dynamicRealm: DynamicRealm, dynamicRealmObject: DynamicRealmObject): Flow> { + return flowOf() + } + } + + val configuration1 = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + .flowFactory(factory) + .build() + assertEquals(factory, configuration1.flowFactory) + + val configuration2 = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) + .build() + assertNotEquals(factory, configuration2.flowFactory) + } } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java index e26de17682..6c0fae3ddf 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionImpl.java @@ -25,7 +25,13 @@ abstract class OrderedRealmCollectionImpl extends AbstractList implements private static final String NOT_SUPPORTED_MESSAGE = "This method is not supported by 'RealmResults' or" + " 'OrderedRealmCollectionSnapshot'."; - final BaseRealm realm; + /** + * The {@link BaseRealm} instance in which this collection resides. + *

            + * Warning: This field is only exposed for internal usage, and should not be used. + */ + public final BaseRealm baseRealm; + @Nullable final Class classSpec; // Return type @Nullable final String className; // Class name used by DynamicRealmObjects // FIXME implement this @@ -34,16 +40,16 @@ abstract class OrderedRealmCollectionImpl extends AbstractList implements final OsResults osResults; - OrderedRealmCollectionImpl(BaseRealm realm, OsResults osResults, Class clazz) { - this(realm, osResults, clazz, null); + OrderedRealmCollectionImpl(BaseRealm baseRealm, OsResults osResults, Class clazz) { + this(baseRealm, osResults, clazz, null); } - OrderedRealmCollectionImpl(BaseRealm realm, OsResults osResults, String className) { - this(realm, osResults, null, className); + OrderedRealmCollectionImpl(BaseRealm baseRealm, OsResults osResults, String className) { + this(baseRealm, osResults, null, className); } - private OrderedRealmCollectionImpl(BaseRealm realm, OsResults osResults, @Nullable Class clazz, @Nullable String className) { - this.realm = realm; + private OrderedRealmCollectionImpl(BaseRealm baseRealm, OsResults osResults, @Nullable Class clazz, @Nullable String className) { + this.baseRealm = baseRealm; this.osResults = osResults; this.classSpec = clazz; this.className = className; @@ -113,14 +119,14 @@ public boolean contains(@Nullable Object object) { @Override @Nullable public E get(int location) { - realm.checkIfValid(); + baseRealm.checkIfValid(); if (forValues) { // TODO implement this return null; } //noinspection unchecked - return (E) realm.get((Class) classSpec, className, osResults.getUncheckedRow(location)); + return (E) baseRealm.get((Class) classSpec, className, osResults.getUncheckedRow(location)); } /** @@ -152,7 +158,7 @@ private E firstImpl(boolean shouldThrow, @Nullable E defaultValue) { if (row != null) { //noinspection unchecked - return (E) realm.get((Class) classSpec, className, row); + return (E) baseRealm.get((Class) classSpec, className, row); } else { if (shouldThrow) { throw new IndexOutOfBoundsException("No results were found."); @@ -192,7 +198,7 @@ private E lastImpl(boolean shouldThrow, @Nullable E defaultValue) { if (row != null) { //noinspection unchecked - return (E) realm.get((Class) classSpec, className, row); + return (E) baseRealm.get((Class) classSpec, className, row); } else { if (shouldThrow) { throw new IndexOutOfBoundsException("No results were found."); @@ -208,7 +214,7 @@ private E lastImpl(boolean shouldThrow, @Nullable E defaultValue) { @Override public void deleteFromRealm(int location) { // TODO: Implement the delete in OS level and do check there! - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); osResults.delete(location); } @@ -217,7 +223,7 @@ public void deleteFromRealm(int location) { */ @Override public boolean deleteAllFromRealm() { - realm.checkIfValid(); + baseRealm.checkIfValid(); if (size() > 0) { osResults.clear(); return true; @@ -348,7 +354,7 @@ public int size() { */ @Override public Number min(String fieldName) { - realm.checkIfValid(); + baseRealm.checkIfValid(); long columnKey = getColumnKeyForSort(fieldName); return osResults.aggregateNumber(OsResults.Aggregate.MINIMUM, columnKey); } @@ -358,7 +364,7 @@ public Number min(String fieldName) { */ @Override public Date minDate(String fieldName) { - realm.checkIfValid(); + baseRealm.checkIfValid(); long columnIndex = getColumnKeyForSort(fieldName); return osResults.aggregateDate(OsResults.Aggregate.MINIMUM, columnIndex); } @@ -368,7 +374,7 @@ public Date minDate(String fieldName) { */ @Override public Number max(String fieldName) { - realm.checkIfValid(); + baseRealm.checkIfValid(); long columnIndex = getColumnKeyForSort(fieldName); return osResults.aggregateNumber(OsResults.Aggregate.MAXIMUM, columnIndex); } @@ -386,7 +392,7 @@ public Number max(String fieldName) { @Override @Nullable public Date maxDate(String fieldName) { - realm.checkIfValid(); + baseRealm.checkIfValid(); long columnIndex = getColumnKeyForSort(fieldName); return osResults.aggregateDate(OsResults.Aggregate.MAXIMUM, columnIndex); } @@ -397,7 +403,7 @@ public Date maxDate(String fieldName) { */ @Override public Number sum(String fieldName) { - realm.checkIfValid(); + baseRealm.checkIfValid(); long columnIndex = getColumnKeyForSort(fieldName); return osResults.aggregateNumber(OsResults.Aggregate.SUM, columnIndex); } @@ -407,7 +413,7 @@ public Number sum(String fieldName) { */ @Override public double average(String fieldName) { - realm.checkIfValid(); + baseRealm.checkIfValid(); long columnIndex = getColumnKeyForSort(fieldName); Number avg = osResults.aggregateNumber(OsResults.Aggregate.AVERAGE, columnIndex); @@ -479,7 +485,7 @@ public boolean retainAll(@SuppressWarnings("NullableProblems") java.util.Collect @Override public boolean deleteLastFromRealm() { // TODO: Implement the deleteLast in OS level and do check there! - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); return osResults.deleteLast(); } @@ -491,7 +497,7 @@ public boolean deleteLastFromRealm() { @Override public boolean deleteFirstFromRealm() { // TODO: Implement the deleteLast in OS level and do check there! - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); return osResults.deleteFirst(); } @@ -564,18 +570,18 @@ protected E convertRowToObject(UncheckedRow row) { return null; } //noinspection unchecked - return (E) realm.get((Class) classSpec, className, row); + return (E) baseRealm.get((Class) classSpec, className, row); } } @Override public OrderedRealmCollectionSnapshot createSnapshot() { if (className != null) { - return new OrderedRealmCollectionSnapshot(realm, osResults, className); + return new OrderedRealmCollectionSnapshot(baseRealm, osResults, className); } else { // 'classSpec' is non-null when 'className' is null. //noinspection ConstantConditions - return new OrderedRealmCollectionSnapshot(realm, osResults, classSpec); + return new OrderedRealmCollectionSnapshot(baseRealm, osResults, classSpec); } } @@ -591,11 +597,11 @@ public OrderedRealmCollectionSnapshot createSnapshot() { * {@link Realm} was already closed. */ public Realm getRealm() { - realm.checkIfValid(); - if (!(realm instanceof Realm)) { + baseRealm.checkIfValid(); + if (!(baseRealm instanceof Realm)) { throw new IllegalStateException("This method is only available for typed Realms"); } - return (Realm) realm; + return (Realm) baseRealm; } // Custom RealmResults list iterator. @@ -611,24 +617,24 @@ protected E convertRowToObject(UncheckedRow row) { return null; } //noinspection unchecked - return (E) realm.get((Class) classSpec, className, row); + return (E) baseRealm.get((Class) classSpec, className, row); } } RealmResults createLoadedResults(OsResults newOsResults) { RealmResults results; if (className != null) { - results = new RealmResults(realm, newOsResults, className); + results = new RealmResults(baseRealm, newOsResults, className); } else { // 'classSpec' is non-null when 'className' is null. //noinspection ConstantConditions - results = new RealmResults(realm, newOsResults, classSpec); + results = new RealmResults(baseRealm, newOsResults, classSpec); } results.load(); return results; } private SchemaConnector getSchemaConnector() { - return new SchemaConnector(realm.getSchema()); + return new SchemaConnector(baseRealm.getSchema()); } } diff --git a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java index 67bba47a8f..f37a227067 100644 --- a/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java +++ b/realm/realm-library/src/main/java/io/realm/OrderedRealmCollectionSnapshot.java @@ -156,7 +156,7 @@ public boolean load() { */ @Override public OrderedRealmCollectionSnapshot createSnapshot() { - realm.checkIfValid(); + baseRealm.checkIfValid(); return this; } @@ -178,7 +178,7 @@ public OrderedRealmCollection freeze() { */ @Override public void deleteFromRealm(int location) { - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); UncheckedRow row = osResults.getUncheckedRow(location); if (row.isValid()) { osResults.delete(location); @@ -193,7 +193,7 @@ public void deleteFromRealm(int location) { */ @Override public boolean deleteFirstFromRealm() { - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); UncheckedRow row = osResults.firstUncheckedRow(); return row != null && row.isValid() && osResults.deleteFirst(); } @@ -206,7 +206,7 @@ public boolean deleteFirstFromRealm() { */ @Override public boolean deleteLastFromRealm() { - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); UncheckedRow row = osResults.lastUncheckedRow(); return row != null && row.isValid() && osResults.deleteLast(); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index 90dc3f4f6b..19f0e87d07 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -27,9 +27,12 @@ import java.util.Locale; import java.util.Set; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import io.realm.annotations.RealmModule; +import io.realm.coroutines.FlowFactory; +import io.realm.coroutines.RealmFlowFactory; import io.realm.exceptions.RealmException; import io.realm.internal.OsRealmConfig; import io.realm.internal.RealmCore; @@ -94,6 +97,7 @@ public class RealmConfiguration { private final OsRealmConfig.Durability durability; private final RealmProxyMediator schemaMediator; private final RxObservableFactory rxObservableFactory; + private final FlowFactory flowFactory; private final Realm.Transaction initialDataTransaction; private final boolean readOnly; private final CompactOnLaunchCallback compactOnLaunch; @@ -118,6 +122,7 @@ protected RealmConfiguration(File realmPath, OsRealmConfig.Durability durability, RealmProxyMediator schemaMediator, @Nullable RxObservableFactory rxObservableFactory, + @Nullable FlowFactory flowFactory, @Nullable Realm.Transaction initialDataTransaction, boolean readOnly, @Nullable CompactOnLaunchCallback compactOnLaunch, @@ -136,6 +141,7 @@ protected RealmConfiguration(File realmPath, this.durability = durability; this.schemaMediator = schemaMediator; this.rxObservableFactory = rxObservableFactory; + this.flowFactory = flowFactory; this.initialDataTransaction = initialDataTransaction; this.readOnly = readOnly; this.compactOnLaunch = compactOnLaunch; @@ -261,12 +267,27 @@ public RxObservableFactory getRxFactory() { // Since RxJava doesn't exist, rxObservableFactory is not initialized. if (rxObservableFactory == null) { throw new UnsupportedOperationException("RxJava seems to be missing from the classpath. " + - "Remember to add it as a compile dependency." + + "Remember to add it as an implementation dependency." + " See https://realm.io/docs/java/latest/#rxjava for more details."); } return rxObservableFactory; } + /** + * Returns the {@link FlowFactory} that is used to create Kotlin Flows from Realm objects. + * + * @return the factory instance used to create Flows. + * @throws UnsupportedOperationException if the required coroutines framework is not on the classpath. + */ + public FlowFactory getFlowFactory() { + if (flowFactory == null) { + throw new UnsupportedOperationException("The coroutines framework is missing from the classpath. " + + "Remember to add it as an implementation dependency. " + + "See https://github.com/Kotlin/kotlinx.coroutines#android for more details"); + } + return flowFactory; + } + /** * Returns whether this Realm is read-only or not. Read-only Realms cannot be modified and will throw an * {@link IllegalStateException} if {@link Realm#beginTransaction()} is called on it. @@ -460,7 +481,7 @@ protected boolean isSyncConfiguration() { } protected static RealmConfiguration forRecovery(String canonicalPath, @Nullable byte[] encryptionKey, RealmProxyMediator schemaMediator) { - return new RealmConfiguration(new File(canonicalPath),null, encryptionKey, 0,null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, true, null, true, Long.MAX_VALUE, false, true); + return new RealmConfiguration(new File(canonicalPath),null, encryptionKey, 0, null, false, OsRealmConfig.Durability.FULL, schemaMediator, null, null, null, true, null, true, Long.MAX_VALUE, false, true); } /** @@ -478,7 +499,10 @@ public static class Builder { private OsRealmConfig.Durability durability; private HashSet modules = new HashSet(); private HashSet> debugSchema = new HashSet>(); + @Nullable private RxObservableFactory rxFactory; + @Nullable + private FlowFactory flowFactory; private Realm.Transaction initialDataTransaction; private boolean readOnly; private CompactOnLaunchCallback compactOnLaunch; @@ -701,11 +725,28 @@ public final Builder addModule(Object module) { * * @param factory factory to use. */ - public Builder rxFactory(RxObservableFactory factory) { + public Builder rxFactory(@Nonnull RxObservableFactory factory) { + if (factory == null) { + throw new IllegalArgumentException("The provided Rx Observable factory must not be null."); + } rxFactory = factory; return this; } + /** + * Sets the {@link FlowFactory} used to create coroutines Flows from Realm objects. + * The default factory is {@link RealmFlowFactory}. + * + * @param factory factory to use. + */ + public Builder flowFactory(@Nonnull FlowFactory factory) { + if (factory == null) { + throw new IllegalArgumentException("The provided Flow factory must not be null."); + } + flowFactory = factory; + return this; + } + /** * Sets the initial data in {@link io.realm.Realm}. This transaction will be executed only for the first time * when database file is created or while migrating the data when {@link Builder#deleteRealmIfMigrationNeeded()} is set. @@ -880,6 +921,10 @@ public RealmConfiguration build() { rxFactory = new RealmObservableFactory(true); } + if (flowFactory == null && Util.isCoroutinesAvailable()) { + flowFactory = new RealmFlowFactory(true); + } + return new RealmConfiguration(new File(directory, fileName), assetFilePath, key, @@ -889,6 +934,7 @@ public RealmConfiguration build() { durability, createSchemaMediator(modules, debugSchema), rxFactory, + flowFactory, initialDataTransaction, readOnly, compactOnLaunch, diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 328f094a25..9fab3ad266 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -73,7 +73,14 @@ public class RealmList extends AbstractList implements OrderedRealmCollect // Always null if RealmList is unmanaged, always non-null if managed. private final ManagedListOperator osListOperator; - protected final BaseRealm realm; + + /** + * The {@link BaseRealm} instance in which this list resides. + *

            + * Warning: This field is only exposed for internal usage, and should not be used. + */ + public final BaseRealm baseRealm; + private List unmanagedList; /** @@ -84,7 +91,7 @@ public class RealmList extends AbstractList implements OrderedRealmCollect * Use {@link io.realm.Realm#copyToRealm(Iterable, ImportFlag...)} to properly persist its elements in Realm. */ public RealmList() { - realm = null; + baseRealm = null; osListOperator = null; unmanagedList = new ArrayList<>(); } @@ -103,7 +110,7 @@ public RealmList(E... objects) { if (objects == null) { throw new IllegalArgumentException("The objects argument cannot be null"); } - realm = null; + baseRealm = null; osListOperator = null; unmanagedList = new ArrayList<>(objects.length); Collections.addAll(unmanagedList, objects); @@ -114,18 +121,18 @@ public RealmList(E... objects) { * * @param clazz type of elements in the Array. * @param osList backing {@link OsList}. - * @param realm reference to Realm containing the data. + * @param baseRealm reference to Realm containing the data. */ - RealmList(Class clazz, OsList osList, BaseRealm realm) { + RealmList(Class clazz, OsList osList, BaseRealm baseRealm) { this.clazz = clazz; - osListOperator = getOperator(realm, osList, clazz, null); - this.realm = realm; + osListOperator = getOperator(baseRealm, osList, clazz, null); + this.baseRealm = baseRealm; } - RealmList(String className, OsList osList, BaseRealm realm) { - this.realm = realm; + RealmList(String className, OsList osList, BaseRealm baseRealm) { + this.baseRealm = baseRealm; this.className = className; - osListOperator = getOperator(realm, osList, null, className); + osListOperator = getOperator(baseRealm, osList, null, className); } OsList getOsList() { @@ -141,11 +148,11 @@ long createAndAddEmbeddedObject() { */ @Override public boolean isValid() { - if (realm == null) { + if (baseRealm == null) { return true; } //noinspection SimplifiableIfStatement - if (realm.isClosed()) { + if (baseRealm.isClosed()) { return false; } return isAttached(); @@ -161,7 +168,7 @@ public RealmList freeze() { throw new IllegalStateException("Only valid, managed RealmLists can be frozen."); } - BaseRealm frozenRealm = realm.freeze(); + BaseRealm frozenRealm = baseRealm.freeze(); OsList frozenList = getOsList().freeze(frozenRealm.sharedRealm); if (className != null) { return new RealmList<>(className, frozenList, frozenRealm); @@ -178,7 +185,7 @@ public RealmList freeze() { */ @Override public boolean isFrozen() { - return (realm != null && realm.isFrozen()); + return (baseRealm != null && baseRealm.isFrozen()); } /** @@ -186,7 +193,7 @@ public boolean isFrozen() { */ @Override public boolean isManaged() { - return realm != null; + return baseRealm != null; } private boolean isAttached() { @@ -366,7 +373,7 @@ public E remove(int location) { */ @Override public boolean remove(@Nullable Object object) { - if (isManaged() && !realm.isInTransaction()) { + if (isManaged() && !baseRealm.isInTransaction()) { throw new IllegalStateException(REMOVE_OUTSIDE_TRANSACTION_ERROR); } return super.remove(object); @@ -390,7 +397,7 @@ public boolean remove(@Nullable Object object) { */ @Override public boolean removeAll(Collection collection) { - if (isManaged() && !realm.isInTransaction()) { + if (isManaged() && !baseRealm.isInTransaction()) { throw new IllegalStateException(REMOVE_OUTSIDE_TRANSACTION_ERROR); } return super.removeAll(collection); @@ -719,7 +726,7 @@ public boolean load() { @Override public boolean contains(@Nullable Object object) { if (isManaged()) { - realm.checkIfValid(); + baseRealm.checkIfValid(); // Deleted objects can never be part of a RealmList if (object instanceof RealmObjectProxy) { @@ -771,7 +778,7 @@ public ListIterator listIterator(int location) { } private void checkValidRealm() { - realm.checkIfValid(); + baseRealm.checkIfValid(); } /** @@ -788,15 +795,15 @@ public OrderedRealmCollectionSnapshot createSnapshot() { } if (className != null) { return new OrderedRealmCollectionSnapshot<>( - realm, - OsResults.createFromQuery(realm.sharedRealm, osListOperator.getOsList().getQuery()), + baseRealm, + OsResults.createFromQuery(baseRealm.sharedRealm, osListOperator.getOsList().getQuery()), className); } else { // 'clazz' is non-null when 'dynamicClassName' is null. //noinspection ConstantConditions return new OrderedRealmCollectionSnapshot<>( - realm, - OsResults.createFromQuery(realm.sharedRealm, osListOperator.getOsList().getQuery()), + baseRealm, + OsResults.createFromQuery(baseRealm.sharedRealm, osListOperator.getOsList().getQuery()), clazz); } } @@ -813,14 +820,14 @@ public OrderedRealmCollectionSnapshot createSnapshot() { * {@link Realm} was already closed. */ public Realm getRealm() { - if (realm == null) { + if (baseRealm == null) { return null; } - realm.checkIfValid(); - if (!(realm instanceof Realm)) { + baseRealm.checkIfValid(); + if (!(baseRealm instanceof Realm)) { throw new IllegalStateException("This method is only available for typed Realms"); } - return (Realm) realm; + return (Realm) baseRealm; } @Override @@ -864,7 +871,7 @@ public String toString() { //noinspection ConstantConditions,unchecked if (isClassForRealmModel(clazz)) { //noinspection ConstantConditions,unchecked - sb.append(realm.getSchema().getSchemaForClass((Class) clazz).getClassName()); + sb.append(baseRealm.getSchema().getSchemaForClass((Class) clazz).getClassName()); } else { if (clazz == byte[].class) { sb.append(clazz.getSimpleName()); @@ -945,14 +952,14 @@ public String toString() { */ @SuppressWarnings("unchecked") public Flowable> asFlowable() { - if (realm instanceof Realm) { - return realm.configuration.getRxFactory().from((Realm) realm, this); - } else if (realm instanceof DynamicRealm) { + if (baseRealm instanceof Realm) { + return baseRealm.configuration.getRxFactory().from((Realm) baseRealm, this); + } else if (baseRealm instanceof DynamicRealm) { @SuppressWarnings("UnnecessaryLocalVariable") - Flowable> results = realm.configuration.getRxFactory().from((DynamicRealm) realm, this); + Flowable> results = baseRealm.configuration.getRxFactory().from((DynamicRealm) baseRealm, this); return results; } else { - throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2."); + throw new UnsupportedOperationException(baseRealm.getClass() + " does not support RxJava2."); } } @@ -986,14 +993,14 @@ public Flowable> asFlowable() { * @see RxJava and Realm */ public Observable>> asChangesetObservable() { - if (realm instanceof Realm) { - return realm.configuration.getRxFactory().changesetsFrom((Realm) realm, this); - } else if (realm instanceof DynamicRealm) { - DynamicRealm dynamicRealm = (DynamicRealm) realm; + if (baseRealm instanceof Realm) { + return baseRealm.configuration.getRxFactory().changesetsFrom((Realm) baseRealm, this); + } else if (baseRealm instanceof DynamicRealm) { + DynamicRealm dynamicRealm = (DynamicRealm) baseRealm; RealmList dynamicResults = (RealmList) this; - return (Observable) realm.configuration.getRxFactory().changesetsFrom(dynamicRealm, dynamicResults); + return (Observable) baseRealm.configuration.getRxFactory().changesetsFrom(dynamicRealm, dynamicResults); } else { - throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2."); + throw new UnsupportedOperationException(baseRealm.getClass() + " does not support RxJava2."); } } @@ -1001,8 +1008,8 @@ private void checkForAddRemoveListener(@Nullable Object listener, boolean checkL if (checkListener && listener == null) { throw new IllegalArgumentException("Listener should not be null"); } - realm.checkIfValid(); - realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); + baseRealm.checkIfValid(); + baseRealm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); } /** @@ -1262,7 +1269,7 @@ public int previousIndex() { */ @Override public void set(@Nullable E e) { - realm.checkIfValid(); + baseRealm.checkIfValid(); if (lastRet < 0) { throw new IllegalStateException(); } @@ -1284,7 +1291,7 @@ public void set(@Nullable E e) { */ @Override public void add(@Nullable E e) { - realm.checkIfValid(); + baseRealm.checkIfValid(); checkConcurrentModification(); try { int i = cursor; diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 1b2131324a..0816521548 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -47,6 +47,8 @@ *

          • byte[]
          • *
          • String
          • *
          • Date
          • + *
          • org.bson.types.Decimal128
          • + *
          • org.bson.types.ObjectId
          • *
          • Any RealmObject subclass
          • *
          • RealmList
          • * diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 494b541efa..3addaa8b65 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -130,8 +130,8 @@ static RealmQuery createQueryFromResult(RealmResults queryResults) { static RealmQuery createQueryFromList(RealmList list) { //noinspection ConstantConditions return (list.clazz == null) - ? new RealmQuery(list.realm, list.getOsList(), list.className) - : new RealmQuery(list.realm, list.getOsList(), list.clazz); + ? new RealmQuery(list.baseRealm, list.getOsList(), list.className) + : new RealmQuery(list.baseRealm, list.getOsList(), list.clazz); } private static boolean isClassForRealmModel(Class clazz) { @@ -158,7 +158,7 @@ private RealmQuery(Realm realm, Class clazz) { } private RealmQuery(RealmResults queryResults, Class clazz) { - this.realm = queryResults.realm; + this.realm = queryResults.baseRealm; this.clazz = clazz; this.forValues = !isClassForRealmModel(clazz); if (forValues) { @@ -206,7 +206,7 @@ private RealmQuery(BaseRealm realm, String className) { } private RealmQuery(RealmResults queryResults, String className) { - this.realm = queryResults.realm; + this.realm = queryResults.baseRealm; this.className = className; this.forValues = false; this.schema = realm.getSchema().getSchemaForClass(className); diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index 270fcec8a7..fd091f3cd1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -23,15 +23,12 @@ import org.bson.types.ObjectId; import java.util.Date; -import java.util.List; import java.util.Locale; import javax.annotation.Nullable; import io.reactivex.Flowable; import io.reactivex.Observable; -import io.realm.annotations.Beta; -import io.realm.internal.CheckedRow; import io.realm.internal.OsResults; import io.realm.internal.RealmObjectProxy; import io.realm.internal.Row; @@ -105,7 +102,7 @@ static RealmResults createDynamicBacklinkResults(DynamicReal */ @Override public RealmQuery where() { - realm.checkIfValid(); + baseRealm.checkIfValid(); return RealmQuery.createQueryFromResult(this); } @@ -125,7 +122,7 @@ public RealmResults sort(String fieldName1, Sort sortOrder1, String fieldName */ @Override public boolean isLoaded() { - realm.checkIfValid(); + baseRealm.checkIfValid(); return osResults.isLoaded(); } @@ -141,7 +138,7 @@ public boolean load() { // Instead, accessing the OsResults will just trigger the execution of query if needed. We add this flag is // only to keep the original behavior of those APIs. eg.: For a async RealmResults, before query returns, the // size() call should return 0 instead of running the query get the real size. - realm.checkIfValid(); + baseRealm.checkIfValid(); osResults.load(); return true; } @@ -165,7 +162,7 @@ public boolean load() { */ public void setValue(String fieldName, @Nullable Object value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); boolean isString = (value instanceof String); String strValue = isString ? (String) value : null; @@ -261,7 +258,7 @@ public void setValue(String fieldName, @Nullable Object value) { */ public void setNull(String fieldName) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); osResults.setNull(fieldName); } @@ -274,7 +271,7 @@ public void setNull(String fieldName) { */ public void setBoolean(String fieldName, boolean value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.BOOLEAN); osResults.setBoolean(fieldName, value); @@ -289,7 +286,7 @@ public void setBoolean(String fieldName, boolean value) { */ public void setByte(String fieldName, byte value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.INTEGER); osResults.setInt(fieldName, value); @@ -304,7 +301,7 @@ public void setByte(String fieldName, byte value) { */ public void setShort(String fieldName, short value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.INTEGER); osResults.setInt(fieldName, value); @@ -321,7 +318,7 @@ public void setInt(String fieldName, int value) { checkNonEmptyFieldName(fieldName); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.INTEGER); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); osResults.setInt(fieldName, value); } @@ -334,7 +331,7 @@ public void setInt(String fieldName, int value) { */ public void setLong(String fieldName, long value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.INTEGER); osResults.setInt(fieldName, value); @@ -349,7 +346,7 @@ public void setLong(String fieldName, long value) { */ public void setFloat(String fieldName, float value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.FLOAT); osResults.setFloat(fieldName, value); @@ -364,7 +361,7 @@ public void setFloat(String fieldName, float value) { */ public void setDouble(String fieldName, double value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.DOUBLE); osResults.setDouble(fieldName, value); @@ -379,7 +376,7 @@ public void setDouble(String fieldName, double value) { */ public void setString(String fieldName, @Nullable String value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.STRING); osResults.setString(fieldName, value); @@ -394,7 +391,7 @@ public void setString(String fieldName, @Nullable String value) { */ public void setBlob(String fieldName, @Nullable byte[] value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.BINARY); osResults.setBlob(fieldName, value); @@ -409,7 +406,7 @@ public void setBlob(String fieldName, @Nullable byte[] value) { */ public void setDate(String fieldName, @Nullable Date value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.DATE); osResults.setDate(fieldName, value); @@ -424,7 +421,7 @@ public void setDate(String fieldName, @Nullable Date value) { */ public void setObject(String fieldName, @Nullable RealmModel value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.OBJECT); Row row = checkRealmObjectConstraints(fieldName, value); @@ -440,7 +437,7 @@ public void setObject(String fieldName, @Nullable RealmModel value) { */ public void setDecimal128(String fieldName, @Nullable Decimal128 value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.DECIMAL128); osResults.setDecimal128(fieldName, value); @@ -455,7 +452,7 @@ public void setDecimal128(String fieldName, @Nullable Decimal128 value) { */ public void setObjectId(String fieldName, @Nullable ObjectId value) { checkNonEmptyFieldName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); fieldName = mapFieldNameToInternalName(fieldName); checkType(fieldName, RealmFieldType.OBJECT_ID); osResults.setObjectId(fieldName, value); @@ -467,7 +464,7 @@ private Row checkRealmObjectConstraints(String fieldName, @Nullable RealmModel v throw new IllegalArgumentException("'value' is not a valid, managed Realm object."); } ProxyState proxyState = ((RealmObjectProxy) value).realmGet$proxyState(); - if (!proxyState.getRealm$realm().getPath().equals(realm.getPath())) { + if (!proxyState.getRealm$realm().getPath().equals(baseRealm.getPath())) { throw new IllegalArgumentException("'value' does not belong to the same Realm as the RealmResults."); } @@ -500,7 +497,7 @@ private Row checkRealmObjectConstraints(String fieldName, @Nullable RealmModel v public void setList(String fieldName, RealmList list) { checkNonEmptyFieldName(fieldName); fieldName = mapFieldNameToInternalName(fieldName); - realm.checkIfValidAndInTransaction(); + baseRealm.checkIfValidAndInTransaction(); //noinspection ConstantConditions if (list == null) { @@ -510,7 +507,7 @@ public void setList(String fieldName, RealmList list) { // Due to type erasure of generics it is not possible to have multiple overloaded methods with the same signature. // So instead we fake it by checking the first element in the list and verifies that // against the underlying type. - RealmFieldType columnType = realm.getSchema().getSchemaForClass(osResults.getTable().getClassName()).getFieldType(fieldName); + RealmFieldType columnType = baseRealm.getSchema().getSchemaForClass(osResults.getTable().getClassName()).getFieldType(fieldName); switch (columnType) { case LIST: checkTypeOfListElements(list, RealmModel.class); @@ -587,7 +584,7 @@ public void setList(String fieldName, RealmList list) { */ @Override public boolean isFrozen() { - return realm != null && realm.isFrozen(); + return baseRealm != null && baseRealm.isFrozen(); } /** @@ -599,7 +596,7 @@ public RealmResults freeze() { throw new IllegalStateException("Only valid, managed RealmResults can be frozen."); } - BaseRealm frozenRealm = realm.freeze(); + BaseRealm frozenRealm = baseRealm.freeze(); OsResults frozenResults = osResults.freeze(frozenRealm.sharedRealm); if (className != null) { return new RealmResults<>(frozenRealm, frozenResults, className); @@ -707,8 +704,8 @@ private void checkForAddListener(@Nullable Object listener) { if (listener == null) { throw new IllegalArgumentException("Listener should not be null"); } - realm.checkIfValid(); - realm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); + baseRealm.checkIfValid(); + baseRealm.sharedRealm.capabilities.checkCanDeliverNotification(BaseRealm.LISTENER_NOT_ALLOWED_MESSAGE); } private void checkForRemoveListener(@Nullable Object listener, boolean checkListener) { @@ -716,9 +713,9 @@ private void checkForRemoveListener(@Nullable Object listener, boolean checkList throw new IllegalArgumentException("Listener should not be null"); } - if (realm.isClosed()) { + if (baseRealm.isClosed()) { RealmLog.warn("Calling removeChangeListener on a closed Realm %s, " + - "make sure to close all listeners before closing the Realm.", realm.configuration.getPath()); + "make sure to close all listeners before closing the Realm.", baseRealm.configuration.getPath()); } } @@ -801,18 +798,18 @@ public void removeChangeListener(OrderedRealmCollectionChangeListener> asFlowable() { - if (realm instanceof Realm) { - return realm.configuration.getRxFactory().from((Realm) realm, this); + if (baseRealm instanceof Realm) { + return baseRealm.configuration.getRxFactory().from((Realm) baseRealm, this); } - if (realm instanceof DynamicRealm) { - DynamicRealm dynamicRealm = (DynamicRealm) realm; + if (baseRealm instanceof DynamicRealm) { + DynamicRealm dynamicRealm = (DynamicRealm) baseRealm; RealmResults dynamicResults = (RealmResults) this; @SuppressWarnings("UnnecessaryLocalVariable") - Flowable results = realm.configuration.getRxFactory().from(dynamicRealm, dynamicResults); + Flowable results = baseRealm.configuration.getRxFactory().from(dynamicRealm, dynamicResults); return results; } else { - throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2."); + throw new UnsupportedOperationException(baseRealm.getClass() + " does not support RxJava2."); } } @@ -846,14 +843,14 @@ public Flowable> asFlowable() { * @see RxJava and Realm */ public Observable>> asChangesetObservable() { - if (realm instanceof Realm) { - return realm.configuration.getRxFactory().changesetsFrom((Realm) realm, this); - } else if (realm instanceof DynamicRealm) { - DynamicRealm dynamicRealm = (DynamicRealm) realm; + if (baseRealm instanceof Realm) { + return baseRealm.configuration.getRxFactory().changesetsFrom((Realm) baseRealm, this); + } else if (baseRealm instanceof DynamicRealm) { + DynamicRealm dynamicRealm = (DynamicRealm) baseRealm; RealmResults dynamicResults = (RealmResults) this; - return (Observable) realm.configuration.getRxFactory().changesetsFrom(dynamicRealm, dynamicResults); + return (Observable) baseRealm.configuration.getRxFactory().changesetsFrom(dynamicRealm, dynamicResults); } else { - throw new UnsupportedOperationException(realm.getClass() + " does not support RxJava2."); + throw new UnsupportedOperationException(baseRealm.getClass() + " does not support RxJava2."); } } @@ -886,7 +883,7 @@ private void checkNotNull(@Nullable Object value) { private void checkType(String fieldName, RealmFieldType expectedFieldType) { String className = osResults.getTable().getClassName(); - RealmFieldType fieldType = realm.getSchema().get(className).getFieldType(fieldName); + RealmFieldType fieldType = baseRealm.getSchema().get(className).getFieldType(fieldName); if (fieldType != expectedFieldType) { throw new IllegalArgumentException(String.format("The field '%s.%s' is not of the expected type. " + "Actual: %s, Expected: %s", className, fieldName, fieldType, expectedFieldType)); @@ -894,10 +891,10 @@ private void checkType(String fieldName, RealmFieldType expectedFieldType) { } private String mapFieldNameToInternalName(String fieldName) { - if (realm instanceof Realm) { + if (baseRealm instanceof Realm) { // We only need to map field names from typed Realms. String className = osResults.getTable().getClassName(); - String mappedFieldName = realm.getSchema().getColumnInfo(className).getInternalFieldName(fieldName); + String mappedFieldName = baseRealm.getSchema().getColumnInfo(className).getInternalFieldName(fieldName); if (mappedFieldName == null) { throw new IllegalArgumentException(String.format("Field '%s' does not exists.", fieldName)); } else { diff --git a/realm/realm-library/src/main/java/io/realm/coroutines/FlowFactory.java b/realm/realm-library/src/main/java/io/realm/coroutines/FlowFactory.java new file mode 100644 index 0000000000..ff2e39fdb6 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/coroutines/FlowFactory.java @@ -0,0 +1,224 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.coroutines; + +import javax.annotation.Nonnull; + +import io.reactivex.Observable; +import io.realm.DynamicRealm; +import io.realm.DynamicRealmObject; +import io.realm.Realm; +import io.realm.RealmList; +import io.realm.RealmModel; +import io.realm.RealmObject; +import io.realm.RealmResults; +import io.realm.annotations.Beta; +import io.realm.rx.CollectionChange; +import io.realm.rx.ObjectChange; +import kotlinx.coroutines.flow.Flow; + +/** + * Factory interface for creating Kotlin {@link Flow}s for Realm classes. + */ +@Beta +public interface FlowFactory { + + /** + * Creates a {@link Flow} for a {@link Realm}. It should emit the initial state of the Realm when subscribed to and + * on each subsequent update of the Realm. + * + * @param realm {@link Realm} instance being observed for changes to be emitted by the flow. + * @return Flow that emits all updates to the Realm. + */ + @Beta + Flow from(@Nonnull Realm realm); + + /** + * Creates a {@link Flow} for a {@link DynamicRealm}. It should emit the initial state of the Realm when subscribed to and + * on each subsequent update of the Realm. + * + * @param dynamicRealm {@link DynamicRealm} instance being observed for changes to be emitted by the flow. + * @return Flow that emits all updates to the Realm. + */ + @Beta + Flow from(@Nonnull DynamicRealm dynamicRealm); + + /** + * Creates a {@link Flow} for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and + * on each subsequent update of the RealmResults. + * + * @param results {@link RealmResults} instance being observed for changes to be emitted by the flow. + * @param realm {@link Realm} instance from where the results are coming. + * @param type of RealmObject. + * @return {@link Flow} that emits all updates to the RealmObject. + */ + @Beta + Flow> from(@Nonnull Realm realm, @Nonnull RealmResults results); + + /** + * Creates a {@link Flow} for a {@link RealmResults} instance. It should emit the initial results when subscribed to and on each + * subsequent update of the results it should emit the results plus the {@link io.realm.rx.CollectionChange} that describes + * the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefore be left to the user. + * + * @param realm {@link Realm} instance from where the object is coming. + * @param results {@link RealmResults} instance being observed for changes to be emitted by the flow. + * @return {@link Flow} that emits all updates to the RealmResults. + */ + @Beta + Flow>> changesetFrom(@Nonnull Realm realm, @Nonnull RealmResults results); + + /** + * Creates a {@link Flow} for a {@link RealmResults}. It should emit the initial RealmResult when subscribed to and + * on each subsequent update of the RealmResults. + * + * @param results {@link RealmResults} instance being observed for changes to be emitted by the flow. + * @param dynamicRealm {@link DynamicRealm} instance from where the results are coming. + * @param type of RealmObject. + * @return {@link Flow} that emits all updates to the RealmObject. + */ + @Beta + Flow> from(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmResults results); + + /** + * Creates a {@link Flow} for a {@link RealmResults} instance. It should emit the initial results when subscribed to and on each + * subsequent update of the results it should emit the results plus the {@link io.realm.rx.CollectionChange} that describes + * the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefore be left to the user. + * + * @param dynamicRealm {@link DynamicRealm} instance from where the object is coming. + * @param results {@link RealmResults} instance being observed for changes to be emitted by the flow. + * @return {@link Flow} that emits all updates to the RealmResults. + */ + @Beta + Flow>> changesetFrom(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmResults results); + + /** + * Creates a {@link Flow} for a {@link RealmList}. It should emit the initial RealmResult when subscribed to and + * on each subsequent update of the RealmList. + *

            + * Note: {@link io.realm.RealmChangeListener} is currently not supported on RealmLists. + * + * @param realmList {@link RealmList} instance being observed for changes to be emitted by the flow. + * @param realm {@link Realm} instance from where the results are coming. + * @param type of RealmObject + * @return {@link Flow} that emit all updates to the RealmList. + */ + @Beta + Flow> from(@Nonnull Realm realm, @Nonnull RealmList realmList); + + /** + * Creates a {@link Flow} for a {@link RealmList}. It should emit the initial list when subscribed to and on each + * subsequent update of the list it should emit the list plus the {@link io.realm.rx.CollectionChange} that describes + * the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefore be left to the user. + * + * @param realm {@link Realm} instance from where the object is coming. + * @param list {@link RealmList} instance being observed for changes to be emitted by the flow. + * @return {@link Flow} that emits all updates to the RealmList. + */ + @Beta + Flow>> changesetFrom(@Nonnull Realm realm, @Nonnull RealmList list); + + /** + * Creates a {@link Flow} for a {@link RealmList}. It should emit the initial RealmResult when subscribed to and + * on each subsequent update of the RealmList. + *

            + * Note: {@link io.realm.RealmChangeListener} is currently not supported on RealmLists. + * + * @param realmList {@link RealmList} instance being observed for changes to be emitted by the flow. + * @param dynamicRealm {@link DynamicRealm} instance from where the results are coming. + * @param type of RealmObject + * @return {@link Flow} that emit all updates to the RealmList. + */ + @Beta + Flow> from(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmList realmList); + + /** + * Creates a {@link Flow} for a {@link RealmList}. It should emit the initial list when subscribed to and on each + * subsequent update of the list it should emit the list plus the {@link io.realm.rx.CollectionChange} that describes + * the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefore be left to the user. + * + * @param dynamicRealm {@link DynamicRealm} instance from where the object is coming. + * @param list {@link RealmList} instance being observed for changes to be emitted by the flow. + * @return {@link Flow} that emits all updates to the RealmList. + */ + @Beta + Flow>> changesetFrom(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmList list); + + /** + * Creates a {@link Flow} for a {@link RealmObject}. It should emit the initial object when subscribed to and on each + * subsequent update of the object. + * + * @param realmObject {@link RealmObject} instance being observed for changes to be emitted by the flow. + * @param realm {@link Realm} instance from where the object is coming. + * @param type of query target + * @return {@link Flow} that emits all updates to the DynamicRealmObject. + */ + @Beta + Flow from(@Nonnull Realm realm, @Nonnull T realmObject); + + /** + * Creates a {@link Flow} for a {@link RealmObject}. It should emit the initial object when subscribed to and on each + * subsequent update of the object it should emit the object plus the {@link io.realm.ObjectChangeSet} that describes + * the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefore be left to the user. + * + * @param realm {@link Realm} instance from where the object is coming. + * @param realmObject {@link RealmObject} instance being observed for changes to be emitted by the flow. + * @return {@link Flow} that emits all updates to the DynamicRealmObject. + */ + @Beta + Flow> changesetFrom(@Nonnull Realm realm, @Nonnull T realmObject); + + /** + * Creates a {@link Flow} for a {@link DynamicRealmObject}. It should emit the initial object when subscribed to and + * on each subsequent update of the object. + * + * @param dynamicRealm {@link DynamicRealm} instance from where the object is coming. + * @param dynamicRealmObject {@link DynamicRealmObject} instance being observed for changes to be emitted by the flow. + * @return {@link Flow} that emits all updates to the DynamicRealmObject. + */ + @Beta + Flow from(@Nonnull DynamicRealm dynamicRealm, @Nonnull DynamicRealmObject dynamicRealmObject); + + /** + * Creates a {@link Flow} for a {@link DynamicRealmObject}. It should emit the initial object when subscribed to and on each + * subsequent update of the object it should emit the object plus the {@link io.realm.ObjectChangeSet} that describes + * the update. + *

            + * Changeset observables do not support backpressure as a changeset depends on the state of the previous + * changeset. Handling backpressure should therefore be left to the user. + * + * @param dynamicRealm {@link DynamicRealm} instance from where the object is coming. + * @param dynamicRealmObject {@link DynamicRealmObject} instance being observed for changes to be emitted by the flow. + * @return {@link Flow} that emits all updates to the DynamicRealmObject. + */ + @Beta + Flow> changesetFrom(@Nonnull DynamicRealm dynamicRealm, @Nonnull DynamicRealmObject dynamicRealmObject); +} diff --git a/realm/realm-library/src/main/java/io/realm/coroutines/RealmFlowFactory.java b/realm/realm-library/src/main/java/io/realm/coroutines/RealmFlowFactory.java new file mode 100644 index 0000000000..df7d4a3e32 --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/coroutines/RealmFlowFactory.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.coroutines; + +import javax.annotation.Nonnull; + +import io.realm.DynamicRealm; +import io.realm.DynamicRealmObject; +import io.realm.Realm; +import io.realm.RealmList; +import io.realm.RealmModel; +import io.realm.RealmResults; +import io.realm.annotations.Beta; +import io.realm.internal.coroutines.InternalFlowFactory; +import io.realm.rx.CollectionChange; +import io.realm.rx.ObjectChange; +import kotlinx.coroutines.flow.Flow; + +/** + * Factory class used to create coroutine {@link Flow}s. + *

            + * This class is used by default unless overridden in {@link io.realm.RealmConfiguration.Builder#flowFactory(FlowFactory)}. + */ +@Beta +public class RealmFlowFactory implements FlowFactory { + + private final InternalFlowFactory factory; + + /** + * Constructor for the Flow factory. + * + * @param returnFrozenObjects whether the emissions should return frozen objects or not. + */ + public RealmFlowFactory(Boolean returnFrozenObjects) { + this.factory = new InternalFlowFactory(returnFrozenObjects); + } + + @Override + public Flow from(@Nonnull Realm realm) { + return factory.from(realm); + } + + @Override + public Flow from(@Nonnull DynamicRealm dynamicRealm) { + return factory.from(dynamicRealm); + } + + @Override + public Flow> from(@Nonnull Realm realm, @Nonnull RealmResults results) { + return factory.from(realm, results); + } + + @Override + public Flow>> changesetFrom(@Nonnull Realm realm, @Nonnull RealmResults results) { + return factory.changesetFrom(realm, results); + } + + @Override + public Flow> from(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmResults results) { + return factory.from(dynamicRealm, results); + } + + @Override + public Flow>> changesetFrom(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmResults results) { + return factory.changesetFrom(dynamicRealm, results); + } + + @Override + public Flow> from(@Nonnull Realm realm, @Nonnull RealmList realmList) { + return factory.from(realm, realmList); + } + + @Override + public Flow>> changesetFrom(@Nonnull Realm realm, @Nonnull RealmList list) { + return factory.changesetFrom(realm, list); + } + + @Override + public Flow> from(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmList realmList) { + return factory.from(dynamicRealm, realmList); + } + + @Override + public Flow>> changesetFrom(@Nonnull DynamicRealm dynamicRealm, @Nonnull RealmList list) { + return factory.changesetFrom(dynamicRealm, list); + } + + @Override + public Flow from(@Nonnull Realm realm, @Nonnull T realmObject) { + return factory.from(realm, realmObject); + } + + @Override + public Flow> changesetFrom(@Nonnull Realm realm, @Nonnull T realmObject) { + return factory.changesetFrom(realm, realmObject); + } + + @Override + public Flow from(@Nonnull DynamicRealm dynamicRealm, @Nonnull DynamicRealmObject dynamicRealmObject) { + return factory.from(dynamicRealm, dynamicRealmObject); + } + + @Override + public Flow> changesetFrom(@Nonnull DynamicRealm dynamicRealm, @Nonnull DynamicRealmObject dynamicRealmObject) { + return factory.changesetFrom(dynamicRealm, dynamicRealmObject); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/internal/Util.java b/realm/realm-library/src/main/java/io/realm/internal/Util.java index 83ae8289b2..a3b142a651 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Util.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Util.java @@ -39,6 +39,7 @@ public class Util { private static Boolean rxJavaAvailable; + private static Boolean coroutinesAvailable; public static String getTablePrefix() { return nativeGetTablePrefix(); @@ -204,9 +205,9 @@ public static void checkNotOnMainThread(String errorMessage) { } /** - * Checks if RxJava is can be loaded. + * Checks if RxJava is present and can be loaded. * - * @return {@code true} if RxJava dependency exist, {@code false} otherwise. + * @return {@code true} if RxJava dependency exists, {@code false} otherwise. */ @SuppressWarnings("LiteralClassName") public static synchronized boolean isRxJavaAvailable() { @@ -221,6 +222,23 @@ public static synchronized boolean isRxJavaAvailable() { return rxJavaAvailable; } + /** + * Checks if the coroutines framework is present and can be loaded. + * + * @return {@code true} if the coroutines dependency exists, {@code false} otherwise. + */ + public static synchronized boolean isCoroutinesAvailable() { + if (coroutinesAvailable == null) { + try { + Class.forName("kotlinx.coroutines.flow.Flow"); + coroutinesAvailable = true; + } catch (ClassNotFoundException ignore) { + coroutinesAvailable = false; + } + } + return coroutinesAvailable; + } + /** * Validates that a key is present in a given map * diff --git a/realm/realm-library/src/main/java/io/realm/internal/coroutines/InternalFlowFactory.kt b/realm/realm-library/src/main/java/io/realm/internal/coroutines/InternalFlowFactory.kt new file mode 100644 index 0000000000..406cc015bf --- /dev/null +++ b/realm/realm-library/src/main/java/io/realm/internal/coroutines/InternalFlowFactory.kt @@ -0,0 +1,666 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.internal.coroutines + +import io.realm.* +import io.realm.annotations.Beta +import io.realm.coroutines.FlowFactory +import io.realm.rx.CollectionChange +import io.realm.rx.ObjectChange +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOf + +/** + * Internal factory implementation used to conceal Kotlin implementation details from the public + * API and to avoid having to use Kotlin's documentation solution for just one class. + */ +class InternalFlowFactory( + private val returnFrozenObjects: Boolean = true +) : FlowFactory { + + @Beta + override fun from(realm: Realm): Flow { + if (realm.isFrozen) { + return flowOf(realm) + } + + return callbackFlow { + val flowRealm = Realm.getInstance(realm.configuration) + val listener = RealmChangeListener { listenerRealm -> + if (returnFrozenObjects) { + offer(realm.freeze()) + } else { + offer(listenerRealm) + } + } + + flowRealm.addChangeListener(listener) + + if (returnFrozenObjects) { + offer(flowRealm.freeze()) + } else { + offer(flowRealm) + } + + awaitClose { + flowRealm.removeChangeListener(listener) + flowRealm.close() + } + } + } + + @Beta + override fun from(dynamicRealm: DynamicRealm): Flow { + if (dynamicRealm.isFrozen) { + return flowOf(dynamicRealm) + } + + return callbackFlow { + val flowRealm = DynamicRealm.getInstance(dynamicRealm.configuration) + val listener = RealmChangeListener { listenerRealm -> + if (returnFrozenObjects) { + offer(dynamicRealm.freeze()) + } else { + offer(listenerRealm) + } + } + + flowRealm.addChangeListener(listener) + + if (returnFrozenObjects) { + offer(flowRealm.freeze()) + } else { + offer(flowRealm) + } + + awaitClose { + flowRealm.removeChangeListener(listener) + flowRealm.close() + } + } + } + + @Beta + override fun from(realm: Realm, results: RealmResults): Flow> { + // Return "as is" if frozen, there will be no listening for changes + if (realm.isFrozen) { + return flowOf(results) + } + + val config = realm.configuration + + return callbackFlow { + // Do nothing if the results are invalid + if (!results.isValid) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = Realm.getInstance(config) + val listener = RealmChangeListener> { listenerResults -> + if (returnFrozenObjects) { + offer(listenerResults.freeze()) + } else { + offer(listenerResults) + } + } + + results.addChangeListener(listener) + + // Emit current value + if (returnFrozenObjects) { + offer(results.freeze()) + } else { + offer(results) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + results.removeChangeListener(listener) + flowRealm.close() + } + } + } + } + + override fun changesetFrom( + realm: Realm, + results: RealmResults + ): Flow>> { + // Return "as is" if frozen, there will be no listening for changes + if (realm.isFrozen) { + return flowOf(CollectionChange(results, null)) + } + + val config = realm.configuration + + return callbackFlow { + // Do nothing if the results are invalid + if (!results.isValid) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = Realm.getInstance(config) + val listener = OrderedRealmCollectionChangeListener> { listenerResults, changeSet -> + if (returnFrozenObjects) { + offer(CollectionChange(listenerResults.freeze(), changeSet)) + } else { + offer(CollectionChange(listenerResults, changeSet)) + } + } + + results.addChangeListener(listener) + + // Emit current value + if (returnFrozenObjects) { + offer(CollectionChange(results.freeze(), null)) + } else { + offer(CollectionChange(results, null)) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + results.removeChangeListener(listener) + flowRealm.close() + } + } + } + } + + override fun from( + dynamicRealm: DynamicRealm, + results: RealmResults + ): Flow> { + // Return "as is" if frozen, there will be no listening for changes + if (dynamicRealm.isFrozen) { + return flowOf(results) + } + + val config = dynamicRealm.configuration + + return callbackFlow { + // Do nothing if the results are invalid + if (!results.isValid) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = DynamicRealm.getInstance(config) + val listener = RealmChangeListener> { listenerResults -> + if (returnFrozenObjects) { + offer(listenerResults.freeze()) + } else { + offer(listenerResults) + } + } + + results.addChangeListener(listener) + + // Emit current value + if (returnFrozenObjects) { + offer(results.freeze()) + } else { + offer(results) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + results.removeChangeListener(listener) + flowRealm.close() + } + } + } + } + + override fun changesetFrom( + dynamicRealm: DynamicRealm, + results: RealmResults + ): Flow>> { + // Return "as is" if frozen, there will be no listening for changes + if (dynamicRealm.isFrozen) { + return flowOf(CollectionChange(results, null)) + } + + val config = dynamicRealm.configuration + + return callbackFlow { + // Do nothing if the results are invalid + if (!results.isValid) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = DynamicRealm.getInstance(config) + val listener = OrderedRealmCollectionChangeListener> { listenerResults, changeSet -> + if (returnFrozenObjects) { + offer(CollectionChange(listenerResults.freeze(), changeSet)) + } else { + offer(CollectionChange(listenerResults, changeSet)) + } + } + + results.addChangeListener(listener) + + // Emit current value + if (returnFrozenObjects) { + offer(CollectionChange(results.freeze(), null)) + } else { + offer(CollectionChange(results, null)) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + results.removeChangeListener(listener) + flowRealm.close() + } + } + } + } + + @Beta + override fun from(realm: Realm, realmList: RealmList): Flow> { + // Return "as is" if frozen, there will be no listening for changes + if (realm.isFrozen) { + return flowOf(realmList) + } + + val config = realm.configuration + + return callbackFlow { + // Do nothing if the results are invalid + if (!realmList.isValid) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = Realm.getInstance(config) + val listener = RealmChangeListener> { listenerResults -> + if (returnFrozenObjects) { + offer(listenerResults.freeze()) + } else { + offer(listenerResults) + } + } + + realmList.addChangeListener(listener) + + // Emit current value + if (returnFrozenObjects) { + offer(realmList.freeze()) + } else { + offer(realmList) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + realmList.removeChangeListener(listener) + flowRealm.close() + } + } + } + } + + override fun changesetFrom( + realm: Realm, + list: RealmList + ): Flow>> { + // Return "as is" if frozen, there will be no listening for changes + if (realm.isFrozen) { + return flowOf(CollectionChange(list, null)) + } + + val config = realm.configuration + + return callbackFlow { + // Do nothing if the results are invalid + if (!list.isValid) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = Realm.getInstance(config) + val listener = OrderedRealmCollectionChangeListener> { listenerList, changeSet -> + if (returnFrozenObjects) { + offer(CollectionChange(listenerList.freeze(), changeSet)) + } else { + offer(CollectionChange(listenerList, changeSet)) + } + } + + list.addChangeListener(listener) + + // Emit current value + if (returnFrozenObjects) { + offer(CollectionChange(list.freeze(), null)) + } else { + offer(CollectionChange(list, null)) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + list.removeChangeListener(listener) + flowRealm.close() + } + } + } + } + + override fun from(dynamicRealm: DynamicRealm, realmList: RealmList): Flow> { + // Return "as is" if frozen, there will be no listening for changes + if (dynamicRealm.isFrozen) { + return flowOf(realmList) + } + + val config = dynamicRealm.configuration + + return callbackFlow { + // Do nothing if the results are invalid + if (!realmList.isValid) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = DynamicRealm.getInstance(config) + val listener = RealmChangeListener> { listenerResults -> + if (returnFrozenObjects) { + offer(listenerResults.freeze()) + } else { + offer(listenerResults) + } + } + + realmList.addChangeListener(listener) + + // Emit current value + if (returnFrozenObjects) { + offer(realmList.freeze()) + } else { + offer(realmList) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + realmList.removeChangeListener(listener) + flowRealm.close() + } + } + } + } + + override fun changesetFrom( + dynamicRealm: DynamicRealm, + list: RealmList + ): Flow>> { + // Return "as is" if frozen, there will be no listening for changes + if (dynamicRealm.isFrozen) { + return flowOf(CollectionChange(list, null)) + } + + val config = dynamicRealm.configuration + + return callbackFlow { + // Do nothing if the results are invalid + if (!list.isValid) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = DynamicRealm.getInstance(config) + val listener = OrderedRealmCollectionChangeListener> { listenerList, changeSet -> + if (returnFrozenObjects) { + offer(CollectionChange(listenerList.freeze(), changeSet)) + } else { + offer(CollectionChange(listenerList, changeSet)) + } + } + + list.addChangeListener(listener) + + // Emit current value + if (returnFrozenObjects) { + offer(CollectionChange(list.freeze(), null)) + } else { + offer(CollectionChange(list, null)) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + list.removeChangeListener(listener) + flowRealm.close() + } + } + } + } + + @Beta + override fun from(realm: Realm, realmObject: T): Flow { + // Return "as is" if frozen, there will be no listening for changes + if (realm.isFrozen) { + return flowOf(realmObject) + } + + val config = realm.configuration + + return callbackFlow { + // Check if the Realm is closed (instead of using isValid - findFirstAsync always return "invalid object" right away, which would render this logic useless + if (realm.isClosed) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = Realm.getInstance(config) + val listener = RealmChangeListener { listenerObj -> + if (returnFrozenObjects) { + offer(RealmObject.freeze(listenerObj) as T) + } else { + offer(listenerObj) + } + } + + RealmObject.addChangeListener(realmObject, listener) + + // Emit current value + if (returnFrozenObjects) { + offer(RealmObject.freeze(realmObject)) + } else { + offer(realmObject) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + RealmObject.removeChangeListener(realmObject, listener) + flowRealm.close() + } + } + } + } + + override fun changesetFrom( + realm: Realm, + realmObject: T + ): Flow> { + // Return "as is" if frozen, there will be no listening for changes + if (realm.isFrozen) { + return flowOf(ObjectChange(realmObject, null)) + } + + val config = realm.configuration + + return callbackFlow { + // Check if the Realm is closed (instead of using isValid - findFirstAsync always return "invalid object" right away, which would render this logic useless + if (realm.isClosed) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = Realm.getInstance(config) + val listener = RealmObjectChangeListener { listenerObject, changeSet -> + if (returnFrozenObjects) { + offer(ObjectChange(RealmObject.freeze(listenerObject), changeSet)) + } else { + offer(ObjectChange(listenerObject, changeSet)) + } + } + + RealmObject.addChangeListener(realmObject, listener) + + // Emit current value + if (returnFrozenObjects) { + offer(ObjectChange(RealmObject.freeze(realmObject), null)) + } else { + offer(ObjectChange(realmObject, null)) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + RealmObject.removeChangeListener(realmObject, listener) + flowRealm.close() + } + } + } + } + + @Beta + override fun from( + dynamicRealm: DynamicRealm, + dynamicRealmObject: DynamicRealmObject + ): Flow { + // Return "as is" if frozen, there will be no listening for changes + if (dynamicRealm.isFrozen) { + return flowOf(dynamicRealmObject) + } + + val config = dynamicRealm.configuration + + return callbackFlow { + // Check if the Realm is closed (instead of using isValid - findFirstAsync always return "invalid object" right away, which would render this logic useless + if (dynamicRealm.isClosed) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = DynamicRealm.getInstance(config) + val listener = RealmChangeListener { listenerObj -> + if (returnFrozenObjects) { + offer(listenerObj.freeze()) + } else { + offer(listenerObj) + } + } + + dynamicRealmObject.addChangeListener(listener) + + // Emit current value + if (returnFrozenObjects) { + offer(RealmObject.freeze(dynamicRealmObject)) + } else { + offer(dynamicRealmObject) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + dynamicRealmObject.removeChangeListener(listener) + flowRealm.close() + } + } + } + } + + override fun changesetFrom( + dynamicRealm: DynamicRealm, + dynamicRealmObject: DynamicRealmObject + ): Flow> { + // Return "as is" if frozen, there will be no listening for changes + if (dynamicRealm.isFrozen) { + return flowOf(ObjectChange(dynamicRealmObject, null)) + } + + val config = dynamicRealm.configuration + + return callbackFlow { + // Do nothing if the results are invalid + if (!RealmObject.isValid(dynamicRealmObject)) { + awaitClose {} + + return@callbackFlow + } + + // Get instance to ensure the Realm is open for as long as we are listening + val flowRealm = Realm.getInstance(config) + val listener = RealmObjectChangeListener { listenerObject, changeSet -> + if (returnFrozenObjects) { + offer(ObjectChange(RealmObject.freeze(listenerObject), changeSet)) + } else { + offer(ObjectChange(listenerObject, changeSet)) + } + } + + RealmObject.addChangeListener(dynamicRealmObject, listener) + + // Emit current value + if (returnFrozenObjects) { + offer(ObjectChange(RealmObject.freeze(dynamicRealmObject), null)) + } else { + offer(ObjectChange(dynamicRealmObject, null)) + } + + awaitClose { + // Remove listener and cleanup + if (!flowRealm.isClosed) { + RealmObject.removeChangeListener(dynamicRealmObject, listener) + flowRealm.close() + } + } + } + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index d489f81637..c25d91c2dd 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -39,6 +39,7 @@ import java.util.Locale; import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -51,13 +52,15 @@ import io.realm.RealmQuery; import io.realm.annotations.Beta; import io.realm.annotations.RealmModule; +import io.realm.coroutines.FlowFactory; +import io.realm.coroutines.RealmFlowFactory; import io.realm.exceptions.RealmException; import io.realm.internal.OsRealmConfig; import io.realm.internal.RealmProxyMediator; import io.realm.internal.Util; import io.realm.mongodb.App; -import io.realm.mongodb.User; import io.realm.mongodb.Credentials; +import io.realm.mongodb.User; import io.realm.rx.RealmObservableFactory; import io.realm.rx.RxObservableFactory; @@ -117,6 +120,7 @@ private SyncConfiguration(File realmPath, OsRealmConfig.Durability durability, RealmProxyMediator schemaMediator, @Nullable RxObservableFactory rxFactory, + @Nullable FlowFactory flowFactory, @Nullable Realm.Transaction initialDataTransaction, boolean readOnly, long maxNumberOfActiveVersions, @@ -143,6 +147,7 @@ private SyncConfiguration(File realmPath, durability, schemaMediator, rxFactory, + flowFactory, initialDataTransaction, readOnly, compactOnLaunch, @@ -467,6 +472,8 @@ public static final class Builder { @Nullable private RxObservableFactory rxFactory; @Nullable + private FlowFactory flowFactory; + @Nullable private Realm.Transaction initialDataTransaction; @Nullable private String filename; @@ -788,11 +795,28 @@ public Builder addModule(Object module) { * * @param factory factory to use. */ - public Builder rxFactory(RxObservableFactory factory) { + public Builder rxFactory(@Nonnull RxObservableFactory factory) { + if (factory == null) { + throw new IllegalArgumentException("The provided Rx Observable factory must not be null."); + } rxFactory = factory; return this; } + /** + * Sets the {@link FlowFactory} used to create coroutines Flows from Realm objects. + * The default factory is {@link RealmFlowFactory}. + * + * @param factory factory to use. + */ + public Builder flowFactory(@Nonnull FlowFactory factory) { + if (factory == null) { + throw new IllegalArgumentException("The provided Flow factory must not be null."); + } + flowFactory = factory; + return this; + } + /** * Sets the initial data in {@link io.realm.Realm}. This transaction will be executed only the first time * the Realm file is opened (created) or while migrating the data if @@ -1080,6 +1104,10 @@ public SyncConfiguration build() { rxFactory = new RealmObservableFactory(true); } + if (flowFactory == null && Util.isCoroutinesAvailable()) { + flowFactory = new RealmFlowFactory(true); + } + URI resolvedServerUrl = serverUrl; syncUrlPrefix = String.format("/api/client/v2.0/app/%s/realm-sync", user.getApp().getConfiguration().getAppId()); @@ -1096,6 +1124,7 @@ public SyncConfiguration build() { durability, createSchemaMediator(modules, debugSchema), rxFactory, + flowFactory, initialDataTransaction, readOnly, maxNumberOfActiveVersions, diff --git a/version.txt b/version.txt index a27abb9ad0..bb2a9abc36 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.0.2-SNAPSHOT \ No newline at end of file +10.1.0-SNAPSHOT From 8b4d9459b46c862b9fabb2e84c17dab894af9d73 Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 17 Nov 2020 18:39:10 +0100 Subject: [PATCH 1746/2110] Realm CLI tool (#7199) --- tools/realm-cli.sh | 185 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100755 tools/realm-cli.sh diff --git a/tools/realm-cli.sh b/tools/realm-cli.sh new file mode 100755 index 0000000000..91ccd0a71b --- /dev/null +++ b/tools/realm-cli.sh @@ -0,0 +1,185 @@ +#!/bin/bash + +# This script provides a single entry point to the different tools used during the +# realm-java development. +# +# It needs REALM_JAVA_PATH to be set to the realm-java repository path. +# +# Create an alias for convenient access, for example in .zshrc: +# alias realm="~/realm-java/tools/realm-cli.sh" + + +is_server_active(){ + return `docker container ls | grep -Fq mongodb-realm` +} + +start_server(){ + if is_server_active + then + echo "Warning: Sync server already runnning" + else + echo -n "Starting sync server..." + $REALM_JAVA_PATH/tools/sync_test_server/start_server.sh > /dev/null 2>&1 + echo " done" + fi +} + +stop_server(){ + if is_server_active + then + echo -n "Stopping sync server..." + $REALM_JAVA_PATH/tools/sync_test_server/stop_server.sh > /dev/null 2>&1 + echo " done" + else + echo "Warning: Sync server not running" + fi +} + +restart_server(){ + if is_server_active + then + stop_server + start_server + else + echo "Warning: Sync server wasn't running" + start_server + fi +} + +server_status(){ + if is_server_active + then + echo "Sync server: ON" + else + echo "Sync server: OFF" + fi +} + +bind_server(){ + echo -n "Forwarding ports... " + adb reverse tcp:9443 tcp:9443 && \ + adb reverse tcp:9080 tcp:9080 && \ + adb reverse tcp:9090 tcp:9090 && \ + adb reverse tcp:8888 tcp:8888 && \ + echo "done" || { echo "failed" ; exit 1 ; } +} + +server_help(){ + echo "Try with: + +start - starts the sync server +stop - stops the sync server +restart - restarts the sync server +status - shows the sync server status +bind - bind the emulator ports to the sync server" +} + +server(){ + action=$2 + case $action in + start) + start_server + ;; + stop) + stop_server + ;; + restart) + restart_server + ;; + status) + server_status + ;; + bind) + bind_server + ;; + *) + server_help + esac +} + +java_install(){ + pushd $REALM_JAVA_PATH + ./gradlew installRealmJava + popd +} + +java_build(){ + pushd $REALM_JAVA_PATH + ./gradlew assemble --stacktrace + popd +} + +java_test(){ + pushd $REALM_JAVA_PATH/realm + ./gradlew connectedObjectServerDebugAndroidTest --stacktrace + popd +} + +java_check(){ + pushd $REALM_JAVA_PATH/realm + ./gradlew spotbugsMain pmd checkstyle + popd +} + +java_clean(){ + pushd $REALM_JAVA_PATH + ./gradlew clean + popd +} + +java_help(){ + echo "Try with: + +install - install realm-java locally +build - builds realm-java +test - runs the realm-java test suite +check - runs realm-java spotbugs, checkstyle, pmd +clean - cleans realm-java" +} + +java(){ + action=$2 + case $action in + install) + java_install + ;; + build) + java_build + ;; + test) + java_test + ;; + check) + java_check + ;; + clean) + java_clean + ;; + *) + java_help + esac +} + +show_help(){ + echo "Try with: + +server - controls the sync server +java - executes realm-java actions" +} + +if ! [[ -n "${REALM_JAVA_PATH}" ]]; then + echo "Error: \$REALM_JAVA_PATH not defined." + exit 1 +fi + +command=$1 +case $command in + server) + server $* + ;; + java) + java $* + ;; + *) + show_help +esac From f42b484fcc723e23697b0405800db9d73e49e6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 20 Nov 2020 13:26:39 +0100 Subject: [PATCH 1747/2110] Ensure flow emissions are cooperative after the job has been cancelled. (#7212) --- CHANGELOG.md | 1 + .../kotlin/io/realm/CoroutinesTests.kt | 37 +++- .../coroutines/InternalFlowFactory.kt | 181 +++++++++++------- 3 files changed, 146 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89cef51ac2..42b423574f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Fixes * Fixed crash when adding classes containing an `ObjectId` as primary key to the schema. (Issue [#7189](https://github.com/realm/realm-java/issues/7189), since v10.0.0) * Fixed crash when creating proxy classes containing an `ObjectId` as primary key. (Issue [#7197](https://github.com/realm/realm-java/issues/7197), since v10.0.0) +* Fixed crash where calls to `toFlow` could crash if the Flow job is canceled and object updates are emitted after that happens. (Issue [7211](https://github.com/realm/realm-java/issues/7211), since v10.0.1) ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. diff --git a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutinesTests.kt b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutinesTests.kt index b415183fdf..e496ce266a 100644 --- a/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutinesTests.kt +++ b/realm/kotlin-extensions/src/androidTest/kotlin/io/realm/CoroutinesTests.kt @@ -214,7 +214,7 @@ class CoroutinesTests { } @Test - fun realmResults_toFlow_resultsEmittedAfterCollect() { + fun findAll_realmResults_toFlow_resultsEmittedAfterCollect() { Realm.getInstance(configuration).use { realm -> realm.executeTransaction { transactionRealm -> transactionRealm.createObject().name = "Foo" @@ -247,6 +247,41 @@ class CoroutinesTests { TestHelper.awaitOrFail(countDownLatch) } + @Test + fun realmObject_findFirst_toFlow_onlyValidObjectsAreEmitted() { + Realm.getInstance(configuration).use { realm -> + realm.executeTransaction { transactionRealm -> + transactionRealm.createObject().name = "Foo" + transactionRealm.createObject().name = "Bar" + } + } + + val countDownLatch = CountDownLatch(1) + + val context = Dispatchers.Main + val scope = CoroutineScope(context) + + scope.launch { + val realmInstance = Realm.getInstance(configuration) + realmInstance.where() + .findFirstAsync() + .toFlow() + .flowOn(context) + .onEach { flowObject -> + assertNotNull(flowObject) + assertTrue(flowObject.isFrozen()) + assertTrue(flowObject.isValid()) + assertEquals("Foo", flowObject.name) + scope.cancel("Cancelling scope...") + }.onCompletion { + realmInstance.close() + countDownLatch.countDown() + }.collect() + } + + TestHelper.awaitOrFail(countDownLatch) + } + @Test fun realmResults_toChangesetFlow_emittedAfterCollect() { Realm.getInstance(configuration).use { realm -> diff --git a/realm/realm-library/src/main/java/io/realm/internal/coroutines/InternalFlowFactory.kt b/realm/realm-library/src/main/java/io/realm/internal/coroutines/InternalFlowFactory.kt index 406cc015bf..c65c3ae8ed 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/coroutines/InternalFlowFactory.kt +++ b/realm/realm-library/src/main/java/io/realm/internal/coroutines/InternalFlowFactory.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.isActive /** * Internal factory implementation used to conceal Kotlin implementation details from the public @@ -43,10 +44,12 @@ class InternalFlowFactory( return callbackFlow { val flowRealm = Realm.getInstance(realm.configuration) val listener = RealmChangeListener { listenerRealm -> - if (returnFrozenObjects) { - offer(realm.freeze()) - } else { - offer(listenerRealm) + if (isActive) { + if (returnFrozenObjects) { + offer(realm.freeze()) + } else { + offer(listenerRealm) + } } } @@ -74,10 +77,12 @@ class InternalFlowFactory( return callbackFlow { val flowRealm = DynamicRealm.getInstance(dynamicRealm.configuration) val listener = RealmChangeListener { listenerRealm -> - if (returnFrozenObjects) { - offer(dynamicRealm.freeze()) - } else { - offer(listenerRealm) + if (isActive) { + if (returnFrozenObjects) { + offer(dynamicRealm.freeze()) + } else { + offer(listenerRealm) + } } } @@ -116,10 +121,12 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = Realm.getInstance(config) val listener = RealmChangeListener> { listenerResults -> - if (returnFrozenObjects) { - offer(listenerResults.freeze()) - } else { - offer(listenerResults) + if (isActive) { + if (returnFrozenObjects) { + offer(listenerResults.freeze()) + } else { + offer(listenerResults) + } } } @@ -164,10 +171,12 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = Realm.getInstance(config) val listener = OrderedRealmCollectionChangeListener> { listenerResults, changeSet -> - if (returnFrozenObjects) { - offer(CollectionChange(listenerResults.freeze(), changeSet)) - } else { - offer(CollectionChange(listenerResults, changeSet)) + if (isActive) { + if (returnFrozenObjects) { + offer(CollectionChange(listenerResults.freeze(), changeSet)) + } else { + offer(CollectionChange(listenerResults, changeSet)) + } } } @@ -212,10 +221,12 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = DynamicRealm.getInstance(config) val listener = RealmChangeListener> { listenerResults -> - if (returnFrozenObjects) { - offer(listenerResults.freeze()) - } else { - offer(listenerResults) + if (isActive) { + if (returnFrozenObjects) { + offer(listenerResults.freeze()) + } else { + offer(listenerResults) + } } } @@ -260,10 +271,12 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = DynamicRealm.getInstance(config) val listener = OrderedRealmCollectionChangeListener> { listenerResults, changeSet -> - if (returnFrozenObjects) { - offer(CollectionChange(listenerResults.freeze(), changeSet)) - } else { - offer(CollectionChange(listenerResults, changeSet)) + if (isActive) { + if (returnFrozenObjects) { + offer(CollectionChange(listenerResults.freeze(), changeSet)) + } else { + offer(CollectionChange(listenerResults, changeSet)) + } } } @@ -306,10 +319,12 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = Realm.getInstance(config) val listener = RealmChangeListener> { listenerResults -> - if (returnFrozenObjects) { - offer(listenerResults.freeze()) - } else { - offer(listenerResults) + if (isActive) { + if (returnFrozenObjects) { + offer(listenerResults.freeze()) + } else { + offer(listenerResults) + } } } @@ -354,10 +369,12 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = Realm.getInstance(config) val listener = OrderedRealmCollectionChangeListener> { listenerList, changeSet -> - if (returnFrozenObjects) { - offer(CollectionChange(listenerList.freeze(), changeSet)) - } else { - offer(CollectionChange(listenerList, changeSet)) + if (isActive) { + if (returnFrozenObjects) { + offer(CollectionChange(listenerList.freeze(), changeSet)) + } else { + offer(CollectionChange(listenerList, changeSet)) + } } } @@ -399,10 +416,12 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = DynamicRealm.getInstance(config) val listener = RealmChangeListener> { listenerResults -> - if (returnFrozenObjects) { - offer(listenerResults.freeze()) - } else { - offer(listenerResults) + if (isActive) { + if (returnFrozenObjects) { + offer(listenerResults.freeze()) + } else { + offer(listenerResults) + } } } @@ -447,10 +466,12 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = DynamicRealm.getInstance(config) val listener = OrderedRealmCollectionChangeListener> { listenerList, changeSet -> - if (returnFrozenObjects) { - offer(CollectionChange(listenerList.freeze(), changeSet)) - } else { - offer(CollectionChange(listenerList, changeSet)) + if (isActive) { + if (returnFrozenObjects) { + offer(CollectionChange(listenerList.freeze(), changeSet)) + } else { + offer(CollectionChange(listenerList, changeSet)) + } } } @@ -493,20 +514,24 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = Realm.getInstance(config) val listener = RealmChangeListener { listenerObj -> - if (returnFrozenObjects) { - offer(RealmObject.freeze(listenerObj) as T) - } else { - offer(listenerObj) + if (isActive) { + if (returnFrozenObjects) { + offer(RealmObject.freeze(listenerObj) as T) + } else { + offer(listenerObj) + } } } RealmObject.addChangeListener(realmObject, listener) // Emit current value - if (returnFrozenObjects) { - offer(RealmObject.freeze(realmObject)) - } else { - offer(realmObject) + if (RealmObject.isLoaded(realmObject)) { + if (returnFrozenObjects) { + offer(RealmObject.freeze(realmObject)) + } else { + offer(realmObject) + } } awaitClose { @@ -541,20 +566,24 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = Realm.getInstance(config) val listener = RealmObjectChangeListener { listenerObject, changeSet -> - if (returnFrozenObjects) { - offer(ObjectChange(RealmObject.freeze(listenerObject), changeSet)) - } else { - offer(ObjectChange(listenerObject, changeSet)) + if (isActive) { + if (returnFrozenObjects) { + offer(ObjectChange(RealmObject.freeze(listenerObject), changeSet)) + } else { + offer(ObjectChange(listenerObject, changeSet)) + } } } RealmObject.addChangeListener(realmObject, listener) // Emit current value - if (returnFrozenObjects) { - offer(ObjectChange(RealmObject.freeze(realmObject), null)) - } else { - offer(ObjectChange(realmObject, null)) + if (RealmObject.isLoaded(realmObject)) { + if (returnFrozenObjects) { + offer(ObjectChange(RealmObject.freeze(realmObject), null)) + } else { + offer(ObjectChange(realmObject, null)) + } } awaitClose { @@ -590,20 +619,24 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = DynamicRealm.getInstance(config) val listener = RealmChangeListener { listenerObj -> - if (returnFrozenObjects) { - offer(listenerObj.freeze()) - } else { - offer(listenerObj) + if (isActive) { + if (returnFrozenObjects) { + offer(listenerObj.freeze()) + } else { + offer(listenerObj) + } } } dynamicRealmObject.addChangeListener(listener) // Emit current value - if (returnFrozenObjects) { - offer(RealmObject.freeze(dynamicRealmObject)) - } else { - offer(dynamicRealmObject) + if (RealmObject.isLoaded(dynamicRealmObject)) { + if (returnFrozenObjects) { + offer(RealmObject.freeze(dynamicRealmObject)) + } else { + offer(dynamicRealmObject) + } } awaitClose { @@ -638,20 +671,24 @@ class InternalFlowFactory( // Get instance to ensure the Realm is open for as long as we are listening val flowRealm = Realm.getInstance(config) val listener = RealmObjectChangeListener { listenerObject, changeSet -> - if (returnFrozenObjects) { - offer(ObjectChange(RealmObject.freeze(listenerObject), changeSet)) - } else { - offer(ObjectChange(listenerObject, changeSet)) + if (isActive) { + if (returnFrozenObjects) { + offer(ObjectChange(RealmObject.freeze(listenerObject), changeSet)) + } else { + offer(ObjectChange(listenerObject, changeSet)) + } } } RealmObject.addChangeListener(dynamicRealmObject, listener) // Emit current value - if (returnFrozenObjects) { - offer(ObjectChange(RealmObject.freeze(dynamicRealmObject), null)) - } else { - offer(ObjectChange(dynamicRealmObject, null)) + if (RealmObject.isLoaded(dynamicRealmObject)) { + if (returnFrozenObjects) { + offer(ObjectChange(RealmObject.freeze(dynamicRealmObject), null)) + } else { + offer(ObjectChange(dynamicRealmObject, null)) + } } awaitClose { From 2c8a3cea45e9bbe9a11350db6bcb3a9152aa9457 Mon Sep 17 00:00:00 2001 From: clementetb Date: Mon, 23 Nov 2020 16:44:02 +0100 Subject: [PATCH 1748/2110] Prepare 10.1.0 (#7208) --- CHANGELOG.md | 6 ++++-- dependencies.list | 4 ++-- realm/realm-library/src/main/cpp/object-store | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42b423574f..219f6828e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.0.2 (YYYY-MM-DD) +## 10.1.0 (YYYY-MM-DD) ### Breaking Changes * None. @@ -18,7 +18,9 @@ * Realm Studio 10.0.0 or above is required to open Realms created by this version. ### Internal -* None. +* Updated to Realm Sync: 10.1.3. +* Updated to Realm Core: 10.1.3. +* Updated to Object Store commit: fc6daca61133aa9601e4cb34fbeb9ec7569e162e. ## 10.0.1 (2020-11-06) diff --git a/dependencies.list b/dependencies.list index 9b809b27df..2b6f841599 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,7 +1,7 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC=10.1.0 -REALM_SYNC_SHA256=a074550f573b5b9f35d1efe84eef0145f2181f1c096a76ff559e7103091bae7e +REALM_SYNC=10.1.3 +REALM_SYNC_SHA256=25453e11051192723a95c63ad378e11c69e55428a6973ff58cc6f9e8b3659870 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index fd246c54de..fc6daca611 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit fd246c54de7d1fee6bcbeb3609de75a4eccd5b70 +Subproject commit fc6daca61133aa9601e4cb34fbeb9ec7569e162e From a58aa924a3712d2ab8100a411a665312fecec26b Mon Sep 17 00:00:00 2001 From: Clemente Tort Date: Mon, 23 Nov 2020 16:59:42 +0100 Subject: [PATCH 1749/2110] Release 10.1.0 --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 219f6828e3..0a5c512c1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.1.0 (YYYY-MM-DD) +## 10.1.0 (2020-10-23) ### Breaking Changes * None. diff --git a/version.txt b/version.txt index bb2a9abc36..4149c39eec 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.1.0-SNAPSHOT +10.1.0 From 1b2b57d26151a7389184b03695e28b80a71fa638 Mon Sep 17 00:00:00 2001 From: Clemente Tort Date: Mon, 23 Nov 2020 17:02:52 +0100 Subject: [PATCH 1750/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 4149c39eec..488c7d383a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.1.0 +10.1.1-SNAPSHOT From 0387fca459981d400330148f04580488c90a5da8 Mon Sep 17 00:00:00 2001 From: Clemente Tort Date: Mon, 23 Nov 2020 17:05:20 +0100 Subject: [PATCH 1751/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 488c7d383a..6639ca8ec9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.1.1-SNAPSHOT +10.2.0-SNAPSHOT From 64fd2b1f026d30f523950ed78dc8521801de4275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Lo=CC=81pez?= Date: Thu, 26 Nov 2020 11:04:40 +0100 Subject: [PATCH 1752/2110] Added temporarily missing dictionary and mixed type functions to java_object_accessor --- .../src/main/cpp/java_object_accessor.hpp | 118 ++++++++++++------ realm/realm-library/src/main/cpp/realm-core | 2 +- 2 files changed, 81 insertions(+), 39 deletions(-) diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp index 16af02f49d..2c5186d34b 100644 --- a/realm/realm-library/src/main/cpp/java_object_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -43,10 +43,12 @@ using namespace realm::_impl; X(Date) \ X(ObjectId) \ X(Decimal) \ + X(Mixed) \ X(Binary) \ X(Object) \ X(List) \ X(PropertyList) \ + X(Dictionary) \ namespace realm { @@ -70,18 +72,20 @@ template constexpr T realm_max(T a, T b, Rest... rest) } template struct JavaValueTypeRepr; -template <> struct JavaValueTypeRepr { using Type = jlong; }; -template <> struct JavaValueTypeRepr { using Type = std::string; }; -template <> struct JavaValueTypeRepr { using Type = jboolean; }; -template <> struct JavaValueTypeRepr { using Type = jfloat; }; -template <> struct JavaValueTypeRepr { using Type = jdouble; }; -template <> struct JavaValueTypeRepr { using Type = Timestamp; }; -template <> struct JavaValueTypeRepr{ using Type = ObjectId; }; -template <> struct JavaValueTypeRepr { using Type = Decimal128; }; -template <> struct JavaValueTypeRepr { using Type = OwnedBinaryData; }; -template <> struct JavaValueTypeRepr { using Type = Obj*; }; -template <> struct JavaValueTypeRepr { using Type = std::vector; }; -template <> struct JavaValueTypeRepr { using Type = std::map; }; +template <> struct JavaValueTypeRepr { using Type = jlong; }; +template <> struct JavaValueTypeRepr { using Type = std::string; }; +template <> struct JavaValueTypeRepr { using Type = jboolean; }; +template <> struct JavaValueTypeRepr { using Type = jfloat; }; +template <> struct JavaValueTypeRepr { using Type = jdouble; }; +template <> struct JavaValueTypeRepr { using Type = Timestamp; }; +template <> struct JavaValueTypeRepr { using Type = ObjectId; }; +template <> struct JavaValueTypeRepr { using Type = Decimal128; }; +template <> struct JavaValueTypeRepr { using Type = Mixed; }; +template <> struct JavaValueTypeRepr { using Type = OwnedBinaryData; }; +template <> struct JavaValueTypeRepr { using Type = Obj*; }; +template <> struct JavaValueTypeRepr { using Type = std::vector; }; +template <> struct JavaValueTypeRepr { using Type = std::map; }; +template <> struct JavaValueTypeRepr { using Type = std::map; }; // Tagged union class representing all the values Java can send to Object Store struct JavaValue { @@ -212,6 +216,11 @@ struct JavaValue { return get_as(); } + auto& get_dictionary() const noexcept + { + return get_as(); + } + auto& get_property_list() const noexcept { return get_as(); @@ -233,6 +242,11 @@ struct JavaValue { return get_as(); } + auto& get_mixed() const noexcept + { + return get_as(); + } + auto& get_binary() const noexcept { return get_as(); @@ -286,6 +300,9 @@ struct JavaValue { return get_object_id().to_string(); case JavaValueType::Decimal: return get_decimal128().to_string(); + case JavaValueType::Mixed: + // TODO: Return actual string + return "Mixed"; case JavaValueType::Binary: ss << "Blob["; ss << get_binary().size(); @@ -370,25 +387,6 @@ class JavaContext { return util::none; } - // Invoke `fn` with each of the values from an enumerable type - template - void enumerate_list(JavaValue& value, Func&& fn) { - if (value.get_type() == JavaValueType::List) { - for (const auto& v : value.get_list()) { - fn(v); - } - } else { - throw std::logic_error("Type is not a list"); - } - } - - // Determine if `value` boxes the same List as `list` - bool is_same_list(List const& /*list*/, JavaValue const& /*value*/) - { - // Lists from Java are currently never the same as the ones found in Object Store. - return false; - } - // Convert from core types to the boxed type. These are currently not used as Proxy objects read // directly from the Row objects. This implementation is thus only here as a reminder of which // method signatures to add if needed. @@ -454,7 +452,46 @@ class JavaContext { Obj create_embedded_object(); -private: + // Determine if `value` boxes the same List as `list` + bool is_same_list(List const& /*list*/, JavaValue const& /*value*/) + { + // Lists from Java are currently never the same as the ones found in Object Store. + return true; + } + + bool is_same_dictionary(const object_store::Dictionary&, JavaValue const& /*value*/){ + //TODO: Implement with sets + return false; + } + + bool is_same_set(object_store::Set const&, JavaValue const& /*value*/){ + //TODO: Implement with sets + return false; + } + + template + void enumerate_collection(JavaValue& value, Func&& fn) { + if (value.get_type() == JavaValueType::List) { + for (const auto& v : value.get_list()) { + fn(v); + } + } else { + throw std::logic_error("Type is not a list"); + } + } + + template + void enumerate_dictionary(JavaValue& value, Func&& fn) { + if (value.get_type() == JavaValueType::Dictionary) { + for (const auto& v : value.get_dictionary()) { + fn(v.first, v.second); + } + } else { + throw std::logic_error("Type is not a dictionary"); + } + } + + private: JNIEnv* m_env; std::shared_ptr realm; Obj m_parent; @@ -528,6 +565,11 @@ inline Decimal128 JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) c return v.has_value() ? v.get_decimal128() : Decimal128(); } +template <> +inline Mixed JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? v.get_mixed() : Mixed(); +} template <> inline ObjectId JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const @@ -571,12 +613,6 @@ inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy return v.has_value() ? util::make_optional(v.get_float()) : util::none; } -template <> -inline Mixed JavaContext::unbox(JavaValue const&, CreatePolicy, ObjKey) const -{ - REALM_TERMINATE("'Mixed' not supported"); -} - template <> inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { @@ -589,6 +625,12 @@ inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePoli return v.has_value() ? util::make_optional(v.get_decimal128()) : util::none; } +template <> +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? util::make_optional(v.get_mixed()) : util::none; +} + inline Obj JavaContext::create_embedded_object() { return m_parent.create_and_set_linked_object(m_property->column_key); } diff --git a/realm/realm-library/src/main/cpp/realm-core b/realm/realm-library/src/main/cpp/realm-core index 2bff29de34..0a4c622924 160000 --- a/realm/realm-library/src/main/cpp/realm-core +++ b/realm/realm-library/src/main/cpp/realm-core @@ -1 +1 @@ -Subproject commit 2bff29de34b03f4bed448512d84ab0f6fd2c1d08 +Subproject commit 0a4c622924018b434af293f9ec2bb9c6b2edbc01 From ae8ae246458741ee80da916d3e84a31efced891b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 27 Nov 2020 19:35:06 +0000 Subject: [PATCH 1753/2110] Fixing annotation processor to safely check for embedded classes (#7220) * Fixes https://github.com/realm/realm-java/issues/7213 --- CHANGELOG.md | 22 +++ .../realm-annotations-processor/build.gradle | 3 +- .../io/realm/processor/ClassCollection.kt | 4 + .../processor/RealmProxyClassGenerator.kt | 143 +++++++++++------- .../src/main/java/io/realm/processor/Utils.kt | 9 ++ 5 files changed, 123 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a5c512c1e..091301987c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## 10.1.1 (2020-10-27) + +### Breaking Changes +* None. + +### Enhancements +* None. + +### Fixes +* KAPT crash when processing a RealmObject referenced from another module (changed revealed after we started checking for embedded types). (Issue [#7213](https://github.com/realm/realm-java/issues/7213), since v10.0.0) + +### Compatibility +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 or above is required to open Realms created by this version. + +### Internal +* Updated to Realm Sync: 10.1.3. +* Updated to Realm Core: 10.1.3. +* Updated to Object Store commit: fc6daca61133aa9601e4cb34fbeb9ec7569e162e. + + ## 10.1.0 (2020-10-23) ### Breaking Changes diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 24d50f40b9..08d5ed3353 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -15,8 +15,8 @@ dependencies { implementation "io.realm:realm-annotations:${version}" implementation "org.mongodb:bson:${properties.getProperty('BSON_DEPENDENCY')}" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation files(Jvm.current().toolsJar) testImplementation files('../realm-library/build/intermediates/aar_main_jar/baseRelease/classes.jar') // Java projects cannot depend on AAR files - testImplementation files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it testImplementation group:'junit', name:'junit', version:'4.12' testImplementation group:'com.google.testing.compile', name:'compile-testing', version:'0.6' testImplementation files(file("${System.env.ANDROID_HOME}/platforms/android-29/android.jar")) @@ -24,6 +24,7 @@ dependencies { // for Ant filter import org.apache.tools.ant.filters.ReplaceTokens +import org.gradle.internal.jvm.Jvm task generateVersionClass(type: Copy) { from 'src/main/templates/Version.java' diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.kt index 14faeab559..b724dca0d6 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassCollection.kt @@ -40,6 +40,10 @@ class ClassCollection { ?: throw IllegalArgumentException("Class $className was not found") } + fun getClassFromQualifiedNameOrNull(className: QualifiedClassName): ClassMetaData? { + return qualifiedNameClassMap[className] + } + fun size(): Int { return classSet.size } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index b9ff7e72a5..6fe4207587 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -17,9 +17,12 @@ package io.realm.processor import com.squareup.javawriter.JavaWriter +import com.sun.tools.javac.code.Attribute +import com.sun.tools.javac.code.Symbol +import com.sun.tools.javac.code.Type +import com.sun.tools.javac.util.Pair import io.realm.processor.ext.beginMethod import io.realm.processor.ext.beginType -import org.bson.types.ObjectId import java.io.BufferedWriter import java.io.IOException import java.util.* @@ -370,8 +373,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi // Getter - End // Setter - Start - val fieldType = QualifiedClassName(field.asType()) - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + val isEmbedded = isFieldTypeEmbedded(field.asType()) val linkedQualifiedClassName: QualifiedClassName = Utils.getFieldTypeQualifiedName(field) val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) emitAnnotation("Override") @@ -383,7 +385,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("return") endControlFlow() beginControlFlow("if (value != null && !RealmObject.isManaged(value))") - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { emitStatement("%1\$s proxyObject = realm.createEmbeddedObject(%1\$s.class, this, \"%2\$s\")", linkedQualifiedClassName, fieldName) emitStatement("%s.updateEmbeddedObject(realm, value, proxyObject, new HashMap(), Collections.EMPTY_SET)", linkedProxyClass) emitStatement("value = proxyObject") @@ -409,7 +411,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("return") endControlFlow() - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (RealmObject.isManaged(value))") emitStatement("proxyState.checkValidObject(value)") endControlFlow() @@ -895,7 +897,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() } } - + @Throws(IOException::class) private fun setTableValues(writer: JavaWriter, fieldType: String, fieldName: String, interfaceName: SimpleClassName, getter: String, isUpdate: Boolean) { writer.apply { @@ -1064,13 +1066,12 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi when { Utils.isRealmModel(field) -> { - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) - + val isEmbedded = isFieldTypeEmbedded(field.asType()) emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cache%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) nextControlFlow("else") @@ -1085,20 +1086,19 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endControlFlow() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field)!! - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) - + val genericType: TypeMirror = Utils.getGenericType(field)!! + val isEmbedded = isFieldTypeEmbedded(genericType) emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) beginControlFlow("if (%sList != null)", fieldName) emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cacheItemIndex%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) nextControlFlow("else") - emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(QualifiedClassName(genericType.toString())), fieldName) endControlFlow() } else { beginControlFlow("if (cacheItemIndex%s == null)", fieldName) @@ -1180,13 +1180,13 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val getter = metadata.getInternalGetter(fieldName) if (Utils.isRealmModel(field)) { - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + val isEmbedded = isFieldTypeEmbedded(field.asType()) emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cache%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) nextControlFlow("else") @@ -1200,8 +1200,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } endControlFlow() } else if (Utils.isRealmModelList(field)) { - val genericType = Utils.getGenericTypeQualifiedName(field)!! - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + val genericType: TypeMirror = Utils.getGenericType(field)!! + val isEmbedded = isFieldTypeEmbedded(genericType) emitEmptyLine() emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) @@ -1209,11 +1209,11 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cacheItemIndex%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) nextControlFlow("else") - emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(QualifiedClassName(genericType.toString())), fieldName) endControlFlow() } else { beginControlFlow("if (cacheItemIndex%s == null)", fieldName) @@ -1284,13 +1284,12 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi val getter = metadata.getInternalGetter(fieldName) if (Utils.isRealmModel(field)) { - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) - + val isEmbedded = isFieldTypeEmbedded(field.asType()) emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cache%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) nextControlFlow("else") @@ -1307,13 +1306,13 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, objKey)", fieldName) endControlFlow() } else if (Utils.isRealmModelList(field)) { - val genericType = Utils.getGenericTypeQualifiedName(field)!! - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + val genericType: TypeMirror = Utils.getGenericType(field)!! + val isEmbedded = isFieldTypeEmbedded(genericType) emitEmptyLine() emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { emitStatement("%1\$sOsList.removeAll()", fieldName) beginControlFlow("if (%sList != null)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) @@ -1321,7 +1320,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (cacheItemIndex%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) nextControlFlow("else") - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(QualifiedClassName(genericType.toString())), fieldName) endControlFlow() endControlFlow() endControlFlow() @@ -1422,13 +1421,12 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi when { Utils.isRealmModel(field) -> { - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) - + val isEmbedded = isFieldTypeEmbedded(field.asType()) emitEmptyLine() emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter) beginControlFlow("if (%sObj != null)", fieldName) emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cache%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName) nextControlFlow("else") @@ -1446,9 +1444,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endControlFlow() } Utils.isRealmModelList(field) -> { - val genericType = Utils.getGenericTypeQualifiedName(field)!! - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) - + val genericType: TypeMirror = Utils.getGenericType(field)!! + val isEmbedded = isFieldTypeEmbedded(genericType) emitEmptyLine() emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName) emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter) @@ -1458,11 +1455,11 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("for (int i = 0; i < objectCount; i++)") emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cacheItemIndex%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) nextControlFlow("else") - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(QualifiedClassName(genericType.toString())), fieldName) endControlFlow() } else { beginControlFlow("if (cacheItemIndex%s == null)", fieldName) @@ -1476,11 +1473,11 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (%sList != null)", fieldName) beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName) emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cacheItemIndex%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName) nextControlFlow("else") - emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(genericType), fieldName) + emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(QualifiedClassName(genericType.toString())), fieldName) endControlFlow() } else { beginControlFlow("if (cacheItemIndex%s == null)", fieldName) @@ -1520,7 +1517,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } endControlFlow() endMethod() - emitEmptyLine() + emitEmptyLine() } } @@ -1576,7 +1573,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } else { emitStatement("objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) } - + if (throwIfPrimaryKeyDuplicate) { nextControlFlow("else") emitStatement("Table.throwDuplicatePrimaryKeyException(primaryKeyValue)") @@ -1648,7 +1645,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi when { Utils.isRealmModel(field) -> { - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + val isEmbedded = isFieldTypeEmbedded(field.asType()) val fieldColKey: String = fieldColKeyVariableReference(field) val linkedQualifiedClassName: QualifiedClassName = Utils.getFieldTypeQualifiedName(field) val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) @@ -1659,7 +1656,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi nextControlFlow("else") emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cache%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) nextControlFlow("else") @@ -1682,10 +1679,10 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() } Utils.isRealmModelList(field) -> { - val listElementType: QualifiedClassName = Utils.getRealmListType(field)!! - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(listElementType) + val listElementType: TypeMirror = Utils.getGenericType(field)!! val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!! val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) + val isEmbedded = isFieldTypeEmbedded(listElementType) emitStatement("RealmList<%s> %sUnmanagedList = unmanagedSource.%s()", genericType, fieldName, getter) beginControlFlow("if (%sUnmanagedList != null)", fieldName) @@ -1696,7 +1693,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("%1\$s %2\$sUnmanagedItem = %2\$sUnmanagedList.get(i)", genericType, fieldName) emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sUnmanagedItem)", genericType, fieldName) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { beginControlFlow("if (cache%s != null)", fieldName) emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName) nextControlFlow("else") @@ -1800,7 +1797,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() } } - + @Throws(IOException::class) private fun emitUpdateMethod(writer: JavaWriter) { if (!metadata.hasPrimaryKey() && !metadata.embedded) { @@ -1828,15 +1825,14 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi when { Utils.isRealmModel(field) -> { - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) - emitEmptyLine() emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter) beginControlFlow("if (%sObj == null)", fieldName) emitStatement("builder.addNull(%s)", fieldColKeyVariableReference(field)) nextControlFlow("else") - if (fieldTypeMetaData.embedded) { + val isEmbedded = isFieldTypeEmbedded(field.asType()) + if (isEmbedded) { // Embedded objects are created in-place as we need to know the // parent object + the property containing it. // After this we know that changing values will always be considered @@ -1867,7 +1863,9 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } Utils.isRealmModelList(field) -> { val genericType: QualifiedClassName = Utils.getRealmListType(field)!! - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(genericType) + val fieldTypeMetaData: TypeMirror = Utils.getGenericType(field)!! + + val isEmbedded = isFieldTypeEmbedded(fieldTypeMetaData) val proxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field) emitEmptyLine() @@ -1875,7 +1873,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi beginControlFlow("if (%sUnmanagedList != null)", fieldName) emitStatement("RealmList<%s> %sManagedCopy = new RealmList<%s>()", genericType, fieldName, genericType) - if (fieldTypeMetaData.embedded) { + if (isEmbedded) { emitStatement("OsList targetList = realmObjectTarget.realmGet\$%s().getOsList()", fieldName) emitStatement("targetList.deleteAll()") beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName) @@ -2032,9 +2030,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitEmptyLine() } } - - @Throws(IOException::class) private fun emitEqualsMethod(writer: JavaWriter) { if (metadata.containsEquals()) { @@ -2069,7 +2065,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi endMethod() } } - + @Throws(IOException::class) private fun emitCreateOrUpdateUsingJsonObject(writer: JavaWriter) { writer.apply { @@ -2158,15 +2154,14 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } when { Utils.isRealmModel(field) -> { - val fieldType = QualifiedClassName(field.asType()) - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + val isEmbedded = isFieldTypeEmbedded(field.asType()) RealmJsonTypeHelper.emitFillRealmObjectWithJsonValue( "objProxy", metadata.getInternalSetter(fieldName), fieldName, qualifiedFieldType, Utils.getProxyClassSimpleName(field), - fieldTypeMetaData.embedded, + isEmbedded, writer) } Utils.isRealmModelList(field) -> { @@ -2301,7 +2296,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("return obj") } endMethod() - emitEmptyLine() + emitEmptyLine() } } @@ -2410,5 +2405,39 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } return count } + + } + + // Returns whether a type of a Realm field is embedded or not. + // For types which are part of this processing round we can look it up immediately from + // the metadata in the `classCollection`. For types defined in other modules we will + // have to use the slower approach of inspecting the `embedded` property of the + // RealmClass annotation using the compiler tool api. + private fun isFieldTypeEmbedded(type: TypeMirror) : Boolean { + val fieldType = QualifiedClassName(type) + val fieldTypeMetaData: ClassMetaData? = classCollection.getClassFromQualifiedNameOrNull(fieldType) + return fieldTypeMetaData?.embedded ?: type.isEmbedded() + } + + private fun TypeMirror.isEmbedded() : Boolean { + var isEmbedded = false + + if (this is Type.ClassType) { + val declarationAttributes: com.sun.tools.javac.util.List? = tsym.metadata?.declarationAttributes + if (declarationAttributes != null) { + loop@for (attribute: Attribute.Compound in declarationAttributes) { + if (attribute.type.tsym.qualifiedName.toString() == "io.realm.annotations.RealmClass") { + for (pair: Pair in attribute.values) { + if (pair.fst.name.toString() == "embedded") { + isEmbedded = pair.snd.value as Boolean + break@loop + } + } + } + } + } + } + + return isEmbedded } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt index dd2264784c..653b40f228 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt @@ -318,6 +318,15 @@ object Utils { return if (typeArguments.isEmpty()) null else QualifiedClassName(typeArguments[0].toString()) } + /** + * Return generic type mirror if any. + */ + fun getGenericType(field: VariableElement): TypeMirror? { + val fieldType = field.asType() + val typeArguments = (fieldType as DeclaredType).typeArguments + return if (typeArguments.isEmpty()) null else typeArguments[0] + } + /** * Strips the package name from a fully qualified class name. */ From 89476c9d25b58a36bb5e11e776d0d6f55b23d30b Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 27 Nov 2020 19:37:16 +0000 Subject: [PATCH 1754/2110] Release 10.1.1 --- CHANGELOG.md | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 091301987c..ce5290b694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * None. ### Fixes -* KAPT crash when processing a RealmObject referenced from another module (changed revealed after we started checking for embedded types). (Issue [#7213](https://github.com/realm/realm-java/issues/7213), since v10.0.0) +* KAPT crash when processing a RealmObject referenced from another module (changed revealed after we started checking for embedded types). (Issue [#7213](https://github.com/realm/realm-java/issues/7213), since v10.0.0). ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index fc6daca611..035eb07f3e 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit fc6daca61133aa9601e4cb34fbeb9ec7569e162e +Subproject commit 035eb07f3ef313bfb78c046be9cf6b4f065d6772 From aaa660a71af594281c735782e1e1149dcb20b1c0 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Fri, 27 Nov 2020 19:37:16 +0000 Subject: [PATCH 1755/2110] Release 10.1.1 --- CHANGELOG.md | 2 +- realm/realm-library/src/main/cpp/object-store | 2 +- version.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 091301987c..ce5290b694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * None. ### Fixes -* KAPT crash when processing a RealmObject referenced from another module (changed revealed after we started checking for embedded types). (Issue [#7213](https://github.com/realm/realm-java/issues/7213), since v10.0.0) +* KAPT crash when processing a RealmObject referenced from another module (changed revealed after we started checking for embedded types). (Issue [#7213](https://github.com/realm/realm-java/issues/7213), since v10.0.0). ### Compatibility * File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index fc6daca611..035eb07f3e 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit fc6daca61133aa9601e4cb34fbeb9ec7569e162e +Subproject commit 035eb07f3ef313bfb78c046be9cf6b4f065d6772 diff --git a/version.txt b/version.txt index 488c7d383a..23127993ac 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.1.1-SNAPSHOT +10.1.1 From 03c59614cac22fdb29c0b328e9edb3a4957b8ad1 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 30 Nov 2020 13:11:04 +0000 Subject: [PATCH 1756/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 23127993ac..a15d2f5919 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.1.1 +10.1.2-SNAPSHOT From cad8da5c987e39f38fed80e063e0436211b63a27 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 2 Dec 2020 12:57:58 +0000 Subject: [PATCH 1757/2110] KAPT complementary fix for missed edge case in https://github.com/realm/realm-java/pull/7220 (#7227) * Fixes a missed edge case for https://github.com/realm/realm-java/issues/7213 --- CHANGELOG.md | 21 ++++++++++++++----- .../processor/RealmProxyClassGenerator.kt | 5 ++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5290b694..05ce7760fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 10.1.2 (YYYY-MM-DD) + +### Breaking Changes +* None. + +### Enhancements +* None. + +### Fixes +* Complementary fix for missed edge case in https://github.com/realm/realm-java/pull/7220 where KAPT crash if we process a RealmObject referencing a type in RealmList defined in another module. (Issue [#7213](https://github.com/realm/realm-java/issues/7213), since v10.0.0). + +### Compatibility +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 or above is required to open Realms created by this version. + ## 10.1.1 (2020-10-27) ### Breaking Changes @@ -14,11 +30,6 @@ * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. -### Internal -* Updated to Realm Sync: 10.1.3. -* Updated to Realm Core: 10.1.3. -* Updated to Object Store commit: fc6daca61133aa9601e4cb34fbeb9ec7569e162e. - ## 10.1.0 (2020-10-23) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index 6fe4207587..f8dc9e386d 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -2165,8 +2165,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi writer) } Utils.isRealmModelList(field) -> { - val fieldType = QualifiedClassName((field.asType() as DeclaredType).typeArguments[0]) - val fieldTypeMetaData: ClassMetaData = classCollection.getClassFromQualifiedName(fieldType) + val fieldType = (field.asType() as DeclaredType).typeArguments[0] RealmJsonTypeHelper.emitFillRealmListWithJsonValue( "objProxy", metadata.getInternalGetter(fieldName), @@ -2174,7 +2173,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi fieldName, (field.asType() as DeclaredType).typeArguments[0].toString(), Utils.getProxyClassSimpleName(field), - fieldTypeMetaData.embedded, + isFieldTypeEmbedded(fieldType), writer) } Utils.isRealmValueList(field) -> emitStatement("ProxyUtils.setRealmListWithJsonObject(objProxy.%1\$s(), json, \"%2\$s\")", metadata.getInternalGetter(fieldName), fieldName) From 7bb45cfe310bb96b8ae7a6ad8c38eed4957bd5ac Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 2 Dec 2020 16:43:14 +0000 Subject: [PATCH 1758/2110] Release 10.1.2 --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05ce7760fe..f33ae7d2a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.1.2 (YYYY-MM-DD) +## 10.1.2 (2020-12-02) ### Breaking Changes * None. diff --git a/version.txt b/version.txt index a15d2f5919..b6132546fc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.1.2-SNAPSHOT +10.1.2 From 9cd3db0806ab326f759f9341775c413f7d75d2d5 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 2 Dec 2020 18:06:37 +0000 Subject: [PATCH 1759/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b6132546fc..4fc37ebda0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.1.2 +10.1.3-SNAPSHOT From dbaec0f32b78c1e58c0f5355eba6bc4543a8fddd Mon Sep 17 00:00:00 2001 From: nate contino Date: Fri, 4 Dec 2020 01:42:24 -0700 Subject: [PATCH 1760/2110] Migrating realm.io javadoc links to docs.mongodb.com (#7230) --- realm/realm-library/build.gradle | 2 +- realm/realm-library/src/overview.html | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 5a1f3a1c9a..bcdf975990 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -362,7 +362,7 @@ install { // Add your description here name 'realm-android-library' description 'Realm is a mobile database: a replacement for SQLite & ORMs.' - url 'http://realm.io' + url 'https://docs.mongodb.com/realm/' // Set your license licenses { diff --git a/realm/realm-library/src/overview.html b/realm/realm-library/src/overview.html index d0cd27f435..52ed5f1048 100644 --- a/realm/realm-library/src/overview.html +++ b/realm/realm-library/src/overview.html @@ -7,43 +7,43 @@

            Quick start

          • {@link io.realm.Realm}
            The Realm database. The storage and transactional manager of your object persistent store. It is in charge of creating and removing instances of your RealmObjects, querying, and performing transactions. - Read more. + Read more.

          • {@link io.realm.RealmConfiguration}
            A configuration object that is used to setup a specific Realm instance. - Read more. + Read more.

          • {@link io.realm.RealmObject}
            The super class of all objects (models) that are to be stored in Realm. A Java object must extend {@link io.realm.RealmObject} in order to be considered a RealmObject. - Read more. + Read more.

          • {@link io.realm.RealmList}
            A List that is used in RealmObjects to model one-to-many relationships with other RealmObjects. - Read more. + Read more.

          • {@link io.realm.RealmQuery}
            An object that encapsulates a query as defined through Realms fluent query interface. Queries are executed using either the {@link io.realm.RealmQuery#findAll}, {@link io.realm.RealmQuery#findFirst} or their variants. - Read more. + Read more.

          • {@link io.realm.RealmResults}
            The result set of an executed RealmQuery for a given Realm. RealmResults are live, - auto-updating views into the underlying + auto-updating views into the underlying data, which means results never have to be re-fetched. - Read more. + Read more.

          • - \ No newline at end of file + From b9dd6d1dc6a9c4528222289f082309afa63bab8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 7 Dec 2020 09:11:40 +0100 Subject: [PATCH 1761/2110] Fix 10.1.0 release date (#7222) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a5c512c1e..63a5124220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 10.1.0 (2020-10-23) +## 10.1.0 (2020-11-23) ### Breaking Changes * None. From 3313e36d3ee70afc325ad541efe568e8f57606f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 8 Dec 2020 11:31:10 +0100 Subject: [PATCH 1762/2110] Clean up local JNI references for network requests to prevent overflow (#7232) --- CHANGELOG.md | 16 ++++++++++ .../src/main/cpp/java_accessor.hpp | 5 +++ .../src/main/cpp/java_network_transport.hpp | 31 +++++++++++++------ realm/realm-library/src/main/cpp/util.cpp | 15 +++++++-- realm/realm-library/src/main/cpp/util.hpp | 3 +- 5 files changed, 56 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f33ae7d2a8..684b6a42ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 10.1.3 (YYYY-MM-DD) + +### Breaking Changes +* None. + +### Enhancements +* None. + +### Fixes +* Clean up JNI references to prevent crash from JNI reference table overflow (Issue [#7217](https://github.com/realm/realm-java/issues/7217)) + +### Compatibility +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 or above is required to open Realms created by this version. + ## 10.1.2 (2020-12-02) ### Breaking Changes diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index 83eed70bd7..0d29fab2f0 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -154,6 +154,11 @@ class JObjectArrayAccessor { jsize m_size; }; +template<> +inline JStringAccessor JObjectArrayAccessor::operator[](const int index) const noexcept { + return JStringAccessor(m_env, static_cast(m_env->GetObjectArrayElement(m_jobject_array, index)), true); +} + // An object accessor context which can be used to create and access objects // using util::Any as the type-erased value type. In addition, this serves as // the reference implementation of an accessor context that must be implemented diff --git a/realm/realm-library/src/main/cpp/java_network_transport.hpp b/realm/realm-library/src/main/cpp/java_network_transport.hpp index 23ffdbb55c..15aa315b05 100644 --- a/realm/realm-library/src/main/cpp/java_network_transport.hpp +++ b/realm/realm-library/src/main/cpp/java_network_transport.hpp @@ -63,18 +63,29 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { size_t map_size = request.headers.size(); jobject request_headers = env->NewObject(mapClass, init, (jsize) map_size); for (auto header : request.headers) { - env->CallObjectMethod(request_headers, put_method, to_jstring(env, header.first), to_jstring(env, header.second)); + jstring key = to_jstring(env, header.first); + jstring value = to_jstring(env, header.second); + env->CallObjectMethod(request_headers, put_method, key, value); + env->DeleteLocalRef(key); + env->DeleteLocalRef(value); } // Execute network request on the Java side - jobject response = env->CallObjectMethod(m_java_network_transport_impl, - m_send_request_method, - to_jstring(env, method), - to_jstring(env, request.url), - static_cast(request.timeout_ms), - request_headers, - to_jstring(env, request.body) - ); + jstring jmethod = to_jstring(env, method); + jstring jurl = to_jstring(env, request.url); + jstring jbody = to_jstring(env, request.body); + jobject response = env->CallObjectMethod( + m_java_network_transport_impl, + m_send_request_method, + jmethod, + jurl, + static_cast(request.timeout_ms), + request_headers, + jbody + ); + env->DeleteLocalRef(jmethod); + env->DeleteLocalRef(jurl); + env->DeleteLocalRef(jbody); env->DeleteLocalRef(request_headers); if (env->ExceptionCheck()) { @@ -92,7 +103,7 @@ struct JavaNetworkTransport : public app::GenericNetworkTransport { jint http_code = env->CallIntMethod(response, get_http_code_method); jint custom_code = env->CallIntMethod(response, get_custom_code_method); - JStringAccessor java_body(env, (jstring) env->CallObjectMethod(response, get_body_method)); + JStringAccessor java_body(env, (jstring) env->CallObjectMethod(response, get_body_method), true); JObjectArrayAccessor java_headers(env, static_cast(env->CallObjectMethod(response, get_headers_method))); auto response_headers = std::map(); for (int i = 0; i < java_headers.size(); i = i + 2) { diff --git a/realm/realm-library/src/main/cpp/util.cpp b/realm/realm-library/src/main/cpp/util.cpp index bb4bf4e3ec..aa21cde659 100644 --- a/realm/realm-library/src/main/cpp/util.cpp +++ b/realm/realm-library/src/main/cpp/util.cpp @@ -306,16 +306,24 @@ struct JcharTraits { }; struct JStringCharsAccessor { - JStringCharsAccessor(JNIEnv* e, jstring s) + JStringCharsAccessor(JNIEnv* e, jstring s, bool delete_jstring_ref_on_delete) : m_env(e) , m_string(s) , m_data(e->GetStringChars(s, 0)) , m_size(get_size(e, s)) + , m_delete_jstring_ref_on_delete(delete_jstring_ref_on_delete) { } ~JStringCharsAccessor() { m_env->ReleaseStringChars(m_string, m_data); + // TODO Left as opt-in to avoid inspecting all usages as part of the fix for + // https://github.com/realm/realm-java/pull/7232. We should consider making this the + // default and try to handle local refs uniformly through JavaLocalRefs or similar + // mechanisms. + if (m_delete_jstring_ref_on_delete) { + m_env->DeleteLocalRef(m_string); + } } const jchar* data() const noexcept { @@ -331,6 +339,7 @@ struct JStringCharsAccessor { const jstring m_string; const jchar* const m_data; const size_t m_size; + const bool m_delete_jstring_ref_on_delete; static size_t get_size(JNIEnv* e, jstring s) { @@ -472,7 +481,7 @@ transcode_complete : { } -JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) +JStringAccessor::JStringAccessor(JNIEnv* env, jstring str, bool delete_jstring_ref) : m_env(env) { // For efficiency, if the incoming UTF-16 string is sufficiently @@ -488,7 +497,7 @@ JStringAccessor::JStringAccessor(JNIEnv* env, jstring str) } m_is_null = false; - JStringCharsAccessor chars(env, str); + JStringCharsAccessor chars(env, str, delete_jstring_ref); typedef Utf8x16 Xcode; size_t max_project_size = 48; diff --git a/realm/realm-library/src/main/cpp/util.hpp b/realm/realm-library/src/main/cpp/util.hpp index 0b2cf5dfee..b704a8535b 100644 --- a/realm/realm-library/src/main/cpp/util.hpp +++ b/realm/realm-library/src/main/cpp/util.hpp @@ -236,7 +236,8 @@ jstring to_jstring(JNIEnv*, realm::StringData); class JStringAccessor { public: - JStringAccessor(JNIEnv*, jstring); // throws + JStringAccessor(JNIEnv* env, jstring s) : JStringAccessor(env, s, false) {}; // throws + JStringAccessor(JNIEnv*, jstring, bool); // throws bool is_null_or_empty() { return m_is_null || m_size == 0; From 64b3a8ceaed6b3b3a992ee741d22a9c01dbae155 Mon Sep 17 00:00:00 2001 From: clementetb Date: Wed, 9 Dec 2020 10:21:30 +0100 Subject: [PATCH 1763/2110] Add support for UUID type (#7152) --- CHANGELOG.md | 19 + .../java/io/realm/annotations/PrimaryKey.java | 2 +- .../java/io/realm/processor/ClassMetaData.kt | 38 +- .../main/java/io/realm/processor/Constants.kt | 11 +- .../processor/OsObjectBuilderTypeHelper.kt | 6 +- .../io/realm/processor/RealmJsonTypeHelper.kt | 82 ++- .../processor/RealmProxyClassGenerator.kt | 79 ++- .../java/io/realm/processor/TypeMirrors.kt | 5 +- .../src/main/java/io/realm/processor/Utils.kt | 26 +- .../realm/processor/RealmProcessorTest.java | 10 +- .../realm/some_test_AllTypesRealmProxy.java | 195 +++++- .../test/resources/some/test/AllTypes.java | 4 + .../androidTest/assets/nulltypes_invalid.json | 4 + .../androidTest/assets/uuid_as_string.json | 3 + .../java/io/realm/BulkInsertTests.java | 12 +- .../io/realm/DynamicRealmObjectTests.java | 92 ++- .../io/realm/LinkingObjectsDynamicTests.java | 9 +- .../ManagedOrderedRealmCollectionTests.java | 2 + .../realm/RealmJsonAbsentPrimaryKeyTests.java | 14 +- .../realm/RealmJsonNullPrimaryKeyTests.java | 31 +- .../java/io/realm/RealmJsonTests.java | 71 +- .../java/io/realm/RealmMigrationTests.java | 37 ++ .../java/io/realm/RealmObjectSchemaTests.java | 27 + .../java/io/realm/RealmObjectTests.java | 2 +- .../java/io/realm/RealmQueryTests.java | 117 +++- .../java/io/realm/RealmResultsTests.java | 121 +++- .../androidTest/java/io/realm/RealmTests.java | 29 +- .../java/io/realm/entities/AllJavaTypes.java | 22 + .../io/realm/entities/MappedAllJavaTypes.java | 5 +- .../realm/entities/NoPrimaryKeyNullTypes.java | 21 + .../entities/NullablePrimitiveFields.java | 11 + .../entities/pojo/AllTypesRealmModel.java | 2 + .../kotlin/io/realm/Decimal128Tests.kt | 284 ++++++-- .../kotlin/io/realm/ObjectIdTests.kt | 333 +++++++--- .../androidTest/kotlin/io/realm/UUIDTests.kt | 613 ++++++++++++++++++ .../src/main/cpp/io_realm_internal_OsList.cpp | 37 +- .../main/cpp/io_realm_internal_OsObject.cpp | 67 +- .../cpp/io_realm_internal_OsObjectStore.cpp | 2 +- .../main/cpp/io_realm_internal_OsResults.cpp | 8 + .../main/cpp/io_realm_internal_Property.cpp | 2 +- .../src/main/cpp/io_realm_internal_Table.cpp | 41 +- .../main/cpp/io_realm_internal_TableQuery.cpp | 135 ++++ .../cpp/io_realm_internal_UncheckedRow.cpp | 35 +- ...m_internal_objectstore_OsObjectBuilder.cpp | 24 + .../src/main/cpp/java_accessor.hpp | 14 + .../src/main/cpp/java_class_global_def.cpp | 6 + .../src/main/cpp/java_class_global_def.hpp | 4 + .../src/main/cpp/java_object_accessor.hpp | 21 + .../java/io/realm/DynamicRealmObject.java | 61 ++ .../main/java/io/realm/FrozenPendingRow.java | 11 + .../java/io/realm/ManagedListOperator.java | 51 ++ .../main/java/io/realm/RealmFieldType.java | 12 +- .../src/main/java/io/realm/RealmList.java | 5 + .../main/java/io/realm/RealmObjectSchema.java | 2 + .../src/main/java/io/realm/RealmQuery.java | 103 +++ .../src/main/java/io/realm/RealmResults.java | 25 + .../java/io/realm/internal/InvalidRow.java | 11 + .../main/java/io/realm/internal/OsList.java | 31 + .../main/java/io/realm/internal/OsObject.java | 22 + .../java/io/realm/internal/OsResults.java | 20 + .../java/io/realm/internal/PendingRow.java | 11 + .../main/java/io/realm/internal/Property.java | 13 + .../src/main/java/io/realm/internal/Row.java | 5 + .../main/java/io/realm/internal/Table.java | 23 + .../java/io/realm/internal/TableQuery.java | 52 +- .../java/io/realm/internal/UncheckedRow.java | 20 + .../realm/internal/core/QueryDescriptor.java | 4 +- .../internal/objectstore/OsObjectBuilder.java | 22 + .../testUtils/java/io/realm/TestHelper.java | 7 +- .../java/io/realm/entities/AllTypes.java | 27 +- .../java/io/realm/entities/NullTypes.java | 51 +- .../io/realm/entities/PrimaryKeyAsUUID.java | 57 ++ 72 files changed, 3044 insertions(+), 337 deletions(-) create mode 100644 realm/realm-library/src/androidTest/assets/uuid_as_string.json create mode 100644 realm/realm-library/src/androidTest/kotlin/io/realm/UUIDTests.kt create mode 100644 realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsUUID.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a5c512c1e..04160ae188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## 10.2.0 (YYYY-MM-DD) + +### Breaking Changes +* None. + +### Enhancements +* Added support for `java.util.UUID` as supported field in model classes. +* Added support for `java.util.UUID` as a primary key. + +### Fixes +* None. + +### Compatibility +* None. + +### Internal +* None. + + ## 10.1.0 (2020-10-23) ### Breaking Changes diff --git a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java index 6790fa9216..d42198f388 100644 --- a/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java +++ b/realm-annotations/src/main/java/io/realm/annotations/PrimaryKey.java @@ -31,7 +31,7 @@ * Primary keys also count as having the {@link Index} annotation. *

            * It is allowed to apply this annotation on the following primitive types: byte, short, int, and long. - * String, Byte, Short, Integer, Long and ObjectId are also allowed, and further permitted to have + * String, Byte, Short, Integer, Long, ObjectId and UUID are also allowed, and further permitted to have * {@code null} as a primary key value. *

            * This annotation is not allowed inside Realm classes marked as {@code \@RealmClass(embedded = true)}. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt index ed07dc98ad..923b93f7b2 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/ClassMetaData.kt @@ -16,36 +16,17 @@ package io.realm.processor -import java.util.ArrayList -import java.util.Arrays -import java.util.Collections -import java.util.LinkedHashSet -import java.util.Locale - +import io.realm.annotations.* +import io.realm.processor.nameconverter.NameConverter +import java.util.* import javax.annotation.processing.ProcessingEnvironment -import javax.lang.model.element.Element -import javax.lang.model.element.ElementKind -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.Modifier -import javax.lang.model.element.PackageElement -import javax.lang.model.element.TypeElement -import javax.lang.model.element.VariableElement +import javax.lang.model.element.* import javax.lang.model.type.DeclaredType import javax.lang.model.type.TypeKind import javax.lang.model.type.TypeMirror import javax.lang.model.util.Elements import javax.lang.model.util.Types -import io.realm.annotations.Ignore -import io.realm.annotations.Index -import io.realm.annotations.LinkingObjects -import io.realm.annotations.PrimaryKey -import io.realm.annotations.RealmClass -import io.realm.annotations.RealmField -import io.realm.annotations.RealmNamingPolicy -import io.realm.annotations.Required -import io.realm.processor.nameconverter.NameConverter - /** * Utility class for holding metadata for RealmProxy classes. */ @@ -84,7 +65,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat typeMirrors.PRIMITIVE_INT_MIRROR, typeMirrors.PRIMITIVE_SHORT_MIRROR, typeMirrors.PRIMITIVE_BYTE_MIRROR, - typeMirrors.OBJECT_ID_MIRROR + typeMirrors.OBJECT_ID_MIRROR, + typeMirrors.UUID_MIRROR ) private val validListValueTypes: List = Arrays.asList( typeMirrors.STRING_MIRROR, @@ -98,7 +80,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat typeMirrors.FLOAT_MIRROR, typeMirrors.DATE_MIRROR, typeMirrors.DECIMAL128_MIRROR, - typeMirrors.OBJECT_ID_MIRROR + typeMirrors.OBJECT_ID_MIRROR, + typeMirrors.UUID_MIRROR ) private val stringType = typeMirrors.STRING_MIRROR @@ -650,7 +633,7 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat } // The field has the @Index annotation. It's only valid for column types: - // STRING, DATE, INTEGER, BOOLEAN, RealmMutableInteger and OBJECT_ID + // STRING, DATE, INTEGER, BOOLEAN, RealmMutableInteger, OBJECT_ID and UUID private fun categorizeIndexField(element: Element, fieldElement: RealmFieldElement): Boolean { var indexable = false @@ -662,7 +645,8 @@ class ClassMetaData(env: ProcessingEnvironment, typeMirrors: TypeMirrors, privat Constants.RealmFieldType.DATE, Constants.RealmFieldType.INTEGER, Constants.RealmFieldType.BOOLEAN, - Constants.RealmFieldType.OBJECT_ID -> { indexable = true } + Constants.RealmFieldType.OBJECT_ID, + Constants.RealmFieldType.UUID -> { indexable = true } else -> { /* Ignore */ } } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt index f537c55725..7ae62491e0 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Constants.kt @@ -49,7 +49,9 @@ object Constants { "java.util.Date" to RealmFieldType.DATE, "byte[]" to RealmFieldType.BINARY, "org.bson.types.Decimal128" to RealmFieldType.DECIMAL128, - "org.bson.types.ObjectId" to RealmFieldType.OBJECT_ID) + "org.bson.types.ObjectId" to RealmFieldType.OBJECT_ID, + "java.util.UUID" to RealmFieldType.UUID + ) val LIST_ELEMENT_TYPE_TO_REALM_TYPES = mapOf( "java.lang.Byte" to RealmFieldType.INTEGER_LIST, @@ -63,7 +65,8 @@ object Constants { "java.util.Date" to RealmFieldType.DATE_LIST, "byte[]" to RealmFieldType.BINARY_LIST, "org.bson.types.Decimal128" to RealmFieldType.DECIMAL128_LIST, - "org.bson.types.ObjectId" to RealmFieldType.OBJECT_ID_LIST + "org.bson.types.ObjectId" to RealmFieldType.OBJECT_ID_LIST, + "java.util.UUID" to RealmFieldType.UUID_LIST ) /** @@ -86,6 +89,7 @@ object Constants { LIST("LIST", "List"), DECIMAL128("DECIMAL128", "Decimal128"), OBJECT_ID("OBJECT_ID", "ObjectId"), + UUID("UUID", "UUID"), BACKLINK("LINKING_OBJECTS", null), @@ -97,7 +101,8 @@ object Constants { FLOAT_LIST("FLOAT_LIST", "List"), DOUBLE_LIST("DOUBLE_LIST", "List"), DECIMAL128_LIST("DECIMAL128_LIST", "List"), - OBJECT_ID_LIST("OBJECT_ID_LIST", "List"); + OBJECT_ID_LIST("OBJECT_ID_LIST", "List"), + UUID_LIST("UUID_LIST", "List"); /** * The name of the enum, used in the Java bindings, used to represent the corresponding type. diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt index 4e74bfda84..c39d438005 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/OsObjectBuilderTypeHelper.kt @@ -15,9 +15,7 @@ */ package io.realm.processor -import java.util.Collections -import java.util.HashMap - +import java.util.* import javax.lang.model.element.VariableElement /** @@ -52,6 +50,7 @@ object OsObjectBuilderTypeHelper { this[QualifiedClassName("java.util.Date")] = "Date" this[QualifiedClassName("org.bson.types.Decimal128")] = "Decimal128" this[QualifiedClassName("org.bson.types.ObjectId")] = "ObjectId" + this[QualifiedClassName("java.util.UUID")] = "UUID" this[QualifiedClassName("io.realm.MutableRealmInteger")] = "MutableRealmInteger" } QUALIFIED_TYPE_TO_BUILDER = Collections.unmodifiableMap(fieldTypes) @@ -72,6 +71,7 @@ object OsObjectBuilderTypeHelper { this[QualifiedClassName("io.realm.MutableRealmInteger")] = "MutableRealmIntegerList" this[QualifiedClassName("org.bson.types.Decimal128")] = "Decimal128List" this[QualifiedClassName("org.bson.types.ObjectId")] = "ObjectIdList" + this[QualifiedClassName("java.util.UUID")] = "UUIDList" } QUALIFIED_LIST_TYPE_TO_BUILDER = Collections.unmodifiableMap(listTypes) } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt index 9c77f3a587..9a25b2046a 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmJsonTypeHelper.kt @@ -48,6 +48,7 @@ object RealmJsonTypeHelper { m[QualifiedClassName("java.util.Date")] = DateTypeConverter() m[QualifiedClassName("org.bson.types.Decimal128")] = Decimal128TypeConverter() m[QualifiedClassName("org.bson.types.ObjectId")] = ObjectIdTypeConverter() + m[QualifiedClassName("java.util.UUID")] = UUIDTypeConverter() m[QualifiedClassName("io.realm.MutableRealmInteger")] = MutableRealmIntegerTypeConverter() JAVA_TO_JSON_TYPES = Collections.unmodifiableMap(m) } @@ -377,16 +378,65 @@ object RealmJsonTypeHelper { override fun emitTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { writer.apply { beginControlFlow("if (json.has(\"%s\"))", fieldName) - beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - emitStatement("%s.%s(null)", varName, accessor) - nextControlFlow("else") - emitStatement("Object id = json.get(\"%s\")", fieldName) - beginControlFlow("if (id instanceof org.bson.types.ObjectId)") - emitStatement("%s.%s((org.bson.types.ObjectId) id)", varName, accessor) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("%s.%s(null)", varName, accessor) + nextControlFlow("else") + emitStatement("Object id = json.get(\"%s\")", fieldName) + beginControlFlow("if (id instanceof org.bson.types.ObjectId)") + emitStatement("%s.%s((org.bson.types.ObjectId) id)", varName, accessor) + nextControlFlow("else") + emitStatement("%s.%s(new org.bson.types.ObjectId((String)id))", varName, accessor) + endControlFlow() + endControlFlow() + endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitStreamTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter, isPrimaryKey: Boolean) { + writer.apply { + beginControlFlow("if (reader.peek() == JsonToken.NULL)") + emitStatement("reader.skipValue()") + emitStatement("%s.%s(null)", varName, accessor) nextControlFlow("else") - emitStatement("%s.%s(new org.bson.types.ObjectId((String)id))", varName, accessor) + emitStatement("%s.%s(new org.bson.types.ObjectId(reader.nextString()))", varName, accessor) endControlFlow() + } + } + + @Throws(IOException::class) + override fun emitGetObjectWithPrimaryKeyValue(realmObjectClass: QualifiedClassName, realmObjectProxyClass: QualifiedClassName, fieldName: String, writer: JavaWriter) { + // No error checking is done here for valid primary key types. + // This should be done by the annotation processor. + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, null, true, excludeFields)", realmObjectProxyClass, realmObjectClass) + nextControlFlow("else") + emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, json.get(\"%3\$s\"), true, excludeFields)", realmObjectProxyClass, realmObjectClass, fieldName) + endControlFlow() + nextControlFlow("else") + emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) endControlFlow() + } + } + } + + private class UUIDTypeConverter() : JsonToRealmFieldTypeConverter { + @Throws(IOException::class) + override fun emitTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter) { + writer.apply { + beginControlFlow("if (json.has(\"%s\"))", fieldName) + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("%s.%s(null)", varName, accessor) + nextControlFlow("else") + emitStatement("Object id = json.get(\"%s\")", fieldName) + beginControlFlow("if (id instanceof java.util.UUID)") + emitStatement("%s.%s((java.util.UUID) id)", varName, accessor) + nextControlFlow("else") + emitStatement("%s.%s(java.util.UUID.fromString((String)id))", varName, accessor) + endControlFlow() + endControlFlow() endControlFlow() } } @@ -395,10 +445,10 @@ object RealmJsonTypeHelper { override fun emitStreamTypeConversion(varName: String, accessor: String, fieldName: String, fieldType: QualifiedClassName, writer: JavaWriter, isPrimaryKey: Boolean) { writer.apply { beginControlFlow("if (reader.peek() == JsonToken.NULL)") - emitStatement("reader.skipValue()") - emitStatement("%s.%s(null)", varName, accessor) + emitStatement("reader.skipValue()") + emitStatement("%s.%s(null)", varName, accessor) nextControlFlow("else") - emitStatement("%s.%s(new org.bson.types.ObjectId(reader.nextString()))", varName, accessor) + emitStatement("%s.%s(java.util.UUID.fromString(reader.nextString()))", varName, accessor) endControlFlow() } } @@ -409,13 +459,13 @@ object RealmJsonTypeHelper { // This should be done by the annotation processor. writer.apply { beginControlFlow("if (json.has(\"%s\"))", fieldName) - beginControlFlow("if (json.isNull(\"%s\"))", fieldName) - emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, null, true, excludeFields)", realmObjectProxyClass, realmObjectClass) - nextControlFlow("else") - emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, json.get(\"%3\$s\"), true, excludeFields)", realmObjectProxyClass, realmObjectClass, fieldName) - endControlFlow() + beginControlFlow("if (json.isNull(\"%s\"))", fieldName) + emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, null, true, excludeFields)", realmObjectProxyClass, realmObjectClass) + nextControlFlow("else") + emitStatement("obj = (%1\$s) realm.createObjectInternal(%2\$s.class, json.get(\"%3\$s\"), true, excludeFields)", realmObjectProxyClass, realmObjectClass, fieldName) + endControlFlow() nextControlFlow("else") - emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) + emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, fieldName) endControlFlow() } } diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt index b9ff7e72a5..bce72366ea 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/RealmProxyClassGenerator.kt @@ -572,6 +572,9 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi if (typeUtils.isSameType(elementTypeMirror, typeMirrors.OBJECT_ID_MIRROR)) { return "$osListVariableName.addObjectId($valueVariableName)" } + if (typeUtils.isSameType(elementTypeMirror, typeMirrors.UUID_MIRROR)) { + return "$osListVariableName.addUUID($valueVariableName)" + } throw RuntimeException("unexpected element type: $elementTypeMirror") } @@ -630,7 +633,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("realm.checkIfValid()") emitStatement("proxyState.getRow\$realm().checkIfAttached()") beginControlFlow("if ($cacheFieldName == null)") - emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\")", backlink.sourceClass, backlink.sourceField) + emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\")", backlink.sourceClass, backlink.sourceField) endControlFlow() emitStatement("return $cacheFieldName") endMethod() @@ -646,7 +649,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("realm.checkIfValid()") emitStatement("proxyState.getRow\$realm().checkIfAttached()") beginControlFlow("if ($cacheFieldName == null)") - emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\").first()", backlink.sourceClass, backlink.sourceField) + emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\").first()", backlink.sourceClass, backlink.sourceField) endControlFlow() emitStatement("return $cacheFieldName") // TODO: Figure out the exact API for this endMethod() @@ -706,6 +709,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi Constants.RealmFieldType.FLOAT_LIST, Constants.RealmFieldType.DECIMAL128_LIST, Constants.RealmFieldType.OBJECT_ID_LIST, + Constants.RealmFieldType.UUID_LIST, Constants.RealmFieldType.DOUBLE_LIST -> { val requiredFlag = if (metadata.isElementNullable(field)) "!Property.REQUIRED" else "Property.REQUIRED" emitStatement("builder.addPersistedValueListProperty(\"%s\", %s, %s)", fieldName, fieldType.realmType, requiredFlag) @@ -722,6 +726,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi Constants.RealmFieldType.BINARY, Constants.RealmFieldType.DECIMAL128, Constants.RealmFieldType.OBJECT_ID, + Constants.RealmFieldType.UUID, Constants.RealmFieldType.REALM_INTEGER -> { val nullableFlag = (if (metadata.isNullable(field)) "!" else "") + "Property.REQUIRED" val indexedFlag = (if (metadata.isIndexed(field)) "" else "!") + "Property.INDEXED" @@ -850,9 +855,17 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("org.bson.types.ObjectId value = ((%s) object).%s()", interfaceName, primaryKeyGetter) emitStatement("long objKey = Table.NO_MATCH") beginControlFlow("if (value == null)") - emitStatement("objKey = table.findFirstNull(pkColumnKey)") + emitStatement("objKey = table.findFirstNull(pkColumnKey)") nextControlFlow("else") - emitStatement("objKey = table.findFirstObjectId(pkColumnKey, value)") + emitStatement("objKey = table.findFirstObjectId(pkColumnKey, value)") + endControlFlow() + } else if (Utils.isUUID(primaryKeyElement)) { + emitStatement("java.util.UUID value = ((%s) object).%s()", interfaceName, primaryKeyGetter) + emitStatement("long objKey = Table.NO_MATCH") + beginControlFlow("if (value == null)") + emitStatement("objKey = table.findFirstNull(pkColumnKey)") + nextControlFlow("else") + emitStatement("objKey = table.findFirstUUID(pkColumnKey, value)") endControlFlow() } else { emitStatement("Number value = ((%s) object).%s()", interfaceName, primaryKeyGetter) @@ -866,10 +879,10 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } else { if (Utils.isString(primaryKeyElement)) { emitStatement("long objKey = table.findFirstString(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) - } else if (Utils.isObjectId(primaryKeyElement)) { emitStatement("long objKey = table.findFirstObjectId(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) - + } else if (Utils.isUUID(primaryKeyElement)) { + emitStatement("long objKey = table.findFirstUUID(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) } else { emitStatement("long objKey = table.findFirstLong(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) } @@ -1001,21 +1014,31 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi "org.bson.types.Decimal128" -> { emitStatement("org.bson.types.Decimal128 %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetDecimal128(tableNativePtr, columnInfo.%1\$sColKey, objKey, %2\$s.getLow(), %2\$s.getHigh(), false)", fieldName, getter) - if (isUpdate) { - nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) - } + emitStatement("Table.nativeSetDecimal128(tableNativePtr, columnInfo.%1\$sColKey, objKey, %2\$s.getLow(), %2\$s.getHigh(), false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) + } endControlFlow() } "org.bson.types.ObjectId" -> { emitStatement("org.bson.types.ObjectId %s = ((%s) object).%s()", getter, interfaceName, getter) beginControlFlow("if (%s != null)", getter) - emitStatement("Table.nativeSetObjectId(tableNativePtr, columnInfo.%sColKey, objKey, %s.toString(), false)", fieldName, getter) - if (isUpdate) { - nextControlFlow("else") - emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) - } + emitStatement("Table.nativeSetObjectId(tableNativePtr, columnInfo.%sColKey, objKey, %s.toString(), false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) + } + endControlFlow() + } + "java.util.UUID" -> { + emitStatement("java.util.UUID %s = ((%s) object).%s()", getter, interfaceName, getter) + beginControlFlow("if (%s != null)", getter) + emitStatement("Table.nativeSetUUID(tableNativePtr, columnInfo.%sColKey, objKey, %s.toString(), false)", fieldName, getter) + if (isUpdate) { + nextControlFlow("else") + emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName) + } endControlFlow() } else -> { @@ -1535,17 +1558,25 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) emitStatement("long objKey = Table.NO_MATCH") beginControlFlow("if (primaryKeyValue == null)") - emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") + emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") nextControlFlow("else") - emitStatement("objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue)") + emitStatement("objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue)") endControlFlow() } else if (Utils.isObjectId(primaryKeyElement)) { emitStatement("org.bson.types.ObjectId primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) emitStatement("long objKey = Table.NO_MATCH") beginControlFlow("if (primaryKeyValue == null)") - emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") + emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") + nextControlFlow("else") + emitStatement("objKey = Table.nativeFindFirstObjectId(tableNativePtr, pkColumnKey, primaryKeyValue.toString())") + endControlFlow() + } else if (Utils.isUUID(primaryKeyElement)) { + emitStatement("java.util.UUID primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) + emitStatement("long objKey = Table.NO_MATCH") + beginControlFlow("if (primaryKeyValue == null)") + emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)") nextControlFlow("else") - emitStatement("objKey = Table.nativeFindFirstObjectId(tableNativePtr, pkColumnKey, primaryKeyValue.toString())") + emitStatement("objKey = Table.nativeFindFirstUUID(tableNativePtr, pkColumnKey, primaryKeyValue.toString())") endControlFlow() } else { emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter) @@ -1564,6 +1595,8 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi emitStatement("objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, (String)primaryKeyValue)") } else if (Utils.isObjectId(metadata.primaryKey)) { emitStatement("objKey = Table.nativeFindFirstObjectId(tableNativePtr, pkColumnKey, ((org.bson.types.ObjectId)primaryKeyValue).toString())") + } else if (Utils.isUUID(metadata.primaryKey)) { + emitStatement("objKey = Table.nativeFindFirstUUID(tableNativePtr, pkColumnKey, ((java.util.UUID)primaryKeyValue).toString())") } else { emitStatement("objKey = Table.nativeFindFirstInt(tableNativePtr, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) } @@ -1571,7 +1604,7 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi } beginControlFlow("if (objKey == Table.NO_MATCH)") - if (Utils.isString(metadata.primaryKey) || Utils.isObjectId(metadata.primaryKey)) { + if (Utils.isString(metadata.primaryKey) || Utils.isObjectId(metadata.primaryKey) || Utils.isUUID(metadata.primaryKey)) { emitStatement("objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue)") } else { emitStatement("objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter) @@ -2105,6 +2138,10 @@ class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvi pkType = "ObjectId" findFirstCast = "(org.bson.types.ObjectId)" jsonAccessorMethodSuffix = "" + } else if (Utils.isUUID(metadata.primaryKey)) { + pkType = "UUID" + findFirstCast = "(java.util.UUID)" + jsonAccessorMethodSuffix = "" } val nullableMetadata = if (Utils.isObjectId(metadata.primaryKey)) { "objKey = table.findFirst%s(pkColumnKey, new org.bson.types.ObjectId((String)json.get%s(\"%s\")))".format(pkType, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt index ab27ca71f1..2123f1f4dd 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/TypeMirrors.kt @@ -18,8 +18,7 @@ package io.realm.processor import org.bson.types.Decimal128 import org.bson.types.ObjectId -import java.util.Date - +import java.util.* import javax.annotation.processing.ProcessingEnvironment import javax.lang.model.element.VariableElement import javax.lang.model.type.DeclaredType @@ -46,6 +45,7 @@ class TypeMirrors(env: ProcessingEnvironment) { @JvmField val DATE_MIRROR: TypeMirror @JvmField val DECIMAL128_MIRROR: TypeMirror @JvmField val OBJECT_ID_MIRROR: TypeMirror + @JvmField val UUID_MIRROR: TypeMirror @JvmField val PRIMITIVE_LONG_MIRROR: TypeMirror @JvmField val PRIMITIVE_INT_MIRROR: TypeMirror @@ -68,6 +68,7 @@ class TypeMirrors(env: ProcessingEnvironment) { DATE_MIRROR = elementUtils.getTypeElement(Date::class.javaObjectType.name).asType() DECIMAL128_MIRROR = elementUtils.getTypeElement(Decimal128::class.javaObjectType.name).asType() OBJECT_ID_MIRROR = elementUtils.getTypeElement(ObjectId::class.javaObjectType.name).asType() + UUID_MIRROR = elementUtils.getTypeElement(UUID::class.javaObjectType.name).asType() PRIMITIVE_LONG_MIRROR = typeUtils.getPrimitiveType(TypeKind.LONG) PRIMITIVE_INT_MIRROR = typeUtils.getPrimitiveType(TypeKind.INT) diff --git a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt index dd2264784c..0e561e95a0 100644 --- a/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt +++ b/realm/realm-annotations-processor/src/main/java/io/realm/processor/Utils.kt @@ -16,13 +16,11 @@ package io.realm.processor +import io.realm.annotations.RealmNamingPolicy +import io.realm.processor.nameconverter.* import javax.annotation.processing.Messager import javax.annotation.processing.ProcessingEnvironment -import javax.lang.model.element.Element -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.Modifier -import javax.lang.model.element.TypeElement -import javax.lang.model.element.VariableElement +import javax.lang.model.element.* import javax.lang.model.type.DeclaredType import javax.lang.model.type.ReferenceType import javax.lang.model.type.TypeKind @@ -30,13 +28,6 @@ import javax.lang.model.type.TypeMirror import javax.lang.model.util.Types import javax.tools.Diagnostic -import io.realm.annotations.RealmNamingPolicy -import io.realm.processor.nameconverter.CamelCaseConverter -import io.realm.processor.nameconverter.IdentityConverter -import io.realm.processor.nameconverter.LowerCaseWithSeparatorConverter -import io.realm.processor.nameconverter.NameConverter -import io.realm.processor.nameconverter.PascalCaseConverter - /** * Utility methods working with the Realm processor. */ @@ -115,6 +106,17 @@ object Utils { return getFieldTypeQualifiedName(field).toString() == "org.bson.types.ObjectId" } + /** + * @return `true` if a field is of type "java.util.UUID", `false` otherwise. + * @throws IllegalArgumentException if the field is `null`. + */ + fun isUUID(field: VariableElement?): Boolean { + if (field == null) { + throw IllegalArgumentException("Argument 'field' cannot be null.") + } + return getFieldTypeQualifiedName(field).toString() == "java.util.UUID" + } + /** * @return `true` if a field is a primitive type, `false` otherwise. * @throws IllegalArgumentException if the typeString is `null`. diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java index 511adf03e1..5ca816782a 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/RealmProcessorTest.java @@ -278,7 +278,7 @@ public void compileCustomAccessor() { @Test public void compileIndexTypes() throws IOException { final String[] validIndexFieldTypes = {"byte", "short", "int", "long", "boolean", "String", "java.util.Date", - "Byte", "Short", "Integer", "Long", "Boolean"}; + "Byte", "Short", "Integer", "Long", "Boolean", "org.bson.types.ObjectId", "java.util.UUID"}; for (String fieldType : validIndexFieldTypes) { RealmSyntheticTestClass javaFileObject = @@ -293,7 +293,7 @@ public void compileIndexTypes() throws IOException { // Unsupported "Index" annotation types @Test public void compileInvalidIndexTypes() throws IOException { - final String[] invalidIndexFieldTypes = {"float", "double", "byte[]", "Simple", "RealmList", "Float", "Double"}; + final String[] invalidIndexFieldTypes = {"float", "double", "byte[]", "Simple", "RealmList", "Float", "Double", "org.bson.types.Decimal128"}; for (String fieldType : invalidIndexFieldTypes) { RealmSyntheticTestClass javaFileObject = @@ -308,7 +308,7 @@ public void compileInvalidIndexTypes() throws IOException { // Supported "PrimaryKey" annotation types @Test public void compilePrimaryKeyTypes() throws IOException { - final String[] validPrimaryKeyFieldTypes = {"byte", "short", "int", "long", "String", "Byte", "Short", "Integer", "Long"}; + final String[] validPrimaryKeyFieldTypes = {"byte", "short", "int", "long", "String", "Byte", "Short", "Integer", "Long", "org.bson.types.ObjectId", "java.util.UUID"}; for (String fieldType : validPrimaryKeyFieldTypes) { RealmSyntheticTestClass javaFileObject = @@ -323,7 +323,7 @@ public void compilePrimaryKeyTypes() throws IOException { // Unsupported "PrimaryKey" annotation types @Test public void compileInvalidPrimaryKeyTypes() throws IOException { - final String[] invalidPrimaryKeyFieldTypes = {"boolean", "java.util.Date", "Simple", "RealmList", "Boolean"}; + final String[] invalidPrimaryKeyFieldTypes = {"boolean", "java.util.Date", "Simple", "RealmList", "Boolean", "org.bson.types.Decimal128"}; for (String fieldType : invalidPrimaryKeyFieldTypes) { RealmSyntheticTestClass javaFileObject = @@ -339,7 +339,7 @@ public void compileInvalidPrimaryKeyTypes() throws IOException { @Test public void compileRequiredTypes() throws IOException { final String[] validPrimaryKeyFieldTypes = {"Byte", "Short", "Integer", "Long", "String", - "Float", "Double", "Boolean", "java.util.Date"}; + "Float", "Double", "Boolean", "java.util.Date", "org.bson.types.ObjectId", "org.bson.types.Decimal128", "java.util.UUID"}; for (String fieldType : validPrimaryKeyFieldTypes) { RealmSyntheticTestClass javaFileObject = diff --git a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java index e4a6abe7c4..540fd25383 100644 --- a/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java +++ b/realm/realm-annotations-processor/src/test/resources/io/realm/some_test_AllTypesRealmProxy.java @@ -46,6 +46,7 @@ static final class AllTypesColumnInfo extends ColumnInfo { long columnBooleanColKey; long columnDecimal128ColKey; long columnObjectIdColKey; + long columnUUIDColKey; long columnDateColKey; long columnBinaryColKey; long columnMutableRealmIntegerColKey; @@ -64,9 +65,10 @@ static final class AllTypesColumnInfo extends ColumnInfo { long columnDateListColKey; long columnDecimal128ListColKey; long columnObjectIdListColKey; + long columnUUIDListColKey; AllTypesColumnInfo(OsSchemaInfo schemaInfo) { - super(25); + super(27); OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo("AllTypes"); this.columnStringColKey = addColumnDetails("columnString", "columnString", objectSchemaInfo); this.columnLongColKey = addColumnDetails("columnLong", "columnLong", objectSchemaInfo); @@ -75,6 +77,7 @@ static final class AllTypesColumnInfo extends ColumnInfo { this.columnBooleanColKey = addColumnDetails("columnBoolean", "columnBoolean", objectSchemaInfo); this.columnDecimal128ColKey = addColumnDetails("columnDecimal128", "columnDecimal128", objectSchemaInfo); this.columnObjectIdColKey = addColumnDetails("columnObjectId", "columnObjectId", objectSchemaInfo); + this.columnUUIDColKey = addColumnDetails("columnUUID", "columnUUID", objectSchemaInfo); this.columnDateColKey = addColumnDetails("columnDate", "columnDate", objectSchemaInfo); this.columnBinaryColKey = addColumnDetails("columnBinary", "columnBinary", objectSchemaInfo); this.columnMutableRealmIntegerColKey = addColumnDetails("columnMutableRealmInteger", "columnMutableRealmInteger", objectSchemaInfo); @@ -93,6 +96,7 @@ static final class AllTypesColumnInfo extends ColumnInfo { this.columnDateListColKey = addColumnDetails("columnDateList", "columnDateList", objectSchemaInfo); this.columnDecimal128ListColKey = addColumnDetails("columnDecimal128List", "columnDecimal128List", objectSchemaInfo); this.columnObjectIdListColKey = addColumnDetails("columnObjectIdList", "columnObjectIdList", objectSchemaInfo); + this.columnUUIDListColKey = addColumnDetails("columnUUIDList", "columnUUIDList", objectSchemaInfo); addBacklinkDetails(schemaInfo, "parentObjects", "AllTypes", "columnObject"); } @@ -117,6 +121,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.columnBooleanColKey = src.columnBooleanColKey; dst.columnDecimal128ColKey = src.columnDecimal128ColKey; dst.columnObjectIdColKey = src.columnObjectIdColKey; + dst.columnUUIDColKey = src.columnUUIDColKey; dst.columnDateColKey = src.columnDateColKey; dst.columnBinaryColKey = src.columnBinaryColKey; dst.columnMutableRealmIntegerColKey = src.columnMutableRealmIntegerColKey; @@ -135,6 +140,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { dst.columnDateListColKey = src.columnDateListColKey; dst.columnDecimal128ListColKey = src.columnDecimal128ListColKey; dst.columnObjectIdListColKey = src.columnObjectIdListColKey; + dst.columnUUIDListColKey = src.columnUUIDListColKey; } } @@ -160,6 +166,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { private RealmList columnDateListRealmList; private RealmList columnDecimal128ListRealmList; private RealmList columnObjectIdListRealmList; + private RealmList columnUUIDListRealmList; private RealmResults parentObjectsBacklinks; some_test_AllTypesRealmProxy() { @@ -342,6 +349,34 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { proxyState.getRow$realm().setObjectId(columnInfo.columnObjectIdColKey, value); } + @Override + @SuppressWarnings("cast") + public java.util.UUID realmGet$columnUUID() { + proxyState.getRealm$realm().checkIfValid(); + return (java.util.UUID) proxyState.getRow$realm().getUUID(columnInfo.columnUUIDColKey); + } + + @Override + public void realmSet$columnUUID(java.util.UUID value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + final Row row = proxyState.getRow$realm(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'columnUUID' to null."); + } + row.getTable().setUUID(columnInfo.columnUUIDColKey, row.getObjectKey(), value, true); + return; + } + + proxyState.getRealm$realm().checkIfValid(); + if (value == null) { + throw new IllegalArgumentException("Trying to set non-nullable field 'columnUUID' to null."); + } + proxyState.getRow$realm().setUUID(columnInfo.columnUUIDColKey, value); + } + @Override @SuppressWarnings("cast") public Date realmGet$columnDate() { @@ -1036,6 +1071,45 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } } + @Override + public RealmList realmGet$columnUUIDList() { + proxyState.getRealm$realm().checkIfValid(); + // use the cached value if available + if (columnUUIDListRealmList != null) { + return columnUUIDListRealmList; + } else { + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnUUIDListColKey, RealmFieldType.UUID_LIST); + columnUUIDListRealmList = new RealmList(java.util.UUID.class, osList, proxyState.getRealm$realm()); + return columnUUIDListRealmList; + } + } + + @Override + public void realmSet$columnUUIDList(RealmList value) { + if (proxyState.isUnderConstruction()) { + if (!proxyState.getAcceptDefaultValue$realm()) { + return; + } + if (proxyState.getExcludeFields$realm().contains("columnUUIDList")) { + return; + } + } + + proxyState.getRealm$realm().checkIfValid(); + OsList osList = proxyState.getRow$realm().getValueList(columnInfo.columnUUIDListColKey, RealmFieldType.UUID_LIST); + osList.removeAll(); + if (value == null) { + return; + } + for (java.util.UUID item : value) { + if (item == null) { + osList.addNull(); + } else { + osList.addUUID(item); + } + } + } + @Override public RealmResults realmGet$parentObjects() { BaseRealm realm = proxyState.getRealm$realm(); @@ -1048,7 +1122,7 @@ protected final void copy(ColumnInfo rawSrc, ColumnInfo rawDst) { } private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { - OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", false, 25, 1); + OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder("AllTypes", false, 27, 1); builder.addPersistedProperty("columnString", RealmFieldType.STRING, Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); builder.addPersistedProperty("columnLong", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnFloat", RealmFieldType.FLOAT, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); @@ -1056,6 +1130,7 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { builder.addPersistedProperty("columnBoolean", RealmFieldType.BOOLEAN, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnDecimal128", RealmFieldType.DECIMAL128, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnObjectId", RealmFieldType.OBJECT_ID, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); + builder.addPersistedProperty("columnUUID", RealmFieldType.UUID, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED); builder.addPersistedProperty("columnMutableRealmInteger", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED); @@ -1074,6 +1149,7 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() { builder.addPersistedValueListProperty("columnDateList", RealmFieldType.DATE_LIST, !Property.REQUIRED); builder.addPersistedValueListProperty("columnDecimal128List", RealmFieldType.DECIMAL128_LIST, !Property.REQUIRED); builder.addPersistedValueListProperty("columnObjectIdList", RealmFieldType.OBJECT_ID_LIST, !Property.REQUIRED); + builder.addPersistedValueListProperty("columnUUIDList", RealmFieldType.UUID_LIST, !Property.REQUIRED); builder.addComputedLinkProperty("parentObjects", "AllTypes", "columnObject"); return builder.build(); } @@ -1097,7 +1173,7 @@ public static final class ClassNameHelper { @SuppressWarnings("cast") public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSONObject json, boolean update) throws JSONException { - final List excludeFields = new ArrayList(15); + final List excludeFields = new ArrayList(16); some.test.AllTypes obj = null; if (update) { Table table = realm.getTable(some.test.AllTypes.class); @@ -1165,6 +1241,9 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON if (json.has("columnObjectIdList")) { excludeFields.add("columnObjectIdList"); } + if (json.has("columnUUIDList")) { + excludeFields.add("columnUUIDList"); + } if (json.has("columnString")) { if (json.isNull("columnString")) { obj = (io.realm.some_test_AllTypesRealmProxy) realm.createObjectInternal(some.test.AllTypes.class, null, true, excludeFields); @@ -1237,6 +1316,18 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON } } } + if (json.has("columnUUID")) { + if (json.isNull("columnUUID")) { + objProxy.realmSet$columnUUID(null); + } else { + Object id = json.get("columnUUID"); + if (id instanceof java.util.UUID) { + objProxy.realmSet$columnUUID((java.util.UUID) id); + } else { + objProxy.realmSet$columnUUID(java.util.UUID.fromString((String)id)); + } + } + } if (json.has("columnDate")) { if (json.isNull("columnDate")) { objProxy.realmSet$columnDate(null); @@ -1303,6 +1394,7 @@ public static some.test.AllTypes createOrUpdateUsingJsonObject(Realm realm, JSON ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnDateList(), json, "columnDateList"); ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnDecimal128List(), json, "columnDecimal128List"); ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnObjectIdList(), json, "columnObjectIdList"); + ProxyUtils.setRealmListWithJsonObject(objProxy.realmGet$columnUUIDList(), json, "columnUUIDList"); return obj; } @@ -1367,6 +1459,13 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r } else { objProxy.realmSet$columnObjectId(new org.bson.types.ObjectId(reader.nextString())); } + } else if (name.equals("columnUUID")) { + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + objProxy.realmSet$columnUUID(null); + } else { + objProxy.realmSet$columnUUID(java.util.UUID.fromString(reader.nextString())); + } } else if (name.equals("columnDate")) { if (reader.peek() == JsonToken.NULL) { reader.skipValue(); @@ -1452,6 +1551,8 @@ public static some.test.AllTypes createUsingJsonStream(Realm realm, JsonReader r objProxy.realmSet$columnDecimal128List(ProxyUtils.createRealmListWithJsonStream(org.bson.types.Decimal128.class, reader)); } else if (name.equals("columnObjectIdList")) { objProxy.realmSet$columnObjectIdList(ProxyUtils.createRealmListWithJsonStream(org.bson.types.ObjectId.class, reader)); + } else if (name.equals("columnUUIDList")) { + objProxy.realmSet$columnUUIDList(ProxyUtils.createRealmListWithJsonStream(java.util.UUID.class, reader)); } else { reader.skipValue(); } @@ -1535,6 +1636,7 @@ public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo builder.addBoolean(columnInfo.columnBooleanColKey, unmanagedSource.realmGet$columnBoolean()); builder.addDecimal128(columnInfo.columnDecimal128ColKey, unmanagedSource.realmGet$columnDecimal128()); builder.addObjectId(columnInfo.columnObjectIdColKey, unmanagedSource.realmGet$columnObjectId()); + builder.addUUID(columnInfo.columnUUIDColKey, unmanagedSource.realmGet$columnUUID()); builder.addDate(columnInfo.columnDateColKey, unmanagedSource.realmGet$columnDate()); builder.addByteArray(columnInfo.columnBinaryColKey, unmanagedSource.realmGet$columnBinary()); builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, unmanagedSource.realmGet$columnMutableRealmInteger()); @@ -1550,6 +1652,7 @@ public static some.test.AllTypes copy(Realm realm, AllTypesColumnInfo columnInfo builder.addDateList(columnInfo.columnDateListColKey, unmanagedSource.realmGet$columnDateList()); builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, unmanagedSource.realmGet$columnDecimal128List()); builder.addObjectIdList(columnInfo.columnObjectIdListColKey, unmanagedSource.realmGet$columnObjectIdList()); + builder.addUUIDList(columnInfo.columnUUIDListColKey, unmanagedSource.realmGet$columnUUIDList()); // Create the underlying object and cache it before setting any object/objectlist references // This will allow us to break any circular dependencies by using the object cache. @@ -1636,6 +1739,10 @@ public static long insert(Realm realm, some.test.AllTypes object, Map columnUUIDListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnUUIDList(); + if (columnUUIDListList != null) { + OsList columnUUIDListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnUUIDListColKey); + for (java.util.UUID columnUUIDListItem : columnUUIDListList) { + if (columnUUIDListItem == null) { + columnUUIDListOsList.addNull(); + } else { + columnUUIDListOsList.addUUID(columnUUIDListItem); + } + } + } return objKey; } @@ -1868,6 +1987,10 @@ public static void insert(Realm realm, Iterator objects, M if (realmGet$columnObjectId != null) { Table.nativeSetObjectId(tableNativePtr, columnInfo.columnObjectIdColKey, objKey, realmGet$columnObjectId.toString(), false); } + java.util.UUID realmGet$columnUUID = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnUUID(); + if (realmGet$columnUUID != null) { + Table.nativeSetUUID(tableNativePtr, columnInfo.columnUUIDColKey, objKey, realmGet$columnUUID.toString(), false); + } java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, objKey, realmGet$columnDate.getTime(), false); @@ -2057,6 +2180,18 @@ public static void insert(Realm realm, Iterator objects, M } } } + + RealmList columnUUIDListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnUUIDList(); + if (columnUUIDListList != null) { + OsList columnUUIDListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnUUIDListColKey); + for (java.util.UUID columnUUIDListItem : columnUUIDListList) { + if (columnUUIDListItem == null) { + columnUUIDListOsList.addNull(); + } else { + columnUUIDListOsList.addUUID(columnUUIDListItem); + } + } + } } } @@ -2095,6 +2230,12 @@ public static long insertOrUpdate(Realm realm, some.test.AllTypes object, Map columnUUIDListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnUUIDList(); + if (columnUUIDListList != null) { + for (java.util.UUID columnUUIDListItem : columnUUIDListList) { + if (columnUUIDListItem == null) { + columnUUIDListOsList.addNull(); + } else { + columnUUIDListOsList.addUUID(columnUUIDListItem); + } + } + } + return objKey; } @@ -2391,6 +2546,12 @@ public static void insertOrUpdate(Realm realm, Iterator ob } else { Table.nativeSetNull(tableNativePtr, columnInfo.columnObjectIdColKey, objKey, false); } + java.util.UUID realmGet$columnUUID = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnUUID(); + if (realmGet$columnUUID != null) { + Table.nativeSetUUID(tableNativePtr, columnInfo.columnUUIDColKey, objKey, realmGet$columnUUID.toString(), false); + } else { + Table.nativeSetNull(tableNativePtr, columnInfo.columnUUIDColKey, objKey, false); + } java.util.Date realmGet$columnDate = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnDate(); if (realmGet$columnDate != null) { Table.nativeSetTimestamp(tableNativePtr, columnInfo.columnDateColKey, objKey, realmGet$columnDate.getTime(), false); @@ -2642,6 +2803,20 @@ public static void insertOrUpdate(Realm realm, Iterator ob } } + + OsList columnUUIDListOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.columnUUIDListColKey); + columnUUIDListOsList.removeAll(); + RealmList columnUUIDListList = ((some_test_AllTypesRealmProxyInterface) object).realmGet$columnUUIDList(); + if (columnUUIDListList != null) { + for (java.util.UUID columnUUIDListItem : columnUUIDListList) { + if (columnUUIDListItem == null) { + columnUUIDListOsList.addNull(); + } else { + columnUUIDListOsList.addUUID(columnUUIDListItem); + } + } + } + } } @@ -2671,6 +2846,7 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje unmanagedCopy.realmSet$columnBoolean(realmSource.realmGet$columnBoolean()); unmanagedCopy.realmSet$columnDecimal128(realmSource.realmGet$columnDecimal128()); unmanagedCopy.realmSet$columnObjectId(realmSource.realmGet$columnObjectId()); + unmanagedCopy.realmSet$columnUUID(realmSource.realmGet$columnUUID()); unmanagedCopy.realmSet$columnDate(realmSource.realmGet$columnDate()); unmanagedCopy.realmSet$columnBinary(realmSource.realmGet$columnBinary()); unmanagedCopy.realmGet$columnMutableRealmInteger().set(realmSource.realmGet$columnMutableRealmInteger().get()); @@ -2744,6 +2920,9 @@ public static some.test.AllTypes createDetachedCopy(some.test.AllTypes realmObje unmanagedCopy.realmSet$columnObjectIdList(new RealmList()); unmanagedCopy.realmGet$columnObjectIdList().addAll(realmSource.realmGet$columnObjectIdList()); + unmanagedCopy.realmSet$columnUUIDList(new RealmList()); + unmanagedCopy.realmGet$columnUUIDList().addAll(realmSource.realmGet$columnUUIDList()); + return unmanagedObject; } @@ -2759,6 +2938,7 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som builder.addBoolean(columnInfo.columnBooleanColKey, realmObjectSource.realmGet$columnBoolean()); builder.addDecimal128(columnInfo.columnDecimal128ColKey, realmObjectSource.realmGet$columnDecimal128()); builder.addObjectId(columnInfo.columnObjectIdColKey, realmObjectSource.realmGet$columnObjectId()); + builder.addUUID(columnInfo.columnUUIDColKey, realmObjectSource.realmGet$columnUUID()); builder.addDate(columnInfo.columnDateColKey, realmObjectSource.realmGet$columnDate()); builder.addByteArray(columnInfo.columnBinaryColKey, realmObjectSource.realmGet$columnBinary()); builder.addMutableRealmInteger(columnInfo.columnMutableRealmIntegerColKey, realmObjectSource.realmGet$columnMutableRealmInteger()); @@ -2820,6 +3000,7 @@ static some.test.AllTypes update(Realm realm, AllTypesColumnInfo columnInfo, som builder.addDateList(columnInfo.columnDateListColKey, realmObjectSource.realmGet$columnDateList()); builder.addDecimal128List(columnInfo.columnDecimal128ListColKey, realmObjectSource.realmGet$columnDecimal128List()); builder.addObjectIdList(columnInfo.columnObjectIdListColKey, realmObjectSource.realmGet$columnObjectIdList()); + builder.addUUIDList(columnInfo.columnUUIDListColKey, realmObjectSource.realmGet$columnUUIDList()); builder.updateExistingTopLevelObject(); return realmObject; @@ -2860,6 +3041,10 @@ public String toString() { stringBuilder.append(realmGet$columnObjectId()); stringBuilder.append("}"); stringBuilder.append(","); + stringBuilder.append("{columnUUID:"); + stringBuilder.append(realmGet$columnUUID()); + stringBuilder.append("}"); + stringBuilder.append(","); stringBuilder.append("{columnDate:"); stringBuilder.append(realmGet$columnDate()); stringBuilder.append("}"); @@ -2931,6 +3116,10 @@ public String toString() { stringBuilder.append("{columnObjectIdList:"); stringBuilder.append("RealmList[").append(realmGet$columnObjectIdList().size()).append("]"); stringBuilder.append("}"); + stringBuilder.append(","); + stringBuilder.append("{columnUUIDList:"); + stringBuilder.append("RealmList[").append(realmGet$columnUUIDList().size()).append("]"); + stringBuilder.append("}"); stringBuilder.append("]"); return stringBuilder.toString(); } diff --git a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java index baf86691a1..0d44d70f47 100644 --- a/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java +++ b/realm/realm-annotations-processor/src/test/resources/some/test/AllTypes.java @@ -20,6 +20,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import io.realm.MutableRealmInteger; import io.realm.RealmList; @@ -45,6 +46,8 @@ public class AllTypes extends RealmObject { private Decimal128 columnDecimal128; @Required private ObjectId columnObjectId; + @Required + private UUID columnUUID; @Required private Date columnDate; @@ -71,6 +74,7 @@ public class AllTypes extends RealmObject { private RealmList columnDateList; private RealmList columnDecimal128List; private RealmList columnObjectIdList; + private RealmList columnUUIDList; @LinkingObjects(FIELD_PARENTS) private final RealmResults parentObjects = null; diff --git a/realm/realm-library/src/androidTest/assets/nulltypes_invalid.json b/realm/realm-library/src/androidTest/assets/nulltypes_invalid.json index 2e34da768e..ff2626f8d1 100644 --- a/realm/realm-library/src/androidTest/assets/nulltypes_invalid.json +++ b/realm/realm-library/src/androidTest/assets/nulltypes_invalid.json @@ -46,5 +46,9 @@ { "id": 12, "fieldObjectIdNotNull": null + }, + { + "id": 13, + "fieldUUIDNotNull": null } ] diff --git a/realm/realm-library/src/androidTest/assets/uuid_as_string.json b/realm/realm-library/src/androidTest/assets/uuid_as_string.json new file mode 100644 index 0000000000..79b1793e68 --- /dev/null +++ b/realm/realm-library/src/androidTest/assets/uuid_as_string.json @@ -0,0 +1,3 @@ +{ + "columnUUID" : "027ba5ca-aa12-4afa-9219-e20cc3018599" +} \ No newline at end of file diff --git a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java index d44764662b..a171b2aca9 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/BulkInsertTests.java @@ -16,8 +16,6 @@ package io.realm; -import androidx.test.ext.junit.runners.AndroidJUnit4; - import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.junit.After; @@ -32,7 +30,9 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.UUID; +import androidx.test.ext.junit.runners.AndroidJUnit4; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; @@ -134,8 +134,10 @@ public void insert() { assertNull(realmTypes.getFieldList().get(0).getFieldIgnored()); assertNull(realmTypes.getFieldDecimal128()); assertNull(realmTypes.getFieldObjectId()); + assertNull(realmTypes.getFieldUUID()); assertEquals(0, realmTypes.getFieldDecimal128List().size()); assertEquals(0, realmTypes.getFieldObjectIdList().size()); + assertEquals(0, realmTypes.getFieldUUIDList().size()); // Makes sure Dog was not inserted twice in the recursive process. @@ -155,6 +157,7 @@ public void insert_realmModel() { allTypes.columnByte = 0x2A; allTypes.columnDecimal128 = new Decimal128(BigDecimal.TEN); allTypes.columnObjectId = new ObjectId(TestHelper.generateObjectIdHexString(7)); + allTypes.columnUUID = UUID.fromString(TestHelper.generateUUIDString(7)); realm.beginTransaction(); realm.insert(allTypes); @@ -172,6 +175,7 @@ public void insert_realmModel() { assertEquals(allTypes.columnByte, first.columnByte); assertEquals(allTypes.columnDecimal128, first.columnDecimal128); assertEquals(allTypes.columnObjectId, first.columnObjectId); + assertEquals(allTypes.columnUUID, first.columnUUID); } @Test @@ -200,6 +204,7 @@ public void insertOrUpdate_nullTypes() { nullTypes1.setFieldDateNull(new Date(12345)); nullTypes1.setFieldDecimal128Null(new Decimal128(BigDecimal.TEN)); nullTypes1.setFieldObjectIdNull(new ObjectId(TestHelper.generateObjectIdHexString(7))); + nullTypes1.setFieldUUIDNull(UUID.fromString(TestHelper.generateUUIDString(7))); realm.beginTransaction(); realm.insert(nullTypes1); @@ -217,6 +222,7 @@ public void insertOrUpdate_nullTypes() { assertEquals(nullTypes1.getFieldDateNull(), first.getFieldDateNull()); assertEquals(nullTypes1.getFieldDecimal128Null(), first.getFieldDecimal128Null()); assertEquals(nullTypes1.getFieldObjectIdNull(), first.getFieldObjectIdNull()); + assertEquals(nullTypes1.getFieldUUIDNull(), first.getFieldUUIDNull()); NullTypes nullTypes2 = new NullTypes(); nullTypes2.setId(2); @@ -234,6 +240,7 @@ public void insertOrUpdate_nullTypes() { nullTypes1.setFieldDateNull(null); nullTypes1.setFieldDecimal128Null(null); nullTypes1.setFieldObjectIdNull(null); + nullTypes1.setFieldUUIDNull(null); nullTypes1.setFieldListNull(new RealmList()); nullTypes1.getFieldListNull().add(nullTypes2); nullTypes1.getFieldListNull().add(nullTypes3); @@ -259,6 +266,7 @@ public void insertOrUpdate_nullTypes() { assertNull(first.getFieldDateNull()); assertNull(first.getFieldDecimal128Null()); assertNull(first.getFieldObjectIdNull()); + assertNull(first.getFieldUUIDNull()); assertEquals(2, first.getFieldListNull().size()); assertEquals(2, first.getFieldListNull().get(0).getId()); assertEquals(3, first.getFieldListNull().get(1).getId()); diff --git a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java index 11e68345dc..dc348bb49e 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/DynamicRealmObjectTests.java @@ -16,8 +16,6 @@ package io.realm; -import androidx.test.ext.junit.runners.AndroidJUnit4; - import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.hamcrest.Matchers; @@ -38,6 +36,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; +import androidx.test.ext.junit.runners.AndroidJUnit4; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.CyclicType; @@ -100,6 +99,7 @@ public void setUp() { typedObj.setFieldDate(new Date(1000)); typedObj.setFieldDecimal128(new Decimal128(BigDecimal.TEN)); typedObj.setFieldObjectId(new ObjectId(TestHelper.generateObjectIdHexString(7))); + typedObj.setFieldUUID(UUID.randomUUID()); typedObj.setFieldObject(typedObj); typedObj.getFieldList().add(typedObj); typedObj.getFieldIntegerList().add(1); @@ -128,15 +128,16 @@ public void tearDown() { // Types supported by the DynamicRealmObject. private enum SupportedType { - BOOLEAN, SHORT, INT, LONG, BYTE, FLOAT, DOUBLE, STRING, BINARY, DATE, OBJECT, DECIMAL128, OBJECT_ID, LIST, - LIST_INTEGER, LIST_STRING, LIST_BOOLEAN, LIST_FLOAT, LIST_DOUBLE, LIST_BINARY, LIST_DATE, LIST_DECIMAL128, LIST_OBJECT_ID + BOOLEAN, SHORT, INT, LONG, BYTE, FLOAT, DOUBLE, STRING, BINARY, DATE, OBJECT, DECIMAL128, OBJECT_ID, UUID, LIST, + LIST_INTEGER, LIST_STRING, LIST_BOOLEAN, LIST_FLOAT, LIST_DOUBLE, LIST_BINARY, LIST_DATE, LIST_DECIMAL128, + LIST_OBJECT_ID, LIST_UUID } private enum ThreadConfinedMethods { - GET_BOOLEAN, GET_BYTE, GET_SHORT, GET_INT, GET_LONG, GET_FLOAT, GET_DOUBLE, + GET_BOOLEAN, GET_BYTE, GET_SHORT, GET_INT, GET_LONG, GET_FLOAT, GET_DOUBLE, GET_UUID, GET_BLOB, GET_STRING, GET_DATE, GET_DECIMAL128, GET_OBJECT_ID, GET_OBJECT, GET_LIST, GET_PRIMITIVE_LIST, GET, - SET_BOOLEAN, SET_BYTE, SET_SHORT, SET_INT, SET_LONG, SET_FLOAT, SET_DOUBLE, + SET_BOOLEAN, SET_BYTE, SET_SHORT, SET_INT, SET_LONG, SET_FLOAT, SET_DOUBLE, SET_UUID, SET_BLOB, SET_STRING, SET_DATE, SET_DECIMAL128, SET_OBJECT_ID, SET_OBJECT, SET_LIST, SET_PRIMITIVE_LIST, SET, IS_NULL, SET_NULL, @@ -161,6 +162,7 @@ private static void callThreadConfinedMethod(DynamicRealmObject obj, ThreadConfi case GET_DATE: obj.getDate(AllJavaTypes.FIELD_DATE); break; case GET_DECIMAL128: obj.getDate(AllJavaTypes.FIELD_DECIMAL128); break; case GET_OBJECT_ID: obj.getDate(AllJavaTypes.FIELD_OBJECT_ID); break; + case GET_UUID: obj.getDate(AllJavaTypes.FIELD_UUID); break; case GET_OBJECT: obj.getObject(AllJavaTypes.FIELD_OBJECT); break; case GET_LIST: obj.getList(AllJavaTypes.FIELD_LIST); break; case GET_PRIMITIVE_LIST: obj.getList(AllJavaTypes.FIELD_STRING_LIST, String.class); break; @@ -177,7 +179,8 @@ private static void callThreadConfinedMethod(DynamicRealmObject obj, ThreadConfi case SET_STRING: obj.setString(AllJavaTypes.FIELD_STRING, "12345"); break; case SET_DATE: obj.setDate(AllJavaTypes.FIELD_DATE, new Date(1L)); break; case SET_DECIMAL128: obj.setDecimal128(AllJavaTypes.FIELD_DECIMAL128, new Decimal128(BigDecimal.ONE)); break; - case SET_OBJECT_ID: obj.setObjectId(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(5))); break; + case SET_OBJECT_ID: obj.setObjectId(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(5))); break; + case SET_UUID: obj.setUUID(AllJavaTypes.FIELD_UUID, UUID.randomUUID()); break; case SET_OBJECT: obj.setObject(AllJavaTypes.FIELD_OBJECT, obj); break; case SET_LIST: obj.setList(AllJavaTypes.FIELD_LIST, new RealmList<>(obj)); break; case SET_PRIMITIVE_LIST: obj.setList(AllJavaTypes.FIELD_STRING_LIST,new RealmList("foo")); break; @@ -348,6 +351,7 @@ private static void callGetter(DynamicRealmObject target, SupportedType type, Li case DATE: target.getDate(fieldName); break; case DECIMAL128: target.getDecimal128(fieldName); break; case OBJECT_ID: target.getObjectId(fieldName); break; + case UUID: target.getUUID(fieldName); break; case OBJECT: target.getObject(fieldName); break; case LIST: case LIST_INTEGER: @@ -359,6 +363,7 @@ private static void callGetter(DynamicRealmObject target, SupportedType type, Li case LIST_DATE: case LIST_DECIMAL128: case LIST_OBJECT_ID: + case LIST_UUID: target.getList(fieldName); break; default: @@ -484,6 +489,7 @@ private static void callSetter(DynamicRealmObject target, SupportedType type, Li case DATE: target.getDate(fieldName); break; case DECIMAL128: target.getDecimal128(fieldName); break; case OBJECT_ID: target.getObjectId(fieldName); break; + case UUID: target.getUUID(fieldName); break; case OBJECT: target.setObject(fieldName, null); target.setObject(fieldName, target); break; case LIST: target.setList(fieldName, new RealmList()); break; case LIST_INTEGER: target.setList(fieldName, new RealmList(1)); break; @@ -495,6 +501,7 @@ private static void callSetter(DynamicRealmObject target, SupportedType type, Li case LIST_DATE: target.setList(fieldName, new RealmList(new Date())); break; case LIST_DECIMAL128: target.setList(fieldName, new RealmList<>(new Decimal128(BigDecimal.ONE))); break; case LIST_OBJECT_ID: target.setList(fieldName, new RealmList<>(new ObjectId(TestHelper.generateObjectIdHexString(7)))); break; + case LIST_UUID: target.setList(fieldName, new RealmList<>(UUID.randomUUID())); break; default: fail(); } @@ -558,6 +565,11 @@ public void typedGettersAndSetters() { dObj.setObjectId(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(0))); assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(0)), dObj.getObjectId(AllJavaTypes.FIELD_OBJECT_ID)); break; + case UUID: + String uuid = UUID.randomUUID().toString(); + dObj.setUUID(AllJavaTypes.FIELD_UUID, UUID.fromString(uuid)); + assertEquals(UUID.fromString(uuid), dObj.getUUID(AllJavaTypes.FIELD_UUID)); + break; case OBJECT: dObj.setObject(AllJavaTypes.FIELD_OBJECT, dObj); assertEquals(dObj, dObj.getObject(AllJavaTypes.FIELD_OBJECT)); @@ -589,6 +601,9 @@ public void typedGettersAndSetters() { case LIST_OBJECT_ID: checkSetGetValueList(dObj, AllJavaTypes.FIELD_OBJECT_ID_LIST, ObjectId.class, new RealmList<>(null, new ObjectId(TestHelper.generateObjectIdHexString(0)))); break; + case LIST_UUID: + checkSetGetValueList(dObj, AllJavaTypes.FIELD_UUID_LIST, UUID.class, new RealmList<>(null, UUID.randomUUID())); + break; case LIST: // Ignores. See testGetList/testSetList. break; @@ -693,6 +708,13 @@ public void setter_null() { } catch (IllegalArgumentException ignored) { } break; + case LIST_UUID: + try { + dObj.setNull(NullTypes.FIELD_UUID_LIST_NULL); + fail(); + } catch (IllegalArgumentException ignored) { + } + break; case BOOLEAN: dObj.setNull(NullTypes.FIELD_BOOLEAN_NULL); assertTrue(dObj.isNull(NullTypes.FIELD_BOOLEAN_NULL)); @@ -741,6 +763,10 @@ public void setter_null() { dObj.setNull(NullTypes.FIELD_OBJECT_ID_NULL); assertTrue(dObj.isNull(NullTypes.FIELD_OBJECT_ID_NULL)); break; + case UUID: + dObj.setNull(NullTypes.FIELD_UUID_NULL); + assertTrue(dObj.isNull(NullTypes.FIELD_UUID_NULL)); + break; default: fail("Unknown type: " + type); } @@ -771,6 +797,7 @@ public void setter_nullOnRequiredFieldsThrows() { case LIST_DATE: fieldName = NullTypes.FIELD_DATE_LIST_NULL; break; case LIST_DECIMAL128: fieldName = NullTypes.FIELD_DECIMAL128_LIST_NULL; break; case LIST_OBJECT_ID: fieldName = NullTypes.FIELD_OBJECT_ID_LIST_NULL; break; + case LIST_UUID: fieldName = NullTypes.FIELD_UUID_LIST_NULL; break; case BOOLEAN: fieldName = NullTypes.FIELD_BOOLEAN_NOT_NULL; break; case BYTE: fieldName = NullTypes.FIELD_BYTE_NOT_NULL; break; case SHORT: fieldName = NullTypes.FIELD_SHORT_NOT_NULL; break; @@ -783,6 +810,7 @@ public void setter_nullOnRequiredFieldsThrows() { case DATE: fieldName = NullTypes.FIELD_DATE_NOT_NULL; break; case DECIMAL128: fieldName = NullTypes.FIELD_DECIMAL128_NOT_NULL; break; case OBJECT_ID: fieldName = NullTypes.FIELD_OBJECT_ID_NOT_NULL; break; + case UUID: fieldName = NullTypes.FIELD_UUID_NOT_NULL; break; default: fail("Unknown type: " + type); } @@ -1224,6 +1252,11 @@ public void untypedGetterSetter() { dObj.set(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(7))); assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(7)), dObj.get(AllJavaTypes.FIELD_OBJECT_ID)); break; + case UUID: + String uuid = UUID.randomUUID().toString(); + dObj.set(AllJavaTypes.FIELD_UUID, UUID.fromString(uuid)); + assertEquals(UUID.fromString(uuid), dObj.get(AllJavaTypes.FIELD_UUID)); + break; case OBJECT: dObj.set(AllJavaTypes.FIELD_OBJECT, dObj); assertEquals(dObj, dObj.get(AllJavaTypes.FIELD_OBJECT)); @@ -1309,6 +1342,14 @@ public void untypedGetterSetter() { assertArrayEquals(newList.toArray(), list.toArray()); break; } + case LIST_UUID: { + RealmList newList = new RealmList<>(null, UUID.randomUUID()); + dObj.set(AllJavaTypes.FIELD_UUID_LIST, newList); + RealmList list = dObj.getList(AllJavaTypes.FIELD_UUID_LIST, UUID.class); + assertEquals(2, list.size()); + assertArrayEquals(newList.toArray(), list.toArray()); + break; + } default: fail(); } @@ -1362,6 +1403,11 @@ public void untypedSetter_usingStringConversion() { dObj.set(AllJavaTypes.FIELD_OBJECT_ID, TestHelper.generateObjectIdHexString(7)); assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(7)), dObj.get(AllJavaTypes.FIELD_OBJECT_ID)); break; + case UUID: + String uuid = UUID.randomUUID().toString(); + dObj.set(AllJavaTypes.FIELD_UUID, UUID.fromString(uuid)); + assertEquals(UUID.fromString(uuid), dObj.get(AllJavaTypes.FIELD_UUID)); + break; // These types don't have a string representation that can be parsed. case OBJECT: case LIST: @@ -1374,6 +1420,7 @@ public void untypedSetter_usingStringConversion() { case LIST_DATE: case LIST_DECIMAL128: case LIST_OBJECT_ID: + case LIST_UUID: case STRING: case BINARY: case BYTE: @@ -1421,6 +1468,9 @@ public void untypedSetter_illegalImplicitConversionThrows() { case OBJECT_ID: dObj.set(AllJavaTypes.FIELD_OBJECT_ID, "foo"); break; + case UUID: + dObj.set(AllJavaTypes.FIELD_UUID, "foo"); + break; // These types don't have a string representation that can be parsed. case BOOLEAN: // Boolean is special as it returns false for all strings != "true" case BYTE: @@ -1435,6 +1485,7 @@ public void untypedSetter_illegalImplicitConversionThrows() { case LIST_DATE: case LIST_DECIMAL128: case LIST_OBJECT_ID: + case LIST_UUID: case STRING: case BINARY: continue; @@ -1513,11 +1564,12 @@ public void getFieldNames() { String[] expectedKeys = {AllJavaTypes.FIELD_STRING, AllJavaTypes.FIELD_ID, AllJavaTypes.FIELD_LONG, AllJavaTypes.FIELD_SHORT, AllJavaTypes.FIELD_INT, AllJavaTypes.FIELD_BYTE, AllJavaTypes.FIELD_FLOAT, AllJavaTypes.FIELD_DOUBLE, AllJavaTypes.FIELD_BOOLEAN, AllJavaTypes.FIELD_DATE, - AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_DECIMAL128, AllJavaTypes.FIELD_OBJECT_ID, AllJavaTypes.FIELD_OBJECT, AllJavaTypes.FIELD_LIST, + AllJavaTypes.FIELD_BINARY, AllJavaTypes.FIELD_DECIMAL128, AllJavaTypes.FIELD_OBJECT_ID, AllJavaTypes.FIELD_UUID, + AllJavaTypes.FIELD_OBJECT, AllJavaTypes.FIELD_LIST, AllJavaTypes.FIELD_STRING_LIST, AllJavaTypes.FIELD_BINARY_LIST, AllJavaTypes.FIELD_BOOLEAN_LIST, AllJavaTypes.FIELD_LONG_LIST, AllJavaTypes.FIELD_INTEGER_LIST, AllJavaTypes.FIELD_SHORT_LIST, AllJavaTypes.FIELD_BYTE_LIST, AllJavaTypes.FIELD_DOUBLE_LIST, AllJavaTypes.FIELD_FLOAT_LIST, - AllJavaTypes.FIELD_DATE_LIST, AllJavaTypes.FIELD_DECIMAL128_LIST, AllJavaTypes.FIELD_OBJECT_ID_LIST}; + AllJavaTypes.FIELD_DATE_LIST, AllJavaTypes.FIELD_DECIMAL128_LIST, AllJavaTypes.FIELD_OBJECT_ID_LIST, AllJavaTypes.FIELD_UUID_LIST}; String[] keys = dObjTyped.getFieldNames(); // After the stable ID support, primary key field will be inserted first before others. So even FIELD_STRING is // the first defined field in the class, it will be inserted after FIELD_ID. @@ -1546,6 +1598,9 @@ public void getFieldType() { assertEquals(RealmFieldType.BINARY, dObjTyped.getFieldType(AllJavaTypes.FIELD_BINARY)); assertEquals(RealmFieldType.BOOLEAN, dObjTyped.getFieldType(AllJavaTypes.FIELD_BOOLEAN)); assertEquals(RealmFieldType.DATE, dObjTyped.getFieldType(AllJavaTypes.FIELD_DATE)); + assertEquals(RealmFieldType.OBJECT_ID, dObjTyped.getFieldType(AllJavaTypes.FIELD_OBJECT_ID)); + assertEquals(RealmFieldType.DECIMAL128, dObjTyped.getFieldType(AllJavaTypes.FIELD_DECIMAL128)); + assertEquals(RealmFieldType.UUID, dObjTyped.getFieldType(AllJavaTypes.FIELD_UUID)); assertEquals(RealmFieldType.DOUBLE, dObjTyped.getFieldType(AllJavaTypes.FIELD_DOUBLE)); assertEquals(RealmFieldType.FLOAT, dObjTyped.getFieldType(AllJavaTypes.FIELD_FLOAT)); assertEquals(RealmFieldType.OBJECT, dObjTyped.getFieldType(AllJavaTypes.FIELD_OBJECT)); @@ -1561,6 +1616,9 @@ public void getFieldType() { assertEquals(RealmFieldType.DOUBLE_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_DOUBLE_LIST)); assertEquals(RealmFieldType.BINARY_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_BINARY_LIST)); assertEquals(RealmFieldType.DATE_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_DATE_LIST)); + assertEquals(RealmFieldType.OBJECT_ID_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_OBJECT_ID_LIST)); + assertEquals(RealmFieldType.DECIMAL128_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_DECIMAL128_LIST)); + assertEquals(RealmFieldType.UUID_LIST, dObjTyped.getFieldType(AllJavaTypes.FIELD_UUID_LIST)); } @Test @@ -1612,6 +1670,9 @@ public void toString_nullValues() { assertTrue(str.contains(NullTypes.FIELD_FLOAT_NULL + ":null")); assertTrue(str.contains(NullTypes.FIELD_DOUBLE_NULL + ":null")); assertTrue(str.contains(NullTypes.FIELD_DATE_NULL + ":null")); + assertTrue(str.contains(NullTypes.FIELD_OBJECT_ID_NULL + ":null")); + assertTrue(str.contains(NullTypes.FIELD_DECIMAL128_NULL + ":null")); + assertTrue(str.contains(NullTypes.FIELD_UUID_NULL + ":null")); assertTrue(str.contains(NullTypes.FIELD_OBJECT_NULL + ":null")); assertTrue(str.contains(NullTypes.FIELD_LIST_NULL + ":RealmList[0]")); assertTrue(str.contains(NullTypes.FIELD_INTEGER_LIST_NULL + ":RealmList[0]")); @@ -1621,6 +1682,9 @@ public void toString_nullValues() { assertTrue(str.contains(NullTypes.FIELD_DOUBLE_LIST_NULL + ":RealmList[0]")); assertTrue(str.contains(NullTypes.FIELD_BINARY_LIST_NULL + ":RealmList[0]")); assertTrue(str.contains(NullTypes.FIELD_DATE_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_OBJECT_ID_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_DECIMAL128_LIST_NULL + ":RealmList[0]")); + assertTrue(str.contains(NullTypes.FIELD_UUID_LIST_NULL + ":RealmList[0]")); } @Test @@ -1743,6 +1807,9 @@ public void getNullableFields() { assertNull(primitiveNullables.getFieldString()); assertNull(primitiveNullables.getFieldBinary()); assertNull(primitiveNullables.getFieldDate()); + assertNull(primitiveNullables.getFieldObjectId()); + assertNull(primitiveNullables.getFieldDecimal128()); + assertNull(primitiveNullables.getFieldUUID()); realm.delete(AllJavaTypes.class); AllJavaTypes allJavaTypes = realm.createObject(AllJavaTypes.class, UUID.randomUUID().getLeastSignificantBits()); @@ -1757,6 +1824,7 @@ public void getNullableFields() { allJavaTypes.getFieldDateList().add(null); allJavaTypes.getFieldObjectIdList().add(null); allJavaTypes.getFieldDecimal128List().add(null); + allJavaTypes.getFieldUUIDList().add(null); }); realm.close(); @@ -1797,6 +1865,9 @@ public void getNullableFields() { case OBJECT_ID: assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_OBJECT_ID)); break; + case UUID: + assertNull(primitiveNullables.get(NullablePrimitiveFields.FIELD_UUID)); + break; case INTEGER_LIST: assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_INTEGER_LIST, Integer.class).get(0)); break; @@ -1824,6 +1895,9 @@ public void getNullableFields() { case OBJECT_ID_LIST: assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_OBJECT_ID_LIST, ObjectId.class).get(0)); break; + case UUID_LIST: + assertNull(allJavaTypes.getList(AllJavaTypes.FIELD_UUID_LIST, UUID.class).get(0)); + break; case LIST: case LINKING_OBJECTS: // Realm lists and back links cannot be null diff --git a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java index a622566d2a..1d82975b1f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/LinkingObjectsDynamicTests.java @@ -16,8 +16,6 @@ package io.realm; -import androidx.test.ext.junit.runners.AndroidJUnit4; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -27,6 +25,7 @@ import java.util.Locale; +import androidx.test.ext.junit.runners.AndroidJUnit4; import io.realm.entities.AllJavaTypes; import io.realm.entities.BacklinksSource; import io.realm.entities.BacklinksTarget; @@ -208,6 +207,9 @@ public void linkingObjects_invalidFieldType() { case OBJECT_ID: object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_OBJECT_ID); break; + case UUID: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_UUID); + break; case INTEGER_LIST: object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_INTEGER_LIST); break; @@ -235,6 +237,9 @@ public void linkingObjects_invalidFieldType() { case OBJECT_ID_LIST: object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_OBJECT_ID_LIST); break; + case UUID_LIST: + object.linkingObjects(AllJavaTypes.CLASS_NAME, AllJavaTypes.FIELD_UUID_LIST); + break; default: fail("unknown type: " + fieldType); break; diff --git a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java index 56b6c6970d..54e7416103 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/ManagedOrderedRealmCollectionTests.java @@ -18,6 +18,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -113,6 +114,7 @@ */ @RunWith(Parameterized.class) +@Ignore("Tests crash due to bug in core, see https://jira.mongodb.org/browse/RCORE-435") public class ManagedOrderedRealmCollectionTests extends CollectionTests { private static final int TEST_SIZE = 10; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java index aae0c1c0b4..490f2db65f 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonAbsentPrimaryKeyTests.java @@ -38,6 +38,7 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsObjectId; import io.realm.entities.PrimaryKeyAsString; +import io.realm.entities.PrimaryKeyAsUUID; import io.realm.rule.TestRealmConfigurationFactory; import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; @@ -69,12 +70,13 @@ public void tearDown() { @Parameterized.Parameters public static Iterable data() { return Arrays.asList(new Object[][]{ - {PrimaryKeyAsBoxedByte.class, "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, - {PrimaryKeyAsBoxedShort.class, "{ \"name\":\"KeyValueTestIsFun\" }"}, - {PrimaryKeyAsBoxedInteger.class, "{ \"name\":\"FunValueTestIsKey\" }"}, - {PrimaryKeyAsBoxedLong.class, "{ \"name\":\"NameAsBoxedLong-!\" }"}, - {PrimaryKeyAsString.class, "{ \"id\":2429214 }"}, - {PrimaryKeyAsObjectId.class, "{ \"name\":\"789ABCDEF0123456789ABCDE\" }"} + {PrimaryKeyAsBoxedByte.class, "{ \"name\":\"HaHaHaHaHaHaHaHaH\" }"}, + {PrimaryKeyAsBoxedShort.class, "{ \"name\":\"KeyValueTestIsFun\" }"}, + {PrimaryKeyAsBoxedInteger.class, "{ \"name\":\"FunValueTestIsKey\" }"}, + {PrimaryKeyAsBoxedLong.class, "{ \"name\":\"NameAsBoxedLong-!\" }"}, + {PrimaryKeyAsString.class, "{ \"id\":2429214 }"}, + {PrimaryKeyAsObjectId.class, "{ \"name\":\"789ABCDEF0123456789ABCDE\" }"}, + {PrimaryKeyAsUUID.class, "{ \"name\":\"NameAsBoxedLong-!\" }"} }); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java index 9f8e7fb166..b1b7420a6c 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonNullPrimaryKeyTests.java @@ -33,6 +33,7 @@ import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsObjectId; import io.realm.entities.PrimaryKeyAsString; +import io.realm.entities.PrimaryKeyAsUUID; import io.realm.objectid.NullPrimaryKey; import io.realm.rule.TestRealmConfigurationFactory; @@ -62,12 +63,13 @@ public void tearDown() { // Parameters for testing null primary key value. PrimaryKey field is explicitly null. @Parameterized.Parameters public static Iterable data() { - return Arrays.asList(new Object[][]{ + return Arrays.asList(new Object[][] { {PrimaryKeyAsBoxedByte.class, "OhThisIsNullKey?!", "{ \"id\":null, \"name\":\"OhThisIsNullKey?!\" }"}, {PrimaryKeyAsBoxedShort.class, "YouBetItIsNullKey", "{ \"id\":null, \"name\":\"YouBetItIsNullKey\" }"}, {PrimaryKeyAsBoxedInteger.class, "Gosh Didnt KnowIt", "{ \"id\":null, \"name\":\"Gosh Didnt KnowIt\" }"}, {PrimaryKeyAsBoxedLong.class, "?YOUNOWKNOWRIGHT?", "{ \"id\":null, \"name\":\"?YOUNOWKNOWRIGHT?\" }"}, {PrimaryKeyAsString.class, "4299121", "{ \"name\":null, \"id\":4299121 }"}, + {PrimaryKeyAsUUID.class, "-!dlroWolleH", "{ \"id\":null, \"name\":\"-!dlroWolleH\" }"}, {PrimaryKeyAsObjectId.class, "Samsquanch", "{ \"id\":null, \"name\":\"Samsquanch\" }"}, }); } @@ -95,6 +97,15 @@ public void createObjectFromJson_primaryKey_isNull_fromJsonObject() throws JSONE assertEquals(1, results.size()); assertEquals(Long.valueOf(secondaryFieldValue).longValue(), results.first().getId()); assertNull(results.first().getName()); + + // PrimaryKeyAsObjectId + } else if (clazz.equals(PrimaryKeyAsUUID.class)) { + RealmResults results = realm.where(PrimaryKeyAsUUID.class).findAll(); + assertEquals(1, results.size()); + assertEquals(null, results.first().getId()); + assertEquals(secondaryFieldValue, results.first().getName()); + + // PrimaryKeyAsNumber } else { // Other types RealmResults results = realm.where(clazz).findAll(); @@ -117,6 +128,15 @@ public void createOrUpdateObjectFromJson_primaryKey_isNull_fromJsonObject() thro assertEquals(1, results.size()); assertEquals(Long.valueOf(secondaryFieldValue).longValue(), results.first().getId()); assertEquals(null, results.first().getName()); + + // PrimaryKeyAsObjectId + } else if (clazz.equals(PrimaryKeyAsUUID.class)) { + RealmResults results = realm.where(PrimaryKeyAsUUID.class).findAll(); + assertEquals(1, results.size()); + assertEquals(null, results.first().getId()); + assertEquals(secondaryFieldValue, results.first().getName()); + + // PrimaryKeyAsNumber } else { // Other types RealmResults results = realm.where(clazz).findAll(); @@ -140,6 +160,15 @@ public void createOrUpdateObjectFromJson_primaryKey_isNull_updateFromJsonObject( assertEquals(1, results.size()); assertEquals(Long.valueOf(secondaryFieldValue).longValue(), results.first().getId()); assertEquals(null, results.first().getName()); + + // PrimaryKeyAsObjectId + } else if (clazz.equals(PrimaryKeyAsUUID.class)) { + RealmResults results = realm.where(PrimaryKeyAsUUID.class).findAll(); + assertEquals(1, results.size()); + assertEquals(null, results.first().getId()); + assertEquals(secondaryFieldValue, results.first().getName()); + + // PrimaryKeyAsNumber } else { // Other types RealmResults results = realm.where(clazz).findAll(); diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java index 13d71fc6bc..57b3fd42d3 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmJsonTests.java @@ -20,9 +20,6 @@ import android.os.Build; import android.util.Base64; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.json.JSONArray; @@ -30,6 +27,7 @@ import org.json.JSONObject; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,9 +44,12 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; +import java.util.UUID; import javax.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.AnnotationTypes; @@ -75,6 +76,7 @@ import static org.junit.Assume.assumeThat; @RunWith(AndroidJUnit4.class) +@Ignore("Tests crash due to bug in core, see https://jira.mongodb.org/browse/RCORE-435") public class RealmJsonTests { private static final Charset UTF_8 = Charset.forName("UTF-8"); @@ -358,6 +360,35 @@ public void createObjectFromJson_objectIdAsString() throws JSONException { assertEquals(new ObjectId(idHex), obj.getColumnObjectId()); } + @Test + public void createObjectFromJson_uuid() throws JSONException { + JSONObject json = new JSONObject(); + String uuid = "027ba5ca-aa12-4afa-9219-e20cc3018599"; + + json.put("columnUUID", UUID.fromString(uuid)); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(UUID.fromString(uuid), obj.getColumnUUID()); + } + + @Test + public void createObjectFromJson_uuidAsString() throws JSONException { + JSONObject json = new JSONObject(); + String uuid = "027ba5ca-aa12-4afa-9219-e20cc3018599"; + json.put("columnUUID", uuid); + + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, json); + realm.commitTransaction(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(UUID.fromString(uuid), obj.getColumnUUID()); + } + @Test public void createObjectFromJson_dateAsStringTimeZone() throws JSONException { // Oct 03 2015 14:45.33 @@ -940,6 +971,20 @@ public void createObjectFromJson_streamObjectIdAsString() throws IOException { assertEquals(new ObjectId("789ABCDEF0123456789ABCDE"), obj.getColumnObjectId()); } + @Test + public void createObjectFromJson_streamUUIDAsString() throws IOException { + assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); + + InputStream in = TestHelper.loadJsonFromAssets(context, "uuid_as_string.json"); + realm.beginTransaction(); + realm.createObjectFromJson(AllTypes.class, in); + realm.commitTransaction(); + in.close(); + + AllTypes obj = realm.where(AllTypes.class).findFirst(); + assertEquals(UUID.fromString("027ba5ca-aa12-4afa-9219-e20cc3018599"), obj.getColumnUUID()); + } + @Test public void createObjectFromJson_streamDateAsString() throws IOException { assumeThat(Build.VERSION.SDK_INT, greaterThanOrEqualTo(Build.VERSION_CODES.HONEYCOMB)); @@ -1775,6 +1820,16 @@ public void createObjectFromJson_nullTypesJSONToNotNullFields() throws IOExcepti fail("Unexpected exception: " + e); } + // 13 UUID + try { + realm.createObjectFromJson(NullTypes.class, array.getJSONObject(12)); + fail(); + } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_UUID_NOT_NULL)); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } + realm.cancelTransaction(); } @@ -1909,6 +1964,16 @@ public void createObjectFromJson_nullTypesJSONStreamToNotNullFields() throws IOE } finally { realm.cancelTransaction(); } + // 13 UUID + try { + realm.beginTransaction(); + realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, convertJsonObjectToStream(array.getJSONObject(12))); + fail(); + } catch (IllegalArgumentException ignored) { + assertTrue(ignored.getMessage().contains(NullTypes.FIELD_UUID_NOT_NULL)); + } finally { + realm.cancelTransaction(); + } } /** diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 7e246a46f9..5bfef8270d 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -35,8 +35,11 @@ import java.io.IOException; import java.util.Date; import java.util.Locale; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import io.realm.entities.AllTypes; import io.realm.entities.AnnotationTypes; import io.realm.entities.CatOwner; @@ -53,6 +56,7 @@ import io.realm.entities.PrimaryKeyAsLong; import io.realm.entities.PrimaryKeyAsShort; import io.realm.entities.PrimaryKeyAsString; +import io.realm.entities.PrimaryKeyAsUUID; import io.realm.entities.StringOnly; import io.realm.entities.StringOnlyRequired; import io.realm.entities.Thread; @@ -1488,6 +1492,39 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { realm.close(); } + @Test + public void migrateRealm_addPrimaryKey_UUID() { + // Creates v0 of the Realm. + RealmConfiguration originalConfig = configFactory.createConfigurationBuilder() + .schema(StringOnly.class) + .build(); + Realm.getInstance(originalConfig).close(); + + RealmMigration migration = new RealmMigration() { + @Override + public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { + RealmSchema schema = realm.getSchema(); + schema.create(PrimaryKeyAsUUID.CLASS_NAME) + .addField(PrimaryKeyAsUUID.FIELD_PRIMARY_KEY, UUID.class) + .addPrimaryKey(PrimaryKeyAsUUID.FIELD_PRIMARY_KEY) + .addField(PrimaryKeyAsUUID.FIELD_NAME, String.class); + } + }; + + // Creates v1 of the Realm. + RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() + .schemaVersion(1) + .schema(StringOnly.class, PrimaryKeyAsUUID.class) + .migration(migration) + .build(); + + realm = Realm.getInstance(realmConfig); + RealmObjectSchema schema = realm.getSchema().get(PrimaryKeyAsUUID.CLASS_NAME); + assertTrue(schema.hasPrimaryKey()); + assertFalse(schema.hasIndex(PrimaryKeyAsUUID.FIELD_PRIMARY_KEY)); + realm.close(); + } + // TODO Add unit tests for default nullability // TODO Add unit tests for default Indexing for Primary keys } diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java index 75fd6cf9e1..7583f23fbc 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectSchemaTests.java @@ -16,6 +16,8 @@ package io.realm; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Before; @@ -29,6 +31,7 @@ import java.util.Date; import java.util.List; import java.util.Set; +import java.util.UUID; import io.realm.entities.AllJavaTypes; import io.realm.entities.CyclicType; @@ -121,6 +124,9 @@ public enum FieldType { DOUBLE(Double.class, true), PRIMITIVE_DOUBLE(double.class, false), BLOB(byte[].class, true), DATE(Date.class, true), + OBJECT_ID(ObjectId.class, true), + DECIMAL128(Decimal128.class, true), + UUID(UUID.class, true), OBJECT(RealmObject.class, false); final Class clazz; @@ -152,6 +158,9 @@ public enum FieldListType { DOUBLE_LIST(Double.class, true), PRIMITIVE_DOUBLE_LIST(double.class, false), BLOB_LIST(byte[].class, true), DATE_LIST(Date.class, true), + OBJECT_ID_LIST(ObjectId.class, true), + DECIMAL128_LIST(Decimal128.class, true), + UUID_LIST(UUID.class, true), LIST(RealmList.class, false); // List of Realm Objects final Class clazz; @@ -767,6 +776,12 @@ public void setRequired_nullValueBecomesDefaultValue() { assertEquals(0.0D, object.getDouble(fieldName), 0D); } else if (fieldType == FieldType.DATE) { assertEquals(new Date(0), object.getDate(fieldName)); + } else if (fieldType == FieldType.OBJECT_ID) { + assertEquals(new ObjectId("000000000000000000000000"), object.getObjectId(fieldName)); + } else if (fieldType == FieldType.DECIMAL128) { + assertEquals(Decimal128.parse("0"), object.getDecimal128(fieldName)); + } else if (fieldType == FieldType.UUID) { + assertEquals(UUID.fromString("00000000-0000-0000-0000-000000000000"), object.getUUID(fieldName)); } else { assertEquals(0, object.getInt(fieldName)); } @@ -808,6 +823,15 @@ public void setRequired_nullValueBecomesDefaultValue() { case DATE_LIST: checkListValueConversionToDefaultValue(Date.class, new Date(0)); break; + case OBJECT_ID_LIST: + checkListValueConversionToDefaultValue(ObjectId.class, new ObjectId("000000000000000000000000")); + break; + case DECIMAL128_LIST: + checkListValueConversionToDefaultValue(Decimal128.class, Decimal128.parse("0")); + break; + case UUID_LIST: + checkListValueConversionToDefaultValue(UUID.class, UUID.fromString("00000000-0000-0000-0000-000000000000")); + break; case PRIMITIVE_INT_LIST: case PRIMITIVE_LONG_LIST: case PRIMITIVE_BYTE_LIST: @@ -1299,6 +1323,9 @@ public void getFieldType() { assertEquals(RealmFieldType.INTEGER, schema.getFieldType(AllJavaTypes.FIELD_SHORT)); assertEquals(RealmFieldType.INTEGER, schema.getFieldType(AllJavaTypes.FIELD_INT)); assertEquals(RealmFieldType.INTEGER, schema.getFieldType(AllJavaTypes.FIELD_LONG)); + assertEquals(RealmFieldType.OBJECT_ID, schema.getFieldType(AllJavaTypes.FIELD_OBJECT_ID)); + assertEquals(RealmFieldType.DECIMAL128, schema.getFieldType(AllJavaTypes.FIELD_DECIMAL128)); + assertEquals(RealmFieldType.UUID, schema.getFieldType(AllJavaTypes.FIELD_UUID)); } @Test(expected = IllegalArgumentException.class) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java index 687046591e..30c134c49a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmObjectTests.java @@ -119,7 +119,7 @@ public void row_isValid() { realm.commitTransaction(); assertNotNull("RealmObject.realmGetRow returns zero ", row); - assertEquals(22, row.getColumnCount()); + assertEquals(24, row.getColumnCount()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 663815a62f..12055f99b6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -16,13 +16,9 @@ package io.realm; -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; - import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.jetbrains.annotations.NotNull; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.internal.util.collections.Sets; @@ -37,9 +33,12 @@ import java.util.Locale; import java.util.Objects; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AnnotationIndexTypes; @@ -62,13 +61,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) public class RealmQueryTests extends QueryTests { - private void populateTestRealm(Realm testRealm, int dataSize) { testRealm.beginTransaction(); testRealm.deleteAll(); @@ -83,6 +82,8 @@ private void populateTestRealm(Realm testRealm, int dataSize) { allTypes.setColumnLong(i); allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(i))); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(i + ".23456789"))); + allTypes.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(i))); + NonLatinFieldNames nonLatinFieldNames = testRealm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); @@ -576,7 +577,7 @@ public void and_implicit() { resultList = query.between(AllTypes.FIELD_LONG, 1, 100).findAll(); assertEquals(1, resultList.size()); } - + @Test public void and_explicit() { populateTestRealm(realm, 200); @@ -670,17 +671,96 @@ public void equalTo() { @Test public void equalTo_decimal128() { populateTestRealm(realm, 10); - RealmResults resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_DECIMAL128, new Decimal128(new BigDecimal( "7.23456789"))).findAll(); - assertEquals(1, resultList.size()); - assertEquals(new Decimal128(new BigDecimal( "7.23456789")), resultList.get(0).getColumnDecimal128()); + + for (int i = 0; i < 10; i++) { + RealmResults resultList = realm.where(AllTypes.class) + .equalTo(AllTypes.FIELD_DECIMAL128, new Decimal128(new BigDecimal(i + ".23456789"))) + .sort(AllTypes.FIELD_DECIMAL128, Sort.ASCENDING) + .findAll(); + + assertEquals(1, resultList.size()); + assertEquals(new Decimal128(new BigDecimal(i + ".23456789")), resultList.get(0).getColumnDecimal128()); + } } @Test public void equalTo_objectId() { populateTestRealm(realm, 10); - RealmResults resultList = realm.where(AllTypes.class).sort(AllTypes.FIELD_OBJECT_ID, Sort.ASCENDING).findAll(); + + for (int i = 0; i < 10; i++) { + RealmResults resultList = realm.where(AllTypes.class) + .equalTo(AllTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(i))) + .sort(AllTypes.FIELD_OBJECT_ID, Sort.ASCENDING) + .findAll(); + + assertEquals(1, resultList.size()); + assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(i)), resultList.get(0).getColumnObjectId()); + } + } + + @Test + public void equalTo_UUID() { + populateTestRealm(realm, 10); + for (int i = 0; i < 10; i++) { + RealmResults resultList = realm + .where(AllTypes.class) + .equalTo(AllTypes.FIELD_UUID, UUID.fromString(TestHelper.generateUUIDString(i))) + .sort(AllTypes.FIELD_UUID, Sort.ASCENDING) + .findAll(); + + assertEquals(1, resultList.size()); + assertEquals(UUID.fromString(TestHelper.generateUUIDString(i)), resultList.get(0).getColumnUUID()); + } + } + + @Test + public void notEqualTo_objectId() { + populateTestRealm(realm, 10); + + RealmResults resultList = realm + .where(AllTypes.class) + .notEqualTo(AllTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(0))) + .sort(AllTypes.FIELD_OBJECT_ID, Sort.ASCENDING) + .findAll(); + + assertEquals(9, resultList.size()); + + for (int i = 1; i < 10; i++) { + assertNotEquals(new ObjectId(TestHelper.generateObjectIdHexString(0)), resultList.get(0).getColumnObjectId()); + } + } + + @Test + public void notEqualTo_decimal128() { + populateTestRealm(realm, 10); + + RealmResults resultList = realm + .where(AllTypes.class) + .notEqualTo(AllTypes.FIELD_DECIMAL128, new Decimal128(new BigDecimal("0.23456789"))) + .sort(AllTypes.FIELD_UUID, Sort.ASCENDING) + .findAll(); + + assertEquals(9, resultList.size()); + + for (int i = 1; i < 10; i++) { + assertNotEquals(new Decimal128(new BigDecimal("0.23456789")), resultList.get(0).getColumnDecimal128()); + } + } + + @Test + public void notEqualTo_UUID() { + populateTestRealm(realm, 10); + + RealmResults resultList = realm + .where(AllTypes.class) + .notEqualTo(AllTypes.FIELD_UUID, UUID.fromString("007ba5ca-aa12-4afa-9219-e20cc3018599")) + .sort(AllTypes.FIELD_UUID, Sort.ASCENDING) + .findAll(); + + assertEquals(10, resultList.size()); + for (int i = 0; i < 10; i++) { - assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(i)), resultList.get(i).getColumnObjectId()); + assertNotEquals(UUID.fromString("007ba5ca-aa12-4afa-9219-e20cc3018599"), resultList.get(0).getColumnUUID()); } } @@ -2828,6 +2908,9 @@ public void isEmpty_illegalFieldTypeThrows() { case OBJECT_ID: realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT_ID).findAll(); break; + case UUID: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_UUID).findAll(); + break; case INTEGER_LIST: realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_INTEGER_LIST).findAll(); break; @@ -2855,6 +2938,9 @@ public void isEmpty_illegalFieldTypeThrows() { case OBJECT_ID_LIST: realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_OBJECT_ID_LIST).findAll(); break; + case UUID_LIST: + realm.where(AllJavaTypes.class).isEmpty(AllJavaTypes.FIELD_UUID_LIST).findAll(); + break; default: fail("Unknown type: " + type); } @@ -2974,6 +3060,9 @@ public void isNotEmpty_illegalFieldTypeThrows() { case OBJECT_ID: realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_OBJECT_ID).findAll(); break; + case UUID: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_UUID).findAll(); + break; case INTEGER_LIST: realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_INTEGER_LIST).findAll(); break; @@ -3001,6 +3090,9 @@ public void isNotEmpty_illegalFieldTypeThrows() { case OBJECT_ID_LIST: realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_OBJECT_ID_LIST).findAll(); break; + case UUID_LIST: + realm.where(AllJavaTypes.class).isNotEmpty(AllJavaTypes.FIELD_UUID_LIST).findAll(); + break; default: fail("Unknown type: " + type); } @@ -3148,6 +3240,7 @@ private void populateForDistinctAllTypes(Realm realm, long numberOfBlocks, long obj.setColumnDate(new Date(1000L * j)); obj.setColumnDecimal128(new Decimal128(j)); obj.setColumnObjectId(new ObjectId(j, j)); + obj.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(j))); obj.setColumnMutableRealmInteger(j); obj.setColumnRealmLink(obj); obj.setColumnRealmObject(dog); @@ -4013,6 +4106,7 @@ private boolean supportDistinct(RealmFieldType type) { case OBJECT: case DECIMAL128: case OBJECT_ID: + case UUID: case LINKING_OBJECTS: return true; case LIST: @@ -4025,6 +4119,7 @@ private boolean supportDistinct(RealmFieldType type) { case DOUBLE_LIST: case DECIMAL128_LIST: case OBJECT_ID_LIST: + case UUID_LIST: return false; } // Should never reach here as the above switch is exhaustive diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java index b892f38fe9..c8a3960821 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmResultsTests.java @@ -16,14 +16,12 @@ package io.realm; -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; - import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.json.JSONException; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -39,9 +37,12 @@ import java.util.Date; import java.util.List; import java.util.TimeZone; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.CyclicType; @@ -68,6 +69,7 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) +@Ignore("Tests crash due to bug in core, see https://jira.mongodb.org/browse/RCORE-435") public class RealmResultsTests extends CollectionTests { private final static int TEST_DATA_SIZE = 100; @@ -84,6 +86,9 @@ public class RealmResultsTests extends CollectionTests { private Realm realm; private RealmResults collection; + private static final String uuid1 = "017ba5ca-aa12-4afa-9219-e20cc3018599"; + private static final String uuid2 = "027ba5ca-aa12-4afa-9219-e20cc3018599"; + @Before public void setUp() { RealmConfiguration realmConfig = configFactory.createConfiguration(); @@ -741,6 +746,7 @@ private void populateMappedAllJavaTypes(int objects) { obj.fieldObject = obj; obj.fieldDecimal128 = new Decimal128( i); obj.fieldObjectId = new ObjectId(TestHelper.generateObjectIdHexString(i)); + obj.fieldUUID = UUID.fromString(TestHelper.generateUUIDString(i)); obj.fieldList.add(obj); } realm.commitTransaction(); @@ -761,6 +767,7 @@ private void populateAllJavaTypes(int objects) { obj.setFieldObject(obj); obj.setFieldDecimal128(new Decimal128(new BigDecimal(i + ".23456789"))); obj.setFieldObjectId(new ObjectId(TestHelper.generateObjectIdHexString(i))); + obj.setFieldUUID(UUID.fromString(TestHelper.generateUUIDString(i))); obj.getFieldList().add(obj); } realm.commitTransaction(); @@ -779,6 +786,7 @@ enum BulkSetMethods { DATE, DECIMAL128, OBJECT_ID, + UUID, OBJECT, MODEL_LIST, STRING_VALUE_LIST, @@ -792,7 +800,8 @@ enum BulkSetMethods { BINARY_VALUE_LIST, DATE_VALUE_LIST, DECIMAL128_VALUE_LIST, - OBJECT_ID_VALUE_LIST + OBJECT_ID_VALUE_LIST, + UUID_VALUE_LIST } interface ElementValidator { @@ -871,6 +880,13 @@ public void setValue() { collection.setValue(AllJavaTypes.FIELD_OBJECT_ID, null); assertElements(collection, obj -> assertNull(obj.getFieldObjectId())); break; + case UUID: + String uuid = UUID.randomUUID().toString(); + collection.setValue(AllJavaTypes.FIELD_UUID, UUID.fromString(uuid)); + assertElements(collection, obj -> assertEquals(UUID.fromString(uuid), obj.getFieldUUID())); + collection.setValue(AllJavaTypes.FIELD_UUID, null); + assertElements(collection, obj -> assertNull(obj.getFieldUUID())); + break; case OBJECT: { AllJavaTypes childObj = realm.createObject(AllJavaTypes.class, 42); collection.setValue(AllJavaTypes.FIELD_OBJECT, childObj); @@ -998,6 +1014,17 @@ public void setValue() { }); break; } + case UUID_VALUE_LIST: { + String uuid1 = UUID.randomUUID().toString(); + String uuid2 = UUID.randomUUID().toString(); + RealmList list = new RealmList<>(UUID.fromString(uuid1), UUID.fromString(uuid2)); + collection.setValue(AllJavaTypes.FIELD_UUID_LIST, list); + assertElements(collection, obj -> { + assertEquals(UUID.fromString(uuid1), obj.getFieldUUIDList().first()); + assertEquals(UUID.fromString(uuid2), obj.getFieldUUIDList().last()); + }); + break; + } default: fail("Unknown type: " + type); } @@ -1062,6 +1089,11 @@ public void setValue_implicitConversions() { collection.setValue(AllJavaTypes.FIELD_OBJECT_ID, new ObjectId(hex)); assertElements(collection, obj -> assertEquals(new ObjectId(hex), obj.getFieldObjectId())); break; + case UUID: + String uuid = UUID.randomUUID().toString(); + collection.setValue(AllJavaTypes.FIELD_UUID, UUID.fromString(uuid)); + assertElements(collection, obj -> assertEquals(UUID.fromString(uuid), obj.getFieldUUID())); + break; // These types do not offer any implicit conversion case STRING: @@ -1080,6 +1112,7 @@ public void setValue_implicitConversions() { case DATE_VALUE_LIST: case DECIMAL128_VALUE_LIST: case OBJECT_ID_VALUE_LIST: + case UUID_VALUE_LIST: continue; default: @@ -1176,6 +1209,12 @@ public void setValue_specificType() { collection.setObjectId(AllJavaTypes.FIELD_OBJECT_ID, null); assertElements(collection, obj -> assertNull(obj.getFieldObjectId())); break; + case UUID: + collection.setUUID(AllJavaTypes.FIELD_UUID, UUID.fromString(uuid1)); + assertElements(collection, obj -> assertEquals(UUID.fromString(uuid1), obj.getFieldUUID())); + collection.setUUID(AllJavaTypes.FIELD_UUID, null); + assertElements(collection, obj -> assertNull(obj.getFieldUUID())); + break; case OBJECT: { AllJavaTypes childObj = realm.createObject(AllJavaTypes.class, 42); collection.setObject(AllJavaTypes.FIELD_OBJECT, childObj); @@ -1303,6 +1342,17 @@ public void setValue_specificType() { }); break; } + case UUID_VALUE_LIST: { + String uuid1 = UUID.randomUUID().toString(); + String uuid2 = UUID.randomUUID().toString(); + RealmList list = new RealmList<>(UUID.fromString(uuid1), UUID.fromString(uuid2)); + collection.setList(AllJavaTypes.FIELD_UUID_LIST, list); + assertElements(collection, obj -> { + assertEquals(UUID.fromString(uuid1), obj.getFieldUUIDList().first()); + assertEquals(UUID.fromString(uuid2), obj.getFieldUUIDList().last()); + }); + break; + } default: fail("Unknown type: " + type); } @@ -1405,6 +1455,7 @@ public void setValue_specificType_wrongFieldNameThrows() { case DATE: collection.setDate("foo", new Date(1000)); break; case DECIMAL128: collection.setDecimal128("foo", new Decimal128(1000)); break; case OBJECT_ID: collection.setObjectId("foo", new ObjectId(TestHelper.randomObjectIdHexString())); break; + case UUID: collection.setUUID("foo", UUID.randomUUID()); break; case OBJECT: collection.setObject("foo", realm.createObject(AllTypes.class)); break; case MODEL_LIST: collection.setList("foo", new RealmList<>()); break; case STRING_VALUE_LIST: collection.setList("foo", new RealmList<>("Foo")); break; @@ -1419,6 +1470,7 @@ public void setValue_specificType_wrongFieldNameThrows() { case DATE_VALUE_LIST: collection.setList("foo", new RealmList<>(new Date())); break; case DECIMAL128_VALUE_LIST: collection.setList("foo", new RealmList<>(new Decimal128(1000))); break; case OBJECT_ID_VALUE_LIST: collection.setList("foo", new RealmList<>(new ObjectId(TestHelper.randomObjectIdHexString()))); break; + case UUID_VALUE_LIST: collection.setList("foo", new RealmList<>(UUID.randomUUID())); break; default: fail("Unknown type: " + type); } @@ -1449,6 +1501,7 @@ public void setValue_specificType_wrongTypeThrows() { case DATE: collection.setDate(AllJavaTypes.FIELD_STRING, new Date(1000)); break; case DECIMAL128: collection.setDecimal128(AllJavaTypes.FIELD_STRING, new Decimal128(1000)); break; case OBJECT_ID: collection.setObjectId(AllJavaTypes.FIELD_STRING, new ObjectId(TestHelper.randomObjectIdHexString())); break; + case UUID: collection.setUUID(AllJavaTypes.FIELD_STRING, UUID.randomUUID()); break; case OBJECT: collection.setObject(AllJavaTypes.FIELD_STRING, realm.createObject(AllJavaTypes.class, 42)); break; case MODEL_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(realm.createObject(AllJavaTypes.class, 43))); break; case STRING_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>("Foo")); break; @@ -1463,6 +1516,7 @@ public void setValue_specificType_wrongTypeThrows() { case DATE_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(new Date())); break; case DECIMAL128_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(new Decimal128(1000))); break; case OBJECT_ID_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(new ObjectId(TestHelper.randomObjectIdHexString()))); break; + case UUID_VALUE_LIST: collection.setList(AllJavaTypes.FIELD_STRING, new RealmList<>(UUID.randomUUID())); break; default: fail("Unknown type: " + type); } @@ -1555,11 +1609,18 @@ public void setValue_specificType_modelClassNameOnTypedRealms() { collection.setDecimal128("fieldDecimal128", new Decimal128(1000)); assertElements(collection, obj -> assertEquals(new Decimal128(1000), obj.fieldDecimal128)); break; - case OBJECT_ID: -// String hex = TestHelper.randomObjectIdHexString(); -// collection.setObjectId("fieldObjectId", new ObjectId(hex)); -// assertElements(collection, obj -> assertEquals(new ObjectId(hex), obj.fieldObjectId)); + case OBJECT_ID:{ + String hex = TestHelper.randomObjectIdHexString(); + collection.setObjectId("fieldObjectId", new ObjectId(hex)); + assertElements(collection, obj -> assertEquals(new ObjectId(hex), obj.fieldObjectId)); break; + } + case UUID:{ + String uuid = UUID.randomUUID().toString(); + collection.setUUID("fieldUUID", UUID.fromString(uuid)); + assertElements(collection, obj -> assertEquals(UUID.fromString(uuid), obj.fieldUUID)); + break; + } case OBJECT: { MappedAllJavaTypes childObj = realm.createObject(MappedAllJavaTypes.class, 42); collection.setObject("fieldObject", childObj); @@ -1652,7 +1713,7 @@ public void setValue_specificType_modelClassNameOnTypedRealms() { assertEquals(new Decimal128(1000), obj.fieldDecimalList.first()); }); break; - case OBJECT_ID_VALUE_LIST: + case OBJECT_ID_VALUE_LIST:{ String hex = TestHelper.randomObjectIdHexString(); collection.setList("fieldObjectIdList", new RealmList<>(new ObjectId(hex))); assertElements(collection, obj -> { @@ -1660,6 +1721,16 @@ public void setValue_specificType_modelClassNameOnTypedRealms() { assertEquals(new ObjectId(hex), obj.fieldObjectIdList.first()); }); break; + } + case UUID_VALUE_LIST:{ + String uuid = UUID.randomUUID().toString(); + collection.setList("fieldUUIDList", new RealmList<>(UUID.fromString(uuid))); + assertElements(collection, obj -> { + assertEquals(1, obj.fieldUUIDList.size()); + assertEquals(UUID.fromString(uuid), obj.fieldUUIDList.first()); + }); + break; + } default: fail("Unknown type: " + type); } @@ -1719,11 +1790,18 @@ public void setValue_specificType_internalNameOnDynamicRealms() { collection.setDecimal128("field_decimal128", new Decimal128(1000)); assertElements(collection, obj -> assertEquals(new Decimal128(1000), obj.getDecimal128("field_decimal128"))); break; - case OBJECT_ID: + case OBJECT_ID:{ String hex = TestHelper.randomObjectIdHexString(); collection.setObjectId("field_object_id", new ObjectId(hex)); assertElements(collection, obj -> assertEquals(new ObjectId(hex), obj.getObjectId("field_object_id"))); break; + } + case UUID:{ + String uuid = UUID.randomUUID().toString(); + collection.setUUID("field_uuid", UUID.fromString(uuid)); + assertElements(collection, obj -> assertEquals(UUID.fromString(uuid), obj.getUUID("field_uuid"))); + break; + } case OBJECT: { DynamicRealmObject childObj = dynamicRealm.createObject("MappedAllJavaTypes", 42); collection.setObject("field_object", childObj); @@ -1828,8 +1906,8 @@ public void setValue_specificType_internalNameOnDynamicRealms() { assertEquals(new Decimal128(1000), list.first()); }); break; - case OBJECT_ID_VALUE_LIST: - hex = TestHelper.randomObjectIdHexString(); + case OBJECT_ID_VALUE_LIST:{ + String hex = TestHelper.randomObjectIdHexString(); collection.setList("field_object_id_list", new RealmList<>(new ObjectId(hex))); assertElements(collection, obj -> { RealmList list = obj.getList("field_object_id_list", ObjectId.class); @@ -1837,6 +1915,17 @@ public void setValue_specificType_internalNameOnDynamicRealms() { assertEquals(new ObjectId(hex), list.first()); }); break; + } + case UUID_VALUE_LIST:{ + String uuid = UUID.randomUUID().toString(); + collection.setList("field_uuid_list", new RealmList<>(UUID.fromString(uuid))); + assertElements(collection, obj -> { + RealmList list = obj.getList("field_uuid_list", UUID.class); + assertEquals(1, list.size()); + assertEquals(UUID.fromString(uuid), list.first()); + }); + break; + } default: fail("Unknown type: " + type); } @@ -1893,6 +1982,7 @@ public void asJSON() throws JSONException { allTypes.setColumnDate(date); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal("0.123456789"))); allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(7))); + allTypes.setColumnUUID(UUID.fromString(uuid1)); allTypes.setColumnBinary(new byte[]{1, 2, 3}); allTypes.setColumnMutableRealmInteger(0); allTypes.setColumnRealmObject(dog1); @@ -1918,6 +2008,8 @@ public void asJSON() throws JSONException { allTypes.getColumnDecimal128List().add(Decimal128.POSITIVE_INFINITY); allTypes.getColumnObjectIdList().add(new ObjectId(TestHelper.generateObjectIdHexString(1))); allTypes.getColumnObjectIdList().add(new ObjectId(TestHelper.generateObjectIdHexString(2))); + allTypes.getColumnUUIDList().add(UUID.fromString(uuid1)); + allTypes.getColumnUUIDList().add(UUID.fromString(uuid2)); AllTypes allTypes2 = realm.createObject(AllTypes.class); allTypes2.setColumnString("alltypes2"); @@ -1940,6 +2032,7 @@ public void asJSON() throws JSONException { " \"columnBinary\":\"AQID\",\n" + " \"columnDecimal128\":\"1.23456789E-1\",\n" + " \"columnObjectId\":\"789abcdef0123456789abcde\",\n" + + " \"columnUUID\":\""+ uuid1 +"\",\n" + " \"columnMutableRealmInteger\":0,\n" + " \"columnRealmObject\":{\n" + " \"_key\":100,\n" + @@ -2018,6 +2111,10 @@ public void asJSON() throws JSONException { " \"columnObjectIdList\":[\n" + " \"123456789abcdef012345678\",\n" + " \"23456789abcdef0123456789\"\n" + + " ],\n" + + " \"columnUUIDList\":[\n" + + " \""+ uuid1 +"\",\n" + + " \""+ uuid2 +"\"\n" + " ]\n" + " }\n" + "]"; diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 8ee1772a6c..8d2a77a2fd 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -21,11 +21,6 @@ import android.os.Looper; import android.os.SystemClock; -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.rule.UiThreadTestRule; - import junit.framework.AssertionFailedError; import org.bson.types.Decimal128; @@ -37,6 +32,7 @@ import org.junit.After; import org.junit.Assume; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -58,6 +54,7 @@ import java.util.Objects; import java.util.Random; import java.util.Scanner; +import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -70,6 +67,10 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.UiThreadTestRule; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; @@ -135,6 +136,7 @@ @RunWith(AndroidJUnit4.class) +@Ignore("Tests crash due to bug in core, see https://jira.mongodb.org/browse/RCORE-435") public class RealmTests { private final static int TEST_DATA_SIZE = 10; @@ -192,6 +194,7 @@ private void populateTestRealm(Realm realm, int objects) { allTypes.setColumnFloat(1.234567F + i); allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(i))); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(i + "12345"))); + allTypes.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(i))); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); @@ -405,6 +408,14 @@ public void where_equalTo_wrongFieldTypeAsInput() throws IOException { } } catch (IllegalArgumentException ignored) { } + + try { + realm.where(AllTypes.class).equalTo(columnData.get(i), UUID.fromString(TestHelper.generateUUIDString(i))).findAll(); + if (i != 8) { + fail("Realm.where should fail with illegal argument"); + } + } catch (IllegalArgumentException ignored) { + } } } @@ -1380,6 +1391,7 @@ public void copyToRealm_fromOtherRealm() { allTypes.setColumnString("Test"); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal("12345"))); allTypes.setColumnObjectId(new ObjectId(TestHelper.randomObjectIdHexString())); + allTypes.setColumnUUID(UUID.randomUUID()); realm.commitTransaction(); RealmConfiguration realmConfig = configFactory.createConfiguration("other-realm"); @@ -1411,6 +1423,7 @@ public void copyToRealm() { allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal("12345"))); allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(7))); + allTypes.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(7))); allTypes.setColumnRealmObject(dog); allTypes.setColumnRealmList(list); @@ -1423,6 +1436,7 @@ public void copyToRealm() { allTypes.setColumnDateList(new RealmList(new Date(1L))); allTypes.setColumnDecimal128List(new RealmList(new Decimal128(new BigDecimal("54321")))); allTypes.setColumnObjectIdList(new RealmList(new ObjectId(TestHelper.generateObjectIdHexString(5)))); + allTypes.setColumnUUIDList(new RealmList<>(UUID.fromString(TestHelper.generateUUIDString(5)))); realm.beginTransaction(); AllTypes realmTypes = realm.copyToRealm(allTypes); @@ -1438,6 +1452,7 @@ public void copyToRealm() { assertArrayEquals(allTypes.getColumnBinary(), realmTypes.getColumnBinary()); assertEquals(allTypes.getColumnDecimal128(), realmTypes.getColumnDecimal128()); assertEquals(allTypes.getColumnObjectId(), realmTypes.getColumnObjectId()); + assertEquals(allTypes.getColumnUUID(), realmTypes.getColumnUUID()); assertEquals(allTypes.getColumnRealmObject().getName(), dog.getName()); assertEquals(list.size(), realmTypes.getColumnRealmList().size()); //noinspection ConstantConditions @@ -1463,6 +1478,9 @@ public void copyToRealm() { assertEquals(1, realmTypes.getColumnObjectIdList().size()); assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(5)), realmTypes.getColumnObjectIdList().get(0)); + assertEquals(1, realmTypes.getColumnUUIDList().size()); + assertEquals(UUID.fromString(TestHelper.generateUUIDString(5)), realmTypes.getColumnUUIDList().get(0)); + } @Test @@ -3475,6 +3493,7 @@ public void copyFromRealm() { assertEquals(realmObject.getColumnDate(), unmanagedObject.getColumnDate()); assertEquals(realmObject.getColumnObjectId(), unmanagedObject.getColumnObjectId()); assertEquals(realmObject.getColumnDecimal128(), unmanagedObject.getColumnDecimal128()); + assertEquals(realmObject.getColumnUUID(), unmanagedObject.getColumnUUID()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java index 881a30b457..496e6e0bd6 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/AllJavaTypes.java @@ -20,6 +20,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import io.realm.RealmList; import io.realm.RealmObject; @@ -48,6 +49,7 @@ public class AllJavaTypes extends RealmObject { public static final String FIELD_BINARY = "fieldBinary"; public static final String FIELD_DECIMAL128 = "fieldDecimal128"; public static final String FIELD_OBJECT_ID = "fieldObjectId"; + public static final String FIELD_UUID = "fieldUUID"; public static final String FIELD_OBJECT = "fieldObject"; public static final String FIELD_LIST = "fieldList"; @@ -63,6 +65,7 @@ public class AllJavaTypes extends RealmObject { public static final String FIELD_DATE_LIST = "fieldDateList"; public static final String FIELD_DECIMAL128_LIST = "fieldDecimal128List"; public static final String FIELD_OBJECT_ID_LIST = "fieldObjectIdList"; + public static final String FIELD_UUID_LIST = "fieldUUIDList"; public static final String FIELD_LO_OBJECT = "objectParents"; public static final String FIELD_LO_LIST = "listParents"; @@ -81,6 +84,7 @@ public class AllJavaTypes extends RealmObject { FIELD_OBJECT + "." + FIELD_DATE_LIST, FIELD_OBJECT + "." + FIELD_DECIMAL128_LIST, FIELD_OBJECT + "." + FIELD_OBJECT_ID_LIST, + FIELD_OBJECT + "." + FIELD_UUID_LIST, }; @Ignore @@ -100,6 +104,7 @@ public class AllJavaTypes extends RealmObject { private byte[] fieldBinary; private Decimal128 fieldDecimal128; private ObjectId fieldObjectId; + private UUID fieldUUID; private AllJavaTypes fieldObject; private RealmList fieldList; @@ -115,6 +120,7 @@ public class AllJavaTypes extends RealmObject { private RealmList fieldDateList; private RealmList fieldDecimal128List; private RealmList fieldObjectIdList; + private RealmList fieldUUIDList; @LinkingObjects(FIELD_OBJECT) private final RealmResults objectParents = null; @@ -338,6 +344,14 @@ public void setFieldObjectId(ObjectId fieldObjectId) { this.fieldObjectId = fieldObjectId; } + public UUID getFieldUUID() { + return fieldUUID; + } + + public void setFieldUUID(UUID fieldUUID) { + this.fieldUUID = fieldUUID; + } + public RealmList getFieldDecimal128List() { return fieldDecimal128List; } @@ -354,6 +368,14 @@ public void setFieldObjectIdList(RealmList fieldObjectIdList) { this.fieldObjectIdList = fieldObjectIdList; } + public RealmList getFieldUUIDList() { + return fieldUUIDList; + } + + public void setFieldUUIDList(RealmList fieldUUIDList) { + this.fieldUUIDList = fieldUUIDList; + } + public RealmResults getObjectParents() { return objectParents; } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java index 1e3da43cf7..518a2a440a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/MappedAllJavaTypes.java @@ -20,13 +20,12 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import io.realm.RealmList; import io.realm.RealmObject; -import io.realm.RealmResults; import io.realm.annotations.Ignore; import io.realm.annotations.Index; -import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.RealmNamingPolicy; @@ -54,6 +53,7 @@ public class MappedAllJavaTypes extends RealmObject { public byte[] fieldBinary; public Decimal128 fieldDecimal128; public ObjectId fieldObjectId; + public UUID fieldUUID; public MappedAllJavaTypes fieldObject; public RealmList fieldList; @@ -69,6 +69,7 @@ public class MappedAllJavaTypes extends RealmObject { public RealmList fieldDateList; public RealmList fieldDecimalList; // FIXME using fieldDecimal128List causes issues investigate public RealmList fieldObjectIdList; + public RealmList fieldUUIDList; public MappedAllJavaTypes() { } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/NoPrimaryKeyNullTypes.java b/realm/realm-library/src/androidTest/java/io/realm/entities/NoPrimaryKeyNullTypes.java index c120399fa8..c68a7845da 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/NoPrimaryKeyNullTypes.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/NoPrimaryKeyNullTypes.java @@ -20,6 +20,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import io.realm.RealmObject; import io.realm.annotations.Required; @@ -105,6 +106,10 @@ public class NoPrimaryKeyNullTypes extends RealmObject { private ObjectId fieldObjectIdNotNull = new ObjectId(); private ObjectId fieldObjectIdNull; + @Required + private UUID fieldUUIDNotNull = UUID.randomUUID(); + private UUID fieldUUIDNull; + private NoPrimaryKeyNullTypes fieldObjectNull; public String getFieldStringNotNull() { @@ -306,4 +311,20 @@ public ObjectId getFieldObjectIdNull() { public void setFieldObjectIdNull(ObjectId fieldObjectIdNull) { this.fieldObjectIdNull = fieldObjectIdNull; } + + public UUID getFieldUUIDNotNull() { + return fieldUUIDNotNull; + } + + public void setFieldUUIDNotNull(UUID fieldUUIDNotNull) { + this.fieldUUIDNotNull = fieldUUIDNotNull; + } + + public UUID getFieldUUIDNull() { + return fieldUUIDNull; + } + + public void setFieldUUIDNull(UUID fieldUUIDNull) { + this.fieldUUIDNull = fieldUUIDNull; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java b/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java index 85b52fc8b9..f01c162a52 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/NullablePrimitiveFields.java @@ -20,6 +20,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import io.realm.RealmObject; @@ -36,6 +37,7 @@ public class NullablePrimitiveFields extends RealmObject { public static final String FIELD_BINARY = "fieldBinary"; public static final String FIELD_OBJECT_ID = "fieldObjectId"; public static final String FIELD_DECIMAL128 = "fieldDecimal128"; + public static final String FIELD_UUID = "fieldUUID"; private Boolean fieldBoolean; private Integer fieldInt; @@ -46,6 +48,7 @@ public class NullablePrimitiveFields extends RealmObject { private Date fieldDate; private ObjectId fieldObjectId; private Decimal128 fieldDecimal128; + private UUID fieldUUID; public Integer getFieldInt() { return fieldInt; @@ -118,4 +121,12 @@ public Decimal128 getFieldDecimal128() { public void setFieldDecimal128(Decimal128 fieldDecimal128) { this.fieldDecimal128 = fieldDecimal128; } + + public UUID getFieldUUID() { + return fieldUUID; + } + + public void setFieldUUID(UUID fieldUUID) { + this.fieldUUID = fieldUUID; + } } diff --git a/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java b/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java index e408f50e61..0637f45691 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java +++ b/realm/realm-library/src/androidTest/java/io/realm/entities/pojo/AllTypesRealmModel.java @@ -20,6 +20,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import io.realm.RealmList; import io.realm.RealmModel; @@ -52,6 +53,7 @@ public class AllTypesRealmModel implements RealmModel { public RealmList columnRealmList; public Decimal128 columnDecimal128; public ObjectId columnObjectId; + public UUID columnUUID; @Override diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt index b47a5d8d6b..d2793df995 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/Decimal128Tests.kt @@ -21,26 +21,28 @@ import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import io.realm.kotlin.createObject import io.realm.kotlin.where +import io.realm.rule.TestRealmConfigurationFactory import org.bson.types.Decimal128 import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import java.math.BigDecimal +import kotlin.test.assertFailsWith open class Decimal128Required : RealmObject() { - @field:PrimaryKey + @PrimaryKey var id: Long = 0 - @field:Required + + @Required var decimal: Decimal128? = null var name: String = "" } open class Decimal128NotRequired : RealmObject() { - @field:PrimaryKey + @PrimaryKey var id: Long = 0 var decimal: Decimal128? = null var name: String = "" @@ -48,7 +50,8 @@ open class Decimal128NotRequired : RealmObject() { open class Decimal128RequiredRealmList : RealmObject() { var id: Long = 0 - @field:Required + + @Required var decimals: RealmList = RealmList() var name: String = "" } @@ -59,6 +62,10 @@ open class Decimal128OptionalRealmList : RealmObject() { var name: String = "" } +open class LinkedDecimal128 : RealmObject() { + var linkedDecimal128: Decimal128NotRequired? = null +} + @RunWith(AndroidJUnit4::class) class Decimal128Tests { private lateinit var realmConfiguration: RealmConfiguration @@ -66,7 +73,7 @@ class Decimal128Tests { @Rule @JvmField - val folder = TemporaryFolder() + val configFactory = TestRealmConfigurationFactory() init { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) @@ -74,13 +81,13 @@ class Decimal128Tests { @Before fun setUp() { - realmConfiguration = RealmConfiguration - .Builder(InstrumentationRegistry.getInstrumentation().targetContext) - .directory(folder.newFolder()) + realmConfiguration = configFactory + .createConfigurationBuilder() .schema(Decimal128Required::class.java, Decimal128NotRequired::class.java, Decimal128RequiredRealmList::class.java, - Decimal128OptionalRealmList::class.java) + Decimal128OptionalRealmList::class.java, + LinkedDecimal128::class.java) .build() realm = Realm.getInstance(realmConfiguration) } @@ -91,61 +98,96 @@ class Decimal128Tests { } @Test - fun copyToAndFromRealm() { + fun copyToRealm() { val value = Decimal128NotRequired() value.decimal = Decimal128(BigDecimal.TEN) value.id = 42 value.name = "Foo" - // copyToRealm realm.beginTransaction() val obj = realm.copyToRealm(value) realm.commitTransaction() + assertEquals(Decimal128(BigDecimal.TEN), obj.decimal) assertEquals(42L, obj.id) assertEquals("Foo", obj.name) + } + + @Test + fun copyFromRealm() { + realm.beginTransaction() + val value = realm.createObject(42) + value.decimal = Decimal128(BigDecimal.TEN) + value.name = "Foo" + realm.commitTransaction() + + val copy = realm.copyFromRealm(value) + + assertEquals(Decimal128(BigDecimal.TEN), copy.decimal) + assertEquals(42L, copy.id) + assertEquals("Foo", copy.name) + } + + @Test + fun copyToRealmOrUpdate() { + realm.executeTransaction { realm -> + val value = realm.createObject(42) + value.decimal = Decimal128(BigDecimal.TEN) + value.name = "Foo" + } - // copyToRealmOrUpdate + val value = Decimal128NotRequired() value.id = 42 value.decimal = Decimal128(BigDecimal.ONE) value.name = "Bar" + realm.beginTransaction() - realm.copyToRealmOrUpdate(value) + val obj = realm.copyToRealmOrUpdate(value) realm.commitTransaction() - // copyFromRealm - val copy = realm.copyFromRealm(obj) - assertEquals(Decimal128(BigDecimal.ONE), copy.decimal) - assertEquals(42L, copy.id) - assertEquals("Bar", copy.name) + assertEquals(42L, obj.id) + assertEquals(Decimal128(BigDecimal.ONE), obj.decimal) + assertEquals("Bar", obj.name) } @Test fun insert() { - val value = Decimal128Required() - value.id = 7 - value.name = "Foo" - value.decimal = Decimal128(10) + realm.executeTransaction { realm -> + val value = Decimal128Required() + value.id = 7 + value.name = "Foo" + value.decimal = Decimal128(10) - // insert - realm.beginTransaction() - realm.insert(value) - realm.commitTransaction() + realm.insert(value) + } val obj = realm.where().findFirst() + assertNotNull(obj) assertEquals(7, obj!!.id) assertEquals(Decimal128(10), obj.decimal) assertEquals("Foo", obj.name) + } - // insertOrUpdate - realm.beginTransaction() - obj.decimal = Decimal128(20) - obj.name = "Bar" - realm.insertOrUpdate(obj) - realm.commitTransaction() + @Test + fun insertOrUpdate() { + realm.executeTransaction { realm -> + val value = realm.createObject(7) + value.name = "Foo" + value.decimal = Decimal128(10) + } + + realm.executeTransaction { realm -> + val value = Decimal128Required() + value.id = 7 + value.name = "Bar" + value.decimal = Decimal128(20) + + realm.insertOrUpdate(value) + } val all = realm.where().findAll() + assertEquals(1, all.size) assertEquals(7, all[0]!!.id) assertEquals(Decimal128(20), all[0]!!.decimal) @@ -161,6 +203,7 @@ class Decimal128Tests { realm.commitTransaction() val frozen = obj.freeze() + assertEquals(Decimal128(BigDecimal.TEN), frozen.decimal) assertEquals("foo", frozen.name) assertEquals(42L, frozen.id) @@ -180,11 +223,11 @@ class Decimal128Tests { assertEquals("foo", result.name) realm.beginTransaction() - try { + + assertFailsWith("It should not be possible to set null value for the required decimal field") { result.decimal = null - fail("It should not be possible to set null value for the required decimal field") - } catch (expected: IllegalArgumentException) { } + realm.commitTransaction() } @@ -214,10 +257,8 @@ class Decimal128Tests { fun requiredRealmList() { realm.beginTransaction() val obj = realm.createObject() - try { + assertFailsWith("It should not be possible to add nullable elements to a required RealmList") { obj.decimals.add(null) - fail("It should not be possible to add nullable elements to a required RealmList") - } catch (expected: Exception) { } } @@ -234,19 +275,16 @@ class Decimal128Tests { @Test fun linkQueryNotSupported() { - try { + assertFailsWith("It should not be possible to perform link query on Decimal128") { realm.where().greaterThan("decimals", Decimal128(BigDecimal.ZERO)).findAll() - fail("It should not be possible to perform link query on Decimal128") - } catch (expected: IllegalArgumentException) { } realm.beginTransaction() val obj = realm.createObject() realm.cancelTransaction() - try { + assertFailsWith { obj.decimals.where().equalTo("decimals", Decimal128(BigDecimal.ZERO)).findAll() - } catch (expected: UnsupportedOperationException) { } } @@ -259,6 +297,7 @@ class Decimal128Tests { realm.commitTransaction() val all = realm.where().equalTo("decimal", Decimal128(Float.NaN.toLong())).findAll() + assertEquals(3, all.size) } @@ -274,6 +313,7 @@ class Decimal128Tests { realm.commitTransaction() var all = realm.where().equalTo("decimal", Decimal128(Float.MIN_VALUE.toLong())).findAll() + assertEquals(4, all.size) assertEquals(Decimal128(BigDecimal(Float.MIN_VALUE.toLong())), all[0]!!.decimal) assertEquals(Decimal128(Float.MIN_VALUE.toLong()), all[1]!!.decimal) @@ -281,6 +321,7 @@ class Decimal128Tests { assertEquals(Decimal128.NEGATIVE_ZERO, all[3]!!.decimal) all = realm.where().notEqualTo("decimal", Decimal128(Float.MIN_VALUE.toLong())).findAll() + assertEquals(2, all.size) assertEquals(Decimal128.NEGATIVE_INFINITY, all[0]!!.decimal) assertEquals(Decimal128.NEGATIVE_NaN, all[1]!!.decimal) @@ -295,6 +336,7 @@ class Decimal128Tests { realm.commitTransaction() val min: Number? = realm.where().min("decimal") + assertNotNull(min) assertTrue(min is Decimal128) assertEquals(Decimal128(BigDecimal.ZERO), min) @@ -319,6 +361,7 @@ class Decimal128Tests { assertEquals(Decimal128(Double.MAX_VALUE.toLong()), all[2]!!.decimal) all = realm.where().notEqualTo("decimal", Decimal128(Float.MAX_VALUE.toLong())).findAll() + assertEquals(3, all.size) assertEquals(Decimal128.POSITIVE_INFINITY, all[0]!!.decimal) assertEquals(Decimal128.NaN, all[1]!!.decimal) @@ -334,6 +377,7 @@ class Decimal128Tests { realm.commitTransaction() val max: Number? = realm.where().max("decimal") + assertNotNull(max) assertTrue(max is Decimal128) assertEquals(Decimal128(BigDecimal.TEN), max) @@ -348,6 +392,7 @@ class Decimal128Tests { realm.commitTransaction() val between = realm.where().between("decimal", Decimal128(-1L), Decimal128(11L)).findAll() + assertEquals(3, between.size) assertEquals(Decimal128(BigDecimal.TEN), between[0]!!.decimal) assertEquals(Decimal128(BigDecimal.ONE), between[1]!!.decimal) @@ -384,6 +429,7 @@ class Decimal128Tests { assertEquals(Decimal128(BigDecimal.TEN), all[2]!!.decimal) all = realm.where().sort("decimal", Sort.DESCENDING).findAll() + assertEquals(3, all.size) assertEquals(Decimal128(BigDecimal.TEN), all[0]!!.decimal) assertEquals(Decimal128(BigDecimal.ONE), all[1]!!.decimal) @@ -405,6 +451,7 @@ class Decimal128Tests { realm.commitTransaction() val all = realm.where().distinct("decimal").sort("decimal", Sort.ASCENDING).findAll() + assertEquals(4, all.size) assertNull(all[0]!!.decimal) assertEquals(Decimal128(BigDecimal.ZERO), all[1]!!.decimal) @@ -414,7 +461,7 @@ class Decimal128Tests { } @Test - fun queries() { + fun queriesCount() { realm.beginTransaction() realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) realm.createObject(2).decimal = null @@ -422,85 +469,186 @@ class Decimal128Tests { realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) realm.commitTransaction() - // count assertEquals(4, realm.where().count()) + } + + @Test + fun queriesNotEqualTo() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() - // notEqualTo - var all = realm.where() + val all = realm.where() .notEqualTo("decimal", Decimal128(BigDecimal.ONE)) .sort("decimal", Sort.ASCENDING) .findAll() + assertEquals(3, all.size) assertNull(all[0]!!.decimal) assertEquals(Decimal128(BigDecimal.ZERO), all[1]!!.decimal) assertEquals(Decimal128(BigDecimal.TEN), all[2]!!.decimal) + } + + @Test + fun queriesGreaterThanOrEqualTo() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() - // greaterThanOrEqualTo - all = realm.where() + val all = realm.where() .greaterThanOrEqualTo("decimal", Decimal128(BigDecimal.ONE)) .sort("decimal", Sort.ASCENDING) .findAll() + assertEquals(2, all.size) assertEquals(Decimal128(BigDecimal.ONE), all[0]!!.decimal) assertEquals(Decimal128(BigDecimal.TEN), all[1]!!.decimal) + } - // greaterThan - all = realm.where() + @Test + fun queriesGreaterThan() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() + + val all = realm.where() .greaterThan("decimal", Decimal128(BigDecimal.ONE)) .sort("decimal", Sort.ASCENDING) .findAll() + assertEquals(1, all.size) assertEquals(Decimal128(BigDecimal.TEN), all[0]!!.decimal) + } + @Test + fun queriesLessThanOrEqualTo() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() - // lessThanOrEqualTo - all = realm.where() + val all = realm.where() .lessThanOrEqualTo("decimal", Decimal128(BigDecimal.ONE)) .sort("decimal", Sort.ASCENDING) .findAll() + assertEquals(2, all.size) assertEquals(Decimal128(BigDecimal.ZERO), all[0]!!.decimal) assertEquals(Decimal128(BigDecimal.ONE), all[1]!!.decimal) + } + + @Test + fun queriesLessThan() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() - // lessThan - all = realm.where() + val all = realm.where() .lessThan("decimal", Decimal128(BigDecimal.ONE)) .sort("decimal", Sort.ASCENDING) .findAll() + assertEquals(1, all.size) assertEquals(Decimal128(BigDecimal.ZERO), all[0]!!.decimal) + } - // isNull - all = realm.where() + @Test + fun queriesIsNull() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() + + val all = realm.where() .isNull("decimal") .findAll() + assertEquals(1, all.size) assertNull(all[0]!!.decimal) assertEquals(2L, all[0]!!.id) + } + + @Test + fun queriesIsNotNull() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() - // isNotNull - all = realm.where() + val all = realm.where() .isNotNull("decimal") .sort("decimal", Sort.ASCENDING) .findAll() + assertEquals(3, all.size) assertEquals(Decimal128(BigDecimal.ZERO), all[0]!!.decimal) assertEquals(Decimal128(BigDecimal.ONE), all[1]!!.decimal) assertEquals(Decimal128(BigDecimal.TEN), all[2]!!.decimal) + } - // average - try { + @Test + fun queriesAverage() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() + + assertFailsWith("Average is not supported for Decimal128") { realm.where().average("decimal") // FIXME should we support avergae queries in Core? - fail("Average is not supported for Decimal128") - } catch (expected: IllegalArgumentException) { } + } - // isEmpty - try { + @Test + fun queriesIsEmpty() { + realm.beginTransaction() + realm.createObject(1).decimal = Decimal128(BigDecimal.ONE) + realm.createObject(2).decimal = null + realm.createObject(3).decimal = Decimal128(BigDecimal.TEN) + realm.createObject(4).decimal = Decimal128(BigDecimal.ZERO) + realm.commitTransaction() + + assertFailsWith("isEmpty is not supported for Decimal128") { realm.where().isEmpty("decimal") - fail("isEmpty is not supported for Decimal128") - } catch (expected: IllegalArgumentException) { } } + @Test + fun linkedQuery() { + val decimal128Array = arrayOf(null, Decimal128.parse("0"), Decimal128.parse("1"), Decimal128.parse("2")) + + realm.executeTransaction { realm -> + decimal128Array.forEachIndexed { i, decimal128 -> + val decimalObj = realm.createObject(i) + decimalObj.decimal = decimal128 + + realm.createObject().linkedDecimal128 = decimalObj + } + } + + for (decimal in decimal128Array) { + val results = realm.where().equalTo("linkedDecimal128.decimal", decimal).findAll() + assertEquals(1, results.size) + assertEquals(decimal, results.first()?.linkedDecimal128?.decimal) + } + } } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt index 7162bbb1f2..9a0b711b46 100644 --- a/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/ObjectIdTests.kt @@ -24,54 +24,57 @@ import io.realm.exceptions.RealmException import io.realm.exceptions.RealmPrimaryKeyConstraintException import io.realm.kotlin.createObject import io.realm.kotlin.where +import io.realm.rule.TestRealmConfigurationFactory import org.bson.types.ObjectId import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith +import kotlin.test.assertFailsWith open class ObjectIdPrimaryKeyRequired - : RealmObject() { - @field:PrimaryKey - @field:Required - var id : ObjectId? = null - var name : String = "" + : RealmObject() { + @PrimaryKey + @Required + var id: ObjectId? = null + var name: String = "" var anotherId: ObjectId? = null - } open class ObjectIdPrimaryKeyNotRequired : RealmObject() { - @field:PrimaryKey - var id : ObjectId? = null - var name : String = "" - + @PrimaryKey + var id: ObjectId? = null + var name: String = "" } open class ObjectIdAndString : RealmObject() { - var id : ObjectId? = null - var name : String = "" + var id: ObjectId? = null + var name: String = "" } open class ObjectIdRequiredRealmList : RealmObject() { var id: Long = 0 - @field:Required - var ids : RealmList = RealmList() - var name : String = "" + @Required + var ids: RealmList = RealmList() + var name: String = "" } open class ObjectIdOptionalRealmList : RealmObject() { var id: Long = 0 - var ids : RealmList = RealmList() - var name : String = "" + var ids: RealmList = RealmList() + var name: String = "" +} + +open class LinkedObjectId : RealmObject() { + var linkedObjectId: ObjectIdPrimaryKeyNotRequired? = null } @RunWith(AndroidJUnit4::class) @@ -80,7 +83,8 @@ class ObjectIdTests { private lateinit var realm: Realm @Rule - @JvmField val folder = TemporaryFolder() + @JvmField + val configFactory = TestRealmConfigurationFactory() init { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) @@ -88,14 +92,14 @@ class ObjectIdTests { @Before fun setUp() { - realmConfiguration = RealmConfiguration - .Builder(InstrumentationRegistry.getInstrumentation().targetContext) - .directory(folder.newFolder()) + realmConfiguration = configFactory + .createConfigurationBuilder() .schema(ObjectIdPrimaryKeyRequired::class.java, ObjectIdPrimaryKeyNotRequired::class.java, ObjectIdAndString::class.java, ObjectIdRequiredRealmList::class.java, - ObjectIdOptionalRealmList::class.java) + ObjectIdOptionalRealmList::class.java, + LinkedObjectId::class.java) .build() realm = Realm.getInstance(realmConfiguration) } @@ -106,67 +110,114 @@ class ObjectIdTests { } @Test - fun copyToAndFromRealm() { + fun copyToRealm() { val objectIdHex1 = generateObjectIdHexString(1) val objectIdHex2 = generateObjectIdHexString(2) - val objectIdHex3 = generateObjectIdHexString(3) val value = ObjectIdPrimaryKeyRequired() value.id = ObjectId(objectIdHex1) value.anotherId = ObjectId(objectIdHex2) value.name = "Foo" - // copyToRealm realm.beginTransaction() val obj = realm.copyToRealm(value) realm.commitTransaction() + assertEquals(ObjectId(objectIdHex1), obj.id) assertEquals(ObjectId(objectIdHex2), obj.anotherId) assertEquals("Foo", obj.name) + } + + @Test + fun copyFromRealm() { + val objectIdHex1 = generateObjectIdHexString(1) + val objectIdHex2 = generateObjectIdHexString(2) - // copyToRealmOrUpdate - value.name = "Bar" - value.anotherId = ObjectId(objectIdHex3) realm.beginTransaction() - realm.copyToRealmOrUpdate(value) + val value = realm.createObject(ObjectId(objectIdHex1)) + value.anotherId = ObjectId(objectIdHex2) + value.name = "Foo" realm.commitTransaction() - // copyFromRealm - val copy = realm.copyFromRealm(obj) + val copy = realm.copyFromRealm(value) + assertEquals(ObjectId(objectIdHex1), copy.id) - assertEquals(ObjectId(objectIdHex3), copy.anotherId) - assertEquals("Bar", copy.name) + assertEquals(ObjectId(objectIdHex2), copy.anotherId) + assertEquals("Foo", copy.name) } @Test - fun insert() { + fun copyToRealmOrUpdate() { + val objectIdHex1 = generateObjectIdHexString(1) + val objectIdHex2 = generateObjectIdHexString(2) + val objectIdHex3 = generateObjectIdHexString(3) + + realm.executeTransaction { realm -> + val obj = realm.createObject(ObjectId(objectIdHex1)) + obj.anotherId = ObjectId(objectIdHex2) + obj.name = "Foo" + } + val value = ObjectIdPrimaryKeyRequired() - val objectIdHex1 = generateObjectIdHexString(0) - val objectIdHex2 = generateObjectIdHexString(7) value.id = ObjectId(objectIdHex1) - value.name = "Foo" - value.anotherId = ObjectId(generateObjectIdHexString(7)) + value.anotherId = ObjectId(objectIdHex3) + value.name = "Bar" - // insert realm.beginTransaction() - realm.insert(value) + val obj = realm.copyToRealmOrUpdate(value) realm.commitTransaction() + assertEquals(ObjectId(objectIdHex1), obj.id) + assertEquals(ObjectId(objectIdHex3), obj.anotherId) + assertEquals("Bar", obj.name) + } + + @Test + fun insert() { + val objectIdHex1 = generateObjectIdHexString(0) + val objectIdHex2 = generateObjectIdHexString(7) + + realm.executeTransaction { realm -> + val value = ObjectIdPrimaryKeyRequired() + value.id = ObjectId(objectIdHex1) + value.name = "Foo" + value.anotherId = ObjectId(generateObjectIdHexString(7)) + + realm.insert(value) + } + val obj = realm.where().findFirst() + assertNotNull(obj) assertEquals(ObjectId(objectIdHex1), obj!!.id) assertEquals(ObjectId(objectIdHex2), obj.anotherId) assertEquals("Foo", obj.name) + } - // insertOrUpdate - realm.beginTransaction() - val objectIdHex3 = generateObjectIdHexString(1) - obj.anotherId = ObjectId(objectIdHex3) - obj.name = "Bar" - realm.insertOrUpdate(obj) - realm.commitTransaction() + @Test + fun insertOrUpdate() { + val value = ObjectIdPrimaryKeyRequired() + val objectIdHex1 = generateObjectIdHexString(0) + val objectIdHex2 = generateObjectIdHexString(6) + val objectIdHex3 = generateObjectIdHexString(7) + + realm.executeTransaction { realm -> + realm.createObject(ObjectId(objectIdHex1)) + value.name = "Foo" + value.anotherId = ObjectId(objectIdHex2) + } + + realm.executeTransaction { realm -> + val value = ObjectIdPrimaryKeyRequired() + value.id = ObjectId(objectIdHex1) + value.name = "Bar" + value.anotherId = ObjectId(objectIdHex3) + + realm.insertOrUpdate(value) + } val all = realm.where().findAll() + assertEquals(1, all.size) assertEquals(ObjectId(objectIdHex1), all[0]!!.id) assertEquals(ObjectId(objectIdHex3), all[0]!!.anotherId) @@ -182,18 +233,17 @@ class ObjectIdTests { realm.commitTransaction() val frozen = obj.freeze() + assertEquals(ObjectId(hex), frozen.id) assertEquals("foo", frozen.name) } - @Test fun requiredPK() { realm.beginTransaction() - try { + + assertFailsWith { realm.createObject() - fail() - } catch (ignore: RealmException) { } val obj = realm.createObject(ObjectId(generateObjectIdHexString(42))) @@ -202,16 +252,15 @@ class ObjectIdTests { realm.commitTransaction() val result = realm.where().equalTo("id", ObjectId(generateObjectIdHexString(42))).findFirst() + assertNotNull(result) assertEquals("foo", result?.name) } @Test fun nullablePK() { - try { + assertFailsWith { realm.createObject() - fail() - } catch (ignore: RealmException) { } realm.beginTransaction() @@ -229,10 +278,9 @@ class ObjectIdTests { fun requiredRealmList() { realm.beginTransaction() val obj = realm.createObject() - try { + + assertFailsWith("It should not be possible to add nullable elements to a required RealmList") { obj.ids.add(null) - fail("It should not be possible to add nullable elements to a required RealmList") - } catch (expected: Exception) { } } @@ -249,28 +297,27 @@ class ObjectIdTests { @Test fun linkQueryNotSupported() { - try { + assertFailsWith("It should not be possible to perform link query on ObjectId") { realm.where().greaterThan("ids", ObjectId(generateObjectIdHexString(0))).findAll() - fail("It should not be possible to perform link query on ObjectId") - } catch (expected: IllegalArgumentException) {} + } realm.beginTransaction() val obj = realm.createObject() realm.cancelTransaction() - try { + assertFailsWith { obj.ids.where().equalTo("ids", ObjectId(generateObjectIdHexString(0))).findAll() - } catch (expected: UnsupportedOperationException) {} + } } @Test fun duplicatePK() { realm.beginTransaction() realm.createObject(ObjectId(generateObjectIdHexString(0))) - try { + + assertFailsWith("It should throw for duplicate PK usage") { realm.createObject(ObjectId(generateObjectIdHexString(0))) - fail("It should throw for duplicate PK usage") - } catch (expected: RealmPrimaryKeyConstraintException) {} + } realm.cancelTransaction() } @@ -312,6 +359,7 @@ class ObjectIdTests { val all = realm.where().distinct("id").sort("id", Sort.ASCENDING).findAll() assertEquals(4, all.size) + assertNull(all[0]!!.id) assertEquals(ObjectId(generateObjectIdHexString(0)), all[1]!!.id) assertEquals(ObjectId(generateObjectIdHexString(1)), all[2]!!.id) @@ -320,7 +368,7 @@ class ObjectIdTests { } @Test - fun queries() { + fun queriesCount() { realm.beginTransaction() realm.createObject().id = ObjectId(generateObjectIdHexString(1)) realm.createObject().id = null @@ -328,82 +376,185 @@ class ObjectIdTests { realm.createObject().id = ObjectId(generateObjectIdHexString(0)) realm.commitTransaction() - // count assertEquals(4, realm.where().count()) + } + + @Test + + fun queriesNotEqualTo() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() - // notEqualTo - var all = realm.where() + val all = realm.where() .notEqualTo("id", ObjectId(generateObjectIdHexString(1))) .sort("id", Sort.ASCENDING) .findAll() + assertEquals(3, all.size) assertNull(all[0]!!.id) assertEquals(ObjectId(generateObjectIdHexString(0)), all[1]!!.id) assertEquals(ObjectId(generateObjectIdHexString(10)), all[2]!!.id) + } - // greaterThanOrEqualTo - all = realm.where() + @Test + fun queriesGreaterThanOrEqualTo() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() + + val all = realm.where() .greaterThanOrEqualTo("id", ObjectId(generateObjectIdHexString(1))) .sort("id", Sort.ASCENDING) .findAll() + assertEquals(2, all.size) assertEquals(ObjectId(generateObjectIdHexString(1)), all[0]!!.id) assertEquals(ObjectId(generateObjectIdHexString(10)), all[1]!!.id) + } + + @Test + fun queriesGreaterThan() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() - // greaterThan - all = realm.where() + val all = realm.where() .greaterThan("id", ObjectId(generateObjectIdHexString(1))) .sort("id", Sort.ASCENDING) .findAll() + assertEquals(1, all.size) assertEquals(ObjectId(generateObjectIdHexString(10)), all[0]!!.id) + } + @Test + fun queriesLessThanOrEqualTo() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() - // lessThanOrEqualTo - all = realm.where() + val all = realm.where() .lessThanOrEqualTo("id", ObjectId(generateObjectIdHexString(1))) .sort("id", Sort.ASCENDING) .findAll() + assertEquals(2, all.size) assertEquals(ObjectId(generateObjectIdHexString(0)), all[0]!!.id) assertEquals(ObjectId(generateObjectIdHexString(1)), all[1]!!.id) + } + + @Test + fun queriesLessThan() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() - // lessThan - all = realm.where() + val all = realm.where() .lessThan("id", ObjectId(generateObjectIdHexString(1))) .sort("id", Sort.ASCENDING) .findAll() + assertEquals(1, all.size) assertEquals(ObjectId(generateObjectIdHexString(0)), all[0]!!.id) + } - // isNull - all = realm.where() + @Test + fun queriesIsNull() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() + + val all = realm.where() .isNull("id") .findAll() + assertEquals(1, all.size) assertNull(all[0]!!.id) + } + + @Test + fun queriesIsNotNull() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() - // isNotNull - all = realm.where() + val all = realm.where() .isNotNull("id") .sort("id", Sort.ASCENDING) .findAll() + assertEquals(3, all.size) assertEquals(ObjectId(generateObjectIdHexString(0)), all[0]!!.id) assertEquals(ObjectId(generateObjectIdHexString(1)), all[1]!!.id) assertEquals(ObjectId(generateObjectIdHexString(10)), all[2]!!.id) + } - // average - try { - realm.where().average("id") // FIXME should we support avergae queries in Core? - fail("Average is not supported for ObjectId") - } catch (expected: IllegalArgumentException) {} + @Test + fun queriesAverage() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() - // isEmpty - try { + assertFailsWith("Average is not supported for ObjectId") { + realm.where().average("id") + } + } + + @Test + fun queriesIsEmpty() { + realm.beginTransaction() + realm.createObject().id = ObjectId(generateObjectIdHexString(1)) + realm.createObject().id = null + realm.createObject().id = ObjectId(generateObjectIdHexString(10)) + realm.createObject().id = ObjectId(generateObjectIdHexString(0)) + realm.commitTransaction() + + assertFailsWith("isEmpty is not supported for ObjectId") { realm.where().isEmpty("id") - fail("isEmpty is not supported for ObjectId") - } catch (expected: IllegalArgumentException) {} + } + } + + @Test + fun linkedQuery() { + val objectIdArray = arrayOf(null, ObjectId(), ObjectId(), ObjectId()) + + realm.executeTransaction { realm -> + for (objectId in objectIdArray) { + val objectIdObj = realm.createObject(objectId) + realm.createObject().linkedObjectId = objectIdObj + } + } + + for (objectId in objectIdArray) { + val results = realm.where().equalTo("linkedObjectId.id", objectId).findAll() + assertEquals(1, results.size) + assertEquals(objectId, results.first()?.linkedObjectId?.id) + } } } diff --git a/realm/realm-library/src/androidTest/kotlin/io/realm/UUIDTests.kt b/realm/realm-library/src/androidTest/kotlin/io/realm/UUIDTests.kt new file mode 100644 index 0000000000..d75c9cfcbe --- /dev/null +++ b/realm/realm-library/src/androidTest/kotlin/io/realm/UUIDTests.kt @@ -0,0 +1,613 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ +package io.realm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.annotations.PrimaryKey +import io.realm.annotations.Required +import io.realm.exceptions.RealmException +import io.realm.exceptions.RealmPrimaryKeyConstraintException +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import io.realm.rule.TestRealmConfigurationFactory +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* +import kotlin.test.assertFailsWith + +open class UUIDPrimaryKeyRequired + : RealmObject() { + @PrimaryKey + @Required + var id: UUID? = null + var name: String = "" + var anotherId: UUID? = null +} + +open class UUIDPrimaryKeyNotRequired + : RealmObject() { + @PrimaryKey + var id: UUID? = null + var name: String = "" +} + +open class UUIDAndString + : RealmObject() { + var id: UUID? = null + var name: String = "" +} + +open class UUIDRequiredRealmList + : RealmObject() { + var id: Long = 0 + + @Required + var ids: RealmList = RealmList() + var name: String = "" +} + +open class UUIDOptionalRealmList + : RealmObject() { + var id: Long = 0 + + var ids: RealmList = RealmList() + var name: String = "" +} + +open class LinkedUUID : RealmObject() { + var linkedUUID: UUIDPrimaryKeyNotRequired? = null +} + +@RunWith(AndroidJUnit4::class) +class UUIDTests { + private lateinit var realmConfiguration: RealmConfiguration + private lateinit var realm: Realm + + @Rule + @JvmField + val configFactory = TestRealmConfigurationFactory() + + init { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + + @Before + fun setUp() { + realmConfiguration = configFactory + .createConfigurationBuilder() + .schema(UUIDPrimaryKeyRequired::class.java, + UUIDPrimaryKeyNotRequired::class.java, + UUIDAndString::class.java, + UUIDRequiredRealmList::class.java, + UUIDOptionalRealmList::class.java, + LinkedUUID::class.java) + .build() + + realm = Realm.getInstance(realmConfiguration) + } + + @After + fun tearDown() { + realm.close() + } + + @Test + fun copyToRealm() { + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + + val value = UUIDPrimaryKeyRequired() + value.id = uuid1 + value.anotherId = uuid2 + value.name = "Foo" + + realm.beginTransaction() + val obj = realm.copyToRealm(value) + realm.commitTransaction() + + assertEquals(uuid1, obj.id) + assertEquals(uuid2, obj.anotherId) + assertEquals("Foo", obj.name) + } + + @Test + fun copyFromRealm() { + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + + realm.beginTransaction() + val obj = realm.createObject(uuid1) + obj.anotherId = uuid2 + obj.name = "Foo" + realm.commitTransaction() + + val copy = realm.copyFromRealm(obj) + + assertEquals(uuid1, copy.id) + assertEquals(uuid2, copy.anotherId) + assertEquals("Foo", copy.name) + } + + @Test + fun copyToRealmOrUpdate() { + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + val uuid3 = UUID.randomUUID() + + realm.executeTransaction { realm -> + val obj = realm.createObject(uuid1) + obj.anotherId = uuid2 + obj.name = "Foo" + } + + val value = UUIDPrimaryKeyRequired() + value.id = uuid1 + value.name = "Bar" + value.anotherId = uuid3 + + realm.beginTransaction() + val obj = realm.copyToRealmOrUpdate(value) + realm.commitTransaction() + + assertEquals(uuid1, obj.id) + assertEquals(uuid3, obj.anotherId) + assertEquals("Bar", obj.name) + } + + @Test + fun insert() { + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + + realm.executeTransaction { realm -> + val value = UUIDPrimaryKeyRequired() + value.id = uuid1 + value.name = "Foo" + value.anotherId = uuid2 + + realm.insert(value) + } + + val obj = realm.where().findFirst() + + assertNotNull(obj) + assertEquals(uuid1, obj!!.id) + assertEquals(uuid2, obj.anotherId) + assertEquals("Foo", obj.name) + } + + @Test + fun insertOrUpdate() { + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + val uuid3 = UUID.randomUUID() + + realm.executeTransaction { realm -> + val value = realm.createObject(uuid1) + value.name = "Foo" + value.anotherId = uuid2 + } + + realm.executeTransaction { realm -> + val obj = UUIDPrimaryKeyRequired() + obj.id = uuid1 + obj.anotherId = uuid3 + obj.name = "Bar" + realm.insertOrUpdate(obj) + } + + val all = realm.where().findAll() + + assertEquals(1, all.size) + assertEquals(uuid1, all[0]!!.id) + assertEquals(uuid3, all[0]!!.anotherId) + assertEquals("Bar", all[0]!!.name) + } + + @Test + fun frozen() { + val uuid1 = UUID.randomUUID() + + realm.beginTransaction() + val obj = realm.createObject(uuid1) + obj.name = "foo" + realm.commitTransaction() + + val frozen = obj.freeze() + assertEquals(uuid1, frozen.id) + assertEquals("foo", frozen.name) + } + + @Test + fun requiredPK() { + val uuid1 = UUID.randomUUID() + + realm.beginTransaction() + + assertFailsWith { + realm.createObject() + } + + val obj = realm.createObject(uuid1) + obj.name = "foo" + + realm.commitTransaction() + + val result = realm.where().equalTo("id", uuid1).findFirst() + assertNotNull(result) + assertEquals("foo", result?.name) + } + + @Test + fun nullablePK() { + assertFailsWith { + realm.createObject() + } + + realm.beginTransaction() + val obj = realm.createObject(null) + obj.name = "foo" + realm.commitTransaction() + + val result = realm.where().equalTo("id", null as UUID?).findFirst() + assertNotNull(result) + assertEquals("foo", result!!.name) + } + + + @Test + fun requiredRealmList() { + realm.beginTransaction() + val obj = realm.createObject() + + assertFailsWith("It should not be possible to add nullable elements to a required RealmList") { + obj.ids.add(null) + } + } + + @Test + fun optionalRealmList() { + val uuid1 = UUID.randomUUID() + + realm.beginTransaction() + val obj = realm.createObject() + obj.ids.add(null) + obj.ids.add(uuid1) + realm.commitTransaction() + + assertEquals(2, realm.where().findFirst()?.ids?.size) + } + + @Test + fun linkQueryNotSupported() { + val uuid1 = UUID.randomUUID() + + assertFailsWith("It should not be possible to perform link query on UUID") { + realm.where().greaterThan("ids", uuid1).findAll() + } + + realm.beginTransaction() + val obj = realm.createObject() + realm.cancelTransaction() + + assertFailsWith { + obj.ids.where().equalTo("ids", uuid1).findAll() + } + } + + @Test + fun duplicatePK() { + val uuid1 = UUID.randomUUID() + + realm.beginTransaction() + realm.createObject(uuid1) + + assertFailsWith("It should throw for duplicate PK usage") { + realm.createObject(uuid1) + } + + realm.cancelTransaction() + } + + @Test + fun sort() { + val uuid1 = UUID.fromString("017ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid2 = UUID.fromString("027ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid3 = UUID.fromString("037ba5ca-aa12-4afa-9219-e20cc3018599") + + realm.beginTransaction() + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + realm.createObject().id = uuid2 + realm.commitTransaction() + + var all = realm.where().sort("id", Sort.ASCENDING).findAll() + + assertEquals(3, all.size) + assertEquals(uuid1, all[0]!!.id) + assertEquals(uuid2, all[1]!!.id) + assertEquals(uuid3, all[2]!!.id) + + all = realm.where().sort("id", Sort.DESCENDING).findAll() + + assertEquals(3, all.size) + assertEquals(uuid3, all[0]!!.id) + assertEquals(uuid2, all[1]!!.id) + assertEquals(uuid1, all[2]!!.id) + } + + @Test + fun distinct() { + val uuid1 = UUID.fromString("017ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid2 = UUID.fromString("027ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid3 = UUID.fromString("037ba5ca-aa12-4afa-9219-e20cc3018599") + + realm.beginTransaction() + realm.createObject().id = uuid2 + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid1 + realm.createObject().id = uuid1 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid3 + realm.createObject().id = null + realm.commitTransaction() + + val all = realm.where().distinct("id").sort("id", Sort.ASCENDING).findAll() + + assertEquals(4, all.size) + assertNull(all[0]!!.id) + assertEquals(uuid1, all[1]!!.id) + assertEquals(uuid2, all[2]!!.id) + assertEquals(uuid3, all[3]!!.id) + + } + + @Test + fun queries() { + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + val uuid3 = UUID.randomUUID() + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + assertEquals(4, realm.where().count()) + } + + @Test + fun queriesNotEqualTo() { + val uuid1 = UUID.fromString("017ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid2 = UUID.fromString("027ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid3 = UUID.fromString("037ba5ca-aa12-4afa-9219-e20cc3018599") + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + val all = realm.where() + .notEqualTo("id", uuid2) + .sort("id", Sort.ASCENDING) + .findAll() + + assertEquals(3, all.size) + assertNull(all[0]!!.id) + assertEquals(uuid1, all[1]!!.id) + assertEquals(uuid3, all[2]!!.id) + } + + @Test + fun queriesGreaterThanOrEqualTo() { + val uuid1 = UUID.fromString("017ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid2 = UUID.fromString("027ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid3 = UUID.fromString("037ba5ca-aa12-4afa-9219-e20cc3018599") + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + val all = realm.where() + .greaterThanOrEqualTo("id", uuid2) + .sort("id", Sort.ASCENDING) + .findAll() + + assertEquals(2, all.size) + assertEquals(uuid2, all[0]!!.id) + assertEquals(uuid3, all[1]!!.id) + } + + @Test + fun queriesGreaterThan() { + val uuid1 = UUID.fromString("017ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid2 = UUID.fromString("027ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid3 = UUID.fromString("037ba5ca-aa12-4afa-9219-e20cc3018599") + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + val all = realm.where() + .greaterThan("id", uuid2) + .sort("id", Sort.ASCENDING) + .findAll() + + assertEquals(1, all.size) + assertEquals(uuid3, all[0]!!.id) + } + + @Test + fun queriesLessThanOrEqualTo() { + val uuid1 = UUID.fromString("017ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid2 = UUID.fromString("027ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid3 = UUID.fromString("037ba5ca-aa12-4afa-9219-e20cc3018599") + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + val all = realm.where() + .lessThanOrEqualTo("id", uuid2) + .sort("id", Sort.ASCENDING) + .findAll() + + assertEquals(2, all.size) + assertEquals(uuid1, all[0]!!.id) + assertEquals(uuid2, all[1]!!.id) + } + + @Test + fun queriesLessThan() { + val uuid1 = UUID.fromString("017ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid2 = UUID.fromString("027ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid3 = UUID.fromString("037ba5ca-aa12-4afa-9219-e20cc3018599") + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + val all = realm.where() + .lessThan("id", uuid2) + .sort("id", Sort.ASCENDING) + .findAll() + + assertEquals(1, all.size) + assertEquals(uuid1, all[0]!!.id) + } + + @Test + fun queriesIsNull() { + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + val uuid3 = UUID.randomUUID() + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + val all = realm.where() + .isNull("id") + .findAll() + + assertEquals(1, all.size) + assertNull(all[0]!!.id) + } + + @Test + fun queriesIsNotNull() { + val uuid1 = UUID.fromString("017ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid2 = UUID.fromString("027ba5ca-aa12-4afa-9219-e20cc3018599") + val uuid3 = UUID.fromString("037ba5ca-aa12-4afa-9219-e20cc3018599") + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + val all = realm.where() + .isNotNull("id") + .sort("id", Sort.ASCENDING) + .findAll() + + assertEquals(3, all.size) + assertEquals(uuid1, all[0]!!.id) + assertEquals(uuid2, all[1]!!.id) + assertEquals(uuid3, all[2]!!.id) + } + + @Test + fun queriesAverage() { + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + val uuid3 = UUID.randomUUID() + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + assertFailsWith("Average is not supported for UUID") { + realm.where().average("id") + } + } + + @Test + fun queriesIsEmpty() { + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + val uuid3 = UUID.randomUUID() + + realm.executeTransaction { realm -> + realm.createObject().id = uuid2 + realm.createObject().id = null + realm.createObject().id = uuid3 + realm.createObject().id = uuid1 + } + + assertFailsWith("isEmpty is not supported for UUID") { + realm.where().isEmpty("id") + } + } + + @Test + fun linkedQuery() { + val uuidArray = arrayOf(null, UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()) + + realm.executeTransaction { realm -> + for (uuid in uuidArray) { + val uuidObj = realm.createObject(uuid) + realm.createObject().linkedUUID = uuidObj + } + } + + for (uuid in uuidArray) { + val results = realm.where().equalTo("linkedUUID.id", uuid).findAll() + assertEquals(1, results.size) + assertEquals(uuid, results.first()?.linkedUUID?.id) + } + } +} diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp index e12b5fa6f1..f95b23d44f 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp @@ -509,7 +509,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetDecimal128(JNIEnv* } JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddObjectId(JNIEnv* env, jclass, jlong list_ptr, - jstring j_value) + jstring j_value) { try { @@ -520,7 +520,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddObjectId(JNIEnv* e } JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertObjectId(JNIEnv* env, jclass, jlong list_ptr, - jlong pos, jstring j_value) + jlong pos, jstring j_value) { try { JStringAccessor value(env, j_value); @@ -530,7 +530,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertObjectId(JNIEnv } JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetObjectId(JNIEnv* env, jclass, jlong list_ptr, jlong pos, - jstring j_value) + jstring j_value) { try { JStringAccessor value(env, j_value); @@ -539,6 +539,37 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetObjectId(JNIEnv* e CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeAddUUID(JNIEnv* env, jclass, jlong list_ptr, + jstring j_value) +{ + try { + JStringAccessor value(env, j_value); + add_value(env, list_ptr, Any(UUID(StringData(value).data()))); + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeInsertUUID(JNIEnv* env, jclass, jlong list_ptr, + jlong pos, jstring j_value) +{ + try { + JStringAccessor value(env, j_value); + insert_value(env, list_ptr, pos, Any(UUID(StringData(value).data()))); + } + CATCH_STD(); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_OsList_nativeSetUUID(JNIEnv* env, jclass, jlong list_ptr, jlong pos, + jstring j_value) +{ + try { + JStringAccessor value(env, j_value); + set_value(env, list_ptr, pos, Any(UUID(StringData(value).data()))); + } + CATCH_STD() +} + JNIEXPORT jobject JNICALL Java_io_realm_internal_OsList_nativeGetValue(JNIEnv* env, jclass, jlong list_ptr, jlong pos) { try { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp index 8582f220f7..f2278be265 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp @@ -168,7 +168,7 @@ static inline Obj do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm TableRef table = TBL_REF(table_ref_ptr); ColKey col_key(pk_column_key); shared_realm->verify_in_write(); // throws - if (is_pk_null && !COL_NULLABLE(env, table, pk_column_key)) { + if (is_pk_null && !COL_NULLABLE(env, table, pk_column_key)) { // throws return Obj(); } @@ -196,7 +196,7 @@ static inline Obj do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm ColKey col_key(pk_column_key); shared_realm->verify_in_write(); // throws JStringAccessor str_accessor(env, pk_value); // throws - if (!pk_value && !COL_NULLABLE(env, table, pk_column_key)) { + if (!pk_value && !COL_NULLABLE(env, table, pk_column_key)) { // throws return Obj(); } @@ -215,14 +215,14 @@ static inline Obj do_create_row_with_primary_key(JNIEnv* env, jlong shared_realm } static inline Obj do_create_row_with_object_id_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ref_ptr, - jlong pk_column_key, jstring pk_value) + jlong pk_column_key, jstring pk_value) { auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); TableRef table = TBL_REF(table_ref_ptr); ColKey col_key(pk_column_key); shared_realm->verify_in_write(); // throws JStringAccessor str_accessor(env, pk_value); // throws - if (!pk_value && !COL_NULLABLE(env, table, pk_column_key)) { + if (!pk_value && !COL_NULLABLE(env, table, pk_column_key)) { // throws return Obj(); } @@ -243,6 +243,35 @@ static inline Obj do_create_row_with_object_id_primary_key(JNIEnv* env, jlong sh } } +static inline Obj do_create_row_with_uuid_primary_key(JNIEnv* env, jlong shared_realm_ptr, jlong table_ref_ptr, + jlong pk_column_key, jstring pk_value) +{ + auto& shared_realm = *(reinterpret_cast(shared_realm_ptr)); + TableRef table = TBL_REF(table_ref_ptr); + ColKey col_key(pk_column_key); + shared_realm->verify_in_write(); // throws + JStringAccessor str_accessor(env, pk_value); // throws + if (!pk_value && !COL_NULLABLE(env, table, pk_column_key)) { //throws + return Obj(); + } + + if (pk_value) { + auto uuid = UUID(StringData(str_accessor).data()); + if (bool(table->find_first_uuid(col_key, uuid))) { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, + util::format(PK_EXCEPTION_MSG_FORMAT, str_accessor.operator std::string())); + } + + return table->create_object_with_primary_key(uuid); + } + else { + if (bool(table->find_first_null(col_key))) { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, util::format(PK_EXCEPTION_MSG_FORMAT, "'null'")); + } + return table->create_object_with_primary_key(realm::util::Optional()); + } +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeGetFinalizerPtr(JNIEnv*, jclass) { @@ -365,7 +394,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithStrin } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithObjectIdPrimaryKey( - JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) { try { Obj obj = do_create_row_with_object_id_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value); @@ -376,7 +405,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithObjec } JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWithObjectIdPrimaryKey( - JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) { try { Obj obj = do_create_row_with_object_id_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value); @@ -388,6 +417,32 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWit return 0; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateRowWithUUIDPrimaryKey( + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) +{ + try { + Obj obj = do_create_row_with_uuid_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value); + return (jlong)(obj.get_key().value); + } + CATCH_STD() + return realm::npos; +} + +JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateNewObjectWithUUIDPrimaryKey( + JNIEnv* env, jclass, jlong shared_realm_ptr, jlong table_ref_ptr, jlong pk_column_ndx, jstring pk_value) +{ + try { + Obj obj = do_create_row_with_uuid_primary_key(env, shared_realm_ptr, table_ref_ptr, pk_column_ndx, pk_value); + if (bool(obj)) { + return reinterpret_cast(new Obj(obj)); + } else { + THROW_JAVA_EXCEPTION(env, PK_CONSTRAINT_EXCEPTION_CLASS, "Invalid Object returned from 'do_create_row_with_uuid_primary_key'"); + } + } + CATCH_STD() + return 0; +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_OsObject_nativeCreateEmbeddedObject( JNIEnv* env, jclass, jlong j_parent_table_ptr, jlong j_parent_object_key, jlong j_parent_column_key) { diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp index f78ef4b89f..94e5ba78ba 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsObjectStore.cpp @@ -61,7 +61,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsObjectStore_nativeSetPrimaryKeyF // Check valid column type auto field_type = table->get_column_type(pk_column_col); - if (field_type != type_Int && field_type != type_String && field_type != type_ObjectId) { + if (field_type != type_Int && field_type != type_String && field_type != type_ObjectId && field_type != type_UUID) { THROW_JAVA_EXCEPTION( env, JavaExceptionDef::IllegalArgument, format("Field '%1' is not a valid primary key type.", StringData(pk_field_name_accessor))); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index 0515a14158..0909d3c635 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -395,6 +395,14 @@ JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetObjectId(JNIEnv update_objects(env, native_ptr, j_field_name, value); } +JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetUUID(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jstring j_value) +{ + JStringAccessor data(env, j_value); + UUID uuid = UUID(StringData(data).data()); + JavaValue value(uuid); + update_objects(env, native_ptr, j_field_name, value); +} + JNIEXPORT void JNICALL Java_io_realm_internal_OsResults_nativeSetObject(JNIEnv* env, jclass, jlong native_ptr, jstring j_field_name, jlong row_ptr) { JavaValue value(reinterpret_cast(row_ptr)); diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp index f76eab7ef1..a398494a7c 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Property.cpp @@ -55,7 +55,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Property_nativeCreatePersistedPro throw std::invalid_argument( "This field cannot be indexed - Only String/byte/short/int/long/boolean/Date fields are supported."); } - if (to_bool(is_primary) && p_type != PropertyType::Int && p_type != PropertyType::String && p_type != PropertyType::ObjectId) { + if (to_bool(is_primary) && p_type != PropertyType::Int && p_type != PropertyType::String && p_type != PropertyType::ObjectId && p_type != PropertyType::UUID) { std::string typ = property->type_string(); throw std::invalid_argument("Invalid primary key type: " + typ); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index 65cb0d36ac..b7342d7baa 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -45,7 +45,8 @@ inline static bool is_allowed_to_index(JNIEnv* env, DataType column_type) || column_type == type_Bool || column_type == type_Timestamp || column_type == type_OldDateTime - || column_type == type_ObjectId) { + || column_type == type_ObjectId + || column_type == type_UUID) { return true; } @@ -600,8 +601,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetDecimal128(JNIEnv* } JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetObjectId(JNIEnv* env, jclass, jlong nativeTableRefPtr, - jlong columnKey, jlong rowKey, jstring j_value, - jboolean isDefault) + jlong columnKey, jlong rowKey, jstring j_value, + jboolean isDefault) { TableRef table = TBL_REF(nativeTableRefPtr); if (!TYPE_VALID(env, table, columnKey, type_ObjectId)) { @@ -614,6 +615,21 @@ JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetObjectId(JNIEnv* en CATCH_STD() } +JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetUUID(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jlong rowKey, jstring j_value, + jboolean isDefault) +{ + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_UUID)) { + return; + } + try { + JStringAccessor value(env, j_value); + table->get_object(ObjKey(rowKey)).set(ColKey(columnKey), UUID(StringData(value).data()), B(isDefault)); + } + CATCH_STD() +} + JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeSetNull(JNIEnv* env, jclass, jlong nativeTableRefPtr, jlong columnKey, jlong rowKey, @@ -887,7 +903,7 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstDecimal128(J } JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstObjectId(JNIEnv* env, jclass, jlong nativeTableRefPtr, - jlong columnKey, jstring j_value) + jlong columnKey, jstring j_value) { TableRef table = TBL_REF(nativeTableRefPtr); if (!TYPE_VALID(env, table, columnKey, type_ObjectId)) { @@ -903,6 +919,23 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstObjectId(JNI return -1; } +JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstUUID(JNIEnv* env, jclass, jlong nativeTableRefPtr, + jlong columnKey, jstring j_value) +{ + TableRef table = TBL_REF(nativeTableRefPtr); + if (!TYPE_VALID(env, table, columnKey, type_UUID)) { + return -1; + } + + try { + JStringAccessor value(env, j_value); // throws + UUID uuid = UUID(StringData(value).data()); + return to_jlong_or_not_found(table->find_first_uuid(ColKey(columnKey), uuid)); + } + CATCH_STD() + return -1; +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_Table_nativeFindFirstNull(JNIEnv* env, jclass, jlong nativeTableRefPtr, jlong columnKey) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp index 7bca0a4c02..8483c3efca 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_TableQuery.cpp @@ -1226,6 +1226,133 @@ static void TableQuery_StringPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongA CATCH_STD() } +// UUID +enum UUIDPredicate { UUIDEqual, UUIDNotEqual, UUIDLess, UUIDLessEqual, UUIDGreater, UUIDGreaterEqual }; +static void TableQuery_UUIDPredicate(JNIEnv* env, jlong nativeQueryPtr, jlongArray columnKeys, + jlongArray tablePointers, jstring j_data, UUIDPredicate predicate) +{ + try { + JStringAccessor data(env, j_data); + JLongArrayAccessor table_arr(env, tablePointers); + JLongArrayAccessor col_key_arr(env, columnKeys); + jsize arr_len = col_key_arr.size(); + + UUID uuid = UUID(StringData(data).data()); + if (arr_len == 1) { + if (!TYPE_VALID(env, Q(nativeQueryPtr)->get_table(), col_key_arr[0], type_UUID)) { + return; + } + + switch (predicate) { + case UUIDEqual: + Q(nativeQueryPtr)->equal(ColKey(col_key_arr[0]), uuid); + break; + case UUIDNotEqual: + Q(nativeQueryPtr)->not_equal(ColKey(col_key_arr[0]), uuid); + break; + case UUIDLess: + Q(nativeQueryPtr)->less(ColKey(col_key_arr[0]), uuid); + break; + case UUIDLessEqual: + Q(nativeQueryPtr)->less_equal(ColKey(col_key_arr[0]), uuid); + break; + case UUIDGreater: + Q(nativeQueryPtr)->greater(ColKey(col_key_arr[0]), uuid); + break; + case UUIDGreaterEqual: + Q(nativeQueryPtr)->greater_equal(ColKey(col_key_arr[0]), uuid); + break; + } + } + else { + LinkChain linkChain = getTableForLinkQuery(nativeQueryPtr, table_arr, col_key_arr); + switch (predicate) { + case UUIDEqual: + Q(nativeQueryPtr) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) == + uuid); + break; + case UUIDNotEqual: + Q(nativeQueryPtr) + ->and_query(linkChain.column(ColKey(col_key_arr[arr_len - 1])) != + uuid); + break; + case UUIDLess: + Q(nativeQueryPtr) + ->and_query(numeric_link_less(linkChain, col_key_arr[arr_len - 1], uuid)); + break; + case UUIDLessEqual: + Q(nativeQueryPtr) + ->and_query(numeric_link_lessequal(linkChain, col_key_arr[arr_len - 1], uuid)); + break; + case UUIDGreater: + Q(nativeQueryPtr) + ->and_query(numeric_link_greater(linkChain, col_key_arr[arr_len - 1], uuid)); + break; + case UUIDGreaterEqual: + Q(nativeQueryPtr) + ->and_query(numeric_link_greaterequal(linkChain, col_key_arr[arr_len - 1], uuid)); + break; + } + } + } + CATCH_STD() +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqualUUID(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring j_data) +{ + TableQuery_UUIDPredicate(env, nativeQueryPtr, columnKeys, tablePointers, j_data, UUIDEqual); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeNotEqualUUID(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring j_data) +{ + TableQuery_UUIDPredicate(env, nativeQueryPtr, columnKeys, tablePointers, j_data, UUIDNotEqual); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessUUID(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring j_data) +{ + TableQuery_UUIDPredicate(env, nativeQueryPtr, columnKeys, tablePointers, j_data, UUIDLess); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeLessEqualUUID(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring j_data) +{ + TableQuery_UUIDPredicate(env, nativeQueryPtr, columnKeys, tablePointers, j_data, UUIDLessEqual); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterUUID(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring j_data) +{ + TableQuery_UUIDPredicate(env, nativeQueryPtr, columnKeys, tablePointers, j_data, UUIDGreater); +} + +JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeGreaterEqualUUID(JNIEnv* env, jobject, + jlong nativeQueryPtr, + jlongArray columnKeys, + jlongArray tablePointers, + jstring j_data) +{ + TableQuery_UUIDPredicate(env, nativeQueryPtr, columnKeys, tablePointers, j_data, UUIDGreaterEqual); +} + JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeEqual__J_3J_3JLjava_lang_String_2Z( JNIEnv* env, jobject, jlong nativeQueryPtr, jlongArray columnKeys, jlongArray tablePointers, jstring value, jboolean caseSensitive) @@ -1786,6 +1913,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en case type_Timestamp: case type_Decimal: case type_ObjectId: + case type_UUID: Q(nativeQueryPtr)->equal(ColKey(column_idx), realm::null()); break; default: @@ -1828,6 +1956,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNull(JNIEnv* en case type_ObjectId: pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); break; + case type_UUID: + pQuery->and_query(linkChain.column(ColKey(column_idx)) == realm::null()); + break; default: REALM_UNREACHABLE(); } @@ -1874,6 +2005,7 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* case type_Timestamp: case type_Decimal: case type_ObjectId: + case type_UUID: pQuery->not_equal(ColKey(column_idx), realm::null()); break; default: @@ -1917,6 +2049,9 @@ JNIEXPORT void JNICALL Java_io_realm_internal_TableQuery_nativeIsNotNull(JNIEnv* case type_ObjectId: pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); break; + case type_UUID: + pQuery->and_query(linkChain.column(ColKey(column_idx)) != realm::null()); + break; default: REALM_UNREACHABLE(); } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp index 6506ca5216..f3a97996a7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_UncheckedRow.cpp @@ -447,8 +447,8 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetDecimal128(J } JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetObjectId(JNIEnv* env, jobject, - jlong nativeRowPtr, - jlong columnKey) + jlong nativeRowPtr, + jlong columnKey) { if (!ROW_VALID(env, OBJ(nativeRowPtr))) { return nullptr; @@ -477,6 +477,37 @@ JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetObjectId(JNI CATCH_STD() } +JNIEXPORT jstring JNICALL Java_io_realm_internal_UncheckedRow_nativeGetUUID(JNIEnv* env, jobject, + jlong nativeRowPtr, + jlong columnKey) +{ + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { + return nullptr; + } + + try { + UUID uuid = OBJ(nativeRowPtr)->get(ColKey(columnKey)); + return to_jstring(env, uuid.to_string().data()); + } + CATCH_STD() + return nullptr; +} + +JNIEXPORT void JNICALL Java_io_realm_internal_UncheckedRow_nativeSetUUID(JNIEnv* env, jobject, + jlong nativeRowPtr, jlong columnKey, + jstring j_value) +{ + if (!ROW_VALID(env, OBJ(nativeRowPtr))) { + return; + } + + try { + JStringAccessor value(env, j_value); + OBJ(nativeRowPtr)->set(ColKey(columnKey), UUID(StringData(value).data())); + } + CATCH_STD() +} + JNIEXPORT jlong JNICALL Java_io_realm_internal_UncheckedRow_nativeCreateEmbeddedObject(JNIEnv* env, jobject, jlong j_obj_ptr, jlong j_column_key) diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp index 57bd430a55..b778c099a7 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsObjectBuilder.cpp @@ -155,6 +155,18 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_native CATCH_STD() } +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddUUID + (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jstring j_data) +{ + try { + JStringAccessor data(env, j_data); + UUID uuid = UUID(StringData(data).data()); + const JavaValue value(uuid); + add_property(data_ptr, column_key, value); + } + CATCH_STD() +} + JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddObject (JNIEnv* env, jclass, jlong data_ptr, jlong column_key, jlong row_ptr) { @@ -396,3 +408,15 @@ JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_native } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_internal_objectstore_OsObjectBuilder_nativeAddUUIDListItem + (JNIEnv* env, jclass, jlong list_ptr, jstring j_data) +{ + try { + JStringAccessor data(env, j_data); + UUID uuid = UUID(StringData(data).data()); + const JavaValue value(uuid); + add_list_element(list_ptr, value); + } + CATCH_STD() +} diff --git a/realm/realm-library/src/main/cpp/java_accessor.hpp b/realm/realm-library/src/main/cpp/java_accessor.hpp index f164cf393e..f485113f8c 100644 --- a/realm/realm-library/src/main/cpp/java_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_accessor.hpp @@ -195,6 +195,10 @@ class JavaAccessorContext { { return JavaClassGlobalDef::new_object_id(m_env, v); } + util::Any box(UUID v) const + { + return JavaClassGlobalDef::new_uuid(m_env, v); + } util::Any box(bool v) const { return _impl::JavaClassGlobalDef::new_boolean(m_env, v); @@ -235,6 +239,10 @@ class JavaAccessorContext { { return v ? _impl::JavaClassGlobalDef::new_object_id(m_env, v.value()) : nullptr; } + util::Any box(util::Optional v) const + { + return v ? _impl::JavaClassGlobalDef::new_uuid(m_env, v.value()) : nullptr; + } util::Any box(Obj) const { REALM_TERMINATE("not supported"); @@ -423,6 +431,12 @@ inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreateP return v.has_value() ? util::make_optional(util::any_cast(v)) : util::none; } +template <> +inline util::Optional JavaAccessorContext::unbox(util::Any& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? util::make_optional(util::any_cast(v)) : util::none; +} + template <> inline Obj JavaAccessorContext::unbox(util::Any&, CreatePolicy, ObjKey) const { diff --git a/realm/realm-library/src/main/cpp/java_class_global_def.cpp b/realm/realm-library/src/main/cpp/java_class_global_def.cpp index 1fd6c45ffd..73a7a1b901 100644 --- a/realm/realm-library/src/main/cpp/java_class_global_def.cpp +++ b/realm/realm-library/src/main/cpp/java_class_global_def.cpp @@ -58,3 +58,9 @@ jobject JavaClassGlobalDef::new_object_id(JNIEnv* env, const ObjectId& objectId) static jni_util::JavaMethod init(env, instance()->m_bson_object_id, "", "(Ljava/lang/String;)V"); return env->NewObject(instance()->m_bson_object_id, init, to_jstring(env, objectId.to_string().data())); } + +jobject JavaClassGlobalDef::new_uuid(JNIEnv* env, const UUID& uuid) +{ + static jni_util::JavaMethod from_string(env, instance()->m_java_util_uuid, "fromString", "(Ljava/lang/String;)Ljava/util/UUID;", true); + return env->CallStaticObjectMethod(instance()->m_java_util_uuid, from_string, to_jstring(env, uuid.to_string().data())); +} diff --git a/realm/realm-library/src/main/cpp/java_class_global_def.hpp b/realm/realm-library/src/main/cpp/java_class_global_def.hpp index cdc979bb72..b003516b1f 100644 --- a/realm/realm-library/src/main/cpp/java_class_global_def.hpp +++ b/realm/realm-library/src/main/cpp/java_class_global_def.hpp @@ -55,6 +55,7 @@ class JavaClassGlobalDef { , m_realm_notifier(env, "io/realm/internal/RealmNotifier", false) , m_bson_decimal128(env, "org/bson/types/Decimal128", false) , m_bson_object_id(env, "org/bson/types/ObjectId", false) + , m_java_util_uuid(env, "java/util/UUID", false) #if REALM_ENABLE_SYNC , m_network_transport_response(env, "io/realm/internal/objectstore/OsJavaNetworkTransport$Response", false) #endif @@ -73,6 +74,7 @@ class JavaClassGlobalDef { jni_util::JavaClass m_realm_notifier; jni_util::JavaClass m_bson_decimal128; jni_util::JavaClass m_bson_object_id; + jni_util::JavaClass m_java_util_uuid; #if REALM_ENABLE_SYNC jni_util::JavaClass m_network_transport_response; @@ -171,6 +173,8 @@ class JavaClassGlobalDef { static jobject new_object_id(JNIEnv* env, const ObjectId& objectId); + static jobject new_uuid(JNIEnv* env, const UUID& uuid); + // io.realm.internal.OsSharedRealm.SchemaChangedCallback inline static const jni_util::JavaClass& shared_realm_schema_change_callback() { diff --git a/realm/realm-library/src/main/cpp/java_object_accessor.hpp b/realm/realm-library/src/main/cpp/java_object_accessor.hpp index 2c5186d34b..a0ea6098a9 100644 --- a/realm/realm-library/src/main/cpp/java_object_accessor.hpp +++ b/realm/realm-library/src/main/cpp/java_object_accessor.hpp @@ -42,6 +42,7 @@ using namespace realm::_impl; X(Double) \ X(Date) \ X(ObjectId) \ + X(UUID) \ X(Decimal) \ X(Mixed) \ X(Binary) \ @@ -81,6 +82,7 @@ template <> struct JavaValueTypeRepr { using Type template <> struct JavaValueTypeRepr { using Type = ObjectId; }; template <> struct JavaValueTypeRepr { using Type = Decimal128; }; template <> struct JavaValueTypeRepr { using Type = Mixed; }; +template <> struct JavaValueTypeRepr { using Type = UUID; }; template <> struct JavaValueTypeRepr { using Type = OwnedBinaryData; }; template <> struct JavaValueTypeRepr { using Type = Obj*; }; template <> struct JavaValueTypeRepr { using Type = std::vector; }; @@ -237,6 +239,11 @@ struct JavaValue { return get_as(); } + auto& get_uuid() const noexcept + { + return get_as(); + } + auto& get_decimal128() const noexcept { return get_as(); @@ -298,6 +305,8 @@ struct JavaValue { return std::string(ss.str()); case JavaValueType::ObjectId: return get_object_id().to_string(); + case JavaValueType::UUID: + return get_uuid().to_string(); case JavaValueType::Decimal: return get_decimal128().to_string(); case JavaValueType::Mixed: @@ -577,6 +586,12 @@ inline ObjectId JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) con return v.has_value() ? v.get_object_id() : ObjectId(); } +template <> +inline UUID JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? v.get_uuid() : UUID(); +} + template <> inline Obj JavaContext::unbox(JavaValue const& v, CreatePolicy policy, ObjKey current_row) const { @@ -619,6 +634,12 @@ inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePol return v.has_value() ? util::make_optional(v.get_object_id()) : util::none; } +template <> +inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const +{ + return v.has_value() ? util::make_optional(v.get_uuid()) : util::none; +} + template <> inline util::Optional JavaContext::unbox(JavaValue const& v, CreatePolicy, ObjKey) const { diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java index d4a86aeeab..9c15eba7f1 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealmObject.java @@ -22,6 +22,7 @@ import java.util.Date; import java.util.Iterator; import java.util.Locale; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -120,6 +121,8 @@ public E get(String fieldName) { return (E) proxyState.getRow$realm().getDecimal128(columnKey); case OBJECT_ID: return (E) proxyState.getRow$realm().getObjectId(columnKey); + case UUID: + return (E) proxyState.getRow$realm().getUUID(columnKey); case OBJECT: return (E) getObject(fieldName); case LIST: @@ -361,6 +364,25 @@ public ObjectId getObjectId(String fieldName) { } } + /** + * Returns the {@code UUID} value for a given field. + * + * @param fieldName the name of the field. + * @return the UUID value. + * @throws IllegalArgumentException if field name doesn't exist or it doesn't contain UUID. + */ + public UUID getUUID(String fieldName) { + proxyState.getRealm$realm().checkIfValid(); + + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + checkFieldType(fieldName, columnKey, RealmFieldType.UUID); + if (proxyState.getRow$realm().isNull(columnKey)) { + return null; + } else { + return proxyState.getRow$realm().getUUID(columnKey); + } + } + /** * Returns the object being linked to from this field. * @@ -458,6 +480,8 @@ private RealmFieldType classToRealmType(Class primitiveType) { return RealmFieldType.DECIMAL128_LIST; } else if (primitiveType.equals(ObjectId.class)) { return RealmFieldType.OBJECT_ID_LIST; + } else if (primitiveType.equals(UUID.class)) { + return RealmFieldType.UUID_LIST; } else { throw new IllegalArgumentException("Unsupported element type. Only primitive types supported. Yours was: " + primitiveType); } @@ -487,6 +511,7 @@ public boolean isNull(String fieldName) { case DATE: case DECIMAL128: case OBJECT_ID: + case UUID: return proxyState.getRow$realm().isNull(columnKey); case LIST: case LINKING_OBJECTS: @@ -499,6 +524,7 @@ public boolean isNull(String fieldName) { case DOUBLE_LIST: case DECIMAL128_LIST: case OBJECT_ID_LIST: + case UUID_LIST: // fall through default: return false; @@ -576,6 +602,9 @@ public void set(String fieldName, Object value) { case OBJECT_ID: value = new ObjectId(strValue); break; + case UUID: + value = UUID.fromString(strValue); + break; default: throw new IllegalArgumentException(String.format(Locale.US, "Field %s is not a String field, " + @@ -623,6 +652,8 @@ private void setValue(String fieldName, Object value) { setDecimal128(fieldName, (Decimal128) value); } else if (valueClass == ObjectId.class) { setObjectId(fieldName, (ObjectId) value); + } else if (valueClass == UUID.class) { + setUUID(fieldName, (UUID) value); } else { throw new IllegalArgumentException("Value is of an type not supported: " + value.getClass()); } @@ -818,6 +849,24 @@ public void setObjectId(String fieldName, @Nullable ObjectId value) { } } + /** + * Sets the {@code UUID} value of the given field. + * + * @param fieldName field name. + * @param value value to insert. + * @throws IllegalArgumentException if field name doesn't exist or field isn't a UUID field. + */ + public void setUUID(String fieldName, @Nullable UUID value) { + proxyState.getRealm$realm().checkIfValid(); + + long columnKey = proxyState.getRow$realm().getColumnKey(fieldName); + if (value == null) { + proxyState.getRow$realm().setNull(columnKey); + } else { + proxyState.getRow$realm().setUUID(columnKey, value); + } + } + /** * Sets a reference to another object on the given field. * @@ -898,6 +947,7 @@ public void setList(String fieldName, RealmList list) { case DOUBLE_LIST: case DECIMAL128_LIST: case OBJECT_ID_LIST: + case UUID_LIST: setValueList(fieldName, list, columnType); break; default: @@ -970,6 +1020,7 @@ private void setValueList(String fieldName, RealmList list, RealmFieldTyp case DOUBLE_LIST: elementClass = (Class) Double.class; break; case DECIMAL128_LIST: elementClass = (Class) Decimal128.class; break; case OBJECT_ID_LIST: elementClass = (Class) ObjectId.class; break; + case UUID_LIST: elementClass = (Class) UUID.class; break; default: throw new IllegalArgumentException("Unsupported type: " + primitiveType); } @@ -1029,6 +1080,10 @@ private ManagedListOperator getOperator(BaseRealm realm, OsList osList, R //noinspection unchecked return (ManagedListOperator) new ObjectIdListOperator(realm, osList, (Class) valueClass); } + if (valueListType == RealmFieldType.UUID_LIST) { + //noinspection unchecked + return (ManagedListOperator) new UUIDListOperator(realm, osList, (Class) valueClass); + } throw new IllegalArgumentException("Unexpected list type: " + valueListType.name()); } @@ -1194,6 +1249,9 @@ public String toString() { case OBJECT_ID: sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getObjectId(columnKey)); break; + case UUID: + sb.append(proxyState.getRow$realm().isNull(columnKey) ? "null" : proxyState.getRow$realm().getUUID(columnKey)); + break; case OBJECT: sb.append(proxyState.getRow$realm().isNullLink(columnKey) ? "null" @@ -1230,6 +1288,9 @@ public String toString() { case OBJECT_ID_LIST: sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); break; + case UUID_LIST: + sb.append(String.format(Locale.US, "RealmList[%s]", proxyState.getRow$realm().getValueList(columnKey, type).size())); + break; default: sb.append("?"); break; diff --git a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java index 027c7722aa..6fb3b9a508 100644 --- a/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/FrozenPendingRow.java @@ -19,6 +19,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import io.realm.internal.InvalidRow; import io.realm.internal.OsList; @@ -112,6 +113,11 @@ public ObjectId getObjectId(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public UUID getUUID(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public long getLink(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); @@ -197,6 +203,11 @@ public void setObjectId(long columnKey, ObjectId value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public void setUUID(long columnKey, UUID value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public long createEmbeddedObject(long columnKey, RealmFieldType parentPropertyType) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); diff --git a/realm/realm-library/src/main/java/io/realm/ManagedListOperator.java b/realm/realm-library/src/main/java/io/realm/ManagedListOperator.java index 36382c9406..6b162422aa 100644 --- a/realm/realm-library/src/main/java/io/realm/ManagedListOperator.java +++ b/realm/realm-library/src/main/java/io/realm/ManagedListOperator.java @@ -23,6 +23,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Locale; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -807,3 +808,53 @@ protected void setValue(int index, Object value) { osList.setObjectId(index, (ObjectId) value); } } + +/** + * A subclass of {@link ManagedListOperator} that deal with {@link UUID} list field. + */ +final class UUIDListOperator extends ManagedListOperator { + + UUIDListOperator(BaseRealm realm, OsList osList, Class clazz) { + super(realm, osList, clazz); + } + + @Override + public boolean forRealmModel() { + return false; + } + + @Nullable + @Override + public UUID get(int index) { + return (UUID) osList.getValue(index); + } + + @Override + protected void checkValidValue(@Nullable Object value) { + if (value == null) { + // null is always valid (but schema may reject null on insertion). + return; + } + if (!(value instanceof UUID)) { + throw new IllegalArgumentException( + String.format(Locale.ENGLISH, INVALID_OBJECT_TYPE_MESSAGE, + "java.util.UUID", + value.getClass().getName())); + } + } + + @Override + public void appendValue(Object value) { + osList.addUUID((UUID) value); + } + + @Override + public void insertValue(int index, Object value) { + osList.insertUUID(index, (UUID) value); + } + + @Override + protected void setValue(int index, Object value) { + osList.setUUID(index, (UUID) value); + } +} diff --git a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java index b723b4a061..8577a8aa93 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmFieldType.java +++ b/realm/realm-library/src/main/java/io/realm/RealmFieldType.java @@ -36,6 +36,7 @@ import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_OBJECT; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_OBJECTID; import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_STRING; +import static io.realm.RealmFieldTypeConstants.CORE_TYPE_VALUE_UUID; import static io.realm.RealmFieldTypeConstants.LIST_OFFSET; import static io.realm.RealmFieldTypeConstants.MAX_CORE_TYPE_VALUE; @@ -55,8 +56,9 @@ interface RealmFieldTypeConstants { int CORE_TYPE_VALUE_LINKING_OBJECTS = 14; int CORE_TYPE_VALUE_DECIMAL128 = 11; int CORE_TYPE_VALUE_OBJECTID = 15; + int CORE_TYPE_VALUE_UUID = 17; - int MAX_CORE_TYPE_VALUE = CORE_TYPE_VALUE_OBJECTID; + int MAX_CORE_TYPE_VALUE = CORE_TYPE_VALUE_UUID; } /** @@ -79,6 +81,7 @@ public enum RealmFieldType { OBJECT(CORE_TYPE_VALUE_OBJECT), DECIMAL128(CORE_TYPE_VALUE_DECIMAL128), OBJECT_ID(CORE_TYPE_VALUE_OBJECTID), + UUID(CORE_TYPE_VALUE_UUID), LIST(CORE_TYPE_VALUE_LIST), LINKING_OBJECTS(CORE_TYPE_VALUE_LINKING_OBJECTS), @@ -91,7 +94,8 @@ public enum RealmFieldType { FLOAT_LIST(CORE_TYPE_VALUE_FLOAT + LIST_OFFSET), DOUBLE_LIST(CORE_TYPE_VALUE_DOUBLE + LIST_OFFSET), DECIMAL128_LIST(CORE_TYPE_VALUE_DECIMAL128 + LIST_OFFSET), - OBJECT_ID_LIST(CORE_TYPE_VALUE_OBJECTID + LIST_OFFSET); + OBJECT_ID_LIST(CORE_TYPE_VALUE_OBJECTID + LIST_OFFSET), + UUID_LIST(CORE_TYPE_VALUE_UUID + LIST_OFFSET); // Primitive array for fast mapping between between native values and their Realm type. private static final RealmFieldType[] basicTypes = new RealmFieldType[MAX_CORE_TYPE_VALUE + 1]; @@ -149,8 +153,9 @@ public boolean isValid(Object obj) { return (obj instanceof Decimal128); case CORE_TYPE_VALUE_OBJECTID: return (obj instanceof ObjectId); + case CORE_TYPE_VALUE_UUID: + return (obj instanceof java.util.UUID); case CORE_TYPE_VALUE_OBJECT: - return false; case CORE_TYPE_VALUE_LIST: case CORE_TYPE_VALUE_LINKING_OBJECTS: case CORE_TYPE_VALUE_INTEGER + LIST_OFFSET: @@ -162,6 +167,7 @@ public boolean isValid(Object obj) { case CORE_TYPE_VALUE_DOUBLE + LIST_OFFSET: case CORE_TYPE_VALUE_DECIMAL128 + LIST_OFFSET: case CORE_TYPE_VALUE_OBJECTID + LIST_OFFSET: + case CORE_TYPE_VALUE_UUID + LIST_OFFSET: return false; default: throw new RuntimeException("Unsupported Realm type: " + this); diff --git a/realm/realm-library/src/main/java/io/realm/RealmList.java b/realm/realm-library/src/main/java/io/realm/RealmList.java index 9fab3ad266..b2cf52b6c1 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmList.java +++ b/realm/realm-library/src/main/java/io/realm/RealmList.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -1348,6 +1349,10 @@ private ManagedListOperator getOperator(BaseRealm realm, OsList osList, @Null //noinspection unchecked return (ManagedListOperator) new ObjectIdListOperator(realm, osList, (Class) clazz); } + if (clazz == UUID.class) { + //noinspection unchecked + return (ManagedListOperator) new UUIDListOperator(realm, osList, (Class) clazz); + } throw new IllegalArgumentException("Unexpected value class: " + clazz.getName()); } } diff --git a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java index 834c3219e6..75ef3fe6b7 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObjectSchema.java @@ -26,6 +26,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.UUID; import javax.annotation.Nullable; @@ -71,6 +72,7 @@ public abstract class RealmObjectSchema { m.put(Date.class, new FieldMetaData(RealmFieldType.DATE, RealmFieldType.DATE_LIST, true)); m.put(ObjectId.class, new FieldMetaData(RealmFieldType.OBJECT_ID, RealmFieldType.OBJECT_ID_LIST, true)); m.put(Decimal128.class, new FieldMetaData(RealmFieldType.DECIMAL128, RealmFieldType.DECIMAL128_LIST, true)); + m.put(UUID.class, new FieldMetaData(RealmFieldType.UUID, RealmFieldType.UUID_LIST, true)); SUPPORTED_SIMPLE_FIELDS = Collections.unmodifiableMap(m); } diff --git a/realm/realm-library/src/main/java/io/realm/RealmQuery.java b/realm/realm-library/src/main/java/io/realm/RealmQuery.java index 3addaa8b65..04b5995772 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmQuery.java +++ b/realm/realm-library/src/main/java/io/realm/RealmQuery.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.Date; import java.util.Locale; +import java.util.UUID; import javax.annotation.Nullable; @@ -335,6 +336,19 @@ public RealmQuery equalTo(String fieldName, @Nullable ObjectId value) { return equalToWithoutThreadValidation(fieldName, value); } + /** + * Equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery equalTo(String fieldName, @Nullable UUID value) { + realm.checkIfValid(); + return equalToWithoutThreadValidation(fieldName, value); + } + private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable String value, Case casing) { FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.STRING); this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value, casing); @@ -568,6 +582,16 @@ private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable return this; } + private RealmQuery equalToWithoutThreadValidation(String fieldName, @Nullable UUID value) { + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.UUID); + if (value == null) { + this.query.isNull(fd.getColumnKeys(), fd.getNativeTablePointers()); + } else { + this.query.equalTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + } + return this; + } + /** * In comparison. This allows you to test if objects match any value in an array of values. @@ -872,6 +896,25 @@ public RealmQuery notEqualTo(String fieldName, ObjectId value) { return this; } + /** + * Not-equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery notEqualTo(String fieldName, UUID value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.UUID); + if (value == null) { + this.query.isNotNull(fd.getColumnKeys(), fd.getNativeTablePointers()); + } else { + this.query.notEqualTo(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + } + return this; + } + /** * Not-equal-to comparison. * @@ -1162,6 +1205,21 @@ public RealmQuery greaterThan(String fieldName, ObjectId value) { return this; } + /** + * Greater-than comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery greaterThan(String fieldName, UUID value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.UUID); + this.query.greaterThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + /** * Greater-than-or-equal-to comparison. * @@ -1272,6 +1330,21 @@ public RealmQuery greaterThanOrEqualTo(String fieldName, ObjectId value) { return this; } + /** + * Greater-than-or-equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery greaterThanOrEqualTo(String fieldName, UUID value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.UUID); + this.query.greaterThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + /** * Less-than comparison. * @@ -1334,6 +1407,21 @@ public RealmQuery lessThan(String fieldName, ObjectId value) { return this; } + /** + * Less-than comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery lessThan(String fieldName, UUID value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.UUID); + this.query.lessThan(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + /** * Less-than comparison. * @@ -1442,6 +1530,21 @@ public RealmQuery lessThanOrEqualTo(String fieldName, ObjectId value) { return this; } + /** + * Less-than-or-equal-to comparison. + * + * @param fieldName the field to compare. + * @param value the value to compare with. + * @return the query object. + * @throws java.lang.IllegalArgumentException if one or more arguments do not match class or field type. + */ + public RealmQuery lessThanOrEqualTo(String fieldName, UUID value) { + realm.checkIfValid(); + FieldDescriptor fd = schema.getFieldDescriptors(fieldName, RealmFieldType.UUID); + this.query.lessThanOrEqual(fd.getColumnKeys(), fd.getNativeTablePointers(), value); + return this; + } + /** * Less-than-or-equal-to comparison. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmResults.java b/realm/realm-library/src/main/java/io/realm/RealmResults.java index fd091f3cd1..566ac3b43e 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmResults.java +++ b/realm/realm-library/src/main/java/io/realm/RealmResults.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.Locale; +import java.util.UUID; import javax.annotation.Nullable; @@ -204,6 +205,9 @@ public void setValue(String fieldName, @Nullable Object value) { case OBJECT_ID: value = new ObjectId(strValue); break; + case UUID: + value = UUID.fromString(strValue); + break; default: throw new IllegalArgumentException(String.format(Locale.US, "Field %s is not a String field, " + @@ -237,6 +241,8 @@ public void setValue(String fieldName, @Nullable Object value) { setDecimal128(fieldName, (Decimal128) value); } else if (value instanceof ObjectId) { setObjectId(fieldName, (ObjectId) value); + } else if (value instanceof UUID) { + setUUID(fieldName, (UUID) value); } else if (value instanceof byte[]) { setBlob(fieldName, (byte[]) value); } else if (value instanceof RealmModel) { @@ -458,6 +464,21 @@ public void setObjectId(String fieldName, @Nullable ObjectId value) { osResults.setObjectId(fieldName, value); } + /** + * Sets the {@code UUID} value of the given field in all of the objects in the collection. + * + * @param fieldName name of the field to update. + * @param value new value for the field. + * @throws IllegalArgumentException if field name doesn't exist, is a primary key property or isn't a {@code UUID} field. + */ + public void setUUID(String fieldName, @Nullable UUID value) { + checkNonEmptyFieldName(fieldName); + baseRealm.checkIfValidAndInTransaction(); + fieldName = mapFieldNameToInternalName(fieldName); + checkType(fieldName, RealmFieldType.UUID); + osResults.setUUID(fieldName, value); + } + private Row checkRealmObjectConstraints(String fieldName, @Nullable RealmModel value) { if (value != null) { if (!(RealmObject.isManaged(value) && RealmObject.isValid(value))) { @@ -556,6 +577,10 @@ public void setList(String fieldName, RealmList list) { checkTypeOfListElements(list, ObjectId.class); osResults.setObjectIdList(fieldName, (RealmList) list); break; + case UUID_LIST: + checkTypeOfListElements(list, UUID.class); + osResults.setUUIDList(fieldName, (RealmList) list); + break; case FLOAT_LIST: checkTypeOfListElements(list, Float.class); osResults.setFloatList(fieldName, (RealmList) list); diff --git a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java index 02880c6a59..6291d31df0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/InvalidRow.java @@ -20,6 +20,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import io.realm.RealmFieldType; @@ -107,6 +108,11 @@ public ObjectId getObjectId(long columnKey) { throw getStubException(); } + @Override + public UUID getUUID(long columnKey) { + throw getStubException(); + } + @Override public long getLink(long columnKey) { throw getStubException(); @@ -192,6 +198,11 @@ public void setObjectId(long columnKey, ObjectId value) { throw getStubException(); } + @Override + public void setUUID(long columnKey, UUID value) { + throw getStubException(); + } + @Override public long createEmbeddedObject(long columnKey, RealmFieldType parentPropertyType) { throw getStubException(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsList.java b/realm/realm-library/src/main/java/io/realm/internal/OsList.java index 5e415e00da..d0c872b2c0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsList.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsList.java @@ -4,6 +4,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import javax.annotation.Nullable; @@ -227,6 +228,30 @@ public void setObjectId(long pos, @Nullable ObjectId value) { } } + public void addUUID(@Nullable UUID value) { + if (value == null) { + nativeAddNull(nativePtr); + } else { + nativeAddUUID(nativePtr, value.toString()); + } + } + + public void insertUUID(long pos, @Nullable UUID value) { + if (value == null) { + nativeInsertNull(nativePtr, pos); + } else { + nativeInsertUUID(nativePtr, pos, value.toString()); + } + } + + public void setUUID(long pos, @Nullable UUID value) { + if (value == null) { + nativeSetNull(nativePtr, pos); + } else { + nativeSetUUID(nativePtr, pos, value.toString()); + } + } + @Nullable public Object getValue(long pos) { return nativeGetValue(nativePtr, pos); @@ -424,6 +449,12 @@ public long createAndSetEmbeddedObject(long index) { private static native void nativeSetObjectId(long nativePtr, long pos, String data); + private static native void nativeAddUUID(long nativePtr, String data); + + private static native void nativeInsertUUID(long nativePtr, long pos, String data); + + private static native void nativeSetUUID(long nativePtr, long pos, String data); + private static native Object nativeGetValue(long nativePtr, long pos); private native void nativeStartListening(long nativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java index a7b94cd1a3..af162b7440 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsObject.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsObject.java @@ -18,6 +18,8 @@ import org.bson.types.ObjectId; +import java.util.UUID; + import javax.annotation.Nullable; import io.realm.ObjectChangeSet; @@ -211,6 +213,11 @@ public static UncheckedRow createWithPrimaryKey(Table table, @Nullable Object pr return new UncheckedRow(sharedRealm.context, table, nativeCreateNewObjectWithObjectIdPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), primaryKeyColumnKey, objectIdValue)); + } else if (type == RealmFieldType.UUID) { + String uuidValue = primaryKeyValue == null ? null : primaryKeyValue.toString(); + return new UncheckedRow(sharedRealm.context, table, + nativeCreateNewObjectWithUUIDPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), + primaryKeyColumnKey, uuidValue)); } else { throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); } @@ -249,6 +256,13 @@ public static long createRowWithPrimaryKey(Table table, long primaryKeyColumnInd String objectIdValue = primaryKeyValue == null ? null : primaryKeyValue.toString(); return nativeCreateRowWithObjectIdPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), primaryKeyColumnIndex, objectIdValue); + } else if (type == RealmFieldType.UUID) { + if (primaryKeyValue != null && !(primaryKeyValue instanceof UUID)) { + throw new IllegalArgumentException("Primary key value is not an UUID: " + primaryKeyValue); + } + String uuidValue = primaryKeyValue == null ? null : primaryKeyValue.toString(); + return nativeCreateRowWithUUIDPrimaryKey(sharedRealm.getNativePtr(), table.getNativePtr(), + primaryKeyColumnIndex, uuidValue); } else { throw new RealmException("Cannot check for duplicate rows for unsupported primary key type: " + type); } @@ -305,6 +319,14 @@ private static native long nativeCreateNewObjectWithObjectIdPrimaryKey(long shar long tableRefPtr, long pk_column_index, @Nullable String data); + private static native long nativeCreateRowWithUUIDPrimaryKey(long sharedRealmPtr, + long tableRefPtr, long pk_column_index, + @Nullable String primaryKeyValue); + + private static native long nativeCreateNewObjectWithUUIDPrimaryKey(long sharedRealmPtr, + long tableRefPtr, long pk_column_index, + @Nullable String data); + private static native long nativeCreateEmbeddedObject(long parentTablePtr, long parentObjectKey, long parentObjectColumnKey); } diff --git a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java index 356697a422..af94eac3ab 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/OsResults.java +++ b/realm/realm-library/src/main/java/io/realm/internal/OsResults.java @@ -23,6 +23,7 @@ import java.util.ConcurrentModificationException; import java.util.Date; import java.util.NoSuchElementException; +import java.util.UUID; import javax.annotation.Nullable; @@ -471,6 +472,14 @@ public void setObjectId(String fieldName, @Nullable ObjectId value) { } } + public void setUUID(String fieldName, @Nullable UUID value) { + if (value == null) { + nativeSetNull(nativePtr, fieldName); + } else { + nativeSetUUID(nativePtr, fieldName, value.toString()); + } + } + public void setObject(String fieldName, @Nullable Row row) { if (row == null) { setNull(fieldName); @@ -624,6 +633,15 @@ public void addList(OsObjectBuilder builder, RealmList list) { }); } + public void setUUIDList(String fieldName, RealmList list) { + addTypeSpecificList(fieldName, list, new AddListTypeDelegate() { + @Override + public void addList(OsObjectBuilder builder, RealmList list) { + builder.addUUIDList(0, list); + } + }); + } + public void addListener(T observer, OrderedRealmCollectionChangeListener listener) { if (observerPairs.isEmpty()) { nativeStartListening(nativePtr); @@ -754,6 +772,8 @@ public void load() { private static native void nativeSetObjectId(long nativePtr, String fieldName, String data); + private static native void nativeSetUUID(long nativePtr, String fieldName, String data); + private static native void nativeSetObject(long nativePtr, String fieldName, long rowNativePtr); private static native void nativeSetList(long nativePtr, String fieldName, long builderNativePtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java index b8c043db3b..d590c38d36 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/PendingRow.java @@ -5,6 +5,7 @@ import java.lang.ref.WeakReference; import java.util.Date; +import java.util.UUID; import io.realm.FrozenPendingRow; import io.realm.RealmChangeListener; @@ -136,6 +137,11 @@ public ObjectId getObjectId(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public UUID getUUID(long columnKey) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public long getLink(long columnKey) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); @@ -221,6 +227,11 @@ public void setObjectId(long columnKey, ObjectId value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } + @Override + public void setUUID(long columnKey, UUID value) { + throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); + } + @Override public long createEmbeddedObject(long columnKey, RealmFieldType parentPropertyType) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Property.java b/realm/realm-library/src/main/java/io/realm/internal/Property.java index d3ea639b84..7a133c190e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Property.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Property.java @@ -29,6 +29,7 @@ import static io.realm.RealmFieldType.FLOAT_LIST; import static io.realm.RealmFieldType.INTEGER_LIST; import static io.realm.RealmFieldType.OBJECT_ID_LIST; +import static io.realm.RealmFieldType.UUID_LIST; import static io.realm.RealmFieldType.STRING_LIST; @@ -64,6 +65,8 @@ public class Property implements NativeObject { @SuppressWarnings("WeakerAccess") public static final int TYPE_OBJECT_ID = 10; @SuppressWarnings("WeakerAccess") + public static final int TYPE_UUID = 12; + @SuppressWarnings("WeakerAccess") public static final int TYPE_REQUIRED = 0; @SuppressWarnings("WeakerAccess") public static final int TYPE_NULLABLE = 64; @@ -114,6 +117,9 @@ static int convertFromRealmFieldType(RealmFieldType fieldType, boolean isRequire case OBJECT_ID: type = TYPE_OBJECT_ID; break; + case UUID: + type = TYPE_UUID; + break; case DOUBLE: type = TYPE_DOUBLE; break; @@ -142,6 +148,9 @@ static int convertFromRealmFieldType(RealmFieldType fieldType, boolean isRequire case OBJECT_ID_LIST: type = TYPE_OBJECT_ID | TYPE_ARRAY; break; + case UUID_LIST: + type = TYPE_UUID | TYPE_ARRAY; + break; case DOUBLE_LIST: type = TYPE_DOUBLE | TYPE_ARRAY; break; @@ -181,6 +190,8 @@ private static RealmFieldType convertToRealmFieldType(int propertyType) { return RealmFieldType.DECIMAL128; case TYPE_OBJECT_ID: return RealmFieldType.OBJECT_ID; + case TYPE_UUID: + return RealmFieldType.UUID; //noinspection PointlessBitwiseExpression case TYPE_INT | TYPE_ARRAY: return INTEGER_LIST; @@ -200,6 +211,8 @@ private static RealmFieldType convertToRealmFieldType(int propertyType) { return DECIMAL128_LIST; case TYPE_OBJECT_ID | TYPE_ARRAY: return OBJECT_ID_LIST; + case TYPE_UUID | TYPE_ARRAY: + return UUID_LIST; default: throw new IllegalArgumentException( String.format(Locale.US, "Unsupported property type: '%d'", propertyType)); diff --git a/realm/realm-library/src/main/java/io/realm/internal/Row.java b/realm/realm-library/src/main/java/io/realm/internal/Row.java index 27c170ee8e..49a4e17cb2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Row.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Row.java @@ -20,6 +20,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import javax.annotation.Nullable; @@ -86,6 +87,8 @@ public interface Row { ObjectId getObjectId(long columnKey); + UUID getUUID(long columnKey); + long getLink(long columnKey); boolean isNullLink(long columnKey); @@ -120,6 +123,8 @@ public interface Row { void setObjectId(long columnKey, ObjectId value); + void setUUID(long columnKey, UUID value); + // Creates a new Embedded object in the given property. // This will replace any existing object which will be // deleted. The Obj pointer for the new object is returned. diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 149316d6c6..fe27806b9a 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -20,6 +20,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import javax.annotation.Nullable; @@ -109,6 +110,7 @@ public long addColumn(RealmFieldType type, String name, boolean isNullable) { case DOUBLE: case DECIMAL128: case OBJECT_ID: + case UUID: return nativeAddColumn(nativeTableRefPtr, type.getNativeValue(), name, isNullable); case INTEGER_LIST: @@ -120,6 +122,7 @@ public long addColumn(RealmFieldType type, String name, boolean isNullable) { case DOUBLE_LIST: case DECIMAL128_LIST: case OBJECT_ID_LIST: + case UUID_LIST: return nativeAddPrimitiveListColumn(nativeTableRefPtr, type.getNativeValue() - 128, name, isNullable); default: @@ -503,6 +506,15 @@ public void setObjectId(long columnKey, long rowKey, @Nullable ObjectId value, b } } + public void setUUID(long columnKey, long rowKey, @Nullable UUID value, boolean isDefault) { + checkImmutable(); + if (value == null) { + nativeSetNull(nativeTableRefPtr, columnKey, rowKey, isDefault); + } else { + nativeSetUUID(nativeTableRefPtr, columnKey, rowKey, value.toString(), isDefault); + } + } + public void setLink(long columnKey, long rowKey, long value, boolean isDefault) { checkImmutable(); nativeSetLink(nativeTableRefPtr, columnKey, rowKey, value, isDefault); @@ -620,6 +632,13 @@ public long findFirstObjectId(long columnKey, ObjectId value) { return nativeFindFirstObjectId(nativeTableRefPtr, columnKey, value.toString()); } + public long findFirstUUID(long columnKey, UUID value) { + if (value == null) { + throw new IllegalArgumentException("null is not supported"); + } + return nativeFindFirstUUID(nativeTableRefPtr, columnKey, value.toString()); + } + /** * Searches for first occurrence of null. Beware that the order in the column is undefined. * @@ -822,6 +841,8 @@ public static String getTableNameForClass(String name) { public static native void nativeSetObjectId(long nativeTableRefPtr, long columnKey, long rowKey, String data, boolean isDefault); + public static native void nativeSetUUID(long nativeTableRefPtr, long columnKey, long rowKey, String data, boolean isDefault); + public static native void nativeSetLink(long nativeTableRefPtr, long columnKey, long rowKey, long value, boolean isDefault); private native void nativeAddSearchIndex(long nativePtr, long columnKey); @@ -860,6 +881,8 @@ public static String getTableNameForClass(String name) { public static native long nativeFindFirstObjectId(long nativeTableRefPtr, long columnKey, String value); + public static native long nativeFindFirstUUID(long nativeTableRefPtr, long columnKey, String value); + public static native long nativeFindFirstNull(long nativeTableRefPtr, long columnKey); private native String nativeGetName(long nativeTableRefPtr); diff --git a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java index 4c0e12f52b..25e2077da0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java +++ b/realm/realm-library/src/main/java/io/realm/internal/TableQuery.java @@ -20,11 +20,11 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import javax.annotation.Nullable; import io.realm.Case; -import io.realm.Sort; import io.realm.log.RealmLog; @@ -499,6 +499,44 @@ public TableQuery greaterThanOrEqual(long[] columnKeys, long[] tablePtrs, Object return this; } + // Queries for UUID + + public TableQuery equalTo(long[] columnKeys, long[] tablePtrs, UUID value) { + nativeEqualUUID(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery notEqualTo(long[] columnKeys, long[] tablePtrs, UUID value) { + nativeNotEqualUUID(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery lessThan(long[] columnKeys, long[] tablePtrs, UUID value) { + nativeLessUUID(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery lessThanOrEqual(long[] columnKeys, long[] tablePtrs, UUID value) { + nativeLessEqualUUID(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery greaterThan(long[] columnKeys, long[] tablePtrs, UUID value) { + nativeGreaterUUID(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + + public TableQuery greaterThanOrEqual(long[] columnKeys, long[] tablePtrs, UUID value) { + nativeGreaterEqualUUID(nativePtr, columnKeys, tablePtrs, value.toString()); + queryValidated = false; + return this; + } + // Searching methods. /** @@ -791,6 +829,18 @@ public void alwaysFalse() { private native void nativeLessEqualObjectId(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + private native void nativeEqualUUID(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeNotEqualUUID(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeGreaterUUID(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeGreaterEqualUUID(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeLessUUID(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + + private native void nativeLessEqualUUID(long nativeQueryPtr, long[] columnIndex, long[] tablePtrs, String data); + private native void nativeIsEmpty(long nativePtr, long[] columnKeys, long[] tablePtrs); private native void nativeIsNotEmpty(long nativePtr, long[] columnKeys, long[] tablePtrs); diff --git a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java index c9f6e5be85..1600c3566e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java @@ -20,6 +20,7 @@ import org.bson.types.ObjectId; import java.util.Date; +import java.util.UUID; import javax.annotation.Nullable; @@ -179,6 +180,11 @@ public ObjectId getObjectId(long columnKey) { return new ObjectId(nativeGetObjectId(nativePtr, columnKey)); } + @Override + public UUID getUUID(long columnKey) { + return UUID.fromString(nativeGetUUID(nativePtr, columnKey)); + } + @Override public long getLink(long columnKey) { return nativeGetLink(nativePtr, columnKey); @@ -306,6 +312,16 @@ public void setObjectId(long columnKey, @Nullable ObjectId value) { } } + @Override + public void setUUID(long columnKey, @Nullable UUID value) { + parent.checkImmutable(); + if (value == null) { + nativeSetNull(nativePtr, columnKey); + } else { + nativeSetUUID(nativePtr, columnKey, value.toString()); + } + } + @Override public long createEmbeddedObject(long columnKey, RealmFieldType parentPropertyType) { switch (parentPropertyType) { @@ -389,6 +405,8 @@ public boolean isLoaded() { protected native String nativeGetObjectId(long nativePtr, long columnKey); + protected native String nativeGetUUID(long nativePtr, long columnKey); + protected native void nativeSetLong(long nativeRowPtr, long columnKey, long value); protected native void nativeSetBoolean(long nativeRowPtr, long columnKey, boolean value); @@ -409,6 +427,8 @@ public boolean isLoaded() { protected native void nativeSetObjectId(long nativePtr, long columnKey, String value); + protected native void nativeSetUUID(long nativePtr, long columnKey, String value); + protected native void nativeSetLink(long nativeRowPtr, long columnKey, long value); protected native void nativeNullifyLink(long nativeRowPtr, long columnKey); diff --git a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java index b8aaeab4e8..540af6210e 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java +++ b/realm/realm-library/src/main/java/io/realm/internal/core/QueryDescriptor.java @@ -47,13 +47,13 @@ public class QueryDescriptor { //@VisibleForTesting public static final Set SORT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, - RealmFieldType.STRING, RealmFieldType.DATE, RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID))); + RealmFieldType.STRING, RealmFieldType.DATE, RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID, RealmFieldType.UUID))); //@VisibleForTesting public static final Set DISTINCT_VALID_FIELD_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( RealmFieldType.BOOLEAN, RealmFieldType.INTEGER, RealmFieldType.STRING, RealmFieldType.BINARY, RealmFieldType.DATE, RealmFieldType.FLOAT, RealmFieldType.DOUBLE, - RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID, RealmFieldType.OBJECT, + RealmFieldType.DECIMAL128, RealmFieldType.OBJECT_ID, RealmFieldType.OBJECT, RealmFieldType.UUID, RealmFieldType.LINKING_OBJECTS ))); diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java index a2daace840..79c8140074 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsObjectBuilder.java @@ -22,6 +22,7 @@ import java.util.Date; import java.util.List; import java.util.Set; +import java.util.UUID; import javax.annotation.Nullable; @@ -177,6 +178,13 @@ public void handleItem(long listPtr, ObjectId item) { } }; + private static ItemCallback uuidItemCallback = new ItemCallback() { + @Override + public void handleItem(long listPtr, UUID item) { + nativeAddUUIDListItem(listPtr, item.toString()); + } + }; + // If true, fields will not be updated if the same value would be written to it. private final boolean ignoreFieldsWithSameValue; @@ -295,6 +303,14 @@ public void addObjectId(long columnKey, @Nullable ObjectId val) { } } + public void addUUID(long columnKey, @Nullable UUID val) { + if (val == null) { + nativeAddNull(builderPtr, columnKey); + } else { + nativeAddUUID(builderPtr, columnKey, val.toString()); + } + } + public void addNull(long columnKey) { nativeAddNull(builderPtr, columnKey); } @@ -397,6 +413,10 @@ public void addObjectIdList(long columnKey, RealmList list) { addListItem(builderPtr, columnKey, list, objectIdItemCallback); } + public void addUUIDList(long columnKey, RealmList list) { + addListItem(builderPtr, columnKey, list, uuidItemCallback); + } + private void addEmptyList(long columnKey) { long listPtr = nativeStartList(0); nativeStopList(builderPtr, columnKey, listPtr); @@ -492,6 +512,7 @@ private static native long nativeUpdateEmbeddedObject(long sharedRealmPtr, private static native void nativeAddObject(long builderPtr, long columnKey, long rowPtr); private static native void nativeAddDecimal128(long builderPtr, long columnKey, long low, long high); private static native void nativeAddObjectId(long builderPtr, long columnKey, String data); + private static native void nativeAddUUID(long builderPtr, long columnKey, String data); // Methods for adding lists // Lists sent across JNI one element at a time @@ -507,6 +528,7 @@ private static native long nativeUpdateEmbeddedObject(long sharedRealmPtr, private static native void nativeAddDateListItem(long listPtr, long val); private static native void nativeAddDecimal128ListItem(long listPtr, long low, long high); private static native void nativeAddObjectIdListItem(long listPtr, String data); + private static native void nativeAddUUIDListItem(long listPtr, String data); private static native void nativeAddObjectListItem(long listPtr, long rowPtr); private static native void nativeAddObjectList(long builderPtr, long columnKey, long[] rowPtrs); } diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 6d0412f2a2..940eefec34 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -21,8 +21,6 @@ import android.os.Build; import android.os.Looper; -import androidx.test.platform.app.InstrumentationRegistry; - import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.junit.Assert; @@ -55,6 +53,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import androidx.test.platform.app.InstrumentationRegistry; import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.AnnotationIndexTypes; import io.realm.entities.BacklinksSource; @@ -1378,4 +1377,8 @@ public static String generateObjectIdHexString(int i) { return randomId.toString(); } + public static String generateUUIDString(int i){ + return String.format("%08d-aa12-4afa-9219-e20cc3018599", i); + } + } diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java index e0177680d4..a05272e635 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/AllTypes.java @@ -16,8 +16,12 @@ package io.realm.entities; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.math.BigDecimal; import java.util.Date; +import java.util.UUID; import io.realm.MutableRealmInteger; import io.realm.RealmList; @@ -27,9 +31,6 @@ import io.realm.annotations.LinkingObjects; import io.realm.annotations.Required; -import org.bson.types.Decimal128; -import org.bson.types.ObjectId; - public class AllTypes extends RealmObject { public static final String CLASS_NAME = "AllTypes"; @@ -43,6 +44,7 @@ public class AllTypes extends RealmObject { public static final String FIELD_MUTABLEREALMINTEGER = "columnMutableRealmInteger"; public static final String FIELD_DECIMAL128 = "columnDecimal128"; public static final String FIELD_OBJECT_ID = "columnObjectId"; + public static final String FIELD_UUID = "columnUUID"; public static final String FIELD_REALMOBJECT = "columnRealmObject"; public static final String FIELD_REALMLINK = "columnRealmLink"; public static final String FIELD_REALMBACKLINK = "columnRealmBackLink"; @@ -75,6 +77,8 @@ public class AllTypes extends RealmObject { private Decimal128 columnDecimal128 = new Decimal128(BigDecimal.ZERO); @Required private ObjectId columnObjectId = new ObjectId(TestHelper.randomObjectIdHexString()); + @Required + private UUID columnUUID = UUID.randomUUID(); private final MutableRealmInteger columnMutableRealmInteger = MutableRealmInteger.ofNull(); @@ -95,6 +99,7 @@ public class AllTypes extends RealmObject { private RealmList columnDateList; private RealmList columnDecimal128List; private RealmList columnObjectIdList; + private RealmList columnUUIDList; public String getColumnString() { return columnString; @@ -261,6 +266,14 @@ public void setColumnObjectId(ObjectId columnObjectId) { this.columnObjectId = columnObjectId; } + public UUID getColumnUUID() { + return columnUUID; + } + + public void setColumnUUID(UUID columnUUID) { + this.columnUUID = columnUUID; + } + public RealmList getColumnDecimal128List() { return columnDecimal128List; } @@ -276,4 +289,12 @@ public RealmList getColumnObjectIdList() { public void setColumnObjectIdList(RealmList columnObjectIdList) { this.columnObjectIdList = columnObjectIdList; } + + public RealmList getColumnUUIDList() { + return columnUUIDList; + } + + public void setColumnUUIDList(RealmList columnUUIDList) { + this.columnUUIDList = columnUUIDList; + } } diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java b/realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java index 62daabede4..b9877ca893 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/NullTypes.java @@ -16,8 +16,12 @@ package io.realm.entities; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + import java.math.BigDecimal; import java.util.Date; +import java.util.UUID; import io.realm.RealmList; import io.realm.RealmObject; @@ -27,9 +31,6 @@ import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; -import org.bson.types.Decimal128; -import org.bson.types.ObjectId; - // Always follow below order and put comments like below to make NullTypes Related cases // 1 String // 2 Bytes @@ -70,6 +71,8 @@ public class NullTypes extends RealmObject { public static final String FIELD_DECIMAL128_NOT_NULL = "fieldDecimal128NotNull"; public static final String FIELD_OBJECT_ID_NULL = "fieldObjectIdNull"; public static final String FIELD_OBJECT_ID_NOT_NULL = "fieldObjectIdNotNull"; + public static final String FIELD_UUID_NULL = "fieldUUIDNull"; + public static final String FIELD_UUID_NOT_NULL = "fieldUUIDNotNull"; public static final String FIELD_OBJECT_NULL = "fieldObjectNull"; public static final String FIELD_LIST_NULL = "fieldListNull"; public static final String FIELD_LO_OBJECT = "objectParents"; @@ -99,6 +102,8 @@ public class NullTypes extends RealmObject { public static final String FIELD_DECIMAL128_LIST_NOT_NULL = "fieldDecimal128ListNotNull"; public static final String FIELD_OBJECT_ID_LIST_NULL = "fieldObjectIdListNull"; public static final String FIELD_OBJECT_ID_LIST_NOT_NULL = "fieldObjectIdListNotNull"; + public static final String FIELD_UUID_LIST_NULL = "fieldUUIDListNull"; + public static final String FIELD_UUID_LIST_NOT_NULL = "fieldUUIDListNotNull"; @PrimaryKey private int id; @@ -151,6 +156,10 @@ public class NullTypes extends RealmObject { private ObjectId fieldObjectIdNotNull = new ObjectId(TestHelper.generateObjectIdHexString(0)); private ObjectId fieldObjectIdNull; + @Required + private UUID fieldUUIDNotNull = UUID.randomUUID(); + private UUID fieldUUIDNull; + private NullTypes fieldObjectNull; // never nullable @@ -204,6 +213,10 @@ public class NullTypes extends RealmObject { private RealmList fieldObjectIdListNotNull; private RealmList fieldObjectIdListNull; + @Required + private RealmList fieldUUIDListNotNull; + private RealmList fieldUUIDListNull; + // never nullable @LinkingObjects(FIELD_OBJECT_NULL) private final RealmResults objectParents = null; @@ -627,4 +640,36 @@ public RealmList getFieldObjectIdListNull() { public void setFieldObjectIdListNull(RealmList fieldObjectIdListNull) { this.fieldObjectIdListNull = fieldObjectIdListNull; } + + public RealmList getFieldUUIDListNotNull() { + return fieldUUIDListNotNull; + } + + public void setFieldUUIDListNotNull(RealmList fieldUUIDListNotNull) { + this.fieldUUIDListNotNull = fieldUUIDListNotNull; + } + + public RealmList getFieldUUIDListNull() { + return fieldUUIDListNull; + } + + public void setFieldUUIDListNull(RealmList fieldUUIDListNull) { + this.fieldUUIDListNull = fieldUUIDListNull; + } + + public UUID getFieldUUIDNotNull() { + return fieldUUIDNotNull; + } + + public void setFieldUUIDNotNull(UUID fieldUUIDNotNull) { + this.fieldUUIDNotNull = fieldUUIDNotNull; + } + + public UUID getFieldUUIDNull() { + return fieldUUIDNull; + } + + public void setFieldUUIDNull(UUID fieldUUIDNull) { + this.fieldUUIDNull = fieldUUIDNull; + } } diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsUUID.java b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsUUID.java new file mode 100644 index 0000000000..d889d3337b --- /dev/null +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/PrimaryKeyAsUUID.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.entities; + +import java.util.UUID; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + + +public class PrimaryKeyAsUUID extends RealmObject { + + public static final String CLASS_NAME = "PrimaryKeyAsUUID"; + public static final String FIELD_PRIMARY_KEY = "id"; + public static final String FIELD_NAME = "name"; + + @PrimaryKey + private UUID id; + + private String name; + + public PrimaryKeyAsUUID() {} + public PrimaryKeyAsUUID(UUID id, String name) { + this.id = id; + this.name = name; + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} From 7bfac1f562ae3197cc2f519ae3592ad3f2ca7afa Mon Sep 17 00:00:00 2001 From: clementetb Date: Wed, 9 Dec 2020 10:21:48 +0100 Subject: [PATCH 1764/2110] Support OpenId credential for google identification (#7225) --- CHANGELOG.md | 19 ++++++++- dependencies.list | 6 +-- .../kotlin/io/realm/CredentialsTests.kt | 29 ++++++++++--- .../network/LoggingInterceptorTest.kt | 41 +++++++++++++++++-- .../io/realm/mongodb/MongoClientTest.kt | 10 +++++ ..._internal_objectstore_OsAppCredentials.cpp | 9 +++- realm/realm-library/src/main/cpp/object-store | 2 +- .../objectstore/OsAppCredentials.java | 25 +++++++++-- .../java/io/realm/mongodb/Credentials.java | 24 +++++++++-- .../io/realm/mongodb/auth/GoogleAuthType.java | 27 ++++++++++++ 10 files changed, 167 insertions(+), 25 deletions(-) create mode 100644 realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/GoogleAuthType.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 684b6a42ce..b02ceee5cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,17 @@ -## 10.1.3 (YYYY-MM-DD) +## 10.2.0 (2020-12-02) + +### Deprecated +* [RealmApp] `Credentials.google(authenticationCode: String)`. Use `Credentials.google(token: String, authType: GoogleAuthType)` instead. ### Breaking Changes * None. ### Enhancements -* None. +* [RealmApp] Added `Credentials.google(token: String, authType: GoogleAuthType)`, as MongoDB Realm now supports multiple ways of logging into Google Accounts. ### Fixes +* [RealmApp] Bug that would prevent eventual consistency during conflict resolution. Affected clients would experience data divergence and potentially consistency errors as a result if they experienced conflict resolution between cycles of Create-Erase-Create for objects with primary keys. +* Complementary fix for missed edge case in https://github.com/realm/realm-java/pull/7220 where KAPT crash if we process a RealmObject referencing a type in RealmList defined in another module. (Issue [#7213](https://github.com/realm/realm-java/issues/7213), since v10.0.0). * Clean up JNI references to prevent crash from JNI reference table overflow (Issue [#7217](https://github.com/realm/realm-java/issues/7217)) ### Compatibility @@ -14,6 +19,11 @@ * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. +### Internal +* Updated to Realm Sync: 10.1.4. +* Updated to Object Store commit: f838a27402c5b5243280102014defd844420abba66eb93c10334507d9c0fd513.. + + ## 10.1.2 (2020-12-02) ### Breaking Changes @@ -46,6 +56,11 @@ * APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. * Realm Studio 10.0.0 or above is required to open Realms created by this version. +### Internal +* Updated to Realm Sync: 10.1.3. +* Updated to Realm Core: 10.1.3. +* Updated to Object Store commit: fc6daca61133aa9601e4cb34fbeb9ec7569e162e. + ## 10.1.0 (2020-10-23) diff --git a/dependencies.list b/dependencies.list index 2b6f841599..8265c6d795 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,11 +1,11 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC=10.1.3 -REALM_SYNC_SHA256=25453e11051192723a95c63ad378e11c69e55428a6973ff58cc6f9e8b3659870 +REALM_SYNC=10.1.4 +REALM_SYNC_SHA256=f838a27402c5b5243280102014defd844420abba66eb93c10334507d9c0fd513 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER=2020-10-03 +MONGODB_REALM_SERVER=2020-12-08 # Common Android settings across projects GRADLE_BUILD_TOOLS=4.0.0 diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index 3f9221af02..275613d16d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -20,6 +20,7 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.mongodb.* import io.realm.mongodb.auth.ApiKey +import io.realm.mongodb.auth.GoogleAuthType import org.bson.Document import org.junit.After import org.junit.Assert.* @@ -134,16 +135,31 @@ class CredentialsTests { } @Test - fun google() { - val creds = Credentials.google("google-token") + fun google_authCode() { + val creds = Credentials.google("google-token", GoogleAuthType.AUTH_CODE) assertEquals(Credentials.Provider.GOOGLE, creds.identityProvider) assertTrue(creds.asJson().contains("google-token")) + assertTrue(creds.asJson().contains("authCode")) } @Test - fun google_invalidInput() { - assertFailsWith { Credentials.google("") } - assertFailsWith { Credentials.google(TestHelper.getNull()) } + fun google_idToken() { + val creds = Credentials.google("google-token", GoogleAuthType.ID_TOKEN) + assertEquals(Credentials.Provider.GOOGLE, creds.identityProvider) + assertTrue(creds.asJson().contains("google-token")) + assertTrue(creds.asJson().contains("id_token")) + } + + @Test + fun google_invalidInput_authCode() { + assertFailsWith { Credentials.google("", GoogleAuthType.AUTH_CODE) } + assertFailsWith { Credentials.google(TestHelper.getNull(), GoogleAuthType.AUTH_CODE) } + } + + @Test + fun google_invalidInput_idToken() { + assertFailsWith { Credentials.google("", GoogleAuthType.ID_TOKEN) } + assertFailsWith { Credentials.google(TestHelper.getNull(), GoogleAuthType.ID_TOKEN) } } @Ignore("FIXME: Awaiting ObjectStore support") @@ -210,7 +226,8 @@ class CredentialsTests { expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.apple("apple-token")) } Credentials.Provider.GOOGLE -> { - expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.google("google-token")) + expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.google("google-token", GoogleAuthType.AUTH_CODE)) + expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.google("google-token", GoogleAuthType.ID_TOKEN)) } Credentials.Provider.JWT -> { expectErrorCode(app, ErrorCode.INVALID_SESSION, Credentials.jwt("jwt-token")) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt index 656e98c351..36df334017 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/internal/network/LoggingInterceptorTest.kt @@ -23,7 +23,9 @@ import io.realm.internal.network.LoggingInterceptor.LOGIN_FEATURE import io.realm.log.LogLevel import io.realm.log.RealmLog import io.realm.mongodb.* +import io.realm.mongodb.auth.GoogleAuthType import io.realm.mongodb.log.obfuscator.HttpLogObfuscator +import io.realm.util.assertFailsWithErrorCode import org.bson.Document import org.junit.After import org.junit.Assert @@ -189,13 +191,13 @@ class LoggingInterceptorTest { } @Test - fun googleTokenLogin_noObfuscation() { + fun googleTokenLogin_authCode_noObfuscation() { app = TestApp() testLogger = getLogger() val token = "google-token" try { - app.login(Credentials.google(token)) + app.login(Credentials.google(token, GoogleAuthType.AUTH_CODE)) } catch (error: AppException) { Assert.assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) } finally { @@ -204,7 +206,7 @@ class LoggingInterceptorTest { } @Test - fun googleTokenLogin_obfuscation() { + fun googleTokenLogin_authCode_obfuscation() { app = TestApp { builder -> builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) } @@ -212,7 +214,7 @@ class LoggingInterceptorTest { val token = "google-token" try { - app.login(Credentials.google(token)) + app.login(Credentials.google(token, GoogleAuthType.AUTH_CODE)) } catch (error: AppException) { Assert.assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) } finally { @@ -220,6 +222,37 @@ class LoggingInterceptorTest { } } + + @Test + fun googleTokenLogin_idToken_noObfuscation() { + app = TestApp() + testLogger = getLogger() + val token = "google-token" + + assertFailsWithErrorCode(ErrorCode.INVALID_SESSION) { + app.login(Credentials.google(token, GoogleAuthType.ID_TOKEN)) + }.also { + assertMessageExists(""""id_token":"$token"""") + } + } + + @Test + fun googleTokenLogin_idToken_obfuscation() { + app = TestApp { builder -> + builder.httpLogObfuscator(HttpLogObfuscator(LOGIN_FEATURE, AppConfiguration.loginObfuscators)) + } + testLogger = getLogger() + val token = "google-token" + + try { + app.login(Credentials.google(token, GoogleAuthType.ID_TOKEN)) + } catch (error: AppException) { + Assert.assertEquals(ErrorCode.INVALID_SESSION, error.errorCode) + } finally { + assertMessageExists(""""id_token":"***"""") + } + } + private fun getLogger(): TestHelper.TestLogger = TestHelper.TestLogger().also { RealmLog.add(it) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt index 33f4dc5682..1eb6e18310 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/MongoClientTest.kt @@ -36,6 +36,7 @@ import org.bson.codecs.configuration.CodecRegistries import org.bson.types.ObjectId import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import java.io.IOException @@ -683,6 +684,7 @@ class MongoClientTest { } } + @Ignore("https://github.com/realm/realm-java/issues/7238") @Test fun findOneAndUpdate_emptyCollection() { with(getCollectionInternal()) { @@ -691,6 +693,7 @@ class MongoClientTest { } } + @Ignore("https://github.com/realm/realm-java/issues/7238") @Test fun findOneAndUpdate_noUpdates() { with(getCollectionInternal()) { @@ -699,6 +702,7 @@ class MongoClientTest { } } + @Ignore("https://github.com/realm/realm-java/issues/7238") @Test fun findOneAndUpdate_noUpsert() { with(getCollectionInternal()) { @@ -844,6 +848,7 @@ class MongoClientTest { } } + @Ignore("https://github.com/realm/realm-java/issues/7238") @Test fun findOneAndReplace_noUpdates() { with(getCollectionInternal()) { @@ -855,6 +860,7 @@ class MongoClientTest { } } + @Ignore("https://github.com/realm/realm-java/issues/7238") @Test fun findOneAndReplace_noUpsert() { with(getCollectionInternal()) { @@ -1347,11 +1353,15 @@ class MongoClientTest { } } + @Ignore("https://github.com/realm/realm-java/issues/7239") @Test fun watchError() { with(getCollectionInternal()) { val watcher = this.watch() + // This should time out after 60 seconds with no events. + // This no longer seems to happen though as pr. https://github.com/realm/realm-java/issues/7239 + // So we need to find another way to test this. assertFailsWith { watcher.next } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp index 22daee9a22..c40c1d88c1 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_objectstore_OsAppCredentials.cpp @@ -63,9 +63,14 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_objectstore_OsAppCredentials_nati creds = AppCredentials::apple(id_token); break; } - case io_realm_internal_objectstore_OsAppCredentials_TYPE_GOOGLE: { + case io_realm_internal_objectstore_OsAppCredentials_TYPE_GOOGLE_AUTH_CODE: { JStringAccessor id_token(env, (jstring) env->GetObjectArrayElement(j_args, 0)); - creds = AppCredentials::google(id_token); + creds = AppCredentials::google(AuthCode(StringData(id_token).data())); + break; + } + case io_realm_internal_objectstore_OsAppCredentials_TYPE_GOOGLE_ID_TOKEN: { + JStringAccessor id_token(env, (jstring) env->GetObjectArrayElement(j_args, 0)); + creds = AppCredentials::google(IdToken(StringData(id_token).data())); break; } case io_realm_internal_objectstore_OsAppCredentials_TYPE_JWT: { diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index fc6daca611..5c36846630 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit fc6daca61133aa9601e4cb34fbeb9ec7569e162e +Subproject commit 5c3684663078d1142259796a2d3c3a8df58851e5 diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java index e7e3ce3911..c38d189923 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/objectstore/OsAppCredentials.java @@ -22,6 +22,8 @@ import io.realm.mongodb.AppConfiguration; import io.realm.mongodb.AppException; import io.realm.mongodb.Credentials; +import io.realm.mongodb.auth.GoogleAuthType; + /** * Class wrapping ObjectStores {@code realm::app::AppCredentials}. @@ -35,8 +37,10 @@ public class OsAppCredentials implements NativeObject { private static final int TYPE_CUSTOM_FUNCTION = 5; private static final int TYPE_EMAIL_PASSWORD = 6; private static final int TYPE_FACEBOOK = 7; - private static final int TYPE_GOOGLE = 8; - private static final int TYPE_JWT = 9; + private static final int TYPE_JWT = 8; + private static final int TYPE_GOOGLE_AUTH_CODE = 9; + private static final int TYPE_GOOGLE_ID_TOKEN = 10; + private static final long finalizerPtr = nativeGetFinalizerMethodPtr(); public static OsAppCredentials anonymous() { @@ -68,8 +72,21 @@ public static OsAppCredentials facebook(String accessToken) { return new OsAppCredentials(nativeCreate(TYPE_FACEBOOK, accessToken)); } - public static OsAppCredentials google(String whatToCallThisToken) { - return new OsAppCredentials(nativeCreate(TYPE_GOOGLE, whatToCallThisToken)); + public static OsAppCredentials google(String accessToken, GoogleAuthType type) { + int authType; + + switch (type){ + case AUTH_CODE: + authType = TYPE_GOOGLE_AUTH_CODE; + break; + case ID_TOKEN: + authType = TYPE_GOOGLE_ID_TOKEN; + break; + default: + throw new RuntimeException("Unsupported GoogleAuthType: " + type); + } + + return new OsAppCredentials(nativeCreate(authType, accessToken)); } public static OsAppCredentials jwt(String jwtToken) { diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java index 8a4f53d52d..41add97436 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/Credentials.java @@ -22,6 +22,8 @@ import io.realm.internal.Util; import io.realm.internal.objectstore.OsAppCredentials; import io.realm.mongodb.auth.EmailPasswordAuth; +import io.realm.mongodb.auth.GoogleAuthType; + /** * Credentials represent a login with a given login provider, and are used by the MongoDB Realm to @@ -150,17 +152,33 @@ public static Credentials facebook(String accessToken) { } /** - * Creates credentials representing a login using a Google Authorization Code. + * Creates credentials representing a login using a Google access token of a given {@link GoogleAuthType}. + *

            + * This provider must be enabled on MongoDB Realm to work. + * + * @param token the access token returned when logging in to Google. + * @param type the access token type + * @return a set of credentials that can be used to log into MongoDB Realm using + * {@link App#loginAsync(Credentials, App.Callback)}. + */ + public static Credentials google(String token, GoogleAuthType type) { + Util.checkEmpty(token, "token"); + return new Credentials(OsAppCredentials.google(token, type), Provider.GOOGLE); + } + + /** + * Creates credentials representing a login using a {@link GoogleAuthType#AUTH_CODE} Google access token. *

            * This provider must be enabled on MongoDB Realm to work. * * @param authorizationCode the authorization code returned when logging in to Google. * @return a set of credentials that can be used to log into MongoDB Realm using * {@link App#loginAsync(Credentials, App.Callback)}. + * @deprecated Use {@link Credentials#google(String, GoogleAuthType)} instead. */ + @Deprecated public static Credentials google(String authorizationCode) { - Util.checkEmpty(authorizationCode, "authorizationCode"); - return new Credentials(OsAppCredentials.google(authorizationCode), Provider.GOOGLE); + return google(authorizationCode, GoogleAuthType.AUTH_CODE); } /** diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/GoogleAuthType.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/GoogleAuthType.java new file mode 100644 index 0000000000..3bbdd3ead9 --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/auth/GoogleAuthType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.mongodb.auth; + +/** + * This enum contains the list of Google authentication types supported by MongoDB Realm. + * + * @see Google Authentication + */ +public enum GoogleAuthType { + AUTH_CODE, + ID_TOKEN +} From c123823eb729e6f2c9c58519626ff6946c0e0dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 9 Dec 2020 13:12:40 +0100 Subject: [PATCH 1765/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 2bd6f7e392..a6d635a4e7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.2.0 +10.2.1-SNAPSHOT From 3962fe032d4a256a5d2d33a0718f3786d1639b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 9 Dec 2020 13:15:19 +0100 Subject: [PATCH 1766/2110] Prepare next dev iteration --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a6d635a4e7..887ee250c7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.2.1-SNAPSHOT +10.3.0-SNAPSHOT From aac2767e61bf158d4ee03437aa03fb570a3ceb72 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Thu, 10 Dec 2020 13:32:30 +0100 Subject: [PATCH 1767/2110] Add reference to Realm Kotlin --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 96089aec8a..cfcfb0b61d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Realm is a mobile database that runs directly inside phones, tablets or wearables. This repository holds the source code for the Java version of Realm, which currently runs only on Android. +## Realm Kotlin + +See [Realm Kotlin](https://github.com/realm/realm-kotlin) for more information about our new SDK written specifically for Kotlin Multiplatform and Android. The SDK is still experimental and the API surface has not been finalized yet, but we highly encourage any feedback you might have. + ## Features * **Mobile-first:** Realm is the first database built from the ground up to run directly inside phones, tablets, and wearables. From 9410346bd10460edfccd06d3f9f532767d7a4283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Fri, 18 Dec 2020 13:02:10 +0100 Subject: [PATCH 1768/2110] Added nullable annotation to RealmObject.isValid to prevent warnings from Kotlin. Updated OS pointer to laters master commit. (#7252) --- CHANGELOG.md | 24 +++++++++++++++++-- realm/realm-library/src/main/cpp/object-store | 2 +- .../src/main/java/io/realm/RealmObject.java | 4 +++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71694c6eda..d3331ff7b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## 10.2.1 (YYYY-MM-DD) + +### Breaking Changes +* None. + +### Enhancements +* None. + +### Fixes +* Added `@Nullable` annotation to input parameter in `RealmObject.isValid(item)` to avoid mismatch warnings from Kotlin code (Issue [#7216](https://github.com/realm/realm-java/issues/7216)). + +### Compatibility +* File format: Generates Realms with format v20. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. +* APIs are backwards compatible with all previous release of realm-java in the 10.x.y series. +* Realm Studio 10.0.0 or above is required to open Realms created by this version. + +### Internal +* Updated to Object Store commit: fc790d558ddc0e25a50d6b27dadf617532a1bf44. + + ## 10.2.0 (2020-12-02) ### Deprecated @@ -7,7 +27,7 @@ * None. ### Enhancements -* [RealmApp] Added `Credentials.google(token: String, authType: GoogleAuthType)`, as MongoDB Realm now supports multiple ways of logging into Google Accounts. +* [RealmApp] Added `Credentials.google(token: String, authType: GoogleAuthType)`, as MongoDB Realm now supports multiple ways of logging into Google Accounts. ### Fixes * [RealmApp] Bug that would prevent eventual consistency during conflict resolution. Affected clients would experience data divergence and potentially consistency errors as a result if they experienced conflict resolution between cycles of Create-Erase-Create for objects with primary keys. @@ -20,7 +40,7 @@ ### Internal * Updated to Realm Sync: 10.1.4. -* Updated to Object Store commit: f838a27402c5b5243280102014defd844420abba66eb93c10334507d9c0fd513.. +* Updated to Object Store commit: f838a27402c5b5243280102014defd844420abba66eb93c10334507d9c0fd513. ## 10.1.2 (2020-12-02) diff --git a/realm/realm-library/src/main/cpp/object-store b/realm/realm-library/src/main/cpp/object-store index 5c36846630..fc790d558d 160000 --- a/realm/realm-library/src/main/cpp/object-store +++ b/realm/realm-library/src/main/cpp/object-store @@ -1 +1 @@ -Subproject commit 5c3684663078d1142259796a2d3c3a8df58851e5 +Subproject commit fc790d558ddc0e25a50d6b27dadf617532a1bf44 diff --git a/realm/realm-library/src/main/java/io/realm/RealmObject.java b/realm/realm-library/src/main/java/io/realm/RealmObject.java index 0816521548..ee9109adef 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmObject.java +++ b/realm/realm-library/src/main/java/io/realm/RealmObject.java @@ -20,6 +20,8 @@ import java.util.Collections; +import javax.annotation.Nullable; + import io.reactivex.Flowable; import io.reactivex.Observable; import io.realm.annotations.RealmClass; @@ -150,7 +152,7 @@ public final boolean isValid() { * @param object RealmObject to check validity for. * @return {@code true} if the object is still accessible or an unmanaged object, {@code false} otherwise. */ - public static boolean isValid(E object) { + public static boolean isValid(@Nullable E object) { if (object instanceof RealmObjectProxy) { RealmObjectProxy proxy = (RealmObjectProxy) object; Row row = proxy.realmGet$proxyState().getRow$realm(); From 580d0753f2752e0cc72e47a63d1404d65f54ffc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=C3=B3pez?= <1874445+edualonso@users.noreply.github.com> Date: Wed, 6 Jan 2021 14:58:22 +0100 Subject: [PATCH 1769/2110] Update coroutines example to be the new newsreader example (#7206) --- examples/coroutinesExample/build.gradle | 30 ++- .../src/main/AndroidManifest.xml | 5 +- .../coroutinesexample/MainActivity.kt | 48 +++- .../coroutinesexample/MainApplication.kt | 12 +- .../data/newsreader/local/RealmNYTDao.kt | 213 ++++++++++++++++++ .../data/newsreader/local/RealmNYTimes.kt | 74 ++++++ .../local/repository/NewsReaderRepository.kt | 122 ++++++++++ .../newsreader/network/NYTimesApiClient.kt | 72 ++++++ .../data/newsreader/network/NYTimesService.kt | 46 ++++ .../data/newsreader/network/model/NYTimes.kt | 61 +++++ .../coroutinesexample/di/DependencyGraph.kt | 83 +++++++ .../examples/coroutinesexample/model/Dog.kt | 11 - .../repository/Repository.kt | 62 ----- .../ui/details/DetailsFragment.kt | 85 +++++++ .../ui/details/DetailsViewModel.kt | 79 +++++++ .../coroutinesexample/ui/main/MainAdapter.kt | 69 ++++++ .../coroutinesexample/ui/main/MainFragment.kt | 189 ++++++++++++---- .../ui/main/MainViewModel.kt | 69 ++---- .../util/NewsReaderFlowFactory.kt | 64 ++++++ .../src/main/res/layout/activity_main.xml | 22 ++ .../src/main/res/layout/fragment_details.xml | 114 ++++++++++ .../src/main/res/layout/fragment_main.xml | 79 +++++++ .../src/main/res/layout/item_article.xml | 34 +++ .../src/main/res/layout/main_activity.xml | 7 - .../src/main/res/layout/main_fragment.xml | 64 ------ .../src/main/res/values/dimens.xml | 8 + .../src/main/res/values/strings.xml | 2 + .../src/main/res/values/styles.xml | 2 +- .../src/main/res/values/themes.xml} | 6 +- examples/newsreaderExample/build.gradle | 54 ----- examples/newsreaderExample/lint.xml | 7 - .../src/debug/AndroidManifest.xml | 6 - .../DebugNewsReaderApplication.java | 29 --- .../src/main/AndroidManifest.xml | 26 --- .../newsreader/NewsReaderApplication.java | 53 ----- .../examples/newsreader/model/Model.java | 142 ------------ .../examples/newsreader/model/Repository.java | 147 ------------ .../model/entity/NYTimesMultimedium.java | 114 ---------- .../newsreader/model/entity/NYTimesStory.java | 208 ----------------- .../model/network/NYTimesDataLoader.java | 110 --------- .../model/network/NYTimesResponse.java | 40 ---- .../model/network/NYTimesService.java | 36 --- ...almListNYTimesMultimediumDeserializer.java | 58 ----- .../examples/newsreader/ui/Presenter.java | 27 --- .../ui/details/DetailsActivity.java | 114 ---------- .../ui/details/DetailsPresenter.java | 76 ------- .../newsreader/ui/main/MainActivity.java | 168 -------------- .../newsreader/ui/main/MainPresenter.java | 110 --------- .../src/main/res/layout/activity_details.xml | 28 --- .../src/main/res/layout/activity_main.xml | 45 ---- .../src/main/res/layout/common_emptylist.xml | 7 - .../src/main/res/layout/content_details.xml | 48 ---- .../src/main/res/layout/content_main.xml | 25 -- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 4906 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2968 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 7076 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 11165 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 16078 -> 0 bytes .../src/main/res/values-w820dp/dimens.xml | 6 - .../src/main/res/values/colors.xml | 6 - .../src/main/res/values/dimens.xml | 5 - .../src/main/res/values/strings.xml | 7 - .../src/main/res/values/styles.xml | 20 -- .../src/release/AndroidManifest.xml | 6 - .../ReleaseNewsReaderApplication.java | 11 - examples/settings.gradle | 1 - .../io/realm/RealmConfigurationTests.java | 4 +- 67 files changed, 1475 insertions(+), 2001 deletions(-) create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTDao.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTimes.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/repository/NewsReaderRepository.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesApiClient.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesService.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/model/NYTimes.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/di/DependencyGraph.kt delete mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/model/Dog.kt delete mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/repository/Repository.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsFragment.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsViewModel.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainAdapter.kt create mode 100644 examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/util/NewsReaderFlowFactory.kt create mode 100644 examples/coroutinesExample/src/main/res/layout/activity_main.xml create mode 100644 examples/coroutinesExample/src/main/res/layout/fragment_details.xml create mode 100644 examples/coroutinesExample/src/main/res/layout/fragment_main.xml create mode 100644 examples/coroutinesExample/src/main/res/layout/item_article.xml delete mode 100644 examples/coroutinesExample/src/main/res/layout/main_activity.xml delete mode 100644 examples/coroutinesExample/src/main/res/layout/main_fragment.xml create mode 100644 examples/coroutinesExample/src/main/res/values/dimens.xml rename examples/{newsreaderExample/src/main/res/values-v21/styles.xml => coroutinesExample/src/main/res/values/themes.xml} (50%) delete mode 100644 examples/newsreaderExample/build.gradle delete mode 100644 examples/newsreaderExample/lint.xml delete mode 100644 examples/newsreaderExample/src/debug/AndroidManifest.xml delete mode 100644 examples/newsreaderExample/src/debug/java/io/realm/examples/newsreader/DebugNewsReaderApplication.java delete mode 100644 examples/newsreaderExample/src/main/AndroidManifest.xml delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/NewsReaderApplication.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Model.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/Repository.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/entity/NYTimesMultimedium.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/entity/NYTimesStory.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesDataLoader.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesResponse.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/NYTimesService.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/network/RealmListNYTimesMultimediumDeserializer.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/Presenter.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsActivity.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/details/DetailsPresenter.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainActivity.java delete mode 100644 examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/ui/main/MainPresenter.java delete mode 100644 examples/newsreaderExample/src/main/res/layout/activity_details.xml delete mode 100644 examples/newsreaderExample/src/main/res/layout/activity_main.xml delete mode 100644 examples/newsreaderExample/src/main/res/layout/common_emptylist.xml delete mode 100644 examples/newsreaderExample/src/main/res/layout/content_details.xml delete mode 100644 examples/newsreaderExample/src/main/res/layout/content_main.xml delete mode 100755 examples/newsreaderExample/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100755 examples/newsreaderExample/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100755 examples/newsreaderExample/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100755 examples/newsreaderExample/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100755 examples/newsreaderExample/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 examples/newsreaderExample/src/main/res/values-w820dp/dimens.xml delete mode 100644 examples/newsreaderExample/src/main/res/values/colors.xml delete mode 100644 examples/newsreaderExample/src/main/res/values/dimens.xml delete mode 100644 examples/newsreaderExample/src/main/res/values/strings.xml delete mode 100644 examples/newsreaderExample/src/main/res/values/styles.xml delete mode 100644 examples/newsreaderExample/src/release/AndroidManifest.xml delete mode 100644 examples/newsreaderExample/src/release/java/io/realm/examples/newsreader/ReleaseNewsReaderApplication.java diff --git a/examples/coroutinesExample/build.gradle b/examples/coroutinesExample/build.gradle index 83490cc5ef..8906d39073 100644 --- a/examples/coroutinesExample/build.gradle +++ b/examples/coroutinesExample/build.gradle @@ -25,6 +25,7 @@ android { minSdkVersion rootProject.minSdkVersion versionCode 1 versionName "1.0" + multiDexEnabled true testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -36,6 +37,10 @@ android { } } + buildFeatures { + dataBinding true + } + packagingOptions { exclude 'META-INF/LICENSE' } @@ -54,18 +59,37 @@ dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "androidx.constraintlayout:constraintlayout:2.0.1" - implementation "androidx.activity:activity-ktx:1.1.0" - implementation "androidx.fragment:fragment-ktx:1.2.5" implementation "androidx.appcompat:appcompat:1.2.0" + implementation "androidx.constraintlayout:constraintlayout:2.0.4" + implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" + + implementation "androidx.fragment:fragment-ktx:1.2.5" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" + implementation "androidx.legacy:legacy-support-v4:1.0.0" + + implementation "androidx.multidex:multidex:2.0.1" + + implementation "androidx.recyclerview:recyclerview:1.1.0" + + implementation "com.dropbox.mobile.store:store4:4.0.0" + + implementation "com.google.android.material:material:1.2.1" + + implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0" + implementation "com.squareup.retrofit2:retrofit:2.8.1" + implementation "com.squareup.retrofit2:converter-moshi:2.8.1" + implementation "com.squareup.okhttp3:logging-interceptor:4.9.0" + + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.4.0" + testImplementation 'junit:junit:4.13' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' diff --git a/examples/coroutinesExample/src/main/AndroidManifest.xml b/examples/coroutinesExample/src/main/AndroidManifest.xml index 3eae725358..c60aa9b2af 100644 --- a/examples/coroutinesExample/src/main/AndroidManifest.xml +++ b/examples/coroutinesExample/src/main/AndroidManifest.xml @@ -3,6 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" package="io.realm.examples.coroutinesexample"> + + - + - diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainActivity.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainActivity.kt index 488efbae97..7c0fceb194 100644 --- a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainActivity.kt +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainActivity.kt @@ -18,17 +18,55 @@ package io.realm.examples.coroutinesexample import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import io.realm.examples.coroutinesexample.ui.details.DetailsFragment import io.realm.examples.coroutinesexample.ui.main.MainFragment +import kotlin.time.ExperimentalTime -class MainActivity : AppCompatActivity() { +@ExperimentalTime +class MainActivity : AppCompatActivity(), MainFragment.OnItemClicked { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.main_activity) + + setContentView(R.layout.activity_main) + if (savedInstanceState == null) { - supportFragmentManager.beginTransaction() - .replace(R.id.container, MainFragment.newInstance()) - .commitNow() + showMainFragment() + } + } + + override fun onAttachFragment(fragment: Fragment) { + when (fragment) { + is MainFragment -> fragment.onItemclickedCallback = this + } + } + + override fun onBackPressed() { + val detailsFragment = supportFragmentManager.findFragmentByTag(DetailsFragment.TAG) + if (detailsFragment != null) { + supportFragmentManager.popBackStackImmediate() + } else { + super.onBackPressed() + } + } + + override fun onItemClicked(id: String) { + val mainFragment = supportFragmentManager.findFragmentByTag(MainFragment.TAG) + val detailsFragment = DetailsFragment.instantiate(DetailsFragment.ArgsBundle(id)) + + supportFragmentManager.commit { + setCustomAnimations(R.anim.fragment_open_enter, R.anim.fragment_open_exit) + add(R.id.container, detailsFragment, DetailsFragment.TAG) + hide(requireNotNull(mainFragment)) + addToBackStack(null) + } + } + + private fun showMainFragment() { + supportFragmentManager.commit { + replace(R.id.container, MainFragment.newInstance(), MainFragment.TAG) } } } diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainApplication.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainApplication.kt index a7bd176646..b572f53a93 100644 --- a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainApplication.kt +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/MainApplication.kt @@ -16,15 +16,23 @@ package io.realm.examples.coroutinesexample -import android.app.Application +import androidx.multidex.MultiDexApplication import io.realm.Realm +import io.realm.examples.coroutinesexample.data.newsreader.local.repository.NewsReaderRepository +import io.realm.examples.coroutinesexample.di.DependencyGraph const val TAG = "--- CoroutinesExample" -class MainApplication : Application() { +class MainApplication : MultiDexApplication() { override fun onCreate() { super.onCreate() Realm.init(this) + + repository = DependencyGraph.provideNewsReaderRepository() + } + + companion object { + lateinit var repository: NewsReaderRepository } } diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTDao.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTDao.kt new file mode 100644 index 0000000000..b73ecea339 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTDao.kt @@ -0,0 +1,213 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.data.newsreader.local + +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.RealmList +import io.realm.RealmResults +import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTMultimedium +import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTimesArticle +import io.realm.kotlin.executeTransactionAwait +import io.realm.kotlin.toFlow +import io.realm.kotlin.where +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import java.io.Closeable +import java.util.concurrent.Executors + +/** + * Data Access Object interface used to gain access to Realm. + * + * It implements [Closeable] to allow proper Realm instance housekeeping linked to handling the + * Android activity/fragment lifecycle. + */ +interface RealmNYTDao : Closeable { + suspend fun insertArticles(articles: List) + suspend fun updateArticle(id: String) + suspend fun deleteArticles(section: String) + suspend fun deleteAllArticles() + fun getArticlesBlocking(section: String): RealmResults + fun getArticles(section: String): Flow> + fun getArticleBlocking(id: String): RealmNYTimesArticle? + fun getArticle(id: String): Flow + fun countArticles(section: String): Long +} + +class RealmNYTDaoImpl( + private val realmConfiguration: RealmConfiguration +) : RealmNYTDao { + + /** + * Dispatcher used to run suspendable functions that run Realm transactions. This is needed to + * confine Realm instances within the same thread as long as the coroutine is running to avoid + * accessing said instances from different threads and thus (potentially) triggering a thread + * violation. + */ + private val monoThreadDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() + + /** + * [Realm] instance used to fire queries. It must not be used for other than firing queries and + * has to be closed when no longer in use. + */ + private val realm = Realm.getInstance(realmConfiguration) + + override suspend fun insertArticles(articles: List) { + withContext(monoThreadDispatcher) { + runCloseableTransaction(realmConfiguration) { transactionRealm -> + transactionRealm.insertOrUpdate(articles) + } + } + } + + override suspend fun updateArticle(id: String) { + withContext(monoThreadDispatcher) { + runCloseableTransaction(realmConfiguration) { transactionRealm -> + val article = transactionRealm.where() + .equalTo(RealmNYTimesArticle.COLUMN_URL, id) + .findFirst() + checkNotNull(article).read = true + } + } + } + + override suspend fun deleteArticles(section: String) { + withContext(monoThreadDispatcher) { + runCloseableTransaction(realmConfiguration) { transactionRealm -> + transactionRealm.deleteAll() + } + } + } + + override suspend fun deleteAllArticles() { + withContext(monoThreadDispatcher) { + runCloseableTransaction(realmConfiguration) { transactionRealm -> + transactionRealm.deleteAll() + } + } + } + + override fun getArticlesBlocking(section: String): RealmResults { + return realm.where() + .equalTo(RealmNYTimesArticle.COLUMN_API_SECTION, section) + .findAll() + } + + override fun getArticles(section: String): Flow> { + return realm.where() + .equalTo(RealmNYTimesArticle.COLUMN_API_SECTION, section) + .findAllAsync() + .toFlow() + } + + override fun getArticleBlocking(id: String): RealmNYTimesArticle? { + return realm.where() + .equalTo(RealmNYTimesArticle.COLUMN_URL, id) + .findFirst() + } + + override fun getArticle(id: String): Flow { + return realm.where() + .equalTo(RealmNYTimesArticle.COLUMN_URL, id) + .findFirstAsync() + .toFlow() + } + + override fun countArticles(section: String): Long { + return realm.where() + .equalTo(RealmNYTimesArticle.COLUMN_API_SECTION, section) + .count() + } + + override fun close() { + realm.close() + } +} + +/** + * Inserts a [List] of [NYTimesArticle]s after they have been mapped to [RealmNYTimesArticle] + * instances. + */ +suspend fun RealmNYTDao.insertArticles(apiSection: String, articles: List) { + val realmArticles = articles.toRealmArticles(apiSection) + insertArticles(realmArticles) +} + +private fun List.toRealmArticles(apiQuerySection: String): List { + val timestamp = System.currentTimeMillis() + return map { article -> + RealmNYTimesArticle().apply { + updateTime = timestamp + apiSection = apiQuerySection + section = article.section + subsection = article.subsection + title = article.title + abstractText = article.abstractText + url = article.url + uri = article.uri + byline = article.byline + itemType = article.itemType + updatedDate = article.updatedDate + createDate = article.createDate + publishedDate = article.publishedDate + materialTypeFacet = article.materialTypeFacet + kicker = article.kicker + desFacet = RealmList().apply { addAll(article.desFacet ?: listOf()) } + orgFacet = RealmList().apply { addAll(article.orgFacet ?: listOf()) } + perFacet = RealmList().apply { addAll(article.perFacet ?: listOf()) } + geoFacet = RealmList().apply { addAll(article.geoFacet ?: listOf()) } + orgFacet = RealmList().apply { addAll(article.orgFacet ?: listOf()) } + perFacet = RealmList().apply { addAll(article.perFacet ?: listOf()) } + geoFacet = RealmList().apply { addAll(article.geoFacet ?: listOf()) } + multimedia = article.multimedia.toRealmMultimediumRealmList() + shortUrl = article.shortUrl + } + } +} + +private fun List?.toRealmMultimediumRealmList(): RealmList { + return RealmList().also { realmList -> + realmList.addAll( + this?.map { multimedium -> + multimedium.toRealmMultimedium() + } ?: listOf() + ) + } +} + +private fun NYTMultimedium.toRealmMultimedium(): RealmNYTMultimedium { + return RealmNYTMultimedium().also { + it.url = url + it.format = format + it.height = height + it.width = width + it.type = type + it.subtype = subtype + it.caption = caption + it.copyright = copyright + } +} + +private suspend fun runCloseableTransaction( + realmConfiguration: RealmConfiguration, + transaction: (realm: Realm) -> Unit +) { + Realm.getInstance(realmConfiguration).use { realmInstance -> + realmInstance.executeTransactionAwait(transaction = transaction) + } +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTimes.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTimes.kt new file mode 100644 index 0000000000..4f7c442af5 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/RealmNYTimes.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.data.newsreader.local + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.LinkingObjects +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmClass +import java.time.LocalDateTime +import java.util.* + +open class RealmNYTimesArticle : RealmObject() { + var read: Boolean = false + var updateTime: Long = 0 + var apiSection: String = "" + var section: String = "" + var subsection: String? = null + var title: String = "" + var abstractText: String? = null + + @PrimaryKey + var url: String = UUID.randomUUID().toString() + + var uri: String? = null + var byline: String? = null + var itemType: String? = null + var updatedDate: String? = null + var createDate: String? = null + var publishedDate: String? = null + var materialTypeFacet: String? = null + var kicker: String? = null + var desFacet: RealmList = RealmList() + var orgFacet: RealmList = RealmList() + var perFacet: RealmList = RealmList() + var geoFacet: RealmList = RealmList() + var multimedia: RealmList = RealmList() + var shortUrl: String? = null + + companion object { + const val EMBEDDED_MULTIMEDIA = "multimedia" + const val COLUMN_URL = "url" + const val COLUMN_API_SECTION = "apiSection" + } +} + +@RealmClass(embedded = true) +open class RealmNYTMultimedium : RealmObject() { + var url: String? = null + var format: String? = null + var height: Int = 0 + var width: Int = 0 + var type: String? = null + var subtype: String? = null + var caption: String? = null + var copyright: String? = null + + @LinkingObjects(RealmNYTimesArticle.EMBEDDED_MULTIMEDIA) + val parent = RealmNYTimesArticle() +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/repository/NewsReaderRepository.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/repository/NewsReaderRepository.kt new file mode 100644 index 0000000000..64d6f57101 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/local/repository/NewsReaderRepository.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.data.newsreader.local.repository + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.dropbox.android.external.store4.* +import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTDao +import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle +import io.realm.examples.coroutinesexample.ui.main.NewsReaderState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +private const val THIRTY_MINUTES = 30 * 60 * 1000 + +class NewsReaderRepository( + private val dao: RealmNYTDao, + private val store: Store> +) { + + private val _newsReaderState = MutableLiveData() + val newsReaderState: LiveData + get() = _newsReaderState + + private val sectionRefreshJobs = mutableMapOf() + + fun getTopStories(scope: CoroutineScope, apiSection: String, refresh: Boolean = false) { + scope.launch { + if (refresh) { + store.fresh(apiSection) + } else { + getFromStream(scope, apiSection) + } + } + } + + fun getStory(id: String): Flow { + return dao.getArticle(id) + } + + fun updateArticle(scope: CoroutineScope, id: String) { + scope.launch { + dao.updateArticle(id) + } + } + + fun close() { + dao.close() + sectionRefreshJobs.values.forEach { it.cancel() } + sectionRefreshJobs.clear() + } + + private fun getFromStream(scope: CoroutineScope, apiSection: String) { + store.stream(StoreRequest.cached( + key = apiSection, + refresh = false + )).onEach { response -> + val origin = response.origin.toString() + when (response) { + is StoreResponse.Loading -> NewsReaderState.Loading(origin) + is StoreResponse.Data -> getNewsReaderState(response, store, apiSection, origin) + is StoreResponse.NoNewData -> NewsReaderState.NoNewData(origin) + is StoreResponse.Error.Exception -> NewsReaderState.ErrorException(origin, response.error) + is StoreResponse.Error.Message -> NewsReaderState.ErrorMessage(origin, response.message) + }.also { + _newsReaderState.postValue(it) + } + }.launchIn( + scope + ).also { job -> + scope.launch { + sectionRefreshJobs.values.forEach { it.cancelAndJoin() } + sectionRefreshJobs.clear() + sectionRefreshJobs[apiSection] = job + } + } + } + + private suspend fun getNewsReaderState( + response: StoreResponse.Data>, + store: Store>, + apiSection: String, + origin: String + ): NewsReaderState { + val data = response.value + return if (data.isNotEmpty()) { + data.first() + .let { firstElement -> + val now = System.currentTimeMillis() + val entryExpired = (firstElement.updateTime + THIRTY_MINUTES) < now + + if (!(response.origin == ResponseOrigin.Fetcher || !entryExpired)) { + store.fresh(apiSection) + NewsReaderState.Loading(origin) + } else { + NewsReaderState.Data(origin, response.value) + } + } + } else { + NewsReaderState.Data(origin, response.value) + } + } +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesApiClient.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesApiClient.kt new file mode 100644 index 0000000000..c88d245275 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesApiClient.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.data.newsreader.network + +import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTimesResponse +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.moshi.MoshiConverterFactory + +private const val API_KEY = "YUPmyj0Q09Fm2VlCHmD9FU7rpCcI5dUD" + +interface NYTimesApiClient { + suspend fun getTopStories(section: String): NYTimesResponse +} + +class NYTimesApiClientImpl : NYTimesApiClient { + + private val service: NYTimesService + + init { + val okHttpClient = OkHttpClient.Builder() + .addInterceptor( + HttpLoggingInterceptor() + .apply { setLevel(HttpLoggingInterceptor.Level.BASIC) } + ) + .addInterceptor { chain -> + val original = chain.request() + val originalHttpUrl = original.url + + val url = originalHttpUrl.newBuilder() + .addEncodedQueryParameter("api-key", API_KEY) + .build() + + val requestBuilder: Request.Builder = original.newBuilder() + .url(url) + + val request: Request = requestBuilder.build() + chain.proceed(request) + } + .build() + + service = Retrofit.Builder() + .client(okHttpClient) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(MoshiConverterFactory.create()) + .baseUrl("https://api.nytimes.com/") + .build() + .create(NYTimesService::class.java) + } + + override suspend fun getTopStories(section: String): NYTimesResponse { + return service.topStories(section) + } +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesService.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesService.kt new file mode 100644 index 0000000000..97eec6c08a --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/NYTimesService.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.data.newsreader.network + +import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTimesResponse +import retrofit2.http.GET +import retrofit2.http.Path + +interface NYTimesService { + @GET("svc/topstories/v2/{section}.json") + suspend fun topStories(@Path("section") section: String): NYTimesResponse +} + +val sectionsToNames = mapOf( + "Home" to "home", + "World" to "world", + "National" to "national", + "Politics" to "politics", + "NY Region" to "nyregion", + "Business" to "business", + "Opinion" to "opinion", + "Technology" to "technology", + "Science" to "science", + "Health" to "health", + "Sports" to "sports", + "Arts" to "arts", + "Fashion" to "fashion", + "Dining" to "dining", + "Travel" to "travel", + "Magazine" to "magazine", + "Real Estate" to "realestate" +) diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/model/NYTimes.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/model/NYTimes.kt new file mode 100644 index 0000000000..893a8eb7b8 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/data/newsreader/network/model/NYTimes.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.data.newsreader.network.model + +import com.squareup.moshi.Json + +data class NYTimesResponse( + val status: String, + val copyright: String, + val section: String, + @field:Json(name = "last_updated") val lastUpdated: String, + @field:Json(name = "num_results") val numResults: Int, + val results: List +) + +data class NYTimesArticle( + val section: String, + val subsection: String, + val title: String, + @field:Json(name = "abstract") val abstractText: String?, + val url: String, + val uri: String, + val byline: String, + @field:Json(name = "item_type") val itemType: String?, + @field:Json(name = "updated_date") val updatedDate: String?, + @field:Json(name = "created_date") val createDate: String?, + @field:Json(name = "published_date") val publishedDate: String?, + @field:Json(name = "material_type_facet") val materialTypeFacet: String?, + val kicker: String, + @field:Json(name = "des_facet") val desFacet: List?, + @field:Json(name = "org_facet") val orgFacet: List?, + @field:Json(name = "per_facet") val perFacet: List?, + @field:Json(name = "geo_facet") val geoFacet: List?, + val multimedia: List, + @field:Json(name = "short_url") val shortUrl: String? +) + +data class NYTMultimedium( + val url: String, + val format: String, + val height: Int, + val width: Int, + val type: String, + val subtype: String, + val caption: String, + val copyright: String +) diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/di/DependencyGraph.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/di/DependencyGraph.kt new file mode 100644 index 0000000000..180b3c15e9 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/di/DependencyGraph.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.di + +import com.dropbox.android.external.store4.Fetcher +import com.dropbox.android.external.store4.SourceOfTruth +import com.dropbox.android.external.store4.Store +import com.dropbox.android.external.store4.StoreBuilder +import io.realm.RealmConfiguration +import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTDao +import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTDaoImpl +import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle +import io.realm.examples.coroutinesexample.data.newsreader.local.insertArticles +import io.realm.examples.coroutinesexample.data.newsreader.local.repository.NewsReaderRepository +import io.realm.examples.coroutinesexample.data.newsreader.network.NYTimesApiClient +import io.realm.examples.coroutinesexample.data.newsreader.network.NYTimesApiClientImpl +import io.realm.examples.coroutinesexample.data.newsreader.network.model.NYTimesArticle +import io.realm.examples.coroutinesexample.util.NewsReaderFlowFactory +import kotlinx.coroutines.flow.map + +/** + * Homemade, simple DI solution - ideally, we should use a proper DI framework instead. + */ +object DependencyGraph { + + // Repository dependencies + fun provideNewsReaderRepository(): NewsReaderRepository = + NewsReaderRepository(provideRealmDao(), provideStore()) + + private fun provideStore(): Store> = StoreBuilder.from( + fetcher = provideFetcher(provideApiClient()), + sourceOfTruth = provideSourceOfTruth(provideRealmDao()) + ).build() + + private fun provideFetcher(nytApiClient: NYTimesApiClient): Fetcher> = + Fetcher.of { apiSection -> + nytApiClient.getTopStories(apiSection).results + } + + private fun provideSourceOfTruth(realmDao: RealmNYTDao): SourceOfTruth, List> = + SourceOfTruth.of( + reader = { apiSection -> + realmDao.getArticles(apiSection) + .map { articles -> + if (articles.isEmpty()) null + else articles + } + }, + writer = { apiSection, articles -> + realmDao.insertArticles(apiSection, articles) + }, + delete = { apiSection -> + realmDao.deleteArticles(apiSection) + }, + deleteAll = { + realmDao.deleteAllArticles() + } + ) + + // Database dependencies + private fun provideRealmDao(): RealmNYTDao = RealmNYTDaoImpl(provideRealmConfig()) + + private fun provideRealmConfig(): RealmConfiguration = RealmConfiguration.Builder() + .flowFactory(NewsReaderFlowFactory()) + .build() + + // Network dependencies + private fun provideApiClient(): NYTimesApiClient = NYTimesApiClientImpl() +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/model/Dog.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/model/Dog.kt deleted file mode 100644 index 0aa35bea25..0000000000 --- a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/model/Dog.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.realm.examples.coroutinesexample.model - -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey - -open class Dog : RealmObject() { - @PrimaryKey - var name: String = "" - var age: Int = 1 - var owner: String = "" -} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/repository/Repository.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/repository/Repository.kt deleted file mode 100644 index c69ac95a09..0000000000 --- a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/repository/Repository.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.examples.coroutinesexample.repository - -import android.util.Log -import io.realm.Realm -import io.realm.examples.coroutinesexample.TAG -import io.realm.examples.coroutinesexample.model.Dog -import io.realm.kotlin.executeTransactionAwait -import io.realm.kotlin.toFlow -import io.realm.kotlin.where -import kotlinx.coroutines.flow.Flow - -interface Repository { - suspend fun insertDogs(number: Int) - suspend fun deleteDogs() - fun getDogs(): Flow> - fun countDogs(): Long -} - -class RealmRepository(private val realm: Realm) : Repository { - - override suspend fun insertDogs(number: Int) { - realm.executeTransactionAwait { transactionRealm -> - Log.d(TAG, "--- BEFORE") - for (i in 1..number) { - transactionRealm.insertOrUpdate(Dog().apply { - name = "Mr. Snuffles $i" - age = 1 - owner = "Mortimer Smith" - }) - } - Log.d(TAG, "--- AFTER") - } - } - - override suspend fun deleteDogs() { - realm.executeTransactionAwait { transactionRealm -> - transactionRealm.deleteAll() - } - } - - override fun getDogs(): Flow> = realm.where(Dog::class.java) - .findAllAsync() - .toFlow() - - override fun countDogs(): Long = realm.where().count() -} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsFragment.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsFragment.kt new file mode 100644 index 0000000000..227ef78d3a --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsFragment.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.ui.details + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import io.realm.examples.coroutinesexample.databinding.FragmentDetailsBinding +import kotlin.time.ExperimentalTime + +@ExperimentalTime +class DetailsFragment : Fragment() { + + private val viewModel: DetailsViewModel by viewModels() + + private lateinit var binding: FragmentDetailsBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return FragmentDetailsBinding.inflate(inflater, container, false) + .also { binding -> + this.binding = binding + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + setupLiveData() + }.root + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + val id = requireNotNull(requireArguments().getString(ARG_ID)) + viewModel.loadDetails(id) + } + + private fun setupLiveData() { + viewModel.read.observe(viewLifecycleOwner, Observer { + setRead() + }) + } + + private fun setRead() { + with(binding.read) { + animate().alpha(1.0f) + } + } + + data class ArgsBundle(val id: String) + + companion object { + + const val TAG = "DetailsFragment" + + private const val ARG_ID = "id" + + fun instantiate(argsBundle: ArgsBundle): DetailsFragment { + return DetailsFragment().apply { + arguments = Bundle().apply { + putString(ARG_ID, argsBundle.id) + } + } + } + } +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsViewModel.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsViewModel.kt new file mode 100644 index 0000000000..45fb32ee00 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/details/DetailsViewModel.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.ui.details + +import androidx.lifecycle.* +import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle +import io.realm.examples.coroutinesexample.di.DependencyGraph +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.isActive +import kotlin.time.ExperimentalTime +import kotlin.time.seconds + +@ExperimentalTime +class DetailsViewModel : ViewModel() { + + private val repository = DependencyGraph.provideNewsReaderRepository() + + private val article = MutableLiveData() + + private val _read = MutableLiveData() + val read: LiveData + get() = _read + + val date = article.map { it.updatedDate.toString() } + val title = article.map { it.title } + val articleText = article.map { it.abstractText } + + override fun onCleared() { + repository.close() + } + + fun loadDetails(id: String) { + repository.getStory(id) + .onEach { realmArticle -> + checkNotNull(realmArticle) + .also { + if (article.value == null) { + article.postValue(it) + + if (!it.read) { + markAsRead(it) + } else { + markAsRead(it, true) + } + } + } + }.launchIn(viewModelScope) + } + + private fun markAsRead(article: RealmNYTimesArticle, immediately: Boolean = false) { + if (immediately) { + _read.postValue(true) + } else { + flow { + delay(2.seconds) + repository.updateArticle(viewModelScope, article.url) + _read.postValue(true) + }.launchIn(viewModelScope) + } + } +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainAdapter.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainAdapter.kt new file mode 100644 index 0000000000..9348960709 --- /dev/null +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainAdapter.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2020 Realm Inc. + * + * 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. + */ + +package io.realm.examples.coroutinesexample.ui.main + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import io.realm.examples.coroutinesexample.R +import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle + +class MainAdapter( + private val onClick: (String) -> Unit +) : ListAdapter(DIFF_CALLBACK) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder = + LayoutInflater.from(parent.context) + .inflate(R.layout.item_article, parent, false) + .let { view -> ArticleViewHolder(view) } + + override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) { + with(holder.title) { + val article = getItem(position) + + text = article.title + isEnabled = !article.read + } + } + + inner class ArticleViewHolder(view: View) : RecyclerView.ViewHolder(view) { + + val title: TextView = view.findViewById(R.id.title) + + init { + view.setOnClickListener { + onClick.invoke(getItem(adapterPosition).url) + } + } + } + + companion object { + val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: RealmNYTimesArticle, newItem: RealmNYTimesArticle): Boolean = + oldItem == newItem + + override fun areContentsTheSame(oldItem: RealmNYTimesArticle, newItem: RealmNYTimesArticle): Boolean = + oldItem.read == newItem.read + && oldItem.title == newItem.title + && oldItem.abstractText == newItem.abstractText + } + } +} diff --git a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainFragment.kt b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainFragment.kt index b59e03c414..e5e5fb5e79 100644 --- a/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainFragment.kt +++ b/examples/coroutinesExample/src/main/java/io/realm/examples/coroutinesexample/ui/main/MainFragment.kt @@ -21,76 +21,175 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Button +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.SpinnerAdapter +import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager import io.realm.examples.coroutinesexample.R -import io.realm.examples.coroutinesexample.TAG -import io.realm.examples.coroutinesexample.model.Dog +import io.realm.examples.coroutinesexample.data.newsreader.local.RealmNYTimesArticle +import io.realm.examples.coroutinesexample.data.newsreader.network.sectionsToNames +import io.realm.examples.coroutinesexample.databinding.FragmentMainBinding +import java.util.* +import kotlin.Comparator class MainFragment : Fragment() { - companion object { - fun newInstance() = MainFragment() + interface OnItemClicked { + fun onItemClicked(id: String) } - private val viewModel: MainViewModel by viewModels() + internal lateinit var onItemclickedCallback: OnItemClicked - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { - return inflater.inflate(R.layout.main_fragment, container, false) - .also { view -> addClickListeners(view) } + private val viewModel: MainViewModel by viewModels() + private val newsReaderAdapter = MainAdapter { id -> + onItemclickedCallback.onItemClicked(id) } - override fun onResume() { - super.onResume() - addObservers() - } + private lateinit var binding: FragmentMainBinding - override fun onPause() { - super.onPause() - removeObservers() - } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = FragmentMainBinding.inflate(inflater, container, false) + .also { binding -> + binding.lifecycleOwner = viewLifecycleOwner + this.binding = binding + setupSpinner() + setupRecyclerView() + setupLiveData() + }.root - private fun addClickListeners(view: View) { - view.findViewById

    * Note that this can be used to check the validity of certain conditions such as being {@code null} * when observed. @@ -126,7 +126,7 @@ public static void deleteFromRealm(E object) { * } * * - * @return {@code true} if the object is still accessible, {@code false} otherwise or if it is an unmanaged object. + * @return {@code true} if the object is still accessible or an unmanaged object, {@code false} otherwise. * @see Examples using Realm with RxJava */ public final boolean isValid() { @@ -135,10 +135,10 @@ public final boolean isValid() { /** * Checks if the RealmObject is still valid to use i.e., the RealmObject hasn't been deleted nor has the - * {@link io.realm.Realm} been closed. It will always return {@code false} for unmanaged objects. + * {@link io.realm.Realm} been closed. It will always return {@code true} for unmanaged objects. * * @param object RealmObject to check validity for. - * @return {@code true} if the object is still accessible, {@code false} otherwise or if it is an unmanaged object. + * @return {@code true} if the object is still accessible or an unmanaged object, {@code false} otherwise. */ public static boolean isValid(E object) { if (object instanceof RealmObjectProxy) { @@ -146,7 +146,7 @@ public static boolean isValid(E object) { Row row = proxy.realmGet$proxyState().getRow$realm(); return row != null && row.isAttached(); } else { - return false; + return true; } } @@ -241,6 +241,52 @@ public static boolean isLoaded(E object) { } } + /** + * Checks if this object is managed by Realm. A managed object is just a wrapper around the data in the underlying + * Realm file. On Looper threads, a managed object will be live-updated so it always points to the latest data. It + * is possible to register a change listener using {@link #addChangeListener(RealmChangeListener)} to be notified + * when changes happen. Managed objects are thread confined so that they cannot be accessed from other threads than + * the one that created them. + *